diff --git a/drivers/staging/speakup/main.c b/drivers/staging/speakup/main.c index b6a65b0c1896..488f2539aa9a 100644 --- a/drivers/staging/speakup/main.c +++ b/drivers/staging/speakup/main.c @@ -2319,6 +2319,7 @@ static void __exit speakup_exit(void) unregister_keyboard_notifier(&keyboard_notifier_block); unregister_vt_notifier(&vt_notifier_block); speakup_unregister_devsynth(); + speakup_cancel_selection(); speakup_cancel_paste(); del_timer_sync(&cursor_timer); kthread_stop(speakup_task); diff --git a/drivers/staging/speakup/selection.c b/drivers/staging/speakup/selection.c index 0ed1fefee0e9..a8b4d0c5ab7e 100644 --- a/drivers/staging/speakup/selection.c +++ b/drivers/staging/speakup/selection.c @@ -9,178 +9,138 @@ #include #include #include +#include #include "speakup.h" -/* ------ cut and paste ----- */ -/* Don't take this from : 011-015 on the screen aren't spaces */ -#define ishardspace(c) ((c) == ' ') - unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */ - -/* Variables for selection control. */ -/* must not be deallocated */ struct vc_data *spk_sel_cons; -/* cleared by clear_selection */ -static int sel_start = -1; -static int sel_end; -static int sel_buffer_lth; -static char *sel_buffer; -static unsigned char sel_pos(int n) -{ - return inverse_translate(spk_sel_cons, - screen_glyph(spk_sel_cons, n), 0); -} - -void speakup_clear_selection(void) -{ - sel_start = -1; -} - -/* does screen address p correspond to character at LH/RH edge of screen? */ -static int atedge(const int p, int size_row) -{ - return !(p % size_row) || !((p + 2) % size_row); -} - -/* constrain v such that v <= u */ -static unsigned short limit(const unsigned short v, const unsigned short u) -{ - return (v > u) ? u : v; -} - -int speakup_set_selection(struct tty_struct *tty) -{ - int new_sel_start, new_sel_end; - char *bp, *obp; - int i, ps, pe; - struct vc_data *vc = vc_cons[fg_console].d; - - spk_xs = limit(spk_xs, vc->vc_cols - 1); - spk_ys = limit(spk_ys, vc->vc_rows - 1); - spk_xe = limit(spk_xe, vc->vc_cols - 1); - spk_ye = limit(spk_ye, vc->vc_rows - 1); - ps = spk_ys * vc->vc_size_row + (spk_xs << 1); - pe = spk_ye * vc->vc_size_row + (spk_xe << 1); - - if (ps > pe) /* make sel_start <= sel_end */ - swap(ps, pe); - - if (spk_sel_cons != vc_cons[fg_console].d) { - speakup_clear_selection(); - spk_sel_cons = vc_cons[fg_console].d; - dev_warn(tty->dev, - "Selection: mark console not the same as cut\n"); - return -EINVAL; - } - - new_sel_start = ps; - new_sel_end = pe; - - /* select to end of line if on trailing space */ - if (new_sel_end > new_sel_start && - !atedge(new_sel_end, vc->vc_size_row) && - ishardspace(sel_pos(new_sel_end))) { - for (pe = new_sel_end + 2; ; pe += 2) - if (!ishardspace(sel_pos(pe)) || - atedge(pe, vc->vc_size_row)) - break; - if (ishardspace(sel_pos(pe))) - new_sel_end = pe; - } - if ((new_sel_start == sel_start) && (new_sel_end == sel_end)) - return 0; /* no action required */ - - sel_start = new_sel_start; - sel_end = new_sel_end; - /* Allocate a new buffer before freeing the old one ... */ - bp = kmalloc((sel_end - sel_start) / 2 + 1, GFP_ATOMIC); - if (!bp) { - speakup_clear_selection(); - return -ENOMEM; - } - kfree(sel_buffer); - sel_buffer = bp; - - obp = bp; - for (i = sel_start; i <= sel_end; i += 2) { - *bp = sel_pos(i); - if (!ishardspace(*bp++)) - obp = bp; - if (!((i + 2) % vc->vc_size_row)) { - /* strip trailing blanks from line and add newline, - * unless non-space at end of line. - */ - if (obp != bp) { - bp = obp; - *bp++ = '\r'; - } - obp = bp; - } - } - sel_buffer_lth = bp - sel_buffer; - return 0; -} - -struct speakup_paste_work { +struct speakup_selection_work { struct work_struct work; + struct tiocl_selection sel; struct tty_struct *tty; }; -static void __speakup_paste_selection(struct work_struct *work) +void speakup_clear_selection(void) { - struct speakup_paste_work *spw = - container_of(work, struct speakup_paste_work, work); - struct tty_struct *tty = xchg(&spw->tty, NULL); - struct vc_data *vc = (struct vc_data *)tty->driver_data; - int pasted = 0, count; - struct tty_ldisc *ld; - DECLARE_WAITQUEUE(wait, current); + console_lock(); + clear_selection(); + console_unlock(); +} - ld = tty_ldisc_ref(tty); - if (!ld) - goto tty_unref; - tty_buffer_lock_exclusive(&vc->port); +static void __speakup_set_selection(struct work_struct *work) +{ + struct speakup_selection_work *ssw = + container_of(work, struct speakup_selection_work, work); - add_wait_queue(&vc->paste_wait, &wait); - while (sel_buffer && sel_buffer_lth > pasted) { - set_current_state(TASK_INTERRUPTIBLE); - if (tty_throttled(tty)) { - schedule(); - continue; - } - count = sel_buffer_lth - pasted; - count = tty_ldisc_receive_buf(ld, sel_buffer + pasted, NULL, - count); - pasted += count; + 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; } - remove_wait_queue(&vc->paste_wait, &wait); - __set_current_state(TASK_RUNNING); - tty_buffer_unlock_exclusive(&vc->port); - tty_ldisc_deref(ld); -tty_unref: + console_lock(); + set_selection_kernel(&sel, tty); + console_unlock(); + +unref: tty_kref_put(tty); } -static struct speakup_paste_work speakup_paste_work = { +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) { - if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) - return -EBUSY; - 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_kref_put(speakup_paste_work.tty); + tty = xchg(&speakup_paste_work.tty, NULL); + if (tty) + tty_kref_put(tty); } diff --git a/drivers/staging/speakup/speakup.h b/drivers/staging/speakup/speakup.h index e4f4f00be2dc..74fe49c2c511 100644 --- a/drivers/staging/speakup/speakup.h +++ b/drivers/staging/speakup/speakup.h @@ -72,6 +72,7 @@ void synth_buffer_add(u16 ch); void synth_buffer_clear(void); void speakup_clear_selection(void); int speakup_set_selection(struct tty_struct *tty); +void speakup_cancel_selection(void); int speakup_paste_selection(struct tty_struct *tty); void speakup_cancel_paste(void); void speakup_register_devsynth(void);