blob: 9b34a23a11787cdd1d2934f97594e9bbb73e6080 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/* Copyright 2013-2019 IBM Corp. */
#define pr_fmt(fmt) "LXVPD: " fmt
#include <skiboot.h>
#include <device.h>
#include <vpd.h>
#include <pci.h>
#include <pci-cfg.h>
#include <pci-slot.h>
#include "lxvpd.h"
/*
* Currently, the lxvpd PCI slot struct is shared by multiple
* platforms (Apollo and Firenze), but each slot still has
* platform specific features. In order for unified data structs,
* "struct lxvpd_slot" is expected to be embedded in platform
* PCI slot struct. "entry_size" indicates the size of platform
* specific PCI slot instance.
*/
struct lxvpd_pci_slot_data {
uint8_t num_slots;
int32_t entry_size; /* Size of platform PCI slot */
void *slots; /* Data of platform PCI slots */
};
static bool lxvpd_supported_slot(struct phb *phb, struct pci_device *pd)
{
/* PHB should always be valid */
if (!phb)
return false;
/* We expect platform slot for root complex */
if (!pd)
return true;
/* We support the root complex at the top level */
if (pd->dev_type == PCIE_TYPE_ROOT_PORT && !pd->parent)
return true;
/* We support an upstream switch port below the root complex */
if (pd->dev_type == PCIE_TYPE_SWITCH_UPPORT &&
pd->parent && pd->parent->dev_type == PCIE_TYPE_ROOT_PORT &&
!pd->parent->parent)
return true;
/* We support a downstream switch port below an upstream port
* below the root complex
*/
if (pd->dev_type == PCIE_TYPE_SWITCH_DNPORT &&
pd->parent && pd->parent->dev_type == PCIE_TYPE_SWITCH_UPPORT &&
pd->parent->parent &&
pd->parent->parent->dev_type == PCIE_TYPE_ROOT_PORT &&
!pd->parent->parent->parent)
return true;
/* Anything else, bail */
return false;
}
void *lxvpd_get_slot(struct pci_slot *slot)
{
struct phb *phb = slot->phb;
struct pci_device *pd = slot->pd;
struct lxvpd_pci_slot_data *sdata = phb->platform_data;
struct lxvpd_pci_slot *s = NULL;
uint8_t slot_num = pd ? PCI_DEV(pd->bdfn) : 0xff;
bool is_phb = (pd && pd->parent) ? false : true;
uint8_t index;
/* Check if we have slot info */
if (!sdata) {
prlog(PR_DEBUG, "PHB%04x not have VPD data\n",
phb->opal_id);
return NULL;
}
/* Platform slot attached ? */
s = slot->data;
if (s) {
prlog(PR_DEBUG, "Slot %016llx had platform data [%s]\n",
slot->id, s->label);
return s;
}
/*
* This code only handles PHBs and PCIe switches at the
* top level. We do not handle any other switch nor any
* other type of PCI/PCI-X bridge. Generally, we have
* more strict rules to support slot than PCI core.
*/
if (!lxvpd_supported_slot(phb, pd)) {
prlog(PR_DEBUG, "Slot %016llx not supported\n",
slot->id);
return NULL;
}
/* Iterate the platform slot array */
for (index = 0; index < sdata->num_slots; index++) {
s = sdata->slots + (index * sdata->entry_size);
/* Match PHB with switch_id == 0 */
if (is_phb && s->switch_id == 0) {
slot->data = s;
s->pci_slot = slot;
prlog(PR_DEBUG, "Found [%s] for PHB slot %016llx\n",
s->label, slot->id);
return s;
}
/* Match downstream switch port with switch_id != 0 */
if (!is_phb && s->switch_id != 0 && !s->upstream_port &&
s->dev_id == slot_num) {
slot->data = s;
s->pci_slot = slot;
prlog(PR_DEBUG, "Found [%s] for slot %016llx\n",
s->label, slot->id);
return s;
}
}
prlog(PR_DEBUG, "No data found for %sslot %016llx\n",
is_phb ? "PHB " : " ", slot->id);
return NULL;
}
void lxvpd_extract_info(struct pci_slot *slot, struct lxvpd_pci_slot *s)
{
slot->pluggable = s->pluggable ? 1 : 0;
slot->power_ctl = s->power_ctl ? 1 : 0;
slot->power_led_ctl = s->pwr_led_ctl;
slot->attn_led_ctl = s->attn_led_ctl;
slot->connector_type = s->connector_type;
slot->card_desc = s->card_desc;
slot->card_mech = s->card_mech;
slot->wired_lanes = s->wired_lanes;
prlog(PR_DEBUG, "[%s]: pluggable: %d power_ctrl: %d\n",
s->label, (int) s->pluggable, (int) s->power_ctl);
}
static struct lxvpd_pci_slot_data *lxvpd_alloc_slots(struct phb *phb,
uint8_t count,
uint32_t slot_size)
{
struct lxvpd_pci_slot_data *sdata;
sdata = zalloc(sizeof(struct lxvpd_pci_slot_data) + count * slot_size);
assert(sdata);
sdata->num_slots = count;
sdata->entry_size = slot_size;
sdata->slots = sdata + 1;
phb->platform_data = sdata;
return sdata;
}
static void lxvpd_format_label(char *dst, const char *src, size_t len)
{
int i;
memcpy(dst, src, len);
/* Remove blank suffix */
for (i = strlen(dst) - 1; i >= 0; i--) {
if (dst[i] != ' ')
break;
dst[i] = 0;
}
}
static void lxvpd_parse_1004_map(struct phb *phb,
const uint8_t *sm,
uint8_t size,
uint32_t slot_size)
{
struct lxvpd_pci_slot_data *sdata;
struct lxvpd_pci_slot *s;
const struct pci_slot_entry_1004 *entry;
uint8_t num_slots, slot;
num_slots = (size / sizeof(struct pci_slot_entry_1004));
sdata = lxvpd_alloc_slots(phb, num_slots, slot_size);
/* Iterate through the entries in the keyword */
entry = (const struct pci_slot_entry_1004 *)sm;
for (slot = 0; slot < num_slots; slot++, entry++) {
s = sdata->slots + slot * sdata->entry_size;
/* Figure out PCI slot info */
lxvpd_format_label(s->label, entry->label, 3);
s->slot_index = entry->slot_index;
s->switch_id = entry->pba >> 4;
s->vswitch_id = entry->pba & 0xf;
s->dev_id = entry->sba;
s->pluggable = ((entry->p0.byte & 0x20) == 0);
s->power_ctl = !!(entry->p0.byte & 0x40);
s->bus_clock = entry->p2.bus_clock - 4;
s->connector_type = entry->p2.connector_type - 5;
s->card_desc = entry->p3.byte >> 6;
if (entry->p3.byte < 0xc0)
s->card_desc -= 4;
s->card_mech = (entry->p3.byte >> 4) & 0x3;
s->pwr_led_ctl = (entry->p3.byte & 0xf) >> 2;
s->attn_led_ctl = entry->p3.byte & 0x3;
switch(entry->p1.wired_lanes) {
case 1: s->wired_lanes = PCI_SLOT_WIRED_LANES_PCIX_32; break;
case 2: /* fall through */
case 3: s->wired_lanes = PCI_SLOT_WIRED_LANES_PCIX_64; break;
case 4: s->wired_lanes = PCI_SLOT_WIRED_LANES_PCIE_X1; break;
case 5: s->wired_lanes = PCI_SLOT_WIRED_LANES_PCIE_X4; break;
case 6: s->wired_lanes = PCI_SLOT_WIRED_LANES_PCIE_X8; break;
case 7: s->wired_lanes = PCI_SLOT_WIRED_LANES_PCIE_X16; break;
default:
s->wired_lanes = PCI_SLOT_WIRED_LANES_UNKNOWN;
}
prlog(PR_DEBUG, "1004 Platform data [%s] %02x %02x on PHB%04x\n",
s->label, s->switch_id, s->dev_id, phb->opal_id);
}
}
static void lxvpd_parse_1005_map(struct phb *phb,
const uint8_t *sm,
uint8_t size,
uint32_t slot_size)
{
struct lxvpd_pci_slot_data *sdata;
struct lxvpd_pci_slot *s;
const struct pci_slot_entry_1005 *entry;
uint8_t num_slots, slot;
num_slots = (size / sizeof(struct pci_slot_entry_1005));
sdata = lxvpd_alloc_slots(phb, num_slots, slot_size);
/* Iterate through the entries in the keyword */
entry = (const struct pci_slot_entry_1005 *)sm;
for (slot = 0; slot < num_slots; slot++, entry++) {
s = sdata->slots + slot * sdata->entry_size;
/* Put slot info into pci device structure */
lxvpd_format_label(s->label, entry->label, 8);
s->slot_index = entry->slot_index;
s->switch_id = entry->pba >> 4;
s->vswitch_id = entry->pba & 0xf;
s->dev_id = entry->switch_device_id;
s->pluggable = (entry->p0.pluggable == 0);
s->power_ctl = entry->p0.power_ctl;
s->upstream_port = entry->p0.upstream_port;
s->bus_clock = entry->p2.bus_clock;
s->connector_type = entry->p2.connector_type;
s->card_desc = entry->p3.byte >> 6;
s->card_mech = (entry->p3.byte >> 4) & 0x3;
s->pwr_led_ctl = (entry->p3.byte & 0xf) >> 2;
s->attn_led_ctl = entry->p3.byte & 0x3;
s->wired_lanes = entry->p1.wired_lanes;
if (s->wired_lanes > PCI_SLOT_WIRED_LANES_PCIE_X32)
s->wired_lanes = PCI_SLOT_WIRED_LANES_UNKNOWN;
prlog(PR_DEBUG, "1005 Platform data [%s] %02x %02x %s on PHB%04x\n",
s->label, s->switch_id, s->dev_id,
s->upstream_port ? "upstream" : "downstream",
phb->opal_id);
}
}
void lxvpd_process_slot_entries(struct phb *phb,
struct dt_node *node,
uint8_t chip_id,
uint8_t index,
uint32_t slot_size)
{
const void *lxvpd;
const uint8_t *pr_rec, *pr_end, *sm;
size_t lxvpd_size, pr_size;
const beint16_t *mf = NULL;
char record[5] = "PR00";
uint8_t mf_sz, sm_sz;
bool found = false;
record[2] += chip_id;
record[3] += index;
record[4] = 0;
/* Get LX VPD pointer */
lxvpd = dt_prop_get_def_size(node, "ibm,io-vpd", NULL, &lxvpd_size);
if (!lxvpd) {
prlog(PR_WARNING, "No data found for PHB%04x %s\n",
phb->opal_id, record);
return;
}
pr_rec = vpd_find_record(lxvpd, lxvpd_size, record, &pr_size);
if (!pr_rec) {
prlog(PR_WARNING, "Record %s not found on PHB%04x\n",
record, phb->opal_id);
return;
}
/* As long as there's still something in the PRxy record */
prlog(PR_DEBUG, "PHB%04x record %s has %ld bytes\n",
phb->opal_id, record, pr_size);
pr_end = pr_rec + pr_size;
while (pr_rec < pr_end) {
pr_size = pr_end - pr_rec;
/* Find the next MF keyword */
mf = vpd_find_keyword(pr_rec, pr_size, "MF", &mf_sz);
/* And the corresponding SM */
sm = vpd_find_keyword(pr_rec, pr_size, "SM", &sm_sz);
if (!mf || !sm) {
if (!found)
prlog(PR_WARNING, "Slot Map keyword %s not found\n",
record);
return;
}
prlog(PR_DEBUG, "Found 0x%04x map...\n", be16_to_cpu(*mf));
switch (be16_to_cpu(*mf)) {
case 0x1004:
lxvpd_parse_1004_map(phb, sm + 1, sm_sz - 1, slot_size);
found = true;
break;
case 0x1005:
lxvpd_parse_1005_map(phb, sm + 1, sm_sz - 1, slot_size);
found = true;
break;
/* Add support for 0x1006 maps ... */
}
pr_rec = sm + sm_sz;
}
}
void lxvpd_add_slot_properties(struct pci_slot *slot,
struct dt_node *np)
{
struct phb *phb = slot->phb;
struct lxvpd_pci_slot *s = slot->data;
char loc_code[LOC_CODE_SIZE];
size_t base_loc_code_len, slot_label_len;
/* Check if we have platform specific slot */
if (!s || !np)
return;
/* Check PHB base location code */
if (!phb->base_loc_code)
return;
/* Check location length is valid */
base_loc_code_len = strlen(phb->base_loc_code);
slot_label_len = strlen(s->label);
if ((base_loc_code_len + slot_label_len + 1) >= LOC_CODE_SIZE)
return;
/* Location code */
strcpy(loc_code, phb->base_loc_code);
strcat(loc_code, "-");
strcat(loc_code, s->label);
dt_add_property(np, "ibm,slot-location-code",
loc_code, strlen(loc_code) + 1);
dt_add_property_string(np, "ibm,slot-label",
s->label);
}