/* 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 #include #include #include #include #include #include #include /* 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 = 0; /* Mambo chip model lacks the f000f register, just make * something up (Murano DD2.1) */ if (is_mambo_chip) val = 0x221EF04980000000; else 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; /* * Some other processor might hold it without having * disabled the console locally so let's make sure that * is over by taking/releasing the lock ourselves */ lock(&xscom_lock); unlock(&xscom_lock); }