1946 lines
51 KiB
C
1946 lines
51 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.
|
|
*/
|
|
|
|
|
|
/*
|
|
* LED location code and indicator handling
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "FSPLED: " fmt
|
|
#include <skiboot.h>
|
|
#include <fsp.h>
|
|
#include <device.h>
|
|
#include <spcn.h>
|
|
#include <lock.h>
|
|
#include <errorlog.h>
|
|
#include <opal.h>
|
|
#include <opal-msg.h>
|
|
#include <fsp-leds.h>
|
|
#include <fsp-sysparam.h>
|
|
|
|
#define buf_write(p, type, val) do { *(type *)(p) = val;\
|
|
p += sizeof(type); } while(0)
|
|
#define buf_read(p, type, addr) do { *addr = *(type *)(p);\
|
|
p += sizeof(type); } while(0)
|
|
|
|
/* SPCN replay threshold */
|
|
#define SPCN_REPLAY_THRESHOLD 2
|
|
|
|
/* LED support status */
|
|
enum led_support_state {
|
|
LED_STATE_ABSENT,
|
|
LED_STATE_READING,
|
|
LED_STATE_PRESENT,
|
|
};
|
|
|
|
static enum led_support_state led_support = LED_STATE_ABSENT;
|
|
|
|
/*
|
|
* PSI mapped buffer for LED data
|
|
*
|
|
* Mapped once and never unmapped. Used for fetching all
|
|
* available LED information and creating the list. Also
|
|
* used for setting individual LED state.
|
|
*
|
|
*/
|
|
static void *led_buffer;
|
|
static u8 *loc_code_list_buffer = NULL;
|
|
|
|
/* Maintain list of all LEDs
|
|
*
|
|
* The contents here will be used to cater requests from FSP
|
|
* async commands and HV initiated OPAL calls.
|
|
*/
|
|
static struct list_head cec_ledq; /* CEC LED list */
|
|
static struct list_head encl_ledq; /* Enclosure LED list */
|
|
static struct list_head spcn_cmdq; /* SPCN command queue */
|
|
|
|
/* LED lock */
|
|
static struct lock led_lock = LOCK_UNLOCKED;
|
|
static struct lock spcn_cmd_lock = LOCK_UNLOCKED;
|
|
static struct lock sai_lock = LOCK_UNLOCKED;
|
|
|
|
static bool spcn_cmd_complete = true; /* SPCN command complete */
|
|
|
|
/* Last SPCN command */
|
|
static u32 last_spcn_cmd;
|
|
static int replay = 0;
|
|
|
|
/*
|
|
* FSP controls System Attention Indicator. But it expects hypervisor
|
|
* keep track of the status and serve get LED state request (both from
|
|
* Linux and FSP itself)!
|
|
*/
|
|
static struct sai_data sai_data;
|
|
|
|
/* Forward declaration */
|
|
static void fsp_read_leds_data_complete(struct fsp_msg *msg);
|
|
static int process_led_state_change(void);
|
|
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_LED_SPCN, OPAL_PLATFORM_ERR_EVT, OPAL_LED,
|
|
OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL,
|
|
OPAL_NA);
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_LED_BUFF, OPAL_PLATFORM_ERR_EVT, OPAL_LED,
|
|
OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL,
|
|
OPAL_NA);
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_LED_LC, OPAL_PLATFORM_ERR_EVT, OPAL_LED,
|
|
OPAL_PLATFORM_FIRMWARE, OPAL_INFO, OPAL_NA);
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_LED_STATE, OPAL_PLATFORM_ERR_EVT, OPAL_LED,
|
|
OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL,
|
|
OPAL_NA);
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_LED_SUPPORT, OPAL_PLATFORM_ERR_EVT, OPAL_LED,
|
|
OPAL_PLATFORM_FIRMWARE, OPAL_INFO, OPAL_NA);
|
|
|
|
|
|
/* Find descendent LED record with CEC location code in CEC list */
|
|
static struct fsp_led_data *fsp_find_cec_led(char *loc_code)
|
|
{
|
|
struct fsp_led_data *led, *next;
|
|
|
|
list_for_each_safe(&cec_ledq, led, next, link) {
|
|
if (strcmp(led->loc_code, loc_code))
|
|
continue;
|
|
return led;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Find encl LED record with ENCL location code in ENCL list */
|
|
static struct fsp_led_data *fsp_find_encl_led(char *loc_code)
|
|
{
|
|
struct fsp_led_data *led, *next;
|
|
|
|
list_for_each_safe(&encl_ledq, led, next, link) {
|
|
if (strcmp(led->loc_code, loc_code))
|
|
continue;
|
|
return led;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Find encl LED record with CEC location code in CEC list */
|
|
static struct fsp_led_data *fsp_find_encl_cec_led(char *loc_code)
|
|
{
|
|
struct fsp_led_data *led, *next;
|
|
|
|
list_for_each_safe(&cec_ledq, led, next, link) {
|
|
if (strstr(led->loc_code, "-"))
|
|
continue;
|
|
if (!strstr(loc_code, led->loc_code))
|
|
continue;
|
|
return led;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Find encl LED record with CEC location code in ENCL list */
|
|
static struct fsp_led_data *fsp_find_encl_encl_led(char *loc_code)
|
|
{
|
|
struct fsp_led_data *led, *next;
|
|
|
|
list_for_each_safe(&encl_ledq, led, next, link) {
|
|
if (!strstr(loc_code, led->loc_code))
|
|
continue;
|
|
return led;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Compute the ENCL LED status in CEC list */
|
|
static void compute_encl_status_cec(struct fsp_led_data *encl_led)
|
|
{
|
|
struct fsp_led_data *led, *next;
|
|
|
|
encl_led->status &= ~SPCN_LED_IDENTIFY_MASK;
|
|
encl_led->status &= ~SPCN_LED_FAULT_MASK;
|
|
|
|
list_for_each_safe(&cec_ledq, led, next, link) {
|
|
if (!strstr(led->loc_code, encl_led->loc_code))
|
|
continue;
|
|
|
|
/* Don't count the enclsure LED itself */
|
|
if (!strcmp(led->loc_code, encl_led->loc_code))
|
|
continue;
|
|
|
|
if (led->status & SPCN_LED_IDENTIFY_MASK)
|
|
encl_led->status |= SPCN_LED_IDENTIFY_MASK;
|
|
|
|
if (led->status & SPCN_LED_FAULT_MASK)
|
|
encl_led->status |= SPCN_LED_FAULT_MASK;
|
|
}
|
|
}
|
|
|
|
/* Is a enclosure LED */
|
|
static bool is_enclosure_led(char *loc_code)
|
|
{
|
|
if (strstr(loc_code, "-"))
|
|
return false;
|
|
if (!fsp_find_cec_led(loc_code) || !fsp_find_encl_led(loc_code))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static inline void opal_led_update_complete(u64 async_token, u64 result)
|
|
{
|
|
opal_queue_msg(OPAL_MSG_ASYNC_COMP, NULL, NULL, async_token, result);
|
|
}
|
|
|
|
static inline bool is_sai_loc_code(const char *loc_code)
|
|
{
|
|
if (!loc_code)
|
|
return false;
|
|
|
|
if (!strncmp(sai_data.loc_code, loc_code, strlen(sai_data.loc_code)))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Set/Reset System attention indicator */
|
|
static void fsp_set_sai_complete(struct fsp_msg *msg)
|
|
{
|
|
int ret = OPAL_SUCCESS;
|
|
int rc = msg->resp->word1 & 0xff00;
|
|
struct led_set_cmd *spcn_cmd = (struct led_set_cmd *)msg->user_data;
|
|
|
|
if (rc) {
|
|
/**
|
|
* @fwts-label FSPSAIFailed
|
|
* @fwts-advice Failed to update System Attention Indicator.
|
|
* Likely means some bug with OPAL interacting with FSP.
|
|
*/
|
|
prlog(PR_ERR, "Update SAI cmd failed [rc=%d].\n", rc);
|
|
ret = OPAL_INTERNAL_ERROR;
|
|
|
|
/* Roll back */
|
|
lock(&sai_lock);
|
|
sai_data.state = spcn_cmd->ckpt_status;
|
|
unlock(&sai_lock);
|
|
}
|
|
|
|
if (spcn_cmd->cmd_src == SPCN_SRC_OPAL)
|
|
opal_led_update_complete(spcn_cmd->async_token, ret);
|
|
|
|
/* free msg and spcn command */
|
|
free(spcn_cmd);
|
|
fsp_freemsg(msg);
|
|
|
|
/* Process pending LED update request */
|
|
process_led_state_change();
|
|
}
|
|
|
|
static int fsp_set_sai(struct led_set_cmd *spcn_cmd)
|
|
{
|
|
int rc = -ENOMEM;
|
|
uint32_t cmd = FSP_CMD_SA_INDICATOR;
|
|
struct fsp_msg *msg;
|
|
|
|
/*
|
|
* FSP does not allow hypervisor to set real SAI, but we can
|
|
* reset real SAI. Also in our case only host can control
|
|
* LEDs, not guests. Hence we will set platform virtual SAI
|
|
* and reset real SAI.
|
|
*/
|
|
if (spcn_cmd->state == LED_STATE_ON)
|
|
cmd |= FSP_LED_SET_PLAT_SAI;
|
|
else
|
|
cmd |= FSP_LED_RESET_REAL_SAI;
|
|
|
|
prlog(PR_TRACE, "Update SAI Indicator [cur : 0x%x, new : 0x%x].\n",
|
|
sai_data.state, spcn_cmd->state);
|
|
|
|
msg = fsp_mkmsg(cmd, 0);
|
|
if (!msg) {
|
|
/**
|
|
* @fwts-label SAIMallocFail
|
|
* @fwts-advice OPAL ran out of memory while trying to
|
|
* allocate an FSP message in SAI code path. This indicates
|
|
* an OPAL bug that caused OPAL to run out of memory.
|
|
*/
|
|
prlog(PR_ERR, "%s: Memory allocation failed.\n", __func__);
|
|
goto sai_fail;
|
|
}
|
|
|
|
spcn_cmd->ckpt_status = sai_data.state;
|
|
msg->user_data = spcn_cmd;
|
|
rc = fsp_queue_msg(msg, fsp_set_sai_complete);
|
|
if (rc) {
|
|
fsp_freemsg(msg);
|
|
/**
|
|
* @fwts-label SAIQueueFail
|
|
* @fwts-advice Error in queueing message to FSP in SAI code
|
|
* path. Likely an OPAL bug.
|
|
*/
|
|
prlog(PR_ERR, "%s: Failed to queue the message\n", __func__);
|
|
goto sai_fail;
|
|
}
|
|
|
|
lock(&sai_lock);
|
|
sai_data.state = spcn_cmd->state;
|
|
unlock(&sai_lock);
|
|
|
|
return OPAL_SUCCESS;
|
|
|
|
sai_fail:
|
|
if (spcn_cmd->cmd_src == SPCN_SRC_OPAL)
|
|
opal_led_update_complete(spcn_cmd->async_token,
|
|
OPAL_INTERNAL_ERROR);
|
|
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
|
|
static void fsp_get_sai_complete(struct fsp_msg *msg)
|
|
{
|
|
int rc = msg->resp->word1 & 0xff00;
|
|
|
|
if (rc) {
|
|
/**
|
|
* @fwts-label FSPSAIGetFailed
|
|
* @fwts-advice Possibly an error on FSP side, OPAL failed
|
|
* to read state from FSP.
|
|
*/
|
|
prlog(PR_ERR, "Read real SAI cmd failed [rc = 0x%x].\n", rc);
|
|
} else { /* Update SAI state */
|
|
lock(&sai_lock);
|
|
sai_data.state = msg->resp->data.words[0] & 0xff;
|
|
unlock(&sai_lock);
|
|
|
|
prlog(PR_TRACE, "SAI initial state = 0x%x\n", sai_data.state);
|
|
}
|
|
|
|
fsp_freemsg(msg);
|
|
}
|
|
|
|
/* Read initial SAI state. */
|
|
static void fsp_get_sai(void)
|
|
{
|
|
int rc;
|
|
uint32_t cmd = FSP_CMD_SA_INDICATOR | FSP_LED_READ_REAL_SAI;
|
|
struct fsp_msg *msg;
|
|
|
|
msg = fsp_mkmsg(cmd, 0);
|
|
if (!msg) {
|
|
/**
|
|
* @fwts-label FSPGetSAIMallocFail
|
|
* @fwts-advice OPAL ran out of memory: OPAL bug.
|
|
*/
|
|
prlog(PR_ERR, "%s: Memory allocation failed.\n", __func__);
|
|
return;
|
|
}
|
|
rc = fsp_queue_msg(msg, fsp_get_sai_complete);
|
|
if (rc) {
|
|
fsp_freemsg(msg);
|
|
/**
|
|
* @fwts-label FSPGetSAIQueueFail
|
|
* @fwts-advice Failed to queue message to FSP: OPAL bug
|
|
*/
|
|
prlog(PR_ERR, "%s: Failed to queue the message\n", __func__);
|
|
}
|
|
}
|
|
|
|
static bool sai_update_notification(struct fsp_msg *msg)
|
|
{
|
|
uint32_t *state = &msg->data.words[2];
|
|
uint32_t param_id = msg->data.words[0];
|
|
int len = msg->data.words[1] & 0xffff;
|
|
|
|
if (param_id != SYS_PARAM_REAL_SAI && param_id != SYS_PARAM_PLAT_SAI)
|
|
return false;
|
|
|
|
if ( len != 4)
|
|
return false;
|
|
|
|
if (*state != LED_STATE_ON && *state != LED_STATE_OFF)
|
|
return false;
|
|
|
|
/* Update SAI state */
|
|
lock(&sai_lock);
|
|
sai_data.state = *state;
|
|
unlock(&sai_lock);
|
|
|
|
prlog(PR_TRACE, "SAI updated. New SAI state = 0x%x\n", *state);
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Update both the local LED lists to reflect upon led state changes
|
|
* occurred with the recent SPCN command. Subsequent LED requests will
|
|
* be served with these updates changed to the list.
|
|
*/
|
|
static void update_led_list(char *loc_code, u32 led_state, u32 excl_bit)
|
|
{
|
|
struct fsp_led_data *led = NULL, *encl_led = NULL, *encl_cec_led = NULL;
|
|
bool is_encl_led = is_enclosure_led(loc_code);
|
|
|
|
/* Enclosure LED in CEC list */
|
|
encl_cec_led = fsp_find_encl_cec_led(loc_code);
|
|
if (!encl_cec_led) {
|
|
log_simple_error(&e_info(OPAL_RC_LED_LC),
|
|
"Could not find enclosure LED in CEC LC=%s\n",
|
|
loc_code);
|
|
return;
|
|
}
|
|
|
|
/* Update state */
|
|
if (is_encl_led) {
|
|
/* Enclosure exclusive bit */
|
|
encl_cec_led->excl_bit = excl_bit;
|
|
} else { /* Descendant LED in CEC list */
|
|
led = fsp_find_cec_led(loc_code);
|
|
if (!led) {
|
|
log_simple_error(&e_info(OPAL_RC_LED_LC),
|
|
"Could not find descendent LED in \
|
|
CEC LC=%s\n", loc_code);
|
|
return;
|
|
}
|
|
led->status = led_state;
|
|
}
|
|
|
|
/* Enclosure LED in ENCL list */
|
|
encl_led = fsp_find_encl_encl_led(loc_code);
|
|
if (!encl_led) {
|
|
log_simple_error(&e_info(OPAL_RC_LED_LC),
|
|
"Could not find enclosure LED in ENCL LC=%s\n",
|
|
loc_code);
|
|
return;
|
|
}
|
|
|
|
/* Compute descendent rolled up status */
|
|
compute_encl_status_cec(encl_cec_led);
|
|
|
|
/* Check whether exclussive bits set */
|
|
if (encl_cec_led->excl_bit & FSP_LED_EXCL_FAULT)
|
|
encl_cec_led->status |= SPCN_LED_FAULT_MASK;
|
|
|
|
if (encl_cec_led->excl_bit & FSP_LED_EXCL_IDENTIFY)
|
|
encl_cec_led->status |= SPCN_LED_IDENTIFY_MASK;
|
|
|
|
/* Copy over */
|
|
encl_led->status = encl_cec_led->status;
|
|
encl_led->excl_bit = encl_cec_led->excl_bit;
|
|
}
|
|
|
|
static int fsp_set_led_response(uint32_t cmd)
|
|
{
|
|
struct fsp_msg *msg;
|
|
int rc = -1;
|
|
|
|
msg = fsp_mkmsg(cmd, 0);
|
|
if (!msg) {
|
|
prerror("Failed to allocate FSP_RSP_SET_LED_STATE [cmd=%x])\n",
|
|
cmd);
|
|
} else {
|
|
rc = fsp_queue_msg(msg, fsp_freemsg);
|
|
if (rc != OPAL_SUCCESS) {
|
|
fsp_freemsg(msg);
|
|
prerror("Failed to queue FSP_RSP_SET_LED_STATE"
|
|
" [cmd=%x]\n", cmd);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static void fsp_spcn_set_led_completion(struct fsp_msg *msg)
|
|
{
|
|
struct fsp_msg *resp = msg->resp;
|
|
u32 cmd = FSP_RSP_SET_LED_STATE;
|
|
u8 status = resp->word1 & 0xff00;
|
|
struct led_set_cmd *spcn_cmd = (struct led_set_cmd *)msg->user_data;
|
|
|
|
lock(&led_lock);
|
|
|
|
/*
|
|
* LED state update request came as part of FSP async message
|
|
* FSP_CMD_SET_LED_STATE, we need to send response message.
|
|
*
|
|
* Also if SPCN command failed, then roll back changes.
|
|
*/
|
|
if (status != FSP_STATUS_SUCCESS) {
|
|
log_simple_error(&e_info(OPAL_RC_LED_SPCN),
|
|
"Last SPCN command failed, status=%02x\n",
|
|
status);
|
|
cmd |= FSP_STATUS_GENERIC_ERROR;
|
|
|
|
/* Rollback the changes */
|
|
update_led_list(spcn_cmd->loc_code,
|
|
spcn_cmd->ckpt_status, spcn_cmd->ckpt_excl_bit);
|
|
}
|
|
|
|
/* FSP initiated SPCN command */
|
|
if (spcn_cmd->cmd_src == SPCN_SRC_FSP)
|
|
fsp_set_led_response(cmd);
|
|
|
|
/* OPAL initiated SPCN command */
|
|
if (spcn_cmd->cmd_src == SPCN_SRC_OPAL) {
|
|
if (status != FSP_STATUS_SUCCESS)
|
|
opal_led_update_complete(spcn_cmd->async_token,
|
|
OPAL_INTERNAL_ERROR);
|
|
else
|
|
opal_led_update_complete(spcn_cmd->async_token,
|
|
OPAL_SUCCESS);
|
|
}
|
|
|
|
unlock(&led_lock);
|
|
|
|
/* free msg and spcn command */
|
|
free(spcn_cmd);
|
|
fsp_freemsg(msg);
|
|
|
|
/* Process pending LED update request */
|
|
process_led_state_change();
|
|
}
|
|
|
|
/*
|
|
* Set the state of the LED pointed by the location code
|
|
*
|
|
* LED command: FAULT state or IDENTIFY state
|
|
* LED state : OFF (reset) or ON (set)
|
|
*
|
|
* SPCN TCE mapped buffer entries for setting LED state
|
|
*
|
|
* struct spcn_led_data {
|
|
* u8 lc_len;
|
|
* u16 state;
|
|
* char lc_code[LOC_CODE_SIZE];
|
|
*};
|
|
*/
|
|
static int fsp_msg_set_led_state(struct led_set_cmd *spcn_cmd)
|
|
{
|
|
struct spcn_led_data sled;
|
|
struct fsp_msg *msg = NULL;
|
|
struct fsp_led_data *led = NULL;
|
|
void *buf = led_buffer;
|
|
u16 data_len = 0;
|
|
u32 cmd_hdr = 0;
|
|
u32 cmd = FSP_RSP_SET_LED_STATE;
|
|
int rc = -1;
|
|
|
|
memset(sled.lc_code, 0, LOC_CODE_SIZE);
|
|
sled.lc_len = strlen(spcn_cmd->loc_code);
|
|
if (sled.lc_len >= LOC_CODE_SIZE)
|
|
sled.lc_len = LOC_CODE_SIZE - 1;
|
|
strncpy(sled.lc_code, spcn_cmd->loc_code, LOC_CODE_SIZE - 1);
|
|
|
|
lock(&led_lock);
|
|
|
|
/* Location code length + Location code + LED control */
|
|
data_len = LOC_CODE_LEN + sled.lc_len + LED_CONTROL_LEN;
|
|
cmd_hdr = SPCN_MOD_SET_LED_CTL_LOC_CODE << 24 | SPCN_CMD_SET << 16 |
|
|
data_len;
|
|
|
|
/* Fetch the current state of LED */
|
|
led = fsp_find_cec_led(spcn_cmd->loc_code);
|
|
|
|
/* LED not present */
|
|
if (led == NULL) {
|
|
if (spcn_cmd->cmd_src == SPCN_SRC_FSP) {
|
|
cmd |= FSP_STATUS_INVALID_LC;
|
|
fsp_set_led_response(cmd);
|
|
}
|
|
|
|
if (spcn_cmd->cmd_src == SPCN_SRC_OPAL)
|
|
opal_led_update_complete(spcn_cmd->async_token,
|
|
OPAL_INTERNAL_ERROR);
|
|
|
|
unlock(&led_lock);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Checkpoint the status here, will use it if the SPCN
|
|
* command eventually fails.
|
|
*/
|
|
spcn_cmd->ckpt_status = led->status;
|
|
spcn_cmd->ckpt_excl_bit = led->excl_bit;
|
|
sled.state = led->status;
|
|
|
|
/* Update the exclussive LED bits */
|
|
if (is_enclosure_led(spcn_cmd->loc_code)) {
|
|
if (spcn_cmd->command == LED_COMMAND_FAULT) {
|
|
if (spcn_cmd->state == LED_STATE_ON)
|
|
led->excl_bit |= FSP_LED_EXCL_FAULT;
|
|
if (spcn_cmd->state == LED_STATE_OFF)
|
|
led->excl_bit &= ~FSP_LED_EXCL_FAULT;
|
|
}
|
|
|
|
if (spcn_cmd->command == LED_COMMAND_IDENTIFY) {
|
|
if (spcn_cmd->state == LED_STATE_ON)
|
|
led->excl_bit |= FSP_LED_EXCL_IDENTIFY;
|
|
if (spcn_cmd->state == LED_STATE_OFF)
|
|
led->excl_bit &= ~FSP_LED_EXCL_IDENTIFY;
|
|
}
|
|
}
|
|
|
|
/* LED FAULT commad */
|
|
if (spcn_cmd->command == LED_COMMAND_FAULT) {
|
|
if (spcn_cmd->state == LED_STATE_ON)
|
|
sled.state |= SPCN_LED_FAULT_MASK;
|
|
if (spcn_cmd->state == LED_STATE_OFF)
|
|
sled.state &= ~SPCN_LED_FAULT_MASK;
|
|
}
|
|
|
|
/* LED IDENTIFY command */
|
|
if (spcn_cmd->command == LED_COMMAND_IDENTIFY) {
|
|
if (spcn_cmd->state == LED_STATE_ON)
|
|
sled.state |= SPCN_LED_IDENTIFY_MASK;
|
|
if (spcn_cmd->state == LED_STATE_OFF)
|
|
sled.state &= ~SPCN_LED_IDENTIFY_MASK;
|
|
}
|
|
|
|
/* Write into SPCN TCE buffer */
|
|
buf_write(buf, u8, sled.lc_len); /* Location code length */
|
|
memcpy(buf, sled.lc_code, sled.lc_len); /* Location code */
|
|
buf += sled.lc_len;
|
|
buf_write(buf, u16, sled.state); /* LED state */
|
|
|
|
msg = fsp_mkmsg(FSP_CMD_SPCN_PASSTHRU, 4,
|
|
SPCN_ADDR_MODE_CEC_NODE, cmd_hdr, 0, PSI_DMA_LED_BUF);
|
|
if (!msg) {
|
|
cmd |= FSP_STATUS_GENERIC_ERROR;
|
|
rc = -1;
|
|
goto update_fail;
|
|
}
|
|
|
|
/*
|
|
* Update the local lists based on the attempted SPCN command to
|
|
* set/reset an individual led (CEC or ENCL).
|
|
*/
|
|
update_led_list(spcn_cmd->loc_code, sled.state, led->excl_bit);
|
|
msg->user_data = spcn_cmd;
|
|
|
|
rc = fsp_queue_msg(msg, fsp_spcn_set_led_completion);
|
|
if (rc != OPAL_SUCCESS) {
|
|
cmd |= FSP_STATUS_GENERIC_ERROR;
|
|
fsp_freemsg(msg);
|
|
/* Revert LED state update */
|
|
update_led_list(spcn_cmd->loc_code, spcn_cmd->ckpt_status,
|
|
spcn_cmd->ckpt_excl_bit);
|
|
}
|
|
|
|
update_fail:
|
|
if (rc) {
|
|
log_simple_error(&e_info(OPAL_RC_LED_STATE),
|
|
"Set led state failed at LC=%s\n",
|
|
spcn_cmd->loc_code);
|
|
|
|
if (spcn_cmd->cmd_src == SPCN_SRC_FSP)
|
|
fsp_set_led_response(cmd);
|
|
|
|
if (spcn_cmd->cmd_src == SPCN_SRC_OPAL)
|
|
opal_led_update_complete(spcn_cmd->async_token,
|
|
OPAL_INTERNAL_ERROR);
|
|
}
|
|
|
|
unlock(&led_lock);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* process_led_state_change
|
|
*
|
|
* If the command queue is empty, it sets the 'spcn_cmd_complete' as true
|
|
* and just returns. Else it pops one element from the command queue
|
|
* and processes the command for the requested LED state change.
|
|
*/
|
|
static int process_led_state_change(void)
|
|
{
|
|
struct led_set_cmd *spcn_cmd;
|
|
int rc = 0;
|
|
|
|
/*
|
|
* The command queue is empty. This will only
|
|
* happen during the SPCN command callback path
|
|
* in which case we set 'spcn_cmd_complete' as true.
|
|
*/
|
|
lock(&spcn_cmd_lock);
|
|
if (list_empty(&spcn_cmdq)) {
|
|
spcn_cmd_complete = true;
|
|
unlock(&spcn_cmd_lock);
|
|
return rc;
|
|
}
|
|
|
|
spcn_cmd = list_pop(&spcn_cmdq, struct led_set_cmd, link);
|
|
unlock(&spcn_cmd_lock);
|
|
|
|
if (is_sai_loc_code(spcn_cmd->loc_code))
|
|
rc = fsp_set_sai(spcn_cmd);
|
|
else
|
|
rc = fsp_msg_set_led_state(spcn_cmd);
|
|
|
|
if (rc) {
|
|
free(spcn_cmd);
|
|
process_led_state_change();
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* queue_led_state_change
|
|
*
|
|
* FSP async command or OPAL based request for LED state change gets queued
|
|
* up in the command queue. If no previous SPCN command is pending, then it
|
|
* immediately pops up one element from the list and processes it. If previous
|
|
* SPCN commands are still pending then it just queues up and return. When the
|
|
* SPCN command callback gets to execute, it processes one element from the
|
|
* list and keeps the chain execution going. At last when there are no elements
|
|
* in the command queue it sets 'spcn_cmd_complete' as true again.
|
|
*/
|
|
static int queue_led_state_change(char *loc_code, u8 command,
|
|
u8 state, int cmd_src, uint64_t async_token)
|
|
{
|
|
struct led_set_cmd *cmd;
|
|
int rc = 0;
|
|
|
|
/* New request node */
|
|
cmd = zalloc(sizeof(struct led_set_cmd));
|
|
if (!cmd) {
|
|
/**
|
|
* @fwts-label FSPLEDRequestMallocFail
|
|
* @fwts-advice OPAL failed to allocate memory for FSP LED
|
|
* command. Likely an OPAL bug led to out of memory.
|
|
*/
|
|
prlog(PR_ERR, "SPCN set command node allocation failed\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Save the request */
|
|
strncpy(cmd->loc_code, loc_code, LOC_CODE_SIZE - 1);
|
|
cmd->command = command;
|
|
cmd->state = state;
|
|
cmd->cmd_src = cmd_src;
|
|
cmd->async_token = async_token;
|
|
|
|
/* Add to the queue */
|
|
lock(&spcn_cmd_lock);
|
|
list_add_tail(&spcn_cmdq, &cmd->link);
|
|
|
|
/* No previous SPCN command pending */
|
|
if (spcn_cmd_complete) {
|
|
spcn_cmd_complete = false;
|
|
unlock(&spcn_cmd_lock);
|
|
rc = process_led_state_change();
|
|
return rc;
|
|
}
|
|
|
|
unlock(&spcn_cmd_lock);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Write single location code information into the TCE outbound buffer
|
|
*
|
|
* Data layout
|
|
*
|
|
* 2 bytes - Length of location code structure
|
|
* 4 bytes - CCIN in ASCII
|
|
* 1 byte - Resource status flag
|
|
* 1 byte - Indicator state
|
|
* 1 byte - Raw loc code length
|
|
* 1 byte - Loc code field size
|
|
* Field size byte - Null terminated ASCII string padded to 4 byte boundary
|
|
*
|
|
*/
|
|
static u32 fsp_push_data_to_tce(struct fsp_led_data *led, u8 *out_data,
|
|
u32 total_size)
|
|
{
|
|
struct fsp_loc_code_data lcode;
|
|
|
|
/* CCIN value is irrelevant */
|
|
lcode.ccin = 0x0;
|
|
|
|
lcode.status = FSP_IND_NOT_IMPLMNTD;
|
|
|
|
if (led->parms & SPCN_LED_IDENTIFY_MASK)
|
|
lcode.status = FSP_IND_IMPLMNTD;
|
|
|
|
/* LED indicator status */
|
|
lcode.ind_state = FSP_IND_INACTIVE;
|
|
if (led->status & SPCN_LED_IDENTIFY_MASK)
|
|
lcode.ind_state |= FSP_IND_IDENTIFY_ACTV;
|
|
if (led->status & SPCN_LED_FAULT_MASK)
|
|
lcode.ind_state |= FSP_IND_FAULT_ACTV;
|
|
|
|
/* Location code */
|
|
memset(lcode.loc_code, 0, LOC_CODE_SIZE);
|
|
lcode.raw_len = strlen(led->loc_code);
|
|
strncpy(lcode.loc_code, led->loc_code, LOC_CODE_SIZE - 1);
|
|
lcode.fld_sz = sizeof(lcode.loc_code);
|
|
|
|
/* Rest of the structure */
|
|
lcode.size = sizeof(lcode);
|
|
lcode.status &= 0x0f;
|
|
|
|
/*
|
|
* Check for outbound buffer overflow. If there are still
|
|
* more LEDs to be sent across to FSP, don't send, ignore.
|
|
*/
|
|
if ((total_size + lcode.size) > PSI_DMA_LOC_COD_BUF_SZ)
|
|
return 0;
|
|
|
|
/* Copy over to the buffer */
|
|
memcpy(out_data, &lcode, sizeof(lcode));
|
|
|
|
return lcode.size;
|
|
}
|
|
|
|
/*
|
|
* Send out LED information structure pointed by "loc_code"
|
|
* to FSP through the PSI DMA mapping. Buffer layout structure
|
|
* must be followed.
|
|
*/
|
|
static void fsp_ret_loc_code_list(u16 req_type, char *loc_code)
|
|
{
|
|
struct fsp_led_data *led, *next;
|
|
struct fsp_msg *msg;
|
|
|
|
u8 *data; /* Start of TCE mapped buffer */
|
|
u8 *out_data; /* Start of location code data */
|
|
u32 bytes_sent = 0, total_size = 0;
|
|
u16 header_size = 0, flags = 0;
|
|
|
|
if (loc_code_list_buffer == NULL) {
|
|
prerror("No loc_code_list_buffer\n");
|
|
return;
|
|
}
|
|
|
|
/* Init the addresses */
|
|
data = loc_code_list_buffer;
|
|
out_data = NULL;
|
|
|
|
/* Unmapping through FSP_CMD_RET_LOC_BUFFER command */
|
|
fsp_tce_map(PSI_DMA_LOC_COD_BUF, (void *)data, PSI_DMA_LOC_COD_BUF_SZ);
|
|
out_data = data + 8;
|
|
|
|
/* CEC LED list */
|
|
list_for_each_safe(&cec_ledq, led, next, link) {
|
|
/*
|
|
* When the request type is system wide led list
|
|
* i.e GET_LC_CMPLT_SYS, send the entire contents
|
|
* of the CEC list including both all descendents
|
|
* and all of their enclosures.
|
|
*/
|
|
|
|
if (req_type == GET_LC_ENCLOSURES)
|
|
break;
|
|
|
|
if (req_type == GET_LC_ENCL_DESCENDANTS) {
|
|
if (strstr(led->loc_code, loc_code) == NULL)
|
|
continue;
|
|
}
|
|
|
|
if (req_type == GET_LC_SINGLE_LOC_CODE) {
|
|
if (strcmp(led->loc_code, loc_code))
|
|
continue;
|
|
}
|
|
|
|
/* Push the data into TCE buffer */
|
|
bytes_sent = fsp_push_data_to_tce(led, out_data, total_size);
|
|
|
|
/* Advance the TCE pointer */
|
|
out_data += bytes_sent;
|
|
total_size += bytes_sent;
|
|
}
|
|
|
|
/* Enclosure LED list */
|
|
if (req_type == GET_LC_ENCLOSURES) {
|
|
list_for_each_safe(&encl_ledq, led, next, link) {
|
|
|
|
/* Push the data into TCE buffer */
|
|
bytes_sent = fsp_push_data_to_tce(led,
|
|
out_data, total_size);
|
|
|
|
/* Advance the TCE pointer */
|
|
out_data += bytes_sent;
|
|
total_size += bytes_sent;
|
|
}
|
|
}
|
|
|
|
/* Count from 'data' instead of 'data_out' */
|
|
total_size += 8;
|
|
memcpy(data, &total_size, sizeof(total_size));
|
|
|
|
header_size = OUTBUF_HEADER_SIZE;
|
|
memcpy(data + sizeof(total_size), &header_size, sizeof(header_size));
|
|
|
|
if (req_type == GET_LC_ENCL_DESCENDANTS)
|
|
flags = 0x8000;
|
|
|
|
memcpy(data + sizeof(total_size) + sizeof(header_size), &flags,
|
|
sizeof(flags));
|
|
msg = fsp_mkmsg(FSP_RSP_GET_LED_LIST, 3, 0,
|
|
PSI_DMA_LOC_COD_BUF, total_size);
|
|
if (!msg) {
|
|
prerror("Failed to allocate FSP_RSP_GET_LED_LIST.\n");
|
|
} else {
|
|
if (fsp_queue_msg(msg, fsp_freemsg)) {
|
|
fsp_freemsg(msg);
|
|
prerror("Failed to queue FSP_RSP_GET_LED_LIST\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* FSP async command: FSP_CMD_GET_LED_LIST
|
|
*
|
|
* (1) FSP sends the list of location codes through inbound buffer
|
|
* (2) HV sends the status of those location codes through outbound buffer
|
|
*
|
|
* Inbound buffer data layout (loc code request structure)
|
|
*
|
|
* 2 bytes - Length of entire structure
|
|
* 2 bytes - Request type
|
|
* 1 byte - Raw length of location code
|
|
* 1 byte - Location code field size
|
|
* `Field size` bytes - NULL terminated ASCII location code string
|
|
*/
|
|
static void fsp_get_led_list(struct fsp_msg *msg)
|
|
{
|
|
struct fsp_loc_code_req req;
|
|
u32 tce_token = msg->data.words[1];
|
|
void *buf;
|
|
|
|
/* Parse inbound buffer */
|
|
buf = fsp_inbound_buf_from_tce(tce_token);
|
|
if (!buf) {
|
|
struct fsp_msg *msg;
|
|
msg = fsp_mkmsg(FSP_RSP_GET_LED_LIST | FSP_STATUS_INVALID_DATA,
|
|
0);
|
|
if (!msg) {
|
|
prerror("Failed to allocate FSP_RSP_GET_LED_LIST"
|
|
" | FSP_STATUS_INVALID_DATA\n");
|
|
} else {
|
|
if (fsp_queue_msg(msg, fsp_freemsg)) {
|
|
fsp_freemsg(msg);
|
|
prerror("Failed to queue "
|
|
"FSP_RSP_GET_LED_LIST |"
|
|
" FSP_STATUS_INVALID_DATA\n");
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
memcpy(&req, buf, sizeof(req));
|
|
|
|
prlog(PR_TRACE, "Request for loc code list type 0x%04x LC=%s\n",
|
|
req.req_type, req.loc_code);
|
|
|
|
fsp_ret_loc_code_list(req.req_type, req.loc_code);
|
|
}
|
|
|
|
/*
|
|
* FSP async command: FSP_CMD_RET_LOC_BUFFER
|
|
*
|
|
* With this command FSP returns ownership of the outbound buffer
|
|
* used by Sapphire to pass the indicator list previous time. That
|
|
* way FSP tells Sapphire that it has consumed all the data present
|
|
* on the outbound buffer and Sapphire can reuse it for next request.
|
|
*/
|
|
static void fsp_free_led_list_buf(struct fsp_msg *msg)
|
|
{
|
|
u32 tce_token = msg->data.words[1];
|
|
u32 cmd = FSP_RSP_RET_LED_BUFFER;
|
|
struct fsp_msg *resp;
|
|
|
|
/* Token does not point to outbound buffer */
|
|
if (tce_token != PSI_DMA_LOC_COD_BUF) {
|
|
log_simple_error(&e_info(OPAL_RC_LED_BUFF),
|
|
"Invalid tce token from FSP\n");
|
|
cmd |= FSP_STATUS_GENERIC_ERROR;
|
|
resp = fsp_mkmsg(cmd, 0);
|
|
if (!resp) {
|
|
prerror("Failed to allocate FSP_RSP_RET_LED_BUFFER"
|
|
"| FSP_STATUS_GENERIC_ERROR\n");
|
|
return;
|
|
}
|
|
|
|
if (fsp_queue_msg(resp, fsp_freemsg)) {
|
|
fsp_freemsg(resp);
|
|
prerror("Failed to queue "
|
|
"RET_LED_BUFFER|ERROR\n");
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Unmap the location code DMA buffer */
|
|
fsp_tce_unmap(PSI_DMA_LOC_COD_BUF, PSI_DMA_LOC_COD_BUF_SZ);
|
|
|
|
resp = fsp_mkmsg(cmd, 0);
|
|
if (!resp) {
|
|
prerror("Failed to allocate FSP_RSP_RET_LED_BUFFER\n");
|
|
return;
|
|
}
|
|
if (fsp_queue_msg(resp, fsp_freemsg)) {
|
|
fsp_freemsg(resp);
|
|
prerror("Failed to queue FSP_RSP_RET_LED_BUFFER\n");
|
|
}
|
|
}
|
|
|
|
static void fsp_ret_led_state(char *loc_code)
|
|
{
|
|
bool found = false;
|
|
u8 ind_state = 0;
|
|
u32 cmd = FSP_RSP_GET_LED_STATE;
|
|
struct fsp_led_data *led, *next;
|
|
struct fsp_msg *msg;
|
|
|
|
if (is_sai_loc_code(loc_code)) {
|
|
if (sai_data.state & OPAL_SLOT_LED_STATE_ON)
|
|
ind_state = FSP_IND_FAULT_ACTV;
|
|
found = true;
|
|
} else {
|
|
list_for_each_safe(&cec_ledq, led, next, link) {
|
|
if (strcmp(loc_code, led->loc_code))
|
|
continue;
|
|
|
|
/* Found the location code */
|
|
if (led->status & SPCN_LED_IDENTIFY_MASK)
|
|
ind_state |= FSP_IND_IDENTIFY_ACTV;
|
|
if (led->status & SPCN_LED_FAULT_MASK)
|
|
ind_state |= FSP_IND_FAULT_ACTV;
|
|
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Location code not found */
|
|
if (!found) {
|
|
log_simple_error(&e_info(OPAL_RC_LED_LC),
|
|
"Could not find the location code LC=%s\n",
|
|
loc_code);
|
|
cmd |= FSP_STATUS_INVALID_LC;
|
|
ind_state = 0xff;
|
|
}
|
|
|
|
msg = fsp_mkmsg(cmd, 1, ind_state);
|
|
if (!msg) {
|
|
prerror("Couldn't alloc FSP_RSP_GET_LED_STATE\n");
|
|
return;
|
|
}
|
|
|
|
if (fsp_queue_msg(msg, fsp_freemsg)) {
|
|
fsp_freemsg(msg);
|
|
prerror("Couldn't queue FSP_RSP_GET_LED_STATE\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* FSP async command: FSP_CMD_GET_LED_STATE
|
|
*
|
|
* With this command FSP query the state for any given LED
|
|
*/
|
|
static void fsp_get_led_state(struct fsp_msg *msg)
|
|
{
|
|
struct fsp_get_ind_state_req req;
|
|
u32 tce_token = msg->data.words[1];
|
|
void *buf;
|
|
|
|
/* Parse the inbound buffer */
|
|
buf = fsp_inbound_buf_from_tce(tce_token);
|
|
if (!buf) {
|
|
struct fsp_msg *msg;
|
|
msg = fsp_mkmsg(FSP_RSP_GET_LED_STATE |
|
|
FSP_STATUS_INVALID_DATA, 0);
|
|
if (!msg) {
|
|
prerror("Failed to allocate FSP_RSP_GET_LED_STATE"
|
|
" | FSP_STATUS_INVALID_DATA\n");
|
|
return;
|
|
}
|
|
if (fsp_queue_msg(msg, fsp_freemsg)) {
|
|
fsp_freemsg(msg);
|
|
prerror("Failed to queue FSP_RSP_GET_LED_STATE"
|
|
" | FSP_STATUS_INVALID_DATA\n");
|
|
}
|
|
return;
|
|
}
|
|
memcpy(&req, buf, sizeof(req));
|
|
|
|
prlog(PR_TRACE, "%s: tce=0x%08x buf=%p rq.sz=%d rq.lc_len=%d"
|
|
" rq.fld_sz=%d LC: %02x %02x %02x %02x....\n", __func__,
|
|
tce_token, buf, req.size, req.lc_len, req.fld_sz,
|
|
req.loc_code[0], req.loc_code[1],
|
|
req.loc_code[2], req.loc_code[3]);
|
|
|
|
/* Bound check */
|
|
if (req.lc_len >= LOC_CODE_SIZE) {
|
|
log_simple_error(&e_info(OPAL_RC_LED_LC),
|
|
"Loc code too large in %s: %d bytes\n",
|
|
__func__, req.lc_len);
|
|
req.lc_len = LOC_CODE_SIZE - 1;
|
|
}
|
|
/* Ensure NULL termination */
|
|
req.loc_code[req.lc_len] = 0;
|
|
|
|
/* Do the deed */
|
|
fsp_ret_led_state(req.loc_code);
|
|
}
|
|
|
|
/*
|
|
* FSP async command: FSP_CMD_SET_LED_STATE
|
|
*
|
|
* With this command FSP sets/resets the state for any given LED
|
|
*/
|
|
static void fsp_set_led_state(struct fsp_msg *msg)
|
|
{
|
|
struct fsp_set_ind_state_req req;
|
|
struct fsp_led_data *led, *next;
|
|
u32 tce_token = msg->data.words[1];
|
|
bool command, state;
|
|
void *buf;
|
|
int rc;
|
|
|
|
/* Parse the inbound buffer */
|
|
buf = fsp_inbound_buf_from_tce(tce_token);
|
|
if (!buf) {
|
|
fsp_set_led_response(FSP_RSP_SET_LED_STATE |
|
|
FSP_STATUS_INVALID_DATA);
|
|
return;
|
|
}
|
|
memcpy(&req, buf, sizeof(req));
|
|
|
|
prlog(PR_TRACE, "%s: tce=0x%08x buf=%p rq.sz=%d rq.typ=0x%04x"
|
|
" rq.lc_len=%d rq.fld_sz=%d LC: %02x %02x %02x %02x....\n",
|
|
__func__, tce_token, buf, req.size, req.lc_len, req.fld_sz,
|
|
req.req_type,
|
|
req.loc_code[0], req.loc_code[1],
|
|
req.loc_code[2], req.loc_code[3]);
|
|
|
|
/* Bound check */
|
|
if (req.lc_len >= LOC_CODE_SIZE) {
|
|
log_simple_error(&e_info(OPAL_RC_LED_LC),
|
|
"Loc code too large in %s: %d bytes\n",
|
|
__func__, req.lc_len);
|
|
req.lc_len = LOC_CODE_SIZE - 1;
|
|
}
|
|
/* Ensure NULL termination */
|
|
req.loc_code[req.lc_len] = 0;
|
|
|
|
/* Decode command */
|
|
command = (req.ind_state & LOGICAL_IND_STATE_MASK) ?
|
|
LED_COMMAND_FAULT : LED_COMMAND_IDENTIFY;
|
|
state = (req.ind_state & ACTIVE_LED_STATE_MASK) ?
|
|
LED_STATE_ON : LED_STATE_OFF;
|
|
|
|
/* Handle requests */
|
|
switch (req.req_type) {
|
|
case SET_IND_ENCLOSURE:
|
|
list_for_each_safe(&cec_ledq, led, next, link) {
|
|
/* Only descendants of the same enclosure */
|
|
if (!strstr(led->loc_code, req.loc_code))
|
|
continue;
|
|
|
|
/* Skip the enclosure */
|
|
if (!strcmp(led->loc_code, req.loc_code))
|
|
continue;
|
|
|
|
rc = queue_led_state_change(led->loc_code, command,
|
|
state, SPCN_SRC_FSP, 0);
|
|
if (rc != 0)
|
|
fsp_set_led_response(FSP_RSP_SET_LED_STATE |
|
|
FSP_STATUS_GENERIC_ERROR);
|
|
}
|
|
break;
|
|
case SET_IND_SINGLE_LOC_CODE:
|
|
/* Set led state for single descendent led */
|
|
rc = queue_led_state_change(req.loc_code,
|
|
command, state, SPCN_SRC_FSP, 0);
|
|
if (rc != 0)
|
|
fsp_set_led_response(FSP_RSP_SET_LED_STATE |
|
|
FSP_STATUS_GENERIC_ERROR);
|
|
break;
|
|
default:
|
|
fsp_set_led_response(FSP_RSP_SET_LED_STATE |
|
|
FSP_STATUS_NOT_SUPPORTED);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Handle received indicator message from FSP */
|
|
static bool fsp_indicator_message(u32 cmd_sub_mod, struct fsp_msg *msg)
|
|
{
|
|
u32 cmd;
|
|
struct fsp_msg *resp;
|
|
|
|
/* LED support not available yet */
|
|
if (led_support != LED_STATE_PRESENT) {
|
|
log_simple_error(&e_info(OPAL_RC_LED_SUPPORT),
|
|
"Indicator message while LED support not"
|
|
" available yet\n");
|
|
return false;
|
|
}
|
|
|
|
switch (cmd_sub_mod) {
|
|
case FSP_CMD_GET_LED_LIST:
|
|
prlog(PR_TRACE, "FSP_CMD_GET_LED_LIST command received\n");
|
|
fsp_get_led_list(msg);
|
|
return true;
|
|
case FSP_CMD_RET_LED_BUFFER:
|
|
prlog(PR_TRACE, "FSP_CMD_RET_LED_BUFFER command received\n");
|
|
fsp_free_led_list_buf(msg);
|
|
return true;
|
|
case FSP_CMD_GET_LED_STATE:
|
|
prlog(PR_TRACE, "FSP_CMD_GET_LED_STATE command received\n");
|
|
fsp_get_led_state(msg);
|
|
return true;
|
|
case FSP_CMD_SET_LED_STATE:
|
|
prlog(PR_TRACE, "FSP_CMD_SET_LED_STATE command received\n");
|
|
fsp_set_led_state(msg);
|
|
return true;
|
|
/*
|
|
* FSP async sub commands which have not been implemented.
|
|
* For these async sub commands, print for the log and ack
|
|
* the field service processor with a generic error.
|
|
*/
|
|
case FSP_CMD_GET_MTMS_LIST:
|
|
prlog(PR_TRACE, "FSP_CMD_GET_MTMS_LIST command received\n");
|
|
cmd = FSP_RSP_GET_MTMS_LIST;
|
|
break;
|
|
case FSP_CMD_RET_MTMS_BUFFER:
|
|
prlog(PR_TRACE, "FSP_CMD_RET_MTMS_BUFFER command received\n");
|
|
cmd = FSP_RSP_RET_MTMS_BUFFER;
|
|
break;
|
|
case FSP_CMD_SET_ENCL_MTMS:
|
|
prlog(PR_TRACE, "FSP_CMD_SET_MTMS command received\n");
|
|
cmd = FSP_RSP_SET_ENCL_MTMS;
|
|
break;
|
|
case FSP_CMD_CLR_INCT_ENCL:
|
|
prlog(PR_TRACE, "FSP_CMD_CLR_INCT_ENCL command received\n");
|
|
cmd = FSP_RSP_CLR_INCT_ENCL;
|
|
break;
|
|
case FSP_CMD_ENCL_MCODE_INIT:
|
|
prlog(PR_TRACE, "FSP_CMD_ENCL_MCODE_INIT command received\n");
|
|
cmd = FSP_RSP_ENCL_MCODE_INIT;
|
|
break;
|
|
case FSP_CMD_ENCL_MCODE_INTR:
|
|
prlog(PR_TRACE, "FSP_CMD_ENCL_MCODE_INTR command received\n");
|
|
cmd = FSP_RSP_ENCL_MCODE_INTR;
|
|
break;
|
|
case FSP_CMD_ENCL_POWR_TRACE:
|
|
prlog(PR_TRACE, "FSP_CMD_ENCL_POWR_TRACE command received\n");
|
|
cmd = FSP_RSP_ENCL_POWR_TRACE;
|
|
break;
|
|
case FSP_CMD_RET_ENCL_TRACE_BUFFER:
|
|
prlog(PR_TRACE, "FSP_CMD_RET_ENCL_TRACE_BUFFER command received\n");
|
|
cmd = FSP_RSP_RET_ENCL_TRACE_BUFFER;
|
|
break;
|
|
case FSP_CMD_GET_SPCN_LOOP_STATUS:
|
|
prlog(PR_TRACE, "FSP_CMD_GET_SPCN_LOOP_STATUS command received\n");
|
|
cmd = FSP_RSP_GET_SPCN_LOOP_STATUS;
|
|
break;
|
|
case FSP_CMD_INITIATE_LAMP_TEST:
|
|
/* XXX: FSP ACK not required for this sub command */
|
|
prlog(PR_TRACE, "FSP_CMD_INITIATE_LAMP_TEST command received\n");
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
cmd |= FSP_STATUS_GENERIC_ERROR;
|
|
resp = fsp_mkmsg(cmd, 0);
|
|
if (!resp) {
|
|
prerror("Failed to allocate FSP_STATUS_GENERIC_ERROR\n");
|
|
return false;
|
|
}
|
|
if (fsp_queue_msg(resp, fsp_freemsg)) {
|
|
fsp_freemsg(resp);
|
|
prerror("Failed to queue FSP_STATUS_GENERIC_ERROR\n");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Indicator class client */
|
|
static struct fsp_client fsp_indicator_client = {
|
|
.message = fsp_indicator_message,
|
|
};
|
|
|
|
|
|
static int fsp_opal_get_sai(u64 *led_mask, u64 *led_value)
|
|
{
|
|
*led_mask |= OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_ATTN;
|
|
if (sai_data.state & OPAL_SLOT_LED_STATE_ON)
|
|
*led_value |=
|
|
OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_ATTN;
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
static int fsp_opal_set_sai(uint64_t async_token, char *loc_code,
|
|
const u64 led_mask, const u64 led_value)
|
|
{
|
|
int state = LED_STATE_OFF;
|
|
|
|
if (!((led_mask >> OPAL_SLOT_LED_TYPE_ATTN) & OPAL_SLOT_LED_STATE_ON))
|
|
return OPAL_PARAMETER;
|
|
|
|
if ((led_value >> OPAL_SLOT_LED_TYPE_ATTN) & OPAL_SLOT_LED_STATE_ON)
|
|
state = LED_STATE_ON;
|
|
|
|
return queue_led_state_change(loc_code, 0,
|
|
state, SPCN_SRC_OPAL, async_token);
|
|
}
|
|
|
|
/*
|
|
* fsp_opal_leds_get_ind (OPAL_LEDS_GET_INDICATOR)
|
|
*
|
|
* Argument Description Updated By
|
|
* -------- ----------- ----------
|
|
* loc_code Location code of the LEDs (Host)
|
|
* led_mask LED types whose status is available (OPAL)
|
|
* led_value Status of the available LED types (OPAL)
|
|
* max_led_type Maximum number of supported LED types (Host/OPAL)
|
|
*
|
|
* The host will pass the location code of the LED types (loc_code) and
|
|
* maximum number of LED types it understands (max_led_type). OPAL will
|
|
* update the 'led_mask' with set bits pointing to LED types whose status
|
|
* is available and updates the 'led_value' with actual status. OPAL checks
|
|
* the 'max_led_type' to understand whether the host is newer or older
|
|
* compared to itself. In the case where the OPAL is newer compared
|
|
* to host (OPAL's max_led_type > host's max_led_type), it will update
|
|
* led_mask and led_value according to max_led_type requested by the host.
|
|
* When the host is newer compared to the OPAL (host's max_led_type >
|
|
* OPAL's max_led_type), OPAL updates 'max_led_type' to the maximum
|
|
* number of LED type it understands and updates 'led_mask', 'led_value'
|
|
* based on that maximum value of LED types.
|
|
*/
|
|
static int64_t fsp_opal_leds_get_ind(char *loc_code, u64 *led_mask,
|
|
u64 *led_value, u64 *max_led_type)
|
|
{
|
|
bool supported = true;
|
|
int64_t max;
|
|
int rc;
|
|
struct fsp_led_data *led;
|
|
|
|
/* FSP not present */
|
|
if (!fsp_present())
|
|
return OPAL_HARDWARE;
|
|
|
|
/* LED support not available */
|
|
if (led_support != LED_STATE_PRESENT)
|
|
return OPAL_HARDWARE;
|
|
|
|
/* Adjust max LED type */
|
|
if (*max_led_type > OPAL_SLOT_LED_TYPE_MAX) {
|
|
supported = false;
|
|
*max_led_type = OPAL_SLOT_LED_TYPE_MAX;
|
|
}
|
|
|
|
/* Invalid parameter */
|
|
max = *max_led_type;
|
|
if (max <= 0)
|
|
return OPAL_PARAMETER;
|
|
|
|
/* Get System attention indicator state */
|
|
if (is_sai_loc_code(loc_code)) {
|
|
rc = fsp_opal_get_sai(led_mask, led_value);
|
|
return rc;
|
|
}
|
|
|
|
/* LED not found */
|
|
led = fsp_find_cec_led(loc_code);
|
|
if (!led)
|
|
return OPAL_PARAMETER;
|
|
|
|
*led_mask = 0;
|
|
*led_value = 0;
|
|
|
|
/* Identify LED */
|
|
--max;
|
|
*led_mask |= OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_ID;
|
|
if (led->status & SPCN_LED_IDENTIFY_MASK)
|
|
*led_value |=
|
|
OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_ID;
|
|
|
|
/* Fault LED */
|
|
if (!max)
|
|
return OPAL_SUCCESS;
|
|
|
|
--max;
|
|
*led_mask |= OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_FAULT;
|
|
if (led->status & SPCN_LED_FAULT_MASK)
|
|
*led_value |=
|
|
OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_FAULT;
|
|
|
|
/* OPAL doesn't support all the LED type requested by payload */
|
|
if (!supported)
|
|
return OPAL_PARTIAL;
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* fsp_opal_leds_set_ind (OPAL_LEDS_SET_INDICATOR)
|
|
*
|
|
* Argument Description Updated By
|
|
* -------- ----------- ----------
|
|
* loc_code Location code of the LEDs (Host)
|
|
* led_mask LED types whose status will be updated (Host)
|
|
* led_value Requested status of various LED types (Host)
|
|
* max_led_type Maximum number of supported LED types (Host/OPAL)
|
|
*
|
|
* The host will pass the location code of the LED types, mask, value
|
|
* and maximum number of LED types it understands. OPAL will update
|
|
* LED status for all the LED types mentioned in the mask with their
|
|
* value mentioned. OPAL checks the 'max_led_type' to understand
|
|
* whether the host is newer or older compared to itself. In case where
|
|
* the OPAL is newer compared to the host (OPAL's max_led_type >
|
|
* host's max_led_type), it updates LED status based on max_led_type
|
|
* requested from the host. When the host is newer compared to the OPAL
|
|
* (host's max_led_type > OPAL's max_led_type), OPAL updates
|
|
* 'max_led_type' to the maximum number of LED type it understands and
|
|
* then it updates LED status based on that updated maximum value of LED
|
|
* types. Host needs to check the returned updated value of max_led_type
|
|
* to figure out which part of it's request got served and which ones got
|
|
* ignored.
|
|
*/
|
|
static int64_t fsp_opal_leds_set_ind(uint64_t async_token,
|
|
char *loc_code, const u64 led_mask,
|
|
const u64 led_value, u64 *max_led_type)
|
|
{
|
|
bool supported = true;
|
|
int command, state, rc = OPAL_SUCCESS;
|
|
int64_t max;
|
|
struct fsp_led_data *led;
|
|
|
|
/* FSP not present */
|
|
if (!fsp_present())
|
|
return OPAL_HARDWARE;
|
|
|
|
/* LED support not available */
|
|
if (led_support != LED_STATE_PRESENT)
|
|
return OPAL_HARDWARE;
|
|
|
|
/* Adjust max LED type */
|
|
if (*max_led_type > OPAL_SLOT_LED_TYPE_MAX) {
|
|
supported = false;
|
|
*max_led_type = OPAL_SLOT_LED_TYPE_MAX;
|
|
}
|
|
|
|
max = *max_led_type;
|
|
/* Invalid parameter */
|
|
if (max <= 0)
|
|
return OPAL_PARAMETER;
|
|
|
|
/* Set System attention indicator state */
|
|
if (is_sai_loc_code(loc_code)) {
|
|
supported = true;
|
|
rc = fsp_opal_set_sai(async_token,
|
|
loc_code, led_mask, led_value);
|
|
goto success;
|
|
}
|
|
|
|
/* LED not found */
|
|
led = fsp_find_cec_led(loc_code);
|
|
if (!led)
|
|
return OPAL_PARAMETER;
|
|
|
|
/* Indentify LED mask */
|
|
--max;
|
|
|
|
if ((led_mask >> OPAL_SLOT_LED_TYPE_ID) & OPAL_SLOT_LED_STATE_ON) {
|
|
supported = true;
|
|
|
|
command = LED_COMMAND_IDENTIFY;
|
|
state = LED_STATE_OFF;
|
|
if ((led_value >> OPAL_SLOT_LED_TYPE_ID)
|
|
& OPAL_SLOT_LED_STATE_ON)
|
|
state = LED_STATE_ON;
|
|
|
|
rc = queue_led_state_change(loc_code, command,
|
|
state, SPCN_SRC_OPAL, async_token);
|
|
}
|
|
|
|
if (!max)
|
|
goto success;
|
|
|
|
/* Fault LED mask */
|
|
--max;
|
|
if ((led_mask >> OPAL_SLOT_LED_TYPE_FAULT) & OPAL_SLOT_LED_STATE_ON) {
|
|
supported = true;
|
|
|
|
command = LED_COMMAND_FAULT;
|
|
state = LED_STATE_OFF;
|
|
if ((led_value >> OPAL_SLOT_LED_TYPE_FAULT)
|
|
& OPAL_SLOT_LED_STATE_ON)
|
|
state = LED_STATE_ON;
|
|
|
|
rc = queue_led_state_change(loc_code, command,
|
|
state, SPCN_SRC_OPAL, async_token);
|
|
}
|
|
|
|
success:
|
|
/* Unsupported LED type */
|
|
if (!supported)
|
|
return OPAL_UNSUPPORTED;
|
|
|
|
if (rc == OPAL_SUCCESS)
|
|
rc = OPAL_ASYNC_COMPLETION;
|
|
else
|
|
rc = OPAL_INTERNAL_ERROR;
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Get LED node from device tree */
|
|
static struct dt_node *dt_get_led_node(void)
|
|
{
|
|
struct dt_node *pled;
|
|
|
|
if (!opal_node) {
|
|
prlog(PR_WARNING, "OPAL parent device node not available\n");
|
|
return NULL;
|
|
}
|
|
|
|
pled = dt_find_by_path(opal_node, DT_PROPERTY_LED_NODE);
|
|
if (!pled)
|
|
prlog(PR_WARNING, "Parent device node not available\n");
|
|
|
|
return pled;
|
|
}
|
|
|
|
/* Get System attention indicator location code from device tree */
|
|
static void dt_get_sai_loc_code(void)
|
|
{
|
|
struct dt_node *pled, *child;
|
|
const char *led_type = NULL;
|
|
|
|
memset(sai_data.loc_code, 0, LOC_CODE_SIZE);
|
|
|
|
pled = dt_get_led_node();
|
|
if (!pled)
|
|
return;
|
|
|
|
list_for_each(&pled->children, child, list) {
|
|
led_type = dt_prop_get(child, DT_PROPERTY_LED_TYPES);
|
|
if (!led_type)
|
|
continue;
|
|
|
|
if (strcmp(led_type, LED_TYPE_ATTENTION))
|
|
continue;
|
|
|
|
memcpy(sai_data.loc_code, child->name, LOC_CODE_SIZE - 1);
|
|
|
|
prlog(PR_TRACE, "SAI Location code = %s\n", sai_data.loc_code);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* create_led_device_node
|
|
*
|
|
* Creates the system parent LED device node and all individual
|
|
* child LED device nodes under it. This is called right before
|
|
* starting the payload (Linux) to ensure that the SPCN command
|
|
* sequence to fetch the LED location code list has been finished
|
|
* and to have a better chance of creating the deviced nodes.
|
|
*/
|
|
void create_led_device_nodes(void)
|
|
{
|
|
const char *led_mode = NULL;
|
|
struct fsp_led_data *led, *next;
|
|
struct dt_node *pled, *cled;
|
|
|
|
if (!fsp_present())
|
|
return;
|
|
|
|
/* Make sure LED list read is completed */
|
|
while (led_support == LED_STATE_READING)
|
|
opal_run_pollers();
|
|
|
|
if (led_support == LED_STATE_ABSENT) {
|
|
prlog(PR_WARNING, "LED support not available, \
|
|
hence device tree nodes will not be created\n");
|
|
return;
|
|
}
|
|
|
|
/* Get LED node */
|
|
pled = dt_get_led_node();
|
|
if (!pled)
|
|
return;
|
|
|
|
/* Check if already populated (fast-reboot) */
|
|
if (dt_has_node_property(pled, "compatible", NULL))
|
|
return;
|
|
dt_add_property_strings(pled, "compatible", DT_PROPERTY_LED_COMPATIBLE);
|
|
|
|
led_mode = dt_prop_get(pled, DT_PROPERTY_LED_MODE);
|
|
if (!led_mode) {
|
|
prlog(PR_WARNING, "Unknown LED operating mode\n");
|
|
return;
|
|
}
|
|
|
|
/* LED child nodes */
|
|
list_for_each_safe(&cec_ledq, led, next, link) {
|
|
/* Duplicate LED location code */
|
|
if (dt_find_by_path(pled, led->loc_code)) {
|
|
prlog(PR_WARNING, "duplicate location code %s\n",
|
|
led->loc_code);
|
|
continue;
|
|
}
|
|
|
|
cled = dt_new(pled, led->loc_code);
|
|
if (!cled) {
|
|
prlog(PR_WARNING, "Child device node creation "
|
|
"failed\n");
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(led_mode, LED_MODE_LIGHT_PATH))
|
|
dt_add_property_strings(cled, DT_PROPERTY_LED_TYPES,
|
|
LED_TYPE_IDENTIFY,
|
|
LED_TYPE_FAULT);
|
|
else
|
|
dt_add_property_strings(cled, DT_PROPERTY_LED_TYPES,
|
|
LED_TYPE_IDENTIFY);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Process the received LED data from SPCN
|
|
*
|
|
* Every LED state data is added into the CEC list. If the location
|
|
* code is a enclosure type, its added into the enclosure list as well.
|
|
*
|
|
*/
|
|
static void fsp_process_leds_data(u16 len)
|
|
{
|
|
struct fsp_led_data *led_data = NULL;
|
|
void *buf = NULL;
|
|
|
|
/*
|
|
* Process the entire captured data from the last command
|
|
*
|
|
* TCE mapped 'led_buffer' contains the fsp_led_data structure
|
|
* one after the other till the total length 'len'.
|
|
*
|
|
*/
|
|
buf = led_buffer;
|
|
while (len) {
|
|
size_t lc_len;
|
|
|
|
/* Prepare */
|
|
led_data = zalloc(sizeof(struct fsp_led_data));
|
|
assert(led_data);
|
|
|
|
/* Resource ID */
|
|
buf_read(buf, u16, &led_data->rid);
|
|
len -= sizeof(led_data->rid);
|
|
|
|
/* Location code length */
|
|
buf_read(buf, u8, &led_data->lc_len);
|
|
len -= sizeof(led_data->lc_len);
|
|
|
|
lc_len = led_data->lc_len;
|
|
if (lc_len == 0) {
|
|
free(led_data);
|
|
break;
|
|
}
|
|
|
|
if (lc_len >= LOC_CODE_SIZE)
|
|
lc_len = LOC_CODE_SIZE - 1;
|
|
|
|
/* Location code */
|
|
strncpy(led_data->loc_code, buf, lc_len);
|
|
led_data->loc_code[lc_len] = '\0';
|
|
|
|
buf += led_data->lc_len;
|
|
len -= led_data->lc_len;
|
|
|
|
/* Parameters */
|
|
buf_read(buf, u16, &led_data->parms);
|
|
len -= sizeof(led_data->parms);
|
|
|
|
/* Status */
|
|
buf_read(buf, u16, &led_data->status);
|
|
len -= sizeof(led_data->status);
|
|
|
|
/*
|
|
* This is Enclosure LED's location code, need to go
|
|
* inside the enclosure LED list as well.
|
|
*/
|
|
if (!strstr(led_data->loc_code, "-")) {
|
|
struct fsp_led_data *encl_led_data = NULL;
|
|
encl_led_data = zalloc(sizeof(struct fsp_led_data));
|
|
assert(encl_led_data);
|
|
|
|
/* copy over the original */
|
|
memcpy(encl_led_data, led_data, sizeof(struct fsp_led_data));
|
|
|
|
/* Add to the list of enclosure LEDs */
|
|
list_add_tail(&encl_ledq, &encl_led_data->link);
|
|
}
|
|
|
|
/* Push this onto the list */
|
|
list_add_tail(&cec_ledq, &led_data->link);
|
|
}
|
|
}
|
|
|
|
/* Replay the SPCN command */
|
|
static void replay_spcn_cmd(u32 last_spcn_cmd)
|
|
{
|
|
u32 cmd_hdr = 0;
|
|
int rc = -1;
|
|
|
|
/* Reached threshold */
|
|
if (replay == SPCN_REPLAY_THRESHOLD) {
|
|
replay = 0;
|
|
led_support = LED_STATE_ABSENT;
|
|
return;
|
|
}
|
|
|
|
replay++;
|
|
if (last_spcn_cmd == SPCN_MOD_PRS_LED_DATA_FIRST) {
|
|
cmd_hdr = SPCN_MOD_PRS_LED_DATA_FIRST << 24 |
|
|
SPCN_CMD_PRS << 16;
|
|
rc = fsp_queue_msg(fsp_mkmsg(FSP_CMD_SPCN_PASSTHRU, 4,
|
|
SPCN_ADDR_MODE_CEC_NODE,
|
|
cmd_hdr, 0,
|
|
PSI_DMA_LED_BUF),
|
|
fsp_read_leds_data_complete);
|
|
if (rc)
|
|
prlog(PR_ERR, "Replay SPCN_MOD_PRS_LED_DATA_FIRST"
|
|
" command could not be queued\n");
|
|
}
|
|
|
|
if (last_spcn_cmd == SPCN_MOD_PRS_LED_DATA_SUB) {
|
|
cmd_hdr = SPCN_MOD_PRS_LED_DATA_SUB << 24 | SPCN_CMD_PRS << 16;
|
|
rc = fsp_queue_msg(fsp_mkmsg(FSP_CMD_SPCN_PASSTHRU, 4,
|
|
SPCN_ADDR_MODE_CEC_NODE, cmd_hdr,
|
|
0, PSI_DMA_LED_BUF),
|
|
fsp_read_leds_data_complete);
|
|
if (rc)
|
|
prlog(PR_ERR, "Replay SPCN_MOD_PRS_LED_DATA_SUB"
|
|
" command could not be queued\n");
|
|
}
|
|
|
|
/* Failed to queue MBOX message */
|
|
if (rc)
|
|
led_support = LED_STATE_ABSENT;
|
|
}
|
|
|
|
/*
|
|
* FSP message response handler for following SPCN LED commands
|
|
* which are used to fetch all of the LED data from SPCN
|
|
*
|
|
* 1. SPCN_MOD_PRS_LED_DATA_FIRST --> First 1KB of LED data
|
|
* 2. SPCN_MOD_PRS_LED_DATA_SUB --> Subsequent 1KB of LED data
|
|
*
|
|
* Once the SPCN_RSP_STATUS_SUCCESS response code has been received
|
|
* indicating the last batch of 1KB LED data is here, the list addition
|
|
* process is now complete and we enable LED support for FSP async commands
|
|
* and for OPAL interface.
|
|
*/
|
|
static void fsp_read_leds_data_complete(struct fsp_msg *msg)
|
|
{
|
|
struct fsp_led_data *led, *next;
|
|
struct fsp_msg *resp = msg->resp;
|
|
u32 cmd_hdr = 0;
|
|
int rc = 0;
|
|
|
|
u32 msg_status = resp->word1 & 0xff00;
|
|
u32 led_status = (resp->data.words[1] >> 24) & 0xff;
|
|
u16 data_len = (u16)(resp->data.words[1] & 0xffff);
|
|
|
|
if (msg_status != FSP_STATUS_SUCCESS) {
|
|
log_simple_error(&e_info(OPAL_RC_LED_SUPPORT),
|
|
"FSP returned error %x LED not supported\n",
|
|
msg_status);
|
|
/* LED support not available */
|
|
led_support = LED_STATE_ABSENT;
|
|
|
|
fsp_freemsg(msg);
|
|
return;
|
|
}
|
|
|
|
/* SPCN command status */
|
|
switch (led_status) {
|
|
/* Last 1KB of LED data */
|
|
case SPCN_RSP_STATUS_SUCCESS:
|
|
prlog(PR_DEBUG, "SPCN_RSP_STATUS_SUCCESS: %d bytes received\n",
|
|
data_len);
|
|
|
|
led_support = LED_STATE_PRESENT;
|
|
|
|
/* Copy data to the local list */
|
|
fsp_process_leds_data(data_len);
|
|
|
|
/* LEDs captured on the system */
|
|
prlog(PR_DEBUG, "CEC LEDs captured on the system:\n");
|
|
list_for_each_safe(&cec_ledq, led, next, link) {
|
|
prlog(PR_DEBUG,
|
|
"rid: %x\t"
|
|
"len: %x "
|
|
"lcode: %-30s\t"
|
|
"parms: %04x\t"
|
|
"status: %04x\n",
|
|
led->rid,
|
|
led->lc_len,
|
|
led->loc_code,
|
|
led->parms,
|
|
led->status);
|
|
}
|
|
|
|
prlog(PR_DEBUG, "ENCL LEDs captured on the system:\n");
|
|
list_for_each_safe(&encl_ledq, led, next, link) {
|
|
prlog(PR_DEBUG,
|
|
"rid: %x\t"
|
|
"len: %x "
|
|
"lcode: %-30s\t"
|
|
"parms: %04x\t"
|
|
"status: %04x\n",
|
|
led->rid,
|
|
led->lc_len,
|
|
led->loc_code,
|
|
led->parms,
|
|
led->status);
|
|
}
|
|
|
|
break;
|
|
|
|
/* If more 1KB of LED data present */
|
|
case SPCN_RSP_STATUS_COND_SUCCESS:
|
|
prlog(PR_DEBUG, "SPCN_RSP_STATUS_COND_SUCCESS: %d bytes "
|
|
" received\n", data_len);
|
|
|
|
/* Copy data to the local list */
|
|
fsp_process_leds_data(data_len);
|
|
|
|
/* Fetch the remaining data from SPCN */
|
|
last_spcn_cmd = SPCN_MOD_PRS_LED_DATA_SUB;
|
|
cmd_hdr = SPCN_MOD_PRS_LED_DATA_SUB << 24 | SPCN_CMD_PRS << 16;
|
|
rc = fsp_queue_msg(fsp_mkmsg(FSP_CMD_SPCN_PASSTHRU, 4,
|
|
SPCN_ADDR_MODE_CEC_NODE,
|
|
cmd_hdr, 0, PSI_DMA_LED_BUF),
|
|
fsp_read_leds_data_complete);
|
|
if (rc) {
|
|
prlog(PR_ERR, "SPCN_MOD_PRS_LED_DATA_SUB command"
|
|
" could not be queued\n");
|
|
|
|
led_support = LED_STATE_ABSENT;
|
|
}
|
|
break;
|
|
|
|
/* Other expected error codes*/
|
|
case SPCN_RSP_STATUS_INVALID_RACK:
|
|
case SPCN_RSP_STATUS_INVALID_SLAVE:
|
|
case SPCN_RSP_STATUS_INVALID_MOD:
|
|
case SPCN_RSP_STATUS_STATE_PROHIBIT:
|
|
case SPCN_RSP_STATUS_UNKNOWN:
|
|
default:
|
|
/* Replay the previous SPCN command */
|
|
replay_spcn_cmd(last_spcn_cmd);
|
|
}
|
|
fsp_freemsg(msg);
|
|
}
|
|
|
|
/*
|
|
* Init the LED state
|
|
*
|
|
* This is called during the host boot process. This is the place where
|
|
* we figure out all the LEDs present on the system, their state and then
|
|
* create structure out of those information and popullate two master lists.
|
|
* One for all the LEDs on the CEC and one for all the LEDs on the enclosure.
|
|
* The LED information contained in the lists will cater either to various
|
|
* FSP initiated async commands or POWERNV initiated OPAL calls. Need to make
|
|
* sure that this initialization process is complete before allowing any requets
|
|
* on LED. Also need to be called to re-fetch data from SPCN after any LED state
|
|
* have been updated.
|
|
*/
|
|
static void fsp_leds_query_spcn(void)
|
|
{
|
|
struct fsp_led_data *led = NULL;
|
|
int rc = 0;
|
|
|
|
u32 cmd_hdr = SPCN_MOD_PRS_LED_DATA_FIRST << 24 | SPCN_CMD_PRS << 16;
|
|
|
|
/* Till the last batch of LED data */
|
|
last_spcn_cmd = 0;
|
|
|
|
/* Empty the lists */
|
|
while (!list_empty(&cec_ledq)) {
|
|
led = list_pop(&cec_ledq, struct fsp_led_data, link);
|
|
free(led);
|
|
}
|
|
|
|
while (!list_empty(&encl_ledq)) {
|
|
led = list_pop(&encl_ledq, struct fsp_led_data, link);
|
|
free(led);
|
|
}
|
|
|
|
/* Allocate buffer with alignment requirements */
|
|
if (led_buffer == NULL) {
|
|
led_buffer = memalign(TCE_PSIZE, PSI_DMA_LED_BUF_SZ);
|
|
if (!led_buffer)
|
|
return;
|
|
}
|
|
|
|
/* TCE mapping - will not unmap */
|
|
fsp_tce_map(PSI_DMA_LED_BUF, led_buffer, PSI_DMA_LED_BUF_SZ);
|
|
|
|
/* Request the first 1KB of LED data */
|
|
last_spcn_cmd = SPCN_MOD_PRS_LED_DATA_FIRST;
|
|
rc = fsp_queue_msg(fsp_mkmsg(FSP_CMD_SPCN_PASSTHRU, 4,
|
|
SPCN_ADDR_MODE_CEC_NODE, cmd_hdr, 0,
|
|
PSI_DMA_LED_BUF), fsp_read_leds_data_complete);
|
|
if (rc)
|
|
prlog(PR_ERR,
|
|
"SPCN_MOD_PRS_LED_DATA_FIRST command could"
|
|
" not be queued\n");
|
|
else /* Initiated LED list fetch MBOX command */
|
|
led_support = LED_STATE_READING;
|
|
}
|
|
|
|
/* Init the LED subsystem at boot time */
|
|
void fsp_led_init(void)
|
|
{
|
|
led_buffer = NULL;
|
|
|
|
if (!fsp_present())
|
|
return;
|
|
|
|
/* Init the master lists */
|
|
list_head_init(&cec_ledq);
|
|
list_head_init(&encl_ledq);
|
|
list_head_init(&spcn_cmdq);
|
|
|
|
fsp_leds_query_spcn();
|
|
|
|
loc_code_list_buffer = memalign(TCE_PSIZE, PSI_DMA_LOC_COD_BUF_SZ);
|
|
if (loc_code_list_buffer == NULL)
|
|
prerror("ERROR: Unable to allocate loc_code_list_buffer!\n");
|
|
|
|
prlog(PR_TRACE, "Init completed\n");
|
|
|
|
/* Get System attention indicator state */
|
|
dt_get_sai_loc_code();
|
|
fsp_get_sai();
|
|
|
|
/* Handle FSP initiated async LED commands */
|
|
fsp_register_client(&fsp_indicator_client, FSP_MCLASS_INDICATOR);
|
|
prlog(PR_TRACE, "FSP async command client registered\n");
|
|
|
|
/* Register for SAI update notification */
|
|
sysparam_add_update_notifier(sai_update_notification);
|
|
|
|
opal_register(OPAL_LEDS_GET_INDICATOR, fsp_opal_leds_get_ind, 4);
|
|
opal_register(OPAL_LEDS_SET_INDICATOR, fsp_opal_leds_set_ind, 5);
|
|
prlog(PR_TRACE, "LED OPAL interface registered\n");
|
|
}
|