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