blob: 5a552ab3e43f5d6748fbb60638fb51e504c7efee [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/*
* LED location code and indicator handling
*
* Copyright 2013-2019 IBM Corp.
*/
#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,
cpu_to_be64(async_token),
cpu_to_be64(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 = fsp_msg_get_data_word(msg->resp, 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 = fsp_msg_get_data_word(msg, 2);
uint32_t param_id = fsp_msg_get_data_word(msg, 0);
int len = fsp_msg_get_data_word(msg, 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 = cpu_to_be16(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 |= cpu_to_be16(SPCN_LED_FAULT_MASK);
if (spcn_cmd->state == LED_STATE_OFF)
sled.state &= cpu_to_be16(~SPCN_LED_FAULT_MASK);
}
/* LED IDENTIFY command */
if (spcn_cmd->command == LED_COMMAND_IDENTIFY) {
if (spcn_cmd->state == LED_STATE_ON)
sled.state |= cpu_to_be16(SPCN_LED_IDENTIFY_MASK);
if (spcn_cmd->state == LED_STATE_OFF)
sled.state &= cpu_to_be16(~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, __be16, 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, be16_to_cpu(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 = cpu_to_be16(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 + be16_to_cpu(lcode.size)) > PSI_DMA_LOC_COD_BUF_SZ)
return 0;
/* Copy over to the buffer */
memcpy(out_data, &lcode, sizeof(lcode));
return be16_to_cpu(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 = fsp_msg_get_data_word(msg, 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",
be16_to_cpu(req.req_type), req.loc_code);
fsp_ret_loc_code_list(be16_to_cpu(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 = fsp_msg_get_data_word(msg, 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 = fsp_msg_get_data_word(msg, 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 = fsp_msg_get_data_word(msg, 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, be16_to_cpu(req.size), req.lc_len, req.fld_sz,
be16_to_cpu(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 (be16_to_cpu(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(__be64 *led_mask, __be64 *led_value)
{
*led_mask |= cpu_to_be64(OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_ATTN);
if (sai_data.state & OPAL_SLOT_LED_STATE_ON)
*led_value |= cpu_to_be64(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, __be64 *led_mask,
__be64 *led_value, __be64 *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;
max = be64_to_cpu(*max_led_type);
/* Adjust max LED type */
if (max > OPAL_SLOT_LED_TYPE_MAX) {
supported = false;
max = OPAL_SLOT_LED_TYPE_MAX;
*max_led_type = cpu_to_be64(max);
}
/* Invalid parameter */
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 |= cpu_to_be64(OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_ID);
if (led->status & SPCN_LED_IDENTIFY_MASK)
*led_value |= cpu_to_be64(OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_ID);
/* Fault LED */
if (!max)
return OPAL_SUCCESS;
--max;
*led_mask |= cpu_to_be64(OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_FAULT);
if (led->status & SPCN_LED_FAULT_MASK)
*led_value |= cpu_to_be64(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, __be64 *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;
max = be64_to_cpu(*max_led_type);
/* Adjust max LED type */
if (max > OPAL_SLOT_LED_TYPE_MAX) {
supported = false;
max = OPAL_SLOT_LED_TYPE_MAX;
*max_led_type = cpu_to_be64(max);
}
/* 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;
__be16 tmp;
/* Prepare */
led_data = zalloc(sizeof(struct fsp_led_data));
assert(led_data);
/* Resource ID */
buf_read(buf, __be16, &tmp);
led_data->rid = be16_to_cpu(tmp);
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, __be16, &tmp);
led_data->parms = be16_to_cpu(tmp);
len -= sizeof(led_data->parms);
/* Status */
buf_read(buf, __be16, &tmp);
led_data->status = be16_to_cpu(tmp);
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 = (fsp_msg_get_data_word(resp, 1) >> 24) & 0xff;
u16 data_len = (u16)(fsp_msg_get_data_word(resp, 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");
}