/* Copyright 2013-2014 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include 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", r->msg.data.words[0]); stlen = -1; /* XXX Find saner error codes */ goto complete; } param_id = r->resp.data.words[0]; len = r->resp.data.words[1] & 0xffff; /* Check params validity */ if (param_id != r->msg.data.words[0]) { prerror("FSP: Request for sysparam 0x%x got resp. for 0x%x!\n", r->msg.data.words[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.words[2], len); /* fallthrough */ case 0x12: /* Data in TCE */ stlen = len; break; default: stlen = -fstat; } complete: /* Call completion if any */ if (comp) comp(r->msg.data.words[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, comp_data->async_token, 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", msg->data.words[0]); rc = OPAL_INTERNAL_ERROR; goto out; } param_id = msg->resp->data.words[0]; if (param_id != msg->data.words[0]) { prerror("FSP: Request for set sysparam 0x%x got resp. for 0x%x!" "\n", msg->data.words[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, comp_data->async_token, 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", msg->data.words[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; uint32_t *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] = sysparam_attrs[i].id; lens[i] = 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(); }