Message ID | 20240618204523.9563-6-semen.protsenko@linaro.org (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | hwrng: exynos: Add support for Exynos850 | expand |
It was <2024-06-18 wto 15:45>, when Sam Protsenko wrote: > On some Exynos chips like Exynos850 the access to Security Sub System > (SSS) registers is protected with TrustZone, and therefore only possible > from EL3 monitor software. The Linux kernel is running in EL1, so the > only way for the driver to obtain TRNG data is via SMC calls to EL3 > monitor. Implement such SMC operation and use it when EXYNOS_SMC flag is > set in the corresponding chip driver data. > > Signed-off-by: Sam Protsenko <semen.protsenko@linaro.org> > Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> > --- > Changes in v2: > - Used the "reversed Christmas tree" style in the variable declaration > block in exynos_trng_do_read_smc() > - Renamed .quirks to .flags in the driver structure > - Added Krzysztof's R-b tag > > drivers/char/hw_random/exynos-trng.c | 133 +++++++++++++++++++++++++-- > 1 file changed, 123 insertions(+), 10 deletions(-) > > diff --git a/drivers/char/hw_random/exynos-trng.c b/drivers/char/hw_random/exynos-trng.c > index 99a0b271ffb7..497d6018c6ba 100644 > --- a/drivers/char/hw_random/exynos-trng.c > +++ b/drivers/char/hw_random/exynos-trng.c > @@ -10,6 +10,7 @@ > * Krzysztof Kozłowski <krzk@kernel.org> > */ [...] > +static int exynos_trng_init_smc(struct hwrng *rng) > +{ > + struct arm_smccc_res res; > + > + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_INIT, 0, 0, 0, 0, 0, 0, &res); > + if (res.a0 != HWRNG_RET_OK) > + return -EIO; > + > + return 0; > +} > + Does this driver requiers some vendor-specifig bootloading code? I am testing the code on a WinLink E850-96 board booted with the upstream u-boot and it fails during init (res0.a is -1). [ 1.883413] exynos-trng 12081400.rng: Could not register hwrng device [ 1.893394] exynos-trng 12081400.rng: probe with driver exynos-trng failed with error -5 If an additional code outside the kernel is required for this to run, then maybe the error message should reflect that. Kind regards,
On Thu, Jun 20, 2024 at 8:46 AM Lukasz Stelmach <l.stelmach@samsung.com> wrote: > > It was <2024-06-18 wto 15:45>, when Sam Protsenko wrote: > > On some Exynos chips like Exynos850 the access to Security Sub System > > (SSS) registers is protected with TrustZone, and therefore only possible > > from EL3 monitor software. The Linux kernel is running in EL1, so the > > only way for the driver to obtain TRNG data is via SMC calls to EL3 > > monitor. Implement such SMC operation and use it when EXYNOS_SMC flag is > > set in the corresponding chip driver data. > > > > Signed-off-by: Sam Protsenko <semen.protsenko@linaro.org> > > Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> > > --- > > Changes in v2: > > - Used the "reversed Christmas tree" style in the variable declaration > > block in exynos_trng_do_read_smc() > > - Renamed .quirks to .flags in the driver structure > > - Added Krzysztof's R-b tag > > > > drivers/char/hw_random/exynos-trng.c | 133 +++++++++++++++++++++++++-- > > 1 file changed, 123 insertions(+), 10 deletions(-) > > > > diff --git a/drivers/char/hw_random/exynos-trng.c b/drivers/char/hw_random/exynos-trng.c > > index 99a0b271ffb7..497d6018c6ba 100644 > > --- a/drivers/char/hw_random/exynos-trng.c > > +++ b/drivers/char/hw_random/exynos-trng.c > > @@ -10,6 +10,7 @@ > > * Krzysztof Kozłowski <krzk@kernel.org> > > */ > > [...] > > > +static int exynos_trng_init_smc(struct hwrng *rng) > > +{ > > + struct arm_smccc_res res; > > + > > + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_INIT, 0, 0, 0, 0, 0, 0, &res); > > + if (res.a0 != HWRNG_RET_OK) > > + return -EIO; > > + > > + return 0; > > +} > > + > > Does this driver requiers some vendor-specifig bootloading code? > I am testing the code on a WinLink E850-96 board booted with the > upstream u-boot and it fails during init (res0.a is -1). > This series was only tested (and works fine) with LittleKernel based bootloader [1]. It's officially recommended and the only feature complete bootloader at the moment. And you are right, the reason why TRNG probe fails when you boot the kernel from U-Boot is that the LDFW (Loadable Firmware) loading is not implemented in U-Boot right now, which makes HWRNG_INIT SMC command fail and return -1. In fact, I'm adding LDFW loading in U-Boot right now and expect it to be ready in 1 week or so. For now, can you please check with LK [1] instead? I'm happy to help if you have any related questions. > [ 1.883413] exynos-trng 12081400.rng: Could not register hwrng device > [ 1.893394] exynos-trng 12081400.rng: probe with driver exynos-trng failed with error -5 > > If an additional code outside the kernel is required for this to run, > then maybe the error message should reflect that. > Good idea! Will send v3 soon, with proper error message added. Thanks! [1] https://gitlab.com/Linaro/96boards/e850-96/lk > Kind regards, > -- > Łukasz Stelmach > Samsung R&D Institute Poland > Samsung Electronics
diff --git a/drivers/char/hw_random/exynos-trng.c b/drivers/char/hw_random/exynos-trng.c index 99a0b271ffb7..497d6018c6ba 100644 --- a/drivers/char/hw_random/exynos-trng.c +++ b/drivers/char/hw_random/exynos-trng.c @@ -10,6 +10,7 @@ * Krzysztof Kozłowski <krzk@kernel.org> */ +#include <linux/arm-smccc.h> #include <linux/clk.h> #include <linux/crypto.h> #include <linux/delay.h> @@ -22,6 +23,7 @@ #include <linux/mod_devicetable.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/property.h> #define EXYNOS_TRNG_CLKDIV 0x0 @@ -44,16 +46,41 @@ #define EXYNOS_TRNG_FIFO_LEN 8 #define EXYNOS_TRNG_CLOCK_RATE 500000 +/* Driver feature flags */ +#define EXYNOS_SMC BIT(0) + +#define EXYNOS_SMC_CALL_VAL(func_num) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_SIP, \ + func_num) + +/* SMC command for DTRNG access */ +#define SMC_CMD_RANDOM EXYNOS_SMC_CALL_VAL(0x1012) + +/* SMC_CMD_RANDOM: arguments */ +#define HWRNG_INIT 0x0 +#define HWRNG_EXIT 0x1 +#define HWRNG_GET_DATA 0x2 +#define HWRNG_RESUME 0x3 + +/* SMC_CMD_RANDOM: return values */ +#define HWRNG_RET_OK 0x0 +#define HWRNG_RET_RETRY_ERROR 0x2 + +#define HWRNG_MAX_TRIES 100 + struct exynos_trng_dev { struct device *dev; void __iomem *mem; struct clk *clk; /* operating clock */ struct clk *pclk; /* bus clock */ struct hwrng rng; + unsigned long flags; }; -static int exynos_trng_do_read(struct hwrng *rng, void *data, size_t max, - bool wait) +static int exynos_trng_do_read_reg(struct hwrng *rng, void *data, size_t max, + bool wait) { struct exynos_trng_dev *trng = (struct exynos_trng_dev *)rng->priv; int val; @@ -70,7 +97,40 @@ static int exynos_trng_do_read(struct hwrng *rng, void *data, size_t max, return max; } -static int exynos_trng_init(struct hwrng *rng) +static int exynos_trng_do_read_smc(struct hwrng *rng, void *data, size_t max, + bool wait) +{ + struct arm_smccc_res res; + unsigned int copied = 0; + u32 *buf = data; + int tries = 0; + + while (copied < max) { + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_GET_DATA, 0, 0, 0, 0, 0, 0, + &res); + switch (res.a0) { + case HWRNG_RET_OK: + *buf++ = res.a2; + *buf++ = res.a3; + copied += 8; + tries = 0; + break; + case HWRNG_RET_RETRY_ERROR: + if (!wait) + return copied; + if (++tries >= HWRNG_MAX_TRIES) + return copied; + cond_resched(); + break; + default: + return -EIO; + } + } + + return copied; +} + +static int exynos_trng_init_reg(struct hwrng *rng) { struct exynos_trng_dev *trng = (struct exynos_trng_dev *)rng->priv; unsigned long sss_rate; @@ -103,6 +163,17 @@ static int exynos_trng_init(struct hwrng *rng) return 0; } +static int exynos_trng_init_smc(struct hwrng *rng) +{ + struct arm_smccc_res res; + + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_INIT, 0, 0, 0, 0, 0, 0, &res); + if (res.a0 != HWRNG_RET_OK) + return -EIO; + + return 0; +} + static int exynos_trng_probe(struct platform_device *pdev) { struct exynos_trng_dev *trng; @@ -112,21 +183,29 @@ static int exynos_trng_probe(struct platform_device *pdev) if (!trng) return ret; + platform_set_drvdata(pdev, trng); + trng->dev = &pdev->dev; + + trng->flags = (unsigned long)device_get_match_data(&pdev->dev); + trng->rng.name = devm_kstrdup(&pdev->dev, dev_name(&pdev->dev), GFP_KERNEL); if (!trng->rng.name) return ret; - trng->rng.init = exynos_trng_init; - trng->rng.read = exynos_trng_do_read; trng->rng.priv = (unsigned long)trng; - platform_set_drvdata(pdev, trng); - trng->dev = &pdev->dev; + if (trng->flags & EXYNOS_SMC) { + trng->rng.init = exynos_trng_init_smc; + trng->rng.read = exynos_trng_do_read_smc; + } else { + trng->rng.init = exynos_trng_init_reg; + trng->rng.read = exynos_trng_do_read_reg; - trng->mem = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(trng->mem)) - return PTR_ERR(trng->mem); + trng->mem = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(trng->mem)) + return PTR_ERR(trng->mem); + } pm_runtime_enable(&pdev->dev); ret = pm_runtime_resume_and_get(&pdev->dev); @@ -170,12 +249,31 @@ static int exynos_trng_probe(struct platform_device *pdev) static void exynos_trng_remove(struct platform_device *pdev) { + struct exynos_trng_dev *trng = platform_get_drvdata(pdev); + + if (trng->flags & EXYNOS_SMC) { + struct arm_smccc_res res; + + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_EXIT, 0, 0, 0, 0, 0, 0, + &res); + } + pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); } static int exynos_trng_suspend(struct device *dev) { + struct exynos_trng_dev *trng = dev_get_drvdata(dev); + struct arm_smccc_res res; + + if (trng->flags & EXYNOS_SMC) { + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_EXIT, 0, 0, 0, 0, 0, 0, + &res); + if (res.a0 != HWRNG_RET_OK) + return -EIO; + } + pm_runtime_put_sync(dev); return 0; @@ -183,6 +281,7 @@ static int exynos_trng_suspend(struct device *dev) static int exynos_trng_resume(struct device *dev) { + struct exynos_trng_dev *trng = dev_get_drvdata(dev); int ret; ret = pm_runtime_resume_and_get(dev); @@ -191,6 +290,20 @@ static int exynos_trng_resume(struct device *dev) return ret; } + if (trng->flags & EXYNOS_SMC) { + struct arm_smccc_res res; + + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_RESUME, 0, 0, 0, 0, 0, 0, + &res); + if (res.a0 != HWRNG_RET_OK) + return -EIO; + + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_INIT, 0, 0, 0, 0, 0, 0, + &res); + if (res.a0 != HWRNG_RET_OK) + return -EIO; + } + return 0; }