aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/platforms/ibm-fsp/lxvpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/platforms/ibm-fsp/lxvpd.c')
-rw-r--r--roms/skiboot/platforms/ibm-fsp/lxvpd.c371
1 files changed, 371 insertions, 0 deletions
diff --git a/roms/skiboot/platforms/ibm-fsp/lxvpd.c b/roms/skiboot/platforms/ibm-fsp/lxvpd.c
new file mode 100644
index 000000000..9b34a23a1
--- /dev/null
+++ b/roms/skiboot/platforms/ibm-fsp/lxvpd.c
@@ -0,0 +1,371 @@
+// 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);
+}