powerpc/pseries/fadump: add support for multiple boot memory regions

Currently, fadump on pseries assumes a single boot memory region even
though f/w supports more than one boot memory region. Add support for
more boot memory regions to make the implementation flexible for any
enhancements that introduce other region types. For this, rtas memory
structure for fadump is updated to have multiple boot memory regions
instead of just one. Additionally, methods responsible for creating
the fadump memory structure during both the first and second kernel
boot have been modified to take these multiple boot memory regions
into account. Also, a new callback has been added to the fadump_ops
structure to get the maximum boot memory regions supported by the
platform.

Signed-off-by: Sourabh Jain <sourabhjain@linux.ibm.com>
Signed-off-by: Hari Bathini <hbathini@linux.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://msgid.link/20240509115755.519982-2-hbathini@linux.ibm.com
This commit is contained in:
Hari Bathini 2024-05-09 17:27:53 +05:30 committed by Michael Ellerman
parent 98ec6d38ee
commit 78d5cc15fb
5 changed files with 195 additions and 118 deletions

View File

@ -156,6 +156,7 @@ struct fadump_ops {
struct seq_file *m);
void (*fadump_trigger)(struct fadump_crash_info_header *fdh,
const char *msg);
int (*fadump_max_boot_mem_rgns)(void);
};
/* Helper functions */
@ -163,7 +164,6 @@ s32 __init fadump_setup_cpu_notes_buf(u32 num_cpus);
void fadump_free_cpu_notes_buf(void);
u32 *__init fadump_regs_to_elf_notes(u32 *buf, struct pt_regs *regs);
void __init fadump_update_elfcore_header(char *bufp);
bool is_fadump_boot_mem_contiguous(void);
bool is_fadump_reserved_mem_contiguous(void);
#else /* !CONFIG_PRESERVE_FA_DUMP */

View File

@ -220,28 +220,6 @@ static bool is_fadump_mem_area_contiguous(u64 d_start, u64 d_end)
return ret;
}
/*
* Returns true, if there are no holes in boot memory area,
* false otherwise.
*/
bool is_fadump_boot_mem_contiguous(void)
{
unsigned long d_start, d_end;
bool ret = false;
int i;
for (i = 0; i < fw_dump.boot_mem_regs_cnt; i++) {
d_start = fw_dump.boot_mem_addr[i];
d_end = d_start + fw_dump.boot_mem_sz[i];
ret = is_fadump_mem_area_contiguous(d_start, d_end);
if (!ret)
break;
}
return ret;
}
/*
* Returns true, if there are no holes in reserved memory area,
* false otherwise.
@ -381,10 +359,11 @@ static unsigned long __init get_fadump_area_size(void)
static int __init add_boot_mem_region(unsigned long rstart,
unsigned long rsize)
{
int max_boot_mem_rgns = fw_dump.ops->fadump_max_boot_mem_rgns();
int i = fw_dump.boot_mem_regs_cnt++;
if (fw_dump.boot_mem_regs_cnt > FADUMP_MAX_MEM_REGS) {
fw_dump.boot_mem_regs_cnt = FADUMP_MAX_MEM_REGS;
if (fw_dump.boot_mem_regs_cnt > max_boot_mem_rgns) {
fw_dump.boot_mem_regs_cnt = max_boot_mem_rgns;
return 0;
}

View File

@ -599,6 +599,12 @@ static void opal_fadump_trigger(struct fadump_crash_info_header *fdh,
pr_emerg("No backend support for MPIPL!\n");
}
/* FADUMP_MAX_MEM_REGS or lower */
static int opal_fadump_max_boot_mem_rgns(void)
{
return FADUMP_MAX_MEM_REGS;
}
static struct fadump_ops opal_fadump_ops = {
.fadump_init_mem_struct = opal_fadump_init_mem_struct,
.fadump_get_metadata_size = opal_fadump_get_metadata_size,
@ -611,6 +617,7 @@ static struct fadump_ops opal_fadump_ops = {
.fadump_process = opal_fadump_process,
.fadump_region_show = opal_fadump_region_show,
.fadump_trigger = opal_fadump_trigger,
.fadump_max_boot_mem_rgns = opal_fadump_max_boot_mem_rgns,
};
void __init opal_fadump_dt_scan(struct fw_dump *fadump_conf, u64 node)

View File

@ -29,9 +29,6 @@ static const struct rtas_fadump_mem_struct *fdm_active;
static void rtas_fadump_update_config(struct fw_dump *fadump_conf,
const struct rtas_fadump_mem_struct *fdm)
{
fadump_conf->boot_mem_dest_addr =
be64_to_cpu(fdm->rmr_region.destination_address);
fadump_conf->fadumphdr_addr = (fadump_conf->boot_mem_dest_addr +
fadump_conf->boot_memory_size);
}
@ -43,20 +40,53 @@ static void rtas_fadump_update_config(struct fw_dump *fadump_conf,
static void __init rtas_fadump_get_config(struct fw_dump *fadump_conf,
const struct rtas_fadump_mem_struct *fdm)
{
fadump_conf->boot_mem_addr[0] =
be64_to_cpu(fdm->rmr_region.source_address);
fadump_conf->boot_mem_sz[0] = be64_to_cpu(fdm->rmr_region.source_len);
fadump_conf->boot_memory_size = fadump_conf->boot_mem_sz[0];
unsigned long base, size, last_end, hole_size;
fadump_conf->boot_mem_top = fadump_conf->boot_memory_size;
fadump_conf->boot_mem_regs_cnt = 1;
last_end = 0;
hole_size = 0;
fadump_conf->boot_memory_size = 0;
fadump_conf->boot_mem_regs_cnt = 0;
pr_debug("Boot memory regions:\n");
for (int i = 0; i < be16_to_cpu(fdm->header.dump_num_sections); i++) {
int type = be16_to_cpu(fdm->rgn[i].source_data_type);
u64 addr;
/*
* Start address of reserve dump area (permanent reservation) for
* re-registering FADump after dump capture.
*/
fadump_conf->reserve_dump_area_start =
be64_to_cpu(fdm->cpu_state_data.destination_address);
switch (type) {
case RTAS_FADUMP_CPU_STATE_DATA:
addr = be64_to_cpu(fdm->rgn[i].destination_address);
fadump_conf->cpu_state_dest_vaddr = (u64)__va(addr);
/*
* Start address of reserve dump area (permanent reservation) for
* re-registering FADump after dump capture.
*/
fadump_conf->reserve_dump_area_start = addr;
break;
case RTAS_FADUMP_HPTE_REGION:
/* Not processed currently. */
break;
case RTAS_FADUMP_REAL_MODE_REGION:
base = be64_to_cpu(fdm->rgn[i].source_address);
size = be64_to_cpu(fdm->rgn[i].source_len);
pr_debug("\t[%03d] base: 0x%lx, size: 0x%lx\n", i, base, size);
if (!base) {
fadump_conf->boot_mem_dest_addr =
be64_to_cpu(fdm->rgn[i].destination_address);
}
fadump_conf->boot_mem_addr[fadump_conf->boot_mem_regs_cnt] = base;
fadump_conf->boot_mem_sz[fadump_conf->boot_mem_regs_cnt] = size;
fadump_conf->boot_memory_size += size;
hole_size += (base - last_end);
last_end = base + size;
fadump_conf->boot_mem_regs_cnt++;
break;
default:
pr_warn("Section type %d unsupported on this kernel. Ignoring!\n", type);
break;
}
}
fadump_conf->boot_mem_top = fadump_conf->boot_memory_size + hole_size;
rtas_fadump_update_config(fadump_conf, fdm);
}
@ -64,16 +94,15 @@ static void __init rtas_fadump_get_config(struct fw_dump *fadump_conf,
static u64 rtas_fadump_init_mem_struct(struct fw_dump *fadump_conf)
{
u64 addr = fadump_conf->reserve_dump_area_start;
u16 sec_cnt = 0;
memset(&fdm, 0, sizeof(struct rtas_fadump_mem_struct));
addr = addr & PAGE_MASK;
fdm.header.dump_format_version = cpu_to_be32(0x00000001);
fdm.header.dump_num_sections = cpu_to_be16(3);
fdm.header.dump_status_flag = 0;
fdm.header.offset_first_dump_section =
cpu_to_be32((u32)offsetof(struct rtas_fadump_mem_struct,
cpu_state_data));
cpu_to_be32((u32)offsetof(struct rtas_fadump_mem_struct, rgn));
/*
* Fields for disk dump option.
@ -89,25 +118,22 @@ static u64 rtas_fadump_init_mem_struct(struct fw_dump *fadump_conf)
/* Kernel dump sections */
/* cpu state data section. */
fdm.cpu_state_data.request_flag =
cpu_to_be32(RTAS_FADUMP_REQUEST_FLAG);
fdm.cpu_state_data.source_data_type =
cpu_to_be16(RTAS_FADUMP_CPU_STATE_DATA);
fdm.cpu_state_data.source_address = 0;
fdm.cpu_state_data.source_len =
cpu_to_be64(fadump_conf->cpu_state_data_size);
fdm.cpu_state_data.destination_address = cpu_to_be64(addr);
fdm.rgn[sec_cnt].request_flag = cpu_to_be32(RTAS_FADUMP_REQUEST_FLAG);
fdm.rgn[sec_cnt].source_data_type = cpu_to_be16(RTAS_FADUMP_CPU_STATE_DATA);
fdm.rgn[sec_cnt].source_address = 0;
fdm.rgn[sec_cnt].source_len = cpu_to_be64(fadump_conf->cpu_state_data_size);
fdm.rgn[sec_cnt].destination_address = cpu_to_be64(addr);
addr += fadump_conf->cpu_state_data_size;
sec_cnt++;
/* hpte region section */
fdm.hpte_region.request_flag = cpu_to_be32(RTAS_FADUMP_REQUEST_FLAG);
fdm.hpte_region.source_data_type =
cpu_to_be16(RTAS_FADUMP_HPTE_REGION);
fdm.hpte_region.source_address = 0;
fdm.hpte_region.source_len =
cpu_to_be64(fadump_conf->hpte_region_size);
fdm.hpte_region.destination_address = cpu_to_be64(addr);
fdm.rgn[sec_cnt].request_flag = cpu_to_be32(RTAS_FADUMP_REQUEST_FLAG);
fdm.rgn[sec_cnt].source_data_type = cpu_to_be16(RTAS_FADUMP_HPTE_REGION);
fdm.rgn[sec_cnt].source_address = 0;
fdm.rgn[sec_cnt].source_len = cpu_to_be64(fadump_conf->hpte_region_size);
fdm.rgn[sec_cnt].destination_address = cpu_to_be64(addr);
addr += fadump_conf->hpte_region_size;
sec_cnt++;
/*
* Align boot memory area destination address to page boundary to
@ -115,15 +141,20 @@ static u64 rtas_fadump_init_mem_struct(struct fw_dump *fadump_conf)
*/
addr = PAGE_ALIGN(addr);
/* RMA region section */
fdm.rmr_region.request_flag = cpu_to_be32(RTAS_FADUMP_REQUEST_FLAG);
fdm.rmr_region.source_data_type =
cpu_to_be16(RTAS_FADUMP_REAL_MODE_REGION);
fdm.rmr_region.source_address = cpu_to_be64(0);
fdm.rmr_region.source_len = cpu_to_be64(fadump_conf->boot_memory_size);
fdm.rmr_region.destination_address = cpu_to_be64(addr);
addr += fadump_conf->boot_memory_size;
/* First boot memory region destination address */
fadump_conf->boot_mem_dest_addr = addr;
for (int i = 0; i < fadump_conf->boot_mem_regs_cnt; i++) {
/* Boot memory regions */
fdm.rgn[sec_cnt].request_flag = cpu_to_be32(RTAS_FADUMP_REQUEST_FLAG);
fdm.rgn[sec_cnt].source_data_type = cpu_to_be16(RTAS_FADUMP_REAL_MODE_REGION);
fdm.rgn[sec_cnt].source_address = cpu_to_be64(fadump_conf->boot_mem_addr[i]);
fdm.rgn[sec_cnt].source_len = cpu_to_be64(fadump_conf->boot_mem_sz[i]);
fdm.rgn[sec_cnt].destination_address = cpu_to_be64(addr);
addr += fadump_conf->boot_mem_sz[i];
sec_cnt++;
}
fdm.header.dump_num_sections = cpu_to_be16(sec_cnt);
rtas_fadump_update_config(fadump_conf, &fdm);
return addr;
@ -136,14 +167,21 @@ static u64 rtas_fadump_get_bootmem_min(void)
static int rtas_fadump_register(struct fw_dump *fadump_conf)
{
unsigned int wait_time;
unsigned int wait_time, fdm_size;
int rc, err = -EIO;
/*
* Platform requires the exact size of the Dump Memory Structure.
* Avoid including any unused rgns in the calculation, as this
* could result in a parameter error (-3) from the platform.
*/
fdm_size = sizeof(struct rtas_fadump_section_header);
fdm_size += be16_to_cpu(fdm.header.dump_num_sections) * sizeof(struct rtas_fadump_section);
/* TODO: Add upper time limit for the delay */
do {
rc = rtas_call(fadump_conf->ibm_configure_kernel_dump, 3, 1,
NULL, FADUMP_REGISTER, &fdm,
sizeof(struct rtas_fadump_mem_struct));
NULL, FADUMP_REGISTER, &fdm, fdm_size);
wait_time = rtas_busy_delay_time(rc);
if (wait_time)
@ -161,9 +199,7 @@ static int rtas_fadump_register(struct fw_dump *fadump_conf)
pr_err("Failed to register. Hardware Error(%d).\n", rc);
break;
case -3:
if (!is_fadump_boot_mem_contiguous())
pr_err("Can't have holes in boot memory area.\n");
else if (!is_fadump_reserved_mem_contiguous())
if (!is_fadump_reserved_mem_contiguous())
pr_err("Can't have holes in reserved memory area.\n");
pr_err("Failed to register. Parameter Error(%d).\n", rc);
@ -316,11 +352,9 @@ static int __init rtas_fadump_build_cpu_notes(struct fw_dump *fadump_conf)
u32 num_cpus, *note_buf;
int i, rc = 0, cpu = 0;
struct pt_regs regs;
unsigned long addr;
void *vaddr;
addr = be64_to_cpu(fdm_active->cpu_state_data.destination_address);
vaddr = __va(addr);
vaddr = (void *)fadump_conf->cpu_state_dest_vaddr;
reg_header = vaddr;
if (be64_to_cpu(reg_header->magic_number) !=
@ -395,18 +429,42 @@ static int __init rtas_fadump_process(struct fw_dump *fadump_conf)
return -EINVAL;
/* Check if the dump data is valid. */
if ((be16_to_cpu(fdm_active->header.dump_status_flag) ==
RTAS_FADUMP_ERROR_FLAG) ||
(fdm_active->cpu_state_data.error_flags != 0) ||
(fdm_active->rmr_region.error_flags != 0)) {
pr_err("Dump taken by platform is not valid\n");
return -EINVAL;
}
if ((fdm_active->rmr_region.bytes_dumped !=
fdm_active->rmr_region.source_len) ||
!fdm_active->cpu_state_data.bytes_dumped) {
pr_err("Dump taken by platform is incomplete\n");
return -EINVAL;
for (int i = 0; i < be16_to_cpu(fdm_active->header.dump_num_sections); i++) {
int type = be16_to_cpu(fdm_active->rgn[i].source_data_type);
int rc = 0;
switch (type) {
case RTAS_FADUMP_CPU_STATE_DATA:
case RTAS_FADUMP_HPTE_REGION:
case RTAS_FADUMP_REAL_MODE_REGION:
if (fdm_active->rgn[i].error_flags != 0) {
pr_err("Dump taken by platform is not valid (%d)\n", i);
rc = -EINVAL;
}
if (fdm_active->rgn[i].bytes_dumped != fdm_active->rgn[i].source_len) {
pr_err("Dump taken by platform is incomplete (%d)\n", i);
rc = -EINVAL;
}
if (rc) {
pr_warn("Region type: %u src addr: 0x%llx dest addr: 0x%llx\n",
be16_to_cpu(fdm_active->rgn[i].source_data_type),
be64_to_cpu(fdm_active->rgn[i].source_address),
be64_to_cpu(fdm_active->rgn[i].destination_address));
return rc;
}
break;
default:
/*
* If the first/crashed kernel added a new region type that the
* second/fadump kernel doesn't recognize, skip it and process
* assuming backward compatibility.
*/
pr_warn("Unknown region found: type: %u src addr: 0x%llx dest addr: 0x%llx\n",
be16_to_cpu(fdm_active->rgn[i].source_data_type),
be64_to_cpu(fdm_active->rgn[i].source_address),
be64_to_cpu(fdm_active->rgn[i].destination_address));
break;
}
}
return rtas_fadump_build_cpu_notes(fadump_conf);
@ -415,7 +473,6 @@ static int __init rtas_fadump_process(struct fw_dump *fadump_conf)
static void rtas_fadump_region_show(struct fw_dump *fadump_conf,
struct seq_file *m)
{
const struct rtas_fadump_section *cpu_data_section;
const struct rtas_fadump_mem_struct *fdm_ptr;
if (fdm_active)
@ -423,27 +480,42 @@ static void rtas_fadump_region_show(struct fw_dump *fadump_conf,
else
fdm_ptr = &fdm;
cpu_data_section = &(fdm_ptr->cpu_state_data);
seq_printf(m, "CPU :[%#016llx-%#016llx] %#llx bytes, Dumped: %#llx\n",
be64_to_cpu(cpu_data_section->destination_address),
be64_to_cpu(cpu_data_section->destination_address) +
be64_to_cpu(cpu_data_section->source_len) - 1,
be64_to_cpu(cpu_data_section->source_len),
be64_to_cpu(cpu_data_section->bytes_dumped));
seq_printf(m, "HPTE:[%#016llx-%#016llx] %#llx bytes, Dumped: %#llx\n",
be64_to_cpu(fdm_ptr->hpte_region.destination_address),
be64_to_cpu(fdm_ptr->hpte_region.destination_address) +
be64_to_cpu(fdm_ptr->hpte_region.source_len) - 1,
be64_to_cpu(fdm_ptr->hpte_region.source_len),
be64_to_cpu(fdm_ptr->hpte_region.bytes_dumped));
for (int i = 0; i < be16_to_cpu(fdm_ptr->header.dump_num_sections); i++) {
int type = be16_to_cpu(fdm_ptr->rgn[i].source_data_type);
seq_printf(m, "DUMP: Src: %#016llx, Dest: %#016llx, ",
be64_to_cpu(fdm_ptr->rmr_region.source_address),
be64_to_cpu(fdm_ptr->rmr_region.destination_address));
seq_printf(m, "Size: %#llx, Dumped: %#llx bytes\n",
be64_to_cpu(fdm_ptr->rmr_region.source_len),
be64_to_cpu(fdm_ptr->rmr_region.bytes_dumped));
switch (type) {
case RTAS_FADUMP_CPU_STATE_DATA:
seq_printf(m, "CPU :[%#016llx-%#016llx] %#llx bytes, Dumped: %#llx\n",
be64_to_cpu(fdm_ptr->rgn[i].destination_address),
be64_to_cpu(fdm_ptr->rgn[i].destination_address) +
be64_to_cpu(fdm_ptr->rgn[i].source_len) - 1,
be64_to_cpu(fdm_ptr->rgn[i].source_len),
be64_to_cpu(fdm_ptr->rgn[i].bytes_dumped));
break;
case RTAS_FADUMP_HPTE_REGION:
seq_printf(m, "HPTE:[%#016llx-%#016llx] %#llx bytes, Dumped: %#llx\n",
be64_to_cpu(fdm_ptr->rgn[i].destination_address),
be64_to_cpu(fdm_ptr->rgn[i].destination_address) +
be64_to_cpu(fdm_ptr->rgn[i].source_len) - 1,
be64_to_cpu(fdm_ptr->rgn[i].source_len),
be64_to_cpu(fdm_ptr->rgn[i].bytes_dumped));
break;
case RTAS_FADUMP_REAL_MODE_REGION:
seq_printf(m, "DUMP: Src: %#016llx, Dest: %#016llx, ",
be64_to_cpu(fdm_ptr->rgn[i].source_address),
be64_to_cpu(fdm_ptr->rgn[i].destination_address));
seq_printf(m, "Size: %#llx, Dumped: %#llx bytes\n",
be64_to_cpu(fdm_ptr->rgn[i].source_len),
be64_to_cpu(fdm_ptr->rgn[i].bytes_dumped));
break;
default:
seq_printf(m, "Unknown region type %d : Src: %#016llx, Dest: %#016llx, ",
type, be64_to_cpu(fdm_ptr->rgn[i].source_address),
be64_to_cpu(fdm_ptr->rgn[i].destination_address));
break;
}
}
/* Dump is active. Show preserved area start address. */
if (fdm_active) {
@ -459,6 +531,20 @@ static void rtas_fadump_trigger(struct fadump_crash_info_header *fdh,
rtas_os_term((char *)msg);
}
/* FADUMP_MAX_MEM_REGS or lower */
static int rtas_fadump_max_boot_mem_rgns(void)
{
/*
* Version 1 of Kernel Assisted Dump Memory Structure (PAPR) supports 10 sections.
* With one each section taken for CPU state data & HPTE respectively, 8 sections
* can be used for boot memory regions.
*
* If new region(s) is(are) defined, maximum boot memory regions will decrease
* proportionally.
*/
return RTAS_FADUMP_MAX_BOOT_MEM_REGS;
}
static struct fadump_ops rtas_fadump_ops = {
.fadump_init_mem_struct = rtas_fadump_init_mem_struct,
.fadump_get_bootmem_min = rtas_fadump_get_bootmem_min,
@ -468,6 +554,7 @@ static struct fadump_ops rtas_fadump_ops = {
.fadump_process = rtas_fadump_process,
.fadump_region_show = rtas_fadump_region_show,
.fadump_trigger = rtas_fadump_trigger,
.fadump_max_boot_mem_rgns = rtas_fadump_max_boot_mem_rgns,
};
void __init rtas_fadump_dt_scan(struct fw_dump *fadump_conf, u64 node)

View File

@ -29,6 +29,15 @@
/* Dump status flag */
#define RTAS_FADUMP_ERROR_FLAG 0x2000
/*
* The Firmware Assisted Dump Memory structure supports a maximum of 10 sections
* in the dump memory structure. Presently, first two sections are used for
* CPU and HPTE data, while the remaining eight sections can be used for
* boot memory regions.
*/
#define MAX_SECTIONS 10
#define RTAS_FADUMP_MAX_BOOT_MEM_REGS 8
/* Kernel Dump section info */
struct rtas_fadump_section {
__be32 request_flag;
@ -61,20 +70,15 @@ struct rtas_fadump_section_header {
* Firmware Assisted dump memory structure. This structure is required for
* registering future kernel dump with power firmware through rtas call.
*
* No disk dump option. Hence disk dump path string section is not included.
* In version 1, the platform permits one section header, dump-disk path
* and ten sections.
*
* Note: No disk dump option. Hence disk dump path string section is not
* included.
*/
struct rtas_fadump_mem_struct {
struct rtas_fadump_section_header header;
/* Kernel dump sections */
struct rtas_fadump_section cpu_state_data;
struct rtas_fadump_section hpte_region;
/*
* TODO: Extend multiple boot memory regions support in the kernel
* for this platform.
*/
struct rtas_fadump_section rmr_region;
struct rtas_fadump_section rgn[MAX_SECTIONS];
};
/*