mirror of https://gitlab.com/qemu-project/qemu
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
732 lines
24 KiB
C
732 lines
24 KiB
C
/*
|
|
* Firmware Assisted Dump in PSeries
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/log.h"
|
|
#include "hw/ppc/spapr.h"
|
|
#include "qemu/units.h"
|
|
#include "system/cpus.h"
|
|
#include "system/hw_accel.h"
|
|
#include <math.h>
|
|
|
|
/*
|
|
* Copy the ascii values for first 8 characters from a string into u64
|
|
* variable at their respective indexes.
|
|
* e.g.
|
|
* The string "FADMPINF" will be converted into 0x4641444d50494e46
|
|
*/
|
|
static uint64_t fadump_str_to_u64(const char *str)
|
|
{
|
|
uint64_t val = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < sizeof(val); i++) {
|
|
val = (*str) ? (val << 8) | *str++ : val << 8;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* Get the identifier id for register entries of GPRs
|
|
*
|
|
* It gives the same id as 'fadump_str_to_u64' when the complete string id
|
|
* of the GPR is given, ie.
|
|
*
|
|
* fadump_str_to_u64("GPR05") == fadump_gpr_id_to_u64(5);
|
|
* fadump_str_to_u64("GPR12") == fadump_gpr_id_to_u64(12);
|
|
*
|
|
* And so on. Hence this can be implemented by creating a dynamic
|
|
* string for each GPR, such as "GPR00", "GPR01", ... "GPR31"
|
|
* Instead of allocating a string, an observation from the math of
|
|
* 'fadump_str_to_u64' or from PAPR tells us that there's a pattern
|
|
* in the identifier IDs, such that the first 4 bytes are affected only by
|
|
* whether it is GPR0*, GPR1*, GPR2*, GPR3*.
|
|
* Upper half of 5th byte is always 0x3. Lower half (nibble) of 5th byte
|
|
* is the tens digit of the GPR id, ie. GPR ID / 10.
|
|
* Upper half of 6th byte is always 0x3. Lower half (nibble) of 5th byte
|
|
* is the ones digit of the GPR id, ie. GPR ID % 10
|
|
*
|
|
* For example, for GPR 29, the 5th and 6th byte will be 0x32 and 0x39
|
|
*/
|
|
static uint64_t fadump_gpr_id_to_u64(uint32_t gpr_id)
|
|
{
|
|
uint64_t val = 0;
|
|
|
|
/* Valid range of GPR id is only GPR0 to GPR31 */
|
|
assert(gpr_id < 32);
|
|
|
|
/* Below calculations set the 0th to 5th byte */
|
|
if (gpr_id <= 9) {
|
|
val = fadump_str_to_u64("GPR0");
|
|
} else if (gpr_id <= 19) {
|
|
val = fadump_str_to_u64("GPR1");
|
|
} else if (gpr_id <= 29) {
|
|
val = fadump_str_to_u64("GPR2");
|
|
} else {
|
|
val = fadump_str_to_u64("GPR3");
|
|
}
|
|
|
|
/* Set the 6th byte */
|
|
val |= 0x30000000;
|
|
val |= ((gpr_id % 10) << 24);
|
|
|
|
return val;
|
|
}
|
|
|
|
/*
|
|
* Handle the "FADUMP_CMD_REGISTER" command in 'ibm,configure-kernel-dump'
|
|
*
|
|
* Note: Any changes made by the kernel to the fadump memory struct won't
|
|
* reflect in QEMU after the 'ibm,configure-kernel-dump' RTAS call has returned,
|
|
* as we store the passed fadump memory structure passed during fadump
|
|
* registration.
|
|
* Kernel has to invalidate & re-register fadump, if it intends to make any
|
|
* changes to the fadump memory structure
|
|
*
|
|
* Returns:
|
|
* * RTAS_OUT_SUCCESS: On successful registration
|
|
* * RTAS_OUT_PARAM_ERROR: If parameters are not correct, eg. too many
|
|
* sections, invalid memory addresses that we are
|
|
* unable to read, etc
|
|
* * RTAS_OUT_DUMP_ALREADY_REGISTERED: Dump already registered
|
|
* * RTAS_OUT_HW_ERROR: Misc issue such as memory access failures
|
|
*/
|
|
uint32_t do_fadump_register(SpaprMachineState *spapr, target_ulong args)
|
|
{
|
|
FadumpSectionHeader header;
|
|
FadumpSection regions[FADUMP_MAX_SECTIONS] = {0};
|
|
target_ulong fdm_addr = rtas_ld(args, 1);
|
|
target_ulong fdm_size = rtas_ld(args, 2);
|
|
AddressSpace *default_as = &address_space_memory;
|
|
MemTxResult io_result;
|
|
MemTxAttrs attrs;
|
|
uint64_t next_section_addr;
|
|
uint16_t dump_num_sections;
|
|
|
|
/* Mark the memory transaction as privileged memory access */
|
|
attrs.user = 0;
|
|
attrs.memory = 1;
|
|
|
|
if (spapr->fadump_registered) {
|
|
/* FADump already registered */
|
|
return RTAS_OUT_DUMP_ALREADY_REGISTERED;
|
|
}
|
|
|
|
if (spapr->fadump_dump_active) {
|
|
return RTAS_OUT_DUMP_ACTIVE;
|
|
}
|
|
|
|
if (fdm_size < sizeof(FadumpSectionHeader)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Header size is invalid: " TARGET_FMT_lu "\n", fdm_size);
|
|
return RTAS_OUT_PARAM_ERROR;
|
|
}
|
|
|
|
/* Ensure fdm_addr points to a valid RMR-memory/RMA-memory buffer */
|
|
if ((fdm_addr <= 0) || ((fdm_addr + fdm_size) > spapr->rma_size)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Invalid fdm address: " TARGET_FMT_lu "\n", fdm_addr);
|
|
return RTAS_OUT_PARAM_ERROR;
|
|
}
|
|
|
|
/* Try to read the passed fadump header */
|
|
io_result = address_space_read(default_as, fdm_addr, attrs,
|
|
&header, sizeof(header));
|
|
if (io_result != MEMTX_OK) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Unable to read fdm: " TARGET_FMT_lu "\n", fdm_addr);
|
|
|
|
return RTAS_OUT_HW_ERROR;
|
|
}
|
|
|
|
/* Verify that we understand the fadump header version */
|
|
if (header.dump_format_version != cpu_to_be32(FADUMP_VERSION)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Unknown fadump header version: 0x%x\n",
|
|
header.dump_format_version);
|
|
return RTAS_OUT_PARAM_ERROR;
|
|
}
|
|
|
|
/* Reset dump status flags */
|
|
header.dump_status_flag = 0;
|
|
|
|
dump_num_sections = be16_to_cpu(header.dump_num_sections);
|
|
|
|
if (dump_num_sections > FADUMP_MAX_SECTIONS) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Too many sections: %d sections\n", dump_num_sections);
|
|
return RTAS_OUT_PARAM_ERROR;
|
|
}
|
|
|
|
next_section_addr =
|
|
fdm_addr +
|
|
be32_to_cpu(header.offset_first_dump_section);
|
|
|
|
for (int i = 0; i < dump_num_sections; ++i) {
|
|
/* Read the fadump section from memory */
|
|
io_result = address_space_read(default_as, next_section_addr, attrs,
|
|
®ions[i], sizeof(regions[i]));
|
|
if (io_result != MEMTX_OK) {
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"FADump: Unable to read fadump %dth section\n", i);
|
|
return RTAS_OUT_PARAM_ERROR;
|
|
}
|
|
|
|
next_section_addr += sizeof(regions[i]);
|
|
}
|
|
|
|
spapr->fadump_registered = true;
|
|
spapr->fadump_dump_active = false;
|
|
|
|
/* Store the registered fadump memory struct */
|
|
spapr->registered_fdm.header = header;
|
|
for (int i = 0; i < dump_num_sections; ++i) {
|
|
spapr->registered_fdm.rgn[i] = regions[i];
|
|
}
|
|
|
|
return RTAS_OUT_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Copy the source region of given fadump section, to the destination
|
|
* address mentioned in the region
|
|
*
|
|
* Also set the region's error flag, if the copy fails due to non-existent
|
|
* address (MEMTX_DECODE_ERROR) or permission issues (MEMTX_ACCESS_ERROR)
|
|
*
|
|
* Returns true if successful copy
|
|
*
|
|
* Returns false in case of any other error, being treated as hardware
|
|
* error for fadump purposes
|
|
*/
|
|
static bool do_preserve_region(FadumpSection *region)
|
|
{
|
|
AddressSpace *default_as = &address_space_memory;
|
|
MemTxResult io_result;
|
|
MemTxAttrs attrs;
|
|
uint64_t src_addr, src_len, dest_addr;
|
|
uint64_t num_chunks;
|
|
g_autofree void *copy_buffer = NULL;
|
|
|
|
src_addr = be64_to_cpu(region->source_address);
|
|
src_len = be64_to_cpu(region->source_len);
|
|
dest_addr = be64_to_cpu(region->destination_address);
|
|
|
|
/* Mark the memory transaction as privileged memory access */
|
|
attrs.user = 0;
|
|
attrs.memory = 1;
|
|
|
|
/*
|
|
* Optimisation: Skip copy if source and destination are same
|
|
* (eg. param area)
|
|
*/
|
|
if (src_addr == dest_addr) {
|
|
region->bytes_dumped = cpu_to_be64(src_len);
|
|
return true;
|
|
}
|
|
|
|
#define FADUMP_CHUNK_SIZE ((size_t)(32 * MiB))
|
|
copy_buffer = g_try_malloc(FADUMP_CHUNK_SIZE);
|
|
if (copy_buffer == NULL) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Failed allocating memory (size: %zu) for copying"
|
|
" reserved memory regions\n", FADUMP_CHUNK_SIZE);
|
|
return false;
|
|
}
|
|
|
|
num_chunks = ceil((src_len * 1.0f) / FADUMP_CHUNK_SIZE);
|
|
for (uint64_t chunk_id = 0; chunk_id < num_chunks; ++chunk_id) {
|
|
/* Take minimum of bytes left to copy, and chunk size */
|
|
uint64_t copy_len = MIN(
|
|
src_len - (chunk_id * FADUMP_CHUNK_SIZE),
|
|
FADUMP_CHUNK_SIZE
|
|
);
|
|
|
|
/* Copy the source region to destination */
|
|
io_result = address_space_read(default_as, src_addr, attrs,
|
|
copy_buffer, copy_len);
|
|
if ((io_result & MEMTX_DECODE_ERROR) ||
|
|
(io_result & MEMTX_ACCESS_ERROR)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Failed to decode/access address in section: %d\n",
|
|
region->source_data_type);
|
|
|
|
/*
|
|
* Invalid source address is not an hardware error, instead
|
|
* wrong parameter from the kernel.
|
|
* Return true to let caller know to continue reading other
|
|
* sections
|
|
*/
|
|
region->error_flags = FADUMP_ERROR_INVALID_SOURCE_ADDR;
|
|
region->bytes_dumped = 0;
|
|
return true;
|
|
} else if (io_result != MEMTX_OK) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Failed to read source region in section: %d\n",
|
|
region->source_data_type);
|
|
|
|
return false;
|
|
}
|
|
|
|
io_result = address_space_write(default_as, dest_addr, attrs,
|
|
copy_buffer, copy_len);
|
|
if ((io_result & MEMTX_DECODE_ERROR) ||
|
|
(io_result & MEMTX_ACCESS_ERROR)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Failed to decode/access address in section: %d\n",
|
|
region->source_data_type);
|
|
|
|
/*
|
|
* Invalid destination address is not an hardware error,
|
|
* instead wrong parameter from the kernel.
|
|
* Return true to let caller know to continue reading other
|
|
* sections
|
|
*/
|
|
region->error_flags = FADUMP_ERROR_INVALID_DEST_ADDR;
|
|
region->bytes_dumped = 0;
|
|
return true;
|
|
} else if (io_result != MEMTX_OK) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Failed to write destination in section: %d\n",
|
|
region->source_data_type);
|
|
|
|
return false;
|
|
}
|
|
|
|
src_addr += FADUMP_CHUNK_SIZE;
|
|
dest_addr += FADUMP_CHUNK_SIZE;
|
|
}
|
|
#undef FADUMP_CHUNK_SIZE
|
|
|
|
/*
|
|
* Considering address_space_write would have copied the
|
|
* complete region
|
|
*/
|
|
region->bytes_dumped = cpu_to_be64(src_len);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Populate the passed CPUs register entries, in the buffer starting at
|
|
* the argument 'curr_reg_entry'
|
|
*
|
|
* The register entries is an array of pair of register id and register
|
|
* value, as described in Table 591/592 in section "H.1 Register Save Area"
|
|
* in PAPR v2.13
|
|
*
|
|
* Returns pointer just past this CPU's register entries, which can be used
|
|
* as the start address for next CPU's register entries
|
|
*/
|
|
static FadumpRegEntry *populate_cpu_reg_entries(CPUState *cpu,
|
|
FadumpRegEntry *curr_reg_entry)
|
|
{
|
|
CPUPPCState *env;
|
|
PowerPCCPU *ppc_cpu;
|
|
uint32_t num_regs_per_cpu = 0;
|
|
|
|
ppc_cpu = POWERPC_CPU(cpu);
|
|
env = cpu_env(cpu);
|
|
num_regs_per_cpu = 0;
|
|
|
|
/*
|
|
* CPUSTRT and CPUEND register entries follow this format:
|
|
*
|
|
* 8 Bytes Reg ID (BE) | 4 Bytes (0x0) | 4 Bytes Logical CPU ID (BE)
|
|
*/
|
|
curr_reg_entry->reg_id =
|
|
cpu_to_be64(fadump_str_to_u64("CPUSTRT"));
|
|
curr_reg_entry->reg_value = cpu_to_be64(
|
|
ppc_cpu->vcpu_id & FADUMP_CPU_ID_MASK);
|
|
++curr_reg_entry;
|
|
|
|
#define REG_ENTRY(id, val) \
|
|
do { \
|
|
curr_reg_entry->reg_id = \
|
|
cpu_to_be64(fadump_str_to_u64(#id)); \
|
|
curr_reg_entry->reg_value = cpu_to_be64(val); \
|
|
++curr_reg_entry; \
|
|
++num_regs_per_cpu; \
|
|
} while (0)
|
|
|
|
REG_ENTRY(ACOP, env->spr[SPR_ACOP]);
|
|
REG_ENTRY(AMR, env->spr[SPR_AMR]);
|
|
REG_ENTRY(BESCR, env->spr[SPR_BESCR]);
|
|
REG_ENTRY(CFAR, env->spr[SPR_CFAR]);
|
|
REG_ENTRY(CIABR, env->spr[SPR_CIABR]);
|
|
|
|
/* Save the condition register */
|
|
REG_ENTRY(CR, ppc_get_cr(env));
|
|
|
|
REG_ENTRY(CTR, env->spr[SPR_CTR]);
|
|
REG_ENTRY(CTRL, env->spr[SPR_CTRL]);
|
|
REG_ENTRY(DABR, env->spr[SPR_DABR]);
|
|
REG_ENTRY(DABRX, env->spr[SPR_DABRX]);
|
|
REG_ENTRY(DAR, env->spr[SPR_DAR]);
|
|
REG_ENTRY(DAWR0, env->spr[SPR_DAWR0]);
|
|
REG_ENTRY(DAWR1, env->spr[SPR_DAWR1]);
|
|
REG_ENTRY(DAWRX0, env->spr[SPR_DAWRX0]);
|
|
REG_ENTRY(DAWRX1, env->spr[SPR_DAWRX1]);
|
|
REG_ENTRY(DPDES, env->spr[SPR_DPDES]);
|
|
REG_ENTRY(DSCR, env->spr[SPR_DSCR]);
|
|
REG_ENTRY(DSISR, env->spr[SPR_DSISR]);
|
|
REG_ENTRY(EBBHR, env->spr[SPR_EBBHR]);
|
|
REG_ENTRY(EBBRR, env->spr[SPR_EBBRR]);
|
|
|
|
REG_ENTRY(FPSCR, env->fpscr);
|
|
REG_ENTRY(FSCR, env->spr[SPR_FSCR]);
|
|
|
|
/* Save the GPRs */
|
|
for (int gpr_id = 0; gpr_id < 32; ++gpr_id) {
|
|
curr_reg_entry->reg_id =
|
|
cpu_to_be64(fadump_gpr_id_to_u64(gpr_id));
|
|
curr_reg_entry->reg_value =
|
|
cpu_to_be64(env->gpr[gpr_id]);
|
|
++curr_reg_entry;
|
|
++num_regs_per_cpu;
|
|
}
|
|
|
|
REG_ENTRY(IAMR, env->spr[SPR_IAMR]);
|
|
REG_ENTRY(IC, env->spr[SPR_IC]);
|
|
REG_ENTRY(LR, env->spr[SPR_LR]);
|
|
|
|
REG_ENTRY(MSR, env->msr);
|
|
REG_ENTRY(NIA, env->nip); /* NIA */
|
|
REG_ENTRY(PIR, env->spr[SPR_PIR]);
|
|
REG_ENTRY(PSPB, env->spr[SPR_PSPB]);
|
|
REG_ENTRY(PVR, env->spr[SPR_PVR]);
|
|
REG_ENTRY(RPR, env->spr[SPR_RPR]);
|
|
REG_ENTRY(SPURR, env->spr[SPR_SPURR]);
|
|
REG_ENTRY(SRR0, env->spr[SPR_SRR0]);
|
|
REG_ENTRY(SRR1, env->spr[SPR_SRR1]);
|
|
REG_ENTRY(TAR, env->spr[SPR_TAR]);
|
|
REG_ENTRY(TEXASR, env->spr[SPR_TEXASR]);
|
|
REG_ENTRY(TFHAR, env->spr[SPR_TFHAR]);
|
|
REG_ENTRY(TFIAR, env->spr[SPR_TFIAR]);
|
|
REG_ENTRY(TIR, env->spr[SPR_TIR]);
|
|
REG_ENTRY(UAMOR, env->spr[SPR_UAMOR]);
|
|
REG_ENTRY(VRSAVE, env->spr[SPR_VRSAVE]);
|
|
REG_ENTRY(VSCR, env->vscr);
|
|
REG_ENTRY(VTB, env->spr[SPR_VTB]);
|
|
REG_ENTRY(WORT, env->spr[SPR_WORT]);
|
|
REG_ENTRY(XER, env->spr[SPR_XER]);
|
|
|
|
/*
|
|
* Ignoring transaction checkpoint and few other registers
|
|
* mentioned in PAPR as not supported in QEMU
|
|
*/
|
|
#undef REG_ENTRY
|
|
|
|
/* End the registers for this CPU with "CPUEND" reg entry */
|
|
curr_reg_entry->reg_id =
|
|
cpu_to_be64(fadump_str_to_u64("CPUEND"));
|
|
curr_reg_entry->reg_value = cpu_to_be64(
|
|
ppc_cpu->vcpu_id & FADUMP_CPU_ID_MASK);
|
|
|
|
/*
|
|
* Ensure number of register entries saved matches the expected
|
|
* 'FADUMP_PER_CPU_REG_ENTRIES' count
|
|
*
|
|
* This will help catch an error if in future a new register entry
|
|
* is added/removed while not modifying FADUMP_PER_CPU_REG_ENTRIES
|
|
*/
|
|
assert(FADUMP_PER_CPU_REG_ENTRIES == num_regs_per_cpu + 2 /*CPUSTRT+CPUEND*/);
|
|
|
|
++curr_reg_entry;
|
|
|
|
return curr_reg_entry;
|
|
}
|
|
|
|
/*
|
|
* Populate the "Register Save Area"/CPU State as mentioned in section "H.1
|
|
* Register Save Area" in PAPR v2.13
|
|
*
|
|
* It allocates the buffer for this region, then populates the register
|
|
* entries
|
|
*
|
|
* Returns the pointer to the buffer (which should be deallocated by the
|
|
* callers), and sets the size of this buffer in the argument
|
|
* 'cpu_state_len'
|
|
*/
|
|
static void *get_cpu_state_data(uint64_t *cpu_state_len)
|
|
{
|
|
FadumpRegSaveAreaHeader reg_save_hdr;
|
|
g_autofree FadumpRegEntry *reg_entries = NULL;
|
|
FadumpRegEntry *curr_reg_entry;
|
|
CPUState *cpu;
|
|
|
|
uint32_t num_reg_entries;
|
|
uint32_t reg_entries_size;
|
|
uint32_t num_cpus = 0;
|
|
|
|
void *cpu_state_buffer = NULL;
|
|
uint64_t offset = 0;
|
|
|
|
CPU_FOREACH(cpu) {
|
|
++num_cpus;
|
|
}
|
|
|
|
reg_save_hdr.version = cpu_to_be32(0);
|
|
reg_save_hdr.magic_number =
|
|
cpu_to_be64(fadump_str_to_u64("REGSAVE"));
|
|
|
|
/* Reg save area header is immediately followed by num cpus */
|
|
reg_save_hdr.num_cpu_offset =
|
|
cpu_to_be32(sizeof(FadumpRegSaveAreaHeader));
|
|
|
|
num_reg_entries = num_cpus * FADUMP_PER_CPU_REG_ENTRIES;
|
|
reg_entries_size = num_reg_entries * sizeof(FadumpRegEntry);
|
|
|
|
reg_entries = g_new(FadumpRegEntry, num_reg_entries);
|
|
|
|
/* Pointer to current CPU's registers */
|
|
curr_reg_entry = reg_entries;
|
|
|
|
/* Populate register entries for all CPUs */
|
|
CPU_FOREACH(cpu) {
|
|
cpu_synchronize_state(cpu);
|
|
curr_reg_entry = populate_cpu_reg_entries(cpu, curr_reg_entry);
|
|
}
|
|
|
|
*cpu_state_len = 0;
|
|
*cpu_state_len += sizeof(reg_save_hdr); /* reg save header */
|
|
*cpu_state_len += 0xc; /* padding as in PAPR */
|
|
*cpu_state_len += sizeof(num_cpus); /* num_cpus */
|
|
*cpu_state_len += reg_entries_size; /* reg entries */
|
|
|
|
cpu_state_buffer = g_malloc(*cpu_state_len);
|
|
|
|
memcpy(cpu_state_buffer + offset,
|
|
®_save_hdr, sizeof(reg_save_hdr));
|
|
offset += sizeof(reg_save_hdr);
|
|
|
|
/* Write num_cpus */
|
|
num_cpus = cpu_to_be32(num_cpus);
|
|
memcpy(cpu_state_buffer + offset, &num_cpus, sizeof(num_cpus));
|
|
offset += sizeof(num_cpus);
|
|
|
|
/* Write the register entries */
|
|
memcpy(cpu_state_buffer + offset, reg_entries, reg_entries_size);
|
|
offset += reg_entries_size;
|
|
|
|
return cpu_state_buffer;
|
|
}
|
|
|
|
/*
|
|
* Save the CPU State Data (aka "Register Save Area") in given region
|
|
*
|
|
* Region argument is expected to be of CPU_STATE_DATA type
|
|
*
|
|
* Returns false only in case of Hardware Error, such as failure to
|
|
* read/write a valid address.
|
|
*
|
|
* Otherwise, even in case of unsuccessful copy of CPU state data for reasons
|
|
* such as invalid destination address or non-fatal error errors likely
|
|
* caused due to invalid parameters, return true and set region->error_flags
|
|
*/
|
|
static bool do_populate_cpu_state(FadumpSection *region)
|
|
{
|
|
uint64_t dest_addr = be64_to_cpu(region->destination_address);
|
|
uint64_t cpu_state_len = 0;
|
|
g_autofree void *cpu_state_buffer = NULL;
|
|
AddressSpace *default_as = &address_space_memory;
|
|
MemTxResult io_result;
|
|
MemTxAttrs attrs;
|
|
|
|
assert(region->source_data_type == cpu_to_be16(FADUMP_CPU_STATE_DATA));
|
|
|
|
/* Mark the memory transaction as privileged memory access */
|
|
attrs.user = 0;
|
|
attrs.memory = 1;
|
|
|
|
cpu_state_buffer = get_cpu_state_data(&cpu_state_len);
|
|
|
|
io_result = address_space_write(default_as, dest_addr, attrs,
|
|
cpu_state_buffer, cpu_state_len);
|
|
if ((io_result & MEMTX_DECODE_ERROR) ||
|
|
(io_result & MEMTX_ACCESS_ERROR)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Failed to decode/access address in CPU State Region's"
|
|
" destination address: 0x%016" PRIx64 "\n", dest_addr);
|
|
|
|
/*
|
|
* Invalid source address is not an hardware error, instead
|
|
* wrong parameter from the kernel.
|
|
* Return true to let caller know to continue reading other
|
|
* sections
|
|
*/
|
|
region->error_flags = FADUMP_ERROR_INVALID_SOURCE_ADDR;
|
|
region->bytes_dumped = 0;
|
|
return true;
|
|
} else if (io_result != MEMTX_OK) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Failed to write CPU state region.\n");
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Set bytes_dumped in cpu state region, so kernel knows platform have
|
|
* exported it
|
|
*/
|
|
region->bytes_dumped = cpu_to_be64(cpu_state_len);
|
|
|
|
if (region->source_len != region->bytes_dumped) {
|
|
/*
|
|
* Log the error, but don't fail the dump collection here, let
|
|
* kernel handle the mismatch
|
|
*/
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Mismatch in CPU State region's length exported:"
|
|
" Kernel expected: 0x%" PRIx64 " bytes,"
|
|
" QEMU exported: 0x%" PRIx64 " bytes\n",
|
|
be64_to_cpu(region->source_len),
|
|
be64_to_cpu(region->bytes_dumped));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Preserve the memory locations registered for fadump
|
|
*
|
|
* Returns false only in case of RTAS_OUT_HW_ERROR, otherwise true
|
|
*/
|
|
static bool fadump_preserve_mem(SpaprMachineState *spapr)
|
|
{
|
|
FadumpMemStruct *fdm = &spapr->registered_fdm;
|
|
uint16_t dump_num_sections, data_type;
|
|
|
|
assert(spapr->fadump_registered);
|
|
|
|
/*
|
|
* Handle all sections
|
|
*
|
|
* CPU State Data and HPTE regions are handled in their own cases
|
|
*
|
|
* RMR regions and any custom OS reserved regions such as parameter
|
|
* save area, are handled by simply copying the source region to
|
|
* destination address
|
|
*/
|
|
dump_num_sections = be16_to_cpu(fdm->header.dump_num_sections);
|
|
for (int i = 0; i < dump_num_sections; ++i) {
|
|
data_type = be16_to_cpu(fdm->rgn[i].source_data_type);
|
|
|
|
/* Reset error_flags & bytes_dumped for now */
|
|
fdm->rgn[i].error_flags = 0;
|
|
fdm->rgn[i].bytes_dumped = 0;
|
|
|
|
/* If kernel did not request for the memory region, then skip it */
|
|
if (be32_to_cpu(fdm->rgn[i].request_flag) != FADUMP_REQUEST_FLAG) {
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"FADump: Skipping copying region as not requested\n");
|
|
continue;
|
|
}
|
|
|
|
switch (data_type) {
|
|
case FADUMP_CPU_STATE_DATA:
|
|
if (!do_populate_cpu_state(&fdm->rgn[i])) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Failed to store CPU State Data");
|
|
fdm->header.dump_status_flag |=
|
|
cpu_to_be16(FADUMP_STATUS_DUMP_ERROR);
|
|
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
case FADUMP_HPTE_REGION:
|
|
/* TODO: Add hpte state data */
|
|
break;
|
|
case FADUMP_REAL_MODE_REGION:
|
|
case FADUMP_PARAM_AREA:
|
|
/* Copy the memory region from region's source to its destination */
|
|
if (!do_preserve_region(&fdm->rgn[i])) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Failed to preserve dump section: %d\n",
|
|
be16_to_cpu(fdm->rgn[i].source_data_type));
|
|
fdm->header.dump_status_flag |=
|
|
cpu_to_be16(FADUMP_STATUS_DUMP_ERROR);
|
|
}
|
|
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"FADump: Skipping unknown source data type: %d\n", data_type);
|
|
|
|
fdm->rgn[i].error_flags =
|
|
cpu_to_be16(FADUMP_ERROR_INVALID_DATA_TYPE);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Trigger a fadump boot, ie. next boot will be a crashkernel/fadump boot
|
|
* with fadump dump active.
|
|
*
|
|
* This is triggered by ibm,os-term RTAS call, if fadump was registered.
|
|
*
|
|
* It preserves the memory and sets 'FADUMP_STATUS_DUMP_TRIGGERED' as
|
|
* fadump status, which can be used later to add the "ibm,kernel-dump"
|
|
* device tree node as presence of 'FADUMP_STATUS_DUMP_TRIGGERED' signifies
|
|
* next boot as fadump boot in our case
|
|
*/
|
|
void trigger_fadump_boot(SpaprMachineState *spapr, target_ulong spapr_retcode)
|
|
{
|
|
FadumpSectionHeader *header = &spapr->registered_fdm.header;
|
|
|
|
pause_all_vcpus();
|
|
|
|
/* Preserve the memory locations registered for fadump */
|
|
if (!fadump_preserve_mem(spapr)) {
|
|
/* Failed to preserve the registered memory regions */
|
|
rtas_st(spapr_retcode, 0, RTAS_OUT_HW_ERROR);
|
|
|
|
/* Cause a reboot */
|
|
qemu_system_guest_panicked(NULL);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Mark next boot as fadump boot
|
|
*
|
|
* Note: These is some bit of assumption involved here, as PAPR doesn't
|
|
* specify any use of the dump status flags, nor does the kernel use it
|
|
*
|
|
* But from description in Table 136 in PAPR v2.13, it looks like:
|
|
* FADUMP_STATUS_DUMP_TRIGGERED
|
|
* = Dump was triggered by the previous system boot (PAPR says)
|
|
* = Next boot will be a fadump boot (Assumed)
|
|
*
|
|
* FADUMP_STATUS_DUMP_PERFORMED
|
|
* = Dump performed (Set to 0 by caller of the
|
|
* ibm,configure-kernel-dump call) (PAPR says)
|
|
* = Firmware has performed the copying/dump of requested regions
|
|
* (Assumed)
|
|
* = Dump is active for the next boot (Assumed)
|
|
*/
|
|
header->dump_status_flag = cpu_to_be16(
|
|
FADUMP_STATUS_DUMP_TRIGGERED | /* Next boot will be fadump boot */
|
|
FADUMP_STATUS_DUMP_PERFORMED /* Dump is active */
|
|
);
|
|
|
|
/* Reset fadump_registered for next boot */
|
|
spapr->fadump_registered = false;
|
|
spapr->fadump_dump_active = true;
|
|
|
|
/*
|
|
* Then do a guest reset
|
|
*
|
|
* Requirement:
|
|
* GUEST_RESET is expected to NOT clear the memory, as is the case when
|
|
* this is merged
|
|
*/
|
|
qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
|
|
|
|
rtas_st(spapr_retcode, 0, RTAS_OUT_SUCCESS);
|
|
}
|