510 lines
12 KiB
C
510 lines
12 KiB
C
/* 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 <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",
|
|
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();
|
|
}
|