/*------------------------------------------------------------------------------------------*\ * * Copyright (C) 2014 AVM GmbH * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * mips-helper-functions for stackdump on smp etc. \*------------------------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_MIPS_MT_SMP) || defined(CONFIG_MIPS_MT_SMTC) #include #else #define is_yield_context() 0 #endif #include #include #include #include #include #include #include #include #include #include #define snprintf_add(ptxt, txtlen, args...) if(ptxt == NULL) printk(args); else { int local_add_len;\ if((local_add_len = snprintf(ptxt, txtlen, args)) > 0) { \ int tail = min((int)txtlen, local_add_len); \ (ptxt) += tail, (txtlen) -= tail; \ } \ } extern void show_backtrace(struct task_struct *task, const struct pt_regs *regs); /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ void print_cp0_status(unsigned int cp0_status) { printk(KERN_ERR"Status: %08x %s%s%s%s%s%s%s\n", cp0_status, (cp0_status & ST0_KX) ? "KX " : "", (cp0_status & ST0_SX) ? "SX " : "", (cp0_status & ST0_UX) ? "UX " : "", (cp0_status & ST0_KSU) == KSU_USER ? "USER " : (cp0_status & ST0_KSU) == KSU_SUPERVISOR ? "SUPERVISOR " : (cp0_status & ST0_KSU) == KSU_KERNEL ? "KERNEL " : "BAD MODE ", (cp0_status & ST0_ERL) ? "ERL " : "", (cp0_status & ST0_EXL) ? "EXL " : "", (cp0_status & ST0_IE) ? "IE" : ""); } #if defined(CONFIG_MIPS_MT_SMP) || defined(CONFIG_MIPS_MT_SMTC) extern unsigned long kernelsp[NR_CPUS]; /*--------------------------------------------------------------------------------*\ * ret: -1 invalid tc * 1 tc is halted * * Achtung! Ein gp im Userland wird in das Kernel-gp konvertiert \*--------------------------------------------------------------------------------*/ int mips_mt_prepare_frametrace(unsigned int tc, struct pt_regs *regs) { unsigned long flags = 0; unsigned long vpflags; unsigned long mvpconf0; int ntc; int ret = 0; unsigned long haltval; #ifdef CONFIG_MIPS_MT_SMTC void smtc_soft_dump(void); #endif /* CONFIG_MIPT_MT_SMTC */ memset(regs, 0, sizeof(*regs)); yield_local_irq_save(flags); /**-------------------------------------------------------------------------------**\ * START-CRITICAL START-CRITICAL START-CRITICAL START-CRITICAL START-CRITICAL * Achtung kritischer Abschnitt - hier keine printks (ergo spinlocks etc.) verwenden * -> Deadlock-Gefahr! \*--------------------------------------------------------------------------------**/ vpflags = dvpe(); mvpconf0 = read_c0_mvpconf0(); ntc = ((mvpconf0 & MVPCONF0_PTC) >> MVPCONF0_PTC_SHIFT) + 1; if(tc >= ntc) { ret = -1; goto end_mt_prepare_registers; } settc(tc); if (read_tc_c0_tcbind() == read_c0_tcbind()) { /* Are we dumping ourself? */ haltval = 0; /* Then we're not halted, and mustn't be */ /*--- goto end_mt_prepare_registers; ---*/ } else { haltval = read_tc_c0_tchalt(); write_tc_c0_tchalt(1); ret = haltval; } regs->regs[28] = read_tc_gpr_gp(); regs->regs[29] = read_tc_gpr_sp(); regs->regs[31] = read_tc_gpr_ra(); regs->cp0_epc = read_tc_c0_tcrestart(); /*--- pc ---*/ regs->cp0_cause = read_vpe_c0_cause(); regs->cp0_status = read_vpe_c0_status(); if(user_mode(regs) && (tc < NR_CPUS)) { regs->regs[28] = kernelsp[tc] & ~THREAD_MASK; /*--- kseg0-gp ermitteln ---*/ } if (!haltval) { write_tc_c0_tchalt(0); } end_mt_prepare_registers: #ifdef CONFIG_MIPS_MT_SMTC /*--- smtc_soft_dump(); ---*/ #endif /* CONFIG_MIPT_MT_SMTC */ evpe(vpflags); /*-------------------------------------------------------------------------------*\ * END-CRITICAL END-CRITICAL END-CRITICAL END-CRITICAL END-CRITICAL \*-------------------------------------------------------------------------------*/ /*--- printk("%s: $28 %08lx $29 %08lx $31 %08lx PC=%08lx\n", __func__, regs->regs[28], regs->regs[29], regs->regs[31], regs->cp0_epc); ---*/ yield_local_irq_restore(flags); return ret; } #endif /** */ static int __get_userinfo(char *buf, unsigned int maxbuflen, struct mm_struct *mmm, unsigned long addr) { struct vm_area_struct *vm; struct vm_area_struct *vm_last = NULL; unsigned int i = 0; if (!virt_addr_valid(mmm)) { return 1; } vm = mmm->mmap; while (vm) { /*--- pr_err("%s[%x]:%p %x - %x vm_mm %p\n", __func__, addr, vm, vm->vm_start, vm->vm_end, vm->vm_mm); ---*/ if (!virt_addr_valid(vm)) { snprintf(buf, maxbuflen, "%s: error: corrupt vm %p vm_last=%pS\n", __func__, vm, vm_last); return 1; } if ((addr >= vm->vm_start) && (addr < vm->vm_end)) { snprintf(buf, maxbuflen, "seg=%3u of=0x%08lx/0x%lx [%s]", i, addr - (unsigned long)vm->vm_start, (unsigned long)vm->vm_end - (unsigned long)vm->vm_start, (vm->vm_file && vm->vm_file->f_path.dentry) ? (char *)vm->vm_file->f_path.dentry->d_name.name : "" ); /*--- pr_err("%s", buf); ---*/ return 0; } vm_last = vm; vm = vm->vm_next; i++; } return 1; } /*--------------------------------------------------------------------------------*\ * gp muss valide KSEG0-Adresse sein * Schwachstelle: thread/task nicht mehr valid (in dem Moment freigegeben) \*--------------------------------------------------------------------------------*/ char *get_user_symbol(char *txt, unsigned int maxttxlen, unsigned long gp, unsigned long addr) { unsigned long flags; struct task_struct *task = NULL; struct thread_info *thread = (struct thread_info *)gp; txt[0] = 0; if(!((gp >= KSEG0) && (gp < KSEG1) && ((gp & THREAD_MASK) == 0))) { /*--- valid gp-address ? ---*/ return txt; } local_irq_save(flags); task = thread->task; if(task == NULL) { local_irq_restore(flags); return txt; } if(spin_trylock(&task->alloc_lock)) { /*--- kritisch: mmput()/get_task_mm() darf im Irq-Kontext nicht verwendet werden, mmput hoffentlich nur im task_lock() ---*/ __get_userinfo(txt, maxttxlen, task->mm, addr); spin_unlock(&task->alloc_lock); } local_irq_restore(flags); return txt; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ bool arch_trigger_all_cpu_backtrace(cpumask_t *cpu_mask) { #if defined(CONFIG_MIPS_MT_SMP) || defined(CONFIG_MIPS_MT_SMTC) struct task_struct *task = NULL; char txtbuf[2][64]; unsigned long gp; struct pt_regs regs, *pregs; int tc = 0, cur_tc; int act_cpu = smp_processor_id(); while((cur_tc = mips_mt_prepare_frametrace(tc, ®s)) != -1) { pregs = ®s; if(((act_cpu == tc)) && (in_irq() || in_nmi())) { if(get_irq_regs()) { pregs = get_irq_regs(); /*--- wir wollen nicht den aktuellen Irq/NMI-Kontext tracen ---*/ } else { printk(KERN_ERR"%s: get_irq_regs() == NULL - check genex.S\n", __func__); } } if(cur_tc == 0) { gp = pregs->regs[28]; /*--- kseg0-gp ermitteln ---*/ if(tc < NR_CPUS) { if(cpu_mask && (cpumask_test_cpu(tc, cpu_mask) == 0)) { tc++; continue; } printk(KERN_ERR"CPU_ID=%u TC%u ----- (LINUX)\n", tc, tc); print_cp0_status(pregs->cp0_status); } else { printk(KERN_ERR"YIELD-TC%u ----- \n", tc); } if(virt_addr_valid(gp) && ((gp & THREAD_MASK) == 0)) { /*--- valid gp-address ? ---*/ const int field = 2 * sizeof(unsigned long); struct thread_info *thread = (struct thread_info *)gp; task = thread->task; if((!virt_addr_valid(task)) /*--- valide kseg0-task-address ? ---*/) { tc++; printk(KERN_ERR"invalid task-pointer %p\n", task); continue; } printk(KERN_ERR"current: %s (pid: %d, threadinfo=%p, task=%p, sp=%08lx tls=0x%0*lx)\n", task->comm, task->pid, thread, task, pregs->regs[29], field, thread->tp_value); if (!user_mode(pregs)) { show_backtrace(task, pregs); tc++; continue; } printk(KERN_ERR"[<%08lx>] %s\n[<%08lx>] %s\n", pregs->cp0_epc, get_user_symbol(txtbuf[0], sizeof(txtbuf[0]), gp, pregs->cp0_epc), pregs->regs[31], get_user_symbol(txtbuf[1], sizeof(txtbuf[1]), gp, pregs->regs[31])); } } tc++; } return 1; #endif /*--- #if defined(CONFIG_MIPS_MT_SMP) || defined(CONFIG_MIPS_MT_SMTC) ---*/ return 0; } #define IS_MIPS16_EXTEND_OR_JAL(a) ((((a) >> (27 - 16)) == 30) | (((a) >> (27 - 16)) == 3)) /*--- Opcode EXTEND oder JAL ---*/ /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int check_pc_adress(unsigned int __user *pc, unsigned int usermode) { if(((unsigned long)pc & (0xFF << 24)) == (PAGE_POISON << 24)) { return 1; } if(((unsigned long)pc & (0xFF << 24)) == (POISON_INUSE << 24)) { return 2; } if(((unsigned long)pc & (0xFF << 24)) == (POISON_FREE << 24)) { return 3; } if(((unsigned long)pc & (0xFF << 24)) == (POISON_END << 24)) { return 4; } if(((unsigned long)pc & (0xFF << 24)) == (POISON_FREE_INITMEM << 24)) { return 5; } if(!usermode && ((unsigned long)!kernel_text_address((unsigned long)pc))) { return 6; } return 0; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int print_code_range(struct seq_file *seq, const char *prefix, unsigned int __user *pc, unsigned int mips16, unsigned int usermode, int left_offset, int right_offset, unsigned long *badaddr) { int ret; if((ret = check_pc_adress(pc, usermode))) { if(seq == NULL) printk(KERN_ERR "no code-dump, address could be %s (no memory at this address).\n", ret == 1 ? "PAGE_POISON" : ret == 2 ? "POISON_INUSE" : ret == 3 ? "POISON_FREE" : ret == 4 ? "POISON_END" : ret == 5 ? "POISON_FREE_INITMEM" : "INVAL_KSEGx"); return 0; } if(!mips16) { signed int i; unsigned long access_addr = (unsigned long)pc + sizeof(unsigned int) * left_offset; if(seq == NULL) printk(KERN_ERR"Code(0x%08lx):", access_addr); for(i = left_offset; i < right_offset; i++) { unsigned int pc_value; access_addr = (unsigned long)pc + sizeof(unsigned int) * i; if(usermode) { if(!unlikely(access_ok(VERIFY_READ, access_addr, 4))) { if(seq == NULL)printk(KERN_ERR "[%s] illegal address 0x%lx (sigill)\n", prefix, access_addr); *badaddr = (unsigned long)access_addr; return -1; /*--- sigill; ---*/ } if(__get_user(pc_value, (unsigned int __user *)access_addr)) { if(seq == NULL)printk(KERN_ERR "[%s] load from address 0x%lx failed (sigbus)\n", prefix, access_addr); *badaddr = (unsigned long)access_addr; return -2; /*--- sigbus; ---*/ } } else { pc_value = *((unsigned int *)access_addr); } if(seq) { seq_printf(seq, "%s %s0x%08x%s", i == left_offset ? prefix : "", i == 0 ? "<" : "", pc_value, i == 0 ? ">" : ""); } else { printk(" %s0x%08x%s", i == 0 ? "<" : "", pc_value, i == 0 ? ">" : ""); } } } else { /*--- wegen EXT-Code nur step by step vorhangeln: ---*/ unsigned short code0, code1; unsigned long pc_addr = (unsigned long)pc & ~0x1; unsigned long access_addr = (unsigned long)pc & ~0x1; unsigned long end_addr = ((unsigned long)pc & ~0x1) + sizeof(unsigned short) * right_offset; while(left_offset < 0) { if(usermode && !unlikely(access_ok(VERIFY_READ, access_addr, 4))) { if(seq == NULL)printk(KERN_ERR "[%s] illegal address 0x%lx (sigill)\n", prefix, access_addr); *badaddr = (unsigned long)access_addr; return -1; /*--- sigill; ---*/ } if(__get_user(code1, (unsigned short __user *)(access_addr - sizeof(short)))) { if(seq == NULL)printk(KERN_ERR "[%s] load from 16 bit address 0x%lx failed (sigbus)\n", prefix, access_addr); *badaddr = (unsigned long)access_addr; return -2; /*--- sigbus; ---*/ } if(__get_user(code0, (unsigned short __user *)(access_addr - 2 * sizeof(short)))) { if(seq == NULL)printk(KERN_ERR "[%s] load from 16 bit address 0x%lx failed (sigbus)\n", prefix, access_addr); *badaddr = (unsigned long)access_addr; return -2; /*--- sigbus; ---*/ } if(IS_MIPS16_EXTEND_OR_JAL(code0)) { access_addr -= 2 * sizeof(short); } else { access_addr -= sizeof(short); } left_offset++; } if(seq) { seq_printf(seq, "%s", prefix); } else { printk(KERN_ERR"Code(0x%08lx):", access_addr); } while(access_addr < end_addr) { if(__get_user(code0, (unsigned short __user *)(access_addr))) { if(seq == NULL)printk(KERN_ERR "[%s] load from 16 bit address 0x%lx failed (sigbus)\n", prefix, access_addr); *badaddr = (unsigned long)access_addr; return -2; /*--- sigbus; ---*/ } if(access_addr == pc_addr) { if(IS_MIPS16_EXTEND_OR_JAL(code0)) { access_addr += sizeof(short); if(__get_user(code1, (unsigned short __user *)(access_addr))) { if(seq == NULL)printk(KERN_ERR "[%s] load from 16 bit address 0x%lx failed (sigbus)\n", prefix, access_addr); *badaddr = (unsigned long)access_addr; return -2; /*--- sigbus; ---*/ } if(seq) { seq_printf(seq, " <0x%04x %04x>", code0, code1); } else { printk(" <0x%04x %04x>", code0, code1); } } else { if(seq) { seq_printf(seq, " <0x%04x>", code0); } else { printk(" <0x%04x>", code0); } } } else { if(seq) { seq_printf(seq, " 0x%04x", code0); } else { printk(" 0x%04x", code0); } } access_addr += sizeof(short); } } if(seq == NULL) printk("\n"); return 0; } /** */ #define IS_KERNEL_ADDR 0x1 #define IS_MODULE_ADDR 0x2 #define IS_VMALLOC_ADDR 0x3 #define IS_STACK_ADDR 0x4 int memory_classifier(unsigned long addr) { if((addr >= (unsigned long)_stext && addr <= (unsigned long)_end)) { return IS_KERNEL_ADDR; } else if(is_module_text_address(addr)) { return IS_MODULE_ADDR; } else if(is_vmalloc_addr((void *)addr)) { return IS_VMALLOC_ADDR; } else if(object_is_on_stack((void *)addr)) { return IS_STACK_ADDR; } return 0; } /** * @return NULL no virtual addr */ static struct page *memory_page_classifier(unsigned long addr) { if(virt_addr_valid(addr)) { return virt_to_head_page((void *)addr); } return NULL; } /** */ static char *print_vmflags(char *txt, unsigned int txtlen, unsigned long vm_flags) { char *txt_start = txt; txt[0] = 0; if (vm_flags & VM_IOREMAP){snprintf_add(txt, txtlen, "ioremap ")}; if (vm_flags & VM_ALLOC) {snprintf_add(txt, txtlen, "vmalloc ")}; if (vm_flags & VM_MAP) {snprintf_add(txt, txtlen, "vmap ")}; if (vm_flags & VM_USERMAP){snprintf_add(txt, txtlen, "user ")}; if (vm_flags & VM_VPAGES) {snprintf_add(txt, txtlen, "vpages ")}; return txt_start; } /** */ char *arch_print_memory_classifier(char *txt, unsigned int txtlen, unsigned long addr, int include_addr_prefix) { char *modname __maybe_unused; char sym[KSYM_SYMBOL_LEN]; char txtbuf[TASK_COMM_LEN + 16]; char *txt_start = txt; unsigned long caller, size, offset __maybe_unused, start, flags, vmflags; int freed, type; const char *name; struct page *page; struct zone *zone; if(include_addr_prefix) { snprintf_add(txt, txtlen, "0x%08lx ", addr); } else { txt[0] = 0; } type = memory_classifier(addr); switch(type) { case IS_KERNEL_ADDR: case IS_MODULE_ADDR: #ifdef CONFIG_KALLSYMS name = kallsyms_lookup(addr, &size, &offset, &modname, sym); if(!name) { return txt_start; } snprintf_add(txt, txtlen, "%s+%#lx/%#lx", name, offset, size); if(modname) { snprintf_add(txt, txtlen, " [%s]", modname); } #else #if defined(CONFIG_AVM_BOOTMEM) if(!IS_ERR(module_alloc_find_module_name)) { module_alloc_find_module_name(txt, txt + txtlen, addr); } #endif /*--- #if defined(CONFIG_AVM_BOOTMEM) ---*/ #endif return txt_start; case IS_VMALLOC_ADDR: if(is_yield_context()) { return txt_start; } if((start = get_vmap_area(addr, &caller, &size, &vmflags))) { snprintf(txt, txtlen, "[%s: size:%lu start:%p+0x%lx alloced by:%pS]", print_vmflags(txtbuf, sizeof(txtbuf), vmflags), size, (void *)start, addr - start, (void *)caller); } return txt_start; case IS_STACK_ADDR: break; default: break; } if((start = get_taskstack_area(addr, txtbuf, sizeof(txtbuf), type == IS_STACK_ADDR ? 1 : 0))) { snprintf(txt, txtlen, "[%s: %p+0x%lx]", txtbuf, (void *)start, addr - start); return txt_start; } #ifdef CONFIG_AVM_RTE if((start = get_simplemempool_area(addr, &caller, txtbuf, sizeof(txtbuf), &size, &freed))) { if(caller) { snprintf(sym, sizeof(sym), " allocated by:%pS ", (void *)caller); } else { sym[0] = 0; } snprintf(txt, txtlen, "[smempool: type:%s size:%lu start:%p%c0x%lx %s%s]", txtbuf, size, (void *)start, addr >= start ? '+' : '-', addr >= start ? addr - start : start - addr, sym, freed == 0 ? "" : freed == 1 ? "free" : freed == 2 ? "ctrl" : "padding"); return txt_start; } if(is_yield_context()) { return txt_start; } #endif /* RTE */ page = memory_page_classifier(addr); if(!page) { return txt_start; } zone = page_zone(page); if(!spin_trylock_irqsave(&zone->lock, flags)) { return txt_start; } if (PageSlab(page)) { if((start = get_kmemalloc_area(addr, &caller, &name, &size, &freed))) { if(caller) { snprintf(sym, sizeof(sym), " %s by:%pS", freed ? "freed" : "allocated", (void *)caller); } else { sym[0] = 0; } snprintf(txt, txtlen, "[slab: type:%s size:%lu start:0x%p+0x%lx%s]", name, size, (void *)start, addr - start, sym); } } else if(PageReserved(page)) { snprintf(txt, txtlen, "[page: type:reserved]"); } else if((atomic_read(&page->_count))) { unsigned long current_pc = 0; #if defined(CONFIG_AVM_PAGE_TRACE) current_pc = avm_get_page_current_pc(page); #endif/*--- #if defined(CONFIG_AVM_PAGE_TRACE) ---*/ if(current_pc) { snprintf(sym, sizeof(sym), " by:%pS", (void *)current_pc); } else { sym[0] = 0; } snprintf(txt, txtlen, "[page: type:alloc%s]", sym); } spin_unlock_irqrestore(&zone->lock, flags); return txt_start; } EXPORT_SYMBOL(print_memory_classifier); /** */ static int match_data(unsigned long data, unsigned long data_array[], unsigned int array_elements) { unsigned int i; for(i = 0; i < array_elements; i++) { if(data_array[i] == 0) { data_array[i] = data; return 0; } if(data_array[i] == data) { return 1; } } return 0; } /** */ void arch_show_stacktrace_memoryclassifier(const struct pt_regs *pregs) { unsigned long data_hist[40]; char txt[KSYM_SYMBOL_LEN]; unsigned int start = 0, limit = 0; unsigned long stackdata; unsigned long __user *sp; mm_segment_t old_fs; if(pregs == NULL) { return; } old_fs = get_fs(); sp = (unsigned long __user *)pregs->regs[29]; while ((unsigned long) sp & (PAGE_SIZE - 1)) { if (limit > 39) { break; } set_fs(KERNEL_DS); if (__get_user(stackdata, sp++)) { break; } if(start >= ARRAY_SIZE(data_hist)) { printk(KERN_ERR"...\n"); break; } if(stackdata && match_data(stackdata, data_hist, ARRAY_SIZE(data_hist))) { continue; } print_memory_classifier(txt, sizeof(txt), stackdata, 0); if(txt[0]) { if(start == 0) { printk(KERN_ERR"Classified pointer on stack:\n"); } printk(KERN_ERR"%08lx %s\n", stackdata, txt); start++; } set_fs(old_fs); limit++; } } /** */ void arch_show_register_memoryclassifier(const struct pt_regs *pregs) { char txt[KSYM_SYMBOL_LEN]; unsigned int i, start = 0; if(pregs == NULL) { return; } for (i = 0; i < ARRAY_SIZE(pregs->regs); i++) { print_memory_classifier(txt, sizeof(txt), pregs->regs[i], 0); if(txt[0]) { if(start == 0) { start = 1; printk(KERN_ERR"Classified pointer on registers:\n"); } printk(KERN_ERR" $%2u : %08lx %s\n", i, pregs->regs[i], txt); } } } /** */ static int print_mem_config(struct mm_struct *mmm, unsigned long addr) { struct vm_area_struct *vm; struct vm_area_struct *vm_last = NULL; unsigned int i = 0; if (!virt_addr_valid(mmm)) return 0; vm = mmm->mmap; while (vm) { if (!virt_addr_valid(vm)) { pr_err("%s: error: corrupt vm %p vm_last=%pS\n", __func__, vm, vm_last); return 0; } if ((addr >= vm->vm_start) && (addr < vm->vm_end)) { pr_err("Adresse-Segment(%d): 0x%lx: 0x%lx :0x%lx (offset 0x%lx)", i, vm->vm_start, addr, vm->vm_end, addr - vm->vm_start); if (vm->vm_file) { pr_err(" Path='%s'", vm->vm_file->f_path.dentry->d_name.name); } pr_err("\n"); return 0; } vm_last = vm; vm = vm->vm_next; i++; } return 1; } /** */ void show_code_position_by_epc(char *prefix, struct pt_regs *regs) { unsigned int __user *pc; unsigned long addr = 0UL; pc = (unsigned int __user *) exception_epc(regs); printk(KERN_ERR "[%s] pc=0x%p(%pF) addr=0x%08lx task=%s pid=%d ra=0x%08lx(%pF)\n", prefix, pc, pc, regs->cp0_badvaddr, current->comm, current->pid, regs->regs[31], (void *)regs->regs[31] ); print_code_range(NULL, prefix, pc, regs->cp0_epc & 0x1, user_mode(regs), -2, 3, &addr); if (user_mode(regs)) { if(print_mem_config(current->active_mm, (unsigned long)pc)) print_mem_config(current->mm, (unsigned long)pc); } } /*------------------------------------------------------------------------------------------*\ * Unaligned-Helper \*------------------------------------------------------------------------------------------*/ #define UNALIGNED_WARN 0x1 #define UNALIGNED_BACKTRACE 0x8 enum _unaligned_mode { UNALIGNED_ACTION_FIXUP = 2, UNALIGNED_ACTION_FIXUP_WARN = 3 | UNALIGNED_WARN, /*-- verodern eigentlich Dummy :-) ---*/ UNALIGNED_ACTION_SIGNAL = 4, UNALIGNED_ACTION_SIGNAL_WARN = 5 | UNALIGNED_WARN, /*-- verodern eigentlich Dummy :-) ---*/ UNALIGNED_ACTION_FIXUP_WARN_BT = UNALIGNED_ACTION_FIXUP_WARN | UNALIGNED_WARN | UNALIGNED_BACKTRACE }; static int ai_usermode = UNALIGNED_ACTION_FIXUP /*--- | UNALIGNED_WARN ---*/ ; static int ai_kernelmode = UNALIGNED_ACTION_FIXUP /*--- | UNALIGNED_WARN ---*/ ; static int developer_ai_usermode; static int developer_ai_kernelmode; #define DEVELOPER_WARNING_THRESHOLD 256 static struct _ai_info { unsigned long ai_count; unsigned long last_pc; char last_comm[TASK_COMM_LEN]; } ai_info[2]; #define AI_SYS 0 #define AI_USER 1 #define ai_user ai_info[AI_USER].ai_count #define ai_sys ai_info[AI_SYS].ai_count /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static inline void inc_ai_info(int ai_idx, unsigned long pc) { ai_info[ai_idx].ai_count++; ai_info[ai_idx].last_pc = pc; memcpy(ai_info[ai_idx].last_comm, current->comm, TASK_COMM_LEN); } #define UNALIGNED_MAX_SCORE_ENTRIES 8 #if defined(UNALIGNED_MAX_SCORE_ENTRIES) /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ struct _unaligned_access_score_entry { unsigned long unaligneds; unsigned long ts; unsigned short mips16; unsigned short caddr_of; /*--- > 0: pc-offset in codetable in bytes---*/ #define CODE_ENTRIES 5 unsigned int code[CODE_ENTRIES]; /*--- Code muss gesichert werden, da evtl. Virtual Memory ---*/ char shortcomm[8]; /*--- Name mal sichern falls beendet ---*/ pid_t pid; void *pc; }; #define LEFT_CODE_OFFSET 2 #define RIGHT_CODE_OFFSET(a) min((a), CODE_ENTRIES - LEFT_CODE_OFFSET) /*--------------------------------------------------------------------------------*\ * 0: Fehler * sonst: liefert byte-offset zum pc \*--------------------------------------------------------------------------------*/ static unsigned short cpy_code_range(unsigned int __user *pc, unsigned int usermode, unsigned int codetable[], unsigned int code_entries, int left_offset) { signed int i; unsigned int idx = 0; int right_offset; unsigned short ret = 0; int mips16_offset = ((unsigned long)pc) & 0x2; unsigned long access_addr = ((unsigned long)pc) & ~0x3; right_offset = left_offset + code_entries; if(check_pc_adress(pc, usermode)) { memset(codetable, 0, code_entries * sizeof(unsigned int)); return ret; } access_addr += sizeof(unsigned int) * left_offset; for(i = left_offset; i < right_offset; i++) { unsigned int pc_value; if(usermode) { if(!unlikely(access_ok(VERIFY_READ, access_addr, 4))) { return ret; } if(__get_user(pc_value, (unsigned int __user *)access_addr)) { return ret; } } else { pc_value = *((unsigned int *)access_addr); } codetable[idx] = pc_value; if(i == 0) { ret = (unsigned char *)&codetable[idx] - (unsigned char *)&codetable[0] + mips16_offset; } idx++; access_addr += sizeof(unsigned int); } return ret; } /*--------------------------------------------------------------------------------*\ * usermode: 0 Kernel * 1 MIPS32 * 2 MIPS16 \*--------------------------------------------------------------------------------*/ static void add_unaligned_access_to_table(void *pc, struct _unaligned_access_score_entry table[], unsigned int table_entries, unsigned int usermode) { unsigned int ts_idx = 0, ts_score = 0; unsigned int i; for(i = 0; i < table_entries; i++) { if(table[i].unaligneds) { if(usermode) { if(task_pid_nr(current) != table[i].pid) { continue; } } if(pc == table[i].pc) { table[i].unaligneds++; /*--- printk(KERN_ERR "%s:[%u]add %s %s pid=%u pc=%p %lu\n", __func__, i, usermode ? "USER" : "KERNEL", current->comm, table[i].pid, pc, table[i].unaligneds); ---*/ return; } continue; } /*--- printk(KERN_ERR "%s:[%u]initial %s %s pc=%p pid=%u\n", __func__, i, usermode ? "USER" : "KERNEL", current->comm, pc, table[i].pid); ---*/ ts_idx = i; goto table_settings; } /*--- alle besetzt: bewerte per unaligneds / per time ---*/ for(i = 0; i < table_entries; i++) { unsigned long diff = jiffies - table[i].ts; unsigned int score = diff / (table[i].unaligneds | 0x1); /*--- printk(KERN_ERR "%s:[%u]score %d diff %u unaligneds=%lu ts_score %u idx=%u\n", __func__, i, score, diff, table[i].unaligneds, ts_score, ts_idx); ---*/ if(score > ts_score) { ts_score = score; ts_idx = i; } } /*--- printk(KERN_ERR "%s:[%u]replace %s old: unaligneds=%lu pc=%p pid=%u ts_score=%u new=%s pc=%p\n", __func__, ts_idx, usermode ? "USER" : "KERNEL", table[ts_idx].unaligneds, table[ts_idx].pc, table[ts_idx].pid, ts_score, current->comm, pc); ---*/ table_settings: table[ts_idx].unaligneds = 1; table[ts_idx].pc = pc; table[ts_idx].ts = jiffies; table[ts_idx].pid = task_pid_nr(current); table[ts_idx].mips16 = (usermode & 0x2) ? 1 : 0; table[ts_idx].caddr_of = cpy_code_range(pc, usermode, table[ts_idx].code, ARRAY_SIZE(table[ts_idx].code), -LEFT_CODE_OFFSET); strncpy(table[ts_idx].shortcomm, current->comm, sizeof(table[ts_idx].shortcomm)); } /**--------------------------------------------------------------------------------**\ * die Normierung erfolgt entsprechend des Zeitfensters \**--------------------------------------------------------------------------------**/ static const char *norm_ai(unsigned long *ai, unsigned int sec) { if(sec >= 60 * 60) { *ai *= 60 * 60; return "h"; } if(sec >= 60) { *ai *= 60; return "min"; } return "s"; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static unsigned long show_unaligned_access_table(struct seq_file *seq, struct _unaligned_access_score_entry table[], unsigned int table_entries, unsigned int usermode) { char comm[sizeof(table[0].shortcomm) + 1]; unsigned int i; unsigned long sum = 0; unsigned long address; seq_printf(seq, "%s:\nunaligneds\t unaligneds/time\t\n", usermode ? "User-Scorelist" : "System-Scorelist"); for(i = 0; i < table_entries; i++) { if(table[i].unaligneds) { unsigned long ai_per_time = table[i].unaligneds; unsigned long sec = (jiffies - table[i].ts) / HZ; const char *post_fix = norm_ai(&ai_per_time, sec); sum += table[i].unaligneds; if(sec == 0) sec = 1; if(usermode) { struct pid *ppid; struct task_struct *tsk; char *pcomm; if((ppid = find_get_pid(table[i].pid)) && (tsk = get_pid_task(ppid, PIDTYPE_PID))) { pcomm = tsk->comm; } else { memcpy(comm, table[i].shortcomm, sizeof(comm) - 1); comm[sizeof(comm) - 1] = 0; pcomm = comm; tsk = NULL; } seq_printf(seq, "%10lu \t%5lu.%02lu/%s \t %s(%u) pc=0x%p ", table[i].unaligneds, ai_per_time / sec, ((ai_per_time * 100) / sec) % 100, post_fix, pcomm, table[i].pid, table[i].pc); if(table[i].caddr_of) { print_code_range(seq, ":", (unsigned int __user *)((unsigned long )&table[i].code + table[i].caddr_of), table[i].mips16, 0 /* use copy in kernel space */, -LEFT_CODE_OFFSET, RIGHT_CODE_OFFSET(2), &address); } if(tsk) { put_task_struct(tsk); } if(ppid) { put_pid(ppid); } seq_printf(seq, "\n"); } else { seq_printf(seq, "%10lu \t%5lu.%02lu/%s \t 0x%p(%pF) ", table[i].unaligneds, ai_per_time / sec, ((ai_per_time * 100) / sec) % 100, post_fix, table[i].pc, table[i].pc); if(table[i].caddr_of) { print_code_range(seq, ":", (unsigned int __user *)((unsigned long )&table[i].code + table[i].caddr_of), table[i].mips16, 0, -LEFT_CODE_OFFSET, RIGHT_CODE_OFFSET(2), &address); } seq_printf(seq, "\n"); } } } return sum; } static struct _unaligned_access_score_entry user_score_table[UNALIGNED_MAX_SCORE_ENTRIES], sys_score_table[UNALIGNED_MAX_SCORE_ENTRIES]; #endif/*--- #if defined(UNALIGNED_MAX_SCORE_ENTRIES) ---*/ /**--------------------------------------------------------------------------------**\ * \\brief: * Liefere Unaligned-Daten * user: 0 Kernel 1 Userland * ret: Zeiger auf last_comm des Userprozesses \**--------------------------------------------------------------------------------**/ const char *get_last_unaligned_info(unsigned long *ai_count, unsigned long *last_pc, int user) { int idx = user ? AI_USER : AI_SYS; if(ai_count) *ai_count = ai_info[idx].ai_count; if(last_pc) *last_pc = ai_info[idx].last_pc; return ai_info[idx].last_comm; } /*--------------------------------------------------------------------------------*\ * ret: 0 ok, sonst -1: sigill, -2 sigbus ausloesen \*--------------------------------------------------------------------------------*/ int show_unaligned_position(struct pt_regs *regs){ unsigned long addr = 0UL; unsigned int __user *pc; pc = (unsigned int __user *) exception_epc(regs); if (!user_mode(regs)) { inc_ai_info(AI_SYS, (unsigned long)pc); #if defined(UNALIGNED_MAX_SCORE_ENTRIES) add_unaligned_access_to_table(pc, sys_score_table, ARRAY_SIZE(sys_score_table), 0); #endif/*--- #if defined(UNALIGNED_MAX_SCORE_ENTRIES) ---*/ if(ai_kernelmode & UNALIGNED_WARN) { int ret; printk(KERN_ERR "[kernel-unaligned %lu] pc=0x%p(%pF) addr=0x%08lx task=%s pid=%d ra=0x%08lx(%pF)\n", ai_sys, pc, pc, regs->cp0_badvaddr, current->comm, current->pid, regs->regs[31], (void *)regs->regs[31] ); ret = print_code_range(NULL, "kernel-unaligned", pc, regs->cp0_epc & 0x1, user_mode(regs), -2, 3, &addr); if(ret) { return ret; } if(print_mem_config(current->active_mm, (unsigned long)pc)) print_mem_config(current->mm, (unsigned long)pc); if(ai_kernelmode & UNALIGNED_BACKTRACE) { show_backtrace(current, regs); } } if(unlikely(developer_ai_kernelmode && (ai_sys > DEVELOPER_WARNING_THRESHOLD))) { ai_kernelmode |= developer_ai_kernelmode; developer_ai_kernelmode = 0; /*--- switch on only one time ---*/ } return (ai_kernelmode & UNALIGNED_ACTION_SIGNAL) ? -2 : 0; } inc_ai_info(AI_USER, (unsigned long)pc); #if defined(UNALIGNED_MAX_SCORE_ENTRIES) add_unaligned_access_to_table(pc, user_score_table, ARRAY_SIZE(user_score_table), 1 + (regs->cp0_epc & 0x1)); #endif/*--- #if defined(UNALIGNED_MAX_SCORE_ENTRIES) ---*/ if(ai_usermode & UNALIGNED_WARN) { int ret; printk(KERN_ERR"Alignment trap %lu: %s (%d) PC=0x%p Address=0x%08lx\n", ai_user, current->comm, task_pid_nr(current), pc, regs->cp0_badvaddr); ret = print_code_range(NULL, "kernel-unaligned", pc, regs->cp0_epc & 0x1, user_mode(regs), -2, 3, &addr); if(ret) { return ret; } if(print_mem_config(current->active_mm, (unsigned long)pc)) { print_mem_config(current->mm, (unsigned long)pc); } /*--- show_registers(regs); ---*/ } if(unlikely(developer_ai_usermode && (ai_user > DEVELOPER_WARNING_THRESHOLD))) { ai_usermode |= developer_ai_usermode; developer_ai_usermode = 0; /*--- switch on only one time ---*/ } return (ai_usermode & UNALIGNED_ACTION_SIGNAL) ? -2 : 0; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static const char *map_mode(enum _unaligned_mode mode) { switch(mode) { case UNALIGNED_ACTION_FIXUP: return "(fixup) "; case UNALIGNED_ACTION_FIXUP_WARN: return "(fixup+warn) "; case UNALIGNED_ACTION_SIGNAL: return "(signal) "; case UNALIGNED_ACTION_SIGNAL_WARN: return "(signal+warn)"; case UNALIGNED_ACTION_FIXUP_WARN_BT: return "(fixup+warn+backtrace)"; } return ""; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void unalignment_proc_read(struct seq_file *seq, void *ref __maybe_unused){ seq_printf(seq, "User:\t\t%lu\n", ai_user); seq_printf(seq, "System:\t\t%lu\n", ai_sys); seq_printf(seq, "User faults:\t%i %s\n", ai_usermode, map_mode(ai_usermode)); seq_printf(seq, "Kernel faults:\t%i %s\n", ai_kernelmode, map_mode(ai_kernelmode)); #if defined(UNALIGNED_MAX_SCORE_ENTRIES) if(ai_user) { if(show_unaligned_access_table(seq, user_score_table, ARRAY_SIZE(user_score_table), 1) != ai_user) { seq_printf(seq, "... only the newest user-unaligneds shown\n"); } } if(ai_sys) { if(show_unaligned_access_table(seq, sys_score_table, ARRAY_SIZE(sys_score_table), 0) != ai_sys) { seq_printf(seq, "... only the newest kernel-unaligneds shown\n"); } } #endif/*--- #if defined(UNALIGNED_MAX_SCORE_ENTRIES) ---*/ } /*--- #define DSP_ASE_UNALIGNED_CHECK ---*/ #if defined(DSP_ASE_UNALIGNED_CHECK) /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static inline unsigned int lwx_unaligned_check(void *addr, unsigned int index) { unsigned int erg; __asm__ __volatile__ (".set\tnoat\n" "LWX %0, %1(%2) \n" : "=r" (erg) : "r" (index), "r" ((unsigned long)addr) : "cc"); return erg; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static inline signed int lhx_unaligned_check(void *addr, unsigned int index) { signed int erg; __asm__ __volatile__ (".set\tnoat\n" "LHX %0, %1(%2) \n" : "=r" (erg) : "r" (index), "r" ((unsigned long)addr) : "cc"); return erg; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static unsigned int test_table[] = { 0x1 << 0, 0xF1 << 0, 0x3 << 0, 0xFF << 0, 0x1 << 8, 0xF1 << 8, 0x3 << 8, 0xFF << 8, 0x1 << 16, 0xF1 << 16, 0x3 << 16, 0xFF << 16, 0x1 << 24, 0xF1 << 24, 0x3 << 24, 0xFF << 24 }; static signed short test2_table[] = { 0x1 << 0, 0xF1 << 0, 0x3 << 0, 0xFF << 0, 0x1 << 8, 0xF1 << 8, 0x3 << 8, 0xFF << 8, }; /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void check_dsp_ase_unaligneds(void) { unsigned int i; unsigned int unalignedtable[16 + 1]; unsigned int *p = (unsigned int *)((unsigned char *)unalignedtable +1); memcpy(p, test_table, sizeof(test_table)); for(i = 0; i < ARRAY_SIZE(test_table); i++) { unsigned int val = lwx_unaligned_check(p, i * 4); if(val != test_table[i]) { printk(KERN_ERR"%s:#1 error: val(%x) != table[%u] (%x)\n", __func__, val, i, test_table[i]); } } for(i = 0; i < ARRAY_SIZE(test_table); i++) { unsigned int val = lwx_unaligned_check(unalignedtable, (i * 4) + 1); if(val != test_table[i]) { printk(KERN_ERR"%s:#2 error: val(%x) != table[%u] (%x)\n", __func__, val, i, test_table[i]); } } memcpy(p, test2_table, sizeof(test2_table)); for(i = 0; i < ARRAY_SIZE(test2_table); i++) { signed int val = lhx_unaligned_check(p, i * 2); if(val != (signed int)test2_table[i]) { printk(KERN_ERR"%s:#3 error: val(%x) != table[%u] (%x)\n", __func__, val, i, (signed int)test2_table[i]); } } for(i = 0; i < ARRAY_SIZE(test2_table); i++) { signed int val = lhx_unaligned_check(unalignedtable, (i * 2) + 1); if(val != (signed int)test2_table[i]) { printk(KERN_ERR"%s:#4 error: val(%x) != table[%u] (%x)\n", __func__, val, i, (signed int)test2_table[i]); } } } #endif/*--- #if defined(DSP_ASE_UNALIGNED_CHECK) ---*/ /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int unalignment_proc_write(char *cmd, void *ref __maybe_unused) { int mode = cmd[0]; #if defined(DSP_ASE_UNALIGNED_CHECK) if(mode == 'T') { check_dsp_ase_unaligneds(); return 0; } #endif/*--- #if defined(DSP_ASE_UNALIGNED_CHECK) ---*/ if (mode >= '2' && mode <= '5') { ai_usermode = mode - '0'; printk(KERN_ERR "set user unaligned-mode: %s\n", map_mode(ai_usermode)); } else if (mode >= '6' && mode <= '8') { ai_kernelmode = mode == '6' ? UNALIGNED_ACTION_FIXUP : mode == '7' ? UNALIGNED_ACTION_FIXUP_WARN : UNALIGNED_ACTION_FIXUP_WARN_BT; printk(KERN_ERR "set kernel unaligned-mode: %s\n", map_mode(ai_kernelmode)); } else { printk(KERN_ERR "parameter: user '2' %s '3' %s '4' %s '5' %s \n", map_mode(UNALIGNED_ACTION_FIXUP), map_mode(UNALIGNED_ACTION_FIXUP_WARN), map_mode(UNALIGNED_ACTION_SIGNAL), map_mode(UNALIGNED_ACTION_SIGNAL_WARN)); printk(KERN_ERR " system '6' %s '7' %s '8' %s\n", map_mode(UNALIGNED_ACTION_FIXUP), map_mode(UNALIGNED_ACTION_FIXUP_WARN), map_mode(UNALIGNED_ACTION_FIXUP_WARN_BT)); } return 0; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int __init alignment_init(void) { int ret; proc_mkdir("cpu", NULL); ret = add_simple_proc_file( "cpu/alignment", unalignment_proc_write, unalignment_proc_read, NULL); #if !defined(CONFIG_ATH79_MACH_AVM_HW238) if(avm_fw_is_internal()) { developer_ai_usermode = UNALIGNED_WARN; developer_ai_kernelmode = UNALIGNED_WARN; } #endif return ret; } __initcall(alignment_init);