summaryrefslogtreecommitdiffstats
path: root/drivers/accessibility/speakup/selection.c
diff options
context:
space:
mode:
authorPaulo Alcantara <pc@manguebit.org>2026-09-26 19:20:30 -0300
committerPaulo Alcantara <pc@manguebit.org>2026-09-30 14:24:12 -0300
commitdd05add7b2b6004c5fa3a1f276f56d8668f86b65 (patch)
tree23efdcaa0b8031f6ac6f57b6734d18d6c1adc6f2 /drivers/accessibility/speakup/selection.c
downloadlinux-stable-dd05add7b2b6004c5fa3a1f276f56d8668f86b65.tar.gz
linux-stable-dd05add7b2b6004c5fa3a1f276f56d8668f86b65.zip
netfs: zero the tail of a short DIO/unbuffered readgrafted
The buffered read collector zero-fills the tail of a short read that stops below the inode's i_size (netfs_clear_unread()), so a read that races an extending write still returns the expected number of bytes. The non-buffered collector path does no such thing: it just records how much was transferred. Add the same zero-fill for the non-buffered case, gated on NETFS_SREQ_CLEAR_TAIL: a subreq's source sets that flag when a short result from it is known to be safe to treat as a hole, as opposed to NETFS_SREQ_HIT_EOF, which means the read genuinely ran off the end of the file and should be reported short as-is. Only CLEAR_TAIL should zero-fill here; a real EOF must stay a real short read. No source currently sets CLEAR_TAIL on an unbuffered/DIO subrequest, so this is inert on its own -- a following change teaches cifs to set it in the one case that needs it. Fixes: e2d46f2ec332 ("netfs: Change the read result collector to only use one work item") Reviewed-by: David Howells <dhowells@redhat.com> Reviewed-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Paulo Alcantara <pc@manguebit.org> Cc: Christian Brauner <brauner@kernel.org> Cc: Matthew Wilcox <willy@infradead.org> Cc: Ronnie Sahlberg <ronniesahlberg@gmail.com> Cc: Shyam Prasad N <sprasad@microsoft.com> Cc: Tom Talpey <tom@talpey.com> Cc: Bharath SM <bharathsm@microsoft.com> Cc: stable@vger.kernel.org
Diffstat (limited to 'drivers/accessibility/speakup/selection.c')
-rw-r--r--drivers/accessibility/speakup/selection.c141
1 files changed, 141 insertions, 0 deletions
diff --git a/drivers/accessibility/speakup/selection.c b/drivers/accessibility/speakup/selection.c
new file mode 100644
index 000000000..1713ce4e0
--- /dev/null
+++ b/drivers/accessibility/speakup/selection.c
@@ -0,0 +1,141 @@
+// 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"
+
+u16 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);
+}