/* * VIRTIO driver * * Copyright (c) 2016 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include "cutils.h" #include "list.h" #include "virtio.h" //#define DEBUG_VIRTIO /* MMIO addresses - from the Linux kernel */ #define VIRTIO_MMIO_MAGIC_VALUE 0x000 #define VIRTIO_MMIO_VERSION 0x004 #define VIRTIO_MMIO_DEVICE_ID 0x008 #define VIRTIO_MMIO_VENDOR_ID 0x00c #define VIRTIO_MMIO_DEVICE_FEATURES 0x010 #define VIRTIO_MMIO_DEVICE_FEATURES_SEL 0x014 #define VIRTIO_MMIO_DRIVER_FEATURES 0x020 #define VIRTIO_MMIO_DRIVER_FEATURES_SEL 0x024 #define VIRTIO_MMIO_GUEST_PAGE_SIZE 0x028 /* version 1 only */ #define VIRTIO_MMIO_QUEUE_SEL 0x030 #define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 #define VIRTIO_MMIO_QUEUE_NUM 0x038 #define VIRTIO_MMIO_QUEUE_ALIGN 0x03c /* version 1 only */ #define VIRTIO_MMIO_QUEUE_PFN 0x040 /* version 1 only */ #define VIRTIO_MMIO_QUEUE_READY 0x044 #define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 #define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 #define VIRTIO_MMIO_INTERRUPT_ACK 0x064 #define VIRTIO_MMIO_STATUS 0x070 #define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 #define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084 #define VIRTIO_MMIO_QUEUE_AVAIL_LOW 0x090 #define VIRTIO_MMIO_QUEUE_AVAIL_HIGH 0x094 #define VIRTIO_MMIO_QUEUE_USED_LOW 0x0a0 #define VIRTIO_MMIO_QUEUE_USED_HIGH 0x0a4 #define VIRTIO_MMIO_CONFIG_GENERATION 0x0fc #define VIRTIO_MMIO_CONFIG 0x100 /* PCI registers */ #define VIRTIO_PCI_DEVICE_FEATURE_SEL 0x000 #define VIRTIO_PCI_DEVICE_FEATURE 0x004 #define VIRTIO_PCI_GUEST_FEATURE_SEL 0x008 #define VIRTIO_PCI_GUEST_FEATURE 0x00c #define VIRTIO_PCI_MSIX_CONFIG 0x010 #define VIRTIO_PCI_NUM_QUEUES 0x012 #define VIRTIO_PCI_DEVICE_STATUS 0x014 #define VIRTIO_PCI_CONFIG_GENERATION 0x015 #define VIRTIO_PCI_QUEUE_SEL 0x016 #define VIRTIO_PCI_QUEUE_SIZE 0x018 #define VIRTIO_PCI_QUEUE_MSIX_VECTOR 0x01a #define VIRTIO_PCI_QUEUE_ENABLE 0x01c #define VIRTIO_PCI_QUEUE_NOTIFY_OFF 0x01e #define VIRTIO_PCI_QUEUE_DESC_LOW 0x020 #define VIRTIO_PCI_QUEUE_DESC_HIGH 0x024 #define VIRTIO_PCI_QUEUE_AVAIL_LOW 0x028 #define VIRTIO_PCI_QUEUE_AVAIL_HIGH 0x02c #define VIRTIO_PCI_QUEUE_USED_LOW 0x030 #define VIRTIO_PCI_QUEUE_USED_HIGH 0x034 #define VIRTIO_PCI_CFG_OFFSET 0x0000 #define VIRTIO_PCI_ISR_OFFSET 0x1000 #define VIRTIO_PCI_CONFIG_OFFSET 0x2000 #define VIRTIO_PCI_NOTIFY_OFFSET 0x3000 #define VIRTIO_PCI_CAP_LEN 16 #define MAX_QUEUE 8 #define MAX_CONFIG_SPACE_SIZE 256 #define MAX_QUEUE_NUM 16 typedef struct { uint32_t ready; /* 0 or 1 */ uint32_t num; uint16_t last_avail_idx; virtio_phys_addr_t desc_addr; virtio_phys_addr_t avail_addr; virtio_phys_addr_t used_addr; BOOL manual_recv; /* if TRUE, the device_recv() callback is not called */ } QueueState; #define VRING_DESC_F_NEXT 1 #define VRING_DESC_F_WRITE 2 #define VRING_DESC_F_INDIRECT 4 typedef struct { uint64_t addr; uint32_t len; uint16_t flags; /* VRING_DESC_F_x */ uint16_t next; } VIRTIODesc; /* return < 0 to stop the notification (it must be manually restarted later), 0 if OK */ typedef int VIRTIODeviceRecvFunc(VIRTIODevice *s1, int queue_idx, int desc_idx, int read_size, int write_size); /* return NULL if no RAM at this address. The mapping is valid for one page */ typedef uint8_t *VIRTIOGetRAMPtrFunc(VIRTIODevice *s, virtio_phys_addr_t paddr, BOOL is_rw); struct VIRTIODevice { PhysMemoryMap *mem_map; PhysMemoryRange *mem_range; /* PCI only */ PCIDevice *pci_dev; /* MMIO only */ IRQSignal *irq; VIRTIOGetRAMPtrFunc *get_ram_ptr; int debug; uint32_t int_status; uint32_t status; uint32_t device_features_sel; uint32_t queue_sel; /* currently selected queue */ QueueState queue[MAX_QUEUE]; /* device specific */ uint32_t device_id; uint32_t vendor_id; uint32_t device_features; VIRTIODeviceRecvFunc *device_recv; void (*config_write)(VIRTIODevice *s); /* called after the config is written */ uint32_t config_space_size; /* in bytes, must be multiple of 4 */ uint8_t config_space[MAX_CONFIG_SPACE_SIZE]; }; static uint32_t virtio_mmio_read(void *opaque, uint32_t offset1, int size_log2); static void virtio_mmio_write(void *opaque, uint32_t offset, uint32_t val, int size_log2); static uint32_t virtio_pci_read(void *opaque, uint32_t offset, int size_log2); static void virtio_pci_write(void *opaque, uint32_t offset, uint32_t val, int size_log2); static void virtio_reset(VIRTIODevice *s) { int i; s->status = 0; s->queue_sel = 0; s->device_features_sel = 0; s->int_status = 0; for(i = 0; i < MAX_QUEUE; i++) { QueueState *qs = &s->queue[i]; qs->ready = 0; qs->num = MAX_QUEUE_NUM; qs->desc_addr = 0; qs->avail_addr = 0; qs->used_addr = 0; qs->last_avail_idx = 0; } } static uint8_t *virtio_pci_get_ram_ptr(VIRTIODevice *s, virtio_phys_addr_t paddr, BOOL is_rw) { return pci_device_get_dma_ptr(s->pci_dev, paddr, is_rw); } static uint8_t *virtio_mmio_get_ram_ptr(VIRTIODevice *s, virtio_phys_addr_t paddr, BOOL is_rw) { return phys_mem_get_ram_ptr(s->mem_map, paddr, is_rw); } static void virtio_add_pci_capability(VIRTIODevice *s, int cfg_type, int bar, uint32_t offset, uint32_t len, uint32_t mult) { uint8_t cap[20]; int cap_len; if (cfg_type == 2) cap_len = 20; else cap_len = 16; memset(cap, 0, cap_len); cap[0] = 0x09; /* vendor specific */ cap[2] = cap_len; /* set by pci_add_capability() */ cap[3] = cfg_type; cap[4] = bar; put_le32(cap + 8, offset); put_le32(cap + 12, len); if (cfg_type == 2) put_le32(cap + 16, mult); pci_add_capability(s->pci_dev, cap, cap_len); } static void virtio_pci_bar_set(void *opaque, int bar_num, uint32_t addr, BOOL enabled) { VIRTIODevice *s = opaque; phys_mem_set_addr(s->mem_range, addr, enabled); } static void virtio_init(VIRTIODevice *s, VIRTIOBusDef *bus, uint32_t device_id, int config_space_size, VIRTIODeviceRecvFunc *device_recv) { memset(s, 0, sizeof(*s)); if (bus->pci_bus) { uint16_t pci_device_id, class_id; char name[32]; int bar_num; switch(device_id) { case 1: pci_device_id = 0x1000; /* net */ class_id = 0x0200; break; case 2: pci_device_id = 0x1001; /* block */ class_id = 0x0100; /* XXX: check it */ break; case 3: pci_device_id = 0x1003; /* console */ class_id = 0x0780; break; case 9: pci_device_id = 0x1040 + device_id; /* use new device ID */ class_id = 0x2; break; case 18: pci_device_id = 0x1040 + device_id; /* use new device ID */ class_id = 0x0980; break; default: abort(); } snprintf(name, sizeof(name), "virtio_%04x", pci_device_id); s->pci_dev = pci_register_device(bus->pci_bus, name, -1, 0x1af4, pci_device_id, 0x00, class_id); pci_device_set_config16(s->pci_dev, 0x2c, 0x1af4); pci_device_set_config16(s->pci_dev, 0x2e, device_id); pci_device_set_config8(s->pci_dev, PCI_INTERRUPT_PIN, 1); bar_num = 4; virtio_add_pci_capability(s, 1, bar_num, VIRTIO_PCI_CFG_OFFSET, 0x1000, 0); /* common */ virtio_add_pci_capability(s, 3, bar_num, VIRTIO_PCI_ISR_OFFSET, 0x1000, 0); /* isr */ virtio_add_pci_capability(s, 4, bar_num, VIRTIO_PCI_CONFIG_OFFSET, 0x1000, 0); /* config */ virtio_add_pci_capability(s, 2, bar_num, VIRTIO_PCI_NOTIFY_OFFSET, 0x1000, 0); /* notify */ s->get_ram_ptr = virtio_pci_get_ram_ptr; s->irq = pci_device_get_irq(s->pci_dev, 0); s->mem_map = pci_device_get_mem_map(s->pci_dev); s->mem_range = cpu_register_device(s->mem_map, 0, 0x4000, s, virtio_pci_read, virtio_pci_write, DEVIO_SIZE8 | DEVIO_SIZE16 | DEVIO_SIZE32 | DEVIO_DISABLED); pci_register_bar(s->pci_dev, bar_num, 0x4000, PCI_ADDRESS_SPACE_MEM, s, virtio_pci_bar_set); } else { /* MMIO case */ s->mem_map = bus->mem_map; s->irq = bus->irq; s->mem_range = cpu_register_device(s->mem_map, bus->addr, VIRTIO_PAGE_SIZE, s, virtio_mmio_read, virtio_mmio_write, DEVIO_SIZE8 | DEVIO_SIZE16 | DEVIO_SIZE32); s->get_ram_ptr = virtio_mmio_get_ram_ptr; } s->device_id = device_id; s->vendor_id = 0xffff; s->config_space_size = config_space_size; s->device_recv = device_recv; virtio_reset(s); } static uint16_t virtio_read16(VIRTIODevice *s, virtio_phys_addr_t addr) { uint8_t *ptr; if (addr & 1) return 0; /* unaligned access are not supported */ ptr = s->get_ram_ptr(s, addr, FALSE); if (!ptr) return 0; return *(uint16_t *)ptr; } static void virtio_write16(VIRTIODevice *s, virtio_phys_addr_t addr, uint16_t val) { uint8_t *ptr; if (addr & 1) return; /* unaligned access are not supported */ ptr = s->get_ram_ptr(s, addr, TRUE); if (!ptr) return; *(uint16_t *)ptr = val; } static void virtio_write32(VIRTIODevice *s, virtio_phys_addr_t addr, uint32_t val) { uint8_t *ptr; if (addr & 3) return; /* unaligned access are not supported */ ptr = s->get_ram_ptr(s, addr, TRUE); if (!ptr) return; *(uint32_t *)ptr = val; } static int virtio_memcpy_from_ram(VIRTIODevice *s, uint8_t *buf, virtio_phys_addr_t addr, int count) { uint8_t *ptr; int l; while (count > 0) { l = min_int(count, VIRTIO_PAGE_SIZE - (addr & (VIRTIO_PAGE_SIZE - 1))); ptr = s->get_ram_ptr(s, addr, FALSE); if (!ptr) return -1; memcpy(buf, ptr, l); addr += l; buf += l; count -= l; } return 0; } static int virtio_memcpy_to_ram(VIRTIODevice *s, virtio_phys_addr_t addr, const uint8_t *buf, int count) { uint8_t *ptr; int l; while (count > 0) { l = min_int(count, VIRTIO_PAGE_SIZE - (addr & (VIRTIO_PAGE_SIZE - 1))); ptr = s->get_ram_ptr(s, addr, TRUE); if (!ptr) return -1; memcpy(ptr, buf, l); addr += l; buf += l; count -= l; } return 0; } static int get_desc(VIRTIODevice *s, VIRTIODesc *desc, int queue_idx, int desc_idx) { QueueState *qs = &s->queue[queue_idx]; return virtio_memcpy_from_ram(s, (void *)desc, qs->desc_addr + desc_idx * sizeof(VIRTIODesc), sizeof(VIRTIODesc)); } static int memcpy_to_from_queue(VIRTIODevice *s, uint8_t *buf, int queue_idx, int desc_idx, int offset, int count, BOOL to_queue) { VIRTIODesc desc; int l, f_write_flag; if (count == 0) return 0; get_desc(s, &desc, queue_idx, desc_idx); if (to_queue) { f_write_flag = VRING_DESC_F_WRITE; /* find the first write descriptor */ for(;;) { if ((desc.flags & VRING_DESC_F_WRITE) == f_write_flag) break; if (!(desc.flags & VRING_DESC_F_NEXT)) return -1; desc_idx = desc.next; get_desc(s, &desc, queue_idx, desc_idx); } } else { f_write_flag = 0; } /* find the descriptor at offset */ for(;;) { if ((desc.flags & VRING_DESC_F_WRITE) != f_write_flag) return -1; if (offset < desc.len) break; if (!(desc.flags & VRING_DESC_F_NEXT)) return -1; desc_idx = desc.next; offset -= desc.len; get_desc(s, &desc, queue_idx, desc_idx); } for(;;) { l = min_int(count, desc.len - offset); if (to_queue) virtio_memcpy_to_ram(s, desc.addr + offset, buf, l); else virtio_memcpy_from_ram(s, buf, desc.addr + offset, l); count -= l; if (count == 0) break; offset += l; buf += l; if (offset == desc.len) { if (!(desc.flags & VRING_DESC_F_NEXT)) return -1; desc_idx = desc.next; get_desc(s, &desc, queue_idx, desc_idx); if ((desc.flags & VRING_DESC_F_WRITE) != f_write_flag) return -1; offset = 0; } } return 0; } static int memcpy_from_queue(VIRTIODevice *s, void *buf, int queue_idx, int desc_idx, int offset, int count) { return memcpy_to_from_queue(s, buf, queue_idx, desc_idx, offset, count, FALSE); } static int memcpy_to_queue(VIRTIODevice *s, int queue_idx, int desc_idx, int offset, const void *buf, int count) { return memcpy_to_from_queue(s, (void *)buf, queue_idx, desc_idx, offset, count, TRUE); } /* signal that the descriptor has been consumed */ static void virtio_consume_desc(VIRTIODevice *s, int queue_idx, int desc_idx, int desc_len) { QueueState *qs = &s->queue[queue_idx]; virtio_phys_addr_t addr; uint32_t index; addr = qs->used_addr + 2; index = virtio_read16(s, addr); virtio_write16(s, addr, index + 1); addr = qs->used_addr + 4 + (index & (qs->num - 1)) * 8; virtio_write32(s, addr, desc_idx); virtio_write32(s, addr + 4, desc_len); s->int_status |= 1; set_irq(s->irq, 1); } static int get_desc_rw_size(VIRTIODevice *s, int *pread_size, int *pwrite_size, int queue_idx, int desc_idx) { VIRTIODesc desc; int read_size, write_size; read_size = 0; write_size = 0; get_desc(s, &desc, queue_idx, desc_idx); for(;;) { if (desc.flags & VRING_DESC_F_WRITE) break; read_size += desc.len; if (!(desc.flags & VRING_DESC_F_NEXT)) goto done; desc_idx = desc.next; get_desc(s, &desc, queue_idx, desc_idx); } for(;;) { if (!(desc.flags & VRING_DESC_F_WRITE)) return -1; write_size += desc.len; if (!(desc.flags & VRING_DESC_F_NEXT)) break; desc_idx = desc.next; get_desc(s, &desc, queue_idx, desc_idx); } done: *pread_size = read_size; *pwrite_size = write_size; return 0; } /* XXX: test if the queue is ready ? */ static void queue_notify(VIRTIODevice *s, int queue_idx) { QueueState *qs = &s->queue[queue_idx]; uint16_t avail_idx; int desc_idx, read_size, write_size; if (qs->manual_recv) return; avail_idx = virtio_read16(s, qs->avail_addr + 2); while (qs->last_avail_idx != avail_idx) { desc_idx = virtio_read16(s, qs->avail_addr + 4 + (qs->last_avail_idx & (qs->num - 1)) * 2); if (!get_desc_rw_size(s, &read_size, &write_size, queue_idx, desc_idx)) { #ifdef DEBUG_VIRTIO if (s->debug & VIRTIO_DEBUG_IO) { printf("queue_notify: idx=%d read_size=%d write_size=%d\n", queue_idx, read_size, write_size); } #endif if (s->device_recv(s, queue_idx, desc_idx, read_size, write_size) < 0) break; } qs->last_avail_idx++; } } static uint32_t virtio_config_read(VIRTIODevice *s, uint32_t offset, int size_log2) { uint32_t val; switch(size_log2) { case 0: if (offset < s->config_space_size) { val = s->config_space[offset]; } else { val = 0; } break; case 1: if (offset < (s->config_space_size - 1)) { val = get_le16(&s->config_space[offset]); } else { val = 0; } break; case 2: if (offset < (s->config_space_size - 3)) { val = get_le32(s->config_space + offset); } else { val = 0; } break; default: abort(); } return val; } static void virtio_config_write(VIRTIODevice *s, uint32_t offset, uint32_t val, int size_log2) { switch(size_log2) { case 0: if (offset < s->config_space_size) { s->config_space[offset] = val; if (s->config_write) s->config_write(s); } break; case 1: if (offset < s->config_space_size - 1) { put_le16(s->config_space + offset, val); if (s->config_write) s->config_write(s); } break; case 2: if (offset < s->config_space_size - 3) { put_le32(s->config_space + offset, val); if (s->config_write) s->config_write(s); } break; } } static uint32_t virtio_mmio_read(void *opaque, uint32_t offset, int size_log2) { VIRTIODevice *s = opaque; uint32_t val; if (offset >= VIRTIO_MMIO_CONFIG) { return virtio_config_read(s, offset - VIRTIO_MMIO_CONFIG, size_log2); } if (size_log2 == 2) { switch(offset) { case VIRTIO_MMIO_MAGIC_VALUE: val = 0x74726976; break; case VIRTIO_MMIO_VERSION: val = 2; break; case VIRTIO_MMIO_DEVICE_ID: val = s->device_id; break; case VIRTIO_MMIO_VENDOR_ID: val = s->vendor_id; break; case VIRTIO_MMIO_DEVICE_FEATURES: switch(s->device_features_sel) { case 0: val = s->device_features; break; case 1: val = 1; /* version 1 */ break; default: val = 0; break; } break; case VIRTIO_MMIO_DEVICE_FEATURES_SEL: val = s->device_features_sel; break; case VIRTIO_MMIO_QUEUE_SEL: val = s->queue_sel; break; case VIRTIO_MMIO_QUEUE_NUM_MAX: val = MAX_QUEUE_NUM; break; case VIRTIO_MMIO_QUEUE_NUM: val = s->queue[s->queue_sel].num; break; case VIRTIO_MMIO_QUEUE_DESC_LOW: val = s->queue[s->queue_sel].desc_addr; break; case VIRTIO_MMIO_QUEUE_AVAIL_LOW: val = s->queue[s->queue_sel].avail_addr; break; case VIRTIO_MMIO_QUEUE_USED_LOW: val = s->queue[s->queue_sel].used_addr; break; #if VIRTIO_ADDR_BITS == 64 case VIRTIO_MMIO_QUEUE_DESC_HIGH: val = s->queue[s->queue_sel].desc_addr >> 32; break; case VIRTIO_MMIO_QUEUE_AVAIL_HIGH: val = s->queue[s->queue_sel].avail_addr >> 32; break; case VIRTIO_MMIO_QUEUE_USED_HIGH: val = s->queue[s->queue_sel].used_addr >> 32; break; #endif case VIRTIO_MMIO_QUEUE_READY: val = s->queue[s->queue_sel].ready; break; case VIRTIO_MMIO_INTERRUPT_STATUS: val = s->int_status; break; case VIRTIO_MMIO_STATUS: val = s->status; break; case VIRTIO_MMIO_CONFIG_GENERATION: val = 0; break; default: val = 0; break; } } else { val = 0; } #ifdef DEBUG_VIRTIO if (s->debug & VIRTIO_DEBUG_IO) { printf("virto_mmio_read: offset=0x%x val=0x%x size=%d\n", offset, val, 1 << size_log2); } #endif return val; } #if VIRTIO_ADDR_BITS == 64 static void set_low32(virtio_phys_addr_t *paddr, uint32_t val) { *paddr = (*paddr & ~(virtio_phys_addr_t)0xffffffff) | val; } static void set_high32(virtio_phys_addr_t *paddr, uint32_t val) { *paddr = (*paddr & 0xffffffff) | ((virtio_phys_addr_t)val << 32); } #else static void set_low32(virtio_phys_addr_t *paddr, uint32_t val) { *paddr = val; } #endif static void virtio_mmio_write(void *opaque, uint32_t offset, uint32_t val, int size_log2) { VIRTIODevice *s = opaque; #ifdef DEBUG_VIRTIO if (s->debug & VIRTIO_DEBUG_IO) { printf("virto_mmio_write: offset=0x%x val=0x%x size=%d\n", offset, val, 1 << size_log2); } #endif if (offset >= VIRTIO_MMIO_CONFIG) { virtio_config_write(s, offset - VIRTIO_MMIO_CONFIG, val, size_log2); return; } if (size_log2 == 2) { switch(offset) { case VIRTIO_MMIO_DEVICE_FEATURES_SEL: s->device_features_sel = val; break; case VIRTIO_MMIO_QUEUE_SEL: if (val < MAX_QUEUE) s->queue_sel = val; break; case VIRTIO_MMIO_QUEUE_NUM: if ((val & (val - 1)) == 0 && val > 0) { s->queue[s->queue_sel].num = val; } break; case VIRTIO_MMIO_QUEUE_DESC_LOW: set_low32(&s->queue[s->queue_sel].desc_addr, val); break; case VIRTIO_MMIO_QUEUE_AVAIL_LOW: set_low32(&s->queue[s->queue_sel].avail_addr, val); break; case VIRTIO_MMIO_QUEUE_USED_LOW: set_low32(&s->queue[s->queue_sel].used_addr, val); break; #if VIRTIO_ADDR_BITS == 64 case VIRTIO_MMIO_QUEUE_DESC_HIGH: set_high32(&s->queue[s->queue_sel].desc_addr, val); break; case VIRTIO_MMIO_QUEUE_AVAIL_HIGH: set_high32(&s->queue[s->queue_sel].avail_addr, val); break; case VIRTIO_MMIO_QUEUE_USED_HIGH: set_high32(&s->queue[s->queue_sel].used_addr, val); break; #endif case VIRTIO_MMIO_STATUS: s->status = val; if (val == 0) { /* reset */ set_irq(s->irq, 0); virtio_reset(s); } break; case VIRTIO_MMIO_QUEUE_READY: s->queue[s->queue_sel].ready = val & 1; break; case VIRTIO_MMIO_QUEUE_NOTIFY: if (val < MAX_QUEUE) queue_notify(s, val); break; case VIRTIO_MMIO_INTERRUPT_ACK: s->int_status &= ~val; if (s->int_status == 0) { set_irq(s->irq, 0); } break; } } } static uint32_t virtio_pci_read(void *opaque, uint32_t offset1, int size_log2) { VIRTIODevice *s = opaque; uint32_t offset; uint32_t val = 0; offset = offset1 & 0xfff; switch(offset1 >> 12) { case VIRTIO_PCI_CFG_OFFSET >> 12: if (size_log2 == 2) { switch(offset) { case VIRTIO_PCI_DEVICE_FEATURE: switch(s->device_features_sel) { case 0: val = s->device_features; break; case 1: val = 1; /* version 1 */ break; default: val = 0; break; } break; case VIRTIO_PCI_DEVICE_FEATURE_SEL: val = s->device_features_sel; break; case VIRTIO_PCI_QUEUE_DESC_LOW: val = s->queue[s->queue_sel].desc_addr; break; case VIRTIO_PCI_QUEUE_AVAIL_LOW: val = s->queue[s->queue_sel].avail_addr; break; case VIRTIO_PCI_QUEUE_USED_LOW: val = s->queue[s->queue_sel].used_addr; break; #if VIRTIO_ADDR_BITS == 64 case VIRTIO_PCI_QUEUE_DESC_HIGH: val = s->queue[s->queue_sel].desc_addr >> 32; break; case VIRTIO_PCI_QUEUE_AVAIL_HIGH: val = s->queue[s->queue_sel].avail_addr >> 32; break; case VIRTIO_PCI_QUEUE_USED_HIGH: val = s->queue[s->queue_sel].used_addr >> 32; break; #endif } } else if (size_log2 == 1) { switch(offset) { case VIRTIO_PCI_NUM_QUEUES: val = MAX_QUEUE_NUM; break; case VIRTIO_PCI_QUEUE_SEL: val = s->queue_sel; break; case VIRTIO_PCI_QUEUE_SIZE: val = s->queue[s->queue_sel].num; break; case VIRTIO_PCI_QUEUE_ENABLE: val = s->queue[s->queue_sel].ready; break; case VIRTIO_PCI_QUEUE_NOTIFY_OFF: val = 0; break; } } else if (size_log2 == 0) { switch(offset) { case VIRTIO_PCI_DEVICE_STATUS: val = s->status; break; } } break; case VIRTIO_PCI_ISR_OFFSET >> 12: if (offset == 0 && size_log2 == 0) { val = s->int_status; s->int_status = 0; set_irq(s->irq, 0); } break; case VIRTIO_PCI_CONFIG_OFFSET >> 12: val = virtio_config_read(s, offset, size_log2); break; } #ifdef DEBUG_VIRTIO if (s->debug & VIRTIO_DEBUG_IO) { printf("virto_pci_read: offset=0x%x val=0x%x size=%d\n", offset1, val, 1 << size_log2); } #endif return val; } static void virtio_pci_write(void *opaque, uint32_t offset1, uint32_t val, int size_log2) { VIRTIODevice *s = opaque; uint32_t offset; #ifdef DEBUG_VIRTIO if (s->debug & VIRTIO_DEBUG_IO) { printf("virto_pci_write: offset=0x%x val=0x%x size=%d\n", offset1, val, 1 << size_log2); } #endif offset = offset1 & 0xfff; switch(offset1 >> 12) { case VIRTIO_PCI_CFG_OFFSET >> 12: if (size_log2 == 2) { switch(offset) { case VIRTIO_PCI_DEVICE_FEATURE_SEL: s->device_features_sel = val; break; case VIRTIO_PCI_QUEUE_DESC_LOW: set_low32(&s->queue[s->queue_sel].desc_addr, val); break; case VIRTIO_PCI_QUEUE_AVAIL_LOW: set_low32(&s->queue[s->queue_sel].avail_addr, val); break; case VIRTIO_PCI_QUEUE_USED_LOW: set_low32(&s->queue[s->queue_sel].used_addr, val); break; #if VIRTIO_ADDR_BITS == 64 case VIRTIO_PCI_QUEUE_DESC_HIGH: set_high32(&s->queue[s->queue_sel].desc_addr, val); break; case VIRTIO_PCI_QUEUE_AVAIL_HIGH: set_high32(&s->queue[s->queue_sel].avail_addr, val); break; case VIRTIO_PCI_QUEUE_USED_HIGH: set_high32(&s->queue[s->queue_sel].used_addr, val); break; #endif } } else if (size_log2 == 1) { switch(offset) { case VIRTIO_PCI_QUEUE_SEL: if (val < MAX_QUEUE) s->queue_sel = val; break; case VIRTIO_PCI_QUEUE_SIZE: if ((val & (val - 1)) == 0 && val > 0) { s->queue[s->queue_sel].num = val; } break; case VIRTIO_PCI_QUEUE_ENABLE: s->queue[s->queue_sel].ready = val & 1; break; } } else if (size_log2 == 0) { switch(offset) { case VIRTIO_PCI_DEVICE_STATUS: s->status = val; if (val == 0) { /* reset */ set_irq(s->irq, 0); virtio_reset(s); } break; } } break; case VIRTIO_PCI_CONFIG_OFFSET >> 12: virtio_config_write(s, offset, val, size_log2); break; case VIRTIO_PCI_NOTIFY_OFFSET >> 12: if (val < MAX_QUEUE) queue_notify(s, val); break; } } void virtio_set_debug(VIRTIODevice *s, int debug) { s->debug = debug; } static void virtio_config_change_notify(VIRTIODevice *s) { /* INT_CONFIG interrupt */ s->int_status |= 2; set_irq(s->irq, 1); } /*********************************************************************/ /* block device */ typedef struct { uint32_t type; uint8_t *buf; int write_size; int queue_idx; int desc_idx; } BlockRequest; typedef struct VIRTIOBlockDevice { VIRTIODevice common; BlockDevice *bs; BOOL req_in_progress; BlockRequest req; /* request in progress */ } VIRTIOBlockDevice; typedef struct { uint32_t type; uint32_t ioprio; uint64_t sector_num; } BlockRequestHeader; #define VIRTIO_BLK_T_IN 0 #define VIRTIO_BLK_T_OUT 1 #define VIRTIO_BLK_T_FLUSH 4 #define VIRTIO_BLK_T_FLUSH_OUT 5 #define VIRTIO_BLK_S_OK 0 #define VIRTIO_BLK_S_IOERR 1 #define VIRTIO_BLK_S_UNSUPP 2 #define SECTOR_SIZE 512 static void virtio_block_req_end(VIRTIODevice *s, int ret) { VIRTIOBlockDevice *s1 = (VIRTIOBlockDevice *)s; int write_size; int queue_idx = s1->req.queue_idx; int desc_idx = s1->req.desc_idx; uint8_t *buf, buf1[1]; switch(s1->req.type) { case VIRTIO_BLK_T_IN: write_size = s1->req.write_size; buf = s1->req.buf; if (ret < 0) { buf[write_size - 1] = VIRTIO_BLK_S_IOERR; } else { buf[write_size - 1] = VIRTIO_BLK_S_OK; } memcpy_to_queue(s, queue_idx, desc_idx, 0, buf, write_size); free(buf); virtio_consume_desc(s, queue_idx, desc_idx, write_size); break; case VIRTIO_BLK_T_OUT: if (ret < 0) buf1[0] = VIRTIO_BLK_S_IOERR; else buf1[0] = VIRTIO_BLK_S_OK; memcpy_to_queue(s, queue_idx, desc_idx, 0, buf1, sizeof(buf1)); virtio_consume_desc(s, queue_idx, desc_idx, 1); break; default: abort(); } } static void virtio_block_req_cb(void *opaque, int ret) { VIRTIODevice *s = opaque; VIRTIOBlockDevice *s1 = (VIRTIOBlockDevice *)s; virtio_block_req_end(s, ret); s1->req_in_progress = FALSE; /* handle next requests */ queue_notify((VIRTIODevice *)s, s1->req.queue_idx); } /* XXX: handle async I/O */ static int virtio_block_recv_request(VIRTIODevice *s, int queue_idx, int desc_idx, int read_size, int write_size) { VIRTIOBlockDevice *s1 = (VIRTIOBlockDevice *)s; BlockDevice *bs = s1->bs; BlockRequestHeader h; uint8_t *buf; int len, ret; if (s1->req_in_progress) return -1; if (memcpy_from_queue(s, &h, queue_idx, desc_idx, 0, sizeof(h)) < 0) return 0; s1->req.type = h.type; s1->req.queue_idx = queue_idx; s1->req.desc_idx = desc_idx; switch(h.type) { case VIRTIO_BLK_T_IN: s1->req.buf = malloc(write_size); s1->req.write_size = write_size; ret = bs->read_async(bs, h.sector_num, s1->req.buf, (write_size - 1) / SECTOR_SIZE, virtio_block_req_cb, s); if (ret > 0) { /* asyncronous read */ s1->req_in_progress = TRUE; } else { virtio_block_req_end(s, ret); } break; case VIRTIO_BLK_T_OUT: assert(write_size >= 1); len = read_size - sizeof(h); buf = malloc(len); memcpy_from_queue(s, buf, queue_idx, desc_idx, sizeof(h), len); ret = bs->write_async(bs, h.sector_num, buf, len / SECTOR_SIZE, virtio_block_req_cb, s); free(buf); if (ret > 0) { /* asyncronous write */ s1->req_in_progress = TRUE; } else { virtio_block_req_end(s, ret); } break; default: break; } return 0; } VIRTIODevice *virtio_block_init(VIRTIOBusDef *bus, BlockDevice *bs) { VIRTIOBlockDevice *s; uint64_t nb_sectors; s = mallocz(sizeof(*s)); virtio_init(&s->common, bus, 2, 8, virtio_block_recv_request); s->bs = bs; nb_sectors = bs->get_sector_count(bs); put_le32(s->common.config_space, nb_sectors); put_le32(s->common.config_space + 4, nb_sectors >> 32); return (VIRTIODevice *)s; } /*********************************************************************/ /* network device */ typedef struct VIRTIONetDevice { VIRTIODevice common; EthernetDevice *es; int header_size; } VIRTIONetDevice; typedef struct { uint8_t flags; uint8_t gso_type; uint16_t hdr_len; uint16_t gso_size; uint16_t csum_start; uint16_t csum_offset; uint16_t num_buffers; } VIRTIONetHeader; static int virtio_net_recv_request(VIRTIODevice *s, int queue_idx, int desc_idx, int read_size, int write_size) { VIRTIONetDevice *s1 = (VIRTIONetDevice *)s; EthernetDevice *es = s1->es; VIRTIONetHeader h; uint8_t *buf; int len; if (queue_idx == 1) { /* send to network */ if (memcpy_from_queue(s, &h, queue_idx, desc_idx, 0, s1->header_size) < 0) return 0; len = read_size - s1->header_size; buf = malloc(len); memcpy_from_queue(s, buf, queue_idx, desc_idx, s1->header_size, len); es->write_packet(es, buf, len); free(buf); virtio_consume_desc(s, queue_idx, desc_idx, 0); } return 0; } static BOOL virtio_net_can_write_packet(EthernetDevice *es) { VIRTIODevice *s = es->device_opaque; QueueState *qs = &s->queue[0]; uint16_t avail_idx; if (!qs->ready) return FALSE; avail_idx = virtio_read16(s, qs->avail_addr + 2); return qs->last_avail_idx != avail_idx; } static void virtio_net_write_packet(EthernetDevice *es, const uint8_t *buf, int buf_len) { VIRTIODevice *s = es->device_opaque; VIRTIONetDevice *s1 = (VIRTIONetDevice *)s; int queue_idx = 0; QueueState *qs = &s->queue[queue_idx]; int desc_idx; VIRTIONetHeader h; int len, read_size, write_size; uint16_t avail_idx; if (!qs->ready) return; avail_idx = virtio_read16(s, qs->avail_addr + 2); if (qs->last_avail_idx == avail_idx) return; desc_idx = virtio_read16(s, qs->avail_addr + 4 + (qs->last_avail_idx & (qs->num - 1)) * 2); if (get_desc_rw_size(s, &read_size, &write_size, queue_idx, desc_idx)) return; len = s1->header_size + buf_len; if (len > write_size) return; memset(&h, 0, s1->header_size); memcpy_to_queue(s, queue_idx, desc_idx, 0, &h, s1->header_size); memcpy_to_queue(s, queue_idx, desc_idx, s1->header_size, buf, buf_len); virtio_consume_desc(s, queue_idx, desc_idx, len); qs->last_avail_idx++; } static void virtio_net_set_carrier(EthernetDevice *es, BOOL carrier_state) { #if 0 VIRTIODevice *s1 = es->device_opaque; VIRTIONetDevice *s = (VIRTIONetDevice *)s1; int cur_carrier_state; // printf("virtio_net_set_carrier: %d\n", carrier_state); cur_carrier_state = s->common.config_space[6] & 1; if (cur_carrier_state != carrier_state) { s->common.config_space[6] = (carrier_state << 0); virtio_config_change_notify(s1); } #endif } VIRTIODevice *virtio_net_init(VIRTIOBusDef *bus, EthernetDevice *es) { VIRTIONetDevice *s; s = mallocz(sizeof(*s)); virtio_init(&s->common, bus, 1, 6 + 2, virtio_net_recv_request); /* VIRTIO_NET_F_MAC, VIRTIO_NET_F_STATUS */ s->common.device_features = (1 << 5) /* | (1 << 16) */; s->common.queue[0].manual_recv = TRUE; s->es = es; memcpy(s->common.config_space, es->mac_addr, 6); /* status */ s->common.config_space[6] = 0; s->common.config_space[7] = 0; s->header_size = sizeof(VIRTIONetHeader); es->device_opaque = s; es->device_can_write_packet = virtio_net_can_write_packet; es->device_write_packet = virtio_net_write_packet; es->device_set_carrier = virtio_net_set_carrier; return (VIRTIODevice *)s; } /*********************************************************************/ /* console device */ typedef struct VIRTIOConsoleDevice { VIRTIODevice common; CharacterDevice *cs; } VIRTIOConsoleDevice; static int virtio_console_recv_request(VIRTIODevice *s, int queue_idx, int desc_idx, int read_size, int write_size) { VIRTIOConsoleDevice *s1 = (VIRTIOConsoleDevice *)s; CharacterDevice *cs = s1->cs; uint8_t *buf; if (queue_idx == 1) { /* send to console */ buf = malloc(read_size); memcpy_from_queue(s, buf, queue_idx, desc_idx, 0, read_size); cs->write_data(cs->opaque, buf, read_size); free(buf); virtio_consume_desc(s, queue_idx, desc_idx, 0); } return 0; } BOOL virtio_console_can_write_data(VIRTIODevice *s) { QueueState *qs = &s->queue[0]; uint16_t avail_idx; if (!qs->ready) return FALSE; avail_idx = virtio_read16(s, qs->avail_addr + 2); return qs->last_avail_idx != avail_idx; } int virtio_console_get_write_len(VIRTIODevice *s) { int queue_idx = 0; QueueState *qs = &s->queue[queue_idx]; int desc_idx; int read_size, write_size; uint16_t avail_idx; if (!qs->ready) return 0; avail_idx = virtio_read16(s, qs->avail_addr + 2); if (qs->last_avail_idx == avail_idx) return 0; desc_idx = virtio_read16(s, qs->avail_addr + 4 + (qs->last_avail_idx & (qs->num - 1)) * 2); if (get_desc_rw_size(s, &read_size, &write_size, queue_idx, desc_idx)) return 0; return write_size; } int virtio_console_write_data(VIRTIODevice *s, const uint8_t *buf, int buf_len) { int queue_idx = 0; QueueState *qs = &s->queue[queue_idx]; int desc_idx; uint16_t avail_idx; if (!qs->ready) return 0; avail_idx = virtio_read16(s, qs->avail_addr + 2); if (qs->last_avail_idx == avail_idx) return 0; desc_idx = virtio_read16(s, qs->avail_addr + 4 + (qs->last_avail_idx & (qs->num - 1)) * 2); memcpy_to_queue(s, queue_idx, desc_idx, 0, buf, buf_len); virtio_consume_desc(s, queue_idx, desc_idx, buf_len); qs->last_avail_idx++; return buf_len; } /* send a resize event */ void virtio_console_resize_event(VIRTIODevice *s, int width, int height) { /* indicate the console size */ put_le16(s->config_space + 0, width); put_le16(s->config_space + 2, height); virtio_config_change_notify(s); } VIRTIODevice *virtio_console_init(VIRTIOBusDef *bus, CharacterDevice *cs) { VIRTIOConsoleDevice *s; s = mallocz(sizeof(*s)); virtio_init(&s->common, bus, 3, 4, virtio_console_recv_request); s->common.device_features = (1 << 0); /* VIRTIO_CONSOLE_F_SIZE */ s->common.queue[0].manual_recv = TRUE; s->cs = cs; return (VIRTIODevice *)s; } /*********************************************************************/ /* input device */ enum { VIRTIO_INPUT_CFG_UNSET = 0x00, VIRTIO_INPUT_CFG_ID_NAME = 0x01, VIRTIO_INPUT_CFG_ID_SERIAL = 0x02, VIRTIO_INPUT_CFG_ID_DEVIDS = 0x03, VIRTIO_INPUT_CFG_PROP_BITS = 0x10, VIRTIO_INPUT_CFG_EV_BITS = 0x11, VIRTIO_INPUT_CFG_ABS_INFO = 0x12, }; #define VIRTIO_INPUT_EV_SYN 0x00 #define VIRTIO_INPUT_EV_KEY 0x01 #define VIRTIO_INPUT_EV_REL 0x02 #define VIRTIO_INPUT_EV_ABS 0x03 #define VIRTIO_INPUT_EV_REP 0x14 #define BTN_LEFT 0x110 #define BTN_RIGHT 0x111 #define BTN_MIDDLE 0x112 #define BTN_GEAR_DOWN 0x150 #define BTN_GEAR_UP 0x151 #define REL_X 0x00 #define REL_Y 0x01 #define REL_Z 0x02 #define REL_WHEEL 0x08 #define ABS_X 0x00 #define ABS_Y 0x01 #define ABS_Z 0x02 typedef struct VIRTIOInputDevice { VIRTIODevice common; VirtioInputTypeEnum type; uint32_t buttons_state; } VIRTIOInputDevice; static const uint16_t buttons_list[] = { BTN_LEFT, BTN_RIGHT, BTN_MIDDLE }; static int virtio_input_recv_request(VIRTIODevice *s, int queue_idx, int desc_idx, int read_size, int write_size) { if (queue_idx == 1) { /* led & keyboard updates */ // printf("%s: write_size=%d\n", __func__, write_size); virtio_consume_desc(s, queue_idx, desc_idx, 0); } return 0; } /* return < 0 if could not send key event */ static int virtio_input_queue_event(VIRTIODevice *s, uint16_t type, uint16_t code, uint32_t value) { int queue_idx = 0; QueueState *qs = &s->queue[queue_idx]; int desc_idx, buf_len; uint16_t avail_idx; uint8_t buf[8]; if (!qs->ready) return -1; put_le16(buf, type); put_le16(buf + 2, code); put_le32(buf + 4, value); buf_len = 8; avail_idx = virtio_read16(s, qs->avail_addr + 2); if (qs->last_avail_idx == avail_idx) return -1; desc_idx = virtio_read16(s, qs->avail_addr + 4 + (qs->last_avail_idx & (qs->num - 1)) * 2); // printf("send: queue_idx=%d desc_idx=%d\n", queue_idx, desc_idx); memcpy_to_queue(s, queue_idx, desc_idx, 0, buf, buf_len); virtio_consume_desc(s, queue_idx, desc_idx, buf_len); qs->last_avail_idx++; return 0; } int virtio_input_send_key_event(VIRTIODevice *s, BOOL is_down, uint16_t key_code) { VIRTIOInputDevice *s1 = (VIRTIOInputDevice *)s; int ret; if (s1->type != VIRTIO_INPUT_TYPE_KEYBOARD) return -1; ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_KEY, key_code, is_down); if (ret) return ret; return virtio_input_queue_event(s, VIRTIO_INPUT_EV_SYN, 0, 0); } /* also used for the tablet */ int virtio_input_send_mouse_event(VIRTIODevice *s, int dx, int dy, int dz, unsigned int buttons) { VIRTIOInputDevice *s1 = (VIRTIOInputDevice *)s; int ret, i, b, last_b; if (s1->type != VIRTIO_INPUT_TYPE_MOUSE && s1->type != VIRTIO_INPUT_TYPE_TABLET) return -1; if (s1->type == VIRTIO_INPUT_TYPE_MOUSE) { ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_REL, REL_X, dx); if (ret != 0) return ret; ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_REL, REL_Y, dy); if (ret != 0) return ret; } else { ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_ABS, ABS_X, dx); if (ret != 0) return ret; ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_ABS, ABS_Y, dy); if (ret != 0) return ret; } if (dz != 0) { ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_REL, REL_WHEEL, dz); if (ret != 0) return ret; } if (buttons != s1->buttons_state) { for(i = 0; i < countof(buttons_list); i++) { b = (buttons >> i) & 1; last_b = (s1->buttons_state >> i) & 1; if (b != last_b) { ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_KEY, buttons_list[i], b); if (ret != 0) return ret; } } s1->buttons_state = buttons; } return virtio_input_queue_event(s, VIRTIO_INPUT_EV_SYN, 0, 0); } static void set_bit(uint8_t *tab, int k) { tab[k >> 3] |= 1 << (k & 7); } static void virtio_input_config_write(VIRTIODevice *s) { VIRTIOInputDevice *s1 = (VIRTIOInputDevice *)s; uint8_t *config = s->config_space; int i; // printf("config_write: %02x %02x\n", config[0], config[1]); switch(config[0]) { case VIRTIO_INPUT_CFG_UNSET: break; case VIRTIO_INPUT_CFG_ID_NAME: { const char *name; int len; switch(s1->type) { case VIRTIO_INPUT_TYPE_KEYBOARD: name = "virtio_keyboard"; break; case VIRTIO_INPUT_TYPE_MOUSE: name = "virtio_mouse"; break; case VIRTIO_INPUT_TYPE_TABLET: name = "virtio_tablet"; break; default: abort(); } len = strlen(name); config[2] = len; memcpy(config + 8, name, len); } break; default: case VIRTIO_INPUT_CFG_ID_SERIAL: case VIRTIO_INPUT_CFG_ID_DEVIDS: case VIRTIO_INPUT_CFG_PROP_BITS: config[2] = 0; /* size of reply */ break; case VIRTIO_INPUT_CFG_EV_BITS: config[2] = 0; switch(s1->type) { case VIRTIO_INPUT_TYPE_KEYBOARD: switch(config[1]) { case VIRTIO_INPUT_EV_KEY: config[2] = 128 / 8; memset(config + 8, 0xff, 128 / 8); /* bitmap */ break; case VIRTIO_INPUT_EV_REP: /* allow key repetition */ config[2] = 1; break; default: break; } break; case VIRTIO_INPUT_TYPE_MOUSE: switch(config[1]) { case VIRTIO_INPUT_EV_KEY: config[2] = 512 / 8; memset(config + 8, 0, 512 / 8); /* bitmap */ for(i = 0; i < countof(buttons_list); i++) set_bit(config + 8, buttons_list[i]); break; case VIRTIO_INPUT_EV_REL: config[2] = 2; config[8] = 0; config[9] = 0; set_bit(config + 8, REL_X); set_bit(config + 8, REL_Y); set_bit(config + 8, REL_WHEEL); break; default: break; } break; case VIRTIO_INPUT_TYPE_TABLET: switch(config[1]) { case VIRTIO_INPUT_EV_KEY: config[2] = 512 / 8; memset(config + 8, 0, 512 / 8); /* bitmap */ for(i = 0; i < countof(buttons_list); i++) set_bit(config + 8, buttons_list[i]); break; case VIRTIO_INPUT_EV_REL: config[2] = 2; config[8] = 0; config[9] = 0; set_bit(config + 8, REL_WHEEL); break; case VIRTIO_INPUT_EV_ABS: config[2] = 1; config[8] = 0; set_bit(config + 8, ABS_X); set_bit(config + 8, ABS_Y); break; default: break; } break; default: abort(); } break; case VIRTIO_INPUT_CFG_ABS_INFO: if (s1->type == VIRTIO_INPUT_TYPE_TABLET && config[1] <= 1) { /* for ABS_X and ABS_Y */ config[2] = 5 * 4; put_le32(config + 8, 0); /* min */ put_le32(config + 12, VIRTIO_INPUT_ABS_SCALE - 1) ; /* max */ put_le32(config + 16, 0); /* fuzz */ put_le32(config + 20, 0); /* flat */ put_le32(config + 24, 0); /* res */ } break; } } VIRTIODevice *virtio_input_init(VIRTIOBusDef *bus, VirtioInputTypeEnum type) { VIRTIOInputDevice *s; s = mallocz(sizeof(*s)); virtio_init(&s->common, bus, 18, 256, virtio_input_recv_request); s->common.queue[0].manual_recv = TRUE; s->common.device_features = 0; s->common.config_write = virtio_input_config_write; s->type = type; return (VIRTIODevice *)s; } /*********************************************************************/ /* 9p filesystem device */ typedef struct { struct list_head link; uint32_t fid; FSFile *fd; } FIDDesc; typedef struct VIRTIO9PDevice { VIRTIODevice common; FSDevice *fs; int msize; /* maximum message size */ struct list_head fid_list; /* list of FIDDesc */ BOOL req_in_progress; } VIRTIO9PDevice; static FIDDesc *fid_find1(VIRTIO9PDevice *s, uint32_t fid) { struct list_head *el; FIDDesc *f; list_for_each(el, &s->fid_list) { f = list_entry(el, FIDDesc, link); if (f->fid == fid) return f; } return NULL; } static FSFile *fid_find(VIRTIO9PDevice *s, uint32_t fid) { FIDDesc *f; f = fid_find1(s, fid); if (!f) return NULL; return f->fd; } static void fid_delete(VIRTIO9PDevice *s, uint32_t fid) { FIDDesc *f; f = fid_find1(s, fid); if (f) { s->fs->fs_delete(s->fs, f->fd); list_del(&f->link); free(f); } } static void fid_set(VIRTIO9PDevice *s, uint32_t fid, FSFile *fd) { FIDDesc *f; f = fid_find1(s, fid); if (f) { s->fs->fs_delete(s->fs, f->fd); f->fd = fd; } else { f = malloc(sizeof(*f)); f->fid = fid; f->fd = fd; list_add(&f->link, &s->fid_list); } } #ifdef DEBUG_VIRTIO typedef struct { uint8_t tag; const char *name; } Virtio9POPName; static const Virtio9POPName virtio_9p_op_names[] = { { 8, "statfs" }, { 12, "lopen" }, { 14, "lcreate" }, { 16, "symlink" }, { 18, "mknod" }, { 22, "readlink" }, { 24, "getattr" }, { 26, "setattr" }, { 30, "xattrwalk" }, { 40, "readdir" }, { 50, "fsync" }, { 52, "lock" }, { 54, "getlock" }, { 70, "link" }, { 72, "mkdir" }, { 74, "renameat" }, { 76, "unlinkat" }, { 100, "version" }, { 104, "attach" }, { 108, "flush" }, { 110, "walk" }, { 116, "read" }, { 118, "write" }, { 120, "clunk" }, { 0, NULL }, }; static const char *get_9p_op_name(int tag) { const Virtio9POPName *p; for(p = virtio_9p_op_names; p->name != NULL; p++) { if (p->tag == tag) return p->name; } return NULL; } #endif /* DEBUG_VIRTIO */ static int marshall(VIRTIO9PDevice *s, uint8_t *buf1, int max_len, const char *fmt, ...) { va_list ap; int c; uint32_t val; uint64_t val64; uint8_t *buf, *buf_end; #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) printf(" ->"); #endif va_start(ap, fmt); buf = buf1; buf_end = buf1 + max_len; for(;;) { c = *fmt++; if (c == '\0') break; switch(c) { case 'b': assert(buf + 1 <= buf_end); val = va_arg(ap, int); #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) printf(" b=%d", val); #endif buf[0] = val; buf += 1; break; case 'h': assert(buf + 2 <= buf_end); val = va_arg(ap, int); #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) printf(" h=%d", val); #endif put_le16(buf, val); buf += 2; break; case 'w': assert(buf + 4 <= buf_end); val = va_arg(ap, int); #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) printf(" w=%d", val); #endif put_le32(buf, val); buf += 4; break; case 'd': assert(buf + 8 <= buf_end); val64 = va_arg(ap, uint64_t); #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) printf(" d=%" PRId64, val64); #endif put_le64(buf, val64); buf += 8; break; case 's': { char *str; int len; str = va_arg(ap, char *); #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) printf(" s=\"%s\"", str); #endif len = strlen(str); assert(len <= 65535); assert(buf + 2 + len <= buf_end); put_le16(buf, len); buf += 2; memcpy(buf, str, len); buf += len; } break; case 'Q': { FSQID *qid; assert(buf + 13 <= buf_end); qid = va_arg(ap, FSQID *); #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) printf(" Q=%d:%d:%" PRIu64, qid->type, qid->version, qid->path); #endif buf[0] = qid->type; put_le32(buf + 1, qid->version); put_le64(buf + 5, qid->path); buf += 13; } break; default: abort(); } } va_end(ap); return buf - buf1; } /* return < 0 if error */ /* XXX: free allocated strings in case of error */ static int unmarshall(VIRTIO9PDevice *s, int queue_idx, int desc_idx, int *poffset, const char *fmt, ...) { VIRTIODevice *s1 = (VIRTIODevice *)s; va_list ap; int offset, c; uint8_t buf[16]; offset = *poffset; va_start(ap, fmt); for(;;) { c = *fmt++; if (c == '\0') break; switch(c) { case 'b': { uint8_t *ptr; if (memcpy_from_queue(s1, buf, queue_idx, desc_idx, offset, 1)) return -1; ptr = va_arg(ap, uint8_t *); *ptr = buf[0]; offset += 1; #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) printf(" b=%d", *ptr); #endif } break; case 'h': { uint16_t *ptr; if (memcpy_from_queue(s1, buf, queue_idx, desc_idx, offset, 2)) return -1; ptr = va_arg(ap, uint16_t *); *ptr = get_le16(buf); offset += 2; #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) printf(" h=%d", *ptr); #endif } break; case 'w': { uint32_t *ptr; if (memcpy_from_queue(s1, buf, queue_idx, desc_idx, offset, 4)) return -1; ptr = va_arg(ap, uint32_t *); *ptr = get_le32(buf); offset += 4; #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) printf(" w=%d", *ptr); #endif } break; case 'd': { uint64_t *ptr; if (memcpy_from_queue(s1, buf, queue_idx, desc_idx, offset, 8)) return -1; ptr = va_arg(ap, uint64_t *); *ptr = get_le64(buf); offset += 8; #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) printf(" d=%" PRId64, *ptr); #endif } break; case 's': { char *str, **ptr; int len; if (memcpy_from_queue(s1, buf, queue_idx, desc_idx, offset, 2)) return -1; len = get_le16(buf); offset += 2; str = malloc(len + 1); if (memcpy_from_queue(s1, str, queue_idx, desc_idx, offset, len)) return -1; str[len] = '\0'; offset += len; ptr = va_arg(ap, char **); *ptr = str; #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) printf(" s=\"%s\"", *ptr); #endif } break; default: abort(); } } va_end(ap); *poffset = offset; return 0; } static void virtio_9p_send_reply(VIRTIO9PDevice *s, int queue_idx, int desc_idx, uint8_t id, uint16_t tag, uint8_t *buf, int buf_len) { uint8_t *buf1; int len; #ifdef DEBUG_VIRTIO if (s->common.debug & VIRTIO_DEBUG_9P) { if (id == 6) printf(" (error)"); printf("\n"); } #endif len = buf_len + 7; buf1 = malloc(len); put_le32(buf1, len); buf1[4] = id + 1; put_le16(buf1 + 5, tag); memcpy(buf1 + 7, buf, buf_len); memcpy_to_queue((VIRTIODevice *)s, queue_idx, desc_idx, 0, buf1, len); virtio_consume_desc((VIRTIODevice *)s, queue_idx, desc_idx, len); free(buf1); } static void virtio_9p_send_error(VIRTIO9PDevice *s, int queue_idx, int desc_idx, uint16_t tag, uint32_t error) { uint8_t buf[4]; int buf_len; buf_len = marshall(s, buf, sizeof(buf), "w", -error); virtio_9p_send_reply(s, queue_idx, desc_idx, 6, tag, buf, buf_len); } typedef struct { VIRTIO9PDevice *dev; int queue_idx; int desc_idx; uint16_t tag; } P9OpenInfo; static void virtio_9p_open_reply(FSDevice *fs, FSQID *qid, int err, P9OpenInfo *oi) { VIRTIO9PDevice *s = oi->dev; uint8_t buf[32]; int buf_len; if (err < 0) { virtio_9p_send_error(s, oi->queue_idx, oi->desc_idx, oi->tag, err); } else { buf_len = marshall(s, buf, sizeof(buf), "Qw", qid, s->msize - 24); virtio_9p_send_reply(s, oi->queue_idx, oi->desc_idx, 12, oi->tag, buf, buf_len); } free(oi); } static void virtio_9p_open_cb(FSDevice *fs, FSQID *qid, int err, void *opaque) { P9OpenInfo *oi = opaque; VIRTIO9PDevice *s = oi->dev; int queue_idx = oi->queue_idx; virtio_9p_open_reply(fs, qid, err, oi); s->req_in_progress = FALSE; /* handle next requests */ queue_notify((VIRTIODevice *)s, queue_idx); } static int virtio_9p_recv_request(VIRTIODevice *s1, int queue_idx, int desc_idx, int read_size, int write_size) { VIRTIO9PDevice *s = (VIRTIO9PDevice *)s1; int offset, header_len; uint8_t id; uint16_t tag; uint8_t buf[1024]; int buf_len, err; FSDevice *fs = s->fs; if (queue_idx != 0) return 0; if (s->req_in_progress) return -1; offset = 0; header_len = 4 + 1 + 2; if (memcpy_from_queue(s1, buf, queue_idx, desc_idx, offset, header_len)) { tag = 0; goto protocol_error; } //size = get_le32(buf); id = buf[4]; tag = get_le16(buf + 5); offset += header_len; #ifdef DEBUG_VIRTIO if (s1->debug & VIRTIO_DEBUG_9P) { const char *name; name = get_9p_op_name(id); printf("9p: op="); if (name) printf("%s", name); else printf("%d", id); } #endif /* Note: same subset as JOR1K */ switch(id) { case 8: /* statfs */ { FSStatFS st; fs->fs_statfs(fs, &st); buf_len = marshall(s, buf, sizeof(buf), "wwddddddw", 0, st.f_bsize, st.f_blocks, st.f_bfree, st.f_bavail, st.f_files, st.f_ffree, 0, /* id */ 256 /* max filename length */ ); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 12: /* lopen */ { uint32_t fid, flags; FSFile *f; FSQID qid; P9OpenInfo *oi; if (unmarshall(s, queue_idx, desc_idx, &offset, "ww", &fid, &flags)) goto protocol_error; f = fid_find(s, fid); if (!f) goto fid_not_found; oi = malloc(sizeof(*oi)); oi->dev = s; oi->queue_idx = queue_idx; oi->desc_idx = desc_idx; oi->tag = tag; err = fs->fs_open(fs, &qid, f, flags, virtio_9p_open_cb, oi); if (err <= 0) { virtio_9p_open_reply(fs, &qid, err, oi); } else { s->req_in_progress = TRUE; } } break; case 14: /* lcreate */ { uint32_t fid, flags, mode, gid; char *name; FSFile *f; FSQID qid; if (unmarshall(s, queue_idx, desc_idx, &offset, "wswww", &fid, &name, &flags, &mode, &gid)) goto protocol_error; f = fid_find(s, fid); if (!f) { err = -P9_EPROTO; } else { err = fs->fs_create(fs, &qid, f, name, flags, mode, gid); } free(name); if (err) goto error; buf_len = marshall(s, buf, sizeof(buf), "Qw", &qid, s->msize - 24); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 16: /* symlink */ { uint32_t fid, gid; char *name, *symgt; FSFile *f; FSQID qid; if (unmarshall(s, queue_idx, desc_idx, &offset, "wssw", &fid, &name, &symgt, &gid)) goto protocol_error; f = fid_find(s, fid); if (!f) { err = -P9_EPROTO; } else { err = fs->fs_symlink(fs, &qid, f, name, symgt, gid); } free(name); free(symgt); if (err) goto error; buf_len = marshall(s, buf, sizeof(buf), "Q", &qid); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 18: /* mknod */ { uint32_t fid, mode, major, minor, gid; char *name; FSFile *f; FSQID qid; if (unmarshall(s, queue_idx, desc_idx, &offset, "wswwww", &fid, &name, &mode, &major, &minor, &gid)) goto protocol_error; f = fid_find(s, fid); if (!f) { err = -P9_EPROTO; } else { err = fs->fs_mknod(fs, &qid, f, name, mode, major, minor, gid); } free(name); if (err) goto error; buf_len = marshall(s, buf, sizeof(buf), "Q", &qid); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 22: /* readlink */ { uint32_t fid; char buf1[1024]; FSFile *f; if (unmarshall(s, queue_idx, desc_idx, &offset, "w", &fid)) goto protocol_error; f = fid_find(s, fid); if (!f) { err = -P9_EPROTO; } else { err = fs->fs_readlink(fs, buf1, sizeof(buf1), f); } if (err) goto error; buf_len = marshall(s, buf, sizeof(buf), "s", buf1); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 24: /* getattr */ { uint32_t fid; uint64_t mask; FSFile *f; FSStat st; if (unmarshall(s, queue_idx, desc_idx, &offset, "wd", &fid, &mask)) goto protocol_error; f = fid_find(s, fid); if (!f) goto fid_not_found; err = fs->fs_stat(fs, f, &st); if (err) goto error; buf_len = marshall(s, buf, sizeof(buf), "dQwwwddddddddddddddd", mask, &st.qid, st.st_mode, st.st_uid, st.st_gid, st.st_nlink, st.st_rdev, st.st_size, st.st_blksize, st.st_blocks, st.st_atime_sec, (uint64_t)st.st_atime_nsec, st.st_mtime_sec, (uint64_t)st.st_mtime_nsec, st.st_ctime_sec, (uint64_t)st.st_ctime_nsec, (uint64_t)0, (uint64_t)0, (uint64_t)0, (uint64_t)0); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 26: /* setattr */ { uint32_t fid, mask, mode, uid, gid; uint64_t size, atime_sec, atime_nsec, mtime_sec, mtime_nsec; FSFile *f; if (unmarshall(s, queue_idx, desc_idx, &offset, "wwwwwddddd", &fid, &mask, &mode, &uid, &gid, &size, &atime_sec, &atime_nsec, &mtime_sec, &mtime_nsec)) goto protocol_error; f = fid_find(s, fid); if (!f) goto fid_not_found; err = fs->fs_setattr(fs, f, mask, mode, uid, gid, size, atime_sec, atime_nsec, mtime_sec, mtime_nsec); if (err) goto error; virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); } break; case 30: /* xattrwalk */ { /* not supported yet */ err = -P9_ENOTSUP; goto error; } break; case 40: /* readdir */ { uint32_t fid, count; uint64_t offs; uint8_t *buf; int n; FSFile *f; if (unmarshall(s, queue_idx, desc_idx, &offset, "wdw", &fid, &offs, &count)) goto protocol_error; f = fid_find(s, fid); if (!f) goto fid_not_found; buf = malloc(count + 4); n = fs->fs_readdir(fs, f, offs, buf + 4, count); if (n < 0) { err = n; goto error; } put_le32(buf, n); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, n + 4); free(buf); } break; case 50: /* fsync */ { uint32_t fid; if (unmarshall(s, queue_idx, desc_idx, &offset, "w", &fid)) goto protocol_error; /* ignored */ virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); } break; case 52: /* lock */ { uint32_t fid; FSFile *f; FSLock lock; if (unmarshall(s, queue_idx, desc_idx, &offset, "wbwddws", &fid, &lock.type, &lock.flags, &lock.start, &lock.length, &lock.proc_id, &lock.client_id)) goto protocol_error; f = fid_find(s, fid); if (!f) err = -P9_EPROTO; else err = fs->fs_lock(fs, f, &lock); free(lock.client_id); if (err < 0) goto error; buf_len = marshall(s, buf, sizeof(buf), "b", err); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 54: /* getlock */ { uint32_t fid; FSFile *f; FSLock lock; if (unmarshall(s, queue_idx, desc_idx, &offset, "wbddws", &fid, &lock.type, &lock.start, &lock.length, &lock.proc_id, &lock.client_id)) goto protocol_error; f = fid_find(s, fid); if (!f) err = -P9_EPROTO; else err = fs->fs_getlock(fs, f, &lock); if (err < 0) { free(lock.client_id); goto error; } buf_len = marshall(s, buf, sizeof(buf), "bddws", &lock.type, &lock.start, &lock.length, &lock.proc_id, &lock.client_id); free(lock.client_id); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 70: /* link */ { uint32_t dfid, fid; char *name; FSFile *f, *df; if (unmarshall(s, queue_idx, desc_idx, &offset, "wws", &dfid, &fid, &name)) goto protocol_error; df = fid_find(s, dfid); f = fid_find(s, fid); if (!df || !f) { err = -P9_EPROTO; } else { err = fs->fs_link(fs, df, f, name); } free(name); if (err) goto error; virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); } break; case 72: /* mkdir */ { uint32_t fid, mode, gid; char *name; FSFile *f; FSQID qid; if (unmarshall(s, queue_idx, desc_idx, &offset, "wsww", &fid, &name, &mode, &gid)) goto protocol_error; f = fid_find(s, fid); if (!f) goto fid_not_found; err = fs->fs_mkdir(fs, &qid, f, name, mode, gid); if (err != 0) goto error; buf_len = marshall(s, buf, sizeof(buf), "Q", &qid); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 74: /* renameat */ { uint32_t fid, new_fid; char *name, *new_name; FSFile *f, *new_f; if (unmarshall(s, queue_idx, desc_idx, &offset, "wsws", &fid, &name, &new_fid, &new_name)) goto protocol_error; f = fid_find(s, fid); new_f = fid_find(s, new_fid); if (!f || !new_f) { err = -P9_EPROTO; } else { err = fs->fs_renameat(fs, f, name, new_f, new_name); } free(name); free(new_name); if (err != 0) goto error; virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); } break; case 76: /* unlinkat */ { uint32_t fid, flags; char *name; FSFile *f; if (unmarshall(s, queue_idx, desc_idx, &offset, "wsw", &fid, &name, &flags)) goto protocol_error; f = fid_find(s, fid); if (!f) { err = -P9_EPROTO; } else { err = fs->fs_unlinkat(fs, f, name); } free(name); if (err != 0) goto error; virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); } break; case 100: /* version */ { uint32_t msize; char *version; if (unmarshall(s, queue_idx, desc_idx, &offset, "ws", &msize, &version)) goto protocol_error; s->msize = msize; // printf("version: msize=%d version=%s\n", msize, version); free(version); buf_len = marshall(s, buf, sizeof(buf), "ws", s->msize, "9P2000.L"); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 104: /* attach */ { uint32_t fid, afid, uid; char *uname, *aname; FSQID qid; FSFile *f; if (unmarshall(s, queue_idx, desc_idx, &offset, "wwssw", &fid, &afid, &uname, &aname, &uid)) goto protocol_error; err = fs->fs_attach(fs, &f, &qid, uid, uname, aname); if (err != 0) goto error; fid_set(s, fid, f); free(uname); free(aname); buf_len = marshall(s, buf, sizeof(buf), "Q", &qid); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 108: /* flush */ { uint16_t oldtag; if (unmarshall(s, queue_idx, desc_idx, &offset, "h", &oldtag)) goto protocol_error; /* ignored */ virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); } break; case 110: /* walk */ { uint32_t fid, newfid; uint16_t nwname; FSQID *qids; char **names; FSFile *f; int i; if (unmarshall(s, queue_idx, desc_idx, &offset, "wwh", &fid, &newfid, &nwname)) goto protocol_error; f = fid_find(s, fid); if (!f) goto fid_not_found; names = mallocz(sizeof(names[0]) * nwname); qids = malloc(sizeof(qids[0]) * nwname); for(i = 0; i < nwname; i++) { if (unmarshall(s, queue_idx, desc_idx, &offset, "s", &names[i])) { err = -P9_EPROTO; goto walk_done; } } err = fs->fs_walk(fs, &f, qids, f, nwname, names); walk_done: for(i = 0; i < nwname; i++) { free(names[i]); } free(names); if (err < 0) { free(qids); goto error; } buf_len = marshall(s, buf, sizeof(buf), "h", err); for(i = 0; i < err; i++) { buf_len += marshall(s, buf + buf_len, sizeof(buf) - buf_len, "Q", &qids[i]); } free(qids); fid_set(s, newfid, f); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 116: /* read */ { uint32_t fid, count; uint64_t offs; uint8_t *buf; int n; FSFile *f; if (unmarshall(s, queue_idx, desc_idx, &offset, "wdw", &fid, &offs, &count)) goto protocol_error; f = fid_find(s, fid); if (!f) goto fid_not_found; buf = malloc(count + 4); n = fs->fs_read(fs, f, offs, buf + 4, count); if (n < 0) { err = n; free(buf); goto error; } put_le32(buf, n); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, n + 4); free(buf); } break; case 118: /* write */ { uint32_t fid, count; uint64_t offs; uint8_t *buf1; int n; FSFile *f; if (unmarshall(s, queue_idx, desc_idx, &offset, "wdw", &fid, &offs, &count)) goto protocol_error; f = fid_find(s, fid); if (!f) goto fid_not_found; buf1 = malloc(count); if (memcpy_from_queue(s1, buf1, queue_idx, desc_idx, offset, count)) { free(buf1); goto protocol_error; } n = fs->fs_write(fs, f, offs, buf1, count); free(buf1); if (n < 0) { err = n; goto error; } buf_len = marshall(s, buf, sizeof(buf), "w", n); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); } break; case 120: /* clunk */ { uint32_t fid; if (unmarshall(s, queue_idx, desc_idx, &offset, "w", &fid)) goto protocol_error; fid_delete(s, fid); virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); } break; default: printf("9p: unsupported operation id=%d\n", id); goto protocol_error; } return 0; error: virtio_9p_send_error(s, queue_idx, desc_idx, tag, err); return 0; protocol_error: fid_not_found: err = -P9_EPROTO; goto error; } VIRTIODevice *virtio_9p_init(VIRTIOBusDef *bus, FSDevice *fs, const char *mount_tag) { VIRTIO9PDevice *s; int len; uint8_t *cfg; len = strlen(mount_tag); s = mallocz(sizeof(*s)); virtio_init(&s->common, bus, 9, 2 + len, virtio_9p_recv_request); s->common.device_features = 1 << 0; /* set the mount tag */ cfg = s->common.config_space; cfg[0] = len; cfg[1] = len >> 8; memcpy(cfg + 2, mount_tag, len); s->fs = fs; s->msize = 8192; init_list_head(&s->fid_list); return (VIRTIODevice *)s; }