]>
Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
c6e3fd22 WH |
2 | #include <linux/slab.h> /* for kmalloc */ |
3 | #include <linux/consolemap.h> | |
4 | #include <linux/interrupt.h> | |
5 | #include <linux/sched.h> | |
593fb1ae | 6 | #include <linux/device.h> /* for dev_warn */ |
c6e3fd22 | 7 | #include <linux/selection.h> |
d7500135 | 8 | #include <linux/workqueue.h> |
28a821c3 BH |
9 | #include <linux/tty.h> |
10 | #include <linux/tty_flip.h> | |
84567995 | 11 | #include <linux/atomic.h> |
41f13084 | 12 | #include <linux/console.h> |
c6e3fd22 WH |
13 | |
14 | #include "speakup.h" | |
15 | ||
ca2beaf8 | 16 | unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ |
c6e3fd22 | 17 | struct vc_data *spk_sel_cons; |
c6e3fd22 | 18 | |
41f13084 OK |
19 | struct speakup_selection_work { |
20 | struct work_struct work; | |
21 | struct tiocl_selection sel; | |
22 | struct tty_struct *tty; | |
23 | }; | |
c6e3fd22 | 24 | |
41f13084 | 25 | static void __speakup_set_selection(struct work_struct *work) |
c6e3fd22 | 26 | { |
41f13084 OK |
27 | struct speakup_selection_work *ssw = |
28 | container_of(work, struct speakup_selection_work, work); | |
c6e3fd22 | 29 | |
41f13084 OK |
30 | struct tty_struct *tty; |
31 | struct tiocl_selection sel; | |
c6e3fd22 | 32 | |
41f13084 | 33 | sel = ssw->sel; |
c6e3fd22 | 34 | |
41f13084 OK |
35 | /* this ensures we copy sel before releasing the lock below */ |
36 | rmb(); | |
c6e3fd22 | 37 | |
41f13084 OK |
38 | /* release the lock by setting tty of the struct to NULL */ |
39 | tty = xchg(&ssw->tty, NULL); | |
c6e3fd22 WH |
40 | |
41 | if (spk_sel_cons != vc_cons[fg_console].d) { | |
c6e3fd22 | 42 | spk_sel_cons = vc_cons[fg_console].d; |
41f13084 OK |
43 | pr_warn("Selection: mark console not the same as cut\n"); |
44 | goto unref; | |
c6e3fd22 WH |
45 | } |
46 | ||
640969a6 ST |
47 | console_lock(); |
48 | clear_selection(); | |
49 | console_unlock(); | |
50 | ||
41f13084 | 51 | set_selection_kernel(&sel, tty); |
41f13084 OK |
52 | |
53 | unref: | |
54 | tty_kref_put(tty); | |
55 | } | |
56 | ||
57 | static struct speakup_selection_work speakup_sel_work = { | |
58 | .work = __WORK_INITIALIZER(speakup_sel_work.work, | |
59 | __speakup_set_selection) | |
60 | }; | |
61 | ||
62 | int speakup_set_selection(struct tty_struct *tty) | |
63 | { | |
64 | /* we get kref here first in order to avoid a subtle race when | |
65 | * cancelling selection work. getting kref first establishes the | |
66 | * invariant that if speakup_sel_work.tty is not NULL when | |
67 | * speakup_cancel_selection() is called, it must be the case that a put | |
68 | * kref is pending. | |
69 | */ | |
70 | tty_kref_get(tty); | |
71 | if (cmpxchg(&speakup_sel_work.tty, NULL, tty)) { | |
72 | tty_kref_put(tty); | |
73 | return -EBUSY; | |
c6e3fd22 | 74 | } |
41f13084 OK |
75 | /* now we have the 'lock' by setting tty member of |
76 | * speakup_selection_work. wmb() ensures that writes to | |
77 | * speakup_sel_work don't happen before cmpxchg() above. | |
78 | */ | |
79 | wmb(); | |
80 | ||
81 | speakup_sel_work.sel.xs = spk_xs + 1; | |
82 | speakup_sel_work.sel.ys = spk_ys + 1; | |
83 | speakup_sel_work.sel.xe = spk_xe + 1; | |
84 | speakup_sel_work.sel.ye = spk_ye + 1; | |
85 | speakup_sel_work.sel.sel_mode = TIOCL_SELCHAR; | |
86 | ||
87 | schedule_work_on(WORK_CPU_UNBOUND, &speakup_sel_work.work); | |
88 | ||
c6e3fd22 WH |
89 | return 0; |
90 | } | |
91 | ||
41f13084 OK |
92 | void speakup_cancel_selection(void) |
93 | { | |
d7500135 | 94 | struct tty_struct *tty; |
41f13084 OK |
95 | |
96 | cancel_work_sync(&speakup_sel_work.work); | |
97 | /* setting to null so that if work fails to run and we cancel it, | |
98 | * we can run it again without getting EBUSY forever from there on. | |
99 | * we need to use xchg here to avoid race with speakup_set_selection() | |
100 | */ | |
101 | tty = xchg(&speakup_sel_work.tty, NULL); | |
102 | if (tty) | |
103 | tty_kref_put(tty); | |
104 | } | |
d7500135 BH |
105 | |
106 | static void __speakup_paste_selection(struct work_struct *work) | |
c6e3fd22 | 107 | { |
41f13084 OK |
108 | struct speakup_selection_work *ssw = |
109 | container_of(work, struct speakup_selection_work, work); | |
110 | struct tty_struct *tty = xchg(&ssw->tty, NULL); | |
28a821c3 | 111 | |
41f13084 | 112 | paste_selection(tty); |
d7500135 BH |
113 | tty_kref_put(tty); |
114 | } | |
115 | ||
41f13084 | 116 | static struct speakup_selection_work speakup_paste_work = { |
d7500135 BH |
117 | .work = __WORK_INITIALIZER(speakup_paste_work.work, |
118 | __speakup_paste_selection) | |
119 | }; | |
120 | ||
121 | int speakup_paste_selection(struct tty_struct *tty) | |
122 | { | |
41f13084 OK |
123 | tty_kref_get(tty); |
124 | if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) { | |
125 | tty_kref_put(tty); | |
d7500135 | 126 | return -EBUSY; |
41f13084 | 127 | } |
d7500135 | 128 | |
d7500135 | 129 | schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work); |
c6e3fd22 WH |
130 | return 0; |
131 | } | |
132 | ||
d7500135 BH |
133 | void speakup_cancel_paste(void) |
134 | { | |
41f13084 OK |
135 | struct tty_struct *tty; |
136 | ||
d7500135 | 137 | cancel_work_sync(&speakup_paste_work.work); |
41f13084 OK |
138 | tty = xchg(&speakup_paste_work.tty, NULL); |
139 | if (tty) | |
140 | tty_kref_put(tty); | |
d7500135 | 141 | } |