[PATCH v3 2/2] staging: speakup: refactor to use existing code in vt

Samuel Thibault samuel.thibault at ens-lyon.org
Thu Apr 4 07:37:10 EDT 2019


Okash Khawaja, le jeu. 04 avril 2019 10:04:42 +0100, a ecrit:
> This patch replaces speakup's implementations with calls to functions
> in tty/vt/selection.c. Those functions are:
> 
> cancel_selection()
> do_set_selection()
> paste_selection()
> 
> Currently setting selection is done in interrupt context. However,
> do_set_selection() can sleep - for instance, it requires console_lock
> which can sleep. Therefore we offload that work to a work_struct thread,
> following the same pattern which was already set for paste_selection().
> When setting selection, we also get a reference to tty and make sure to
> release the reference before returning.
> 
> struct speakup_paste_work has been renamed to the more generic
> speakup_selection_work because it is now used for both pasting as well
> as setting selection. When paste work is cancelled, the code wasn't
> setting tty to NULL. This patch also fixes that by setting tty to NULL
> so that in case of failure we don't get EBUSY forever.
> 
> Signed-off-by: Okash Khawaja <okash.khawaja at gmail.com>

Reviewed-by: Samuel Thibault <samuel.thibault at ens-lyon.org>

> ---
>  drivers/staging/speakup/main.c      |   1 +
>  drivers/staging/speakup/selection.c | 212 +++++++++++-----------------
>  drivers/staging/speakup/speakup.h   |   1 +
>  3 files changed, 88 insertions(+), 126 deletions(-)
> 
> 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..6c6d77f8bc24 100644
> --- a/drivers/staging/speakup/selection.c
> +++ b/drivers/staging/speakup/selection.c
> @@ -9,178 +9,138 @@
>  #include <linux/tty.h>
>  #include <linux/tty_flip.h>
>  #include <linux/atomic.h>
> +#include <linux/console.h>
>  
>  #include "speakup.h"
>  
> -/* ------ cut and paste ----- */
> -/* Don't take this from <ctype.h>: 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);
> -}
> +struct speakup_selection_work {
> +	struct work_struct work;
> +	struct tiocl_selection sel;
> +	struct tty_struct *tty;
> +};
>  
>  void speakup_clear_selection(void)
>  {
> -	sel_start = -1;
> +	console_lock();
> +	clear_selection();
> +	console_unlock();
>  }
>  
> -/* does screen address p correspond to character at LH/RH edge of screen? */
> -static int atedge(const int p, int size_row)
> +static void __speakup_set_selection(struct work_struct *work)
>  {
> -	return !(p % size_row) || !((p + 2) % size_row);
> -}
> +	struct speakup_selection_work *ssw =
> +		container_of(work, struct speakup_selection_work, work);
>  
> -/* constrain v such that v <= u */
> -static unsigned short limit(const unsigned short v, const unsigned short u)
> -{
> -	return (v > u) ? u : v;
> -}
> +	struct tty_struct *tty;
> +	struct tiocl_selection sel;
>  
> -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;
> +	sel = ssw->sel;
>  
> -	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);
> +	/* this ensures we copy sel before releasing the lock below */
> +	rmb();
>  
> -	if (ps > pe)	/* make sel_start <= sel_end */
> -		swap(ps, pe);
> +	/* 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) {
> -		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;
> +		pr_warn("Selection: mark console not the same as cut\n");
> +		goto unref;
>  	}
>  
> -	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;
> -		}
> +	console_lock();
> +	do_set_selection(&sel, tty);
> +	console_unlock();
> +
> +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;
>  	}
> -	sel_buffer_lth = bp - sel_buffer;
> +	/* 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;
>  }
>  
> -struct speakup_paste_work {
> -	struct work_struct work;
> +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_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);
> -
> -	ld = tty_ldisc_ref(tty);
> -	if (!ld)
> -		goto tty_unref;
> -	tty_buffer_lock_exclusive(&vc->port);
> -
> -	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;
> -	}
> -	remove_wait_queue(&vc->paste_wait, &wait);
> -	__set_current_state(TASK_RUNNING);
> +	struct speakup_selection_work *ssw =
> +		container_of(work, struct speakup_selection_work, work);
> +	struct tty_struct *tty = xchg(&ssw->tty, NULL);
>  
> -	tty_buffer_unlock_exclusive(&vc->port);
> -	tty_ldisc_deref(ld);
> -tty_unref:
> +	paste_selection(tty);
>  	tty_kref_put(tty);
>  }
>  
> -static struct speakup_paste_work speakup_paste_work = {
> +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))
> +	tty_kref_get(tty);
> +	if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) {
> +		tty_kref_put(tty);
>  		return -EBUSY;
> +	}
>  
> -	tty_kref_get(tty);
>  	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);
> -- 
> 2.21.0
> 

-- 
Samuel
 tohi.cybercable.fr (212.198.0.3) si une personne se reconnait derriere
 cette adresse que ce soit un pirate ou une victime qu'il se manifeste,
 cette personne pourrait bien etre un petit malin
 -+- Fred in NPC : Mamaaaaan, y a le routeur qui veut me hacker -+-


More information about the Speakup mailing list