| /****************************************************************************** |
| * Copyright (c) 2011 IBM Corporation |
| * All rights reserved. |
| * This program and the accompanying materials |
| * are made available under the terms of the BSD License |
| * which accompanies this distribution, and is available at |
| * http://www.opensource.org/licenses/bsd-license.php |
| * |
| * Contributors: |
| * IBM Corporation - initial implementation |
| *****************************************************************************/ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stddef.h> |
| #include <cpu.h> |
| #include <cache.h> |
| #include <byteorder.h> |
| #include "virtio.h" |
| #include "helpers.h" |
| #include "virtio-internal.h" |
| |
| /* PCI virtio header offsets */ |
| #define VIRTIOHDR_DEVICE_FEATURES 0 |
| #define VIRTIOHDR_GUEST_FEATURES 4 |
| #define VIRTIOHDR_QUEUE_ADDRESS 8 |
| #define VIRTIOHDR_QUEUE_SIZE 12 |
| #define VIRTIOHDR_QUEUE_SELECT 14 |
| #define VIRTIOHDR_QUEUE_NOTIFY 16 |
| #define VIRTIOHDR_DEVICE_STATUS 18 |
| #define VIRTIOHDR_ISR_STATUS 19 |
| #define VIRTIOHDR_DEVICE_CONFIG 20 |
| |
| /* PCI defines */ |
| #define PCI_BASE_ADDR_SPACE_IO 0x01 |
| #define PCI_BASE_ADDR_SPACE_64BIT 0x04 |
| #define PCI_BASE_ADDR_MEM_MASK (~0x0fUL) |
| #define PCI_BASE_ADDR_IO_MASK (~0x03UL) |
| |
| #define PCI_BASE_ADDR_REG_0 0x10 |
| #define PCI_CONFIG_CAP_REG 0x34 |
| |
| #define PCI_CAP_ID_VNDR 0x9 |
| |
| /* Common configuration */ |
| #define VIRTIO_PCI_CAP_COMMON_CFG 1 |
| /* Notifications */ |
| #define VIRTIO_PCI_CAP_NOTIFY_CFG 2 |
| /* ISR access */ |
| #define VIRTIO_PCI_CAP_ISR_CFG 3 |
| /* Device specific configuration */ |
| #define VIRTIO_PCI_CAP_DEVICE_CFG 4 |
| /* PCI configuration access */ |
| #define VIRTIO_PCI_CAP_PCI_CFG 5 |
| |
| #define VIRTIO_PCI_CAP_VNDR 0 /* Generic PCI field: PCI_CAP_ID_VNDR */ |
| #define VIRTIO_PCI_CAP_NEXT 1 /* Generic PCI field: next ptr. */ |
| #define VIRTIO_PCI_CAP_LEN 2 /* Generic PCI field: capability length */ |
| #define VIRTIO_PCI_CAP_CFG_TYPE 3 /* Identifies the structure. */ |
| #define VIRTIO_PCI_CAP_BAR 4 /* Where to find it. */ |
| #define VIRTIO_PCI_CAP_OFFSET 8 /* Offset within bar. */ |
| #define VIRTIO_PCI_CAP_LENGTH 12 /* Length of the structure, in bytes. */ |
| |
| struct virtio_dev_common { |
| le32 dev_features_sel; |
| le32 dev_features; |
| le32 drv_features_sel; |
| le32 drv_features; |
| le16 msix_config; |
| le16 num_queues; |
| uint8_t dev_status; |
| uint8_t cfg_generation; |
| |
| le16 q_select; |
| le16 q_size; |
| le16 q_msix_vec; |
| le16 q_enable; |
| le16 q_notify_off; |
| le64 q_desc; |
| le64 q_avail; |
| le64 q_used; |
| } __attribute__ ((packed)); |
| |
| /* virtio 1.0 Spec: 4.1.3 PCI Device Layout |
| * |
| * Fields of different sizes are present in the device configuration regions. |
| * All 64-bit, 32-bit and 16-bit fields are little-endian. 64-bit fields are to |
| * be treated as two 32-bit fields, with low 32 bit part followed by the high 32 |
| * bit part. |
| */ |
| static void virtio_pci_write64(void *addr, uint64_t val) |
| { |
| uint32_t hi = (val >> 32) & 0xFFFFFFFF; |
| uint32_t lo = val & 0xFFFFFFFF; |
| |
| ci_write_32(addr, cpu_to_le32(lo)); |
| ci_write_32(addr + 4, cpu_to_le32(hi)); |
| } |
| |
| static void virtio_cap_set_base_addr(struct virtio_cap *cap, uint32_t offset) |
| { |
| uint64_t addr; |
| |
| addr = SLOF_pci_config_read32(PCI_BASE_ADDR_REG_0 + 4 * cap->bar); |
| if (addr & PCI_BASE_ADDR_SPACE_IO) { |
| addr = addr & PCI_BASE_ADDR_IO_MASK; |
| cap->is_io = 1; |
| } else { |
| if (addr & PCI_BASE_ADDR_SPACE_64BIT) |
| addr |= SLOF_pci_config_read32(PCI_BASE_ADDR_REG_0 + 4 * (cap->bar + 1)) << 32; |
| addr = addr & PCI_BASE_ADDR_MEM_MASK; |
| cap->is_io = 0; |
| } |
| addr = (uint64_t)SLOF_translate_my_address((void *)addr); |
| cap->addr = (void *)addr + offset; |
| } |
| |
| static void virtio_process_cap(struct virtio_device *dev, uint8_t cap_ptr) |
| { |
| struct virtio_cap *cap; |
| uint8_t cfg_type, bar; |
| uint32_t offset; |
| |
| cfg_type = SLOF_pci_config_read8(cap_ptr + VIRTIO_PCI_CAP_CFG_TYPE); |
| bar = SLOF_pci_config_read8(cap_ptr + VIRTIO_PCI_CAP_BAR); |
| offset = SLOF_pci_config_read32(cap_ptr + VIRTIO_PCI_CAP_OFFSET); |
| |
| switch(cfg_type) { |
| case VIRTIO_PCI_CAP_COMMON_CFG: |
| cap = &dev->common; |
| break; |
| case VIRTIO_PCI_CAP_NOTIFY_CFG: |
| cap = &dev->notify; |
| dev->notify_off_mul = SLOF_pci_config_read32(cap_ptr + sizeof(struct virtio_cap)); |
| break; |
| case VIRTIO_PCI_CAP_ISR_CFG: |
| cap = &dev->isr; |
| break; |
| case VIRTIO_PCI_CAP_DEVICE_CFG: |
| cap = &dev->device; |
| break; |
| default: |
| return; |
| } |
| |
| cap->bar = bar; |
| virtio_cap_set_base_addr(cap, offset); |
| cap->cap_id = cfg_type; |
| } |
| |
| /** |
| * Reads the virtio device capabilities, gets called from SLOF routines The |
| * function determines legacy or modern device and sets up driver registers |
| */ |
| struct virtio_device *virtio_setup_vd(void) |
| { |
| uint8_t cap_ptr, cap_vndr; |
| struct virtio_device *dev; |
| |
| dev = SLOF_alloc_mem(sizeof(struct virtio_device)); |
| if (!dev) { |
| printf("Failed to allocate memory"); |
| return NULL; |
| } |
| |
| cap_ptr = SLOF_pci_config_read8(PCI_CONFIG_CAP_REG); |
| while (cap_ptr != 0) { |
| cap_vndr = SLOF_pci_config_read8(cap_ptr + VIRTIO_PCI_CAP_VNDR); |
| if (cap_vndr == PCI_CAP_ID_VNDR) |
| virtio_process_cap(dev, cap_ptr); |
| cap_ptr = SLOF_pci_config_read8(cap_ptr+VIRTIO_PCI_CAP_NEXT); |
| } |
| |
| if (dev->common.cap_id && dev->notify.cap_id && |
| dev->isr.cap_id && dev->device.cap_id) { |
| dev->features = VIRTIO_F_VERSION_1; |
| } else { |
| dev->features = 0; |
| dev->legacy.cap_id = 0; |
| dev->legacy.bar = 0; |
| virtio_cap_set_base_addr(&dev->legacy, 0); |
| } |
| return dev; |
| } |
| |
| /** |
| * Calculate ring size according to queue size number |
| */ |
| unsigned long virtio_vring_size(unsigned int qsize) |
| { |
| return VQ_ALIGN(sizeof(struct vring_desc) * qsize + |
| sizeof(struct vring_avail) + sizeof(uint16_t) * qsize) + |
| VQ_ALIGN(sizeof(struct vring_used) + |
| sizeof(struct vring_used_elem) * qsize); |
| } |
| |
| |
| /** |
| * Get number of elements in a vring |
| * @param dev pointer to virtio device information |
| * @param queue virtio queue number |
| * @return number of elements |
| */ |
| unsigned int virtio_get_qsize(struct virtio_device *dev, int queue) |
| { |
| unsigned int size = 0; |
| |
| if (dev->features & VIRTIO_F_VERSION_1) { |
| void *addr = dev->common.addr + offset_of(struct virtio_dev_common, q_select); |
| ci_write_16(addr, cpu_to_le16(queue)); |
| eieio(); |
| addr = dev->common.addr + offset_of(struct virtio_dev_common, q_size); |
| size = le16_to_cpu(ci_read_16(addr)); |
| } |
| else { |
| ci_write_16(dev->legacy.addr+VIRTIOHDR_QUEUE_SELECT, |
| cpu_to_le16(queue)); |
| eieio(); |
| size = le16_to_cpu(ci_read_16(dev->legacy.addr+VIRTIOHDR_QUEUE_SIZE)); |
| } |
| |
| return size; |
| } |
| |
| |
| /** |
| * Get address of descriptor vring |
| * @param dev pointer to virtio device information |
| * @param queue virtio queue number |
| * @return pointer to the descriptor ring |
| */ |
| struct vring_desc *virtio_get_vring_desc(struct virtio_device *dev, int queue) |
| { |
| return dev->vq[queue].desc; |
| } |
| |
| |
| /** |
| * Get address of "available" vring |
| * @param dev pointer to virtio device information |
| * @param queue virtio queue number |
| * @return pointer to the "available" ring |
| */ |
| struct vring_avail *virtio_get_vring_avail(struct virtio_device *dev, int queue) |
| { |
| return dev->vq[queue].avail; |
| } |
| |
| |
| /** |
| * Get address of "used" vring |
| * @param dev pointer to virtio device information |
| * @param queue virtio queue number |
| * @return pointer to the "used" ring |
| */ |
| struct vring_used *virtio_get_vring_used(struct virtio_device *dev, int queue) |
| { |
| return dev->vq[queue].used; |
| } |
| |
| /** |
| * Fill the virtio ring descriptor depending on the legacy mode or virtio 1.0 |
| */ |
| void virtio_fill_desc(struct vqs *vq, int id, uint64_t features, |
| uint64_t addr, uint32_t len, |
| uint16_t flags, uint16_t next) |
| { |
| struct vring_desc *desc; |
| |
| id %= vq->size; |
| desc = &vq->desc[id]; |
| next %= vq->size; |
| |
| if (features & VIRTIO_F_VERSION_1) { |
| if (features & VIRTIO_F_IOMMU_PLATFORM) { |
| void *gpa = (void *) addr; |
| |
| if (!vq->desc_gpas) { |
| fprintf(stderr, "IOMMU setup has not been done!\n"); |
| return; |
| } |
| |
| addr = SLOF_dma_map_in(gpa, len, 0); |
| vq->desc_gpas[id] = gpa; |
| } |
| desc->addr = cpu_to_le64(addr); |
| desc->len = cpu_to_le32(len); |
| desc->flags = cpu_to_le16(flags); |
| desc->next = cpu_to_le16(next); |
| } else { |
| desc->addr = addr; |
| desc->len = len; |
| desc->flags = flags; |
| desc->next = next; |
| } |
| } |
| |
| void virtio_free_desc(struct vqs *vq, int id, uint64_t features) |
| { |
| struct vring_desc *desc; |
| |
| id %= vq->size; |
| desc = &vq->desc[id]; |
| |
| if (!(features & VIRTIO_F_VERSION_1) || |
| !(features & VIRTIO_F_IOMMU_PLATFORM)) |
| return; |
| |
| if (!vq->desc_gpas[id]) |
| return; |
| |
| SLOF_dma_map_out(le64_to_cpu(desc->addr), 0, le32_to_cpu(desc->len)); |
| vq->desc_gpas[id] = NULL; |
| } |
| |
| void *virtio_desc_addr(struct virtio_device *vdev, int queue, int id) |
| { |
| struct vqs *vq = &vdev->vq[queue]; |
| |
| if (vq->desc_gpas) |
| return vq->desc_gpas[id]; |
| |
| return (void *) virtio_modern64_to_cpu(vdev, vq->desc[id].addr); |
| } |
| |
| /** |
| * Reset virtio device |
| */ |
| void virtio_reset_device(struct virtio_device *dev) |
| { |
| virtio_set_status(dev, 0); |
| } |
| |
| |
| /** |
| * Notify hypervisor about queue update |
| */ |
| void virtio_queue_notify(struct virtio_device *dev, int queue) |
| { |
| if (dev->features & VIRTIO_F_VERSION_1) { |
| void *q_sel = dev->common.addr + offset_of(struct virtio_dev_common, q_select); |
| void *q_ntfy = dev->common.addr + offset_of(struct virtio_dev_common, q_notify_off); |
| void *addr; |
| uint16_t q_notify_off; |
| |
| ci_write_16(q_sel, cpu_to_le16(queue)); |
| eieio(); |
| q_notify_off = le16_to_cpu(ci_read_16(q_ntfy)); |
| addr = dev->notify.addr + q_notify_off * dev->notify_off_mul; |
| ci_write_16(addr, cpu_to_le16(queue)); |
| } else { |
| ci_write_16(dev->legacy.addr+VIRTIOHDR_QUEUE_NOTIFY, cpu_to_le16(queue)); |
| } |
| } |
| |
| /** |
| * Set queue address |
| */ |
| static void virtio_set_qaddr(struct virtio_device *dev, int queue, unsigned long qaddr) |
| { |
| if (dev->features & VIRTIO_F_VERSION_1) { |
| uint64_t q_desc = qaddr; |
| uint64_t q_avail; |
| uint64_t q_used; |
| uint32_t q_size = virtio_get_qsize(dev, queue); |
| |
| if (dev->features & VIRTIO_F_IOMMU_PLATFORM) { |
| unsigned long cb; |
| |
| cb = q_size * sizeof(struct vring_desc); |
| cb += sizeof(struct vring_avail) + |
| sizeof(uint16_t) * q_size; |
| cb = VQ_ALIGN(cb); |
| cb += sizeof(struct vring_used) + |
| sizeof(struct vring_used_elem) * q_size; |
| cb = VQ_ALIGN(cb); |
| q_desc = SLOF_dma_map_in((void *)q_desc, cb, 0); |
| |
| dev->vq[queue].bus_desc = q_desc; |
| } |
| |
| virtio_pci_write64(dev->common.addr + offset_of(struct virtio_dev_common, q_desc), q_desc); |
| q_avail = q_desc + q_size * sizeof(struct vring_desc); |
| virtio_pci_write64(dev->common.addr + offset_of(struct virtio_dev_common, q_avail), q_avail); |
| q_used = VQ_ALIGN(q_avail + sizeof(struct vring_avail) + sizeof(uint16_t) * q_size); |
| virtio_pci_write64(dev->common.addr + offset_of(struct virtio_dev_common, q_used), q_used); |
| ci_write_16(dev->common.addr + offset_of(struct virtio_dev_common, q_enable), cpu_to_le16(1)); |
| } else { |
| uint32_t val = qaddr; |
| val = val >> 12; |
| ci_write_16(dev->legacy.addr+VIRTIOHDR_QUEUE_SELECT, |
| cpu_to_le16(queue)); |
| eieio(); |
| ci_write_32(dev->legacy.addr+VIRTIOHDR_QUEUE_ADDRESS, |
| cpu_to_le32(val)); |
| } |
| } |
| |
| struct vqs *virtio_queue_init_vq(struct virtio_device *dev, unsigned int id) |
| { |
| struct vqs *vq; |
| |
| if (id >= sizeof(dev->vq)/sizeof(dev->vq[0])) { |
| printf("Queue index is too big!\n"); |
| return NULL; |
| } |
| vq = &dev->vq[id]; |
| |
| memset(vq, 0, sizeof(*vq)); |
| |
| vq->size = virtio_get_qsize(dev, id); |
| vq->desc = SLOF_alloc_mem_aligned(virtio_vring_size(vq->size), 4096); |
| if (!vq->desc) { |
| printf("memory allocation failed!\n"); |
| return NULL; |
| } |
| |
| vq->avail = (void *) vq->desc + vq->size * sizeof(struct vring_desc); |
| vq->used = (void *) VQ_ALIGN((unsigned long) vq->avail + |
| sizeof(struct vring_avail) + |
| sizeof(uint16_t) * vq->size); |
| |
| memset(vq->desc, 0, virtio_vring_size(vq->size)); |
| virtio_set_qaddr(dev, id, (unsigned long)vq->desc); |
| |
| vq->avail->flags = virtio_cpu_to_modern16(dev, VRING_AVAIL_F_NO_INTERRUPT); |
| vq->avail->idx = 0; |
| if (dev->features & VIRTIO_F_IOMMU_PLATFORM) |
| vq->desc_gpas = SLOF_alloc_mem_aligned( |
| vq->size * sizeof(vq->desc_gpas[0]), 4096); |
| |
| return vq; |
| } |
| |
| void virtio_queue_term_vq(struct virtio_device *dev, struct vqs *vq, unsigned int id) |
| { |
| if (vq->desc_gpas) { |
| unsigned i; |
| |
| for (i = 0; i < vq->size; ++i) |
| virtio_free_desc(vq, i, dev->features); |
| |
| SLOF_free_mem(vq->desc_gpas, |
| vq->size * sizeof(vq->desc_gpas[0])); |
| } |
| if (vq->desc) { |
| if (dev->features & VIRTIO_F_IOMMU_PLATFORM) { |
| unsigned long cb; |
| uint32_t q_size = virtio_get_qsize(dev, id); |
| |
| cb = q_size * sizeof(struct vring_desc); |
| cb += sizeof(struct vring_avail) + |
| sizeof(uint16_t) * q_size; |
| cb = VQ_ALIGN(cb); |
| cb += sizeof(struct vring_used) + |
| sizeof(struct vring_used_elem) * q_size; |
| cb = VQ_ALIGN(cb); |
| |
| SLOF_dma_map_out(vq->bus_desc, 0, cb); |
| } |
| |
| SLOF_free_mem(vq->desc, virtio_vring_size(vq->size)); |
| } |
| memset(vq, 0, sizeof(*vq)); |
| } |
| |
| /** |
| * Set device status bits |
| */ |
| void virtio_set_status(struct virtio_device *dev, int status) |
| { |
| if (dev->features & VIRTIO_F_VERSION_1) { |
| ci_write_8(dev->common.addr + |
| offset_of(struct virtio_dev_common, dev_status), status); |
| } else { |
| ci_write_8(dev->legacy.addr+VIRTIOHDR_DEVICE_STATUS, status); |
| } |
| } |
| |
| /** |
| * Get device status bits |
| */ |
| void virtio_get_status(struct virtio_device *dev, int *status) |
| { |
| if (dev->features & VIRTIO_F_VERSION_1) { |
| *status = ci_read_8(dev->common.addr + |
| offset_of(struct virtio_dev_common, dev_status)); |
| } else { |
| *status = ci_read_8(dev->legacy.addr+VIRTIOHDR_DEVICE_STATUS); |
| } |
| } |
| |
| /** |
| * Set guest feature bits |
| */ |
| void virtio_set_guest_features(struct virtio_device *dev, uint64_t features) |
| |
| { |
| if (dev->features & VIRTIO_F_VERSION_1) { |
| uint32_t f1 = (features >> 32) & 0xFFFFFFFF; |
| uint32_t f0 = features & 0xFFFFFFFF; |
| void *addr = dev->common.addr; |
| |
| ci_write_32(addr + offset_of(struct virtio_dev_common, drv_features_sel), |
| cpu_to_le32(1)); |
| ci_write_32(addr + offset_of(struct virtio_dev_common, drv_features), |
| cpu_to_le32(f1)); |
| |
| ci_write_32(addr + offset_of(struct virtio_dev_common, drv_features_sel), |
| cpu_to_le32(0)); |
| ci_write_32(addr + offset_of(struct virtio_dev_common, drv_features), |
| cpu_to_le32(f0)); |
| } else { |
| ci_write_32(dev->legacy.addr+VIRTIOHDR_GUEST_FEATURES, cpu_to_le32(features)); |
| } |
| } |
| |
| /** |
| * Get host feature bits |
| */ |
| uint64_t virtio_get_host_features(struct virtio_device *dev) |
| |
| { |
| uint64_t features = 0; |
| if (dev->features & VIRTIO_F_VERSION_1) { |
| uint32_t f0 = 0, f1 = 0; |
| void *addr = dev->common.addr; |
| |
| ci_write_32(addr + offset_of(struct virtio_dev_common, dev_features_sel), |
| cpu_to_le32(1)); |
| f1 = ci_read_32(addr + |
| offset_of(struct virtio_dev_common, dev_features)); |
| ci_write_32(addr + offset_of(struct virtio_dev_common, dev_features_sel), |
| cpu_to_le32(0)); |
| f0 = ci_read_32(addr + |
| offset_of(struct virtio_dev_common, dev_features)); |
| |
| features = ((uint64_t)le32_to_cpu(f1) << 32) | le32_to_cpu(f0); |
| } else { |
| features = le32_to_cpu(ci_read_32(dev->legacy.addr+VIRTIOHDR_DEVICE_FEATURES)); |
| } |
| return features; |
| } |
| |
| int virtio_negotiate_guest_features(struct virtio_device *dev, uint64_t features) |
| { |
| uint64_t host_features = 0; |
| int status; |
| |
| /* Negotiate features */ |
| host_features = virtio_get_host_features(dev); |
| if (!(host_features & VIRTIO_F_VERSION_1)) { |
| fprintf(stderr, "Device does not support virtio 1.0 %llx\n", host_features); |
| return -1; |
| } |
| |
| if (host_features & VIRTIO_F_IOMMU_PLATFORM) |
| features |= VIRTIO_F_IOMMU_PLATFORM; |
| |
| virtio_set_guest_features(dev, features); |
| host_features = virtio_get_host_features(dev); |
| if ((host_features & features) != features) { |
| fprintf(stderr, "Features error %llx\n", features); |
| return -1; |
| } |
| |
| virtio_get_status(dev, &status); |
| status |= VIRTIO_STAT_FEATURES_OK; |
| virtio_set_status(dev, status); |
| |
| /* Read back to verify the FEATURES_OK bit */ |
| virtio_get_status(dev, &status); |
| if ((status & VIRTIO_STAT_FEATURES_OK) != VIRTIO_STAT_FEATURES_OK) |
| return -1; |
| |
| dev->features = features; |
| |
| return 0; |
| } |
| |
| /** |
| * Get additional config values |
| */ |
| uint64_t virtio_get_config(struct virtio_device *dev, int offset, int size) |
| { |
| uint64_t val = ~0ULL; |
| uint32_t hi, lo; |
| void *confbase; |
| |
| if (dev->features & VIRTIO_F_VERSION_1) |
| confbase = dev->device.addr; |
| else |
| confbase = dev->legacy.addr+VIRTIOHDR_DEVICE_CONFIG; |
| |
| switch (size) { |
| case 1: |
| val = ci_read_8(confbase+offset); |
| break; |
| case 2: |
| val = ci_read_16(confbase+offset); |
| if (dev->features & VIRTIO_F_VERSION_1) |
| val = le16_to_cpu(val); |
| break; |
| case 4: |
| val = ci_read_32(confbase+offset); |
| if (dev->features & VIRTIO_F_VERSION_1) |
| val = le32_to_cpu(val); |
| break; |
| case 8: |
| /* We don't support 8 bytes PIO accesses |
| * in qemu and this is all PIO |
| */ |
| lo = ci_read_32(confbase+offset); |
| hi = ci_read_32(confbase+offset+4); |
| if (dev->features & VIRTIO_F_VERSION_1) |
| val = (uint64_t)le32_to_cpu(hi) << 32 | le32_to_cpu(lo); |
| else |
| val = (uint64_t)hi << 32 | lo; |
| break; |
| } |
| |
| return val; |
| } |
| |
| /** |
| * Get config blob |
| */ |
| int __virtio_read_config(struct virtio_device *dev, void *dst, |
| int offset, int len) |
| { |
| void *confbase; |
| unsigned char *buf = dst; |
| int i; |
| |
| if (dev->features & VIRTIO_F_VERSION_1) |
| confbase = dev->device.addr; |
| else |
| confbase = dev->legacy.addr+VIRTIOHDR_DEVICE_CONFIG; |
| |
| for (i = 0; i < len; i++) |
| buf[i] = ci_read_8(confbase + offset + i); |
| |
| return len; |
| } |