diff options
Diffstat (limited to 'roms/skiboot/platforms')
46 files changed, 10237 insertions, 0 deletions
diff --git a/roms/skiboot/platforms/Makefile.inc b/roms/skiboot/platforms/Makefile.inc new file mode 100644 index 000000000..3269532a3 --- /dev/null +++ b/roms/skiboot/platforms/Makefile.inc @@ -0,0 +1,12 @@ +PLATDIR = platforms + +SUBDIRS += $(PLATDIR) +PLATFORMS = $(PLATDIR)/built-in.a + +include $(SRC)/$(PLATDIR)/ibm-fsp/Makefile.inc +include $(SRC)/$(PLATDIR)/rhesus/Makefile.inc +include $(SRC)/$(PLATDIR)/astbmc/Makefile.inc +include $(SRC)/$(PLATDIR)/mambo/Makefile.inc +include $(SRC)/$(PLATDIR)/qemu/Makefile.inc + +$(PLATFORMS): $(IBM_FSP) $(RHESUS) $(ASTBMC) $(MAMBO) $(QEMU) diff --git a/roms/skiboot/platforms/astbmc/Makefile.inc b/roms/skiboot/platforms/astbmc/Makefile.inc new file mode 100644 index 000000000..070813231 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/Makefile.inc @@ -0,0 +1,13 @@ +SUBDIRS += $(PLATDIR)/astbmc + +ASTBMC_OBJS = pnor.o common.o slots.o \ + palmetto.o habanero.o firestone.o \ + p8dtu.o p8dnu.o \ + garrison.o barreleye.o \ + witherspoon.o zaius.o romulus.o p9dsu.o \ + vesnin.o nicole.o mihawk.o mowgli.o \ + talos.o blackbird.o \ + swift.o rainier.o + +ASTBMC = $(PLATDIR)/astbmc/built-in.a +$(ASTBMC): $(ASTBMC_OBJS:%=$(PLATDIR)/astbmc/%) diff --git a/roms/skiboot/platforms/astbmc/astbmc.h b/roms/skiboot/platforms/astbmc/astbmc.h new file mode 100644 index 000000000..00f221230 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/astbmc.h @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#ifndef __ASTBMC_H +#define __ASTBMC_H + +#include <platform.h> + +#define ST_LOC_PHB(chip_id, phb_idx) ((chip_id) << 16 | (phb_idx)) +#define ST_LOC_DEVFN(dev, fn) ((dev) << 3 | (fn)) +/* + * NPU groups are used to allocate device numbers. There is a 1 to 1 + * correlation between a NPU group and a physical GPU. Links within a group + * are allocated as functions within a device, so groups must be numbered + * sequentially starting at 0. + */ +#define ST_LOC_NPU_GROUP(group_id) (group_id << 3) + +struct slot_table_entry { + enum slot_table_etype { + st_end, /* End of list */ + st_phb, + st_pluggable_slot, + st_builtin_dev, + st_npu_slot + } etype; + uint32_t location; + const char *name; + const struct slot_table_entry *children; + uint8_t power_limit; +}; + +/* + * Helper to reduce the noise in the PHB table + */ +#define ST_PHB_ENTRY(chip_id, phb_id, child_table) \ +{ \ + .etype = st_phb, \ + .location = ST_LOC_PHB(chip_id, phb_id), \ + .children = child_table \ +} + +/* + * For the most part the "table" isn't really a table and only contains + * a single real entry and the etype = st_end terminator. In these cases + * we can use these helpers. If you need something special in the slot + * table for each slot (e.g. power limit, devfn != 0) then you need to + * define the actual structure. + */ +#define ST_BUILTIN_DEV(st_name, slot_name, ...) \ +static struct slot_table_entry st_name[] = \ +{ \ + { \ + .etype = st_pluggable_slot, \ + .name = slot_name, \ + ##__VA_ARGS__ \ + }, \ + { .etype = st_end }, \ +} + +#define ST_PLUGGABLE(st_name, slot_name, ...) \ +static struct slot_table_entry st_name[] = \ +{ \ + { \ + .etype = st_pluggable_slot, \ + .name = slot_name, \ + ##__VA_ARGS__ \ + }, \ + { .etype = st_end }, \ +} + +#define SW_PLUGGABLE(slot_name, port, ...) \ +{ \ + .etype = st_pluggable_slot, \ + .name = slot_name, \ + .location = ST_LOC_DEVFN(port, 0), \ + ##__VA_ARGS__ \ +} + +#define SW_BUILTIN(slot_name, port, ...) \ +{ \ + .etype = st_builtin_dev, \ + .name = slot_name, \ + .location = ST_LOC_DEVFN(port, 0), \ + ##__VA_ARGS__ \ +} + +extern const struct bmc_hw_config bmc_hw_ast2400; +extern const struct bmc_hw_config bmc_hw_ast2500; +extern const struct bmc_hw_config bmc_hw_ast2600; +extern const struct bmc_platform bmc_plat_ast2400_ami; +extern const struct bmc_platform bmc_plat_ast2500_ami; +extern const struct bmc_platform bmc_plat_ast2500_openbmc; +extern const struct bmc_platform bmc_plat_ast2600_openbmc; + +extern void astbmc_early_init(void); +extern int64_t astbmc_ipmi_reboot(void); +extern int64_t astbmc_ipmi_power_down(uint64_t request); +extern void astbmc_init(void); +extern void astbmc_ext_irq_serirq_cpld(unsigned int chip_id); +extern int pnor_init(void); +extern void check_all_slot_table(void); +extern void astbmc_exit(void); +extern void astbmc_seeprom_update(void); + +extern void slot_table_init(const struct slot_table_entry *top_table); +extern void slot_table_get_slot_info(struct phb *phb, struct pci_device * pd); +void slot_table_add_slot_info(struct pci_device *pd, + const struct slot_table_entry *ent); + +void dt_slot_get_slot_info(struct phb *phb, struct pci_device *pd); + +#endif /* __ASTBMC_H */ diff --git a/roms/skiboot/platforms/astbmc/barreleye.c b/roms/skiboot/platforms/astbmc/barreleye.c new file mode 100644 index 000000000..f7542d66a --- /dev/null +++ b/roms/skiboot/platforms/astbmc/barreleye.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright 2016 Ingrasys. + * Copyright 2016-2019 IBM Corp. + */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> + +#include "astbmc.h" + +static const struct slot_table_entry barreleye_phb0_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot1", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry barreleye_plx_slots[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(1,0), + .name = "IO Board SATA", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(2,0), + .name = "IO Board USB", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(3,0), + .name = "IO Board BMC", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(4,0), + .name = "IO Board NIC", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry barreleye_plx_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = barreleye_plx_slots, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry barreleye_phb0_1_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "PLX Switch", + .children = barreleye_plx_up, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry barreleye_phb0_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Network Mezz", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry barreleye_phb8_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Storage Mezz", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry barreleye_phb8_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot3", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry barreleye_phb8_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot2", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry barreleye_phb_table[] = { + { + .etype = st_phb, + .location = ST_LOC_PHB(0,0), + .children = barreleye_phb0_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,1), + .children = barreleye_phb0_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,2), + .children = barreleye_phb0_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,0), + .children = barreleye_phb8_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,1), + .children = barreleye_phb8_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,2), + .children = barreleye_phb8_2_slot, + }, + { .etype = st_end }, +}; + +static bool barreleye_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "ingrasys,barreleye")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + slot_table_init(barreleye_phb_table); + + return true; +} + + +DECLARE_PLATFORM(barreleye) = { + .name = "Barreleye", + .probe = barreleye_probe, + .init = astbmc_init, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .external_irq = astbmc_ext_irq_serirq_cpld, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .exit = ipmi_wdt_final_reset, + .terminate = ipmi_terminate, + .op_display = op_display_lpc, +}; diff --git a/roms/skiboot/platforms/astbmc/blackbird.c b/roms/skiboot/platforms/astbmc/blackbird.c new file mode 100644 index 000000000..d7a1f81d6 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/blackbird.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 +/* Copyright 2017 IBM Corp. + * Copyright 2018-2019 Raptor Engineering, LLC + * Copyright 2019 Stewart Smith + */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> +#include <psi.h> +#include <lpc.h> + +#include "astbmc.h" + +ST_PLUGGABLE(blackbird_cpu1_slot1, "SLOT1 PCIE 4.0 X16"); +ST_PLUGGABLE(blackbird_cpu1_slot2, "SLOT2 PCIE 4.0 X8"); + +ST_BUILTIN_DEV(blackbird_builtin_sata, "Builtin SATA"); +ST_BUILTIN_DEV(blackbird_builtin_usb, "Builtin USB"); +ST_BUILTIN_DEV(blackbird_builtin_ethernet, "Builtin Ethernet"); +ST_BUILTIN_DEV(blackbird_builtin_bmc, "BMC"); + +static const struct slot_table_entry blackbird_phb_table[] = { + ST_PHB_ENTRY(0, 0, blackbird_cpu1_slot1), + ST_PHB_ENTRY(0, 1, blackbird_cpu1_slot2), + + ST_PHB_ENTRY(0, 2, blackbird_builtin_sata), + ST_PHB_ENTRY(0, 3, blackbird_builtin_usb), + ST_PHB_ENTRY(0, 4, blackbird_builtin_ethernet), + ST_PHB_ENTRY(0, 5, blackbird_builtin_bmc), + + { .etype = st_end }, +}; + +static bool blackbird_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "rcs,blackbird")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Setup UART for use by OPAL (Linux hvc) */ + uart_set_console_policy(UART_CONSOLE_OPAL); + + slot_table_init(blackbird_phb_table); + + return true; +} + +static int64_t blackbird_write_oppanel_async(uint64_t async_token __unused, + oppanel_line_t *lines, + uint64_t num_lines) +{ + uint8_t *line; + int len; + + if (num_lines != 1) + return OPAL_PARAMETER; + + line = (void *) be64_to_cpu(lines[0].line); + len = be64_to_cpu(lines[0].line_len); + + if (len > 0) + lpc_probe_write(OPAL_LPC_IO, 0x80, line[0], 1); + if (len > 1) + lpc_probe_write(OPAL_LPC_IO, 0x81, line[1], 1); + if (len > 2) + lpc_probe_write(OPAL_LPC_IO, 0x82, line[2], 1); + + return OPAL_SUCCESS; +} + +static void blackbird_init(void) +{ + struct dt_node *oppanel; + + astbmc_init(); + + opal_register(OPAL_WRITE_OPPANEL_ASYNC, blackbird_write_oppanel_async, 3); + + oppanel = dt_new(opal_node, "oppanel"); + dt_add_property_cells(oppanel, "#length", 3); + dt_add_property_cells(oppanel, "#lines", 1); + dt_add_property_strings(oppanel, "compatible", "ibm,opal-oppanel", "rcs,ipl-observer"); + +} + +DECLARE_PLATFORM(blackbird) = { + .name = "Blackbird", + .probe = blackbird_probe, + .init = blackbird_init, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .bmc = &bmc_plat_ast2500_openbmc, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .exit = astbmc_exit, + .terminate = ipmi_terminate, + .op_display = op_display_lpc, +}; diff --git a/roms/skiboot/platforms/astbmc/common.c b/roms/skiboot/platforms/astbmc/common.c new file mode 100644 index 000000000..83ef70ad3 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/common.c @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <psi.h> +#include <chip.h> +#include <xscom.h> +#include <ast.h> +#include <ipmi.h> +#include <bt.h> +#include <errorlog.h> +#include <lpc.h> +#include <timebase.h> + +#include "astbmc.h" + +/* UART1 config */ +#define UART_IO_BASE 0x3f8 +#define UART_IO_COUNT 8 +#define UART_LPC_IRQ 4 + +/* BT config */ +#define BT_IO_BASE 0xe4 +#define BT_IO_COUNT 3 +#define BT_LPC_IRQ 10 + +/* MBOX config */ +#define MBOX_IO_BASE 0x1000 +#define MBOX_IO_COUNT 6 +#define MBOX_LPC_IRQ 9 + +void astbmc_ext_irq_serirq_cpld(unsigned int chip_id) +{ + lpc_all_interrupts(chip_id); +} + +static void astbmc_ipmi_error(struct ipmi_msg *msg) +{ + prlog(PR_DEBUG, "ASTBMC: error sending msg. cc = %02x\n", msg->cc); + + ipmi_free_msg(msg); +} + +static void astbmc_ipmi_setenables(void) +{ + struct ipmi_msg *msg; + + struct { + uint8_t oem2_en : 1; + uint8_t oem1_en : 1; + uint8_t oem0_en : 1; + uint8_t reserved : 1; + uint8_t sel_en : 1; + uint8_t msgbuf_en : 1; + uint8_t msgbuf_full_int_en : 1; + uint8_t rxmsg_queue_int_en : 1; + } data; + + memset(&data, 0, sizeof(data)); + + /* The spec says we need to read-modify-write to not clobber + * the state of the other flags. These are set on by the bmc */ + data.rxmsg_queue_int_en = 1; + data.sel_en = 1; + + /* These are the ones we want to set on */ + data.msgbuf_en = 1; + + msg = ipmi_mkmsg_simple(IPMI_SET_ENABLES, &data, sizeof(data)); + if (!msg) { + /** + * @fwts-label ASTBMCFailedSetEnables + * @fwts-advice AST BMC is likely to be non-functional + * when accessed from host. + */ + prlog(PR_ERR, "ASTBMC: failed to set enables\n"); + return; + } + + msg->error = astbmc_ipmi_error; + + ipmi_queue_msg(msg); + +} + +static int astbmc_fru_init(void) +{ + const struct dt_property *prop; + struct dt_node *node; + uint8_t fru_id; + + node = dt_find_by_path(dt_root, "bmc"); + if (!node) + return -1; + + prop = dt_find_property(node, "firmware-fru-id"); + if (!prop) + return -1; + + fru_id = dt_property_get_cell(prop, 0) & 0xff; + ipmi_fru_init(fru_id); + return 0; +} + + +void astbmc_init(void) +{ + /* Register the BT interface with the IPMI layer + * + * Initialise this first to enable PNOR access + */ + bt_init(); + + /* Initialize PNOR/NVRAM */ + pnor_init(); + + /* Initialize elog */ + elog_init(); + ipmi_sel_init(); + ipmi_wdt_init(); + ipmi_rtc_init(); + ipmi_opal_init(); + astbmc_fru_init(); + ipmi_sensor_init(); + + /* Request BMC information */ + ipmi_get_bmc_info_request(); + + /* As soon as IPMI is up, inform BMC we are in "S0" */ + ipmi_set_power_state(IPMI_PWR_SYS_S0_WORKING, IPMI_PWR_NOCHANGE); + + /* Enable IPMI OEM message interrupts */ + astbmc_ipmi_setenables(); + + ipmi_set_fw_progress_sensor(IPMI_FW_MOTHERBOARD_INIT); + + /* Setup UART console for use by Linux via OPAL API */ + set_opal_console(&uart_opal_con); +} + +int64_t astbmc_ipmi_power_down(uint64_t request) +{ + if (request != IPMI_CHASSIS_PWR_DOWN) { + prlog(PR_WARNING, "PLAT: unexpected shutdown request %llx\n", + request); + } + + return ipmi_chassis_control(request); +} + +int64_t astbmc_ipmi_reboot(void) +{ + return ipmi_chassis_control(IPMI_CHASSIS_HARD_RESET); +} + +void astbmc_seeprom_update(void) +{ + int flag_set, counter, rc; + + rc = ipmi_get_chassis_boot_opt_request(); + + if (rc) { + prlog(PR_WARNING, "Failed to check SBE validation flag\n"); + return; + } + + flag_set = ipmi_chassis_check_sbe_validation(); + + if (flag_set <= 0) { + prlog(PR_DEBUG, "SBE validation flag unset or invalid\n"); + return; + } + + /* + * Flag is set, wait until SBE validation is complete and the flag + * has been reset. + */ + prlog(PR_WARNING, "SBE validation required, waiting for completion\n"); + prlog(PR_WARNING, "System will be powered off if validation fails\n"); + counter = 0; + + while (flag_set > 0) { + time_wait_ms(10000); + if (++counter % 3 == 0) { + /* Let the user know we're alive every 30s */ + prlog(PR_WARNING, "waiting for completion...\n"); + } + if (counter == 180) { + /* This is longer than expected and we have no way of + * checking if it's still running. Apologies if you + * ever see this message. + */ + prlog(PR_WARNING, "30 minutes has elapsed, this is longer than expected for verification\n"); + prlog(PR_WARNING, "If no progress is made a power reset of the BMC and Host may be required\n"); + counter = 0; + } + + /* As above, loop anyway if we fail to check the flag */ + rc = ipmi_get_chassis_boot_opt_request(); + if (rc == 0) + flag_set = ipmi_chassis_check_sbe_validation(); + else + prlog(PR_WARNING, "Failed to check SBE validation flag\n"); + } + + /* + * The SBE validation can (will) leave the SBE in a bad state, + * preventing timers from working properly. Reboot so that we + * can boot normally with everything intact. + */ + prlog(PR_WARNING, "SBE validation complete, rebooting\n"); + if (platform.cec_reboot) + platform.cec_reboot(); + else + abort(); + while(true); +} + +static void astbmc_fixup_dt_system_id(void) +{ + /* Make sure we don't already have one */ + if (dt_find_property(dt_root, "system-id")) + return; + + dt_add_property_strings(dt_root, "system-id", "unavailable"); +} + +static void astbmc_fixup_dt_bt(struct dt_node *lpc) +{ + struct dt_node *bt; + char namebuf[32]; + + /* First check if the BT interface is already there */ + dt_for_each_child(lpc, bt) { + if (dt_node_is_compatible(bt, "bt")) + return; + } + + snprintf(namebuf, sizeof(namebuf), "ipmi-bt@i%x", BT_IO_BASE); + bt = dt_new(lpc, namebuf); + + dt_add_property_cells(bt, "reg", + 1, /* IO space */ + BT_IO_BASE, BT_IO_COUNT); + dt_add_property_strings(bt, "compatible", "ipmi-bt"); + + /* Mark it as reserved to avoid Linux trying to claim it */ + dt_add_property_strings(bt, "status", "reserved"); + + dt_add_property_cells(bt, "interrupts", BT_LPC_IRQ); + dt_add_property_cells(bt, "interrupt-parent", lpc->phandle); +} + +static void astbmc_fixup_dt_mbox(struct dt_node *lpc) +{ + struct dt_node *mbox; + char namebuf[32]; + + if (!lpc) + return; + + /* + * P9 machines always use hiomap, either by ipmi or mbox. P8 machines + * can indicate they support mbox using the scratch register, or ipmi + * by configuring the hiomap ipmi command. If neither are configured + * for P8 then skiboot will drive the flash controller directly. + * XXX P10 + */ + if (proc_gen == proc_gen_p8 && !ast_scratch_reg_is_mbox()) + return; + + /* First check if the mbox interface is already there */ + dt_for_each_child(lpc, mbox) { + if (dt_node_is_compatible(mbox, "mbox")) + return; + } + + snprintf(namebuf, sizeof(namebuf), "mbox@i%x", MBOX_IO_BASE); + mbox = dt_new(lpc, namebuf); + + dt_add_property_cells(mbox, "reg", + 1, /* IO space */ + MBOX_IO_BASE, MBOX_IO_COUNT); + dt_add_property_strings(mbox, "compatible", "mbox"); + + /* Mark it as reserved to avoid Linux trying to claim it */ + dt_add_property_strings(mbox, "status", "reserved"); + + dt_add_property_cells(mbox, "interrupts", MBOX_LPC_IRQ); + dt_add_property_cells(mbox, "interrupt-parent", lpc->phandle); +} + +static void astbmc_fixup_dt_uart(struct dt_node *lpc) +{ + /* + * The official OF ISA/LPC binding is a bit odd, it prefixes + * the unit address for IO with "i". It uses 2 cells, the first + * one indicating IO vs. Memory space (along with bits to + * represent aliasing). + * + * We pickup that binding and add to it "2" as a indication + * of FW space. + */ + struct dt_node *uart; + char namebuf[32]; + + /* First check if the UART is already there */ + dt_for_each_child(lpc, uart) { + if (dt_node_is_compatible(uart, "ns16550")) + return; + } + + /* Otherwise, add a node for it */ + snprintf(namebuf, sizeof(namebuf), "serial@i%x", UART_IO_BASE); + uart = dt_new(lpc, namebuf); + + dt_add_property_cells(uart, "reg", + 1, /* IO space */ + UART_IO_BASE, UART_IO_COUNT); + dt_add_property_strings(uart, "compatible", + "ns16550", + "pnpPNP,501"); + dt_add_property_cells(uart, "clock-frequency", 1843200); + dt_add_property_cells(uart, "current-speed", 115200); + + /* + * This is needed by Linux for some obscure reasons, + * we'll eventually need to sanitize it but in the meantime + * let's make sure it's there + */ + dt_add_property_strings(uart, "device_type", "serial"); + + /* Add interrupt */ + dt_add_property_cells(uart, "interrupts", UART_LPC_IRQ); + dt_add_property_cells(uart, "interrupt-parent", lpc->phandle); +} + +static void del_compatible(struct dt_node *node) +{ + struct dt_property *prop; + + prop = __dt_find_property(node, "compatible"); + if (prop) + dt_del_property(node, prop); +} + + +static void astbmc_fixup_bmc_sensors(void) +{ + struct dt_node *parent, *node; + + parent = dt_find_by_path(dt_root, "bmc"); + if (!parent) + return; + del_compatible(parent); + + parent = dt_find_by_name(parent, "sensors"); + if (!parent) + return; + del_compatible(parent); + + dt_for_each_child(parent, node) { + if (dt_find_property(node, "compatible")) + continue; + dt_add_property_string(node, "compatible", "ibm,ipmi-sensor"); + } +} + +static struct dt_node *dt_find_primary_lpc(void) +{ + struct dt_node *n, *primary_lpc = NULL; + + /* Find the primary LPC bus */ + dt_for_each_compatible(dt_root, n, "ibm,power8-lpc") { + if (!primary_lpc || dt_has_node_property(n, "primary", NULL)) + primary_lpc = n; + if (dt_has_node_property(n, "#address-cells", NULL)) + break; + } + dt_for_each_compatible(dt_root, n, "ibm,power9-lpc") { + if (!primary_lpc || dt_has_node_property(n, "primary", NULL)) + primary_lpc = n; + if (dt_has_node_property(n, "#address-cells", NULL)) + break; + } + + return primary_lpc; +} + +static void astbmc_fixup_dt(void) +{ + struct dt_node *primary_lpc; + + primary_lpc = dt_find_primary_lpc(); + + if (!primary_lpc) + return; + + /* Fixup the UART, that might be missing from HB */ + astbmc_fixup_dt_uart(primary_lpc); + + /* BT is not in HB either */ + astbmc_fixup_dt_bt(primary_lpc); + + /* The pel logging code needs a system-id property to work so + make sure we have one. */ + astbmc_fixup_dt_system_id(); + + if (proc_gen == proc_gen_p8) + astbmc_fixup_bmc_sensors(); +} + +static void astbmc_fixup_psi_bar(void) +{ + struct proc_chip *chip = next_chip(NULL); + uint64_t psibar; + + /* This is P8 specific */ + if (proc_gen != proc_gen_p8) + return; + + /* Read PSI BAR */ + if (xscom_read(chip->id, 0x201090A, &psibar)) { + prerror("PLAT: Error reading PSI BAR\n"); + return; + } + /* Already configured, bail out */ + if (psibar & 1) + return; + + /* Hard wire ... yuck */ + psibar = 0x3fffe80000001UL; + + printf("PLAT: Fixing up PSI BAR on chip %d BAR=%llx\n", + chip->id, psibar); + + /* Now write it */ + xscom_write(chip->id, 0x201090A, psibar); +} + +static void astbmc_fixup_uart(void) +{ + /* + * Depending on which image we are running, it may be configuring the + * virtual UART or not. Check if VUART is enabled and use SIO if not. + * We also correct the configuration of VUART as some BMC images don't + * setup the interrupt properly + */ + if (ast_is_vuart1_enabled()) { + printf("PLAT: Using virtual UART\n"); + ast_disable_sio_uart1(); + ast_setup_vuart1(UART_IO_BASE, UART_LPC_IRQ); + } else { + printf("PLAT: Using SuperIO UART\n"); + ast_setup_sio_uart1(UART_IO_BASE, UART_LPC_IRQ); + } +} + +void astbmc_early_init(void) +{ + /* Hostboot's device-tree isn't quite right yet */ + astbmc_fixup_dt(); + + /* Hostboot forgets to populate the PSI BAR */ + astbmc_fixup_psi_bar(); + + if (ast_sio_init()) { + if (ast_io_init()) { + astbmc_fixup_uart(); + ast_setup_ibt(BT_IO_BASE, BT_LPC_IRQ); + } else + prerror("PLAT: AST IO initialisation failed!\n"); + + /* + * P9 prefers IPMI for HIOMAP but will use MBOX if IPMI is not + * supported. P8 either uses IPMI HIOMAP or direct IO, and + * never MBOX. Thus only populate the MBOX node on P9 to allow + * fallback. + */ + if (proc_gen >= proc_gen_p9) { + astbmc_fixup_dt_mbox(dt_find_primary_lpc()); + ast_setup_sio_mbox(MBOX_IO_BASE, MBOX_LPC_IRQ); + } + } else { + /* + * This may or may not be an error depending on if we set up + * hiomap or not. In the old days it *was* an error, but now + * with the way we configure the BMC hardware, this is actually + * the not error case. + */ + prlog(PR_INFO, "PLAT: AST SIO unavailable!\n"); + } + + /* Setup UART and use it as console */ + uart_init(); + + prd_init(); +} + +void astbmc_exit(void) +{ + ipmi_wdt_final_reset(); +} + +static const struct bmc_sw_config bmc_sw_ami = { + .ipmi_oem_partial_add_esel = IPMI_CODE(0x3a, 0xf0), + .ipmi_oem_pnor_access_status = IPMI_CODE(0x3a, 0x07), + .ipmi_oem_hiomap_cmd = IPMI_CODE(0x3a, 0x5a), +}; + +static const struct bmc_sw_config bmc_sw_openbmc = { + .ipmi_oem_partial_add_esel = IPMI_CODE(0x3a, 0xf0), + .ipmi_oem_hiomap_cmd = IPMI_CODE(0x3a, 0x5a), +}; + +/* Extracted from a Palmetto */ +const struct bmc_hw_config bmc_hw_ast2400 = { + .scu_revision_id = 0x2010303, + .mcr_configuration = 0x00000577, + .mcr_scu_mpll = 0x000050c0, + .mcr_scu_strap = 0x00000000, +}; + +/* Extracted from a Witherspoon */ +const struct bmc_hw_config bmc_hw_ast2500 = { + .scu_revision_id = 0x04030303, + .mcr_configuration = 0x11200756, + .mcr_scu_mpll = 0x000071C1, + .mcr_scu_strap = 0x00000000, +}; + +/* XXX P10: Update with Rainier values */ +const struct bmc_hw_config bmc_hw_ast2600 = { + .scu_revision_id = 0x05000303, + .mcr_configuration = 0x11200756, + .mcr_scu_mpll = 0x1008405F, + .mcr_scu_strap = 0x000030E0, +}; + +const struct bmc_platform bmc_plat_ast2400_ami = { + .name = "ast2400:ami", + .hw = &bmc_hw_ast2400, + .sw = &bmc_sw_ami, +}; + +const struct bmc_platform bmc_plat_ast2500_ami = { + .name = "ast2500:ami", + .hw = &bmc_hw_ast2500, + .sw = &bmc_sw_ami, +}; + +const struct bmc_platform bmc_plat_ast2500_openbmc = { + .name = "ast2500:openbmc", + .hw = &bmc_hw_ast2500, + .sw = &bmc_sw_openbmc, +}; + +const struct bmc_platform bmc_plat_ast2600_openbmc = { + .name = "ast2600:openbmc", + .hw = &bmc_hw_ast2600, + .sw = &bmc_sw_openbmc, +}; diff --git a/roms/skiboot/platforms/astbmc/firestone.c b/roms/skiboot/platforms/astbmc/firestone.c new file mode 100644 index 000000000..ae5603ebc --- /dev/null +++ b/roms/skiboot/platforms/astbmc/firestone.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> + +#include "astbmc.h" + +static const struct slot_table_entry firestone_phb0_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot5", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry firestone_phb0_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot4", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry firestone_phb8_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot2", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry firestone_plx_slots[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(1,0), + .name = "Slot3", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(9,0), + .name = "Backplane USB", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0xa,0), + .name = "Backplane SATA", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0xb,0), + .name = "Backplane BMC", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry firestone_plx_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = firestone_plx_slots, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry firestone_phb8_1_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Backplane PLX", + .children = firestone_plx_up, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry firestone_phb8_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot1", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry firestone_phb_table[] = { + { + .etype = st_phb, + .location = ST_LOC_PHB(0,0), + .children = firestone_phb0_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,1), + .children = firestone_phb0_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,0), + .children = firestone_phb8_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,1), + .children = firestone_phb8_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,2), + .children = firestone_phb8_2_slot, + }, + { .etype = st_end }, +}; + +static bool firestone_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "ibm,firestone")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + slot_table_init(firestone_phb_table); + + return true; +} + + +DECLARE_PLATFORM(firestone) = { + .name = "Firestone", + .bmc = &bmc_plat_ast2400_ami, + .probe = firestone_probe, + .init = astbmc_init, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .external_irq = astbmc_ext_irq_serirq_cpld, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .exit = ipmi_wdt_final_reset, + .terminate = ipmi_terminate, + .op_display = op_display_lpc, +}; diff --git a/roms/skiboot/platforms/astbmc/garrison.c b/roms/skiboot/platforms/astbmc/garrison.c new file mode 100644 index 000000000..e698e2f68 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/garrison.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> +#include <psi.h> +#include <npu-regs.h> + +#include "astbmc.h" + +static const struct slot_table_entry garrison_phb0_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot3", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry garrison_phb0_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot2", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry garrison_phb0_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "GPU1", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry garrison_phb0_3_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "GPU2", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry garrison_npu0_slots[] = { + { + .etype = st_npu_slot, + .location = ST_LOC_NPU_GROUP(0), + .name = "GPU2", + }, + { + .etype = st_npu_slot, + .location = ST_LOC_NPU_GROUP(1), + .name = "GPU1", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry garrison_phb1_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot1", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry garrison_plx_slots[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(1,0), + .name = "Backplane USB", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(2,0), + .name = "Backplane SATA", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(3,0), + .name = "Backplane BMC", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry garrison_plx_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = garrison_plx_slots, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry garrison_phb1_1_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Backplane PLX", + .children = garrison_plx_up, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry garrison_phb1_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "GPU3", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry garrison_phb1_3_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "GPU4", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry garrison_npu1_slots[] = { + { + .etype = st_npu_slot, + .location = ST_LOC_NPU_GROUP(0), + .name = "GPU4", + }, + { + .etype = st_npu_slot, + .location = ST_LOC_NPU_GROUP(1), + .name = "GPU3", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry garrison_phb_table[] = { + { + .etype = st_phb, + .location = ST_LOC_PHB(0,0), + .children = garrison_phb0_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,1), + .children = garrison_phb0_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,2), + .children = garrison_phb0_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,3), + .children = garrison_phb0_3_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,4), + .children = garrison_npu0_slots, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(1,0), + .children = garrison_phb1_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(1,1), + .children = garrison_phb1_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(1,2), + .children = garrison_phb1_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(1,3), + .children = garrison_phb1_3_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(1,4), + .children = garrison_npu1_slots, + }, + { .etype = st_end }, +}; + +#define NPU_BASE 0x8013c00 +#define NPU_SIZE 0x2c +#define NPU_INDIRECT0 0x8000000008010c3fUL +#define NPU_INDIRECT1 0x8000000008010c7fUL + +static void create_link(struct dt_node *npu, int group, int index) +{ + struct dt_node *link; + uint32_t lane_mask; + uint64_t phy; + char namebuf[32]; + + snprintf(namebuf, sizeof(namebuf), "link@%x", index); + link = dt_new(npu, namebuf); + + dt_add_property_string(link, "compatible", "ibm,npu-link"); + dt_add_property_cells(link, "ibm,npu-link-index", index); + + if (index < 4) { + phy = NPU_INDIRECT0; + lane_mask = 0xff << (index * 8); + } else { + phy = NPU_INDIRECT1; + lane_mask = 0xff0000 >> (index - 3) * 8; + } + dt_add_property_u64s(link, "ibm,npu-phy", phy); + dt_add_property_cells(link, "ibm,npu-lane-mask", lane_mask); + dt_add_property_cells(link, "ibm,npu-group-id", group); +} + +static void dt_create_npu(void) +{ + struct dt_node *xscom, *npu; + char namebuf[32]; + + dt_for_each_compatible(dt_root, xscom, "ibm,xscom") { + snprintf(namebuf, sizeof(namebuf), "npu@%x", NPU_BASE); + npu = dt_new(xscom, namebuf); + dt_add_property_cells(npu, "reg", NPU_BASE, NPU_SIZE); + dt_add_property_strings(npu, "compatible", "ibm,power8-npu"); + + /* Use the first available PHB index which is 4 given + * there are three normal PHBs. */ + dt_add_property_cells(npu, "ibm,phb-index", 4); + dt_add_property_cells(npu, "ibm,npu-index", 0); + dt_add_property_cells(npu, "ibm,npu-links", 4); + + /* On Garrison we have 2 links per GPU device. These are + * grouped together as per the slot tables above. */ + create_link(npu, 0, 0); + create_link(npu, 0, 1); + create_link(npu, 1, 4); + create_link(npu, 1, 5); + } +} + +static bool garrison_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "ibm,garrison")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Fixups until HB get the NPU bindings */ + dt_create_npu(); + + slot_table_init(garrison_phb_table); + + return true; +} + +DECLARE_PLATFORM(garrison) = { + .name = "Garrison", + .bmc = &bmc_plat_ast2400_ami, + .probe = garrison_probe, + .init = astbmc_init, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .exit = ipmi_wdt_final_reset, + .terminate = ipmi_terminate, + .seeprom_update = astbmc_seeprom_update, + .op_display = op_display_lpc, +}; diff --git a/roms/skiboot/platforms/astbmc/habanero.c b/roms/skiboot/platforms/astbmc/habanero.c new file mode 100644 index 000000000..b98ff8960 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/habanero.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> + +#include "astbmc.h" + +static const struct slot_table_entry habanero_phb0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot3", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry habanero_plx_slots[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(1,0), + .name = "Network Mezz", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(8,0), + .name = "Storage Mezz", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(9,0), + .name = "Backplane USB", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0xa,0), + .name = "Backplane BMC", + }, + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0x10,0), + .name = "Slot2", + }, + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0x11,0), + .name = "Slot1", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry habanero_plx_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = habanero_plx_slots, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry habanero_phb1_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Backplane PLX", + .children = habanero_plx_up, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry habanero_phb2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot4", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry habanero_phb_table[] = { + { + .etype = st_phb, + .location = ST_LOC_PHB(0,0), + .children = habanero_phb0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,1), + .children = habanero_phb1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,2), + .children = habanero_phb2_slot, + }, + { .etype = st_end }, +}; + +static bool habanero_probe(void) +{ + const char *model; + + if (!dt_node_is_compatible(dt_root, "ibm,powernv")) + return false; + + /* Temporary ... eventually we'll get that in compatible */ + model = dt_prop_get_def(dt_root, "model", NULL); + if ((!model || !strstr(model, "habanero")) && + (!dt_node_is_compatible(dt_root, "tyan,habanero"))) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + slot_table_init(habanero_phb_table); + + return true; +} + +DECLARE_PLATFORM(habanero) = { + .name = "Habanero", + .bmc = &bmc_plat_ast2400_ami, + .probe = habanero_probe, + .init = astbmc_init, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .external_irq = astbmc_ext_irq_serirq_cpld, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .exit = ipmi_wdt_final_reset, + .terminate = ipmi_terminate, + .seeprom_update = astbmc_seeprom_update, + .op_display = op_display_lpc, +}; diff --git a/roms/skiboot/platforms/astbmc/mihawk.c b/roms/skiboot/platforms/astbmc/mihawk.c new file mode 100644 index 000000000..54c288416 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/mihawk.c @@ -0,0 +1,567 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright 2019 Wistron Corp. + * Copyright 2017 IBM Corp. + */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> +#include <psi.h> +#include <npu-regs.h> +#include <npu2.h> +#include <pci.h> +#include <pci-cfg.h> + +#include <timebase.h> + +#include "astbmc.h" + +/* IPMI message code for Riser-F query (OEM). */ +#define IPMI_RISERF_QUERY IPMI_CODE(0x32, 0x01) + +static bool mihawk_riserF_found = false; +static bool bmc_query_waiting = false; + +#define OPAL_ID_SLOT2 0x01 +#define OPAL_ID_SLOT4 0x03 +#define OPAL_ID_SLOT7 0x31 +#define OPAL_ID_SLOT9 0x33 + +/* nvme backplane slots */ +static const struct slot_table_entry hdd_bay_s2_slots[] = { + SW_PLUGGABLE("nvme13", 0x0), + SW_PLUGGABLE("nvme14", 0x1), + SW_PLUGGABLE("nvme15", 0x2), + SW_PLUGGABLE("nvme16", 0x3), + + { .etype = st_end }, +}; + +static const struct slot_table_entry hdd_bay_s4_slots[] = { + SW_PLUGGABLE("nvme17", 0x0), + SW_PLUGGABLE("nvme18", 0x1), + SW_PLUGGABLE("nvme19", 0x2), + SW_PLUGGABLE("nvme20", 0x3), + SW_PLUGGABLE("nvme21", 0x4), + SW_PLUGGABLE("nvme22", 0x5), + SW_PLUGGABLE("nvme23", 0x6), + SW_PLUGGABLE("nvme24", 0x7), + + { .etype = st_end }, +}; + +static const struct slot_table_entry hdd_bay_s7_slots[] = { + SW_PLUGGABLE("nvme9", 0x0), + SW_PLUGGABLE("nvme10", 0x1), + SW_PLUGGABLE("nvme11", 0x2), + SW_PLUGGABLE("nvme12", 0x3), + + { .etype = st_end }, +}; + +static const struct slot_table_entry hdd_bay_s9_slots[] = { + SW_PLUGGABLE("nvme1", 0x0), + SW_PLUGGABLE("nvme2", 0x1), + SW_PLUGGABLE("nvme3", 0x2), + SW_PLUGGABLE("nvme4", 0x3), + SW_PLUGGABLE("nvme5", 0x4), + SW_PLUGGABLE("nvme6", 0x5), + SW_PLUGGABLE("nvme7", 0x6), + SW_PLUGGABLE("nvme8", 0x7), + + { .etype = st_end }, +}; + +static void mihawk_get_slot_info(struct phb *phb, struct pci_device *pd) +{ + const struct slot_table_entry *ent = NULL; + + if (!pd || pd->slot) + return; + + /* + * If we find a 8533 or c012 switch then assume it's the NVMe Rack. + * This might break if we have another switch with the same vdid in + * the system for some reason. This is a really dumb hack, but until + * we get query the BMC about wether we have a HDD rack or not we + * don't have much of a choice. + */ + if (pd->dev_type == PCIE_TYPE_SWITCH_DNPORT) { + if (pd->vdid == 0x853311f8) { // for microsemi controller + for (ent = hdd_bay_s9_slots; ent->etype != st_end; ent++) + if (ent->location == (pd->bdfn & 0xff)) + break; + } else if (pd->vdid == 0xc0121000) { // for broadcom nvme hba + switch (phb->opal_id) { + case OPAL_ID_SLOT2: + ent = hdd_bay_s2_slots; + break; + case OPAL_ID_SLOT4: + ent = hdd_bay_s4_slots; + break; + case OPAL_ID_SLOT7: + ent = hdd_bay_s7_slots; + break; + case OPAL_ID_SLOT9: + default: + ent = hdd_bay_s9_slots; + break; + } + + for (; ent->etype != st_end; ent++) + if (ent->location == (pd->bdfn & 0xff)) + break; + } + } + + if (ent) + slot_table_add_slot_info(pd, ent); + else + slot_table_get_slot_info(phb, pd); +} + +static const char *mihawk_ocapi_slot_label(uint32_t chip_id, + uint32_t brick_index) +{ + const char *name = NULL; + + if (chip_id == 0) { + if (brick_index == 2) + name = "JP90NVB1"; + else + name = "JP90NVT1"; + } else { + if (brick_index == 2) + name = "JP91NVB1"; + else + name = "JP91NVT1"; + } + return name; +} + +static const struct ocapi_phy_setup mihawk_phy = { + .tx_ffe_pre_coeff = 0x3, + .tx_ffe_post_coeff = 0x14, + .tx_ffe_boost_en = 0, +}; + +static const struct platform_ocapi mihawk_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 = mihawk_ocapi_slot_label, + .phy_setup = &mihawk_phy, +}; + +static const struct slot_table_entry P1E1A_x8_PLX8748_RiserA_down[] = { + SW_PLUGGABLE("Slot7", 0x10), + SW_PLUGGABLE("Slot8", 0x8), + SW_PLUGGABLE("Slot10", 0x9), + + { .etype = st_end } +}; + +static const struct slot_table_entry P1E1A_x8_PLX8748_RiserA_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = P1E1A_x8_PLX8748_RiserA_down, + }, + { .etype = st_end } +}; + +static const struct slot_table_entry p1phb1_rA_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = P1E1A_x8_PLX8748_RiserA_up, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry P0E1A_x8_PLX8748_RiserA_down[] = { + SW_PLUGGABLE("Slot2", 0x10), + SW_PLUGGABLE("Slot3", 0x8), + SW_PLUGGABLE("Slot5", 0x9), + + { .etype = st_end } +}; + +static const struct slot_table_entry P0E1A_x8_PLX8748_RiserA_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = P0E1A_x8_PLX8748_RiserA_down, + }, + { .etype = st_end } +}; + +static const struct slot_table_entry p0phb1_rA_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = P0E1A_x8_PLX8748_RiserA_up, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry P1E1A_x8_PLX8748_RiserF_down[] = { + SW_PLUGGABLE("Slot7", 0x10), + SW_PLUGGABLE("Slot10", 0x9), + + { .etype = st_end } +}; + +static const struct slot_table_entry P1E1A_x8_PLX8748_RiserF_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = P1E1A_x8_PLX8748_RiserF_down, + }, + { .etype = st_end } +}; + +static const struct slot_table_entry p1phb1_rF_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = P1E1A_x8_PLX8748_RiserF_up, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry P0E1A_x8_PLX8748_RiserF_down[] = { + SW_PLUGGABLE("Slot2", 0x10), + SW_PLUGGABLE("Slot5", 0x9), + + { .etype = st_end } +}; + +static const struct slot_table_entry P0E1A_x8_PLX8748_RiserF_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = P0E1A_x8_PLX8748_RiserF_down, + }, + { .etype = st_end } +}; + +static const struct slot_table_entry p0phb1_rF_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = P0E1A_x8_PLX8748_RiserF_up, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry P1E2_x16_Switch_down[] = { + SW_PLUGGABLE("Slot8", 0x1), + SW_PLUGGABLE("Slot9", 0x0), + + { .etype = st_end } +}; + +static const struct slot_table_entry P1E2_x16_Switch_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = P1E2_x16_Switch_down, + }, + { .etype = st_end } +}; + +static const struct slot_table_entry p1phb3_switch_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = P1E2_x16_Switch_up, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry P0E2_x16_Switch_down[] = { + SW_PLUGGABLE("Slot3", 0x1), + SW_PLUGGABLE("Slot4", 0x0), + + { .etype = st_end } +}; + +static const struct slot_table_entry P0E2_x16_Switch_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = P0E2_x16_Switch_down, + }, + { .etype = st_end } +}; + +static const struct slot_table_entry p0phb3_switch_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = P0E2_x16_Switch_up, + }, + { .etype = st_end }, +}; + +ST_PLUGGABLE(p0phb0_slot, "Slot1"); +ST_PLUGGABLE(p0phb3_slot, "Slot4"); +ST_PLUGGABLE(p1phb0_slot, "Slot6"); +ST_PLUGGABLE(p1phb3_slot, "Slot9"); + +static const struct slot_table_entry mihawk_riserA_phb_table[] = { + /* ==== CPU0 ==== */ + ST_PHB_ENTRY(0, 0, p0phb0_slot), /* P0E0_x16_Slot1 */ + ST_PHB_ENTRY(0, 1, p0phb1_rA_slot), /* P0E1A_x8_PLX8748-1_Slot2-3-5 */ + //ST_PHB_ENTRY(0, 2, p0phb2_slot), /* P0E1B_x8_USBTI7340 */ + ST_PHB_ENTRY(0, 3, p0phb3_slot), /* P0E2_x16_Slot4 */ + + /* ==== CPU1 ==== */ + ST_PHB_ENTRY(8, 0, p1phb0_slot), /* P1E0_x16_Slot6 */ + ST_PHB_ENTRY(8, 1, p1phb1_rA_slot), /* P1E1A_x8_PLX8748-2_Slot7-8-10 */ + //ST_PHB_ENTRY(8, 2, p1phb2_slot), /* P1E1B_x8_NA */ + ST_PHB_ENTRY(8, 3, p1phb3_slot), /* P1E2_x16_Slot9 */ + + { .etype = st_end }, +}; + +static const struct slot_table_entry mihawk_riserF_phb_table[] = { + /* ==== CPU0 ==== */ + ST_PHB_ENTRY(0, 0, p0phb0_slot), /* P0E0_x16_Slot1 */ + ST_PHB_ENTRY(0, 1, p0phb1_rF_slot), /* P0E1A_x8_PLX8748-1_Slot2-5 */ + //ST_PHB_ENTRY(0, 2, p0phb2_slot), /* P0E1B_x8_USBTI7340 */ + ST_PHB_ENTRY(0, 3, p0phb3_switch_slot),/* P0E2_x16_SWITCH_Slot3-4 */ + + /* ==== CPU1 ==== */ + ST_PHB_ENTRY(8, 0, p1phb0_slot), /* P1E0_x16_Slot6 */ + ST_PHB_ENTRY(8, 1, p1phb1_rF_slot), /* P1E1A_x8_PLX8748-2_Slot7-10 */ + //ST_PHB_ENTRY(8, 2, p1phb2_slot), /* P1E1B_x8_NA */ + ST_PHB_ENTRY(8, 3, p1phb3_switch_slot),/* P1E2_x16_SWITCH_Slot8-9 */ + + { .etype = st_end }, +}; + +#define NPU_BASE 0x5011000 +#define NPU_SIZE 0x2c +#define NPU_INDIRECT0 0x8000000009010c3fUL /* OB0 - no OB3 on Mihawk */ + +/* OpenCAPI only */ +static void create_link(struct dt_node *npu, int group, int index) +{ + struct dt_node *link; + uint32_t lane_mask; + char namebuf[32]; + + snprintf(namebuf, sizeof(namebuf), "link@%x", index); + link = dt_new(npu, namebuf); + assert(link); + + dt_add_property_string(link, "compatible", "ibm,npu-link"); + dt_add_property_cells(link, "ibm,npu-link-index", index); + + 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); + } + + 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); +} + +/* FIXME: Get rid of this after we get NPU information properly via HDAT/MRW */ +static void mihawk_create_npu(void) +{ + struct dt_node *xscom, *npu; + int npu_index = 0; + char namebuf[32]; + + /* Return if there's already an NPU in the device tree */ + if (dt_find_compatible_node(dt_root, NULL, "ibm,power9-npu")) + return; + + prlog(PR_DEBUG, "OCAPI: Adding NPU device nodes\n"); + dt_for_each_compatible(dt_root, xscom, "ibm,xscom") { + snprintf(namebuf, sizeof(namebuf), "npu@%x", NPU_BASE); + npu = dt_new(xscom, namebuf); + 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); + } +} + +/* FIXME: Get rid of this after we get NPU information properly via HDAT/MRW */ +static void mihawk_create_ocapi_i2c_bus(void) +{ + struct dt_node *xscom, *i2cm, *i2c_bus; + prlog(PR_DEBUG, "OCAPI: Adding I2C bus device node for OCAPI reset\n"); + dt_for_each_compatible(dt_root, xscom, "ibm,xscom") { + i2cm = dt_find_by_name(xscom, "i2cm@a1000"); + if (!i2cm) { + prlog(PR_ERR, "OCAPI: Failed to get I2C bus device node\n"); + continue; + } + + if (dt_find_by_name(i2cm, "i2c-bus@4")) + continue; + + i2c_bus = dt_new_addr(i2cm, "i2c-bus", 4); + dt_add_property_cells(i2c_bus, "reg", 4); + dt_add_property_cells(i2c_bus, "bus-frequency", 0x61a80); + dt_add_property_strings(i2c_bus, "compatible", + "ibm,opal-i2c", "ibm,power8-i2c-port", + "ibm,power9-i2c-port"); + } +} + +/* + * HACK: Hostboot doesn't export the correct data for the system VPD EEPROM + * for this system. So we need to work around it here. + */ +static void vpd_dt_fixup(void) +{ + struct dt_node *n = dt_find_by_path(dt_root, + "/xscom@603fc00000000/i2cm@a2000/i2c-bus@0/eeprom@50"); + + if (n) { + dt_check_del_prop(n, "compatible"); + dt_add_property_string(n, "compatible", "atmel,24c512"); + + dt_check_del_prop(n, "label"); + dt_add_property_string(n, "label", "system-vpd"); + } +} + +static bool mihawk_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "ibm,mihawk") && + !dt_node_is_compatible(dt_root, "wistron,mihawk")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Setup UART for use by OPAL (Linux hvc) */ + uart_set_console_policy(UART_CONSOLE_OPAL); + + vpd_dt_fixup(); + + mihawk_create_npu(); + mihawk_create_ocapi_i2c_bus(); + + return true; +} + +static void mihawk_riser_query_complete(struct ipmi_msg *msg) +{ + uint8_t *riser_state; + + if (msg->cc != IPMI_CC_NO_ERROR) { + prlog(PR_ERR, "Mihawk: IPMI riser query returned error. cmd=0x%02x," + " netfn=0x%02x, rc=0x%x\n", msg->cmd, msg->netfn, msg->cc); + bmc_query_waiting = false; + ipmi_free_msg(msg); + return; + } + + prlog(PR_DEBUG, "Mihawk: IPMI Got riser query result. p0:%02x, p1:%02x\n" + , msg->data[0], msg->data[1]); + + riser_state = (uint8_t*)msg->user_data; + lwsync(); + *riser_state = msg->data[0] << 4 | msg->data[1]; + + bmc_query_waiting = false; + ipmi_free_msg(msg); +} + +static void mihawk_init(void) +{ + struct ipmi_msg *ipmi_msg; + uint8_t riser_state = 0; + int timeout_ms = 3000; + + astbmc_init(); + + /* + * We use IPMI to ask BMC if Riser-F is installed and set up the + * corresponding slot table. + */ + ipmi_msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, + IPMI_RISERF_QUERY, + mihawk_riser_query_complete, + &riser_state, NULL, 0, 2); + + if (!ipmi_msg) { + prlog(PR_ERR, "Mihawk: Couldn't create ipmi msg."); + } else { + ipmi_msg->error = mihawk_riser_query_complete; + ipmi_queue_msg(ipmi_msg); + bmc_query_waiting = true; + + prlog(PR_DEBUG, "Mihawk: Requesting IPMI_RISERF_QUERY (netfn " + "%02x, cmd %02x)\n", ipmi_msg->netfn, ipmi_msg->cmd); + + while (bmc_query_waiting) { + time_wait_ms(10); + timeout_ms -= 10; + + if (timeout_ms == 0) + break; + } + } + + prlog(PR_DEBUG, "Mihawk: IPMI_RISERF_QUERY finish. riser_state: %02x" + ", waiting: %d\n", riser_state, bmc_query_waiting); + + if (riser_state != 0) { + mihawk_riserF_found = true; + slot_table_init(mihawk_riserF_phb_table); + prlog(PR_DEBUG, "Mihawk: Detect Riser-F via IPMI\n"); + } else { + slot_table_init(mihawk_riserA_phb_table); + prlog(PR_DEBUG, "Mihawk: No Riser-F found, use Riser-A table\n"); + } +} + +DECLARE_PLATFORM(mihawk) = { + .name = "Mihawk", + .probe = mihawk_probe, + .init = mihawk_init, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .bmc = &bmc_plat_ast2500_openbmc, + .pci_get_slot_info = mihawk_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .exit = ipmi_wdt_final_reset, + .terminate = ipmi_terminate, + .ocapi = &mihawk_ocapi, + .npu2_device_detect = npu2_i2c_presence_detect, +}; diff --git a/roms/skiboot/platforms/astbmc/mowgli.c b/roms/skiboot/platforms/astbmc/mowgli.c new file mode 100644 index 000000000..df83319de --- /dev/null +++ b/roms/skiboot/platforms/astbmc/mowgli.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Copyright 2020 Wistron Corp. + * Copyright 2017-2019 IBM Corp. + */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> +#include <psi.h> +#include <npu-regs.h> +#include <secvar.h> + +#include "astbmc.h" + +ST_PLUGGABLE(mowgli_slot1, "Pcie Slot1"); +ST_PLUGGABLE(mowgli_builtin_SAS, "Builtin SAS"); +ST_BUILTIN_DEV(mowgli_builtin_bmc, "BMC"); +ST_PLUGGABLE(mowgli_builtin_ethernet, "Builtin Ethernet"); +ST_BUILTIN_DEV(mowgli_builtin_usb, "Builtin USB"); + +static const struct slot_table_entry mowgli_phb_table[] = { + ST_PHB_ENTRY(0, 0, mowgli_slot1), + ST_PHB_ENTRY(0, 1, mowgli_builtin_SAS), + ST_PHB_ENTRY(0, 2, mowgli_builtin_bmc), + ST_PHB_ENTRY(0, 3, mowgli_builtin_ethernet), + ST_PHB_ENTRY(0, 4, mowgli_builtin_usb), + + { .etype = st_end }, +}; + +/* + * HACK: Hostboot doesn't export the correct data for the system VPD EEPROM + * for this system. So we need to work around it here. + */ +static void vpd_dt_fixup(void) +{ + struct dt_node *n = dt_find_by_path(dt_root, + "/xscom@603fc00000000/i2cm@a2000/i2c-bus@0/eeprom@50"); + + if (n) { + dt_check_del_prop(n, "compatible"); + dt_add_property_string(n, "compatible", "atmel,24c512"); + + dt_check_del_prop(n, "label"); + dt_add_property_string(n, "label", "system-vpd"); + } +} + +static void phb0_fixup(void) +{ + struct dt_node *stk; + u32 phb_index; + + /* Limit PHB0/(pec0) to gen3 speed */ + dt_for_each_compatible(dt_root, stk, "ibm,power9-phb-stack") { + phb_index = dt_prop_get_u32_def(stk, "ibm,phb-index", -1); + if (phb_index == 0) { + dt_check_del_prop(stk, "ibm,max-link-speed"); + dt_add_property_cells(stk, "ibm,max-link-speed", 3); + } + } +} + +static bool mowgli_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "ibm,mowgli")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Setup UART for use by OPAL (Linux hvc) */ + uart_set_console_policy(UART_CONSOLE_OPAL); + + vpd_dt_fixup(); + + slot_table_init(mowgli_phb_table); + phb0_fixup(); + + return true; +} +static int mowgli_secvar_init(void) +{ + return secvar_main(secboot_tpm_driver, edk2_compatible_v1); +} + + +DECLARE_PLATFORM(mowgli) = { + .name = "Mowgli", + .probe = mowgli_probe, + .init = astbmc_init, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .bmc = &bmc_plat_ast2500_openbmc, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .exit = astbmc_exit, + .terminate = ipmi_terminate, + .op_display = op_display_lpc, + .secvar_init = mowgli_secvar_init, +}; diff --git a/roms/skiboot/platforms/astbmc/nicole.c b/roms/skiboot/platforms/astbmc/nicole.c new file mode 100644 index 000000000..f21d0361a --- /dev/null +++ b/roms/skiboot/platforms/astbmc/nicole.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Copyright (c) 2019 YADRO + */ + +#include <skiboot.h> +#include <device.h> +#include <ipmi.h> + +#include "astbmc.h" + +#define CHIP_ID_CPU0 0x00 +#define CHIP_ID_CPU1 0x08 + +ST_PLUGGABLE(nicole_backplane0, "Backplane0 (16x)"); +ST_PLUGGABLE(nicole_backplane1, "Backplane1 (16x)"); + +ST_BUILTIN_DEV(nicole_builtin_net, "Builtin Network"); +ST_BUILTIN_DEV(nicole_builtin_ssd0, "Builtin SSD0"); +ST_BUILTIN_DEV(nicole_builtin_ssd1, "Builtin SSD1"); +ST_BUILTIN_DEV(nicole_builtin_vga, "Builtin VGA"); +ST_BUILTIN_DEV(nicole_builtin_usb, "Builtin USB"); + +static const struct slot_table_entry nicole_phb_table[] = { + ST_PHB_ENTRY(CHIP_ID_CPU0, 0, nicole_backplane0), + ST_PHB_ENTRY(CHIP_ID_CPU0, 1, nicole_builtin_net), + ST_PHB_ENTRY(CHIP_ID_CPU0, 2, nicole_builtin_ssd0), + ST_PHB_ENTRY(CHIP_ID_CPU0, 3, nicole_backplane1), + + ST_PHB_ENTRY(CHIP_ID_CPU1, 3, nicole_builtin_ssd1), + ST_PHB_ENTRY(CHIP_ID_CPU1, 4, nicole_builtin_vga), + ST_PHB_ENTRY(CHIP_ID_CPU1, 5, nicole_builtin_usb), + + { .etype = st_end }, +}; + +/* Fixup the system VPD EEPROM size. + * + * Hostboot doesn't export the correct description for EEPROMs, as a result, + * all EEPROMs in the system work in "atmel,24c128" compatibility mode (16KiB). + * Nicole platform has 32KiB EEPROM for the system VPD. + */ +static void vpd_dt_fixup(void) +{ + struct dt_node* vpd_eeprom = dt_find_by_path(dt_root, + "/xscom@603fc00000000/i2cm@a2000/i2c-bus@0/eeprom@50"); + + if (vpd_eeprom) { + dt_check_del_prop(vpd_eeprom, "compatible"); + dt_add_property_string(vpd_eeprom, "compatible", "atmel,24c256"); + + dt_check_del_prop(vpd_eeprom, "label"); + dt_add_property_string(vpd_eeprom, "label", "system-vpd"); + } +} + +static bool nicole_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "YADRO,nicole")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Setup UART for use by OPAL (Linux hvc) */ + uart_set_console_policy(UART_CONSOLE_OPAL); + + /* Fixup system VPD EEPROM size */ + vpd_dt_fixup(); + + slot_table_init(nicole_phb_table); + + return true; +} + +DECLARE_PLATFORM(nicole) = { + .name = "Nicole", + .probe = nicole_probe, + .init = astbmc_init, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .bmc = &bmc_plat_ast2500_openbmc, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .exit = astbmc_exit, + .terminate = ipmi_terminate, +}; diff --git a/roms/skiboot/platforms/astbmc/p8dnu.c b/roms/skiboot/platforms/astbmc/p8dnu.c new file mode 100644 index 000000000..e223d158b --- /dev/null +++ b/roms/skiboot/platforms/astbmc/p8dnu.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright 2017 Supermicro + * Copyright 2017-2019 IBM Corp. + */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> +#include <psi.h> +#include <npu-regs.h> + +#include "astbmc.h" + +static const struct slot_table_entry p8dnu_phb0_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "UIO SLOT1", + }, + { .etype = st_end }, +}; + + +static const struct slot_table_entry p8dnu_plx_slots_00[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(1,0), + .name = "Onboard SATA Marvell 88SE9230", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(2,0), + .name = "Slot_DUIO ", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(8,0), + .name = "Intel LAN X710/X557-AT", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(9,0), + .name = "Onboard VGA AST2400", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0xa,0), + .name = "Onboard USB TI TUSB7340", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dnu_plx_up_00[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = p8dnu_plx_slots_00, + }, + { .etype = st_end }, +}; + + + +static const struct slot_table_entry p8dnu_phb0_1_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Backplane PLX VS0", + .children = p8dnu_plx_up_00, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dnu_phb0_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "GPU1", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dnu_phb0_3_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "GPU2", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dnu_npu0_slots[] = { + { + .etype = st_npu_slot, + .location = ST_LOC_NPU_GROUP(0), + .name = "GPU2", + }, + { + .etype = st_npu_slot, + .location = ST_LOC_NPU_GROUP(1), + .name = "GPU1", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dnu_phb1_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO SLOT1", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dnu_plx_slots[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(5,0), + .name = "RSC-R1UW-E8R SLOT1", + }, + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0xd,0), + .name = "WIO SLOT2", + }, + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0xc,0), + .name = "WIO SLOT3", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dnu_plx_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = p8dnu_plx_slots, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dnu_phb1_1_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Backplane PLX VS1", + .children = p8dnu_plx_up, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dnu_phb1_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "GPU3", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dnu_phb1_3_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "GPU4", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dnu_npu1_slots[] = { + { + .etype = st_npu_slot, + .location = ST_LOC_NPU_GROUP(0), + .name = "GPU4", + }, + { + .etype = st_npu_slot, + .location = ST_LOC_NPU_GROUP(1), + .name = "GPU3", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dnu_phb_table[] = { + { + .etype = st_phb, + .location = ST_LOC_PHB(0,0), + .children = p8dnu_phb0_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,1), + .children = p8dnu_phb0_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,2), + .children = p8dnu_phb0_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,3), + .children = p8dnu_phb0_3_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,4), + .children = p8dnu_npu0_slots, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(1,0), + .children = p8dnu_phb1_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(1,1), + .children = p8dnu_phb1_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(1,2), + .children = p8dnu_phb1_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(1,3), + .children = p8dnu_phb1_3_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(1,4), + .children = p8dnu_npu1_slots, + }, + { .etype = st_end }, +}; + +#define NPU_BASE 0x8013c00 +#define NPU_SIZE 0x2c +#define NPU_INDIRECT0 0x8000000008010c3fUL +#define NPU_INDIRECT1 0x8000000008010c7fUL + +static void create_link(struct dt_node *npu, int group, int index) +{ + struct dt_node *link; + uint32_t lane_mask; + uint64_t phy; + char namebuf[32]; + + snprintf(namebuf, sizeof(namebuf), "link@%x", index); + link = dt_new(npu, namebuf); + + dt_add_property_string(link, "compatible", "ibm,npu-link"); + dt_add_property_cells(link, "ibm,npu-link-index", index); + + if (index < 4) { + phy = NPU_INDIRECT0; + lane_mask = 0xff << (index * 8); + } else { + phy = NPU_INDIRECT1; + lane_mask = 0xff0000 >> (index - 3) * 8; + } + dt_add_property_u64s(link, "ibm,npu-phy", phy); + dt_add_property_cells(link, "ibm,npu-lane-mask", lane_mask); + dt_add_property_cells(link, "ibm,npu-group-id", group); +} + +static void dt_create_npu(void) +{ + struct dt_node *xscom, *npu; + char namebuf[32]; + + dt_for_each_compatible(dt_root, xscom, "ibm,xscom") { + snprintf(namebuf, sizeof(namebuf), "npu@%x", NPU_BASE); + npu = dt_new(xscom, namebuf); + dt_add_property_cells(npu, "reg", NPU_BASE, NPU_SIZE); + dt_add_property_strings(npu, "compatible", "ibm,power8-npu"); + + /* + * Use the first available PHB index which is 4 given + * there are three normal PHBs. + */ + dt_add_property_cells(npu, "ibm,phb-index", 4); + dt_add_property_cells(npu, "ibm,npu-index", 0); + dt_add_property_cells(npu, "ibm,npu-links", 4); + + /* + * On p8dnu we have 2 links per GPU device. These are + * grouped together as per the slot tables above. + */ + create_link(npu, 0, 0); + create_link(npu, 0, 1); + create_link(npu, 1, 4); + create_link(npu, 1, 5); + } +} + +static bool p8dnu_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "supermicro,p8dnu")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Fixups until HB get the NPU bindings */ + dt_create_npu(); + + slot_table_init(p8dnu_phb_table); + + return true; +} + +static const struct bmc_sw_config bmc_sw_smc = { + .ipmi_oem_partial_add_esel = IPMI_CODE(0x3a, 0xf0), + .ipmi_oem_pnor_access_status = IPMI_CODE(0x3a, 0x07), +}; + +static const struct bmc_platform bmc_plat_ast2400_smc = { + .name = "SMC", + .hw = &bmc_hw_ast2400, + .sw = &bmc_sw_smc, +}; + +DECLARE_PLATFORM(p8dnu) = { + .name = "P8DNU", + .probe = p8dnu_probe, + .bmc = &bmc_plat_ast2400_smc, + .init = astbmc_init, + .pci_get_slot_info = slot_table_get_slot_info, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .exit = ipmi_wdt_final_reset, + .terminate = ipmi_terminate, + .seeprom_update = astbmc_seeprom_update, + .op_display = op_display_lpc, +}; diff --git a/roms/skiboot/platforms/astbmc/p8dtu.c b/roms/skiboot/platforms/astbmc/p8dtu.c new file mode 100644 index 000000000..a9d8dc068 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/p8dtu.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright 2016 Supermicro. + * Copyright 2016-2019 IBM Corp. + */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> + +#include "astbmc.h" + +static const struct slot_table_entry p8dtu_phb0_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "UIO Slot1", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dtu_phb0_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "UIO Network", + }, + { .etype = st_end }, +}; + + +static const struct slot_table_entry p8dtu_plx_slots[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(1,0), + .name = "PLX Slot1", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0x9,0), + .name = "Onboard USB", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0xa,0), + .name = "Onboard SATA1", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0xb,0), + .name = "Onboard BMC", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0xc,0), + .name = "Onboard SATA2", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dtu_plx_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = p8dtu_plx_slots, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dtu_phb0_1_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "PLX Switch", + .children = p8dtu_plx_up, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dtu_phb8_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot1", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dtu2u_phb8_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot3", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dtu2u_phb8_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot2", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dtu1u_phb8_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot2", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dtu1u_phb8_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot3", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dtu2u_phb_table[] = { + { + .etype = st_phb, + .location = ST_LOC_PHB(0,0), + .children = p8dtu_phb0_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,1), + .children = p8dtu_phb0_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,2), + .children = p8dtu_phb0_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,0), + .children = p8dtu_phb8_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,1), + .children = p8dtu2u_phb8_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,2), + .children = p8dtu2u_phb8_2_slot, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p8dtu1u_phb_table[] = { + { + .etype = st_phb, + .location = ST_LOC_PHB(0,0), + .children = p8dtu_phb0_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,1), + .children = p8dtu_phb0_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,2), + .children = p8dtu_phb0_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,0), + .children = p8dtu_phb8_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,1), + .children = p8dtu1u_phb8_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,2), + .children = p8dtu1u_phb8_2_slot, + }, + { .etype = st_end }, +}; + +static bool p8dtu1u_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "supermicro,p8dtu1u")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + slot_table_init(p8dtu1u_phb_table); + + return true; +} + +static bool p8dtu2u_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "supermicro,p8dtu2u")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + slot_table_init(p8dtu2u_phb_table); + + return true; +} + +static const struct bmc_sw_config bmc_sw_smc = { + .ipmi_oem_partial_add_esel = IPMI_CODE(0x3a, 0xf0), + .ipmi_oem_pnor_access_status = IPMI_CODE(0x3a, 0x07), + .ipmi_oem_hiomap_cmd = IPMI_CODE(0x3a, 0x5a), +}; + +/* Provided by Eric Chen (SMC) */ +static const struct bmc_hw_config p8dtu_bmc_hw = { + .scu_revision_id = 0x02010303, + .mcr_configuration = 0x00000577, + .mcr_scu_mpll = 0x000050c0, + .mcr_scu_strap = 0x00000000, +}; + +static const struct bmc_platform bmc_plat_ast2400_smc = { + .name = "SMC", + .hw = &p8dtu_bmc_hw, + .sw = &bmc_sw_smc, +}; + +DECLARE_PLATFORM(p8dtu1u) = { + .name = "p8dtu1u", + .probe = p8dtu1u_probe, + .bmc = &bmc_plat_ast2400_smc, + .init = astbmc_init, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .external_irq = astbmc_ext_irq_serirq_cpld, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .exit = ipmi_wdt_final_reset, + .terminate = ipmi_terminate, + .seeprom_update = astbmc_seeprom_update, + .op_display = op_display_lpc, +}; + +DECLARE_PLATFORM(p8dtu2u) = { + .name = "p8dtu2u", + .probe = p8dtu2u_probe, + .bmc = &bmc_plat_ast2400_smc, + .init = astbmc_init, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .external_irq = astbmc_ext_irq_serirq_cpld, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .exit = ipmi_wdt_final_reset, + .terminate = ipmi_terminate, + .seeprom_update = astbmc_seeprom_update, + .op_display = op_display_lpc, +}; + diff --git a/roms/skiboot/platforms/astbmc/p9dsu.c b/roms/skiboot/platforms/astbmc/p9dsu.c new file mode 100644 index 000000000..5c9756ec6 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/p9dsu.c @@ -0,0 +1,725 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright 2017 Supermicro Inc. + * Copyright 2018-2019 IBM Corp. + */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> +#include <psi.h> +#include <npu-regs.h> +#include <opal-internal.h> +#include <cpu.h> +#include <timebase.h> + +#include "astbmc.h" + +static bool p9dsu_riser_found = false; + +static const struct slot_table_entry p9dsu1u_phb0_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "UIO Slot1", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu1u_phb0_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "UIO Slot2", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu1u_phb0_2_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Onboard LAN", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu1u_phb0_3_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Onboard SAS", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu1u_phb0_4_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Onboard BMC", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu1u_phb0_5_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Onboard USB", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu1u_phb8_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot1", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu1u_phb8_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO-R Slot", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu1u_phb8_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot3", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu1u_phb8_3_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot2", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu1u_phb_table[] = { + { + .etype = st_phb, + .location = ST_LOC_PHB(0,0), + .children = p9dsu1u_phb0_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,1), + .children = p9dsu1u_phb0_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,2), + .children = p9dsu1u_phb0_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,3), + .children = p9dsu1u_phb0_3_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,4), + .children = p9dsu1u_phb0_4_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,5), + .children = p9dsu1u_phb0_5_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,0), + .children = p9dsu1u_phb8_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,1), + .children = p9dsu1u_phb8_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,2), + .children = p9dsu1u_phb8_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,3), + .children = p9dsu1u_phb8_3_slot, + }, + { .etype = st_end }, +}; + + +static const struct slot_table_entry p9dsu2u_phb0_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "UIO Slot1", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2u_phb0_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "UIO Slot2", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2u_phb0_2_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Onboard LAN", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2u_phb0_3_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Onboard SAS", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2u_phb0_4_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Onboard BMC", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2u_phb0_5_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Onboard USB", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2u_phb8_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot1", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2u_phb8_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO-R Slot", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2u_phb8_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot3", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2u_phb8_3_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot3", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2u_phb8_4_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot2", + .power_limit = 75, + }, + { .etype = st_end }, +}; + + +static const struct slot_table_entry p9dsu2u_phb_table[] = { + { + .etype = st_phb, + .location = ST_LOC_PHB(0,0), + .children = p9dsu2u_phb0_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,1), + .children = p9dsu2u_phb0_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,2), + .children = p9dsu2u_phb0_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,3), + .children = p9dsu2u_phb0_3_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,4), + .children = p9dsu2u_phb0_4_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,5), + .children = p9dsu2u_phb0_5_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,0), + .children = p9dsu2u_phb8_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,1), + .children = p9dsu2u_phb8_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,2), + .children = p9dsu2u_phb8_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,3), + .children = p9dsu2u_phb8_3_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,4), + .children = p9dsu2u_phb8_4_slot, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_uio_plx_down[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0x1,0), + .name = "UIO Slot2", + .power_limit = 75, + }, + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0x8,0), + .name = "PLX switch", + .power_limit = 75, + }, + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0x9,0), + .name = "Onboard LAN", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_uio_plx_up[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .children = p9dsu2uess_uio_plx_down, + .name = "PLX up", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_wio_plx_down[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0x1,0), + .name = "WIO Slot1", + .power_limit = 75, + }, + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0x8,0), + .name = "PLX switch", + .power_limit = 75, + }, + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0x9,0), + .name = "WIO Slot2", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_wio_plx_up[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .children = p9dsu2uess_wio_plx_down, + .name = "PLX up", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_phb0_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "UIO Slot1", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_phb0_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .children = p9dsu2uess_uio_plx_up, + .name = "PLX", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_phb0_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "UIO Slot3", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_phb0_3_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Onboard SAS", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_phb0_4_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Onboard BMC", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_phb0_5_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Onboard USB", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_phb8_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot3", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_phb8_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO-R Slot", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_phb8_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .children = p9dsu2uess_wio_plx_up, + .name = "PLX", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_phb8_3_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot4", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_phb8_4_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "WIO Slot5", + .power_limit = 75, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry p9dsu2uess_phb_table[] = { + { + .etype = st_phb, + .location = ST_LOC_PHB(0,0), + .children = p9dsu2uess_phb0_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,1), + .children = p9dsu2uess_phb0_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,2), + .children = p9dsu2uess_phb0_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,3), + .children = p9dsu2uess_phb0_3_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,4), + .children = p9dsu2uess_phb0_4_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,5), + .children = p9dsu2uess_phb0_5_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,0), + .children = p9dsu2uess_phb8_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,1), + .children = p9dsu2uess_phb8_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,2), + .children = p9dsu2uess_phb8_2_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,3), + .children = p9dsu2uess_phb8_3_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(8,4), + .children = p9dsu2uess_phb8_4_slot, + }, + { .etype = st_end }, +}; + + +/* + * HACK: Hostboot doesn't export the correct data for the system VPD EEPROM + * for this system. So we need to work around it here. + */ +static void p9dsu_dt_fixups(void) +{ + struct dt_node *n = dt_find_by_path(dt_root, + "/xscom@603fc00000000/i2cm@a2000/i2c-bus@0/eeprom@50"); + + if (n) { + dt_check_del_prop(n, "compatible"); + dt_add_property_string(n, "compatible", "atmel,24c256"); + + dt_check_del_prop(n, "label"); + dt_add_property_string(n, "label", "system-vpd"); + } +} + +static bool p9dsu_probe(void) +{ + if (!(dt_node_is_compatible(dt_root, "supermicro,p9dsu") || + dt_node_is_compatible(dt_root, "supermicro,p9dsu1u") || + dt_node_is_compatible(dt_root, "supermicro,p9dsu2u") || + dt_node_is_compatible(dt_root, "supermicro,p9dsu2uess"))) + return false; + + p9dsu_riser_found = true; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Setup UART for use by OPAL (Linux hvc) */ + uart_set_console_policy(UART_CONSOLE_OPAL); + + p9dsu_dt_fixups(); + + if (dt_node_is_compatible(dt_root, "supermicro,p9dsu1u")) { + prlog(PR_INFO, "Detected p9dsu1u variant\n"); + slot_table_init(p9dsu1u_phb_table); + } else if (dt_node_is_compatible(dt_root, "supermicro,p9dsu2u")) { + prlog(PR_INFO, "Detected p9dsu2u variant\n"); + slot_table_init(p9dsu2u_phb_table); + } else if (dt_node_is_compatible(dt_root, "supermicro,p9dsu2uess")) { + prlog(PR_INFO, "Detected p9dsu2uess variant\n"); + slot_table_init(p9dsu2uess_phb_table); + } else { + /* + * else we need to ask the BMC what subtype we are, but we need IPMI + * which we don't get until astbmc_init(), so we delay setting up the + * slot table until later. + * + * This only applies if you're using a Hostboot that doesn't do this + * for us. + */ + p9dsu_riser_found = false; + } + + return true; +} + +static void p9dsu_riser_query_complete(struct ipmi_msg *m) +{ + u8 *riser_id = (u8*)m->user_data; + lwsync(); + *riser_id = m->data[0]; + ipmi_free_msg(m); +} + +static void p9dsu_init(void) +{ + u8 smc_riser_req[] = {0x03, 0x70, 0x01, 0x02}; + struct ipmi_msg *ipmi_msg; + u8 riser_id = 0; + const char *p9dsu_variant; + int timeout_ms = 3000; + + astbmc_init(); + /* + * Now we have IPMI up and running we can ask the BMC for what p9dsu + * variant we are if Hostboot isn't the patched one that does this + * for us. + */ + if (!p9dsu_riser_found) { + ipmi_msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, + IPMI_CODE(IPMI_NETFN_APP, 0x52), + p9dsu_riser_query_complete, + &riser_id, + smc_riser_req, sizeof(smc_riser_req), 1); + ipmi_queue_msg(ipmi_msg); + while(riser_id==0 && timeout_ms > 0) { + time_wait_ms(10); + timeout_ms -= 10; + } + switch (riser_id) { + case 0x9: + p9dsu_variant = "supermicro,p9dsu1u"; + slot_table_init(p9dsu1u_phb_table); + break; + case 0x19: + p9dsu_variant = "supermicro,p9dsu2u"; + slot_table_init(p9dsu2u_phb_table); + break; + case 0x1D: + p9dsu_variant = "supermicro,p9dsu2uess"; + slot_table_init(p9dsu2uess_phb_table); + break; + default: + prlog(PR_ERR, "Defaulting to p9dsu2uess\n"); + p9dsu_variant = "supermicro,p9dsu2uess"; + slot_table_init(p9dsu2uess_phb_table); + break; + } + prlog(PR_INFO,"Detected %s variant via IPMI\n", p9dsu_variant); + dt_check_del_prop(dt_root, "compatible"); + dt_add_property_strings(dt_root, "compatible", "ibm,powernv", + "supermicro,p9dsu", p9dsu_variant); + } +} + +static const struct bmc_sw_config bmc_sw_smc = { + .ipmi_oem_partial_add_esel = IPMI_CODE(0x3a, 0xf0), + .ipmi_oem_hiomap_cmd = IPMI_CODE(0x3a, 0x5a), +}; + +/* Provided by Eric Chen (SMC) */ +static const struct bmc_hw_config p9dsu_bmc_hw = { + .scu_revision_id = 0x04030303, + .mcr_configuration = 0x11000756, + .mcr_scu_mpll = 0x000071c1, + .mcr_scu_strap = 0x00000000, +}; + +static const struct bmc_platform bmc_plat_ast2500_smc = { + .name = "SMC", + .hw = &p9dsu_bmc_hw, + .sw = &bmc_sw_smc, +}; + +DECLARE_PLATFORM(p9dsu1u) = { + .name = "p9dsu", + .probe = p9dsu_probe, + .init = p9dsu_init, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .bmc = &bmc_plat_ast2500_smc, + .pci_get_slot_info = slot_table_get_slot_info, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .exit = ipmi_wdt_final_reset, + .terminate = ipmi_terminate, + .op_display = op_display_lpc, +}; diff --git a/roms/skiboot/platforms/astbmc/palmetto.c b/roms/skiboot/platforms/astbmc/palmetto.c new file mode 100644 index 000000000..546d51197 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/palmetto.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> + +#include "astbmc.h" + +static const struct slot_table_entry palmetto_phb0_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot2", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry palmetto_plx_slots[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(1,0), + .name = "Backplane BMC", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(2,0), + .name = "Backplane USB", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(3,0), + .name = "Backplane Network", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(4,0), + .name = "Backplane SATA", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry palmetto_plx_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = palmetto_plx_slots, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry palmetto_phb0_1_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Backplane PLX", + .children = palmetto_plx_up, + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry palmetto_phb0_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "Slot1", + }, + { .etype = st_end }, +}; + +static const struct slot_table_entry palmetto_phb_table[] = { + { + .etype = st_phb, + .location = ST_LOC_PHB(0,0), + .children = palmetto_phb0_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,1), + .children = palmetto_phb0_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(0,2), + .children = palmetto_phb0_2_slot, + }, + { .etype = st_end }, +}; + +static bool palmetto_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "ibm,powernv") || + !dt_node_is_compatible(dt_root, "tyan,palmetto")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + slot_table_init(palmetto_phb_table); + + return true; +} + + +DECLARE_PLATFORM(palmetto) = { + .name = "Palmetto", + .probe = palmetto_probe, + .bmc = &bmc_plat_ast2400_ami, + .init = astbmc_init, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .external_irq = astbmc_ext_irq_serirq_cpld, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .exit = ipmi_wdt_final_reset, + .terminate = ipmi_terminate, + .op_display = op_display_lpc, +}; diff --git a/roms/skiboot/platforms/astbmc/pnor.c b/roms/skiboot/platforms/astbmc/pnor.c new file mode 100644 index 000000000..64f2249d1 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/pnor.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <opal.h> +#include <libflash/ipmi-hiomap.h> +#include <libflash/mbox-flash.h> +#include <libflash/libflash.h> +#include <libflash/libffs.h> +#include <libflash/blocklevel.h> +#include <ast.h> + +#include "astbmc.h" + +enum ast_flash_style { + raw_flash, + raw_mem, + ipmi_hiomap, + mbox_hiomap, +}; + +static enum ast_flash_style ast_flash_get_fallback_style(void) +{ + if (ast_lpc_fw_mbox_hiomap()) + return mbox_hiomap; + + if (ast_lpc_fw_maps_flash()) + return raw_flash; + + return raw_mem; +} + +int pnor_init(void) +{ + struct spi_flash_ctrl *pnor_ctrl = NULL; + struct blocklevel_device *bl = NULL; + enum ast_flash_style style; + int rc = 0; + + if (ast_lpc_fw_ipmi_hiomap()) { + style = ipmi_hiomap; + rc = ipmi_hiomap_init(&bl); + } + + if (!ast_lpc_fw_ipmi_hiomap() || rc) { + if (!ast_sio_is_enabled()) + return -ENODEV; + + style = ast_flash_get_fallback_style(); + if (style == mbox_hiomap) + rc = mbox_flash_init(&bl); + else if (style == raw_flash) + rc = ast_sf_open(AST_SF_TYPE_PNOR, &pnor_ctrl); + else if (style == raw_mem) + rc = ast_sf_open(AST_SF_TYPE_MEM, &pnor_ctrl); + else { + prerror("Unhandled flash mode: %d\n", style); + return -ENODEV; + } + } + + if (rc) { + prerror("PLAT: Failed to init PNOR driver\n"); + goto fail; + } + + if (style == raw_flash || style == raw_mem) { + rc = flash_init(pnor_ctrl, &bl, NULL); + if (rc) + goto fail; + } + + rc = flash_register(bl); + if (!rc) + return 0; + +fail: + if (bl) { + switch (style) { + case raw_flash: + case raw_mem: + flash_exit(bl); + break; + case ipmi_hiomap: + ipmi_hiomap_exit(bl); + break; + case mbox_hiomap: + mbox_flash_exit(bl); + break; + } + } + if (pnor_ctrl) + ast_sf_close(pnor_ctrl); + + return rc; +} diff --git a/roms/skiboot/platforms/astbmc/rainier.c b/roms/skiboot/platforms/astbmc/rainier.c new file mode 100644 index 000000000..17d9fe2bf --- /dev/null +++ b/roms/skiboot/platforms/astbmc/rainier.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (c) 2020 IBM + */ + +#include <skiboot.h> +#include <device.h> +#include <ipmi.h> +#include <chip.h> +#include <i2c.h> +#include <timebase.h> + +#include "astbmc.h" + +/* + * puti2c pu 2 1 C6 00 6 1 -quiet + * puti2c pu 2 1 C6 54 7 1 -quiet + * puti2c pu 2 1 C6 05 8 1 -quiet + * puti2c pu 2 1 C6 00 9 1 -quiet + * + * sleep 4 + * + * puti2c pu 2 1 C6 55 6 1 -quiet + * puti2c pu 2 1 C6 55 7 1 -quiet + * 2 - engine + * 1 - port + * C6 - slave addr + * 55 - data + * 7 - register + * 1 - register length? + */ + +static int64_t smbus_write8(struct i2c_bus *bus, uint8_t reg, uint8_t data) +{ + struct i2c_request req; + + memset(&req, 0, sizeof(req)); + + req.bus = bus; + req.dev_addr = 0xC6 >> 1; /* Docs use 8bit addresses */ + + req.op = SMBUS_WRITE; + req.offset = reg; + req.offset_bytes = 1; + req.rw_buf = &data; + req.rw_len = 1; + req.timeout = 100; + + return i2c_request_sync(&req); +} + +static int64_t slot_power_enable(struct i2c_bus *bus) +{ + /* FIXME: we could do this in one transaction using auto-increment */ + if (smbus_write8(bus, 0x6, 0x00)) + return -1; + if (smbus_write8(bus, 0x7, 0x54)) + return -1; + if (smbus_write8(bus, 0x8, 0x05)) + return -1; + if (smbus_write8(bus, 0x9, 0x00)) + return -1; + + /* FIXME: Poll for PGOOD going high */ + + if (smbus_write8(bus, 0x6, 0x55)) + return -1; + if (smbus_write8(bus, 0x7, 0x55)) + return -1; + + return 0; +} + +static void rainier_init_slot_power(void) +{ + struct proc_chip *chip; + struct i2c_bus *bus; + + /* + * Controller on P0 is for slots C7 -> C11 + * on P2 is for slots C0 -> C4 + * Both chips use engine 2 port 1 + * + * Rainier with only one socket is officially supported, so + * we may not have slots C0 -> C4 + */ + for_each_chip(chip) { + if (chip->id % 4) + continue; + bus = p8_i2c_add_bus(chip->id, 2, 1, 400000); + if (!bus) { + prerror("Unable to find PCIe power controller I2C bus!\n"); + return; + } + if (slot_power_enable(bus)) { + prerror("Error enabling PCIe slot power on chip %d\n", + chip->id); + } + } +} + +static void rainier_init(void) +{ + astbmc_init(); + rainier_init_slot_power(); +} + +static bool rainier_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "ibm,rainier") && + !dt_node_is_compatible(dt_root, "ibm,rainier-2s2u") && + !dt_node_is_compatible(dt_root, "ibm,rainier-2s4u")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Setup UART for use by OPAL (Linux hvc) */ + uart_set_console_policy(UART_CONSOLE_OPAL); + + return true; +} + +DECLARE_PLATFORM(rainier) = { + .name = "Rainier", + .probe = rainier_probe, + .init = rainier_init, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .bmc = &bmc_plat_ast2600_openbmc, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .exit = astbmc_exit, + .terminate = ipmi_terminate, +}; diff --git a/roms/skiboot/platforms/astbmc/romulus.c b/roms/skiboot/platforms/astbmc/romulus.c new file mode 100644 index 000000000..9afb7d3de --- /dev/null +++ b/roms/skiboot/platforms/astbmc/romulus.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2017-2019 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> +#include <psi.h> +#include <npu-regs.h> + +#include "astbmc.h" + +ST_PLUGGABLE(romulus_cpu1_slot1, "CPU1 Slot1 (8x)"); +ST_PLUGGABLE(romulus_cpu1_slot2, "CPU1 Slot2 (16x)"); + +ST_PLUGGABLE(romulus_cpu2_slot1, "CPU2 Slot1 (16x)"); +ST_PLUGGABLE(romulus_cpu2_slot2, "CPU2 Slot2 (16x)"); +ST_PLUGGABLE(romulus_cpu2_slot3, "CPU2 Slot3 (8x)"); + +ST_BUILTIN_DEV(romulus_builtin_raid, "Builtin RAID"); +ST_BUILTIN_DEV(romulus_builtin_usb, "Builtin USB"); +ST_BUILTIN_DEV(romulus_builtin_ethernet, "Builtin Ethernet"); +ST_BUILTIN_DEV(romulus_builtin_bmc, "BMC"); + +static const struct slot_table_entry romulus_phb_table[] = { + ST_PHB_ENTRY(0, 0, romulus_cpu1_slot2), + ST_PHB_ENTRY(0, 1, romulus_cpu1_slot1), + + ST_PHB_ENTRY(0, 2, romulus_builtin_raid), + ST_PHB_ENTRY(0, 3, romulus_builtin_usb), + ST_PHB_ENTRY(0, 4, romulus_builtin_ethernet), + ST_PHB_ENTRY(0, 5, romulus_builtin_bmc), + + ST_PHB_ENTRY(8, 0, romulus_cpu2_slot2), // might be swapped with 3 + ST_PHB_ENTRY(8, 1, romulus_cpu2_slot3), // might be PHB1 or 2 + ST_PHB_ENTRY(8, 3, romulus_cpu2_slot1), + + { .etype = st_end }, +}; + +static bool romulus_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "ibm,romulus")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Setup UART for use by OPAL (Linux hvc) */ + uart_set_console_policy(UART_CONSOLE_OPAL); + + slot_table_init(romulus_phb_table); + + return true; +} + +DECLARE_PLATFORM(romulus) = { + .name = "Romulus", + .probe = romulus_probe, + .init = astbmc_init, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .bmc = &bmc_plat_ast2500_openbmc, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .exit = astbmc_exit, + .terminate = ipmi_terminate, + .op_display = op_display_lpc, +}; diff --git a/roms/skiboot/platforms/astbmc/slots.c b/roms/skiboot/platforms/astbmc/slots.c new file mode 100644 index 000000000..622483a15 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/slots.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2015-2018 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <pci-cfg.h> +#include <pci.h> +#include <pci-slot.h> + +#include "astbmc.h" + +static const struct slot_table_entry *slot_top_table; + +void slot_table_init(const struct slot_table_entry *top_table) +{ + slot_top_table = top_table; +} + +static const struct slot_table_entry *match_slot_phb_entry(struct phb *phb) +{ + uint32_t chip_id = dt_get_chip_id(phb->dt_node); + uint32_t phb_idx = dt_prop_get_u32_def(phb->dt_node, + "ibm,phb-index", 0); + const struct slot_table_entry *ent; + + if (!slot_top_table) + return NULL; + + for (ent = slot_top_table; ent->etype != st_end; ent++) { + if (ent->etype != st_phb) { + prerror("SLOT: Bad DEV entry type in table !\n"); + continue; + } + if (ent->location == ST_LOC_PHB(chip_id, phb_idx)) + return ent; + } + return NULL; +} + +static const struct slot_table_entry *match_slot_dev_entry(struct phb *phb, + struct pci_device *pd) +{ + const struct slot_table_entry *parent, *ent; + uint32_t bdfn; + + /* Find a parent recursively */ + if (pd->parent) + parent = match_slot_dev_entry(phb, pd->parent); + else { + /* No parent, this is a root complex, find the PHB */ + parent = match_slot_phb_entry(phb); + } + /* No parent ? Oops ... */ + if (!parent || !parent->children) + return NULL; + for (ent = parent->children; ent->etype != st_end; ent++) { + if (ent->etype == st_phb) { + prerror("SLOT: Bad PHB entry type in table !\n"); + continue; + } + + /* NPU slots match on device, not function */ + if (ent->etype == st_npu_slot) + bdfn = pd->bdfn & 0xf8; + else + bdfn = pd->bdfn & 0xff; + + if (ent->location == bdfn) + return ent; + } + return NULL; +} + +static void slot_table_add_properties(struct pci_slot *slot, + struct dt_node *np) +{ + struct slot_table_entry *ent = slot->data; + + if (ent) + pci_slot_add_loc(slot, np, ent->name); + else + pci_slot_add_loc(slot, np, NULL); +} + +void slot_table_add_slot_info(struct pci_device *pd, + const struct slot_table_entry *ent) +{ + struct pci_slot *slot; + + if (!ent || !ent->name) { + slot = pcie_slot_create_dynamic(pd->phb, pd); + if (slot) { + slot->ops.add_properties = slot_table_add_properties; + slot->pluggable = true; + } + + return; + } + + slot = pcie_slot_create(pd->phb, pd); + assert(slot); + + slot->pluggable = !!(ent->etype == st_pluggable_slot); + slot->ops.add_properties = slot_table_add_properties; + slot->power_limit = ent->power_limit; + slot->data = (void *)ent; +} + +void slot_table_get_slot_info(struct phb *phb, struct pci_device *pd) +{ + const struct slot_table_entry *ent; + + if (!pd || pd->slot) + return; + + ent = match_slot_dev_entry(phb, pd); + slot_table_add_slot_info(pd, ent); +} + +static void dt_slot_add_properties(struct pci_slot *slot, + struct dt_node *np) +{ + struct dt_node *slot_np = slot->data; + const char *label = NULL; + + if (slot_np) + label = dt_prop_get_def(slot_np, "ibm,slot-label", NULL); + + pci_slot_add_loc(slot, np, label); +} + +void dt_slot_get_slot_info(struct phb *phb, struct pci_device *pd) +{ + struct dt_node *slot_np; + struct pci_slot *slot; + const char *name = NULL; + uint32_t power_limit = 0; + bool pluggable = false; + + if (!pd || pd->slot) + return; + + slot_np = map_pci_dev_to_slot(phb, pd); + if (slot_np) { + pluggable = dt_has_node_property(slot_np, + "ibm,pluggable", NULL); + power_limit = dt_prop_get_u32_def(slot_np, + "ibm,power-limit", 0); + name = dt_prop_get_def(slot_np, "ibm,slot-label", NULL); + } + + if (!slot_np || !name) { + slot = pcie_slot_create_dynamic(phb, pd); + if (slot) { + slot->ops.add_properties = dt_slot_add_properties; + slot->pluggable = true; + slot->data = (void *)slot_np; + } + + return; + } + + slot = pcie_slot_create(phb, pd); + assert(slot); + + slot->ops.add_properties = dt_slot_add_properties; + slot->pluggable = pluggable; + slot->power_limit = power_limit; + slot->data = (void *)slot_np; +} + +static int __pci_find_dev_by_location(struct phb *phb, + struct pci_device *pd, void *userdata) +{ + uint16_t location = *((uint16_t *)userdata); + + if (!phb || !pd) + return 0; + + if ((pd->bdfn & 0xff) == location) + return 1; + + return 0; +} + +static struct pci_device *pci_find_dev_by_location(struct phb *phb, uint16_t location) +{ + return pci_walk_dev(phb, NULL, __pci_find_dev_by_location, &location); +} + +static struct phb* get_phb_by_location(uint32_t location) +{ + struct phb *phb = NULL; + uint32_t chip_id, phb_idx; + + for_each_phb(phb) { + chip_id = dt_get_chip_id(phb->dt_node); + phb_idx = dt_prop_get_u32_def(phb->dt_node, + "ibm,phb-index", 0); + if (location == ST_LOC_PHB(chip_id, phb_idx)) + break; + } + + return phb; +} + +static int check_slot_table(struct phb *phb, + const struct slot_table_entry *parent) +{ + const struct slot_table_entry *ent; + struct pci_device *dev = NULL; + int r = 0; + + if (parent == NULL) + return 0; + + for (ent = parent; ent->etype != st_end; ent++) { + switch (ent->etype) { + case st_phb: + phb = get_phb_by_location(ent->location); + if (!phb) { + prlog(PR_ERR, "PCI: PHB %s (%x) not found\n", + ent->name, ent->location); + r++; + } + break; + case st_pluggable_slot: + case st_builtin_dev: + if (!phb) + break; + phb_lock(phb); + dev = pci_find_dev_by_location(phb, ent->location); + phb_unlock(phb); + if (!dev) { + prlog(PR_ERR, "PCI: built-in device not found: %s (loc: %x)\n", + ent->name, ent->location); + r++; + } + break; + case st_end: + case st_npu_slot: + break; + } + if (ent->children) + r+= check_slot_table(phb, ent->children); + } + return r; +} + +void check_all_slot_table(void) +{ + if (!slot_top_table) + return; + + prlog(PR_DEBUG, "PCI: Checking slot table against detected devices\n"); + check_slot_table(NULL, slot_top_table); +} diff --git a/roms/skiboot/platforms/astbmc/swift.c b/roms/skiboot/platforms/astbmc/swift.c new file mode 100644 index 000000000..991a79d42 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/swift.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Copyright 2019 IBM Corp. + */ + +#include <skiboot.h> +#include <ipmi.h> +#include <npu3.h> +#include "astbmc.h" + +/* nvidia,link-speed uses a magic driver value */ +#define NVIDIA_LINK_SPEED_20000000000_BPS 3 +#define NVIDIA_LINK_SPEED_25781250000_BPS 8 +#define NVIDIA_LINK_SPEED_25000000000_BPS 9 + +static void swift_npu3_device_detect(struct npu3 *npu) +{ + struct npu3_dev *dev; + uint32_t node, gpu_index; + char slot[6]; + + node = P9_GCID2NODEID(npu->chip_id); + + switch (npu->index) { + case 0: + gpu_index = node * 2 + 1; + break; + case 2: + gpu_index = node * 2; + break; + default: + return; + } + + snprintf(slot, sizeof(slot), "GPU%d", gpu_index); + + npu3_for_each_dev(dev, npu) { + dev->type = NPU3_DEV_TYPE_NVLINK; + dt_add_property_string(dev->dn, "ibm,slot-label", slot); + dt_add_property_u64(dev->dn, "ibm,link-speed", 25000000000ull); + dt_add_property_cells(dev->dn, "nvidia,link-speed", + NVIDIA_LINK_SPEED_25000000000_BPS); + } +} + +#define SWIFT_POSSIBLE_GPUS 4 + +#define G(g) (devs[g] ? devs[g]->nvlink.gpu->dn->phandle : 0) +#define N(g) (devs[g] ? devs[g]->npu->nvlink.phb.dt_node->phandle : 0) + +#define add_peers_prop(g, p...) \ + if (devs[g]) \ + dt_add_property_cells(devs[g]->nvlink.gpu->dn, \ + "ibm,nvlink-peers", ##p) + +static void swift_finalise_dt(bool is_reboot) +{ + struct npu3 *npu; + struct npu3_dev *dev; + struct npu3_dev *devs[SWIFT_POSSIBLE_GPUS] = {}; + int32_t index; + + if (is_reboot) + return; + + /* Collect the first link we find for each GPU */ + npu3_for_each_nvlink_npu(npu) { + npu3_for_each_nvlink_dev(dev, npu) { + index = npu3_dev_gpu_index(dev); + if (index == -1 || index >= ARRAY_SIZE(devs)) + continue; + + if (dev->nvlink.gpu && !devs[index]) + devs[index] = dev; + } + } + + /* Add GPU interconnect properties */ + add_peers_prop(0, G(3), G(2), G(2), G(2), + G(3), G(1), G(1), G(1), + N(0), N(0), N(0), N(0)); + + add_peers_prop(1, G(2), G(3), G(3), G(3), + G(0), G(0), G(0), G(2), + N(1), N(1), N(1), N(1)); + + add_peers_prop(2, G(1), G(3), G(3), G(3), + G(0), G(0), G(0), G(1), + N(2), N(2), N(2), N(2)); + + add_peers_prop(3, G(2), G(2), G(2), G(0), + G(1), G(1), G(0), G(1), + N(3), N(3), N(3), N(3)); +} + +static bool swift_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "ibm,swift")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Setup UART for use by OPAL (Linux hvc) */ + uart_set_console_policy(UART_CONSOLE_OPAL); + + return true; +} + +DECLARE_PLATFORM(swift) = { + .bmc = &bmc_plat_ast2500_openbmc, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .exit = astbmc_exit, + .finalise_dt = swift_finalise_dt, + .init = astbmc_init, + .name = "Swift", + .npu3_device_detect = swift_npu3_device_detect, + .pci_get_slot_info = dt_slot_get_slot_info, + .probe = swift_probe, + .resource_loaded = flash_resource_loaded, + .start_preload_resource = flash_start_preload_resource, + .terminate = ipmi_terminate, +}; diff --git a/roms/skiboot/platforms/astbmc/talos.c b/roms/skiboot/platforms/astbmc/talos.c new file mode 100644 index 000000000..a5a37d342 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/talos.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Copyright 2017-2019 IBM Corp. + * Copyright 2018-2019 Raptor Engineering, LLC + */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> +#include <psi.h> +#include <npu-regs.h> + +#include "astbmc.h" + +ST_PLUGGABLE(talos_cpu1_slot1, "CPU1 Slot1 (8x)"); +ST_PLUGGABLE(talos_cpu1_slot2, "CPU1 Slot2 (16x)"); + +ST_PLUGGABLE(talos_cpu2_slot1, "CPU2 Slot1 (16x)"); +ST_PLUGGABLE(talos_cpu2_slot2, "CPU2 Slot2 (16x)"); +ST_PLUGGABLE(talos_cpu2_slot3, "CPU2 Slot3 (8x)"); + +ST_BUILTIN_DEV(talos_builtin_raid, "Builtin SAS"); +ST_BUILTIN_DEV(talos_builtin_usb, "Builtin USB"); +ST_BUILTIN_DEV(talos_builtin_ethernet, "Builtin Ethernet"); +ST_BUILTIN_DEV(talos_builtin_bmc, "BMC"); + +static const struct slot_table_entry talos_phb_table[] = { + ST_PHB_ENTRY(0, 0, talos_cpu1_slot2), + ST_PHB_ENTRY(0, 1, talos_cpu1_slot1), + + ST_PHB_ENTRY(0, 2, talos_builtin_raid), + ST_PHB_ENTRY(0, 3, talos_builtin_usb), + ST_PHB_ENTRY(0, 4, talos_builtin_ethernet), + ST_PHB_ENTRY(0, 5, talos_builtin_bmc), + + ST_PHB_ENTRY(8, 0, talos_cpu2_slot2), // might be swapped with 3 + ST_PHB_ENTRY(8, 1, talos_cpu2_slot3), // might be PHB1 or 2 + ST_PHB_ENTRY(8, 3, talos_cpu2_slot1), + + { .etype = st_end }, +}; + +static bool talos_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "rcs,talos")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Setup UART for use by OPAL (Linux hvc) */ + uart_set_console_policy(UART_CONSOLE_OPAL); + + slot_table_init(talos_phb_table); + + return true; +} + +DECLARE_PLATFORM(talos) = { + .name = "Talos", + .probe = talos_probe, + .init = astbmc_init, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .bmc = &bmc_plat_ast2500_openbmc, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .exit = astbmc_exit, + .terminate = ipmi_terminate, + .op_display = op_display_lpc, +}; diff --git a/roms/skiboot/platforms/astbmc/vesnin.c b/roms/skiboot/platforms/astbmc/vesnin.c new file mode 100644 index 000000000..f7e1844fc --- /dev/null +++ b/roms/skiboot/platforms/astbmc/vesnin.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Copyright (c) 2018 YADRO + * Copyright 2018-2019 IBM Corp. + */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> +#include <pci.h> +#include <pci-cfg.h> + +#include "astbmc.h" + +#define CHIP_ID_CPU0 0x00 +#define CHIP_ID_CPU1 0x08 +#define CHIP_ID_CPU2 0x10 +#define CHIP_ID_CPU3 0x18 + +/* IPMI message code for PCI inventory (OEM). */ +#define PCIINV_IPMI_CODE IPMI_CODE(0x2e, 0x2a) +/* IANA number used to identify IPMI OEM command group. */ +#define PCIINV_OEM_IANA 49769 /* YADRO */ + +/** + * struct pciinv_device - PCI device inventory description. + * @domain_num: Domain number. + * @bus_num: Bus number. + * @device_num: Device number. + * @func_num: Function number. + * @vendor_id: Vendor Id. + * @device_id: Device Id. + * @class_code: Device class code. + * @revision: Revision number. + * + * All fields have Big Endian byte order. + */ +struct pciinv_device { + beint16_t domain_num; + uint8_t bus_num; + uint8_t device_num; + uint8_t func_num; + beint16_t vendor_id; + beint16_t device_id; + beint32_t class_code; + uint8_t revision; +} __packed; + +/** + * struct pciinv_message - IPMI message packet data. + * @iana: IANA id for OEM message, must be set to PCIINV_OEM_IANA. + * @reset: Reset flag. + * @device: PCI device description. + */ +struct pciinv_message { + uint8_t iana[3]; + uint8_t reset; + struct pciinv_device device; +} __packed; + + +static const struct slot_table_entry vesnin_phb0_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "AUX connector00", + }, + { .etype = st_end } +}; + + +static const struct slot_table_entry vesnin_plx_slots[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0x01,0), + .name = "Backplane SSD0", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0x02,0), + .name = "Backplane SSD1", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0x03,0), + .name = "Backplane LAN", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0x04,0), + .name = "Backplane BMC", + }, + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0x05,0), + .name = "Backplane USB", + }, + { .etype = st_end } +}; + +static const struct slot_table_entry vesnin_plx_up[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .children = vesnin_plx_slots, + }, + { .etype = st_end } +}; + +static const struct slot_table_entry vesnin_phb0_1_slot[] = { + { + .etype = st_builtin_dev, + .location = ST_LOC_DEVFN(0,0), + .name = "Backplane PLX", + .children = vesnin_plx_up, + }, + { .etype = st_end } +}; + +static const struct slot_table_entry vesnin_phb0_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "PCIE0_x8_CPU0", + }, + { .etype = st_end } +}; + +static const struct slot_table_entry vesnin_phb8_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "PCIE1_x16_CPU1", + }, + { .etype = st_end } +}; + +static const struct slot_table_entry vesnin_phb8_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "AUX connector10", + }, + { .etype = st_end } +}; + +static const struct slot_table_entry vesnin_phb9_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "AUX connector30", + }, + { .etype = st_end } +}; + +static const struct slot_table_entry vesnin_phb9_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "PCIE3_x8_CPU2", + }, + { .etype = st_end } +}; + +static const struct slot_table_entry vesnin_phb9_2_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "PCIE2_x8_CPU2", + }, + { .etype = st_end } +}; + +static const struct slot_table_entry vesnin_phbA_0_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "PCIE4_x16_CPU3", + }, + { .etype = st_end } +}; + +static const struct slot_table_entry vesnin_phbA_1_slot[] = { + { + .etype = st_pluggable_slot, + .location = ST_LOC_DEVFN(0,0), + .name = "AUX connector40", + }, + { .etype = st_end } +}; + +static const struct slot_table_entry vesnin_phb_table[] = { + { + .etype = st_phb, + .location = ST_LOC_PHB(CHIP_ID_CPU0,0), + .children = vesnin_phb0_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(CHIP_ID_CPU0,1), + .children = vesnin_phb0_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(CHIP_ID_CPU0,2), + .children = vesnin_phb0_2_slot, + }, + + { + .etype = st_phb, + .location = ST_LOC_PHB(CHIP_ID_CPU1,0), + .children = vesnin_phb8_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(CHIP_ID_CPU1,1), + .children = vesnin_phb8_1_slot, + }, + + { + .etype = st_phb, + .location = ST_LOC_PHB(CHIP_ID_CPU2,0), + .children = vesnin_phb9_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(CHIP_ID_CPU2,1), + .children = vesnin_phb9_1_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(CHIP_ID_CPU2,2), + .children = vesnin_phb9_2_slot, + }, + + { + .etype = st_phb, + .location = ST_LOC_PHB(CHIP_ID_CPU3,0), + .children = vesnin_phbA_0_slot, + }, + { + .etype = st_phb, + .location = ST_LOC_PHB(CHIP_ID_CPU3,1), + .children = vesnin_phbA_1_slot, + }, + { .etype = st_end } +}; + +/** + * pciinv_walk() - Callback from PCI enumerator, see :c:func:`pci_walk_dev`. + * User data parameter is interpreted as a pointer to pciinv_message structure. + */ +static int pciinv_walk(struct phb *phb, struct pci_device *pd, void *data) +{ + struct ipmi_msg *msg; + struct pciinv_message* pack = (struct pciinv_message*)data; + + /* PCI device filter: Skip non-EP devices */ + if (pci_has_cap(pd, PCI_CFG_CAP_ID_EXP, false)) { + if (pd->dev_type != PCIE_TYPE_ENDPOINT) + return OPAL_SUCCESS; + } + else if (pd->is_bridge) + return OPAL_SUCCESS; + + /* Fill the PCI device inventory description */ + pack->device.domain_num = cpu_to_be16(phb->opal_id & 0xffff); + pack->device.bus_num = PCI_BUS_NUM(pd->bdfn); + pack->device.device_num = PCI_DEV(pd->bdfn); + pack->device.func_num = PCI_FUNC(pd->bdfn); + pack->device.vendor_id = cpu_to_be16(PCI_VENDOR_ID(pd->vdid)); + pack->device.device_id = cpu_to_be16(PCI_DEVICE_ID(pd->vdid)); + pack->device.class_code = cpu_to_be32(pd->class & 0xffffff); + pci_cfg_read8(phb, pd->bdfn, PCI_CFG_REV_ID, &pack->device.revision); + + msg = ipmi_mkmsg_simple(PCIINV_IPMI_CODE, pack, sizeof(*pack)); + if (!msg) + return OPAL_HARDWARE; + + ipmi_queue_msg(msg); + + /* Disable reset flag for further messages in the current session. */ + pack->reset = 0; + + return OPAL_SUCCESS; +} + +static void vesnin_pci_probe_complete(void) +{ + struct phb *phb; + + /* IPMI message packet instance. + * PCI device description will be filled in the PCI enumerator, see + * `pciinv_walk()` function. + * For each first message in a session, the Reset flag is turned on, + * this indicates that the list of existing PCI devices must be + * cleaned. */ + struct pciinv_message pack = { + .iana = { + PCIINV_OEM_IANA & 0xff, + (PCIINV_OEM_IANA >> 8) & 0xff, + (PCIINV_OEM_IANA >> 16) & 0xff + }, + .reset = 1 + }; + + check_all_slot_table(); + + /* Send PCI device list to the BMC */ + prlog(PR_INFO, "Send PCI device list\n"); + for_each_phb(phb) { + pci_walk_dev(phb, NULL, &pciinv_walk, &pack); + } +} + +static bool vesnin_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "YADRO,vesnin")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + slot_table_init(vesnin_phb_table); + + return true; +} + +DECLARE_PLATFORM(vesnin) = { + .name = "vesnin", + .bmc = &bmc_plat_ast2400_ami, + .probe = vesnin_probe, + .init = astbmc_init, + .pci_get_slot_info = slot_table_get_slot_info, + .pci_probe_complete = vesnin_pci_probe_complete, + .external_irq = astbmc_ext_irq_serirq_cpld, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .exit = ipmi_wdt_final_reset, + .terminate = ipmi_terminate, + .op_display = op_display_lpc, +}; diff --git a/roms/skiboot/platforms/astbmc/witherspoon.c b/roms/skiboot/platforms/astbmc/witherspoon.c new file mode 100644 index 000000000..67c24b532 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/witherspoon.c @@ -0,0 +1,604 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2017-2019 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> +#include <psi.h> +#include <npu-regs.h> +#include <xscom.h> +#include <xscom-p9-regs.h> +#include <timebase.h> +#include <pci.h> +#include <pci-slot.h> +#include <phb4.h> +#include <npu2.h> +#include <occ.h> +#include <i2c.h> +#include <secvar.h> + +#include "astbmc.h" +#include "ast.h" + +static enum { + WITHERSPOON_TYPE_UNKNOWN, + WITHERSPOON_TYPE_SEQUOIA, + WITHERSPOON_TYPE_REDBUD +} witherspoon_type; + +/* + * HACK: Hostboot doesn't export the correct data for the system VPD EEPROM + * for this system. So we need to work around it here. + */ +static void vpd_dt_fixup(void) +{ + struct dt_node *n = dt_find_by_path(dt_root, + "/xscom@603fc00000000/i2cm@a2000/i2c-bus@0/eeprom@50"); + + if (n) { + dt_check_del_prop(n, "compatible"); + dt_add_property_string(n, "compatible", "atmel,24c512"); + + dt_check_del_prop(n, "label"); + dt_add_property_string(n, "label", "system-vpd"); + } +} + +static void witherspoon_create_ocapi_i2c_bus(void) +{ + struct dt_node *xscom, *i2cm, *i2c_bus; + prlog(PR_DEBUG, "OCAPI: Adding I2C bus device node for OCAPI reset\n"); + dt_for_each_compatible(dt_root, xscom, "ibm,xscom") { + i2cm = dt_find_by_name(xscom, "i2cm@a1000"); + if (!i2cm) { + prlog(PR_ERR, "OCAPI: Failed to add I2C bus device node\n"); + continue; + } + + if (dt_find_by_name(i2cm, "i2c-bus@4")) + continue; + + i2c_bus = dt_new_addr(i2cm, "i2c-bus", 4); + dt_add_property_cells(i2c_bus, "reg", 4); + dt_add_property_cells(i2c_bus, "bus-frequency", 0x61a80); + dt_add_property_strings(i2c_bus, "compatible", + "ibm,opal-i2c", "ibm,power8-i2c-port", + "ibm,power9-i2c-port"); + } +} + +static bool witherspoon_probe(void) +{ + struct dt_node *np; + int highest_gpu_group_id = 0; + int gpu_group_id; + + if (!dt_node_is_compatible(dt_root, "ibm,witherspoon")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Setup UART for use by OPAL (Linux hvc) */ + uart_set_console_policy(UART_CONSOLE_OPAL); + + vpd_dt_fixup(); + + witherspoon_create_ocapi_i2c_bus(); + + dt_for_each_compatible(dt_root, np, "ibm,npu-link") { + gpu_group_id = dt_prop_get_u32(np, "ibm,npu-group-id"); + if (gpu_group_id > highest_gpu_group_id) + highest_gpu_group_id = gpu_group_id; + }; + + switch (highest_gpu_group_id) { + case 1: + witherspoon_type = WITHERSPOON_TYPE_REDBUD; + break; + case 2: + witherspoon_type = WITHERSPOON_TYPE_SEQUOIA; + break; + default: + witherspoon_type = WITHERSPOON_TYPE_UNKNOWN; + prlog(PR_NOTICE, "PLAT: Unknown Witherspoon variant detected\n"); + } + + return true; +} + +static void phb4_activate_shared_slot_witherspoon(struct proc_chip *chip) +{ + uint64_t val; + + /* + * Shared slot activation is done by raising a GPIO line on the + * chip with the secondary slot. It will somehow activate the + * sideband signals between the slots. + * Need to wait 100us for stability. + */ + xscom_read(chip->id, P9_GPIO_DATA_OUT_ENABLE, &val); + val |= PPC_BIT(2); + xscom_write(chip->id, P9_GPIO_DATA_OUT_ENABLE, val); + + xscom_read(chip->id, P9_GPIO_DATA_OUT, &val); + val |= PPC_BIT(2); + xscom_write(chip->id, P9_GPIO_DATA_OUT, val); + time_wait_us(100); + prlog(PR_INFO, "Shared PCI slot activated\n"); +} + +static void witherspoon_shared_slot_fixup(void) +{ + struct pci_slot *slot0, *slot1; + struct proc_chip *chip0, *chip1; + uint8_t p0 = 0, p1 = 0; + + /* + * Detect if a x16 card is present on the shared slot and + * do some extra configuration if it is. + * + * The shared slot, a.k.a "Slot 2" in the documentation, is + * connected to PEC2 phb index 3 on both chips. From skiboot, + * it looks like two x8 slots, each with its own presence bit. + * + * Here is the matrix of possibilities for the presence bits: + * + * slot0 presence slot1 presence + * 0 0 => no card + * 1 0 => x8 or less card detected + * 1 1 => x16 card detected + * 0 1 => invalid combination + * + * We only act if a x16 card is detected ('1 1' combination above). + * + * One issue is that we don't really know if it is a + * shared-slot-compatible card (such as Mellanox CX5) or + * a 'normal' x16 PCI card. We activate the shared slot in both cases, + * as it doesn't seem to hurt. + * + * If the card is a normal x16 PCI card, the link won't train on the + * second slot (nothing to do with the shared slot activation), the + * procedure will timeout, thus adding some delay to the boot time. + * Therefore the recommendation is that we shouldn't use a normal + * x16 card on the shared slot of a witherspoon. + * + * Plugging a x8 or less adapter on the shared slot should work + * like any other physical slot. + */ + chip0 = next_chip(NULL); + chip1 = next_chip(chip0); + if (!chip1 || next_chip(chip1)) { + prlog(PR_WARNING, + "PLAT: Can't find second chip, " + "skipping PCIe shared slot detection\n"); + return; + } + + /* the shared slot is connected to PHB3 on both chips */ + slot0 = pci_slot_find(phb4_get_opal_id(chip0->id, 3)); + slot1 = pci_slot_find(phb4_get_opal_id(chip1->id, 3)); + if (slot0 && slot1) { + if (slot0->ops.get_presence_state) + slot0->ops.get_presence_state(slot0, &p0); + if (slot1->ops.get_presence_state) + slot1->ops.get_presence_state(slot1, &p1); + if (p0 == 1 && p1 == 1) { + phb4_activate_shared_slot_witherspoon(chip1); + slot0->peer_slot = slot1; + slot1->peer_slot = slot0; + } + } +} + +static int check_mlx_cards(struct phb *phb __unused, struct pci_device *dev, + void *userdata __unused) +{ + uint16_t mlx_cards[] = { + 0x1017, /* ConnectX-5 */ + 0x1019, /* ConnectX-5 Ex */ + 0x101b, /* ConnectX-6 */ + 0x101d, /* ConnectX-6 Dx */ + 0x101f, /* ConnectX-6 Lx */ + 0x1021, /* ConnectX-7 */ + }; + + if (PCI_VENDOR_ID(dev->vdid) == 0x15b3) { /* Mellanox */ + for (int i = 0; i < ARRAY_SIZE(mlx_cards); i++) { + if (mlx_cards[i] == PCI_DEVICE_ID(dev->vdid)) + return 1; + } + } + return 0; +} + +static void witherspoon_pci_probe_complete(void) +{ + struct pci_device *dev; + struct phb *phb; + struct phb4 *p; + + /* + * Reallocate dma engines between stacks in PEC2 if a Mellanox + * card is found on the shared slot, as it is required to get + * good GPU direct performance. + */ + for_each_phb(phb) { + /* skip the virtual PHBs */ + if (phb->phb_type != phb_type_pcie_v4) + continue; + p = phb_to_phb4(phb); + /* Keep only the first PHB on PEC2 */ + if (p->index != 3) + continue; + dev = pci_walk_dev(phb, NULL, check_mlx_cards, NULL); + if (dev) + phb4_pec2_dma_engine_realloc(p); + } +} + +static void set_link_details(struct npu2 *npu, uint32_t link_index, + uint32_t brick_index, enum npu2_dev_type type) +{ + struct npu2_dev *dev = NULL; + for (int i = 0; i < npu->total_devices; i++) { + if (npu->devices[i].link_index == link_index) { + dev = &npu->devices[i]; + break; + } + } + if (!dev) { + prlog(PR_ERR, "PLAT: Could not find NPU link index %d\n", + link_index); + return; + } + dev->brick_index = brick_index; + dev->type = type; +} + +static void witherspoon_npu2_device_detect(struct npu2 *npu) +{ + struct proc_chip *chip; + uint8_t state; + uint64_t i2c_port_id = 0; + char port_name[17]; + struct dt_node *dn; + int rc; + + bool gpu0_present, gpu1_present; + + if (witherspoon_type != WITHERSPOON_TYPE_REDBUD) { + prlog(PR_DEBUG, "PLAT: Setting all NPU links to NVLink, OpenCAPI only supported on Redbud\n"); + for (int i = 0; i < npu->total_devices; i++) { + npu->devices[i].type = NPU2_DEV_TYPE_NVLINK; + } + return; + } + assert(npu->total_devices == 6); + + chip = get_chip(npu->chip_id); + + /* Find I2C port */ + snprintf(port_name, sizeof(port_name), "p8_%08x_e%dp%d", + chip->id, platform.ocapi->i2c_engine, + platform.ocapi->i2c_port); + dt_for_each_compatible(dt_root, dn, "ibm,power9-i2c-port") { + if (streq(port_name, dt_prop_get(dn, "ibm,port-name"))) { + i2c_port_id = dt_prop_get_u32(dn, "ibm,opal-id"); + break; + } + } + + if (!i2c_port_id) { + prlog(PR_ERR, "PLAT: Could not find NPU presence I2C port\n"); + return; + } + + gpu0_present = occ_get_gpu_presence(chip, 0); + if (gpu0_present) { + prlog(PR_DEBUG, "PLAT: Chip %d GPU#0 slot present\n", chip->id); + } + + gpu1_present = occ_get_gpu_presence(chip, 1); + if (gpu1_present) { + prlog(PR_DEBUG, "PLAT: Chip %d GPU#1 slot present\n", chip->id); + } + + /* + * The following I2C ops generate errors if no device is + * present on any SXM2 slot. Since it's useless, let's skip it + */ + if (!gpu0_present && !gpu1_present) + return; + + /* Set pins to input */ + state = 0xff; + rc = i2c_request_send(i2c_port_id, + platform.ocapi->i2c_presence_addr, SMBUS_WRITE, 3, + 1, &state, 1, 120); + if (rc) + goto i2c_failed; + + /* Read the presence value */ + state = 0x00; + rc = i2c_request_send(i2c_port_id, + platform.ocapi->i2c_presence_addr, SMBUS_READ, 0, + 1, &state, 1, 120); + if (rc) + goto i2c_failed; + + if (gpu0_present) { + if (state & (1 << 0)) { + prlog(PR_DEBUG, "PLAT: Chip %d GPU#0 is OpenCAPI\n", + chip->id); + /* + * On witherspoon, bricks 2 and 3 are connected to + * the lanes matching links 0 and 1 in OpenCAPI mode. + */ + set_link_details(npu, 1, 3, NPU2_DEV_TYPE_OPENCAPI); + /* We current don't support using the second link */ + set_link_details(npu, 0, 2, NPU2_DEV_TYPE_UNKNOWN); + } else { + prlog(PR_DEBUG, "PLAT: Chip %d GPU#0 is NVLink\n", + chip->id); + set_link_details(npu, 0, 0, NPU2_DEV_TYPE_NVLINK); + set_link_details(npu, 1, 1, NPU2_DEV_TYPE_NVLINK); + set_link_details(npu, 2, 2, NPU2_DEV_TYPE_NVLINK); + } + } + + if (gpu1_present) { + if (state & (1 << 1)) { + prlog(PR_DEBUG, "PLAT: Chip %d GPU#1 is OpenCAPI\n", + chip->id); + set_link_details(npu, 4, 4, NPU2_DEV_TYPE_OPENCAPI); + /* We current don't support using the second link */ + set_link_details(npu, 5, 5, NPU2_DEV_TYPE_UNKNOWN); + } else { + prlog(PR_DEBUG, "PLAT: Chip %d GPU#1 is NVLink\n", + chip->id); + set_link_details(npu, 3, 3, NPU2_DEV_TYPE_NVLINK); + set_link_details(npu, 4, 4, NPU2_DEV_TYPE_NVLINK); + set_link_details(npu, 5, 5, NPU2_DEV_TYPE_NVLINK); + } + } + + return; + +i2c_failed: + prlog(PR_ERR, "PLAT: NPU device type detection failed, rc=%d\n", rc); + return; +} + +static const char *witherspoon_ocapi_slot_label(uint32_t chip_id, + uint32_t brick_index) +{ + const char *name = NULL; + + if (chip_id == 0) { + if (brick_index == 3) + name = "OPENCAPI-GPU0"; + else if (brick_index == 4) + name = "OPENCAPI-GPU1"; + } else { + if (brick_index == 3) + name = "OPENCAPI-GPU3"; + else if (brick_index == 4) + name = "OPENCAPI-GPU4"; + } + return name; +} + +static const struct platform_ocapi witherspoon_ocapi = { + .i2c_engine = 1, + .i2c_port = 4, + .odl_phy_swap = false, + .i2c_reset_addr = 0x20, + /* + * Witherspoon uses SXM2 connectors, carrying 2 OCAPI links + * over a single connector - hence each pair of bricks shares + * the same pin for resets. We currently only support using + * bricks 3 and 4, among other reasons because we can't handle + * a reset on one link causing the other link to reset as + * well. + */ + .i2c_reset_brick2 = 1 << 0, + .i2c_reset_brick3 = 1 << 0, + .i2c_reset_brick4 = 1 << 1, + .i2c_reset_brick5 = 1 << 1, + .i2c_presence_addr = 0x20, + /* unused, we do this in custom presence detect */ + .i2c_presence_brick2 = 0, + .i2c_presence_brick3 = 0, + .i2c_presence_brick4 = 0, + .i2c_presence_brick5 = 0, + .ocapi_slot_label = witherspoon_ocapi_slot_label, +}; + +static int gpu_slot_to_num(const char *slot) +{ + char *p = NULL; + int ret; + + if (!slot) + return -1; + + if (memcmp(slot, "GPU", 3)) + return -1; + + ret = strtol(slot + 3, &p, 10); + if (*p || p == slot + 3) + return -1; + + return ret; +} + +static void npu2_phb_nvlink_dt(struct phb *npuphb) +{ + struct dt_node *g[3] = { NULL }; /* Current maximum 3 GPUs per 1 NPU */ + struct dt_node *n[6] = { NULL }; + int max_gpus, i, gpuid, first, last; + struct npu2 *npu2_phb = phb_to_npu2_nvlink(npuphb); + struct pci_device *npd; + + switch (witherspoon_type) { + case WITHERSPOON_TYPE_REDBUD: + max_gpus = 4; + break; + case WITHERSPOON_TYPE_SEQUOIA: + max_gpus = 6; + break; + default: + /* witherspoon_probe() already reported missing support */ + return; + } + + /* Find the indexes of GPUs connected to this NPU */ + for (i = 0, first = max_gpus, last = 0; i < npu2_phb->total_devices; + ++i) { + gpuid = gpu_slot_to_num(npu2_phb->devices[i].nvlink.slot_label); + if (gpuid < 0) + continue; + if (gpuid > last) + last = gpuid; + if (gpuid < first) + first = gpuid; + } + + /* Either no "GPUx" slots found or they are not consecutive, abort */ + if (!last || last + 1 - first > max_gpus) + return; + + /* Collect GPU device nodes, sorted by an index from "GPUn" */ + for (i = 0; i < npu2_phb->total_devices; ++i) { + gpuid = gpu_slot_to_num(npu2_phb->devices[i].nvlink.slot_label); + g[gpuid - first] = npu2_phb->devices[i].nvlink.pd->dn; + + /* Collect NVLink bridge nodes too, for their phandles */ + list_for_each(&npuphb->devices, npd, link) { + if (npd->bdfn == npu2_phb->devices[i].bdfn) { + assert(npu2_phb->devices[i].brick_index < + ARRAY_SIZE(n)); + n[npu2_phb->devices[i].brick_index] = npd->dn; + } + } + } + + /* + * Store interconnect phandles in the device tree. + * The mapping is from Witherspoon_Design_Workbook_v1.7_19June2018.pdf, + * pages 39 (Sequoia), 40 (Redbud): + * Figure 16: NVLink wiring diagram for planar with 6 GPUs + * Figure 17: NVLink wiring diagram for planar with 4 GPUs + */ +#define PEERPH(g) ((g)?(g)->phandle:0) + switch (witherspoon_type) { + case WITHERSPOON_TYPE_REDBUD: + if (g[0]) + dt_add_property_cells(g[0], "ibm,nvlink-peers", + PEERPH(g[1]), PEERPH(n[0]), + PEERPH(g[1]), PEERPH(n[1]), + PEERPH(g[1]), PEERPH(n[2])); + if (g[1]) + dt_add_property_cells(g[1], "ibm,nvlink-peers", + PEERPH(g[0]), PEERPH(n[3]), + PEERPH(g[0]), PEERPH(n[4]), + PEERPH(g[0]), PEERPH(n[5])); + break; + case WITHERSPOON_TYPE_SEQUOIA: + if (g[0]) + dt_add_property_cells(g[0], "ibm,nvlink-peers", + PEERPH(g[1]), PEERPH(n[0]), + PEERPH(g[2]), PEERPH(g[2]), + PEERPH(g[1]), PEERPH(n[1])); + if (g[1]) + dt_add_property_cells(g[1], "ibm,nvlink-peers", + PEERPH(g[0]), PEERPH(n[2]), + PEERPH(g[2]), PEERPH(g[2]), + PEERPH(g[0]), PEERPH(n[3])); + if (g[2]) + dt_add_property_cells(g[2], "ibm,nvlink-peers", + PEERPH(g[1]), PEERPH(g[0]), + PEERPH(g[1]), PEERPH(n[4]), + PEERPH(g[0]), PEERPH(n[5])); + break; + default: + break; + } +} + +static void witherspoon_finalise_dt(bool is_reboot) +{ + struct dt_node *np; + struct proc_chip *c; + + if (is_reboot) + return; + + dt_for_each_compatible(dt_root, np, "ibm,power9-npu-pciex") { + u32 opal_id = dt_prop_get_cell(np, "ibm,opal-phbid", 1); + struct phb *npphb = pci_get_phb(opal_id); + + if (!npphb) + continue; + if (npphb->phb_type != phb_type_npu_v2) + continue; + npu2_phb_nvlink_dt(npphb); + } + + /* + * The I2C bus on used to talk to the GPUs has a 750K pullup + * which is way too big. If there's no GPUs connected to the + * chip all I2C transactions fail with an Arb loss error since + * SCL/SDA don't return to the idle state fast enough. Disable + * the port to squash the errors. + */ + for (c = next_chip(NULL); c; c = next_chip(c)) { + bool detected = false; + int i; + + np = dt_find_by_path(c->devnode, "i2cm@a1000/i2c-bus@4"); + if (!np) + continue; + + for (i = 0; i < 3; i++) + detected |= occ_get_gpu_presence(c, i); + + if (!detected) { + dt_check_del_prop(np, "status"); + dt_add_property_string(np, "status", "disabled"); + } + } +} + +static int witherspoon_secvar_init(void) +{ + return secvar_main(secboot_tpm_driver, edk2_compatible_v1); +} + +/* The only difference between these is the PCI slot handling */ + +DECLARE_PLATFORM(witherspoon) = { + .name = "Witherspoon", + .probe = witherspoon_probe, + .init = astbmc_init, + .pre_pci_fixup = witherspoon_shared_slot_fixup, + .pci_probe_complete = witherspoon_pci_probe_complete, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .bmc = &bmc_plat_ast2500_openbmc, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .finalise_dt = witherspoon_finalise_dt, + .exit = astbmc_exit, + .terminate = ipmi_terminate, + + .pci_get_slot_info = dt_slot_get_slot_info, + .ocapi = &witherspoon_ocapi, + .npu2_device_detect = witherspoon_npu2_device_detect, + .op_display = op_display_lpc, + .secvar_init = witherspoon_secvar_init, +}; diff --git a/roms/skiboot/platforms/astbmc/zaius.c b/roms/skiboot/platforms/astbmc/zaius.c new file mode 100644 index 000000000..f3807a007 --- /dev/null +++ b/roms/skiboot/platforms/astbmc/zaius.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2017-2019 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <ipmi.h> +#include <psi.h> +#include <npu-regs.h> +#include <npu2.h> +#include <pci.h> +#include <pci-cfg.h> + +#include "astbmc.h" + +/* backplane slots */ +static const struct slot_table_entry hdd_bay_slots[] = { + SW_PLUGGABLE("hdd0", 0xe), + SW_PLUGGABLE("hdd1", 0x4), + SW_PLUGGABLE("hdd2", 0x5), + SW_PLUGGABLE("hdd3", 0x6), + SW_PLUGGABLE("hdd4", 0x7), + SW_PLUGGABLE("hdd5", 0xf), + SW_PLUGGABLE("hdd6", 0xc), + SW_PLUGGABLE("hdd7", 0xd), + SW_PLUGGABLE("hdd8", 0x14), + SW_PLUGGABLE("hdd9", 0x17), + SW_PLUGGABLE("hdd10", 0x8), + SW_PLUGGABLE("hdd11", 0xb), + SW_PLUGGABLE("hdd12", 0x10), + SW_PLUGGABLE("hdd13", 0x13), + SW_PLUGGABLE("hdd14", 0x16), + SW_PLUGGABLE("hdd15", 0x09), + SW_PLUGGABLE("hdd16", 0xa), + SW_PLUGGABLE("hdd17", 0x11), + SW_PLUGGABLE("hdd18", 0x12), + SW_PLUGGABLE("hdd19", 0x15), + + { .etype = st_end }, +}; + +static void zaius_get_slot_info(struct phb *phb, struct pci_device *pd) +{ + const struct slot_table_entry *ent = NULL; + + if (!pd || pd->slot) + return; + + /* + * If we find a 9797 switch then assume it's the HDD Rack. This might + * break if we have another 9797 in the system for some reason. This is + * a really dumb hack, but until we get query the BMC about whether we + * have a HDD rack or not we don't have much of a choice. + */ + if (pd->dev_type == PCIE_TYPE_SWITCH_DNPORT && pd->vdid == 0x979710b5) + for (ent = hdd_bay_slots; ent->etype != st_end; ent++) + if (ent->location == (pd->bdfn & 0xff)) + break; + if (ent) + slot_table_add_slot_info(pd, ent); + else + slot_table_get_slot_info(phb, pd); +} + +static const struct platform_ocapi zaius_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, +}; + +ST_PLUGGABLE(pe0_slot, "PE0"); +ST_PLUGGABLE(pe1_slot, "PE1"); +ST_PLUGGABLE(pe2_slot, "PE2"); +ST_PLUGGABLE(pe3_slot, "PE3"); +ST_PLUGGABLE(pe4_slot, "PE4"); +ST_PLUGGABLE(mezz_slot_a, "MEZZ A"); +ST_PLUGGABLE(mezz_slot_b, "MEZZ B"); + +static const struct slot_table_entry zaius_phb_table[] = { + ST_PHB_ENTRY(0, 0, pe1_slot), /* PE1 is on PHB0 */ + ST_PHB_ENTRY(0, 1, pe0_slot), /* PE0 is on PHB1 */ +/* ST_PHB_ENTRY(0, 2, builtin_sata), */ + ST_PHB_ENTRY(0, 3, pe2_slot), /* un-bifurcated 16x */ + + ST_PHB_ENTRY(8, 0, pe3_slot), + ST_PHB_ENTRY(8, 1, pe4_slot), +/* ST_PHB_ENTRY(8, 2, builtin_usb), */ + + /* + * The MEZZ slot is kind of weird. Conceptually it's a 16x slot, but + * physically it's two separate 8x slots (MEZZ A and B) which can be + * used as a 16x slot if the PHB is un-bifurcated. The BMC detects what + * to do based on the the presence detect bits of the MEZZ slots to + * configure the correct bifurcation at IPL time. + * + * There's some additional weirdness too since MEZZ B can be used to + * access the built-in BCM5719 and the BMC PCIe interface via a special + * module that bridges MEZZ B to an adjacent connector. + * + * We should probably detect the bifurcation setting and set the slot + * names appropriately, but this will do for now. + */ + ST_PHB_ENTRY(8, 3, mezz_slot_a), + ST_PHB_ENTRY(8, 4, mezz_slot_b), +/* ST_PHB_ENTRY(8, 5, builtin_bmc), */ + + { .etype = st_end }, +}; + +#define NPU_BASE 0x5011000 +#define NPU_SIZE 0x2c +#define NPU_INDIRECT0 0x8000000009010c3fUL /* OB0 - no OB3 on Zaius */ + +/* OpenCAPI only */ +static void create_link(struct dt_node *npu, int group, int index) +{ + struct dt_node *link; + uint32_t lane_mask; + char namebuf[32]; + + snprintf(namebuf, sizeof(namebuf), "link@%x", index); + link = dt_new(npu, namebuf); + + dt_add_property_string(link, "compatible", "ibm,npu-link"); + dt_add_property_cells(link, "ibm,npu-link-index", index); + + 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); + } + + 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); +} + +/* FIXME: Get rid of this after we get NPU information properly via HDAT/MRW */ +static void zaius_create_npu(void) +{ + struct dt_node *xscom, *npu; + int npu_index = 0; + char namebuf[32]; + + /* Abort if there's already an NPU in the device tree */ + if (dt_find_compatible_node(dt_root, NULL, "ibm,power9-npu")) + return; + + prlog(PR_DEBUG, "OCAPI: Adding NPU device nodes\n"); + dt_for_each_compatible(dt_root, xscom, "ibm,xscom") { + snprintf(namebuf, sizeof(namebuf), "npu@%x", NPU_BASE); + npu = dt_new(xscom, namebuf); + 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); + } +} + +/* FIXME: Get rid of this after we get NPU information properly via HDAT/MRW */ +static void zaius_create_ocapi_i2c_bus(void) +{ + struct dt_node *xscom, *i2cm, *i2c_bus; + prlog(PR_DEBUG, "OCAPI: Adding I2C bus device node for OCAPI reset\n"); + dt_for_each_compatible(dt_root, xscom, "ibm,xscom") { + i2cm = dt_find_by_name(xscom, "i2cm@a1000"); + if (!i2cm) { + prlog(PR_ERR, "OCAPI: Failed to add I2C bus device node\n"); + continue; + } + + if (dt_find_by_name(i2cm, "i2c-bus@4")) + continue; + + i2c_bus = dt_new_addr(i2cm, "i2c-bus", 4); + dt_add_property_cells(i2c_bus, "reg", 4); + dt_add_property_cells(i2c_bus, "bus-frequency", 0x61a80); + dt_add_property_strings(i2c_bus, "compatible", + "ibm,opal-i2c", "ibm,power8-i2c-port", + "ibm,power9-i2c-port"); + } +} + +static bool zaius_probe(void) +{ + if (!dt_node_is_compatible(dt_root, "ingrasys,zaius")) + return false; + + /* Lot of common early inits here */ + astbmc_early_init(); + + /* Setup UART for direct use by Linux */ + uart_set_console_policy(UART_CONSOLE_OS); + + zaius_create_npu(); + zaius_create_ocapi_i2c_bus(); + + slot_table_init(zaius_phb_table); + + return true; +} + +/* Extracted from zaius1-bmc */ +static const struct bmc_hw_config bmc_hw_zaius = { + .scu_revision_id = 0x04030303, + .mcr_configuration = 0x11000FD7, + .mcr_scu_mpll = 0x000071C1, + .mcr_scu_strap = 0x00000000, +}; + +static const struct bmc_sw_config bmc_sw_openbmc = { + .ipmi_oem_partial_add_esel = IPMI_CODE(0x3a, 0xf0), + .ipmi_oem_hiomap_cmd = IPMI_CODE(0x3a, 0x5a), +}; + +static const struct bmc_platform bmc_zaius_openbmc = { + .name = "zaius:openbmc", + .hw = &bmc_hw_zaius, + .sw = &bmc_sw_openbmc, +}; + +DECLARE_PLATFORM(zaius) = { + .name = "Zaius", + .probe = zaius_probe, + .init = astbmc_init, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .bmc = &bmc_zaius_openbmc, + .pci_get_slot_info = zaius_get_slot_info, + .pci_probe_complete = check_all_slot_table, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, + .exit = ipmi_wdt_final_reset, + .terminate = ipmi_terminate, + .ocapi = &zaius_ocapi, + .npu2_device_detect = npu2_i2c_presence_detect, + .op_display = op_display_lpc, +}; 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, +}; diff --git a/roms/skiboot/platforms/mambo/Makefile.inc b/roms/skiboot/platforms/mambo/Makefile.inc new file mode 100644 index 000000000..b0e6b0c46 --- /dev/null +++ b/roms/skiboot/platforms/mambo/Makefile.inc @@ -0,0 +1,6 @@ +SUBDIRS += $(PLATDIR)/mambo + +MAMBO_OBJS = mambo.o console.o +MAMBO = $(PLATDIR)/mambo/built-in.a +$(MAMBO): $(MAMBO_OBJS:%=$(PLATDIR)/mambo/%) + diff --git a/roms/skiboot/platforms/mambo/console.c b/roms/skiboot/platforms/mambo/console.c new file mode 100644 index 000000000..6d5b20b56 --- /dev/null +++ b/roms/skiboot/platforms/mambo/console.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2016-2017 IBM Corp. */ + +#include <skiboot.h> +#include <console.h> + +#include "mambo.h" + +/* + * The SIM_READ_CONSOLE callout will return -1 if there is no character to read. + * There's no explicit poll callout so we "poll" by doing a read and stashing + * the result until we do an actual read. + */ +static int mambo_char = -1; + +static bool mambo_console_poll(void) +{ + if (mambo_char < 0) + mambo_char = callthru0(SIM_READ_CONSOLE_CODE); + + return mambo_char >= 0; +} + +static size_t mambo_console_read(char *buf, size_t len) +{ + size_t count = 0; + + while (count < len) { + if (!mambo_console_poll()) + break; + + buf[count++] = mambo_char; + mambo_char = -1; + } + + return count; +} + +size_t mambo_console_write(const char *buf, size_t len) +{ + callthru2(SIM_WRITE_CONSOLE_CODE, (unsigned long)buf, len); + return len; +} + +static struct con_ops mambo_con_driver = { + .poll_read = mambo_console_poll, + .read = mambo_console_read, + .write = mambo_console_write, +}; + +void enable_mambo_console(void) +{ + prlog(PR_NOTICE, "Enabling Mambo console\n"); + set_console(&mambo_con_driver); +} + +/* + * mambo console based printf(), this is useful for debugging the console + * since mambo_console_write() can be safely called from anywhere. + * + * This is a debug hack and you shouldn't use it in real code. + */ +void mprintf(const char *fmt, ...) +{ + char buf[320]; + va_list args; + int i; + + va_start(args, fmt); + i = vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + mambo_console_write(buf, i); +} diff --git a/roms/skiboot/platforms/mambo/mambo.c b/roms/skiboot/platforms/mambo/mambo.c new file mode 100644 index 000000000..a1e0488c8 --- /dev/null +++ b/roms/skiboot/platforms/mambo/mambo.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2015-2017 IBM Corp. */ + +#include <skiboot.h> +#include <device.h> +#include <console.h> +#include <chip.h> +#include <cpu.h> +#include <opal-api.h> +#include <opal-internal.h> +#include <time-utils.h> +#include <time.h> + +#include "mambo.h" + +static bool mambo_probe(void) +{ + if (!dt_find_by_path(dt_root, "/mambo")) + return false; + + return true; +} + +#define BD_INFO_SYNC 0 +#define BD_INFO_STATUS 1 +#define BD_INFO_BLKSZ 2 +#define BD_INFO_DEVSZ 3 +#define BD_INFO_CHANGE 4 + +#define BD_SECT_SZ 512 + +static inline int callthru_disk_read(int id, void *buf, unsigned long sect, + unsigned long nrsect) +{ + return callthru3(SIM_BOGUS_DISK_READ, (unsigned long)buf, sect, + (nrsect << 16) | id); +} + +static inline int callthru_disk_write(int id, void *buf, unsigned long sect, + unsigned long nrsect) +{ + return callthru3(SIM_BOGUS_DISK_WRITE, (unsigned long)buf, sect, + (nrsect << 16) | id); +} + +static inline unsigned long callthru_disk_info(int op, int id) +{ + return callthru2(SIM_BOGUS_DISK_INFO, (unsigned long)op, + (unsigned long)id); +} + +extern unsigned long callthru_tcl(const char *str, int len); + +unsigned long callthru_tcl(const char *str, int len) +{ + prlog(PR_DEBUG, "Sending TCL to Mambo, cmd: %s\n", str); + return callthru2(SIM_CALL_TCL, (unsigned long)str, (unsigned long)len); +} + +struct bogus_disk_info { + unsigned long size; + int id; +}; + +static int bogus_disk_read(struct blocklevel_device *bl, uint64_t pos, void *buf, + uint64_t len) +{ + struct bogus_disk_info *bdi = bl->priv; + int rc, read_sectors = 0; + char b[BD_SECT_SZ]; + + if (len >= BD_SECT_SZ) { + rc = callthru_disk_read(bdi->id, buf, pos/BD_SECT_SZ, + len/BD_SECT_SZ); + if (rc) + return rc; + read_sectors = (len / BD_SECT_SZ); + } + + if ((len % BD_SECT_SZ) == 0) + return 0; + + /* + * Read any unaligned data into a temporaty buffer b, then copy + * to buf + */ + rc = callthru_disk_read(bdi->id, b, (pos/BD_SECT_SZ) + read_sectors, 1); + if (rc) + return rc; + memcpy(buf + (read_sectors * BD_SECT_SZ) , &b[pos % BD_SECT_SZ], + len - (read_sectors * BD_SECT_SZ)); + return rc; +} + +static int bogus_disk_write(struct blocklevel_device *bl, uint64_t pos, + const void *buf, uint64_t len) +{ + struct bogus_disk_info *bdi = bl->priv; + + if ((len % BD_SECT_SZ) != 0) + return OPAL_PARAMETER; + + return callthru_disk_write(bdi->id, (void *)buf, pos/BD_SECT_SZ, + len/BD_SECT_SZ); + +} + +static int bogus_disk_erase(struct blocklevel_device *bl __unused, + uint64_t pos __unused, uint64_t len __unused) +{ + return 0; /* NOP */ +} + +static int bogus_disk_get_info(struct blocklevel_device *bl, const char **name, + uint64_t *total_size, uint32_t *erase_granule) +{ + struct bogus_disk_info *bdi = bl->priv; + + if (total_size) + *total_size = bdi->size; + + if (erase_granule) + *erase_granule = BD_SECT_SZ; + + if (name) + *name = "mambobogus"; + + return 0; +} + +static void bogus_disk_flash_init(void) +{ + struct blocklevel_device *bl; + struct bogus_disk_info *bdi; + unsigned long id = 0, size; + int rc; + + if (!chip_quirk(QUIRK_MAMBO_CALLOUTS)) + return; + + while (1) { + + rc = callthru_disk_info(BD_INFO_STATUS, id); + if (rc < 0) + return; + + size = callthru_disk_info(BD_INFO_DEVSZ, id) * 1024; + prlog(PR_NOTICE, "mambo: Found bogus disk size: 0x%lx\n", size); + + bl = zalloc(sizeof(struct blocklevel_device)); + bdi = zalloc(sizeof(struct bogus_disk_info)); + if (!bl || !bdi) { + free(bl); + free(bdi); + prerror("mambo: Failed to start bogus disk, ENOMEM\n"); + return; + } + + bl->read = &bogus_disk_read; + bl->write = &bogus_disk_write; + bl->erase = &bogus_disk_erase; + bl->get_info = &bogus_disk_get_info; + bdi->id = id; + bdi->size = size; + bl->priv = bdi; + bl->erase_mask = BD_SECT_SZ - 1; + + rc = flash_register(bl); + if (rc) + prerror("mambo: Failed to register bogus disk: %li\n", + id); + id++; + } +} + +static int64_t time_delta = 0; + +static int64_t mambo_rtc_read(__be32 *ymd, __be64 *hmsm) +{ + int64_t mambo_time; + struct tm t; + time_t mt; + uint32_t __ymd; + uint64_t __hmsm; + + if (!ymd || !hmsm) + return OPAL_PARAMETER; + + mambo_time = callthru0(SIM_GET_TIME_CODE); + mt = mambo_time >> 32; + mt += time_delta; + gmtime_r(&mt, &t); + tm_to_datetime(&t, &__ymd, &__hmsm); + + *ymd = cpu_to_be32(__ymd); + *hmsm = cpu_to_be64(__hmsm); + + return OPAL_SUCCESS; +} + +static int64_t mambo_rtc_write(uint32_t ymd, uint64_t hmsm) +{ + int64_t mambo_time; + struct tm tm; + time_t mt, new_mt; + + mambo_time = callthru0(SIM_GET_TIME_CODE); + mt = mambo_time >> 32; + + datetime_to_tm(ymd, hmsm, &tm); + new_mt = mktime(&tm); + + time_delta = new_mt - mt; + + return OPAL_SUCCESS; +} + +static void mambo_rtc_init(void) +{ + struct dt_node *np = dt_new(opal_node, "rtc"); + dt_add_property_strings(np, "compatible", "ibm,opal-rtc"); + + opal_register(OPAL_RTC_READ, mambo_rtc_read, 2); + opal_register(OPAL_RTC_WRITE, mambo_rtc_write, 2); +} + +static void mambo_system_reset_cpu(struct cpu_thread *cpu) +{ + uint32_t core_id; + uint32_t thread_id; + char tcl_cmd[50]; + + core_id = pir_to_core_id(cpu->pir); + thread_id = pir_to_thread_id(cpu->pir); + + snprintf(tcl_cmd, sizeof(tcl_cmd), "mysim cpu %i:%i interrupt SystemReset", core_id, thread_id); + callthru_tcl(tcl_cmd, strlen(tcl_cmd)); +} + +#define SYS_RESET_ALL -1 +#define SYS_RESET_ALL_OTHERS -2 + +static int64_t mambo_signal_system_reset(int32_t cpu_nr) +{ + struct cpu_thread *cpu; + + if (cpu_nr < 0) { + if (cpu_nr < SYS_RESET_ALL_OTHERS) + return OPAL_PARAMETER; + + for_each_cpu(cpu) { + if (cpu == this_cpu()) + continue; + mambo_system_reset_cpu(cpu); + + } + if (cpu_nr == SYS_RESET_ALL) + mambo_system_reset_cpu(this_cpu()); + + return OPAL_SUCCESS; + + } else { + cpu = find_cpu_by_server(cpu_nr); + if (!cpu) + return OPAL_PARAMETER; + + mambo_system_reset_cpu(cpu); + return OPAL_SUCCESS; + } +} + +static void mambo_sreset_init(void) +{ + opal_register(OPAL_SIGNAL_SYSTEM_RESET, mambo_signal_system_reset, 1); +} + +static void mambo_platform_init(void) +{ + mambo_sreset_init(); + mambo_rtc_init(); + bogus_disk_flash_init(); +} + +static int64_t mambo_cec_power_down(uint64_t request __unused) +{ + if (chip_quirk(QUIRK_MAMBO_CALLOUTS)) + callthru0(SIM_EXIT_CODE); + + return OPAL_UNSUPPORTED; +} + +static void __attribute__((noreturn)) mambo_terminate(const char *msg __unused) +{ + if (chip_quirk(QUIRK_MAMBO_CALLOUTS)) + callthru0(SIM_EXIT_CODE); + + for (;;) ; +} + +static int mambo_heartbeat_time(void) +{ + /* + * Mambo is slow and has no console input interrupt, so faster + * polling is needed to ensure its responsiveness. + */ + return 100; +} + +DECLARE_PLATFORM(mambo) = { + .name = "Mambo", + .probe = mambo_probe, + .init = mambo_platform_init, + .cec_power_down = mambo_cec_power_down, + .terminate = mambo_terminate, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .heartbeat_time = mambo_heartbeat_time, + .nvram_info = fake_nvram_info, + .nvram_start_read = fake_nvram_start_read, + .nvram_write = fake_nvram_write, +}; diff --git a/roms/skiboot/platforms/mambo/mambo.h b/roms/skiboot/platforms/mambo/mambo.h new file mode 100644 index 000000000..0b3bb60c4 --- /dev/null +++ b/roms/skiboot/platforms/mambo/mambo.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2016 IBM Corp. */ + +#ifndef __MAMBO_H__ +#define __MAMBO_H__ + +static inline unsigned long callthru0(int command) +{ + register uint64_t c asm("r3") = command; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c)); + return c; +} + +static inline unsigned long callthru2(int command, unsigned long arg1, + unsigned long arg2) +{ + register unsigned long c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2)); + return c; +} + +static inline unsigned long callthru3(int command, unsigned long arg1, + unsigned long arg2, unsigned long arg3) +{ + register unsigned long c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + register unsigned long a3 asm("r6") = arg3; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2), + "r"(a3)); + return c; +} + +/* Mambo callthru commands */ +#define SIM_WRITE_CONSOLE_CODE 0 +#define SIM_EXIT_CODE 31 +#define SIM_READ_CONSOLE_CODE 60 +#define SIM_GET_TIME_CODE 70 +#define SIM_CALL_TCL 86 +#define SIM_BOGUS_DISK_READ 116 +#define SIM_BOGUS_DISK_WRITE 117 +#define SIM_BOGUS_DISK_INFO 118 + +#endif /* __MAMBO_H__ */ diff --git a/roms/skiboot/platforms/qemu/Makefile.inc b/roms/skiboot/platforms/qemu/Makefile.inc new file mode 100644 index 000000000..d4f988e7c --- /dev/null +++ b/roms/skiboot/platforms/qemu/Makefile.inc @@ -0,0 +1,6 @@ +SUBDIRS += $(PLATDIR)/qemu + +QEMU_OBJS = qemu.o +QEMU = $(PLATDIR)/qemu/built-in.a +$(QEMU): $(QEMU_OBJS:%=$(PLATDIR)/qemu/%) + diff --git a/roms/skiboot/platforms/qemu/qemu.c b/roms/skiboot/platforms/qemu/qemu.c new file mode 100644 index 000000000..96153e853 --- /dev/null +++ b/roms/skiboot/platforms/qemu/qemu.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 <console.h> +#include <device.h> +#include <ipmi.h> + +#include <platforms/astbmc/astbmc.h> + +static bool bt_device_present; + +ST_PLUGGABLE(qemu_slot0, "pcie.0"); +ST_PLUGGABLE(qemu_slot1, "pcie.1"); + +static const struct slot_table_entry qemu_sw_down[] = { + SW_PLUGGABLE("sw0_down0", 0x0), + SW_BUILTIN ("sw0_down1", 0x1), + SW_PLUGGABLE("sw0_down2", 0x2), + { .etype = st_end }, +}; +ST_BUILTIN_DEV(qemu_sw_up, "sw0_up", .children = qemu_sw_down); +ST_BUILTIN_DEV(qemu_sw_root, "pcie.2", .children = qemu_sw_up); + +ST_PLUGGABLE(qemu_slot3, "pcie.3"); +ST_PLUGGABLE(qemu_slot4, "pcie.4"); +ST_PLUGGABLE(qemu_slot5, "pcie.5"); + +static const struct slot_table_entry qemu_phb_table[] = { + ST_PHB_ENTRY(0, 0, qemu_slot0), + ST_PHB_ENTRY(0, 1, qemu_slot1), + ST_PHB_ENTRY(0, 2, qemu_sw_root), + ST_PHB_ENTRY(0, 3, qemu_slot3), + ST_PHB_ENTRY(0, 4, qemu_slot4), + ST_PHB_ENTRY(0, 5, qemu_slot5), + { .etype = st_end }, +}; + +static bool qemu_probe_common(const char *compat) +{ + struct dt_node *n; + + if (!dt_node_is_compatible(dt_root, compat)) + return false; + + astbmc_early_init(); + + /* check if the BT device was defined by QEMU */ + dt_for_each_compatible(dt_root, n, "bt") { + bt_device_present = true; + } + + slot_table_init(qemu_phb_table); + + return true; +} + +static bool qemu_probe(void) +{ + return qemu_probe_common("qemu,powernv"); +} + +static bool qemu_probe_powernv8(void) +{ + return qemu_probe_common("qemu,powernv8"); +} + +static bool qemu_probe_powernv9(void) +{ + return qemu_probe_common("qemu,powernv9"); +} + +static bool qemu_probe_powernv10(void) +{ + return qemu_probe_common("qemu,powernv10"); +} + +static void qemu_init(void) +{ + if (!bt_device_present) { + set_opal_console(&uart_opal_con); + } else { + astbmc_init(); + } +} + +DECLARE_PLATFORM(qemu) = { + .name = "QEMU", + .probe = qemu_probe, + .init = qemu_init, + .external_irq = astbmc_ext_irq_serirq_cpld, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .pci_get_slot_info = slot_table_get_slot_info, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .terminate = ipmi_terminate, +}; + +/* + * For a QEMU PowerNV machine using POWER8 CPUs (Palmetto) + */ +DECLARE_PLATFORM(qemu_powernv8) = { + .name = "QEMU POWER8", + .probe = qemu_probe_powernv8, + .bmc = &bmc_plat_ast2400_ami, + .init = qemu_init, + .external_irq = astbmc_ext_irq_serirq_cpld, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .pci_get_slot_info = slot_table_get_slot_info, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .exit = astbmc_exit, + .terminate = ipmi_terminate, +}; + +/* + * For a QEMU PowerNV machine using POWER9 CPUs (Witherspoon) + */ +DECLARE_PLATFORM(qemu_powernv9) = { + .name = "QEMU POWER9", + .probe = qemu_probe_powernv9, + .bmc = &bmc_plat_ast2500_openbmc, + .init = qemu_init, + .external_irq = astbmc_ext_irq_serirq_cpld, + .cec_power_down = astbmc_ipmi_power_down, + .pci_get_slot_info = slot_table_get_slot_info, + .cec_reboot = astbmc_ipmi_reboot, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .exit = astbmc_exit, + .terminate = ipmi_terminate, +}; + +/* + * For a QEMU PowerNV machine using POWER10 CPUs (Rainier) + */ +DECLARE_PLATFORM(qemu_powernv10) = { + .name = "QEMU POWER10", + .probe = qemu_probe_powernv10, + .bmc = &bmc_plat_ast2500_openbmc, + .init = qemu_init, + .external_irq = astbmc_ext_irq_serirq_cpld, + .cec_power_down = astbmc_ipmi_power_down, + .cec_reboot = astbmc_ipmi_reboot, + .pci_get_slot_info = slot_table_get_slot_info, + .start_preload_resource = flash_start_preload_resource, + .resource_loaded = flash_resource_loaded, + .exit = astbmc_exit, + .terminate = ipmi_terminate, +}; diff --git a/roms/skiboot/platforms/rhesus/Makefile.inc b/roms/skiboot/platforms/rhesus/Makefile.inc new file mode 100644 index 000000000..aa66f6aea --- /dev/null +++ b/roms/skiboot/platforms/rhesus/Makefile.inc @@ -0,0 +1,6 @@ +SUBDIRS += $(PLATDIR)/rhesus + +RHESUS_OBJS = rhesus.o gpio.o +RHESUS = $(PLATDIR)/rhesus/built-in.a +$(RHESUS): $(RHESUS_OBJS:%=$(PLATDIR)/rhesus/%) + diff --git a/roms/skiboot/platforms/rhesus/ec/config.h b/roms/skiboot/platforms/rhesus/ec/config.h new file mode 100644 index 000000000..df2ad5d46 --- /dev/null +++ b/roms/skiboot/platforms/rhesus/ec/config.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +/* Copyright 2013-2014 IBM Corp. */ + +/** + * @file config.H + * + * @brief Definitions for EC configuration values. + * + */ + +#ifndef __EC_CONFIG_H_ +#define __EC_CONFIG_H_ + +#include <stdint.h> + +#define EC_RTC_PORT_BASE (0x70) // RTC/CMOS LPC base address +#define EC_RTC_BLOCK_SIZE (512) // Size of addressable data in RTC +#define EC_RTC_CENTURY (1) // 1 if century format is enabled +#if EC_RTC_CENTURY +#define EC_RTC_BBRAM_OFFSET (0x33) // Offset of NV data (= size of calendar) +#else +#define EC_RTC_BBRAM_OFFSET (0x0E) // Offset of NV data (= size of calendar) +#endif // #if EC_RTC_CENTURY + +#define EC_RTCDD_READ_TRIES (2) // Times to try the RTC if updating +#define EC_RTCDD_RETRY_DELAY (300000) // Delay between RTC read retries in ns + // based on update time of 244 + 30.5 µs + +#define EC_GPIO_INDEX 0x200 +#define EC_GPIO_DATA 0x201 +#define EC_GPIO_NUM_PORTS 17 +#define EC_GPIO_PORT_SKIP 4 + +#define EC_GPIO_DATA_OFFSET 0x0 +#define EC_GPIO_DDR_OFFSET 0x1 +#define EC_GPIO_PIN_OFFSET 0x2 +#define EC_GPIO_PUP_OFFSET 0x3 + +typedef enum EcGpioPort { + EC_GPIO_PORT_A = 0, + EC_GPIO_PORT_B = 1, + EC_GPIO_PORT_C = 2, + EC_GPIO_PORT_D = 3, + EC_GPIO_PORT_E = 4, + EC_GPIO_PORT_F = 5, + EC_GPIO_PORT_G = 6, + EC_GPIO_PORT_H = 7, + // skip port I + EC_GPIO_PORT_J = 8, + EC_GPIO_PORT_K = 9, + EC_GPIO_PORT_L = 10, + EC_GPIO_PORT_M = 11, + EC_GPIO_PORT_N = 12, + // skip port O + EC_GPIO_PORT_P = 13, + EC_GPIO_PORT_Q = 14, + EC_GPIO_PORT_R = 15, + EC_GPIO_PORT_S = 16, +} EcGpioPort; + +#ifdef __cplusplus +extern "C" { +#endif +void ec_outb(uint16_t, uint8_t); +uint8_t ec_inb(uint16_t); +#ifdef __cplusplus +} +#endif + +#endif // __EC_CONFIG_H_ diff --git a/roms/skiboot/platforms/rhesus/ec/gpio.h b/roms/skiboot/platforms/rhesus/ec/gpio.h new file mode 100644 index 000000000..65d7d878a --- /dev/null +++ b/roms/skiboot/platforms/rhesus/ec/gpio.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +/* Copyright 2013-2014 Google Corp. */ + +/** + * @file gpio.h + * + * @brief Public interface of the EC GPIO device driver + * + */ + +#ifndef __EC_GPIO_H_ +#define __EC_GPIO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define EC_GPIO_INPUT 0 +#define EC_GPIO_OUTPUT 1 +#define EC_GPIO_PULLUP_DISABLE 0 +#define EC_GPIO_PULLUP_ENABLE 1 + +// Sets up a GPIO as output or input. +// Returns: <0 on error +int ec_gpio_setup(EcGpioPort port, uint8_t pin, + int is_output, int pullup_enable); + +// Reads the current value of an input GPIO. +// Returns: GPIO value (0,1) or <0 on error. +int ec_gpio_read(EcGpioPort port, uint8_t pin); + +// Sets the driving value of an output GPIO. Port should already be set +// to output mode. +// Returns: <0 on error +int ec_gpio_set(EcGpioPort port, uint8_t pin, int val); + +#ifdef __cplusplus +} +#endif + +#endif // __EC_GPIO_H_ diff --git a/roms/skiboot/platforms/rhesus/gpio.c b/roms/skiboot/platforms/rhesus/gpio.c new file mode 100644 index 000000000..2ac36303e --- /dev/null +++ b/roms/skiboot/platforms/rhesus/gpio.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +/* Copyright 2013-2014 Google Corp. */ + +#include <stdint.h> +#include "ec/config.h" +#include "ec/gpio.h" + +int ec_gpio_setup(EcGpioPort port, uint8_t pin, + int is_output, int pullup_enable) +{ + uint8_t ddr_reg; + if (pin > 7) { + return -1; + } + + /* Set data direction */ + ec_outb(EC_GPIO_INDEX, + port * EC_GPIO_PORT_SKIP + EC_GPIO_DDR_OFFSET); + ddr_reg = ec_inb(EC_GPIO_DATA); + if (is_output) { + ddr_reg |= (1 << pin); + } else { + ddr_reg &= ~(1 << pin); + } + ec_outb(EC_GPIO_DATA, ddr_reg); + + /* Set pullup enable for output GPOs */ + if (is_output) + { + uint8_t pup_reg; + ec_outb(EC_GPIO_INDEX, + port * EC_GPIO_PORT_SKIP + EC_GPIO_PUP_OFFSET); + pup_reg = ec_inb(EC_GPIO_DATA); + if (pullup_enable) { + pup_reg |= (1 << pin); + } else { + pup_reg &= ~(1 << pin); + } + ec_outb(EC_GPIO_DATA, pup_reg); + } + + return 0; +} + +int ec_gpio_read(EcGpioPort port, uint8_t pin) +{ + uint8_t pin_reg; + if (pin > 7) { + return -1; + } + + ec_outb(EC_GPIO_INDEX, + port * EC_GPIO_PORT_SKIP + EC_GPIO_PIN_OFFSET); + pin_reg = ec_inb(EC_GPIO_DATA); + return !!(pin_reg & (1 << pin)); +} + +int ec_gpio_set(EcGpioPort port, uint8_t pin, int val) +{ + uint8_t data_reg; + if (pin > 7) { + return -1; + } + + ec_outb(EC_GPIO_INDEX, + port * EC_GPIO_PORT_SKIP + EC_GPIO_DATA_OFFSET); + data_reg = ec_inb(EC_GPIO_DATA); + if (val) { + data_reg |= (1 << pin); + } else { + data_reg &= ~(1 << pin); + } + ec_outb(EC_GPIO_DATA, data_reg); + return 0; +} diff --git a/roms/skiboot/platforms/rhesus/rhesus.c b/roms/skiboot/platforms/rhesus/rhesus.c new file mode 100644 index 000000000..125f2c5ff --- /dev/null +++ b/roms/skiboot/platforms/rhesus/rhesus.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright 2013-2017 IBM Corp. + * Copyright 2014 Google Corp. + */ + +#include <skiboot.h> +#include <device.h> +#include <lpc.h> +#include <console.h> +#include <opal.h> +#include <interrupts.h> +#include <libflash/libflash.h> +#include <libflash/libffs.h> +#include <libflash/blocklevel.h> +#include <sfc-ctrl.h> +#include "ec/config.h" +#include "ec/gpio.h" + +/* + * EC GPIO mapping + */ +#define RHESUS_RST_UCD90160_N EC_GPIO_PORT_J, 3 +#define RHESUS_FM_PWR_CYCLE_N EC_GPIO_PORT_K, 2 +#define RHESUS_EN_PWR_ON_SEQ EC_GPIO_PORT_R, 1 +#define RHESUS_BOARD_REVISION0 EC_GPIO_PORT_F, 3 +#define RHESUS_BOARD_REVISION1 EC_GPIO_PORT_F, 2 +#define RHESUS_BOARD_REVISION2 EC_GPIO_PORT_E, 5 +#define RHESUS_BOARD_REVISION3 EC_GPIO_PORT_E, 4 +#define RHESUS_BOARD_REVISION4 EC_GPIO_PORT_E, 1 + + +/* + * IO accessors for the EC driver + */ +void ec_outb(uint16_t addr, uint8_t data) +{ + lpc_outb(data, addr); +} + +uint8_t ec_inb(uint16_t addr) +{ + return lpc_inb(addr); +} + +static int rhesus_board_revision(void) +{ + int revision = 0, ret = 0, i = 0; + + static const struct { + EcGpioPort port; + uint8_t pin; + } revision_gpios[] = { + { RHESUS_BOARD_REVISION0 }, + { RHESUS_BOARD_REVISION1 }, + { RHESUS_BOARD_REVISION2 }, + { RHESUS_BOARD_REVISION3 }, + { RHESUS_BOARD_REVISION4 }, + }; + for (i = 0; i < sizeof(revision_gpios) / sizeof(revision_gpios[0]); ++i) + { + ret = ec_gpio_read(revision_gpios[i].port, revision_gpios[i].pin); + if (ret < 0) + return ret; + revision <<= 1; revision |= ret; + } + + return revision; +} + +static int64_t rhesus_reboot(void) +{ + // TODO(rlippert): This should use EC_SYS_RST_N, but there is nothing to + // deassert that at the moment. + int ret = 0; + ret = ec_gpio_set(RHESUS_FM_PWR_CYCLE_N, 0); + if (ret < 0) { + return ret; + } + + ret = ec_gpio_setup(RHESUS_FM_PWR_CYCLE_N, + EC_GPIO_OUTPUT, + EC_GPIO_PULLUP_DISABLE); + if (ret < 0) { + return ret; + } + + return 0; +} + +static int64_t rhesus_power_down(uint64_t request __unused) +{ + int ret = 0; + ret = ec_gpio_set(RHESUS_EN_PWR_ON_SEQ, 0); + if (ret < 0) { + return ret; + } + + ret = ec_gpio_setup(RHESUS_EN_PWR_ON_SEQ, + EC_GPIO_OUTPUT, + EC_GPIO_PULLUP_DISABLE); + if (ret < 0) { + return ret; + } + + return 0; +} + +static int rhesus_pnor_init(void) +{ + struct spi_flash_ctrl *pnor_ctrl; + struct blocklevel_device *bl = NULL; + int rc; + + /* Open controller, flash and ffs */ + rc = sfc_open(&pnor_ctrl); + if (rc) { + prerror("PLAT: Failed to open PNOR flash controller\n"); + goto fail; + } + rc = flash_init(pnor_ctrl, &bl, NULL); + if (rc) { + prerror("PLAT: Failed to open init PNOR driver\n"); + goto fail; + } + + rc = flash_register(bl); + if (!rc) + return 0; + + fail: + if (bl) + flash_exit(bl); + if (pnor_ctrl) + sfc_close(pnor_ctrl); + + return rc; +} + +static void rhesus_init(void) +{ + /* Initialize PNOR/NVRAM */ + rhesus_pnor_init(); + + /* Setup UART for direct use by Linux */ + uart_set_console_policy(UART_CONSOLE_OS); +} + +static void rhesus_dt_fixup_uart(struct dt_node *lpc, bool has_irq) +{ + /* + * The official OF ISA/LPC binding is a bit odd, it prefixes + * the unit address for IO with "i". It uses 2 cells, the first + * one indicating IO vs. Memory space (along with bits to + * represent aliasing). + * + * We pickup that binding and add to it "2" as a indication + * of FW space. + * + * TODO: Probe the UART instead if the LPC bus allows for it + */ + struct dt_node *uart; + char namebuf[32]; +#define UART_IO_BASE 0x3f8 +#define UART_IO_COUNT 8 + + snprintf(namebuf, sizeof(namebuf), "serial@i%x", UART_IO_BASE); + uart = dt_new(lpc, namebuf); + + dt_add_property_cells(uart, "reg", + 1, /* IO space */ + UART_IO_BASE, UART_IO_COUNT); + dt_add_property_strings(uart, "compatible", + "ns16550", + "pnpPNP,501"); + dt_add_property_cells(uart, "clock-frequency", 1843200); + dt_add_property_cells(uart, "current-speed", 115200); + + /* + * This is needed by Linux for some obscure reasons, + * we'll eventually need to sanitize it but in the meantime + * let's make sure it's there + */ + dt_add_property_strings(uart, "device_type", "serial"); + + /* Expose the external interrupt if supported + */ + if (has_irq) { + uint32_t chip_id = dt_get_chip_id(lpc); + uint32_t irq = get_psi_interrupt(chip_id) + P8_IRQ_PSI_EXTERNAL; + dt_add_property_cells(uart, "interrupts", irq, 1); + dt_add_property_cells(uart, "interrupt-parent", + get_ics_phandle()); + } +} + +/* + * This adds the legacy RTC device to the device-tree + * for Linux to use + */ +static void rhesus_dt_fixup_rtc(struct dt_node *lpc) +{ + struct dt_node *rtc; + + /* + * Follows the structure expected by the kernel file + * arch/powerpc/sysdev/rtc_cmos_setup.c + */ + rtc = dt_new_addr(lpc, "rtc", EC_RTC_PORT_BASE); + assert(rtc); + dt_add_property_string(rtc, "compatible", "pnpPNP,b00"); + dt_add_property_cells(rtc, "reg", + 1, /* IO space */ + EC_RTC_PORT_BASE, + /* 1 index/data pair per 128 bytes */ + (EC_RTC_BLOCK_SIZE / 128) * 2); +} + +static void rhesus_dt_fixup(bool has_uart_irq) +{ + struct dt_node *n, *primary_lpc = NULL; + + /* Find the primary LPC bus */ + dt_for_each_compatible(dt_root, n, "ibm,power8-lpc") { + if (!primary_lpc || dt_has_node_property(n, "primary", NULL)) + primary_lpc = n; + if (dt_has_node_property(n, "#address-cells", NULL)) + break; + } + + if (!primary_lpc) + return; + + rhesus_dt_fixup_rtc(primary_lpc); + rhesus_dt_fixup_uart(primary_lpc, has_uart_irq); +} + +static bool rhesus_probe(void) +{ + const char *model; + int rev; + bool has_uart_irq = false; + + if (!dt_node_is_compatible(dt_root, "ibm,powernv")) + return false; + + model = dt_prop_get_def(dt_root, "model", NULL); + if (!model || !(strstr(model, "rhesus") || strstr(model, "RHESUS"))) + return false; + + /* Grab board version from EC */ + rev = rhesus_board_revision(); + if (rev >= 0) { + printf("Rhesus board rev %d\n", rev); + dt_add_property_cells(dt_root, "revision-id", rev); + } else + prerror("Rhesus board revision not found !\n"); + + /* Add missing bits of device-tree such as the UART */ + rhesus_dt_fixup(has_uart_irq); + + /* Setup UART and use it as console */ + uart_init(); + + return true; +} + +DECLARE_PLATFORM(rhesus) = { + .name = "Rhesus", + .probe = rhesus_probe, + .init = rhesus_init, + .cec_power_down = rhesus_power_down, + .cec_reboot = rhesus_reboot, +}; |