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