From 1d880992fd8c8457a2d990ac6622cfd58fb1b261 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Wed, 2 Jul 2014 15:36:20 +1000 Subject: Initial commit of Open Source release Signed-off-by: Benjamin Herrenschmidt --- hw/fsp/fsp-leds.c | 1080 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1080 insertions(+) create mode 100644 hw/fsp/fsp-leds.c (limited to 'hw/fsp/fsp-leds.c') diff --git a/hw/fsp/fsp-leds.c b/hw/fsp/fsp-leds.c new file mode 100644 index 0000000..69b0583 --- /dev/null +++ b/hw/fsp/fsp-leds.c @@ -0,0 +1,1080 @@ +/* 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Debug prefix */ +#define PREFIX "FSPLED: " + +#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) + +//#define DBG(fmt...) do { printf(PREFIX fmt); } while(0) +#define DBG(fmt...) do { } while(0) + +/* SPCN replay threshold */ +#define SPCN_REPLAY_THRESHOLD 2 + +/* Sapphire LED support */ +static bool led_support; + +/* + * 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; + +/* 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 */ + +/* LED lock */ +static struct lock led_lock = LOCK_UNLOCKED; + +/* Last SPCN command */ +static u32 last_spcn_cmd; +static int replay = 0; + + +static void fsp_leds_query_spcn(void); +static void fsp_read_leds_data_complete(struct fsp_msg *msg); + +DEFINE_LOG_ENTRY(OPAL_RC_LED_SPCN, OPAL_PLATFORM_ERR_EVT, OPAL_LED, + OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA, NULL); + +DEFINE_LOG_ENTRY(OPAL_RC_LED_BUFF, OPAL_PLATFORM_ERR_EVT, OPAL_LED, + OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA, NULL); + +DEFINE_LOG_ENTRY(OPAL_RC_LED_LC, OPAL_PLATFORM_ERR_EVT, OPAL_LED, + OPAL_PLATFORM_FIRMWARE, OPAL_INFO, OPAL_NA, NULL); + +DEFINE_LOG_ENTRY(OPAL_RC_LED_STATE, OPAL_PLATFORM_ERR_EVT, OPAL_LED, + OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA, NULL); + +DEFINE_LOG_ENTRY(OPAL_RC_LED_SUPPORT, OPAL_PLATFORM_ERR_EVT, OPAL_LED, + OPAL_PLATFORM_FIRMWARE, OPAL_INFO, OPAL_NA, NULL); + +/* 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; + + /* Dont 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; +} + +/* + * Update both the local LED lists to reflect upon led state changes + * occured 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) +{ + struct fsp_led_data *led = NULL, *encl_led = NULL, *encl_cec_led = NULL; + bool is_encl_led = is_enclosure_led(loc_code); + + if (is_encl_led) + goto enclosure; + + /* Descendant LED in CEC list */ + led = fsp_find_cec_led(loc_code); + if (!led) { + log_simple_error(&e_info(OPAL_RC_LED_LC), + "LED: Could not find descendent LED in CEC LC=%s\n", + loc_code); + return; + } + led->status = led_state; + +enclosure: + /* 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), + "LED: Could not find enclosure LED in CEC LC=%s\n", + loc_code); + return; + } + + /* 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), + "LED: 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 void fsp_spcn_set_led_completion(struct fsp_msg *msg) +{ + bool fail; + u16 ckpt_status; + char loc_code[LOC_CODE_SIZE + 1]; + struct fsp_msg *resp = msg->resp; + u32 cmd = FSP_RSP_SET_LED_STATE; + u8 status = resp->word1 & 0xff00; + + /* + * LED state update request came as part of FSP async message + * FSP_CMD_SET_LED_STATE, hence need to send response message. + */ + fail = (status == FSP_STATUS_INVALID_DATA) || + (status == FSP_STATUS_DMA_ERROR) || + (status == FSP_STATUS_SPCN_ERROR); + + /* SPCN command failed: Identify the command and roll back changes */ + if (fail) { + log_simple_error(&e_info(OPAL_RC_LED_SPCN), + "LED: Last SPCN command failed, status=%02x\n", + status); + cmd |= FSP_STATUS_GENERIC_ERROR; + + /* Identify the failed command */ + memset(loc_code, 0, sizeof(loc_code)); + strncpy(loc_code, + ((struct fsp_led_data *)(msg->user_data))->loc_code, + LOC_CODE_SIZE); + ckpt_status = ((struct fsp_led_data *)(msg->user_data)) + ->ckpt_status; + + /* Rollback the changes */ + update_led_list(loc_code, ckpt_status); + } + fsp_queue_msg(fsp_mkmsg(cmd, 0), fsp_freemsg); +} + +/* + * 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(char *loc_code, bool command, bool state) +{ + 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; + int rc = 0; + + sled.lc_len = strlen(loc_code); + strncpy(sled.lc_code, loc_code, sled.lc_len); + + /* 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(loc_code); + + /* LED not present */ + if (led == NULL) { + u32 cmd = 0; + int rc = -1; + + cmd = FSP_RSP_SET_LED_STATE | FSP_STATUS_INVALID_LC; + fsp_queue_msg(fsp_mkmsg(cmd, 0), fsp_freemsg); + return rc; + } + + /* + * Checkpoint the status here, will use it if the SPCN + * command eventually fails. + */ + led->ckpt_status = led->status; + sled.state = led->status; + + /* Update the exclussive LED bits */ + if (is_enclosure_led(loc_code)) { + if (command == LED_COMMAND_FAULT) { + if (state == LED_STATE_ON) + led->excl_bit |= FSP_LED_EXCL_FAULT; + if (state == LED_STATE_OFF) + led->excl_bit &= ~FSP_LED_EXCL_FAULT; + } + + if (command == LED_COMMAND_IDENTIFY) { + if (state == LED_STATE_ON) + led->excl_bit |= FSP_LED_EXCL_IDENTIFY; + if (state == LED_STATE_OFF) + led->excl_bit &= ~FSP_LED_EXCL_IDENTIFY; + } + } + + /* LED FAULT commad */ + if (command == LED_COMMAND_FAULT) { + if (state == LED_STATE_ON) + sled.state |= SPCN_LED_FAULT_MASK; + if (state == LED_STATE_OFF) + sled.state &= ~SPCN_LED_FAULT_MASK; + } + + /* LED IDENTIFY command */ + if (command == LED_COMMAND_IDENTIFY){ + if (state == LED_STATE_ON) + sled.state |= SPCN_LED_IDENTIFY_MASK; + if (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); + /* + * Update the local lists based on the attempted SPCN command to + * set/reset an individual led (CEC or ENCL). + */ + lock(&led_lock); + update_led_list(loc_code, sled.state); + msg->user_data = led; + unlock(&led_lock); + + rc = fsp_queue_msg(msg, fsp_spcn_set_led_completion); + 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, dont 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; + + 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; + + /* Init the addresses */ + data = (u8 *) PSI_DMA_LOC_COD_BUF; + 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 = 0; + 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 = 0; + 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)); + fsp_queue_msg(fsp_mkmsg(FSP_RSP_GET_LED_LIST, + 3, 0, PSI_DMA_LOC_COD_BUF, total_size), + fsp_freemsg); +} + +/* + * 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 + */ +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) { + fsp_queue_msg(fsp_mkmsg(FSP_RSP_GET_LED_LIST | + FSP_STATUS_INVALID_DATA, + 0), fsp_freemsg); + return; + } + memcpy(&req, buf, sizeof(req)); + + printf(PREFIX "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. + */ +void fsp_free_led_list_buf(struct fsp_msg *msg) +{ + u32 tce_token = msg->data.words[1]; + u32 cmd = FSP_RSP_RET_LED_BUFFER; + + /* Token does not point to outbound buffer */ + if (tce_token != PSI_DMA_LOC_COD_BUF) { + log_simple_error(&e_info(OPAL_RC_LED_BUFF), + "LED: Invalid tce token from FSP\n"); + cmd |= FSP_STATUS_GENERIC_ERROR; + fsp_queue_msg(fsp_mkmsg(cmd, 0), fsp_freemsg); + return; + } + + /* Unmap the location code DMA buffer */ + fsp_tce_unmap(PSI_DMA_LOC_COD_BUF, PSI_DMA_LOC_COD_BUF_SZ); + + /* Respond the FSP */ + fsp_queue_msg(fsp_mkmsg(cmd, 0), fsp_freemsg); +} + +static void fsp_ret_led_state(char *loc_code) +{ + struct fsp_led_data *led, *next; + u8 ind_state = 0; + + 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; + fsp_queue_msg(fsp_mkmsg(FSP_RSP_GET_LED_STATE, 1, ind_state), + fsp_freemsg); + return; + } + + /* Location code not found */ + log_simple_error(&e_info(OPAL_RC_LED_LC), + "LED: Could not find the location code LC=%s\n", loc_code); + + fsp_queue_msg(fsp_mkmsg(FSP_RSP_GET_LED_STATE | + FSP_STATUS_INVALID_LC, 1, 0xff), fsp_freemsg); +} + +/* + * FSP async command: FSP_CMD_GET_LED_STATE + * + * With this command FSP query the state for any given LED + */ +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) { + fsp_queue_msg(fsp_mkmsg(FSP_RSP_GET_LED_STATE | + FSP_STATUS_INVALID_DATA, 0), + fsp_freemsg); + return; + } + memcpy(&req, buf, sizeof(req)); + + DBG("%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), + "LED: 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 + */ +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; + + /* Parse the inbound buffer */ + buf = fsp_inbound_buf_from_tce(tce_token); + if (!buf) { + fsp_queue_msg(fsp_mkmsg(FSP_RSP_SET_LED_STATE | + FSP_STATUS_INVALID_DATA, + 0), fsp_freemsg); + return; + } + memcpy(&req, buf, sizeof(req)); + + DBG("%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), + "LED: 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; + + if (fsp_msg_set_led_state(led->loc_code, + command, state)) + log_simple_error(&e_info(OPAL_RC_LED_STATE), + "LED: Set led state failed at LC=%s\n", + led->loc_code); + } + break; + case SET_IND_SINGLE_LOC_CODE: + /* Set led state for single descendent led */ + if (fsp_msg_set_led_state(req.loc_code, command, state)) + log_simple_error(&e_info(OPAL_RC_LED_STATE), + "LED: Set led state failed at LC=%s\n", + req.loc_code); + break; + default: + fsp_queue_msg(fsp_mkmsg(FSP_RSP_SET_LED_STATE | + FSP_STATUS_NOT_SUPPORTED, 0), + fsp_freemsg); + } +} + +/* Handle received indicator message from FSP */ +static bool fsp_indicator_message(u32 cmd_sub_mod, struct fsp_msg *msg) +{ + /* LED support not available yet */ + if (!led_support) { + log_simple_error(&e_info(OPAL_RC_LED_SUPPORT), + PREFIX "Indicator message while LED support not" + " available yet\n"); + return false; + } + + switch(cmd_sub_mod) { + case FSP_CMD_GET_LED_LIST: + printf(PREFIX + "FSP_CMD_GET_LED_LIST command received\n"); + fsp_get_led_list(msg); + return true; + case FSP_CMD_RET_LED_BUFFER: + printf(PREFIX + "FSP_CMD_RET_LED_BUFFER command received\n"); + fsp_free_led_list_buf(msg); + return true; + case FSP_CMD_GET_LED_STATE: + printf(PREFIX + "FSP_CMD_GET_LED_STATE command received\n"); + fsp_get_led_state(msg); + return true; + case FSP_CMD_SET_LED_STATE: + printf(PREFIX + "FSP_CMD_SET_LED_STATE command received\n"); + fsp_set_led_state(msg); + return true; + default: + printf(PREFIX + "Invalid FSP async sub command %06x\n", + cmd_sub_mod); + return false; + } +} + +/* Indicator class client */ +static struct fsp_client fsp_indicator_client = { + .message = fsp_indicator_message, +}; + +/* + * 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 lenght '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 = 0; + + /* Reached threshold */ + if (replay == SPCN_REPLAY_THRESHOLD) { + replay = 0; + 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) + printf(PREFIX + "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) + printf(PREFIX + "Replay SPCN_MOD_PRS_LED_DATA_SUB" + " command could not be queued\n"); + } +} + +/* + * 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), + "LED: FSP returned error %x LED not supported\n", + msg_status); + /* LED support not available */ + led_support = false; + return; + } + + /* SPCN command status */ + switch (led_status) { + /* Last 1KB of LED data */ + case SPCN_RSP_STATUS_SUCCESS: + printf(PREFIX + "SPCN_RSP_STATUS_SUCCESS: %d bytes received\n", + data_len); + + /* Copy data to the local list */ + fsp_process_leds_data(data_len); + led_support = true; + + /* LEDs captured on the system */ + printf(PREFIX "CEC LEDs captured on the system:\n"); + list_for_each_safe(&cec_ledq, led, next, link) { + printf(PREFIX "rid: %x\t", led->rid); + printf("len: %x ", led->lc_len); + printf("lcode: %-30s\t", led->loc_code); + printf("parms: %04x\t", led->parms); + printf("status: %04x\n", led->status); + } + + printf(PREFIX "ENCL LEDs captured on the system:\n"); + list_for_each_safe(&encl_ledq, led, next, link) { + printf(PREFIX "rid: %x\t", led->rid); + printf("len: %x ", led->lc_len); + printf("lcode: %-30s\t", led->loc_code); + printf("parms: %04x\t", led->parms); + printf("status: %04x\n", led->status); + } + + break; + + /* If more 1KB of LED data present */ + case SPCN_RSP_STATUS_COND_SUCCESS: + printf(PREFIX + "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) + printf(PREFIX + "SPCN_MOD_PRS_LED_DATA_SUB command" + " could not be queued\n"); + 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: + /* 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() +{ + 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 */ + led_support = false; + 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) + printf(PREFIX + "SPCN_MOD_PRS_LED_DATA_FIRST command could" + " not be queued\n"); +} + +/* Init the LED subsystem at boot time */ +void fsp_led_init(void) +{ + led_buffer = NULL; + + /* Init the master lists */ + list_head_init(&cec_ledq); + list_head_init(&encl_ledq); + + fsp_leds_query_spcn(); + printf(PREFIX "Init completed\n"); + + /* Handle FSP initiated async LED commands */ + fsp_register_client(&fsp_indicator_client, FSP_MCLASS_INDICATOR); + printf(PREFIX "FSP async command client registered\n"); +} -- cgit v1.1