[PATCH] hwrng: core - allocate a one page buffer

From: Laurent Vivier
Date: Thu Aug 06 2020 - 12:47:28 EST


This allows the driver to move bigger data block when the backend allows
it.

Add a backend buffer_size for this purpose. For the moment only
virtio-rng defines it.

Using bigger buffer with virtio-rng improves performance from:

# dd if=/dev/hwrng of=/dev/null bs=1024 count=1024000
1048576000 bytes (1.0 GB, 1000 MiB) copied, 674.303 s, 1.6 MB/s
# dd if=/dev/hwrng of=/dev/null bs=4096 count=256000
1048576000 bytes (1.0 GB, 1000 MiB) copied, 622.394 s, 1.7 MB/s

to

# dd if=/dev/hwrng of=/dev/null bs=1024 count=1024000
1048576000 bytes (1.0 GB, 1000 MiB) copied, 41.0579 s, 25.5 MB/s
# dd if=/dev/hwrng of=/dev/null bs=4096 count=256000
1048576000 bytes (1.0 GB, 1000 MiB) copied, 14.394 s, 72.8 MB/s

Signed-off-by: Laurent Vivier <lvivier@xxxxxxxxxx>
---
drivers/char/hw_random/core.c | 37 ++++++++++++++++++++---------
drivers/char/hw_random/virtio-rng.c | 1 +
include/linux/hw_random.h | 2 ++
3 files changed, 29 insertions(+), 11 deletions(-)

diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c
index 8c1c47dd9f46..3d8ce3c4d79c 100644
--- a/drivers/char/hw_random/core.c
+++ b/drivers/char/hw_random/core.c
@@ -56,15 +56,25 @@ static void start_khwrngd(void);
static inline int rng_get_data(struct hwrng *rng, u8 *buffer, size_t size,
int wait);

-static size_t rng_buffer_size(void)
+static size_t rng_min_buffer_size(void)
{
- return SMP_CACHE_BYTES < 32 ? 32 : SMP_CACHE_BYTES;
+ return max_t(size_t, 32, SMP_CACHE_BYTES);
+}
+
+static size_t rng_max_buffer_size(struct hwrng *rng)
+{
+ size_t size;
+
+ size = max_t(size_t, rng->buffer_size, SMP_CACHE_BYTES);
+
+ /* rng_buffer can store up to PAGE_SIZE */
+ return min(PAGE_SIZE, size);
}

static void add_early_randomness(struct hwrng *rng)
{
int bytes_read;
- size_t size = min_t(size_t, 16, rng_buffer_size());
+ size_t size = min_t(size_t, 16, rng_min_buffer_size());

mutex_lock(&reading_mutex);
bytes_read = rng_get_data(rng, rng_buffer, size, 0);
@@ -226,9 +236,14 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf,
goto out_put;
}
if (!data_avail) {
- bytes_read = rng_get_data(rng, rng_buffer,
- rng_buffer_size(),
- !(filp->f_flags & O_NONBLOCK));
+ size_t to_read;
+ /* read at least 32 bytes, up to rng_max_buffer_size()
+ * but no more than size
+ */
+ to_read = max_t(size_t, 32,
+ min(size, rng_max_buffer_size(rng)));
+ bytes_read = rng_get_data(rng, rng_buffer, to_read,
+ !(filp->f_flags & O_NONBLOCK));
if (bytes_read < 0) {
err = bytes_read;
goto out_unlock_reading;
@@ -440,7 +455,7 @@ static int hwrng_fillfn(void *unused)
break;
mutex_lock(&reading_mutex);
rc = rng_get_data(rng, rng_fillbuf,
- rng_buffer_size(), 1);
+ rng_min_buffer_size(), 1);
mutex_unlock(&reading_mutex);
put_rng(rng);
if (rc <= 0) {
@@ -614,11 +629,11 @@ static int __init hwrng_modinit(void)
int ret;

/* kmalloc makes this safe for virt_to_page() in virtio_rng.c */
- rng_buffer = kmalloc(rng_buffer_size(), GFP_KERNEL);
+ rng_buffer = (u8 *)get_zeroed_page(GFP_KERNEL);
if (!rng_buffer)
return -ENOMEM;

- rng_fillbuf = kmalloc(rng_buffer_size(), GFP_KERNEL);
+ rng_fillbuf = kmalloc(rng_min_buffer_size(), GFP_KERNEL);
if (!rng_fillbuf) {
kfree(rng_buffer);
return -ENOMEM;
@@ -627,7 +642,7 @@ static int __init hwrng_modinit(void)
ret = register_miscdev();
if (ret) {
kfree(rng_fillbuf);
- kfree(rng_buffer);
+ free_page((unsigned long)rng_buffer);
}

return ret;
@@ -637,7 +652,7 @@ static void __exit hwrng_modexit(void)
{
mutex_lock(&rng_mutex);
BUG_ON(current_rng);
- kfree(rng_buffer);
+ free_page((unsigned long)rng_buffer);
kfree(rng_fillbuf);
mutex_unlock(&rng_mutex);

diff --git a/drivers/char/hw_random/virtio-rng.c b/drivers/char/hw_random/virtio-rng.c
index a90001e02bf7..b71c137fcd05 100644
--- a/drivers/char/hw_random/virtio-rng.c
+++ b/drivers/char/hw_random/virtio-rng.c
@@ -104,6 +104,7 @@ static int probe_common(struct virtio_device *vdev)

vi->hwrng = (struct hwrng) {
.read = virtio_read,
+ .buffer_size = PAGE_SIZE,
.cleanup = virtio_cleanup,
.priv = (unsigned long)vi,
.name = vi->name,
diff --git a/include/linux/hw_random.h b/include/linux/hw_random.h
index 8e6dd908da21..582c8787f808 100644
--- a/include/linux/hw_random.h
+++ b/include/linux/hw_random.h
@@ -31,6 +31,7 @@
* @read: New API. drivers can fill up to max bytes of data
* into the buffer. The buffer is aligned for any type
* and max is a multiple of 4 and >= 32 bytes.
+ * @buffer_size: Optional. if not 0, optimal buffer size.
* @priv: Private data, for use by the RNG driver.
* @quality: Estimation of true entropy in RNG's bitstream
* (in bits of entropy per 1024 bits of input;
@@ -43,6 +44,7 @@ struct hwrng {
int (*data_present)(struct hwrng *rng, int wait);
int (*data_read)(struct hwrng *rng, u32 *data);
int (*read)(struct hwrng *rng, void *data, size_t max, bool wait);
+ size_t buffer_size;
unsigned long priv;
unsigned short quality;

--
2.26.2