mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
The NGDump DFI interface enables zgetdump to read and mount dumps created with NGDump stand-alone dump. Under the hood, the NGDump DFI delegates the task of reading and parsing of ELF dump files to the ELF DFI interface dfi_elf. Usage example 1: ---------------- $ zgetdump -i /dev/nvme0n1p1 General dump info: Dump format........: elf Version............: 1 UTS node name......: t83lp49.lnxne.boe UTS kernel release.: 5.14.0-20210819.rc6.git0.efb8a921eec7.300.fc34.s390x UTS kernel version.: #1 SMP Thu Aug 19 00:22:04 CEST 2021 System arch........: s390x (64 bit) CPU count (online).: 32 Dump memory range..: 16384 MB Memory map: 0000000000000000 - 00000003ffffffff (16384 MB) Usage example 2: ---------------- $ zgetdump /dev/nvme0n1p1 > dump.elf Usage example 3: ---------------- $ zgetdump -m /dev/nvme0n1p1 /mnt $ ls -l /mnt/dump.elf $ zgetdump -u /mnt Signed-off-by: Alexander Egorenkov <egorenar@linux.ibm.com> Reviewed-by: Alexander Gordeev <agordeev@linux.ibm.com> Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com> Tested-by: Mikhail Zaslonko <zaslonko@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
802 lines
17 KiB
C
802 lines
17 KiB
C
/*
|
|
* zgetdump - Tool for copying and converting System z dumps
|
|
*
|
|
* Generic input dump format functions (DFI - Dump Format Input)
|
|
*
|
|
* Copyright IBM Corp. 2001, 2018
|
|
*
|
|
* s390-tools is free software; you can redistribute it and/or modify
|
|
* it under the terms of the MIT license. See LICENSE for details.
|
|
*/
|
|
|
|
#include "lib/util_log.h"
|
|
|
|
#include "zgetdump.h"
|
|
#include "dfi_mem_chunk.h"
|
|
#include "dfi_vmcoreinfo.h"
|
|
#include "dfi.h"
|
|
|
|
#define TIME_FMT_STR "%a, %d %b %Y %H:%M:%S %z"
|
|
#define PROGRESS_HASH_CNT 50
|
|
|
|
#define KDUMP_OLDMEM_BASE 0x10418
|
|
#define KDUMP_OLDMEM_SIZE 0x10420
|
|
|
|
/*
|
|
* DFI vector - ensure that tape is the first in the list and devmem the second!
|
|
*/
|
|
static struct dfi *dfi_vec[] = {
|
|
&dfi_s390tape,
|
|
&dfi_devmem,
|
|
&dfi_s390mv_ext,
|
|
&dfi_s390mv,
|
|
&dfi_s390_ext,
|
|
&dfi_s390,
|
|
&dfi_lkcd,
|
|
&dfi_elf,
|
|
&dfi_kdump,
|
|
&dfi_kdump_flat,
|
|
&dfi_ngdump,
|
|
NULL,
|
|
};
|
|
|
|
/*
|
|
* Live dump magic
|
|
*/
|
|
u64 dfi_live_dump_magic = 0x4c49564544554d50ULL; /* LIVEDUMP */
|
|
|
|
/*
|
|
* CPU information
|
|
*/
|
|
struct cpus {
|
|
struct util_list list;
|
|
enum dfi_cpu_content content;
|
|
int fac;
|
|
unsigned int cnt;
|
|
};
|
|
|
|
/*
|
|
* Dump header attribute information
|
|
*/
|
|
struct attr {
|
|
unsigned int *dfi_version;
|
|
struct timeval *time;
|
|
struct timeval *time_end;
|
|
u64 *cpu_id;
|
|
u64 *mem_size_real;
|
|
enum dfi_arch *build_arch;
|
|
unsigned int *vol_nr;
|
|
u32 *real_cpu_cnt;
|
|
struct new_utsname *utsname;
|
|
char *dump_method;
|
|
u64 *file_size;
|
|
};
|
|
|
|
/*
|
|
* File local static data
|
|
*/
|
|
static struct {
|
|
enum dfi_arch arch;
|
|
struct attr attr;
|
|
struct cpus cpus;
|
|
struct dfi *dfi;
|
|
unsigned long kdump_base;
|
|
unsigned long kdump_size;
|
|
} l;
|
|
|
|
/*
|
|
* Print Dump date
|
|
*/
|
|
static void date_print(void)
|
|
{
|
|
char time_str[80];
|
|
struct tm *tmp;
|
|
|
|
if (l.attr.time) {
|
|
tmp = localtime(&l.attr.time->tv_sec);
|
|
strftime(time_str, sizeof(time_str), TIME_FMT_STR, tmp);
|
|
STDERR(" Dump created.......: %s\n", time_str);
|
|
}
|
|
if (l.attr.time_end) {
|
|
tmp = localtime(&l.attr.time_end->tv_sec);
|
|
strftime(time_str, sizeof(time_str), TIME_FMT_STR, tmp);
|
|
STDERR(" Dump ended.........: %s\n", time_str);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print dump information (--info option)
|
|
*/
|
|
void dfi_info_print(void)
|
|
{
|
|
STDERR("General dump info:\n");
|
|
STDERR(" Dump format........: %s\n", l.dfi->name);
|
|
if (l.attr.dfi_version)
|
|
STDERR(" Version............: %u\n", *l.attr.dfi_version);
|
|
date_print();
|
|
if (l.attr.dump_method)
|
|
STDERR(" Dump method........: %s\n", l.attr.dump_method);
|
|
if (l.attr.cpu_id)
|
|
STDERR(" Dump CPU ID........: %llx\n", *l.attr.cpu_id);
|
|
if (l.attr.utsname) {
|
|
STDERR(" UTS node name......: %s\n", l.attr.utsname->nodename);
|
|
STDERR(" UTS kernel release.: %s\n", l.attr.utsname->release);
|
|
STDERR(" UTS kernel version.: %s\n", l.attr.utsname->version);
|
|
}
|
|
if (l.attr.vol_nr)
|
|
STDERR(" Volume number......: %u\n", *l.attr.vol_nr);
|
|
if (l.attr.build_arch)
|
|
STDERR(" Build arch.........: %s\n",
|
|
dfi_arch_str(*l.attr.build_arch));
|
|
STDERR(" System arch........: %s\n", dfi_arch_str(l.arch));
|
|
if (l.cpus.cnt)
|
|
STDERR(" CPU count (online).: %u\n", l.cpus.cnt);
|
|
if (l.attr.real_cpu_cnt)
|
|
STDERR(" CPU count (real)...: %u\n", *l.attr.real_cpu_cnt);
|
|
if (dfi_mem_range())
|
|
STDERR(" Dump memory range..: %lld MB\n",
|
|
TO_MIB(dfi_mem_range()));
|
|
if (l.attr.mem_size_real)
|
|
STDERR(" Real memory range..: %lld MB\n",
|
|
TO_MIB(*l.attr.mem_size_real));
|
|
if (l.attr.file_size)
|
|
STDERR(" Dump file size.....: %lld MB\n",
|
|
TO_MIB(*l.attr.file_size));
|
|
if (dfi_mem_range())
|
|
dfi_mem_map_print(g.opts.verbose);
|
|
if (l.dfi->info_dump) {
|
|
STDERR("\nDump device info:\n");
|
|
l.dfi->info_dump();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialize CPU info
|
|
*/
|
|
void dfi_cpu_info_init(enum dfi_cpu_content cpu_content)
|
|
{
|
|
l.cpus.content = cpu_content;
|
|
util_list_init(&l.cpus.list, struct dfi_cpu, list);
|
|
l.cpus.cnt = 0;
|
|
}
|
|
|
|
/*
|
|
* Allocate new DFI CPU
|
|
*/
|
|
struct dfi_cpu *dfi_cpu_alloc(void)
|
|
{
|
|
return zg_alloc(sizeof(struct dfi_cpu));
|
|
}
|
|
|
|
/*
|
|
* Add DFI CPU
|
|
*/
|
|
void dfi_cpu_add(struct dfi_cpu *cpu)
|
|
{
|
|
util_list_add_tail(&l.cpus.list, cpu);
|
|
l.cpus.cnt++;
|
|
}
|
|
|
|
/*
|
|
* Return CPU with number cpu_nr
|
|
*/
|
|
struct dfi_cpu *dfi_cpu(unsigned int cpu_nr)
|
|
{
|
|
struct dfi_cpu *cpu;
|
|
unsigned int i = 0;
|
|
|
|
dfi_cpu_iterate(cpu) {
|
|
if (i == cpu_nr)
|
|
return cpu;
|
|
i++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Return CPU count
|
|
*/
|
|
unsigned int dfi_cpu_cnt(void)
|
|
{
|
|
return l.cpus.cnt;
|
|
}
|
|
|
|
/*
|
|
* Return CPU content
|
|
*/
|
|
enum dfi_cpu_content dfi_cpu_content(void)
|
|
{
|
|
return l.cpus.content;
|
|
}
|
|
|
|
/*
|
|
* Add CPU facility
|
|
*/
|
|
void dfi_cpu_content_fac_add(int flags)
|
|
{
|
|
l.cpus.fac |= flags;
|
|
}
|
|
|
|
/*
|
|
* Check CPU facility
|
|
*/
|
|
int dfi_cpu_content_fac_check(int flags)
|
|
{
|
|
return l.cpus.fac & flags;
|
|
}
|
|
|
|
/*
|
|
* Set DFI architecture
|
|
*/
|
|
void dfi_arch_set(enum dfi_arch arch)
|
|
{
|
|
l.arch = arch;
|
|
}
|
|
|
|
/*
|
|
* Return DFI architecture
|
|
*/
|
|
enum dfi_arch dfi_arch(void)
|
|
{
|
|
return l.arch;
|
|
}
|
|
|
|
/*
|
|
* Return DFI CPU list
|
|
*/
|
|
struct util_list *dfi_cpu_list(void)
|
|
{
|
|
return &l.cpus.list;
|
|
}
|
|
|
|
/*
|
|
* Get input dump format name
|
|
*/
|
|
const char *dfi_name(void)
|
|
{
|
|
return l.dfi->name;
|
|
}
|
|
|
|
/*
|
|
* Can input dump format seek?
|
|
*/
|
|
int dfi_feat_seek(void)
|
|
{
|
|
return l.dfi->feat_bits & DFI_FEAT_SEEK;
|
|
};
|
|
|
|
/*
|
|
* Can input dump format be used for copying?
|
|
*/
|
|
int dfi_feat_copy(void)
|
|
{
|
|
return l.dfi->feat_bits & DFI_FEAT_COPY;
|
|
};
|
|
|
|
/*
|
|
* Return DFI arch string
|
|
*/
|
|
const char *dfi_arch_str(enum dfi_arch arch)
|
|
{
|
|
switch (arch) {
|
|
case DFI_ARCH_32:
|
|
return "s390 (32 bit)";
|
|
case DFI_ARCH_64:
|
|
return "s390x (64 bit)";
|
|
case DFI_ARCH_UNKNOWN:
|
|
return "unknown";
|
|
}
|
|
ABORT("dfi_arch_str: Invalid dfi arch: %d", arch);
|
|
return NULL; /* UNREACHABLE */
|
|
}
|
|
|
|
/*
|
|
* Initialize attributes
|
|
*/
|
|
static void attr_init(void)
|
|
{
|
|
memset(&l.attr, 0, sizeof(l.attr));
|
|
}
|
|
|
|
/*
|
|
* Attribute: Dump time
|
|
*/
|
|
void dfi_attr_time_set(struct timeval *time)
|
|
{
|
|
if (time->tv_sec == 0)
|
|
return;
|
|
l.attr.time = zg_alloc(sizeof(*l.attr.time));
|
|
*l.attr.time = *time;
|
|
}
|
|
|
|
struct timeval *dfi_attr_time(void)
|
|
{
|
|
return l.attr.time;
|
|
}
|
|
|
|
/*
|
|
* Attribute: Dump end time
|
|
*/
|
|
void dfi_attr_time_end_set(struct timeval *time_end)
|
|
{
|
|
if (time_end->tv_sec == 0)
|
|
return;
|
|
l.attr.time_end = zg_alloc(sizeof(*l.attr.time_end));
|
|
*l.attr.time_end = *time_end;
|
|
}
|
|
|
|
struct timeval *dfi_attr_time_end(void)
|
|
{
|
|
return l.attr.time_end;
|
|
}
|
|
|
|
/*
|
|
* Attribute: Volume number
|
|
*/
|
|
void dfi_attr_vol_nr_set(unsigned int vol_nr)
|
|
{
|
|
l.attr.vol_nr = zg_alloc(sizeof(*l.attr.vol_nr));
|
|
*l.attr.vol_nr = vol_nr;
|
|
}
|
|
|
|
/*
|
|
* Attribute: DFI version
|
|
*/
|
|
void dfi_attr_version_set(unsigned int dfi_version)
|
|
{
|
|
l.attr.dfi_version = zg_alloc(sizeof(*l.attr.dfi_version));
|
|
*l.attr.dfi_version = dfi_version;
|
|
}
|
|
|
|
/*
|
|
* Attribute: CPU ID
|
|
*/
|
|
void dfi_attr_cpu_id_set(u64 cpu_id)
|
|
{
|
|
l.attr.cpu_id = zg_alloc(sizeof(*l.attr.cpu_id));
|
|
*l.attr.cpu_id = cpu_id;
|
|
}
|
|
|
|
u64 *dfi_attr_cpu_id(void)
|
|
{
|
|
return l.attr.cpu_id;
|
|
}
|
|
|
|
/*
|
|
* Attribute: utsname
|
|
*/
|
|
void dfi_attr_utsname_set(struct new_utsname *utsname)
|
|
{
|
|
l.attr.utsname = zg_alloc(sizeof(*utsname));
|
|
memcpy(l.attr.utsname, utsname, sizeof(*utsname));
|
|
}
|
|
|
|
struct new_utsname *dfi_attr_utsname(void)
|
|
{
|
|
return l.attr.utsname;
|
|
}
|
|
|
|
/*
|
|
* Attribute: Dump method
|
|
*/
|
|
void dfi_attr_dump_method_set(char *dump_method)
|
|
{
|
|
l.attr.dump_method = zg_strdup(dump_method);
|
|
}
|
|
|
|
char *dfi_attr_dump_method(void)
|
|
{
|
|
return l.attr.dump_method;
|
|
}
|
|
|
|
/*
|
|
* Attribute: Real memory size
|
|
*/
|
|
void dfi_attr_mem_size_real_set(u64 mem_size_real)
|
|
{
|
|
l.attr.mem_size_real = zg_alloc(sizeof(*l.attr.mem_size_real));
|
|
*l.attr.mem_size_real = mem_size_real;
|
|
}
|
|
|
|
u64 *dfi_attr_mem_size_real(void)
|
|
{
|
|
return l.attr.mem_size_real;
|
|
}
|
|
|
|
/*
|
|
* Attribute: Dump file size
|
|
*/
|
|
void dfi_attr_file_size_set(u64 file_size)
|
|
{
|
|
l.attr.file_size = zg_alloc(sizeof(*l.attr.file_size));
|
|
*l.attr.file_size = file_size;
|
|
}
|
|
|
|
u64 *dfi_attr_file_size(void)
|
|
{
|
|
return l.attr.file_size;
|
|
}
|
|
|
|
|
|
/*
|
|
* Attribute: Build architecture
|
|
*/
|
|
void dfi_attr_build_arch_set(enum dfi_arch build_arch)
|
|
{
|
|
l.attr.build_arch = zg_alloc(sizeof(*l.attr.build_arch));
|
|
*l.attr.build_arch = build_arch;
|
|
}
|
|
|
|
enum dfi_arch *dfi_attr_build_arch(void)
|
|
{
|
|
return l.attr.build_arch;
|
|
}
|
|
|
|
/*
|
|
* Attribute: Real CPU count
|
|
*/
|
|
void dfi_attr_real_cpu_cnt_set(unsigned int real_cnt_cnt)
|
|
{
|
|
l.attr.real_cpu_cnt = zg_alloc(sizeof(*l.attr.real_cpu_cnt));
|
|
*l.attr.real_cpu_cnt = real_cnt_cnt;
|
|
}
|
|
|
|
unsigned int *dfi_attr_real_cpu_cnt(void)
|
|
{
|
|
return l.attr.real_cpu_cnt;
|
|
}
|
|
|
|
/*
|
|
* Convert 32 bit CPU register set to 64 bit
|
|
*/
|
|
static void cpu_32_to_64(struct dfi_cpu *cpu_64, struct dfi_cpu_32 *cpu_32)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
cpu_64->gprs[i] = cpu_32->gprs[i];
|
|
cpu_64->ctrs[i] = cpu_32->ctrs[i];
|
|
cpu_64->acrs[i] = cpu_32->acrs[i];
|
|
if (i < 4)
|
|
cpu_64->fprs[i] = cpu_32->fprs[i];
|
|
}
|
|
cpu_64->psw[0] = cpu_32->psw[0];
|
|
cpu_64->psw[1] = cpu_32->psw[1];
|
|
cpu_64->prefix = cpu_32->prefix;
|
|
cpu_64->timer = cpu_32->timer;
|
|
cpu_64->todcmp = cpu_32->todcmp;
|
|
}
|
|
|
|
/*
|
|
* Convert 64 bit CPU register set to 32 bit
|
|
*/
|
|
void dfi_cpu_64_to_32(struct dfi_cpu_32 *cpu_32, struct dfi_cpu *cpu_64)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
cpu_32->gprs[i] = (u32) cpu_64->gprs[i];
|
|
cpu_32->ctrs[i] = (u32) cpu_64->ctrs[i];
|
|
cpu_32->acrs[i] = (u32) cpu_64->acrs[i];
|
|
if (i < 4)
|
|
cpu_32->fprs[i] = (u32) cpu_64->fprs[i];
|
|
}
|
|
cpu_32->psw[0] = (u32) cpu_64->psw[0];
|
|
cpu_32->psw[1] = (u32) cpu_64->psw[1];
|
|
cpu_32->prefix = cpu_64->prefix;
|
|
cpu_32->timer = cpu_64->timer;
|
|
cpu_32->todcmp = cpu_64->todcmp;
|
|
}
|
|
|
|
/*
|
|
* Copy 64 bit lowcore to internal register set
|
|
*/
|
|
static void lc2cpu_64(struct dfi_cpu *cpu, struct dfi_lowcore_64 *lc)
|
|
{
|
|
char vx_sa[DFI_VX_SA_SIZE];
|
|
int i;
|
|
|
|
memcpy(&cpu->gprs, lc->gpregs_save_area, sizeof(cpu->gprs));
|
|
memcpy(&cpu->ctrs, lc->cregs_save_area, sizeof(cpu->ctrs));
|
|
memcpy(&cpu->acrs, lc->access_regs_save_area, sizeof(cpu->acrs));
|
|
memcpy(&cpu->fprs, lc->floating_pt_save_area, sizeof(cpu->fprs));
|
|
memcpy(&cpu->fpc, &lc->fpt_creg_save_area, sizeof(cpu->fpc));
|
|
memcpy(&cpu->psw, lc->st_status_fixed_logout, sizeof(cpu->psw));
|
|
memcpy(&cpu->prefix, &lc->prefixreg_save_area, sizeof(cpu->prefix));
|
|
memcpy(&cpu->timer, lc->timer_save_area, sizeof(cpu->timer));
|
|
memcpy(&cpu->todpreg, &lc->tod_progreg_save_area, sizeof(cpu->todpreg));
|
|
memcpy(&cpu->todcmp, lc->clock_comp_save_area, sizeof(cpu->todcmp));
|
|
/* Add VX registers if available */
|
|
if (!dfi_cpu_lc_has_vx_sa(lc))
|
|
return;
|
|
if (dfi_mem_virt_read(lc->vector_save_area_addr, &vx_sa, sizeof(vx_sa))) {
|
|
STDERR("zgetdump: Vector registers save area is beyond dump memory limit for CPU %d\n", cpu->cpu_id);
|
|
return;
|
|
}
|
|
memcpy(cpu->vxrs_high, &vx_sa[16 * 16], sizeof(cpu->vxrs_high));
|
|
for (i = 0; i < 16; i++)
|
|
memcpy(&cpu->vxrs_low[i], &vx_sa[16 * i + 8], sizeof(u64));
|
|
dfi_cpu_content_fac_add(DFI_CPU_CONTENT_FAC_VX);
|
|
}
|
|
|
|
/*
|
|
* Copy 32 bit lowcore to internal 32 bit cpu
|
|
*/
|
|
static void lc2cpu_32(struct dfi_cpu_32 *cpu, struct dfi_lowcore_32 *lc)
|
|
{
|
|
memcpy(&cpu->gprs, lc->gpregs_save_area, sizeof(cpu->gprs));
|
|
memcpy(&cpu->ctrs, lc->cregs_save_area, sizeof(cpu->ctrs));
|
|
memcpy(&cpu->acrs, lc->access_regs_save_area, sizeof(cpu->acrs));
|
|
memcpy(&cpu->fprs, lc->floating_pt_save_area, sizeof(cpu->fprs));
|
|
memcpy(&cpu->psw, lc->st_status_fixed_logout, sizeof(cpu->psw));
|
|
memcpy(&cpu->prefix, &lc->prefixreg_save_area, sizeof(cpu->prefix));
|
|
memcpy(&cpu->timer, lc->timer_save_area, sizeof(cpu->timer));
|
|
memcpy(&cpu->todcmp, lc->clock_comp_save_area, sizeof(cpu->todcmp));
|
|
}
|
|
|
|
/*
|
|
* Initialize and add a new CPU with given lowcore pointer
|
|
*
|
|
* Note: When this function is called, the memory chunks have to be already
|
|
* defined by the DFI dump specific code.
|
|
*/
|
|
int dfi_cpu_add_from_lc(u32 lc_addr)
|
|
{
|
|
struct dfi_cpu *cpu = dfi_cpu_alloc();
|
|
|
|
cpu->cpu_id = l.cpus.cnt;
|
|
switch (l.cpus.content) {
|
|
case DFI_CPU_CONTENT_LC:
|
|
cpu->prefix = lc_addr;
|
|
break;
|
|
case DFI_CPU_CONTENT_ALL:
|
|
if (l.arch == DFI_ARCH_32) {
|
|
struct dfi_cpu_32 cpu_32;
|
|
struct dfi_lowcore_32 lc;
|
|
if (dfi_mem_virt_read(lc_addr, &lc, sizeof(lc)))
|
|
return -EINVAL;
|
|
lc2cpu_32(&cpu_32, &lc);
|
|
cpu_32_to_64(cpu, &cpu_32);
|
|
} else {
|
|
struct dfi_lowcore_64 lc;
|
|
if (dfi_mem_virt_read(lc_addr, &lc, sizeof(lc)))
|
|
return -EINVAL;
|
|
lc2cpu_64(cpu, &lc);
|
|
}
|
|
break;
|
|
case DFI_CPU_CONTENT_NONE:
|
|
ABORT("dfi_cpu_add_from_lc() called for CONTENT_NONE");
|
|
}
|
|
dfi_cpu_add(cpu);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Check if lowcore has VX registers
|
|
*/
|
|
int dfi_cpu_lc_has_vx_sa(void *_lc)
|
|
{
|
|
struct dfi_lowcore_64 *lc = _lc;
|
|
|
|
if (l.arch == DFI_ARCH_32)
|
|
return 0;
|
|
if (lc->vector_save_area_addr == 0)
|
|
return 0;
|
|
if (lc->vector_save_area_addr % 1024 != 0)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Copy VX registers out of save areas
|
|
*/
|
|
void dfi_cpu_vx_copy(void *buf, struct dfi_cpu *cpu)
|
|
{
|
|
char *_buf = buf;
|
|
int i;
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
memcpy(&_buf[i * 16], &cpu->fprs[i], 8);
|
|
memcpy(&_buf[i * 16 + 8], &cpu->vxrs_low[i], 8);
|
|
}
|
|
memcpy(&_buf[16 * 16], &cpu->vxrs_high[0], 16 * 16);
|
|
}
|
|
|
|
/*
|
|
* Return kdump base
|
|
*/
|
|
unsigned long dfi_kdump_base(void)
|
|
{
|
|
return l.kdump_base;
|
|
}
|
|
|
|
/*
|
|
* Check if dump contains a kdump dump and initialize kdump_base and kdump_size
|
|
*/
|
|
static void kdump_init(void)
|
|
{
|
|
unsigned long base, size;
|
|
|
|
util_log_print(UTIL_LOG_TRACE, "DFI kdump initialization\n");
|
|
|
|
if (dfi_mem_phys_read(KDUMP_OLDMEM_BASE, &base, sizeof(base)))
|
|
return;
|
|
if (dfi_mem_phys_read(KDUMP_OLDMEM_SIZE, &size, sizeof(size)))
|
|
return;
|
|
if (base == 0 || size == 0)
|
|
return;
|
|
if (base % MIB || size % MIB)
|
|
return;
|
|
if (!dfi_mem_range_valid(base, size))
|
|
return;
|
|
l.kdump_base = base;
|
|
l.kdump_size = size;
|
|
util_log_print(UTIL_LOG_INFO,
|
|
"DFI found valid kdump base 0x%016lx size 0x%016lx\n",
|
|
l.kdump_base, l.kdump_size);
|
|
/*
|
|
* For dumped kdump and user has selected "prod" we swap
|
|
* the crashkernel memory with old memory. If user selected "kdump",
|
|
* we only provide kdump memory. If user selected "all", we
|
|
* provide the complete dump.
|
|
*/
|
|
if (!g.opts.select_specified)
|
|
return;
|
|
if (g.opts.select == OPTS_SELECT_PROD) {
|
|
dfi_mem_unmap(0, size);
|
|
dfi_mem_unmap(base, size);
|
|
dfi_mem_map(0, size, base);
|
|
} else if (g.opts.select == OPTS_SELECT_KDUMP) {
|
|
dfi_mem_unmap(l.kdump_size, U64_MAX - l.kdump_size);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If "--select prod" is set, modify DFI to show production system dump
|
|
*/
|
|
static void kdump_select_prod_init(void)
|
|
{
|
|
unsigned long prefix, ptr, count, tv_sec, i;
|
|
struct timeval timeval;
|
|
|
|
if (g.opts.select_specified && !l.kdump_base)
|
|
ERR_EXIT("The \"--select\" option is not possible with this "
|
|
"dump");
|
|
attr_init();
|
|
dfi_arch_set(DFI_ARCH_64);
|
|
dfi_cpu_info_init(DFI_CPU_CONTENT_NONE);
|
|
if (dfi_vmcoreinfo_symbol(&ptr, "lowcore_ptr"))
|
|
return;
|
|
if (dfi_vmcoreinfo_length(&count, "lowcore_ptr"))
|
|
return;
|
|
if (dfi_vmcoreinfo_val(&tv_sec, "CRASHTIME") == 0) {
|
|
timeval.tv_sec = tv_sec;
|
|
timeval.tv_usec = 0;
|
|
dfi_attr_time_set(&timeval);
|
|
}
|
|
dfi_cpu_info_init(DFI_CPU_CONTENT_ALL);
|
|
for (i = 0; i < count; i++) {
|
|
if (dfi_mem_virt_read(ptr + i * sizeof(long), &prefix,
|
|
sizeof(prefix)))
|
|
continue;
|
|
if (prefix == 0)
|
|
continue;
|
|
if (prefix % 0x1000)
|
|
continue;
|
|
dfi_cpu_add_from_lc(prefix);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Try to get utsname info from dump
|
|
*/
|
|
static void utsname_init(void)
|
|
{
|
|
struct new_utsname *utsname;
|
|
unsigned long ptr;
|
|
char buf[1024];
|
|
|
|
util_log_print(UTIL_LOG_TRACE, "DFI utsname initialization\n");
|
|
|
|
if (dfi_vmcoreinfo_symbol(&ptr, "init_uts_ns"))
|
|
return;
|
|
if (dfi_mem_virt_read(ptr, buf, sizeof(buf)))
|
|
return;
|
|
utsname = memchr(buf, 'L', sizeof(buf) - sizeof(*utsname));
|
|
if (!utsname)
|
|
return;
|
|
if (strncmp(utsname->sysname, "Linux", sizeof(utsname->version)) != 0)
|
|
return;
|
|
dfi_attr_utsname_set(utsname);
|
|
util_log_print(UTIL_LOG_INFO, "DFI utsname release %s version %s\n",
|
|
utsname->release, utsname->version);
|
|
}
|
|
|
|
/*
|
|
* Try to get livedump magic
|
|
*/
|
|
static void livedump_init(void)
|
|
{
|
|
u64 magic;
|
|
|
|
util_log_print(UTIL_LOG_TRACE, "DFI livedump initialization\n");
|
|
|
|
if (dfi_mem_virt_read(0, &magic, sizeof(magic)))
|
|
return;
|
|
if (magic == dfi_live_dump_magic)
|
|
dfi_attr_dump_method_set(DFI_DUMP_METHOD_LIVE);
|
|
}
|
|
|
|
/*
|
|
* Open the dump
|
|
*
|
|
* In case of --mount we first try O_EXCL in order to prevent other
|
|
* tools like zipl or mkfs.xxx to use the disk.
|
|
*
|
|
* On Linux 2.6 and later, O_EXCL can be used without O_CREAT if pathname
|
|
* refers to a block device. If the block device is in use by the system
|
|
* (e.g., mounted), open() fails with the error EBUSY.
|
|
*/
|
|
struct zg_fh *dfi_dump_open(const char *path)
|
|
{
|
|
struct zg_fh *zg_fh;
|
|
|
|
if (g.opts.action == ZG_ACTION_MOUNT) {
|
|
zg_fh = zg_open(path, O_RDONLY | O_EXCL, ZG_CHECK_NONE);
|
|
if (zg_fh)
|
|
return zg_fh;
|
|
}
|
|
return zg_open(path, O_RDONLY, ZG_CHECK);
|
|
}
|
|
|
|
/*
|
|
* Initialize input dump format.
|
|
*/
|
|
int dfi_init(void)
|
|
{
|
|
struct dfi *dfi;
|
|
int i = 0, rc;
|
|
|
|
util_log_print(UTIL_LOG_TRACE, "DFI initialization\n");
|
|
|
|
l.arch = DFI_ARCH_UNKNOWN;
|
|
rc = dfi_mem_chunk_init();
|
|
if (rc)
|
|
return rc;
|
|
attr_init();
|
|
dfi_cpu_info_init(DFI_CPU_CONTENT_NONE);
|
|
while ((dfi = dfi_vec[i])) {
|
|
util_log_print(UTIL_LOG_DEBUG, "DFI trying %s\n", dfi->name);
|
|
l.dfi = dfi;
|
|
g.fh = dfi_dump_open(g.opts.device);
|
|
rc = dfi->init();
|
|
if (rc == 0 && dfi_feat_seek()) {
|
|
kdump_init();
|
|
dfi_vmcoreinfo_init();
|
|
if (g.opts.select == OPTS_SELECT_PROD)
|
|
kdump_select_prod_init();
|
|
utsname_init();
|
|
livedump_init();
|
|
}
|
|
util_log_print(UTIL_LOG_DEBUG, "DFI %s returned with rc %d\n",
|
|
dfi->name, rc);
|
|
if (rc == 0 || rc == -EINVAL)
|
|
return rc;
|
|
zg_close(g.fh);
|
|
i++;
|
|
}
|
|
ERR_EXIT("No valid dump found on \"%s\"", g.opts.device);
|
|
return -1; /* UNREACHABLE */
|
|
}
|
|
|
|
/*
|
|
* Cleanup input dump format.
|
|
*/
|
|
void dfi_exit(void)
|
|
{
|
|
util_log_print(UTIL_LOG_TRACE, "DFI exit\n");
|
|
|
|
if (l.dfi && l.dfi->exit)
|
|
l.dfi->exit();
|
|
}
|