|
11 | 11 | #include <linux/module.h>
|
12 | 12 | #include <linux/string.h>
|
13 | 13 | #include <linux/wait.h>
|
| 14 | +#include <asm/irq.h> |
| 15 | +#include <asm/delay.h> |
14 | 16 |
|
15 | 17 | #define BT_BUF_SIZE (PAGE_SIZE * 4)
|
16 | 18 |
|
@@ -100,18 +102,23 @@ static noinline int test_unwind(struct task_struct *task, struct pt_regs *regs,
|
100 | 102 | /* State of the task being unwound. */
|
101 | 103 | struct unwindme {
|
102 | 104 | int flags;
|
| 105 | + int ret; |
| 106 | + struct task_struct *task; |
103 | 107 | struct completion task_ready;
|
104 | 108 | wait_queue_head_t task_wq;
|
105 | 109 | unsigned long sp;
|
106 | 110 | };
|
107 | 111 |
|
| 112 | +static struct unwindme *unwindme; |
| 113 | + |
108 | 114 | /* Values of unwindme.flags. */
|
109 | 115 | #define UWM_DEFAULT 0x0
|
110 | 116 | #define UWM_THREAD 0x1 /* Unwind a separate task. */
|
111 | 117 | #define UWM_REGS 0x2 /* Pass regs to test_unwind(). */
|
112 | 118 | #define UWM_SP 0x4 /* Pass sp to test_unwind(). */
|
113 | 119 | #define UWM_CALLER 0x8 /* Unwind starting from caller. */
|
114 | 120 | #define UWM_SWITCH_STACK 0x10 /* Use CALL_ON_STACK. */
|
| 121 | +#define UWM_IRQ 0x20 /* Unwind from irq context. */ |
115 | 122 |
|
116 | 123 | static __always_inline unsigned long get_psw_addr(void)
|
117 | 124 | {
|
@@ -173,6 +180,34 @@ static noinline int unwindme_func1(void *u)
|
173 | 180 | return unwindme_func2((struct unwindme *)u);
|
174 | 181 | }
|
175 | 182 |
|
| 183 | +static void unwindme_irq_handler(struct ext_code ext_code, |
| 184 | + unsigned int param32, |
| 185 | + unsigned long param64) |
| 186 | +{ |
| 187 | + struct unwindme *u = READ_ONCE(unwindme); |
| 188 | + |
| 189 | + if (u && u->task == current) { |
| 190 | + unwindme = NULL; |
| 191 | + u->task = NULL; |
| 192 | + u->ret = unwindme_func1(u); |
| 193 | + } |
| 194 | +} |
| 195 | + |
| 196 | +static int test_unwind_irq(struct unwindme *u) |
| 197 | +{ |
| 198 | + preempt_disable(); |
| 199 | + if (register_external_irq(EXT_IRQ_CLK_COMP, unwindme_irq_handler)) { |
| 200 | + pr_info("Couldn't reqister external interrupt handler"); |
| 201 | + return -1; |
| 202 | + } |
| 203 | + u->task = current; |
| 204 | + unwindme = u; |
| 205 | + udelay(1); |
| 206 | + unregister_external_irq(EXT_IRQ_CLK_COMP, unwindme_irq_handler); |
| 207 | + preempt_enable(); |
| 208 | + return u->ret; |
| 209 | +} |
| 210 | + |
176 | 211 | /* Spawns a task and passes it to test_unwind(). */
|
177 | 212 | static int test_unwind_task(struct unwindme *u)
|
178 | 213 | {
|
@@ -211,6 +246,8 @@ static int test_unwind_flags(int flags)
|
211 | 246 | u.flags = flags;
|
212 | 247 | if (u.flags & UWM_THREAD)
|
213 | 248 | return test_unwind_task(&u);
|
| 249 | + else if (u.flags & UWM_IRQ) |
| 250 | + return test_unwind_irq(&u); |
214 | 251 | else
|
215 | 252 | return unwindme_func1(&u);
|
216 | 253 | }
|
@@ -241,6 +278,14 @@ do { \
|
241 | 278 | TEST(UWM_THREAD);
|
242 | 279 | TEST(UWM_THREAD | UWM_SP);
|
243 | 280 | TEST(UWM_THREAD | UWM_CALLER | UWM_SP);
|
| 281 | + TEST(UWM_IRQ); |
| 282 | + TEST(UWM_IRQ | UWM_SWITCH_STACK); |
| 283 | + TEST(UWM_IRQ | UWM_SP); |
| 284 | + TEST(UWM_IRQ | UWM_REGS); |
| 285 | + TEST(UWM_IRQ | UWM_SP | UWM_REGS); |
| 286 | + TEST(UWM_IRQ | UWM_CALLER | UWM_SP); |
| 287 | + TEST(UWM_IRQ | UWM_CALLER | UWM_SP | UWM_REGS); |
| 288 | + TEST(UWM_IRQ | UWM_CALLER | UWM_SP | UWM_REGS | UWM_SWITCH_STACK); |
244 | 289 | #undef TEST
|
245 | 290 |
|
246 | 291 | return ret;
|
|
0 commit comments