devices/
smccc_trng.rs

1// Copyright 2025 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::ops::Range;
6
7use anyhow::bail;
8use base::fold_into_i32;
9use base::warn;
10use base::NegativeI32;
11use base::U31;
12use hypervisor::HypercallAbi;
13use static_assertions::const_assert_ne;
14use uuid::uuid;
15use uuid::Uuid;
16use vm_control::DeviceId;
17use vm_control::PlatformDeviceId;
18
19use crate::BusAccessInfo;
20use crate::BusDevice;
21use crate::BusDeviceSync;
22use crate::Suspendable;
23
24/// Error codes, as defined by the SMCCC TRNG (DEN0098).
25///
26/// Note that values are strictly positive but API return values are strictly negative.
27#[repr(i32)]
28#[derive(Clone, Copy, Debug)]
29enum SmcccTrngError {
30    NotSupported = -1,
31    InvalidParameters = -2,
32    #[allow(unused)]
33    NoEntropy = -3,
34}
35
36impl SmcccTrngError {
37    const fn as_i32(&self) -> i32 {
38        *self as _
39    }
40}
41
42impl From<SmcccTrngError> for i32 {
43    fn from(e: SmcccTrngError) -> Self {
44        e.as_i32()
45    }
46}
47
48impl From<SmcccTrngError> for NegativeI32 {
49    fn from(e: SmcccTrngError) -> Self {
50        Self::new(e.as_i32()).unwrap()
51    }
52}
53
54/// An backend implementation of the SMCCC TRNG (DEN0098).
55///
56/// Might not be fully spec-compliant regarding the conditioned entropy.
57pub struct SmcccTrng {}
58
59impl Default for SmcccTrng {
60    fn default() -> Self {
61        Self::new()
62    }
63}
64
65impl SmcccTrng {
66    /// Standard function ID ranges for TRNG 32-bit calls, defined in SMCCC (DEN0028).
67    pub const HVC32_FID_RANGE: Range<u32> = 0x8400_0050..0x8400_0060;
68    /// Standard function ID ranges for TRNG 64-bit calls, defined in SMCCC (DEN0028).
69    pub const HVC64_FID_RANGE: Range<u32> = 0xC400_0050..0xC400_0060;
70
71    const FID_TRNG_VERSION: u32 = 0x8400_0050;
72    const FID_TRNG_FEATURES: u32 = 0x8400_0051;
73    const FID_TRNG_GET_UUID: u32 = 0x8400_0052;
74    const FID_TRNG_RND32: u32 = 0x8400_0053;
75    const FID_TRNG_RND64: u32 = 0xC400_0053;
76
77    const VERSION: (u16, u16) = (1, 0);
78    /// CrosVM SMCCC TRNG back-end UUID.
79    ///
80    /// Equivalent to `Uuid::new_v8(*b"SMCCCTRNG-CrosVM")`.
81    const UUID: Uuid = uuid!("534d4343-4354-824e-872d-43726f73564d");
82
83    /// Creates a new instance of `SmcccTrng`.
84    pub fn new() -> Self {
85        Self {}
86    }
87
88    fn version(&self) -> Result<U31, SmcccTrngError> {
89        Ok(U31::new(((Self::VERSION.0 as u32) << 16) | (Self::VERSION.1 as u32)).unwrap())
90    }
91
92    fn features(&self, func_id: u32) -> Result<U31, SmcccTrngError> {
93        const AVAILABLE: U31 = U31::new(0).unwrap();
94        match func_id {
95            Self::FID_TRNG_VERSION => Ok(AVAILABLE),
96            Self::FID_TRNG_FEATURES => Ok(AVAILABLE),
97            Self::FID_TRNG_GET_UUID => Ok(AVAILABLE),
98            Self::FID_TRNG_RND32 => Ok(AVAILABLE),
99            Self::FID_TRNG_RND64 => Ok(AVAILABLE),
100            _ => Err(SmcccTrngError::NotSupported),
101        }
102    }
103
104    fn get_uuid(&self) -> Result<[u32; 4], SmcccTrngError> {
105        const UUID: u128 = SmcccTrng::UUID.to_u128_le();
106        const R3: u32 = (UUID >> (3 * u32::BITS)) as _;
107        const R2: u32 = (UUID >> (2 * u32::BITS)) as _;
108        const R1: u32 = (UUID >> u32::BITS) as _;
109        const R0: u32 = UUID as _;
110        // Otherwise return would be indistinguishable from SMCCC's NOT_SUPPORTED
111        const_assert_ne!(R0, u32::MAX);
112
113        Ok([R0, R1, R2, R3])
114    }
115
116    fn rnd32(&self, n_bits: u32) -> Result<[u32; 3], SmcccTrngError> {
117        match n_bits.div_ceil(u32::BITS) {
118            1 => Ok([rand::random(), 0, 0]),
119            2 => Ok([rand::random(), rand::random(), 0]),
120            3 => Ok([rand::random(), rand::random(), rand::random()]),
121            n => {
122                warn!("SMCCC TRNG: Invalid request for {n} u32 words");
123                Err(SmcccTrngError::InvalidParameters)
124            }
125        }
126    }
127
128    fn rnd64(&self, n_bits: u64) -> Result<[u64; 3], SmcccTrngError> {
129        match n_bits.div_ceil(u64::BITS.into()) {
130            1 => Ok([rand::random(), 0, 0]),
131            2 => Ok([rand::random(), rand::random(), 0]),
132            3 => Ok([rand::random(), rand::random(), rand::random()]),
133            n => {
134                warn!("SMCCC TRNG: Invalid request for {n} u64 words");
135                Err(SmcccTrngError::InvalidParameters)
136            }
137        }
138    }
139}
140
141fn as_signed_usize(i: i32) -> usize {
142    let sign_extended = i64::from(i);
143    (sign_extended as u64).try_into().unwrap()
144}
145
146impl BusDevice for SmcccTrng {
147    fn device_id(&self) -> DeviceId {
148        PlatformDeviceId::SmcccTrng.into()
149    }
150
151    fn debug_label(&self) -> String {
152        "SmcccTrng".to_owned()
153    }
154
155    fn handle_hypercall(&self, abi: &mut HypercallAbi) -> anyhow::Result<()> {
156        let regs = match abi.hypercall_id() as u32 {
157            Self::FID_TRNG_VERSION => {
158                let r0 = as_signed_usize(fold_into_i32(self.version()));
159                [r0, 0, 0, 0]
160            }
161            Self::FID_TRNG_FEATURES => {
162                let feat = (*abi.get_argument(0).unwrap()) as u32;
163                let r0 = as_signed_usize(fold_into_i32(self.features(feat)));
164                [r0, 0, 0, 0]
165            }
166            Self::FID_TRNG_GET_UUID => match self.get_uuid() {
167                Ok(uuid) => [
168                    uuid[0].try_into().unwrap(),
169                    uuid[1].try_into().unwrap(),
170                    uuid[2].try_into().unwrap(),
171                    uuid[3].try_into().unwrap(),
172                ],
173                Err(e) => [as_signed_usize(e.into()), 0, 0, 0],
174            },
175            Self::FID_TRNG_RND32 => {
176                let n_bits = (*abi.get_argument(0).unwrap()) as u32;
177                match self.rnd32(n_bits) {
178                    Ok(entropy) => [
179                        0,
180                        entropy[0].try_into().unwrap(),
181                        entropy[1].try_into().unwrap(),
182                        entropy[2].try_into().unwrap(),
183                    ],
184                    Err(e) => [as_signed_usize(e.into()), 0, 0, 0],
185                }
186            }
187            Self::FID_TRNG_RND64 => {
188                let n_bits = (*abi.get_argument(0).unwrap()).try_into().unwrap();
189                match self.rnd64(n_bits) {
190                    Ok(entropy) => [
191                        0,
192                        entropy[0].try_into().unwrap(),
193                        entropy[1].try_into().unwrap(),
194                        entropy[2].try_into().unwrap(),
195                    ],
196                    Err(e) => [as_signed_usize(e.into()), 0, 0, 0],
197                }
198            }
199            fid => bail!("SmcccTrng: Call {fid:#x} is not implemented"),
200        };
201        abi.set_results(&regs);
202        Ok(())
203    }
204
205    fn read(&mut self, _info: BusAccessInfo, _data: &mut [u8]) {
206        unimplemented!("SmcccTrng: read not supported");
207    }
208
209    fn write(&mut self, _info: BusAccessInfo, _data: &[u8]) {
210        unimplemented!("SmcccTrng: write not supported");
211    }
212}
213
214impl BusDeviceSync for SmcccTrng {
215    fn read(&self, _info: BusAccessInfo, _data: &mut [u8]) {
216        unimplemented!("SmcccTrng: read not supported");
217    }
218
219    fn write(&self, _info: BusAccessInfo, _data: &[u8]) {
220        unimplemented!("SmcccTrng: write not supported");
221    }
222}
223
224impl Suspendable for SmcccTrng {}