/* * Copyright (C) 2013 Spreadtrum Communications Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CORE_STR "CORE" #ifndef ELF_CORE_EFLAGS #define ELF_CORE_EFLAGS 0 #endif #define NR_KCORE_MEM 80 #define SYSDUMP_MAGIC "SPRD_SYSDUMP_119" #define SYSDUMP_NOTE_BYTES (ALIGN(sizeof(struct elf_note), 4) + \ ALIGN(sizeof(CORE_STR), 4) + \ ALIGN(sizeof(struct elf_prstatus), 4)) typedef char note_buf_t[SYSDUMP_NOTE_BYTES]; note_buf_t __percpu *crash_notes; /* An ELF note in memory */ struct memelfnote { const char *name; int type; unsigned int datasz; void *data; }; struct sysdump_info { char magic[16]; char time[32]; char dump_path[128]; int elfhdr_size; int mem_num; unsigned long dump_mem_paddr; int crash_key; }; struct sysdump_extra { int enter_id; int enter_cpu; char reason[256]; struct pt_regs cpu_context[CONFIG_NR_CPUS]; }; struct sysdump_config { int enable; int crashkey_only; int dump_modem; int reboot; char dump_path[128]; }; static struct sysdump_info *sprd_sysdump_info = NULL; /* must be global to let gdb know */ struct sysdump_extra sprd_sysdump_extra = { .enter_id = -1, .enter_cpu = -1, .reason = {0}, }; static struct sysdump_config sysdump_conf = { .enable = 1, .crashkey_only = 0, .dump_modem = 1, .reboot = 1, .dump_path = "", }; #ifdef CONFIG_SPRD_SYSDUMP struct sysdump_mem sprd_dump_mem[] = { #if defined(CONFIG_ARCH_SCX15) #if defined(CONFIG_SPRD_MODEM_TD) { .paddr = CONFIG_PHYS_OFFSET, .vaddr = PAGE_OFFSET, .soff = 0xffffffff, .size = CPT_START_ADDR - CONFIG_PHYS_OFFSET, .type = SYSDUMP_RAM, }, { .paddr = CPT_START_ADDR, .vaddr = PAGE_OFFSET + (CPT_START_ADDR - CONFIG_PHYS_OFFSET), .soff = 0xffffffff, .size = CPT_TOTAL_SIZE, #ifdef CONFIG_SIPC_TD .type = SYSDUMP_MODEM, #else .type = SYSDUMP_RAM, #endif }, { .paddr = CPT_START_ADDR + CPT_TOTAL_SIZE, .vaddr = PAGE_OFFSET + CPT_START_ADDR + CPT_TOTAL_SIZE - CONFIG_PHYS_OFFSET, .soff = 0xffffffff, .size = WCN_START_ADDR - CPT_START_ADDR - CPT_TOTAL_SIZE, .type = SYSDUMP_RAM, }, #else { .paddr = CONFIG_PHYS_OFFSET, .vaddr = PAGE_OFFSET, .soff = 0xffffffff, .size = CPW_START_ADDR - CONFIG_PHYS_OFFSET, .type = SYSDUMP_RAM, }, { .paddr = CPW_START_ADDR, .vaddr = PAGE_OFFSET + (CPW_START_ADDR - CONFIG_PHYS_OFFSET), .soff = 0xffffffff, .size = CPW_TOTAL_SIZE, #ifdef CONFIG_SIPC_WCDMA .type = SYSDUMP_MODEM, #else .type = SYSDUMP_RAM, #endif }, { .paddr = CPW_START_ADDR + CPW_TOTAL_SIZE, .vaddr = PAGE_OFFSET + CPW_START_ADDR + CPW_TOTAL_SIZE - CONFIG_PHYS_OFFSET, .soff = 0xffffffff, .size = WCN_START_ADDR - CPW_START_ADDR - CPW_TOTAL_SIZE, .type = SYSDUMP_RAM, }, #endif #elif defined(CONFIG_ARCH_SCX30G) #if defined(CONFIG_SPRD_MODEM_TD) { .paddr = CONFIG_PHYS_OFFSET, .vaddr = PAGE_OFFSET, .soff = 0xffffffff, .size = CPT_START_ADDR - CONFIG_PHYS_OFFSET, .type = SYSDUMP_RAM, }, { .paddr = CPT_START_ADDR, .vaddr = PAGE_OFFSET + (CPT_START_ADDR - CONFIG_PHYS_OFFSET), .soff = 0xffffffff, .size = CPT_TOTAL_SIZE, #ifdef CONFIG_SIPC_TD .type = SYSDUMP_MODEM, #else .type = SYSDUMP_RAM, #endif }, #else { .paddr = CONFIG_PHYS_OFFSET, .vaddr = PAGE_OFFSET, .soff = 0xffffffff, .size = CPW_START_ADDR - CONFIG_PHYS_OFFSET, .type = SYSDUMP_RAM, }, { .paddr = CPW_START_ADDR, .vaddr = PAGE_OFFSET + (CPW_START_ADDR - CONFIG_PHYS_OFFSET), .soff = 0xffffffff, .size = CPW_TOTAL_SIZE, #ifdef CONFIG_SIPC_WCDMA .type = SYSDUMP_MODEM, #else .type = SYSDUMP_RAM, #endif }, #endif #elif defined(CONFIG_ARCH_SCX35L) { .paddr = CONFIG_PHYS_OFFSET, .vaddr = PAGE_OFFSET, .soff = 0xffffffff, .size = GGE_START_ADDR - CONFIG_PHYS_OFFSET, .type = SYSDUMP_RAM, }, { .paddr = GGE_START_ADDR, .vaddr = PAGE_OFFSET + (GGE_START_ADDR - CONFIG_PHYS_OFFSET), .soff = 0xffffffff, .size = GGE_TOTAL_SIZE, #ifdef CONFIG_SIPC_GGE .type = SYSDUMP_MODEM, #else .type = SYSDUMP_RAM, #endif }, { .paddr = LTE_START_ADDR, .vaddr = PAGE_OFFSET + (LTE_START_ADDR - CONFIG_PHYS_OFFSET), .soff = 0xffffffff, .size = LTE_TOTAL_SIZE, #ifdef CONFIG_SIPC_LTE .type = SYSDUMP_MODEM, #else .type = SYSDUMP_RAM, #endif }, #else { .paddr = CONFIG_PHYS_OFFSET, .vaddr = PAGE_OFFSET, .soff = 0xffffffff, .size = CPT_START_ADDR - CONFIG_PHYS_OFFSET, .type = SYSDUMP_RAM, }, { .paddr = CPT_START_ADDR, .vaddr = PAGE_OFFSET + (CPT_START_ADDR - CONFIG_PHYS_OFFSET), .soff = 0xffffffff, .size = CPT_TOTAL_SIZE, #ifdef CONFIG_SIPC_TD .type = SYSDUMP_MODEM, #else .type = SYSDUMP_RAM, #endif }, { .paddr = CPT_START_ADDR + CPT_TOTAL_SIZE, .vaddr = PAGE_OFFSET + (CPT_START_ADDR + CPT_TOTAL_SIZE - CONFIG_PHYS_OFFSET), .soff = 0xffffffff, .size = CPW_START_ADDR - (CPT_START_ADDR + CPT_TOTAL_SIZE), .type = SYSDUMP_RAM, }, { .paddr = CPW_START_ADDR, .vaddr = PAGE_OFFSET + (CPW_START_ADDR - CONFIG_PHYS_OFFSET), .soff = 0xffffffff, .size = CPW_TOTAL_SIZE, #ifdef CONFIG_SIPC_WCDMA .type = SYSDUMP_MODEM, #else .type = SYSDUMP_RAM, #endif }, #endif #ifdef CONFIG_ARCH_SCX35L { .paddr = LTE_START_ADDR + LTE_TOTAL_SIZE, .vaddr = PAGE_OFFSET + (LTE_START_ADDR + LTE_TOTAL_SIZE - CONFIG_PHYS_OFFSET), .soff = 0xffffffff, .size = 0, .type = SYSDUMP_RAM, }, #else { .paddr = WCN_START_ADDR, .vaddr = PAGE_OFFSET + (WCN_START_ADDR - CONFIG_PHYS_OFFSET), .soff = 0xffffffff, .size = WCN_TOTAL_SIZE, //#ifdef CONFIG_SIPC_WCN .type = SYSDUMP_MODEM, //#else // .type = SYSDUMP_RAM, //#endif }, { .paddr = WCN_START_ADDR + WCN_TOTAL_SIZE, .vaddr = PAGE_OFFSET + (WCN_START_ADDR + WCN_TOTAL_SIZE - CONFIG_PHYS_OFFSET), .soff = 0xffffffff, .size = 0, /* fill this dynamically according to real ram size */ .type = SYSDUMP_RAM, }, #endif /* { .paddr = SPRD_AHB_PHYS, .vaddr = SPRD_AHB_BASE, .soff = 0x0, .size = SPRD_AHB_SIZE, .type = SYSDUMP_IOMEM, }, { .paddr = SPRD_INTC0_PHYS, .vaddr = SPRD_INTC0_BASE, .soff = 0x0, .size = SPRD_INTC0_SIZE, .type = SYSDUMP_IOMEM, }, { .paddr = SPRD_GPTIMER0_PHYS, .vaddr = SPRD_GPTIMER0_BASE, .soff = 0x0, .size = SPRD_GPTIMER0_SIZE, .type = SYSDUMP_IOMEM, }, { .paddr = SPRD_ADI_PHYS, .vaddr = SPRD_ADI_BASE, .soff = 0x0, .size = SPRD_ADI_SIZE, .type = SYSDUMP_IOMEM, }, { .paddr = SPRD_GPIO_PHYS, .vaddr = SPRD_GPIO_BASE, .soff = 0x0, .size = SPRD_GPIO_SIZE, .type = SYSDUMP_IOMEM, }, { .paddr = SPRD_EIC_PHYS, .vaddr = SPRD_EIC_BASE, .soff = 0x0, .size = SPRD_EIC_SIZE, .type = SYSDUMP_IOMEM, }, { .paddr = SPRD_GREG_PHYS, .vaddr = SPRD_GREG_BASE, .soff = 0x0, .size = SPRD_GREG_SIZE, .type = SYSDUMP_IOMEM, },*/ }; int sprd_dump_mem_num = ARRAY_SIZE(sprd_dump_mem); #endif /* CONFIG_SPRD_SYSDUMP */ static size_t get_elfhdr_size(int nphdr) { size_t elfhdr_len; elfhdr_len = sizeof(struct elfhdr) + (nphdr + 1)*sizeof(struct elf_phdr) + ((sizeof(struct elf_note)) * 3 + roundup(sizeof(CORE_STR), 4)) + roundup(sizeof(struct elf_prstatus), 4) + roundup(sizeof(struct elf_prpsinfo), 4) + roundup(sizeof(struct task_struct), 4); elfhdr_len = PAGE_ALIGN(elfhdr_len); return elfhdr_len; } static int notesize(struct memelfnote *en) { int sz; sz = sizeof(struct elf_note); sz += roundup((strlen(en->name) + 1), 4); sz += roundup(en->datasz, 4); return sz; } static char *storenote(struct memelfnote *men, char *bufp) { struct elf_note en; #define DUMP_WRITE(addr,nr) do { memcpy(bufp,addr,nr); bufp += nr; } while(0) en.n_namesz = strlen(men->name) + 1; en.n_descsz = men->datasz; en.n_type = men->type; DUMP_WRITE(&en, sizeof(en)); DUMP_WRITE(men->name, en.n_namesz); /* XXX - cast from long long to long to avoid need for libgcc.a */ bufp = (char*) roundup((unsigned long)bufp,4); DUMP_WRITE(men->data, men->datasz); bufp = (char*) roundup((unsigned long)bufp,4); #undef DUMP_WRITE return bufp; } /* * fill up all the fields in prstatus from the given task struct, except * registers which need to be filled up separately. */ static void fill_prstatus(struct elf_prstatus *prstatus, struct task_struct *p, long signr) { prstatus->pr_info.si_signo = prstatus->pr_cursig = signr; prstatus->pr_sigpend = p->pending.signal.sig[0]; prstatus->pr_sighold = p->blocked.sig[0]; rcu_read_lock(); prstatus->pr_ppid = task_pid_vnr(rcu_dereference(p->real_parent)); rcu_read_unlock(); prstatus->pr_pid = task_pid_vnr(p); prstatus->pr_pgrp = task_pgrp_vnr(p); prstatus->pr_sid = task_session_vnr(p); if ( 0 /* thread_group_leader(p) */) { struct task_cputime cputime; /* * This is the record for the group leader. It shows the * group-wide total, not its individual thread total. */ /* thread_group_cputime(p, &cputime); */ cputime_to_timeval(cputime.utime, &prstatus->pr_utime); cputime_to_timeval(cputime.stime, &prstatus->pr_stime); } else { cputime_to_timeval(p->utime, &prstatus->pr_utime); cputime_to_timeval(p->stime, &prstatus->pr_stime); } cputime_to_timeval(p->signal->cutime, &prstatus->pr_cutime); cputime_to_timeval(p->signal->cstime, &prstatus->pr_cstime); } static int fill_psinfo(struct elf_prpsinfo *psinfo, struct task_struct *p, struct mm_struct *mm) { const struct cred *cred; unsigned int i, len; /* first copy the parameters from user space */ memset(psinfo, 0, sizeof(struct elf_prpsinfo)); if (mm) { len = mm->arg_end - mm->arg_start; if (len >= ELF_PRARGSZ) len = ELF_PRARGSZ-1; if (copy_from_user(&psinfo->pr_psargs, (const char __user *)mm->arg_start, len)) return -EFAULT; for(i = 0; i < len; i++) if (psinfo->pr_psargs[i] == 0) psinfo->pr_psargs[i] = ' '; psinfo->pr_psargs[len] = 0; } rcu_read_lock(); psinfo->pr_ppid = task_pid_vnr(rcu_dereference(p->real_parent)); rcu_read_unlock(); psinfo->pr_pid = task_pid_vnr(p); psinfo->pr_pgrp = task_pgrp_vnr(p); psinfo->pr_sid = task_session_vnr(p); i = p->state ? ffz(~p->state) + 1 : 0; psinfo->pr_state = i; psinfo->pr_sname = (i > 5) ? '.' : "RSDTZW"[i]; psinfo->pr_zomb = psinfo->pr_sname == 'Z'; psinfo->pr_nice = task_nice(p); psinfo->pr_flag = p->flags; rcu_read_lock(); cred = __task_cred(p); SET_UID(psinfo->pr_uid, cred->uid); SET_GID(psinfo->pr_gid, cred->gid); rcu_read_unlock(); strncpy(psinfo->pr_fname, p->comm, sizeof(psinfo->pr_fname)); return 0; } /** * crash_setup_regs() - save registers for the panic kernel * @newregs: registers are saved here * @oldregs: registers to be saved (may be %NULL) * * Function copies machine registers from @oldregs to @newregs. If @oldregs is * %NULL then current registers are stored there. */ static inline void crash_setup_regs(struct pt_regs *newregs, struct pt_regs *oldregs) { if (oldregs) { memcpy(newregs, oldregs, sizeof(*newregs)); } else { __asm__ __volatile__ ( "stmia %[regs_base], {r0-r12}\n\t" "mov %[_ARM_sp], sp\n\t" "str lr, %[_ARM_lr]\n\t" "adr %[_ARM_pc], 1f\n\t" "mrs %[_ARM_cpsr], cpsr\n\t" "1:" : [_ARM_pc] "=r" (newregs->ARM_pc), [_ARM_cpsr] "=r" (newregs->ARM_cpsr), [_ARM_sp] "=r" (newregs->ARM_sp), [_ARM_lr] "=o" (newregs->ARM_lr) : [regs_base] "r" (&newregs->ARM_r0) : "memory" ); } } void crash_note_save_cpu(struct pt_regs *regs, int cpu) { struct elf_prstatus prstatus; struct memelfnote notes; notes.name = CORE_STR; notes.type = NT_PRSTATUS; notes.datasz = sizeof(struct elf_prstatus); notes.data = &prstatus; memset(&prstatus, 0, sizeof(struct elf_prstatus)); fill_prstatus(&prstatus, current, 0); memcpy(&prstatus.pr_reg, regs, sizeof(struct pt_regs)); storenote(¬es, per_cpu_ptr(crash_notes, cpu)); } static void sysdump_fill_core_hdr(struct pt_regs *regs, struct sysdump_mem *sysmem, int mem_num, char *bufp, int nphdr, int dataoff) { struct elf_prstatus prstatus; /* NT_PRSTATUS */ struct elf_prpsinfo prpsinfo; /* NT_PRPSINFO */ struct elf_phdr *nhdr, *phdr; struct elfhdr *elf; struct memelfnote notes; off_t offset = 0; int i; /* setup ELF header */ elf = (struct elfhdr *) bufp; bufp += sizeof(struct elfhdr); offset += sizeof(struct elfhdr); memcpy(elf->e_ident, ELFMAG, SELFMAG); elf->e_ident[EI_CLASS] = ELF_CLASS; elf->e_ident[EI_DATA] = ELF_DATA; elf->e_ident[EI_VERSION]= EV_CURRENT; elf->e_ident[EI_OSABI] = ELF_OSABI; memset(elf->e_ident+EI_PAD, 0, EI_NIDENT-EI_PAD); elf->e_type = ET_CORE; elf->e_machine = ELF_ARCH; elf->e_version = EV_CURRENT; elf->e_entry = 0; elf->e_phoff = sizeof(struct elfhdr); elf->e_shoff = 0; elf->e_flags = ELF_CORE_EFLAGS; elf->e_ehsize = sizeof(struct elfhdr); elf->e_phentsize= sizeof(struct elf_phdr); elf->e_phnum = nphdr; elf->e_shentsize= 0; elf->e_shnum = 0; elf->e_shstrndx = 0; /* setup ELF PT_NOTE program header */ nhdr = (struct elf_phdr *) bufp; bufp += sizeof(struct elf_phdr); offset += sizeof(struct elf_phdr); nhdr->p_type = PT_NOTE; nhdr->p_offset = 0; nhdr->p_vaddr = 0; nhdr->p_paddr = 0; nhdr->p_filesz = 0; nhdr->p_memsz = SYSDUMP_NOTE_BYTES*NR_CPUS; nhdr->p_flags = 0; nhdr->p_align = 0; /* setup ELF PT_LOAD program header for every area */ for (i = 0; i < mem_num; i++) { phdr = (struct elf_phdr *) bufp; bufp += sizeof(struct elf_phdr); offset += sizeof(struct elf_phdr); phdr->p_type = PT_LOAD; phdr->p_flags = PF_R|PF_W|PF_X; phdr->p_offset = dataoff; phdr->p_vaddr = sysmem[i].vaddr; phdr->p_paddr = sysmem[i].paddr; phdr->p_filesz = phdr->p_memsz = sysmem[i].size; phdr->p_align = 0;//PAGE_SIZE; dataoff += sysmem[i].size; } /* * Set up the notes in similar form to SVR4 core dumps made * with info from their /proc. */ nhdr->p_offset = offset; nhdr->p_filesz = 0; return; } /* end elf_kcore_store_hdr() */ static void sysdump_prepare_info(int enter_id, const char *reason, struct pt_regs *regs) { unsigned long long t; unsigned long nanosec_rem; int iocnt, i; char *iomem; strncpy(sprd_sysdump_extra.reason, reason, sizeof(sprd_sysdump_extra.reason)); sprd_sysdump_extra.enter_id = enter_id; /* sprd_sysdump_info = (struct sysdump_info *)phys_to_virt(SPRD_SYSDUMP_MAGIC); */ if(sprd_sysdump_info == NULL){ printk(KERN_ERR"[ sysdump_prepare_info ] sprd_sysdump_info ioremap error!! \n"); return; } printk("vaddr is %p,paddr is %p\n",sprd_sysdump_info, (void *)SPRD_SYSDUMP_MAGIC); memcpy(sprd_sysdump_info->magic, SYSDUMP_MAGIC, sizeof(sprd_sysdump_info->magic)); if (reason != NULL && !strcmp(reason, "Crash Key")) { sprd_sysdump_info->crash_key = 1; } else { sprd_sysdump_info->crash_key = 0; } printk("reason: %s, sprd_sysdump_info->crash_key: %d\n", reason, sprd_sysdump_info->crash_key); t = cpu_clock(smp_processor_id()); nanosec_rem = do_div(t, 1000000000); sprintf(sprd_sysdump_info->time, "%lu.%06lu", (unsigned long)t, nanosec_rem / 1000); memcpy(sprd_sysdump_info->dump_path, sysdump_conf.dump_path, sizeof(sprd_sysdump_info->dump_path)); sprd_sysdump_info->dump_mem_paddr = virt_to_phys(sprd_dump_mem); sprd_sysdump_info->mem_num = sprd_dump_mem_num; sprd_sysdump_info->elfhdr_size = get_elfhdr_size(sprd_sysdump_info->mem_num); /* this must before sysdump_fill_core_hdr, it needs size */ for (i = 0; i < sprd_dump_mem_num; i++) { if (SYSDUMP_RAM == sprd_dump_mem[i].type && 0 == sprd_dump_mem[i].size) sprd_dump_mem[i].size = CONFIG_PHYS_OFFSET + (num_physpages << PAGE_SHIFT) - sprd_dump_mem[i].paddr; if (!sysdump_conf.dump_modem && SYSDUMP_MODEM == sprd_dump_mem[i].type) sprd_dump_mem[i].size = 0; } sysdump_fill_core_hdr(regs, sprd_dump_mem, sprd_dump_mem_num, (char *)sprd_sysdump_info + sizeof(*sprd_sysdump_info), sprd_sysdump_info->mem_num + 1, sprd_sysdump_info->elfhdr_size); iocnt = 0; for (i = 0; i < sprd_dump_mem_num; i++) { /* save iomem(regiters) to ram, cause they will change while rebooting */ if (0xffffffff != sprd_dump_mem[i].soff) { sprd_dump_mem[i].soff = iocnt; iomem = (char *)sprd_sysdump_info + sizeof(*sprd_sysdump_info) + sprd_sysdump_info->elfhdr_size + iocnt; memcpy(iomem, (void const *)(sprd_dump_mem[i].vaddr), sprd_dump_mem[i].size); iocnt += sprd_dump_mem[i].size; } } return; } struct sprd_debug_mmu_reg_t { int SCTLR; int TTBR0; int TTBR1; int TTBCR; int DACR; int DFSR; int DFAR; int IFSR; int IFAR; int DAFSR; int IAFSR; int PMRRR; int NMRRR; int FCSEPID; int CONTEXT; int URWTPID; int UROTPID; int POTPIDR; }; /* ARM CORE regs mapping structure */ struct sprd_debug_core_t { /* COMMON */ unsigned int r0; unsigned int r1; unsigned int r2; unsigned int r3; unsigned int r4; unsigned int r5; unsigned int r6; unsigned int r7; unsigned int r8; unsigned int r9; unsigned int r10; unsigned int r11; unsigned int r12; /* SVC */ unsigned int r13_svc; unsigned int r14_svc; unsigned int spsr_svc; /* PC & CPSR */ unsigned int pc; unsigned int cpsr; /* USR/SYS */ unsigned int r13_usr; unsigned int r14_usr; /* FIQ */ unsigned int r8_fiq; unsigned int r9_fiq; unsigned int r10_fiq; unsigned int r11_fiq; unsigned int r12_fiq; unsigned int r13_fiq; unsigned int r14_fiq; unsigned int spsr_fiq; /* IRQ */ unsigned int r13_irq; unsigned int r14_irq; unsigned int spsr_irq; /* MON */ unsigned int r13_mon; unsigned int r14_mon; unsigned int spsr_mon; /* ABT */ unsigned int r13_abt; unsigned int r14_abt; unsigned int spsr_abt; /* UNDEF */ unsigned int r13_und; unsigned int r14_und; unsigned int spsr_und; }; DEFINE_PER_CPU(struct sprd_debug_core_t, sprd_debug_core_reg); DEFINE_PER_CPU(struct sprd_debug_mmu_reg_t, sprd_debug_mmu_reg); /* core reg dump function*/ static inline void sprd_debug_save_core_reg(struct sprd_debug_core_t *core_reg) { /* we will be in SVC mode when we enter this function. Collect * SVC registers along with cmn registers. */ asm("str r0, [%0,#0]\n\t" /* R0 is pushed first to core_reg */ "mov r0, %0\n\t" /* R0 will be alias for core_reg */ "str r1, [r0,#4]\n\t" /* R1 */ "str r2, [r0,#8]\n\t" /* R2 */ "str r3, [r0,#12]\n\t" /* R3 */ "str r4, [r0,#16]\n\t" /* R4 */ "str r5, [r0,#20]\n\t" /* R5 */ "str r6, [r0,#24]\n\t" /* R6 */ "str r7, [r0,#28]\n\t" /* R7 */ "str r8, [r0,#32]\n\t" /* R8 */ "str r9, [r0,#36]\n\t" /* R9 */ "str r10, [r0,#40]\n\t" /* R10 */ "str r11, [r0,#44]\n\t" /* R11 */ "str r12, [r0,#48]\n\t" /* R12 */ /* SVC */ "str r13, [r0,#52]\n\t" /* R13_SVC */ "str r14, [r0,#56]\n\t" /* R14_SVC */ "mrs r1, spsr\n\t" /* SPSR_SVC */ "str r1, [r0,#60]\n\t" /* PC and CPSR */ "sub r1, r15, #0x4\n\t" /* PC */ "str r1, [r0,#64]\n\t" "mrs r1, cpsr\n\t" /* CPSR */ "str r1, [r0,#68]\n\t" /* SYS/USR */ "mrs r1, cpsr\n\t" /* switch to SYS mode */ "and r1, r1, #0xFFFFFFE0\n\t" "orr r1, r1, #0x1f\n\t" "msr cpsr,r1\n\t" "str r13, [r0,#72]\n\t" /* R13_USR */ "str r14, [r0,#76]\n\t" /* R14_USR */ /* FIQ */ "mrs r1, cpsr\n\t" /* switch to FIQ mode */ "and r1,r1,#0xFFFFFFE0\n\t" "orr r1,r1,#0x11\n\t" "msr cpsr,r1\n\t" "str r8, [r0,#80]\n\t" /* R8_FIQ */ "str r9, [r0,#84]\n\t" /* R9_FIQ */ "str r10, [r0,#88]\n\t" /* R10_FIQ */ "str r11, [r0,#92]\n\t" /* R11_FIQ */ "str r12, [r0,#96]\n\t" /* R12_FIQ */ "str r13, [r0,#100]\n\t" /* R13_FIQ */ "str r14, [r0,#104]\n\t" /* R14_FIQ */ "mrs r1, spsr\n\t" /* SPSR_FIQ */ "str r1, [r0,#108]\n\t" /* IRQ */ "mrs r1, cpsr\n\t" /* switch to IRQ mode */ "and r1, r1, #0xFFFFFFE0\n\t" "orr r1, r1, #0x12\n\t" "msr cpsr,r1\n\t" "str r13, [r0,#112]\n\t" /* R13_IRQ */ "str r14, [r0,#116]\n\t" /* R14_IRQ */ "mrs r1, spsr\n\t" /* SPSR_IRQ */ "str r1, [r0,#120]\n\t" /* MON */ "mrs r1, cpsr\n\t" /* switch to monitor mode */ "and r1, r1, #0xFFFFFFE0\n\t" "orr r1, r1, #0x16\n\t" "msr cpsr,r1\n\t" "str r13, [r0,#124]\n\t" /* R13_MON */ "str r14, [r0,#128]\n\t" /* R14_MON */ "mrs r1, spsr\n\t" /* SPSR_MON */ "str r1, [r0,#132]\n\t" /* ABT */ "mrs r1, cpsr\n\t" /* switch to Abort mode */ "and r1, r1, #0xFFFFFFE0\n\t" "orr r1, r1, #0x17\n\t" "msr cpsr,r1\n\t" "str r13, [r0,#136]\n\t" /* R13_ABT */ "str r14, [r0,#140]\n\t" /* R14_ABT */ "mrs r1, spsr\n\t" /* SPSR_ABT */ "str r1, [r0,#144]\n\t" /* UND */ "mrs r1, cpsr\n\t" /* switch to undef mode */ "and r1, r1, #0xFFFFFFE0\n\t" "orr r1, r1, #0x1B\n\t" "msr cpsr,r1\n\t" "str r13, [r0,#148]\n\t" /* R13_UND */ "str r14, [r0,#152]\n\t" /* R14_UND */ "mrs r1, spsr\n\t" /* SPSR_UND */ "str r1, [r0,#156]\n\t" /* restore to SVC mode */ "mrs r1, cpsr\n\t" /* switch to SVC mode */ "and r1, r1, #0xFFFFFFE0\n\t" "orr r1, r1, #0x13\n\t" "msr cpsr,r1\n\t" : /* output */ : "r"(core_reg) /* input */ : "%r0", "%r1" /* clobbered registers */ ); return; } static inline void sprd_debug_save_mmu_reg(struct sprd_debug_mmu_reg_t *mmu_reg) { asm("mrc p15, 0, r1, c1, c0, 0\n\t" /* SCTLR */ "str r1, [%0]\n\t" "mrc p15, 0, r1, c2, c0, 0\n\t" /* TTBR0 */ "str r1, [%0,#4]\n\t" "mrc p15, 0, r1, c2, c0,1\n\t" /* TTBR1 */ "str r1, [%0,#8]\n\t" "mrc p15, 0, r1, c2, c0,2\n\t" /* TTBCR */ "str r1, [%0,#12]\n\t" "mrc p15, 0, r1, c3, c0,0\n\t" /* DACR */ "str r1, [%0,#16]\n\t" "mrc p15, 0, r1, c5, c0,0\n\t" /* DFSR */ "str r1, [%0,#20]\n\t" "mrc p15, 0, r1, c6, c0,0\n\t" /* DFAR */ "str r1, [%0,#24]\n\t" "mrc p15, 0, r1, c5, c0,1\n\t" /* IFSR */ "str r1, [%0,#28]\n\t" "mrc p15, 0, r1, c6, c0,2\n\t" /* IFAR */ "str r1, [%0,#32]\n\t" /* Don't populate DAFSR and RAFSR */ "mrc p15, 0, r1, c10, c2,0\n\t" /* PMRRR */ "str r1, [%0,#44]\n\t" "mrc p15, 0, r1, c10, c2,1\n\t" /* NMRRR */ "str r1, [%0,#48]\n\t" "mrc p15, 0, r1, c13, c0,0\n\t" /* FCSEPID */ "str r1, [%0,#52]\n\t" "mrc p15, 0, r1, c13, c0,1\n\t" /* CONTEXT */ "str r1, [%0,#56]\n\t" "mrc p15, 0, r1, c13, c0,2\n\t" /* URWTPID */ "str r1, [%0,#60]\n\t" "mrc p15, 0, r1, c13, c0,3\n\t" /* UROTPID */ "str r1, [%0,#64]\n\t" "mrc p15, 0, r1, c13, c0,4\n\t" /* POTPIDR */ "str r1, [%0,#68]\n\t" : /* output */ : "r"(mmu_reg) /* input */ : "%r1", "memory" /* clobbered register */ ); } static inline void sprd_debug_save_context(void) { unsigned long flags; local_irq_save(flags); sprd_debug_save_mmu_reg(&per_cpu(sprd_debug_mmu_reg, smp_processor_id())); sprd_debug_save_core_reg(&per_cpu (sprd_debug_core_reg, smp_processor_id())); pr_emerg("(%s) context saved(CPU:%d)\n", __func__, smp_processor_id()); local_irq_restore(flags); flush_cache_all(); } extern void emergency_restart(void); void sysdump_enter(int enter_id, const char *reason, struct pt_regs *regs) { struct pt_regs * pregs; if (!sysdump_conf.enable) return; if ((sysdump_conf.crashkey_only) && !(reason != NULL && !strcmp(reason, "Crash Key"))) return; /* this should before smp_send_stop() to make sysdump_ipi enable */ sprd_sysdump_extra.enter_cpu = smp_processor_id(); pregs = &sprd_sysdump_extra.cpu_context[sprd_sysdump_extra.enter_cpu]; if (regs) memcpy(pregs, regs, sizeof(*regs)); else crash_setup_regs((struct pt_regs *)pregs, NULL); crash_note_save_cpu(pregs, sprd_sysdump_extra.enter_cpu); sprd_debug_save_context(); smp_send_stop(); mdelay(1000); printk("\n"); printk("*****************************************************\n"); printk("* *\n"); printk("* Sysdump enter, preparing debug info to dump ... *\n"); printk("* *\n"); printk("*****************************************************\n"); printk("\n"); flush_cache_all(); outer_flush_all(); mdelay(1000); sysdump_prepare_info(enter_id, reason, regs); flush_cache_all(); outer_flush_all(); mdelay(1000); printk("\n"); printk("*****************************************************\n"); printk("* *\n"); printk("* Try to reboot system ... *\n"); printk("* *\n"); printk("*****************************************************\n"); printk("\n"); emergency_restart(); return; } void sysdump_ipi(struct pt_regs *regs) { int cpu = smp_processor_id(); if (sprd_sysdump_extra.enter_cpu != -1) { memcpy((void *)&(sprd_sysdump_extra.cpu_context[cpu]), (void *)regs, sizeof(struct pt_regs)); crash_note_save_cpu(regs, cpu); sprd_debug_save_context(); } return; } static ctl_table sysdump_sysctl_table[] = { { .procname = "sysdump_enable", .data = &sysdump_conf.enable, .maxlen = sizeof(int), .mode = 0644, .proc_handler = proc_dointvec, }, { .procname = "sysdump_crashkey_only", .data = &sysdump_conf.crashkey_only, .maxlen = sizeof(int), .mode = 0644, .proc_handler = proc_dointvec, }, { .procname = "sysdump_dump_modem", .data = &sysdump_conf.dump_modem, .maxlen = sizeof(int), .mode = 0644, .proc_handler = proc_dointvec, }, { .procname = "sysdump_reboot", .data = &sysdump_conf.reboot, .maxlen = sizeof(int), .mode = 0644, .proc_handler = proc_dointvec, }, { .procname = "sysdump_dump_path", .data = sysdump_conf.dump_path, .maxlen = sizeof(sysdump_conf.dump_path), .mode = 0644, .proc_handler = proc_dostring, }, {} }; static ctl_table sysdump_sysctl_root[] = { { .procname = "kernel", .mode = 0555, .child = sysdump_sysctl_table, }, {} }; static struct ctl_table_header *sysdump_sysctl_hdr = NULL; int sysdump_sysctl_init(void) { char *phy_sprd_sysdump_magic; sysdump_sysctl_hdr = register_sysctl_table(sysdump_sysctl_root); if (!sysdump_sysctl_hdr) return -ENOMEM; phy_sprd_sysdump_magic =((SPRD_SYSDUMP_MAGIC & (~(SZ_256M - 1))) + SZ_256M - SZ_1M); /* FIXME: replace ioremap with some other methods... */ sprd_sysdump_info = (struct sysdump_info *)ioremap(phy_sprd_sysdump_magic, SZ_1M); crash_notes = alloc_percpu(note_buf_t); return 0; } void sysdump_sysctl_exit(void) { if (sysdump_sysctl_hdr) unregister_sysctl_table(sysdump_sysctl_hdr); } module_init(sysdump_sysctl_init); module_exit(sysdump_sysctl_exit); MODULE_AUTHOR("Jianjun.He "); MODULE_DESCRIPTION("kernel core dump for Spreadtrum"); MODULE_LICENSE("GPL");