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