lib/vmd: hotplug / hotremove support

This patch adds support for hotplug / hotremove detection for devices
behind the VMD.  The detection acts similarly to the one implemented for
regular PCIe devices, that is user has to periodically call probe
function.  Additionally, for applications not using SPDK's event
framework, spdk_vmd_hotplug_monitor has to be called periodically as
well.

Change-Id: I9f6839560efcf16c839b01976639d835f119cb47
Signed-off-by: orden smith <orden.e.smith@intel.com>
Signed-off-by: Konrad Sztyber <konrad.sztyber@intel.com>
Reviewed-on: https://review.gerrithub.io/c/spdk/spdk/+/472741
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Changpeng Liu <changpeng.liu@intel.com>
Reviewed-by: Shuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
This commit is contained in:
Konrad Sztyber 2019-10-29 17:51:25 +01:00 committed by Tomasz Zawadzki
parent 2e2f383c3a
commit af4e2a3277
5 changed files with 226 additions and 20 deletions

View File

@ -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); 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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -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); 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 * static struct vmd_pci_device *
vmd_alloc_dev(struct vmd_pci_bus *bus, uint32_t devfn) 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; uint8_t header_type;
uint32_t rev_class; uint32_t rev_class;
header = (struct pci_header * volatile)(bus->vmd->cfg_vaddr + /* Make sure we're not creating two devices on the same dev/fn */
CONFIG_OFFSET_ADDR(bus->bus_number, devfn, 0, 0)); TAILQ_FOREACH(dev, &bus->dev_list, tailq) {
if (!vmd_is_valid_cfg_addr(bus, (uint64_t)header)) { if (dev->devfn == devfn) {
return NULL;
}
}
if (!vmd_bus_device_present(bus, devfn)) {
return NULL; return NULL;
} }
if (header->common.vendor_id == PCI_INVALID_VENDORID || header->common.vendor_id == 0x0) { header = (struct pci_header * volatile)(bus->vmd->cfg_vaddr +
return NULL; CONFIG_OFFSET_ADDR(bus->bus_number, devfn, 0, 0));
}
SPDK_DEBUGLOG(SPDK_LOG_VMD, "PCI device found: %04x:%04x ***\n", SPDK_DEBUGLOG(SPDK_LOG_VMD, "PCI device found: %04x:%04x ***\n",
header->common.vendor_id, header->common.device_id); 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 static void
vmd_dev_detach(struct spdk_pci_device *dev) 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 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_read = vmd_dev_cfg_read;
dev->pci.cfg_write = vmd_dev_cfg_write; dev->pci.cfg_write = vmd_dev_cfg_write;
dev->pci.detach = vmd_dev_detach; dev->pci.detach = vmd_dev_detach;
dev->cached_slot_control = dev->pcie_cap->slot_control;
dev->hotplug_capable = false; dev->hotplug_capable = false;
if (dev->pcie_cap != NULL) {
dev->cached_slot_control = dev->pcie_cap->slot_control;
}
if (vmd_is_supported_device(dev)) { if (vmd_is_supported_device(dev)) {
spdk_pci_addr_fmt(bdf, sizeof(bdf), &dev->pci.addr); 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_dev->pcie_cap->express_cap_register.bit_field.slot_implemented) {
new_bus->hotplug_buses = vmd_get_hotplug_bus_numbers(new_dev); new_bus->hotplug_buses = vmd_get_hotplug_bus_numbers(new_dev);
new_bus->subordinate_bus += new_bus->hotplug_buses; 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->parent_bridge = parent_bridge;
new_dev->header->one.primary = new_bus->primary_bus; new_dev->header->one.primary = new_bus->primary_bus;
new_dev->header->one.secondary = new_bus->secondary_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); vmd_bus_update_bridge_info(new_dev);
TAILQ_INSERT_TAIL(&bus->vmd->bus_list, new_bus, tailq); 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); 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) { new_dev->pcie_cap->express_cap_register.bit_field.slot_implemented) {
vmd_init_hotplug(new_dev, new_bus); 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; 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 int
spdk_vmd_init(void) spdk_vmd_init(void)
{ {

View File

@ -383,6 +383,52 @@ union express_root_control_register {
uint16_t as_uint16_t; 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 { struct pci_express_cap {
uint8_t capid; uint8_t capid;
uint8_t next_cap; uint8_t next_cap;
@ -390,9 +436,9 @@ struct pci_express_cap {
uint32_t device_cap; uint32_t device_cap;
uint16_t device_control; uint16_t device_control;
uint16_t device_status; uint16_t device_status;
uint32_t link_cap; union express_link_capability_register link_cap;
uint16_t link_control; union express_link_control_register link_control;
uint16_t link_status; union express_link_status_register link_status;
union express_slot_capabilities_register slot_cap; union express_slot_capabilities_register slot_cap;
union express_slot_control_register slot_control; union express_slot_control_register slot_control;
union express_slot_status_register slot_status; union express_slot_status_register slot_status;

View File

@ -145,7 +145,7 @@ DEPDIRS-app_rpc := log util thread event $(JSON_LIBS)
# the SPDK event subsystem code. # the SPDK event subsystem code.
DEPDIRS-event_copy := copy event DEPDIRS-event_copy := copy event
DEPDIRS-event_net := sock net 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 DEPDIRS-event_bdev := bdev event event_copy event_vmd

View File

@ -33,16 +33,46 @@
#include "spdk/stdinc.h" #include "spdk/stdinc.h"
#include "spdk/conf.h" #include "spdk/conf.h"
#include "spdk/thread.h"
#include "spdk/likely.h"
#include "spdk/vmd.h" #include "spdk/vmd.h"
#include "spdk_internal/event.h" #include "spdk_internal/event.h"
#include "event_vmd.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 int
vmd_subsystem_init(void) 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 static void
@ -64,6 +94,8 @@ spdk_vmd_subsystem_init(void)
static void static void
spdk_vmd_subsystem_fini(void) spdk_vmd_subsystem_fini(void)
{ {
spdk_poller_unregister(&g_hotplug_poller);
spdk_subsystem_fini_next(); spdk_subsystem_fini_next();
} }