| // 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(); |
| } |