aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/hw/fsp/fsp-leds.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/hw/fsp/fsp-leds.c')
-rw-r--r--roms/skiboot/hw/fsp/fsp-leds.c1939
1 files changed, 1939 insertions, 0 deletions
diff --git a/roms/skiboot/hw/fsp/fsp-leds.c b/roms/skiboot/hw/fsp/fsp-leds.c
new file mode 100644
index 000000000..5a552ab3e
--- /dev/null
+++ b/roms/skiboot/hw/fsp/fsp-leds.c
@@ -0,0 +1,1939 @@
+// 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");
+}