mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-04 08:08:54 +00:00
hwrng: stm32 - implement error concealment
The RNG driver should be capable of recovering from an error. Implement an error concealment API. This avoids irrecoverable RNG state. Signed-off-by: Gatien Chevallier <gatien.chevallier@foss.st.com> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
This commit is contained in:
parent
6b85a7e141
commit
8f1c5227ec
1 changed files with 110 additions and 4 deletions
|
@ -29,10 +29,12 @@
|
|||
#define RNG_CR_ENTROPY_SRC_MASK (RNG_CR_CONFIG1 | RNG_CR_NISTC | RNG_CR_CONFIG2 | RNG_CR_CONFIG3)
|
||||
#define RNG_CR_CONFIG_MASK (RNG_CR_ENTROPY_SRC_MASK | RNG_CR_CED)
|
||||
|
||||
#define RNG_SR 0x04
|
||||
#define RNG_SR_SEIS BIT(6)
|
||||
#define RNG_SR_CEIS BIT(5)
|
||||
#define RNG_SR_DRDY BIT(0)
|
||||
#define RNG_SR 0x04
|
||||
#define RNG_SR_DRDY BIT(0)
|
||||
#define RNG_SR_CECS BIT(1)
|
||||
#define RNG_SR_SECS BIT(2)
|
||||
#define RNG_SR_CEIS BIT(5)
|
||||
#define RNG_SR_SEIS BIT(6)
|
||||
|
||||
#define RNG_DR 0x08
|
||||
|
||||
|
@ -57,6 +59,107 @@ struct stm32_rng_private {
|
|||
bool ced;
|
||||
};
|
||||
|
||||
/*
|
||||
* Extracts from the STM32 RNG specification when RNG supports CONDRST.
|
||||
*
|
||||
* When a noise source (or seed) error occurs, the RNG stops generating
|
||||
* random numbers and sets to “1” both SEIS and SECS bits to indicate
|
||||
* that a seed error occurred. (...)
|
||||
*
|
||||
* 1. Software reset by writing CONDRST at 1 and at 0 (see bitfield
|
||||
* description for details). This step is needed only if SECS is set.
|
||||
* Indeed, when SEIS is set and SECS is cleared it means RNG performed
|
||||
* the reset automatically (auto-reset).
|
||||
* 2. If SECS was set in step 1 (no auto-reset) wait for CONDRST
|
||||
* to be cleared in the RNG_CR register, then confirm that SEIS is
|
||||
* cleared in the RNG_SR register. Otherwise just clear SEIS bit in
|
||||
* the RNG_SR register.
|
||||
* 3. If SECS was set in step 1 (no auto-reset) wait for SECS to be
|
||||
* cleared by RNG. The random number generation is now back to normal.
|
||||
*/
|
||||
static int stm32_rng_conceal_seed_error_cond_reset(struct stm32_rng_private *priv)
|
||||
{
|
||||
struct device *dev = (struct device *)priv->rng.priv;
|
||||
u32 sr = readl_relaxed(priv->base + RNG_SR);
|
||||
u32 cr = readl_relaxed(priv->base + RNG_CR);
|
||||
int err;
|
||||
|
||||
if (sr & RNG_SR_SECS) {
|
||||
/* Conceal by resetting the subsystem (step 1.) */
|
||||
writel_relaxed(cr | RNG_CR_CONDRST, priv->base + RNG_CR);
|
||||
writel_relaxed(cr & ~RNG_CR_CONDRST, priv->base + RNG_CR);
|
||||
} else {
|
||||
/* RNG auto-reset (step 2.) */
|
||||
writel_relaxed(sr & ~RNG_SR_SEIS, priv->base + RNG_SR);
|
||||
goto end;
|
||||
}
|
||||
|
||||
err = readl_relaxed_poll_timeout_atomic(priv->base + RNG_CR, cr, !(cr & RNG_CR_CONDRST), 10,
|
||||
100000);
|
||||
if (err) {
|
||||
dev_err(dev, "%s: timeout %x\n", __func__, sr);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Check SEIS is cleared (step 2.) */
|
||||
if (readl_relaxed(priv->base + RNG_SR) & RNG_SR_SEIS)
|
||||
return -EINVAL;
|
||||
|
||||
err = readl_relaxed_poll_timeout_atomic(priv->base + RNG_SR, sr, !(sr & RNG_SR_SECS), 10,
|
||||
100000);
|
||||
if (err) {
|
||||
dev_err(dev, "%s: timeout %x\n", __func__, sr);
|
||||
return err;
|
||||
}
|
||||
|
||||
end:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Extracts from the STM32 RNG specification, when CONDRST is not supported
|
||||
*
|
||||
* When a noise source (or seed) error occurs, the RNG stops generating
|
||||
* random numbers and sets to “1” both SEIS and SECS bits to indicate
|
||||
* that a seed error occurred. (...)
|
||||
*
|
||||
* The following sequence shall be used to fully recover from a seed
|
||||
* error after the RNG initialization:
|
||||
* 1. Clear the SEIS bit by writing it to “0”.
|
||||
* 2. Read out 12 words from the RNG_DR register, and discard each of
|
||||
* them in order to clean the pipeline.
|
||||
* 3. Confirm that SEIS is still cleared. Random number generation is
|
||||
* back to normal.
|
||||
*/
|
||||
static int stm32_rng_conceal_seed_error_sw_reset(struct stm32_rng_private *priv)
|
||||
{
|
||||
unsigned int i = 0;
|
||||
u32 sr = readl_relaxed(priv->base + RNG_SR);
|
||||
|
||||
writel_relaxed(sr & ~RNG_SR_SEIS, priv->base + RNG_SR);
|
||||
|
||||
for (i = 12; i != 0; i--)
|
||||
(void)readl_relaxed(priv->base + RNG_DR);
|
||||
|
||||
if (readl_relaxed(priv->base + RNG_SR) & RNG_SR_SEIS)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_rng_conceal_seed_error(struct hwrng *rng)
|
||||
{
|
||||
struct stm32_rng_private *priv = container_of(rng, struct stm32_rng_private, rng);
|
||||
|
||||
dev_dbg((struct device *)priv->rng.priv, "Concealing seed error\n");
|
||||
|
||||
if (priv->data->has_cond_reset)
|
||||
return stm32_rng_conceal_seed_error_cond_reset(priv);
|
||||
else
|
||||
return stm32_rng_conceal_seed_error_sw_reset(priv);
|
||||
};
|
||||
|
||||
|
||||
static int stm32_rng_read(struct hwrng *rng, void *data, size_t max, bool wait)
|
||||
{
|
||||
struct stm32_rng_private *priv =
|
||||
|
@ -66,6 +169,9 @@ static int stm32_rng_read(struct hwrng *rng, void *data, size_t max, bool wait)
|
|||
|
||||
pm_runtime_get_sync((struct device *) priv->rng.priv);
|
||||
|
||||
if (readl_relaxed(priv->base + RNG_SR) & RNG_SR_SEIS)
|
||||
stm32_rng_conceal_seed_error(rng);
|
||||
|
||||
while (max >= sizeof(u32)) {
|
||||
sr = readl_relaxed(priv->base + RNG_SR);
|
||||
/* Manage timeout which is based on timer and take */
|
||||
|
|
Loading…
Reference in a new issue