| // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| /* |
| * Dump support: |
| * We get dump notification from different sources: |
| * - During system initialization via HDAT |
| * - During FSP reset/reload (FipS dump) |
| * - Dump available notification MBOX command (0xCE, 0x78, 0x00) |
| * |
| * To avoid complications, we keep list of dumps in a list and fetch |
| * them serially. |
| * |
| * Dump retrieve process: |
| * - Once we get notification from FSP we enqueue the dump ID and notify |
| * Linux via OPAL event notification. |
| * - Linux reads dump info and allocates required memory to fetch the dump |
| * and makes dump read call. |
| * - Sapphire fetches dump data from FSP. |
| * - Linux writes dump to disk and sends acknowledgement. |
| * - Sapphire acknowledges FSP. |
| * |
| * Copyright 2013-2015 IBM Corp. |
| */ |
| |
| #include <fsp.h> |
| #include <psi.h> |
| #include <lock.h> |
| #include <device.h> |
| #include <skiboot.h> |
| #include <errorlog.h> |
| #include <opal-api.h> |
| |
| /* |
| * Max outstanding dumps to retrieve |
| * |
| * Note: |
| * Dumps are serialized. We don't get notification for second |
| * dump of given type until we acknowledge first one. But we |
| * may get notification for different dump type. And our dump |
| * retrieval code is serialized. Hence we use list to keep |
| * track of outstanding dumps to be retrieved. |
| */ |
| #define MAX_DUMP_RECORD 0x04 |
| |
| /* Max retry */ |
| #define FIPS_DUMP_MAX_RETRY 0x03 |
| |
| /* Dump type */ |
| #define DUMP_TYPE_FSP 0x01 |
| #define DUMP_TYPE_SYS 0x02 |
| #define DUMP_TYPE_SMA 0x03 |
| |
| /* Dump fetch size */ |
| #define DUMP_FETCH_SIZE_FSP 0x500000 |
| #define DUMP_FETCH_SIZE_SYS 0x400000 |
| #define DUMP_FETCH_SIZE_RES 0x200000 |
| |
| /* Params for Fips dump */ |
| #define FSP_DUMP_TOOL_TYPE "SYS " |
| #define FSP_DUMP_CLIENT_ID "SAPPHIRE_CLIENT" |
| |
| enum dump_state { |
| DUMP_STATE_ABSENT, /* No FSP dump */ |
| DUMP_STATE_NONE, /* No dump to retrieve */ |
| DUMP_STATE_NOTIFY, /* Notified Linux */ |
| DUMP_STATE_FETCHING, /* Dump retrieval is in progress */ |
| DUMP_STATE_FETCH, /* Dump retrieve complete */ |
| DUMP_STATE_PARTIAL, /* Partial read */ |
| DUMP_STATE_ABORTING, /* Aborting due to kexec */ |
| }; |
| |
| /* Pending dump list */ |
| struct dump_record { |
| uint8_t type; |
| uint32_t id; |
| uint32_t size; |
| struct list_node link; |
| }; |
| |
| /* List definations */ |
| static LIST_HEAD(dump_pending); |
| static LIST_HEAD(dump_free); |
| |
| /* Dump retrieve state */ |
| static enum dump_state dump_state = DUMP_STATE_NONE; |
| |
| /* Dump buffer SG list */ |
| static struct opal_sg_list *dump_data; |
| static struct dump_record *dump_entry; |
| static int64_t dump_offset; |
| static size_t fetch_remain; |
| |
| /* FipS dump retry count */ |
| static int retry_cnt; |
| |
| /* Protect list and dump retrieve state */ |
| static struct lock dump_lock = LOCK_UNLOCKED; |
| |
| /* Forward declaration */ |
| static int64_t fsp_opal_dump_init(uint8_t dump_type); |
| static int64_t fsp_dump_read(void); |
| |
| DEFINE_LOG_ENTRY(OPAL_RC_DUMP_INIT, OPAL_PLATFORM_ERR_EVT, OPAL_DUMP, |
| OPAL_PLATFORM_FIRMWARE, |
| OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_REBOOT, |
| OPAL_NA); |
| |
| DEFINE_LOG_ENTRY(OPAL_RC_DUMP_LIST, OPAL_PLATFORM_ERR_EVT, OPAL_DUMP, |
| OPAL_PLATFORM_FIRMWARE, |
| OPAL_INFO, |
| OPAL_NA); |
| |
| DEFINE_LOG_ENTRY(OPAL_RC_DUMP_ACK, OPAL_PLATFORM_ERR_EVT, OPAL_DUMP, |
| OPAL_PLATFORM_FIRMWARE, OPAL_INFO, |
| OPAL_NA); |
| |
| /* |
| * Helper functions |
| */ |
| static inline void update_dump_state(enum dump_state state) |
| { |
| dump_state = state; |
| } |
| |
| static int64_t check_dump_state(void) |
| { |
| switch (dump_state) { |
| case DUMP_STATE_ABSENT: |
| return OPAL_HARDWARE; |
| case DUMP_STATE_NONE: |
| case DUMP_STATE_NOTIFY: |
| /* During dump fetch, notify is wrong state */ |
| return OPAL_WRONG_STATE; |
| case DUMP_STATE_FETCHING: |
| case DUMP_STATE_ABORTING: |
| return OPAL_BUSY_EVENT; |
| case DUMP_STATE_FETCH: |
| return OPAL_SUCCESS; |
| case DUMP_STATE_PARTIAL: |
| return OPAL_PARTIAL; |
| } |
| return OPAL_SUCCESS; |
| } |
| |
| static inline void dump_tce_map(uint32_t tce_offset, |
| void *buffer, uint32_t size) |
| { |
| uint32_t tlen = ALIGN_UP(size, TCE_PSIZE); |
| fsp_tce_map(PSI_DMA_DUMP_DATA + tce_offset, buffer, tlen); |
| } |
| |
| static inline void dump_tce_unmap(uint32_t size) |
| { |
| fsp_tce_unmap(PSI_DMA_DUMP_DATA, size); |
| } |
| |
| /* |
| * Returns Data set ID for the given dump type |
| */ |
| static inline uint16_t get_dump_data_set_id(uint8_t type) |
| { |
| switch (type) { |
| case DUMP_TYPE_FSP: |
| return FSP_DATASET_SP_DUMP; |
| case DUMP_TYPE_SYS: |
| return FSP_DATASET_HW_DUMP; |
| default: |
| break; |
| } |
| return OPAL_INTERNAL_ERROR; |
| } |
| |
| /* |
| * Returns max data we can fetch from FSP fetch data call |
| */ |
| static inline int64_t get_dump_fetch_max_size(uint8_t type) |
| { |
| switch (type) { |
| case DUMP_TYPE_FSP: |
| return DUMP_FETCH_SIZE_FSP; |
| case DUMP_TYPE_SYS: |
| return DUMP_FETCH_SIZE_SYS; |
| default: |
| break; |
| } |
| return OPAL_INTERNAL_ERROR; |
| } |
| |
| /* |
| * Get dump record from pending list |
| */ |
| static inline struct dump_record *get_dump_rec_from_list(uint32_t id) |
| { |
| struct dump_record *record; |
| |
| list_for_each(&dump_pending, record, link) { |
| if (record->id == id) |
| return record; |
| } |
| return NULL; |
| } |
| |
| /* |
| * New dump available notification to Linux |
| */ |
| static void update_opal_dump_notify(void) |
| { |
| /* |
| * Wait until current dump retrieval to complete |
| * before notifying again. |
| */ |
| if (dump_state != DUMP_STATE_NONE) |
| return; |
| |
| /* More dump's to retrieve */ |
| if (!list_empty(&dump_pending)) { |
| update_dump_state(DUMP_STATE_NOTIFY); |
| opal_update_pending_evt(OPAL_EVENT_DUMP_AVAIL, |
| OPAL_EVENT_DUMP_AVAIL); |
| } |
| } |
| |
| static int64_t remove_dump_id_from_list(uint32_t dump_id) |
| { |
| struct dump_record *record, *nxt_record; |
| int rc = OPAL_SUCCESS; |
| bool found = false; |
| |
| /* Remove record from pending list */ |
| list_for_each_safe(&dump_pending, record, nxt_record, link) { |
| if (record->id != dump_id) |
| continue; |
| |
| found = true; |
| list_del(&record->link); |
| list_add(&dump_free, &record->link); |
| break; |
| } |
| |
| /* |
| * Continue update_opal_dump_notify even if it fails |
| * to remove ID. So that we can resend notification |
| * for the same dump ID to Linux. |
| */ |
| if (!found) { /* List corrupted? */ |
| log_simple_error(&e_info(OPAL_RC_DUMP_LIST), |
| "DUMP: ID 0x%x not found in list!\n", |
| dump_id); |
| rc = OPAL_PARAMETER; |
| } |
| |
| /* Update state */ |
| update_dump_state(DUMP_STATE_NONE); |
| /* Notify next available dump to retrieve */ |
| update_opal_dump_notify(); |
| |
| return rc; |
| } |
| |
| static int64_t add_dump_id_to_list(uint8_t dump_type, |
| uint32_t dump_id, uint32_t dump_size) |
| { |
| struct dump_record *record; |
| int rc = OPAL_SUCCESS; |
| |
| lock(&dump_lock); |
| |
| rc = check_dump_state(); |
| if (rc == OPAL_HARDWARE) |
| goto out; |
| |
| /* List is full ? */ |
| if (list_empty(&dump_free)) { |
| printf("DUMP: Dump ID 0x%x is not queued.\n", dump_id); |
| rc = OPAL_RESOURCE; |
| goto out; |
| } |
| |
| /* Already queued? */ |
| record = get_dump_rec_from_list(dump_id); |
| if (record) { |
| rc = OPAL_SUCCESS; |
| goto out; |
| } |
| |
| /* Add to list */ |
| record = list_pop(&dump_free, struct dump_record, link); |
| record->type = dump_type; |
| record->id = dump_id; |
| record->size = dump_size; |
| list_add_tail(&dump_pending, &record->link); |
| |
| /* OPAL notification */ |
| update_opal_dump_notify(); |
| rc = OPAL_SUCCESS; |
| |
| out: |
| unlock(&dump_lock); |
| return rc; |
| } |
| |
| static void dump_init_complete(struct fsp_msg *msg) |
| { |
| uint8_t status = (msg->resp->word1 >> 8) & 0xff; |
| |
| printf("DUMP: FipS dump init status = 0x%x\n", status); |
| fsp_freemsg(msg); |
| |
| switch (status) { |
| case FSP_STATUS_SUCCESS: |
| printf("DUMP: Initiated FipS dump.\n"); |
| break; |
| case FSP_STATUS_BUSY: /* Retry, if FSP is busy */ |
| if (retry_cnt++ < FIPS_DUMP_MAX_RETRY) |
| if (fsp_opal_dump_init(DUMP_TYPE_FSP) == OPAL_SUCCESS) |
| return; |
| break; |
| default: |
| break; |
| } |
| /* Reset max retry count */ |
| retry_cnt = 0; |
| } |
| |
| /* |
| * Initiate new FipS dump |
| */ |
| static int64_t fsp_opal_dump_init(uint8_t dump_type) |
| { |
| struct fsp_msg *msg; |
| int rc = OPAL_SUCCESS; |
| uint32_t *tool_type = (void *)FSP_DUMP_TOOL_TYPE; |
| uint32_t *client_id = (void *)FSP_DUMP_CLIENT_ID; |
| |
| /* Only FipS dump generate request is supported */ |
| if (dump_type != DUMP_TYPE_FSP) |
| return OPAL_PARAMETER; |
| |
| msg = fsp_mkmsg(FSP_CMD_FSP_DUMP_INIT, 6, *tool_type, |
| sizeof(FSP_DUMP_CLIENT_ID), *client_id, |
| *(client_id + 1), *(client_id + 2), *(client_id + 3)); |
| |
| if (!msg) { |
| log_simple_error(&e_info(OPAL_RC_DUMP_INIT), |
| "DUMP: Message allocation failed.\n"); |
| rc = OPAL_INTERNAL_ERROR; |
| } else if (fsp_queue_msg(msg, dump_init_complete)) { |
| log_simple_error(&e_info(OPAL_RC_DUMP_INIT), |
| "DUMP: Failed to queue FipS dump init request.\n"); |
| fsp_freemsg(msg); |
| rc = OPAL_INTERNAL_ERROR; |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * OPAL interface to send dump information to Linux. |
| */ |
| static int64_t fsp_opal_dump_info2(__be32 *dump_id, __be32 *dump_size, |
| __be32 *dump_type) |
| { |
| struct dump_record *record; |
| int rc = OPAL_SUCCESS; |
| |
| lock(&dump_lock); |
| |
| /* Clear notification */ |
| opal_update_pending_evt(OPAL_EVENT_DUMP_AVAIL, 0); |
| |
| record = list_top(&dump_pending, struct dump_record, link); |
| if (!record) { /* List corrupted? */ |
| update_dump_state(DUMP_STATE_NONE); |
| rc = OPAL_INTERNAL_ERROR; |
| goto out; |
| } |
| *dump_id = cpu_to_be32(record->id); |
| *dump_size = cpu_to_be32(record->size); |
| *dump_type = cpu_to_be32(record->type); |
| |
| out: |
| unlock(&dump_lock); |
| return rc; |
| } |
| |
| static int64_t fsp_opal_dump_info(__be32 *dump_id, __be32 *dump_size) |
| { |
| __be32 dump_type; |
| return fsp_opal_dump_info2(dump_id, dump_size, &dump_type); |
| } |
| |
| static int64_t validate_dump_sglist(struct opal_sg_list *list, |
| int64_t *size) |
| { |
| struct opal_sg_list *sg; |
| struct opal_sg_entry *prev_entry, *entry; |
| int length, num_entries, i; |
| |
| prev_entry = NULL; |
| *size = 0; |
| for (sg = list; sg; sg = (struct opal_sg_list*)be64_to_cpu(sg->next)) { |
| length = be64_to_cpu(sg->length) - 16; |
| num_entries = length / sizeof(struct opal_sg_entry); |
| if (num_entries <= 0) |
| return OPAL_PARAMETER; |
| |
| for (i = 0; i < num_entries; i++) { |
| entry = &sg->entry[i]; |
| *size += be64_to_cpu(entry->length); |
| |
| /* All entries must be aligned */ |
| if (((uint64_t)be64_to_cpu(entry->data)) & 0xfff) |
| return OPAL_PARAMETER; |
| |
| /* All non-terminal entries size must be aligned */ |
| if (prev_entry && (be64_to_cpu(prev_entry->length) & 0xfff)) |
| return OPAL_PARAMETER; |
| |
| prev_entry = entry; |
| } |
| } |
| return OPAL_SUCCESS; |
| } |
| |
| /* |
| * Map dump buffer to TCE buffer |
| */ |
| static int64_t map_dump_buffer(void) |
| { |
| struct opal_sg_list *sg; |
| struct opal_sg_entry *entry; |
| int64_t fetch_max; |
| int length, num_entries, i; |
| int buf_off, fetch_off, tce_off, sg_off; |
| bool last = false; |
| |
| /* FSP fetch max size */ |
| fetch_max = get_dump_fetch_max_size(dump_entry->type); |
| if (fetch_max > (dump_entry->size - dump_offset)) |
| fetch_remain = dump_entry->size - dump_offset; |
| else |
| fetch_remain = fetch_max; |
| |
| /* offsets */ |
| fetch_off = fetch_remain; |
| tce_off = sg_off = 0; |
| |
| for (sg = dump_data; sg; sg = (struct opal_sg_list*)be64_to_cpu(sg->next)) { |
| num_entries = (be64_to_cpu(sg->length) - 16) / |
| sizeof(struct opal_sg_entry); |
| if (num_entries <= 0) |
| return OPAL_PARAMETER; |
| |
| for (i = 0; i < num_entries; i++) { |
| entry = &sg->entry[i]; |
| |
| /* Continue until we get offset */ |
| if ((sg_off + be64_to_cpu(entry->length)) < dump_offset) { |
| sg_off += be64_to_cpu(entry->length); |
| continue; |
| } |
| |
| /* |
| * SG list entry size can be more than 4k. |
| * Map only required pages, instead of |
| * mapping entire entry. |
| */ |
| if (!tce_off) { |
| buf_off = (dump_offset - sg_off) & ~0xfff; |
| length = be64_to_cpu(entry->length) - buf_off; |
| } else { |
| buf_off = 0; |
| length = be64_to_cpu(entry->length); |
| } |
| |
| /* Adjust length for last mapping */ |
| if (fetch_off <= length) { |
| length = fetch_off; |
| last = true; |
| } |
| |
| /* Adjust offset */ |
| sg_off += be64_to_cpu(entry->length); |
| fetch_off -= length; |
| |
| /* TCE mapping */ |
| dump_tce_map(tce_off, (void*)(be64_to_cpu(entry->data) + buf_off), length); |
| tce_off += length; |
| |
| /* TCE mapping complete */ |
| if (last) |
| return OPAL_SUCCESS; |
| } |
| } /* outer loop */ |
| return OPAL_PARAMETER; |
| } |
| |
| static void dump_read_complete(struct fsp_msg *msg) |
| { |
| void *buffer; |
| size_t length, offset; |
| int rc; |
| uint32_t dump_id; |
| uint16_t id; |
| uint8_t flags, status; |
| bool compl = false; |
| |
| status = (msg->resp->word1 >> 8) & 0xff; |
| flags = (fsp_msg_get_data_word(msg, 0) >> 16) & 0xff; |
| id = fsp_msg_get_data_word(msg, 0) & 0xffff; |
| dump_id = fsp_msg_get_data_word(msg, 1); |
| offset = fsp_msg_get_data_word(msg->resp, 1); |
| length = fsp_msg_get_data_word(msg->resp, 2); |
| |
| fsp_freemsg(msg); |
| |
| lock(&dump_lock); |
| |
| if (dump_state == DUMP_STATE_ABORTING) { |
| printf("DUMP: Fetch dump aborted, ID = 0x%x\n", dump_id); |
| dump_tce_unmap(PSI_DMA_DUMP_DATA_SIZE); |
| update_dump_state(DUMP_STATE_NONE); |
| goto bail; |
| } |
| |
| switch (status) { |
| case FSP_STATUS_SUCCESS: /* Fetch next dump block */ |
| if (dump_offset < dump_entry->size) { |
| dump_tce_unmap(PSI_DMA_DUMP_DATA_SIZE); |
| rc = fsp_dump_read(); |
| if (rc == OPAL_SUCCESS) |
| goto bail; |
| } else { /* Dump read complete */ |
| compl = true; |
| } |
| break; |
| case FSP_STATUS_MORE_DATA: /* More data to read */ |
| offset += length; |
| buffer = (void *)PSI_DMA_DUMP_DATA + offset; |
| fetch_remain -= length; |
| |
| rc = fsp_fetch_data_queue(flags, id, dump_id, offset, buffer, |
| &fetch_remain, dump_read_complete); |
| if (rc == OPAL_SUCCESS) |
| goto bail; |
| break; |
| default: |
| break; |
| } |
| |
| dump_tce_unmap(PSI_DMA_DUMP_DATA_SIZE); |
| |
| /* Update state */ |
| if (compl) { |
| printf("DUMP: Fetch dump success. ID = 0x%x\n", dump_id); |
| update_dump_state(DUMP_STATE_FETCH); |
| } else { |
| printf("DUMP: Fetch dump partial. ID = 0x%x\n", dump_id); |
| update_dump_state(DUMP_STATE_PARTIAL); |
| } |
| bail: |
| unlock(&dump_lock); |
| } |
| |
| /* |
| * Fetch dump data from FSP |
| */ |
| static int64_t fsp_dump_read(void) |
| { |
| int64_t rc; |
| uint16_t data_set; |
| uint8_t flags = 0x00; |
| |
| /* Get data set ID */ |
| data_set = get_dump_data_set_id(dump_entry->type); |
| |
| /* Map TCE buffer */ |
| rc = map_dump_buffer(); |
| if (rc != OPAL_SUCCESS) { |
| printf("DUMP: TCE mapping failed\n"); |
| return rc; |
| } |
| |
| printf("DUMP: Fetch Dump. ID = %02x, sub ID = %08x, len = %ld\n", |
| data_set, dump_entry->id, fetch_remain); |
| |
| /* Fetch data */ |
| rc = fsp_fetch_data_queue(flags, data_set, dump_entry->id, |
| dump_offset, (void *)PSI_DMA_DUMP_DATA, |
| &fetch_remain, dump_read_complete); |
| |
| /* Adjust dump fetch offset */ |
| dump_offset += fetch_remain; |
| |
| return rc; |
| } |
| |
| static int64_t fsp_opal_dump_read(uint32_t dump_id, |
| struct opal_sg_list *list) |
| { |
| struct dump_record *record; |
| int64_t rc, size; |
| |
| lock(&dump_lock); |
| |
| /* Check state */ |
| if (dump_state != DUMP_STATE_NOTIFY) { |
| rc = check_dump_state(); |
| goto out; |
| } |
| |
| /* Validate dump ID */ |
| record = get_dump_rec_from_list(dump_id); |
| if (!record) { /* List corrupted? */ |
| rc = OPAL_INTERNAL_ERROR; |
| goto out; |
| } |
| |
| /* Validate dump buffer and size */ |
| rc = validate_dump_sglist(list, &size); |
| if (rc != OPAL_SUCCESS) { |
| printf("DUMP: SG list validation failed\n"); |
| goto out; |
| } |
| |
| if (size < record->size) { /* Insuffient buffer */ |
| printf("DUMP: Insufficient buffer\n"); |
| rc = OPAL_PARAMETER; |
| goto out; |
| } |
| |
| /* Update state */ |
| update_dump_state(DUMP_STATE_FETCHING); |
| |
| /* Fetch dump data */ |
| dump_entry = record; |
| dump_data = list; |
| dump_offset = 0; |
| rc = fsp_dump_read(); |
| if (rc != OPAL_SUCCESS) |
| goto out; |
| |
| /* Check status after initiating fetch data */ |
| rc = check_dump_state(); |
| |
| out: |
| unlock(&dump_lock); |
| return rc; |
| } |
| |
| static void dump_ack_complete(struct fsp_msg *msg) |
| { |
| uint8_t status = (msg->resp->word1 >> 8) & 0xff; |
| |
| if (status) |
| log_simple_error(&e_info(OPAL_RC_DUMP_ACK), |
| "DUMP: ACK failed for ID: 0x%x\n", |
| fsp_msg_get_data_word(msg, 0)); |
| else |
| printf("DUMP: ACKed dump ID: 0x%x\n", fsp_msg_get_data_word(msg, 0)); |
| |
| fsp_freemsg(msg); |
| } |
| |
| /* |
| * Acknowledge dump |
| */ |
| static int64_t fsp_opal_dump_ack(uint32_t dump_id) |
| { |
| struct dump_record *record; |
| struct fsp_msg *msg; |
| int rc; |
| uint32_t cmd; |
| uint8_t dump_type = 0; |
| |
| /* Get dump type */ |
| lock(&dump_lock); |
| record = get_dump_rec_from_list(dump_id); |
| if (record) |
| dump_type = record->type; |
| |
| /* |
| * Next available dump in pending list will be of different |
| * type. Hence we don't need to wait for ack complete. |
| * |
| * Note: |
| * This allows us to proceed even if we fail to ACK. |
| * In the worst case we may get notification for the |
| * same dump again, which is probably better than |
| * looping forever. |
| */ |
| rc = remove_dump_id_from_list(dump_id); |
| if (rc != OPAL_SUCCESS) /* Invalid dump id */ |
| goto out; |
| |
| /* Adjust mod value */ |
| cmd = FSP_CMD_ACK_DUMP | (dump_type & 0xff); |
| msg = fsp_mkmsg(cmd, 1, dump_id); |
| if (!msg) { |
| log_simple_error(&e_info(OPAL_RC_DUMP_ACK), |
| "DUMP: Message allocation failed.!\n"); |
| rc = OPAL_INTERNAL_ERROR; |
| } else if (fsp_queue_msg(msg, dump_ack_complete)) { |
| log_simple_error(&e_info(OPAL_RC_DUMP_ACK), |
| "DUMP: Failed to queue dump ack message.\n"); |
| fsp_freemsg(msg); |
| rc = OPAL_INTERNAL_ERROR; |
| } |
| out: |
| unlock(&dump_lock); |
| return rc; |
| } |
| |
| /* Resend dump available notification */ |
| static int64_t fsp_opal_dump_resend_notification(void) |
| { |
| lock(&dump_lock); |
| |
| if (dump_state != DUMP_STATE_ABSENT) |
| update_dump_state(DUMP_STATE_NONE); |
| |
| update_opal_dump_notify(); |
| |
| unlock(&dump_lock); |
| |
| return OPAL_SUCCESS; |
| } |
| |
| /* |
| * Handle FSP R/R event. |
| */ |
| static bool fsp_dump_retrieve_rr(uint32_t cmd_sub_mod, |
| struct fsp_msg *msg __unused) |
| { |
| switch (cmd_sub_mod) { |
| case FSP_RESET_START: |
| lock(&dump_lock); |
| /* Reset dump state */ |
| if (dump_state == DUMP_STATE_FETCHING) |
| update_dump_state(DUMP_STATE_ABORTING); |
| unlock(&dump_lock); |
| return true; |
| case FSP_RELOAD_COMPLETE: |
| lock(&dump_lock); |
| |
| /* Reset TCE mapping */ |
| dump_tce_unmap(PSI_DMA_DUMP_DATA_SIZE); |
| |
| /* Reset dump state */ |
| update_dump_state(DUMP_STATE_NONE); |
| |
| /* |
| * For now keeping R/R handler simple. In the worst case |
| * we may endup resending dump available notification for |
| * same dump ID twice to Linux. |
| */ |
| update_opal_dump_notify(); |
| unlock(&dump_lock); |
| return true; |
| } |
| return false; |
| } |
| |
| /* |
| * Handle host kexec'ing scenarios |
| */ |
| static bool opal_kexec_dump_notify(void *data __unused) |
| { |
| bool ready = true; |
| |
| lock(&dump_lock); |
| |
| /* Dump retrieve is in progress? */ |
| if (dump_state == DUMP_STATE_FETCHING) |
| dump_state = DUMP_STATE_ABORTING; |
| |
| /* Not yet safe to kexec */ |
| if (dump_state == DUMP_STATE_ABORTING) |
| ready = false; |
| |
| unlock(&dump_lock); |
| |
| return ready; |
| } |
| |
| /* |
| * FipS dump notification |
| */ |
| void fsp_fips_dump_notify(uint32_t dump_id, uint32_t dump_size) |
| { |
| printf("DUMP: FipS dump available. ID = 0x%x [size: %d bytes]\n", |
| dump_id, dump_size); |
| add_dump_id_to_list(DUMP_TYPE_FSP, dump_id, dump_size); |
| } |
| |
| /* |
| * System/Platform dump notification |
| */ |
| static bool fsp_sys_dump_notify(uint32_t cmd_sub_mod, struct fsp_msg *msg) |
| { |
| /* |
| * Though spec says mod 00 is deprecated we still |
| * seems to get mod 00 notification (at least on |
| * P7 machine). |
| */ |
| if (cmd_sub_mod != FSP_RSP_SYS_DUMP && |
| cmd_sub_mod != FSP_RSP_SYS_DUMP_OLD) |
| return false; |
| |
| printf("DUMP: Platform dump available. ID = 0x%x [size: %d bytes]\n", |
| fsp_msg_get_data_word(msg, 0), fsp_msg_get_data_word(msg, 1)); |
| |
| add_dump_id_to_list(DUMP_TYPE_SYS, |
| fsp_msg_get_data_word(msg, 0), |
| fsp_msg_get_data_word(msg, 1)); |
| return true; |
| } |
| |
| /* |
| * If platform dump available during IPL time, then we |
| * get notification via HDAT. Check for DT for the dump |
| * presence. |
| */ |
| static void check_ipl_sys_dump(void) |
| { |
| struct dt_node *dump_node, *opal_node; |
| uint32_t dump_id, dump_size; |
| |
| if (proc_gen >= proc_gen_p9) { |
| opal_node = dt_find_by_path(dt_root, "ibm,opal"); |
| if (!opal_node) |
| return; |
| dump_node = dt_find_by_path(opal_node, "dump"); |
| if (dump_node) { |
| if (dt_find_property(dump_node, "mpipl-boot")) |
| return; |
| } |
| } |
| |
| dump_node = dt_find_by_path(dt_root, "ipl-params/platform-dump"); |
| if (!dump_node) |
| return; |
| |
| if (!dt_find_property(dump_node, "dump-id")) |
| return; |
| |
| dump_id = dt_prop_get_u32(dump_node, "dump-id"); |
| dump_size = (uint32_t)dt_prop_get_u64(dump_node, "total-size"); |
| |
| printf("DUMP: Platform dump present during IPL.\n"); |
| printf(" ID = 0x%x [size: %d bytes]\n", dump_id, dump_size); |
| |
| add_dump_id_to_list(DUMP_TYPE_SYS, dump_id, dump_size); |
| } |
| |
| /* |
| * Allocate and initialize dump list |
| */ |
| static int init_dump_free_list(void) |
| { |
| struct dump_record *entry; |
| int i; |
| |
| entry = zalloc(sizeof(struct dump_record) * MAX_DUMP_RECORD); |
| if (!entry) { |
| log_simple_error(&e_info(OPAL_RC_DUMP_INIT), |
| "DUMP: Out of memory\n"); |
| return -ENOMEM; |
| } |
| |
| for (i = 0; i < MAX_DUMP_RECORD; i++) { |
| list_add_tail(&dump_free, &entry->link); |
| entry++; |
| } |
| return 0; |
| } |
| |
| static struct fsp_client fsp_sys_dump_client = { |
| .message = fsp_sys_dump_notify, |
| }; |
| |
| static struct fsp_client fsp_dump_client_rr = { |
| .message = fsp_dump_retrieve_rr, |
| }; |
| |
| void fsp_dump_init(void) |
| { |
| if (!fsp_present()) { |
| update_dump_state(DUMP_STATE_ABSENT); |
| return; |
| } |
| |
| /* Initialize list */ |
| if (init_dump_free_list() != 0) { |
| update_dump_state(DUMP_STATE_ABSENT); |
| return; |
| } |
| |
| /* Register for Class CE */ |
| fsp_register_client(&fsp_sys_dump_client, FSP_MCLASS_SERVICE); |
| /* Register for Class AA (FSP R/R) */ |
| fsp_register_client(&fsp_dump_client_rr, FSP_MCLASS_RR_EVENT); |
| |
| /* Register for sync on host reboot call */ |
| opal_add_host_sync_notifier(opal_kexec_dump_notify, NULL); |
| |
| /* OPAL interface */ |
| opal_register(OPAL_DUMP_INIT, fsp_opal_dump_init, 1); |
| opal_register(OPAL_DUMP_INFO, fsp_opal_dump_info, 2); |
| opal_register(OPAL_DUMP_INFO2, fsp_opal_dump_info2, 3); |
| opal_register(OPAL_DUMP_READ, fsp_opal_dump_read, 2); |
| opal_register(OPAL_DUMP_ACK, fsp_opal_dump_ack, 1); |
| opal_register(OPAL_DUMP_RESEND, fsp_opal_dump_resend_notification, 0); |
| |
| /* Check for platform dump presence during IPL time */ |
| check_ipl_sys_dump(); |
| } |