mirror of https://lore.kernel.org/lkml/
 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; 13+ 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	[flat|nested] 13+ 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-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, 1 reply; 13+ 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	[flat|nested] 13+ 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 18:47 ` [Patch 3/9]: Replace scr_readw/writew by scr_readg/writeg, etc Alan Mackenzie
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 13+ 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	[flat|nested] 13+ 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 18:48 ` [Patch 4/9]: Amend internal manipulation of glyph structure Alan Mackenzie
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 13+ 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	[flat|nested] 13+ 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 18:50 ` [Patch 5/9]: vt: Amend three Kconfig files Alan Mackenzie
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 13+ 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	[flat|nested] 13+ 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 18:52 ` [Patch 6/9]: vt: Use u32 and typedef u1632 to handle whole glyphs Alan Mackenzie
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 13+ 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	[flat|nested] 13+ 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 18:54 ` [Patch 7/9]: vt: Handle up to 2^21 glyphs, rather than 256/512 Alan Mackenzie
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 13+ 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	[flat|nested] 13+ 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 18:56 ` [Patch 8/9]: vt: Enhancements to the VT ioctl interface Alan Mackenzie
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 13+ 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	[flat|nested] 13+ 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 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, 0 replies; 13+ 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	[flat|nested] 13+ 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-28  6:12 ` vt: Enlarge the framebuffer glyph size from 16 to 32 bits Thomas Zimmermann
  9 siblings, 0 replies; 13+ 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	[flat|nested] 13+ 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-28  4:57   ` Jiri Slaby
  0 siblings, 0 replies; 13+ 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] 13+ 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; 13+ 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] 13+ 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
  0 siblings, 0 replies; 13+ 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] 13+ messages in thread

end of thread, other threads:[~2026-08-28 14:36 UTC | newest]

Thread overview: 13+ 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-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
2026-08-27 18:47 ` [Patch 3/9]: Replace scr_readw/writew by scr_readg/writeg, etc Alan Mackenzie
2026-08-27 18:48 ` [Patch 4/9]: Amend internal manipulation of glyph structure Alan Mackenzie
2026-08-27 18:50 ` [Patch 5/9]: vt: Amend three Kconfig files Alan Mackenzie
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 ` [Patch 7/9]: vt: Handle up to 2^21 glyphs, rather than 256/512 Alan Mackenzie
2026-08-27 18:56 ` [Patch 8/9]: vt: Enhancements to the VT ioctl interface Alan Mackenzie
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
2026-08-28 14:36   ` Alan Mackenzie

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

all inboxes | Powered by JetHome®