| /* | |
| * Copyright © 2018, 2021 Oracle and/or its affiliates. | |
| * | |
| * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
| * See the COPYING file in the top-level directory. | |
| * | |
| */ | |
| #include "qemu/osdep.h" | |
| #include "hw/remote/proxy.h" | |
| #include "hw/pci/pci.h" | |
| #include "qapi/error.h" | |
| #include "io/channel-util.h" | |
| #include "hw/core/qdev-properties.h" | |
| #include "monitor/monitor.h" | |
| #include "migration/blocker.h" | |
| #include "qemu/sockets.h" | |
| #include "hw/remote/mpqemu-link.h" | |
| #include "qemu/error-report.h" | |
| #include "hw/remote/proxy-memory-listener.h" | |
| #include "qom/object.h" | |
| #include "qemu/event_notifier.h" | |
| #include "system/kvm.h" | |
| static void probe_pci_info(PCIDevice *dev, Error **errp); | |
| static void proxy_device_reset(DeviceState *dev); | |
| static void proxy_intx_update(PCIDevice *pci_dev) | |
| { | |
| PCIProxyDev *dev = PCI_PROXY_DEV(pci_dev); | |
| PCIINTxRoute route; | |
| int pin = pci_get_byte(pci_dev->config + PCI_INTERRUPT_PIN) - 1; | |
| if (dev->virq != -1) { | |
| kvm_irqchip_remove_irqfd_notifier_gsi(kvm_state, &dev->intr, dev->virq); | |
| dev->virq = -1; | |
| } | |
| route = pci_device_route_intx_to_irq(pci_dev, pin); | |
| dev->virq = route.irq; | |
| if (dev->virq != -1) { | |
| kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, &dev->intr, | |
| &dev->resample, dev->virq); | |
| } | |
| } | |
| static void setup_irqfd(PCIProxyDev *dev) | |
| { | |
| PCIDevice *pci_dev = PCI_DEVICE(dev); | |
| MPQemuMsg msg; | |
| Error *local_err = NULL; | |
| int ret = 0; | |
| ret = event_notifier_init(&dev->intr, 0); | |
| if (ret < 0) { | |
| error_report("Failed to init intr notifier: %s", strerror(-ret)); | |
| return; | |
| } | |
| ret = event_notifier_init(&dev->resample, 0); | |
| if (ret < 0) { | |
| error_report("Failed to init resample notifier: %s", strerror(-ret)); | |
| event_notifier_cleanup(&dev->intr); | |
| return; | |
| } | |
| memset(&msg, 0, sizeof(MPQemuMsg)); | |
| msg.cmd = MPQEMU_CMD_SET_IRQFD; | |
| msg.num_fds = 2; | |
| msg.fds[0] = event_notifier_get_fd(&dev->intr); | |
| msg.fds[1] = event_notifier_get_fd(&dev->resample); | |
| msg.size = 0; | |
| if (!mpqemu_msg_send(&msg, dev->ioc, &local_err)) { | |
| error_report_err(local_err); | |
| } | |
| dev->virq = -1; | |
| proxy_intx_update(pci_dev); | |
| pci_device_set_intx_routing_notifier(pci_dev, proxy_intx_update); | |
| } | |
| static void pci_proxy_dev_realize(PCIDevice *device, Error **errp) | |
| { | |
| ERRP_GUARD(); | |
| PCIProxyDev *dev = PCI_PROXY_DEV(device); | |
| uint8_t *pci_conf = device->config; | |
| int fd; | |
| if (!dev->fd) { | |
| error_setg(errp, "fd parameter not specified for %s", | |
| DEVICE(device)->id); | |
| return; | |
| } | |
| fd = monitor_fd_param(monitor_cur(), dev->fd, errp); | |
| if (fd == -1) { | |
| error_prepend(errp, "proxy: unable to parse fd %s: ", dev->fd); | |
| return; | |
| } | |
| if (!fd_is_socket(fd)) { | |
| error_setg(errp, "proxy: fd %d is not a socket", fd); | |
| close(fd); | |
| return; | |
| } | |
| dev->ioc = qio_channel_new_fd(fd, errp); | |
| if (!dev->ioc) { | |
| close(fd); | |
| return; | |
| } | |
| error_setg(&dev->migration_blocker, "%s does not support migration", | |
| TYPE_PCI_PROXY_DEV); | |
| if (migrate_add_blocker(&dev->migration_blocker, errp) < 0) { | |
| object_unref(dev->ioc); | |
| return; | |
| } | |
| if (!qio_channel_set_blocking(dev->ioc, true, errp)) { | |
| object_unref(dev->ioc); | |
| return; | |
| } | |
| qemu_mutex_init(&dev->io_mutex); | |
| pci_conf[PCI_LATENCY_TIMER] = 0xff; | |
| pci_conf[PCI_INTERRUPT_PIN] = 0x01; | |
| proxy_memory_listener_configure(&dev->proxy_listener, dev->ioc); | |
| setup_irqfd(dev); | |
| probe_pci_info(PCI_DEVICE(dev), errp); | |
| } | |
| static void pci_proxy_dev_exit(PCIDevice *pdev) | |
| { | |
| PCIProxyDev *dev = PCI_PROXY_DEV(pdev); | |
| if (dev->ioc) { | |
| qio_channel_close(dev->ioc, NULL); | |
| } | |
| migrate_del_blocker(&dev->migration_blocker); | |
| proxy_memory_listener_deconfigure(&dev->proxy_listener); | |
| event_notifier_cleanup(&dev->intr); | |
| event_notifier_cleanup(&dev->resample); | |
| } | |
| static void config_op_send(PCIProxyDev *pdev, uint32_t addr, uint32_t *val, | |
| int len, unsigned int op) | |
| { | |
| MPQemuMsg msg = { 0 }; | |
| uint64_t ret = -EINVAL; | |
| Error *local_err = NULL; | |
| msg.cmd = op; | |
| msg.data.pci_conf_data.addr = addr; | |
| msg.data.pci_conf_data.val = (op == MPQEMU_CMD_PCI_CFGWRITE) ? *val : 0; | |
| msg.data.pci_conf_data.len = len; | |
| msg.size = sizeof(PciConfDataMsg); | |
| ret = mpqemu_msg_send_and_await_reply(&msg, pdev, &local_err); | |
| if (local_err) { | |
| error_report_err(local_err); | |
| } | |
| if (ret == UINT64_MAX) { | |
| error_report("Failed to perform PCI config %s operation", | |
| (op == MPQEMU_CMD_PCI_CFGREAD) ? "READ" : "WRITE"); | |
| } | |
| if (op == MPQEMU_CMD_PCI_CFGREAD) { | |
| *val = (uint32_t)ret; | |
| } | |
| } | |
| static uint32_t pci_proxy_read_config(PCIDevice *d, uint32_t addr, int len) | |
| { | |
| uint32_t val; | |
| config_op_send(PCI_PROXY_DEV(d), addr, &val, len, MPQEMU_CMD_PCI_CFGREAD); | |
| return val; | |
| } | |
| static void pci_proxy_write_config(PCIDevice *d, uint32_t addr, uint32_t val, | |
| int len) | |
| { | |
| /* | |
| * Some of the functions access the copy of remote device's PCI config | |
| * space which is cached in the proxy device. Therefore, maintain | |
| * it updated. | |
| */ | |
| pci_default_write_config(d, addr, val, len); | |
| config_op_send(PCI_PROXY_DEV(d), addr, &val, len, MPQEMU_CMD_PCI_CFGWRITE); | |
| } | |
| static const Property proxy_properties[] = { | |
| DEFINE_PROP_STRING("fd", PCIProxyDev, fd), | |
| }; | |
| static void pci_proxy_dev_class_init(ObjectClass *klass, const void *data) | |
| { | |
| DeviceClass *dc = DEVICE_CLASS(klass); | |
| PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); | |
| k->realize = pci_proxy_dev_realize; | |
| k->exit = pci_proxy_dev_exit; | |
| k->config_read = pci_proxy_read_config; | |
| k->config_write = pci_proxy_write_config; | |
| device_class_set_legacy_reset(dc, proxy_device_reset); | |
| device_class_set_props(dc, proxy_properties); | |
| } | |
| static const TypeInfo pci_proxy_dev_type_info = { | |
| .name = TYPE_PCI_PROXY_DEV, | |
| .parent = TYPE_PCI_DEVICE, | |
| .instance_size = sizeof(PCIProxyDev), | |
| .class_init = pci_proxy_dev_class_init, | |
| .interfaces = (const InterfaceInfo[]) { | |
| { INTERFACE_CONVENTIONAL_PCI_DEVICE }, | |
| { }, | |
| }, | |
| }; | |
| static void pci_proxy_dev_register_types(void) | |
| { | |
| type_register_static(&pci_proxy_dev_type_info); | |
| } | |
| type_init(pci_proxy_dev_register_types) | |
| static void send_bar_access_msg(PCIProxyDev *pdev, MemoryRegion *mr, | |
| bool write, hwaddr addr, uint64_t *val, | |
| unsigned size, bool memory) | |
| { | |
| MPQemuMsg msg = { 0 }; | |
| long ret = -EINVAL; | |
| Error *local_err = NULL; | |
| msg.size = sizeof(BarAccessMsg); | |
| msg.data.bar_access.addr = mr->addr + addr; | |
| msg.data.bar_access.size = size; | |
| msg.data.bar_access.memory = memory; | |
| if (write) { | |
| msg.cmd = MPQEMU_CMD_BAR_WRITE; | |
| msg.data.bar_access.val = *val; | |
| } else { | |
| msg.cmd = MPQEMU_CMD_BAR_READ; | |
| } | |
| ret = mpqemu_msg_send_and_await_reply(&msg, pdev, &local_err); | |
| if (local_err) { | |
| error_report_err(local_err); | |
| } | |
| if (!write) { | |
| *val = ret; | |
| } | |
| } | |
| static void proxy_bar_write(void *opaque, hwaddr addr, uint64_t val, | |
| unsigned size) | |
| { | |
| ProxyMemoryRegion *pmr = opaque; | |
| send_bar_access_msg(pmr->dev, &pmr->mr, true, addr, &val, size, | |
| pmr->memory); | |
| } | |
| static uint64_t proxy_bar_read(void *opaque, hwaddr addr, unsigned size) | |
| { | |
| ProxyMemoryRegion *pmr = opaque; | |
| uint64_t val; | |
| send_bar_access_msg(pmr->dev, &pmr->mr, false, addr, &val, size, | |
| pmr->memory); | |
| return val; | |
| } | |
| const MemoryRegionOps proxy_mr_ops = { | |
| .read = proxy_bar_read, | |
| .write = proxy_bar_write, | |
| .endianness = DEVICE_NATIVE_ENDIAN, | |
| .impl = { | |
| .min_access_size = 1, | |
| .max_access_size = 8, | |
| }, | |
| }; | |
| static void probe_pci_info(PCIDevice *dev, Error **errp) | |
| { | |
| PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev); | |
| uint32_t orig_val, new_val, base_class, val; | |
| PCIProxyDev *pdev = PCI_PROXY_DEV(dev); | |
| DeviceClass *dc = DEVICE_CLASS(pc); | |
| uint8_t type; | |
| int i, size; | |
| config_op_send(pdev, PCI_VENDOR_ID, &val, 2, MPQEMU_CMD_PCI_CFGREAD); | |
| pc->vendor_id = (uint16_t)val; | |
| config_op_send(pdev, PCI_DEVICE_ID, &val, 2, MPQEMU_CMD_PCI_CFGREAD); | |
| pc->device_id = (uint16_t)val; | |
| config_op_send(pdev, PCI_CLASS_DEVICE, &val, 2, MPQEMU_CMD_PCI_CFGREAD); | |
| pc->class_id = (uint16_t)val; | |
| config_op_send(pdev, PCI_SUBSYSTEM_ID, &val, 2, MPQEMU_CMD_PCI_CFGREAD); | |
| pc->subsystem_id = (uint16_t)val; | |
| base_class = pc->class_id >> 4; | |
| switch (base_class) { | |
| case PCI_BASE_CLASS_BRIDGE: | |
| set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); | |
| break; | |
| case PCI_BASE_CLASS_STORAGE: | |
| set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); | |
| break; | |
| case PCI_BASE_CLASS_NETWORK: | |
| case PCI_BASE_CLASS_WIRELESS: | |
| set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); | |
| break; | |
| case PCI_BASE_CLASS_INPUT: | |
| set_bit(DEVICE_CATEGORY_INPUT, dc->categories); | |
| break; | |
| case PCI_BASE_CLASS_DISPLAY: | |
| set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories); | |
| break; | |
| case PCI_BASE_CLASS_PROCESSOR: | |
| set_bit(DEVICE_CATEGORY_CPU, dc->categories); | |
| break; | |
| default: | |
| set_bit(DEVICE_CATEGORY_MISC, dc->categories); | |
| break; | |
| } | |
| for (i = 0; i < PCI_NUM_REGIONS; i++) { | |
| config_op_send(pdev, PCI_BASE_ADDRESS_0 + (4 * i), &orig_val, 4, | |
| MPQEMU_CMD_PCI_CFGREAD); | |
| new_val = 0xffffffff; | |
| config_op_send(pdev, PCI_BASE_ADDRESS_0 + (4 * i), &new_val, 4, | |
| MPQEMU_CMD_PCI_CFGWRITE); | |
| config_op_send(pdev, PCI_BASE_ADDRESS_0 + (4 * i), &new_val, 4, | |
| MPQEMU_CMD_PCI_CFGREAD); | |
| size = (~(new_val & 0xFFFFFFF0)) + 1; | |
| config_op_send(pdev, PCI_BASE_ADDRESS_0 + (4 * i), &orig_val, 4, | |
| MPQEMU_CMD_PCI_CFGWRITE); | |
| type = (new_val & 0x1) ? | |
| PCI_BASE_ADDRESS_SPACE_IO : PCI_BASE_ADDRESS_SPACE_MEMORY; | |
| if (size) { | |
| g_autofree char *name = g_strdup_printf("bar-region-%d", i); | |
| pdev->region[i].dev = pdev; | |
| pdev->region[i].present = true; | |
| if (type == PCI_BASE_ADDRESS_SPACE_MEMORY) { | |
| pdev->region[i].memory = true; | |
| } | |
| memory_region_init_io(&pdev->region[i].mr, OBJECT(pdev), | |
| &proxy_mr_ops, &pdev->region[i], | |
| name, size); | |
| pci_register_bar(dev, i, type, &pdev->region[i].mr); | |
| } | |
| } | |
| } | |
| static void proxy_device_reset(DeviceState *dev) | |
| { | |
| PCIProxyDev *pdev = PCI_PROXY_DEV(dev); | |
| MPQemuMsg msg = { 0 }; | |
| Error *local_err = NULL; | |
| msg.cmd = MPQEMU_CMD_DEVICE_RESET; | |
| msg.size = 0; | |
| mpqemu_msg_send_and_await_reply(&msg, pdev, &local_err); | |
| if (local_err) { | |
| error_report_err(local_err); | |
| } | |
| } |