141 lines · 3.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0#include <linux/slab.h> /* for kmalloc */#include <linux/consolemap.h>#include <linux/interrupt.h>#include <linux/sched.h>#include <linux/device.h> /* for dev_warn */#include <linux/selection.h>#include <linux/workqueue.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/atomic.h>#include <linux/console.h> #include "speakup.h" u16 spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */struct vc_data *spk_sel_cons; struct speakup_selection_work {	struct work_struct work;	struct tiocl_selection sel;	struct tty_struct *tty;}; static void __speakup_set_selection(struct work_struct *work){	struct speakup_selection_work *ssw =		container_of(work, struct speakup_selection_work, work); 	struct tty_struct *tty;	struct tiocl_selection sel; 	sel = ssw->sel; 	/* this ensures we copy sel before releasing the lock below */	rmb(); 	/* release the lock by setting tty of the struct to NULL */	tty = xchg(&ssw->tty, NULL); 	if (spk_sel_cons != vc_cons[fg_console].d) {		spk_sel_cons = vc_cons[fg_console].d;		pr_warn("Selection: mark console not the same as cut\n");		goto unref;	} 	console_lock();	clear_selection();	console_unlock(); 	set_selection_kernel(&sel, tty); unref:	tty_kref_put(tty);} static struct speakup_selection_work speakup_sel_work = {	.work = __WORK_INITIALIZER(speakup_sel_work.work,				   __speakup_set_selection)}; int speakup_set_selection(struct tty_struct *tty){	/* we get kref here first in order to avoid a subtle race when	 * cancelling selection work. getting kref first establishes the	 * invariant that if speakup_sel_work.tty is not NULL when	 * speakup_cancel_selection() is called, it must be the case that a put	 * kref is pending.	 */	tty_kref_get(tty);	if (cmpxchg(&speakup_sel_work.tty, NULL, tty)) {		tty_kref_put(tty);		return -EBUSY;	}	/* now we have the 'lock' by setting tty member of	 * speakup_selection_work. wmb() ensures that writes to	 * speakup_sel_work don't happen before cmpxchg() above.	 */	wmb(); 	speakup_sel_work.sel.xs = spk_xs + 1;	speakup_sel_work.sel.ys = spk_ys + 1;	speakup_sel_work.sel.xe = spk_xe + 1;	speakup_sel_work.sel.ye = spk_ye + 1;	speakup_sel_work.sel.sel_mode = TIOCL_SELCHAR; 	schedule_work_on(WORK_CPU_UNBOUND, &speakup_sel_work.work); 	return 0;} void speakup_cancel_selection(void){	struct tty_struct *tty; 	cancel_work_sync(&speakup_sel_work.work);	/* setting to null so that if work fails to run and we cancel it,	 * we can run it again without getting EBUSY forever from there on.	 * we need to use xchg here to avoid race with speakup_set_selection()	 */	tty = xchg(&speakup_sel_work.tty, NULL);	if (tty)		tty_kref_put(tty);} static void __speakup_paste_selection(struct work_struct *work){	struct speakup_selection_work *ssw =		container_of(work, struct speakup_selection_work, work);	struct tty_struct *tty = xchg(&ssw->tty, NULL); 	paste_selection(tty);	tty_kref_put(tty);} static struct speakup_selection_work speakup_paste_work = {	.work = __WORK_INITIALIZER(speakup_paste_work.work,				   __speakup_paste_selection)}; int speakup_paste_selection(struct tty_struct *tty){	tty_kref_get(tty);	if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) {		tty_kref_put(tty);		return -EBUSY;	} 	schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work);	return 0;} void speakup_cancel_paste(void){	struct tty_struct *tty; 	cancel_work_sync(&speakup_paste_work.work);	tty = xchg(&speakup_paste_work.tty, NULL);	if (tty)		tty_kref_put(tty);}