/* * drivers/trace/exynos-condbg.c * * Copyright (c) 2016 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Exynos-Console-Debugger for Exynos SoC * This codes are based on fiq_debugger of google * /driver/staging/android/fiq_debugger * * Author: Hosung Kim * Changki Kim * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "exynos-condbg-dev.h" #include "exynos-condbg-ringbuf.h" #define MAX_DEBUGGER_PORTS (4) #define MAX_IRQS (512) #define ECD_RC_PATH "/data/ecd.rc" #define PR_ECD "[ECD]" #ifdef TEST_BIN #define FW_PATH "/data/" #else #define FW_PATH "/vendor/firmware/" #endif static struct ecd_interface *interface = NULL; static bool initial_console_enable = false; static bool initial_ecd_enable = false; bool initial_no_firmware = false; extern int ecd_init_binary(unsigned long, unsigned long); extern int ecd_start_binary(unsigned long); extern struct irq_domain *gic_get_root_irqdomain(unsigned int gic_nr); extern int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw); static struct list_head ecd_ioremap_list; static struct vm_struct ecd_early_vm; struct ecd_ioremap_item { unsigned long vaddr; unsigned long paddr; unsigned int size; struct list_head list; }; struct ecd_interface_ops { int (*do_bad)(unsigned long, struct pt_regs *); int (*do_breakpoint)(unsigned long, struct pt_regs *); int (*do_watchpoint)(unsigned long, struct pt_regs *); void (*do_break_now)(void); int (*get_console_enable)(void); int (*get_debug_mode)(void); int (*get_debug_panic)(void); int (*uart_irq_handler)(int irq, void *dev); int (*sysfs_profile_show)(char *); int (*sysfs_profile_store)(const char *, int); int (*sysfs_enable_show)(char *); int (*sysfs_enable_store)(const char *, int); int (*sysfs_break_now_store)(const char *, int); int (*sysfs_switch_dbg_store)(const char *, int); }; struct ecd_rc { int setup_idx; int action_idx; char setup[SZ_8][SZ_64]; char action[SZ_32][SZ_64]; }; struct ecd_param { unsigned int console_enable; unsigned int debug_panic; unsigned int debug_mode; unsigned int no_sleep; unsigned int count_break; struct ecd_rc rc; }; struct ecd_interface { struct ecd_pdata *pdata; const struct firmware *fw; void *fw_data; size_t fw_size; size_t fw_addr; struct ecd_interface_ops ops; struct ecd_param param; struct platform_device *pdev; char *output_buf; int uart_irq; unsigned int debug_count; bool fw_loaded; bool fw_ready; bool unhandled_irq; spinlock_t iomap_lock; spinlock_t work_lock; unsigned int last_irqs[MAX_IRQS]; struct delayed_work check_load_firmware; #ifdef CONFIG_EXYNOS_CONSOLE_DEBUGGER_INTERFACE spinlock_t console_lock; struct console console; struct tty_port tty_port; struct ecd_ringbuf *tty_rbuf; bool syslog_dumping; #endif }; #ifdef CONFIG_EXYNOS_CONSOLE_DEBUGGER_INTERFACE static struct tty_driver *ecd_tty_driver; #endif bool ecd_get_debug_panic(void) { if (interface && interface->fw_loaded) { return interface->ops.get_debug_panic(); } else return false; } bool ecd_get_enable(void) { if (interface) { return interface->fw_loaded; } else return false; } int ecd_get_debug_mode(void) { if (interface && interface->fw_loaded) return interface->ops.get_debug_mode(); else return 0; } int ecd_do_bad(unsigned long addr, struct pt_regs *regs) { if (interface && interface->fw_loaded) return interface->ops.do_bad(addr, regs); else return 1; } int ecd_hook_ioremap(unsigned long paddr, unsigned long vaddr, unsigned int size) { struct ecd_ioremap_item *item; if (interface) { spin_lock(&interface->iomap_lock); item = kmalloc(sizeof(struct ecd_ioremap_item), GFP_ATOMIC); item->paddr = paddr; item->vaddr = vaddr; item->size = size; list_add(&item->list, &ecd_ioremap_list); spin_unlock(&interface->iomap_lock); } return 0; } void ecd_hook_iounmap(unsigned long vaddr) { struct ecd_ioremap_item *item, *next_item; struct list_head *list_main = &ecd_ioremap_list; if (interface) { spin_lock(&interface->iomap_lock); list_for_each_entry_safe(item, next_item, list_main, list) { if (vaddr == item->vaddr) { list_del(&item->list); break; } } spin_unlock(&interface->iomap_lock); } } bool ecd_lookup_check_sfr(unsigned long vaddr) { struct ecd_ioremap_item *item, *next_item; struct list_head *list_main = &ecd_ioremap_list; list_for_each_entry_safe(item, next_item, list_main, list) { if ((vaddr == item->vaddr) || (vaddr > item->vaddr && vaddr < item->vaddr + item->size)) return true; } return false; } void ecd_lookup_dump_sfr(unsigned long paddr) { struct ecd_ioremap_item *item, *next_item; struct list_head *list_main = &ecd_ioremap_list; unsigned long vaddr = 0; if (!paddr) { list_for_each_entry_safe(item, next_item, list_main, list) { ecd_printf(" 0x%8zx | 0x%16zx | 0x%8zx |\n", item->paddr, item->vaddr, item->size); } } else { list_for_each_entry_safe(item, next_item, list_main, list) { if (paddr == item->paddr) { vaddr = item->vaddr; } else if (paddr > item->paddr && paddr < item->paddr + item->size) { long sub = paddr - item->paddr; vaddr = item->vaddr + sub; } else vaddr = 0; if (vaddr) { ecd_printf(" 0x%8zx | 0x%16zx | 0x%8zx |\n", paddr, vaddr, item->size); } } } } #ifdef CONFIG_EXYNOS_CONSOLE_DEBUGGER_INTERFACE static void ecd_begin_syslog_dump(void) { interface->syslog_dumping = true; } static void ecd_end_syslog_dump(void) { interface->syslog_dumping = false; } #else extern int do_syslog(int type, char __user *bug, int count); static void ecd_begin_syslog_dump(void) { do_syslog(5 /* clear */, NULL, 0); } static void ecd_end_syslog_dump(void) { ecd_dump_kernel_log(); } #endif static void ecd_do_sysrq(char rq) { ecd_begin_syslog_dump(); handle_sysrq(rq); ecd_end_syslog_dump(); } static void ecd_uart_putc(char c) { if (interface && interface->pdata->uart_putc) interface->pdata->uart_putc(interface->pdev, c); } static void ecd_uart_puts(char *s) { unsigned c; while ((c = *s++)) { if (c == '\n') ecd_uart_putc('\r'); ecd_uart_putc(c); } } static int ecd_uart_getc(void) { if (interface && interface->pdata->uart_getc) return interface->pdata->uart_getc(interface->pdev); else return -1; } static bool ecd_uart_check_break(void) { int c = ecd_uart_getc(); bool ret = false; if (c == 0x3) { ecd_uart_puts("BREAK\n"); ret = true; } return ret; } static void ecd_uart_flush(void) { if (interface && interface->pdata->uart_flush) interface->pdata->uart_flush(interface->pdev); } static void ecd_uart_clear_rxfifo(void) { if (interface && interface->pdata->uart_clear_rxfifo) interface->pdata->uart_clear_rxfifo(interface->pdev); } static void ecd_uart_init(void) { if (interface && interface->pdata->uart_init) interface->pdata->uart_init(interface->pdev); } static void ecd_uart_enable(void) { /* TODO: clk control */ if (interface && interface->pdata->uart_enable) interface->pdata->uart_enable(interface->pdev); } static void ecd_uart_disable(void) { if (interface && interface->pdata->uart_disable) interface->pdata->uart_disable(interface->pdev); /* TODO: clk control */ } #if defined(CONFIG_EXYNOS_CONSOLE_DEBUGGER_INTERFACE) struct tty_driver *ecd_console_device(struct console *co, int *index) { *index = co->index; return ecd_tty_driver; } static void ecd_console_write(struct console *co, const char *s, unsigned int count) { unsigned long flags; if (!interface->fw_loaded) { if (!interface->param.console_enable) return; } else { if (!interface->ops.get_console_enable() && !interface->syslog_dumping) return; } ecd_uart_enable(); spin_lock_irqsave(&interface->console_lock, flags); while (count--) { if (*s == '\n') ecd_uart_putc('\r'); ecd_uart_putc(*s++); } ecd_uart_flush(); spin_unlock_irqrestore(&interface->console_lock, flags); ecd_uart_disable(); } static struct console ecd_console = { .name = "ttyECD", .device = ecd_console_device, .write = ecd_console_write, .flags = CON_PRINTBUFFER | CON_ANYTIME | CON_ENABLED, }; int ecd_tty_open(struct tty_struct *tty, struct file *filp) { int line = tty->index; struct ecd_interface **interfaces = tty->driver->driver_state; struct ecd_interface *interface = interfaces[line]; return tty_port_open(&interface->tty_port, tty, filp); } void ecd_tty_close(struct tty_struct *tty, struct file *filp) { tty_port_close(tty->port, tty, filp); } int ecd_tty_write(struct tty_struct *tty, const unsigned char *buf, int count) { int i; int line = tty->index; struct ecd_interface **interfaces = tty->driver->driver_state; struct ecd_interface *interface = interfaces[line]; unsigned long flags; if (!interface->fw_loaded) { if (!interface->param.console_enable) return count; } else { if (!interface->ops.get_console_enable()) return count; } ecd_uart_enable(); spin_lock_irqsave(&interface->console_lock, flags); for (i = 0; i < count; i++) ecd_uart_putc(*buf++); spin_unlock_irqrestore(&interface->console_lock, flags); ecd_uart_disable(); return count; } int ecd_tty_write_room(struct tty_struct *tty) { return 16; } static const struct tty_port_operations ecd_tty_port_ops; static const struct tty_operations ecd_tty_driver_ops = { .write = ecd_tty_write, .write_room = ecd_tty_write_room, .open = ecd_tty_open, .close = ecd_tty_close, }; static int ecd_tty_init(void) { int ret; struct ecd_interface **interfaces = NULL; if (initial_no_firmware) return -EINVAL; interfaces = kzalloc(sizeof(*interfaces) * MAX_DEBUGGER_PORTS, GFP_KERNEL); if (!interfaces) { pr_err("Failed to allocate console debugger state structres\n"); return -ENOMEM; } ecd_tty_driver = alloc_tty_driver(MAX_DEBUGGER_PORTS); if (!ecd_tty_driver) { pr_err("Failed to allocate console debugger tty\n"); ret = -ENOMEM; goto err_free_state; } ecd_tty_driver->owner = THIS_MODULE; ecd_tty_driver->driver_name = "console-debugger"; ecd_tty_driver->name = "ttyECD"; ecd_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; ecd_tty_driver->subtype = SERIAL_TYPE_NORMAL; ecd_tty_driver->init_termios = tty_std_termios; ecd_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; ecd_tty_driver->driver_state = interfaces; ecd_tty_driver->init_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL; ecd_tty_driver->init_termios.c_ispeed = 115200; ecd_tty_driver->init_termios.c_ospeed = 115200; tty_set_operations(ecd_tty_driver, &ecd_tty_driver_ops); ret = tty_register_driver(ecd_tty_driver); if (ret) { pr_err("Failed to register ECD tty: %d\n", ret); goto err_free_tty; } pr_info("Registered ECD tty driver\n"); return 0; err_free_tty: put_tty_driver(ecd_tty_driver); ecd_tty_driver = NULL; err_free_state: kfree(interfaces); return ret; } static int ecd_tty_init_one(struct ecd_interface *interface) { int ret; struct device *tty_dev; struct ecd_interface **interfaces = ecd_tty_driver->driver_state; interfaces[interface->pdev->id] = interface; interface->tty_rbuf = ecd_ringbuf_alloc(SZ_1K); if (!interface->tty_rbuf) { pr_err("Failed to allocate console debugger ringbuf\n"); ret = -ENOMEM; goto err; } tty_port_init(&interface->tty_port); interface->tty_port.ops = &ecd_tty_port_ops; tty_dev = tty_port_register_device(&interface->tty_port, ecd_tty_driver, interface->pdev->id, &interface->pdev->dev); if (IS_ERR(tty_dev)) { pr_err("Failed to register console debugger tty device\n"); ret = PTR_ERR(tty_dev); goto err; } device_set_wakeup_capable(tty_dev, 1); pr_info("Registered console debugger ttyECD%d\n", interface->pdev->id); return 0; err: ecd_ringbuf_free(interface->tty_rbuf); interface->tty_rbuf = NULL; return ret; } #endif #define CONDBG_FW_ADDR (VMALLOC_START + 0xF6000000 + 0x03000000) #define CONDBG_FW_SIZE (SZ_512K) #define CONDBG_FW_TEXT_SIZE (SZ_128K) struct page_change_data { pgprot_t set_mask; pgprot_t clear_mask; }; static int ecd_change_page_range(pte_t *ptep, pgtable_t token, unsigned long addr, void *data) { struct page_change_data *cdata = data; pte_t pte = *ptep; pte = clear_pte_bit(pte, cdata->clear_mask); pte = set_pte_bit(pte, cdata->set_mask); set_pte(ptep, pte); return 0; } static int ecd_change_memory_common(unsigned long addr, int numpages, pgprot_t set_mask, pgprot_t clear_mask) { unsigned long start = addr; unsigned long size = PAGE_SIZE*numpages; unsigned long end = start + size; int ret; struct page_change_data data; if (!PAGE_ALIGNED(addr)) { start &= PAGE_MASK; end = start + size; WARN_ON_ONCE(1); } if (!numpages) return 0; data.set_mask = set_mask; data.clear_mask = clear_mask; ret = apply_to_page_range(&init_mm, start, size, ecd_change_page_range, &data); flush_tlb_kernel_range(start, end); return ret; } static int ecd_set_memory_ro(unsigned long addr, int numpages) { return ecd_change_memory_common(addr, numpages, __pgprot(PTE_RDONLY), __pgprot(PTE_WRITE)); } static int ecd_set_memory_rw(unsigned long addr, int numpages) { return ecd_change_memory_common(addr, numpages, __pgprot(PTE_WRITE), __pgprot(PTE_RDONLY)); } static int ecd_set_memory_nx(unsigned long addr, int numpages) { return ecd_change_memory_common(addr, numpages, __pgprot(PTE_PXN), __pgprot(0)); } static int ecd_set_memory_x(unsigned long addr, int numpages) { return ecd_change_memory_common(addr, numpages, __pgprot(0), __pgprot(PTE_PXN)); } static noinline_for_stack long get_file_size(struct file *file) { struct kstat st; if (vfs_getattr(&file->f_path, &st)) return -1; if (!S_ISREG(st.mode)) return -1; if (st.size != (long)st.size) return -1; return st.size; } static int get_filesystem_binary(const char *filename, struct ecd_interface *interface) { struct file *fp; int ret = 0; long size; char *buf; fp = filp_open(filename, O_RDONLY, 0); if (!IS_ERR_OR_NULL(fp)) { size = get_file_size(fp); if (size <= 0) { ret = -EBADF; goto out; } /* if a buffer for interfaceary is already allocated */ if (interface->fw_data) { if (!interface->fw_size) { ret = -EINVAL; goto out; } buf = interface->fw_data; /* shrink read size to fit the size of a given buffer */ if (size > interface->fw_size) { pr_crit("%s: will read only %ld bytes from a file (%ld)", __func__, interface->fw_size, size); size = interface->fw_size; } } else { buf = vmalloc(size); if (!buf) { ret = -ENOMEM; goto out; } } ret = kernel_read(fp, 0, buf, size); if (ret != size) { if (!interface->fw_data) vfree(buf); ret = -EBADF; goto out; } else ret = 0; interface->fw_data = buf; interface->fw_size = size; fput(fp); } else { ret = PTR_ERR(fp); } out: return ret; } static int request_binary(struct ecd_interface *interface, const char *path, const char *name, struct device *device) { char *filename; unsigned int retry_cnt = 2; int ret; interface->fw_data = NULL; interface->fw_size = 0; interface->fw = NULL; /* read the requested interfaceary from file system directly */ if (path) { filename = __getname(); if (unlikely(!filename)) return -ENOMEM; snprintf(filename, PATH_MAX, "%s%s", path, name); ret = get_filesystem_binary(filename, interface); __putname(filename); /* read successfully or don't want to go further more */ if (!ret || !device) return ret; } /* ask to 'request_firmware' */ do { ret = request_firmware(&interface->fw, name, device); if (!ret && interface->fw) { interface->fw_data = (void *)interface->fw->data; interface->fw_size = interface->fw->size; break; } } while (!(retry_cnt--)); return ret; } static void release_binary(struct ecd_interface *interface) { if (interface->fw) release_firmware(interface->fw); else if (interface->fw_data) vfree(interface->fw_data); } static void ecd_writel(u32 val, volatile void __iomem *addr) { asm volatile("str %w0, [%1]" : : "r" (val), "r" (addr)); } static void ecd_writeq(u64 val, volatile void __iomem *addr) { asm volatile("str %0, [%1]" : : "r" (val), "r" (addr)); } static u64 ecd_readq(const volatile void __iomem *addr) { u64 val; asm volatile(ALTERNATIVE("ldr %0, [%1]", "ldar %0, [%1]", ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) : "=r" (val) : "r" (addr)); return val; } static u32 ecd_readl(const volatile void __iomem *addr) { u32 val; asm volatile(ALTERNATIVE("ldr %w0, [%1]", "ldar %w0, [%1]", ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) : "=r" (val) : "r" (addr)); return val; } static void __iomem *ecd_ioremap(phys_addr_t phys_addr, size_t size) { return __ioremap((phys_addr), (size), __pgprot(PROT_DEVICE_nGnRE)); } static void ecd_iounmap(volatile void __iomem *io_addr) { return __iounmap(io_addr); } static void *ecd_kzalloc(size_t size) { return kmalloc(size, __GFP_ZERO | GFP_KERNEL); } static void ecd_kfree(const void *addr) { kfree(addr); } static void ecd_spin_lock_irqsave(spinlock_t *lock, unsigned long flags) { spin_lock_irqsave(lock, flags); } static void ecd_spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) { spin_unlock_irqrestore(lock, flags); } static void ecd_local_irq_save(unsigned long flags) { local_irq_save(flags); } static void ecd_local_irq_restore(unsigned long flags) { local_irq_restore(flags); } static void ecd_preempt_enable(void) { preempt_enable(); } static void ecd_preempt_disable(void) { preempt_disable(); } static const char *ecd_get_linux_banner(void) { return linux_banner; } static int ecd_request_irq(unsigned int irq, irq_handler_t handler, unsigned int flags, char* name) { struct irq_domain *domain = (struct irq_domain *)gic_get_root_irqdomain(0); int virq, ret; virq = irq_create_mapping(domain, irq); if (!virq) { pr_err("failed to irq_crete_mapping : %d\n", irq); } gic_irq_domain_map(domain, virq, irq); ret = request_irq(virq, handler, flags, name, interface); if (ret) { pr_err("failed to register interrupt : %d\n", irq); } return virq; } static int ecd_irq_force_affinity(unsigned int irq, int cpu) { return irq_force_affinity(irq, cpumask_of(cpu)); } static void ecd_init_work(struct work_struct **work, work_func_t fn) { *work = kzalloc(sizeof(struct work_struct), GFP_KERNEL); INIT_WORK(*work, fn); } static void ecd_schedule_work(struct work_struct *work) { schedule_work(work); } static void ecd_spin_lock_init(spinlock_t **lock) { *lock = kzalloc(sizeof(spinlock_t), GFP_KERNEL); spin_lock_init(*lock); } static void ecd_wake_lock_init(struct wake_lock **lock, int type, const char *name) { *lock = kzalloc(sizeof(struct wake_lock), GFP_KERNEL); wake_lock_init(*lock, type, name); } static int ecd_spin_trylock(spinlock_t *lock) { return spin_trylock(lock); } static int ecd_cpu_online_func(int cpu) { return cpu_online(cpu); } static int ecd_cpu_possible_func(int cpu) { return cpu_possible(cpu); } static int ecd_num_online_cpus(void) { return num_online_cpus(); } static int ecd_num_possible_cpus(void) { return num_possible_cpus(); } static int ecd_raw_smp_processor_id(int cpu) { return raw_smp_processor_id(); } static char *mode_name(const struct pt_regs *regs) { if (compat_user_mode(regs)) { return "USR"; } else { switch (processor_mode(regs)) { case PSR_MODE_EL0t: return "EL0t"; case PSR_MODE_EL1t: return "EL1t"; case PSR_MODE_EL1h: return "EL1h"; case PSR_MODE_EL2t: return "EL2t"; case PSR_MODE_EL2h: return "EL2h"; default: return "???"; } } } void ecd_printf(const char *fmt, ...) { char *buf; va_list ap; if (!interface || !interface->output_buf) return; buf = interface->output_buf; va_start(ap, fmt); vscnprintf(buf, SZ_1K, fmt, ap); va_end(ap); ecd_uart_puts(buf); } static void ecd_dump_pc(const struct pt_regs *regs) { ecd_printf(" pc %016lx cpsr %08lx mode %s\n", regs->pc, regs->pstate, mode_name(regs)); } static void ecd_dump_regs_aarch32(const struct pt_regs *regs) { ecd_printf(" r0 %08x r1 %08x r2 %08x r3 %08x\n", regs->compat_usr(0), regs->compat_usr(1), regs->compat_usr(2), regs->compat_usr(3)); ecd_printf(" r4 %08x r5 %08x r6 %08x r7 %08x\n", regs->compat_usr(4), regs->compat_usr(5), regs->compat_usr(6), regs->compat_usr(7)); ecd_printf(" r8 %08x r9 %08x r10 %08x r11 %08x\n", regs->compat_usr(8), regs->compat_usr(9), regs->compat_usr(10), regs->compat_usr(11)); ecd_printf(" ip %08x sp %08x lr %08x pc %08x\n", regs->compat_usr(12), regs->compat_sp, regs->compat_lr, regs->pc); ecd_printf(" cpsr %08x (%s)\n", regs->pstate, mode_name(regs)); } static void ecd_dump_regs_aarch64(const struct pt_regs *regs) { ecd_printf(" x0 %016lx x1 %016lx\n", regs->regs[0], regs->regs[1]); ecd_printf(" x2 %016lx x3 %016lx\n", regs->regs[2], regs->regs[3]); ecd_printf(" x4 %016lx x5 %016lx\n", regs->regs[4], regs->regs[5]); ecd_printf(" x6 %016lx x7 %016lx\n", regs->regs[6], regs->regs[7]); ecd_printf(" x8 %016lx x9 %016lx\n", regs->regs[8], regs->regs[9]); ecd_printf(" x10 %016lx x11 %016lx\n", regs->regs[10], regs->regs[11]); ecd_printf(" x12 %016lx x13 %016lx\n", regs->regs[12], regs->regs[13]); ecd_printf(" x14 %016lx x15 %016lx\n", regs->regs[14], regs->regs[15]); ecd_printf(" x16 %016lx x17 %016lx\n", regs->regs[16], regs->regs[17]); ecd_printf(" x18 %016lx x19 %016lx\n", regs->regs[18], regs->regs[19]); ecd_printf(" x20 %016lx x21 %016lx\n", regs->regs[20], regs->regs[21]); ecd_printf(" x22 %016lx x23 %016lx\n", regs->regs[22], regs->regs[23]); ecd_printf(" x24 %016lx x25 %016lx\n", regs->regs[24], regs->regs[25]); ecd_printf(" x26 %016lx x27 %016lx\n", regs->regs[26], regs->regs[27]); ecd_printf(" x28 %016lx x29 %016lx\n", regs->regs[28], regs->regs[29]); ecd_printf(" x30 %016lx sp %016lx\n", regs->regs[30], regs->sp); ecd_printf(" pc %016lx cpsr %08x (%s)\n", regs->pc, regs->pstate, mode_name(regs)); } static void ecd_dump_regs(const struct pt_regs *regs) { if (compat_user_mode(regs)) ecd_dump_regs_aarch32(regs); else ecd_dump_regs_aarch64(regs); } #define READ_SPECIAL_REG(x) ({ \ u64 val; \ asm volatile ("mrs %0, " # x : "=r"(val)); \ val; \ }) static void ecd_dump_allregs(const struct pt_regs *regs) { u32 pstate = READ_SPECIAL_REG(CurrentEl); bool in_el2 = (pstate & PSR_MODE_MASK) >= PSR_MODE_EL2t; ecd_dump_regs(regs); ecd_printf(" sp_el0 %016lx\n", READ_SPECIAL_REG(sp_el0)); if (in_el2) ecd_printf(" sp_el1 %016lx\n", READ_SPECIAL_REG(sp_el1)); ecd_printf(" elr_el1 %016lx\n", READ_SPECIAL_REG(elr_el1)); ecd_printf(" spsr_el1 %08lx\n", READ_SPECIAL_REG(spsr_el1)); if (in_el2) { ecd_printf(" spsr_irq %08lx\n", READ_SPECIAL_REG(spsr_irq)); ecd_printf(" spsr_abt %08lx\n", READ_SPECIAL_REG(spsr_abt)); ecd_printf(" spsr_und %08lx\n", READ_SPECIAL_REG(spsr_und)); ecd_printf(" spsr_fiq %08lx\n", READ_SPECIAL_REG(spsr_fiq)); ecd_printf(" spsr_el2 %08lx\n", READ_SPECIAL_REG(elr_el2)); ecd_printf(" spsr_el2 %08lx\n", READ_SPECIAL_REG(spsr_el2)); } } static int report_trace(struct stackframe *frame, void *d) { ecd_printf("%pF:\n", frame->pc); ecd_printf(" pc %016lx sp %016lx fp %016lx\n", frame->pc, frame->sp, frame->fp); return 0; } static void ecd_dump_stacktrace_task(struct pt_regs *regs, struct task_struct *tsk) { struct stackframe frame; if (!tsk) tsk = current; if (regs) { frame.fp = regs->regs[29]; frame.sp = regs->sp; frame.pc = regs->pc; } else if (tsk == current) { frame.fp = (unsigned long)__builtin_frame_address(0); frame.sp = current_stack_pointer; frame.pc = (unsigned long)ecd_dump_stacktrace_task; } else { /* * task blocked in __switch_to */ frame.fp = thread_saved_fp(tsk); frame.sp = thread_saved_sp(tsk); frame.pc = thread_saved_pc(tsk); } ecd_printf("Call trace:\n"); walk_stackframe(NULL, &frame, report_trace, NULL); } #define THREAD_INFO(sp) ((struct thread_info *) \ ((unsigned long)(sp) & ~(THREAD_SIZE - 1))) static void ecd_dump_stacktrace(const struct pt_regs *regs, void *ssp) { struct thread_info *real_thread_info; struct thread_info flags; if (!ssp) real_thread_info = current_thread_info(); else real_thread_info = THREAD_INFO(ssp); memcpy(&flags, current_thread_info(), sizeof(struct thread_info)); *current_thread_info() = *real_thread_info; if (!current) ecd_printf("current NULL\n"); else ecd_printf("comm: %s\n", current->comm); ecd_dump_regs(regs); if (!user_mode(regs)) { struct stackframe frame; frame.fp = regs->regs[29]; frame.sp = regs->sp; frame.pc = regs->pc; ecd_printf("\n"); walk_stackframe(NULL, &frame, report_trace, NULL); } memcpy(current_thread_info(), &flags, sizeof(struct thread_info)); } static void ecd_dump_one_task(struct task_struct *tsk, bool is_main) { char state_array[] = {'R', 'S', 'D', 'T', 't', 'Z', 'X', 'x', 'K', 'W'}; unsigned char idx = 0; unsigned int status = (tsk->state & TASK_REPORT) | tsk->exit_state; unsigned long wchan; unsigned long pc = 0; char symname[KSYM_NAME_LEN]; int permitted; struct mm_struct *mm; permitted = ptrace_may_access(tsk, PTRACE_MODE_READ_FSCREDS); mm = get_task_mm(tsk); if (mm) { if (permitted) pc = KSTK_EIP(tsk); } wchan = get_wchan(tsk); if (lookup_symbol_name(wchan, symname) < 0) { if (!ptrace_may_access(tsk, PTRACE_MODE_READ_FSCREDS)) snprintf(symname, KSYM_NAME_LEN, "_____"); else snprintf(symname, KSYM_NAME_LEN, "%lu", wchan); } while (status) { idx++; status >>= 1; } touch_softlockup_watchdog(); ecd_printf("%8d %8d %8d %16lld %c(%d) %3d %16zx %16zx %16zx %c %16s [%s]\n", tsk->pid, (int)(tsk->utime), (int)(tsk->stime), tsk->se.exec_start, state_array[idx], (int)(tsk->state), task_cpu(tsk), wchan, pc, (unsigned long)tsk, is_main ? '*' : ' ', tsk->comm, symname); if (tsk->state == TASK_RUNNING || tsk->state == TASK_UNINTERRUPTIBLE || tsk->mm == NULL) { ecd_dump_stacktrace_task(NULL, tsk); barrier(); } } static inline struct task_struct *get_next_thread(struct task_struct *tsk) { return container_of(tsk->thread_group.next, struct task_struct, thread_group); } static void ecd_dump_task(void) { struct task_struct *frst_tsk; struct task_struct *curr_tsk; struct task_struct *frst_thr; struct task_struct *curr_thr; unsigned long flags; ecd_printf("\n"); ecd_printf(" current proc : %d %s\n", current->pid, current->comm); ecd_printf(" ----------------------------------------------------------------------------------------------------------------------------\n"); ecd_printf(" pid uTime sTime exec(ns) stat cpu wchan user_pc task_struct comm sym_wchan\n"); ecd_printf(" ----------------------------------------------------------------------------------------------------------------------------\n"); /* processes */ frst_tsk = &init_task; curr_tsk = frst_tsk; while (curr_tsk != NULL) { spin_lock_irqsave(&interface->work_lock, flags); ecd_dump_one_task(curr_tsk, true); if (ecd_uart_check_break()) { spin_unlock_irqrestore(&interface->work_lock, flags); break; } else { spin_unlock_irqrestore(&interface->work_lock, flags); } /* threads */ if (curr_tsk->thread_group.next != NULL) { frst_thr = get_next_thread(curr_tsk); curr_thr = frst_thr; if (frst_thr != curr_tsk) { while (curr_thr != NULL) { ecd_dump_one_task(curr_thr, false); curr_thr = get_next_thread(curr_thr); if (curr_thr == curr_tsk) break; } } } curr_tsk = container_of(curr_tsk->tasks.next, struct task_struct, tasks); if (curr_tsk == frst_tsk) break; } ecd_printf("----------------------------------------------------------------------------------------------------------------------------\n"); } static void ecd_dump_irqs(void) { int n; struct irq_desc *desc; ecd_printf("irqnr total since-last status name\n"); for_each_irq_desc(n, desc) { struct irqaction *act = desc->action; if (!act && !kstat_irqs(n)) continue; ecd_printf("%5d: %10u %11u %8x %s\n", n, kstat_irqs(n), kstat_irqs(n) - interface->last_irqs[n], desc->status_use_accessors, (act && act->name) ? act->name : "???"); interface->last_irqs[n] = kstat_irqs(n); } } static void ecd_dump_ps(void) { struct task_struct *g; struct task_struct *p; unsigned task_state; static const char stat_nam[] = "RSDTtZX"; ecd_printf("pid ppid prio task pc\n"); read_lock(&tasklist_lock); do_each_thread(g, p) { task_state = p->state ? __ffs(p->state) + 1 : 0; ecd_printf("%5d %5d %4d ", p->pid, p->parent->pid, p->prio); ecd_printf("%-13.13s %c", p->comm, task_state >= sizeof(stat_nam) ? '?' : stat_nam[task_state]); if (task_state == TASK_RUNNING) ecd_printf(" running\n"); else ecd_printf(" %08lx\n", thread_saved_pc(p)); } while_each_thread(g, p); read_unlock(&tasklist_lock); } static void ecd_dump_kernel_log(void) { char buf[512]; size_t len; struct kmsg_dumper dumper = { .active = true }; unsigned long flags; kmsg_dump_rewind_nolock(&dumper); while (kmsg_dump_get_line_nolock(&dumper, true, buf, sizeof(buf) - 1, &len)) { buf[len] = 0; spin_lock_irqsave(&interface->work_lock, flags); ecd_uart_puts(buf); if (ecd_uart_check_break()) { spin_unlock_irqrestore(&interface->work_lock, flags); break; } else { spin_unlock_irqrestore(&interface->work_lock, flags); } } } static void ecd_tty_ringbuf_consume(void) { #if defined(CONFIG_EXYNOS_CONSOLE_DEBUGGER_INTERFACE) int i, count = ecd_ringbuf_level(interface->tty_rbuf); for (i = 0; i < count; i++) { int c = ecd_ringbuf_peek(interface->tty_rbuf, 0); tty_insert_flip_char(&interface->tty_port, c, TTY_NORMAL); if (!ecd_ringbuf_consume(interface->tty_rbuf, 1)) ecd_printf("ttyECD failed to consume byte\n"); } tty_flip_buffer_push(&interface->tty_port); #endif } static void ecd_tty_ringbuf_push(char c) { ecd_ringbuf_push(interface->tty_rbuf, c); } static ktime_t ecd_ktime_sub(ktime_t lhs, ktime_t rhs) { return ktime_sub(lhs, rhs); } static int ecd_register_hotcpu_notifier(void *nbp, notifier_fn_t func) { struct notifier_block *nb = (struct notifier_block *)nbp; nb = devm_kzalloc(&interface->pdev->dev, sizeof(struct notifier_block), GFP_KERNEL); nb->notifier_call = func; return register_hotcpu_notifier((struct notifier_block *)nb); } static int ecd_register_lowpm_notifier(void *nbp, notifier_fn_t func) { struct notifier_block *nb = (struct notifier_block *)nbp; nb = devm_kzalloc(&interface->pdev->dev, sizeof(struct notifier_block), GFP_KERNEL); nb->notifier_call = func; return cpu_pm_register_notifier((struct notifier_block *)nb); } static int ecd_register_str_pm_notifier(void *nbp, notifier_fn_t func) { struct notifier_block *nb = (struct notifier_block *)nbp; nb = devm_kzalloc(&interface->pdev->dev, sizeof(struct notifier_block), GFP_KERNEL); nb->notifier_call = func; return register_pm_notifier((struct notifier_block *)nb); } static int ecd_do_breakpoint(unsigned long addr, unsigned int esr, struct pt_regs *regs) { if (interface && interface->fw_loaded) return interface->ops.do_breakpoint(addr, regs); else return 0; } static int ecd_do_watchpoint(unsigned long addr, unsigned int esr, struct pt_regs *regs) { if (interface && interface->fw_loaded) return interface->ops.do_watchpoint(addr, regs); else return 0; } void ecd_do_break_now(void) { interface->ops.do_break_now(); } static int __init add_exception_func(void) { if (!initial_no_firmware) { hook_debug_fault_code(DBG_ESR_EVT_HWBP, ecd_do_breakpoint, SIGTRAP, TRAP_HWBKPT, "hw-breakpoint handler"); hook_debug_fault_code(DBG_ESR_EVT_HWWP, ecd_do_watchpoint, SIGTRAP, TRAP_HWBKPT, "hw-watchpoint handler"); } return 0; } arch_initcall(add_exception_func); enum os_func { READL = 0, READQ, WRITEL, WRITEQ, IOREMAP = 10, IOUNMAP, KZALLOC, KFREE, SPIN_LOCK_INIT = 20, SPIN_LOCK, SPIN_TRYLOCK, SPIN_LOCK_IRQSAVE, SPIN_UNLOCK, SPIN_UNLOCK_IRQRESTORE, LOCAL_IRQ_SAVE, LOCAL_IRQ_RESTORE, PREEMPT_ENABLE, PREEMPT_DISABLE, KTIME_GET = 30, KTIME_TO_US, KTIME_SUB, CLOCKSOURCE_SUSPEND, CLOCKSOURCE_RESUME, TOUCH_ALL_SOFTLOCKUP_WATCHDOGS, STRNCMP = 40, STRNCPY, STRLEN, STRSEP, SCNPRINTF, VSCNPRINTF, KSTRTOUL, SIMPLE_STRTOUL, REGISTER_HOTCPU_NOTIFIER = 50, REGISTER_CPU_PM_NOTIFIER, PANIC, MACHINE_RESTART, KERNEL_RESTART, REGISTER_STR_PM_NOTIFIER, PRINTK = 60, KALLSYMS_LOOKUP, KALLSYMS_LOOKUP_NAME, SPRINT_SYMBOL, MEMCPY, MEMSET, REQUEST_IRQ = 70, IRQ_FORCE_AFFINITY, GET_IRQ_REGS, CPUIDLE_PAUSE = 80, CPUIDLE_RESUME, HOOK_DEBUG_FAULT_CODE = 90, SMP_CALL_FUNCTION, SMP_CALL_FUNCTION_SINGLE, ON_EACH_CPU, SCHEDULE_WORK, INIT_WORK, CUR_THD_INFO, LINUX_BANNER, CPU_LOGICAL_MAP = 100, CPU_ONLINE_FUNC, CPU_POSSIBLE_FUNC, NUM_ONLINE_CPUS, NUM_POSSIBLE_CPUS, SET_BIT, CLEAR_BIT, TEST_BIT, RAW_SMP_PROCESSOR_ID, ECD_DO_SYSRQ = 110, ECD_DUMP_PC, ECD_DUMP_REGS, ECD_DUMP_ALLREGS, ECD_DUMP_TASK, ECD_DUMP_KERNEL_LOG, ECD_DUMP_IRQS, ECD_DUMP_PS, ECD_DUMP_STACKTRACE, ECD_TTY_RINGBUF_PUSH = 120, ECD_TTY_RINGBUF_CONSUME, ECD_UART_INIT, ECD_UART_PUTS, ECD_UART_PUTC, ECD_UART_GETC, ECD_UART_FLUSH, ECD_UART_ENABLE, ECD_UART_DISABLE, ECD_UART_CHECK_BREAK, ECD_LOOKUP_DUMP_SFR = 130, ECD_LOOKUP_CHECK_SFR, ECD_UART_CLEAR_RXFIFO, ITMON_ENABLE = 140, EXYNOS_SS_DUMPER_ONE, WDT_SET_EMERGENCY_RESET, WAKE_LOCK_INIT, WAKE_LOCK, WAKE_UNLOCK, FUNC_END, }; typedef void(*set_param_fn_t)(void); typedef u32 (*base_fn_t)(void **func1, void *func2, void *func3); static void set_param(set_param_fn_t *fn) { fn[READL] = (set_param_fn_t)ecd_readl; fn[READQ] = (set_param_fn_t)ecd_readq; fn[WRITEL] = (set_param_fn_t)ecd_writel; fn[WRITEQ] = (set_param_fn_t)ecd_writeq; fn[IOREMAP] = (set_param_fn_t)ecd_ioremap; fn[IOUNMAP] = (set_param_fn_t)ecd_iounmap; fn[KZALLOC] = (set_param_fn_t)ecd_kzalloc; fn[KFREE] = (set_param_fn_t)ecd_kfree; fn[SPIN_LOCK_INIT] = (set_param_fn_t)ecd_spin_lock_init; fn[SPIN_LOCK] = (set_param_fn_t)spin_lock; fn[SPIN_UNLOCK] = (set_param_fn_t)spin_unlock; fn[SPIN_TRYLOCK] = (set_param_fn_t)ecd_spin_trylock; fn[SPIN_LOCK_IRQSAVE] = (set_param_fn_t)ecd_spin_lock_irqsave; fn[SPIN_UNLOCK_IRQRESTORE] = (set_param_fn_t)ecd_spin_unlock_irqrestore; fn[LOCAL_IRQ_SAVE] = (set_param_fn_t)ecd_local_irq_save; fn[LOCAL_IRQ_RESTORE] = (set_param_fn_t)ecd_local_irq_restore; fn[PREEMPT_ENABLE] = (set_param_fn_t)ecd_preempt_enable; fn[PREEMPT_DISABLE] = (set_param_fn_t)ecd_preempt_disable; fn[KTIME_GET] = (set_param_fn_t)ktime_get; fn[KTIME_TO_US] = (set_param_fn_t)ktime_to_us; fn[KTIME_SUB] = (set_param_fn_t)ecd_ktime_sub; fn[CLOCKSOURCE_SUSPEND] = (set_param_fn_t)clocksource_suspend; fn[CLOCKSOURCE_RESUME] = (set_param_fn_t)clocksource_resume; fn[TOUCH_ALL_SOFTLOCKUP_WATCHDOGS] = (set_param_fn_t)touch_softlockup_watchdog; fn[STRNCMP] = (set_param_fn_t)strncmp; fn[STRNCPY] = (set_param_fn_t)strncpy; fn[STRLEN] = (set_param_fn_t)strlen; fn[STRSEP] = (set_param_fn_t)strsep; fn[SCNPRINTF] = (set_param_fn_t)scnprintf; fn[VSCNPRINTF] = (set_param_fn_t)vscnprintf; fn[KSTRTOUL] = (set_param_fn_t)kstrtoul; fn[SIMPLE_STRTOUL] = (set_param_fn_t)simple_strtol; fn[REGISTER_HOTCPU_NOTIFIER] = (set_param_fn_t)ecd_register_hotcpu_notifier; fn[REGISTER_CPU_PM_NOTIFIER] = (set_param_fn_t)ecd_register_lowpm_notifier; fn[PANIC] = (set_param_fn_t)panic; fn[MACHINE_RESTART] = (set_param_fn_t)machine_restart; fn[KERNEL_RESTART] = (set_param_fn_t)kernel_restart; fn[REGISTER_STR_PM_NOTIFIER] = (set_param_fn_t)ecd_register_str_pm_notifier; fn[PRINTK] = (set_param_fn_t)printk; fn[KALLSYMS_LOOKUP] = (set_param_fn_t)kallsyms_lookup; fn[KALLSYMS_LOOKUP_NAME] = (set_param_fn_t)kallsyms_lookup_name; fn[SPRINT_SYMBOL] = (set_param_fn_t)sprint_symbol; fn[MEMCPY] = (set_param_fn_t)memcpy; fn[MEMSET] = (set_param_fn_t)memset; fn[REQUEST_IRQ] = (set_param_fn_t)ecd_request_irq; fn[IRQ_FORCE_AFFINITY] = (set_param_fn_t)ecd_irq_force_affinity; fn[GET_IRQ_REGS] = (set_param_fn_t)get_irq_regs; fn[CPUIDLE_PAUSE] = (set_param_fn_t)cpuidle_pause; fn[CPUIDLE_RESUME] = (set_param_fn_t)cpuidle_resume; fn[SMP_CALL_FUNCTION_SINGLE] = (set_param_fn_t)smp_call_function_single; fn[SMP_CALL_FUNCTION] = (set_param_fn_t)smp_call_function; fn[ON_EACH_CPU] = (set_param_fn_t)on_each_cpu; fn[SCHEDULE_WORK] = (set_param_fn_t)ecd_schedule_work; fn[INIT_WORK] = (set_param_fn_t)ecd_init_work; fn[LINUX_BANNER] = (set_param_fn_t)ecd_get_linux_banner; fn[CPU_LOGICAL_MAP] = (set_param_fn_t)__cpu_logical_map; fn[CPU_ONLINE_FUNC] = (set_param_fn_t)ecd_cpu_online_func; fn[CPU_POSSIBLE_FUNC] = (set_param_fn_t)ecd_cpu_possible_func; fn[NUM_ONLINE_CPUS] = (set_param_fn_t)ecd_num_online_cpus; fn[NUM_POSSIBLE_CPUS] = (set_param_fn_t)ecd_num_possible_cpus; fn[SET_BIT] = (set_param_fn_t)set_bit; fn[CLEAR_BIT] = (set_param_fn_t)clear_bit; fn[TEST_BIT] = (set_param_fn_t)test_bit; fn[RAW_SMP_PROCESSOR_ID] = (set_param_fn_t)ecd_raw_smp_processor_id; fn[ECD_DO_SYSRQ] = (set_param_fn_t)ecd_do_sysrq; fn[ECD_DUMP_PC] = (set_param_fn_t)ecd_dump_pc; fn[ECD_DUMP_REGS] = (set_param_fn_t)ecd_dump_regs; fn[ECD_DUMP_ALLREGS] = (set_param_fn_t)ecd_dump_allregs; fn[ECD_DUMP_TASK] = (set_param_fn_t)ecd_dump_task; fn[ECD_DUMP_KERNEL_LOG] = (set_param_fn_t)ecd_dump_kernel_log; fn[ECD_DUMP_IRQS] = (set_param_fn_t)ecd_dump_irqs; fn[ECD_DUMP_PS] = (set_param_fn_t)ecd_dump_ps; fn[ECD_DUMP_STACKTRACE] = (set_param_fn_t)ecd_dump_stacktrace; fn[ECD_TTY_RINGBUF_PUSH] = (set_param_fn_t)ecd_tty_ringbuf_push; fn[ECD_TTY_RINGBUF_CONSUME] = (set_param_fn_t)ecd_tty_ringbuf_consume; fn[ECD_UART_INIT] = (set_param_fn_t)ecd_uart_init; fn[ECD_UART_PUTS] = (set_param_fn_t)ecd_uart_puts; fn[ECD_UART_PUTC] = (set_param_fn_t)ecd_uart_putc; fn[ECD_UART_GETC] = (set_param_fn_t)ecd_uart_getc; fn[ECD_UART_FLUSH] = (set_param_fn_t)ecd_uart_flush; fn[ECD_UART_ENABLE] = (set_param_fn_t)ecd_uart_enable; fn[ECD_UART_DISABLE] = (set_param_fn_t)ecd_uart_disable; fn[ECD_UART_CHECK_BREAK] = (set_param_fn_t)ecd_uart_check_break; fn[ECD_LOOKUP_DUMP_SFR] = (set_param_fn_t)ecd_lookup_dump_sfr; fn[ECD_LOOKUP_CHECK_SFR] = (set_param_fn_t)ecd_lookup_check_sfr; fn[ECD_UART_CLEAR_RXFIFO] = (set_param_fn_t)ecd_uart_clear_rxfifo; fn[ITMON_ENABLE] = (set_param_fn_t)itmon_enable; fn[EXYNOS_SS_DUMPER_ONE] = (set_param_fn_t)exynos_ss_dumper_one; fn[WDT_SET_EMERGENCY_RESET] = (set_param_fn_t)s3c2410wdt_set_emergency_reset; fn[WAKE_LOCK_INIT] = (set_param_fn_t)ecd_wake_lock_init; fn[WAKE_LOCK] = (set_param_fn_t)wake_lock; fn[WAKE_UNLOCK] = (set_param_fn_t)wake_unlock; } int ecd_start_binary(unsigned long jump_addr) { /* address information */ set_param_fn_t fn[SZ_256] = {0, }; /* Set/Get parameter */ set_param(fn); /* Complete to load the firmware */ interface->fw_loaded = ((base_fn_t)jump_addr)((void **)fn, (void *)&interface->ops, (void *)&interface->param); return interface->fw_loaded; } int ecd_init_binary(unsigned long fw_addr, unsigned long fw_size) { int ret; ret = request_binary(interface, FW_PATH, "ecd_fw.bin", NULL); if (ret) { pr_err("failed to load ECD firmware - %d\n", ret); return ret; } fw_size = CONDBG_FW_TEXT_SIZE; fw_addr = CONDBG_FW_ADDR; /* Memory attributes => NX */ ret = ecd_set_memory_nx(fw_addr, PFN_UP(fw_size)); if (ret) { pr_err("failed to change memory attributes to NX - %d\n", ret); goto out; } /* Memory attributes => RW */ ret = ecd_set_memory_rw(fw_addr, PFN_UP(fw_size)); if (ret) { pr_err("failed to change memory attributes to RW - %d\n", ret); goto out; } memcpy((void *)fw_addr, interface->fw_data, interface->fw_size); release_binary(interface); /* Memory attributes => RO */ ret = ecd_set_memory_ro(fw_addr, PFN_UP(fw_size)); if (ret) { pr_err("failed to change memory attributes to RO - %d\n", ret); return ret; } /* Memory attributes => X */ ret = ecd_set_memory_x(fw_addr, PFN_UP(fw_size)); if (ret) { pr_err("failed to change memory attributes to X - %d\n", ret); goto out; } out: return ret; } static irqreturn_t ecd_uart_irq(int irq, void *dev) { int c; irqreturn_t ret = IRQ_HANDLED; if (!interface->fw_loaded) { while (((c = ecd_uart_getc()) != DEBUGGER_NO_CHAR) && interface->tty_rbuf) { if (interface->param.console_enable) { ecd_ringbuf_push(interface->tty_rbuf, c); ecd_uart_clear_rxfifo(); } else if (c == 26) { /* CTRL + Z */ ecd_uart_puts("console mode\n"); ecd_uart_flush(); interface->param.console_enable = true; interface->param.debug_mode = MODE_CONSOLE; ecd_ringbuf_push(interface->tty_rbuf, '\n'); } else { ecd_uart_clear_rxfifo(); udelay(1); } }; if (interface->param.console_enable) ecd_tty_ringbuf_consume(); } else { ret = interface->ops.uart_irq_handler(irq, dev); if (ret == IRQ_NONE) { pr_err("ECD: No handled serial irq, Disable ECD\n"); interface->unhandled_irq = true; disable_irq_nosync(irq); } } return ret; } static ssize_t profile_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { if (interface && interface->fw_loaded) interface->ops.sysfs_profile_store(buf, size); return size; } static ssize_t profile_show(struct device *dev, struct device_attribute *attr, char *buf) { if (interface && interface->fw_loaded) return interface->ops.sysfs_profile_show(buf); else return 0; } static ssize_t enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { if (interface && interface->fw_loaded) interface->ops.sysfs_enable_store(buf, size); return size; } static ssize_t enable_show(struct device *dev, struct device_attribute *attr, char *buf) { if (interface && interface->fw_loaded) return interface->ops.sysfs_enable_show(buf); else return 0; } static ssize_t break_now_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { if (interface && interface->fw_loaded) interface->ops.sysfs_break_now_store(buf, size); return size; } static ssize_t switch_debug_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { if (interface && interface->fw_loaded) interface->ops.sysfs_switch_dbg_store(buf, size); return size; } void read_ecd_rc(void) { struct ecd_param *param = &interface->param; struct ecd_rc *rc = ¶m->rc; int fd, index = 0, data_type = 0; char ch[1], data[SZ_64] = {0, }; mm_segment_t old_fs; old_fs = get_fs(); set_fs(KERNEL_DS); fd = sys_open(ECD_RC_PATH, O_RDONLY, 0); if (fd < 0) { pr_err(PR_ECD "%s not found!!\n", ECD_RC_PATH); set_fs(old_fs); return; } while(sys_read(fd, ch, 1) == 1 && index < SZ_64) { data[index++] = ch[0]; if (ch[0] != '\n') continue; index = 0; if (strnstr(data, "on property", strlen(data))) { data_type = 0; memset(data, 0, SZ_64); continue; } else if (strnstr(data, "on setup", strlen(data))) { data_type = 1; memset(data, 0, SZ_64); continue; } else if (strnstr(data, "on action", strlen(data))) { data_type = 2; memset(data, 0, SZ_64); continue; } if (data[0] == '\n') continue; strreplace(data, 13, 0); strreplace(data, 10, 0); switch (data_type) { char *p; unsigned long val; case 0: p = strnstr(data, "=", strlen(data)); if (!p || kstrtoul(p + 1, 0, &val)) { memset(data, 0, SZ_64); continue; } if (strnstr(data, "console=", strlen(data))) param->console_enable = val; else if (strnstr(data, "panic=", strlen(data))) param->debug_panic = val; else if (strnstr(data, "sleep=", strlen(data))) param->no_sleep = val; else if (strnstr(data, "count=", strlen(data))) param->count_break = val; break; case 1: strncpy(rc->setup[rc->setup_idx++], data, SZ_64 - 1); break; case 2: strncpy(rc->action[rc->action_idx++], data, SZ_64 - 1); break; } memset(data, 0, SZ_64); } set_fs(old_fs); sys_close(fd); } static ssize_t load_firmware_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { unsigned long fw_addr = CONDBG_FW_ADDR; unsigned long fw_size = CONDBG_FW_SIZE; if (interface && !interface->fw_loaded) { if (!ecd_init_binary(fw_addr, fw_size)) { read_ecd_rc(); ecd_start_binary(fw_addr); } } return size; } static DEVICE_ATTR_RW(profile); static DEVICE_ATTR_RW(enable); static DEVICE_ATTR_WO(break_now); static DEVICE_ATTR_WO(switch_debug); static DEVICE_ATTR_WO(load_firmware); static struct attribute *ecd_sysfs_attrs[] = { &dev_attr_profile.attr, &dev_attr_enable.attr, &dev_attr_break_now.attr, &dev_attr_switch_debug.attr, &dev_attr_load_firmware.attr, NULL }; ATTRIBUTE_GROUPS(ecd_sysfs); int ecd_sysfs_init(struct device *dev) { int ret = 0; char path[SZ_256]; char *kobj_path; ret = sysfs_create_groups(&dev->kobj, ecd_sysfs_groups); if (ret) { dev_err(dev, "fail to register debugger sysfs.\n"); return ret; } kobj_path = kobject_get_path(&dev->kobj, GFP_KERNEL); snprintf(path, SZ_256, "/sys%s/", kobj_path); kfree(kobj_path); if (!proc_symlink("ecd", NULL, path)) dev_warn(dev, "Can't create symbolic link\n"); return ret; } static void ecd_delayed_work_check_firmware(struct work_struct *work) { if (interface && interface->fw_loaded) return; ecd_printf("Failed to loading ECD firmware\n"); /* TODO: clean-up ecd's kernel driver if it needs */ } static int ecd_probe(struct platform_device *pdev) { struct ecd_interface *inf; struct ecd_pdata *pdata = dev_get_platdata(&pdev->dev); int uart_irq, ret; int page_size, i; struct page *page; struct page **pages; if (!initial_ecd_enable) { dev_err(&pdev->dev, "initial_ecd_enable is not set"); return -EINVAL; } inf = devm_kzalloc(&pdev->dev, sizeof(*inf), GFP_KERNEL); if (!inf) { dev_err(&pdev->dev, "failed to allocate memory for driver"); return -ENOMEM; } page_size = ecd_early_vm.size / PAGE_SIZE; pages = kzalloc(sizeof(struct page*) * page_size, GFP_KERNEL); page = phys_to_page(ecd_early_vm.phys_addr); for (i = 0; i < page_size; i++) pages[i] = page++; ret = map_vm_area(&ecd_early_vm, PAGE_KERNEL, pages); if (ret) { dev_err(&pdev->dev, "failed to mapping between virt and phys for firmware"); return -ENOMEM; } kfree(pages); if (pdev->id >= MAX_DEBUGGER_PORTS) return -EINVAL; if (!pdata->uart_getc || !pdata->uart_putc) { dev_err(&pdev->dev, "did not have getc & putc function"); return -EINVAL; } if ((pdata->uart_enable && !pdata->uart_disable) || (!pdata->uart_enable && pdata->uart_disable)) { dev_err(&pdev->dev, "did not have uart_enable or uart_disable"); return -EINVAL; } uart_irq = platform_get_irq_byname(pdev, "uart_irq"); if (uart_irq < 0) { dev_err(&pdev->dev, "did not find uart_irq"); return -EINVAL; } inf->fw_loaded = false; inf->param.console_enable = initial_console_enable; if (inf->param.console_enable) { inf->param.debug_mode = MODE_CONSOLE; } else { inf->param.debug_mode = MODE_NORMAL; } inf->pdev = pdev; inf->pdata = pdata; inf->output_buf = devm_kzalloc(&pdev->dev, SZ_1K, GFP_KERNEL); inf->uart_irq = uart_irq; inf->unhandled_irq = false; spin_lock_init(&inf->iomap_lock); spin_lock_init(&inf->work_lock); interface = inf; platform_set_drvdata(pdev, inf); if (pdata->uart_init) { ret = pdata->uart_init(pdev); if (ret) goto err_uart_init; } spin_lock_init(&inf->console_lock); inf->console = ecd_console; inf->console.index = pdev->id; if (!console_set_on_cmdline) add_preferred_console(inf->console.name, inf->console.index, NULL); register_console(&inf->console); ecd_tty_init_one(inf); ret = devm_request_irq(&pdev->dev, uart_irq, ecd_uart_irq, IRQF_NO_SUSPEND, "ecd_uart", inf); if (ret) { pr_err("%s: could not install irq handler\n", __func__); goto err_register_irq; } ecd_sysfs_init(&pdev->dev); INIT_DELAYED_WORK(&inf->check_load_firmware, ecd_delayed_work_check_firmware); schedule_delayed_work(&inf->check_load_firmware, msecs_to_jiffies(20 * MSEC_PER_SEC)); ecd_printf(" > Exynos Console Debugger(ECD) is Loading, Wait\n" " > Push CTRL + Z, You can switch kernel console, "); return 0; err_register_irq: if (pdata->uart_free) pdata->uart_free(pdev); err_uart_init: platform_set_drvdata(pdev, NULL); kfree(inf); return ret; } static int __init ecd_setup(char *str) { char *move; char *console = NULL, *option = NULL; unsigned long paddr; if (!str) goto out; move = strchr((const char *)str, ','); if (!move) { console = str; } else { console = strsep(&str, ","); option = strsep(&str, " "); } if (console && !strncmp(console, "disable", strlen("disable"))) { initial_no_firmware = true; goto out; } if (console && !strncmp(console, "console", strlen("console"))) initial_console_enable = true; if (option && strncmp(option, "no_firmare", strlen("no_firmare"))) initial_no_firmware = true; if (!initial_no_firmware) { ecd_early_vm.phys_addr = memblock_alloc(CONDBG_FW_SIZE, SZ_4K); ecd_early_vm.addr = (void *)CONDBG_FW_ADDR; ecd_early_vm.size = CONDBG_FW_SIZE + PAGE_SIZE; /* Reserved fixed virtual memory within VMALLOC region */ vm_area_add_early(&ecd_early_vm); pr_info("ECD reserved memory:%zx, %zx, for firmware\n", paddr, CONDBG_FW_ADDR); initial_ecd_enable = true; INIT_LIST_HEAD(&ecd_ioremap_list); } out: return 0; } __setup("ecd_setup=", ecd_setup); #ifdef CONFIG_PM_SLEEP static int ecd_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); if (interface && interface->pdata->uart_dev_suspend) return interface->pdata->uart_dev_suspend(pdev); else return 0; } static int ecd_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); if (interface && interface->pdata->uart_dev_resume) return interface->pdata->uart_dev_resume(pdev); else return 0; } static const struct dev_pm_ops ecd_pm_ops = { .suspend = ecd_suspend, .resume = ecd_resume, }; #endif static struct platform_driver ecd_driver = { .probe = ecd_probe, .driver = { .name = "console_debugger", #ifdef CONFIG_PM_SLEEP .pm = &ecd_pm_ops, #endif }, }; static int __init ecd_init(void) { #if defined(CONFIG_EXYNOS_CONSOLE_DEBUGGER_INTERFACE) ecd_tty_init(); #endif return platform_driver_register(&ecd_driver); } postcore_initcall(ecd_init);