| /* 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 <errorlog.h> |
| #include <opal-api.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)) |
| |
| #define XSCOM_ADDR_IND_FLAG PPC_BIT(0) |
| #define XSCOM_ADDR_IND_ADDR PPC_BITMASK(12,31) |
| #define XSCOM_ADDR_IND_DATA 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 PPC_BITMASK(33,35) |
| #define XSCOM_DATA_IND_DATA 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); |
| 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) == 0)) { |
| *val = data & XSCOM_DATA_IND_DATA; |
| 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; |
| data |= val & XSCOM_ADDR_IND_DATA; |
| |
| 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) == 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) |
| { |
| 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 us to do global locking */ |
| lock(&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 */ |
| 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) |
| { |
| 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 us to do global locking */ |
| lock(&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 */ |
| 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); |
| } |
| |
| int64_t xscom_read_cfam_chipid(uint32_t partid, uint32_t *chip_id) |
| { |
| uint64_t val; |
| int64_t rc = OPAL_SUCCESS; |
| |
| /* Mambo chip model lacks the f000f register, just make |
| * something up (Murano DD2.1) |
| */ |
| if (chip_quirk(QUIRK_NO_F000F)) |
| val = 0x221EF04980000000; |
| else |
| rc = xscom_read(partid, 0xf000f, &val); |
| |
| /* Extract CFAM id */ |
| *chip_id = (uint32_t)(val >> 44); |
| |
| return rc; |
| } |
| |
| static void xscom_init_chip_info(struct proc_chip *chip) |
| { |
| uint32_t val; |
| int64_t rc; |
| |
| rc = xscom_read_cfam_chipid(chip->id, &val); |
| if (rc) { |
| prerror("XSCOM: Error %lld reading 0xf000f register\n", rc); |
| /* We leave chip type to UNKNOWN */ |
| return; |
| } |
| |
| /* 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); |
| } |
| |
| bool xscom_ok(void) |
| { |
| return !lock_held_by_me(&xscom_lock); |
| } |