424 lines
10 KiB
C
424 lines
10 KiB
C
/* Copyright 2013-2019 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 <xscom.h>
|
|
#include <xscom-p8-regs.h>
|
|
#include <io.h>
|
|
#include <cpu.h>
|
|
#include <chip.h>
|
|
#include <mem_region.h>
|
|
#include <fsp.h>
|
|
#include <timebase.h>
|
|
#include <hostservices.h>
|
|
#include <errorlog.h>
|
|
#include <opal-api.h>
|
|
#include <opal-msg.h>
|
|
#include <timer.h>
|
|
#include <i2c.h>
|
|
#include <powercap.h>
|
|
#include <psr.h>
|
|
#include <sensor.h>
|
|
#include <occ.h>
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_OCC_LOAD, OPAL_PLATFORM_ERR_EVT, OPAL_OCC,
|
|
OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
|
|
OPAL_NA);
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_OCC_RESET, OPAL_PLATFORM_ERR_EVT, OPAL_OCC,
|
|
OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
|
|
OPAL_NA);
|
|
|
|
struct occ_load_req {
|
|
u8 scope;
|
|
u32 dbob_id;
|
|
u32 seq_id;
|
|
struct list_node link;
|
|
};
|
|
static LIST_HEAD(occ_load_req_list);
|
|
|
|
|
|
static void occ_queue_load(u8 scope, u32 dbob_id, u32 seq_id)
|
|
{
|
|
struct occ_load_req *occ_req;
|
|
|
|
occ_req = zalloc(sizeof(struct occ_load_req));
|
|
if (!occ_req) {
|
|
/**
|
|
* @fwts-label OCCload_reqENOMEM
|
|
* @fwts-advice ENOMEM while allocating OCC load message.
|
|
* OCCs not started, consequently no power/frequency scaling
|
|
* will be functional.
|
|
*/
|
|
prlog(PR_ERR, "OCC: Could not allocate occ_load_req\n");
|
|
return;
|
|
}
|
|
|
|
occ_req->scope = scope;
|
|
occ_req->dbob_id = dbob_id;
|
|
occ_req->seq_id = seq_id;
|
|
list_add_tail(&occ_load_req_list, &occ_req->link);
|
|
}
|
|
|
|
static void __occ_do_load(u8 scope, u32 dbob_id __unused, u32 seq_id)
|
|
{
|
|
struct fsp_msg *stat;
|
|
int rc = -ENOMEM;
|
|
int status_word = 0;
|
|
struct proc_chip *chip = next_chip(NULL);
|
|
|
|
/* Call HBRT... */
|
|
rc = host_services_occ_load();
|
|
|
|
/* Handle fallback to preload */
|
|
if (rc == -ENOENT && chip->homer_base) {
|
|
prlog(PR_INFO, "OCC: Load: Fallback to preloaded image\n");
|
|
rc = 0;
|
|
} else if (!rc) {
|
|
struct opal_occ_msg occ_msg = { CPU_TO_BE64(OCC_LOAD), 0, 0 };
|
|
|
|
rc = _opal_queue_msg(OPAL_MSG_OCC, NULL, NULL,
|
|
sizeof(struct opal_occ_msg), &occ_msg);
|
|
if (rc)
|
|
prlog(PR_INFO, "OCC: Failed to queue message %d\n",
|
|
OCC_LOAD);
|
|
|
|
/* Success, start OCC */
|
|
rc = host_services_occ_start();
|
|
}
|
|
if (rc) {
|
|
/* If either of hostservices call fail, send fail to FSP */
|
|
/* Find a chip ID to send failure */
|
|
for_each_chip(chip) {
|
|
if (scope == 0x01 && dbob_id != chip->dbob_id)
|
|
continue;
|
|
status_word = 0xB500 | (chip->pcid & 0xff);
|
|
break;
|
|
}
|
|
log_simple_error(&e_info(OPAL_RC_OCC_LOAD),
|
|
"OCC: Error %d in load/start OCC\n", rc);
|
|
}
|
|
|
|
/* Send a single response for all chips */
|
|
stat = fsp_mkmsg(FSP_CMD_LOAD_OCC_STAT, 2, status_word, seq_id);
|
|
if (stat)
|
|
rc = fsp_queue_msg(stat, fsp_freemsg);
|
|
if (rc) {
|
|
log_simple_error(&e_info(OPAL_RC_OCC_LOAD),
|
|
"OCC: Error %d queueing FSP OCC LOAD STATUS msg", rc);
|
|
fsp_freemsg(stat);
|
|
}
|
|
}
|
|
|
|
void occ_poke_load_queue(void)
|
|
{
|
|
struct occ_load_req *occ_req, *next;
|
|
|
|
if (list_empty(&occ_load_req_list))
|
|
return;
|
|
|
|
list_for_each_safe(&occ_load_req_list, occ_req, next, link) {
|
|
__occ_do_load(occ_req->scope, occ_req->dbob_id,
|
|
occ_req->seq_id);
|
|
list_del(&occ_req->link);
|
|
free(occ_req);
|
|
}
|
|
}
|
|
|
|
static u32 last_seq_id;
|
|
static bool in_ipl = true;
|
|
static void occ_do_load(u8 scope, u32 dbob_id __unused, u32 seq_id)
|
|
{
|
|
struct fsp_msg *rsp;
|
|
int rc = -ENOMEM;
|
|
u8 err = 0;
|
|
|
|
if (scope != 0x01 && scope != 0x02) {
|
|
/**
|
|
* @fwts-label OCCLoadInvalidScope
|
|
* @fwts-advice Invalid request for loading OCCs. Power and
|
|
* frequency management not functional
|
|
*/
|
|
prlog(PR_ERR, "OCC: Load message with invalid scope 0x%x\n",
|
|
scope);
|
|
err = 0x22;
|
|
}
|
|
|
|
/* First queue up an OK response to the load message itself */
|
|
rsp = fsp_mkmsg(FSP_RSP_LOAD_OCC | err, 0);
|
|
if (rsp)
|
|
rc = fsp_queue_msg(rsp, fsp_freemsg);
|
|
if (rc) {
|
|
log_simple_error(&e_info(OPAL_RC_OCC_LOAD),
|
|
"OCC: Error %d queueing FSP OCC LOAD reply\n", rc);
|
|
fsp_freemsg(rsp);
|
|
return;
|
|
}
|
|
|
|
if (err)
|
|
return;
|
|
|
|
if (proc_gen == proc_gen_p9) {
|
|
if (in_ipl) {
|
|
/* OCC is pre-loaded in P9, so send SUCCESS to FSP */
|
|
rsp = fsp_mkmsg(FSP_CMD_LOAD_OCC_STAT, 2, 0, seq_id);
|
|
if (!rsp)
|
|
return;
|
|
|
|
rc = fsp_queue_msg(rsp, fsp_freemsg);
|
|
if (rc) {
|
|
log_simple_error(&e_info(OPAL_RC_OCC_LOAD),
|
|
"OCC: Error %d queueing OCC LOAD STATUS msg",
|
|
rc);
|
|
fsp_freemsg(rsp);
|
|
}
|
|
in_ipl = false;
|
|
} else {
|
|
struct proc_chip *chip = next_chip(NULL);
|
|
|
|
last_seq_id = seq_id;
|
|
prd_fsp_occ_load_start(chip->id);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check if hostservices lid caching is complete. If not, queue
|
|
* the load request.
|
|
*/
|
|
if (!hservices_lid_preload_complete()) {
|
|
occ_queue_load(scope, dbob_id, seq_id);
|
|
return;
|
|
}
|
|
|
|
__occ_do_load(scope, dbob_id, seq_id);
|
|
}
|
|
|
|
int fsp_occ_reset_status(u64 chipid, s64 status)
|
|
{
|
|
struct fsp_msg *stat;
|
|
int rc = OPAL_NO_MEM;
|
|
int status_word = 0;
|
|
|
|
prlog(PR_INFO, "HBRT: OCC stop() completed with %lld\n", status);
|
|
|
|
if (status) {
|
|
struct proc_chip *chip = get_chip(chipid);
|
|
|
|
if (!chip)
|
|
return OPAL_PARAMETER;
|
|
|
|
status_word = 0xfe00 | (chip->pcid & 0xff);
|
|
log_simple_error(&e_info(OPAL_RC_OCC_RESET),
|
|
"OCC: Error %lld in OCC reset of chip %lld\n",
|
|
status, chipid);
|
|
} else {
|
|
occ_msg_queue_occ_reset();
|
|
}
|
|
|
|
stat = fsp_mkmsg(FSP_CMD_RESET_OCC_STAT, 2, status_word, last_seq_id);
|
|
if (!stat)
|
|
return rc;
|
|
|
|
rc = fsp_queue_msg(stat, fsp_freemsg);
|
|
if (rc) {
|
|
fsp_freemsg(stat);
|
|
log_simple_error(&e_info(OPAL_RC_OCC_RESET),
|
|
"OCC: Error %d queueing FSP OCC RESET STATUS message\n",
|
|
rc);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int fsp_occ_load_start_status(u64 chipid, s64 status)
|
|
{
|
|
struct fsp_msg *stat;
|
|
int rc = OPAL_NO_MEM;
|
|
int status_word = 0;
|
|
|
|
if (status) {
|
|
struct proc_chip *chip = get_chip(chipid);
|
|
|
|
if (!chip)
|
|
return OPAL_PARAMETER;
|
|
|
|
status_word = 0xB500 | (chip->pcid & 0xff);
|
|
log_simple_error(&e_info(OPAL_RC_OCC_LOAD),
|
|
"OCC: Error %d in load/start OCC %lld\n", rc,
|
|
chipid);
|
|
}
|
|
|
|
stat = fsp_mkmsg(FSP_CMD_LOAD_OCC_STAT, 2, status_word, last_seq_id);
|
|
if (!stat)
|
|
return rc;
|
|
|
|
rc = fsp_queue_msg(stat, fsp_freemsg);
|
|
if (rc) {
|
|
fsp_freemsg(stat);
|
|
log_simple_error(&e_info(OPAL_RC_OCC_LOAD),
|
|
"OCC: Error %d queueing FSP OCC LOAD STATUS msg", rc);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void occ_do_reset(u8 scope, u32 dbob_id, u32 seq_id)
|
|
{
|
|
struct fsp_msg *rsp, *stat;
|
|
struct proc_chip *chip = next_chip(NULL);
|
|
int rc = -ENOMEM;
|
|
u8 err = 0;
|
|
|
|
/* Check arguments */
|
|
if (scope != 0x01 && scope != 0x02) {
|
|
/**
|
|
* @fwts-label OCCResetInvalidScope
|
|
* @fwts-advice Invalid request for resetting OCCs. Power and
|
|
* frequency management not functional
|
|
*/
|
|
prlog(PR_ERR, "OCC: Reset message with invalid scope 0x%x\n",
|
|
scope);
|
|
err = 0x22;
|
|
}
|
|
|
|
/* First queue up an OK response to the reset message itself */
|
|
rsp = fsp_mkmsg(FSP_RSP_RESET_OCC | err, 0);
|
|
if (rsp)
|
|
rc = fsp_queue_msg(rsp, fsp_freemsg);
|
|
if (rc) {
|
|
fsp_freemsg(rsp);
|
|
log_simple_error(&e_info(OPAL_RC_OCC_RESET),
|
|
"OCC: Error %d queueing FSP OCC RESET reply\n", rc);
|
|
return;
|
|
}
|
|
|
|
/* If we had an error, return */
|
|
if (err)
|
|
return;
|
|
|
|
/*
|
|
* Call HBRT to stop OCC and leave it stopped. FSP will send load/start
|
|
* request subsequently. Also after few runtime restarts (currently 3),
|
|
* FSP will request OCC to left in stopped state.
|
|
*/
|
|
|
|
switch (proc_gen) {
|
|
case proc_gen_p8:
|
|
rc = host_services_occ_stop();
|
|
break;
|
|
case proc_gen_p9:
|
|
last_seq_id = seq_id;
|
|
chip = next_chip(NULL);
|
|
prd_fsp_occ_reset(chip->id);
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
/* Handle fallback to preload */
|
|
if (rc == -ENOENT && chip->homer_base) {
|
|
prlog(PR_INFO, "OCC: Reset: Fallback to preloaded image\n");
|
|
rc = 0;
|
|
}
|
|
if (!rc) {
|
|
/* Send a single success response for all chips */
|
|
stat = fsp_mkmsg(FSP_CMD_RESET_OCC_STAT, 2, 0, seq_id);
|
|
if (stat)
|
|
rc = fsp_queue_msg(stat, fsp_freemsg);
|
|
if (rc) {
|
|
fsp_freemsg(stat);
|
|
log_simple_error(&e_info(OPAL_RC_OCC_RESET),
|
|
"OCC: Error %d queueing FSP OCC RESET"
|
|
" STATUS message\n", rc);
|
|
}
|
|
occ_msg_queue_occ_reset();
|
|
} else {
|
|
|
|
/*
|
|
* Then send a matching OCC Reset Status message with an 0xFE
|
|
* (fail) response code as well to the first matching chip
|
|
*/
|
|
for_each_chip(chip) {
|
|
if (scope == 0x01 && dbob_id != chip->dbob_id)
|
|
continue;
|
|
rc = -ENOMEM;
|
|
stat = fsp_mkmsg(FSP_CMD_RESET_OCC_STAT, 2,
|
|
0xfe00 | (chip->pcid & 0xff), seq_id);
|
|
if (stat)
|
|
rc = fsp_queue_msg(stat, fsp_freemsg);
|
|
if (rc) {
|
|
fsp_freemsg(stat);
|
|
log_simple_error(&e_info(OPAL_RC_OCC_RESET),
|
|
"OCC: Error %d queueing FSP OCC RESET"
|
|
" STATUS message\n", rc);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool fsp_occ_msg(u32 cmd_sub_mod, struct fsp_msg *msg)
|
|
{
|
|
u32 dbob_id, seq_id;
|
|
u8 scope;
|
|
|
|
switch (cmd_sub_mod) {
|
|
case FSP_CMD_LOAD_OCC:
|
|
/*
|
|
* We get the "Load OCC" command at boot. We don't currently
|
|
* support loading it ourselves (we don't have the procedures,
|
|
* they will come with Host Services). For now HostBoot will
|
|
* have loaded a OCC firmware for us, but we still need to
|
|
* be nice and respond to OCC.
|
|
*/
|
|
scope = msg->data.bytes[3];
|
|
dbob_id = msg->data.words[1];
|
|
seq_id = msg->data.words[2];
|
|
prlog(PR_INFO, "OCC: Got OCC Load message, scope=0x%x"
|
|
" dbob=0x%x seq=0x%x\n", scope, dbob_id, seq_id);
|
|
occ_do_load(scope, dbob_id, seq_id);
|
|
return true;
|
|
|
|
case FSP_CMD_RESET_OCC:
|
|
/*
|
|
* We shouldn't be getting this one, but if we do, we have
|
|
* to reply something sensible or the FSP will get upset
|
|
*/
|
|
scope = msg->data.bytes[3];
|
|
dbob_id = msg->data.words[1];
|
|
seq_id = msg->data.words[2];
|
|
prlog(PR_INFO, "OCC: Got OCC Reset message, scope=0x%x"
|
|
" dbob=0x%x seq=0x%x\n", scope, dbob_id, seq_id);
|
|
occ_do_reset(scope, dbob_id, seq_id);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static struct fsp_client fsp_occ_client = {
|
|
.message = fsp_occ_msg,
|
|
};
|
|
|
|
void occ_fsp_init(void)
|
|
{
|
|
/* OCC is supported in P8 and P9 */
|
|
if (proc_gen < proc_gen_p8)
|
|
return;
|
|
|
|
/* If we have an FSP, register for notifications */
|
|
if (fsp_present())
|
|
fsp_register_client(&fsp_occ_client, FSP_MCLASS_OCC);
|
|
}
|