1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <linux/slab.h> /* for kmalloc */ |
3 | #include <linux/consolemap.h> |
4 | #include <linux/interrupt.h> |
5 | #include <linux/sched.h> |
6 | #include <linux/device.h> /* for dev_warn */ |
7 | #include <linux/selection.h> |
8 | #include <linux/workqueue.h> |
9 | #include <linux/tty.h> |
10 | #include <linux/tty_flip.h> |
11 | #include <linux/atomic.h> |
12 | #include <linux/console.h> |
13 | |
14 | #include "speakup.h" |
15 | |
16 | unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ |
17 | struct vc_data *spk_sel_cons; |
18 | |
19 | struct speakup_selection_work { |
20 | struct work_struct work; |
21 | struct tiocl_selection sel; |
22 | struct tty_struct *tty; |
23 | }; |
24 | |
25 | static void __speakup_set_selection(struct work_struct *work) |
26 | { |
27 | struct speakup_selection_work *ssw = |
28 | container_of(work, struct speakup_selection_work, work); |
29 | |
30 | struct tty_struct *tty; |
31 | struct tiocl_selection sel; |
32 | |
33 | sel = ssw->sel; |
34 | |
35 | /* this ensures we copy sel before releasing the lock below */ |
36 | rmb(); |
37 | |
38 | /* release the lock by setting tty of the struct to NULL */ |
39 | tty = xchg(&ssw->tty, NULL); |
40 | |
41 | if (spk_sel_cons != vc_cons[fg_console].d) { |
42 | spk_sel_cons = vc_cons[fg_console].d; |
43 | pr_warn("Selection: mark console not the same as cut\n" ); |
44 | goto unref; |
45 | } |
46 | |
47 | console_lock(); |
48 | clear_selection(); |
49 | console_unlock(); |
50 | |
51 | set_selection_kernel(v: &sel, tty); |
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; |
74 | } |
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(cpu: WORK_CPU_UNBOUND, work: &speakup_sel_work.work); |
88 | |
89 | return 0; |
90 | } |
91 | |
92 | void speakup_cancel_selection(void) |
93 | { |
94 | struct tty_struct *tty; |
95 | |
96 | cancel_work_sync(work: &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 | } |
105 | |
106 | static void __speakup_paste_selection(struct work_struct *work) |
107 | { |
108 | struct speakup_selection_work *ssw = |
109 | container_of(work, struct speakup_selection_work, work); |
110 | struct tty_struct *tty = xchg(&ssw->tty, NULL); |
111 | |
112 | paste_selection(tty); |
113 | tty_kref_put(tty); |
114 | } |
115 | |
116 | static struct speakup_selection_work speakup_paste_work = { |
117 | .work = __WORK_INITIALIZER(speakup_paste_work.work, |
118 | __speakup_paste_selection) |
119 | }; |
120 | |
121 | int speakup_paste_selection(struct tty_struct *tty) |
122 | { |
123 | tty_kref_get(tty); |
124 | if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) { |
125 | tty_kref_put(tty); |
126 | return -EBUSY; |
127 | } |
128 | |
129 | schedule_work_on(cpu: WORK_CPU_UNBOUND, work: &speakup_paste_work.work); |
130 | return 0; |
131 | } |
132 | |
133 | void speakup_cancel_paste(void) |
134 | { |
135 | struct tty_struct *tty; |
136 | |
137 | cancel_work_sync(work: &speakup_paste_work.work); |
138 | tty = xchg(&speakup_paste_work.tty, NULL); |
139 | if (tty) |
140 | tty_kref_put(tty); |
141 | } |
142 | |