diff --git a/include/spdk/vmd.h b/include/spdk/vmd.h index 644cbf869..a07b381b1 100644 --- a/include/spdk/vmd.h +++ b/include/spdk/vmd.h @@ -96,6 +96,14 @@ int spdk_vmd_set_led_state(struct spdk_pci_device *pci_device, enum spdk_vmd_led */ int spdk_vmd_get_led_state(struct spdk_pci_device *pci_device, enum spdk_vmd_led_state *state); +/** + * Checks for hotplug/hotremove events of the devices behind the VMD. Needs to be called + * periodically to detect them. + * + * \return number of hotplug events detected or negative errno in case of errors + */ +int spdk_vmd_hotplug_monitor(void); + #ifdef __cplusplus } #endif diff --git a/lib/vmd/vmd.c b/lib/vmd/vmd.c index 5fc38ddbe..7fcd8d684 100644 --- a/lib/vmd/vmd.c +++ b/lib/vmd/vmd.c @@ -467,6 +467,24 @@ vmd_init_hotplug(struct vmd_pci_device *dev, struct vmd_pci_bus *bus) bus->self->header->one.mem_base, bus->self->header->one.mem_limit); } +static bool +vmd_bus_device_present(struct vmd_pci_bus *bus, uint32_t devfn) +{ + volatile struct pci_header *header; + + header = (volatile struct pci_header *)(bus->vmd->cfg_vaddr + + CONFIG_OFFSET_ADDR(bus->bus_number, devfn, 0, 0)); + if (!vmd_is_valid_cfg_addr(bus, (uint64_t)header)) { + return false; + } + + if (header->common.vendor_id == PCI_INVALID_VENDORID || header->common.vendor_id == 0x0) { + return false; + } + + return true; +} + static struct vmd_pci_device * vmd_alloc_dev(struct vmd_pci_bus *bus, uint32_t devfn) { @@ -475,15 +493,19 @@ vmd_alloc_dev(struct vmd_pci_bus *bus, uint32_t devfn) uint8_t header_type; uint32_t rev_class; - header = (struct pci_header * volatile)(bus->vmd->cfg_vaddr + - CONFIG_OFFSET_ADDR(bus->bus_number, devfn, 0, 0)); - if (!vmd_is_valid_cfg_addr(bus, (uint64_t)header)) { + /* Make sure we're not creating two devices on the same dev/fn */ + TAILQ_FOREACH(dev, &bus->dev_list, tailq) { + if (dev->devfn == devfn) { + return NULL; + } + } + + if (!vmd_bus_device_present(bus, devfn)) { return NULL; } - if (header->common.vendor_id == PCI_INVALID_VENDORID || header->common.vendor_id == 0x0) { - return NULL; - } + header = (struct pci_header * volatile)(bus->vmd->cfg_vaddr + + CONFIG_OFFSET_ADDR(bus->bus_number, devfn, 0, 0)); SPDK_DEBUGLOG(SPDK_LOG_VMD, "PCI device found: %04x:%04x ***\n", header->common.vendor_id, header->common.device_id); @@ -724,7 +746,13 @@ vmd_dev_cfg_write(struct spdk_pci_device *_dev, void *value, static void vmd_dev_detach(struct spdk_pci_device *dev) { - return; + struct vmd_pci_device *vmd_device = (struct vmd_pci_device *)dev; + struct vmd_pci_bus *bus = vmd_device->bus; + + spdk_pci_unhook_device(dev); + + TAILQ_REMOVE(&bus->dev_list, vmd_device, tailq); + free(dev); } static void @@ -744,8 +772,10 @@ vmd_dev_init(struct vmd_pci_device *dev) dev->pci.cfg_read = vmd_dev_cfg_read; dev->pci.cfg_write = vmd_dev_cfg_write; dev->pci.detach = vmd_dev_detach; - dev->cached_slot_control = dev->pcie_cap->slot_control; dev->hotplug_capable = false; + if (dev->pcie_cap != NULL) { + dev->cached_slot_control = dev->pcie_cap->slot_control; + } if (vmd_is_supported_device(dev)) { spdk_pci_addr_fmt(bdf, sizeof(bdf), &dev->pci.addr); @@ -817,7 +847,14 @@ vmd_scan_single_bus(struct vmd_pci_bus *bus, struct vmd_pci_device *parent_bridg new_dev->pcie_cap->express_cap_register.bit_field.slot_implemented) { new_bus->hotplug_buses = vmd_get_hotplug_bus_numbers(new_dev); new_bus->subordinate_bus += new_bus->hotplug_buses; + + /* Attach hot plug instance if HP is supported */ + /* Hot inserted SSDs can be assigned port bus of sub-ordinate + 1 */ + SPDK_DEBUGLOG(SPDK_LOG_VMD, "hotplug_capable/slot_implemented = " + "%x:%x\n", slot_cap.bit_field.hotplug_capable, + new_dev->pcie_cap->express_cap_register.bit_field.slot_implemented); } + new_dev->parent_bridge = parent_bridge; new_dev->header->one.primary = new_bus->primary_bus; new_dev->header->one.secondary = new_bus->secondary_bus; @@ -826,15 +863,9 @@ vmd_scan_single_bus(struct vmd_pci_bus *bus, struct vmd_pci_device *parent_bridg vmd_bus_update_bridge_info(new_dev); TAILQ_INSERT_TAIL(&bus->vmd->bus_list, new_bus, tailq); - /* Attach hot plug instance if HP is supported */ - /* Hot inserted SSDs can be assigned port bus of sub-ordinate + 1 */ - SPDK_DEBUGLOG(SPDK_LOG_VMD, "bit_field.hotplug_capable:slot_implemented = %x:%x\n", - slot_cap.bit_field.hotplug_capable, - new_dev->pcie_cap->express_cap_register.bit_field.slot_implemented); - vmd_dev_init(new_dev); - if (slot_cap.bit_field.hotplug_capable && + if (slot_cap.bit_field.hotplug_capable && new_dev->pcie_cap != NULL && new_dev->pcie_cap->express_cap_register.bit_field.slot_implemented) { vmd_init_hotplug(new_dev, new_bus); } @@ -1105,6 +1136,95 @@ spdk_vmd_pci_device_list(struct spdk_pci_addr vmd_addr, struct spdk_pci_device * return cnt; } +static void +vmd_clear_hotplug_status(struct vmd_pci_bus *bus) +{ + struct vmd_pci_device *device = bus->self; + uint16_t status __attribute__((unused)); + + status = device->pcie_cap->slot_status.as_uint16_t; + device->pcie_cap->slot_status.as_uint16_t = status; + status = device->pcie_cap->slot_status.as_uint16_t; + + status = device->pcie_cap->link_status.as_uint16_t; + device->pcie_cap->link_status.as_uint16_t = status; + status = device->pcie_cap->link_status.as_uint16_t; +} + +static void +vmd_bus_handle_hotplug(struct vmd_pci_bus *bus) +{ + uint8_t num_devices, sleep_count; + + for (sleep_count = 0; sleep_count < 20; ++sleep_count) { + /* Scan until a new device is found */ + num_devices = vmd_scan_single_bus(bus, bus->self); + if (num_devices > 0) { + break; + } + + spdk_delay_us(200000); + } + + if (num_devices == 0) { + SPDK_ERRLOG("Timed out while scanning for hotplugged devices\n"); + } +} + +static void +vmd_bus_handle_hotremove(struct vmd_pci_bus *bus) +{ + struct vmd_pci_device *device, *tmpdev; + + TAILQ_FOREACH_SAFE(device, &bus->dev_list, tailq, tmpdev) { + if (!vmd_bus_device_present(bus, device->devfn)) { + device->pci.internal.pending_removal = true; + + /* If the device isn't attached, remove it immediately */ + if (!device->pci.internal.attached) { + vmd_dev_detach(&device->pci); + } + } + } +} + +int +spdk_vmd_hotplug_monitor(void) +{ + struct vmd_pci_bus *bus; + struct vmd_pci_device *device; + int num_hotplugs = 0; + uint32_t i; + + for (i = 0; i < g_vmd_container.count; ++i) { + TAILQ_FOREACH(bus, &g_vmd_container.vmd[i].bus_list, tailq) { + device = bus->self; + if (device == NULL || !device->hotplug_capable) { + continue; + } + + if (device->pcie_cap->slot_status.bit_field.datalink_state_changed != 1) { + continue; + } + + if (device->pcie_cap->link_status.bit_field.datalink_layer_active == 1) { + SPDK_DEBUGLOG(SPDK_LOG_VMD, "Device hotplug detected on bus " + "%"PRIu32"\n", bus->bus_number); + vmd_bus_handle_hotplug(bus); + } else { + SPDK_DEBUGLOG(SPDK_LOG_VMD, "Device hotremove detected on bus " + "%"PRIu32"\n", bus->bus_number); + vmd_bus_handle_hotremove(bus); + } + + vmd_clear_hotplug_status(bus); + num_hotplugs++; + } + } + + return num_hotplugs; +} + int spdk_vmd_init(void) { diff --git a/lib/vmd/vmd_spec.h b/lib/vmd/vmd_spec.h index c54b0567a..07a4a113d 100644 --- a/lib/vmd/vmd_spec.h +++ b/lib/vmd/vmd_spec.h @@ -383,6 +383,52 @@ union express_root_control_register { uint16_t as_uint16_t; }; +union express_link_capability_register { + struct { + uint32_t maximum_link_speed : 4; + uint32_t maximum_link_width : 6; + uint32_t active_state_pms_support : 2; + uint32_t l0_exit_latency : 3; + uint32_t l1_exit_latency : 3; + uint32_t clock_power_management : 1; + uint32_t surprise_down_error_reporting_capable : 1; + uint32_t datalink_layer_active_reporting_capable : 1; + uint32_t link_bandwidth_notification_capability : 1; + uint32_t aspm_optionality_compliance : 1; + uint32_t rsvd : 1; + uint32_t port_number : 8; + } bit_field; + uint32_t as_uint32_t; +}; + +union express_link_control_register { + struct { + uint16_t active_state_pm_control : 2; + uint16_t rsvd1 : 1; + uint16_t read_completion_boundary : 1; + uint16_t link_disable : 1; + uint16_t retrain_link : 1; + uint16_t common_clock_config : 1; + uint16_t extended_synch : 1; + uint16_t enable_clock_power_management : 1; + uint16_t rsvd2 : 7; + } bit_field; + uint16_t as_uint16_t; +}; + +union express_link_status_register { + struct { + uint16_t link_speed : 4; + uint16_t link_width : 6; + uint16_t undefined : 1; + uint16_t link_training : 1; + uint16_t slot_clock_config : 1; + uint16_t datalink_layer_active : 1; + uint16_t asvd : 2; + } bit_field; + uint16_t as_uint16_t; +}; + struct pci_express_cap { uint8_t capid; uint8_t next_cap; @@ -390,9 +436,9 @@ struct pci_express_cap { uint32_t device_cap; uint16_t device_control; uint16_t device_status; - uint32_t link_cap; - uint16_t link_control; - uint16_t link_status; + union express_link_capability_register link_cap; + union express_link_control_register link_control; + union express_link_status_register link_status; union express_slot_capabilities_register slot_cap; union express_slot_control_register slot_control; union express_slot_status_register slot_status; diff --git a/mk/spdk.lib_deps.mk b/mk/spdk.lib_deps.mk index bc81ae017..7acd7cd55 100644 --- a/mk/spdk.lib_deps.mk +++ b/mk/spdk.lib_deps.mk @@ -145,7 +145,7 @@ DEPDIRS-app_rpc := log util thread event $(JSON_LIBS) # the SPDK event subsystem code. DEPDIRS-event_copy := copy event DEPDIRS-event_net := sock net event -DEPDIRS-event_vmd := vmd conf $(JSON_LIBS) event +DEPDIRS-event_vmd := vmd conf $(JSON_LIBS) event log thread DEPDIRS-event_bdev := bdev event event_copy event_vmd diff --git a/module/event/subsystems/vmd/vmd.c b/module/event/subsystems/vmd/vmd.c index 49b7506e4..cf49960bf 100644 --- a/module/event/subsystems/vmd/vmd.c +++ b/module/event/subsystems/vmd/vmd.c @@ -33,16 +33,46 @@ #include "spdk/stdinc.h" #include "spdk/conf.h" +#include "spdk/thread.h" +#include "spdk/likely.h" #include "spdk/vmd.h" #include "spdk_internal/event.h" #include "event_vmd.h" +static struct spdk_poller *g_hotplug_poller; + +static int +vmd_hotplug_monitor(void *ctx) +{ + return spdk_vmd_hotplug_monitor(); +} + int vmd_subsystem_init(void) { - return spdk_vmd_init(); + int rc; + + /* If the poller is registered, the initialization already took place */ + if (g_hotplug_poller != NULL) { + SPDK_ERRLOG("The initialization has already been performed\n"); + return -EBUSY; + } + + rc = spdk_vmd_init(); + if (spdk_likely(rc != 0)) { + SPDK_ERRLOG("Failed to initialize the VMD library\n"); + return rc; + } + + g_hotplug_poller = spdk_poller_register(vmd_hotplug_monitor, NULL, 1000000ULL); + if (g_hotplug_poller == NULL) { + SPDK_ERRLOG("Failed to register hotplug monitor poller\n"); + return -ENOMEM; + } + + return 0; } static void @@ -64,6 +94,8 @@ spdk_vmd_subsystem_init(void) static void spdk_vmd_subsystem_fini(void) { + spdk_poller_unregister(&g_hotplug_poller); + spdk_subsystem_fini_next(); }