/* data circular buffer in words must be: * - of a power-of-2 size (limitation of circ_buf.h macros) * - at least 6, the size generated in the EHR according to HW implementation
*/ #define CCTRNG_DATA_BUF_WORDS 32
/* The timeout for the TRNG operation should be calculated with the formula: * Timeout = EHR_NUM * VN_COEFF * EHR_LENGTH * SAMPLE_CNT * SCALE_VALUE * while: * - SAMPLE_CNT is input value from the characterisation process * - all the rest are constants
*/ #define EHR_NUM 1 #define VN_COEFF 4 #define EHR_LENGTH CC_TRNG_EHR_IN_BITS #define SCALE_VALUE 2 #define CCTRNG_TIMEOUT(smpl_cnt) \
(EHR_NUM * VN_COEFF * EHR_LENGTH * smpl_cnt * SCALE_VALUE)
struct cctrng_drvdata { struct platform_device *pdev; void __iomem *cc_base; struct clk *clk; struct hwrng rng;
u32 active_rosc; /* Sampling interval for each ring oscillator: * count of ring oscillator cycles between consecutive bits sampling. * Value of 0 indicates non-valid rosc
*/
u32 smpl_ratio[CC_TRNG_NUM_OF_ROSCS];
/* must be before the enabling to avoid redundant suspending */
pm_runtime_set_autosuspend_delay(dev, CC_TRNG_SUSPEND_TIMEOUT);
pm_runtime_use_autosuspend(dev); /* set us as active - note we won't do PM ops until cc_trng_pm_go()! */ return pm_runtime_set_active(dev);
}
staticinlineint cc_trng_parse_sampling_ratio(struct cctrng_drvdata *drvdata)
{ struct device *dev = &(drvdata->pdev->dev); struct device_node *np = drvdata->pdev->dev.of_node; int rc; int i; /* ret will be set to 0 if at least one rosc has (sampling ratio > 0) */ int ret = -EINVAL;
rc = of_property_read_u32_array(np, "arm,rosc-ratio",
drvdata->smpl_ratio,
CC_TRNG_NUM_OF_ROSCS); if (rc) { /* arm,rosc-ratio was not found in device tree */ return rc;
}
/* verify that at least one rosc has (sampling ratio > 0) */ for (i = 0; i < CC_TRNG_NUM_OF_ROSCS; ++i) {
dev_dbg(dev, "rosc %d sampling ratio %u",
i, drvdata->smpl_ratio[i]);
/* Set watchdog threshold to maximal allowed time (in CPU cycles) */
max_cycles = CCTRNG_TIMEOUT(drvdata->smpl_ratio[drvdata->active_rosc]);
cc_iowrite(drvdata, CC_RNG_WATCHDOG_VAL_REG_OFFSET, max_cycles);
/* enable the RND source */
cc_iowrite(drvdata, CC_RND_SOURCE_ENABLE_REG_OFFSET, 0x1);
if (!spin_trylock(&drvdata->read_lock)) { /* concurrent consumers from data_buf cannot be served */
dev_dbg_ratelimited(dev, "unable to hold lock\n"); return 0;
}
/* copy till end of data buffer (without wrap back) */
cnt_w = CIRC_CNT_TO_END(drvdata->circ.head,
drvdata->circ.tail, CCTRNG_DATA_BUF_WORDS);
size = min((cnt_w<<2), max);
memcpy(data, &(buf[drvdata->circ.tail]), size);
copied = size;
circ_idx_inc(&drvdata->circ.tail, size); /* copy rest of data in data buffer */
left = max - copied; if (left > 0) {
cnt_w = CIRC_CNT(drvdata->circ.head,
drvdata->circ.tail, CCTRNG_DATA_BUF_WORDS);
size = min((cnt_w<<2), left);
memcpy(data, &(buf[drvdata->circ.tail]), size);
copied += size;
circ_idx_inc(&drvdata->circ.tail, size);
}
spin_unlock(&drvdata->read_lock);
if (circ_buf_space(drvdata) >= CC_TRNG_EHR_IN_WORDS) { if (atomic_cmpxchg(&drvdata->pending_hw, 0, 1) == 0) { /* re-check space in buffer to avoid potential race */ if (circ_buf_space(drvdata) >= CC_TRNG_EHR_IN_WORDS) { /* increment device's usage counter */ int rc = cc_trng_pm_get(dev);
if (rc) {
dev_err(dev, "cc_trng_pm_get returned %x\n",
rc); return rc;
}
/* schedule execution of deferred work handler * for filling of data buffer
*/
schedule_work(&drvdata->startwork);
} else {
atomic_set(&drvdata->pending_hw, 0);
}
}
}
/* enable the HW RND clock */
cc_iowrite(drvdata, CC_RNG_CLK_ENABLE_REG_OFFSET, 0x1);
/* do software reset */
cc_iowrite(drvdata, CC_RNG_SW_RESET_REG_OFFSET, 0x1); /* in order to verify that the reset has completed, * the sample count need to be verified
*/ do { /* enable the HW RND clock */
cc_iowrite(drvdata, CC_RNG_CLK_ENABLE_REG_OFFSET, 0x1);
/* set sampling ratio (rng_clocks) between consecutive bits */
cc_iowrite(drvdata, CC_SAMPLE_CNT1_REG_OFFSET,
drvdata->smpl_ratio[drvdata->active_rosc]);
/* read the sampling ratio */
tmp_smpl_cnt = cc_ioread(drvdata, CC_SAMPLE_CNT1_REG_OFFSET);
} while (tmp_smpl_cnt != drvdata->smpl_ratio[drvdata->active_rosc]);
/* disable the RND source for setting new parameters in HW */
cc_iowrite(drvdata, CC_RND_SOURCE_ENABLE_REG_OFFSET, 0);
if (fips_enabled && CC_REG_FLD_GET(RNG_ISR, CRNGT_ERR, isr)) {
fips_fail_notify(); /* FIPS error is fatal */
panic("Got HW CRNGT error while fips is enabled!\n");
}
/* Clear all pending RNG interrupts */
cc_iowrite(drvdata, CC_RNG_ICR_REG_OFFSET, isr);
if (!ehr_valid) { /* in case of AUTOCORR/TIMEOUT error, try the next ROSC */ if (CC_REG_FLD_GET(RNG_ISR, AUTOCORR_ERR, isr) ||
CC_REG_FLD_GET(RNG_ISR, WATCHDOG, isr)) {
dev_dbg(dev, "cctrng autocorr/timeout error.\n"); goto next_rosc;
}
/* in case of VN error, ignore it */
}
/* read EHR data from registers */ for (i = 0; i < CC_TRNG_EHR_IN_WORDS; i++) { /* calc word ptr in data_buf */
u32 *buf = (u32 *)drvdata->circ.buf;
/* EHR_DATA registers are cleared on read. In case 0 value was * returned, restart the entropy collection.
*/ if (buf[drvdata->circ.head] == 0) {
dev_dbg(dev, "Got 0 value in EHR. active_rosc %u\n",
drvdata->active_rosc); goto next_rosc;
}
circ_idx_inc(&drvdata->circ.head, 1<<2);
}
atomic_set(&drvdata->pending_hw, 0);
/* continue to fill data buffer if needed */ if (circ_buf_space(drvdata) >= CC_TRNG_EHR_IN_WORDS) { if (atomic_cmpxchg(&drvdata->pending_hw, 0, 1) == 0) { /* Re-enable rnd source */
cc_trng_enable_rnd_source(drvdata); return;
}
}
cc_trng_pm_put_suspend(dev);
dev_dbg(dev, "compwork handler done\n"); return;
next_rosc: if ((circ_buf_space(drvdata) >= CC_TRNG_EHR_IN_WORDS) &&
(cc_trng_change_rosc(drvdata) == 0)) { /* trigger trng hw with next rosc */
cc_trng_hw_trigger(drvdata);
} else {
atomic_set(&drvdata->pending_hw, 0);
cc_trng_pm_put_suspend(dev);
}
}
/* if driver suspended return, probably shared interrupt */ if (pm_runtime_suspended(dev)) return IRQ_NONE;
/* read the interrupt status */
irr = cc_ioread(drvdata, CC_HOST_RGF_IRR_REG_OFFSET);
dev_dbg(dev, "Got IRR=0x%08X\n", irr);
if (irr == 0) /* Probably shared interrupt line */ return IRQ_NONE;
/* clear interrupt - must be before processing events */
cc_iowrite(drvdata, CC_HOST_RGF_ICR_REG_OFFSET, irr);
/* RNG interrupt - most probable */ if (irr & CC_HOST_RNG_IRQ_MASK) { /* Mask RNG interrupts - will be unmasked in deferred work */
cc_iowrite(drvdata, CC_RNG_IMR_REG_OFFSET, 0xFFFFFFFF);
/* We clear RNG interrupt here, * to avoid it from firing as we'll unmask RNG interrupts.
*/
cc_iowrite(drvdata, CC_HOST_RGF_ICR_REG_OFFSET,
CC_HOST_RNG_IRQ_MASK);
irr &= ~CC_HOST_RNG_IRQ_MASK;
/* schedule execution of deferred work handler */
schedule_work(&drvdata->compwork);
}
if (irr) {
dev_dbg_ratelimited(dev, "IRR includes unknown cause bits (0x%08X)\n",
irr); /* Just warning */
}
drvdata->cc_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(drvdata->cc_base)) return dev_err_probe(dev, PTR_ERR(drvdata->cc_base), "Failed to ioremap registers");
/* Then IRQ */
irq = platform_get_irq(pdev, 0); if (irq < 0) return irq;
/* parse sampling rate from device tree */
rc = cc_trng_parse_sampling_ratio(drvdata); if (rc) return dev_err_probe(dev, rc, "Failed to get legal sampling ratio for rosc\n");
drvdata->clk = devm_clk_get_optional_enabled(dev, NULL); if (IS_ERR(drvdata->clk)) return dev_err_probe(dev, PTR_ERR(drvdata->clk), "Failed to get or enable the clock\n");
for (i = 0; i < CC_HW_RESET_LOOP_COUNT; i++) { /* in cc7x3 NVM_IS_IDLE indicates that CC reset is * completed and device is fully functional
*/
val = cc_ioread(drvdata, CC_NVM_IS_IDLE_REG_OFFSET); if (val & BIT(CC_NVM_IS_IDLE_VALUE_BIT_SHIFT)) { /* hw indicate reset completed */ returntrue;
} /* allow scheduling other process on the processor */
schedule();
} /* reset not completed */ returnfalse;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.