/* ppc64.c -- core analysis suite * * Copyright (C) 2004-2015,2018 David Anderson * Copyright (C) 2004-2015,2018 Red Hat, Inc. All rights reserved. * Copyright (C) 2004, 2006 Haren Myneni, IBM Corporation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #ifdef PPC64 #include "defs.h" #include #include static int ppc64_kvtop(struct task_context *, ulong, physaddr_t *, int); static int ppc64_uvtop(struct task_context *, ulong, physaddr_t *, int); static ulong ppc64_vmalloc_start(void); static int ppc64_vmemmap_to_phys(ulong, physaddr_t *, int); static int ppc64_is_task_addr(ulong); static int ppc64_verify_symbol(const char *, ulong, char); static ulong ppc64_get_task_pgd(ulong); static int ppc64_translate_pte(ulong, void *, ulonglong); static ulong ppc64_processor_speed(void); static int ppc64_eframe_search(struct bt_info *); static void ppc64_back_trace_cmd(struct bt_info *); static void ppc64_back_trace(struct gnu_request *, struct bt_info *); static void get_ppc64_frame(struct bt_info *, ulong *, ulong *); static void ppc64_print_stack_entry(int,struct gnu_request *, ulong, ulong, struct bt_info *); static void ppc64_dump_irq(int); static ulong ppc64_get_sp(ulong); static void ppc64_get_stack_frame(struct bt_info *, ulong *, ulong *); static int ppc64_dis_filter(ulong, char *, unsigned int); static void ppc64_cmd_mach(void); static int ppc64_get_smp_cpus(void); static void ppc64_display_machine_stats(void); static void ppc64_dump_line_number(ulong); static struct line_number_hook ppc64_line_number_hooks[]; static ulong ppc64_get_stackbase(ulong); static ulong ppc64_get_stacktop(ulong); void ppc64_compiler_warning_stub(void); static ulong ppc64_in_irqstack(ulong); static char * ppc64_check_eframe(struct ppc64_pt_regs *); static void ppc64_print_eframe(char *, struct ppc64_pt_regs *, struct bt_info *); static void parse_cmdline_args(void); static int ppc64_paca_init(int); static void ppc64_init_cpu_info(void); static int ppc64_get_cpu_map(void); static void ppc64_clear_machdep_cache(void); static void ppc64_vmemmap_init(void); static int ppc64_get_kvaddr_ranges(struct vaddr_range *); static uint get_ptetype(ulong pte); static int is_hugepage(ulong pte); static int is_hugepd(ulong pte); static ulong hugepage_dir(ulong pte); static ulong pgd_page_vaddr_l4(ulong pgd); static ulong pud_page_vaddr_l4(ulong pud); static ulong pmd_page_vaddr_l4(ulong pmd); static int is_opal_context(ulong sp, ulong nip); void opalmsg(void); static int is_opal_context(ulong sp, ulong nip) { uint64_t opal_start, opal_end; if (!(machdep->flags & OPAL_FW)) return FALSE; opal_start = machdep->machspec->opal.base; opal_end = opal_start + machdep->machspec->opal.size; if (((sp >= opal_start) && (sp < opal_end)) || ((nip >= opal_start) && (nip < opal_end))) return TRUE; return FALSE; } static inline int is_hugepage(ulong pte) { if ((machdep->flags & BOOK3E) || (THIS_KERNEL_VERSION < LINUX(3,10,0))) { /* * hugepage support via hugepd for book3e and * also kernel v3.9 & below. */ return 0; } else if (THIS_KERNEL_VERSION >= LINUX(4,5,0)) { /* * leaf pte for huge page, if _PAGE_PTE is set. */ return !!(pte & _PAGE_PTE); } else { /* BOOK3S, kernel v3.10 - v4.4 */ /* * leaf pte for huge page, bottom two bits != 00 */ return ((pte & HUGE_PTE_MASK) != 0x0); } } static inline int is_hugepd(ulong pte) { if ((machdep->flags & BOOK3E) || (THIS_KERNEL_VERSION < LINUX(3,10,0))) return ((pte & PD_HUGE) == 0x0); else if (THIS_KERNEL_VERSION >= LINUX(4,5,0)) { /* * hugepd pointer, if _PAGE_PTE is not set and * hugepd shift mask is set. */ return (!(pte & _PAGE_PTE) && ((pte & HUGEPD_SHIFT_MASK) != 0)); } else { /* BOOK3S, kernel v3.10 - v4.4 */ /* * hugepd pointer, bottom two bits == 00 and next 4 bits * indicate size of table */ return (((pte & HUGE_PTE_MASK) == 0x0) && ((pte & HUGEPD_SHIFT_MASK) != 0)); } } static inline uint get_ptetype(ulong pte) { uint pte_type = 0; /* 0: regular entry; 1: huge pte; 2: huge pd */ if (is_hugepage(pte)) pte_type = 1; else if (!(machdep->flags & RADIX_MMU) && (PAGESIZE() != PPC64_64K_PAGE_SIZE) && is_hugepd(pte)) pte_type = 2; return pte_type; } static inline ulong hugepage_dir(ulong pte) { if ((machdep->flags & BOOK3E) || (THIS_KERNEL_VERSION < LINUX(3,10,0))) return (ulong)((pte & ~HUGEPD_SHIFT_MASK) | PD_HUGE); else if (machdep->flags & PHYS_ENTRY_L4) return PTOV(pte & ~HUGEPD_ADDR_MASK); else /* BOOK3S, kernel v3.10 - v4.4 */ return (ulong)(pte & ~HUGEPD_SHIFT_MASK); } static inline ulong pgd_page_vaddr_l4(ulong pgd) { ulong pgd_val; pgd_val = (pgd & ~machdep->machspec->pgd_masked_bits); if (machdep->flags & PHYS_ENTRY_L4) { /* * physical address is stored starting from kernel v4.6 */ pgd_val = PTOV(pgd_val); } return pgd_val; } static inline ulong pud_page_vaddr_l4(ulong pud) { ulong pud_val; pud_val = (pud & ~machdep->machspec->pud_masked_bits); if (machdep->flags & PHYS_ENTRY_L4) { /* * physical address is stored starting from kernel v4.6 */ pud_val = PTOV(pud_val); } return pud_val; } static inline ulong pmd_page_vaddr_l4(ulong pmd) { ulong pmd_val; pmd_val = (pmd & ~machdep->machspec->pmd_masked_bits); if (machdep->flags & PHYS_ENTRY_L4) { /* * physical address is stored starting from kernel v4.6 */ pmd_val = PTOV(pmd_val); } return pmd_val; } static int book3e_is_kvaddr(ulong addr) { return (addr >= BOOK3E_VMBASE); } static int book3e_is_vmaddr(ulong addr) { return (addr >= BOOK3E_VMBASE) && (addr < machdep->identity_map_base); } static int ppc64_is_vmaddr(ulong addr) { return (vt->vmalloc_start && addr >= vt->vmalloc_start); } #define is_RHEL8() (strstr(kt->proc_version, ".el8.")) static int set_ppc64_max_physmem_bits(void) { int dimension; char *string; if ((string = pc->read_vmcoreinfo("NUMBER(MAX_PHYSMEM_BITS)"))) { machdep->max_physmem_bits = atol(string); free(string); return 0; } get_array_length("mem_section", &dimension, 0); if ((machdep->flags & VMEMMAP) && (THIS_KERNEL_VERSION >= LINUX(4,20,0)) && !dimension && (machdep->pagesize == 65536)) { /* * SPARSEMEM_VMEMMAP & SPARSEMEM_EXTREME configurations with * 64K pagesize and v4.20 kernel or later. */ machdep->max_physmem_bits = _MAX_PHYSMEM_BITS_4_20; } else if ((machdep->flags & VMEMMAP) && ((THIS_KERNEL_VERSION >= LINUX(4,19,0)) || is_RHEL8())) { /* SPARSEMEM_VMEMMAP & v4.19 kernel or later, or RHEL8 */ machdep->max_physmem_bits = _MAX_PHYSMEM_BITS_4_19; } else if (THIS_KERNEL_VERSION >= LINUX(3,7,0)) machdep->max_physmem_bits = _MAX_PHYSMEM_BITS_3_7; else machdep->max_physmem_bits = _MAX_PHYSMEM_BITS; return 0; } struct machine_specific ppc64_machine_specific = { .hwintrstack = { 0 }, .hwstackbuf = 0, .hwstacksize = 0, .pte_rpn_shift = PTE_RPN_SHIFT_DEFAULT, ._page_pte = 0x0UL, ._page_present = 0x1UL, ._page_user = 0x2UL, ._page_rw = 0x4UL, ._page_guarded = 0x8UL, ._page_coherent = 0x10UL, ._page_no_cache = 0x20UL, ._page_writethru = 0x40UL, ._page_dirty = 0x80UL, ._page_accessed = 0x100UL, .is_kvaddr = generic_is_kvaddr, .is_vmaddr = ppc64_is_vmaddr, }; struct machine_specific book3e_machine_specific = { .hwintrstack = { 0 }, .hwstackbuf = 0, .hwstacksize = 0, .pte_rpn_shift = PTE_RPN_SHIFT_L4_BOOK3E_64K, ._page_pte = 0x0UL, ._page_present = 0x1UL, ._page_user = 0xCUL, ._page_rw = 0x30UL, ._page_guarded = 0x100000UL, ._page_coherent = 0x200000UL, ._page_no_cache = 0x400000UL, ._page_writethru = 0x800000UL, ._page_dirty = 0x1000UL, ._page_accessed = 0x40000UL, .is_kvaddr = book3e_is_kvaddr, .is_vmaddr = book3e_is_vmaddr, }; #define SKIBOOT_BASE 0x30000000 /* * Do all necessary machine-specific setup here. This is called several * times during initialization. */ void ppc64_init(int when) { #if defined(__x86_64__) if (ACTIVE()) error(FATAL, "compiled for the PPC64 architecture\n"); #endif switch (when) { case SETUP_ENV: machdep->process_elf_notes = process_elf64_notes; break; case PRE_SYMTAB: machdep->machspec = &ppc64_machine_specific; machdep->verify_symbol = ppc64_verify_symbol; if (pc->flags & KERNEL_DEBUG_QUERY) return; machdep->stacksize = PPC64_STACK_SIZE; machdep->last_pgd_read = 0; machdep->last_pud_read = 0; machdep->last_pmd_read = 0; machdep->last_ptbl_read = 0; machdep->verify_paddr = generic_verify_paddr; machdep->ptrs_per_pgd = PTRS_PER_PGD; machdep->flags |= MACHDEP_BT_TEXT; if (machdep->cmdline_args[0]) parse_cmdline_args(); machdep->clear_machdep_cache = ppc64_clear_machdep_cache; break; case PRE_GDB: /* * Recently there were changes made to kexec tools * to support 64K page size. With those changes * vmcore file obtained from a kernel which supports * 64K page size cannot be analyzed using crash on a * machine running with kernel supporting 4K page size * * The following modifications are required in crash * tool to be in sync with kexec tools. * * Look if the following symbol exists. If yes then * the dump was taken with a kernel supporting 64k * page size. So change the page size accordingly. * * Also moved the following code block from * PRE_SYMTAB case here. */ if (symbol_exists("interrupt_base_book3e")) { machdep->machspec = &book3e_machine_specific; machdep->flags |= BOOK3E; machdep->kvbase = BOOK3E_VMBASE; } else machdep->kvbase = symbol_value("_stext"); if (symbol_exists("__hash_page_64K")) machdep->pagesize = PPC64_64K_PAGE_SIZE; else machdep->pagesize = memory_page_size(); machdep->pageshift = ffs(machdep->pagesize) - 1; machdep->pageoffset = machdep->pagesize - 1; machdep->pagemask = ~((ulonglong)machdep->pageoffset); if ((machdep->pgd = (char *)malloc(PAGESIZE())) == NULL) error(FATAL, "cannot malloc pgd space."); if ((machdep->pud = (char *)malloc(PAGESIZE())) == NULL) error(FATAL, "cannot malloc pud space."); if ((machdep->pmd = (char *)malloc(PAGESIZE())) == NULL) error(FATAL, "cannot malloc pmd space."); if ((machdep->ptbl = (char *)malloc(PAGESIZE())) == NULL) error(FATAL, "cannot malloc ptbl space."); machdep->identity_map_base = symbol_value("_stext"); machdep->is_kvaddr = machdep->machspec->is_kvaddr; machdep->is_uvaddr = generic_is_uvaddr; machdep->eframe_search = ppc64_eframe_search; machdep->back_trace = ppc64_back_trace_cmd; machdep->processor_speed = ppc64_processor_speed; machdep->uvtop = ppc64_uvtop; machdep->kvtop = ppc64_kvtop; machdep->get_task_pgd = ppc64_get_task_pgd; machdep->get_stack_frame = ppc64_get_stack_frame; machdep->get_stackbase = ppc64_get_stackbase; machdep->get_stacktop = ppc64_get_stacktop; machdep->translate_pte = ppc64_translate_pte; machdep->memory_size = generic_memory_size; machdep->is_task_addr = ppc64_is_task_addr; machdep->dis_filter = ppc64_dis_filter; machdep->cmd_mach = ppc64_cmd_mach; machdep->get_smp_cpus = ppc64_get_smp_cpus; machdep->line_number_hooks = ppc64_line_number_hooks; machdep->value_to_symbol = generic_machdep_value_to_symbol; machdep->get_kvaddr_ranges = ppc64_get_kvaddr_ranges; machdep->init_kernel_pgd = NULL; if (symbol_exists("vmemmap_populate")) { if (symbol_exists("vmemmap")) { readmem(symbol_value("vmemmap"), KVADDR, &machdep->machspec->vmemmap_base, sizeof(void *), "vmemmap", QUIET|FAULT_ON_ERROR); } else machdep->machspec->vmemmap_base = VMEMMAP_REGION_ID << REGION_SHIFT; machdep->flags |= VMEMMAP; } machdep->get_irq_affinity = generic_get_irq_affinity; machdep->show_interrupts = generic_show_interrupts; break; case POST_GDB: if (!(machdep->flags & BOOK3E)) { struct machine_specific *m = machdep->machspec; /* * To determine if the kernel was running on OPAL based platform, * use struct opal, which is populated with relevant values. */ if (symbol_exists("opal")) { get_symbol_data("opal", sizeof(struct ppc64_opal), &(m->opal)); if (m->opal.base == SKIBOOT_BASE) machdep->flags |= OPAL_FW; } /* * On Power ISA 3.0 based server processors, a kernel can * run with radix MMU or standard MMU. Set the flag, * if it is radix MMU. */ if (symbol_exists("cur_cpu_spec") && MEMBER_EXISTS("cpu_spec", "mmu_features")) { ulong cur_cpu_spec; uint mmu_features, offset; get_symbol_data("cur_cpu_spec", sizeof(void *), &cur_cpu_spec); offset = MEMBER_OFFSET("cpu_spec", "mmu_features"); readmem(cur_cpu_spec + offset, KVADDR, &mmu_features, sizeof(uint), "cpu mmu features", FAULT_ON_ERROR); machdep->flags |= (mmu_features & RADIX_MMU); } /* * Starting with v3.14 we no longer use _PAGE_COHERENT * bit as it is always set on hash64 and on platforms * that cannot always set it, _PAGE_NO_CACHE and * _PAGE_WRITETHRU can be used to infer it. */ if (THIS_KERNEL_VERSION >= LINUX(3,14,0)) m->_page_coherent = 0x0UL; /* * In kernel v4.5, _PAGE_PTE bit is introduced to * distinguish PTEs from pointers. */ if (THIS_KERNEL_VERSION >= LINUX(4,5,0)) { m->_page_pte = 0x1UL; m->_page_present = 0x2UL; m->_page_user = 0x4UL; m->_page_rw = 0x8UL; m->_page_guarded = 0x10UL; } /* * Starting with kernel v4.6, to accommodate both * radix and hash MMU modes in a single kernel, * _PAGE_PTE & _PAGE_PRESENT page flags are changed. * Also, page table entries store physical addresses. */ if (THIS_KERNEL_VERSION >= LINUX(4,6,0)) { m->_page_pte = 0x1UL << 62; m->_page_present = 0x1UL << 63; machdep->flags |= PHYS_ENTRY_L4; } if (THIS_KERNEL_VERSION >= LINUX(4,7,0)) { /* * Starting with kernel v4.7 page table entries * are always big endian on BOOK3S. Set this * flag if kernel is not big endian. */ if (__BYTE_ORDER == __LITTLE_ENDIAN) machdep->flags |= SWAP_ENTRY_L4; } } if (!(machdep->flags & (VM_ORIG|VM_4_LEVEL))) { if (THIS_KERNEL_VERSION >= LINUX(2,6,14)) { machdep->flags |= VM_4_LEVEL; } else { machdep->flags |= VM_ORIG; } } if (machdep->flags & VM_ORIG) { /* pre-2.6.14 layout */ free(machdep->pud); machdep->pud = NULL; machdep->ptrs_per_pgd = PTRS_PER_PGD; } else { /* 2.6.14 layout */ struct machine_specific *m = machdep->machspec; if (machdep->pagesize == 65536) { /* 64K pagesize */ if (machdep->flags & RADIX_MMU) { m->l1_index_size = PTE_INDEX_SIZE_RADIX_64K; m->l2_index_size = PMD_INDEX_SIZE_RADIX_64K; m->l3_index_size = PUD_INDEX_SIZE_RADIX_64K; m->l4_index_size = PGD_INDEX_SIZE_RADIX_64K; } else if (!(machdep->flags & BOOK3E) && (THIS_KERNEL_VERSION >= LINUX(4,6,0))) { m->l1_index_size = PTE_INDEX_SIZE_L4_64K_3_10; if (THIS_KERNEL_VERSION >= LINUX(4,12,0)) { m->l2_index_size = PMD_INDEX_SIZE_L4_64K_4_12; if (THIS_KERNEL_VERSION >= LINUX(4,17,0)) m->l3_index_size = PUD_INDEX_SIZE_L4_64K_4_17; else m->l3_index_size = PUD_INDEX_SIZE_L4_64K_4_12; m->l4_index_size = PGD_INDEX_SIZE_L4_64K_4_12; } else { m->l2_index_size = PMD_INDEX_SIZE_L4_64K_4_6; m->l3_index_size = PUD_INDEX_SIZE_L4_64K_4_6; m->l4_index_size = PGD_INDEX_SIZE_L4_64K_3_10; } } else if (THIS_KERNEL_VERSION >= LINUX(3,10,0)) { m->l1_index_size = PTE_INDEX_SIZE_L4_64K_3_10; m->l2_index_size = PMD_INDEX_SIZE_L4_64K_3_10; m->l3_index_size = PUD_INDEX_SIZE_L4_64K; m->l4_index_size = PGD_INDEX_SIZE_L4_64K_3_10; } else { m->l1_index_size = PTE_INDEX_SIZE_L4_64K; m->l2_index_size = PMD_INDEX_SIZE_L4_64K; m->l3_index_size = PUD_INDEX_SIZE_L4_64K; m->l4_index_size = PGD_INDEX_SIZE_L4_64K; } if (!(machdep->flags & BOOK3E)) m->pte_rpn_shift = symbol_exists("demote_segment_4k") ? PTE_RPN_SHIFT_L4_64K_V2 : PTE_RPN_SHIFT_L4_64K_V1; if (!(machdep->flags & BOOK3E) && (THIS_KERNEL_VERSION >= LINUX(4,6,0))) { m->pgd_masked_bits = PGD_MASKED_BITS_64K_4_6; m->pud_masked_bits = PUD_MASKED_BITS_64K_4_6; m->pmd_masked_bits = PMD_MASKED_BITS_64K_4_6; } else { m->pgd_masked_bits = PGD_MASKED_BITS_64K; m->pud_masked_bits = PUD_MASKED_BITS_64K; if ((machdep->flags & BOOK3E) && (THIS_KERNEL_VERSION >= LINUX(4,5,0))) m->pmd_masked_bits = PMD_MASKED_BITS_BOOK3E_64K_4_5; else if (THIS_KERNEL_VERSION >= LINUX(3,11,0)) m->pmd_masked_bits = PMD_MASKED_BITS_64K_3_11; else m->pmd_masked_bits = PMD_MASKED_BITS_64K; } } else { /* 4K pagesize */ if (machdep->flags & RADIX_MMU) { m->l1_index_size = PTE_INDEX_SIZE_RADIX_4K; m->l2_index_size = PMD_INDEX_SIZE_RADIX_4K; m->l3_index_size = PUD_INDEX_SIZE_RADIX_4K; m->l4_index_size = PGD_INDEX_SIZE_RADIX_4K; } else { m->l1_index_size = PTE_INDEX_SIZE_L4_4K; m->l2_index_size = PMD_INDEX_SIZE_L4_4K; if (THIS_KERNEL_VERSION >= LINUX(3,7,0)) m->l3_index_size = PUD_INDEX_SIZE_L4_4K_3_7; else m->l3_index_size = PUD_INDEX_SIZE_L4_4K; m->l4_index_size = PGD_INDEX_SIZE_L4_4K; if (machdep->flags & BOOK3E) m->pte_rpn_shift = PTE_RPN_SHIFT_L4_BOOK3E_4K; else m->pte_rpn_shift = THIS_KERNEL_VERSION >= LINUX(4,5,0) ? PTE_RPN_SHIFT_L4_4K_4_5 : PTE_RPN_SHIFT_L4_4K; } m->pgd_masked_bits = PGD_MASKED_BITS_4K; m->pud_masked_bits = PUD_MASKED_BITS_4K; m->pmd_masked_bits = PMD_MASKED_BITS_4K; } m->pte_rpn_mask = PTE_RPN_MASK_DEFAULT; if (!(machdep->flags & BOOK3E)) { if (THIS_KERNEL_VERSION >= LINUX(4,6,0)) { m->pte_rpn_mask = PTE_RPN_MASK_L4_4_6; m->pte_rpn_shift = PTE_RPN_SHIFT_L4_4_6; } if (THIS_KERNEL_VERSION >= LINUX(4,7,0)) { m->pgd_masked_bits = PGD_MASKED_BITS_4_7; m->pud_masked_bits = PUD_MASKED_BITS_4_7; m->pmd_masked_bits = PMD_MASKED_BITS_4_7; } } /* Compute ptrs per each level */ m->l1_shift = machdep->pageshift; m->ptrs_per_l1 = (1 << m->l1_index_size); m->ptrs_per_l2 = (1 << m->l2_index_size); m->ptrs_per_l3 = (1 << m->l3_index_size); m->ptrs_per_l4 = (1 << m->l4_index_size); machdep->ptrs_per_pgd = m->ptrs_per_l4; /* Compute shifts */ m->l2_shift = m->l1_shift + m->l1_index_size; m->l3_shift = m->l2_shift + m->l2_index_size; m->l4_shift = m->l3_shift + m->l3_index_size; } if (machdep->flags & VMEMMAP) ppc64_vmemmap_init(); machdep->section_size_bits = _SECTION_SIZE_BITS; set_ppc64_max_physmem_bits(); ppc64_init_cpu_info(); machdep->vmalloc_start = ppc64_vmalloc_start; MEMBER_OFFSET_INIT(thread_struct_pg_tables, "thread_struct", "pg_tables"); STRUCT_SIZE_INIT(irqdesc, "irqdesc"); STRUCT_SIZE_INIT(irq_desc_t, "irq_desc_t"); if (INVALID_SIZE(irqdesc) && INVALID_SIZE(irq_desc_t)) STRUCT_SIZE_INIT(irq_desc_t, "irq_desc"); /* as of 2.3.x PPC uses the generic irq handlers */ if (VALID_SIZE(irq_desc_t)) machdep->dump_irq = generic_dump_irq; else { machdep->dump_irq = ppc64_dump_irq; MEMBER_OFFSET_INIT(irqdesc_action, "irqdesc", "action"); MEMBER_OFFSET_INIT(irqdesc_ctl, "irqdesc", "ctl"); MEMBER_OFFSET_INIT(irqdesc_level, "irqdesc", "level"); } MEMBER_OFFSET_INIT(device_node_type, "device_node", "type"); MEMBER_OFFSET_INIT(device_node_allnext, "device_node", "allnext"); MEMBER_OFFSET_INIT(device_node_properties, "device_node", "properties"); MEMBER_OFFSET_INIT(property_name, "property", "name"); MEMBER_OFFSET_INIT(property_value, "property", "value"); MEMBER_OFFSET_INIT(property_next, "property", "next"); MEMBER_OFFSET_INIT(machdep_calls_setup_residual, "machdep_calls", "setup_residual"); MEMBER_OFFSET_INIT(RESIDUAL_VitalProductData, "RESIDUAL", "VitalProductData"); MEMBER_OFFSET_INIT(VPD_ProcessorHz, "VPD", "ProcessorHz"); MEMBER_OFFSET_INIT(bd_info_bi_intfreq, "bd_info", "bi_intfreq"); if (symbol_exists("irq_desc")) ARRAY_LENGTH_INIT(machdep->nr_irqs, irq_desc, "irq_desc", NULL, 0); else if (kernel_symbol_exists("nr_irqs")) get_symbol_data("nr_irqs", sizeof(unsigned int), &machdep->nr_irqs); else machdep->nr_irqs = 0; if (symbol_exists("paca") && MEMBER_EXISTS("paca_struct", "xHrdIntStack")) { ulong paca_sym, offset; uint cpu, paca_size = STRUCT_SIZE("paca_struct"); /* * Get the HW Interrupt stack base and top values. * Note that, this stack will be used to store frames * when the CPU received IPI (only for 2.4 kernel). * Hence it is needed to retrieve IPI symbols * (Ex: smp_message_recv, xics_ipi_action, and etc) * and to get the top SP in the process's stack. */ offset = MEMBER_OFFSET("paca_struct", "xHrdIntStack"); paca_sym = symbol_value("paca"); for (cpu = 0; cpu < kt->cpus; cpu++) { readmem(paca_sym + (paca_size * cpu) + offset, KVADDR, &machdep->machspec->hwintrstack[cpu], sizeof(ulong), "PPC64 HW_intr_stack", FAULT_ON_ERROR); } machdep->machspec->hwstacksize = 8 * machdep->pagesize; if ((machdep->machspec->hwstackbuf = (char *) malloc(machdep->machspec->hwstacksize)) == NULL) error(FATAL, "cannot malloc hwirqstack space."); } else /* * 'xHrdIntStack' member in "paca_struct" is not * available for 2.6 kernel. */ BZERO(&machdep->machspec->hwintrstack, NR_CPUS*sizeof(ulong)); if (!machdep->hz) { machdep->hz = HZ; if (THIS_KERNEL_VERSION >= LINUX(2,6,0)) machdep->hz = 1000; } /* * IRQ stacks are introduced in 2.6 and also configurable. */ if ((THIS_KERNEL_VERSION >= LINUX(2,6,0)) && symbol_exists("hardirq_ctx")) ASSIGN_SIZE(irq_ctx) = STACKSIZE(); break; case POST_INIT: break; case LOG_ONLY: machdep->identity_map_base = kt->vmcoreinfo._stext_SYMBOL; break; } } #ifndef KSYMS_START #define KSYMS_START 1 #endif static ulong ppc64_task_to_stackbase(ulong task) { ulong stackbase; if (tt->flags & THREAD_INFO_IN_TASK) { readmem(task + OFFSET(task_struct_stack), KVADDR, &stackbase, sizeof(void *), "task_struct.stack", FAULT_ON_ERROR); return stackbase; } else if (tt->flags & THREAD_INFO) return task_to_thread_info(task); else return task; } static ulong ppc64_get_stackbase(ulong task) { return ppc64_task_to_stackbase(task); } static ulong ppc64_get_stacktop(ulong task) { return ppc64_task_to_stackbase(task) + STACKSIZE(); } void ppc64_dump_machdep_table(ulong arg) { int i, c, others; others = 0; fprintf(fp, " flags: %lx (", machdep->flags); if (machdep->flags & KSYMS_START) fprintf(fp, "%sKSYMS_START", others++ ? "|" : ""); if (machdep->flags & MACHDEP_BT_TEXT) fprintf(fp, "%sMACHDEP_BT_TEXT", others++ ? "|" : ""); if (machdep->flags & VM_ORIG) fprintf(fp, "%sVM_ORIG", others++ ? "|" : ""); if (machdep->flags & VM_4_LEVEL) fprintf(fp, "%sVM_4_LEVEL", others++ ? "|" : ""); if (machdep->flags & VMEMMAP) fprintf(fp, "%sVMEMMAP", others++ ? "|" : ""); if (machdep->flags & VMEMMAP_AWARE) fprintf(fp, "%sVMEMMAP_AWARE", others++ ? "|" : ""); if (machdep->flags & BOOK3E) fprintf(fp, "%sBOOK3E", others++ ? "|" : ""); if (machdep->flags & PHYS_ENTRY_L4) fprintf(fp, "%sPHYS_ENTRY_L4", others++ ? "|" : ""); if (machdep->flags & SWAP_ENTRY_L4) fprintf(fp, "%sSWAP_ENTRY_L4", others++ ? "|" : ""); if (machdep->flags & RADIX_MMU) fprintf(fp, "%sRADIX_MMU", others++ ? "|" : ""); if (machdep->flags & OPAL_FW) fprintf(fp, "%sOPAL_FW", others++ ? "|" : ""); fprintf(fp, ")\n"); fprintf(fp, " kvbase: %lx\n", machdep->kvbase); fprintf(fp, " identity_map_base: %lx\n", machdep->identity_map_base); fprintf(fp, " pagesize: %d\n", machdep->pagesize); fprintf(fp, " pageshift: %d\n", machdep->pageshift); fprintf(fp, " pagemask: %llx\n", machdep->pagemask); fprintf(fp, " pageoffset: %lx\n", machdep->pageoffset); fprintf(fp, " stacksize: %ld\n", machdep->stacksize); fprintf(fp, " hz: %d\n", machdep->hz); fprintf(fp, " mhz: %ld\n", machdep->mhz); fprintf(fp, " memsize: %ld (0x%lx)\n", machdep->memsize, machdep->memsize); fprintf(fp, " bits: %d\n", machdep->bits); fprintf(fp, " nr_irqs: %d\n", machdep->nr_irqs); fprintf(fp, " eframe_search: ppc64_eframe_search()\n"); fprintf(fp, " back_trace: ppc64_back_trace_cmd()\n"); fprintf(fp, " processor_speed: ppc64_processor_speed()\n"); fprintf(fp, " uvtop: ppc64_uvtop()\n"); fprintf(fp, " kvtop: ppc64_kvtop()\n"); fprintf(fp, " get_task_pgd: ppc64_get_task_pgd()\n"); fprintf(fp, " dump_irq: ppc64_dump_irq()\n"); fprintf(fp, " get_stack_frame: ppc64_get_stack_frame()\n"); fprintf(fp, " get_stackbase: ppc64_get_stackbase()\n"); fprintf(fp, " get_stacktop: ppc64_get_stacktop()\n"); fprintf(fp, " translate_pte: ppc64_translate_pte()\n"); fprintf(fp, " memory_size: generic_memory_size()\n"); fprintf(fp, " vmalloc_start: ppc64_vmalloc_start()\n"); fprintf(fp, " is_task_addr: ppc64_is_task_addr()\n"); fprintf(fp, " verify_symbol: ppc64_verify_symbol()\n"); fprintf(fp, " dis_filter: ppc64_dis_filter()\n"); fprintf(fp, " cmd_mach: ppc64_cmd_mach()\n"); fprintf(fp, " get_smp_cpus: ppc64_get_smp_cpus()\n"); fprintf(fp, " is_kvaddr: %s\n", machdep->is_kvaddr == book3e_is_kvaddr ? "book3e_is_kvaddr()" : "generic_is_kvaddr()"); fprintf(fp, " is_uvaddr: generic_is_uvaddr()\n"); fprintf(fp, " verify_paddr: generic_verify_paddr()\n"); fprintf(fp, " get_kvaddr_ranges: ppc64_get_kvaddr_ranges()\n"); fprintf(fp, " get_irq_affinity: generic_get_irq_affinity()\n"); fprintf(fp, " show_interrupts: generic_show_interrupts()\n"); fprintf(fp, " xendump_p2m_create: NULL\n"); fprintf(fp, "xen_kdump_p2m_create: NULL\n"); fprintf(fp, " line_number_hooks: ppc64_line_number_hooks\n"); fprintf(fp, " last_pgd_read: %lx\n", machdep->last_pgd_read); fprintf(fp, " last_pud_read: %lx\n", machdep->last_pud_read); fprintf(fp, " last_pmd_read: %lx\n", machdep->last_pmd_read); fprintf(fp, " last_ptbl_read: %lx\n", machdep->last_ptbl_read); fprintf(fp, "clear_machdep_cache: ppc64_clear_machdep_cache()\n"); fprintf(fp, " pgd: %lx\n", (ulong)machdep->pgd); fprintf(fp, " pud: %lx\n", (ulong)machdep->pud); fprintf(fp, " pmd: %lx\n", (ulong)machdep->pmd); fprintf(fp, " ptbl: %lx\n", (ulong)machdep->ptbl); fprintf(fp, " ptrs_per_pgd: %d\n", machdep->ptrs_per_pgd); fprintf(fp, " section_size_bits: %ld\n", machdep->section_size_bits); fprintf(fp, " max_physmem_bits: %ld\n", machdep->max_physmem_bits); fprintf(fp, " sections_per_root: %ld\n", machdep->sections_per_root); for (i = 0; i < MAX_MACHDEP_ARGS; i++) { fprintf(fp, " cmdline_args[%d]: %s\n", i, machdep->cmdline_args[i] ? machdep->cmdline_args[i] : "(unused)"); } fprintf(fp, " machspec: %lx\n", (ulong)machdep->machspec); fprintf(fp, " is_kvaddr: %s\n", machdep->machspec->is_kvaddr == book3e_is_kvaddr ? "book3e_is_kvaddr()" : "generic_is_kvaddr()"); fprintf(fp, " is_vmaddr: %s\n", machdep->machspec->is_vmaddr == book3e_is_vmaddr ? "book3e_is_vmaddr()" : "ppc64_is_vmaddr()"); fprintf(fp, " hwintrstack[%d]: ", NR_CPUS); for (c = 0; c < NR_CPUS; c++) { for (others = 0, i = c; i < NR_CPUS; i++) { if (machdep->machspec->hwintrstack[i]) others++; } if (!others) { fprintf(fp, "%s%s", c && ((c % 4) == 0) ? "\n " : "", c ? "(remainder unused)" : "(unused)"); break; } fprintf(fp, "%s%016lx ", ((c % 4) == 0) ? "\n " : "", machdep->machspec->hwintrstack[c]); } fprintf(fp, "\n"); fprintf(fp, " hwstackbuf: %lx\n", (ulong)machdep->machspec->hwstackbuf); fprintf(fp, " hwstacksize: %d\n", machdep->machspec->hwstacksize); fprintf(fp, " l4_index_size: %d\n", machdep->machspec->l4_index_size); fprintf(fp, " l3_index_size: %d\n", machdep->machspec->l3_index_size); fprintf(fp, " l2_index_size: %d\n", machdep->machspec->l2_index_size); fprintf(fp, " l1_index_size: %d\n", machdep->machspec->l1_index_size); fprintf(fp, " ptrs_per_l4: %d\n", machdep->machspec->ptrs_per_l4); fprintf(fp, " ptrs_per_l3: %d\n", machdep->machspec->ptrs_per_l3); fprintf(fp, " ptrs_per_l2: %d\n", machdep->machspec->ptrs_per_l2); fprintf(fp, " ptrs_per_l1: %d\n", machdep->machspec->ptrs_per_l1); fprintf(fp, " l4_shift: %d\n", machdep->machspec->l4_shift); fprintf(fp, " l3_shift: %d\n", machdep->machspec->l3_shift); fprintf(fp, " l2_shift: %d\n", machdep->machspec->l2_shift); fprintf(fp, " l1_shift: %d\n", machdep->machspec->l1_shift); fprintf(fp, " pte_rpn_mask: %lx\n", machdep->machspec->pte_rpn_mask); fprintf(fp, " pte_rpn_shift: %d\n", machdep->machspec->pte_rpn_shift); fprintf(fp, " pgd_masked_bits: %lx\n", machdep->machspec->pgd_masked_bits); fprintf(fp, " pud_masked_bits: %lx\n", machdep->machspec->pud_masked_bits); fprintf(fp, " pmd_masked_bits: %lx\n", machdep->machspec->pmd_masked_bits); fprintf(fp, " vmemmap_base: "); if (machdep->machspec->vmemmap_base) fprintf(fp, "%lx\n", machdep->machspec->vmemmap_base); else fprintf(fp, "(unused)\n"); if (machdep->machspec->vmemmap_cnt) { fprintf(fp, " vmemmap_cnt: %d\n", machdep->machspec->vmemmap_cnt); fprintf(fp, " vmemmap_psize: %d\n", machdep->machspec->vmemmap_psize); for (i = 0; i < machdep->machspec->vmemmap_cnt; i++) { fprintf(fp, " vmemmap_list[%d]: virt: %lx phys: %lx\n", i, machdep->machspec->vmemmap_list[i].virt, machdep->machspec->vmemmap_list[i].phys); } } else { fprintf(fp, " vmemmap_cnt: (unused)\n"); fprintf(fp, " vmemmap_page_size: (unused)\n"); fprintf(fp, " vmemmap_list[]: (unused)\n"); } } /* * Virtual to physical memory translation. This function will be called * by both ppc64_kvtop and ppc64_uvtop. */ static int ppc64_vtop(ulong vaddr, ulong *pgd, physaddr_t *paddr, int verbose) { ulong *page_dir; ulong *page_middle; ulong *page_table; ulong pgd_pte, pmd_pte; ulong pte; if (verbose) fprintf(fp, "PAGE DIRECTORY: %lx\n", (ulong)pgd); if (THIS_KERNEL_VERSION < LINUX(2,6,0)) page_dir = (ulong *)((uint *)pgd + PGD_OFFSET_24(vaddr)); else page_dir = (ulong *)((uint *)pgd + PGD_OFFSET(vaddr)); FILL_PGD(PAGEBASE(pgd), KVADDR, PAGESIZE()); pgd_pte = UINT(machdep->pgd + PAGEOFFSET(page_dir)); if (verbose) fprintf(fp, " PGD: %lx => %lx\n", (ulong)page_dir, pgd_pte); if (!pgd_pte) return FALSE; pgd_pte <<= PAGESHIFT(); page_middle = (ulong *)((uint *)pgd_pte + PMD_OFFSET(vaddr)); FILL_PMD(PTOV(PAGEBASE(pgd_pte)), KVADDR, PAGESIZE()); pmd_pte = UINT(machdep->pmd + PAGEOFFSET(page_middle)); if (verbose) fprintf(fp, " PMD: %lx => %lx\n", (ulong)page_middle, pmd_pte); if (!(pmd_pte)) return FALSE; if (THIS_KERNEL_VERSION < LINUX(2,6,0)) pmd_pte <<= PAGESHIFT(); else pmd_pte = ((pmd_pte << PAGESHIFT()) >> PMD_TO_PTEPAGE_SHIFT); page_table = (ulong *)pmd_pte + (BTOP(vaddr) & (PTRS_PER_PTE - 1)); if (verbose) fprintf(fp, " PMD: %lx => %lx\n",(ulong)page_middle, (ulong)page_table); FILL_PTBL(PTOV(PAGEBASE(pmd_pte)), KVADDR, PAGESIZE()); pte = ULONG(machdep->ptbl + PAGEOFFSET(page_table)); if (verbose) fprintf(fp, " PTE: %lx => %lx\n", (ulong)page_table, pte); if (!(pte & _PAGE_PRESENT)) { if (pte && verbose) { fprintf(fp, "\n"); ppc64_translate_pte(pte, 0, PTE_RPN_SHIFT_DEFAULT); } return FALSE; } if (!pte) return FALSE; *paddr = PAGEBASE(PTOB(pte >> PTE_RPN_SHIFT_DEFAULT)) + PAGEOFFSET(vaddr); if (verbose) { fprintf(fp, " PAGE: %lx\n\n", PAGEBASE(*paddr)); ppc64_translate_pte(pte, 0, PTE_RPN_SHIFT_DEFAULT); } return TRUE; } /* * Virtual to physical memory translation. This function will be called * by both ppc64_kvtop and ppc64_uvtop. */ static int ppc64_vtop_level4(ulong vaddr, ulong *level4, physaddr_t *paddr, int verbose) { ulong *pgdir; ulong *page_upper; ulong *page_middle; ulong *page_table; ulong pgd_pte, pud_pte, pmd_pte; ulong pte; uint pdshift; uint hugepage_type = 0; /* 0: regular entry; 1: huge pte; 2: huge pd */ uint swap = !!(machdep->flags & SWAP_ENTRY_L4); if (verbose) fprintf(fp, "PAGE DIRECTORY: %lx\n", (ulong)level4); pgdir = (ulong *)((ulong *)level4 + PGD_OFFSET_L4(vaddr)); FILL_PGD(PAGEBASE(level4), KVADDR, PAGESIZE()); pgd_pte = swap64(ULONG(machdep->pgd + PAGEOFFSET(pgdir)), swap); if (verbose) fprintf(fp, " PGD: %lx => %lx\n", (ulong)pgdir, pgd_pte); if (!pgd_pte) return FALSE; hugepage_type = get_ptetype(pgd_pte); if (hugepage_type) { pte = pgd_pte; pdshift = machdep->machspec->l4_shift; goto out; } /* Sometimes we don't have level3 pagetable entries */ if (machdep->machspec->l3_index_size != 0) { pgd_pte = pgd_page_vaddr_l4(pgd_pte); page_upper = (ulong *)((ulong *)pgd_pte + PUD_OFFSET_L4(vaddr)); FILL_PUD(PAGEBASE(pgd_pte), KVADDR, PAGESIZE()); pud_pte = swap64(ULONG(machdep->pud + PAGEOFFSET(page_upper)), swap); if (verbose) fprintf(fp, " PUD: %lx => %lx\n", (ulong)page_upper, pud_pte); if (!pud_pte) return FALSE; hugepage_type = get_ptetype(pud_pte); if (hugepage_type) { pte = pud_pte; pdshift = machdep->machspec->l3_shift; goto out; } } else { pud_pte = pgd_pte; } pud_pte = pud_page_vaddr_l4(pud_pte); page_middle = (ulong *)((ulong *)pud_pte + PMD_OFFSET_L4(vaddr)); FILL_PMD(PAGEBASE(pud_pte), KVADDR, PAGESIZE()); pmd_pte = swap64(ULONG(machdep->pmd + PAGEOFFSET(page_middle)), swap); if (verbose) fprintf(fp, " PMD: %lx => %lx\n", (ulong)page_middle, pmd_pte); if (!(pmd_pte)) return FALSE; hugepage_type = get_ptetype(pmd_pte); if (hugepage_type) { pte = pmd_pte; pdshift = machdep->machspec->l2_shift; goto out; } pmd_pte = pmd_page_vaddr_l4(pmd_pte); page_table = (ulong *)(pmd_pte) + (BTOP(vaddr) & (machdep->machspec->ptrs_per_l1 - 1)); if (verbose) fprintf(fp, " PMD: %lx => %lx\n",(ulong)page_middle, (ulong)page_table); FILL_PTBL(PAGEBASE(pmd_pte), KVADDR, PAGESIZE()); pte = swap64(ULONG(machdep->ptbl + PAGEOFFSET(page_table)), swap); if (verbose) fprintf(fp, " PTE: %lx => %lx\n", (ulong)page_table, pte); if (!(pte & _PAGE_PRESENT)) { if (pte && verbose) { fprintf(fp, "\n"); ppc64_translate_pte(pte, 0, machdep->machspec->pte_rpn_shift); } return FALSE; } if (!pte) return FALSE; out: if (hugepage_type) { if (hugepage_type == 2) { /* TODO: Calculate the offset within the huge page * directory for this huge page to get corresponding * physical address. In the current form, it may * return the physical address of the first huge page * in this directory for all the huge pages * in this huge page directory. */ ulong hugepd = hugepage_dir(pte); readmem(hugepd, KVADDR, &pte, sizeof(pte), "hugepd_entry", RETURN_ON_ERROR); if (verbose) fprintf(fp, " HUGE PD: %lx => %lx\n", hugepd, pte); if (!pte) return FALSE; } *paddr = PAGEBASE(PTOB((pte & PTE_RPN_MASK) >> PTE_RPN_SHIFT)) + (vaddr & ((1UL << pdshift) - 1)); } else { *paddr = PAGEBASE(PTOB((pte & PTE_RPN_MASK) >> PTE_RPN_SHIFT)) + PAGEOFFSET(vaddr); } if (verbose) { if (hugepage_type) fprintf(fp, " HUGE PAGE: %lx\n\n", PAGEBASE(*paddr)); else fprintf(fp, " PAGE: %lx\n\n", PAGEBASE(*paddr)); ppc64_translate_pte(pte, 0, machdep->machspec->pte_rpn_shift); } return TRUE; } /* * Translates a user virtual address to its physical address. cmd_vtop() * sets the verbose flag so that the pte translation gets displayed; all * other callers quietly accept the translation. * * This routine can also take mapped kernel virtual addresses if the -u flag * was passed to cmd_vtop(). If so, it makes the translation using the * kernel-memory PGD entry instead of swapper_pg_dir. */ static int ppc64_uvtop(struct task_context *tc, ulong vaddr, physaddr_t *paddr, int verbose) { ulong mm, active_mm; ulong *pgd; if (!tc) error(FATAL, "current context invalid\n"); *paddr = 0; if (is_kernel_thread(tc->task) && IS_KVADDR(vaddr)) { if (VALID_MEMBER(thread_struct_pg_tables)) pgd = (ulong *)machdep->get_task_pgd(tc->task); else { if (INVALID_MEMBER(task_struct_active_mm)) error(FATAL, "no pg_tables or active_mm?\n"); readmem(tc->task + OFFSET(task_struct_active_mm), KVADDR, &active_mm, sizeof(void *), "task active_mm contents", FAULT_ON_ERROR); if (!active_mm) error(FATAL, "no active_mm for this kernel thread\n"); readmem(active_mm + OFFSET(mm_struct_pgd), KVADDR, &pgd, sizeof(long), "mm_struct pgd", FAULT_ON_ERROR); } } else { if ((mm = task_mm(tc->task, TRUE))) pgd = ULONG_PTR(tt->mm_struct + OFFSET(mm_struct_pgd)); else readmem(tc->mm_struct + OFFSET(mm_struct_pgd), KVADDR, &pgd, sizeof(long), "mm_struct pgd", FAULT_ON_ERROR); } if (machdep->flags & VM_4_LEVEL) return ppc64_vtop_level4(vaddr, pgd, paddr, verbose); else return ppc64_vtop(vaddr, pgd, paddr, verbose); } /* * Translates a kernel virtual address to its physical address. cmd_vtop() * sets the verbose flag so that the pte translation gets displayed; all * other callers quietly accept the translation. */ static int ppc64_kvtop(struct task_context *tc, ulong kvaddr, physaddr_t *paddr, int verbose) { if (!IS_KVADDR(kvaddr)) return FALSE; if ((machdep->flags & VMEMMAP) && (kvaddr >= machdep->machspec->vmemmap_base)) return ppc64_vmemmap_to_phys(kvaddr, paddr, verbose); if (!vt->vmalloc_start) { *paddr = VTOP(kvaddr); return TRUE; } if (!IS_VMALLOC_ADDR(kvaddr)) { *paddr = VTOP(kvaddr); if (!verbose) return TRUE; } if (machdep->flags & VM_4_LEVEL) return ppc64_vtop_level4(kvaddr, (ulong *)vt->kernel_pgd[0], paddr, verbose); else return ppc64_vtop(kvaddr, (ulong *)vt->kernel_pgd[0], paddr, verbose); } /* * Verify that the kernel has made the vmemmap list available, * and if so, stash the relevant data required to make vtop * translations. */ static void ppc64_vmemmap_init(void) { int i, psize, shift, cnt; struct list_data list_data, *ld; long backing_size, virt_addr_offset, phys_offset, list_offset; ulong *vmemmap_list; char *vmemmap_buf; struct machine_specific *ms; if (!(kernel_symbol_exists("vmemmap_list")) || !(kernel_symbol_exists("mmu_psize_defs")) || !(kernel_symbol_exists("mmu_vmemmap_psize")) || !STRUCT_EXISTS("vmemmap_backing") || !STRUCT_EXISTS("mmu_psize_def") || !MEMBER_EXISTS("mmu_psize_def", "shift") || !MEMBER_EXISTS("vmemmap_backing", "phys") || !MEMBER_EXISTS("vmemmap_backing", "virt_addr") || !MEMBER_EXISTS("vmemmap_backing", "list")) return; ms = machdep->machspec; backing_size = STRUCT_SIZE("vmemmap_backing"); virt_addr_offset = MEMBER_OFFSET("vmemmap_backing", "virt_addr"); phys_offset = MEMBER_OFFSET("vmemmap_backing", "phys"); list_offset = MEMBER_OFFSET("vmemmap_backing", "list"); if (!readmem(symbol_value("mmu_vmemmap_psize"), KVADDR, &psize, sizeof(int), "mmu_vmemmap_psize", RETURN_ON_ERROR)) return; if (!readmem(symbol_value("mmu_psize_defs") + (STRUCT_SIZE("mmu_psize_def") * psize) + MEMBER_OFFSET("mmu_psize_def", "shift"), KVADDR, &shift, sizeof(int), "mmu_psize_def shift", RETURN_ON_ERROR)) return; ms->vmemmap_psize = 1 << shift; ld = &list_data; BZERO(ld, sizeof(struct list_data)); if (!readmem(symbol_value("vmemmap_list"), KVADDR, &ld->start, sizeof(void *), "vmemmap_list", RETURN_ON_ERROR)) return; ld->end = symbol_value("vmemmap_list"); ld->list_head_offset = list_offset; hq_open(); cnt = do_list(ld); vmemmap_list = (ulong *)GETBUF(cnt * sizeof(ulong)); cnt = retrieve_list(vmemmap_list, cnt); hq_close(); if ((ms->vmemmap_list = (struct ppc64_vmemmap *)malloc(cnt * sizeof(struct ppc64_vmemmap))) == NULL) error(FATAL, "cannot malloc vmemmap list space"); vmemmap_buf = GETBUF(backing_size); for (i = 0; i < cnt; i++) { if (!readmem(vmemmap_list[i], KVADDR, vmemmap_buf, backing_size, "vmemmap_backing", RETURN_ON_ERROR)) { free(ms->vmemmap_list); goto out; } ms->vmemmap_list[i].phys = ULONG(vmemmap_buf + phys_offset); ms->vmemmap_list[i].virt = ULONG(vmemmap_buf + virt_addr_offset); if (ms->vmemmap_list[i].virt < ms->vmemmap_base) ms->vmemmap_base = ms->vmemmap_list[i].virt; } ms->vmemmap_cnt = cnt; machdep->flags |= VMEMMAP_AWARE; if (CRASHDEBUG(1)) fprintf(fp, "ppc64_vmemmap_init: vmemmap base: %lx\n", ms->vmemmap_base); out: FREEBUF(vmemmap_buf); FREEBUF(vmemmap_list); } /* * If the vmemmap address translation information is stored in the kernel, * make the translation. */ static int ppc64_vmemmap_to_phys(ulong kvaddr, physaddr_t *paddr, int verbose) { int i; ulong offset; struct machine_specific *ms; if (!(machdep->flags & VMEMMAP_AWARE)) { /* * During runtime, just fail the command. */ if (vt->flags & VM_INIT) error(FATAL, "cannot translate vmemmap address: %lx\n", kvaddr); /* * During vm_init() initialization, print a warning message. */ error(WARNING, "cannot translate vmemmap kernel virtual addresses:\n" " commands requiring page structure contents" " will fail\n\n"); return FALSE; } ms = machdep->machspec; for (i = 0; i < ms->vmemmap_cnt; i++) { if ((kvaddr >= ms->vmemmap_list[i].virt) && (kvaddr < (ms->vmemmap_list[i].virt + ms->vmemmap_psize))) { offset = kvaddr - ms->vmemmap_list[i].virt; *paddr = ms->vmemmap_list[i].phys + offset; return TRUE; } } return FALSE; } /* * Determine where vmalloc'd memory starts. */ static ulong ppc64_vmalloc_start(void) { return (first_vmalloc_address()); } /* * */ static int ppc64_is_task_addr(ulong task) { int i; if (tt->flags & THREAD_INFO) return IS_KVADDR(task); else if (IS_KVADDR(task) && (ALIGNED_STACK_OFFSET(task) == 0)) return TRUE; for (i = 0; i < kt->cpus; i++) if (task == tt->idle_threads[i]) return TRUE; return FALSE; } /* * */ static ulong ppc64_processor_speed(void) { ulong res, value, ppc_md, md_setup_res; ulong prep_setup_res; ulong node, type, name, properties; char str_buf[32]; uint len; ulong mhz = 0; if (machdep->mhz) return(machdep->mhz); if (symbol_exists("ppc_proc_freq")) { get_symbol_data("ppc_proc_freq", sizeof(ulong), &mhz); mhz /= 1000000; return (machdep->mhz = mhz); } if(symbol_exists("allnodes")) { get_symbol_data("allnodes", sizeof(void *), &node); while(node) { readmem(node+OFFSET(device_node_type), KVADDR, &type, sizeof(ulong), "node type", FAULT_ON_ERROR); if(type != 0) { len = read_string(type, str_buf, sizeof(str_buf)); if(len && (strcasecmp(str_buf, "cpu") == 0)) break; } readmem(node+OFFSET(device_node_allnext), KVADDR, &node, sizeof(ulong), "node allnext", FAULT_ON_ERROR); } /* now, if we found a CPU node, get the speed property */ if(node) { readmem(node+OFFSET(device_node_properties), KVADDR, &properties, sizeof(ulong), "node properties", FAULT_ON_ERROR); while(properties) { readmem(properties+OFFSET(property_name), KVADDR, &name, sizeof(ulong), "property name", FAULT_ON_ERROR); len = read_string(name, str_buf, sizeof(str_buf)); if (len && (strcasecmp(str_buf, "clock-frequency") == 0)) { /* found the right cpu property */ readmem(properties+ OFFSET(property_value), KVADDR, &value, sizeof(ulong), "clock freqency pointer", FAULT_ON_ERROR); readmem(value, KVADDR, &mhz, sizeof(int), "clock frequency value", FAULT_ON_ERROR); mhz /= 1000000; break; } else if(len && (strcasecmp(str_buf, "ibm,extended-clock-frequency") == 0)){ /* found the right cpu property */ readmem(properties+ OFFSET(property_value), KVADDR, &value, sizeof(ulong), "clock freqency pointer", FAULT_ON_ERROR); readmem(value, KVADDR, &mhz, sizeof(ulong), "clock frequency value", FAULT_ON_ERROR); mhz /= 1000000; break; } /* keep looking */ readmem(properties+ OFFSET(property_next), KVADDR, &properties, sizeof(ulong), "property next", FAULT_ON_ERROR); } if(!properties) { /* didn't find the cpu speed for some reason */ return (machdep->mhz = 0); } } } /* for machines w/o OF */ /* untested, but in theory this should work on prep machines */ if (symbol_exists("res") && !mhz) { get_symbol_data("res", sizeof(void *), &res); if (symbol_exists("prep_setup_residual")) { get_symbol_data("prep_setup_residual", sizeof(void *), &prep_setup_res); get_symbol_data("ppc_md", sizeof(void *), &ppc_md); readmem(ppc_md + OFFSET(machdep_calls_setup_residual), KVADDR, &md_setup_res, sizeof(ulong), "ppc_md setup_residual", FAULT_ON_ERROR); if(prep_setup_res == md_setup_res) { /* PREP machine */ readmem(res+ OFFSET(RESIDUAL_VitalProductData)+ OFFSET(VPD_ProcessorHz), KVADDR, &mhz, sizeof(ulong), "res VitalProductData", FAULT_ON_ERROR); mhz = (mhz > 1024) ? mhz >> 20 : mhz; } } if(!mhz) { /* everything else seems to do this the same way... */ readmem(res + OFFSET(bd_info_bi_intfreq), KVADDR, &mhz, sizeof(ulong), "bd_info bi_intfreq", FAULT_ON_ERROR); mhz /= 1000000; } } /* else...well, we don't have OF, or a residual structure, so * just print unknown MHz */ return (machdep->mhz = (ulong)mhz); } /* * Accept or reject a symbol from the kernel namelist. */ static int ppc64_verify_symbol(const char *name, ulong value, char type) { if (CRASHDEBUG(8) && name && strlen(name)) fprintf(fp, "%08lx %s\n", value, name); if (STREQ(name, "_start") || STREQ(name, "_stext")) machdep->flags |= KSYMS_START; return (name && strlen(name) && (machdep->flags & KSYMS_START) && !STREQ(name, "Letext") && !STRNEQ(name, "__func__.")); } /* * Get the relevant page directory pointer from a task structure. */ static ulong ppc64_get_task_pgd(ulong task) { long offset; ulong pg_tables; offset = VALID_MEMBER(task_struct_thread) ? OFFSET(task_struct_thread) : OFFSET(task_struct_tss); if (INVALID_MEMBER(thread_struct_pg_tables)) error(FATAL, "pg_tables does not exist in this kernel's thread_struct\n"); offset += OFFSET(thread_struct_pg_tables); readmem(task + offset, KVADDR, &pg_tables, sizeof(ulong), "task thread pg_tables", FAULT_ON_ERROR); return(pg_tables); } /* * Translate a PTE, returning TRUE if the page is present. * If a physaddr pointer is passed in, don't print anything. */ static int ppc64_translate_pte(ulong pte, void *physaddr, ulonglong pte_rpn_shift) { int c, len1, len2, len3, others, page_present; char buf[BUFSIZE]; char buf2[BUFSIZE]; char buf3[BUFSIZE]; char ptebuf[BUFSIZE]; char physbuf[BUFSIZE]; char *arglist[MAXARGS]; ulong paddr; if (STREQ(pc->curcmd, "pte")) pte_rpn_shift = machdep->machspec->pte_rpn_shift; paddr = PTOB(pte >> pte_rpn_shift); page_present = !!(pte & _PAGE_PRESENT); if (physaddr) { *((ulong *)physaddr) = paddr; return page_present; } sprintf(ptebuf, "%lx", pte); len1 = MAX(strlen(ptebuf), strlen("PTE")); if (!page_present && pte) { swap_location(pte, buf); if ((c = parse_line(buf, arglist)) != 3) error(FATAL, "cannot determine swap location\n"); fprintf(fp, "%s ", mkstring(buf2, len1, CENTER|LJUST, "PTE")); len2 = MAX(strlen(arglist[0]), strlen("SWAP")); len3 = MAX(strlen(arglist[2]), strlen("OFFSET")); fprintf(fp, "%s %s\n", mkstring(buf2, len2, CENTER|LJUST, "SWAP"), mkstring(buf3, len3, CENTER|LJUST, "OFFSET")); strcpy(buf2, arglist[0]); strcpy(buf3, arglist[2]); fprintf(fp, "%s %s %s\n", mkstring(ptebuf, len1, CENTER|RJUST, NULL), mkstring(buf2, len2, CENTER|RJUST, NULL), mkstring(buf3, len3, CENTER|RJUST, NULL)); return page_present; } fprintf(fp, "%s ", mkstring(buf, len1, CENTER|LJUST, "PTE")); sprintf(physbuf, "%lx", paddr); len2 = MAX(strlen(physbuf), strlen("PHYSICAL")); fprintf(fp, "%s ", mkstring(buf, len2, CENTER|LJUST, "PHYSICAL")); fprintf(fp, "FLAGS\n"); fprintf(fp, "%s %s ", mkstring(ptebuf, len1, CENTER|RJUST, NULL), mkstring(physbuf, len2, CENTER|RJUST, NULL)); fprintf(fp, "("); others = 0; if (pte) { if (pte & _PAGE_PTE) fprintf(fp, "%sPTE", others++ ? "|" : ""); if (pte & _PAGE_PRESENT) fprintf(fp, "%sPRESENT", others++ ? "|" : ""); if (pte & _PAGE_USER) fprintf(fp, "%sUSER", others++ ? "|" : ""); if (pte & _PAGE_RW) fprintf(fp, "%sRW", others++ ? "|" : ""); if (pte & _PAGE_GUARDED) fprintf(fp, "%sGUARDED", others++ ? "|" : ""); if (pte & _PAGE_COHERENT) fprintf(fp, "%sCOHERENT", others++ ? "|" : ""); if (pte & _PAGE_NO_CACHE) fprintf(fp, "%sNO_CACHE", others++ ? "|" : ""); if (pte & _PAGE_WRITETHRU) fprintf(fp, "%sWRITETHRU", others++ ? "|" : ""); if (pte & _PAGE_DIRTY) fprintf(fp, "%sDIRTY", others++ ? "|" : ""); if (pte & _PAGE_ACCESSED) fprintf(fp, "%sACCESSED", others++ ? "|" : ""); } else fprintf(fp, "no mapping"); fprintf(fp, ")\n"); return page_present; } /* * The user specified SP could be in HW interrupt stack for tasks running on * other CPUs. Hence, get the SP which is in process's stack. */ static ulong ppc64_check_sp_in_HWintrstack(ulong sp, struct bt_info *bt) { /* * Since the seperate HW Interrupt stack is involved to store * IPI frames, printing all stack symbols or searching for exception * frames for running tasks on other CPUS is tricky. The simple * solution is - ignore HW intr stack and search in the process stack. * Anyway the user will be interested only frames that are * involved before receiving CALL_FUNCTION_IPI. * So, if the SP is not within the stack, read the top value * from the HW Interrupt stack which is the SP points to top * frame in the process's stack. * * Note: HW Interrupt stack is used only in 2.4 kernel. */ if (is_task_active(bt->task) && (tt->panic_task != bt->task) && machdep->machspec->hwintrstack[bt->tc->processor]) { ulong newsp; readmem(machdep->machspec->hwintrstack[bt->tc->processor], KVADDR, &newsp, sizeof(ulong), "stack pointer", FAULT_ON_ERROR); if (INSTACK(newsp, bt)) sp = newsp; } return sp; } /* * Look for likely exception frames in a stack. */ static int ppc64_eframe_search(struct bt_info *bt_in) { ulong addr; struct bt_info bt_local, *bt; ulong *stack, *first, *last; ulong irqstack; char *mode; ulong eframe_addr; int c, cnt; struct ppc64_pt_regs *regs; bt = bt_in; if (bt->flags & BT_EFRAME_SEARCH2) { if (!(tt->flags & IRQSTACKS)) { error(INFO, "This kernel does not have IRQ stacks\n"); return 0; } BCOPY(bt_in, &bt_local, sizeof(struct bt_info)); bt = &bt_local; bt->flags &= ~(ulonglong)BT_EFRAME_SEARCH2; for (c = 0; c < NR_CPUS; c++) { if (tt->hardirq_ctx[c]) { if ((bt->flags & BT_CPUMASK) && !(NUM_IN_BITMAP(bt->cpumask, c))) continue; bt->hp->esp = tt->hardirq_ctx[c]; fprintf(fp, "CPU %d HARD IRQ STACK:\n", c); if ((cnt = ppc64_eframe_search(bt))) fprintf(fp, "\n"); else fprintf(fp, "(none found)\n\n"); } } for (c = 0; c < NR_CPUS; c++) { if (tt->softirq_ctx[c]) { if ((bt->flags & BT_CPUMASK) && !(NUM_IN_BITMAP(bt->cpumask, c))) continue; bt->hp->esp = tt->softirq_ctx[c]; fprintf(fp, "CPU %d SOFT IRQ STACK:\n", c); if ((cnt = ppc64_eframe_search(bt))) fprintf(fp, "\n"); else fprintf(fp, "(none found)\n\n"); } } return 0; } if (bt->hp && bt->hp->esp) { BCOPY(bt_in, &bt_local, sizeof(struct bt_info)); bt = &bt_local; addr = bt->hp->esp; if ((irqstack = ppc64_in_irqstack(addr))) { bt->stackbase = irqstack; bt->stacktop = irqstack + STACKSIZE(); alter_stackbuf(bt); addr = bt->stackbase + roundup(SIZE(thread_info), sizeof(ulong)); } else if (!INSTACK(addr, bt)) { /* * If the user specified SP is in HW interrupt stack * (only for tasks running on other CPUs and in 2.4 * kernel), get the top SP points to process's stack. */ addr = ppc64_check_sp_in_HWintrstack(addr, bt); if (!INSTACK(addr, bt)) error(FATAL, "unrecognized stack address for this task: %lx\n", addr); } } else if (tt->flags & THREAD_INFO) addr = bt->stackbase + roundup(SIZE(thread_info), sizeof(ulong)); else addr = bt->stackbase + roundup(SIZE(task_struct), sizeof(ulong)); if (!INSTACK(addr, bt)) return(0); stack = (ulong *)bt->stackbuf; first = stack + ((addr - bt->stackbase) / sizeof(ulong)); last = stack + (((bt->stacktop - bt->stackbase) - SIZE(pt_regs)) / sizeof(ulong)); for ( ; first <= last; first++) { char *efrm_str = NULL; eframe_addr = bt->stackbase + sizeof(ulong) * (first - stack); if (THIS_KERNEL_VERSION < LINUX(2,6,0)) { regs = (struct ppc64_pt_regs *)first; if (!IS_KVADDR(regs->gpr[1]) || !IS_KVADDR(regs->nip) || !is_kernel_text(regs->nip)) if (!IS_UVADDR(regs->gpr[1], bt->tc) || !IS_UVADDR(regs->nip, bt->tc)) continue; } else { /* * In 2.6 or later, 0x7265677368657265 is saved in the * stack (sp + 96) for the exception frame. Also, * pt_regs will be saved at sp + 112. * Hence, once we know the location of exception marker * in the stack, pt_regs is saved at * - 96 + 112. ==> first + 16. */ if (*first == EXCP_FRAME_MARKER) { ulong *sp; /* * SP points to - 96/8; */ sp = (ulong *)(first - 12); if (!IS_KVADDR(*sp)) if (!IS_UVADDR(*sp, bt->tc)) continue; first = (ulong *)((char *)first + 16); regs = (struct ppc64_pt_regs *)first; } else continue; } if ((efrm_str = ppc64_check_eframe(regs)) != NULL) { if ((((regs)->msr) >> MSR_PR_LG) & 0x1) mode = "USER-MODE"; else mode = "KERNEL-MODE"; fprintf(fp, "%s %s EXCEPTION FRAME AT %lx:\n", bt->flags & BT_EFRAME_SEARCH ? "\n" : "", mode, eframe_addr); ppc64_print_eframe(efrm_str, regs, bt); } } return 0; } static ulong ppc64_in_irqstack(ulong addr) { int c; if (!(tt->flags & IRQSTACKS)) return 0; for (c = 0; c < NR_CPUS; c++) { if (tt->hardirq_ctx[c]) { if ((addr >= tt->hardirq_ctx[c]) && (addr < (tt->hardirq_ctx[c] + SIZE(irq_ctx)))) return(tt->hardirq_ctx[c]); } if (tt->softirq_ctx[c]) { if ((addr >= tt->softirq_ctx[c]) && (addr < (tt->softirq_ctx[c] + SIZE(irq_ctx)))) return(tt->softirq_ctx[c]); } } return 0; } /* * Unroll a kernel stack. */ static void ppc64_back_trace_cmd(struct bt_info *bt) { char buf[BUFSIZE]; struct gnu_request *req; extern void print_stack_text_syms(struct bt_info *, ulong, ulong); bt->flags |= BT_EXCEPTION_FRAME; if (CRASHDEBUG(1) || bt->debug) fprintf(fp, " => PC: %lx (%s) FP: %lx \n", bt->instptr, value_to_symstr(bt->instptr, buf, 0), bt->stkptr); req = (struct gnu_request *)GETBUF(sizeof(struct gnu_request)); req->command = GNU_STACK_TRACE; req->flags = GNU_RETURN_ON_ERROR; req->buf = GETBUF(BUFSIZE); req->debug = bt->debug; req->task = bt->task; req->pc = bt->instptr; req->sp = bt->stkptr; if (bt->flags & (BT_TEXT_SYMBOLS|BT_TEXT_SYMBOLS_PRINT|BT_TEXT_SYMBOLS_NOPRINT)) { if (!INSTACK(req->sp, bt)) /* * If the user specified SP is in HW interrupt stack * (only for tasks running on other CPUs and in 2.4 * kernel), get the top SP points to process's stack. */ req->sp = ppc64_check_sp_in_HWintrstack(req->sp, bt); print_stack_text_syms(bt, req->sp, req->pc); } else { if (bt->flags & BT_USE_GDB) { strcpy(req->buf, "backtrace"); gdb_interface(req); } else ppc64_back_trace(req, bt); } FREEBUF(req->buf); FREEBUF(req); } /* * Unroll the kernel stack using a minimal amount of gdb services. * * (Ref: 64-bit PowerPC ELF ABI Spplement; Ian Lance Taylor, Zembu Labs). * A PPC64 stack frame looks like this: * * High Address * .-> Back Chain (etc...) * | FP reg save area * | GP reg save area * | Local var space * | Parameter save area (SP+48) * | TOC save area (SP+40) * | link editor doubleword (SP+32) * | compiler doubleword (SP+24) * | LR save (SP+16) * | CR save (SP+8) * `- Back Chain <-- sp (SP+0) * * Note that the LR (ret addr) may not be saved in the current frame if * no functions have been called from the current function. */ /* HACK: put an initial lr in this var for find_trace(). It will be * cleared during the trace. */ static void ppc64_back_trace(struct gnu_request *req, struct bt_info *bt) { int frame = 0; ulong lr = 0; /* hack...need to pass in initial lr reg */ ulong newpc = 0, newsp, marker; int eframe_found; if (!INSTACK(req->sp, bt)) { ulong irqstack; struct machine_specific *ms = machdep->machspec; if ((irqstack = ppc64_in_irqstack(req->sp))) { bt->stackbase = irqstack; bt->stacktop = bt->stackbase + STACKSIZE(); alter_stackbuf(bt); } else if (ms->hwintrstack[bt->tc->processor]) { bt->stacktop = ms->hwintrstack[bt->tc->processor] + sizeof(ulong); bt->stackbase = ms->hwintrstack[bt->tc->processor] - ms->hwstacksize + STACK_FRAME_OVERHEAD; bt->stackbuf = ms->hwstackbuf; alter_stackbuf(bt); } else { if (CRASHDEBUG(1)) { fprintf(fp, "cannot find the stack info.\n"); } return; } } while (INSTACK(req->sp, bt)) { newsp = *(ulong *)&bt->stackbuf[req->sp - bt->stackbase]; if ((req->name = closest_symbol(req->pc)) == NULL) { if (CRASHDEBUG(1)) { error(FATAL, "ppc64_back_trace hit unknown symbol (%lx).\n", req->pc); } } bt->flags |= BT_SAVE_LASTSP; ppc64_print_stack_entry(frame, req, newsp, lr, bt); bt->flags &= ~(ulonglong)BT_SAVE_LASTSP; lr = 0; if (IS_KVADDR(newsp)) { /* * In 2.4, HW interrupt stack will be used to save * smp_call_functions symbols. i.e, when the dumping * CPU is issued IPI call to freeze other CPUS, */ if (INSTACK(newsp, bt) && (newsp + 16 > bt->stacktop)) newsp = *(ulong *)&bt->stackbuf[newsp - bt->stackbase]; if (!INSTACK(newsp, bt)) { /* * Switch HW interrupt stack to process's stack. */ bt->stackbase = GET_STACKBASE(bt->task); bt->stacktop = GET_STACKTOP(bt->task); alter_stackbuf(bt); } if (IS_KVADDR(newsp) && INSTACK(newsp, bt)) newpc = *(ulong *)&bt->stackbuf[newsp + 16 - bt->stackbase]; } if (BT_REFERENCE_FOUND(bt)) return; eframe_found = FALSE; /* * Is this frame an execption one? * In 2.6, 0x7265677368657265 is saved and used * to determine the execption frame. */ if (THIS_KERNEL_VERSION < LINUX(2,6,0)) { if (frame && (newsp - req->sp - STACK_FRAME_OVERHEAD) >= sizeof(struct ppc64_pt_regs)) eframe_found = TRUE; else if (STREQ(req->name, ".ret_from_except")) eframe_found = TRUE; } else if ((newsp - req->sp - STACK_FRAME_OVERHEAD) >= sizeof(struct ppc64_pt_regs)) { readmem(req->sp+0x60, KVADDR, &marker, sizeof(ulong), "stack frame", FAULT_ON_ERROR); if (marker == EXCP_FRAME_MARKER) eframe_found = TRUE; } if (eframe_found) { char *efrm_str = NULL; struct ppc64_pt_regs regs; readmem(req->sp+STACK_FRAME_OVERHEAD, KVADDR, ®s, sizeof(struct ppc64_pt_regs), "exception frame", FAULT_ON_ERROR); efrm_str = ppc64_check_eframe(®s); if (efrm_str) { ppc64_print_eframe(efrm_str, ®s, bt); lr = regs.link; newpc = regs.nip; newsp = regs.gpr[1]; } } /* * Some Linux 3.7 kernel threads have been seen to have * their end-of-trace stack linkage pointer pointing * back to itself (instead of NULL), which would cause * an infinite loop at the .ret_from_kernel_thread frame. */ if (req->sp == newsp) break; req->pc = newpc; req->sp = newsp; frame++; } } static void ppc64_display_full_frame(struct bt_info *bt, ulong nextsp, FILE *ofp) { int i, u_idx; ulong *nip; ulong words, addr; char buf[BUFSIZE]; if (!INSTACK(nextsp, bt)) nextsp = bt->stacktop; words = (nextsp - bt->frameptr) / sizeof(ulong); addr = bt->frameptr; u_idx = (bt->frameptr - bt->stackbase)/sizeof(ulong); for (i = 0; i < words; i++, u_idx++) { if (!(i & 1)) fprintf(ofp, "%s %lx: ", i ? "\n" : "", addr); nip = (ulong *)(&bt->stackbuf[u_idx*sizeof(ulong)]); fprintf(ofp, "%s ", format_stack_entry(bt, buf, *nip, 0)); addr += sizeof(ulong); } fprintf(ofp, "\n"); } /* * print one entry of a stack trace */ static void ppc64_print_stack_entry(int frame, struct gnu_request *req, ulong newsp, ulong lr, struct bt_info *bt) { struct load_module *lm; char *lrname = NULL; ulong offset; struct syment *sp; char *name_plus_offset; char buf[BUFSIZE]; if (BT_REFERENCE_CHECK(bt)) { switch (bt->ref->cmdflags & (BT_REF_SYMBOL|BT_REF_HEXVAL)) { case BT_REF_SYMBOL: if (STREQ(req->name, bt->ref->str)) bt->ref->cmdflags |= BT_REF_FOUND; break; case BT_REF_HEXVAL: if (bt->ref->hexval == req->pc) bt->ref->cmdflags |= BT_REF_FOUND; break; } } else { name_plus_offset = NULL; if (bt->flags & BT_SYMBOL_OFFSET) { sp = value_search(req->pc, &offset); if (sp && offset) name_plus_offset = value_to_symstr(req->pc, buf, bt->radix); } fprintf(fp, "%s#%d [%lx] %s at %lx", frame < 10 ? " " : "", frame, req->sp, name_plus_offset ? name_plus_offset : req->name, req->pc); if (module_symbol(req->pc, NULL, &lm, NULL, 0)) fprintf(fp, " [%s]", lm->mod_name); if (req->ra) { /* * Previous frame is an exception one. If the func * symbol for the current frame is same as with * the previous frame's LR value, print "(unreliable)". */ lrname = closest_symbol(req->ra); req->ra = 0; if (!lrname) { if (CRASHDEBUG(1)) error(FATAL, "ppc64_back_trace hit unknown symbol (%lx).\n", req->ra); return; } } if (lr) { /* * Link register value for an expection frame. */ if ((lrname = closest_symbol(lr)) == NULL) { if (CRASHDEBUG(1)) error(FATAL, "ppc64_back_trace hit unknown symbol (%lx).\n", lr); return; } req->ra = lr; } if (!req->name || STREQ(req->name, lrname) || !is_kernel_text(req->pc)) fprintf(fp, " (unreliable)"); fprintf(fp, "\n"); } if (bt->flags & BT_SAVE_LASTSP) req->lastsp = req->sp; bt->frameptr = req->sp; if (bt->flags & BT_FULL) if (IS_KVADDR(newsp)) ppc64_display_full_frame(bt, newsp, fp); if (bt->flags & BT_LINE_NUMBERS) ppc64_dump_line_number(req->pc); } /* * Check whether the frame is exception one! */ static char * ppc64_check_eframe(struct ppc64_pt_regs *regs) { switch(regs->trap & ~0xF) { case 0x100: return("System Reset"); case 0x200: return("Machine Check"); case 0x300: return("Data Access"); case 0x380: return("Data SLB Access"); case 0x400: return("Instruction Access"); case 0x480: return("Instruction SLB Access"); case 0x500: return("Hardware Interrupt"); case 0x600: return("Alignment"); case 0x700: return("Program Check"); case 0x800: return("FPU Unavailable"); case 0x900: return("Decrementer"); case 0x980: return("Hypervisor Decrementer"); case 0xa00: return("Doorbell"); case 0xb00: return("reserved"); case 0xc00: return("System Call"); case 0xd00: return("Single Step"); case 0xe00: return("fp assist"); case 0xe40: return("Emulation Assist"); case 0xe60: return("HMI"); case 0xe80: return("Hypervisor Doorbell"); case 0xf00: return("Performance Monitor"); case 0xf20: return("Altivec Unavailable"); case 0x1300: return("Instruction Breakpoint"); case 0x1500: return("Denormalisation"); case 0x1700: return("Altivec Assist"); } /* No exception frame exists */ return NULL; } static void ppc64_print_regs(struct ppc64_pt_regs *regs) { int i; /* print out the gprs... */ for (i=0; i<32; i++) { if (i && !(i % 3)) fprintf(fp, "\n"); fprintf(fp, " R%d:%s %016lx ", i, ((i < 10) ? " " : ""), regs->gpr[i]); /* * In 2.6, some stack frame contains only partial regs set. * For the partial set, only 14 regs will be saved and trap * field will contain 1 in the least significant bit. */ if ((i == 13) && (regs->trap & 1)) break; } fprintf(fp, "\n"); /* print out the rest of the registers */ fprintf(fp, " NIP: %016lx ", regs->nip); fprintf(fp, " MSR: %016lx ", regs->msr); fprintf(fp, "OR3: %016lx\n", regs->orig_gpr3); fprintf(fp, " CTR: %016lx ", regs->ctr); fprintf(fp, " LR: %016lx ", regs->link); fprintf(fp, "XER: %016lx\n", regs->xer); fprintf(fp, " CCR: %016lx ", regs->ccr); fprintf(fp, " MQ: %016lx ", regs->mq); fprintf(fp, "DAR: %016lx\n", regs->dar); fprintf(fp, " DSISR: %016lx ", regs->dsisr); fprintf(fp, " Syscall Result: %016lx\n", regs->result); } static void ppc64_print_nip_lr(struct ppc64_pt_regs *regs, int print_lr) { char buf[BUFSIZE]; char *sym_buf; sym_buf = value_to_symstr(regs->nip, buf, 0); if (sym_buf[0] != NULLCHAR) fprintf(fp, " [NIP : %s]\n", sym_buf); if (print_lr) { sym_buf = value_to_symstr(regs->link, buf, 0); if (sym_buf[0] != NULLCHAR) fprintf(fp, " [LR : %s]\n", sym_buf); } } /* * Print the exception frame information */ static void ppc64_print_eframe(char *efrm_str, struct ppc64_pt_regs *regs, struct bt_info *bt) { if (BT_REFERENCE_CHECK(bt)) return; fprintf(fp, " %s [%lx] exception frame:\n", efrm_str, regs->trap); ppc64_print_regs(regs); ppc64_print_nip_lr(regs, 1); } /* * For vmcore typically saved with KDump or FADump, get SP and IP values * from the saved ptregs. */ static int ppc64_vmcore_stack_frame(struct bt_info *bt_in, ulong *nip, ulong *ksp) { struct ppc64_pt_regs *pt_regs; unsigned long unip; /* * TRUE: task is running in a different context (userspace, OPAL..) * FALSE: task is probably running in kernel space. */ int out_of_context = FALSE; pt_regs = (struct ppc64_pt_regs *)bt_in->machdep; if (!pt_regs || !pt_regs->gpr[1]) { /* * Not collected regs. May be the corresponding CPU not * responded to an IPI in case of KDump OR f/w has not * not provided the register info in case of FADump. */ fprintf(fp, "%0lx: GPR1 register value (SP) was not saved\n", bt_in->task); return FALSE; } *ksp = pt_regs->gpr[1]; if (IS_KVADDR(*ksp)) { readmem(*ksp+16, KVADDR, &unip, sizeof(ulong), "Regs NIP value", FAULT_ON_ERROR); *nip = unip; } else { *nip = pt_regs->nip; if (IN_TASK_VMA(bt_in->task, *ksp)) { fprintf(fp, "%0lx: Task is running in user space\n", bt_in->task); out_of_context = TRUE; } else if (is_opal_context(*ksp, *nip)) { fprintf(fp, "%0lx: Task is running in OPAL (firmware) context\n", bt_in->task); out_of_context = TRUE; } else fprintf(fp, "%0lx: Invalid Stack Pointer %0lx\n", bt_in->task, *ksp); } if (bt_in->flags && ((BT_TEXT_SYMBOLS|BT_TEXT_SYMBOLS_PRINT|BT_TEXT_SYMBOLS_NOPRINT))) return TRUE; /* * Print the collected regs for the active task */ ppc64_print_regs(pt_regs); if (out_of_context) return TRUE; if (!IS_KVADDR(*ksp)) return FALSE; ppc64_print_nip_lr(pt_regs, (unip != pt_regs->link) ? 1 : 0); return TRUE; } /* * Get the starting point for the active cpus in a diskdump/netdump. */ static int ppc64_get_dumpfile_stack_frame(struct bt_info *bt_in, ulong *nip, ulong *ksp) { int i, ret, panic_task; char *sym; ulong *up; struct bt_info bt_local, *bt; struct machine_specific *ms; ulong ur_nip = 0; ulong ur_ksp = 0; int check_hardirq, check_softirq; int check_intrstack = TRUE; struct ppc64_pt_regs *pt_regs; struct syment *sp; bt = &bt_local; BCOPY(bt_in, bt, sizeof(struct bt_info)); ms = machdep->machspec; ur_nip = ur_ksp = 0; panic_task = tt->panic_task == bt->task ? TRUE : FALSE; check_hardirq = check_softirq = tt->flags & IRQSTACKS ? TRUE : FALSE; if (panic_task && bt->machdep) { pt_regs = (struct ppc64_pt_regs *)bt->machdep; ur_nip = pt_regs->nip; ur_ksp = pt_regs->gpr[1]; } else if ((pc->flags & KDUMP) || ((pc->flags & DISKDUMP) && (*diskdump_flags & KDUMP_CMPRS_LOCAL))) { /* * For the KDump or FADump vmcore, use SP and IP values * that are saved in ptregs. */ ret = ppc64_vmcore_stack_frame(bt_in, nip, ksp); if (ret) return TRUE; } if (bt->task != tt->panic_task) { char cpu_frozen = FALSE; /* * Determine whether the CPU responded to an IPI. * We captured the GPR1 register value in the * platform_freeze_cpu() function. */ if ((sp = symbol_search("dump_header")) && !is_symbol_text(sp)) { /* Diskdump */ ulong task_addr; /* * The dump_header struct is specified in the module. */ ulong offset = roundup(STRUCT_SIZE("timespec") + STRUCT_SIZE("new_utsname") + 52, 8); offset += sizeof(ulong) * bt->tc->processor; readmem(symbol_value("dump_header") + offset, KVADDR, &task_addr, sizeof(ulong), "Task Address", FAULT_ON_ERROR); if (task_addr) cpu_frozen = TRUE; } if (!cpu_frozen && symbol_exists("cpus_frozen")) { /* Netdump */ readmem(symbol_value("cpus_frozen") + sizeof(char) * bt->tc->processor, KVADDR, &cpu_frozen, sizeof(char), "CPU Frozen Value", FAULT_ON_ERROR); } ur_ksp = ppc64_get_sp(bt->task); if (IS_KVADDR(ur_ksp)) { /* * Since we could not capture the NIP value, we do not * know the top symbol name. Hence, move the SP to next * frame. */ if (cpu_frozen) readmem(ur_ksp, KVADDR, &ur_ksp, sizeof(ulong), "Stack Pointer", FAULT_ON_ERROR); else if (symbol_exists("platform_freeze_cpu")) fprintf(fp, "%0lx: GPR1 register value (SP) was not saved\n", bt->task); if (IS_KVADDR(ur_ksp)) /* * Get the LR value stored in the stack frame. */ readmem(ur_ksp+16, KVADDR, &ur_nip, sizeof(ulong), "Regs NIP value", FAULT_ON_ERROR); *ksp = ur_ksp; *nip = ur_nip; } else { *ksp = ur_ksp; fprintf(fp, "Could not find SP for task %0lx\n", bt->task); } } /* * Check the process stack first. We are scanning stack for only * panic task. Even though we have dumping CPU's regs, we will be * looking for specific symbols to display trace from actual dump * functions. If these symbols are not exists, consider the regs * stored in the ELF header. */ retry: for (i = 0, up = (ulong *)bt->stackbuf; i < (bt->stacktop - bt->stackbase)/sizeof(ulong); i++, up++) { sym = closest_symbol(*up); if (STREQ(sym, ".netconsole_netdump") || STREQ(sym, ".netpoll_start_netdump") || STREQ(sym, ".start_disk_dump") || STREQ(sym, "crash_kexec") || STREQ(sym, "crash_fadump") || STREQ(sym, "crash_ipi_callback") || STREQ(sym, ".crash_kexec") || STREQ(sym, ".crash_fadump") || STREQ(sym, ".crash_ipi_callback") || STREQ(sym, ".disk_dump")) { *nip = *up; *ksp = bt->stackbase + ((char *)(up) - 16 - bt->stackbuf); /* * Check whether this symbol relates to a * backtrace or not */ ur_ksp = *(ulong *)&bt->stackbuf[(*ksp) - bt->stackbase]; if (!INSTACK(ur_ksp, bt)) continue; return TRUE; } } bt->flags &= ~(BT_HARDIRQ|BT_SOFTIRQ); if (check_hardirq && (tt->hardirq_tasks[bt->tc->processor] == bt->tc->task)) { bt->stackbase = tt->hardirq_ctx[bt->tc->processor]; bt->stacktop = bt->stackbase + STACKSIZE(); alter_stackbuf(bt); bt->flags |= BT_HARDIRQ; check_hardirq = FALSE; goto retry; } if (check_softirq && (tt->softirq_tasks[bt->tc->processor] == bt->tc->task)) { bt->stackbase = tt->softirq_ctx[bt->tc->processor]; bt->stacktop = bt->stackbase + STACKSIZE(); alter_stackbuf(bt); bt->flags |= BT_SOFTIRQ; check_softirq = FALSE; goto retry; } if (check_intrstack && ms->hwintrstack[bt->tc->processor]) { bt->stacktop = ms->hwintrstack[bt->tc->processor] + sizeof(ulong); bt->stackbase = ms->hwintrstack[bt->tc->processor] - ms->hwstacksize + STACK_FRAME_OVERHEAD; bt->stackbuf = ms->hwstackbuf; alter_stackbuf(bt); check_intrstack = FALSE; goto retry; } /* * We didn't find what we were looking for, so just use what was * passed in the ELF header. */ if (ur_nip && ur_ksp) { *nip = ur_nip; *ksp = ur_ksp; return TRUE; } console("ppc64_get_dumpfile_stack_frame: cannot find SP for panic task\n"); return FALSE; } /* * Get a stack frame combination of pc and ra from the most relevent spot. */ static void ppc64_get_stack_frame(struct bt_info *bt, ulong *pcp, ulong *spp) { ulong ksp, nip; nip = ksp = 0; if (DUMPFILE() && is_task_active(bt->task)) ppc64_get_dumpfile_stack_frame(bt, &nip, &ksp); else get_ppc64_frame(bt, &nip, &ksp); if (pcp) *pcp = nip; if (spp) *spp = ksp; } static ulong ppc64_get_sp(ulong task) { ulong sp; if (tt->flags & THREAD_INFO) readmem(task + OFFSET(task_struct_thread_ksp), KVADDR, &sp, sizeof(void *), "thread_struct ksp", FAULT_ON_ERROR); else { ulong offset; offset = OFFSET_OPTION(task_struct_thread_ksp, task_struct_tss_ksp); readmem(task + offset, KVADDR, &sp, sizeof(void *), "task_struct ksp", FAULT_ON_ERROR); } return sp; } /* * get the SP and PC values for idle tasks. */ static void get_ppc64_frame(struct bt_info *bt, ulong *getpc, ulong *getsp) { ulong ip; ulong sp; ulong *stack; ulong task; char *closest; struct ppc64_pt_regs regs; ip = 0; task = bt->task; stack = (ulong *)bt->stackbuf; sp = ppc64_get_sp(task); if (!INSTACK(sp, bt)) goto out; readmem(sp+STACK_FRAME_OVERHEAD, KVADDR, ®s, sizeof(struct ppc64_pt_regs), "PPC64 pt_regs", FAULT_ON_ERROR); ip = regs.nip; closest = closest_symbol(ip); if (STREQ(closest, ".__switch_to") || STREQ(closest, "__switch_to")) { /* NOTE: _switch_to() calls _switch() which * is asm. _switch leaves pc == lr. * Working through this frame is tricky, * and this mess isn't going to help if we * actually dumped here. Most likely the * analyzer is trying to backtrace a task. * Need to skip 2 frames. */ sp = stack[(sp - bt->stackbase)/sizeof(ulong)]; if (!INSTACK(sp, bt)) goto out; sp = stack[(sp - bt->stackbase)/sizeof(ulong)]; if (!INSTACK(sp+16, bt)) goto out; ip = stack[(sp + 16 - bt->stackbase)/sizeof(ulong)]; } out: *getsp = sp; *getpc = ip; } /* * Do the work for cmd_irq(). */ static void ppc64_dump_irq(int irq) { ulong irq_desc_addr, addr; int level, others; ulong action, ctl, value; char typename[32]; irq_desc_addr = symbol_value("irq_desc") + (SIZE(irqdesc) * irq); readmem(irq_desc_addr + OFFSET(irqdesc_level), KVADDR, &level, sizeof(int), "irq_desc entry", FAULT_ON_ERROR); readmem(irq_desc_addr + OFFSET(irqdesc_action), KVADDR, &action, sizeof(long), "irq_desc entry", FAULT_ON_ERROR); readmem(irq_desc_addr + OFFSET(irqdesc_ctl), KVADDR, &ctl, sizeof(long), "irq_desc entry", FAULT_ON_ERROR); fprintf(fp, " IRQ: %d\n", irq); fprintf(fp, " STATUS: 0\n"); fprintf(fp, "HANDLER: "); if (value_symbol(ctl)) { fprintf(fp, "%lx ", ctl); pad_line(fp, VADDR_PRLEN == 8 ? VADDR_PRLEN+2 : VADDR_PRLEN-6, ' '); fprintf(fp, "<%s>\n", value_symbol(ctl)); } else fprintf(fp, "%lx\n", ctl); if(ctl) { /* typename */ readmem(ctl + OFFSET(hw_interrupt_type_typename), KVADDR, &addr, sizeof(ulong), "typename pointer", FAULT_ON_ERROR); fprintf(fp, " typename: %08lx ", addr); if (read_string(addr, typename, 32)) fprintf(fp, "\"%s\"\n", typename); else fprintf(fp, "\n"); /* startup...I think this is always 0 */ readmem(ctl + OFFSET(hw_interrupt_type_startup), KVADDR, &addr, sizeof(ulong), "interrupt startup", FAULT_ON_ERROR); fprintf(fp, " startup: "); if(value_symbol(addr)) { fprintf(fp, "%08lx <%s>\n", addr, value_symbol(addr)); } else fprintf(fp, "%lx\n", addr); /* shutdown...I think this is always 0 */ readmem(ctl + OFFSET(hw_interrupt_type_shutdown), KVADDR, &addr, sizeof(ulong), "interrupt shutdown", FAULT_ON_ERROR); fprintf(fp, " shutdown: "); if(value_symbol(addr)) { fprintf(fp, "%08lx <%s>\n", addr, value_symbol(addr)); } else fprintf(fp, "%lx\n", addr); if (VALID_MEMBER(hw_interrupt_type_handle)) { /* handle */ readmem(ctl + OFFSET(hw_interrupt_type_handle), KVADDR, &addr, sizeof(ulong), "interrupt handle", FAULT_ON_ERROR); fprintf(fp, " handle: "); if(value_symbol(addr)) { fprintf(fp, "%08lx <%s>\n", addr, value_symbol(addr)); } else fprintf(fp, "%lx\n", addr); } /* enable/disable */ readmem(ctl + OFFSET(hw_interrupt_type_enable), KVADDR, &addr, sizeof(ulong), "interrupt enable", FAULT_ON_ERROR); fprintf(fp, " enable: "); if(value_symbol(addr)) { fprintf(fp, "%08lx <%s>\n", addr, value_symbol(addr)); } else fprintf(fp, "%lx\n", addr); readmem(ctl + OFFSET(hw_interrupt_type_disable), KVADDR, &addr, sizeof(ulong), "interrupt disable", FAULT_ON_ERROR); fprintf(fp, " disable: "); if(value_symbol(addr)) { fprintf(fp, "%08lx <%s>\n", addr, value_symbol(addr)); } else fprintf(fp, "0\n"); } /* next, the action... and its submembers */ if(!action) fprintf(fp, " ACTION: (none)\n"); while(action) { fprintf(fp, " ACTION: %08lx\n", action); /* handler */ readmem(action + OFFSET(irqaction_handler), KVADDR, &addr, sizeof(ulong), "action handler", FAULT_ON_ERROR); fprintf(fp, " handler: "); if(value_symbol(addr)) { fprintf(fp, "%08lx <%s>\n", addr, value_symbol(addr)); } else fprintf(fp, "0\n"); /* flags */ readmem(action + OFFSET(irqaction_flags), KVADDR, &value, sizeof(ulong), "action flags", FAULT_ON_ERROR); fprintf(fp, " flags: %lx ", value); if (value) { others = 0; fprintf(fp, "("); if (value & SA_INTERRUPT) fprintf(fp, "%sSA_INTERRUPT", others++ ? "|" : ""); if (value & SA_PROBE) fprintf(fp, "%sSA_PROBE", others++ ? "|" : ""); if (value & SA_SAMPLE_RANDOM) fprintf(fp, "%sSA_SAMPLE_RANDOM", others++ ? "|" : ""); if (value & SA_SHIRQ) fprintf(fp, "%sSA_SHIRQ", others++ ? "|" : ""); fprintf(fp, ")"); if (value & ~ACTION_FLAGS) { fprintf(fp, " (bits %lx not translated)", value & ~ACTION_FLAGS); } } fprintf(fp, "\n"); /* mask */ readmem(action + OFFSET(irqaction_mask), KVADDR, &value, sizeof(ulong), "action mask", FAULT_ON_ERROR); fprintf(fp, " mask: %lx\n", value); /* name */ readmem(action + OFFSET(irqaction_name), KVADDR, &addr, sizeof(ulong), "action name", FAULT_ON_ERROR); fprintf(fp, " name: %08lx ", addr); if (read_string(addr, typename, 32)) fprintf(fp, "\"%s\"\n", typename); else fprintf(fp, "\n"); /* dev_id */ readmem(action + OFFSET(irqaction_dev_id), KVADDR, &value, sizeof(ulong), "action dev_id", FAULT_ON_ERROR); fprintf(fp, " dev_id: %08lx\n", value); /* next */ readmem(action + OFFSET(irqaction_next), KVADDR, &value, sizeof(ulong), "action next", FAULT_ON_ERROR); fprintf(fp, " next: %lx\n", value); /* keep going if there are chained interrupts */ action = value; } fprintf(fp, " DEPTH: %x\n\n", level); } /* * Filter disassembly output if the output radix is not gdb's default 10 */ static int ppc64_dis_filter(ulong vaddr, char *inbuf, unsigned int output_radix) { char buf1[BUFSIZE]; char buf2[BUFSIZE]; char *colon, *p1; int argc; char *argv[MAXARGS]; ulong value; if (!inbuf) return TRUE; /* * For some reason gdb can go off into the weeds translating text addresses, * (on alpha -- not necessarily seen on ppc64) so this routine both fixes the * references as well as imposing the current output radix on the translations. */ console("IN: %s", inbuf); colon = strstr(inbuf, ":"); if (colon) { sprintf(buf1, "0x%lx <%s>", vaddr, value_to_symstr(vaddr, buf2, output_radix)); sprintf(buf2, "%s%s", buf1, colon); strcpy(inbuf, buf2); } strcpy(buf1, inbuf); argc = parse_line(buf1, argv); if ((FIRSTCHAR(argv[argc-1]) == '<') && (LASTCHAR(argv[argc-1]) == '>')) { p1 = rindex(inbuf, '<'); while ((p1 > inbuf) && !(STRNEQ(p1, " 0x") || STRNEQ(p1, ",0x"))) p1--; if (!(STRNEQ(p1, " 0x") || STRNEQ(p1, ",0x"))) return FALSE; p1++; if (!extract_hex(p1, &value, NULLCHAR, TRUE)) return FALSE; sprintf(buf1, "0x%lx <%s>\n", value, value_to_symstr(value, buf2, output_radix)); sprintf(p1, "%s", buf1); } console(" %s", inbuf); return TRUE; } /* * Override smp_num_cpus if possible and necessary. */ int ppc64_get_smp_cpus(void) { return get_cpus_online(); } /* * Definitions derived from OPAL. These need to track corresponding values in * https://github.com/open-power/skiboot/blob/master/include/mem-map.h */ #define SKIBOOT_CONSOLE_DUMP_START 0x31000000 #define SKIBOOT_CONSOLE_DUMP_SIZE 0x100000 #define ASCII_UNLIMITED ((ulong)(-1) >> 1) void opalmsg(void) { struct memloc { uint8_t u8; uint16_t u16; uint32_t u32; uint64_t u64; uint64_t limit64; }; int i, a; size_t typesz; void *location; char readtype[20]; struct memloc mem; int displayed, per_line; int lost; ulong error_handle; long count = SKIBOOT_CONSOLE_DUMP_SIZE; ulonglong addr = SKIBOOT_CONSOLE_DUMP_START; if (!(machdep->flags & OPAL_FW)) error(FATAL, "dump was not captured on OPAL based system"); if (CRASHDEBUG(4)) fprintf(fp, "\n", addr, count, "PHYSADDR"); BZERO(&mem, sizeof(struct memloc)); lost = typesz = per_line = 0; location = NULL; /* ASCII */ typesz = SIZEOF_8BIT; location = &mem.u8; sprintf(readtype, "ascii"); per_line = 256; displayed = 0; error_handle = FAULT_ON_ERROR; for (i = a = 0; i < count; i++) { if (!readmem(addr, PHYSADDR, location, typesz, readtype, error_handle)) { addr += typesz; lost += 1; continue; } if (isprint(mem.u8)) { if ((a % per_line) == 0) { if (displayed && i) fprintf(fp, "\n"); } fprintf(fp, "%c", mem.u8); displayed++; a++; } else { if (count == ASCII_UNLIMITED) return; a = 0; } addr += typesz; } if (lost != count) fprintf(fp, "\n"); } /* * Machine dependent command. */ void ppc64_cmd_mach(void) { int c; while ((c = getopt(argcnt, args, "cmo")) != EOF) { switch(c) { case 'c': case 'm': fprintf(fp, "PPC64: '-%c' option is not supported\n", c); break; case 'o': return opalmsg(); default: argerrs++; break; } } if (argerrs) cmd_usage(pc->curcmd, SYNOPSIS); ppc64_display_machine_stats(); } /* * "mach" command output. */ static void ppc64_display_machine_stats(void) { int c; struct new_utsname *uts; char buf[BUFSIZE]; ulong mhz; uts = &kt->utsname; fprintf(fp, " MACHINE TYPE: %s\n", uts->machine); fprintf(fp, " MEMORY SIZE: %s\n", get_memory_size(buf)); fprintf(fp, " CPUS: %d\n", get_cpus_to_display()); fprintf(fp, " PROCESSOR SPEED: "); if ((mhz = machdep->processor_speed())) fprintf(fp, "%ld Mhz\n", mhz); else fprintf(fp, "(unknown)\n"); fprintf(fp, " HZ: %d\n", machdep->hz); fprintf(fp, " PAGE SIZE: %d\n", PAGESIZE()); // fprintf(fp, " L1 CACHE SIZE: %d\n", l1_cache_size()); fprintf(fp, "KERNEL VIRTUAL BASE: %lx\n", machdep->kvbase); fprintf(fp, "KERNEL VMALLOC BASE: %lx\n", vt->vmalloc_start); fprintf(fp, " KERNEL STACK SIZE: %ld\n", STACKSIZE()); if (tt->flags & IRQSTACKS) { fprintf(fp, "HARD IRQ STACK SIZE: %ld\n", STACKSIZE()); fprintf(fp, " HARD IRQ STACKS:\n"); for (c = 0; c < kt->cpus; c++) { if (!tt->hardirq_ctx[c]) break; sprintf(buf, "CPU %d", c); fprintf(fp, "%19s: %lx\n", buf, tt->hardirq_ctx[c]); } fprintf(fp, "SOFT IRQ STACK SIZE: %ld\n", STACKSIZE()); fprintf(fp, " SOFT IRQ STACKS:\n"); for (c = 0; c < kt->cpus; c++) { if (!tt->softirq_ctx) break; sprintf(buf, "CPU %d", c); fprintf(fp, "%19s: %lx\n", buf, tt->softirq_ctx[c]); } } } static const char *hook_files[] = { "arch/ppc64/kernel/entry.S", "arch/ppc64/kernel/head.S", "arch/ppc64/kernel/semaphore.c" }; #define ENTRY_S ((char **)&hook_files[0]) #define HEAD_S ((char **)&hook_files[1]) #define SEMAPHORE_C ((char **)&hook_files[2]) static struct line_number_hook ppc64_line_number_hooks[] = { {"DoSyscall", ENTRY_S}, {"_switch", ENTRY_S}, {"ret_from_syscall_1", ENTRY_S}, {"ret_from_syscall_2", ENTRY_S}, {"ret_from_fork", ENTRY_S}, {"ret_from_except", ENTRY_S}, {"do_signal_ret", ENTRY_S}, {"ret_to_user_hook", ENTRY_S}, {"enter_rtas", ENTRY_S}, {"restore", ENTRY_S}, {"do_bottom_half_ret", ENTRY_S}, {"ret_to_user_hook", ENTRY_S}, {"_stext", HEAD_S}, {"_start", HEAD_S}, {"__start", HEAD_S}, {"__secondary_hold", HEAD_S}, {"DataAccessCont", HEAD_S}, {"DataAccess", HEAD_S}, {"i0x300", HEAD_S}, {"DataSegmentCont", HEAD_S}, {"InstructionAccessCont", HEAD_S}, {"InstructionAccess", HEAD_S}, {"i0x400", HEAD_S}, {"InstructionSegmentCont", HEAD_S}, {"HardwareInterrupt", HEAD_S}, {"do_IRQ_intercept", HEAD_S}, {"i0x600", HEAD_S}, {"ProgramCheck", HEAD_S}, {"i0x700", HEAD_S}, {"FPUnavailable", HEAD_S}, {"i0x800", HEAD_S}, {"Decrementer", HEAD_S}, {"timer_interrupt_intercept", HEAD_S}, {"SystemCall", HEAD_S}, {"trap_0f_cont", HEAD_S}, {"Trap_0f", HEAD_S}, {"InstructionTLBMiss", HEAD_S}, {"InstructionAddressInvalid", HEAD_S}, {"DataLoadTLBMiss", HEAD_S}, {"DataAddressInvalid", HEAD_S}, {"DataStoreTLBMiss", HEAD_S}, {"AltiVecUnavailable", HEAD_S}, {"DataAccess", HEAD_S}, {"InstructionAccess", HEAD_S}, {"DataSegment", HEAD_S}, {"InstructionSegment", HEAD_S}, {"transfer_to_handler", HEAD_S}, {"stack_ovf", HEAD_S}, {"load_up_fpu", HEAD_S}, {"KernelFP", HEAD_S}, {"load_up_altivec", HEAD_S}, {"KernelAltiVec", HEAD_S}, {"giveup_altivec", HEAD_S}, {"giveup_fpu", HEAD_S}, {"relocate_kernel", HEAD_S}, {"copy_and_flush", HEAD_S}, {"fix_mem_constants", HEAD_S}, {"apus_interrupt_entry", HEAD_S}, {"__secondary_start_gemini", HEAD_S}, {"__secondary_start_psurge", HEAD_S}, {"__secondary_start_psurge2", HEAD_S}, {"__secondary_start_psurge3", HEAD_S}, {"__secondary_start_psurge99", HEAD_S}, {"__secondary_start", HEAD_S}, {"setup_common_caches", HEAD_S}, {"setup_604_hid0", HEAD_S}, {"setup_750_7400_hid0", HEAD_S}, {"load_up_mmu", HEAD_S}, {"start_here", HEAD_S}, {"clear_bats", HEAD_S}, {"flush_tlbs", HEAD_S}, {"mmu_off", HEAD_S}, {"initial_bats", HEAD_S}, {"setup_disp_bat", HEAD_S}, {"m8260_gorom", HEAD_S}, {"sdata", HEAD_S}, {"empty_zero_page", HEAD_S}, {"swapper_pg_dir", HEAD_S}, {"cmd_line", HEAD_S}, {"intercept_table", HEAD_S}, {"set_context", HEAD_S}, {NULL, NULL} /* list must be NULL-terminated */ }; static void ppc64_dump_line_number(ulong callpc) { int retries; char buf[BUFSIZE], *p; retries = 0; try_closest: get_line_number(callpc, buf, FALSE); if (strlen(buf)) { if (retries) { p = strstr(buf, ": "); if (p) *p = NULLCHAR; } fprintf(fp, " %s\n", buf); } else { if (retries) fprintf(fp, GDB_PATCHED() ? "" : " (cannot determine file and line number)\n"); else { retries++; callpc = closest_symbol_value(callpc); goto try_closest; } } } void ppc64_compiler_warning_stub(void) { struct line_number_hook *lhp; lhp = &ppc64_line_number_hooks[0]; lhp++; ppc64_back_trace(NULL, NULL); ppc64_dump_line_number(0); } /* * Force the VM address-range selection via: * * --machdep vm=orig * --machdep vm=2.6.14 */ void parse_cmdline_args(void) { int index, i, c; char *p; char buf[BUFSIZE]; char *arglist[MAXARGS]; int lines = 0; for (index = 0; index < MAX_MACHDEP_ARGS; index++) { if (!machdep->cmdline_args[index]) break; if (!strstr(machdep->cmdline_args[index], "=")) { error(WARNING, "ignoring --machdep option: %s\n\n", machdep->cmdline_args[index]); continue; } strcpy(buf, machdep->cmdline_args[index]); for (p = buf; *p; p++) { if (*p == ',') *p = ' '; } c = parse_line(buf, arglist); for (i = 0; i < c; i++) { if (STRNEQ(arglist[i], "vm=")) { p = arglist[i] + strlen("vm="); if (strlen(p)) { if (STREQ(p, "orig")) { machdep->flags |= VM_ORIG; continue; } else if (STREQ(p, "2.6.14")) { machdep->flags |= VM_4_LEVEL; continue; } } } error(WARNING, "ignoring --machdep option: %s\n", arglist[i]); lines++; } switch (machdep->flags & (VM_ORIG|VM_4_LEVEL)) { case VM_ORIG: error(NOTE, "using original PPC64 VM address ranges\n"); lines++; break; case VM_4_LEVEL: error(NOTE, "using 4-level pagetable PPC64 VM address ranges\n"); lines++; break; case (VM_ORIG|VM_4_LEVEL): error(WARNING, "cannot set both vm=orig and vm=2.6.14\n"); lines++; machdep->flags &= ~(VM_ORIG|VM_4_LEVEL); break; } if (lines) fprintf(fp, "\n"); } } /* * Initialize the per cpu data_offset values from paca structure. */ static int ppc64_paca_init(int map) { int i, cpus, nr_paca; char *cpu_paca_buf; ulong data_offset; ulong paca; if (!symbol_exists("paca")) error(FATAL, "PPC64: Could not find 'paca' symbol\n"); /* * In v2.6.34 ppc64, the upstream commit 1426d5a3 (powerpc: Dynamically * allocate pacas) now dynamically allocates the paca and have * changed data type of 'paca' symbol from array to pointer. With this * change in place crash utility fails to read vmcore generated for * upstream kernel. * Add a check for paca variable data type before accessing. */ if (get_symbol_type("paca", NULL, NULL) == TYPE_CODE_PTR) readmem(symbol_value("paca"), KVADDR, &paca, sizeof(ulong), "paca", FAULT_ON_ERROR); else paca = symbol_value("paca"); if (!MEMBER_EXISTS("paca_struct", "data_offset")) return kt->cpus; STRUCT_SIZE_INIT(ppc64_paca, "paca_struct"); data_offset = MEMBER_OFFSET("paca_struct", "data_offset"); cpu_paca_buf = GETBUF(SIZE(ppc64_paca)); if (!(nr_paca = get_array_length("paca", NULL, 0))) nr_paca = (kt->kernel_NR_CPUS ? kt->kernel_NR_CPUS : NR_CPUS); if (nr_paca > NR_CPUS) { error(WARNING, "PPC64: Number of paca entries (%d) greater than NR_CPUS (%d)\n", nr_paca, NR_CPUS); error(FATAL, "Recompile crash with larger NR_CPUS\n"); } for (i = cpus = 0; i < nr_paca; i++) { /* * CPU present or online or can exist in the system(possible)? */ if (!in_cpu_map(map, i)) continue; readmem(paca + (i * SIZE(ppc64_paca)), KVADDR, cpu_paca_buf, SIZE(ppc64_paca), "paca entry", FAULT_ON_ERROR); kt->__per_cpu_offset[i] = ULONG(cpu_paca_buf + data_offset); kt->flags |= PER_CPU_OFF; cpus++; } return cpus; } static int ppc64_get_cpu_map(void) { int map; if (cpu_map_addr("possible")) map = POSSIBLE_MAP; else if (cpu_map_addr("present")) map = PRESENT_MAP; else if (cpu_map_addr("online")) map = ONLINE_MAP; else if (cpu_map_addr("active")) map = ACTIVE_MAP; else { map = 0; error(FATAL, "PPC64: cannot find 'cpu_possible_map', " "'cpu_present_map', 'cpu_online_map' or 'cpu_active_map' symbols\n"); } return map; } /* * Updating any smp-related items that were possibly bypassed * or improperly initialized in kernel_init(). */ static void ppc64_init_cpu_info(void) { int i, map, cpus, nr_cpus; map = ppc64_get_cpu_map(); /* * starting from v2.6.36 we can not rely on paca structure to get * per cpu data_offset. The upstream commit fc53b420 overwrites * the paca pointer variable to point to static paca that contains * valid data_offset only for crashing cpu. * * But the kernel v2.6.36 ppc64 introduces __per_cpu_offset symbol * which was removed post v2.6.15 ppc64 and now we get the per cpu * data_offset from __per_cpu_offset symbol during kernel_init() * call. Hence for backward (pre-2.6.36) compatibility, call * ppc64_paca_init() only if symbol __per_cpu_offset does not exist. */ if (!symbol_exists("__per_cpu_offset")) cpus = ppc64_paca_init(map); else { if (!(nr_cpus = get_array_length("__per_cpu_offset", NULL, 0))) nr_cpus = (kt->kernel_NR_CPUS ? kt->kernel_NR_CPUS : NR_CPUS); for (i = cpus = 0; i < nr_cpus; i++) { if (!in_cpu_map(map, i)) continue; cpus++; } } switch (map) { case POSSIBLE_MAP: if (cpus > kt->cpus) { i = get_highest_cpu_online() + 1; if (i > kt->cpus) kt->cpus = i; } break; case ONLINE_MAP: case PRESENT_MAP: kt->cpus = cpus; break; } if (kt->cpus > 1) kt->flags |= SMP; } void ppc64_clear_machdep_cache(void) { if (machdep->last_pgd_read != vt->kernel_pgd[0]) machdep->last_pgd_read = 0; } static int ppc64_get_kvaddr_ranges(struct vaddr_range *vrp) { int cnt; physaddr_t phys1, phys2; ulong pp1, pp2; cnt = 0; vrp[cnt].type = KVADDR_UNITY_MAP; vrp[cnt].start = machdep->kvbase; vrp[cnt++].end = vt->high_memory; vrp[cnt].type = KVADDR_VMALLOC; vrp[cnt].start = first_vmalloc_address(); vrp[cnt++].end = last_vmalloc_address(); if (machdep->flags & VMEMMAP) { phys1 = (physaddr_t)(0); phys2 = (physaddr_t)VTOP((vt->high_memory - PAGESIZE())); if (phys_to_page(phys1, &pp1) && phys_to_page(phys2, &pp2)) { vrp[cnt].type = KVADDR_VMEMMAP; vrp[cnt].start = pp1; vrp[cnt++].end = pp2; } } return cnt; } #endif /* PPC64 */