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 <[email protected]>

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).

Reply via email to