// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #define SKIP_LOCKUP_CHECK #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if IS_ENABLED(CONFIG_AMLOGIC_DEBUG_IOTM) #include #endif #if IS_ENABLED(CONFIG_AMLOGIC_DEBUG_AOCPU_MONITOR) #include #endif #define AML_PERSISTENT_RAM_SIG (0x4c4d41) /* AML */ int ramoops_ftrace_en; EXPORT_SYMBOL(ramoops_ftrace_en); /* * bit0: io_trace * bit1: sched_trace * bit2: irq_trace * bit3: smc_trace * bit4: misc_trace: use for record usb/frc/sync and other trace * disable bits will forbid record this type log * record all type log as default */ int ramoops_trace_mask = 0x1f; EXPORT_SYMBOL(ramoops_trace_mask); static int ramoops_trace_mask_setup(char *buf) { if (!buf) return -EINVAL; if (kstrtoint(buf, 0, &ramoops_trace_mask)) { pr_err("ramoops_trace_mask error: %s\n", buf); return -EINVAL; } return 1; } __setup("ramoops_trace_mask=", ramoops_trace_mask_setup); int ramoops_io_skip; static int ramoops_io_skip_setup(char *buf) { if (!buf) return -EINVAL; if (kstrtoint(buf, 0, &ramoops_io_skip)) { pr_err("ramoops_io_skip error: %s\n", buf); return -EINVAL; } if (ramoops_io_skip > 2) ramoops_io_skip = 2; return 1; } __setup("ramoops_io_skip=", ramoops_io_skip_setup); int ramoops_io_stack = 1; static int ramoops_io_stack_setup(char *buf) { if (!buf) return -EINVAL; if (kstrtoint(buf, 0, &ramoops_io_stack)) { pr_err("ramoops_io_stack error: %s\n", buf); return -EINVAL; } return 1; } __setup("ramoops_io_stack=", ramoops_io_stack_setup); /* * module_debug = 0:disable insmod/rmmod * module_debug = 1:enable insmod/rmmod * module_debug > 1:enable rmmod */ static int module_debug = 2; static int module_debug_setup(char *buf) { if (!buf) return -EINVAL; if (kstrtoint(buf, 0, &module_debug)) { pr_err("module_debug error: %s\n", buf); return -EINVAL; } return 1; } __setup("module_debug=", module_debug_setup); static int ramoops_io_en; static int ramoops_io_en_setup(char *buf) { if (!buf) return -EINVAL; if (kstrtoint(buf, 0, &ramoops_io_en)) { pr_err("ramoops_io_en error: %s\n", buf); return -EINVAL; } return 0; } __setup("ramoops_io_en=", ramoops_io_en_setup); #define DUMP_WATCHDOG 1 #define DUMP_WATCHDOG_PANIC 2 #define DUMP_ALL 3 int ramoops_io_dump = DUMP_WATCHDOG; static int ramoops_io_dump_setup(char *buf) { if (!buf) return -EINVAL; if (kstrtoint(buf, 0, &ramoops_io_dump)) { pr_err("ramoops_io_dump error: %s\n", buf); return -EINVAL; } return 1; } __setup("ramoops_io_dump=", ramoops_io_dump_setup); /* ramoops_io_dump_delay_secs : iotrace dump delayed time, s */ static int ramoops_io_dump_delay_secs = 10; /* default : 10s */ static struct delayed_work iotrace_work; static int ramoops_io_dump_delay_secs_setup(char *buf) { if (!buf) return -EINVAL; if (kstrtoint(buf, 0, &ramoops_io_dump_delay_secs)) { pr_err("ramoops_io_dump_delay_secs error: %s\n", buf); return -EINVAL; } return 1; } __setup("ramoops_io_dump_delay_secs=", ramoops_io_dump_delay_secs_setup); struct prz_record_iter { void *ptr; void *ptr_end; int cpu; enum aml_pstore_type_id type; bool over; }; const char *record_name[] = { [RECORD_TYPE_IO_R] = "IO-R", [RECORD_TYPE_IO_W] = "IO-W", [RECORD_TYPE_IO_R_END] = "IO-R-E", [RECORD_TYPE_IO_W_END] = "IO-W-E", [RECORD_TYPE_SCHED_SWITCH] = "SCHED_SWITCH", [RECORD_TYPE_ISR_IN] = "ISR_IN", [RECORD_TYPE_ISR_OUT] = "ISR_OUT", [RECORD_TYPE_SMC_IN] = "SMC_IN", [RECORD_TYPE_SMC_OUT] = "SMC_OUT", [RECORD_TYPE_SMC_NORET_IN] = "SMC_NORET_IN", [RECORD_TYPE_USB_IN] = "USB_IN", [RECORD_TYPE_USB_OUT] = "USB_OUT", [RECORD_TYPE_FRC_INPUT_IN] = "FRC_INPUT_IN", [RECORD_TYPE_FRC_INPUT_OUT] = "FRC_INPUT_OUT", [RECORD_TYPE_FRC_OUTPUT_IN] = "FRC_OUTPUT_IN", [RECORD_TYPE_FRC_OUTPUT_OUT] = "FRC_OUTPUT_OUT", [RECORD_TYPE_VSYNC_IN] = "VSYNC_IN", [RECORD_TYPE_VSYNC_OUT] = "VSYNC_OUT", [RECORD_TYPE_AMVECM_IN] = "AMVECM_IN", [RECORD_TYPE_AMVECM_OUT] = "AMVECM_OUT", }; struct percpu_trace_data { void *ptr; void *ptr_end; unsigned long total_size; }; static char get_disirq_flag(bool dis_irq) { if (dis_irq) return 'd'; else return '.'; } static char get_irq_flag(bool irq, bool softirq) { if (irq) { if (softirq) return 'H'; else return 'h'; } else { if (softirq) return 's'; else return '.'; } } struct aml_persistent_ram_buffer { u32 sig; atomic_t start; atomic_t size; u8 data[]; }; struct aml_persistent_ram_zone { phys_addr_t paddr; size_t size; void *vaddr; enum aml_pstore_type_id type; int cpu; struct aml_persistent_ram_buffer *buffer; atomic_t quick_start;/* equal to buffer->start, purpose for quick read */ atomic_t quick_size; /* equcl to buffer->size, purpose for quick read */ size_t buffer_size; char *old_log; size_t old_log_size; }; struct aml_ramoops_context { struct aml_persistent_ram_zone **io_przs; /* io message zones */ struct aml_persistent_ram_zone **sched_przs; /* sched message zones */ struct aml_persistent_ram_zone **irq_przs; /* irq message zones */ struct aml_persistent_ram_zone **smc_przs; /* smc message zones */ struct aml_persistent_ram_zone **misc_przs; /* other message zones */ struct prz_record_iter record_iter[AML_PSTORE_TYPE_MAX][8]; // max possible cpu = 8 struct prz_record_iter *curr_record_iter; unsigned int seq_file_overflow; /* percpu sched/irq/smc/other/io size */ unsigned int io_size; unsigned int sched_size; unsigned int irq_size; unsigned int smc_size; unsigned int misc_size; phys_addr_t phys_addr; unsigned long size; }; static struct aml_ramoops_context aml_oops_cxt; static int aml_ramoops_parse_dt(void) { struct aml_ramoops_context *cxt = &aml_oops_cxt; struct device_node *node; struct resource res; int ret, possible_cpu; possible_cpu = num_possible_cpus(); node = of_find_node_by_path("/reserved-memory/linux,iotrace"); if (!node) return -EINVAL; ret = of_address_to_resource(node, 0, &res); if (ret) return -EINVAL; cxt->phys_addr = res.start; cxt->size = res.end - res.start + 1; of_property_read_u32(node, "io-size", &cxt->io_size); of_property_read_u32(node, "sched-size", &cxt->sched_size); of_property_read_u32(node, "irq-size", &cxt->irq_size); of_property_read_u32(node, "smc-size", &cxt->smc_size); of_property_read_u32(node, "misc-size", &cxt->misc_size); /* dts size design for 8 cpus, enlarge size according to actual possible cpu */ cxt->io_size = cxt->io_size * (8 / possible_cpu); cxt->sched_size = cxt->sched_size * (8 / possible_cpu); cxt->irq_size = cxt->irq_size * (8 / possible_cpu); cxt->smc_size = cxt->smc_size * (8 / possible_cpu); cxt->misc_size = cxt->misc_size * (8 / possible_cpu); if (((cxt->io_size + cxt->sched_size + cxt->irq_size + cxt->smc_size + cxt->misc_size) * possible_cpu) > cxt->size) { pr_err("Please check linux,iotrace dts configuration\n"); return -EINVAL; } return 0; } static void *aml_persistent_ram_vmap(phys_addr_t start, size_t size) { struct page **pages; phys_addr_t page_start; unsigned int page_count; pgprot_t prot; unsigned int i; void *vaddr; page_start = start - offset_in_page(start); page_count = DIV_ROUND_UP(size + offset_in_page(start), PAGE_SIZE); prot = pgprot_writecombine(PAGE_KERNEL); pages = kmalloc_array(page_count, sizeof(struct page *), GFP_KERNEL); if (!pages) return NULL; for (i = 0; i < page_count; i++) { phys_addr_t addr = page_start + i * PAGE_SIZE; pages[i] = pfn_to_page(addr >> PAGE_SHIFT); } vaddr = vmap(pages, page_count, VM_MAP, prot); kfree(pages); /* * Since vmap() uses page granularity, we must add the offset * into the page here, to get the byte granularity address * into the mapping to represent the actual "start" location. */ return vaddr + offset_in_page(start); } static inline size_t buffer_size(struct aml_persistent_ram_zone *prz) { return atomic_read(&prz->buffer->size); } static inline size_t buffer_start(struct aml_persistent_ram_zone *prz) { return atomic_read(&prz->buffer->start); } static char reboot_mode[16]; static int reboot_mode_setup(char *s) { if (s) snprintf(reboot_mode, sizeof(reboot_mode), "%s", s); return 1; } __setup("reboot_mode=", reboot_mode_setup); static bool is_shutdown_reboot(void) { return !strcmp(reboot_mode, "shutdown_reboot"); } static bool is_cold_boot(void) { return !strcmp(reboot_mode, "cold_boot"); } static void aml_persistent_ram_save_old(struct aml_persistent_ram_zone *prz) { struct aml_persistent_ram_buffer *buffer = prz->buffer; size_t size = buffer_size(prz); size_t start = buffer_start(prz); if (is_shutdown_reboot() || is_cold_boot()) return; if (!size) return; if (!prz->old_log) prz->old_log = kmalloc(size, GFP_KERNEL); if (!prz->old_log) return; prz->old_log_size = size; memcpy_fromio(prz->old_log, &buffer->data[start], size - start); memcpy_fromio(prz->old_log + size - start, &buffer->data[0], start); } static int aml_persistent_ram_post_init(struct aml_persistent_ram_zone *prz) { u32 sig = AML_PERSISTENT_RAM_SIG; if (prz->buffer->sig == sig) { if (buffer_size(prz) == 0) { pr_debug("found existing empty buffer\n"); return 0; } if (buffer_size(prz) > prz->buffer_size || buffer_start(prz) > buffer_size(prz)) { pr_info("found existing invalid buffer, size %zu, start %zu\n", buffer_size(prz), buffer_start(prz)); } else { pr_debug("found existing buffer, size %zu, start %zu\n", buffer_size(prz), buffer_start(prz)); aml_persistent_ram_save_old(prz); } } else { pr_debug("no valid data in buffer (sig = 0x%08x)\n", prz->buffer->sig); prz->buffer->sig = sig; } atomic_set(&prz->buffer->start, 0); atomic_set(&prz->quick_start, 0); atomic_set(&prz->buffer->size, 0); atomic_set(&prz->quick_size, 0); return 0; } static void aml_persistent_ram_free_old(struct aml_persistent_ram_zone *prz) { kfree(prz->old_log); prz->old_log = NULL; prz->old_log_size = 0; } static void aml_persistent_ram_free(struct aml_persistent_ram_zone *prz) { if (!prz) return; if (prz->vaddr) { vunmap(prz->vaddr - offset_in_page(prz->paddr)); prz->vaddr = NULL; } aml_persistent_ram_free_old(prz); kfree(prz); } static struct aml_persistent_ram_zone *aml_persistent_ram_new(phys_addr_t start, size_t size, enum aml_pstore_type_id type, int cpu) { struct aml_persistent_ram_zone *prz; int ret = -ENOMEM; prz = kzalloc(sizeof(*prz), GFP_KERNEL); if (!prz) goto err; /* Initialize general buffer state. */ prz->type = type; prz->size = size; prz->cpu = cpu; prz->paddr = start + cpu * size; prz->vaddr = aml_persistent_ram_vmap(prz->paddr, prz->size); if (!prz->vaddr) goto err; prz->buffer = prz->vaddr; prz->buffer_size = size - sizeof(struct aml_persistent_ram_buffer); ret = aml_persistent_ram_post_init(prz); if (ret) goto err; return prz; err: aml_persistent_ram_free(prz); return ERR_PTR(ret); } static int aml_ramoops_init_przs(struct aml_persistent_ram_zone ***przs, enum aml_pstore_type_id type) { int cpu; int possible_cpu = num_possible_cpus(); struct aml_persistent_ram_zone **prz_ar; struct aml_ramoops_context *cxt = &aml_oops_cxt; phys_addr_t phys_base; ssize_t phys_size; prz_ar = NULL; switch (type) { case AML_PSTORE_TYPE_IO: phys_base = cxt->phys_addr; phys_size = cxt->io_size; break; case AML_PSTORE_TYPE_SCHED: phys_base = cxt->phys_addr + possible_cpu * cxt->io_size; phys_size = cxt->sched_size; break; case AML_PSTORE_TYPE_IRQ: phys_base = cxt->phys_addr + possible_cpu * (cxt->io_size + cxt->sched_size); phys_size = cxt->irq_size; break; case AML_PSTORE_TYPE_SMC: phys_base = cxt->phys_addr + possible_cpu * (cxt->io_size + cxt->sched_size + cxt->irq_size); phys_size = cxt->smc_size; break; case AML_PSTORE_TYPE_MISC: phys_base = cxt->phys_addr + possible_cpu * (cxt->io_size + cxt->sched_size + cxt->irq_size + cxt->smc_size); phys_size = cxt->misc_size; break; default: pr_err("Wrong ram zone type\n"); return -ENOMEM; } prz_ar = kcalloc(possible_cpu, sizeof(**przs), GFP_KERNEL); if (!prz_ar) return -ENOMEM; for (cpu = 0; cpu < possible_cpu; cpu++) { prz_ar[cpu] = aml_persistent_ram_new(phys_base, phys_size, type, cpu); if (IS_ERR(prz_ar[cpu])) { while (cpu > 0) { cpu--; aml_persistent_ram_free(prz_ar[cpu]); } kfree(prz_ar); return -ENOMEM; } } *przs = prz_ar; return 0; } static size_t aml_buffer_start_add(struct aml_persistent_ram_zone *prz, size_t a) { int old; int new; old = atomic_read(&prz->quick_start); new = old + a; while (unlikely(new >= prz->buffer_size)) new -= prz->buffer_size; atomic_set(&prz->buffer->start, new); atomic_set(&prz->quick_start, new); return old; } static void aml_buffer_size_add(struct aml_persistent_ram_zone *prz, size_t a) { size_t old; size_t new; old = atomic_read(&prz->quick_size); if (old == prz->buffer_size) return; new = old + a; if (new > prz->buffer_size) new = prz->buffer_size; atomic_set(&prz->buffer->size, new); atomic_set(&prz->quick_size, new); } static void notrace aml_persistent_ram_update(struct aml_persistent_ram_zone *prz, const void *s, unsigned int start, unsigned int count) { struct aml_persistent_ram_buffer *buffer = prz->buffer; memcpy_toio(buffer->data + start, s, count); } static int notrace aml_persistent_ram_write(struct aml_persistent_ram_zone *prz, const void *s, unsigned int count) { int rem; int c = count; size_t start; if (unlikely(c > prz->buffer_size)) { s += c - prz->buffer_size; c = prz->buffer_size; } aml_buffer_size_add(prz, c); start = aml_buffer_start_add(prz, c); rem = prz->buffer_size - start; if (unlikely(rem < c)) { aml_persistent_ram_update(prz, s, start, rem); s += rem; c -= rem; start = 0; } aml_persistent_ram_update(prz, s, start, c); return count; } static void notrace aml_ramoops_pstore_write(int cpu, enum aml_pstore_type_id type, struct iotrace_record *buf, ssize_t size) { switch (type) { case AML_PSTORE_TYPE_IO: aml_persistent_ram_write(aml_oops_cxt.io_przs[cpu], buf, size); break; case AML_PSTORE_TYPE_SCHED: aml_persistent_ram_write(aml_oops_cxt.sched_przs[cpu], buf, size); break; case AML_PSTORE_TYPE_IRQ: aml_persistent_ram_write(aml_oops_cxt.irq_przs[cpu], buf, size); break; case AML_PSTORE_TYPE_SMC: aml_persistent_ram_write(aml_oops_cxt.smc_przs[cpu], buf, size); break; case AML_PSTORE_TYPE_MISC: aml_persistent_ram_write(aml_oops_cxt.misc_przs[cpu], buf, size); break; default: pr_info("Error record type %d\n", type); break; } } void __nocfi aml_pstore_write(enum aml_pstore_type_id type, struct iotrace_record *rec, unsigned int dis_irq, unsigned int io_flag) { int cpu = raw_smp_processor_id(); unsigned long flags = 0; if (!ramoops_ftrace_en) return; if (type != AML_PSTORE_TYPE_IO) raw_local_irq_save(flags); rec->magic = 0xabcd; rec->cpu = cpu; rec->time = sched_clock(); rec->pid = current->pid; rec->flag.in_irq = in_hardirq() ? 1 : 0; rec->flag.in_softirq = in_serving_softirq() ? 1 : 0; rec->flag.irq_disabled = dis_irq ? 1 : 0; aml_ramoops_pstore_write(cpu, type, rec, sizeof(struct iotrace_record)); if (type != AML_PSTORE_TYPE_IO) raw_local_irq_restore(flags); } EXPORT_SYMBOL(aml_pstore_write); void iotrace_misc_record_write(enum iotrace_record_type_id type, unsigned long misc_data_1, unsigned long misc_data_2, unsigned long misc_data_3) { struct iotrace_record rec = { .type = type, }; if (!ramoops_ftrace_en || !(ramoops_trace_mask & TRACE_MASK_MISC)) return; switch (type) { case RECORD_TYPE_USB_IN: __this_cpu_write(usb_iotrace_cut, 1); break; case RECORD_TYPE_USB_OUT: __this_cpu_write(usb_iotrace_cut, 0); break; case RECORD_TYPE_FRC_INPUT_IN: case RECORD_TYPE_FRC_OUTPUT_IN: __this_cpu_write(frc_iotrace_cut, 1); break; case RECORD_TYPE_FRC_INPUT_OUT: case RECORD_TYPE_FRC_OUTPUT_OUT: __this_cpu_write(frc_iotrace_cut, 0); break; case RECORD_TYPE_VSYNC_IN: __this_cpu_write(vsync_iotrace_cut, 1); break; case RECORD_TYPE_VSYNC_OUT: __this_cpu_write(vsync_iotrace_cut, 0); break; case RECORD_TYPE_AMVECM_IN: __this_cpu_write(amvecm_iotrace_cut, 1); break; case RECORD_TYPE_AMVECM_OUT: __this_cpu_write(amvecm_iotrace_cut, 0); break; default: return; } aml_pstore_write(AML_PSTORE_TYPE_MISC, &rec, irqs_disabled(), 0); } EXPORT_SYMBOL(iotrace_misc_record_write); static int aml_ramoops_init(void) { int ret = 0; ret = aml_ramoops_parse_dt(); if (ret) return -EINVAL; ret = aml_ramoops_init_przs(&aml_oops_cxt.io_przs, AML_PSTORE_TYPE_IO); if (ret) return -ENOMEM; ret = aml_ramoops_init_przs(&aml_oops_cxt.sched_przs, AML_PSTORE_TYPE_SCHED); if (ret) return -ENOMEM; ret = aml_ramoops_init_przs(&aml_oops_cxt.irq_przs, AML_PSTORE_TYPE_IRQ); if (ret) return -ENOMEM; ret = aml_ramoops_init_przs(&aml_oops_cxt.smc_przs, AML_PSTORE_TYPE_SMC); if (ret) return -ENOMEM; ret = aml_ramoops_init_przs(&aml_oops_cxt.misc_przs, AML_PSTORE_TYPE_MISC); if (ret) return -ENOMEM; return ret; } static void record_print_buf(struct iotrace_record *rec, enum aml_pstore_type_id type, char *buf) { unsigned long sec = 0, us = 0; unsigned long long time = rec->time; if (rec->type >= ARRAY_SIZE(record_name)) { sprintf(buf, "Unknown Record Type:%d", rec->type); return; } do_div(time, 1000); us = (unsigned long)do_div(time, 1000000); sec = (unsigned long)time; switch (type) { case AML_PSTORE_TYPE_IO: sprintf(buf, "[%04ld.%06ld@%d %c%c] <%d> <%6s %08x-%8x> <%pS <- %pS>\n", sec, us, rec->cpu, get_disirq_flag(rec->flag.irq_disabled), get_irq_flag(rec->flag.in_irq, rec->flag.in_softirq), rec->pid, record_name[rec->type], rec->reg, rec->reg_val, (void *)rec->ip, (void *)rec->parent_ip); break; case AML_PSTORE_TYPE_SCHED: sprintf(buf, "[%04ld.%06ld@%d %c%c] <%d> <%s prev:%s/%d next:%s/%d>\n", sec, us, rec->cpu, get_disirq_flag(rec->flag.irq_disabled), get_irq_flag(rec->flag.in_irq, rec->flag.in_softirq), rec->pid, record_name[rec->type], rec->curr_comm, rec->curr_pid, rec->next_comm, rec->next_pid); break; case AML_PSTORE_TYPE_IRQ: sprintf(buf, "[%04ld.%06ld@%d %c%c] <%d> <%s %d>\n", sec, us, rec->cpu, get_disirq_flag(rec->flag.irq_disabled), get_irq_flag(rec->flag.in_irq, rec->flag.in_softirq), rec->pid, record_name[rec->type], rec->irq); break; case AML_PSTORE_TYPE_SMC: sprintf(buf, "[%04ld.%06ld@%d %c%c] <%d> <%s 0x%lx 0x%lx>\n", sec, us, rec->cpu, get_disirq_flag(rec->flag.irq_disabled), get_irq_flag(rec->flag.in_irq, rec->flag.in_softirq), rec->pid, record_name[rec->type], rec->smcid, rec->val); break; case AML_PSTORE_TYPE_MISC: sprintf(buf, "[%04ld.%06ld@%d %c%c] <%d> <%s>\n", sec, us, rec->cpu, get_disirq_flag(rec->flag.irq_disabled), get_irq_flag(rec->flag.in_irq, rec->flag.in_softirq), rec->pid, record_name[rec->type]); break; default: sprintf(buf, "Unknown Type:%d", type); break; } } static void *percpu_trace_start(struct seq_file *s, loff_t *pos) { struct aml_persistent_ram_zone *prz = (struct aml_persistent_ram_zone *)s->private; struct percpu_trace_data *data; data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return NULL; data->ptr = (void *)prz->old_log + *pos; data->ptr_end = (void *)prz->old_log + prz->old_log_size; if (data->ptr + sizeof(struct iotrace_record) > data->ptr_end) { kfree(data); return NULL; } if (!(*pos)) { data->ptr += prz->old_log_size % sizeof(struct iotrace_record); (*pos) += prz->old_log_size % sizeof(struct iotrace_record); if (data->ptr + sizeof(struct iotrace_record) > data->ptr_end) { kfree(data); return NULL; } } return data; } static void percpu_trace_stop(struct seq_file *s, void *v) { kfree(v); } static void *percpu_trace_next(struct seq_file *s, void *v, loff_t *pos) { struct percpu_trace_data *data = v; data->ptr += sizeof(struct iotrace_record); *pos += sizeof(struct iotrace_record); if (data->ptr + sizeof(struct iotrace_record) > data->ptr_end) return NULL; return data; } static int percpu_trace_show(struct seq_file *s, void *v) { char buf[1024]; struct aml_persistent_ram_zone *prz; struct iotrace_record *rec; struct percpu_trace_data *data = v; if (!data) return 0; prz = (struct aml_persistent_ram_zone *)s->private; rec = (struct iotrace_record *)data->ptr; record_print_buf(rec, prz->type, buf); seq_printf(s, buf); return 0; } static const struct seq_operations percpu_trace_ops = { .start = percpu_trace_start, .next = percpu_trace_next, .stop = percpu_trace_stop, .show = percpu_trace_show, }; static int percpu_trace_open(struct inode *inode, struct file *file) { struct seq_file *sf; int err; err = seq_open(file, &percpu_trace_ops); if (err < 0) return err; sf = file->private_data; if (!sf) return -EINVAL; sf->private = pde_data(inode); return 0; } static const struct proc_ops percpu_trace_file_ops = { .proc_open = percpu_trace_open, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = seq_release, }; static void get_first_record_type(struct prz_record_iter **iter, int cpu, enum aml_pstore_type_id type, unsigned long long *min_time) { switch (type) { case AML_PSTORE_TYPE_IO: aml_oops_cxt.record_iter[type][cpu].ptr = aml_oops_cxt.io_przs[cpu]->old_log + aml_oops_cxt.io_przs[cpu]->old_log_size % sizeof(struct iotrace_record); aml_oops_cxt.record_iter[type][cpu].ptr_end = aml_oops_cxt.io_przs[cpu]->old_log + aml_oops_cxt.io_przs[cpu]->old_log_size; break; case AML_PSTORE_TYPE_SCHED: aml_oops_cxt.record_iter[type][cpu].ptr = aml_oops_cxt.sched_przs[cpu]->old_log + aml_oops_cxt.sched_przs[cpu]->old_log_size % sizeof(struct iotrace_record); aml_oops_cxt.record_iter[type][cpu].ptr_end = aml_oops_cxt.sched_przs[cpu]->old_log + aml_oops_cxt.sched_przs[cpu]->old_log_size; break; case AML_PSTORE_TYPE_IRQ: aml_oops_cxt.record_iter[type][cpu].ptr = aml_oops_cxt.irq_przs[cpu]->old_log + aml_oops_cxt.irq_przs[cpu]->old_log_size % sizeof(struct iotrace_record); aml_oops_cxt.record_iter[type][cpu].ptr_end = aml_oops_cxt.irq_przs[cpu]->old_log + aml_oops_cxt.irq_przs[cpu]->old_log_size; break; case AML_PSTORE_TYPE_SMC: aml_oops_cxt.record_iter[type][cpu].ptr = aml_oops_cxt.smc_przs[cpu]->old_log + aml_oops_cxt.smc_przs[cpu]->old_log_size % sizeof(struct iotrace_record); aml_oops_cxt.record_iter[type][cpu].ptr_end = aml_oops_cxt.smc_przs[cpu]->old_log + aml_oops_cxt.smc_przs[cpu]->old_log_size; break; case AML_PSTORE_TYPE_MISC: aml_oops_cxt.record_iter[type][cpu].ptr = aml_oops_cxt.misc_przs[cpu]->old_log + aml_oops_cxt.misc_przs[cpu]->old_log_size % sizeof(struct iotrace_record); aml_oops_cxt.record_iter[type][cpu].ptr_end = aml_oops_cxt.misc_przs[cpu]->old_log + aml_oops_cxt.misc_przs[cpu]->old_log_size; break; default: pr_err("Error aml_pstore_type_id %d\n", type); return; } aml_oops_cxt.record_iter[type][cpu].type = type; aml_oops_cxt.record_iter[type][cpu].over = 0; aml_oops_cxt.record_iter[type][cpu].cpu = cpu; if (aml_oops_cxt.record_iter[type][cpu].ptr + sizeof(struct iotrace_record) > aml_oops_cxt.record_iter[type][cpu].ptr_end) { aml_oops_cxt.record_iter[type][cpu].over = 1; return; } if (!aml_oops_cxt.record_iter[type][cpu].over && ((struct iotrace_record *)aml_oops_cxt.record_iter[type][cpu].ptr)->time < *min_time) { *min_time = ((struct iotrace_record *)aml_oops_cxt.record_iter[type][cpu].ptr)->time; *iter = &aml_oops_cxt.record_iter[type][cpu]; } } static struct prz_record_iter *prz_record_iter_init(void) { u64 min_time = ~0x0ULL; int possible_cpu = num_possible_cpus(); struct prz_record_iter *iter = NULL; int cpu; memset(aml_oops_cxt.record_iter, 0, sizeof(aml_oops_cxt.record_iter[0][0]) * AML_PSTORE_TYPE_MAX * 8); // max possible cpu 8 for (cpu = 0; cpu < possible_cpu; cpu++) { if (aml_oops_cxt.io_przs[cpu]->old_log) get_first_record_type(&iter, cpu, AML_PSTORE_TYPE_IO, &min_time); else aml_oops_cxt.record_iter[AML_PSTORE_TYPE_IO][cpu].over = 1; if (aml_oops_cxt.sched_przs[cpu]->old_log) get_first_record_type(&iter, cpu, AML_PSTORE_TYPE_SCHED, &min_time); else aml_oops_cxt.record_iter[AML_PSTORE_TYPE_SCHED][cpu].over = 1; if (aml_oops_cxt.irq_przs[cpu]->old_log) get_first_record_type(&iter, cpu, AML_PSTORE_TYPE_IRQ, &min_time); else aml_oops_cxt.record_iter[AML_PSTORE_TYPE_IRQ][cpu].over = 1; if (aml_oops_cxt.smc_przs[cpu]->old_log) get_first_record_type(&iter, cpu, AML_PSTORE_TYPE_SMC, &min_time); else aml_oops_cxt.record_iter[AML_PSTORE_TYPE_SMC][cpu].over = 1; if (aml_oops_cxt.misc_przs[cpu]->old_log) get_first_record_type(&iter, cpu, AML_PSTORE_TYPE_MISC, &min_time); else aml_oops_cxt.record_iter[AML_PSTORE_TYPE_MISC][cpu].over = 1; } return iter; } static void get_next_record_type(struct prz_record_iter **iter, int cpu, enum aml_pstore_type_id type, unsigned long long *min_time) { if (!aml_oops_cxt.record_iter[type][cpu].over) { if (aml_oops_cxt.record_iter[type][cpu].ptr + sizeof(struct iotrace_record) > aml_oops_cxt.record_iter[type][cpu].ptr_end) { aml_oops_cxt.record_iter[type][cpu].over = 1; return; } if (((struct iotrace_record *)aml_oops_cxt.record_iter[type][cpu].ptr)->time < *min_time) { *min_time = ((struct iotrace_record *)aml_oops_cxt.record_iter[type][cpu].ptr)->time; *iter = &aml_oops_cxt.record_iter[type][cpu]; } } } static struct prz_record_iter *get_next_record(void) { unsigned long long min_time = ~0x0ULL; struct prz_record_iter *iter; int possible_cpu = num_possible_cpus(); int cpu; iter = NULL; for (cpu = 0; cpu < possible_cpu; cpu++) { get_next_record_type(&iter, cpu, AML_PSTORE_TYPE_IO, &min_time); get_next_record_type(&iter, cpu, AML_PSTORE_TYPE_SCHED, &min_time); get_next_record_type(&iter, cpu, AML_PSTORE_TYPE_IRQ, &min_time); get_next_record_type(&iter, cpu, AML_PSTORE_TYPE_SMC, &min_time); get_next_record_type(&iter, cpu, AML_PSTORE_TYPE_MISC, &min_time); } return iter; } static void trace_show_iter(struct seq_file *s, struct prz_record_iter *iter) { char buf[1024]; struct iotrace_record *rec = (struct iotrace_record *)iter->ptr; record_print_buf(rec, iter->type, buf); seq_printf(s, buf); } static void *trace_start(struct seq_file *seq, loff_t *pos) { if (*pos && aml_oops_cxt.seq_file_overflow) { aml_oops_cxt.seq_file_overflow = 0; return aml_oops_cxt.curr_record_iter; } if (!*pos) aml_oops_cxt.curr_record_iter = prz_record_iter_init(); else aml_oops_cxt.curr_record_iter = get_next_record(); return aml_oops_cxt.curr_record_iter; } static void *trace_next(struct seq_file *seq, void *v, loff_t *pos) { struct prz_record_iter *iter = (struct prz_record_iter *)v; (*pos)++; iter->ptr += sizeof(struct iotrace_record); aml_oops_cxt.curr_record_iter = get_next_record(); return aml_oops_cxt.curr_record_iter; } static void trace_stop(struct seq_file *seq, void *v) { aml_oops_cxt.seq_file_overflow = 1; } static int trace_show(struct seq_file *seq, void *v) { trace_show_iter(seq, v); return 0; } static const struct seq_operations trace_ops = { .start = trace_start, .next = trace_next, .stop = trace_stop, .show = trace_show, }; static int trace_open(struct inode *inode, struct file *filp) { return seq_open(filp, &trace_ops); } static const struct proc_ops trace_file_ops = { .proc_open = trace_open, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = seq_release, }; static void proc_init(void) { struct proc_dir_entry *dir_aml_iotrace; struct proc_dir_entry *dir_aml_percpu; struct proc_dir_entry *dir_cpu_ar[8]; // max possible cpu 8 int possible_cpu = num_possible_cpus(); int cpu; char buf[10]; dir_aml_iotrace = proc_mkdir("aml_iotrace", NULL); if (IS_ERR_OR_NULL(dir_aml_iotrace)) { pr_warn("failed to create /proc/aml_iotrace directory\n"); dir_aml_iotrace = NULL; return; } /* proc/aml_iotrace/trace */ proc_create_data("trace", S_IFREG | 0440, dir_aml_iotrace, &trace_file_ops, NULL); dir_aml_percpu = proc_mkdir("per_cpu", dir_aml_iotrace); if (IS_ERR_OR_NULL(dir_aml_percpu)) { pr_warn("failed to create /proc/per_cpu directory\n"); dir_aml_percpu = NULL; return; } for (cpu = 0; cpu < possible_cpu; cpu++) { memset(buf, 0, sizeof(buf)); sprintf(buf, "cpu%d", cpu); dir_cpu_ar[cpu] = proc_mkdir(buf, dir_aml_percpu); if (IS_ERR_OR_NULL(dir_cpu_ar[cpu])) { pr_warn("failed to create /proc/per_cpu/%s directory\n", buf); dir_aml_percpu = NULL; return; } if (aml_oops_cxt.io_przs[cpu]->old_log_size) proc_create_data("io_trace", S_IFREG | 0440, dir_cpu_ar[cpu], &percpu_trace_file_ops, aml_oops_cxt.io_przs[cpu]); if (aml_oops_cxt.sched_przs[cpu]->old_log_size) proc_create_data("sched_trace", S_IFREG | 0440, dir_cpu_ar[cpu], &percpu_trace_file_ops, aml_oops_cxt.sched_przs[cpu]); if (aml_oops_cxt.irq_przs[cpu]->old_log_size) proc_create_data("irq_trace", S_IFREG | 0440, dir_cpu_ar[cpu], &percpu_trace_file_ops, aml_oops_cxt.irq_przs[cpu]); if (aml_oops_cxt.smc_przs[cpu]->old_log_size) proc_create_data("smc_trace", S_IFREG | 0440, dir_cpu_ar[cpu], &percpu_trace_file_ops, aml_oops_cxt.smc_przs[cpu]); if (aml_oops_cxt.misc_przs[cpu]->old_log_size) proc_create_data("misc_trace", S_IFREG | 0440, dir_cpu_ar[cpu], &percpu_trace_file_ops, aml_oops_cxt.misc_przs[cpu]); } } static void trace_auto_show_iter(struct prz_record_iter *iter) { char buf[256]; static unsigned int autodump_rec_num; struct iotrace_record *rec = (struct iotrace_record *)iter->ptr; record_print_buf(rec, iter->type, buf); pr_info("%s", buf); iter->ptr += sizeof(struct iotrace_record); if (!(autodump_rec_num++ % 100)) msleep(50); } void iotrace_auto_dump(void) { aml_oops_cxt.curr_record_iter = prz_record_iter_init(); if (!aml_oops_cxt.curr_record_iter) return; trace_auto_show_iter(aml_oops_cxt.curr_record_iter); while (1) { aml_oops_cxt.curr_record_iter = get_next_record(); if (!aml_oops_cxt.curr_record_iter) break; trace_auto_show_iter(aml_oops_cxt.curr_record_iter); } pr_info("iotrace log auto dump finished\n"); } static void iotrace_work_func(struct work_struct *work) { if (!strcmp(reboot_mode, "watchdog_reboot") || (!strcmp(reboot_mode, "kernel_panic") && ramoops_io_dump == DUMP_WATCHDOG_PANIC) || ramoops_io_dump == DUMP_ALL) { pr_info("ramoops: en:%d, dump=%d, skip=%d, stack=%d\n", ramoops_io_en, ramoops_io_dump, ramoops_io_skip, ramoops_io_stack); iotrace_auto_dump(); } } static int module_base_callback(struct notifier_block *nb, unsigned long val, void *data) { struct module *mod = data; switch (mod->state) { case MODULE_STATE_COMING: if (module_debug == 1) pr_info("install module:%s core_base:%lx, size=%lx, init_base:%lx, size=%lx\n", mod->name, (unsigned long)mod->mem[MOD_TEXT].base, (unsigned long)mod->mem[MOD_TEXT].size, (unsigned long)mod->mem[MOD_INIT_TEXT].base, (unsigned long)mod->mem[MOD_INIT_TEXT].size); break; case MODULE_STATE_GOING: if (module_debug) pr_info("remove module:%s core_base:%lx, size=%lx, init_base:%lx, size=%lx\n", mod->name, (unsigned long)mod->mem[MOD_TEXT].base, (unsigned long)mod->mem[MOD_TEXT].size, (unsigned long)mod->mem[MOD_INIT_TEXT].base, (unsigned long)mod->mem[MOD_INIT_TEXT].size); break; default: break; } return 0; } static struct notifier_block module_base_nb = { .notifier_call = module_base_callback, }; int __init aml_iotrace_init(void) { int ret = 0; if (ramoops_io_en || module_debug) register_module_notifier(&module_base_nb); #if IS_ENABLED(CONFIG_AMLOGIC_DEBUG_IOTM) iotm_init(); #endif #if IS_ENABLED(CONFIG_AMLOGIC_DEBUG_AOCPU_MONITOR) aocpu_monitor_init(); #endif if (!ramoops_io_en) return 0; ret = aml_ramoops_init(); if (ret) { pr_err("Fail to init ramoops\n"); return 0; } proc_init(); ftrace_ramoops_init(); if (ramoops_io_dump) { INIT_DELAYED_WORK(&iotrace_work, iotrace_work_func); queue_delayed_work(system_unbound_wq, &iotrace_work, ramoops_io_dump_delay_secs * HZ); } ramoops_ftrace_en = 1; /* * V1: iotrace builtin,like 5.4/4.9 * V2: iotrace built to ko * V3: iotrace do not modify module init_layout free, * use offset to record pc_symbol * V4: iotrace read/write use vendor hooks * depends on 13-5.15-16 or 14-5.15-9 * V5: modify iotrace data, delay free module init_layout memory * V6: iotm_v1 support * V7: iotm_v2 support */ pr_info("iotrace V6\n"); return 0; } void __exit aml_iotrace_exit(void) { } module_init(aml_iotrace_init); module_exit(aml_iotrace_exit); MODULE_LICENSE("GPL");