Index: sys/dev/acpica/acpi_pcib_acpi.c =================================================================== --- sys/dev/acpica/acpi_pcib_acpi.c +++ sys/dev/acpica/acpi_pcib_acpi.c @@ -309,8 +309,8 @@ /* Support Field: Extended PCI Config Space, MSI */ cap_set[1] = 0x11; - /* Control Field */ - cap_set[2] = 0; + /* Control Field: PCI Express Native Hot Plug */ + cap_set[2] = 0x1; status = acpi_EvaluateOSC(sc->ap_handle, pci_host_bridge_uuid, 1, nitems(cap_set), cap_set, cap_set, false); Index: sys/dev/pci/pci_pci.c =================================================================== --- sys/dev/pci/pci_pci.c +++ sys/dev/pci/pci_pci.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -67,6 +68,9 @@ static int pcib_ari_enabled(device_t pcib); static void pcib_ari_decode_rid(device_t pcib, uint16_t rid, int *bus, int *slot, int *func); +static void pcib_pcie_ab_timeout(void *arg); +static void pcib_pcie_cc_timeout(void *arg); +static void pcib_pcie_dll_timeout(void *arg); static device_method_t pcib_methods[] = { /* Device interface */ @@ -78,6 +82,7 @@ DEVMETHOD(device_resume, pcib_resume), /* Bus interface */ + DEVMETHOD(bus_child_present, pcib_child_present), DEVMETHOD(bus_read_ivar, pcib_read_ivar), DEVMETHOD(bus_write_ivar, pcib_write_ivar), DEVMETHOD(bus_alloc_resource, pcib_alloc_resource), @@ -840,6 +845,460 @@ #endif /* + * PCI-express HotPlug support. + */ +static void +pcib_probe_hotplug(struct pcib_softc *sc) +{ + device_t dev; + + dev = sc->dev; + if (pci_find_cap(dev, PCIY_EXPRESS, NULL) != 0) + return; + + if (!(pcie_read_config(dev, PCIER_FLAGS, 2) & PCIEM_FLAGS_SLOT)) + return; + + sc->pcie_link_cap = pcie_read_config(dev, PCIER_LINK_CAP, 4); + sc->pcie_slot_cap = pcie_read_config(dev, PCIER_SLOT_CAP, 4); + + if (sc->pcie_slot_cap & PCIEM_SLOT_CAP_HPC) + sc->flags |= PCIB_HOTPLUG; +} + +/* + * Send a HotPlug command to the slot control register. If this slot + * uses command completion interrupts, these updates will be buffered + * while a previous command is completing. + */ +static void +pcib_pcie_hotplug_command(struct pcib_softc *sc, uint16_t val, uint16_t mask) +{ + device_t dev; + uint16_t ctl, new; + + dev = sc->dev; + if (sc->pcie_slot_cap & PCIEM_SLOT_CAP_NCCS) { + ctl = pcie_read_config(dev, PCIER_SLOT_CTL, 2); + new = (ctl & ~mask) | val; + if (new != ctl) + pcie_write_config(dev, PCIER_SLOT_CTL, new, 2); + } + + if (sc->flags & PCIB_HOTPLUG_CMD_PENDING) { + sc->pcie_pending_link_ctl_val &= ~mask; + sc->pcie_pending_link_ctl_val |= val; + sc->pcie_pending_link_ctl_mask |= mask; + } else { + ctl = pcie_read_config(dev, PCIER_SLOT_CTL, 2); + new = (ctl & ~mask) | val; + if (new != ctl) { + pcie_write_config(dev, PCIER_SLOT_CTL, ctl, 2); + sc->flags |= PCIB_HOTPLUG_CMD_PENDING; + if (!cold) + callout_reset(&sc->pcie_cc_timer, hz, + pcib_pcie_cc_timeout, sc); + } + } +} + +static void +pcib_pcie_hotplug_command_completed(struct pcib_softc *sc) +{ + device_t dev; + uint16_t ctl, new; + + dev = sc->dev; + + /* XXX */ + device_printf(dev, "Command Completed\n"); + if (!(sc->flags & PCIB_HOTPLUG_CMD_PENDING)) + return; + if (sc->pcie_pending_link_ctl_mask != 0) { + ctl = pcie_read_config(dev, PCIER_SLOT_CTL, 2); + new = ctl & ~sc->pcie_pending_link_ctl_mask; + new |= sc->pcie_pending_link_ctl_val; + if (new != ctl) { + pcie_write_config(dev, PCIER_SLOT_CTL, ctl, 2); + if (!cold) + callout_reset(&sc->pcie_cc_timer, hz, + pcib_pcie_cc_timeout, sc); + } else + sc->flags &= ~PCIB_HOTPLUG_CMD_PENDING; + sc->pcie_pending_link_ctl_mask = 0; + sc->pcie_pending_link_ctl_val = 0; + } else { + callout_stop(&sc->pcie_cc_timer); + sc->flags &= ~PCIB_HOTPLUG_CMD_PENDING; + } +} + +/* + * Returns true if a card is fully inserted from the user's + * perspective. It may not yet be ready for access, but the driver + * can now start enabling access if necessary. + */ +static bool +pcib_hotplug_inserted(struct pcib_softc *sc) +{ + + /* Pretend the card isn't present if a detach is forced. */ + if (sc->flags & PCIB_DETACHING) + return (false); + + /* Card must be present in the slot. */ + if ((sc->pcie_slot_sta & PCIEM_SLOT_STA_PDS) == 0) + return (false); + + /* A power fault implicitly turns off power to the slot. */ + if (sc->pcie_slot_sta & PCIEM_SLOT_STA_PFD) + return (false); + + /* If the MRL is disengaged, the slot is powered off. */ + if (sc->pcie_slot_cap & PCIEM_SLOT_CAP_MRLSP && + (sc->pcie_slot_sta & PCIEM_SLOT_STA_MRLSS) != 0) + return (false); + + return (true); +} + +/* + * Returns -1 if the card is fully inserted, powered, and ready for + * access. Otherwise, returns 0. + */ +static int +pcib_hotplug_present(struct pcib_softc *sc) +{ + device_t dev; + + dev = sc->dev; + + /* Card must be inserted. */ + if (!pcib_hotplug_inserted(sc)) + return (0); + + /* + * Require the Electromechanical Interlock to be engaged if + * present. + */ + if (sc->pcie_slot_cap & PCIEM_SLOT_CAP_EIP && + (sc->pcie_slot_sta & PCIEM_SLOT_STA_EIS) == 0) + return (0); + + /* Require the Data Link Layer to be active. */ + if (sc->pcie_link_cap & PCIEM_LINK_CAP_DL_ACTIVE) { + if (!(sc->pcie_link_sta & PCIEM_LINK_STA_DL_ACTIVE)) + return (0); + } + + return (-1); +} + +static void +pcib_pcie_hotplug_update(struct pcib_softc *sc, uint16_t val, uint16_t mask, + bool schedule_task) +{ + bool card_inserted; + + /* Clear DETACHING if Present Detect has cleared. */ + if ((sc->pcie_slot_sta & (PCIEM_SLOT_STA_PDC | PCIEM_SLOT_STA_PDS)) == + PCIEM_SLOT_STA_PDC) + sc->flags &= ~PCIB_DETACHING; + + card_inserted = pcib_hotplug_inserted(sc); + + /* Turn the power indicator on if a card is inserted. */ + if (sc->pcie_slot_cap & PCIEM_SLOT_CAP_PIP) { + mask |= PCIEM_SLOT_CTL_PIC; + if (card_inserted) + val |= PCIEM_SLOT_CTL_PI_ON; + else if (sc->flags & PCIB_DETACH_PENDING) + val |= PCIEM_SLOT_CTL_PI_BLINK; + else + val |= PCIEM_SLOT_CTL_PI_OFF; + } + + /* Turn the power on via the Power Controller if a card is inserted. */ + if (sc->pcie_slot_cap & PCIEM_SLOT_CAP_PCP) { + mask |= PCIEM_SLOT_CTL_PCC; + if (card_inserted) + val |= PCIEM_SLOT_CTL_PC_ON; + else + val |= PCIEM_SLOT_CTL_PC_OFF; + } + + /* + * If a card is inserted, enable the Electromechanical + * Interlock. If a card is not inserted (or we are in the + * process of detaching), disable the Electromechanical + * Interlock. + */ + if (sc->pcie_slot_cap & PCIEM_SLOT_CAP_EIP) { + if (card_inserted != + !(sc->pcie_slot_sta & PCIEM_SLOT_STA_EIS)) { + mask |= PCIEM_SLOT_CTL_EIC; + val |= PCIEM_SLOT_CTL_EIC; + } + } + + /* + * Start a timer to see if the Data Link Layer times out. + * Note that we only start the timer if Presence Detect + * changed on this interrupt. + */ + if (sc->pcie_link_cap & PCIEM_LINK_CAP_DL_ACTIVE && card_inserted && + !(sc->pcie_link_sta & PCIEM_LINK_STA_DL_ACTIVE) && + sc->pcie_slot_sta & PCIEM_SLOT_STA_PDC) { + if (cold) + device_printf(sc->dev, "Data Link Layer inactive\n"); + else + callout_reset(&sc->pcie_dll_timer, hz, + pcib_pcie_dll_timeout, sc); + } + + pcib_pcie_hotplug_command(sc, val, mask); + + /* + * During attach the child "pci" device is added sychronously; + * otherwise, the task is scheduled to manage the child + * device. + */ + if (schedule_task && + (pcib_hotplug_present(sc) != 0) != (sc->child != NULL)) + taskqueue_enqueue(taskqueue_thread, &sc->pcie_hp_task); +} + +static void +pcib_pcie_intr(void *arg) +{ + struct pcib_softc *sc; + device_t dev; + + sc = arg; + dev = sc->dev; + sc->pcie_slot_sta = pcie_read_config(dev, PCIER_SLOT_STA, 2); + + /* Clear the events just reported. */ + pcie_write_config(dev, PCIER_SLOT_STA, sc->pcie_slot_sta, 2); + + if (sc->pcie_slot_sta & PCIEM_SLOT_STA_ABP) { + if (sc->flags & PCIB_DETACH_PENDING) { + device_printf(dev, + "Attention Button Pressed: Detach Cancelled\n"); + sc->flags &= ~PCIB_DETACH_PENDING; + callout_stop(&sc->pcie_ab_timer); + } else { + device_printf(dev, + "Attention Button Pressed: Detaching in 5 seconds\n"); + sc->flags |= PCIB_DETACH_PENDING; + callout_reset(&sc->pcie_ab_timer, 5 * hz, + pcib_pcie_ab_timeout, sc); + } + } + if (sc->pcie_slot_sta & PCIEM_SLOT_STA_PFD) + device_printf(dev, "Power Fault Detected\n"); + if (sc->pcie_slot_sta & PCIEM_SLOT_STA_MRLSC) + device_printf(dev, "MRL Sensor Changed to %s\n", + sc->pcie_slot_sta & PCIEM_SLOT_STA_MRLSS ? "open" : + "closed"); + /* XXX */ + if (sc->pcie_slot_sta & PCIEM_SLOT_STA_PDC) + device_printf(dev, "Present Detect Changed to %s\n", + sc->pcie_slot_sta & PCIEM_SLOT_STA_PDS ? "card present" : + "empty"); + if (sc->pcie_slot_sta & PCIEM_SLOT_STA_CC) + pcib_pcie_hotplug_command_completed(sc); + /* XXX */ + if (sc->pcie_slot_sta & PCIEM_SLOT_STA_DLLSC) { + sc->pcie_link_sta = pcie_read_config(dev, PCIER_LINK_STA, 2); + device_printf(dev, "Data Link Layer State Changed to %s\n", + sc->pcie_link_sta & PCIEM_LINK_STA_DL_ACTIVE ? "active" : + "inactive"); + } + + pcib_pcie_hotplug_update(sc, 0, 0, true); +} + +static void +pcib_pcie_hotplug_task(void *context, int pending) +{ + struct pcib_softc *sc; + device_t dev; + + sc = context; + mtx_lock(&Giant); + dev = sc->dev; + if (pcib_hotplug_present(sc) != 0) { + if (sc->child == NULL) { + sc->child = device_add_child(dev, "pci", -1); + bus_generic_attach(dev); + } + } else { + if (sc->child != NULL) { + if (device_delete_child(dev, sc->child) == 0) + sc->child = NULL; + } + } + mtx_unlock(&Giant); +} + +static void +pcib_pcie_ab_timeout(void *arg) +{ + struct pcib_softc *sc; + device_t dev; + + sc = arg; + dev = sc->dev; + mtx_assert(&Giant, MA_OWNED); + if (sc->flags & PCIB_DETACH_PENDING) { + sc->flags |= PCIB_DETACHING; + sc->flags &= ~PCIB_DETACH_PENDING; + pcib_pcie_hotplug_update(sc, 0, 0, true); + } +} + +static void +pcib_pcie_cc_timeout(void *arg) +{ + struct pcib_softc *sc; + device_t dev; + + sc = arg; + dev = sc->dev; + mtx_assert(&Giant, MA_OWNED); + if (sc->flags & PCIB_HOTPLUG_CMD_PENDING) { + device_printf(dev, + "Hotplug Command Timed Out - forcing detach\n"); + sc->flags &= ~(PCIB_HOTPLUG_CMD_PENDING | PCIB_DETACH_PENDING); + sc->flags |= PCIB_DETACHING; + pcib_pcie_hotplug_update(sc, 0, 0, true); + } +} + +static void +pcib_pcie_dll_timeout(void *arg) +{ + struct pcib_softc *sc; + device_t dev; + uint16_t sta; + + sc = arg; + dev = sc->dev; + mtx_assert(&Giant, MA_OWNED); + sta = pcie_read_config(dev, PCIER_LINK_STA, 2); + if (!(sta & PCIEM_LINK_STA_DL_ACTIVE)) { + device_printf(dev, + "Timed out waiting for Data Link Layer Active\n"); + pcib_pcie_hotplug_update(sc, 0, 0, true); + } + + if (sta != sc->pcie_link_sta) { + device_printf(dev, + "Missed HotPlug interrupt waiting for DLL Active\n"); + pcib_pcie_intr(sc); + } +} + +static int +pcib_alloc_pcie_irq(struct pcib_softc *sc) +{ + device_t dev; + int count, error, rid; + + rid = -1; + dev = sc->dev; + + /* + * For simplicity, only use MSI-X if there is a single message. + * To support a device with multiple messages we would have to + * use remap intr if the MSI number is not 0. + */ + count = pci_msix_count(dev); + if (count == 1) { + count = 1; + error = pci_alloc_msix(dev, &count); + if (error == 0) + rid = 1; + } + + if (rid < 0 && pci_msi_count(dev) > 0) { + count = 1; + error = pci_alloc_msi(dev, &count); + if (error == 0) + rid = 1; + } + + if (rid < 0) + rid = 0; + + sc->pcie_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_ACTIVE); + if (sc->pcie_irq == NULL) { + device_printf(dev, + "Failed to allocate interrupt for PCI-e events\n"); + if (rid > 0) + pci_release_msi(dev); + return (ENXIO); + } + + error = bus_setup_intr(dev, sc->pcie_irq, INTR_TYPE_MISC, + NULL, pcib_pcie_intr, sc, &sc->pcie_ihand); + if (error) { + device_printf(dev, "Failed to setup PCI-e interrupt handler\n"); + bus_release_resource(dev, SYS_RES_IRQ, rid, sc->pcie_irq); + if (rid > 0) + pci_release_msi(dev); + return (error); + } + return (0); +} + +static void +pcib_setup_hotplug(struct pcib_softc *sc) +{ + device_t dev; + uint16_t mask, val; + + dev = sc->dev; + callout_init(&sc->pcie_ab_timer, 0); + callout_init(&sc->pcie_cc_timer, 0); + callout_init(&sc->pcie_dll_timer, 0); + TASK_INIT(&sc->pcie_hp_task, 0, pcib_pcie_hotplug_task, sc); + + /* Allocate IRQ. */ + if (pcib_alloc_pcie_irq(sc) != 0) + return; + + sc->pcie_link_sta = pcie_read_config(dev, PCIER_LINK_STA, 2); + sc->pcie_slot_sta = pcie_read_config(dev, PCIER_SLOT_STA, 2); + + /* Enable HotPlug events. */ + mask = PCIEM_SLOT_CTL_DLLSCE | PCIEM_SLOT_CTL_HPIE | + PCIEM_SLOT_CTL_CCIE | PCIEM_SLOT_CTL_PDCE | PCIEM_SLOT_CTL_MRLSCE | + PCIEM_SLOT_CTL_PFDE | PCIEM_SLOT_CTL_ABPE; + val = PCIEM_SLOT_CTL_PDCE | PCIEM_SLOT_CTL_HPIE; + if (sc->pcie_slot_cap & PCIEM_SLOT_CAP_APB) + val |= PCIEM_SLOT_CTL_ABPE; + if (sc->pcie_slot_cap & PCIEM_SLOT_CAP_PCP) + val |= PCIEM_SLOT_CTL_PFDE; + if (sc->pcie_slot_cap & PCIEM_SLOT_CAP_MRLSP) + val |= PCIEM_SLOT_CTL_MRLSCE; + if (!(sc->pcie_slot_cap & PCIEM_SLOT_CAP_NCCS)) + val |= PCIEM_SLOT_CTL_CCIE; + if (sc->pcie_link_cap & PCIEM_LINK_CAP_DL_ACTIVE) + val |= PCIEM_SLOT_CTL_DLLSCE; + + /* Turn the attention indicator off. */ + if (sc->pcie_slot_cap & PCIEM_SLOT_CAP_AIP) { + mask |= PCIEM_SLOT_CTL_AIC; + val |= PCIEM_SLOT_CTL_AI_OFF; + } + + pcib_pcie_hotplug_update(sc, val, mask, false); +} + +/* * Get current bridge configuration. */ static void @@ -1017,12 +1476,15 @@ pci_read_config(dev, PCIR_PROGIF, 1) == PCIP_BRIDGE_PCI_SUBTRACTIVE) sc->flags |= PCIB_SUBTRACTIVE; + pcib_probe_hotplug(sc); #ifdef NEW_PCIB #ifdef PCI_RES_BUS pcib_setup_secbus(dev, &sc->bus, 1); #endif pcib_probe_windows(sc); #endif + if (sc->flags & PCIB_HOTPLUG) + pcib_setup_hotplug(sc); if (bootverbose) { device_printf(dev, " domain %d\n", sc->domain); device_printf(dev, " secondary bus %d\n", sc->bus.sec); @@ -1074,6 +1536,15 @@ pci_enable_busmaster(dev); } +static int +pcib_present(struct pcib_softc *sc) +{ + + if (sc->flags & PCIB_HOTPLUG) + return (pcib_hotplug_present(sc) != 0); + return (1); +} + int pcib_attach_child(device_t dev) { @@ -1085,6 +1556,11 @@ return(0); } + if (!pcib_present(sc)) { + /* An empty HotPlug slot, so don't add a PCI bus yet. */ + return (0); + } + sc->child = device_add_child(dev, "pci", -1); return (bus_generic_attach(dev)); } @@ -1129,6 +1605,18 @@ } int +pcib_child_present(device_t dev, device_t child) +{ + struct pcib_softc *sc = device_get_softc(dev); + int retval; + + retval = bus_child_present(dev); + if (retval != 0 && sc->flags & PCIB_HOTPLUG) + retval = pcib_hotplug_present(sc); + return (retval); +} + +int pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct pcib_softc *sc = device_get_softc(dev); @@ -1909,7 +2397,19 @@ static uint32_t pcib_read_config(device_t dev, u_int b, u_int s, u_int f, u_int reg, int width) { + struct pcib_softc *sc; + sc = device_get_softc(dev); + if (!pcib_present(sc)) { + switch (width) { + case 2: + return (0xffff); + case 1: + return (0xff); + default: + return (0xffffffff); + } + } pcib_xlate_ari(dev, b, &s, &f); return(PCIB_READ_CONFIG(device_get_parent(device_get_parent(dev)), b, s, f, reg, width)); @@ -1918,7 +2418,11 @@ static void pcib_write_config(device_t dev, u_int b, u_int s, u_int f, u_int reg, uint32_t val, int width) { + struct pcib_softc *sc; + sc = device_get_softc(dev); + if (!pcib_present(sc)) + return; pcib_xlate_ari(dev, b, &s, &f); PCIB_WRITE_CONFIG(device_get_parent(device_get_parent(dev)), b, s, f, reg, val, width); Index: sys/dev/pci/pcib_private.h =================================================================== --- sys/dev/pci/pcib_private.h +++ sys/dev/pci/pcib_private.h @@ -33,6 +33,9 @@ #ifndef __PCIB_PRIVATE_H__ #define __PCIB_PRIVATE_H__ +#include +#include + #ifdef NEW_PCIB /* * Data structure and routines that Host to PCI bridge drivers can use @@ -107,6 +110,10 @@ #define PCIB_DISABLE_MSI 0x2 #define PCIB_DISABLE_MSIX 0x4 #define PCIB_ENABLE_ARI 0x8 +#define PCIB_HOTPLUG 0x10 +#define PCIB_HOTPLUG_CMD_PENDING 0x20 +#define PCIB_DETACH_PENDING 0x40 +#define PCIB_DETACHING 0x80 u_int domain; /* domain number */ u_int pribus; /* primary bus number */ struct pcib_secbus bus; /* secondary bus numbers */ @@ -123,6 +130,18 @@ uint32_t iolimit; /* topmost address of port window */ #endif uint16_t bridgectl; /* bridge control register */ + uint16_t pcie_link_sta; + uint16_t pcie_slot_sta; + uint32_t pcie_link_cap; + uint32_t pcie_slot_cap; + uint16_t pcie_pending_link_ctl_mask; + uint16_t pcie_pending_link_ctl_val; + struct resource *pcie_irq; + void *pcie_ihand; + struct task pcie_hp_task; + struct callout pcie_ab_timer; + struct callout pcie_cc_timer; + struct callout pcie_dll_timer; }; #define PCIB_SUPPORTED_ARI_VER 1 @@ -151,6 +170,7 @@ #ifdef NEW_PCIB const char *pcib_child_name(device_t child); #endif +int pcib_child_present(device_t dev, device_t child); int pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result); int pcib_write_ivar(device_t dev, device_t child, int which, uintptr_t value); struct resource *pcib_alloc_resource(device_t dev, device_t child, int type, int *rid, Index: sys/dev/pci/pcireg.h =================================================================== --- sys/dev/pci/pcireg.h +++ sys/dev/pci/pcireg.h @@ -850,8 +850,16 @@ #define PCIEM_SLOT_CTL_CCIE 0x0010 #define PCIEM_SLOT_CTL_HPIE 0x0020 #define PCIEM_SLOT_CTL_AIC 0x00c0 +#define PCIEM_SLOT_CTL_AI_ON 0x0040 +#define PCIEM_SLOT_CTL_AI_BLINK 0x0080 +#define PCIEM_SLOT_CTL_AI_OFF 0x00c0 #define PCIEM_SLOT_CTL_PIC 0x0300 +#define PCIEM_SLOT_CTL_PI_ON 0x0100 +#define PCIEM_SLOT_CTL_PI_BLINK 0x0200 +#define PCIEM_SLOT_CTL_PI_OFF 0x0300 #define PCIEM_SLOT_CTL_PCC 0x0400 +#define PCIEM_SLOT_CTL_PC_ON 0x0000 +#define PCIEM_SLOT_CTL_PC_OFF 0x0400 #define PCIEM_SLOT_CTL_EIC 0x0800 #define PCIEM_SLOT_CTL_DLLSCE 0x1000 #define PCIER_SLOT_STA 0x1a