1964 lines
51 KiB
C
1964 lines
51 KiB
C
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
|
|
/*
|
|
* Base PCI support
|
|
*
|
|
* Copyright 2013-2019 IBM Corp.
|
|
*/
|
|
|
|
#include <skiboot.h>
|
|
#include <cpu.h>
|
|
#include <pci.h>
|
|
#include <pci-cfg.h>
|
|
#include <pci-slot.h>
|
|
#include <pci-quirk.h>
|
|
#include <timebase.h>
|
|
#include <device.h>
|
|
|
|
#define MAX_PHB_ID 256
|
|
static struct phb *phbs[MAX_PHB_ID];
|
|
int last_phb_id = 0;
|
|
|
|
/*
|
|
* Generic PCI utilities
|
|
*/
|
|
|
|
static int64_t __pci_find_cap(struct phb *phb, uint16_t bdfn,
|
|
uint8_t want, bool check_cap_indicator)
|
|
{
|
|
int64_t rc;
|
|
uint16_t stat, cap;
|
|
uint8_t pos, next;
|
|
|
|
rc = pci_cfg_read16(phb, bdfn, PCI_CFG_STAT, &stat);
|
|
if (rc)
|
|
return rc;
|
|
if (check_cap_indicator && !(stat & PCI_CFG_STAT_CAP))
|
|
return OPAL_UNSUPPORTED;
|
|
rc = pci_cfg_read8(phb, bdfn, PCI_CFG_CAP, &pos);
|
|
if (rc)
|
|
return rc;
|
|
pos &= 0xfc;
|
|
while(pos) {
|
|
rc = pci_cfg_read16(phb, bdfn, pos, &cap);
|
|
if (rc)
|
|
return rc;
|
|
if ((cap & 0xff) == want)
|
|
return pos;
|
|
next = (cap >> 8) & 0xfc;
|
|
if (next == pos) {
|
|
PCIERR(phb, bdfn, "pci_find_cap hit a loop !\n");
|
|
break;
|
|
}
|
|
pos = next;
|
|
}
|
|
return OPAL_UNSUPPORTED;
|
|
}
|
|
|
|
/* pci_find_cap - Find a PCI capability in a device config space
|
|
*
|
|
* This will return a config space offset (positive) or a negative
|
|
* error (OPAL error codes).
|
|
*
|
|
* OPAL_UNSUPPORTED is returned if the capability doesn't exist
|
|
*/
|
|
int64_t pci_find_cap(struct phb *phb, uint16_t bdfn, uint8_t want)
|
|
{
|
|
return __pci_find_cap(phb, bdfn, want, true);
|
|
}
|
|
|
|
/* pci_find_ecap - Find a PCIe extended capability in a device
|
|
* config space
|
|
*
|
|
* This will return a config space offset (positive) or a negative
|
|
* error (OPAL error code). Additionally, if the "version" argument
|
|
* is non-NULL, the capability version will be returned there.
|
|
*
|
|
* OPAL_UNSUPPORTED is returned if the capability doesn't exist
|
|
*/
|
|
int64_t pci_find_ecap(struct phb *phb, uint16_t bdfn, uint16_t want,
|
|
uint8_t *version)
|
|
{
|
|
int64_t rc;
|
|
uint32_t cap;
|
|
uint16_t off, prev = 0;
|
|
|
|
for (off = 0x100; off && off < 0x1000; off = (cap >> 20) & 0xffc ) {
|
|
if (off == prev) {
|
|
PCIERR(phb, bdfn, "pci_find_ecap hit a loop !\n");
|
|
break;
|
|
}
|
|
prev = off;
|
|
rc = pci_cfg_read32(phb, bdfn, off, &cap);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* no ecaps supported */
|
|
if (cap == 0 || (cap & 0xffff) == 0xffff)
|
|
return OPAL_UNSUPPORTED;
|
|
|
|
if ((cap & 0xffff) == want) {
|
|
if (version)
|
|
*version = (cap >> 16) & 0xf;
|
|
return off;
|
|
}
|
|
}
|
|
return OPAL_UNSUPPORTED;
|
|
}
|
|
|
|
static void pci_init_pcie_cap(struct phb *phb, struct pci_device *pd)
|
|
{
|
|
int64_t ecap = 0;
|
|
uint16_t reg;
|
|
uint32_t val;
|
|
|
|
/* On the upstream port of PLX bridge 8724 (rev ba), PCI_STATUS
|
|
* register doesn't have capability indicator though it support
|
|
* various PCI capabilities. So we need ignore that bit when
|
|
* looking for PCI capabilities on the upstream port, which is
|
|
* limited to one that seats directly under root port.
|
|
*/
|
|
if (pd->vdid == 0x872410b5 && pd->parent && !pd->parent->parent) {
|
|
uint8_t rev;
|
|
|
|
pci_cfg_read8(phb, pd->bdfn, PCI_CFG_REV_ID, &rev);
|
|
if (rev == 0xba)
|
|
ecap = __pci_find_cap(phb, pd->bdfn,
|
|
PCI_CFG_CAP_ID_EXP, false);
|
|
else
|
|
ecap = pci_find_cap(phb, pd->bdfn, PCI_CFG_CAP_ID_EXP);
|
|
} else {
|
|
ecap = pci_find_cap(phb, pd->bdfn, PCI_CFG_CAP_ID_EXP);
|
|
}
|
|
|
|
if (ecap <= 0) {
|
|
pd->dev_type = PCIE_TYPE_LEGACY;
|
|
return;
|
|
}
|
|
|
|
pci_set_cap(pd, PCI_CFG_CAP_ID_EXP, ecap, NULL, NULL, false);
|
|
|
|
/*
|
|
* XXX We observe a problem on some PLX switches where one
|
|
* of the downstream ports appears as an upstream port, we
|
|
* fix that up here otherwise, other code will misbehave
|
|
*/
|
|
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_CAPABILITY_REG, ®);
|
|
pd->dev_type = GETFIELD(PCICAP_EXP_CAP_TYPE, reg);
|
|
if (pd->parent && pd->parent->dev_type == PCIE_TYPE_SWITCH_UPPORT &&
|
|
pd->vdid == 0x874810b5 && pd->dev_type == PCIE_TYPE_SWITCH_UPPORT) {
|
|
PCIDBG(phb, pd->bdfn, "Fixing up bad PLX downstream port !\n");
|
|
pd->dev_type = PCIE_TYPE_SWITCH_DNPORT;
|
|
}
|
|
|
|
/* XXX Handle ARI */
|
|
if (pd->dev_type == PCIE_TYPE_SWITCH_DNPORT ||
|
|
pd->dev_type == PCIE_TYPE_ROOT_PORT)
|
|
pd->scan_map = 0x1;
|
|
|
|
/* Read MPS capability, whose maximal size is 4096 */
|
|
pci_cfg_read32(phb, pd->bdfn, ecap + PCICAP_EXP_DEVCAP, &val);
|
|
pd->mps = (128 << GETFIELD(PCICAP_EXP_DEVCAP_MPSS, val));
|
|
if (pd->mps > 4096)
|
|
pd->mps = 4096;
|
|
}
|
|
|
|
static void pci_init_aer_cap(struct phb *phb, struct pci_device *pd)
|
|
{
|
|
int64_t pos;
|
|
|
|
if (!pci_has_cap(pd, PCI_CFG_CAP_ID_EXP, false))
|
|
return;
|
|
|
|
pos = pci_find_ecap(phb, pd->bdfn, PCIECAP_ID_AER, NULL);
|
|
if (pos > 0)
|
|
pci_set_cap(pd, PCIECAP_ID_AER, pos, NULL, NULL, true);
|
|
}
|
|
|
|
static void pci_init_pm_cap(struct phb *phb, struct pci_device *pd)
|
|
{
|
|
int64_t pos;
|
|
|
|
pos = pci_find_cap(phb, pd->bdfn, PCI_CFG_CAP_ID_PM);
|
|
if (pos > 0)
|
|
pci_set_cap(pd, PCI_CFG_CAP_ID_PM, pos, NULL, NULL, false);
|
|
}
|
|
|
|
void pci_init_capabilities(struct phb *phb, struct pci_device *pd)
|
|
{
|
|
pci_init_pcie_cap(phb, pd);
|
|
pci_init_aer_cap(phb, pd);
|
|
pci_init_pm_cap(phb, pd);
|
|
}
|
|
|
|
bool pci_wait_crs(struct phb *phb, uint16_t bdfn, uint32_t *out_vdid)
|
|
{
|
|
uint32_t retries, vdid;
|
|
int64_t rc;
|
|
bool had_crs = false;
|
|
|
|
for (retries = 0; retries < 40; retries++) {
|
|
rc = pci_cfg_read32(phb, bdfn, PCI_CFG_VENDOR_ID, &vdid);
|
|
if (rc)
|
|
return false;
|
|
if (vdid == 0xffffffff || vdid == 0x00000000)
|
|
return false;
|
|
if (vdid != 0xffff0001)
|
|
break;
|
|
had_crs = true;
|
|
time_wait_ms(100);
|
|
}
|
|
if (vdid == 0xffff0001) {
|
|
PCIERR(phb, bdfn, "CRS timeout !\n");
|
|
return false;
|
|
}
|
|
if (had_crs)
|
|
PCIDBG(phb, bdfn, "Probe success after %d CRS\n", retries);
|
|
|
|
if (out_vdid)
|
|
*out_vdid = vdid;
|
|
return true;
|
|
}
|
|
|
|
static struct pci_device *pci_scan_one(struct phb *phb, struct pci_device *parent,
|
|
uint16_t bdfn)
|
|
{
|
|
struct pci_device *pd = NULL;
|
|
uint32_t vdid;
|
|
int64_t rc;
|
|
uint8_t htype;
|
|
|
|
if (!pci_wait_crs(phb, bdfn, &vdid))
|
|
return NULL;
|
|
|
|
/* Perform a dummy write to the device in order for it to
|
|
* capture it's own bus number, so any subsequent error
|
|
* messages will be properly tagged
|
|
*/
|
|
pci_cfg_write32(phb, bdfn, PCI_CFG_VENDOR_ID, vdid);
|
|
|
|
pd = zalloc(sizeof(struct pci_device));
|
|
if (!pd) {
|
|
PCIERR(phb, bdfn,"Failed to allocate structure pci_device !\n");
|
|
goto fail;
|
|
}
|
|
pd->phb = phb;
|
|
pd->bdfn = bdfn;
|
|
pd->vdid = vdid;
|
|
pci_cfg_read32(phb, bdfn, PCI_CFG_SUBSYS_VENDOR_ID, &pd->sub_vdid);
|
|
pci_cfg_read32(phb, bdfn, PCI_CFG_REV_ID, &pd->class);
|
|
pd->class >>= 8;
|
|
|
|
pd->parent = parent;
|
|
list_head_init(&pd->pcrf);
|
|
list_head_init(&pd->children);
|
|
rc = pci_cfg_read8(phb, bdfn, PCI_CFG_HDR_TYPE, &htype);
|
|
if (rc) {
|
|
PCIERR(phb, bdfn, "Failed to read header type !\n");
|
|
goto fail;
|
|
}
|
|
pd->is_multifunction = !!(htype & 0x80);
|
|
pd->is_bridge = (htype & 0x7f) != 0;
|
|
pd->is_vf = false;
|
|
pd->scan_map = 0xffffffff; /* Default */
|
|
pd->primary_bus = PCI_BUS_NUM(bdfn);
|
|
|
|
pci_init_capabilities(phb, pd);
|
|
|
|
/* If it's a bridge, sanitize the bus numbers to avoid forwarding
|
|
*
|
|
* This will help when walking down those bridges later on
|
|
*/
|
|
if (pd->is_bridge) {
|
|
pci_cfg_write8(phb, bdfn, PCI_CFG_PRIMARY_BUS, pd->primary_bus);
|
|
pci_cfg_write8(phb, bdfn, PCI_CFG_SECONDARY_BUS, 0);
|
|
pci_cfg_write8(phb, bdfn, PCI_CFG_SUBORDINATE_BUS, 0);
|
|
}
|
|
|
|
/* XXX Need to do some basic setups, such as MPSS, MRS,
|
|
* RCB, etc...
|
|
*/
|
|
|
|
PCIDBG(phb, bdfn, "Found VID:%04x DEV:%04x TYP:%d MF%s BR%s EX%s\n",
|
|
vdid & 0xffff, vdid >> 16, pd->dev_type,
|
|
pd->is_multifunction ? "+" : "-",
|
|
pd->is_bridge ? "+" : "-",
|
|
pci_has_cap(pd, PCI_CFG_CAP_ID_EXP, false) ? "+" : "-");
|
|
|
|
/* Try to get PCI slot behind the device */
|
|
if (platform.pci_get_slot_info)
|
|
platform.pci_get_slot_info(phb, pd);
|
|
|
|
/* Put it to the child device of list of PHB or parent */
|
|
if (!parent)
|
|
list_add_tail(&phb->devices, &pd->link);
|
|
else
|
|
list_add_tail(&parent->children, &pd->link);
|
|
|
|
/*
|
|
* Call PHB hook
|
|
*/
|
|
if (phb->ops->device_init)
|
|
phb->ops->device_init(phb, pd, NULL);
|
|
|
|
return pd;
|
|
fail:
|
|
if (pd)
|
|
free(pd);
|
|
return NULL;
|
|
}
|
|
|
|
/* pci_check_clear_freeze - Probing empty slot will result in an EEH
|
|
* freeze. Currently we have a single PE mapping
|
|
* everything (default state of our backend) so
|
|
* we just check and clear the state of PE#0
|
|
*
|
|
* returns true if a freeze was detected
|
|
*
|
|
* NOTE: We currently only handle simple PE freeze, not PHB fencing
|
|
* (or rather our backend does)
|
|
*/
|
|
bool pci_check_clear_freeze(struct phb *phb)
|
|
{
|
|
uint8_t freeze_state;
|
|
uint16_t pci_error_type, sev;
|
|
int64_t pe_number, rc;
|
|
|
|
/* Retrieve the reserved PE number */
|
|
pe_number = OPAL_PARAMETER;
|
|
if (phb->ops->get_reserved_pe_number)
|
|
pe_number = phb->ops->get_reserved_pe_number(phb);
|
|
if (pe_number < 0)
|
|
return false;
|
|
|
|
/* Retrieve the frozen state */
|
|
rc = phb->ops->eeh_freeze_status(phb, pe_number, &freeze_state,
|
|
&pci_error_type, &sev);
|
|
if (rc)
|
|
return true; /* phb fence? */
|
|
|
|
if (freeze_state == OPAL_EEH_STOPPED_NOT_FROZEN)
|
|
return false;
|
|
/* We can't handle anything worse than an ER here */
|
|
if (sev > OPAL_EEH_SEV_NO_ERROR &&
|
|
sev < OPAL_EEH_SEV_PE_ER) {
|
|
PCIERR(phb, 0, "Fatal probe in %s error !\n", __func__);
|
|
return true;
|
|
}
|
|
|
|
phb->ops->eeh_freeze_clear(phb, pe_number,
|
|
OPAL_EEH_ACTION_CLEAR_FREEZE_ALL);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Turn off slot's power supply if there are nothing connected for
|
|
* 2 purposes: power saving obviously and initialize the slot to
|
|
* to initial power-off state for hotplug.
|
|
*
|
|
* The power should be turned on if the downstream link of the slot
|
|
* isn't up.
|
|
*/
|
|
static void pci_slot_set_power_state(struct phb *phb,
|
|
struct pci_device *pd,
|
|
uint8_t state)
|
|
{
|
|
struct pci_slot *slot;
|
|
uint8_t cur_state;
|
|
int32_t wait = 100;
|
|
int64_t rc;
|
|
|
|
if (!pd || !pd->slot)
|
|
return;
|
|
|
|
slot = pd->slot;
|
|
if (!slot->pluggable ||
|
|
!slot->ops.get_power_state ||
|
|
!slot->ops.set_power_state)
|
|
return;
|
|
|
|
if (state == PCI_SLOT_POWER_OFF) {
|
|
/* Bail if there're something connected */
|
|
if (!list_empty(&pd->children)) {
|
|
PCIERR(phb, pd->bdfn, "Attempted to power off slot with attached devices!\n");
|
|
return;
|
|
}
|
|
|
|
pci_slot_add_flags(slot, PCI_SLOT_FLAG_BOOTUP);
|
|
rc = slot->ops.get_power_state(slot, &cur_state);
|
|
if (rc != OPAL_SUCCESS) {
|
|
PCINOTICE(phb, pd->bdfn, "Error %lld getting slot power state\n", rc);
|
|
cur_state = PCI_SLOT_POWER_OFF;
|
|
}
|
|
|
|
pci_slot_remove_flags(slot, PCI_SLOT_FLAG_BOOTUP);
|
|
if (cur_state == PCI_SLOT_POWER_OFF)
|
|
return;
|
|
}
|
|
|
|
pci_slot_add_flags(slot,
|
|
(PCI_SLOT_FLAG_BOOTUP | PCI_SLOT_FLAG_ENFORCE));
|
|
rc = slot->ops.set_power_state(slot, state);
|
|
if (rc == OPAL_SUCCESS)
|
|
goto success;
|
|
if (rc != OPAL_ASYNC_COMPLETION) {
|
|
PCINOTICE(phb, pd->bdfn, "Error %lld powering %s slot\n",
|
|
rc, state == PCI_SLOT_POWER_ON ? "on" : "off");
|
|
goto error;
|
|
}
|
|
|
|
/* Wait until the operation is completed */
|
|
do {
|
|
if (slot->state == PCI_SLOT_STATE_SPOWER_DONE)
|
|
break;
|
|
|
|
check_timers(false);
|
|
time_wait_ms(10);
|
|
} while (--wait >= 0);
|
|
|
|
if (wait < 0) {
|
|
PCINOTICE(phb, pd->bdfn, "Timeout powering %s slot\n",
|
|
state == PCI_SLOT_POWER_ON ? "on" : "off");
|
|
goto error;
|
|
}
|
|
|
|
success:
|
|
PCIDBG(phb, pd->bdfn, "Powering %s hotpluggable slot\n",
|
|
state == PCI_SLOT_POWER_ON ? "on" : "off");
|
|
error:
|
|
pci_slot_remove_flags(slot,
|
|
(PCI_SLOT_FLAG_BOOTUP | PCI_SLOT_FLAG_ENFORCE));
|
|
pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL);
|
|
}
|
|
|
|
static bool pci_bridge_power_on(struct phb *phb, struct pci_device *pd)
|
|
{
|
|
int32_t ecap;
|
|
uint16_t pcie_cap, slot_sts, slot_ctl, link_ctl;
|
|
uint32_t slot_cap;
|
|
int64_t rc;
|
|
|
|
/*
|
|
* If there is a PCI slot associated with the bridge, to use
|
|
* the PCI slot's facality to power it on.
|
|
*/
|
|
if (pd->slot) {
|
|
struct pci_slot *slot = pd->slot;
|
|
uint8_t presence;
|
|
|
|
/*
|
|
* We assume the presence state is OPAL_PCI_SLOT_PRESENT
|
|
* by default. In this way, we won't miss anything when
|
|
* the operation isn't supported or hitting error upon
|
|
* retrieving it.
|
|
*/
|
|
if (slot->ops.get_presence_state) {
|
|
rc = slot->ops.get_presence_state(slot, &presence);
|
|
if (rc == OPAL_SUCCESS &&
|
|
presence == OPAL_PCI_SLOT_EMPTY)
|
|
return false;
|
|
}
|
|
|
|
/* To power it on */
|
|
pci_slot_set_power_state(phb, pd, PCI_SLOT_POWER_ON);
|
|
return true;
|
|
}
|
|
|
|
if (!pci_has_cap(pd, PCI_CFG_CAP_ID_EXP, false))
|
|
return true;
|
|
|
|
/* Check if slot is supported */
|
|
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
|
|
pci_cfg_read16(phb, pd->bdfn,
|
|
ecap + PCICAP_EXP_CAPABILITY_REG, &pcie_cap);
|
|
if (!(pcie_cap & PCICAP_EXP_CAP_SLOT))
|
|
return true;
|
|
|
|
/* Check presence */
|
|
pci_cfg_read16(phb, pd->bdfn,
|
|
ecap + PCICAP_EXP_SLOTSTAT, &slot_sts);
|
|
if (!(slot_sts & PCICAP_EXP_SLOTSTAT_PDETECTST))
|
|
return false;
|
|
|
|
/* Ensure that power control is supported */
|
|
pci_cfg_read32(phb, pd->bdfn,
|
|
ecap + PCICAP_EXP_SLOTCAP, &slot_cap);
|
|
if (!(slot_cap & PCICAP_EXP_SLOTCAP_PWCTRL))
|
|
return true;
|
|
|
|
|
|
/* Read the slot control register, check if the slot is off */
|
|
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL, &slot_ctl);
|
|
PCITRACE(phb, pd->bdfn, " SLOT_CTL=%04x\n", slot_ctl);
|
|
if (slot_ctl & PCICAP_EXP_SLOTCTL_PWRCTLR) {
|
|
PCIDBG(phb, pd->bdfn, "Bridge power is off, turning on ...\n");
|
|
slot_ctl &= ~PCICAP_EXP_SLOTCTL_PWRCTLR;
|
|
slot_ctl |= SETFIELD(PCICAP_EXP_SLOTCTL_PWRI, 0, PCIE_INDIC_ON);
|
|
pci_cfg_write16(phb, pd->bdfn,
|
|
ecap + PCICAP_EXP_SLOTCTL, slot_ctl);
|
|
|
|
/* Wait a couple of seconds */
|
|
time_wait_ms(2000);
|
|
}
|
|
|
|
/* Enable link */
|
|
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_LCTL, &link_ctl);
|
|
PCITRACE(phb, pd->bdfn, " LINK_CTL=%04x\n", link_ctl);
|
|
link_ctl &= ~PCICAP_EXP_LCTL_LINK_DIS;
|
|
pci_cfg_write16(phb, pd->bdfn, ecap + PCICAP_EXP_LCTL, link_ctl);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool pci_bridge_wait_link(struct phb *phb,
|
|
struct pci_device *pd,
|
|
bool was_reset)
|
|
{
|
|
int32_t ecap = 0;
|
|
uint32_t link_cap = 0, retries = 100;
|
|
uint16_t link_sts;
|
|
|
|
if (pci_has_cap(pd, PCI_CFG_CAP_ID_EXP, false)) {
|
|
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
|
|
pci_cfg_read32(phb, pd->bdfn, ecap + PCICAP_EXP_LCAP, &link_cap);
|
|
}
|
|
|
|
/*
|
|
* If link state reporting isn't supported, wait 1 second
|
|
* if the downstream link was ever resetted.
|
|
*/
|
|
if (!(link_cap & PCICAP_EXP_LCAP_DL_ACT_REP)) {
|
|
if (was_reset)
|
|
time_wait_ms(1000);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Link state reporting is supported, wait for the link to
|
|
* come up until timeout.
|
|
*/
|
|
PCIDBG(phb, pd->bdfn, "waiting for link... \n");
|
|
while (retries--) {
|
|
pci_cfg_read16(phb, pd->bdfn,
|
|
ecap + PCICAP_EXP_LSTAT, &link_sts);
|
|
if (link_sts & PCICAP_EXP_LSTAT_DLLL_ACT)
|
|
break;
|
|
|
|
time_wait_ms(100);
|
|
}
|
|
|
|
if (!(link_sts & PCICAP_EXP_LSTAT_DLLL_ACT)) {
|
|
PCIERR(phb, pd->bdfn, "Timeout waiting for downstream link\n");
|
|
return false;
|
|
}
|
|
|
|
/* Need another 100ms before touching the config space */
|
|
time_wait_ms(100);
|
|
PCIDBG(phb, pd->bdfn, "link is up\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
/* pci_enable_bridge - Called before scanning a bridge
|
|
*
|
|
* Ensures error flags are clean, disable master abort, and
|
|
* check if the subordinate bus isn't reset, the slot is enabled
|
|
* on PCIe, etc...
|
|
*/
|
|
static bool pci_enable_bridge(struct phb *phb, struct pci_device *pd)
|
|
{
|
|
uint16_t bctl;
|
|
bool was_reset = false;
|
|
|
|
/* Disable master aborts, clear errors */
|
|
pci_cfg_read16(phb, pd->bdfn, PCI_CFG_BRCTL, &bctl);
|
|
bctl &= ~PCI_CFG_BRCTL_MABORT_REPORT;
|
|
pci_cfg_write16(phb, pd->bdfn, PCI_CFG_BRCTL, bctl);
|
|
|
|
|
|
/* PCI-E bridge, check the slot state. We don't do that on the
|
|
* root complex as this is handled separately and not all our
|
|
* RCs implement the standard register set.
|
|
*/
|
|
if ((pd->dev_type == PCIE_TYPE_ROOT_PORT && pd->primary_bus > 0) ||
|
|
pd->dev_type == PCIE_TYPE_SWITCH_DNPORT) {
|
|
if (pci_has_cap(pd, PCI_CFG_CAP_ID_EXP, false)) {
|
|
int32_t ecap;
|
|
uint32_t link_cap = 0;
|
|
uint16_t link_sts = 0;
|
|
|
|
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
|
|
pci_cfg_read32(phb, pd->bdfn,
|
|
ecap + PCICAP_EXP_LCAP, &link_cap);
|
|
|
|
/*
|
|
* No need to touch the power supply if the PCIe link has
|
|
* been up. Further more, the slot presence bit is lost while
|
|
* the PCIe link is up on the specific PCI topology. In that
|
|
* case, we need ignore the slot presence bit and go ahead for
|
|
* probing. Otherwise, the NVMe adapter won't be probed.
|
|
*
|
|
* PHB3 root port, PLX switch 8748 (10b5:8748), PLX swich 9733
|
|
* (10b5:9733), PMC 8546 swtich (11f8:8546), NVMe adapter
|
|
* (1c58:0023).
|
|
*/
|
|
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
|
|
pci_cfg_read32(phb, pd->bdfn,
|
|
ecap + PCICAP_EXP_LCAP, &link_cap);
|
|
pci_cfg_read16(phb, pd->bdfn,
|
|
ecap + PCICAP_EXP_LSTAT, &link_sts);
|
|
if ((link_cap & PCICAP_EXP_LCAP_DL_ACT_REP) &&
|
|
(link_sts & PCICAP_EXP_LSTAT_DLLL_ACT))
|
|
return true;
|
|
}
|
|
|
|
/* Power on the downstream slot or link */
|
|
if (!pci_bridge_power_on(phb, pd))
|
|
return false;
|
|
}
|
|
|
|
/* Clear secondary reset */
|
|
if (bctl & PCI_CFG_BRCTL_SECONDARY_RESET) {
|
|
PCIDBG(phb, pd->bdfn,
|
|
"Bridge secondary reset is on, clearing it ...\n");
|
|
bctl &= ~PCI_CFG_BRCTL_SECONDARY_RESET;
|
|
pci_cfg_write16(phb, pd->bdfn, PCI_CFG_BRCTL, bctl);
|
|
time_wait_ms(1000);
|
|
was_reset = true;
|
|
}
|
|
|
|
/* PCI-E bridge, wait for link */
|
|
if (pd->dev_type == PCIE_TYPE_ROOT_PORT ||
|
|
pd->dev_type == PCIE_TYPE_SWITCH_DNPORT) {
|
|
if (!pci_bridge_wait_link(phb, pd, was_reset))
|
|
return false;
|
|
}
|
|
|
|
/* Clear error status */
|
|
pci_cfg_write16(phb, pd->bdfn, PCI_CFG_STAT, 0xffff);
|
|
return true;
|
|
}
|
|
|
|
/* Clear up bridge resources */
|
|
static void pci_cleanup_bridge(struct phb *phb, struct pci_device *pd)
|
|
{
|
|
uint16_t cmd;
|
|
|
|
pci_cfg_write16(phb, pd->bdfn, PCI_CFG_IO_BASE_U16, 0xffff);
|
|
pci_cfg_write8(phb, pd->bdfn, PCI_CFG_IO_BASE, 0xf0);
|
|
pci_cfg_write16(phb, pd->bdfn, PCI_CFG_IO_LIMIT_U16, 0);
|
|
pci_cfg_write8(phb, pd->bdfn, PCI_CFG_IO_LIMIT, 0);
|
|
pci_cfg_write16(phb, pd->bdfn, PCI_CFG_MEM_BASE, 0xfff0);
|
|
pci_cfg_write16(phb, pd->bdfn, PCI_CFG_MEM_LIMIT, 0);
|
|
pci_cfg_write32(phb, pd->bdfn, PCI_CFG_PREF_MEM_BASE_U32, 0xffffffff);
|
|
pci_cfg_write16(phb, pd->bdfn, PCI_CFG_PREF_MEM_BASE, 0xfff0);
|
|
pci_cfg_write32(phb, pd->bdfn, PCI_CFG_PREF_MEM_LIMIT_U32, 0);
|
|
pci_cfg_write16(phb, pd->bdfn, PCI_CFG_PREF_MEM_LIMIT, 0);
|
|
|
|
/* Note: This is a bit fishy but since we have closed all the
|
|
* bridge windows above, it shouldn't be a problem. Basically
|
|
* we enable Memory, IO and Bus Master on the bridge because
|
|
* some versions of Linux will fail to do it themselves.
|
|
*/
|
|
pci_cfg_read16(phb, pd->bdfn, PCI_CFG_CMD, &cmd);
|
|
cmd |= PCI_CFG_CMD_IO_EN | PCI_CFG_CMD_MEM_EN;
|
|
cmd |= PCI_CFG_CMD_BUS_MASTER_EN;
|
|
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);
|
|
|
|
if (phb->ops->device_remove)
|
|
phb->ops->device_remove(phb, pd);
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
static void pci_set_power_limit(struct pci_device *pd)
|
|
{
|
|
uint32_t offset, val;
|
|
uint16_t caps;
|
|
|
|
offset = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
|
|
if (!offset)
|
|
return; /* legacy dev */
|
|
|
|
pci_cfg_read16(pd->phb, pd->bdfn,
|
|
offset + PCICAP_EXP_CAPABILITY_REG, &caps);
|
|
|
|
if (!(caps & PCICAP_EXP_CAP_SLOT))
|
|
return; /* bridge has no slot capabilities */
|
|
if (!pd->slot || !pd->slot->power_limit)
|
|
return;
|
|
|
|
pci_cfg_read32(pd->phb, pd->bdfn, offset + PCICAP_EXP_SLOTCAP, &val);
|
|
|
|
val = SETFIELD(PCICAP_EXP_SLOTCAP_SPLSC, val, 0); /* 1W scale */
|
|
val = SETFIELD(PCICAP_EXP_SLOTCAP_SPLVA, val, pd->slot->power_limit);
|
|
|
|
pci_cfg_write32(pd->phb, pd->bdfn, offset + PCICAP_EXP_SLOTCAP, val);
|
|
|
|
/* update the cached copy in the slot */
|
|
pd->slot->slot_cap = val;
|
|
|
|
PCIDBG(pd->phb, pd->bdfn, "Slot power limit set to %dW\n",
|
|
pd->slot->power_limit);
|
|
}
|
|
|
|
/* 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..
|
|
*
|
|
* XXX NOTE: We might want to enable ARI along the way...
|
|
*
|
|
* XXX NOTE: We might also want to setup the PCIe MPS/MRSS properly
|
|
* here as Linux may or may not do it
|
|
*/
|
|
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, *rc = NULL;
|
|
uint8_t dev, fn, next_bus, max_sub;
|
|
uint32_t scan_map;
|
|
|
|
/* Decide what to scan */
|
|
scan_map = parent ? parent->scan_map : phb->scan_map;
|
|
|
|
/* Do scan */
|
|
for (dev = 0; dev < 32; dev++) {
|
|
if (!(scan_map & (1ul << dev)))
|
|
continue;
|
|
|
|
/* Scan the device */
|
|
pd = pci_scan_one(phb, parent, (bus << 8) | (dev << 3));
|
|
pci_check_clear_freeze(phb);
|
|
if (!pd)
|
|
continue;
|
|
|
|
/* Record RC when its downstream link is down */
|
|
if (!scan_downstream && dev == 0 && !rc)
|
|
rc = pd;
|
|
|
|
/* XXX Handle ARI */
|
|
if (!pd->is_multifunction)
|
|
continue;
|
|
for (fn = 1; fn < 8; fn++) {
|
|
pd = pci_scan_one(phb, parent,
|
|
((uint16_t)bus << 8) | (dev << 3) | fn);
|
|
pci_check_clear_freeze(phb);
|
|
}
|
|
}
|
|
|
|
/* Reserve all possible buses if RC's downstream link is down
|
|
* if PCI hotplug is supported.
|
|
*/
|
|
if (rc && rc->slot && rc->slot->pluggable) {
|
|
next_bus = bus + 1;
|
|
rc->secondary_bus = next_bus;
|
|
rc->subordinate_bus = max_bus;
|
|
pci_cfg_write8(phb, rc->bdfn, PCI_CFG_SECONDARY_BUS,
|
|
rc->secondary_bus);
|
|
pci_cfg_write8(phb, rc->bdfn, PCI_CFG_SUBORDINATE_BUS,
|
|
rc->subordinate_bus);
|
|
}
|
|
|
|
/* set the power limit for any downstream slots while we're here */
|
|
list_for_each(list, pd, link) {
|
|
if (pd->is_bridge)
|
|
pci_set_power_limit(pd);
|
|
}
|
|
|
|
/*
|
|
* We only scan downstream if instructed to do so by the
|
|
* caller. Typically we avoid the scan when we know the
|
|
* link is down already, which happens for the top level
|
|
* root complex, and avoids a long secondary timeout
|
|
*/
|
|
if (!scan_downstream) {
|
|
list_for_each(list, pd, link)
|
|
pci_slot_set_power_state(phb, pd, PCI_SLOT_POWER_OFF);
|
|
|
|
return bus;
|
|
}
|
|
|
|
next_bus = bus + 1;
|
|
max_sub = bus;
|
|
|
|
/* Scan down bridges */
|
|
list_for_each(list, pd, link) {
|
|
bool do_scan;
|
|
|
|
if (!pd->is_bridge)
|
|
continue;
|
|
|
|
/* Configure the bridge with the returned values */
|
|
if (next_bus <= bus) {
|
|
PCIERR(phb, pd->bdfn, "Out of bus numbers !\n");
|
|
max_bus = next_bus = 0; /* Failure case */
|
|
}
|
|
|
|
pd->secondary_bus = next_bus;
|
|
pd->subordinate_bus = max_bus;
|
|
pci_cfg_write8(phb, pd->bdfn, PCI_CFG_SECONDARY_BUS, next_bus);
|
|
pci_cfg_write8(phb, pd->bdfn, PCI_CFG_SUBORDINATE_BUS, max_bus);
|
|
if (!next_bus)
|
|
break;
|
|
|
|
PCIDBG(phb, pd->bdfn, "Bus %02x..%02x scanning...\n",
|
|
next_bus, max_bus);
|
|
|
|
/* Clear up bridge resources */
|
|
pci_cleanup_bridge(phb, pd);
|
|
|
|
/* Configure the bridge. This will enable power to the slot
|
|
* if it's currently disabled, lift reset, etc...
|
|
*
|
|
* Return false if we know there's nothing behind the bridge
|
|
*/
|
|
do_scan = pci_enable_bridge(phb, pd);
|
|
|
|
/* Perform recursive scan */
|
|
if (do_scan) {
|
|
max_sub = pci_scan_bus(phb, next_bus, max_bus,
|
|
&pd->children, pd, true);
|
|
} else {
|
|
/* Empty bridge. We leave room for hotplug
|
|
* slots if the downstream port is pluggable.
|
|
*/
|
|
if (pd->slot && !pd->slot->pluggable)
|
|
max_sub = next_bus;
|
|
else {
|
|
max_sub = next_bus + 4;
|
|
if (max_sub > max_bus)
|
|
max_sub = max_bus;
|
|
}
|
|
}
|
|
|
|
pd->subordinate_bus = max_sub;
|
|
pci_cfg_write8(phb, pd->bdfn, PCI_CFG_SUBORDINATE_BUS, max_sub);
|
|
next_bus = max_sub + 1;
|
|
|
|
/* power off the slot if there's nothing below it */
|
|
if (list_empty(&pd->children))
|
|
pci_slot_set_power_state(phb, pd, PCI_SLOT_POWER_OFF);
|
|
}
|
|
|
|
return max_sub;
|
|
}
|
|
|
|
static int pci_get_mps(struct phb *phb,
|
|
struct pci_device *pd, void *userdata)
|
|
{
|
|
uint32_t *mps = (uint32_t *)userdata;
|
|
|
|
/* Only check PCI device that had MPS capacity */
|
|
if (phb && pd && pd->mps && *mps > pd->mps)
|
|
*mps = pd->mps;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pci_configure_mps(struct phb *phb,
|
|
struct pci_device *pd,
|
|
void *userdata __unused)
|
|
{
|
|
uint32_t ecap, aercap, mps;
|
|
uint16_t val;
|
|
|
|
assert(phb);
|
|
assert(pd);
|
|
|
|
/* If the MPS isn't acceptable one, bail immediately */
|
|
mps = phb->mps;
|
|
if (mps < 128 || mps > 4096)
|
|
return 1;
|
|
|
|
/* Retrieve PCIe and AER capability */
|
|
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
|
|
aercap = pci_cap(pd, PCIECAP_ID_AER, true);
|
|
|
|
/* PCIe device always has MPS capacity */
|
|
if (pd->mps) {
|
|
mps = ilog2(mps) - 7;
|
|
|
|
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_DEVCTL, &val);
|
|
val = SETFIELD(PCICAP_EXP_DEVCTL_MPS, val, mps);
|
|
pci_cfg_write16(phb, pd->bdfn, ecap + PCICAP_EXP_DEVCTL, val);
|
|
}
|
|
|
|
/* Changing MPS on upstream PCI bridge might cause some error
|
|
* bits in PCIe and AER capability. To clear them to avoid
|
|
* confusion.
|
|
*/
|
|
if (aercap) {
|
|
pci_cfg_write32(phb, pd->bdfn, aercap + PCIECAP_AER_UE_STATUS,
|
|
0xffffffff);
|
|
pci_cfg_write32(phb, pd->bdfn, aercap + PCIECAP_AER_CE_STATUS,
|
|
0xffffffff);
|
|
}
|
|
if (ecap)
|
|
pci_cfg_write16(phb, pd->bdfn, ecap + PCICAP_EXP_DEVSTAT, 0xf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pci_disable_completion_timeout(struct phb *phb, struct pci_device *pd)
|
|
{
|
|
uint32_t ecap, val;
|
|
uint16_t pcie_cap;
|
|
|
|
/* PCIE capability required */
|
|
if (!pci_has_cap(pd, PCI_CFG_CAP_ID_EXP, false))
|
|
return;
|
|
|
|
/* Check PCIe capability version */
|
|
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
|
|
pci_cfg_read16(phb, pd->bdfn,
|
|
ecap + PCICAP_EXP_CAPABILITY_REG, &pcie_cap);
|
|
if ((pcie_cap & PCICAP_EXP_CAP_VERSION) <= 1)
|
|
return;
|
|
|
|
/* Check if it has capability to disable completion timeout */
|
|
pci_cfg_read32(phb, pd->bdfn, ecap + PCIECAP_EXP_DCAP2, &val);
|
|
if (!(val & PCICAP_EXP_DCAP2_CMPTOUT_DIS))
|
|
return;
|
|
|
|
/* Disable completion timeout without more check */
|
|
pci_cfg_read32(phb, pd->bdfn, ecap + PCICAP_EXP_DCTL2, &val);
|
|
val |= PCICAP_EXP_DCTL2_CMPTOUT_DIS;
|
|
pci_cfg_write32(phb, pd->bdfn, ecap + PCICAP_EXP_DCTL2, val);
|
|
}
|
|
|
|
void pci_device_init(struct phb *phb, struct pci_device *pd)
|
|
{
|
|
pci_configure_mps(phb, pd, NULL);
|
|
pci_disable_completion_timeout(phb, pd);
|
|
}
|
|
|
|
static void pci_reset_phb(void *data)
|
|
{
|
|
struct phb *phb = data;
|
|
struct pci_slot *slot = phb->slot;
|
|
int64_t rc;
|
|
|
|
if (!slot || !slot->ops.run_sm) {
|
|
PCINOTICE(phb, 0, "Cannot issue reset\n");
|
|
return;
|
|
}
|
|
|
|
pci_slot_add_flags(slot, PCI_SLOT_FLAG_BOOTUP);
|
|
rc = slot->ops.run_sm(slot);
|
|
while (rc > 0) {
|
|
PCITRACE(phb, 0, "Waiting %ld ms\n", tb_to_msecs(rc));
|
|
time_wait(rc);
|
|
rc = slot->ops.run_sm(slot);
|
|
}
|
|
pci_slot_remove_flags(slot, PCI_SLOT_FLAG_BOOTUP);
|
|
if (rc < 0)
|
|
PCIDBG(phb, 0, "Error %lld resetting\n", rc);
|
|
}
|
|
|
|
static void pci_scan_phb(void *data)
|
|
{
|
|
struct phb *phb = data;
|
|
struct pci_slot *slot = phb->slot;
|
|
uint8_t link;
|
|
uint32_t mps = 0xffffffff;
|
|
int64_t rc;
|
|
|
|
if (!slot || !slot->ops.get_link_state) {
|
|
PCIERR(phb, 0, "Cannot query link status\n");
|
|
link = 0;
|
|
} else {
|
|
rc = slot->ops.get_link_state(slot, &link);
|
|
if (rc != OPAL_SUCCESS) {
|
|
PCIERR(phb, 0, "Error %lld querying link status\n",
|
|
rc);
|
|
link = 0;
|
|
}
|
|
}
|
|
|
|
if (!link)
|
|
PCIDBG(phb, 0, "Link down\n");
|
|
else
|
|
PCIDBG(phb, 0, "Link up at x%d width\n", link);
|
|
|
|
/* Scan root port and downstream ports if applicable */
|
|
PCIDBG(phb, 0, "Scanning (upstream%s)...\n",
|
|
link ? "+downsteam" : " only");
|
|
pci_scan_bus(phb, 0, 0xff, &phb->devices, NULL, link);
|
|
|
|
/* Configure MPS (Max Payload Size) for PCIe domain */
|
|
pci_walk_dev(phb, NULL, pci_get_mps, &mps);
|
|
phb->mps = mps;
|
|
pci_walk_dev(phb, NULL, pci_configure_mps, NULL);
|
|
}
|
|
|
|
int64_t pci_register_phb(struct phb *phb, int opal_id)
|
|
{
|
|
/* The user didn't specify an opal_id, allocate one */
|
|
if (opal_id == OPAL_DYNAMIC_PHB_ID) {
|
|
/* This is called at init time in non-concurrent way, so no lock needed */
|
|
for (opal_id = 0; opal_id < ARRAY_SIZE(phbs); opal_id++)
|
|
if (!phbs[opal_id])
|
|
break;
|
|
if (opal_id >= ARRAY_SIZE(phbs)) {
|
|
prerror("PHB: Failed to find a free ID slot\n");
|
|
return OPAL_RESOURCE;
|
|
}
|
|
} else {
|
|
if (opal_id >= ARRAY_SIZE(phbs)) {
|
|
prerror("PHB: ID %x out of range !\n", opal_id);
|
|
return OPAL_PARAMETER;
|
|
}
|
|
/* The user did specify an opal_id, check it's free */
|
|
if (phbs[opal_id]) {
|
|
prerror("PHB: Duplicate registration of ID %x\n", opal_id);
|
|
return OPAL_PARAMETER;
|
|
}
|
|
}
|
|
|
|
phbs[opal_id] = phb;
|
|
phb->opal_id = opal_id;
|
|
if (opal_id > last_phb_id)
|
|
last_phb_id = opal_id;
|
|
dt_add_property_cells(phb->dt_node, "ibm,opal-phbid", 0, phb->opal_id);
|
|
PCIDBG(phb, 0, "PCI: Registered PHB\n");
|
|
|
|
init_lock(&phb->lock);
|
|
list_head_init(&phb->devices);
|
|
|
|
phb->filter_map = zalloc(BITMAP_BYTES(0x10000));
|
|
assert(phb->filter_map);
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
int64_t pci_unregister_phb(struct phb *phb)
|
|
{
|
|
/* XXX We want some kind of RCU or RWlock to make things
|
|
* like that happen while no OPAL callback is in progress,
|
|
* that way we avoid taking a lock in each of them.
|
|
*
|
|
* Right now we don't unregister so we are fine
|
|
*/
|
|
phbs[phb->opal_id] = phb;
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
struct phb *pci_get_phb(uint64_t phb_id)
|
|
{
|
|
if (phb_id >= ARRAY_SIZE(phbs))
|
|
return NULL;
|
|
|
|
/* XXX See comment in pci_unregister_phb() about locking etc... */
|
|
return phbs[phb_id];
|
|
}
|
|
|
|
static const char *pci_class_name(uint32_t class_code)
|
|
{
|
|
uint8_t class = class_code >> 16;
|
|
uint8_t sub = (class_code >> 8) & 0xff;
|
|
uint8_t pif = class_code & 0xff;
|
|
|
|
switch(class) {
|
|
case 0x00:
|
|
switch(sub) {
|
|
case 0x00: return "device";
|
|
case 0x01: return "vga";
|
|
}
|
|
break;
|
|
case 0x01:
|
|
switch(sub) {
|
|
case 0x00: return "scsi";
|
|
case 0x01: return "ide";
|
|
case 0x02: return "fdc";
|
|
case 0x03: return "ipi";
|
|
case 0x04: return "raid";
|
|
case 0x05: return "ata";
|
|
case 0x06: return "sata";
|
|
case 0x07: return "sas";
|
|
default: return "mass-storage";
|
|
}
|
|
case 0x02:
|
|
switch(sub) {
|
|
case 0x00: return "ethernet";
|
|
case 0x01: return "token-ring";
|
|
case 0x02: return "fddi";
|
|
case 0x03: return "atm";
|
|
case 0x04: return "isdn";
|
|
case 0x05: return "worldfip";
|
|
case 0x06: return "picmg";
|
|
default: return "network";
|
|
}
|
|
case 0x03:
|
|
switch(sub) {
|
|
case 0x00: return "vga";
|
|
case 0x01: return "xga";
|
|
case 0x02: return "3d-controller";
|
|
default: return "display";
|
|
}
|
|
case 0x04:
|
|
switch(sub) {
|
|
case 0x00: return "video";
|
|
case 0x01: return "sound";
|
|
case 0x02: return "telephony";
|
|
default: return "multimedia-device";
|
|
}
|
|
case 0x05:
|
|
switch(sub) {
|
|
case 0x00: return "memory";
|
|
case 0x01: return "flash";
|
|
default: return "memory-controller";
|
|
}
|
|
case 0x06:
|
|
switch(sub) {
|
|
case 0x00: return "host";
|
|
case 0x01: return "isa";
|
|
case 0x02: return "eisa";
|
|
case 0x03: return "mca";
|
|
case 0x04: return "pci";
|
|
case 0x05: return "pcmcia";
|
|
case 0x06: return "nubus";
|
|
case 0x07: return "cardbus";
|
|
case 0x08: return "raceway";
|
|
case 0x09: return "semi-transparent-pci";
|
|
case 0x0a: return "infiniband";
|
|
default: return "unknown-bridge";
|
|
}
|
|
case 0x07:
|
|
switch(sub) {
|
|
case 0x00:
|
|
switch(pif) {
|
|
case 0x01: return "16450-serial";
|
|
case 0x02: return "16550-serial";
|
|
case 0x03: return "16650-serial";
|
|
case 0x04: return "16750-serial";
|
|
case 0x05: return "16850-serial";
|
|
case 0x06: return "16950-serial";
|
|
default: return "serial";
|
|
}
|
|
case 0x01:
|
|
switch(pif) {
|
|
case 0x01: return "bi-directional-parallel";
|
|
case 0x02: return "ecp-1.x-parallel";
|
|
case 0x03: return "ieee1284-controller";
|
|
case 0xfe: return "ieee1284-device";
|
|
default: return "parallel";
|
|
}
|
|
case 0x02: return "multiport-serial";
|
|
case 0x03:
|
|
switch(pif) {
|
|
case 0x01: return "16450-modem";
|
|
case 0x02: return "16550-modem";
|
|
case 0x03: return "16650-modem";
|
|
case 0x04: return "16750-modem";
|
|
default: return "modem";
|
|
}
|
|
case 0x04: return "gpib";
|
|
case 0x05: return "smart-card";
|
|
default: return "communication-controller";
|
|
}
|
|
case 0x08:
|
|
switch(sub) {
|
|
case 0x00:
|
|
switch(pif) {
|
|
case 0x01: return "isa-pic";
|
|
case 0x02: return "eisa-pic";
|
|
case 0x10: return "io-apic";
|
|
case 0x20: return "iox-apic";
|
|
default: return "interrupt-controller";
|
|
}
|
|
case 0x01:
|
|
switch(pif) {
|
|
case 0x01: return "isa-dma";
|
|
case 0x02: return "eisa-dma";
|
|
default: return "dma-controller";
|
|
}
|
|
case 0x02:
|
|
switch(pif) {
|
|
case 0x01: return "isa-system-timer";
|
|
case 0x02: return "eisa-system-timer";
|
|
default: return "timer";
|
|
}
|
|
case 0x03:
|
|
switch(pif) {
|
|
case 0x01: return "isa-rtc";
|
|
default: return "rtc";
|
|
}
|
|
case 0x04: return "hotplug-controller";
|
|
case 0x05: return "sd-host-controller";
|
|
default: return "system-peripheral";
|
|
}
|
|
case 0x09:
|
|
switch(sub) {
|
|
case 0x00: return "keyboard";
|
|
case 0x01: return "pen";
|
|
case 0x02: return "mouse";
|
|
case 0x03: return "scanner";
|
|
case 0x04: return "gameport";
|
|
default: return "input-controller";
|
|
}
|
|
case 0x0a:
|
|
switch(sub) {
|
|
case 0x00: return "clock";
|
|
default: return "docking-station";
|
|
}
|
|
case 0x0b:
|
|
switch(sub) {
|
|
case 0x00: return "386";
|
|
case 0x01: return "486";
|
|
case 0x02: return "pentium";
|
|
case 0x10: return "alpha";
|
|
case 0x20: return "powerpc";
|
|
case 0x30: return "mips";
|
|
case 0x40: return "co-processor";
|
|
default: return "cpu";
|
|
}
|
|
case 0x0c:
|
|
switch(sub) {
|
|
case 0x00: return "firewire";
|
|
case 0x01: return "access-bus";
|
|
case 0x02: return "ssa";
|
|
case 0x03:
|
|
switch(pif) {
|
|
case 0x00: return "usb-uhci";
|
|
case 0x10: return "usb-ohci";
|
|
case 0x20: return "usb-ehci";
|
|
case 0x30: return "usb-xhci";
|
|
case 0xfe: return "usb-device";
|
|
default: return "usb";
|
|
}
|
|
case 0x04: return "fibre-channel";
|
|
case 0x05: return "smb";
|
|
case 0x06: return "infiniband";
|
|
case 0x07:
|
|
switch(pif) {
|
|
case 0x00: return "impi-smic";
|
|
case 0x01: return "impi-kbrd";
|
|
case 0x02: return "impi-bltr";
|
|
default: return "impi";
|
|
}
|
|
case 0x08: return "secos";
|
|
case 0x09: return "canbus";
|
|
default: return "serial-bus";
|
|
}
|
|
case 0x0d:
|
|
switch(sub) {
|
|
case 0x00: return "irda";
|
|
case 0x01: return "consumer-ir";
|
|
case 0x10: return "rf-controller";
|
|
case 0x11: return "bluetooth";
|
|
case 0x12: return "broadband";
|
|
case 0x20: return "enet-802.11a";
|
|
case 0x21: return "enet-802.11b";
|
|
default: return "wireless-controller";
|
|
}
|
|
case 0x0e: return "intelligent-controller";
|
|
case 0x0f:
|
|
switch(sub) {
|
|
case 0x01: return "satellite-tv";
|
|
case 0x02: return "satellite-audio";
|
|
case 0x03: return "satellite-voice";
|
|
case 0x04: return "satellite-data";
|
|
default: return "satellite-device";
|
|
}
|
|
case 0x10:
|
|
switch(sub) {
|
|
case 0x00: return "network-encryption";
|
|
case 0x01: return "entertainment-encryption";
|
|
default: return "encryption";
|
|
}
|
|
case 0x011:
|
|
switch(sub) {
|
|
case 0x00: return "dpio";
|
|
case 0x01: return "counter";
|
|
case 0x10: return "measurement";
|
|
case 0x20: return "management-card";
|
|
default: return "data-processing";
|
|
}
|
|
}
|
|
return "device";
|
|
}
|
|
|
|
void pci_std_swizzle_irq_map(struct dt_node *np,
|
|
struct pci_device *pd,
|
|
struct pci_lsi_state *lstate,
|
|
uint8_t swizzle)
|
|
{
|
|
__be32 *p, *map;
|
|
int dev, irq, esize, edevcount;
|
|
size_t map_size;
|
|
|
|
/* Some emulated setups don't use standard interrupts
|
|
* representation
|
|
*/
|
|
if (lstate->int_size == 0)
|
|
return;
|
|
|
|
/* Calculate the size of a map entry:
|
|
*
|
|
* 3 cells : PCI Address
|
|
* 1 cell : PCI IRQ
|
|
* 1 cell : PIC phandle
|
|
* n cells : PIC irq (n = lstate->int_size)
|
|
*
|
|
* Assumption: PIC address is 0-size
|
|
*/
|
|
esize = 3 + 1 + 1 + lstate->int_size;
|
|
|
|
/* Number of map "device" entries
|
|
*
|
|
* A PCI Express root or downstream port needs only one
|
|
* entry for device 0. Anything else will get a full map
|
|
* for all possible 32 child device numbers
|
|
*
|
|
* If we have been passed a host bridge (pd == NULL) we also
|
|
* do a simple per-pin map
|
|
*/
|
|
if (!pd || (pd->dev_type == PCIE_TYPE_ROOT_PORT ||
|
|
pd->dev_type == PCIE_TYPE_SWITCH_DNPORT)) {
|
|
edevcount = 1;
|
|
dt_add_property_cells(np, "interrupt-map-mask", 0, 0, 0, 7);
|
|
} else {
|
|
edevcount = 32;
|
|
dt_add_property_cells(np, "interrupt-map-mask",
|
|
0xf800, 0, 0, 7);
|
|
}
|
|
map_size = esize * edevcount * 4 * sizeof(u32);
|
|
map = p = zalloc(map_size);
|
|
if (!map) {
|
|
prerror("Failed to allocate interrupt-map-mask !\n");
|
|
return;
|
|
}
|
|
|
|
for (dev = 0; dev < edevcount; dev++) {
|
|
for (irq = 0; irq < 4; irq++) {
|
|
/* Calculate pin */
|
|
size_t i;
|
|
uint32_t new_irq = (irq + dev + swizzle) % 4;
|
|
|
|
/* PCI address portion */
|
|
*(p++) = cpu_to_be32(dev << (8 + 3));
|
|
*(p++) = 0;
|
|
*(p++) = 0;
|
|
|
|
/* PCI interrupt portion */
|
|
*(p++) = cpu_to_be32(irq + 1);
|
|
|
|
/* Parent phandle */
|
|
*(p++) = cpu_to_be32(lstate->int_parent[new_irq]);
|
|
|
|
/* Parent desc */
|
|
for (i = 0; i < lstate->int_size; i++)
|
|
*(p++) = cpu_to_be32(lstate->int_val[new_irq][i]);
|
|
}
|
|
}
|
|
|
|
dt_add_property(np, "interrupt-map", map, map_size);
|
|
free(map);
|
|
}
|
|
|
|
static void pci_add_loc_code(struct dt_node *np)
|
|
{
|
|
struct dt_node *p;
|
|
const char *lcode = NULL;
|
|
|
|
for (p = np->parent; p; p = p->parent) {
|
|
/* prefer slot-label by default */
|
|
lcode = dt_prop_get_def(p, "ibm,slot-label", NULL);
|
|
if (lcode)
|
|
break;
|
|
|
|
/* otherwise use the fully qualified location code */
|
|
lcode = dt_prop_get_def(p, "ibm,slot-location-code", NULL);
|
|
if (lcode)
|
|
break;
|
|
}
|
|
|
|
if (!lcode)
|
|
lcode = dt_prop_get_def(np, "ibm,slot-location-code", NULL);
|
|
|
|
if (!lcode) {
|
|
/* Fall back to finding a ibm,loc-code */
|
|
for (p = np->parent; p; p = p->parent) {
|
|
lcode = dt_prop_get_def(p, "ibm,loc-code", NULL);
|
|
if (lcode)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!lcode)
|
|
return;
|
|
|
|
dt_add_property_string(np, "ibm,loc-code", lcode);
|
|
}
|
|
|
|
static void pci_print_summary_line(struct phb *phb, struct pci_device *pd,
|
|
struct dt_node *np, u32 rev_class,
|
|
const char *cname)
|
|
{
|
|
const char *label, *dtype, *s;
|
|
#define MAX_SLOTSTR 80
|
|
char slotstr[MAX_SLOTSTR + 1] = { 0, };
|
|
|
|
/* If it's a slot, it has a slot-label */
|
|
label = dt_prop_get_def(np, "ibm,slot-label", NULL);
|
|
if (label) {
|
|
u32 lanes = dt_prop_get_u32_def(np, "ibm,slot-wired-lanes", 0);
|
|
static const char *lanestrs[] = {
|
|
"", " x1", " x2", " x4", " x8", "x16", "x32", "32b", "64b"
|
|
};
|
|
const char *lstr = lanes > PCI_SLOT_WIRED_LANES_PCIX_64 ? "" : lanestrs[lanes];
|
|
snprintf(slotstr, MAX_SLOTSTR, "SLOT=%3s %s", label, lstr);
|
|
/* XXX Add more slot info */
|
|
} else {
|
|
/*
|
|
* No label, ignore downstream switch legs and root complex,
|
|
* Those would essentially be non-populated
|
|
*/
|
|
if (pd->dev_type != PCIE_TYPE_ROOT_PORT &&
|
|
pd->dev_type != PCIE_TYPE_SWITCH_DNPORT) {
|
|
/* It's a mere device, get loc code */
|
|
s = dt_prop_get_def(np, "ibm,loc-code", NULL);
|
|
if (s)
|
|
snprintf(slotstr, MAX_SLOTSTR, "LOC_CODE=%s", s);
|
|
}
|
|
}
|
|
|
|
if (pci_has_cap(pd, PCI_CFG_CAP_ID_EXP, false)) {
|
|
static const char *pcie_types[] = {
|
|
"EP ", "LGCY", "????", "????", "ROOT", "SWUP", "SWDN",
|
|
"ETOX", "XTOE", "RINT", "EVTC" };
|
|
if (pd->dev_type >= ARRAY_SIZE(pcie_types))
|
|
dtype = "????";
|
|
else
|
|
dtype = pcie_types[pd->dev_type];
|
|
} else
|
|
dtype = pd->is_bridge ? "PCIB" : "PCID";
|
|
|
|
if (pd->is_bridge)
|
|
PCINOTICE(phb, pd->bdfn,
|
|
"[%s] %04x %04x R:%02x C:%06x B:%02x..%02x %s\n",
|
|
dtype, PCI_VENDOR_ID(pd->vdid),
|
|
PCI_DEVICE_ID(pd->vdid),
|
|
rev_class & 0xff, rev_class >> 8, pd->secondary_bus,
|
|
pd->subordinate_bus, slotstr);
|
|
else
|
|
PCINOTICE(phb, pd->bdfn,
|
|
"[%s] %04x %04x R:%02x C:%06x (%14s) %s\n",
|
|
dtype, PCI_VENDOR_ID(pd->vdid),
|
|
PCI_DEVICE_ID(pd->vdid),
|
|
rev_class & 0xff, rev_class >> 8, cname, slotstr);
|
|
}
|
|
|
|
static void __noinline 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 dt_node *np;
|
|
const char *cname;
|
|
#define MAX_NAME 256
|
|
char name[MAX_NAME];
|
|
char compat[MAX_NAME];
|
|
uint32_t rev_class;
|
|
uint8_t intpin;
|
|
bool is_pcie;
|
|
|
|
pci_cfg_read32(phb, pd->bdfn, PCI_CFG_REV_ID, &rev_class);
|
|
pci_cfg_read8(phb, pd->bdfn, PCI_CFG_INT_PIN, &intpin);
|
|
is_pcie = pci_has_cap(pd, PCI_CFG_CAP_ID_EXP, false);
|
|
|
|
/*
|
|
* Some IBM PHBs (p7ioc?) have an invalid PCI class code. Linux
|
|
* uses prefers to read the class code from the DT rather than
|
|
* re-reading config space we can hack around it here.
|
|
*/
|
|
if (is_pcie && pd->dev_type == PCIE_TYPE_ROOT_PORT)
|
|
rev_class = (rev_class & 0xff) | 0x6040000;
|
|
cname = pci_class_name(rev_class >> 8);
|
|
|
|
if (PCI_FUNC(pd->bdfn))
|
|
snprintf(name, MAX_NAME - 1, "%s@%x,%x",
|
|
cname, PCI_DEV(pd->bdfn), PCI_FUNC(pd->bdfn));
|
|
else
|
|
snprintf(name, MAX_NAME - 1, "%s@%x",
|
|
cname, PCI_DEV(pd->bdfn));
|
|
pd->dn = np = dt_new(parent_node, name);
|
|
|
|
/*
|
|
* NB: ibm,pci-config-space-type is the PAPR way of indicating the
|
|
* device has a 4KB config space. It's got nothing to do with the
|
|
* standard Type 0/1 config spaces defined by PCI.
|
|
*/
|
|
if (is_pcie ||
|
|
(phb->phb_type == phb_type_npu_v2_opencapi) ||
|
|
(phb->phb_type == phb_type_pau_opencapi)) {
|
|
snprintf(compat, MAX_NAME, "pciex%x,%x",
|
|
PCI_VENDOR_ID(pd->vdid), PCI_DEVICE_ID(pd->vdid));
|
|
dt_add_property_cells(np, "ibm,pci-config-space-type", 1);
|
|
} else {
|
|
snprintf(compat, MAX_NAME, "pci%x,%x",
|
|
PCI_VENDOR_ID(pd->vdid), PCI_DEVICE_ID(pd->vdid));
|
|
dt_add_property_cells(np, "ibm,pci-config-space-type", 0);
|
|
}
|
|
dt_add_property_cells(np, "class-code", rev_class >> 8);
|
|
dt_add_property_cells(np, "revision-id", rev_class & 0xff);
|
|
dt_add_property_cells(np, "vendor-id", PCI_VENDOR_ID(pd->vdid));
|
|
dt_add_property_cells(np, "device-id", PCI_DEVICE_ID(pd->vdid));
|
|
if (intpin)
|
|
dt_add_property_cells(np, "interrupts", intpin);
|
|
|
|
pci_handle_quirk(phb, pd);
|
|
|
|
/* XXX FIXME: Add a few missing ones such as
|
|
*
|
|
* - devsel-speed (!express)
|
|
* - max-latency
|
|
* - min-grant
|
|
* - subsystem-id
|
|
* - subsystem-vendor-id
|
|
* - ...
|
|
*/
|
|
|
|
/* Add slot properties if needed and iff this is a bridge */
|
|
if (pd->slot)
|
|
pci_slot_add_dt_properties(pd->slot, np);
|
|
|
|
/*
|
|
* Use the phb base location code for root ports if the platform
|
|
* doesn't provide one via slot->add_properties() operation.
|
|
*/
|
|
if (pd->dev_type == PCIE_TYPE_ROOT_PORT && phb->base_loc_code &&
|
|
!dt_has_node_property(np, "ibm,slot-location-code", NULL))
|
|
dt_add_property_string(np, "ibm,slot-location-code",
|
|
phb->base_loc_code);
|
|
|
|
/* Make up location code */
|
|
if (platform.pci_add_loc_code)
|
|
platform.pci_add_loc_code(np, pd);
|
|
else
|
|
pci_add_loc_code(np);
|
|
|
|
/* XXX FIXME: We don't look for BARs, we only put the config space
|
|
* entry in the "reg" property. That's enough for Linux and we might
|
|
* even want to make this legit in future ePAPR
|
|
*/
|
|
dt_add_property_cells(np, "reg", pd->bdfn << 8, 0, 0, 0, 0);
|
|
|
|
/* Print summary info about the device */
|
|
pci_print_summary_line(phb, pd, np, rev_class, cname);
|
|
if (!pd->is_bridge)
|
|
return;
|
|
|
|
dt_add_property_cells(np, "#address-cells", 3);
|
|
dt_add_property_cells(np, "#size-cells", 2);
|
|
dt_add_property_cells(np, "#interrupt-cells", 1);
|
|
|
|
/* We want "device_type" for bridges */
|
|
if (is_pcie)
|
|
dt_add_property_string(np, "device_type", "pciex");
|
|
else
|
|
dt_add_property_string(np, "device_type", "pci");
|
|
|
|
/* Update the current interrupt swizzling level based on our own
|
|
* device number
|
|
*/
|
|
swizzle = (swizzle + PCI_DEV(pd->bdfn)) & 3;
|
|
|
|
/* We generate a standard-swizzling interrupt map. This is pretty
|
|
* big, we *could* try to be smarter for things that aren't hotplug
|
|
* slots at least and only populate those entries for which there's
|
|
* an actual children (especially on PCI Express), but for now that
|
|
* will do
|
|
*/
|
|
pci_std_swizzle_irq_map(np, pd, lstate, swizzle);
|
|
|
|
/* Parts of the OF address translation in the kernel will fail to
|
|
* correctly translate a PCI address if translating a 1:1 mapping
|
|
* (ie. an empty ranges property).
|
|
* Instead add a ranges property that explicitly translates 1:1.
|
|
*/
|
|
dt_add_property_cells(np, "ranges",
|
|
/* 64-bit direct mapping. We know the bridges
|
|
* don't cover the entire address space so
|
|
* use 0xf00... as a good compromise. */
|
|
0x02000000, 0x0, 0x0,
|
|
0x02000000, 0x0, 0x0,
|
|
0xf0000000, 0x0);
|
|
}
|
|
|
|
void __noinline 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_device *pd;
|
|
|
|
/* Add all child devices */
|
|
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_do_jobs(void (*fn)(void *))
|
|
{
|
|
struct cpu_job **jobs;
|
|
int i;
|
|
|
|
jobs = zalloc(sizeof(struct cpu_job *) * ARRAY_SIZE(phbs));
|
|
assert(jobs);
|
|
for (i = 0; i < ARRAY_SIZE(phbs); i++) {
|
|
if (!phbs[i]) {
|
|
jobs[i] = NULL;
|
|
continue;
|
|
}
|
|
|
|
jobs[i] = __cpu_queue_job(NULL, phbs[i]->dt_node->name,
|
|
fn, phbs[i], false);
|
|
assert(jobs[i]);
|
|
|
|
}
|
|
|
|
/* If no secondary CPUs, do everything sync */
|
|
cpu_process_local_jobs();
|
|
|
|
/* Wait until all tasks are done */
|
|
for (i = 0; i < ARRAY_SIZE(phbs); i++) {
|
|
if (!jobs[i])
|
|
continue;
|
|
|
|
cpu_wait_job(jobs[i], true);
|
|
}
|
|
free(jobs);
|
|
}
|
|
|
|
static void __pci_init_slots(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
/* Some PHBs may need that long to debounce the presence detect
|
|
* after HW initialization.
|
|
*/
|
|
for (i = 0; i < ARRAY_SIZE(phbs); i++) {
|
|
if (phbs[i]) {
|
|
time_wait_ms(20);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (platform.pre_pci_fixup)
|
|
platform.pre_pci_fixup();
|
|
|
|
prlog(PR_NOTICE, "PCI: Resetting PHBs and training links...\n");
|
|
pci_do_jobs(pci_reset_phb);
|
|
|
|
prlog(PR_NOTICE, "PCI: Probing slots...\n");
|
|
pci_do_jobs(pci_scan_phb);
|
|
|
|
if (platform.pci_probe_complete)
|
|
platform.pci_probe_complete();
|
|
|
|
prlog(PR_NOTICE, "PCI Summary:\n");
|
|
|
|
for (i = 0; i < ARRAY_SIZE(phbs); i++) {
|
|
if (!phbs[i])
|
|
continue;
|
|
|
|
pci_add_device_nodes(phbs[i], &phbs[i]->devices,
|
|
phbs[i]->dt_node, &phbs[i]->lstate, 0);
|
|
}
|
|
|
|
/* PHB final fixup */
|
|
for (i = 0; i < ARRAY_SIZE(phbs); i++) {
|
|
if (!phbs[i] || !phbs[i]->ops || !phbs[i]->ops->phb_final_fixup)
|
|
continue;
|
|
|
|
phbs[i]->ops->phb_final_fixup(phbs[i]);
|
|
}
|
|
}
|
|
|
|
static void __pci_reset(struct list_head *list)
|
|
{
|
|
struct pci_device *pd;
|
|
struct pci_cfg_reg_filter *pcrf;
|
|
int i;
|
|
|
|
while ((pd = list_pop(list, struct pci_device, link)) != NULL) {
|
|
__pci_reset(&pd->children);
|
|
dt_free(pd->dn);
|
|
free(pd->slot);
|
|
while((pcrf = list_pop(&pd->pcrf, struct pci_cfg_reg_filter, link)) != NULL) {
|
|
free(pcrf);
|
|
}
|
|
for(i=0; i < 64; i++)
|
|
if (pd->cap[i].free_func)
|
|
pd->cap[i].free_func(pd->cap[i].data);
|
|
free(pd);
|
|
}
|
|
}
|
|
|
|
int64_t pci_reset(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
prlog(PR_NOTICE, "PCI: Clearing all devices...\n");
|
|
|
|
for (i = 0; i < ARRAY_SIZE(phbs); i++) {
|
|
struct phb *phb = phbs[i];
|
|
if (!phb)
|
|
continue;
|
|
__pci_reset(&phb->devices);
|
|
|
|
pci_slot_set_state(phb->slot, PCI_SLOT_STATE_CRESET_START);
|
|
}
|
|
|
|
/* Do init and discovery of PCI slots in parallel */
|
|
__pci_init_slots();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void pci_init_slots(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(phbs); i++) {
|
|
struct phb *phb = phbs[i];
|
|
if (!phb)
|
|
continue;
|
|
pci_slot_set_state(phb->slot, PCI_SLOT_STATE_FRESET_POWER_OFF);
|
|
}
|
|
__pci_init_slots();
|
|
}
|
|
|
|
/*
|
|
* Complete iteration on current level before switching to
|
|
* child level, which is the proper order for restoring
|
|
* PCI bus range on bridges.
|
|
*/
|
|
static struct pci_device *__pci_walk_dev(struct phb *phb,
|
|
struct list_head *l,
|
|
int (*cb)(struct phb *,
|
|
struct pci_device *,
|
|
void *),
|
|
void *userdata)
|
|
{
|
|
struct pci_device *pd, *child;
|
|
|
|
if (list_empty(l))
|
|
return NULL;
|
|
|
|
list_for_each(l, pd, link) {
|
|
if (cb && cb(phb, pd, userdata))
|
|
return pd;
|
|
}
|
|
|
|
list_for_each(l, pd, link) {
|
|
child = __pci_walk_dev(phb, &pd->children, cb, userdata);
|
|
if (child)
|
|
return child;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct pci_device *pci_walk_dev(struct phb *phb,
|
|
struct pci_device *pd,
|
|
int (*cb)(struct phb *,
|
|
struct pci_device *,
|
|
void *),
|
|
void *userdata)
|
|
{
|
|
if (pd)
|
|
return __pci_walk_dev(phb, &pd->children, cb, userdata);
|
|
|
|
return __pci_walk_dev(phb, &phb->devices, cb, userdata);
|
|
}
|
|
|
|
static int __pci_find_dev(struct phb *phb,
|
|
struct pci_device *pd, void *userdata)
|
|
{
|
|
uint16_t bdfn = *((uint16_t *)userdata);
|
|
|
|
if (!phb || !pd)
|
|
return 0;
|
|
|
|
if (pd->bdfn == bdfn)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct pci_device *pci_find_dev(struct phb *phb, uint16_t bdfn)
|
|
{
|
|
return pci_walk_dev(phb, NULL, __pci_find_dev, &bdfn);
|
|
}
|
|
|
|
static int __pci_restore_bridge_buses(struct phb *phb,
|
|
struct pci_device *pd,
|
|
void *data __unused)
|
|
{
|
|
uint32_t vdid;
|
|
|
|
/* If the device is behind a switch, wait for the switch */
|
|
if (!pd->is_vf && !(pd->bdfn & 7) && pd->parent != NULL &&
|
|
pd->parent->dev_type == PCIE_TYPE_SWITCH_DNPORT) {
|
|
if (!pci_bridge_wait_link(phb, pd->parent, true)) {
|
|
PCIERR(phb, pd->bdfn, "Timeout waiting for switch\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Wait for config space to stop returning CRS */
|
|
if (!pci_wait_crs(phb, pd->bdfn, &vdid))
|
|
return -1;
|
|
|
|
/* Make all devices below a bridge "re-capture" the bdfn */
|
|
pci_cfg_write32(phb, pd->bdfn, PCI_CFG_VENDOR_ID, vdid);
|
|
|
|
if (!pd->is_bridge)
|
|
return 0;
|
|
|
|
pci_cfg_write8(phb, pd->bdfn, PCI_CFG_PRIMARY_BUS,
|
|
pd->primary_bus);
|
|
pci_cfg_write8(phb, pd->bdfn, PCI_CFG_SECONDARY_BUS,
|
|
pd->secondary_bus);
|
|
pci_cfg_write8(phb, pd->bdfn, PCI_CFG_SUBORDINATE_BUS,
|
|
pd->subordinate_bus);
|
|
return 0;
|
|
}
|
|
|
|
void pci_restore_bridge_buses(struct phb *phb, struct pci_device *pd)
|
|
{
|
|
pci_walk_dev(phb, pd, __pci_restore_bridge_buses, NULL);
|
|
}
|
|
|
|
void pci_restore_slot_bus_configs(struct pci_slot *slot)
|
|
{
|
|
/*
|
|
* We might lose the bus numbers during the reset operation
|
|
* and we need to restore them. Otherwise, some adapters (e.g.
|
|
* IPR) can't be probed properly by the kernel. We don't need
|
|
* to restore bus numbers for every kind of reset, however,
|
|
* it's not harmful to always restore the bus numbers, which
|
|
* simplifies the logic.
|
|
*/
|
|
pci_restore_bridge_buses(slot->phb, slot->pd);
|
|
if (slot->phb->ops->device_init)
|
|
pci_walk_dev(slot->phb, slot->pd,
|
|
slot->phb->ops->device_init, NULL);
|
|
}
|
|
|
|
struct pci_cfg_reg_filter *pci_find_cfg_reg_filter(struct pci_device *pd,
|
|
uint32_t start, uint32_t len)
|
|
{
|
|
struct pci_cfg_reg_filter *pcrf;
|
|
|
|
/* Check on the cached range, which contains holes */
|
|
if ((start + len) <= pd->pcrf_start ||
|
|
pd->pcrf_end <= start)
|
|
return NULL;
|
|
|
|
list_for_each(&pd->pcrf, pcrf, link) {
|
|
if (start >= pcrf->start &&
|
|
(start + len) <= (pcrf->start + pcrf->len))
|
|
return pcrf;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool pci_device_has_cfg_reg_filters(struct phb *phb, uint16_t bdfn)
|
|
{
|
|
return bitmap_tst_bit(*phb->filter_map, bdfn);
|
|
}
|
|
|
|
int64_t pci_handle_cfg_filters(struct phb *phb, uint32_t bdfn,
|
|
uint32_t offset, uint32_t len,
|
|
uint32_t *data, bool write)
|
|
{
|
|
struct pci_device *pd;
|
|
struct pci_cfg_reg_filter *pcrf;
|
|
uint32_t flags;
|
|
|
|
if (!pci_device_has_cfg_reg_filters(phb, bdfn))
|
|
return OPAL_PARTIAL;
|
|
pd = pci_find_dev(phb, bdfn);
|
|
pcrf = pd ? pci_find_cfg_reg_filter(pd, offset, len) : NULL;
|
|
if (!pcrf || !pcrf->func)
|
|
return OPAL_PARTIAL;
|
|
|
|
flags = write ? PCI_REG_FLAG_WRITE : PCI_REG_FLAG_READ;
|
|
if ((pcrf->flags & flags) != flags)
|
|
return OPAL_PARTIAL;
|
|
|
|
return pcrf->func(pd, pcrf, offset, len, data, write);
|
|
}
|
|
|
|
struct pci_cfg_reg_filter *pci_add_cfg_reg_filter(struct pci_device *pd,
|
|
uint32_t start, uint32_t len,
|
|
uint32_t flags,
|
|
pci_cfg_reg_func func)
|
|
{
|
|
struct pci_cfg_reg_filter *pcrf;
|
|
|
|
pcrf = pci_find_cfg_reg_filter(pd, start, len);
|
|
if (pcrf)
|
|
return pcrf;
|
|
|
|
pcrf = zalloc(sizeof(*pcrf) + ((len + 0x4) & ~0x3));
|
|
if (!pcrf)
|
|
return NULL;
|
|
|
|
/* Don't validate the flags so that the private flags
|
|
* can be supported for debugging purpose.
|
|
*/
|
|
pcrf->flags = flags;
|
|
pcrf->start = start;
|
|
pcrf->len = len;
|
|
pcrf->func = func;
|
|
pcrf->data = (uint8_t *)(pcrf + 1);
|
|
|
|
if (start < pd->pcrf_start)
|
|
pd->pcrf_start = start;
|
|
if (pd->pcrf_end < (start + len))
|
|
pd->pcrf_end = start + len;
|
|
list_add_tail(&pd->pcrf, &pcrf->link);
|
|
bitmap_set_bit(*pd->phb->filter_map, pd->bdfn);
|
|
|
|
return pcrf;
|
|
}
|