918 lines
21 KiB
C
918 lines
21 KiB
C
/* 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.
|
|
*/
|
|
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
#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(uint32_t *dump_id, uint32_t *dump_size,
|
|
uint32_t *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 = record->id;
|
|
*dump_size = record->size;
|
|
*dump_type = record->type;
|
|
|
|
out:
|
|
unlock(&dump_lock);
|
|
return rc;
|
|
}
|
|
|
|
static int64_t fsp_opal_dump_info(uint32_t *dump_id, uint32_t *dump_size)
|
|
{
|
|
uint32_t 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 = (msg->data.words[0] >> 16) & 0xff;
|
|
id = msg->data.words[0] & 0xffff;
|
|
dump_id = msg->data.words[1];
|
|
offset = msg->resp->data.words[1];
|
|
length = msg->resp->data.words[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",
|
|
msg->data.words[0]);
|
|
else
|
|
printf("DUMP: ACKed dump ID: 0x%x\n", msg->data.words[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",
|
|
msg->data.words[0], msg->data.words[1]);
|
|
|
|
add_dump_id_to_list(DUMP_TYPE_SYS,
|
|
msg->data.words[0], msg->data.words[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;
|
|
uint32_t dump_id, dump_size;
|
|
|
|
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();
|
|
}
|