selection.c (3588B)
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 16unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ 17struct vc_data *spk_sel_cons; 18 19struct speakup_selection_work { 20 struct work_struct work; 21 struct tiocl_selection sel; 22 struct tty_struct *tty; 23}; 24 25static 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(&sel, tty); 52 53unref: 54 tty_kref_put(tty); 55} 56 57static struct speakup_selection_work speakup_sel_work = { 58 .work = __WORK_INITIALIZER(speakup_sel_work.work, 59 __speakup_set_selection) 60}; 61 62int 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(WORK_CPU_UNBOUND, &speakup_sel_work.work); 88 89 return 0; 90} 91 92void speakup_cancel_selection(void) 93{ 94 struct tty_struct *tty; 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} 105 106static 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 116static struct speakup_selection_work speakup_paste_work = { 117 .work = __WORK_INITIALIZER(speakup_paste_work.work, 118 __speakup_paste_selection) 119}; 120 121int 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(WORK_CPU_UNBOUND, &speakup_paste_work.work); 130 return 0; 131} 132 133void speakup_cancel_paste(void) 134{ 135 struct tty_struct *tty; 136 137 cancel_work_sync(&speakup_paste_work.work); 138 tty = xchg(&speakup_paste_work.tty, NULL); 139 if (tty) 140 tty_kref_put(tty); 141}