]>
Commit | Line | Data |
---|---|---|
6e43a9dd GKH |
1 | From d7500135802ca55b3f4e01a16544e8b34082f8c3 Mon Sep 17 00:00:00 2001 |
2 | From: Ben Hutchings <ben@decadent.org.uk> | |
3 | Date: Mon, 19 May 2014 00:56:22 +0100 | |
4 | Subject: Staging: speakup: Move pasting into a work item | |
5 | ||
6 | From: Ben Hutchings <ben@decadent.org.uk> | |
7 | ||
8 | commit d7500135802ca55b3f4e01a16544e8b34082f8c3 upstream. | |
9 | ||
10 | Input is handled in softirq context, but when pasting we may | |
11 | need to sleep. speakup_paste_selection() currently tries to | |
12 | bodge this by busy-waiting if in_atomic(), but that doesn't | |
13 | help because the ldisc may also sleep. | |
14 | ||
15 | For bonus breakage, speakup_paste_selection() changes the | |
16 | state of current, even though it's not running in process | |
17 | context. | |
18 | ||
19 | Move it into a work item and make sure to cancel it on exit. | |
20 | ||
21 | References: https://bugs.debian.org/735202 | |
22 | References: https://bugs.debian.org/744015 | |
23 | Reported-by: Paul Gevers <elbrus@debian.org> | |
24 | Reported-and-tested-by: Jarek Czekalski <jarekczek@poczta.onet.pl> | |
25 | Signed-off-by: Ben Hutchings <ben@decadent.org.uk> | |
26 | Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> | |
27 | ||
28 | --- | |
29 | drivers/staging/speakup/main.c | 1 | |
30 | drivers/staging/speakup/selection.c | 40 +++++++++++++++++++++++++++++------- | |
31 | drivers/staging/speakup/speakup.h | 1 | |
32 | 3 files changed, 35 insertions(+), 7 deletions(-) | |
33 | ||
34 | --- a/drivers/staging/speakup/main.c | |
35 | +++ b/drivers/staging/speakup/main.c | |
36 | @@ -2218,6 +2218,7 @@ static void __exit speakup_exit(void) | |
37 | unregister_keyboard_notifier(&keyboard_notifier_block); | |
38 | unregister_vt_notifier(&vt_notifier_block); | |
39 | speakup_unregister_devsynth(); | |
40 | + speakup_cancel_paste(); | |
41 | del_timer(&cursor_timer); | |
42 | kthread_stop(speakup_task); | |
43 | speakup_task = NULL; | |
44 | --- a/drivers/staging/speakup/selection.c | |
45 | +++ b/drivers/staging/speakup/selection.c | |
46 | @@ -4,6 +4,8 @@ | |
47 | #include <linux/sched.h> | |
48 | #include <linux/device.h> /* for dev_warn */ | |
49 | #include <linux/selection.h> | |
50 | +#include <linux/workqueue.h> | |
51 | +#include <asm/cmpxchg.h> | |
52 | ||
53 | #include "speakup.h" | |
54 | ||
55 | @@ -121,20 +123,24 @@ int speakup_set_selection(struct tty_str | |
56 | return 0; | |
57 | } | |
58 | ||
59 | -/* TODO: move to some helper thread, probably. That'd fix having to check for | |
60 | - * in_atomic(). */ | |
61 | -int speakup_paste_selection(struct tty_struct *tty) | |
62 | -{ | |
63 | +struct speakup_paste_work { | |
64 | + struct work_struct work; | |
65 | + struct tty_struct *tty; | |
66 | +}; | |
67 | + | |
68 | +static void __speakup_paste_selection(struct work_struct *work) | |
69 | +{ | |
70 | + struct speakup_paste_work *spw = | |
71 | + container_of(work, struct speakup_paste_work, work); | |
72 | + struct tty_struct *tty = xchg(&spw->tty, NULL); | |
73 | struct vc_data *vc = (struct vc_data *) tty->driver_data; | |
74 | int pasted = 0, count; | |
75 | DECLARE_WAITQUEUE(wait, current); | |
76 | + | |
77 | add_wait_queue(&vc->paste_wait, &wait); | |
78 | while (sel_buffer && sel_buffer_lth > pasted) { | |
79 | set_current_state(TASK_INTERRUPTIBLE); | |
80 | if (test_bit(TTY_THROTTLED, &tty->flags)) { | |
81 | - if (in_atomic()) | |
82 | - /* if we are in an interrupt handler, abort */ | |
83 | - break; | |
84 | schedule(); | |
85 | continue; | |
86 | } | |
87 | @@ -146,6 +152,26 @@ int speakup_paste_selection(struct tty_s | |
88 | } | |
89 | remove_wait_queue(&vc->paste_wait, &wait); | |
90 | current->state = TASK_RUNNING; | |
91 | + tty_kref_put(tty); | |
92 | +} | |
93 | + | |
94 | +static struct speakup_paste_work speakup_paste_work = { | |
95 | + .work = __WORK_INITIALIZER(speakup_paste_work.work, | |
96 | + __speakup_paste_selection) | |
97 | +}; | |
98 | + | |
99 | +int speakup_paste_selection(struct tty_struct *tty) | |
100 | +{ | |
101 | + if (cmpxchg(&speakup_paste_work.tty, NULL, tty) != NULL) | |
102 | + return -EBUSY; | |
103 | + | |
104 | + tty_kref_get(tty); | |
105 | + schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work); | |
106 | return 0; | |
107 | } | |
108 | ||
109 | +void speakup_cancel_paste(void) | |
110 | +{ | |
111 | + cancel_work_sync(&speakup_paste_work.work); | |
112 | + tty_kref_put(speakup_paste_work.tty); | |
113 | +} | |
114 | --- a/drivers/staging/speakup/speakup.h | |
115 | +++ b/drivers/staging/speakup/speakup.h | |
116 | @@ -77,6 +77,7 @@ extern void synth_buffer_clear(void); | |
117 | extern void speakup_clear_selection(void); | |
118 | extern int speakup_set_selection(struct tty_struct *tty); | |
119 | extern int speakup_paste_selection(struct tty_struct *tty); | |
120 | +extern void speakup_cancel_paste(void); | |
121 | extern void speakup_register_devsynth(void); | |
122 | extern void speakup_unregister_devsynth(void); | |
123 | extern void synth_write(const char *buf, size_t count); |