diff options
author | Jeremy Kerr <jk@ozlabs.org> | 2015-02-27 17:11:06 +0800 |
---|---|---|
committer | Stewart Smith <stewart@linux.vnet.ibm.com> | 2015-03-04 13:08:00 +1100 |
commit | 7c3b463e98590ddb9c01e6e88057eca00da28cad (patch) | |
tree | 592d26553275980dbed690b3a9ba936f2de64941 /external/opal-prd/opal-prd.c | |
parent | 9ad75c41bd92ab33c1131a1e028e85e1e1a59098 (diff) | |
download | blackbird-skiboot-7c3b463e98590ddb9c01e6e88057eca00da28cad.tar.gz blackbird-skiboot-7c3b463e98590ddb9c01e6e88057eca00da28cad.zip |
external/opal-prd: Add userspace support for PRD facility
This change adds an application in external/opal-prd, implementing the
userspace portion of a PRD stack.
This code is responsible for loading the HBRT code from reserved memory,
and provding hostboot runtime functionality through a set of
callbacks.
Because we may be running little-endian (and expect the HBRT code to be
big-endian), we need to thunk the endianness between calls through the
HBRT interface.
Includes multiple contributions from:
Joel Stanley <joel@jms.id.au>
Vaidyanathan Srinivasan <svaidy@linux.vnet.ibm.com>
Benjamin Herrenschmidt <benh@kernel.crashing.org
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
Signed-off-by: Vaidyanathan Srinivasan <svaidy@linux.vnet.ibm.com>
Signed-off-by: Joel Stanley <joel@jms.id.au>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
[stewart@linux.vnet.ibm.com: squash trailing whitespace]
Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
Diffstat (limited to 'external/opal-prd/opal-prd.c')
-rw-r--r-- | external/opal-prd/opal-prd.c | 1206 |
1 files changed, 1206 insertions, 0 deletions
diff --git a/external/opal-prd/opal-prd.c b/external/opal-prd/opal-prd.c new file mode 100644 index 00000000..402820ad --- /dev/null +++ b/external/opal-prd/opal-prd.c @@ -0,0 +1,1206 @@ +/* Copyright 2014-2015 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 + * imitations under the License. + */ + +#include <assert.h> +#include <stdio.h> +#include <unistd.h> +#include <getopt.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <sys/stat.h> +#include <errno.h> +#include <stdbool.h> +#include <stdarg.h> +#include <time.h> +#include <err.h> +#include <poll.h> + +#include <endian.h> + +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <linux/ipmi.h> +#include <linux/limits.h> + +#include <asm/opal-prd.h> +#include <opal.h> + +#include "hostboot-interface.h" +#include "pnor.h" +#include "i2c.h" + +struct opal_prd_ctx { + int fd; + int socket; + struct opal_prd_info info; + long page_size; + void *code_addr; + size_t code_size; + bool debug; + bool allow_fsp_calls; + struct pnor pnor; + char *hbrt_file_name; +}; + +enum control_msg_type { + CONTROL_MSG_ENABLE_OCCS = 0x00, + CONTROL_MSG_DISABLE_OCCS = 0x01, + CONTROL_MSG_TEMP_OCC_RESET = 0x02, + CONTROL_MSG_TEMP_OCC_ERROR = 0x03, +}; + +struct control_msg { + enum control_msg_type type; + uint64_t response; +}; + +static struct opal_prd_ctx *ctx; + +static const char *opal_prd_devnode = "/dev/opal-prd"; +static const char *opal_prd_socket = "/run/opal-prd-control"; +static const char *hbrt_code_region_name = "ibm,hbrt-code-image"; +static const int opal_prd_version = 1; +static const uint64_t opal_prd_ipoll = 0xf000000000000000; + +static const char *ipmi_devnode = "/dev/ipmi0"; +static const int ipmi_timeout_ms = 2000; + +/* Memory error handling */ +static const char *mem_offline_soft = + "/sys/devices/system/memory/soft_offline_page"; +static const char *mem_offline_hard = + "/sys/devices/system/memory/hard_offline_page"; + +#define ADDR_STRING_SZ 20 /* Hold %16lx */ + +/* This is the "real" HBRT call table for calling into HBRT as + * provided by it. It will be used by the assembly thunk + */ +struct runtime_interfaces *hservice_runtime; +struct runtime_interfaces hservice_runtime_fixed; + +/* This is the callback table provided by assembly code */ +extern struct host_interfaces hinterface; + +/* Create opd to call hostservice init */ +struct func_desc { + void *addr; + void *toc; +} hbrt_entry; + +static struct opal_prd_range *find_range(const char *name) +{ + struct opal_prd_range *range; + unsigned int i; + + for (i = 0; i < OPAL_PRD_MAX_RANGES; i++) { + range = &ctx->info.ranges[i]; + + if (!strncmp(range->name, name, sizeof(range->name))) + return range; + } + + return NULL; +} + +static void pr_debug(struct opal_prd_ctx *ctx, const char *fmt, ...) +{ + va_list ap; + + if (!ctx->debug) + return; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +/* HBRT init wrappers */ +extern struct runtime_interfaces *call_hbrt_init(struct host_interfaces *); + +/* hservice Call wrappers */ + +extern void call_cxxtestExecute(void *); +extern int call_handle_attns(uint64_t i_proc, + uint64_t i_ipollStatus, + uint64_t i_ipollMask); +extern void call_process_occ_error (uint64_t i_chipId); +extern int call_enable_attns(void); +extern int call_enable_occ_actuation(bool i_occActivation); +extern void call_process_occ_reset(uint64_t i_chipId); + +/* Dummy calls for hservices */ +static inline int __fsp_only_assert(const char *name) +{ + printf("error: %s is only implemented for FSP\n", name); + if (!ctx->allow_fsp_calls) + exit(EXIT_FAILURE); + return 0; +} +#define fsp_stub(name) \ + int hservice_ ##name(void) { return __fsp_only_assert(#name); } + +fsp_stub(send_error_log); +fsp_stub(lid_load); +fsp_stub(lid_unload); +fsp_stub(wakeup); +fsp_stub(report_occ_failure); + +void hservice_puts(const char *str) +{ + printf("%s\n", str); +} + +void hservice_assert(void) +{ + fprintf(stderr, "ERR: assert! exiting.\n"); + exit(EXIT_FAILURE); +} + +void *hservice_malloc(size_t size) +{ + return malloc(size); +} + +void hservice_free(void *ptr) +{ + free(ptr); +} + +void *hservice_realloc(void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +int hservice_scom_read(uint64_t chip_id, uint64_t addr, void *buf) +{ + int rc; + struct opal_prd_scom scom; + + scom.chip = chip_id; + scom.addr = addr; + + rc = ioctl(ctx->fd, OPAL_PRD_SCOM_READ, &scom); + if (rc) { + perror("ioctl scom_read"); + return 0; + } + + pr_debug(ctx, "scom read: chip %lx addr %lx val %lx\n", + chip_id, addr, scom.data); + + *(uint64_t *)buf = htobe64(scom.data); + + return 0; +} + +int hservice_scom_write(uint64_t chip_id, uint64_t addr, + const void *buf) +{ + int rc; + struct opal_prd_scom scom; + + scom.chip = chip_id; + scom.addr = addr; + scom.data = be64toh(*(uint64_t *)buf); + + rc = ioctl(ctx->fd, OPAL_PRD_SCOM_WRITE, &scom); + if (rc) { + perror("ioctl scom_write"); + return 0; + } + + pr_debug(ctx, "scom write: chip %lx addr %lx val %lx\n", + chip_id, addr, scom.data); + + return 0; +} + +uint64_t hservice_get_reserved_mem(const char *name) +{ + uint64_t align_physaddr, offset; + struct opal_prd_range *range; + void *addr; + + pr_debug(ctx, "hservice_get_reserved_mem: %s\n", name); + + range = find_range(name); + if (!range) { + warnx("get_reserved_mem: no such range %s", name); + return 0; + } + + pr_debug(ctx, "Mapping 0x%016lx 0x%08lx %s\n", + range->physaddr, range->size, range->name); + + align_physaddr = range->physaddr & ~(ctx->page_size-1); + offset = range->physaddr & (ctx->page_size-1); + addr = mmap(NULL, range->size, PROT_WRITE | PROT_READ, + MAP_SHARED, ctx->fd, align_physaddr); + + if (addr == MAP_FAILED) { + perror("mmap"); + return 0; + } + + pr_debug(ctx, "hservice_get_reserved_mem: %s address %p\n", name, addr); + if (addr) + return (uint64_t)addr + offset; + + return 0; +} + +void hservice_nanosleep(uint64_t i_seconds, uint64_t i_nano_seconds) +{ + const struct timespec ns = { + .tv_sec = i_seconds, + .tv_nsec = i_nano_seconds + }; + + nanosleep(&ns, NULL); +} + +int hservice_set_page_execute(void *addr) +{ + pr_debug(ctx, "FIXME: hservice_set_page_execute(%p)\n", addr); + return -1; +} + +int hservice_clock_gettime(clockid_t i_clkId, struct timespec *o_tp) +{ + struct timespec tmp; + int rc; + + rc = clock_gettime(i_clkId, &tmp); + if (rc) + return rc; + + o_tp->tv_sec = htobe64(tmp.tv_sec); + o_tp->tv_nsec = htobe64(tmp.tv_nsec); + + return 0; +} + +int hservice_pnor_read(uint32_t i_proc, const char* i_partitionName, + uint64_t i_offset, void* o_data, size_t i_sizeBytes) +{ + return pnor_operation(&ctx->pnor, i_partitionName, i_offset, o_data, + i_sizeBytes, PNOR_OP_READ); +} + +int hservice_pnor_write(uint32_t i_proc, const char* i_partitionName, + uint64_t i_offset, void* o_data, size_t i_sizeBytes) +{ + return pnor_operation(&ctx->pnor, i_partitionName, i_offset, o_data, + i_sizeBytes, PNOR_OP_WRITE); +} + +int hservice_i2c_read(uint64_t i_master, uint8_t i_engine, uint8_t i_port, + uint16_t i_devAddr, uint32_t i_offsetSize, uint32_t i_offset, + uint32_t i_length, void* o_data) +{ + uint32_t chip_id; + uint8_t engine, port; + + chip_id = (i_master & HBRT_I2C_MASTER_CHIP_MASK) >> + HBRT_I2C_MASTER_CHIP_SHIFT; + engine = (i_master & HBRT_I2C_MASTER_ENGINE_MASK) >> + HBRT_I2C_MASTER_ENGINE_SHIFT; + port = (i_master & HBRT_I2C_MASTER_PORT_MASK) >> + HBRT_I2C_MASTER_PORT_SHIFT; + return i2c_read(chip_id, engine, port, i_devAddr, i_offsetSize, + i_offset, i_length, o_data); +} + +int hservice_i2c_write(uint64_t i_master, uint8_t i_engine, uint8_t i_port, + uint16_t i_devAddr, uint32_t i_offsetSize, uint32_t i_offset, + uint32_t i_length, void* i_data) +{ + uint32_t chip_id; + uint8_t engine, port; + + chip_id = (i_master & HBRT_I2C_MASTER_CHIP_MASK) >> + HBRT_I2C_MASTER_CHIP_SHIFT; + engine = (i_master & HBRT_I2C_MASTER_ENGINE_MASK) >> + HBRT_I2C_MASTER_ENGINE_SHIFT; + port = (i_master & HBRT_I2C_MASTER_PORT_MASK) >> + HBRT_I2C_MASTER_PORT_SHIFT; + return i2c_write(chip_id, engine, port, i_devAddr, i_offsetSize, + i_offset, i_length, i_data); +} + +static int ipmi_send(int fd, uint8_t netfn, uint8_t cmd, long seq, + uint8_t *buf, size_t len) +{ + struct ipmi_system_interface_addr addr; + struct ipmi_req req; + int rc; + + memset(&addr, 0, sizeof(addr)); + addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + addr.channel = IPMI_BMC_CHANNEL; + + memset(&req, 0, sizeof(req)); + req.addr = (unsigned char *)&addr; + req.addr_len = sizeof(addr); + + req.msgid = seq; + req.msg.netfn = netfn; + req.msg.cmd = cmd; + req.msg.data = buf; + req.msg.data_len = len; + + rc = ioctl(fd, IPMICTL_SEND_COMMAND, &req); + if (rc < 0) + return -1; + + return 0; +} + +static int ipmi_recv(int fd, uint8_t *netfn, uint8_t *cmd, long *seq, + uint8_t *buf, size_t *len) +{ + struct ipmi_recv recv; + struct ipmi_addr addr; + int rc; + + recv.addr = (unsigned char *)&addr; + recv.addr_len = sizeof(addr); + recv.msg.data = buf; + recv.msg.data_len = *len; + + rc = ioctl(fd, IPMICTL_RECEIVE_MSG_TRUNC, &recv); + if (rc < 0 && errno != EMSGSIZE) { + warn("IPMI: recv (%zd bytes) failed: %m\n", *len); + return -1; + } else if (rc < 0 && errno == EMSGSIZE) { + warn("IPMI: truncated message (netfn %d, cmd %d, " + "size %zd), continuing anyway\n", + recv.msg.netfn, recv.msg.cmd, *len); + } + + *netfn = recv.msg.netfn; + *cmd = recv.msg.cmd; + *seq = recv.msgid; + *len = recv.msg.data_len; + + return 0; +} + +int hservice_ipmi_msg(uint8_t netfn, uint8_t cmd, + void *tx_buf, size_t tx_size, + void *rx_buf, size_t *rx_size) +{ + struct timeval start, now, delta; + struct pollfd pollfds[1]; + static long seq; + size_t size; + int rc, fd; + + size = be64toh(*rx_size); + + fd = open(ipmi_devnode, O_RDWR); + if (fd < 0) { + warn("Failed to open IPMI device %s", ipmi_devnode); + return -1; + } + + seq++; + pr_debug(ctx, "IPMI: sending %zd bytes (netfn 0x%02x, cmd 0x%02x)\n", + tx_size, netfn, cmd); + + rc = ipmi_send(fd, netfn, cmd, seq, tx_buf, tx_size); + if (rc) { + warnx("IPMI: send failed"); + goto out; + } + + gettimeofday(&start, NULL); + + pollfds[0].fd = fd; + pollfds[0].events = POLLIN; + + for (;;) { + long rx_seq; + int timeout; + + gettimeofday(&now, NULL); + timersub(&now, &start, &delta); + timeout = ipmi_timeout_ms - ((delta.tv_sec * 1000) + + (delta.tv_usec / 1000)); + if (timeout < 0) + timeout = 0; + + rc = poll(pollfds, 1, timeout); + if (rc < 0) { + warn("poll(%s)", ipmi_devnode); + break; + } + + if (rc == 0) { + warnx("IPMI response timeout"); + rc = -1; + break; + } + + rc = ipmi_recv(fd, &netfn, &cmd, &rx_seq, rx_buf, &size); + if (rc) + break; + + if (seq != rx_seq) { + pr_debug(ctx, "IPMI: out-of-sequence reply: %ld, " + "expected %ld. Dropping message.\n", + rx_seq, seq); + continue; + } + + pr_debug(ctx, "IPMI: received %zd bytes\n", tx_size); + *rx_size = be64toh(size); + rc = 0; + break; + } + +out: + close(fd); + return rc; +} + +int hservice_memory_error(uint64_t i_start_addr, uint64_t i_endAddr, + enum MemoryError_t i_errorType) +{ + char buf[ADDR_STRING_SZ]; + const char *sysfsfile; + int memfd, rc, n; + uint64_t addr; + + pr_debug(ctx, "Memory error addr:%016lx-%016lx type: %d\n", + i_start_addr, i_endAddr, i_errorType); + + switch(i_errorType) { + case MEMORY_ERROR_CE: + sysfsfile = mem_offline_soft; + break; + case MEMORY_ERROR_UE: + sysfsfile = mem_offline_hard; + break; + default: + warn("Invalid memory error type %d", i_errorType); + return -1; + } + + memfd = open(sysfsfile, O_WRONLY); + if (memfd < 0) { + warn("Unable to open sysfs: %s", sysfsfile); + return -1; + } + + for (addr = i_start_addr; addr <= i_endAddr; addr += ctx->page_size) { + n = snprintf(buf, ADDR_STRING_SZ, "0x%lx", addr); + rc = write(memfd, buf, n); + if (rc != n) { + warn("Memory offine of addr: %016lx type: %d failed", + addr, i_errorType); + return rc; + } + } + + return 0; +} + +void hservices_init(struct opal_prd_ctx *ctx, void *code) +{ + uint64_t *s, *d; + int i, sz; + + pr_debug(ctx, "Code Address : [%p]\n", code); + + /* We enter at 0x100 into the image. */ + /* Load func desc in BE since we reverse it in thunk */ + + hbrt_entry.addr = (void *)htobe64((unsigned long)code + 0x100); + hbrt_entry.toc = 0; /* No toc for init entry point */ + + if (memcmp(code, "HBRTVERS", 8) != 0) + errx(EXIT_FAILURE, "HBRT: Bad signature for " + "ibm,hbrt-code-image! exiting\n"); + + pr_debug(ctx, "HBRT: calling ibm,hbrt_init() %p\n", hservice_runtime); + hservice_runtime = call_hbrt_init(&hinterface); + pr_debug(ctx, "HBRT: hbrt_init passed..... %p version %016lx\n", + hservice_runtime, hservice_runtime->interface_version); + + sz = sizeof(struct runtime_interfaces)/sizeof(uint64_t); + s = (uint64_t *)hservice_runtime; + d = (uint64_t *)&hservice_runtime_fixed; + /* Byte swap the function pointers */ + for (i = 0; i < sz; i++) + d[i] = be64toh(s[i]); +} + +static void fixup_hinterface_table(void) +{ + uint64_t *t64; + unsigned int i, sz; + + /* Swap interface version */ + hinterface.interface_version = + htobe64(hinterface.interface_version); + + /* Swap OPDs */ + sz = sizeof(struct host_interfaces) / sizeof(uint64_t); + t64 = (uint64_t *)&hinterface; + for (i = 1; i < sz; i++) { + uint64_t *opd = (uint64_t *)t64[i]; + if (!opd) + continue; + t64[i] = htobe64(t64[i]); + opd[0] = htobe64(opd[0]); + opd[1] = htobe64(opd[1]); + opd[2] = htobe64(opd[2]); + } +} + +static int map_hbrt_file(struct opal_prd_ctx *ctx, const char *name) +{ + struct stat statbuf; + int fd, rc; + void *buf; + + fd = open(name, O_RDONLY); + if (fd < 0) { + warn("open(%s)", name); + return -1; + } + + rc = fstat(fd, &statbuf); + if (rc < 0) { + warn("fstat(%s)", name); + close(fd); + return -1; + } + + buf = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE, fd, 0); + close(fd); + + if (buf == MAP_FAILED) { + warn("mmap(%s)", name); + return -1; + } + + ctx->code_addr = buf; + ctx->code_size = statbuf.st_size; + return -0; +} + +static int map_hbrt_physmem(struct opal_prd_ctx *ctx, const char *name) +{ + struct opal_prd_range *range; + void *buf; + + range = find_range(name); + if (!range) { + warnx("can't find code region %s\n", name); + return -1; + } + + buf = mmap(NULL, range->size, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE, ctx->fd, range->physaddr); + if (buf == MAP_FAILED) { + warn("mmap(range:%s)\n", name); + return -1; + } + + ctx->code_addr = buf; + ctx->code_size = range->size; + return 0; +} + +static void dump_hbrt_map(struct opal_prd_ctx *ctx) +{ + const char *dump_name = "hbrt.bin"; + int fd, rc; + + if (!ctx->debug) + return; + + fd = open(dump_name, O_WRONLY | O_CREAT, 0644); + if (fd < 0) + err(EXIT_FAILURE, "couldn't open %s for writing", dump_name); + + ftruncate(fd, 0); + rc = write(fd, ctx->code_addr, ctx->code_size); + close(fd); + + if (rc != ctx->code_size) + warn("write to %s failed", dump_name); + else + pr_debug(ctx, "dumped HBRT binary to %s\n", dump_name); +} + +static int prd_init(struct opal_prd_ctx *ctx) +{ + int rc; + + ctx->page_size = sysconf(_SC_PAGE_SIZE); + + /* set up the device, and do our get_info ioctl */ + ctx->fd = open(opal_prd_devnode, O_RDWR); + if (ctx->fd < 0) { + warn("Can't open PRD device %s\n", opal_prd_devnode); + return -1; + } + + rc = ioctl(ctx->fd, OPAL_PRD_GET_INFO, &ctx->info); + if (rc) { + warn("Can't get PRD info"); + return -1; + } + + return 0; +} + +static int handle_msg_attn(struct opal_prd_ctx *ctx, struct opal_prd_msg *msg) +{ + uint64_t proc, ipoll_mask, ipoll_status; + int rc; + + proc = be64toh(msg->attn.proc); + ipoll_status = be64toh(msg->attn.ipoll_status); + ipoll_mask = be64toh(msg->attn.ipoll_mask); + + if (!hservice_runtime->handle_attns) { + fprintf(stderr, "no handle_attns call\n"); + return -1; + } + + rc = call_handle_attns(proc, ipoll_status, ipoll_mask); + if (rc) { + fprintf(stderr, "enable_attns(%lx,%lx,%lx) failed, rc %d", + proc, ipoll_status, ipoll_mask, rc); + return -1; + } + + /* send the response */ + msg->type = OPAL_PRD_MSG_TYPE_ATTN_ACK; + msg->attn_ack.proc = htobe64(proc); + msg->attn_ack.ipoll_ack = htobe64(ipoll_status); + rc = write(ctx->fd, msg, sizeof(*msg)); + + if (rc != sizeof(*msg)) { + warn("write(ATTN_ACK) failed"); + return -1; + } + + return 0; +} + +static int handle_msg_occ_error(struct opal_prd_ctx *ctx, + struct opal_prd_msg *msg) +{ + uint32_t proc; + + proc = be64toh(msg->occ_error.chip); + + if (!hservice_runtime->process_occ_error) { + fprintf(stderr, "no process_occ_error call\n"); + return -1; + } + + call_process_occ_error(proc); + return 0; +} + +static int handle_msg_occ_reset(struct opal_prd_ctx *ctx, + struct opal_prd_msg *msg) +{ + uint32_t proc; + + proc = be64toh(msg->occ_reset.chip); + + if (!hservice_runtime->process_occ_reset) { + fprintf(stderr, "no handle_reset call\n"); + return -1; + } + + call_process_occ_reset(proc); + return 0; +} + +static int handle_prd_msg(struct opal_prd_ctx *ctx) +{ + struct opal_prd_msg msg; + int rc; + + rc = read(ctx->fd, &msg, sizeof(msg)); + if (rc < 0 && errno == EAGAIN) + return -1; + + if (rc != sizeof(msg)) { + warn("read on opal prd device failed"); + return -1; + } + + switch (msg.type) { + case OPAL_PRD_MSG_TYPE_ATTN: + rc = handle_msg_attn(ctx, &msg); + break; + case OPAL_PRD_MSG_TYPE_OCC_RESET: + rc = handle_msg_occ_reset(ctx, &msg); + break; + case OPAL_PRD_MSG_TYPE_OCC_ERROR: + rc = handle_msg_occ_error(ctx, &msg); + break; + default: + warn("Invalid incoming message type 0x%x\n", msg.type); + return -1; + } + + return 0; +} + +static int handle_prd_control(struct opal_prd_ctx *ctx, int fd) +{ + struct control_msg msg; + bool enabled; + int rc; + + rc = recv(fd, &msg, sizeof(msg), MSG_TRUNC); + if (rc != sizeof(msg)) { + warn("recvfrom"); + return -1; + } + + enabled = false; + rc = -1; + + switch (msg.type) { + case CONTROL_MSG_ENABLE_OCCS: + enabled = true; + /* fall through */ + case CONTROL_MSG_DISABLE_OCCS: + if (!hservice_runtime->enable_occ_actuation) { + fprintf(stderr, "no enable_occ_actuation call\n"); + } else { + pr_debug(ctx, "calling enable_occ_actuation(%s)\n", + enabled ? "true" : "false"); + rc = call_enable_occ_actuation(enabled); + pr_debug(ctx, " -> %d\n", rc); + } + break; + case CONTROL_MSG_TEMP_OCC_RESET: + if (hservice_runtime->process_occ_reset) { + pr_debug(ctx, "calling process_occ_reset(0)\n"); + call_process_occ_reset(0); + rc = 0; + } else { + fprintf(stderr, "no process_occ_reset call\n"); + } + break; + case CONTROL_MSG_TEMP_OCC_ERROR: + if (hservice_runtime->process_occ_error) { + pr_debug(ctx, "calling process_occ_error(0)\n"); + call_process_occ_error(0); + rc = 0; + } else { + fprintf(stderr, "no process_occ_error call\n"); + } + break; + default: + fprintf(stderr, "Unknown control message action %d", + msg.type); + } + + /* send a response */ + msg.response = rc; + rc = send(fd, &msg, sizeof(msg), MSG_DONTWAIT | MSG_NOSIGNAL); + if (rc && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EPIPE)) + pr_debug(ctx, "control send() returned %d, ignoring failure\n", + rc); + else if (rc != sizeof(msg)) + warn("control socket send failed"); + + return 0; +} + +static int run_attn_loop(struct opal_prd_ctx *ctx) +{ + struct pollfd pollfds[2]; + struct opal_prd_msg msg; + int rc, fd; + + if (hservice_runtime->enable_attns) { + pr_debug(ctx, "calling enable_attns()\n"); + rc = call_enable_attns(); + if (rc) { + fprintf(stderr, "enable_attns() failed, aborting\n"); + return -1; + } + } + + /* send init message, to unmask interrupts */ + msg.type = OPAL_PRD_MSG_TYPE_INIT; + msg.init.version = htobe64(opal_prd_version); + msg.init.ipoll = htobe64(opal_prd_ipoll); + + pr_debug(ctx, "writing init message\n"); + rc = write(ctx->fd, &msg, sizeof(msg)); + if (rc != sizeof(msg)) { + warn("init message failed, aborting"); + return -1; + } + + pollfds[0].fd = ctx->fd; + pollfds[0].events = POLLIN | POLLERR; + pollfds[1].fd = ctx->socket; + pollfds[1].events = POLLIN | POLLERR; + + for (;;) { + rc = poll(pollfds, 2, -1); + if (rc < 0) + err(EXIT_FAILURE, "poll"); + + if (!rc) + continue; + + if (pollfds[0].revents & POLLIN) + handle_prd_msg(ctx); + + if (pollfds[1].revents & POLLIN) { + fd = accept(ctx->socket, NULL, NULL); + if (fd < 0) { + warn("accept"); + continue; + } + handle_prd_control(ctx, fd); + close(fd); + } + } + + return 0; +} + +static int init_control_socket(struct opal_prd_ctx *ctx) +{ + struct sockaddr_un addr; + int fd, rc; + + unlink(opal_prd_socket); + + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, opal_prd_socket); + + fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (fd < 0) { + warn("Can't open control socket %s", opal_prd_socket); + return -1; + } + + rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rc) { + warn("Can't bind control socket %s", opal_prd_socket); + close(fd); + return -1; + } + + rc = listen(fd, 0); + if (rc) { + warn("Can't listen on control socket %s", opal_prd_socket); + close(fd); + return -1; + } + + ctx->socket = fd; + return 0; +} + +static int run_prd_daemon(struct opal_prd_ctx *ctx) +{ + int rc; + + ctx->fd = -1; + ctx->socket = -1; + + i2c_init(); + +#ifdef DEBUG_I2C + { + uint8_t foo[128]; + int i; + + rc = i2c_read(0, 1, 2, 0x50, 2, 0x10, 128, foo); + printf("read rc: %d\n", rc); + for (i = 0; i < sizeof(foo); i += 8) { + printf("%02x %02x %02x %02x %02x %02x %02x %02x\n", + foo[i + 0], foo[i + 1], foo[i + 2], foo[i + 3], + foo[i + 4], foo[i + 5], foo[i + 6], foo[i + 7]); + } + } +#endif + rc = init_control_socket(ctx); + if (rc) { + warnx("Error initialising PRD control"); + goto out_close; + } + + + rc = prd_init(ctx); + if (rc) { + warnx("Error initialising PRD setup"); + goto out_close; + } + + + if (ctx->hbrt_file_name) { + rc = map_hbrt_file(ctx, ctx->hbrt_file_name); + if (rc) { + warnx("can't access hbrt file %s", ctx->hbrt_file_name); + goto out_close; + } + } else { + rc = map_hbrt_physmem(ctx, hbrt_code_region_name); + if (rc) { + warn("can't access hbrt physical memory"); + goto out_close; + } + dump_hbrt_map(ctx); + } + + pr_debug(ctx, "hbrt map at %p, size 0x%zx\n", + ctx->code_addr, ctx->code_size); + + fixup_hinterface_table(); + + pr_debug(ctx, "calling hservices_init\n"); + hservices_init(ctx, ctx->code_addr); + pr_debug(ctx, "hservices_init done\n"); + + if (ctx->pnor.path) { + rc = pnor_init(&ctx->pnor); + if (rc) { + printf("Failed to open pnor\n"); + goto out_close; + } + } + + /* Test a scom */ + if (ctx->debug) { + uint64_t val; + printf("trying scom read\n"); + fflush(stdout); + hservice_scom_read(0x00, 0xf000f, &val); + printf("f00f: %lx\n", be64toh(val)); + } + + run_attn_loop(ctx); + rc = 0; + +out_close: + pnor_close(&ctx->pnor); + if (ctx->fd != -1) + close(ctx->fd); + if (ctx->socket != -1) + close(ctx->socket); + return rc; +} + +static int send_occ_control(struct opal_prd_ctx *ctx, const char *str) +{ + struct sockaddr_un addr; + struct control_msg msg; + int sd, rc; + + memset(&msg, 0, sizeof(msg)); + + if (!strcmp(str, "enable")) { + msg.type = CONTROL_MSG_ENABLE_OCCS; + } else if (!strcmp(str, "disable")) { + msg.type = CONTROL_MSG_DISABLE_OCCS; + } else if (!strcmp(str, "reset")) { + msg.type = CONTROL_MSG_TEMP_OCC_RESET; + } else if (!strcmp(str, "process-error")) { + msg.type = CONTROL_MSG_TEMP_OCC_ERROR; + } else { + fprintf(stderr, "Invalid OCC action '%s'\n", str); + return -1; + } + + sd = socket(AF_UNIX, SOCK_STREAM, 0); + if (!sd) { + warn("Failed to create control socket"); + return -1; + } + + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, opal_prd_socket); + + rc = connect(sd, (struct sockaddr *)&addr, sizeof(addr)); + if (rc) { + warn("Failed to connect to prd daemon"); + goto out_close; + } + + rc = send(sd, &msg, sizeof(msg), 0); + if (rc != sizeof(msg)) { + warn("send"); + rc = -1; + goto out_close; + } + + /* wait for our reply */ + rc = recv(sd, &msg, sizeof(msg), 0); + if (rc < 0) { + warn("control socket receive failed"); + goto out_close; + + } else if (rc != sizeof(msg)) { + warnx("short read from control socket"); + rc = -1; + goto out_close; + } + + if (msg.response || ctx->debug) { + warnx("OCC action %s returned status %ld\n", + str, msg.response); + } + + rc = msg.response; + +out_close: + close(sd); + return rc; +} + +static void usage(const char *progname) +{ + printf("Usage:\n"); + printf("\t%s [--debug] [--file <hbrt-image>] [--pnor <device>]\n" + "\t\t[--allow-fsp-calls]\n", + progname); + printf("\t%s occ <enable|disable>\n", progname); + printf("\n"); + printf("Options:\n" +"\t--debug verbose logging for debug information\n" +"\t--pnor DEVICE use PNOR MTD device\n" +"\t--file FILE use FILE for hostboot runtime code (instead of code\n" +"\t exported by firmware)\n" +"\t--allow-fsp-calls don't exit on FSP-only callbacks from HBRT code, but\n" +"\t return success instead. Intended for workarounds\n" +"\t during PRD testing only.\n"); +} + +static struct option opal_diag_options[] = { + {"file", required_argument, NULL, 'f'}, + {"pnor", required_argument, NULL, 'p'}, + {"debug", no_argument, NULL, 'd'}, + {"allow-fsp-calls", no_argument, NULL, 'a'}, + {"help", no_argument, NULL, 'h'}, + { 0 }, +}; + +enum action { + ACTION_RUN_DAEMON, + ACTION_OCC_CONTROL, +}; + +static int parse_action(const char *str, enum action *action) +{ + if (!strcmp(str, "occ")) { + *action = ACTION_OCC_CONTROL; + return 0; + } + + if (!strcmp(str, "daemon")) { + *action = ACTION_RUN_DAEMON; + return 0; + } + + fprintf(stderr, "unknown argument '%s'\n", str); + return -1; +} + +int main(int argc, char *argv[]) +{ + struct opal_prd_ctx _ctx; + enum action action; + int rc; + + ctx = &_ctx; + memset(ctx, 0, sizeof(*ctx)); + + /* Parse options */ + for (;;) { + int c; + + c = getopt_long(argc, argv, "f:p:dh", opal_diag_options, NULL); + if (c == -1) + break; + + switch (c) { + case 'f': + ctx->hbrt_file_name = optarg; + break; + case 'd': + ctx->debug = true; + break; + case 'p': + ctx->pnor.path = strndup(optarg, PATH_MAX); + break; + case 'a': + ctx->allow_fsp_calls = true; + break; + case 'h': + usage(argv[0]); + return EXIT_SUCCESS; + case '?': + default: + usage(argv[0]); + return EXIT_FAILURE; + } + } + + if (optind < argc) { + rc = parse_action(argv[optind], &action); + if (rc) + return EXIT_FAILURE; + } else { + action = ACTION_RUN_DAEMON; + } + + if (action == ACTION_RUN_DAEMON) { + rc = run_prd_daemon(ctx); + + } else if (action == ACTION_OCC_CONTROL) { + + if (optind + 1 >= argc) { + fprintf(stderr, "occ command requires an argument\n"); + return EXIT_FAILURE; + } + + rc = send_occ_control(ctx, argv[optind + 1]); + } + + return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + |