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