| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Copyright (c) 2019, Linaro Limited | |
| */ | |
| #include <dm.h> | |
| #include <log.h> | |
| #include <rng.h> | |
| #include <virtio_types.h> | |
| #include <virtio.h> | |
| #include <virtio_ring.h> | |
| #define BUFFER_SIZE 16UL | |
| struct virtio_rng_priv { | |
| struct virtqueue *rng_vq; | |
| }; | |
| static int virtio_rng_read(struct udevice *dev, void *data, size_t len) | |
| { | |
| int ret; | |
| unsigned int rsize = 1; | |
| unsigned char buf[BUFFER_SIZE] __aligned(4); | |
| unsigned char *ptr = data; | |
| struct virtio_sg sg; | |
| struct virtio_sg *sgs[1]; | |
| struct virtio_rng_priv *priv = dev_get_priv(dev); | |
| while (len) { | |
| sg.addr = buf; | |
| /* | |
| * Work around implementations which always return 8 bytes | |
| * less than requested, down to 0 bytes, which would | |
| * cause an endless loop otherwise. | |
| */ | |
| sg.length = min(rsize ? len : len + 8, sizeof(buf)); | |
| sgs[0] = &sg; | |
| ret = virtqueue_add(priv->rng_vq, sgs, 0, 1); | |
| if (ret) | |
| return ret; | |
| virtqueue_kick(priv->rng_vq); | |
| while (!virtqueue_get_buf(priv->rng_vq, &rsize)) | |
| ; | |
| if (rsize > sg.length) | |
| return -EIO; | |
| memcpy(ptr, buf, rsize); | |
| len -= rsize; | |
| ptr += rsize; | |
| } | |
| return 0; | |
| } | |
| static int virtio_rng_bind(struct udevice *dev) | |
| { | |
| struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent); | |
| /* Indicate what driver features we support */ | |
| virtio_driver_features_init(uc_priv, NULL, 0, NULL, 0); | |
| return 0; | |
| } | |
| static int virtio_rng_probe(struct udevice *dev) | |
| { | |
| struct virtio_rng_priv *priv = dev_get_priv(dev); | |
| int ret; | |
| ret = virtio_find_vqs(dev, 1, &priv->rng_vq); | |
| if (ret < 0) { | |
| debug("%s: virtio_find_vqs failed\n", __func__); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static const struct dm_rng_ops virtio_rng_ops = { | |
| .read = virtio_rng_read, | |
| }; | |
| U_BOOT_DRIVER(virtio_rng) = { | |
| .name = VIRTIO_RNG_DRV_NAME, | |
| .id = UCLASS_RNG, | |
| .bind = virtio_rng_bind, | |
| .probe = virtio_rng_probe, | |
| .remove = virtio_reset, | |
| .ops = &virtio_rng_ops, | |
| .priv_auto = sizeof(struct virtio_rng_priv), | |
| .flags = DM_FLAG_ACTIVE_DMA, | |
| }; |