linux/arch/s390/kernel/crash_dump.c
<<
>>
Prefs
   1/*
   2 * S390 kdump implementation
   3 *
   4 * Copyright IBM Corp. 2011
   5 * Author(s): Michael Holzheu <holzheu@linux.vnet.ibm.com>
   6 */
   7
   8#include <linux/crash_dump.h>
   9#include <asm/lowcore.h>
  10#include <linux/kernel.h>
  11#include <linux/module.h>
  12#include <linux/gfp.h>
  13#include <linux/slab.h>
  14#include <linux/bootmem.h>
  15#include <linux/elf.h>
  16#include <asm/os_info.h>
  17#include <asm/elf.h>
  18#include <asm/ipl.h>
  19
  20#define PTR_ADD(x, y) (((char *) (x)) + ((unsigned long) (y)))
  21#define PTR_SUB(x, y) (((char *) (x)) - ((unsigned long) (y)))
  22#define PTR_DIFF(x, y) ((unsigned long)(((char *) (x)) - ((unsigned long) (y))))
  23
  24/*
  25 * Copy one page from "oldmem"
  26 *
  27 * For the kdump reserved memory this functions performs a swap operation:
  28 *  - [OLDMEM_BASE - OLDMEM_BASE + OLDMEM_SIZE] is mapped to [0 - OLDMEM_SIZE].
  29 *  - [0 - OLDMEM_SIZE] is mapped to [OLDMEM_BASE - OLDMEM_BASE + OLDMEM_SIZE]
  30 */
  31ssize_t copy_oldmem_page(unsigned long pfn, char *buf,
  32                         size_t csize, unsigned long offset, int userbuf)
  33{
  34        unsigned long src;
  35
  36        if (!csize)
  37                return 0;
  38
  39        src = (pfn << PAGE_SHIFT) + offset;
  40        if (src < OLDMEM_SIZE)
  41                src += OLDMEM_BASE;
  42        else if (src > OLDMEM_BASE &&
  43                 src < OLDMEM_BASE + OLDMEM_SIZE)
  44                src -= OLDMEM_BASE;
  45        if (userbuf)
  46                copy_to_user_real((void __force __user *) buf, (void *) src,
  47                                  csize);
  48        else
  49                memcpy_real(buf, (void *) src, csize);
  50        return csize;
  51}
  52
  53/*
  54 * Copy memory from old kernel
  55 */
  56int copy_from_oldmem(void *dest, void *src, size_t count)
  57{
  58        unsigned long copied = 0;
  59        int rc;
  60
  61        if ((unsigned long) src < OLDMEM_SIZE) {
  62                copied = min(count, OLDMEM_SIZE - (unsigned long) src);
  63                rc = memcpy_real(dest, src + OLDMEM_BASE, copied);
  64                if (rc)
  65                        return rc;
  66        }
  67        return memcpy_real(dest + copied, src + copied, count - copied);
  68}
  69
  70/*
  71 * Alloc memory and panic in case of ENOMEM
  72 */
  73static void *kzalloc_panic(int len)
  74{
  75        void *rc;
  76
  77        rc = kzalloc(len, GFP_KERNEL);
  78        if (!rc)
  79                panic("s390 kdump kzalloc (%d) failed", len);
  80        return rc;
  81}
  82
  83/*
  84 * Get memory layout and create hole for oldmem
  85 */
  86static struct mem_chunk *get_memory_layout(void)
  87{
  88        struct mem_chunk *chunk_array;
  89
  90        chunk_array = kzalloc_panic(MEMORY_CHUNKS * sizeof(struct mem_chunk));
  91        detect_memory_layout(chunk_array);
  92        create_mem_hole(chunk_array, OLDMEM_BASE, OLDMEM_SIZE, CHUNK_CRASHK);
  93        return chunk_array;
  94}
  95
  96/*
  97 * Initialize ELF note
  98 */
  99static void *nt_init(void *buf, Elf64_Word type, void *desc, int d_len,
 100                     const char *name)
 101{
 102        Elf64_Nhdr *note;
 103        u64 len;
 104
 105        note = (Elf64_Nhdr *)buf;
 106        note->n_namesz = strlen(name) + 1;
 107        note->n_descsz = d_len;
 108        note->n_type = type;
 109        len = sizeof(Elf64_Nhdr);
 110
 111        memcpy(buf + len, name, note->n_namesz);
 112        len = roundup(len + note->n_namesz, 4);
 113
 114        memcpy(buf + len, desc, note->n_descsz);
 115        len = roundup(len + note->n_descsz, 4);
 116
 117        return PTR_ADD(buf, len);
 118}
 119
 120/*
 121 * Initialize prstatus note
 122 */
 123static void *nt_prstatus(void *ptr, struct save_area *sa)
 124{
 125        struct elf_prstatus nt_prstatus;
 126        static int cpu_nr = 1;
 127
 128        memset(&nt_prstatus, 0, sizeof(nt_prstatus));
 129        memcpy(&nt_prstatus.pr_reg.gprs, sa->gp_regs, sizeof(sa->gp_regs));
 130        memcpy(&nt_prstatus.pr_reg.psw, sa->psw, sizeof(sa->psw));
 131        memcpy(&nt_prstatus.pr_reg.acrs, sa->acc_regs, sizeof(sa->acc_regs));
 132        nt_prstatus.pr_pid = cpu_nr;
 133        cpu_nr++;
 134
 135        return nt_init(ptr, NT_PRSTATUS, &nt_prstatus, sizeof(nt_prstatus),
 136                         "CORE");
 137}
 138
 139/*
 140 * Initialize fpregset (floating point) note
 141 */
 142static void *nt_fpregset(void *ptr, struct save_area *sa)
 143{
 144        elf_fpregset_t nt_fpregset;
 145
 146        memset(&nt_fpregset, 0, sizeof(nt_fpregset));
 147        memcpy(&nt_fpregset.fpc, &sa->fp_ctrl_reg, sizeof(sa->fp_ctrl_reg));
 148        memcpy(&nt_fpregset.fprs, &sa->fp_regs, sizeof(sa->fp_regs));
 149
 150        return nt_init(ptr, NT_PRFPREG, &nt_fpregset, sizeof(nt_fpregset),
 151                       "CORE");
 152}
 153
 154/*
 155 * Initialize timer note
 156 */
 157static void *nt_s390_timer(void *ptr, struct save_area *sa)
 158{
 159        return nt_init(ptr, NT_S390_TIMER, &sa->timer, sizeof(sa->timer),
 160                         KEXEC_CORE_NOTE_NAME);
 161}
 162
 163/*
 164 * Initialize TOD clock comparator note
 165 */
 166static void *nt_s390_tod_cmp(void *ptr, struct save_area *sa)
 167{
 168        return nt_init(ptr, NT_S390_TODCMP, &sa->clk_cmp,
 169                       sizeof(sa->clk_cmp), KEXEC_CORE_NOTE_NAME);
 170}
 171
 172/*
 173 * Initialize TOD programmable register note
 174 */
 175static void *nt_s390_tod_preg(void *ptr, struct save_area *sa)
 176{
 177        return nt_init(ptr, NT_S390_TODPREG, &sa->tod_reg,
 178                       sizeof(sa->tod_reg), KEXEC_CORE_NOTE_NAME);
 179}
 180
 181/*
 182 * Initialize control register note
 183 */
 184static void *nt_s390_ctrs(void *ptr, struct save_area *sa)
 185{
 186        return nt_init(ptr, NT_S390_CTRS, &sa->ctrl_regs,
 187                       sizeof(sa->ctrl_regs), KEXEC_CORE_NOTE_NAME);
 188}
 189
 190/*
 191 * Initialize prefix register note
 192 */
 193static void *nt_s390_prefix(void *ptr, struct save_area *sa)
 194{
 195        return nt_init(ptr, NT_S390_PREFIX, &sa->pref_reg,
 196                         sizeof(sa->pref_reg), KEXEC_CORE_NOTE_NAME);
 197}
 198
 199/*
 200 * Fill ELF notes for one CPU with save area registers
 201 */
 202void *fill_cpu_elf_notes(void *ptr, struct save_area *sa)
 203{
 204        ptr = nt_prstatus(ptr, sa);
 205        ptr = nt_fpregset(ptr, sa);
 206        ptr = nt_s390_timer(ptr, sa);
 207        ptr = nt_s390_tod_cmp(ptr, sa);
 208        ptr = nt_s390_tod_preg(ptr, sa);
 209        ptr = nt_s390_ctrs(ptr, sa);
 210        ptr = nt_s390_prefix(ptr, sa);
 211        return ptr;
 212}
 213
 214/*
 215 * Initialize prpsinfo note (new kernel)
 216 */
 217static void *nt_prpsinfo(void *ptr)
 218{
 219        struct elf_prpsinfo prpsinfo;
 220
 221        memset(&prpsinfo, 0, sizeof(prpsinfo));
 222        prpsinfo.pr_sname = 'R';
 223        strcpy(prpsinfo.pr_fname, "vmlinux");
 224        return nt_init(ptr, NT_PRPSINFO, &prpsinfo, sizeof(prpsinfo),
 225                       KEXEC_CORE_NOTE_NAME);
 226}
 227
 228/*
 229 * Get vmcoreinfo using lowcore->vmcore_info (new kernel)
 230 */
 231static void *get_vmcoreinfo_old(unsigned long *size)
 232{
 233        char nt_name[11], *vmcoreinfo;
 234        Elf64_Nhdr note;
 235        void *addr;
 236
 237        if (copy_from_oldmem(&addr, &S390_lowcore.vmcore_info, sizeof(addr)))
 238                return NULL;
 239        memset(nt_name, 0, sizeof(nt_name));
 240        if (copy_from_oldmem(&note, addr, sizeof(note)))
 241                return NULL;
 242        if (copy_from_oldmem(nt_name, addr + sizeof(note), sizeof(nt_name) - 1))
 243                return NULL;
 244        if (strcmp(nt_name, "VMCOREINFO") != 0)
 245                return NULL;
 246        vmcoreinfo = kzalloc_panic(note.n_descsz);
 247        if (copy_from_oldmem(vmcoreinfo, addr + 24, note.n_descsz))
 248                return NULL;
 249        *size = note.n_descsz;
 250        return vmcoreinfo;
 251}
 252
 253/*
 254 * Initialize vmcoreinfo note (new kernel)
 255 */
 256static void *nt_vmcoreinfo(void *ptr)
 257{
 258        unsigned long size;
 259        void *vmcoreinfo;
 260
 261        vmcoreinfo = os_info_old_entry(OS_INFO_VMCOREINFO, &size);
 262        if (!vmcoreinfo)
 263                vmcoreinfo = get_vmcoreinfo_old(&size);
 264        if (!vmcoreinfo)
 265                return ptr;
 266        return nt_init(ptr, 0, vmcoreinfo, size, "VMCOREINFO");
 267}
 268
 269/*
 270 * Initialize ELF header (new kernel)
 271 */
 272static void *ehdr_init(Elf64_Ehdr *ehdr, int mem_chunk_cnt)
 273{
 274        memset(ehdr, 0, sizeof(*ehdr));
 275        memcpy(ehdr->e_ident, ELFMAG, SELFMAG);
 276        ehdr->e_ident[EI_CLASS] = ELFCLASS64;
 277        ehdr->e_ident[EI_DATA] = ELFDATA2MSB;
 278        ehdr->e_ident[EI_VERSION] = EV_CURRENT;
 279        memset(ehdr->e_ident + EI_PAD, 0, EI_NIDENT - EI_PAD);
 280        ehdr->e_type = ET_CORE;
 281        ehdr->e_machine = EM_S390;
 282        ehdr->e_version = EV_CURRENT;
 283        ehdr->e_phoff = sizeof(Elf64_Ehdr);
 284        ehdr->e_ehsize = sizeof(Elf64_Ehdr);
 285        ehdr->e_phentsize = sizeof(Elf64_Phdr);
 286        ehdr->e_phnum = mem_chunk_cnt + 1;
 287        return ehdr + 1;
 288}
 289
 290/*
 291 * Return CPU count for ELF header (new kernel)
 292 */
 293static int get_cpu_cnt(void)
 294{
 295        int i, cpus = 0;
 296
 297        for (i = 0; zfcpdump_save_areas[i]; i++) {
 298                if (zfcpdump_save_areas[i]->pref_reg == 0)
 299                        continue;
 300                cpus++;
 301        }
 302        return cpus;
 303}
 304
 305/*
 306 * Return memory chunk count for ELF header (new kernel)
 307 */
 308static int get_mem_chunk_cnt(void)
 309{
 310        struct mem_chunk *chunk_array, *mem_chunk;
 311        int i, cnt = 0;
 312
 313        chunk_array = get_memory_layout();
 314        for (i = 0; i < MEMORY_CHUNKS; i++) {
 315                mem_chunk = &chunk_array[i];
 316                if (chunk_array[i].type != CHUNK_READ_WRITE &&
 317                    chunk_array[i].type != CHUNK_READ_ONLY)
 318                        continue;
 319                if (mem_chunk->size == 0)
 320                        continue;
 321                cnt++;
 322        }
 323        kfree(chunk_array);
 324        return cnt;
 325}
 326
 327/*
 328 * Relocate pointer in order to allow vmcore code access the data
 329 */
 330static inline unsigned long relocate(unsigned long addr)
 331{
 332        return OLDMEM_BASE + addr;
 333}
 334
 335/*
 336 * Initialize ELF loads (new kernel)
 337 */
 338static int loads_init(Elf64_Phdr *phdr, u64 loads_offset)
 339{
 340        struct mem_chunk *chunk_array, *mem_chunk;
 341        int i;
 342
 343        chunk_array = get_memory_layout();
 344        for (i = 0; i < MEMORY_CHUNKS; i++) {
 345                mem_chunk = &chunk_array[i];
 346                if (mem_chunk->size == 0)
 347                        break;
 348                if (chunk_array[i].type != CHUNK_READ_WRITE &&
 349                    chunk_array[i].type != CHUNK_READ_ONLY)
 350                        continue;
 351                else
 352                        phdr->p_filesz = mem_chunk->size;
 353                phdr->p_type = PT_LOAD;
 354                phdr->p_offset = mem_chunk->addr;
 355                phdr->p_vaddr = mem_chunk->addr;
 356                phdr->p_paddr = mem_chunk->addr;
 357                phdr->p_memsz = mem_chunk->size;
 358                phdr->p_flags = PF_R | PF_W | PF_X;
 359                phdr->p_align = PAGE_SIZE;
 360                phdr++;
 361        }
 362        kfree(chunk_array);
 363        return i;
 364}
 365
 366/*
 367 * Initialize notes (new kernel)
 368 */
 369static void *notes_init(Elf64_Phdr *phdr, void *ptr, u64 notes_offset)
 370{
 371        struct save_area *sa;
 372        void *ptr_start = ptr;
 373        int i;
 374
 375        ptr = nt_prpsinfo(ptr);
 376
 377        for (i = 0; zfcpdump_save_areas[i]; i++) {
 378                sa = zfcpdump_save_areas[i];
 379                if (sa->pref_reg == 0)
 380                        continue;
 381                ptr = fill_cpu_elf_notes(ptr, sa);
 382        }
 383        ptr = nt_vmcoreinfo(ptr);
 384        memset(phdr, 0, sizeof(*phdr));
 385        phdr->p_type = PT_NOTE;
 386        phdr->p_offset = relocate(notes_offset);
 387        phdr->p_filesz = (unsigned long) PTR_SUB(ptr, ptr_start);
 388        phdr->p_memsz = phdr->p_filesz;
 389        return ptr;
 390}
 391
 392/*
 393 * Create ELF core header (new kernel)
 394 */
 395static void s390_elf_corehdr_create(char **elfcorebuf, size_t *elfcorebuf_sz)
 396{
 397        Elf64_Phdr *phdr_notes, *phdr_loads;
 398        int mem_chunk_cnt;
 399        void *ptr, *hdr;
 400        u32 alloc_size;
 401        u64 hdr_off;
 402
 403        mem_chunk_cnt = get_mem_chunk_cnt();
 404
 405        alloc_size = 0x1000 + get_cpu_cnt() * 0x300 +
 406                mem_chunk_cnt * sizeof(Elf64_Phdr);
 407        hdr = kzalloc_panic(alloc_size);
 408        /* Init elf header */
 409        ptr = ehdr_init(hdr, mem_chunk_cnt);
 410        /* Init program headers */
 411        phdr_notes = ptr;
 412        ptr = PTR_ADD(ptr, sizeof(Elf64_Phdr));
 413        phdr_loads = ptr;
 414        ptr = PTR_ADD(ptr, sizeof(Elf64_Phdr) * mem_chunk_cnt);
 415        /* Init notes */
 416        hdr_off = PTR_DIFF(ptr, hdr);
 417        ptr = notes_init(phdr_notes, ptr, ((unsigned long) hdr) + hdr_off);
 418        /* Init loads */
 419        hdr_off = PTR_DIFF(ptr, hdr);
 420        loads_init(phdr_loads, ((unsigned long) hdr) + hdr_off);
 421        *elfcorebuf_sz = hdr_off;
 422        *elfcorebuf = (void *) relocate((unsigned long) hdr);
 423        BUG_ON(*elfcorebuf_sz > alloc_size);
 424}
 425
 426/*
 427 * Create kdump ELF core header in new kernel, if it has not been passed via
 428 * the "elfcorehdr" kernel parameter
 429 */
 430static int setup_kdump_elfcorehdr(void)
 431{
 432        size_t elfcorebuf_sz;
 433        char *elfcorebuf;
 434
 435        if (!OLDMEM_BASE || is_kdump_kernel())
 436                return -EINVAL;
 437        s390_elf_corehdr_create(&elfcorebuf, &elfcorebuf_sz);
 438        elfcorehdr_addr = (unsigned long long) elfcorebuf;
 439        elfcorehdr_size = elfcorebuf_sz;
 440        return 0;
 441}
 442
 443subsys_initcall(setup_kdump_elfcorehdr);
 444