aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBen Hutchings <ben@decadent.org.uk>2014-05-18 19:56:22 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-05-23 13:25:10 -0400
commitd7500135802ca55b3f4e01a16544e8b34082f8c3 (patch)
tree67baa0387aee5792f9d62e485e1add9dec14675d
parentffed54dced86723f352323f15789d9ad6bee25e1 (diff)
Staging: speakup: Move pasting into a work item
Input is handled in softirq context, but when pasting we may need to sleep. speakup_paste_selection() currently tries to bodge this by busy-waiting if in_atomic(), but that doesn't help because the ldisc may also sleep. For bonus breakage, speakup_paste_selection() changes the state of current, even though it's not running in process context. Move it into a work item and make sure to cancel it on exit. References: https://bugs.debian.org/735202 References: https://bugs.debian.org/744015 Reported-by: Paul Gevers <elbrus@debian.org> Reported-and-tested-by: Jarek Czekalski <jarekczek@poczta.onet.pl> Signed-off-by: Ben Hutchings <ben@decadent.org.uk> Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/staging/speakup/main.c1
-rw-r--r--drivers/staging/speakup/selection.c38
-rw-r--r--drivers/staging/speakup/speakup.h1
3 files changed, 34 insertions, 6 deletions
diff --git a/drivers/staging/speakup/main.c b/drivers/staging/speakup/main.c
index 3b6e5358c723..7de79d59a4cd 100644
--- a/drivers/staging/speakup/main.c
+++ b/drivers/staging/speakup/main.c
@@ -2218,6 +2218,7 @@ static void __exit speakup_exit(void)
2218 unregister_keyboard_notifier(&keyboard_notifier_block); 2218 unregister_keyboard_notifier(&keyboard_notifier_block);
2219 unregister_vt_notifier(&vt_notifier_block); 2219 unregister_vt_notifier(&vt_notifier_block);
2220 speakup_unregister_devsynth(); 2220 speakup_unregister_devsynth();
2221 speakup_cancel_paste();
2221 del_timer(&cursor_timer); 2222 del_timer(&cursor_timer);
2222 kthread_stop(speakup_task); 2223 kthread_stop(speakup_task);
2223 speakup_task = NULL; 2224 speakup_task = NULL;
diff --git a/drivers/staging/speakup/selection.c b/drivers/staging/speakup/selection.c
index f0fb00392d6b..f67941e78e4a 100644
--- a/drivers/staging/speakup/selection.c
+++ b/drivers/staging/speakup/selection.c
@@ -4,6 +4,8 @@
4#include <linux/sched.h> 4#include <linux/sched.h>
5#include <linux/device.h> /* for dev_warn */ 5#include <linux/device.h> /* for dev_warn */
6#include <linux/selection.h> 6#include <linux/selection.h>
7#include <linux/workqueue.h>
8#include <asm/cmpxchg.h>
7 9
8#include "speakup.h" 10#include "speakup.h"
9 11
@@ -121,20 +123,24 @@ int speakup_set_selection(struct tty_struct *tty)
121 return 0; 123 return 0;
122} 124}
123 125
124/* TODO: move to some helper thread, probably. That'd fix having to check for 126struct speakup_paste_work {
125 * in_atomic(). */ 127 struct work_struct work;
126int speakup_paste_selection(struct tty_struct *tty) 128 struct tty_struct *tty;
129};
130
131static void __speakup_paste_selection(struct work_struct *work)
127{ 132{
133 struct speakup_paste_work *spw =
134 container_of(work, struct speakup_paste_work, work);
135 struct tty_struct *tty = xchg(&spw->tty, NULL);
128 struct vc_data *vc = (struct vc_data *) tty->driver_data; 136 struct vc_data *vc = (struct vc_data *) tty->driver_data;
129 int pasted = 0, count; 137 int pasted = 0, count;
130 DECLARE_WAITQUEUE(wait, current); 138 DECLARE_WAITQUEUE(wait, current);
139
131 add_wait_queue(&vc->paste_wait, &wait); 140 add_wait_queue(&vc->paste_wait, &wait);
132 while (sel_buffer && sel_buffer_lth > pasted) { 141 while (sel_buffer && sel_buffer_lth > pasted) {
133 set_current_state(TASK_INTERRUPTIBLE); 142 set_current_state(TASK_INTERRUPTIBLE);
134 if (test_bit(TTY_THROTTLED, &tty->flags)) { 143 if (test_bit(TTY_THROTTLED, &tty->flags)) {
135 if (in_atomic())
136 /* if we are in an interrupt handler, abort */
137 break;
138 schedule(); 144 schedule();
139 continue; 145 continue;
140 } 146 }
@@ -146,6 +152,26 @@ int speakup_paste_selection(struct tty_struct *tty)
146 } 152 }
147 remove_wait_queue(&vc->paste_wait, &wait); 153 remove_wait_queue(&vc->paste_wait, &wait);
148 current->state = TASK_RUNNING; 154 current->state = TASK_RUNNING;
155 tty_kref_put(tty);
156}
157
158static struct speakup_paste_work speakup_paste_work = {
159 .work = __WORK_INITIALIZER(speakup_paste_work.work,
160 __speakup_paste_selection)
161};
162
163int speakup_paste_selection(struct tty_struct *tty)
164{
165 if (cmpxchg(&speakup_paste_work.tty, NULL, tty) != NULL)
166 return -EBUSY;
167
168 tty_kref_get(tty);
169 schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work);
149 return 0; 170 return 0;
150} 171}
151 172
173void speakup_cancel_paste(void)
174{
175 cancel_work_sync(&speakup_paste_work.work);
176 tty_kref_put(speakup_paste_work.tty);
177}
diff --git a/drivers/staging/speakup/speakup.h b/drivers/staging/speakup/speakup.h
index a7bcceec436a..898dce5e1243 100644
--- a/drivers/staging/speakup/speakup.h
+++ b/drivers/staging/speakup/speakup.h
@@ -75,6 +75,7 @@ extern void synth_buffer_clear(void);
75extern void speakup_clear_selection(void); 75extern void speakup_clear_selection(void);
76extern int speakup_set_selection(struct tty_struct *tty); 76extern int speakup_set_selection(struct tty_struct *tty);
77extern int speakup_paste_selection(struct tty_struct *tty); 77extern int speakup_paste_selection(struct tty_struct *tty);
78extern void speakup_cancel_paste(void);
78extern void speakup_register_devsynth(void); 79extern void speakup_register_devsynth(void);
79extern void speakup_unregister_devsynth(void); 80extern void speakup_unregister_devsynth(void);
80extern void synth_write(const char *buf, size_t count); 81extern void synth_write(const char *buf, size_t count);