diff options
Diffstat (limited to 'roms/skiboot/core/pcie-slot.c')
-rw-r--r-- | roms/skiboot/core/pcie-slot.c | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/roms/skiboot/core/pcie-slot.c b/roms/skiboot/core/pcie-slot.c new file mode 100644 index 000000000..03326e58f --- /dev/null +++ b/roms/skiboot/core/pcie-slot.c @@ -0,0 +1,566 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * PCIe Slots + * + * Copyright 2013-2019 IBM Corp. + */ + +#include <skiboot.h> +#include <opal-msg.h> +#include <pci-cfg.h> +#include <pci.h> +#include <pci-slot.h> + +/* Debugging options */ +#define PCIE_SLOT_PREFIX "PCIE-SLOT-%016llx " +#define PCIE_SLOT_DBG(s, fmt, a...) \ + prlog(PR_DEBUG, PCIE_SLOT_PREFIX fmt, (s)->id, ##a) + +static int64_t pcie_slot_get_presence_state(struct pci_slot *slot, uint8_t *val) +{ + struct phb *phb = slot->phb; + struct pci_device *pd = slot->pd; + uint32_t ecap; + uint16_t state; + + /* The presence is always on if it's a switch upstream port */ + if (pd->dev_type == PCIE_TYPE_SWITCH_UPPORT) { + *val = OPAL_PCI_SLOT_PRESENT; + return OPAL_SUCCESS; + } + + /* + * The presence is always on if a switch downstream port + * doesn't support slot capability according to PCIE spec. + */ + if (pd->dev_type == PCIE_TYPE_SWITCH_DNPORT && + !(slot->pcie_cap & PCICAP_EXP_CAP_SLOT)) { + *val = OPAL_PCI_SLOT_PRESENT; + return OPAL_SUCCESS; + } + + /* Retrieve presence status */ + ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false); + pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTSTAT, &state); + if (state & PCICAP_EXP_SLOTSTAT_PDETECTST) + *val = OPAL_PCI_SLOT_PRESENT; + else + *val = OPAL_PCI_SLOT_EMPTY; + + return OPAL_SUCCESS; +} + +static int64_t pcie_slot_get_link_state(struct pci_slot *slot, + uint8_t *val) +{ + struct phb *phb = slot->phb; + struct pci_device *pd = slot->pd; + uint32_t ecap; + int16_t state; + + /* + * The link behind switch upstream port is always on + * since it doesn't have a valid link indicator. + */ + if (pd->dev_type == PCIE_TYPE_SWITCH_UPPORT) { + *val = 1; + return OPAL_SUCCESS; + } + + /* Retrieve link width */ + ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false); + pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_LSTAT, &state); + if (state & PCICAP_EXP_LSTAT_DLLL_ACT) + *val = ((state & PCICAP_EXP_LSTAT_WIDTH) >> 4); + else + *val = 0; + + return OPAL_SUCCESS; +} + +static int64_t pcie_slot_get_power_state(struct pci_slot *slot __unused, + uint8_t *val) +{ + /* We should return the cached power state that is same to + * the PCI slot hotplug state (added/removed). Otherwise, + * the OS will see mismatched states, causing the adapter + * behind the slot can't be probed successfully on request + * of hot add. So we could run into the situation where the + * OS sees power-off but it's on in hardware. + */ + *val = slot->power_state; + + return OPAL_SUCCESS; +} + +static int64_t pcie_slot_get_attention_state(struct pci_slot *slot, + uint8_t *val) +{ + struct phb *phb = slot->phb; + struct pci_device *pd = slot->pd; + uint32_t ecap; + uint16_t state; + + /* Attention is off if the capability is missing */ + if (!(slot->slot_cap & PCICAP_EXP_SLOTCAP_ATTNI)) { + *val = 0; + return OPAL_SUCCESS; + } + + /* Retrieve attention state */ + ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false); + pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL, &state); + state = (state & PCICAP_EXP_SLOTCTL_ATTNI) >> 6; + switch (state) { + case PCIE_INDIC_ON: + *val = PCI_SLOT_ATTN_LED_ON; + break; + case PCIE_INDIC_BLINK: + *val = PCI_SLOT_ATTN_LED_BLINK; + break; + case PCIE_INDIC_OFF: + default: + *val = PCI_SLOT_ATTN_LED_OFF; + } + + return OPAL_SUCCESS; +} + +static int64_t pcie_slot_get_latch_state(struct pci_slot *slot, + uint8_t *val) +{ + struct phb *phb = slot->phb; + struct pci_device *pd = slot->pd; + uint32_t ecap; + uint16_t state; + + /* Latch is off if MRL sensor doesn't exist */ + if (!(slot->slot_cap & PCICAP_EXP_SLOTCAP_MRLSENS)) { + *val = 0; + return OPAL_SUCCESS; + } + + /* Retrieve MRL sensor state */ + ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false); + pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTSTAT, &state); + if (state & PCICAP_EXP_SLOTSTAT_MRLSENSST) + *val = 1; + else + *val = 0; + + return OPAL_SUCCESS; +} + +static int64_t pcie_slot_set_attention_state(struct pci_slot *slot, + uint8_t val) +{ + struct phb *phb = slot->phb; + struct pci_device *pd = slot->pd; + uint32_t ecap; + uint16_t state; + + /* Drop the request if functionality doesn't exist */ + if (!(slot->slot_cap & PCICAP_EXP_SLOTCAP_ATTNI)) + return OPAL_SUCCESS; + + /* Update with the requested state */ + ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false); + pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL, &state); + state &= ~PCICAP_EXP_SLOTCTL_ATTNI; + switch (val) { + case PCI_SLOT_ATTN_LED_ON: + state |= (PCIE_INDIC_ON << 6); + break; + case PCI_SLOT_ATTN_LED_BLINK: + state |= (PCIE_INDIC_BLINK << 6); + break; + case PCI_SLOT_ATTN_LED_OFF: + state |= (PCIE_INDIC_OFF << 6); + break; + default: + prlog(PR_ERR, PCIE_SLOT_PREFIX + "Invalid attention state (0x%x)\n", slot->id, val); + return OPAL_PARAMETER; + } + + pci_cfg_write16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL, state); + return OPAL_SUCCESS; +} + +static int64_t pcie_slot_set_power_state_ext(struct pci_slot *slot, uint8_t val, + bool surprise_check) +{ + struct phb *phb = slot->phb; + struct pci_device *pd = slot->pd; + uint32_t ecap; + uint16_t state; + + if (slot->power_state == val) + return OPAL_SUCCESS; + + /* Update the power state and return immediately if the power + * control functionality isn't supported on the PCI slot. + */ + if (!(slot->slot_cap & PCICAP_EXP_SLOTCAP_PWCTRL)) { + slot->power_state = val; + return OPAL_SUCCESS; + } + + /* + * Suprise hotpluggable slots need to be handled with care since + * many systems do not implement the presence detect side-band + * signal. Instead, they rely on in-band presence to report the + * existence of a hotplugged card. + * + * This is problematic because: + * a) When PERST is asserted in-band presence doesn't work, and + * b) Switches assert PERST as a part of the "slot power down" sequence + * + * To work around the problem we leave the slot physically powered on + * and exit early here. This way when a new card is inserted, the switch + * will raise an interrupt due to the PresDet status changing. + */ + if (surprise_check && slot->surprise_pluggable) { + slot->power_state = val; + if (val == PCI_SLOT_POWER_OFF) + return OPAL_SUCCESS; + + /* + * Some systems have the slot power disabled by default + * so we always perform the power-on step. This is not + * *strictly* required, but it's probably a good idea. + */ + } + + pci_slot_set_state(slot, PCI_SLOT_STATE_SPOWER_START); + slot->power_state = val; + ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false); + pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL, &state); + state &= ~(PCICAP_EXP_SLOTCTL_PWRCTLR | PCICAP_EXP_SLOTCTL_PWRI); + switch (val) { + case PCI_SLOT_POWER_OFF: + state |= (PCICAP_EXP_SLOTCTL_PWRCTLR | (PCIE_INDIC_OFF << 8)); + break; + case PCI_SLOT_POWER_ON: + state |= (PCIE_INDIC_ON << 8); + break; + default: + pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); + prlog(PR_ERR, PCIE_SLOT_PREFIX + "Invalid power state (0x%x)\n", slot->id, val); + return OPAL_PARAMETER; + } + + pci_cfg_write16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL, state); + pci_slot_set_state(slot, PCI_SLOT_STATE_SPOWER_DONE); + + return OPAL_ASYNC_COMPLETION; +} + +static int64_t pcie_slot_set_power_state(struct pci_slot *slot, uint8_t val) +{ + return pcie_slot_set_power_state_ext(slot, val, true); +} + +static int64_t pcie_slot_sm_poll_link(struct pci_slot *slot) +{ + struct phb *phb = slot->phb; + struct pci_device *pd = slot->pd; + uint32_t ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false); + uint16_t val; + uint8_t presence = 0; + + switch (slot->state) { + case PCI_SLOT_STATE_LINK_START_POLL: + PCIE_SLOT_DBG(slot, "LINK: Start polling\n"); + + /* Link is down for ever without devices attached */ + if (slot->ops.get_presence_state) + slot->ops.get_presence_state(slot, &presence); + if (!presence) { + PCIE_SLOT_DBG(slot, "LINK: No adapter, end polling\n"); + pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); + return OPAL_SUCCESS; + } + + /* Enable the link without check */ + pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_LCTL, &val); + val &= ~PCICAP_EXP_LCTL_LINK_DIS; + pci_cfg_write16(phb, pd->bdfn, ecap + PCICAP_EXP_LCTL, val); + + /* + * If the link change report isn't supported, we expect + * the link is up and stabilized after one second. + */ + if (!(slot->link_cap & PCICAP_EXP_LCAP_DL_ACT_REP)) { + pci_slot_set_state(slot, + PCI_SLOT_STATE_LINK_DELAY_FINALIZED); + return pci_slot_set_sm_timeout(slot, secs_to_tb(1)); + } + + /* + * Poll the link state if link state change report is + * supported on the link. + */ + pci_slot_set_state(slot, PCI_SLOT_STATE_LINK_POLLING); + slot->retries = 250; + return pci_slot_set_sm_timeout(slot, msecs_to_tb(20)); + case PCI_SLOT_STATE_LINK_DELAY_FINALIZED: + PCIE_SLOT_DBG(slot, "LINK: No link report, end polling\n"); + if (slot->ops.prepare_link_change) + slot->ops.prepare_link_change(slot, true); + pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); + return OPAL_SUCCESS; + case PCI_SLOT_STATE_LINK_POLLING: + pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_LSTAT, &val); + if (val & PCICAP_EXP_LSTAT_DLLL_ACT) { + PCIE_SLOT_DBG(slot, "LINK: Link is up, end polling\n"); + if (slot->ops.prepare_link_change) + slot->ops.prepare_link_change(slot, true); + pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); + return OPAL_SUCCESS; + } + + /* Check link state again until timeout */ + if (slot->retries-- == 0) { + prlog(PR_ERR, PCIE_SLOT_PREFIX + "LINK: Timeout waiting for up (%04x)\n", + slot->id, val); + pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); + return OPAL_SUCCESS; + } + + return pci_slot_set_sm_timeout(slot, msecs_to_tb(20)); + default: + prlog(PR_ERR, PCIE_SLOT_PREFIX + "Link: Unexpected slot state %08x\n", + slot->id, slot->state); + } + + pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); + return OPAL_HARDWARE; +} + +static void pcie_slot_reset(struct pci_slot *slot, bool assert) +{ + struct phb *phb = slot->phb; + struct pci_device *pd = slot->pd; + uint16_t ctl; + + pci_cfg_read16(phb, pd->bdfn, PCI_CFG_BRCTL, &ctl); + if (assert) + ctl |= PCI_CFG_BRCTL_SECONDARY_RESET; + else + ctl &= ~PCI_CFG_BRCTL_SECONDARY_RESET; + pci_cfg_write16(phb, pd->bdfn, PCI_CFG_BRCTL, ctl); +} + +static int64_t pcie_slot_sm_hreset(struct pci_slot *slot) +{ + switch (slot->state) { + case PCI_SLOT_STATE_NORMAL: + PCIE_SLOT_DBG(slot, "HRESET: Starts\n"); + if (slot->ops.prepare_link_change) { + PCIE_SLOT_DBG(slot, "HRESET: Prepare for link down\n"); + slot->ops.prepare_link_change(slot, false); + } + /* fall through */ + case PCI_SLOT_STATE_HRESET_START: + PCIE_SLOT_DBG(slot, "HRESET: Assert\n"); + pcie_slot_reset(slot, true); + pci_slot_set_state(slot, PCI_SLOT_STATE_HRESET_HOLD); + return pci_slot_set_sm_timeout(slot, msecs_to_tb(250)); + case PCI_SLOT_STATE_HRESET_HOLD: + PCIE_SLOT_DBG(slot, "HRESET: Deassert\n"); + pcie_slot_reset(slot, false); + pci_slot_set_state(slot, PCI_SLOT_STATE_LINK_START_POLL); + return pci_slot_set_sm_timeout(slot, msecs_to_tb(1800)); + default: + PCIE_SLOT_DBG(slot, "HRESET: Unexpected slot state %08x\n", + slot->state); + } + + pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); + return OPAL_HARDWARE; +} + +/* + * Usually, individual platforms need to override the power + * management methods for fundamental reset, but the hot + * reset method is commonly shared. + */ +static int64_t pcie_slot_sm_freset(struct pci_slot *slot) +{ + uint8_t power_state = PCI_SLOT_POWER_ON; + + switch (slot->state) { + case PCI_SLOT_STATE_NORMAL: + PCIE_SLOT_DBG(slot, "FRESET: Starts\n"); + if (slot->ops.prepare_link_change) + slot->ops.prepare_link_change(slot, false); + + /* Retrieve power state */ + if (slot->ops.get_power_state) { + PCIE_SLOT_DBG(slot, "FRESET: Retrieve power state\n"); + slot->ops.get_power_state(slot, &power_state); + } + + /* In power on state, power it off */ + if (power_state == PCI_SLOT_POWER_ON) { + PCIE_SLOT_DBG(slot, "FRESET: Power is on, turn off\n"); + pcie_slot_set_power_state_ext(slot, + PCI_SLOT_POWER_OFF, false); + pci_slot_set_state(slot, + PCI_SLOT_STATE_FRESET_POWER_OFF); + return pci_slot_set_sm_timeout(slot, msecs_to_tb(50)); + } + /* No power state change, */ + /* fallthrough */ + case PCI_SLOT_STATE_FRESET_POWER_OFF: + PCIE_SLOT_DBG(slot, "FRESET: Power is off, turn on\n"); + pcie_slot_set_power_state_ext(slot, PCI_SLOT_POWER_ON, false); + + pci_slot_set_state(slot, PCI_SLOT_STATE_LINK_START_POLL); + return pci_slot_set_sm_timeout(slot, msecs_to_tb(50)); + default: + prlog(PR_ERR, PCIE_SLOT_PREFIX + "FRESET: Unexpected slot state %08x\n", + slot->id, slot->state); + } + + pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); + return OPAL_HARDWARE; +} + +struct pci_slot *pcie_slot_create(struct phb *phb, struct pci_device *pd) +{ + struct pci_slot *slot; + uint32_t ecap; + uint16_t slot_ctl; + + /* Allocate PCI slot */ + slot = pci_slot_alloc(phb, pd); + if (!slot) + return NULL; + + /* Cache the link and slot capabilities */ + ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false); + pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_CAPABILITY_REG, + &slot->pcie_cap); + pci_cfg_read32(phb, pd->bdfn, ecap + PCICAP_EXP_LCAP, + &slot->link_cap); + + /* Leave PCI slot capability blank if PCI slot isn't supported */ + if (slot->pcie_cap & PCICAP_EXP_CAP_SLOT) + pci_cfg_read32(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCAP, + &slot->slot_cap); + else + slot->slot_cap = 0; + + if (slot->slot_cap & PCICAP_EXP_SLOTCAP_HPLUG_CAP) + slot->pluggable = 1; + + /* Assume the slot is powered on by default */ + slot->power_state = PCI_SLOT_POWER_ON; + if (slot->slot_cap & PCICAP_EXP_SLOTCAP_PWCTRL) { + slot->power_ctl = 1; + + pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL, + &slot_ctl); + if (slot_ctl & PCICAP_EXP_SLOTCTL_PWRCTLR) + slot->power_state = PCI_SLOT_POWER_OFF; + } + + if (slot->slot_cap & PCICAP_EXP_SLOTCAP_PWRI) + slot->power_led_ctl = PCI_SLOT_PWR_LED_CTL_KERNEL; + if (slot->slot_cap & PCICAP_EXP_SLOTCAP_ATTNI) + slot->attn_led_ctl = PCI_SLOT_ATTN_LED_CTL_KERNEL; + slot->wired_lanes = ((slot->link_cap & PCICAP_EXP_LCAP_MAXWDTH) >> 4); + + /* The surprise hotplug capability is claimed when it's supported + * in the slot's capability bits or link state change reporting is + * supported in PCIe link capability. It means the surprise hotplug + * relies on presence or link state change events. In order for the + * link state change event to be properly raised during surprise hot + * add/remove, the power supply to the slot should be always on. + * + * For PCI slots that don't claim surprise hotplug capability explicitly. + * Its PDC (Presence Detection Change) isn't reliable. To mark that as + * broken on them. + */ + if (slot->pcie_cap & PCICAP_EXP_CAP_SLOT) { + if (slot->slot_cap & PCICAP_EXP_SLOTCAP_HPLUG_SURP) { + slot->surprise_pluggable = 1; + } else if (slot->link_cap & PCICAP_EXP_LCAP_DL_ACT_REP) { + slot->surprise_pluggable = 1; + + pci_slot_add_flags(slot, PCI_SLOT_FLAG_BROKEN_PDC); + } + } + + /* Standard slot operations */ + slot->ops.get_presence_state = pcie_slot_get_presence_state; + slot->ops.get_link_state = pcie_slot_get_link_state; + slot->ops.get_power_state = pcie_slot_get_power_state; + slot->ops.get_attention_state = pcie_slot_get_attention_state; + slot->ops.get_latch_state = pcie_slot_get_latch_state; + slot->ops.set_power_state = pcie_slot_set_power_state; + slot->ops.set_attention_state = pcie_slot_set_attention_state; + + /* + * State machine (SM) based reset stuff. The poll function is always + * unified for all cases. + */ + slot->ops.poll_link = pcie_slot_sm_poll_link; + slot->ops.hreset = pcie_slot_sm_hreset; + slot->ops.freset = pcie_slot_sm_freset; + + slot->wired_lanes = PCI_SLOT_WIRED_LANES_UNKNOWN; + slot->connector_type = PCI_SLOT_CONNECTOR_PCIE_NS; + slot->card_desc = PCI_SLOT_DESC_NON_STANDARD; + slot->card_mech = PCI_SLOT_MECH_NONE; + slot->power_led_ctl = PCI_SLOT_PWR_LED_CTL_NONE; + slot->attn_led_ctl = PCI_SLOT_ATTN_LED_CTL_NONE; + + return slot; +} + +/* FIXME: this is kind of insane */ +struct pci_slot *pcie_slot_create_dynamic(struct phb *phb, + struct pci_device *pd) +{ + uint32_t ecap, val; + struct pci_slot *slot; + + if (!phb || !pd || pd->slot) + return NULL; + + /* Try to create slot whose details aren't provided by platform. */ + if (pd->dev_type != PCIE_TYPE_SWITCH_DNPORT) + return NULL; + + ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false); + pci_cfg_read32(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCAP, &val); + if (!(val & PCICAP_EXP_SLOTCAP_HPLUG_CAP)) + return NULL; + + slot = pcie_slot_create(phb, pd); + + /* On superMicro's "p8dnu" platform, we create dynamic PCI slots + * for all downstream ports of PEX9733 that is connected to PHB + * direct slot. The power supply to the PCI slot is lost after + * PCI adapter is removed from it. The power supply can't be + * turned on when the slot is in empty state. The power supply + * isn't turned on automatically when inserting PCI adapter to + * the slot at later point. We set a flag to the slot here, to + * turn on the power supply in (suprise or managed) hot-add path. + * + * We have same issue with PEX8718 as above on "p8dnu" platform. + */ + if (dt_node_is_compatible(dt_root, "supermicro,p8dnu") && slot && + slot->pd && (slot->pd->vdid == 0x973310b5 || + slot->pd->vdid == 0x871810b5)) + pci_slot_add_flags(slot, PCI_SLOT_FLAG_FORCE_POWERON); + + return slot; +} |