diff options
Diffstat (limited to 'roms/skiboot/platforms/ibm-fsp')
-rw-r--r-- | roms/skiboot/platforms/ibm-fsp/Makefile.inc | 9 | ||||
-rw-r--r-- | roms/skiboot/platforms/ibm-fsp/common.c | 279 | ||||
-rw-r--r-- | roms/skiboot/platforms/ibm-fsp/firenze-pci.c | 1044 | ||||
-rw-r--r-- | roms/skiboot/platforms/ibm-fsp/firenze.c | 223 | ||||
-rw-r--r-- | roms/skiboot/platforms/ibm-fsp/fsp-vpd.c | 152 | ||||
-rw-r--r-- | roms/skiboot/platforms/ibm-fsp/hostservices.c | 912 | ||||
-rw-r--r-- | roms/skiboot/platforms/ibm-fsp/ibm-fsp.h | 45 | ||||
-rw-r--r-- | roms/skiboot/platforms/ibm-fsp/lxvpd.c | 371 | ||||
-rw-r--r-- | roms/skiboot/platforms/ibm-fsp/lxvpd.h | 164 | ||||
-rw-r--r-- | roms/skiboot/platforms/ibm-fsp/zz.c | 213 |
10 files changed, 3412 insertions, 0 deletions
diff --git a/roms/skiboot/platforms/ibm-fsp/Makefile.inc b/roms/skiboot/platforms/ibm-fsp/Makefile.inc new file mode 100644 index 000000000..8883f09c1 --- /dev/null +++ b/roms/skiboot/platforms/ibm-fsp/Makefile.inc @@ -0,0 +1,9 @@ +SUBDIRS += $(PLATDIR)/ibm-fsp + +IBM_FSP_OBJS = common.o lxvpd.o hostservices.o fsp-vpd.o \ + firenze.o firenze-pci.o zz.o +IBM_FSP = $(PLATDIR)/ibm-fsp/built-in.a + +ifeq ($(CONFIG_FSP),1) +$(IBM_FSP): $(IBM_FSP_OBJS:%=$(PLATDIR)/ibm-fsp/%) +endif diff --git a/roms/skiboot/platforms/ibm-fsp/common.c b/roms/skiboot/platforms/ibm-fsp/common.c new file mode 100644 index 000000000..4a723b25b --- /dev/null +++ b/roms/skiboot/platforms/ibm-fsp/common.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + + +#include <skiboot.h> +#include <fsp.h> +#include <fsp-sysparam.h> +#include <opal.h> +#include <console.h> +#include <hostservices.h> +#include <ipmi.h> +#include <debug_descriptor.h> +#include <occ.h> + +#include "ibm-fsp.h" + +static void map_debug_areas(void) +{ + uint64_t t, i; + + /* Our memcons is in a section of its own and already + * aligned to 4K. The buffers are mapped as a whole + */ + fsp_tce_map(PSI_DMA_MEMCONS, &memcons, 0x1000); + fsp_tce_map(PSI_DMA_LOG_BUF, (void*)INMEM_CON_START, INMEM_CON_LEN); + + debug_descriptor.memcons_tce = cpu_to_be32(PSI_DMA_MEMCONS); + t = be64_to_cpu(memcons.obuf_phys) - INMEM_CON_START + PSI_DMA_LOG_BUF; + debug_descriptor.memcons_obuf_tce = cpu_to_be32(t); + t = be64_to_cpu(memcons.ibuf_phys) - INMEM_CON_START + PSI_DMA_LOG_BUF; + debug_descriptor.memcons_ibuf_tce = cpu_to_be32(t); + + t = PSI_DMA_TRACE_BASE; + for (i = 0; i < be32_to_cpu(debug_descriptor.num_traces); i++) { + /* + * Trace buffers are misaligned by 0x10 due to the lock + * in the trace structure, and their size is also not + * completely aligned. (They are allocated so that with + * the lock included, they do cover entire multiple of + * a 4K page however). + * + * This means we have to map the lock into the TCEs and + * align everything. Not a huge deal but needs to be + * taken into account. + * + * Note: Maybe we should map them read-only... + */ + uint64_t tstart, tend, toff, tsize; + uint64_t trace_phys = be64_to_cpu(debug_descriptor.trace_phys[i]); + uint32_t trace_size = be32_to_cpu(debug_descriptor.trace_size[i]); + + tstart = ALIGN_DOWN(trace_phys, 0x1000); + tend = ALIGN_UP(trace_phys + trace_size, 0x1000); + toff = trace_phys - tstart; + tsize = tend - tstart; + + fsp_tce_map(t, (void *)tstart, tsize); + debug_descriptor.trace_tce[i] = cpu_to_be32(t + toff); + t += tsize; + } +} + + +void ibm_fsp_init(void) +{ + /* Early initializations of the FSP interface */ + fsp_init(); + map_debug_areas(); + fsp_sysparam_init(); + + /* Get ready to receive E0 class messages. We need to respond + * to some of these for the init sequence to make forward progress + */ + fsp_console_preinit(); + + /* Get ready to receive OCC related messages */ + occ_fsp_init(); + + /* Get ready to receive Memory [Un]corretable Error messages. */ + fsp_memory_err_init(); + + /* Initialize elog access */ + fsp_elog_read_init(); + fsp_elog_write_init(); + + /* Initiate dump service */ + fsp_dump_init(); + + /* Start FSP/HV state controller & perform OPL */ + fsp_opl(); + + /* Preload hostservices lids */ + hservices_lid_preload(); + + /* Initialize SP attention area */ + fsp_attn_init(); + + /* Initialize monitoring of TOD topology change event notification */ + fsp_chiptod_init(); + + /* Send MDST table notification to FSP */ + op_display(OP_LOG, OP_MOD_INIT, 0x0000); + fsp_mdst_table_init(); + + /* Initialize the panel */ + op_display(OP_LOG, OP_MOD_INIT, 0x0001); + fsp_oppanel_init(); + + /* Start the surveillance process */ + op_display(OP_LOG, OP_MOD_INIT, 0x0002); + fsp_init_surveillance(); + + /* IPMI */ + fsp_ipmi_init(); + ipmi_opal_init(); + + /* Initialize sensor access */ + op_display(OP_LOG, OP_MOD_INIT, 0x0003); + fsp_init_sensor(); + + /* LED */ + op_display(OP_LOG, OP_MOD_INIT, 0x0004); + fsp_led_init(); + + /* Monitor for DIAG events */ + op_display(OP_LOG, OP_MOD_INIT, 0x0005); + fsp_init_diag(); + + /* Finish initializing the console */ + op_display(OP_LOG, OP_MOD_INIT, 0x0006); + fsp_console_init(); + + /* Read our initial RTC value */ + op_display(OP_LOG, OP_MOD_INIT, 0x0008); + fsp_rtc_init(); + + /* Initialize code update access */ + op_display(OP_LOG, OP_MOD_INIT, 0x0009); + fsp_code_update_init(); + + /* EPOW */ + op_display(OP_LOG, OP_MOD_INIT, 0x000A); + fsp_epow_init(); + + /* EPOW */ + op_display(OP_LOG, OP_MOD_INIT, 0x000B); + fsp_dpo_init(); + + /* Setup console */ + if (fsp_present()) + fsp_console_add_nodes(); + + if (proc_gen >= proc_gen_p9) + prd_init(); + + preload_io_vpd(); +} + +void ibm_fsp_finalise_dt(bool is_reboot) +{ + if (is_reboot) + return; + + /* + * LED related SPCN commands might take a while to + * complete. Call this as late as possible to + * ensure we have all the LED information. + */ + create_led_device_nodes(); + + /* + * OCC takes few secs to boot. Call this as late as + * as possible to avoid delay. + */ + occ_pstates_init(); + + /* Wait for FW VPD data read to complete */ + fsp_code_update_wait_vpd(true); + + fsp_console_select_stdout(); +} + +void ibm_fsp_exit(void) +{ + op_panel_disable_src_echo(); + + /* Clear SRCs on the op-panel when Linux starts */ + op_panel_clear_src(); +} + +int64_t ibm_fsp_cec_reboot(void) +{ + uint32_t cmd = FSP_CMD_REBOOT; + + if (!fsp_present()) + return OPAL_UNSUPPORTED; + + /* Flash new firmware */ + if (fsp_flash_term_hook && + fsp_flash_term_hook() == OPAL_SUCCESS) + cmd = FSP_CMD_DEEP_REBOOT; + + /* Clear flash hook */ + fsp_flash_term_hook = NULL; + + printf("FSP: Sending 0x%02x reboot command to FSP...\n", cmd); + + /* If that failed, talk to the FSP */ + if (fsp_sync_msg(fsp_mkmsg(cmd, 0), true)) + return OPAL_BUSY_EVENT; + + return OPAL_SUCCESS; +} + +int64_t ibm_fsp_cec_power_down(uint64_t request) +{ + /* Request is: + * + * 0 = normal + * 1 = immediate + * (we do not allow 2 for "pci cfg reset" just yet) + */ + + if (request !=0 && request != 1) + return OPAL_PARAMETER; + + if (!fsp_present()) + return OPAL_UNSUPPORTED; + + /* Flash new firmware */ + if (fsp_flash_term_hook) + fsp_flash_term_hook(); + + /* Clear flash hook */ + fsp_flash_term_hook = NULL; + + printf("FSP: Sending shutdown command to FSP...\n"); + + if (fsp_sync_msg(fsp_mkmsg(FSP_CMD_POWERDOWN_NORM, 1, request), true)) + return OPAL_BUSY_EVENT; + + fsp_reset_links(); + return OPAL_SUCCESS; +} + +int64_t ibm_fsp_sensor_read(uint32_t sensor_hndl, int token, + __be64 *sensor_data) +{ + return fsp_opal_read_sensor(sensor_hndl, token, sensor_data); +} + +int __attrconst fsp_heartbeat_time(void) +{ + /* Same as core/timer.c HEARTBEAT_DEFAULT_MS * 10 */ + return 200 * 10; +} + +static void fsp_psihb_interrupt(void) +{ + /* Poll the console buffers on any interrupt since we don't + * get send notifications + */ + fsp_console_poll(NULL); +} + +struct platform_psi fsp_platform_psi = { + .psihb_interrupt = fsp_psihb_interrupt, + .link_established = fsp_reinit_fsp, + .fsp_interrupt = fsp_interrupt, +}; + +struct platform_prd fsp_platform_prd = { + .msg_response = hservice_hbrt_msg_response, + .send_error_log = hservice_send_error_log, + .send_hbrt_msg = hservice_send_hbrt_msg, + .wakeup = hservice_wakeup, + .fsp_occ_load_start_status = fsp_occ_load_start_status, + .fsp_occ_reset_status = fsp_occ_reset_status, +}; diff --git a/roms/skiboot/platforms/ibm-fsp/firenze-pci.c b/roms/skiboot/platforms/ibm-fsp/firenze-pci.c new file mode 100644 index 000000000..5fbbc2ff2 --- /dev/null +++ b/roms/skiboot/platforms/ibm-fsp/firenze-pci.c @@ -0,0 +1,1044 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#define pr_fmt(fmt) "FIRENZE-PCI: " fmt +#include <skiboot.h> +#include <device.h> +#include <fsp.h> +#include <lock.h> +#include <timer.h> +#include <xscom.h> +#include <pci-cfg.h> +#include <pci.h> +#include <pci-slot.h> +#include <phb3.h> +#include <chip.h> +#include <i2c.h> + +#include "ibm-fsp.h" +#include "lxvpd.h" + +/* Dump PCI slots before sending to FSP */ +#define FIRENZE_PCI_INVENTORY_DUMP + +/* + * Firenze PCI slot states to override the default set. + * Refer to pci-slot.h for the default PCI state set + * when you're going to change below values. + */ +#define FIRENZE_PCI_SLOT_NORMAL PCI_SLOT_STATE_NORMAL +#define FIRENZE_PCI_SLOT_LINK PCI_SLOT_STATE_LINK +#define FIRENZE_PCI_SLOT_LINK_START (FIRENZE_PCI_SLOT_LINK + 1) +#define FIRENZE_PCI_SLOT_HRESET PCI_SLOT_STATE_HRESET +#define FIRENZE_PCI_SLOT_HRESET_START (FIRENZE_PCI_SLOT_HRESET + 1) +#define FIRENZE_PCI_SLOT_FRESET PCI_SLOT_STATE_FRESET +#define FIRENZE_PCI_SLOT_FRESET_START (FIRENZE_PCI_SLOT_FRESET + 1) +#define FIRENZE_PCI_SLOT_FRESET_WAIT_RSP (FIRENZE_PCI_SLOT_FRESET + 2) +#define FIRENZE_PCI_SLOT_FRESET_DELAY (FIRENZE_PCI_SLOT_FRESET + 3) +#define FIRENZE_PCI_SLOT_FRESET_POWER_STATE (FIRENZE_PCI_SLOT_FRESET + 4) +#define FIRENZE_PCI_SLOT_FRESET_POWER_OFF (FIRENZE_PCI_SLOT_FRESET + 5) +#define FIRENZE_PCI_SLOT_FRESET_POWER_ON (FIRENZE_PCI_SLOT_FRESET + 6) +#define FIRENZE_PCI_SLOT_PERST_DEASSERT (FIRENZE_PCI_SLOT_FRESET + 7) +#define FIRENZE_PCI_SLOT_PERST_DELAY (FIRENZE_PCI_SLOT_FRESET + 8) +#define FIRENZE_PCI_SLOT_GPOWER PCI_SLOT_STATE_GPOWER +#define FIRENZE_PCI_SLOT_GPOWER_START (FIRENZE_PCI_SLOT_GPOWER + 1) +#define FIRENZE_PCI_SLOT_SPOWER PCI_SLOT_STATE_SPOWER +#define FIRENZE_PCI_SLOT_SPOWER_START (FIRENZE_PCI_SLOT_SPOWER + 1) +#define FIRENZE_PCI_SLOT_SPOWER_DONE (FIRENZE_PCI_SLOT_SPOWER + 2) + +/* Timeout for power status */ +#define FIRENZE_PCI_SLOT_RETRIES 500 +#define FIRENZE_PCI_SLOT_DELAY 10 /* ms */ +#define FIRENZE_PCI_I2C_TIMEOUT 500 /* ms */ + +/* + * Need figure out more stuff later: LED and presence + * detection sensors are accessed from PSI/FSP. + */ +struct firenze_pci_slot { + struct lxvpd_pci_slot lxvpd_slot; /* LXVPD slot data */ + + /* Next slot state */ + uint32_t next_state; + + /* Power management */ + struct i2c_bus *i2c_bus; /* Where MAX5961 seats */ + struct i2c_request *req; /* I2C request message */ + uint8_t i2c_rw_buf[8]; /* I2C read/write buffer */ + uint8_t power_mask; /* Bits for power status */ + uint8_t power_on; /* Bits for power on */ + uint8_t power_off; /* Bits for power off */ + uint8_t *power_status; /* Last power status */ + uint16_t perst_reg; /* PERST config register */ + uint16_t perst_bit; /* PERST bit */ +}; + +struct firenze_pci_slot_info { + uint8_t index; + const char *label; + uint8_t external_power_mgt; + uint8_t inband_perst; + uint8_t chip_id; + uint8_t master_id; + uint8_t port_id; + uint8_t slave_addr; + uint8_t channel; + uint8_t power_status; + uint8_t buddy; +}; + +struct firenze_pci_slot_fixup_info { + const char *label; + uint8_t reg; + uint8_t val; +}; + +struct firenze_pci_inv { + __be32 hw_proc_id; + __be16 slot_idx; + __be16 reserved; + __be16 vendor_id; + __be16 device_id; + __be16 subsys_vendor_id; + __be16 subsys_device_id; +} __packed; + +struct firenze_pci_inv_data { + __be32 version; /* currently 1 */ + __be32 num_entries; + __be32 entry_size; + __be32 entry_offset; + struct firenze_pci_inv entries[]; +} __packed; + +/* + * Note: According to Tuleta system workbook, I didn't figure + * out the I2C mapping info for slot C14/C15. + */ +static struct firenze_pci_inv_data *firenze_inv_data; +static uint32_t firenze_inv_cnt; +static struct firenze_pci_slot_info firenze_pci_slots[] = { + { 0x0B, "C7", 1, 1, 0, 1, 0, 0x35, 1, 0xAA, 0 }, + { 0x11, "C14", 0, 1, 0, 0, 0, 0x00, 0, 0xAA, 1 }, + { 0x0F, "C11", 1, 1, 0, 1, 0, 0x32, 1, 0xAA, 2 }, + { 0x10, "C12", 1, 1, 0, 1, 0, 0x39, 0, 0xAA, 3 }, + { 0x0A, "C6", 1, 1, 0, 1, 0, 0x35, 0, 0xAA, 0 }, + { 0x12, "C15", 0, 1, 0, 0, 0, 0x00, 0, 0xAA, 5 }, + { 0x01, "USB", 0, 0, 0, 0, 0, 0x00, 0, 0xAA, 6 }, + { 0x0C, "C8", 1, 1, 0, 1, 0, 0x36, 0, 0xAA, 7 }, + { 0x0D, "C9", 1, 1, 0, 1, 0, 0x36, 1, 0xAA, 7 }, + { 0x0E, "C10", 1, 1, 0, 1, 0, 0x32, 0, 0xAA, 2 }, + { 0x09, "C5", 1, 1, 0x10, 1, 0, 0x39, 1, 0xAA, 10 }, + { 0x08, "C4", 1, 1, 0x10, 1, 0, 0x39, 0, 0xAA, 10 }, + { 0x07, "C3", 1, 1, 0x10, 1, 0, 0x3A, 1, 0xAA, 12 }, + { 0x06, "C2", 1, 1, 0x10, 1, 0, 0x3A, 0, 0xAA, 12 } +}; + +/* + * I2C power controller register fix up table. Not sure what they do, but + * they seem to relate to the fast-trip setpoint. + */ +static struct firenze_pci_slot_fixup_info firenze_pci_slot_fixup_tbl[] = { + { "C3", 0x5e, 0xfb }, + { "C3", 0x5b, 0xff }, + { "C5", 0x5e, 0xfb }, + { "C5", 0x5b, 0xff }, + { "C6", 0x5e, 0xfa }, + { "C6", 0x5a, 0xff }, + { "C6", 0x5b, 0xff }, + { "C7", 0x5e, 0xfa }, + { "C7", 0x5a, 0xff }, + { "C7", 0x5b, 0xff } +}; + +static void firenze_pci_add_inventory(struct phb *phb, + struct pci_device *pd) +{ + struct lxvpd_pci_slot *lxvpd_slot; + struct firenze_pci_inv *entry; + struct proc_chip *chip; + size_t size; + bool need_init = false; + u32 num_entries; + u16 tmp16; + + /* + * Do we need to add that to the FSP inventory for power + * management? + * + * For now, we only add devices that: + * + * - Are function 0 + * - Are not an RC or a downstream bridge + * - Have a direct parent that has a slot entry + * - Slot entry says pluggable + * - Aren't an upstream switch that has slot info + */ + if (!pd || !pd->parent) + return; + if (pd->bdfn & 7) + return; + if (pd->dev_type == PCIE_TYPE_ROOT_PORT || + pd->dev_type == PCIE_TYPE_SWITCH_DNPORT) + return; + if (pd->dev_type == PCIE_TYPE_SWITCH_UPPORT && + pd->slot && pd->slot->data) + return; + if (!pd->parent->slot || + !pd->parent->slot->data) + return; + lxvpd_slot = pd->parent->slot->data; + if (!lxvpd_slot->pluggable) + return; + + /* Check if we need to do some (Re)allocation */ + if (!firenze_inv_data || + be32_to_cpu(firenze_inv_data->num_entries) == firenze_inv_cnt) { + need_init = !firenze_inv_data; + + /* (Re)allocate the block to the new size */ + firenze_inv_cnt += 4; + size = sizeof(struct firenze_pci_inv_data) + + sizeof(struct firenze_pci_inv) * firenze_inv_cnt; + firenze_inv_data = realloc(firenze_inv_data, size); + } + + /* Initialize the header for a new inventory */ + if (need_init) { + firenze_inv_data->version = cpu_to_be32(1); + firenze_inv_data->num_entries = 0; + firenze_inv_data->entry_size = + cpu_to_be32(sizeof(struct firenze_pci_inv)); + firenze_inv_data->entry_offset = + cpu_to_be32(offsetof(struct firenze_pci_inv_data, entries)); + } + + /* Append slot entry */ + num_entries = be32_to_cpu(firenze_inv_data->num_entries); + firenze_inv_data->num_entries = cpu_to_be32(num_entries + 1); + entry = &firenze_inv_data->entries[num_entries]; + chip = get_chip(dt_get_chip_id(phb->dt_node)); + if (!chip) { + /** + * @fwts-label FirenzePCIInventory + * @fwts-advice Device tree didn't contain enough information + * to correctly report back PCI inventory. Service processor + * is likely to be missing information about what hardware + * is physically present in the machine. + */ + prlog(PR_ERR, "No chip device node for PHB%04x\n", + phb->opal_id); + return; + } + + entry->hw_proc_id = cpu_to_be32(chip->pcid); + entry->reserved = 0; + if (pd->parent && + pd->parent->slot && + pd->parent->slot->data) { + lxvpd_slot = pd->parent->slot->data; + entry->slot_idx = cpu_to_be16(lxvpd_slot->slot_index); + } + + pci_cfg_read16(phb, pd->bdfn, PCI_CFG_VENDOR_ID, &tmp16); + entry->vendor_id = cpu_to_be16(tmp16); + pci_cfg_read16(phb, pd->bdfn, PCI_CFG_DEVICE_ID, &tmp16); + entry->device_id = cpu_to_be16(tmp16); + if (pd->is_bridge) { + int64_t ssvc = pci_find_cap(phb, pd->bdfn, + PCI_CFG_CAP_ID_SUBSYS_VID); + if (ssvc <= 0) { + entry->subsys_vendor_id = cpu_to_be16(0xffff); + entry->subsys_device_id = cpu_to_be16(0xffff); + } else { + pci_cfg_read16(phb, pd->bdfn, + ssvc + PCICAP_SUBSYS_VID_VENDOR, &tmp16); + entry->subsys_vendor_id = cpu_to_be16(tmp16); + pci_cfg_read16(phb, pd->bdfn, + ssvc + PCICAP_SUBSYS_VID_DEVICE, &tmp16); + entry->subsys_device_id = cpu_to_be16(tmp16); + } + } else { + pci_cfg_read16(phb, pd->bdfn, PCI_CFG_SUBSYS_VENDOR_ID, &tmp16); + entry->subsys_vendor_id = cpu_to_be16(tmp16); + pci_cfg_read16(phb, pd->bdfn, PCI_CFG_SUBSYS_ID, &tmp16); + entry->subsys_device_id = cpu_to_be16(tmp16); + } +} + +static void firenze_dump_pci_inventory(void) +{ +#ifdef FIRENZE_PCI_INVENTORY_DUMP + struct firenze_pci_inv *e; + uint32_t i; + + if (!firenze_inv_data) + return; + + prlog(PR_INFO, "Dumping Firenze PCI inventory\n"); + prlog(PR_INFO, "HWP SLT VDID DVID SVID SDID\n"); + prlog(PR_INFO, "---------------------------\n"); + for (i = 0; i < be32_to_cpu(firenze_inv_data->num_entries); i++) { + e = &firenze_inv_data->entries[i]; + + prlog(PR_INFO, "%03d %03d %04x %04x %04x %04x\n", + be32_to_cpu(e->hw_proc_id), + be16_to_cpu(e->slot_idx), + be16_to_cpu(e->vendor_id), + be16_to_cpu(e->device_id), + be16_to_cpu(e->subsys_vendor_id), + be16_to_cpu(e->subsys_device_id)); + } +#endif /* FIRENZE_PCI_INVENTORY_DUMP */ +} + +void firenze_pci_send_inventory(void) +{ + uint64_t base, abase, end, aend, offset; + int64_t rc; + + if (!firenze_inv_data) + return; + + /* Dump the inventory */ + prlog(PR_INFO, "Sending %d inventory to FSP\n", + be32_to_cpu(firenze_inv_data->num_entries)); + firenze_dump_pci_inventory(); + + /* Memory location for inventory */ + base = (uint64_t)firenze_inv_data; + end = base + sizeof(struct firenze_pci_inv_data) + + be32_to_cpu(firenze_inv_data->num_entries) * + be32_to_cpu(firenze_inv_data->entry_size); + abase = base & ~0xffful; + aend = (end + 0xffful) & ~0xffful; + offset = PSI_DMA_PCIE_INVENTORY + (base & 0xfff); + + /* We can only accomodate so many entries in the PSI map */ + if ((aend - abase) > PSI_DMA_PCIE_INVENTORY_SIZE) { + /** + * @fwts-label FirenzePCIInventoryTooLarge + * @fwts-advice More PCI inventory than we can send to service + * processor. The service processor will have an incomplete + * view of the world. + */ + prlog(PR_ERR, "Inventory (%lld bytes) too large\n", + aend - abase); + goto bail; + } + + /* Map this in the TCEs */ + fsp_tce_map(PSI_DMA_PCIE_INVENTORY, (void *)abase, aend - abase); + + /* Send FSP message */ + rc = fsp_sync_msg(fsp_mkmsg(FSP_CMD_PCI_POWER_CONF, 3, + hi32(offset), lo32(offset), + end - base), true); + if (rc) + { + /** + * @fwts-label FirenzePCIInventoryError + * @fwts-advice Error communicating with service processor + * when sending PCI Inventory. + */ + prlog(PR_ERR, "Error %lld sending inventory\n", rc); + } + + /* Unmap */ + fsp_tce_unmap(PSI_DMA_PCIE_INVENTORY, aend - abase); +bail: + /* + * We free the inventory. We'll have to redo that on hotplug + * when we support it but that isn't the case yet + */ + free(firenze_inv_data); + firenze_inv_data = NULL; + firenze_inv_cnt = 0; +} + +/* The function is called when the I2C request is completed + * successfully, or with errors. + */ +static void firenze_i2c_req_done(int rc, struct i2c_request *req) +{ + struct pci_slot *slot = req->user_data; + uint32_t state; + + /* Check if there are errors for the completion */ + if (rc) { + /** + * @fwts-label FirenzePCII2CError + * @fwts-advice On Firenze platforms, I2C is used to control + * power to PCI slots. Errors here mean we may be in trouble + * in regards to PCI slot power on/off. + */ + prlog(PR_ERR, "Error %d from I2C request on slot %016llx\n", + rc, slot->id); + return; + } + + /* Check the request type */ + if (req->op != SMBUS_READ && req->op != SMBUS_WRITE) { + /** + * @fwts-label FirenzePCII2CInvalid + * @fwts-advice Likely a coding error: invalid I2C request. + */ + prlog(PR_ERR, "Invalid I2C request %d on slot %016llx\n", + req->op, slot->id); + return; + } + + /* After writting power status to I2C slave, we need at least + * 5ms delay for the slave to settle down. We also have the + * delay after reading the power status as well. + */ + switch (slot->state) { + case FIRENZE_PCI_SLOT_FRESET_WAIT_RSP: + prlog(PR_DEBUG, "%016llx FRESET: I2C request completed\n", + slot->id); + state = FIRENZE_PCI_SLOT_FRESET_DELAY; + break; + case FIRENZE_PCI_SLOT_SPOWER_START: + prlog(PR_DEBUG, "%016llx SPOWER: I2C request completed\n", + slot->id); + state = FIRENZE_PCI_SLOT_SPOWER_DONE; + break; + default: + /** + * @fwts-label FirenzePCISlotI2CStateError + * @fwts-advice The Firenze platform uses I2C to control + * power to PCI slots. Something went wrong in the state + * machine controlling that. Slots may/may not have power. + */ + prlog(PR_ERR, "Wrong state %08x on slot %016llx\n", + slot->state, slot->id); + return; + } + + /* Switch to net state */ + pci_slot_set_state(slot, state); +} + +/* This function is called to setup normal PCI device or PHB slot. + * For the later case, the slot doesn't have the associated PCI + * device. Besides, the I2C response timeout is set to 5s. We might + * improve I2C in future to support priorized requests so that the + * timeout can be shortened. + */ +static int64_t firenze_pci_slot_freset(struct pci_slot *slot) +{ + struct firenze_pci_slot *plat_slot = slot->data; + uint8_t *pval, presence = 1; + uint32_t timeout; + + switch (slot->state) { + case FIRENZE_PCI_SLOT_NORMAL: + case FIRENZE_PCI_SLOT_FRESET_START: + prlog(PR_DEBUG, "%016llx FRESET: Starts\n", + slot->id); + + /* Bail if nothing is connected */ + if (slot->ops.get_presence_state) + slot->ops.get_presence_state(slot, &presence); + if (!presence) { + prlog(PR_DEBUG, "%016llx FRESET: No device\n", + slot->id); + return OPAL_SUCCESS; + } + + /* Prepare link down */ + if (slot->ops.prepare_link_change) { + prlog(PR_DEBUG, "%016llx FRESET: Prepares link down\n", + slot->id); + slot->ops.prepare_link_change(slot, false); + } + + /* Send I2C request */ + prlog(PR_DEBUG, "%016llx FRESET: Check power state\n", + slot->id); + plat_slot->next_state = + FIRENZE_PCI_SLOT_FRESET_POWER_STATE; + plat_slot->req->op = SMBUS_READ; + slot->retries = FIRENZE_PCI_SLOT_RETRIES; + pci_slot_set_state(slot, + FIRENZE_PCI_SLOT_FRESET_WAIT_RSP); + if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BOOTUP)) + plat_slot->req->timeout = FIRENZE_PCI_I2C_TIMEOUT; + else + plat_slot->req->timeout = 0ul; + i2c_queue_req(plat_slot->req); + return pci_slot_set_sm_timeout(slot, + msecs_to_tb(FIRENZE_PCI_SLOT_DELAY)); + case FIRENZE_PCI_SLOT_FRESET_WAIT_RSP: + if (slot->retries-- == 0) { + prlog(PR_DEBUG, "%016llx FRESET: Timeout waiting for %08x\n", + slot->id, plat_slot->next_state); + goto out; + } + + check_timers(false); + return pci_slot_set_sm_timeout(slot, + msecs_to_tb(FIRENZE_PCI_SLOT_DELAY)); + case FIRENZE_PCI_SLOT_FRESET_DELAY: + prlog(PR_DEBUG, "%016llx FRESET: Delay %dms on I2C completion\n", + slot->id, FIRENZE_PCI_SLOT_DELAY); + pci_slot_set_state(slot, plat_slot->next_state); + return pci_slot_set_sm_timeout(slot, + msecs_to_tb(FIRENZE_PCI_SLOT_DELAY)); + case FIRENZE_PCI_SLOT_FRESET_POWER_STATE: + /* Update last power status */ + pval = (uint8_t *)(plat_slot->req->rw_buf); + *plat_slot->power_status = *pval; + + /* Power is on, turn it off */ + if (((*pval) & plat_slot->power_mask) == plat_slot->power_on) { + prlog(PR_DEBUG, "%016llx FRESET: Power (%02x) on, turn off\n", + slot->id, *pval); + (*pval) &= ~plat_slot->power_mask; + (*pval) |= plat_slot->power_off; + plat_slot->req->op = SMBUS_WRITE; + slot->retries = FIRENZE_PCI_SLOT_RETRIES; + plat_slot->next_state = + FIRENZE_PCI_SLOT_FRESET_POWER_OFF; + pci_slot_set_state(slot, + FIRENZE_PCI_SLOT_FRESET_WAIT_RSP); + + if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BOOTUP)) + timeout = FIRENZE_PCI_I2C_TIMEOUT; + else + timeout = 0ul; + plat_slot->req->timeout = timeout; + + i2c_queue_req(plat_slot->req); + return pci_slot_set_sm_timeout(slot, + msecs_to_tb(FIRENZE_PCI_SLOT_DELAY)); + } + + /* Power is off, turn it on */ + /* Fallthrough */ + case FIRENZE_PCI_SLOT_FRESET_POWER_OFF: + /* Update last power status */ + pval = (uint8_t *)(plat_slot->req->rw_buf); + *plat_slot->power_status = *pval; + + prlog(PR_DEBUG, "%016llx FRESET: Power (%02x) off, turn on\n", + slot->id, *pval); + (*pval) &= ~plat_slot->power_mask; + (*pval) |= plat_slot->power_on; + plat_slot->req->op = SMBUS_WRITE; + plat_slot->next_state = + FIRENZE_PCI_SLOT_FRESET_POWER_ON; + slot->retries = FIRENZE_PCI_SLOT_RETRIES; + pci_slot_set_state(slot, + FIRENZE_PCI_SLOT_FRESET_WAIT_RSP); + + if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BOOTUP)) + plat_slot->req->timeout = FIRENZE_PCI_I2C_TIMEOUT; + else + plat_slot->req->timeout = 0ul; + i2c_queue_req(plat_slot->req); + return pci_slot_set_sm_timeout(slot, + msecs_to_tb(FIRENZE_PCI_SLOT_DELAY)); + case FIRENZE_PCI_SLOT_FRESET_POWER_ON: + /* Update last power status */ + pval = (uint8_t *)(plat_slot->req->rw_buf); + *plat_slot->power_status = *pval; + + pci_slot_set_state(slot, FIRENZE_PCI_SLOT_LINK_START); + return slot->ops.poll_link(slot); + default: + prlog(PR_DEBUG, "%016llx FRESET: Unexpected state %08x\n", + slot->id, slot->state); + } + +out: + pci_slot_set_state(slot, FIRENZE_PCI_SLOT_NORMAL); + return OPAL_HARDWARE; +} + +static int64_t firenze_pci_slot_perst(struct pci_slot *slot) +{ + struct firenze_pci_slot *plat_slot = slot->data; + uint8_t presence = 1; + uint16_t ctrl; + + switch (slot->state) { + case FIRENZE_PCI_SLOT_NORMAL: + case FIRENZE_PCI_SLOT_FRESET_START: + prlog(PR_DEBUG, "%016llx PERST: Starts\n", + slot->id); + + /* Bail if nothing is connected */ + if (slot->ops.get_presence_state) + slot->ops.get_presence_state(slot, &presence); + if (!presence) { + prlog(PR_DEBUG, "%016llx PERST: No device\n", + slot->id); + return OPAL_SUCCESS; + } + + /* Prepare link down */ + if (slot->ops.prepare_link_change) { + prlog(PR_DEBUG, "%016llx PERST: Prepare link down\n", + slot->id); + slot->ops.prepare_link_change(slot, false); + } + + /* Assert PERST */ + prlog(PR_DEBUG, "%016llx PERST: Assert\n", + slot->id); + pci_cfg_read16(slot->phb, slot->pd->bdfn, + plat_slot->perst_reg, &ctrl); + ctrl |= plat_slot->perst_bit; + pci_cfg_write16(slot->phb, slot->pd->bdfn, + plat_slot->perst_reg, ctrl); + pci_slot_set_state(slot, + FIRENZE_PCI_SLOT_PERST_DEASSERT); + return pci_slot_set_sm_timeout(slot, msecs_to_tb(250)); + case FIRENZE_PCI_SLOT_PERST_DEASSERT: + /* Deassert PERST */ + pci_cfg_read16(slot->phb, slot->pd->bdfn, + plat_slot->perst_reg, &ctrl); + ctrl &= ~plat_slot->perst_bit; + pci_cfg_write16(slot->phb, slot->pd->bdfn, + plat_slot->perst_reg, ctrl); + pci_slot_set_state(slot, + FIRENZE_PCI_SLOT_PERST_DELAY); + return pci_slot_set_sm_timeout(slot, msecs_to_tb(1500)); + case FIRENZE_PCI_SLOT_PERST_DELAY: + pci_slot_set_state(slot, FIRENZE_PCI_SLOT_LINK_START); + return slot->ops.poll_link(slot); + default: + prlog(PR_DEBUG, "%016llx PERST: Unexpected state %08x\n", + slot->id, slot->state); + } + + pci_slot_set_state(slot, FIRENZE_PCI_SLOT_NORMAL); + return OPAL_HARDWARE; +} + +static int64_t firenze_pci_slot_get_power_state(struct pci_slot *slot, + uint8_t *val) +{ + if (slot->state != FIRENZE_PCI_SLOT_NORMAL) + { + /** + * @fwts-label FirenzePCISlotGPowerState + * @fwts-advice Unexpected state in the FIRENZE PCI Slot + * state machine. This could mean PCI is not functioning + * correctly. + */ + prlog(PR_ERR, "%016llx GPOWER: Unexpected state %08x\n", + slot->id, slot->state); + } + + *val = slot->power_state; + return OPAL_SUCCESS; +} + +static int64_t firenze_pci_slot_set_power_state(struct pci_slot *slot, + uint8_t val) +{ + struct firenze_pci_slot *plat_slot = slot->data; + uint8_t *pval; + + if (slot->state != FIRENZE_PCI_SLOT_NORMAL) + { + /** + * @fwts-label FirenzePCISlotSPowerState + * @fwts-advice Unexpected state in the FIRENZE PCI Slot + * state machine. This could mean PCI is not functioning + * correctly. + */ + prlog(PR_ERR, "%016llx SPOWER: Unexpected state %08x\n", + slot->id, slot->state); + } + + if (val != PCI_SLOT_POWER_OFF && val != PCI_SLOT_POWER_ON) + return OPAL_PARAMETER; + + if (!pci_slot_has_flags(slot, PCI_SLOT_FLAG_ENFORCE) && + slot->power_state == val) + return OPAL_SUCCESS; + + /* Update with the requested power state and bail immediately when + * surprise hotplug is supported on the slot. It keeps the power + * supply to the slot on and it guarentees the link state change + * events will be raised properly during surprise hot add/remove. + */ + if (!pci_slot_has_flags(slot, PCI_SLOT_FLAG_ENFORCE) && + slot->surprise_pluggable) { + slot->power_state = val; + return OPAL_SUCCESS; + } + + slot->power_state = val; + pci_slot_set_state(slot, FIRENZE_PCI_SLOT_SPOWER_START); + + plat_slot->req->op = SMBUS_WRITE; + pval = (uint8_t *)plat_slot->req->rw_buf; + if (val == PCI_SLOT_POWER_ON) { + *pval = *plat_slot->power_status; + (*pval) &= ~plat_slot->power_mask; + (*pval) |= plat_slot->power_on; + } else { + *pval = *plat_slot->power_status; + (*pval) &= ~plat_slot->power_mask; + (*pval) |= plat_slot->power_off; + } + + if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BOOTUP)) + plat_slot->req->timeout = FIRENZE_PCI_I2C_TIMEOUT; + else + plat_slot->req->timeout = 0ul; + i2c_queue_req(plat_slot->req); + + return OPAL_ASYNC_COMPLETION; +} + +static struct i2c_bus *firenze_pci_find_i2c_bus(uint8_t chip, + uint8_t eng, + uint8_t port) +{ + struct dt_node *np, *child; + uint32_t reg; + + /* Iterate I2C masters */ + dt_for_each_compatible(dt_root, np, "ibm,power8-i2cm") { + if (!np->parent || + !dt_node_is_compatible(np->parent, "ibm,power8-xscom")) + continue; + + /* Check chip index */ + reg = dt_prop_get_u32(np->parent, "ibm,chip-id"); + if (reg != chip) + continue; + + /* Check I2C master index */ + reg = dt_prop_get_u32(np, "chip-engine#"); + if (reg != eng) + continue; + + /* Iterate I2C buses */ + dt_for_each_child(np, child) { + if (!dt_node_is_compatible(child, "ibm,power8-i2c-port")) + continue; + + /* Check I2C port index */ + reg = dt_prop_get_u32(child, "reg"); + if (reg != port) + continue; + + reg = dt_prop_get_u32(child, "ibm,opal-id"); + return i2c_find_bus_by_id(reg); + } + } + + return NULL; +} + +static int64_t firenze_pci_slot_fixup_one_reg(struct pci_slot *slot, + struct firenze_pci_slot_fixup_info *fixup) +{ + struct firenze_pci_slot *plat_slot = slot->data; + struct i2c_request req; + uint8_t buf; + int64_t rc; + + /* + * Fill out our own request structure since we don't want to invoke the + * normal completion handler. + */ + memset(&req, 0, sizeof(req)); + req.dev_addr = plat_slot->req->dev_addr; + req.bus = plat_slot->req->bus; + req.offset = fixup->reg; + req.offset_bytes = 1; + req.rw_buf = &buf; + req.rw_len = 1; + req.timeout = FIRENZE_PCI_I2C_TIMEOUT; + + req.op = SMBUS_WRITE; + buf = fixup->val; + rc = i2c_request_sync(&req); + if (rc < 0) + return rc; + + /* + * Check the register fixup has been applied. It's not the end of the + * world we don't, but eh... + */ + req.op = SMBUS_READ; + rc = i2c_request_sync(&req); + if (rc == OPAL_SUCCESS && buf != fixup->val) { + prlog(PR_ERR, "Error verifying fixup [%s] - (%02x, %02x, %02x)\n", + fixup->label, fixup->reg, fixup->val, buf); + } + + return rc; +} + +static void firenze_pci_slot_fixup(struct pci_slot *slot, + struct firenze_pci_slot_info *info) +{ + int64_t rc, i, applied = 0; + const uint32_t *p; + uint64_t id; + + p = dt_prop_get_def(dt_root, "ibm,vpd-lx-info", NULL); + id = p ? (((uint64_t)p[1] << 32) | p[2]) : 0ul; + if (id != LX_VPD_2S4U_BACKPLANE && + id != LX_VPD_1S4U_BACKPLANE) + return; + + for (i = 0; i < ARRAY_SIZE(firenze_pci_slot_fixup_tbl); i++) { + struct firenze_pci_slot_fixup_info *fixup = + &firenze_pci_slot_fixup_tbl[i]; + + if (strcmp(info->label, fixup->label)) + continue; + + rc = firenze_pci_slot_fixup_one_reg(slot, fixup); + if (rc) { + prlog(PR_ERR, "I2C error (%lld) applying fixup [%s] - (%02x, %02x)\n", + rc, fixup->label, fixup->reg, fixup->val); + return; + } + + applied++; + } + + if (applied) + prlog(PR_INFO, "Applied %lld fixups for [%s]\n", + applied, info->label); +} + +static void firenze_pci_setup_power_mgt(struct pci_slot *slot, + struct firenze_pci_slot *plat_slot, + struct firenze_pci_slot_info *info) +{ + plat_slot->i2c_bus = firenze_pci_find_i2c_bus(info->chip_id, + info->master_id, + info->port_id); + if (!plat_slot->i2c_bus) + return; + + plat_slot->req = zalloc(sizeof(*plat_slot->req)); + if (!plat_slot->req) + return; + + plat_slot->req->dev_addr = info->slave_addr; + plat_slot->req->offset_bytes = 1; + plat_slot->req->rw_buf = plat_slot->i2c_rw_buf; + plat_slot->req->rw_len = 1; + plat_slot->req->completion = firenze_i2c_req_done; + plat_slot->req->user_data = slot; + plat_slot->req->bus = plat_slot->i2c_bus; + + firenze_pci_slot_fixup(slot, info); + + /* + * For all slots, the register used to change the power state is + * always 0x69. It could have been set to something else in the + * above fixup. Lets fix it to 0x69 here. + * + * The power states of two slots are controlled by one register. + * This means two slots have to share data buffer for power states, + * which are tracked by struct firenze_pci_slot_info::power_status. + * With it, we can avoid affecting slot#B's power state when trying + * to adjust that on slot#A. Also, the initial power states for all + * slots are assumed to be PCI_SLOT_POWER_ON. + */ + plat_slot->req->offset = 0x69; + plat_slot->power_status = &firenze_pci_slots[info->buddy].power_status; + switch (info->channel) { + case 0: + plat_slot->power_mask = 0x33; + plat_slot->power_on = 0x22; + plat_slot->power_off = 0; + break; + case 1: + plat_slot->power_status = &firenze_pci_slots[info->buddy].power_status; + plat_slot->power_mask = 0xcc; + plat_slot->power_on = 0x88; + plat_slot->power_off = 0; + break; + default: + prlog(PR_ERR, "%016llx: Invalid channel %d\n", + slot->id, info->channel); + } +} + +static void firenze_pci_slot_init(struct pci_slot *slot) +{ + struct lxvpd_pci_slot *s = slot->data; + struct firenze_pci_slot *plat_slot = slot->data; + struct firenze_pci_slot_info *info = NULL; + uint32_t vdid; + int i; + + /* Init the slot info from the LXVPD */ + slot->ops.add_properties = lxvpd_add_slot_properties; + + /* Search for power control information in the per-system table */ + for (i = 0; i < ARRAY_SIZE(firenze_pci_slots); i++) { + if (firenze_pci_slots[i].index == s->slot_index && + !strcmp(firenze_pci_slots[i].label, s->label)) { + info = &firenze_pci_slots[i]; + break; + } + } + if (!info) + return; + + /* Search I2C bus for external power mgt */ + if (slot->power_ctl) + firenze_pci_setup_power_mgt(slot, plat_slot, info); + + /* + * If the slot has external power logic, to override the + * default power management methods. Because of the bad + * I2C design, the API supplied by I2C is really hard to + * be utilized. To figure out power status retrival or + * configuration after we have a blocking API for that. + */ + if (plat_slot->req) { + slot->ops.freset = firenze_pci_slot_freset; + slot->ops.get_power_state = firenze_pci_slot_get_power_state; + slot->ops.set_power_state = firenze_pci_slot_set_power_state; + prlog(PR_DEBUG, "%016llx: External power mgt initialized\n", + slot->id); + } else if (info->inband_perst) { + /* + * For PLX downstream ports, PCI config register can be + * leveraged to do PERST. If the slot doesn't have external + * power management stuff, lets try to stick to the PERST + * logic if applicable + */ + if (slot->pd->dev_type == PCIE_TYPE_SWITCH_DNPORT) { + pci_cfg_read32(slot->phb, slot->pd->bdfn, + PCI_CFG_VENDOR_ID, &vdid); + switch (vdid) { + case 0x873210b5: /* PLX8732 */ + case 0x874810b5: /* PLX8748 */ + plat_slot->perst_reg = 0x80; + plat_slot->perst_bit = 0x0400; + slot->ops.freset = firenze_pci_slot_perst; + break; + } + } + } +} + +void firenze_pci_setup_phb(struct phb *phb, unsigned int index) +{ + uint32_t hub_id; + + /* Grab Hub ID used to parse VPDs */ + hub_id = dt_prop_get_u32_def(phb->dt_node, "ibm,hub-id", 0); + + /* Process the pcie slot entries from the lx vpd lid */ + lxvpd_process_slot_entries(phb, dt_root, hub_id, + index, sizeof(struct firenze_pci_slot)); +} + +void firenze_pci_get_slot_info(struct phb *phb, struct pci_device *pd) +{ + struct pci_slot *slot; + struct lxvpd_pci_slot *s; + + /* Prepare the PCI inventory */ + firenze_pci_add_inventory(phb, pd); + + if (pd->dev_type != PCIE_TYPE_ROOT_PORT && + pd->dev_type != PCIE_TYPE_SWITCH_UPPORT && + pd->dev_type != PCIE_TYPE_SWITCH_DNPORT && + pd->dev_type != PCIE_TYPE_PCIE_TO_PCIX) + return; + + /* Create PCIe slot */ + slot = pcie_slot_create(phb, pd); + if (!slot) + return; + + /* Root complex inherits methods from PHB slot */ + if (!pd->parent && phb->slot) + memcpy(&slot->ops, &phb->slot->ops, sizeof(struct pci_slot_ops)); + + /* Patch PCIe slot */ + s = lxvpd_get_slot(slot); + if (s) { + lxvpd_extract_info(slot, s); + firenze_pci_slot_init(slot); + } +} + +void firenze_pci_add_loc_code(struct dt_node *np, struct pci_device *pd) +{ + struct dt_node *p; + const char *blcode = NULL; + char *lcode; + uint32_t class_code; + uint8_t class,sub; + uint8_t pos, len; + + + /* + * prefer fully-qualified slot-location-code, walk-up parent tree + * to find one + */ + for (p = np->parent; p; p = p->parent) { + blcode = dt_prop_get_def(p, "ibm,slot-location-code", NULL); + if (blcode) + break; + } + + /* try the node itself if none is found */ + if (!blcode) + blcode = dt_prop_get_def(np, "ibm,slot-location-code", NULL); + + if (!blcode) { + /* still not found, fall back to ibm,loc-code */ + + for (p = np->parent; p; p = p->parent) { + blcode = dt_prop_get_def(p, "ibm,loc-code", NULL); + if (blcode) + break; + } + } + + if (!blcode) { + prlog(PR_ERR, + "No suitable location code to add for device PHB#%04x:%02x:%02x.%x\n", + pd->phb->opal_id, PCI_BUS_NUM(pd->bdfn), + PCI_DEV(pd->bdfn), PCI_FUNC(pd->bdfn)); + return; + } + + /* ethernet devices get port codes */ + class_code = dt_prop_get_u32(np, "class-code"); + class = class_code >> 16; + sub = (class_code >> 8) & 0xff; + + if (class == 0x02 && sub == 0x00) { + /* There's usually several spaces at the end of the property. + Test for, but don't rely on, that being the case */ + len = strlen(blcode); + for (pos = 0; pos < len; pos++) + if (blcode[pos] == ' ') break; + if (pos + 3 < len) + lcode = strdup(blcode); + else { + lcode = malloc(pos + 3); + memcpy(lcode, blcode, len); + } + lcode[pos++] = '-'; + lcode[pos++] = 'T'; + lcode[pos++] = (char)PCI_FUNC(pd->bdfn) + '1'; + lcode[pos++] = '\0'; + dt_add_property_string(np, "ibm,loc-code", lcode); + free(lcode); + } else { + dt_add_property_string(np, "ibm,loc-code", blcode); + } +} diff --git a/roms/skiboot/platforms/ibm-fsp/firenze.c b/roms/skiboot/platforms/ibm-fsp/firenze.c new file mode 100644 index 000000000..887a9c0ac --- /dev/null +++ b/roms/skiboot/platforms/ibm-fsp/firenze.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <fsp.h> +#include <pci.h> +#include <pci-cfg.h> +#include <chip.h> +#include <i2c.h> +#include <timebase.h> +#include <hostservices.h> + +#include "ibm-fsp.h" +#include "lxvpd.h" + +static struct dt_node *dt_create_i2c_master(struct dt_node *n, uint32_t eng_id) +{ + struct dt_node *i2cm; + uint64_t freq; + uint32_t clock; + + /* Each master registers set is of length 0x20 */ + i2cm = dt_new_addr(n, "i2cm", 0xa0000 + eng_id * 0x20); + if (!i2cm) + return NULL; + + dt_add_property_string(i2cm, "compatible", + "ibm,power8-i2cm"); + dt_add_property_cells(i2cm, "reg", 0xa0000 + eng_id * 0x20, + 0x20); + dt_add_property_cells(i2cm, "chip-engine#", eng_id); + dt_add_property_cells(i2cm, "#address-cells", 1); + dt_add_property_cells(i2cm, "#size-cells", 0); + + /* Derive the clock source frequency */ + freq = dt_prop_get_u64_def(n, "bus-frequency", 0); + clock = (u32)(freq / 4); + if (clock) + dt_add_property_cells(i2cm, "clock-frequency", clock); + else + dt_add_property_cells(i2cm, "clock-frequency", 125000000); + return i2cm; +} + +static struct dt_node *dt_create_i2c_bus(struct dt_node *i2cm, + const char *port_name, uint32_t port_id) +{ + static struct dt_node *port; + + port = dt_new_addr(i2cm, "i2c-bus", port_id); + if (!port) + return NULL; + + dt_add_property_strings(port, "compatible", "ibm,power8-i2c-port", + "ibm,opal-i2c"); + dt_add_property_string(port, "ibm,port-name", port_name); + dt_add_property_cells(port, "reg", port_id); + dt_add_property_cells(port, "bus-frequency", 400000); + dt_add_property_cells(port, "#address-cells", 1); + dt_add_property_cells(port, "#size-cells", 0); + + return port; +} + +static struct dt_node *dt_create_i2c_device(struct dt_node *bus, uint8_t addr, + const char *name, const char *compat, + const char *label) +{ + struct dt_node *dev; + + dev = dt_new_addr(bus, name, addr); + if (!dev) + return NULL; + + dt_add_property_string(dev, "compatible", compat); + dt_add_property_string(dev, "label", label); + dt_add_property_cells(dev, "reg", addr); + dt_add_property_string(dev, "status", "reserved"); + + return dev; +} + +static void firenze_dt_fixup_i2cm(void) +{ + struct dt_node *master, *bus, *dev; + struct proc_chip *c; + const uint32_t *p; + char name[32]; + uint64_t lx; + + if (dt_find_compatible_node(dt_root, NULL, "ibm,power8-i2cm")) + return; + + p = dt_prop_get_def(dt_root, "ibm,vpd-lx-info", NULL); + if (!p) + return; + + lx = ((uint64_t)p[1] << 32) | p[2]; + + switch (lx) { + case LX_VPD_2S4U_BACKPLANE: + case LX_VPD_2S2U_BACKPLANE: + case LX_VPD_SHARK_BACKPLANE: /* XXX confirm ? */ + /* i2c nodes on chip 0x10 */ + c = get_chip(0x10); + if (c) { + /* Engine 1 */ + master = dt_create_i2c_master(c->devnode, 1); + assert(master); + snprintf(name, sizeof(name), "p8_%08x_e%dp%d", c->id, 1, 0); + bus = dt_create_i2c_bus(master, name, 0); + assert(bus); + dev = dt_create_i2c_device(bus, 0x39, "power-control", + "maxim,5961", "pcie-hotplug"); + assert(dev); + dt_add_property_strings(dev, "target-list", "slot-C4", + "slot-C5"); + + dev = dt_create_i2c_device(bus, 0x3a, "power-control", + "maxim,5961", "pcie-hotplug"); + assert(dev); + dt_add_property_strings(dev, "target-list", "slot-C2", + "slot-C3"); + } else { + prlog(PR_INFO, "PLAT: Chip not found for the id 0x10\n"); + } + + /* Fall through */ + case LX_VPD_1S4U_BACKPLANE: + case LX_VPD_1S2U_BACKPLANE: + /* i2c nodes on chip 0 */ + c = get_chip(0); + if (!c) { + prlog(PR_INFO, "PLAT: Chip not found for the id 0x0\n"); + break; + } + + /* Engine 1*/ + master = dt_create_i2c_master(c->devnode, 1); + assert(master); + snprintf(name, sizeof(name), "p8_%08x_e%dp%d", c->id, 1, 0); + bus = dt_create_i2c_bus(master, name, 0); + assert(bus); + dev = dt_create_i2c_device(bus, 0x32, "power-control", + "maxim,5961", "pcie-hotplug"); + assert(dev); + dt_add_property_strings(dev, "target-list", "slot-C10", "slot-C11"); + + dev = dt_create_i2c_device(bus, 0x35, "power-control", + "maxim,5961", "pcie-hotplug"); + assert(dev); + dt_add_property_strings(dev, "target-list", "slot-C6", "slot-C7"); + + dev = dt_create_i2c_device(bus, 0x36, "power-control", + "maxim,5961", "pcie-hotplug"); + assert(dev); + dt_add_property_strings(dev, "target-list", "slot-C8", "slot-C9"); + + dev = dt_create_i2c_device(bus, 0x39, "power-control", "maxim,5961", + "pcie-hotplug"); + assert(dev); + dt_add_property_strings(dev, "target-list", "slot-C12"); + break; + default: + break; + } +} + +static bool firenze_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "ibm,firenze")) + return false; + + firenze_dt_fixup_i2cm(); + + return true; +} + +static uint32_t ibm_fsp_occ_timeout(void) +{ + /* Use a fixed 60s value for now */ + return 60; +} + +static void firenze_init(void) +{ + /* We call hservices_init to relocate the hbrt image now, as the FSP + * may request an OCC load any time after ibm_fsp_init. + */ + hservices_init(); + + ibm_fsp_init(); +} + +DECLARE_PLATFORM(firenze) = { + .name = "Firenze", + .psi = &fsp_platform_psi, + .prd = &fsp_platform_prd, + .probe = firenze_probe, + .init = firenze_init, + .fast_reboot_init = fsp_console_reset, + .finalise_dt = ibm_fsp_finalise_dt, + .exit = ibm_fsp_exit, + .cec_power_down = ibm_fsp_cec_power_down, + .cec_reboot = ibm_fsp_cec_reboot, + .pci_setup_phb = firenze_pci_setup_phb, + .pci_get_slot_info = firenze_pci_get_slot_info, + .pci_add_loc_code = firenze_pci_add_loc_code, + .pci_probe_complete = firenze_pci_send_inventory, + .nvram_info = fsp_nvram_info, + .nvram_start_read = fsp_nvram_start_read, + .nvram_write = fsp_nvram_write, + .occ_timeout = ibm_fsp_occ_timeout, + .elog_commit = elog_fsp_commit, + .start_preload_resource = fsp_start_preload_resource, + .resource_loaded = fsp_resource_loaded, + .sensor_read = ibm_fsp_sensor_read, + .terminate = ibm_fsp_terminate, + .op_display = fsp_op_display, + .vpd_iohub_load = vpd_iohub_load, + .heartbeat_time = fsp_heartbeat_time, +}; diff --git a/roms/skiboot/platforms/ibm-fsp/fsp-vpd.c b/roms/skiboot/platforms/ibm-fsp/fsp-vpd.c new file mode 100644 index 000000000..52d2c8255 --- /dev/null +++ b/roms/skiboot/platforms/ibm-fsp/fsp-vpd.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#include <skiboot.h> +#include <vpd.h> +#include <string.h> +#include <fsp.h> +#include <device.h> +#include "ibm-fsp.h" + +static void *vpd_lid; +static size_t vpd_lid_size; +static uint32_t vpd_lid_no; + + +void vpd_iohub_load(struct dt_node *hub_node) +{ + uint8_t record[4] = { 'L','X','R','0' }; /* not null terminated */ + const void *valid_lx; + uint8_t lx_size; + int r; + const uint32_t *p; + const uint8_t *lx; + unsigned int lxrn; + + if (!fsp_present()) + return; + + p = dt_prop_get_def(hub_node, "ibm,vpd-lx-info", NULL); + if (!p) + return; + + lxrn = p[0]; + lx = (const char *)&p[1]; + + /* verify the lid preload has started */ + if (!vpd_lid || !vpd_lid_no) { + prlog(PR_WARNING, "VPD: WARNING: Unable to load VPD lid"); + return; + } + + r = fsp_wait_lid_loaded(vpd_lid_no); + + if (r) + goto fail; + + /* Validate it */ + if (lxrn < 9) + record[3] = '0' + lxrn; + else + memcpy(record, "VINI", 4); + + valid_lx = vpd_find(vpd_lid, vpd_lid_size, record, "LX", &lx_size); + if (!valid_lx || lx_size != 8) { + prerror("VPD: Cannot find validation LX record\n"); + goto fail; + } + if (memcmp(valid_lx, lx, 8) != 0) { + prerror("VPD: LX record mismatch !\n"); + goto fail; + } + + printf("VPD: Loaded %zu bytes\n", vpd_lid_size); + + dt_add_property(hub_node, "ibm,io-vpd", vpd_lid, vpd_lid_size); + free(vpd_lid); + return; + +fail: + free(vpd_lid); + vpd_lid = NULL; + prerror("VPD: Failed to load VPD LID\n"); + return; +} + +/* Helper to load a VPD LID. Pass a ptr to the corresponding LX keyword */ +static void *vpd_lid_preload(const uint8_t *lx) +{ + int rc; + + if (!fsp_present()) + return NULL; + + /* Now this is a guess game as we don't have the info from the + * pHyp folks. But basically, it seems to boil down to loading + * a LID whose name is 0x80e000yy where yy is the last 2 digits + * of the LX record in hex. + * + * [ Correction: After a chat with some folks, it looks like it's + * actually 4 digits, though the lid number is limited to fff + * so we weren't far off. ] + * + * For safety, we look for a matching LX record in an LXRn + * (n = lxrn argument) or in VINI if lxrn=0xff + */ + vpd_lid_no = 0x80e00000 | ((lx[6] & 0xf) << 8) | lx[7]; + + /* We don't quite know how to get to the LID directory so + * we don't know the size. Let's allocate 16K. All the VPD LIDs + * I've seen so far are much smaller. + */ +#define VPD_LID_MAX_SIZE 0x4000 + vpd_lid = malloc(VPD_LID_MAX_SIZE); + + if (!vpd_lid) { + prerror("VPD: Failed to allocate memory for LID\n"); + return NULL; + } + + /* Adjust LID number for flash side */ + vpd_lid_no = fsp_adjust_lid_side(vpd_lid_no); + printf("VPD: Trying to load VPD LID 0x%08x...\n", vpd_lid_no); + + vpd_lid_size = VPD_LID_MAX_SIZE; + + /* Load it from the FSP */ + rc = fsp_preload_lid(vpd_lid_no, vpd_lid, &vpd_lid_size); + if (rc) { + prerror("VPD: Error %d loading VPD LID\n", rc); + goto fail; + } + + return vpd_lid; + fail: + free(vpd_lid); + return NULL; +} + +void vpd_preload(struct dt_node *hub_node) +{ + const uint32_t *p; + const char *lxr; + + p = dt_prop_get_def(hub_node, "ibm,vpd-lx-info", NULL); + if (!p) + return; + + lxr = (const char *)&p[1]; + + vpd_lid = vpd_lid_preload(lxr); +} + +void preload_io_vpd(void) +{ + const struct dt_property *prop; + + prop = dt_find_property(dt_root, "ibm,io-vpd"); + if (!prop) { + /* LX VPD Lid not already loaded */ + vpd_preload(dt_root); + } +} diff --git a/roms/skiboot/platforms/ibm-fsp/hostservices.c b/roms/skiboot/platforms/ibm-fsp/hostservices.c new file mode 100644 index 000000000..accc0989a --- /dev/null +++ b/roms/skiboot/platforms/ibm-fsp/hostservices.c @@ -0,0 +1,912 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> + +#include <lock.h> +#include <device.h> +#include <compiler.h> +#include <hostservices.h> +#include <mem_region.h> +#include <xscom.h> +#include <fsp.h> +#include <chip.h> +#include <console.h> +#include <mem-map.h> +#include <timebase.h> +#include <occ.h> + +#define HOSTBOOT_RUNTIME_INTERFACE_VERSION 1 + +struct host_interfaces { + /** Interface version. */ + uint64_t interface_version; + + /** Put a string to the console. */ + void (*puts)(const char*); + /** Critical failure in runtime execution. */ + void (*assert)(void); + + /** OPTIONAL. Hint to environment that the page may be executed. */ + int (*set_page_execute)(void*); + + /** malloc */ + void *(*malloc)(size_t); + /** free */ + void (*free)(void*); + /** realloc */ + void *(*realloc)(void*, size_t); + + /** sendErrorLog + * @param[in] plid Platform Log identifier + * @param[in] data size in bytes + * @param[in] pointer to data + * @return 0 on success else error code + */ + int (*send_error_log)(uint32_t,uint32_t,void *); + + /** Scan communication read + * @param[in] chip_id (based on devtree defn) + * @param[in] address + * @param[in] pointer to 8-byte data buffer + * @return 0 on success else return code + */ + int (*scom_read)(uint64_t, uint64_t, void*); + + /** Scan communication write + * @param[in] chip_id (based on devtree defn) + * @param[in] address + * @param[in] pointer to 8-byte data buffer + * @return 0 on success else return code + */ + int (*scom_write)(uint64_t, uint64_t, const void *); + + /** lid_load + * Load a LID from PNOR, FSP, etc. + * + * @param[in] LID number. + * @param[out] Allocated buffer for LID. + * @param[out] Size of LID (in bytes). + * + * @return 0 on success, else RC. + */ + int (*lid_load)(uint32_t lid, void **buf, size_t *len); + + /** lid_unload + * Release memory from previously loaded LID. + * + * @param[in] Allocated buffer for LID to release. + * + * @return 0 on success, else RC. + */ + int (*lid_unload)(void *buf); + + /** Get the address of a reserved memory region by its devtree name. + * + * @param[in] Devtree name (ex. "ibm,hbrt-vpd-image") + * @return physical address of region (or NULL). + **/ + uint64_t (*get_reserved_mem)(const char*); + + /** + * @brief Force a core to be awake, or clear the force + * @param[in] i_core Core to wake up (pid) + * @param[in] i_mode 0=force awake + * 1=clear force + * 2=clear all previous forces + * @return rc non-zero on error + */ + int (*wakeup)( uint32_t i_core, uint32_t i_mode ); + + /** + * @brief Delay/sleep for at least the time given + * @param[in] seconds + * @param[in] nano seconds + */ + void (*nanosleep)(uint64_t i_seconds, uint64_t i_nano_seconds); + + // Reserve some space for future growth. + void (*reserved[32])(void); +}; + +struct runtime_interfaces { + /** Interface version. */ + uint64_t interface_version; + + /** Execute CxxTests that may be contained in the image. + * + * @param[in] - Pointer to CxxTestStats structure for results reporting. + */ + void (*cxxtestExecute)(void *); + /** Get a list of lids numbers of the lids known to HostBoot + * + * @param[out] o_num - the number of lids in the list + * @return a pointer to the list + */ + const uint32_t * (*get_lid_list)(size_t * o_num); + + /** Load OCC Image and common data into mainstore, also setup OCC BARSs + * + * @param[in] i_homer_addr_phys - The physical mainstore address of the + * start of the HOMER image + * @param[in] i_homer_addr_va - Virtual memory address of the HOMER image + * @param[in] i_common_addr_phys - The physical mainstore address of the + * OCC common area. + * @param[in] i_common_addr_va - Virtual memory address of the common area + * @param[in] i_chip - The HW chip id (XSCOM chip ID) + * @return 0 on success else return code + */ + int(*loadOCC)(uint64_t i_homer_addr_phys, + uint64_t i_homer_addr_va, + uint64_t i_common_addr_phys, + uint64_t i_common_addr_va, + uint64_t i_chip); + + /** Start OCC on all chips, by module + * + * @param[in] i_chip - Array of functional HW chip ids + * @Note The caller must include a complete modules worth of chips + * @param[in] i_num_chips - Number of chips in the array + * @return 0 on success else return code + */ + int (*startOCCs)(uint64_t* i_chip, + size_t i_num_chips); + + /** Stop OCC hold OCCs in reset + * + * @param[in] i_chip - Array of functional HW chip ids + * @Note The caller must include a complete modules worth of chips + * @param[in] i_num_chips - Number of chips in the array + * @return 0 on success else return code + */ + int (*stopOCCs)(uint64_t* i_chip, + size_t i_num_chips); + + /* Reserve some space for future growth. */ + void (*reserved[32])(void); +}; + +static struct runtime_interfaces *hservice_runtime; + +static char *hbrt_con_buf = (char *)HBRT_CON_START; +static size_t hbrt_con_pos; +static bool hbrt_con_wrapped; + +#define HBRT_CON_IN_LEN 0 +#define HBRT_CON_OUT_LEN (HBRT_CON_LEN - HBRT_CON_IN_LEN) + +static struct memcons hbrt_memcons __section(".data.memcons") = { + .magic = CPU_TO_BE64(MEMCONS_MAGIC), + .obuf_phys = CPU_TO_BE64(HBRT_CON_START), + .ibuf_phys = CPU_TO_BE64(HBRT_CON_START + HBRT_CON_OUT_LEN), + .obuf_size = CPU_TO_BE32(HBRT_CON_OUT_LEN), + .ibuf_size = CPU_TO_BE32(HBRT_CON_IN_LEN), +}; + +static void hservice_putc(char c) +{ + uint32_t opos; + + hbrt_con_buf[hbrt_con_pos++] = c; + if (hbrt_con_pos >= HBRT_CON_OUT_LEN) { + hbrt_con_pos = 0; + hbrt_con_wrapped = true; + } + + /* + * We must always re-generate memcons.out_pos because + * under some circumstances, the console script will + * use a broken putmemproc that does RMW on the full + * 8 bytes containing out_pos and in_prod, thus corrupting + * out_pos + */ + opos = hbrt_con_pos; + if (hbrt_con_wrapped) + opos |= MEMCONS_OUT_POS_WRAP; + lwsync(); + hbrt_memcons.out_pos = cpu_to_be32(opos); +} + +static void hservice_puts(const char *str) +{ + char c; + + while((c = *(str++)) != 0) + hservice_putc(c); + hservice_putc(10); +} + +static void hservice_mark(void) +{ + hservice_puts("--------------------------------------------------" + "--------------------------------------------------\n"); +} + +static void hservice_assert(void) +{ + /** + * @fwts-label HBRTassert + * @fwts-advice HBRT triggered assert: you need to debug HBRT + */ + prlog(PR_EMERG, "HBRT: Assertion from hostservices\n"); + abort(); +} + +static void *hservice_malloc(size_t size) +{ + return malloc(size); +} + +static void hservice_free(void *ptr) +{ + free(ptr); +} + + +static void *hservice_realloc(void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +struct hbrt_elog_ent { + void *buf; + unsigned int size; + unsigned int plid; + struct list_node link; +}; +static LIST_HEAD(hbrt_elogs); +static struct lock hbrt_elog_lock = LOCK_UNLOCKED; +static bool hbrt_elog_sending; +static void hservice_start_elog_send(void); + +static void hservice_elog_write_complete(struct fsp_msg *msg) +{ + struct hbrt_elog_ent *ent = msg->user_data; + + lock(&hbrt_elog_lock); + prlog(PR_DEBUG, "HBRT: Completed send of PLID 0x%08x\n", ent->plid); + hbrt_elog_sending = false; + fsp_tce_unmap(PSI_DMA_HBRT_LOG_WRITE_BUF, + PSI_DMA_HBRT_LOG_WRITE_BUF_SZ); + free(ent->buf); + free(ent); + fsp_freemsg(msg); + hservice_start_elog_send(); + unlock(&hbrt_elog_lock); +} + +static void hservice_start_elog_send(void) +{ + struct fsp_msg *msg; + struct hbrt_elog_ent *ent; + + again: + if (list_empty(&hbrt_elogs)) + return; + ent = list_pop(&hbrt_elogs, struct hbrt_elog_ent, link); + + hbrt_elog_sending = true; + + prlog(PR_DEBUG, "HBRT: Starting send of PLID 0x%08x\n", ent->plid); + + fsp_tce_map(PSI_DMA_HBRT_LOG_WRITE_BUF, ent->buf, + PSI_DMA_HBRT_LOG_WRITE_BUF_SZ); + + msg = fsp_mkmsg(FSP_CMD_WRITE_SP_DATA, 6, FSP_DATASET_HBRT_BLOB, + 0, 0, 0, PSI_DMA_HBRT_LOG_WRITE_BUF, + ent->size); + + if (!msg) { + prerror("HBRT: Failed to create error msg log to FSP\n"); + goto error; + } + msg->user_data = ent; + if (!fsp_queue_msg(msg, hservice_elog_write_complete)) + return; + prerror("FSP: Error queueing elog update\n"); + error: + if (msg) + fsp_freemsg(msg); + fsp_tce_unmap(PSI_DMA_HBRT_LOG_WRITE_BUF, + PSI_DMA_HBRT_LOG_WRITE_BUF_SZ); + free(ent->buf); + free(ent); + hbrt_elog_sending = false; + goto again; +} + +int hservice_send_error_log(uint32_t plid, uint32_t dsize, void *data) +{ + struct hbrt_elog_ent *ent; + void *abuf; + + prlog(PR_ERR, "HBRT: Error log generated with plid 0x%08x\n", plid); + + /* We only know how to send error logs to FSP */ + if (!fsp_present()) { + prerror("HBRT: Warning, error log from HBRT discarded !\n"); + return OPAL_UNSUPPORTED; + } + if (dsize > PSI_DMA_HBRT_LOG_WRITE_BUF_SZ) { + prerror("HBRT: Warning, error log from HBRT too big (%d) !\n", + dsize); + dsize = PSI_DMA_HBRT_LOG_WRITE_BUF_SZ; + } + + lock(&hbrt_elog_lock); + + /* Create and populate a tracking structure */ + ent = zalloc(sizeof(struct hbrt_elog_ent)); + if (!ent) { + unlock(&hbrt_elog_lock); + return OPAL_NO_MEM; + } + + /* Grab a 4k aligned page */ + abuf = memalign(0x1000, PSI_DMA_HBRT_LOG_WRITE_BUF_SZ); + if (!abuf) { + free(ent); + unlock(&hbrt_elog_lock); + return OPAL_NO_MEM; + } + memset(abuf, 0, PSI_DMA_HBRT_LOG_WRITE_BUF_SZ); + memcpy(abuf, data, dsize); + ent->buf = abuf; + ent->size = dsize; + ent->plid = plid; + list_add_tail(&hbrt_elogs, &ent->link); + if (!hbrt_elog_sending) + hservice_start_elog_send(); + unlock(&hbrt_elog_lock); + + return 0; +} + +static int hservice_scom_read(uint64_t chip_id, uint64_t addr, void *buf) +{ + return xscom_read(chip_id, addr, buf); +} + +static int hservice_scom_write(uint64_t chip_id, uint64_t addr, + const void *buf) +{ + uint64_t val; + + memcpy(&val, buf, sizeof(val)); + return xscom_write(chip_id, addr, val); +} + +struct hbrt_lid { + void *load_addr; + size_t len; + uint32_t id; + struct list_node link; +}; +static LIST_HEAD(hbrt_lid_list); + +static bool hbrt_lid_preload_complete = false; + +bool hservices_lid_preload_complete(void) +{ + return hbrt_lid_preload_complete; +} + +/* TODO: Few of the following routines can be generalized */ +static int __hservice_lid_load(uint32_t lid, void **buf, size_t *len) +{ + int rc; + + /* Adjust LID side first or we get a cache mismatch */ + lid = fsp_adjust_lid_side(lid); + + /* + * Allocate a new buffer and load the LID into it + * XXX: We currently use the same size for each HBRT lid. + */ + *buf = malloc(HBRT_LOAD_LID_SIZE); + *len = HBRT_LOAD_LID_SIZE; + rc = fsp_preload_lid(lid, *buf, len); + rc = fsp_wait_lid_loaded(lid); + if (rc != 0) + /* Take advantage of realloc corner case here. */ + *len = 0; + *buf = realloc(*buf, *len); + + prlog(PR_DEBUG, "HBRT: LID 0x%08x successfully loaded, len=0x%lx\n", + lid, (unsigned long)len); + + return rc; +} + +static int __hservice_lid_preload(const uint32_t lid) +{ + struct hbrt_lid *hlid; + void *buf; + size_t len; + int rc; + + hlid = zalloc(sizeof(struct hbrt_lid)); + if (!hlid) { + prerror("HBRT: Could not allocate struct hbrt_lid\n"); + return OPAL_NO_MEM; + } + + rc = __hservice_lid_load(lid, &buf, &len); + if (rc) { + free(hlid); + return rc; + } + + hlid->load_addr = buf; + hlid->len = len; + hlid->id = lid; + list_add_tail(&hbrt_lid_list, &hlid->link); + + return 0; +} + +/* Find and preload all lids needed by hostservices */ +void hservices_lid_preload(void) +{ + const uint32_t *lid_list = NULL; + size_t num_lids; + int i; + + if (!hservice_runtime) + return; + + lid_list = (const uint32_t *)hservice_runtime->get_lid_list(&num_lids); + if (!lid_list) { + prerror("HBRT: get_lid_list() returned NULL\n"); + return; + } + + prlog(PR_INFO, "HBRT: %d lids to load\n", (int)num_lids); + + /* Currently HBRT needs only one (OCC) lid */ + for (i = 0; i < num_lids; i++) + __hservice_lid_preload(lid_list[i]); + + hbrt_lid_preload_complete = true; + occ_poke_load_queue(); +} + +static int hservice_lid_load(uint32_t lid, void **buf, size_t *len) +{ + struct hbrt_lid *hlid; + + prlog(PR_INFO, "HBRT: Lid load request for 0x%08x\n", lid); + + if (list_empty(&hbrt_lid_list)) { /* Should not happen */ + /** + * @fwts-label HBRTlidLoadFail + * @fwts-advice Firmware should have aborted boot + */ + prlog(PR_CRIT, "HBRT: LID Load failed\n"); + abort(); + } + + list_for_each(&hbrt_lid_list, hlid, link) { + if (hlid->id == lid) { + *buf = hlid->load_addr; + *len = hlid->len; + prlog(PR_DEBUG, "HBRT: LID Serviced from cache," + " %x, len=0x%lx\n", hlid->id, hlid->len); + return 0; + } + } + return -ENOENT; +} + +static int hservice_lid_unload(void *buf __unused) +{ + /* We do nothing as the LID is held in cache */ + return 0; +} + +static uint64_t hservice_get_reserved_mem(const char *name) +{ + struct mem_region *region; + uint64_t ret; + + /* We assume it doesn't change after we've unlocked it, but + * lock ensures list is safe to walk. */ + lock(&mem_region_lock); + region = find_mem_region(name); + ret = region ? region->start : 0; + unlock(&mem_region_lock); + + if (!ret) + prlog(PR_WARNING, "HBRT: Mem region '%s' not found !\n", name); + + return ret; +} + +static void hservice_nanosleep(uint64_t i_seconds, uint64_t i_nano_seconds) +{ + struct timespec ts; + + ts.tv_sec = i_seconds; + ts.tv_nsec = i_nano_seconds; + nanosleep_nopoll(&ts, NULL); +} + +int hservice_wakeup(uint32_t i_core, uint32_t i_mode) +{ + struct cpu_thread *cpu; + int rc = OPAL_SUCCESS; + + switch (proc_gen) { + case proc_gen_p8: + /* + * Mask out the top nibble of i_core since it may contain + * 0x4 (which we use for XSCOM targeting) + */ + i_core &= 0x0fffffff; + i_core <<= 3; + break; + case proc_gen_p9: + i_core &= SPR_PIR_P9_MASK; + i_core <<= 2; + break; + case proc_gen_p10: + i_core &= SPR_PIR_P10_MASK; + i_core <<= 2; + break; + default: + return OPAL_UNSUPPORTED; + } + + /* What do we need to do ? */ + switch(i_mode) { + case 0: /* Assert special wakeup */ + cpu = find_cpu_by_pir(i_core); + if (!cpu) + return OPAL_PARAMETER; + prlog(PR_TRACE, "HBRT: Special wakeup assert for core 0x%x," + " count=%d\n", i_core, cpu->hbrt_spec_wakeup); + if (cpu->hbrt_spec_wakeup == 0) + rc = dctl_set_special_wakeup(cpu); + if (rc == 0) + cpu->hbrt_spec_wakeup++; + return rc; + case 1: /* Deassert special wakeup */ + cpu = find_cpu_by_pir(i_core); + if (!cpu) + return OPAL_PARAMETER; + prlog(PR_TRACE, "HBRT: Special wakeup release for core" + " 0x%x, count=%d\n", i_core, cpu->hbrt_spec_wakeup); + if (cpu->hbrt_spec_wakeup == 0) { + prerror("HBRT: Special wakeup clear" + " on core 0x%x with count=0\n", + i_core); + return OPAL_WRONG_STATE; + } + /* What to do with count on errors ? */ + cpu->hbrt_spec_wakeup--; + if (cpu->hbrt_spec_wakeup == 0) + rc = dctl_clear_special_wakeup(cpu); + return rc; + case 2: /* Clear all special wakeups */ + prlog(PR_DEBUG, "HBRT: Special wakeup release for all cores\n"); + for_each_cpu(cpu) { + if (cpu->hbrt_spec_wakeup) { + cpu->hbrt_spec_wakeup = 0; + /* What to do on errors ? */ + dctl_clear_special_wakeup(cpu); + } + } + return OPAL_SUCCESS; + default: + return OPAL_PARAMETER; + } +} + +static struct host_interfaces hinterface = { + .interface_version = HOSTBOOT_RUNTIME_INTERFACE_VERSION, + .puts = hservice_puts, + .assert = hservice_assert, + .malloc = hservice_malloc, + .free = hservice_free, + .realloc = hservice_realloc, + .send_error_log = hservice_send_error_log, + .scom_read = hservice_scom_read, + .scom_write = hservice_scom_write, + .lid_load = hservice_lid_load, + .lid_unload = hservice_lid_unload, + .get_reserved_mem = hservice_get_reserved_mem, + .wakeup = hservice_wakeup, + .nanosleep = hservice_nanosleep, +}; + +int host_services_occ_load(void) +{ + struct proc_chip *chip; + int rc = 0; + + prlog(PR_DEBUG, "HBRT: OCC Load requested\n"); + + if (!(hservice_runtime && hservice_runtime->loadOCC)) { + prerror("HBRT: No hservice_runtime->loadOCC\n"); + return -ENOENT; + } + + for_each_chip(chip) { + + prlog(PR_DEBUG, "HBRT: Calling loadOCC() homer" + " %016llx, occ_common_area %016llx, chip %04x\n", + chip->homer_base, + chip->occ_common_base, + chip->id); + + rc = hservice_runtime->loadOCC(chip->homer_base, + chip->homer_base, + chip->occ_common_base, + chip->occ_common_base, + chip->id); + + hservice_mark(); + prlog(PR_DEBUG, "HBRT: -> rc = %d\n", rc); + } + return rc; +} + +int host_services_occ_start(void) +{ + struct proc_chip *chip; + int i, rc = 0, nr_chips=0; + uint64_t chipids[MAX_CHIPS]; + + prlog(PR_INFO, "HBRT: OCC Start requested\n"); + + if (!(hservice_runtime && hservice_runtime->startOCCs)) { + prerror("HBRT: No hservice_runtime->startOCCs\n"); + return -ENOENT; + } + + for_each_chip(chip) { + chipids[nr_chips++] = chip->id; + } + + for (i = 0; i < nr_chips; i++) + prlog(PR_TRACE, "HBRT: Calling startOCC() for %04llx\n", + chipids[i]); + + /* Lets start all OCC */ + rc = hservice_runtime->startOCCs(chipids, nr_chips); + hservice_mark(); + prlog(PR_DEBUG, "HBRT: startOCCs() rc = %d\n", rc); + return rc; +} + +int host_services_occ_stop(void) +{ + int i, rc = 0, nr_slaves = 0, nr_masters = 0; + uint64_t *master_chipids = NULL, *slave_chipids = NULL; + + prlog(PR_INFO, "HBRT: OCC Stop requested\n"); + + if (!(hservice_runtime && hservice_runtime->stopOCCs)) { + prerror("HBRT: No hservice_runtime->stopOCCs\n"); + return -ENOENT; + } + + rc = find_master_and_slave_occ(&master_chipids, &slave_chipids, + &nr_masters, &nr_slaves); + if (rc) + goto out; + + for (i = 0; i < nr_slaves; i++) + prlog(PR_TRACE, "HBRT: Calling stopOCC() for %04llx ", + slave_chipids[i]); + + if (!nr_slaves) + goto master; + + /* Lets STOP all the slave OCC */ + rc = hservice_runtime->stopOCCs(slave_chipids, nr_slaves); + prlog(PR_DEBUG, "HBRT: stopOCCs() slave rc = %d\n", rc); + +master: + for (i = 0; i < nr_masters; i++) + prlog(PR_TRACE, "HBRT: Calling stopOCC() for %04llx ", + master_chipids[i]); + + /* Lets STOP all the master OCC */ + rc = hservice_runtime->stopOCCs(master_chipids, nr_masters); + + hservice_mark(); + prlog(PR_DEBUG, "HBRT: stopOCCs() master rc = %d\n", rc); + +out: + free(master_chipids); + free(slave_chipids); + return rc; +} + +bool hservices_init(void) +{ + void *code = NULL; + struct runtime_interfaces *(*hbrt_init)(struct host_interfaces *); + + struct function_descriptor { + void *addr; + void *toc; + } fdesc; + + code = (void *)hservice_get_reserved_mem("ibm,hbrt-code-image"); + if (!code) { + prerror("HBRT: No ibm,hbrt-code-image found.\n"); + return false; + } + + if (memcmp(code, "HBRTVERS", 8) != 0) { + prerror("HBRT: Bad eyecatcher for ibm,hbrt-code-image!\n"); + return false; + } + + prlog(PR_INFO, "HBRT: Found HostBoot Runtime version %llu\n", + ((u64 *)code)[1]); + + /* We enter at 0x100 into the image. */ + fdesc.addr = code + 0x100; + /* It doesn't care about TOC */ + fdesc.toc = NULL; + + hbrt_init = (void *)&fdesc; + + hservice_runtime = hbrt_init(&hinterface); + hservice_mark(); + if (!hservice_runtime) { + prerror("HBRT: Host services init failed\n"); + return false; + } + + prlog(PR_INFO, "HBRT: Interface version %llu\n", + hservice_runtime->interface_version); + + return true; +} + +static void hservice_send_hbrt_msg_resp(struct fsp_msg *msg) +{ + int status = (msg->resp->word1 >> 8) & 0xff; + + fsp_freemsg(msg); + if (status) { + prlog(PR_NOTICE, "HBRT: HBRT to FSP MBOX command failed " + "[rc=0x%x]\n", status); + } + + fsp_tce_unmap(PSI_DMA_HBRT_FSP_MSG, PSI_DMA_HBRT_FSP_MSG_SIZE); + /* Send response data to HBRT */ + prd_fw_resp_fsp_response(status); +} + +#define FSP_STATUS_RR (-8193) +/* Caller takes care of serializing MBOX message */ +int hservice_send_hbrt_msg(void *data, u64 dsize) +{ + uint32_t tce_len, offset; + int rc; + uint64_t addr; + struct fsp_msg *msg; + + prlog(PR_NOTICE, "HBRT: HBRT - FSP message generated\n"); + + /* We only support FSP based system */ + if (!fsp_present()) { + prlog(PR_DEBUG, + "HBRT: Warning, HBRT - FSP message discarded!\n"); + return OPAL_UNSUPPORTED; + } + + /* + * If FSP is in R/R then send specific return code to HBRT (inside + * HBRT message) and return success to caller (opal_prd_msg()). + */ + if (fsp_in_rr()) { + prlog(PR_DEBUG, + "HBRT: FSP is in R/R. Dropping HBRT - FSP message\n"); + prd_fw_resp_fsp_response(FSP_STATUS_RR); + return OPAL_SUCCESS; + } + + /* Adjust address, size for TCE mapping */ + addr = (u64)data & ~TCE_MASK; + offset = (u64)data & TCE_MASK; + tce_len = ALIGN_UP((dsize + offset), TCE_PSIZE); + + if (tce_len > PSI_DMA_HBRT_FSP_MSG_SIZE) { + prlog(PR_DEBUG, + "HBRT: HBRT - FSP message is too big, discarded\n"); + return OPAL_PARAMETER; + } + fsp_tce_map(PSI_DMA_HBRT_FSP_MSG, (void *)addr, tce_len); + + msg = fsp_mkmsg(FSP_CMD_HBRT_TO_FSP, 3, 0, + (PSI_DMA_HBRT_FSP_MSG + offset), dsize); + if (!msg) { + prlog(PR_DEBUG, + "HBRT: Failed to create HBRT - FSP message to FSP\n"); + rc = OPAL_NO_MEM; + goto out_tce_unmap; + } + + rc = fsp_queue_msg(msg, hservice_send_hbrt_msg_resp); + if (rc == 0) + return rc; + + prlog(PR_DEBUG, "HBRT: Failed to queue HBRT message to FSP\n"); + fsp_freemsg(msg); + +out_tce_unmap: + fsp_tce_unmap(PSI_DMA_HBRT_FSP_MSG, PSI_DMA_HBRT_FSP_MSG_SIZE); + return rc; +} + +void hservice_hbrt_msg_response(uint32_t rc) +{ + struct fsp_msg *resp; + + resp = fsp_mkmsg(FSP_RSP_FSP_TO_HBRT | (uint8_t)rc, 0); + if (!resp) { + prlog(PR_DEBUG, + "HBRT: Failed to allocate FSP - HBRT response message\n"); + return; + } + + if (fsp_queue_msg(resp, fsp_freemsg)) { + prlog(PR_DEBUG, + "HBRT: Failed to send FSP - HBRT response message\n"); + fsp_freemsg(resp); + return; + } +} + +/* FSP sends HBRT notification message. Pass this message to HBRT */ +static bool hservice_hbrt_msg_notify(uint32_t cmd_sub_mod, struct fsp_msg *msg) +{ + u32 tce_token, len; + void *buf; + + if (cmd_sub_mod != FSP_CMD_FSP_TO_HBRT) + return false; + + prlog(PR_TRACE, "HBRT: FSP - HBRT message generated\n"); + + tce_token = fsp_msg_get_data_word(msg, 1); + len = fsp_msg_get_data_word(msg, 2); + buf = fsp_inbound_buf_from_tce(tce_token); + if (!buf) { + prlog(PR_DEBUG, "HBRT: Invalid inbound data\n"); + hservice_hbrt_msg_response(FSP_STATUS_INVALID_DATA); + return true; + } + + if (prd_hbrt_fsp_msg_notify(buf, len)) { + hservice_hbrt_msg_response(FSP_STATUS_GENERIC_ERROR); + prlog(PR_NOTICE, "Unable to send FSP - HBRT message\n"); + } + + return true; +} + +static struct fsp_client fsp_hbrt_msg_client = { + .message = hservice_hbrt_msg_notify, +}; + +/* Register for FSP 0xF2 class messages */ +void hservice_fsp_init(void) +{ + if (proc_gen < proc_gen_p9) + return; + + if (!fsp_present()) + return; + + /* Register for Class F2 */ + fsp_register_client(&fsp_hbrt_msg_client, FSP_MCLASS_HBRT); +} diff --git a/roms/skiboot/platforms/ibm-fsp/ibm-fsp.h b/roms/skiboot/platforms/ibm-fsp/ibm-fsp.h new file mode 100644 index 000000000..bb191645c --- /dev/null +++ b/roms/skiboot/platforms/ibm-fsp/ibm-fsp.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#ifndef __IBM_FSP_COMMON_H +#define __IBM_FSP_COMMON_H + +extern void ibm_fsp_init(void); +extern void ibm_fsp_exit(void); +void ibm_fsp_finalise_dt(bool is_reboot); + +extern int64_t ibm_fsp_cec_power_down(uint64_t request); +extern int64_t ibm_fsp_cec_reboot(void); + +struct errorlog; +extern int elog_fsp_commit(struct errorlog *buf); + +extern int64_t ibm_fsp_sensor_read(uint32_t sensor_hndl, int token, + __be64 *sensor_data); + +/* Apollo PCI support */ +extern void apollo_pci_setup_phb(struct phb *phb, + unsigned int index); +extern void apollo_pci_get_slot_info(struct phb *phb, + struct pci_device *pd); + +/* Firenze PCI support */ +extern void firenze_pci_send_inventory(void); +extern void firenze_pci_setup_phb(struct phb *phb, + unsigned int index); +extern void firenze_pci_get_slot_info(struct phb *phb, + struct pci_device *pd); +extern void firenze_pci_add_loc_code(struct dt_node *np, + struct pci_device *pd); + +/* VPD support */ +void vpd_iohub_load(struct dt_node *hub_node); +void vpd_preload(struct dt_node *hub_node); + +/* Platform heartbeat time */ +int __attrconst fsp_heartbeat_time(void); + +extern struct platform_psi fsp_platform_psi; +extern struct platform_prd fsp_platform_prd; + +#endif /* __IBM_FSP_COMMON_H */ 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); +} diff --git a/roms/skiboot/platforms/ibm-fsp/lxvpd.h b/roms/skiboot/platforms/ibm-fsp/lxvpd.h new file mode 100644 index 000000000..4b771f3f7 --- /dev/null +++ b/roms/skiboot/platforms/ibm-fsp/lxvpd.h @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2016 IBM Corp. */ + +#ifndef __LXVPD_H +#define __LXVPD_H + +#define LX_VPD_1S2U_BACKPLANE 0x3100040100300041ull +#define LX_VPD_2S2U_BACKPLANE 0x3100040100300042ull +#define LX_VPD_SHARK_BACKPLANE 0x3100040100300942ull +#define LX_VPD_1S4U_BACKPLANE 0x3100040100300043ull +#define LX_VPD_2S4U_BACKPLANE 0x3100040100300044ull + +struct slot_p0 { + union { + uint8_t byte; + struct { +#if HAVE_BIG_ENDIAN + uint8_t pluggable:1; + uint8_t pluggable_location:3; + uint8_t power_ctl:1; + uint8_t rsvd_5:1; + uint8_t upstream_port:1; + uint8_t alt_load_source:1; +#else + uint8_t alt_load_source:1; + uint8_t upstream_port:1; + uint8_t rsvd_5:1; + uint8_t power_ctl:1; + uint8_t pluggable_location:3; + uint8_t pluggable:1; +#endif + }; + }; +}; + +struct slot_p1 { +#if HAVE_BIG_ENDIAN + uint8_t rsvd_0:1; + uint8_t wired_lanes:3; + uint8_t rsvd_4:4; +#else + uint8_t rsvd_4:4; + uint8_t wired_lanes:3; + uint8_t rsvd_0:1; +#endif +}; + +struct slot_p2 { +#if HAVE_BIG_ENDIAN + uint8_t rsvd_0:1; + uint8_t bus_clock:3; + uint8_t connector_type:4; +#else + uint8_t connector_type:4; + uint8_t bus_clock:3; + uint8_t rsvd_0:1; +#endif +}; + +struct slot_p3 { + union { + uint8_t byte; + struct { +#if HAVE_BIG_ENDIAN + uint8_t height:1; + uint8_t length:1; + uint8_t left_mech:1; + uint8_t right_mech:1; + uint8_t pow_led_kvm:1; + uint8_t pow_led_fsp:1; + uint8_t attn_led_kvm:1; + uint8_t attn_led_fsp:1; +#else + uint8_t attn_led_fsp:1; + uint8_t attn_led_kvm:1; + uint8_t pow_led_fsp:1; + uint8_t pow_led_kvm:1; + uint8_t right_mech:1; + uint8_t left_mech:1; + uint8_t length:1; + uint8_t height:1; +#endif + }; + }; +}; + +struct pci_slot_entry_1004 { + uint8_t pba; + uint8_t sba; + uint8_t phb_or_slot_type; + char label[3]; + __be16 bis; + struct slot_p0 p0; + struct slot_p1 p1; + struct slot_p2 p2; + struct slot_p3 p3; + uint8_t left_pitch; + uint8_t right_pitch; + uint8_t slot_index; + uint8_t max_slot_power; +}; + +/* P8 PCI Slot Entry Definitions -- 1005 */ +struct pci_slot_entry_1005 { + union { + uint8_t pba; + struct { +#if HAVE_BIG_ENDIAN + uint8_t switch_id:4; + uint8_t vswitch_id:4; +#else + uint8_t vswitch_id:4; + uint8_t switch_id:4; +#endif + }; + }; + uint8_t switch_device_id; +#if HAVE_BIG_ENDIAN + uint8_t slot_type:4; + uint8_t phb_id:4; +#else + uint8_t phb_id:4; + uint8_t slot_type:4; +#endif + char label[8]; + uint8_t rsvd_11[4]; + struct slot_p0 p0; + struct slot_p1 p1; + struct slot_p2 p2; + struct slot_p3 p3; + uint8_t left_pitch; + uint8_t right_pitch; + uint8_t slot_index; + uint8_t rsvd_22[2]; +}; + +struct lxvpd_pci_slot { + struct pci_slot *pci_slot; + uint8_t switch_id; + uint8_t vswitch_id; + uint8_t dev_id; + char label[9]; + bool pluggable; + bool power_ctl; + bool upstream_port; + uint8_t wired_lanes; + uint8_t bus_clock; + uint8_t connector_type; + uint8_t card_desc; + uint8_t card_mech; + uint8_t pwr_led_ctl; + uint8_t attn_led_ctl; + uint8_t slot_index; +}; + +extern void lxvpd_process_slot_entries(struct phb *phb, struct dt_node *node, + uint8_t chip_id, uint8_t index, + uint32_t slot_size); +extern void *lxvpd_get_slot(struct pci_slot *slot); +extern void lxvpd_extract_info(struct pci_slot *slot, + struct lxvpd_pci_slot *s); +extern void lxvpd_add_slot_properties(struct pci_slot *slot, + struct dt_node *np); +#endif /* __LXVPD_H */ diff --git a/roms/skiboot/platforms/ibm-fsp/zz.c b/roms/skiboot/platforms/ibm-fsp/zz.c new file mode 100644 index 000000000..493d6030a --- /dev/null +++ b/roms/skiboot/platforms/ibm-fsp/zz.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2016-2019 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <fsp.h> +#include <pci.h> +#include <pci-cfg.h> +#include <chip.h> +#include <i2c.h> +#include <timebase.h> +#include <hostservices.h> +#include <npu2.h> + +#include "ibm-fsp.h" +#include "lxvpd.h" + +static const char *zz_ocapi_slot_label(uint32_t chip_id, + uint32_t brick_index) +{ + const char *name = NULL; + + if (chip_id == 0) { + if (brick_index == 2) + name = "P1-T5"; + else + name = "P1-T6"; + } else { + if (brick_index == 2) + name = "P1-T7"; + else + name = "P1-T8"; + } + return name; +} + +/* We don't yet create NPU device nodes on ZZ, but these values are correct */ +static const struct platform_ocapi zz_ocapi = { + .i2c_engine = 1, + .i2c_port = 4, + .i2c_reset_addr = 0x20, + .i2c_reset_brick2 = (1 << 1), + .i2c_reset_brick3 = (1 << 6), + .i2c_reset_brick4 = 0, /* unused */ + .i2c_reset_brick5 = 0, /* unused */ + .i2c_presence_addr = 0x20, + .i2c_presence_brick2 = (1 << 2), /* bottom connector */ + .i2c_presence_brick3 = (1 << 7), /* top connector */ + .i2c_presence_brick4 = 0, /* unused */ + .i2c_presence_brick5 = 0, /* unused */ + .odl_phy_swap = true, + .ocapi_slot_label = zz_ocapi_slot_label, +}; + +#define NPU_BASE 0x5011000 +#define NPU_SIZE 0x2c +#define NPU_INDIRECT0 0x8000000009010c3f /* OB0 - no OB3 on ZZ */ + +static void create_link(struct dt_node *npu, int group, int index) +{ + struct dt_node *link; + uint32_t lane_mask; + + switch (index) { + case 2: + lane_mask = 0xf1e000; /* 0-3, 7-10 */ + break; + case 3: + lane_mask = 0x00078f; /* 13-16, 20-23 */ + break; + default: + assert(0); + } + + link = dt_new_addr(npu, "link", index); + dt_add_property_string(link, "compatible", "ibm,npu-link"); + dt_add_property_cells(link, "ibm,npu-link-index", index); + dt_add_property_u64s(link, "ibm,npu-phy", NPU_INDIRECT0); + dt_add_property_cells(link, "ibm,npu-lane-mask", lane_mask); + dt_add_property_cells(link, "ibm,npu-group-id", group); + dt_add_property_u64s(link, "ibm,link-speed", 25000000000ul); +} + +static void add_opencapi_dt_nodes(void) +{ + struct dt_node *npu, *xscom; + int npu_index = 0; + + /* + * In an ideal world, we should get all the NPU links + * information from HDAT. But after some effort, HDAT is still + * giving incorrect information for opencapi. As of this + * writing: + * 1. link usage is wrong for most FPGA cards (0xFFFF vs. 2) + * 2. the 24-bit lane mask is aligned differently than on + * other platforms (witherspoon) + * 3. connecting a link entry in HDAT to the real physical + * link will need extra work: + * - HDAT does presence detection and only lists links with + * an adapter, so we cannot use default ordering like on + * witherspoon + * - best option is probably the brick ID field (offset 8). + * It's coming straight from the MRW, but seems to match + * what we expect (2 or 3). Would need to be checked. + * + * To make things more fun, any change in the HDAT data needs + * to be coordinated with PHYP, which is using (some of) those + * fields. + * + * As a consequence: + * 1. the hdat parsing code in skiboot remains disabled (for + * opencapi) + * 2. we hard-code the NPU and links entries in the device + * tree. + * + * Getting the data from HDAT would have the advantage of + * providing the real link speed (20.0 vs. 25.78125 gbps), + * which is useful as there's one speed-dependent setting we + * need to do when initializing the NPU. Our hard coded + * definition assumes the higher speed and may need tuning in + * debug scenario using a lower link speed. + */ + dt_for_each_compatible(dt_root, xscom, "ibm,xscom") { + /* + * our hdat parsing code may create NPU nodes with no + * links, so let's make sure we start from a clean + * state + */ + npu = dt_find_by_name_addr(xscom, "npu", NPU_BASE); + if (npu) + dt_free(npu); + + npu = dt_new_addr(xscom, "npu", NPU_BASE); + dt_add_property_cells(npu, "reg", NPU_BASE, NPU_SIZE); + dt_add_property_strings(npu, "compatible", "ibm,power9-npu"); + dt_add_property_cells(npu, "ibm,npu-index", npu_index++); + dt_add_property_cells(npu, "ibm,npu-links", 2); + + create_link(npu, 1, 2); + create_link(npu, 2, 3); + } +} + +static bool zz_probe(void) +{ + /* FIXME: make this neater when the dust settles */ + if (dt_node_is_compatible(dt_root, "ibm,zz-1s2u") || + dt_node_is_compatible(dt_root, "ibm,zz-1s4u") || + dt_node_is_compatible(dt_root, "ibm,zz-2s2u") || + dt_node_is_compatible(dt_root, "ibm,zz-2s4u") || + dt_node_is_compatible(dt_root, "ibm,zz-1s4u+gen4") || + dt_node_is_compatible(dt_root, "ibm,zz-2s2u+gen4") || + dt_node_is_compatible(dt_root, "ibm,zz-2s4u+gen4")) { + + add_opencapi_dt_nodes(); + return true; + } + + /* Add Fleetwood FSP platform and map it to ZZ */ + if (dt_node_is_compatible(dt_root, "ibm,fleetwood-m9s")) { + return true; + } + + /* Add Denali FSP platform and map it to ZZ */ + if (dt_node_is_compatible(dt_root, "ibm,denali")) { + return true; + } + + return false; +} + +static uint32_t ibm_fsp_occ_timeout(void) +{ + /* Use a fixed 60s value for now */ + return 60; +} + +static void zz_init(void) +{ + ibm_fsp_init(); + hservice_fsp_init(); +} + +DECLARE_PLATFORM(zz) = { + .name = "ZZ", + .psi = &fsp_platform_psi, + .prd = &fsp_platform_prd, + .probe = zz_probe, + .init = zz_init, + .fast_reboot_init = fsp_console_reset, + .finalise_dt = ibm_fsp_finalise_dt, + .exit = ibm_fsp_exit, + .cec_power_down = ibm_fsp_cec_power_down, + .cec_reboot = ibm_fsp_cec_reboot, + .pci_setup_phb = firenze_pci_setup_phb, + .pci_get_slot_info = firenze_pci_get_slot_info, + .pci_add_loc_code = firenze_pci_add_loc_code, + .pci_probe_complete = firenze_pci_send_inventory, + .nvram_info = fsp_nvram_info, + .nvram_start_read = fsp_nvram_start_read, + .nvram_write = fsp_nvram_write, + .occ_timeout = ibm_fsp_occ_timeout, + .elog_commit = elog_fsp_commit, + .start_preload_resource = fsp_start_preload_resource, + .resource_loaded = fsp_resource_loaded, + .sensor_read = ibm_fsp_sensor_read, + .terminate = ibm_fsp_terminate, + .ocapi = &zz_ocapi, + .npu2_device_detect = npu2_i2c_presence_detect, + .op_display = fsp_op_display, + .vpd_iohub_load = vpd_iohub_load, + .heartbeat_time = fsp_heartbeat_time, +}; |