diff options
author | Alistair Popple <alistair@popple.id.au> | 2016-09-16 16:45:39 +1000 |
---|---|---|
committer | Alistair Popple <alistair@popple.id.au> | 2016-10-25 13:32:35 +1100 |
commit | 749351433237295a86afcf451b947bc6621b946f (patch) | |
tree | 4141847a38ed325dfa7d94751ad34a899301a7ea /libpdbg/chip.c | |
parent | e622883810266bcbc09ab5e1bae6a137dcda74c7 (diff) | |
download | pdbg-749351433237295a86afcf451b947bc6621b946f.tar.gz pdbg-749351433237295a86afcf451b947bc6621b946f.zip |
Add support for i2c access to secondary processors
This includes better support for selecting targets, probing thread
status and a number of other minor bugfixes. Also adds an option to
print version numbers.
Signed-off-by: Alistair Popple <alistair@popple.id.au>
Diffstat (limited to 'libpdbg/chip.c')
-rw-r--r-- | libpdbg/chip.c | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/libpdbg/chip.c b/libpdbg/chip.c new file mode 100644 index 0000000..9a0b963 --- /dev/null +++ b/libpdbg/chip.c @@ -0,0 +1,510 @@ +/* Copyright 2016 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 <stdio.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <ccan/array_size/array_size.h> +#include <unistd.h> + +#include "operations.h" +#include "bitutils.h" + +#define RAS_STATUS_TIMEOUT 100 + +#define DIRECT_CONTROLS_REG 0x0 +#define DIRECT_CONTROL_SP_STEP PPC_BIT(61) +#define DIRECT_CONTROL_SP_START PPC_BIT(62) +#define DIRECT_CONTROL_SP_STOP PPC_BIT(63) +#define RAS_MODE_REG 0x1 +#define MR_THREAD_IN_DEBUG PPC_BIT(43) +#define MR_DO_SINGLE_MODE PPC_BIT(50) +#define RAS_STATUS_REG 0x2 +#define RAS_STATUS_SRQ_EMPTY PPC_BIT(8) +#define RAS_STATUS_LSU_QUIESCED PPC_BIT(9) +#define RAS_STATUS_INST_COMPLETE PPC_BIT(12) +#define RAS_STATUS_THREAD_ACTIVE PPC_BIT(48) +#define RAS_STATUS_TS_QUIESCE PPC_BIT(49) +#define POW_STATUS_REG 0x4 +#define PMC_POW_STATE PPC_BITMASK(4, 5) +#define CORE_POW_STATE PPC_BITMASK(23, 25) +#define THREAD_ACTIVE_REG 0x1310e +#define THREAD_ACTIVE PPC_BITMASK(0, 7) +#define RAM_THREAD_ACTIVE PPC_BITMASK(8, 15) +#define SPR_MODE_REG 0x13281 +#define SPR_MODE_SPRC_WR_EN PPC_BIT(3) +#define SPR_MODE_SPRC_SEL PPC_BITMASK(16, 19) +#define SPR_MODE_SPRC_T_SEL PPC_BITMASK(20, 27) +#define L0_SCOM_SPRC_REG 0x13280 +#define SCOM_SPRC_SCRATCH_SPR 0x40 +#define SCR0_REG 0x13283 +#define RAM_MODE_REG 0x13c00 +#define RAM_MODE_ENABLE PPC_BIT(0) +#define RAM_CTRL_REG 0x13c01 +#define RAM_THREAD_SELECT PPC_BITMASK(0, 2) +#define RAM_INSTR PPC_BITMASK(3, 34) +#define RAM_STATUS_REG 0x13c02 +#define RAM_CONTROL_RECOV PPC_BIT(0) +#define RAM_STATUS PPC_BIT(1) +#define RAM_EXCEPTION PPC_BIT(2) +#define LSU_EMPTY PPC_BIT(3) +#define SCOM_EX_GP3 0xf0012 +#define PMSPCWKUPFSP_REG 0xf010d +#define FSP_SPECIAL_WAKEUP PPC_BIT(0) +#define EX_PM_GP0_REG 0xf0100 +#define SPECIAL_WKUP_DONE PPC_BIT(31) + +/* How long (in us) to wait for a special wakeup to complete */ +#define SPECIAL_WKUP_TIMEOUT 1000 + +/* Opcodes */ +#define MTNIA_OPCODE 0x00000002UL +#define MFNIA_OPCODE 0x00000004UL +#define MFMSR_OPCODE 0x7c0000a6UL +#define MTMSR_OPCODE 0x7c000124UL +#define MFSPR_OPCODE 0x7c0002a6UL +#define MTSPR_OPCODE 0x7c0003a6UL + +static uint64_t mfspr(uint64_t reg, uint64_t spr) +{ + if (reg > 31) + PR_ERROR("Invalid register specified\n"); + + return MFSPR_OPCODE | (reg << 21) | ((spr & 0x1f) << 16) | ((spr & 0x3e0) << 6); +} + +static uint64_t mtspr(uint64_t spr, uint64_t reg) +{ + if (reg > 31) + PR_ERROR("Invalid register specified\n"); + + return MTSPR_OPCODE | (reg << 21) | ((spr & 0x1f) << 16) | ((spr & 0x3e0) << 6); +} + +static uint64_t mfnia(uint64_t reg) +{ + if (reg > 31) + PR_ERROR("Invalid register specified\n"); + + return MFNIA_OPCODE | (reg << 21); +} + +static uint64_t mtnia(uint64_t reg) +{ + if (reg > 31) + PR_ERROR("Invalid register specified\n"); + + return MTNIA_OPCODE | (reg << 21); +} + +static uint64_t mfmsr(uint64_t reg) +{ + if (reg > 31) + PR_ERROR("Invalid register specified\n"); + + return MFMSR_OPCODE | (reg << 21); +} + +static uint64_t mtmsr(uint64_t reg) +{ + if (reg > 31) + PR_ERROR("Invalid register specified\n"); + + return MTMSR_OPCODE | (reg << 21); +} + +static int assert_special_wakeup(struct target *chip) +{ + int i = 0; + uint64_t gp0; + + /* Assert special wakeup to prevent low power states */ + CHECK_ERR(write_target(chip, PMSPCWKUPFSP_REG, FSP_SPECIAL_WAKEUP)); + + /* Poll for completion */ + do { + usleep(1); + CHECK_ERR(read_target(chip, EX_PM_GP0_REG, &gp0)); + + if (i++ > SPECIAL_WKUP_TIMEOUT) { + PR_ERROR("Timeout waiting for special wakeup\n"); + return -1; + } + } while (!(gp0 & SPECIAL_WKUP_DONE)); + + return 0; +} + +static int deassert_special_wakeup(struct target *chip) +{ + /* Assert special wakeup to prevent low power states */ + CHECK_ERR(write_target(chip, PMSPCWKUPFSP_REG, 0)); + + return 0; +} + +uint64_t chiplet_thread_status(struct target *thread) +{ + return thread->status; +} + +static uint64_t get_thread_status(struct target *thread) +{ + uint64_t val, mode_reg, thread_status = 0; + + /* Need to activete debug mode to get complete status */ + CHECK_ERR(read_target(thread, RAS_MODE_REG, &mode_reg)); + mode_reg |= MR_THREAD_IN_DEBUG; + CHECK_ERR(write_target(thread, RAS_MODE_REG, mode_reg)); + + /* Read status */ + CHECK_ERR(read_target(thread, RAS_STATUS_REG, &val)); + + if (val & RAS_STATUS_THREAD_ACTIVE) + thread_status |= THREAD_STATUS_ACTIVE; + + if (val & RAS_STATUS_TS_QUIESCE) + thread_status |= THREAD_STATUS_QUIESCE; + + /* Read POW status */ + CHECK_ERR(read_target(thread, POW_STATUS_REG, &val)); + thread_status = SETFIELD(THREAD_STATUS_STATE, thread_status, GETFIELD(PMC_POW_STATE, val)); + + /* Clear debug mode */ + mode_reg &= ~MR_THREAD_IN_DEBUG; + CHECK_ERR(write_target(thread, RAS_MODE_REG, mode_reg)); + + return thread_status; +} + +/* + * Single step the thread count instructions. + */ +int ram_step_thread(struct target *thread, int count) +{ + int i; + uint64_t ras_mode, ras_status; + + /* Activate single-step mode */ + CHECK_ERR(read_target(thread, RAS_MODE_REG, &ras_mode)); + ras_mode |= MR_DO_SINGLE_MODE; + CHECK_ERR(write_target(thread, RAS_MODE_REG, ras_mode)); + + /* Step the core */ + for (i = 0; i < count; i++) { + CHECK_ERR(write_target(thread, DIRECT_CONTROLS_REG, DIRECT_CONTROL_SP_STEP)); + + /* Wait for step to complete */ + do { + CHECK_ERR(read_target(thread, RAS_STATUS_REG, &ras_status)); + } while (!(ras_status & RAS_STATUS_INST_COMPLETE)); + } + + /* Deactivate single-step mode */ + ras_mode &= ~MR_DO_SINGLE_MODE; + CHECK_ERR(write_target(thread, RAS_MODE_REG, ras_mode)); + + return 0; +} + +int ram_stop_thread(struct target *thread) +{ + int i = 0, thread_id = (thread->base >> 4) & 0x7; + uint64_t val; + struct target *chip = thread->next; + + /* Skip inactive threads */ + if (!GETFIELD(THREAD_STATUS_ACTIVE, thread->status)) + return -1; + + do { + /* Quiese active thread */ + CHECK_ERR(write_target(thread, DIRECT_CONTROLS_REG, DIRECT_CONTROL_SP_STOP)); + + /* Wait for thread to quiese */ + CHECK_ERR(read_target(chip, RAS_STATUS_REG, &val)); + if (i++ > RAS_STATUS_TIMEOUT) { + PR_ERROR("Unable to quiesce thread %d (0x%016llx).\n", + thread->index, val); + PR_ERROR("Continuing anyway.\n"); + if (val & PPC_BIT(48)) { + PR_ERROR("Unable to continue\n"); + } + break; + } + + /* We can continue ramming if either the + * thread is not active or the SRQ/LSU/TS bits + * are set. */ + } while ((val & RAS_STATUS_THREAD_ACTIVE) && + !((val & RAS_STATUS_SRQ_EMPTY) + && (val & RAS_STATUS_LSU_QUIESCED) + && (val & RAS_STATUS_TS_QUIESCE))); + + + /* Make the threads RAM thread active */ + CHECK_ERR(read_target(chip, THREAD_ACTIVE_REG, &val)); + val |= PPC_BIT(8) >> thread_id; + CHECK_ERR(write_target(chip, THREAD_ACTIVE_REG, val)); + + return 0; +} + +int ram_start_thread(struct target *thread) +{ + uint64_t val; + struct target *chip = thread->next; + int thread_id = (thread->base >> 4) & 0x7; + + if (!GETFIELD(THREAD_STATUS_ACTIVE, thread->status)) + return -1; + + /* Activate thread */ + CHECK_ERR(write_target(thread, DIRECT_CONTROLS_REG, DIRECT_CONTROL_SP_START)); + + /* Restore thread active */ + CHECK_ERR(read_target(chip, THREAD_ACTIVE_REG, &val)); + val &= ~(PPC_BIT(8) >> thread_id); + val |= PPC_BIT(thread_id); + CHECK_ERR(write_target(chip, THREAD_ACTIVE_REG, val)); + + return 0; +} + +/* + * RAMs the opcodes in *opcodes and store the results of each opcode + * into *results. *results must point to an array the same size as + * *opcodes. Each entry from *results is put into SCR0 prior to + * executing an opcode so that it may also be used to pass in + * data. Note that only register r0 is saved and restored so opcodes + * must not touch other registers. + */ +static int ram_instructions(struct target *thread, uint64_t *opcodes, + uint64_t *results, int len, unsigned int lpar) +{ + uint64_t ram_mode, val, opcode, r0 = 0; + struct target *chiplet = thread->next; + int thread_id = (thread->base >> 4) & 0x7; + int i; + + /* Activate RAM mode */ + CHECK_ERR(read_target(chiplet, RAM_MODE_REG, &ram_mode)); + ram_mode |= RAM_MODE_ENABLE; + CHECK_ERR(write_target(chiplet, RAM_MODE_REG, ram_mode)); + + /* Setup SPRC to use SPRD */ + val = SPR_MODE_SPRC_WR_EN; + val = SETFIELD(SPR_MODE_SPRC_SEL, val, 1 << (3 - lpar)); + val = SETFIELD(SPR_MODE_SPRC_T_SEL, val, 1 << (7 - thread_id)); + CHECK_ERR(write_target(chiplet, SPR_MODE_REG, val)); + CHECK_ERR(write_target(chiplet, L0_SCOM_SPRC_REG, SCOM_SPRC_SCRATCH_SPR)); + + /* RAM instructions */ + for (i = -1; i <= len; i++) { + if (i < 0) + /* Save r0 (assumes opcodes don't touch other registers) */ + opcode = mtspr(277, 0); + else if (i < len) { + CHECK_ERR(write_target(chiplet, SCR0_REG, results[i])); + opcode = opcodes[i]; + } else if (i >= len) { + /* Restore r0 */ + CHECK_ERR(write_target(chiplet, SCR0_REG, r0)); + opcode = mfspr(0, 277); + } + + /* ram instruction */ + val = SETFIELD(RAM_THREAD_SELECT, 0ULL, thread_id); + val = SETFIELD(RAM_INSTR, val, opcode); + CHECK_ERR(write_target(chiplet, RAM_CTRL_REG, val)); + + /* wait for completion */ + do { + CHECK_ERR(read_target(chiplet, RAM_STATUS_REG, &val)); + } while (!val); + + if (!(val & RAM_STATUS)) + PR_ERROR("RAMMING failed with status 0x%llx\n", val); + + /* Save the results */ + CHECK_ERR(read_target(chiplet, SCR0_REG, &val)); + if (i < 0) + r0 = val; + else if (i < len) + results[i] = val; + } + + /* Disable RAM mode */ + ram_mode &= ~RAM_MODE_ENABLE; + CHECK_ERR(write_target(chiplet, RAM_MODE_REG, ram_mode)); + + return 0; +} + +/* + * Get gpr value. Chip must be stopped. + */ +int ram_getgpr(struct target *thread, int gpr, uint64_t *value) +{ + uint64_t opcodes[] = {mtspr(277, gpr)}; + uint64_t results[] = {0}; + + CHECK_ERR(ram_instructions(thread, opcodes, results, ARRAY_SIZE(opcodes), 0)); + *value = results[0]; + return 0; +} + +int ram_putgpr(struct target *thread, int gpr, uint64_t value) +{ + uint64_t opcodes[] = {mfspr(gpr, 277)}; + uint64_t results[] = {value}; + + CHECK_ERR(ram_instructions(thread, opcodes, results, ARRAY_SIZE(opcodes), 0)); + + return 0; +} + +int ram_getnia(struct target *thread, uint64_t *value) +{ + uint64_t opcodes[] = {mfnia(0), mtspr(277, 0)}; + uint64_t results[] = {0, 0}; + + CHECK_ERR(ram_instructions(thread, opcodes, results, ARRAY_SIZE(opcodes), 0)); + *value = results[1]; + return 0; +} + +int ram_putnia(struct target *thread, uint64_t value) +{ + uint64_t opcodes[] = {mfspr(0, 277), mtnia(0)}; + uint64_t results[] = {value, 0}; + + CHECK_ERR(ram_instructions(thread, opcodes, results, ARRAY_SIZE(opcodes), 0)); + return 0; +} + +int ram_getspr(struct target *thread, int spr, uint64_t *value) +{ + uint64_t opcodes[] = {mfspr(0, spr), mtspr(277, 0)}; + uint64_t results[] = {0, 0}; + + CHECK_ERR(ram_instructions(thread, opcodes, results, ARRAY_SIZE(opcodes), 0)); + *value = results[1]; + return 0; +} + +int ram_putspr(struct target *thread, int spr, uint64_t value) +{ + uint64_t opcodes[] = {mfspr(0, 277), mtspr(spr, 0)}; + uint64_t results[] = {value, 0}; + + CHECK_ERR(ram_instructions(thread, opcodes, results, ARRAY_SIZE(opcodes), 0)); + return 0; +} + +int ram_getmsr(struct target *thread, uint64_t *value) +{ + uint64_t opcodes[] = {mfmsr(0), mtspr(277, 0)}; + uint64_t results[] = {0, 0}; + + CHECK_ERR(ram_instructions(thread, opcodes, results, ARRAY_SIZE(opcodes), 0)); + *value = results[1]; + return 0; +} + +int ram_putmsr(struct target *thread, uint64_t value) +{ + uint64_t opcodes[] = {mfspr(0, 277), mtmsr(0)}; + uint64_t results[] = {value, 0}; + + CHECK_ERR(ram_instructions(thread, opcodes, results, ARRAY_SIZE(opcodes), 0)); + return 0; +} + +int thread_target_init(struct target *thread, const char *name, uint64_t thread_id, struct target *next) +{ + target_init(thread, name, 0x13000 | (thread_id << 4), NULL, NULL, NULL, next); + thread->status = get_thread_status(thread); + + /* Threads always exist, although they may not be in a useful state for most operations */ + return 0; +} + +/* + * Initialise all viable threads for ramming on the given chiplet. + */ +int thread_target_probe(struct target *chiplet, struct target *targets, int max_target_count) +{ + struct target *thread = targets; + int i, count = 0; + + for (i = 0; i < THREADS_PER_CORE && i < max_target_count; i++) { + thread_target_init(thread, "Thread", i, chiplet); + thread++; + count++; + } + + return count; +} + +int chiplet_target_init(struct target *target, const char *name, uint64_t chip_id, struct target *next) +{ + uint64_t value, base; + + base = 0x10000000 | (chip_id << 24); + + target_init(target, name, base, NULL, NULL, NULL, next); + + /* Work out if this chip is actually present */ + if (read_target(target, SCOM_EX_GP3, &value)) { + PR_DEBUG("Error reading chip GP3 register\n"); + return -1; + } + + /* Return 0 is a chip is actually present. We still leave the + target initialised even if it isn't present as a user may + want to continue anyway. */ + return -!GETFIELD(PPC_BIT(0), value); +} + +/* Initialises all possible chiplets on the given processor + * target. *targets should point to pre-allocated memory with enough + * free space for the maximum number of targets. Returns the number of + * chips found. */ +int chiplet_target_probe(struct target *processor, struct target *targets, int max_target_count) +{ + struct target *target = targets; + int i, count = 0, rc = 0; + + for (i = 0; i <= 0xf && i < max_target_count; i++) { + if (i == 0 || i == 7 || i == 8 || i == 0xf) + /* 0, 7, 8 & 0xf are reserved */ + continue; + + if (!(rc = chiplet_target_init(target, "Chiplet", i, processor))) { + assert_special_wakeup(target); + target++; + count++; + } else + target_del(target); + } + + if (rc) + /* The last target is invalid, zero it out */ + memset(target, 0, sizeof(*target)); + + return count; +} |