// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later /* * FSP/OCC interactions * * Unlike OpenPOWER machines, FSP machines are much more tightly coupled * between FSP, host, and OCC. On P8 we have to do a dance to start the * OCC, but on P9 Hostboot does that, consistent with what we do on * OpenPOWER. * * Copyright 2013-2019 IBM Corp. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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: case proc_gen_p10: 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 = fsp_msg_get_data_word(msg, 1); seq_id = fsp_msg_get_data_word(msg, 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 = fsp_msg_get_data_word(msg, 1); seq_id = fsp_msg_get_data_word(msg, 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) { /* If we have an FSP, register for notifications */ if (fsp_present()) fsp_register_client(&fsp_occ_client, FSP_MCLASS_OCC); }