dri-devel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* vt: Enlarge the framebuffer glyph size from 16 to 32 bits
@ 2026-08-27 18:38 Alan Mackenzie
  2026-08-27 18:42 ` [Patch 1/9]: Make consolemap.c handle Unicode planes outside BMP Alan Mackenzie
                   ` (9 more replies)
  0 siblings, 10 replies; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-27 18:38 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, Thomas Zimmermann, linux-fbdev,
	dri-devel
  Cc: acm

[-- Attachment #1: Type: text/plain, Size: 23433 bytes --]

vt: Enlarge the framebuffer glyph size from 16 to 32 bits.

This allows:
  o - More than 256/512 distinct half-width glyphs (in fact, up to 2^21).
  o - Unicode code points >0xffff (outside the Basic Multilingual Plane).
  o - Coloured glyphs (as before).

The patch doesn't improve the handling of full-width glyphs.

To maximise backward compatibility:
  o - The enlarged glyph size must be positively configured in the kernel by
    enabling CONFIG_FB_GLYPH_21BIT.  Without this, the glyph size remains 16
    bits as before.
  o - Existing console fonts can still be loaded and used.
  o - The console keyboard handling remains unchanged.

The format of PSFU font files remains unchanged, and the utility programs
txt2psf and psf2txt (from package psftools) work unchanged on the new fonts.

An enhanced protocol is needed for the up-/downloading of fonts, and thus
enhanced versions of the utility programs setfont and showconsolefont.

The new kernel requires two new ioctl codes, which have been provisionally
given these values in include/uapi/linux/kd.h:
  o - #define GIO_UNIMAP21 0x4B6E
  o - #define PIO_UNIMAP21 0x4B6F.
These are like GIO_UNIMAP and PIO_UNIMAP but work with 21-bit code point and
glyph numbers.  They are used to get and put the new font format, and are
needed by setfont and showconsolefont.

Some pertinent code has not yet been amended for the new feature.  In
particular, the files fbcon_cw.c, fbcon_ud.c, fbcon_ccw.c (which display
rotated output on the screen) still need amendment.

For the time being, CONFIG_A11Y_BRAILLE_CONSOLE and CONFIG_SPEAKUP have been
disabled when CONFIG_FB_GLYPH_21BIT is set, until it becomes clear how to
integrate them.

The new code has been tested only on an AMD64 system.  In particular, it
hasn't been tested on a big-endian system.

For the avoidance of doubt, no LLM has been used in the preparation of this
change.

#########################################################################

Building the Kernel
-------------------

Apply the patch series.

The new option CONFIG_FB_GLYPH_21BIT can be found in the make menuconfig tree
under Device Drivers/Graphics support/Direct Rendering Manager/Supported DRM
clients/Enable legacy fbdev support .../Enable 21-bit glyphs ....  Enable
CONFIG_DRM_FBDEV_EMULATION and CONFIG_FB_GLYPH_21BIT.

[ Note: the kernel can alternatively be built with CONFIG_FB_GLYPH_21BIT
unset, in which case it uses the well known 16-bit glyphs, essentially
unchanged from the current kernel. ]

Also enable CONFIG_HEADERS_INSTALL, so that the user side version of kd.h will
get built in usr/include/linux/kd.h.  This is needed by the enhanced version
of the kbd package.  After building, ensure it gets copied to where the
compiler will find it.

Build the kernel in the usual way, and install it into your boot loader.

#########################################################################

Testing
-------

To try out the new features, you will need a console font with glyphs outside
the Basic Multilingual Plane, and an enhanced setfont program (which uses one
of the new ioctl codes above) to load it with.

The font latn-16-unifont-base+upper.psfu.gz attached to this post has 11,240
glyphs, and its source code can be inspected by gunzipping it and feeding it
through the program psf2txt from the package psftools.  This font was hacked
together from the standard fonts lat<n>-16 and parts of GNU Unifont.  Examples
of code points outside the Basic Multilingual Plane are U+1fbf0 to U+1fbf9,
variants of the decimal digits.

To enter a Unicode code point on the keyboard, the partial keymap
/usr/share/keymaps/i386/include/keypad.map.gz is useful.  With it, one can
hold down the AltGr key and type in the codepoint in hex using 16 of the
keypad keys.

An enhanced version of setfont, built for AMD64, is also attached.  The source
code for this, in the form of a unified diff from the commit with tag v2.9.0
in the repository
git://git.kernel.org/pub/scm/linux/kernel/git/legion/kbd.git, is at the end of
this post.  Before building this version of kbd, ensure that the kernel has
been build with CONFIG_HEADERS_INSTALL set, and that the newly generated
usr/include/linux/kd.h has been copied to somewhere the compiler will find it.

#########################################################################

Kernel Patches
--------------

The series of patches applies cleanly to:

commit 8b0b29fdcb47907ae0296b8fe829e918e05e300f
(HEAD -> tty-testing, origin/tty-testing, origin/tty-next)
Author: Karl Mehltretter <kmehltretter@gmail.com>
Date:   Fri Jul 31 20:18:44 2026 +0200rts[] lifetimert in imx_uart_ports[]ntryt)gnamic")

in branch origin/testing of repository
git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git.

This series of patches is essentially a single atomic patch; no partial
application of them will result in a useful kernel.  The 3,500 line patch from
git diff has been divided according to theme, although this is approximate
since not all larger diff hunks have been split into "components".

These files get changed by the patches:
drivers/accessibility/Kconfig
drivers/accessibility/speakup/Kconfig
drivers/gpu/drm/clients/Kconfig

drivers/tty/vt/consolemap.c
drivers/tty/vt/keyboard.c
drivers/tty/vt/selection.c
drivers/tty/vt/vc_screen.c
drivers/tty/vt/vt.c
drivers/tty/vt/vt_ioctl.c

drivers/video/fbdev/core/bitblit.c
drivers/video/fbdev/core/fbcon.c
drivers/video/fbdev/core/fbcon.h

include/linux/console_struct.h
include/linux/consolemap.h
include/linux/selection.h
include/linux/vt_buffer.h
include/linux/vt_kern.h
include/uapi/linux/kd.h

The patches are as follows:
(i) 1 "Make consolemap.c handle Unicode planes outside BMP" contains the
  enhancements to drivers/tty/vt/consolemap.c to handle 21 bit codepoints
  (outside the BMP).
(ii) 2 "Glyph size: Use GLYPH_SZ/HW rather than hardcoded 2, 1" substitutes
  the #defines GLYPH_SZ and GLYPH_HW, the size of a glyph in bytes and half
  words, for the previously hard coded and implicit values of 2 and 1.
(iii) 3 "Replace scr_readw/writew by scr_readg/writeg, etc" is the replacement
  of the macros scr_readw/writew with scr_readg, scr_writeg,
  scr_readg_plusplus, scr_writeg_plusplus (where "g" stands for "glyph").
  Also one or two other similar macros are replaced.
(iv) 4 "Amend internal manipulation of glyph structure" contains amendments to
  the manipulations of the internal structure of the now 32 bit glyph from the
  previous 16 bit glyph.
(v) 5 "Amend three Kconfig files" has the changes to three Kconfig files.  One
  of these introduces CONFIG_FB_GLYPH_21BIT, the other two mask accessibility
  features of the terminal until it becomes clear how to integrate them.
(vi) 6 "Use u32 and typedef u1632 to handle whole glyphs" introduces the
  typedef u1632 (which is either u16 or u32 depending on
  CONFIG_FB_GLYPH_21BIT) and contains the changes for its use, and needed
  changes from u16 to u32.
(vii) 7 "Handle up to 2^21 glyphs, rather than 256/512" handles the change in
  the maximum number of glyphs from 256/512 to 0x110000.
(viii) 8 "Enhancements to the VT ioctl interface" contains the changes to
  drivers/tty/vt/vt_ioctl.c including the handling of GIO_UNIMAP21 and
  PIO_UNIMAP21, and the conversion of userspace data (whether 16 or 21 bit) to
  the new internal 21 bit format.
(ix) 9 "Misc changes, e.g. to #include directives" Miscellaneous changes which
  didn't fit anywhere else, including reordering some #include directives in
  the .c files.

#########################################################################

Enhancements to git://git.kernel.org/pub/scm/linux/kernel/git/legion/kbd.git,
-----------------------------------------------------------------------------
tag v2.9.0, for setfont.
------------------------

diff --git a/src/include/kbd/kfont.h b/src/include/kbd/kfont.h
index 67ad917..570b5f0 100644
--- a/src/include/kbd/kfont.h
+++ b/src/include/kbd/kfont.h
@@ -211,7 +211,9 @@ int kfont_get_unicodemap(struct kfont_context *ctx, int consolefd,
 	KBD_ATTR_NONNULL(1, 3);
 
 int kfont_put_unicodemap(struct kfont_context *ctx, int consolefd,
-		struct unimapinit *ui, struct unimapdesc *ud)
+			 struct unimapinit *ui,
+			 int bit21,
+			 struct unimapdesc *ud, struct unimapdesc21 *ud21)
 	KBD_ATTR_NONNULL(1);
 
 /* setfont.c */
@@ -269,7 +271,8 @@ int kfont_read_psffont(struct kfont_context *ctx,
 		unsigned char **fontbufp, unsigned int *fontszp,
 		unsigned int *fontwidthp, unsigned int *fontheightp,
 		unsigned int *fontlenp, unsigned int fontpos0,
-		struct unicode_list **uclistheadsp)
+		       struct unicode_list **uclistheadsp,
+		       int *bit21)
 	KBD_ATTR_NONNULL(1);
 
 int kfont_write_psffont(struct kfont_context *ctx,
diff --git a/src/libkbdfile/kbdfile.c b/src/libkbdfile/kbdfile.c
index 5b1ee6d..50b117f 100644
--- a/src/libkbdfile/kbdfile.c
+++ b/src/libkbdfile/kbdfile.c
@@ -337,7 +337,8 @@ static int
 findfile_in_dir(const char *fnam, const char *dir, const int recdepth, const char *const *suf, struct kbdfile *fp)
 {
 	char errbuf[200];
-	char *ff, *fdir, *path;
+	const char *ff;
+	char *fdir, *path;
 	int rc = 1, secondpass = 0;
 	size_t dir_len;
 
diff --git a/src/libkfont/kdmapop.c b/src/libkfont/kdmapop.c
index b868f7b..5be0b88 100644
--- a/src/libkfont/kdmapop.c
+++ b/src/libkfont/kdmapop.c
@@ -179,7 +179,9 @@ kfont_get_unicodemap(struct kfont_context *ctx, int fd, struct unimapdesc *ud0)
 }
 
 int
-kfont_put_unicodemap(struct kfont_context *ctx, int fd, struct unimapinit *ui, struct unimapdesc *ud)
+kfont_put_unicodemap(struct kfont_context *ctx, int fd, struct unimapinit *ui,
+		     int bit21,
+		     struct unimapdesc *ud, struct unimapdesc21 *ud21)
 {
 	struct unimapinit advice;
 
@@ -202,10 +204,26 @@ again:
 			KFONT_ERR(ctx, "ioctl(PIO_UNIMAPCLR): %m");
 		return -1;
 	}
-	if (ud == NULL)
+	if ((bit21 && !ud21) ||
+	    (!bit21 && !ud))
 		return 0;
 
-	if (ioctl(fd, PIO_UNIMAP, ud)) {
+	if (bit21) {
+		if (ioctl(fd, PIO_UNIMAP21, ud21)) {
+			if (errno == ENOMEM && advice.advised_hashlevel < 100) {
+				advice.advised_hashlevel++;
+				goto again;
+			}
+#ifdef ENOIOCTLCMD
+			if (errno == ENOIOCTLCMD)
+				errno = EINVAL; /* 21-bit Unicode table
+						 * invalid for older kernel */
+#endif
+			KFONT_ERR(ctx, "ioctl(PIO_UNIMAP21): %m");
+			return -1;
+		}
+	}
+	else if (ioctl(fd, PIO_UNIMAP, ud)) {
 		if (errno == ENOMEM && advice.advised_hashlevel < 100) {
 			advice.advised_hashlevel++;
 			goto again;
diff --git a/src/libkfont/loadunimap.c b/src/libkfont/loadunimap.c
index e2cfa05..06015c5 100644
--- a/src/libkfont/loadunimap.c
+++ b/src/libkfont/loadunimap.c
@@ -281,7 +281,8 @@ kfont_load_unicodemap(struct kfont_context *ctx, int fd, const char *tblname)
 	} else {
 		descr.entry_ct = listct;
 		descr.entries  = list;
-		if ((ret = kfont_put_unicodemap(ctx, fd, NULL, &descr)) < 0)
+		if ((ret = kfont_put_unicodemap(ctx, fd, NULL, 0, &descr,
+						NULL)) < 0)
 			goto err;
 		listct = 0;
 	}
diff --git a/src/libkfont/psffontop.c b/src/libkfont/psffontop.c
index 3f94028..179a271 100644
--- a/src/libkfont/psffontop.c
+++ b/src/libkfont/psffontop.c
@@ -147,7 +147,7 @@ read_fontfile(struct kfont_context *ctx, FILE *fontf, unsigned char **inputbuf,
 {
 	unsigned char *buf = NULL;
 	unsigned int buflth = 0;
-	unsigned int chunksz = MAXFONTSIZE / 4; /* random */
+	unsigned int chunksz = MAXFONTSIZE / 4; /* 1 MB; random */
 	int ret = 0;
 	size_t n = 0;
 
@@ -174,12 +174,6 @@ read_fontfile(struct kfont_context *ctx, FILE *fontf, unsigned char **inputbuf,
 			ret = -EX_DATAERR;
 			goto end;
 		}
-
-		if (n > MAXFONTSIZE) {
-			KFONT_ERR(ctx, _("Font is too big"));
-			ret = -EX_DATAERR;
-			goto end;
-		}
 	}
 
 	*inputbuf = buf;
@@ -197,12 +191,14 @@ kfont_read_psffont(struct kfont_context *ctx,
 		unsigned char **fontbufp, unsigned int *fontszp,
 		unsigned int *fontwidthp, unsigned int *fontheightp,
 		unsigned int *fontlenp, unsigned int fontpos0,
-		struct unicode_list **uclistheadsp)
+		   struct unicode_list **uclistheadsp,
+		   int *bit21)
 {
 	unsigned char *inputbuf;
 	unsigned int inputlth;
 	int ret;
 
+	*bit21 = 0;
 	/*
 	 * We used to look at the length of the input file
 	 * with stat(); now that we accept compressed files,
@@ -225,7 +221,8 @@ kfont_read_psffont(struct kfont_context *ctx,
 		inputlth = *allszp;
 	}
 
-	unsigned int fontlen, fontwidth, fontheight, charsize, hastable, ftoffset;
+	unsigned int fontlen, fontwidth, fontheight, charsize, hastable,
+		ftoffset;
 	int utf8;
 
 	if (inputlth >= sizeof(struct psf1_header) && PSF1_MAGIC_OK(inputbuf)) {
@@ -317,11 +314,29 @@ kfont_read_psffont(struct kfont_context *ctx,
 		inptr  = inputbuf + ftoffset + fontlen * charsize;
 		endptr = inputbuf + inputlth;
 
+		if (fontlen > 0xffff)
+			*bit21 = 1;
 		for (i = 0; i < fontlen; i++) {
 			ret = get_uni_entry(ctx, &inptr, &endptr,
 					&(*uclistheadsp)[fontpos0 + i], utf8);
 			if (ret < 0)
 				return ret;
+			{
+				struct unicode_list *list = &(*uclistheadsp)[fontpos0 + i];
+				struct unicode_seq *seq;
+
+				while (list) {
+					seq = list->seq;
+					while (seq) {
+						if (seq->uc > 0xffff)
+							*bit21 = 1;
+						seq = seq->next;
+					}
+					list = list->prev;
+					if (list == &(*uclistheadsp)[fontpos0 + i])
+						break;
+				}
+			}
 		}
 		if (inptr != endptr) {
 			KFONT_ERR(ctx, _("Input file: trailing garbage"));
diff --git a/src/libkfont/setfont.c b/src/libkfont/setfont.c
index 45652c0..f6f0560 100644
--- a/src/libkfont/setfont.c
+++ b/src/libkfont/setfont.c
@@ -185,21 +185,25 @@ err:
 static int
 do_loadfont(struct kfont_context *ctx, int fd, const unsigned char *inbuf,
 		unsigned int width, unsigned int height, unsigned int hwunit,
-		unsigned int fontsize, const char *filename)
+	    unsigned int fontsize, const char *filename)
 {
 	if (height <= 32 && width <= 32)
 		/* This can work with pre-6.2 kernels and its size and vpitch limitations */
-		return try_loadfont(ctx, fd, inbuf, width, height, 32, hwunit, fontsize, filename);
+		return try_loadfont(ctx, fd, inbuf, width, height, 32, hwunit,
+				    fontsize, filename);
 	else
-		return try_loadfont(ctx, fd, inbuf, width, height, height, hwunit, fontsize, filename);
+		return try_loadfont(ctx, fd, inbuf, width, height, height,
+				    hwunit, fontsize, filename);
 }
 
 static int
 do_loadtable(struct kfont_context *ctx, int fd, struct unicode_list *uclistheads,
-		unsigned int fontsize)
+	     unsigned int fontsize, int bit21)
 {
 	struct unimapdesc ud;
 	struct unipair *up = NULL;
+	struct unimapdesc21 ud21;
+	struct unipair21 *up21 = NULL;
 	unsigned int i, ct = 0, maxct;
 	struct unicode_list *ul;
 	struct unicode_seq *us;
@@ -215,11 +219,19 @@ do_loadtable(struct kfont_context *ctx, int fd, struct unicode_list *uclistheads
 			ul = ul->next;
 		}
 	}
-
-	up = malloc(maxct * sizeof(*up));
-	if (!up) {
-		KFONT_ERR(ctx, "malloc: %m");
-		return -EX_OSERR;
+	if (bit21) {
+		up21 = malloc(maxct * sizeof(*up21));
+		if (!up21) {
+			KFONT_ERR(ctx, "malloc: %m");
+			return -EX_OSERR;
+		}
+	}
+	else {
+		up = malloc(maxct * sizeof(*up));
+		if (!up) {
+			KFONT_ERR(ctx, "malloc: %m");
+			return -EX_OSERR;
+		}
 	}
 
 	for (i = 0; i < fontsize; i++) {
@@ -229,8 +241,14 @@ do_loadtable(struct kfont_context *ctx, int fd, struct unicode_list *uclistheads
 		while (ul) {
 			us = ul->seq;
 			if (us && !us->next) {
-				up[ct].unicode = (unsigned short) us->uc;
-				up[ct].fontpos = (unsigned short) i;
+				if (bit21) {
+					up21[ct].unicode = (unsigned) us->uc;
+					up21[ct].fontpos = (unsigned) i;
+				}
+				else {
+					up[ct].unicode = (unsigned short) us->uc;
+					up[ct].fontpos = (unsigned short) i;
+				}
 				ct++;
 				if (ctx->verbose > 1)
 					printf(" %04x", us->uc);
@@ -250,7 +268,7 @@ do_loadtable(struct kfont_context *ctx, int fd, struct unicode_list *uclistheads
 			printf("\n");
 	}
 
-	if (ct > USHRT_MAX || ct != maxct) {
+	if (ct != maxct) {
 		KFONT_ERR(ctx, _("bug in do_loadtable"));
 		ret = -EX_SOFTWARE;
 		goto err;
@@ -258,17 +276,26 @@ do_loadtable(struct kfont_context *ctx, int fd, struct unicode_list *uclistheads
 
 	KFONT_INFO(ctx, _("Loading Unicode mapping table..."));
 
-	ud.entry_ct = (unsigned short) ct;
-	ud.entries  = up;
+	if (bit21) {
+		ud21.entry_ct = ct;
+		ud21.entries = up21;
+	}
+	else {
+		ud.entry_ct = (unsigned short) ct;
+		ud.entries  = up;
+	}
 
-	if (kfont_put_unicodemap(ctx, fd, NULL, &ud) < 0) {
+	if (kfont_put_unicodemap(ctx, fd, NULL, bit21, &ud, &ud21) < 0) {
 		ret = -EX_OSERR;
 		goto err;
 	}
 
 	ret = 0;
 err:
-	free(up);
+	if (bit21)
+		free(up21);
+	else
+		free(up);
 	return ret;
 }
 
@@ -286,6 +313,7 @@ kfont_load_fonts(struct kfont_context *ctx,
 	struct kbdfile *fp = NULL;
 	int i;
 	int ret = 0;
+	int bit21 = 0;
 
 	if (ifilct == 1)
 		return kfont_load_font(ctx, fd, ifiles[0], iunit, hwunit, no_m, no_u);
@@ -321,12 +349,18 @@ kfont_load_fonts(struct kfont_context *ctx,
 
 		if (kfont_read_psffont(ctx, kbdfile_get_file(fp), &inbuf,
 			&inputlth, &fontbuf, &fontbuflth, &width, &height, &fontsize,
-			bigfontsize, no_u ? NULL : &uclistheads)) {
+				       bigfontsize, no_u ? NULL : &uclistheads,
+				       &bit21)) {
 			KFONT_ERR(ctx, _("When loading several fonts, all must be psf fonts - %s isn't"),
 			    kbdfile_get_pathname(fp));
 			ret = -EX_DATAERR;
 			goto end;
 		}
+		if (bit21) {
+			KFONT_ERR(ctx, _("Full Unicode fonts aren't (yet) handled by this program."));
+			ret = -EX_DATAERR;
+			goto end;
+		}
 
 		if (!height) {
 			unsigned int bytewidth;
@@ -372,10 +406,10 @@ kfont_load_fonts(struct kfont_context *ctx,
 	}
 
 	ret = do_loadfont(ctx, fd, bigfontbuf, bigwidth, bigheight, hwunit,
-		bigfontsize, NULL);
+			  bigfontsize, NULL);
 
 	if (!ret && uclistheads && !no_u)
-		ret = do_loadtable(ctx, fd, uclistheads, bigfontsize);
+		ret = do_loadtable(ctx, fd, uclistheads, bigfontsize, 0);
 
 end:
 	free(bigfontbuf);
@@ -398,6 +432,7 @@ kfont_load_font(struct kfont_context *ctx, int fd, const char *ifil,
 	unsigned char *inbuf, *fontbuf;
 	unsigned int inputlth, fontbuflth, fontsize, offset;
 	struct unicode_list *uclistheads;
+	int bit21 = 0;
 	int ret;
 
 	if (!(fp = kbdfile_new(NULL))) {
@@ -447,10 +482,13 @@ kfont_load_font(struct kfont_context *ctx, int fd, const char *ifil,
 	height = 0;
 	uclistheads = NULL;
 
-	if (!kfont_read_psffont(ctx, kbdfile_get_file(fp), &inbuf, &inputlth,
-		&fontbuf, &fontbuflth, &width, &height, &fontsize, 0,
-		no_u ? NULL : &uclistheads)) {
-
+	ret = kfont_read_psffont(ctx, kbdfile_get_file(fp), &inbuf, &inputlth,
+				 &fontbuf, &fontbuflth, &width, &height, &fontsize, 0,
+				 no_u ? NULL : &uclistheads,
+				 &bit21) ;
+	if (ret)
+		KFONT_ERR(ctx, "Non-zero RET from kfont_read_psffont: %d, %m\n", ret);
+	else {
 		/* we've got a psf font */
 		if (!height) {
 			unsigned int bytewidth;
@@ -459,12 +497,13 @@ kfont_load_font(struct kfont_context *ctx, int fd, const char *ifil,
 		}
 
 		ret = do_loadfont(ctx, fd, fontbuf, width, height, hwunit,
-			fontsize, kbdfile_get_pathname(fp));
+				  fontsize, kbdfile_get_pathname(fp));
 		if (ret < 0)
 			goto end;
 
 		if (uclistheads && !no_u) {
-			ret = do_loadtable(ctx, fd, uclistheads, fontsize);
+			ret = do_loadtable(ctx, fd, uclistheads, fontsize,
+					   bit21);
 			if (ret < 0)
 				goto end;
 		}
diff --git a/src/psfxtable.c b/src/psfxtable.c
index 9886ad4..2a8fdf4 100644
--- a/src/psfxtable.c
+++ b/src/psfxtable.c
@@ -45,6 +45,7 @@ int main(int argc, char **argv)
 	unsigned char *inbuf, *fontbuf;
 	unsigned int inbuflth, fontbuflth;
 	struct unicode_list *uclistheads = NULL;
+	int bit21 = 0;
 
 	setuplocale();
 
@@ -143,9 +144,12 @@ int main(int argc, char **argv)
 		kbd_error(EX_CANTCREAT, 0, _("Unable to open file: %s: %m"), otname);
 
 	if (kfont_read_psffont(kfont, ifil, &inbuf, &inbuflth, &fontbuf,
-				&fontbuflth, &width, &height, &fontlen, 0,
-				itab ? NULL : &uclistheads) < 0)
+			       &fontbuflth, &width, &height, &fontlen, 0,
+			       itab ? NULL : &uclistheads,
+			       &bit21) < 0)
 		kbd_error(EX_DATAERR, 0, _("Bad magic number on %s"), ifname);
+	if (bit21)
+		kbd_error(EX_DATAERR, 0, _("Full Unicode fonts aren't (yet) handled by this program."));
 
 	fclose(ifil);
 
diff --git a/src/readpsfheader.c b/src/readpsfheader.c
index 2d87e59..7c1b406 100644
--- a/src/readpsfheader.c
+++ b/src/readpsfheader.c
@@ -29,6 +29,7 @@ int main(int argc, char **argv)
 	char *inbuf, *fontbuf;
 	int inbuflth, fontbuflth;
 	struct unicode_list *uclistheads = NULL;
+	int bit21 = 0;
 
 	const char *short_opts = "hV";
 	const struct option long_opts[] = {
@@ -73,8 +74,12 @@ int main(int argc, char **argv)
 	if ((ret = kfont_init(program_invocation_short_name, &kfont)) < 0)
 		return -ret;
 
-	if (kfont_read_psffont(kfont, f, &inbuf, &inbuflth, &fontbuf, &fontbuflth, &width, &fontlen, 0, &uclistheads) < 0)
+	if (kfont_read_psffont(kfont, f, &inbuf, &inbuflth, &fontbuf,
+			       &fontbuflth, &width, &fontlen, 0, &uclistheads,
+			       &bit21) < 0)
 		kbd_error(EX_DATAERR, 0, "Bad magic number");
+	if (bit21)
+		kbd_error(EX_DATAERR, 0, "Full Unicode fonts aren't (yet) handled by this program");
 
 	close(f);
 
diff --git a/src/showconsolefont.c b/src/showconsolefont.c
index 9230e2b..27c04ec 100644
--- a/src/showconsolefont.c
+++ b/src/showconsolefont.c
@@ -36,7 +36,8 @@ leave(struct kfont_context *ctx, int n)
 		kbd_warning(0, _("failed to restore original translation table"));
 		n = EXIT_FAILURE;
 	}
-	if (have_ounimap && kfont_put_unicodemap(ctx, fd, NULL, &ounimap)) {
+	if (have_ounimap && kfont_put_unicodemap(ctx, fd, NULL, 0,
+						 &ounimap , NULL)) {
 		kbd_warning(0, _("failed to restore original unimap"));
 		n = EXIT_FAILURE;
 	}
@@ -92,7 +93,7 @@ setnewunicodemap(struct kfont_context *ctx, unsigned int *list, int cnt)
 	for (i = 0; i < cnt; i++)
 		nunimap.entries[list[i]].unicode = (unsigned short) (BASE + i);
 
-	if (kfont_put_unicodemap(ctx, fd, NULL, &nunimap))
+	if (kfont_put_unicodemap(ctx, fd, NULL, 0, &nunimap, NULL))
 		leave(ctx, EXIT_FAILURE);
 }
 
@@ -111,7 +112,7 @@ usage(int rc, const struct kbd_help *options)
 int main(int argc, char **argv)
 {
 	int c, ret;
-	unsigned int cols, rows, nr, n, i, j, k;
+	int cols = 0, rows = 0, nr = 0, n, i, j, k;
 	int mode;
 	const char *space, *sep;
 	char *console = NULL;
@@ -176,19 +177,22 @@ int main(int argc, char **argv)
 		kbd_warning(errno, _("Unable to read keyboard mode"));
 		leave(kfont, EX_OSERR);
 	}
-	if (mode == K_UNICODE)
-		space = "\xef\x80\xa0"; /* U+F020 (direct-to-font space) */
-	else
-		space = " ";
-
-	if (info) {
-		nr = rows = cols = 0;
-
-		ret = kfont_get_font(kfont, fd, NULL, &nr, &rows, &cols, NULL);
-		if (ret != 0)
-			leave(kfont, EXIT_FAILURE);
-
-		if (kfont_get_verbosity(kfont)) {
+	/* TEMP COMMENTED OUT STUFF, 2026-05-20.  What is this F020 for? */
+	/* if (mode == K_UNICODE) */
+	/* 	space = "\xef\x80\xa0"; /\* U+F020 (direct-to-font space) *\/ */
+	/* else */
+	/* END OF TC STUFF */
+	space = " ";
+
+	ret = kfont_get_font(kfont, fd, NULL, &nr, &rows, &cols, NULL);
+	if (ret != 0)
+		leave(kfont, EXIT_FAILURE);
+
+	if (!info && nr > 512)
+		printf(_("Too many characters to print individually\n"));
+	if (info || nr > 512) {
+		if (kfont_get_verbosity(kfont)
+		    || (!info && nr > 512)) {
 			printf(_("Character count: %u\n"), nr);
 			printf(_("Font width     : %u\n"), rows);
 			printf(_("Font height    : %u\n"), cols);


-- 
Alan Mackenzie (Nuremberg, Germany).

[-- Attachment #2: latn-16-unifont-base+upper.psfu.gz --]
[-- Type: application/gzip, Size: 86935 bytes --]

[-- Attachment #3: setfont --]
[-- Type: application/octet-stream, Size: 83048 bytes --]

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [Patch 1/9]: Make consolemap.c handle Unicode planes outside BMP
  2026-08-27 18:38 vt: Enlarge the framebuffer glyph size from 16 to 32 bits Alan Mackenzie
@ 2026-08-27 18:42 ` Alan Mackenzie
  2026-08-27 19:14   ` sashiko-bot
  2026-08-28  4:57   ` Jiri Slaby
  2026-08-27 18:45 ` [Patch 2/9]: Glyph size: Use GLYPH_SZ/HW rather than hardcoded 2, 1 Alan Mackenzie
                   ` (8 subsequent siblings)
  9 siblings, 2 replies; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-27 18:42 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, Thomas Zimmermann, linux-fbdev,
	dri-devel
  Cc: acm

vt: 32b glyph: 1. Make consolemap.c handle Unicode planes outside BMP

For this, add a fourth layer "planes" onto the sparse map in
this file.  Also handle glyph numbers up to 2^21 rather than
256/512.  Move the conversion between __user and kernel data
from consolemap.c to vt_ioctl.c.

Signed-off-by: Alan Mackenzie <acm@muc.de>

diff --git a/drivers/tty/vt/consolemap.c b/drivers/tty/vt/consolemap.c
index 3fa89a2dbeba..2f695d97f0eb 100644
--- a/drivers/tty/vt/consolemap.c
+++ b/drivers/tty/vt/consolemap.c
@@ -34,11 +34,11 @@
 #include <linux/tty.h>
 #include <linux/uaccess.h>
 #include <linux/console.h>
-#include <linux/consolemap.h>
 #include <linux/vt_kern.h>
+#include <linux/consolemap.h>
 #include <linux/string.h>
 
-static unsigned short translations[][E_TABSZ] = {
+static u1632 translations[][E_TABSZ] = {
   /* 8-bit Latin-1 mapped to Unicode -- trivial mapping */
   [LAT1_MAP] = {
     0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
@@ -184,66 +184,79 @@ static unsigned short translations[][E_TABSZ] = {
 /* The standard kernel character-to-font mappings are not invertible
    -- this is just a best effort. */
 
-#define MAX_GLYPH 512		/* Max possible glyph value */
+#ifdef CONFIG_FB_GLYPH_21BIT
+#define MAX_GLYPH 0x110000
+#else
+#define MAX_GLYPH 512
+#endif
 
 static enum translation_map inv_translate[MAX_NR_CONSOLES];
 
-#define UNI_DIRS	32U
+#define UNI_PLANES	32U	/* Actually, only 17. */
+#define UNI_PLANE_DIRS	32U
 #define UNI_DIR_ROWS	32U
 #define UNI_ROW_GLYPHS	64U
 
+#define UNI_PLANE_BITS		GENMASK(20, 16)
 #define UNI_DIR_BITS		GENMASK(15, 11)
 #define UNI_ROW_BITS		GENMASK(10,  6)
 #define UNI_GLYPH_BITS		GENMASK( 5,  0)
 
+#ifdef CONFIG_FB_GLYPH_21BIT
+#define UNI_PLANE(uni)		FIELD_GET(UNI_PLANE_BITS, (uni))
+#else
+#define UNI_PLANE(uni)		0
+#endif
 #define UNI_DIR(uni)		FIELD_GET(UNI_DIR_BITS, (uni))
 #define UNI_ROW(uni)		FIELD_GET(UNI_ROW_BITS, (uni))
 #define UNI_GLYPH(uni)		FIELD_GET(UNI_GLYPH_BITS, (uni))
 
-#define UNI(dir, row, glyph)	(FIELD_PREP(UNI_DIR_BITS, (dir)) | \
+#define UNI(plane, dir, row, glyph) (FIELD_PREP(UNI_PLANE_BITS, (plane)) | \
+				 FIELD_PREP(UNI_DIR_BITS, (dir)) | \
 				 FIELD_PREP(UNI_ROW_BITS, (row)) | \
 				 FIELD_PREP(UNI_GLYPH_BITS, (glyph)))
 
 /**
  * struct uni_pagedict - unicode directory
  *
- * @uni_pgdir: 32*32*64 table with glyphs
+ * @uni_plane: 32*32*32*64 table with glyphs
+ * @count: Number of unicode entries in this structure
  * @refcount: reference count of this structure
  * @sum: checksum
  * @inverse_translations: best-effort inverse mapping
  * @inverse_trans_unicode: best-effort inverse mapping to unicode
  */
 struct uni_pagedict {
-	u16		**uni_pgdir[UNI_DIRS];
+	u1632		***uni_plane[UNI_PLANES];
+	u32		count;
 	unsigned long	refcount;
 	unsigned long	sum;
 	unsigned char	*inverse_translations[LAST_MAP + 1];
-	u16		*inverse_trans_unicode;
+	u1632		*inverse_trans_unicode;
 };
 
 static struct uni_pagedict *dflt;
 
 static void set_inverse_transl(struct vc_data *conp, struct uni_pagedict *dict,
-	       enum translation_map m)
+			       enum translation_map m)
 {
-	unsigned short *t = translations[m];
+	u1632 *t = translations[m];
 	unsigned char *inv;
 
 	if (!dict)
 		return;
 	inv = dict->inverse_translations[m];
 
-	if (!inv) {
-		inv = dict->inverse_translations[m] = kmalloc(MAX_GLYPH,
-				GFP_KERNEL);
-		if (!inv)
-			return;
-	}
-	memset(inv, 0, MAX_GLYPH);
+	kfree(inv);
+	inv = dict->inverse_translations[m] =
+		kmalloc(dict->count * (sizeof(*t)), GFP_KERNEL);
+	if (!inv)
+		return;
+	memset(inv, 0, dict->count * (sizeof(*t)));
 
 	for (unsigned int ch = 0; ch < ARRAY_SIZE(translations[m]); ch++) {
 		int glyph = conv_uni_to_pc(conp, t[ch]);
-		if (glyph >= 0 && glyph < MAX_GLYPH && inv[glyph] < 32) {
+		if (glyph >= 0 && glyph < dict->count && inv[glyph] < 32) {
 			/* prefer '-' above SHY etc. */
 			inv[glyph] = ch;
 		}
@@ -252,39 +265,48 @@ static void set_inverse_transl(struct vc_data *conp, struct uni_pagedict *dict,
 
 static void set_inverse_trans_unicode(struct uni_pagedict *dict)
 {
-	unsigned int d, r, g;
-	u16 *inv;
+	unsigned int p, d, r, g;
+	u1632 *inv;
 
 	if (!dict)
 		return;
 
 	inv = dict->inverse_trans_unicode;
-	if (!inv) {
-		inv = dict->inverse_trans_unicode = kmalloc_array(MAX_GLYPH,
-				sizeof(*inv), GFP_KERNEL);
-		if (!inv)
-			return;
-	}
-	memset(inv, 0, MAX_GLYPH * sizeof(*inv));
+	kfree(inv);
+	inv = dict->inverse_trans_unicode =
+		kmalloc_array(dict->count, sizeof(*inv), GFP_KERNEL);
+	if (!inv)
+		return;
+	memset(inv, 0, dict->count * sizeof(*inv));
 
-	for (d = 0; d < UNI_DIRS; d++) {
-		u16 **dir = dict->uni_pgdir[d];
-		if (!dir)
+	for (p = 0; p < UNI_PLANES; p++) {
+		u1632 ***plane = dict->uni_plane[p];
+
+		if (!plane)
 			continue;
-		for (r = 0; r < UNI_DIR_ROWS; r++) {
-			u16 *row = dir[r];
-			if (!row)
+		for (d = 0; d < UNI_PLANE_DIRS; d++) {
+			u1632 **dir = plane[d];
+
+			if (!dir)
 				continue;
-			for (g = 0; g < UNI_ROW_GLYPHS; g++) {
-				u16 glyph = row[g];
-				if (glyph < MAX_GLYPH && inv[glyph] < 32)
-					inv[glyph] = UNI(d, r, g);
+			for (r = 0; r < UNI_DIR_ROWS; r++) {
+				u1632 *row = dir[r];
+
+				if (!row)
+					continue;
+				for (g = 0; g < UNI_ROW_GLYPHS; g++) {
+					u1632 glyph = row[g];
+
+					if (glyph < dict->count &&
+					    inv[glyph] < 32)
+						inv[glyph] = UNI(p, d, r, g);
+				}
 			}
 		}
 	}
 }
 
-unsigned short *set_translate(enum translation_map m, struct vc_data *vc)
+u1632 *set_translate(enum translation_map m, struct vc_data *vc)
 {
 	inv_translate[vc->vc_num] = m;
 	return translations[m];
@@ -297,18 +319,19 @@ unsigned short *set_translate(enum translation_map m, struct vc_data *vc)
  *    was active.
  * Still, it is now possible to a certain extent to cut and paste non-ASCII.
  */
-u16 inverse_translate(const struct vc_data *conp, u16 glyph, bool use_unicode)
+u1632 inverse_translate(const struct vc_data *conp, u1632 glyph,
+			bool use_unicode)
 {
 	struct uni_pagedict *p;
 	enum translation_map m;
 
-	if (glyph >= MAX_GLYPH)
-		return 0;
-
 	p = *conp->uni_pagedict_loc;
 	if (!p)
 		return glyph;
 
+	if (glyph >= p->count)
+		return 0;
+
 	if (use_unicode) {
 		if (!p->inverse_trans_unicode)
 			return glyph;
@@ -371,7 +394,7 @@ int con_set_trans_old(unsigned char __user * arg)
 int con_get_trans_old(unsigned char __user * arg)
 {
 	int i, ch;
-	unsigned short *p = translations[USER_MAP];
+	u1632 *p = translations[USER_MAP];
 	unsigned char outbuf[E_TABSZ];
 
 	scoped_guard(console_lock)
@@ -410,31 +433,38 @@ int con_get_trans_new(ushort __user * arg)
 /*
  * Unicode -> current font conversion
  *
- * A font has at most 512 chars, usually 256.
+ * A font has at most 512 chars (2026-01: no longer true), usually 256.
  * But one font position may represent several Unicode chars.
  * A hashtable is somewhat of a pain to deal with, so use a
  * "paged table" instead.  Simulation has shown the memory cost of
- * this 3-level paged table scheme to be comparable to a hash table.
+ * this 4-level paged table scheme to be comparable to a hash table.
  */
 
-extern u8 dfont_unicount[];	/* Defined in console_defmap.c */
-extern u16 dfont_unitable[];
-
 static void con_release_unimap(struct uni_pagedict *dict)
 {
-	unsigned int d, r;
+	unsigned int p, d, r;
 
 	if (dict == dflt)
 		dflt = NULL;
 
-	for (d = 0; d < UNI_DIRS; d++) {
-		u16 **dir = dict->uni_pgdir[d];
-		if (dir != NULL) {
-			for (r = 0; r < UNI_DIR_ROWS; r++)
-				kfree(dir[r]);
-			kfree(dir);
+	for (p = 0; p < UNI_PLANES; p++) {
+		u1632 ***plane = dict->uni_plane[p];
+
+		if (plane != NULL) {
+			for (d = 0; d < UNI_PLANE_DIRS; d++) {
+				u1632 **dir = plane[d];
+
+				if (dir != NULL) {
+					for (r = 0; r < UNI_DIR_ROWS; r++)
+						if (dir[r] != NULL)
+							kfree(dir[r]);
+					kfree(dir);
+				}
+				plane[d] = NULL;
+			}
+			kfree(plane);
+			dict->uni_plane[p] = NULL;
 		}
-		dict->uni_pgdir[d] = NULL;
 	}
 
 	for (r = 0; r < ARRAY_SIZE(dict->inverse_translations); r++) {
@@ -464,7 +494,7 @@ void con_free_unimap(struct vc_data *vc)
 static int con_unify_unimap(struct vc_data *conp, struct uni_pagedict *dict1)
 {
 	struct uni_pagedict *dict2;
-	unsigned int cons, d, r;
+	unsigned int cons, p, d, r;
 
 	for (cons = 0; cons < MAX_NR_CONSOLES; cons++) {
 		if (!vc_cons_allocated(cons))
@@ -472,26 +502,39 @@ static int con_unify_unimap(struct vc_data *conp, struct uni_pagedict *dict1)
 		dict2 = *vc_cons[cons].d->uni_pagedict_loc;
 		if (!dict2 || dict2 == dict1 || dict2->sum != dict1->sum)
 			continue;
-		for (d = 0; d < UNI_DIRS; d++) {
-			u16 **dir1 = dict1->uni_pgdir[d];
-			u16 **dir2 = dict2->uni_pgdir[d];
-			if (!dir1 && !dir2)
+		for (p = 0; p < UNI_PLANES; p++) {
+			u1632 ***plane1 = dict1->uni_plane[p];
+			u1632 ***plane2 = dict2->uni_plane[p];
+
+			if (!plane1 && !plane2)
 				continue;
-			if (!dir1 || !dir2)
+			if (!plane1 || !plane2)
 				break;
-			for (r = 0; r < UNI_DIR_ROWS; r++) {
-				if (!dir1[r] && !dir2[r])
+			for (d = 0; d < UNI_PLANE_DIRS; d++) {
+				u1632 **dir1 = plane1[d];
+				u1632 **dir2 = plane2[d];
+
+				if (!dir1 && !dir2)
 					continue;
-				if (!dir1[r] || !dir2[r])
+				if (!dir1 || !dir2)
 					break;
-				if (memcmp(dir1[r], dir2[r], UNI_ROW_GLYPHS *
-							sizeof(*dir1[r])))
+				for (r = 0; r < UNI_DIR_ROWS; r++) {
+					if (!dir1[r] && !dir2[r])
+						continue;
+					if (!dir1[r] || !dir2[r])
+						break;
+					if (memcmp(dir1[r], dir2[r],
+						   UNI_ROW_GLYPHS *
+						   sizeof(*dir1[r])))
+						break;
+				}
+				if (r < UNI_DIR_ROWS)
 					break;
 			}
-			if (r < UNI_DIR_ROWS)
+			if (d < UNI_PLANE_DIRS)
 				break;
 		}
-		if (d == UNI_DIRS) {
+		if (p == UNI_PLANES) {
 			dict2->refcount++;
 			*conp->uni_pagedict_loc = dict2;
 			con_release_unimap(dict1);
@@ -503,15 +546,24 @@ static int con_unify_unimap(struct vc_data *conp, struct uni_pagedict *dict1)
 }
 
 static int
-con_insert_unipair(struct uni_pagedict *p, u_short unicode, u_short fontpos)
+con_insert_unipair(struct uni_pagedict *p, u1632 unicode, u1632 fontpos)
 {
-	u16 **dir, *row;
+	u1632 ***plane, **dir, *row;
 	unsigned int n;
 
+	n = UNI_PLANE(unicode);
+	plane = p->uni_plane[n];
+	if (!plane) {
+		plane = p->uni_plane[n] = kcalloc
+			(UNI_PLANE_DIRS, sizeof(*plane), GFP_KERNEL);
+		if (!plane)
+			return -ENOMEM;
+	}
+
 	n = UNI_DIR(unicode);
-	dir = p->uni_pgdir[n];
+	dir = plane[n];
 	if (!dir) {
-		dir = p->uni_pgdir[n] = kcalloc(UNI_DIR_ROWS, sizeof(*dir),
+		dir = plane[n] = kcalloc(UNI_DIR_ROWS, sizeof(*dir),
 				GFP_KERNEL);
 		if (!dir)
 			return -ENOMEM;
@@ -573,12 +625,12 @@ int con_clear_unimap(struct vc_data *vc)
 }
 
 static struct uni_pagedict *con_unshare_unimap(struct vc_data *vc,
-		struct uni_pagedict *old)
+					       struct uni_pagedict *old)
 {
 	struct uni_pagedict *new;
-	unsigned int d, r, g;
+	unsigned int p, d, r, g;
 	int ret;
-	u16 uni = 0;
+	u32 uni = 0;
 
 	ret = con_allocate_new(vc);
 	if (ret)
@@ -587,63 +639,70 @@ static struct uni_pagedict *con_unshare_unimap(struct vc_data *vc,
 	new = *vc->uni_pagedict_loc;
 
 	/*
-	 * uni_pgdir is a 32*32*64 table with rows allocated when its first
+	 * uni_plane is a 32*32*32*64 table with a row allocated when its first
 	 * entry is added. The unicode value must still be incremented for
 	 * empty rows. We are copying entries from "old" to "new".
 	 */
-	for (d = 0; d < UNI_DIRS; d++) {
-		u16 **dir = old->uni_pgdir[d];
-		if (!dir) {
+	for (p = 0; p < UNI_PLANES; p++) {
+		u1632 ***plane = old->uni_plane[p];
+
+		if (!plane) {
 			/* Account for empty table */
-			uni += UNI_DIR_ROWS * UNI_ROW_GLYPHS;
+			uni += UNI_PLANE_DIRS * UNI_DIR_ROWS * UNI_ROW_GLYPHS;
 			continue;
 		}
 
-		for (r = 0; r < UNI_DIR_ROWS; r++) {
-			u16 *row = dir[r];
-			if (!row) {
-				/* Account for row of 64 empty entries */
-				uni += UNI_ROW_GLYPHS;
+		for (d = 0; d < UNI_PLANE_DIRS; d++) {
+			u1632 **dir = plane[d];
+
+			if (!dir) {
+				/* Account for empty table */
+				uni += UNI_DIR_ROWS * UNI_ROW_GLYPHS;
 				continue;
 			}
 
-			for (g = 0; g < UNI_ROW_GLYPHS; g++, uni++) {
-				if (row[g] == 0xffff)
+			for (r = 0; r < UNI_DIR_ROWS; r++) {
+				u1632 *row = dir[r];
+
+				if (!row) {
+					/* Account for row of 64 empty entries */
+					uni += UNI_ROW_GLYPHS;
 					continue;
-				/*
-				 * Found one, copy entry for unicode uni with
-				 * fontpos value row[g].
-				 */
-				ret = con_insert_unipair(new, uni, row[g]);
-				if (ret) {
-					old->refcount++;
-					*vc->uni_pagedict_loc = old;
-					con_release_unimap(new);
-					kfree(new);
-					return ERR_PTR(ret);
+				}
+
+				for (g = 0; g < UNI_ROW_GLYPHS; g++, uni++) {
+					if (row[g] == ~0)
+						continue;
+					/*
+					 * Found one, copy entry for unicode
+					 * uni with fontpos value row[g].
+					 */
+					ret = con_insert_unipair(new, uni,
+								 row[g]);
+					if (ret) {
+						old->refcount++;
+						*vc->uni_pagedict_loc = old;
+						con_release_unimap(new);
+						kfree(new);
+						return ERR_PTR(ret);
+					}
 				}
 			}
 		}
 	}
-
 	return new;
 }
 
-int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
+int con_set_unimap(struct vc_data *vc, u32 ct, struct unipair8_21 *list)
 {
-	struct uni_pagedict *dict;
-	struct unipair *plist;
 	int err = 0;
+	struct uni_pagedict *dict;
+	struct unipair8_21 *plist;
 
 	if (!ct)
 		return 0;
 
-	struct unipair *unilist __free(kvfree) = vmemdup_array_user(list, ct, sizeof(*unilist));
-	if (IS_ERR(unilist))
-		return PTR_ERR(unilist);
-
 	guard(console_lock)();
-
 	/* Save original vc_unipagdir_loc in case we allocate a new one */
 	dict = *vc->uni_pagedict_loc;
 	if (!dict)
@@ -660,7 +719,9 @@ int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
 	/*
 	 * Insert user specified unicode pairs into new table.
 	 */
-	for (plist = unilist; ct; ct--, plist++) {
+	err = 0;
+	dict->count = ct;
+	for (plist = list; ct; ct--, plist++) {
 		int err1 = con_insert_unipair(dict, plist->unicode, plist->fontpos);
 		if (err1)
 			err = err1;
@@ -769,54 +830,69 @@ EXPORT_SYMBOL(con_copy_unimap);
  *	Read the console unicode data for this console. Called from the ioctl
  *	handlers.
  */
-int con_get_unimap(struct vc_data *vc, ushort ct, ushort __user *uct,
-		struct unipair __user *list)
+int con_get_unimap(struct vc_data *vc, u32 ct, u1632 *uct,
+		   struct unipair8_21 *list)
 {
-	ushort ect;
+	u1632 ect;
 	struct uni_pagedict *dict;
-	unsigned int d, r, g;
+	unsigned int p, d, r, g;
+	int ret = 0;
 
-	struct unipair *unilist __free(kvfree) = kvmalloc_array(ct, sizeof(*unilist), GFP_KERNEL);
-	if (!unilist)
-		return -ENOMEM;
+	scoped_guard (console_lock) {
 
-	scoped_guard(console_lock) {
 		ect = 0;
 		dict = *vc->uni_pagedict_loc;
 		if (!dict)
 			break;
 
-		for (d = 0; d < UNI_DIRS; d++) {
-			u16 **dir = dict->uni_pgdir[d];
-			if (!dir)
+		for (p = 0; p < UNI_PLANES; p++) {
+			u1632 ***plane = dict->uni_plane[p];
+
+			if (!plane)
 				continue;
 
-			for (r = 0; r < UNI_DIR_ROWS; r++) {
-				u16 *row = dir[r];
-				if (!row)
+			for (d = 0; d < UNI_PLANE_DIRS; d++) {
+				u1632 **dir = plane[d];
+
+				if (!dir)
 					continue;
 
-				for (g = 0; g < UNI_ROW_GLYPHS; g++, row++) {
-					if (*row >= MAX_GLYPH)
+				for (r = 0; r < UNI_DIR_ROWS; r++) {
+					u1632 *row = dir[r];
+
+					if (!row)
 						continue;
-					if (ect < ct) {
-						unilist[ect].unicode = UNI(d, r, g);
-						unilist[ect].fontpos = *row;
+
+					for (g = 0; g < UNI_ROW_GLYPHS; g++, row++) {
+						if (*row >= dict->count)
+							continue;
+						if (ect < ct) {
+#ifndef CONFIG_FB_GLYPH_21BIT
+							if ((p || *row > 0xffff)) {
+								ret = -EINVAL;
+								goto unlock;
+							}
+#endif
+							if (list) {
+								list[ect].unicode = UNI(p, d, r, g);
+								list[ect].fontpos = *row;
+							}
+						}
+						ect++;
 					}
-					ect++;
 				}
 			}
 		}
+#ifndef CONFIG_FB_GLYPH_21BIT
+unlock:
+#endif
 	}
-
-	if (copy_to_user(list, unilist, min(ect, ct) * sizeof(*unilist)))
-		return -EFAULT;
-	if (put_user(ect, uct))
-		return -EFAULT;
-	if (ect > ct)
-		return -ENOMEM;
-
-	return 0;
+	*uct = ect;
+	/* NOTE: For the correct functioning of the program setfont, it is
+	 * critical that -ENOMEM, not 0, is returned when ct is zero.  ACM,
+	 * 2025-02-12.
+	 */
+	return ret ? ret : (ect <= ct) ? 0 : -ENOMEM;
 }
 
 /*
@@ -849,26 +925,33 @@ int conv_uni_to_8bit(u32 uni)
 int conv_uni_to_pc(struct vc_data *conp, long ucs)
 {
 	struct uni_pagedict *dict;
-	u16 **dir, *row, glyph;
+	u1632 ***plane, **dir, *row, glyph;
 
-	/* Only 16-bit codes supported at this time */
-	if (ucs > 0xffff)
-		return -4;		/* Not found */
+	if (ucs >= MAX_GLYPH)
+		return -4;	/* Not found */
 	else if (ucs < 0x20)
 		return -1;		/* Not a printable character */
+	else if (ucs == 0xfeff || (ucs >= 0x200b && ucs <= 0x200f))
+		return -2;			/* Zero-width space */
 	/*
 	 * UNI_DIRECT_BASE indicates the start of the region in the User Zone
 	 * which always has a 1:1 mapping to the currently loaded font.  The
 	 * UNI_DIRECT_MASK indicates the bit span of the region.
 	 */
+#ifndef CONFIG_FB_GLYPH_21BIT
 	else if ((ucs & ~UNI_DIRECT_MASK) == UNI_DIRECT_BASE)
 		return ucs & UNI_DIRECT_MASK;
+#endif
 
 	dict = *conp->uni_pagedict_loc;
 	if (!dict)
 		return -3;
 
-	dir = dict->uni_pgdir[UNI_DIR(ucs)];
+	plane = dict->uni_plane[UNI_PLANE(ucs)];
+	if (!plane)
+		return -4;
+
+	dir = plane[UNI_DIR(ucs)];
 	if (!dir)
 		return -4;
 
@@ -897,4 +980,3 @@ console_map_init(void)
 		if (vc_cons_allocated(i) && !*vc_cons[i].d->uni_pagedict_loc)
 			con_set_default_unimap(vc_cons[i].d);
 }
-

-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [Patch 2/9]: Glyph size: Use GLYPH_SZ/HW rather than hardcoded 2, 1
  2026-08-27 18:38 vt: Enlarge the framebuffer glyph size from 16 to 32 bits Alan Mackenzie
  2026-08-27 18:42 ` [Patch 1/9]: Make consolemap.c handle Unicode planes outside BMP Alan Mackenzie
@ 2026-08-27 18:45 ` Alan Mackenzie
  2026-08-27 19:22   ` sashiko-bot
  2026-08-27 18:47 ` [Patch 3/9]: Replace scr_readw/writew by scr_readg/writeg, etc Alan Mackenzie
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-27 18:45 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, Thomas Zimmermann, linux-fbdev,
	dri-devel
  Cc: acm


vt: 32b glyph: 2. Use GLYPH_SZ, GLYPH_HW rather than hardcoded 2, 1.

These #defines enable the console to be built for either 16-bit or
32-bit glyphs.

Signed-off-by: Alan Mackenzie <acm@muc.de>

diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c
index 13f4e48b4142..18affdb3c7c5 100644
--- a/drivers/tty/vt/selection.c
+++ b/drivers/tty/vt/selection.c
@@ -53,8 +55,8 @@
 
 /* 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);
+	invert_screen(vc_sel.cons, s, e-s + GLYPH_SZ, true);
 }
 
 /* use complementary color to show the pointer */
@@ -67,7 +69,7 @@ static u32
 sel_pos(int n, bool unicode)
 {
 	if (unicode)
-		return screen_glyph_unicode(vc_sel.cons, n / 2);
+		return screen_glyph_unicode(vc_sel.cons, n / GLYPH_SZ);
 	return inverse_translate(vc_sel.cons, screen_glyph(vc_sel.cons, n),
 			false);
 }
@@ -136,7 +138,7 @@ int sel_loadlut(u32 __user *lut)
 /* does screen address p correspond to character at LH/RH edge of screen? */
 static inline int atedge(const int p, int size_row)
 {
-	return (!(p % size_row)	|| !((p + 2) % size_row));
+	return (!(p % size_row)	|| !((p + GLYPH_SZ) % size_row));
 }
 
 /* stores the char in UTF8 and returns the number of bytes used (1-4) */
@@ -214,8 +216,8 @@ static int vc_selection_store_chars(struct vc_data *vc, bool unicode)
 
 	/* 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);
+	bp = kmalloc_array((vc_sel.end - vc_sel.start) / GLYPH_SZ + 1,
+			   unicode ? 4 : 1, GFP_KERNEL | __GFP_NOWARN);
 	if (!bp) {
 		printk(KERN_WARNING "selection: kmalloc() failed\n");
 		clear_selection();
@@ -225,7 +227,7 @@ static int vc_selection_store_chars(struct vc_data *vc, bool unicode)
 	vc_sel.buffer = bp;
 
 	obp = bp;
-	for (i = vc_sel.start; i <= vc_sel.end; i += 2) {
+	for (i = vc_sel.start; i <= vc_sel.end; i += GLYPH_SZ) {
 		u32 c = sel_pos(i, unicode);
 		if (unicode)
 			bp += store_utf8(c, bp);
@@ -233,7 +235,7 @@ static int vc_selection_store_chars(struct vc_data *vc, bool unicode)
 			*bp++ = c;
 		if (!is_space_on_vt(c))
 			obp = bp;
-		if (!((i + 2) % vc->vc_size_row)) {
+		if (!((i + GLYPH_SZ) % size_row)) {
 			/* strip trailing blanks from line and add newline,
 			   unless non-space at end of line. */
 			if (obp != bp) {
@@ -261,7 +263,7 @@ static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps,
 		break;
 	case TIOCL_SELWORD:	/* word-by-word selection */
 		spc = is_space_on_vt(sel_pos(ps, unicode));
-		for (new_sel_start = ps; ; ps -= 2) {
+		for (new_sel_start = ps; ; ps -= GLYPH_SZ) {
 			if ((spc && !is_space_on_vt(sel_pos(ps, unicode))) ||
 			    (!spc && !inword(sel_pos(ps, unicode))))
 				break;
@@ -271,19 +273,19 @@ static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps,
 		}
 
 		spc = is_space_on_vt(sel_pos(pe, unicode));
-		for (new_sel_end = pe; ; pe += 2) {
+		for (new_sel_end = pe; ; pe += GLYPH_SZ) {
 			if ((spc && !is_space_on_vt(sel_pos(pe, unicode))) ||
 			    (!spc && !inword(sel_pos(pe, unicode))))
 				break;
 			new_sel_end = pe;
-			if (!((pe + 2) % vc->vc_size_row))
+			if (!((pe + GLYPH_SZ) % size_row))
 				break;
 		}
 		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);
+		new_sel_end = rounddown(pe, size_row) +
+			size_row - GLYPH_SZ;
 		break;
 	case TIOCL_SELPOINTER:
 		highlight_pointer(pe);
@@ -298,8 +300,8 @@ static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps,
 	/* 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)
+	    is_space_on_vt(sel_pos(new_sel_end, unicode))) {
+		for (pe = new_sel_end + GLYPH_SZ; ; pe += GLYPH_SZ)
 			if (!is_space_on_vt(sel_pos(pe, unicode)) ||
 			    atedge(pe, vc->vc_size_row))
 				break;
@@ -313,16 +315,16 @@ static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps,
 		if (new_sel_end == vc_sel.end)	/* no action required */
 			return 0;
 		else if (new_sel_end > vc_sel.end)	/* extend to right */
-			highlight(vc_sel.end + 2, new_sel_end);
+			highlight(vc_sel.end + GLYPH_SZ, new_sel_end);
 		else				/* contract from right */
-			highlight(new_sel_end + 2, vc_sel.end);
+			highlight(new_sel_end + GLYPH_SZ, vc_sel.end);
 	}
 	else if (new_sel_end == vc_sel.end)
 	{
 		if (new_sel_start < vc_sel.start) /* extend to left */
-			highlight(new_sel_start, vc_sel.start - 2);
+			highlight(new_sel_start, vc_sel.start - GLYPH_SZ);
 		else				/* contract from left */
-			highlight(vc_sel.start, new_sel_start - 2);
+			highlight(vc_sel.start, new_sel_start - GLYPH_SZ);
 	}
 	else	/* some other case; start selection from scratch */
 	{
@@ -360,8 +363,8 @@ static int vc_selection(struct vc_data *vc, struct tiocl_selection *v,
 		return 0;
 	}
 
-	ps = v->ys * vc->vc_size_row + (v->xs << 1);
-	pe = v->ye * vc->vc_size_row + (v->xe << 1);
+	ps = v->ys * size_row + (v->xs * GLYPH_SZ);
+	pe = v->ye * size_row + (v->xe * GLYPH_SZ);
 	if (ps > pe)	/* make vc_sel.start <= vc_sel.end */
 		swap(ps, pe);
 
diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c
index 7d40eacc21b3..f23d61fe10c1 100644
--- a/drivers/tty/vt/vc_screen.c
+++ b/drivers/tty/vt/vc_screen.c
@@ -213,16 +215,15 @@ static int vcs_size(const struct vc_data *vc, bool attr, bool unicode)
 
 	WARN_CONSOLE_UNLOCKED();
 
-	size = vc->vc_rows * vc->vc_cols;
-
-	if (attr) {
-		if (unicode)
-			return -EOPNOTSUPP;
-
-		size = 2 * size + HEADER_SIZE;
-	} else if (unicode)
-		size *= 4;
-
+	if (attr && unicode)
+		return -EOPNOTSUPP;
+	else if (unicode)
+		size = (vc->vc_rows * vc->vc_cols) * 4;
+	else if (attr)
+		size = (vc->vc_rows * vc->vc_cols) * GLYPH_SZ +
+			HEADER_SIZE;
+	else
+		size = (vc->vc_rows * vc->vc_cols);
 	return size;
 }
 
@@ -283,7 +288,8 @@ static void vcs_read_buf_noattr(const struct vc_data *vc, char *con_buf,
 	pos += maxcol - col;
 
 	while (count-- > 0) {
-		*con_buf++ = (vcs_scr_readw(vc, org++) & 0xff);
+		*con_buf++ = (vcs_scr_readw(vc, org) & 0xff);
+		org += GLYPH_HW;
 		if (++col == maxcol) {
 			org = screen_pos(vc, pos, viewed);
 			col = 0;
@@ -298,6 +304,7 @@ static unsigned int vcs_read_buf(const struct vc_data *vc, char *con_buf,
 {
 	u16 *org, *con_buf16;
 	unsigned int col, maxcol = vc->vc_cols;
+	unsigned int sz = GLYPH_SZ;
 	unsigned int filled = count;
 
 	if (pos < HEADER_SIZE) {
@@ -315,40 +324,60 @@ static unsigned int vcs_read_buf(const struct vc_data *vc, char *con_buf,
 
 		/* Advance state pointers and move on. */
 		count -= min(HEADER_SIZE, count);
+		/* COUNT is now a count of the number of bytes after the
+		 * header to be read.
+		 */
 		pos = HEADER_SIZE;
 		con_buf += HEADER_SIZE;
 		/* If count >= 0, then pos is even... */
-	} else if (pos & 1) {
+	} else if (pos & (sz - 1)) {
 		/*
-		 * Skip first byte for output if start address is odd. Update
-		 * region sizes up/down depending on free space in buffer.
+		 * Skip any unaligned bytes for output if the start address
+		 * is not on a glyph boundary.  Update region sizes up/down
+		 * depending on free space in buffer.
+		 */
+		(*skip) += (pos & (sz - 1));
+		if (count + (pos & (sz - 1)) <= CON_BUF_SIZE)
+			count += (pos & (sz - 1));
+		/* COUNT is now the number of bytes to be read starting at
+		 * the whole character at or before POS.
 		 */
-		(*skip)++;
-		if (count < CON_BUF_SIZE)
-			count++;
 		else
-			filled--;
+			filled -= (pos & (sz - 1));
 	}
 
 	if (!count)
 		return filled;
 
 	pos -= HEADER_SIZE;
-	pos /= 2;
+	pos /= sz;
 	col = pos % maxcol;
 
 	org = screen_pos(vc, pos, viewed);
 	pos += maxcol - col;
+	/* From now on, POS is the offset from the start of the screen in
+	 * characters of the next end of line.
+	 */
 
 	/*
 	 * Buffer has even length, so we can always copy character + attribute.
-	 * We do not copy last byte to userspace if count is odd.
+	 * We will not copy unaligned last bytes to userspace if count is not a
+	 * multiple of SZ.
+	 */
+	count = (count + sz - 1) / sz;
+	/* COUNT is now the number of screen characters to read, including any
+	 * leading/trailing bytes which the caller will discard.
 	 */
-	count = (count + 1) / 2;
 	con_buf16 = (u16 *)con_buf;
 
 	while (count) {
-		*con_buf16++ = vcs_scr_readw(vc, org++);
+#ifdef CONFIG_FB_GLYPH_21BIT
+		*(u32 *)con_buf16 = vcs_scr_readw(vc, org);
+#else
+		*con_buf16 = vcs_scr_readw(vc, org);
+#endif
+		con_buf16 += GLYPH_HW;
+		org += GLYPH_HW;
 		count--;
 		if (++col == maxcol) {
 			org = screen_pos(vc, pos, viewed);
@@ -481,12 +514,15 @@ static u16 *vcs_write_buf_noattr(struct vc_data *vc, const char *con_buf,
 	pos += maxcol - col;
 
 	while (count > 0) {
-		unsigned char c = *con_buf++;
+		unsigned int c;
 
+		c = *con_buf++;
 		count--;
 		vcs_scr_writew(vc,
-			       (vcs_scr_readw(vc, org) & 0xff00) | c, org);
-		org++;
+			       ((vcs_scr_readw(vc, org) & ~0xffu) |
+				(c & 0xff)),
+			       org);
+		org += GLYPH_HW;
 		if (++col == maxcol) {
 			org = screen_pos(vc, pos, viewed);
 			col = 0;
@@ -534,35 +560,55 @@ static u16 *vcs_write_buf(struct vc_data *vc, const char *con_buf,
 		return NULL;
 
 	pos -= HEADER_SIZE;
-	col = (pos/2) % maxcol;
+	col = (pos / sz) % maxcol;
 
-	*org0 = org = screen_pos(vc, pos/2, viewed);
+	*org0 = org = screen_pos(vc, pos / sz, viewed);
 
-	/* odd pos -- the first single character */
-	if (pos & 1) {
-		count--;
-		c = *con_buf++;
-		vcs_scr_writew(vc, vc_compile_le16(c, vcs_scr_readw(vc, org)),
-				org);
-		org++;
-		pos++;
+	/* Unaligned bytes at start of buffer */
+	if (pos & (sz - 1)) {
+		u32 old_char = vcs_scr_readw(vc, org);
+		u32 new_char = 0;
+		int old_bytes = pos & (sz - 1);
+
+		for (i = 0; i < sz; i++, old_char >>= 8)
+			lo_to_hi[i] = old_char & 0xff;
+		for (i = old_bytes; i < sz && count; i++, count--) {
+			c = *con_buf++;
+			pos++;
+#ifdef __BIG_ENDIAN
+			lo_to_hi[sz - i - 1] = c;
+#else
+			lo_to_hi[i] = c;
+#endif
+		}
+		for (i = sz - 1; i >= 0; i--) {
+			new_char <<= 8;
+			new_char |= lo_to_hi[i];
+		}
+		vcs_scr_writew(vc, new_char, org);
+		org += GLYPH_HW;
 		if (++col == maxcol) {
-			org = screen_pos(vc, pos/2, viewed);
+			org = screen_pos(vc, pos / sz, viewed);
 			col = 0;
 		}
 	}
 
-	pos /= 2;
+	pos /= sz;
 	pos += maxcol - col;
 
-	/* even pos -- handle attr+character pairs */
-	while (count > 1) {
-		unsigned short w;
+	/* aligned pos -- handle attr+character pairs */
+	while (count >= sz) {
+		unsigned int w;
 
+#ifdef CONFIG_FB_GLYPH_21BIT
+		w = get_unaligned(((unsigned int *)con_buf));
+#else
 		w = get_unaligned(((unsigned short *)con_buf));
-		vcs_scr_writew(vc, w, org++);
-		con_buf += 2;
-		count -= 2;
+#endif
+		vcs_scr_writew(vc, w, org);
+		org += GLYPH_HW;
+		con_buf += sz;
+		count -= sz;
 		if (++col == maxcol) {
 			org = screen_pos(vc, pos, viewed);
 			col = 0;
@@ -592,6 +654,7 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
 	ssize_t ret;
 	loff_t pos;
 	bool viewed, attr;
+	int unaligned_before;
 
 	if (use_unicode(inode))
 		return -EOPNOTSUPP;
@@ -620,12 +683,29 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
 	if (count > size - pos)
 		count = size - pos;
 	written = 0;
+
+	/* Ensure a region being written, except the last one, ends on a
+	 * character boundary.
+	 */
+	unaligned_before = (-pos) % GLYPH_SZ;
+	if (unaligned_before < 0)
+		unaligned_before += GLYPH_SZ;
+
 	while (count) {
 		unsigned int this_round = count;
 
 		if (this_round > CON_BUF_SIZE)
 			this_round = CON_BUF_SIZE;
 
+		if (unaligned_before) {
+			this_round += unaligned_before;
+			if (this_round > CON_BUF_SIZE)
+				this_round -= GLYPH_SZ;
+			if (this_round > count)
+				this_round = count;
+			unaligned_before = 0;
+		}
+
 		/* Temporarily drop the console lock so that we can read
 		 * in the write data from userspace safely.
 		 */
@@ -681,8 +761,13 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
 		written += this_round;
 		buf += this_round;
 		pos += this_round;
+		/* update_region needs ORG0 and ORG on character boundaries. */
+		org0 = (u16 *)((unsigned long)org0 & ~((unsigned long)GLYPH_SZ - 1));
+		org = (u16 *)(((unsigned long)org + GLYPH_SZ - 1) &
+			      ~((unsigned long)GLYPH_SZ - 1));
 		if (org)
-			update_region(vc, (unsigned long)(org0), org - org0);
+			update_region(vc, (unsigned long)(org0),
+				      (org - org0) / GLYPH_HW);
 	}
 	*ppos += written;
 	ret = written;
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 8f467b22b799..0c389a564357 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -566,7 +567,7 @@ 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;
-	int offset = row * vc->vc_size_row + col * 2;
+	int offset = row * vc->vc_size_row + col * GLYPH_SZ;
 	unsigned long pos;
 
 	if (WARN_ON_ONCE(!uni_lines))
@@ -580,7 +581,8 @@ void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
 		 * scrollback is active.
 		 */
 		row = (pos - vc->vc_origin) / vc->vc_size_row;
-		col = ((pos - vc->vc_origin) % vc->vc_size_row) / 2;
+		col = ((pos - vc->vc_origin) % vc->vc_size_row) /
+			GLYPH_SZ;
 		memcpy(dest, &uni_lines[row][col], nr * sizeof(u32));
 	} else {
 		/*
@@ -620,7 +622,7 @@ static void con_scroll(struct vc_data *vc, unsigned int top,
 	dst = (u16 *)(vc->vc_origin + vc->vc_size_row * (top + nr));
 
 	if (dir == SM_UP) {
-		clear = src + (rows - nr) * vc->vc_cols;
+		clear = src + (rows - nr) * vc->vc_cols * GLYPH_HW;
 		swap(src, dst);
 	}
 	scr_memmovew(dst, src, (rows - nr) * vc->vc_size_row);
@@ -632,7 +634,7 @@ static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 	unsigned int xx, yy, offset;
 	u16 *p = (u16 *)start;
 
-	offset = (start - vc->vc_origin) / 2;
+	offset = (start - vc->vc_origin) / GLYPH_SZ;
 	xx = offset % vc->vc_cols;
 	yy = offset / vc->vc_cols;
 
@@ -653,7 +657,9 @@ static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 			count--;
 		}
 		if (p > q)
-			vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+			vc->vc_sw->con_putcs(vc, q,
+					     (p-q) / GLYPH_HW,
+					     yy, startx);
 		if (!count)
 			break;
 		xx = 0;
@@ -736,7 +740,7 @@ void invert_screen(struct vc_data *vc, int offset, int count, bool viewed)
 
 	WARN_CONSOLE_UNLOCKED();
 
-	count /= 2;
+	count /= GLYPH_SZ;
 	p = screenpos(vc, offset, viewed);
 	if (vc->vc_sw->con_invert_region) {
 		vc->vc_sw->con_invert_region(vc, p, count);
@@ -818,8 +809,9 @@ static void insert_char(struct vc_data *vc, unsigned int nr)
 	unsigned short *p = (unsigned short *) vc->vc_pos;
 
 	vc_uniscr_insert(vc, nr);
-	scr_memmovew(p + nr, p, (vc->vc_cols - vc->state.x - nr) * 2);
-	scr_memsetw(p, vc->vc_video_erase_char, nr * 2);
+	scr_memmovew(p + nr * GLYPH_HW,
+		     p, (vc->vc_cols - vc->state.x - nr) * GLYPH_SZ);
+	scr_memset_worl(p, vc->vc_video_erase_char, nr * GLYPH_SZ);
 	vc->vc_need_wrap = 0;
 	if (con_should_update(vc))
 		do_update_region(vc, (unsigned long) p,
@@ -831,9 +823,10 @@ static void delete_char(struct vc_data *vc, unsigned int nr)
 	unsigned short *p = (unsigned short *) vc->vc_pos;
 
 	vc_uniscr_delete(vc, nr);
-	scr_memmovew(p, p + nr, (vc->vc_cols - vc->state.x - nr) * 2);
-	scr_memsetw(p + vc->vc_cols - vc->state.x - nr, vc->vc_video_erase_char,
-			nr * 2);
+	scr_memmovew(p, p + nr * GLYPH_HW,
+		     (vc->vc_cols - vc->state.x - nr) * GLYPH_SZ);
+	scr_memset_worl(p + (vc->vc_cols - vc->state.x - nr) * GLYPH_HW,
+		    vc->vc_video_erase_char, nr * GLYPH_SZ);
 	vc->vc_need_wrap = 0;
 	if (con_should_update(vc))
 		do_update_region(vc, (unsigned long) p,
@@ -909,7 +902,7 @@ static void set_origin(struct vc_data *vc)
 	vc->vc_visible_origin = vc->vc_origin;
 	vc->vc_scr_end = vc->vc_origin + vc->vc_screenbuf_size;
 	vc->vc_pos = vc->vc_origin + vc->vc_size_row * vc->state.y +
-		2 * vc->state.x;
+		GLYPH_SZ * vc->state.x;
 }
 
 static void save_screen(struct vc_data *vc)
@@ -1005,7 +999,9 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 		}
 
 		if (update && vc->vc_mode != KD_GRAPHICS)
-			do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+			do_update_region(vc, vc->vc_origin,
+					 vc->vc_screenbuf_size /
+					 GLYPH_SZ);
 	}
 	set_cursor(vc);
 	if (is_switch) {
@@ -1184,6 +1217,6 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 	new_cols = (cols ? cols : vc->vc_cols);
 	new_rows = (lines ? lines : vc->vc_rows);
-	new_row_size = new_cols << 1;
+	new_row_size = new_cols * GLYPH_SZ;
 	new_screen_size = new_row_size * new_rows;
 
 	if (new_cols == vc->vc_cols && new_rows == vc->vc_rows) {
@@ -1233,13 +1266,9 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 		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;
+	rlth = min(old_row_size / GLYPH_SZ,
+		   new_row_size / GLYPH_SZ);
+	rrem = (new_row_size / GLYPH_SZ) - rlth;
 	old_origin = vc->vc_origin;
 	new_origin = (long) newscreen;
 	new_scr_end = new_origin + new_screen_size;
@@ -1264,24 +1293,37 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 	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,
+			    vc->vc_uni_lines, rlth, first_copied_row,
 			    min(old_rows, new_rows));
 	vc_uniscr_set(vc, new_uniscr);
 
+	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);
 
 	while (old_origin < end) {
 		scr_memcpyw((unsigned short *) new_origin,
-			    (unsigned short *) old_origin, rlth);
+			    (unsigned short *) old_origin,
+			    rlth * GLYPH_SZ);
 		if (rrem)
-			scr_memsetw((void *)(new_origin + rlth),
-				    vc->vc_video_erase_char, rrem);
+			scr_memset_worl((void *) (new_origin +
+						  rlth * GLYPH_SZ),
+					new_vc_video_erase_char,
+					rrem * GLYPH_SZ);
 		old_origin += old_row_size;
 		new_origin += new_row_size;
 	}
 	if (new_scr_end > new_origin)
-		scr_memsetw((void *)new_origin, vc->vc_video_erase_char,
-			    new_scr_end - new_origin);
+		scr_memset_worl((void *)new_origin,
+				new_vc_video_erase_char,
+				new_scr_end - new_origin);
+	vc->vc_char_mask = new_vc_char_mask;
+	vc->vc_attr_mask = new_vc_attr_mask;
+	vc->vc_attr_shift_pos = new_vc_attr_shift_pos;
+	vc->vc_video_erase_char = new_vc_video_erase_char;
 	oldscreen = vc->vc_screenbuf;
 	vc->vc_screenbuf = newscreen;
 	vc->vc_screenbuf_size = new_screen_size;
@@ -1452,7 +1494,7 @@ static void gotoxy(struct vc_data *vc, int new_x, int new_y)
 	else
 		vc->state.y = new_y;
 	vc->vc_pos = vc->vc_origin + vc->state.y * vc->vc_size_row +
-		(vc->state.x << 1);
+		(vc->state.x * GLYPH_SZ);
 	vc->vc_need_wrap = 0;
 }
 
@@ -1505,7 +1547,7 @@ static void ri(struct vc_data *vc)
 
 static inline void cr(struct vc_data *vc)
 {
-	vc->vc_pos -= vc->state.x << 1;
+	vc->vc_pos -= vc->state.x * GLYPH_SZ;
 	vc->vc_need_wrap = vc->state.x = 0;
 	notify_write(vc, '\r');
 }
@@ -1513,7 +1555,7 @@ static inline void cr(struct vc_data *vc)
 static inline void bs(struct vc_data *vc)
 {
 	if (vc->state.x) {
-		vc->vc_pos -= 2;
+		vc->vc_pos -= GLYPH_SZ;
 		vc->state.x--;
 		vc->vc_need_wrap = 0;
 		notify_write(vc, '\b');
@@ -1543,13 +1585,13 @@ static void csi_J(struct vc_data *vc, enum CSI_J vpar)
 				     vc->vc_cols - vc->state.x);
 		vc_uniscr_clear_lines(vc, vc->state.y + 1,
 				      vc->vc_rows - vc->state.y - 1);
-		count = (vc->vc_scr_end - vc->vc_pos) >> 1;
+		count = (vc->vc_scr_end - vc->vc_pos) / GLYPH_SZ;
 		start = (unsigned short *)vc->vc_pos;
 		break;
 	case CSI_J_START_TO_CURSOR:
 		vc_uniscr_clear_line(vc, 0, vc->state.x + 1);
 		vc_uniscr_clear_lines(vc, 0, vc->state.y);
-		count = ((vc->vc_pos - vc->vc_origin) >> 1) + 1;
+		count = ((vc->vc_pos - vc->vc_origin) / GLYPH_SZ) + 1;
 		start = (unsigned short *)vc->vc_origin;
 		break;
 	case CSI_J_FULL:
@@ -1563,7 +1605,8 @@ static void csi_J(struct vc_data *vc, enum CSI_J vpar)
 	default:
 		return;
 	}
-	scr_memsetw(start, vc->vc_video_erase_char, 2 * count);
+	scr_memset_worl(start, vc->vc_video_erase_char,
+			GLYPH_SZ * count);
 	if (con_should_update(vc))
 		do_update_region(vc, (unsigned long) start, count);
 	vc->vc_need_wrap = 0;
@@ -1598,10 +1641,13 @@ static void csi_K(struct vc_data *vc)
 		return;
 	}
 	vc_uniscr_clear_line(vc, vc->state.x + offset, count);
-	scr_memsetw(start + offset, vc->vc_video_erase_char, 2 * count);
+	scr_memset_worl(start + offset * (int)GLYPH_HW,
+			vc->vc_video_erase_char, GLYPH_SZ * count);
 	vc->vc_need_wrap = 0;
 	if (con_should_update(vc))
-		do_update_region(vc, (unsigned long)(start + offset), count);
+		do_update_region(vc, (unsigned long)(start + offset *
+						     (int)GLYPH_HW),
+				 count);
 }
 
 /* erase the following count positions */
@@ -1610,7 +1656,8 @@ static void csi_X(struct vc_data *vc)
 	unsigned int count = clamp(vc->vc_par[0], 1, vc->vc_cols - vc->state.x);
 
 	vc_uniscr_clear_line(vc, vc->state.x, count);
-	scr_memsetw((unsigned short *)vc->vc_pos, vc->vc_video_erase_char, 2 * count);
+	scr_memset_worl((unsigned short *)vc->vc_pos, vc->vc_video_erase_char,
+			GLYPH_SZ * count);
 	if (con_should_update(vc))
 		vc->vc_sw->con_clear(vc, vc->state.y, vc->state.x, count);
 	vc->vc_need_wrap = 0;
@@ -1912,7 +1959,7 @@ static int get_bracketed_paste(struct tty_struct *tty)
 /* console_lock is held */
 static void enter_alt_screen(struct vc_data *vc)
 {
-	unsigned int size = vc->vc_rows * vc->vc_cols * 2;
+	unsigned int size = vc->vc_rows * vc->vc_cols * GLYPH_SZ;
 
 	if (vc->vc_saved_screen != NULL)
 		return; /* Already inside an alt-screen */
@@ -1938,9 +1985,11 @@ static void leave_alt_screen(struct vc_data *vc)
 	if (vc->vc_saved_screen == NULL)
 		return; /* Not inside an alt-screen */
 	for (unsigned int r = 0; r < rows; r++) {
-		src = vc->vc_saved_screen + r * vc->vc_saved_cols;
-		dest = ((u16 *)vc->vc_origin) + r * vc->vc_cols;
-		memcpy(dest, src, 2 * cols);
+		src = vc->vc_saved_screen +
+			GLYPH_HW * r * vc->vc_saved_cols;
+		dest = ((u16 *)vc->vc_origin) +
+			GLYPH_HW * r * vc->vc_cols;
+		memcpy(dest, src, GLYPH_SZ * cols);
 	}
 	/*
 	 * If the console was resized while in the alternate screen,
@@ -1965,7 +2014,8 @@ static void leave_alt_screen(struct vc_data *vc)
 	restore_cur(vc);
 	/* Update the entire screen */
 	if (con_should_update(vc))
-		do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+		do_update_region(vc, vc->vc_origin,
+				 vc->vc_screenbuf_size / GLYPH_SZ);
 	kfree(vc->vc_saved_screen);
 	vc->vc_saved_screen = NULL;
 }
@@ -2385,7 +2437,7 @@ static bool handle_ascii(struct tty_struct *tty, struct vc_data *vc, u8 c)
 		bs(vc);
 		return true;
 	case ASCII_HTAB:
-		vc->vc_pos -= (vc->state.x << 1);
+		vc->vc_pos -= (vc->state.x * GLYPH_SZ);
 
 		vc->state.x = find_next_bit(vc->vc_tab_stop,
 				min(vc->vc_cols - 1, VC_TABSTOPS_COUNT),
@@ -2393,7 +2445,7 @@ static bool handle_ascii(struct tty_struct *tty, struct vc_data *vc, u8 c)
 		if (vc->state.x >= VC_TABSTOPS_COUNT)
 			vc->state.x = vc->vc_cols - 1;
 
-		vc->vc_pos += (vc->state.x << 1);
+		vc->vc_pos += (vc->state.x * GLYPH_SZ);
 		notify_write(vc, '\t');
 		return true;
 	case ASCII_LINEFEED:
@@ -2895,8 +2949,9 @@ static void con_flush(struct vc_data *vc, struct vc_draw_region *draw)
 		return;
 
 	vc->vc_sw->con_putcs(vc, (u16 *)draw->from,
-			(u16 *)draw->to - (u16 *)draw->from, vc->state.y,
-			draw->x);
+			     (int)((u16 *)draw->to - (u16 *)draw->from) /
+			     GLYPH_HW,
+			     vc->state.y, draw->x);
 	draw->x = -1;
 }
 
@@ -3081,7 +3138,7 @@ static bool vc_is_control(struct vc_data *vc, int tc, int c)
 static void vc_con_rewind(struct vc_data *vc)
 {
 	if (vc->state.x && !vc->vc_need_wrap) {
-		vc->vc_pos -= 2;
+		vc->vc_pos -= GLYPH_SZ;
 		vc->state.x--;
 	}
 	vc->vc_need_wrap = 0;
@@ -3111,7 +3168,7 @@ static int vc_process_ucs(struct vc_data *vc, int *c, int *tc)
 
 	/* From here curr_c is known to be zero-width. */
 
-	if (ucs_get_width(vc_uniscr_getc(vc, -2)) == 2) {
+	if (ucs_get_width(vc_uniscr_getc(vc, -GLYPH_SZ)) == 2) {
 		/*
 		 * Let's merge this zero-width code point with the preceding
 		 * double-width code point by replacing the existing
@@ -3243,10 +3298,10 @@ static int vc_con_write_normal(struct vc_data *vc, int tc, int c,
 		}
 		if (vc->state.x == vc->vc_cols - 1) {
 			vc->vc_need_wrap = vc->vc_decawm;
-			draw->to = vc->vc_pos + 2;
+			draw->to = vc->vc_pos + GLYPH_SZ;
 		} else {
 			vc->state.x++;
-			draw->to = (vc->vc_pos += 2);
+			draw->to = (vc->vc_pos += GLYPH_SZ);
 		}
 
 		if (!--width)
@@ -3508,7 +3564,7 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
 		if (vc->state.x == vc->vc_cols - 1) {
 			vc->vc_need_wrap = 1;
 		} else {
-			vc->vc_pos += 2;
+			vc->vc_pos += GLYPH_SZ;
 			vc->state.x++;
 		}
 	}
@@ -5065,14 +5125,15 @@ u32 screen_glyph_unicode(const struct vc_data *vc, int n)
 	if (uni_lines)
 		return uni_lines[n / vc->vc_cols][n % vc->vc_cols];
 
-	return inverse_translate(vc, screen_glyph(vc, n * 2), true);
+	return inverse_translate(vc, screen_glyph(vc, n * GLYPH_SZ),
+				 true);
 }
 EXPORT_SYMBOL_GPL(screen_glyph_unicode);
 
-/* used by vcs - note the word offset */
+/* used by vcs - note the (screen)char sized offset */
 unsigned short *screen_pos(const struct vc_data *vc, int w_offset, bool viewed)
 {
-	return screenpos(vc, 2 * w_offset, viewed);
+	return screenpos(vc, GLYPH_SZ * w_offset, viewed);
 }
 EXPORT_SYMBOL_GPL(screen_pos);
 
diff --git a/drivers/video/fbdev/core/bitblit.c b/drivers/video/fbdev/core/bitblit.c
index 65681dcc5930..84e073162311 100644
--- a/drivers/video/fbdev/core/bitblit.c
+++ b/drivers/video/fbdev/core/bitblit.c
@@ -214,7 +213,7 @@ static void bit_putcs(struct vc_data *vc, struct fb_info *info,
 
 		image.dx += cnt * vc->vc_font.width;
 		count -= cnt;
-		s += cnt;
+		s += GLYPH_HW * cnt;
 	}
 
 	/* buf is always NULL except when in monochrome mode, so in this case
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 9f5c4c101581..18fbf7cf11dd 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -668,22 +668,28 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
 			break;
 	if (r != q && new_rows >= rows + logo_lines) {
-		save = kmalloc(array3_size(logo_lines, new_cols, 2),
+		save = kmalloc(array3_size(logo_lines, new_cols, GLYPH_SZ),
 			       GFP_KERNEL);
 		if (save) {
 			int i = min(cols, new_cols);
-			scr_memsetw(save, erase, array3_size(logo_lines, new_cols, 2));
-			r = q - step;
-			for (cnt = 0; cnt < logo_lines; cnt++, r += i)
-				scr_memcpyw(save + cnt * new_cols, r, 2 * i);
+			scr_memset_worl(save, erase, array3_size(logo_lines,
+								 new_cols,
+								 GLYPH_SZ));
+			r = q - step * GLYPH_HW;
+			for (cnt = 0; cnt < logo_lines;
+			     cnt++, r += i * GLYPH_HW)
+				scr_memcpyw(save +
+					    cnt * new_cols * GLYPH_HW,
+					    r, GLYPH_SZ * i);
 			r = q;
 		}
 	}
 	if (r == q) {
 		/* We can scroll screen down */
-		r = q - step - cols;
+		r = q - (step + cols) * GLYPH_HW;
 		for (cnt = rows - logo_lines; cnt > 0; cnt--) {
-			scr_memcpyw(r + step, r, vc->vc_size_row);
-			r -= cols;
+			scr_memcpyw(r + step * GLYPH_HW,
+				    r, vc->vc_size_row);
+			r -= cols * GLYPH_HW;
 		}
 		if (!save) {
 			int lines;
@@ -708,7 +714,7 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
 		q = (unsigned short *) (vc->vc_origin +
 					vc->vc_size_row *
 					rows);
-		scr_memcpyw(q, save, array3_size(logo_lines, new_cols, 2));
+		scr_memcpyw(q, save, array3_size(logo_lines, new_cols, GLYPH_SZ));
 		vc->state.y += logo_lines;
 		vc->vc_pos += logo_lines * vc->vc_size_row;
 		kfree(save);
@@ -1714,7 +1734,7 @@ static void fbcon_redraw(struct vc_data *vc, int line, int count, int offset)
 {
 	unsigned short *d = (unsigned short *)
 	    (vc->vc_origin + vc->vc_size_row * line);
-	unsigned short *s = d + offset;
+	unsigned short *s = d + ((long) offset) * GLYPH_HW;
 
 	while (count--) {
 		unsigned short *start = s;
@@ -2260,7 +2296,7 @@ static bool fbcon_switch(struct vc_data *vc)
 		update_region(vc,
 			      vc->vc_origin + vc->vc_size_row * vc->vc_top,
 			      vc->vc_size_row * (vc->vc_bottom -
-						 vc->vc_top) / 2);
+						 vc->vc_top) / GLYPH_SZ);
 		return false;
 	}
 	return true;
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index fe915afdece5..5afce52057ca 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -29,6 +30,14 @@ enum vc_intensity {
 	VCI_MASK = 0x3,
 };
 
+#ifdef CONFIG_FB_GLYPH_21BIT
+#define GLYPH_SZ 4		/* Size of glyph in bytes */
+#define GLYPH_HW 2		/* Size of glyph in u16s ("Half Words") */
+#else
+#define GLYPH_SZ 2
+#define GLYPH_HW 1
+#endif
+
 /**
  * struct vc_state -- state of a VC
  * @x: cursor's x-position


-- 
Alan Mackenzie (Nuremberg, Germany)

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [Patch 3/9]: Replace scr_readw/writew by scr_readg/writeg, etc
  2026-08-27 18:38 vt: Enlarge the framebuffer glyph size from 16 to 32 bits Alan Mackenzie
  2026-08-27 18:42 ` [Patch 1/9]: Make consolemap.c handle Unicode planes outside BMP Alan Mackenzie
  2026-08-27 18:45 ` [Patch 2/9]: Glyph size: Use GLYPH_SZ/HW rather than hardcoded 2, 1 Alan Mackenzie
@ 2026-08-27 18:47 ` Alan Mackenzie
  2026-08-27 19:18   ` sashiko-bot
  2026-08-29 12:11   ` Greg Kroah-Hartman
  2026-08-27 18:48 ` [Patch 4/9]: Amend internal manipulation of glyph structure Alan Mackenzie
                   ` (6 subsequent siblings)
  9 siblings, 2 replies; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-27 18:47 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, Thomas Zimmermann, linux-fbdev,
	dri-devel
  Cc: acm


vt: 32b glyph: 3. replace scr_readw/writew by scr_readg/writeg, etc.

The former are hardcoded to work with 16-bit glyphs, the latter
handle glyphs correctly regardless of whether they are 16-bit
or 32-bit.  Introduce scr_readg/writeg_plusplus correctly to
handle post-increment operands for these functions.

Signed-off-by: Alan Mackenzie <acm@muc.de>

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 8f467b22b799..0c389a564357 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -547,7 +548,7 @@ int vc_uniscr_check(struct vc_data *vc)
 	for (y = 0; y < vc->vc_rows; y++) {
 		u32 *line = uni_lines[y];
 		for (x = 0; x < vc->vc_cols; x++) {
-			u16 glyph = scr_readw(p++) & mask;
+			u32 glyph = scr_readg_plusplus(p++) & vc->vc_char_mask;
 			line[x] = inverse_translate(vc, glyph, true);
 		}
 	}
@@ -593,7 +595,7 @@ void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
 		int mask = vc->vc_hi_font_mask | 0xff;
 		u32 *uni_buf = dest;
 		while (nr--) {
-			u16 glyph = scr_readw(p++) & mask;
+			u32 glyph = scr_readg_plusplus(p++) & mask;
 			*uni_buf++ = inverse_translate(vc, glyph, true);
 		}
 	}
@@ -624,7 +626,7 @@ static void con_scroll(struct vc_data *vc, unsigned int top,
 		swap(src, dst);
 	}
 	scr_memmovew(dst, src, (rows - nr) * vc->vc_size_row);
-	scr_memsetw(clear, vc->vc_video_erase_char, vc->vc_size_row * nr);
+	scr_memset_worl(clear, vc->vc_video_erase_char, vc->vc_size_row * nr);
 }
 
 static void do_update_region(struct vc_data *vc, unsigned long start, int count)
@@ -637,18 +639,20 @@ static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 	yy = offset / vc->vc_cols;
 
 	for(;;) {
-		u16 attrib = scr_readw(p) & 0xff00;
+		u32 attrib = scr_readg(p) & vc->vc_attr_mask;
 		int startx = xx;
 		u16 *q = p;
 		while (xx < vc->vc_cols && count) {
-			if (attrib != (scr_readw(p) & 0xff00)) {
+			if (attrib != (scr_readg(p) & vc->vc_attr_mask)) {
 				if (p > q)
-					vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+					vc->vc_sw->con_putcs(vc, q,
+							     (p-q) / GLYPH_HW,
+							     yy, startx);
 				startx = xx;
 				q = p;
-				attrib = scr_readw(p) & 0xff00;
+				attrib = scr_readg(p) & vc->vc_attr_mask;
 			}
-			p++;
+			p += GLYPH_HW;
 			xx++;
 			count--;
 		}
@@ -789,7 +780,7 @@ void complement_pos(struct vc_data *vc, int offset)
 
 	if (old_offset != -1 && old_offset >= 0 &&
 	    old_offset < vc->vc_screenbuf_size) {
-		scr_writew(old, screenpos(vc, old_offset, true));
+		scr_writeg(old, screenpos(vc, old_offset, true));
 		if (con_should_update(vc))
 			con_putc(vc, old, oldy, oldx);
 		notify_update(vc);
@@ -799,14 +790,14 @@ void complement_pos(struct vc_data *vc, int offset)
 
 	if (offset != -1 && offset >= 0 &&
 	    offset < vc->vc_screenbuf_size) {
-		unsigned short new;
+		unsigned int new;
 		u16 *p = screenpos(vc, offset, true);
-		old = scr_readw(p);
+		old = scr_readg(p);
 		new = old ^ vc->vc_complement_mask;
-		scr_writew(new, p);
+		scr_writeg(new, p);
 		if (con_should_update(vc)) {
-			oldx = (offset >> 1) % vc->vc_cols;
-			oldy = (offset >> 1) / vc->vc_cols;
+			oldx = (offset / GLYPH_SZ) % vc->vc_cols;
+			oldy = (offset / GLYPH_SZ) / vc->vc_cols;
 			con_putc(vc, new, oldy, oldx);
 		}
 		notify_update(vc);
@@ -844,7 +837,7 @@ static int softcursor_original = -1;
 
 static void add_softcursor(struct vc_data *vc)
 {
-	int i = scr_readw((u16 *) vc->vc_pos);
+	int i = scr_readg((u16 *) vc->vc_pos);
 	u32 type = vc->vc_cursor_type;
 
 	if (!(type & CUR_SW))
@@ -859,7 +852,7 @@ static void add_softcursor(struct vc_data *vc)
 		i ^= CUR_BG;
 	if ((type & CUR_INVERT_FG_BG) && (i & CUR_FG) == ((i & CUR_BG) >> 4))
 		i ^= CUR_FG;
-	scr_writew(i, (u16 *)vc->vc_pos);
+	scr_writeg(i, (u16 *)vc->vc_pos);
 	if (con_should_update(vc))
 		con_putc(vc, i, vc->state.y, vc->state.x);
 }
@@ -867,7 +860,7 @@ static void add_softcursor(struct vc_data *vc)
 static void hide_softcursor(struct vc_data *vc)
 {
 	if (softcursor_original != -1) {
-		scr_writew(softcursor_original, (u16 *)vc->vc_pos);
+		scr_writeg(softcursor_original, (u16 *)vc->vc_pos);
 		if (con_should_update(vc))
 			con_putc(vc, softcursor_original, vc->state.y,
 				 vc->state.x);
@@ -946,11 +939,12 @@ static void flush_scrollback(struct vc_data *vc)
 void clear_buffer_attributes(struct vc_data *vc)
 {
 	unsigned short *p = (unsigned short *)vc->vc_origin;
-	int count = vc->vc_screenbuf_size / 2;
-	int mask = vc->vc_hi_font_mask | 0xff;
+	int count = vc->vc_screenbuf_size / GLYPH_SZ;
+	unsigned int mask = vc->vc_char_mask;
 
-	for (; count > 0; count--, p++) {
-		scr_writew((scr_readw(p)&mask) | (vc->vc_video_erase_char & ~mask), p);
+	for (; count > 0; count--, p += GLYPH_HW) {
+		scr_writeg((scr_readg(p)&mask) |
+			   (vc->vc_video_erase_char & ~mask), p);
 	}
 }
 
@@ -3231,11 +3287,10 @@ static int vc_con_write_normal(struct vc_data *vc, int tc, int c,
 		vc_uniscr_putc(vc, next_c);
 
 		if (himask)
-			tc = ((tc & 0x100) ? himask : 0) |
-			      (tc &  0xff);
-		tc |= (vc_attr << 8) & ~himask;
+			tc = tc & vc->vc_char_mask;
+		tc |= (vc_attr << vc->vc_attr_shift_pos);
 
-		scr_writew(tc, (u16 *)vc->vc_pos);
+		scr_writeg(tc, (u16 *)vc->vc_pos);
 
 		if (con_should_update(vc) && draw->x < 0) {
 			draw->x = vc->state.x;
@@ -3502,7 +3557,8 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
 				continue;
 		}
 		vc_uniscr_putc(vc, c);
-		scr_writew((vc->vc_attr << 8) + c, (unsigned short *)vc->vc_pos);
+		scr_writeg((vc->vc_attr << vc->vc_attr_shift_pos) + c,
+			   (unsigned short *)vc->vc_pos);
 		notify_write(vc, c);
 		cnt++;
 		if (vc->state.x == vc->vc_cols - 1) {
@@ -5047,13 +5110,10 @@ int con_font_op(struct vc_data *vc, struct console_font_op *op)
  */
 
 /* used by selection */
-u16 screen_glyph(const struct vc_data *vc, int offset)
+u32 screen_glyph(const struct vc_data *vc, int offset)
 {
-	u16 w = scr_readw(screenpos(vc, offset, true));
-	u16 c = w & 0xff;
-
-	if (w & vc->vc_hi_font_mask)
-		c |= 0x100;
+	u32 w = scr_readg(screenpos(vc, offset, true));
+	u32 c = w & vc->vc_char_mask;
 	return c;
 }
 EXPORT_SYMBOL_GPL(screen_glyph);
@@ -5090,16 +5151,16 @@ void putconsxy(struct vc_data *vc, unsigned char xy[static const 2])
 	set_cursor(vc);
 }
 
-u16 vcs_scr_readw(const struct vc_data *vc, const u16 *org)
+u32 vcs_scr_readw(const struct vc_data *vc, const u16 *org)
 {
 	if ((unsigned long)org == vc->vc_pos && softcursor_original != -1)
 		return softcursor_original;
-	return scr_readw(org);
+	return scr_readg(org);
 }
 
-void vcs_scr_writew(struct vc_data *vc, u16 val, u16 *org)
+void vcs_scr_writew(struct vc_data *vc, u32 val, u16 *org)
 {
-	scr_writew(val, org);
+	scr_writeg(val, org);
 	if ((unsigned long)org == vc->vc_pos) {
 		softcursor_original = -1;
 		add_softcursor(vc);
diff --git a/drivers/video/fbdev/core/bitblit.c b/drivers/video/fbdev/core/bitblit.c
index 65681dcc5930..84e073162311 100644
--- a/drivers/video/fbdev/core/bitblit.c
+++ b/drivers/video/fbdev/core/bitblit.c
@@ -83,12 +83,11 @@ static inline void bit_putcs_aligned(struct vc_data *vc, struct fb_info *info,
 	const u8 *src;
 
 	while (cnt--) {
-		u16 ch = scr_readw(s++) & charmask;
+		u32 ch = scr_readg_plusplus(s++) & charmask;
 
 		if (ch >= charcnt)
 			ch = 0;
-		src = vc->vc_font.data + (unsigned int)ch * cellsize;
-
+		src = vc->vc_font.data + ch * cellsize;
 		if (attr) {
 			update_attr(buf, src, attr, vc);
 			src = buf;
@@ -122,7 +121,7 @@ static inline void bit_putcs_unaligned(struct vc_data *vc,
 	const u8 *src;
 
 	while (cnt--) {
-		u16 ch = scr_readw(s++) & charmask;
+		u32 ch = scr_readg_plusplus(s++) & charmask;
 
 		if (ch >= charcnt)
 			ch = 0;
@@ -157,7 +156,7 @@ static void bit_putcs(struct vc_data *vc, struct fb_info *info,
 	u32 scan_align = info->pixmap.scan_align - 1;
 	u32 buf_align = info->pixmap.buf_align - 1;
 	u32 mod = vc->vc_font.width % 8, cnt, pitch, size;
-	u32 attribute = get_attribute(info, scr_readw(s));
+	u32 attribute = get_attribute(info, scr_readg(s));
 	u8 *dst, *buf = NULL;
 
 	image.fg_color = fg;
@@ -273,7 +272,7 @@ static void bit_cursor(struct vc_data *vc, struct fb_info *info, bool enable,
 	if (!vc->vc_font.data)
 		return;
 
- 	c = scr_readw((u16 *) vc->vc_pos);
+	c = scr_readg((u16 *) vc->vc_pos);
 	attribute = get_attribute(info, c);
 	src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height));
 
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 9f5c4c101581..18fbf7cf11dd 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -418,7 +418,7 @@ static void fb_flashcursor(struct work_struct *work)
 		return;
 	}
 
-	c = scr_readw((u16 *) vc->vc_pos);
+	c = scr_readg((u16 *) vc->vc_pos);
 	enable = par->cursor_flash && !par->cursor_state.enable;
 	par->bitops->cursor(vc, info, enable,
 			    get_fg_color(vc, info, c),
@@ -663,7 +663,7 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
 	q = (unsigned short *) (vc->vc_origin +
 				vc->vc_size_row * rows);
 	step = logo_lines * cols;
-	for (r = q - logo_lines * cols; r < q; r++)
-		if (scr_readw(r) != vc->vc_video_erase_char)
+	for (r = q - logo_lines * cols * GLYPH_HW; r < q; r++)
+		if (scr_readg(r) != vc->vc_video_erase_char)
 			break;
 	if (r != q && new_rows >= rows + logo_lines) {
@@ -695,9 +701,9 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
 			vc->vc_pos += lines * vc->vc_size_row;
 		}
 	}
-	scr_memsetw((unsigned short *) vc->vc_origin,
-		    erase,
-		    vc->vc_size_row * logo_lines);
+	scr_memset_worl((unsigned short *) vc->vc_origin,
+			erase,
+			vc->vc_size_row * logo_lines);
 
 	if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) {
 		fbcon_clear_margins(vc, 0);
@@ -1390,8 +1396,8 @@ static void fbcon_putcs(struct vc_data *vc, const u16 *s, unsigned int count,
 
 	if (fbcon_is_active(vc, info))
 		par->bitops->putcs(vc, info, s, count, real_y(p, ypos), xpos,
-				   get_fg_color(vc, info, scr_readw(s)),
-				   get_bg_color(vc, info, scr_readw(s)));
+				   get_fg_color(vc, info, scr_readg(s)),
+				   get_bg_color(vc, info, scr_readg(s)));
 }
 
 static void fbcon_clear_margins(struct vc_data *vc, int bottom_only)
@@ -1407,7 +1413,7 @@ static void fbcon_cursor(struct vc_data *vc, bool enable)
 {
 	struct fb_info *info = fbcon_info_from_console(vc->vc_num);
 	struct fbcon_par *par = info->fbcon_par;
- 	int c = scr_readw((u16 *) vc->vc_pos);
+	int c = scr_readg((u16 *) vc->vc_pos);
 
 	par->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
 
@@ -1645,19 +1663,21 @@ static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p,
 
 		do {
-			c = scr_readw(s);
-			if (attr != (c & 0xff00)) {
-				attr = c & 0xff00;
+			c = scr_readg(s);
+			if (attr != (c & vc->vc_attr_mask)) {
+				attr = c & vc->vc_attr_mask;
 				if (s > start) {
-					fbcon_putcs(vc, start, s - start,
+					fbcon_putcs(vc, start,
+						    (s - start) / GLYPH_HW,
 						    dy, x);
-					x += s - start;
+					x += (s - start) / GLYPH_HW;
 					start = s;
 				}
 			}
-			s++;
+			s += GLYPH_HW;
 		} while (s < le);
 		if (s > start)
-			fbcon_putcs(vc, start, s - start, dy, x);
+			fbcon_putcs(vc, start, (s - start) / GLYPH_HW,
+				    dy, x);
 		dy++;
 	}
 }
@@ -1674,31 +1694,31 @@ static void fbcon_redraw_blit(struct vc_data *vc, struct fb_info *info,
 	while (count--) {
 		unsigned short *start = s;
 		unsigned short *le = advance_row(s, 1);
-		unsigned short c;
+		unsigned int c;
 		int x = 0;
 
 		do {
-			c = scr_readw(s);
+			c = scr_readg(s);
 
-			if (c == scr_readw(d)) {
+			if (c == scr_readg(d)) {
 				if (s > start) {
 					par->bitops->bmove(vc, info, line + ycount, x,
-							   line, x, 1, s - start);
-					x += s - start + 1;
-					start = s + 1;
+							   line, x, 1,
+							   (s - start) / GLYPH_HW);
+					x += (s - start) / GLYPH_HW + 1;
+					start = s + GLYPH_HW;
 				} else {
 					x++;
-					start++;
+					start += GLYPH_HW;
 				}
 			}
 
-			scr_writew(c, d);
-			s++;
-			d++;
+			scr_writeg_plusplus(c, d++);
+			s += GLYPH_HW;
 		} while (s < le);
 		if (s > start)
 			par->bitops->bmove(vc, info, line + ycount, x, line, x, 1,
-					     s - start);
+					   (s - start) / GLYPH_HW);
 		if (ycount > 0)
 			line++;
 		else {
@@ -1725,32 +1745,34 @@ static void fbcon_redraw(struct vc_data *vc, int line, int count, int offset)
 
 		do {
-			c = scr_readw(s);
-			if (attr != (c & 0xff00)) {
-				attr = c & 0xff00;
+			c = scr_readg(s);
+			if (attr != (c & vc->vc_attr_mask)) {
+				attr = c & vc->vc_attr_mask;
 				if (s > start) {
-					fbcon_putcs(vc, start, s - start,
+					fbcon_putcs(vc, start,
+						    (s - start) / GLYPH_HW,
 						    line, x);
-					x += s - start;
+					x += (s - start) / GLYPH_HW;
 					start = s;
 				}
 			}
-			if (c == scr_readw(d)) {
+			if (c == scr_readg(d)) {
 				if (s > start) {
-					fbcon_putcs(vc, start, s - start,
-						     line, x);
-					x += s - start + 1;
-					start = s + 1;
+					fbcon_putcs(vc, start,
+						    (s - start) / GLYPH_HW,
+						    line, x);
+					x += (s - start) / GLYPH_HW + 1;
+					start = s + GLYPH_HW;
 				} else {
 					x++;
-					start++;
+					start += GLYPH_HW;
 				}
 			}
-			scr_writew(c, d);
-			s++;
-			d++;
+			scr_writeg_plusplus(c, d++);
+			s += GLYPH_HW;
 		} while (s < le);
 		if (s > start)
-			fbcon_putcs(vc, start, s - start, line, x);
+			fbcon_putcs(vc, start, (s - start) / GLYPH_HW,
+				    line, x);
 		if (offset > 0)
 			line++;
 		else {
@@ -1854,7 +1876,7 @@ static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
 			fbcon_redraw_blit(vc, info, p, t, b - t - count,
 				     count);
 			__fbcon_clear(vc, b - count, 0, count, vc->vc_cols);
-			scr_memsetw((unsigned short *) (vc->vc_origin +
+			scr_memset_worl((unsigned short *) (vc->vc_origin +
 							vc->vc_size_row *
 							(b - count)),
 				    vc->vc_video_erase_char,
@@ -1925,7 +1947,7 @@ static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
 			fbcon_redraw(vc, t, b - t - count,
 				     count * vc->vc_cols);
 			__fbcon_clear(vc, b - count, 0, count, vc->vc_cols);
-			scr_memsetw((unsigned short *) (vc->vc_origin +
+			scr_memset_worl((unsigned short *) (vc->vc_origin +
 							vc->vc_size_row *
 							(b - count)),
 				    vc->vc_video_erase_char,
@@ -1942,7 +1964,7 @@ static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
 			fbcon_redraw_blit(vc, info, p, b - 1, b - t - count,
 				     -count);
 			__fbcon_clear(vc, t, 0, count, vc->vc_cols);
-			scr_memsetw((unsigned short *) (vc->vc_origin +
+			scr_memset_worl((unsigned short *) (vc->vc_origin +
 							vc->vc_size_row *
 							t),
 				    vc->vc_video_erase_char,
@@ -2011,7 +2033,7 @@ static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
 			fbcon_redraw(vc, b - 1, b - t - count,
 				     -count * vc->vc_cols);
 			__fbcon_clear(vc, t, 0, count, vc->vc_cols);
-			scr_memsetw((unsigned short *) (vc->vc_origin +
+			scr_memset_worl((unsigned short *) (vc->vc_origin +
 							vc->vc_size_row *
 							t),
 				    vc->vc_video_erase_char,
@@ -2155,8 +2178,23 @@ static bool fbcon_switch(struct vc_data *vc)
 		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_memset_worl(d, conp2->vc_video_erase_char,
+				    conp2->vc_size_row * logo_lines);
 			conp2->vc_top = 0;
+		}
 		logo_shown = FBCON_LOGO_CANSHOW;
 	}
 
@@ -2350,20 +2391,23 @@ static void set_vc_hi_font(struct vc_data *vc, bool set)
 		if (vc->vc_can_do_color) {
 			unsigned short *cp =
 			    (unsigned short *) vc->vc_origin;
-			int count = vc->vc_screenbuf_size / 2;
-			unsigned short c;
-			for (; count > 0; count--, cp++) {
-				c = scr_readw(cp);
-				scr_writew(((c & 0xfe00) >> 1) |
-					   (c & 0xff), cp);
+			int count = vc->vc_screenbuf_size / GLYPH_SZ;
+			unsigned int c;
+
+			for (; count > 0; count--) {
+				c = scr_readg(cp);
+				scr_writeg_plusplus(((c & 0xfe00) >> 1) |
+						    (c & 0xff), cp++);
 			}
 			c = vc->vc_video_erase_char;
 			vc->vc_video_erase_char =
 			    ((c & 0xfe00) >> 1) | (c & 0xff);
-			vc->vc_attr >>= 1;
 		}
 	} else {
 		vc->vc_hi_font_mask = 0x100;
+		vc->vc_attr_mask = 0xfe00;
+		vc->vc_char_mask = 0x1ff;
+		vc->vc_attr_shift_pos = 9;
 		if (vc->vc_can_do_color) {
 			vc->vc_complement_mask <<= 1;
 			vc->vc_s_complement_mask <<= 1;
@@ -2373,29 +2417,31 @@ static void set_vc_hi_font(struct vc_data *vc, bool set)
 		{
 			unsigned short *cp =
 			    (unsigned short *) vc->vc_origin;
-			int count = vc->vc_screenbuf_size / 2;
-			unsigned short c;
-			for (; count > 0; count--, cp++) {
-				unsigned short newc;
-				c = scr_readw(cp);
+			int count = vc->vc_screenbuf_size / GLYPH_SZ;
+			unsigned int c;
+
+			for (; count > 0; count--) {
+				unsigned int newc;
+
+				c = scr_readg(cp);
 				if (vc->vc_can_do_color)
 					newc =
 					    ((c & 0xff00) << 1) | (c &
 								   0xff);
 				else
 					newc = c & ~0x100;
-				scr_writew(newc, cp);
+				scr_writeg_plusplus(newc, cp++);
 			}
 			c = vc->vc_video_erase_char;
-			if (vc->vc_can_do_color) {
+			if (vc->vc_can_do_color)
 				vc->vc_video_erase_char =
 				    ((c & 0xff00) << 1) | (c & 0xff);
-				vc->vc_attr <<= 1;
-			} else
+			else
 				vc->vc_video_erase_char = c & ~0x100;
 		}
 	}
 }
+#endif
 
 static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount,
 			     font_data_t *data)
diff --git a/include/linux/vt_buffer.h b/include/linux/vt_buffer.h
index 6c15c6a15f74..714ce5f75c7e 100644
--- a/include/linux/vt_buffer.h
+++ b/include/linux/vt_buffer.h
@@ -25,6 +25,32 @@
 #define scr_readw(addr) (*(addr))
 #endif
 
+#ifdef CONFIG_FB_GLYPH_21BIT
+#define scr_writeg(val, addr)			\
+	(*((u32 *)(addr)) = (val))
+#define scr_readg(addr)				\
+	(*((u32 *)(addr)))
+/* The following two macros must be invoked with ADDR being a post-increment
+ * of a u16 *variable.  The pointer will be correctly incremented by 4 bytes.
+ */
+#define scr_writeg_plusplus(val, addr)		\
+	do {					\
+		*((u32 *)(addr)) = (val);	\
+		(addr);				\
+	} while (0)
+#define scr_readg_plusplus(addr)		\
+	(*(u32 *)((addr), ((addr) - 1)))
+#else
+#define scr_writeg(val, addr) scr_writew(val, addr)
+#define scr_readg(addr) scr_readw(addr)
+
+/* The following two macros must be invoked with ADDR being a post-increment
+ * of a u16 *variable.  The pointer will be correctly incremented by 2 bytes.
+ */
+#define scr_writeg_plusplus(val, addr) scr_writew(val, addr)
+#define scr_readg_plusplus(addr) scr_readw(addr)
+#endif
+
 #ifndef VT_BUF_HAVE_MEMSETW
 static inline void scr_memsetw(u16 *s, u16 c, unsigned int count)
 {
@@ -46,4 +72,17 @@ static inline void scr_memmovew(u16 *d, const u16 *s, unsigned int count)
 }
 #endif
 
+static inline void scr_memsetl(u16 *s, u32 c, unsigned int count)
+{
+	count /= 4;
+	memset32((u32 *)s, c, count);
+}
+
+#ifdef CONFIG_FB_GLYPH_21BIT
+#define scr_memset_worl(s, c, count)		\
+	scr_memsetl((s), (c), (count))
+#else
+#define scr_memset_worl(s, c, count)		\
+	scr_memsetw((s), (c), (count))
+#endif
 #endif


-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [Patch 4/9]: Amend internal manipulation of glyph structure
  2026-08-27 18:38 vt: Enlarge the framebuffer glyph size from 16 to 32 bits Alan Mackenzie
                   ` (2 preceding siblings ...)
  2026-08-27 18:47 ` [Patch 3/9]: Replace scr_readw/writew by scr_readg/writeg, etc Alan Mackenzie
@ 2026-08-27 18:48 ` Alan Mackenzie
  2026-08-27 19:19   ` sashiko-bot
  2026-08-27 18:50 ` [Patch 5/9]: vt: Amend three Kconfig files Alan Mackenzie
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-27 18:48 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, Thomas Zimmermann, linux-fbdev,
	dri-devel
  Cc: acm


vt: 32b glyph: 4. Amend internal manipulation of glyph structure

This manipulation is the extraction/insertion of bit fields from/into the
16-bit/32-bit glyph.  It uses the new fields of struct vc_data,
vc_char_mask, vc_attr_mask, vc_attr_shift_pos in place of old hard coded
constants such as 0xff, 0xff00, 8.

Signed-off-by: Alan Mackenzie <acm@muc.de>

diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c
index 7d40eacc21b3..f23d61fe10c1 100644
--- a/drivers/tty/vt/vc_screen.c
+++ b/drivers/tty/vt/vc_screen.c
@@ -272,6 +273,10 @@ static int vcs_read_buf_uni(struct vc_data *vc, char *con_buf,
 	return 0;
 }
 
+/*
+ * NOTE: This function returns the bottom 8 bits of the glyph for each pertinent
+ * screen character.  It is not very useful for a 9-bit or 21-bit glyph setup.
+ */
 static void vcs_read_buf_noattr(const struct vc_data *vc, char *con_buf,
 		unsigned int pos, unsigned int count, bool viewed)
 {
@@ -470,6 +499,10 @@ vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
 	return ret;
 }
 
+/*
+ * NOTE: This function writes the bottom 8-bits of the glyph code into the
+ * screen buffer.  It is not very useful for 9-bit or 21-bit glyphs.
+ */
 static u16 *vcs_write_buf_noattr(struct vc_data *vc, const char *con_buf,
 		unsigned int pos, unsigned int count, bool viewed, u16 **org0)
 {
@@ -497,25 +533,15 @@ static u16 *vcs_write_buf_noattr(struct vc_data *vc, const char *con_buf,
 	return org;
 }
 
-/*
- * Compilers (gcc 10) are unable to optimize the swap in cpu_to_le16. So do it
- * the poor man way.
- */
-static inline u16 vc_compile_le16(u8 hi, u8 lo)
-{
-#ifdef __BIG_ENDIAN
-	return (lo << 8u) | hi;
-#else
-	return (hi << 8u) | lo;
-#endif
-}
-
 static u16 *vcs_write_buf(struct vc_data *vc, const char *con_buf,
 		unsigned int pos, unsigned int count, bool viewed, u16 **org0)
 {
 	u16 *org;
 	unsigned int col, maxcol = vc->vc_cols;
 	unsigned char c;
+	unsigned int sz = GLYPH_SZ;
+	int i;
+	u8 lo_to_hi[4];
 
 	/* header */
 	if (pos < HEADER_SIZE) {
@@ -573,11 +619,27 @@ static u16 *vcs_write_buf(struct vc_data *vc, const char *con_buf,
 	if (!count)
 		return org;
 
-	/* odd pos -- the remaining character */
-	c = *con_buf++;
-	vcs_scr_writew(vc, vc_compile_le16(vcs_scr_readw(vc, org) >> 8, c),
-				org);
+	/* Unaligned bytes at end of buffer */
+	{
+		u32 old_char = vcs_scr_readw(vc, org);
+		u32 new_char = 0;
 
+		for (i = 0; i < sz; i++, old_char >>= 8)
+			lo_to_hi[i] = old_char & 0xff;
+		for (i = 0; i < count; i++) {
+			c = *con_buf++;
+#ifdef __BIG_ENDIAN
+			lo_to_hi[sz - i - 1] = c;
+#else
+			lo_to_hi[i] = c;
+#endif
+		}
+		for (i = sz - 1; i >= 0; i--) {
+			new_char <<= 8;
+			new_char |= lo_to_hi[i];
+		}
+		vcs_scr_writew(vc, new_char, org);
+	}
 	return org;
 }
 
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 8f467b22b799..0c389a564357 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -543,7 +544,7 @@ int vc_uniscr_check(struct vc_data *vc)
 	 * unicode content will be available after a complete screen refresh.
 	 */
 	p = (unsigned short *)vc->vc_origin;
-	mask = vc->vc_hi_font_mask | 0xff;
+	mask = vc->vc_char_mask;
 	for (y = 0; y < vc->vc_rows; y++) {
 		u32 *line = uni_lines[y];
 		for (x = 0; x < vc->vc_cols; x++) {
@@ -713,8 +719,6 @@ static u8 build_attr(struct vc_data *vc, u8 _color,
 		a ^= 0x80;
 	if (_intensity == VCI_BOLD)
 		a ^= 0x08;
-	if (vc->vc_hi_font_mask == 0x100)
-		a <<= 1;
 	return a;
 	}
 }
@@ -726,11 +730,11 @@ static void update_attr(struct vc_data *vc)
 	              vc->state.reverse ^ vc->vc_decscnm, vc->state.italic);
 	vc->vc_video_erase_char = ' ' | (build_attr(vc, vc->state.color,
 				VCI_NORMAL, vc->state.blink, false,
-				vc->vc_decscnm, false) << 8);
+				vc->vc_decscnm, false) << vc->vc_attr_shift_pos);
 }
 
-/* Note: inverting the screen twice should revert to the original state */
+/* Note: inverting the screen twice should revert to the original state.  */
 void invert_screen(struct vc_data *vc, int offset, int count, bool viewed)
 {
 	u16 *p;
 
@@ -742,34 +746,21 @@ void invert_screen(struct vc_data *vc, int offset, int count, bool viewed)
 		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++;
-			}
-		} else {
-			while (cnt--) {
-				a = scr_readw(q);
-				a = (a & 0x88ff) |
-				   ((a & 0x7000) >> 4) |
-				   ((a & 0x0700) << 4);
-				scr_writew(a, q);
-				q++;
-			}
+		u32 a, c;
+
+		while (cnt--) {
+			c = scr_readg(q);
+			a = c >> vc->vc_attr_shift_pos;
+			if (!vc->vc_can_do_color)
+				a ^= 0x08;
+			else
+				a = (a & 0x88) |
+					((a & 0x70) >> 4) |
+					((a & 0x07) << 4);
+			c = (c & vc->vc_char_mask) |
+				(a << vc->vc_attr_shift_pos);
+			scr_writeg_plusplus(c, q++);
 		}
 	}
 
@@ -1041,19 +1037,37 @@ static void visual_init(struct vc_data *vc, int num, bool init)
 		con_free_unimap(vc);
 	vc->uni_pagedict_loc = &vc->uni_pagedict;
 	vc->uni_pagedict = NULL;
-	vc->vc_hi_font_mask = 0;
+	vc->vc_hi_font_mask = 0; /* Probably redundant, 2025-01-16. */
 	vc->vc_complement_mask = 0;
 	vc->vc_can_do_color = 0;
 	vc->vc_cur_blink_ms = DEFAULT_CURSOR_BLINK_MS;
+
 	vc->vc_sw->con_init(vc, init);
+	if (init) {
+#ifdef CONFIG_FB_GLYPH_21BIT
+		vc->vc_char_mask = 0x1fffff;
+		vc->vc_attr_mask = 0xff000000;
+		vc->vc_attr_shift_pos = 24;
+#else
+		if (vc->vc_font.charcount == 256) {
+			vc->vc_char_mask = 0xff;
+			vc->vc_attr_mask = 0xff00;
+			vc->vc_attr_shift_pos = 8;
+		} else {
+			vc->vc_char_mask = 0x1ff;
+			vc->vc_attr_mask = 0xfe00;
+			vc->vc_attr_shift_pos = 9;
+		}
+#endif
+	}
 	if (!vc->vc_complement_mask)
-		vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
+		vc->vc_complement_mask = (vc->vc_can_do_color ? 0x77 : 0x08)
+			<< vc->vc_attr_shift_pos;
 	vc->vc_s_complement_mask = vc->vc_complement_mask;
-	vc->vc_size_row = vc->vc_cols << 1;
+	vc->vc_size_row = vc->vc_cols * GLYPH_SZ;
 	vc->vc_screenbuf_size = vc->vc_rows * vc->vc_size_row;
 }
 
-
 static void visual_deinit(struct vc_data *vc)
 {
 	vc->vc_sw->con_deinit(vc);
@@ -1175,11 +1189,30 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 	unsigned int new_cols, new_rows, new_row_size, new_screen_size;
 	unsigned short *oldscreen, *newscreen;
 	u32 **new_uniscr = NULL;
-
+	unsigned int new_vc_char_mask = vc->vc_char_mask;
+	unsigned int new_vc_attr_mask = vc->vc_attr_mask;
+	unsigned int new_vc_attr_shift_pos = vc->vc_attr_shift_pos;
+	unsigned int new_vc_video_erase_char = vc->vc_video_erase_char;
 	WARN_CONSOLE_UNLOCKED();
 
 	if (cols > VC_MAXCOL || lines > VC_MAXROW)
 		return -EINVAL;
 
+#ifndef CONFIG_FB_GLYPH_21BIT
+	if (vc->vc_hi_font_mask == 0x100) {
+		new_vc_char_mask = 0x1ff;
+		new_vc_attr_mask = 0xfe00;
+		new_vc_attr_shift_pos = 9;
+	} else {
+		new_vc_char_mask = 0xff;
+		new_vc_attr_mask = 0xff00;
+		new_vc_attr_shift_pos = 8;
+	}
+#endif
+	new_vc_video_erase_char =
+		(vc->vc_video_erase_char & vc->vc_char_mask) |
+		((vc->vc_video_erase_char >> vc->vc_attr_shift_pos) <<
+		 new_vc_attr_shift_pos);
+
 	new_cols = (cols ? cols : vc->vc_cols);
 	new_rows = (lines ? lines : vc->vc_rows);
@@ -2114,8 +2164,10 @@ static void csi_RSB(struct vc_data *vc)
 		break;
 	case CSI_RSB_MAKE_CUR_COLOR_DEFAULT:
 		vc->vc_def_color = vc->vc_attr;
+#ifndef CONFIG_FB_GLYPH_21BIT
 		if (vc->vc_hi_font_mask == 0x100)
 			vc->vc_def_color >>= 1;
+#endif
 		default_attr(vc);
 		update_attr(vc);
 		break;
@@ -2856,11 +2908,13 @@ static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, u8 c)
 		if (c == '8') {
 			/* DEC screen alignment test. kludge :-) */
 			vc->vc_video_erase_char =
-				(vc->vc_video_erase_char & 0xff00) | 'E';
+				(vc->vc_video_erase_char & vc->vc_attr_mask) | 'E';
 			csi_J(vc, CSI_J_VISIBLE);
 			vc->vc_video_erase_char =
-				(vc->vc_video_erase_char & 0xff00) | ' ';
-			do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+				(vc->vc_video_erase_char & vc->vc_attr_mask) | ' ';
+			do_update_region(vc, vc->vc_origin,
+					 vc->vc_screenbuf_size /
+					 GLYPH_SZ);
 		}
 		return;
 	case ESsetG0:	/* ESC ( */
@@ -3021,10 +3076,12 @@ static inline unsigned char vc_invert_attr(const struct vc_data *vc)
 	if (!vc->vc_can_do_color)
 		return vc->vc_attr ^ 0x08;
 
+#ifndef CONFIG_FB_GLYPH_21BIT
 	if (vc->vc_hi_font_mask == 0x100)
 		return   (vc->vc_attr & 0x11) |
 			((vc->vc_attr & 0xe0) >> 4) |
 			((vc->vc_attr & 0x0e) << 4);
+#endif
 
 	return   (vc->vc_attr & 0x88) |
 		((vc->vc_attr & 0x70) >> 4) |
@@ -3153,16 +3210,15 @@ static int vc_process_ucs(struct vc_data *vc, int *c, int *tc)
 static int vc_get_glyph(struct vc_data *vc, int tc)
 {
 	int glyph = conv_uni_to_pc(vc, tc);
-	u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
 
-	if (!(glyph & ~charmask))
+	if (!(glyph & ~vc->vc_char_mask))
 		return glyph;
 
 	if (glyph == -1)
 		return -1; /* nothing to display */
 
 	/* Glyph not found */
-	if ((!vc->vc_utf || vc->vc_disp_ctrl || tc < 128) && !(tc & ~charmask)) {
+	if ((!vc->vc_utf || vc->vc_disp_ctrl || tc < 128) && !(tc & ~vc->vc_char_mask)) {
 		/*
 		 * In legacy mode use the glyph we get by a 1:1 mapping.
 		 * This would make absolutely no sense with Unicode in mind, but do this for
diff --git a/drivers/video/fbdev/core/bitblit.c b/drivers/video/fbdev/core/bitblit.c
index 65681dcc5930..84e073162311 100644
--- a/drivers/video/fbdev/core/bitblit.c
+++ b/drivers/video/fbdev/core/bitblit.c
@@ -77,8 +77,8 @@ static inline void bit_putcs_aligned(struct vc_data *vc, struct fb_info *info,
 				     u32 d_pitch, u32 s_pitch, u32 cellsize,
 				     struct fb_image *image, u8 *buf, u8 *dst)
 {
-	u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
 	unsigned int charcnt = vc->vc_font.charcount;
+	u32 charmask = vc->vc_char_mask;
 	u32 idx = vc->vc_font.width >> 3;
 	const u8 *src;
 
@@ -114,8 +113,8 @@ static inline void bit_putcs_unaligned(struct vc_data *vc,
 				       struct fb_image *image, u8 *buf,
 				       u8 *dst)
 {
-	u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
 	unsigned int charcnt = vc->vc_font.charcount;
+	u32 charmask = vc->vc_char_mask;
 	u32 shift_low = 0, mod = vc->vc_font.width % 8;
 	u32 shift_high = 8;
 	u32 idx = vc->vc_font.width >> 3;
@@ -261,7 +260,7 @@ static void bit_cursor(struct vc_data *vc, struct fb_info *info, bool enable,
 {
 	struct fb_cursor cursor;
 	struct fbcon_par *par = info->fbcon_par;
-	unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+	unsigned int charmask = vc->vc_char_mask;
 	int w = DIV_ROUND_UP(vc->vc_font.width, 8), c;
 	int y = real_y(par->p, vc->state.y);
 	int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 9f5c4c101581..18fbf7cf11dd 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -319,14 +319,14 @@ static int get_color(struct vc_data *vc, struct fb_info *info,
 	int color = 0;
 
 	if (console_blanked) {
-		unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+		unsigned int charmask = vc->vc_char_mask;
 
 		c = vc->vc_video_erase_char & charmask;
 	}
 
 	if (depth != 1)
-		color = (is_fg) ? attr_fgcol((vc->vc_hi_font_mask) ? 9 : 8, c)
-			: attr_bgcol((vc->vc_hi_font_mask) ? 13 : 12, c);
+		color = (is_fg) ? ((c >> vc->vc_attr_shift_pos) & 0x0f)
+			: (c >> (vc->vc_attr_shift_pos + 4));
 
 	switch (depth) {
 	case 1:
@@ -1150,14 +1157,8 @@ static void fbcon_init(struct vc_data *vc, bool init)
 	}
 
 	vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1);
-	vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
-	if (vc->vc_font.charcount == 256) {
-		vc->vc_hi_font_mask = 0;
-	} else {
-		vc->vc_hi_font_mask = 0x100;
-		if (vc->vc_can_do_color)
-			vc->vc_complement_mask <<= 1;
-	}
+	vc->vc_complement_mask = (vc->vc_can_do_color ? 0x77 : 0x08) <<
+		(vc->vc_attr_shift_pos ? vc->vc_attr_shift_pos : 8);
 
 	if (!*svc->uni_pagedict_loc)
 		con_set_default_unimap(svc);
@@ -1242,7 +1244,9 @@ static void fbcon_free_font(struct fbcon_display *p)
 	}
 }
 
+#ifndef CONFIG_FB_GLYPH_21BIT
 static void set_vc_hi_font(struct vc_data *vc, bool set);
+#endif
 
 static void fbcon_release_all(void)
 {
@@ -1298,8 +1302,10 @@ static void fbcon_deinit(struct vc_data *vc)
 	fbcon_free_font(p);
 	vc->vc_font.data = NULL;
 
+#ifndef CONFIG_FB_GLYPH_21BIT
 	if (vc->vc_hi_font_mask && vc->vc_screenbuf)
 		set_vc_hi_font(vc, false);
+#endif
 
 	if (!con_is_bound(&fb_con))
 		fbcon_release_all();
@@ -1471,16 +1477,28 @@ static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var,
 	var->xoffset = info->var.xoffset;
 	fb_set_var(info, var);
 	par->var = info->var;
-	vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1);
-	vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
+#ifdef CONFIG_FB_GLYPH_21BIT
+	vc->vc_hi_font_mask = 0;
+	vc->vc_char_mask = 0x1fffff;
+	vc->vc_attr_mask = 0xff000000;
+	vc->vc_attr_shift_pos = 24;
+#else
 	if (vc->vc_font.charcount == 256) {
 		vc->vc_hi_font_mask = 0;
+		vc->vc_char_mask = 0xff;
+		vc->vc_attr_mask = 0xff00;
+		vc->vc_attr_shift_pos = 8;
 	} else {
 		vc->vc_hi_font_mask = 0x100;
-		if (vc->vc_can_do_color)
-			vc->vc_complement_mask <<= 1;
+		vc->vc_char_mask = 0x1ff;
+		vc->vc_attr_mask = 0xfe00;
+		vc->vc_attr_shift_pos = 9;
 	}
+#endif
 
+	vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix) != 1);
+	vc->vc_complement_mask = (vc->vc_can_do_color ? 0x77 : 0x08) <<
+		(vc->vc_attr_shift_pos ? vc->vc_attr_shift_pos : 8);
 	if (!*svc->uni_pagedict_loc)
 		con_set_default_unimap(svc);
 	if (!*vc->uni_pagedict_loc)
@@ -2220,10 +2258,8 @@ static bool fbcon_switch(struct vc_data *vc)
 	}
 
 	vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1);
-	vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
-
-	if (vc->vc_font.charcount > 256)
-		vc->vc_complement_mask <<= 1;
+	vc->vc_complement_mask = (vc->vc_can_do_color ? 0x77 : 0x08) <<
+		(vc->vc_attr_shift_pos ? vc->vc_attr_shift_pos : 8);
 
 	updatescrollmode(p, info, vc);
 
@@ -2270,9 +2306,8 @@ static void fbcon_generic_blank(struct vc_data *vc, struct fb_info *info,
 				int blank)
 {
 	if (blank) {
-		unsigned short charmask = vc->vc_hi_font_mask ?
-			0x1ff : 0xff;
-		unsigned short oldc;
+		unsigned int charmask = vc->vc_char_mask;
+		unsigned int oldc;
 
 		oldc = vc->vc_video_erase_char;
 		vc->vc_video_erase_char &= charmask;
@@ -2336,11 +2373,15 @@ static int fbcon_get_font(struct vc_data *vc, struct console_font *font, unsigned int vpitch)
 	return font_data_export(p->fontdata, font, vpitch);
 }
 
+#ifndef CONFIG_FB_GLYPH_21BIT
 /* set/clear vc_hi_font_mask and update vc attrs accordingly */
 static void set_vc_hi_font(struct vc_data *vc, bool set)
 {
 	if (!set) {
 		vc->vc_hi_font_mask = 0;
+		vc->vc_attr_mask = 0xff00;
+		vc->vc_char_mask = 0xff;
+		vc->vc_attr_shift_pos = 8;
 		if (vc->vc_can_do_color) {
 			vc->vc_complement_mask >>= 1;
 			vc->vc_s_complement_mask >>= 1;
@@ -2406,7 +2452,9 @@ static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount,
 	int resize, ret, old_width, old_height, old_charcount;
 	font_data_t *old_fontdata = p->fontdata;
 	const u8 *old_data = vc->vc_font.data;
+#ifndef CONFIG_FB_GLYPH_21BIT
 	unsigned short old_hi_font_mask = vc->vc_hi_font_mask;
+#endif
 
 	font_data_get(data);
 
@@ -2453,11 +2504,13 @@ static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount,
 	vc->vc_font.height = old_height;
 	vc->vc_font.charcount = old_charcount;
 
+#ifndef CONFIG_FB_GLYPH_21BIT
 	/* Restore the hi_font state and screen buffer */
 	if (old_hi_font_mask && !vc->vc_hi_font_mask)
 		set_vc_hi_font(vc, true);
 	else if (!old_hi_font_mask && vc->vc_hi_font_mask)
 		set_vc_hi_font(vc, false);
+#endif
 
 	font_data_put(data);
 
@@ -2583,19 +2645,23 @@ static void fbcon_set_palette(struct vc_data *vc, const unsigned char *table)
 
 /* As we might be inside of softback, we may work with non-contiguous buffer,
    that's why we have to use a separate routine. */
-static void fbcon_invert_region(struct vc_data *vc, u16 * p, int cnt)
+static void fbcon_invert_region(struct vc_data *vc, u16 *p, int cnt)
 {
+	u32 attr;
+
 	while (cnt--) {
-		u16 a = scr_readw(p);
+		u32 a = scr_readg(p);
+
+		attr = a >> vc->vc_attr_shift_pos;
 		if (!vc->vc_can_do_color)
-			a ^= 0x0800;
-		else if (vc->vc_hi_font_mask == 0x100)
-			a = ((a) & 0x11ff) | (((a) & 0xe000) >> 4) |
-			    (((a) & 0x0e00) << 4);
+			attr ^= 0x08;
 		else
-			a = ((a) & 0x88ff) | (((a) & 0x7000) >> 4) |
-			    (((a) & 0x0700) << 4);
-		scr_writew(a, p++);
+			attr = (attr & 0x88) |
+				((attr & 0x70) >> 4) |
+				((attr & 0x07) << 4);
+		a = (a & vc->vc_char_mask) |
+			(attr << vc->vc_attr_shift_pos);
+		scr_writeg_plusplus(a, p++);
 	}
 }
 
diff --git a/drivers/video/fbdev/core/fbcon.h b/drivers/video/fbdev/core/fbcon.h
index 407d207b14f1..5aaef363a343 100644
--- a/drivers/video/fbdev/core/fbcon.h
+++ b/drivers/video/fbdev/core/fbcon.h
@@ -105,12 +105,6 @@ struct fbcon_par {
      *  Attribute Decoding
      */
 
-/* Color */
-#define attr_fgcol(fgshift,s)    \
-	(((s) >> (fgshift)) & 0x0f)
-#define attr_bgcol(bgshift,s)    \
-	(((s) >> (bgshift)) & 0x0f)
-
 /* Monochrome */
 #define attr_bold(s) \
 	((s) & 0x200)
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index fe915afdece5..5afce52057ca 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -144,13 +153,16 @@ struct vc_data {
 	unsigned char	vc_halfcolor;		/* Color for half intensity mode */
 	/* cursor */
 	unsigned int	vc_cursor_type;
-	unsigned short	vc_complement_mask;	/* [#] Xor mask for mouse pointer */
-	unsigned short	vc_s_complement_mask;	/* Saved mouse pointer mask */
+	unsigned int	vc_complement_mask;	/* [#] Xor mask for mouse pointer */
+	unsigned int	vc_s_complement_mask;	/* Saved mouse pointer mask */
 	unsigned long	vc_pos;			/* Cursor address */
 	/* fonts */
 	unsigned short	vc_hi_font_mask;	/* [#] Attribute set for upper 256 chars of font or 0 if not supported */
-	struct vc_font vc_font;			/* Current VC font set */
-	unsigned short	vc_video_erase_char;	/* Background erase character */
+	unsigned int	vc_char_mask;		/* Character mask.  0x1fffff, 0x1ff, or 0xff. */
+	unsigned int	vc_attr_mask;		/* Attribute mask.  0xff000000, 0xfe00, 0xff00. */
+	unsigned int	vc_attr_shift_pos;	/* Position of 8-bit attributes: 24, 9, or 8. */
+	struct vc_font	vc_font;		/* Current VC font set */
+	unsigned int	vc_video_erase_char;	/* Background erase character */
 	/* VT terminal data */
 	unsigned int	vc_state;		/* Escape sequence parser state */
 	unsigned int	vc_npar,vc_par[NPAR];	/* Parameters of current escape sequence */
@@ -217,9 +229,18 @@ extern void vc_SAK(struct work_struct *work);
 #define CUR_SW				0x000010
 #define CUR_ALWAYS_BG			0x000020
 #define CUR_INVERT_FG_BG		0x000040
+
+#ifdef CONFIG_FB_GLYPH_21BIT
+#define CUR_FG (0x07 << vc->vc_attr_shift_pos)
+#define CUR_BG (0x70 << vc->vc_attr_shift_pos)
+#define CUR_TYPE_CHANGE			0x00ff00
+#define CUR_CHANGE(c) (((c) & CUR_TYPE_CHANGE) << (vc->vc_attr_shift_pos - 8))
+#else
 #define CUR_FG				0x000700
 #define CUR_BG				0x007000
 #define CUR_CHANGE(c)		 ((c) & 0x00ff00)
+#endif
+
 #define CUR_SET(c)		(((c) & 0xff0000) >> 8)
 
 bool con_is_visible(const struct vc_data *vc);


-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [Patch 5/9]: vt: Amend three Kconfig files
  2026-08-27 18:38 vt: Enlarge the framebuffer glyph size from 16 to 32 bits Alan Mackenzie
                   ` (3 preceding siblings ...)
  2026-08-27 18:48 ` [Patch 4/9]: Amend internal manipulation of glyph structure Alan Mackenzie
@ 2026-08-27 18:50 ` Alan Mackenzie
  2026-08-27 19:12   ` sashiko-bot
  2026-08-27 18:52 ` [Patch 6/9]: vt: Use u32 and typedef u1632 to handle whole glyphs Alan Mackenzie
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-27 18:50 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, Thomas Zimmermann, linux-fbdev,
	dri-devel
  Cc: acm


vt: 32b glyph: 5. Amend three Kconfig files

Until it becomes clear how to integrate CONFIG_A11Y_BRAILLE_CONSOLE and
CONFIG_SPEAKUP, they will remain disabled when CONFIG_FB_GLYPH_21BIT is
#defined.

Signed-off-by: Alan Mackenzie <acm@muc.de>

diff --git a/drivers/accessibility/Kconfig b/drivers/accessibility/Kconfig
index 6b2f79d1f1b8..8f27e1c657b6 100644
--- a/drivers/accessibility/Kconfig
+++ b/drivers/accessibility/Kconfig
@@ -21,6 +21,7 @@ config A11Y_BRAILLE_CONSOLE
 	bool "Console on braille device"
 	depends on VT
 	depends on SERIAL_CORE_CONSOLE
+	depends on !FB_GLYPH_21BIT
 	help
 	  Enables console output on a braille device connected to a 8250
 	  serial port. For now only the VisioBraille device is supported.
diff --git a/drivers/accessibility/speakup/Kconfig b/drivers/accessibility/speakup/Kconfig
index e84fb617acc4..458bee03d3e8 100644
--- a/drivers/accessibility/speakup/Kconfig
+++ b/drivers/accessibility/speakup/Kconfig
@@ -3,6 +3,7 @@ menu "Speakup console speech"
 
 config SPEAKUP
 	depends on VT
+	depends on !FB_GLYPH_21BIT
 	tristate "Speakup core"
 	help
 		This is the Speakup screen reader.  Think of it as a
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index 6096c623d9d5..0ce8c1506eaa 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -42,6 +42,15 @@ config DRM_FBDEV_EMULATION
 
 	  If in doubt, say "Y".
 
+config FB_GLYPH_21BIT
+	bool "Enable 21-bit glyphs in the frame buffer device"
+	depends on (DRM_FBDEV_EMULATION = y)
+	help
+	  This option enables 21-bit glyphs in the fbdev framebuffer,
+	  surmounting the traditional restriction to 256/512 glyphs dating back
+	  to the VGA hardware of the 1980s and 1990s.  To use this effectively
+	  you will need a suitable font and possibly a suitable keyboard layout.
+
 config DRM_FBDEV_OVERALLOC
 	int "Overallocation of the fbdev buffer"
 	depends on DRM_FBDEV_EMULATION

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [Patch 6/9]: vt: Use u32 and typedef u1632 to handle whole glyphs
  2026-08-27 18:38 vt: Enlarge the framebuffer glyph size from 16 to 32 bits Alan Mackenzie
                   ` (4 preceding siblings ...)
  2026-08-27 18:50 ` [Patch 5/9]: vt: Amend three Kconfig files Alan Mackenzie
@ 2026-08-27 18:52 ` Alan Mackenzie
  2026-08-27 19:22   ` sashiko-bot
  2026-08-27 18:54 ` [Patch 7/9]: vt: Handle up to 2^21 glyphs, rather than 256/512 Alan Mackenzie
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-27 18:52 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, Thomas Zimmermann, linux-fbdev,
	dri-devel
  Cc: acm

vt: 32b glyph: 6. Use u32 and typedef u1632 to handle whole glyphs

The glyph size u1632 is an either 32- or 16-bit unsigned word
depending on whether or not CONFIG_FB_GLYPH_21BIT is #defined.

Signed-off-by: Alan Mackenzie <acm@muc.de>

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 8f467b22b799..0c389a564357 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -322,12 +323,12 @@ static inline u16 *screenpos(const struct vc_data *vc, unsigned int offset,
 	return (u16 *)(origin + offset);
 }
 
-static void con_putc(struct vc_data *vc, u16 ca, unsigned int y, unsigned int x)
+static void con_putc(struct vc_data *vc, u32 ca, unsigned int y, unsigned int x)
 {
 	if (vc->vc_sw->con_putc)
 		vc->vc_sw->con_putc(vc, ca, y, x);
 	else
-		vc->vc_sw->con_putcs(vc, &ca, 1, y, x);
+		vc->vc_sw->con_putcs(vc, (u16 *)&ca, 1, y, x);
 }
 
 /* Called  from the keyboard irq path.. */
@@ -782,7 +773,7 @@ void invert_screen(struct vc_data *vc, int offset, int count, bool viewed)
 void complement_pos(struct vc_data *vc, int offset)
 {
 	static int old_offset = -1;
-	static unsigned short old;
+	static unsigned int old;
 	static unsigned short oldx, oldy;
 
 	WARN_CONSOLE_UNLOCKED();
@@ -3194,7 +3250,7 @@ static int vc_con_write_normal(struct vc_data *vc, int tc, int c,
 {
 	int next_c;
 	unsigned char vc_attr = vc->vc_attr;
-	u16 himask = vc->vc_hi_font_mask;
+	u32 himask = vc->vc_hi_font_mask;
 	u8 width = 1;
 	bool inverse = false;
 
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 9f5c4c101581..18fbf7cf11dd 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -313,7 +313,7 @@ static inline bool fbcon_is_active(struct vc_data *vc, struct fb_info *info)
 }
 
 static int get_color(struct vc_data *vc, struct fb_info *info,
-		     u16 c, bool is_fg)
+		     u32 c, bool is_fg)
 {
 	int depth = fb_get_color_depth(&info->var, &info->fix);
 	int color = 0;
@@ -379,12 +379,12 @@ static int get_color(struct vc_data *vc, struct fb_info *info,
 	return color;
 }
 
-static int get_fg_color(struct vc_data *vc, struct fb_info *info, u16 c)
+static int get_fg_color(struct vc_data *vc, struct fb_info *info, u32 c)
 {
 	return get_color(vc, info, c, true);
 }
 
-static int get_bg_color(struct vc_data *vc, struct fb_info *info, u16 c)
+static int get_bg_color(struct vc_data *vc, struct fb_info *info, u32 c)
 {
 	return get_color(vc, info, c, false);
 }
@@ -1639,8 +1657,8 @@ static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p,
 	while (count--) {
 		unsigned short *start = s;
 		unsigned short *le = advance_row(s, 1);
-		unsigned short c;
+		unsigned int c;
 		int x = 0;
-		unsigned short attr = 1;
+		unsigned int attr = 1;
 
 		do {
@@ -1668,7 +1688,7 @@ static void fbcon_redraw_blit(struct vc_data *vc, struct fb_info *info,
 	int offset = ycount * vc->vc_cols;
 	unsigned short *d = (unsigned short *)
 	    (vc->vc_origin + vc->vc_size_row * line);
-	unsigned short *s = d + offset;
+	unsigned short *s = d + ((long) offset) * GLYPH_HW;
 	struct fbcon_par *par = info->fbcon_par;
 
 	while (count--) {
@@ -1719,8 +1739,8 @@ static void fbcon_redraw(struct vc_data *vc, int line, int count, int offset)
 	while (count--) {
 		unsigned short *start = s;
 		unsigned short *le = advance_row(s, 1);
-		unsigned short c;
+		unsigned int c;
 		int x = 0;
-		unsigned short attr = 1;
+		unsigned int attr = 1;
 
 		do {
@@ -2030,7 +2052,7 @@ static void updatescrollmode_accel(struct fbcon_display *p,
 #ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
 	struct fbcon_par *par = info->fbcon_par;
 	int cap = info->flags;
-	u16 t = 0;
+	u32 t = 0;
 	int ypan = FBCON_SWAP(par->rotate, info->fix.ypanstep, info->fix.xpanstep);
 	int ywrap = FBCON_SWAP(par->rotate, info->fix.ywrapstep, t);
 	int yres = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
@@ -2146,6 +2168,7 @@ static bool fbcon_switch(struct vc_data *vc)
 	struct fbcon_par *par;
 	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);
diff --git a/drivers/video/fbdev/core/fbcon.h b/drivers/video/fbdev/core/fbcon.h
index 407d207b14f1..5aaef363a343 100644
--- a/drivers/video/fbdev/core/fbcon.h
+++ b/drivers/video/fbdev/core/fbcon.h
@@ -217,7 +211,7 @@ static inline int real_y(struct fbcon_display *p, int ypos)
 }
 
 
-static inline int get_attribute(struct fb_info *info, u16 c)
+static inline int get_attribute(struct fb_info *info, u32 c)
 {
 	int attribute = 0;
 
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index fe915afdece5..5afce52057ca 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -110,7 +119,7 @@ unsigned int vc_font_size(const struct vc_font *font);
  * (vc_origin +            |                      |  \ EMPTY, to be filled by
  *  vc_screenbuf_size)     |                      |  / vc_video_erase_char
  *                         +----------------------+-'
- *                         <---- 2 * vc_cols ----->
+ *                         <--- 2/4 * vc_cols ---->
  *                         <---- vc_size_row ----->
  *
  * Note that every character in the console buffer is accompanied with an
@@ -178,7 +190,7 @@ struct vc_data {
 		 int	vc_utf_char;
 	DECLARE_BITMAP(vc_tab_stop, VC_TABSTOPS_COUNT);	/* Tab stops. 256 columns. */
 	unsigned char   vc_palette[16*3];       /* Colour palette for VGA+ */
-	unsigned short * vc_translate;
+	u1632		*vc_translate;
 	unsigned int	vc_bell_pitch;		/* Console bell pitch */
 	unsigned int	vc_bell_duration;	/* Console bell duration */
 	unsigned short	vc_cur_blink_ms;	/* Cursor blink duration */
diff --git a/include/linux/consolemap.h b/include/linux/consolemap.h
index 539d488fdc03..5e50b4d4bd0b 100644
--- a/include/linux/consolemap.h
+++ b/include/linux/consolemap.h
@@ -19,11 +19,21 @@ enum translation_map {
 
 #include <linux/types.h>
 
+#ifdef CONFIG_FB_GLYPH_21BIT
+#define u1632 u32
+#else
+#define u1632 u16
+#endif
+
 struct vc_data;
 
+extern u8 dfont_unicount[];	/* Defined in consolemap_deftbl.c */
+extern u16 dfont_unitable[];	/* Ditto */
+
 #ifdef CONFIG_CONSOLE_TRANSLATIONS
-u16 inverse_translate(const struct vc_data *conp, u16 glyph, bool use_unicode);
-unsigned short *set_translate(enum translation_map m, struct vc_data *vc);
+u1632 inverse_translate(const struct vc_data *conp, u1632 glyph,
+			bool use_unicode);
+u1632 *set_translate(enum translation_map m, struct vc_data *vc);
 int conv_uni_to_pc(struct vc_data *conp, long ucs);
 u32 conv_8bit_to_uni(unsigned char c);
 int conv_uni_to_8bit(u32 uni);
@@ -32,7 +42,7 @@ unsigned int ucs_get_width(uint32_t cp);
 u32 ucs_recompose(u32 base, u32 mark);
 u32 ucs_get_fallback(u32 cp);
 #else
-static inline u16 inverse_translate(const struct vc_data *conp, u16 glyph,
+static inline u1632 inverse_translate(const struct vc_data *conp, u16 glyph,
 		bool use_unicode)
 {
 	return glyph;
diff --git a/include/linux/selection.h b/include/linux/selection.h
index bab7d30d3446..44bc2b2892d8 100644
--- a/include/linux/selection.h
+++ b/include/linux/selection.h
@@ -33,7 +33,7 @@ extern unsigned char default_grn[];
 extern unsigned char default_blu[];
 
 unsigned short *screen_pos(const struct vc_data *vc, int w_offset, bool viewed);
-u16 screen_glyph(const struct vc_data *vc, int offset);
+u32 screen_glyph(const struct vc_data *vc, int offset);
 u32 screen_glyph_unicode(const struct vc_data *vc, int offset);
 void complement_pos(struct vc_data *vc, int offset);
 void invert_screen(struct vc_data *vc, int offset, int count, bool viewed);
@@ -41,8 +41,8 @@ void invert_screen(struct vc_data *vc, int offset, int count, bool viewed);
 void getconsxy(const struct vc_data *vc, unsigned char xy[static 2]);
 void putconsxy(struct vc_data *vc, unsigned char xy[static const 2]);
 
-u16 vcs_scr_readw(const struct vc_data *vc, const u16 *org);
-void vcs_scr_writew(struct vc_data *vc, u16 val, u16 *org);
+u32 vcs_scr_readw(const struct vc_data *vc, const u16 *org);
+void vcs_scr_writew(struct vc_data *vc, u32 val, u16 *org);
 void vcs_scr_updated(struct vc_data *vc);
 
 int vc_uniscr_check(struct vc_data *vc);
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
index d008c3d0a9bb..af99aeefe177 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -56,14 +56,20 @@ int tioclinux(struct tty_struct *tty, unsigned long arg);
 /* consolemap.c */
 
 struct unipair;
+struct unipair21;
+#ifdef CONFIG_FB_GLYPH_21BIT
+#define unipair8_21 unipair21
+#else
+#define unipair8_21 unipair
+#endif
 
 int con_set_trans_old(unsigned char __user * table);
 int con_get_trans_old(unsigned char __user * table);
 int con_set_trans_new(unsigned short __user * table);
 int con_get_trans_new(unsigned short __user * table);
 int con_clear_unimap(struct vc_data *vc);
-int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list);
-int con_get_unimap(struct vc_data *vc, ushort ct, ushort __user *uct, struct unipair __user *list);
+int con_set_unimap(struct vc_data *vc, u32 ct, struct unipair8_21 *list);
+int con_get_unimap(struct vc_data *vc, u32 ct, u1632 __user *uct, struct unipair8_21 *list);
 int con_set_default_unimap(struct vc_data *vc);
 void con_free_unimap(struct vc_data *vc);
 int con_copy_unimap(struct vc_data *dst_vc, struct vc_data *src_vc);


-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [Patch 7/9]: vt: Handle up to 2^21 glyphs, rather than 256/512
  2026-08-27 18:38 vt: Enlarge the framebuffer glyph size from 16 to 32 bits Alan Mackenzie
                   ` (5 preceding siblings ...)
  2026-08-27 18:52 ` [Patch 6/9]: vt: Use u32 and typedef u1632 to handle whole glyphs Alan Mackenzie
@ 2026-08-27 18:54 ` Alan Mackenzie
  2026-08-27 19:14   ` sashiko-bot
  2026-08-27 18:56 ` [Patch 8/9]: vt: Enhancements to the VT ioctl interface Alan Mackenzie
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-27 18:54 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, Thomas Zimmermann, linux-fbdev,
	dri-devel
  Cc: acm

vt: 32b glyph: 7. Handle up to 2^21 glyphs, rather than 256/512

Replace/supplement tests for number of glyphs with the full
Unicode limits.

Signed-off-by: Alan Mackenzie <acm@muc.de>

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 8f467b22b799..0c389a564357 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -4905,6 +4961,7 @@ void reset_palette(struct vc_data *vc)
 #define max_font_width	64
 #define max_font_height	128
 #define max_font_glyphs	512
+#define max_font_glyphs21 0x110000
 #define max_font_size	(max_font_glyphs*max_font_width*max_font_height)
 
 static int con_font_get(struct vc_data *vc, struct console_font_op *op)
@@ -4960,17 +5017,23 @@ static int con_font_set(struct vc_data *vc, const struct console_font_op *op)
 
 	if (!op->data)
 		return -EINVAL;
+#ifndef CONFIG_FB_GLYPH_21BIT
 	if (op->charcount > max_font_glyphs)
 		return -EINVAL;
+#else
+	if (op->charcount > max_font_glyphs21)
+		return -EINVAL;
+#endif
 	if (op->width <= 0 || op->width > max_font_width || !op->height ||
 	    op->height > max_font_height)
 		return -EINVAL;
 	if (vpitch < op->height)
 		return -EINVAL;
 	size = DIV_ROUND_UP(op->width, 8) * vpitch * op->charcount;
+#ifndef CONFIG_FB_GLYPH_21BIT
 	if (size > max_font_size)
 		return -ENOSPC;
-
+#endif
 	void *font_data __free(kfree) = font.data = memdup_user(op->data, size);
 	if (IS_ERR(font.data))
 		return PTR_ERR(font.data);
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 9f5c4c101581..18fbf7cf11dd 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -2331,7 +2366,9 @@ static int fbcon_get_font(struct vc_data *vc, struct console_font *font, unsigne
 	font->height = vc->vc_font.height;
 	if (font->height > vpitch)
 		return -ENOSPC;
-	font->charcount = vc->vc_hi_font_mask ? 512 : 256;
+	font->charcount = vc->vc_font.charcount;
+	if (!font->data)
+		return 0;
 
 	return font_data_export(p->fontdata, font, vpitch);
 }
@@ -2420,10 +2468,13 @@ static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount,
 	vc->vc_font.width = w;
 	vc->vc_font.height = h;
 	vc->vc_font.charcount = charcount;
-	if (vc->vc_hi_font_mask && charcount == 256)
-		set_vc_hi_font(vc, false);
-	else if (!vc->vc_hi_font_mask && charcount == 512)
+
+#ifndef CONFIG_FB_GLYPH_21BIT
+	if (charcount == 512 && !vc->vc_hi_font_mask)
 		set_vc_hi_font(vc, true);
+	else if (charcount == 256 && vc->vc_hi_font_mask)
+		set_vc_hi_font(vc, false);
+#endif
 
 	if (resize) {
 		int cols, rows;
@@ -2465,8 +2518,9 @@ static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount,
 }
 
 /*
- *  User asked to set font; we are guaranteed that charcount does not exceed 512
- *  but lets not assume that, since charcount of 512 is small for unicode support.
+ *  User asked to set font; we were once guaranteed that charcount did not
+ *  exceed 512 but that is no longer the case, since charcount of 512 is too
+ *  small for unicode support.
  */
 
 static int fbcon_set_font(struct vc_data *vc, const struct console_font *font,
@@ -2479,10 +2533,18 @@ static int fbcon_set_font(struct vc_data *vc, const struct console_font *font,
 	int i, ret;
 	font_data_t *new_data;
 
-	/* Is there a reason why fbconsole couldn't handle any charcount >256?
-	 * If not this check should be changed to charcount < 256 */
+#ifdef CONFIG_FB_GLYPH_21BIT
+	if (charcount < 256)
+		return -EINVAL;
+#else
+	/* There is no longer any reason why fbconsole can't handle
+	 * any charcount >256, when CONFIG_FB_GLYPH_21BIT is #defined.
+	 * Hence this check has been changed to charcount < 256
+	 * above.
+	 */
 	if (charcount != 256 && charcount != 512)
 		return -EINVAL;
+#endif
 
 	/* font bigger than screen resolution ? */
 	if (w > FBCON_SWAP(info->var.rotate, info->var.xres, info->var.yres) ||


-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [Patch 8/9]: vt: Enhancements to the VT ioctl interface
  2026-08-27 18:38 vt: Enlarge the framebuffer glyph size from 16 to 32 bits Alan Mackenzie
                   ` (6 preceding siblings ...)
  2026-08-27 18:54 ` [Patch 7/9]: vt: Handle up to 2^21 glyphs, rather than 256/512 Alan Mackenzie
@ 2026-08-27 18:56 ` Alan Mackenzie
  2026-08-27 19:25   ` sashiko-bot
  2026-08-27 18:58 ` [Patch 9/9]: vt: Misc changes, e.g. to #include directives Alan Mackenzie
  2026-08-28  6:12 ` vt: Enlarge the framebuffer glyph size from 16 to 32 bits Thomas Zimmermann
  9 siblings, 1 reply; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-27 18:56 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, Thomas Zimmermann, linux-fbdev,
	dri-devel
  Cc: acm

vt: 32b glyph: 10. Enhancements to the VT ioctl interface

Handle the current 16-bit P/GIO_UNIMAP ioctls by converting
to/from 32-bit code-points/glyph numbers.  Add handling for new
32-bit ioctls P/GIO_UNIMAP21.  Convert to and from __user in
vt_ioctl.c rather than in vt.c or consolemap.c.

Signed-off-by: Alan Mackenzie <acm@muc.de>

diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c
index 28993a3d0acb..05986430e1bb 100644
--- a/drivers/tty/vt/vt_ioctl.c
+++ b/drivers/tty/vt/vt_ioctl.c
@@ -24,7 +24,6 @@
 #include <linux/major.h>
 #include <linux/fs.h>
 #include <linux/console.h>
-#include <linux/consolemap.h>
 #include <linux/signal.h>
 #include <linux/suspend.h>
 #include <linux/timex.h>
@@ -36,6 +35,7 @@
 
 #include <linux/kbd_kern.h>
 #include <linux/vt_kern.h>
+#include <linux/consolemap.h>
 #include <linux/kbd_diacr.h>
 #include <linux/selection.h>
 
@@ -484,10 +484,155 @@ static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd,
 	return 0;
 }
 
+#ifdef CONFIG_FB_GLYPH_21BIT
+static int vt__user_unipair_8_to_21(unsigned int ct,
+				    struct unipair __user *entries,
+				    struct unipair21 **tmp21_entries)
+{
+	struct unipair *tmp_entries;
+	int i, ret = 0;
+
+	tmp_entries = kmalloc_array(ct, sizeof(struct unipair), GFP_KERNEL);
+	if (!tmp_entries)
+		return -ENOMEM;
+	if (copy_from_user(tmp_entries, entries, ct * sizeof(struct unipair))) {
+		ret = -EFAULT;
+		goto free_tmp;
+	}
+	*tmp21_entries = kmalloc_array(ct, sizeof(struct unipair21), GFP_KERNEL);
+	if (!*tmp21_entries) {
+		ret = -ENOMEM;
+		goto free_tmp;
+	}
+	for (i = 0; i < ct; i++) {
+		(*tmp21_entries)[i].unicode = tmp_entries[i].unicode;
+		(*tmp21_entries)[i].fontpos = tmp_entries[i].fontpos;
+	}
+free_tmp: kfree(tmp_entries);
+	return ret;
+}
+
+static int vt_unipair_21_to__user_8(unsigned int ct,
+				    struct unipair21 *entries21,
+				    struct unipair __user *entries)
+{
+	struct unipair *tmp_entries;
+	int i, ret = 0;
+
+	tmp_entries = kmalloc_array(ct, sizeof(struct unipair), GFP_KERNEL);
+	if (!tmp_entries)
+		return -ENOMEM;
+	for (i = 0; i < ct; i++) {
+		if ((entries21[i].unicode > 0xffff) ||
+		    (entries21[i].fontpos > 0xffff)) {
+			ret = -EINVAL;
+			goto free_tmp;
+		}
+		tmp_entries[i].unicode = entries21[i].unicode;
+		tmp_entries[i].fontpos = entries21[i].fontpos;
+	}
+	if (copy_to_user(entries, tmp_entries, ct * sizeof(struct unipair)))
+		ret = -EFAULT;
+free_tmp: kfree(tmp_entries);
+	return ret;
+}
+
 static inline int do_unimap_ioctl(int cmd, struct unimapdesc __user *user_ud,
 		bool perm, struct vc_data *vc)
 {
 	struct unimapdesc tmp;
+	struct unipair21 *tmp21_entries = NULL;
+	int ret = 0, ret1 = 0;
+	unsigned int ct;
+
+	if (copy_from_user(&tmp, user_ud, sizeof(tmp)))
+		return -EFAULT;
+	switch (cmd) {
+	case PIO_UNIMAP:
+		if (!perm)
+			return -EPERM;
+		ret = vt__user_unipair_8_to_21(tmp.entry_ct, tmp.entries,
+					       &tmp21_entries);
+		if (ret)
+			return ret;
+		ret = con_set_unimap(vc, tmp.entry_ct, tmp21_entries);
+		kfree(tmp21_entries);
+		return ret;
+
+	case GIO_UNIMAP:
+		if (!perm && fg_console != vc->vc_num)
+			return -EPERM;
+		tmp21_entries = kmalloc_array(tmp.entry_ct,
+					      sizeof(struct unipair21),
+					      GFP_KERNEL);
+		if (!tmp21_entries)
+			return -ENOMEM;
+		ret = con_get_unimap(vc, tmp.entry_ct, &ct, tmp21_entries);
+		if (ret)
+			goto free_tmp21;
+		ret = vt_unipair_21_to__user_8(ct, tmp21_entries, tmp.entries);
+free_tmp21:	ret1 = put_user(ct, &user_ud->entry_ct);
+		kfree(tmp21_entries);
+		return ret ? ret : ret1;
+	}
+	return 0;
+}
+
+static inline int do_unimap_ioctl21(int cmd, struct unimapdesc21 __user *user_ud,
+		bool perm, struct vc_data *vc)
+{
+	struct unimapdesc21 tmp;
+	struct unipair21 *k_entries;
+	int ret = 0, ret1 = 0;
+
+	if (copy_from_user(&tmp, user_ud, sizeof(tmp)))
+		return -EFAULT;
+	switch (cmd) {
+	case PIO_UNIMAP21:
+		if (!perm)
+			return -EPERM;
+		k_entries = kmalloc_array(tmp.entry_ct,
+					  sizeof(struct unipair21),
+					  GFP_KERNEL);
+		if (!k_entries)
+			return -ENOMEM;
+		if (copy_from_user(k_entries, tmp.entries,
+				   tmp.entry_ct * sizeof(struct unipair21))) {
+			ret = -EFAULT;
+			goto free_k_entries;
+		}
+		ret = con_set_unimap(vc, tmp.entry_ct, k_entries);
+free_k_entries: kfree(k_entries);
+		return ret;
+	case GIO_UNIMAP21:
+		if (!perm && fg_console != vc->vc_num)
+			return -EPERM;
+		k_entries = kmalloc_array(tmp.entry_ct,
+					  sizeof(struct unipair21),
+					  GFP_KERNEL);
+		if (!k_entries)
+			return -ENOMEM;
+		ret = con_get_unimap(vc, tmp.entry_ct, &tmp.entry_ct,
+				     k_entries);
+		if (ret)
+			goto free_k_entries1;
+		if (copy_to_user(tmp.entries, k_entries,
+				 tmp.entry_ct * sizeof(struct unipair21)))
+			ret = -EFAULT;
+free_k_entries1: ret1 = put_user(tmp.entry_ct, &user_ud->entry_ct);
+		kfree(k_entries);
+		return ret ? ret : ret1;
+	}
+	return 0;
+}
+
+#else
+static inline int do_unimap_ioctl(int cmd, struct unimapdesc __user *user_ud,
+		bool perm, struct vc_data *vc)
+{
+	struct unimapdesc tmp;
+	struct unipair *k_entries;
+	int ret = 0, ret1 = 0;
 
 	if (copy_from_user(&tmp, user_ud, sizeof tmp))
 		return -EFAULT;
@@ -495,15 +640,41 @@ static inline int do_unimap_ioctl(int cmd, struct unimapdesc __user *user_ud,
 	case PIO_UNIMAP:
 		if (!perm)
 			return -EPERM;
-		return con_set_unimap(vc, tmp.entry_ct, tmp.entries);
+		k_entries = kmalloc_array(tmp.entry_ct,
+					  sizeof(struct unipair),
+					  GFP_KERNEL);
+		if (!k_entries)
+			return -ENOMEM;
+		if (copy_from_user(k_entries, tmp.entries,
+				   tmp.entry_ct * sizeof(struct unipair))) {
+			ret = -EFAULT;
+			goto free_k_entries;
+		}
+		ret = con_set_unimap(vc, tmp.entry_ct, k_entries);
+free_k_entries: kfree(k_entries);
+		return ret;
 	case GIO_UNIMAP:
 		if (!perm && fg_console != vc->vc_num)
 			return -EPERM;
-		return con_get_unimap(vc, tmp.entry_ct, &(user_ud->entry_ct),
-				tmp.entries);
+		k_entries = kmalloc_array(tmp.entry_ct,
+					  sizeof(struct unipair),
+					  GFP_KERNEL);
+		if (!k_entries)
+			return -ENOMEM;
+		ret = con_get_unimap(vc, tmp.entry_ct, &tmp.entry_ct,
+				     k_entries);
+		if (ret)
+			goto free_k_entries1;
+		if (copy_to_user(tmp.entries, k_entries,
+				 tmp.entry_ct * sizeof(struct unipair)))
+			ret = -EFAULT;
+free_k_entries1: ret1 = put_user(tmp.entry_ct, &user_ud->entry_ct);
+		kfree(k_entries);
+		return ret ? ret : ret1;
 	}
 	return 0;
 }
+#endif /* CONFIG_FB_GLYPH_21BIT */
 
 static int vt_io_ioctl(struct vc_data *vc, unsigned int cmd, void __user *up,
 		bool perm)
@@ -543,6 +714,12 @@ static int vt_io_ioctl(struct vc_data *vc, unsigned int cmd, void __user *up,
 	case GIO_UNIMAP:
 		return do_unimap_ioctl(cmd, up, perm, vc);
 
+#ifdef CONFIG_FB_GLYPH_21BIT
+	case PIO_UNIMAP21:
+	case GIO_UNIMAP21:
+		return do_unimap_ioctl21(cmd, up, perm, vc);
+#endif
+
 	default:
 		return -ENOIOCTLCMD;
 	}
@@ -1026,16 +1203,20 @@ compat_kdfontop_ioctl(struct compat_console_font_op __user *fontop,
 }
 
 struct compat_unimapdesc {
-	unsigned short entry_ct;
+	u16 entry_ct;
 	compat_caddr_t entries;
 };
 
+#ifdef CONFIG_FB_GLYPH_21BIT
 static inline int
 compat_unimap_ioctl(unsigned int cmd, struct compat_unimapdesc __user *user_ud,
 			 int perm, struct vc_data *vc)
 {
 	struct compat_unimapdesc tmp;
 	struct unipair __user *tmp_entries;
+	struct unipair21 *tmp21_entries = NULL;
+	int ret = 0, ret1 = 0;
+	unsigned int ct;
 
 	if (copy_from_user(&tmp, user_ud, sizeof tmp))
 		return -EFAULT;
@@ -1044,14 +1225,83 @@ compat_unimap_ioctl(unsigned int cmd, struct compat_unimapdesc __user *user_ud,
 	case PIO_UNIMAP:
 		if (!perm)
 			return -EPERM;
-		return con_set_unimap(vc, tmp.entry_ct, tmp_entries);
+		ret = vt__user_unipair_8_to_21(tmp.entry_ct, tmp_entries,
+					       &tmp21_entries);
+		if (ret)
+			return ret;
+		ret = con_set_unimap(vc, tmp.entry_ct, tmp21_entries);
+		kfree(tmp21_entries);
+		return ret;
+
 	case GIO_UNIMAP:
 		if (!perm && fg_console != vc->vc_num)
 			return -EPERM;
-		return con_get_unimap(vc, tmp.entry_ct, &(user_ud->entry_ct), tmp_entries);
+		tmp21_entries = kmalloc_array(tmp.entry_ct,
+					      sizeof(struct unipair21),
+					      GFP_KERNEL);
+		if (!tmp21_entries)
+			return -ENOMEM;
+		ret = con_get_unimap(vc, tmp.entry_ct, &ct, tmp21_entries);
+		if (ret)
+			goto free_tmp21;
+		ret = vt_unipair_21_to__user_8(ct, tmp21_entries, tmp_entries);
+free_tmp21:	ret1 = put_user(ct, &user_ud->entry_ct);
+		kfree(tmp21_entries);
+		return ret ? ret : ret1;
+	}
+	return 0;
+}
+#else
+static inline int
+compat_unimap_ioctl(unsigned int cmd, struct compat_unimapdesc __user *user_ud,
+			 int perm, struct vc_data *vc)
+{
+	struct compat_unimapdesc tmp;
+	struct unipair __user *tmp_entries;
+	struct unipair *k_entries;
+	int ret = 0, ret1 = 0;
+
+	if (copy_from_user(&tmp, user_ud, sizeof(tmp)))
+		return -EFAULT;
+	tmp_entries = compat_ptr(tmp.entries);
+	switch (cmd) {
+	case PIO_UNIMAP:
+		if (!perm)
+			return -EPERM;
+		k_entries = kmalloc_array(tmp.entry_ct,
+					  sizeof(struct unipair),
+					  GFP_KERNEL);
+		if (!k_entries)
+			return -ENOMEM;
+		if (copy_from_user(k_entries, tmp_entries,
+				   tmp.entry_ct * sizeof(struct unipair))) {
+			ret = -EFAULT;
+			goto free_k_entries;
+		}
+		ret = con_set_unimap(vc, tmp.entry_ct, k_entries);
+free_k_entries: kfree(k_entries);
+		return ret;
+	case GIO_UNIMAP:
+		if (!perm && fg_console != vc->vc_num)
+			return -EPERM;
+		k_entries = kmalloc_array(tmp.entry_ct, sizeof(struct unipair),
+					  GFP_KERNEL);
+		if (!k_entries)
+			return -ENOMEM;
+		ret = con_get_unimap(vc, tmp.entry_ct, &tmp.entry_ct,
+				     k_entries);
+		if (ret)
+			goto free_k_entries1;
+		if (copy_to_user(tmp_entries, k_entries,
+				 tmp.entry_ct * sizeof(struct unipair)))
+			ret = -EFAULT;
+free_k_entries1: ret1 = put_user(tmp.entry_ct, &user_ud->entry_ct);
+		kfree(k_entries);
+		return ret ? ret : ret1;
 	}
 	return 0;
 }
+#endif /* CONFIG_FB_GLYPH_21BIT */
 
 long vt_compat_ioctl(struct tty_struct *tty,
 	     unsigned int cmd, unsigned long arg)
diff --git a/include/uapi/linux/kd.h b/include/uapi/linux/kd.h
index 6b384065c013..342654dfbc16 100644
--- a/include/uapi/linux/kd.h
+++ b/include/uapi/linux/kd.h
@@ -56,7 +56,7 @@ typedef char scrnmap_t;
 #define GIO_SCRNMAP	0x4B40	/* get screen mapping from kernel */
 #define PIO_SCRNMAP	0x4B41	/* put screen mapping table in kernel */
 #define GIO_UNISCRNMAP  0x4B69	/* get full Unicode screen mapping */
-#define PIO_UNISCRNMAP  0x4B6A  /* set full Unicode screen mapping */
+#define PIO_UNISCRNMAP  0x4B6A	/* set full Unicode screen mapping */
 
 #define GIO_UNIMAP	0x4B66	/* get unicode-to-font mapping from kernel */
 struct unipair {
@@ -67,8 +67,19 @@ struct unimapdesc {
 	unsigned short entry_ct;
 	struct unipair __user *entries;
 };
+struct unipair21 {
+	unsigned int unicode;
+	unsigned int fontpos;
+};
+struct unimapdesc21 {
+	unsigned int entry_ct;
+	struct unipair21 __user *entries;
+};
 #define PIO_UNIMAP	0x4B67	/* put unicode-to-font mapping in kernel */
 #define PIO_UNIMAPCLR	0x4B68	/* clear table, possibly advise hash algorithm */
+#define GIO_UNIMAP21	0x4B6E	/* get 21-bit unicode-to-font mapping from kernel */
+#define PIO_UNIMAP21	0x4B6F	/* put 21-bit unicode-to-font mapping to kernel */
+
 struct unimapinit {
 	unsigned short advised_hashsize;  /* 0 if no opinion */
 	unsigned short advised_hashstep;  /* 0 if no opinion */
@@ -185,6 +196,6 @@ struct console_font {
 
 /* note: 0x4B00-0x4B4E all have had a value at some time;
    don't reuse for the time being */
-/* note: 0x4B60-0x4B6D, 0x4B70-0x4B72 used above */
+/* note: 0x4B60-0x4B6F, 0x4B70-0x4B72 used above */
 
 #endif /* _UAPI_LINUX_KD_H */
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
index d008c3d0a9bb..af99aeefe177 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -90,13 +96,13 @@ static inline int con_clear_unimap(struct vc_data *vc)
 	return 0;
 }
 static inline
-int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
+int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair *list)
 {
 	return 0;
 }
 static inline
-int con_get_unimap(struct vc_data *vc, ushort ct, ushort __user *uct,
-		   struct unipair __user *list)
+int con_get_unimap(struct vc_data *vc, ushort ct, ushort *uct,
+		   struct unipair *list)
 {
 	return -EINVAL;
 }


-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [Patch 9/9]: vt: Misc changes, e.g. to #include directives
  2026-08-27 18:38 vt: Enlarge the framebuffer glyph size from 16 to 32 bits Alan Mackenzie
                   ` (7 preceding siblings ...)
  2026-08-27 18:56 ` [Patch 8/9]: vt: Enhancements to the VT ioctl interface Alan Mackenzie
@ 2026-08-27 18:58 ` Alan Mackenzie
  2026-08-27 19:25   ` sashiko-bot
  2026-08-29 12:06   ` Greg Kroah-Hartman
  2026-08-28  6:12 ` vt: Enlarge the framebuffer glyph size from 16 to 32 bits Thomas Zimmermann
  9 siblings, 2 replies; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-27 18:58 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, Thomas Zimmermann, linux-fbdev,
	dri-devel
  Cc: acm

vt: 32b glyph: 9. Misc changes, e.g. to #include directives

Rearrange some #include directives, add some clarifying
comments, and some code changes which didn't fit elsewhere.

Signed-off-by: Alan Mackenzie <acm@muc.de>

diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
index dfdea0842149..35da5e127a49 100644
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -25,7 +25,6 @@
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
-#include <linux/consolemap.h>
 #include <linux/init.h>
 #include <linux/input.h>
 #include <linux/jiffies.h>
@@ -46,6 +45,7 @@
 #include <linux/tty.h>
 #include <linux/uaccess.h>
 #include <linux/vt_kern.h>
+#include <linux/consolemap.h>
 
 #include <asm/irq_regs.h>
 
diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c
index 13f4e48b4142..18affdb3c7c5 100644
--- a/drivers/tty/vt/selection.c
+++ b/drivers/tty/vt/selection.c
@@ -48,6 +48,8 @@ static struct vc_selection {
 	.start = -1,
 };
 
+static unsigned int size_row;
+
 /* clear_selection, highlight and highlight_pointer can be called
    from interrupt (via scrollback/front) */
 
@@ -340,6 +342,7 @@ static int vc_selection(struct vc_data *vc, struct tiocl_selection *v,
 {
 	int ps, pe;
 
+	size_row = vc->vc_size_row;
 	poke_blanked_console();
 
 	if (v->sel_mode == TIOCL_SELCLEAR) {
diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c
index 7d40eacc21b3..f23d61fe10c1 100644
--- a/drivers/tty/vt/vc_screen.c
+++ b/drivers/tty/vt/vc_screen.c
@@ -201,6 +201,8 @@ static struct vc_data *vcs_vc(struct inode *inode, bool *viewed)
 
 /**
  * vcs_size - return size for a VC in @vc
+ * The size is the number of bytes required by a buffer reading to/writing from
+ * the entire device.
  * @vc: which VC
  * @attr: does it use attributes?
  * @unicode: is it unicode?
@@ -307,7 +314,9 @@ static unsigned int vcs_read_buf(const struct vc_data *vc, char *con_buf,
 		getconsxy(vc, con_buf + 2);
 
 		*skip += pos;
-		count += pos;
+		count += pos;	/* COUNT is now the byte offset of the end
+				 * position from the start of the header.
+				 */
 		if (count > CON_BUF_SIZE) {
 			count = CON_BUF_SIZE;
 			filled = count - pos;
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 8f467b22b799..0c389a564357 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -89,6 +89,7 @@
 #include <linux/init.h>
 #include <linux/mutex.h>
 #include <linux/vt_kern.h>
+#include <linux/vt_buffer.h>
 #include <linux/selection.h>
 #include <linux/tiocl.h>
 #include <linux/kbd_kern.h>
@@ -4910,15 +4967,17 @@ static int con_font_get(struct vc_data *vc, struct console_font_op *op)
 static int con_font_get(struct vc_data *vc, struct console_font_op *op)
 {
 	struct console_font font;
 	int c;
-	unsigned int vpitch = op->op == KD_FONT_OP_GET_TALL ? op->height : 32;
+	unsigned int vpitch =
+		op->op == KD_FONT_OP_GET_TALL ? vc->vc_font.height : 32;
 
 	if (vpitch > max_font_height)
 		return -EINVAL;
 
 	void *font_data __free(kvfree) = NULL;
+	c = DIV_ROUND_UP(vc->vc_font.width, 8) * vpitch * vc->vc_font.charcount;
 	if (op->data) {
-		font.data = font_data = kvzalloc(max_font_size, GFP_KERNEL);
+		font.data = font_data = kvzalloc(c, GFP_KERNEL);
 		if (!font.data)
 			return -ENOMEM;
 	} else
@@ -4935,8 +4994,6 @@ static int con_font_get(struct vc_data *vc, struct console_font_op *op)
 			return ret;
 	}
 
-	c = DIV_ROUND_UP(font.width, 8) * vpitch * font.charcount;
-
 	if (op->data && font.charcount > op->charcount)
 		return -ENOSPC;
 	if (font.width > op->width || font.height > op->height)
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index fe915afdece5..5afce52057ca 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -13,6 +13,7 @@
 #ifndef _LINUX_CONSOLE_STRUCT_H
 #define _LINUX_CONSOLE_STRUCT_H
 
+#include <linux/consolemap.h>
 #include <linux/vt.h>
 #include <linux/wait.h>
 #include <linux/workqueue.h>


-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* Re: [Patch 5/9]: vt: Amend three Kconfig files
  2026-08-27 18:50 ` [Patch 5/9]: vt: Amend three Kconfig files Alan Mackenzie
@ 2026-08-27 19:12   ` sashiko-bot
  0 siblings, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-08-27 19:12 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: linux-serial, dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Enabling CONFIG_FB_GLYPH_21BIT changes GLYPH_SZ to 4 globally, breaking hardware console drivers (like vgacon) and rotated fbcon drivers that still hardcode 16-bit screen buffer accesses.
- [Medium] FB_GLYPH_21BIT is a generic VT/fbdev feature but is inappropriately placed in the DRM clients menu, unnecessarily restricting its use.
--

commit 7b9fb605add2fa8c6f62e8ba79cee762cbcbd24c
Author: Alan Mackenzie <acm@muc.de>

vt: Amend three Kconfig files

Until it becomes clear how to integrate CONFIG_A11Y_BRAILLE_CONSOLE and
CONFIG_SPEAKUP, they will remain disabled when CONFIG_FB_GLYPH_21BIT is
#defined.

[ ... ]

> diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
> index 6096c623d9d5b..0ce8c1506eaad 100644
> --- a/drivers/gpu/drm/clients/Kconfig
> +++ b/drivers/gpu/drm/clients/Kconfig
> @@ -42,6 +42,15 @@ config DRM_FBDEV_EMULATION
>  
>  	  If in doubt, say "Y".
>  
> +config FB_GLYPH_21BIT
> +	bool "Enable 21-bit glyphs in the frame buffer device"
> +	depends on (DRM_FBDEV_EMULATION = y)

[Severity: Medium]
Could placing this generic VT/fbdev feature in the DRM clients menu
inappropriately restrict its use?

Native framebuffer drivers (e.g., vesafb, efifb, simplefb) might be prevented
from utilizing 21-bit glyph support, as this strictly requires
DRM_FBDEV_EMULATION. The feature modifies the generic Virtual Terminal and
fbcon subsystems, which are independent of DRM.

[Severity: High]
Can enabling CONFIG_FB_GLYPH_21BIT corrupt the screen buffer for hardware
console drivers and rotated fbcon drivers?

When this is enabled, GLYPH_SZ becomes 4 globally and VT uses 32-bit screen
buffer entries. However, the consw interface still passes pointers typed as
const unsigned short *, and some drivers still hardcode 16-bit accesses.

For example, rotated fbcon implementations iterate the buffer using scr_readw()
which advances the u16 pointer by 2 bytes instead of 4, reading half-characters:

drivers/video/fbdev/core/fbcon_cw.c:cw_putcs_aligned() {
    ...
    src = par->rotated.buf + (scr_readw(s++) & charmask) * cellsize;
    ...
}

Similarly, vgacon maps physical VGA VRAM assuming exactly 16 bits per character,
meaning 32-bit writes from scr_writeg in vt.c will overwrite adjacent cells
in hardware memory:

drivers/video/console/vgacon.c:vgacon_invert_region() {
    ...
    u16 a = scr_readw(p);
    ...
    scr_writew(a, p++);
    ...
}

> +	help
> +	  This option enables 21-bit glyphs in the fbdev framebuffer,
> +	  surmounting the traditional restriction to 256/512 glyphs dating back
> +	  to the VGA hardware of the 1980s and 1990s.  To use this effectively
> +	  you will need a suitable font and possibly a suitable keyboard layout.
> +

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/apCEDM2sWv_M354-@MAC.fritz.box?part=5

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 7/9]: vt: Handle up to 2^21 glyphs, rather than 256/512
  2026-08-27 18:54 ` [Patch 7/9]: vt: Handle up to 2^21 glyphs, rather than 256/512 Alan Mackenzie
@ 2026-08-27 19:14   ` sashiko-bot
  0 siblings, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-08-27 19:14 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: linux-serial, dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The intended support for large 21-bit fonts is functionally broken, and allocations near the 4MB boundary trigger a kernel warning.
--

commit 0f8b2aafff067c8299c320bab75fcb965a117b84
Author: Alan Mackenzie <acm@muc.de>

vt: Handle up to 2^21 glyphs, rather than 256/512

This commit increases the maximum number of font glyphs supported by the
vt subsystem to 2^21 to supplement tests with full Unicode limits.

> diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
> index 9208970f731e0..baf7c98429e9c 100644
> --- a/drivers/tty/vt/vt.c
> +++ b/drivers/tty/vt/vt.c
[ ... ]
> @@ -5015,17 +5016,23 @@ static int con_font_set(struct vc_data *vc, const struct console_font_op *op)
[ ... ]
>  	size = DIV_ROUND_UP(op->width, 8) * vpitch * op->charcount;
> +#ifndef CONFIG_FB_GLYPH_21BIT
>  	if (size > max_font_size)
>  		return -ENOSPC;
> -
> +#endif
>  	void *font_data __free(kfree) = font.data = memdup_user(op->data, size);

[Severity: High]
Will memdup_user() reliably handle the newly permitted size allocations?

Because memdup_user() relies on kmalloc(), it is bounded to allocations
up to 4MB (MAX_PAGE_ORDER). If a user requests a font with 2^21 glyphs, the
size could easily exceed this limit, causing the allocation to silently fail
with -ENOMEM. 

Would it be better to use vmemdup_user() here to fully support the larger
21-bit limit?

> diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
> index 576be479d9286..a610071c89f53 100644
> --- a/drivers/video/fbdev/core/fbcon.c
> +++ b/drivers/video/fbdev/core/fbcon.c
[ ... ]
> @@ -2525,10 +2531,18 @@ static int fbcon_set_font(struct vc_data *vc, const struct console_font *font,
>  	int i, ret;
>  	font_data_t *new_data;
>  
> -	/* Is there a reason why fbconsole couldn't handle any charcount >256?
> -	 * If not this check should be changed to charcount < 256 */
> +#ifdef CONFIG_FB_GLYPH_21BIT
> +	if (charcount < 256)
> +		return -EINVAL;

[Severity: High]
Does this relaxed limit expose a kernel warning crash vector in the page
allocator?

By allowing charcount to reach 2^21, font allocations can approach or exactly
hit the 4MB limit. When font_data_import() is called later in this function
with these parameters, it does the following:

lib/fonts/fonts.c:font_data_import() {
    ...
    alloc_size = size + sizeof(*font_data);
    font_data = kmalloc(alloc_size, GFP_USER);
    ...
}

If a user with CAP_SYS_TTY_CONFIG configures a font where the requested size
is exactly 4MB (which passes the memdup_user() check in con_font_set()), the
addition of sizeof(*font_data) will push the alloc_size slightly over 4MB. 

Since this kmalloc() lacks the __GFP_NOWARN flag and requires an order 11
allocation, it will trigger a WARN_ON_ONCE in the page allocator. This could
crash systems configured with panic_on_warn.

> +#else
> +	/* There is no longer any reason why fbconsole can't handle
> +	 * any charcount >256, when CONFIG_FB_GLYPH_21BIT is #defined.
> +	 * Hence this check has been changed to charcount < 256
> +	 * above.
> +	 */
>  	if (charcount != 256 && charcount != 512)
>  		return -EINVAL;
> +#endif

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/apCEDM2sWv_M354-@MAC.fritz.box?part=7

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 1/9]: Make consolemap.c handle Unicode planes outside BMP
  2026-08-27 18:42 ` [Patch 1/9]: Make consolemap.c handle Unicode planes outside BMP Alan Mackenzie
@ 2026-08-27 19:14   ` sashiko-bot
  2026-08-28  4:57   ` Jiri Slaby
  1 sibling, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-08-27 19:14 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: linux-serial, dri-devel

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:

New issues:
- [Critical] Use-After-Free window during active dictionary reallocation for inverse translation maps.
- [High] Unicode characters >= 512 are incorrectly rejected in conv_uni_to_pc when CONFIG_FB_GLYPH_21BIT is disabled.
- [High] dict->count is misused as the maximum font glyph index, is uninitialized for default maps, and causes functional breakage.
- [Medium] Empty glyph slots are incorrectly copied due to C integer promotion in con_unshare_unimap when using 16-bit values.
- [Medium] Incorrect allocation size calculation wastes memory for user-controlled allocations.
- [Low] Label at the end of a compound statement without a trailing null statement causes a build failure.

Pre-existing issues:
- [Critical] Concurrent read UAF in con_unshare_unimap error path.
--

commit 58f6de25804fcafcee92f219003205102885c384
Author: Alan Mackenzie <acm@muc.de>

vt: 32b glyph: 1. Make consolemap.c handle Unicode planes outside BMP

This commit adds a fourth layer to the sparse map in consolemap.c to handle
Unicode planes outside the Basic Multilingual Plane (BMP). It increases the
supported glyph number limit up to 2^21 and shifts user-to-kernel data
conversions to vt_ioctl.c.

> diff --git a/drivers/tty/vt/consolemap.c b/drivers/tty/vt/consolemap.c
> index 3fa89a2dbebab..2f695d97f0ebd 100644
> --- a/drivers/tty/vt/consolemap.c
> +++ b/drivers/tty/vt/consolemap.c
[ ... ]
> @@ -235,23 +248,22 @@ static void set_inverse_transl(struct vc_data *conp, struct uni_pagedict *dict,
>  {
[ ... ]
> -	if (!inv) {
> -		inv = dict->inverse_translations[m] = kmalloc(MAX_GLYPH,
> -				GFP_KERNEL);
> -		if (!inv)
> -			return;
> -	}
> -	memset(inv, 0, MAX_GLYPH);
> +	kfree(inv);
> +	inv = dict->inverse_translations[m] =
> +		kmalloc(dict->count * (sizeof(*t)), GFP_KERNEL);

[Severity: Critical]
Does this create a Use-After-Free window? Since GFP_KERNEL allocations can 
sleep, freeing the active dictionary array and leaving the pointer populated 
before the reallocation completes might expose freed memory to concurrent 
lockless readers (like accessibility driver keyboard handlers) calling 
inverse_translate().

[Severity: Medium]
Also, does this calculation allocate more memory than intended? inv is an 
unsigned char pointer, but the allocation uses sizeof(*t) where t is a 
u1632 pointer. This allocates 2-4x the needed size for 8-bit inverse maps 
based on a user-controlled element count.

> +	if (!inv)
> +		return;
> +	memset(inv, 0, dict->count * (sizeof(*t)));
[ ... ]
> @@ -252,39 +265,48 @@ static void set_inverse_transl(struct vc_data *conp, struct uni_pagedict *dict,
>  static void set_inverse_trans_unicode(struct uni_pagedict *dict)
>  {
[ ... ]
> -	if (!inv) {
> -		inv = dict->inverse_trans_unicode = kmalloc_array(MAX_GLYPH,
> -				sizeof(*inv), GFP_KERNEL);
> -		if (!inv)
> -			return;
> -	}
> -	memset(inv, 0, MAX_GLYPH * sizeof(*inv));
> +	kfree(inv);
> +	inv = dict->inverse_trans_unicode =
> +		kmalloc_array(dict->count, sizeof(*inv), GFP_KERNEL);

[Severity: Critical]
Similar to the 8-bit inverse translation map above, could this expose a freed
pointer to concurrent readers while waiting on the GFP_KERNEL allocation?

> +	if (!inv)
> +		return;
> +	memset(inv, 0, dict->count * sizeof(*inv));
[ ... ]
> @@ -297,18 +319,19 @@ unsigned short *set_translate(enum translation_map m, struct vc_data *vc)
>   *    was active.
>   * Still, it is now possible to a certain extent to cut and paste non-ASCII.
>   */
> -u16 inverse_translate(const struct vc_data *conp, u16 glyph, bool use_unicode)
> +u1632 inverse_translate(const struct vc_data *conp, u1632 glyph,
> +			bool use_unicode)
>  {
>  	struct uni_pagedict *p;
>  	enum translation_map m;
>  
> -	if (glyph >= MAX_GLYPH)
> -		return 0;
> -
>  	p = *conp->uni_pagedict_loc;
>  	if (!p)
>  		return glyph;
>  
> +	if (glyph >= p->count)
> +		return 0;

[Severity: High]
Is p->count the correct upper boundary for a font index here? dict->count 
is initialized to the number of user-provided mapping pairs, which might be 
much smaller than the highest glyph index in the font. This seems like it 
will falsely reject valid glyphs.

Furthermore, looking at con_set_default_unimap() (not modified here but 
impacted), dict->count is never initialized to 256 for default maps, leaving 
it at 0. Won't this break reverse translations for default fonts entirely?

> +
>  	if (use_unicode) {
>  		if (!p->inverse_trans_unicode)
>  			return glyph;
[ ... ]
> @@ -587,63 +639,70 @@ static struct uni_pagedict *con_unshare_unimap(struct vc_data *vc,
[ ... ]
> +				for (g = 0; g < UNI_ROW_GLYPHS; g++, uni++) {
> +					if (row[g] == ~0)
> +						continue;

[Severity: Medium]
Will this optimization check fail when CONFIG_FB_GLYPH_21BIT is disabled? 
In that configuration, u1632 is a 16-bit unsigned integer. Due to C integer 
promotion, 0xFFFF is zero-extended to 65535, which does not equal the 
signed int -1. 

It looks like this will result in all uninitialized empty slots being copied 
via con_insert_unipair(), wasting memory and corrupting dict->sum by 
adding 0xFFFF for each empty slot.

> +					/*
> +					 * Found one, copy entry for unicode
> +					 * uni with fontpos value row[g].
> +					 */
> +					ret = con_insert_unipair(new, uni,
> +								 row[g]);
> +					if (ret) {
> +						old->refcount++;
> +						*vc->uni_pagedict_loc = old;
> +						con_release_unimap(new);
> +						kfree(new);

[Severity: Critical]
This is a pre-existing issue, but does this error path leave a dangling 
pointer? The new dictionary was already published to readers globally via 
*vc->uni_pagedict_loc = new inside con_allocate_new(). Freeing the new 
dict here could cause a Use-After-Free for concurrent lockless readers.

> +						return ERR_PTR(ret);
> +					}
>  				}
>  			}
>  		}
>  	}
> -
>  	return new;
>  }
>  
> -int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
> +int con_set_unimap(struct vc_data *vc, u32 ct, struct unipair8_21 *list)
>  {
[ ... ]
> @@ -660,7 +719,9 @@ int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
>  	/*
>  	 * Insert user specified unicode pairs into new table.
>  	 */
> -	for (plist = unilist; ct; ct--, plist++) {
> +	err = 0;
> +	dict->count = ct;
> +	for (plist = list; ct; ct--, plist++) {

[Severity: High]
As mentioned above, assigning ct to dict->count seems to mistakenly track 
the total number of user map entries instead of the maximum glyph index, which 
breaks subsequent boundary checks.

>  		int err1 = con_insert_unipair(dict, plist->unicode, plist->fontpos);
>  		if (err1)
>  			err = err1;
[ ... ]
> @@ -769,54 +830,69 @@ EXPORT_SYMBOL(con_copy_unimap);
>   *	Read the console unicode data for this console. Called from the ioctl
>   *	handlers.
>   */
> -int con_get_unimap(struct vc_data *vc, ushort ct, ushort __user *uct,
> -		struct unipair __user *list)
> +int con_get_unimap(struct vc_data *vc, u32 ct, u1632 *uct,
> +		   struct unipair8_21 *list)
>  {
[ ... ]
> +					for (g = 0; g < UNI_ROW_GLYPHS; g++, row++) {
> +						if (*row >= dict->count)
> +							continue;

[Severity: High]
Does this drop valid unicode mappings when reading back the unimap? If *row 
is a valid glyph index larger than the total number of mapping pairs 
(dict->count), it seems it will be skipped entirely.

> -					if (ect < ct) {
> -						unilist[ect].unicode = UNI(d, r, g);
> -						unilist[ect].fontpos = *row;
> +						if (ect < ct) {
> +#ifndef CONFIG_FB_GLYPH_21BIT
> +							if ((p || *row > 0xffff)) {
> +								ret = -EINVAL;
> +								goto unlock;
> +							}
> +#endif
> +							if (list) {
> +								list[ect].unicode = UNI(p, d, r, g);
> +								list[ect].fontpos = *row;
> +							}
> +						}
> +						ect++;
>  					}
> -					ect++;
>  				}
>  			}
>  		}
> +#ifndef CONFIG_FB_GLYPH_21BIT
> +unlock:
> +#endif
>  	}

[Severity: Low]
When CONFIG_FB_GLYPH_21BIT is disabled, will this label at the end of the 
compound statement cause a build failure? Older or strict C compilers require 
a trailing empty statement (like a semicolon) after a label that sits right 
before a closing brace.

> -
> -	if (copy_to_user(list, unilist, min(ect, ct) * sizeof(*unilist)))
> -		return -EFAULT;
> -	if (put_user(ect, uct))
> -		return -EFAULT;
> -	if (ect > ct)
> -		return -ENOMEM;
> -
> -	return 0;
> +	*uct = ect;
[ ... ]
> @@ -849,26 +925,33 @@ int conv_uni_to_8bit(u32 uni)
>  int conv_uni_to_pc(struct vc_data *conp, long ucs)
>  {
>  	struct uni_pagedict *dict;
> -	u16 **dir, *row, glyph;
> +	u1632 ***plane, **dir, *row, glyph;
>  
> -	/* Only 16-bit codes supported at this time */
> -	if (ucs > 0xffff)
> -		return -4;		/* Not found */
> +	if (ucs >= MAX_GLYPH)
> +		return -4;	/* Not found */

[Severity: High]
Does this improperly restrict valid Unicode code points when 
CONFIG_FB_GLYPH_21BIT is disabled? MAX_GLYPH resolves to 512 in that 
configuration. 

This validation replaces the old ucs > 0xffff boundary check. Valid Unicode 
code points (like Cyrillic, Greek, or Arabic) that are numerically greater 
than 512 appear to be erroneously rejected here, breaking console text 
rendering for non-Latin alphabets.

>  	else if (ucs < 0x20)
>  		return -1;		/* Not a printable character */
> +	else if (ucs == 0xfeff || (ucs >= 0x200b && ucs <= 0x200f))
> +		return -2;			/* Zero-width space */

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/apCEDM2sWv_M354-@MAC.fritz.box?part=1

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 3/9]: Replace scr_readw/writew by scr_readg/writeg, etc
  2026-08-27 18:47 ` [Patch 3/9]: Replace scr_readw/writew by scr_readg/writeg, etc Alan Mackenzie
@ 2026-08-27 19:18   ` sashiko-bot
  2026-08-29 12:11   ` Greg Kroah-Hartman
  1 sibling, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-08-27 19:18 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: linux-serial, dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The loop evaluating screen blankness in fbcon_prepare_logo increments the pointer incorrectly, leading to overlapping/unaligned 32-bit glyph reads and an out-of-bounds read.
- [High] Incorrect pointer arithmetic in fbcon_switch when scrolling the logo leads to severe screen buffer corruption in 32-bit glyph mode.
--

commit 161dfd956a8c1063b40c6d0ff761c9b6540eff43
Author: Alan Mackenzie <acm@muc.de>

Replace scr_readw/writew by scr_readg/writeg, etc

This patch updates vt and fbcon drivers to handle 32-bit glyphs by
replacing 16-bit specific scr_readw/writew with scr_readg/writeg,
adjusting pointers and attributes accordingly.

> diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
> --- a/drivers/video/fbdev/core/fbcon.c
> +++ b/drivers/video/fbdev/core/fbcon.c

[ ... ]

> @@ -663,8 +663,8 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
>  	q = (unsigned short *) (vc->vc_origin +
>  				vc->vc_size_row * rows);
>  	step = logo_lines * cols;
> -	for (r = q - logo_lines * cols; r < q; r++)
> -		if (scr_readw(r) != vc->vc_video_erase_char)
> +	for (r = q - logo_lines * cols * GLYPH_HW; r < q; r++)
> +		if (scr_readg(r) != vc->vc_video_erase_char)

[Severity: High]
Could this loop read out of bounds when evaluating screen blankness?
The pointer r is of type unsigned short *, so r++ advances it by 2 bytes.
However, scr_readg() reads 4 bytes, resulting in overlapping reads. On the
final iteration (when r equals q - 1), it appears scr_readg() would read 2
bytes past the end of the screen buffer allocation. Should this increment by
GLYPH_HW instead?

>  			break;
>  	if (r != q && new_rows >= rows + logo_lines) {
>  		save = kmalloc(array3_size(logo_lines, new_cols, GLYPH_SZ),

[ ... ]

> @@ -2161,8 +2165,23 @@ static bool fbcon_switch(struct vc_data *vc)
>  		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;

[Severity: High]
Does this pointer arithmetic corrupt the console screen buffer for 32-bit
glyphs? The pointers d and s are unsigned short *, so advancing them by
vc_cols moves them forward by vc_cols * 2 bytes. Since conp2->vc_size_row
is vc_cols * 4 bytes for 32-bit glyphs, this seems to advance the pointers
by exactly half a row.

Because of this, the next loop iteration would overwrite the middle of the
previously copied row. Furthermore, the subsequent scr_memset_worl() would
start clearing from the wrong offset. Should these pointers be advanced by
conp2->vc_cols * GLYPH_HW?

> +			}
> +			scr_memset_worl(d, conp2->vc_video_erase_char,
> +				    conp2->vc_size_row * logo_lines);
>  			conp2->vc_top = 0;
> +		}
>  		logo_shown = FBCON_LOGO_CANSHOW;
>  	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/apCEDM2sWv_M354-@MAC.fritz.box?part=3

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 4/9]: Amend internal manipulation of glyph structure
  2026-08-27 18:48 ` [Patch 4/9]: Amend internal manipulation of glyph structure Alan Mackenzie
@ 2026-08-27 19:19   ` sashiko-bot
  0 siblings, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-08-27 19:19 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: dri-devel, linux-serial

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] Missing bounds check in `bit_cursor` allows an out-of-bounds read of kernel memory, leading to a severe information leak, exacerbated by the new `vc_char_mask`.
- [High] `vc_do_resize` corrupts `vc_video_erase_char` by overwriting the correct value from `update_attr(vc)` with a manually calculated, malformed value.
- [High] `fbcon_init` fails to initialize `vc_char_mask` and `vc_attr_shift_pos` when `init == false`, truncating 512-character fonts to 256 characters during console driver binding.
- [High] The patch removed the left-shift of attributes for 512-character fonts in `build_attr()`, but failed to update `vc_invert_attr()` and `csi_RSB()`, which still expect the shifted format.
- [High] `vc_complement_mask` is incorrectly initialized for 512-character fonts due to out-of-order execution between `fbcon_init` and `visual_init`.
--

commit 2c79dd51730bb5b8d8d112b0649dbd28e356ba79
Author: Alan Mackenzie <acm@muc.de>

vt: 32b glyph: 4. Amend internal manipulation of glyph structure

This commit replaces hard-coded bitmask constants with new variables
in the vc_data structure to handle the extraction and insertion of bit
fields for 16-bit and 32-bit glyphs.

> diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
> --- a/drivers/tty/vt/vt.c
> +++ b/drivers/tty/vt/vt.c
> @@ -1051,19 +1036,37 @@ static void visual_init(struct vc_data *vc, int num, bool init)
>  		con_free_unimap(vc);
>  	vc->uni_pagedict_loc = &vc->uni_pagedict;
>  	vc->uni_pagedict = NULL;
> -	vc->vc_hi_font_mask = 0;
> +	vc->vc_hi_font_mask = 0; /* Probably redundant, 2025-01-16. */
>  	vc->vc_complement_mask = 0;
>  	vc->vc_can_do_color = 0;
>  	vc->vc_cur_blink_ms = DEFAULT_CURSOR_BLINK_MS;
> +
>  	vc->vc_sw->con_init(vc, init);

[ ... ]

>  	if (!vc->vc_complement_mask)
> -		vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
> +		vc->vc_complement_mask = (vc->vc_can_do_color ? 0x77 : 0x08)
> +			<< vc->vc_attr_shift_pos;

[Severity: High]
Can this fail to correct a wrongly initialized complement mask?

visual_init() calls the console driver init (which reaches fbcon_init)
before vc_attr_shift_pos is fully initialized. fbcon_init() configures
vc_complement_mask falling back to a shift of 8.

When execution returns here, the zero-check skips execution because the
mask is already non-zero. For 512-character fonts, does this leave the
mask targeting bits 8-15 instead of 9-15, causing the mouse pointer to
corrupt the character glyph index?

[ ... ]

> @@ -1185,12 +1188,31 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
>  	unsigned int new_cols, new_rows, new_row_size, new_screen_size;
>  	unsigned short *oldscreen, *newscreen;
>  	u32 **new_uniscr = NULL;
> -
> +	unsigned int new_vc_char_mask = vc->vc_char_mask;
> +	unsigned int new_vc_attr_mask = vc->vc_attr_mask;
> +	unsigned int new_vc_attr_shift_pos = vc->vc_attr_shift_pos;
> +	unsigned int new_vc_video_erase_char = vc->vc_video_erase_char;
>  	WARN_CONSOLE_UNLOCKED();
>  
>  	if (cols > VC_MAXCOL || lines > VC_MAXROW)
>  		return -EINVAL;
>  
> +#ifndef CONFIG_FB_GLYPH_21BIT
> +	if (vc->vc_hi_font_mask == 0x100) {
> +		new_vc_char_mask = 0x1ff;
> +		new_vc_attr_mask = 0xfe00;
> +		new_vc_attr_shift_pos = 9;
> +	} else {
> +		new_vc_char_mask = 0xff;
> +		new_vc_attr_mask = 0xff00;
> +		new_vc_attr_shift_pos = 8;
> +	}
> +#endif
> +	new_vc_video_erase_char =
> +		(vc->vc_video_erase_char & vc->vc_char_mask) |
> +		((vc->vc_video_erase_char >> vc->vc_attr_shift_pos) <<
> +		 new_vc_attr_shift_pos);

[Severity: High]
Could this recalculation corrupt the erase character during a resize?

When fbcon_set_disp() triggers a resize, this section recalculates
new_vc_video_erase_char by shifting the old value, dropping bits.
Later in vc_do_resize(), the correctly rebuilt erase character
from update_attr() is explicitly overwritten with this malformed
new_vc_video_erase_char.

Will this cause severe background color corruption following a display
mode or font change?

[ ... ]

> @@ -2141,8 +2163,10 @@ static void csi_RSB(struct vc_data *vc)
>  		break;
>  	case CSI_RSB_MAKE_CUR_COLOR_DEFAULT:
>  		vc->vc_def_color = vc->vc_attr;
> +#ifndef CONFIG_FB_GLYPH_21BIT
>  		if (vc->vc_hi_font_mask == 0x100)
>  			vc->vc_def_color >>= 1;
> +#endif
>  		default_attr(vc);
>  		update_attr(vc);
>  		break;

[Severity: High]
Does this code still need to shift the default color?

The patch removes the left-shift of attributes for 512-character fonts
in build_attr(). Since vc_attr is now in an unshifted format, shifting
it right here halves the color value on a color reset instead of undoing
the shift.

[ ... ]

> @@ -3049,10 +3075,12 @@ static inline unsigned char vc_invert_attr(const struct vc_data *vc)
>  	if (!vc->vc_can_do_color)
>  		return vc->vc_attr ^ 0x08;
>  
> +#ifndef CONFIG_FB_GLYPH_21BIT
>  	if (vc->vc_hi_font_mask == 0x100)
>  		return   (vc->vc_attr & 0x11) |
>  			((vc->vc_attr & 0xe0) >> 4) |
>  			((vc->vc_attr & 0x0e) << 4);
> +#endif

[Severity: High]
Are these bitwise operations still valid for the new attribute layout?

The legacy logic in vc_invert_attr() applies bitwise transformations
(0x11, 0xe0, 0x0e) meant for attributes shifted left by 1. Applying
these to the new unshifted layout scrambles the foreground and
background colors when the selection or cursor is inverted.

> diff --git a/drivers/video/fbdev/core/bitblit.c b/drivers/video/fbdev/core/bitblit.c
> --- a/drivers/video/fbdev/core/bitblit.c
> +++ b/drivers/video/fbdev/core/bitblit.c
> @@ -260,7 +260,7 @@ static void bit_cursor(struct vc_data *vc, struct fb_info *info, bool enable,
>  {
>  	struct fb_cursor cursor;
>  	struct fbcon_par *par = info->fbcon_par;
> -	unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
> +	unsigned int charmask = vc->vc_char_mask;
>  	int w = DIV_ROUND_UP(vc->vc_font.width, 8), c;
>  	int y = real_y(par->p, vc->state.y);
>  	int attribute, use_sw = vc->vc_cursor_type & CUR_SW;

[Severity: Critical]
Does this sequence allow an out-of-bounds read of kernel memory?

The function extracts the character index from the screen buffer without
validating it against vc_font.charcount. Under CONFIG_FB_GLYPH_21BIT,
vc_char_mask expands to 0x1fffff, removing the implicit bitmask limit.

When calculating the pointer later in bit_cursor():

src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height));

Could a user with write access to /dev/vcsaN write crafted 32-bit
values to the screen buffer to leak sensitive memory to the display?

> diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
> --- a/drivers/video/fbdev/core/fbcon.c
> +++ b/drivers/video/fbdev/core/fbcon.c
> @@ -1156,14 +1156,8 @@ static void fbcon_init(struct vc_data *vc, bool init)
>  	}
>  
>  	vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1);
> -	vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
> -	if (vc->vc_font.charcount == 256) {
> -		vc->vc_hi_font_mask = 0;
> -	} else {
> -		vc->vc_hi_font_mask = 0x100;
> -		if (vc->vc_can_do_color)
> -			vc->vc_complement_mask <<= 1;
> -	}
> +	vc->vc_complement_mask = (vc->vc_can_do_color ? 0x77 : 0x08) <<
> +		(vc->vc_attr_shift_pos ? vc->vc_attr_shift_pos : 8);

[Severity: High]
Will this skip initializing vc_char_mask when init == false?

When switching console drivers, visual_init() clears vc_hi_font_mask
and calls fbcon_init() with init == false. Because the initialization of
the font mask was removed here, vc_do_resize() forcefully applies a
256-character mask (0xff).

Does this truncate valid 512-character fonts during driver binding,
rendering the upper 256 characters unreachable?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/apCEDM2sWv_M354-@MAC.fritz.box?part=4

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 6/9]: vt: Use u32 and typedef u1632 to handle whole glyphs
  2026-08-27 18:52 ` [Patch 6/9]: vt: Use u32 and typedef u1632 to handle whole glyphs Alan Mackenzie
@ 2026-08-27 19:22   ` sashiko-bot
  0 siblings, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-08-27 19:22 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: linux-serial, dri-devel

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] Using a raw 32-bit pointer dereference for VGA I/O memory causes hardware faults on strict architectures and overflows 16-bit hardware video RAM.
- [High] Casting a 32-bit stack variable's address to a 16-bit pointer in `con_putc` leads to blank or NUL characters on all big-endian architectures when 21-bit glyphs are disabled.
- [High] Pointer arithmetic in `fbcon_switch` implicitly assumes 16-bit glyphs, causing buffer misalignment and screen corruption when 32-bit glyphs are enabled.
- [Medium] Fallback console translation macros were not updated for the new `u1632` types, resulting in data truncation and build warnings.
--

commit 6fc77218ba179478ec0c7c9f101ceadca53ba76a
Author: Alan Mackenzie <acm@muc.de>

vt: Use u32 and typedef u1632 to handle whole glyphs

vt: 32b glyph: 6. Use u32 and typedef u1632 to handle whole glyphs

The glyph size u1632 is an either 32- or 16-bit unsigned word
depending on whether or not CONFIG_FB_GLYPH_21BIT is #defined.

> diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
> index 8c7cbc47836fd..9208970f731e0 100644
> --- a/drivers/tty/vt/vt.c
> +++ b/drivers/tty/vt/vt.c
[ ... ]
> @@ -322,12 +322,12 @@ static inline u16 *screenpos(const struct vc_data *vc, unsigned int offset,
>  	return (u16 *)(origin + offset);
>  }
>  
> -static void con_putc(struct vc_data *vc, u16 ca, unsigned int y, unsigned int x)
> +static void con_putc(struct vc_data *vc, u32 ca, unsigned int y, unsigned int x)
>  {
>  	if (vc->vc_sw->con_putc)
>  		vc->vc_sw->con_putc(vc, ca, y, x);
>  	else
> -		vc->vc_sw->con_putcs(vc, &ca, 1, y, x);
> +		vc->vc_sw->con_putcs(vc, (u16 *)&ca, 1, y, x);

[Severity: High]
Does casting this 32-bit local variable address to a 16-bit pointer expose
the endian-dependent memory layout to the backend driver?

On big-endian architectures where 21-bit glyphs are disabled, this cast
points to the most-significant 16 bits of the 32-bit variable (which are
zero). This leads to the backend reading 0x0000 and rendering a NUL character
instead of the true glyph.

[ ... ]
> diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
> index 3e7cba53bdfc2..576be479d9286 100644
> --- a/drivers/video/fbdev/core/fbcon.c
> +++ b/drivers/video/fbdev/core/fbcon.c
[ ... ]
> @@ -2166,6 +2166,7 @@ static bool fbcon_switch(struct vc_data *vc)
>  	struct fbcon_par *par;
>  	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);

[Severity: High]
Does the pointer arithmetic in fbcon_switch implicitly assume 16-bit glyphs?

When a logo is displayed and the console is switched, the row pointers are
advanced using vc_cols:

			while (i--) {
				scr_memcpyw(d, s, conp2->vc_size_row);
				d += conp2->vc_cols;
				s += conp2->vc_cols;
			}

When CONFIG_FB_GLYPH_21BIT is enabled, vc_size_row is vc_cols * 4 bytes.
However, d and s are unsigned short *, so adding vc_cols only advances the
pointer by vc_cols * 2 bytes. Can this cause subsequent rows to overwrite
the second half of the previous row?

[ ... ]
> diff --git a/include/linux/consolemap.h b/include/linux/consolemap.h
> index 539d488fdc03f..5e50b4d4bd0b3 100644
> --- a/include/linux/consolemap.h
> +++ b/include/linux/consolemap.h
[ ... ]
> @@ -32,7 +42,7 @@ unsigned int ucs_get_width(uint32_t cp);
>  u32 ucs_recompose(u32 base, u32 mark);
>  u32 ucs_get_fallback(u32 cp);
>  #else
> -static inline u16 inverse_translate(const struct vc_data *conp, u16 glyph,
> +static inline u1632 inverse_translate(const struct vc_data *conp, u16 glyph,
>  		bool use_unicode)
>  {
>  	return glyph;

[Severity: Medium]
Were the fallback console translation macros missed when updating to the new
u1632 types?

The fallback for inverse_translate still accepts a u16 glyph, truncating any
32-bit glyph passed into it.

Additionally, the set_translate fallback below this (not shown in the diff)
still returns unsigned short *, which causes a type mismatch when assigned to
vc->vc_translate (which is now a u1632 *).

[ ... ]

Additionally, I have a question regarding the scr_writeg macro defined in
include/linux/vt_buffer.h, which is heavily used by these console updates:

include/linux/vt_buffer.h:
#define scr_writeg(val, addr) \
        (*((u32 *)(addr)) = (val))

[Severity: Critical]
Does using a raw 32-bit pointer dereference for VGA I/O memory cause hardware
faults on strict architectures (e.g., Alpha, MIPS)?

This bypasses architectural I/O accessors like writel/writew, which are
required for accessing MMIO space on these architectures. Furthermore, can
this overflow 16-bit hardware video RAM, since VGA VRAM requires exactly
16-bit accesses per character cell?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/apCEDM2sWv_M354-@MAC.fritz.box?part=6

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 2/9]: Glyph size: Use GLYPH_SZ/HW rather than hardcoded 2, 1
  2026-08-27 18:45 ` [Patch 2/9]: Glyph size: Use GLYPH_SZ/HW rather than hardcoded 2, 1 Alan Mackenzie
@ 2026-08-27 19:22   ` sashiko-bot
  0 siblings, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-08-27 19:22 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: dri-devel, linux-serial

> 
> vt: 32b glyph: 2. Use GLYPH_SZ, GLYPH_HW rather than hardcoded 2, 1.
> 
> These #defines enable the console to be built for either 16-bit or
> 32-bit glyphs.
> 
> Signed-off-by: Alan Mackenzie <acm@muc.de>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/apCEDM2sWv_M354-@MAC.fritz.box?part=2


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 9/9]: vt: Misc changes, e.g. to #include directives
  2026-08-27 18:58 ` [Patch 9/9]: vt: Misc changes, e.g. to #include directives Alan Mackenzie
@ 2026-08-27 19:25   ` sashiko-bot
  2026-08-29 12:06   ` Greg Kroah-Hartman
  1 sibling, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-08-27 19:25 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: dri-devel, linux-serial

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] A Time-of-Check to Time-of-Use (TOCTOU) race condition in `con_font_get()` can lead to a kernel heap buffer overflow.
- [Critical] Pre-calculating the allocation size `c` based on the per-console `vc->vc_font.charcount` causes a heap buffer overflow in `vgacon_font_get()` due to a per-console versus global state mismatch.
--

commit 501185cf94f089f684db150e409e50e6d10bc832
Author: Alan Mackenzie <acm@muc.de>

vt: Misc changes, e.g. to #include directives

Rearrange some #include directives, add some clarifying comments, and some
code changes which didn't fit elsewhere.

> diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
> index baf7c98429e9c..0c389a5643576 100644
> --- a/drivers/tty/vt/vt.c
> +++ b/drivers/tty/vt/vt.c
[ ... ]
> @@ -4967,14 +4968,16 @@ static int con_font_get(struct vc_data *vc, struct console_font_op *op)
>  {
>  	struct console_font font;
>  	int c;
> -	unsigned int vpitch = op->op == KD_FONT_OP_GET_TALL ? op->height : 32;
> +	unsigned int vpitch =
> +		op->op == KD_FONT_OP_GET_TALL ? vc->vc_font.height : 32;
>  
>  	if (vpitch > max_font_height)
>  		return -EINVAL;
>  
>  	void *font_data __free(kvfree) = NULL;
> +	c = DIV_ROUND_UP(vc->vc_font.width, 8) * vpitch * vc->vc_font.charcount;
>  	if (op->data) {
> -		font.data = font_data = kvzalloc(max_font_size, GFP_KERNEL);
> +		font.data = font_data = kvzalloc(c, GFP_KERNEL);

[Severity: Critical]
Does this introduce a race condition regarding the allocation size?

Since the buffer size c is now calculated using vc->vc_font.width and
vc->vc_font.charcount before acquiring the console_lock, couldn't another
thread concurrently call con_font_set(), update the console font dimensions
to be larger, and cause the newly sized font data to be written into this
undersized heap allocation later when vc_sw->con_font_get() is called inside
the lock?

Additionally, can this cause an issue with the vgacon driver due to a mismatch
between per-console and global state?

The vgacon driver tracks whether a 512-character font is in use globally
via vga_512_chars. However, vc->vc_font.charcount is maintained per-console.

If a 512-character font is set on one terminal (setting vga_512_chars to
true), but con_font_get() is called on a different terminal that still
has a 256-character font, this calculation will size the allocation for 256
characters.

The vgacon_font_get() callback will then observe the global flag and
force a read of 512 characters, writing out of bounds into the smaller
buffer during vgacon_do_font_op().

>  		if (!font.data)
>  			return -ENOMEM;
>  	} else
> @@ -4991,8 +4994,6 @@ static int con_font_get(struct vc_data *vc, struct console_font_op *op)
>  			return ret;
>  	}
>  
> -	c = DIV_ROUND_UP(font.width, 8) * vpitch * font.charcount;
> -
>  	if (op->data && font.charcount > op->charcount)
>  		return -ENOSPC;
>  	if (font.width > op->width || font.height > op->height)

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/apCEDM2sWv_M354-@MAC.fritz.box?part=9

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 8/9]: vt: Enhancements to the VT ioctl interface
  2026-08-27 18:56 ` [Patch 8/9]: vt: Enhancements to the VT ioctl interface Alan Mackenzie
@ 2026-08-27 19:25   ` sashiko-bot
  0 siblings, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-08-27 19:25 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: dri-devel, linux-serial

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Downgrading user-sized allocations from kvmalloc-backed routines to kmalloc_array breaks legitimate large requests and allows an unprivileged user to exhaust memory or flood the kernel log.
- [High] Missing compat ioctl handlers for the newly introduced `PIO_UNIMAP21` and `GIO_UNIMAP21` commands breaks UAPI for 32-bit applications.
- [High] Uninitialized kernel stack memory leaked to userspace when CONFIG_CONSOLE_TRANSLATIONS is disabled.
- [Medium] Required buffer size is silently truncated in the legacy 16-bit GET ioctl if the dictionary exceeds 65535 entries.
- [Low] Incompatible pointer types passed to fallback dummy functions break the build when CONFIG_CONSOLE_TRANSLATIONS is disabled.
--

commit 50062db5106d84e65d87483ac16b8d41dbdd6175
Author: Alan Mackenzie <acm@muc.de>

    vt: Enhancements to the VT ioctl interface
    
    Handle the current 16-bit P/GIO_UNIMAP ioctls by converting
    to/from 32-bit code-points/glyph numbers.  Add handling for new
    32-bit ioctls P/GIO_UNIMAP21.  Convert to and from __user in
    vt_ioctl.c rather than in vt.c or consolemap.c.

> diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c
> index 28993a3d0acb6..05986430e1bb3 100644
> --- a/drivers/tty/vt/vt_ioctl.c
> +++ b/drivers/tty/vt/vt_ioctl.c

[ ... ]

> @@ -484,10 +484,155 @@ static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd,
>  	return 0;
>  }
>  
> +#ifdef CONFIG_FB_GLYPH_21BIT
> +static int vt__user_unipair_8_to_21(unsigned int ct,
> +				    struct unipair __user *entries,
> +				    struct unipair21 **tmp21_entries)
> +{
> +	struct unipair *tmp_entries;
> +	int i, ret = 0;
> +
> +	tmp_entries = kmalloc_array(ct, sizeof(struct unipair), GFP_KERNEL);
> +	if (!tmp_entries)
> +		return -ENOMEM;
> +	if (copy_from_user(tmp_entries, entries, ct * sizeof(struct unipair))) {
> +		ret = -EFAULT;
> +		goto free_tmp;
> +	}
> +	*tmp21_entries = kmalloc_array(ct, sizeof(struct unipair21), GFP_KERNEL);

[Severity: High]
Could these unbounded allocations fail or cause page allocator warnings? A full
legacy map (65535 entries) requires an order-7 allocation for `tmp21_entries`
(524KB), which might spuriously fail without a vmalloc fallback. Additionally,
does this allow an unprivileged user to exhaust memory or flood the kernel log
by providing large `ct` values without `__GFP_NOWARN`?

> +	if (!*tmp21_entries) {
> +		ret = -ENOMEM;
> +		goto free_tmp;
> +	}

[ ... ]

> +static inline int do_unimap_ioctl(int cmd, struct unimapdesc __user *user_ud,
> +		bool perm, struct vc_data *vc)
> +{
> +	struct unimapdesc tmp;
> +	struct unipair21 *tmp21_entries = NULL;
> +	int ret = 0, ret1 = 0;
> +	unsigned int ct;
> +
> +	if (copy_from_user(&tmp, user_ud, sizeof(tmp)))
> +		return -EFAULT;
> +	switch (cmd) {

[ ... ]

> +	case GIO_UNIMAP:
> +		if (!perm && fg_console != vc->vc_num)
> +			return -EPERM;
> +		tmp21_entries = kmalloc_array(tmp.entry_ct,
> +					      sizeof(struct unipair21),
> +					      GFP_KERNEL);
> +		if (!tmp21_entries)
> +			return -ENOMEM;
> +		ret = con_get_unimap(vc, tmp.entry_ct, &ct, tmp21_entries);
> +		if (ret)
> +			goto free_tmp21;
> +		ret = vt_unipair_21_to__user_8(ct, tmp21_entries, tmp.entries);
> +free_tmp21:	ret1 = put_user(ct, &user_ud->entry_ct);

[Severity: High]
If CONFIG_CONSOLE_TRANSLATIONS is disabled, the dummy `con_get_unimap` inline
returns `-EINVAL` without initializing `ct`. Does this result in uninitialized
kernel stack memory leaking to userspace when `ct` is copied by `put_user()`
on the error path?

[Severity: Medium]
Also, could this silently truncate the required buffer size if the dictionary
exceeds 65535 entries? The 32-bit accurate size `ct` is written to a 16-bit
`user_ud->entry_ct`, causing legacy applications reading the map via GIO_UNIMAP
to receive an incorrect, truncated size and fail to allocate a sufficient buffer.

> +		kfree(tmp21_entries);
> +		return ret ? ret : ret1;
> +	}
> +	return 0;
> +}
> +
> +static inline int do_unimap_ioctl21(int cmd, struct unimapdesc21 __user *user_ud,
> +		bool perm, struct vc_data *vc)
> +{
> +	struct unimapdesc21 tmp;
> +	struct unipair21 *k_entries;
> +	int ret = 0, ret1 = 0;
> +
> +	if (copy_from_user(&tmp, user_ud, sizeof(tmp)))
> +		return -EFAULT;
> +	switch (cmd) {
> +	case PIO_UNIMAP21:
> +		if (!perm)
> +			return -EPERM;
> +		k_entries = kmalloc_array(tmp.entry_ct,
> +					  sizeof(struct unipair21),
> +					  GFP_KERNEL);

[Severity: High]
Does this allow unbounded allocations similar to `vt__user_unipair_8_to_21()`?
Passing an unbounded user-provided size `tmp.entry_ct` to `kmalloc_array`
without `__GFP_NOWARN` could allow an unprivileged user to trigger page
allocator warnings and exhaust memory. Furthermore, `con_set_unimap()`
propagates this count to `dict->count`, leading to additional unbounded
`kmalloc` allocations in `set_inverse_transl()`.

[ ... ]

> @@ -1026,16 +1203,20 @@ compat_kdfontop_ioctl(struct compat_console_font_op __user *fontop,
>  }
>  
>  struct compat_unimapdesc {
> -	unsigned short entry_ct;
> +	u16 entry_ct;
>  	compat_caddr_t entries;
>  };
>  
> +#ifdef CONFIG_FB_GLYPH_21BIT
>  static inline int
>  compat_unimap_ioctl(unsigned int cmd, struct compat_unimapdesc __user *user_ud,
>  			 int perm, struct vc_data *vc)
>  {

[Severity: High]
Are we missing compat ioctl handlers for `PIO_UNIMAP21` and `GIO_UNIMAP21`?
`struct unimapdesc21` contains a pointer, so its layout differs between 32-bit
and 64-bit architectures (8 bytes vs 16 bytes). Without explicit handling in
`vt_compat_ioctl()`, 32-bit processes on 64-bit kernels will fail to invoke
these new ioctls and receive -ENOIOCTLCMD or -ENOTTY.

[ ... ]

> diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
> index e017588979194..af99aeefe177f 100644
> --- a/include/linux/vt_kern.h
> +++ b/include/linux/vt_kern.h
> @@ -96,13 +96,13 @@ static inline int con_clear_unimap(struct vc_data *vc)
>  	return 0;
>  }
>  static inline
> -int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
> +int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair *list)

[Severity: Low]
Will this break the build when CONFIG_CONSOLE_TRANSLATIONS is disabled?
The fallback dummy function for `con_set_unimap` expects a `struct unipair *`,
but the callers in `vt_ioctl.c` under `CONFIG_FB_GLYPH_21BIT` (like
`do_unimap_ioctl21`) pass `struct unipair21 *`, which violates strict type
checking.

>  {
>  	return 0;
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/apCEDM2sWv_M354-@MAC.fritz.box?part=8

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 1/9]: Make consolemap.c handle Unicode planes outside BMP
  2026-08-27 18:42 ` [Patch 1/9]: Make consolemap.c handle Unicode planes outside BMP Alan Mackenzie
  2026-08-27 19:14   ` sashiko-bot
@ 2026-08-28  4:57   ` Jiri Slaby
  2026-08-29 11:51     ` Alan Mackenzie
  1 sibling, 1 reply; 32+ messages in thread
From: Jiri Slaby @ 2026-08-28  4:57 UTC (permalink / raw)
  To: Alan Mackenzie, Greg Kroah-Hartman, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, Thomas Zimmermann, linux-fbdev,
	dri-devel

On 27. 08. 26, 20:42, Alan Mackenzie wrote:
> vt: 32b glyph: 1. Make consolemap.c handle Unicode planes outside BMP
> 
> For this, add a fourth layer "planes" onto the sparse map in
> this file.  Also handle glyph numbers up to 2^21 rather than
> 256/512.  Move the conversion between __user and kernel data
> from consolemap.c to vt_ioctl.c.
> 
> Signed-off-by: Alan Mackenzie <acm@muc.de>
> 
> diff --git a/drivers/tty/vt/consolemap.c b/drivers/tty/vt/consolemap.c
> index 3fa89a2dbeba..2f695d97f0eb 100644
> --- a/drivers/tty/vt/consolemap.c
> +++ b/drivers/tty/vt/consolemap.c
> @@ -34,11 +34,11 @@
>   #include <linux/tty.h>
>   #include <linux/uaccess.h>
>   #include <linux/console.h>
> -#include <linux/consolemap.h>
>   #include <linux/vt_kern.h>
> +#include <linux/consolemap.h>

Why do you do this?

>   #include <linux/string.h>
>   
> -static unsigned short translations[][E_TABSZ] = {
> +static u1632 translations[][E_TABSZ] = {

This breaks the build at this point, right?

u1632 is a bad name though.

 > For the avoidance of doubt, no LLM has been used in the preparation 
of this change.

Given the above, perhaps you should have?

See also what Sashiko thinks:
https://sashiko.dev/#/patchset/apCEDM2sWv_M354-%40MAC.fritz.box

thanks,
-- 
js
suse labs

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: vt: Enlarge the framebuffer glyph size from 16 to 32 bits
  2026-08-27 18:38 vt: Enlarge the framebuffer glyph size from 16 to 32 bits Alan Mackenzie
                   ` (8 preceding siblings ...)
  2026-08-27 18:58 ` [Patch 9/9]: vt: Misc changes, e.g. to #include directives Alan Mackenzie
@ 2026-08-28  6:12 ` Thomas Zimmermann
  2026-08-28 14:36   ` Alan Mackenzie
  9 siblings, 1 reply; 32+ messages in thread
From: Thomas Zimmermann @ 2026-08-28  6:12 UTC (permalink / raw)
  To: Alan Mackenzie, Greg Kroah-Hartman, Jiri Slaby, Simona Vetter,
	linux-serial, linux-kernel, Helge Deller, linux-fbdev, dri-devel

Hi

Am 27.08.26 um 20:38 schrieb Alan Mackenzie:
> vt: Enlarge the framebuffer glyph size from 16 to 32 bits.
>
> This allows:
>    o - More than 256/512 distinct half-width glyphs (in fact, up to 2^21).
>    o - Unicode code points >0xffff (outside the Basic Multilingual Plane).
>    o - Coloured glyphs (as before).

But what is the use case here? We have excellent support for text and 
font rendering in graphical userspace for I18N and L10N.

In contrast, the kernel's text-mode support is limited and a hotbed for 
bugs. IMHO we shouldn't add features, but try to simplify that code base.

Best regards
Thomas

>
> The patch doesn't improve the handling of full-width glyphs.
>
> To maximise backward compatibility:
>    o - The enlarged glyph size must be positively configured in the kernel by
>      enabling CONFIG_FB_GLYPH_21BIT.  Without this, the glyph size remains 16
>      bits as before.
>    o - Existing console fonts can still be loaded and used.
>    o - The console keyboard handling remains unchanged.
>
> The format of PSFU font files remains unchanged, and the utility programs
> txt2psf and psf2txt (from package psftools) work unchanged on the new fonts.
>
> An enhanced protocol is needed for the up-/downloading of fonts, and thus
> enhanced versions of the utility programs setfont and showconsolefont.
>
> The new kernel requires two new ioctl codes, which have been provisionally
> given these values in include/uapi/linux/kd.h:
>    o - #define GIO_UNIMAP21 0x4B6E
>    o - #define PIO_UNIMAP21 0x4B6F.
> These are like GIO_UNIMAP and PIO_UNIMAP but work with 21-bit code point and
> glyph numbers.  They are used to get and put the new font format, and are
> needed by setfont and showconsolefont.
>
> Some pertinent code has not yet been amended for the new feature.  In
> particular, the files fbcon_cw.c, fbcon_ud.c, fbcon_ccw.c (which display
> rotated output on the screen) still need amendment.
>
> For the time being, CONFIG_A11Y_BRAILLE_CONSOLE and CONFIG_SPEAKUP have been
> disabled when CONFIG_FB_GLYPH_21BIT is set, until it becomes clear how to
> integrate them.
>
> The new code has been tested only on an AMD64 system.  In particular, it
> hasn't been tested on a big-endian system.
>
> For the avoidance of doubt, no LLM has been used in the preparation of this
> change.
>
> #########################################################################
>
> Building the Kernel
> -------------------
>
> Apply the patch series.
>
> The new option CONFIG_FB_GLYPH_21BIT can be found in the make menuconfig tree
> under Device Drivers/Graphics support/Direct Rendering Manager/Supported DRM
> clients/Enable legacy fbdev support .../Enable 21-bit glyphs ....  Enable
> CONFIG_DRM_FBDEV_EMULATION and CONFIG_FB_GLYPH_21BIT.
>
> [ Note: the kernel can alternatively be built with CONFIG_FB_GLYPH_21BIT
> unset, in which case it uses the well known 16-bit glyphs, essentially
> unchanged from the current kernel. ]
>
> Also enable CONFIG_HEADERS_INSTALL, so that the user side version of kd.h will
> get built in usr/include/linux/kd.h.  This is needed by the enhanced version
> of the kbd package.  After building, ensure it gets copied to where the
> compiler will find it.
>
> Build the kernel in the usual way, and install it into your boot loader.
>
> #########################################################################
>
> Testing
> -------
>
> To try out the new features, you will need a console font with glyphs outside
> the Basic Multilingual Plane, and an enhanced setfont program (which uses one
> of the new ioctl codes above) to load it with.
>
> The font latn-16-unifont-base+upper.psfu.gz attached to this post has 11,240
> glyphs, and its source code can be inspected by gunzipping it and feeding it
> through the program psf2txt from the package psftools.  This font was hacked
> together from the standard fonts lat<n>-16 and parts of GNU Unifont.  Examples
> of code points outside the Basic Multilingual Plane are U+1fbf0 to U+1fbf9,
> variants of the decimal digits.
>
> To enter a Unicode code point on the keyboard, the partial keymap
> /usr/share/keymaps/i386/include/keypad.map.gz is useful.  With it, one can
> hold down the AltGr key and type in the codepoint in hex using 16 of the
> keypad keys.
>
> An enhanced version of setfont, built for AMD64, is also attached.  The source
> code for this, in the form of a unified diff from the commit with tag v2.9.0
> in the repository
> git://git.kernel.org/pub/scm/linux/kernel/git/legion/kbd.git, is at the end of
> this post.  Before building this version of kbd, ensure that the kernel has
> been build with CONFIG_HEADERS_INSTALL set, and that the newly generated
> usr/include/linux/kd.h has been copied to somewhere the compiler will find it.
>
> #########################################################################
>
> Kernel Patches
> --------------
>
> The series of patches applies cleanly to:
>
> commit 8b0b29fdcb47907ae0296b8fe829e918e05e300f
> (HEAD -> tty-testing, origin/tty-testing, origin/tty-next)
> Author: Karl Mehltretter <kmehltretter@gmail.com>
> Date:   Fri Jul 31 20:18:44 2026 +0200rts[] lifetimert in imx_uart_ports[]ntryt)gnamic")
>
> in branch origin/testing of repository
> git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git.
>
> This series of patches is essentially a single atomic patch; no partial
> application of them will result in a useful kernel.  The 3,500 line patch from
> git diff has been divided according to theme, although this is approximate
> since not all larger diff hunks have been split into "components".
>
> These files get changed by the patches:
> drivers/accessibility/Kconfig
> drivers/accessibility/speakup/Kconfig
> drivers/gpu/drm/clients/Kconfig
>
> drivers/tty/vt/consolemap.c
> drivers/tty/vt/keyboard.c
> drivers/tty/vt/selection.c
> drivers/tty/vt/vc_screen.c
> drivers/tty/vt/vt.c
> drivers/tty/vt/vt_ioctl.c
>
> drivers/video/fbdev/core/bitblit.c
> drivers/video/fbdev/core/fbcon.c
> drivers/video/fbdev/core/fbcon.h
>
> include/linux/console_struct.h
> include/linux/consolemap.h
> include/linux/selection.h
> include/linux/vt_buffer.h
> include/linux/vt_kern.h
> include/uapi/linux/kd.h
>
> The patches are as follows:
> (i) 1 "Make consolemap.c handle Unicode planes outside BMP" contains the
>    enhancements to drivers/tty/vt/consolemap.c to handle 21 bit codepoints
>    (outside the BMP).
> (ii) 2 "Glyph size: Use GLYPH_SZ/HW rather than hardcoded 2, 1" substitutes
>    the #defines GLYPH_SZ and GLYPH_HW, the size of a glyph in bytes and half
>    words, for the previously hard coded and implicit values of 2 and 1.
> (iii) 3 "Replace scr_readw/writew by scr_readg/writeg, etc" is the replacement
>    of the macros scr_readw/writew with scr_readg, scr_writeg,
>    scr_readg_plusplus, scr_writeg_plusplus (where "g" stands for "glyph").
>    Also one or two other similar macros are replaced.
> (iv) 4 "Amend internal manipulation of glyph structure" contains amendments to
>    the manipulations of the internal structure of the now 32 bit glyph from the
>    previous 16 bit glyph.
> (v) 5 "Amend three Kconfig files" has the changes to three Kconfig files.  One
>    of these introduces CONFIG_FB_GLYPH_21BIT, the other two mask accessibility
>    features of the terminal until it becomes clear how to integrate them.
> (vi) 6 "Use u32 and typedef u1632 to handle whole glyphs" introduces the
>    typedef u1632 (which is either u16 or u32 depending on
>    CONFIG_FB_GLYPH_21BIT) and contains the changes for its use, and needed
>    changes from u16 to u32.
> (vii) 7 "Handle up to 2^21 glyphs, rather than 256/512" handles the change in
>    the maximum number of glyphs from 256/512 to 0x110000.
> (viii) 8 "Enhancements to the VT ioctl interface" contains the changes to
>    drivers/tty/vt/vt_ioctl.c including the handling of GIO_UNIMAP21 and
>    PIO_UNIMAP21, and the conversion of userspace data (whether 16 or 21 bit) to
>    the new internal 21 bit format.
> (ix) 9 "Misc changes, e.g. to #include directives" Miscellaneous changes which
>    didn't fit anywhere else, including reordering some #include directives in
>    the .c files.
>
> #########################################################################
>
> Enhancements to git://git.kernel.org/pub/scm/linux/kernel/git/legion/kbd.git,
> -----------------------------------------------------------------------------
> tag v2.9.0, for setfont.
> ------------------------
>
> diff --git a/src/include/kbd/kfont.h b/src/include/kbd/kfont.h
> index 67ad917..570b5f0 100644
> --- a/src/include/kbd/kfont.h
> +++ b/src/include/kbd/kfont.h
> @@ -211,7 +211,9 @@ int kfont_get_unicodemap(struct kfont_context *ctx, int consolefd,
>   	KBD_ATTR_NONNULL(1, 3);
>   
>   int kfont_put_unicodemap(struct kfont_context *ctx, int consolefd,
> -		struct unimapinit *ui, struct unimapdesc *ud)
> +			 struct unimapinit *ui,
> +			 int bit21,
> +			 struct unimapdesc *ud, struct unimapdesc21 *ud21)
>   	KBD_ATTR_NONNULL(1);
>   
>   /* setfont.c */
> @@ -269,7 +271,8 @@ int kfont_read_psffont(struct kfont_context *ctx,
>   		unsigned char **fontbufp, unsigned int *fontszp,
>   		unsigned int *fontwidthp, unsigned int *fontheightp,
>   		unsigned int *fontlenp, unsigned int fontpos0,
> -		struct unicode_list **uclistheadsp)
> +		       struct unicode_list **uclistheadsp,
> +		       int *bit21)
>   	KBD_ATTR_NONNULL(1);
>   
>   int kfont_write_psffont(struct kfont_context *ctx,
> diff --git a/src/libkbdfile/kbdfile.c b/src/libkbdfile/kbdfile.c
> index 5b1ee6d..50b117f 100644
> --- a/src/libkbdfile/kbdfile.c
> +++ b/src/libkbdfile/kbdfile.c
> @@ -337,7 +337,8 @@ static int
>   findfile_in_dir(const char *fnam, const char *dir, const int recdepth, const char *const *suf, struct kbdfile *fp)
>   {
>   	char errbuf[200];
> -	char *ff, *fdir, *path;
> +	const char *ff;
> +	char *fdir, *path;
>   	int rc = 1, secondpass = 0;
>   	size_t dir_len;
>   
> diff --git a/src/libkfont/kdmapop.c b/src/libkfont/kdmapop.c
> index b868f7b..5be0b88 100644
> --- a/src/libkfont/kdmapop.c
> +++ b/src/libkfont/kdmapop.c
> @@ -179,7 +179,9 @@ kfont_get_unicodemap(struct kfont_context *ctx, int fd, struct unimapdesc *ud0)
>   }
>   
>   int
> -kfont_put_unicodemap(struct kfont_context *ctx, int fd, struct unimapinit *ui, struct unimapdesc *ud)
> +kfont_put_unicodemap(struct kfont_context *ctx, int fd, struct unimapinit *ui,
> +		     int bit21,
> +		     struct unimapdesc *ud, struct unimapdesc21 *ud21)
>   {
>   	struct unimapinit advice;
>   
> @@ -202,10 +204,26 @@ again:
>   			KFONT_ERR(ctx, "ioctl(PIO_UNIMAPCLR): %m");
>   		return -1;
>   	}
> -	if (ud == NULL)
> +	if ((bit21 && !ud21) ||
> +	    (!bit21 && !ud))
>   		return 0;
>   
> -	if (ioctl(fd, PIO_UNIMAP, ud)) {
> +	if (bit21) {
> +		if (ioctl(fd, PIO_UNIMAP21, ud21)) {
> +			if (errno == ENOMEM && advice.advised_hashlevel < 100) {
> +				advice.advised_hashlevel++;
> +				goto again;
> +			}
> +#ifdef ENOIOCTLCMD
> +			if (errno == ENOIOCTLCMD)
> +				errno = EINVAL; /* 21-bit Unicode table
> +						 * invalid for older kernel */
> +#endif
> +			KFONT_ERR(ctx, "ioctl(PIO_UNIMAP21): %m");
> +			return -1;
> +		}
> +	}
> +	else if (ioctl(fd, PIO_UNIMAP, ud)) {
>   		if (errno == ENOMEM && advice.advised_hashlevel < 100) {
>   			advice.advised_hashlevel++;
>   			goto again;
> diff --git a/src/libkfont/loadunimap.c b/src/libkfont/loadunimap.c
> index e2cfa05..06015c5 100644
> --- a/src/libkfont/loadunimap.c
> +++ b/src/libkfont/loadunimap.c
> @@ -281,7 +281,8 @@ kfont_load_unicodemap(struct kfont_context *ctx, int fd, const char *tblname)
>   	} else {
>   		descr.entry_ct = listct;
>   		descr.entries  = list;
> -		if ((ret = kfont_put_unicodemap(ctx, fd, NULL, &descr)) < 0)
> +		if ((ret = kfont_put_unicodemap(ctx, fd, NULL, 0, &descr,
> +						NULL)) < 0)
>   			goto err;
>   		listct = 0;
>   	}
> diff --git a/src/libkfont/psffontop.c b/src/libkfont/psffontop.c
> index 3f94028..179a271 100644
> --- a/src/libkfont/psffontop.c
> +++ b/src/libkfont/psffontop.c
> @@ -147,7 +147,7 @@ read_fontfile(struct kfont_context *ctx, FILE *fontf, unsigned char **inputbuf,
>   {
>   	unsigned char *buf = NULL;
>   	unsigned int buflth = 0;
> -	unsigned int chunksz = MAXFONTSIZE / 4; /* random */
> +	unsigned int chunksz = MAXFONTSIZE / 4; /* 1 MB; random */
>   	int ret = 0;
>   	size_t n = 0;
>   
> @@ -174,12 +174,6 @@ read_fontfile(struct kfont_context *ctx, FILE *fontf, unsigned char **inputbuf,
>   			ret = -EX_DATAERR;
>   			goto end;
>   		}
> -
> -		if (n > MAXFONTSIZE) {
> -			KFONT_ERR(ctx, _("Font is too big"));
> -			ret = -EX_DATAERR;
> -			goto end;
> -		}
>   	}
>   
>   	*inputbuf = buf;
> @@ -197,12 +191,14 @@ kfont_read_psffont(struct kfont_context *ctx,
>   		unsigned char **fontbufp, unsigned int *fontszp,
>   		unsigned int *fontwidthp, unsigned int *fontheightp,
>   		unsigned int *fontlenp, unsigned int fontpos0,
> -		struct unicode_list **uclistheadsp)
> +		   struct unicode_list **uclistheadsp,
> +		   int *bit21)
>   {
>   	unsigned char *inputbuf;
>   	unsigned int inputlth;
>   	int ret;
>   
> +	*bit21 = 0;
>   	/*
>   	 * We used to look at the length of the input file
>   	 * with stat(); now that we accept compressed files,
> @@ -225,7 +221,8 @@ kfont_read_psffont(struct kfont_context *ctx,
>   		inputlth = *allszp;
>   	}
>   
> -	unsigned int fontlen, fontwidth, fontheight, charsize, hastable, ftoffset;
> +	unsigned int fontlen, fontwidth, fontheight, charsize, hastable,
> +		ftoffset;
>   	int utf8;
>   
>   	if (inputlth >= sizeof(struct psf1_header) && PSF1_MAGIC_OK(inputbuf)) {
> @@ -317,11 +314,29 @@ kfont_read_psffont(struct kfont_context *ctx,
>   		inptr  = inputbuf + ftoffset + fontlen * charsize;
>   		endptr = inputbuf + inputlth;
>   
> +		if (fontlen > 0xffff)
> +			*bit21 = 1;
>   		for (i = 0; i < fontlen; i++) {
>   			ret = get_uni_entry(ctx, &inptr, &endptr,
>   					&(*uclistheadsp)[fontpos0 + i], utf8);
>   			if (ret < 0)
>   				return ret;
> +			{
> +				struct unicode_list *list = &(*uclistheadsp)[fontpos0 + i];
> +				struct unicode_seq *seq;
> +
> +				while (list) {
> +					seq = list->seq;
> +					while (seq) {
> +						if (seq->uc > 0xffff)
> +							*bit21 = 1;
> +						seq = seq->next;
> +					}
> +					list = list->prev;
> +					if (list == &(*uclistheadsp)[fontpos0 + i])
> +						break;
> +				}
> +			}
>   		}
>   		if (inptr != endptr) {
>   			KFONT_ERR(ctx, _("Input file: trailing garbage"));
> diff --git a/src/libkfont/setfont.c b/src/libkfont/setfont.c
> index 45652c0..f6f0560 100644
> --- a/src/libkfont/setfont.c
> +++ b/src/libkfont/setfont.c
> @@ -185,21 +185,25 @@ err:
>   static int
>   do_loadfont(struct kfont_context *ctx, int fd, const unsigned char *inbuf,
>   		unsigned int width, unsigned int height, unsigned int hwunit,
> -		unsigned int fontsize, const char *filename)
> +	    unsigned int fontsize, const char *filename)
>   {
>   	if (height <= 32 && width <= 32)
>   		/* This can work with pre-6.2 kernels and its size and vpitch limitations */
> -		return try_loadfont(ctx, fd, inbuf, width, height, 32, hwunit, fontsize, filename);
> +		return try_loadfont(ctx, fd, inbuf, width, height, 32, hwunit,
> +				    fontsize, filename);
>   	else
> -		return try_loadfont(ctx, fd, inbuf, width, height, height, hwunit, fontsize, filename);
> +		return try_loadfont(ctx, fd, inbuf, width, height, height,
> +				    hwunit, fontsize, filename);
>   }
>   
>   static int
>   do_loadtable(struct kfont_context *ctx, int fd, struct unicode_list *uclistheads,
> -		unsigned int fontsize)
> +	     unsigned int fontsize, int bit21)
>   {
>   	struct unimapdesc ud;
>   	struct unipair *up = NULL;
> +	struct unimapdesc21 ud21;
> +	struct unipair21 *up21 = NULL;
>   	unsigned int i, ct = 0, maxct;
>   	struct unicode_list *ul;
>   	struct unicode_seq *us;
> @@ -215,11 +219,19 @@ do_loadtable(struct kfont_context *ctx, int fd, struct unicode_list *uclistheads
>   			ul = ul->next;
>   		}
>   	}
> -
> -	up = malloc(maxct * sizeof(*up));
> -	if (!up) {
> -		KFONT_ERR(ctx, "malloc: %m");
> -		return -EX_OSERR;
> +	if (bit21) {
> +		up21 = malloc(maxct * sizeof(*up21));
> +		if (!up21) {
> +			KFONT_ERR(ctx, "malloc: %m");
> +			return -EX_OSERR;
> +		}
> +	}
> +	else {
> +		up = malloc(maxct * sizeof(*up));
> +		if (!up) {
> +			KFONT_ERR(ctx, "malloc: %m");
> +			return -EX_OSERR;
> +		}
>   	}
>   
>   	for (i = 0; i < fontsize; i++) {
> @@ -229,8 +241,14 @@ do_loadtable(struct kfont_context *ctx, int fd, struct unicode_list *uclistheads
>   		while (ul) {
>   			us = ul->seq;
>   			if (us && !us->next) {
> -				up[ct].unicode = (unsigned short) us->uc;
> -				up[ct].fontpos = (unsigned short) i;
> +				if (bit21) {
> +					up21[ct].unicode = (unsigned) us->uc;
> +					up21[ct].fontpos = (unsigned) i;
> +				}
> +				else {
> +					up[ct].unicode = (unsigned short) us->uc;
> +					up[ct].fontpos = (unsigned short) i;
> +				}
>   				ct++;
>   				if (ctx->verbose > 1)
>   					printf(" %04x", us->uc);
> @@ -250,7 +268,7 @@ do_loadtable(struct kfont_context *ctx, int fd, struct unicode_list *uclistheads
>   			printf("\n");
>   	}
>   
> -	if (ct > USHRT_MAX || ct != maxct) {
> +	if (ct != maxct) {
>   		KFONT_ERR(ctx, _("bug in do_loadtable"));
>   		ret = -EX_SOFTWARE;
>   		goto err;
> @@ -258,17 +276,26 @@ do_loadtable(struct kfont_context *ctx, int fd, struct unicode_list *uclistheads
>   
>   	KFONT_INFO(ctx, _("Loading Unicode mapping table..."));
>   
> -	ud.entry_ct = (unsigned short) ct;
> -	ud.entries  = up;
> +	if (bit21) {
> +		ud21.entry_ct = ct;
> +		ud21.entries = up21;
> +	}
> +	else {
> +		ud.entry_ct = (unsigned short) ct;
> +		ud.entries  = up;
> +	}
>   
> -	if (kfont_put_unicodemap(ctx, fd, NULL, &ud) < 0) {
> +	if (kfont_put_unicodemap(ctx, fd, NULL, bit21, &ud, &ud21) < 0) {
>   		ret = -EX_OSERR;
>   		goto err;
>   	}
>   
>   	ret = 0;
>   err:
> -	free(up);
> +	if (bit21)
> +		free(up21);
> +	else
> +		free(up);
>   	return ret;
>   }
>   
> @@ -286,6 +313,7 @@ kfont_load_fonts(struct kfont_context *ctx,
>   	struct kbdfile *fp = NULL;
>   	int i;
>   	int ret = 0;
> +	int bit21 = 0;
>   
>   	if (ifilct == 1)
>   		return kfont_load_font(ctx, fd, ifiles[0], iunit, hwunit, no_m, no_u);
> @@ -321,12 +349,18 @@ kfont_load_fonts(struct kfont_context *ctx,
>   
>   		if (kfont_read_psffont(ctx, kbdfile_get_file(fp), &inbuf,
>   			&inputlth, &fontbuf, &fontbuflth, &width, &height, &fontsize,
> -			bigfontsize, no_u ? NULL : &uclistheads)) {
> +				       bigfontsize, no_u ? NULL : &uclistheads,
> +				       &bit21)) {
>   			KFONT_ERR(ctx, _("When loading several fonts, all must be psf fonts - %s isn't"),
>   			    kbdfile_get_pathname(fp));
>   			ret = -EX_DATAERR;
>   			goto end;
>   		}
> +		if (bit21) {
> +			KFONT_ERR(ctx, _("Full Unicode fonts aren't (yet) handled by this program."));
> +			ret = -EX_DATAERR;
> +			goto end;
> +		}
>   
>   		if (!height) {
>   			unsigned int bytewidth;
> @@ -372,10 +406,10 @@ kfont_load_fonts(struct kfont_context *ctx,
>   	}
>   
>   	ret = do_loadfont(ctx, fd, bigfontbuf, bigwidth, bigheight, hwunit,
> -		bigfontsize, NULL);
> +			  bigfontsize, NULL);
>   
>   	if (!ret && uclistheads && !no_u)
> -		ret = do_loadtable(ctx, fd, uclistheads, bigfontsize);
> +		ret = do_loadtable(ctx, fd, uclistheads, bigfontsize, 0);
>   
>   end:
>   	free(bigfontbuf);
> @@ -398,6 +432,7 @@ kfont_load_font(struct kfont_context *ctx, int fd, const char *ifil,
>   	unsigned char *inbuf, *fontbuf;
>   	unsigned int inputlth, fontbuflth, fontsize, offset;
>   	struct unicode_list *uclistheads;
> +	int bit21 = 0;
>   	int ret;
>   
>   	if (!(fp = kbdfile_new(NULL))) {
> @@ -447,10 +482,13 @@ kfont_load_font(struct kfont_context *ctx, int fd, const char *ifil,
>   	height = 0;
>   	uclistheads = NULL;
>   
> -	if (!kfont_read_psffont(ctx, kbdfile_get_file(fp), &inbuf, &inputlth,
> -		&fontbuf, &fontbuflth, &width, &height, &fontsize, 0,
> -		no_u ? NULL : &uclistheads)) {
> -
> +	ret = kfont_read_psffont(ctx, kbdfile_get_file(fp), &inbuf, &inputlth,
> +				 &fontbuf, &fontbuflth, &width, &height, &fontsize, 0,
> +				 no_u ? NULL : &uclistheads,
> +				 &bit21) ;
> +	if (ret)
> +		KFONT_ERR(ctx, "Non-zero RET from kfont_read_psffont: %d, %m\n", ret);
> +	else {
>   		/* we've got a psf font */
>   		if (!height) {
>   			unsigned int bytewidth;
> @@ -459,12 +497,13 @@ kfont_load_font(struct kfont_context *ctx, int fd, const char *ifil,
>   		}
>   
>   		ret = do_loadfont(ctx, fd, fontbuf, width, height, hwunit,
> -			fontsize, kbdfile_get_pathname(fp));
> +				  fontsize, kbdfile_get_pathname(fp));
>   		if (ret < 0)
>   			goto end;
>   
>   		if (uclistheads && !no_u) {
> -			ret = do_loadtable(ctx, fd, uclistheads, fontsize);
> +			ret = do_loadtable(ctx, fd, uclistheads, fontsize,
> +					   bit21);
>   			if (ret < 0)
>   				goto end;
>   		}
> diff --git a/src/psfxtable.c b/src/psfxtable.c
> index 9886ad4..2a8fdf4 100644
> --- a/src/psfxtable.c
> +++ b/src/psfxtable.c
> @@ -45,6 +45,7 @@ int main(int argc, char **argv)
>   	unsigned char *inbuf, *fontbuf;
>   	unsigned int inbuflth, fontbuflth;
>   	struct unicode_list *uclistheads = NULL;
> +	int bit21 = 0;
>   
>   	setuplocale();
>   
> @@ -143,9 +144,12 @@ int main(int argc, char **argv)
>   		kbd_error(EX_CANTCREAT, 0, _("Unable to open file: %s: %m"), otname);
>   
>   	if (kfont_read_psffont(kfont, ifil, &inbuf, &inbuflth, &fontbuf,
> -				&fontbuflth, &width, &height, &fontlen, 0,
> -				itab ? NULL : &uclistheads) < 0)
> +			       &fontbuflth, &width, &height, &fontlen, 0,
> +			       itab ? NULL : &uclistheads,
> +			       &bit21) < 0)
>   		kbd_error(EX_DATAERR, 0, _("Bad magic number on %s"), ifname);
> +	if (bit21)
> +		kbd_error(EX_DATAERR, 0, _("Full Unicode fonts aren't (yet) handled by this program."));
>   
>   	fclose(ifil);
>   
> diff --git a/src/readpsfheader.c b/src/readpsfheader.c
> index 2d87e59..7c1b406 100644
> --- a/src/readpsfheader.c
> +++ b/src/readpsfheader.c
> @@ -29,6 +29,7 @@ int main(int argc, char **argv)
>   	char *inbuf, *fontbuf;
>   	int inbuflth, fontbuflth;
>   	struct unicode_list *uclistheads = NULL;
> +	int bit21 = 0;
>   
>   	const char *short_opts = "hV";
>   	const struct option long_opts[] = {
> @@ -73,8 +74,12 @@ int main(int argc, char **argv)
>   	if ((ret = kfont_init(program_invocation_short_name, &kfont)) < 0)
>   		return -ret;
>   
> -	if (kfont_read_psffont(kfont, f, &inbuf, &inbuflth, &fontbuf, &fontbuflth, &width, &fontlen, 0, &uclistheads) < 0)
> +	if (kfont_read_psffont(kfont, f, &inbuf, &inbuflth, &fontbuf,
> +			       &fontbuflth, &width, &fontlen, 0, &uclistheads,
> +			       &bit21) < 0)
>   		kbd_error(EX_DATAERR, 0, "Bad magic number");
> +	if (bit21)
> +		kbd_error(EX_DATAERR, 0, "Full Unicode fonts aren't (yet) handled by this program");
>   
>   	close(f);
>   
> diff --git a/src/showconsolefont.c b/src/showconsolefont.c
> index 9230e2b..27c04ec 100644
> --- a/src/showconsolefont.c
> +++ b/src/showconsolefont.c
> @@ -36,7 +36,8 @@ leave(struct kfont_context *ctx, int n)
>   		kbd_warning(0, _("failed to restore original translation table"));
>   		n = EXIT_FAILURE;
>   	}
> -	if (have_ounimap && kfont_put_unicodemap(ctx, fd, NULL, &ounimap)) {
> +	if (have_ounimap && kfont_put_unicodemap(ctx, fd, NULL, 0,
> +						 &ounimap , NULL)) {
>   		kbd_warning(0, _("failed to restore original unimap"));
>   		n = EXIT_FAILURE;
>   	}
> @@ -92,7 +93,7 @@ setnewunicodemap(struct kfont_context *ctx, unsigned int *list, int cnt)
>   	for (i = 0; i < cnt; i++)
>   		nunimap.entries[list[i]].unicode = (unsigned short) (BASE + i);
>   
> -	if (kfont_put_unicodemap(ctx, fd, NULL, &nunimap))
> +	if (kfont_put_unicodemap(ctx, fd, NULL, 0, &nunimap, NULL))
>   		leave(ctx, EXIT_FAILURE);
>   }
>   
> @@ -111,7 +112,7 @@ usage(int rc, const struct kbd_help *options)
>   int main(int argc, char **argv)
>   {
>   	int c, ret;
> -	unsigned int cols, rows, nr, n, i, j, k;
> +	int cols = 0, rows = 0, nr = 0, n, i, j, k;
>   	int mode;
>   	const char *space, *sep;
>   	char *console = NULL;
> @@ -176,19 +177,22 @@ int main(int argc, char **argv)
>   		kbd_warning(errno, _("Unable to read keyboard mode"));
>   		leave(kfont, EX_OSERR);
>   	}
> -	if (mode == K_UNICODE)
> -		space = "\xef\x80\xa0"; /* U+F020 (direct-to-font space) */
> -	else
> -		space = " ";
> -
> -	if (info) {
> -		nr = rows = cols = 0;
> -
> -		ret = kfont_get_font(kfont, fd, NULL, &nr, &rows, &cols, NULL);
> -		if (ret != 0)
> -			leave(kfont, EXIT_FAILURE);
> -
> -		if (kfont_get_verbosity(kfont)) {
> +	/* TEMP COMMENTED OUT STUFF, 2026-05-20.  What is this F020 for? */
> +	/* if (mode == K_UNICODE) */
> +	/* 	space = "\xef\x80\xa0"; /\* U+F020 (direct-to-font space) *\/ */
> +	/* else */
> +	/* END OF TC STUFF */
> +	space = " ";
> +
> +	ret = kfont_get_font(kfont, fd, NULL, &nr, &rows, &cols, NULL);
> +	if (ret != 0)
> +		leave(kfont, EXIT_FAILURE);
> +
> +	if (!info && nr > 512)
> +		printf(_("Too many characters to print individually\n"));
> +	if (info || nr > 512) {
> +		if (kfont_get_verbosity(kfont)
> +		    || (!info && nr > 512)) {
>   			printf(_("Character count: %u\n"), nr);
>   			printf(_("Font width     : %u\n"), rows);
>   			printf(_("Font height    : %u\n"), cols);
>
>

-- 
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
GF: Jochen Jaser, Andrew McDonald, (HRB 36809, AG Nürnberg)



^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: vt: Enlarge the framebuffer glyph size from 16 to 32 bits
  2026-08-28  6:12 ` vt: Enlarge the framebuffer glyph size from 16 to 32 bits Thomas Zimmermann
@ 2026-08-28 14:36   ` Alan Mackenzie
  2026-08-29 12:09     ` Greg Kroah-Hartman
  0 siblings, 1 reply; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-28 14:36 UTC (permalink / raw)
  To: Thomas Zimmermann
  Cc: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, linux-fbdev, dri-devel

On Fri, Aug 28, 2026 at 08:12:16 +0200, Thomas Zimmermann wrote:
> Hi

> Am 27.08.26 um 20:38 schrieb Alan Mackenzie:
> > vt: Enlarge the framebuffer glyph size from 16 to 32 bits.

> > This allows:
> >    o - More than 256/512 distinct half-width glyphs (in fact, up to 2^21).
> >    o - Unicode code points >0xffff (outside the Basic Multilingual Plane).
> >    o - Coloured glyphs (as before).

> But what is the use case here? We have excellent support for text and 
> font rendering in graphical userspace for I18N and L10N.

GUIs are sub-optimal for pure text work.  A full screen terminal is
wanted.  The current preeminent user space terminal, kmscon, appears to
lack the refinements of many decades development of the kernel's
console.

Support for blind users exists in the kernel's console.  I don't know
how good it is, as I don't (yet) need to use it.

> In contrast, the kernel's text-mode support is limited and a hotbed for 
> bugs. IMHO we shouldn't add features, but try to simplify that code base.

From a user's point of view, the kernel's console is rock solid and
dependable; it doesn't crash, and its interfaces don't suddenly change
with the latest release of KDE or Gnome or whatever.

As is clear, I think the console's limitations should be eliminated.

> Best regards
> Thomas

-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 1/9]: Make consolemap.c handle Unicode planes outside BMP
  2026-08-28  4:57   ` Jiri Slaby
@ 2026-08-29 11:51     ` Alan Mackenzie
  0 siblings, 0 replies; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-29 11:51 UTC (permalink / raw)
  To: Jiri Slaby
  Cc: Greg Kroah-Hartman, Simona Vetter, linux-serial, linux-kernel,
	Helge Deller, Thomas Zimmermann, linux-fbdev, dri-devel

Thanks for trying the patch out.

On Fri, Aug 28, 2026 at 06:57:04 +0200, Jiri Slaby wrote:
> On 27. 08. 26, 20:42, Alan Mackenzie wrote:
> > vt: 32b glyph: 1. Make consolemap.c handle Unicode planes outside BMP

> > For this, add a fourth layer "planes" onto the sparse map in
> > this file.  Also handle glyph numbers up to 2^21 rather than
> > 256/512.  Move the conversion between __user and kernel data
> > from consolemap.c to vt_ioctl.c.

> > Signed-off-by: Alan Mackenzie <acm@muc.de>

> > diff --git a/drivers/tty/vt/consolemap.c b/drivers/tty/vt/consolemap.c
> > index 3fa89a2dbeba..2f695d97f0eb 100644
> > --- a/drivers/tty/vt/consolemap.c
> > +++ b/drivers/tty/vt/consolemap.c
> > @@ -34,11 +34,11 @@
> >   #include <linux/tty.h>
> >   #include <linux/uaccess.h>
> >   #include <linux/console.h>
> > -#include <linux/consolemap.h>
> >   #include <linux/vt_kern.h>
> > +#include <linux/consolemap.h>

> Why do you do this?

That's a remnant of a struggle to put in new declarations such that the
kernel would build at all.  I've just tried restoring the original order
of the #includes for consolemap.h and vt_kern.h, and it builds equally
well.  So that change could/should be undone.

> >   #include <linux/string.h>

> > -static unsigned short translations[][E_TABSZ] = {
> > +static u1632 translations[][E_TABSZ] = {

> This breaks the build at this point, right?

I'm assuming your error message was "symbol u1632 undeclared".  I don't
understand yet what's happened here.  u1632 gets declared in
consolemap.h, so should be avaiable throughout consolemap.c.

> u1632 is a bad name though.

Possibly.  It's the best I could come up with, given it should be concise
and thus not cause a lot of refilling of the source code.  I'm not sure
what would be better instead of u1632.

> See also what Sashiko thinks:
> https://sashiko.dev/#/patchset/apCEDM2sWv_M354-%40MAC.fritz.box

Sashiko has pointed out a number of errors I'll need to fix.  I got nine
emails directly from it.

> thanks,
> -- 
> js
> suse labs

-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 9/9]: vt: Misc changes, e.g. to #include directives
  2026-08-27 18:58 ` [Patch 9/9]: vt: Misc changes, e.g. to #include directives Alan Mackenzie
  2026-08-27 19:25   ` sashiko-bot
@ 2026-08-29 12:06   ` Greg Kroah-Hartman
  1 sibling, 0 replies; 32+ messages in thread
From: Greg Kroah-Hartman @ 2026-08-29 12:06 UTC (permalink / raw)
  To: Alan Mackenzie
  Cc: Jiri Slaby, Simona Vetter, linux-serial, linux-kernel,
	Helge Deller, Thomas Zimmermann, linux-fbdev, dri-devel

On Thu, Aug 27, 2026 at 06:58:48PM +0000, Alan Mackenzie wrote:
> vt: 32b glyph: 9. Misc changes, e.g. to #include directives
> 
> Rearrange some #include directives, add some clarifying
> comments, and some code changes which didn't fit elsewhere.

But you need to list those code changes and why you are making them, and
the #include stuff should be it's own patch, right?

And why this:

> diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c
> index 13f4e48b4142..18affdb3c7c5 100644
> --- a/drivers/tty/vt/selection.c
> +++ b/drivers/tty/vt/selection.c
> @@ -48,6 +48,8 @@ static struct vc_selection {
>  	.start = -1,
>  };
>  
> +static unsigned int size_row;
> +
>  /* clear_selection, highlight and highlight_pointer can be called
>     from interrupt (via scrollback/front) */
>  
> @@ -340,6 +342,7 @@ static int vc_selection(struct vc_data *vc, struct tiocl_selection *v,
>  {
>  	int ps, pe;
>  
> +	size_row = vc->vc_size_row;
>  	poke_blanked_console();
>  
>  	if (v->sel_mode == TIOCL_SELCLEAR) {


You set a local variable that is never actually used anywhere?

This feels very very odd, especially as the "last" patch in the series.

greg k-h

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: vt: Enlarge the framebuffer glyph size from 16 to 32 bits
  2026-08-28 14:36   ` Alan Mackenzie
@ 2026-08-29 12:09     ` Greg Kroah-Hartman
  2026-08-29 14:39       ` Alan Mackenzie
  0 siblings, 1 reply; 32+ messages in thread
From: Greg Kroah-Hartman @ 2026-08-29 12:09 UTC (permalink / raw)
  To: Alan Mackenzie
  Cc: Thomas Zimmermann, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, linux-fbdev, dri-devel

On Fri, Aug 28, 2026 at 02:36:19PM +0000, Alan Mackenzie wrote:
> On Fri, Aug 28, 2026 at 08:12:16 +0200, Thomas Zimmermann wrote:
> > Hi
> 
> > Am 27.08.26 um 20:38 schrieb Alan Mackenzie:
> > > vt: Enlarge the framebuffer glyph size from 16 to 32 bits.
> 
> > > This allows:
> > >    o - More than 256/512 distinct half-width glyphs (in fact, up to 2^21).
> > >    o - Unicode code points >0xffff (outside the Basic Multilingual Plane).
> > >    o - Coloured glyphs (as before).
> 
> > But what is the use case here? We have excellent support for text and 
> > font rendering in graphical userspace for I18N and L10N.
> 
> GUIs are sub-optimal for pure text work.  A full screen terminal is
> wanted.  The current preeminent user space terminal, kmscon, appears to
> lack the refinements of many decades development of the kernel's
> console.

What is missing from kmscon that the kernel currently supports?  Why not
just make it feature-equal there, and then make these additional changes
there as well?

As others have said, we don't want to add new features to the in-kernel
console mode due to all of the reasons the patch review has shown on
this series already (i.e. complex interactions that end up making the
system broken.)  This is best all done in userspace, where a bug there
will not cause these same problems, right?

> Support for blind users exists in the kernel's console.  I don't know
> how good it is, as I don't (yet) need to use it.

But others use it, so we can't break that, and the fact that you ignored
those codepaths here makes me very worried.

> > In contrast, the kernel's text-mode support is limited and a hotbed for 
> > bugs. IMHO we shouldn't add features, but try to simplify that code base.
> 
> >From a user's point of view, the kernel's console is rock solid and
> dependable; it doesn't crash, and its interfaces don't suddenly change
> with the latest release of KDE or Gnome or whatever.

This has nothing to do with KDE or Gnome, if you have issues with those
projects, please take it up with them and contribute to them to resolve
those issues.  I know they can always use the help.

> As is clear, I think the console's limitations should be eliminated.

And the rest of us feel the kernel console's code should be eliminated
entirely, when ever possible :)

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 3/9]: Replace scr_readw/writew by scr_readg/writeg, etc
  2026-08-27 18:47 ` [Patch 3/9]: Replace scr_readw/writew by scr_readg/writeg, etc Alan Mackenzie
  2026-08-27 19:18   ` sashiko-bot
@ 2026-08-29 12:11   ` Greg Kroah-Hartman
  2026-08-29 13:33     ` Alan Mackenzie
  1 sibling, 1 reply; 32+ messages in thread
From: Greg Kroah-Hartman @ 2026-08-29 12:11 UTC (permalink / raw)
  To: Alan Mackenzie
  Cc: Jiri Slaby, Simona Vetter, linux-serial, linux-kernel,
	Helge Deller, Thomas Zimmermann, linux-fbdev, dri-devel

On Thu, Aug 27, 2026 at 06:47:05PM +0000, Alan Mackenzie wrote:
> 
> vt: 32b glyph: 3. replace scr_readw/writew by scr_readg/writeg, etc.

I don't understand these lines in your patches, where are they from and
what are they for?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [Patch 3/9]: Replace scr_readw/writew by scr_readg/writeg, etc
  2026-08-29 12:11   ` Greg Kroah-Hartman
@ 2026-08-29 13:33     ` Alan Mackenzie
  0 siblings, 0 replies; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-29 13:33 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Jiri Slaby, Simona Vetter, linux-serial, linux-kernel,
	Helge Deller, Thomas Zimmermann, linux-fbdev, dri-devel

On Sat, Aug 29, 2026 at 14:11:00 +0200, Greg Kroah-Hartman wrote:
> On Thu, Aug 27, 2026 at 06:47:05PM +0000, Alan Mackenzie wrote:

> > vt: 32b glyph: 3. replace scr_readw/writew by scr_readg/writeg, etc.

> I don't understand these lines in your patches, where are they from and
> what are they for?

scr_readg and scr_writeg read and write an entire glyph, regardless of
whether that glyph is 16-bit or 32-bit.  The latter is determined by the
setting of CONFIG_FB_GLYPH_21BIT.

This is the main complication in these patches; having to cope with both
sizes of glyph, for backward compatibility, rather than just switching
to 32-bit glyphs.

> thanks,

> greg k-h

-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: vt: Enlarge the framebuffer glyph size from 16 to 32 bits
  2026-08-29 12:09     ` Greg Kroah-Hartman
@ 2026-08-29 14:39       ` Alan Mackenzie
  2026-08-31  6:20         ` Thomas Zimmermann
  0 siblings, 1 reply; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-29 14:39 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Thomas Zimmermann, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, linux-fbdev, dri-devel

On Sat, Aug 29, 2026 at 14:09:52 +0200, Greg Kroah-Hartman wrote:
> On Fri, Aug 28, 2026 at 02:36:19PM +0000, Alan Mackenzie wrote:
> > On Fri, Aug 28, 2026 at 08:12:16 +0200, Thomas Zimmermann wrote:
> > > Hi

> > > Am 27.08.26 um 20:38 schrieb Alan Mackenzie:
> > > > vt: Enlarge the framebuffer glyph size from 16 to 32 bits.

> > > > This allows:
> > > >    o - More than 256/512 distinct half-width glyphs (in fact, up to 2^21).
> > > >    o - Unicode code points >0xffff (outside the Basic Multilingual Plane).
> > > >    o - Coloured glyphs (as before).

> > > But what is the use case here? We have excellent support for text and 
> > > font rendering in graphical userspace for I18N and L10N.

> > GUIs are sub-optimal for pure text work.  A full screen terminal is
> > wanted.  The current preeminent user space terminal, kmscon, appears to
> > lack the refinements of many decades development of the kernel's
> > console.

> What is missing from kmscon that the kernel currently supports?

<Alt>-<Fn>, for a start.  Also the facility to enter an arbitrary Unicode
code point by holding down <AltGr> and typing the hex code on the numeric
keypad.  There are probably other things, too.

> Why not just make it feature-equal there, and then make these
> additional changes there as well?

I suspect the politics involved would be more difficult than here, and
I'm not good at politics.  kmscon is controlled by Redhat, and they have
a reputation for dismissing third party suggestions and patches rather
than integrating them.

But it's not just a matter of adding features.  The kernel console is an
elegant system.  You just have to add entries in /etc/inittab, select a
keyboard, select a font (all mostly done at system installation) and
that's it.  kmscon is anything but elegant.  It has something like 75
command line options, and a configuration file is virtually mandatory.

kmscon has no documentation for its keyboard system, nothing to compare
with Andries Brouwer's keymaps man page.  It uses xkb.  I remember
searching for xkb doc around 10 years ago and found nothing systematic.
Just a very helpful tutorial, which enabled me to  make my changes.

I don't think it's got doc for its various font systems either.  It might
well be that you need to use a GUI to make font changes.  That is not a
Good Thing.

> As others have said, we don't want to add new features to the in-kernel
> console mode due to all of the reasons the patch review has shown on
> this series already (i.e. complex interactions that end up making the
> system broken.)  This is best all done in userspace, where a bug there
> will not cause these same problems, right?

From the user's point of view, a bug in either will render his computer
unusable.

Have you considered that the code in kmscon might not be any better than
that in the kernel's console?  I've no evidence either way.

> > Support for blind users exists in the kernel's console.  I don't know
> > how good it is, as I don't (yet) need to use it.

> But others use it, so we can't break that, and the fact that you ignored
> those codepaths here makes me very worried.

Hey, give me a chance!  One thing at a time.  I didn't ignore that code,
I noted (in two Kconfig's) that it would also need attention, and I
blocked it out safely when CONFIG_FB_GLYPH_21BIT was set.

It may well be (I'm not sure) that these facilities for the blind are an
integral part of the kernel's console.  That would be a good reason not
to remove it from the kernel.

> > > In contrast, the kernel's text-mode support is limited and a hotbed for 
> > > bugs. IMHO we shouldn't add features, but try to simplify that code base.

> > >From a user's point of view, the kernel's console is rock solid and
> > dependable; it doesn't crash, and its interfaces don't suddenly change
> > with the latest release of KDE or Gnome or whatever.

> This has nothing to do with KDE or Gnome, if you have issues with those
> projects, please take it up with them and contribute to them to resolve
> those issues.  I know they can always use the help.

My point is that the kernel's console is rock solid and dependable.
Things like kmscon are subject to the vagaries of fashion.

> > As is clear, I think the console's limitations should be eliminated.

> And the rest of us feel the kernel console's code should be eliminated
> entirely, when ever possible :)

The rest of you don't appear to be console users.  It's easy to advocate
for removal when you don't use it yourself.

> thanks,

> greg k-h

-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: vt: Enlarge the framebuffer glyph size from 16 to 32 bits
  2026-08-29 14:39       ` Alan Mackenzie
@ 2026-08-31  6:20         ` Thomas Zimmermann
  2026-08-31 14:45           ` Alan Mackenzie
  0 siblings, 1 reply; 32+ messages in thread
From: Thomas Zimmermann @ 2026-08-31  6:20 UTC (permalink / raw)
  To: Alan Mackenzie, Greg Kroah-Hartman
  Cc: Jiri Slaby, Simona Vetter, linux-serial, linux-kernel,
	Helge Deller, linux-fbdev, dri-devel

Hi

Am 29.08.26 um 16:39 schrieb Alan Mackenzie:
[...]
>> This has nothing to do with KDE or Gnome, if you have issues with those
>> projects, please take it up with them and contribute to them to resolve
>> those issues.  I know they can always use the help.
> My point is that the kernel's console is rock solid and dependable.

That is a bold statement.  I've spend the last years working on related 
code (fbcon) and it mostly feels dated and unmaintained. Our prime 
implementation (again fbcon) has been de-facto unmaintained for years. 
We recently had contributors who are pushing the ioctls semantics and 
found bugs in fbcon for a number of corner cases.


> Things like kmscon are subject to the vagaries of fashion.
>
>>> As is clear, I think the console's limitations should be eliminated.
>> And the rest of us feel the kernel console's code should be eliminated
>> entirely, when ever possible :)
> The rest of you don't appear to be console users.  It's easy to advocate
> for removal when you don't use it yourself.

FTR I'm not at all interested in eliminating the kernel's console. But 
there are must-have features and nice-to-have features. IMHO for the 
kernel console i18n really belongs in the later category.

Best regards
Thomas


>
>> thanks,
>> greg k-h

-- 
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
GF: Jochen Jaser, Andrew McDonald, (HRB 36809, AG Nürnberg)



^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: vt: Enlarge the framebuffer glyph size from 16 to 32 bits
  2026-08-31  6:20         ` Thomas Zimmermann
@ 2026-08-31 14:45           ` Alan Mackenzie
  2026-08-31 17:21             ` Helge Deller
  0 siblings, 1 reply; 32+ messages in thread
From: Alan Mackenzie @ 2026-08-31 14:45 UTC (permalink / raw)
  To: Thomas Zimmermann
  Cc: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, Helge Deller, linux-fbdev, dri-devel

Hello!

On Mon, Aug 31, 2026 at 08:20:10 +0200, Thomas Zimmermann wrote:
> Hi

> Am 29.08.26 um 16:39 schrieb Alan Mackenzie:

> >> This has nothing to do with KDE or Gnome, if you have issues with those
> >> projects, please take it up with them and contribute to them to resolve
> >> those issues.  I know they can always use the help.

> > My point is that the kernel's console is rock solid and dependable.

> That is a bold statement.

I've been using the console for around 28 years, now.  I don't recall it
crashing at all.  My current font and keyboard files are in exactly the
same form as they were 28 years ago.  These formats are stable and
dependable.

> I've spend the last years working on related code (fbcon) and it
> mostly feels dated and unmaintained. Our prime implementation (again
> fbcon) has been de-facto unmaintained for years.  We recently had
> contributors who are pushing the ioctls semantics and found bugs in
> fbcon for a number of corner cases.

Something like:

commit ef7656e85f1a4400999625cd398b655517368e7e
Author: Zizhi Wo <wozizhi@huawei.com>
Date:   Wed Jul 29 10:12:04 2026 +0800

    fbdev: Fix out-of-bounds access when rotating console after font resize

> > Things like kmscon are subject to the vagaries of fashion.

> >>> As is clear, I think the console's limitations should be eliminated.

> >> And the rest of us feel the kernel console's code should be eliminated
> >> entirely, when ever possible :)

> > The rest of you don't appear to be console users.  It's easy to advocate
> > for removal when you don't use it yourself.

> FTR I'm not at all interested in eliminating the kernel's console.

Other people, in particular Greg KH, have said this is the current aim.
I'm worried for myself.  It is far from clear that there is a suitable
replacement for the kernel's console, and its elimination could easily
happen without that replacement.  I suppose I could always (laboriously)
maintain the console for myself personally.  Or there's always the BSDs.
I just don't know what I'd do.

> But there are must-have features and nice-to-have features. IMHO for
> the kernel console i18n really belongs in the later category.

It's 2026, four decades after VGA was the latest thing, and Unicode
support is merely a nice-to-have?

> Best regards
> Thomas

> -- 
> --
> Thomas Zimmermann
> Graphics Driver Developer
> SUSE Software Solutions Germany GmbH
> Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
> GF: Jochen Jaser, Andrew McDonald, (HRB 36809, AG Nürnberg)

-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: vt: Enlarge the framebuffer glyph size from 16 to 32 bits
  2026-08-31 14:45           ` Alan Mackenzie
@ 2026-08-31 17:21             ` Helge Deller
  2026-09-01 11:14               ` Alan Mackenzie
  0 siblings, 1 reply; 32+ messages in thread
From: Helge Deller @ 2026-08-31 17:21 UTC (permalink / raw)
  To: Alan Mackenzie, Thomas Zimmermann
  Cc: Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, linux-serial,
	linux-kernel, linux-fbdev, dri-devel

On 8/31/26 16:45, Alan Mackenzie wrote:
> On Mon, Aug 31, 2026 at 08:20:10 +0200, Thomas Zimmermann wrote:
>> Am 29.08.26 um 16:39 schrieb Alan Mackenzie:
>>>> This has nothing to do with KDE or Gnome, if you have issues with those
>>>> projects, please take it up with them and contribute to them to resolve
>>>> those issues.  I know they can always use the help.
> 
>>> My point is that the kernel's console is rock solid and dependable.
> 
>> That is a bold statement.
> 
> I've been using the console for around 28 years, now.  I don't recall it
> crashing at all.  My current font and keyboard files are in exactly the
> same form as they were 28 years ago.  These formats are stable and
> dependable.
> 
>> I've spend the last years working on related code (fbcon) and it
>> mostly feels dated and unmaintained. Our prime implementation (again
>> fbcon) has been de-facto unmaintained for years.  We recently had
>> contributors who are pushing the ioctls semantics and found bugs in
>> fbcon for a number of corner cases.
> 
> Something like:
> 
> commit ef7656e85f1a4400999625cd398b655517368e7e
> Author: Zizhi Wo <wozizhi@huawei.com>
> Date:   Wed Jul 29 10:12:04 2026 +0800
> 
>      fbdev: Fix out-of-bounds access when rotating console after font resize
> 
>>> Things like kmscon are subject to the vagaries of fashion.
> 
>>>>> As is clear, I think the console's limitations should be eliminated.
> 
>>>> And the rest of us feel the kernel console's code should be eliminated
>>>> entirely, when ever possible :)
> 
>>> The rest of you don't appear to be console users.  It's easy to advocate
>>> for removal when you don't use it yourself.
> 
>> FTR I'm not at all interested in eliminating the kernel's console.
> 
> Other people, in particular Greg KH, have said this is the current aim.
> I'm worried for myself.  It is far from clear that there is a suitable
> replacement for the kernel's console, and its elimination could easily
> happen without that replacement.  I suppose I could always (laboriously)
> maintain the console for myself personally.  Or there's always the BSDs.
> I just don't know what I'd do.

Alan, I really appreciate your efforts to enhance the kernel console code!
And I'm not interested to eliminate the kernel console either.

>> But there are must-have features and nice-to-have features. IMHO for
>> the kernel console i18n really belongs in the later category.
> 
> It's 2026, four decades after VGA was the latest thing, and Unicode
> support is merely a nice-to-have?
No. IMHO Unicode support would be great.

Thanks!
Helge

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: vt: Enlarge the framebuffer glyph size from 16 to 32 bits
  2026-08-31 17:21             ` Helge Deller
@ 2026-09-01 11:14               ` Alan Mackenzie
  0 siblings, 0 replies; 32+ messages in thread
From: Alan Mackenzie @ 2026-09-01 11:14 UTC (permalink / raw)
  To: Helge Deller
  Cc: Thomas Zimmermann, Greg Kroah-Hartman, Jiri Slaby, Simona Vetter,
	linux-serial, linux-kernel, linux-fbdev, dri-devel

On Mon, Aug 31, 2026 at 19:21:30 +0200, Helge Deller wrote:
> On 8/31/26 16:45, Alan Mackenzie wrote:
> > On Mon, Aug 31, 2026 at 08:20:10 +0200, Thomas Zimmermann wrote:
> >> Am 29.08.26 um 16:39 schrieb Alan Mackenzie:

[ .... ]

> > I've been using the console for around 28 years, now.  I don't recall it
> > crashing at all.  My current font and keyboard files are in exactly the
> > same form as they were 28 years ago.  These formats are stable and
> > dependable.

> >> I've spend the last years working on related code (fbcon) and it
> >> mostly feels dated and unmaintained. Our prime implementation (again
> >> fbcon) has been de-facto unmaintained for years.  We recently had
> >> contributors who are pushing the ioctls semantics and found bugs in
> >> fbcon for a number of corner cases.

[ .... ]

> >> FTR I'm not at all interested in eliminating the kernel's console.

> > Other people, in particular Greg KH, have said this is the current aim.
> > I'm worried for myself.  It is far from clear that there is a suitable
> > replacement for the kernel's console, and its elimination could easily
> > happen without that replacement.  I suppose I could always (laboriously)
> > maintain the console for myself personally.  Or there's always the BSDs.
> > I just don't know what I'd do.

> Alan, I really appreciate your efforts to enhance the kernel console code!
> And I'm not interested to eliminate the kernel console either.

> >> But there are must-have features and nice-to-have features. IMHO for
> >> the kernel console i18n really belongs in the later category.

> > It's 2026, four decades after VGA was the latest thing, and Unicode
> > support is merely a nice-to-have?

> No. IMHO Unicode support would be great.

Thank you, indeed!  That's cheering to hear.

> Thanks!
> Helge

-- 
Alan Mackenzie (Nuremberg, Germany).

^ permalink raw reply	[flat|nested] 32+ messages in thread

end of thread, other threads:[~2026-09-03  7:34 UTC | newest]

Thread overview: 32+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-27 18:38 vt: Enlarge the framebuffer glyph size from 16 to 32 bits Alan Mackenzie
2026-08-27 18:42 ` [Patch 1/9]: Make consolemap.c handle Unicode planes outside BMP Alan Mackenzie
2026-08-27 19:14   ` sashiko-bot
2026-08-28  4:57   ` Jiri Slaby
2026-08-29 11:51     ` Alan Mackenzie
2026-08-27 18:45 ` [Patch 2/9]: Glyph size: Use GLYPH_SZ/HW rather than hardcoded 2, 1 Alan Mackenzie
2026-08-27 19:22   ` sashiko-bot
2026-08-27 18:47 ` [Patch 3/9]: Replace scr_readw/writew by scr_readg/writeg, etc Alan Mackenzie
2026-08-27 19:18   ` sashiko-bot
2026-08-29 12:11   ` Greg Kroah-Hartman
2026-08-29 13:33     ` Alan Mackenzie
2026-08-27 18:48 ` [Patch 4/9]: Amend internal manipulation of glyph structure Alan Mackenzie
2026-08-27 19:19   ` sashiko-bot
2026-08-27 18:50 ` [Patch 5/9]: vt: Amend three Kconfig files Alan Mackenzie
2026-08-27 19:12   ` sashiko-bot
2026-08-27 18:52 ` [Patch 6/9]: vt: Use u32 and typedef u1632 to handle whole glyphs Alan Mackenzie
2026-08-27 19:22   ` sashiko-bot
2026-08-27 18:54 ` [Patch 7/9]: vt: Handle up to 2^21 glyphs, rather than 256/512 Alan Mackenzie
2026-08-27 19:14   ` sashiko-bot
2026-08-27 18:56 ` [Patch 8/9]: vt: Enhancements to the VT ioctl interface Alan Mackenzie
2026-08-27 19:25   ` sashiko-bot
2026-08-27 18:58 ` [Patch 9/9]: vt: Misc changes, e.g. to #include directives Alan Mackenzie
2026-08-27 19:25   ` sashiko-bot
2026-08-29 12:06   ` Greg Kroah-Hartman
2026-08-28  6:12 ` vt: Enlarge the framebuffer glyph size from 16 to 32 bits Thomas Zimmermann
2026-08-28 14:36   ` Alan Mackenzie
2026-08-29 12:09     ` Greg Kroah-Hartman
2026-08-29 14:39       ` Alan Mackenzie
2026-08-31  6:20         ` Thomas Zimmermann
2026-08-31 14:45           ` Alan Mackenzie
2026-08-31 17:21             ` Helge Deller
2026-09-01 11:14               ` Alan Mackenzie

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox