mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
3.5 KiB
141 lines
3.5 KiB
// 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" |
|
|
|
unsigned short 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); |
|
}
|
|
|