From 4c3473a4785336103cc47de95fddbbba12b12e7a Mon Sep 17 00:00:00 2001 From: Alistair Popple Date: Thu, 7 Jan 2016 16:19:43 +1100 Subject: Initial release of pdbg pdbg is a tool to access P8 host registers and memory from the BMC for kernel and firmware debugging purposes. Signed-off-by: Alistair Popple --- libpdbg/adu.c | 216 +++++++++++++++++++++ libpdbg/bitutils.h | 51 +++++ libpdbg/bmcfsi.c | 517 +++++++++++++++++++++++++++++++++++++++++++++++++++ libpdbg/bmcfsi.h | 26 +++ libpdbg/operations.h | 70 +++++++ libpdbg/ram.c | 306 ++++++++++++++++++++++++++++++ libpdbg/scom.c | 77 ++++++++ 7 files changed, 1263 insertions(+) create mode 100644 libpdbg/adu.c create mode 100644 libpdbg/bitutils.h create mode 100644 libpdbg/bmcfsi.c create mode 100644 libpdbg/bmcfsi.h create mode 100644 libpdbg/operations.h create mode 100644 libpdbg/ram.c create mode 100644 libpdbg/scom.c (limited to 'libpdbg') diff --git a/libpdbg/adu.c b/libpdbg/adu.c new file mode 100644 index 0000000..49c7977 --- /dev/null +++ b/libpdbg/adu.c @@ -0,0 +1,216 @@ +/* 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 +#include +#include + +#include "operations.h" +#include "bitutils.h" + +/* ADU SCOM Register Definitions */ +#define ALTD_CONTROL_REG 0x2020000 +#define ALTD_CMD_REG 0x2020001 +#define ALTD_STATUS_REG 0x2020002 +#define ALTD_DATA_REG 0x2020003 + +/* ALTD_CMD_REG fields */ +#define FBC_ALTD_START_OP PPC_BIT(2) +#define FBC_ALTD_CLEAR_STATUS PPC_BIT(3) +#define FBC_ALTD_RESET_AD_PCB PPC_BIT(4) +#define FBC_ALTD_SCOPE PPC_BITMASK(16, 18) +#define FBC_ALTD_AUTO_INC PPC_BIT(19) +#define FBC_ALTD_DROP_PRIORITY PPC_BITMASK(20, 21) +#define FBC_LOCKED PPC_BIT(11) + +#define DROP_PRIORITY_LOW 0ULL +#define DROP_PRIORITY_MEDIUM 1ULL +#define DROP_PRIORITY_HIGH 2ULL + +#define SCOPE_NODAL 0 +#define SCOPE_GROUP 1 +#define SCOPE_SYSTEM 2 +#define SCOPE_REMOTE 3 + +/* ALTD_CONTROL_REG fields */ +#define FBC_ALTD_TTYPE PPC_BITMASK(0, 5) +#define TTYPE_TREAD PPC_BIT(6) +#define FBC_ALTD_TSIZE PPC_BITMASK(7, 13) +#define FBC_ALTD_ADDRESS PPC_BITMASK(14, 63) + +#define TTYPE_CI_PARTIAL_WRITE 0b110111 +#define TTYPE_CI_PARTIAL_OOO_WRITE 0b110110 +#define TTYPE_DMA_PARTIAL_WRITE 0b100110 +#define TTYPE_CI_PARTIAL_READ 0b110100 +#define TTYPE_DMA_PARTIAL_READ 0b110101 +#define TTYPE_PBOPERATION 0b111111 + +/* ALTD_STATUS_REG fields */ +#define FBC_ALTD_ADDR_DONE PPC_BIT(2) +#define FBC_ALTD_DATA_DONE PPC_BIT(3) +#define FBC_ALTD_PBINIT_MISSING PPC_BIT(18) + +static int adu_lock(void) +{ + uint64_t val; + + CHECK_ERR(getscom(&val, ALTD_CMD_REG)); + + if (val & FBC_LOCKED) + PR_INFO("ADU already locked! Ignoring.\n"); + + val |= FBC_LOCKED; + CHECK_ERR(putscom(val, ALTD_CMD_REG)); + + return 0; +} + +static int adu_unlock(void) +{ + uint64_t val; + + CHECK_ERR(getscom(&val, ALTD_CMD_REG)); + + if (!(val & FBC_LOCKED)) { + PR_INFO("ADU already unlocked!\n"); + return 0; + } + + val &= ~FBC_LOCKED; + CHECK_ERR(putscom(val, ALTD_CMD_REG)); + + return 0; +} + +static int adu_reset(void) +{ + uint64_t val; + + CHECK_ERR(getscom(&val, ALTD_CMD_REG)); + val |= FBC_ALTD_CLEAR_STATUS | FBC_ALTD_RESET_AD_PCB; + CHECK_ERR(putscom(val, ALTD_CMD_REG)); + + return 0; +} + +/* Return size bytes of memory in *output. *output must point to an + * array large enough to hold size bytes. block_size specifies whether + * to read 1, 2, 4, or 8 bytes at a time. A value of 0 selects the + * best size to use based on the number of bytes to read. addr must be + * block_size aligned. */ +int adu_getmem(uint64_t addr, uint8_t *output, uint64_t size, int block_size) +{ + int rc = 0; + uint64_t i, cmd_reg, ctrl_reg, val; + + CHECK_ERR(adu_lock()); + + if (!block_size) { + /* TODO: We could optimise this better, but this will + * do the moment. */ + switch(size % 8) { + case 0: + block_size = 8; + break; + + case 1: + case 3: + case 5: + case 7: + block_size = 1; + break; + + case 2: + block_size = 2; + break; + + case 4: + block_size = 4; + break; + } + + /* Fall back to byte at a time if the selected block + * size is not aligned to the address. */ + if (addr % block_size) + block_size = 1; + } + + if (addr % block_size) { + PR_INFO("Unaligned address\n"); + return -1; + } + + if (size % block_size) { + PR_INFO("Invalid size\n"); + return -1; + } + + ctrl_reg = TTYPE_TREAD; + ctrl_reg = SETFIELD(FBC_ALTD_TTYPE, ctrl_reg, TTYPE_DMA_PARTIAL_READ); + ctrl_reg = SETFIELD(FBC_ALTD_TSIZE, ctrl_reg, block_size); + + CHECK_ERR(getscom(&cmd_reg, ALTD_CMD_REG)); + cmd_reg |= FBC_ALTD_START_OP; + cmd_reg = SETFIELD(FBC_ALTD_SCOPE, cmd_reg, SCOPE_SYSTEM); + cmd_reg = SETFIELD(FBC_ALTD_DROP_PRIORITY, cmd_reg, DROP_PRIORITY_MEDIUM); + + for (i = addr; i < addr + size; i += block_size) { + uint64_t data; + + retry: + /* Clear status bits */ + CHECK_ERR(adu_reset()); + + /* Set the address */ + ctrl_reg = SETFIELD(FBC_ALTD_ADDRESS, ctrl_reg, i); + CHECK_ERR(putscom(ctrl_reg, ALTD_CONTROL_REG)); + + /* Start the command */ + CHECK_ERR(putscom(cmd_reg, ALTD_CMD_REG)); + + /* Wait for completion */ + do { + CHECK_ERR(getscom(&val, ALTD_STATUS_REG)); + } while (!val); + + if( !(val & FBC_ALTD_ADDR_DONE) || + !(val & FBC_ALTD_DATA_DONE)) { + /* PBINIT_MISSING is expected occasionally so just retry */ + if (val & FBC_ALTD_PBINIT_MISSING) + goto retry; + else { + PR_ERROR("Unable to read memory. " \ + "ALTD_STATUS_REG = 0x%016llx\n", val); + rc = -1; + break; + } + } + + /* Read data */ + CHECK_ERR(getscom(&data, ALTD_DATA_REG)); + + /* ADU returns data in big-endian form in the register */ + data = __builtin_bswap64(data); + + /* TODO: Not sure where the data for a read < 8 bytes + * ends up */ + memcpy(output, &data, block_size); + output += block_size; + } + + adu_unlock(); + + return rc; +} diff --git a/libpdbg/bitutils.h b/libpdbg/bitutils.h new file mode 100644 index 0000000..76b54a3 --- /dev/null +++ b/libpdbg/bitutils.h @@ -0,0 +1,51 @@ +/* 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. + */ + +#ifndef __BITUTILS_H +#define __BITUTILS_H + +/* PPC bit number conversion */ +#ifdef __ASSEMBLY__ +#define PPC_BIT(bit) (0x8000000000000000 >> (bit)) +#define PPC_BIT32(bit) (0x80000000 >> (bit)) +#define PPC_BIT8(bit) (0x80 >> (bit)) +#else +#define PPC_BIT(bit) (0x8000000000000000UL >> (bit)) +#define PPC_BIT32(bit) (0x80000000UL >> (bit)) +#define PPC_BIT8(bit) (0x80UL >> (bit)) +#endif +#define PPC_BITMASK(bs,be) ((PPC_BIT(bs) - PPC_BIT(be)) | PPC_BIT(bs)) +#define PPC_BITMASK32(bs,be) ((PPC_BIT32(bs) - PPC_BIT32(be))|PPC_BIT32(bs)) +#define PPC_BITLSHIFT(be) (63 - (be)) +#define PPC_BITLSHIFT32(be) (31 - (be)) + +/* + * PPC bitmask field manipulation + */ + +/* Find left shift from first set bit in mask */ +#define MASK_TO_LSH(m) (__builtin_ffsll(m) - 1) + +/* Extract field fname from val */ +#define GETFIELD(m, v) (((v) & (m)) >> MASK_TO_LSH(m)) + +/* Set field fname of oval to fval + * NOTE: oval isn't modified, the combined result is returned + */ +#define SETFIELD(m, v, val) \ + (((v) & ~(m)) | ((((typeof(v))(val)) << MASK_TO_LSH(m)) & (m))) + +#endif /* __BITUTILS_H */ diff --git a/libpdbg/bmcfsi.c b/libpdbg/bmcfsi.c new file mode 100644 index 0000000..fe8298f --- /dev/null +++ b/libpdbg/bmcfsi.c @@ -0,0 +1,517 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bitutils.h" +#include "bmcfsi.h" +#include "operations.h" + +#define FSI_CLK 4 //GPIOA4 +#define FSI_DAT 5 //GPIOA5 +#define CRONUS_SEL 6 //GPIOA6 +#define PCIE_RST_N 13 //GPIOB5 +#define PEX_PERST_N 14 //GPIOB6 +#define POWER 33 //GPIOE1 +#define PGOOD 23 //GPIOC7 +#define FSI_ENABLE 24 //GPIOD0 +#define FSI_DAT_EN 62 //GPIOH6 + +#define GPIO_BASE 0x1e780000 +#define GPIO_DATA 0x0 +#define GPIO_DIR 0x4 +#define GPIOE_DATA 0x20 +#define GPIOE_DIR 0x24 + +#define CRC_LEN 4 + +/* FSI result symbols */ +enum fsi_result { + FSI_MERR_TIMEOUT = -2, + FSI_MERR_C = -1, + FSI_ACK = 0x0, + FSI_BUSY = 0x1, + FSI_ERR_A = 0x2, + FSI_ERR_C = 0x3, +}; + +#define FSI_DATA0_REG 0x1000 +#define FSI_DATA1_REG 0x1001 +#define FSI_CMD_REG 0x1002 +#define FSI_CMD_REG_WRITE PPC_BIT32(0) + +#define FSI_RESET_REG 0x1006 +#define FSI_RESET_CMD PPC_BIT32(0) + +#define FSI_SET_PIB_RESET_REG 0x1007 +#define FSI_SET_PIB_RESET PPC_BIT32(0) + +/* Clock delay in a for loop, determined by trial and error with + * -O2 */ +#define CLOCK_DELAY 3 + +/* For some reason the FSI2PIB engine dies with frequent + * access. Letting it have a bit of a rest seems to stop the + * problem. This sets the number of usecs to sleep between SCOM + * accesses. */ +#define FSI2PIB_RELAX 50 + +/* FSI private data */ +static void *gpio_reg = NULL; +static int mem_fd = 0; +static int slave = 0; + +static uint32_t readl(void *addr) +{ + asm volatile("" : : : "memory"); + return *(volatile uint32_t *) addr; +} + +static void writel(uint32_t val, void *addr) +{ + asm volatile("" : : : "memory"); + *(volatile uint32_t *) addr = val; +} + +static int __attribute__((unused)) get_direction(int gpio) +{ + void *offset = gpio_reg + GPIO_DIR; + + if (gpio > 31) { + gpio -= 32; + offset = gpio_reg + GPIOE_DIR; + } + + return !!(readl(offset) & (1ULL << gpio)); +} + +static void set_direction_out(int gpio) +{ + uint32_t x; + void *offset = gpio_reg + GPIO_DIR; + + if (gpio > 31) { + gpio -= 32; + offset = gpio_reg + GPIOE_DIR; + } + + x = readl(offset); + x |= 1ULL << gpio; + writel(x, offset); +} + +static void set_direction_in(int gpio) +{ + uint32_t x; + void *offset = gpio_reg + GPIO_DIR; + + if (gpio > 31) { + gpio -= 32; + offset = gpio_reg + GPIOE_DIR; + } + + x = readl(offset); + x &= ~(1ULL << gpio); + writel(x, offset); +} + +static int read_gpio(int gpio) +{ + void *offset = gpio_reg + GPIO_DATA; + + if (gpio > 31) { + gpio -= 32; + offset = gpio_reg + GPIOE_DATA; + } + + return (readl(offset) >> gpio) & 0x1; +} + +static void write_gpio(int gpio, int val) +{ + uint32_t x; + void *offset = gpio_reg + GPIO_DATA; + + if (gpio > 31) { + gpio -= 32; + offset = gpio_reg + GPIOE_DATA; + } + + x = readl(offset); + if (val) + x |= 1ULL << gpio; + else + x &= ~(1ULL << gpio); + writel(x, offset); +} + +static inline void clock_cycle(int gpio, int num_clks) +{ + int i; + volatile int j; + + /* Need to introduce delays when inlining this function */ + for (j = 0; j < CLOCK_DELAY; j++); + for (i = 0; i < num_clks; i++) { + write_gpio(gpio, 0); + write_gpio(gpio, 1); + } + for (j = 0; j < CLOCK_DELAY; j++); +} + +static uint8_t crc4(uint8_t c, int b) +{ + uint8_t m = 0; + + c &= 0xf; + m = b ^ ((c >> 3) & 0x1); + m = (m << 2) | (m << 1) | (m); + c <<= 1; + c ^= m; + + return c & 0xf; +} + +/* FSI bits should be reading on the falling edge. Read a bit and + * clock the next one out. */ +static inline unsigned int fsi_read_bit(void) +{ + int x; + + x = read_gpio(FSI_DAT); + clock_cycle(FSI_CLK, 1); + + /* The FSI hardware is active low (ie. inverted) */ + return !(x & 1); +} + +static inline void fsi_send_bit(uint64_t bit) +{ + write_gpio(FSI_DAT, !bit); + clock_cycle(FSI_CLK, 1); +} + +/* Format a CFAM address into an FSI slaveId, command and address. */ +static uint64_t fsi_abs_ar(uint8_t chip_id, uint32_t addr, int read) +{ + /* Reformat the address. I'm not sure I fully understand this + * yet but we basically shift the bottom byte and add 0b01 + * (for the write word?) */ + addr = ((addr & 0xff00) | ((addr & 0xff) << 2)) << 1; + addr |= 0x3; + addr |= chip_id << 26; + addr |= (0x8ULL | !!(read)) << 22; + + return addr; +} + +static uint64_t fsi_d_poll(uint8_t slave_id) +{ + return slave_id << 3 | 0x2; +} + +static void fsi_break(void) +{ + set_direction_out(FSI_CLK); + set_direction_out(FSI_DAT); + write_gpio(FSI_DAT_EN, 1); + + /* Crank things - not sure if we need this yet */ + write_gpio(FSI_CLK, 1); + write_gpio(FSI_DAT, 1); /* Data standby state */ + clock_cycle(FSI_CLK, 5000); + + /* Send break command */ + write_gpio(FSI_DAT, 0); + clock_cycle(FSI_CLK, 256); +} + +/* Send a sequence, including start bit and crc */ +static void fsi_send_seq(uint64_t seq, int len) +{ + int i; + uint8_t crc; + + set_direction_out(FSI_CLK); + set_direction_out(FSI_DAT); + write_gpio(FSI_DAT_EN, 1); + + write_gpio(FSI_DAT, 1); + clock_cycle(FSI_CLK, 50); + + /* Send the start bit */ + write_gpio(FSI_DAT, 0); + clock_cycle(FSI_CLK, 1); + + /* crc includes start bit */ + crc = crc4(0, 1); + + for (i = 63; i >= 64 - len; i--) { + crc = crc4(crc, !!(seq & (1ULL << i))); + fsi_send_bit(seq & (1ULL << i)); + } + + /* Send the CRC */ + for (i = 3; i >= 0; i--) + fsi_send_bit(crc & (1ULL << i)); + + write_gpio(FSI_CLK, 0); +} + +/* Read a response. Only supports upto 60 bits at the moment. */ +static enum fsi_result fsi_read_resp(uint64_t *result, int len) +{ + int i, x; + uint8_t crc; + uint64_t resp = 0; + uint8_t ack = 0; + + write_gpio(FSI_DAT_EN, 0); + set_direction_in(FSI_DAT); + + /* Wait for start bit */ + for (i = 0; i < 512; i++) { + x = fsi_read_bit(); + if (x) + break; + } + + if (i == 512) { + printf("Timeout waiting for start bit\n"); + return FSI_MERR_TIMEOUT; + } + + crc = crc4(0, 1); + + /* Read the response code (ACK, ERR_A, etc.) */ + for (i = 0; i < 4; i++) { + ack <<= 1; + ack |= fsi_read_bit(); + crc = crc4(crc, ack & 0x1); + } + + /* A non-ACK response has no data but should include a CRC */ + if (ack != FSI_ACK) + len = 7; + + for (; i < len + CRC_LEN; i++) { + resp <<= 1; + resp |= fsi_read_bit(); + crc = crc4(crc, resp & 0x1); + } + + if (crc != 0) { + printf("CRC error: 0x%llx\n", resp); + return FSI_MERR_C; + } + + write_gpio(FSI_CLK, 0); + + /* Strip the CRC off */ + *result = resp >> 4; + + return ack; +} + +static enum fsi_result fsi_d_poll_wait(uint8_t chip_id, uint64_t *resp, int len) +{ + int i; + uint64_t seq; + enum fsi_result rc; + + /* Poll for response if busy */ + for (i = 0; i < 512; i++) { + seq = fsi_d_poll(chip_id) << 59; + fsi_send_seq(seq, 5); + + if ((rc = fsi_read_resp(resp, len)) != FSI_BUSY) + break; + } + + return rc; +} + +static int fsi_getcfam(struct scom_backend *backend, uint32_t *value, uint32_t addr) +{ + uint64_t seq; + uint64_t resp; + uint8_t chip_id = slave; + enum fsi_result rc; + + /* Format of the read sequence is: + * 6666555555555544444444443333333333222222222211111111110000000000 + * 3210987654321098765432109876543210987654321098765432109876543210 + * + * ii1001aaaaaaaaaaaaaaaaaaa011cccc + * + * Where: + * ii = slaveId (hardcoded to 11 for the moment) + * a = address bit + * 011 = write word size + * d = data bit + * c = crc bit + * + * When applying the sequence it should be inverted (active + * low) + */ + seq = fsi_abs_ar(chip_id, addr, 1) << 36; + fsi_send_seq(seq, 28); + + if ((rc = fsi_read_resp(&resp, 36)) == FSI_BUSY) + rc = fsi_d_poll_wait(chip_id, &resp, 36); + + if (rc != FSI_ACK) { + PR_ERROR("getcfam error. Response: 0x%01x\n", rc); + rc = -1; + } + + *value = resp & 0xffffffff; + return rc; +} + +static int fsi_putcfam(struct scom_backend *backend, uint32_t data, uint32_t addr) +{ + uint64_t seq; + uint64_t resp; + uint8_t chip_id = slave; + enum fsi_result rc; + + /* Format of the sequence is: + * 6666555555555544444444443333333333222222222211111111110000000000 + * 3210987654321098765432109876543210987654321098765432109876543210 + * + * ii1000aaaaaaaaaaaaaaaaaaa011ddddddddddddddddddddddddddddddddcccc + * + * Where: + * ii = slaveId (hardcoded to 11 for the moment) + * a = address bit + * 011 = write word size + * d = data bit + * c = crc bit + * + * When applying the sequence it should be inverted (active + * low) + */ + seq = fsi_abs_ar(chip_id, addr, 0) << 36; + seq |= ((uint64_t) data & 0xffffffff) << (4); + + fsi_send_seq(seq, 60); + if ((rc =fsi_read_resp(&resp, 4)) == FSI_BUSY) + rc = fsi_d_poll_wait(chip_id, &resp, 4); + + if (rc != FSI_ACK) { + PR_ERROR("putcfam error. Response: 0x%01x\n", rc); + } else + rc = 0; + + return rc; +} + +static int fsi_getscom(struct scom_backend *backend, uint64_t *value, uint32_t addr) +{ + uint32_t result; + + usleep(FSI2PIB_RELAX); + + /* Get scom works by putting the address in FSI_CMD_REG and + * reading the result from FST_DATA[01]_REG. */ + CHECK_ERR(fsi_putcfam(backend, addr, FSI_CMD_REG)); + CHECK_ERR(fsi_getcfam(backend, &result, FSI_DATA0_REG)); + *value = (uint64_t) result << 32; + CHECK_ERR(fsi_getcfam(backend, &result, FSI_DATA1_REG)); + *value |= result; + return 0; +} + +static int fsi_putscom(struct scom_backend *backend, uint64_t value, uint32_t addr) +{ + usleep(FSI2PIB_RELAX); + + CHECK_ERR(fsi_putcfam(backend, FSI_RESET_CMD, FSI_RESET_REG)); + CHECK_ERR(fsi_putcfam(backend, (value >> 32) & 0xffffffff, FSI_DATA0_REG)); + CHECK_ERR(fsi_putcfam(backend, value & 0xffffffff, FSI_DATA1_REG)); + CHECK_ERR(fsi_putcfam(backend, FSI_CMD_REG_WRITE | addr, FSI_CMD_REG)); + + return 0; +} + +struct scom_backend *fsi_init(int slave_id) +{ + struct scom_backend *backend; + + if (gpio_reg) { + /* FIXME .... */ + PR_ERROR("One a single instance of this backend is supported\n"); + return NULL; + } + + backend = malloc(sizeof(*backend)); + if (!backend) + return NULL; + + mem_fd = open("/dev/mem", O_RDWR | O_SYNC); + if (mem_fd < 0) { + perror("Unable to open /dev/mem"); + exit(1); + } + + gpio_reg = mmap(NULL, getpagesize(), + PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, GPIO_BASE); + if (gpio_reg == MAP_FAILED) { + perror("Unable to map GPIO register memory"); + exit(1); + } + + set_direction_out(CRONUS_SEL); + set_direction_out(FSI_ENABLE); + set_direction_out(FSI_DAT_EN); + + write_gpio(FSI_ENABLE, 1); + write_gpio(CRONUS_SEL, 1); //Set Cronus control to BMC + + slave = slave_id; + backend->getscom = fsi_getscom; + backend->putscom = fsi_putscom; + backend->getcfam = fsi_getcfam; + backend->putcfam = fsi_putcfam; + backend->destroy = fsi_destroy; + backend->priv = NULL; + + fsi_break(); + + /* Make sure the FSI2PIB engine is in a good state */ + if (fsi_putcfam(backend, FSI_SET_PIB_RESET, FSI_SET_PIB_RESET_REG)) + return NULL; + + return backend; +} + +void fsi_destroy(struct scom_backend *backend) +{ + /* Clean up in case we busted the bus */ + fsi_break(); + + write_gpio(FSI_ENABLE, 1); + write_gpio(CRONUS_SEL, 0); //Set Cronus control to FSP2 +} diff --git a/libpdbg/bmcfsi.h b/libpdbg/bmcfsi.h new file mode 100644 index 0000000..8635b07 --- /dev/null +++ b/libpdbg/bmcfsi.h @@ -0,0 +1,26 @@ +/* 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. + */ +#ifndef __BMCFSI_H +#define __BMCFSI_H + +/* We only support slave 0 at the moment */ +#define SLAVE_ID 0x0 + +/* backend initialisation */ +struct scom_backend *fsi_init(int slave_id); +void fsi_destroy(struct scom_backend *backend); + +#endif diff --git a/libpdbg/operations.h b/libpdbg/operations.h new file mode 100644 index 0000000..5e4bdda --- /dev/null +++ b/libpdbg/operations.h @@ -0,0 +1,70 @@ +/* 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. + */ +#ifndef __OPERATIONS_H +#define __OPERATIONS_H + +/* Error codes */ +#define EFSI 1 + +#define PR_DEBUG(x, args...) \ + //fprintf(stderr, x, ##args) +#define PR_INFO(x, args...) \ + fprintf(stderr, x, ##args) +#define PR_ERROR(x, args...) \ + fprintf(stderr, "%s: " x, __FUNCTION__, ##args) + +#define CHECK_ERR(x) do { \ + if (x) { \ + PR_DEBUG("%s: %d\n", __FUNCTION__, __LINE__); \ + return -EFSI; \ + } \ + } while(0) + +#define THREADS_PER_CORE 8 + +/* Structure to allow alternative backend implentations */ +struct scom_backend { + void (*destroy)(struct scom_backend *backend); + int (*getscom)(struct scom_backend *backend, uint64_t *value, uint32_t addr); + int (*putscom)(struct scom_backend *backend, uint64_t value, uint32_t addr); + int (*getcfam)(struct scom_backend *backend, uint32_t *value, uint32_t addr); + int (*putcfam)(struct scom_backend *backend, uint32_t value, uint32_t addr); + void *priv; +}; + +/* Alter display unit functions */ +int adu_getmem(uint64_t addr, uint8_t *output, uint64_t size, int block_size); + +/* Functions to ram instructions */ +int ram_getgpr(int chip, int thread, int gpr, uint64_t *value); +int ram_getnia(int chip, int thread, uint64_t *value); +int ram_getspr(int chip, int thread, int spr, uint64_t *value); +int ram_running_threads(uint64_t chip, uint64_t *active_threads); +int ram_stop_chip(uint64_t chip, uint64_t *thread_active); +int ram_start_chip(uint64_t chip, uint64_t thread_active); + +/* GDB server functionality */ +int gdbserver_start(uint16_t port); + +/* scom backend functions. Most other operations use these. */ +int backend_init(int slave_id); +void backend_destroy(void); +int getscom(uint64_t *value, uint32_t addr); +int putscom(uint64_t value, uint32_t addr); +int getcfam(uint32_t *value, uint32_t addr); +int putcfam(uint32_t value, uint32_t addr); + +#endif diff --git a/libpdbg/ram.c b/libpdbg/ram.c new file mode 100644 index 0000000..4d9954b --- /dev/null +++ b/libpdbg/ram.c @@ -0,0 +1,306 @@ +/* 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 +#include +#include +#include +#include + +#include "operations.h" +#include "bitutils.h" + +#define RAS_STATUS_TIMEOUT 100 + +#define DIRECT_CONTROLS_REG 0x10013000 +#define DIRECT_CONTROL_SP_START PPC_BIT(62) +#define DIRECT_CONTROL_SP_STOP PPC_BIT(63) +#define RAS_STATUS_REG 0x10013002 +#define RAS_STATUS_SRQ_EMPTY PPC_BIT(8) +#define RAS_STATUS_NCU_QUIESCED PPC_BIT(10) +#define RAS_STATUS_TS_QUIESCE PPC_BIT(49) +#define THREAD_ACTIVE_REG 0x1001310E +#define THREAD_ACTIVE PPC_BITMASK(0, 7) +#define RAM_THREAD_ACTIVE PPC_BITMASK(8, 15) +#define SPR_MODE_REG 0x10013281 +#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 0x10013280 +#define SCOM_SPRC_SCRATCH_SPR 0x40 +#define SCR0_REG 0x10013283 +#define RAM_MODE_REG 0x10013c00 +#define RAM_MODE_ENABLE PPC_BIT(0) +#define RAM_CTRL_REG 0x10013c01 +#define RAM_THREAD_SELECT PPC_BITMASK(0, 2) +#define RAM_INSTR PPC_BITMASK(3, 34) +#define RAM_STATUS_REG 0x10013c02 +#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 PMSPCWKUPFSP_REG 0x100f010b +#define FSP_SPECIAL_WAKEUP PPC_BIT(0) + +/* Opcodes */ +#define MFNIA_OPCODE 0x00000004UL +#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); +} + +/* + * Return a mask of currently active threads. + */ +int ram_running_threads(uint64_t chip, uint64_t *running_threads) +{ + uint64_t addr, val; + int thread; + + chip <<= 24; + *running_threads = 0; + + for (thread = 0; thread < THREADS_PER_CORE; thread++) { + /* Wait for thread to quiese */ + addr = RAS_STATUS_REG | chip; + addr |= thread << 4; + CHECK_ERR(getscom(&val, addr)); + + if (!((val & RAS_STATUS_SRQ_EMPTY) + && (val & RAS_STATUS_TS_QUIESCE))) + *running_threads |= 1 << (THREADS_PER_CORE - thread - 1); + } + return 0; +} + +/* + * Stop active threads on the chip. Returns the active_threads in + * *active_threads. This should be passed to ram_start_chip() to + * restart the stopped threads. + */ +int ram_stop_chip(uint64_t chip, uint64_t *active_threads) +{ + int i = 0, thread; + uint64_t addr, val, thread_active; + + chip <<= 24; + + /* Assert special wakeup to prevent low power states */ + addr = PMSPCWKUPFSP_REG | chip; + CHECK_ERR(putscom(FSP_SPECIAL_WAKEUP, addr)); + + /* Read active threads */ + addr = THREAD_ACTIVE_REG | chip; + CHECK_ERR(getscom(&val, addr)); + thread_active = GETFIELD(THREAD_ACTIVE, val); + + for (thread = 0; thread < THREADS_PER_CORE; thread++) { + /* Skip inactive threads (stupid IBM bit ordering) */ + if (!(thread_active & (1 << (THREADS_PER_CORE - thread - 1)))) + continue; + + /* Quiese active thread */ + addr = DIRECT_CONTROLS_REG | chip; + addr |= thread << 4; + val = DIRECT_CONTROL_SP_STOP; + CHECK_ERR(putscom(val, addr)); + + /* Wait for thread to quiese */ + addr = RAS_STATUS_REG | chip; + addr |= thread << 4; + i = 0; + do { + CHECK_ERR(getscom(&val, addr)); + if (i++ > RAS_STATUS_TIMEOUT) { + PR_ERROR("Unable to quiesce thread %d (0x%016llx).\n", + thread, val); + PR_ERROR("Continuing anyway.\n"); + break; + } + } while (!((val & RAS_STATUS_SRQ_EMPTY) + /* + * Workbook says check for bit 10 but it + * never seems to get set and everything + * still works as expected. + */ + /* && (val & RAS_STATUS_NCU_QUIESCED) */ + && (val & RAS_STATUS_TS_QUIESCE))); + } + + /* Make the RAM threads active */ + addr = THREAD_ACTIVE_REG | chip; + CHECK_ERR(getscom(&val, addr)); + val = SETFIELD(RAM_THREAD_ACTIVE, val, (1 << THREADS_PER_CORE) - 1); + CHECK_ERR(putscom(val, addr)); + + /* Activate RAM mode */ + addr = RAM_MODE_REG | chip; + CHECK_ERR(getscom(&val, addr)); + val |= RAM_MODE_ENABLE; + CHECK_ERR(putscom(val, addr)); + + *active_threads = thread_active; + return 0; +} + +int ram_start_chip(uint64_t chip, uint64_t thread_active) +{ + uint64_t addr, val; + int thread; + + chip <<= 24; + + /* De-activate RAM mode */ + addr = RAM_MODE_REG | chip; + CHECK_ERR(putscom(0, addr)); + + /* Start cores which were active */ + for (thread = 0; thread < THREADS_PER_CORE; thread++) { + if (!(thread_active & (1 << (THREADS_PER_CORE - thread - 1)))) + continue; + + /* Activate thread */ + addr = DIRECT_CONTROLS_REG | chip; + addr |= thread << 4; + val = DIRECT_CONTROL_SP_START; + CHECK_ERR(putscom(val, addr)); + } + + /* De-assert special wakeup to enable low power states */ + addr = PMSPCWKUPFSP_REG | chip; + CHECK_ERR(putscom(0, addr)); + + 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. Note that only register r0 is saved and restored so + * opcodes must not touch other registers. + */ +static int ram_instructions(uint64_t *opcodes, uint64_t *results, int len, + uint64_t chip, unsigned int lpar, uint64_t thread) +{ + uint64_t addr, val, opcode, r0 = 0; + int i; + + /* Setup SPRC to use SPRD */ + chip <<= 24; + addr = SPR_MODE_REG | chip; + 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)); + CHECK_ERR(putscom(val, addr)); + + addr = L0_SCOM_SPRC_REG | chip; + val = SCOM_SPRC_SCRATCH_SPR; + CHECK_ERR(putscom(val, addr)); + + /* RAM instructions */ + addr = RAM_CTRL_REG | chip; + 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) + opcode = opcodes[i]; + else if (i >= len) { + /* Restore r0 */ + CHECK_ERR(putscom(r0, SCR0_REG | chip)); + opcode = mfspr(0, 277); + } + + /* ram instruction */ + val = SETFIELD(RAM_THREAD_SELECT, 0ULL, thread); + val = SETFIELD(RAM_INSTR, val, opcode); + CHECK_ERR(putscom(val, addr)); + + /* wait for completion */ + do { + CHECK_ERR(getscom(&val, RAM_STATUS_REG | chip)); + } while (!val); + + if (!(val & RAM_STATUS)) + PR_ERROR("RAMMING failed with status 0x%llx\n", val); + + /* Save the results */ + CHECK_ERR(getscom(&val, SCR0_REG | chip)); + if (i < 0) + r0 = val; + else if (i < len) + results[i] = val; + } + + return 0; +} + +/* + * Get gpr value. Chip must be stopped. + */ +int ram_getgpr(int chip, int thread, int gpr, uint64_t *value) +{ + uint64_t opcodes[] = {mtspr(277, gpr)}; + uint64_t results[] = {0}; + + CHECK_ERR(ram_instructions(opcodes, results, ARRAY_SIZE(opcodes), + chip, 0, thread)); + *value = results[0]; + return 0; +} + +int ram_getnia(int chip, int thread, uint64_t *value) +{ + uint64_t opcodes[] = {mfnia(0), mtspr(277, 0)}; + uint64_t results[] = {0, 0}; + + CHECK_ERR(ram_instructions(opcodes, results, ARRAY_SIZE(opcodes), + chip, 0, thread)); + *value = results[0]; + return 0; +} + +int ram_getspr(int chip, int thread, int spr, uint64_t *value) +{ + uint64_t opcodes[] = {mfspr(0, spr), mtspr(277, 0)}; + uint64_t results[] = {0, 0}; + + CHECK_ERR(ram_instructions(opcodes, results, ARRAY_SIZE(opcodes), + chip, 0, thread)); + *value = results[1]; + return 0; +} diff --git a/libpdbg/scom.c b/libpdbg/scom.c new file mode 100644 index 0000000..2e4d93b --- /dev/null +++ b/libpdbg/scom.c @@ -0,0 +1,77 @@ +/* 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 +#include + +#include "bmcfsi.h" +#include "operations.h" + +struct scom_backend *backend = NULL; + +int backend_init(int slave_id) +{ + backend = fsi_init(slave_id); + if (!backend) + return -1; + + return 0; +} + +void backend_destroy(void) +{ + if (!backend || backend->destroy) + backend->destroy(backend); +} + +int getscom(uint64_t *value, uint32_t addr) +{ + if (!backend || !backend->getscom) { + PR_ERROR("Backend does not support %s\n", __FUNCTION__); + return -1; + } + + return backend->getscom(backend, value, addr); +} + +int putscom(uint64_t value, uint32_t addr) +{ + if (!backend || !backend->getscom) { + PR_ERROR("Backend does not support %s\n", __FUNCTION__); + return -1; + } + + return backend->putscom(backend, value, addr); +} + +int getcfam(uint32_t *value, uint32_t addr) +{ + if (!backend || !backend->getscom) { + PR_ERROR("Backend does not support %s\n", __FUNCTION__); + return -1; + } + + return backend->getcfam(backend, value, addr); +} + +int putcfam(uint32_t value, uint32_t addr) +{ + if (!backend || !backend->putscom) { + PR_ERROR("Backend does not support %s\n", __FUNCTION__); + return -1; + } + + return backend->putcfam(backend, value, addr); +} -- cgit v1.2.1