| /* |
| * vNPlugDev (PCI Device) |
| * |
| * Authors: |
| * |
| * Alfredo Cardigliano <cardigliano@ntop.org> |
| * |
| * This work is licensed under the terms of the GNU GPL version 2. |
| * |
| */ |
| |
| #include <sys/mman.h> |
| #include <sys/types.h> |
| #include <sys/eventfd.h> |
| |
| #include "hw.h" |
| #include "pc.h" |
| #include "pci.h" |
| #include "msix.h" |
| #include "kvm.h" |
| |
| #include "vnplug.h" |
| #include "vnplug-dev.h" |
| |
| //#define VNPLUGDEV_DEBUG |
| |
| #ifdef VNPLUGDEV_DEBUG |
| #define VNPLUGDEV_DEBUG_PRINTF(fmt, ...) do {printf("[vNPlugDev] " fmt, ## __VA_ARGS__); } while (0) |
| #else |
| #define VNPLUGDEV_DEBUG_PRINTF(fmt, ...) |
| #endif |
| |
| static uint32_t vnplug_device_id_counter = 0; |
| |
| /* ******************************************************************************************* */ |
| |
| static void vnplug_dev_io_writew(void *opaque, target_phys_addr_t addr, uint32_t val) |
| { |
| VNPLUGDEV_DEBUG_PRINTF("io_writew: unhandled\n"); |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| /* called when the guest writes to the registers region */ |
| static void vnplug_dev_io_writel(void *opaque, target_phys_addr_t addr, uint32_t val) |
| { |
| struct vNPlugDev *s = opaque; |
| |
| addr &= 0xffc; |
| |
| VNPLUGDEV_DEBUG_PRINTF("io_writel: registers[" TARGET_FMT_plx "] = %u\n", addr, val); |
| |
| if (!((struct vNPlugDevClientInfo *) s->dev_client)->io_writel_handler) |
| fprintf(stderr, "[vNPlugDev] io_writel: client handler undefined\n"); |
| else |
| ((struct vNPlugDevClientInfo *) s->dev_client)->io_writel_handler((struct vNPlugDevClientInfo *) s->dev_client, addr, val); |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| static void vnplug_dev_io_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) |
| { |
| VNPLUGDEV_DEBUG_PRINTF("io_writeb: unhandled\n"); |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| static uint32_t vnplug_dev_io_readw(void *opaque, target_phys_addr_t addr) |
| { |
| VNPLUGDEV_DEBUG_PRINTF("io_readw: unhandled\n"); |
| return 0; |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| /* called when the guest reads from the registers region */ |
| static uint32_t vnplug_dev_io_readl(void *opaque, target_phys_addr_t addr) |
| { |
| struct vNPlugDev *s = opaque; |
| uint32_t ret = 0; |
| |
| addr &= 0xffc; |
| |
| if (addr == VNPLUGDEV_REG_OFF_ID){ |
| ret = s->dev_id; |
| goto return_ret; |
| } |
| |
| if ( addr >= VNPLUGDEV_REG_OFF_BASE_MM_SIZE && |
| addr < VNPLUGDEV_REG_OFF_BASE_MM_SIZE + VNPLUGDEV_MAX_MM_BARS * sizeof(uint32_t) && |
| !(addr & 0x3)){ |
| ret = s->mm_dev_size[(addr-VNPLUGDEV_REG_OFF_BASE_MM_SIZE)>>2]; |
| goto return_ret; |
| } |
| |
| if (((struct vNPlugDevClientInfo *) s->dev_client)->io_readl_handler){ |
| ret = ((struct vNPlugDevClientInfo *) s->dev_client)->io_readl_handler((struct vNPlugDevClientInfo *) s->dev_client, addr); |
| goto return_ret; |
| } |
| |
| fprintf(stderr, "[vNPlugDev] io_readl: client handler undefined\n"); |
| ret = 0; |
| |
| return_ret: |
| VNPLUGDEV_DEBUG_PRINTF("io_readl: registers[" TARGET_FMT_plx "] value=%u\n", addr, ret); |
| return ret; |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| static uint32_t vnplug_dev_io_readb(void *opaque, target_phys_addr_t addr) |
| { |
| VNPLUGDEV_DEBUG_PRINTF("io_readb: unhandled, returning 0\n"); |
| return 0; |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| static CPUReadMemoryFunc * const vnplug_dev_mmio_read[3] = { |
| vnplug_dev_io_readb, |
| vnplug_dev_io_readw, |
| vnplug_dev_io_readl, |
| }; |
| |
| static CPUWriteMemoryFunc * const vnplug_dev_mmio_write[3] = { |
| vnplug_dev_io_writeb, |
| vnplug_dev_io_writew, |
| vnplug_dev_io_writel, |
| }; |
| |
| /* ******************************************************************************************* */ |
| |
| /* to setup irqfds use on=1, to unset them use on=0 */ |
| static int setup_irqfds(struct vNPlugDev *s, int on) { |
| int i, err = 0; |
| |
| for (i = 0; i < s->vectors; i++) |
| /* irqfd support: interrupts are injected when a signal on an eventfd occurs */ |
| if ( ( err = kvm_set_irqfd(s->dev.msix_irq_entries[i].gsi, s->backend_eventfds[i], on) ) < 0) |
| fprintf(stderr, "[vNPlugDev] irqfd warning (err=%d). Not available, or already set?\n", err); |
| else { |
| VNPLUGDEV_DEBUG_PRINTF("irqfd on=%d for host event %d\n", on, i); |
| } |
| |
| return err; |
| } |
| |
| |
| /* ******************************************************************************************* */ |
| |
| /* to setup ioeventfds use on=1, to unset them use on=0 */ |
| static int setup_ioeventfds(struct vNPlugDev *s, int on) { |
| int i, err = 0; |
| |
| for (i = 0; i < s->guest_events_n; i++) |
| /* setting ioeventfd support to raise an event when a write on |
| * mmio_pci_addr + VNPLUGDEV_REG_OFF_DOORBELL occurs, passing i as the value to match |
| * (the relative io_writel handler will not be called) */ |
| if ( (err = kvm_set_ioeventfd_mmio_long(s->guest_eventfds[i], s->mmio_pci_addr + VNPLUGDEV_REG_OFF_DOORBELL, i, on)) < 0) |
| fprintf(stderr, "[vNPlugDev] ioeventfd warning (err=%d). Not available, or already set?\n", err); |
| else { |
| VNPLUGDEV_DEBUG_PRINTF("ioeventfd on=%d for guest event %d\n", on, i); |
| } |
| |
| return err; |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| static void vnplug_dev_reset(DeviceState *d) |
| { |
| //struct vNPlugDev *s = DO_UPCAST(struct vNPlugDev, dev.qdev, d); |
| return; |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| /* region 2..5 (mmap-ed memory) map handler */ |
| static void vnplug_dev_mm_map(PCIDevice *pci_dev, int region_num, pcibus_t addr, pcibus_t size, int type) |
| { |
| struct vNPlugDev *s = DO_UPCAST(struct vNPlugDev, dev, pci_dev); |
| |
| // BAR0=registers, BAR1=MSI, BAR2..5=mm |
| int index = region_num - VNPLUGDEV_BASE_MM_BAR; |
| |
| VNPLUGDEV_DEBUG_PRINTF("registering shared memory %d at guest pci addr=%" FMT_PCIBUS ", guest hw addr=%" PRIu64 ", with size=%" FMT_PCIBUS "\n", index, addr, s->mm_dev_offset[index], size); |
| |
| if (index < 0 || index > VNPLUGDEV_MAX_MM_BARS-1) { |
| fprintf(stderr, "[vNPlugDev] mm bar index out of range: %d\n", index); |
| return; |
| } |
| |
| s->mm_pci_addr[index] = addr; |
| |
| if (s->mm_dev_offset[index] > 0) |
| cpu_register_physical_memory(s->mm_pci_addr[index], s->mm_dev_size[index], s->mm_dev_offset[index]); |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| static void vnplug_dev_mmio_map(PCIDevice *pci_dev, int region_num, pcibus_t addr, pcibus_t size, int type) |
| { |
| struct vNPlugDev *s = DO_UPCAST(struct vNPlugDev, dev, pci_dev); |
| |
| VNPLUGDEV_DEBUG_PRINTF("registering mmio memory\n"); |
| |
| s->mmio_pci_addr = addr; |
| cpu_register_physical_memory(addr + 0, VNPLUGDEV_REG_BAR_SIZE, s->mmio_dev_offset); |
| |
| setup_ioeventfds(s, 1); |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| static void vnplug_dev_setup_msi(struct vNPlugDev * s) |
| { |
| int i; |
| |
| if (msix_init(&s->dev, s->vectors, 1, 0) == 0) { |
| /* MSI region */ |
| pci_register_bar(&s->dev, 1, msix_bar_size(&s->dev), PCI_BASE_ADDRESS_SPACE_MEMORY, msix_mmio_map); |
| |
| VNPLUGDEV_DEBUG_PRINTF("msix initialized (%d vectors)\n", s->vectors); |
| } else { |
| VNPLUGDEV_DEBUG_PRINTF("msix initialization failed\n"); |
| exit(1); |
| } |
| |
| for (i = 0; i < s->vectors; i++) { |
| msix_vector_use(&s->dev, i); |
| } |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| static const VMStateDescription vmstate_vnplug_device = { |
| .name = "vnplug-dev", |
| .fields = (VMStateField []) { |
| VMSTATE_END_OF_LIST() |
| } |
| }; |
| |
| /* ******************************************************************************************* */ |
| |
| static int pci_vnplug_dev_init(PCIDevice *dev) |
| { |
| struct vNPlugDev *s = DO_UPCAST(struct vNPlugDev, dev, dev); |
| uint8_t *pci_conf; |
| int i; |
| char ram_block_name[32]; |
| |
| struct vNPlugDevClientInfo *client = (struct vNPlugDevClientInfo *) s->dev_client; |
| if (!client){ |
| fprintf(stderr, "[vNPlugDev] undefined vNPlug-dev client info\n"); |
| exit(-1); |
| } |
| |
| s->dev_id = vnplug_device_id_counter++; |
| |
| vmstate_register(&dev->qdev, s->dev_id, &vmstate_vnplug_device, s); |
| register_device_unmigratable(&dev->qdev, vmstate_vnplug_device.name, s); |
| |
| pci_conf = s->dev.config; |
| pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_SILICOM); |
| pci_conf[0x02] = (PCI_DEVICE_ID_VNPLUG_DEV) & 0xff; |
| pci_conf[0x03] = (PCI_DEVICE_ID_VNPLUG_DEV >> 8) & 0xff; |
| pci_conf[PCI_COMMAND] = PCI_COMMAND_IO | PCI_COMMAND_MEMORY; |
| pci_config_set_class(pci_conf, PCI_CLASS_MEMORY_RAM); |
| pci_conf[PCI_HEADER_TYPE] = PCI_HEADER_TYPE_NORMAL; |
| pci_config_set_interrupt_pin(pci_conf, 1); |
| |
| for (i=0; i<VNPLUGDEV_MAX_MM_BARS; i++){ |
| s->mm_pci_addr[i] = 0; |
| s->mm_dev_offset [i] = 0; |
| } |
| s->mm_dev_size [0] = s->mm_dev_size_0; |
| s->mm_dev_vma_ptr[0] = s->mm_dev_vma_ptr_0; |
| s->mm_dev_size [1] = s->mm_dev_size_1; |
| s->mm_dev_vma_ptr[1] = s->mm_dev_vma_ptr_1; |
| s->mm_dev_size [2] = s->mm_dev_size_2; |
| s->mm_dev_vma_ptr[2] = s->mm_dev_vma_ptr_2; |
| s->mm_dev_size [3] = s->mm_dev_size_3; |
| s->mm_dev_vma_ptr[3] = s->mm_dev_vma_ptr_3; |
| |
| VNPLUGDEV_DEBUG_PRINTF("setting mmio read/write handlers for device %u [ passing opaque (struct vNPlugDev *) = %p ]\n", s->dev_id, s); |
| |
| s->mmio_dev_offset = cpu_register_io_memory(vnplug_dev_mmio_read, vnplug_dev_mmio_write, s, DEVICE_NATIVE_ENDIAN); |
| |
| /* region for registers */ |
| pci_register_bar(&s->dev, 0, VNPLUGDEV_REG_BAR_SIZE, PCI_BASE_ADDRESS_SPACE_MEMORY, vnplug_dev_mmio_map); |
| |
| /* setup host to guest events via MSI */ |
| |
| s->vectors = s->backend_events_n; |
| |
| VNPLUGDEV_DEBUG_PRINTF("setting up %d MSI-X vectors\n", s->vectors); |
| |
| /* msi init & region for msi */ |
| vnplug_dev_setup_msi(s); |
| |
| VNPLUGDEV_DEBUG_PRINTF("setting up %d backend eventdfds\n", s->vectors); |
| s->backend_eventfds = qemu_mallocz(s->vectors * sizeof(int)); |
| for (i=0;i<s->vectors;i++){ |
| if ((s->backend_eventfds[i] = eventfd(0, 0)) < 0) { |
| fprintf(stderr, "[vNPlugDev] failed to create eventfd\n"); |
| exit(-1); |
| } |
| } |
| |
| VNPLUGDEV_DEBUG_PRINTF("setting up the irqfd support\n"); |
| |
| setup_irqfds(s, 1); |
| |
| VNPLUGDEV_DEBUG_PRINTF("setting up %d guest eventdfds\n", s->guest_events_n); |
| s->guest_eventfds = qemu_mallocz(s->guest_events_n * sizeof(int)); |
| for (i=0;i<s->guest_events_n;i++){ |
| if ((s->guest_eventfds[i] = eventfd(0, 0)) < 0) { |
| fprintf(stderr, "[vNPlugDev] failed to create eventfd\n"); |
| exit(-1); |
| } |
| } |
| |
| for (i=0; i<VNPLUGDEV_MAX_MM_BARS; i++) |
| if (s->mm_dev_size[i] && s->mm_dev_vma_ptr[i]){ |
| |
| /* Checking size (it must be power of two) */ |
| if ((s->mm_dev_size[i] & (s->mm_dev_size[i] - 1)) != 0){ |
| fprintf(stderr, "[vNPlugDev] device memory %d size is not power of 2\n", i); |
| exit(-1); |
| } |
| |
| /* ram block with mmapped memory to share */ |
| snprintf(ram_block_name, 32, "vnplug-dev-%u.bar%d", s->dev_id, VNPLUGDEV_BASE_MM_BAR+i); |
| s->mm_dev_offset[i] = qemu_ram_alloc_from_ptr(&s->dev.qdev, ram_block_name, s->mm_dev_size[i], (void *) s->mm_dev_vma_ptr[i]); |
| |
| VNPLUGDEV_DEBUG_PRINTF("ram block %d allocated from ptr %" PRIu64 " and size %u at guest hw addr %" PRIu64 "\n", i, s->mm_dev_vma_ptr[i], s->mm_dev_size[i], s->mm_dev_offset[i]); |
| |
| /* region for shared memory */ |
| pci_register_bar(&s->dev, VNPLUGDEV_BASE_MM_BAR+i, s->mm_dev_size[i], PCI_BASE_ADDRESS_SPACE_MEMORY, vnplug_dev_mm_map); |
| } |
| |
| if (!s->dev_client || !(client->set_init_data_handler)){ |
| fprintf(stderr, "[vNPlugDev] client or client handlers undefined\n"); |
| exit(-1); |
| } |
| |
| /* setting dev id and eventfds on client */ |
| client->set_init_data_handler(client, s->dev_id, s->backend_events_n, s->backend_eventfds, s->guest_events_n, s->guest_eventfds); |
| |
| return 0; |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| static int pci_vnplug_dev_uninit(PCIDevice *dev) |
| { |
| int i; |
| struct vNPlugDev *s = DO_UPCAST(struct vNPlugDev, dev, dev); |
| struct vNPlugDevClientInfo *client = (struct vNPlugDevClientInfo *) s->dev_client; |
| |
| if (client && client->pre_unplug_handler) |
| client->pre_unplug_handler(client); |
| |
| setup_irqfds(s, 0); |
| |
| setup_ioeventfds(s, 0); |
| |
| for (i=0; i<VNPLUGDEV_MAX_MM_BARS; i++) |
| if (s->mm_dev_size[i] && s->mm_dev_vma_ptr[i]){ |
| if (s->mm_pci_addr[i]){ |
| cpu_register_physical_memory(s->mm_pci_addr[i], s->mm_dev_size[i], IO_MEM_UNASSIGNED); |
| VNPLUGDEV_DEBUG_PRINTF("vnplug BAR%d unregistered\n", VNPLUGDEV_BASE_MM_BAR+i); |
| } |
| if (s->mm_dev_offset[i]){ |
| qemu_ram_free_from_ptr(s->mm_dev_offset[i]); |
| VNPLUGDEV_DEBUG_PRINTF("vnplug BAR%d unmapped\n", VNPLUGDEV_BASE_MM_BAR+i); |
| } |
| } |
| |
| cpu_unregister_io_memory(s->mmio_dev_offset); |
| |
| vmstate_unregister(&dev->qdev, &vmstate_vnplug_device, s); |
| |
| for (i = 0; i < s->vectors; i++) |
| close(s->backend_eventfds[i]); |
| |
| for (i = 0; i < s->guest_events_n; i++) { |
| close(s->guest_eventfds[i]); |
| } |
| |
| qemu_free(s->backend_eventfds); |
| qemu_free(s->guest_eventfds); |
| |
| msix_uninit(dev); |
| |
| if (client && client->post_unplug_handler) |
| client->post_unplug_handler(client); |
| |
| VNPLUGDEV_DEBUG_PRINTF("vnplug device uninitalization done\n"); |
| |
| return 0; |
| } |
| |
| /* ******************************************************************************************* */ |
| |
| static PCIDeviceInfo vnplug_dev_info = { |
| .qdev.name = "vnplug-dev", |
| .qdev.size = sizeof(struct vNPlugDev), |
| .qdev.reset = vnplug_dev_reset, |
| .init = pci_vnplug_dev_init, |
| .exit = pci_vnplug_dev_uninit, |
| .qdev.props = (Property[]) { |
| DEFINE_PROP_UINT32("backend_events_n", struct vNPlugDev, backend_events_n, 1), |
| DEFINE_PROP_UINT32("guest_events_n", struct vNPlugDev, guest_events_n, 1), |
| //generated in pci_vnplug_dev_init: DEFINE_PROP_UINT32("dev_id", struct vNPlugDev, dev_id, 0), |
| |
| DEFINE_PROP_UINT32("vma_size", struct vNPlugDev, mm_dev_size_0, 0), |
| //ptrs changed to uint64: DEFINE_PROP_PTR("vma_ptr", struct vNPlugDev, mm_dev_vma_ptr), |
| DEFINE_PROP_UINT64("vma_ptr", struct vNPlugDev, mm_dev_vma_ptr_0, 0), |
| |
| DEFINE_PROP_UINT32("vma_size_1", struct vNPlugDev, mm_dev_size_1, 0), |
| DEFINE_PROP_UINT64("vma_ptr_1", struct vNPlugDev, mm_dev_vma_ptr_1, 0), |
| |
| DEFINE_PROP_UINT32("vma_size_2", struct vNPlugDev, mm_dev_size_2, 0), |
| DEFINE_PROP_UINT64("vma_ptr_2", struct vNPlugDev, mm_dev_vma_ptr_2, 0), |
| |
| DEFINE_PROP_UINT32("vma_size_3", struct vNPlugDev, mm_dev_size_3, 0), |
| DEFINE_PROP_UINT64("vma_ptr_3", struct vNPlugDev, mm_dev_vma_ptr_3, 0), |
| |
| DEFINE_PROP_UINT64("client_info_ptr", struct vNPlugDev, dev_client, 0), |
| DEFINE_PROP_END_OF_LIST(), |
| } |
| }; |
| |
| static void vnplug_dev_register_devices(void) |
| { |
| pci_qdev_register(&vnplug_dev_info); |
| } |
| |
| device_init(vnplug_dev_register_devices) |