[Patch 1/9]: Make consolemap.c handle Unicode planes outside BMP
From: Alan Mackenzie
Date: Thu Aug 27 2026 - 14:43:07 EST
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@xxxxxx>
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).