| /* |
| * drivers/amlogic/debug/debug_ftrace_ramoops.c |
| * |
| * Copyright (C) 2017 Amlogic, Inc. All rights reserved. |
| * |
| * 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. |
| * |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/compiler.h> |
| #include <linux/irqflags.h> |
| #include <linux/percpu.h> |
| #include <linux/smp.h> |
| #include <linux/atomic.h> |
| #include <linux/types.h> |
| #include <linux/ftrace.h> |
| #include <linux/fs.h> |
| #include <linux/debugfs.h> |
| #include <linux/err.h> |
| #include <linux/amlogic/debug_ftrace_ramoops.h> |
| #include <../../../fs/pstore/internal.h> |
| #include <linux/trace_clock.h> |
| #include <linux/percpu.h> |
| #include <linux/moduleparam.h> |
| #include <linux/pstore_ram.h> |
| |
| static DEFINE_PER_CPU(int, en); |
| |
| #define IRQ_D 1 |
| |
| unsigned int dump_iomap; |
| core_param(dump_iomap, dump_iomap, uint, 0664); |
| |
| unsigned int ramoops_ftrace_en; |
| EXPORT_SYMBOL(ramoops_ftrace_en); |
| |
| int ramoops_io_en; |
| EXPORT_SYMBOL(ramoops_io_en); |
| core_param(ramoops_io_en, ramoops_io_en, int, 0664); |
| |
| int ramoops_io_dump; |
| EXPORT_SYMBOL(ramoops_io_dump); |
| core_param(ramoops_io_dump, ramoops_io_dump, int, 0664); |
| |
| int ramoops_io_skip; |
| EXPORT_SYMBOL(ramoops_io_skip); |
| core_param(ramoops_io_skip, ramoops_io_skip, int, 0664); |
| |
| const char *record_name[] = { |
| "NULL", |
| "FUNC", |
| "IO-R", |
| "IO-W", |
| "IO-R-E", |
| "IO-W-E", |
| "IO-TAG", |
| }; |
| |
| void notrace pstore_ftrace_save(struct pstore_ftrace_record *rec) |
| { |
| int cpu = raw_smp_processor_id(); |
| |
| if (unlikely(oops_in_progress) || unlikely(per_cpu(en, cpu))) |
| return; |
| per_cpu(en, cpu) = 1; |
| pstore_ftrace_encode_cpu(rec, cpu); |
| strlcpy(rec->comm, current->comm, sizeof(rec->comm) - 1); |
| rec->pid = current->pid; |
| rec->time = trace_clock_local(); |
| psinfo->write_buf(PSTORE_TYPE_FTRACE, 0, NULL, 0, (void *)rec, |
| 0, sizeof(*rec), psinfo); |
| per_cpu(en, cpu) = 0; |
| } |
| EXPORT_SYMBOL(pstore_ftrace_save); |
| |
| static void notrace pstore_function_dump(struct pstore_ftrace_record *rec, |
| struct seq_file *s) |
| { |
| unsigned long sec = 0, us = 0; |
| unsigned long long time = rec->time; |
| |
| do_div(time, 1000); |
| us = (unsigned long)do_div(time, 1000000); |
| sec = (unsigned long)time; |
| seq_printf(s, "[%04ld.%06ld@%d %d] <%5d-%s> <%pf <- %pF>\n", |
| sec, us, pstore_ftrace_decode_cpu(rec), rec->in_irq, |
| rec->pid, rec->comm, (void *)rec->ip, |
| (void *)rec->parent_ip); |
| } |
| |
| void notrace pstore_io_rw_dump(struct pstore_ftrace_record *rec, |
| struct seq_file *s) |
| { |
| unsigned long sec = 0, us = 0; |
| unsigned long long time = rec->time; |
| unsigned int cpu = pstore_ftrace_decode_cpu(rec); |
| |
| do_div(time, 1000); |
| us = (unsigned long)do_div(time, 1000000); |
| sec = (unsigned long)time; |
| seq_printf(s, "[%04ld.%06ld@%d %d] <%5d-%6s> <%6s %08lx-%8lx> <%pf <- %pF>\n", |
| sec, us, cpu, rec->in_irq, rec->pid, rec->comm, |
| record_name[rec->flag], rec->val1, |
| (rec->flag == PSTORE_FLAG_IO_W) ? rec->val2 : 0, |
| (void *)rec->ip, (void *)rec->parent_ip); |
| } |
| |
| void notrace pstore_ftrace_dump(struct pstore_ftrace_record *rec, |
| struct seq_file *s) |
| { |
| switch (rec->flag & PSTORE_FLAG_MASK) { |
| case PSTORE_FLAG_FUNC: |
| pstore_function_dump(rec, s); |
| break; |
| case PSTORE_FLAG_IO_R: |
| case PSTORE_FLAG_IO_W: |
| case PSTORE_FLAG_IO_W_END: |
| case PSTORE_FLAG_IO_R_END: |
| case PSTORE_FLAG_IO_TAG: |
| pstore_io_rw_dump(rec, s); |
| break; |
| default: |
| seq_printf(s, "Unknown Msg:%x\n", rec->flag); |
| } |
| } |
| |
| void notrace pstore_io_save(unsigned long reg, unsigned long val, |
| unsigned long parant, unsigned int flag, |
| unsigned long *irq_flag) |
| { |
| struct pstore_ftrace_record rec; |
| |
| if (!ramoops_ftrace_en || !ramoops_io_en) |
| return; |
| |
| if ((flag == PSTORE_FLAG_IO_R || flag == PSTORE_FLAG_IO_W) && IRQ_D) |
| local_irq_save(*irq_flag); |
| |
| switch (ramoops_io_skip) { |
| case 1: |
| rec.ip = CALLER_ADDR1; |
| rec.parent_ip = CALLER_ADDR2; |
| break; |
| case 2: |
| rec.ip = CALLER_ADDR2; |
| rec.parent_ip = CALLER_ADDR3; |
| break; |
| case 3: |
| rec.ip = CALLER_ADDR3; |
| rec.parent_ip = CALLER_ADDR4; |
| break; |
| default: |
| rec.ip = CALLER_ADDR0; |
| rec.parent_ip = parant; |
| break; |
| } |
| |
| rec.flag = flag; |
| rec.in_irq = !!in_irq(); |
| rec.val1 = reg; |
| rec.val2 = val; |
| pstore_ftrace_save(&rec); |
| |
| if ((flag == PSTORE_FLAG_IO_R_END || flag == PSTORE_FLAG_IO_W_END) && |
| IRQ_D) |
| local_irq_restore(*irq_flag); |
| } |
| EXPORT_SYMBOL(pstore_io_save); |
| |
| static void notrace __pstore_io_rw_dump(struct pstore_ftrace_record *rec) |
| { |
| unsigned long sec = 0, us = 0; |
| unsigned long long time = rec->time; |
| unsigned int cpu = pstore_ftrace_decode_cpu(rec); |
| |
| do_div(time, 1000); |
| us = (unsigned long)do_div(time, 1000000); |
| sec = (unsigned long)time; |
| pr_info("[%04ld.%06ld@%d %d] <%5d-%6s> <%6s %08lx-%8lx> <%pf <- %pF>\n", |
| sec, us, cpu, rec->in_irq, rec->pid, rec->comm, |
| record_name[rec->flag], rec->val1, |
| (rec->flag == PSTORE_FLAG_IO_W) ? rec->val2 : 0, |
| (void *)rec->ip, (void *)rec->parent_ip); |
| } |
| |
| static void notrace __pstore_ftrace_dump_old(struct pstore_ftrace_record *rec) |
| { |
| switch (rec->flag & PSTORE_FLAG_MASK) { |
| case PSTORE_FLAG_FUNC: |
| break; |
| case PSTORE_FLAG_IO_R: |
| case PSTORE_FLAG_IO_W: |
| case PSTORE_FLAG_IO_W_END: |
| case PSTORE_FLAG_IO_R_END: |
| case PSTORE_FLAG_IO_TAG: |
| __pstore_io_rw_dump(rec); |
| break; |
| default: |
| pr_err("Unknown Msg:%x\n", rec->flag); |
| } |
| } |
| |
| static char reboot_mode[16]; |
| static int __init reboot_mode_setup(char *s) |
| { |
| if (s != NULL) |
| snprintf(reboot_mode, sizeof(reboot_mode), "%s", s); |
| |
| return 0; |
| } |
| __setup("reboot_mode=", reboot_mode_setup); |
| |
| void notrace pstore_ftrace_dump_old(struct persistent_ram_zone *prz) |
| { |
| struct pstore_ftrace_record *rec; |
| void *rec_end; |
| |
| rec = (struct pstore_ftrace_record *)prz->old_log; |
| rec_end = (void *)rec + prz->old_log_size; |
| |
| pr_info("ramoops_io_dump=%d, buffer=%p ftrace_old_log=%p, size=%u, reboot_mode=%s\n", |
| ramoops_io_dump, |
| prz->buffer, |
| rec, |
| (unsigned int)prz->old_log_size, |
| reboot_mode); |
| |
| if (!strcmp(reboot_mode, "cold_boot")) |
| return; |
| |
| if (!strcmp(reboot_mode, "normal")) |
| return; |
| |
| if (!ramoops_io_dump) |
| return; |
| |
| if (!persistent_ram_old_size(prz)) |
| return; |
| |
| rec = (void *)rec + prz->old_log_size % sizeof(*rec); |
| |
| while ((void *)rec < rec_end) { |
| __pstore_ftrace_dump_old(rec); |
| rec++; |
| } |
| } |