diff options
author | Daniel Thompson <daniel.thompson@linaro.org> | 2014-05-29 04:48:45 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-05-29 14:28:05 -0400 |
commit | 8a0ff60f7eeab3df34d475c952b9d75799de8975 (patch) | |
tree | 7f9f3fd66a75044d5ef44924d95effc08cd2cf6a /drivers/tty/serial/kgdb_nmi.c | |
parent | 06d18289256ec58b78ae8d5b83ff70c3c34309f5 (diff) |
serial: kgdb_nmi: Switch from tasklets to real timers
kgdb_nmi uses tasklets on the assumption they will not be scheduled
until the next timer tick. This assumption is invalid and can lead to
live lock, continually servicing the kgdb_nmi tasklet. This is fixed
by using the timer API instead.
Signed-off-by: Daniel Thompson <daniel.thompson@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty/serial/kgdb_nmi.c')
-rw-r--r-- | drivers/tty/serial/kgdb_nmi.c | 30 |
1 files changed, 9 insertions, 21 deletions
diff --git a/drivers/tty/serial/kgdb_nmi.c b/drivers/tty/serial/kgdb_nmi.c index d51b2a1ba909..20d21d09ee86 100644 --- a/drivers/tty/serial/kgdb_nmi.c +++ b/drivers/tty/serial/kgdb_nmi.c | |||
@@ -80,24 +80,10 @@ static struct console kgdb_nmi_console = { | |||
80 | 80 | ||
81 | struct kgdb_nmi_tty_priv { | 81 | struct kgdb_nmi_tty_priv { |
82 | struct tty_port port; | 82 | struct tty_port port; |
83 | struct tasklet_struct tlet; | 83 | struct timer_list timer; |
84 | STRUCT_KFIFO(char, KGDB_NMI_FIFO_SIZE) fifo; | 84 | STRUCT_KFIFO(char, KGDB_NMI_FIFO_SIZE) fifo; |
85 | }; | 85 | }; |
86 | 86 | ||
87 | /* | ||
88 | * Our debugging console is polled in a tasklet, so we'll check for input | ||
89 | * every tick. In HZ-less mode, we should program the next tick. We have | ||
90 | * to use the lowlevel stuff as no locks should be grabbed. | ||
91 | */ | ||
92 | #ifdef CONFIG_HIGH_RES_TIMERS | ||
93 | static void kgdb_tty_poke(void) | ||
94 | { | ||
95 | tick_program_event(ktime_get(), 0); | ||
96 | } | ||
97 | #else | ||
98 | static inline void kgdb_tty_poke(void) {} | ||
99 | #endif | ||
100 | |||
101 | static struct tty_port *kgdb_nmi_port; | 87 | static struct tty_port *kgdb_nmi_port; |
102 | 88 | ||
103 | static void kgdb_tty_recv(int ch) | 89 | static void kgdb_tty_recv(int ch) |
@@ -108,14 +94,13 @@ static void kgdb_tty_recv(int ch) | |||
108 | if (!kgdb_nmi_port || ch < 0) | 94 | if (!kgdb_nmi_port || ch < 0) |
109 | return; | 95 | return; |
110 | /* | 96 | /* |
111 | * Can't use port->tty->driver_data as tty might be not there. Tasklet | 97 | * Can't use port->tty->driver_data as tty might be not there. Timer |
112 | * will check for tty and will get the ref, but here we don't have to | 98 | * will check for tty and will get the ref, but here we don't have to |
113 | * do that, and actually, we can't: we're in NMI context, no locks are | 99 | * do that, and actually, we can't: we're in NMI context, no locks are |
114 | * possible. | 100 | * possible. |
115 | */ | 101 | */ |
116 | priv = container_of(kgdb_nmi_port, struct kgdb_nmi_tty_priv, port); | 102 | priv = container_of(kgdb_nmi_port, struct kgdb_nmi_tty_priv, port); |
117 | kfifo_in(&priv->fifo, &c, 1); | 103 | kfifo_in(&priv->fifo, &c, 1); |
118 | kgdb_tty_poke(); | ||
119 | } | 104 | } |
120 | 105 | ||
121 | static int kgdb_nmi_poll_one_knock(void) | 106 | static int kgdb_nmi_poll_one_knock(void) |
@@ -199,7 +184,8 @@ static void kgdb_nmi_tty_receiver(unsigned long data) | |||
199 | struct kgdb_nmi_tty_priv *priv = (void *)data; | 184 | struct kgdb_nmi_tty_priv *priv = (void *)data; |
200 | char ch; | 185 | char ch; |
201 | 186 | ||
202 | tasklet_schedule(&priv->tlet); | 187 | priv->timer.expires = jiffies + (HZ/100); |
188 | add_timer(&priv->timer); | ||
203 | 189 | ||
204 | if (likely(!kgdb_nmi_tty_enabled || !kfifo_len(&priv->fifo))) | 190 | if (likely(!kgdb_nmi_tty_enabled || !kfifo_len(&priv->fifo))) |
205 | return; | 191 | return; |
@@ -215,7 +201,9 @@ static int kgdb_nmi_tty_activate(struct tty_port *port, struct tty_struct *tty) | |||
215 | container_of(port, struct kgdb_nmi_tty_priv, port); | 201 | container_of(port, struct kgdb_nmi_tty_priv, port); |
216 | 202 | ||
217 | kgdb_nmi_port = port; | 203 | kgdb_nmi_port = port; |
218 | tasklet_schedule(&priv->tlet); | 204 | priv->timer.expires = jiffies + (HZ/100); |
205 | add_timer(&priv->timer); | ||
206 | |||
219 | return 0; | 207 | return 0; |
220 | } | 208 | } |
221 | 209 | ||
@@ -224,7 +212,7 @@ static void kgdb_nmi_tty_shutdown(struct tty_port *port) | |||
224 | struct kgdb_nmi_tty_priv *priv = | 212 | struct kgdb_nmi_tty_priv *priv = |
225 | container_of(port, struct kgdb_nmi_tty_priv, port); | 213 | container_of(port, struct kgdb_nmi_tty_priv, port); |
226 | 214 | ||
227 | tasklet_kill(&priv->tlet); | 215 | del_timer(&priv->timer); |
228 | kgdb_nmi_port = NULL; | 216 | kgdb_nmi_port = NULL; |
229 | } | 217 | } |
230 | 218 | ||
@@ -243,7 +231,7 @@ static int kgdb_nmi_tty_install(struct tty_driver *drv, struct tty_struct *tty) | |||
243 | return -ENOMEM; | 231 | return -ENOMEM; |
244 | 232 | ||
245 | INIT_KFIFO(priv->fifo); | 233 | INIT_KFIFO(priv->fifo); |
246 | tasklet_init(&priv->tlet, kgdb_nmi_tty_receiver, (unsigned long)priv); | 234 | setup_timer(&priv->timer, kgdb_nmi_tty_receiver, (unsigned long)priv); |
247 | tty_port_init(&priv->port); | 235 | tty_port_init(&priv->port); |
248 | priv->port.ops = &kgdb_nmi_tty_port_ops; | 236 | priv->port.ops = &kgdb_nmi_tty_port_ops; |
249 | tty->driver_data = priv; | 237 | tty->driver_data = priv; |