diff options
Diffstat (limited to 'roms/skiboot/core/pci-slot.c')
-rw-r--r-- | roms/skiboot/core/pci-slot.c | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/roms/skiboot/core/pci-slot.c b/roms/skiboot/core/pci-slot.c new file mode 100644 index 000000000..71d3d329c --- /dev/null +++ b/roms/skiboot/core/pci-slot.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * PCI 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 PCI_SLOT_PREFIX "PCI-SLOT-%016llx " +#define PCI_SLOT_DBG(s, fmt, a...) \ + prlog(PR_DEBUG, PCI_SLOT_PREFIX fmt, (s)->id, ##a) + +static void pci_slot_prepare_link_change(struct pci_slot *slot, bool up) +{ + struct phb *phb = slot->phb; + struct pci_device *pd = slot->pd; + uint32_t aercap, mask; + + /* + * Mask the link down and receiver error before the link becomes + * down. Otherwise, unmask the errors when the link is up. + */ + if (pci_has_cap(pd, PCIECAP_ID_AER, true)) { + aercap = pci_cap(pd, PCIECAP_ID_AER, true); + + /* Mask link surprise down event. The event is always + * masked when the associated PCI slot supports PCI + * surprise hotplug. We needn't toggle it when the link + * bounces caused by reset and just keep it always masked. + */ + if (!pd->slot || !pd->slot->surprise_pluggable) { + pci_cfg_read32(phb, pd->bdfn, + aercap + PCIECAP_AER_UE_MASK, &mask); + if (up) + mask &= ~PCIECAP_AER_UE_MASK_SURPRISE_DOWN; + else + mask |= PCIECAP_AER_UE_MASK_SURPRISE_DOWN; + pci_cfg_write32(phb, pd->bdfn, + aercap + PCIECAP_AER_UE_MASK, mask); + } + + /* Receiver error */ + pci_cfg_read32(phb, pd->bdfn, aercap + PCIECAP_AER_CE_MASK, + &mask); + if (up) + mask &= ~PCIECAP_AER_CE_RECVR_ERR; + else + mask |= PCIECAP_AER_CE_RECVR_ERR; + pci_cfg_write32(phb, pd->bdfn, aercap + PCIECAP_AER_CE_MASK, + mask); + } + + /* + * We're coming back from reset. We need restore bus ranges + * and reinitialize the affected bridges and devices. + */ + if (up) { + pci_restore_bridge_buses(phb, pd); + if (phb->ops->device_init) + pci_walk_dev(phb, pd, phb->ops->device_init, NULL); + } +} + +static int64_t pci_slot_run_sm(struct pci_slot *slot) +{ + uint64_t now = mftb(); + int64_t ret; + + /* Return remaining timeout if we're still waiting */ + if (slot->delay_tgt_tb && + tb_compare(now, slot->delay_tgt_tb) == TB_ABEFOREB) + return slot->delay_tgt_tb - now; + + slot->delay_tgt_tb = 0; + switch (slot->state & PCI_SLOT_STATE_MASK) { + case PCI_SLOT_STATE_LINK: + ret = slot->ops.poll_link(slot); + break; + case PCI_SLOT_STATE_HRESET: + ret = slot->ops.hreset(slot); + break; + case PCI_SLOT_STATE_FRESET: + ret = slot->ops.freset(slot); + break; + case PCI_SLOT_STATE_CRESET: + ret = slot->ops.creset(slot); + break; + default: + prlog(PR_ERR, PCI_SLOT_PREFIX + "Invalid state %08x\n", slot->id, slot->state); + pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); + ret = OPAL_HARDWARE; + } + + /* Notify about the pci slot state machine completion */ + if (ret <= 0 && slot->ops.completed_sm_run) + slot->ops.completed_sm_run(slot, ret); + + return ret; +} + +void pci_slot_add_dt_properties(struct pci_slot *slot, + struct dt_node *np) +{ + /* Bail without device node */ + if (!np) + return; + + dt_add_property_cells(np, "ibm,reset-by-firmware", 1); + dt_add_property_cells(np, "ibm,slot-pluggable", slot->pluggable); + dt_add_property_cells(np, "ibm,slot-surprise-pluggable", + slot->surprise_pluggable); + if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BROKEN_PDC)) + dt_add_property_cells(np, "ibm,slot-broken-pdc", 1); + + dt_add_property_cells(np, "ibm,slot-power-ctl", slot->power_ctl); + dt_add_property_cells(np, "ibm,slot-power-led-ctlled", + slot->power_led_ctl); + dt_add_property_cells(np, "ibm,slot-attn-led", slot->attn_led_ctl); + dt_add_property_cells(np, "ibm,slot-connector-type", + slot->connector_type); + dt_add_property_cells(np, "ibm,slot-card-desc", slot->card_desc); + dt_add_property_cells(np, "ibm,slot-card-mech", slot->card_mech); + dt_add_property_cells(np, "ibm,slot-wired-lanes", slot->wired_lanes); + dt_add_property_cells(np, "ibm,power-limit", slot->power_limit); + + if (slot->ops.add_properties) + slot->ops.add_properties(slot, np); +} + +struct pci_slot *pci_slot_alloc(struct phb *phb, + struct pci_device *pd) +{ + struct pci_slot *slot = NULL; + + /* + * The function can be used to allocate either PHB slot or normal + * one. For both cases, the @phb should be always valid. + */ + if (!phb) + return NULL; + + /* + * When @pd is NULL, we're going to create a PHB slot. Otherwise, + * a normal slot will be created. Check if the specified slot + * already exists or not. + */ + slot = pd ? pd->slot : phb->slot; + if (slot) { + prlog(PR_ERR, PCI_SLOT_PREFIX "Already exists\n", slot->id); + return slot; + } + + /* Allocate memory chunk */ + slot = zalloc(sizeof(struct pci_slot)); + if (!slot) { + prlog(PR_ERR, "%s: Out of memory\n", __func__); + return NULL; + } + + /* + * The polling function sholdn't be overridden by individual + * platforms + */ + slot->phb = phb; + slot->pd = pd; + pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); + slot->power_state = PCI_SLOT_POWER_ON; + slot->ops.run_sm = pci_slot_run_sm; + slot->ops.prepare_link_change = pci_slot_prepare_link_change; + slot->peer_slot = NULL; + if (!pd) { + slot->id = PCI_PHB_SLOT_ID(phb); + phb->slot = slot; + } else { + slot->id = PCI_SLOT_ID(phb, pd->bdfn); + pd->slot = slot; + } + + return slot; +} + +struct pci_slot *pci_slot_find(uint64_t id) +{ + struct phb *phb; + struct pci_device *pd; + struct pci_slot *slot; + uint64_t index; + uint16_t bdfn; + + index = PCI_SLOT_PHB_INDEX(id); + phb = pci_get_phb(index); + + /* PHB slot */ + if (!(id & PCI_SLOT_ID_PREFIX)) { + slot = phb ? phb->slot : NULL; + return slot; + } + + /* Normal PCI slot */ + bdfn = PCI_SLOT_BDFN(id); + pd = phb ? pci_find_dev(phb, bdfn) : NULL; + slot = pd ? pd->slot : NULL; + return slot; +} + +void pci_slot_add_loc(struct pci_slot *slot, + struct dt_node *np, const char *label) +{ + char tmp[8], loc_code[LOC_CODE_SIZE]; + struct pci_device *pd = slot->pd; + struct phb *phb = slot->phb; + + if (!np) + return; + + /* didn't get a real slot label? generate one! */ + if (!label) { + snprintf(tmp, sizeof(tmp), "S%04x%02x", phb->opal_id, + pd->secondary_bus); + label = tmp; + } + + /* Make a <PHB_LOC_CODE>-<LABEL> pair if we have a PHB loc code */ + if (phb->base_loc_code) { + snprintf(loc_code, sizeof(loc_code), "%s-%s", + phb->base_loc_code, label); + } else { + strncpy(loc_code, label, sizeof(loc_code) - 1); + loc_code[LOC_CODE_SIZE - 1] = '\0'; + } + + dt_add_property_string(np, "ibm,slot-label", label); + dt_add_property_string(np, "ibm,slot-location-code", loc_code); +} |