From ca3bff70ab320a9132c5524c495455526df4b078 Mon Sep 17 00:00:00 2001 From: Rick Altherr Date: Wed, 5 Apr 2017 16:21:00 -0700 Subject: hwrng: timeriomem - Improve performance for sub-jiffie update periods Some hardware RNGs provide a single register for obtaining random data. Instead of signaling when new data is available, the reader must wait a fixed amount of time between reads for new data to be generated. timeriomem_rng implements this scheme with the period specified in platform data or device tree. While the period is specified in microseconds, the implementation used a standard timer which has a minimum delay of 1 jiffie and caused a significant bottleneck for devices that can update at 1us. By switching to an hrtimer, 1us periods now only delay at most 2us per read. Signed-off-by: Rick Altherr Signed-off-by: Herbert Xu --- drivers/char/hw_random/timeriomem-rng.c | 86 +++++++++++++++++---------------- 1 file changed, 45 insertions(+), 41 deletions(-) (limited to 'drivers/char/hw_random') diff --git a/drivers/char/hw_random/timeriomem-rng.c b/drivers/char/hw_random/timeriomem-rng.c index 024bdff7999f..a0faa5f05deb 100644 --- a/drivers/char/hw_random/timeriomem-rng.c +++ b/drivers/char/hw_random/timeriomem-rng.c @@ -21,23 +21,24 @@ */ #include +#include +#include #include #include -#include +#include #include #include #include #include +#include #include -#include struct timeriomem_rng_private { void __iomem *io_base; - unsigned int expires; - unsigned int period; + ktime_t period; unsigned int present:1; - struct timer_list timer; + struct hrtimer timer; struct completion completion; struct hwrng rng_ops; @@ -48,10 +49,13 @@ static int timeriomem_rng_read(struct hwrng *hwrng, void *data, { struct timeriomem_rng_private *priv = container_of(hwrng, struct timeriomem_rng_private, rng_ops); - unsigned long cur; - s32 delay; + int retval = 0; + int period_us = ktime_to_us(priv->period); - /* The RNG provides 32-bit per read. Ensure there is enough space. */ + /* + * The RNG provides 32-bits per read. Ensure there is enough space for + * at minimum one read. + */ if (max < sizeof(u32)) return 0; @@ -66,33 +70,44 @@ static int timeriomem_rng_read(struct hwrng *hwrng, void *data, wait_for_completion(&priv->completion); - *(u32 *)data = readl(priv->io_base); + do { + /* + * After the first read, all additional reads will need to wait + * for the RNG to generate new data. Since the period can have + * a wide range of values (1us to 1s have been observed), allow + * for 1% tolerance in the sleep time rather than a fixed value. + */ + if (retval > 0) + usleep_range(period_us, + period_us + min(1, period_us / 100)); + + *(u32 *)data = readl(priv->io_base); + retval += sizeof(u32); + data += sizeof(u32); + max -= sizeof(u32); + } while (wait && max > sizeof(u32)); /* * Block any new callers until the RNG has had time to generate new * data. */ - cur = jiffies; - - delay = cur - priv->expires; - delay = priv->period - (delay % priv->period); - - priv->expires = cur + delay; priv->present = 0; - reinit_completion(&priv->completion); - mod_timer(&priv->timer, priv->expires); + hrtimer_forward_now(&priv->timer, priv->period); + hrtimer_restart(&priv->timer); - return 4; + return retval; } -static void timeriomem_rng_trigger(unsigned long data) +static enum hrtimer_restart timeriomem_rng_trigger(struct hrtimer *timer) { struct timeriomem_rng_private *priv - = (struct timeriomem_rng_private *)data; + = container_of(timer, struct timeriomem_rng_private, timer); priv->present = 1; complete(&priv->completion); + + return HRTIMER_NORESTART; } static int timeriomem_rng_probe(struct platform_device *pdev) @@ -140,43 +155,33 @@ static int timeriomem_rng_probe(struct platform_device *pdev) period = pdata->period; } - priv->period = usecs_to_jiffies(period); - if (priv->period < 1) { - dev_err(&pdev->dev, "period is less than one jiffy\n"); - return -EINVAL; - } - - priv->expires = jiffies; - priv->present = 1; - + priv->period = ns_to_ktime(period * NSEC_PER_USEC); init_completion(&priv->completion); - complete(&priv->completion); - - setup_timer(&priv->timer, timeriomem_rng_trigger, (unsigned long)priv); + hrtimer_init(&priv->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + priv->timer.function = timeriomem_rng_trigger; priv->rng_ops.name = dev_name(&pdev->dev); priv->rng_ops.read = timeriomem_rng_read; priv->io_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(priv->io_base)) { - err = PTR_ERR(priv->io_base); - goto out_timer; + return PTR_ERR(priv->io_base); } + /* Assume random data is already available. */ + priv->present = 1; + complete(&priv->completion); + err = hwrng_register(&priv->rng_ops); if (err) { dev_err(&pdev->dev, "problem registering\n"); - goto out_timer; + return err; } dev_info(&pdev->dev, "32bits from 0x%p @ %dus\n", priv->io_base, period); return 0; - -out_timer: - del_timer_sync(&priv->timer); - return err; } static int timeriomem_rng_remove(struct platform_device *pdev) @@ -184,8 +189,7 @@ static int timeriomem_rng_remove(struct platform_device *pdev) struct timeriomem_rng_private *priv = platform_get_drvdata(pdev); hwrng_unregister(&priv->rng_ops); - - del_timer_sync(&priv->timer); + hrtimer_cancel(&priv->timer); return 0; } -- cgit v1.2.3-59-g8ed1b