2717 lines
59 KiB
C
2717 lines
59 KiB
C
/* 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.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#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 <poll.h>
|
|
#include <dirent.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-api.h>
|
|
#include <types.h>
|
|
|
|
#include <ccan/list/list.h>
|
|
|
|
#include "opal-prd.h"
|
|
#include "hostboot-interface.h"
|
|
#include "module.h"
|
|
#include "pnor.h"
|
|
#include "i2c.h"
|
|
|
|
struct prd_range {
|
|
const char *name;
|
|
uint64_t physaddr;
|
|
uint64_t size;
|
|
void *buf;
|
|
bool multiple;
|
|
uint32_t instance;
|
|
};
|
|
|
|
struct prd_msgq_item {
|
|
struct list_node list;
|
|
struct opal_prd_msg msg;
|
|
};
|
|
|
|
struct opal_prd_ctx {
|
|
int fd;
|
|
int socket;
|
|
struct opal_prd_info info;
|
|
struct prd_range *ranges;
|
|
int n_ranges;
|
|
bool fw_range_instances;
|
|
long page_size;
|
|
void *code_addr;
|
|
size_t code_size;
|
|
bool debug;
|
|
struct pnor pnor;
|
|
char *hbrt_file_name;
|
|
bool use_syslog;
|
|
bool expert_mode;
|
|
struct list_head msgq;
|
|
struct opal_prd_msg *msg;
|
|
size_t msg_alloc_len;
|
|
void (*vlog)(int, const char *, va_list);
|
|
};
|
|
|
|
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,
|
|
CONTROL_MSG_ATTR_OVERRIDE = 0x04,
|
|
CONTROL_MSG_HTMGT_PASSTHRU = 0x05,
|
|
CONTROL_MSG_RUN_CMD = 0x30,
|
|
};
|
|
|
|
struct control_msg {
|
|
enum control_msg_type type;
|
|
int response;
|
|
union {
|
|
struct {
|
|
unsigned int argc;
|
|
} run_cmd;
|
|
struct {
|
|
uint64_t chip;
|
|
} occ_reset;
|
|
struct {
|
|
uint64_t chip;
|
|
} occ_error;
|
|
};
|
|
unsigned int data_len;
|
|
unsigned char data[];
|
|
|
|
};
|
|
|
|
#define MAX_CONTROL_MSG_BUF 4096
|
|
|
|
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 uint64_t opal_prd_ipoll = 0xf000000000000000;
|
|
|
|
static const int max_msgq_len = 16;
|
|
|
|
static const char *ipmi_devnode = "/dev/ipmi0";
|
|
static const int ipmi_timeout_ms = 5000;
|
|
|
|
static const char *devicetree_base =
|
|
"/sys/firmware/devicetree/base";
|
|
|
|
/* 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 int nr_chips;
|
|
static u64 chips[256];
|
|
|
|
static int read_prd_msg(struct opal_prd_ctx *ctx);
|
|
|
|
static struct prd_range *find_range(const char *name, uint32_t instance)
|
|
{
|
|
struct prd_range *range;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ctx->n_ranges; i++) {
|
|
range = &ctx->ranges[i];
|
|
|
|
if (strcmp(range->name, name))
|
|
continue;
|
|
|
|
if (range->multiple && range->instance != instance)
|
|
continue;
|
|
|
|
return range;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void pr_log_stdio(int priority, const char *fmt, va_list ap)
|
|
{
|
|
if (!ctx->debug && priority >= LOG_DEBUG)
|
|
return;
|
|
|
|
vprintf(fmt, ap);
|
|
printf("\n");
|
|
|
|
if (ctx->debug)
|
|
fflush(stdout);
|
|
}
|
|
|
|
/* standard logging prefixes:
|
|
* HBRT: Messages from hostboot runtime code
|
|
* FW: Interactions with OPAL firmware
|
|
* IMAGE: HBRT image loading
|
|
* MEM: Memory failure interface
|
|
* SCOM: Chip SCOM interface
|
|
* IPMI: IPMI interface
|
|
* PNOR: PNOR interface
|
|
* I2C: i2c interface
|
|
* PM: PM/OCC interface
|
|
* CTRL: User-triggered control events
|
|
* KMOD: Kernel module functions
|
|
*/
|
|
|
|
void pr_log(int priority, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
ctx->vlog(priority, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static void pr_log_nocall(const char *name)
|
|
{
|
|
pr_log(LOG_WARNING, "HBRT: Call %s not provided", name);
|
|
}
|
|
|
|
static void hexdump(const uint8_t *data, uint32_t len)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
if (i % 16 == 0)
|
|
printf("\n");
|
|
else if(i % 4 == 0)
|
|
printf(" ");
|
|
|
|
printf("%02x", data[i]);
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
static void pr_log_daemon_init(void)
|
|
{
|
|
if (ctx->use_syslog) {
|
|
openlog("opal-prd", LOG_NDELAY, LOG_DAEMON);
|
|
ctx->vlog = vsyslog;
|
|
}
|
|
}
|
|
|
|
/* Check service processor type */
|
|
static bool is_fsp_system(void)
|
|
{
|
|
bool fsp_system = true;
|
|
char *path;
|
|
int rc;
|
|
|
|
rc = asprintf(&path, "%s/fsps", devicetree_base);
|
|
if (rc < 0) {
|
|
pr_log(LOG_ERR, "FW: error creating '/fsps' path %m");
|
|
return false;
|
|
}
|
|
|
|
if (access(path, F_OK))
|
|
fsp_system = false;
|
|
|
|
free(path);
|
|
return fsp_system;
|
|
}
|
|
|
|
/**
|
|
* ABI check that we can't perform at build-time: we want to ensure that the
|
|
* layout of struct host_interfaces matches that defined in the thunk.
|
|
*/
|
|
static void check_abi(void)
|
|
{
|
|
extern unsigned char __hinterface_start, __hinterface_pad,
|
|
__hinterface_end;
|
|
|
|
/* ensure our struct size matches the thunk definition */
|
|
assert((&__hinterface_end - &__hinterface_start)
|
|
== sizeof(struct host_interfaces));
|
|
|
|
/* ensure the padding layout is as expected */
|
|
assert((void *)&__hinterface_start == (void *)&hinterface);
|
|
assert((void *)&__hinterface_pad == (void *)&hinterface.reserved);
|
|
}
|
|
|
|
/* 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);
|
|
extern int call_mfg_htmgt_pass_thru(uint16_t i_cmdLength, uint8_t *i_cmdData,
|
|
uint16_t *o_rspLength, uint8_t *o_rspData);
|
|
extern int call_apply_attr_override(uint8_t *i_data, size_t size);
|
|
extern int call_run_command(int argc, const char **argv, char **o_outString);
|
|
extern int call_sbe_message_passing(uint32_t i_chipId);
|
|
extern uint64_t call_get_ipoll_events(void);
|
|
extern int call_firmware_notify(uint64_t len, void *data);
|
|
extern int call_reset_pm_complex(uint64_t chip);
|
|
extern int call_load_pm_complex(u64 chip, u64 homer, u64 occ_common, u32 mode);
|
|
extern int call_start_pm_complex(u64 chip);
|
|
|
|
void hservice_puts(const char *str)
|
|
{
|
|
int priority = LOG_INFO;
|
|
|
|
/* Interpret the 2-character ERR_MRK/FAIL_MRK/WARN_MRK prefixes that
|
|
* may be present on HBRT log messages, and bump the log priority as
|
|
* appropriate.
|
|
*/
|
|
if (strlen(str) >= 2 && str[1] == '>') {
|
|
switch (str[0]) {
|
|
case 'E':
|
|
case 'F':
|
|
priority = LOG_ERR;
|
|
break;
|
|
case 'W':
|
|
priority = LOG_WARNING;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pr_log(priority, "HBRT: %s", str);
|
|
}
|
|
|
|
void hservice_assert(void)
|
|
{
|
|
pr_log(LOG_ERR, "HBRT: Failed assertion! exiting.");
|
|
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) {
|
|
pr_log(LOG_ERR, "SCOM: ioctl read(chip 0x%lx, addr 0x%lx) "
|
|
"failed: %m", chip_id, addr);
|
|
return 0;
|
|
}
|
|
rc = (int)scom.rc;
|
|
|
|
pr_debug("SCOM: read: chip 0x%lx, addr 0x%lx, val 0x%lx, rc %d",
|
|
chip_id, addr, scom.data, rc);
|
|
|
|
*(uint64_t *)buf = htobe64(scom.data);
|
|
|
|
return rc;
|
|
}
|
|
|
|
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) {
|
|
pr_log(LOG_ERR, "SCOM: ioctl write(chip 0x%lx, addr 0x%lx) "
|
|
"failed: %m", chip_id, addr);
|
|
return 0;
|
|
}
|
|
rc = (int)scom.rc;
|
|
|
|
pr_debug("SCOM: write: chip 0x%lx, addr 0x%lx, val 0x%lx, rc %d",
|
|
chip_id, addr, scom.data, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
uint64_t hservice_get_reserved_mem(const char *name, uint32_t instance)
|
|
{
|
|
struct prd_range *range;
|
|
|
|
pr_debug("IMAGE: hservice_get_reserved_mem: %s, %d", name, instance);
|
|
|
|
range = find_range(name, instance);
|
|
if (!range) {
|
|
pr_log(LOG_WARNING, "IMAGE: get_reserved_mem: "
|
|
"no such range %s", name);
|
|
return 0;
|
|
}
|
|
|
|
if (!range->buf) {
|
|
uint64_t align_physaddr, offset;
|
|
|
|
pr_debug("IMAGE: Mapping 0x%016lx 0x%08lx %s[%d]",
|
|
range->physaddr, range->size,
|
|
range->name, range->instance);
|
|
|
|
align_physaddr = range->physaddr & ~(ctx->page_size-1);
|
|
offset = range->physaddr & (ctx->page_size-1);
|
|
range->buf = mmap(NULL, range->size, PROT_WRITE | PROT_READ,
|
|
MAP_SHARED, ctx->fd, align_physaddr);
|
|
|
|
if (range->buf == MAP_FAILED)
|
|
pr_log(LOG_ERR,
|
|
"IMAGE: mmap of %s[%d](0x%016lx) failed: %m",
|
|
name, instance, range->physaddr);
|
|
else
|
|
range->buf += offset;
|
|
}
|
|
|
|
if (range->buf == MAP_FAILED) {
|
|
pr_log(LOG_WARNING,
|
|
"IMAGE: get_reserved_mem: %s[%d] has no vaddr",
|
|
name, instance);
|
|
return 0;
|
|
}
|
|
|
|
pr_debug(
|
|
"IMAGE: hservice_get_reserved_mem: %s[%d](0x%016lx) address %p",
|
|
name, range->instance, range->physaddr,
|
|
range->buf);
|
|
|
|
return (uint64_t)range->buf;
|
|
}
|
|
|
|
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)
|
|
{
|
|
/* HBRT calls this on the pages that are already being executed,
|
|
* nothing to do here */
|
|
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, 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, 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);
|
|
}
|
|
|
|
int hservice_wakeup(u32 core, u32 mode)
|
|
{
|
|
struct opal_prd_msg msg;
|
|
|
|
msg.hdr.type = OPAL_PRD_MSG_TYPE_CORE_SPECIAL_WAKEUP;
|
|
msg.hdr.size = htobe16(sizeof(msg));
|
|
msg.spl_wakeup.core = htobe32(core);
|
|
msg.spl_wakeup.mode = htobe32(mode);
|
|
|
|
if (write(ctx->fd, &msg, sizeof(msg)) != sizeof(msg)) {
|
|
pr_log(LOG_ERR, "FW: Failed to send CORE_SPECIAL_WAKEUP msg %x : %m\n",
|
|
core);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pnor_load_module(struct opal_prd_ctx *ctx)
|
|
{
|
|
insert_module("powernv_flash");
|
|
}
|
|
|
|
static void ipmi_init(struct opal_prd_ctx *ctx)
|
|
{
|
|
insert_module("ipmi_devintf");
|
|
}
|
|
|
|
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) {
|
|
pr_log(LOG_WARNING, "IPMI: recv (%zd bytes) failed: %m", *len);
|
|
return -1;
|
|
} else if (rc < 0 && errno == EMSGSIZE) {
|
|
pr_log(LOG_NOTICE, "IPMI: truncated message (netfn %d, cmd %d, "
|
|
"size %zd), continuing anyway",
|
|
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) {
|
|
pr_log(LOG_WARNING, "IPMI: Failed to open IPMI device %s: %m",
|
|
ipmi_devnode);
|
|
return -1;
|
|
}
|
|
|
|
seq++;
|
|
pr_debug("IPMI: sending %zd bytes (netfn 0x%02x, cmd 0x%02x)",
|
|
tx_size, netfn, cmd);
|
|
|
|
rc = ipmi_send(fd, netfn, cmd, seq, tx_buf, tx_size);
|
|
if (rc) {
|
|
pr_log(LOG_WARNING, "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) {
|
|
pr_log(LOG_ERR, "IPMI: poll(%s) failed: %m",
|
|
ipmi_devnode);
|
|
break;
|
|
}
|
|
|
|
if (rc == 0) {
|
|
pr_log(LOG_WARNING, "IPMI: response timeout (>%dms)",
|
|
ipmi_timeout_ms);
|
|
rc = -1;
|
|
break;
|
|
}
|
|
|
|
rc = ipmi_recv(fd, &netfn, &cmd, &rx_seq, rx_buf, &size);
|
|
if (rc)
|
|
break;
|
|
|
|
if (seq != rx_seq) {
|
|
pr_log(LOG_NOTICE, "IPMI: out-of-sequence reply: %ld, "
|
|
"expected %ld. Dropping message.",
|
|
rx_seq, seq);
|
|
continue;
|
|
}
|
|
|
|
pr_debug("IPMI: received %zd bytes", 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)
|
|
{
|
|
const char *sysfsfile, *typestr;
|
|
char buf[ADDR_STRING_SZ];
|
|
int memfd, rc, n, ret = 0;
|
|
uint64_t addr;
|
|
|
|
switch(i_errorType) {
|
|
case MEMORY_ERROR_CE:
|
|
sysfsfile = mem_offline_soft;
|
|
typestr = "correctable";
|
|
break;
|
|
case MEMORY_ERROR_UE:
|
|
sysfsfile = mem_offline_hard;
|
|
typestr = "uncorrectable";
|
|
break;
|
|
default:
|
|
pr_log(LOG_WARNING, "MEM: Invalid memory error type %d",
|
|
i_errorType);
|
|
return -1;
|
|
}
|
|
|
|
pr_log(LOG_ERR, "MEM: Memory error: range %016lx-%016lx, type: %s",
|
|
i_start_addr, i_endAddr, typestr);
|
|
|
|
|
|
memfd = open(sysfsfile, O_WRONLY);
|
|
if (memfd < 0) {
|
|
pr_log(LOG_CRIT, "MEM: Failed to offline memory! "
|
|
"Unable to open sysfs node %s: %m", 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) {
|
|
pr_log(LOG_CRIT, "MEM: Failed to offline memory! "
|
|
"page addr: %016lx type: %d: %m",
|
|
addr, i_errorType);
|
|
ret = rc;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
uint64_t hservice_get_interface_capabilities(uint64_t set)
|
|
{
|
|
if (set == HBRT_CAPS_SET1_OPAL)
|
|
return HBRT_CAPS_OPAL_HAS_XSCOM_RC ||
|
|
HBRT_CAPS_OPAL_HAS_WAKEUP_SUPPORT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint64_t hservice_firmware_request(uint64_t req_len, void *req,
|
|
uint64_t *resp_lenp, void *resp)
|
|
{
|
|
struct opal_prd_msg *msg = ctx->msg;
|
|
uint64_t resp_len;
|
|
size_t size;
|
|
int rc, n;
|
|
|
|
resp_len = be64_to_cpu(*resp_lenp);
|
|
|
|
pr_log(LOG_DEBUG,
|
|
"HBRT: firmware request: %lu bytes req, %lu bytes resp",
|
|
req_len, resp_len);
|
|
|
|
/* sanity check for potential overflows */
|
|
if (req_len > 0xffff || resp_len > 0xffff)
|
|
return -1;
|
|
|
|
size = sizeof(msg->hdr) + sizeof(msg->token) +
|
|
sizeof(msg->fw_req) + req_len;
|
|
|
|
/* we need the entire message to fit within the 2-byte size field */
|
|
if (size > 0xffff)
|
|
return -1;
|
|
|
|
/* variable sized message, so we may need to expand our buffer */
|
|
if (size > ctx->msg_alloc_len) {
|
|
msg = realloc(ctx->msg, size);
|
|
if (!msg) {
|
|
pr_log(LOG_ERR,
|
|
"FW: failed to expand message buffer: %m");
|
|
return -1;
|
|
}
|
|
ctx->msg = msg;
|
|
ctx->msg_alloc_len = size;
|
|
}
|
|
|
|
memset(msg, 0, size);
|
|
|
|
/* construct request message... */
|
|
msg->hdr.type = OPAL_PRD_MSG_TYPE_FIRMWARE_REQUEST;
|
|
msg->hdr.size = htobe16(size);
|
|
msg->fw_req.req_len = htobe64(req_len);
|
|
msg->fw_req.resp_len = htobe64(resp_len);
|
|
memcpy(msg->fw_req.data, req, req_len);
|
|
|
|
hexdump((void *)msg, size);
|
|
|
|
/* ... and send to firmware */
|
|
rc = write(ctx->fd, msg, size);
|
|
if (rc != size) {
|
|
pr_log(LOG_WARNING,
|
|
"FW: Failed to send FIRMWARE_REQUEST message: %m");
|
|
return -1;
|
|
}
|
|
|
|
/* We have an "inner" poll loop here, as we want to ensure that the
|
|
* next entry into HBRT is the return from this function. So, only
|
|
* read from the prd fd, and queue anything that isn't a response
|
|
* to this request
|
|
*/
|
|
n = 0;
|
|
for (;;) {
|
|
struct prd_msgq_item *item;
|
|
|
|
rc = read_prd_msg(ctx);
|
|
if (rc)
|
|
return -1;
|
|
|
|
msg = ctx->msg;
|
|
if (msg->hdr.type == OPAL_PRD_MSG_TYPE_FIRMWARE_RESPONSE) {
|
|
size = be64toh(msg->fw_resp.len);
|
|
if (size > resp_len)
|
|
return -1;
|
|
|
|
/* success! a valid response that fits into HBRT's
|
|
* resp buffer */
|
|
memcpy(resp, msg->fw_resp.data, size);
|
|
*resp_lenp = htobe64(size);
|
|
return 0;
|
|
}
|
|
|
|
/* not a response? queue up for later consumption */
|
|
if (++n > max_msgq_len) {
|
|
pr_log(LOG_ERR,
|
|
"FW: too many messages queued (%d) while "
|
|
"waiting for FIRMWARE_RESPONSE", n);
|
|
return -1;
|
|
}
|
|
size = be16toh(msg->hdr.size);
|
|
item = malloc(sizeof(*item) + size);
|
|
memcpy(&item->msg, msg, size);
|
|
list_add_tail(&ctx->msgq, &item->list);
|
|
}
|
|
}
|
|
|
|
int hservices_init(struct opal_prd_ctx *ctx, void *code)
|
|
{
|
|
uint64_t *s, *d;
|
|
int i, sz;
|
|
|
|
pr_debug("IMAGE: code address: %p", 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) {
|
|
pr_log(LOG_ERR, "IMAGE: Bad signature for "
|
|
"ibm,hbrt-code-image! exiting");
|
|
return -1;
|
|
}
|
|
|
|
pr_debug("IMAGE: calling ibm,hbrt_init()");
|
|
hservice_runtime = call_hbrt_init(&hinterface);
|
|
if (!hservice_runtime) {
|
|
pr_log(LOG_ERR, "IMAGE: hbrt_init failed, exiting");
|
|
return -1;
|
|
}
|
|
|
|
pr_log(LOG_NOTICE, "IMAGE: hbrt_init complete, version %016lx",
|
|
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]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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) {
|
|
pr_log(LOG_ERR, "IMAGE: HBRT file open(%s) failed: %m", name);
|
|
return -1;
|
|
}
|
|
|
|
rc = fstat(fd, &statbuf);
|
|
if (rc < 0) {
|
|
pr_log(LOG_ERR, "IMAGE: HBRT file fstat(%s) failed: %m", 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) {
|
|
pr_log(LOG_ERR, "IMAGE: HBRT file mmap(%s, 0x%zx) failed: %m",
|
|
name, statbuf.st_size);
|
|
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 prd_range *range;
|
|
void *buf;
|
|
|
|
range = find_range(name, 0);
|
|
if (!range) {
|
|
pr_log(LOG_ERR, "IMAGE: can't find code region %s", name);
|
|
return -1;
|
|
}
|
|
|
|
buf = mmap(NULL, range->size, PROT_READ | PROT_WRITE | PROT_EXEC,
|
|
MAP_PRIVATE, ctx->fd, range->physaddr);
|
|
if (buf == MAP_FAILED) {
|
|
pr_log(LOG_ERR, "IMAGE: mmap(range:%s, "
|
|
"phys:0x%016lx, size:0x%016lx) failed: %m",
|
|
name, range->physaddr, range->size);
|
|
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) {
|
|
pr_log(LOG_NOTICE, "IMAGE: couldn't debug image %s for writing",
|
|
dump_name);
|
|
return;
|
|
}
|
|
|
|
rc = ftruncate(fd, 0);
|
|
if (rc < 0) {
|
|
pr_log(LOG_NOTICE, "IMAGE: couldn't truncate image %s for writing",
|
|
dump_name);
|
|
return;
|
|
}
|
|
rc = write(fd, ctx->code_addr, ctx->code_size);
|
|
close(fd);
|
|
|
|
if (rc != ctx->code_size)
|
|
pr_log(LOG_NOTICE, "IMAGE: write to %s failed: %m", dump_name);
|
|
else
|
|
pr_debug("IMAGE: dumped HBRT binary to %s", dump_name);
|
|
}
|
|
|
|
static int open_and_read(const char *path, void **bufp, int *lenp)
|
|
{
|
|
struct stat statbuf;
|
|
int fd, rc, bytes;
|
|
void *buf;
|
|
|
|
fd = open(path, O_RDONLY);
|
|
if (fd < 0)
|
|
return -1;
|
|
|
|
rc = fstat(fd, &statbuf);
|
|
if (rc) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
buf = malloc(statbuf.st_size);
|
|
if (!buf) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
for (rc = bytes = 0; bytes < statbuf.st_size; bytes += rc) {
|
|
rc = read(fd, buf + bytes, statbuf.st_size - bytes);
|
|
if (rc < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
break;
|
|
} else if (rc == 0)
|
|
break;
|
|
}
|
|
|
|
if (bytes == statbuf.st_size)
|
|
rc = 0;
|
|
|
|
if (rc == 0) {
|
|
if (lenp)
|
|
*lenp = bytes;
|
|
if (bufp)
|
|
*bufp = buf;
|
|
} else {
|
|
free(buf);
|
|
}
|
|
|
|
close(fd);
|
|
|
|
return rc == 0 ? 0 : -1;
|
|
}
|
|
|
|
static int prd_init_one_range(struct opal_prd_ctx *ctx, const char *path,
|
|
struct dirent *dirent)
|
|
{
|
|
char *label_path, *reg_path, *instance_path;
|
|
struct prd_range *range;
|
|
int label_len, len, rc;
|
|
__be64 *reg;
|
|
char *label;
|
|
void *buf;
|
|
|
|
rc = asprintf(&label_path, "%s/%s/ibm,prd-label", path, dirent->d_name);
|
|
if (rc < 0) {
|
|
pr_log(LOG_ERR, "FW: error creating 'ibm,prd-label' path "
|
|
"node: %m");
|
|
return -1;
|
|
}
|
|
rc = asprintf(&instance_path, "%s/%s/ibm,prd-instance",
|
|
path, dirent->d_name);
|
|
if (rc < 0) {
|
|
pr_log(LOG_ERR, "FW: error creating 'ibm,prd-instance' path "
|
|
"node: %m");
|
|
return -1;
|
|
}
|
|
rc = asprintf(®_path, "%s/%s/reg", path, dirent->d_name);
|
|
if (rc < 0) {
|
|
pr_log(LOG_ERR, "FW: error creating 'reg' path "
|
|
" node: %m");
|
|
return -1;
|
|
}
|
|
|
|
reg = NULL;
|
|
label = NULL;
|
|
rc = -1;
|
|
|
|
rc = open_and_read(label_path, &buf, &label_len);
|
|
if (rc)
|
|
goto out_free;
|
|
|
|
label = buf;
|
|
|
|
if (label[label_len-1] != '\0')
|
|
pr_log(LOG_INFO, "FW: node %s has invalid ibm,prd-label - "
|
|
"not nul-terminated",
|
|
dirent->d_name);
|
|
|
|
rc = open_and_read(reg_path, &buf, &len);
|
|
if (rc)
|
|
goto out_free;
|
|
|
|
reg = buf;
|
|
|
|
if (len != 2 * sizeof(*reg)) {
|
|
pr_log(LOG_ERR, "FW: node %s has invalid 'reg' size: %d",
|
|
dirent->d_name, len);
|
|
goto out_free;
|
|
}
|
|
|
|
|
|
ctx->ranges = realloc(ctx->ranges, ++ctx->n_ranges * sizeof(*range));
|
|
range = &ctx->ranges[ctx->n_ranges - 1];
|
|
range->name = strndup(label, label_len);
|
|
range->physaddr = be64toh(reg[0]);
|
|
range->size = be64toh(reg[1]);
|
|
range->buf = NULL;
|
|
range->multiple = false;
|
|
range->instance = 0;
|
|
|
|
/* optional instance */
|
|
rc = open_and_read(instance_path, &buf, &len);
|
|
if (!rc && len == sizeof(uint32_t)) {
|
|
range->multiple = true;
|
|
range->instance = be32toh(*(uint32_t *)buf);
|
|
ctx->fw_range_instances = true;
|
|
}
|
|
rc = 0;
|
|
|
|
out_free:
|
|
free(reg);
|
|
free(label);
|
|
free(instance_path);
|
|
free(reg_path);
|
|
free(label_path);
|
|
return rc;
|
|
}
|
|
|
|
static int compare_ranges(const void *ap, const void *bp)
|
|
{
|
|
const struct prd_range *a = ap, *b = bp;
|
|
int rc;
|
|
|
|
rc = strcmp(a->name, b->name);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (a->physaddr < b->physaddr)
|
|
return -1;
|
|
else if (a->physaddr > b->physaddr)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void assign_range_instances(struct opal_prd_ctx *ctx)
|
|
{
|
|
int i;
|
|
|
|
if (!ctx->n_ranges)
|
|
return;
|
|
|
|
ctx->ranges[0].multiple = false;
|
|
ctx->ranges[0].instance = 0;
|
|
|
|
for (i = 1; i < ctx->n_ranges; i++) {
|
|
struct prd_range *cur, *prev;
|
|
|
|
cur = &ctx->ranges[i];
|
|
prev = &ctx->ranges[i-1];
|
|
|
|
if (!strcmp(cur->name, prev->name)) {
|
|
prev->multiple = true;
|
|
cur->multiple = true;
|
|
cur->instance = prev->instance + 1;
|
|
} else {
|
|
cur->multiple = false;
|
|
cur->instance = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void print_ranges(struct opal_prd_ctx *ctx)
|
|
{
|
|
int i;
|
|
|
|
if (ctx->n_ranges == 0)
|
|
pr_log(LOG_INFO, "FW: No PRD ranges");
|
|
|
|
pr_log(LOG_DEBUG, "FW: %d PRD ranges, instances assigned by %s",
|
|
ctx->n_ranges,
|
|
ctx->fw_range_instances ? "firmware" : "userspace");
|
|
|
|
for (i = 0; i < ctx->n_ranges; i++) {
|
|
struct prd_range *range = &ctx->ranges[i];
|
|
char instance_str[20];
|
|
|
|
if (range->multiple)
|
|
snprintf(instance_str, sizeof(instance_str),
|
|
" [%d]", range->instance);
|
|
else
|
|
instance_str[0] = '\0';
|
|
|
|
pr_log(LOG_DEBUG, "FW: %016lx-%016lx %s%s", range->physaddr,
|
|
range->physaddr + range->size - 1,
|
|
range->name,
|
|
instance_str);
|
|
}
|
|
}
|
|
|
|
static int chip_init(void)
|
|
{
|
|
struct dirent *dirent;
|
|
char *path;
|
|
DIR *dir;
|
|
__be32 *chipid;
|
|
void *buf;
|
|
int rc, len, i;
|
|
|
|
dir = opendir(devicetree_base);
|
|
if (!dir) {
|
|
pr_log(LOG_ERR, "FW: Can't open %s", devicetree_base);
|
|
return -1;
|
|
}
|
|
|
|
for (;;) {
|
|
dirent = readdir(dir);
|
|
if (!dirent)
|
|
break;
|
|
|
|
if (strncmp("xscom", dirent->d_name, 5))
|
|
continue;
|
|
|
|
rc = asprintf(&path, "%s/%s/ibm,chip-id", devicetree_base,
|
|
dirent->d_name);
|
|
if (rc < 0) {
|
|
pr_log(LOG_ERR, "FW: Failed to create chip-id path");
|
|
return -1;
|
|
}
|
|
|
|
rc = open_and_read(path, &buf, &len);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "FW; Failed to read chipid");
|
|
return -1;
|
|
}
|
|
chipid = buf;
|
|
chips[nr_chips++] = be32toh(*chipid);
|
|
}
|
|
|
|
pr_log(LOG_DEBUG, "FW: Chip init");
|
|
for (i = 0; i < nr_chips; i++)
|
|
pr_log(LOG_DEBUG, "FW: Chip 0x%lx", chips[i]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int prd_init_ranges(struct opal_prd_ctx *ctx)
|
|
{
|
|
struct dirent *dirent;
|
|
char *path;
|
|
DIR *dir;
|
|
int rc;
|
|
|
|
rc = asprintf(&path, "%s/reserved-memory", devicetree_base);
|
|
if (rc < 0) {
|
|
pr_log(LOG_ERR, "FW: error creating 'reserved-memory' path "
|
|
"node: %m");
|
|
return -1;
|
|
}
|
|
|
|
rc = -1;
|
|
|
|
dir = opendir(path);
|
|
if (!dir) {
|
|
pr_log(LOG_ERR, "FW: can't open reserved-memory device-tree "
|
|
"node: %m");
|
|
goto out_free;
|
|
}
|
|
|
|
for (;;) {
|
|
dirent = readdir(dir);
|
|
if (!dirent)
|
|
break;
|
|
|
|
prd_init_one_range(ctx, path, dirent);
|
|
}
|
|
|
|
rc = 0;
|
|
/* sort ranges and assign instance numbers for duplicates (if the
|
|
* firmware doesn't number instances for us) */
|
|
qsort(ctx->ranges, ctx->n_ranges, sizeof(struct prd_range),
|
|
compare_ranges);
|
|
|
|
if (!ctx->fw_range_instances)
|
|
assign_range_instances(ctx);
|
|
|
|
print_ranges(ctx);
|
|
|
|
out_free:
|
|
free(path);
|
|
closedir(dir);
|
|
return rc;
|
|
}
|
|
|
|
bool find_string(const char *buffer, size_t len, const char *s)
|
|
{
|
|
const char *c, *end;
|
|
|
|
if (!buffer)
|
|
return false;
|
|
c = buffer;
|
|
end = c + len;
|
|
|
|
while (c < end) {
|
|
if (!strcasecmp(s, c))
|
|
return true;
|
|
c += strlen(c) + 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int is_prd_supported(void)
|
|
{
|
|
char *path;
|
|
int rc;
|
|
int len;
|
|
char *buf;
|
|
|
|
rc = asprintf(&path, "%s/ibm,opal/diagnostics/compatible",
|
|
devicetree_base);
|
|
if (rc < 0) {
|
|
pr_log(LOG_ERR, "FW: error creating 'compatible' node path: %m");
|
|
return -1;
|
|
}
|
|
|
|
rc = open_and_read(path, (void *) &buf, &len);
|
|
if (rc)
|
|
goto out_free;
|
|
|
|
if (buf[len - 1] != '\0')
|
|
pr_log(LOG_INFO, "FW: node %s is not nul-terminated", path);
|
|
|
|
rc = find_string(buf, len, "ibm,opal-prd") ? 0 : -1;
|
|
|
|
free(buf);
|
|
out_free:
|
|
free(path);
|
|
return rc;
|
|
}
|
|
|
|
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) {
|
|
pr_log(LOG_ERR, "FW: Can't open PRD device %s: %m",
|
|
opal_prd_devnode);
|
|
return -1;
|
|
}
|
|
|
|
rc = ioctl(ctx->fd, OPAL_PRD_GET_INFO, &ctx->info);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "FW: Can't query PRD information: %m");
|
|
return -1;
|
|
}
|
|
|
|
rc = prd_init_ranges(ctx);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "FW: can't parse PRD memory information");
|
|
return -1;
|
|
}
|
|
|
|
rc = chip_init();
|
|
if (rc)
|
|
pr_log(LOG_ERR, "FW: Failed to initialize chip IDs");
|
|
|
|
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) {
|
|
pr_log_nocall("handle_attns");
|
|
return -1;
|
|
}
|
|
|
|
rc = call_handle_attns(proc, ipoll_status, ipoll_mask);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "HBRT: handle_attns(%lx,%lx,%lx) failed, rc %d",
|
|
proc, ipoll_status, ipoll_mask, rc);
|
|
return -1;
|
|
}
|
|
|
|
/* send the response */
|
|
msg->hdr.type = OPAL_PRD_MSG_TYPE_ATTN_ACK;
|
|
msg->hdr.size = htobe16(sizeof(*msg));
|
|
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)) {
|
|
pr_log(LOG_WARNING, "FW: Failed to send ATTN_ACK message: %m");
|
|
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);
|
|
|
|
pr_debug("FW: firmware signaled OCC error for proc 0x%x", proc);
|
|
|
|
if (!hservice_runtime->process_occ_error) {
|
|
pr_log_nocall("process_occ_error");
|
|
return -1;
|
|
}
|
|
|
|
call_process_occ_error(proc);
|
|
return 0;
|
|
}
|
|
|
|
static int pm_complex_load_start(void)
|
|
{
|
|
struct prd_range *range;
|
|
u64 homer, occ_common;
|
|
int rc = -1, i;
|
|
|
|
if (!hservice_runtime->load_pm_complex) {
|
|
pr_log_nocall("load_pm_complex");
|
|
return rc;
|
|
}
|
|
|
|
if (!hservice_runtime->start_pm_complex) {
|
|
pr_log_nocall("start_pm_complex");
|
|
return rc;
|
|
}
|
|
|
|
range = find_range("ibm,occ-common-area", 0);
|
|
if (!range) {
|
|
pr_log(LOG_ERR, "PM: ibm,occ-common-area not found");
|
|
return rc;
|
|
}
|
|
occ_common = range->physaddr;
|
|
|
|
for (i = 0; i < nr_chips; i++) {
|
|
range = find_range("ibm,homer-image", chips[i]);
|
|
if (!range) {
|
|
pr_log(LOG_ERR, "PM: ibm,homer-image not found 0x%lx",
|
|
chips[i]);
|
|
return -1;
|
|
}
|
|
homer = range->physaddr;
|
|
|
|
pr_debug("PM: calling load_pm_complex(0x%lx, 0x%lx, 0x%lx, LOAD)",
|
|
chips[i], homer, occ_common);
|
|
rc = call_load_pm_complex(chips[i], homer, occ_common, 0);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "PM: Failed load_pm_complex(0x%lx) %m",
|
|
chips[i]);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < nr_chips; i++) {
|
|
pr_debug("PM: calling start_pm_complex(0x%lx)", chips[i]);
|
|
rc = call_start_pm_complex(chips[i]);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "PM: Failed start_pm_complex(0x%lx): %m",
|
|
chips[i]);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int pm_complex_reset(uint64_t chip)
|
|
{
|
|
int rc;
|
|
|
|
/*
|
|
* FSP system -> reset_pm_complex
|
|
* BMC system -> process_occ_reset
|
|
*/
|
|
if (is_fsp_system()) {
|
|
int i;
|
|
|
|
if (!hservice_runtime->reset_pm_complex) {
|
|
pr_log_nocall("reset_pm_complex");
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < nr_chips; i++) {
|
|
pr_debug("PM: calling pm_complex_reset(%ld)", chips[i]);
|
|
rc = call_reset_pm_complex(chip);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "PM: Failed pm_complex_reset(%ld): %m",
|
|
chips[i]);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
rc = pm_complex_load_start();
|
|
} else {
|
|
if (!hservice_runtime->process_occ_reset) {
|
|
pr_log_nocall("process_occ_reset");
|
|
return -1;
|
|
}
|
|
|
|
pr_debug("PM: calling process_occ_reset(%ld)", chip);
|
|
call_process_occ_reset(chip);
|
|
rc = 0;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int handle_msg_occ_reset(struct opal_prd_ctx *ctx,
|
|
struct opal_prd_msg *msg)
|
|
{
|
|
uint32_t proc;
|
|
int rc;
|
|
|
|
proc = be64toh(msg->occ_reset.chip);
|
|
|
|
pr_debug("FW: firmware requested OCC reset for proc 0x%x", proc);
|
|
|
|
rc = pm_complex_reset(proc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int handle_msg_firmware_notify(struct opal_prd_ctx *ctx,
|
|
struct opal_prd_msg *msg)
|
|
{
|
|
uint64_t len;
|
|
void *buf;
|
|
|
|
len = be64toh(msg->fw_notify.len);
|
|
buf = msg->fw_notify.data;
|
|
|
|
pr_debug("FW: firmware notification, %ld bytes", len);
|
|
|
|
if (!hservice_runtime->firmware_notify) {
|
|
pr_log_nocall("firmware_notify");
|
|
return -1;
|
|
}
|
|
|
|
call_firmware_notify(len, buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_msg_sbe_passthrough(struct opal_prd_ctx *ctx,
|
|
struct opal_prd_msg *msg)
|
|
{
|
|
uint32_t proc;
|
|
int rc;
|
|
|
|
proc = be64toh(msg->sbe_passthrough.chip);
|
|
|
|
pr_debug("FW: firmware sent SBE pass through command for proc 0x%x\n",
|
|
proc);
|
|
|
|
if (!hservice_runtime->sbe_message_passing) {
|
|
pr_log_nocall("sbe_message_passing");
|
|
return -1;
|
|
}
|
|
|
|
rc = call_sbe_message_passing(proc);
|
|
return rc;
|
|
}
|
|
|
|
static int handle_msg_fsp_occ_reset(struct opal_prd_msg *msg)
|
|
{
|
|
struct opal_prd_msg omsg;
|
|
int rc = -1, i;
|
|
|
|
pr_debug("FW: FSP requested OCC reset");
|
|
|
|
if (!hservice_runtime->reset_pm_complex) {
|
|
pr_log_nocall("reset_pm_complex");
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0; i < nr_chips; i++) {
|
|
pr_debug("PM: calling pm_complex_reset(0x%lx)", chips[i]);
|
|
rc = call_reset_pm_complex(chips[i]);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "PM: Failed pm_complex_reset(0x%lx) %m",
|
|
chips[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
omsg.hdr.type = OPAL_PRD_MSG_TYPE_FSP_OCC_RESET_STATUS;
|
|
omsg.hdr.size = htobe16(sizeof(omsg));
|
|
omsg.fsp_occ_reset_status.chip = msg->occ_reset.chip;
|
|
omsg.fsp_occ_reset_status.status = htobe64(rc);
|
|
|
|
if (write(ctx->fd, &omsg, sizeof(omsg)) != sizeof(omsg)) {
|
|
pr_log(LOG_ERR, "FW: Failed to send FSP_OCC_RESET_STATUS msg: %m");
|
|
return -1;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int handle_msg_fsp_occ_load_start(struct opal_prd_msg *msg)
|
|
{
|
|
struct opal_prd_msg omsg;
|
|
int rc;
|
|
|
|
pr_debug("FW: FSP requested OCC load/start");
|
|
rc = pm_complex_load_start();
|
|
|
|
omsg.hdr.type = OPAL_PRD_MSG_TYPE_FSP_OCC_LOAD_START_STATUS;
|
|
omsg.hdr.size = htobe16(sizeof(omsg));
|
|
omsg.fsp_occ_reset_status.chip = msg->occ_reset.chip;
|
|
omsg.fsp_occ_reset_status.status = htobe64(rc);
|
|
|
|
if (write(ctx->fd, &omsg, sizeof(omsg)) != sizeof(omsg)) {
|
|
pr_log(LOG_ERR, "FW: Failed to send FSP_OCC_LOAD_START_STATUS msg: %m");
|
|
return -1;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int handle_prd_msg(struct opal_prd_ctx *ctx, struct opal_prd_msg *msg)
|
|
{
|
|
int rc = -1;
|
|
|
|
switch (msg->hdr.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;
|
|
case OPAL_PRD_MSG_TYPE_FIRMWARE_NOTIFY:
|
|
rc = handle_msg_firmware_notify(ctx, msg);
|
|
break;
|
|
case OPAL_PRD_MSG_TYPE_SBE_PASSTHROUGH:
|
|
rc = handle_msg_sbe_passthrough(ctx, msg);
|
|
break;
|
|
case OPAL_PRD_MSG_TYPE_FSP_OCC_RESET:
|
|
rc = handle_msg_fsp_occ_reset(msg);
|
|
break;
|
|
case OPAL_PRD_MSG_TYPE_FSP_OCC_LOAD_START:
|
|
rc = handle_msg_fsp_occ_load_start(msg);
|
|
break;
|
|
default:
|
|
pr_log(LOG_WARNING, "Invalid incoming message type 0x%x",
|
|
msg->hdr.type);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define list_for_each_pop(h, i, type, member) \
|
|
for (i = list_pop((h), type, member); \
|
|
i; \
|
|
i = list_pop((h), type, member))
|
|
|
|
|
|
static int process_msgq(struct opal_prd_ctx *ctx)
|
|
{
|
|
struct prd_msgq_item *item;
|
|
|
|
list_for_each_pop(&ctx->msgq, item, struct prd_msgq_item, list) {
|
|
handle_prd_msg(ctx, &item->msg);
|
|
free(item);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int read_prd_msg(struct opal_prd_ctx *ctx)
|
|
{
|
|
struct opal_prd_msg *msg;
|
|
int size;
|
|
int rc;
|
|
|
|
msg = ctx->msg;
|
|
|
|
rc = read(ctx->fd, msg, ctx->msg_alloc_len);
|
|
if (rc < 0 && errno == EAGAIN)
|
|
return -1;
|
|
|
|
/* we need at least enough for the message header... */
|
|
if (rc < 0) {
|
|
pr_log(LOG_WARNING, "FW: error reading from firmware: %m");
|
|
return -1;
|
|
}
|
|
|
|
if (rc < sizeof(msg->hdr)) {
|
|
pr_log(LOG_WARNING, "FW: short message read from firmware");
|
|
return -1;
|
|
}
|
|
|
|
/* ... and for the reported message size to be sane */
|
|
size = htobe16(msg->hdr.size);
|
|
if (size < sizeof(msg->hdr)) {
|
|
pr_log(LOG_ERR, "FW: Mismatched message size "
|
|
"between opal-prd and firmware "
|
|
"(%d from FW, %zd expected)",
|
|
size, sizeof(msg->hdr));
|
|
return -1;
|
|
}
|
|
|
|
/* expand our message buffer if necessary... */
|
|
if (size > ctx->msg_alloc_len) {
|
|
msg = realloc(ctx->msg, size);
|
|
if (!msg) {
|
|
pr_log(LOG_ERR,
|
|
"FW: Can't expand PRD message buffer: %m");
|
|
return -1;
|
|
}
|
|
ctx->msg = msg;
|
|
ctx->msg_alloc_len = size;
|
|
}
|
|
|
|
/* ... and complete the read */
|
|
if (size > rc) {
|
|
size_t pos;
|
|
|
|
for (pos = rc; pos < size;) {
|
|
rc = read(ctx->fd, msg + pos, size - pos);
|
|
|
|
if (rc < 0 && errno == EAGAIN)
|
|
continue;
|
|
|
|
if (rc <= 0) {
|
|
pr_log(LOG_WARNING,
|
|
"FW: error reading from firmware: %m");
|
|
return -1;
|
|
}
|
|
|
|
pos += rc;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void handle_prd_control_occ_error(struct control_msg *send_msg,
|
|
struct control_msg *recv_msg)
|
|
{
|
|
uint64_t chip;
|
|
|
|
if (!hservice_runtime->process_occ_error) {
|
|
pr_log_nocall("process_occ_error");
|
|
return;
|
|
}
|
|
|
|
chip = recv_msg->occ_error.chip;
|
|
|
|
pr_debug("CTRL: calling process_occ_error(%lu)", chip);
|
|
call_process_occ_error(chip);
|
|
|
|
send_msg->data_len = 0;
|
|
send_msg->response = 0;
|
|
}
|
|
|
|
static void handle_prd_control_occ_reset(struct control_msg *send_msg,
|
|
struct control_msg *msg)
|
|
{
|
|
struct opal_prd_msg omsg;
|
|
uint64_t chip;
|
|
int rc;
|
|
|
|
/* notify OPAL of the impending reset */
|
|
memset(&omsg, 0, sizeof(omsg));
|
|
omsg.hdr.type = OPAL_PRD_MSG_TYPE_OCC_RESET_NOTIFY;
|
|
omsg.hdr.size = htobe16(sizeof(omsg));
|
|
rc = write(ctx->fd, &omsg, sizeof(omsg));
|
|
if (rc != sizeof(omsg))
|
|
pr_log(LOG_WARNING, "FW: Failed to send OCC_RESET message: %m");
|
|
|
|
chip = msg->occ_reset.chip;
|
|
|
|
/* do reset */
|
|
pr_debug("CTRL: Calling OCC reset on chip %ld", chip);
|
|
pm_complex_reset(chip);
|
|
|
|
send_msg->data_len = 0;
|
|
send_msg->response = 0;
|
|
}
|
|
|
|
static void handle_prd_control_occ_actuation(struct control_msg *msg,
|
|
bool enable)
|
|
{
|
|
if (!hservice_runtime->enable_occ_actuation) {
|
|
pr_log_nocall("enable_occ_actuation");
|
|
return;
|
|
}
|
|
|
|
pr_debug("CTRL: calling enable_occ_actuation(%s)",
|
|
enable ? "true" : "false");
|
|
msg->data_len = 0;
|
|
msg->response = call_enable_occ_actuation(enable);
|
|
}
|
|
|
|
static void handle_prd_control_attr_override(struct control_msg *send_msg,
|
|
struct control_msg *recv_msg)
|
|
{
|
|
if (!hservice_runtime->apply_attr_override) {
|
|
pr_log_nocall("apply_attr_override");
|
|
return;
|
|
}
|
|
|
|
pr_debug("CTRL: calling apply_attr_override");
|
|
send_msg->response = call_apply_attr_override(
|
|
recv_msg->data, recv_msg->data_len);
|
|
send_msg->data_len = 0;
|
|
}
|
|
|
|
static void handle_prd_control_htmgt_passthru(struct control_msg *send_msg,
|
|
struct control_msg *recv_msg)
|
|
{
|
|
uint16_t rsp_len;
|
|
|
|
if (!hservice_runtime->mfg_htmgt_pass_thru) {
|
|
pr_log_nocall("mfg_htmgt_pass_thru");
|
|
return;
|
|
}
|
|
|
|
pr_debug("CTRL: calling mfg_htmgt_pass_thru");
|
|
send_msg->response = call_mfg_htmgt_pass_thru(recv_msg->data_len,
|
|
recv_msg->data, &rsp_len,
|
|
send_msg->data);
|
|
send_msg->data_len = be16toh(rsp_len);
|
|
if (send_msg->data_len > MAX_CONTROL_MSG_BUF) {
|
|
pr_log(LOG_ERR, "CTRL: response buffer overrun, data len: %d",
|
|
send_msg->data_len);
|
|
send_msg->data_len = MAX_CONTROL_MSG_BUF;
|
|
}
|
|
}
|
|
|
|
static void handle_prd_control_run_cmd(struct control_msg *send_msg,
|
|
struct control_msg *recv_msg)
|
|
{
|
|
char *runcmd_output, *s;
|
|
const char **argv;
|
|
int i, argc;
|
|
size_t size;
|
|
|
|
if (!hservice_runtime->run_command) {
|
|
pr_log_nocall("run_command");
|
|
return;
|
|
}
|
|
|
|
argc = recv_msg->run_cmd.argc;
|
|
pr_debug("CTRL: run_command, argc:%d\n", argc);
|
|
|
|
argv = malloc(argc * sizeof(*argv));
|
|
if (!argv) {
|
|
pr_log(LOG_ERR, "CTRL: argv buffer malloc failed: %m");
|
|
return;
|
|
}
|
|
|
|
s = (char *)recv_msg->data;
|
|
size = 0;
|
|
for (i = 0; i < argc; i++) {
|
|
argv[i] = (char *)htobe64((uint64_t)&s[size]);
|
|
size += (strlen(&s[size]) + 1);
|
|
}
|
|
|
|
/* Call HBRT */
|
|
send_msg->response = call_run_command(argc, argv, &runcmd_output);
|
|
runcmd_output = (char *)be64toh((uint64_t)runcmd_output);
|
|
free(argv);
|
|
|
|
s = (char *)send_msg->data;
|
|
if (runcmd_output) {
|
|
size = strlen(runcmd_output);
|
|
if (size >= MAX_CONTROL_MSG_BUF) {
|
|
pr_log(LOG_WARNING, "CTRL: output message truncated");
|
|
runcmd_output[MAX_CONTROL_MSG_BUF] = '\0';
|
|
size = MAX_CONTROL_MSG_BUF;
|
|
}
|
|
|
|
strcpy(s, runcmd_output);
|
|
send_msg->data_len = size + 1;
|
|
free(runcmd_output);
|
|
} else {
|
|
strcpy(s, "Null");
|
|
send_msg->data_len = strlen("Null") + 1;
|
|
}
|
|
}
|
|
|
|
static void handle_prd_control(struct opal_prd_ctx *ctx, int fd)
|
|
{
|
|
struct control_msg msg, *recv_msg, *send_msg;
|
|
bool enabled = false;
|
|
int rc, size;
|
|
|
|
/* Default reply, in the error path */
|
|
send_msg = &msg;
|
|
|
|
/* Peek into the socket to ascertain the size of the available data */
|
|
rc = recv(fd, &msg, sizeof(msg), MSG_PEEK);
|
|
if (rc != sizeof(msg)) {
|
|
pr_log(LOG_WARNING, "CTRL: failed to receive control "
|
|
"message: %m");
|
|
msg.response = -1;
|
|
msg.data_len = 0;
|
|
goto out_send;
|
|
}
|
|
|
|
size = sizeof(*recv_msg) + msg.data_len;
|
|
|
|
/* Default reply, in the error path */
|
|
msg.data_len = 0;
|
|
msg.response = -1;
|
|
|
|
recv_msg = malloc(size);
|
|
if (!recv_msg) {
|
|
pr_log(LOG_ERR, "CTRL: message buffer malloc failed: %m");
|
|
goto out_send;
|
|
}
|
|
|
|
rc = recv(fd, recv_msg, size, MSG_TRUNC);
|
|
if (rc != size) {
|
|
pr_log(LOG_WARNING, "CTRL: failed to receive control "
|
|
"message: %m");
|
|
goto out_free_recv;
|
|
}
|
|
|
|
send_msg = malloc(sizeof(*send_msg) + MAX_CONTROL_MSG_BUF);
|
|
if (!send_msg) {
|
|
pr_log(LOG_ERR, "CTRL: message buffer malloc failed: %m");
|
|
send_msg = &msg;
|
|
goto out_free_recv;
|
|
}
|
|
|
|
send_msg->type = recv_msg->type;
|
|
send_msg->response = -1;
|
|
switch (recv_msg->type) {
|
|
case CONTROL_MSG_ENABLE_OCCS:
|
|
enabled = true;
|
|
/* fall through */
|
|
case CONTROL_MSG_DISABLE_OCCS:
|
|
handle_prd_control_occ_actuation(send_msg, enabled);
|
|
break;
|
|
case CONTROL_MSG_TEMP_OCC_RESET:
|
|
handle_prd_control_occ_reset(send_msg, recv_msg);
|
|
break;
|
|
case CONTROL_MSG_TEMP_OCC_ERROR:
|
|
handle_prd_control_occ_error(send_msg, recv_msg);
|
|
break;
|
|
case CONTROL_MSG_ATTR_OVERRIDE:
|
|
handle_prd_control_attr_override(send_msg, recv_msg);
|
|
break;
|
|
case CONTROL_MSG_HTMGT_PASSTHRU:
|
|
handle_prd_control_htmgt_passthru(send_msg, recv_msg);
|
|
break;
|
|
case CONTROL_MSG_RUN_CMD:
|
|
handle_prd_control_run_cmd(send_msg, recv_msg);
|
|
break;
|
|
default:
|
|
pr_log(LOG_WARNING, "CTRL: Unknown control message action %d",
|
|
recv_msg->type);
|
|
send_msg->data_len = 0;
|
|
break;
|
|
}
|
|
|
|
out_free_recv:
|
|
free(recv_msg);
|
|
out_send:
|
|
size = sizeof(*send_msg) + send_msg->data_len;
|
|
rc = send(fd, send_msg, size, MSG_DONTWAIT | MSG_NOSIGNAL);
|
|
if (rc && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EPIPE))
|
|
pr_debug("CTRL: control send() returned %d, ignoring failure",
|
|
rc);
|
|
else if (rc != size)
|
|
pr_log(LOG_NOTICE, "CTRL: Failed to send control response: %m");
|
|
|
|
if (send_msg != &msg)
|
|
free(send_msg);
|
|
}
|
|
|
|
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("HBRT: calling enable_attns");
|
|
rc = call_enable_attns();
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "HBRT: enable_attns() failed, "
|
|
"aborting");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (hservice_runtime->get_ipoll_events) {
|
|
pr_debug("HBRT: calling get_ipoll_events");
|
|
opal_prd_ipoll = call_get_ipoll_events();
|
|
}
|
|
|
|
pr_debug("HBRT: enabling IPOLL events 0x%016lx", opal_prd_ipoll);
|
|
|
|
/* send init message, to unmask interrupts */
|
|
msg.hdr.type = OPAL_PRD_MSG_TYPE_INIT;
|
|
msg.hdr.size = htobe16(sizeof(msg));
|
|
msg.init.version = htobe64(opal_prd_version);
|
|
msg.init.ipoll = htobe64(opal_prd_ipoll);
|
|
|
|
pr_debug("FW: writing init message");
|
|
rc = write(ctx->fd, &msg, sizeof(msg));
|
|
if (rc != sizeof(msg)) {
|
|
pr_log(LOG_ERR, "FW: Init message failed: %m. Aborting.");
|
|
return -1;
|
|
}
|
|
|
|
pollfds[0].fd = ctx->fd;
|
|
pollfds[0].events = POLLIN | POLLERR;
|
|
pollfds[1].fd = ctx->socket;
|
|
pollfds[1].events = POLLIN | POLLERR;
|
|
|
|
for (;;) {
|
|
/* run through any pending messages */
|
|
process_msgq(ctx);
|
|
|
|
rc = poll(pollfds, 2, -1);
|
|
if (rc < 0) {
|
|
pr_log(LOG_ERR, "FW: event poll failed: %m");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!rc)
|
|
continue;
|
|
|
|
if (pollfds[0].revents & POLLIN) {
|
|
rc = read_prd_msg(ctx);
|
|
if (!rc)
|
|
handle_prd_msg(ctx, ctx->msg);
|
|
}
|
|
|
|
if (pollfds[1].revents & POLLIN) {
|
|
fd = accept(ctx->socket, NULL, NULL);
|
|
if (fd < 0) {
|
|
pr_log(LOG_NOTICE, "CTRL: accept failed: %m");
|
|
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) {
|
|
pr_log(LOG_WARNING, "CTRL: Can't open control socket %s: %m",
|
|
opal_prd_socket);
|
|
return -1;
|
|
}
|
|
|
|
rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
|
|
if (rc) {
|
|
pr_log(LOG_WARNING, "CTRL: Can't bind control socket %s: %m",
|
|
opal_prd_socket);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
rc = listen(fd, 0);
|
|
if (rc) {
|
|
pr_log(LOG_WARNING, "CTRL: Can't listen on "
|
|
"control socket %s: %m", opal_prd_socket);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
pr_log(LOG_INFO, "CTRL: Listening on control socket %s",
|
|
opal_prd_socket);
|
|
|
|
ctx->socket = fd;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int run_prd_daemon(struct opal_prd_ctx *ctx)
|
|
{
|
|
char *opal_msg_path;
|
|
void *buf;
|
|
int rc, len;
|
|
|
|
/* log to syslog */
|
|
pr_log_daemon_init();
|
|
|
|
pr_debug("CTRL: Starting PRD daemon\n");
|
|
|
|
ctx->fd = -1;
|
|
ctx->socket = -1;
|
|
|
|
/*
|
|
* Set up our message buffer. Use opal-msg-size device tree
|
|
* property to get message buffer size.
|
|
*/
|
|
rc = asprintf(&opal_msg_path,
|
|
"%s/ibm,opal/opal-msg-size", devicetree_base);
|
|
if (rc > 0) {
|
|
rc = open_and_read(opal_msg_path, &buf, &len);
|
|
if (rc == 0) {
|
|
ctx->msg_alloc_len = be32toh(*(__be32 *)buf);
|
|
free(buf);
|
|
}
|
|
|
|
free(opal_msg_path);
|
|
}
|
|
|
|
if (ctx->msg_alloc_len == 0)
|
|
ctx->msg_alloc_len = sizeof(*ctx->msg);
|
|
|
|
ctx->msg = malloc(ctx->msg_alloc_len);
|
|
if (!ctx->msg) {
|
|
pr_log(LOG_ERR, "FW: Can't allocate PRD message buffer: %m");
|
|
return -1;
|
|
}
|
|
memset(ctx->msg, 0, ctx->msg_alloc_len);
|
|
|
|
list_head_init(&ctx->msgq);
|
|
|
|
i2c_init();
|
|
|
|
#ifdef DEBUG_I2C
|
|
{
|
|
uint8_t foo[128];
|
|
int i;
|
|
|
|
rc = i2c_read(0, 1, 2, 0x50, 2, 0x10, 128, foo);
|
|
pr_debug("I2C: read rc: %d", rc);
|
|
for (i = 0; i < sizeof(foo); i += 8) {
|
|
pr_debug("I2C: %02x %02x %02x %02x %02x %02x %02x %02x",
|
|
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) {
|
|
pr_log(LOG_WARNING, "CTRL: Error initialising PRD control: %m");
|
|
goto out_close;
|
|
}
|
|
|
|
|
|
rc = prd_init(ctx);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "FW: Error initialising PRD channel");
|
|
goto out_close;
|
|
}
|
|
|
|
if (ctx->hbrt_file_name) {
|
|
rc = map_hbrt_file(ctx, ctx->hbrt_file_name);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "IMAGE: 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) {
|
|
pr_log(LOG_ERR, "IMAGE: Can't access hbrt "
|
|
"physical memory");
|
|
goto out_close;
|
|
}
|
|
dump_hbrt_map(ctx);
|
|
}
|
|
|
|
pr_debug("IMAGE: hbrt map at %p, size 0x%zx",
|
|
ctx->code_addr, ctx->code_size);
|
|
|
|
fixup_hinterface_table();
|
|
|
|
if (!is_fsp_system()) {
|
|
pnor_load_module(ctx);
|
|
|
|
rc = pnor_init(&ctx->pnor);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "PNOR: Failed to open pnor: %m");
|
|
goto out_close;
|
|
}
|
|
} else {
|
|
/* Disable PNOR function pointers */
|
|
hinterface.pnor_read = NULL;
|
|
hinterface.pnor_write = NULL;
|
|
}
|
|
|
|
ipmi_init(ctx);
|
|
|
|
pr_debug("HBRT: calling hservices_init");
|
|
rc = hservices_init(ctx, ctx->code_addr);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "HBRT: Can't initialise HBRT");
|
|
goto out_close;
|
|
}
|
|
pr_debug("HBRT: hservices_init done");
|
|
|
|
/* Test a scom */
|
|
if (ctx->debug) {
|
|
uint64_t val;
|
|
pr_debug("SCOM: trying scom read");
|
|
fflush(stdout);
|
|
hservice_scom_read(0x00, 0xf000f, &val);
|
|
pr_debug("SCOM: f00f: %lx", be64toh(val));
|
|
}
|
|
|
|
run_attn_loop(ctx);
|
|
rc = 0;
|
|
|
|
out_close:
|
|
pr_debug("CTRL: stopping PRD daemon\n");
|
|
pnor_close(&ctx->pnor);
|
|
if (ctx->fd != -1)
|
|
close(ctx->fd);
|
|
if (ctx->socket != -1)
|
|
close(ctx->socket);
|
|
if (ctx->msg)
|
|
free(ctx->msg);
|
|
return rc;
|
|
}
|
|
|
|
static int send_prd_control(struct control_msg *send_msg,
|
|
struct control_msg **recv_msg)
|
|
{
|
|
struct sockaddr_un addr;
|
|
struct control_msg *msg;
|
|
int sd, rc, size;
|
|
|
|
sd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (!sd) {
|
|
pr_log(LOG_ERR, "CTRL: Failed to create control socket: %m");
|
|
return -1;
|
|
}
|
|
|
|
addr.sun_family = AF_UNIX;
|
|
strcpy(addr.sun_path, opal_prd_socket);
|
|
|
|
rc = connect(sd, (struct sockaddr *)&addr, sizeof(addr));
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "CTRL: Failed to connect to prd daemon: %m");
|
|
goto out_close;
|
|
}
|
|
|
|
size = sizeof(*send_msg) + send_msg->data_len;
|
|
rc = send(sd, send_msg, size, 0);
|
|
if (rc != size) {
|
|
pr_log(LOG_ERR, "CTRL: Failed to send control message: %m");
|
|
rc = -1;
|
|
goto out_close;
|
|
}
|
|
|
|
size = sizeof(*msg) + MAX_CONTROL_MSG_BUF;
|
|
msg = malloc(size);
|
|
if (!msg) {
|
|
pr_log(LOG_ERR, "CTRL: msg buffer malloc failed: %m");
|
|
rc = -1;
|
|
goto out_close;
|
|
}
|
|
|
|
*recv_msg = msg;
|
|
|
|
/* wait for our reply */
|
|
rc = recv(sd, msg, size, 0);
|
|
if (rc < 0) {
|
|
pr_log(LOG_ERR, "CTRL: Failed to receive control message: %m");
|
|
goto out_close;
|
|
|
|
} else if (rc != (sizeof(*msg) + msg->data_len)) {
|
|
pr_log(LOG_WARNING, "CTRL: Short read from control socket");
|
|
rc = -1;
|
|
goto out_close;
|
|
}
|
|
|
|
rc = msg->response;
|
|
|
|
out_close:
|
|
close(sd);
|
|
return rc;
|
|
}
|
|
|
|
static int send_occ_control(struct opal_prd_ctx *ctx, int argc, char *argv[])
|
|
{
|
|
struct control_msg send_msg, *recv_msg = NULL;
|
|
unsigned long chip = 0;
|
|
const char *op;
|
|
int rc;
|
|
|
|
assert(argc >= 1);
|
|
op = argv[0];
|
|
|
|
/* some commands accept a 'chip' argument, so parse it here */
|
|
if (argc > 1) {
|
|
char *arg, *end;
|
|
arg = argv[1];
|
|
chip = strtoul(arg, &end, 0);
|
|
if (end == arg) {
|
|
pr_log(LOG_ERR, "CTRL: invalid argument %s", arg);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
memset(&send_msg, 0, sizeof(send_msg));
|
|
|
|
if (!strcmp(op, "enable"))
|
|
send_msg.type = CONTROL_MSG_ENABLE_OCCS;
|
|
else if (!strcmp(op, "disable"))
|
|
send_msg.type = CONTROL_MSG_DISABLE_OCCS;
|
|
|
|
else if (!strcmp(op, "reset")) {
|
|
send_msg.type = CONTROL_MSG_TEMP_OCC_RESET;
|
|
send_msg.occ_reset.chip = (uint64_t)chip;
|
|
|
|
} else if (!strcmp(op, "process-error")) {
|
|
send_msg.type = CONTROL_MSG_TEMP_OCC_ERROR;
|
|
send_msg.occ_error.chip = (uint64_t)chip;
|
|
} else {
|
|
pr_log(LOG_ERR, "CTRL: Invalid OCC action '%s'", op);
|
|
return -1;
|
|
}
|
|
|
|
rc = send_prd_control(&send_msg, &recv_msg);
|
|
if (recv_msg) {
|
|
if (recv_msg->response || ctx->debug)
|
|
pr_debug("CTRL: OCC action %s returned status %d", op,
|
|
recv_msg->response);
|
|
free(recv_msg);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int send_attr_override(struct opal_prd_ctx *ctx, uint32_t argc,
|
|
char *argv[])
|
|
{
|
|
struct control_msg *send_msg, *recv_msg = NULL;
|
|
struct stat statbuf;
|
|
size_t sz;
|
|
FILE *fd;
|
|
int rc;
|
|
|
|
rc = stat(argv[0], &statbuf);
|
|
if (rc) {
|
|
pr_log(LOG_ERR, "CTRL: stat() failed on the file: %m");
|
|
return -1;
|
|
}
|
|
|
|
send_msg = malloc(sizeof(*send_msg) + statbuf.st_size);
|
|
if (!send_msg) {
|
|
pr_log(LOG_ERR, "CTRL: msg buffer malloc failed: %m");
|
|
return -1;
|
|
}
|
|
|
|
send_msg->type = CONTROL_MSG_ATTR_OVERRIDE;
|
|
send_msg->data_len = statbuf.st_size;
|
|
|
|
fd = fopen(argv[0], "r");
|
|
if (!fd) {
|
|
pr_log(LOG_NOTICE, "CTRL: can't open %s: %m", argv[0]);
|
|
rc = -1;
|
|
goto out_free;
|
|
}
|
|
|
|
sz = fread(send_msg->data, 1, send_msg->data_len, fd);
|
|
fclose(fd);
|
|
if (sz != statbuf.st_size) {
|
|
pr_log(LOG_ERR, "CTRL: short read from the file");
|
|
rc = -1;
|
|
goto out_free;
|
|
}
|
|
|
|
rc = send_prd_control(send_msg, &recv_msg);
|
|
if (recv_msg) {
|
|
if (recv_msg->response || ctx->debug)
|
|
pr_debug("CTRL: attribute override returned status %d",
|
|
recv_msg->response);
|
|
free(recv_msg);
|
|
}
|
|
|
|
out_free:
|
|
free(send_msg);
|
|
return rc;
|
|
}
|
|
|
|
static int send_htmgt_passthru(struct opal_prd_ctx *ctx, int argc, char *argv[])
|
|
{
|
|
struct control_msg *send_msg, *recv_msg = NULL;
|
|
int rc, i;
|
|
|
|
if (!ctx->expert_mode) {
|
|
pr_log(LOG_WARNING, "CTRL: need to be in expert mode");
|
|
return -1;
|
|
}
|
|
|
|
send_msg = malloc(sizeof(*send_msg) + argc);
|
|
if (!send_msg) {
|
|
pr_log(LOG_ERR, "CTRL: message buffer malloc failed: %m");
|
|
return -1;
|
|
}
|
|
|
|
send_msg->type = CONTROL_MSG_HTMGT_PASSTHRU;
|
|
send_msg->data_len = argc;
|
|
|
|
if (ctx->debug)
|
|
pr_debug("CTRL: HTMGT passthru arguments:");
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
if (ctx->debug)
|
|
pr_debug("argv[%d] = %s", i, argv[i]);
|
|
|
|
sscanf(argv[i], "%hhx", &send_msg->data[i]);
|
|
}
|
|
|
|
rc = send_prd_control(send_msg, &recv_msg);
|
|
free(send_msg);
|
|
|
|
if (recv_msg) {
|
|
if (recv_msg->response || ctx->debug)
|
|
pr_debug("CTRL: HTMGT passthru returned status %d",
|
|
recv_msg->response);
|
|
if (recv_msg->response == 0 && recv_msg->data_len)
|
|
hexdump(recv_msg->data, recv_msg->data_len);
|
|
|
|
free(recv_msg);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int send_run_command(struct opal_prd_ctx *ctx, int argc, char *argv[])
|
|
{
|
|
struct control_msg *send_msg, *recv_msg = NULL;
|
|
uint32_t size = 0;
|
|
int rc, i;
|
|
char *s;
|
|
|
|
if (!ctx->expert_mode) {
|
|
pr_log(LOG_WARNING, "CTRL: need to be in expert mode");
|
|
return -1;
|
|
}
|
|
|
|
if (ctx->debug) {
|
|
pr_debug("CTRL: run command arguments:");
|
|
for (i=0; i < argc; i++)
|
|
pr_debug("argv[%d] = %s", i, argv[i]);
|
|
}
|
|
|
|
for (i = 0; i < argc; i++)
|
|
size += (strlen(argv[i]) + 1);
|
|
|
|
send_msg = malloc(sizeof(*send_msg) + size);
|
|
if (!send_msg) {
|
|
pr_log(LOG_ERR, "CTRL: msg buffer malloc failed: %m");
|
|
return -1;
|
|
}
|
|
|
|
/* Setup message */
|
|
send_msg->type = CONTROL_MSG_RUN_CMD;
|
|
send_msg->run_cmd.argc = argc;
|
|
send_msg->data_len = size;
|
|
s = (char *)send_msg->data;
|
|
for (i = 0; i < argc; i++) {
|
|
strcpy(s, argv[i]);
|
|
s = s + strlen(argv[i]) + 1;
|
|
}
|
|
|
|
rc = send_prd_control(send_msg, &recv_msg);
|
|
free(send_msg);
|
|
if (recv_msg) {
|
|
if (!rc)
|
|
pr_log(LOG_INFO, "Received: %s", recv_msg->data);
|
|
|
|
if (recv_msg->response || ctx->debug)
|
|
pr_debug("CTRL: run command returned status %d",
|
|
recv_msg->response);
|
|
free(recv_msg);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void usage(const char *progname)
|
|
{
|
|
printf("Usage:\n");
|
|
printf("\t%s [--debug] [--file <hbrt-image>] [--pnor <device>]\n",
|
|
progname);
|
|
printf("\t%s occ <enable|disable|reset [chip]>\n", progname);
|
|
printf("\t%s pm-complex reset [chip]>\n", progname);
|
|
printf("\t%s htmgt-passthru <bytes...>\n", progname);
|
|
printf("\t%s override <FILE>\n", progname);
|
|
printf("\t%s run [arg 0] [arg 1]..[arg n]\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--stdio log to stdio, instead of syslog\n");
|
|
}
|
|
|
|
static void print_version(void)
|
|
{
|
|
extern const char version[];
|
|
printf("opal-prd %s\n", version);
|
|
}
|
|
|
|
static struct option opal_diag_options[] = {
|
|
{"file", required_argument, NULL, 'f'},
|
|
{"pnor", required_argument, NULL, 'p'},
|
|
{"debug", no_argument, NULL, 'd'},
|
|
{"help", no_argument, NULL, 'h'},
|
|
{"version", no_argument, NULL, 'v'},
|
|
{"stdio", no_argument, NULL, 's'},
|
|
{"expert-mode", no_argument, NULL, 'e'},
|
|
{ 0 },
|
|
};
|
|
|
|
enum action {
|
|
ACTION_RUN_DAEMON,
|
|
ACTION_OCC_CONTROL,
|
|
ACTION_ATTR_OVERRIDE,
|
|
ACTION_HTMGT_PASSTHRU,
|
|
ACTION_RUN_COMMAND,
|
|
};
|
|
|
|
static int parse_action(const char *str, enum action *action)
|
|
{
|
|
int rc;
|
|
|
|
if (!strcmp(str, "occ")) {
|
|
*action = ACTION_OCC_CONTROL;
|
|
rc = 0;
|
|
|
|
if (is_fsp_system()) {
|
|
pr_log(LOG_ERR, "CTRL: occ commands are not "
|
|
"supported on this system");
|
|
rc = -1;
|
|
}
|
|
} else if (!strcmp(str, "pm-complex")) {
|
|
*action = ACTION_OCC_CONTROL;
|
|
rc = 0;
|
|
|
|
if (!is_fsp_system()) {
|
|
pr_log(LOG_ERR, "CTRL: pm-complex commands are not "
|
|
"supported on this system");
|
|
rc = -1;
|
|
}
|
|
} else if (!strcmp(str, "daemon")) {
|
|
*action = ACTION_RUN_DAEMON;
|
|
rc = 0;
|
|
} else if (!strcmp(str, "override")) {
|
|
*action = ACTION_ATTR_OVERRIDE;
|
|
rc = 0;
|
|
} else if (!strcmp(str, "htmgt-passthru")) {
|
|
*action = ACTION_HTMGT_PASSTHRU;
|
|
rc = 0;
|
|
} else if (!strcmp(str, "run")) {
|
|
*action = ACTION_RUN_COMMAND;
|
|
return 0;
|
|
} else {
|
|
pr_log(LOG_ERR, "CTRL: unknown argument '%s'", str);
|
|
rc = -1;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct opal_prd_ctx _ctx;
|
|
enum action action;
|
|
int rc;
|
|
|
|
check_abi();
|
|
|
|
ctx = &_ctx;
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
ctx->vlog = pr_log_stdio;
|
|
ctx->use_syslog = true;
|
|
|
|
/* Parse options */
|
|
for (;;) {
|
|
int c;
|
|
|
|
c = getopt_long(argc, argv, "f:p:dhse", 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 's':
|
|
ctx->use_syslog = false;
|
|
break;
|
|
case 'h':
|
|
usage(argv[0]);
|
|
return EXIT_SUCCESS;
|
|
case 'e':
|
|
ctx->expert_mode = true;
|
|
break;
|
|
case 'v':
|
|
print_version();
|
|
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;
|
|
optind++;
|
|
} else {
|
|
action = ACTION_RUN_DAEMON;
|
|
}
|
|
|
|
if (is_prd_supported() < 0) {
|
|
pr_log(LOG_ERR, "CTRL: PowerNV OPAL runtime diagnostic "
|
|
"is not supported on this system");
|
|
return -1;
|
|
}
|
|
|
|
switch (action) {
|
|
case ACTION_RUN_DAEMON:
|
|
rc = run_prd_daemon(ctx);
|
|
break;
|
|
case ACTION_OCC_CONTROL:
|
|
if (optind >= argc) {
|
|
pr_log(LOG_ERR, "CTRL: occ command requires "
|
|
"an argument");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
rc = send_occ_control(ctx, argc - optind, &argv[optind]);
|
|
break;
|
|
case ACTION_ATTR_OVERRIDE:
|
|
if (optind >= argc) {
|
|
pr_log(LOG_ERR, "CTRL: attribute override command "
|
|
"requires an argument");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
rc = send_attr_override(ctx, argc - optind, &argv[optind]);
|
|
break;
|
|
case ACTION_HTMGT_PASSTHRU:
|
|
if (optind >= argc) {
|
|
pr_log(LOG_ERR, "CTRL: htmgt passthru requires at least "
|
|
"one argument");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
rc = send_htmgt_passthru(ctx, argc - optind, &argv[optind]);
|
|
break;
|
|
case ACTION_RUN_COMMAND:
|
|
if (optind >= argc) {
|
|
pr_log(LOG_ERR, "CTRL: run command requires "
|
|
"argument(s)");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
rc = send_run_command(ctx, argc - optind, &argv[optind]);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
}
|