Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | // SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2022, Linaro Limited */ #define LOG_CATEGORY UCLASS_RNG #include <dm.h> #include <linker_lists.h> #include <log.h> #include <rng.h> #include <dm/device_compat.h> #include <linux/arm-smccc.h> #include <linux/bitops.h> #include <linux/kernel.h> #include <linux/psci.h> #define DRIVER_NAME "smccc-trng" /** * Arm SMCCC TRNG firmware interface specification: * https://developer.arm.com/documentation/den0098/latest/ */ #define ARM_SMCCC_TRNG_VERSION 0x84000050 #define ARM_SMCCC_TRNG_FEATURES 0x84000051 #define ARM_SMCCC_TRNG_GET_UUID 0x84000052 #define ARM_SMCCC_TRNG_RND_32 0x84000053 #define ARM_SMCCC_TRNG_RND_64 0xC4000053 #define ARM_SMCCC_RET_TRNG_SUCCESS ((ulong)0) #define ARM_SMCCC_RET_TRNG_NOT_SUPPORTED ((ulong)-1) #define ARM_SMCCC_RET_TRNG_INVALID_PARAMETER ((ulong)-2) #define ARM_SMCCC_RET_TRNG_NO_ENTROPY ((ulong)-3) #define TRNG_MAJOR_MASK GENMASK(30, 16) #define TRNG_MAJOR_SHIFT 16 #define TRNG_MINOR_MASK GENMASK(15, 0) #define TRNG_MINOR_SHIFT 0 #define TRNG_MAX_RND_64 (192 / 8) #define TRNG_MAX_RND_32 (96 / 8) /** * struct smccc_trng_priv - Private data for SMCCC TRNG support * * @smc64 - True if TRNG_RND_64 is supported, false if TRNG_RND_32 is supported */ struct smccc_trng_priv { bool smc64; }; /* * Copy random bytes from ulong SMCCC output register to target buffer * Defines 2 function flavors for whether ARM_SMCCC_TRNG_RND_32 or * ARM_SMCCC_TRNG_RND_64 was used to invoke the service. */ static size_t smc32_copy_sample(u8 **ptr, size_t size, ulong *rnd) { size_t len = min(size, sizeof(u32)); u32 sample = *rnd; memcpy(*ptr, &sample, len); *ptr += len; return size - len; } static size_t smc64_copy_sample(u8 **ptr, size_t size, ulong *rnd) { size_t len = min(size, sizeof(u64)); u64 sample = *rnd; memcpy(*ptr, &sample, len); *ptr += len; return size - len; } static int smccc_trng_read(struct udevice *dev, void *data, size_t len) { struct psci_plat_data *smccc = dev_get_parent_plat(dev); struct smccc_trng_priv *priv = dev_get_priv(dev); struct arm_smccc_res res; u32 func_id; u8 *ptr = data; size_t rem = len; size_t max_sz; size_t (*copy_sample)(u8 **ptr, size_t size, ulong *rnd); if (priv->smc64) { copy_sample = smc64_copy_sample; func_id = ARM_SMCCC_TRNG_RND_64; max_sz = TRNG_MAX_RND_64; } else { copy_sample = smc32_copy_sample; func_id = ARM_SMCCC_TRNG_RND_32; max_sz = TRNG_MAX_RND_32; } while (rem) { size_t sz = min(rem, max_sz); smccc->invoke_fn(func_id, sz * 8, 0, 0, 0, 0, 0, 0, &res); switch (res.a0) { case ARM_SMCCC_RET_TRNG_SUCCESS: break; case ARM_SMCCC_RET_TRNG_NO_ENTROPY: continue; default: return -EIO; } rem -= sz; sz = copy_sample(&ptr, sz, &res.a3); if (sz) sz = copy_sample(&ptr, sz, &res.a2); if (sz) sz = copy_sample(&ptr, sz, &res.a1); } return 0; } static const struct dm_rng_ops smccc_trng_ops = { .read = smccc_trng_read, }; static bool smccc_trng_is_supported(void (*invoke_fn)(unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5, unsigned long a6, unsigned long a7, struct arm_smccc_res *res)) { struct arm_smccc_res res; (*invoke_fn)(ARM_SMCCC_TRNG_VERSION, 0, 0, 0, 0, 0, 0, 0, &res); if (res.a0 & BIT(31)) return false; /* Test 64bit interface and fallback to 32bit interface */ invoke_fn(ARM_SMCCC_TRNG_FEATURES, ARM_SMCCC_TRNG_RND_64, 0, 0, 0, 0, 0, 0, &res); if (res.a0 == ARM_SMCCC_RET_TRNG_NOT_SUPPORTED) invoke_fn(ARM_SMCCC_TRNG_FEATURES, ARM_SMCCC_TRNG_RND_32, 0, 0, 0, 0, 0, 0, &res); return res.a0 == ARM_SMCCC_RET_TRNG_SUCCESS; } ARM_SMCCC_FEATURE_DRIVER(smccc_trng) = { .driver_name = DRIVER_NAME, .is_supported = smccc_trng_is_supported, }; static int smccc_trng_probe(struct udevice *dev) { struct psci_plat_data *smccc = dev_get_parent_plat(dev); struct smccc_trng_priv *priv = dev_get_priv(dev); struct arm_smccc_res res; if (!smccc || !(smccc_trng_is_supported(smccc->invoke_fn))) return -ENODEV; /* At least one of 64bit and 32bit interfaces is available */ smccc->invoke_fn(ARM_SMCCC_TRNG_FEATURES, ARM_SMCCC_TRNG_RND_64, 0, 0, 0, 0, 0, 0, &res); priv->smc64 = (res.a0 == ARM_SMCCC_RET_TRNG_SUCCESS); #ifdef DEBUG smccc->invoke_fn(ARM_SMCCC_TRNG_GET_UUID, 0, 0, 0, 0, 0, 0, 0, &res); if (res.a0 != ARM_SMCCC_RET_TRNG_NOT_SUPPORTED) { unsigned long uuid_a0 = res.a0; unsigned long uuid_a1 = res.a1; unsigned long uuid_a2 = res.a2; unsigned long uuid_a3 = res.a3; unsigned long major, minor; smccc->invoke_fn(ARM_SMCCC_TRNG_VERSION, 0, 0, 0, 0, 0, 0, 0, &res); major = (res.a0 & TRNG_MAJOR_MASK) >> TRNG_MAJOR_SHIFT; minor = (res.a0 & TRNG_MINOR_MASK) >> TRNG_MINOR_SHIFT; dev_dbg(dev, "Version %lu.%lu, UUID %08lx-%04lx-%04lx-%04lx-%04lx%08lx\n", major, minor, uuid_a0, uuid_a1 >> 16, uuid_a1 & GENMASK(16, 0), uuid_a2 >> 16, uuid_a2 & GENMASK(16, 0), uuid_a3); } else { dev_warn(dev, "Can't get TRNG UUID\n"); } #endif return 0; } U_BOOT_DRIVER(smccc_trng) = { .name = DRIVER_NAME, .id = UCLASS_RNG, .ops = &smccc_trng_ops, .probe = smccc_trng_probe, .priv_auto = sizeof(struct smccc_trng_priv), }; |