summaryrefslogtreecommitdiffstats
path: root/target/linux/s3c24xx/patches-2.6.26/1192-introduce-resume-exception-capture.patch.patch
blob: 6f9b54de3d7ebc0ada1707f659d400684137f5c0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
From 081a639e9274072fbd34ee778188332ed972734e Mon Sep 17 00:00:00 2001
From: Andy Green <andy@openmoko.com>
Date: Fri, 25 Jul 2008 23:06:16 +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 <andy@openmoko.com>
---
 drivers/serial/s3c2410.c |   77 +++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/kernel.h   |    2 +
 include/linux/suspend.h  |    6 +++
 kernel/power/main.c      |    7 ++++
 kernel/printk.c          |   42 +++++++++++++++++++++++++
 5 files changed, 133 insertions(+), 1 deletions(-)

diff --git a/drivers/serial/s3c2410.c b/drivers/serial/s3c2410.c
index 32cceae..f20f63b 100644
--- a/drivers/serial/s3c2410.c
+++ b/drivers/serial/s3c2410.c
@@ -81,6 +81,7 @@
 
 #include <asm/plat-s3c/regs-serial.h>
 #include <asm/arch/regs-gpio.h>
+#include <asm/arch/regs-clock.h>
 
 /* structures */
 
@@ -1170,7 +1171,13 @@ static int s3c24xx_serial_init(struct platform_driver *drv,
 			       struct s3c24xx_uart_info *info)
 {
 	dbg("s3c24xx_serial_init(%p,%p)\n", drv, info);
-	return platform_driver_register(drv);
+        /* 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);
 }
 
 
@@ -1647,6 +1654,74 @@ static struct platform_driver s3c2412_serial_drv = {
 	},
 };
 
+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
+ * and the settings)
+*/
 
 static inline int s3c2412_serial_init(void)
 {
diff --git a/include/linux/kernel.h b/include/linux/kernel.h
index 2e70006..fcad89e 100644
--- a/include/linux/kernel.h
+++ b/include/linux/kernel.h
@@ -198,6 +198,8 @@ extern int __ratelimit(int ratelimit_jiffies, int ratelimit_burst);
 extern int __printk_ratelimit(int ratelimit_jiffies, int ratelimit_burst);
 extern bool printk_timed_ratelimit(unsigned long *caller_jiffies,
 				   unsigned int interval_msec);
+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 a697742..8164615 100644
--- a/include/linux/suspend.h
+++ b/include/linux/suspend.h
@@ -140,6 +140,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 mark_free_pages(struct zone *zone);
 
diff --git a/kernel/power/main.c b/kernel/power/main.c
index 6a6d5eb..1169648 100644
--- a/kernel/power/main.c
+++ b/kernel/power/main.c
@@ -130,6 +130,9 @@ static inline int suspend_test(int level) { return 0; }
 
 #endif /* CONFIG_PM_SLEEP */
 
+int global_inside_suspend;
+EXPORT_SYMBOL(global_inside_suspend);
+
 #ifdef CONFIG_SUSPEND
 
 /* This is just an arbitrary number */
@@ -258,6 +261,8 @@ int suspend_devices_and_enter(suspend_state_t state)
 	if (!suspend_ops)
 		return -ENOSYS;
 
+	global_inside_suspend = 1;
+
 	if (suspend_ops->begin) {
 		error = suspend_ops->begin(state);
 		if (error)
@@ -297,6 +302,8 @@ int suspend_devices_and_enter(suspend_state_t state)
  Close:
 	if (suspend_ops->end)
 		suspend_ops->end();
+	global_inside_suspend = 0;
+
 	return error;
 }
 
diff --git a/kernel/printk.c b/kernel/printk.c
index e2129e8..00df30e 100644
--- a/kernel/printk.c
+++ b/kernel/printk.c
@@ -32,8 +32,12 @@
 #include <linux/security.h>
 #include <linux/bootmem.h>
 #include <linux/syscalls.h>
+#include <linux/jiffies.h>
+#include <linux/suspend.h>
 
 #include <asm/uaccess.h>
+#include <asm/plat-s3c24xx/neo1973.h>
+#include <asm/arch/gta02.h>
 
 /*
  * Architectures can override it:
@@ -67,6 +71,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
@@ -718,6 +728,38 @@ asmlinkage int vprintk(const char *fmt, va_list args)
 	printed_len += vscnprintf(printk_buf + printed_len,
 				  sizeof(printk_buf) - printed_len, 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.3