diff options
author | Gavin Shan <gwshan@linux.vnet.ibm.com> | 2016-06-10 15:03:42 +1000 |
---|---|---|
committer | Stewart Smith <stewart@linux.vnet.ibm.com> | 2016-06-14 16:00:16 +1000 |
commit | bc66fb67aee6f9e6520120c2476d58f3899c9221 (patch) | |
tree | 378f502d261691a66fc19ff96ebf301c22c9c7fc /core | |
parent | 0bf9c3c44bf1bde1c7bec86d33a6e3ccb6e56c90 (diff) | |
download | skiboot-bc66fb67aee6f9e6520120c2476d58f3899c9221.zip skiboot-bc66fb67aee6f9e6520120c2476d58f3899c9221.tar.gz skiboot-bc66fb67aee6f9e6520120c2476d58f3899c9221.tar.bz2 |
core/pci: Support PCI slot
Every PCIE bridge port or PHB is expected to be bound with PCI slot
, to which various PCI slot's functionalities are attached (e.g. power,
link, reset). This supports PCI slot:
* PCI slot is reprsented by "struct pci_slot".
* "struct pci_slot_ops" represents the functions supported on the
PCI slot. It's initialized by PCI slot core at the beginning and
allowed to be overrided by platform partially or completely.
* On PCI hot plugging event, the PCI devices behind the slot are
enumarated. Device sub-tree is populated and sent to OS by OPAL
message.
* On PCI hot unplugging event, the PCI devices behind the slot are
destroyed. Device sub-tree is removed and the slot is powered off.
Signed-off-by: Gavin Shan <gwshan@linux.vnet.ibm.com>
Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
Diffstat (limited to 'core')
-rw-r--r-- | core/Makefile.inc | 4 | ||||
-rw-r--r-- | core/pci-slot.c | 206 | ||||
-rw-r--r-- | core/pci.c | 77 | ||||
-rw-r--r-- | core/pcie-slot.c | 452 |
4 files changed, 714 insertions, 25 deletions
diff --git a/core/Makefile.inc b/core/Makefile.inc index 5af0d7c..13b287c 100644 --- a/core/Makefile.inc +++ b/core/Makefile.inc @@ -3,8 +3,8 @@ SUBDIRS += core CORE_OBJS = relocate.o console.o stack.o init.o chip.o mem_region.o CORE_OBJS += malloc.o lock.o cpu.o utils.o fdt.o opal.o interrupts.o -CORE_OBJS += timebase.o opal-msg.o pci.o pci-opal.o fast-reboot.o -CORE_OBJS += device.o exceptions.o trace.o affinity.o vpd.o +CORE_OBJS += timebase.o opal-msg.o pci.o pci-slot.o pcie-slot.o pci-opal.o +CORE_OBJS += fast-reboot.o device.o exceptions.o trace.o affinity.o vpd.o CORE_OBJS += hostservices.o platform.o nvram.o nvram-format.o hmi.o CORE_OBJS += console-log.o ipmi.o time-utils.o pel.o pool.o errorlog.o CORE_OBJS += timer.o i2c.o rtc.o flash.o sensor.o ipmi-opal.o diff --git a/core/pci-slot.c b/core/pci-slot.c new file mode 100644 index 0000000..8735598 --- /dev/null +++ b/core/pci-slot.c @@ -0,0 +1,206 @@ +/* Copyright 2013-2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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); + + /* Link down error */ + 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_sm_poll(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_PFRESET: + ret = slot->ops.pfreset(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); + return OPAL_HARDWARE; + } + + 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-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); + + 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.poll = pci_slot_sm_poll; + slot->ops.prepare_link_change = pci_slot_prepare_link_change; + 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; +} @@ -18,6 +18,7 @@ #include <cpu.h> #include <pci.h> #include <pci-cfg.h> +#include <pci-slot.h> #include <timebase.h> #include <device.h> #include <fsp.h> @@ -459,11 +460,33 @@ static void pci_cleanup_bridge(struct phb *phb, struct pci_device *pd) pci_cfg_write16(phb, pd->bdfn, PCI_CFG_CMD, cmd); } +/* Remove all subordinate PCI devices leading from the indicated + * PCI bus. It's used to remove all PCI devices behind one PCI + * slot at unplugging time + */ +void pci_remove_bus(struct phb *phb, struct list_head *list) +{ + struct pci_device *pd, *tmp; + + list_for_each_safe(list, pd, tmp, link) { + pci_remove_bus(phb, &pd->children); + + /* Release device node and PCI slot */ + if (pd->dn) + dt_free(pd->dn); + if (pd->slot) + free(pd->slot); + + /* Remove from parent list and release itself */ + list_del(&pd->link); + free(pd); + } +} -/* pci_scan - Perform a recursive scan of the bus at bus_number - * populating the list passed as an argument. This also - * performs the bus numbering, so it returns the largest - * bus number that was assigned. +/* Perform a recursive scan of the bus at bus_number populating + * the list passed as an argument. This also performs the bus + * numbering, so it returns the largest bus number that was + * assigned. * * Note: Eventually this might want to access some VPD information * in order to know what slots to scan and what not etc.. @@ -473,9 +496,9 @@ static void pci_cleanup_bridge(struct phb *phb, struct pci_device *pd) * XXX NOTE: We might also want to setup the PCIe MPS/MRSS properly * here as Linux may or may not do it */ -static uint8_t pci_scan(struct phb *phb, uint8_t bus, uint8_t max_bus, - struct list_head *list, struct pci_device *parent, - bool scan_downstream) +uint8_t pci_scan_bus(struct phb *phb, uint8_t bus, uint8_t max_bus, + struct list_head *list, struct pci_device *parent, + bool scan_downstream) { struct pci_device *pd = NULL; uint8_t dev, fn, next_bus, max_sub, save_max; @@ -592,8 +615,8 @@ static uint8_t pci_scan(struct phb *phb, uint8_t bus, uint8_t max_bus, /* Perform recursive scan */ if (do_scan) { - max_sub = pci_scan(phb, next_bus, max_bus, - &pd->children, pd, true); + max_sub = pci_scan_bus(phb, next_bus, max_bus, + &pd->children, pd, true); } else if (!use_max) { /* XXX Empty bridge... we leave room for hotplug * slots etc.. but we should be smarter at figuring @@ -801,7 +824,7 @@ static void pci_scan_phb(void *data) /* Scan root port and downstream ports if applicable */ PCIDBG(phb, 0, "Scanning (upstream%s)...\n", has_link ? "+downsteam" : " only"); - pci_scan(phb, 0, 0xff, &phb->devices, NULL, has_link); + pci_scan_bus(phb, 0, 0xff, &phb->devices, NULL, has_link); /* Configure MPS (Max Payload Size) for PCIe domain */ pci_walk_dev(phb, NULL, pci_get_mps, &mps); @@ -1327,12 +1350,12 @@ static void pci_print_summary_line(struct phb *phb, struct pci_device *pd, rev_class & 0xff, rev_class >> 8, cname, slotstr); } - -static void pci_add_one_node(struct phb *phb, struct pci_device *pd, - struct dt_node *parent_node, - struct pci_lsi_state *lstate, uint8_t swizzle) +static void pci_add_one_device_node(struct phb *phb, + struct pci_device *pd, + struct dt_node *parent_node, + struct pci_lsi_state *lstate, + uint8_t swizzle) { - struct pci_device *child; struct dt_node *np; const char *cname; #define MAX_NAME 256 @@ -1447,14 +1470,14 @@ static void pci_add_one_node(struct phb *phb, struct pci_device *pd, * Instead add a ranges property that explicitly translates 1:1. */ dt_add_property(np, "ranges", ranges_direct, sizeof(ranges_direct)); - - list_for_each(&pd->children, child, link) - pci_add_one_node(phb, child, np, lstate, swizzle); } -static void pci_add_nodes(struct phb *phb) +void pci_add_device_nodes(struct phb *phb, + struct list_head *list, + struct dt_node *parent_node, + struct pci_lsi_state *lstate, + uint8_t swizzle) { - struct pci_lsi_state *lstate = &phb->lstate; struct pci_device *pd; /* If the PHB has its own slot info, add them */ @@ -1462,8 +1485,15 @@ static void pci_add_nodes(struct phb *phb) pci_add_slot_properties(phb, phb->slot_info, NULL); /* Add all child devices */ - list_for_each(&phb->devices, pd, link) - pci_add_one_node(phb, pd, phb->dt_node, lstate, 0); + list_for_each(list, pd, link) { + pci_add_one_device_node(phb, pd, parent_node, + lstate, swizzle); + if (list_empty(&pd->children)) + continue; + + pci_add_device_nodes(phb, &pd->children, + pd->dn, lstate, swizzle); + } } static void __pci_reset(struct list_head *list) @@ -1544,7 +1574,8 @@ void pci_init_slots(void) for (i = 0; i < ARRAY_SIZE(phbs); i++) { if (!phbs[i]) continue; - pci_add_nodes(phbs[i]); + pci_add_device_nodes(phbs[i], &phbs[i]->devices, + phbs[i]->dt_node, &phbs[i]->lstate, 0); } /* PHB final fixup */ diff --git a/core/pcie-slot.c b/core/pcie-slot.c new file mode 100644 index 0000000..62933a4 --- /dev/null +++ b/core/pcie-slot.c @@ -0,0 +1,452 @@ +/* Copyright 2013-2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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->slot_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) +{ + /* The power is always on if no functionality is supported */ + if (!(slot->slot_cap & PCICAP_EXP_SLOTCAP_PWCTRL)) + *val = PCI_SLOT_POWER_ON; + else + *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(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_PWCTRL)) + return OPAL_SUCCESS; + + if (slot->power_state == val) + return OPAL_SUCCESS; + + 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_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 && + slot->ops.set_power_state) { + PCIE_SLOT_DBG(slot, "FRESET: Power is on, turn off\n"); + slot->ops.set_power_state(slot, PCI_SLOT_POWER_OFF); + 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, fall through */ + case PCI_SLOT_STATE_FRESET_POWER_OFF: + PCIE_SLOT_DBG(slot, "FRESET: Power is off, turn on\n"); + if (slot->ops.set_power_state) + slot->ops.set_power_state(slot, PCI_SLOT_POWER_ON); + pci_slot_set_state(slot, PCI_SLOT_STATE_HRESET_START); + 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; + + /* Allocate PCI slot */ + slot = pci_slot_alloc(phb, pd); + if (!slot) + return NULL; + + /* Cache the link and slot capabilities */ + if (pd) { + ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false); + pci_cfg_read32(phb, pd->bdfn, ecap + PCICAP_EXP_LCAP, + &slot->link_cap); + pci_cfg_read32(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCAP, + &slot->slot_cap); + } + + if ((slot->slot_cap & PCICAP_EXP_SLOTCAP_HPLUG_SURP) && + (slot->slot_cap & PCICAP_EXP_SLOTCAP_HPLUG_CAP)) + slot->pluggable = 1; + if (slot->slot_cap & PCICAP_EXP_SLOTCAP_PWCTRL) + slot->power_ctl = 1; + 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); + + /* 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->ops.pfreset = NULL; + + return slot; +} |