diff options
Diffstat (limited to 'roms/skiboot/hw/fsp/fsp-sysparam.c')
-rw-r--r-- | roms/skiboot/hw/fsp/fsp-sysparam.c | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/roms/skiboot/hw/fsp/fsp-sysparam.c b/roms/skiboot/hw/fsp/fsp-sysparam.c new file mode 100644 index 000000000..adb424e5e --- /dev/null +++ b/roms/skiboot/hw/fsp/fsp-sysparam.c @@ -0,0 +1,508 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * There's some system level parameters that aren't over IPMI or NVRAM + * but that the FSP exposes through this interface. + * + * We expose these through an OPAL API as there really isn't any other/better + * way of doing so. + * + * Copyright 2013-2017 IBM Corp. + */ + +#include <skiboot.h> +#include <fsp.h> +#include <opal.h> +#include <device.h> +#include <lock.h> +#include <processor.h> +#include <psi.h> +#include <opal-msg.h> +#include <fsp-sysparam.h> + +struct sysparam_comp_data { + uint32_t param_len; + uint64_t async_token; +}; + +struct sysparam_req { + sysparam_compl_t completion; + void *comp_data; + void *ubuf; + uint32_t ulen; + struct fsp_msg msg; + struct fsp_msg resp; + bool done; +}; + +static struct sysparam_attr { + const char *name; + uint32_t id; + uint32_t length; + uint8_t perm; +} sysparam_attrs[] = { +#define _R OPAL_SYSPARAM_READ +#define _W OPAL_SYSPARAM_WRITE +#define _RW OPAL_SYSPARAM_RW + {"surveillance", SYS_PARAM_SURV, 4, _RW}, + {"hmc-management", SYS_PARAM_HMC_MANAGED, 4, _R}, + {"cupd-policy", SYS_PARAM_FLASH_POLICY, 4, _RW}, + {"plat-hmc-managed", SYS_PARAM_NEED_HMC, 4, _RW}, + {"fw-license-policy", SYS_PARAM_FW_LICENSE, 4, _RW}, + {"world-wide-port-num", SYS_PARAM_WWPN, 12, _W}, + {"default-boot-device", SYS_PARAM_DEF_BOOT_DEV, 1, _RW}, + {"next-boot-device", SYS_PARAM_NEXT_BOOT_DEV,1, _RW}, + {"console-select", SYS_PARAM_CONSOLE_SELECT,1, _RW}, + {"boot-device-path", SYS_PARAM_BOOT_DEV_PATH,48, _RW} +#undef _R +#undef _W +#undef _RW +}; + +static int fsp_sysparam_process(struct sysparam_req *r) +{ + u32 param_id, len; + int stlen = 0; + u8 fstat; + /* Snapshot completion before we set the "done" flag */ + sysparam_compl_t comp = r->completion; + void *cdata = r->comp_data; + + if (r->msg.state != fsp_msg_done) { + prerror("FSP: Request for sysparam 0x%x got FSP failure!\n", + fsp_msg_get_data_word(&r->msg, 0)); + stlen = -1; /* XXX Find saner error codes */ + goto complete; + } + + param_id = fsp_msg_get_data_word(&r->resp, 0); + len = fsp_msg_get_data_word(&r->resp, 1) & 0xffff; + + /* Check params validity */ + if (param_id != fsp_msg_get_data_word(&r->msg, 0)) { + prerror("FSP: Request for sysparam 0x%x got resp. for 0x%x!\n", + fsp_msg_get_data_word(&r->msg, 0), param_id); + stlen = -2; /* XXX Sane error codes */ + goto complete; + } + if (len > r->ulen) { + prerror("FSP: Request for sysparam 0x%x truncated!\n", + param_id); + len = r->ulen; + } + + /* Decode the request status */ + fstat = (r->msg.resp->word1 >> 8) & 0xff; + switch(fstat) { + case 0x00: /* XXX Is that even possible ? */ + case 0x11: /* Data in request */ + memcpy(r->ubuf, &r->resp.data.bytes[8], len); + /* fallthrough */ + case 0x12: /* Data in TCE */ + stlen = len; + break; + default: + stlen = -fstat; + } + complete: + /* Call completion if any */ + if (comp) + comp(fsp_msg_get_data_word(&r->msg, 0), stlen, cdata); + + free(r); + + return stlen; +} + +static void fsp_sysparam_get_complete(struct fsp_msg *msg) +{ + struct sysparam_req *r = container_of(msg, struct sysparam_req, msg); + + /* If it's an asynchronous request, process it now */ + if (r->completion) { + fsp_sysparam_process(r); + return; + } + + /* Else just set the done flag */ + + /* Another CPU can be polling on the "done" flag without the + * lock held, so let's order the udpates to the structure + */ + lwsync(); + r->done = true; +} + +int fsp_get_sys_param(uint32_t param_id, void *buffer, uint32_t length, + sysparam_compl_t async_complete, void *comp_data) +{ + struct sysparam_req *r; + uint64_t baddr, tce_token; + int rc; + + if (!fsp_present()) + return -ENODEV; + /* + * XXX FIXME: We currently always allocate the sysparam_req here + * however, we want to avoid runtime allocations as much as + * possible, so if this is going to be used a lot at runtime, + * we probably want to pre-allocate a pool of these + */ + if (length > 4096) + return -EINVAL; + r = zalloc(sizeof(struct sysparam_req)); + if (!r) + return -ENOMEM; + r->completion = async_complete; + r->comp_data = comp_data; + r->done = false; + r->ubuf = buffer; + r->ulen = length; + r->msg.resp = &r->resp; + + /* Map always 1 page ... easier that way and none of that + * is performance critical + */ + baddr = (uint64_t)buffer; + fsp_tce_map(PSI_DMA_GET_SYSPARAM, (void *)(baddr & ~0xffful), 0x1000); + tce_token = PSI_DMA_GET_SYSPARAM | (baddr & 0xfff); + fsp_fillmsg(&r->msg, FSP_CMD_QUERY_SPARM, 3, + param_id, length, tce_token); + rc = fsp_queue_msg(&r->msg, fsp_sysparam_get_complete); + + if (rc) + free(r); + + /* Asynchronous operation or queueing failure, return */ + if (rc || async_complete) + return rc; + + /* Synchronous operation requested, spin and process */ + while(!r->done) + opal_run_pollers(); + + /* Will free the request */ + return fsp_sysparam_process(r); +} + +static void fsp_opal_getparam_complete(uint32_t param_id __unused, int err_len, + void *data) +{ + struct sysparam_comp_data *comp_data = data; + int rc = OPAL_SUCCESS; + + if (comp_data->param_len != err_len) + rc = OPAL_INTERNAL_ERROR; + + opal_queue_msg(OPAL_MSG_ASYNC_COMP, NULL, NULL, + cpu_to_be64(comp_data->async_token), + cpu_to_be64(rc)); + free(comp_data); +} + +static void fsp_opal_setparam_complete(struct fsp_msg *msg) +{ + struct sysparam_comp_data *comp_data = msg->user_data; + u8 fstat; + uint32_t param_id; + int rc = OPAL_SUCCESS; + + if (msg->state != fsp_msg_done) { + prerror("FSP: Request for set sysparam 0x%x got FSP failure!\n", + fsp_msg_get_data_word(msg, 0)); + rc = OPAL_INTERNAL_ERROR; + goto out; + } + + param_id = fsp_msg_get_data_word(msg->resp, 0); + if (param_id != fsp_msg_get_data_word(msg, 0)) { + prerror("FSP: Request for set sysparam 0x%x got resp. for 0x%x!" + "\n", fsp_msg_get_data_word(msg, 0), param_id); + rc = OPAL_INTERNAL_ERROR; + goto out; + } + + fstat = (msg->resp->word1 >> 8) & 0xff; + switch (fstat) { + case 0x00: + rc = OPAL_SUCCESS; + break; + case 0x22: + prerror("%s: Response status 0x%x, invalid data\n", __func__, + fstat); + rc = OPAL_INTERNAL_ERROR; + break; + case 0x24: + prerror("%s: Response status 0x%x, DMA error\n", __func__, + fstat); + rc = OPAL_INTERNAL_ERROR; + break; + default: + rc = OPAL_INTERNAL_ERROR; + break; + } + +out: + opal_queue_msg(OPAL_MSG_ASYNC_COMP, NULL, NULL, + cpu_to_be64(comp_data->async_token), + cpu_to_be64(rc)); + free(comp_data); + fsp_freemsg(msg); +} + +/* OPAL interface for PowerNV to read the system parameter from FSP */ +static int64_t fsp_opal_get_param(uint64_t async_token, uint32_t param_id, + uint64_t buffer, uint64_t length) +{ + struct sysparam_comp_data *comp_data; + int count, rc, i; + + if (!fsp_present()) + return OPAL_HARDWARE; + + count = ARRAY_SIZE(sysparam_attrs); + for (i = 0; i < count; i++) + if (sysparam_attrs[i].id == param_id) + break; + if (i == count) + return OPAL_PARAMETER; + + if (length < sysparam_attrs[i].length) + return OPAL_PARAMETER; + if (!(sysparam_attrs[i].perm & OPAL_SYSPARAM_READ)) + return OPAL_PERMISSION; + + comp_data = zalloc(sizeof(struct sysparam_comp_data)); + if (!comp_data) + return OPAL_NO_MEM; + + comp_data->param_len = sysparam_attrs[i].length; + comp_data->async_token = async_token; + rc = fsp_get_sys_param(param_id, (void *)buffer, + sysparam_attrs[i].length, fsp_opal_getparam_complete, + comp_data); + if (rc) { + free(comp_data); + prerror("%s: Error %d queuing param request\n", __func__, rc); + return OPAL_INTERNAL_ERROR; + } + + return OPAL_ASYNC_COMPLETION; +} + +/* OPAL interface for PowerNV to update the system parameter to FSP */ +static int64_t fsp_opal_set_param(uint64_t async_token, uint32_t param_id, + uint64_t buffer, uint64_t length) +{ + struct sysparam_comp_data *comp_data; + struct fsp_msg *msg; + uint64_t tce_token; + int count, rc, i; + + if (!fsp_present()) + return OPAL_HARDWARE; + + count = ARRAY_SIZE(sysparam_attrs); + for (i = 0; i < count; i++) + if (sysparam_attrs[i].id == param_id) + break; + if (i == count) + return OPAL_PARAMETER; + + if (length < sysparam_attrs[i].length) + return OPAL_PARAMETER; + if (!(sysparam_attrs[i].perm & OPAL_SYSPARAM_WRITE)) + return OPAL_PERMISSION; + + fsp_tce_map(PSI_DMA_SET_SYSPARAM, (void *)(buffer & ~0xffful), 0x1000); + tce_token = PSI_DMA_SET_SYSPARAM | (buffer & 0xfff); + + msg = fsp_mkmsg(FSP_CMD_SET_SPARM_2, 4, param_id, length, + tce_token >> 32, tce_token); + if (!msg) { + prerror("%s: Failed to allocate the message\n", __func__); + return OPAL_INTERNAL_ERROR; + } + + comp_data = zalloc(sizeof(struct sysparam_comp_data)); + if (!comp_data) { + fsp_freemsg(msg); + return OPAL_NO_MEM; + } + + comp_data->param_len = length; + comp_data->async_token = async_token; + msg->user_data = comp_data; + + rc = fsp_queue_msg(msg, fsp_opal_setparam_complete); + if (rc) { + free(comp_data); + fsp_freemsg(msg); + prerror("%s: Failed to queue the message\n", __func__); + return OPAL_INTERNAL_ERROR; + } + + return OPAL_ASYNC_COMPLETION; +} + +struct sysparam_notify_entry { + struct list_node link; + sysparam_update_notify notify; +}; + +static LIST_HEAD(sysparam_update_notifiers); + +/* Add client to notifier chain */ +void sysparam_add_update_notifier(sysparam_update_notify notify) +{ + struct sysparam_notify_entry *entry; + + entry = zalloc(sizeof(struct sysparam_notify_entry)); + assert(entry); + + entry->notify = notify; + list_add_tail(&sysparam_update_notifiers, &entry->link); +} + +/* Remove client from notifier chain */ +void sysparam_del_update_notifier(sysparam_update_notify notify) +{ + struct sysparam_notify_entry *entry; + + list_for_each(&sysparam_update_notifiers, entry, link) { + if (entry->notify == notify) { + list_del(&entry->link); + free(entry); + return; + } + } +} + +/* Update notification chain */ +static void sysparam_run_update_notifier(struct fsp_msg *msg) +{ + bool ret; + struct sysparam_notify_entry *entry; + + list_for_each(&sysparam_update_notifiers, entry, link) { + ret = entry->notify(msg); + if (ret == true) + break; + } +} + +static bool fsp_sysparam_msg(u32 cmd_sub_mod, struct fsp_msg *msg) +{ + struct fsp_msg *rsp; + int rc = -ENOMEM; + + switch(cmd_sub_mod) { + case FSP_CMD_SP_SPARM_UPD_0: + case FSP_CMD_SP_SPARM_UPD_1: + printf("FSP: Got sysparam update, param ID 0x%x\n", + fsp_msg_get_data_word(msg, 0)); + + sysparam_run_update_notifier(msg); + + rsp = fsp_mkmsg((cmd_sub_mod & 0xffff00) | 0x008000, 0); + if (rsp) + rc = fsp_queue_msg(rsp, fsp_freemsg); + if (rc) { + prerror("FSP: Error %d queuing sysparam reply\n", rc); + /* What to do here ? R/R ? */ + fsp_freemsg(rsp); + } + return true; + } + return false; +} + +static struct fsp_client fsp_sysparam_client = { + .message = fsp_sysparam_msg, +}; + +static void add_opal_sysparam_node(void) +{ + struct dt_node *sysparams; + char *names, *s; + __be32 *ids, *lens; + uint8_t *perms; + unsigned int i, count, size = 0; + + if (!fsp_present()) + return; + + sysparams = dt_new(opal_node, "sysparams"); + dt_add_property_string(sysparams, "compatible", "ibm,opal-sysparams"); + + count = ARRAY_SIZE(sysparam_attrs); + for (i = 0; i < count; i++) + size = size + strlen(sysparam_attrs[i].name) + 1; + + names = zalloc(size); + if (!names) { + prerror("%s: Failed to allocate memory for parameter names\n", + __func__); + return; + } + + ids = zalloc(count * sizeof(*ids)); + if (!ids) { + prerror("%s: Failed to allocate memory for parameter ids\n", + __func__); + goto out_free_name; + } + + lens = zalloc(count * sizeof(*lens)); + if (!lens) { + prerror("%s: Failed to allocate memory for parameter length\n", + __func__); + goto out_free_id; + } + + perms = zalloc(count * sizeof(*perms)); + if (!perms) { + prerror("%s: Failed to allocate memory for parameter length\n", + __func__); + goto out_free_len; + } + + s = names; + for (i = 0; i < count; i++) { + strcpy(s, sysparam_attrs[i].name); + s = s + strlen(sysparam_attrs[i].name) + 1; + + ids[i] = cpu_to_be32(sysparam_attrs[i].id); + lens[i] = cpu_to_be32(sysparam_attrs[i].length); + perms[i] = sysparam_attrs[i].perm; + } + + dt_add_property(sysparams, "param-name", names, size); + dt_add_property(sysparams, "param-id", ids, count * sizeof(*ids)); + dt_add_property(sysparams, "param-len", lens, count * sizeof(*lens)); + dt_add_property(sysparams, "param-perm", perms, count * sizeof(*perms)); + + free(perms); + +out_free_len: + free(lens); +out_free_id: + free(ids); +out_free_name: + free(names); +} + +void fsp_sysparam_init(void) +{ + if (!fsp_present()) + return; + + /* Register change notifications */ + fsp_register_client(&fsp_sysparam_client, FSP_MCLASS_SERVICE); + + /* Register OPAL interfaces */ + opal_register(OPAL_GET_PARAM, fsp_opal_get_param, 4); + opal_register(OPAL_SET_PARAM, fsp_opal_set_param, 4); + + /* Add device-tree nodes */ + add_opal_sysparam_node(); +} |