/* Copyright 2013-2014 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. */ #define pr_fmt(fmt) "LXVPD: " fmt #include #include #include #include #include #include #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 ? ((pd->bdfn >> 3) & 0x1f) : 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 switch port with switch_id != 0 */ if (!is_phb && s->switch_id != 0 && 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; } 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->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 on PHB%04x\n", s->label, s->switch_id, s->dev_id, 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 uint16_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", *mf); switch (*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); }