Hello, Gentoo.

The topic of this post is my kernel patch which enables soft scrolling
on Linux tty's with <shift><PgUp> and <shift><PgDn>, and also enables
the GPM mouse utility on those scrolled regions.

Attached is a patch which enables this facility in kernel 6.18.12 (and
likely future 6.18.n versions, too).

To use the patch:
(i) cd /usr/src/linux-6.12.18-gentoo, or similar.
(ii) patch -p1 < 6.12.18-GPM.20260222.diff.
(iii) Configure the kernel as normal.  Additionally, under Device
drivers/Graphic Support/Console display driver support, enable
CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK, set the buffer size to taste
(it's default is 128 kB but I use 512 kB, personally) and accept the
default enablement of CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM.
(iv) Build the kernel and install it into your boot manager.
(v) Reboot and enjoy!  You can now use GPM in scrolled consoles.

The usual disclaimers apply, here.  If this patch breaks anything for
you, please let me know, so that I can try to fix the bug.  My only
promise is that there's nothing malicious in the patch.                         
                                                                                
                                                                                

If anybody still needs them, I've still got the patches for some earlier
kernel versions too.

Have fun!

-- 
Alan Mackenzie (Nuremberg, Germany).

--- a/include/linux/console_struct.h    2026-02-22 17:41:32.714679279 +0000
+++ b/include/linux/console_struct.h    2026-02-22 15:38:39.132803820 +0000
@@ -109,6 +109,17 @@
        unsigned short  *vc_screenbuf;          /* In-memory 
character/attribute buffer */
        unsigned int    vc_screenbuf_size;
        unsigned char   vc_mode;                /* KD_TEXT, ... */
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       unsigned int    vc_softback_size;       /* Size in bytes of scrollback 
buffer. */
+       unsigned long   vc_softback_buf;        /* Address of scrollback 
buffer. */
+       unsigned long   vc_softback_end;        /* (Just past) end of buffer. */
+       unsigned long   vc_softback_in;         /* Head pointer into circular 
buffer. */
+       unsigned long   vc_softback_top;        /* Tail pointer into circular 
buffer. */
+       unsigned long   vc_softback_curr;       /* Pos in vc_screenbuf or 
vc_softback_buf
+                                                  corresponding to visible 
screen. */
+       int             vc_softback_lines;      /* Number of lines currently 
scrolled. */
+       unsigned short  vc_char_at_pos;         /* Char at vc_pos when no soft 
scroll */
+#endif
        /* attributes for all characters on screen */
        unsigned char   vc_attr;                /* Current attributes */
        unsigned char   vc_def_color;           /* Default colors */
@@ -158,10 +169,12 @@
        struct vc_data **vc_display_fg;         /* [!] Ptr to var holding fg 
console for this display */
        struct uni_pagedict *uni_pagedict;
        struct uni_pagedict **uni_pagedict_loc; /* [!] Location of uni_pagedict 
variable for this console */
-       u32 **vc_uni_lines;                     /* unicode screen content */
        u16             *vc_saved_screen;
        unsigned int    vc_saved_cols;
        unsigned int    vc_saved_rows;
+       uint32_t *vc_uniscr_buf;    /* Address of unicode screen content */
+       unsigned int vc_uniscr_char_size; /* Size of *vc-uniscr_buf in 32-bit 
chars */
+       uint32_t *vc_uniscr_curr;       /* Pos of first char of (unscrolled) 
screen */
        /* additional information is in vt_kern.h */
 };
 
@@ -196,4 +209,22 @@
 
 bool con_is_visible(const struct vc_data *vc);
 
+/* Macros for wraparound in the uniscr buffer.  POS must be a uint32_t pointer
+ * variable which is expected to be within the confines of vc->vc_uniscr_buf
+ * and vc->vc_uniscr_buf + vc->vc_uniscr_char_size.  OFFSET must be a positive
+ * distance measured in uint32_t's, which when added to or subtracted from POS
+ * won't be too far away from the buffer. */
+#define UNISCR_PLUS(pos,offset)                                                
\
+       do {                                                            \
+               pos += offset;                                          \
+               if (pos >= vc->vc_uniscr_buf + vc->vc_uniscr_char_size) \
+                       pos -= vc->vc_uniscr_char_size;                 \
+       } while (0)
+#define UNISCR_MINUS(pos,offset)                       \
+       do {                                            \
+               pos -= offset;                          \
+               if (pos < vc->vc_uniscr_buf)            \
+                       pos += vc->vc_uniscr_char_size; \
+       } while (0)
+
 #endif /* _LINUX_CONSOLE_STRUCT_H */
--- a/include/linux/vt_kern.h   2026-02-22 17:41:32.795302219 +0000
+++ b/include/linux/vt_kern.h   2026-02-22 15:18:55.382750836 +0000
@@ -121,6 +121,9 @@
 /* vt.c */
 void vt_event_post(unsigned int event, unsigned int old, unsigned int new);
 int vt_waitactive(int n);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+void concon_scrolldelta(struct vc_data *vc, int lines);
+#endif
 void change_console(struct vc_data *new_vc);
 void reset_vc(struct vc_data *vc);
 int do_unbind_con_driver(const struct consw *csw, int first, int last,
--- a/drivers/video/fbdev/core/fbcon.c  2026-02-22 17:41:32.545737942 +0000
+++ b/drivers/video/fbdev/core/fbcon.c  2026-02-22 15:18:55.380750857 +0000
@@ -639,6 +639,28 @@
                    erase,
                    vc->vc_size_row * logo_lines);
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+       {
+               uint32_t *s = vc->vc_uniscr_curr, *d = vc->vc_uniscr_curr;
+               unsigned int i = vc->vc_rows - logo_lines;
+
+               UNISCR_PLUS(d, vc->vc_rows * vc->vc_cols);
+               UNISCR_PLUS(s, (vc->vc_rows - logo_lines) * vc->vc_cols);
+               while (i--) {
+                       UNISCR_MINUS(d, vc->vc_cols);
+                       UNISCR_MINUS(s, vc->vc_cols);
+                       memcpy(d, s, vc->vc_cols * sizeof (uint32_t));
+               }
+               i = logo_lines;
+               d = vc->vc_uniscr_curr;
+               while (i--) {
+                       memset32(d, ' ', vc->vc_cols);
+                       UNISCR_PLUS(d, vc->vc_cols);
+               }
+               vc->vc_uniscr_curr = d;
+       }
+#endif
+
        if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) {
                fbcon_clear_margins(vc, 0);
                update_screen(vc);
@@ -1289,6 +1311,25 @@
                 * bitmap stretched into the margin area.
                 */
                fbcon_clear_margins(vc, 0);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+               {
+                       uint32_t *s = vc->vc_uniscr_curr, *d = 
vc->vc_uniscr_curr;
+                       unsigned int i = vc->vc_rows - logo_lines;
+
+                       UNISCR_PLUS(d, (vc->vc_rows - logo_lines)  * 
vc->vc_cols);
+                       UNISCR_PLUS(s, (vc->vc_rows - 2 * logo_lines) * 
vc->vc_cols);
+                       while (i--) {
+                               UNISCR_MINUS(d, vc->vc_cols);
+                               UNISCR_MINUS(s, vc->vc_cols);
+                               memcpy (d, s, vc->vc_cols * sizeof (uint32_t));
+                       }
+                       i = logo_lines;
+                       while (i--) {
+                               UNISCR_MINUS(d, vc->vc_cols);
+                               memset32(d, ' ', vc->vc_cols);
+                       }
+               }
+#endif
        }
 
        fg = get_color(vc, info, vc->vc_video_erase_char, 1);
@@ -2096,6 +2137,7 @@
        struct fbcon_ops *ops;
        struct fbcon_display *p = &fb_display[vc->vc_num];
        struct fb_var_screeninfo var;
+       unsigned short *d, *s;
        int i, ret, prev_console;
 
        info = fbcon_info_from_console(vc->vc_num);
@@ -2105,8 +2147,21 @@
                struct vc_data *conp2 = vc_cons[logo_shown].d;
 
                if (conp2->vc_top == logo_lines
-                   && conp2->vc_bottom == conp2->vc_rows)
+                   && conp2->vc_bottom == conp2->vc_rows) {
+                       /* Scroll the bottom part of the screen up to fill the
+                        * logo lines. */
+                       i = conp2->vc_bottom - conp2->vc_top;
+                       d = (unsigned short *)conp2->vc_origin;
+                       s = (unsigned short *)(conp2->vc_origin + logo_lines * 
conp2->vc_size_row);
+                       while (i--) {
+                               scr_memcpyw(d, s, conp2->vc_size_row);
+                               d += conp2->vc_cols;
+                               s += conp2->vc_cols;
+                       }
+                       scr_memsetw(d, conp2->vc_video_erase_char,
+                                   conp2->vc_size_row * logo_lines);
                        conp2->vc_top = 0;
+               }
                logo_shown = FBCON_LOGO_CANSHOW;
        }
 
@@ -3199,6 +3254,9 @@
        .con_font_get           = fbcon_get_font,
        .con_font_default       = fbcon_set_def_font,
        .con_set_palette        = fbcon_set_palette,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       .con_scrolldelta        = concon_scrolldelta,
+#endif
        .con_invert_region      = fbcon_invert_region,
        .con_resize             = fbcon_resize,
        .con_debug_enter        = fbcon_debug_enter,
--- a/drivers/video/console/Kconfig     2026-02-22 17:41:32.542536616 +0000
+++ b/drivers/video/console/Kconfig     2026-02-22 15:18:55.379750869 +0000
@@ -100,6 +100,44 @@
 
          If unsure, select n.
 
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       bool "Enable Scrollback Buffer in System RAM"
+       depends on FB=y && FRAMEBUFFER_CONSOLE
+       default y
+       help
+         This option creates a scrollback buffer for each framebuffer console.
+          These buffers are allocated dynamically during initialisation.
+
+         If you want this feature, say 'Y' here and enter in
+          FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE the amount of RAM to
+          allocate for each buffer.  If unsure, say 'N'.
+
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE
+       int "Scrollback Buffer Size (in KB)"
+       depends on FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       range 1 1024
+       default "128"
+       help
+         Enter the amount of System RAM in kilobytes to allocate for the
+          scrollback buffer of each framebuffer console.  Each character
+         position on the video takes 2 bytes of storage.  128kB will give you
+         approximately four 240x67 screenfuls of scrollback buffer.
+
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+       bool "Enable a working GPM for scrolled back scrollback buffer in 
System RAM"
+       depends on FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       default y
+       help
+         This option buffers up Unicode characters corresponding to the glyphs
+         displayed by the scrollback buffer.  This enables the GPM mouse driver
+         (or similar) to copy characters from a scrolled back buffer.
+
+         A buffer is created for each framebuffer console, this buffer being
+         approximately twice as big as the buffer size specified in
+         FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE.
+
+         If unsure, say 'Y'.
+
 config FRAMEBUFFER_CONSOLE_DETECT_PRIMARY
        bool "Map the console to the primary display device"
        depends on FRAMEBUFFER_CONSOLE
--- a/drivers/tty/vt/vt.c       2026-02-22 17:41:32.501447609 +0000
+++ b/drivers/tty/vt/vt.c       2026-02-22 16:05:14.063711989 +0000
@@ -133,6 +133,11 @@
 #define DEFAULT_BELL_DURATION  (HZ/8)
 #define DEFAULT_CURSOR_BLINK_MS        200
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static unsigned int console_soft_scrollback_size =
+       1024 * CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE;
+#endif
+
 struct vc vc_cons [MAX_NR_CONSOLES];
 EXPORT_SYMBOL(vc_cons);
 
@@ -289,9 +294,33 @@
 static inline u16 *screenpos(const struct vc_data *vc, unsigned int offset,
                             bool viewed)
 {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       unsigned long softback_pos, scrolled_expanse;
+
+       if (vc->vc_softback_curr == vc->vc_origin) /* Should never happen! */
+               return (unsigned short *)(vc->vc_origin + offset);
+       else {
+               if (vc->vc_softback_in >= vc->vc_softback_curr)
+                       scrolled_expanse = vc->vc_softback_in - 
vc->vc_softback_curr;
+               else
+                       scrolled_expanse = vc->vc_softback_in - 
vc->vc_softback_curr
+                               + vc->vc_softback_end - vc->vc_softback_buf;
+               if (offset >= scrolled_expanse)
+                       return (unsigned short *)(vc->vc_origin
+                                                 + (offset - 
scrolled_expanse));
+               else {
+                       softback_pos = vc->vc_softback_curr + offset;
+                       if (softback_pos >= vc->vc_softback_end)
+                               softback_pos -= vc->vc_softback_end
+                                       - vc->vc_softback_buf;
+               }
+       }
+       return (unsigned short *)softback_pos;
+#else
        unsigned long origin = viewed ? vc->vc_visible_origin : vc->vc_origin;
 
        return (u16 *)(origin + offset);
+#endif
 }
 
 static void con_putc(struct vc_data *vc, u16 ca, unsigned int y, unsigned int 
x)
@@ -321,107 +350,111 @@
  * Code to manage unicode-based screen buffers
  */
 
-/*
- * Our screen buffer is preceded by an array of line pointers so that
- * scrolling only implies some pointer shuffling.
- */
+#define vc_uniscr_buf_end(vc) (vc->vc_uniscr_buf + vc->vc_uniscr_char_size)
 
-static u32 **vc_uniscr_alloc(unsigned int cols, unsigned int rows)
+static int vc_uniscr_alloc(struct vc_data *vc, unsigned int cols, unsigned int 
rows)
 {
-       u32 **uni_lines;
-       void *p;
-       unsigned int memsize, i, col_size = cols * sizeof(**uni_lines);
-
-       /* allocate everything in one go */
-       memsize = col_size * rows;
-       memsize += rows * sizeof(*uni_lines);
-       uni_lines = vzalloc(memsize);
-       if (!uni_lines)
-               return NULL;
+       uint32_t *p;
+       unsigned int new_size;  /* In 32-bit characters */
 
-       /* initial line pointers */
-       p = uni_lines + rows;
-       for (i = 0; i < rows; i++) {
-               uni_lines[i] = p;
-               p += col_size;
-       }
-
-       return uni_lines;
-}
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+       unsigned int num_scrollback_rows;
 
-static void vc_uniscr_free(u32 **uni_lines)
-{
-       vfree(uni_lines);
+       num_scrollback_rows = (console_soft_scrollback_size / 2) / cols;
+       new_size = cols * (num_scrollback_rows + rows + 1);
+#else
+       new_size = cols * (rows + 1); /* 1 row for the circular buffer admin */
+#endif
+       p = (uint32_t *)vzalloc (sizeof (uint32_t) * new_size);
+       if (!p)
+               return -ENOMEM;
+       vc->vc_uniscr_buf = p;
+       vc->vc_uniscr_curr = p;
+       vc->vc_uniscr_char_size = new_size;
+       memset32(p, ' ', new_size); /* Probably redundant. */
+       return 0;
 }
 
-static void vc_uniscr_set(struct vc_data *vc, u32 **new_uni_lines)
+static void vc_uniscr_free(struct vc_data *vc)
 {
-       vc_uniscr_free(vc->vc_uni_lines);
-       vc->vc_uni_lines = new_uni_lines;
+       kvfree(vc->vc_uniscr_buf);
+       vc->vc_uniscr_buf = NULL;
 }
 
 static void vc_uniscr_putc(struct vc_data *vc, u32 uc)
 {
-       if (vc->vc_uni_lines)
-               vc->vc_uni_lines[vc->state.y][vc->state.x] = uc;
+       uint32_t *pos;
+
+       if (vc->vc_uniscr_buf) {
+               pos = vc->vc_uniscr_curr;
+               UNISCR_PLUS(pos, vc->state.y * vc->vc_cols + vc->state.x);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+               UNISCR_MINUS(pos, vc->vc_cols * (vc->vc_softback_lines + 
vc->vc_top));
+#endif
+               *pos = uc;
+       }
 }
 
 static void vc_uniscr_insert(struct vc_data *vc, unsigned int nr)
 {
-       if (vc->vc_uni_lines) {
-               u32 *ln = vc->vc_uni_lines[vc->state.y];
-               unsigned int x = vc->state.x, cols = vc->vc_cols;
+       unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+       uint32_t *ln = vc->vc_uniscr_curr;
 
-               memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(*ln));
+       if (vc->vc_uniscr_buf) {
+               UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+               UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + 
vc->vc_top));
+#endif
+               memmove(&ln[x + nr], &ln[x], (cols - x - nr) * 
sizeof(uint32_t));
                memset32(&ln[x], ' ', nr);
        }
 }
 
 static void vc_uniscr_delete(struct vc_data *vc, unsigned int nr)
 {
-       if (vc->vc_uni_lines) {
-               u32 *ln = vc->vc_uni_lines[vc->state.y];
-               unsigned int x = vc->state.x, cols = vc->vc_cols;
+       unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+       uint32_t *ln = vc->vc_uniscr_curr;
 
-               memmove(&ln[x], &ln[x + nr], (cols - x - nr) * sizeof(*ln));
+       if (vc->vc_uniscr_buf) {
+               UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+               UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + 
vc->vc_top));
+#endif
+               memmove(&ln[x], &ln[x + nr], (cols - x - nr) * 
sizeof(uint32_t));
                memset32(&ln[cols - nr], ' ', nr);
        }
 }
 
+/* FIXME!!!  We need to check that NR never goes beyond the current line end.  
!!! */
 static void vc_uniscr_clear_line(struct vc_data *vc, unsigned int x,
                                 unsigned int nr)
 {
-       if (vc->vc_uni_lines)
-               memset32(&vc->vc_uni_lines[vc->state.y][x], ' ', nr);
+       if (vc->vc_uniscr_buf) {
+               uint32_t *ln = vc->vc_uniscr_curr;
+
+               UNISCR_PLUS(ln, vc->state.y * vc->vc_cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+               UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + 
vc->vc_top));
+#endif
+               memset32(&ln[x], ' ', nr);
+       }
 }
 
 static void vc_uniscr_clear_lines(struct vc_data *vc, unsigned int y,
                                  unsigned int nr)
 {
-       if (vc->vc_uni_lines)
-               while (nr--)
-                       memset32(vc->vc_uni_lines[y++], ' ', vc->vc_cols);
-}
-
-/* juggling array rotation algorithm (complexity O(N), size complexity O(1)) */
-static void juggle_array(u32 **array, unsigned int size, unsigned int nr)
-{
-       unsigned int gcd_idx;
-
-       for (gcd_idx = 0; gcd_idx < gcd(nr, size); gcd_idx++) {
-               u32 *gcd_idx_val = array[gcd_idx];
-               unsigned int dst_idx = gcd_idx;
-
-               while (1) {
-                       unsigned int src_idx = (dst_idx + nr) % size;
-                       if (src_idx == gcd_idx)
-                               break;
-
-                       array[dst_idx] = array[src_idx];
-                       dst_idx = src_idx;
+       if (vc->vc_uniscr_buf) {
+               unsigned int cols = vc->vc_cols;
+               uint32_t *ln = vc->vc_uniscr_curr;
+
+               UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+               UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + 
vc->vc_top));
+#endif
+               while (nr--) {
+                       memset32(ln, ' ', cols);
+                       UNISCR_PLUS(ln, cols);
                }
-
-               array[dst_idx] = gcd_idx_val;
        }
 }
 
@@ -429,60 +462,101 @@
                             unsigned int bottom, enum con_scroll dir,
                             unsigned int nr)
 {
-       u32 **uni_lines = vc->vc_uni_lines;
-       unsigned int size = bottom - top;
+/*     u32 **uni_lines = vc->vc_uni_lines; */
+/*     unsigned int size = bottom - top; */
 
-       if (!uni_lines)
-               return;
+/*     if (!uni_lines) */
+/*             return; */
 
-       if (dir == SM_DOWN) {
-               juggle_array(&uni_lines[top], size, size - nr);
-               vc_uniscr_clear_lines(vc, top, nr);
-       } else {
-               juggle_array(&uni_lines[top], size, nr);
-               vc_uniscr_clear_lines(vc, bottom - nr, nr);
+/*     if (dir == SM_DOWN) { */
+/*             juggle_array(&uni_lines[top], size, size - nr); */
+/*             vc_uniscr_clear_lines(vc, top, nr); */
+/*     } else { */
+/*             juggle_array(&uni_lines[top], size, nr); */
+/*             vc_uniscr_clear_lines(vc, bottom - nr, nr); */
+/*     } */
+/* } */
+
+       if (vc->vc_uniscr_buf) {
+               unsigned int cols = vc->vc_cols;
+               unsigned int sz, /* number of rows being scrolled */
+                       d,                /* number of rows needing blanking */
+                       clear;            /* The number of the topmost row 
needing blanking. */
+               uint32_t *dest, *src;
+               unsigned int i;
+
+               if (dir == SM_UP /* && top == 0 */&& bottom == vc->vc_rows) {
+                       UNISCR_PLUS(vc->vc_uniscr_curr, nr * cols);
+                       d = nr;
+                       clear = vc->vc_rows - nr;
+               } else if (dir == SM_DOWN && top == 0 && bottom == vc->vc_rows 
- nr) {
+                       UNISCR_MINUS(vc->vc_uniscr_curr, nr * cols);
+                       d = nr;
+               clear = top;
+               } else if (dir == SM_UP) {
+                       sz = bottom - top;
+                       src = vc->vc_uniscr_curr;
+                       UNISCR_PLUS(src, top * cols);
+                       dest = src;
+                       UNISCR_MINUS(dest, nr * cols);
+                       i = bottom - top;
+                       while (i--) {
+                               memcpy(dest, src, cols * sizeof(uint32_t));
+                               UNISCR_PLUS(src, cols);
+                               UNISCR_PLUS(dest, cols);
+                       }
+                       d = nr;
+                       clear = bottom - nr;
+               } else {
+                       src = vc->vc_uniscr_curr;
+                       UNISCR_PLUS(src, bottom * cols);
+                       dest = src;
+                       UNISCR_PLUS(dest, nr * cols);
+                       i = bottom - top;
+                       while (i--) {
+                               UNISCR_MINUS(src, cols);
+                               UNISCR_MINUS(dest, cols);
+                               memcpy(dest, src, cols * sizeof(uint32_t));
+                       }
+                       d = nr;
+                       clear = top;
+               }
+               if (d)
+                       vc_uniscr_clear_lines(vc, clear, nr);
        }
 }
 
+/* OLD STOUGH, 2025-10-24 */
+/* static u32 vc_uniscr_getc(struct vc_data *vc, int relative_pos) */
+/* { */
+/*     int pos = vc->state.x + vc->vc_need_wrap + relative_pos; */
+
+/*     if (vc->vc_uni_lines && in_range(pos, 0, vc->vc_cols)) */
+/*             return vc->vc_uni_lines[vc->state.y][pos]; */
+/*     return 0; */
+/* } */
+/* NEW STOUGH, 2025-10-24 */
 static u32 vc_uniscr_getc(struct vc_data *vc, int relative_pos)
 {
-       int pos = vc->state.x + vc->vc_need_wrap + relative_pos;
-
-       if (vc->vc_uni_lines && in_range(pos, 0, vc->vc_cols))
-               return vc->vc_uni_lines[vc->state.y][pos];
-       return 0;
-}
-
-static void vc_uniscr_copy_area(u32 **dst_lines,
-                               unsigned int dst_cols,
-                               unsigned int dst_rows,
-                               u32 **src_lines,
-                               unsigned int src_cols,
-                               unsigned int src_top_row,
-                               unsigned int src_bot_row)
-{
-       unsigned int dst_row = 0;
-
-       if (!dst_lines)
-               return;
-
-       while (src_top_row < src_bot_row) {
-               u32 *src_line = src_lines[src_top_row];
-               u32 *dst_line = dst_lines[dst_row];
-
-               memcpy(dst_line, src_line, src_cols * sizeof(*src_line));
-               if (dst_cols - src_cols)
-                       memset32(dst_line + src_cols, ' ', dst_cols - src_cols);
-               src_top_row++;
-               dst_row++;
-       }
-       while (dst_row < dst_rows) {
-               u32 *dst_line = dst_lines[dst_row];
+       int xpos = vc->state.x + vc->vc_need_wrap + relative_pos;
 
-               memset32(dst_line, ' ', dst_cols);
-               dst_row++;
+       if (vc->vc_uniscr_buf && in_range(xpos, 0, vc->vc_cols))
+       {
+               int pos = (vc->state.y * vc->vc_cols) + xpos;
+               uint32_t *upos = vc->vc_uniscr_curr;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+               UNISCR_PLUS(upos, pos);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+               UNISCR_MINUS(upos, vc->vc_softback_lines * vc->vc_cols);
+#endif
+               return *upos;
+#else
+               return *(upos + pos);
+#endif
        }
+       return 0;       
 }
+/* END OF NEW STOUGH */
 
 /*
  * Called from vcs_read() to make sure unicode screen retrieval is possible.
@@ -492,7 +566,6 @@
  */
 int vc_uniscr_check(struct vc_data *vc)
 {
-       u32 **uni_lines;
        unsigned short *p;
        int x, y, mask;
 
@@ -501,11 +574,10 @@
        if (!vc->vc_utf)
                return -ENODATA;
 
-       if (vc->vc_uni_lines)
+       if (vc->vc_uniscr_buf)
                return 0;
 
-       uni_lines = vc_uniscr_alloc(vc->vc_cols, vc->vc_rows);
-       if (!uni_lines)
+       if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows))
                return -ENOMEM;
 
        /*
@@ -517,15 +589,14 @@
        p = (unsigned short *)vc->vc_origin;
        mask = vc->vc_hi_font_mask | 0xff;
        for (y = 0; y < vc->vc_rows; y++) {
-               u32 *line = uni_lines[y];
+               uint32_t *line = vc->vc_uniscr_curr;
+               UNISCR_PLUS(line, y * vc->vc_cols);
                for (x = 0; x < vc->vc_cols; x++) {
                        u16 glyph = scr_readw(p++) & mask;
                        line[x] = inverse_translate(vc, glyph, true);
                }
        }
 
-       vc->vc_uni_lines = uni_lines;
-
        return 0;
 }
 
@@ -537,13 +608,24 @@
 void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
                         unsigned int row, unsigned int col, unsigned int nr)
 {
-       u32 **uni_lines = vc->vc_uni_lines;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       uint32_t *pos;         /* Position in the unicode buffer of col/row */
+#else
        int offset = row * vc->vc_size_row + col * 2;
-       unsigned long pos;
+       unsigned long pos; /* Position in the main screen buffer of col/row */
+#endif
 
-       if (WARN_ON_ONCE(!uni_lines))
+       if (WARN_ON_ONCE(!vc->vc_uniscr_buf))
                return;
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       pos = vc->vc_uniscr_curr;
+       UNISCR_PLUS(pos, row * vc->vc_cols + col);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+       UNISCR_MINUS(pos, vc->vc_softback_lines * vc->vc_cols);
+#endif
+       memcpy(dest, pos, nr * sizeof(uint32_t));
+#else
        pos = (unsigned long)screenpos(vc, offset, viewed);
        if (pos >= vc->vc_origin && pos < vc->vc_scr_end) {
                /*
@@ -553,24 +635,233 @@
                 */
                row = (pos - vc->vc_origin) / vc->vc_size_row;
                col = ((pos - vc->vc_origin) % vc->vc_size_row) / 2;
-               memcpy(dest, &uni_lines[row][col], nr * sizeof(u32));
+               memcpy(dest,
+                      (void *)(vc->vc_uniscr_curr + row * vc->vc_cols + col),
+                      nr);
        } else {
                /*
-                * Scrollback is active. For now let's simply backtranslate
-                * the screen glyphs until the unicode screen buffer does
-                * synchronize with console display drivers for a scrollback
-                * buffer of its own.
+                * Scrollback is active.  So hoik the unicode characters out
+                * of the unicode circular buffer.
                 */
-               u16 *p = (u16 *)pos;
-               int mask = vc->vc_hi_font_mask | 0xff;
-               u32 *uni_buf = dest;
-               while (nr--) {
-                       u16 glyph = scr_readw(p++) & mask;
-                       *uni_buf++ = inverse_translate(vc, glyph, true);
+               /* CAN'T HAPPEN!!!  (Hah hah!) */
+       }
+#endif
+}
+
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void con_update_softback(struct vc_data *vc)
+{
+       int l = console_soft_scrollback_size / vc->vc_size_row;
+       if (l > 5)
+       {
+               vc->vc_softback_end = vc->vc_softback_buf + l * vc->vc_size_row;
+               vc->vc_softback_top = vc->vc_softback_buf;
+       }
+       else
+               /* Smaller scrollback makes no sense, and 0 would screw
+                  the operation totally */
+               vc->vc_softback_top = 0;
+}
+
+static int concon_set_origin(struct vc_data *vc)
+{
+       if (vc->vc_softback_lines)
+               concon_scrolldelta(vc, vc->vc_softback_lines);
+       return 0;
+}
+
+#define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) 
* vc->vc_size_row)
+
+static void con_redraw_softback(struct vc_data *vc, /* struct display *p, */
+                               long delta)
+{
+       int count = vc->vc_rows;
+       unsigned short *d, *s;
+       unsigned long n;
+       int line = 0;
+
+       if (!vc->vc_softback_lines)
+               vc->vc_char_at_pos = scr_readw((u16 *)vc->vc_pos);
+
+       d = (u16 *) vc->vc_softback_curr;
+       if (d == (u16 *) vc->vc_softback_in)
+               d = (u16 *) vc->vc_origin;
+       n = vc->vc_softback_curr + delta * vc->vc_size_row;
+       vc->vc_softback_lines -= delta;
+       if (delta < 0) {
+               if (vc->vc_softback_curr < vc->vc_softback_top
+                   && n < vc->vc_softback_buf) {
+                       n += vc->vc_softback_end - vc->vc_softback_buf;
+                       if (n < vc->vc_softback_top) {
+                               vc->vc_softback_lines -=
+                                   (vc->vc_softback_top - n) / vc->vc_size_row;
+                               n = vc->vc_softback_top;
+                       }
+               } else if (vc->vc_softback_curr >= vc->vc_softback_top
+                          && n < vc->vc_softback_top) {
+                       vc->vc_softback_lines -=
+                           (vc->vc_softback_top - n) / vc->vc_size_row;
+                       n = vc->vc_softback_top;
                }
+       } else {
+               if (vc->vc_softback_curr > vc->vc_softback_in
+                   && n >= vc->vc_softback_end) {
+                       n += vc->vc_softback_buf - vc->vc_softback_end;
+                       if (n > vc->vc_softback_in) {
+                               n = vc->vc_softback_in;
+                               vc->vc_softback_lines = 0;
+                       }
+               } else if (vc->vc_softback_curr <= vc->vc_softback_in
+                          && n > vc->vc_softback_in) {
+                       n = vc->vc_softback_in;
+                       vc->vc_softback_lines = 0;
+               }
+       }
+       if (n == vc->vc_softback_curr)
+               return;
+       vc->vc_softback_curr = n;
+       /* If we're not scrolled any more, restore the character to the cursor
+        * position */
+       if (!vc->vc_softback_lines)
+               scr_writew(vc->vc_char_at_pos, (u16 *)vc->vc_pos);
+       s = (u16 *) vc->vc_softback_curr;
+       if (s == (u16 *) vc->vc_softback_in)
+               s = (u16 *) vc->vc_origin;
+       while (count--) {
+               unsigned short *start;
+               unsigned short *le;
+               unsigned short c;
+               int x = 0;
+               unsigned short attr = 1;
+
+               start = s;
+               le = advance_row(s, 1);
+               /* Temporarily overwrite the character at the cursor position
+                * with the one we actually want to see on the screen.  */
+               if (count == vc->vc_rows - vc->state.y - 1)
+               {
+                       c = scr_readw((u16 *)(s + vc->state.x));
+                       scr_writew(c, (u16 *)vc->vc_pos);
+                       vc->vc_sw->con_putcs
+                               (vc, (u16 *)vc->vc_pos, 1, line, vc->state.x);
+               }
+               do {
+                       c = scr_readw(s);
+                       if (attr != (c & 0xff00)) {
+                               attr = c & 0xff00;
+                               if (s > start) {
+                                       vc->vc_sw->con_putcs(
+                                               vc, start, s - start,
+                                               line, x);
+                                       x += s - start;
+                                       start = s;
+                               }
+                       }
+                       if (c == scr_readw(d)) {
+                               if (s > start) {
+                                       vc->vc_sw->con_putcs(
+                                               vc, start, s - start,
+                                               line, x);
+                                       x += s - start + 1;
+                                       start = s + 1;
+                               } else {
+                                       x++;
+                                       start++;
+                               }
+                       }
+                       s++;
+                       d++;
+               } while (s < le);
+               if (s > start)
+                       vc->vc_sw->con_putcs(vc, start, s - start, line, x);
+               line++;
+               if (d == (u16 *) vc->vc_softback_end)
+                       d = (u16 *) vc->vc_softback_buf;
+               if (d == (u16 *) vc->vc_softback_in)
+                       d = (u16 *) vc->vc_origin;
+               if (s == (u16 *) vc->vc_softback_end)
+                       s = (u16 *) vc->vc_softback_buf;
+               if (s == (u16 *) vc->vc_softback_in)
+                       s = (u16 *) vc->vc_origin;
        }
 }
 
+static inline void con_softback_note(struct vc_data *vc, int t,
+                                    int count)
+{
+       unsigned short *p;
+
+       if (vc->vc_num != fg_console)
+               return;
+       p = (unsigned short *) (vc->vc_origin + t * vc->vc_size_row);
+
+       while (count) {
+               scr_memcpyw((u16 *) vc->vc_softback_in, p, vc->vc_size_row);
+               count--;
+               p = advance_row(p, 1);
+               vc->vc_softback_in += vc->vc_size_row;
+               if (vc->vc_softback_in == vc->vc_softback_end)
+                       vc->vc_softback_in = vc->vc_softback_buf;
+               if (vc->vc_softback_in == vc->vc_softback_top) {
+                       vc->vc_softback_top += vc->vc_size_row;
+                       if (vc->vc_softback_top == vc->vc_softback_end)
+                               vc->vc_softback_top = vc->vc_softback_buf;
+               }
+       }
+       vc->vc_softback_curr = vc->vc_softback_in;
+}
+
+void concon_scrolldelta(struct vc_data *vc, int lines)
+{
+       /* struct display *disp = &fb_display[fg_console]; */
+       /* int offset, limit, scrollback_old; */
+
+       if (vc->vc_softback_top) {
+               if (vc->vc_num != fg_console)
+                       return;
+               if (vc->vc_mode != KD_TEXT || !lines)
+                       return;
+#if 0
+               if (logo_shown >= 0) {
+                       struct vc_data *conp2 = vc_cons[logo_shown].d;
+
+                       if (conp2->vc_top == logo_lines
+                           && conp2->vc_bottom == conp2->vc_rows)
+                               conp2->vc_top = 0;
+                       if (logo_shown == vc->vc_num) {
+                               unsigned long p, q;
+                               int i;
+
+                               p = vc->vc_softback_in;
+                               q = vc->vc_origin +
+                                   logo_lines * vc->vc_size_row;
+                               for (i = 0; i < logo_lines; i++) {
+                                       if (p == vc->vc_softback_top)
+                                               break;
+                                       if (p == vc->vc_softback_buf)
+                                               p = vc->vc_softback_end;
+                                       p -= vc->vc_size_row;
+                                       q -= vc->vc_size_row;
+                                       scr_memcpyw((u16 *) q, (u16 *) p,
+                                                   vc->vc_size_row);
+                               }
+                               vc->vc_softback_in = vc->vc_softback_curr = p;
+                               update_region(vc, vc->vc_origin,
+                                             logo_lines * vc->vc_cols);
+                       }
+                       logo_shown = FBCON_LOGO_CANSHOW;
+               }
+#endif
+               vc->vc_sw->con_cursor(vc, false /* CM_ERASE */ /* | CM_SOFTBACK 
*/);
+               con_redraw_softback(vc, /* disp, */ lines);
+               if (!vc->vc_softback_lines)
+                       vc->vc_sw->con_cursor(vc, true /* CM_DRAW */ /* | 
CM_SOFTBACK */);
+       }
+}
+
+#endif  /* CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK */
+
 static void con_scroll(struct vc_data *vc, unsigned int top,
                       unsigned int bottom, enum con_scroll dir,
                       unsigned int nr)
@@ -582,6 +873,10 @@
                nr = rows - 1;
        if (bottom > vc->vc_rows || top >= bottom || nr < 1)
                return;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       if (dir == SM_UP && vc->vc_softback_top)
+               con_softback_note (vc, top, nr);
+#endif
 
        vc_uniscr_scroll(vc, top, bottom, dir, nr);
        if (con_is_visible(vc) &&
@@ -599,6 +894,53 @@
        scr_memsetw(clear, vc->vc_video_erase_char, vc->vc_size_row * nr);
 }
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void do_update_region(struct vc_data *vc, unsigned long start, int 
count)
+{
+       unsigned int xx, yy, offset;
+       u16 *p;
+
+       unsigned long origin =
+               (start >= vc->vc_softback_buf && start < vc->vc_softback_end)
+               ? start >= vc->vc_softback_curr
+                 ? vc->vc_softback_curr
+                 : vc->vc_softback_curr
+                   - (vc->vc_softback_end - vc->vc_softback_buf)
+               : vc->vc_origin - vc->vc_softback_lines * vc->vc_size_row;
+       p = (u16 *) start;
+       offset = (start - origin) / 2;
+       xx = offset % vc->vc_cols;
+       yy = offset / vc->vc_cols;
+
+       for(;;) {
+               u16 attrib = scr_readw(p) & 0xff00;
+               int startx = xx;
+               u16 *q = p;
+               while (xx < vc->vc_cols && count) {
+                       if (attrib != (scr_readw(p) & 0xff00)) {
+                               if (p > q)
+                                       vc->vc_sw->con_putcs(vc, q, p-q, yy, 
startx);
+                               startx = xx;
+                               q = p;
+                               attrib = scr_readw(p) & 0xff00;
+                       }
+                       p++;
+                       xx++;
+                       count--;
+               }
+               if (p > q)
+                       vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+               if (p == (u16 *) vc->vc_softback_end)
+                       p = (u16 *)vc->vc_softback_buf;
+               if (p == (u16 *) vc->vc_softback_in)
+                       p = (u16 *)vc->vc_origin;
+               if (!count)
+                       break;
+               xx = 0;
+               yy++;
+       }
+}
+#else
 static void do_update_region(struct vc_data *vc, unsigned long start, int 
count)
 {
        unsigned int xx, yy, offset;
@@ -632,6 +974,7 @@
                yy++;
        }
 }
+#endif
 
 void update_region(struct vc_data *vc, unsigned long start, int count)
 {
@@ -640,7 +983,10 @@
        if (con_should_update(vc)) {
                hide_cursor(vc);
                do_update_region(vc, start, count);
-               set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+               if (!vc->vc_softback_lines)
+#endif
+                       set_cursor(vc);
        }
 }
 EXPORT_SYMBOL(update_region);
@@ -702,51 +1048,68 @@
 }
 
 /* Note: inverting the screen twice should revert to the original state */
+/* OFFSET is the offset in bytes (not 16-bit characters), COUNT is a byte
+ * count (not a character count).  */
 void invert_screen(struct vc_data *vc, int offset, int count, bool viewed)
 {
        u16 *p;
+       int row_offset, bytes_left_in_row;
+       int row_count;
 
        WARN_CONSOLE_UNLOCKED();
 
        count /= 2;
-       p = screenpos(vc, offset, viewed);
-       if (vc->vc_sw->con_invert_region) {
-               vc->vc_sw->con_invert_region(vc, p, count);
-       } else {
-               u16 *q = p;
-               int cnt = count;
-               u16 a;
-
-               if (!vc->vc_can_do_color) {
-                       while (cnt--) {
-                           a = scr_readw(q);
-                           a ^= 0x0800;
-                           scr_writew(a, q);
-                           q++;
-                       }
-               } else if (vc->vc_hi_font_mask == 0x100) {
-                       while (cnt--) {
-                               a = scr_readw(q);
-                               a = (a & 0x11ff) |
-                                  ((a & 0xe000) >> 4) |
-                                  ((a & 0x0e00) << 4);
-                               scr_writew(a, q);
-                               q++;
-                       }
+       row_offset = offset;
+       bytes_left_in_row = vc->vc_size_row - (row_offset % vc->vc_size_row);
+       row_count = (count < bytes_left_in_row / 2)
+               ? count
+               : bytes_left_in_row / 2;
+
+       while (count) {
+               p = screenpos(vc, row_offset, viewed);
+               if (vc->vc_sw->con_invert_region) {
+                       vc->vc_sw->con_invert_region(vc, p, row_count);
                } else {
-                       while (cnt--) {
-                               a = scr_readw(q);
-                               a = (a & 0x88ff) |
-                                  ((a & 0x7000) >> 4) |
-                                  ((a & 0x0700) << 4);
-                               scr_writew(a, q);
-                               q++;
+                       u16 *q = p;
+                       int cnt = row_count;
+                       u16 a;
+
+                       if (!vc->vc_can_do_color) {
+                               while (cnt--) {
+                                       a = scr_readw(q);
+                                       a ^= 0x0800;
+                                       scr_writew(a, q);
+                                       q++;
+                               }
+                       } else if (vc->vc_hi_font_mask == 0x100) {
+                               while (cnt--) {
+                                       a = scr_readw(q);
+                                       a = (a & 0x11ff) |
+                                               ((a & 0xe000) >> 4) |
+                                               ((a & 0x0e00) << 4);
+                                       scr_writew(a, q);
+                                       q++;
+                               }
+                       } else {
+                               while (cnt--) {
+                                       a = scr_readw(q);
+                                       a = (a & 0x88ff) |
+                                               ((a & 0x7000) >> 4) |
+                                               ((a & 0x0700) << 4);
+                                       scr_writew(a, q);
+                                       q++;
+                               }
                        }
                }
-       }
 
-       if (con_should_update(vc))
-               do_update_region(vc, (unsigned long) p, count);
+               if (con_should_update(vc))
+                       do_update_region(vc, (unsigned long) p, row_count);
+               row_offset += 2 * row_count;
+               count -= row_count;
+               row_count = (count >= vc->vc_cols)
+                       ? vc->vc_cols
+                       : count;
+       }
        notify_update(vc);
 }
 
@@ -875,8 +1238,17 @@
        WARN_CONSOLE_UNLOCKED();
 
        if (!con_is_visible(vc) ||
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+           (
+            !concon_set_origin (vc) &&
+            (
+#endif
            !vc->vc_sw->con_set_origin ||
-           !vc->vc_sw->con_set_origin(vc))
+           !vc->vc_sw->con_set_origin(vc)
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+                    ))
+#endif
+                                         )
                vc->vc_origin = (unsigned long)vc->vc_screenbuf;
        vc->vc_visible_origin = vc->vc_origin;
        vc->vc_scr_end = vc->vc_origin + vc->vc_screenbuf_size;
@@ -934,7 +1306,6 @@
 
        if (!vc) {
                /* strange ... */
-               /* printk("redraw_screen: tty %d not allocated ??\n", 
new_console+1); */
                return;
        }
 
@@ -949,7 +1320,6 @@
                hide_cursor(old_vc);
                if (!con_is_visible(old_vc)) {
                        save_screen(old_vc);
-                       set_origin(old_vc);
                }
                if (tty0dev)
                        sysfs_notify(&tty0dev->kobj, NULL, "active");
@@ -962,7 +1332,6 @@
                bool update;
                int old_was_color = vc->vc_can_do_color;
 
-               set_origin(vc);
                update = vc->vc_sw->con_switch(vc);
                set_palette(vc);
                /*
@@ -977,9 +1346,19 @@
                }
 
                if (update && vc->vc_mode != KD_GRAPHICS)
-                       do_update_region(vc, vc->vc_origin, 
vc->vc_screenbuf_size / 2);
+                       do_update_region(vc,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+                                        vc->vc_softback_lines
+                                        ? vc->vc_softback_curr
+                                        :
+#endif
+                                          vc->vc_origin,
+                                        vc->vc_screenbuf_size / 2);
        }
-       set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       if (!vc->vc_softback_lines)
+#endif
+               set_cursor(vc);
        if (is_switch) {
                vt_set_leds_compute_shiftstate();
                notify_update(vc);
@@ -1100,9 +1479,24 @@
                global_cursor_default = 1;
 
        vc_init(vc, 1);
+       if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows) != 0)
+               goto err_free;
        vcs_make_sysfs(currcons);
        atomic_notifier_call_chain(&vt_notifier_list, VT_ALLOCATE, &param);
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       vc->vc_softback_size = console_soft_scrollback_size;
+       err = -ENOMEM;
+       vc->vc_softback_buf =
+               (unsigned long)vzalloc(console_soft_scrollback_size);
+       if (!vc->vc_softback_buf)
+               goto err_free;
+       vc->vc_softback_in = vc->vc_softback_top = vc->vc_softback_curr =
+               vc->vc_softback_buf;
+       vc->vc_softback_lines = 0;
+       con_update_softback(vc);
+       vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
+#endif
        return 0;
 err_free:
        visual_deinit(vc);
@@ -1123,6 +1517,75 @@
        return err;
 }
 
+static int vc_copy_uniscr_to_new_area (struct vc_data *vc,
+                                      unsigned int new_cols,
+                                      unsigned int new_rows)
+{
+       unsigned int old_rows = vc->vc_rows, old_cols = vc->vc_cols;
+        uint32_t *old_uniscr_curr = vc->vc_uniscr_curr,
+               *old_uniscr_buf = vc->vc_uniscr_buf;
+       unsigned int old_uniscr_char_size = vc->vc_uniscr_char_size;
+       unsigned int new_lines;
+       unsigned int copy_cols;
+       uint32_t *dest, *src;
+       int res;
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+       unsigned int new_uniscr_rows;
+       unsigned int old_lines;
+       unsigned long tmp;
+
+       if (vc->vc_softback_in >= vc->vc_softback_top)
+               tmp = vc->vc_softback_in - vc->vc_softback_top;
+       else
+               tmp = vc->vc_softback_in - vc->vc_softback_top
+                       + vc->vc_softback_end - vc->vc_softback_buf;
+       old_lines = tmp / vc->vc_size_row + old_rows;
+
+       if ((res = vc_uniscr_alloc(vc, new_cols, new_rows)) != 0)
+               return res;
+
+       new_uniscr_rows = vc->vc_uniscr_char_size / new_cols + new_rows;
+       new_lines = min(old_lines, new_uniscr_rows);
+       copy_cols = min(old_cols, new_cols);
+
+       dest = vc->vc_uniscr_curr;
+       if (new_lines > old_rows) {
+               dest -= (new_lines - old_rows) * new_cols;
+               while (dest < vc->vc_uniscr_buf) /* Could happen twice.  */
+                       dest += vc->vc_uniscr_char_size;
+       }
+       src = old_uniscr_curr;
+       if (new_lines > old_rows) {
+               src -= (new_lines - old_rows) * old_cols;
+               while (src < old_uniscr_buf)
+                       src += old_uniscr_char_size;
+       }
+#else
+       if ((res = vc_uniscr_alloc(vc, new_cols, new_rows)) != 0)
+               return res;
+
+       new_lines = min(old_rows, new_rows);
+       copy_cols = min(old_cols, new_cols);
+       dest = vc->vc_uniscr_curr;
+       src = old_uniscr_curr;
+#endif
+       if (old_uniscr_buf) {
+               while (new_lines--) {
+                       memcpy(dest, src, copy_cols * sizeof(uint32_t));
+                       if (new_cols > old_cols)
+                               memset32(dest + old_cols, ' ',
+                                        new_cols - old_cols);
+                       UNISCR_PLUS(dest, new_cols);
+                       src += old_cols;
+                       if (src >= old_uniscr_buf + old_uniscr_char_size)
+                               src -= old_uniscr_char_size;
+               }
+               kvfree(old_uniscr_buf);
+       }
+       return 0;
+}
+
 /**
  * vc_do_resize - resizing method for the tty
  * @tty: tty being resized
@@ -1146,8 +1609,15 @@
        unsigned int old_rows, old_row_size, first_copied_row;
        unsigned int new_cols, new_rows, new_row_size, new_screen_size;
        unsigned short *oldscreen, *newscreen;
-       u32 **new_uniscr = NULL;
-
+       uint32_t *old_uniscr = vc->vc_uniscr_buf;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       unsigned short *d;
+       unsigned long old_softback_buf, new_softback_buf, tmp;
+       unsigned long old_softback_end, new_softback_end;
+       unsigned int old_scrolled_rows, new_scrolled_rows;
+       unsigned int count, copied_scrolled_rows;
+       void *temp_new_softback_buf;
+#endif
        WARN_CONSOLE_UNLOCKED();
 
        if (cols > VC_MAXCOL || lines > VC_MAXROW)
@@ -1184,14 +1654,6 @@
        if (!newscreen)
                return -ENOMEM;
 
-       if (vc->vc_uni_lines) {
-               new_uniscr = vc_uniscr_alloc(new_cols, new_rows);
-               if (!new_uniscr) {
-                       kfree(newscreen);
-                       return -ENOMEM;
-               }
-       }
-
        if (vc_is_sel(vc))
                clear_selection();
 
@@ -1201,15 +1663,11 @@
        err = resize_screen(vc, new_cols, new_rows, from_user);
        if (err) {
                kfree(newscreen);
-               vc_uniscr_free(new_uniscr);
+               vc_uniscr_free(vc);
+               vc->vc_uniscr_buf = old_uniscr;
                return err;
        }
 
-       vc->vc_rows = new_rows;
-       vc->vc_cols = new_cols;
-       vc->vc_size_row = new_row_size;
-       vc->vc_screenbuf_size = new_screen_size;
-
        rlth = min(old_row_size, new_row_size);
        rrem = new_row_size - rlth;
        old_origin = vc->vc_origin;
@@ -1217,13 +1675,27 @@
        new_scr_end = new_origin + new_screen_size;
 
        if (vc->state.y > new_rows) {
-               if (old_rows - vc->state.y < new_rows) {
+               if (old_rows - new_rows < vc->vc_top + vc->state.y) {
+                       if (old_rows - new_rows > vc->vc_top) {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+                               con_softback_note(vc, vc->vc_top,
+                                                 old_rows - new_rows - 
vc->vc_top);
+#endif
+                               vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+                                                old_rows - new_rows - 
vc->vc_top);
+                       }
                        /*
                         * Cursor near the bottom, copy contents from the
                         * bottom of buffer
                         */
                        first_copied_row = (old_rows - new_rows);
                } else {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+                       con_softback_note(vc, vc->vc_top,
+                                         vc->state.y - new_rows/2);
+#endif
+                       vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+                                        vc->state.y - new_rows/2);
                        /*
                         * Cursor is in no man's land, copy 1/2 screenful
                         * from the top and bottom of cursor position
@@ -1235,10 +1707,38 @@
                first_copied_row = 0;
        end = old_origin + old_row_size * min(old_rows, new_rows);
 
-       vc_uniscr_copy_area(new_uniscr, new_cols, new_rows,
-                           vc->vc_uni_lines, rlth/2, first_copied_row,
-                           min(old_rows, new_rows));
-       vc_uniscr_set(vc, new_uniscr);
+       if ((err = vc_copy_uniscr_to_new_area(vc, new_cols, new_rows)) != 0)
+               return err;
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       concon_set_origin(vc);
+       old_softback_buf = vc->vc_softback_buf;
+       old_softback_end = vc->vc_softback_end;
+       vc->vc_softback_size = console_soft_scrollback_size;
+       temp_new_softback_buf = vzalloc(console_soft_scrollback_size);
+       new_softback_buf = (unsigned long)temp_new_softback_buf;
+       if (!new_softback_buf)
+               return -ENOMEM;
+       new_softback_end = new_softback_buf + console_soft_scrollback_size;
+       d = (unsigned short *)new_softback_buf;
+       while (d != (u16 *)new_softback_end) {
+               scr_writew (0x0020, d);
+               d++;
+       }
+       if (vc->vc_softback_top <= vc->vc_softback_in)
+               tmp = vc->vc_softback_in - vc->vc_softback_top;
+       else
+               tmp = vc->vc_softback_in - vc->vc_softback_top
+                       + vc->vc_softback_end - vc->vc_softback_buf;
+       old_scrolled_rows = tmp / vc->vc_size_row;
+       new_scrolled_rows = console_soft_scrollback_size / new_row_size;
+       copied_scrolled_rows = min(old_scrolled_rows, new_scrolled_rows);
+#endif
+
+       vc->vc_cols = new_cols;
+       vc->vc_rows = new_rows;
+       vc->vc_size_row = new_row_size;
+       vc->vc_screenbuf_size = new_screen_size;
 
        update_attr(vc);
 
@@ -1254,11 +1754,54 @@
        if (new_scr_end > new_origin)
                scr_memsetw((void *)new_origin, vc->vc_video_erase_char,
                            new_scr_end - new_origin);
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       new_origin = new_softback_buf;
+       if (copied_scrolled_rows) {
+               old_origin = vc->vc_softback_in
+                       - copied_scrolled_rows * old_row_size;
+               if (old_origin < vc->vc_softback_buf)
+                       old_origin += vc->vc_softback_end
+                               - vc->vc_softback_buf;
+               count = copied_scrolled_rows;
+
+               while (count--) {
+                       scr_memcpyw((unsigned short *) new_origin,
+                                   (unsigned short *) old_origin, rlth);
+                       if (rrem)
+                               scr_memsetw((void *)(new_origin + rlth),
+                                           vc->vc_video_erase_char, rrem);
+                       old_origin += old_row_size;
+                       if (old_origin >= old_softback_end)
+                               old_origin -= old_softback_end
+                                       - old_softback_buf;
+                       new_origin += new_row_size;
+               }
+       }
+#endif
        oldscreen = vc->vc_screenbuf;
        vc->vc_screenbuf = newscreen;
        vc->vc_screenbuf_size = new_screen_size;
        set_origin(vc);
        kfree(oldscreen);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       vc->vc_softback_buf = new_softback_buf;
+       vc->vc_softback_end = new_softback_buf
+               + new_scrolled_rows * new_row_size;
+       if (copied_scrolled_rows) {
+               if (new_origin >= vc->vc_softback_end)
+                       new_origin -= vc->vc_softback_end - vc->vc_softback_buf;
+               vc->vc_softback_in = new_origin;
+       } else
+               vc->vc_softback_in = new_softback_buf;
+       vc->vc_softback_top = new_softback_buf;
+       if (copied_scrolled_rows
+           && (new_origin == new_softback_buf))
+               vc->vc_softback_top += new_row_size;
+       vc->vc_softback_curr = vc->vc_softback_in;
+       vc->vc_softback_lines = 0; /* Probably redundant. */
+       kvfree((void *)old_softback_buf);
+#endif
 
        /* do part of a reset_terminal() */
        vc->vc_top = 0;
@@ -1338,8 +1881,8 @@
                visual_deinit(vc);
                con_free_unimap(vc);
                put_pid(vc->vt_pid);
-               vc_uniscr_set(vc, NULL);
                kfree(vc->vc_screenbuf);
+               vc->vc_uniscr_buf = NULL;
                vc_cons[currcons].d = NULL;
                if (vc->vc_saved_screen != NULL) {
                        kfree(vc->vc_saved_screen);
@@ -3205,6 +3748,12 @@
 
        param.vc = vc;
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       /* Undo any soft scrolling - <Alt><Fn> and <Shift><PgUp/Down> do
+          not pass through this function.  */
+       concon_set_origin (vc);
+#endif
+
        while (!tty->flow.stopped && count) {
                u8 orig = *buf;
                buf++;
@@ -3418,7 +3967,11 @@
        }
        if (cnt && con_is_visible(vc))
                vc->vc_sw->con_putcs(vc, start, cnt, vc->state.y, start_x);
-       set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       if (!vc->vc_softback_lines)
+#endif
+               set_cursor(vc);
+
        notify_update(vc);
 
 quit:
@@ -3632,7 +4185,12 @@
                return;
 
        guard(console_lock)();
-       set_cursor(vc);
+       if (vc
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+           && !vc->vc_softback_lines
+#endif
+               )
+               set_cursor(vc);
 }
 
 /*
@@ -3651,6 +4209,14 @@
 
        vc = vc_cons[currcons].d;
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       con_update_softback(vc);
+       if (!vc->vc_uniscr_buf)
+               ret = vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
+       if (ret)
+               return ret;
+#endif
+
        /* Still being freed */
        if (vc->port.tty)
                return -ERESTARTSYS;
@@ -3791,6 +4357,7 @@
                /* Assuming vc->vc_{cols,rows,screenbuf_size} are sane here. */
                vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_NOWAIT);
                vc_init(vc, currcons || !vc->vc_sw->con_save_screen);
+               vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
        }
        currcons = fg_console = 0;
        master_display_fg = vc = vc_cons[currcons].d;
@@ -4686,7 +5253,10 @@
        if (console_blank_hook)
                console_blank_hook(0);
        set_palette(vc);
-       set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       if (!vc->vc_softback_lines)
+#endif
+               set_cursor(vc);
        vt_event_post(VT_EVENT_UNBLANK, vc->vc_num, vc->vc_num);
        notify_update(vc);
 }
@@ -4987,12 +5557,17 @@
 
 u32 screen_glyph_unicode(const struct vc_data *vc, int n)
 {
-       u32 **uni_lines = vc->vc_uni_lines;
+       int y = n / vc->vc_cols, x = n % vc->vc_cols;
+       uint32_t *ln = vc->vc_uniscr_curr;
 
-       if (uni_lines)
-               return uni_lines[n / vc->vc_cols][n % vc->vc_cols];
-
-       return inverse_translate(vc, screen_glyph(vc, n * 2), true);
+       if (vc->vc_uniscr_curr) {
+               UNISCR_PLUS(ln, y * vc->vc_cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+               UNISCR_MINUS(ln, vc->vc_softback_lines * vc->vc_cols);
+#endif
+               return ln[x];
+       }
+       return inverse_translate(vc, screen_glyph(vc, n * 2), 1);
 }
 EXPORT_SYMBOL_GPL(screen_glyph_unicode);
 
--- a/drivers/tty/vt/selection.c        2026-02-22 17:41:32.501216710 +0000
+++ b/drivers/tty/vt/selection.c        2026-02-22 15:18:55.370750965 +0000
@@ -42,21 +42,20 @@
        char *buffer;
        unsigned int buf_len;
        volatile int start;                     /* cleared by clear_selection */
-       int end;
+       int end; /* Note: this points to the last char, not to after it. */
+       bool mouse_at_e;
+       unsigned short prev_x;
 } vc_sel = {
        .lock = __MUTEX_INITIALIZER(vc_sel.lock),
        .start = -1,
 };
 
+static bool unicode;
+static unsigned int size_row;
+
 /* clear_selection, highlight and highlight_pointer can be called
    from interrupt (via scrollback/front) */
 
-/* set reverse video on characters s-e of console with selection. */
-static inline void highlight(const int s, const int e)
-{
-       invert_screen(vc_sel.cons, s, e-s+2, true);
-}
-
 /* use complementary color to show the pointer */
 static inline void highlight_pointer(const int where)
 {
@@ -64,7 +63,7 @@
 }
 
 static u32
-sel_pos(int n, bool unicode)
+sel_pos(int n)
 {
        if (unicode)
                return screen_glyph_unicode(vc_sel.cons, n / 2);
@@ -72,6 +71,61 @@
                        false);
 }
 
+static int last_char_pos(int line_end, int limit)
+{
+       int pos = line_end;
+       while ((pos >= limit) && is_space_on_vt(sel_pos(pos)))
+               pos -= 2;
+       return pos;
+}
+
+/* set reverse video on characters s-e of console with selection. */
+static void highlight(const int s, const int e)
+{
+       bool mouse_at_e = (vc_sel.start != -1) && vc_sel.mouse_at_e;
+       int bol = s;
+       int eol = rounddown(s, size_row) + size_row - 2;
+       int pos;
+
+       if (mouse_at_e) {
+               int prev_e = s - 2; /* For the + 2 in vc_do_selection. */
+               int low_mouse_beg = rounddown(prev_e, size_row);
+               int high_mouse_beg = rounddown(e, size_row);
+               if (low_mouse_beg != high_mouse_beg) {
+                       /* Unhighlight any trailing space on the previous line
+                        * (moving down), or rehighlight it (moving up). */
+                       int low_mouse_end = low_mouse_beg + size_row - 2;
+
+                       pos = last_char_pos(low_mouse_end,
+                                           max(vc_sel.start, low_mouse_beg)) + 
2;
+                       if (prev_e >= pos)
+                               invert_screen(vc_sel.cons, pos,
+                                             prev_e - pos + 2, true);
+               }
+       }
+
+       while (eol <  e) {
+               if ((pos = last_char_pos(eol, bol)) >= bol)
+                       invert_screen(vc_sel.cons, bol, pos - bol + 2, true);
+               bol = eol + 2;
+               eol += size_row;
+       }
+       /* Last line: Firstly, are we (un)highlighting the entire selection? */
+       if ((vc_sel.start == -1) || ((s == vc_sel.start && e == vc_sel.end)) ||
+           /* .... or trailing space at the end of the selection? */
+           (eol >= vc_sel.end))
+               /* YES: so highlight the entire last line up to E. */
+               invert_screen(vc_sel.cons, bol, e - bol + 2, true);
+       else {
+               /* NO: Don't highlight the trailing spaces. */
+               pos = last_char_pos(eol, bol);
+               if (pos > e)
+                       pos = e;
+               if (pos >= bol)
+                       invert_screen(vc_sel.cons, bol, pos - bol + 2, true);
+       }
+}
+
 /**
  * clear_selection - remove current selection
  *
@@ -207,15 +261,23 @@
        return set_selection_kernel(&v, tty);
 }
 
-static int vc_selection_store_chars(struct vc_data *vc, bool unicode)
+static int vc_selection_store_chars(void)
 {
        char *bp, *obp;
        unsigned int i;
 
        /* Allocate a new buffer before freeing the old one ... */
        /* chars can take up to 4 bytes with unicode */
-       bp = kmalloc_array((vc_sel.end - vc_sel.start) / 2 + 1, unicode ? 4 : 1,
-                          GFP_KERNEL | __GFP_NOWARN);
+       if (vc_sel.end >= vc_sel.start)
+               bp = kmalloc_array((vc_sel.end - vc_sel.start) / 2 + 1,
+                                  unicode ? 4 : 1,
+                                  GFP_KERNEL | __GFP_NOWARN);
+       else {               /* Happens with TIOCL_WORDSEL on a blank line. */
+               kfree(vc_sel.buffer);
+               vc_sel.buffer = NULL;
+               vc_sel.buf_len = 0;
+               return 0;
+       }
        if (!bp) {
                printk(KERN_WARNING "selection: kmalloc() failed\n");
                clear_selection();
@@ -226,16 +288,17 @@
 
        obp = bp;
        for (i = vc_sel.start; i <= vc_sel.end; i += 2) {
-               u32 c = sel_pos(i, unicode);
+               u32 c = sel_pos(i);
                if (unicode)
                        bp += store_utf8(c, bp);
                else
                        *bp++ = c;
                if (!is_space_on_vt(c))
                        obp = bp;
-               if (!((i + 2) % vc->vc_size_row)) {
+               if (!((i + 2) % size_row) &&
+                   (i + 2) < vc_sel.end) {
                        /* strip trailing blanks from line and add newline,
-                          unless non-space at end of line. */
+                          unless non-space at end of line or on last line. */
                        if (obp != bp) {
                                bp = obp;
                                *bp++ = '\r';
@@ -248,11 +311,10 @@
        return 0;
 }
 
-static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps,
-               int pe)
+static int vc_do_selection(unsigned short mode, int ps, int pe)
 {
        int new_sel_start, new_sel_end, spc;
-       bool unicode = vt_do_kdgkbmode(fg_console) == K_UNICODE;
+       int pe_line_start, term_space;
 
        switch (mode) {
        case TIOCL_SELCHAR:     /* character-by-character selection */
@@ -260,30 +322,37 @@
                new_sel_end = pe;
                break;
        case TIOCL_SELWORD:     /* word-by-word selection */
-               spc = is_space_on_vt(sel_pos(ps, unicode));
+               spc = is_space_on_vt(sel_pos(ps));
                for (new_sel_start = ps; ; ps -= 2) {
-                       if ((spc && !is_space_on_vt(sel_pos(ps, unicode))) ||
-                           (!spc && !inword(sel_pos(ps, unicode))))
+                       if ((spc && !is_space_on_vt(sel_pos(ps))) ||
+                           (!spc && !inword(sel_pos(ps))))
                                break;
                        new_sel_start = ps;
-                       if (!(ps % vc->vc_size_row))
+                       if (!(ps % size_row))
                                break;
                }
 
-               spc = is_space_on_vt(sel_pos(pe, unicode));
+               spc = is_space_on_vt(sel_pos(pe));
+               if (spc) term_space = pe;
                for (new_sel_end = pe; ; pe += 2) {
-                       if ((spc && !is_space_on_vt(sel_pos(pe, unicode))) ||
-                           (!spc && !inword(sel_pos(pe, unicode))))
+                       if ((spc && !is_space_on_vt(sel_pos(pe))) ||
+                           (!spc && !inword(sel_pos(pe))))
                                break;
                        new_sel_end = pe;
-                       if (!((pe + 2) % vc->vc_size_row))
+                       if (!((pe + 2) % size_row))
                                break;
                }
+               /* Don't highlight trailing space after the mouse on the last
+                * line. */
+               if (spc &&
+                   !((pe + 2) % size_row))
+                       new_sel_end = last_char_pos(term_space, ps);
                break;
        case TIOCL_SELLINE:     /* line-by-line selection */
-               new_sel_start = rounddown(ps, vc->vc_size_row);
-               new_sel_end = rounddown(pe, vc->vc_size_row) +
-                       vc->vc_size_row - 2;
+               new_sel_start = rounddown(ps, size_row);
+               pe_line_start = rounddown(pe, size_row);
+               new_sel_end = last_char_pos(pe_line_start + size_row - 2,
+                                           new_sel_start); /* Can be < BOL. */
                break;
        case TIOCL_SELPOINTER:
                highlight_pointer(pe);
@@ -295,17 +364,6 @@
        /* remove the pointer */
        highlight_pointer(-1);
 
-       /* select to end of line if on trailing space */
-       if (new_sel_end > new_sel_start &&
-               !atedge(new_sel_end, vc->vc_size_row) &&
-               is_space_on_vt(sel_pos(new_sel_end, unicode))) {
-               for (pe = new_sel_end + 2; ; pe += 2)
-                       if (!is_space_on_vt(sel_pos(pe, unicode)) ||
-                           atedge(pe, vc->vc_size_row))
-                               break;
-               if (is_space_on_vt(sel_pos(pe, unicode)))
-                       new_sel_end = pe;
-       }
        if (vc_sel.start == -1) /* no current selection */
                highlight(new_sel_start, new_sel_end);
        else if (new_sel_start == vc_sel.start)
@@ -324,22 +382,32 @@
                else                            /* contract from left */
                        highlight(vc_sel.start, new_sel_start - 2);
        }
-       else    /* some other case; start selection from scratch */
+       else    /* The mouse has been moved through the starting point or we
+                * have word or line selection. */
        {
-               clear_selection();
+               /* Restore vc_sel.mouse_at_e to its previous value to
+                * unhighlight the previous selection. */
+               vc_sel.mouse_at_e = !vc_sel.mouse_at_e;
+               highlight(vc_sel.start, vc_sel.end);
+               vc_sel.mouse_at_e = !vc_sel.mouse_at_e;
+
+               vc_sel.start = new_sel_start;
+               vc_sel.end = new_sel_end;
                highlight(new_sel_start, new_sel_end);
        }
        vc_sel.start = new_sel_start;
        vc_sel.end = new_sel_end;
 
-       return vc_selection_store_chars(vc, unicode);
+       return vc_selection_store_chars();
 }
 
 static int vc_selection(struct vc_data *vc, struct tiocl_selection *v,
                struct tty_struct *tty)
 {
        int ps, pe;
+       int res;
 
+       size_row = vc->vc_size_row;
        poke_blanked_console();
 
        if (v->sel_mode == TIOCL_SELCLEAR) {
@@ -348,6 +416,20 @@
                return 0;
        }
 
+       /* Heuristically correct any strange values from GPM.  When the mouse
+        * is dragged off the left hand edge, GPM reports it as being at the
+        * end of the previous line.  When it is dragged off the bottom edge,
+        * it is reported as being at the end of the last line. */
+       if ((v->xe == vc->vc_cols) && (vc_sel.start != -1)) {
+               if (v->ye == vc->vc_rows) {
+                       if (vc_sel.prev_x <= (vc->vc_cols - 20))
+                               v->xe = vc_sel.prev_x;
+               } else if (vc_sel.prev_x <= (vc->vc_cols >> 1)) {
+                       v->xe = 1;
+                       v->ye++;
+               }
+       }
+
        v->xs = min_t(u16, v->xs - 1, vc->vc_cols - 1);
        v->ys = min_t(u16, v->ys - 1, vc->vc_rows - 1);
        v->xe = min_t(u16, v->xe - 1, vc->vc_cols - 1);
@@ -359,17 +441,27 @@
                return 0;
        }
 
-       ps = v->ys * vc->vc_size_row + (v->xs << 1);
-       pe = v->ye * vc->vc_size_row + (v->xe << 1);
-       if (ps > pe)    /* make vc_sel.start <= vc_sel.end */
+       vc_sel.mouse_at_e = true;
+       ps = v->ys * size_row + (v->xs << 1);
+       pe = v->ye * size_row + (v->xe << 1);
+       if (ps > pe) {  /* make vc_sel.start <= vc_sel.end */
                swap(ps, pe);
+               vc_sel.mouse_at_e = false;
+       }
 
        if (vc_sel.cons != vc) {
                clear_selection();
                vc_sel.cons = vc;
        }
 
-       return vc_do_selection(vc, v->sel_mode, ps, pe);
+       unicode = vt_do_kdgkbmode(fg_console) == K_UNICODE;
+
+       res = vc_do_selection(v->sel_mode, ps, pe);
+       if ((vc_sel.start != -1) &&
+           ((v->sel_mode == TIOCL_SELCHAR) || (v->sel_mode == TIOCL_SELWORD) ||
+            (v->sel_mode == TIOCL_SELLINE)))
+               vc_sel.prev_x = v->xe + 1; /* Convert back to 1-based. */
+       return res;
 }
 
 int set_selection_kernel(struct tiocl_selection *v, struct tty_struct *tty)
--- a/drivers/md/md-autodetect.c        2026-02-22 17:41:31.849844491 +0000
+++ b/drivers/md/md-autodetect.c        2026-02-22 15:18:55.366751006 +0000
@@ -29,6 +29,8 @@
 
 static struct md_setup_args {
        int minor;
+       int major_version;
+       int minor_version;
        int partitioned;
        int level;
        int chunk;
@@ -63,6 +65,16 @@
        char *pername = "";
        char *str1;
        int ent;
+       int i;
+       static const struct {
+               char *metadata;
+               int major_version;
+               int minor_version;
+       } metadata_table[] =
+               {{"0.90", 0, 90},
+                {"1.0", 1, 0},
+                {"1.1", 1, 1},
+                {"1.2", 1, 2}};
 
        if (*str == 'd') {
                partitioned = 1;
@@ -84,6 +96,24 @@
                printk(KERN_WARNING "md: md=%s%d - too many md 
initialisations\n", partitioned?"d":"", minor);
                return 0;
        }
+
+       md_setup_args[ent].major_version = 0;
+       md_setup_args[ent].minor_version = 90;
+       str = strchr(str, ',');
+       if (str)
+       {
+               *str = 0;
+               for (i = 0; i < ARRAY_SIZE(metadata_table); i++)
+                       if (!strncmp(str1, metadata_table[i].metadata, 5)) {
+                               md_setup_args[ent].major_version = 
metadata_table[i].major_version;
+                               md_setup_args[ent].minor_version = 
metadata_table[i].minor_version;
+                               str1 = str + 1;
+                               break;
+                       }
+               *str = ',';
+               str = str1;
+       }
+
        if (ent >= md_setup_ents)
                md_setup_ents++;
        switch (get_option(&str, &level)) {     /* RAID level */
@@ -187,6 +217,8 @@
                goto out_unlock;
        }
 
+       ainfo.major_version = args->major_version;
+       ainfo.minor_version = args->minor_version;
        if (args->level != LEVEL_NONE) {
                /* non-persistent */
                ainfo.level = args->level;

Reply via email to