From 8e18a9db5f895f654b06000d7bd10528e6b795f3 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Wed, 2 Jul 2008 22:44:11 +0100 Subject: [PATCH] introduce-resume-exception-capture.patch This patch introduces a new resume debugging concept: if we get an OOPS inbetween starting suspend and finishing resume, it uses a new "emergency spew" device similar to BUT NOT REQUIRING CONFIG_DEBUG_LL to dump the syslog buffer and then the OOPS on the debug device defined by the existing CONFIG_DEBUG_S3C_UART index. But neither CONFIG_DEBUG_LL nor the S3C low level configs are needed to use this feature. Another difference between this feature and CONFIG_DEBUG_LL is that it does not affect resume timing, ordering or UART traffic UNLESS there is an OOPS during resume. The patch adds three global exports, one to say if we are inside suspend / resume, and two callbacks for printk() to use to init and dump the emergency data. The callbacks are set in s3c serial device init, but the whole structure is arch independent. Signed-off-by: Andy Green --- drivers/serial/s3c2410.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/kernel.h | 2 + include/linux/suspend.h | 6 ++++ kernel/power/main.c | 10 ++++++- kernel/printk.c | 41 +++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 1 deletions(-) diff --git a/drivers/serial/s3c2410.c b/drivers/serial/s3c2410.c index 73c6284..fef3c8f 100644 --- a/drivers/serial/s3c2410.c +++ b/drivers/serial/s3c2410.c @@ -81,6 +81,7 @@ #include #include +#include /* structures */ @@ -992,6 +993,69 @@ static struct s3c24xx_uart_port s3c24xx_serial_ports[NR_PORTS] = { #endif }; +static void s3c24xx_serial_force_debug_port_up(void) +{ + struct s3c24xx_uart_port *ourport = &s3c24xx_serial_ports[ + CONFIG_DEBUG_S3C_UART]; + struct s3c24xx_uart_clksrc *clksrc = NULL; + struct clk *clk = NULL; + unsigned long tmp; + + s3c24xx_serial_getclk(&ourport->port, &clksrc, &clk, 115200); + + tmp = __raw_readl(S3C2410_CLKCON); + + /* re-start uart clocks */ + tmp |= S3C2410_CLKCON_UART0; + tmp |= S3C2410_CLKCON_UART1; + tmp |= S3C2410_CLKCON_UART2; + + __raw_writel(tmp, S3C2410_CLKCON); + udelay(10); + + s3c24xx_serial_setsource(&ourport->port, clksrc); + + if (ourport->baudclk != NULL && !IS_ERR(ourport->baudclk)) { + clk_disable(ourport->baudclk); + ourport->baudclk = NULL; + } + + clk_enable(clk); + + ourport->clksrc = clksrc; + ourport->baudclk = clk; +} + +static void s3c2410_printascii(const char *sz) +{ + struct s3c24xx_uart_port *ourport = &s3c24xx_serial_ports[ + CONFIG_DEBUG_S3C_UART]; + struct uart_port *port = &ourport->port; + + /* 8 N 1 */ + wr_regl(port, S3C2410_ULCON, (rd_regl(port, S3C2410_ULCON)) | 3); + /* polling mode */ + wr_regl(port, S3C2410_UCON, (rd_regl(port, S3C2410_UCON) & ~0xc0f) | 5); + /* disable FIFO */ + wr_regl(port, S3C2410_UFCON, (rd_regl(port, S3C2410_UFCON) & ~0x01)); + /* fix baud rate */ + wr_regl(port, S3C2410_UBRDIV, 26); + + while (*sz) { + int timeout = 10000000; + + /* spin on it being busy */ + while ((!(rd_regl(port, S3C2410_UTRSTAT) & 2)) && timeout--) + ; + + /* transmit register */ + wr_regl(port, S3C2410_UTXH, *sz); + + sz++; + } +} + + /* s3c24xx_serial_resetport * * wrapper to call the specific reset for this port (reset the fifos @@ -1167,6 +1231,11 @@ static int s3c24xx_serial_resume(struct platform_device *dev) static int s3c24xx_serial_init(struct platform_driver *drv, struct s3c24xx_uart_info *info) { + /* set up the emergency debug UART functions */ + + printk_emergency_debug_spew_init = s3c24xx_serial_force_debug_port_up; + printk_emergency_debug_spew_send_string = s3c2410_printascii; + dbg("s3c24xx_serial_init(%p,%p)\n", drv, info); return platform_driver_register(drv); } diff --git a/include/linux/kernel.h b/include/linux/kernel.h index 94bc996..8e565b4 100644 --- a/include/linux/kernel.h +++ b/include/linux/kernel.h @@ -182,6 +182,8 @@ asmlinkage int printk(const char * fmt, ...) extern int log_buf_get_len(void); extern int log_buf_read(int idx); extern int log_buf_copy(char *dest, int idx, int len); +extern void (*printk_emergency_debug_spew_init)(void); +extern void (*printk_emergency_debug_spew_send_string)(const char *); #else static inline int vprintk(const char *s, va_list args) __attribute__ ((format (printf, 1, 0))); diff --git a/include/linux/suspend.h b/include/linux/suspend.h index 4360e08..7186eef 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h @@ -122,6 +122,12 @@ struct pbe { struct pbe *next; }; +/** + * global indication we are somewhere between start of suspend and end of + * resume, nonzero is true + */ +extern int global_inside_suspend; + /* mm/page_alloc.c */ extern void drain_local_pages(void); extern void mark_free_pages(struct zone *zone); diff --git a/kernel/power/main.c b/kernel/power/main.c index f71c950..d87bf0d 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -31,6 +31,10 @@ DEFINE_MUTEX(pm_mutex); unsigned int pm_flags; EXPORT_SYMBOL(pm_flags); +int global_inside_suspend; +EXPORT_SYMBOL(global_inside_suspend); + + #ifdef CONFIG_SUSPEND /* This is just an arbitrary number */ @@ -156,10 +160,12 @@ int suspend_devices_and_enter(suspend_state_t state) if (!suspend_ops) return -ENOSYS; + global_inside_suspend = 1; + if (suspend_ops->set_target) { error = suspend_ops->set_target(state); if (error) - return error; + goto bail; } suspend_console(); error = device_suspend(PMSG_SUSPEND); @@ -183,6 +189,8 @@ int suspend_devices_and_enter(suspend_state_t state) device_resume(); Resume_console: resume_console(); +bail: + global_inside_suspend = 0; return error; } diff --git a/kernel/printk.c b/kernel/printk.c index 89011bf..c8691bc 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -33,8 +33,11 @@ #include #include #include +#include #include +#include +#include #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) @@ -61,6 +64,12 @@ int console_printk[4] = { int oops_in_progress; EXPORT_SYMBOL(oops_in_progress); +void (*printk_emergency_debug_spew_init)(void) = NULL; +EXPORT_SYMBOL(printk_emergency_debug_spew_init); + +void (*printk_emergency_debug_spew_send_string)(const char *) = NULL; +EXPORT_SYMBOL(printk_emergency_debug_spew_send_string); + /* * console_sem protects the console_drivers list, and also * provides serialisation for access to the entire console @@ -653,6 +662,38 @@ asmlinkage int vprintk(const char *fmt, va_list args) /* Emit the output into the temporary buffer */ printed_len = vscnprintf(printk_buf, sizeof(printk_buf), fmt, args); + /* if you're debugging resume, the normal methods can change resume + * ordering behaviours because their debugging output is synchronous + * (ie, CONFIG_DEBUG_LL). If your problem is an OOPS, this code + * will not affect the speed and duration and ordering of resume + * actions, but will give you a chance to read the full undumped + * syslog AND the OOPS data when it happens + * + * if you support it, your debug device init can override the exported + * emergency_debug_spew_init and emergency_debug_spew_send_string to + * usually force polling or bitbanging on your debug console device + */ + if (oops_in_progress && global_inside_suspend && + printk_emergency_debug_spew_init && + printk_emergency_debug_spew_send_string) { + unsigned long cur_index; + char ch[2]; + + if (global_inside_suspend == 1) { + (printk_emergency_debug_spew_init)(); + + ch[1] = '\0'; + cur_index = con_start; + while (cur_index != log_end) { + ch[0] = LOG_BUF(cur_index); + (printk_emergency_debug_spew_send_string)(ch); + cur_index++; + } + global_inside_suspend++; /* only once */ + } + (printk_emergency_debug_spew_send_string)(printk_buf); + } + /* * Copy the output into log_buf. If the caller didn't provide * appropriate log level tags, we insert them here -- 1.5.6.5