summaryrefslogtreecommitdiffstats
path: root/libpdbg
diff options
context:
space:
mode:
authorAlistair Popple <alistair@popple.id.au>2016-01-07 16:19:43 +1100
committerAlistair Popple <alistair@popple.id.au>2016-01-11 18:51:07 +1100
commit4c3473a4785336103cc47de95fddbbba12b12e7a (patch)
tree06c8c19e6c8494e8dc9695bec8bc7c446d930d95 /libpdbg
downloadpdbg-4c3473a4785336103cc47de95fddbbba12b12e7a.tar.gz
pdbg-4c3473a4785336103cc47de95fddbbba12b12e7a.zip
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 <alistair@popple.id.au>
Diffstat (limited to 'libpdbg')
-rw-r--r--libpdbg/adu.c216
-rw-r--r--libpdbg/bitutils.h51
-rw-r--r--libpdbg/bmcfsi.c517
-rw-r--r--libpdbg/bmcfsi.h26
-rw-r--r--libpdbg/operations.h70
-rw-r--r--libpdbg/ram.c306
-rw-r--r--libpdbg/scom.c77
7 files changed, 1263 insertions, 0 deletions
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 <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#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 <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <argp.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <time.h>
+
+#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 <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ccan/array_size/array_size.h>
+
+#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 <stdint.h>
+#include <stdio.h>
+
+#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);
+}
OpenPOWER on IntegriCloud