Path: blob/master/drivers/accessibility/speakup/selection.c
26282 views
// SPDX-License-Identifier: GPL-2.01#include <linux/slab.h> /* for kmalloc */2#include <linux/consolemap.h>3#include <linux/interrupt.h>4#include <linux/sched.h>5#include <linux/device.h> /* for dev_warn */6#include <linux/selection.h>7#include <linux/workqueue.h>8#include <linux/tty.h>9#include <linux/tty_flip.h>10#include <linux/atomic.h>11#include <linux/console.h>1213#include "speakup.h"1415unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */16struct vc_data *spk_sel_cons;1718struct speakup_selection_work {19struct work_struct work;20struct tiocl_selection sel;21struct tty_struct *tty;22};2324static void __speakup_set_selection(struct work_struct *work)25{26struct speakup_selection_work *ssw =27container_of(work, struct speakup_selection_work, work);2829struct tty_struct *tty;30struct tiocl_selection sel;3132sel = ssw->sel;3334/* this ensures we copy sel before releasing the lock below */35rmb();3637/* release the lock by setting tty of the struct to NULL */38tty = xchg(&ssw->tty, NULL);3940if (spk_sel_cons != vc_cons[fg_console].d) {41spk_sel_cons = vc_cons[fg_console].d;42pr_warn("Selection: mark console not the same as cut\n");43goto unref;44}4546console_lock();47clear_selection();48console_unlock();4950set_selection_kernel(&sel, tty);5152unref:53tty_kref_put(tty);54}5556static struct speakup_selection_work speakup_sel_work = {57.work = __WORK_INITIALIZER(speakup_sel_work.work,58__speakup_set_selection)59};6061int speakup_set_selection(struct tty_struct *tty)62{63/* we get kref here first in order to avoid a subtle race when64* cancelling selection work. getting kref first establishes the65* invariant that if speakup_sel_work.tty is not NULL when66* speakup_cancel_selection() is called, it must be the case that a put67* kref is pending.68*/69tty_kref_get(tty);70if (cmpxchg(&speakup_sel_work.tty, NULL, tty)) {71tty_kref_put(tty);72return -EBUSY;73}74/* now we have the 'lock' by setting tty member of75* speakup_selection_work. wmb() ensures that writes to76* speakup_sel_work don't happen before cmpxchg() above.77*/78wmb();7980speakup_sel_work.sel.xs = spk_xs + 1;81speakup_sel_work.sel.ys = spk_ys + 1;82speakup_sel_work.sel.xe = spk_xe + 1;83speakup_sel_work.sel.ye = spk_ye + 1;84speakup_sel_work.sel.sel_mode = TIOCL_SELCHAR;8586schedule_work_on(WORK_CPU_UNBOUND, &speakup_sel_work.work);8788return 0;89}9091void speakup_cancel_selection(void)92{93struct tty_struct *tty;9495cancel_work_sync(&speakup_sel_work.work);96/* setting to null so that if work fails to run and we cancel it,97* we can run it again without getting EBUSY forever from there on.98* we need to use xchg here to avoid race with speakup_set_selection()99*/100tty = xchg(&speakup_sel_work.tty, NULL);101if (tty)102tty_kref_put(tty);103}104105static void __speakup_paste_selection(struct work_struct *work)106{107struct speakup_selection_work *ssw =108container_of(work, struct speakup_selection_work, work);109struct tty_struct *tty = xchg(&ssw->tty, NULL);110111paste_selection(tty);112tty_kref_put(tty);113}114115static struct speakup_selection_work speakup_paste_work = {116.work = __WORK_INITIALIZER(speakup_paste_work.work,117__speakup_paste_selection)118};119120int speakup_paste_selection(struct tty_struct *tty)121{122tty_kref_get(tty);123if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) {124tty_kref_put(tty);125return -EBUSY;126}127128schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work);129return 0;130}131132void speakup_cancel_paste(void)133{134struct tty_struct *tty;135136cancel_work_sync(&speakup_paste_work.work);137tty = xchg(&speakup_paste_work.tty, NULL);138if (tty)139tty_kref_put(tty);140}141142143