Files
s390-tools/zdump/dfi.c
Mikhail Zaslonko cf99ea019e zgetdump: Add 'Dump file size' field for zgetdump -i output
Add 'Dump file size' field for zgetdump -i output to show the actual size
of dump file in s390 extended format on disk in megabytes.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2018-05-07 13:42:03 +02:00

1237 lines
26 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 <time.h>
#include "zgetdump.h"
#define TIME_FMT_STR "%a, %d %b %Y %H:%M:%S %z"
#define PROGRESS_HASH_CNT 50
/*
* 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,
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;
};
/*
* Memory information
*/
struct mem {
struct dfi_mem_chunk *chunk_cache;
u64 start_addr;
u64 end_addr;
unsigned int chunk_cnt;
struct util_list chunk_list;
};
/*
* 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 mem mem_phys;
struct mem mem_virt;
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);
}
}
/*
* Initialize DFI memory chunks
*/
static void mem_init(struct mem *mem)
{
mem->start_addr = U64_MAX;
mem->end_addr = 0;
util_list_init(&mem->chunk_list, struct dfi_mem_chunk, list);
}
/*
* Memory chunk compare function for list sorting
*/
static int mem_chunk_cmp_fn(void *a, void *b, void *UNUSED(data))
{
struct dfi_mem_chunk *mem_chunk1 = a;
struct dfi_mem_chunk *mem_chunk2 = b;
return mem_chunk1->start < mem_chunk2->start ? -1 : 1;
}
/*
* Update DFI memory chunks
*/
static void mem_update(struct mem *mem)
{
struct dfi_mem_chunk *mem_chunk;
util_list_sort(&mem->chunk_list, mem_chunk_cmp_fn, NULL);
mem->start_addr = U64_MAX;
mem->end_addr = 0;
util_list_iterate(&mem->chunk_list, mem_chunk) {
mem->start_addr = MIN(mem->start_addr, mem_chunk->start);
mem->end_addr = MAX(mem->end_addr, mem_chunk->end);
}
}
/*
* Print memory map
*/
static void mem_map_print(void)
{
struct dfi_mem_chunk *mem_chunk;
u64 print_start = 0, print_end = 0;
const char *zero_str;
u32 volnr = 0;
STDERR("\nMemory map:\n");
/*
* Print each memory chunk if verbose specified
*/
if (g.opts.verbose_specified) {
dfi_mem_chunk_iterate(mem_chunk) {
zero_str = "";
if (mem_chunk->read_fn == dfi_mem_chunk_read_zero)
zero_str = " zeroes";
STDERR(" %016llx - %016llx (%llu MB%s)\n",
mem_chunk->start, mem_chunk->end,
TO_MIB(mem_chunk->size), zero_str);
}
return;
}
/*
* Merge adjacent memory chunks from the same volume
*/
dfi_mem_chunk_iterate(mem_chunk) {
if (print_end == 0) {
print_start = mem_chunk->start;
print_end = mem_chunk->end;
volnr = mem_chunk->volnr;
continue;
}
if (mem_chunk->start != print_end + 1 ||
mem_chunk->volnr != volnr) {
STDERR(" %016llx - %016llx (%llu MB)\n", print_start,
print_end, TO_MIB(print_end - print_start + 1));
print_start = mem_chunk->start;
volnr = mem_chunk->volnr;
}
print_end = mem_chunk->end;
}
STDERR(" %016llx - %016llx (%llu MB)\n", print_start,
print_end, TO_MIB(print_end - print_start + 1));
}
/*
* Is memory range valid?
*/
int dfi_mem_range_valid(u64 addr, u64 len)
{
struct dfi_mem_chunk *mem_chunk;
u64 addr_end = addr + len;
do {
mem_chunk = dfi_mem_chunk_find(addr);
if (!mem_chunk)
return 0;
addr += MIN(len, mem_chunk->end - addr + 1);
} while (addr < addr_end);
return 1;
}
/*
* Is memory already mapped at range?
*/
static int mem_range_mapped(u64 start, u64 size)
{
struct dfi_mem_chunk *mem_chunk;
u64 end = start + size - 1;
dfi_mem_chunk_iterate(mem_chunk) {
if (mem_chunk->start > end)
continue;
if (mem_chunk->end < start)
continue;
return 1;
}
return 0;
}
/*
* 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............: %d\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......: %d\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).: %d\n", l.cpus.cnt);
if (l.attr.real_cpu_cnt)
STDERR(" CPU count (real)...: %d\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())
mem_map_print();
if (l.dfi->info_dump) {
STDERR("\nDump device info:\n");
l.dfi->info_dump();
}
}
/*
* Add memory chunk to memory
*/
static void mem_chunk_create(struct mem *mem, u64 start, u64 size, void *data,
dfi_mem_chunk_read_fn read_fn,
dfi_mem_chunk_free_fn free_fn)
{
struct dfi_mem_chunk *mem_chunk;
mem_chunk = zg_alloc(sizeof(*mem_chunk));
mem_chunk->start = start;
mem_chunk->end = start + size - 1;
mem_chunk->size = size;
mem_chunk->read_fn = read_fn;
mem_chunk->free_fn = free_fn;
mem_chunk->data = data;
util_list_add_tail(&mem->chunk_list, mem_chunk);
mem->start_addr = MIN(mem->start_addr, mem_chunk->start);
mem->end_addr = MAX(mem->end_addr, mem_chunk->end);
mem->chunk_cache = mem_chunk;
mem->chunk_cnt++;
}
/*
* Check if memory chunk contains address
*/
static int mem_chunk_has_addr(struct dfi_mem_chunk *mem_chunk, u64 addr)
{
return (addr >= mem_chunk->start && addr <= mem_chunk->end);
}
/*
* Find memory chunk that contains address
*/
static struct dfi_mem_chunk *mem_chunk_find(struct mem *mem, u64 addr)
{
struct dfi_mem_chunk *mem_chunk;
if (mem_chunk_has_addr(mem->chunk_cache, addr))
return mem->chunk_cache;
util_list_iterate(&mem->chunk_list, mem_chunk) {
if (mem_chunk_has_addr(mem_chunk, addr)) {
mem->chunk_cache = mem_chunk;
return mem_chunk;
}
}
return NULL;
}
/*
* Read memory at given address
*/
static void mem_read(struct mem *mem, u64 addr, void *buf, size_t cnt)
{
struct dfi_mem_chunk *mem_chunk;
u64 size, off, copied = 0;
while (copied != cnt) {
mem_chunk = mem_chunk_find(mem, addr);
size = MIN(cnt - copied, mem_chunk->end - addr + 1);
off = addr - mem_chunk->start;
mem_chunk->read_fn(mem_chunk, off, buf + copied, size);
copied += size;
addr += size;
}
}
/*
* Read memory for virtual map memory chunk
*/
static void mem_chunk_map_read_fn(struct dfi_mem_chunk *mem_chunk, u64 off,
void *buf, u64 cnt)
{
u64 *start = mem_chunk->data;
dfi_mem_phys_read(*start + off, buf, cnt);
}
/*
* Check if memory chunk is a virtual mapping
*/
static int mem_chunk_is_map(struct dfi_mem_chunk *mem_chunk)
{
return mem_chunk->read_fn == mem_chunk_map_read_fn;
}
/*
* Return physical start address for memory chunk
*/
static u64 mem_chunk_start_phys(struct dfi_mem_chunk *mem_chunk)
{
if (mem_chunk_is_map(mem_chunk))
return *((u64 *) mem_chunk->data);
else
return mem_chunk->start;
}
/*
* Add virtual memory chunk with simple virtual mapping
*/
static void mem_chunk_map_add(u64 start, u64 size, u64 start_p)
{
u64 *data = zg_alloc(sizeof(*data));
*data = start_p;
dfi_mem_chunk_virt_add(start, size, data, mem_chunk_map_read_fn,
zg_free);
}
/*
* Add virtual memory chunk
*/
void dfi_mem_chunk_virt_add(u64 start, u64 size, void *data,
dfi_mem_chunk_read_fn read_fn,
dfi_mem_chunk_free_fn free_fn)
{
if (size == 0)
return;
mem_chunk_create(&l.mem_virt, start, size, data, read_fn, free_fn);
}
/*
* Add memory chunk with volume index
*/
void dfi_mem_chunk_add_vol(u64 start, u64 size, void *data,
dfi_mem_chunk_read_fn read_fn,
dfi_mem_chunk_free_fn free_fn,
u32 volnr)
{
if (size == 0)
return;
mem_chunk_create(&l.mem_phys, start, size, data, read_fn, free_fn);
mem_chunk_create(&l.mem_virt, start, size, data, read_fn, free_fn);
l.mem_virt.chunk_cache->volnr = volnr;
}
/*
* Add memory chunk
*/
void dfi_mem_chunk_add(u64 start, u64 size, void *data,
dfi_mem_chunk_read_fn read_fn,
dfi_mem_chunk_free_fn free_fn)
{
dfi_mem_chunk_add_vol(start, size, data, read_fn, free_fn, 0);
}
/*
* Read zero pages
*/
void dfi_mem_chunk_read_zero(struct dfi_mem_chunk *UNUSED(mem_chunk),
u64 UNUSED(off), void *buf, u64 cnt)
{
memset(buf, 0, cnt);
}
/*
* Return mem_chunk list head
*/
struct util_list *dfi_mem_chunk_list(void)
{
return &l.mem_virt.chunk_list;
}
/*
* Return number of memory chunks in input dump
*/
unsigned int dfi_mem_chunk_cnt(void)
{
return l.mem_virt.chunk_cnt;
}
/*
* Return maximum memory range
*/
u64 dfi_mem_range(void)
{
if (l.mem_virt.start_addr == U64_MAX)
return 0;
return l.mem_virt.end_addr - l.mem_virt.start_addr + 1;
}
/*
* Return first memory chunk
*/
struct dfi_mem_chunk *dfi_mem_chunk_first(void)
{
if (util_list_is_empty(&l.mem_virt.chunk_list))
return NULL;
return util_list_start(&l.mem_virt.chunk_list);
}
/*
* Return last memory chunk
*/
struct dfi_mem_chunk *dfi_mem_chunk_last(void)
{
if (util_list_is_empty(&l.mem_virt.chunk_list))
return NULL;
return util_list_end(&l.mem_virt.chunk_list);
}
/*
* Return next memory chunk
*/
struct dfi_mem_chunk *dfi_mem_chunk_next(struct dfi_mem_chunk *mem_chunk)
{
return util_list_next(&l.mem_virt.chunk_list, mem_chunk);
}
/*
* Return previous memory chunk
*/
struct dfi_mem_chunk *dfi_mem_chunk_prev(struct dfi_mem_chunk *mem_chunk)
{
return util_list_prev(&l.mem_virt.chunk_list, mem_chunk);
}
/*
* Find memory chunk for given address
*/
struct dfi_mem_chunk *dfi_mem_chunk_find(u64 addr)
{
return mem_chunk_find(&l.mem_virt, addr);
}
/*
* 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;
}
/*
* Read memory at given address and do kdump swap if necessary
*/
void dfi_mem_read(u64 addr, void *buf, size_t cnt)
{
mem_read(&l.mem_virt, addr, buf, cnt);
}
/*
* Read physical memory at given address
*/
void dfi_mem_phys_read(u64 addr, void *buf, size_t cnt)
{
mem_read(&l.mem_phys, addr, buf, cnt);
}
/*
* Read memory at given address with return code
*/
int dfi_mem_read_rc(u64 addr, void *buf, size_t cnt)
{
if (!dfi_mem_range_valid(addr, cnt))
return -EINVAL;
dfi_mem_read(addr, buf, cnt);
return 0;
}
/*
* 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);
}
/*
* 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_read_rc(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.
*/
void 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;
dfi_mem_read(lc_addr, &lc, sizeof(lc));
lc2cpu_32(&cpu_32, &lc);
cpu_32_to_64(cpu, &cpu_32);
} else {
struct dfi_lowcore_64 lc;
dfi_mem_read(lc_addr, &lc, sizeof(lc));
lc2cpu_64(cpu, &lc);
}
break;
case DFI_CPU_CONTENT_NONE:
ABORT("dfi_cpu_add_from_lc() called for CONTENT_NONE");
}
dfi_cpu_add(cpu);
}
/*
* 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;
}
/*
* Unmap memory region
*/
static void mem_unmap(u64 start, u64 size)
{
u64 start_phys, end_phys, addr_phys, addr_virt, size_virt;
struct dfi_mem_chunk *mem_chunk, *tmp;
u64 end = start + size - 1;
util_list_iterate_safe(&l.mem_virt.chunk_list, mem_chunk, tmp) {
/*
* Chunk not hit?
*/
if (mem_chunk->start >= start + size)
continue;
if (mem_chunk->end < start)
continue;
/*
* Chunk completely unmapped
*
* UNMAP: UUUUUUUUU || UUUUUU
* CHUNK: CCCC || CCCCCC
* TO:
*/
if (mem_chunk->start >= start && mem_chunk->end <= end)
goto free;
/*
* Get real start and end addresses
*/
start_phys = mem_chunk_start_phys(mem_chunk);
end_phys = start_phys + mem_chunk->size - 1;
/*
* Chunk hit at start or in the middle?
*
* UNMAP: UUUUUU || UU || UUU
* CHUNK: CCCCC || CCCCCC || CCCC
* TO: NN || NN || NNN
*/
if (mem_chunk->end > end) {
addr_virt = end + 1;
size_virt = mem_chunk->end - end;
addr_phys = end_phys - size_virt + 1;
mem_chunk_map_add(addr_virt, size_virt, addr_phys);
}
/*
* Chunk hit at end or in the middle?
*
* UNMAP: UUUUUU || UU || UUU
* CHUNK: CCCCC || CCCCCC || CCC
* TO: NN || NN || NN
*/
if (mem_chunk->start < start) {
addr_virt = mem_chunk->start;
size_virt = start - addr_virt;
addr_phys = start_phys;
mem_chunk_map_add(addr_virt, size_virt, addr_phys);
}
free:
util_list_remove(&l.mem_virt.chunk_list, mem_chunk);
l.mem_virt.chunk_cnt--;
if (mem_chunk->data && mem_chunk->free_fn)
mem_chunk->free_fn(mem_chunk->data);
zg_free(mem_chunk);
}
mem_update(&l.mem_virt);
}
/*
* Map memory region
*/
static void mem_map(u64 start, u64 size, u64 start_phys)
{
if (mem_range_mapped(start, size)) {
mem_map_print();
ABORT("Map request for already mapped region (%llx/%llx/%llx)",
start, size, start_phys);
}
mem_chunk_map_add(start, size, start_phys);
mem_update(&l.mem_virt);
}
/*
* Check if dump contains a kdump dump and initialize kdump_base and kdump_size
*/
static void kdump_init(void)
{
unsigned long base, size;
dfi_mem_phys_read(0x10418, &base, sizeof(base));
dfi_mem_phys_read(0x10420, &size, sizeof(size));
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;
/*
* 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) {
mem_unmap(0, size);
mem_unmap(base, size);
mem_map(0, size, base);
} else if (g.opts.select == OPTS_SELECT_KDUMP) {
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_read_rc(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];
if (dfi_vmcoreinfo_symbol(&ptr, "init_uts_ns"))
return;
if (dfi_mem_read_rc(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);
}
/*
* Try to get livedump magic
*/
static void livedump_init(void)
{
u64 magic;
if (dfi_mem_read_rc(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;
l.arch = DFI_ARCH_UNKNOWN;
mem_init(&l.mem_virt);
mem_init(&l.mem_phys);
attr_init();
dfi_cpu_info_init(DFI_CPU_CONTENT_NONE);
while ((dfi = dfi_vec[i])) {
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();
}
if (rc == 0 || rc == -EINVAL)
return rc;
zg_close(g.fh);
i++;
}
ERR_EXIT("No valid dump found on \"%s\"", g.opts.device);
}
/*
* Cleanup input dump format.
*/
void dfi_exit(void)
{
if (l.dfi && l.dfi->exit)
l.dfi->exit();
}