18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci#include <linux/slab.h> /* for kmalloc */ 38c2ecf20Sopenharmony_ci#include <linux/consolemap.h> 48c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 58c2ecf20Sopenharmony_ci#include <linux/sched.h> 68c2ecf20Sopenharmony_ci#include <linux/device.h> /* for dev_warn */ 78c2ecf20Sopenharmony_ci#include <linux/selection.h> 88c2ecf20Sopenharmony_ci#include <linux/workqueue.h> 98c2ecf20Sopenharmony_ci#include <linux/tty.h> 108c2ecf20Sopenharmony_ci#include <linux/tty_flip.h> 118c2ecf20Sopenharmony_ci#include <linux/atomic.h> 128c2ecf20Sopenharmony_ci#include <linux/console.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include "speakup.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ciunsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ 178c2ecf20Sopenharmony_cistruct vc_data *spk_sel_cons; 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistruct speakup_selection_work { 208c2ecf20Sopenharmony_ci struct work_struct work; 218c2ecf20Sopenharmony_ci struct tiocl_selection sel; 228c2ecf20Sopenharmony_ci struct tty_struct *tty; 238c2ecf20Sopenharmony_ci}; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistatic void __speakup_set_selection(struct work_struct *work) 268c2ecf20Sopenharmony_ci{ 278c2ecf20Sopenharmony_ci struct speakup_selection_work *ssw = 288c2ecf20Sopenharmony_ci container_of(work, struct speakup_selection_work, work); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci struct tty_struct *tty; 318c2ecf20Sopenharmony_ci struct tiocl_selection sel; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci sel = ssw->sel; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci /* this ensures we copy sel before releasing the lock below */ 368c2ecf20Sopenharmony_ci rmb(); 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci /* release the lock by setting tty of the struct to NULL */ 398c2ecf20Sopenharmony_ci tty = xchg(&ssw->tty, NULL); 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci if (spk_sel_cons != vc_cons[fg_console].d) { 428c2ecf20Sopenharmony_ci spk_sel_cons = vc_cons[fg_console].d; 438c2ecf20Sopenharmony_ci pr_warn("Selection: mark console not the same as cut\n"); 448c2ecf20Sopenharmony_ci goto unref; 458c2ecf20Sopenharmony_ci } 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci console_lock(); 488c2ecf20Sopenharmony_ci clear_selection(); 498c2ecf20Sopenharmony_ci console_unlock(); 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci set_selection_kernel(&sel, tty); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ciunref: 548c2ecf20Sopenharmony_ci tty_kref_put(tty); 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic struct speakup_selection_work speakup_sel_work = { 588c2ecf20Sopenharmony_ci .work = __WORK_INITIALIZER(speakup_sel_work.work, 598c2ecf20Sopenharmony_ci __speakup_set_selection) 608c2ecf20Sopenharmony_ci}; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ciint speakup_set_selection(struct tty_struct *tty) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci /* we get kref here first in order to avoid a subtle race when 658c2ecf20Sopenharmony_ci * cancelling selection work. getting kref first establishes the 668c2ecf20Sopenharmony_ci * invariant that if speakup_sel_work.tty is not NULL when 678c2ecf20Sopenharmony_ci * speakup_cancel_selection() is called, it must be the case that a put 688c2ecf20Sopenharmony_ci * kref is pending. 698c2ecf20Sopenharmony_ci */ 708c2ecf20Sopenharmony_ci tty_kref_get(tty); 718c2ecf20Sopenharmony_ci if (cmpxchg(&speakup_sel_work.tty, NULL, tty)) { 728c2ecf20Sopenharmony_ci tty_kref_put(tty); 738c2ecf20Sopenharmony_ci return -EBUSY; 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci /* now we have the 'lock' by setting tty member of 768c2ecf20Sopenharmony_ci * speakup_selection_work. wmb() ensures that writes to 778c2ecf20Sopenharmony_ci * speakup_sel_work don't happen before cmpxchg() above. 788c2ecf20Sopenharmony_ci */ 798c2ecf20Sopenharmony_ci wmb(); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci speakup_sel_work.sel.xs = spk_xs + 1; 828c2ecf20Sopenharmony_ci speakup_sel_work.sel.ys = spk_ys + 1; 838c2ecf20Sopenharmony_ci speakup_sel_work.sel.xe = spk_xe + 1; 848c2ecf20Sopenharmony_ci speakup_sel_work.sel.ye = spk_ye + 1; 858c2ecf20Sopenharmony_ci speakup_sel_work.sel.sel_mode = TIOCL_SELCHAR; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci schedule_work_on(WORK_CPU_UNBOUND, &speakup_sel_work.work); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci return 0; 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_civoid speakup_cancel_selection(void) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci struct tty_struct *tty; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci cancel_work_sync(&speakup_sel_work.work); 978c2ecf20Sopenharmony_ci /* setting to null so that if work fails to run and we cancel it, 988c2ecf20Sopenharmony_ci * we can run it again without getting EBUSY forever from there on. 998c2ecf20Sopenharmony_ci * we need to use xchg here to avoid race with speakup_set_selection() 1008c2ecf20Sopenharmony_ci */ 1018c2ecf20Sopenharmony_ci tty = xchg(&speakup_sel_work.tty, NULL); 1028c2ecf20Sopenharmony_ci if (tty) 1038c2ecf20Sopenharmony_ci tty_kref_put(tty); 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic void __speakup_paste_selection(struct work_struct *work) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci struct speakup_selection_work *ssw = 1098c2ecf20Sopenharmony_ci container_of(work, struct speakup_selection_work, work); 1108c2ecf20Sopenharmony_ci struct tty_struct *tty = xchg(&ssw->tty, NULL); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci paste_selection(tty); 1138c2ecf20Sopenharmony_ci tty_kref_put(tty); 1148c2ecf20Sopenharmony_ci} 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_cistatic struct speakup_selection_work speakup_paste_work = { 1178c2ecf20Sopenharmony_ci .work = __WORK_INITIALIZER(speakup_paste_work.work, 1188c2ecf20Sopenharmony_ci __speakup_paste_selection) 1198c2ecf20Sopenharmony_ci}; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ciint speakup_paste_selection(struct tty_struct *tty) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci tty_kref_get(tty); 1248c2ecf20Sopenharmony_ci if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) { 1258c2ecf20Sopenharmony_ci tty_kref_put(tty); 1268c2ecf20Sopenharmony_ci return -EBUSY; 1278c2ecf20Sopenharmony_ci } 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work); 1308c2ecf20Sopenharmony_ci return 0; 1318c2ecf20Sopenharmony_ci} 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_civoid speakup_cancel_paste(void) 1348c2ecf20Sopenharmony_ci{ 1358c2ecf20Sopenharmony_ci struct tty_struct *tty; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci cancel_work_sync(&speakup_paste_work.work); 1388c2ecf20Sopenharmony_ci tty = xchg(&speakup_paste_work.tty, NULL); 1398c2ecf20Sopenharmony_ci if (tty) 1408c2ecf20Sopenharmony_ci tty_kref_put(tty); 1418c2ecf20Sopenharmony_ci} 142