| /* |
| * 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 <hosung0.kim@samsung.com> |
| * Changki Kim <changki.kim@samsung.com> |
| * |
| * 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 <linux/io.h> |
| #include <linux/console.h> |
| #include <linux/interrupt.h> |
| #include <linux/clk.h> |
| #include <linux/platform_device.h> |
| #include <linux/kernel_stat.h> |
| #include <linux/kmsg_dump.h> |
| #include <linux/irq.h> |
| #include <linux/irqdomain.h> |
| #include <linux/delay.h> |
| #include <linux/reboot.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/smp.h> |
| #include <linux/cpu.h> |
| #include <linux/cpu_pm.h> |
| #include <linux/timer.h> |
| #include <linux/tty.h> |
| #include <linux/tty_flip.h> |
| #include <linux/kthread.h> |
| #include <linux/completion.h> |
| #include <linux/err.h> |
| #include <linux/wakelock.h> |
| #include <linux/clocksource.h> |
| #include <linux/bitops.h> |
| #include <linux/memblock.h> |
| #include <linux/vmalloc.h> |
| #include <linux/fs.h> |
| #include <linux/file.h> |
| #include <linux/firmware.h> |
| #include <linux/exynos-ss.h> |
| #include <soc/samsung/exynos-condbg.h> |
| #include <linux/kallsyms.h> |
| #include <linux/ptrace.h> |
| #include <linux/proc_fs.h> |
| #include <linux/syscalls.h> |
| #include <linux/notifier.h> |
| #include <linux/cpuidle.h> |
| #include <linux/suspend.h> |
| #include <soc/samsung/exynos-itmon.h> |
| |
| #include <asm/debug-monitors.h> |
| #include <asm/system_misc.h> |
| #include <asm/stacktrace.h> |
| #include <asm/cacheflush.h> |
| #include <asm/tlbflush.h> |
| #include <asm/map.h> |
| #include <asm/smp_plat.h> |
| |
| #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); |