2117 lines
51 KiB
C
2117 lines
51 KiB
C
/*
|
|
* 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);
|