diff options
Diffstat (limited to 'drivers/virtio')
| -rw-r--r-- | drivers/virtio/virtio-uclass.c | 4 | ||||
| -rw-r--r-- | drivers/virtio/virtio_blk.c | 134 | ||||
| -rw-r--r-- | drivers/virtio/virtio_mmio.c | 33 |
3 files changed, 125 insertions, 46 deletions
diff --git a/drivers/virtio/virtio-uclass.c b/drivers/virtio/virtio-uclass.c index c36e9e9b3a7..a871a1439d4 100644 --- a/drivers/virtio/virtio-uclass.c +++ b/drivers/virtio/virtio-uclass.c @@ -400,9 +400,6 @@ UCLASS_DRIVER(virtio) = { .per_device_auto = sizeof(struct virtio_dev_priv), }; -struct bootdev_ops virtio_bootdev_ops = { -}; - static const struct udevice_id virtio_bootdev_ids[] = { { .compatible = "u-boot,bootdev-virtio" }, { } @@ -411,7 +408,6 @@ static const struct udevice_id virtio_bootdev_ids[] = { U_BOOT_DRIVER(virtio_bootdev) = { .name = "virtio_bootdev", .id = UCLASS_BOOTDEV, - .ops = &virtio_bootdev_ops, .bind = virtio_bootdev_bind, .of_match = virtio_bootdev_ids, }; diff --git a/drivers/virtio/virtio_blk.c b/drivers/virtio/virtio_blk.c index 3dd0cf36268..404d9140cb2 100644 --- a/drivers/virtio/virtio_blk.c +++ b/drivers/virtio/virtio_blk.c @@ -13,7 +13,9 @@ #include <virtio.h> #include <virtio_ring.h> #include <linux/log2.h> +#include <linux/err.h> #include "virtio_blk.h" +#include <malloc.h> /** * struct virtio_blk_priv - private data for virtio block device @@ -23,10 +25,16 @@ struct virtio_blk_priv { struct virtqueue *vq; /** @blksz_shift - log2 of block size divided by 512 */ u32 blksz_shift; + /** @size_max - maximum segment size */ + u32 size_max; + /** @seg_max - maximum segment count */ + u32 seg_max; }; static const u32 feature[] = { VIRTIO_BLK_F_BLK_SIZE, + VIRTIO_BLK_F_SIZE_MAX, + VIRTIO_BLK_F_SEG_MAX, VIRTIO_BLK_F_WRITE_ZEROES }; @@ -67,50 +75,77 @@ static void virtio_blk_init_data_sg(void *buffer, lbaint_t blkcnt, struct virtio sg->length = blkcnt * 512; } -static ulong virtio_blk_do_req(struct udevice *dev, u64 sector, - lbaint_t blkcnt, void *buffer, u32 type) +/* + * Create, execute and wait for one single virtio request. On success the + * transferred block count is returned and in the error case -EIO. + */ +static ulong virtio_blk_do_single_req(struct udevice *dev, u64 sector, + lbaint_t blkcnt, char *buffer, u32 type) { struct virtio_blk_priv *priv = dev_get_priv(dev); + /* + * The virtio device may have constrains on the maximum segment size. + * Calculate how many segments we need. + */ + u32 seg_cnt = (blkcnt * 512) / priv->size_max + 1; + lbaint_t seg_sec_cnt = priv->size_max / 512; struct virtio_blk_outhdr out_hdr; struct virtio_blk_discard_write_zeroes wz_hdr; unsigned int num_out = 0, num_in = 0; - struct virtio_sg hdr_sg, wz_sg, data_sg, status_sg; - struct virtio_sg *sgs[3]; - u8 status; + struct virtio_sg **sgs; + u8 status = VIRTIO_BLK_S_IOERR; int ret; + u32 i; - sector <<= priv->blksz_shift; - blkcnt <<= priv->blksz_shift; - virtio_blk_init_header_sg(dev, sector, type, &out_hdr, &hdr_sg); - sgs[num_out++] = &hdr_sg; + /* + * +2 is header and status descriptor; seg_cnt is the number of data segments + * required. Needs to be dynamically allocated. + */ + sgs = calloc(seg_cnt + 2, sizeof(struct virtio_sg *)); + if (!sgs) + return -ENOMEM; + + for (i = 0; i < seg_cnt + 2; ++i) { + sgs[i] = malloc(sizeof(struct virtio_sg)); + if (!sgs[i]) + goto err_free; + } + + virtio_blk_init_header_sg(dev, sector, type, &out_hdr, sgs[num_out++]); switch (type) { case VIRTIO_BLK_T_IN: - case VIRTIO_BLK_T_OUT: - virtio_blk_init_data_sg(buffer, blkcnt, &data_sg); - if (type & VIRTIO_BLK_T_OUT) - sgs[num_out++] = &data_sg; - else - sgs[num_out + num_in++] = &data_sg; - break; + case VIRTIO_BLK_T_OUT: { + i = 0; + while (i < blkcnt) { + u32 blk_per_seg = min(blkcnt - i, seg_sec_cnt); + if (type & VIRTIO_BLK_T_OUT) + virtio_blk_init_data_sg(buffer + i * 512, blk_per_seg, + sgs[num_out++]); + else + virtio_blk_init_data_sg(buffer + i * 512, blk_per_seg, + sgs[num_out + num_in++]); + i += blk_per_seg; + } + break; + } case VIRTIO_BLK_T_WRITE_ZEROES: - virtio_blk_init_write_zeroes_sg(dev, sector, blkcnt, &wz_hdr, &wz_sg); - sgs[num_out++] = &wz_sg; + virtio_blk_init_write_zeroes_sg(dev, sector, blkcnt, &wz_hdr, + sgs[num_out++]); break; default: - return -EINVAL; + goto err_free; } - virtio_blk_init_status_sg(&status, &status_sg); - sgs[num_out + num_in++] = &status_sg; + virtio_blk_init_status_sg(&status, sgs[num_out + num_in++]); log_debug("dev=%s, active=%d, priv=%p, priv->vq=%p\n", dev->name, device_active(dev), priv, priv->vq); ret = virtqueue_add(priv->vq, sgs, num_out, num_in); if (ret) - return ret; + goto err_free; virtqueue_kick(priv->vq); @@ -119,7 +154,40 @@ static ulong virtio_blk_do_req(struct udevice *dev, u64 sector, ; log_debug("done\n"); - return status == VIRTIO_BLK_S_OK ? blkcnt >> priv->blksz_shift : -EIO; +err_free: + for (i = 0; i < seg_cnt + 2; ++i) + free(sgs[i]); + free(sgs); + + return status == VIRTIO_BLK_S_OK ? blkcnt : -EIO; +} + +static ulong virtio_blk_do_req(struct udevice *dev, u64 sector, + lbaint_t blkcnt, char *buffer, u32 type) +{ + struct virtio_blk_priv *priv = dev_get_priv(dev); + lbaint_t seg_sec_cnt = priv->size_max / 512; + u32 i = 0; + ulong ret; + + sector <<= priv->blksz_shift; + blkcnt <<= priv->blksz_shift; + + /* + * The virtio device may have constrains on the maximum segment count. So + * send multiple virtio requests one after each other, if so. + */ + while (i < blkcnt) { + u32 blk_per_sg = min(blkcnt - i, seg_sec_cnt * priv->seg_max); + + ret = virtio_blk_do_single_req(dev, sector + i, blk_per_sg, + buffer + i * 512, type); + if (IS_ERR_VALUE(ret)) + return ret; + i += blk_per_sg; + } + + return blkcnt >> priv->blksz_shift; } static ulong virtio_blk_read(struct udevice *dev, lbaint_t start, @@ -164,14 +232,11 @@ static int virtio_blk_bind(struct udevice *dev) return devnum; desc->devnum = devnum; desc->part_type = PART_TYPE_UNKNOWN; - /* - * virtio mmio transport supplies string identification for us, - * while pci trnasport uses a 2-byte subvendor value. - */ - if (uc_priv->vendor >> 16) - sprintf(desc->vendor, "%s", (char *)&uc_priv->vendor); + + if (uc_priv->vendor == VIRTIO_VENDOR_QEMU) + strcpy(desc->vendor, "QEMU"); else - sprintf(desc->vendor, "%04x", uc_priv->vendor); + sprintf(desc->vendor, "%08x", uc_priv->vendor); desc->bdev = dev; /* Indicate what driver features we support */ @@ -207,6 +272,15 @@ static int virtio_blk_probe(struct udevice *dev) priv->blksz_shift = desc->log2blksz - 9; desc->lba >>= priv->blksz_shift; + if (virtio_has_feature(dev, VIRTIO_BLK_F_SIZE_MAX)) + virtio_cread(dev, struct virtio_blk_config, size_max, &priv->size_max); + else + priv->size_max = -1U; + if (virtio_has_feature(dev, VIRTIO_BLK_F_SEG_MAX)) + virtio_cread(dev, struct virtio_blk_config, seg_max, &priv->seg_max); + else + priv->seg_max = -1U; + return 0; } diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c index 1cd737aca24..975f98cd9e5 100644 --- a/drivers/virtio/virtio_mmio.c +++ b/drivers/virtio/virtio_mmio.c @@ -12,6 +12,7 @@ #include <virtio_types.h> #include <virtio.h> #include <virtio_ring.h> +#include <virtio_mmio.h> #include <linux/bug.h> #include <linux/compat.h> #include <linux/err.h> @@ -335,26 +336,33 @@ static int virtio_mmio_notify(struct udevice *udev, struct virtqueue *vq) static int virtio_mmio_of_to_plat(struct udevice *udev) { - struct virtio_mmio_priv *priv = dev_get_priv(udev); + struct virtio_mmio_plat *plat = dev_get_plat(udev); + fdt_addr_t addr; + + addr = dev_read_addr(udev); - priv->base = (void __iomem *)(ulong)dev_read_addr(udev); - if (priv->base == (void __iomem *)FDT_ADDR_T_NONE) + if (addr == FDT_ADDR_T_NONE) return -EINVAL; + plat->base = addr; + return 0; } static int virtio_mmio_probe(struct udevice *udev) { + struct virtio_mmio_plat *plat = dev_get_plat(udev); struct virtio_mmio_priv *priv = dev_get_priv(udev); struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev); u32 magic; + priv->base = (void __iomem *)(uintptr_t)plat->base; + /* Check magic value */ magic = readl(priv->base + VIRTIO_MMIO_MAGIC_VALUE); if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)) { debug("(%s): wrong magic value 0x%08x!\n", udev->name, magic); - return 0; + return -ENODEV; } /* Check device version */ @@ -362,7 +370,7 @@ static int virtio_mmio_probe(struct udevice *udev) if (priv->version < 1 || priv->version > 2) { debug("(%s): version %d not supported!\n", udev->name, priv->version); - return 0; + return -ENXIO; } /* Check device ID */ @@ -372,7 +380,7 @@ static int virtio_mmio_probe(struct udevice *udev) * virtio-mmio device with an ID 0 is a (dummy) placeholder * with no function. End probing now with no error reported. */ - return 0; + return -ENOENT; } uc_priv->vendor = readl(priv->base + VIRTIO_MMIO_VENDOR_ID); @@ -405,11 +413,12 @@ static const struct udevice_id virtio_mmio_ids[] = { }; U_BOOT_DRIVER(virtio_mmio) = { - .name = "virtio-mmio", - .id = UCLASS_VIRTIO, - .of_match = virtio_mmio_ids, - .ops = &virtio_mmio_ops, - .probe = virtio_mmio_probe, + .name = "virtio-mmio", + .id = UCLASS_VIRTIO, + .of_match = virtio_mmio_ids, + .ops = &virtio_mmio_ops, + .probe = virtio_mmio_probe, .of_to_plat = virtio_mmio_of_to_plat, - .priv_auto = sizeof(struct virtio_mmio_priv), + .priv_auto = sizeof(struct virtio_mmio_priv), + .plat_auto = sizeof(struct virtio_mmio_plat), }; |
