mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
For vr-kernel dumps use the offsets stored in os_info entries for virt to phys address conversion when dump virtual address is to be read, (e.g. vmcoreinfo symbols) using similar method as implemented in crash-utility. It is mainly required for reading "init_uts_ns" symbol and, in case of crashed kdump, "lowcore_ptr" symbol along with a pointers to the lowcore of every CPU. Acked-by: Alexander Egorenkov <egorenar@linux.ibm.com> Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com> Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
536 lines
12 KiB
C
536 lines
12 KiB
C
/*
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "lib/util_libc.h"
|
|
#include "lib/util_log.h"
|
|
|
|
#include "zg.h"
|
|
#include "dfi_mem_chunk.h"
|
|
|
|
/*
|
|
* 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;
|
|
};
|
|
|
|
/*
|
|
* File local static data
|
|
*/
|
|
static struct {
|
|
struct mem mem_phys;
|
|
struct mem mem_virt;
|
|
} l;
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
void dfi_mem_map_print(bool verbose)
|
|
{
|
|
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 (verbose) {
|
|
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));
|
|
}
|
|
|
|
/*
|
|
* 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_cache && 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;
|
|
}
|
|
|
|
/*
|
|
* Is memory range valid?
|
|
*/
|
|
static int mem_range_valid(struct mem *mem, u64 addr, u64 len)
|
|
{
|
|
struct dfi_mem_chunk *mem_chunk;
|
|
u64 addr_end = addr + len;
|
|
|
|
/* check for unsigned wrap */
|
|
if (addr_end < addr)
|
|
return 0;
|
|
|
|
do {
|
|
mem_chunk = mem_chunk_find(mem, 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;
|
|
}
|
|
|
|
/*
|
|
* 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 = util_malloc(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++;
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
static void mem_chunk_virt_add(u64 start, u64 size, void *data,
|
|
dfi_mem_chunk_read_fn read_fn,
|
|
dfi_mem_chunk_free_fn free_fn)
|
|
{
|
|
util_log_print(UTIL_LOG_DEBUG,
|
|
"DFI add %svirt mem chunk start 0x%016lx size 0x%016lx\n",
|
|
read_fn == dfi_mem_chunk_read_zero ? "zero " : "",
|
|
start, size);
|
|
|
|
if (size == 0)
|
|
return;
|
|
mem_chunk_create(&l.mem_virt, start, size, data, read_fn, free_fn);
|
|
}
|
|
|
|
/*
|
|
* Add virtual memory chunk with simple virtual mapping
|
|
*/
|
|
static void mem_chunk_map_add(u64 start, u64 size, u64 start_p)
|
|
{
|
|
u64 *data = util_malloc(sizeof(*data));
|
|
|
|
*data = start_p;
|
|
mem_chunk_virt_add(start, size, data, mem_chunk_map_read_fn, free);
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
util_log_print(UTIL_LOG_DEBUG,
|
|
"DFI add %svol mem chunk start 0x%016lx size 0x%016lx volnr %u\n",
|
|
read_fn == dfi_mem_chunk_read_zero ? "zero " : "",
|
|
start, size, 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, NULL);
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Is memory range valid?
|
|
*/
|
|
int dfi_mem_range_valid(u64 addr, u64 len)
|
|
{
|
|
return mem_range_valid(&l.mem_virt, addr, len);
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
/*
|
|
* Read physical memory at given address
|
|
*/
|
|
int dfi_mem_phys_read(u64 addr, void *buf, size_t cnt)
|
|
{
|
|
util_log_print(UTIL_LOG_TRACE,
|
|
"DFI phys mem read addr 0x%016lx size 0x%016lx\n",
|
|
addr, cnt);
|
|
if (!mem_range_valid(&l.mem_phys, addr, cnt))
|
|
return -EINVAL;
|
|
mem_read(&l.mem_phys, addr, buf, cnt);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Read virtual memory at given address
|
|
*/
|
|
int dfi_mem_virt_read(u64 addr, void *buf, size_t cnt)
|
|
{
|
|
util_log_print(UTIL_LOG_TRACE,
|
|
"DFI virt mem read addr 0x%016lx size 0x%016lx\n",
|
|
addr, cnt);
|
|
if (!mem_range_valid(&l.mem_virt, addr, cnt))
|
|
return -EINVAL;
|
|
mem_read(&l.mem_virt, addr, buf, cnt);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Unmap memory region
|
|
*/
|
|
void dfi_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_log_print(UTIL_LOG_TRACE,
|
|
"DFI mem unmap start 0x%016lx, size 0x%016lx\n",
|
|
start, size);
|
|
|
|
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);
|
|
free(mem_chunk);
|
|
}
|
|
mem_update(&l.mem_virt);
|
|
}
|
|
|
|
/*
|
|
* Map memory region
|
|
*/
|
|
void dfi_mem_map(u64 start, u64 size, u64 start_phys)
|
|
{
|
|
util_log_print(UTIL_LOG_TRACE,
|
|
"DFI mem map start 0x%016lx, size 0x%016lx, base 0x%016lx\n",
|
|
start, size, start_phys);
|
|
|
|
if (mem_range_mapped(start, size)) {
|
|
dfi_mem_map_print(false);
|
|
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);
|
|
}
|
|
|
|
int dfi_mem_chunk_init(void)
|
|
{
|
|
mem_init(&l.mem_virt);
|
|
mem_init(&l.mem_phys);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void dfi_mem_chunk_deinit(void)
|
|
{
|
|
memset(&l, 0, sizeof(l));
|
|
}
|