|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | + |
| 3 | +//! RNG driver for Exynos TRNGs |
| 4 | +//! |
| 5 | +//! Author: Maciej Falkowski <[email protected]> |
| 6 | +//! |
| 7 | +//! Copyright 2021 (c) Samsung Electronics Software, Inc. |
| 8 | +//! |
| 9 | +//! Based on the Exynos TRNG driver drivers/char/hw_random/exynos-rng by |
| 10 | +//! Łukasz Stelmach <[email protected]> |
| 11 | +
|
| 12 | +#![no_std] |
| 13 | +#![feature(allocator_api, global_asm)] |
| 14 | + |
| 15 | +use core::{cmp::min, convert::TryInto, str}; |
| 16 | + |
| 17 | +use kernel::{ |
| 18 | + c_str, |
| 19 | + clk::{self, Clk}, |
| 20 | + declare_hwrng_operations, |
| 21 | + device::RawDevice, |
| 22 | + hw_random::{self, HwrngOperations}, |
| 23 | + io_mem::{self, IoMem}, |
| 24 | + of::ConstOfMatchTable, |
| 25 | + platdev::{self, PlatformDevice, PlatformDriver}, |
| 26 | + prelude::*, |
| 27 | +}; |
| 28 | + |
| 29 | +module! { |
| 30 | + type: ExynosTrngDriverModule, |
| 31 | + name: b"exynos_trng_rust", |
| 32 | + author : b"Maciej Falkowski <[email protected]>", |
| 33 | + description: b"RNG driver for Exynos TRNGs", |
| 34 | + license: b"GPL v2", |
| 35 | +} |
| 36 | + |
| 37 | +const EXYNOS_TRNG_CLKDIV: usize = 0x0; |
| 38 | + |
| 39 | +const EXYNOS_TRNG_CTRL: usize = 0x20; |
| 40 | +const EXYNOS_TRNG_CTRL_RNGEN: u32 = 0x1 << 31; // BIT(31) |
| 41 | + |
| 42 | +const EXYNOS_REG_SIZE: usize = 0x100; |
| 43 | +const EXYNOS_TRNG_POST_CTRL: usize = 0x30; |
| 44 | +const _EXYNOS_TRNG_ONLINE_CTRL: usize = 0x40; |
| 45 | +const _EXYNOS_TRNG_ONLINE_STAT: usize = 0x44; |
| 46 | +const _EXYNOS_TRNG_ONLINE_MAXCHI2: usize = 0x48; |
| 47 | +const EXYNOS_TRNG_FIFO_CTRL: usize = 0x50; |
| 48 | +const EXYNOS_TRNG_FIFO_0: usize = 0x80; |
| 49 | +const _EXYNOS_TRNG_FIFO_1: usize = 0x84; |
| 50 | +const _EXYNOS_TRNG_FIFO_2: usize = 0x88; |
| 51 | +const _EXYNOS_TRNG_FIFO_3: usize = 0x8c; |
| 52 | +const _EXYNOS_TRNG_FIFO_4: usize = 0x90; |
| 53 | +const _EXYNOS_TRNG_FIFO_5: usize = 0x94; |
| 54 | +const _EXYNOS_TRNG_FIFO_6: usize = 0x98; |
| 55 | +const _EXYNOS_TRNG_FIFO_7: usize = 0x9c; |
| 56 | +const EXYNOS_TRNG_FIFO_LEN: u32 = 8; |
| 57 | +const EXYNOS_TRNG_CLOCK_RATE: u32 = 500000; |
| 58 | + |
| 59 | +struct ExynosTrngDevice { |
| 60 | + clk: Clk, |
| 61 | + mem: IoMem<EXYNOS_REG_SIZE>, |
| 62 | +} |
| 63 | + |
| 64 | +impl HwrngOperations for ExynosTrngDevice { |
| 65 | + declare_hwrng_operations!(init); |
| 66 | + |
| 67 | + type Data = Box<ExynosTrngDevice>; |
| 68 | + |
| 69 | + fn init(trng: &Self) -> Result { |
| 70 | + let sss_rate = trng.clk.get_rate(); |
| 71 | + |
| 72 | + // For most TRNG circuits the clock frequency of under 500 kHz |
| 73 | + // is safe. |
| 74 | + let mut val = sss_rate / (EXYNOS_TRNG_CLOCK_RATE * 2); |
| 75 | + if val > 0x7fff { |
| 76 | + // dev_err(trng->dev, "clock divider too large: %d", val); |
| 77 | + pr_info!("rust_hwrng_init(): clock divider too large: {}\n", val); |
| 78 | + return Err(Error::ERANGE); |
| 79 | + } |
| 80 | + |
| 81 | + val <<= 1; |
| 82 | + trng.mem.writel_relaxed(val, EXYNOS_TRNG_CLKDIV); |
| 83 | + |
| 84 | + // Enable the generator. |
| 85 | + val = EXYNOS_TRNG_CTRL_RNGEN; |
| 86 | + trng.mem.writel_relaxed(val, EXYNOS_TRNG_CTRL); |
| 87 | + |
| 88 | + // Disable post-processing. /dev/hwrng is supposed to deliver |
| 89 | + // unprocessed data. |
| 90 | + trng.mem.writel_relaxed(0, EXYNOS_TRNG_POST_CTRL); |
| 91 | + |
| 92 | + Ok(()) |
| 93 | + } |
| 94 | + |
| 95 | + fn read(trng: &Self, data: &mut [i8], _wait: bool) -> Result<i32> { |
| 96 | + let max: u32 = min(data.len().try_into()?, EXYNOS_TRNG_FIFO_LEN * 4); |
| 97 | + |
| 98 | + trng.mem.writel_relaxed(max * 8, EXYNOS_TRNG_FIFO_CTRL); |
| 99 | + |
| 100 | + let _ = |
| 101 | + trng.mem |
| 102 | + .readl_poll_timeout(EXYNOS_TRNG_FIFO_CTRL, |val| *val == 0, 200, 1000000)?; |
| 103 | + |
| 104 | + io_mem::try_memcpy_fromio(&trng.mem, data, EXYNOS_TRNG_FIFO_0, max.try_into()?)?; |
| 105 | + |
| 106 | + Ok(max as i32) |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +struct ExynosTrngDriver; |
| 111 | + |
| 112 | +impl PlatformDriver for ExynosTrngDriver { |
| 113 | + type DrvData = Pin<Box<hw_random::Registration<ExynosTrngDevice>>>; |
| 114 | + |
| 115 | + fn probe(pdev: &mut PlatformDevice) -> Result<Self::DrvData> { |
| 116 | + let mut clk = clk::devm_clk_get(pdev, c_str!("secss"))?; |
| 117 | + clk.prepare_enable()?; |
| 118 | + |
| 119 | + let mem = IoMem::<EXYNOS_REG_SIZE>::try_platform_ioremap_resource(pdev, 0)?; |
| 120 | + |
| 121 | + let exynos_trng = Box::try_new(ExynosTrngDevice { clk, mem })?; |
| 122 | + |
| 123 | + let drv_data = hw_random::Registration::new_pinned(pdev.name(), 0, exynos_trng)?; |
| 124 | + |
| 125 | + Ok(drv_data) |
| 126 | + } |
| 127 | + |
| 128 | + fn remove(_pdev: &mut PlatformDevice, _drv_data: Self::DrvData) -> Result { |
| 129 | + Ok(()) |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +struct ExynosTrngDriverModule { |
| 134 | + _pdev: Pin<Box<platdev::Registration>>, |
| 135 | +} |
| 136 | + |
| 137 | +impl KernelModule for ExynosTrngDriverModule { |
| 138 | + fn init(name: &'static CStr, module: &'static ThisModule) -> Result<Self> { |
| 139 | + const OF_MATCH_TBL: ConstOfMatchTable<1> = |
| 140 | + ConstOfMatchTable::new_const([c_str!("samsung,exynos5250-trng")]); |
| 141 | + |
| 142 | + let pdev = platdev::Registration::new_pinned::<ExynosTrngDriver>( |
| 143 | + name, |
| 144 | + Some(&OF_MATCH_TBL), |
| 145 | + module, |
| 146 | + )?; |
| 147 | + |
| 148 | + Ok(ExynosTrngDriverModule { _pdev: pdev }) |
| 149 | + } |
| 150 | +} |
0 commit comments