aboutsummaryrefslogtreecommitdiff
path: root/hw/xscom.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/xscom.c')
-rw-r--r--hw/xscom.c518
1 files changed, 518 insertions, 0 deletions
diff --git a/hw/xscom.c b/hw/xscom.c
new file mode 100644
index 0000000..c4c3be2
--- /dev/null
+++ b/hw/xscom.c
@@ -0,0 +1,518 @@
+/* Copyright 2013-2014 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <skiboot.h>
+#include <xscom.h>
+#include <io.h>
+#include <processor.h>
+#include <device.h>
+#include <chip.h>
+#include <centaur.h>
+#include <fsp-elog.h>
+
+/* Mask of bits to clear in HMER before an access */
+#define HMER_CLR_MASK (~(SPR_HMER_XSCOM_FAIL | \
+ SPR_HMER_XSCOM_DONE | \
+ SPR_HMER_XSCOM_STATUS_MASK))
+
+#define XSCOM_ADDR_IND_FLAG PPC_BIT(0)
+#define XSCOM_ADDR_IND_ADDR_MASK PPC_BITMASK(12,31)
+#define XSCOM_ADDR_IND_ADDR_LSH PPC_BITLSHIFT(31)
+#define XSCOM_ADDR_IND_DATA_MSK PPC_BITMASK(48,63)
+
+#define XSCOM_DATA_IND_READ PPC_BIT(0)
+#define XSCOM_DATA_IND_COMPLETE PPC_BIT(32)
+#define XSCOM_DATA_IND_ERR_MASK PPC_BITMASK(33,35)
+#define XSCOM_DATA_IND_ERR_LSH PPC_BITLSHIFT(35)
+#define XSCOM_DATA_IND_DATA_MSK PPC_BITMASK(48,63)
+
+/* HB folks say: try 10 time for now */
+#define XSCOM_IND_MAX_RETRIES 10
+
+DEFINE_LOG_ENTRY(OPAL_RC_XSCOM_RW, OPAL_PLATFORM_ERR_EVT, OPAL_XSCOM,
+ OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
+ OPAL_NA, NULL);
+
+DEFINE_LOG_ENTRY(OPAL_RC_XSCOM_INDIRECT_RW, OPAL_PLATFORM_ERR_EVT, OPAL_XSCOM,
+ OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
+ OPAL_NA, NULL);
+
+DEFINE_LOG_ENTRY(OPAL_RC_XSCOM_RESET, OPAL_PLATFORM_ERR_EVT, OPAL_XSCOM,
+ OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
+ OPAL_NA, NULL);
+
+/*
+ * Locking notes:
+ *
+ * We used to have a per-target lock. However due to errata HW822317
+ * we can have issues on the issuer side if multiple threads try to
+ * send XSCOMs simultaneously (HMER responses get mixed up), so just
+ * use a global lock instead
+ */
+static struct lock xscom_lock = LOCK_UNLOCKED;
+
+static inline void *xscom_addr(uint32_t gcid, uint32_t pcb_addr)
+{
+ struct proc_chip *chip = get_chip(gcid);
+ uint64_t addr;
+
+ assert(chip);
+ addr = chip->xscom_base;
+ addr |= ((uint64_t)pcb_addr << 4) & ~0xfful;
+ addr |= (pcb_addr << 3) & 0x78;
+
+ return (void *)addr;
+}
+
+static uint64_t xscom_wait_done(void)
+{
+ uint64_t hmer;
+
+ do
+ hmer = mfspr(SPR_HMER);
+ while(!(hmer & SPR_HMER_XSCOM_DONE));
+
+ /*
+ * HW822317: We need to read a second time as the actual
+ * status can be delayed by 1 cycle after DONE
+ */
+ return mfspr(SPR_HMER);
+}
+
+static void xscom_reset(uint32_t gcid)
+{
+ u64 hmer;
+
+ /* Clear errors in HMER */
+ mtspr(SPR_HMER, HMER_CLR_MASK);
+
+ /* First we need to write 0 to a register on our chip */
+ out_be64(xscom_addr(this_cpu()->chip_id, 0x202000f), 0);
+ hmer = xscom_wait_done();
+ if (hmer & SPR_HMER_XSCOM_FAIL)
+ goto fail;
+
+ /* Then we need to clear those two other registers on the target */
+ out_be64(xscom_addr(gcid, 0x2020007), 0);
+ hmer = xscom_wait_done();
+ if (hmer & SPR_HMER_XSCOM_FAIL)
+ goto fail;
+ out_be64(xscom_addr(gcid, 0x2020009), 0);
+ hmer = xscom_wait_done();
+ if (hmer & SPR_HMER_XSCOM_FAIL)
+ goto fail;
+ return;
+ fail:
+ /* Fatal error resetting XSCOM */
+ log_simple_error(&e_info(OPAL_RC_XSCOM_RESET),
+ "XSCOM: Fatal error resetting engine after failed access !\n");
+
+ /* XXX Generate error log ? attn ? panic ?
+ * If we decide to panic, change the above severity to PANIC
+ */
+}
+
+static bool xscom_handle_error(uint64_t hmer, uint32_t gcid, uint32_t pcb_addr,
+ bool is_write)
+{
+ unsigned int stat = GETFIELD(SPR_HMER_XSCOM_STATUS, hmer);
+
+ /* XXX Figure out error codes from doc and error
+ * recovery procedures
+ */
+ switch(stat) {
+ /* XSCOM blocked, just retry */
+ case 1:
+ return true;
+ }
+
+ /* XXX: Create error log entry ? */
+ log_simple_error(&e_info(OPAL_RC_XSCOM_RW),
+ "XSCOM: %s error gcid=0x%x pcb_addr=0x%x stat=0x%x\n",
+ is_write ? "write" : "read", gcid, pcb_addr, stat);
+
+ /* We need to reset the XSCOM or we'll hang on the next access */
+ xscom_reset(gcid);
+
+ /* Non recovered ... just fail */
+ return false;
+}
+
+static void xscom_handle_ind_error(uint64_t data, uint32_t gcid,
+ uint64_t pcb_addr, bool is_write)
+{
+ unsigned int stat = GETFIELD(XSCOM_DATA_IND_ERR, data);
+ bool timeout = !(data & XSCOM_DATA_IND_COMPLETE);
+
+ /* XXX: Create error log entry ? */
+ if (timeout)
+ log_simple_error(&e_info(OPAL_RC_XSCOM_INDIRECT_RW),
+ "XSCOM: %s indirect timeout, gcid=0x%x pcb_addr=0x%llx"
+ " stat=0x%x\n",
+ is_write ? "write" : "read", gcid, pcb_addr, stat);
+ else
+ log_simple_error(&e_info(OPAL_RC_XSCOM_INDIRECT_RW),
+ "XSCOM: %s indirect error, gcid=0x%x pcb_addr=0x%llx"
+ " stat=0x%x\n",
+ is_write ? "write" : "read", gcid, pcb_addr, stat);
+}
+
+static bool xscom_gcid_ok(uint32_t gcid)
+{
+ return get_chip(gcid) != NULL;
+}
+
+/*
+ * Low level XSCOM access functions, perform a single direct xscom
+ * access via MMIO
+ */
+static int __xscom_read(uint32_t gcid, uint32_t pcb_addr, uint64_t *val)
+{
+ uint64_t hmer;
+
+ if (!xscom_gcid_ok(gcid)) {
+ prerror("%s: invalid XSCOM gcid 0x%x\n", __func__, gcid);
+ return OPAL_PARAMETER;
+ }
+
+ for (;;) {
+ /* Clear status bits in HMER (HMER is special
+ * writing to it *ands* bits
+ */
+ mtspr(SPR_HMER, HMER_CLR_MASK);
+
+ /* Read value from SCOM */
+ *val = in_be64(xscom_addr(gcid, pcb_addr));
+
+ /* Wait for done bit */
+ hmer = xscom_wait_done();
+
+ /* Check for error */
+ if (!(hmer & SPR_HMER_XSCOM_FAIL))
+ break;
+
+ /* Handle error and eventually retry */
+ if (!xscom_handle_error(hmer, gcid, pcb_addr, false))
+ return OPAL_HARDWARE;
+ }
+ return 0;
+}
+
+static int __xscom_write(uint32_t gcid, uint32_t pcb_addr, uint64_t val)
+{
+ uint64_t hmer;
+
+ if (!xscom_gcid_ok(gcid)) {
+ prerror("%s: invalid XSCOM gcid 0x%x\n", __func__, gcid);
+ return OPAL_PARAMETER;
+ }
+
+ for (;;) {
+ /* Clear status bits in HMER (HMER is special
+ * writing to it *ands* bits
+ */
+ mtspr(SPR_HMER, HMER_CLR_MASK);
+
+ /* Write value to SCOM */
+ out_be64(xscom_addr(gcid, pcb_addr), val);
+
+ /* Wait for done bit */
+ hmer = xscom_wait_done();
+
+ /* Check for error */
+ if (!(hmer & SPR_HMER_XSCOM_FAIL))
+ break;
+
+ /* Handle error and eventually retry */
+ if (!xscom_handle_error(hmer, gcid, pcb_addr, true))
+ return OPAL_HARDWARE;
+ }
+ return 0;
+}
+
+/*
+ * Indirect XSCOM access functions
+ */
+static int xscom_indirect_read(uint32_t gcid, uint64_t pcb_addr, uint64_t *val)
+{
+ uint32_t addr;
+ uint64_t data;
+ int rc, retries;
+
+ if (proc_gen != proc_gen_p8) {
+ *val = (uint64_t)-1;
+ return OPAL_UNSUPPORTED;
+ }
+
+ /* Write indirect address */
+ addr = pcb_addr & 0x7fffffff;
+ data = XSCOM_DATA_IND_READ |
+ (pcb_addr & XSCOM_ADDR_IND_ADDR_MASK);
+ rc = __xscom_write(gcid, addr, data);
+ if (rc)
+ goto bail;
+
+ /* Wait for completion */
+ for (retries = 0; retries < XSCOM_IND_MAX_RETRIES; retries++) {
+ rc = __xscom_read(gcid, addr, &data);
+ if (rc)
+ goto bail;
+ if ((data & XSCOM_DATA_IND_COMPLETE) &&
+ ((data & XSCOM_DATA_IND_ERR_MASK) == 0)) {
+ *val = data & XSCOM_DATA_IND_DATA_MSK;
+ break;
+ }
+ if ((data & XSCOM_DATA_IND_COMPLETE) ||
+ (retries >= XSCOM_IND_MAX_RETRIES)) {
+ xscom_handle_ind_error(data, gcid, pcb_addr,
+ false);
+ rc = OPAL_HARDWARE;
+ goto bail;
+ }
+ }
+ bail:
+ if (rc)
+ *val = (uint64_t)-1;
+ return rc;
+}
+
+static int xscom_indirect_write(uint32_t gcid, uint64_t pcb_addr, uint64_t val)
+{
+ uint32_t addr;
+ uint64_t data;
+ int rc, retries;
+
+ if (proc_gen != proc_gen_p8)
+ return OPAL_UNSUPPORTED;
+
+ /* Write indirect address & data */
+ addr = pcb_addr & 0x7fffffff;
+ data = pcb_addr & XSCOM_ADDR_IND_ADDR_MASK;
+ data |= val & XSCOM_ADDR_IND_DATA_MSK;
+
+ rc = __xscom_write(gcid, addr, data);
+ if (rc)
+ goto bail;
+
+ /* Wait for completion */
+ for (retries = 0; retries < XSCOM_IND_MAX_RETRIES; retries++) {
+ rc = __xscom_read(gcid, addr, &data);
+ if (rc)
+ goto bail;
+ if ((data & XSCOM_DATA_IND_COMPLETE) &&
+ ((data & XSCOM_DATA_IND_ERR_MASK) == 0))
+ break;
+ if ((data & XSCOM_DATA_IND_COMPLETE) ||
+ (retries >= XSCOM_IND_MAX_RETRIES)) {
+ xscom_handle_ind_error(data, gcid, pcb_addr,
+ false);
+ rc = OPAL_HARDWARE;
+ goto bail;
+ }
+ }
+ bail:
+ return rc;
+}
+
+static uint32_t xscom_decode_chiplet(uint32_t partid, uint64_t *pcb_addr)
+{
+ uint32_t gcid = (partid & 0x0fffffff) >> 4;
+ uint32_t core = partid & 0xf;
+
+ *pcb_addr |= P8_EX_PCB_SLAVE_BASE;
+ *pcb_addr |= core << 24;
+
+ return gcid;
+}
+
+/*
+ * External API
+ */
+int xscom_read(uint32_t partid, uint64_t pcb_addr, uint64_t *val)
+{
+ bool need_unlock;
+ uint32_t gcid;
+ int rc;
+
+ /* Handle part ID decoding */
+ switch(partid >> 28) {
+ case 0: /* Normal processor chip */
+ gcid = partid;
+ break;
+ case 8: /* Centaur */
+ return centaur_xscom_read(partid, pcb_addr, val);
+ case 4: /* EX chiplet */
+ gcid = xscom_decode_chiplet(partid, &pcb_addr);
+ break;
+ default:
+ return OPAL_PARAMETER;
+ }
+
+ /*
+ * HW822317 requires locking. We use a recursive lock as error
+ * conditions might cause printf's which might then try to take
+ * the lock again
+ */
+ need_unlock = lock_recursive(&xscom_lock);
+
+ /* Direct vs indirect access */
+ if (pcb_addr & XSCOM_ADDR_IND_FLAG)
+ rc = xscom_indirect_read(gcid, pcb_addr, val);
+ else
+ rc = __xscom_read(gcid, pcb_addr & 0x7fffffff, val);
+
+ /* Unlock it */
+ if (need_unlock)
+ unlock(&xscom_lock);
+ return rc;
+}
+
+opal_call(OPAL_XSCOM_READ, xscom_read, 3);
+
+int xscom_write(uint32_t partid, uint64_t pcb_addr, uint64_t val)
+{
+ bool need_unlock;
+ uint32_t gcid;
+ int rc;
+
+ /* Handle part ID decoding */
+ switch(partid >> 28) {
+ case 0: /* Normal processor chip */
+ gcid = partid;
+ break;
+ case 8: /* Centaur */
+ return centaur_xscom_write(partid, pcb_addr, val);
+ case 4: /* EX chiplet */
+ gcid = xscom_decode_chiplet(partid, &pcb_addr);
+ break;
+ default:
+ return OPAL_PARAMETER;
+ }
+
+ /*
+ * HW822317 requires locking. We use a recursive lock as error
+ * conditions might cause printf's which might then try to take
+ * the lock again
+ */
+ need_unlock = lock_recursive(&xscom_lock);
+
+ /* Direct vs indirect access */
+ if (pcb_addr & XSCOM_ADDR_IND_FLAG)
+ rc = xscom_indirect_write(gcid, pcb_addr, val);
+ else
+ rc = __xscom_write(gcid, pcb_addr & 0x7fffffff, val);
+
+ /* Unlock it */
+ if (need_unlock)
+ unlock(&xscom_lock);
+ return rc;
+}
+opal_call(OPAL_XSCOM_WRITE, xscom_write, 3);
+
+int xscom_readme(uint64_t pcb_addr, uint64_t *val)
+{
+ return xscom_read(this_cpu()->chip_id, pcb_addr, val);
+}
+
+int xscom_writeme(uint64_t pcb_addr, uint64_t val)
+{
+ return xscom_write(this_cpu()->chip_id, pcb_addr, val);
+}
+
+static void xscom_init_chip_info(struct proc_chip *chip)
+{
+ uint64_t val;
+ int64_t rc;
+
+ rc = xscom_read(chip->id, 0xf000f, &val);
+ if (rc) {
+ prerror("XSCOM: Error %lld reading 0xf000f register\n", rc);
+ /* We leave chip type to UNKNOWN */
+ return;
+ }
+
+ /* Extract CFAM id */
+ val >>= 44;
+
+ /* Identify chip */
+ switch(val & 0xff) {
+ case 0xf9:
+ chip->type = PROC_CHIP_P7;
+ assert(proc_gen == proc_gen_p7);
+ break;
+ case 0xe8:
+ chip->type = PROC_CHIP_P7P;
+ assert(proc_gen == proc_gen_p7);
+ break;
+ case 0xef:
+ chip->type = PROC_CHIP_P8_MURANO;
+ assert(proc_gen == proc_gen_p8);
+ break;
+ case 0xea:
+ chip->type = PROC_CHIP_P8_VENICE;
+ assert(proc_gen == proc_gen_p8);
+ break;
+ default:
+ printf("CHIP: Unknown chip type 0x%02x !!!\n",
+ (unsigned char)(val & 0xff));
+ }
+
+ /* Get EC level from CFAM ID */
+ chip->ec_level = ((val >> 16) & 0xf) << 4;
+ chip->ec_level |= (val >> 8) & 0xf;
+}
+
+void xscom_init(void)
+{
+ struct dt_node *xn;
+
+ dt_for_each_compatible(dt_root, xn, "ibm,xscom") {
+ uint32_t gcid = dt_get_chip_id(xn);
+ const struct dt_property *reg;
+ struct proc_chip *chip;
+ const char *chip_name;
+ static const char *chip_names[] = {
+ "UNKNOWN", "P7", "P7+", "P8E", "P8",
+ };
+
+ chip = get_chip(gcid);
+ assert(chip);
+
+ /* XXX We need a proper address parsing. For now, we just
+ * "know" that we are looking at a u64
+ */
+ reg = dt_find_property(xn, "reg");
+ assert(reg);
+
+ chip->xscom_base = dt_translate_address(xn, 0, NULL);
+
+ /* Grab processor type and EC level */
+ xscom_init_chip_info(chip);
+
+ chip_name = chip->type > PROC_CHIP_P8_VENICE ? "INVALID" :
+ chip_names[chip->type];
+ printf("XSCOM: chip 0x%x at 0x%llx [%s DD%x.%x]\n",
+ gcid, chip->xscom_base,
+ chip_name,
+ chip->ec_level >> 4,
+ chip->ec_level & 0xf);
+ }
+}
+
+void xscom_used_by_console(void)
+{
+ xscom_lock.in_con_path = true;
+}