summaryrefslogtreecommitdiff
path: root/drivers/tty/vt
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty/vt')
-rw-r--r--drivers/tty/vt/.gitignore9
-rw-r--r--drivers/tty/vt/Makefile47
-rw-r--r--drivers/tty/vt/conmakehash.c287
-rw-r--r--drivers/tty/vt/consolemap.c800
-rw-r--r--drivers/tty/vt/cp437.uni1
-rw-r--r--drivers/tty/vt/defkeymap.c_shipped201
-rw-r--r--drivers/tty/vt/defkeymap.map1
-rwxr-xr-xdrivers/tty/vt/gen_ucs_fallback_table.py360
-rwxr-xr-xdrivers/tty/vt/gen_ucs_recompose_table.py257
-rwxr-xr-xdrivers/tty/vt/gen_ucs_width_table.py307
-rw-r--r--drivers/tty/vt/keyboard.c1241
-rw-r--r--drivers/tty/vt/selection.c489
-rw-r--r--drivers/tty/vt/ucs.c251
-rw-r--r--drivers/tty/vt/ucs_fallback_table.h_shipped3346
-rw-r--r--drivers/tty/vt/ucs_recompose_table.h_shipped102
-rw-r--r--drivers/tty/vt/ucs_width_table.h_shipped453
-rw-r--r--drivers/tty/vt/vc_screen.c723
-rw-r--r--drivers/tty/vt/vt.c4221
-rw-r--r--drivers/tty/vt/vt_ioctl.c1188
19 files changed, 10411 insertions, 3873 deletions
diff --git a/drivers/tty/vt/.gitignore b/drivers/tty/vt/.gitignore
index 83683a2d8e6a..a74859bab862 100644
--- a/drivers/tty/vt/.gitignore
+++ b/drivers/tty/vt/.gitignore
@@ -1,2 +1,7 @@
-consolemap_deftbl.c
-defkeymap.c
+# SPDX-License-Identifier: GPL-2.0
+/conmakehash
+/consolemap_deftbl.c
+/defkeymap.c
+/ucs_fallback_table.h
+/ucs_recompose_table.h
+/ucs_width_table.h
diff --git a/drivers/tty/vt/Makefile b/drivers/tty/vt/Makefile
index 17ae94cb29f8..ae746dcdeec8 100644
--- a/drivers/tty/vt/Makefile
+++ b/drivers/tty/vt/Makefile
@@ -1,20 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
#
# This file contains the font map for the default (hardware) font
#
FONTMAPFILE = cp437.uni
obj-$(CONFIG_VT) += vt_ioctl.o vc_screen.o \
- selection.o keyboard.o
-obj-$(CONFIG_CONSOLE_TRANSLATIONS) += consolemap.o consolemap_deftbl.o
-obj-$(CONFIG_HW_CONSOLE) += vt.o defkeymap.o
+ selection.o keyboard.o \
+ vt.o defkeymap.o
+obj-$(CONFIG_CONSOLE_TRANSLATIONS) += consolemap.o consolemap_deftbl.o \
+ ucs.o
# Files generated that shall be removed upon make clean
-clean-files := consolemap_deftbl.c defkeymap.c
+clean-files := consolemap_deftbl.c defkeymap.c \
+ ucs_width_table.h ucs_recompose_table.h ucs_fallback_table.h
+
+hostprogs += conmakehash
quiet_cmd_conmk = CONMK $@
- cmd_conmk = scripts/conmakehash $< > $@
+ cmd_conmk = $(obj)/conmakehash $< > $@
-$(obj)/consolemap_deftbl.c: $(src)/$(FONTMAPFILE)
+$(obj)/consolemap_deftbl.c: $(src)/$(FONTMAPFILE) $(obj)/conmakehash
$(call cmd,conmk)
$(obj)/defkeymap.o: $(obj)/defkeymap.c
@@ -27,6 +32,34 @@ $(obj)/defkeymap.o: $(obj)/defkeymap.c
ifdef GENERATE_KEYMAP
$(obj)/defkeymap.c: $(obj)/%.c: $(src)/%.map
- loadkeys --mktable $< > $@
+ loadkeys --mktable --unicode $< > $@
+
+endif
+
+$(obj)/ucs.o: $(src)/ucs.c $(obj)/ucs_width_table.h \
+ $(obj)/ucs_recompose_table.h $(obj)/ucs_fallback_table.h
+
+# You may uncomment one of those to have the UCS tables be regenerated
+# during the build process. By default the _shipped versions are used.
+#
+#GENERATE_UCS_TABLES := 1
+#GENERATE_UCS_TABLES := 2 # invokes gen_ucs_recompose_table.py with --full
+
+ifdef GENERATE_UCS_TABLES
+
+$(obj)/ucs_width_table.h: $(src)/gen_ucs_width_table.py
+ $(PYTHON3) $< -o $@
+
+ifeq ($(GENERATE_UCS_TABLES),2)
+gen_recomp_arg := --full
+else
+gen_recomp_arg :=
+endif
+
+$(obj)/ucs_recompose_table.h: $(src)/gen_ucs_recompose_table.py
+ $(PYTHON3) $< -o $@ $(gen_recomp_arg)
+
+$(obj)/ucs_fallback_table.h: $(src)/gen_ucs_fallback_table.py
+ $(PYTHON3) $< -o $@
endif
diff --git a/drivers/tty/vt/conmakehash.c b/drivers/tty/vt/conmakehash.c
new file mode 100644
index 000000000000..a931fcde7ad9
--- /dev/null
+++ b/drivers/tty/vt/conmakehash.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * conmakehash.c
+ *
+ * Create arrays for initializing the kernel folded tables (using a hash
+ * table turned out to be to limiting...) Unfortunately we can't simply
+ * preinitialize the tables at compile time since kfree() cannot accept
+ * memory not allocated by kmalloc(), and doing our own memory management
+ * just for this seems like massive overkill.
+ *
+ * Copyright (C) 1995-1997 H. Peter Anvin
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <string.h>
+#include <ctype.h>
+
+#define MAX_FONTLEN 256
+
+typedef unsigned short unicode;
+
+static void usage(char *argv0)
+{
+ fprintf(stderr, "Usage: \n"
+ " %s chartable [hashsize] [hashstep] [maxhashlevel]\n", argv0);
+ exit(EX_USAGE);
+}
+
+static int getunicode(char **p0)
+{
+ char *p = *p0;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+ if (*p != 'U' || p[1] != '+' ||
+ !isxdigit(p[2]) || !isxdigit(p[3]) || !isxdigit(p[4]) ||
+ !isxdigit(p[5]) || isxdigit(p[6]))
+ return -1;
+ *p0 = p+6;
+ return strtol(p+2,0,16);
+}
+
+unicode unitable[MAX_FONTLEN][255];
+ /* Massive overkill, but who cares? */
+int unicount[MAX_FONTLEN];
+
+static void addpair(int fp, int un)
+{
+ int i;
+
+ if ( un <= 0xfffe )
+ {
+ /* Check it isn't a duplicate */
+
+ for ( i = 0 ; i < unicount[fp] ; i++ )
+ if ( unitable[fp][i] == un )
+ return;
+
+ /* Add to list */
+
+ if ( unicount[fp] > 254 )
+ {
+ fprintf(stderr, "ERROR: Only 255 unicodes/glyph permitted!\n");
+ exit(EX_DATAERR);
+ }
+
+ unitable[fp][unicount[fp]] = un;
+ unicount[fp]++;
+ }
+
+ /* otherwise: ignore */
+}
+
+int main(int argc, char *argv[])
+{
+ FILE *ctbl;
+ const char *tblname;
+ char buffer[65536];
+ int fontlen;
+ int i, nuni, nent;
+ int fp0, fp1, un0, un1;
+ char *p, *p1;
+
+ if ( argc < 2 || argc > 5 )
+ usage(argv[0]);
+
+ if ( !strcmp(argv[1],"-") )
+ {
+ ctbl = stdin;
+ tblname = "stdin";
+ }
+ else
+ {
+ ctbl = fopen(tblname = argv[1], "r");
+ if ( !ctbl )
+ {
+ perror(tblname);
+ exit(EX_NOINPUT);
+ }
+ }
+
+ /* For now we assume the default font is always 256 characters. */
+ fontlen = 256;
+
+ /* Initialize table */
+
+ for ( i = 0 ; i < fontlen ; i++ )
+ unicount[i] = 0;
+
+ /* Now we come to the tricky part. Parse the input table. */
+
+ while ( fgets(buffer, sizeof(buffer), ctbl) != NULL )
+ {
+ if ( (p = strchr(buffer, '\n')) != NULL )
+ *p = '\0';
+ else
+ fprintf(stderr, "%s: Warning: line too long\n", tblname);
+
+ p = buffer;
+
+/*
+ * Syntax accepted:
+ * <fontpos> <unicode> <unicode> ...
+ * <range> idem
+ * <range> <unicode range>
+ *
+ * where <range> ::= <fontpos>-<fontpos>
+ * and <unicode> ::= U+<h><h><h><h>
+ * and <h> ::= <hexadecimal digit>
+ */
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+ if (!*p || *p == '#')
+ continue; /* skip comment or blank line */
+
+ fp0 = strtol(p, &p1, 0);
+ if (p1 == p)
+ {
+ fprintf(stderr, "Bad input line: %s\n", buffer);
+ exit(EX_DATAERR);
+ }
+ p = p1;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+ if (*p == '-')
+ {
+ p++;
+ fp1 = strtol(p, &p1, 0);
+ if (p1 == p)
+ {
+ fprintf(stderr, "Bad input line: %s\n", buffer);
+ exit(EX_DATAERR);
+ }
+ p = p1;
+ }
+ else
+ fp1 = 0;
+
+ if ( fp0 < 0 || fp0 >= fontlen )
+ {
+ fprintf(stderr,
+ "%s: Glyph number (0x%x) larger than font length\n",
+ tblname, fp0);
+ exit(EX_DATAERR);
+ }
+ if ( fp1 && (fp1 < fp0 || fp1 >= fontlen) )
+ {
+ fprintf(stderr,
+ "%s: Bad end of range (0x%x)\n",
+ tblname, fp1);
+ exit(EX_DATAERR);
+ }
+
+ if (fp1)
+ {
+ /* we have a range; expect the word "idem" or a Unicode range of the
+ same length */
+ while (*p == ' ' || *p == '\t')
+ p++;
+ if (!strncmp(p, "idem", 4))
+ {
+ for (i=fp0; i<=fp1; i++)
+ addpair(i,i);
+ p += 4;
+ }
+ else
+ {
+ un0 = getunicode(&p);
+ while (*p == ' ' || *p == '\t')
+ p++;
+ if (*p != '-')
+ {
+ fprintf(stderr,
+"%s: Corresponding to a range of font positions, there should be a Unicode range\n",
+ tblname);
+ exit(EX_DATAERR);
+ }
+ p++;
+ un1 = getunicode(&p);
+ if (un0 < 0 || un1 < 0)
+ {
+ fprintf(stderr,
+"%s: Bad Unicode range corresponding to font position range 0x%x-0x%x\n",
+ tblname, fp0, fp1);
+ exit(EX_DATAERR);
+ }
+ if (un1 - un0 != fp1 - fp0)
+ {
+ fprintf(stderr,
+"%s: Unicode range U+%x-U+%x not of the same length as font position range 0x%x-0x%x\n",
+ tblname, un0, un1, fp0, fp1);
+ exit(EX_DATAERR);
+ }
+ for(i=fp0; i<=fp1; i++)
+ addpair(i,un0-fp0+i);
+ }
+ }
+ else
+ {
+ /* no range; expect a list of unicode values for a single font position */
+
+ while ( (un0 = getunicode(&p)) >= 0 )
+ addpair(fp0, un0);
+ }
+ while (*p == ' ' || *p == '\t')
+ p++;
+ if (*p && *p != '#')
+ fprintf(stderr, "%s: trailing junk (%s) ignored\n", tblname, p);
+ }
+
+ /* Okay, we hit EOF, now output hash table */
+
+ fclose(ctbl);
+
+
+ /* Compute total size of Unicode list */
+ nuni = 0;
+ for ( i = 0 ; i < fontlen ; i++ )
+ nuni += unicount[i];
+
+ printf("\
+/*\n\
+ * Automatically generated file; Do not edit.\n\
+ */\n\
+\n\
+#include <linux/types.h>\n\
+\n\
+u8 dfont_unicount[%d] = \n\
+{\n\t", fontlen);
+
+ for ( i = 0 ; i < fontlen ; i++ )
+ {
+ printf("%3d", unicount[i]);
+ if ( i == fontlen-1 )
+ printf("\n};\n");
+ else if ( i % 8 == 7 )
+ printf(",\n\t");
+ else
+ printf(", ");
+ }
+
+ printf("\nu16 dfont_unitable[%d] = \n{\n\t", nuni);
+
+ fp0 = 0;
+ nent = 0;
+ for ( i = 0 ; i < nuni ; i++ )
+ {
+ while ( nent >= unicount[fp0] )
+ {
+ fp0++;
+ nent = 0;
+ }
+ printf("0x%04x", unitable[fp0][nent++]);
+ if ( i == nuni-1 )
+ printf("\n};\n");
+ else if ( i % 8 == 7 )
+ printf(",\n\t");
+ else
+ printf(", ");
+ }
+
+ exit(EX_OK);
+}
diff --git a/drivers/tty/vt/consolemap.c b/drivers/tty/vt/consolemap.c
index 2978ca596a7f..7a11c3f2e875 100644
--- a/drivers/tty/vt/consolemap.c
+++ b/drivers/tty/vt/consolemap.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* consolemap.c
*
@@ -9,8 +10,21 @@
* Support for multiple unimaps by Jakub Jelinek <jj@ultra.linux.cz>, July 1998
*
* Fix bug in inverse translation. Stanislav Voronyi <stas@cnti.uanet.kharkov.ua>, Dec 1998
+ *
+ * In order to prevent the following circular lock dependency:
+ * &mm->mmap_lock --> cpu_hotplug.lock --> console_lock --> &mm->mmap_lock
+ *
+ * We cannot allow page fault to happen while holding the console_lock.
+ * Therefore, all the userspace copy operations have to be done outside
+ * the console_lock critical sections.
+ *
+ * As all the affected functions are all called directly from vt_ioctl(), we
+ * can allocate some small buffers directly on stack without worrying about
+ * stack overflow.
*/
+#include <linux/bitfield.h>
+#include <linux/bits.h>
#include <linux/module.h>
#include <linux/kd.h>
#include <linux/errno.h>
@@ -18,14 +32,15 @@
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/tty.h>
-#include <asm/uaccess.h>
+#include <linux/uaccess.h>
#include <linux/console.h>
#include <linux/consolemap.h>
#include <linux/vt_kern.h>
+#include <linux/string.h>
-static unsigned short translations[][256] = {
+static unsigned short translations[][E_TABSZ] = {
/* 8-bit Latin-1 mapped to Unicode -- trivial mapping */
- {
+ [LAT1_MAP] = {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
@@ -58,9 +73,9 @@ static unsigned short translations[][256] = {
0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
- },
+ },
/* VT100 graphics mapped to Unicode */
- {
+ [GRAF_MAP] = {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
@@ -95,8 +110,8 @@ static unsigned short translations[][256] = {
0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
},
/* IBM Codepage 437 mapped to Unicode */
- {
- 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
+ [IBMPC_MAP] = {
+ 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8,
0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
@@ -128,9 +143,9 @@ static unsigned short translations[][256] = {
0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
- },
+ },
/* User mapping -- default to codes for direct font mapping */
- {
+ [USER_MAP] = {
0xf000, 0xf001, 0xf002, 0xf003, 0xf004, 0xf005, 0xf006, 0xf007,
0xf008, 0xf009, 0xf00a, 0xf00b, 0xf00c, 0xf00d, 0xf00e, 0xf00f,
0xf010, 0xf011, 0xf012, 0xf013, 0xf014, 0xf015, 0xf016, 0xf017,
@@ -171,79 +186,105 @@ static unsigned short translations[][256] = {
#define MAX_GLYPH 512 /* Max possible glyph value */
-static int inv_translate[MAX_NR_CONSOLES];
+static enum translation_map inv_translate[MAX_NR_CONSOLES];
+
+#define UNI_DIRS 32U
+#define UNI_DIR_ROWS 32U
+#define UNI_ROW_GLYPHS 64U
+
+#define UNI_DIR_BITS GENMASK(15, 11)
+#define UNI_ROW_BITS GENMASK(10, 6)
+#define UNI_GLYPH_BITS GENMASK( 5, 0)
+
+#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)) | \
+ FIELD_PREP(UNI_ROW_BITS, (row)) | \
+ FIELD_PREP(UNI_GLYPH_BITS, (glyph)))
-struct uni_pagedir {
- u16 **uni_pgdir[32];
+/**
+ * struct uni_pagedict - unicode directory
+ *
+ * @uni_pgdir: 32*32*64 table with glyphs
+ * @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];
unsigned long refcount;
unsigned long sum;
- unsigned char *inverse_translations[4];
+ unsigned char *inverse_translations[LAST_MAP + 1];
u16 *inverse_trans_unicode;
- int readonly;
};
-static struct uni_pagedir *dflt;
+static struct uni_pagedict *dflt;
-static void set_inverse_transl(struct vc_data *conp, struct uni_pagedir *p, int i)
+static void set_inverse_transl(struct vc_data *conp, struct uni_pagedict *dict,
+ enum translation_map m)
{
- int j, glyph;
- unsigned short *t = translations[i];
- unsigned char *q;
-
- if (!p) return;
- q = p->inverse_translations[i];
-
- if (!q) {
- q = p->inverse_translations[i] = kmalloc(MAX_GLYPH, GFP_KERNEL);
- if (!q) return;
+ unsigned short *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(q, 0, MAX_GLYPH);
+ memset(inv, 0, MAX_GLYPH);
- for (j = 0; j < E_TABSZ; j++) {
- glyph = conv_uni_to_pc(conp, t[j]);
- if (glyph >= 0 && glyph < MAX_GLYPH && q[glyph] < 32) {
+ 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) {
/* prefer '-' above SHY etc. */
- q[glyph] = j;
+ inv[glyph] = ch;
}
}
}
-static void set_inverse_trans_unicode(struct vc_data *conp,
- struct uni_pagedir *p)
+static void set_inverse_trans_unicode(struct uni_pagedict *dict)
{
- int i, j, k, glyph;
- u16 **p1, *p2;
- u16 *q;
-
- if (!p) return;
- q = p->inverse_trans_unicode;
- if (!q) {
- q = p->inverse_trans_unicode =
- kmalloc(MAX_GLYPH * sizeof(u16), GFP_KERNEL);
- if (!q)
+ unsigned int d, r, g;
+ u16 *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(q, 0, MAX_GLYPH * sizeof(u16));
+ memset(inv, 0, MAX_GLYPH * sizeof(*inv));
- for (i = 0; i < 32; i++) {
- p1 = p->uni_pgdir[i];
- if (!p1)
+ for (d = 0; d < UNI_DIRS; d++) {
+ u16 **dir = dict->uni_pgdir[d];
+ if (!dir)
continue;
- for (j = 0; j < 32; j++) {
- p2 = p1[j];
- if (!p2)
+ for (r = 0; r < UNI_DIR_ROWS; r++) {
+ u16 *row = dir[r];
+ if (!row)
continue;
- for (k = 0; k < 64; k++) {
- glyph = p2[k];
- if (glyph >= 0 && glyph < MAX_GLYPH
- && q[glyph] < 32)
- q[glyph] = (i << 11) + (j << 6) + k;
+ 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);
}
}
}
}
-unsigned short *set_translate(int m, struct vc_data *vc)
+unsigned short *set_translate(enum translation_map m, struct vc_data *vc)
{
inv_translate[vc->vc_num] = m;
return translations[m];
@@ -256,41 +297,45 @@ unsigned short *set_translate(int 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(struct vc_data *conp, int glyph, int use_unicode)
+u16 inverse_translate(const struct vc_data *conp, u16 glyph, bool use_unicode)
{
- struct uni_pagedir *p;
- int m;
- if (glyph < 0 || glyph >= MAX_GLYPH)
+ struct uni_pagedict *p;
+ enum translation_map m;
+
+ if (glyph >= MAX_GLYPH)
return 0;
- else if (!(p = (struct uni_pagedir *)*conp->vc_uni_pagedir_loc))
+
+ p = *conp->uni_pagedict_loc;
+ if (!p)
return glyph;
- else if (use_unicode) {
+
+ if (use_unicode) {
if (!p->inverse_trans_unicode)
return glyph;
- else
- return p->inverse_trans_unicode[glyph];
- } else {
- m = inv_translate[conp->vc_num];
- if (!p->inverse_translations[m])
- return glyph;
- else
- return p->inverse_translations[m][glyph];
+
+ return p->inverse_trans_unicode[glyph];
}
+
+ m = inv_translate[conp->vc_num];
+ if (!p->inverse_translations[m])
+ return glyph;
+
+ return p->inverse_translations[m][glyph];
}
EXPORT_SYMBOL_GPL(inverse_translate);
static void update_user_maps(void)
{
int i;
- struct uni_pagedir *p, *q = NULL;
-
+ struct uni_pagedict *p, *q = NULL;
+
for (i = 0; i < MAX_NR_CONSOLES; i++) {
if (!vc_cons_allocated(i))
continue;
- p = (struct uni_pagedir *)*vc_cons[i].d->vc_uni_pagedir_loc;
+ p = *vc_cons[i].d->uni_pagedict_loc;
if (p && p != q) {
set_inverse_transl(vc_cons[i].d, p, USER_MAP);
- set_inverse_trans_unicode(vc_cons[i].d, p);
+ set_inverse_trans_unicode(p);
q = p;
}
}
@@ -306,21 +351,20 @@ static void update_user_maps(void)
*/
int con_set_trans_old(unsigned char __user * arg)
{
- int i;
- unsigned short *p = translations[USER_MAP];
-
- if (!access_ok(VERIFY_READ, arg, E_TABSZ))
- return -EFAULT;
-
- console_lock();
- for (i=0; i<E_TABSZ ; i++) {
- unsigned char uc;
- __get_user(uc, arg+i);
- p[i] = UNI_DIRECT_BASE | uc;
+ unsigned short inbuf[E_TABSZ];
+ unsigned int i;
+ unsigned char ch;
+
+ for (i = 0; i < ARRAY_SIZE(inbuf); i++) {
+ if (get_user(ch, &arg[i]))
+ return -EFAULT;
+ inbuf[i] = UNI_DIRECT_BASE | ch;
}
+ guard(console_lock)();
+ memcpy(translations[USER_MAP], inbuf, sizeof(inbuf));
update_user_maps();
- console_unlock();
+
return 0;
}
@@ -328,58 +372,43 @@ int con_get_trans_old(unsigned char __user * arg)
{
int i, ch;
unsigned short *p = translations[USER_MAP];
+ unsigned char outbuf[E_TABSZ];
- if (!access_ok(VERIFY_WRITE, arg, E_TABSZ))
- return -EFAULT;
+ scoped_guard(console_lock)
+ for (i = 0; i < ARRAY_SIZE(outbuf); i++) {
+ ch = conv_uni_to_pc(vc_cons[fg_console].d, p[i]);
+ outbuf[i] = (ch & ~0xff) ? 0 : ch;
+ }
- console_lock();
- for (i=0; i<E_TABSZ ; i++)
- {
- ch = conv_uni_to_pc(vc_cons[fg_console].d, p[i]);
- __put_user((ch & ~0xff) ? 0 : ch, arg+i);
- }
- console_unlock();
- return 0;
+ return copy_to_user(arg, outbuf, sizeof(outbuf)) ? -EFAULT : 0;
}
int con_set_trans_new(ushort __user * arg)
{
- int i;
- unsigned short *p = translations[USER_MAP];
+ unsigned short inbuf[E_TABSZ];
- if (!access_ok(VERIFY_READ, arg, E_TABSZ*sizeof(unsigned short)))
+ if (copy_from_user(inbuf, arg, sizeof(inbuf)))
return -EFAULT;
- console_lock();
- for (i=0; i<E_TABSZ ; i++) {
- unsigned short us;
- __get_user(us, arg+i);
- p[i] = us;
- }
-
+ guard(console_lock)();
+ memcpy(translations[USER_MAP], inbuf, sizeof(inbuf));
update_user_maps();
- console_unlock();
+
return 0;
}
int con_get_trans_new(ushort __user * arg)
{
- int i;
- unsigned short *p = translations[USER_MAP];
+ unsigned short outbuf[E_TABSZ];
- if (!access_ok(VERIFY_WRITE, arg, E_TABSZ*sizeof(unsigned short)))
- return -EFAULT;
+ scoped_guard(console_lock)
+ memcpy(outbuf, translations[USER_MAP], sizeof(outbuf));
- console_lock();
- for (i=0; i<E_TABSZ ; i++)
- __put_user(p[i], arg+i);
- console_unlock();
-
- return 0;
+ return copy_to_user(arg, outbuf, sizeof(outbuf)) ? -EFAULT : 0;
}
/*
- * Unicode -> current font conversion
+ * Unicode -> current font conversion
*
* A font has at most 512 chars, usually 256.
* But one font position may represent several Unicode chars.
@@ -391,77 +420,82 @@ int con_get_trans_new(ushort __user * arg)
extern u8 dfont_unicount[]; /* Defined in console_defmap.c */
extern u16 dfont_unitable[];
-static void con_release_unimap(struct uni_pagedir *p)
+static void con_release_unimap(struct uni_pagedict *dict)
{
- u16 **p1;
- int i, j;
-
- if (p == dflt) dflt = NULL;
- for (i = 0; i < 32; i++) {
- if ((p1 = p->uni_pgdir[i]) != NULL) {
- for (j = 0; j < 32; j++)
- kfree(p1[j]);
- kfree(p1);
+ unsigned int 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);
}
- p->uni_pgdir[i] = NULL;
+ dict->uni_pgdir[d] = NULL;
}
- for (i = 0; i < 4; i++) {
- kfree(p->inverse_translations[i]);
- p->inverse_translations[i] = NULL;
+
+ for (r = 0; r < ARRAY_SIZE(dict->inverse_translations); r++) {
+ kfree(dict->inverse_translations[r]);
+ dict->inverse_translations[r] = NULL;
}
- kfree(p->inverse_trans_unicode);
- p->inverse_trans_unicode = NULL;
+
+ kfree(dict->inverse_trans_unicode);
+ dict->inverse_trans_unicode = NULL;
}
/* Caller must hold the console lock */
void con_free_unimap(struct vc_data *vc)
{
- struct uni_pagedir *p;
+ struct uni_pagedict *p;
- p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+ p = *vc->uni_pagedict_loc;
if (!p)
return;
- *vc->vc_uni_pagedir_loc = 0;
+ *vc->uni_pagedict_loc = NULL;
if (--p->refcount)
return;
con_release_unimap(p);
kfree(p);
}
-
-static int con_unify_unimap(struct vc_data *conp, struct uni_pagedir *p)
+
+static int con_unify_unimap(struct vc_data *conp, struct uni_pagedict *dict1)
{
- int i, j, k;
- struct uni_pagedir *q;
-
- for (i = 0; i < MAX_NR_CONSOLES; i++) {
- if (!vc_cons_allocated(i))
+ struct uni_pagedict *dict2;
+ unsigned int cons, d, r;
+
+ for (cons = 0; cons < MAX_NR_CONSOLES; cons++) {
+ if (!vc_cons_allocated(cons))
continue;
- q = (struct uni_pagedir *)*vc_cons[i].d->vc_uni_pagedir_loc;
- if (!q || q == p || q->sum != p->sum)
+ dict2 = *vc_cons[cons].d->uni_pagedict_loc;
+ if (!dict2 || dict2 == dict1 || dict2->sum != dict1->sum)
continue;
- for (j = 0; j < 32; j++) {
- u16 **p1, **q1;
- p1 = p->uni_pgdir[j]; q1 = q->uni_pgdir[j];
- if (!p1 && !q1)
+ for (d = 0; d < UNI_DIRS; d++) {
+ u16 **dir1 = dict1->uni_pgdir[d];
+ u16 **dir2 = dict2->uni_pgdir[d];
+ if (!dir1 && !dir2)
continue;
- if (!p1 || !q1)
+ if (!dir1 || !dir2)
break;
- for (k = 0; k < 32; k++) {
- if (!p1[k] && !q1[k])
+ for (r = 0; r < UNI_DIR_ROWS; r++) {
+ if (!dir1[r] && !dir2[r])
continue;
- if (!p1[k] || !q1[k])
+ if (!dir1[r] || !dir2[r])
break;
- if (memcmp(p1[k], q1[k], 64*sizeof(u16)))
+ if (memcmp(dir1[r], dir2[r], UNI_ROW_GLYPHS *
+ sizeof(*dir1[r])))
break;
}
- if (k < 32)
+ if (r < UNI_DIR_ROWS)
break;
}
- if (j == 32) {
- q->refcount++;
- *conp->vc_uni_pagedir_loc = (unsigned long)q;
- con_release_unimap(p);
- kfree(p);
+ if (d == UNI_DIRS) {
+ dict2->refcount++;
+ *conp->uni_pagedict_loc = dict2;
+ con_release_unimap(dict1);
+ kfree(dict1);
return 1;
}
}
@@ -469,171 +503,179 @@ static int con_unify_unimap(struct vc_data *conp, struct uni_pagedir *p)
}
static int
-con_insert_unipair(struct uni_pagedir *p, u_short unicode, u_short fontpos)
+con_insert_unipair(struct uni_pagedict *p, u_short unicode, u_short fontpos)
{
- int i, n;
- u16 **p1, *p2;
-
- if (!(p1 = p->uni_pgdir[n = unicode >> 11])) {
- p1 = p->uni_pgdir[n] = kmalloc(32*sizeof(u16 *), GFP_KERNEL);
- if (!p1) return -ENOMEM;
- for (i = 0; i < 32; i++)
- p1[i] = NULL;
+ u16 **dir, *row;
+ unsigned int n;
+
+ n = UNI_DIR(unicode);
+ dir = p->uni_pgdir[n];
+ if (!dir) {
+ dir = p->uni_pgdir[n] = kcalloc(UNI_DIR_ROWS, sizeof(*dir),
+ GFP_KERNEL);
+ if (!dir)
+ return -ENOMEM;
}
- if (!(p2 = p1[n = (unicode >> 6) & 0x1f])) {
- p2 = p1[n] = kmalloc(64*sizeof(u16), GFP_KERNEL);
- if (!p2) return -ENOMEM;
- memset(p2, 0xff, 64*sizeof(u16)); /* No glyphs for the characters (yet) */
+ n = UNI_ROW(unicode);
+ row = dir[n];
+ if (!row) {
+ row = dir[n] = kmalloc_array(UNI_ROW_GLYPHS, sizeof(*row),
+ GFP_KERNEL);
+ if (!row)
+ return -ENOMEM;
+ /* No glyphs for the characters (yet) */
+ memset(row, 0xff, UNI_ROW_GLYPHS * sizeof(*row));
}
- p2[unicode & 0x3f] = fontpos;
-
- p->sum += (fontpos << 20) + unicode;
+ row[UNI_GLYPH(unicode)] = fontpos;
+
+ p->sum += (fontpos << 20U) + unicode;
return 0;
}
-/* ui is a leftover from using a hashtable, but might be used again
- Caller must hold the lock */
-static int con_do_clear_unimap(struct vc_data *vc, struct unimapinit *ui)
+static int con_allocate_new(struct vc_data *vc)
{
- struct uni_pagedir *p, *q;
+ struct uni_pagedict *new, *old = *vc->uni_pagedict_loc;
- p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
- if (p && p->readonly)
- return -EIO;
+ new = kzalloc(sizeof(*new), GFP_KERNEL);
+ if (!new)
+ return -ENOMEM;
+
+ new->refcount = 1;
+ *vc->uni_pagedict_loc = new;
+
+ if (old)
+ old->refcount--;
- if (!p || --p->refcount) {
- q = kzalloc(sizeof(*p), GFP_KERNEL);
- if (!q) {
- if (p)
- p->refcount++;
- return -ENOMEM;
- }
- q->refcount=1;
- *vc->vc_uni_pagedir_loc = (unsigned long)q;
- } else {
- if (p == dflt) dflt = NULL;
- p->refcount++;
- p->sum = 0;
- con_release_unimap(p);
- }
return 0;
}
-int con_clear_unimap(struct vc_data *vc, struct unimapinit *ui)
+/* Caller must hold the lock */
+static int con_do_clear_unimap(struct vc_data *vc)
{
- int ret;
- console_lock();
- ret = con_do_clear_unimap(vc, ui);
- console_unlock();
- return ret;
+ struct uni_pagedict *old = *vc->uni_pagedict_loc;
+
+ if (!old || old->refcount > 1)
+ return con_allocate_new(vc);
+
+ old->sum = 0;
+ con_release_unimap(old);
+
+ return 0;
}
-
-int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
+
+int con_clear_unimap(struct vc_data *vc)
{
- int err = 0, err1, i;
- struct uni_pagedir *p, *q;
+ guard(console_lock)();
+ return con_do_clear_unimap(vc);
+}
- console_lock();
+static struct uni_pagedict *con_unshare_unimap(struct vc_data *vc,
+ struct uni_pagedict *old)
+{
+ struct uni_pagedict *new;
+ unsigned int d, r, g;
+ int ret;
+ u16 uni = 0;
- /* Save original vc_unipagdir_loc in case we allocate a new one */
- p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
- if (p->readonly) {
- console_unlock();
- return -EIO;
- }
-
- if (!ct) {
- console_unlock();
- return 0;
- }
-
- if (p->refcount > 1) {
- int j, k;
- u16 **p1, *p2, l;
-
- err1 = con_do_clear_unimap(vc, NULL);
- if (err1) {
- console_unlock();
- return err1;
+ ret = con_allocate_new(vc);
+ if (ret)
+ return ERR_PTR(ret);
+
+ new = *vc->uni_pagedict_loc;
+
+ /*
+ * uni_pgdir is a 32*32*64 table with rows 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) {
+ /* Account for empty table */
+ uni += UNI_DIR_ROWS * UNI_ROW_GLYPHS;
+ continue;
}
-
- /*
- * Since refcount was > 1, con_clear_unimap() allocated a
- * a new uni_pagedir for this vc. Re: p != q
- */
- q = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
-
- /*
- * uni_pgdir is a 32*32*64 table with rows allocated
- * when its first entry is added. The unicode value must
- * still be incremented for empty rows. We are copying
- * entries from "p" (old) to "q" (new).
- */
- l = 0; /* unicode value */
- for (i = 0; i < 32; i++)
- if ((p1 = p->uni_pgdir[i]))
- for (j = 0; j < 32; j++)
- if ((p2 = p1[j])) {
- for (k = 0; k < 64; k++, l++)
- if (p2[k] != 0xffff) {
- /*
- * Found one, copy entry for unicode
- * l with fontpos value p2[k].
- */
- err1 = con_insert_unipair(q, l, p2[k]);
- if (err1) {
- p->refcount++;
- *vc->vc_uni_pagedir_loc = (unsigned long)p;
- con_release_unimap(q);
- kfree(q);
- console_unlock();
- return err1;
- }
- }
- } else {
+
+ for (r = 0; r < UNI_DIR_ROWS; r++) {
+ u16 *row = dir[r];
+ if (!row) {
/* Account for row of 64 empty entries */
- l += 64;
+ uni += UNI_ROW_GLYPHS;
+ continue;
}
- else
- /* Account for empty table */
- l += 32 * 64;
- /*
- * Finished copying font table, set vc_uni_pagedir to new table
- */
- p = q;
- } else if (p == dflt) {
+ for (g = 0; g < UNI_ROW_GLYPHS; g++, uni++) {
+ if (row[g] == 0xffff)
+ 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)
+{
+ struct uni_pagedict *dict;
+ struct unipair *plist;
+ int err = 0;
+
+ 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)
+ return -EINVAL;
+
+ if (dict->refcount > 1) {
+ dict = con_unshare_unimap(vc, dict);
+ if (IS_ERR(dict))
+ return PTR_ERR(dict);
+ } else if (dict == dflt) {
dflt = NULL;
}
/*
* Insert user specified unicode pairs into new table.
*/
- while (ct--) {
- unsigned short unicode, fontpos;
- __get_user(unicode, &list->unicode);
- __get_user(fontpos, &list->fontpos);
- if ((err1 = con_insert_unipair(p, unicode,fontpos)) != 0)
+ for (plist = unilist; ct; ct--, plist++) {
+ int err1 = con_insert_unipair(dict, plist->unicode, plist->fontpos);
+ if (err1)
err = err1;
- list++;
}
-
+
/*
* Merge with fontmaps of any other virtual consoles.
*/
- if (con_unify_unimap(vc, p)) {
- console_unlock();
+ if (con_unify_unimap(vc, dict))
return err;
- }
- for (i = 0; i <= 3; i++)
- set_inverse_transl(vc, p, i); /* Update inverse translations */
- set_inverse_trans_unicode(vc, p);
+ for (enum translation_map m = FIRST_MAP; m <= LAST_MAP; m++)
+ set_inverse_transl(vc, dict, m);
+ set_inverse_trans_unicode(dict);
- console_unlock();
return err;
}
@@ -644,55 +686,56 @@ int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
* Loads the unimap for the hardware font, as defined in uni_hash.tbl.
* The representation used was the most compact I could come up
* with. This routine is executed at video setup, and when the
- * PIO_FONTRESET ioctl is called.
+ * PIO_FONTRESET ioctl is called.
*
* The caller must hold the console lock
*/
int con_set_default_unimap(struct vc_data *vc)
{
- int i, j, err = 0, err1;
- u16 *q;
- struct uni_pagedir *p;
+ struct uni_pagedict *dict;
+ unsigned int fontpos, count;
+ int err = 0, err1;
+ u16 *dfont;
if (dflt) {
- p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
- if (p == dflt)
+ dict = *vc->uni_pagedict_loc;
+ if (dict == dflt)
return 0;
dflt->refcount++;
- *vc->vc_uni_pagedir_loc = (unsigned long)dflt;
- if (p && !--p->refcount) {
- con_release_unimap(p);
- kfree(p);
+ *vc->uni_pagedict_loc = dflt;
+ if (dict && !--dict->refcount) {
+ con_release_unimap(dict);
+ kfree(dict);
}
return 0;
}
-
+
/* The default font is always 256 characters */
- err = con_do_clear_unimap(vc, NULL);
+ err = con_do_clear_unimap(vc);
if (err)
return err;
-
- p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
- q = dfont_unitable;
-
- for (i = 0; i < 256; i++)
- for (j = dfont_unicount[i]; j; j--) {
- err1 = con_insert_unipair(p, *(q++), i);
+
+ dict = *vc->uni_pagedict_loc;
+ dfont = dfont_unitable;
+
+ for (fontpos = 0; fontpos < 256U; fontpos++)
+ for (count = dfont_unicount[fontpos]; count; count--) {
+ err1 = con_insert_unipair(dict, *(dfont++), fontpos);
if (err1)
err = err1;
}
-
- if (con_unify_unimap(vc, p)) {
- dflt = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+
+ if (con_unify_unimap(vc, dict)) {
+ dflt = *vc->uni_pagedict_loc;
return err;
}
- for (i = 0; i <= 3; i++)
- set_inverse_transl(vc, p, i); /* Update all inverse translations */
- set_inverse_trans_unicode(vc, p);
- dflt = p;
+ for (enum translation_map m = FIRST_MAP; m <= LAST_MAP; m++)
+ set_inverse_transl(vc, dict, m);
+ set_inverse_trans_unicode(dict);
+ dflt = dict;
return err;
}
EXPORT_SYMBOL(con_set_default_unimap);
@@ -700,62 +743,80 @@ EXPORT_SYMBOL(con_set_default_unimap);
/**
* con_copy_unimap - copy unimap between two vts
* @dst_vc: target
- * @src_vt: source
+ * @src_vc: source
*
* The caller must hold the console lock when invoking this method
*/
int con_copy_unimap(struct vc_data *dst_vc, struct vc_data *src_vc)
{
- struct uni_pagedir *q;
+ struct uni_pagedict *src;
- if (!*src_vc->vc_uni_pagedir_loc)
+ if (!*src_vc->uni_pagedict_loc)
return -EINVAL;
- if (*dst_vc->vc_uni_pagedir_loc == *src_vc->vc_uni_pagedir_loc)
+ if (*dst_vc->uni_pagedict_loc == *src_vc->uni_pagedict_loc)
return 0;
con_free_unimap(dst_vc);
- q = (struct uni_pagedir *)*src_vc->vc_uni_pagedir_loc;
- q->refcount++;
- *dst_vc->vc_uni_pagedir_loc = (long)q;
+ src = *src_vc->uni_pagedict_loc;
+ src->refcount++;
+ *dst_vc->uni_pagedict_loc = src;
return 0;
}
EXPORT_SYMBOL(con_copy_unimap);
-/**
+/*
* con_get_unimap - get the unicode map
- * @vc: the console to read from
*
* 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, ushort ct, ushort __user *uct,
+ struct unipair __user *list)
{
- int i, j, k, ect;
- u16 **p1, *p2;
- struct uni_pagedir *p;
-
- console_lock();
-
- ect = 0;
- if (*vc->vc_uni_pagedir_loc) {
- p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
- for (i = 0; i < 32; i++)
- if ((p1 = p->uni_pgdir[i]))
- for (j = 0; j < 32; j++)
- if ((p2 = *(p1++)))
- for (k = 0; k < 64; k++) {
- if (*p2 < MAX_GLYPH && ect++ < ct) {
- __put_user((u_short)((i<<11)+(j<<6)+k),
- &list->unicode);
- __put_user((u_short) *p2,
- &list->fontpos);
- list++;
+ ushort ect;
+ struct uni_pagedict *dict;
+ unsigned int d, r, g;
+
+ struct unipair *unilist __free(kvfree) = kvmalloc_array(ct, sizeof(*unilist), GFP_KERNEL);
+ if (!unilist)
+ return -ENOMEM;
+
+ 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)
+ continue;
+
+ for (r = 0; r < UNI_DIR_ROWS; r++) {
+ u16 *row = dir[r];
+ if (!row)
+ continue;
+
+ for (g = 0; g < UNI_ROW_GLYPHS; g++, row++) {
+ if (*row >= MAX_GLYPH)
+ continue;
+ if (ect < ct) {
+ unilist[ect].unicode = UNI(d, r, g);
+ unilist[ect].fontpos = *row;
}
- p2++;
+ ect++;
}
+ }
+ }
}
- __put_user(ect, uct);
- console_unlock();
- return ((ect <= ct) ? 0 : -ENOMEM);
+
+ 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;
}
/*
@@ -778,27 +839,23 @@ u32 conv_8bit_to_uni(unsigned char c)
int conv_uni_to_8bit(u32 uni)
{
int c;
- for (c = 0; c < 0x100; c++)
+ for (c = 0; c < ARRAY_SIZE(translations[USER_MAP]); c++)
if (translations[USER_MAP][c] == uni ||
(translations[USER_MAP][c] == (c | 0xf000) && uni == c))
return c;
return -1;
}
-int
-conv_uni_to_pc(struct vc_data *conp, long ucs)
+int conv_uni_to_pc(struct vc_data *conp, long ucs)
{
- int h;
- u16 **p1, *p2;
- struct uni_pagedir *p;
-
+ struct uni_pagedict *dict;
+ u16 **dir, *row, glyph;
+
/* Only 16-bit codes supported at this time */
if (ucs > 0xffff)
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
@@ -806,17 +863,24 @@ conv_uni_to_pc(struct vc_data *conp, long ucs)
*/
else if ((ucs & ~UNI_DIRECT_MASK) == UNI_DIRECT_BASE)
return ucs & UNI_DIRECT_MASK;
-
- if (!*conp->vc_uni_pagedir_loc)
+
+ dict = *conp->uni_pagedict_loc;
+ if (!dict)
return -3;
- p = (struct uni_pagedir *)*conp->vc_uni_pagedir_loc;
- if ((p1 = p->uni_pgdir[ucs >> 11]) &&
- (p2 = p1[(ucs >> 6) & 0x1f]) &&
- (h = p2[ucs & 0x3f]) < MAX_GLYPH)
- return h;
+ dir = dict->uni_pgdir[UNI_DIR(ucs)];
+ if (!dir)
+ return -4;
+
+ row = dir[UNI_ROW(ucs)];
+ if (!row)
+ return -4;
- return -4; /* not found */
+ glyph = row[UNI_GLYPH(ucs)];
+ if (glyph >= MAX_GLYPH)
+ return -4;
+
+ return glyph;
}
/*
@@ -824,13 +888,13 @@ conv_uni_to_pc(struct vc_data *conp, long ucs)
* initialized. It must be possible to call kmalloc(..., GFP_KERNEL)
* from this function, hence the call from sys_setup.
*/
-void __init
+void __init
console_map_init(void)
{
int i;
-
+
for (i = 0; i < MAX_NR_CONSOLES; i++)
- if (vc_cons_allocated(i) && !*vc_cons[i].d->vc_uni_pagedir_loc)
+ if (vc_cons_allocated(i) && !*vc_cons[i].d->uni_pagedict_loc)
con_set_default_unimap(vc_cons[i].d);
}
diff --git a/drivers/tty/vt/cp437.uni b/drivers/tty/vt/cp437.uni
index bc6163484f62..a1991904c559 100644
--- a/drivers/tty/vt/cp437.uni
+++ b/drivers/tty/vt/cp437.uni
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
#
# Unicode table for IBM Codepage 437. Note that there are many more
# substitutions that could be conceived (for example, thick-line
diff --git a/drivers/tty/vt/defkeymap.c_shipped b/drivers/tty/vt/defkeymap.c_shipped
index d2208dfe3f67..6af7bf8d5460 100644
--- a/drivers/tty/vt/defkeymap.c_shipped
+++ b/drivers/tty/vt/defkeymap.c_shipped
@@ -1,11 +1,12 @@
-/* Do not edit this file! It was automatically generated by */
-/* loadkeys --mktable defkeymap.map > defkeymap.c */
+// SPDX-License-Identifier: GPL-2.0
+/* Do not edit this file! It was automatically generated by */
+/* loadkeys --mktable --unicode defkeymap.map > defkeymap.c */
#include <linux/types.h>
#include <linux/keyboard.h>
#include <linux/kd.h>
-u_short plain_map[NR_KEYS] = {
+unsigned short plain_map[NR_KEYS] = {
0xf200, 0xf01b, 0xf031, 0xf032, 0xf033, 0xf034, 0xf035, 0xf036,
0xf037, 0xf038, 0xf039, 0xf030, 0xf02d, 0xf03d, 0xf07f, 0xf009,
0xfb71, 0xfb77, 0xfb65, 0xfb72, 0xfb74, 0xfb79, 0xfb75, 0xfb69,
@@ -22,9 +23,25 @@ u_short plain_map[NR_KEYS] = {
0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116,
0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
};
-u_short shift_map[NR_KEYS] = {
+static unsigned short shift_map[NR_KEYS] = {
0xf200, 0xf01b, 0xf021, 0xf040, 0xf023, 0xf024, 0xf025, 0xf05e,
0xf026, 0xf02a, 0xf028, 0xf029, 0xf05f, 0xf02b, 0xf07f, 0xf009,
0xfb51, 0xfb57, 0xfb45, 0xfb52, 0xfb54, 0xfb59, 0xfb55, 0xfb49,
@@ -41,9 +58,25 @@ u_short shift_map[NR_KEYS] = {
0xf20b, 0xf601, 0xf602, 0xf117, 0xf600, 0xf20a, 0xf115, 0xf116,
0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
};
-u_short altgr_map[NR_KEYS] = {
+static unsigned short altgr_map[NR_KEYS] = {
0xf200, 0xf200, 0xf200, 0xf040, 0xf200, 0xf024, 0xf200, 0xf200,
0xf07b, 0xf05b, 0xf05d, 0xf07d, 0xf05c, 0xf200, 0xf200, 0xf200,
0xfb71, 0xfb77, 0xf918, 0xfb72, 0xfb74, 0xfb79, 0xfb75, 0xfb69,
@@ -60,9 +93,25 @@ u_short altgr_map[NR_KEYS] = {
0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116,
0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
};
-u_short ctrl_map[NR_KEYS] = {
+static unsigned short ctrl_map[NR_KEYS] = {
0xf200, 0xf200, 0xf200, 0xf000, 0xf01b, 0xf01c, 0xf01d, 0xf01e,
0xf01f, 0xf07f, 0xf200, 0xf200, 0xf01f, 0xf200, 0xf008, 0xf200,
0xf011, 0xf017, 0xf005, 0xf012, 0xf014, 0xf019, 0xf015, 0xf009,
@@ -79,9 +128,25 @@ u_short ctrl_map[NR_KEYS] = {
0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116,
0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
};
-u_short shift_ctrl_map[NR_KEYS] = {
+static unsigned short shift_ctrl_map[NR_KEYS] = {
0xf200, 0xf200, 0xf200, 0xf000, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf01f, 0xf200, 0xf200, 0xf200,
0xf011, 0xf017, 0xf005, 0xf012, 0xf014, 0xf019, 0xf015, 0xf009,
@@ -98,9 +163,25 @@ u_short shift_ctrl_map[NR_KEYS] = {
0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116,
0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
};
-u_short alt_map[NR_KEYS] = {
+static unsigned short alt_map[NR_KEYS] = {
0xf200, 0xf81b, 0xf831, 0xf832, 0xf833, 0xf834, 0xf835, 0xf836,
0xf837, 0xf838, 0xf839, 0xf830, 0xf82d, 0xf83d, 0xf87f, 0xf809,
0xf871, 0xf877, 0xf865, 0xf872, 0xf874, 0xf879, 0xf875, 0xf869,
@@ -117,9 +198,25 @@ u_short alt_map[NR_KEYS] = {
0xf118, 0xf210, 0xf211, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116,
0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
};
-u_short ctrl_alt_map[NR_KEYS] = {
+static unsigned short ctrl_alt_map[NR_KEYS] = {
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf811, 0xf817, 0xf805, 0xf812, 0xf814, 0xf819, 0xf815, 0xf809,
@@ -136,9 +233,25 @@ u_short ctrl_alt_map[NR_KEYS] = {
0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf20c,
0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
};
-ushort *key_maps[MAX_NR_KEYMAPS] = {
+unsigned short *key_maps[MAX_NR_KEYMAPS] = {
plain_map, shift_map, altgr_map, NULL,
ctrl_map, shift_ctrl_map, NULL, NULL,
alt_map, NULL, NULL, NULL,
@@ -223,40 +336,40 @@ char *func_table[MAX_NR_FUNC] = {
};
struct kbdiacruc accent_table[MAX_DIACR] = {
- {'`', 'A', 0300}, {'`', 'a', 0340},
- {'\'', 'A', 0301}, {'\'', 'a', 0341},
- {'^', 'A', 0302}, {'^', 'a', 0342},
- {'~', 'A', 0303}, {'~', 'a', 0343},
- {'"', 'A', 0304}, {'"', 'a', 0344},
- {'O', 'A', 0305}, {'o', 'a', 0345},
- {'0', 'A', 0305}, {'0', 'a', 0345},
- {'A', 'A', 0305}, {'a', 'a', 0345},
- {'A', 'E', 0306}, {'a', 'e', 0346},
- {',', 'C', 0307}, {',', 'c', 0347},
- {'`', 'E', 0310}, {'`', 'e', 0350},
- {'\'', 'E', 0311}, {'\'', 'e', 0351},
- {'^', 'E', 0312}, {'^', 'e', 0352},
- {'"', 'E', 0313}, {'"', 'e', 0353},
- {'`', 'I', 0314}, {'`', 'i', 0354},
- {'\'', 'I', 0315}, {'\'', 'i', 0355},
- {'^', 'I', 0316}, {'^', 'i', 0356},
- {'"', 'I', 0317}, {'"', 'i', 0357},
- {'-', 'D', 0320}, {'-', 'd', 0360},
- {'~', 'N', 0321}, {'~', 'n', 0361},
- {'`', 'O', 0322}, {'`', 'o', 0362},
- {'\'', 'O', 0323}, {'\'', 'o', 0363},
- {'^', 'O', 0324}, {'^', 'o', 0364},
- {'~', 'O', 0325}, {'~', 'o', 0365},
- {'"', 'O', 0326}, {'"', 'o', 0366},
- {'/', 'O', 0330}, {'/', 'o', 0370},
- {'`', 'U', 0331}, {'`', 'u', 0371},
- {'\'', 'U', 0332}, {'\'', 'u', 0372},
- {'^', 'U', 0333}, {'^', 'u', 0373},
- {'"', 'U', 0334}, {'"', 'u', 0374},
- {'\'', 'Y', 0335}, {'\'', 'y', 0375},
- {'T', 'H', 0336}, {'t', 'h', 0376},
- {'s', 's', 0337}, {'"', 'y', 0377},
- {'s', 'z', 0337}, {'i', 'j', 0377},
+ {'`', 'A', 0x00c0}, {'`', 'a', 0x00e0},
+ {'\'', 'A', 0x00c1}, {'\'', 'a', 0x00e1},
+ {'^', 'A', 0x00c2}, {'^', 'a', 0x00e2},
+ {'~', 'A', 0x00c3}, {'~', 'a', 0x00e3},
+ {'"', 'A', 0x00c4}, {'"', 'a', 0x00e4},
+ {'O', 'A', 0x00c5}, {'o', 'a', 0x00e5},
+ {'0', 'A', 0x00c5}, {'0', 'a', 0x00e5},
+ {'A', 'A', 0x00c5}, {'a', 'a', 0x00e5},
+ {'A', 'E', 0x00c6}, {'a', 'e', 0x00e6},
+ {',', 'C', 0x00c7}, {',', 'c', 0x00e7},
+ {'`', 'E', 0x00c8}, {'`', 'e', 0x00e8},
+ {'\'', 'E', 0x00c9}, {'\'', 'e', 0x00e9},
+ {'^', 'E', 0x00ca}, {'^', 'e', 0x00ea},
+ {'"', 'E', 0x00cb}, {'"', 'e', 0x00eb},
+ {'`', 'I', 0x00cc}, {'`', 'i', 0x00ec},
+ {'\'', 'I', 0x00cd}, {'\'', 'i', 0x00ed},
+ {'^', 'I', 0x00ce}, {'^', 'i', 0x00ee},
+ {'"', 'I', 0x00cf}, {'"', 'i', 0x00ef},
+ {'-', 'D', 0x00d0}, {'-', 'd', 0x00f0},
+ {'~', 'N', 0x00d1}, {'~', 'n', 0x00f1},
+ {'`', 'O', 0x00d2}, {'`', 'o', 0x00f2},
+ {'\'', 'O', 0x00d3}, {'\'', 'o', 0x00f3},
+ {'^', 'O', 0x00d4}, {'^', 'o', 0x00f4},
+ {'~', 'O', 0x00d5}, {'~', 'o', 0x00f5},
+ {'"', 'O', 0x00d6}, {'"', 'o', 0x00f6},
+ {'/', 'O', 0x00d8}, {'/', 'o', 0x00f8},
+ {'`', 'U', 0x00d9}, {'`', 'u', 0x00f9},
+ {'\'', 'U', 0x00da}, {'\'', 'u', 0x00fa},
+ {'^', 'U', 0x00db}, {'^', 'u', 0x00fb},
+ {'"', 'U', 0x00dc}, {'"', 'u', 0x00fc},
+ {'\'', 'Y', 0x00dd}, {'\'', 'y', 0x00fd},
+ {'T', 'H', 0x00de}, {'t', 'h', 0x00fe},
+ {'s', 's', 0x00df}, {'"', 'y', 0x00ff},
+ {'s', 'z', 0x00df}, {'i', 'j', 0x00ff},
};
unsigned int accent_table_size = 68;
diff --git a/drivers/tty/vt/defkeymap.map b/drivers/tty/vt/defkeymap.map
index 50b30cace261..37f1ac6ddfb9 100644
--- a/drivers/tty/vt/defkeymap.map
+++ b/drivers/tty/vt/defkeymap.map
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
# Default kernel keymap. This uses 7 modifier combinations.
keymaps 0-2,4-5,8,12
# Change the above line into
diff --git a/drivers/tty/vt/gen_ucs_fallback_table.py b/drivers/tty/vt/gen_ucs_fallback_table.py
new file mode 100755
index 000000000000..6e09c1cb6d4b
--- /dev/null
+++ b/drivers/tty/vt/gen_ucs_fallback_table.py
@@ -0,0 +1,360 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+#
+# Leverage Python's unidecode module to generate ucs_fallback_table.h
+#
+# The generated table maps complex characters to their simpler fallback forms
+# for a terminal display when corresponding glyphs are unavailable.
+#
+# Usage:
+# python3 gen_ucs_fallback_table.py # Generate fallback tables
+# python3 gen_ucs_fallback_table.py -o FILE # Specify output file
+
+import unicodedata
+from unidecode import unidecode
+import sys
+import argparse
+from collections import defaultdict
+
+# Try to get unidecode version
+try:
+ from importlib.metadata import version
+ unidecode_version = version('unidecode')
+except:
+ unidecode_version = 'unknown'
+
+# This script's file name
+from pathlib import Path
+this_file = Path(__file__).name
+
+# Default output file name
+DEFAULT_OUT_FILE = "ucs_fallback_table.h"
+
+# Define the range marker value
+RANGE_MARKER = 0x00
+
+def generate_fallback_map():
+ """Generate a fallback map using unidecode for all relevant Unicode points."""
+ fallback_map = {}
+
+ # Process BMP characters (0x0000 - 0xFFFF) to keep table size manageable
+ for cp in range(0x0080, 0x10000): # Skip ASCII range (0x00-0x7F)
+ char = chr(cp)
+
+ # Skip unassigned/control characters
+ try:
+ if not unicodedata.name(char, ''):
+ continue
+ except ValueError:
+ continue
+
+ # Get the unidecode transliteration
+ ascii_version = unidecode(char)
+
+ # Only store if it results in a single character mapping
+ if len(ascii_version) == 1:
+ fallback_map[cp] = ord(ascii_version)
+
+ # Apply manual overrides for special cases
+ fallback_map.update(get_special_overrides())
+
+ return fallback_map
+
+def get_special_overrides():
+ """Get special case overrides that need different handling than unidecode
+ provides... or doesn't provide at all."""
+
+ overrides = {}
+
+ # Multi-character unidecode output
+ # These map to single chars instead of unidecode's multiple-char mappings
+ # In a terminal fallback context, we need a single character rather than multiple
+ overrides[0x00C6] = ord('E') # Æ LATIN CAPITAL LETTER AE -> E (unidecode: "AE")
+ overrides[0x00E6] = ord('e') # æ LATIN SMALL LETTER AE -> e (unidecode: "ae")
+ overrides[0x0152] = ord('E') # Œ LATIN CAPITAL LIGATURE OE -> E (unidecode: "OE")
+ overrides[0x0153] = ord('e') # œ LATIN SMALL LETTER LIGATURE OE -> e (unidecode: "oe")
+ overrides[0x00DF] = ord('s') # ß LATIN SMALL LETTER SHARP S -> s (unidecode: "ss")
+
+ # Comparison operators that unidecode renders as multiple characters
+ overrides[0x2264] = ord('<') # ≤ LESS-THAN OR EQUAL TO -> < (unidecode: "<=")
+ overrides[0x2265] = ord('>') # ≥ GREATER-THAN OR EQUAL TO -> > (unidecode: ">=")
+
+ # Unidecode returns an empty string for these
+ overrides[0x2260] = ord('#') # ≠ NOT EQUAL TO -> # (unidecode: empty string)
+
+ # Quadrant block characters that unidecode doesn't map
+ for cp in range(0x2596, 0x259F+1):
+ overrides[cp] = ord('#') # ▖ ▗ ▘ ▙ etc. - map to # (unidecode: empty string)
+
+ # Directional arrows
+ # These provide better semantic meaning than unidecode's mappings
+ overrides[0x2192] = ord('>') # → RIGHTWARDS ARROW -> > (unidecode: "-")
+ overrides[0x2190] = ord('<') # ← LEFTWARDS ARROW -> < (unidecode: "-")
+ overrides[0x2191] = ord('^') # ↑ UPWARDS ARROW -> ^ (unidecode: "|")
+ overrides[0x2193] = ord('v') # ↓ DOWNWARDS ARROW -> v (unidecode: "|")
+
+ # Double arrows with their directional semantic mappings
+ overrides[0x21D0] = ord('<') # ⇐ LEFTWARDS DOUBLE ARROW -> <
+ overrides[0x21D1] = ord('^') # ⇑ UPWARDS DOUBLE ARROW -> ^
+ overrides[0x21D2] = ord('>') # ⇒ RIGHTWARDS DOUBLE ARROW -> >
+ overrides[0x21D3] = ord('v') # ⇓ DOWNWARDS DOUBLE ARROW -> v
+
+ # Halfwidth arrows
+ # These need the same treatment as their normal-width counterparts
+ overrides[0xFFE9] = ord('<') # ← HALFWIDTH LEFTWARDS ARROW -> < (unidecode: "-")
+ overrides[0xFFEA] = ord('^') # ↑ HALFWIDTH UPWARDS ARROW -> ^ (unidecode: "|")
+ overrides[0xFFEB] = ord('>') # → HALFWIDTH RIGHTWARDS ARROW -> > (unidecode: "-")
+ overrides[0xFFEC] = ord('v') # ↓ HALFWIDTH DOWNWARDS ARROW -> v (unidecode: "|")
+
+ # Currency symbols - each mapped to a representative letter
+ overrides[0x00A2] = ord('c') # ¢ CENT SIGN -> c
+ overrides[0x00A3] = ord('L') # £ POUND SIGN -> L
+ overrides[0x00A5] = ord('Y') # ¥ YEN SIGN -> Y
+ overrides[0x20AC] = ord('E') # € EURO SIGN -> E
+
+ # Symbols mapped to letters
+ overrides[0x00A7] = ord('S') # § SECTION SIGN -> S
+ overrides[0x00A9] = ord('C') # © COPYRIGHT SIGN -> C
+ overrides[0x00AE] = ord('R') # ® REGISTERED SIGN -> R
+ overrides[0x2122] = ord('T') # ™ TRADE MARK SIGN -> T
+
+ # Degree-related symbols
+ overrides[0x00B0] = ord('o') # ° DEGREE SIGN -> o
+ overrides[0x2103] = ord('C') # ℃ DEGREE CELSIUS -> C
+ overrides[0x2109] = ord('F') # ℉ DEGREE FAHRENHEIT -> F
+
+ # Angle quotation marks
+ overrides[0x00AB] = ord('<') # « LEFT-POINTING DOUBLE ANGLE QUOTATION MARK -> <
+ overrides[0x00BB] = ord('>') # » RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -> >
+
+ # Operators with circular shape
+ overrides[0x2218] = ord('o') # ∘ RING OPERATOR -> o
+ overrides[0x2219] = ord('.') # ∙ BULLET OPERATOR -> .
+
+ # Negated mathematical symbols (preserving the negation semantics)
+ # Negated symbols mapped to exclamation mark (semantically "not")
+ for cp in (0x2204, 0x2209, 0x220C, 0x2224, 0x2226, 0x226E, 0x226F, 0x2280, 0x2281, 0x2284, 0x2285):
+ overrides[cp] = ord('!') # Negated math symbols -> ! (not)
+
+ # Negated symbols mapped to hash sign (semantically "not equal")
+ for cp in (0x2241, 0x2244, 0x2249, 0x2262, 0x2268, 0x2269, 0x226D, 0x228A, 0x228B):
+ overrides[cp] = ord('#') # Negated equality symbols -> # (not equal)
+
+ # Negated arrows - all mapped to exclamation mark
+ for cp in (0x219A, 0x219B, 0x21AE, 0x21CD, 0x21CE, 0x21CF):
+ overrides[cp] = ord('!') # Negated arrows -> ! (not)
+
+ # Dashes and hyphens
+ for cp in (0x2010, 0x2011, 0x2012, 0x2013, 0x2014, 0x2015, 0x2043, 0x2052):
+ overrides[cp] = ord('-') # Dashes and hyphens -> -
+
+ # Question mark punctuation
+ for cp in (0x203D, 0x2047, 0x2048):
+ overrides[cp] = ord('?') # Question marks -> ?
+
+ # Exclamation mark punctuation
+ for cp in (0x203C, 0x2049):
+ overrides[cp] = ord('!') # Exclamation marks -> !
+
+ # Asterisk-like symbols
+ for cp in (0x2042, 0x2051, 0x2055):
+ overrides[cp] = ord('*')
+
+ # Other specific punctuation with unique mappings
+ overrides[0x201E] = ord('"') # „ DOUBLE LOW-9 QUOTATION MARK
+ overrides[0x2023] = ord('>') # ‣ TRIANGULAR BULLET
+ overrides[0x2026] = ord('.') # … HORIZONTAL ELLIPSIS
+ overrides[0x2033] = ord('"') # ″ DOUBLE PRIME
+ overrides[0x204B] = ord('P') # ⁋ REVERSED PILCROW SIGN
+ overrides[0x204C] = ord('<') # ⁌ BLACK LEFTWARDS BULLET
+ overrides[0x204D] = ord('>') # ⁍ BLACK RIGHTWARDS BULLET
+ overrides[0x204F] = ord(';') # ⁏ REVERSED SEMICOLON
+ overrides[0x205B] = ord(':') # ⁛ FOUR DOT MARK
+
+ # Check marks
+ overrides[0x2713] = ord('v') # ✓ CHECK MARK
+ overrides[0x2714] = ord('V') # ✔ HEAVY CHECK MARK
+
+ # X marks - lowercase for regular, uppercase for heavy
+ for cp in (0x2715, 0x2717):
+ overrides[cp] = ord('x') # Regular X marks -> x
+ for cp in (0x2716, 0x2718):
+ overrides[cp] = ord('X') # Heavy X marks -> X
+
+ # Stars and asterisk-like symbols mapped to '*'
+ for cp in (0x2605, 0x2606, 0x262A, 0x269D, 0x2698):
+ overrides[cp] = ord('*') # All star and asterisk symbols -> *
+ for cp in range(0x2721, 0x2746+1):
+ overrides[cp] = ord('*') # All star and asterisk symbols -> *
+ for cp in range(0x2749, 0x274B+1):
+ overrides[cp] = ord('*') # Last set of asterisk symbols -> *
+ for cp in (0x229B, 0x22C6, 0x235F, 0x2363):
+ overrides[cp] = ord('*') # Star operators -> *
+
+ # Special exclusions with fallback value of 0
+ # These will be filtered out in organize_by_pages()
+
+ # Exclude U+2028 (LINE SEPARATOR)
+ overrides[0x2028] = 0 # LINE SEPARATOR (unidecode: '\n')
+
+ # Full-width to ASCII mapping (covering all printable ASCII 33-126)
+ # 0xFF01 (!) to 0xFF5E (~) -> ASCII 33 (!) to 126 (~)
+ # Those are excluded here to reduce the table size.
+ # It is more efficient to process them programmatically in
+ # ucs.c:ucs_get_fallback().
+ for cp in range(0xFF01, 0xFF5E + 1):
+ overrides[cp] = 0 # Double-width ASCII characters
+
+ return overrides
+
+def organize_by_pages(fallback_map):
+ """Organize the fallback mappings by their high byte (page)."""
+ # Group by high byte (page)
+ page_groups = defaultdict(list)
+ for code, fallback in fallback_map.items():
+ # Skip characters with fallback value of 0 (excluded characters)
+ if fallback == 0:
+ continue
+
+ page = code >> 8 # Get the high byte (page)
+ offset = code & 0xFF # Get the low byte (offset within page)
+ page_groups[page].append((offset, fallback))
+
+ # Sort each page's entries by offset
+ for page in page_groups:
+ page_groups[page].sort()
+
+ return page_groups
+
+def compress_ranges(page_groups):
+ """Compress consecutive entries with the same fallback character into ranges.
+ A range is only compressed if it contains 3 or more consecutive entries."""
+
+ compressed_pages = {}
+
+ for page, entries in page_groups.items():
+ compressed_entries = []
+ i = 0
+ while i < len(entries):
+ start_offset, fallback = entries[i]
+
+ # Look ahead to find consecutive entries with the same fallback
+ j = i + 1
+ while (j < len(entries) and
+ entries[j][0] == entries[j-1][0] + 1 and # consecutive offsets
+ entries[j][1] == fallback): # same fallback
+ j += 1
+
+ # Calculate the range end
+ end_offset = entries[j-1][0]
+
+ # If we found a range with 3 or more entries (worth compressing)
+ if j - i >= 3:
+ # Add a range entry
+ compressed_entries.append((start_offset, RANGE_MARKER))
+ compressed_entries.append((end_offset, fallback))
+ else:
+ # Add the individual entries as is
+ for k in range(i, j):
+ compressed_entries.append(entries[k])
+
+ i = j
+
+ compressed_pages[page] = compressed_entries
+
+ return compressed_pages
+
+def cp_name(cp):
+ """Get the Unicode character name for a code point."""
+ try:
+ return unicodedata.name(chr(cp))
+ except:
+ return f"U+{cp:04X}"
+
+def generate_fallback_tables(out_file=DEFAULT_OUT_FILE):
+ """Generate the fallback character tables."""
+ # Generate fallback map using unidecode
+ fallback_map = generate_fallback_map()
+ print(f"Generated {len(fallback_map)} total fallback mappings")
+
+ # Organize by pages
+ page_groups = organize_by_pages(fallback_map)
+ print(f"Organized into {len(page_groups)} pages")
+
+ # Compress ranges
+ compressed_pages = compress_ranges(page_groups)
+ total_compressed_entries = sum(len(entries) for entries in compressed_pages.values())
+ print(f"Total compressed entries: {total_compressed_entries}")
+
+ # Create output file
+ with open(out_file, 'w') as f:
+ f.write(f"""\
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * {out_file} - Unicode character fallback table
+ *
+ * Auto-generated by {this_file}
+ *
+ * Unicode Version: {unicodedata.unidata_version}
+ * Unidecode Version: {unidecode_version}
+ *
+ * This file contains optimized tables that map complex Unicode characters
+ * to simpler fallback characters for terminal display when corresponding
+ * glyphs are unavailable.
+ */
+
+static const struct ucs_page_desc ucs_fallback_pages[] = {{
+""")
+
+ # Convert compressed_pages to a sorted list of (page, entries) tuples
+ sorted_pages = sorted(compressed_pages.items())
+
+ # Track the start index for each page
+ start_index = 0
+
+ # Write page descriptors
+ for page, entries in sorted_pages:
+ count = len(entries)
+ f.write(f"\t{{ 0x{page:02X}, {count}, {start_index} }},\n")
+ start_index += count
+
+ # Write entries array
+ f.write("""\
+};
+
+/* Page entries array (referenced by page descriptors) */
+static const struct ucs_page_entry ucs_fallback_entries[] = {
+""")
+
+ # Write all entries
+ for page, entries in sorted_pages:
+ page_hex = f"0x{page:02X}"
+ f.write(f"\t/* Entries for page {page_hex} */\n")
+
+ for i, (offset, fallback) in enumerate(entries):
+ # Convert to hex for better readability
+ offset_hex = f"0x{offset:02X}"
+ fallback_hex = f"0x{fallback:02X}"
+
+ # Handle comments
+ codepoint = (page << 8) | offset
+
+ if fallback == RANGE_MARKER:
+ comment = f"{cp_name(codepoint)} -> ..."
+ else:
+ comment = f"{cp_name(codepoint)} -> '{chr(fallback)}'"
+ f.write(f"\t{{ 0x{offset:02X}, 0x{fallback:02X} }}, /* {comment} */\n")
+
+ f.write(f"""\
+}};
+
+#define UCS_PAGE_ENTRY_RANGE_MARKER {RANGE_MARKER}
+""")
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Generate Unicode fallback character tables")
+ parser.add_argument("-o", "--output", dest="output_file", default=DEFAULT_OUT_FILE,
+ help=f"Output file name (default: {DEFAULT_OUT_FILE})")
+ args = parser.parse_args()
+
+ generate_fallback_tables(out_file=args.output_file)
diff --git a/drivers/tty/vt/gen_ucs_recompose_table.py b/drivers/tty/vt/gen_ucs_recompose_table.py
new file mode 100755
index 000000000000..4434a436ac9e
--- /dev/null
+++ b/drivers/tty/vt/gen_ucs_recompose_table.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+#
+# Leverage Python's unicodedata module to generate ucs_recompose_table.h
+#
+# The generated table maps base character + combining mark pairs to their
+# precomposed equivalents.
+#
+# Usage:
+# python3 gen_ucs_recompose_table.py # Generate with common recomposition pairs
+# python3 gen_ucs_recompose_table.py --full # Generate with all recomposition pairs
+
+import unicodedata
+import sys
+import argparse
+import textwrap
+
+# This script's file name
+from pathlib import Path
+this_file = Path(__file__).name
+
+# Default output file name
+DEFAULT_OUT_FILE = "ucs_recompose_table.h"
+
+common_recompose_description = "most commonly used Latin, Greek, and Cyrillic recomposition pairs only"
+COMMON_RECOMPOSITION_PAIRS = [
+ # Latin letters with accents - uppercase
+ (0x0041, 0x0300, 0x00C0), # A + COMBINING GRAVE ACCENT = LATIN CAPITAL LETTER A WITH GRAVE
+ (0x0041, 0x0301, 0x00C1), # A + COMBINING ACUTE ACCENT = LATIN CAPITAL LETTER A WITH ACUTE
+ (0x0041, 0x0302, 0x00C2), # A + COMBINING CIRCUMFLEX ACCENT = LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+ (0x0041, 0x0303, 0x00C3), # A + COMBINING TILDE = LATIN CAPITAL LETTER A WITH TILDE
+ (0x0041, 0x0308, 0x00C4), # A + COMBINING DIAERESIS = LATIN CAPITAL LETTER A WITH DIAERESIS
+ (0x0041, 0x030A, 0x00C5), # A + COMBINING RING ABOVE = LATIN CAPITAL LETTER A WITH RING ABOVE
+ (0x0043, 0x0327, 0x00C7), # C + COMBINING CEDILLA = LATIN CAPITAL LETTER C WITH CEDILLA
+ (0x0045, 0x0300, 0x00C8), # E + COMBINING GRAVE ACCENT = LATIN CAPITAL LETTER E WITH GRAVE
+ (0x0045, 0x0301, 0x00C9), # E + COMBINING ACUTE ACCENT = LATIN CAPITAL LETTER E WITH ACUTE
+ (0x0045, 0x0302, 0x00CA), # E + COMBINING CIRCUMFLEX ACCENT = LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+ (0x0045, 0x0308, 0x00CB), # E + COMBINING DIAERESIS = LATIN CAPITAL LETTER E WITH DIAERESIS
+ (0x0049, 0x0300, 0x00CC), # I + COMBINING GRAVE ACCENT = LATIN CAPITAL LETTER I WITH GRAVE
+ (0x0049, 0x0301, 0x00CD), # I + COMBINING ACUTE ACCENT = LATIN CAPITAL LETTER I WITH ACUTE
+ (0x0049, 0x0302, 0x00CE), # I + COMBINING CIRCUMFLEX ACCENT = LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+ (0x0049, 0x0308, 0x00CF), # I + COMBINING DIAERESIS = LATIN CAPITAL LETTER I WITH DIAERESIS
+ (0x004E, 0x0303, 0x00D1), # N + COMBINING TILDE = LATIN CAPITAL LETTER N WITH TILDE
+ (0x004F, 0x0300, 0x00D2), # O + COMBINING GRAVE ACCENT = LATIN CAPITAL LETTER O WITH GRAVE
+ (0x004F, 0x0301, 0x00D3), # O + COMBINING ACUTE ACCENT = LATIN CAPITAL LETTER O WITH ACUTE
+ (0x004F, 0x0302, 0x00D4), # O + COMBINING CIRCUMFLEX ACCENT = LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+ (0x004F, 0x0303, 0x00D5), # O + COMBINING TILDE = LATIN CAPITAL LETTER O WITH TILDE
+ (0x004F, 0x0308, 0x00D6), # O + COMBINING DIAERESIS = LATIN CAPITAL LETTER O WITH DIAERESIS
+ (0x0055, 0x0300, 0x00D9), # U + COMBINING GRAVE ACCENT = LATIN CAPITAL LETTER U WITH GRAVE
+ (0x0055, 0x0301, 0x00DA), # U + COMBINING ACUTE ACCENT = LATIN CAPITAL LETTER U WITH ACUTE
+ (0x0055, 0x0302, 0x00DB), # U + COMBINING CIRCUMFLEX ACCENT = LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+ (0x0055, 0x0308, 0x00DC), # U + COMBINING DIAERESIS = LATIN CAPITAL LETTER U WITH DIAERESIS
+ (0x0059, 0x0301, 0x00DD), # Y + COMBINING ACUTE ACCENT = LATIN CAPITAL LETTER Y WITH ACUTE
+
+ # Latin letters with accents - lowercase
+ (0x0061, 0x0300, 0x00E0), # a + COMBINING GRAVE ACCENT = LATIN SMALL LETTER A WITH GRAVE
+ (0x0061, 0x0301, 0x00E1), # a + COMBINING ACUTE ACCENT = LATIN SMALL LETTER A WITH ACUTE
+ (0x0061, 0x0302, 0x00E2), # a + COMBINING CIRCUMFLEX ACCENT = LATIN SMALL LETTER A WITH CIRCUMFLEX
+ (0x0061, 0x0303, 0x00E3), # a + COMBINING TILDE = LATIN SMALL LETTER A WITH TILDE
+ (0x0061, 0x0308, 0x00E4), # a + COMBINING DIAERESIS = LATIN SMALL LETTER A WITH DIAERESIS
+ (0x0061, 0x030A, 0x00E5), # a + COMBINING RING ABOVE = LATIN SMALL LETTER A WITH RING ABOVE
+ (0x0063, 0x0327, 0x00E7), # c + COMBINING CEDILLA = LATIN SMALL LETTER C WITH CEDILLA
+ (0x0065, 0x0300, 0x00E8), # e + COMBINING GRAVE ACCENT = LATIN SMALL LETTER E WITH GRAVE
+ (0x0065, 0x0301, 0x00E9), # e + COMBINING ACUTE ACCENT = LATIN SMALL LETTER E WITH ACUTE
+ (0x0065, 0x0302, 0x00EA), # e + COMBINING CIRCUMFLEX ACCENT = LATIN SMALL LETTER E WITH CIRCUMFLEX
+ (0x0065, 0x0308, 0x00EB), # e + COMBINING DIAERESIS = LATIN SMALL LETTER E WITH DIAERESIS
+ (0x0069, 0x0300, 0x00EC), # i + COMBINING GRAVE ACCENT = LATIN SMALL LETTER I WITH GRAVE
+ (0x0069, 0x0301, 0x00ED), # i + COMBINING ACUTE ACCENT = LATIN SMALL LETTER I WITH ACUTE
+ (0x0069, 0x0302, 0x00EE), # i + COMBINING CIRCUMFLEX ACCENT = LATIN SMALL LETTER I WITH CIRCUMFLEX
+ (0x0069, 0x0308, 0x00EF), # i + COMBINING DIAERESIS = LATIN SMALL LETTER I WITH DIAERESIS
+ (0x006E, 0x0303, 0x00F1), # n + COMBINING TILDE = LATIN SMALL LETTER N WITH TILDE
+ (0x006F, 0x0300, 0x00F2), # o + COMBINING GRAVE ACCENT = LATIN SMALL LETTER O WITH GRAVE
+ (0x006F, 0x0301, 0x00F3), # o + COMBINING ACUTE ACCENT = LATIN SMALL LETTER O WITH ACUTE
+ (0x006F, 0x0302, 0x00F4), # o + COMBINING CIRCUMFLEX ACCENT = LATIN SMALL LETTER O WITH CIRCUMFLEX
+ (0x006F, 0x0303, 0x00F5), # o + COMBINING TILDE = LATIN SMALL LETTER O WITH TILDE
+ (0x006F, 0x0308, 0x00F6), # o + COMBINING DIAERESIS = LATIN SMALL LETTER O WITH DIAERESIS
+ (0x0075, 0x0300, 0x00F9), # u + COMBINING GRAVE ACCENT = LATIN SMALL LETTER U WITH GRAVE
+ (0x0075, 0x0301, 0x00FA), # u + COMBINING ACUTE ACCENT = LATIN SMALL LETTER U WITH ACUTE
+ (0x0075, 0x0302, 0x00FB), # u + COMBINING CIRCUMFLEX ACCENT = LATIN SMALL LETTER U WITH CIRCUMFLEX
+ (0x0075, 0x0308, 0x00FC), # u + COMBINING DIAERESIS = LATIN SMALL LETTER U WITH DIAERESIS
+ (0x0079, 0x0301, 0x00FD), # y + COMBINING ACUTE ACCENT = LATIN SMALL LETTER Y WITH ACUTE
+ (0x0079, 0x0308, 0x00FF), # y + COMBINING DIAERESIS = LATIN SMALL LETTER Y WITH DIAERESIS
+
+ # Common Greek characters
+ (0x0391, 0x0301, 0x0386), # Α + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER ALPHA WITH TONOS
+ (0x0395, 0x0301, 0x0388), # Ε + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER EPSILON WITH TONOS
+ (0x0397, 0x0301, 0x0389), # Η + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER ETA WITH TONOS
+ (0x0399, 0x0301, 0x038A), # Ι + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER IOTA WITH TONOS
+ (0x039F, 0x0301, 0x038C), # Ο + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER OMICRON WITH TONOS
+ (0x03A5, 0x0301, 0x038E), # Υ + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER UPSILON WITH TONOS
+ (0x03A9, 0x0301, 0x038F), # Ω + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER OMEGA WITH TONOS
+ (0x03B1, 0x0301, 0x03AC), # α + COMBINING ACUTE ACCENT = GREEK SMALL LETTER ALPHA WITH TONOS
+ (0x03B5, 0x0301, 0x03AD), # ε + COMBINING ACUTE ACCENT = GREEK SMALL LETTER EPSILON WITH TONOS
+ (0x03B7, 0x0301, 0x03AE), # η + COMBINING ACUTE ACCENT = GREEK SMALL LETTER ETA WITH TONOS
+ (0x03B9, 0x0301, 0x03AF), # ι + COMBINING ACUTE ACCENT = GREEK SMALL LETTER IOTA WITH TONOS
+ (0x03BF, 0x0301, 0x03CC), # ο + COMBINING ACUTE ACCENT = GREEK SMALL LETTER OMICRON WITH TONOS
+ (0x03C5, 0x0301, 0x03CD), # υ + COMBINING ACUTE ACCENT = GREEK SMALL LETTER UPSILON WITH TONOS
+ (0x03C9, 0x0301, 0x03CE), # ω + COMBINING ACUTE ACCENT = GREEK SMALL LETTER OMEGA WITH TONOS
+
+ # Common Cyrillic characters
+ (0x0418, 0x0306, 0x0419), # И + COMBINING BREVE = CYRILLIC CAPITAL LETTER SHORT I
+ (0x0438, 0x0306, 0x0439), # и + COMBINING BREVE = CYRILLIC SMALL LETTER SHORT I
+ (0x0423, 0x0306, 0x040E), # У + COMBINING BREVE = CYRILLIC CAPITAL LETTER SHORT U
+ (0x0443, 0x0306, 0x045E), # у + COMBINING BREVE = CYRILLIC SMALL LETTER SHORT U
+]
+
+full_recompose_description = "all possible recomposition pairs from the Unicode BMP"
+def collect_all_recomposition_pairs():
+ """Collect all possible recomposition pairs from the Unicode data."""
+ # Map to store recomposition pairs: (base, combining) -> recomposed
+ recompose_map = {}
+
+ # Process all assigned Unicode code points in BMP (Basic Multilingual Plane)
+ # We limit to BMP (0x0000-0xFFFF) to keep our table smaller with uint16_t
+ for cp in range(0, 0x10000):
+ try:
+ char = chr(cp)
+
+ # Skip unassigned or control characters
+ if not unicodedata.name(char, ''):
+ continue
+
+ # Find decomposition
+ decomp = unicodedata.decomposition(char)
+ if not decomp or '<' in decomp: # Skip compatibility decompositions
+ continue
+
+ # Parse the decomposition
+ parts = decomp.split()
+ if len(parts) == 2: # Simple base + combining mark
+ base = int(parts[0], 16)
+ combining = int(parts[1], 16)
+
+ # Only store if both are in BMP
+ if base < 0x10000 and combining < 0x10000:
+ recompose_map[(base, combining)] = cp
+
+ except (ValueError, TypeError):
+ continue
+
+ # Convert to a list of tuples and sort for binary search
+ recompose_list = [(base, combining, recomposed)
+ for (base, combining), recomposed in recompose_map.items()]
+ recompose_list.sort()
+
+ return recompose_list
+
+def validate_common_pairs(full_list):
+ """Validate that all common pairs are in the full list.
+
+ Raises:
+ ValueError: If any common pair is missing or has a different recomposition
+ value than what's in the full table.
+ """
+ full_pairs = {(base, combining): recomposed for base, combining, recomposed in full_list}
+ for base, combining, recomposed in COMMON_RECOMPOSITION_PAIRS:
+ full_recomposed = full_pairs.get((base, combining))
+ if full_recomposed is None:
+ error_msg = f"Error: Common pair (0x{base:04X}, 0x{combining:04X}) not found in full data"
+ print(error_msg)
+ raise ValueError(error_msg)
+ elif full_recomposed != recomposed:
+ error_msg = (f"Error: Common pair (0x{base:04X}, 0x{combining:04X}) has different recomposition: "
+ f"0x{recomposed:04X} vs 0x{full_recomposed:04X}")
+ print(error_msg)
+ raise ValueError(error_msg)
+
+def generate_recomposition_table(use_full_list=False, out_file=DEFAULT_OUT_FILE):
+ """Generate the recomposition C table."""
+
+ # Collect all recomposition pairs for validation
+ full_recompose_list = collect_all_recomposition_pairs()
+
+ # Decide which list to use
+ if use_full_list:
+ print("Using full recomposition list...")
+ recompose_list = full_recompose_list
+ table_description = full_recompose_description
+ alt_list = COMMON_RECOMPOSITION_PAIRS
+ alt_description = common_recompose_description
+ else:
+ print("Using common recomposition list...")
+ # Validate that all common pairs are in the full list
+ validate_common_pairs(full_recompose_list)
+ recompose_list = sorted(COMMON_RECOMPOSITION_PAIRS)
+ table_description = common_recompose_description
+ alt_list = full_recompose_list
+ alt_description = full_recompose_description
+ generation_mode = " --full" if use_full_list else ""
+ alternative_mode = " --full" if not use_full_list else ""
+ table_description_detail = f"{table_description} ({len(recompose_list)} entries)"
+ alt_description_detail = f"{alt_description} ({len(alt_list)} entries)"
+
+ # Calculate min/max values for boundary checks
+ min_base = min(base for base, _, _ in recompose_list)
+ max_base = max(base for base, _, _ in recompose_list)
+ min_combining = min(combining for _, combining, _ in recompose_list)
+ max_combining = max(combining for _, combining, _ in recompose_list)
+
+ # Generate implementation file
+ with open(out_file, 'w') as f:
+ f.write(f"""\
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * {out_file} - Unicode character recomposition
+ *
+ * Auto-generated by {this_file}{generation_mode}
+ *
+ * Unicode Version: {unicodedata.unidata_version}
+ *
+{textwrap.fill(
+ f"This file contains a table with {table_description_detail}. " +
+ f"To generate a table with {alt_description_detail} instead, run:",
+ width=75, initial_indent=" * ", subsequent_indent=" * ")}
+ *
+ * python3 {this_file}{alternative_mode}
+ */
+
+/*
+ * Table of {table_description}
+ * Sorted by base character and then combining mark for binary search
+ */
+static const struct ucs_recomposition ucs_recomposition_table[] = {{
+""")
+
+ for base, combining, recomposed in recompose_list:
+ try:
+ base_name = unicodedata.name(chr(base))
+ combining_name = unicodedata.name(chr(combining))
+ recomposed_name = unicodedata.name(chr(recomposed))
+ comment = f"/* {base_name} + {combining_name} = {recomposed_name} */"
+ except ValueError:
+ comment = f"/* U+{base:04X} + U+{combining:04X} = U+{recomposed:04X} */"
+ f.write(f"\t{{ 0x{base:04X}, 0x{combining:04X}, 0x{recomposed:04X} }}, {comment}\n")
+
+ f.write(f"""\
+}};
+
+/*
+ * Boundary values for quick rejection
+ * These are calculated by analyzing the table during generation
+ */
+#define UCS_RECOMPOSE_MIN_BASE 0x{min_base:04X}
+#define UCS_RECOMPOSE_MAX_BASE 0x{max_base:04X}
+#define UCS_RECOMPOSE_MIN_MARK 0x{min_combining:04X}
+#define UCS_RECOMPOSE_MAX_MARK 0x{max_combining:04X}
+""")
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Generate Unicode recomposition table")
+ parser.add_argument("--full", action="store_true",
+ help="Generate a full recomposition table (default: common pairs only)")
+ parser.add_argument("-o", "--output", dest="output_file", default=DEFAULT_OUT_FILE,
+ help=f"Output file name (default: {DEFAULT_OUT_FILE})")
+ args = parser.parse_args()
+
+ generate_recomposition_table(use_full_list=args.full, out_file=args.output_file)
diff --git a/drivers/tty/vt/gen_ucs_width_table.py b/drivers/tty/vt/gen_ucs_width_table.py
new file mode 100755
index 000000000000..76e80ebeff13
--- /dev/null
+++ b/drivers/tty/vt/gen_ucs_width_table.py
@@ -0,0 +1,307 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+#
+# Leverage Python's unicodedata module to generate ucs_width_table.h
+
+import unicodedata
+import sys
+import argparse
+
+# This script's file name
+from pathlib import Path
+this_file = Path(__file__).name
+
+# Default output file name
+DEFAULT_OUT_FILE = "ucs_width_table.h"
+
+# --- Global Constants for Width Assignments ---
+
+# Known zero-width characters
+KNOWN_ZERO_WIDTH = (
+ 0x200B, # ZERO WIDTH SPACE
+ 0x200C, # ZERO WIDTH NON-JOINER
+ 0x200D, # ZERO WIDTH JOINER
+ 0x2060, # WORD JOINER
+ 0xFEFF # ZERO WIDTH NO-BREAK SPACE (BOM)
+)
+
+# Zero-width emoji modifiers and components
+# NOTE: Some of these characters would normally be single-width according to
+# East Asian Width properties, but we deliberately override them to be
+# zero-width because they function as modifiers in emoji sequences.
+EMOJI_ZERO_WIDTH = [
+ # Skin tone modifiers
+ (0x1F3FB, 0x1F3FF), # Emoji modifiers (skin tones)
+
+ # Variation selectors (note: VS16 is treated specially in vt.c)
+ (0xFE00, 0xFE0F), # Variation Selectors 1-16
+
+ # Gender and hair style modifiers
+ # These would be single-width by Unicode properties, but are zero-width
+ # when part of emoji
+ (0x2640, 0x2640), # Female sign
+ (0x2642, 0x2642), # Male sign
+ (0x26A7, 0x26A7), # Transgender symbol
+ (0x1F9B0, 0x1F9B3), # Hair components (red, curly, white, bald)
+
+ # Tag characters
+ (0xE0020, 0xE007E), # Tags
+]
+
+# Regional indicators (flag components)
+REGIONAL_INDICATORS = (0x1F1E6, 0x1F1FF) # Regional indicator symbols A-Z
+
+# Double-width emoji ranges
+#
+# Many emoji characters are classified as single-width according to Unicode
+# Standard Annex #11 East Asian Width property (N or Neutral), but we
+# deliberately override them to be double-width. References:
+# 1. Unicode Technical Standard #51: Unicode Emoji
+# (https://www.unicode.org/reports/tr51/)
+# 2. Principle of "emoji presentation" in WHATWG CSS Text specification
+# (https://drafts.csswg.org/css-text-3/#character-properties)
+# 3. Terminal emulator implementations (iTerm2, Windows Terminal, etc.) which
+# universally render emoji as double-width characters regardless of their
+# Unicode EAW property
+# 4. W3C Work Item: Requirements for Japanese Text Layout - Section 3.8.1
+# Emoji width (https://www.w3.org/TR/jlreq/)
+EMOJI_RANGES = [
+ (0x1F000, 0x1F02F), # Mahjong Tiles (EAW: N, but displayed as double-width)
+ (0x1F0A0, 0x1F0FF), # Playing Cards (EAW: N, but displayed as double-width)
+ (0x1F300, 0x1F5FF), # Miscellaneous Symbols and Pictographs
+ (0x1F600, 0x1F64F), # Emoticons
+ (0x1F680, 0x1F6FF), # Transport and Map Symbols
+ (0x1F700, 0x1F77F), # Alchemical Symbols
+ (0x1F780, 0x1F7FF), # Geometric Shapes Extended
+ (0x1F800, 0x1F8FF), # Supplemental Arrows-C
+ (0x1F900, 0x1F9FF), # Supplemental Symbols and Pictographs
+ (0x1FA00, 0x1FA6F), # Chess Symbols
+ (0x1FA70, 0x1FAFF), # Symbols and Pictographs Extended-A
+]
+
+def create_width_tables():
+ """
+ Creates Unicode character width tables and returns the data structures.
+
+ Returns:
+ tuple: (zero_width_ranges, double_width_ranges)
+ """
+
+ # Width data mapping
+ width_map = {} # Maps code points to width (0, 1, 2)
+
+ # Mark emoji modifiers as zero-width
+ for start, end in EMOJI_ZERO_WIDTH:
+ for cp in range(start, end + 1):
+ width_map[cp] = 0
+
+ # Mark all regional indicators as single-width as they are usually paired
+ # providing a combined width of 2 when displayed together.
+ start, end = REGIONAL_INDICATORS
+ for cp in range(start, end + 1):
+ width_map[cp] = 1
+
+ # Process all assigned Unicode code points (Basic Multilingual Plane +
+ # Supplementary Planes) Range 0x0 to 0x10FFFF (the full Unicode range)
+ for block_start in range(0, 0x110000, 0x1000):
+ block_end = block_start + 0x1000
+ for cp in range(block_start, block_end):
+ try:
+ char = chr(cp)
+
+ # Skip if already processed
+ if cp in width_map:
+ continue
+
+ # Check for combining marks and a format characters
+ category = unicodedata.category(char)
+
+ # Combining marks
+ if category.startswith('M'):
+ width_map[cp] = 0
+ continue
+
+ # Format characters
+ # Since we have no support for bidirectional text, all format
+ # characters (category Cf) can be treated with width 0 (zero)
+ # for simplicity, as they don't need to occupy visual space
+ # in a non-bidirectional text environment.
+ if category == 'Cf':
+ width_map[cp] = 0
+ continue
+
+ # Known zero-width characters
+ if cp in KNOWN_ZERO_WIDTH:
+ width_map[cp] = 0
+ continue
+
+ # Use East Asian Width property
+ eaw = unicodedata.east_asian_width(char)
+ if eaw in ('F', 'W'): # Fullwidth or Wide
+ width_map[cp] = 2
+ elif eaw in ('Na', 'H', 'N', 'A'): # Narrow, Halfwidth, Neutral, Ambiguous
+ width_map[cp] = 1
+ else:
+ # Default to single-width for unknown
+ width_map[cp] = 1
+
+ except (ValueError, OverflowError):
+ # Skip invalid code points
+ continue
+
+ # Process Emoji - generally double-width
+ for start, end in EMOJI_RANGES:
+ for cp in range(start, end + 1):
+ if cp not in width_map or width_map[cp] != 0: # Don't override zero-width
+ try:
+ char = chr(cp)
+ width_map[cp] = 2
+ except (ValueError, OverflowError):
+ continue
+
+ # Optimize to create range tables
+ def ranges_optimize(width_data, target_width):
+ points = sorted([cp for cp, width in width_data.items() if width == target_width])
+ if not points:
+ return []
+
+ # Group consecutive code points into ranges
+ ranges = []
+ start = points[0]
+ prev = start
+
+ for cp in points[1:]:
+ if cp > prev + 1:
+ ranges.append((start, prev))
+ start = cp
+ prev = cp
+
+ # Add the last range
+ ranges.append((start, prev))
+ return ranges
+
+ # Extract ranges for each width
+ zero_width_ranges = ranges_optimize(width_map, 0)
+ double_width_ranges = ranges_optimize(width_map, 2)
+
+ return zero_width_ranges, double_width_ranges
+
+def write_tables(zero_width_ranges, double_width_ranges, out_file=DEFAULT_OUT_FILE):
+ """
+ Write the generated tables to C header file.
+
+ Args:
+ zero_width_ranges: List of (start, end) ranges for zero-width characters
+ double_width_ranges: List of (start, end) ranges for double-width characters
+ out_file: Output file name (default: DEFAULT_OUT_FILE)
+ """
+
+ # Function to split ranges into BMP (16-bit) and non-BMP (above 16-bit)
+ def split_ranges_by_size(ranges):
+ bmp_ranges = []
+ non_bmp_ranges = []
+
+ for start, end in ranges:
+ if end <= 0xFFFF:
+ bmp_ranges.append((start, end))
+ elif start > 0xFFFF:
+ non_bmp_ranges.append((start, end))
+ else:
+ # Split the range at 0xFFFF
+ bmp_ranges.append((start, 0xFFFF))
+ non_bmp_ranges.append((0x10000, end))
+
+ return bmp_ranges, non_bmp_ranges
+
+ # Split ranges into BMP and non-BMP
+ zero_width_bmp, zero_width_non_bmp = split_ranges_by_size(zero_width_ranges)
+ double_width_bmp, double_width_non_bmp = split_ranges_by_size(double_width_ranges)
+
+ # Function to generate code point description comments
+ def get_code_point_comment(start, end):
+ try:
+ start_char_desc = unicodedata.name(chr(start))
+ if start == end:
+ return f"/* {start_char_desc} */"
+ else:
+ end_char_desc = unicodedata.name(chr(end))
+ return f"/* {start_char_desc} - {end_char_desc} */"
+ except:
+ if start == end:
+ return f"/* U+{start:04X} */"
+ else:
+ return f"/* U+{start:04X} - U+{end:04X} */"
+
+ # Generate C tables
+ with open(out_file, 'w') as f:
+ f.write(f"""\
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * {out_file} - Unicode character width
+ *
+ * Auto-generated by {this_file}
+ *
+ * Unicode Version: {unicodedata.unidata_version}
+ */
+
+/* Zero-width character ranges (BMP - Basic Multilingual Plane, U+0000 to U+FFFF) */
+static const struct ucs_interval16 ucs_zero_width_bmp_ranges[] = {{
+""")
+
+ for start, end in zero_width_bmp:
+ comment = get_code_point_comment(start, end)
+ f.write(f"\t{{ 0x{start:04X}, 0x{end:04X} }}, {comment}\n")
+
+ f.write("""\
+};
+
+/* Zero-width character ranges (non-BMP, U+10000 and above) */
+static const struct ucs_interval32 ucs_zero_width_non_bmp_ranges[] = {
+""")
+
+ for start, end in zero_width_non_bmp:
+ comment = get_code_point_comment(start, end)
+ f.write(f"\t{{ 0x{start:05X}, 0x{end:05X} }}, {comment}\n")
+
+ f.write("""\
+};
+
+/* Double-width character ranges (BMP - Basic Multilingual Plane, U+0000 to U+FFFF) */
+static const struct ucs_interval16 ucs_double_width_bmp_ranges[] = {
+""")
+
+ for start, end in double_width_bmp:
+ comment = get_code_point_comment(start, end)
+ f.write(f"\t{{ 0x{start:04X}, 0x{end:04X} }}, {comment}\n")
+
+ f.write("""\
+};
+
+/* Double-width character ranges (non-BMP, U+10000 and above) */
+static const struct ucs_interval32 ucs_double_width_non_bmp_ranges[] = {
+""")
+
+ for start, end in double_width_non_bmp:
+ comment = get_code_point_comment(start, end)
+ f.write(f"\t{{ 0x{start:05X}, 0x{end:05X} }}, {comment}\n")
+
+ f.write("};\n")
+
+if __name__ == "__main__":
+ # Parse command line arguments
+ parser = argparse.ArgumentParser(description="Generate Unicode width tables")
+ parser.add_argument("-o", "--output", dest="output_file", default=DEFAULT_OUT_FILE,
+ help=f"Output file name (default: {DEFAULT_OUT_FILE})")
+ args = parser.parse_args()
+
+ # Write tables to header file
+ zero_width_ranges, double_width_ranges = create_width_tables()
+ write_tables(zero_width_ranges, double_width_ranges, out_file=args.output_file)
+
+ # Print summary
+ zero_width_count = sum(end - start + 1 for start, end in zero_width_ranges)
+ double_width_count = sum(end - start + 1 for start, end in double_width_ranges)
+ print(f"Generated {args.output_file} with:")
+ print(f"- {len(zero_width_ranges)} zero-width ranges covering ~{zero_width_count} code points")
+ print(f"- {len(double_width_ranges)} double-width ranges covering ~{double_width_count} code points")
+ print(f"- Unicode Version: {unicodedata.unidata_version}")
diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
index a9af1b9ae160..d65fc60dd7be 100644
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* Written for linux by Johan Myreen as a translation from
* the assembly version by Linus (with diacriticals added)
@@ -25,33 +26,34 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/consolemap.h>
-#include <linux/module.h>
-#include <linux/sched.h>
-#include <linux/tty.h>
-#include <linux/tty_flip.h>
-#include <linux/mm.h>
-#include <linux/string.h>
#include <linux/init.h>
-#include <linux/slab.h>
-
-#include <linux/kbd_kern.h>
-#include <linux/kbd_diacr.h>
-#include <linux/vt_kern.h>
#include <linux/input.h>
-#include <linux/reboot.h>
-#include <linux/notifier.h>
#include <linux/jiffies.h>
+#include <linux/kbd_diacr.h>
+#include <linux/kbd_kern.h>
+#include <linux/leds.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/nospec.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/sched/debug.h>
+#include <linux/sched/signal.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/tty_flip.h>
+#include <linux/tty.h>
#include <linux/uaccess.h>
+#include <linux/vt_kern.h>
#include <asm/irq_regs.h>
-extern void ctrl_alt_del(void);
-
/*
* Exported functions/variables
*/
-#define KBD_DEFMODE ((1 << VC_REPEAT) | (1 << VC_META))
+#define KBD_DEFMODE (BIT(VC_REPEAT) | BIT(VC_META))
#if defined(CONFIG_X86) || defined(CONFIG_PARISC)
#include <asm/kbdleds.h>
@@ -109,34 +111,49 @@ static struct kbd_struct kbd_table[MAX_NR_CONSOLES];
static struct kbd_struct *kbd = kbd_table;
/* maximum values each key_handler can handle */
-static const int max_vals[] = {
- 255, ARRAY_SIZE(func_table) - 1, ARRAY_SIZE(fn_handler) - 1, NR_PAD - 1,
- NR_DEAD - 1, 255, 3, NR_SHIFT - 1, 255, NR_ASCII - 1, NR_LOCK - 1,
- 255, NR_LOCK - 1, 255, NR_BRL - 1
+static const unsigned char max_vals[] = {
+ [ KT_LATIN ] = 255,
+ [ KT_FN ] = ARRAY_SIZE(func_table) - 1,
+ [ KT_SPEC ] = ARRAY_SIZE(fn_handler) - 1,
+ [ KT_PAD ] = NR_PAD - 1,
+ [ KT_DEAD ] = NR_DEAD - 1,
+ [ KT_CONS ] = 255,
+ [ KT_CUR ] = 3,
+ [ KT_SHIFT ] = NR_SHIFT - 1,
+ [ KT_META ] = 255,
+ [ KT_ASCII ] = NR_ASCII - 1,
+ [ KT_LOCK ] = NR_LOCK - 1,
+ [ KT_LETTER ] = 255,
+ [ KT_SLOCK ] = NR_LOCK - 1,
+ [ KT_DEAD2 ] = 255,
+ [ KT_BRL ] = NR_BRL - 1,
};
static const int NR_TYPES = ARRAY_SIZE(max_vals);
+static void kbd_bh(struct tasklet_struct *unused);
+static DECLARE_TASKLET_DISABLED(keyboard_tasklet, kbd_bh);
+
static struct input_handler kbd_handler;
static DEFINE_SPINLOCK(kbd_event_lock);
static DEFINE_SPINLOCK(led_lock);
-static unsigned long key_down[BITS_TO_LONGS(KEY_CNT)]; /* keyboard key bitmap */
+static DEFINE_SPINLOCK(func_buf_lock); /* guard 'func_buf' and friends */
+static DECLARE_BITMAP(key_down, KEY_CNT); /* keyboard key bitmap */
static unsigned char shift_down[NR_SHIFT]; /* shift state counters.. */
static bool dead_key_next;
-static int npadch = -1; /* -1 or number assembled on pad */
+
+/* Handles a number being assembled on the number pad */
+static bool npadch_active;
+static unsigned int npadch_value;
+
static unsigned int diacr;
-static char rep; /* flag telling character repeat */
+static bool rep; /* flag telling character repeat */
static int shift_state = 0;
-static unsigned char ledstate = 0xff; /* undefined */
+static unsigned int ledstate = -1U; /* undefined */
static unsigned char ledioctl;
-
-static struct ledptr {
- unsigned int *addr;
- unsigned int mask;
- unsigned char valid:1;
-} ledptrs[3];
+static bool vt_switch;
/*
* Notifier list for console keyboard events
@@ -247,18 +264,18 @@ static int kd_sound_helper(struct input_handle *handle, void *data)
return 0;
}
-static void kd_nosound(unsigned long ignored)
+static void kd_nosound(struct timer_list *unused)
{
static unsigned int zero;
input_handler_for_each_handle(&kbd_handler, &zero, kd_sound_helper);
}
-static DEFINE_TIMER(kd_mksound_timer, kd_nosound, 0, 0);
+static DEFINE_TIMER(kd_mksound_timer, kd_nosound);
void kd_mksound(unsigned int hz, unsigned int ticks)
{
- del_timer_sync(&kd_mksound_timer);
+ timer_delete_sync(&kd_mksound_timer);
input_handler_for_each_handle(&kbd_handler, &hz, kd_sound_helper);
@@ -274,30 +291,30 @@ EXPORT_SYMBOL(kd_mksound);
static int kbd_rate_helper(struct input_handle *handle, void *data)
{
struct input_dev *dev = handle->dev;
- struct kbd_repeat *rep = data;
+ struct kbd_repeat *rpt = data;
if (test_bit(EV_REP, dev->evbit)) {
- if (rep[0].delay > 0)
+ if (rpt[0].delay > 0)
input_inject_event(handle,
- EV_REP, REP_DELAY, rep[0].delay);
- if (rep[0].period > 0)
+ EV_REP, REP_DELAY, rpt[0].delay);
+ if (rpt[0].period > 0)
input_inject_event(handle,
- EV_REP, REP_PERIOD, rep[0].period);
+ EV_REP, REP_PERIOD, rpt[0].period);
- rep[1].delay = dev->rep[REP_DELAY];
- rep[1].period = dev->rep[REP_PERIOD];
+ rpt[1].delay = dev->rep[REP_DELAY];
+ rpt[1].period = dev->rep[REP_PERIOD];
}
return 0;
}
-int kbd_rate(struct kbd_repeat *rep)
+int kbd_rate(struct kbd_repeat *rpt)
{
- struct kbd_repeat data[2] = { *rep };
+ struct kbd_repeat data[2] = { *rpt };
input_handler_for_each_handle(&kbd_handler, data, kbd_rate_helper);
- *rep = data[1]; /* Copy currently used settings */
+ *rpt = data[1]; /* Copy currently used settings */
return 0;
}
@@ -308,16 +325,13 @@ int kbd_rate(struct kbd_repeat *rep)
static void put_queue(struct vc_data *vc, int ch)
{
tty_insert_flip_char(&vc->port, ch, 0);
- tty_schedule_flip(&vc->port);
+ tty_flip_buffer_push(&vc->port);
}
-static void puts_queue(struct vc_data *vc, char *cp)
+static void puts_queue(struct vc_data *vc, const char *cp)
{
- while (*cp) {
- tty_insert_flip_char(&vc->port, *cp, 0);
- cp++;
- }
- tty_schedule_flip(&vc->port);
+ tty_insert_flip_string(&vc->port, cp, strlen(cp));
+ tty_flip_buffer_push(&vc->port);
}
static void applkey(struct vc_data *vc, int key, char mode)
@@ -362,6 +376,23 @@ static void to_utf8(struct vc_data *vc, uint c)
}
}
+static void put_queue_utf8(struct vc_data *vc, u32 value)
+{
+ if (kbd->kbdmode == VC_UNICODE)
+ to_utf8(vc, value);
+ else {
+ int c = conv_uni_to_8bit(value);
+ if (c != -1)
+ put_queue(vc, c);
+ }
+}
+
+/* FIXME: review locking for vt.c callers */
+static void set_leds(void)
+{
+ tasklet_schedule(&keyboard_tasklet);
+}
+
/*
* Called after returning from RAW mode or when changing consoles - recompute
* shift_down[] and shift_state from key_down[] maybe called when keymap is
@@ -371,44 +402,38 @@ static void to_utf8(struct vc_data *vc, uint c)
static void do_compute_shiftstate(void)
{
- unsigned int i, j, k, sym, val;
+ unsigned int k, sym, val;
shift_state = 0;
memset(shift_down, 0, sizeof(shift_down));
- for (i = 0; i < ARRAY_SIZE(key_down); i++) {
-
- if (!key_down[i])
+ for_each_set_bit(k, key_down, min(NR_KEYS, KEY_CNT)) {
+ sym = U(key_maps[0][k]);
+ if (KTYP(sym) != KT_SHIFT && KTYP(sym) != KT_SLOCK)
continue;
- k = i * BITS_PER_LONG;
+ val = KVAL(sym);
+ if (val == KVAL(K_CAPSSHIFT))
+ val = KVAL(K_SHIFT);
- for (j = 0; j < BITS_PER_LONG; j++, k++) {
-
- if (!test_bit(k, key_down))
- continue;
-
- sym = U(key_maps[0][k]);
- if (KTYP(sym) != KT_SHIFT && KTYP(sym) != KT_SLOCK)
- continue;
-
- val = KVAL(sym);
- if (val == KVAL(K_CAPSSHIFT))
- val = KVAL(K_SHIFT);
-
- shift_down[val]++;
- shift_state |= (1 << val);
- }
+ shift_down[val]++;
+ shift_state |= BIT(val);
}
}
/* We still have to export this method to vt.c */
-void compute_shiftstate(void)
+void vt_set_leds_compute_shiftstate(void)
{
- unsigned long flags;
- spin_lock_irqsave(&kbd_event_lock, flags);
+ /*
+ * When VT is switched, the keyboard led needs to be set once.
+ * Ensure that after the switch is completed, the state of the
+ * keyboard LED is consistent with the state of the keyboard lock.
+ */
+ vt_switch = true;
+ set_leds();
+
+ guard(spinlock_irqsave)(&kbd_event_lock);
do_compute_shiftstate();
- spin_unlock_irqrestore(&kbd_event_lock, flags);
}
/*
@@ -437,13 +462,7 @@ static unsigned int handle_diacr(struct vc_data *vc, unsigned int ch)
if (ch == ' ' || ch == (BRL_UC_ROW|0) || ch == d)
return d;
- if (kbd->kbdmode == VC_UNICODE)
- to_utf8(vc, d);
- else {
- int c = conv_uni_to_8bit(d);
- if (c != -1)
- put_queue(vc, c);
- }
+ put_queue_utf8(vc, d);
return ch;
}
@@ -454,19 +473,13 @@ static unsigned int handle_diacr(struct vc_data *vc, unsigned int ch)
static void fn_enter(struct vc_data *vc)
{
if (diacr) {
- if (kbd->kbdmode == VC_UNICODE)
- to_utf8(vc, diacr);
- else {
- int c = conv_uni_to_8bit(diacr);
- if (c != -1)
- put_queue(vc, c);
- }
+ put_queue_utf8(vc, diacr);
diacr = 0;
}
- put_queue(vc, 13);
+ put_queue(vc, '\r');
if (vc_kbd_mode(kbd, VC_CRLF))
- put_queue(vc, 10);
+ put_queue(vc, '\n');
}
static void fn_caps_toggle(struct vc_data *vc)
@@ -505,7 +518,7 @@ static void fn_hold(struct vc_data *vc)
* these routines are also activated by ^S/^Q.
* (And SCROLLOCK can also be set by the ioctl KDSKBLED.)
*/
- if (tty->stopped)
+ if (tty->flow.stopped)
start_tty(tty);
else
stop_tty(tty);
@@ -574,7 +587,7 @@ static void fn_inc_console(struct vc_data *vc)
static void fn_send_intr(struct vc_data *vc)
{
tty_insert_flip_char(&vc->port, 0, TTY_BREAK);
- tty_schedule_flip(&vc->port);
+ tty_flip_buffer_push(&vc->port);
}
static void fn_scroll_forw(struct vc_data *vc)
@@ -584,12 +597,12 @@ static void fn_scroll_forw(struct vc_data *vc)
static void fn_scroll_back(struct vc_data *vc)
{
- scrollback(vc, 0);
+ scrollback(vc);
}
static void fn_show_mem(struct vc_data *vc)
{
- show_mem(0);
+ show_mem();
}
static void fn_show_state(struct vc_data *vc)
@@ -609,13 +622,12 @@ static void fn_compose(struct vc_data *vc)
static void fn_spawn_con(struct vc_data *vc)
{
- spin_lock(&vt_spawn_con.lock);
+ guard(spinlock)(&vt_spawn_con.lock);
if (vt_spawn_con.pid)
if (kill_pid(vt_spawn_con.pid, vt_spawn_con.sig, 1)) {
put_pid(vt_spawn_con.pid);
vt_spawn_con.pid = NULL;
}
- spin_unlock(&vt_spawn_con.lock);
}
static void fn_SAK(struct vc_data *vc)
@@ -668,13 +680,7 @@ static void k_unicode(struct vc_data *vc, unsigned int value, char up_flag)
diacr = value;
return;
}
- if (kbd->kbdmode == VC_UNICODE)
- to_utf8(vc, value);
- else {
- int c = conv_uni_to_8bit(value);
- if (c != -1)
- put_queue(vc, c);
- }
+ put_queue_utf8(vc, value);
}
/*
@@ -705,7 +711,35 @@ static void k_dead2(struct vc_data *vc, unsigned char value, char up_flag)
*/
static void k_dead(struct vc_data *vc, unsigned char value, char up_flag)
{
- static const unsigned char ret_diacr[NR_DEAD] = {'`', '\'', '^', '~', '"', ',' };
+ static const unsigned char ret_diacr[NR_DEAD] = {
+ '`', /* dead_grave */
+ '\'', /* dead_acute */
+ '^', /* dead_circumflex */
+ '~', /* dead_tilda */
+ '"', /* dead_diaeresis */
+ ',', /* dead_cedilla */
+ '_', /* dead_macron */
+ 'U', /* dead_breve */
+ '.', /* dead_abovedot */
+ '*', /* dead_abovering */
+ '=', /* dead_doubleacute */
+ 'c', /* dead_caron */
+ 'k', /* dead_ogonek */
+ 'i', /* dead_iota */
+ '#', /* dead_voiced_sound */
+ 'o', /* dead_semivoiced_sound */
+ '!', /* dead_belowdot */
+ '?', /* dead_hook */
+ '+', /* dead_horn */
+ '-', /* dead_stroke */
+ ')', /* dead_abovecomma */
+ '(', /* dead_abovereversedcomma */
+ ':', /* dead_doublegrave */
+ 'n', /* dead_invertedbreve */
+ ';', /* dead_belowcomma */
+ '$', /* dead_currency */
+ '@', /* dead_greek */
+ };
k_deadunicode(vc, ret_diacr[value], up_flag);
}
@@ -724,6 +758,7 @@ static void k_fn(struct vc_data *vc, unsigned char value, char up_flag)
return;
if ((unsigned)value < ARRAY_SIZE(func_table)) {
+ guard(spinlock_irqsave)(&func_buf_lock);
if (func_table[value])
puts_queue(vc, func_table[value]);
} else
@@ -796,7 +831,7 @@ static void k_pad(struct vc_data *vc, unsigned char value, char up_flag)
put_queue(vc, pad_chars[value]);
if (value == KVAL(K_PENTER) && vc_kbd_mode(kbd, VC_CRLF))
- put_queue(vc, 10);
+ put_queue(vc, '\n');
}
static void k_shift(struct vc_data *vc, unsigned char value, char up_flag)
@@ -826,17 +861,17 @@ static void k_shift(struct vc_data *vc, unsigned char value, char up_flag)
shift_down[value]++;
if (shift_down[value])
- shift_state |= (1 << value);
+ shift_state |= BIT(value);
else
- shift_state &= ~(1 << value);
+ shift_state &= ~BIT(value);
/* kludge */
- if (up_flag && shift_state != old_state && npadch != -1) {
+ if (up_flag && shift_state != old_state && npadch_active) {
if (kbd->kbdmode == VC_UNICODE)
- to_utf8(vc, npadch);
+ to_utf8(vc, npadch_value);
else
- put_queue(vc, npadch & 0xff);
- npadch = -1;
+ put_queue(vc, npadch_value & 0xff);
+ npadch_active = false;
}
}
@@ -849,12 +884,12 @@ static void k_meta(struct vc_data *vc, unsigned char value, char up_flag)
put_queue(vc, '\033');
put_queue(vc, value);
} else
- put_queue(vc, value | 0x80);
+ put_queue(vc, value | BIT(7));
}
static void k_ascii(struct vc_data *vc, unsigned char value, char up_flag)
{
- int base;
+ unsigned int base;
if (up_flag)
return;
@@ -868,10 +903,12 @@ static void k_ascii(struct vc_data *vc, unsigned char value, char up_flag)
base = 16;
}
- if (npadch == -1)
- npadch = value;
- else
- npadch = npadch * base + value;
+ if (!npadch_active) {
+ npadch_value = 0;
+ npadch_active = true;
+ }
+
+ npadch_value = npadch_value * base + value;
}
static void k_lock(struct vc_data *vc, unsigned char value, char up_flag)
@@ -930,7 +967,7 @@ static void k_brl(struct vc_data *vc, unsigned char value, char up_flag)
if (kbd->kbdmode != VC_UNICODE) {
if (!up_flag)
- pr_warning("keyboard mode must be unicode for braille patterns\n");
+ pr_warn("keyboard mode must be unicode for braille patterns\n");
return;
}
@@ -943,7 +980,7 @@ static void k_brl(struct vc_data *vc, unsigned char value, char up_flag)
return;
if (!up_flag) {
- pressed |= 1 << (value - 1);
+ pressed |= BIT(value - 1);
if (!brl_timeout)
committing = pressed;
} else if (brl_timeout) {
@@ -953,7 +990,7 @@ static void k_brl(struct vc_data *vc, unsigned char value, char up_flag)
committing = pressed;
releasestart = jiffies;
}
- pressed &= ~(1 << (value - 1));
+ pressed &= ~BIT(value - 1);
if (!pressed && committing) {
k_brlcommit(vc, committing, 0);
committing = 0;
@@ -963,71 +1000,158 @@ static void k_brl(struct vc_data *vc, unsigned char value, char up_flag)
k_brlcommit(vc, committing, 0);
committing = 0;
}
- pressed &= ~(1 << (value - 1));
+ pressed &= ~BIT(value - 1);
}
}
-/*
- * The leds display either (i) the status of NumLock, CapsLock, ScrollLock,
- * or (ii) whatever pattern of lights people want to show using KDSETLED,
- * or (iii) specified bits of specified words in kernel memory.
- */
-static unsigned char getledstate(void)
+#if IS_ENABLED(CONFIG_INPUT_LEDS) && IS_ENABLED(CONFIG_LEDS_TRIGGERS)
+
+struct kbd_led_trigger {
+ struct led_trigger trigger;
+ unsigned int mask;
+};
+
+static int kbd_led_trigger_activate(struct led_classdev *cdev)
{
- return ledstate;
+ struct kbd_led_trigger *trigger =
+ container_of(cdev->trigger, struct kbd_led_trigger, trigger);
+
+ tasklet_disable(&keyboard_tasklet);
+ if (ledstate != -1U)
+ led_set_brightness(cdev, ledstate & trigger->mask ? LED_FULL : LED_OFF);
+ tasklet_enable(&keyboard_tasklet);
+
+ return 0;
}
-void setledstate(struct kbd_struct *kbd, unsigned int led)
+#define KBD_LED_TRIGGER(_led_bit, _name) { \
+ .trigger = { \
+ .name = _name, \
+ .activate = kbd_led_trigger_activate, \
+ }, \
+ .mask = BIT(_led_bit), \
+ }
+
+#define KBD_LOCKSTATE_TRIGGER(_led_bit, _name) \
+ KBD_LED_TRIGGER((_led_bit) + 8, _name)
+
+static struct kbd_led_trigger kbd_led_triggers[] = {
+ KBD_LED_TRIGGER(VC_SCROLLOCK, "kbd-scrolllock"),
+ KBD_LED_TRIGGER(VC_NUMLOCK, "kbd-numlock"),
+ KBD_LED_TRIGGER(VC_CAPSLOCK, "kbd-capslock"),
+ KBD_LED_TRIGGER(VC_KANALOCK, "kbd-kanalock"),
+
+ KBD_LOCKSTATE_TRIGGER(VC_SHIFTLOCK, "kbd-shiftlock"),
+ KBD_LOCKSTATE_TRIGGER(VC_ALTGRLOCK, "kbd-altgrlock"),
+ KBD_LOCKSTATE_TRIGGER(VC_CTRLLOCK, "kbd-ctrllock"),
+ KBD_LOCKSTATE_TRIGGER(VC_ALTLOCK, "kbd-altlock"),
+ KBD_LOCKSTATE_TRIGGER(VC_SHIFTLLOCK, "kbd-shiftllock"),
+ KBD_LOCKSTATE_TRIGGER(VC_SHIFTRLOCK, "kbd-shiftrlock"),
+ KBD_LOCKSTATE_TRIGGER(VC_CTRLLLOCK, "kbd-ctrlllock"),
+ KBD_LOCKSTATE_TRIGGER(VC_CTRLRLOCK, "kbd-ctrlrlock"),
+};
+
+static void kbd_propagate_led_state(unsigned int old_state,
+ unsigned int new_state)
{
- unsigned long flags;
- spin_lock_irqsave(&led_lock, flags);
- if (!(led & ~7)) {
- ledioctl = led;
- kbd->ledmode = LED_SHOW_IOCTL;
- } else
- kbd->ledmode = LED_SHOW_FLAGS;
+ struct kbd_led_trigger *trigger;
+ unsigned int changed = old_state ^ new_state;
+ int i;
- set_leds();
- spin_unlock_irqrestore(&led_lock, flags);
+ for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); i++) {
+ trigger = &kbd_led_triggers[i];
+
+ if (changed & trigger->mask)
+ led_trigger_event(&trigger->trigger,
+ new_state & trigger->mask ?
+ LED_FULL : LED_OFF);
+ }
}
-static inline unsigned char getleds(void)
+static int kbd_update_leds_helper(struct input_handle *handle, void *data)
{
- struct kbd_struct *kbd = kbd_table + fg_console;
- unsigned char leds;
- int i;
+ unsigned int led_state = *(unsigned int *)data;
- if (kbd->ledmode == LED_SHOW_IOCTL)
- return ledioctl;
+ if (test_bit(EV_LED, handle->dev->evbit))
+ kbd_propagate_led_state(~led_state, led_state);
- leds = kbd->ledflagstate;
+ return 0;
+}
- if (kbd->ledmode == LED_SHOW_MEM) {
- for (i = 0; i < 3; i++)
- if (ledptrs[i].valid) {
- if (*ledptrs[i].addr & ledptrs[i].mask)
- leds |= (1 << i);
- else
- leds &= ~(1 << i);
- }
+static void kbd_init_leds(void)
+{
+ int error;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); i++) {
+ error = led_trigger_register(&kbd_led_triggers[i].trigger);
+ if (error)
+ pr_err("error %d while registering trigger %s\n",
+ error, kbd_led_triggers[i].trigger.name);
}
- return leds;
}
+#else
+
static int kbd_update_leds_helper(struct input_handle *handle, void *data)
{
- unsigned char leds = *(unsigned char *)data;
+ unsigned int leds = *(unsigned int *)data;
if (test_bit(EV_LED, handle->dev->evbit)) {
- input_inject_event(handle, EV_LED, LED_SCROLLL, !!(leds & 0x01));
- input_inject_event(handle, EV_LED, LED_NUML, !!(leds & 0x02));
- input_inject_event(handle, EV_LED, LED_CAPSL, !!(leds & 0x04));
+ input_inject_event(handle, EV_LED, LED_SCROLLL, !!(leds & BIT(0)));
+ input_inject_event(handle, EV_LED, LED_NUML, !!(leds & BIT(1)));
+ input_inject_event(handle, EV_LED, LED_CAPSL, !!(leds & BIT(2)));
input_inject_event(handle, EV_SYN, SYN_REPORT, 0);
}
return 0;
}
+static void kbd_propagate_led_state(unsigned int old_state,
+ unsigned int new_state)
+{
+ input_handler_for_each_handle(&kbd_handler, &new_state,
+ kbd_update_leds_helper);
+}
+
+static void kbd_init_leds(void)
+{
+}
+
+#endif
+
+/*
+ * The leds display either (i) the status of NumLock, CapsLock, ScrollLock,
+ * or (ii) whatever pattern of lights people want to show using KDSETLED,
+ * or (iii) specified bits of specified words in kernel memory.
+ */
+static unsigned char getledstate(void)
+{
+ return ledstate & 0xff;
+}
+
+void setledstate(struct kbd_struct *kb, unsigned int led)
+{
+ guard(spinlock_irqsave)(&led_lock);
+ if (!(led & ~7)) {
+ ledioctl = led;
+ kb->ledmode = LED_SHOW_IOCTL;
+ } else
+ kb->ledmode = LED_SHOW_FLAGS;
+
+ set_leds();
+}
+
+static inline unsigned char getleds(void)
+{
+ struct kbd_struct *kb = kbd_table + fg_console;
+
+ if (kb->ledmode == LED_SHOW_IOCTL)
+ return ledioctl;
+
+ return kb->ledflagstate;
+}
+
/**
* vt_get_leds - helper for braille console
* @console: console to read
@@ -1035,17 +1159,12 @@ static int kbd_update_leds_helper(struct input_handle *handle, void *data)
*
* Check the status of a keyboard led flag and report it back
*/
-int vt_get_leds(int console, int flag)
+int vt_get_leds(unsigned int console, int flag)
{
- struct kbd_struct * kbd = kbd_table + console;
- int ret;
- unsigned long flags;
-
- spin_lock_irqsave(&led_lock, flags);
- ret = vc_kbd_led(kbd, flag);
- spin_unlock_irqrestore(&led_lock, flags);
+ struct kbd_struct *kb = &kbd_table[console];
- return ret;
+ guard(spinlock_irqsave)(&led_lock);
+ return vc_kbd_led(kb, flag);
}
EXPORT_SYMBOL_GPL(vt_get_leds);
@@ -1057,10 +1176,10 @@ EXPORT_SYMBOL_GPL(vt_get_leds);
* Set the LEDs on a console. This is a wrapper for the VT layer
* so that we can keep kbd knowledge internal
*/
-void vt_set_led_state(int console, int leds)
+void vt_set_led_state(unsigned int console, int leds)
{
- struct kbd_struct * kbd = kbd_table + console;
- setledstate(kbd, leds);
+ struct kbd_struct *kb = &kbd_table[console];
+ setledstate(kb, leds);
}
/**
@@ -1076,14 +1195,13 @@ void vt_set_led_state(int console, int leds)
* don't hold the lock. We probably need to split out an LED lock
* but not during an -rc release!
*/
-void vt_kbd_con_start(int console)
+void vt_kbd_con_start(unsigned int console)
{
- struct kbd_struct * kbd = kbd_table + console;
- unsigned long flags;
- spin_lock_irqsave(&led_lock, flags);
- clr_vc_kbd_led(kbd, VC_SCROLLOCK);
+ struct kbd_struct *kb = &kbd_table[console];
+
+ guard(spinlock_irqsave)(&led_lock);
+ clr_vc_kbd_led(kb, VC_SCROLLOCK);
set_leds();
- spin_unlock_irqrestore(&led_lock, flags);
}
/**
@@ -1093,49 +1211,54 @@ void vt_kbd_con_start(int console)
* Handle console stop. This is a wrapper for the VT layer
* so that we can keep kbd knowledge internal
*/
-void vt_kbd_con_stop(int console)
+void vt_kbd_con_stop(unsigned int console)
{
- struct kbd_struct * kbd = kbd_table + console;
- unsigned long flags;
- spin_lock_irqsave(&led_lock, flags);
- set_vc_kbd_led(kbd, VC_SCROLLOCK);
+ struct kbd_struct *kb = &kbd_table[console];
+
+ guard(spinlock_irqsave)(&led_lock);
+ set_vc_kbd_led(kb, VC_SCROLLOCK);
set_leds();
- spin_unlock_irqrestore(&led_lock, flags);
}
/*
- * This is the tasklet that updates LED state on all keyboards
- * attached to the box. The reason we use tasklet is that we
- * need to handle the scenario when keyboard handler is not
- * registered yet but we already getting updates from the VT to
- * update led state.
+ * This is the tasklet that updates LED state of LEDs using standard
+ * keyboard triggers. The reason we use tasklet is that we need to
+ * handle the scenario when keyboard handler is not registered yet
+ * but we already getting updates from the VT to update led state.
*/
-static void kbd_bh(unsigned long dummy)
+static void kbd_bh(struct tasklet_struct *unused)
{
- unsigned char leds;
- unsigned long flags;
-
- spin_lock_irqsave(&led_lock, flags);
- leds = getleds();
- spin_unlock_irqrestore(&led_lock, flags);
+ unsigned int leds;
+
+ scoped_guard(spinlock_irqsave, &led_lock) {
+ leds = getleds();
+ leds |= (unsigned int)kbd->lockstate << 8;
+ }
+
+ if (vt_switch) {
+ ledstate = ~leds;
+ vt_switch = false;
+ }
if (leds != ledstate) {
- input_handler_for_each_handle(&kbd_handler, &leds,
- kbd_update_leds_helper);
+ kbd_propagate_led_state(ledstate, leds);
ledstate = leds;
}
}
-DECLARE_TASKLET_DISABLED(keyboard_tasklet, kbd_bh, 0);
-
-#if defined(CONFIG_X86) || defined(CONFIG_IA64) || defined(CONFIG_ALPHA) ||\
+#if defined(CONFIG_X86) || defined(CONFIG_ALPHA) ||\
defined(CONFIG_MIPS) || defined(CONFIG_PPC) || defined(CONFIG_SPARC) ||\
defined(CONFIG_PARISC) || defined(CONFIG_SUPERH) ||\
- (defined(CONFIG_ARM) && defined(CONFIG_KEYBOARD_ATKBD) && !defined(CONFIG_ARCH_RPC)) ||\
- defined(CONFIG_AVR32)
+ (defined(CONFIG_ARM) && defined(CONFIG_KEYBOARD_ATKBD) && !defined(CONFIG_ARCH_RPC))
-#define HW_RAW(dev) (test_bit(EV_MSC, dev->evbit) && test_bit(MSC_RAW, dev->mscbit) &&\
- ((dev)->id.bustype == BUS_I8042) && ((dev)->id.vendor == 0x0001) && ((dev)->id.product == 0x0001))
+static inline bool kbd_is_hw_raw(const struct input_dev *dev)
+{
+ if (!test_bit(EV_MSC, dev->evbit) || !test_bit(MSC_RAW, dev->mscbit))
+ return false;
+
+ return dev->id.bustype == BUS_I8042 &&
+ dev->id.vendor == 0x0001 && dev->id.product == 0x0001;
+}
static const unsigned short x86_keycodes[256] =
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
@@ -1185,7 +1308,7 @@ static int emulate_raw(struct vc_data *vc, unsigned int keycode,
case KEY_SYSRQ:
/*
* Real AT keyboards (that's what we're trying
- * to emulate here emit 0xe0 0x2a 0xe0 0x37 when
+ * to emulate here) emit 0xe0 0x2a 0xe0 0x37 when
* pressing PrtSc/SysRq alone, but simply 0x54
* when pressing Alt+PrtSc/SysRq.
*/
@@ -1220,7 +1343,10 @@ static int emulate_raw(struct vc_data *vc, unsigned int keycode,
#else
-#define HW_RAW(dev) 0
+static inline bool kbd_is_hw_raw(const struct input_dev *dev)
+{
+ return false;
+}
static int emulate_raw(struct vc_data *vc, unsigned int keycode, unsigned char up_flag)
{
@@ -1236,12 +1362,12 @@ static void kbd_rawcode(unsigned char data)
{
struct vc_data *vc = vc_cons[fg_console].d;
- kbd = kbd_table + vc->vc_num;
+ kbd = &kbd_table[vc->vc_num];
if (kbd->kbdmode == VC_RAW)
put_queue(vc, data);
}
-static void kbd_keycode(unsigned int keycode, int down, int hw_raw)
+static void kbd_keycode(unsigned int keycode, int down, bool hw_raw)
{
struct vc_data *vc = vc_cons[fg_console].d;
unsigned short keysym, *key_map;
@@ -1259,7 +1385,7 @@ static void kbd_keycode(unsigned int keycode, int down, int hw_raw)
tty->driver_data = vc;
}
- kbd = kbd_table + vc->vc_num;
+ kbd = &kbd_table[vc->vc_num];
#ifdef CONFIG_SPARC
if (keycode == KEY_STOP)
@@ -1272,8 +1398,8 @@ static void kbd_keycode(unsigned int keycode, int down, int hw_raw)
if (raw_mode && !hw_raw)
if (emulate_raw(vc, keycode, !down << 7))
if (keycode < BTN_MISC && printk_ratelimit())
- pr_warning("can't emulate rawmode for keycode %d\n",
- keycode);
+ pr_warn("can't emulate rawmode for keycode %d\n",
+ keycode);
#ifdef CONFIG_SPARC
if (keycode == KEY_A && sparc_l1_a_state) {
@@ -1296,16 +1422,13 @@ static void kbd_keycode(unsigned int keycode, int down, int hw_raw)
put_queue(vc, keycode | (!down << 7));
} else {
put_queue(vc, !down << 7);
- put_queue(vc, (keycode >> 7) | 0x80);
- put_queue(vc, keycode | 0x80);
+ put_queue(vc, (keycode >> 7) | BIT(7));
+ put_queue(vc, keycode | BIT(7));
}
raw_mode = true;
}
- if (down)
- set_bit(keycode, key_down);
- else
- clear_bit(keycode, key_down);
+ assign_bit(keycode, key_down, down);
if (rep &&
(!vc_kbd_mode(kbd, VC_REPEAT) ||
@@ -1346,8 +1469,8 @@ static void kbd_keycode(unsigned int keycode, int down, int hw_raw)
rc = atomic_notifier_call_chain(&keyboard_notifier_list,
KBD_UNICODE, &param);
if (rc != NOTIFY_STOP)
- if (down && !raw_mode)
- to_utf8(vc, keysym);
+ if (down && !(raw_mode || kbd->kbdmode == VC_OFF))
+ k_unicode(vc, keysym, !down);
return;
}
@@ -1356,7 +1479,7 @@ static void kbd_keycode(unsigned int keycode, int down, int hw_raw)
if (type == KT_LETTER) {
type = KT_LATIN;
if (vc_kbd_led(kbd, VC_CAPSLOCK)) {
- key_map = key_maps[shift_final ^ (1 << KG_SHIFT)];
+ key_map = key_maps[shift_final ^ BIT(KG_SHIFT)];
if (key_map)
keysym = key_map[keycode];
}
@@ -1371,7 +1494,7 @@ static void kbd_keycode(unsigned int keycode, int down, int hw_raw)
if ((raw_mode || kbd->kbdmode == VC_OFF) && type != KT_SPEC && type != KT_SHIFT)
return;
- (*k_handler[type])(vc, keysym & 0xff, !down);
+ (*k_handler[type])(vc, KVAL(keysym), !down);
param.ledstate = kbd->ledflagstate;
atomic_notifier_call_chain(&keyboard_notifier_list, KBD_POST_KEYSYM, &param);
@@ -1384,14 +1507,13 @@ static void kbd_event(struct input_handle *handle, unsigned int event_type,
unsigned int event_code, int value)
{
/* We are called with interrupts disabled, just take the lock */
- spin_lock(&kbd_event_lock);
-
- if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev))
- kbd_rawcode(value);
- if (event_type == EV_KEY)
- kbd_keycode(event_code, value, HW_RAW(handle->dev));
-
- spin_unlock(&kbd_event_lock);
+ scoped_guard(spinlock, &kbd_event_lock) {
+ if (event_type == EV_MSC && event_code == MSC_RAW &&
+ kbd_is_hw_raw(handle->dev))
+ kbd_rawcode(value);
+ if (event_type == EV_KEY && event_code <= KEY_MAX)
+ kbd_keycode(event_code, value, kbd_is_hw_raw(handle->dev));
+ }
tasklet_schedule(&keyboard_tasklet);
do_poke_blanked_console = 1;
@@ -1400,18 +1522,16 @@ static void kbd_event(struct input_handle *handle, unsigned int event_type,
static bool kbd_match(struct input_handler *handler, struct input_dev *dev)
{
- int i;
-
if (test_bit(EV_SND, dev->evbit))
return true;
if (test_bit(EV_KEY, dev->evbit)) {
- for (i = KEY_RESERVED; i < BTN_MISC; i++)
- if (test_bit(i, dev->keybit))
- return true;
- for (i = KEY_BRL_DOT1; i <= KEY_BRL_DOT10; i++)
- if (test_bit(i, dev->keybit))
- return true;
+ if (find_next_bit(dev->keybit, BTN_MISC, KEY_RESERVED) <
+ BTN_MISC)
+ return true;
+ if (find_next_bit(dev->keybit, KEY_BRL_DOT10 + 1,
+ KEY_BRL_DOT1) <= KEY_BRL_DOT10)
+ return true;
}
return false;
@@ -1426,10 +1546,9 @@ static bool kbd_match(struct input_handler *handler, struct input_dev *dev)
static int kbd_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
- struct input_handle *handle;
int error;
- handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
+ struct input_handle __free(kfree) *handle = kzalloc(sizeof(*handle), GFP_KERNEL);
if (!handle)
return -ENOMEM;
@@ -1439,18 +1558,18 @@ static int kbd_connect(struct input_handler *handler, struct input_dev *dev,
error = input_register_handle(handle);
if (error)
- goto err_free_handle;
+ return error;
error = input_open_device(handle);
if (error)
goto err_unregister_handle;
+ retain_and_null_ptr(handle);
+
return 0;
err_unregister_handle:
input_unregister_handle(handle);
- err_free_handle:
- kfree(handle);
return error;
}
@@ -1469,7 +1588,7 @@ static void kbd_start(struct input_handle *handle)
{
tasklet_disable(&keyboard_tasklet);
- if (ledstate != 0xff)
+ if (ledstate != -1U)
kbd_update_leds_helper(handle, &ledstate);
tasklet_enable(&keyboard_tasklet);
@@ -1516,6 +1635,8 @@ int __init kbd_init(void)
kbd_table[i].kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE;
}
+ kbd_init_leds();
+
error = input_register_handler(&kbd_handler);
if (error)
return error;
@@ -1531,85 +1652,72 @@ int __init kbd_init(void)
/**
* vt_do_diacrit - diacritical table updates
* @cmd: ioctl request
- * @up: pointer to user data for ioctl
+ * @udp: pointer to user data for ioctl
* @perm: permissions check computed by caller
*
* Update the diacritical tables atomically and safely. Lock them
* against simultaneous keypresses
*/
-int vt_do_diacrit(unsigned int cmd, void __user *up, int perm)
+int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm)
{
- struct kbdiacrs __user *a = up;
- unsigned long flags;
int asize;
- int ret = 0;
switch (cmd) {
case KDGKBDIACR:
{
- struct kbdiacr *diacr;
+ struct kbdiacrs __user *a = udp;
int i;
- diacr = kmalloc(MAX_DIACR * sizeof(struct kbdiacr),
- GFP_KERNEL);
- if (diacr == NULL)
+ struct kbdiacr __free(kfree) *dia = kmalloc_array(MAX_DIACR, sizeof(struct kbdiacr),
+ GFP_KERNEL);
+ if (!dia)
return -ENOMEM;
/* Lock the diacriticals table, make a copy and then
copy it after we unlock */
- spin_lock_irqsave(&kbd_event_lock, flags);
-
- asize = accent_table_size;
- for (i = 0; i < asize; i++) {
- diacr[i].diacr = conv_uni_to_8bit(
- accent_table[i].diacr);
- diacr[i].base = conv_uni_to_8bit(
- accent_table[i].base);
- diacr[i].result = conv_uni_to_8bit(
- accent_table[i].result);
+ scoped_guard(spinlock_irqsave, &kbd_event_lock) {
+ asize = accent_table_size;
+ for (i = 0; i < asize; i++) {
+ dia[i].diacr = conv_uni_to_8bit(accent_table[i].diacr);
+ dia[i].base = conv_uni_to_8bit(accent_table[i].base);
+ dia[i].result = conv_uni_to_8bit(accent_table[i].result);
+ }
}
- spin_unlock_irqrestore(&kbd_event_lock, flags);
if (put_user(asize, &a->kb_cnt))
- ret = -EFAULT;
- else if (copy_to_user(a->kbdiacr, diacr,
- asize * sizeof(struct kbdiacr)))
- ret = -EFAULT;
- kfree(diacr);
- return ret;
+ return -EFAULT;
+ if (copy_to_user(a->kbdiacr, dia, asize * sizeof(struct kbdiacr)))
+ return -EFAULT;
+ return 0;
}
case KDGKBDIACRUC:
{
- struct kbdiacrsuc __user *a = up;
- void *buf;
+ struct kbdiacrsuc __user *a = udp;
- buf = kmalloc(MAX_DIACR * sizeof(struct kbdiacruc),
- GFP_KERNEL);
+ void __free(kfree) *buf = kmalloc_array(MAX_DIACR, sizeof(struct kbdiacruc),
+ GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
/* Lock the diacriticals table, make a copy and then
copy it after we unlock */
- spin_lock_irqsave(&kbd_event_lock, flags);
-
- asize = accent_table_size;
- memcpy(buf, accent_table, asize * sizeof(struct kbdiacruc));
-
- spin_unlock_irqrestore(&kbd_event_lock, flags);
+ scoped_guard(spinlock_irqsave, &kbd_event_lock) {
+ asize = accent_table_size;
+ memcpy(buf, accent_table, asize * sizeof(struct kbdiacruc));
+ }
if (put_user(asize, &a->kb_cnt))
- ret = -EFAULT;
- else if (copy_to_user(a->kbdiacruc, buf,
- asize*sizeof(struct kbdiacruc)))
- ret = -EFAULT;
- kfree(buf);
- return ret;
+ return -EFAULT;
+ if (copy_to_user(a->kbdiacruc, buf, asize * sizeof(struct kbdiacruc)))
+ return -EFAULT;
+
+ return 0;
}
case KDSKBDIACR:
{
- struct kbdiacrs __user *a = up;
- struct kbdiacr *diacr = NULL;
+ struct kbdiacrs __user *a = udp;
+ struct kbdiacr __free(kfree) *dia = NULL;
unsigned int ct;
int i;
@@ -1621,38 +1729,31 @@ int vt_do_diacrit(unsigned int cmd, void __user *up, int perm)
return -EINVAL;
if (ct) {
- diacr = kmalloc(sizeof(struct kbdiacr) * ct,
- GFP_KERNEL);
- if (diacr == NULL)
- return -ENOMEM;
-
- if (copy_from_user(diacr, a->kbdiacr,
- sizeof(struct kbdiacr) * ct)) {
- kfree(diacr);
- return -EFAULT;
- }
+ dia = memdup_array_user(a->kbdiacr,
+ ct, sizeof(struct kbdiacr));
+ if (IS_ERR(dia))
+ return PTR_ERR(dia);
}
- spin_lock_irqsave(&kbd_event_lock, flags);
+ guard(spinlock_irqsave)(&kbd_event_lock);
accent_table_size = ct;
for (i = 0; i < ct; i++) {
accent_table[i].diacr =
- conv_8bit_to_uni(diacr[i].diacr);
+ conv_8bit_to_uni(dia[i].diacr);
accent_table[i].base =
- conv_8bit_to_uni(diacr[i].base);
+ conv_8bit_to_uni(dia[i].base);
accent_table[i].result =
- conv_8bit_to_uni(diacr[i].result);
+ conv_8bit_to_uni(dia[i].result);
}
- spin_unlock_irqrestore(&kbd_event_lock, flags);
- kfree(diacr);
+
return 0;
}
case KDSKBDIACRUC:
{
- struct kbdiacrsuc __user *a = up;
+ struct kbdiacrsuc __user *a = udp;
unsigned int ct;
- void *buf = NULL;
+ void __free(kfree) *buf = NULL;
if (!perm)
return -EPERM;
@@ -1664,28 +1765,20 @@ int vt_do_diacrit(unsigned int cmd, void __user *up, int perm)
return -EINVAL;
if (ct) {
- buf = kmalloc(ct * sizeof(struct kbdiacruc),
- GFP_KERNEL);
- if (buf == NULL)
- return -ENOMEM;
-
- if (copy_from_user(buf, a->kbdiacruc,
- ct * sizeof(struct kbdiacruc))) {
- kfree(buf);
- return -EFAULT;
- }
- }
- spin_lock_irqsave(&kbd_event_lock, flags);
+ buf = memdup_array_user(a->kbdiacruc,
+ ct, sizeof(struct kbdiacruc));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+ }
+ guard(spinlock_irqsave)(&kbd_event_lock);
if (ct)
memcpy(accent_table, buf,
ct * sizeof(struct kbdiacruc));
accent_table_size = ct;
- spin_unlock_irqrestore(&kbd_event_lock, flags);
- kfree(buf);
return 0;
}
}
- return ret;
+ return 0;
}
/**
@@ -1696,36 +1789,32 @@ int vt_do_diacrit(unsigned int cmd, void __user *up, int perm)
* Update the keyboard mode bits while holding the correct locks.
* Return 0 for success or an error code.
*/
-int vt_do_kdskbmode(int console, unsigned int arg)
+int vt_do_kdskbmode(unsigned int console, unsigned int arg)
{
- struct kbd_struct * kbd = kbd_table + console;
- int ret = 0;
- unsigned long flags;
+ struct kbd_struct *kb = &kbd_table[console];
- spin_lock_irqsave(&kbd_event_lock, flags);
+ guard(spinlock_irqsave)(&kbd_event_lock);
switch(arg) {
case K_RAW:
- kbd->kbdmode = VC_RAW;
- break;
+ kb->kbdmode = VC_RAW;
+ return 0;
case K_MEDIUMRAW:
- kbd->kbdmode = VC_MEDIUMRAW;
- break;
+ kb->kbdmode = VC_MEDIUMRAW;
+ return 0;
case K_XLATE:
- kbd->kbdmode = VC_XLATE;
+ kb->kbdmode = VC_XLATE;
do_compute_shiftstate();
- break;
+ return 0;
case K_UNICODE:
- kbd->kbdmode = VC_UNICODE;
+ kb->kbdmode = VC_UNICODE;
do_compute_shiftstate();
- break;
+ return 0;
case K_OFF:
- kbd->kbdmode = VC_OFF;
- break;
+ kb->kbdmode = VC_OFF;
+ return 0;
default:
- ret = -EINVAL;
+ return -EINVAL;
}
- spin_unlock_irqrestore(&kbd_event_lock, flags);
- return ret;
}
/**
@@ -1736,285 +1825,227 @@ int vt_do_kdskbmode(int console, unsigned int arg)
* Update the keyboard meta bits while holding the correct locks.
* Return 0 for success or an error code.
*/
-int vt_do_kdskbmeta(int console, unsigned int arg)
+int vt_do_kdskbmeta(unsigned int console, unsigned int arg)
{
- struct kbd_struct * kbd = kbd_table + console;
- int ret = 0;
- unsigned long flags;
+ struct kbd_struct *kb = &kbd_table[console];
- spin_lock_irqsave(&kbd_event_lock, flags);
+ guard(spinlock_irqsave)(&kbd_event_lock);
switch(arg) {
case K_METABIT:
- clr_vc_kbd_mode(kbd, VC_META);
- break;
+ clr_vc_kbd_mode(kb, VC_META);
+ return 0;
case K_ESCPREFIX:
- set_vc_kbd_mode(kbd, VC_META);
- break;
+ set_vc_kbd_mode(kb, VC_META);
+ return 0;
default:
- ret = -EINVAL;
+ return -EINVAL;
}
- spin_unlock_irqrestore(&kbd_event_lock, flags);
- return ret;
}
-int vt_do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc,
- int perm)
+int vt_do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc, int perm)
{
struct kbkeycode tmp;
- int kc = 0;
+ int kc;
if (copy_from_user(&tmp, user_kbkc, sizeof(struct kbkeycode)))
return -EFAULT;
+
switch (cmd) {
case KDGETKEYCODE:
kc = getkeycode(tmp.scancode);
- if (kc >= 0)
- kc = put_user(kc, &user_kbkc->keycode);
- break;
+ if (kc < 0)
+ return kc;
+ return put_user(kc, &user_kbkc->keycode);
case KDSETKEYCODE:
if (!perm)
return -EPERM;
- kc = setkeycode(tmp.scancode, tmp.keycode);
- break;
+ return setkeycode(tmp.scancode, tmp.keycode);
}
- return kc;
-}
-#define i (tmp.kb_index)
-#define s (tmp.kb_table)
-#define v (tmp.kb_value)
+ return 0;
+}
-int vt_do_kdsk_ioctl(int cmd, struct kbentry __user *user_kbe, int perm,
- int console)
+static unsigned short vt_kdgkbent(unsigned char kbdmode, unsigned char idx,
+ unsigned char map)
{
- struct kbd_struct * kbd = kbd_table + console;
- struct kbentry tmp;
- ushort *key_map, *new_map, val, ov;
- unsigned long flags;
+ unsigned short *key_map;
- if (copy_from_user(&tmp, user_kbe, sizeof(struct kbentry)))
- return -EFAULT;
+ /* Ensure another thread doesn't free it under us */
+ guard(spinlock_irqsave)(&kbd_event_lock);
+ key_map = key_maps[map];
+ if (key_map) {
+ unsigned short val = U(key_map[idx]);
+ if (kbdmode != VC_UNICODE && KTYP(val) >= NR_TYPES)
+ return K_HOLE;
+ return val;
+ }
- if (!capable(CAP_SYS_TTY_CONFIG))
- perm = 0;
+ return idx ? K_HOLE : K_NOSUCHMAP;
+}
- switch (cmd) {
- case KDGKBENT:
- /* Ensure another thread doesn't free it under us */
- spin_lock_irqsave(&kbd_event_lock, flags);
- key_map = key_maps[s];
- if (key_map) {
- val = U(key_map[i]);
- if (kbd->kbdmode != VC_UNICODE && KTYP(val) >= NR_TYPES)
- val = K_HOLE;
- } else
- val = (i ? K_HOLE : K_NOSUCHMAP);
- spin_unlock_irqrestore(&kbd_event_lock, flags);
- return put_user(val, &user_kbe->kb_value);
- case KDSKBENT:
- if (!perm)
- return -EPERM;
- if (!i && v == K_NOSUCHMAP) {
- spin_lock_irqsave(&kbd_event_lock, flags);
- /* deallocate map */
- key_map = key_maps[s];
- if (s && key_map) {
- key_maps[s] = NULL;
- if (key_map[0] == U(K_ALLOCATED)) {
- kfree(key_map);
- keymap_count--;
- }
+static int vt_kdskbent(unsigned char kbdmode, unsigned char idx,
+ unsigned char map, unsigned short val)
+{
+ unsigned short *key_map, oldval;
+
+ if (!idx && val == K_NOSUCHMAP) {
+ guard(spinlock_irqsave)(&kbd_event_lock);
+ /* deallocate map */
+ key_map = key_maps[map];
+ if (map && key_map) {
+ key_maps[map] = NULL;
+ if (key_map[0] == U(K_ALLOCATED)) {
+ kfree(key_map);
+ keymap_count--;
}
- spin_unlock_irqrestore(&kbd_event_lock, flags);
- break;
}
- if (KTYP(v) < NR_TYPES) {
- if (KVAL(v) > max_vals[KTYP(v)])
- return -EINVAL;
- } else
- if (kbd->kbdmode != VC_UNICODE)
- return -EINVAL;
+ return 0;
+ }
+
+ if (KTYP(val) < NR_TYPES) {
+ if (KVAL(val) > max_vals[KTYP(val)])
+ return -EINVAL;
+ } else if (kbdmode != VC_UNICODE)
+ return -EINVAL;
- /* ++Geert: non-PC keyboards may generate keycode zero */
+ /* ++Geert: non-PC keyboards may generate keycode zero */
#if !defined(__mc68000__) && !defined(__powerpc__)
- /* assignment to entry 0 only tests validity of args */
- if (!i)
- break;
+ /* assignment to entry 0 only tests validity of args */
+ if (!idx)
+ return 0;
#endif
- new_map = kmalloc(sizeof(plain_map), GFP_KERNEL);
- if (!new_map)
- return -ENOMEM;
- spin_lock_irqsave(&kbd_event_lock, flags);
- key_map = key_maps[s];
- if (key_map == NULL) {
- int j;
-
- if (keymap_count >= MAX_NR_OF_USER_KEYMAPS &&
- !capable(CAP_SYS_RESOURCE)) {
- spin_unlock_irqrestore(&kbd_event_lock, flags);
- kfree(new_map);
- return -EPERM;
- }
- key_maps[s] = new_map;
- key_map = new_map;
- key_map[0] = U(K_ALLOCATED);
- for (j = 1; j < NR_KEYS; j++)
- key_map[j] = U(K_HOLE);
- keymap_count++;
- } else
- kfree(new_map);
-
- ov = U(key_map[i]);
- if (v == ov)
- goto out;
- /*
- * Attention Key.
- */
- if (((ov == K_SAK) || (v == K_SAK)) && !capable(CAP_SYS_ADMIN)) {
- spin_unlock_irqrestore(&kbd_event_lock, flags);
+ unsigned short __free(kfree) *new_map = kmalloc(sizeof(plain_map), GFP_KERNEL);
+ if (!new_map)
+ return -ENOMEM;
+
+ guard(spinlock_irqsave)(&kbd_event_lock);
+ key_map = key_maps[map];
+ if (key_map == NULL) {
+ int j;
+
+ if (keymap_count >= MAX_NR_OF_USER_KEYMAPS && !capable(CAP_SYS_RESOURCE))
return -EPERM;
- }
- key_map[i] = U(v);
- if (!s && (KTYP(ov) == KT_SHIFT || KTYP(v) == KT_SHIFT))
- do_compute_shiftstate();
-out:
- spin_unlock_irqrestore(&kbd_event_lock, flags);
- break;
+
+ key_map = key_maps[map] = no_free_ptr(new_map);
+ key_map[0] = U(K_ALLOCATED);
+ for (j = 1; j < NR_KEYS; j++)
+ key_map[j] = U(K_HOLE);
+ keymap_count++;
}
+
+ oldval = U(key_map[idx]);
+ if (val == oldval)
+ return 0;
+
+ /* Attention Key */
+ if ((oldval == K_SAK || val == K_SAK) && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ key_map[idx] = U(val);
+ if (!map && (KTYP(oldval) == KT_SHIFT || KTYP(val) == KT_SHIFT))
+ do_compute_shiftstate();
+
return 0;
}
-#undef i
-#undef s
-#undef v
-/* FIXME: This one needs untangling and locking */
-int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm)
+int vt_do_kdsk_ioctl(int cmd, struct kbentry __user *user_kbe, int perm,
+ unsigned int console)
{
- struct kbsentry *kbs;
- char *p;
- u_char *q;
- u_char __user *up;
- int sz;
- int delta;
- char *first_free, *fj, *fnw;
- int i, j, k;
- int ret;
-
- if (!capable(CAP_SYS_TTY_CONFIG))
- perm = 0;
-
- kbs = kmalloc(sizeof(*kbs), GFP_KERNEL);
- if (!kbs) {
- ret = -ENOMEM;
- goto reterr;
+ struct kbd_struct *kb = &kbd_table[console];
+ struct kbentry kbe;
+
+ if (copy_from_user(&kbe, user_kbe, sizeof(struct kbentry)))
+ return -EFAULT;
+
+ switch (cmd) {
+ case KDGKBENT:
+ return put_user(vt_kdgkbent(kb->kbdmode, kbe.kb_index,
+ kbe.kb_table),
+ &user_kbe->kb_value);
+ case KDSKBENT:
+ if (!perm || !capable(CAP_SYS_TTY_CONFIG))
+ return -EPERM;
+ return vt_kdskbent(kb->kbdmode, kbe.kb_index, kbe.kb_table,
+ kbe.kb_value);
}
+ return 0;
+}
- /* we mostly copy too much here (512bytes), but who cares ;) */
- if (copy_from_user(kbs, user_kdgkb, sizeof(struct kbsentry))) {
- ret = -EFAULT;
- goto reterr;
+static char *vt_kdskbsent(char *kbs, unsigned char cur)
+{
+ static DECLARE_BITMAP(is_kmalloc, MAX_NR_FUNC);
+ char *cur_f = func_table[cur];
+
+ if (cur_f && strlen(cur_f) >= strlen(kbs)) {
+ strcpy(cur_f, kbs);
+ return kbs;
}
- kbs->kb_string[sizeof(kbs->kb_string)-1] = '\0';
- i = kbs->kb_func;
+
+ func_table[cur] = kbs;
+
+ return __test_and_set_bit(cur, is_kmalloc) ? cur_f : NULL;
+}
+
+int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm)
+{
+ unsigned char kb_func;
+
+ if (get_user(kb_func, &user_kdgkb->kb_func))
+ return -EFAULT;
+
+ kb_func = array_index_nospec(kb_func, MAX_NR_FUNC);
switch (cmd) {
- case KDGKBSENT:
- sz = sizeof(kbs->kb_string) - 1; /* sz should have been
- a struct member */
- up = user_kdgkb->kb_string;
- p = func_table[i];
- if(p)
- for ( ; *p && sz; p++, sz--)
- if (put_user(*p, up++)) {
- ret = -EFAULT;
- goto reterr;
- }
- if (put_user('\0', up)) {
- ret = -EFAULT;
- goto reterr;
- }
- kfree(kbs);
- return ((p && *p) ? -EOVERFLOW : 0);
+ case KDGKBSENT: {
+ /* size should have been a struct member */
+ ssize_t len = sizeof(user_kdgkb->kb_string);
+
+ char __free(kfree) *kbs = kmalloc(len, GFP_KERNEL);
+ if (!kbs)
+ return -ENOMEM;
+
+ scoped_guard(spinlock_irqsave, &func_buf_lock)
+ len = strscpy(kbs, func_table[kb_func] ? : "", len);
+
+ if (len < 0)
+ return -ENOSPC;
+
+ if (copy_to_user(user_kdgkb->kb_string, kbs, len + 1))
+ return -EFAULT;
+
+ return 0;
+ }
case KDSKBSENT:
- if (!perm) {
- ret = -EPERM;
- goto reterr;
- }
+ if (!perm || !capable(CAP_SYS_TTY_CONFIG))
+ return -EPERM;
- q = func_table[i];
- first_free = funcbufptr + (funcbufsize - funcbufleft);
- for (j = i+1; j < MAX_NR_FUNC && !func_table[j]; j++)
- ;
- if (j < MAX_NR_FUNC)
- fj = func_table[j];
- else
- fj = first_free;
-
- delta = (q ? -strlen(q) : 1) + strlen(kbs->kb_string);
- if (delta <= funcbufleft) { /* it fits in current buf */
- if (j < MAX_NR_FUNC) {
- memmove(fj + delta, fj, first_free - fj);
- for (k = j; k < MAX_NR_FUNC; k++)
- if (func_table[k])
- func_table[k] += delta;
- }
- if (!q)
- func_table[i] = fj;
- funcbufleft -= delta;
- } else { /* allocate a larger buffer */
- sz = 256;
- while (sz < funcbufsize - funcbufleft + delta)
- sz <<= 1;
- fnw = kmalloc(sz, GFP_KERNEL);
- if(!fnw) {
- ret = -ENOMEM;
- goto reterr;
- }
-
- if (!q)
- func_table[i] = fj;
- if (fj > funcbufptr)
- memmove(fnw, funcbufptr, fj - funcbufptr);
- for (k = 0; k < j; k++)
- if (func_table[k])
- func_table[k] = fnw + (func_table[k] - funcbufptr);
-
- if (first_free > fj) {
- memmove(fnw + (fj - funcbufptr) + delta, fj, first_free - fj);
- for (k = j; k < MAX_NR_FUNC; k++)
- if (func_table[k])
- func_table[k] = fnw + (func_table[k] - funcbufptr) + delta;
- }
- if (funcbufptr != func_buf)
- kfree(funcbufptr);
- funcbufptr = fnw;
- funcbufleft = funcbufleft - delta + sz - funcbufsize;
- funcbufsize = sz;
- }
- strcpy(func_table[i], kbs->kb_string);
- break;
+ char __free(kfree) *kbs = strndup_user(user_kdgkb->kb_string,
+ sizeof(user_kdgkb->kb_string));
+ if (IS_ERR(kbs))
+ return PTR_ERR(kbs);
+
+ guard(spinlock_irqsave)(&func_buf_lock);
+ kbs = vt_kdskbsent(kbs, kb_func);
+
+ return 0;
}
- ret = 0;
-reterr:
- kfree(kbs);
- return ret;
+
+ return 0;
}
-int vt_do_kdskled(int console, int cmd, unsigned long arg, int perm)
+int vt_do_kdskled(unsigned int console, int cmd, unsigned long arg, int perm)
{
- struct kbd_struct * kbd = kbd_table + console;
- unsigned long flags;
+ struct kbd_struct *kb = &kbd_table[console];
unsigned char ucval;
switch(cmd) {
/* the ioctls below read/set the flags usually shown in the leds */
/* don't use them - they will go away without warning */
case KDGKBLED:
- spin_lock_irqsave(&kbd_event_lock, flags);
- ucval = kbd->ledflagstate | (kbd->default_ledflagstate << 4);
- spin_unlock_irqrestore(&kbd_event_lock, flags);
+ scoped_guard(spinlock_irqsave, &kbd_event_lock)
+ ucval = kb->ledflagstate | (kb->default_ledflagstate << 4);
return put_user(ucval, (char __user *)arg);
case KDSKBLED:
@@ -2022,11 +2053,11 @@ int vt_do_kdskled(int console, int cmd, unsigned long arg, int perm)
return -EPERM;
if (arg & ~0x77)
return -EINVAL;
- spin_lock_irqsave(&led_lock, flags);
- kbd->ledflagstate = (arg & 7);
- kbd->default_ledflagstate = ((arg >> 4) & 7);
- set_leds();
- spin_unlock_irqrestore(&led_lock, flags);
+ scoped_guard(spinlock_irqsave, &led_lock) {
+ kb->ledflagstate = (arg & 7);
+ kb->default_ledflagstate = ((arg >> 4) & 7);
+ set_leds();
+ }
return 0;
/* the ioctls below only set the lights, not the functions */
@@ -2038,17 +2069,17 @@ int vt_do_kdskled(int console, int cmd, unsigned long arg, int perm)
case KDSETLED:
if (!perm)
return -EPERM;
- setledstate(kbd, arg);
+ setledstate(kb, arg);
return 0;
}
return -ENOIOCTLCMD;
}
-int vt_do_kdgkbmode(int console)
+int vt_do_kdgkbmode(unsigned int console)
{
- struct kbd_struct * kbd = kbd_table + console;
+ struct kbd_struct *kb = &kbd_table[console];
/* This is a spot read so needs no locking */
- switch (kbd->kbdmode) {
+ switch (kb->kbdmode) {
case VC_RAW:
return K_RAW;
case VC_MEDIUMRAW:
@@ -2068,11 +2099,11 @@ int vt_do_kdgkbmode(int console)
*
* Report the meta flag status of this console
*/
-int vt_do_kdgkbmeta(int console)
+int vt_do_kdgkbmeta(unsigned int console)
{
- struct kbd_struct * kbd = kbd_table + console;
+ struct kbd_struct *kb = &kbd_table[console];
/* Again a spot read so no locking */
- return vc_kbd_mode(kbd, VC_META) ? K_ESCPREFIX : K_METABIT;
+ return vc_kbd_mode(kb, VC_META) ? K_ESCPREFIX : K_METABIT;
}
/**
@@ -2081,17 +2112,14 @@ int vt_do_kdgkbmeta(int console)
*
* Restore the unicode console state to its default
*/
-void vt_reset_unicode(int console)
+void vt_reset_unicode(unsigned int console)
{
- unsigned long flags;
-
- spin_lock_irqsave(&kbd_event_lock, flags);
+ guard(spinlock_irqsave)(&kbd_event_lock);
kbd_table[console].kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE;
- spin_unlock_irqrestore(&kbd_event_lock, flags);
}
/**
- * vt_get_shiftstate - shift bit state
+ * vt_get_shift_state - shift bit state
*
* Report the shift bits from the keyboard state. We have to export
* this to support some oddities in the vt layer.
@@ -2109,25 +2137,22 @@ int vt_get_shift_state(void)
* Reset the keyboard bits for a console as part of a general console
* reset event
*/
-void vt_reset_keyboard(int console)
-{
- struct kbd_struct * kbd = kbd_table + console;
- unsigned long flags;
-
- spin_lock_irqsave(&kbd_event_lock, flags);
- set_vc_kbd_mode(kbd, VC_REPEAT);
- clr_vc_kbd_mode(kbd, VC_CKMODE);
- clr_vc_kbd_mode(kbd, VC_APPLIC);
- clr_vc_kbd_mode(kbd, VC_CRLF);
- kbd->lockstate = 0;
- kbd->slockstate = 0;
- spin_lock(&led_lock);
- kbd->ledmode = LED_SHOW_FLAGS;
- kbd->ledflagstate = kbd->default_ledflagstate;
- spin_unlock(&led_lock);
+void vt_reset_keyboard(unsigned int console)
+{
+ struct kbd_struct *kb = &kbd_table[console];
+
+ guard(spinlock_irqsave)(&kbd_event_lock);
+ set_vc_kbd_mode(kb, VC_REPEAT);
+ clr_vc_kbd_mode(kb, VC_CKMODE);
+ clr_vc_kbd_mode(kb, VC_APPLIC);
+ clr_vc_kbd_mode(kb, VC_CRLF);
+ kb->lockstate = 0;
+ kb->slockstate = 0;
+ guard(spinlock)(&led_lock);
+ kb->ledmode = LED_SHOW_FLAGS;
+ kb->ledflagstate = kb->default_ledflagstate;
/* do not do set_leds here because this causes an endless tasklet loop
when the keyboard hasn't been initialized yet */
- spin_unlock_irqrestore(&kbd_event_lock, flags);
}
/**
@@ -2139,10 +2164,10 @@ void vt_reset_keyboard(int console)
* caller must be sure that there are no synchronization needs
*/
-int vt_get_kbd_mode_bit(int console, int bit)
+int vt_get_kbd_mode_bit(unsigned int console, int bit)
{
- struct kbd_struct * kbd = kbd_table + console;
- return vc_kbd_mode(kbd, bit);
+ struct kbd_struct *kb = &kbd_table[console];
+ return vc_kbd_mode(kb, bit);
}
/**
@@ -2154,14 +2179,12 @@ int vt_get_kbd_mode_bit(int console, int bit)
* caller must be sure that there are no synchronization needs
*/
-void vt_set_kbd_mode_bit(int console, int bit)
+void vt_set_kbd_mode_bit(unsigned int console, int bit)
{
- struct kbd_struct * kbd = kbd_table + console;
- unsigned long flags;
+ struct kbd_struct *kb = &kbd_table[console];
- spin_lock_irqsave(&kbd_event_lock, flags);
- set_vc_kbd_mode(kbd, bit);
- spin_unlock_irqrestore(&kbd_event_lock, flags);
+ guard(spinlock_irqsave)(&kbd_event_lock);
+ set_vc_kbd_mode(kb, bit);
}
/**
@@ -2173,12 +2196,10 @@ void vt_set_kbd_mode_bit(int console, int bit)
* caller must be sure that there are no synchronization needs
*/
-void vt_clr_kbd_mode_bit(int console, int bit)
+void vt_clr_kbd_mode_bit(unsigned int console, int bit)
{
- struct kbd_struct * kbd = kbd_table + console;
- unsigned long flags;
+ struct kbd_struct *kb = &kbd_table[console];
- spin_lock_irqsave(&kbd_event_lock, flags);
- clr_vc_kbd_mode(kbd, bit);
- spin_unlock_irqrestore(&kbd_event_lock, flags);
+ guard(spinlock_irqsave)(&kbd_event_lock);
+ clr_vc_kbd_mode(kb, bit);
}
diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c
index 60b7b6926059..13f4e48b4142 100644
--- a/drivers/tty/vt/selection.c
+++ b/drivers/tty/vt/selection.c
@@ -1,10 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* This module exports the functions:
*
- * 'int set_selection(struct tiocl_selection __user *, struct tty_struct *)'
+ * 'int set_selection_user(struct tiocl_selection __user *,
+ * struct tty_struct *)'
+ * 'int set_selection_kernel(struct tiocl_selection *, struct tty_struct *)'
* 'void clear_selection(void)'
* 'int paste_selection(struct tty_struct *)'
- * 'int sel_loadlut(char __user *)'
+ * 'int sel_loadlut(u32 __user *)'
*
* Now that /dev/vcs exists, most of this can disappear again.
*/
@@ -13,10 +16,11 @@
#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/mm.h>
+#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/types.h>
-#include <asm/uaccess.h>
+#include <linux/uaccess.h>
#include <linux/kbd_kern.h>
#include <linux/vt_kern.h>
@@ -24,21 +28,25 @@
#include <linux/selection.h>
#include <linux/tiocl.h>
#include <linux/console.h>
+#include <linux/tty_flip.h>
-/* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */
-#define isspace(c) ((c) == ' ')
+#include <linux/sched/signal.h>
-extern void poke_blanked_console(void);
+/* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */
+#define is_space_on_vt(c) ((c) == ' ')
/* FIXME: all this needs locking */
-/* Variables for selection control. */
-/* Use a dynamic buffer, instead of static (Dec 1994) */
-struct vc_data *sel_cons; /* must not be deallocated */
-static int use_unicode;
-static volatile int sel_start = -1; /* cleared by clear_selection */
-static int sel_end;
-static int sel_buffer_lth;
-static char *sel_buffer;
+static struct vc_selection {
+ struct mutex lock;
+ struct vc_data *cons; /* must not be deallocated */
+ char *buffer;
+ unsigned int buf_len;
+ volatile int start; /* cleared by clear_selection */
+ int end;
+} vc_sel = {
+ .lock = __MUTEX_INITIALIZER(vc_sel.lock),
+ .start = -1,
+};
/* clear_selection, highlight and highlight_pointer can be called
from interrupt (via scrollback/front) */
@@ -46,69 +54,82 @@ static char *sel_buffer;
/* set reverse video on characters s-e of console with selection. */
static inline void highlight(const int s, const int e)
{
- invert_screen(sel_cons, s, e-s+2, 1);
+ invert_screen(vc_sel.cons, s, e-s+2, true);
}
/* use complementary color to show the pointer */
static inline void highlight_pointer(const int where)
{
- complement_pos(sel_cons, where);
+ complement_pos(vc_sel.cons, where);
}
-static u16
-sel_pos(int n)
+static u32
+sel_pos(int n, bool unicode)
{
- return inverse_translate(sel_cons, screen_glyph(sel_cons, n),
- use_unicode);
+ if (unicode)
+ return screen_glyph_unicode(vc_sel.cons, n / 2);
+ return inverse_translate(vc_sel.cons, screen_glyph(vc_sel.cons, n),
+ false);
}
/**
- * clear_selection - remove current selection
+ * clear_selection - remove current selection
+ *
+ * Remove the current selection highlight, if any from the console holding the
+ * selection.
*
- * Remove the current selection highlight, if any from the console
- * holding the selection. The caller must hold the console lock.
+ * Locking: The caller must hold the console lock.
*/
void clear_selection(void)
{
highlight_pointer(-1); /* hide the pointer */
- if (sel_start != -1) {
- highlight(sel_start, sel_end);
- sel_start = -1;
+ if (vc_sel.start != -1) {
+ highlight(vc_sel.start, vc_sel.end);
+ vc_sel.start = -1;
}
}
+EXPORT_SYMBOL_GPL(clear_selection);
+
+bool vc_is_sel(const struct vc_data *vc)
+{
+ return vc == vc_sel.cons;
+}
/*
* User settable table: what characters are to be considered alphabetic?
- * 256 bits. Locked by the console lock.
+ * 128 bits. Locked by the console lock.
*/
-static u32 inwordLut[8]={
+static u32 inwordLut[]={
0x00000000, /* control chars */
- 0x03FF0000, /* digits */
+ 0x03FFE000, /* digits and "-./" */
0x87FFFFFE, /* uppercase and '_' */
0x07FFFFFE, /* lowercase */
- 0x00000000,
- 0x00000000,
- 0xFF7FFFFF, /* latin-1 accented letters, not multiplication sign */
- 0xFF7FFFFF /* latin-1 accented letters, not division sign */
};
-static inline int inword(const u16 c) {
- return c > 0xff || (( inwordLut[c>>5] >> (c & 0x1F) ) & 1);
+static inline int inword(const u32 c)
+{
+ return c > 0x7f || (( inwordLut[c>>5] >> (c & 0x1F) ) & 1);
}
/**
- * set loadlut - load the LUT table
- * @p: user table
+ * sel_loadlut() - load the LUT table
+ * @lut: user table
+ *
+ * Load the LUT table from user space. Make a temporary copy so a partial
+ * update doesn't make a mess.
*
- * Load the LUT table from user space. The caller must hold the console
- * lock. Make a temporary copy so a partial update doesn't make a mess.
+ * Locking: The console lock is acquired.
*/
-int sel_loadlut(char __user *p)
+int sel_loadlut(u32 __user *lut)
{
- u32 tmplut[8];
- if (copy_from_user(tmplut, (u32 __user *)(p+4), 32))
+ u32 tmplut[ARRAY_SIZE(inwordLut)];
+
+ if (copy_from_user(tmplut, lut, sizeof(inwordLut)))
return -EFAULT;
- memcpy(inwordLut, tmplut, 32);
+
+ guard(console_lock)();
+ memcpy(inwordLut, tmplut, sizeof(inwordLut));
+
return 0;
}
@@ -118,14 +139,8 @@ static inline int atedge(const int p, int size_row)
return (!(p % size_row) || !((p + 2) % size_row));
}
-/* constrain v such that v <= u */
-static inline unsigned short limit(const unsigned short v, const unsigned short u)
-{
- return (v > u) ? u : v;
-}
-
-/* stores the char in UTF8 and returns the number of bytes used (1-3) */
-static int store_utf8(u16 c, char *p)
+/* stores the char in UTF8 and returns the number of bytes used (1-4) */
+static int store_utf8(u32 c, char *p)
{
if (c < 0x80) {
/* 0******* */
@@ -136,120 +151,145 @@ static int store_utf8(u16 c, char *p)
p[0] = 0xc0 | (c >> 6);
p[1] = 0x80 | (c & 0x3f);
return 2;
- } else {
+ } else if (c < 0x10000) {
/* 1110**** 10****** 10****** */
p[0] = 0xe0 | (c >> 12);
p[1] = 0x80 | ((c >> 6) & 0x3f);
p[2] = 0x80 | (c & 0x3f);
return 3;
- }
+ } else if (c < 0x110000) {
+ /* 11110*** 10****** 10****** 10****** */
+ p[0] = 0xf0 | (c >> 18);
+ p[1] = 0x80 | ((c >> 12) & 0x3f);
+ p[2] = 0x80 | ((c >> 6) & 0x3f);
+ p[3] = 0x80 | (c & 0x3f);
+ return 4;
+ } else {
+ /* outside Unicode, replace with U+FFFD */
+ p[0] = 0xef;
+ p[1] = 0xbf;
+ p[2] = 0xbd;
+ return 3;
+ }
}
/**
- * set_selection - set the current selection.
- * @sel: user selection info
- * @tty: the console tty
+ * set_selection_user - set the current selection.
+ * @sel: user selection info
+ * @tty: the console tty
*
- * Invoked by the ioctl handle for the vt layer.
+ * Invoked by the ioctl handle for the vt layer.
*
- * The entire selection process is managed under the console_lock. It's
- * a lot under the lock but its hardly a performance path
+ * Locking: The entire selection process is managed under the console_lock.
+ * It's a lot under the lock but its hardly a performance path.
*/
-int set_selection(const struct tiocl_selection __user *sel, struct tty_struct *tty)
+int set_selection_user(const struct tiocl_selection __user *sel,
+ struct tty_struct *tty)
{
- struct vc_data *vc = vc_cons[fg_console].d;
- int sel_mode, new_sel_start, new_sel_end, spc;
- char *bp, *obp;
- int i, ps, pe, multiplier;
- u16 c;
- int mode;
-
- poke_blanked_console();
-
- { unsigned short xs, ys, xe, ye;
+ struct tiocl_selection v;
- if (!access_ok(VERIFY_READ, sel, sizeof(*sel)))
+ if (copy_from_user(&v, sel, sizeof(*sel)))
return -EFAULT;
- __get_user(xs, &sel->xs);
- __get_user(ys, &sel->ys);
- __get_user(xe, &sel->xe);
- __get_user(ye, &sel->ye);
- __get_user(sel_mode, &sel->sel_mode);
- xs--; ys--; xe--; ye--;
- xs = limit(xs, vc->vc_cols - 1);
- ys = limit(ys, vc->vc_rows - 1);
- xe = limit(xe, vc->vc_cols - 1);
- ye = limit(ye, vc->vc_rows - 1);
- ps = ys * vc->vc_size_row + (xs << 1);
- pe = ye * vc->vc_size_row + (xe << 1);
-
- if (sel_mode == TIOCL_SELCLEAR) {
- /* useful for screendump without selection highlights */
- clear_selection();
- return 0;
- }
-
- if (mouse_reporting() && (sel_mode & TIOCL_SELMOUSEREPORT)) {
- mouse_report(tty, sel_mode & TIOCL_SELBUTTONMASK, xs, ys);
- return 0;
- }
- }
-
- if (ps > pe) /* make sel_start <= sel_end */
- {
- int tmp = ps;
- ps = pe;
- pe = tmp;
+
+ /*
+ * TIOCL_SELCLEAR and TIOCL_SELPOINTER are OK to use without
+ * CAP_SYS_ADMIN as they do not modify the selection.
+ */
+ switch (v.sel_mode) {
+ case TIOCL_SELCLEAR:
+ case TIOCL_SELPOINTER:
+ break;
+ default:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
}
- if (sel_cons != vc_cons[fg_console].d) {
+ return set_selection_kernel(&v, tty);
+}
+
+static int vc_selection_store_chars(struct vc_data *vc, bool unicode)
+{
+ char *bp, *obp;
+ unsigned int i;
+
+ /* Allocate a new buffer before freeing the old one ... */
+ /* chars can take up to 4 bytes with unicode */
+ bp = kmalloc_array((vc_sel.end - vc_sel.start) / 2 + 1, unicode ? 4 : 1,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!bp) {
+ printk(KERN_WARNING "selection: kmalloc() failed\n");
clear_selection();
- sel_cons = vc_cons[fg_console].d;
+ return -ENOMEM;
}
- mode = vt_do_kdgkbmode(fg_console);
- if (mode == K_UNICODE)
- use_unicode = 1;
- else
- use_unicode = 0;
+ kfree(vc_sel.buffer);
+ vc_sel.buffer = bp;
- switch (sel_mode)
- {
- case TIOCL_SELCHAR: /* character-by-character selection */
+ obp = bp;
+ for (i = vc_sel.start; i <= vc_sel.end; i += 2) {
+ u32 c = sel_pos(i, unicode);
+ if (unicode)
+ bp += store_utf8(c, bp);
+ else
+ *bp++ = c;
+ if (!is_space_on_vt(c))
+ obp = bp;
+ if (!((i + 2) % vc->vc_size_row)) {
+ /* strip trailing blanks from line and add newline,
+ unless non-space at end of line. */
+ if (obp != bp) {
+ bp = obp;
+ *bp++ = '\r';
+ }
+ obp = bp;
+ }
+ }
+ vc_sel.buf_len = bp - vc_sel.buffer;
+
+ return 0;
+}
+
+static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps,
+ int pe)
+{
+ int new_sel_start, new_sel_end, spc;
+ bool unicode = vt_do_kdgkbmode(fg_console) == K_UNICODE;
+
+ switch (mode) {
+ case TIOCL_SELCHAR: /* character-by-character selection */
+ new_sel_start = ps;
+ new_sel_end = pe;
+ break;
+ case TIOCL_SELWORD: /* word-by-word selection */
+ spc = is_space_on_vt(sel_pos(ps, unicode));
+ for (new_sel_start = ps; ; ps -= 2) {
+ if ((spc && !is_space_on_vt(sel_pos(ps, unicode))) ||
+ (!spc && !inword(sel_pos(ps, unicode))))
+ break;
new_sel_start = ps;
+ if (!(ps % vc->vc_size_row))
+ break;
+ }
+
+ spc = is_space_on_vt(sel_pos(pe, unicode));
+ for (new_sel_end = pe; ; pe += 2) {
+ if ((spc && !is_space_on_vt(sel_pos(pe, unicode))) ||
+ (!spc && !inword(sel_pos(pe, unicode))))
+ break;
new_sel_end = pe;
- break;
- case TIOCL_SELWORD: /* word-by-word selection */
- spc = isspace(sel_pos(ps));
- for (new_sel_start = ps; ; ps -= 2)
- {
- if ((spc && !isspace(sel_pos(ps))) ||
- (!spc && !inword(sel_pos(ps))))
- break;
- new_sel_start = ps;
- if (!(ps % vc->vc_size_row))
- break;
- }
- spc = isspace(sel_pos(pe));
- for (new_sel_end = pe; ; pe += 2)
- {
- if ((spc && !isspace(sel_pos(pe))) ||
- (!spc && !inword(sel_pos(pe))))
- break;
- new_sel_end = pe;
- if (!((pe + 2) % vc->vc_size_row))
- break;
- }
- break;
- case TIOCL_SELLINE: /* line-by-line selection */
- new_sel_start = ps - ps % vc->vc_size_row;
- new_sel_end = pe + vc->vc_size_row
- - pe % vc->vc_size_row - 2;
- break;
- case TIOCL_SELPOINTER:
- highlight_pointer(pe);
- return 0;
- default:
- return -EINVAL;
+ if (!((pe + 2) % vc->vc_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;
+ break;
+ case TIOCL_SELPOINTER:
+ highlight_pointer(pe);
+ return 0;
+ default:
+ return -EINVAL;
}
/* remove the pointer */
@@ -258,74 +298,89 @@ int set_selection(const struct tiocl_selection __user *sel, struct tty_struct *t
/* select to end of line if on trailing space */
if (new_sel_end > new_sel_start &&
!atedge(new_sel_end, vc->vc_size_row) &&
- isspace(sel_pos(new_sel_end))) {
+ is_space_on_vt(sel_pos(new_sel_end, unicode))) {
for (pe = new_sel_end + 2; ; pe += 2)
- if (!isspace(sel_pos(pe)) ||
+ if (!is_space_on_vt(sel_pos(pe, unicode)) ||
atedge(pe, vc->vc_size_row))
break;
- if (isspace(sel_pos(pe)))
+ if (is_space_on_vt(sel_pos(pe, unicode)))
new_sel_end = pe;
}
- if (sel_start == -1) /* no current selection */
+ if (vc_sel.start == -1) /* no current selection */
highlight(new_sel_start, new_sel_end);
- else if (new_sel_start == sel_start)
+ else if (new_sel_start == vc_sel.start)
{
- if (new_sel_end == sel_end) /* no action required */
+ if (new_sel_end == vc_sel.end) /* no action required */
return 0;
- else if (new_sel_end > sel_end) /* extend to right */
- highlight(sel_end + 2, new_sel_end);
+ else if (new_sel_end > vc_sel.end) /* extend to right */
+ highlight(vc_sel.end + 2, new_sel_end);
else /* contract from right */
- highlight(new_sel_end + 2, sel_end);
+ highlight(new_sel_end + 2, vc_sel.end);
}
- else if (new_sel_end == sel_end)
+ else if (new_sel_end == vc_sel.end)
{
- if (new_sel_start < sel_start) /* extend to left */
- highlight(new_sel_start, sel_start - 2);
+ if (new_sel_start < vc_sel.start) /* extend to left */
+ highlight(new_sel_start, vc_sel.start - 2);
else /* contract from left */
- highlight(sel_start, new_sel_start - 2);
+ highlight(vc_sel.start, new_sel_start - 2);
}
else /* some other case; start selection from scratch */
{
clear_selection();
highlight(new_sel_start, new_sel_end);
}
- sel_start = new_sel_start;
- sel_end = new_sel_end;
+ vc_sel.start = new_sel_start;
+ vc_sel.end = new_sel_end;
- /* Allocate a new buffer before freeing the old one ... */
- multiplier = use_unicode ? 3 : 1; /* chars can take up to 3 bytes */
- bp = kmalloc(((sel_end-sel_start)/2+1)*multiplier, GFP_KERNEL);
- if (!bp) {
- printk(KERN_WARNING "selection: kmalloc() failed\n");
+ return vc_selection_store_chars(vc, unicode);
+}
+
+static int vc_selection(struct vc_data *vc, struct tiocl_selection *v,
+ struct tty_struct *tty)
+{
+ int ps, pe;
+
+ poke_blanked_console();
+
+ if (v->sel_mode == TIOCL_SELCLEAR) {
+ /* useful for screendump without selection highlights */
clear_selection();
- return -ENOMEM;
+ return 0;
}
- kfree(sel_buffer);
- sel_buffer = bp;
- obp = bp;
- for (i = sel_start; i <= sel_end; i += 2) {
- c = sel_pos(i);
- if (use_unicode)
- bp += store_utf8(c, bp);
- else
- *bp++ = c;
- if (!isspace(c))
- obp = bp;
- if (! ((i + 2) % vc->vc_size_row)) {
- /* strip trailing blanks from line and add newline,
- unless non-space at end of line. */
- if (obp != bp) {
- bp = obp;
- *bp++ = '\r';
- }
- obp = bp;
- }
+ /* Historically 0 => max value */
+ v->xs = umin(v->xs - 1, vc->vc_cols - 1);
+ v->ys = umin(v->ys - 1, vc->vc_rows - 1);
+ v->xe = umin(v->xe - 1, vc->vc_cols - 1);
+ v->ye = umin(v->ye - 1, vc->vc_rows - 1);
+
+ if (mouse_reporting() && (v->sel_mode & TIOCL_SELMOUSEREPORT)) {
+ mouse_report(tty, v->sel_mode & TIOCL_SELBUTTONMASK, v->xs,
+ v->ys);
+ return 0;
}
- sel_buffer_lth = bp - sel_buffer;
- return 0;
+
+ ps = v->ys * vc->vc_size_row + (v->xs << 1);
+ pe = v->ye * vc->vc_size_row + (v->xe << 1);
+ if (ps > pe) /* make vc_sel.start <= vc_sel.end */
+ swap(ps, pe);
+
+ if (vc_sel.cons != vc) {
+ clear_selection();
+ vc_sel.cons = vc;
+ }
+
+ return vc_do_selection(vc, v->sel_mode, ps, pe);
}
+int set_selection_kernel(struct tiocl_selection *v, struct tty_struct *tty)
+{
+ guard(mutex)(&vc_sel.lock);
+ guard(console_lock)();
+ return vc_selection(vc_cons[fg_console].d, v, tty);
+}
+EXPORT_SYMBOL_GPL(set_selection_kernel);
+
/* Insert the contents of the selection buffer into the
* queue of the tty associated with the current console.
* Invoked by ioctl().
@@ -337,32 +392,68 @@ int paste_selection(struct tty_struct *tty)
{
struct vc_data *vc = tty->driver_data;
int pasted = 0;
- unsigned int count;
+ size_t count;
struct tty_ldisc *ld;
DECLARE_WAITQUEUE(wait, current);
+ int ret = 0;
- console_lock();
- poke_blanked_console();
- console_unlock();
+ bool bp = vc->vc_bracketed_paste;
+ static const char bracketed_paste_start[] = "\033[200~";
+ static const char bracketed_paste_end[] = "\033[201~";
+ const char *bps = bp ? bracketed_paste_start : NULL;
+ const char *bpe = bp ? bracketed_paste_end : NULL;
+
+ scoped_guard(console_lock)
+ poke_blanked_console();
ld = tty_ldisc_ref_wait(tty);
+ if (!ld)
+ return -EIO; /* ldisc was hung up */
+ tty_buffer_lock_exclusive(&vc->port);
- /* FIXME: this is completely unsafe */
add_wait_queue(&vc->paste_wait, &wait);
- while (sel_buffer && sel_buffer_lth > pasted) {
+ mutex_lock(&vc_sel.lock);
+ while (vc_sel.buffer && (vc_sel.buf_len > pasted || bpe)) {
set_current_state(TASK_INTERRUPTIBLE);
- if (test_bit(TTY_THROTTLED, &tty->flags)) {
+ if (signal_pending(current)) {
+ ret = -EINTR;
+ break;
+ }
+ if (tty_throttled(tty)) {
+ mutex_unlock(&vc_sel.lock);
schedule();
+ mutex_lock(&vc_sel.lock);
continue;
}
- count = sel_buffer_lth - pasted;
- count = min(count, tty->receive_room);
- ld->ops->receive_buf(tty, sel_buffer + pasted, NULL, count);
- pasted += count;
+ __set_current_state(TASK_RUNNING);
+
+ if (bps) {
+ bps += tty_ldisc_receive_buf(ld, bps, NULL, strlen(bps));
+ if (*bps != '\0')
+ continue;
+ bps = NULL;
+ }
+
+ count = vc_sel.buf_len - pasted;
+ if (count) {
+ pasted += tty_ldisc_receive_buf(ld, vc_sel.buffer + pasted,
+ NULL, count);
+ if (vc_sel.buf_len > pasted)
+ continue;
+ }
+
+ if (bpe) {
+ bpe += tty_ldisc_receive_buf(ld, bpe, NULL, strlen(bpe));
+ if (*bpe == '\0')
+ bpe = NULL;
+ }
}
+ mutex_unlock(&vc_sel.lock);
remove_wait_queue(&vc->paste_wait, &wait);
__set_current_state(TASK_RUNNING);
+ tty_buffer_unlock_exclusive(&vc->port);
tty_ldisc_deref(ld);
- return 0;
+ return ret;
}
+EXPORT_SYMBOL_GPL(paste_selection);
diff --git a/drivers/tty/vt/ucs.c b/drivers/tty/vt/ucs.c
new file mode 100644
index 000000000000..03877485dfb7
--- /dev/null
+++ b/drivers/tty/vt/ucs.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ucs.c - Universal Character Set processing
+ */
+
+#include <linux/array_size.h>
+#include <linux/bsearch.h>
+#include <linux/consolemap.h>
+#include <linux/minmax.h>
+
+struct ucs_interval16 {
+ u16 first;
+ u16 last;
+};
+
+struct ucs_interval32 {
+ u32 first;
+ u32 last;
+};
+
+#include "ucs_width_table.h"
+
+static int interval16_cmp(const void *key, const void *element)
+{
+ u16 cp = *(u16 *)key;
+ const struct ucs_interval16 *entry = element;
+
+ if (cp < entry->first)
+ return -1;
+ if (cp > entry->last)
+ return 1;
+ return 0;
+}
+
+static int interval32_cmp(const void *key, const void *element)
+{
+ u32 cp = *(u32 *)key;
+ const struct ucs_interval32 *entry = element;
+
+ if (cp < entry->first)
+ return -1;
+ if (cp > entry->last)
+ return 1;
+ return 0;
+}
+
+static bool cp_in_range16(u16 cp, const struct ucs_interval16 *ranges, size_t size)
+{
+ if (cp < ranges[0].first || cp > ranges[size - 1].last)
+ return false;
+
+ return __inline_bsearch(&cp, ranges, size, sizeof(*ranges),
+ interval16_cmp) != NULL;
+}
+
+static bool cp_in_range32(u32 cp, const struct ucs_interval32 *ranges, size_t size)
+{
+ if (cp < ranges[0].first || cp > ranges[size - 1].last)
+ return false;
+
+ return __inline_bsearch(&cp, ranges, size, sizeof(*ranges),
+ interval32_cmp) != NULL;
+}
+
+#define UCS_IS_BMP(cp) ((cp) <= 0xffff)
+
+/**
+ * ucs_is_zero_width() - Determine if a Unicode code point is zero-width.
+ * @cp: Unicode code point (UCS-4)
+ *
+ * Return: true if the character is zero-width, false otherwise
+ */
+bool ucs_is_zero_width(u32 cp)
+{
+ if (UCS_IS_BMP(cp))
+ return cp_in_range16(cp, ucs_zero_width_bmp_ranges,
+ ARRAY_SIZE(ucs_zero_width_bmp_ranges));
+ else
+ return cp_in_range32(cp, ucs_zero_width_non_bmp_ranges,
+ ARRAY_SIZE(ucs_zero_width_non_bmp_ranges));
+}
+
+/**
+ * ucs_is_double_width() - Determine if a Unicode code point is double-width.
+ * @cp: Unicode code point (UCS-4)
+ *
+ * Return: true if the character is double-width, false otherwise
+ */
+bool ucs_is_double_width(u32 cp)
+{
+ if (UCS_IS_BMP(cp))
+ return cp_in_range16(cp, ucs_double_width_bmp_ranges,
+ ARRAY_SIZE(ucs_double_width_bmp_ranges));
+ else
+ return cp_in_range32(cp, ucs_double_width_non_bmp_ranges,
+ ARRAY_SIZE(ucs_double_width_non_bmp_ranges));
+}
+
+/*
+ * Structure for base with combining mark pairs and resulting recompositions.
+ * Using u16 to save space since all values are within BMP range.
+ */
+struct ucs_recomposition {
+ u16 base; /* base character */
+ u16 mark; /* combining mark */
+ u16 recomposed; /* corresponding recomposed character */
+};
+
+#include "ucs_recompose_table.h"
+
+struct compare_key {
+ u16 base;
+ u16 mark;
+};
+
+static int recomposition_cmp(const void *key, const void *element)
+{
+ const struct compare_key *search_key = key;
+ const struct ucs_recomposition *entry = element;
+
+ /* Compare base character first */
+ if (search_key->base < entry->base)
+ return -1;
+ if (search_key->base > entry->base)
+ return 1;
+
+ /* Base characters match, now compare combining character */
+ if (search_key->mark < entry->mark)
+ return -1;
+ if (search_key->mark > entry->mark)
+ return 1;
+
+ /* Both match */
+ return 0;
+}
+
+/**
+ * ucs_recompose() - Attempt to recompose two Unicode characters into a single character.
+ * @base: Base Unicode code point (UCS-4)
+ * @mark: Combining mark Unicode code point (UCS-4)
+ *
+ * Return: Recomposed Unicode code point, or 0 if no recomposition is possible
+ */
+u32 ucs_recompose(u32 base, u32 mark)
+{
+ /* Check if characters are within the range of our table */
+ if (base < UCS_RECOMPOSE_MIN_BASE || base > UCS_RECOMPOSE_MAX_BASE ||
+ mark < UCS_RECOMPOSE_MIN_MARK || mark > UCS_RECOMPOSE_MAX_MARK)
+ return 0;
+
+ struct compare_key key = { base, mark };
+ struct ucs_recomposition *result =
+ __inline_bsearch(&key, ucs_recomposition_table,
+ ARRAY_SIZE(ucs_recomposition_table),
+ sizeof(*ucs_recomposition_table),
+ recomposition_cmp);
+
+ return result ? result->recomposed : 0;
+}
+
+/*
+ * The fallback table structures implement a 2-level lookup.
+ */
+
+struct ucs_page_desc {
+ u8 page; /* Page index (high byte of code points) */
+ u8 count; /* Number of entries in this page */
+ u16 start; /* Start index in entries array */
+};
+
+struct ucs_page_entry {
+ u8 offset; /* Offset within page (0-255) */
+ u8 fallback; /* Fallback character or range start marker */
+};
+
+#include "ucs_fallback_table.h"
+
+static int ucs_page_desc_cmp(const void *key, const void *element)
+{
+ u8 page = *(u8 *)key;
+ const struct ucs_page_desc *entry = element;
+
+ if (page < entry->page)
+ return -1;
+ if (page > entry->page)
+ return 1;
+ return 0;
+}
+
+static int ucs_page_entry_cmp(const void *key, const void *element)
+{
+ u8 offset = *(u8 *)key;
+ const struct ucs_page_entry *entry = element;
+
+ if (offset < entry->offset)
+ return -1;
+ if (entry->fallback == UCS_PAGE_ENTRY_RANGE_MARKER) {
+ if (offset > entry[1].offset)
+ return 1;
+ } else {
+ if (offset > entry->offset)
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * ucs_get_fallback() - Get a substitution for the provided Unicode character
+ * @cp: Unicode code point (UCS-4)
+ *
+ * Get a simpler fallback character for the provided Unicode character.
+ * This is used for terminal display when corresponding glyph is unavailable.
+ * The substitution may not be as good as the actual glyph for the original
+ * character but still way more helpful than a squared question mark.
+ *
+ * Return: Fallback Unicode code point, or 0 if none is available
+ */
+u32 ucs_get_fallback(u32 cp)
+{
+ const struct ucs_page_desc *page;
+ const struct ucs_page_entry *entry;
+ u8 page_idx = cp >> 8, offset = cp;
+
+ if (!UCS_IS_BMP(cp))
+ return 0;
+
+ /*
+ * Full-width to ASCII mapping (covering all printable ASCII 33-126)
+ * 0xFF01 (!) to 0xFF5E (~) -> ASCII 33 (!) to 126 (~)
+ * We process them programmatically to reduce the table size.
+ */
+ if (cp >= 0xFF01 && cp <= 0xFF5E)
+ return cp - 0xFF01 + 33;
+
+ page = __inline_bsearch(&page_idx, ucs_fallback_pages,
+ ARRAY_SIZE(ucs_fallback_pages),
+ sizeof(*ucs_fallback_pages),
+ ucs_page_desc_cmp);
+ if (!page)
+ return 0;
+
+ entry = __inline_bsearch(&offset, ucs_fallback_entries + page->start,
+ page->count, sizeof(*ucs_fallback_entries),
+ ucs_page_entry_cmp);
+ if (!entry)
+ return 0;
+
+ if (entry->fallback == UCS_PAGE_ENTRY_RANGE_MARKER)
+ entry++;
+ return entry->fallback;
+}
diff --git a/drivers/tty/vt/ucs_fallback_table.h_shipped b/drivers/tty/vt/ucs_fallback_table.h_shipped
new file mode 100644
index 000000000000..2da5a8fe1cf1
--- /dev/null
+++ b/drivers/tty/vt/ucs_fallback_table.h_shipped
@@ -0,0 +1,3346 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ucs_fallback_table.h - Unicode character fallback table
+ *
+ * Auto-generated by gen_ucs_fallback_table.py
+ *
+ * Unicode Version: 16.0.0
+ * Unidecode Version: 1.3.8
+ *
+ * This file contains optimized tables that map complex Unicode characters
+ * to simpler fallback characters for terminal display when corresponding
+ * glyphs are unavailable.
+ */
+
+static const struct ucs_page_desc ucs_fallback_pages[] = {
+ { 0x00, 62, 0 },
+ { 0x01, 218, 62 },
+ { 0x02, 196, 280 },
+ { 0x03, 96, 476 },
+ { 0x04, 113, 572 },
+ { 0x05, 100, 685 },
+ { 0x06, 119, 785 },
+ { 0x07, 91, 904 },
+ { 0x09, 99, 995 },
+ { 0x0A, 78, 1094 },
+ { 0x0B, 79, 1172 },
+ { 0x0C, 85, 1251 },
+ { 0x0D, 73, 1336 },
+ { 0x0E, 83, 1409 },
+ { 0x0F, 69, 1492 },
+ { 0x10, 93, 1561 },
+ { 0x11, 51, 1654 },
+ { 0x13, 22, 1705 },
+ { 0x14, 30, 1727 },
+ { 0x15, 17, 1757 },
+ { 0x16, 81, 1774 },
+ { 0x17, 47, 1855 },
+ { 0x18, 96, 1902 },
+ { 0x1D, 105, 1998 },
+ { 0x1E, 246, 2103 },
+ { 0x1F, 94, 2349 },
+ { 0x20, 107, 2443 },
+ { 0x21, 136, 2550 },
+ { 0x22, 34, 2686 },
+ { 0x23, 4, 2720 },
+ { 0x24, 72, 2724 },
+ { 0x25, 60, 2796 },
+ { 0x26, 6, 2856 },
+ { 0x27, 18, 2862 },
+ { 0x28, 64, 2880 },
+ { 0x29, 1, 2944 },
+ { 0x2C, 15, 2945 },
+ { 0x2E, 29, 2960 },
+ { 0x30, 53, 2989 },
+ { 0x31, 50, 3042 },
+ { 0x32, 5, 3092 },
+ { 0xA0, 4, 3097 },
+ { 0xC5, 2, 3101 },
+ { 0xC6, 2, 3103 },
+ { 0xC7, 1, 3105 },
+ { 0xFB, 35, 3106 },
+ { 0xFE, 37, 3141 },
+ { 0xFF, 50, 3178 },
+};
+
+/* Page entries array (referenced by page descriptors) */
+static const struct ucs_page_entry ucs_fallback_entries[] = {
+ /* Entries for page 0x00 */
+ { 0xA0, 0x20 }, /* NO-BREAK SPACE -> ' ' */
+ { 0xA1, 0x21 }, /* INVERTED EXCLAMATION MARK -> '!' */
+ { 0xA2, 0x63 }, /* CENT SIGN -> 'c' */
+ { 0xA3, 0x4C }, /* POUND SIGN -> 'L' */
+ { 0xA5, 0x59 }, /* YEN SIGN -> 'Y' */
+ { 0xA6, 0x7C }, /* BROKEN BAR -> '|' */
+ { 0xA7, 0x53 }, /* SECTION SIGN -> 'S' */
+ { 0xA8, 0x22 }, /* DIAERESIS -> '"' */
+ { 0xA9, 0x43 }, /* COPYRIGHT SIGN -> 'C' */
+ { 0xAA, 0x61 }, /* FEMININE ORDINAL INDICATOR -> 'a' */
+ { 0xAB, 0x3C }, /* LEFT-POINTING DOUBLE ANGLE QUOTATION MARK -> '<' */
+ { 0xAC, 0x21 }, /* NOT SIGN -> '!' */
+ { 0xAE, 0x52 }, /* REGISTERED SIGN -> 'R' */
+ { 0xAF, 0x2D }, /* MACRON -> '-' */
+ { 0xB0, 0x6F }, /* DEGREE SIGN -> 'o' */
+ { 0xB2, 0x32 }, /* SUPERSCRIPT TWO -> '2' */
+ { 0xB3, 0x33 }, /* SUPERSCRIPT THREE -> '3' */
+ { 0xB4, 0x27 }, /* ACUTE ACCENT -> ''' */
+ { 0xB5, 0x75 }, /* MICRO SIGN -> 'u' */
+ { 0xB6, 0x50 }, /* PILCROW SIGN -> 'P' */
+ { 0xB7, 0x2A }, /* MIDDLE DOT -> '*' */
+ { 0xB8, 0x2C }, /* CEDILLA -> ',' */
+ { 0xB9, 0x31 }, /* SUPERSCRIPT ONE -> '1' */
+ { 0xBA, 0x6F }, /* MASCULINE ORDINAL INDICATOR -> 'o' */
+ { 0xBB, 0x3E }, /* RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -> '>' */
+ { 0xBF, 0x3F }, /* INVERTED QUESTION MARK -> '?' */
+ { 0xC0, 0x00 }, /* LATIN CAPITAL LETTER A WITH GRAVE -> ... */
+ { 0xC5, 0x41 }, /* LATIN CAPITAL LETTER A WITH RING ABOVE -> 'A' */
+ { 0xC6, 0x45 }, /* LATIN CAPITAL LETTER AE -> 'E' */
+ { 0xC7, 0x43 }, /* LATIN CAPITAL LETTER C WITH CEDILLA -> 'C' */
+ { 0xC8, 0x00 }, /* LATIN CAPITAL LETTER E WITH GRAVE -> ... */
+ { 0xCB, 0x45 }, /* LATIN CAPITAL LETTER E WITH DIAERESIS -> 'E' */
+ { 0xCC, 0x00 }, /* LATIN CAPITAL LETTER I WITH GRAVE -> ... */
+ { 0xCF, 0x49 }, /* LATIN CAPITAL LETTER I WITH DIAERESIS -> 'I' */
+ { 0xD0, 0x44 }, /* LATIN CAPITAL LETTER ETH -> 'D' */
+ { 0xD1, 0x4E }, /* LATIN CAPITAL LETTER N WITH TILDE -> 'N' */
+ { 0xD2, 0x00 }, /* LATIN CAPITAL LETTER O WITH GRAVE -> ... */
+ { 0xD6, 0x4F }, /* LATIN CAPITAL LETTER O WITH DIAERESIS -> 'O' */
+ { 0xD7, 0x78 }, /* MULTIPLICATION SIGN -> 'x' */
+ { 0xD8, 0x4F }, /* LATIN CAPITAL LETTER O WITH STROKE -> 'O' */
+ { 0xD9, 0x00 }, /* LATIN CAPITAL LETTER U WITH GRAVE -> ... */
+ { 0xDC, 0x55 }, /* LATIN CAPITAL LETTER U WITH DIAERESIS -> 'U' */
+ { 0xDD, 0x59 }, /* LATIN CAPITAL LETTER Y WITH ACUTE -> 'Y' */
+ { 0xDF, 0x73 }, /* LATIN SMALL LETTER SHARP S -> 's' */
+ { 0xE0, 0x00 }, /* LATIN SMALL LETTER A WITH GRAVE -> ... */
+ { 0xE5, 0x61 }, /* LATIN SMALL LETTER A WITH RING ABOVE -> 'a' */
+ { 0xE6, 0x65 }, /* LATIN SMALL LETTER AE -> 'e' */
+ { 0xE7, 0x63 }, /* LATIN SMALL LETTER C WITH CEDILLA -> 'c' */
+ { 0xE8, 0x00 }, /* LATIN SMALL LETTER E WITH GRAVE -> ... */
+ { 0xEB, 0x65 }, /* LATIN SMALL LETTER E WITH DIAERESIS -> 'e' */
+ { 0xEC, 0x00 }, /* LATIN SMALL LETTER I WITH GRAVE -> ... */
+ { 0xEF, 0x69 }, /* LATIN SMALL LETTER I WITH DIAERESIS -> 'i' */
+ { 0xF0, 0x64 }, /* LATIN SMALL LETTER ETH -> 'd' */
+ { 0xF1, 0x6E }, /* LATIN SMALL LETTER N WITH TILDE -> 'n' */
+ { 0xF2, 0x00 }, /* LATIN SMALL LETTER O WITH GRAVE -> ... */
+ { 0xF6, 0x6F }, /* LATIN SMALL LETTER O WITH DIAERESIS -> 'o' */
+ { 0xF7, 0x2F }, /* DIVISION SIGN -> '/' */
+ { 0xF8, 0x6F }, /* LATIN SMALL LETTER O WITH STROKE -> 'o' */
+ { 0xF9, 0x00 }, /* LATIN SMALL LETTER U WITH GRAVE -> ... */
+ { 0xFC, 0x75 }, /* LATIN SMALL LETTER U WITH DIAERESIS -> 'u' */
+ { 0xFD, 0x79 }, /* LATIN SMALL LETTER Y WITH ACUTE -> 'y' */
+ { 0xFF, 0x79 }, /* LATIN SMALL LETTER Y WITH DIAERESIS -> 'y' */
+ /* Entries for page 0x01 */
+ { 0x00, 0x41 }, /* LATIN CAPITAL LETTER A WITH MACRON -> 'A' */
+ { 0x01, 0x61 }, /* LATIN SMALL LETTER A WITH MACRON -> 'a' */
+ { 0x02, 0x41 }, /* LATIN CAPITAL LETTER A WITH BREVE -> 'A' */
+ { 0x03, 0x61 }, /* LATIN SMALL LETTER A WITH BREVE -> 'a' */
+ { 0x04, 0x41 }, /* LATIN CAPITAL LETTER A WITH OGONEK -> 'A' */
+ { 0x05, 0x61 }, /* LATIN SMALL LETTER A WITH OGONEK -> 'a' */
+ { 0x06, 0x43 }, /* LATIN CAPITAL LETTER C WITH ACUTE -> 'C' */
+ { 0x07, 0x63 }, /* LATIN SMALL LETTER C WITH ACUTE -> 'c' */
+ { 0x08, 0x43 }, /* LATIN CAPITAL LETTER C WITH CIRCUMFLEX -> 'C' */
+ { 0x09, 0x63 }, /* LATIN SMALL LETTER C WITH CIRCUMFLEX -> 'c' */
+ { 0x0A, 0x43 }, /* LATIN CAPITAL LETTER C WITH DOT ABOVE -> 'C' */
+ { 0x0B, 0x63 }, /* LATIN SMALL LETTER C WITH DOT ABOVE -> 'c' */
+ { 0x0C, 0x43 }, /* LATIN CAPITAL LETTER C WITH CARON -> 'C' */
+ { 0x0D, 0x63 }, /* LATIN SMALL LETTER C WITH CARON -> 'c' */
+ { 0x0E, 0x44 }, /* LATIN CAPITAL LETTER D WITH CARON -> 'D' */
+ { 0x0F, 0x64 }, /* LATIN SMALL LETTER D WITH CARON -> 'd' */
+ { 0x10, 0x44 }, /* LATIN CAPITAL LETTER D WITH STROKE -> 'D' */
+ { 0x11, 0x64 }, /* LATIN SMALL LETTER D WITH STROKE -> 'd' */
+ { 0x12, 0x45 }, /* LATIN CAPITAL LETTER E WITH MACRON -> 'E' */
+ { 0x13, 0x65 }, /* LATIN SMALL LETTER E WITH MACRON -> 'e' */
+ { 0x14, 0x45 }, /* LATIN CAPITAL LETTER E WITH BREVE -> 'E' */
+ { 0x15, 0x65 }, /* LATIN SMALL LETTER E WITH BREVE -> 'e' */
+ { 0x16, 0x45 }, /* LATIN CAPITAL LETTER E WITH DOT ABOVE -> 'E' */
+ { 0x17, 0x65 }, /* LATIN SMALL LETTER E WITH DOT ABOVE -> 'e' */
+ { 0x18, 0x45 }, /* LATIN CAPITAL LETTER E WITH OGONEK -> 'E' */
+ { 0x19, 0x65 }, /* LATIN SMALL LETTER E WITH OGONEK -> 'e' */
+ { 0x1A, 0x45 }, /* LATIN CAPITAL LETTER E WITH CARON -> 'E' */
+ { 0x1B, 0x65 }, /* LATIN SMALL LETTER E WITH CARON -> 'e' */
+ { 0x1C, 0x47 }, /* LATIN CAPITAL LETTER G WITH CIRCUMFLEX -> 'G' */
+ { 0x1D, 0x67 }, /* LATIN SMALL LETTER G WITH CIRCUMFLEX -> 'g' */
+ { 0x1E, 0x47 }, /* LATIN CAPITAL LETTER G WITH BREVE -> 'G' */
+ { 0x1F, 0x67 }, /* LATIN SMALL LETTER G WITH BREVE -> 'g' */
+ { 0x20, 0x47 }, /* LATIN CAPITAL LETTER G WITH DOT ABOVE -> 'G' */
+ { 0x21, 0x67 }, /* LATIN SMALL LETTER G WITH DOT ABOVE -> 'g' */
+ { 0x22, 0x47 }, /* LATIN CAPITAL LETTER G WITH CEDILLA -> 'G' */
+ { 0x23, 0x67 }, /* LATIN SMALL LETTER G WITH CEDILLA -> 'g' */
+ { 0x24, 0x48 }, /* LATIN CAPITAL LETTER H WITH CIRCUMFLEX -> 'H' */
+ { 0x25, 0x68 }, /* LATIN SMALL LETTER H WITH CIRCUMFLEX -> 'h' */
+ { 0x26, 0x48 }, /* LATIN CAPITAL LETTER H WITH STROKE -> 'H' */
+ { 0x27, 0x68 }, /* LATIN SMALL LETTER H WITH STROKE -> 'h' */
+ { 0x28, 0x49 }, /* LATIN CAPITAL LETTER I WITH TILDE -> 'I' */
+ { 0x29, 0x69 }, /* LATIN SMALL LETTER I WITH TILDE -> 'i' */
+ { 0x2A, 0x49 }, /* LATIN CAPITAL LETTER I WITH MACRON -> 'I' */
+ { 0x2B, 0x69 }, /* LATIN SMALL LETTER I WITH MACRON -> 'i' */
+ { 0x2C, 0x49 }, /* LATIN CAPITAL LETTER I WITH BREVE -> 'I' */
+ { 0x2D, 0x69 }, /* LATIN SMALL LETTER I WITH BREVE -> 'i' */
+ { 0x2E, 0x49 }, /* LATIN CAPITAL LETTER I WITH OGONEK -> 'I' */
+ { 0x2F, 0x69 }, /* LATIN SMALL LETTER I WITH OGONEK -> 'i' */
+ { 0x30, 0x49 }, /* LATIN CAPITAL LETTER I WITH DOT ABOVE -> 'I' */
+ { 0x31, 0x69 }, /* LATIN SMALL LETTER DOTLESS I -> 'i' */
+ { 0x34, 0x4A }, /* LATIN CAPITAL LETTER J WITH CIRCUMFLEX -> 'J' */
+ { 0x35, 0x6A }, /* LATIN SMALL LETTER J WITH CIRCUMFLEX -> 'j' */
+ { 0x36, 0x4B }, /* LATIN CAPITAL LETTER K WITH CEDILLA -> 'K' */
+ { 0x37, 0x6B }, /* LATIN SMALL LETTER K WITH CEDILLA -> 'k' */
+ { 0x38, 0x6B }, /* LATIN SMALL LETTER KRA -> 'k' */
+ { 0x39, 0x4C }, /* LATIN CAPITAL LETTER L WITH ACUTE -> 'L' */
+ { 0x3A, 0x6C }, /* LATIN SMALL LETTER L WITH ACUTE -> 'l' */
+ { 0x3B, 0x4C }, /* LATIN CAPITAL LETTER L WITH CEDILLA -> 'L' */
+ { 0x3C, 0x6C }, /* LATIN SMALL LETTER L WITH CEDILLA -> 'l' */
+ { 0x3D, 0x4C }, /* LATIN CAPITAL LETTER L WITH CARON -> 'L' */
+ { 0x3E, 0x6C }, /* LATIN SMALL LETTER L WITH CARON -> 'l' */
+ { 0x3F, 0x4C }, /* LATIN CAPITAL LETTER L WITH MIDDLE DOT -> 'L' */
+ { 0x40, 0x6C }, /* LATIN SMALL LETTER L WITH MIDDLE DOT -> 'l' */
+ { 0x41, 0x4C }, /* LATIN CAPITAL LETTER L WITH STROKE -> 'L' */
+ { 0x42, 0x6C }, /* LATIN SMALL LETTER L WITH STROKE -> 'l' */
+ { 0x43, 0x4E }, /* LATIN CAPITAL LETTER N WITH ACUTE -> 'N' */
+ { 0x44, 0x6E }, /* LATIN SMALL LETTER N WITH ACUTE -> 'n' */
+ { 0x45, 0x4E }, /* LATIN CAPITAL LETTER N WITH CEDILLA -> 'N' */
+ { 0x46, 0x6E }, /* LATIN SMALL LETTER N WITH CEDILLA -> 'n' */
+ { 0x47, 0x4E }, /* LATIN CAPITAL LETTER N WITH CARON -> 'N' */
+ { 0x48, 0x6E }, /* LATIN SMALL LETTER N WITH CARON -> 'n' */
+ { 0x4C, 0x4F }, /* LATIN CAPITAL LETTER O WITH MACRON -> 'O' */
+ { 0x4D, 0x6F }, /* LATIN SMALL LETTER O WITH MACRON -> 'o' */
+ { 0x4E, 0x4F }, /* LATIN CAPITAL LETTER O WITH BREVE -> 'O' */
+ { 0x4F, 0x6F }, /* LATIN SMALL LETTER O WITH BREVE -> 'o' */
+ { 0x50, 0x4F }, /* LATIN CAPITAL LETTER O WITH DOUBLE ACUTE -> 'O' */
+ { 0x51, 0x6F }, /* LATIN SMALL LETTER O WITH DOUBLE ACUTE -> 'o' */
+ { 0x52, 0x45 }, /* LATIN CAPITAL LIGATURE OE -> 'E' */
+ { 0x53, 0x65 }, /* LATIN SMALL LIGATURE OE -> 'e' */
+ { 0x54, 0x52 }, /* LATIN CAPITAL LETTER R WITH ACUTE -> 'R' */
+ { 0x55, 0x72 }, /* LATIN SMALL LETTER R WITH ACUTE -> 'r' */
+ { 0x56, 0x52 }, /* LATIN CAPITAL LETTER R WITH CEDILLA -> 'R' */
+ { 0x57, 0x72 }, /* LATIN SMALL LETTER R WITH CEDILLA -> 'r' */
+ { 0x58, 0x52 }, /* LATIN CAPITAL LETTER R WITH CARON -> 'R' */
+ { 0x59, 0x72 }, /* LATIN SMALL LETTER R WITH CARON -> 'r' */
+ { 0x5A, 0x53 }, /* LATIN CAPITAL LETTER S WITH ACUTE -> 'S' */
+ { 0x5B, 0x73 }, /* LATIN SMALL LETTER S WITH ACUTE -> 's' */
+ { 0x5C, 0x53 }, /* LATIN CAPITAL LETTER S WITH CIRCUMFLEX -> 'S' */
+ { 0x5D, 0x73 }, /* LATIN SMALL LETTER S WITH CIRCUMFLEX -> 's' */
+ { 0x5E, 0x53 }, /* LATIN CAPITAL LETTER S WITH CEDILLA -> 'S' */
+ { 0x5F, 0x73 }, /* LATIN SMALL LETTER S WITH CEDILLA -> 's' */
+ { 0x60, 0x53 }, /* LATIN CAPITAL LETTER S WITH CARON -> 'S' */
+ { 0x61, 0x73 }, /* LATIN SMALL LETTER S WITH CARON -> 's' */
+ { 0x62, 0x54 }, /* LATIN CAPITAL LETTER T WITH CEDILLA -> 'T' */
+ { 0x63, 0x74 }, /* LATIN SMALL LETTER T WITH CEDILLA -> 't' */
+ { 0x64, 0x54 }, /* LATIN CAPITAL LETTER T WITH CARON -> 'T' */
+ { 0x65, 0x74 }, /* LATIN SMALL LETTER T WITH CARON -> 't' */
+ { 0x66, 0x54 }, /* LATIN CAPITAL LETTER T WITH STROKE -> 'T' */
+ { 0x67, 0x74 }, /* LATIN SMALL LETTER T WITH STROKE -> 't' */
+ { 0x68, 0x55 }, /* LATIN CAPITAL LETTER U WITH TILDE -> 'U' */
+ { 0x69, 0x75 }, /* LATIN SMALL LETTER U WITH TILDE -> 'u' */
+ { 0x6A, 0x55 }, /* LATIN CAPITAL LETTER U WITH MACRON -> 'U' */
+ { 0x6B, 0x75 }, /* LATIN SMALL LETTER U WITH MACRON -> 'u' */
+ { 0x6C, 0x55 }, /* LATIN CAPITAL LETTER U WITH BREVE -> 'U' */
+ { 0x6D, 0x75 }, /* LATIN SMALL LETTER U WITH BREVE -> 'u' */
+ { 0x6E, 0x55 }, /* LATIN CAPITAL LETTER U WITH RING ABOVE -> 'U' */
+ { 0x6F, 0x75 }, /* LATIN SMALL LETTER U WITH RING ABOVE -> 'u' */
+ { 0x70, 0x55 }, /* LATIN CAPITAL LETTER U WITH DOUBLE ACUTE -> 'U' */
+ { 0x71, 0x75 }, /* LATIN SMALL LETTER U WITH DOUBLE ACUTE -> 'u' */
+ { 0x72, 0x55 }, /* LATIN CAPITAL LETTER U WITH OGONEK -> 'U' */
+ { 0x73, 0x75 }, /* LATIN SMALL LETTER U WITH OGONEK -> 'u' */
+ { 0x74, 0x57 }, /* LATIN CAPITAL LETTER W WITH CIRCUMFLEX -> 'W' */
+ { 0x75, 0x77 }, /* LATIN SMALL LETTER W WITH CIRCUMFLEX -> 'w' */
+ { 0x76, 0x59 }, /* LATIN CAPITAL LETTER Y WITH CIRCUMFLEX -> 'Y' */
+ { 0x77, 0x79 }, /* LATIN SMALL LETTER Y WITH CIRCUMFLEX -> 'y' */
+ { 0x78, 0x59 }, /* LATIN CAPITAL LETTER Y WITH DIAERESIS -> 'Y' */
+ { 0x79, 0x5A }, /* LATIN CAPITAL LETTER Z WITH ACUTE -> 'Z' */
+ { 0x7A, 0x7A }, /* LATIN SMALL LETTER Z WITH ACUTE -> 'z' */
+ { 0x7B, 0x5A }, /* LATIN CAPITAL LETTER Z WITH DOT ABOVE -> 'Z' */
+ { 0x7C, 0x7A }, /* LATIN SMALL LETTER Z WITH DOT ABOVE -> 'z' */
+ { 0x7D, 0x5A }, /* LATIN CAPITAL LETTER Z WITH CARON -> 'Z' */
+ { 0x7E, 0x7A }, /* LATIN SMALL LETTER Z WITH CARON -> 'z' */
+ { 0x7F, 0x73 }, /* LATIN SMALL LETTER LONG S -> 's' */
+ { 0x80, 0x62 }, /* LATIN SMALL LETTER B WITH STROKE -> 'b' */
+ { 0x81, 0x42 }, /* LATIN CAPITAL LETTER B WITH HOOK -> 'B' */
+ { 0x82, 0x42 }, /* LATIN CAPITAL LETTER B WITH TOPBAR -> 'B' */
+ { 0x83, 0x62 }, /* LATIN SMALL LETTER B WITH TOPBAR -> 'b' */
+ { 0x84, 0x36 }, /* LATIN CAPITAL LETTER TONE SIX -> '6' */
+ { 0x85, 0x36 }, /* LATIN SMALL LETTER TONE SIX -> '6' */
+ { 0x86, 0x4F }, /* LATIN CAPITAL LETTER OPEN O -> 'O' */
+ { 0x87, 0x43 }, /* LATIN CAPITAL LETTER C WITH HOOK -> 'C' */
+ { 0x88, 0x63 }, /* LATIN SMALL LETTER C WITH HOOK -> 'c' */
+ { 0x89, 0x00 }, /* LATIN CAPITAL LETTER AFRICAN D -> ... */
+ { 0x8B, 0x44 }, /* LATIN CAPITAL LETTER D WITH TOPBAR -> 'D' */
+ { 0x8C, 0x64 }, /* LATIN SMALL LETTER D WITH TOPBAR -> 'd' */
+ { 0x8D, 0x64 }, /* LATIN SMALL LETTER TURNED DELTA -> 'd' */
+ { 0x8E, 0x33 }, /* LATIN CAPITAL LETTER REVERSED E -> '3' */
+ { 0x8F, 0x40 }, /* LATIN CAPITAL LETTER SCHWA -> '@' */
+ { 0x90, 0x45 }, /* LATIN CAPITAL LETTER OPEN E -> 'E' */
+ { 0x91, 0x46 }, /* LATIN CAPITAL LETTER F WITH HOOK -> 'F' */
+ { 0x92, 0x66 }, /* LATIN SMALL LETTER F WITH HOOK -> 'f' */
+ { 0x93, 0x47 }, /* LATIN CAPITAL LETTER G WITH HOOK -> 'G' */
+ { 0x94, 0x47 }, /* LATIN CAPITAL LETTER GAMMA -> 'G' */
+ { 0x96, 0x49 }, /* LATIN CAPITAL LETTER IOTA -> 'I' */
+ { 0x97, 0x49 }, /* LATIN CAPITAL LETTER I WITH STROKE -> 'I' */
+ { 0x98, 0x4B }, /* LATIN CAPITAL LETTER K WITH HOOK -> 'K' */
+ { 0x99, 0x6B }, /* LATIN SMALL LETTER K WITH HOOK -> 'k' */
+ { 0x9A, 0x6C }, /* LATIN SMALL LETTER L WITH BAR -> 'l' */
+ { 0x9B, 0x6C }, /* LATIN SMALL LETTER LAMBDA WITH STROKE -> 'l' */
+ { 0x9C, 0x57 }, /* LATIN CAPITAL LETTER TURNED M -> 'W' */
+ { 0x9D, 0x4E }, /* LATIN CAPITAL LETTER N WITH LEFT HOOK -> 'N' */
+ { 0x9E, 0x6E }, /* LATIN SMALL LETTER N WITH LONG RIGHT LEG -> 'n' */
+ { 0x9F, 0x4F }, /* LATIN CAPITAL LETTER O WITH MIDDLE TILDE -> 'O' */
+ { 0xA0, 0x4F }, /* LATIN CAPITAL LETTER O WITH HORN -> 'O' */
+ { 0xA1, 0x6F }, /* LATIN SMALL LETTER O WITH HORN -> 'o' */
+ { 0xA4, 0x50 }, /* LATIN CAPITAL LETTER P WITH HOOK -> 'P' */
+ { 0xA5, 0x70 }, /* LATIN SMALL LETTER P WITH HOOK -> 'p' */
+ { 0xA7, 0x32 }, /* LATIN CAPITAL LETTER TONE TWO -> '2' */
+ { 0xA8, 0x32 }, /* LATIN SMALL LETTER TONE TWO -> '2' */
+ { 0xAB, 0x74 }, /* LATIN SMALL LETTER T WITH PALATAL HOOK -> 't' */
+ { 0xAC, 0x54 }, /* LATIN CAPITAL LETTER T WITH HOOK -> 'T' */
+ { 0xAD, 0x74 }, /* LATIN SMALL LETTER T WITH HOOK -> 't' */
+ { 0xAE, 0x54 }, /* LATIN CAPITAL LETTER T WITH RETROFLEX HOOK -> 'T' */
+ { 0xAF, 0x55 }, /* LATIN CAPITAL LETTER U WITH HORN -> 'U' */
+ { 0xB0, 0x75 }, /* LATIN SMALL LETTER U WITH HORN -> 'u' */
+ { 0xB1, 0x59 }, /* LATIN CAPITAL LETTER UPSILON -> 'Y' */
+ { 0xB2, 0x56 }, /* LATIN CAPITAL LETTER V WITH HOOK -> 'V' */
+ { 0xB3, 0x59 }, /* LATIN CAPITAL LETTER Y WITH HOOK -> 'Y' */
+ { 0xB4, 0x79 }, /* LATIN SMALL LETTER Y WITH HOOK -> 'y' */
+ { 0xB5, 0x5A }, /* LATIN CAPITAL LETTER Z WITH STROKE -> 'Z' */
+ { 0xB6, 0x7A }, /* LATIN SMALL LETTER Z WITH STROKE -> 'z' */
+ { 0xBB, 0x32 }, /* LATIN LETTER TWO WITH STROKE -> '2' */
+ { 0xBC, 0x35 }, /* LATIN CAPITAL LETTER TONE FIVE -> '5' */
+ { 0xBD, 0x35 }, /* LATIN SMALL LETTER TONE FIVE -> '5' */
+ { 0xBF, 0x77 }, /* LATIN LETTER WYNN -> 'w' */
+ { 0xC0, 0x7C }, /* LATIN LETTER DENTAL CLICK -> '|' */
+ { 0xC3, 0x21 }, /* LATIN LETTER RETROFLEX CLICK -> '!' */
+ { 0xCD, 0x41 }, /* LATIN CAPITAL LETTER A WITH CARON -> 'A' */
+ { 0xCE, 0x61 }, /* LATIN SMALL LETTER A WITH CARON -> 'a' */
+ { 0xCF, 0x49 }, /* LATIN CAPITAL LETTER I WITH CARON -> 'I' */
+ { 0xD0, 0x69 }, /* LATIN SMALL LETTER I WITH CARON -> 'i' */
+ { 0xD1, 0x4F }, /* LATIN CAPITAL LETTER O WITH CARON -> 'O' */
+ { 0xD2, 0x6F }, /* LATIN SMALL LETTER O WITH CARON -> 'o' */
+ { 0xD3, 0x55 }, /* LATIN CAPITAL LETTER U WITH CARON -> 'U' */
+ { 0xD4, 0x75 }, /* LATIN SMALL LETTER U WITH CARON -> 'u' */
+ { 0xD5, 0x55 }, /* LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON -> 'U' */
+ { 0xD6, 0x75 }, /* LATIN SMALL LETTER U WITH DIAERESIS AND MACRON -> 'u' */
+ { 0xD7, 0x55 }, /* LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE -> 'U' */
+ { 0xD8, 0x75 }, /* LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE -> 'u' */
+ { 0xD9, 0x55 }, /* LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON -> 'U' */
+ { 0xDA, 0x75 }, /* LATIN SMALL LETTER U WITH DIAERESIS AND CARON -> 'u' */
+ { 0xDB, 0x55 }, /* LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE -> 'U' */
+ { 0xDC, 0x75 }, /* LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE -> 'u' */
+ { 0xDD, 0x40 }, /* LATIN SMALL LETTER TURNED E -> '@' */
+ { 0xDE, 0x41 }, /* LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON -> 'A' */
+ { 0xDF, 0x61 }, /* LATIN SMALL LETTER A WITH DIAERESIS AND MACRON -> 'a' */
+ { 0xE0, 0x41 }, /* LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON -> 'A' */
+ { 0xE1, 0x61 }, /* LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON -> 'a' */
+ { 0xE4, 0x47 }, /* LATIN CAPITAL LETTER G WITH STROKE -> 'G' */
+ { 0xE5, 0x67 }, /* LATIN SMALL LETTER G WITH STROKE -> 'g' */
+ { 0xE6, 0x47 }, /* LATIN CAPITAL LETTER G WITH CARON -> 'G' */
+ { 0xE7, 0x67 }, /* LATIN SMALL LETTER G WITH CARON -> 'g' */
+ { 0xE8, 0x4B }, /* LATIN CAPITAL LETTER K WITH CARON -> 'K' */
+ { 0xE9, 0x6B }, /* LATIN SMALL LETTER K WITH CARON -> 'k' */
+ { 0xEA, 0x4F }, /* LATIN CAPITAL LETTER O WITH OGONEK -> 'O' */
+ { 0xEB, 0x6F }, /* LATIN SMALL LETTER O WITH OGONEK -> 'o' */
+ { 0xEC, 0x4F }, /* LATIN CAPITAL LETTER O WITH OGONEK AND MACRON -> 'O' */
+ { 0xED, 0x6F }, /* LATIN SMALL LETTER O WITH OGONEK AND MACRON -> 'o' */
+ { 0xF0, 0x6A }, /* LATIN SMALL LETTER J WITH CARON -> 'j' */
+ { 0xF4, 0x47 }, /* LATIN CAPITAL LETTER G WITH ACUTE -> 'G' */
+ { 0xF5, 0x67 }, /* LATIN SMALL LETTER G WITH ACUTE -> 'g' */
+ { 0xF7, 0x57 }, /* LATIN CAPITAL LETTER WYNN -> 'W' */
+ { 0xF8, 0x4E }, /* LATIN CAPITAL LETTER N WITH GRAVE -> 'N' */
+ { 0xF9, 0x6E }, /* LATIN SMALL LETTER N WITH GRAVE -> 'n' */
+ { 0xFA, 0x41 }, /* LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE -> 'A' */
+ { 0xFB, 0x61 }, /* LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE -> 'a' */
+ { 0xFE, 0x4F }, /* LATIN CAPITAL LETTER O WITH STROKE AND ACUTE -> 'O' */
+ { 0xFF, 0x6F }, /* LATIN SMALL LETTER O WITH STROKE AND ACUTE -> 'o' */
+ /* Entries for page 0x02 */
+ { 0x00, 0x41 }, /* LATIN CAPITAL LETTER A WITH DOUBLE GRAVE -> 'A' */
+ { 0x01, 0x61 }, /* LATIN SMALL LETTER A WITH DOUBLE GRAVE -> 'a' */
+ { 0x02, 0x41 }, /* LATIN CAPITAL LETTER A WITH INVERTED BREVE -> 'A' */
+ { 0x03, 0x61 }, /* LATIN SMALL LETTER A WITH INVERTED BREVE -> 'a' */
+ { 0x04, 0x45 }, /* LATIN CAPITAL LETTER E WITH DOUBLE GRAVE -> 'E' */
+ { 0x05, 0x65 }, /* LATIN SMALL LETTER E WITH DOUBLE GRAVE -> 'e' */
+ { 0x06, 0x45 }, /* LATIN CAPITAL LETTER E WITH INVERTED BREVE -> 'E' */
+ { 0x07, 0x65 }, /* LATIN SMALL LETTER E WITH INVERTED BREVE -> 'e' */
+ { 0x08, 0x49 }, /* LATIN CAPITAL LETTER I WITH DOUBLE GRAVE -> 'I' */
+ { 0x09, 0x69 }, /* LATIN SMALL LETTER I WITH DOUBLE GRAVE -> 'i' */
+ { 0x0A, 0x49 }, /* LATIN CAPITAL LETTER I WITH INVERTED BREVE -> 'I' */
+ { 0x0B, 0x69 }, /* LATIN SMALL LETTER I WITH INVERTED BREVE -> 'i' */
+ { 0x0C, 0x4F }, /* LATIN CAPITAL LETTER O WITH DOUBLE GRAVE -> 'O' */
+ { 0x0D, 0x6F }, /* LATIN SMALL LETTER O WITH DOUBLE GRAVE -> 'o' */
+ { 0x0E, 0x4F }, /* LATIN CAPITAL LETTER O WITH INVERTED BREVE -> 'O' */
+ { 0x0F, 0x6F }, /* LATIN SMALL LETTER O WITH INVERTED BREVE -> 'o' */
+ { 0x10, 0x52 }, /* LATIN CAPITAL LETTER R WITH DOUBLE GRAVE -> 'R' */
+ { 0x11, 0x72 }, /* LATIN SMALL LETTER R WITH DOUBLE GRAVE -> 'r' */
+ { 0x12, 0x52 }, /* LATIN CAPITAL LETTER R WITH INVERTED BREVE -> 'R' */
+ { 0x13, 0x72 }, /* LATIN SMALL LETTER R WITH INVERTED BREVE -> 'r' */
+ { 0x14, 0x55 }, /* LATIN CAPITAL LETTER U WITH DOUBLE GRAVE -> 'U' */
+ { 0x15, 0x75 }, /* LATIN SMALL LETTER U WITH DOUBLE GRAVE -> 'u' */
+ { 0x16, 0x55 }, /* LATIN CAPITAL LETTER U WITH INVERTED BREVE -> 'U' */
+ { 0x17, 0x75 }, /* LATIN SMALL LETTER U WITH INVERTED BREVE -> 'u' */
+ { 0x18, 0x53 }, /* LATIN CAPITAL LETTER S WITH COMMA BELOW -> 'S' */
+ { 0x19, 0x73 }, /* LATIN SMALL LETTER S WITH COMMA BELOW -> 's' */
+ { 0x1A, 0x54 }, /* LATIN CAPITAL LETTER T WITH COMMA BELOW -> 'T' */
+ { 0x1B, 0x74 }, /* LATIN SMALL LETTER T WITH COMMA BELOW -> 't' */
+ { 0x1C, 0x59 }, /* LATIN CAPITAL LETTER YOGH -> 'Y' */
+ { 0x1D, 0x79 }, /* LATIN SMALL LETTER YOGH -> 'y' */
+ { 0x1E, 0x48 }, /* LATIN CAPITAL LETTER H WITH CARON -> 'H' */
+ { 0x1F, 0x68 }, /* LATIN SMALL LETTER H WITH CARON -> 'h' */
+ { 0x20, 0x4E }, /* LATIN CAPITAL LETTER N WITH LONG RIGHT LEG -> 'N' */
+ { 0x21, 0x64 }, /* LATIN SMALL LETTER D WITH CURL -> 'd' */
+ { 0x24, 0x5A }, /* LATIN CAPITAL LETTER Z WITH HOOK -> 'Z' */
+ { 0x25, 0x7A }, /* LATIN SMALL LETTER Z WITH HOOK -> 'z' */
+ { 0x26, 0x41 }, /* LATIN CAPITAL LETTER A WITH DOT ABOVE -> 'A' */
+ { 0x27, 0x61 }, /* LATIN SMALL LETTER A WITH DOT ABOVE -> 'a' */
+ { 0x28, 0x45 }, /* LATIN CAPITAL LETTER E WITH CEDILLA -> 'E' */
+ { 0x29, 0x65 }, /* LATIN SMALL LETTER E WITH CEDILLA -> 'e' */
+ { 0x2A, 0x4F }, /* LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON -> 'O' */
+ { 0x2B, 0x6F }, /* LATIN SMALL LETTER O WITH DIAERESIS AND MACRON -> 'o' */
+ { 0x2C, 0x4F }, /* LATIN CAPITAL LETTER O WITH TILDE AND MACRON -> 'O' */
+ { 0x2D, 0x6F }, /* LATIN SMALL LETTER O WITH TILDE AND MACRON -> 'o' */
+ { 0x2E, 0x4F }, /* LATIN CAPITAL LETTER O WITH DOT ABOVE -> 'O' */
+ { 0x2F, 0x6F }, /* LATIN SMALL LETTER O WITH DOT ABOVE -> 'o' */
+ { 0x30, 0x4F }, /* LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON -> 'O' */
+ { 0x31, 0x6F }, /* LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON -> 'o' */
+ { 0x32, 0x59 }, /* LATIN CAPITAL LETTER Y WITH MACRON -> 'Y' */
+ { 0x33, 0x79 }, /* LATIN SMALL LETTER Y WITH MACRON -> 'y' */
+ { 0x34, 0x6C }, /* LATIN SMALL LETTER L WITH CURL -> 'l' */
+ { 0x35, 0x6E }, /* LATIN SMALL LETTER N WITH CURL -> 'n' */
+ { 0x36, 0x74 }, /* LATIN SMALL LETTER T WITH CURL -> 't' */
+ { 0x37, 0x6A }, /* LATIN SMALL LETTER DOTLESS J -> 'j' */
+ { 0x3A, 0x41 }, /* LATIN CAPITAL LETTER A WITH STROKE -> 'A' */
+ { 0x3B, 0x43 }, /* LATIN CAPITAL LETTER C WITH STROKE -> 'C' */
+ { 0x3C, 0x63 }, /* LATIN SMALL LETTER C WITH STROKE -> 'c' */
+ { 0x3D, 0x4C }, /* LATIN CAPITAL LETTER L WITH BAR -> 'L' */
+ { 0x3E, 0x54 }, /* LATIN CAPITAL LETTER T WITH DIAGONAL STROKE -> 'T' */
+ { 0x3F, 0x73 }, /* LATIN SMALL LETTER S WITH SWASH TAIL -> 's' */
+ { 0x40, 0x7A }, /* LATIN SMALL LETTER Z WITH SWASH TAIL -> 'z' */
+ { 0x43, 0x42 }, /* LATIN CAPITAL LETTER B WITH STROKE -> 'B' */
+ { 0x44, 0x55 }, /* LATIN CAPITAL LETTER U BAR -> 'U' */
+ { 0x45, 0x5E }, /* LATIN CAPITAL LETTER TURNED V -> '^' */
+ { 0x46, 0x45 }, /* LATIN CAPITAL LETTER E WITH STROKE -> 'E' */
+ { 0x47, 0x65 }, /* LATIN SMALL LETTER E WITH STROKE -> 'e' */
+ { 0x48, 0x4A }, /* LATIN CAPITAL LETTER J WITH STROKE -> 'J' */
+ { 0x49, 0x6A }, /* LATIN SMALL LETTER J WITH STROKE -> 'j' */
+ { 0x4A, 0x71 }, /* LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL -> 'q' */
+ { 0x4B, 0x71 }, /* LATIN SMALL LETTER Q WITH HOOK TAIL -> 'q' */
+ { 0x4C, 0x52 }, /* LATIN CAPITAL LETTER R WITH STROKE -> 'R' */
+ { 0x4D, 0x72 }, /* LATIN SMALL LETTER R WITH STROKE -> 'r' */
+ { 0x4E, 0x59 }, /* LATIN CAPITAL LETTER Y WITH STROKE -> 'Y' */
+ { 0x4F, 0x79 }, /* LATIN SMALL LETTER Y WITH STROKE -> 'y' */
+ { 0x50, 0x00 }, /* LATIN SMALL LETTER TURNED A -> ... */
+ { 0x52, 0x61 }, /* LATIN SMALL LETTER TURNED ALPHA -> 'a' */
+ { 0x53, 0x62 }, /* LATIN SMALL LETTER B WITH HOOK -> 'b' */
+ { 0x54, 0x6F }, /* LATIN SMALL LETTER OPEN O -> 'o' */
+ { 0x55, 0x63 }, /* LATIN SMALL LETTER C WITH CURL -> 'c' */
+ { 0x56, 0x64 }, /* LATIN SMALL LETTER D WITH TAIL -> 'd' */
+ { 0x57, 0x64 }, /* LATIN SMALL LETTER D WITH HOOK -> 'd' */
+ { 0x58, 0x65 }, /* LATIN SMALL LETTER REVERSED E -> 'e' */
+ { 0x59, 0x40 }, /* LATIN SMALL LETTER SCHWA -> '@' */
+ { 0x5A, 0x40 }, /* LATIN SMALL LETTER SCHWA WITH HOOK -> '@' */
+ { 0x5B, 0x00 }, /* LATIN SMALL LETTER OPEN E -> ... */
+ { 0x5E, 0x65 }, /* LATIN SMALL LETTER CLOSED REVERSED OPEN E -> 'e' */
+ { 0x5F, 0x6A }, /* LATIN SMALL LETTER DOTLESS J WITH STROKE -> 'j' */
+ { 0x60, 0x00 }, /* LATIN SMALL LETTER G WITH HOOK -> ... */
+ { 0x63, 0x67 }, /* LATIN SMALL LETTER GAMMA -> 'g' */
+ { 0x64, 0x75 }, /* LATIN SMALL LETTER RAMS HORN -> 'u' */
+ { 0x65, 0x59 }, /* LATIN SMALL LETTER TURNED H -> 'Y' */
+ { 0x66, 0x68 }, /* LATIN SMALL LETTER H WITH HOOK -> 'h' */
+ { 0x67, 0x68 }, /* LATIN SMALL LETTER HENG WITH HOOK -> 'h' */
+ { 0x68, 0x69 }, /* LATIN SMALL LETTER I WITH STROKE -> 'i' */
+ { 0x69, 0x69 }, /* LATIN SMALL LETTER IOTA -> 'i' */
+ { 0x6A, 0x49 }, /* LATIN LETTER SMALL CAPITAL I -> 'I' */
+ { 0x6B, 0x00 }, /* LATIN SMALL LETTER L WITH MIDDLE TILDE -> ... */
+ { 0x6D, 0x6C }, /* LATIN SMALL LETTER L WITH RETROFLEX HOOK -> 'l' */
+ { 0x6F, 0x57 }, /* LATIN SMALL LETTER TURNED M -> 'W' */
+ { 0x70, 0x57 }, /* LATIN SMALL LETTER TURNED M WITH LONG LEG -> 'W' */
+ { 0x71, 0x6D }, /* LATIN SMALL LETTER M WITH HOOK -> 'm' */
+ { 0x72, 0x00 }, /* LATIN SMALL LETTER N WITH LEFT HOOK -> ... */
+ { 0x74, 0x6E }, /* LATIN LETTER SMALL CAPITAL N -> 'n' */
+ { 0x75, 0x6F }, /* LATIN SMALL LETTER BARRED O -> 'o' */
+ { 0x77, 0x4F }, /* LATIN SMALL LETTER CLOSED OMEGA -> 'O' */
+ { 0x78, 0x46 }, /* LATIN SMALL LETTER PHI -> 'F' */
+ { 0x79, 0x00 }, /* LATIN SMALL LETTER TURNED R -> ... */
+ { 0x7F, 0x72 }, /* LATIN SMALL LETTER REVERSED R WITH FISHHOOK -> 'r' */
+ { 0x80, 0x52 }, /* LATIN LETTER SMALL CAPITAL R -> 'R' */
+ { 0x81, 0x52 }, /* LATIN LETTER SMALL CAPITAL INVERTED R -> 'R' */
+ { 0x82, 0x73 }, /* LATIN SMALL LETTER S WITH HOOK -> 's' */
+ { 0x83, 0x53 }, /* LATIN SMALL LETTER ESH -> 'S' */
+ { 0x84, 0x6A }, /* LATIN SMALL LETTER DOTLESS J WITH STROKE AND HOOK -> 'j' */
+ { 0x85, 0x53 }, /* LATIN SMALL LETTER SQUAT REVERSED ESH -> 'S' */
+ { 0x86, 0x53 }, /* LATIN SMALL LETTER ESH WITH CURL -> 'S' */
+ { 0x87, 0x74 }, /* LATIN SMALL LETTER TURNED T -> 't' */
+ { 0x88, 0x74 }, /* LATIN SMALL LETTER T WITH RETROFLEX HOOK -> 't' */
+ { 0x89, 0x75 }, /* LATIN SMALL LETTER U BAR -> 'u' */
+ { 0x8A, 0x55 }, /* LATIN SMALL LETTER UPSILON -> 'U' */
+ { 0x8B, 0x76 }, /* LATIN SMALL LETTER V WITH HOOK -> 'v' */
+ { 0x8C, 0x5E }, /* LATIN SMALL LETTER TURNED V -> '^' */
+ { 0x8D, 0x77 }, /* LATIN SMALL LETTER TURNED W -> 'w' */
+ { 0x8E, 0x79 }, /* LATIN SMALL LETTER TURNED Y -> 'y' */
+ { 0x8F, 0x59 }, /* LATIN LETTER SMALL CAPITAL Y -> 'Y' */
+ { 0x90, 0x7A }, /* LATIN SMALL LETTER Z WITH RETROFLEX HOOK -> 'z' */
+ { 0x91, 0x7A }, /* LATIN SMALL LETTER Z WITH CURL -> 'z' */
+ { 0x92, 0x5A }, /* LATIN SMALL LETTER EZH -> 'Z' */
+ { 0x93, 0x5A }, /* LATIN SMALL LETTER EZH WITH CURL -> 'Z' */
+ { 0x94, 0x00 }, /* LATIN LETTER GLOTTAL STOP -> ... */
+ { 0x96, 0x3F }, /* LATIN LETTER INVERTED GLOTTAL STOP -> '?' */
+ { 0x97, 0x43 }, /* LATIN LETTER STRETCHED C -> 'C' */
+ { 0x98, 0x40 }, /* LATIN LETTER BILABIAL CLICK -> '@' */
+ { 0x99, 0x42 }, /* LATIN LETTER SMALL CAPITAL B -> 'B' */
+ { 0x9A, 0x45 }, /* LATIN SMALL LETTER CLOSED OPEN E -> 'E' */
+ { 0x9B, 0x47 }, /* LATIN LETTER SMALL CAPITAL G WITH HOOK -> 'G' */
+ { 0x9C, 0x48 }, /* LATIN LETTER SMALL CAPITAL H -> 'H' */
+ { 0x9D, 0x6A }, /* LATIN SMALL LETTER J WITH CROSSED-TAIL -> 'j' */
+ { 0x9E, 0x6B }, /* LATIN SMALL LETTER TURNED K -> 'k' */
+ { 0x9F, 0x4C }, /* LATIN LETTER SMALL CAPITAL L -> 'L' */
+ { 0xA0, 0x71 }, /* LATIN SMALL LETTER Q WITH HOOK -> 'q' */
+ { 0xA1, 0x3F }, /* LATIN LETTER GLOTTAL STOP WITH STROKE -> '?' */
+ { 0xA2, 0x3F }, /* LATIN LETTER REVERSED GLOTTAL STOP WITH STROKE -> '?' */
+ { 0xAE, 0x00 }, /* LATIN SMALL LETTER TURNED H WITH FISHHOOK -> ... */
+ { 0xB1, 0x68 }, /* MODIFIER LETTER SMALL H WITH HOOK -> 'h' */
+ { 0xB2, 0x6A }, /* MODIFIER LETTER SMALL J -> 'j' */
+ { 0xB3, 0x00 }, /* MODIFIER LETTER SMALL R -> ... */
+ { 0xB6, 0x72 }, /* MODIFIER LETTER SMALL CAPITAL INVERTED R -> 'r' */
+ { 0xB7, 0x77 }, /* MODIFIER LETTER SMALL W -> 'w' */
+ { 0xB8, 0x79 }, /* MODIFIER LETTER SMALL Y -> 'y' */
+ { 0xB9, 0x27 }, /* MODIFIER LETTER PRIME -> ''' */
+ { 0xBA, 0x22 }, /* MODIFIER LETTER DOUBLE PRIME -> '"' */
+ { 0xBB, 0x60 }, /* MODIFIER LETTER TURNED COMMA -> '`' */
+ { 0xBC, 0x27 }, /* MODIFIER LETTER APOSTROPHE -> ''' */
+ { 0xBD, 0x60 }, /* MODIFIER LETTER REVERSED COMMA -> '`' */
+ { 0xBE, 0x60 }, /* MODIFIER LETTER RIGHT HALF RING -> '`' */
+ { 0xBF, 0x27 }, /* MODIFIER LETTER LEFT HALF RING -> ''' */
+ { 0xC0, 0x3F }, /* MODIFIER LETTER GLOTTAL STOP -> '?' */
+ { 0xC1, 0x3F }, /* MODIFIER LETTER REVERSED GLOTTAL STOP -> '?' */
+ { 0xC2, 0x3C }, /* MODIFIER LETTER LEFT ARROWHEAD -> '<' */
+ { 0xC3, 0x3E }, /* MODIFIER LETTER RIGHT ARROWHEAD -> '>' */
+ { 0xC4, 0x5E }, /* MODIFIER LETTER UP ARROWHEAD -> '^' */
+ { 0xC5, 0x56 }, /* MODIFIER LETTER DOWN ARROWHEAD -> 'V' */
+ { 0xC6, 0x5E }, /* MODIFIER LETTER CIRCUMFLEX ACCENT -> '^' */
+ { 0xC7, 0x56 }, /* CARON -> 'V' */
+ { 0xC8, 0x27 }, /* MODIFIER LETTER VERTICAL LINE -> ''' */
+ { 0xC9, 0x2D }, /* MODIFIER LETTER MACRON -> '-' */
+ { 0xCA, 0x2F }, /* MODIFIER LETTER ACUTE ACCENT -> '/' */
+ { 0xCB, 0x5C }, /* MODIFIER LETTER GRAVE ACCENT -> '\' */
+ { 0xCC, 0x2C }, /* MODIFIER LETTER LOW VERTICAL LINE -> ',' */
+ { 0xCD, 0x5F }, /* MODIFIER LETTER LOW MACRON -> '_' */
+ { 0xCE, 0x5C }, /* MODIFIER LETTER LOW GRAVE ACCENT -> '\' */
+ { 0xCF, 0x2F }, /* MODIFIER LETTER LOW ACUTE ACCENT -> '/' */
+ { 0xD0, 0x3A }, /* MODIFIER LETTER TRIANGULAR COLON -> ':' */
+ { 0xD1, 0x2E }, /* MODIFIER LETTER HALF TRIANGULAR COLON -> '.' */
+ { 0xD2, 0x60 }, /* MODIFIER LETTER CENTRED RIGHT HALF RING -> '`' */
+ { 0xD3, 0x27 }, /* MODIFIER LETTER CENTRED LEFT HALF RING -> ''' */
+ { 0xD4, 0x5E }, /* MODIFIER LETTER UP TACK -> '^' */
+ { 0xD5, 0x56 }, /* MODIFIER LETTER DOWN TACK -> 'V' */
+ { 0xD6, 0x2B }, /* MODIFIER LETTER PLUS SIGN -> '+' */
+ { 0xD7, 0x2D }, /* MODIFIER LETTER MINUS SIGN -> '-' */
+ { 0xD8, 0x56 }, /* BREVE -> 'V' */
+ { 0xD9, 0x2E }, /* DOT ABOVE -> '.' */
+ { 0xDA, 0x40 }, /* RING ABOVE -> '@' */
+ { 0xDB, 0x2C }, /* OGONEK -> ',' */
+ { 0xDC, 0x7E }, /* SMALL TILDE -> '~' */
+ { 0xDD, 0x22 }, /* DOUBLE ACUTE ACCENT -> '"' */
+ { 0xDE, 0x52 }, /* MODIFIER LETTER RHOTIC HOOK -> 'R' */
+ { 0xDF, 0x58 }, /* MODIFIER LETTER CROSS ACCENT -> 'X' */
+ { 0xE0, 0x47 }, /* MODIFIER LETTER SMALL GAMMA -> 'G' */
+ { 0xE1, 0x6C }, /* MODIFIER LETTER SMALL L -> 'l' */
+ { 0xE2, 0x73 }, /* MODIFIER LETTER SMALL S -> 's' */
+ { 0xE3, 0x78 }, /* MODIFIER LETTER SMALL X -> 'x' */
+ { 0xE4, 0x3F }, /* MODIFIER LETTER SMALL REVERSED GLOTTAL STOP -> '?' */
+ { 0xEC, 0x56 }, /* MODIFIER LETTER VOICING -> 'V' */
+ { 0xED, 0x3D }, /* MODIFIER LETTER UNASPIRATED -> '=' */
+ { 0xEE, 0x22 }, /* MODIFIER LETTER DOUBLE APOSTROPHE -> '"' */
+ /* Entries for page 0x03 */
+ { 0x63, 0x61 }, /* COMBINING LATIN SMALL LETTER A -> 'a' */
+ { 0x64, 0x65 }, /* COMBINING LATIN SMALL LETTER E -> 'e' */
+ { 0x65, 0x69 }, /* COMBINING LATIN SMALL LETTER I -> 'i' */
+ { 0x66, 0x6F }, /* COMBINING LATIN SMALL LETTER O -> 'o' */
+ { 0x67, 0x75 }, /* COMBINING LATIN SMALL LETTER U -> 'u' */
+ { 0x68, 0x63 }, /* COMBINING LATIN SMALL LETTER C -> 'c' */
+ { 0x69, 0x64 }, /* COMBINING LATIN SMALL LETTER D -> 'd' */
+ { 0x6A, 0x68 }, /* COMBINING LATIN SMALL LETTER H -> 'h' */
+ { 0x6B, 0x6D }, /* COMBINING LATIN SMALL LETTER M -> 'm' */
+ { 0x6C, 0x72 }, /* COMBINING LATIN SMALL LETTER R -> 'r' */
+ { 0x6D, 0x74 }, /* COMBINING LATIN SMALL LETTER T -> 't' */
+ { 0x6E, 0x76 }, /* COMBINING LATIN SMALL LETTER V -> 'v' */
+ { 0x6F, 0x78 }, /* COMBINING LATIN SMALL LETTER X -> 'x' */
+ { 0x74, 0x27 }, /* GREEK NUMERAL SIGN -> ''' */
+ { 0x75, 0x2C }, /* GREEK LOWER NUMERAL SIGN -> ',' */
+ { 0x7E, 0x3F }, /* GREEK QUESTION MARK -> '?' */
+ { 0x86, 0x41 }, /* GREEK CAPITAL LETTER ALPHA WITH TONOS -> 'A' */
+ { 0x87, 0x3B }, /* GREEK ANO TELEIA -> ';' */
+ { 0x88, 0x45 }, /* GREEK CAPITAL LETTER EPSILON WITH TONOS -> 'E' */
+ { 0x89, 0x45 }, /* GREEK CAPITAL LETTER ETA WITH TONOS -> 'E' */
+ { 0x8A, 0x49 }, /* GREEK CAPITAL LETTER IOTA WITH TONOS -> 'I' */
+ { 0x8C, 0x4F }, /* GREEK CAPITAL LETTER OMICRON WITH TONOS -> 'O' */
+ { 0x8E, 0x55 }, /* GREEK CAPITAL LETTER UPSILON WITH TONOS -> 'U' */
+ { 0x8F, 0x4F }, /* GREEK CAPITAL LETTER OMEGA WITH TONOS -> 'O' */
+ { 0x90, 0x49 }, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS -> 'I' */
+ { 0x91, 0x41 }, /* GREEK CAPITAL LETTER ALPHA -> 'A' */
+ { 0x92, 0x42 }, /* GREEK CAPITAL LETTER BETA -> 'B' */
+ { 0x93, 0x47 }, /* GREEK CAPITAL LETTER GAMMA -> 'G' */
+ { 0x94, 0x44 }, /* GREEK CAPITAL LETTER DELTA -> 'D' */
+ { 0x95, 0x45 }, /* GREEK CAPITAL LETTER EPSILON -> 'E' */
+ { 0x96, 0x5A }, /* GREEK CAPITAL LETTER ZETA -> 'Z' */
+ { 0x97, 0x45 }, /* GREEK CAPITAL LETTER ETA -> 'E' */
+ { 0x99, 0x49 }, /* GREEK CAPITAL LETTER IOTA -> 'I' */
+ { 0x9A, 0x4B }, /* GREEK CAPITAL LETTER KAPPA -> 'K' */
+ { 0x9B, 0x4C }, /* GREEK CAPITAL LETTER LAMDA -> 'L' */
+ { 0x9C, 0x4D }, /* GREEK CAPITAL LETTER MU -> 'M' */
+ { 0x9D, 0x4E }, /* GREEK CAPITAL LETTER NU -> 'N' */
+ { 0x9F, 0x4F }, /* GREEK CAPITAL LETTER OMICRON -> 'O' */
+ { 0xA0, 0x50 }, /* GREEK CAPITAL LETTER PI -> 'P' */
+ { 0xA1, 0x52 }, /* GREEK CAPITAL LETTER RHO -> 'R' */
+ { 0xA3, 0x53 }, /* GREEK CAPITAL LETTER SIGMA -> 'S' */
+ { 0xA4, 0x54 }, /* GREEK CAPITAL LETTER TAU -> 'T' */
+ { 0xA5, 0x55 }, /* GREEK CAPITAL LETTER UPSILON -> 'U' */
+ { 0xA9, 0x4F }, /* GREEK CAPITAL LETTER OMEGA -> 'O' */
+ { 0xAA, 0x49 }, /* GREEK CAPITAL LETTER IOTA WITH DIALYTIKA -> 'I' */
+ { 0xAB, 0x55 }, /* GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA -> 'U' */
+ { 0xAC, 0x61 }, /* GREEK SMALL LETTER ALPHA WITH TONOS -> 'a' */
+ { 0xAD, 0x65 }, /* GREEK SMALL LETTER EPSILON WITH TONOS -> 'e' */
+ { 0xAE, 0x65 }, /* GREEK SMALL LETTER ETA WITH TONOS -> 'e' */
+ { 0xAF, 0x69 }, /* GREEK SMALL LETTER IOTA WITH TONOS -> 'i' */
+ { 0xB0, 0x75 }, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS -> 'u' */
+ { 0xB1, 0x61 }, /* GREEK SMALL LETTER ALPHA -> 'a' */
+ { 0xB2, 0x62 }, /* GREEK SMALL LETTER BETA -> 'b' */
+ { 0xB3, 0x67 }, /* GREEK SMALL LETTER GAMMA -> 'g' */
+ { 0xB4, 0x64 }, /* GREEK SMALL LETTER DELTA -> 'd' */
+ { 0xB5, 0x65 }, /* GREEK SMALL LETTER EPSILON -> 'e' */
+ { 0xB6, 0x7A }, /* GREEK SMALL LETTER ZETA -> 'z' */
+ { 0xB7, 0x65 }, /* GREEK SMALL LETTER ETA -> 'e' */
+ { 0xB9, 0x69 }, /* GREEK SMALL LETTER IOTA -> 'i' */
+ { 0xBA, 0x6B }, /* GREEK SMALL LETTER KAPPA -> 'k' */
+ { 0xBB, 0x6C }, /* GREEK SMALL LETTER LAMDA -> 'l' */
+ { 0xBC, 0x6D }, /* GREEK SMALL LETTER MU -> 'm' */
+ { 0xBD, 0x6E }, /* GREEK SMALL LETTER NU -> 'n' */
+ { 0xBE, 0x78 }, /* GREEK SMALL LETTER XI -> 'x' */
+ { 0xBF, 0x6F }, /* GREEK SMALL LETTER OMICRON -> 'o' */
+ { 0xC0, 0x70 }, /* GREEK SMALL LETTER PI -> 'p' */
+ { 0xC1, 0x72 }, /* GREEK SMALL LETTER RHO -> 'r' */
+ { 0xC2, 0x73 }, /* GREEK SMALL LETTER FINAL SIGMA -> 's' */
+ { 0xC3, 0x73 }, /* GREEK SMALL LETTER SIGMA -> 's' */
+ { 0xC4, 0x74 }, /* GREEK SMALL LETTER TAU -> 't' */
+ { 0xC5, 0x75 }, /* GREEK SMALL LETTER UPSILON -> 'u' */
+ { 0xC9, 0x6F }, /* GREEK SMALL LETTER OMEGA -> 'o' */
+ { 0xCA, 0x69 }, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA -> 'i' */
+ { 0xCB, 0x75 }, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA -> 'u' */
+ { 0xCC, 0x6F }, /* GREEK SMALL LETTER OMICRON WITH TONOS -> 'o' */
+ { 0xCD, 0x75 }, /* GREEK SMALL LETTER UPSILON WITH TONOS -> 'u' */
+ { 0xCE, 0x6F }, /* GREEK SMALL LETTER OMEGA WITH TONOS -> 'o' */
+ { 0xD0, 0x62 }, /* GREEK BETA SYMBOL -> 'b' */
+ { 0xD2, 0x00 }, /* GREEK UPSILON WITH HOOK SYMBOL -> ... */
+ { 0xD4, 0x55 }, /* GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL -> 'U' */
+ { 0xD6, 0x70 }, /* GREEK PI SYMBOL -> 'p' */
+ { 0xD7, 0x26 }, /* GREEK KAI SYMBOL -> '&' */
+ { 0xDC, 0x57 }, /* GREEK LETTER DIGAMMA -> 'W' */
+ { 0xDD, 0x77 }, /* GREEK SMALL LETTER DIGAMMA -> 'w' */
+ { 0xDE, 0x51 }, /* GREEK LETTER KOPPA -> 'Q' */
+ { 0xDF, 0x71 }, /* GREEK SMALL LETTER KOPPA -> 'q' */
+ { 0xE4, 0x46 }, /* COPTIC CAPITAL LETTER FEI -> 'F' */
+ { 0xE5, 0x66 }, /* COPTIC SMALL LETTER FEI -> 'f' */
+ { 0xE8, 0x48 }, /* COPTIC CAPITAL LETTER HORI -> 'H' */
+ { 0xE9, 0x68 }, /* COPTIC SMALL LETTER HORI -> 'h' */
+ { 0xEA, 0x47 }, /* COPTIC CAPITAL LETTER GANGIA -> 'G' */
+ { 0xEB, 0x67 }, /* COPTIC SMALL LETTER GANGIA -> 'g' */
+ { 0xF0, 0x6B }, /* GREEK KAPPA SYMBOL -> 'k' */
+ { 0xF1, 0x72 }, /* GREEK RHO SYMBOL -> 'r' */
+ { 0xF2, 0x63 }, /* GREEK LUNATE SIGMA SYMBOL -> 'c' */
+ { 0xF3, 0x6A }, /* GREEK LETTER YOT -> 'j' */
+ /* Entries for page 0x04 */
+ { 0x06, 0x49 }, /* CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I -> 'I' */
+ { 0x08, 0x4A }, /* CYRILLIC CAPITAL LETTER JE -> 'J' */
+ { 0x0D, 0x49 }, /* CYRILLIC CAPITAL LETTER I WITH GRAVE -> 'I' */
+ { 0x0E, 0x55 }, /* CYRILLIC CAPITAL LETTER SHORT U -> 'U' */
+ { 0x10, 0x41 }, /* CYRILLIC CAPITAL LETTER A -> 'A' */
+ { 0x11, 0x42 }, /* CYRILLIC CAPITAL LETTER BE -> 'B' */
+ { 0x12, 0x56 }, /* CYRILLIC CAPITAL LETTER VE -> 'V' */
+ { 0x13, 0x47 }, /* CYRILLIC CAPITAL LETTER GHE -> 'G' */
+ { 0x14, 0x44 }, /* CYRILLIC CAPITAL LETTER DE -> 'D' */
+ { 0x15, 0x45 }, /* CYRILLIC CAPITAL LETTER IE -> 'E' */
+ { 0x17, 0x5A }, /* CYRILLIC CAPITAL LETTER ZE -> 'Z' */
+ { 0x18, 0x49 }, /* CYRILLIC CAPITAL LETTER I -> 'I' */
+ { 0x19, 0x49 }, /* CYRILLIC CAPITAL LETTER SHORT I -> 'I' */
+ { 0x1A, 0x4B }, /* CYRILLIC CAPITAL LETTER KA -> 'K' */
+ { 0x1B, 0x4C }, /* CYRILLIC CAPITAL LETTER EL -> 'L' */
+ { 0x1C, 0x4D }, /* CYRILLIC CAPITAL LETTER EM -> 'M' */
+ { 0x1D, 0x4E }, /* CYRILLIC CAPITAL LETTER EN -> 'N' */
+ { 0x1E, 0x4F }, /* CYRILLIC CAPITAL LETTER O -> 'O' */
+ { 0x1F, 0x50 }, /* CYRILLIC CAPITAL LETTER PE -> 'P' */
+ { 0x20, 0x52 }, /* CYRILLIC CAPITAL LETTER ER -> 'R' */
+ { 0x21, 0x53 }, /* CYRILLIC CAPITAL LETTER ES -> 'S' */
+ { 0x22, 0x54 }, /* CYRILLIC CAPITAL LETTER TE -> 'T' */
+ { 0x23, 0x55 }, /* CYRILLIC CAPITAL LETTER U -> 'U' */
+ { 0x24, 0x46 }, /* CYRILLIC CAPITAL LETTER EF -> 'F' */
+ { 0x2A, 0x27 }, /* CYRILLIC CAPITAL LETTER HARD SIGN -> ''' */
+ { 0x2B, 0x59 }, /* CYRILLIC CAPITAL LETTER YERU -> 'Y' */
+ { 0x2C, 0x27 }, /* CYRILLIC CAPITAL LETTER SOFT SIGN -> ''' */
+ { 0x2D, 0x45 }, /* CYRILLIC CAPITAL LETTER E -> 'E' */
+ { 0x30, 0x61 }, /* CYRILLIC SMALL LETTER A -> 'a' */
+ { 0x31, 0x62 }, /* CYRILLIC SMALL LETTER BE -> 'b' */
+ { 0x32, 0x76 }, /* CYRILLIC SMALL LETTER VE -> 'v' */
+ { 0x33, 0x67 }, /* CYRILLIC SMALL LETTER GHE -> 'g' */
+ { 0x34, 0x64 }, /* CYRILLIC SMALL LETTER DE -> 'd' */
+ { 0x35, 0x65 }, /* CYRILLIC SMALL LETTER IE -> 'e' */
+ { 0x37, 0x7A }, /* CYRILLIC SMALL LETTER ZE -> 'z' */
+ { 0x38, 0x69 }, /* CYRILLIC SMALL LETTER I -> 'i' */
+ { 0x39, 0x69 }, /* CYRILLIC SMALL LETTER SHORT I -> 'i' */
+ { 0x3A, 0x6B }, /* CYRILLIC SMALL LETTER KA -> 'k' */
+ { 0x3B, 0x6C }, /* CYRILLIC SMALL LETTER EL -> 'l' */
+ { 0x3C, 0x6D }, /* CYRILLIC SMALL LETTER EM -> 'm' */
+ { 0x3D, 0x6E }, /* CYRILLIC SMALL LETTER EN -> 'n' */
+ { 0x3E, 0x6F }, /* CYRILLIC SMALL LETTER O -> 'o' */
+ { 0x3F, 0x70 }, /* CYRILLIC SMALL LETTER PE -> 'p' */
+ { 0x40, 0x72 }, /* CYRILLIC SMALL LETTER ER -> 'r' */
+ { 0x41, 0x73 }, /* CYRILLIC SMALL LETTER ES -> 's' */
+ { 0x42, 0x74 }, /* CYRILLIC SMALL LETTER TE -> 't' */
+ { 0x43, 0x75 }, /* CYRILLIC SMALL LETTER U -> 'u' */
+ { 0x44, 0x66 }, /* CYRILLIC SMALL LETTER EF -> 'f' */
+ { 0x4A, 0x27 }, /* CYRILLIC SMALL LETTER HARD SIGN -> ''' */
+ { 0x4B, 0x79 }, /* CYRILLIC SMALL LETTER YERU -> 'y' */
+ { 0x4C, 0x27 }, /* CYRILLIC SMALL LETTER SOFT SIGN -> ''' */
+ { 0x4D, 0x65 }, /* CYRILLIC SMALL LETTER E -> 'e' */
+ { 0x56, 0x69 }, /* CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -> 'i' */
+ { 0x58, 0x6A }, /* CYRILLIC SMALL LETTER JE -> 'j' */
+ { 0x5D, 0x69 }, /* CYRILLIC SMALL LETTER I WITH GRAVE -> 'i' */
+ { 0x5E, 0x75 }, /* CYRILLIC SMALL LETTER SHORT U -> 'u' */
+ { 0x60, 0x4F }, /* CYRILLIC CAPITAL LETTER OMEGA -> 'O' */
+ { 0x61, 0x6F }, /* CYRILLIC SMALL LETTER OMEGA -> 'o' */
+ { 0x62, 0x45 }, /* CYRILLIC CAPITAL LETTER YAT -> 'E' */
+ { 0x63, 0x65 }, /* CYRILLIC SMALL LETTER YAT -> 'e' */
+ { 0x66, 0x45 }, /* CYRILLIC CAPITAL LETTER LITTLE YUS -> 'E' */
+ { 0x67, 0x65 }, /* CYRILLIC SMALL LETTER LITTLE YUS -> 'e' */
+ { 0x6A, 0x4F }, /* CYRILLIC CAPITAL LETTER BIG YUS -> 'O' */
+ { 0x6B, 0x6F }, /* CYRILLIC SMALL LETTER BIG YUS -> 'o' */
+ { 0x72, 0x46 }, /* CYRILLIC CAPITAL LETTER FITA -> 'F' */
+ { 0x73, 0x66 }, /* CYRILLIC SMALL LETTER FITA -> 'f' */
+ { 0x74, 0x59 }, /* CYRILLIC CAPITAL LETTER IZHITSA -> 'Y' */
+ { 0x75, 0x79 }, /* CYRILLIC SMALL LETTER IZHITSA -> 'y' */
+ { 0x76, 0x59 }, /* CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT -> 'Y' */
+ { 0x77, 0x79 }, /* CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT -> 'y' */
+ { 0x78, 0x75 }, /* CYRILLIC CAPITAL LETTER UK -> 'u' */
+ { 0x79, 0x75 }, /* CYRILLIC SMALL LETTER UK -> 'u' */
+ { 0x7A, 0x4F }, /* CYRILLIC CAPITAL LETTER ROUND OMEGA -> 'O' */
+ { 0x7B, 0x6F }, /* CYRILLIC SMALL LETTER ROUND OMEGA -> 'o' */
+ { 0x7C, 0x4F }, /* CYRILLIC CAPITAL LETTER OMEGA WITH TITLO -> 'O' */
+ { 0x7D, 0x6F }, /* CYRILLIC SMALL LETTER OMEGA WITH TITLO -> 'o' */
+ { 0x80, 0x51 }, /* CYRILLIC CAPITAL LETTER KOPPA -> 'Q' */
+ { 0x81, 0x71 }, /* CYRILLIC SMALL LETTER KOPPA -> 'q' */
+ { 0x8C, 0x22 }, /* CYRILLIC CAPITAL LETTER SEMISOFT SIGN -> '"' */
+ { 0x8D, 0x22 }, /* CYRILLIC SMALL LETTER SEMISOFT SIGN -> '"' */
+ { 0xAE, 0x55 }, /* CYRILLIC CAPITAL LETTER STRAIGHT U -> 'U' */
+ { 0xAF, 0x75 }, /* CYRILLIC SMALL LETTER STRAIGHT U -> 'u' */
+ { 0xBA, 0x48 }, /* CYRILLIC CAPITAL LETTER SHHA -> 'H' */
+ { 0xBB, 0x68 }, /* CYRILLIC SMALL LETTER SHHA -> 'h' */
+ { 0xC0, 0x60 }, /* CYRILLIC LETTER PALOCHKA -> '`' */
+ { 0xD0, 0x61 }, /* CYRILLIC CAPITAL LETTER A WITH BREVE -> 'a' */
+ { 0xD1, 0x61 }, /* CYRILLIC SMALL LETTER A WITH BREVE -> 'a' */
+ { 0xD2, 0x41 }, /* CYRILLIC CAPITAL LETTER A WITH DIAERESIS -> 'A' */
+ { 0xD3, 0x61 }, /* CYRILLIC SMALL LETTER A WITH DIAERESIS -> 'a' */
+ { 0xD8, 0x00 }, /* CYRILLIC CAPITAL LETTER SCHWA -> ... */
+ { 0xDB, 0x40 }, /* CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS -> '@' */
+ { 0xDE, 0x5A }, /* CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS -> 'Z' */
+ { 0xDF, 0x7A }, /* CYRILLIC SMALL LETTER ZE WITH DIAERESIS -> 'z' */
+ { 0xE2, 0x49 }, /* CYRILLIC CAPITAL LETTER I WITH MACRON -> 'I' */
+ { 0xE3, 0x69 }, /* CYRILLIC SMALL LETTER I WITH MACRON -> 'i' */
+ { 0xE4, 0x49 }, /* CYRILLIC CAPITAL LETTER I WITH DIAERESIS -> 'I' */
+ { 0xE5, 0x69 }, /* CYRILLIC SMALL LETTER I WITH DIAERESIS -> 'i' */
+ { 0xE6, 0x4F }, /* CYRILLIC CAPITAL LETTER O WITH DIAERESIS -> 'O' */
+ { 0xE7, 0x6F }, /* CYRILLIC SMALL LETTER O WITH DIAERESIS -> 'o' */
+ { 0xE8, 0x4F }, /* CYRILLIC CAPITAL LETTER BARRED O -> 'O' */
+ { 0xE9, 0x6F }, /* CYRILLIC SMALL LETTER BARRED O -> 'o' */
+ { 0xEA, 0x4F }, /* CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS -> 'O' */
+ { 0xEB, 0x6F }, /* CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS -> 'o' */
+ { 0xEC, 0x45 }, /* CYRILLIC CAPITAL LETTER E WITH DIAERESIS -> 'E' */
+ { 0xED, 0x65 }, /* CYRILLIC SMALL LETTER E WITH DIAERESIS -> 'e' */
+ { 0xEE, 0x55 }, /* CYRILLIC CAPITAL LETTER U WITH MACRON -> 'U' */
+ { 0xEF, 0x75 }, /* CYRILLIC SMALL LETTER U WITH MACRON -> 'u' */
+ { 0xF0, 0x55 }, /* CYRILLIC CAPITAL LETTER U WITH DIAERESIS -> 'U' */
+ { 0xF1, 0x75 }, /* CYRILLIC SMALL LETTER U WITH DIAERESIS -> 'u' */
+ { 0xF2, 0x55 }, /* CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE -> 'U' */
+ { 0xF3, 0x75 }, /* CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE -> 'u' */
+ { 0xF8, 0x59 }, /* CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS -> 'Y' */
+ { 0xF9, 0x79 }, /* CYRILLIC SMALL LETTER YERU WITH DIAERESIS -> 'y' */
+ /* Entries for page 0x05 */
+ { 0x31, 0x41 }, /* ARMENIAN CAPITAL LETTER AYB -> 'A' */
+ { 0x32, 0x42 }, /* ARMENIAN CAPITAL LETTER BEN -> 'B' */
+ { 0x33, 0x47 }, /* ARMENIAN CAPITAL LETTER GIM -> 'G' */
+ { 0x34, 0x44 }, /* ARMENIAN CAPITAL LETTER DA -> 'D' */
+ { 0x35, 0x45 }, /* ARMENIAN CAPITAL LETTER ECH -> 'E' */
+ { 0x36, 0x5A }, /* ARMENIAN CAPITAL LETTER ZA -> 'Z' */
+ { 0x37, 0x45 }, /* ARMENIAN CAPITAL LETTER EH -> 'E' */
+ { 0x38, 0x45 }, /* ARMENIAN CAPITAL LETTER ET -> 'E' */
+ { 0x3B, 0x49 }, /* ARMENIAN CAPITAL LETTER INI -> 'I' */
+ { 0x3C, 0x4C }, /* ARMENIAN CAPITAL LETTER LIWN -> 'L' */
+ { 0x3F, 0x4B }, /* ARMENIAN CAPITAL LETTER KEN -> 'K' */
+ { 0x40, 0x48 }, /* ARMENIAN CAPITAL LETTER HO -> 'H' */
+ { 0x44, 0x4D }, /* ARMENIAN CAPITAL LETTER MEN -> 'M' */
+ { 0x45, 0x59 }, /* ARMENIAN CAPITAL LETTER YI -> 'Y' */
+ { 0x46, 0x4E }, /* ARMENIAN CAPITAL LETTER NOW -> 'N' */
+ { 0x48, 0x4F }, /* ARMENIAN CAPITAL LETTER VO -> 'O' */
+ { 0x4A, 0x50 }, /* ARMENIAN CAPITAL LETTER PEH -> 'P' */
+ { 0x4B, 0x4A }, /* ARMENIAN CAPITAL LETTER JHEH -> 'J' */
+ { 0x4D, 0x53 }, /* ARMENIAN CAPITAL LETTER SEH -> 'S' */
+ { 0x4E, 0x56 }, /* ARMENIAN CAPITAL LETTER VEW -> 'V' */
+ { 0x4F, 0x54 }, /* ARMENIAN CAPITAL LETTER TIWN -> 'T' */
+ { 0x50, 0x52 }, /* ARMENIAN CAPITAL LETTER REH -> 'R' */
+ { 0x52, 0x57 }, /* ARMENIAN CAPITAL LETTER YIWN -> 'W' */
+ { 0x55, 0x4F }, /* ARMENIAN CAPITAL LETTER OH -> 'O' */
+ { 0x56, 0x46 }, /* ARMENIAN CAPITAL LETTER FEH -> 'F' */
+ { 0x59, 0x3C }, /* ARMENIAN MODIFIER LETTER LEFT HALF RING -> '<' */
+ { 0x5A, 0x27 }, /* ARMENIAN APOSTROPHE -> ''' */
+ { 0x5B, 0x2F }, /* ARMENIAN EMPHASIS MARK -> '/' */
+ { 0x5C, 0x21 }, /* ARMENIAN EXCLAMATION MARK -> '!' */
+ { 0x5D, 0x2C }, /* ARMENIAN COMMA -> ',' */
+ { 0x5E, 0x3F }, /* ARMENIAN QUESTION MARK -> '?' */
+ { 0x5F, 0x2E }, /* ARMENIAN ABBREVIATION MARK -> '.' */
+ { 0x61, 0x61 }, /* ARMENIAN SMALL LETTER AYB -> 'a' */
+ { 0x62, 0x62 }, /* ARMENIAN SMALL LETTER BEN -> 'b' */
+ { 0x63, 0x67 }, /* ARMENIAN SMALL LETTER GIM -> 'g' */
+ { 0x64, 0x64 }, /* ARMENIAN SMALL LETTER DA -> 'd' */
+ { 0x65, 0x65 }, /* ARMENIAN SMALL LETTER ECH -> 'e' */
+ { 0x66, 0x7A }, /* ARMENIAN SMALL LETTER ZA -> 'z' */
+ { 0x67, 0x65 }, /* ARMENIAN SMALL LETTER EH -> 'e' */
+ { 0x68, 0x65 }, /* ARMENIAN SMALL LETTER ET -> 'e' */
+ { 0x6B, 0x69 }, /* ARMENIAN SMALL LETTER INI -> 'i' */
+ { 0x6C, 0x6C }, /* ARMENIAN SMALL LETTER LIWN -> 'l' */
+ { 0x6F, 0x6B }, /* ARMENIAN SMALL LETTER KEN -> 'k' */
+ { 0x70, 0x68 }, /* ARMENIAN SMALL LETTER HO -> 'h' */
+ { 0x74, 0x6D }, /* ARMENIAN SMALL LETTER MEN -> 'm' */
+ { 0x75, 0x79 }, /* ARMENIAN SMALL LETTER YI -> 'y' */
+ { 0x76, 0x6E }, /* ARMENIAN SMALL LETTER NOW -> 'n' */
+ { 0x78, 0x6F }, /* ARMENIAN SMALL LETTER VO -> 'o' */
+ { 0x7A, 0x70 }, /* ARMENIAN SMALL LETTER PEH -> 'p' */
+ { 0x7B, 0x6A }, /* ARMENIAN SMALL LETTER JHEH -> 'j' */
+ { 0x7D, 0x73 }, /* ARMENIAN SMALL LETTER SEH -> 's' */
+ { 0x7E, 0x76 }, /* ARMENIAN SMALL LETTER VEW -> 'v' */
+ { 0x7F, 0x74 }, /* ARMENIAN SMALL LETTER TIWN -> 't' */
+ { 0x80, 0x72 }, /* ARMENIAN SMALL LETTER REH -> 'r' */
+ { 0x82, 0x77 }, /* ARMENIAN SMALL LETTER YIWN -> 'w' */
+ { 0x85, 0x6F }, /* ARMENIAN SMALL LETTER OH -> 'o' */
+ { 0x86, 0x66 }, /* ARMENIAN SMALL LETTER FEH -> 'f' */
+ { 0x89, 0x3A }, /* ARMENIAN FULL STOP -> ':' */
+ { 0x8A, 0x2D }, /* ARMENIAN HYPHEN -> '-' */
+ { 0xB1, 0x65 }, /* HEBREW POINT HATAF SEGOL -> 'e' */
+ { 0xB2, 0x61 }, /* HEBREW POINT HATAF PATAH -> 'a' */
+ { 0xB3, 0x6F }, /* HEBREW POINT HATAF QAMATS -> 'o' */
+ { 0xB4, 0x69 }, /* HEBREW POINT HIRIQ -> 'i' */
+ { 0xB5, 0x65 }, /* HEBREW POINT TSERE -> 'e' */
+ { 0xB6, 0x65 }, /* HEBREW POINT SEGOL -> 'e' */
+ { 0xB7, 0x61 }, /* HEBREW POINT PATAH -> 'a' */
+ { 0xB8, 0x61 }, /* HEBREW POINT QAMATS -> 'a' */
+ { 0xB9, 0x6F }, /* HEBREW POINT HOLAM -> 'o' */
+ { 0xBA, 0x6F }, /* HEBREW POINT HOLAM HASER FOR VAV -> 'o' */
+ { 0xBB, 0x75 }, /* HEBREW POINT QUBUTS -> 'u' */
+ { 0xBE, 0x2D }, /* HEBREW PUNCTUATION MAQAF -> '-' */
+ { 0xC0, 0x7C }, /* HEBREW PUNCTUATION PASEQ -> '|' */
+ { 0xC3, 0x2E }, /* HEBREW PUNCTUATION SOF PASUQ -> '.' */
+ { 0xC6, 0x6E }, /* HEBREW PUNCTUATION NUN HAFUKHA -> 'n' */
+ { 0xC7, 0x6F }, /* HEBREW POINT QAMATS QATAN -> 'o' */
+ { 0xD0, 0x41 }, /* HEBREW LETTER ALEF -> 'A' */
+ { 0xD1, 0x62 }, /* HEBREW LETTER BET -> 'b' */
+ { 0xD2, 0x67 }, /* HEBREW LETTER GIMEL -> 'g' */
+ { 0xD3, 0x64 }, /* HEBREW LETTER DALET -> 'd' */
+ { 0xD4, 0x68 }, /* HEBREW LETTER HE -> 'h' */
+ { 0xD5, 0x76 }, /* HEBREW LETTER VAV -> 'v' */
+ { 0xD6, 0x7A }, /* HEBREW LETTER ZAYIN -> 'z' */
+ { 0xD7, 0x48 }, /* HEBREW LETTER HET -> 'H' */
+ { 0xD8, 0x54 }, /* HEBREW LETTER TET -> 'T' */
+ { 0xD9, 0x79 }, /* HEBREW LETTER YOD -> 'y' */
+ { 0xDC, 0x6C }, /* HEBREW LETTER LAMED -> 'l' */
+ { 0xDD, 0x6D }, /* HEBREW LETTER FINAL MEM -> 'm' */
+ { 0xDE, 0x6D }, /* HEBREW LETTER MEM -> 'm' */
+ { 0xDF, 0x6E }, /* HEBREW LETTER FINAL NUN -> 'n' */
+ { 0xE0, 0x6E }, /* HEBREW LETTER NUN -> 'n' */
+ { 0xE1, 0x73 }, /* HEBREW LETTER SAMEKH -> 's' */
+ { 0xE2, 0x60 }, /* HEBREW LETTER AYIN -> '`' */
+ { 0xE3, 0x70 }, /* HEBREW LETTER FINAL PE -> 'p' */
+ { 0xE4, 0x70 }, /* HEBREW LETTER PE -> 'p' */
+ { 0xE7, 0x6B }, /* HEBREW LETTER QOF -> 'k' */
+ { 0xE8, 0x72 }, /* HEBREW LETTER RESH -> 'r' */
+ { 0xEA, 0x74 }, /* HEBREW LETTER TAV -> 't' */
+ { 0xF0, 0x56 }, /* HEBREW LIGATURE YIDDISH DOUBLE VAV -> 'V' */
+ { 0xF3, 0x27 }, /* HEBREW PUNCTUATION GERESH -> ''' */
+ { 0xF4, 0x22 }, /* HEBREW PUNCTUATION GERSHAYIM -> '"' */
+ /* Entries for page 0x06 */
+ { 0x0C, 0x2C }, /* ARABIC COMMA -> ',' */
+ { 0x1B, 0x3B }, /* ARABIC SEMICOLON -> ';' */
+ { 0x1F, 0x3F }, /* ARABIC QUESTION MARK -> '?' */
+ { 0x22, 0x61 }, /* ARABIC LETTER ALEF WITH MADDA ABOVE -> 'a' */
+ { 0x23, 0x27 }, /* ARABIC LETTER ALEF WITH HAMZA ABOVE -> ''' */
+ { 0x28, 0x62 }, /* ARABIC LETTER BEH -> 'b' */
+ { 0x29, 0x40 }, /* ARABIC LETTER TEH MARBUTA -> '@' */
+ { 0x2A, 0x74 }, /* ARABIC LETTER TEH -> 't' */
+ { 0x2C, 0x6A }, /* ARABIC LETTER JEEM -> 'j' */
+ { 0x2D, 0x48 }, /* ARABIC LETTER HAH -> 'H' */
+ { 0x2F, 0x64 }, /* ARABIC LETTER DAL -> 'd' */
+ { 0x31, 0x72 }, /* ARABIC LETTER REH -> 'r' */
+ { 0x32, 0x7A }, /* ARABIC LETTER ZAIN -> 'z' */
+ { 0x33, 0x73 }, /* ARABIC LETTER SEEN -> 's' */
+ { 0x35, 0x53 }, /* ARABIC LETTER SAD -> 'S' */
+ { 0x36, 0x44 }, /* ARABIC LETTER DAD -> 'D' */
+ { 0x37, 0x54 }, /* ARABIC LETTER TAH -> 'T' */
+ { 0x38, 0x5A }, /* ARABIC LETTER ZAH -> 'Z' */
+ { 0x39, 0x60 }, /* ARABIC LETTER AIN -> '`' */
+ { 0x3A, 0x47 }, /* ARABIC LETTER GHAIN -> 'G' */
+ { 0x41, 0x66 }, /* ARABIC LETTER FEH -> 'f' */
+ { 0x42, 0x71 }, /* ARABIC LETTER QAF -> 'q' */
+ { 0x43, 0x6B }, /* ARABIC LETTER KAF -> 'k' */
+ { 0x44, 0x6C }, /* ARABIC LETTER LAM -> 'l' */
+ { 0x45, 0x6D }, /* ARABIC LETTER MEEM -> 'm' */
+ { 0x46, 0x6E }, /* ARABIC LETTER NOON -> 'n' */
+ { 0x47, 0x68 }, /* ARABIC LETTER HEH -> 'h' */
+ { 0x48, 0x77 }, /* ARABIC LETTER WAW -> 'w' */
+ { 0x49, 0x7E }, /* ARABIC LETTER ALEF MAKSURA -> '~' */
+ { 0x4A, 0x79 }, /* ARABIC LETTER YEH -> 'y' */
+ { 0x4E, 0x61 }, /* ARABIC FATHA -> 'a' */
+ { 0x4F, 0x75 }, /* ARABIC DAMMA -> 'u' */
+ { 0x50, 0x69 }, /* ARABIC KASRA -> 'i' */
+ { 0x51, 0x57 }, /* ARABIC SHADDA -> 'W' */
+ { 0x54, 0x27 }, /* ARABIC HAMZA ABOVE -> ''' */
+ { 0x55, 0x27 }, /* ARABIC HAMZA BELOW -> ''' */
+ { 0x60, 0x30 }, /* ARABIC-INDIC DIGIT ZERO -> '0' */
+ { 0x61, 0x31 }, /* ARABIC-INDIC DIGIT ONE -> '1' */
+ { 0x62, 0x32 }, /* ARABIC-INDIC DIGIT TWO -> '2' */
+ { 0x63, 0x33 }, /* ARABIC-INDIC DIGIT THREE -> '3' */
+ { 0x64, 0x34 }, /* ARABIC-INDIC DIGIT FOUR -> '4' */
+ { 0x65, 0x35 }, /* ARABIC-INDIC DIGIT FIVE -> '5' */
+ { 0x66, 0x36 }, /* ARABIC-INDIC DIGIT SIX -> '6' */
+ { 0x67, 0x37 }, /* ARABIC-INDIC DIGIT SEVEN -> '7' */
+ { 0x68, 0x38 }, /* ARABIC-INDIC DIGIT EIGHT -> '8' */
+ { 0x69, 0x39 }, /* ARABIC-INDIC DIGIT NINE -> '9' */
+ { 0x6A, 0x25 }, /* ARABIC PERCENT SIGN -> '%' */
+ { 0x6B, 0x2E }, /* ARABIC DECIMAL SEPARATOR -> '.' */
+ { 0x6C, 0x2C }, /* ARABIC THOUSANDS SEPARATOR -> ',' */
+ { 0x6D, 0x2A }, /* ARABIC FIVE POINTED STAR -> '*' */
+ { 0x71, 0x00 }, /* ARABIC LETTER ALEF WASLA -> ... */
+ { 0x73, 0x27 }, /* ARABIC LETTER ALEF WITH WAVY HAMZA BELOW -> ''' */
+ { 0x75, 0x27 }, /* ARABIC LETTER HIGH HAMZA ALEF -> ''' */
+ { 0x7B, 0x62 }, /* ARABIC LETTER BEEH -> 'b' */
+ { 0x7C, 0x74 }, /* ARABIC LETTER TEH WITH RING -> 't' */
+ { 0x7D, 0x54 }, /* ARABIC LETTER TEH WITH THREE DOTS ABOVE DOWNWARDS -> 'T' */
+ { 0x7E, 0x70 }, /* ARABIC LETTER PEH -> 'p' */
+ { 0x82, 0x48 }, /* ARABIC LETTER HAH WITH TWO DOTS VERTICAL ABOVE -> 'H' */
+ { 0x85, 0x48 }, /* ARABIC LETTER HAH WITH THREE DOTS ABOVE -> 'H' */
+ { 0x89, 0x44 }, /* ARABIC LETTER DAL WITH RING -> 'D' */
+ { 0x8A, 0x44 }, /* ARABIC LETTER DAL WITH DOT BELOW -> 'D' */
+ { 0x8E, 0x64 }, /* ARABIC LETTER DUL -> 'd' */
+ { 0x8F, 0x44 }, /* ARABIC LETTER DAL WITH THREE DOTS ABOVE DOWNWARDS -> 'D' */
+ { 0x90, 0x44 }, /* ARABIC LETTER DAL WITH FOUR DOTS ABOVE -> 'D' */
+ { 0x92, 0x00 }, /* ARABIC LETTER REH WITH SMALL V -> ... */
+ { 0x97, 0x52 }, /* ARABIC LETTER REH WITH TWO DOTS ABOVE -> 'R' */
+ { 0x98, 0x6A }, /* ARABIC LETTER JEH -> 'j' */
+ { 0x99, 0x52 }, /* ARABIC LETTER REH WITH FOUR DOTS ABOVE -> 'R' */
+ { 0x9A, 0x00 }, /* ARABIC LETTER SEEN WITH DOT BELOW AND DOT ABOVE -> ... */
+ { 0x9E, 0x53 }, /* ARABIC LETTER SAD WITH THREE DOTS ABOVE -> 'S' */
+ { 0x9F, 0x54 }, /* ARABIC LETTER TAH WITH THREE DOTS ABOVE -> 'T' */
+ { 0xA1, 0x00 }, /* ARABIC LETTER DOTLESS FEH -> ... */
+ { 0xA3, 0x46 }, /* ARABIC LETTER FEH WITH DOT BELOW -> 'F' */
+ { 0xA4, 0x76 }, /* ARABIC LETTER VEH -> 'v' */
+ { 0xA5, 0x66 }, /* ARABIC LETTER FEH WITH THREE DOTS BELOW -> 'f' */
+ { 0xA7, 0x51 }, /* ARABIC LETTER QAF WITH DOT ABOVE -> 'Q' */
+ { 0xA8, 0x51 }, /* ARABIC LETTER QAF WITH THREE DOTS ABOVE -> 'Q' */
+ { 0xAA, 0x6B }, /* ARABIC LETTER SWASH KAF -> 'k' */
+ { 0xAB, 0x4B }, /* ARABIC LETTER KAF WITH RING -> 'K' */
+ { 0xAC, 0x4B }, /* ARABIC LETTER KAF WITH DOT ABOVE -> 'K' */
+ { 0xAE, 0x4B }, /* ARABIC LETTER KAF WITH THREE DOTS BELOW -> 'K' */
+ { 0xAF, 0x67 }, /* ARABIC LETTER GAF -> 'g' */
+ { 0xB0, 0x47 }, /* ARABIC LETTER GAF WITH RING -> 'G' */
+ { 0xB1, 0x4E }, /* ARABIC LETTER NGOEH -> 'N' */
+ { 0xB2, 0x00 }, /* ARABIC LETTER GAF WITH TWO DOTS BELOW -> ... */
+ { 0xB4, 0x47 }, /* ARABIC LETTER GAF WITH THREE DOTS ABOVE -> 'G' */
+ { 0xB5, 0x00 }, /* ARABIC LETTER LAM WITH SMALL V -> ... */
+ { 0xB8, 0x4C }, /* ARABIC LETTER LAM WITH THREE DOTS BELOW -> 'L' */
+ { 0xB9, 0x00 }, /* ARABIC LETTER NOON WITH DOT BELOW -> ... */
+ { 0xBD, 0x4E }, /* ARABIC LETTER NOON WITH THREE DOTS ABOVE -> 'N' */
+ { 0xBE, 0x68 }, /* ARABIC LETTER HEH DOACHASHMEE -> 'h' */
+ { 0xC1, 0x68 }, /* ARABIC LETTER HEH GOAL -> 'h' */
+ { 0xC2, 0x48 }, /* ARABIC LETTER HEH GOAL WITH HAMZA ABOVE -> 'H' */
+ { 0xC3, 0x40 }, /* ARABIC LETTER TEH MARBUTA GOAL -> '@' */
+ { 0xC4, 0x57 }, /* ARABIC LETTER WAW WITH RING -> 'W' */
+ { 0xC7, 0x75 }, /* ARABIC LETTER U -> 'u' */
+ { 0xCA, 0x57 }, /* ARABIC LETTER WAW WITH TWO DOTS ABOVE -> 'W' */
+ { 0xCB, 0x76 }, /* ARABIC LETTER VE -> 'v' */
+ { 0xCC, 0x79 }, /* ARABIC LETTER FARSI YEH -> 'y' */
+ { 0xCD, 0x59 }, /* ARABIC LETTER YEH WITH TAIL -> 'Y' */
+ { 0xCE, 0x59 }, /* ARABIC LETTER YEH WITH SMALL V -> 'Y' */
+ { 0xCF, 0x57 }, /* ARABIC LETTER WAW WITH DOT ABOVE -> 'W' */
+ { 0xD2, 0x79 }, /* ARABIC LETTER YEH BARREE -> 'y' */
+ { 0xD4, 0x2E }, /* ARABIC FULL STOP -> '.' */
+ { 0xDD, 0x40 }, /* ARABIC END OF AYAH -> '@' */
+ { 0xDE, 0x23 }, /* ARABIC START OF RUB EL HIZB -> '#' */
+ { 0xE9, 0x5E }, /* ARABIC PLACE OF SAJDAH -> '^' */
+ { 0xF0, 0x30 }, /* EXTENDED ARABIC-INDIC DIGIT ZERO -> '0' */
+ { 0xF1, 0x31 }, /* EXTENDED ARABIC-INDIC DIGIT ONE -> '1' */
+ { 0xF2, 0x32 }, /* EXTENDED ARABIC-INDIC DIGIT TWO -> '2' */
+ { 0xF3, 0x33 }, /* EXTENDED ARABIC-INDIC DIGIT THREE -> '3' */
+ { 0xF4, 0x34 }, /* EXTENDED ARABIC-INDIC DIGIT FOUR -> '4' */
+ { 0xF5, 0x35 }, /* EXTENDED ARABIC-INDIC DIGIT FIVE -> '5' */
+ { 0xF6, 0x36 }, /* EXTENDED ARABIC-INDIC DIGIT SIX -> '6' */
+ { 0xF7, 0x37 }, /* EXTENDED ARABIC-INDIC DIGIT SEVEN -> '7' */
+ { 0xF8, 0x38 }, /* EXTENDED ARABIC-INDIC DIGIT EIGHT -> '8' */
+ { 0xF9, 0x39 }, /* EXTENDED ARABIC-INDIC DIGIT NINE -> '9' */
+ { 0xFB, 0x44 }, /* ARABIC LETTER DAD WITH DOT BELOW -> 'D' */
+ { 0xFD, 0x26 }, /* ARABIC SIGN SINDHI AMPERSAND -> '&' */
+ /* Entries for page 0x07 */
+ { 0x01, 0x2F }, /* SYRIAC SUPRALINEAR FULL STOP -> '/' */
+ { 0x02, 0x2C }, /* SYRIAC SUBLINEAR FULL STOP -> ',' */
+ { 0x03, 0x21 }, /* SYRIAC SUPRALINEAR COLON -> '!' */
+ { 0x04, 0x21 }, /* SYRIAC SUBLINEAR COLON -> '!' */
+ { 0x05, 0x2D }, /* SYRIAC HORIZONTAL COLON -> '-' */
+ { 0x06, 0x2C }, /* SYRIAC COLON SKEWED LEFT -> ',' */
+ { 0x07, 0x2C }, /* SYRIAC COLON SKEWED RIGHT -> ',' */
+ { 0x08, 0x3B }, /* SYRIAC SUPRALINEAR COLON SKEWED LEFT -> ';' */
+ { 0x09, 0x3F }, /* SYRIAC SUBLINEAR COLON SKEWED RIGHT -> '?' */
+ { 0x0A, 0x7E }, /* SYRIAC CONTRACTION -> '~' */
+ { 0x0B, 0x7B }, /* SYRIAC HARKLEAN OBELUS -> '{' */
+ { 0x0C, 0x7D }, /* SYRIAC HARKLEAN METOBELUS -> '}' */
+ { 0x0D, 0x2A }, /* SYRIAC HARKLEAN ASTERISCUS -> '*' */
+ { 0x10, 0x27 }, /* SYRIAC LETTER ALAPH -> ''' */
+ { 0x12, 0x62 }, /* SYRIAC LETTER BETH -> 'b' */
+ { 0x13, 0x67 }, /* SYRIAC LETTER GAMAL -> 'g' */
+ { 0x14, 0x67 }, /* SYRIAC LETTER GAMAL GARSHUNI -> 'g' */
+ { 0x15, 0x64 }, /* SYRIAC LETTER DALATH -> 'd' */
+ { 0x16, 0x64 }, /* SYRIAC LETTER DOTLESS DALATH RISH -> 'd' */
+ { 0x17, 0x68 }, /* SYRIAC LETTER HE -> 'h' */
+ { 0x18, 0x77 }, /* SYRIAC LETTER WAW -> 'w' */
+ { 0x19, 0x7A }, /* SYRIAC LETTER ZAIN -> 'z' */
+ { 0x1A, 0x48 }, /* SYRIAC LETTER HETH -> 'H' */
+ { 0x1B, 0x74 }, /* SYRIAC LETTER TETH -> 't' */
+ { 0x1C, 0x74 }, /* SYRIAC LETTER TETH GARSHUNI -> 't' */
+ { 0x1D, 0x79 }, /* SYRIAC LETTER YUDH -> 'y' */
+ { 0x1F, 0x6B }, /* SYRIAC LETTER KAPH -> 'k' */
+ { 0x20, 0x6C }, /* SYRIAC LETTER LAMADH -> 'l' */
+ { 0x21, 0x6D }, /* SYRIAC LETTER MIM -> 'm' */
+ { 0x22, 0x6E }, /* SYRIAC LETTER NUN -> 'n' */
+ { 0x23, 0x73 }, /* SYRIAC LETTER SEMKATH -> 's' */
+ { 0x24, 0x73 }, /* SYRIAC LETTER FINAL SEMKATH -> 's' */
+ { 0x25, 0x60 }, /* SYRIAC LETTER E -> '`' */
+ { 0x26, 0x70 }, /* SYRIAC LETTER PE -> 'p' */
+ { 0x27, 0x70 }, /* SYRIAC LETTER REVERSED PE -> 'p' */
+ { 0x28, 0x53 }, /* SYRIAC LETTER SADHE -> 'S' */
+ { 0x29, 0x71 }, /* SYRIAC LETTER QAPH -> 'q' */
+ { 0x2A, 0x72 }, /* SYRIAC LETTER RISH -> 'r' */
+ { 0x2C, 0x74 }, /* SYRIAC LETTER TAW -> 't' */
+ { 0x30, 0x00 }, /* SYRIAC PTHAHA ABOVE -> ... */
+ { 0x32, 0x61 }, /* SYRIAC PTHAHA DOTTED -> 'a' */
+ { 0x33, 0x00 }, /* SYRIAC ZQAPHA ABOVE -> ... */
+ { 0x35, 0x41 }, /* SYRIAC ZQAPHA DOTTED -> 'A' */
+ { 0x36, 0x00 }, /* SYRIAC RBASA ABOVE -> ... */
+ { 0x38, 0x65 }, /* SYRIAC DOTTED ZLAMA HORIZONTAL -> 'e' */
+ { 0x39, 0x45 }, /* SYRIAC DOTTED ZLAMA ANGULAR -> 'E' */
+ { 0x3A, 0x69 }, /* SYRIAC HBASA ABOVE -> 'i' */
+ { 0x3B, 0x69 }, /* SYRIAC HBASA BELOW -> 'i' */
+ { 0x3C, 0x00 }, /* SYRIAC HBASA-ESASA DOTTED -> ... */
+ { 0x3E, 0x75 }, /* SYRIAC ESASA BELOW -> 'u' */
+ { 0x3F, 0x6F }, /* SYRIAC RWAHA -> 'o' */
+ { 0x41, 0x60 }, /* SYRIAC QUSHSHAYA -> '`' */
+ { 0x42, 0x27 }, /* SYRIAC RUKKAKHA -> ''' */
+ { 0x45, 0x58 }, /* SYRIAC THREE DOTS ABOVE -> 'X' */
+ { 0x46, 0x51 }, /* SYRIAC THREE DOTS BELOW -> 'Q' */
+ { 0x47, 0x40 }, /* SYRIAC OBLIQUE LINE ABOVE -> '@' */
+ { 0x48, 0x40 }, /* SYRIAC OBLIQUE LINE BELOW -> '@' */
+ { 0x49, 0x7C }, /* SYRIAC MUSIC -> '|' */
+ { 0x4A, 0x2B }, /* SYRIAC BARREKH -> '+' */
+ { 0x80, 0x68 }, /* THAANA LETTER HAA -> 'h' */
+ { 0x82, 0x6E }, /* THAANA LETTER NOONU -> 'n' */
+ { 0x83, 0x72 }, /* THAANA LETTER RAA -> 'r' */
+ { 0x84, 0x62 }, /* THAANA LETTER BAA -> 'b' */
+ { 0x85, 0x4C }, /* THAANA LETTER LHAVIYANI -> 'L' */
+ { 0x86, 0x6B }, /* THAANA LETTER KAAFU -> 'k' */
+ { 0x87, 0x27 }, /* THAANA LETTER ALIFU -> ''' */
+ { 0x88, 0x76 }, /* THAANA LETTER VAAVU -> 'v' */
+ { 0x89, 0x6D }, /* THAANA LETTER MEEMU -> 'm' */
+ { 0x8A, 0x66 }, /* THAANA LETTER FAAFU -> 'f' */
+ { 0x8D, 0x6C }, /* THAANA LETTER LAAMU -> 'l' */
+ { 0x8E, 0x67 }, /* THAANA LETTER GAAFU -> 'g' */
+ { 0x90, 0x73 }, /* THAANA LETTER SEENU -> 's' */
+ { 0x91, 0x64 }, /* THAANA LETTER DAVIYANI -> 'd' */
+ { 0x92, 0x7A }, /* THAANA LETTER ZAVIYANI -> 'z' */
+ { 0x93, 0x74 }, /* THAANA LETTER TAVIYANI -> 't' */
+ { 0x94, 0x79 }, /* THAANA LETTER YAA -> 'y' */
+ { 0x95, 0x70 }, /* THAANA LETTER PAVIYANI -> 'p' */
+ { 0x96, 0x6A }, /* THAANA LETTER JAVIYANI -> 'j' */
+ { 0x9C, 0x7A }, /* THAANA LETTER ZAA -> 'z' */
+ { 0x9E, 0x73 }, /* THAANA LETTER SAADHU -> 's' */
+ { 0x9F, 0x64 }, /* THAANA LETTER DAADHU -> 'd' */
+ { 0xA0, 0x74 }, /* THAANA LETTER TO -> 't' */
+ { 0xA1, 0x7A }, /* THAANA LETTER ZO -> 'z' */
+ { 0xA2, 0x60 }, /* THAANA LETTER AINU -> '`' */
+ { 0xA4, 0x71 }, /* THAANA LETTER QAAFU -> 'q' */
+ { 0xA5, 0x77 }, /* THAANA LETTER WAAVU -> 'w' */
+ { 0xA6, 0x61 }, /* THAANA ABAFILI -> 'a' */
+ { 0xA8, 0x69 }, /* THAANA IBIFILI -> 'i' */
+ { 0xAA, 0x75 }, /* THAANA UBUFILI -> 'u' */
+ { 0xAC, 0x65 }, /* THAANA EBEFILI -> 'e' */
+ { 0xAE, 0x6F }, /* THAANA OBOFILI -> 'o' */
+ /* Entries for page 0x09 */
+ { 0x01, 0x4E }, /* DEVANAGARI SIGN CANDRABINDU -> 'N' */
+ { 0x02, 0x4E }, /* DEVANAGARI SIGN ANUSVARA -> 'N' */
+ { 0x03, 0x48 }, /* DEVANAGARI SIGN VISARGA -> 'H' */
+ { 0x05, 0x61 }, /* DEVANAGARI LETTER A -> 'a' */
+ { 0x07, 0x69 }, /* DEVANAGARI LETTER I -> 'i' */
+ { 0x09, 0x75 }, /* DEVANAGARI LETTER U -> 'u' */
+ { 0x0B, 0x52 }, /* DEVANAGARI LETTER VOCALIC R -> 'R' */
+ { 0x0C, 0x4C }, /* DEVANAGARI LETTER VOCALIC L -> 'L' */
+ { 0x0E, 0x65 }, /* DEVANAGARI LETTER SHORT E -> 'e' */
+ { 0x0F, 0x65 }, /* DEVANAGARI LETTER E -> 'e' */
+ { 0x12, 0x6F }, /* DEVANAGARI LETTER SHORT O -> 'o' */
+ { 0x13, 0x6F }, /* DEVANAGARI LETTER O -> 'o' */
+ { 0x15, 0x6B }, /* DEVANAGARI LETTER KA -> 'k' */
+ { 0x17, 0x67 }, /* DEVANAGARI LETTER GA -> 'g' */
+ { 0x1A, 0x63 }, /* DEVANAGARI LETTER CA -> 'c' */
+ { 0x1C, 0x6A }, /* DEVANAGARI LETTER JA -> 'j' */
+ { 0x24, 0x74 }, /* DEVANAGARI LETTER TA -> 't' */
+ { 0x26, 0x64 }, /* DEVANAGARI LETTER DA -> 'd' */
+ { 0x28, 0x6E }, /* DEVANAGARI LETTER NA -> 'n' */
+ { 0x2A, 0x70 }, /* DEVANAGARI LETTER PA -> 'p' */
+ { 0x2C, 0x62 }, /* DEVANAGARI LETTER BA -> 'b' */
+ { 0x2E, 0x6D }, /* DEVANAGARI LETTER MA -> 'm' */
+ { 0x2F, 0x79 }, /* DEVANAGARI LETTER YA -> 'y' */
+ { 0x30, 0x72 }, /* DEVANAGARI LETTER RA -> 'r' */
+ { 0x32, 0x6C }, /* DEVANAGARI LETTER LA -> 'l' */
+ { 0x33, 0x6C }, /* DEVANAGARI LETTER LLA -> 'l' */
+ { 0x35, 0x76 }, /* DEVANAGARI LETTER VA -> 'v' */
+ { 0x38, 0x73 }, /* DEVANAGARI LETTER SA -> 's' */
+ { 0x39, 0x68 }, /* DEVANAGARI LETTER HA -> 'h' */
+ { 0x3C, 0x27 }, /* DEVANAGARI SIGN NUKTA -> ''' */
+ { 0x3D, 0x27 }, /* DEVANAGARI SIGN AVAGRAHA -> ''' */
+ { 0x3F, 0x69 }, /* DEVANAGARI VOWEL SIGN I -> 'i' */
+ { 0x41, 0x75 }, /* DEVANAGARI VOWEL SIGN U -> 'u' */
+ { 0x43, 0x52 }, /* DEVANAGARI VOWEL SIGN VOCALIC R -> 'R' */
+ { 0x46, 0x65 }, /* DEVANAGARI VOWEL SIGN SHORT E -> 'e' */
+ { 0x47, 0x65 }, /* DEVANAGARI VOWEL SIGN E -> 'e' */
+ { 0x4A, 0x6F }, /* DEVANAGARI VOWEL SIGN SHORT O -> 'o' */
+ { 0x4B, 0x6F }, /* DEVANAGARI VOWEL SIGN O -> 'o' */
+ { 0x51, 0x27 }, /* DEVANAGARI STRESS SIGN UDATTA -> ''' */
+ { 0x52, 0x27 }, /* DEVANAGARI STRESS SIGN ANUDATTA -> ''' */
+ { 0x53, 0x60 }, /* DEVANAGARI GRAVE ACCENT -> '`' */
+ { 0x54, 0x27 }, /* DEVANAGARI ACUTE ACCENT -> ''' */
+ { 0x58, 0x71 }, /* DEVANAGARI LETTER QA -> 'q' */
+ { 0x5B, 0x7A }, /* DEVANAGARI LETTER ZA -> 'z' */
+ { 0x5E, 0x66 }, /* DEVANAGARI LETTER FA -> 'f' */
+ { 0x62, 0x4C }, /* DEVANAGARI VOWEL SIGN VOCALIC L -> 'L' */
+ { 0x66, 0x30 }, /* DEVANAGARI DIGIT ZERO -> '0' */
+ { 0x67, 0x31 }, /* DEVANAGARI DIGIT ONE -> '1' */
+ { 0x68, 0x32 }, /* DEVANAGARI DIGIT TWO -> '2' */
+ { 0x69, 0x33 }, /* DEVANAGARI DIGIT THREE -> '3' */
+ { 0x6A, 0x34 }, /* DEVANAGARI DIGIT FOUR -> '4' */
+ { 0x6B, 0x35 }, /* DEVANAGARI DIGIT FIVE -> '5' */
+ { 0x6C, 0x36 }, /* DEVANAGARI DIGIT SIX -> '6' */
+ { 0x6D, 0x37 }, /* DEVANAGARI DIGIT SEVEN -> '7' */
+ { 0x6E, 0x38 }, /* DEVANAGARI DIGIT EIGHT -> '8' */
+ { 0x6F, 0x39 }, /* DEVANAGARI DIGIT NINE -> '9' */
+ { 0x70, 0x2E }, /* DEVANAGARI ABBREVIATION SIGN -> '.' */
+ { 0x81, 0x4E }, /* BENGALI SIGN CANDRABINDU -> 'N' */
+ { 0x82, 0x4E }, /* BENGALI SIGN ANUSVARA -> 'N' */
+ { 0x83, 0x48 }, /* BENGALI SIGN VISARGA -> 'H' */
+ { 0x85, 0x61 }, /* BENGALI LETTER A -> 'a' */
+ { 0x87, 0x69 }, /* BENGALI LETTER I -> 'i' */
+ { 0x89, 0x75 }, /* BENGALI LETTER U -> 'u' */
+ { 0x8B, 0x52 }, /* BENGALI LETTER VOCALIC R -> 'R' */
+ { 0x8F, 0x65 }, /* BENGALI LETTER E -> 'e' */
+ { 0x93, 0x6F }, /* BENGALI LETTER O -> 'o' */
+ { 0x95, 0x6B }, /* BENGALI LETTER KA -> 'k' */
+ { 0x97, 0x67 }, /* BENGALI LETTER GA -> 'g' */
+ { 0x9A, 0x63 }, /* BENGALI LETTER CA -> 'c' */
+ { 0x9C, 0x6A }, /* BENGALI LETTER JA -> 'j' */
+ { 0xA4, 0x74 }, /* BENGALI LETTER TA -> 't' */
+ { 0xA6, 0x64 }, /* BENGALI LETTER DA -> 'd' */
+ { 0xA8, 0x6E }, /* BENGALI LETTER NA -> 'n' */
+ { 0xAA, 0x70 }, /* BENGALI LETTER PA -> 'p' */
+ { 0xAC, 0x62 }, /* BENGALI LETTER BA -> 'b' */
+ { 0xAE, 0x6D }, /* BENGALI LETTER MA -> 'm' */
+ { 0xAF, 0x79 }, /* BENGALI LETTER YA -> 'y' */
+ { 0xB0, 0x72 }, /* BENGALI LETTER RA -> 'r' */
+ { 0xB2, 0x6C }, /* BENGALI LETTER LA -> 'l' */
+ { 0xB8, 0x73 }, /* BENGALI LETTER SA -> 's' */
+ { 0xB9, 0x68 }, /* BENGALI LETTER HA -> 'h' */
+ { 0xBC, 0x27 }, /* BENGALI SIGN NUKTA -> ''' */
+ { 0xBF, 0x69 }, /* BENGALI VOWEL SIGN I -> 'i' */
+ { 0xC1, 0x75 }, /* BENGALI VOWEL SIGN U -> 'u' */
+ { 0xC3, 0x52 }, /* BENGALI VOWEL SIGN VOCALIC R -> 'R' */
+ { 0xC7, 0x65 }, /* BENGALI VOWEL SIGN E -> 'e' */
+ { 0xCB, 0x6F }, /* BENGALI VOWEL SIGN O -> 'o' */
+ { 0xD7, 0x2B }, /* BENGALI AU LENGTH MARK -> '+' */
+ { 0xE2, 0x4C }, /* BENGALI VOWEL SIGN VOCALIC L -> 'L' */
+ { 0xE6, 0x30 }, /* BENGALI DIGIT ZERO -> '0' */
+ { 0xE7, 0x31 }, /* BENGALI DIGIT ONE -> '1' */
+ { 0xE8, 0x32 }, /* BENGALI DIGIT TWO -> '2' */
+ { 0xE9, 0x33 }, /* BENGALI DIGIT THREE -> '3' */
+ { 0xEA, 0x34 }, /* BENGALI DIGIT FOUR -> '4' */
+ { 0xEB, 0x35 }, /* BENGALI DIGIT FIVE -> '5' */
+ { 0xEC, 0x36 }, /* BENGALI DIGIT SIX -> '6' */
+ { 0xED, 0x37 }, /* BENGALI DIGIT SEVEN -> '7' */
+ { 0xEE, 0x38 }, /* BENGALI DIGIT EIGHT -> '8' */
+ { 0xEF, 0x39 }, /* BENGALI DIGIT NINE -> '9' */
+ /* Entries for page 0x0A */
+ { 0x02, 0x4E }, /* GURMUKHI SIGN BINDI -> 'N' */
+ { 0x05, 0x61 }, /* GURMUKHI LETTER A -> 'a' */
+ { 0x07, 0x69 }, /* GURMUKHI LETTER I -> 'i' */
+ { 0x09, 0x75 }, /* GURMUKHI LETTER U -> 'u' */
+ { 0x15, 0x6B }, /* GURMUKHI LETTER KA -> 'k' */
+ { 0x17, 0x67 }, /* GURMUKHI LETTER GA -> 'g' */
+ { 0x1A, 0x63 }, /* GURMUKHI LETTER CA -> 'c' */
+ { 0x1C, 0x6A }, /* GURMUKHI LETTER JA -> 'j' */
+ { 0x24, 0x74 }, /* GURMUKHI LETTER TA -> 't' */
+ { 0x26, 0x64 }, /* GURMUKHI LETTER DA -> 'd' */
+ { 0x28, 0x6E }, /* GURMUKHI LETTER NA -> 'n' */
+ { 0x2A, 0x70 }, /* GURMUKHI LETTER PA -> 'p' */
+ { 0x2C, 0x62 }, /* GURMUKHI LETTER BA -> 'b' */
+ { 0x2E, 0x6D }, /* GURMUKHI LETTER MA -> 'm' */
+ { 0x2F, 0x79 }, /* GURMUKHI LETTER YA -> 'y' */
+ { 0x30, 0x72 }, /* GURMUKHI LETTER RA -> 'r' */
+ { 0x32, 0x6C }, /* GURMUKHI LETTER LA -> 'l' */
+ { 0x35, 0x76 }, /* GURMUKHI LETTER VA -> 'v' */
+ { 0x38, 0x73 }, /* GURMUKHI LETTER SA -> 's' */
+ { 0x39, 0x68 }, /* GURMUKHI LETTER HA -> 'h' */
+ { 0x3C, 0x27 }, /* GURMUKHI SIGN NUKTA -> ''' */
+ { 0x3F, 0x69 }, /* GURMUKHI VOWEL SIGN I -> 'i' */
+ { 0x41, 0x75 }, /* GURMUKHI VOWEL SIGN U -> 'u' */
+ { 0x5B, 0x7A }, /* GURMUKHI LETTER ZA -> 'z' */
+ { 0x5E, 0x66 }, /* GURMUKHI LETTER FA -> 'f' */
+ { 0x66, 0x30 }, /* GURMUKHI DIGIT ZERO -> '0' */
+ { 0x67, 0x31 }, /* GURMUKHI DIGIT ONE -> '1' */
+ { 0x68, 0x32 }, /* GURMUKHI DIGIT TWO -> '2' */
+ { 0x69, 0x33 }, /* GURMUKHI DIGIT THREE -> '3' */
+ { 0x6A, 0x34 }, /* GURMUKHI DIGIT FOUR -> '4' */
+ { 0x6B, 0x35 }, /* GURMUKHI DIGIT FIVE -> '5' */
+ { 0x6C, 0x36 }, /* GURMUKHI DIGIT SIX -> '6' */
+ { 0x6D, 0x37 }, /* GURMUKHI DIGIT SEVEN -> '7' */
+ { 0x6E, 0x38 }, /* GURMUKHI DIGIT EIGHT -> '8' */
+ { 0x6F, 0x39 }, /* GURMUKHI DIGIT NINE -> '9' */
+ { 0x70, 0x4E }, /* GURMUKHI TIPPI -> 'N' */
+ { 0x71, 0x48 }, /* GURMUKHI ADDAK -> 'H' */
+ { 0x81, 0x4E }, /* GUJARATI SIGN CANDRABINDU -> 'N' */
+ { 0x82, 0x4E }, /* GUJARATI SIGN ANUSVARA -> 'N' */
+ { 0x83, 0x48 }, /* GUJARATI SIGN VISARGA -> 'H' */
+ { 0x85, 0x61 }, /* GUJARATI LETTER A -> 'a' */
+ { 0x87, 0x69 }, /* GUJARATI LETTER I -> 'i' */
+ { 0x89, 0x75 }, /* GUJARATI LETTER U -> 'u' */
+ { 0x8B, 0x52 }, /* GUJARATI LETTER VOCALIC R -> 'R' */
+ { 0x8F, 0x65 }, /* GUJARATI LETTER E -> 'e' */
+ { 0x93, 0x6F }, /* GUJARATI LETTER O -> 'o' */
+ { 0x95, 0x6B }, /* GUJARATI LETTER KA -> 'k' */
+ { 0x97, 0x67 }, /* GUJARATI LETTER GA -> 'g' */
+ { 0x9A, 0x63 }, /* GUJARATI LETTER CA -> 'c' */
+ { 0x9C, 0x6A }, /* GUJARATI LETTER JA -> 'j' */
+ { 0xA4, 0x74 }, /* GUJARATI LETTER TA -> 't' */
+ { 0xA6, 0x64 }, /* GUJARATI LETTER DA -> 'd' */
+ { 0xA8, 0x6E }, /* GUJARATI LETTER NA -> 'n' */
+ { 0xAA, 0x70 }, /* GUJARATI LETTER PA -> 'p' */
+ { 0xAC, 0x62 }, /* GUJARATI LETTER BA -> 'b' */
+ { 0xAE, 0x6D }, /* GUJARATI LETTER MA -> 'm' */
+ { 0xB0, 0x72 }, /* GUJARATI LETTER RA -> 'r' */
+ { 0xB2, 0x6C }, /* GUJARATI LETTER LA -> 'l' */
+ { 0xB5, 0x76 }, /* GUJARATI LETTER VA -> 'v' */
+ { 0xB8, 0x73 }, /* GUJARATI LETTER SA -> 's' */
+ { 0xB9, 0x68 }, /* GUJARATI LETTER HA -> 'h' */
+ { 0xBC, 0x27 }, /* GUJARATI SIGN NUKTA -> ''' */
+ { 0xBD, 0x27 }, /* GUJARATI SIGN AVAGRAHA -> ''' */
+ { 0xBF, 0x69 }, /* GUJARATI VOWEL SIGN I -> 'i' */
+ { 0xC1, 0x75 }, /* GUJARATI VOWEL SIGN U -> 'u' */
+ { 0xC3, 0x52 }, /* GUJARATI VOWEL SIGN VOCALIC R -> 'R' */
+ { 0xC7, 0x65 }, /* GUJARATI VOWEL SIGN E -> 'e' */
+ { 0xCB, 0x6F }, /* GUJARATI VOWEL SIGN O -> 'o' */
+ { 0xE6, 0x30 }, /* GUJARATI DIGIT ZERO -> '0' */
+ { 0xE7, 0x31 }, /* GUJARATI DIGIT ONE -> '1' */
+ { 0xE8, 0x32 }, /* GUJARATI DIGIT TWO -> '2' */
+ { 0xE9, 0x33 }, /* GUJARATI DIGIT THREE -> '3' */
+ { 0xEA, 0x34 }, /* GUJARATI DIGIT FOUR -> '4' */
+ { 0xEB, 0x35 }, /* GUJARATI DIGIT FIVE -> '5' */
+ { 0xEC, 0x36 }, /* GUJARATI DIGIT SIX -> '6' */
+ { 0xED, 0x37 }, /* GUJARATI DIGIT SEVEN -> '7' */
+ { 0xEE, 0x38 }, /* GUJARATI DIGIT EIGHT -> '8' */
+ { 0xEF, 0x39 }, /* GUJARATI DIGIT NINE -> '9' */
+ /* Entries for page 0x0B */
+ { 0x01, 0x4E }, /* ORIYA SIGN CANDRABINDU -> 'N' */
+ { 0x02, 0x4E }, /* ORIYA SIGN ANUSVARA -> 'N' */
+ { 0x03, 0x48 }, /* ORIYA SIGN VISARGA -> 'H' */
+ { 0x05, 0x61 }, /* ORIYA LETTER A -> 'a' */
+ { 0x07, 0x69 }, /* ORIYA LETTER I -> 'i' */
+ { 0x09, 0x75 }, /* ORIYA LETTER U -> 'u' */
+ { 0x0B, 0x52 }, /* ORIYA LETTER VOCALIC R -> 'R' */
+ { 0x0C, 0x4C }, /* ORIYA LETTER VOCALIC L -> 'L' */
+ { 0x0F, 0x65 }, /* ORIYA LETTER E -> 'e' */
+ { 0x13, 0x6F }, /* ORIYA LETTER O -> 'o' */
+ { 0x15, 0x6B }, /* ORIYA LETTER KA -> 'k' */
+ { 0x17, 0x67 }, /* ORIYA LETTER GA -> 'g' */
+ { 0x1A, 0x63 }, /* ORIYA LETTER CA -> 'c' */
+ { 0x1C, 0x6A }, /* ORIYA LETTER JA -> 'j' */
+ { 0x24, 0x74 }, /* ORIYA LETTER TA -> 't' */
+ { 0x26, 0x64 }, /* ORIYA LETTER DA -> 'd' */
+ { 0x28, 0x6E }, /* ORIYA LETTER NA -> 'n' */
+ { 0x2A, 0x70 }, /* ORIYA LETTER PA -> 'p' */
+ { 0x2C, 0x62 }, /* ORIYA LETTER BA -> 'b' */
+ { 0x2E, 0x6D }, /* ORIYA LETTER MA -> 'm' */
+ { 0x2F, 0x79 }, /* ORIYA LETTER YA -> 'y' */
+ { 0x30, 0x72 }, /* ORIYA LETTER RA -> 'r' */
+ { 0x32, 0x6C }, /* ORIYA LETTER LA -> 'l' */
+ { 0x38, 0x73 }, /* ORIYA LETTER SA -> 's' */
+ { 0x39, 0x68 }, /* ORIYA LETTER HA -> 'h' */
+ { 0x3C, 0x27 }, /* ORIYA SIGN NUKTA -> ''' */
+ { 0x3D, 0x27 }, /* ORIYA SIGN AVAGRAHA -> ''' */
+ { 0x3F, 0x69 }, /* ORIYA VOWEL SIGN I -> 'i' */
+ { 0x41, 0x75 }, /* ORIYA VOWEL SIGN U -> 'u' */
+ { 0x43, 0x52 }, /* ORIYA VOWEL SIGN VOCALIC R -> 'R' */
+ { 0x47, 0x65 }, /* ORIYA VOWEL SIGN E -> 'e' */
+ { 0x4B, 0x6F }, /* ORIYA VOWEL SIGN O -> 'o' */
+ { 0x56, 0x2B }, /* ORIYA AI LENGTH MARK -> '+' */
+ { 0x57, 0x2B }, /* ORIYA AU LENGTH MARK -> '+' */
+ { 0x66, 0x30 }, /* ORIYA DIGIT ZERO -> '0' */
+ { 0x67, 0x31 }, /* ORIYA DIGIT ONE -> '1' */
+ { 0x68, 0x32 }, /* ORIYA DIGIT TWO -> '2' */
+ { 0x69, 0x33 }, /* ORIYA DIGIT THREE -> '3' */
+ { 0x6A, 0x34 }, /* ORIYA DIGIT FOUR -> '4' */
+ { 0x6B, 0x35 }, /* ORIYA DIGIT FIVE -> '5' */
+ { 0x6C, 0x36 }, /* ORIYA DIGIT SIX -> '6' */
+ { 0x6D, 0x37 }, /* ORIYA DIGIT SEVEN -> '7' */
+ { 0x6E, 0x38 }, /* ORIYA DIGIT EIGHT -> '8' */
+ { 0x6F, 0x39 }, /* ORIYA DIGIT NINE -> '9' */
+ { 0x82, 0x4E }, /* TAMIL SIGN ANUSVARA -> 'N' */
+ { 0x83, 0x48 }, /* TAMIL SIGN VISARGA -> 'H' */
+ { 0x85, 0x61 }, /* TAMIL LETTER A -> 'a' */
+ { 0x87, 0x69 }, /* TAMIL LETTER I -> 'i' */
+ { 0x89, 0x75 }, /* TAMIL LETTER U -> 'u' */
+ { 0x8E, 0x65 }, /* TAMIL LETTER E -> 'e' */
+ { 0x92, 0x6F }, /* TAMIL LETTER O -> 'o' */
+ { 0x95, 0x6B }, /* TAMIL LETTER KA -> 'k' */
+ { 0x9A, 0x63 }, /* TAMIL LETTER CA -> 'c' */
+ { 0x9C, 0x6A }, /* TAMIL LETTER JA -> 'j' */
+ { 0xA4, 0x74 }, /* TAMIL LETTER TA -> 't' */
+ { 0xA8, 0x6E }, /* TAMIL LETTER NA -> 'n' */
+ { 0xAA, 0x70 }, /* TAMIL LETTER PA -> 'p' */
+ { 0xAE, 0x6D }, /* TAMIL LETTER MA -> 'm' */
+ { 0xAF, 0x79 }, /* TAMIL LETTER YA -> 'y' */
+ { 0xB0, 0x72 }, /* TAMIL LETTER RA -> 'r' */
+ { 0xB2, 0x6C }, /* TAMIL LETTER LA -> 'l' */
+ { 0xB5, 0x76 }, /* TAMIL LETTER VA -> 'v' */
+ { 0xB8, 0x73 }, /* TAMIL LETTER SA -> 's' */
+ { 0xB9, 0x68 }, /* TAMIL LETTER HA -> 'h' */
+ { 0xBF, 0x69 }, /* TAMIL VOWEL SIGN I -> 'i' */
+ { 0xC1, 0x75 }, /* TAMIL VOWEL SIGN U -> 'u' */
+ { 0xC6, 0x65 }, /* TAMIL VOWEL SIGN E -> 'e' */
+ { 0xCA, 0x6F }, /* TAMIL VOWEL SIGN O -> 'o' */
+ { 0xD7, 0x2B }, /* TAMIL AU LENGTH MARK -> '+' */
+ { 0xE6, 0x30 }, /* TAMIL DIGIT ZERO -> '0' */
+ { 0xE7, 0x31 }, /* TAMIL DIGIT ONE -> '1' */
+ { 0xE8, 0x32 }, /* TAMIL DIGIT TWO -> '2' */
+ { 0xE9, 0x33 }, /* TAMIL DIGIT THREE -> '3' */
+ { 0xEA, 0x34 }, /* TAMIL DIGIT FOUR -> '4' */
+ { 0xEB, 0x35 }, /* TAMIL DIGIT FIVE -> '5' */
+ { 0xEC, 0x36 }, /* TAMIL DIGIT SIX -> '6' */
+ { 0xED, 0x37 }, /* TAMIL DIGIT SEVEN -> '7' */
+ { 0xEE, 0x38 }, /* TAMIL DIGIT EIGHT -> '8' */
+ { 0xEF, 0x39 }, /* TAMIL DIGIT NINE -> '9' */
+ /* Entries for page 0x0C */
+ { 0x01, 0x4E }, /* TELUGU SIGN CANDRABINDU -> 'N' */
+ { 0x02, 0x4E }, /* TELUGU SIGN ANUSVARA -> 'N' */
+ { 0x03, 0x48 }, /* TELUGU SIGN VISARGA -> 'H' */
+ { 0x05, 0x61 }, /* TELUGU LETTER A -> 'a' */
+ { 0x07, 0x69 }, /* TELUGU LETTER I -> 'i' */
+ { 0x09, 0x75 }, /* TELUGU LETTER U -> 'u' */
+ { 0x0B, 0x52 }, /* TELUGU LETTER VOCALIC R -> 'R' */
+ { 0x0C, 0x4C }, /* TELUGU LETTER VOCALIC L -> 'L' */
+ { 0x0E, 0x65 }, /* TELUGU LETTER E -> 'e' */
+ { 0x12, 0x6F }, /* TELUGU LETTER O -> 'o' */
+ { 0x15, 0x6B }, /* TELUGU LETTER KA -> 'k' */
+ { 0x17, 0x67 }, /* TELUGU LETTER GA -> 'g' */
+ { 0x1A, 0x63 }, /* TELUGU LETTER CA -> 'c' */
+ { 0x1C, 0x6A }, /* TELUGU LETTER JA -> 'j' */
+ { 0x24, 0x74 }, /* TELUGU LETTER TA -> 't' */
+ { 0x26, 0x64 }, /* TELUGU LETTER DA -> 'd' */
+ { 0x28, 0x6E }, /* TELUGU LETTER NA -> 'n' */
+ { 0x2A, 0x70 }, /* TELUGU LETTER PA -> 'p' */
+ { 0x2C, 0x62 }, /* TELUGU LETTER BA -> 'b' */
+ { 0x2E, 0x6D }, /* TELUGU LETTER MA -> 'm' */
+ { 0x2F, 0x79 }, /* TELUGU LETTER YA -> 'y' */
+ { 0x30, 0x72 }, /* TELUGU LETTER RA -> 'r' */
+ { 0x32, 0x6C }, /* TELUGU LETTER LA -> 'l' */
+ { 0x35, 0x76 }, /* TELUGU LETTER VA -> 'v' */
+ { 0x38, 0x73 }, /* TELUGU LETTER SA -> 's' */
+ { 0x39, 0x68 }, /* TELUGU LETTER HA -> 'h' */
+ { 0x3F, 0x69 }, /* TELUGU VOWEL SIGN I -> 'i' */
+ { 0x41, 0x75 }, /* TELUGU VOWEL SIGN U -> 'u' */
+ { 0x43, 0x52 }, /* TELUGU VOWEL SIGN VOCALIC R -> 'R' */
+ { 0x46, 0x65 }, /* TELUGU VOWEL SIGN E -> 'e' */
+ { 0x4A, 0x6F }, /* TELUGU VOWEL SIGN O -> 'o' */
+ { 0x55, 0x2B }, /* TELUGU LENGTH MARK -> '+' */
+ { 0x56, 0x2B }, /* TELUGU AI LENGTH MARK -> '+' */
+ { 0x66, 0x30 }, /* TELUGU DIGIT ZERO -> '0' */
+ { 0x67, 0x31 }, /* TELUGU DIGIT ONE -> '1' */
+ { 0x68, 0x32 }, /* TELUGU DIGIT TWO -> '2' */
+ { 0x69, 0x33 }, /* TELUGU DIGIT THREE -> '3' */
+ { 0x6A, 0x34 }, /* TELUGU DIGIT FOUR -> '4' */
+ { 0x6B, 0x35 }, /* TELUGU DIGIT FIVE -> '5' */
+ { 0x6C, 0x36 }, /* TELUGU DIGIT SIX -> '6' */
+ { 0x6D, 0x37 }, /* TELUGU DIGIT SEVEN -> '7' */
+ { 0x6E, 0x38 }, /* TELUGU DIGIT EIGHT -> '8' */
+ { 0x6F, 0x39 }, /* TELUGU DIGIT NINE -> '9' */
+ { 0x82, 0x4E }, /* KANNADA SIGN ANUSVARA -> 'N' */
+ { 0x83, 0x48 }, /* KANNADA SIGN VISARGA -> 'H' */
+ { 0x85, 0x61 }, /* KANNADA LETTER A -> 'a' */
+ { 0x87, 0x69 }, /* KANNADA LETTER I -> 'i' */
+ { 0x89, 0x75 }, /* KANNADA LETTER U -> 'u' */
+ { 0x8B, 0x52 }, /* KANNADA LETTER VOCALIC R -> 'R' */
+ { 0x8C, 0x4C }, /* KANNADA LETTER VOCALIC L -> 'L' */
+ { 0x8E, 0x65 }, /* KANNADA LETTER E -> 'e' */
+ { 0x92, 0x6F }, /* KANNADA LETTER O -> 'o' */
+ { 0x95, 0x6B }, /* KANNADA LETTER KA -> 'k' */
+ { 0x97, 0x67 }, /* KANNADA LETTER GA -> 'g' */
+ { 0x9A, 0x63 }, /* KANNADA LETTER CA -> 'c' */
+ { 0x9C, 0x6A }, /* KANNADA LETTER JA -> 'j' */
+ { 0xA4, 0x74 }, /* KANNADA LETTER TA -> 't' */
+ { 0xA6, 0x64 }, /* KANNADA LETTER DA -> 'd' */
+ { 0xA8, 0x6E }, /* KANNADA LETTER NA -> 'n' */
+ { 0xAA, 0x70 }, /* KANNADA LETTER PA -> 'p' */
+ { 0xAC, 0x62 }, /* KANNADA LETTER BA -> 'b' */
+ { 0xAE, 0x6D }, /* KANNADA LETTER MA -> 'm' */
+ { 0xAF, 0x79 }, /* KANNADA LETTER YA -> 'y' */
+ { 0xB0, 0x72 }, /* KANNADA LETTER RA -> 'r' */
+ { 0xB2, 0x6C }, /* KANNADA LETTER LA -> 'l' */
+ { 0xB5, 0x76 }, /* KANNADA LETTER VA -> 'v' */
+ { 0xB8, 0x73 }, /* KANNADA LETTER SA -> 's' */
+ { 0xB9, 0x68 }, /* KANNADA LETTER HA -> 'h' */
+ { 0xBF, 0x69 }, /* KANNADA VOWEL SIGN I -> 'i' */
+ { 0xC1, 0x75 }, /* KANNADA VOWEL SIGN U -> 'u' */
+ { 0xC3, 0x52 }, /* KANNADA VOWEL SIGN VOCALIC R -> 'R' */
+ { 0xC6, 0x65 }, /* KANNADA VOWEL SIGN E -> 'e' */
+ { 0xCA, 0x6F }, /* KANNADA VOWEL SIGN O -> 'o' */
+ { 0xD5, 0x2B }, /* KANNADA LENGTH MARK -> '+' */
+ { 0xD6, 0x2B }, /* KANNADA AI LENGTH MARK -> '+' */
+ { 0xE6, 0x30 }, /* KANNADA DIGIT ZERO -> '0' */
+ { 0xE7, 0x31 }, /* KANNADA DIGIT ONE -> '1' */
+ { 0xE8, 0x32 }, /* KANNADA DIGIT TWO -> '2' */
+ { 0xE9, 0x33 }, /* KANNADA DIGIT THREE -> '3' */
+ { 0xEA, 0x34 }, /* KANNADA DIGIT FOUR -> '4' */
+ { 0xEB, 0x35 }, /* KANNADA DIGIT FIVE -> '5' */
+ { 0xEC, 0x36 }, /* KANNADA DIGIT SIX -> '6' */
+ { 0xED, 0x37 }, /* KANNADA DIGIT SEVEN -> '7' */
+ { 0xEE, 0x38 }, /* KANNADA DIGIT EIGHT -> '8' */
+ { 0xEF, 0x39 }, /* KANNADA DIGIT NINE -> '9' */
+ /* Entries for page 0x0D */
+ { 0x02, 0x4E }, /* MALAYALAM SIGN ANUSVARA -> 'N' */
+ { 0x03, 0x48 }, /* MALAYALAM SIGN VISARGA -> 'H' */
+ { 0x05, 0x61 }, /* MALAYALAM LETTER A -> 'a' */
+ { 0x07, 0x69 }, /* MALAYALAM LETTER I -> 'i' */
+ { 0x09, 0x75 }, /* MALAYALAM LETTER U -> 'u' */
+ { 0x0B, 0x52 }, /* MALAYALAM LETTER VOCALIC R -> 'R' */
+ { 0x0C, 0x4C }, /* MALAYALAM LETTER VOCALIC L -> 'L' */
+ { 0x0E, 0x65 }, /* MALAYALAM LETTER E -> 'e' */
+ { 0x12, 0x6F }, /* MALAYALAM LETTER O -> 'o' */
+ { 0x15, 0x6B }, /* MALAYALAM LETTER KA -> 'k' */
+ { 0x17, 0x67 }, /* MALAYALAM LETTER GA -> 'g' */
+ { 0x1A, 0x63 }, /* MALAYALAM LETTER CA -> 'c' */
+ { 0x1C, 0x6A }, /* MALAYALAM LETTER JA -> 'j' */
+ { 0x24, 0x74 }, /* MALAYALAM LETTER TA -> 't' */
+ { 0x26, 0x64 }, /* MALAYALAM LETTER DA -> 'd' */
+ { 0x28, 0x6E }, /* MALAYALAM LETTER NA -> 'n' */
+ { 0x2A, 0x70 }, /* MALAYALAM LETTER PA -> 'p' */
+ { 0x2C, 0x62 }, /* MALAYALAM LETTER BA -> 'b' */
+ { 0x2E, 0x6D }, /* MALAYALAM LETTER MA -> 'm' */
+ { 0x2F, 0x79 }, /* MALAYALAM LETTER YA -> 'y' */
+ { 0x30, 0x72 }, /* MALAYALAM LETTER RA -> 'r' */
+ { 0x32, 0x6C }, /* MALAYALAM LETTER LA -> 'l' */
+ { 0x35, 0x76 }, /* MALAYALAM LETTER VA -> 'v' */
+ { 0x38, 0x73 }, /* MALAYALAM LETTER SA -> 's' */
+ { 0x39, 0x68 }, /* MALAYALAM LETTER HA -> 'h' */
+ { 0x3F, 0x69 }, /* MALAYALAM VOWEL SIGN I -> 'i' */
+ { 0x41, 0x75 }, /* MALAYALAM VOWEL SIGN U -> 'u' */
+ { 0x43, 0x52 }, /* MALAYALAM VOWEL SIGN VOCALIC R -> 'R' */
+ { 0x46, 0x65 }, /* MALAYALAM VOWEL SIGN E -> 'e' */
+ { 0x4A, 0x6F }, /* MALAYALAM VOWEL SIGN O -> 'o' */
+ { 0x57, 0x2B }, /* MALAYALAM AU LENGTH MARK -> '+' */
+ { 0x66, 0x30 }, /* MALAYALAM DIGIT ZERO -> '0' */
+ { 0x67, 0x31 }, /* MALAYALAM DIGIT ONE -> '1' */
+ { 0x68, 0x32 }, /* MALAYALAM DIGIT TWO -> '2' */
+ { 0x69, 0x33 }, /* MALAYALAM DIGIT THREE -> '3' */
+ { 0x6A, 0x34 }, /* MALAYALAM DIGIT FOUR -> '4' */
+ { 0x6B, 0x35 }, /* MALAYALAM DIGIT FIVE -> '5' */
+ { 0x6C, 0x36 }, /* MALAYALAM DIGIT SIX -> '6' */
+ { 0x6D, 0x37 }, /* MALAYALAM DIGIT SEVEN -> '7' */
+ { 0x6E, 0x38 }, /* MALAYALAM DIGIT EIGHT -> '8' */
+ { 0x6F, 0x39 }, /* MALAYALAM DIGIT NINE -> '9' */
+ { 0x82, 0x4E }, /* SINHALA SIGN ANUSVARAYA -> 'N' */
+ { 0x83, 0x48 }, /* SINHALA SIGN VISARGAYA -> 'H' */
+ { 0x85, 0x61 }, /* SINHALA LETTER AYANNA -> 'a' */
+ { 0x89, 0x69 }, /* SINHALA LETTER IYANNA -> 'i' */
+ { 0x8B, 0x75 }, /* SINHALA LETTER UYANNA -> 'u' */
+ { 0x8D, 0x52 }, /* SINHALA LETTER IRUYANNA -> 'R' */
+ { 0x8F, 0x4C }, /* SINHALA LETTER ILUYANNA -> 'L' */
+ { 0x91, 0x65 }, /* SINHALA LETTER EYANNA -> 'e' */
+ { 0x94, 0x6F }, /* SINHALA LETTER OYANNA -> 'o' */
+ { 0x9A, 0x6B }, /* SINHALA LETTER ALPAPRAANA KAYANNA -> 'k' */
+ { 0x9C, 0x67 }, /* SINHALA LETTER ALPAPRAANA GAYANNA -> 'g' */
+ { 0xA0, 0x63 }, /* SINHALA LETTER ALPAPRAANA CAYANNA -> 'c' */
+ { 0xA2, 0x6A }, /* SINHALA LETTER ALPAPRAANA JAYANNA -> 'j' */
+ { 0xAD, 0x74 }, /* SINHALA LETTER ALPAPRAANA TAYANNA -> 't' */
+ { 0xAF, 0x64 }, /* SINHALA LETTER ALPAPRAANA DAYANNA -> 'd' */
+ { 0xB1, 0x6E }, /* SINHALA LETTER DANTAJA NAYANNA -> 'n' */
+ { 0xB4, 0x70 }, /* SINHALA LETTER ALPAPRAANA PAYANNA -> 'p' */
+ { 0xB6, 0x62 }, /* SINHALA LETTER ALPAPRAANA BAYANNA -> 'b' */
+ { 0xB8, 0x6D }, /* SINHALA LETTER MAYANNA -> 'm' */
+ { 0xBA, 0x79 }, /* SINHALA LETTER YAYANNA -> 'y' */
+ { 0xBB, 0x72 }, /* SINHALA LETTER RAYANNA -> 'r' */
+ { 0xBD, 0x6C }, /* SINHALA LETTER DANTAJA LAYANNA -> 'l' */
+ { 0xC0, 0x76 }, /* SINHALA LETTER VAYANNA -> 'v' */
+ { 0xC3, 0x73 }, /* SINHALA LETTER DANTAJA SAYANNA -> 's' */
+ { 0xC4, 0x68 }, /* SINHALA LETTER HAYANNA -> 'h' */
+ { 0xC6, 0x66 }, /* SINHALA LETTER FAYANNA -> 'f' */
+ { 0xD2, 0x69 }, /* SINHALA VOWEL SIGN KETTI IS-PILLA -> 'i' */
+ { 0xD4, 0x75 }, /* SINHALA VOWEL SIGN KETTI PAA-PILLA -> 'u' */
+ { 0xD8, 0x52 }, /* SINHALA VOWEL SIGN GAETTA-PILLA -> 'R' */
+ { 0xD9, 0x65 }, /* SINHALA VOWEL SIGN KOMBUVA -> 'e' */
+ { 0xDC, 0x6F }, /* SINHALA VOWEL SIGN KOMBUVA HAA AELA-PILLA -> 'o' */
+ { 0xDF, 0x4C }, /* SINHALA VOWEL SIGN GAYANUKITTA -> 'L' */
+ /* Entries for page 0x0E */
+ { 0x01, 0x6B }, /* THAI CHARACTER KO KAI -> 'k' */
+ { 0x0D, 0x79 }, /* THAI CHARACTER YO YING -> 'y' */
+ { 0x0E, 0x64 }, /* THAI CHARACTER DO CHADA -> 'd' */
+ { 0x0F, 0x74 }, /* THAI CHARACTER TO PATAK -> 't' */
+ { 0x13, 0x6E }, /* THAI CHARACTER NO NEN -> 'n' */
+ { 0x14, 0x64 }, /* THAI CHARACTER DO DEK -> 'd' */
+ { 0x15, 0x74 }, /* THAI CHARACTER TO TAO -> 't' */
+ { 0x19, 0x6E }, /* THAI CHARACTER NO NU -> 'n' */
+ { 0x1A, 0x62 }, /* THAI CHARACTER BO BAIMAI -> 'b' */
+ { 0x1B, 0x70 }, /* THAI CHARACTER PO PLA -> 'p' */
+ { 0x1D, 0x66 }, /* THAI CHARACTER FO FA -> 'f' */
+ { 0x1F, 0x66 }, /* THAI CHARACTER FO FAN -> 'f' */
+ { 0x21, 0x6D }, /* THAI CHARACTER MO MA -> 'm' */
+ { 0x22, 0x79 }, /* THAI CHARACTER YO YAK -> 'y' */
+ { 0x23, 0x72 }, /* THAI CHARACTER RO RUA -> 'r' */
+ { 0x24, 0x52 }, /* THAI CHARACTER RU -> 'R' */
+ { 0x25, 0x6C }, /* THAI CHARACTER LO LING -> 'l' */
+ { 0x26, 0x4C }, /* THAI CHARACTER LU -> 'L' */
+ { 0x27, 0x77 }, /* THAI CHARACTER WO WAEN -> 'w' */
+ { 0x28, 0x00 }, /* THAI CHARACTER SO SALA -> ... */
+ { 0x2A, 0x73 }, /* THAI CHARACTER SO SUA -> 's' */
+ { 0x2B, 0x68 }, /* THAI CHARACTER HO HIP -> 'h' */
+ { 0x2C, 0x6C }, /* THAI CHARACTER LO CHULA -> 'l' */
+ { 0x2D, 0x60 }, /* THAI CHARACTER O ANG -> '`' */
+ { 0x2E, 0x68 }, /* THAI CHARACTER HO NOKHUK -> 'h' */
+ { 0x2F, 0x7E }, /* THAI CHARACTER PAIYANNOI -> '~' */
+ { 0x30, 0x61 }, /* THAI CHARACTER SARA A -> 'a' */
+ { 0x31, 0x61 }, /* THAI CHARACTER MAI HAN-AKAT -> 'a' */
+ { 0x34, 0x69 }, /* THAI CHARACTER SARA I -> 'i' */
+ { 0x38, 0x75 }, /* THAI CHARACTER SARA U -> 'u' */
+ { 0x3A, 0x27 }, /* THAI CHARACTER PHINTHU -> ''' */
+ { 0x40, 0x65 }, /* THAI CHARACTER SARA E -> 'e' */
+ { 0x42, 0x6F }, /* THAI CHARACTER SARA O -> 'o' */
+ { 0x46, 0x2B }, /* THAI CHARACTER MAIYAMOK -> '+' */
+ { 0x4D, 0x4D }, /* THAI CHARACTER NIKHAHIT -> 'M' */
+ { 0x50, 0x30 }, /* THAI DIGIT ZERO -> '0' */
+ { 0x51, 0x31 }, /* THAI DIGIT ONE -> '1' */
+ { 0x52, 0x32 }, /* THAI DIGIT TWO -> '2' */
+ { 0x53, 0x33 }, /* THAI DIGIT THREE -> '3' */
+ { 0x54, 0x34 }, /* THAI DIGIT FOUR -> '4' */
+ { 0x55, 0x35 }, /* THAI DIGIT FIVE -> '5' */
+ { 0x56, 0x36 }, /* THAI DIGIT SIX -> '6' */
+ { 0x57, 0x37 }, /* THAI DIGIT SEVEN -> '7' */
+ { 0x58, 0x38 }, /* THAI DIGIT EIGHT -> '8' */
+ { 0x59, 0x39 }, /* THAI DIGIT NINE -> '9' */
+ { 0x81, 0x6B }, /* LAO LETTER KO -> 'k' */
+ { 0x8A, 0x73 }, /* LAO LETTER SO TAM -> 's' */
+ { 0x94, 0x64 }, /* LAO LETTER DO -> 'd' */
+ { 0x95, 0x68 }, /* LAO LETTER TO -> 'h' */
+ { 0x99, 0x6E }, /* LAO LETTER NO -> 'n' */
+ { 0x9A, 0x62 }, /* LAO LETTER BO -> 'b' */
+ { 0x9B, 0x70 }, /* LAO LETTER PO -> 'p' */
+ { 0x9D, 0x66 }, /* LAO LETTER FO TAM -> 'f' */
+ { 0x9F, 0x66 }, /* LAO LETTER FO SUNG -> 'f' */
+ { 0xA1, 0x6D }, /* LAO LETTER MO -> 'm' */
+ { 0xA2, 0x79 }, /* LAO LETTER YO -> 'y' */
+ { 0xA3, 0x72 }, /* LAO LETTER LO LING -> 'r' */
+ { 0xA5, 0x6C }, /* LAO LETTER LO LOOT -> 'l' */
+ { 0xA7, 0x77 }, /* LAO LETTER WO -> 'w' */
+ { 0xAA, 0x73 }, /* LAO LETTER SO SUNG -> 's' */
+ { 0xAB, 0x68 }, /* LAO LETTER HO SUNG -> 'h' */
+ { 0xAD, 0x60 }, /* LAO LETTER O -> '`' */
+ { 0xAF, 0x7E }, /* LAO ELLIPSIS -> '~' */
+ { 0xB0, 0x61 }, /* LAO VOWEL SIGN A -> 'a' */
+ { 0xB4, 0x69 }, /* LAO VOWEL SIGN I -> 'i' */
+ { 0xB6, 0x79 }, /* LAO VOWEL SIGN Y -> 'y' */
+ { 0xB8, 0x75 }, /* LAO VOWEL SIGN U -> 'u' */
+ { 0xBB, 0x6F }, /* LAO VOWEL SIGN MAI KON -> 'o' */
+ { 0xBC, 0x6C }, /* LAO SEMIVOWEL SIGN LO -> 'l' */
+ { 0xC0, 0x65 }, /* LAO VOWEL SIGN E -> 'e' */
+ { 0xC2, 0x6F }, /* LAO VOWEL SIGN O -> 'o' */
+ { 0xC6, 0x2B }, /* LAO KO LA -> '+' */
+ { 0xCD, 0x4D }, /* LAO NIGGAHITA -> 'M' */
+ { 0xD0, 0x30 }, /* LAO DIGIT ZERO -> '0' */
+ { 0xD1, 0x31 }, /* LAO DIGIT ONE -> '1' */
+ { 0xD2, 0x32 }, /* LAO DIGIT TWO -> '2' */
+ { 0xD3, 0x33 }, /* LAO DIGIT THREE -> '3' */
+ { 0xD4, 0x34 }, /* LAO DIGIT FOUR -> '4' */
+ { 0xD5, 0x35 }, /* LAO DIGIT FIVE -> '5' */
+ { 0xD6, 0x36 }, /* LAO DIGIT SIX -> '6' */
+ { 0xD7, 0x37 }, /* LAO DIGIT SEVEN -> '7' */
+ { 0xD8, 0x38 }, /* LAO DIGIT EIGHT -> '8' */
+ { 0xD9, 0x39 }, /* LAO DIGIT NINE -> '9' */
+ /* Entries for page 0x0F */
+ { 0x0B, 0x2D }, /* TIBETAN MARK INTERSYLLABIC TSHEG -> '-' */
+ { 0x20, 0x30 }, /* TIBETAN DIGIT ZERO -> '0' */
+ { 0x21, 0x31 }, /* TIBETAN DIGIT ONE -> '1' */
+ { 0x22, 0x32 }, /* TIBETAN DIGIT TWO -> '2' */
+ { 0x23, 0x33 }, /* TIBETAN DIGIT THREE -> '3' */
+ { 0x24, 0x34 }, /* TIBETAN DIGIT FOUR -> '4' */
+ { 0x25, 0x35 }, /* TIBETAN DIGIT FIVE -> '5' */
+ { 0x26, 0x36 }, /* TIBETAN DIGIT SIX -> '6' */
+ { 0x27, 0x37 }, /* TIBETAN DIGIT SEVEN -> '7' */
+ { 0x28, 0x38 }, /* TIBETAN DIGIT EIGHT -> '8' */
+ { 0x29, 0x39 }, /* TIBETAN DIGIT NINE -> '9' */
+ { 0x34, 0x2B }, /* TIBETAN MARK BSDUS RTAGS -> '+' */
+ { 0x35, 0x2A }, /* TIBETAN MARK NGAS BZUNG NYI ZLA -> '*' */
+ { 0x36, 0x5E }, /* TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN -> '^' */
+ { 0x37, 0x5F }, /* TIBETAN MARK NGAS BZUNG SGOR RTAGS -> '_' */
+ { 0x39, 0x7E }, /* TIBETAN MARK TSA -PHRU -> '~' */
+ { 0x3B, 0x5D }, /* TIBETAN MARK GUG RTAGS GYAS -> ']' */
+ { 0x40, 0x6B }, /* TIBETAN LETTER KA -> 'k' */
+ { 0x42, 0x67 }, /* TIBETAN LETTER GA -> 'g' */
+ { 0x45, 0x63 }, /* TIBETAN LETTER CA -> 'c' */
+ { 0x47, 0x6A }, /* TIBETAN LETTER JA -> 'j' */
+ { 0x4F, 0x74 }, /* TIBETAN LETTER TA -> 't' */
+ { 0x51, 0x64 }, /* TIBETAN LETTER DA -> 'd' */
+ { 0x53, 0x6E }, /* TIBETAN LETTER NA -> 'n' */
+ { 0x54, 0x70 }, /* TIBETAN LETTER PA -> 'p' */
+ { 0x56, 0x62 }, /* TIBETAN LETTER BA -> 'b' */
+ { 0x58, 0x6D }, /* TIBETAN LETTER MA -> 'm' */
+ { 0x5D, 0x77 }, /* TIBETAN LETTER WA -> 'w' */
+ { 0x5F, 0x7A }, /* TIBETAN LETTER ZA -> 'z' */
+ { 0x60, 0x27 }, /* TIBETAN LETTER -A -> ''' */
+ { 0x61, 0x79 }, /* TIBETAN LETTER YA -> 'y' */
+ { 0x62, 0x72 }, /* TIBETAN LETTER RA -> 'r' */
+ { 0x63, 0x6C }, /* TIBETAN LETTER LA -> 'l' */
+ { 0x66, 0x73 }, /* TIBETAN LETTER SA -> 's' */
+ { 0x67, 0x68 }, /* TIBETAN LETTER HA -> 'h' */
+ { 0x68, 0x61 }, /* TIBETAN LETTER A -> 'a' */
+ { 0x6A, 0x72 }, /* TIBETAN LETTER FIXED-FORM RA -> 'r' */
+ { 0x72, 0x69 }, /* TIBETAN VOWEL SIGN I -> 'i' */
+ { 0x74, 0x75 }, /* TIBETAN VOWEL SIGN U -> 'u' */
+ { 0x76, 0x52 }, /* TIBETAN VOWEL SIGN VOCALIC R -> 'R' */
+ { 0x78, 0x4C }, /* TIBETAN VOWEL SIGN VOCALIC L -> 'L' */
+ { 0x7A, 0x65 }, /* TIBETAN VOWEL SIGN E -> 'e' */
+ { 0x7C, 0x6F }, /* TIBETAN VOWEL SIGN O -> 'o' */
+ { 0x7E, 0x4D }, /* TIBETAN SIGN RJES SU NGA RO -> 'M' */
+ { 0x7F, 0x48 }, /* TIBETAN SIGN RNAM BCAD -> 'H' */
+ { 0x80, 0x69 }, /* TIBETAN VOWEL SIGN REVERSED I -> 'i' */
+ { 0x90, 0x6B }, /* TIBETAN SUBJOINED LETTER KA -> 'k' */
+ { 0x92, 0x67 }, /* TIBETAN SUBJOINED LETTER GA -> 'g' */
+ { 0x95, 0x63 }, /* TIBETAN SUBJOINED LETTER CA -> 'c' */
+ { 0x97, 0x6A }, /* TIBETAN SUBJOINED LETTER JA -> 'j' */
+ { 0x9F, 0x74 }, /* TIBETAN SUBJOINED LETTER TA -> 't' */
+ { 0xA1, 0x64 }, /* TIBETAN SUBJOINED LETTER DA -> 'd' */
+ { 0xA3, 0x6E }, /* TIBETAN SUBJOINED LETTER NA -> 'n' */
+ { 0xA4, 0x70 }, /* TIBETAN SUBJOINED LETTER PA -> 'p' */
+ { 0xA6, 0x62 }, /* TIBETAN SUBJOINED LETTER BA -> 'b' */
+ { 0xA8, 0x6D }, /* TIBETAN SUBJOINED LETTER MA -> 'm' */
+ { 0xAD, 0x77 }, /* TIBETAN SUBJOINED LETTER WA -> 'w' */
+ { 0xAF, 0x7A }, /* TIBETAN SUBJOINED LETTER ZA -> 'z' */
+ { 0xB0, 0x27 }, /* TIBETAN SUBJOINED LETTER -A -> ''' */
+ { 0xB1, 0x79 }, /* TIBETAN SUBJOINED LETTER YA -> 'y' */
+ { 0xB2, 0x72 }, /* TIBETAN SUBJOINED LETTER RA -> 'r' */
+ { 0xB3, 0x6C }, /* TIBETAN SUBJOINED LETTER LA -> 'l' */
+ { 0xB6, 0x73 }, /* TIBETAN SUBJOINED LETTER SA -> 's' */
+ { 0xB7, 0x68 }, /* TIBETAN SUBJOINED LETTER HA -> 'h' */
+ { 0xB8, 0x61 }, /* TIBETAN SUBJOINED LETTER A -> 'a' */
+ { 0xBA, 0x77 }, /* TIBETAN SUBJOINED LETTER FIXED-FORM WA -> 'w' */
+ { 0xBB, 0x79 }, /* TIBETAN SUBJOINED LETTER FIXED-FORM YA -> 'y' */
+ { 0xBC, 0x72 }, /* TIBETAN SUBJOINED LETTER FIXED-FORM RA -> 'r' */
+ { 0xBE, 0x58 }, /* TIBETAN KU RU KHA -> 'X' */
+ /* Entries for page 0x10 */
+ { 0x00, 0x6B }, /* MYANMAR LETTER KA -> 'k' */
+ { 0x02, 0x67 }, /* MYANMAR LETTER GA -> 'g' */
+ { 0x05, 0x63 }, /* MYANMAR LETTER CA -> 'c' */
+ { 0x07, 0x6A }, /* MYANMAR LETTER JA -> 'j' */
+ { 0x12, 0x64 }, /* MYANMAR LETTER DA -> 'd' */
+ { 0x14, 0x6E }, /* MYANMAR LETTER NA -> 'n' */
+ { 0x15, 0x70 }, /* MYANMAR LETTER PA -> 'p' */
+ { 0x17, 0x62 }, /* MYANMAR LETTER BA -> 'b' */
+ { 0x19, 0x6D }, /* MYANMAR LETTER MA -> 'm' */
+ { 0x1A, 0x79 }, /* MYANMAR LETTER YA -> 'y' */
+ { 0x1B, 0x72 }, /* MYANMAR LETTER RA -> 'r' */
+ { 0x1C, 0x6C }, /* MYANMAR LETTER LA -> 'l' */
+ { 0x1D, 0x77 }, /* MYANMAR LETTER WA -> 'w' */
+ { 0x1E, 0x73 }, /* MYANMAR LETTER SA -> 's' */
+ { 0x1F, 0x68 }, /* MYANMAR LETTER HA -> 'h' */
+ { 0x21, 0x61 }, /* MYANMAR LETTER A -> 'a' */
+ { 0x23, 0x69 }, /* MYANMAR LETTER I -> 'i' */
+ { 0x25, 0x75 }, /* MYANMAR LETTER U -> 'u' */
+ { 0x27, 0x65 }, /* MYANMAR LETTER E -> 'e' */
+ { 0x29, 0x6F }, /* MYANMAR LETTER O -> 'o' */
+ { 0x2D, 0x69 }, /* MYANMAR VOWEL SIGN I -> 'i' */
+ { 0x2F, 0x75 }, /* MYANMAR VOWEL SIGN U -> 'u' */
+ { 0x31, 0x65 }, /* MYANMAR VOWEL SIGN E -> 'e' */
+ { 0x36, 0x4E }, /* MYANMAR SIGN ANUSVARA -> 'N' */
+ { 0x37, 0x27 }, /* MYANMAR SIGN DOT BELOW -> ''' */
+ { 0x38, 0x3A }, /* MYANMAR SIGN VISARGA -> ':' */
+ { 0x40, 0x30 }, /* MYANMAR DIGIT ZERO -> '0' */
+ { 0x41, 0x31 }, /* MYANMAR DIGIT ONE -> '1' */
+ { 0x42, 0x32 }, /* MYANMAR DIGIT TWO -> '2' */
+ { 0x43, 0x33 }, /* MYANMAR DIGIT THREE -> '3' */
+ { 0x44, 0x34 }, /* MYANMAR DIGIT FOUR -> '4' */
+ { 0x45, 0x35 }, /* MYANMAR DIGIT FIVE -> '5' */
+ { 0x46, 0x36 }, /* MYANMAR DIGIT SIX -> '6' */
+ { 0x47, 0x37 }, /* MYANMAR DIGIT SEVEN -> '7' */
+ { 0x48, 0x38 }, /* MYANMAR DIGIT EIGHT -> '8' */
+ { 0x49, 0x39 }, /* MYANMAR DIGIT NINE -> '9' */
+ { 0x52, 0x52 }, /* MYANMAR LETTER VOCALIC R -> 'R' */
+ { 0x54, 0x4C }, /* MYANMAR LETTER VOCALIC L -> 'L' */
+ { 0x56, 0x52 }, /* MYANMAR VOWEL SIGN VOCALIC R -> 'R' */
+ { 0x58, 0x4C }, /* MYANMAR VOWEL SIGN VOCALIC L -> 'L' */
+ { 0xA0, 0x41 }, /* GEORGIAN CAPITAL LETTER AN -> 'A' */
+ { 0xA1, 0x42 }, /* GEORGIAN CAPITAL LETTER BAN -> 'B' */
+ { 0xA2, 0x47 }, /* GEORGIAN CAPITAL LETTER GAN -> 'G' */
+ { 0xA3, 0x44 }, /* GEORGIAN CAPITAL LETTER DON -> 'D' */
+ { 0xA4, 0x45 }, /* GEORGIAN CAPITAL LETTER EN -> 'E' */
+ { 0xA5, 0x56 }, /* GEORGIAN CAPITAL LETTER VIN -> 'V' */
+ { 0xA6, 0x5A }, /* GEORGIAN CAPITAL LETTER ZEN -> 'Z' */
+ { 0xA8, 0x49 }, /* GEORGIAN CAPITAL LETTER IN -> 'I' */
+ { 0xA9, 0x4B }, /* GEORGIAN CAPITAL LETTER KAN -> 'K' */
+ { 0xAA, 0x4C }, /* GEORGIAN CAPITAL LETTER LAS -> 'L' */
+ { 0xAB, 0x4D }, /* GEORGIAN CAPITAL LETTER MAN -> 'M' */
+ { 0xAC, 0x4E }, /* GEORGIAN CAPITAL LETTER NAR -> 'N' */
+ { 0xAD, 0x4F }, /* GEORGIAN CAPITAL LETTER ON -> 'O' */
+ { 0xAE, 0x50 }, /* GEORGIAN CAPITAL LETTER PAR -> 'P' */
+ { 0xB0, 0x52 }, /* GEORGIAN CAPITAL LETTER RAE -> 'R' */
+ { 0xB1, 0x53 }, /* GEORGIAN CAPITAL LETTER SAN -> 'S' */
+ { 0xB2, 0x54 }, /* GEORGIAN CAPITAL LETTER TAR -> 'T' */
+ { 0xB3, 0x55 }, /* GEORGIAN CAPITAL LETTER UN -> 'U' */
+ { 0xB7, 0x51 }, /* GEORGIAN CAPITAL LETTER QAR -> 'Q' */
+ { 0xBC, 0x43 }, /* GEORGIAN CAPITAL LETTER CIL -> 'C' */
+ { 0xBE, 0x58 }, /* GEORGIAN CAPITAL LETTER XAN -> 'X' */
+ { 0xBF, 0x4A }, /* GEORGIAN CAPITAL LETTER JHAN -> 'J' */
+ { 0xC0, 0x48 }, /* GEORGIAN CAPITAL LETTER HAE -> 'H' */
+ { 0xC1, 0x45 }, /* GEORGIAN CAPITAL LETTER HE -> 'E' */
+ { 0xC2, 0x59 }, /* GEORGIAN CAPITAL LETTER HIE -> 'Y' */
+ { 0xC3, 0x57 }, /* GEORGIAN CAPITAL LETTER WE -> 'W' */
+ { 0xD0, 0x61 }, /* GEORGIAN LETTER AN -> 'a' */
+ { 0xD1, 0x62 }, /* GEORGIAN LETTER BAN -> 'b' */
+ { 0xD2, 0x67 }, /* GEORGIAN LETTER GAN -> 'g' */
+ { 0xD3, 0x64 }, /* GEORGIAN LETTER DON -> 'd' */
+ { 0xD4, 0x65 }, /* GEORGIAN LETTER EN -> 'e' */
+ { 0xD5, 0x76 }, /* GEORGIAN LETTER VIN -> 'v' */
+ { 0xD6, 0x7A }, /* GEORGIAN LETTER ZEN -> 'z' */
+ { 0xD8, 0x69 }, /* GEORGIAN LETTER IN -> 'i' */
+ { 0xD9, 0x6B }, /* GEORGIAN LETTER KAN -> 'k' */
+ { 0xDA, 0x6C }, /* GEORGIAN LETTER LAS -> 'l' */
+ { 0xDB, 0x6D }, /* GEORGIAN LETTER MAN -> 'm' */
+ { 0xDC, 0x6E }, /* GEORGIAN LETTER NAR -> 'n' */
+ { 0xDD, 0x6F }, /* GEORGIAN LETTER ON -> 'o' */
+ { 0xDE, 0x70 }, /* GEORGIAN LETTER PAR -> 'p' */
+ { 0xE0, 0x72 }, /* GEORGIAN LETTER RAE -> 'r' */
+ { 0xE1, 0x73 }, /* GEORGIAN LETTER SAN -> 's' */
+ { 0xE2, 0x74 }, /* GEORGIAN LETTER TAR -> 't' */
+ { 0xE3, 0x75 }, /* GEORGIAN LETTER UN -> 'u' */
+ { 0xE7, 0x71 }, /* GEORGIAN LETTER QAR -> 'q' */
+ { 0xEC, 0x63 }, /* GEORGIAN LETTER CIL -> 'c' */
+ { 0xEE, 0x78 }, /* GEORGIAN LETTER XAN -> 'x' */
+ { 0xEF, 0x6A }, /* GEORGIAN LETTER JHAN -> 'j' */
+ { 0xF0, 0x68 }, /* GEORGIAN LETTER HAE -> 'h' */
+ { 0xF1, 0x65 }, /* GEORGIAN LETTER HE -> 'e' */
+ { 0xF2, 0x79 }, /* GEORGIAN LETTER HIE -> 'y' */
+ { 0xF3, 0x77 }, /* GEORGIAN LETTER WE -> 'w' */
+ { 0xF6, 0x66 }, /* GEORGIAN LETTER FI -> 'f' */
+ /* Entries for page 0x11 */
+ { 0x00, 0x67 }, /* HANGUL CHOSEONG KIYEOK -> 'g' */
+ { 0x02, 0x6E }, /* HANGUL CHOSEONG NIEUN -> 'n' */
+ { 0x03, 0x64 }, /* HANGUL CHOSEONG TIKEUT -> 'd' */
+ { 0x05, 0x72 }, /* HANGUL CHOSEONG RIEUL -> 'r' */
+ { 0x06, 0x6D }, /* HANGUL CHOSEONG MIEUM -> 'm' */
+ { 0x07, 0x62 }, /* HANGUL CHOSEONG PIEUP -> 'b' */
+ { 0x09, 0x73 }, /* HANGUL CHOSEONG SIOS -> 's' */
+ { 0x0C, 0x6A }, /* HANGUL CHOSEONG CIEUC -> 'j' */
+ { 0x0E, 0x63 }, /* HANGUL CHOSEONG CHIEUCH -> 'c' */
+ { 0x0F, 0x6B }, /* HANGUL CHOSEONG KHIEUKH -> 'k' */
+ { 0x10, 0x74 }, /* HANGUL CHOSEONG THIEUTH -> 't' */
+ { 0x11, 0x70 }, /* HANGUL CHOSEONG PHIEUPH -> 'p' */
+ { 0x12, 0x68 }, /* HANGUL CHOSEONG HIEUH -> 'h' */
+ { 0x35, 0x73 }, /* HANGUL CHOSEONG SIOS-IEUNG -> 's' */
+ { 0x40, 0x5A }, /* HANGUL CHOSEONG PANSIOS -> 'Z' */
+ { 0x41, 0x67 }, /* HANGUL CHOSEONG IEUNG-KIYEOK -> 'g' */
+ { 0x42, 0x64 }, /* HANGUL CHOSEONG IEUNG-TIKEUT -> 'd' */
+ { 0x43, 0x6D }, /* HANGUL CHOSEONG IEUNG-MIEUM -> 'm' */
+ { 0x44, 0x62 }, /* HANGUL CHOSEONG IEUNG-PIEUP -> 'b' */
+ { 0x45, 0x73 }, /* HANGUL CHOSEONG IEUNG-SIOS -> 's' */
+ { 0x46, 0x5A }, /* HANGUL CHOSEONG IEUNG-PANSIOS -> 'Z' */
+ { 0x48, 0x6A }, /* HANGUL CHOSEONG IEUNG-CIEUC -> 'j' */
+ { 0x49, 0x63 }, /* HANGUL CHOSEONG IEUNG-CHIEUCH -> 'c' */
+ { 0x4A, 0x74 }, /* HANGUL CHOSEONG IEUNG-THIEUTH -> 't' */
+ { 0x4B, 0x70 }, /* HANGUL CHOSEONG IEUNG-PHIEUPH -> 'p' */
+ { 0x4C, 0x4E }, /* HANGUL CHOSEONG YESIEUNG -> 'N' */
+ { 0x4D, 0x6A }, /* HANGUL CHOSEONG CIEUC-IEUNG -> 'j' */
+ { 0x59, 0x51 }, /* HANGUL CHOSEONG YEORINHIEUH -> 'Q' */
+ { 0x61, 0x61 }, /* HANGUL JUNGSEONG A -> 'a' */
+ { 0x66, 0x65 }, /* HANGUL JUNGSEONG E -> 'e' */
+ { 0x69, 0x6F }, /* HANGUL JUNGSEONG O -> 'o' */
+ { 0x6E, 0x75 }, /* HANGUL JUNGSEONG U -> 'u' */
+ { 0x75, 0x69 }, /* HANGUL JUNGSEONG I -> 'i' */
+ { 0x9E, 0x55 }, /* HANGUL JUNGSEONG ARAEA -> 'U' */
+ { 0xA8, 0x67 }, /* HANGUL JONGSEONG KIYEOK -> 'g' */
+ { 0xAB, 0x6E }, /* HANGUL JONGSEONG NIEUN -> 'n' */
+ { 0xAE, 0x64 }, /* HANGUL JONGSEONG TIKEUT -> 'd' */
+ { 0xAF, 0x6C }, /* HANGUL JONGSEONG RIEUL -> 'l' */
+ { 0xB7, 0x6D }, /* HANGUL JONGSEONG MIEUM -> 'm' */
+ { 0xB8, 0x62 }, /* HANGUL JONGSEONG PIEUP -> 'b' */
+ { 0xBA, 0x73 }, /* HANGUL JONGSEONG SIOS -> 's' */
+ { 0xBD, 0x6A }, /* HANGUL JONGSEONG CIEUC -> 'j' */
+ { 0xBE, 0x63 }, /* HANGUL JONGSEONG CHIEUCH -> 'c' */
+ { 0xBF, 0x6B }, /* HANGUL JONGSEONG KHIEUKH -> 'k' */
+ { 0xC0, 0x74 }, /* HANGUL JONGSEONG THIEUTH -> 't' */
+ { 0xC1, 0x70 }, /* HANGUL JONGSEONG PHIEUPH -> 'p' */
+ { 0xC2, 0x68 }, /* HANGUL JONGSEONG HIEUH -> 'h' */
+ { 0xEB, 0x5A }, /* HANGUL JONGSEONG PANSIOS -> 'Z' */
+ { 0xEC, 0x67 }, /* HANGUL JONGSEONG IEUNG-KIYEOK -> 'g' */
+ { 0xF0, 0x4E }, /* HANGUL JONGSEONG YESIEUNG -> 'N' */
+ { 0xF9, 0x51 }, /* HANGUL JONGSEONG YEORINHIEUH -> 'Q' */
+ /* Entries for page 0x13 */
+ { 0x61, 0x20 }, /* ETHIOPIC WORDSPACE -> ' ' */
+ { 0x62, 0x2E }, /* ETHIOPIC FULL STOP -> '.' */
+ { 0x63, 0x2C }, /* ETHIOPIC COMMA -> ',' */
+ { 0x64, 0x3B }, /* ETHIOPIC SEMICOLON -> ';' */
+ { 0x65, 0x3A }, /* ETHIOPIC COLON -> ':' */
+ { 0x67, 0x3F }, /* ETHIOPIC QUESTION MARK -> '?' */
+ { 0x69, 0x31 }, /* ETHIOPIC DIGIT ONE -> '1' */
+ { 0x6A, 0x32 }, /* ETHIOPIC DIGIT TWO -> '2' */
+ { 0x6B, 0x33 }, /* ETHIOPIC DIGIT THREE -> '3' */
+ { 0x6C, 0x34 }, /* ETHIOPIC DIGIT FOUR -> '4' */
+ { 0x6D, 0x35 }, /* ETHIOPIC DIGIT FIVE -> '5' */
+ { 0x6E, 0x36 }, /* ETHIOPIC DIGIT SIX -> '6' */
+ { 0x6F, 0x37 }, /* ETHIOPIC DIGIT SEVEN -> '7' */
+ { 0x70, 0x38 }, /* ETHIOPIC DIGIT EIGHT -> '8' */
+ { 0x71, 0x39 }, /* ETHIOPIC DIGIT NINE -> '9' */
+ { 0xA0, 0x61 }, /* CHEROKEE LETTER A -> 'a' */
+ { 0xA1, 0x65 }, /* CHEROKEE LETTER E -> 'e' */
+ { 0xA2, 0x69 }, /* CHEROKEE LETTER I -> 'i' */
+ { 0xA3, 0x6F }, /* CHEROKEE LETTER O -> 'o' */
+ { 0xA4, 0x75 }, /* CHEROKEE LETTER U -> 'u' */
+ { 0xA5, 0x76 }, /* CHEROKEE LETTER V -> 'v' */
+ { 0xCD, 0x73 }, /* CHEROKEE LETTER S -> 's' */
+ /* Entries for page 0x14 */
+ { 0x01, 0x65 }, /* CANADIAN SYLLABICS E -> 'e' */
+ { 0x03, 0x69 }, /* CANADIAN SYLLABICS I -> 'i' */
+ { 0x05, 0x6F }, /* CANADIAN SYLLABICS O -> 'o' */
+ { 0x09, 0x69 }, /* CANADIAN SYLLABICS CARRIER I -> 'i' */
+ { 0x0A, 0x61 }, /* CANADIAN SYLLABICS A -> 'a' */
+ { 0x1D, 0x77 }, /* CANADIAN SYLLABICS Y-CREE W -> 'w' */
+ { 0x1E, 0x27 }, /* CANADIAN SYLLABICS GLOTTAL STOP -> ''' */
+ { 0x1F, 0x74 }, /* CANADIAN SYLLABICS FINAL ACUTE -> 't' */
+ { 0x20, 0x6B }, /* CANADIAN SYLLABICS FINAL GRAVE -> 'k' */
+ { 0x22, 0x73 }, /* CANADIAN SYLLABICS FINAL TOP HALF RING -> 's' */
+ { 0x23, 0x6E }, /* CANADIAN SYLLABICS FINAL RIGHT HALF RING -> 'n' */
+ { 0x24, 0x77 }, /* CANADIAN SYLLABICS FINAL RING -> 'w' */
+ { 0x25, 0x6E }, /* CANADIAN SYLLABICS FINAL DOUBLE ACUTE -> 'n' */
+ { 0x27, 0x77 }, /* CANADIAN SYLLABICS FINAL MIDDLE DOT -> 'w' */
+ { 0x28, 0x63 }, /* CANADIAN SYLLABICS FINAL SHORT HORIZONTAL STROKE -> 'c' */
+ { 0x29, 0x3F }, /* CANADIAN SYLLABICS FINAL PLUS -> '?' */
+ { 0x2A, 0x6C }, /* CANADIAN SYLLABICS FINAL DOWN TACK -> 'l' */
+ { 0x49, 0x70 }, /* CANADIAN SYLLABICS P -> 'p' */
+ { 0x4A, 0x70 }, /* CANADIAN SYLLABICS WEST-CREE P -> 'p' */
+ { 0x4B, 0x68 }, /* CANADIAN SYLLABICS CARRIER H -> 'h' */
+ { 0x66, 0x74 }, /* CANADIAN SYLLABICS T -> 't' */
+ { 0x83, 0x6B }, /* CANADIAN SYLLABICS K -> 'k' */
+ { 0xA1, 0x63 }, /* CANADIAN SYLLABICS C -> 'c' */
+ { 0xBB, 0x6D }, /* CANADIAN SYLLABICS M -> 'm' */
+ { 0xBC, 0x6D }, /* CANADIAN SYLLABICS WEST-CREE M -> 'm' */
+ { 0xBE, 0x6D }, /* CANADIAN SYLLABICS ATHAPASCAN M -> 'm' */
+ { 0xBF, 0x6D }, /* CANADIAN SYLLABICS SAYISI M -> 'm' */
+ { 0xD0, 0x6E }, /* CANADIAN SYLLABICS N -> 'n' */
+ { 0xEA, 0x00 }, /* CANADIAN SYLLABICS L -> ... */
+ { 0xEC, 0x6C }, /* CANADIAN SYLLABICS MEDIAL L -> 'l' */
+ /* Entries for page 0x15 */
+ { 0x05, 0x73 }, /* CANADIAN SYLLABICS S -> 's' */
+ { 0x06, 0x73 }, /* CANADIAN SYLLABICS ATHAPASCAN S -> 's' */
+ { 0x08, 0x73 }, /* CANADIAN SYLLABICS BLACKFOOT S -> 's' */
+ { 0x3E, 0x00 }, /* CANADIAN SYLLABICS Y -> ... */
+ { 0x40, 0x79 }, /* CANADIAN SYLLABICS WEST-CREE Y -> 'y' */
+ { 0x50, 0x00 }, /* CANADIAN SYLLABICS R -> ... */
+ { 0x52, 0x72 }, /* CANADIAN SYLLABICS MEDIAL R -> 'r' */
+ { 0x5D, 0x66 }, /* CANADIAN SYLLABICS F -> 'f' */
+ { 0x7B, 0x68 }, /* CANADIAN SYLLABICS NUNAVIK H -> 'h' */
+ { 0x7C, 0x68 }, /* CANADIAN SYLLABICS NUNAVUT H -> 'h' */
+ { 0x85, 0x71 }, /* CANADIAN SYLLABICS Q -> 'q' */
+ { 0xAF, 0x62 }, /* CANADIAN SYLLABICS AIVILIK B -> 'b' */
+ { 0xB0, 0x65 }, /* CANADIAN SYLLABICS BLACKFOOT E -> 'e' */
+ { 0xB1, 0x69 }, /* CANADIAN SYLLABICS BLACKFOOT I -> 'i' */
+ { 0xB2, 0x6F }, /* CANADIAN SYLLABICS BLACKFOOT O -> 'o' */
+ { 0xB3, 0x61 }, /* CANADIAN SYLLABICS BLACKFOOT A -> 'a' */
+ { 0xEE, 0x70 }, /* CANADIAN SYLLABICS CARRIER P -> 'p' */
+ /* Entries for page 0x16 */
+ { 0x46, 0x7A }, /* CANADIAN SYLLABICS CARRIER Z -> 'z' */
+ { 0x47, 0x7A }, /* CANADIAN SYLLABICS CARRIER INITIAL Z -> 'z' */
+ { 0x6D, 0x58 }, /* CANADIAN SYLLABICS CHI SIGN -> 'X' */
+ { 0x6E, 0x2E }, /* CANADIAN SYLLABICS FULL STOP -> '.' */
+ { 0x80, 0x20 }, /* OGHAM SPACE MARK -> ' ' */
+ { 0x81, 0x62 }, /* OGHAM LETTER BEITH -> 'b' */
+ { 0x82, 0x6C }, /* OGHAM LETTER LUIS -> 'l' */
+ { 0x83, 0x66 }, /* OGHAM LETTER FEARN -> 'f' */
+ { 0x84, 0x73 }, /* OGHAM LETTER SAIL -> 's' */
+ { 0x85, 0x6E }, /* OGHAM LETTER NION -> 'n' */
+ { 0x86, 0x68 }, /* OGHAM LETTER UATH -> 'h' */
+ { 0x87, 0x64 }, /* OGHAM LETTER DAIR -> 'd' */
+ { 0x88, 0x74 }, /* OGHAM LETTER TINNE -> 't' */
+ { 0x89, 0x63 }, /* OGHAM LETTER COLL -> 'c' */
+ { 0x8A, 0x71 }, /* OGHAM LETTER CEIRT -> 'q' */
+ { 0x8B, 0x6D }, /* OGHAM LETTER MUIN -> 'm' */
+ { 0x8C, 0x67 }, /* OGHAM LETTER GORT -> 'g' */
+ { 0x8E, 0x7A }, /* OGHAM LETTER STRAIF -> 'z' */
+ { 0x8F, 0x72 }, /* OGHAM LETTER RUIS -> 'r' */
+ { 0x90, 0x61 }, /* OGHAM LETTER AILM -> 'a' */
+ { 0x91, 0x6F }, /* OGHAM LETTER ONN -> 'o' */
+ { 0x92, 0x75 }, /* OGHAM LETTER UR -> 'u' */
+ { 0x93, 0x65 }, /* OGHAM LETTER EADHADH -> 'e' */
+ { 0x94, 0x69 }, /* OGHAM LETTER IODHADH -> 'i' */
+ { 0x98, 0x70 }, /* OGHAM LETTER IFIN -> 'p' */
+ { 0x99, 0x78 }, /* OGHAM LETTER EAMHANCHOLL -> 'x' */
+ { 0x9A, 0x70 }, /* OGHAM LETTER PEITH -> 'p' */
+ { 0x9B, 0x3C }, /* OGHAM FEATHER MARK -> '<' */
+ { 0x9C, 0x3E }, /* OGHAM REVERSED FEATHER MARK -> '>' */
+ { 0xA0, 0x66 }, /* RUNIC LETTER FEHU FEOH FE F -> 'f' */
+ { 0xA1, 0x76 }, /* RUNIC LETTER V -> 'v' */
+ { 0xA2, 0x75 }, /* RUNIC LETTER URUZ UR U -> 'u' */
+ { 0xA4, 0x79 }, /* RUNIC LETTER Y -> 'y' */
+ { 0xA5, 0x77 }, /* RUNIC LETTER W -> 'w' */
+ { 0xA8, 0x61 }, /* RUNIC LETTER ANSUZ A -> 'a' */
+ { 0xA9, 0x6F }, /* RUNIC LETTER OS O -> 'o' */
+ { 0xAC, 0x00 }, /* RUNIC LETTER LONG-BRANCH-OSS O -> ... */
+ { 0xAE, 0x6F }, /* RUNIC LETTER O -> 'o' */
+ { 0xB1, 0x72 }, /* RUNIC LETTER RAIDO RAD REID R -> 'r' */
+ { 0xB2, 0x6B }, /* RUNIC LETTER KAUNA -> 'k' */
+ { 0xB3, 0x63 }, /* RUNIC LETTER CEN -> 'c' */
+ { 0xB4, 0x6B }, /* RUNIC LETTER KAUN K -> 'k' */
+ { 0xB5, 0x67 }, /* RUNIC LETTER G -> 'g' */
+ { 0xB7, 0x67 }, /* RUNIC LETTER GEBO GYFU G -> 'g' */
+ { 0xB8, 0x67 }, /* RUNIC LETTER GAR -> 'g' */
+ { 0xB9, 0x77 }, /* RUNIC LETTER WUNJO WYNN W -> 'w' */
+ { 0xBA, 0x00 }, /* RUNIC LETTER HAGLAZ H -> ... */
+ { 0xBD, 0x68 }, /* RUNIC LETTER SHORT-TWIG-HAGALL H -> 'h' */
+ { 0xBE, 0x00 }, /* RUNIC LETTER NAUDIZ NYD NAUD N -> ... */
+ { 0xC0, 0x6E }, /* RUNIC LETTER DOTTED-N -> 'n' */
+ { 0xC1, 0x69 }, /* RUNIC LETTER ISAZ IS ISS I -> 'i' */
+ { 0xC2, 0x65 }, /* RUNIC LETTER E -> 'e' */
+ { 0xC3, 0x6A }, /* RUNIC LETTER JERAN J -> 'j' */
+ { 0xC4, 0x67 }, /* RUNIC LETTER GER -> 'g' */
+ { 0xC6, 0x61 }, /* RUNIC LETTER SHORT-TWIG-AR A -> 'a' */
+ { 0xC8, 0x70 }, /* RUNIC LETTER PERTHO PEORTH P -> 'p' */
+ { 0xC9, 0x7A }, /* RUNIC LETTER ALGIZ EOLHX -> 'z' */
+ { 0xCA, 0x00 }, /* RUNIC LETTER SOWILO S -> ... */
+ { 0xCC, 0x73 }, /* RUNIC LETTER SHORT-TWIG-SOL S -> 's' */
+ { 0xCD, 0x63 }, /* RUNIC LETTER C -> 'c' */
+ { 0xCE, 0x7A }, /* RUNIC LETTER Z -> 'z' */
+ { 0xCF, 0x74 }, /* RUNIC LETTER TIWAZ TIR TYR T -> 't' */
+ { 0xD0, 0x74 }, /* RUNIC LETTER SHORT-TWIG-TYR T -> 't' */
+ { 0xD1, 0x64 }, /* RUNIC LETTER D -> 'd' */
+ { 0xD2, 0x62 }, /* RUNIC LETTER BERKANAN BEORC BJARKAN B -> 'b' */
+ { 0xD3, 0x62 }, /* RUNIC LETTER SHORT-TWIG-BJARKAN B -> 'b' */
+ { 0xD4, 0x70 }, /* RUNIC LETTER DOTTED-P -> 'p' */
+ { 0xD5, 0x70 }, /* RUNIC LETTER OPEN-P -> 'p' */
+ { 0xD6, 0x65 }, /* RUNIC LETTER EHWAZ EH E -> 'e' */
+ { 0xD7, 0x00 }, /* RUNIC LETTER MANNAZ MAN M -> ... */
+ { 0xD9, 0x6D }, /* RUNIC LETTER SHORT-TWIG-MADR M -> 'm' */
+ { 0xDA, 0x6C }, /* RUNIC LETTER LAUKAZ LAGU LOGR L -> 'l' */
+ { 0xDB, 0x6C }, /* RUNIC LETTER DOTTED-L -> 'l' */
+ { 0xDE, 0x64 }, /* RUNIC LETTER DAGAZ DAEG D -> 'd' */
+ { 0xDF, 0x6F }, /* RUNIC LETTER OTHALAN ETHEL O -> 'o' */
+ { 0xE5, 0x73 }, /* RUNIC LETTER STAN -> 's' */
+ { 0xE9, 0x71 }, /* RUNIC LETTER Q -> 'q' */
+ { 0xEA, 0x78 }, /* RUNIC LETTER X -> 'x' */
+ { 0xEB, 0x2E }, /* RUNIC SINGLE PUNCTUATION -> '.' */
+ { 0xEC, 0x3A }, /* RUNIC MULTIPLE PUNCTUATION -> ':' */
+ { 0xED, 0x2B }, /* RUNIC CROSS PUNCTUATION -> '+' */
+ /* Entries for page 0x17 */
+ { 0x80, 0x6B }, /* KHMER LETTER KA -> 'k' */
+ { 0x82, 0x67 }, /* KHMER LETTER KO -> 'g' */
+ { 0x85, 0x63 }, /* KHMER LETTER CA -> 'c' */
+ { 0x87, 0x6A }, /* KHMER LETTER CO -> 'j' */
+ { 0x8A, 0x74 }, /* KHMER LETTER DA -> 't' */
+ { 0x8C, 0x64 }, /* KHMER LETTER DO -> 'd' */
+ { 0x8F, 0x74 }, /* KHMER LETTER TA -> 't' */
+ { 0x91, 0x64 }, /* KHMER LETTER TO -> 'd' */
+ { 0x93, 0x6E }, /* KHMER LETTER NO -> 'n' */
+ { 0x94, 0x70 }, /* KHMER LETTER BA -> 'p' */
+ { 0x96, 0x62 }, /* KHMER LETTER PO -> 'b' */
+ { 0x98, 0x6D }, /* KHMER LETTER MO -> 'm' */
+ { 0x99, 0x79 }, /* KHMER LETTER YO -> 'y' */
+ { 0x9A, 0x72 }, /* KHMER LETTER RO -> 'r' */
+ { 0x9B, 0x6C }, /* KHMER LETTER LO -> 'l' */
+ { 0x9C, 0x76 }, /* KHMER LETTER VO -> 'v' */
+ { 0x9F, 0x73 }, /* KHMER LETTER SA -> 's' */
+ { 0xA0, 0x68 }, /* KHMER LETTER HA -> 'h' */
+ { 0xA1, 0x6C }, /* KHMER LETTER LA -> 'l' */
+ { 0xA2, 0x71 }, /* KHMER LETTER QA -> 'q' */
+ { 0xA3, 0x61 }, /* KHMER INDEPENDENT VOWEL QAQ -> 'a' */
+ { 0xA5, 0x69 }, /* KHMER INDEPENDENT VOWEL QI -> 'i' */
+ { 0xA7, 0x75 }, /* KHMER INDEPENDENT VOWEL QU -> 'u' */
+ { 0xAF, 0x65 }, /* KHMER INDEPENDENT VOWEL QE -> 'e' */
+ { 0xB4, 0x61 }, /* KHMER VOWEL INHERENT AQ -> 'a' */
+ { 0xB7, 0x69 }, /* KHMER VOWEL SIGN I -> 'i' */
+ { 0xB9, 0x79 }, /* KHMER VOWEL SIGN Y -> 'y' */
+ { 0xBB, 0x75 }, /* KHMER VOWEL SIGN U -> 'u' */
+ { 0xC1, 0x65 }, /* KHMER VOWEL SIGN E -> 'e' */
+ { 0xC6, 0x4D }, /* KHMER SIGN NIKAHIT -> 'M' */
+ { 0xC7, 0x48 }, /* KHMER SIGN REAHMUK -> 'H' */
+ { 0xCC, 0x72 }, /* KHMER SIGN ROBAT -> 'r' */
+ { 0xCE, 0x21 }, /* KHMER SIGN KAKABAT -> '!' */
+ { 0xD4, 0x2E }, /* KHMER SIGN KHAN -> '.' */
+ { 0xD6, 0x3A }, /* KHMER SIGN CAMNUC PII KUUH -> ':' */
+ { 0xD7, 0x2B }, /* KHMER SIGN LEK TOO -> '+' */
+ { 0xDC, 0x27 }, /* KHMER SIGN AVAKRAHASANYA -> ''' */
+ { 0xE0, 0x30 }, /* KHMER DIGIT ZERO -> '0' */
+ { 0xE1, 0x31 }, /* KHMER DIGIT ONE -> '1' */
+ { 0xE2, 0x32 }, /* KHMER DIGIT TWO -> '2' */
+ { 0xE3, 0x33 }, /* KHMER DIGIT THREE -> '3' */
+ { 0xE4, 0x34 }, /* KHMER DIGIT FOUR -> '4' */
+ { 0xE5, 0x35 }, /* KHMER DIGIT FIVE -> '5' */
+ { 0xE6, 0x36 }, /* KHMER DIGIT SIX -> '6' */
+ { 0xE7, 0x37 }, /* KHMER DIGIT SEVEN -> '7' */
+ { 0xE8, 0x38 }, /* KHMER DIGIT EIGHT -> '8' */
+ { 0xE9, 0x39 }, /* KHMER DIGIT NINE -> '9' */
+ /* Entries for page 0x18 */
+ { 0x07, 0x2D }, /* MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER -> '-' */
+ { 0x10, 0x30 }, /* MONGOLIAN DIGIT ZERO -> '0' */
+ { 0x11, 0x31 }, /* MONGOLIAN DIGIT ONE -> '1' */
+ { 0x12, 0x32 }, /* MONGOLIAN DIGIT TWO -> '2' */
+ { 0x13, 0x33 }, /* MONGOLIAN DIGIT THREE -> '3' */
+ { 0x14, 0x34 }, /* MONGOLIAN DIGIT FOUR -> '4' */
+ { 0x15, 0x35 }, /* MONGOLIAN DIGIT FIVE -> '5' */
+ { 0x16, 0x36 }, /* MONGOLIAN DIGIT SIX -> '6' */
+ { 0x17, 0x37 }, /* MONGOLIAN DIGIT SEVEN -> '7' */
+ { 0x18, 0x38 }, /* MONGOLIAN DIGIT EIGHT -> '8' */
+ { 0x19, 0x39 }, /* MONGOLIAN DIGIT NINE -> '9' */
+ { 0x20, 0x61 }, /* MONGOLIAN LETTER A -> 'a' */
+ { 0x21, 0x65 }, /* MONGOLIAN LETTER E -> 'e' */
+ { 0x22, 0x69 }, /* MONGOLIAN LETTER I -> 'i' */
+ { 0x23, 0x6F }, /* MONGOLIAN LETTER O -> 'o' */
+ { 0x24, 0x75 }, /* MONGOLIAN LETTER U -> 'u' */
+ { 0x25, 0x4F }, /* MONGOLIAN LETTER OE -> 'O' */
+ { 0x26, 0x55 }, /* MONGOLIAN LETTER UE -> 'U' */
+ { 0x28, 0x6E }, /* MONGOLIAN LETTER NA -> 'n' */
+ { 0x2A, 0x62 }, /* MONGOLIAN LETTER BA -> 'b' */
+ { 0x2B, 0x70 }, /* MONGOLIAN LETTER PA -> 'p' */
+ { 0x2C, 0x71 }, /* MONGOLIAN LETTER QA -> 'q' */
+ { 0x2D, 0x67 }, /* MONGOLIAN LETTER GA -> 'g' */
+ { 0x2E, 0x6D }, /* MONGOLIAN LETTER MA -> 'm' */
+ { 0x2F, 0x6C }, /* MONGOLIAN LETTER LA -> 'l' */
+ { 0x30, 0x73 }, /* MONGOLIAN LETTER SA -> 's' */
+ { 0x32, 0x74 }, /* MONGOLIAN LETTER TA -> 't' */
+ { 0x33, 0x64 }, /* MONGOLIAN LETTER DA -> 'd' */
+ { 0x35, 0x6A }, /* MONGOLIAN LETTER JA -> 'j' */
+ { 0x36, 0x79 }, /* MONGOLIAN LETTER YA -> 'y' */
+ { 0x37, 0x72 }, /* MONGOLIAN LETTER RA -> 'r' */
+ { 0x38, 0x77 }, /* MONGOLIAN LETTER WA -> 'w' */
+ { 0x39, 0x66 }, /* MONGOLIAN LETTER FA -> 'f' */
+ { 0x3A, 0x6B }, /* MONGOLIAN LETTER KA -> 'k' */
+ { 0x3D, 0x7A }, /* MONGOLIAN LETTER ZA -> 'z' */
+ { 0x3E, 0x68 }, /* MONGOLIAN LETTER HAA -> 'h' */
+ { 0x43, 0x2D }, /* MONGOLIAN LETTER TODO LONG VOWEL SIGN -> '-' */
+ { 0x44, 0x65 }, /* MONGOLIAN LETTER TODO E -> 'e' */
+ { 0x45, 0x69 }, /* MONGOLIAN LETTER TODO I -> 'i' */
+ { 0x46, 0x6F }, /* MONGOLIAN LETTER TODO O -> 'o' */
+ { 0x47, 0x75 }, /* MONGOLIAN LETTER TODO U -> 'u' */
+ { 0x48, 0x4F }, /* MONGOLIAN LETTER TODO OE -> 'O' */
+ { 0x49, 0x55 }, /* MONGOLIAN LETTER TODO UE -> 'U' */
+ { 0x4B, 0x62 }, /* MONGOLIAN LETTER TODO BA -> 'b' */
+ { 0x4C, 0x70 }, /* MONGOLIAN LETTER TODO PA -> 'p' */
+ { 0x4D, 0x71 }, /* MONGOLIAN LETTER TODO QA -> 'q' */
+ { 0x4E, 0x67 }, /* MONGOLIAN LETTER TODO GA -> 'g' */
+ { 0x4F, 0x6D }, /* MONGOLIAN LETTER TODO MA -> 'm' */
+ { 0x50, 0x74 }, /* MONGOLIAN LETTER TODO TA -> 't' */
+ { 0x51, 0x64 }, /* MONGOLIAN LETTER TODO DA -> 'd' */
+ { 0x53, 0x6A }, /* MONGOLIAN LETTER TODO JA -> 'j' */
+ { 0x55, 0x79 }, /* MONGOLIAN LETTER TODO YA -> 'y' */
+ { 0x56, 0x77 }, /* MONGOLIAN LETTER TODO WA -> 'w' */
+ { 0x57, 0x6B }, /* MONGOLIAN LETTER TODO KA -> 'k' */
+ { 0x58, 0x67 }, /* MONGOLIAN LETTER TODO GAA -> 'g' */
+ { 0x59, 0x68 }, /* MONGOLIAN LETTER TODO HAA -> 'h' */
+ { 0x5D, 0x65 }, /* MONGOLIAN LETTER SIBE E -> 'e' */
+ { 0x5E, 0x69 }, /* MONGOLIAN LETTER SIBE I -> 'i' */
+ { 0x60, 0x55 }, /* MONGOLIAN LETTER SIBE UE -> 'U' */
+ { 0x61, 0x75 }, /* MONGOLIAN LETTER SIBE U -> 'u' */
+ { 0x63, 0x6B }, /* MONGOLIAN LETTER SIBE KA -> 'k' */
+ { 0x64, 0x67 }, /* MONGOLIAN LETTER SIBE GA -> 'g' */
+ { 0x65, 0x68 }, /* MONGOLIAN LETTER SIBE HA -> 'h' */
+ { 0x66, 0x70 }, /* MONGOLIAN LETTER SIBE PA -> 'p' */
+ { 0x68, 0x74 }, /* MONGOLIAN LETTER SIBE TA -> 't' */
+ { 0x69, 0x64 }, /* MONGOLIAN LETTER SIBE DA -> 'd' */
+ { 0x6A, 0x6A }, /* MONGOLIAN LETTER SIBE JA -> 'j' */
+ { 0x6B, 0x66 }, /* MONGOLIAN LETTER SIBE FA -> 'f' */
+ { 0x6C, 0x67 }, /* MONGOLIAN LETTER SIBE GAA -> 'g' */
+ { 0x6D, 0x68 }, /* MONGOLIAN LETTER SIBE HAA -> 'h' */
+ { 0x6F, 0x7A }, /* MONGOLIAN LETTER SIBE ZA -> 'z' */
+ { 0x70, 0x72 }, /* MONGOLIAN LETTER SIBE RAA -> 'r' */
+ { 0x73, 0x69 }, /* MONGOLIAN LETTER MANCHU I -> 'i' */
+ { 0x74, 0x6B }, /* MONGOLIAN LETTER MANCHU KA -> 'k' */
+ { 0x75, 0x72 }, /* MONGOLIAN LETTER MANCHU RA -> 'r' */
+ { 0x76, 0x66 }, /* MONGOLIAN LETTER MANCHU FA -> 'f' */
+ { 0x81, 0x48 }, /* MONGOLIAN LETTER ALI GALI VISARGA ONE -> 'H' */
+ { 0x82, 0x58 }, /* MONGOLIAN LETTER ALI GALI DAMARU -> 'X' */
+ { 0x83, 0x57 }, /* MONGOLIAN LETTER ALI GALI UBADAMA -> 'W' */
+ { 0x84, 0x4D }, /* MONGOLIAN LETTER ALI GALI INVERTED UBADAMA -> 'M' */
+ { 0x87, 0x61 }, /* MONGOLIAN LETTER ALI GALI A -> 'a' */
+ { 0x88, 0x69 }, /* MONGOLIAN LETTER ALI GALI I -> 'i' */
+ { 0x89, 0x6B }, /* MONGOLIAN LETTER ALI GALI KA -> 'k' */
+ { 0x8B, 0x63 }, /* MONGOLIAN LETTER ALI GALI CA -> 'c' */
+ { 0x90, 0x74 }, /* MONGOLIAN LETTER ALI GALI TA -> 't' */
+ { 0x91, 0x64 }, /* MONGOLIAN LETTER ALI GALI DA -> 'd' */
+ { 0x92, 0x70 }, /* MONGOLIAN LETTER ALI GALI PA -> 'p' */
+ { 0x96, 0x7A }, /* MONGOLIAN LETTER ALI GALI ZA -> 'z' */
+ { 0x97, 0x61 }, /* MONGOLIAN LETTER ALI GALI AH -> 'a' */
+ { 0x98, 0x74 }, /* MONGOLIAN LETTER TODO ALI GALI TA -> 't' */
+ { 0x9C, 0x63 }, /* MONGOLIAN LETTER MANCHU ALI GALI CA -> 'c' */
+ { 0xA0, 0x74 }, /* MONGOLIAN LETTER MANCHU ALI GALI TA -> 't' */
+ { 0xA5, 0x7A }, /* MONGOLIAN LETTER MANCHU ALI GALI ZA -> 'z' */
+ { 0xA6, 0x75 }, /* MONGOLIAN LETTER ALI GALI HALF U -> 'u' */
+ { 0xA7, 0x79 }, /* MONGOLIAN LETTER ALI GALI HALF YA -> 'y' */
+ { 0xA9, 0x27 }, /* MONGOLIAN LETTER ALI GALI DAGALGA -> ''' */
+ /* Entries for page 0x1D */
+ { 0x00, 0x41 }, /* LATIN LETTER SMALL CAPITAL A -> 'A' */
+ { 0x03, 0x42 }, /* LATIN LETTER SMALL CAPITAL BARRED B -> 'B' */
+ { 0x04, 0x43 }, /* LATIN LETTER SMALL CAPITAL C -> 'C' */
+ { 0x05, 0x44 }, /* LATIN LETTER SMALL CAPITAL D -> 'D' */
+ { 0x06, 0x44 }, /* LATIN LETTER SMALL CAPITAL ETH -> 'D' */
+ { 0x07, 0x45 }, /* LATIN LETTER SMALL CAPITAL E -> 'E' */
+ { 0x08, 0x65 }, /* LATIN SMALL LETTER TURNED OPEN E -> 'e' */
+ { 0x09, 0x69 }, /* LATIN SMALL LETTER TURNED I -> 'i' */
+ { 0x0A, 0x4A }, /* LATIN LETTER SMALL CAPITAL J -> 'J' */
+ { 0x0B, 0x4B }, /* LATIN LETTER SMALL CAPITAL K -> 'K' */
+ { 0x0C, 0x4C }, /* LATIN LETTER SMALL CAPITAL L WITH STROKE -> 'L' */
+ { 0x0D, 0x4D }, /* LATIN LETTER SMALL CAPITAL M -> 'M' */
+ { 0x0E, 0x4E }, /* LATIN LETTER SMALL CAPITAL REVERSED N -> 'N' */
+ { 0x0F, 0x4F }, /* LATIN LETTER SMALL CAPITAL O -> 'O' */
+ { 0x11, 0x4F }, /* LATIN SMALL LETTER SIDEWAYS O -> 'O' */
+ { 0x13, 0x4F }, /* LATIN SMALL LETTER SIDEWAYS O WITH STROKE -> 'O' */
+ { 0x18, 0x50 }, /* LATIN LETTER SMALL CAPITAL P -> 'P' */
+ { 0x19, 0x52 }, /* LATIN LETTER SMALL CAPITAL REVERSED R -> 'R' */
+ { 0x1A, 0x52 }, /* LATIN LETTER SMALL CAPITAL TURNED R -> 'R' */
+ { 0x1B, 0x54 }, /* LATIN LETTER SMALL CAPITAL T -> 'T' */
+ { 0x1C, 0x55 }, /* LATIN LETTER SMALL CAPITAL U -> 'U' */
+ { 0x1D, 0x75 }, /* LATIN SMALL LETTER SIDEWAYS U -> 'u' */
+ { 0x1E, 0x75 }, /* LATIN SMALL LETTER SIDEWAYS DIAERESIZED U -> 'u' */
+ { 0x1F, 0x6D }, /* LATIN SMALL LETTER SIDEWAYS TURNED M -> 'm' */
+ { 0x20, 0x56 }, /* LATIN LETTER SMALL CAPITAL V -> 'V' */
+ { 0x21, 0x57 }, /* LATIN LETTER SMALL CAPITAL W -> 'W' */
+ { 0x22, 0x5A }, /* LATIN LETTER SMALL CAPITAL Z -> 'Z' */
+ { 0x2C, 0x41 }, /* MODIFIER LETTER CAPITAL A -> 'A' */
+ { 0x2E, 0x42 }, /* MODIFIER LETTER CAPITAL B -> 'B' */
+ { 0x2F, 0x42 }, /* MODIFIER LETTER CAPITAL BARRED B -> 'B' */
+ { 0x30, 0x44 }, /* MODIFIER LETTER CAPITAL D -> 'D' */
+ { 0x31, 0x45 }, /* MODIFIER LETTER CAPITAL E -> 'E' */
+ { 0x32, 0x45 }, /* MODIFIER LETTER CAPITAL REVERSED E -> 'E' */
+ { 0x33, 0x47 }, /* MODIFIER LETTER CAPITAL G -> 'G' */
+ { 0x34, 0x48 }, /* MODIFIER LETTER CAPITAL H -> 'H' */
+ { 0x35, 0x49 }, /* MODIFIER LETTER CAPITAL I -> 'I' */
+ { 0x36, 0x4A }, /* MODIFIER LETTER CAPITAL J -> 'J' */
+ { 0x37, 0x4B }, /* MODIFIER LETTER CAPITAL K -> 'K' */
+ { 0x38, 0x4C }, /* MODIFIER LETTER CAPITAL L -> 'L' */
+ { 0x39, 0x4D }, /* MODIFIER LETTER CAPITAL M -> 'M' */
+ { 0x3A, 0x4E }, /* MODIFIER LETTER CAPITAL N -> 'N' */
+ { 0x3B, 0x4E }, /* MODIFIER LETTER CAPITAL REVERSED N -> 'N' */
+ { 0x3C, 0x4F }, /* MODIFIER LETTER CAPITAL O -> 'O' */
+ { 0x3E, 0x50 }, /* MODIFIER LETTER CAPITAL P -> 'P' */
+ { 0x3F, 0x52 }, /* MODIFIER LETTER CAPITAL R -> 'R' */
+ { 0x40, 0x54 }, /* MODIFIER LETTER CAPITAL T -> 'T' */
+ { 0x41, 0x55 }, /* MODIFIER LETTER CAPITAL U -> 'U' */
+ { 0x42, 0x57 }, /* MODIFIER LETTER CAPITAL W -> 'W' */
+ { 0x43, 0x00 }, /* MODIFIER LETTER SMALL A -> ... */
+ { 0x45, 0x61 }, /* MODIFIER LETTER SMALL ALPHA -> 'a' */
+ { 0x47, 0x62 }, /* MODIFIER LETTER SMALL B -> 'b' */
+ { 0x48, 0x64 }, /* MODIFIER LETTER SMALL D -> 'd' */
+ { 0x49, 0x65 }, /* MODIFIER LETTER SMALL E -> 'e' */
+ { 0x4B, 0x65 }, /* MODIFIER LETTER SMALL OPEN E -> 'e' */
+ { 0x4C, 0x65 }, /* MODIFIER LETTER SMALL TURNED OPEN E -> 'e' */
+ { 0x4D, 0x67 }, /* MODIFIER LETTER SMALL G -> 'g' */
+ { 0x4E, 0x69 }, /* MODIFIER LETTER SMALL TURNED I -> 'i' */
+ { 0x4F, 0x6B }, /* MODIFIER LETTER SMALL K -> 'k' */
+ { 0x50, 0x6D }, /* MODIFIER LETTER SMALL M -> 'm' */
+ { 0x52, 0x6F }, /* MODIFIER LETTER SMALL O -> 'o' */
+ { 0x56, 0x70 }, /* MODIFIER LETTER SMALL P -> 'p' */
+ { 0x57, 0x74 }, /* MODIFIER LETTER SMALL T -> 't' */
+ { 0x58, 0x75 }, /* MODIFIER LETTER SMALL U -> 'u' */
+ { 0x59, 0x75 }, /* MODIFIER LETTER SMALL SIDEWAYS U -> 'u' */
+ { 0x5A, 0x6D }, /* MODIFIER LETTER SMALL TURNED M -> 'm' */
+ { 0x5B, 0x76 }, /* MODIFIER LETTER SMALL V -> 'v' */
+ { 0x5D, 0x62 }, /* MODIFIER LETTER SMALL BETA -> 'b' */
+ { 0x5E, 0x67 }, /* MODIFIER LETTER SMALL GREEK GAMMA -> 'g' */
+ { 0x5F, 0x64 }, /* MODIFIER LETTER SMALL DELTA -> 'd' */
+ { 0x60, 0x66 }, /* MODIFIER LETTER SMALL GREEK PHI -> 'f' */
+ { 0x62, 0x69 }, /* LATIN SUBSCRIPT SMALL LETTER I -> 'i' */
+ { 0x63, 0x72 }, /* LATIN SUBSCRIPT SMALL LETTER R -> 'r' */
+ { 0x64, 0x75 }, /* LATIN SUBSCRIPT SMALL LETTER U -> 'u' */
+ { 0x65, 0x76 }, /* LATIN SUBSCRIPT SMALL LETTER V -> 'v' */
+ { 0x66, 0x62 }, /* GREEK SUBSCRIPT SMALL LETTER BETA -> 'b' */
+ { 0x67, 0x67 }, /* GREEK SUBSCRIPT SMALL LETTER GAMMA -> 'g' */
+ { 0x68, 0x72 }, /* GREEK SUBSCRIPT SMALL LETTER RHO -> 'r' */
+ { 0x69, 0x66 }, /* GREEK SUBSCRIPT SMALL LETTER PHI -> 'f' */
+ { 0x6C, 0x62 }, /* LATIN SMALL LETTER B WITH MIDDLE TILDE -> 'b' */
+ { 0x6D, 0x64 }, /* LATIN SMALL LETTER D WITH MIDDLE TILDE -> 'd' */
+ { 0x6E, 0x66 }, /* LATIN SMALL LETTER F WITH MIDDLE TILDE -> 'f' */
+ { 0x6F, 0x6D }, /* LATIN SMALL LETTER M WITH MIDDLE TILDE -> 'm' */
+ { 0x70, 0x6E }, /* LATIN SMALL LETTER N WITH MIDDLE TILDE -> 'n' */
+ { 0x71, 0x70 }, /* LATIN SMALL LETTER P WITH MIDDLE TILDE -> 'p' */
+ { 0x72, 0x72 }, /* LATIN SMALL LETTER R WITH MIDDLE TILDE -> 'r' */
+ { 0x73, 0x72 }, /* LATIN SMALL LETTER R WITH FISHHOOK AND MIDDLE TILDE -> 'r' */
+ { 0x74, 0x73 }, /* LATIN SMALL LETTER S WITH MIDDLE TILDE -> 's' */
+ { 0x75, 0x74 }, /* LATIN SMALL LETTER T WITH MIDDLE TILDE -> 't' */
+ { 0x76, 0x7A }, /* LATIN SMALL LETTER Z WITH MIDDLE TILDE -> 'z' */
+ { 0x77, 0x67 }, /* LATIN SMALL LETTER TURNED G -> 'g' */
+ { 0x7D, 0x70 }, /* LATIN SMALL LETTER P WITH STROKE -> 'p' */
+ { 0x80, 0x62 }, /* LATIN SMALL LETTER B WITH PALATAL HOOK -> 'b' */
+ { 0x81, 0x64 }, /* LATIN SMALL LETTER D WITH PALATAL HOOK -> 'd' */
+ { 0x82, 0x66 }, /* LATIN SMALL LETTER F WITH PALATAL HOOK -> 'f' */
+ { 0x83, 0x67 }, /* LATIN SMALL LETTER G WITH PALATAL HOOK -> 'g' */
+ { 0x84, 0x6B }, /* LATIN SMALL LETTER K WITH PALATAL HOOK -> 'k' */
+ { 0x85, 0x6C }, /* LATIN SMALL LETTER L WITH PALATAL HOOK -> 'l' */
+ { 0x86, 0x6D }, /* LATIN SMALL LETTER M WITH PALATAL HOOK -> 'm' */
+ { 0x87, 0x6E }, /* LATIN SMALL LETTER N WITH PALATAL HOOK -> 'n' */
+ { 0x88, 0x70 }, /* LATIN SMALL LETTER P WITH PALATAL HOOK -> 'p' */
+ { 0x89, 0x72 }, /* LATIN SMALL LETTER R WITH PALATAL HOOK -> 'r' */
+ { 0x8A, 0x73 }, /* LATIN SMALL LETTER S WITH PALATAL HOOK -> 's' */
+ { 0x8C, 0x76 }, /* LATIN SMALL LETTER V WITH PALATAL HOOK -> 'v' */
+ { 0x8D, 0x78 }, /* LATIN SMALL LETTER X WITH PALATAL HOOK -> 'x' */
+ { 0x8E, 0x7A }, /* LATIN SMALL LETTER Z WITH PALATAL HOOK -> 'z' */
+ /* Entries for page 0x1E */
+ { 0x00, 0x41 }, /* LATIN CAPITAL LETTER A WITH RING BELOW -> 'A' */
+ { 0x01, 0x61 }, /* LATIN SMALL LETTER A WITH RING BELOW -> 'a' */
+ { 0x02, 0x42 }, /* LATIN CAPITAL LETTER B WITH DOT ABOVE -> 'B' */
+ { 0x03, 0x62 }, /* LATIN SMALL LETTER B WITH DOT ABOVE -> 'b' */
+ { 0x04, 0x42 }, /* LATIN CAPITAL LETTER B WITH DOT BELOW -> 'B' */
+ { 0x05, 0x62 }, /* LATIN SMALL LETTER B WITH DOT BELOW -> 'b' */
+ { 0x06, 0x42 }, /* LATIN CAPITAL LETTER B WITH LINE BELOW -> 'B' */
+ { 0x07, 0x62 }, /* LATIN SMALL LETTER B WITH LINE BELOW -> 'b' */
+ { 0x08, 0x43 }, /* LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE -> 'C' */
+ { 0x09, 0x63 }, /* LATIN SMALL LETTER C WITH CEDILLA AND ACUTE -> 'c' */
+ { 0x0A, 0x44 }, /* LATIN CAPITAL LETTER D WITH DOT ABOVE -> 'D' */
+ { 0x0B, 0x64 }, /* LATIN SMALL LETTER D WITH DOT ABOVE -> 'd' */
+ { 0x0C, 0x44 }, /* LATIN CAPITAL LETTER D WITH DOT BELOW -> 'D' */
+ { 0x0D, 0x64 }, /* LATIN SMALL LETTER D WITH DOT BELOW -> 'd' */
+ { 0x0E, 0x44 }, /* LATIN CAPITAL LETTER D WITH LINE BELOW -> 'D' */
+ { 0x0F, 0x64 }, /* LATIN SMALL LETTER D WITH LINE BELOW -> 'd' */
+ { 0x10, 0x44 }, /* LATIN CAPITAL LETTER D WITH CEDILLA -> 'D' */
+ { 0x11, 0x64 }, /* LATIN SMALL LETTER D WITH CEDILLA -> 'd' */
+ { 0x12, 0x44 }, /* LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW -> 'D' */
+ { 0x13, 0x64 }, /* LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW -> 'd' */
+ { 0x14, 0x45 }, /* LATIN CAPITAL LETTER E WITH MACRON AND GRAVE -> 'E' */
+ { 0x15, 0x65 }, /* LATIN SMALL LETTER E WITH MACRON AND GRAVE -> 'e' */
+ { 0x16, 0x45 }, /* LATIN CAPITAL LETTER E WITH MACRON AND ACUTE -> 'E' */
+ { 0x17, 0x65 }, /* LATIN SMALL LETTER E WITH MACRON AND ACUTE -> 'e' */
+ { 0x18, 0x45 }, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW -> 'E' */
+ { 0x19, 0x65 }, /* LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW -> 'e' */
+ { 0x1A, 0x45 }, /* LATIN CAPITAL LETTER E WITH TILDE BELOW -> 'E' */
+ { 0x1B, 0x65 }, /* LATIN SMALL LETTER E WITH TILDE BELOW -> 'e' */
+ { 0x1C, 0x45 }, /* LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE -> 'E' */
+ { 0x1D, 0x65 }, /* LATIN SMALL LETTER E WITH CEDILLA AND BREVE -> 'e' */
+ { 0x1E, 0x46 }, /* LATIN CAPITAL LETTER F WITH DOT ABOVE -> 'F' */
+ { 0x1F, 0x66 }, /* LATIN SMALL LETTER F WITH DOT ABOVE -> 'f' */
+ { 0x20, 0x47 }, /* LATIN CAPITAL LETTER G WITH MACRON -> 'G' */
+ { 0x21, 0x67 }, /* LATIN SMALL LETTER G WITH MACRON -> 'g' */
+ { 0x22, 0x48 }, /* LATIN CAPITAL LETTER H WITH DOT ABOVE -> 'H' */
+ { 0x23, 0x68 }, /* LATIN SMALL LETTER H WITH DOT ABOVE -> 'h' */
+ { 0x24, 0x48 }, /* LATIN CAPITAL LETTER H WITH DOT BELOW -> 'H' */
+ { 0x25, 0x68 }, /* LATIN SMALL LETTER H WITH DOT BELOW -> 'h' */
+ { 0x26, 0x48 }, /* LATIN CAPITAL LETTER H WITH DIAERESIS -> 'H' */
+ { 0x27, 0x68 }, /* LATIN SMALL LETTER H WITH DIAERESIS -> 'h' */
+ { 0x28, 0x48 }, /* LATIN CAPITAL LETTER H WITH CEDILLA -> 'H' */
+ { 0x29, 0x68 }, /* LATIN SMALL LETTER H WITH CEDILLA -> 'h' */
+ { 0x2A, 0x48 }, /* LATIN CAPITAL LETTER H WITH BREVE BELOW -> 'H' */
+ { 0x2B, 0x68 }, /* LATIN SMALL LETTER H WITH BREVE BELOW -> 'h' */
+ { 0x2C, 0x49 }, /* LATIN CAPITAL LETTER I WITH TILDE BELOW -> 'I' */
+ { 0x2D, 0x69 }, /* LATIN SMALL LETTER I WITH TILDE BELOW -> 'i' */
+ { 0x2E, 0x49 }, /* LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE -> 'I' */
+ { 0x2F, 0x69 }, /* LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE -> 'i' */
+ { 0x30, 0x4B }, /* LATIN CAPITAL LETTER K WITH ACUTE -> 'K' */
+ { 0x31, 0x6B }, /* LATIN SMALL LETTER K WITH ACUTE -> 'k' */
+ { 0x32, 0x4B }, /* LATIN CAPITAL LETTER K WITH DOT BELOW -> 'K' */
+ { 0x33, 0x6B }, /* LATIN SMALL LETTER K WITH DOT BELOW -> 'k' */
+ { 0x34, 0x4B }, /* LATIN CAPITAL LETTER K WITH LINE BELOW -> 'K' */
+ { 0x35, 0x6B }, /* LATIN SMALL LETTER K WITH LINE BELOW -> 'k' */
+ { 0x36, 0x4C }, /* LATIN CAPITAL LETTER L WITH DOT BELOW -> 'L' */
+ { 0x37, 0x6C }, /* LATIN SMALL LETTER L WITH DOT BELOW -> 'l' */
+ { 0x38, 0x4C }, /* LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON -> 'L' */
+ { 0x39, 0x6C }, /* LATIN SMALL LETTER L WITH DOT BELOW AND MACRON -> 'l' */
+ { 0x3A, 0x4C }, /* LATIN CAPITAL LETTER L WITH LINE BELOW -> 'L' */
+ { 0x3B, 0x6C }, /* LATIN SMALL LETTER L WITH LINE BELOW -> 'l' */
+ { 0x3C, 0x4C }, /* LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW -> 'L' */
+ { 0x3D, 0x6C }, /* LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW -> 'l' */
+ { 0x3E, 0x4D }, /* LATIN CAPITAL LETTER M WITH ACUTE -> 'M' */
+ { 0x3F, 0x6D }, /* LATIN SMALL LETTER M WITH ACUTE -> 'm' */
+ { 0x40, 0x4D }, /* LATIN CAPITAL LETTER M WITH DOT ABOVE -> 'M' */
+ { 0x41, 0x6D }, /* LATIN SMALL LETTER M WITH DOT ABOVE -> 'm' */
+ { 0x42, 0x4D }, /* LATIN CAPITAL LETTER M WITH DOT BELOW -> 'M' */
+ { 0x43, 0x6D }, /* LATIN SMALL LETTER M WITH DOT BELOW -> 'm' */
+ { 0x44, 0x4E }, /* LATIN CAPITAL LETTER N WITH DOT ABOVE -> 'N' */
+ { 0x45, 0x6E }, /* LATIN SMALL LETTER N WITH DOT ABOVE -> 'n' */
+ { 0x46, 0x4E }, /* LATIN CAPITAL LETTER N WITH DOT BELOW -> 'N' */
+ { 0x47, 0x6E }, /* LATIN SMALL LETTER N WITH DOT BELOW -> 'n' */
+ { 0x48, 0x4E }, /* LATIN CAPITAL LETTER N WITH LINE BELOW -> 'N' */
+ { 0x49, 0x6E }, /* LATIN SMALL LETTER N WITH LINE BELOW -> 'n' */
+ { 0x4A, 0x4E }, /* LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW -> 'N' */
+ { 0x4B, 0x6E }, /* LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW -> 'n' */
+ { 0x4C, 0x4F }, /* LATIN CAPITAL LETTER O WITH TILDE AND ACUTE -> 'O' */
+ { 0x4D, 0x6F }, /* LATIN SMALL LETTER O WITH TILDE AND ACUTE -> 'o' */
+ { 0x4E, 0x4F }, /* LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS -> 'O' */
+ { 0x4F, 0x6F }, /* LATIN SMALL LETTER O WITH TILDE AND DIAERESIS -> 'o' */
+ { 0x50, 0x4F }, /* LATIN CAPITAL LETTER O WITH MACRON AND GRAVE -> 'O' */
+ { 0x51, 0x6F }, /* LATIN SMALL LETTER O WITH MACRON AND GRAVE -> 'o' */
+ { 0x52, 0x4F }, /* LATIN CAPITAL LETTER O WITH MACRON AND ACUTE -> 'O' */
+ { 0x53, 0x6F }, /* LATIN SMALL LETTER O WITH MACRON AND ACUTE -> 'o' */
+ { 0x54, 0x50 }, /* LATIN CAPITAL LETTER P WITH ACUTE -> 'P' */
+ { 0x55, 0x70 }, /* LATIN SMALL LETTER P WITH ACUTE -> 'p' */
+ { 0x56, 0x50 }, /* LATIN CAPITAL LETTER P WITH DOT ABOVE -> 'P' */
+ { 0x57, 0x70 }, /* LATIN SMALL LETTER P WITH DOT ABOVE -> 'p' */
+ { 0x58, 0x52 }, /* LATIN CAPITAL LETTER R WITH DOT ABOVE -> 'R' */
+ { 0x59, 0x72 }, /* LATIN SMALL LETTER R WITH DOT ABOVE -> 'r' */
+ { 0x5A, 0x52 }, /* LATIN CAPITAL LETTER R WITH DOT BELOW -> 'R' */
+ { 0x5B, 0x72 }, /* LATIN SMALL LETTER R WITH DOT BELOW -> 'r' */
+ { 0x5C, 0x52 }, /* LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON -> 'R' */
+ { 0x5D, 0x72 }, /* LATIN SMALL LETTER R WITH DOT BELOW AND MACRON -> 'r' */
+ { 0x5E, 0x52 }, /* LATIN CAPITAL LETTER R WITH LINE BELOW -> 'R' */
+ { 0x5F, 0x72 }, /* LATIN SMALL LETTER R WITH LINE BELOW -> 'r' */
+ { 0x60, 0x53 }, /* LATIN CAPITAL LETTER S WITH DOT ABOVE -> 'S' */
+ { 0x61, 0x73 }, /* LATIN SMALL LETTER S WITH DOT ABOVE -> 's' */
+ { 0x62, 0x53 }, /* LATIN CAPITAL LETTER S WITH DOT BELOW -> 'S' */
+ { 0x63, 0x73 }, /* LATIN SMALL LETTER S WITH DOT BELOW -> 's' */
+ { 0x64, 0x53 }, /* LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE -> 'S' */
+ { 0x65, 0x73 }, /* LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE -> 's' */
+ { 0x66, 0x53 }, /* LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE -> 'S' */
+ { 0x67, 0x73 }, /* LATIN SMALL LETTER S WITH CARON AND DOT ABOVE -> 's' */
+ { 0x68, 0x53 }, /* LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE -> 'S' */
+ { 0x69, 0x73 }, /* LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE -> 's' */
+ { 0x6A, 0x54 }, /* LATIN CAPITAL LETTER T WITH DOT ABOVE -> 'T' */
+ { 0x6B, 0x74 }, /* LATIN SMALL LETTER T WITH DOT ABOVE -> 't' */
+ { 0x6C, 0x54 }, /* LATIN CAPITAL LETTER T WITH DOT BELOW -> 'T' */
+ { 0x6D, 0x74 }, /* LATIN SMALL LETTER T WITH DOT BELOW -> 't' */
+ { 0x6E, 0x54 }, /* LATIN CAPITAL LETTER T WITH LINE BELOW -> 'T' */
+ { 0x6F, 0x74 }, /* LATIN SMALL LETTER T WITH LINE BELOW -> 't' */
+ { 0x70, 0x54 }, /* LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW -> 'T' */
+ { 0x71, 0x74 }, /* LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW -> 't' */
+ { 0x72, 0x55 }, /* LATIN CAPITAL LETTER U WITH DIAERESIS BELOW -> 'U' */
+ { 0x73, 0x75 }, /* LATIN SMALL LETTER U WITH DIAERESIS BELOW -> 'u' */
+ { 0x74, 0x55 }, /* LATIN CAPITAL LETTER U WITH TILDE BELOW -> 'U' */
+ { 0x75, 0x75 }, /* LATIN SMALL LETTER U WITH TILDE BELOW -> 'u' */
+ { 0x76, 0x55 }, /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW -> 'U' */
+ { 0x77, 0x75 }, /* LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW -> 'u' */
+ { 0x78, 0x55 }, /* LATIN CAPITAL LETTER U WITH TILDE AND ACUTE -> 'U' */
+ { 0x79, 0x75 }, /* LATIN SMALL LETTER U WITH TILDE AND ACUTE -> 'u' */
+ { 0x7A, 0x55 }, /* LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS -> 'U' */
+ { 0x7B, 0x75 }, /* LATIN SMALL LETTER U WITH MACRON AND DIAERESIS -> 'u' */
+ { 0x7C, 0x56 }, /* LATIN CAPITAL LETTER V WITH TILDE -> 'V' */
+ { 0x7D, 0x76 }, /* LATIN SMALL LETTER V WITH TILDE -> 'v' */
+ { 0x7E, 0x56 }, /* LATIN CAPITAL LETTER V WITH DOT BELOW -> 'V' */
+ { 0x7F, 0x76 }, /* LATIN SMALL LETTER V WITH DOT BELOW -> 'v' */
+ { 0x80, 0x57 }, /* LATIN CAPITAL LETTER W WITH GRAVE -> 'W' */
+ { 0x81, 0x77 }, /* LATIN SMALL LETTER W WITH GRAVE -> 'w' */
+ { 0x82, 0x57 }, /* LATIN CAPITAL LETTER W WITH ACUTE -> 'W' */
+ { 0x83, 0x77 }, /* LATIN SMALL LETTER W WITH ACUTE -> 'w' */
+ { 0x84, 0x57 }, /* LATIN CAPITAL LETTER W WITH DIAERESIS -> 'W' */
+ { 0x85, 0x77 }, /* LATIN SMALL LETTER W WITH DIAERESIS -> 'w' */
+ { 0x86, 0x57 }, /* LATIN CAPITAL LETTER W WITH DOT ABOVE -> 'W' */
+ { 0x87, 0x77 }, /* LATIN SMALL LETTER W WITH DOT ABOVE -> 'w' */
+ { 0x88, 0x57 }, /* LATIN CAPITAL LETTER W WITH DOT BELOW -> 'W' */
+ { 0x89, 0x77 }, /* LATIN SMALL LETTER W WITH DOT BELOW -> 'w' */
+ { 0x8A, 0x58 }, /* LATIN CAPITAL LETTER X WITH DOT ABOVE -> 'X' */
+ { 0x8B, 0x78 }, /* LATIN SMALL LETTER X WITH DOT ABOVE -> 'x' */
+ { 0x8C, 0x58 }, /* LATIN CAPITAL LETTER X WITH DIAERESIS -> 'X' */
+ { 0x8D, 0x78 }, /* LATIN SMALL LETTER X WITH DIAERESIS -> 'x' */
+ { 0x8E, 0x59 }, /* LATIN CAPITAL LETTER Y WITH DOT ABOVE -> 'Y' */
+ { 0x8F, 0x79 }, /* LATIN SMALL LETTER Y WITH DOT ABOVE -> 'y' */
+ { 0x90, 0x5A }, /* LATIN CAPITAL LETTER Z WITH CIRCUMFLEX -> 'Z' */
+ { 0x91, 0x7A }, /* LATIN SMALL LETTER Z WITH CIRCUMFLEX -> 'z' */
+ { 0x92, 0x5A }, /* LATIN CAPITAL LETTER Z WITH DOT BELOW -> 'Z' */
+ { 0x93, 0x7A }, /* LATIN SMALL LETTER Z WITH DOT BELOW -> 'z' */
+ { 0x94, 0x5A }, /* LATIN CAPITAL LETTER Z WITH LINE BELOW -> 'Z' */
+ { 0x95, 0x7A }, /* LATIN SMALL LETTER Z WITH LINE BELOW -> 'z' */
+ { 0x96, 0x68 }, /* LATIN SMALL LETTER H WITH LINE BELOW -> 'h' */
+ { 0x97, 0x74 }, /* LATIN SMALL LETTER T WITH DIAERESIS -> 't' */
+ { 0x98, 0x77 }, /* LATIN SMALL LETTER W WITH RING ABOVE -> 'w' */
+ { 0x99, 0x79 }, /* LATIN SMALL LETTER Y WITH RING ABOVE -> 'y' */
+ { 0x9A, 0x61 }, /* LATIN SMALL LETTER A WITH RIGHT HALF RING -> 'a' */
+ { 0x9B, 0x53 }, /* LATIN SMALL LETTER LONG S WITH DOT ABOVE -> 'S' */
+ { 0xA0, 0x41 }, /* LATIN CAPITAL LETTER A WITH DOT BELOW -> 'A' */
+ { 0xA1, 0x61 }, /* LATIN SMALL LETTER A WITH DOT BELOW -> 'a' */
+ { 0xA2, 0x41 }, /* LATIN CAPITAL LETTER A WITH HOOK ABOVE -> 'A' */
+ { 0xA3, 0x61 }, /* LATIN SMALL LETTER A WITH HOOK ABOVE -> 'a' */
+ { 0xA4, 0x41 }, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE -> 'A' */
+ { 0xA5, 0x61 }, /* LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE -> 'a' */
+ { 0xA6, 0x41 }, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE -> 'A' */
+ { 0xA7, 0x61 }, /* LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE -> 'a' */
+ { 0xA8, 0x41 }, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE -> 'A' */
+ { 0xA9, 0x61 }, /* LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE -> 'a' */
+ { 0xAA, 0x41 }, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE -> 'A' */
+ { 0xAB, 0x61 }, /* LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE -> 'a' */
+ { 0xAC, 0x41 }, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW -> 'A' */
+ { 0xAD, 0x61 }, /* LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW -> 'a' */
+ { 0xAE, 0x41 }, /* LATIN CAPITAL LETTER A WITH BREVE AND ACUTE -> 'A' */
+ { 0xAF, 0x61 }, /* LATIN SMALL LETTER A WITH BREVE AND ACUTE -> 'a' */
+ { 0xB0, 0x41 }, /* LATIN CAPITAL LETTER A WITH BREVE AND GRAVE -> 'A' */
+ { 0xB1, 0x61 }, /* LATIN SMALL LETTER A WITH BREVE AND GRAVE -> 'a' */
+ { 0xB2, 0x41 }, /* LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE -> 'A' */
+ { 0xB3, 0x61 }, /* LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE -> 'a' */
+ { 0xB4, 0x41 }, /* LATIN CAPITAL LETTER A WITH BREVE AND TILDE -> 'A' */
+ { 0xB5, 0x61 }, /* LATIN SMALL LETTER A WITH BREVE AND TILDE -> 'a' */
+ { 0xB6, 0x41 }, /* LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW -> 'A' */
+ { 0xB7, 0x61 }, /* LATIN SMALL LETTER A WITH BREVE AND DOT BELOW -> 'a' */
+ { 0xB8, 0x45 }, /* LATIN CAPITAL LETTER E WITH DOT BELOW -> 'E' */
+ { 0xB9, 0x65 }, /* LATIN SMALL LETTER E WITH DOT BELOW -> 'e' */
+ { 0xBA, 0x45 }, /* LATIN CAPITAL LETTER E WITH HOOK ABOVE -> 'E' */
+ { 0xBB, 0x65 }, /* LATIN SMALL LETTER E WITH HOOK ABOVE -> 'e' */
+ { 0xBC, 0x45 }, /* LATIN CAPITAL LETTER E WITH TILDE -> 'E' */
+ { 0xBD, 0x65 }, /* LATIN SMALL LETTER E WITH TILDE -> 'e' */
+ { 0xBE, 0x45 }, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE -> 'E' */
+ { 0xBF, 0x65 }, /* LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE -> 'e' */
+ { 0xC0, 0x45 }, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE -> 'E' */
+ { 0xC1, 0x65 }, /* LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE -> 'e' */
+ { 0xC2, 0x45 }, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE -> 'E' */
+ { 0xC3, 0x65 }, /* LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE -> 'e' */
+ { 0xC4, 0x45 }, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE -> 'E' */
+ { 0xC5, 0x65 }, /* LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE -> 'e' */
+ { 0xC6, 0x45 }, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW -> 'E' */
+ { 0xC7, 0x65 }, /* LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW -> 'e' */
+ { 0xC8, 0x49 }, /* LATIN CAPITAL LETTER I WITH HOOK ABOVE -> 'I' */
+ { 0xC9, 0x69 }, /* LATIN SMALL LETTER I WITH HOOK ABOVE -> 'i' */
+ { 0xCA, 0x49 }, /* LATIN CAPITAL LETTER I WITH DOT BELOW -> 'I' */
+ { 0xCB, 0x69 }, /* LATIN SMALL LETTER I WITH DOT BELOW -> 'i' */
+ { 0xCC, 0x4F }, /* LATIN CAPITAL LETTER O WITH DOT BELOW -> 'O' */
+ { 0xCD, 0x6F }, /* LATIN SMALL LETTER O WITH DOT BELOW -> 'o' */
+ { 0xCE, 0x4F }, /* LATIN CAPITAL LETTER O WITH HOOK ABOVE -> 'O' */
+ { 0xCF, 0x6F }, /* LATIN SMALL LETTER O WITH HOOK ABOVE -> 'o' */
+ { 0xD0, 0x4F }, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE -> 'O' */
+ { 0xD1, 0x6F }, /* LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE -> 'o' */
+ { 0xD2, 0x4F }, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE -> 'O' */
+ { 0xD3, 0x6F }, /* LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE -> 'o' */
+ { 0xD4, 0x4F }, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE -> 'O' */
+ { 0xD5, 0x6F }, /* LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE -> 'o' */
+ { 0xD6, 0x4F }, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE -> 'O' */
+ { 0xD7, 0x6F }, /* LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE -> 'o' */
+ { 0xD8, 0x4F }, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW -> 'O' */
+ { 0xD9, 0x6F }, /* LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW -> 'o' */
+ { 0xDA, 0x4F }, /* LATIN CAPITAL LETTER O WITH HORN AND ACUTE -> 'O' */
+ { 0xDB, 0x6F }, /* LATIN SMALL LETTER O WITH HORN AND ACUTE -> 'o' */
+ { 0xDC, 0x4F }, /* LATIN CAPITAL LETTER O WITH HORN AND GRAVE -> 'O' */
+ { 0xDD, 0x6F }, /* LATIN SMALL LETTER O WITH HORN AND GRAVE -> 'o' */
+ { 0xDE, 0x4F }, /* LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE -> 'O' */
+ { 0xDF, 0x6F }, /* LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE -> 'o' */
+ { 0xE0, 0x4F }, /* LATIN CAPITAL LETTER O WITH HORN AND TILDE -> 'O' */
+ { 0xE1, 0x6F }, /* LATIN SMALL LETTER O WITH HORN AND TILDE -> 'o' */
+ { 0xE2, 0x4F }, /* LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW -> 'O' */
+ { 0xE3, 0x6F }, /* LATIN SMALL LETTER O WITH HORN AND DOT BELOW -> 'o' */
+ { 0xE4, 0x55 }, /* LATIN CAPITAL LETTER U WITH DOT BELOW -> 'U' */
+ { 0xE5, 0x75 }, /* LATIN SMALL LETTER U WITH DOT BELOW -> 'u' */
+ { 0xE6, 0x55 }, /* LATIN CAPITAL LETTER U WITH HOOK ABOVE -> 'U' */
+ { 0xE7, 0x75 }, /* LATIN SMALL LETTER U WITH HOOK ABOVE -> 'u' */
+ { 0xE8, 0x55 }, /* LATIN CAPITAL LETTER U WITH HORN AND ACUTE -> 'U' */
+ { 0xE9, 0x75 }, /* LATIN SMALL LETTER U WITH HORN AND ACUTE -> 'u' */
+ { 0xEA, 0x55 }, /* LATIN CAPITAL LETTER U WITH HORN AND GRAVE -> 'U' */
+ { 0xEB, 0x75 }, /* LATIN SMALL LETTER U WITH HORN AND GRAVE -> 'u' */
+ { 0xEC, 0x55 }, /* LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE -> 'U' */
+ { 0xED, 0x75 }, /* LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE -> 'u' */
+ { 0xEE, 0x55 }, /* LATIN CAPITAL LETTER U WITH HORN AND TILDE -> 'U' */
+ { 0xEF, 0x75 }, /* LATIN SMALL LETTER U WITH HORN AND TILDE -> 'u' */
+ { 0xF0, 0x55 }, /* LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW -> 'U' */
+ { 0xF1, 0x75 }, /* LATIN SMALL LETTER U WITH HORN AND DOT BELOW -> 'u' */
+ { 0xF2, 0x59 }, /* LATIN CAPITAL LETTER Y WITH GRAVE -> 'Y' */
+ { 0xF3, 0x79 }, /* LATIN SMALL LETTER Y WITH GRAVE -> 'y' */
+ { 0xF4, 0x59 }, /* LATIN CAPITAL LETTER Y WITH DOT BELOW -> 'Y' */
+ { 0xF5, 0x79 }, /* LATIN SMALL LETTER Y WITH DOT BELOW -> 'y' */
+ { 0xF6, 0x59 }, /* LATIN CAPITAL LETTER Y WITH HOOK ABOVE -> 'Y' */
+ { 0xF7, 0x79 }, /* LATIN SMALL LETTER Y WITH HOOK ABOVE -> 'y' */
+ { 0xF8, 0x59 }, /* LATIN CAPITAL LETTER Y WITH TILDE -> 'Y' */
+ { 0xF9, 0x79 }, /* LATIN SMALL LETTER Y WITH TILDE -> 'y' */
+ /* Entries for page 0x1F */
+ { 0x00, 0x00 }, /* GREEK SMALL LETTER ALPHA WITH PSILI -> ... */
+ { 0x07, 0x61 }, /* GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI -> 'a' */
+ { 0x08, 0x00 }, /* GREEK CAPITAL LETTER ALPHA WITH PSILI -> ... */
+ { 0x0F, 0x41 }, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI -> 'A' */
+ { 0x10, 0x00 }, /* GREEK SMALL LETTER EPSILON WITH PSILI -> ... */
+ { 0x15, 0x65 }, /* GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA -> 'e' */
+ { 0x18, 0x00 }, /* GREEK CAPITAL LETTER EPSILON WITH PSILI -> ... */
+ { 0x1D, 0x45 }, /* GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA -> 'E' */
+ { 0x20, 0x00 }, /* GREEK SMALL LETTER ETA WITH PSILI -> ... */
+ { 0x27, 0x65 }, /* GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI -> 'e' */
+ { 0x28, 0x00 }, /* GREEK CAPITAL LETTER ETA WITH PSILI -> ... */
+ { 0x2F, 0x45 }, /* GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI -> 'E' */
+ { 0x30, 0x00 }, /* GREEK SMALL LETTER IOTA WITH PSILI -> ... */
+ { 0x37, 0x69 }, /* GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI -> 'i' */
+ { 0x38, 0x00 }, /* GREEK CAPITAL LETTER IOTA WITH PSILI -> ... */
+ { 0x3F, 0x49 }, /* GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI -> 'I' */
+ { 0x40, 0x00 }, /* GREEK SMALL LETTER OMICRON WITH PSILI -> ... */
+ { 0x45, 0x6F }, /* GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA -> 'o' */
+ { 0x48, 0x00 }, /* GREEK CAPITAL LETTER OMICRON WITH PSILI -> ... */
+ { 0x4D, 0x4F }, /* GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA -> 'O' */
+ { 0x50, 0x00 }, /* GREEK SMALL LETTER UPSILON WITH PSILI -> ... */
+ { 0x57, 0x75 }, /* GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI -> 'u' */
+ { 0x59, 0x55 }, /* GREEK CAPITAL LETTER UPSILON WITH DASIA -> 'U' */
+ { 0x5B, 0x55 }, /* GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA -> 'U' */
+ { 0x5D, 0x55 }, /* GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA -> 'U' */
+ { 0x5F, 0x55 }, /* GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI -> 'U' */
+ { 0x60, 0x00 }, /* GREEK SMALL LETTER OMEGA WITH PSILI -> ... */
+ { 0x67, 0x6F }, /* GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI -> 'o' */
+ { 0x68, 0x00 }, /* GREEK CAPITAL LETTER OMEGA WITH PSILI -> ... */
+ { 0x6F, 0x4F }, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI -> 'O' */
+ { 0x70, 0x61 }, /* GREEK SMALL LETTER ALPHA WITH VARIA -> 'a' */
+ { 0x71, 0x61 }, /* GREEK SMALL LETTER ALPHA WITH OXIA -> 'a' */
+ { 0x72, 0x00 }, /* GREEK SMALL LETTER EPSILON WITH VARIA -> ... */
+ { 0x75, 0x65 }, /* GREEK SMALL LETTER ETA WITH OXIA -> 'e' */
+ { 0x76, 0x69 }, /* GREEK SMALL LETTER IOTA WITH VARIA -> 'i' */
+ { 0x77, 0x69 }, /* GREEK SMALL LETTER IOTA WITH OXIA -> 'i' */
+ { 0x78, 0x6F }, /* GREEK SMALL LETTER OMICRON WITH VARIA -> 'o' */
+ { 0x79, 0x6F }, /* GREEK SMALL LETTER OMICRON WITH OXIA -> 'o' */
+ { 0x7A, 0x75 }, /* GREEK SMALL LETTER UPSILON WITH VARIA -> 'u' */
+ { 0x7B, 0x75 }, /* GREEK SMALL LETTER UPSILON WITH OXIA -> 'u' */
+ { 0x7C, 0x6F }, /* GREEK SMALL LETTER OMEGA WITH VARIA -> 'o' */
+ { 0x7D, 0x6F }, /* GREEK SMALL LETTER OMEGA WITH OXIA -> 'o' */
+ { 0x80, 0x00 }, /* GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI -> ... */
+ { 0x87, 0x61 }, /* GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI -> 'a' */
+ { 0x88, 0x00 }, /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI -> ... */
+ { 0x8F, 0x41 }, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI -> 'A' */
+ { 0x90, 0x00 }, /* GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI -> ... */
+ { 0x97, 0x65 }, /* GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI -> 'e' */
+ { 0x98, 0x00 }, /* GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI -> ... */
+ { 0x9F, 0x45 }, /* GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI -> 'E' */
+ { 0xA0, 0x00 }, /* GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI -> ... */
+ { 0xA7, 0x6F }, /* GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI -> 'o' */
+ { 0xA8, 0x00 }, /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI -> ... */
+ { 0xAF, 0x4F }, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI -> 'O' */
+ { 0xB0, 0x00 }, /* GREEK SMALL LETTER ALPHA WITH VRACHY -> ... */
+ { 0xB4, 0x61 }, /* GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI -> 'a' */
+ { 0xB6, 0x61 }, /* GREEK SMALL LETTER ALPHA WITH PERISPOMENI -> 'a' */
+ { 0xB7, 0x61 }, /* GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI -> 'a' */
+ { 0xB8, 0x00 }, /* GREEK CAPITAL LETTER ALPHA WITH VRACHY -> ... */
+ { 0xBC, 0x41 }, /* GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI -> 'A' */
+ { 0xBD, 0x27 }, /* GREEK KORONIS -> ''' */
+ { 0xBE, 0x69 }, /* GREEK PROSGEGRAMMENI -> 'i' */
+ { 0xBF, 0x27 }, /* GREEK PSILI -> ''' */
+ { 0xC0, 0x7E }, /* GREEK PERISPOMENI -> '~' */
+ { 0xC2, 0x00 }, /* GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI -> ... */
+ { 0xC4, 0x65 }, /* GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI -> 'e' */
+ { 0xC6, 0x65 }, /* GREEK SMALL LETTER ETA WITH PERISPOMENI -> 'e' */
+ { 0xC7, 0x65 }, /* GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI -> 'e' */
+ { 0xC8, 0x00 }, /* GREEK CAPITAL LETTER EPSILON WITH VARIA -> ... */
+ { 0xCC, 0x45 }, /* GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI -> 'E' */
+ { 0xD0, 0x00 }, /* GREEK SMALL LETTER IOTA WITH VRACHY -> ... */
+ { 0xD3, 0x69 }, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA -> 'i' */
+ { 0xD6, 0x69 }, /* GREEK SMALL LETTER IOTA WITH PERISPOMENI -> 'i' */
+ { 0xD7, 0x69 }, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI -> 'i' */
+ { 0xD8, 0x00 }, /* GREEK CAPITAL LETTER IOTA WITH VRACHY -> ... */
+ { 0xDB, 0x49 }, /* GREEK CAPITAL LETTER IOTA WITH OXIA -> 'I' */
+ { 0xE0, 0x00 }, /* GREEK SMALL LETTER UPSILON WITH VRACHY -> ... */
+ { 0xE3, 0x75 }, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA -> 'u' */
+ { 0xE4, 0x52 }, /* GREEK SMALL LETTER RHO WITH PSILI -> 'R' */
+ { 0xE5, 0x52 }, /* GREEK SMALL LETTER RHO WITH DASIA -> 'R' */
+ { 0xE6, 0x75 }, /* GREEK SMALL LETTER UPSILON WITH PERISPOMENI -> 'u' */
+ { 0xE7, 0x75 }, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI -> 'u' */
+ { 0xE8, 0x00 }, /* GREEK CAPITAL LETTER UPSILON WITH VRACHY -> ... */
+ { 0xEB, 0x55 }, /* GREEK CAPITAL LETTER UPSILON WITH OXIA -> 'U' */
+ { 0xEC, 0x52 }, /* GREEK CAPITAL LETTER RHO WITH DASIA -> 'R' */
+ { 0xEF, 0x60 }, /* GREEK VARIA -> '`' */
+ { 0xF2, 0x00 }, /* GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI -> ... */
+ { 0xF4, 0x6F }, /* GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI -> 'o' */
+ { 0xF6, 0x6F }, /* GREEK SMALL LETTER OMEGA WITH PERISPOMENI -> 'o' */
+ { 0xF7, 0x6F }, /* GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI -> 'o' */
+ { 0xF8, 0x00 }, /* GREEK CAPITAL LETTER OMICRON WITH VARIA -> ... */
+ { 0xFC, 0x4F }, /* GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI -> 'O' */
+ { 0xFD, 0x27 }, /* GREEK OXIA -> ''' */
+ { 0xFE, 0x60 }, /* GREEK DASIA -> '`' */
+ /* Entries for page 0x20 */
+ { 0x00, 0x00 }, /* EN QUAD -> ... */
+ { 0x0B, 0x20 }, /* ZERO WIDTH SPACE -> ' ' */
+ { 0x10, 0x00 }, /* HYPHEN -> ... */
+ { 0x15, 0x2D }, /* HORIZONTAL BAR -> '-' */
+ { 0x17, 0x5F }, /* DOUBLE LOW LINE -> '_' */
+ { 0x18, 0x27 }, /* LEFT SINGLE QUOTATION MARK -> ''' */
+ { 0x19, 0x27 }, /* RIGHT SINGLE QUOTATION MARK -> ''' */
+ { 0x1A, 0x2C }, /* SINGLE LOW-9 QUOTATION MARK -> ',' */
+ { 0x1B, 0x27 }, /* SINGLE HIGH-REVERSED-9 QUOTATION MARK -> ''' */
+ { 0x1C, 0x00 }, /* LEFT DOUBLE QUOTATION MARK -> ... */
+ { 0x1F, 0x22 }, /* DOUBLE HIGH-REVERSED-9 QUOTATION MARK -> '"' */
+ { 0x20, 0x2B }, /* DAGGER -> '+' */
+ { 0x22, 0x2A }, /* BULLET -> '*' */
+ { 0x23, 0x3E }, /* TRIANGULAR BULLET -> '>' */
+ { 0x24, 0x2E }, /* ONE DOT LEADER -> '.' */
+ { 0x26, 0x2E }, /* HORIZONTAL ELLIPSIS -> '.' */
+ { 0x27, 0x2E }, /* HYPHENATION POINT -> '.' */
+ { 0x2F, 0x20 }, /* NARROW NO-BREAK SPACE -> ' ' */
+ { 0x32, 0x27 }, /* PRIME -> ''' */
+ { 0x33, 0x22 }, /* DOUBLE PRIME -> '"' */
+ { 0x35, 0x60 }, /* REVERSED PRIME -> '`' */
+ { 0x38, 0x5E }, /* CARET -> '^' */
+ { 0x39, 0x3C }, /* SINGLE LEFT-POINTING ANGLE QUOTATION MARK -> '<' */
+ { 0x3A, 0x3E }, /* SINGLE RIGHT-POINTING ANGLE QUOTATION MARK -> '>' */
+ { 0x3B, 0x2A }, /* REFERENCE MARK -> '*' */
+ { 0x3C, 0x21 }, /* DOUBLE EXCLAMATION MARK -> '!' */
+ { 0x3D, 0x3F }, /* INTERROBANG -> '?' */
+ { 0x3E, 0x2D }, /* OVERLINE -> '-' */
+ { 0x3F, 0x5F }, /* UNDERTIE -> '_' */
+ { 0x40, 0x2D }, /* CHARACTER TIE -> '-' */
+ { 0x41, 0x5E }, /* CARET INSERTION POINT -> '^' */
+ { 0x42, 0x2A }, /* ASTERISM -> '*' */
+ { 0x43, 0x2D }, /* HYPHEN BULLET -> '-' */
+ { 0x44, 0x2F }, /* FRACTION SLASH -> '/' */
+ { 0x47, 0x3F }, /* DOUBLE QUESTION MARK -> '?' */
+ { 0x48, 0x3F }, /* QUESTION EXCLAMATION MARK -> '?' */
+ { 0x49, 0x21 }, /* EXCLAMATION QUESTION MARK -> '!' */
+ { 0x4A, 0x26 }, /* TIRONIAN SIGN ET -> '&' */
+ { 0x4B, 0x50 }, /* REVERSED PILCROW SIGN -> 'P' */
+ { 0x4C, 0x3C }, /* BLACK LEFTWARDS BULLET -> '<' */
+ { 0x4D, 0x3E }, /* BLACK RIGHTWARDS BULLET -> '>' */
+ { 0x4E, 0x2A }, /* LOW ASTERISK -> '*' */
+ { 0x4F, 0x3B }, /* REVERSED SEMICOLON -> ';' */
+ { 0x51, 0x2A }, /* TWO ASTERISKS ALIGNED VERTICALLY -> '*' */
+ { 0x52, 0x2D }, /* COMMERCIAL MINUS SIGN -> '-' */
+ { 0x53, 0x7E }, /* SWUNG DASH -> '~' */
+ { 0x55, 0x2A }, /* FLOWER PUNCTUATION MARK -> '*' */
+ { 0x5B, 0x3A }, /* FOUR DOT MARK -> ':' */
+ { 0x5F, 0x20 }, /* MEDIUM MATHEMATICAL SPACE -> ' ' */
+ { 0x70, 0x30 }, /* SUPERSCRIPT ZERO -> '0' */
+ { 0x71, 0x69 }, /* SUPERSCRIPT LATIN SMALL LETTER I -> 'i' */
+ { 0x74, 0x34 }, /* SUPERSCRIPT FOUR -> '4' */
+ { 0x75, 0x35 }, /* SUPERSCRIPT FIVE -> '5' */
+ { 0x76, 0x36 }, /* SUPERSCRIPT SIX -> '6' */
+ { 0x77, 0x37 }, /* SUPERSCRIPT SEVEN -> '7' */
+ { 0x78, 0x38 }, /* SUPERSCRIPT EIGHT -> '8' */
+ { 0x79, 0x39 }, /* SUPERSCRIPT NINE -> '9' */
+ { 0x7A, 0x2B }, /* SUPERSCRIPT PLUS SIGN -> '+' */
+ { 0x7B, 0x2D }, /* SUPERSCRIPT MINUS -> '-' */
+ { 0x7C, 0x3D }, /* SUPERSCRIPT EQUALS SIGN -> '=' */
+ { 0x7D, 0x28 }, /* SUPERSCRIPT LEFT PARENTHESIS -> '(' */
+ { 0x7E, 0x29 }, /* SUPERSCRIPT RIGHT PARENTHESIS -> ')' */
+ { 0x7F, 0x6E }, /* SUPERSCRIPT LATIN SMALL LETTER N -> 'n' */
+ { 0x80, 0x30 }, /* SUBSCRIPT ZERO -> '0' */
+ { 0x81, 0x31 }, /* SUBSCRIPT ONE -> '1' */
+ { 0x82, 0x32 }, /* SUBSCRIPT TWO -> '2' */
+ { 0x83, 0x33 }, /* SUBSCRIPT THREE -> '3' */
+ { 0x84, 0x34 }, /* SUBSCRIPT FOUR -> '4' */
+ { 0x85, 0x35 }, /* SUBSCRIPT FIVE -> '5' */
+ { 0x86, 0x36 }, /* SUBSCRIPT SIX -> '6' */
+ { 0x87, 0x37 }, /* SUBSCRIPT SEVEN -> '7' */
+ { 0x88, 0x38 }, /* SUBSCRIPT EIGHT -> '8' */
+ { 0x89, 0x39 }, /* SUBSCRIPT NINE -> '9' */
+ { 0x8A, 0x2B }, /* SUBSCRIPT PLUS SIGN -> '+' */
+ { 0x8B, 0x2D }, /* SUBSCRIPT MINUS -> '-' */
+ { 0x8C, 0x3D }, /* SUBSCRIPT EQUALS SIGN -> '=' */
+ { 0x8D, 0x28 }, /* SUBSCRIPT LEFT PARENTHESIS -> '(' */
+ { 0x8E, 0x29 }, /* SUBSCRIPT RIGHT PARENTHESIS -> ')' */
+ { 0x90, 0x61 }, /* LATIN SUBSCRIPT SMALL LETTER A -> 'a' */
+ { 0x91, 0x65 }, /* LATIN SUBSCRIPT SMALL LETTER E -> 'e' */
+ { 0x92, 0x6F }, /* LATIN SUBSCRIPT SMALL LETTER O -> 'o' */
+ { 0x93, 0x78 }, /* LATIN SUBSCRIPT SMALL LETTER X -> 'x' */
+ { 0x95, 0x68 }, /* LATIN SUBSCRIPT SMALL LETTER H -> 'h' */
+ { 0x96, 0x6B }, /* LATIN SUBSCRIPT SMALL LETTER K -> 'k' */
+ { 0x97, 0x6C }, /* LATIN SUBSCRIPT SMALL LETTER L -> 'l' */
+ { 0x98, 0x6D }, /* LATIN SUBSCRIPT SMALL LETTER M -> 'm' */
+ { 0x99, 0x6E }, /* LATIN SUBSCRIPT SMALL LETTER N -> 'n' */
+ { 0x9A, 0x70 }, /* LATIN SUBSCRIPT SMALL LETTER P -> 'p' */
+ { 0x9B, 0x73 }, /* LATIN SUBSCRIPT SMALL LETTER S -> 's' */
+ { 0x9C, 0x74 }, /* LATIN SUBSCRIPT SMALL LETTER T -> 't' */
+ { 0xA4, 0x4C }, /* LIRA SIGN -> 'L' */
+ { 0xA6, 0x4E }, /* NAIRA SIGN -> 'N' */
+ { 0xA9, 0x57 }, /* WON SIGN -> 'W' */
+ { 0xAB, 0x44 }, /* DONG SIGN -> 'D' */
+ { 0xAC, 0x45 }, /* EURO SIGN -> 'E' */
+ { 0xAD, 0x4B }, /* KIP SIGN -> 'K' */
+ { 0xAE, 0x54 }, /* TUGRIK SIGN -> 'T' */
+ { 0xB1, 0x50 }, /* PESO SIGN -> 'P' */
+ { 0xB2, 0x47 }, /* GUARANI SIGN -> 'G' */
+ { 0xB3, 0x41 }, /* AUSTRAL SIGN -> 'A' */
+ { 0xB6, 0x4C }, /* LIVRE TOURNOIS SIGN -> 'L' */
+ { 0xB8, 0x54 }, /* TENGE SIGN -> 'T' */
+ { 0xBA, 0x4C }, /* TURKISH LIRA SIGN -> 'L' */
+ { 0xBB, 0x4D }, /* NORDIC MARK SIGN -> 'M' */
+ { 0xBC, 0x6D }, /* MANAT SIGN -> 'm' */
+ { 0xBD, 0x52 }, /* RUBLE SIGN -> 'R' */
+ { 0xBE, 0x6C }, /* LARI SIGN -> 'l' */
+ /* Entries for page 0x21 */
+ { 0x02, 0x43 }, /* DOUBLE-STRUCK CAPITAL C -> 'C' */
+ { 0x03, 0x43 }, /* DEGREE CELSIUS -> 'C' */
+ { 0x09, 0x46 }, /* DEGREE FAHRENHEIT -> 'F' */
+ { 0x0A, 0x67 }, /* SCRIPT SMALL G -> 'g' */
+ { 0x0B, 0x00 }, /* SCRIPT CAPITAL H -> ... */
+ { 0x0D, 0x48 }, /* DOUBLE-STRUCK CAPITAL H -> 'H' */
+ { 0x0E, 0x68 }, /* PLANCK CONSTANT -> 'h' */
+ { 0x10, 0x49 }, /* SCRIPT CAPITAL I -> 'I' */
+ { 0x11, 0x49 }, /* BLACK-LETTER CAPITAL I -> 'I' */
+ { 0x12, 0x4C }, /* SCRIPT CAPITAL L -> 'L' */
+ { 0x13, 0x6C }, /* SCRIPT SMALL L -> 'l' */
+ { 0x15, 0x4E }, /* DOUBLE-STRUCK CAPITAL N -> 'N' */
+ { 0x19, 0x50 }, /* DOUBLE-STRUCK CAPITAL P -> 'P' */
+ { 0x1A, 0x51 }, /* DOUBLE-STRUCK CAPITAL Q -> 'Q' */
+ { 0x1B, 0x00 }, /* SCRIPT CAPITAL R -> ... */
+ { 0x1D, 0x52 }, /* DOUBLE-STRUCK CAPITAL R -> 'R' */
+ { 0x22, 0x54 }, /* TRADE MARK SIGN -> 'T' */
+ { 0x24, 0x5A }, /* DOUBLE-STRUCK CAPITAL Z -> 'Z' */
+ { 0x28, 0x5A }, /* BLACK-LETTER CAPITAL Z -> 'Z' */
+ { 0x2A, 0x4B }, /* KELVIN SIGN -> 'K' */
+ { 0x2B, 0x41 }, /* ANGSTROM SIGN -> 'A' */
+ { 0x2C, 0x42 }, /* SCRIPT CAPITAL B -> 'B' */
+ { 0x2D, 0x43 }, /* BLACK-LETTER CAPITAL C -> 'C' */
+ { 0x2E, 0x65 }, /* ESTIMATED SYMBOL -> 'e' */
+ { 0x2F, 0x65 }, /* SCRIPT SMALL E -> 'e' */
+ { 0x30, 0x45 }, /* SCRIPT CAPITAL E -> 'E' */
+ { 0x31, 0x46 }, /* SCRIPT CAPITAL F -> 'F' */
+ { 0x32, 0x46 }, /* TURNED CAPITAL F -> 'F' */
+ { 0x33, 0x4D }, /* SCRIPT CAPITAL M -> 'M' */
+ { 0x34, 0x6F }, /* SCRIPT SMALL O -> 'o' */
+ { 0x39, 0x69 }, /* INFORMATION SOURCE -> 'i' */
+ { 0x45, 0x44 }, /* DOUBLE-STRUCK ITALIC CAPITAL D -> 'D' */
+ { 0x46, 0x64 }, /* DOUBLE-STRUCK ITALIC SMALL D -> 'd' */
+ { 0x47, 0x65 }, /* DOUBLE-STRUCK ITALIC SMALL E -> 'e' */
+ { 0x48, 0x69 }, /* DOUBLE-STRUCK ITALIC SMALL I -> 'i' */
+ { 0x49, 0x6A }, /* DOUBLE-STRUCK ITALIC SMALL J -> 'j' */
+ { 0x4E, 0x46 }, /* TURNED SMALL F -> 'F' */
+ { 0x60, 0x49 }, /* ROMAN NUMERAL ONE -> 'I' */
+ { 0x64, 0x56 }, /* ROMAN NUMERAL FIVE -> 'V' */
+ { 0x69, 0x58 }, /* ROMAN NUMERAL TEN -> 'X' */
+ { 0x6C, 0x4C }, /* ROMAN NUMERAL FIFTY -> 'L' */
+ { 0x6D, 0x43 }, /* ROMAN NUMERAL ONE HUNDRED -> 'C' */
+ { 0x6E, 0x44 }, /* ROMAN NUMERAL FIVE HUNDRED -> 'D' */
+ { 0x6F, 0x4D }, /* ROMAN NUMERAL ONE THOUSAND -> 'M' */
+ { 0x70, 0x69 }, /* SMALL ROMAN NUMERAL ONE -> 'i' */
+ { 0x74, 0x76 }, /* SMALL ROMAN NUMERAL FIVE -> 'v' */
+ { 0x79, 0x78 }, /* SMALL ROMAN NUMERAL TEN -> 'x' */
+ { 0x7C, 0x6C }, /* SMALL ROMAN NUMERAL FIFTY -> 'l' */
+ { 0x7D, 0x63 }, /* SMALL ROMAN NUMERAL ONE HUNDRED -> 'c' */
+ { 0x7E, 0x64 }, /* SMALL ROMAN NUMERAL FIVE HUNDRED -> 'd' */
+ { 0x7F, 0x6D }, /* SMALL ROMAN NUMERAL ONE THOUSAND -> 'm' */
+ { 0x83, 0x29 }, /* ROMAN NUMERAL REVERSED ONE HUNDRED -> ')' */
+ { 0x90, 0x3C }, /* LEFTWARDS ARROW -> '<' */
+ { 0x91, 0x5E }, /* UPWARDS ARROW -> '^' */
+ { 0x92, 0x3E }, /* RIGHTWARDS ARROW -> '>' */
+ { 0x93, 0x76 }, /* DOWNWARDS ARROW -> 'v' */
+ { 0x94, 0x2D }, /* LEFT RIGHT ARROW -> '-' */
+ { 0x95, 0x7C }, /* UP DOWN ARROW -> '|' */
+ { 0x96, 0x5C }, /* NORTH WEST ARROW -> '\' */
+ { 0x97, 0x2F }, /* NORTH EAST ARROW -> '/' */
+ { 0x98, 0x5C }, /* SOUTH EAST ARROW -> '\' */
+ { 0x99, 0x2F }, /* SOUTH WEST ARROW -> '/' */
+ { 0x9A, 0x21 }, /* LEFTWARDS ARROW WITH STROKE -> '!' */
+ { 0x9B, 0x21 }, /* RIGHTWARDS ARROW WITH STROKE -> '!' */
+ { 0x9C, 0x7E }, /* LEFTWARDS WAVE ARROW -> '~' */
+ { 0x9D, 0x7E }, /* RIGHTWARDS WAVE ARROW -> '~' */
+ { 0x9E, 0x2D }, /* LEFTWARDS TWO HEADED ARROW -> '-' */
+ { 0x9F, 0x7C }, /* UPWARDS TWO HEADED ARROW -> '|' */
+ { 0xA0, 0x2D }, /* RIGHTWARDS TWO HEADED ARROW -> '-' */
+ { 0xA1, 0x7C }, /* DOWNWARDS TWO HEADED ARROW -> '|' */
+ { 0xA2, 0x00 }, /* LEFTWARDS ARROW WITH TAIL -> ... */
+ { 0xA4, 0x2D }, /* LEFTWARDS ARROW FROM BAR -> '-' */
+ { 0xA5, 0x7C }, /* UPWARDS ARROW FROM BAR -> '|' */
+ { 0xA6, 0x2D }, /* RIGHTWARDS ARROW FROM BAR -> '-' */
+ { 0xA7, 0x7C }, /* DOWNWARDS ARROW FROM BAR -> '|' */
+ { 0xA8, 0x7C }, /* UP DOWN ARROW WITH BASE -> '|' */
+ { 0xA9, 0x00 }, /* LEFTWARDS ARROW WITH HOOK -> ... */
+ { 0xAD, 0x2D }, /* LEFT RIGHT WAVE ARROW -> '-' */
+ { 0xAE, 0x21 }, /* LEFT RIGHT ARROW WITH STROKE -> '!' */
+ { 0xAF, 0x00 }, /* DOWNWARDS ZIGZAG ARROW -> ... */
+ { 0xB5, 0x7C }, /* DOWNWARDS ARROW WITH CORNER LEFTWARDS -> '|' */
+ { 0xB6, 0x5E }, /* ANTICLOCKWISE TOP SEMICIRCLE ARROW -> '^' */
+ { 0xB7, 0x56 }, /* CLOCKWISE TOP SEMICIRCLE ARROW -> 'V' */
+ { 0xB8, 0x5C }, /* NORTH WEST ARROW TO LONG BAR -> '\' */
+ { 0xB9, 0x3D }, /* LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR -> '=' */
+ { 0xBA, 0x56 }, /* ANTICLOCKWISE OPEN CIRCLE ARROW -> 'V' */
+ { 0xBB, 0x5E }, /* CLOCKWISE OPEN CIRCLE ARROW -> '^' */
+ { 0xBC, 0x2D }, /* LEFTWARDS HARPOON WITH BARB UPWARDS -> '-' */
+ { 0xBD, 0x2D }, /* LEFTWARDS HARPOON WITH BARB DOWNWARDS -> '-' */
+ { 0xBE, 0x7C }, /* UPWARDS HARPOON WITH BARB RIGHTWARDS -> '|' */
+ { 0xBF, 0x7C }, /* UPWARDS HARPOON WITH BARB LEFTWARDS -> '|' */
+ { 0xC0, 0x2D }, /* RIGHTWARDS HARPOON WITH BARB UPWARDS -> '-' */
+ { 0xC1, 0x2D }, /* RIGHTWARDS HARPOON WITH BARB DOWNWARDS -> '-' */
+ { 0xC2, 0x7C }, /* DOWNWARDS HARPOON WITH BARB RIGHTWARDS -> '|' */
+ { 0xC3, 0x7C }, /* DOWNWARDS HARPOON WITH BARB LEFTWARDS -> '|' */
+ { 0xC4, 0x3D }, /* RIGHTWARDS ARROW OVER LEFTWARDS ARROW -> '=' */
+ { 0xC5, 0x7C }, /* UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW -> '|' */
+ { 0xC6, 0x3D }, /* LEFTWARDS ARROW OVER RIGHTWARDS ARROW -> '=' */
+ { 0xC7, 0x3D }, /* LEFTWARDS PAIRED ARROWS -> '=' */
+ { 0xC8, 0x7C }, /* UPWARDS PAIRED ARROWS -> '|' */
+ { 0xC9, 0x3D }, /* RIGHTWARDS PAIRED ARROWS -> '=' */
+ { 0xCA, 0x7C }, /* DOWNWARDS PAIRED ARROWS -> '|' */
+ { 0xCB, 0x3D }, /* LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON -> '=' */
+ { 0xCC, 0x3D }, /* RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON -> '=' */
+ { 0xCD, 0x00 }, /* LEFTWARDS DOUBLE ARROW WITH STROKE -> ... */
+ { 0xCF, 0x21 }, /* RIGHTWARDS DOUBLE ARROW WITH STROKE -> '!' */
+ { 0xD0, 0x3C }, /* LEFTWARDS DOUBLE ARROW -> '<' */
+ { 0xD1, 0x5E }, /* UPWARDS DOUBLE ARROW -> '^' */
+ { 0xD2, 0x3E }, /* RIGHTWARDS DOUBLE ARROW -> '>' */
+ { 0xD3, 0x76 }, /* DOWNWARDS DOUBLE ARROW -> 'v' */
+ { 0xD4, 0x3D }, /* LEFT RIGHT DOUBLE ARROW -> '=' */
+ { 0xD5, 0x7C }, /* UP DOWN DOUBLE ARROW -> '|' */
+ { 0xD6, 0x5C }, /* NORTH WEST DOUBLE ARROW -> '\' */
+ { 0xD7, 0x2F }, /* NORTH EAST DOUBLE ARROW -> '/' */
+ { 0xD8, 0x5C }, /* SOUTH EAST DOUBLE ARROW -> '\' */
+ { 0xD9, 0x2F }, /* SOUTH WEST DOUBLE ARROW -> '/' */
+ { 0xDA, 0x3D }, /* LEFTWARDS TRIPLE ARROW -> '=' */
+ { 0xDB, 0x3D }, /* RIGHTWARDS TRIPLE ARROW -> '=' */
+ { 0xDC, 0x7E }, /* LEFTWARDS SQUIGGLE ARROW -> '~' */
+ { 0xDD, 0x7E }, /* RIGHTWARDS SQUIGGLE ARROW -> '~' */
+ { 0xDE, 0x7C }, /* UPWARDS ARROW WITH DOUBLE STROKE -> '|' */
+ { 0xDF, 0x7C }, /* DOWNWARDS ARROW WITH DOUBLE STROKE -> '|' */
+ { 0xE0, 0x2D }, /* LEFTWARDS DASHED ARROW -> '-' */
+ { 0xE1, 0x7C }, /* UPWARDS DASHED ARROW -> '|' */
+ { 0xE2, 0x2D }, /* RIGHTWARDS DASHED ARROW -> '-' */
+ { 0xE3, 0x7C }, /* DOWNWARDS DASHED ARROW -> '|' */
+ { 0xE4, 0x00 }, /* LEFTWARDS ARROW TO BAR -> ... */
+ { 0xE6, 0x2D }, /* LEFTWARDS WHITE ARROW -> '-' */
+ { 0xE7, 0x7C }, /* UPWARDS WHITE ARROW -> '|' */
+ { 0xE8, 0x2D }, /* RIGHTWARDS WHITE ARROW -> '-' */
+ { 0xE9, 0x00 }, /* DOWNWARDS WHITE ARROW -> ... */
+ { 0xEF, 0x7C }, /* UPWARDS WHITE DOUBLE ARROW ON PEDESTAL -> '|' */
+ { 0xF0, 0x2D }, /* RIGHTWARDS WHITE ARROW FROM WALL -> '-' */
+ { 0xF1, 0x5C }, /* NORTH WEST ARROW TO CORNER -> '\' */
+ { 0xF2, 0x5C }, /* SOUTH EAST ARROW TO CORNER -> '\' */
+ { 0xF3, 0x7C }, /* UP DOWN WHITE ARROW -> '|' */
+ /* Entries for page 0x22 */
+ { 0x04, 0x21 }, /* THERE DOES NOT EXIST -> '!' */
+ { 0x09, 0x21 }, /* NOT AN ELEMENT OF -> '!' */
+ { 0x0C, 0x21 }, /* DOES NOT CONTAIN AS MEMBER -> '!' */
+ { 0x12, 0x2D }, /* MINUS SIGN -> '-' */
+ { 0x15, 0x2F }, /* DIVISION SLASH -> '/' */
+ { 0x16, 0x5C }, /* SET MINUS -> '\' */
+ { 0x17, 0x2A }, /* ASTERISK OPERATOR -> '*' */
+ { 0x18, 0x6F }, /* RING OPERATOR -> 'o' */
+ { 0x19, 0x2E }, /* BULLET OPERATOR -> '.' */
+ { 0x23, 0x7C }, /* DIVIDES -> '|' */
+ { 0x24, 0x21 }, /* DOES NOT DIVIDE -> '!' */
+ { 0x26, 0x21 }, /* NOT PARALLEL TO -> '!' */
+ { 0x36, 0x3A }, /* RATIO -> ':' */
+ { 0x3C, 0x7E }, /* TILDE OPERATOR -> '~' */
+ { 0x41, 0x23 }, /* NOT TILDE -> '#' */
+ { 0x44, 0x23 }, /* NOT ASYMPTOTICALLY EQUAL TO -> '#' */
+ { 0x49, 0x23 }, /* NOT ALMOST EQUAL TO -> '#' */
+ { 0x60, 0x23 }, /* NOT EQUAL TO -> '#' */
+ { 0x62, 0x23 }, /* NOT IDENTICAL TO -> '#' */
+ { 0x64, 0x3C }, /* LESS-THAN OR EQUAL TO -> '<' */
+ { 0x65, 0x3E }, /* GREATER-THAN OR EQUAL TO -> '>' */
+ { 0x68, 0x23 }, /* LESS-THAN BUT NOT EQUAL TO -> '#' */
+ { 0x69, 0x23 }, /* GREATER-THAN BUT NOT EQUAL TO -> '#' */
+ { 0x6D, 0x23 }, /* NOT EQUIVALENT TO -> '#' */
+ { 0x6E, 0x21 }, /* NOT LESS-THAN -> '!' */
+ { 0x6F, 0x21 }, /* NOT GREATER-THAN -> '!' */
+ { 0x80, 0x21 }, /* DOES NOT PRECEDE -> '!' */
+ { 0x81, 0x21 }, /* DOES NOT SUCCEED -> '!' */
+ { 0x84, 0x21 }, /* NOT A SUBSET OF -> '!' */
+ { 0x85, 0x21 }, /* NOT A SUPERSET OF -> '!' */
+ { 0x8A, 0x23 }, /* SUBSET OF WITH NOT EQUAL TO -> '#' */
+ { 0x8B, 0x23 }, /* SUPERSET OF WITH NOT EQUAL TO -> '#' */
+ { 0x9B, 0x2A }, /* CIRCLED ASTERISK OPERATOR -> '*' */
+ { 0xC6, 0x2A }, /* STAR OPERATOR -> '*' */
+ /* Entries for page 0x23 */
+ { 0x03, 0x5E }, /* UP ARROWHEAD -> '^' */
+ { 0x29, 0x3C }, /* LEFT-POINTING ANGLE BRACKET -> '<' */
+ { 0x5F, 0x2A }, /* APL FUNCTIONAL SYMBOL CIRCLE STAR -> '*' */
+ { 0x63, 0x2A }, /* APL FUNCTIONAL SYMBOL STAR DIAERESIS -> '*' */
+ /* Entries for page 0x24 */
+ { 0x60, 0x31 }, /* CIRCLED DIGIT ONE -> '1' */
+ { 0x61, 0x32 }, /* CIRCLED DIGIT TWO -> '2' */
+ { 0x62, 0x33 }, /* CIRCLED DIGIT THREE -> '3' */
+ { 0x63, 0x34 }, /* CIRCLED DIGIT FOUR -> '4' */
+ { 0x64, 0x35 }, /* CIRCLED DIGIT FIVE -> '5' */
+ { 0x65, 0x36 }, /* CIRCLED DIGIT SIX -> '6' */
+ { 0x66, 0x37 }, /* CIRCLED DIGIT SEVEN -> '7' */
+ { 0x67, 0x38 }, /* CIRCLED DIGIT EIGHT -> '8' */
+ { 0x68, 0x39 }, /* CIRCLED DIGIT NINE -> '9' */
+ { 0xB6, 0x41 }, /* CIRCLED LATIN CAPITAL LETTER A -> 'A' */
+ { 0xB7, 0x42 }, /* CIRCLED LATIN CAPITAL LETTER B -> 'B' */
+ { 0xB8, 0x43 }, /* CIRCLED LATIN CAPITAL LETTER C -> 'C' */
+ { 0xB9, 0x44 }, /* CIRCLED LATIN CAPITAL LETTER D -> 'D' */
+ { 0xBA, 0x45 }, /* CIRCLED LATIN CAPITAL LETTER E -> 'E' */
+ { 0xBB, 0x46 }, /* CIRCLED LATIN CAPITAL LETTER F -> 'F' */
+ { 0xBC, 0x47 }, /* CIRCLED LATIN CAPITAL LETTER G -> 'G' */
+ { 0xBD, 0x48 }, /* CIRCLED LATIN CAPITAL LETTER H -> 'H' */
+ { 0xBE, 0x49 }, /* CIRCLED LATIN CAPITAL LETTER I -> 'I' */
+ { 0xBF, 0x4A }, /* CIRCLED LATIN CAPITAL LETTER J -> 'J' */
+ { 0xC0, 0x4B }, /* CIRCLED LATIN CAPITAL LETTER K -> 'K' */
+ { 0xC1, 0x4C }, /* CIRCLED LATIN CAPITAL LETTER L -> 'L' */
+ { 0xC2, 0x4D }, /* CIRCLED LATIN CAPITAL LETTER M -> 'M' */
+ { 0xC3, 0x4E }, /* CIRCLED LATIN CAPITAL LETTER N -> 'N' */
+ { 0xC4, 0x4F }, /* CIRCLED LATIN CAPITAL LETTER O -> 'O' */
+ { 0xC5, 0x50 }, /* CIRCLED LATIN CAPITAL LETTER P -> 'P' */
+ { 0xC6, 0x51 }, /* CIRCLED LATIN CAPITAL LETTER Q -> 'Q' */
+ { 0xC7, 0x52 }, /* CIRCLED LATIN CAPITAL LETTER R -> 'R' */
+ { 0xC8, 0x53 }, /* CIRCLED LATIN CAPITAL LETTER S -> 'S' */
+ { 0xC9, 0x54 }, /* CIRCLED LATIN CAPITAL LETTER T -> 'T' */
+ { 0xCA, 0x55 }, /* CIRCLED LATIN CAPITAL LETTER U -> 'U' */
+ { 0xCB, 0x56 }, /* CIRCLED LATIN CAPITAL LETTER V -> 'V' */
+ { 0xCC, 0x57 }, /* CIRCLED LATIN CAPITAL LETTER W -> 'W' */
+ { 0xCD, 0x58 }, /* CIRCLED LATIN CAPITAL LETTER X -> 'X' */
+ { 0xCE, 0x59 }, /* CIRCLED LATIN CAPITAL LETTER Y -> 'Y' */
+ { 0xCF, 0x5A }, /* CIRCLED LATIN CAPITAL LETTER Z -> 'Z' */
+ { 0xD0, 0x61 }, /* CIRCLED LATIN SMALL LETTER A -> 'a' */
+ { 0xD1, 0x62 }, /* CIRCLED LATIN SMALL LETTER B -> 'b' */
+ { 0xD2, 0x63 }, /* CIRCLED LATIN SMALL LETTER C -> 'c' */
+ { 0xD3, 0x64 }, /* CIRCLED LATIN SMALL LETTER D -> 'd' */
+ { 0xD4, 0x65 }, /* CIRCLED LATIN SMALL LETTER E -> 'e' */
+ { 0xD5, 0x66 }, /* CIRCLED LATIN SMALL LETTER F -> 'f' */
+ { 0xD6, 0x67 }, /* CIRCLED LATIN SMALL LETTER G -> 'g' */
+ { 0xD7, 0x68 }, /* CIRCLED LATIN SMALL LETTER H -> 'h' */
+ { 0xD8, 0x69 }, /* CIRCLED LATIN SMALL LETTER I -> 'i' */
+ { 0xD9, 0x6A }, /* CIRCLED LATIN SMALL LETTER J -> 'j' */
+ { 0xDA, 0x6B }, /* CIRCLED LATIN SMALL LETTER K -> 'k' */
+ { 0xDB, 0x6C }, /* CIRCLED LATIN SMALL LETTER L -> 'l' */
+ { 0xDC, 0x6D }, /* CIRCLED LATIN SMALL LETTER M -> 'm' */
+ { 0xDD, 0x6E }, /* CIRCLED LATIN SMALL LETTER N -> 'n' */
+ { 0xDE, 0x6F }, /* CIRCLED LATIN SMALL LETTER O -> 'o' */
+ { 0xDF, 0x70 }, /* CIRCLED LATIN SMALL LETTER P -> 'p' */
+ { 0xE0, 0x71 }, /* CIRCLED LATIN SMALL LETTER Q -> 'q' */
+ { 0xE1, 0x72 }, /* CIRCLED LATIN SMALL LETTER R -> 'r' */
+ { 0xE2, 0x73 }, /* CIRCLED LATIN SMALL LETTER S -> 's' */
+ { 0xE3, 0x74 }, /* CIRCLED LATIN SMALL LETTER T -> 't' */
+ { 0xE4, 0x75 }, /* CIRCLED LATIN SMALL LETTER U -> 'u' */
+ { 0xE5, 0x76 }, /* CIRCLED LATIN SMALL LETTER V -> 'v' */
+ { 0xE6, 0x77 }, /* CIRCLED LATIN SMALL LETTER W -> 'w' */
+ { 0xE7, 0x78 }, /* CIRCLED LATIN SMALL LETTER X -> 'x' */
+ { 0xE8, 0x79 }, /* CIRCLED LATIN SMALL LETTER Y -> 'y' */
+ { 0xE9, 0x7A }, /* CIRCLED LATIN SMALL LETTER Z -> 'z' */
+ { 0xEA, 0x30 }, /* CIRCLED DIGIT ZERO -> '0' */
+ { 0xF5, 0x31 }, /* DOUBLE CIRCLED DIGIT ONE -> '1' */
+ { 0xF6, 0x32 }, /* DOUBLE CIRCLED DIGIT TWO -> '2' */
+ { 0xF7, 0x33 }, /* DOUBLE CIRCLED DIGIT THREE -> '3' */
+ { 0xF8, 0x34 }, /* DOUBLE CIRCLED DIGIT FOUR -> '4' */
+ { 0xF9, 0x35 }, /* DOUBLE CIRCLED DIGIT FIVE -> '5' */
+ { 0xFA, 0x36 }, /* DOUBLE CIRCLED DIGIT SIX -> '6' */
+ { 0xFB, 0x37 }, /* DOUBLE CIRCLED DIGIT SEVEN -> '7' */
+ { 0xFC, 0x38 }, /* DOUBLE CIRCLED DIGIT EIGHT -> '8' */
+ { 0xFD, 0x39 }, /* DOUBLE CIRCLED DIGIT NINE -> '9' */
+ { 0xFF, 0x30 }, /* NEGATIVE CIRCLED DIGIT ZERO -> '0' */
+ /* Entries for page 0x25 */
+ { 0x00, 0x2D }, /* BOX DRAWINGS LIGHT HORIZONTAL -> '-' */
+ { 0x01, 0x2D }, /* BOX DRAWINGS HEAVY HORIZONTAL -> '-' */
+ { 0x02, 0x7C }, /* BOX DRAWINGS LIGHT VERTICAL -> '|' */
+ { 0x03, 0x7C }, /* BOX DRAWINGS HEAVY VERTICAL -> '|' */
+ { 0x04, 0x2D }, /* BOX DRAWINGS LIGHT TRIPLE DASH HORIZONTAL -> '-' */
+ { 0x05, 0x2D }, /* BOX DRAWINGS HEAVY TRIPLE DASH HORIZONTAL -> '-' */
+ { 0x06, 0x7C }, /* BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL -> '|' */
+ { 0x07, 0x7C }, /* BOX DRAWINGS HEAVY TRIPLE DASH VERTICAL -> '|' */
+ { 0x08, 0x2D }, /* BOX DRAWINGS LIGHT QUADRUPLE DASH HORIZONTAL -> '-' */
+ { 0x09, 0x2D }, /* BOX DRAWINGS HEAVY QUADRUPLE DASH HORIZONTAL -> '-' */
+ { 0x0A, 0x7C }, /* BOX DRAWINGS LIGHT QUADRUPLE DASH VERTICAL -> '|' */
+ { 0x0B, 0x7C }, /* BOX DRAWINGS HEAVY QUADRUPLE DASH VERTICAL -> '|' */
+ { 0x0C, 0x00 }, /* BOX DRAWINGS LIGHT DOWN AND RIGHT -> ... */
+ { 0x4B, 0x2B }, /* BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL -> '+' */
+ { 0x4C, 0x2D }, /* BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL -> '-' */
+ { 0x4D, 0x2D }, /* BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL -> '-' */
+ { 0x4E, 0x7C }, /* BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL -> '|' */
+ { 0x4F, 0x7C }, /* BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL -> '|' */
+ { 0x50, 0x2D }, /* BOX DRAWINGS DOUBLE HORIZONTAL -> '-' */
+ { 0x51, 0x7C }, /* BOX DRAWINGS DOUBLE VERTICAL -> '|' */
+ { 0x52, 0x00 }, /* BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE -> ... */
+ { 0x70, 0x2B }, /* BOX DRAWINGS LIGHT ARC UP AND RIGHT -> '+' */
+ { 0x71, 0x2F }, /* BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT -> '/' */
+ { 0x72, 0x5C }, /* BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT -> '\' */
+ { 0x73, 0x58 }, /* BOX DRAWINGS LIGHT DIAGONAL CROSS -> 'X' */
+ { 0x74, 0x2D }, /* BOX DRAWINGS LIGHT LEFT -> '-' */
+ { 0x75, 0x7C }, /* BOX DRAWINGS LIGHT UP -> '|' */
+ { 0x76, 0x2D }, /* BOX DRAWINGS LIGHT RIGHT -> '-' */
+ { 0x77, 0x7C }, /* BOX DRAWINGS LIGHT DOWN -> '|' */
+ { 0x78, 0x2D }, /* BOX DRAWINGS HEAVY LEFT -> '-' */
+ { 0x79, 0x7C }, /* BOX DRAWINGS HEAVY UP -> '|' */
+ { 0x7A, 0x2D }, /* BOX DRAWINGS HEAVY RIGHT -> '-' */
+ { 0x7B, 0x7C }, /* BOX DRAWINGS HEAVY DOWN -> '|' */
+ { 0x7C, 0x2D }, /* BOX DRAWINGS LIGHT LEFT AND HEAVY RIGHT -> '-' */
+ { 0x7D, 0x7C }, /* BOX DRAWINGS LIGHT UP AND HEAVY DOWN -> '|' */
+ { 0x7E, 0x2D }, /* BOX DRAWINGS HEAVY LEFT AND LIGHT RIGHT -> '-' */
+ { 0x7F, 0x7C }, /* BOX DRAWINGS HEAVY UP AND LIGHT DOWN -> '|' */
+ { 0x80, 0x00 }, /* UPPER HALF BLOCK -> ... */
+ { 0x93, 0x23 }, /* DARK SHADE -> '#' */
+ { 0x94, 0x2D }, /* UPPER ONE EIGHTH BLOCK -> '-' */
+ { 0x95, 0x7C }, /* RIGHT ONE EIGHTH BLOCK -> '|' */
+ { 0x96, 0x00 }, /* QUADRANT LOWER LEFT -> ... */
+ { 0xB1, 0x23 }, /* WHITE PARALLELOGRAM -> '#' */
+ { 0xB2, 0x00 }, /* BLACK UP-POINTING TRIANGLE -> ... */
+ { 0xB5, 0x5E }, /* WHITE UP-POINTING SMALL TRIANGLE -> '^' */
+ { 0xB6, 0x00 }, /* BLACK RIGHT-POINTING TRIANGLE -> ... */
+ { 0xBB, 0x3E }, /* WHITE RIGHT-POINTING POINTER -> '>' */
+ { 0xBC, 0x00 }, /* BLACK DOWN-POINTING TRIANGLE -> ... */
+ { 0xBF, 0x56 }, /* WHITE DOWN-POINTING SMALL TRIANGLE -> 'V' */
+ { 0xC0, 0x00 }, /* BLACK LEFT-POINTING TRIANGLE -> ... */
+ { 0xC5, 0x3C }, /* WHITE LEFT-POINTING POINTER -> '<' */
+ { 0xC6, 0x00 }, /* BLACK DIAMOND -> ... */
+ { 0xE6, 0x2A }, /* WHITE BULLET -> '*' */
+ { 0xE7, 0x00 }, /* SQUARE WITH LEFT HALF BLACK -> ... */
+ { 0xEB, 0x23 }, /* WHITE SQUARE WITH VERTICAL BISECTING LINE -> '#' */
+ { 0xEC, 0x00 }, /* WHITE UP-POINTING TRIANGLE WITH DOT -> ... */
+ { 0xEE, 0x5E }, /* UP-POINTING TRIANGLE WITH RIGHT HALF BLACK -> '^' */
+ { 0xEF, 0x4F }, /* LARGE CIRCLE -> 'O' */
+ { 0xF0, 0x00 }, /* WHITE SQUARE WITH UPPER LEFT QUADRANT -> ... */
+ { 0xF7, 0x23 }, /* WHITE CIRCLE WITH UPPER RIGHT QUADRANT -> '#' */
+ /* Entries for page 0x26 */
+ { 0x05, 0x2A }, /* BLACK STAR -> '*' */
+ { 0x06, 0x2A }, /* WHITE STAR -> '*' */
+ { 0x2A, 0x2A }, /* STAR AND CRESCENT -> '*' */
+ { 0x6F, 0x23 }, /* MUSIC SHARP SIGN -> '#' */
+ { 0x98, 0x2A }, /* FLOWER -> '*' */
+ { 0x9D, 0x2A }, /* OUTLINED WHITE STAR -> '*' */
+ /* Entries for page 0x27 */
+ { 0x13, 0x76 }, /* CHECK MARK -> 'v' */
+ { 0x14, 0x56 }, /* HEAVY CHECK MARK -> 'V' */
+ { 0x15, 0x78 }, /* MULTIPLICATION X -> 'x' */
+ { 0x16, 0x58 }, /* HEAVY MULTIPLICATION X -> 'X' */
+ { 0x17, 0x78 }, /* BALLOT X -> 'x' */
+ { 0x18, 0x58 }, /* HEAVY BALLOT X -> 'X' */
+ { 0x21, 0x00 }, /* STAR OF DAVID -> ... */
+ { 0x46, 0x2A }, /* HEAVY CHEVRON SNOWFLAKE -> '*' */
+ { 0x49, 0x00 }, /* BALLOON-SPOKED ASTERISK -> ... */
+ { 0x4B, 0x2A }, /* HEAVY EIGHT TEARDROP-SPOKED PROPELLER ASTERISK -> '*' */
+ { 0x58, 0x7C }, /* LIGHT VERTICAL BAR -> '|' */
+ { 0x5C, 0x27 }, /* HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT -> ''' */
+ { 0x5D, 0x22 }, /* HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT -> '"' */
+ { 0x5E, 0x22 }, /* HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT -> '"' */
+ { 0x5F, 0x2C }, /* HEAVY LOW SINGLE COMMA QUOTATION MARK ORNAMENT -> ',' */
+ { 0x62, 0x21 }, /* HEAVY EXCLAMATION MARK ORNAMENT -> '!' */
+ { 0xE6, 0x5B }, /* MATHEMATICAL LEFT WHITE SQUARE BRACKET -> '[' */
+ { 0xE8, 0x3C }, /* MATHEMATICAL LEFT ANGLE BRACKET -> '<' */
+ /* Entries for page 0x28 */
+ { 0x00, 0x20 }, /* BRAILLE PATTERN BLANK -> ' ' */
+ { 0x01, 0x61 }, /* BRAILLE PATTERN DOTS-1 -> 'a' */
+ { 0x02, 0x31 }, /* BRAILLE PATTERN DOTS-2 -> '1' */
+ { 0x03, 0x62 }, /* BRAILLE PATTERN DOTS-12 -> 'b' */
+ { 0x04, 0x27 }, /* BRAILLE PATTERN DOTS-3 -> ''' */
+ { 0x05, 0x6B }, /* BRAILLE PATTERN DOTS-13 -> 'k' */
+ { 0x06, 0x32 }, /* BRAILLE PATTERN DOTS-23 -> '2' */
+ { 0x07, 0x6C }, /* BRAILLE PATTERN DOTS-123 -> 'l' */
+ { 0x08, 0x40 }, /* BRAILLE PATTERN DOTS-4 -> '@' */
+ { 0x09, 0x63 }, /* BRAILLE PATTERN DOTS-14 -> 'c' */
+ { 0x0A, 0x69 }, /* BRAILLE PATTERN DOTS-24 -> 'i' */
+ { 0x0B, 0x66 }, /* BRAILLE PATTERN DOTS-124 -> 'f' */
+ { 0x0C, 0x2F }, /* BRAILLE PATTERN DOTS-34 -> '/' */
+ { 0x0D, 0x6D }, /* BRAILLE PATTERN DOTS-134 -> 'm' */
+ { 0x0E, 0x73 }, /* BRAILLE PATTERN DOTS-234 -> 's' */
+ { 0x0F, 0x70 }, /* BRAILLE PATTERN DOTS-1234 -> 'p' */
+ { 0x10, 0x22 }, /* BRAILLE PATTERN DOTS-5 -> '"' */
+ { 0x11, 0x65 }, /* BRAILLE PATTERN DOTS-15 -> 'e' */
+ { 0x12, 0x33 }, /* BRAILLE PATTERN DOTS-25 -> '3' */
+ { 0x13, 0x68 }, /* BRAILLE PATTERN DOTS-125 -> 'h' */
+ { 0x14, 0x39 }, /* BRAILLE PATTERN DOTS-35 -> '9' */
+ { 0x15, 0x6F }, /* BRAILLE PATTERN DOTS-135 -> 'o' */
+ { 0x16, 0x36 }, /* BRAILLE PATTERN DOTS-235 -> '6' */
+ { 0x17, 0x72 }, /* BRAILLE PATTERN DOTS-1235 -> 'r' */
+ { 0x18, 0x5E }, /* BRAILLE PATTERN DOTS-45 -> '^' */
+ { 0x19, 0x64 }, /* BRAILLE PATTERN DOTS-145 -> 'd' */
+ { 0x1A, 0x6A }, /* BRAILLE PATTERN DOTS-245 -> 'j' */
+ { 0x1B, 0x67 }, /* BRAILLE PATTERN DOTS-1245 -> 'g' */
+ { 0x1C, 0x3E }, /* BRAILLE PATTERN DOTS-345 -> '>' */
+ { 0x1D, 0x6E }, /* BRAILLE PATTERN DOTS-1345 -> 'n' */
+ { 0x1E, 0x74 }, /* BRAILLE PATTERN DOTS-2345 -> 't' */
+ { 0x1F, 0x71 }, /* BRAILLE PATTERN DOTS-12345 -> 'q' */
+ { 0x20, 0x2C }, /* BRAILLE PATTERN DOTS-6 -> ',' */
+ { 0x21, 0x2A }, /* BRAILLE PATTERN DOTS-16 -> '*' */
+ { 0x22, 0x35 }, /* BRAILLE PATTERN DOTS-26 -> '5' */
+ { 0x23, 0x3C }, /* BRAILLE PATTERN DOTS-126 -> '<' */
+ { 0x24, 0x2D }, /* BRAILLE PATTERN DOTS-36 -> '-' */
+ { 0x25, 0x75 }, /* BRAILLE PATTERN DOTS-136 -> 'u' */
+ { 0x26, 0x38 }, /* BRAILLE PATTERN DOTS-236 -> '8' */
+ { 0x27, 0x76 }, /* BRAILLE PATTERN DOTS-1236 -> 'v' */
+ { 0x28, 0x2E }, /* BRAILLE PATTERN DOTS-46 -> '.' */
+ { 0x29, 0x25 }, /* BRAILLE PATTERN DOTS-146 -> '%' */
+ { 0x2A, 0x5B }, /* BRAILLE PATTERN DOTS-246 -> '[' */
+ { 0x2B, 0x24 }, /* BRAILLE PATTERN DOTS-1246 -> '$' */
+ { 0x2C, 0x2B }, /* BRAILLE PATTERN DOTS-346 -> '+' */
+ { 0x2D, 0x78 }, /* BRAILLE PATTERN DOTS-1346 -> 'x' */
+ { 0x2E, 0x21 }, /* BRAILLE PATTERN DOTS-2346 -> '!' */
+ { 0x2F, 0x26 }, /* BRAILLE PATTERN DOTS-12346 -> '&' */
+ { 0x30, 0x3B }, /* BRAILLE PATTERN DOTS-56 -> ';' */
+ { 0x31, 0x3A }, /* BRAILLE PATTERN DOTS-156 -> ':' */
+ { 0x32, 0x34 }, /* BRAILLE PATTERN DOTS-256 -> '4' */
+ { 0x33, 0x5C }, /* BRAILLE PATTERN DOTS-1256 -> '\' */
+ { 0x34, 0x30 }, /* BRAILLE PATTERN DOTS-356 -> '0' */
+ { 0x35, 0x7A }, /* BRAILLE PATTERN DOTS-1356 -> 'z' */
+ { 0x36, 0x37 }, /* BRAILLE PATTERN DOTS-2356 -> '7' */
+ { 0x37, 0x28 }, /* BRAILLE PATTERN DOTS-12356 -> '(' */
+ { 0x38, 0x5F }, /* BRAILLE PATTERN DOTS-456 -> '_' */
+ { 0x39, 0x3F }, /* BRAILLE PATTERN DOTS-1456 -> '?' */
+ { 0x3A, 0x77 }, /* BRAILLE PATTERN DOTS-2456 -> 'w' */
+ { 0x3B, 0x5D }, /* BRAILLE PATTERN DOTS-12456 -> ']' */
+ { 0x3C, 0x23 }, /* BRAILLE PATTERN DOTS-3456 -> '#' */
+ { 0x3D, 0x79 }, /* BRAILLE PATTERN DOTS-13456 -> 'y' */
+ { 0x3E, 0x29 }, /* BRAILLE PATTERN DOTS-23456 -> ')' */
+ { 0x3F, 0x3D }, /* BRAILLE PATTERN DOTS-123456 -> '=' */
+ /* Entries for page 0x29 */
+ { 0x83, 0x7B }, /* LEFT WHITE CURLY BRACKET -> '{' */
+ /* Entries for page 0x2C */
+ { 0x60, 0x4C }, /* LATIN CAPITAL LETTER L WITH DOUBLE BAR -> 'L' */
+ { 0x61, 0x6C }, /* LATIN SMALL LETTER L WITH DOUBLE BAR -> 'l' */
+ { 0x62, 0x4C }, /* LATIN CAPITAL LETTER L WITH MIDDLE TILDE -> 'L' */
+ { 0x63, 0x50 }, /* LATIN CAPITAL LETTER P WITH STROKE -> 'P' */
+ { 0x64, 0x52 }, /* LATIN CAPITAL LETTER R WITH TAIL -> 'R' */
+ { 0x65, 0x61 }, /* LATIN SMALL LETTER A WITH STROKE -> 'a' */
+ { 0x66, 0x74 }, /* LATIN SMALL LETTER T WITH DIAGONAL STROKE -> 't' */
+ { 0x67, 0x48 }, /* LATIN CAPITAL LETTER H WITH DESCENDER -> 'H' */
+ { 0x68, 0x68 }, /* LATIN SMALL LETTER H WITH DESCENDER -> 'h' */
+ { 0x69, 0x4B }, /* LATIN CAPITAL LETTER K WITH DESCENDER -> 'K' */
+ { 0x6A, 0x6B }, /* LATIN SMALL LETTER K WITH DESCENDER -> 'k' */
+ { 0x6B, 0x5A }, /* LATIN CAPITAL LETTER Z WITH DESCENDER -> 'Z' */
+ { 0x6C, 0x7A }, /* LATIN SMALL LETTER Z WITH DESCENDER -> 'z' */
+ { 0x6E, 0x4D }, /* LATIN CAPITAL LETTER M WITH HOOK -> 'M' */
+ { 0x6F, 0x41 }, /* LATIN CAPITAL LETTER TURNED A -> 'A' */
+ /* Entries for page 0x2E */
+ { 0x00, 0x72 }, /* RIGHT ANGLE SUBSTITUTION MARKER -> 'r' */
+ { 0x06, 0x54 }, /* RAISED INTERPOLATION MARKER -> 'T' */
+ { 0x09, 0x73 }, /* LEFT TRANSPOSITION BRACKET -> 's' */
+ { 0x0C, 0x5C }, /* LEFT RAISED OMISSION BRACKET -> '\' */
+ { 0x0D, 0x2F }, /* RIGHT RAISED OMISSION BRACKET -> '/' */
+ { 0x12, 0x3E }, /* HYPODIASTOLE -> '>' */
+ { 0x13, 0x25 }, /* DOTTED OBELOS -> '%' */
+ { 0x16, 0x3E }, /* DOTTED RIGHT-POINTING ANGLE -> '>' */
+ { 0x17, 0x3D }, /* DOUBLE OBLIQUE HYPHEN -> '=' */
+ { 0x19, 0x2F }, /* PALM BRANCH -> '/' */
+ { 0x1A, 0x2D }, /* HYPHEN WITH DIAERESIS -> '-' */
+ { 0x1B, 0x7E }, /* TILDE WITH RING ABOVE -> '~' */
+ { 0x1C, 0x5C }, /* LEFT LOW PARAPHRASE BRACKET -> '\' */
+ { 0x1D, 0x2F }, /* RIGHT LOW PARAPHRASE BRACKET -> '/' */
+ { 0x1E, 0x7E }, /* TILDE WITH DOT ABOVE -> '~' */
+ { 0x1F, 0x7E }, /* TILDE WITH DOT BELOW -> '~' */
+ { 0x2E, 0x3F }, /* REVERSED QUESTION MARK -> '?' */
+ { 0x2F, 0x27 }, /* VERTICAL TILDE -> ''' */
+ { 0x30, 0x6F }, /* RING POINT -> 'o' */
+ { 0x31, 0x2E }, /* WORD SEPARATOR MIDDLE DOT -> '.' */
+ { 0x32, 0x2C }, /* TURNED COMMA -> ',' */
+ { 0x33, 0x2E }, /* RAISED DOT -> '.' */
+ { 0x34, 0x2C }, /* RAISED COMMA -> ',' */
+ { 0x35, 0x3B }, /* TURNED SEMICOLON -> ';' */
+ { 0x3C, 0x78 }, /* STENOGRAPHIC FULL STOP -> 'x' */
+ { 0x3D, 0x7C }, /* VERTICAL SIX DOTS -> '|' */
+ { 0x40, 0x3D }, /* DOUBLE HYPHEN -> '=' */
+ { 0x41, 0x2C }, /* REVERSED COMMA -> ',' */
+ { 0x42, 0x22 }, /* DOUBLE LOW-REVERSED-9 QUOTATION MARK -> '"' */
+ /* Entries for page 0x30 */
+ { 0x00, 0x20 }, /* IDEOGRAPHIC SPACE -> ' ' */
+ { 0x03, 0x22 }, /* DITTO MARK -> '"' */
+ { 0x05, 0x22 }, /* IDEOGRAPHIC ITERATION MARK -> '"' */
+ { 0x06, 0x2F }, /* IDEOGRAPHIC CLOSING MARK -> '/' */
+ { 0x07, 0x30 }, /* IDEOGRAPHIC NUMBER ZERO -> '0' */
+ { 0x08, 0x3C }, /* LEFT ANGLE BRACKET -> '<' */
+ { 0x0C, 0x5B }, /* LEFT CORNER BRACKET -> '[' */
+ { 0x0E, 0x7B }, /* LEFT WHITE CORNER BRACKET -> '{' */
+ { 0x12, 0x40 }, /* POSTAL MARK -> '@' */
+ { 0x14, 0x5B }, /* LEFT TORTOISE SHELL BRACKET -> '[' */
+ { 0x20, 0x40 }, /* POSTAL MARK FACE -> '@' */
+ { 0x21, 0x31 }, /* HANGZHOU NUMERAL ONE -> '1' */
+ { 0x22, 0x32 }, /* HANGZHOU NUMERAL TWO -> '2' */
+ { 0x23, 0x33 }, /* HANGZHOU NUMERAL THREE -> '3' */
+ { 0x24, 0x34 }, /* HANGZHOU NUMERAL FOUR -> '4' */
+ { 0x25, 0x35 }, /* HANGZHOU NUMERAL FIVE -> '5' */
+ { 0x26, 0x36 }, /* HANGZHOU NUMERAL SIX -> '6' */
+ { 0x27, 0x37 }, /* HANGZHOU NUMERAL SEVEN -> '7' */
+ { 0x28, 0x38 }, /* HANGZHOU NUMERAL EIGHT -> '8' */
+ { 0x29, 0x39 }, /* HANGZHOU NUMERAL NINE -> '9' */
+ { 0x30, 0x7E }, /* WAVY DASH -> '~' */
+ { 0x31, 0x00 }, /* VERTICAL KANA REPEAT MARK -> ... */
+ { 0x34, 0x2B }, /* VERTICAL KANA REPEAT WITH VOICED SOUND MARK UPPER HALF -> '+' */
+ { 0x36, 0x40 }, /* CIRCLED POSTAL MARK -> '@' */
+ { 0x41, 0x61 }, /* HIRAGANA LETTER SMALL A -> 'a' */
+ { 0x42, 0x61 }, /* HIRAGANA LETTER A -> 'a' */
+ { 0x43, 0x69 }, /* HIRAGANA LETTER SMALL I -> 'i' */
+ { 0x44, 0x69 }, /* HIRAGANA LETTER I -> 'i' */
+ { 0x45, 0x75 }, /* HIRAGANA LETTER SMALL U -> 'u' */
+ { 0x46, 0x75 }, /* HIRAGANA LETTER U -> 'u' */
+ { 0x47, 0x65 }, /* HIRAGANA LETTER SMALL E -> 'e' */
+ { 0x48, 0x65 }, /* HIRAGANA LETTER E -> 'e' */
+ { 0x49, 0x6F }, /* HIRAGANA LETTER SMALL O -> 'o' */
+ { 0x4A, 0x6F }, /* HIRAGANA LETTER O -> 'o' */
+ { 0x93, 0x6E }, /* HIRAGANA LETTER N -> 'n' */
+ { 0x9D, 0x22 }, /* HIRAGANA ITERATION MARK -> '"' */
+ { 0x9E, 0x22 }, /* HIRAGANA VOICED ITERATION MARK -> '"' */
+ { 0xA0, 0x3D }, /* KATAKANA-HIRAGANA DOUBLE HYPHEN -> '=' */
+ { 0xA1, 0x61 }, /* KATAKANA LETTER SMALL A -> 'a' */
+ { 0xA2, 0x61 }, /* KATAKANA LETTER A -> 'a' */
+ { 0xA3, 0x69 }, /* KATAKANA LETTER SMALL I -> 'i' */
+ { 0xA4, 0x69 }, /* KATAKANA LETTER I -> 'i' */
+ { 0xA5, 0x75 }, /* KATAKANA LETTER SMALL U -> 'u' */
+ { 0xA6, 0x75 }, /* KATAKANA LETTER U -> 'u' */
+ { 0xA7, 0x65 }, /* KATAKANA LETTER SMALL E -> 'e' */
+ { 0xA8, 0x65 }, /* KATAKANA LETTER E -> 'e' */
+ { 0xA9, 0x6F }, /* KATAKANA LETTER SMALL O -> 'o' */
+ { 0xAA, 0x6F }, /* KATAKANA LETTER O -> 'o' */
+ { 0xF3, 0x6E }, /* KATAKANA LETTER N -> 'n' */
+ { 0xFB, 0x2A }, /* KATAKANA MIDDLE DOT -> '*' */
+ { 0xFC, 0x2D }, /* KATAKANA-HIRAGANA PROLONGED SOUND MARK -> '-' */
+ { 0xFD, 0x22 }, /* KATAKANA ITERATION MARK -> '"' */
+ { 0xFE, 0x22 }, /* KATAKANA VOICED ITERATION MARK -> '"' */
+ /* Entries for page 0x31 */
+ { 0x05, 0x42 }, /* BOPOMOFO LETTER B -> 'B' */
+ { 0x06, 0x50 }, /* BOPOMOFO LETTER P -> 'P' */
+ { 0x07, 0x4D }, /* BOPOMOFO LETTER M -> 'M' */
+ { 0x08, 0x46 }, /* BOPOMOFO LETTER F -> 'F' */
+ { 0x09, 0x44 }, /* BOPOMOFO LETTER D -> 'D' */
+ { 0x0A, 0x54 }, /* BOPOMOFO LETTER T -> 'T' */
+ { 0x0B, 0x4E }, /* BOPOMOFO LETTER N -> 'N' */
+ { 0x0C, 0x4C }, /* BOPOMOFO LETTER L -> 'L' */
+ { 0x0D, 0x47 }, /* BOPOMOFO LETTER G -> 'G' */
+ { 0x0E, 0x4B }, /* BOPOMOFO LETTER K -> 'K' */
+ { 0x0F, 0x48 }, /* BOPOMOFO LETTER H -> 'H' */
+ { 0x10, 0x4A }, /* BOPOMOFO LETTER J -> 'J' */
+ { 0x11, 0x51 }, /* BOPOMOFO LETTER Q -> 'Q' */
+ { 0x12, 0x58 }, /* BOPOMOFO LETTER X -> 'X' */
+ { 0x16, 0x52 }, /* BOPOMOFO LETTER R -> 'R' */
+ { 0x17, 0x5A }, /* BOPOMOFO LETTER Z -> 'Z' */
+ { 0x18, 0x43 }, /* BOPOMOFO LETTER C -> 'C' */
+ { 0x19, 0x53 }, /* BOPOMOFO LETTER S -> 'S' */
+ { 0x1A, 0x41 }, /* BOPOMOFO LETTER A -> 'A' */
+ { 0x1B, 0x4F }, /* BOPOMOFO LETTER O -> 'O' */
+ { 0x1C, 0x45 }, /* BOPOMOFO LETTER E -> 'E' */
+ { 0x27, 0x49 }, /* BOPOMOFO LETTER I -> 'I' */
+ { 0x28, 0x55 }, /* BOPOMOFO LETTER U -> 'U' */
+ { 0x2A, 0x56 }, /* BOPOMOFO LETTER V -> 'V' */
+ { 0x31, 0x67 }, /* HANGUL LETTER KIYEOK -> 'g' */
+ { 0x34, 0x6E }, /* HANGUL LETTER NIEUN -> 'n' */
+ { 0x37, 0x64 }, /* HANGUL LETTER TIKEUT -> 'd' */
+ { 0x39, 0x72 }, /* HANGUL LETTER RIEUL -> 'r' */
+ { 0x41, 0x6D }, /* HANGUL LETTER MIEUM -> 'm' */
+ { 0x42, 0x62 }, /* HANGUL LETTER PIEUP -> 'b' */
+ { 0x45, 0x73 }, /* HANGUL LETTER SIOS -> 's' */
+ { 0x48, 0x6A }, /* HANGUL LETTER CIEUC -> 'j' */
+ { 0x4A, 0x63 }, /* HANGUL LETTER CHIEUCH -> 'c' */
+ { 0x4B, 0x6B }, /* HANGUL LETTER KHIEUKH -> 'k' */
+ { 0x4C, 0x74 }, /* HANGUL LETTER THIEUTH -> 't' */
+ { 0x4D, 0x70 }, /* HANGUL LETTER PHIEUPH -> 'p' */
+ { 0x4E, 0x68 }, /* HANGUL LETTER HIEUH -> 'h' */
+ { 0x4F, 0x61 }, /* HANGUL LETTER A -> 'a' */
+ { 0x54, 0x65 }, /* HANGUL LETTER E -> 'e' */
+ { 0x57, 0x6F }, /* HANGUL LETTER O -> 'o' */
+ { 0x5C, 0x75 }, /* HANGUL LETTER U -> 'u' */
+ { 0x63, 0x69 }, /* HANGUL LETTER I -> 'i' */
+ { 0x7F, 0x5A }, /* HANGUL LETTER PANSIOS -> 'Z' */
+ { 0x81, 0x4E }, /* HANGUL LETTER YESIEUNG -> 'N' */
+ { 0x86, 0x51 }, /* HANGUL LETTER YEORINHIEUH -> 'Q' */
+ { 0x8D, 0x55 }, /* HANGUL LETTER ARAEA -> 'U' */
+ { 0xB4, 0x50 }, /* BOPOMOFO FINAL LETTER P -> 'P' */
+ { 0xB5, 0x54 }, /* BOPOMOFO FINAL LETTER T -> 'T' */
+ { 0xB6, 0x4B }, /* BOPOMOFO FINAL LETTER K -> 'K' */
+ { 0xB7, 0x48 }, /* BOPOMOFO FINAL LETTER H -> 'H' */
+ /* Entries for page 0x32 */
+ { 0xD0, 0x61 }, /* CIRCLED KATAKANA A -> 'a' */
+ { 0xD1, 0x69 }, /* CIRCLED KATAKANA I -> 'i' */
+ { 0xD2, 0x75 }, /* CIRCLED KATAKANA U -> 'u' */
+ { 0xD3, 0x75 }, /* CIRCLED KATAKANA E -> 'u' */
+ { 0xD4, 0x6F }, /* CIRCLED KATAKANA O -> 'o' */
+ /* Entries for page 0xA0 */
+ { 0x02, 0x69 }, /* YI SYLLABLE I -> 'i' */
+ { 0x0A, 0x61 }, /* YI SYLLABLE A -> 'a' */
+ { 0x11, 0x6F }, /* YI SYLLABLE O -> 'o' */
+ { 0x14, 0x65 }, /* YI SYLLABLE E -> 'e' */
+ /* Entries for page 0xC5 */
+ { 0x44, 0x61 }, /* HANGUL SYLLABLE A -> 'a' */
+ { 0xD0, 0x65 }, /* HANGUL SYLLABLE E -> 'e' */
+ /* Entries for page 0xC6 */
+ { 0x24, 0x6F }, /* HANGUL SYLLABLE O -> 'o' */
+ { 0xB0, 0x75 }, /* HANGUL SYLLABLE U -> 'u' */
+ /* Entries for page 0xC7 */
+ { 0x74, 0x69 }, /* HANGUL SYLLABLE I -> 'i' */
+ /* Entries for page 0xFB */
+ { 0x1D, 0x69 }, /* HEBREW LETTER YOD WITH HIRIQ -> 'i' */
+ { 0x20, 0x60 }, /* HEBREW LETTER ALTERNATIVE AYIN -> '`' */
+ { 0x21, 0x41 }, /* HEBREW LETTER WIDE ALEF -> 'A' */
+ { 0x22, 0x64 }, /* HEBREW LETTER WIDE DALET -> 'd' */
+ { 0x23, 0x68 }, /* HEBREW LETTER WIDE HE -> 'h' */
+ { 0x25, 0x6C }, /* HEBREW LETTER WIDE LAMED -> 'l' */
+ { 0x26, 0x6D }, /* HEBREW LETTER WIDE FINAL MEM -> 'm' */
+ { 0x27, 0x72 }, /* HEBREW LETTER WIDE RESH -> 'r' */
+ { 0x28, 0x74 }, /* HEBREW LETTER WIDE TAV -> 't' */
+ { 0x29, 0x2B }, /* HEBREW LETTER ALTERNATIVE PLUS SIGN -> '+' */
+ { 0x2B, 0x53 }, /* HEBREW LETTER SHIN WITH SIN DOT -> 'S' */
+ { 0x2D, 0x53 }, /* HEBREW LETTER SHIN WITH DAGESH AND SIN DOT -> 'S' */
+ { 0x2E, 0x61 }, /* HEBREW LETTER ALEF WITH PATAH -> 'a' */
+ { 0x2F, 0x61 }, /* HEBREW LETTER ALEF WITH QAMATS -> 'a' */
+ { 0x30, 0x41 }, /* HEBREW LETTER ALEF WITH MAPIQ -> 'A' */
+ { 0x31, 0x62 }, /* HEBREW LETTER BET WITH DAGESH -> 'b' */
+ { 0x32, 0x67 }, /* HEBREW LETTER GIMEL WITH DAGESH -> 'g' */
+ { 0x33, 0x64 }, /* HEBREW LETTER DALET WITH DAGESH -> 'd' */
+ { 0x34, 0x68 }, /* HEBREW LETTER HE WITH MAPIQ -> 'h' */
+ { 0x35, 0x76 }, /* HEBREW LETTER VAV WITH DAGESH -> 'v' */
+ { 0x36, 0x7A }, /* HEBREW LETTER ZAYIN WITH DAGESH -> 'z' */
+ { 0x38, 0x74 }, /* HEBREW LETTER TET WITH DAGESH -> 't' */
+ { 0x39, 0x79 }, /* HEBREW LETTER YOD WITH DAGESH -> 'y' */
+ { 0x3C, 0x6C }, /* HEBREW LETTER LAMED WITH DAGESH -> 'l' */
+ { 0x3E, 0x6D }, /* HEBREW LETTER MEM WITH DAGESH -> 'm' */
+ { 0x40, 0x6E }, /* HEBREW LETTER NUN WITH DAGESH -> 'n' */
+ { 0x41, 0x73 }, /* HEBREW LETTER SAMEKH WITH DAGESH -> 's' */
+ { 0x43, 0x70 }, /* HEBREW LETTER FINAL PE WITH DAGESH -> 'p' */
+ { 0x44, 0x70 }, /* HEBREW LETTER PE WITH DAGESH -> 'p' */
+ { 0x47, 0x6B }, /* HEBREW LETTER QOF WITH DAGESH -> 'k' */
+ { 0x48, 0x72 }, /* HEBREW LETTER RESH WITH DAGESH -> 'r' */
+ { 0x4A, 0x74 }, /* HEBREW LETTER TAV WITH DAGESH -> 't' */
+ { 0x4B, 0x6F }, /* HEBREW LETTER VAV WITH HOLAM -> 'o' */
+ { 0x4C, 0x76 }, /* HEBREW LETTER BET WITH RAFE -> 'v' */
+ { 0x4E, 0x66 }, /* HEBREW LETTER PE WITH RAFE -> 'f' */
+ /* Entries for page 0xFE */
+ { 0x23, 0x7E }, /* COMBINING DOUBLE TILDE RIGHT HALF -> '~' */
+ { 0x32, 0x2D }, /* PRESENTATION FORM FOR VERTICAL EN DASH -> '-' */
+ { 0x33, 0x5F }, /* PRESENTATION FORM FOR VERTICAL LOW LINE -> '_' */
+ { 0x34, 0x5F }, /* PRESENTATION FORM FOR VERTICAL WAVY LOW LINE -> '_' */
+ { 0x35, 0x28 }, /* PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS -> '(' */
+ { 0x37, 0x7B }, /* PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET -> '{' */
+ { 0x39, 0x5B }, /* PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET -> '[' */
+ { 0x3F, 0x3C }, /* PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET -> '<' */
+ { 0x41, 0x5B }, /* PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET -> '[' */
+ { 0x43, 0x7B }, /* PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET -> '{' */
+ { 0x44, 0x7D }, /* PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET -> '}' */
+ { 0x50, 0x2C }, /* SMALL COMMA -> ',' */
+ { 0x51, 0x2C }, /* SMALL IDEOGRAPHIC COMMA -> ',' */
+ { 0x52, 0x2E }, /* SMALL FULL STOP -> '.' */
+ { 0x54, 0x3B }, /* SMALL SEMICOLON -> ';' */
+ { 0x55, 0x3A }, /* SMALL COLON -> ':' */
+ { 0x56, 0x3F }, /* SMALL QUESTION MARK -> '?' */
+ { 0x57, 0x21 }, /* SMALL EXCLAMATION MARK -> '!' */
+ { 0x58, 0x2D }, /* SMALL EM DASH -> '-' */
+ { 0x59, 0x28 }, /* SMALL LEFT PARENTHESIS -> '(' */
+ { 0x5A, 0x29 }, /* SMALL RIGHT PARENTHESIS -> ')' */
+ { 0x5B, 0x7B }, /* SMALL LEFT CURLY BRACKET -> '{' */
+ { 0x5C, 0x7D }, /* SMALL RIGHT CURLY BRACKET -> '}' */
+ { 0x5D, 0x7B }, /* SMALL LEFT TORTOISE SHELL BRACKET -> '{' */
+ { 0x5E, 0x7D }, /* SMALL RIGHT TORTOISE SHELL BRACKET -> '}' */
+ { 0x5F, 0x23 }, /* SMALL NUMBER SIGN -> '#' */
+ { 0x60, 0x26 }, /* SMALL AMPERSAND -> '&' */
+ { 0x61, 0x2A }, /* SMALL ASTERISK -> '*' */
+ { 0x62, 0x2B }, /* SMALL PLUS SIGN -> '+' */
+ { 0x63, 0x2D }, /* SMALL HYPHEN-MINUS -> '-' */
+ { 0x64, 0x3C }, /* SMALL LESS-THAN SIGN -> '<' */
+ { 0x65, 0x3E }, /* SMALL GREATER-THAN SIGN -> '>' */
+ { 0x66, 0x3D }, /* SMALL EQUALS SIGN -> '=' */
+ { 0x68, 0x5C }, /* SMALL REVERSE SOLIDUS -> '\' */
+ { 0x69, 0x24 }, /* SMALL DOLLAR SIGN -> '$' */
+ { 0x6A, 0x25 }, /* SMALL PERCENT SIGN -> '%' */
+ { 0x6B, 0x40 }, /* SMALL COMMERCIAL AT -> '@' */
+ /* Entries for page 0xFF */
+ { 0x61, 0x2E }, /* HALFWIDTH IDEOGRAPHIC FULL STOP -> '.' */
+ { 0x62, 0x5B }, /* HALFWIDTH LEFT CORNER BRACKET -> '[' */
+ { 0x63, 0x5D }, /* HALFWIDTH RIGHT CORNER BRACKET -> ']' */
+ { 0x64, 0x2C }, /* HALFWIDTH IDEOGRAPHIC COMMA -> ',' */
+ { 0x65, 0x2A }, /* HALFWIDTH KATAKANA MIDDLE DOT -> '*' */
+ { 0x67, 0x61 }, /* HALFWIDTH KATAKANA LETTER SMALL A -> 'a' */
+ { 0x68, 0x69 }, /* HALFWIDTH KATAKANA LETTER SMALL I -> 'i' */
+ { 0x69, 0x75 }, /* HALFWIDTH KATAKANA LETTER SMALL U -> 'u' */
+ { 0x6A, 0x65 }, /* HALFWIDTH KATAKANA LETTER SMALL E -> 'e' */
+ { 0x6B, 0x6F }, /* HALFWIDTH KATAKANA LETTER SMALL O -> 'o' */
+ { 0x70, 0x2B }, /* HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK -> '+' */
+ { 0x71, 0x61 }, /* HALFWIDTH KATAKANA LETTER A -> 'a' */
+ { 0x72, 0x69 }, /* HALFWIDTH KATAKANA LETTER I -> 'i' */
+ { 0x73, 0x75 }, /* HALFWIDTH KATAKANA LETTER U -> 'u' */
+ { 0x74, 0x65 }, /* HALFWIDTH KATAKANA LETTER E -> 'e' */
+ { 0x75, 0x6F }, /* HALFWIDTH KATAKANA LETTER O -> 'o' */
+ { 0x9D, 0x6E }, /* HALFWIDTH KATAKANA LETTER N -> 'n' */
+ { 0x9E, 0x3A }, /* HALFWIDTH KATAKANA VOICED SOUND MARK -> ':' */
+ { 0x9F, 0x3B }, /* HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK -> ';' */
+ { 0xA1, 0x67 }, /* HALFWIDTH HANGUL LETTER KIYEOK -> 'g' */
+ { 0xA4, 0x6E }, /* HALFWIDTH HANGUL LETTER NIEUN -> 'n' */
+ { 0xA7, 0x64 }, /* HALFWIDTH HANGUL LETTER TIKEUT -> 'd' */
+ { 0xA9, 0x72 }, /* HALFWIDTH HANGUL LETTER RIEUL -> 'r' */
+ { 0xB1, 0x6D }, /* HALFWIDTH HANGUL LETTER MIEUM -> 'm' */
+ { 0xB2, 0x62 }, /* HALFWIDTH HANGUL LETTER PIEUP -> 'b' */
+ { 0xB5, 0x73 }, /* HALFWIDTH HANGUL LETTER SIOS -> 's' */
+ { 0xB8, 0x6A }, /* HALFWIDTH HANGUL LETTER CIEUC -> 'j' */
+ { 0xBA, 0x63 }, /* HALFWIDTH HANGUL LETTER CHIEUCH -> 'c' */
+ { 0xBB, 0x6B }, /* HALFWIDTH HANGUL LETTER KHIEUKH -> 'k' */
+ { 0xBC, 0x74 }, /* HALFWIDTH HANGUL LETTER THIEUTH -> 't' */
+ { 0xBD, 0x70 }, /* HALFWIDTH HANGUL LETTER PHIEUPH -> 'p' */
+ { 0xBE, 0x68 }, /* HALFWIDTH HANGUL LETTER HIEUH -> 'h' */
+ { 0xC2, 0x61 }, /* HALFWIDTH HANGUL LETTER A -> 'a' */
+ { 0xC7, 0x65 }, /* HALFWIDTH HANGUL LETTER E -> 'e' */
+ { 0xCC, 0x6F }, /* HALFWIDTH HANGUL LETTER O -> 'o' */
+ { 0xD3, 0x75 }, /* HALFWIDTH HANGUL LETTER U -> 'u' */
+ { 0xDC, 0x69 }, /* HALFWIDTH HANGUL LETTER I -> 'i' */
+ { 0xE2, 0x21 }, /* FULLWIDTH NOT SIGN -> '!' */
+ { 0xE3, 0x2D }, /* FULLWIDTH MACRON -> '-' */
+ { 0xE4, 0x7C }, /* FULLWIDTH BROKEN BAR -> '|' */
+ { 0xE8, 0x7C }, /* HALFWIDTH FORMS LIGHT VERTICAL -> '|' */
+ { 0xE9, 0x3C }, /* HALFWIDTH LEFTWARDS ARROW -> '<' */
+ { 0xEA, 0x5E }, /* HALFWIDTH UPWARDS ARROW -> '^' */
+ { 0xEB, 0x3E }, /* HALFWIDTH RIGHTWARDS ARROW -> '>' */
+ { 0xEC, 0x76 }, /* HALFWIDTH DOWNWARDS ARROW -> 'v' */
+ { 0xED, 0x23 }, /* HALFWIDTH BLACK SQUARE -> '#' */
+ { 0xEE, 0x4F }, /* HALFWIDTH WHITE CIRCLE -> 'O' */
+ { 0xF9, 0x7B }, /* INTERLINEAR ANNOTATION ANCHOR -> '{' */
+ { 0xFA, 0x7C }, /* INTERLINEAR ANNOTATION SEPARATOR -> '|' */
+ { 0xFB, 0x7D }, /* INTERLINEAR ANNOTATION TERMINATOR -> '}' */
+};
+
+#define UCS_PAGE_ENTRY_RANGE_MARKER 0
diff --git a/drivers/tty/vt/ucs_recompose_table.h_shipped b/drivers/tty/vt/ucs_recompose_table.h_shipped
new file mode 100644
index 000000000000..bd91edde5d19
--- /dev/null
+++ b/drivers/tty/vt/ucs_recompose_table.h_shipped
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ucs_recompose_table.h - Unicode character recomposition
+ *
+ * Auto-generated by gen_ucs_recompose_table.py
+ *
+ * Unicode Version: 16.0.0
+ *
+ * This file contains a table with most commonly used Latin, Greek, and
+ * Cyrillic recomposition pairs only (71 entries). To generate a table with
+ * all possible recomposition pairs from the Unicode BMP (1000 entries)
+ * instead, run:
+ *
+ * python gen_ucs_recompose_table.py --full
+ */
+
+/*
+ * Table of most commonly used Latin, Greek, and Cyrillic recomposition pairs only
+ * Sorted by base character and then combining mark for binary search
+ */
+static const struct ucs_recomposition ucs_recomposition_table[] = {
+ { 0x0041, 0x0300, 0x00C0 }, /* LATIN CAPITAL LETTER A + COMBINING GRAVE ACCENT = LATIN CAPITAL LETTER A WITH GRAVE */
+ { 0x0041, 0x0301, 0x00C1 }, /* LATIN CAPITAL LETTER A + COMBINING ACUTE ACCENT = LATIN CAPITAL LETTER A WITH ACUTE */
+ { 0x0041, 0x0302, 0x00C2 }, /* LATIN CAPITAL LETTER A + COMBINING CIRCUMFLEX ACCENT = LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
+ { 0x0041, 0x0303, 0x00C3 }, /* LATIN CAPITAL LETTER A + COMBINING TILDE = LATIN CAPITAL LETTER A WITH TILDE */
+ { 0x0041, 0x0308, 0x00C4 }, /* LATIN CAPITAL LETTER A + COMBINING DIAERESIS = LATIN CAPITAL LETTER A WITH DIAERESIS */
+ { 0x0041, 0x030A, 0x00C5 }, /* LATIN CAPITAL LETTER A + COMBINING RING ABOVE = LATIN CAPITAL LETTER A WITH RING ABOVE */
+ { 0x0043, 0x0327, 0x00C7 }, /* LATIN CAPITAL LETTER C + COMBINING CEDILLA = LATIN CAPITAL LETTER C WITH CEDILLA */
+ { 0x0045, 0x0300, 0x00C8 }, /* LATIN CAPITAL LETTER E + COMBINING GRAVE ACCENT = LATIN CAPITAL LETTER E WITH GRAVE */
+ { 0x0045, 0x0301, 0x00C9 }, /* LATIN CAPITAL LETTER E + COMBINING ACUTE ACCENT = LATIN CAPITAL LETTER E WITH ACUTE */
+ { 0x0045, 0x0302, 0x00CA }, /* LATIN CAPITAL LETTER E + COMBINING CIRCUMFLEX ACCENT = LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
+ { 0x0045, 0x0308, 0x00CB }, /* LATIN CAPITAL LETTER E + COMBINING DIAERESIS = LATIN CAPITAL LETTER E WITH DIAERESIS */
+ { 0x0049, 0x0300, 0x00CC }, /* LATIN CAPITAL LETTER I + COMBINING GRAVE ACCENT = LATIN CAPITAL LETTER I WITH GRAVE */
+ { 0x0049, 0x0301, 0x00CD }, /* LATIN CAPITAL LETTER I + COMBINING ACUTE ACCENT = LATIN CAPITAL LETTER I WITH ACUTE */
+ { 0x0049, 0x0302, 0x00CE }, /* LATIN CAPITAL LETTER I + COMBINING CIRCUMFLEX ACCENT = LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
+ { 0x0049, 0x0308, 0x00CF }, /* LATIN CAPITAL LETTER I + COMBINING DIAERESIS = LATIN CAPITAL LETTER I WITH DIAERESIS */
+ { 0x004E, 0x0303, 0x00D1 }, /* LATIN CAPITAL LETTER N + COMBINING TILDE = LATIN CAPITAL LETTER N WITH TILDE */
+ { 0x004F, 0x0300, 0x00D2 }, /* LATIN CAPITAL LETTER O + COMBINING GRAVE ACCENT = LATIN CAPITAL LETTER O WITH GRAVE */
+ { 0x004F, 0x0301, 0x00D3 }, /* LATIN CAPITAL LETTER O + COMBINING ACUTE ACCENT = LATIN CAPITAL LETTER O WITH ACUTE */
+ { 0x004F, 0x0302, 0x00D4 }, /* LATIN CAPITAL LETTER O + COMBINING CIRCUMFLEX ACCENT = LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
+ { 0x004F, 0x0303, 0x00D5 }, /* LATIN CAPITAL LETTER O + COMBINING TILDE = LATIN CAPITAL LETTER O WITH TILDE */
+ { 0x004F, 0x0308, 0x00D6 }, /* LATIN CAPITAL LETTER O + COMBINING DIAERESIS = LATIN CAPITAL LETTER O WITH DIAERESIS */
+ { 0x0055, 0x0300, 0x00D9 }, /* LATIN CAPITAL LETTER U + COMBINING GRAVE ACCENT = LATIN CAPITAL LETTER U WITH GRAVE */
+ { 0x0055, 0x0301, 0x00DA }, /* LATIN CAPITAL LETTER U + COMBINING ACUTE ACCENT = LATIN CAPITAL LETTER U WITH ACUTE */
+ { 0x0055, 0x0302, 0x00DB }, /* LATIN CAPITAL LETTER U + COMBINING CIRCUMFLEX ACCENT = LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
+ { 0x0055, 0x0308, 0x00DC }, /* LATIN CAPITAL LETTER U + COMBINING DIAERESIS = LATIN CAPITAL LETTER U WITH DIAERESIS */
+ { 0x0059, 0x0301, 0x00DD }, /* LATIN CAPITAL LETTER Y + COMBINING ACUTE ACCENT = LATIN CAPITAL LETTER Y WITH ACUTE */
+ { 0x0061, 0x0300, 0x00E0 }, /* LATIN SMALL LETTER A + COMBINING GRAVE ACCENT = LATIN SMALL LETTER A WITH GRAVE */
+ { 0x0061, 0x0301, 0x00E1 }, /* LATIN SMALL LETTER A + COMBINING ACUTE ACCENT = LATIN SMALL LETTER A WITH ACUTE */
+ { 0x0061, 0x0302, 0x00E2 }, /* LATIN SMALL LETTER A + COMBINING CIRCUMFLEX ACCENT = LATIN SMALL LETTER A WITH CIRCUMFLEX */
+ { 0x0061, 0x0303, 0x00E3 }, /* LATIN SMALL LETTER A + COMBINING TILDE = LATIN SMALL LETTER A WITH TILDE */
+ { 0x0061, 0x0308, 0x00E4 }, /* LATIN SMALL LETTER A + COMBINING DIAERESIS = LATIN SMALL LETTER A WITH DIAERESIS */
+ { 0x0061, 0x030A, 0x00E5 }, /* LATIN SMALL LETTER A + COMBINING RING ABOVE = LATIN SMALL LETTER A WITH RING ABOVE */
+ { 0x0063, 0x0327, 0x00E7 }, /* LATIN SMALL LETTER C + COMBINING CEDILLA = LATIN SMALL LETTER C WITH CEDILLA */
+ { 0x0065, 0x0300, 0x00E8 }, /* LATIN SMALL LETTER E + COMBINING GRAVE ACCENT = LATIN SMALL LETTER E WITH GRAVE */
+ { 0x0065, 0x0301, 0x00E9 }, /* LATIN SMALL LETTER E + COMBINING ACUTE ACCENT = LATIN SMALL LETTER E WITH ACUTE */
+ { 0x0065, 0x0302, 0x00EA }, /* LATIN SMALL LETTER E + COMBINING CIRCUMFLEX ACCENT = LATIN SMALL LETTER E WITH CIRCUMFLEX */
+ { 0x0065, 0x0308, 0x00EB }, /* LATIN SMALL LETTER E + COMBINING DIAERESIS = LATIN SMALL LETTER E WITH DIAERESIS */
+ { 0x0069, 0x0300, 0x00EC }, /* LATIN SMALL LETTER I + COMBINING GRAVE ACCENT = LATIN SMALL LETTER I WITH GRAVE */
+ { 0x0069, 0x0301, 0x00ED }, /* LATIN SMALL LETTER I + COMBINING ACUTE ACCENT = LATIN SMALL LETTER I WITH ACUTE */
+ { 0x0069, 0x0302, 0x00EE }, /* LATIN SMALL LETTER I + COMBINING CIRCUMFLEX ACCENT = LATIN SMALL LETTER I WITH CIRCUMFLEX */
+ { 0x0069, 0x0308, 0x00EF }, /* LATIN SMALL LETTER I + COMBINING DIAERESIS = LATIN SMALL LETTER I WITH DIAERESIS */
+ { 0x006E, 0x0303, 0x00F1 }, /* LATIN SMALL LETTER N + COMBINING TILDE = LATIN SMALL LETTER N WITH TILDE */
+ { 0x006F, 0x0300, 0x00F2 }, /* LATIN SMALL LETTER O + COMBINING GRAVE ACCENT = LATIN SMALL LETTER O WITH GRAVE */
+ { 0x006F, 0x0301, 0x00F3 }, /* LATIN SMALL LETTER O + COMBINING ACUTE ACCENT = LATIN SMALL LETTER O WITH ACUTE */
+ { 0x006F, 0x0302, 0x00F4 }, /* LATIN SMALL LETTER O + COMBINING CIRCUMFLEX ACCENT = LATIN SMALL LETTER O WITH CIRCUMFLEX */
+ { 0x006F, 0x0303, 0x00F5 }, /* LATIN SMALL LETTER O + COMBINING TILDE = LATIN SMALL LETTER O WITH TILDE */
+ { 0x006F, 0x0308, 0x00F6 }, /* LATIN SMALL LETTER O + COMBINING DIAERESIS = LATIN SMALL LETTER O WITH DIAERESIS */
+ { 0x0075, 0x0300, 0x00F9 }, /* LATIN SMALL LETTER U + COMBINING GRAVE ACCENT = LATIN SMALL LETTER U WITH GRAVE */
+ { 0x0075, 0x0301, 0x00FA }, /* LATIN SMALL LETTER U + COMBINING ACUTE ACCENT = LATIN SMALL LETTER U WITH ACUTE */
+ { 0x0075, 0x0302, 0x00FB }, /* LATIN SMALL LETTER U + COMBINING CIRCUMFLEX ACCENT = LATIN SMALL LETTER U WITH CIRCUMFLEX */
+ { 0x0075, 0x0308, 0x00FC }, /* LATIN SMALL LETTER U + COMBINING DIAERESIS = LATIN SMALL LETTER U WITH DIAERESIS */
+ { 0x0079, 0x0301, 0x00FD }, /* LATIN SMALL LETTER Y + COMBINING ACUTE ACCENT = LATIN SMALL LETTER Y WITH ACUTE */
+ { 0x0079, 0x0308, 0x00FF }, /* LATIN SMALL LETTER Y + COMBINING DIAERESIS = LATIN SMALL LETTER Y WITH DIAERESIS */
+ { 0x0391, 0x0301, 0x0386 }, /* GREEK CAPITAL LETTER ALPHA + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER ALPHA WITH TONOS */
+ { 0x0395, 0x0301, 0x0388 }, /* GREEK CAPITAL LETTER EPSILON + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER EPSILON WITH TONOS */
+ { 0x0397, 0x0301, 0x0389 }, /* GREEK CAPITAL LETTER ETA + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER ETA WITH TONOS */
+ { 0x0399, 0x0301, 0x038A }, /* GREEK CAPITAL LETTER IOTA + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER IOTA WITH TONOS */
+ { 0x039F, 0x0301, 0x038C }, /* GREEK CAPITAL LETTER OMICRON + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER OMICRON WITH TONOS */
+ { 0x03A5, 0x0301, 0x038E }, /* GREEK CAPITAL LETTER UPSILON + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER UPSILON WITH TONOS */
+ { 0x03A9, 0x0301, 0x038F }, /* GREEK CAPITAL LETTER OMEGA + COMBINING ACUTE ACCENT = GREEK CAPITAL LETTER OMEGA WITH TONOS */
+ { 0x03B1, 0x0301, 0x03AC }, /* GREEK SMALL LETTER ALPHA + COMBINING ACUTE ACCENT = GREEK SMALL LETTER ALPHA WITH TONOS */
+ { 0x03B5, 0x0301, 0x03AD }, /* GREEK SMALL LETTER EPSILON + COMBINING ACUTE ACCENT = GREEK SMALL LETTER EPSILON WITH TONOS */
+ { 0x03B7, 0x0301, 0x03AE }, /* GREEK SMALL LETTER ETA + COMBINING ACUTE ACCENT = GREEK SMALL LETTER ETA WITH TONOS */
+ { 0x03B9, 0x0301, 0x03AF }, /* GREEK SMALL LETTER IOTA + COMBINING ACUTE ACCENT = GREEK SMALL LETTER IOTA WITH TONOS */
+ { 0x03BF, 0x0301, 0x03CC }, /* GREEK SMALL LETTER OMICRON + COMBINING ACUTE ACCENT = GREEK SMALL LETTER OMICRON WITH TONOS */
+ { 0x03C5, 0x0301, 0x03CD }, /* GREEK SMALL LETTER UPSILON + COMBINING ACUTE ACCENT = GREEK SMALL LETTER UPSILON WITH TONOS */
+ { 0x03C9, 0x0301, 0x03CE }, /* GREEK SMALL LETTER OMEGA + COMBINING ACUTE ACCENT = GREEK SMALL LETTER OMEGA WITH TONOS */
+ { 0x0418, 0x0306, 0x0419 }, /* CYRILLIC CAPITAL LETTER I + COMBINING BREVE = CYRILLIC CAPITAL LETTER SHORT I */
+ { 0x0423, 0x0306, 0x040E }, /* CYRILLIC CAPITAL LETTER U + COMBINING BREVE = CYRILLIC CAPITAL LETTER SHORT U */
+ { 0x0438, 0x0306, 0x0439 }, /* CYRILLIC SMALL LETTER I + COMBINING BREVE = CYRILLIC SMALL LETTER SHORT I */
+ { 0x0443, 0x0306, 0x045E }, /* CYRILLIC SMALL LETTER U + COMBINING BREVE = CYRILLIC SMALL LETTER SHORT U */
+};
+
+/*
+ * Boundary values for quick rejection
+ * These are calculated by analyzing the table during generation
+ */
+#define UCS_RECOMPOSE_MIN_BASE 0x0041
+#define UCS_RECOMPOSE_MAX_BASE 0x0443
+#define UCS_RECOMPOSE_MIN_MARK 0x0300
+#define UCS_RECOMPOSE_MAX_MARK 0x0327
diff --git a/drivers/tty/vt/ucs_width_table.h_shipped b/drivers/tty/vt/ucs_width_table.h_shipped
new file mode 100644
index 000000000000..6fcb8f1d577d
--- /dev/null
+++ b/drivers/tty/vt/ucs_width_table.h_shipped
@@ -0,0 +1,453 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ucs_width_table.h - Unicode character width
+ *
+ * Auto-generated by gen_ucs_width_table.py
+ *
+ * Unicode Version: 16.0.0
+ */
+
+/* Zero-width character ranges (BMP - Basic Multilingual Plane, U+0000 to U+FFFF) */
+static const struct ucs_interval16 ucs_zero_width_bmp_ranges[] = {
+ { 0x00AD, 0x00AD }, /* SOFT HYPHEN */
+ { 0x0300, 0x036F }, /* COMBINING GRAVE ACCENT - COMBINING LATIN SMALL LETTER X */
+ { 0x0483, 0x0489 }, /* COMBINING CYRILLIC TITLO - COMBINING CYRILLIC MILLIONS SIGN */
+ { 0x0591, 0x05BD }, /* HEBREW ACCENT ETNAHTA - HEBREW POINT METEG */
+ { 0x05BF, 0x05BF }, /* HEBREW POINT RAFE */
+ { 0x05C1, 0x05C2 }, /* HEBREW POINT SHIN DOT - HEBREW POINT SIN DOT */
+ { 0x05C4, 0x05C5 }, /* HEBREW MARK UPPER DOT - HEBREW MARK LOWER DOT */
+ { 0x05C7, 0x05C7 }, /* HEBREW POINT QAMATS QATAN */
+ { 0x0600, 0x0605 }, /* ARABIC NUMBER SIGN - ARABIC NUMBER MARK ABOVE */
+ { 0x0610, 0x061A }, /* ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM - ARABIC SMALL KASRA */
+ { 0x061C, 0x061C }, /* ARABIC LETTER MARK */
+ { 0x064B, 0x065F }, /* ARABIC FATHATAN - ARABIC WAVY HAMZA BELOW */
+ { 0x0670, 0x0670 }, /* ARABIC LETTER SUPERSCRIPT ALEF */
+ { 0x06D6, 0x06DD }, /* ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA - ARABIC END OF AYAH */
+ { 0x06DF, 0x06E4 }, /* ARABIC SMALL HIGH ROUNDED ZERO - ARABIC SMALL HIGH MADDA */
+ { 0x06E7, 0x06E8 }, /* ARABIC SMALL HIGH YEH - ARABIC SMALL HIGH NOON */
+ { 0x06EA, 0x06ED }, /* ARABIC EMPTY CENTRE LOW STOP - ARABIC SMALL LOW MEEM */
+ { 0x070F, 0x070F }, /* SYRIAC ABBREVIATION MARK */
+ { 0x0711, 0x0711 }, /* SYRIAC LETTER SUPERSCRIPT ALAPH */
+ { 0x0730, 0x074A }, /* SYRIAC PTHAHA ABOVE - SYRIAC BARREKH */
+ { 0x07A6, 0x07B0 }, /* THAANA ABAFILI - THAANA SUKUN */
+ { 0x07EB, 0x07F3 }, /* NKO COMBINING SHORT HIGH TONE - NKO COMBINING DOUBLE DOT ABOVE */
+ { 0x07FD, 0x07FD }, /* NKO DANTAYALAN */
+ { 0x0816, 0x0819 }, /* SAMARITAN MARK IN - SAMARITAN MARK DAGESH */
+ { 0x081B, 0x0823 }, /* SAMARITAN MARK EPENTHETIC YUT - SAMARITAN VOWEL SIGN A */
+ { 0x0825, 0x0827 }, /* SAMARITAN VOWEL SIGN SHORT A - SAMARITAN VOWEL SIGN U */
+ { 0x0829, 0x082D }, /* SAMARITAN VOWEL SIGN LONG I - SAMARITAN MARK NEQUDAA */
+ { 0x0859, 0x085B }, /* MANDAIC AFFRICATION MARK - MANDAIC GEMINATION MARK */
+ { 0x0890, 0x0891 }, /* ARABIC POUND MARK ABOVE - ARABIC PIASTRE MARK ABOVE */
+ { 0x0897, 0x089F }, /* ARABIC PEPET - ARABIC HALF MADDA OVER MADDA */
+ { 0x08CA, 0x0903 }, /* ARABIC SMALL HIGH FARSI YEH - DEVANAGARI SIGN VISARGA */
+ { 0x093A, 0x093C }, /* DEVANAGARI VOWEL SIGN OE - DEVANAGARI SIGN NUKTA */
+ { 0x093E, 0x094F }, /* DEVANAGARI VOWEL SIGN AA - DEVANAGARI VOWEL SIGN AW */
+ { 0x0951, 0x0957 }, /* DEVANAGARI STRESS SIGN UDATTA - DEVANAGARI VOWEL SIGN UUE */
+ { 0x0962, 0x0963 }, /* DEVANAGARI VOWEL SIGN VOCALIC L - DEVANAGARI VOWEL SIGN VOCALIC LL */
+ { 0x0981, 0x0983 }, /* BENGALI SIGN CANDRABINDU - BENGALI SIGN VISARGA */
+ { 0x09BC, 0x09BC }, /* BENGALI SIGN NUKTA */
+ { 0x09BE, 0x09C4 }, /* BENGALI VOWEL SIGN AA - BENGALI VOWEL SIGN VOCALIC RR */
+ { 0x09C7, 0x09C8 }, /* BENGALI VOWEL SIGN E - BENGALI VOWEL SIGN AI */
+ { 0x09CB, 0x09CD }, /* BENGALI VOWEL SIGN O - BENGALI SIGN VIRAMA */
+ { 0x09D7, 0x09D7 }, /* BENGALI AU LENGTH MARK */
+ { 0x09E2, 0x09E3 }, /* BENGALI VOWEL SIGN VOCALIC L - BENGALI VOWEL SIGN VOCALIC LL */
+ { 0x09FE, 0x09FE }, /* BENGALI SANDHI MARK */
+ { 0x0A01, 0x0A03 }, /* GURMUKHI SIGN ADAK BINDI - GURMUKHI SIGN VISARGA */
+ { 0x0A3C, 0x0A3C }, /* GURMUKHI SIGN NUKTA */
+ { 0x0A3E, 0x0A42 }, /* GURMUKHI VOWEL SIGN AA - GURMUKHI VOWEL SIGN UU */
+ { 0x0A47, 0x0A48 }, /* GURMUKHI VOWEL SIGN EE - GURMUKHI VOWEL SIGN AI */
+ { 0x0A4B, 0x0A4D }, /* GURMUKHI VOWEL SIGN OO - GURMUKHI SIGN VIRAMA */
+ { 0x0A51, 0x0A51 }, /* GURMUKHI SIGN UDAAT */
+ { 0x0A70, 0x0A71 }, /* GURMUKHI TIPPI - GURMUKHI ADDAK */
+ { 0x0A75, 0x0A75 }, /* GURMUKHI SIGN YAKASH */
+ { 0x0A81, 0x0A83 }, /* GUJARATI SIGN CANDRABINDU - GUJARATI SIGN VISARGA */
+ { 0x0ABC, 0x0ABC }, /* GUJARATI SIGN NUKTA */
+ { 0x0ABE, 0x0AC5 }, /* GUJARATI VOWEL SIGN AA - GUJARATI VOWEL SIGN CANDRA E */
+ { 0x0AC7, 0x0AC9 }, /* GUJARATI VOWEL SIGN E - GUJARATI VOWEL SIGN CANDRA O */
+ { 0x0ACB, 0x0ACD }, /* GUJARATI VOWEL SIGN O - GUJARATI SIGN VIRAMA */
+ { 0x0AE2, 0x0AE3 }, /* GUJARATI VOWEL SIGN VOCALIC L - GUJARATI VOWEL SIGN VOCALIC LL */
+ { 0x0AFA, 0x0AFF }, /* GUJARATI SIGN SUKUN - GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE */
+ { 0x0B01, 0x0B03 }, /* ORIYA SIGN CANDRABINDU - ORIYA SIGN VISARGA */
+ { 0x0B3C, 0x0B3C }, /* ORIYA SIGN NUKTA */
+ { 0x0B3E, 0x0B44 }, /* ORIYA VOWEL SIGN AA - ORIYA VOWEL SIGN VOCALIC RR */
+ { 0x0B47, 0x0B48 }, /* ORIYA VOWEL SIGN E - ORIYA VOWEL SIGN AI */
+ { 0x0B4B, 0x0B4D }, /* ORIYA VOWEL SIGN O - ORIYA SIGN VIRAMA */
+ { 0x0B55, 0x0B57 }, /* ORIYA SIGN OVERLINE - ORIYA AU LENGTH MARK */
+ { 0x0B62, 0x0B63 }, /* ORIYA VOWEL SIGN VOCALIC L - ORIYA VOWEL SIGN VOCALIC LL */
+ { 0x0B82, 0x0B82 }, /* TAMIL SIGN ANUSVARA */
+ { 0x0BBE, 0x0BC2 }, /* TAMIL VOWEL SIGN AA - TAMIL VOWEL SIGN UU */
+ { 0x0BC6, 0x0BC8 }, /* TAMIL VOWEL SIGN E - TAMIL VOWEL SIGN AI */
+ { 0x0BCA, 0x0BCD }, /* TAMIL VOWEL SIGN O - TAMIL SIGN VIRAMA */
+ { 0x0BD7, 0x0BD7 }, /* TAMIL AU LENGTH MARK */
+ { 0x0C00, 0x0C04 }, /* TELUGU SIGN COMBINING CANDRABINDU ABOVE - TELUGU SIGN COMBINING ANUSVARA ABOVE */
+ { 0x0C3C, 0x0C3C }, /* TELUGU SIGN NUKTA */
+ { 0x0C3E, 0x0C44 }, /* TELUGU VOWEL SIGN AA - TELUGU VOWEL SIGN VOCALIC RR */
+ { 0x0C46, 0x0C48 }, /* TELUGU VOWEL SIGN E - TELUGU VOWEL SIGN AI */
+ { 0x0C4A, 0x0C4D }, /* TELUGU VOWEL SIGN O - TELUGU SIGN VIRAMA */
+ { 0x0C55, 0x0C56 }, /* TELUGU LENGTH MARK - TELUGU AI LENGTH MARK */
+ { 0x0C62, 0x0C63 }, /* TELUGU VOWEL SIGN VOCALIC L - TELUGU VOWEL SIGN VOCALIC LL */
+ { 0x0C81, 0x0C83 }, /* KANNADA SIGN CANDRABINDU - KANNADA SIGN VISARGA */
+ { 0x0CBC, 0x0CBC }, /* KANNADA SIGN NUKTA */
+ { 0x0CBE, 0x0CC4 }, /* KANNADA VOWEL SIGN AA - KANNADA VOWEL SIGN VOCALIC RR */
+ { 0x0CC6, 0x0CC8 }, /* KANNADA VOWEL SIGN E - KANNADA VOWEL SIGN AI */
+ { 0x0CCA, 0x0CCD }, /* KANNADA VOWEL SIGN O - KANNADA SIGN VIRAMA */
+ { 0x0CD5, 0x0CD6 }, /* KANNADA LENGTH MARK - KANNADA AI LENGTH MARK */
+ { 0x0CE2, 0x0CE3 }, /* KANNADA VOWEL SIGN VOCALIC L - KANNADA VOWEL SIGN VOCALIC LL */
+ { 0x0CF3, 0x0CF3 }, /* KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT */
+ { 0x0D00, 0x0D03 }, /* MALAYALAM SIGN COMBINING ANUSVARA ABOVE - MALAYALAM SIGN VISARGA */
+ { 0x0D3B, 0x0D3C }, /* MALAYALAM SIGN VERTICAL BAR VIRAMA - MALAYALAM SIGN CIRCULAR VIRAMA */
+ { 0x0D3E, 0x0D44 }, /* MALAYALAM VOWEL SIGN AA - MALAYALAM VOWEL SIGN VOCALIC RR */
+ { 0x0D46, 0x0D48 }, /* MALAYALAM VOWEL SIGN E - MALAYALAM VOWEL SIGN AI */
+ { 0x0D4A, 0x0D4D }, /* MALAYALAM VOWEL SIGN O - MALAYALAM SIGN VIRAMA */
+ { 0x0D57, 0x0D57 }, /* MALAYALAM AU LENGTH MARK */
+ { 0x0D62, 0x0D63 }, /* MALAYALAM VOWEL SIGN VOCALIC L - MALAYALAM VOWEL SIGN VOCALIC LL */
+ { 0x0D81, 0x0D83 }, /* SINHALA SIGN CANDRABINDU - SINHALA SIGN VISARGAYA */
+ { 0x0DCA, 0x0DCA }, /* SINHALA SIGN AL-LAKUNA */
+ { 0x0DCF, 0x0DD4 }, /* SINHALA VOWEL SIGN AELA-PILLA - SINHALA VOWEL SIGN KETTI PAA-PILLA */
+ { 0x0DD6, 0x0DD6 }, /* SINHALA VOWEL SIGN DIGA PAA-PILLA */
+ { 0x0DD8, 0x0DDF }, /* SINHALA VOWEL SIGN GAETTA-PILLA - SINHALA VOWEL SIGN GAYANUKITTA */
+ { 0x0DF2, 0x0DF3 }, /* SINHALA VOWEL SIGN DIGA GAETTA-PILLA - SINHALA VOWEL SIGN DIGA GAYANUKITTA */
+ { 0x0E31, 0x0E31 }, /* THAI CHARACTER MAI HAN-AKAT */
+ { 0x0E34, 0x0E3A }, /* THAI CHARACTER SARA I - THAI CHARACTER PHINTHU */
+ { 0x0E47, 0x0E4E }, /* THAI CHARACTER MAITAIKHU - THAI CHARACTER YAMAKKAN */
+ { 0x0EB1, 0x0EB1 }, /* LAO VOWEL SIGN MAI KAN */
+ { 0x0EB4, 0x0EBC }, /* LAO VOWEL SIGN I - LAO SEMIVOWEL SIGN LO */
+ { 0x0EC8, 0x0ECE }, /* LAO TONE MAI EK - LAO YAMAKKAN */
+ { 0x0F18, 0x0F19 }, /* TIBETAN ASTROLOGICAL SIGN -KHYUD PA - TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS */
+ { 0x0F35, 0x0F35 }, /* TIBETAN MARK NGAS BZUNG NYI ZLA */
+ { 0x0F37, 0x0F37 }, /* TIBETAN MARK NGAS BZUNG SGOR RTAGS */
+ { 0x0F39, 0x0F39 }, /* TIBETAN MARK TSA -PHRU */
+ { 0x0F3E, 0x0F3F }, /* TIBETAN SIGN YAR TSHES - TIBETAN SIGN MAR TSHES */
+ { 0x0F71, 0x0F84 }, /* TIBETAN VOWEL SIGN AA - TIBETAN MARK HALANTA */
+ { 0x0F86, 0x0F87 }, /* TIBETAN SIGN LCI RTAGS - TIBETAN SIGN YANG RTAGS */
+ { 0x0F8D, 0x0F97 }, /* TIBETAN SUBJOINED SIGN LCE TSA CAN - TIBETAN SUBJOINED LETTER JA */
+ { 0x0F99, 0x0FBC }, /* TIBETAN SUBJOINED LETTER NYA - TIBETAN SUBJOINED LETTER FIXED-FORM RA */
+ { 0x0FC6, 0x0FC6 }, /* TIBETAN SYMBOL PADMA GDAN */
+ { 0x102B, 0x103E }, /* MYANMAR VOWEL SIGN TALL AA - MYANMAR CONSONANT SIGN MEDIAL HA */
+ { 0x1056, 0x1059 }, /* MYANMAR VOWEL SIGN VOCALIC R - MYANMAR VOWEL SIGN VOCALIC LL */
+ { 0x105E, 0x1060 }, /* MYANMAR CONSONANT SIGN MON MEDIAL NA - MYANMAR CONSONANT SIGN MON MEDIAL LA */
+ { 0x1062, 0x1064 }, /* MYANMAR VOWEL SIGN SGAW KAREN EU - MYANMAR TONE MARK SGAW KAREN KE PHO */
+ { 0x1067, 0x106D }, /* MYANMAR VOWEL SIGN WESTERN PWO KAREN EU - MYANMAR SIGN WESTERN PWO KAREN TONE-5 */
+ { 0x1071, 0x1074 }, /* MYANMAR VOWEL SIGN GEBA KAREN I - MYANMAR VOWEL SIGN KAYAH EE */
+ { 0x1082, 0x108D }, /* MYANMAR CONSONANT SIGN SHAN MEDIAL WA - MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE */
+ { 0x108F, 0x108F }, /* MYANMAR SIGN RUMAI PALAUNG TONE-5 */
+ { 0x109A, 0x109D }, /* MYANMAR SIGN KHAMTI TONE-1 - MYANMAR VOWEL SIGN AITON AI */
+ { 0x135D, 0x135F }, /* ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK - ETHIOPIC COMBINING GEMINATION MARK */
+ { 0x1712, 0x1715 }, /* TAGALOG VOWEL SIGN I - TAGALOG SIGN PAMUDPOD */
+ { 0x1732, 0x1734 }, /* HANUNOO VOWEL SIGN I - HANUNOO SIGN PAMUDPOD */
+ { 0x1752, 0x1753 }, /* BUHID VOWEL SIGN I - BUHID VOWEL SIGN U */
+ { 0x1772, 0x1773 }, /* TAGBANWA VOWEL SIGN I - TAGBANWA VOWEL SIGN U */
+ { 0x17B4, 0x17D3 }, /* KHMER VOWEL INHERENT AQ - KHMER SIGN BATHAMASAT */
+ { 0x17DD, 0x17DD }, /* KHMER SIGN ATTHACAN */
+ { 0x180B, 0x180F }, /* MONGOLIAN FREE VARIATION SELECTOR ONE - MONGOLIAN FREE VARIATION SELECTOR FOUR */
+ { 0x1885, 0x1886 }, /* MONGOLIAN LETTER ALI GALI BALUDA - MONGOLIAN LETTER ALI GALI THREE BALUDA */
+ { 0x18A9, 0x18A9 }, /* MONGOLIAN LETTER ALI GALI DAGALGA */
+ { 0x1920, 0x192B }, /* LIMBU VOWEL SIGN A - LIMBU SUBJOINED LETTER WA */
+ { 0x1930, 0x193B }, /* LIMBU SMALL LETTER KA - LIMBU SIGN SA-I */
+ { 0x1A17, 0x1A1B }, /* BUGINESE VOWEL SIGN I - BUGINESE VOWEL SIGN AE */
+ { 0x1A55, 0x1A5E }, /* TAI THAM CONSONANT SIGN MEDIAL RA - TAI THAM CONSONANT SIGN SA */
+ { 0x1A60, 0x1A7C }, /* TAI THAM SIGN SAKOT - TAI THAM SIGN KHUEN-LUE KARAN */
+ { 0x1A7F, 0x1A7F }, /* TAI THAM COMBINING CRYPTOGRAMMIC DOT */
+ { 0x1AB0, 0x1ACE }, /* COMBINING DOUBLED CIRCUMFLEX ACCENT - COMBINING LATIN SMALL LETTER INSULAR T */
+ { 0x1B00, 0x1B04 }, /* BALINESE SIGN ULU RICEM - BALINESE SIGN BISAH */
+ { 0x1B34, 0x1B44 }, /* BALINESE SIGN REREKAN - BALINESE ADEG ADEG */
+ { 0x1B6B, 0x1B73 }, /* BALINESE MUSICAL SYMBOL COMBINING TEGEH - BALINESE MUSICAL SYMBOL COMBINING GONG */
+ { 0x1B80, 0x1B82 }, /* SUNDANESE SIGN PANYECEK - SUNDANESE SIGN PANGWISAD */
+ { 0x1BA1, 0x1BAD }, /* SUNDANESE CONSONANT SIGN PAMINGKAL - SUNDANESE CONSONANT SIGN PASANGAN WA */
+ { 0x1BE6, 0x1BF3 }, /* BATAK SIGN TOMPI - BATAK PANONGONAN */
+ { 0x1C24, 0x1C37 }, /* LEPCHA SUBJOINED LETTER YA - LEPCHA SIGN NUKTA */
+ { 0x1CD0, 0x1CD2 }, /* VEDIC TONE KARSHANA - VEDIC TONE PRENKHA */
+ { 0x1CD4, 0x1CE8 }, /* VEDIC SIGN YAJURVEDIC MIDLINE SVARITA - VEDIC SIGN VISARGA ANUDATTA WITH TAIL */
+ { 0x1CED, 0x1CED }, /* VEDIC SIGN TIRYAK */
+ { 0x1CF4, 0x1CF4 }, /* VEDIC TONE CANDRA ABOVE */
+ { 0x1CF7, 0x1CF9 }, /* VEDIC SIGN ATIKRAMA - VEDIC TONE DOUBLE RING ABOVE */
+ { 0x1DC0, 0x1DFF }, /* COMBINING DOTTED GRAVE ACCENT - COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW */
+ { 0x200B, 0x200F }, /* ZERO WIDTH SPACE - RIGHT-TO-LEFT MARK */
+ { 0x202A, 0x202E }, /* LEFT-TO-RIGHT EMBEDDING - RIGHT-TO-LEFT OVERRIDE */
+ { 0x2060, 0x2064 }, /* WORD JOINER - INVISIBLE PLUS */
+ { 0x2066, 0x206F }, /* LEFT-TO-RIGHT ISOLATE - NOMINAL DIGIT SHAPES */
+ { 0x20D0, 0x20F0 }, /* COMBINING LEFT HARPOON ABOVE - COMBINING ASTERISK ABOVE */
+ { 0x2640, 0x2640 }, /* FEMALE SIGN */
+ { 0x2642, 0x2642 }, /* MALE SIGN */
+ { 0x26A7, 0x26A7 }, /* MALE WITH STROKE AND MALE AND FEMALE SIGN */
+ { 0x2CEF, 0x2CF1 }, /* COPTIC COMBINING NI ABOVE - COPTIC COMBINING SPIRITUS LENIS */
+ { 0x2D7F, 0x2D7F }, /* TIFINAGH CONSONANT JOINER */
+ { 0x2DE0, 0x2DFF }, /* COMBINING CYRILLIC LETTER BE - COMBINING CYRILLIC LETTER IOTIFIED BIG YUS */
+ { 0x302A, 0x302F }, /* IDEOGRAPHIC LEVEL TONE MARK - HANGUL DOUBLE DOT TONE MARK */
+ { 0x3099, 0x309A }, /* COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK - COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
+ { 0xA66F, 0xA672 }, /* COMBINING CYRILLIC VZMET - COMBINING CYRILLIC THOUSAND MILLIONS SIGN */
+ { 0xA674, 0xA67D }, /* COMBINING CYRILLIC LETTER UKRAINIAN IE - COMBINING CYRILLIC PAYEROK */
+ { 0xA69E, 0xA69F }, /* COMBINING CYRILLIC LETTER EF - COMBINING CYRILLIC LETTER IOTIFIED E */
+ { 0xA6F0, 0xA6F1 }, /* BAMUM COMBINING MARK KOQNDON - BAMUM COMBINING MARK TUKWENTIS */
+ { 0xA802, 0xA802 }, /* SYLOTI NAGRI SIGN DVISVARA */
+ { 0xA806, 0xA806 }, /* SYLOTI NAGRI SIGN HASANTA */
+ { 0xA80B, 0xA80B }, /* SYLOTI NAGRI SIGN ANUSVARA */
+ { 0xA823, 0xA827 }, /* SYLOTI NAGRI VOWEL SIGN A - SYLOTI NAGRI VOWEL SIGN OO */
+ { 0xA82C, 0xA82C }, /* SYLOTI NAGRI SIGN ALTERNATE HASANTA */
+ { 0xA880, 0xA881 }, /* SAURASHTRA SIGN ANUSVARA - SAURASHTRA SIGN VISARGA */
+ { 0xA8B4, 0xA8C5 }, /* SAURASHTRA CONSONANT SIGN HAARU - SAURASHTRA SIGN CANDRABINDU */
+ { 0xA8E0, 0xA8F1 }, /* COMBINING DEVANAGARI DIGIT ZERO - COMBINING DEVANAGARI SIGN AVAGRAHA */
+ { 0xA8FF, 0xA8FF }, /* DEVANAGARI VOWEL SIGN AY */
+ { 0xA926, 0xA92D }, /* KAYAH LI VOWEL UE - KAYAH LI TONE CALYA PLOPHU */
+ { 0xA947, 0xA953 }, /* REJANG VOWEL SIGN I - REJANG VIRAMA */
+ { 0xA980, 0xA983 }, /* JAVANESE SIGN PANYANGGA - JAVANESE SIGN WIGNYAN */
+ { 0xA9B3, 0xA9C0 }, /* JAVANESE SIGN CECAK TELU - JAVANESE PANGKON */
+ { 0xA9E5, 0xA9E5 }, /* MYANMAR SIGN SHAN SAW */
+ { 0xAA29, 0xAA36 }, /* CHAM VOWEL SIGN AA - CHAM CONSONANT SIGN WA */
+ { 0xAA43, 0xAA43 }, /* CHAM CONSONANT SIGN FINAL NG */
+ { 0xAA4C, 0xAA4D }, /* CHAM CONSONANT SIGN FINAL M - CHAM CONSONANT SIGN FINAL H */
+ { 0xAA7B, 0xAA7D }, /* MYANMAR SIGN PAO KAREN TONE - MYANMAR SIGN TAI LAING TONE-5 */
+ { 0xAAB0, 0xAAB0 }, /* TAI VIET MAI KANG */
+ { 0xAAB2, 0xAAB4 }, /* TAI VIET VOWEL I - TAI VIET VOWEL U */
+ { 0xAAB7, 0xAAB8 }, /* TAI VIET MAI KHIT - TAI VIET VOWEL IA */
+ { 0xAABE, 0xAABF }, /* TAI VIET VOWEL AM - TAI VIET TONE MAI EK */
+ { 0xAAC1, 0xAAC1 }, /* TAI VIET TONE MAI THO */
+ { 0xAAEB, 0xAAEF }, /* MEETEI MAYEK VOWEL SIGN II - MEETEI MAYEK VOWEL SIGN AAU */
+ { 0xAAF5, 0xAAF6 }, /* MEETEI MAYEK VOWEL SIGN VISARGA - MEETEI MAYEK VIRAMA */
+ { 0xABE3, 0xABEA }, /* MEETEI MAYEK VOWEL SIGN ONAP - MEETEI MAYEK VOWEL SIGN NUNG */
+ { 0xABEC, 0xABED }, /* MEETEI MAYEK LUM IYEK - MEETEI MAYEK APUN IYEK */
+ { 0xFB1E, 0xFB1E }, /* HEBREW POINT JUDEO-SPANISH VARIKA */
+ { 0xFE00, 0xFE0F }, /* VARIATION SELECTOR-1 - VARIATION SELECTOR-16 */
+ { 0xFE20, 0xFE2F }, /* COMBINING LIGATURE LEFT HALF - COMBINING CYRILLIC TITLO RIGHT HALF */
+ { 0xFEFF, 0xFEFF }, /* ZERO WIDTH NO-BREAK SPACE */
+ { 0xFFF9, 0xFFFB }, /* INTERLINEAR ANNOTATION ANCHOR - INTERLINEAR ANNOTATION TERMINATOR */
+};
+
+/* Zero-width character ranges (non-BMP, U+10000 and above) */
+static const struct ucs_interval32 ucs_zero_width_non_bmp_ranges[] = {
+ { 0x101FD, 0x101FD }, /* PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE */
+ { 0x102E0, 0x102E0 }, /* COPTIC EPACT THOUSANDS MARK */
+ { 0x10376, 0x1037A }, /* COMBINING OLD PERMIC LETTER AN - COMBINING OLD PERMIC LETTER SII */
+ { 0x10A01, 0x10A03 }, /* KHAROSHTHI VOWEL SIGN I - KHAROSHTHI VOWEL SIGN VOCALIC R */
+ { 0x10A05, 0x10A06 }, /* KHAROSHTHI VOWEL SIGN E - KHAROSHTHI VOWEL SIGN O */
+ { 0x10A0C, 0x10A0F }, /* KHAROSHTHI VOWEL LENGTH MARK - KHAROSHTHI SIGN VISARGA */
+ { 0x10A38, 0x10A3A }, /* KHAROSHTHI SIGN BAR ABOVE - KHAROSHTHI SIGN DOT BELOW */
+ { 0x10A3F, 0x10A3F }, /* KHAROSHTHI VIRAMA */
+ { 0x10AE5, 0x10AE6 }, /* MANICHAEAN ABBREVIATION MARK ABOVE - MANICHAEAN ABBREVIATION MARK BELOW */
+ { 0x10D24, 0x10D27 }, /* HANIFI ROHINGYA SIGN HARBAHAY - HANIFI ROHINGYA SIGN TASSI */
+ { 0x10D69, 0x10D6D }, /* GARAY VOWEL SIGN E - GARAY CONSONANT NASALIZATION MARK */
+ { 0x10EAB, 0x10EAC }, /* YEZIDI COMBINING HAMZA MARK - YEZIDI COMBINING MADDA MARK */
+ { 0x10EFC, 0x10EFF }, /* ARABIC COMBINING ALEF OVERLAY - ARABIC SMALL LOW WORD MADDA */
+ { 0x10F46, 0x10F50 }, /* SOGDIAN COMBINING DOT BELOW - SOGDIAN COMBINING STROKE BELOW */
+ { 0x10F82, 0x10F85 }, /* OLD UYGHUR COMBINING DOT ABOVE - OLD UYGHUR COMBINING TWO DOTS BELOW */
+ { 0x11000, 0x11002 }, /* BRAHMI SIGN CANDRABINDU - BRAHMI SIGN VISARGA */
+ { 0x11038, 0x11046 }, /* BRAHMI VOWEL SIGN AA - BRAHMI VIRAMA */
+ { 0x11070, 0x11070 }, /* BRAHMI SIGN OLD TAMIL VIRAMA */
+ { 0x11073, 0x11074 }, /* BRAHMI VOWEL SIGN OLD TAMIL SHORT E - BRAHMI VOWEL SIGN OLD TAMIL SHORT O */
+ { 0x1107F, 0x11082 }, /* BRAHMI NUMBER JOINER - KAITHI SIGN VISARGA */
+ { 0x110B0, 0x110BA }, /* KAITHI VOWEL SIGN AA - KAITHI SIGN NUKTA */
+ { 0x110BD, 0x110BD }, /* KAITHI NUMBER SIGN */
+ { 0x110C2, 0x110C2 }, /* KAITHI VOWEL SIGN VOCALIC R */
+ { 0x110CD, 0x110CD }, /* KAITHI NUMBER SIGN ABOVE */
+ { 0x11100, 0x11102 }, /* CHAKMA SIGN CANDRABINDU - CHAKMA SIGN VISARGA */
+ { 0x11127, 0x11134 }, /* CHAKMA VOWEL SIGN A - CHAKMA MAAYYAA */
+ { 0x11145, 0x11146 }, /* CHAKMA VOWEL SIGN AA - CHAKMA VOWEL SIGN EI */
+ { 0x11173, 0x11173 }, /* MAHAJANI SIGN NUKTA */
+ { 0x11180, 0x11182 }, /* SHARADA SIGN CANDRABINDU - SHARADA SIGN VISARGA */
+ { 0x111B3, 0x111C0 }, /* SHARADA VOWEL SIGN AA - SHARADA SIGN VIRAMA */
+ { 0x111C9, 0x111CC }, /* SHARADA SANDHI MARK - SHARADA EXTRA SHORT VOWEL MARK */
+ { 0x111CE, 0x111CF }, /* SHARADA VOWEL SIGN PRISHTHAMATRA E - SHARADA SIGN INVERTED CANDRABINDU */
+ { 0x1122C, 0x11237 }, /* KHOJKI VOWEL SIGN AA - KHOJKI SIGN SHADDA */
+ { 0x1123E, 0x1123E }, /* KHOJKI SIGN SUKUN */
+ { 0x11241, 0x11241 }, /* KHOJKI VOWEL SIGN VOCALIC R */
+ { 0x112DF, 0x112EA }, /* KHUDAWADI SIGN ANUSVARA - KHUDAWADI SIGN VIRAMA */
+ { 0x11300, 0x11303 }, /* GRANTHA SIGN COMBINING ANUSVARA ABOVE - GRANTHA SIGN VISARGA */
+ { 0x1133B, 0x1133C }, /* COMBINING BINDU BELOW - GRANTHA SIGN NUKTA */
+ { 0x1133E, 0x11344 }, /* GRANTHA VOWEL SIGN AA - GRANTHA VOWEL SIGN VOCALIC RR */
+ { 0x11347, 0x11348 }, /* GRANTHA VOWEL SIGN EE - GRANTHA VOWEL SIGN AI */
+ { 0x1134B, 0x1134D }, /* GRANTHA VOWEL SIGN OO - GRANTHA SIGN VIRAMA */
+ { 0x11357, 0x11357 }, /* GRANTHA AU LENGTH MARK */
+ { 0x11362, 0x11363 }, /* GRANTHA VOWEL SIGN VOCALIC L - GRANTHA VOWEL SIGN VOCALIC LL */
+ { 0x11366, 0x1136C }, /* COMBINING GRANTHA DIGIT ZERO - COMBINING GRANTHA DIGIT SIX */
+ { 0x11370, 0x11374 }, /* COMBINING GRANTHA LETTER A - COMBINING GRANTHA LETTER PA */
+ { 0x113B8, 0x113C0 }, /* TULU-TIGALARI VOWEL SIGN AA - TULU-TIGALARI VOWEL SIGN VOCALIC LL */
+ { 0x113C2, 0x113C2 }, /* TULU-TIGALARI VOWEL SIGN EE */
+ { 0x113C5, 0x113C5 }, /* TULU-TIGALARI VOWEL SIGN AI */
+ { 0x113C7, 0x113CA }, /* TULU-TIGALARI VOWEL SIGN OO - TULU-TIGALARI SIGN CANDRA ANUNASIKA */
+ { 0x113CC, 0x113D0 }, /* TULU-TIGALARI SIGN ANUSVARA - TULU-TIGALARI CONJOINER */
+ { 0x113D2, 0x113D2 }, /* TULU-TIGALARI GEMINATION MARK */
+ { 0x113E1, 0x113E2 }, /* TULU-TIGALARI VEDIC TONE SVARITA - TULU-TIGALARI VEDIC TONE ANUDATTA */
+ { 0x11435, 0x11446 }, /* NEWA VOWEL SIGN AA - NEWA SIGN NUKTA */
+ { 0x1145E, 0x1145E }, /* NEWA SANDHI MARK */
+ { 0x114B0, 0x114C3 }, /* TIRHUTA VOWEL SIGN AA - TIRHUTA SIGN NUKTA */
+ { 0x115AF, 0x115B5 }, /* SIDDHAM VOWEL SIGN AA - SIDDHAM VOWEL SIGN VOCALIC RR */
+ { 0x115B8, 0x115C0 }, /* SIDDHAM VOWEL SIGN E - SIDDHAM SIGN NUKTA */
+ { 0x115DC, 0x115DD }, /* SIDDHAM VOWEL SIGN ALTERNATE U - SIDDHAM VOWEL SIGN ALTERNATE UU */
+ { 0x11630, 0x11640 }, /* MODI VOWEL SIGN AA - MODI SIGN ARDHACANDRA */
+ { 0x116AB, 0x116B7 }, /* TAKRI SIGN ANUSVARA - TAKRI SIGN NUKTA */
+ { 0x1171D, 0x1172B }, /* AHOM CONSONANT SIGN MEDIAL LA - AHOM SIGN KILLER */
+ { 0x1182C, 0x1183A }, /* DOGRA VOWEL SIGN AA - DOGRA SIGN NUKTA */
+ { 0x11930, 0x11935 }, /* DIVES AKURU VOWEL SIGN AA - DIVES AKURU VOWEL SIGN E */
+ { 0x11937, 0x11938 }, /* DIVES AKURU VOWEL SIGN AI - DIVES AKURU VOWEL SIGN O */
+ { 0x1193B, 0x1193E }, /* DIVES AKURU SIGN ANUSVARA - DIVES AKURU VIRAMA */
+ { 0x11940, 0x11940 }, /* DIVES AKURU MEDIAL YA */
+ { 0x11942, 0x11943 }, /* DIVES AKURU MEDIAL RA - DIVES AKURU SIGN NUKTA */
+ { 0x119D1, 0x119D7 }, /* NANDINAGARI VOWEL SIGN AA - NANDINAGARI VOWEL SIGN VOCALIC RR */
+ { 0x119DA, 0x119E0 }, /* NANDINAGARI VOWEL SIGN E - NANDINAGARI SIGN VIRAMA */
+ { 0x119E4, 0x119E4 }, /* NANDINAGARI VOWEL SIGN PRISHTHAMATRA E */
+ { 0x11A01, 0x11A0A }, /* ZANABAZAR SQUARE VOWEL SIGN I - ZANABAZAR SQUARE VOWEL LENGTH MARK */
+ { 0x11A33, 0x11A39 }, /* ZANABAZAR SQUARE FINAL CONSONANT MARK - ZANABAZAR SQUARE SIGN VISARGA */
+ { 0x11A3B, 0x11A3E }, /* ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA - ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA */
+ { 0x11A47, 0x11A47 }, /* ZANABAZAR SQUARE SUBJOINER */
+ { 0x11A51, 0x11A5B }, /* SOYOMBO VOWEL SIGN I - SOYOMBO VOWEL LENGTH MARK */
+ { 0x11A8A, 0x11A99 }, /* SOYOMBO FINAL CONSONANT SIGN G - SOYOMBO SUBJOINER */
+ { 0x11C2F, 0x11C36 }, /* BHAIKSUKI VOWEL SIGN AA - BHAIKSUKI VOWEL SIGN VOCALIC L */
+ { 0x11C38, 0x11C3F }, /* BHAIKSUKI VOWEL SIGN E - BHAIKSUKI SIGN VIRAMA */
+ { 0x11C92, 0x11CA7 }, /* MARCHEN SUBJOINED LETTER KA - MARCHEN SUBJOINED LETTER ZA */
+ { 0x11CA9, 0x11CB6 }, /* MARCHEN SUBJOINED LETTER YA - MARCHEN SIGN CANDRABINDU */
+ { 0x11D31, 0x11D36 }, /* MASARAM GONDI VOWEL SIGN AA - MASARAM GONDI VOWEL SIGN VOCALIC R */
+ { 0x11D3A, 0x11D3A }, /* MASARAM GONDI VOWEL SIGN E */
+ { 0x11D3C, 0x11D3D }, /* MASARAM GONDI VOWEL SIGN AI - MASARAM GONDI VOWEL SIGN O */
+ { 0x11D3F, 0x11D45 }, /* MASARAM GONDI VOWEL SIGN AU - MASARAM GONDI VIRAMA */
+ { 0x11D47, 0x11D47 }, /* MASARAM GONDI RA-KARA */
+ { 0x11D8A, 0x11D8E }, /* GUNJALA GONDI VOWEL SIGN AA - GUNJALA GONDI VOWEL SIGN UU */
+ { 0x11D90, 0x11D91 }, /* GUNJALA GONDI VOWEL SIGN EE - GUNJALA GONDI VOWEL SIGN AI */
+ { 0x11D93, 0x11D97 }, /* GUNJALA GONDI VOWEL SIGN OO - GUNJALA GONDI VIRAMA */
+ { 0x11EF3, 0x11EF6 }, /* MAKASAR VOWEL SIGN I - MAKASAR VOWEL SIGN O */
+ { 0x11F00, 0x11F01 }, /* KAWI SIGN CANDRABINDU - KAWI SIGN ANUSVARA */
+ { 0x11F03, 0x11F03 }, /* KAWI SIGN VISARGA */
+ { 0x11F34, 0x11F3A }, /* KAWI VOWEL SIGN AA - KAWI VOWEL SIGN VOCALIC R */
+ { 0x11F3E, 0x11F42 }, /* KAWI VOWEL SIGN E - KAWI CONJOINER */
+ { 0x11F5A, 0x11F5A }, /* KAWI SIGN NUKTA */
+ { 0x13430, 0x13440 }, /* EGYPTIAN HIEROGLYPH VERTICAL JOINER - EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY */
+ { 0x13447, 0x13455 }, /* EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START - EGYPTIAN HIEROGLYPH MODIFIER DAMAGED */
+ { 0x1611E, 0x1612F }, /* GURUNG KHEMA VOWEL SIGN AA - GURUNG KHEMA SIGN THOLHOMA */
+ { 0x16AF0, 0x16AF4 }, /* BASSA VAH COMBINING HIGH TONE - BASSA VAH COMBINING HIGH-LOW TONE */
+ { 0x16B30, 0x16B36 }, /* PAHAWH HMONG MARK CIM TUB - PAHAWH HMONG MARK CIM TAUM */
+ { 0x16F4F, 0x16F4F }, /* MIAO SIGN CONSONANT MODIFIER BAR */
+ { 0x16F51, 0x16F87 }, /* MIAO SIGN ASPIRATION - MIAO VOWEL SIGN UI */
+ { 0x16F8F, 0x16F92 }, /* MIAO TONE RIGHT - MIAO TONE BELOW */
+ { 0x16FE4, 0x16FE4 }, /* KHITAN SMALL SCRIPT FILLER */
+ { 0x16FF0, 0x16FF1 }, /* VIETNAMESE ALTERNATE READING MARK CA - VIETNAMESE ALTERNATE READING MARK NHAY */
+ { 0x1BC9D, 0x1BC9E }, /* DUPLOYAN THICK LETTER SELECTOR - DUPLOYAN DOUBLE MARK */
+ { 0x1BCA0, 0x1BCA3 }, /* SHORTHAND FORMAT LETTER OVERLAP - SHORTHAND FORMAT UP STEP */
+ { 0x1CF00, 0x1CF2D }, /* ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT - ZNAMENNY COMBINING MARK KRYZH ON LEFT */
+ { 0x1CF30, 0x1CF46 }, /* ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO - ZNAMENNY PRIZNAK MODIFIER ROG */
+ { 0x1D165, 0x1D169 }, /* MUSICAL SYMBOL COMBINING STEM - MUSICAL SYMBOL COMBINING TREMOLO-3 */
+ { 0x1D16D, 0x1D182 }, /* MUSICAL SYMBOL COMBINING AUGMENTATION DOT - MUSICAL SYMBOL COMBINING LOURE */
+ { 0x1D185, 0x1D18B }, /* MUSICAL SYMBOL COMBINING DOIT - MUSICAL SYMBOL COMBINING TRIPLE TONGUE */
+ { 0x1D1AA, 0x1D1AD }, /* MUSICAL SYMBOL COMBINING DOWN BOW - MUSICAL SYMBOL COMBINING SNAP PIZZICATO */
+ { 0x1D242, 0x1D244 }, /* COMBINING GREEK MUSICAL TRISEME - COMBINING GREEK MUSICAL PENTASEME */
+ { 0x1DA00, 0x1DA36 }, /* SIGNWRITING HEAD RIM - SIGNWRITING AIR SUCKING IN */
+ { 0x1DA3B, 0x1DA6C }, /* SIGNWRITING MOUTH CLOSED NEUTRAL - SIGNWRITING EXCITEMENT */
+ { 0x1DA75, 0x1DA75 }, /* SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS */
+ { 0x1DA84, 0x1DA84 }, /* SIGNWRITING LOCATION HEAD NECK */
+ { 0x1DA9B, 0x1DA9F }, /* SIGNWRITING FILL MODIFIER-2 - SIGNWRITING FILL MODIFIER-6 */
+ { 0x1DAA1, 0x1DAAF }, /* SIGNWRITING ROTATION MODIFIER-2 - SIGNWRITING ROTATION MODIFIER-16 */
+ { 0x1E000, 0x1E006 }, /* COMBINING GLAGOLITIC LETTER AZU - COMBINING GLAGOLITIC LETTER ZHIVETE */
+ { 0x1E008, 0x1E018 }, /* COMBINING GLAGOLITIC LETTER ZEMLJA - COMBINING GLAGOLITIC LETTER HERU */
+ { 0x1E01B, 0x1E021 }, /* COMBINING GLAGOLITIC LETTER SHTA - COMBINING GLAGOLITIC LETTER YATI */
+ { 0x1E023, 0x1E024 }, /* COMBINING GLAGOLITIC LETTER YU - COMBINING GLAGOLITIC LETTER SMALL YUS */
+ { 0x1E026, 0x1E02A }, /* COMBINING GLAGOLITIC LETTER YO - COMBINING GLAGOLITIC LETTER FITA */
+ { 0x1E08F, 0x1E08F }, /* COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */
+ { 0x1E130, 0x1E136 }, /* NYIAKENG PUACHUE HMONG TONE-B - NYIAKENG PUACHUE HMONG TONE-D */
+ { 0x1E2AE, 0x1E2AE }, /* TOTO SIGN RISING TONE */
+ { 0x1E2EC, 0x1E2EF }, /* WANCHO TONE TUP - WANCHO TONE KOINI */
+ { 0x1E4EC, 0x1E4EF }, /* NAG MUNDARI SIGN MUHOR - NAG MUNDARI SIGN SUTUH */
+ { 0x1E5EE, 0x1E5EF }, /* OL ONAL SIGN MU - OL ONAL SIGN IKIR */
+ { 0x1E8D0, 0x1E8D6 }, /* MENDE KIKAKUI COMBINING NUMBER TEENS - MENDE KIKAKUI COMBINING NUMBER MILLIONS */
+ { 0x1E944, 0x1E94A }, /* ADLAM ALIF LENGTHENER - ADLAM NUKTA */
+ { 0x1F3FB, 0x1F3FF }, /* EMOJI MODIFIER FITZPATRICK TYPE-1-2 - EMOJI MODIFIER FITZPATRICK TYPE-6 */
+ { 0x1F9B0, 0x1F9B3 }, /* EMOJI COMPONENT RED HAIR - EMOJI COMPONENT WHITE HAIR */
+ { 0xE0001, 0xE0001 }, /* LANGUAGE TAG */
+ { 0xE0020, 0xE007F }, /* TAG SPACE - CANCEL TAG */
+ { 0xE0100, 0xE01EF }, /* VARIATION SELECTOR-17 - VARIATION SELECTOR-256 */
+};
+
+/* Double-width character ranges (BMP - Basic Multilingual Plane, U+0000 to U+FFFF) */
+static const struct ucs_interval16 ucs_double_width_bmp_ranges[] = {
+ { 0x1100, 0x115F }, /* HANGUL CHOSEONG KIYEOK - HANGUL CHOSEONG FILLER */
+ { 0x231A, 0x231B }, /* WATCH - HOURGLASS */
+ { 0x2329, 0x232A }, /* LEFT-POINTING ANGLE BRACKET - RIGHT-POINTING ANGLE BRACKET */
+ { 0x23E9, 0x23EC }, /* BLACK RIGHT-POINTING DOUBLE TRIANGLE - BLACK DOWN-POINTING DOUBLE TRIANGLE */
+ { 0x23F0, 0x23F0 }, /* ALARM CLOCK */
+ { 0x23F3, 0x23F3 }, /* HOURGLASS WITH FLOWING SAND */
+ { 0x25FD, 0x25FE }, /* WHITE MEDIUM SMALL SQUARE - BLACK MEDIUM SMALL SQUARE */
+ { 0x2614, 0x2615 }, /* UMBRELLA WITH RAIN DROPS - HOT BEVERAGE */
+ { 0x2630, 0x2637 }, /* TRIGRAM FOR HEAVEN - TRIGRAM FOR EARTH */
+ { 0x2648, 0x2653 }, /* ARIES - PISCES */
+ { 0x267F, 0x267F }, /* WHEELCHAIR SYMBOL */
+ { 0x268A, 0x268F }, /* MONOGRAM FOR YANG - DIGRAM FOR GREATER YIN */
+ { 0x2693, 0x2693 }, /* ANCHOR */
+ { 0x26A1, 0x26A1 }, /* HIGH VOLTAGE SIGN */
+ { 0x26AA, 0x26AB }, /* MEDIUM WHITE CIRCLE - MEDIUM BLACK CIRCLE */
+ { 0x26BD, 0x26BE }, /* SOCCER BALL - BASEBALL */
+ { 0x26C4, 0x26C5 }, /* SNOWMAN WITHOUT SNOW - SUN BEHIND CLOUD */
+ { 0x26CE, 0x26CE }, /* OPHIUCHUS */
+ { 0x26D4, 0x26D4 }, /* NO ENTRY */
+ { 0x26EA, 0x26EA }, /* CHURCH */
+ { 0x26F2, 0x26F3 }, /* FOUNTAIN - FLAG IN HOLE */
+ { 0x26F5, 0x26F5 }, /* SAILBOAT */
+ { 0x26FA, 0x26FA }, /* TENT */
+ { 0x26FD, 0x26FD }, /* FUEL PUMP */
+ { 0x2705, 0x2705 }, /* WHITE HEAVY CHECK MARK */
+ { 0x270A, 0x270B }, /* RAISED FIST - RAISED HAND */
+ { 0x2728, 0x2728 }, /* SPARKLES */
+ { 0x274C, 0x274C }, /* CROSS MARK */
+ { 0x274E, 0x274E }, /* NEGATIVE SQUARED CROSS MARK */
+ { 0x2753, 0x2755 }, /* BLACK QUESTION MARK ORNAMENT - WHITE EXCLAMATION MARK ORNAMENT */
+ { 0x2757, 0x2757 }, /* HEAVY EXCLAMATION MARK SYMBOL */
+ { 0x2795, 0x2797 }, /* HEAVY PLUS SIGN - HEAVY DIVISION SIGN */
+ { 0x27B0, 0x27B0 }, /* CURLY LOOP */
+ { 0x27BF, 0x27BF }, /* DOUBLE CURLY LOOP */
+ { 0x2B1B, 0x2B1C }, /* BLACK LARGE SQUARE - WHITE LARGE SQUARE */
+ { 0x2B50, 0x2B50 }, /* WHITE MEDIUM STAR */
+ { 0x2B55, 0x2B55 }, /* HEAVY LARGE CIRCLE */
+ { 0x2E80, 0x2E99 }, /* CJK RADICAL REPEAT - CJK RADICAL RAP */
+ { 0x2E9B, 0x2EF3 }, /* CJK RADICAL CHOKE - CJK RADICAL C-SIMPLIFIED TURTLE */
+ { 0x2F00, 0x2FD5 }, /* KANGXI RADICAL ONE - KANGXI RADICAL FLUTE */
+ { 0x2FF0, 0x3029 }, /* IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT - HANGZHOU NUMERAL NINE */
+ { 0x3030, 0x303E }, /* WAVY DASH - IDEOGRAPHIC VARIATION INDICATOR */
+ { 0x3041, 0x3096 }, /* HIRAGANA LETTER SMALL A - HIRAGANA LETTER SMALL KE */
+ { 0x309B, 0x30FF }, /* KATAKANA-HIRAGANA VOICED SOUND MARK - KATAKANA DIGRAPH KOTO */
+ { 0x3105, 0x312F }, /* BOPOMOFO LETTER B - BOPOMOFO LETTER NN */
+ { 0x3131, 0x318E }, /* HANGUL LETTER KIYEOK - HANGUL LETTER ARAEAE */
+ { 0x3190, 0x31E5 }, /* IDEOGRAPHIC ANNOTATION LINKING MARK - CJK STROKE SZP */
+ { 0x31EF, 0x321E }, /* IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION - PARENTHESIZED KOREAN CHARACTER O HU */
+ { 0x3220, 0x3247 }, /* PARENTHESIZED IDEOGRAPH ONE - CIRCLED IDEOGRAPH KOTO */
+ { 0x3250, 0xA48C }, /* PARTNERSHIP SIGN - YI SYLLABLE YYR */
+ { 0xA490, 0xA4C6 }, /* YI RADICAL QOT - YI RADICAL KE */
+ { 0xA960, 0xA97C }, /* HANGUL CHOSEONG TIKEUT-MIEUM - HANGUL CHOSEONG SSANGYEORINHIEUH */
+ { 0xAC00, 0xD7A3 }, /* HANGUL SYLLABLE GA - HANGUL SYLLABLE HIH */
+ { 0xF900, 0xFAFF }, /* U+F900 - U+FAFF */
+ { 0xFE10, 0xFE19 }, /* PRESENTATION FORM FOR VERTICAL COMMA - PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS */
+ { 0xFE30, 0xFE52 }, /* PRESENTATION FORM FOR VERTICAL TWO DOT LEADER - SMALL FULL STOP */
+ { 0xFE54, 0xFE66 }, /* SMALL SEMICOLON - SMALL EQUALS SIGN */
+ { 0xFE68, 0xFE6B }, /* SMALL REVERSE SOLIDUS - SMALL COMMERCIAL AT */
+ { 0xFF01, 0xFF60 }, /* FULLWIDTH EXCLAMATION MARK - FULLWIDTH RIGHT WHITE PARENTHESIS */
+ { 0xFFE0, 0xFFE6 }, /* FULLWIDTH CENT SIGN - FULLWIDTH WON SIGN */
+};
+
+/* Double-width character ranges (non-BMP, U+10000 and above) */
+static const struct ucs_interval32 ucs_double_width_non_bmp_ranges[] = {
+ { 0x16FE0, 0x16FE3 }, /* TANGUT ITERATION MARK - OLD CHINESE ITERATION MARK */
+ { 0x17000, 0x187F7 }, /* U+17000 - U+187F7 */
+ { 0x18800, 0x18CD5 }, /* TANGUT COMPONENT-001 - KHITAN SMALL SCRIPT CHARACTER-18CD5 */
+ { 0x18CFF, 0x18D08 }, /* U+18CFF - U+18D08 */
+ { 0x1AFF0, 0x1AFF3 }, /* KATAKANA LETTER MINNAN TONE-2 - KATAKANA LETTER MINNAN TONE-5 */
+ { 0x1AFF5, 0x1AFFB }, /* KATAKANA LETTER MINNAN TONE-7 - KATAKANA LETTER MINNAN NASALIZED TONE-5 */
+ { 0x1AFFD, 0x1AFFE }, /* KATAKANA LETTER MINNAN NASALIZED TONE-7 - KATAKANA LETTER MINNAN NASALIZED TONE-8 */
+ { 0x1B000, 0x1B122 }, /* KATAKANA LETTER ARCHAIC E - KATAKANA LETTER ARCHAIC WU */
+ { 0x1B132, 0x1B132 }, /* HIRAGANA LETTER SMALL KO */
+ { 0x1B150, 0x1B152 }, /* HIRAGANA LETTER SMALL WI - HIRAGANA LETTER SMALL WO */
+ { 0x1B155, 0x1B155 }, /* KATAKANA LETTER SMALL KO */
+ { 0x1B164, 0x1B167 }, /* KATAKANA LETTER SMALL WI - KATAKANA LETTER SMALL N */
+ { 0x1B170, 0x1B2FB }, /* NUSHU CHARACTER-1B170 - NUSHU CHARACTER-1B2FB */
+ { 0x1D300, 0x1D356 }, /* MONOGRAM FOR EARTH - TETRAGRAM FOR FOSTERING */
+ { 0x1D360, 0x1D376 }, /* COUNTING ROD UNIT DIGIT ONE - IDEOGRAPHIC TALLY MARK FIVE */
+ { 0x1F000, 0x1F02F }, /* U+1F000 - U+1F02F */
+ { 0x1F0A0, 0x1F0FF }, /* U+1F0A0 - U+1F0FF */
+ { 0x1F18E, 0x1F18E }, /* NEGATIVE SQUARED AB */
+ { 0x1F191, 0x1F19A }, /* SQUARED CL - SQUARED VS */
+ { 0x1F200, 0x1F202 }, /* SQUARE HIRAGANA HOKA - SQUARED KATAKANA SA */
+ { 0x1F210, 0x1F23B }, /* SQUARED CJK UNIFIED IDEOGRAPH-624B - SQUARED CJK UNIFIED IDEOGRAPH-914D */
+ { 0x1F240, 0x1F248 }, /* TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C - TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557 */
+ { 0x1F250, 0x1F251 }, /* CIRCLED IDEOGRAPH ADVANTAGE - CIRCLED IDEOGRAPH ACCEPT */
+ { 0x1F260, 0x1F265 }, /* ROUNDED SYMBOL FOR FU - ROUNDED SYMBOL FOR CAI */
+ { 0x1F300, 0x1F3FA }, /* CYCLONE - AMPHORA */
+ { 0x1F400, 0x1F64F }, /* RAT - PERSON WITH FOLDED HANDS */
+ { 0x1F680, 0x1F9AF }, /* ROCKET - PROBING CANE */
+ { 0x1F9B4, 0x1FAFF }, /* U+1F9B4 - U+1FAFF */
+ { 0x20000, 0x2FFFD }, /* U+20000 - U+2FFFD */
+ { 0x30000, 0x3FFFD }, /* U+30000 - U+3FFFD */
+};
diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c
index 14a2b5f11bca..c814644ef4ee 100644
--- a/drivers/tty/vt/vc_screen.c
+++ b/drivers/tty/vt/vc_screen.c
@@ -1,6 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* Provide access to virtual console memory.
- * /dev/vcs0: the screen as it is being viewed right now (possibly scrolled)
+ * /dev/vcs: the screen as it is being viewed right now (possibly scrolled)
* /dev/vcsN: the screen of /dev/ttyN (1 <= N <= 63)
* [minor: N]
*
@@ -9,6 +10,12 @@
* Attribute/character pair is in native endianity.
* [minor: N+128]
*
+ * /dev/vcsuN: similar to /dev/vcsaN but using 4-byte unicode values
+ * instead of 1-byte screen glyph values.
+ * [minor: N+64]
+ *
+ * /dev/vcsuaN: same idea as /dev/vcsaN for unicode (not yet implemented).
+ *
* This replaces screendump and part of selection, so that the system
* administrator can control access using file system permissions.
*
@@ -39,21 +46,38 @@
#include <linux/slab.h>
#include <linux/notifier.h>
-#include <asm/uaccess.h>
+#include <linux/uaccess.h>
#include <asm/byteorder.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
+
+#define HEADER_SIZE 4u
+#define CON_BUF_SIZE (IS_ENABLED(CONFIG_BASE_SMALL) ? 256 : PAGE_SIZE)
-#undef attr
-#undef org
-#undef addr
-#define HEADER_SIZE 4
+DEFINE_FREE(free_page_ptr, void *, if (_T) free_page((unsigned long)_T));
+
+/*
+ * Our minor space:
+ *
+ * 0 ... 63 glyph mode without attributes
+ * 64 ... 127 unicode mode without attributes
+ * 128 ... 191 glyph mode with attributes
+ * 192 ... 255 unused (reserved for unicode with attributes)
+ *
+ * This relies on MAX_NR_CONSOLES being <= 63, meaning 63 actual consoles
+ * with minors 0, 64, 128 and 192 being proxies for the foreground console.
+ */
+#if MAX_NR_CONSOLES > 63
+#warning "/dev/vcs* devices may not accommodate more than 63 consoles"
+#endif
-#define CON_BUF_SIZE (CONFIG_BASE_SMALL ? 256 : PAGE_SIZE)
+#define console(inode) (iminor(inode) & 63)
+#define use_unicode(inode) (iminor(inode) & 64)
+#define use_attributes(inode) (iminor(inode) & 128)
struct vcs_poll_data {
struct notifier_block notifier;
unsigned int cons_num;
- bool seen_last_update;
+ int event;
wait_queue_head_t waitq;
struct fasync_struct *fasync;
};
@@ -66,9 +90,18 @@ vcs_notifier(struct notifier_block *nb, unsigned long code, void *_param)
struct vcs_poll_data *poll =
container_of(nb, struct vcs_poll_data, notifier);
int currcons = poll->cons_num;
-
- if (code != VT_UPDATE)
+ int fa_band;
+
+ switch (code) {
+ case VT_UPDATE:
+ fa_band = POLL_PRI;
+ break;
+ case VT_DEALLOCATE:
+ fa_band = POLL_HUP;
+ break;
+ default:
return NOTIFY_DONE;
+ }
if (currcons == 0)
currcons = fg_console;
@@ -77,9 +110,9 @@ vcs_notifier(struct notifier_block *nb, unsigned long code, void *_param)
if (currcons != vc->vc_num)
return NOTIFY_DONE;
- poll->seen_last_update = false;
+ poll->event = code;
wake_up_interruptible(&poll->waitq);
- kill_fasync(&poll->fasync, SIGIO, POLL_IN);
+ kill_fasync(&poll->fasync, SIGIO, fa_band);
return NOTIFY_OK;
}
@@ -101,9 +134,18 @@ vcs_poll_data_get(struct file *file)
poll = kzalloc(sizeof(*poll), GFP_KERNEL);
if (!poll)
return NULL;
- poll->cons_num = iminor(file_inode(file)) & 127;
+ poll->cons_num = console(file_inode(file));
init_waitqueue_head(&poll->waitq);
poll->notifier.notifier_call = vcs_notifier;
+ /*
+ * In order not to lose any update event, we must pretend one might
+ * have occurred before we have a chance to register our notifier.
+ * This is also how user space has come to detect which kernels
+ * support POLLPRI on /dev/vcs* devices i.e. using poll() with
+ * POLLPRI and a zero timeout.
+ */
+ poll->event = VT_UPDATE;
+
if (register_vt_notifier(&poll->notifier) != 0) {
kfree(poll);
return NULL;
@@ -132,81 +174,204 @@ vcs_poll_data_get(struct file *file)
return poll;
}
-/*
- * Returns VC for inode.
+/**
+ * vcs_vc - return VC for @inode
+ * @inode: inode for which to return a VC
+ * @viewed: returns whether this console is currently foreground (viewed)
+ *
* Must be called with console_lock.
*/
-static struct vc_data*
-vcs_vc(struct inode *inode, int *viewed)
+static struct vc_data *vcs_vc(struct inode *inode, bool *viewed)
{
- unsigned int currcons = iminor(inode) & 127;
+ unsigned int currcons = console(inode);
WARN_CONSOLE_UNLOCKED();
if (currcons == 0) {
currcons = fg_console;
if (viewed)
- *viewed = 1;
+ *viewed = true;
} else {
currcons--;
if (viewed)
- *viewed = 0;
+ *viewed = false;
}
return vc_cons[currcons].d;
}
-/*
- * Returns size for VC carried by inode.
+/**
+ * vcs_size - return size for a VC in @vc
+ * @vc: which VC
+ * @attr: does it use attributes?
+ * @unicode: is it unicode?
+ *
* Must be called with console_lock.
*/
-static int
-vcs_size(struct inode *inode)
+static int vcs_size(const struct vc_data *vc, bool attr, bool unicode)
{
int size;
- int minor = iminor(inode);
- struct vc_data *vc;
WARN_CONSOLE_UNLOCKED();
- vc = vcs_vc(inode, NULL);
- if (!vc)
- return -ENXIO;
-
size = vc->vc_rows * vc->vc_cols;
- if (minor & 128)
- size = 2*size + HEADER_SIZE;
+ if (attr) {
+ if (unicode)
+ return -EOPNOTSUPP;
+
+ size = 2 * size + HEADER_SIZE;
+ } else if (unicode)
+ size *= 4;
+
return size;
}
static loff_t vcs_lseek(struct file *file, loff_t offset, int orig)
{
+ struct inode *inode = file_inode(file);
+ struct vc_data *vc;
int size;
- console_lock();
- size = vcs_size(file_inode(file));
- console_unlock();
+ scoped_guard(console_lock) {
+ vc = vcs_vc(inode, NULL);
+ if (!vc)
+ return -ENXIO;
+
+ size = vcs_size(vc, use_attributes(inode), use_unicode(inode));
+ }
if (size < 0)
return size;
return fixed_size_llseek(file, offset, orig, size);
}
+static int vcs_read_buf_uni(struct vc_data *vc, char *con_buf,
+ unsigned int pos, unsigned int count, bool viewed)
+{
+ unsigned int nr, row, col, maxcol = vc->vc_cols;
+ int ret;
+
+ ret = vc_uniscr_check(vc);
+ if (ret)
+ return ret;
+
+ pos /= 4;
+ row = pos / maxcol;
+ col = pos % maxcol;
+ nr = maxcol - col;
+ do {
+ if (nr > count / 4)
+ nr = count / 4;
+ vc_uniscr_copy_line(vc, con_buf, viewed, row, col, nr);
+ con_buf += nr * 4;
+ count -= nr * 4;
+ row++;
+ col = 0;
+ nr = maxcol;
+ } while (count);
+
+ return 0;
+}
+
+static void vcs_read_buf_noattr(const struct vc_data *vc, char *con_buf,
+ unsigned int pos, unsigned int count, bool viewed)
+{
+ u16 *org;
+ unsigned int col, maxcol = vc->vc_cols;
+
+ org = screen_pos(vc, pos, viewed);
+ col = pos % maxcol;
+ pos += maxcol - col;
+
+ while (count-- > 0) {
+ *con_buf++ = (vcs_scr_readw(vc, org++) & 0xff);
+ if (++col == maxcol) {
+ org = screen_pos(vc, pos, viewed);
+ col = 0;
+ pos += maxcol;
+ }
+ }
+}
+
+static unsigned int vcs_read_buf(const struct vc_data *vc, char *con_buf,
+ unsigned int pos, unsigned int count, bool viewed,
+ unsigned int *skip)
+{
+ u16 *org, *con_buf16;
+ unsigned int col, maxcol = vc->vc_cols;
+ unsigned int filled = count;
+
+ if (pos < HEADER_SIZE) {
+ /* clamp header values if they don't fit */
+ con_buf[0] = min(vc->vc_rows, 0xFFu);
+ con_buf[1] = min(vc->vc_cols, 0xFFu);
+ getconsxy(vc, con_buf + 2);
+
+ *skip += pos;
+ count += pos;
+ if (count > CON_BUF_SIZE) {
+ count = CON_BUF_SIZE;
+ filled = count - pos;
+ }
+
+ /* Advance state pointers and move on. */
+ count -= min(HEADER_SIZE, count);
+ pos = HEADER_SIZE;
+ con_buf += HEADER_SIZE;
+ /* If count >= 0, then pos is even... */
+ } else if (pos & 1) {
+ /*
+ * Skip first byte for output if start address is odd. Update
+ * region sizes up/down depending on free space in buffer.
+ */
+ (*skip)++;
+ if (count < CON_BUF_SIZE)
+ count++;
+ else
+ filled--;
+ }
+
+ if (!count)
+ return filled;
+
+ pos -= HEADER_SIZE;
+ pos /= 2;
+ col = pos % maxcol;
+
+ org = screen_pos(vc, pos, viewed);
+ pos += maxcol - col;
+
+ /*
+ * Buffer has even length, so we can always copy character + attribute.
+ * We do not copy last byte to userspace if count is odd.
+ */
+ count = (count + 1) / 2;
+ con_buf16 = (u16 *)con_buf;
+
+ while (count) {
+ *con_buf16++ = vcs_scr_readw(vc, org++);
+ count--;
+ if (++col == maxcol) {
+ org = screen_pos(vc, pos, viewed);
+ col = 0;
+ pos += maxcol;
+ }
+ }
+
+ return filled;
+}
static ssize_t
vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct inode *inode = file_inode(file);
- unsigned int currcons = iminor(inode);
struct vc_data *vc;
struct vcs_poll_data *poll;
- long pos;
- long attr, read;
- int col, maxcol, viewed;
- unsigned short *org = NULL;
+ unsigned int read;
ssize_t ret;
- char *con_buf;
+ loff_t pos;
+ bool viewed, attr, uni_mode;
- con_buf = (char *) __get_free_page(GFP_KERNEL);
+ char *con_buf __free(free_page_ptr) = (char *)__get_free_page(GFP_KERNEL);
if (!con_buf)
return -ENOMEM;
@@ -215,38 +380,40 @@ vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
/* Select the proper current console and verify
* sanity of the situation under the console lock.
*/
- console_lock();
+ guard(console_lock)();
- attr = (currcons & 128);
- ret = -ENXIO;
- vc = vcs_vc(inode, &viewed);
- if (!vc)
- goto unlock_out;
+ uni_mode = use_unicode(inode);
+ attr = use_attributes(inode);
- ret = -EINVAL;
if (pos < 0)
- goto unlock_out;
+ return -EINVAL;
+ /* we enforce 32-bit alignment for pos and count in unicode mode */
+ if (uni_mode && (pos | count) & 3)
+ return -EINVAL;
+
poll = file->private_data;
if (count && poll)
- poll->seen_last_update = true;
+ poll->event = 0;
read = 0;
ret = 0;
while (count) {
- char *con_buf0, *con_buf_start;
- long this_round, size;
- ssize_t orig_count;
- long p = pos;
+ unsigned int this_round, skip = 0;
+ int size;
+
+ vc = vcs_vc(inode, &viewed);
+ if (!vc) {
+ ret = -ENXIO;
+ break;
+ }
/* Check whether we are above size each round,
* as copy_to_user at the end of this loop
* could sleep.
*/
- size = vcs_size(inode);
+ size = vcs_size(vc, attr, uni_mode);
if (size < 0) {
- if (read)
- break;
ret = size;
- goto unlock_out;
+ break;
}
if (pos >= size)
break;
@@ -262,82 +429,17 @@ vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
* attempt to move it to userspace.
*/
- con_buf_start = con_buf0 = con_buf;
- orig_count = this_round;
- maxcol = vc->vc_cols;
- if (!attr) {
- org = screen_pos(vc, p, viewed);
- col = p % maxcol;
- p += maxcol - col;
- while (this_round-- > 0) {
- *con_buf0++ = (vcs_scr_readw(vc, org++) & 0xff);
- if (++col == maxcol) {
- org = screen_pos(vc, p, viewed);
- col = 0;
- p += maxcol;
- }
- }
+ if (uni_mode) {
+ ret = vcs_read_buf_uni(vc, con_buf, pos, this_round,
+ viewed);
+ if (ret)
+ break;
+ } else if (!attr) {
+ vcs_read_buf_noattr(vc, con_buf, pos, this_round,
+ viewed);
} else {
- if (p < HEADER_SIZE) {
- size_t tmp_count;
-
- con_buf0[0] = (char)vc->vc_rows;
- con_buf0[1] = (char)vc->vc_cols;
- getconsxy(vc, con_buf0 + 2);
-
- con_buf_start += p;
- this_round += p;
- if (this_round > CON_BUF_SIZE) {
- this_round = CON_BUF_SIZE;
- orig_count = this_round - p;
- }
-
- tmp_count = HEADER_SIZE;
- if (tmp_count > this_round)
- tmp_count = this_round;
-
- /* Advance state pointers and move on. */
- this_round -= tmp_count;
- p = HEADER_SIZE;
- con_buf0 = con_buf + HEADER_SIZE;
- /* If this_round >= 0, then p is even... */
- } else if (p & 1) {
- /* Skip first byte for output if start address is odd
- * Update region sizes up/down depending on free
- * space in buffer.
- */
- con_buf_start++;
- if (this_round < CON_BUF_SIZE)
- this_round++;
- else
- orig_count--;
- }
- if (this_round > 0) {
- unsigned short *tmp_buf = (unsigned short *)con_buf0;
-
- p -= HEADER_SIZE;
- p /= 2;
- col = p % maxcol;
-
- org = screen_pos(vc, p, viewed);
- p += maxcol - col;
-
- /* Buffer has even length, so we can always copy
- * character + attribute. We do not copy last byte
- * to userspace if this_round is odd.
- */
- this_round = (this_round + 1) >> 1;
-
- while (this_round) {
- *tmp_buf++ = vcs_scr_readw(vc, org++);
- this_round --;
- if (++col == maxcol) {
- org = screen_pos(vc, p, viewed);
- col = 0;
- p += maxcol;
- }
- }
- }
+ this_round = vcs_read_buf(vc, con_buf, pos, this_round,
+ viewed, &skip);
}
/* Finally, release the console semaphore while we push
@@ -348,43 +450,153 @@ vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
*/
console_unlock();
- ret = copy_to_user(buf, con_buf_start, orig_count);
+ ret = copy_to_user(buf, con_buf + skip, this_round);
console_lock();
if (ret) {
- read += (orig_count - ret);
+ read += this_round - ret;
ret = -EFAULT;
break;
}
- buf += orig_count;
- pos += orig_count;
- read += orig_count;
- count -= orig_count;
+ buf += this_round;
+ pos += this_round;
+ read += this_round;
+ count -= this_round;
}
*ppos += read;
if (read)
- ret = read;
-unlock_out:
- console_unlock();
- free_page((unsigned long) con_buf);
+ return read;
+
return ret;
}
+static u16 *vcs_write_buf_noattr(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;
+
+ *org0 = org = screen_pos(vc, pos, viewed);
+ col = pos % maxcol;
+ pos += maxcol - col;
+
+ while (count > 0) {
+ unsigned char c = *con_buf++;
+
+ count--;
+ vcs_scr_writew(vc,
+ (vcs_scr_readw(vc, org) & 0xff00) | c, org);
+ org++;
+ if (++col == maxcol) {
+ org = screen_pos(vc, pos, viewed);
+ col = 0;
+ pos += maxcol;
+ }
+ }
+
+ 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;
+
+ /* header */
+ if (pos < HEADER_SIZE) {
+ char header[HEADER_SIZE];
+
+ getconsxy(vc, header + 2);
+ while (pos < HEADER_SIZE && count > 0) {
+ count--;
+ header[pos++] = *con_buf++;
+ }
+ if (!viewed)
+ putconsxy(vc, header + 2);
+ }
+
+ if (!count)
+ return NULL;
+
+ pos -= HEADER_SIZE;
+ col = (pos/2) % maxcol;
+
+ *org0 = org = screen_pos(vc, pos/2, 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++;
+ if (++col == maxcol) {
+ org = screen_pos(vc, pos/2, viewed);
+ col = 0;
+ }
+ }
+
+ pos /= 2;
+ pos += maxcol - col;
+
+ /* even pos -- handle attr+character pairs */
+ while (count > 1) {
+ unsigned short w;
+
+ w = get_unaligned(((unsigned short *)con_buf));
+ vcs_scr_writew(vc, w, org++);
+ con_buf += 2;
+ count -= 2;
+ if (++col == maxcol) {
+ org = screen_pos(vc, pos, viewed);
+ col = 0;
+ pos += maxcol;
+ }
+ }
+
+ 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);
+
+ return org;
+}
+
static ssize_t
vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
struct inode *inode = file_inode(file);
- unsigned int currcons = iminor(inode);
struct vc_data *vc;
- long pos;
- long attr, size, written;
- char *con_buf0;
- int col, maxcol, viewed;
- u16 *org0 = NULL, *org = NULL;
- size_t ret;
- char *con_buf;
-
- con_buf = (char *) __get_free_page(GFP_KERNEL);
+ u16 *org0, *org;
+ unsigned int written;
+ int size;
+ ssize_t ret;
+ loff_t pos;
+ bool viewed, attr;
+
+ if (use_unicode(inode))
+ return -EOPNOTSUPP;
+
+ char *con_buf __free(free_page_ptr) = (char *)__get_free_page(GFP_KERNEL);
if (!con_buf)
return -ENOMEM;
@@ -393,25 +605,23 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
/* Select the proper current console and verify
* sanity of the situation under the console lock.
*/
- console_lock();
+ guard(console_lock)();
- attr = (currcons & 128);
- ret = -ENXIO;
+ attr = use_attributes(inode);
vc = vcs_vc(inode, &viewed);
if (!vc)
- goto unlock_out;
+ return -ENXIO;
- size = vcs_size(inode);
- ret = -EINVAL;
+ size = vcs_size(vc, attr, false);
+ if (size < 0)
+ return size;
if (pos < 0 || pos > size)
- goto unlock_out;
+ return -EINVAL;
if (count > size - pos)
count = size - pos;
written = 0;
while (count) {
- long this_round = count;
- size_t orig_count;
- long p;
+ unsigned int this_round = count;
if (this_round > CON_BUF_SIZE)
this_round = CON_BUF_SIZE;
@@ -431,21 +641,25 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
*/
if (written)
break;
- ret = -EFAULT;
- goto unlock_out;
+ return -EFAULT;
}
}
- /* The vcs_size might have changed while we slept to grab
- * the user buffer, so recheck.
+ /* The vc might have been freed or vcs_size might have changed
+ * while we slept to grab the user buffer, so recheck.
* Return data written up to now on failure.
*/
- size = vcs_size(inode);
+ vc = vcs_vc(inode, &viewed);
+ if (!vc) {
+ if (written)
+ break;
+ return -ENXIO;
+ }
+ size = vcs_size(vc, attr, false);
if (size < 0) {
if (written)
break;
- ret = size;
- goto unlock_out;
+ return size;
}
if (pos >= size)
break;
@@ -456,95 +670,18 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
* under the lock using the local kernel buffer.
*/
- con_buf0 = con_buf;
- orig_count = this_round;
- maxcol = vc->vc_cols;
- p = pos;
- if (!attr) {
- org0 = org = screen_pos(vc, p, viewed);
- col = p % maxcol;
- p += maxcol - col;
-
- while (this_round > 0) {
- unsigned char c = *con_buf0++;
-
- this_round--;
- vcs_scr_writew(vc,
- (vcs_scr_readw(vc, org) & 0xff00) | c, org);
- org++;
- if (++col == maxcol) {
- org = screen_pos(vc, p, viewed);
- col = 0;
- p += maxcol;
- }
- }
- } else {
- if (p < HEADER_SIZE) {
- char header[HEADER_SIZE];
-
- getconsxy(vc, header + 2);
- while (p < HEADER_SIZE && this_round > 0) {
- this_round--;
- header[p++] = *con_buf0++;
- }
- if (!viewed)
- putconsxy(vc, header + 2);
- }
- p -= HEADER_SIZE;
- col = (p/2) % maxcol;
- if (this_round > 0) {
- org0 = org = screen_pos(vc, p/2, viewed);
- if ((p & 1) && this_round > 0) {
- char c;
-
- this_round--;
- c = *con_buf0++;
-#ifdef __BIG_ENDIAN
- vcs_scr_writew(vc, c |
- (vcs_scr_readw(vc, org) & 0xff00), org);
-#else
- vcs_scr_writew(vc, (c << 8) |
- (vcs_scr_readw(vc, org) & 0xff), org);
-#endif
- org++;
- p++;
- if (++col == maxcol) {
- org = screen_pos(vc, p/2, viewed);
- col = 0;
- }
- }
- p /= 2;
- p += maxcol - col;
- }
- while (this_round > 1) {
- unsigned short w;
-
- w = get_unaligned(((unsigned short *)con_buf0));
- vcs_scr_writew(vc, w, org++);
- con_buf0 += 2;
- this_round -= 2;
- if (++col == maxcol) {
- org = screen_pos(vc, p, viewed);
- col = 0;
- p += maxcol;
- }
- }
- if (this_round > 0) {
- unsigned char c;
-
- c = *con_buf0++;
-#ifdef __BIG_ENDIAN
- vcs_scr_writew(vc, (vcs_scr_readw(vc, org) & 0xff) | (c << 8), org);
-#else
- vcs_scr_writew(vc, (vcs_scr_readw(vc, org) & 0xff00) | c, org);
-#endif
- }
- }
- count -= orig_count;
- written += orig_count;
- buf += orig_count;
- pos += orig_count;
- if (org0)
+ if (attr)
+ org = vcs_write_buf(vc, con_buf, pos, this_round,
+ viewed, &org0);
+ else
+ org = vcs_write_buf_noattr(vc, con_buf, pos, this_round,
+ viewed, &org0);
+
+ count -= this_round;
+ written += this_round;
+ buf += this_round;
+ pos += this_round;
+ if (org)
update_region(vc, (unsigned long)(org0), org - org0);
}
*ppos += written;
@@ -552,22 +689,28 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
if (written)
vcs_scr_updated(vc);
-unlock_out:
- console_unlock();
- free_page((unsigned long) con_buf);
return ret;
}
-static unsigned int
+static __poll_t
vcs_poll(struct file *file, poll_table *wait)
{
struct vcs_poll_data *poll = vcs_poll_data_get(file);
- int ret = DEFAULT_POLLMASK|POLLERR|POLLPRI;
+ __poll_t ret = DEFAULT_POLLMASK|EPOLLERR;
if (poll) {
poll_wait(file, &poll->waitq, wait);
- if (poll->seen_last_update)
+ switch (poll->event) {
+ case VT_UPDATE:
+ ret = DEFAULT_POLLMASK|EPOLLPRI;
+ break;
+ case VT_DEALLOCATE:
+ ret = DEFAULT_POLLMASK|EPOLLHUP|EPOLLERR;
+ break;
+ case 0:
ret = DEFAULT_POLLMASK;
+ break;
+ }
}
return ret;
}
@@ -592,14 +735,20 @@ vcs_fasync(int fd, struct file *file, int on)
static int
vcs_open(struct inode *inode, struct file *filp)
{
- unsigned int currcons = iminor(inode) & 127;
- int ret = 0;
-
- console_lock();
- if(currcons && !vc_cons_allocated(currcons-1))
- ret = -ENXIO;
- console_unlock();
- return ret;
+ unsigned int currcons = console(inode);
+ bool attr = use_attributes(inode);
+ bool uni_mode = use_unicode(inode);
+
+ /* we currently don't support attributes in unicode mode */
+ if (attr && uni_mode)
+ return -EOPNOTSUPP;
+
+ guard(console_lock)();
+
+ if (currcons && !vc_cons_allocated(currcons - 1))
+ return -ENXIO;
+
+ return 0;
}
static int vcs_release(struct inode *inode, struct file *file)
@@ -621,20 +770,22 @@ static const struct file_operations vcs_fops = {
.release = vcs_release,
};
-static struct class *vc_class;
+static const struct class vc_class = {
+ .name = "vc",
+};
void vcs_make_sysfs(int index)
{
- device_create(vc_class, NULL, MKDEV(VCS_MAJOR, index + 1), NULL,
- "vcs%u", index + 1);
- device_create(vc_class, NULL, MKDEV(VCS_MAJOR, index + 129), NULL,
- "vcsa%u", index + 1);
+ device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, index + 1), NULL, "vcs%u", index + 1);
+ device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, index + 65), NULL, "vcsu%u", index + 1);
+ device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, index + 129), NULL, "vcsa%u", index + 1);
}
void vcs_remove_sysfs(int index)
{
- device_destroy(vc_class, MKDEV(VCS_MAJOR, index + 1));
- device_destroy(vc_class, MKDEV(VCS_MAJOR, index + 129));
+ device_destroy(&vc_class, MKDEV(VCS_MAJOR, index + 1));
+ device_destroy(&vc_class, MKDEV(VCS_MAJOR, index + 65));
+ device_destroy(&vc_class, MKDEV(VCS_MAJOR, index + 129));
}
int __init vcs_init(void)
@@ -643,10 +794,12 @@ int __init vcs_init(void)
if (register_chrdev(VCS_MAJOR, "vcs", &vcs_fops))
panic("unable to get major %d for vcs device", VCS_MAJOR);
- vc_class = class_create(THIS_MODULE, "vc");
+ if (class_register(&vc_class))
+ panic("unable to create vc_class");
- device_create(vc_class, NULL, MKDEV(VCS_MAJOR, 0), NULL, "vcs");
- device_create(vc_class, NULL, MKDEV(VCS_MAJOR, 128), NULL, "vcsa");
+ device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, 0), NULL, "vcs");
+ device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, 64), NULL, "vcsu");
+ device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, 128), NULL, "vcsa");
for (i = 0; i < MIN_NR_CONSOLES; i++)
vcs_make_sysfs(i);
return 0;
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index c677829baa8b..59b4b5e126ba 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
@@ -72,7 +73,7 @@
#include <linux/module.h>
#include <linux/types.h>
-#include <linux/sched.h>
+#include <linux/sched/signal.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/kernel.h>
@@ -80,6 +81,7 @@
#include <linux/errno.h>
#include <linux/kd.h>
#include <linux/slab.h>
+#include <linux/vmalloc.h>
#include <linux/major.h>
#include <linux/mm.h>
#include <linux/console.h>
@@ -102,12 +104,14 @@
#include <linux/uaccess.h>
#include <linux/kdb.h>
#include <linux/ctype.h>
+#include <linux/gcd.h>
#define MAX_NR_CON_DRIVER 16
#define CON_DRIVER_FLAG_MODULE 1
#define CON_DRIVER_FLAG_INIT 2
#define CON_DRIVER_FLAG_ATTR 4
+#define CON_DRIVER_FLAG_ZOMBIE 8
struct con_driver {
const struct consw *con;
@@ -122,47 +126,43 @@ struct con_driver {
static struct con_driver registered_con_driver[MAX_NR_CON_DRIVER];
const struct consw *conswitchp;
-/* A bitmap for codes <32. A bit of 1 indicates that the code
- * corresponding to that bit number invokes some special action
- * (such as cursor movement) and should not be displayed as a
- * glyph unless the disp_ctrl mode is explicitly enabled.
- */
-#define CTRL_ACTION 0x0d00ff81
-#define CTRL_ALWAYS 0x0800f501 /* Cannot be overridden by disp_ctrl */
-
/*
* Here is the default bell parameters: 750HZ, 1/8th of a second
*/
#define DEFAULT_BELL_PITCH 750
#define DEFAULT_BELL_DURATION (HZ/8)
+#define DEFAULT_CURSOR_BLINK_MS 200
struct vc vc_cons [MAX_NR_CONSOLES];
+EXPORT_SYMBOL(vc_cons);
-#ifndef VT_SINGLE_DRIVER
static const struct consw *con_driver_map[MAX_NR_CONSOLES];
-#endif
static int con_open(struct tty_struct *, struct file *);
-static void vc_init(struct vc_data *vc, unsigned int rows,
- unsigned int cols, int do_clear);
+static void vc_init(struct vc_data *vc, int do_clear);
static void gotoxy(struct vc_data *vc, int new_x, int new_y);
+static void restore_cur(struct vc_data *vc);
static void save_cur(struct vc_data *vc);
static void reset_terminal(struct vc_data *vc, int do_clear);
static void con_flush_chars(struct tty_struct *tty);
-static int set_vesa_blanking(char __user *p);
+static int set_vesa_blanking(u8 __user *mode);
static void set_cursor(struct vc_data *vc);
static void hide_cursor(struct vc_data *vc);
static void console_callback(struct work_struct *ignored);
-static void blank_screen_t(unsigned long dummy);
+static void con_driver_unregister_callback(struct work_struct *ignored);
+static void blank_screen_t(struct timer_list *unused);
static void set_palette(struct vc_data *vc);
+static void unblank_screen(void);
+
+#define vt_get_kmsg_redirect() vt_kmsg_redirect(-1)
-static int printable; /* Is console ready for printing? */
int default_utf8 = true;
module_param(default_utf8, int, S_IRUGO | S_IWUSR);
int global_cursor_default = -1;
module_param(global_cursor_default, int, S_IRUGO | S_IWUSR);
+EXPORT_SYMBOL(global_cursor_default);
-static int cur_default = CUR_DEFAULT;
+static int cur_default = CUR_UNDERLINE;
module_param(cur_default, int, S_IRUGO | S_IWUSR);
/*
@@ -173,13 +173,15 @@ static int ignore_poke;
int do_poke_blanked_console;
int console_blanked;
+EXPORT_SYMBOL(console_blanked);
-static int vesa_blank_mode; /* 0:none 1:suspendV 2:suspendH 3:powerdown */
+static enum vesa_blank_mode vesa_blank_mode;
static int vesa_off_interval;
-static int blankinterval = 10*60;
+static int blankinterval;
core_param(consoleblank, blankinterval, int, 0444);
static DECLARE_WORK(console_work, console_callback);
+static DECLARE_WORK(con_driver_unregister_work, con_driver_unregister_callback);
/*
* fg_console is the current virtual console,
@@ -188,8 +190,10 @@ static DECLARE_WORK(console_work, console_callback);
* saved_* variants are for save/restore around kernel debugger enter/leave
*/
int fg_console;
+EXPORT_SYMBOL(fg_console);
int last_console;
int want_console = -1;
+
static int saved_fg_console;
static int saved_last_console;
static int saved_want_console;
@@ -221,8 +225,9 @@ static int scrollback_delta;
* the console on our behalf.
*/
int (*console_blank_hook)(int);
+EXPORT_SYMBOL(console_blank_hook);
-static DEFINE_TIMER(console_timer, blank_screen_t, 0, 0);
+static DEFINE_TIMER(console_timer, blank_screen_t);
static int blank_state;
static int blank_timer_expired;
enum {
@@ -271,25 +276,30 @@ static void notify_update(struct vc_data *vc)
* Low-Level Functions
*/
-#define IS_FG(vc) ((vc)->vc_num == fg_console)
+static inline bool con_is_fg(const struct vc_data *vc)
+{
+ return vc->vc_num == fg_console;
+}
-#ifdef VT_BUF_VRAM_ONLY
-#define DO_UPDATE(vc) 0
-#else
-#define DO_UPDATE(vc) (CON_IS_VISIBLE(vc) && !console_blanked)
-#endif
+static inline bool con_should_update(const struct vc_data *vc)
+{
+ return con_is_visible(vc) && !console_blanked;
+}
-static inline unsigned short *screenpos(struct vc_data *vc, int offset, int viewed)
+static inline u16 *screenpos(const struct vc_data *vc, unsigned int offset,
+ bool viewed)
{
- unsigned short *p;
-
- if (!viewed)
- p = (unsigned short *)(vc->vc_origin + offset);
- else if (!vc->vc_sw->con_screen_pos)
- p = (unsigned short *)(vc->vc_visible_origin + offset);
+ unsigned long origin = viewed ? vc->vc_visible_origin : vc->vc_origin;
+
+ return (u16 *)(origin + offset);
+}
+
+static void con_putc(struct vc_data *vc, u16 ca, unsigned int y, unsigned int x)
+{
+ if (vc->vc_sw->con_putc)
+ vc->vc_sw->con_putc(vc, ca, y, x);
else
- p = vc->vc_sw->con_screen_pos(vc, offset);
- return p;
+ vc->vc_sw->con_putcs(vc, &ca, 1, y, x);
}
/* Called from the keyboard irq path.. */
@@ -307,56 +317,297 @@ void schedule_console_callback(void)
schedule_work(&console_work);
}
-static void scrup(struct vc_data *vc, unsigned int t, unsigned int b, int nr)
+/*
+ * Code to manage unicode-based screen buffers
+ */
+
+/*
+ * Our screen buffer is preceded by an array of line pointers so that
+ * scrolling only implies some pointer shuffling.
+ */
+
+static u32 **vc_uniscr_alloc(unsigned int cols, unsigned int rows)
+{
+ u32 **uni_lines;
+ void *p;
+ unsigned int memsize, i, col_size = cols * sizeof(**uni_lines);
+
+ /* allocate everything in one go */
+ memsize = col_size * rows;
+ memsize += rows * sizeof(*uni_lines);
+ uni_lines = vzalloc(memsize);
+ if (!uni_lines)
+ return NULL;
+
+ /* initial line pointers */
+ p = uni_lines + rows;
+ for (i = 0; i < rows; i++) {
+ uni_lines[i] = p;
+ p += col_size;
+ }
+
+ return uni_lines;
+}
+
+static void vc_uniscr_free(u32 **uni_lines)
+{
+ vfree(uni_lines);
+}
+
+static void vc_uniscr_set(struct vc_data *vc, u32 **new_uni_lines)
+{
+ vc_uniscr_free(vc->vc_uni_lines);
+ vc->vc_uni_lines = new_uni_lines;
+}
+
+static void vc_uniscr_putc(struct vc_data *vc, u32 uc)
+{
+ if (vc->vc_uni_lines)
+ vc->vc_uni_lines[vc->state.y][vc->state.x] = uc;
+}
+
+static void vc_uniscr_insert(struct vc_data *vc, unsigned int nr)
+{
+ if (vc->vc_uni_lines) {
+ u32 *ln = vc->vc_uni_lines[vc->state.y];
+ unsigned int x = vc->state.x, cols = vc->vc_cols;
+
+ memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(*ln));
+ memset32(&ln[x], ' ', nr);
+ }
+}
+
+static void vc_uniscr_delete(struct vc_data *vc, unsigned int nr)
+{
+ if (vc->vc_uni_lines) {
+ u32 *ln = vc->vc_uni_lines[vc->state.y];
+ unsigned int x = vc->state.x, cols = vc->vc_cols;
+
+ memmove(&ln[x], &ln[x + nr], (cols - x - nr) * sizeof(*ln));
+ memset32(&ln[cols - nr], ' ', nr);
+ }
+}
+
+static void vc_uniscr_clear_line(struct vc_data *vc, unsigned int x,
+ unsigned int nr)
+{
+ if (vc->vc_uni_lines)
+ memset32(&vc->vc_uni_lines[vc->state.y][x], ' ', nr);
+}
+
+static void vc_uniscr_clear_lines(struct vc_data *vc, unsigned int y,
+ unsigned int nr)
+{
+ if (vc->vc_uni_lines)
+ while (nr--)
+ memset32(vc->vc_uni_lines[y++], ' ', vc->vc_cols);
+}
+
+/* juggling array rotation algorithm (complexity O(N), size complexity O(1)) */
+static void juggle_array(u32 **array, unsigned int size, unsigned int nr)
+{
+ unsigned int gcd_idx;
+
+ for (gcd_idx = 0; gcd_idx < gcd(nr, size); gcd_idx++) {
+ u32 *gcd_idx_val = array[gcd_idx];
+ unsigned int dst_idx = gcd_idx;
+
+ while (1) {
+ unsigned int src_idx = (dst_idx + nr) % size;
+ if (src_idx == gcd_idx)
+ break;
+
+ array[dst_idx] = array[src_idx];
+ dst_idx = src_idx;
+ }
+
+ array[dst_idx] = gcd_idx_val;
+ }
+}
+
+static void vc_uniscr_scroll(struct vc_data *vc, unsigned int top,
+ unsigned int bottom, enum con_scroll dir,
+ unsigned int nr)
{
- unsigned short *d, *s;
+ u32 **uni_lines = vc->vc_uni_lines;
+ unsigned int size = bottom - top;
- if (t+nr >= b)
- nr = b - t - 1;
- if (b > vc->vc_rows || t >= b || nr < 1)
+ if (!uni_lines)
return;
- if (CON_IS_VISIBLE(vc) && vc->vc_sw->con_scroll(vc, t, b, SM_UP, nr))
+
+ if (dir == SM_DOWN) {
+ juggle_array(&uni_lines[top], size, size - nr);
+ vc_uniscr_clear_lines(vc, top, nr);
+ } else {
+ juggle_array(&uni_lines[top], size, nr);
+ vc_uniscr_clear_lines(vc, bottom - nr, nr);
+ }
+}
+
+static u32 vc_uniscr_getc(struct vc_data *vc, int relative_pos)
+{
+ int pos = vc->state.x + vc->vc_need_wrap + relative_pos;
+
+ if (vc->vc_uni_lines && in_range(pos, 0, vc->vc_cols))
+ return vc->vc_uni_lines[vc->state.y][pos];
+ return 0;
+}
+
+static void vc_uniscr_copy_area(u32 **dst_lines,
+ unsigned int dst_cols,
+ unsigned int dst_rows,
+ u32 **src_lines,
+ unsigned int src_cols,
+ unsigned int src_top_row,
+ unsigned int src_bot_row)
+{
+ unsigned int dst_row = 0;
+
+ if (!dst_lines)
return;
- d = (unsigned short *)(vc->vc_origin + vc->vc_size_row * t);
- s = (unsigned short *)(vc->vc_origin + vc->vc_size_row * (t + nr));
- scr_memmovew(d, s, (b - t - nr) * vc->vc_size_row);
- scr_memsetw(d + (b - t - nr) * vc->vc_cols, vc->vc_video_erase_char,
- vc->vc_size_row * nr);
+
+ while (src_top_row < src_bot_row) {
+ u32 *src_line = src_lines[src_top_row];
+ u32 *dst_line = dst_lines[dst_row];
+
+ memcpy(dst_line, src_line, src_cols * sizeof(*src_line));
+ if (dst_cols - src_cols)
+ memset32(dst_line + src_cols, ' ', dst_cols - src_cols);
+ src_top_row++;
+ dst_row++;
+ }
+ while (dst_row < dst_rows) {
+ u32 *dst_line = dst_lines[dst_row];
+
+ memset32(dst_line, ' ', dst_cols);
+ dst_row++;
+ }
}
-static void scrdown(struct vc_data *vc, unsigned int t, unsigned int b, int nr)
+/*
+ * Called from vcs_read() to make sure unicode screen retrieval is possible.
+ * This will initialize the unicode screen buffer if not already done.
+ * This returns 0 if OK, or a negative error code otherwise.
+ * In particular, -ENODATA is returned if the console is not in UTF-8 mode.
+ */
+int vc_uniscr_check(struct vc_data *vc)
{
- unsigned short *s;
- unsigned int step;
+ u32 **uni_lines;
+ unsigned short *p;
+ int x, y, mask;
+
+ WARN_CONSOLE_UNLOCKED();
+
+ if (!vc->vc_utf)
+ return -ENODATA;
+
+ if (vc->vc_uni_lines)
+ return 0;
- if (t+nr >= b)
- nr = b - t - 1;
- if (b > vc->vc_rows || t >= b || nr < 1)
+ uni_lines = vc_uniscr_alloc(vc->vc_cols, vc->vc_rows);
+ if (!uni_lines)
+ return -ENOMEM;
+
+ /*
+ * Let's populate it initially with (imperfect) reverse translation.
+ * This is the next best thing we can do short of having it enabled
+ * from the start even when no users rely on this functionality. True
+ * unicode content will be available after a complete screen refresh.
+ */
+ p = (unsigned short *)vc->vc_origin;
+ mask = vc->vc_hi_font_mask | 0xff;
+ for (y = 0; y < vc->vc_rows; y++) {
+ u32 *line = uni_lines[y];
+ for (x = 0; x < vc->vc_cols; x++) {
+ u16 glyph = scr_readw(p++) & mask;
+ line[x] = inverse_translate(vc, glyph, true);
+ }
+ }
+
+ vc->vc_uni_lines = uni_lines;
+
+ return 0;
+}
+
+/*
+ * Called from vcs_read() to get the unicode data from the screen.
+ * This must be preceded by a successful call to vc_uniscr_check() once
+ * the console lock has been taken.
+ */
+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;
+ unsigned long pos;
+
+ if (WARN_ON_ONCE(!uni_lines))
+ return;
+
+ pos = (unsigned long)screenpos(vc, offset, viewed);
+ if (pos >= vc->vc_origin && pos < vc->vc_scr_end) {
+ /*
+ * Desired position falls in the main screen buffer.
+ * However the actual row/col might be different if
+ * scrollback is active.
+ */
+ row = (pos - vc->vc_origin) / vc->vc_size_row;
+ col = ((pos - vc->vc_origin) % vc->vc_size_row) / 2;
+ memcpy(dest, &uni_lines[row][col], nr * sizeof(u32));
+ } else {
+ /*
+ * Scrollback is active. For now let's simply backtranslate
+ * the screen glyphs until the unicode screen buffer does
+ * synchronize with console display drivers for a scrollback
+ * buffer of its own.
+ */
+ u16 *p = (u16 *)pos;
+ int mask = vc->vc_hi_font_mask | 0xff;
+ u32 *uni_buf = dest;
+ while (nr--) {
+ u16 glyph = scr_readw(p++) & mask;
+ *uni_buf++ = inverse_translate(vc, glyph, true);
+ }
+ }
+}
+
+static void con_scroll(struct vc_data *vc, unsigned int top,
+ unsigned int bottom, enum con_scroll dir,
+ unsigned int nr)
+{
+ unsigned int rows = bottom - top;
+ u16 *clear, *dst, *src;
+
+ if (top + nr >= bottom)
+ nr = rows - 1;
+ if (bottom > vc->vc_rows || top >= bottom || nr < 1)
return;
- if (CON_IS_VISIBLE(vc) && vc->vc_sw->con_scroll(vc, t, b, SM_DOWN, nr))
+
+ vc_uniscr_scroll(vc, top, bottom, dir, nr);
+ if (con_is_visible(vc) &&
+ vc->vc_sw->con_scroll(vc, top, bottom, dir, nr))
return;
- s = (unsigned short *)(vc->vc_origin + vc->vc_size_row * t);
- step = vc->vc_cols * nr;
- scr_memmovew(s + step, s, (b - t - nr) * vc->vc_size_row);
- scr_memsetw(s, vc->vc_video_erase_char, 2 * step);
+
+ src = clear = (u16 *)(vc->vc_origin + vc->vc_size_row * top);
+ dst = (u16 *)(vc->vc_origin + vc->vc_size_row * (top + nr));
+
+ if (dir == SM_UP) {
+ clear = src + (rows - nr) * vc->vc_cols;
+ 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);
}
static void do_update_region(struct vc_data *vc, unsigned long start, int count)
{
-#ifndef VT_BUF_VRAM_ONLY
unsigned int xx, yy, offset;
- u16 *p;
+ u16 *p = (u16 *)start;
+
+ offset = (start - vc->vc_origin) / 2;
+ xx = offset % vc->vc_cols;
+ yy = offset / vc->vc_cols;
- p = (u16 *) start;
- if (!vc->vc_sw->con_getxy) {
- offset = (start - vc->vc_origin) / 2;
- xx = offset % vc->vc_cols;
- yy = offset / vc->vc_cols;
- } else {
- int nxx, nyy;
- start = vc->vc_sw->con_getxy(vc, start, &nxx, &nyy);
- xx = nxx; yy = nyy;
- }
for(;;) {
u16 attrib = scr_readw(p) & 0xff00;
int startx = xx;
@@ -379,35 +630,31 @@ static void do_update_region(struct vc_data *vc, unsigned long start, int count)
break;
xx = 0;
yy++;
- if (vc->vc_sw->con_getxy) {
- p = (u16 *)start;
- start = vc->vc_sw->con_getxy(vc, start, NULL, NULL);
- }
}
-#endif
}
void update_region(struct vc_data *vc, unsigned long start, int count)
{
WARN_CONSOLE_UNLOCKED();
- if (DO_UPDATE(vc)) {
+ if (con_should_update(vc)) {
hide_cursor(vc);
do_update_region(vc, start, count);
set_cursor(vc);
}
}
+EXPORT_SYMBOL(update_region);
/* Structure of attributes is hardware-dependent */
-static u8 build_attr(struct vc_data *vc, u8 _color, u8 _intensity, u8 _blink,
- u8 _underline, u8 _reverse, u8 _italic)
+static u8 build_attr(struct vc_data *vc, u8 _color,
+ enum vc_intensity _intensity, bool _blink, bool _underline,
+ bool _reverse, bool _italic)
{
if (vc->vc_sw->con_build_attr)
return vc->vc_sw->con_build_attr(vc, _color, _intensity,
_blink, _underline, _reverse, _italic);
-#ifndef VT_BUF_VRAM_ONLY
/*
* ++roman: I completely changed the attribute format for monochrome
* mode (!can_do_color). The formerly used MDA (monochrome display
@@ -422,52 +669,50 @@ static u8 build_attr(struct vc_data *vc, u8 _color, u8 _intensity, u8 _blink,
u8 a = _color;
if (!vc->vc_can_do_color)
return _intensity |
- (_italic ? 2 : 0) |
- (_underline ? 4 : 0) |
- (_reverse ? 8 : 0) |
- (_blink ? 0x80 : 0);
+ (_italic << 1) |
+ (_underline << 2) |
+ (_reverse << 3) |
+ (_blink << 7);
if (_italic)
a = (a & 0xF0) | vc->vc_itcolor;
else if (_underline)
a = (a & 0xf0) | vc->vc_ulcolor;
- else if (_intensity == 0)
- a = (a & 0xf0) | vc->vc_ulcolor;
+ else if (_intensity == VCI_HALF_BRIGHT)
+ a = (a & 0xf0) | vc->vc_halfcolor;
if (_reverse)
- a = ((a) & 0x88) | ((((a) >> 4) | ((a) << 4)) & 0x77);
+ a = (a & 0x88) | (((a >> 4) | (a << 4)) & 0x77);
if (_blink)
a ^= 0x80;
- if (_intensity == 2)
+ if (_intensity == VCI_BOLD)
a ^= 0x08;
if (vc->vc_hi_font_mask == 0x100)
a <<= 1;
return a;
}
-#else
- return 0;
-#endif
}
static void update_attr(struct vc_data *vc)
{
- vc->vc_attr = build_attr(vc, vc->vc_color, vc->vc_intensity,
- vc->vc_blink, vc->vc_underline,
- vc->vc_reverse ^ vc->vc_decscnm, vc->vc_italic);
- vc->vc_video_erase_char = (build_attr(vc, vc->vc_color, 1, vc->vc_blink, 0, vc->vc_decscnm, 0) << 8) | ' ';
+ vc->vc_attr = build_attr(vc, vc->state.color, vc->state.intensity,
+ vc->state.blink, vc->state.underline,
+ 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);
}
/* Note: inverting the screen twice should revert to the original state */
-void invert_screen(struct vc_data *vc, int offset, int count, int viewed)
+void invert_screen(struct vc_data *vc, int offset, int count, bool viewed)
{
- unsigned short *p;
+ u16 *p;
WARN_CONSOLE_UNLOCKED();
count /= 2;
p = screenpos(vc, offset, viewed);
- if (vc->vc_sw->con_invert_region)
+ if (vc->vc_sw->con_invert_region) {
vc->vc_sw->con_invert_region(vc, p, count);
-#ifndef VT_BUF_VRAM_ONLY
- else {
+ } else {
u16 *q = p;
int cnt = count;
u16 a;
@@ -482,22 +727,27 @@ void invert_screen(struct vc_data *vc, int offset, int count, int viewed)
} else if (vc->vc_hi_font_mask == 0x100) {
while (cnt--) {
a = scr_readw(q);
- a = ((a) & 0x11ff) | (((a) & 0xe000) >> 4) | (((a) & 0x0e00) << 4);
+ 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);
+ a = (a & 0x88ff) |
+ ((a & 0x7000) >> 4) |
+ ((a & 0x0700) << 4);
scr_writew(a, q);
q++;
}
}
}
-#endif
- if (DO_UPDATE(vc))
+
+ if (con_should_update(vc))
do_update_region(vc, (unsigned long) p, count);
+ notify_update(vc);
}
/* used by selection: complement pointer position */
@@ -511,9 +761,10 @@ 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, 1));
- if (DO_UPDATE(vc))
- vc->vc_sw->con_putc(vc, old, oldy, oldx);
+ scr_writew(old, screenpos(vc, old_offset, true));
+ if (con_should_update(vc))
+ con_putc(vc, old, oldy, oldx);
+ notify_update(vc);
}
old_offset = offset;
@@ -521,94 +772,100 @@ void complement_pos(struct vc_data *vc, int offset)
if (offset != -1 && offset >= 0 &&
offset < vc->vc_screenbuf_size) {
unsigned short new;
- unsigned short *p;
- p = screenpos(vc, offset, 1);
+ u16 *p = screenpos(vc, offset, true);
old = scr_readw(p);
new = old ^ vc->vc_complement_mask;
scr_writew(new, p);
- if (DO_UPDATE(vc)) {
+ if (con_should_update(vc)) {
oldx = (offset >> 1) % vc->vc_cols;
oldy = (offset >> 1) / vc->vc_cols;
- vc->vc_sw->con_putc(vc, new, oldy, oldx);
+ con_putc(vc, new, oldy, oldx);
}
+ notify_update(vc);
}
-
}
static void insert_char(struct vc_data *vc, unsigned int nr)
{
unsigned short *p = (unsigned short *) vc->vc_pos;
- scr_memmovew(p + nr, p, (vc->vc_cols - vc->vc_x - nr) * 2);
+ 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);
vc->vc_need_wrap = 0;
- if (DO_UPDATE(vc))
+ if (con_should_update(vc))
do_update_region(vc, (unsigned long) p,
- vc->vc_cols - vc->vc_x);
+ vc->vc_cols - vc->state.x);
}
static void delete_char(struct vc_data *vc, unsigned int nr)
{
unsigned short *p = (unsigned short *) vc->vc_pos;
- scr_memcpyw(p, p + nr, (vc->vc_cols - vc->vc_x - nr) * 2);
- scr_memsetw(p + vc->vc_cols - vc->vc_x - nr, vc->vc_video_erase_char,
+ 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);
vc->vc_need_wrap = 0;
- if (DO_UPDATE(vc))
+ if (con_should_update(vc))
do_update_region(vc, (unsigned long) p,
- vc->vc_cols - vc->vc_x);
+ vc->vc_cols - vc->state.x);
}
-static int softcursor_original;
+static int softcursor_original = -1;
static void add_softcursor(struct vc_data *vc)
{
int i = scr_readw((u16 *) vc->vc_pos);
u32 type = vc->vc_cursor_type;
- if (! (type & 0x10)) return;
- if (softcursor_original != -1) return;
+ if (!(type & CUR_SW))
+ return;
+ if (softcursor_original != -1)
+ return;
softcursor_original = i;
- i |= ((type >> 8) & 0xff00 );
- i ^= ((type) & 0xff00 );
- if ((type & 0x20) && ((softcursor_original & 0x7000) == (i & 0x7000))) i ^= 0x7000;
- if ((type & 0x40) && ((i & 0x700) == ((i & 0x7000) >> 4))) i ^= 0x0700;
- scr_writew(i, (u16 *) vc->vc_pos);
- if (DO_UPDATE(vc))
- vc->vc_sw->con_putc(vc, i, vc->vc_y, vc->vc_x);
+ i |= CUR_SET(type);
+ i ^= CUR_CHANGE(type);
+ if ((type & CUR_ALWAYS_BG) &&
+ (softcursor_original & CUR_BG) == (i & CUR_BG))
+ 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);
+ if (con_should_update(vc))
+ con_putc(vc, i, vc->state.y, vc->state.x);
}
static void hide_softcursor(struct vc_data *vc)
{
if (softcursor_original != -1) {
scr_writew(softcursor_original, (u16 *)vc->vc_pos);
- if (DO_UPDATE(vc))
- vc->vc_sw->con_putc(vc, softcursor_original,
- vc->vc_y, vc->vc_x);
+ if (con_should_update(vc))
+ con_putc(vc, softcursor_original, vc->state.y,
+ vc->state.x);
softcursor_original = -1;
}
}
static void hide_cursor(struct vc_data *vc)
{
- if (vc == sel_cons)
+ if (vc_is_sel(vc))
clear_selection();
- vc->vc_sw->con_cursor(vc, CM_ERASE);
+
+ vc->vc_sw->con_cursor(vc, false);
hide_softcursor(vc);
}
static void set_cursor(struct vc_data *vc)
{
- if (!IS_FG(vc) || console_blanked ||
- vc->vc_mode == KD_GRAPHICS)
+ if (!con_is_fg(vc) || console_blanked || vc->vc_mode == KD_GRAPHICS)
return;
if (vc->vc_deccm) {
- if (vc == sel_cons)
+ if (vc_is_sel(vc))
clear_selection();
add_softcursor(vc);
- if ((vc->vc_cursor_type & 0x0f) != 1)
- vc->vc_sw->con_cursor(vc, CM_DRAW);
+ if (CUR_SIZE(vc->vc_cursor_type) != CUR_NONE)
+ vc->vc_sw->con_cursor(vc, true);
} else
hide_cursor(vc);
}
@@ -617,16 +874,17 @@ static void set_origin(struct vc_data *vc)
{
WARN_CONSOLE_UNLOCKED();
- if (!CON_IS_VISIBLE(vc) ||
+ if (!con_is_visible(vc) ||
!vc->vc_sw->con_set_origin ||
!vc->vc_sw->con_set_origin(vc))
vc->vc_origin = (unsigned long)vc->vc_screenbuf;
vc->vc_visible_origin = vc->vc_origin;
vc->vc_scr_end = vc->vc_origin + vc->vc_screenbuf_size;
- vc->vc_pos = vc->vc_origin + vc->vc_size_row * vc->vc_y + 2 * vc->vc_x;
+ vc->vc_pos = vc->vc_origin + vc->vc_size_row * vc->state.y +
+ 2 * vc->state.x;
}
-static inline void save_screen(struct vc_data *vc)
+static void save_screen(struct vc_data *vc)
{
WARN_CONSOLE_UNLOCKED();
@@ -634,6 +892,25 @@ static inline void save_screen(struct vc_data *vc)
vc->vc_sw->con_save_screen(vc);
}
+static void flush_scrollback(struct vc_data *vc)
+{
+ WARN_CONSOLE_UNLOCKED();
+
+ set_origin(vc);
+ if (!con_is_visible(vc))
+ return;
+
+ /*
+ * The legacy way for flushing the scrollback buffer is to use a side
+ * effect of the con_switch method. We do it only on the foreground
+ * console as background consoles have no scrollback buffers in that
+ * case and we obviously don't want to switch to them.
+ */
+ hide_cursor(vc);
+ vc->vc_sw->con_switch(vc);
+ set_cursor(vc);
+}
+
/*
* Redrawing of screen
*/
@@ -665,12 +942,12 @@ void redraw_screen(struct vc_data *vc, int is_switch)
struct vc_data *old_vc = vc_cons[fg_console].d;
if (old_vc == vc)
return;
- if (!CON_IS_VISIBLE(vc))
+ if (!con_is_visible(vc))
redraw = 1;
*vc->vc_display_fg = vc;
fg_console = vc->vc_num;
hide_cursor(old_vc);
- if (!CON_IS_VISIBLE(old_vc)) {
+ if (!con_is_visible(old_vc)) {
save_screen(old_vc);
set_origin(old_vc);
}
@@ -682,7 +959,7 @@ void redraw_screen(struct vc_data *vc, int is_switch)
}
if (redraw) {
- int update;
+ bool update;
int old_was_color = vc->vc_can_do_color;
set_origin(vc);
@@ -699,18 +976,16 @@ void redraw_screen(struct vc_data *vc, int is_switch)
clear_buffer_attributes(vc);
}
- /* Forcibly update if we're panicing */
- if ((update && vc->vc_mode != KD_GRAPHICS) ||
- vt_force_oops_output(vc))
+ if (update && vc->vc_mode != KD_GRAPHICS)
do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
}
set_cursor(vc);
if (is_switch) {
- set_leds();
- compute_shiftstate();
+ vt_set_leds_compute_shiftstate();
notify_update(vc);
}
}
+EXPORT_SYMBOL(redraw_screen);
/*
* Allocation, freeing and resizing of VTs.
@@ -721,25 +996,27 @@ int vc_cons_allocated(unsigned int i)
return (i < MAX_NR_CONSOLES && vc_cons[i].d);
}
-static void visual_init(struct vc_data *vc, int num, int init)
+static void visual_init(struct vc_data *vc, int num, bool init)
{
/* ++Geert: vc->vc_sw->con_init determines console size */
if (vc->vc_sw)
module_put(vc->vc_sw->owner);
vc->vc_sw = conswitchp;
-#ifndef VT_SINGLE_DRIVER
+
if (con_driver_map[num])
vc->vc_sw = con_driver_map[num];
-#endif
+
__module_get(vc->vc_sw->owner);
vc->vc_num = num;
vc->vc_display_fg = &master_display_fg;
- vc->vc_uni_pagedir_loc = &vc->vc_uni_pagedir;
- vc->vc_uni_pagedir = 0;
+ if (vc->uni_pagedict_loc)
+ con_free_unimap(vc);
+ vc->uni_pagedict_loc = &vc->uni_pagedict;
+ vc->uni_pagedict = NULL;
vc->vc_hi_font_mask = 0;
vc->vc_complement_mask = 0;
vc->vc_can_do_color = 0;
- vc->vc_panic_force_write = false;
+ vc->vc_cur_blink_ms = DEFAULT_CURSOR_BLINK_MS;
vc->vc_sw->con_init(vc, init);
if (!vc->vc_complement_mask)
vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
@@ -748,109 +1025,132 @@ static void visual_init(struct vc_data *vc, int num, int init)
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);
+ module_put(vc->vc_sw->owner);
+}
+
+static void vc_port_destruct(struct tty_port *port)
+{
+ struct vc_data *vc = container_of(port, struct vc_data, port);
+
+ kfree(vc);
+}
+
+static const struct tty_port_operations vc_port_ops = {
+ .destruct = vc_port_destruct,
+};
+
+/*
+ * Change # of rows and columns (0 means unchanged/the size of fg_console)
+ * [this is to be used together with some user program
+ * like resize that changes the hardware videomode]
+ */
+#define VC_MAXCOL (32767)
+#define VC_MAXROW (32767)
+
int vc_allocate(unsigned int currcons) /* return 0 on success */
{
+ struct vt_notifier_param param;
+ struct vc_data *vc;
+ int err;
+
WARN_CONSOLE_UNLOCKED();
if (currcons >= MAX_NR_CONSOLES)
return -ENXIO;
- if (!vc_cons[currcons].d) {
- struct vc_data *vc;
- struct vt_notifier_param param;
-
- /* prevent users from taking too much memory */
- if (currcons >= MAX_NR_USER_CONSOLES && !capable(CAP_SYS_RESOURCE))
- return -EPERM;
-
- /* due to the granularity of kmalloc, we waste some memory here */
- /* the alloc is done in two steps, to optimize the common situation
- of a 25x80 console (structsize=216, screenbuf_size=4000) */
- /* although the numbers above are not valid since long ago, the
- point is still up-to-date and the comment still has its value
- even if only as a historical artifact. --mj, July 1998 */
- param.vc = vc = kzalloc(sizeof(struct vc_data), GFP_KERNEL);
- if (!vc)
+
+ if (vc_cons[currcons].d)
+ return 0;
+
+ /* due to the granularity of kmalloc, we waste some memory here */
+ /* the alloc is done in two steps, to optimize the common situation
+ of a 25x80 console (structsize=216, screenbuf_size=4000) */
+ /* although the numbers above are not valid since long ago, the
+ point is still up-to-date and the comment still has its value
+ even if only as a historical artifact. --mj, July 1998 */
+ param.vc = vc = kzalloc(sizeof(struct vc_data), GFP_KERNEL);
+ if (!vc)
return -ENOMEM;
- vc_cons[currcons].d = vc;
- tty_port_init(&vc->port);
- INIT_WORK(&vc_cons[currcons].SAK_work, vc_SAK);
- visual_init(vc, currcons, 1);
- if (!*vc->vc_uni_pagedir_loc)
+
+ vc_cons[currcons].d = vc;
+ tty_port_init(&vc->port);
+ vc->port.ops = &vc_port_ops;
+ INIT_WORK(&vc_cons[currcons].SAK_work, vc_SAK);
+
+ visual_init(vc, currcons, true);
+
+ if (!*vc->uni_pagedict_loc)
con_set_default_unimap(vc);
- vc->vc_screenbuf = kmalloc(vc->vc_screenbuf_size, GFP_KERNEL);
- if (!vc->vc_screenbuf) {
- kfree(vc);
- vc_cons[currcons].d = NULL;
- return -ENOMEM;
- }
- /* If no drivers have overridden us and the user didn't pass a
- boot option, default to displaying the cursor */
- if (global_cursor_default == -1)
- global_cursor_default = 1;
+ err = -EINVAL;
+ if (vc->vc_cols > VC_MAXCOL || vc->vc_rows > VC_MAXROW ||
+ vc->vc_screenbuf_size > KMALLOC_MAX_SIZE || !vc->vc_screenbuf_size)
+ goto err_free;
+ err = -ENOMEM;
+ vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_KERNEL);
+ if (!vc->vc_screenbuf)
+ goto err_free;
+
+ /* If no drivers have overridden us and the user didn't pass a
+ boot option, default to displaying the cursor */
+ if (global_cursor_default == -1)
+ global_cursor_default = 1;
+
+ vc_init(vc, 1);
+ vcs_make_sysfs(currcons);
+ atomic_notifier_call_chain(&vt_notifier_list, VT_ALLOCATE, &param);
- vc_init(vc, vc->vc_rows, vc->vc_cols, 1);
- vcs_make_sysfs(currcons);
- atomic_notifier_call_chain(&vt_notifier_list, VT_ALLOCATE, &param);
- }
return 0;
+err_free:
+ visual_deinit(vc);
+ kfree(vc);
+ vc_cons[currcons].d = NULL;
+ return err;
}
static inline int resize_screen(struct vc_data *vc, int width, int height,
- int user)
+ bool from_user)
{
/* Resizes the resolution of the display adapater */
int err = 0;
- if (vc->vc_mode != KD_GRAPHICS && vc->vc_sw->con_resize)
- err = vc->vc_sw->con_resize(vc, width, height, user);
+ if (vc->vc_sw->con_resize)
+ err = vc->vc_sw->con_resize(vc, width, height, from_user);
return err;
}
-/*
- * Change # of rows and columns (0 means unchanged/the size of fg_console)
- * [this is to be used together with some user program
- * like resize that changes the hardware videomode]
- */
-#define VC_RESIZE_MAXCOL (32767)
-#define VC_RESIZE_MAXROW (32767)
-
/**
- * vc_do_resize - resizing method for the tty
- * @tty: tty being resized
- * @real_tty: real tty (different to tty if a pty/tty pair)
- * @vc: virtual console private data
- * @cols: columns
- * @lines: lines
+ * vc_do_resize - resizing method for the tty
+ * @tty: tty being resized
+ * @vc: virtual console private data
+ * @cols: columns
+ * @lines: lines
+ * @from_user: invoked by a user?
*
- * Resize a virtual console, clipping according to the actual constraints.
- * If the caller passes a tty structure then update the termios winsize
- * information and perform any necessary signal handling.
+ * Resize a virtual console, clipping according to the actual constraints. If
+ * the caller passes a tty structure then update the termios winsize
+ * information and perform any necessary signal handling.
*
- * Caller must hold the console semaphore. Takes the termios mutex and
- * ctrl_lock of the tty IFF a tty is passed.
+ * Locking: Caller must hold the console semaphore. Takes the termios rwsem and
+ * ctrl.lock of the tty IFF a tty is passed.
*/
-
static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
- unsigned int cols, unsigned int lines)
+ unsigned int cols, unsigned int lines, bool from_user)
{
unsigned long old_origin, new_origin, new_scr_end, rlth, rrem, err = 0;
unsigned long end;
- unsigned int old_rows, old_row_size;
+ unsigned int old_rows, old_row_size, first_copied_row;
unsigned int new_cols, new_rows, new_row_size, new_screen_size;
- unsigned int user;
- unsigned short *newscreen;
+ unsigned short *oldscreen, *newscreen;
+ u32 **new_uniscr = NULL;
WARN_CONSOLE_UNLOCKED();
- if (!vc)
- return -ENXIO;
-
- user = vc->vc_resize_user;
- vc->vc_resize_user = 0;
-
- if (cols > VC_RESIZE_MAXCOL || lines > VC_RESIZE_MAXROW)
+ if (cols > VC_MAXCOL || lines > VC_MAXROW)
return -EINVAL;
new_cols = (cols ? cols : vc->vc_cols);
@@ -858,19 +1158,50 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
new_row_size = new_cols << 1;
new_screen_size = new_row_size * new_rows;
- if (new_cols == vc->vc_cols && new_rows == vc->vc_rows)
- return 0;
+ if (new_cols == vc->vc_cols && new_rows == vc->vc_rows) {
+ /*
+ * This function is being called here to cover the case
+ * where the userspace calls the FBIOPUT_VSCREENINFO twice,
+ * passing the same fb_var_screeninfo containing the fields
+ * yres/xres equal to a number non-multiple of vc_font.height
+ * and yres_virtual/xres_virtual equal to number lesser than the
+ * vc_font.height and yres/xres.
+ * In the second call, the struct fb_var_screeninfo isn't
+ * being modified by the underlying driver because of the
+ * if above, and this causes the fbcon_display->vrows to become
+ * negative and it eventually leads to out-of-bound
+ * access by the imageblit function.
+ * To give the correct values to the struct and to not have
+ * to deal with possible errors from the code below, we call
+ * the resize_screen here as well.
+ */
+ return resize_screen(vc, new_cols, new_rows, from_user);
+ }
- newscreen = kmalloc(new_screen_size, GFP_USER);
+ if (new_screen_size > KMALLOC_MAX_SIZE || !new_screen_size)
+ return -EINVAL;
+ newscreen = kzalloc(new_screen_size, GFP_USER);
if (!newscreen)
return -ENOMEM;
+ if (vc->vc_uni_lines) {
+ new_uniscr = vc_uniscr_alloc(new_cols, new_rows);
+ if (!new_uniscr) {
+ kfree(newscreen);
+ return -ENOMEM;
+ }
+ }
+
+ if (vc_is_sel(vc))
+ clear_selection();
+
old_rows = vc->vc_rows;
old_row_size = vc->vc_size_row;
- err = resize_screen(vc, new_cols, new_rows, user);
+ err = resize_screen(vc, new_cols, new_rows, from_user);
if (err) {
kfree(newscreen);
+ vc_uniscr_free(new_uniscr);
return err;
}
@@ -885,24 +1216,30 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
new_origin = (long) newscreen;
new_scr_end = new_origin + new_screen_size;
- if (vc->vc_y > new_rows) {
- if (old_rows - vc->vc_y < new_rows) {
+ if (vc->state.y > new_rows) {
+ if (old_rows - vc->state.y < new_rows) {
/*
* Cursor near the bottom, copy contents from the
* bottom of buffer
*/
- old_origin += (old_rows - new_rows) * old_row_size;
+ first_copied_row = (old_rows - new_rows);
} else {
/*
* Cursor is in no man's land, copy 1/2 screenful
* from the top and bottom of cursor position
*/
- old_origin += (vc->vc_y - new_rows/2) * old_row_size;
+ first_copied_row = (vc->state.y - new_rows/2);
}
- }
-
+ old_origin += first_copied_row * old_row_size;
+ } else
+ first_copied_row = 0;
end = old_origin + old_row_size * min(old_rows, new_rows);
+ vc_uniscr_copy_area(new_uniscr, new_cols, new_rows,
+ vc->vc_uni_lines, rlth/2, first_copied_row,
+ min(old_rows, new_rows));
+ vc_uniscr_set(vc, new_uniscr);
+
update_attr(vc);
while (old_origin < end) {
@@ -917,15 +1254,16 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
if (new_scr_end > new_origin)
scr_memsetw((void *)new_origin, vc->vc_video_erase_char,
new_scr_end - new_origin);
- kfree(vc->vc_screenbuf);
+ oldscreen = vc->vc_screenbuf;
vc->vc_screenbuf = newscreen;
vc->vc_screenbuf_size = new_screen_size;
set_origin(vc);
+ kfree(oldscreen);
/* do part of a reset_terminal() */
vc->vc_top = 0;
vc->vc_bottom = vc->vc_rows;
- gotoxy(vc, vc->vc_x, vc->vc_y);
+ gotoxy(vc, vc->state.x, vc->state.y);
save_cur(vc);
if (tty) {
@@ -939,50 +1277,50 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
tty_do_resize(tty, &ws);
}
- if (CON_IS_VISIBLE(vc))
+ if (con_is_visible(vc))
update_screen(vc);
vt_event_post(VT_EVENT_RESIZE, vc->vc_num, vc->vc_num);
+ notify_update(vc);
return err;
}
/**
- * vc_resize - resize a VT
- * @vc: virtual console
- * @cols: columns
- * @rows: rows
+ * __vc_resize - resize a VT
+ * @vc: virtual console
+ * @cols: columns
+ * @rows: rows
+ * @from_user: invoked by a user?
*
- * Resize a virtual console as seen from the console end of things. We
- * use the common vc_do_resize methods to update the structures. The
- * caller must hold the console sem to protect console internals and
- * vc->port.tty
+ * Resize a virtual console as seen from the console end of things. We use the
+ * common vc_do_resize() method to update the structures.
+ *
+ * Locking: The caller must hold the console sem to protect console internals
+ * and @vc->port.tty.
*/
-
-int vc_resize(struct vc_data *vc, unsigned int cols, unsigned int rows)
+int __vc_resize(struct vc_data *vc, unsigned int cols, unsigned int rows,
+ bool from_user)
{
- return vc_do_resize(vc->port.tty, vc, cols, rows);
+ return vc_do_resize(vc->port.tty, vc, cols, rows, from_user);
}
+EXPORT_SYMBOL(__vc_resize);
/**
- * vt_resize - resize a VT
- * @tty: tty to resize
- * @ws: winsize attributes
+ * vt_resize - resize a VT
+ * @tty: tty to resize
+ * @ws: winsize attributes
*
- * Resize a virtual terminal. This is called by the tty layer as we
- * register our own handler for resizing. The mutual helper does all
- * the actual work.
+ * Resize a virtual terminal. This is called by the tty layer as we register
+ * our own handler for resizing. The mutual helper does all the actual work.
*
- * Takes the console sem and the called methods then take the tty
- * termios_mutex and the tty ctrl_lock in that order.
+ * Locking: Takes the console sem and the called methods then take the tty
+ * termios_rwsem and the tty ctrl.lock in that order.
*/
static int vt_resize(struct tty_struct *tty, struct winsize *ws)
{
struct vc_data *vc = tty->driver_data;
- int ret;
- console_lock();
- ret = vc_do_resize(tty, vc, ws->ws_col, ws->ws_row);
- console_unlock();
- return ret;
+ guard(console_lock)();
+ return vc_do_resize(tty, vc, ws->ws_col, ws->ws_row, false);
}
struct vc_data *vc_deallocate(unsigned int currcons)
@@ -997,11 +1335,16 @@ struct vc_data *vc_deallocate(unsigned int currcons)
param.vc = vc = vc_cons[currcons].d;
atomic_notifier_call_chain(&vt_notifier_list, VT_DEALLOCATE, &param);
vcs_remove_sysfs(currcons);
- vc->vc_sw->con_deinit(vc);
+ visual_deinit(vc);
+ con_free_unimap(vc);
put_pid(vc->vt_pid);
- module_put(vc->vc_sw->owner);
+ vc_uniscr_set(vc, NULL);
kfree(vc->vc_screenbuf);
vc_cons[currcons].d = NULL;
+ if (vc->vc_saved_screen != NULL) {
+ kfree(vc->vc_saved_screen);
+ vc->vc_saved_screen = NULL;
+ }
}
return vc;
}
@@ -1010,6 +1353,8 @@ struct vc_data *vc_deallocate(unsigned int currcons)
* VT102 emulator
*/
+enum { EPecma = 0, EPdec, EPeq, EPgt, EPlt};
+
#define set_kbd(vc, x) vt_set_kbd_mode_bit((vc)->vc_num, (x))
#define clr_kbd(vc, x) vt_clr_kbd_mode_bit((vc)->vc_num, (x))
#define is_kbd(vc, x) vt_get_kbd_mode_bit((vc)->vc_num, (x))
@@ -1019,26 +1364,31 @@ struct vc_data *vc_deallocate(unsigned int currcons)
#define kbdapplic VC_APPLIC
#define lnm VC_CRLF
-/*
- * this is what the terminal answers to a ESC-Z or csi0c query.
- */
-#define VT100ID "\033[?1;2c"
-#define VT102ID "\033[?6c"
-
-unsigned char color_table[] = { 0, 4, 2, 6, 1, 5, 3, 7,
+const unsigned char color_table[] = { 0, 4, 2, 6, 1, 5, 3, 7,
8,12,10,14, 9,13,11,15 };
+EXPORT_SYMBOL(color_table);
/* the default colour table, for VGA+ colour systems */
-int default_red[] = {0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,
- 0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff};
-int default_grn[] = {0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,
- 0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff};
-int default_blu[] = {0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,
- 0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff};
+unsigned char default_red[] = {
+ 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa,
+ 0x55, 0xff, 0x55, 0xff, 0x55, 0xff, 0x55, 0xff
+};
+module_param_array(default_red, byte, NULL, S_IRUGO | S_IWUSR);
+EXPORT_SYMBOL(default_red);
+
+unsigned char default_grn[] = {
+ 0x00, 0x00, 0xaa, 0x55, 0x00, 0x00, 0xaa, 0xaa,
+ 0x55, 0x55, 0xff, 0xff, 0x55, 0x55, 0xff, 0xff
+};
+module_param_array(default_grn, byte, NULL, S_IRUGO | S_IWUSR);
+EXPORT_SYMBOL(default_grn);
-module_param_array(default_red, int, NULL, S_IRUGO | S_IWUSR);
-module_param_array(default_grn, int, NULL, S_IRUGO | S_IWUSR);
-module_param_array(default_blu, int, NULL, S_IRUGO | S_IWUSR);
+unsigned char default_blu[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff
+};
+module_param_array(default_blu, byte, NULL, S_IRUGO | S_IWUSR);
+EXPORT_SYMBOL(default_blu);
/*
* gotoxy() must verify all boundaries, because the arguments
@@ -1050,12 +1400,12 @@ static void gotoxy(struct vc_data *vc, int new_x, int new_y)
int min_y, max_y;
if (new_x < 0)
- vc->vc_x = 0;
+ vc->state.x = 0;
else {
if (new_x >= vc->vc_cols)
- vc->vc_x = vc->vc_cols - 1;
+ vc->state.x = vc->vc_cols - 1;
else
- vc->vc_x = new_x;
+ vc->state.x = new_x;
}
if (vc->vc_decom) {
@@ -1066,12 +1416,13 @@ static void gotoxy(struct vc_data *vc, int new_x, int new_y)
max_y = vc->vc_rows;
}
if (new_y < min_y)
- vc->vc_y = min_y;
+ vc->state.y = min_y;
else if (new_y >= max_y)
- vc->vc_y = max_y - 1;
+ vc->state.y = max_y - 1;
else
- vc->vc_y = new_y;
- vc->vc_pos = vc->vc_origin + vc->vc_y * vc->vc_size_row + (vc->vc_x<<1);
+ vc->state.y = new_y;
+ vc->vc_pos = vc->vc_origin + vc->state.y * vc->vc_size_row +
+ (vc->state.x << 1);
vc->vc_need_wrap = 0;
}
@@ -1081,11 +1432,9 @@ static void gotoxay(struct vc_data *vc, int new_x, int new_y)
gotoxy(vc, new_x, vc->vc_decom ? (vc->vc_top + new_y) : new_y);
}
-void scrollback(struct vc_data *vc, int lines)
+void scrollback(struct vc_data *vc)
{
- if (!lines)
- lines = vc->vc_rows / 2;
- scrolldelta(-lines);
+ scrolldelta(-(vc->vc_rows / 2));
}
void scrollfront(struct vc_data *vc, int lines)
@@ -1100,10 +1449,10 @@ static void lf(struct vc_data *vc)
/* don't scroll if above bottom of scrolling region, or
* if below scrolling region
*/
- if (vc->vc_y + 1 == vc->vc_bottom)
- scrup(vc, vc->vc_top, vc->vc_bottom, 1);
- else if (vc->vc_y < vc->vc_rows - 1) {
- vc->vc_y++;
+ if (vc->state.y + 1 == vc->vc_bottom)
+ con_scroll(vc, vc->vc_top, vc->vc_bottom, SM_UP, 1);
+ else if (vc->state.y < vc->vc_rows - 1) {
+ vc->state.y++;
vc->vc_pos += vc->vc_size_row;
}
vc->vc_need_wrap = 0;
@@ -1115,10 +1464,10 @@ static void ri(struct vc_data *vc)
/* don't scroll if below top of scrolling region, or
* if above scrolling region
*/
- if (vc->vc_y == vc->vc_top)
- scrdown(vc, vc->vc_top, vc->vc_bottom, 1);
- else if (vc->vc_y > 0) {
- vc->vc_y--;
+ if (vc->state.y == vc->vc_top)
+ con_scroll(vc, vc->vc_top, vc->vc_bottom, SM_DOWN, 1);
+ else if (vc->state.y > 0) {
+ vc->state.y--;
vc->vc_pos -= vc->vc_size_row;
}
vc->vc_need_wrap = 0;
@@ -1126,16 +1475,16 @@ static void ri(struct vc_data *vc)
static inline void cr(struct vc_data *vc)
{
- vc->vc_pos -= vc->vc_x << 1;
- vc->vc_need_wrap = vc->vc_x = 0;
+ vc->vc_pos -= vc->state.x << 1;
+ vc->vc_need_wrap = vc->state.x = 0;
notify_write(vc, '\r');
}
static inline void bs(struct vc_data *vc)
{
- if (vc->vc_x) {
+ if (vc->state.x) {
vc->vc_pos -= 2;
- vc->vc_x--;
+ vc->state.x--;
vc->vc_need_wrap = 0;
notify_write(vc, '\b');
}
@@ -1146,89 +1495,228 @@ static inline void del(struct vc_data *vc)
/* ignored */
}
-static void csi_J(struct vc_data *vc, int vpar)
+enum CSI_J {
+ CSI_J_CURSOR_TO_END = 0,
+ CSI_J_START_TO_CURSOR = 1,
+ CSI_J_VISIBLE = 2,
+ CSI_J_FULL = 3,
+};
+
+static void csi_J(struct vc_data *vc, enum CSI_J vpar)
{
+ unsigned short *start;
unsigned int count;
- unsigned short * start;
switch (vpar) {
- case 0: /* erase from cursor to end of display */
- count = (vc->vc_scr_end - vc->vc_pos) >> 1;
- start = (unsigned short *)vc->vc_pos;
- break;
- case 1: /* erase from start to cursor */
- count = ((vc->vc_pos - vc->vc_origin) >> 1) + 1;
- start = (unsigned short *)vc->vc_origin;
- break;
- case 3: /* erase scroll-back buffer (and whole display) */
- scr_memsetw(vc->vc_screenbuf, vc->vc_video_erase_char,
- vc->vc_screenbuf_size >> 1);
- set_origin(vc);
- /* fall through */
- case 2: /* erase whole display */
- count = vc->vc_cols * vc->vc_rows;
- start = (unsigned short *)vc->vc_origin;
- break;
- default:
- return;
+ case CSI_J_CURSOR_TO_END:
+ vc_uniscr_clear_line(vc, vc->state.x,
+ 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;
+ 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;
+ start = (unsigned short *)vc->vc_origin;
+ break;
+ case CSI_J_FULL:
+ flush_scrollback(vc);
+ fallthrough;
+ case CSI_J_VISIBLE:
+ vc_uniscr_clear_lines(vc, 0, vc->vc_rows);
+ count = vc->vc_cols * vc->vc_rows;
+ start = (unsigned short *)vc->vc_origin;
+ break;
+ default:
+ return;
}
scr_memsetw(start, vc->vc_video_erase_char, 2 * count);
- if (DO_UPDATE(vc))
+ if (con_should_update(vc))
do_update_region(vc, (unsigned long) start, count);
vc->vc_need_wrap = 0;
}
-static void csi_K(struct vc_data *vc, int vpar)
+enum {
+ CSI_K_CURSOR_TO_LINEEND = 0,
+ CSI_K_LINESTART_TO_CURSOR = 1,
+ CSI_K_LINE = 2,
+};
+
+static void csi_K(struct vc_data *vc)
{
unsigned int count;
- unsigned short * start;
-
- switch (vpar) {
- case 0: /* erase from cursor to end of line */
- count = vc->vc_cols - vc->vc_x;
- start = (unsigned short *)vc->vc_pos;
- break;
- case 1: /* erase from start of line to cursor */
- start = (unsigned short *)(vc->vc_pos - (vc->vc_x << 1));
- count = vc->vc_x + 1;
- break;
- case 2: /* erase whole line */
- start = (unsigned short *)(vc->vc_pos - (vc->vc_x << 1));
- count = vc->vc_cols;
- break;
- default:
- return;
+ unsigned short *start = (unsigned short *)vc->vc_pos;
+ int offset;
+
+ switch (vc->vc_par[0]) {
+ case CSI_K_CURSOR_TO_LINEEND:
+ offset = 0;
+ count = vc->vc_cols - vc->state.x;
+ break;
+ case CSI_K_LINESTART_TO_CURSOR:
+ offset = -vc->state.x;
+ count = vc->state.x + 1;
+ break;
+ case CSI_K_LINE:
+ offset = -vc->state.x;
+ count = vc->vc_cols;
+ break;
+ default:
+ return;
}
- scr_memsetw(start, vc->vc_video_erase_char, 2 * count);
+ vc_uniscr_clear_line(vc, vc->state.x + offset, count);
+ scr_memsetw(start + offset, vc->vc_video_erase_char, 2 * count);
vc->vc_need_wrap = 0;
- if (DO_UPDATE(vc))
- do_update_region(vc, (unsigned long) start, count);
+ if (con_should_update(vc))
+ do_update_region(vc, (unsigned long)(start + offset), count);
}
-static void csi_X(struct vc_data *vc, int vpar) /* erase the following vpar positions */
+/* erase the following count positions */
+static void csi_X(struct vc_data *vc)
{ /* not vt100? */
- int count;
-
- if (!vpar)
- vpar++;
- count = (vpar > vc->vc_cols - vc->vc_x) ? (vc->vc_cols - vc->vc_x) : vpar;
+ 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);
- if (DO_UPDATE(vc))
- vc->vc_sw->con_clear(vc, vc->vc_y, vc->vc_x, 1, count);
+ if (con_should_update(vc))
+ vc->vc_sw->con_clear(vc, vc->state.y, vc->state.x, count);
vc->vc_need_wrap = 0;
}
static void default_attr(struct vc_data *vc)
{
- vc->vc_intensity = 1;
- vc->vc_italic = 0;
- vc->vc_underline = 0;
- vc->vc_reverse = 0;
- vc->vc_blink = 0;
- vc->vc_color = vc->vc_def_color;
+ vc->state.intensity = VCI_NORMAL;
+ vc->state.italic = false;
+ vc->state.underline = false;
+ vc->state.reverse = false;
+ vc->state.blink = false;
+ vc->state.color = vc->vc_def_color;
+}
+
+struct rgb { u8 r; u8 g; u8 b; };
+
+static void rgb_from_256(unsigned int i, struct rgb *c)
+{
+ if (i < 8) { /* Standard colours. */
+ c->r = i&1 ? 0xaa : 0x00;
+ c->g = i&2 ? 0xaa : 0x00;
+ c->b = i&4 ? 0xaa : 0x00;
+ } else if (i < 16) {
+ c->r = i&1 ? 0xff : 0x55;
+ c->g = i&2 ? 0xff : 0x55;
+ c->b = i&4 ? 0xff : 0x55;
+ } else if (i < 232) { /* 6x6x6 colour cube. */
+ i -= 16;
+ c->b = i % 6 * 255 / 6;
+ i /= 6;
+ c->g = i % 6 * 255 / 6;
+ i /= 6;
+ c->r = i * 255 / 6;
+ } else /* Grayscale ramp. */
+ c->r = c->g = c->b = i * 10 - 2312;
+}
+
+static void rgb_foreground(struct vc_data *vc, const struct rgb *c)
+{
+ u8 hue = 0, max = max3(c->r, c->g, c->b);
+
+ if (c->r > max / 2)
+ hue |= 4;
+ if (c->g > max / 2)
+ hue |= 2;
+ if (c->b > max / 2)
+ hue |= 1;
+
+ if (hue == 7 && max <= 0x55) {
+ hue = 0;
+ vc->state.intensity = VCI_BOLD;
+ } else if (max > 0xaa)
+ vc->state.intensity = VCI_BOLD;
+ else
+ vc->state.intensity = VCI_NORMAL;
+
+ vc->state.color = (vc->state.color & 0xf0) | hue;
}
+static void rgb_background(struct vc_data *vc, const struct rgb *c)
+{
+ /* For backgrounds, err on the dark side. */
+ vc->state.color = (vc->state.color & 0x0f)
+ | (c->r&0x80) >> 1 | (c->g&0x80) >> 2 | (c->b&0x80) >> 3;
+}
+
+/*
+ * ITU T.416 Higher colour modes. They break the usual properties of SGR codes
+ * and thus need to be detected and ignored by hand. That standard also
+ * wants : rather than ; as separators but sequences containing : are currently
+ * completely ignored by the parser.
+ *
+ * Subcommands 3 (CMY) and 4 (CMYK) are so insane there's no point in
+ * supporting them.
+ */
+static int vc_t416_color(struct vc_data *vc, int i,
+ void(*set_color)(struct vc_data *vc, const struct rgb *c))
+{
+ struct rgb c;
+
+ i++;
+ if (i > vc->vc_npar)
+ return i;
+
+ if (vc->vc_par[i] == 5 && i + 1 <= vc->vc_npar) {
+ /* 256 colours */
+ i++;
+ rgb_from_256(vc->vc_par[i], &c);
+ } else if (vc->vc_par[i] == 2 && i + 3 <= vc->vc_npar) {
+ /* 24 bit */
+ c.r = vc->vc_par[i + 1];
+ c.g = vc->vc_par[i + 2];
+ c.b = vc->vc_par[i + 3];
+ i += 3;
+ } else
+ return i;
+
+ set_color(vc, &c);
+
+ return i;
+}
+
+enum {
+ CSI_m_DEFAULT = 0,
+ CSI_m_BOLD = 1,
+ CSI_m_HALF_BRIGHT = 2,
+ CSI_m_ITALIC = 3,
+ CSI_m_UNDERLINE = 4,
+ CSI_m_BLINK = 5,
+ CSI_m_REVERSE = 7,
+ CSI_m_PRI_FONT = 10,
+ CSI_m_ALT_FONT1 = 11,
+ CSI_m_ALT_FONT2 = 12,
+ CSI_m_DOUBLE_UNDERLINE = 21,
+ CSI_m_NORMAL_INTENSITY = 22,
+ CSI_m_NO_ITALIC = 23,
+ CSI_m_NO_UNDERLINE = 24,
+ CSI_m_NO_BLINK = 25,
+ CSI_m_NO_REVERSE = 27,
+ CSI_m_FG_COLOR_BEG = 30,
+ CSI_m_FG_COLOR_END = 37,
+ CSI_m_FG_COLOR = 38,
+ CSI_m_DEFAULT_FG_COLOR = 39,
+ CSI_m_BG_COLOR_BEG = 40,
+ CSI_m_BG_COLOR_END = 47,
+ CSI_m_BG_COLOR = 48,
+ CSI_m_DEFAULT_BG_COLOR = 49,
+ CSI_m_BRIGHT_FG_COLOR_BEG = 90,
+ CSI_m_BRIGHT_FG_COLOR_END = 97,
+ CSI_m_BRIGHT_FG_COLOR_OFF = CSI_m_BRIGHT_FG_COLOR_BEG - CSI_m_FG_COLOR_BEG,
+ CSI_m_BRIGHT_BG_COLOR_BEG = 100,
+ CSI_m_BRIGHT_BG_COLOR_END = 107,
+ CSI_m_BRIGHT_BG_COLOR_OFF = CSI_m_BRIGHT_BG_COLOR_BEG - CSI_m_BG_COLOR_BEG,
+};
+
/* console_lock is held */
static void csi_m(struct vc_data *vc)
{
@@ -1236,366 +1724,500 @@ static void csi_m(struct vc_data *vc)
for (i = 0; i <= vc->vc_npar; i++)
switch (vc->vc_par[i]) {
- case 0: /* all attributes off */
- default_attr(vc);
- break;
- case 1:
- vc->vc_intensity = 2;
- break;
- case 2:
- vc->vc_intensity = 0;
- break;
- case 3:
- vc->vc_italic = 1;
- break;
- case 4:
- vc->vc_underline = 1;
- break;
- case 5:
- vc->vc_blink = 1;
- break;
- case 7:
- vc->vc_reverse = 1;
- break;
- case 10: /* ANSI X3.64-1979 (SCO-ish?)
- * Select primary font, don't display
- * control chars if defined, don't set
- * bit 8 on output.
- */
- vc->vc_translate = set_translate(vc->vc_charset == 0
- ? vc->vc_G0_charset
- : vc->vc_G1_charset, vc);
- vc->vc_disp_ctrl = 0;
- vc->vc_toggle_meta = 0;
- break;
- case 11: /* ANSI X3.64-1979 (SCO-ish?)
- * Select first alternate font, lets
- * chars < 32 be displayed as ROM chars.
- */
- vc->vc_translate = set_translate(IBMPC_MAP, vc);
- vc->vc_disp_ctrl = 1;
- vc->vc_toggle_meta = 0;
- break;
- case 12: /* ANSI X3.64-1979 (SCO-ish?)
- * Select second alternate font, toggle
- * high bit before displaying as ROM char.
- */
- vc->vc_translate = set_translate(IBMPC_MAP, vc);
- vc->vc_disp_ctrl = 1;
- vc->vc_toggle_meta = 1;
- break;
- case 21:
- case 22:
- vc->vc_intensity = 1;
- break;
- case 23:
- vc->vc_italic = 0;
- break;
- case 24:
- vc->vc_underline = 0;
- break;
- case 25:
- vc->vc_blink = 0;
- break;
- case 27:
- vc->vc_reverse = 0;
- break;
- case 38: /* ANSI X3.64-1979 (SCO-ish?)
- * Enables underscore, white foreground
- * with white underscore (Linux - use
- * default foreground).
- */
- vc->vc_color = (vc->vc_def_color & 0x0f) | (vc->vc_color & 0xf0);
- vc->vc_underline = 1;
- break;
- case 39: /* ANSI X3.64-1979 (SCO-ish?)
- * Disable underline option.
- * Reset colour to default? It did this
- * before...
- */
- vc->vc_color = (vc->vc_def_color & 0x0f) | (vc->vc_color & 0xf0);
- vc->vc_underline = 0;
- break;
- case 49:
- vc->vc_color = (vc->vc_def_color & 0xf0) | (vc->vc_color & 0x0f);
- break;
- default:
- if (vc->vc_par[i] >= 30 && vc->vc_par[i] <= 37)
- vc->vc_color = color_table[vc->vc_par[i] - 30]
- | (vc->vc_color & 0xf0);
- else if (vc->vc_par[i] >= 40 && vc->vc_par[i] <= 47)
- vc->vc_color = (color_table[vc->vc_par[i] - 40] << 4)
- | (vc->vc_color & 0x0f);
- break;
+ case CSI_m_DEFAULT: /* all attributes off */
+ default_attr(vc);
+ break;
+ case CSI_m_BOLD:
+ vc->state.intensity = VCI_BOLD;
+ break;
+ case CSI_m_HALF_BRIGHT:
+ vc->state.intensity = VCI_HALF_BRIGHT;
+ break;
+ case CSI_m_ITALIC:
+ vc->state.italic = true;
+ break;
+ case CSI_m_DOUBLE_UNDERLINE:
+ /*
+ * No console drivers support double underline, so
+ * convert it to a single underline.
+ */
+ case CSI_m_UNDERLINE:
+ vc->state.underline = true;
+ break;
+ case CSI_m_BLINK:
+ vc->state.blink = true;
+ break;
+ case CSI_m_REVERSE:
+ vc->state.reverse = true;
+ break;
+ case CSI_m_PRI_FONT: /* ANSI X3.64-1979 (SCO-ish?)
+ * Select primary font, don't display control chars if
+ * defined, don't set bit 8 on output.
+ */
+ vc->vc_translate = set_translate(vc->state.Gx_charset[vc->state.charset], vc);
+ vc->vc_disp_ctrl = 0;
+ vc->vc_toggle_meta = 0;
+ break;
+ case CSI_m_ALT_FONT1: /* ANSI X3.64-1979 (SCO-ish?)
+ * Select first alternate font, lets chars < 32 be
+ * displayed as ROM chars.
+ */
+ vc->vc_translate = set_translate(IBMPC_MAP, vc);
+ vc->vc_disp_ctrl = 1;
+ vc->vc_toggle_meta = 0;
+ break;
+ case CSI_m_ALT_FONT2: /* ANSI X3.64-1979 (SCO-ish?)
+ * Select second alternate font, toggle high bit
+ * before displaying as ROM char.
+ */
+ vc->vc_translate = set_translate(IBMPC_MAP, vc);
+ vc->vc_disp_ctrl = 1;
+ vc->vc_toggle_meta = 1;
+ break;
+ case CSI_m_NORMAL_INTENSITY:
+ vc->state.intensity = VCI_NORMAL;
+ break;
+ case CSI_m_NO_ITALIC:
+ vc->state.italic = false;
+ break;
+ case CSI_m_NO_UNDERLINE:
+ vc->state.underline = false;
+ break;
+ case CSI_m_NO_BLINK:
+ vc->state.blink = false;
+ break;
+ case CSI_m_NO_REVERSE:
+ vc->state.reverse = false;
+ break;
+ case CSI_m_FG_COLOR:
+ i = vc_t416_color(vc, i, rgb_foreground);
+ break;
+ case CSI_m_BG_COLOR:
+ i = vc_t416_color(vc, i, rgb_background);
+ break;
+ case CSI_m_DEFAULT_FG_COLOR:
+ vc->state.color = (vc->vc_def_color & 0x0f) |
+ (vc->state.color & 0xf0);
+ break;
+ case CSI_m_DEFAULT_BG_COLOR:
+ vc->state.color = (vc->vc_def_color & 0xf0) |
+ (vc->state.color & 0x0f);
+ break;
+ case CSI_m_BRIGHT_FG_COLOR_BEG ... CSI_m_BRIGHT_FG_COLOR_END:
+ vc->state.intensity = VCI_BOLD;
+ vc->vc_par[i] -= CSI_m_BRIGHT_FG_COLOR_OFF;
+ fallthrough;
+ case CSI_m_FG_COLOR_BEG ... CSI_m_FG_COLOR_END:
+ vc->vc_par[i] -= CSI_m_FG_COLOR_BEG;
+ vc->state.color = color_table[vc->vc_par[i]] |
+ (vc->state.color & 0xf0);
+ break;
+ case CSI_m_BRIGHT_BG_COLOR_BEG ... CSI_m_BRIGHT_BG_COLOR_END:
+ vc->vc_par[i] -= CSI_m_BRIGHT_BG_COLOR_OFF;
+ fallthrough;
+ case CSI_m_BG_COLOR_BEG ... CSI_m_BG_COLOR_END:
+ vc->vc_par[i] -= CSI_m_BG_COLOR_BEG;
+ vc->state.color = (color_table[vc->vc_par[i]] << 4) |
+ (vc->state.color & 0x0f);
+ break;
}
update_attr(vc);
}
-static void respond_string(const char *p, struct tty_port *port)
+static void respond_string(const char *p, size_t len, struct tty_port *port)
{
- while (*p) {
- tty_insert_flip_char(port, *p, 0);
- p++;
- }
- tty_schedule_flip(port);
+ tty_insert_flip_string(port, p, len);
+ tty_flip_buffer_push(port);
}
static void cursor_report(struct vc_data *vc, struct tty_struct *tty)
{
char buf[40];
+ int len;
- sprintf(buf, "\033[%d;%dR", vc->vc_y + (vc->vc_decom ? vc->vc_top + 1 : 1), vc->vc_x + 1);
- respond_string(buf, tty->port);
+ len = sprintf(buf, "\033[%d;%dR", vc->state.y +
+ (vc->vc_decom ? vc->vc_top + 1 : 1),
+ vc->state.x + 1);
+ respond_string(buf, len, tty->port);
}
static inline void status_report(struct tty_struct *tty)
{
- respond_string("\033[0n", tty->port); /* Terminal ok */
+ static const char teminal_ok[] = "\033[0n";
+
+ respond_string(teminal_ok, strlen(teminal_ok), tty->port);
}
static inline void respond_ID(struct tty_struct *tty)
{
- respond_string(VT102ID, tty->port);
+ /* terminal answer to an ESC-Z or csi0c query. */
+ static const char vt102_id[] = "\033[?6c";
+
+ respond_string(vt102_id, strlen(vt102_id), tty->port);
}
void mouse_report(struct tty_struct *tty, int butt, int mrx, int mry)
{
char buf[8];
+ int len;
- sprintf(buf, "\033[M%c%c%c", (char)(' ' + butt), (char)('!' + mrx),
- (char)('!' + mry));
- respond_string(buf, tty->port);
+ len = sprintf(buf, "\033[M%c%c%c", (char)(' ' + butt),
+ (char)('!' + mrx), (char)('!' + mry));
+ respond_string(buf, len, tty->port);
}
-/* invoked via ioctl(TIOCLINUX) and through set_selection */
+/* invoked via ioctl(TIOCLINUX) and through set_selection_user */
int mouse_reporting(void)
{
return vc_cons[fg_console].d->vc_report_mouse;
}
+/* invoked via ioctl(TIOCLINUX) */
+static int get_bracketed_paste(struct tty_struct *tty)
+{
+ struct vc_data *vc = tty->driver_data;
+
+ return vc->vc_bracketed_paste;
+}
+
/* console_lock is held */
-static void set_mode(struct vc_data *vc, int on_off)
+static void enter_alt_screen(struct vc_data *vc)
{
- int i;
+ unsigned int size = vc->vc_rows * vc->vc_cols * 2;
- for (i = 0; i <= vc->vc_npar; i++)
- if (vc->vc_ques) {
- switch(vc->vc_par[i]) { /* DEC private modes set/reset */
- case 1: /* Cursor keys send ^[Ox/^[[x */
- if (on_off)
- set_kbd(vc, decckm);
- else
- clr_kbd(vc, decckm);
- break;
- case 3: /* 80/132 mode switch unimplemented */
- vc->vc_deccolm = on_off;
-#if 0
- vc_resize(deccolm ? 132 : 80, vc->vc_rows);
- /* this alone does not suffice; some user mode
- utility has to change the hardware regs */
-#endif
- break;
- case 5: /* Inverted screen on/off */
- if (vc->vc_decscnm != on_off) {
- vc->vc_decscnm = on_off;
- invert_screen(vc, 0, vc->vc_screenbuf_size, 0);
- update_attr(vc);
- }
- break;
- case 6: /* Origin relative/absolute */
- vc->vc_decom = on_off;
- gotoxay(vc, 0, 0);
- break;
- case 7: /* Autowrap on/off */
- vc->vc_decawm = on_off;
- break;
- case 8: /* Autorepeat on/off */
- if (on_off)
- set_kbd(vc, decarm);
- else
- clr_kbd(vc, decarm);
- break;
- case 9:
- vc->vc_report_mouse = on_off ? 1 : 0;
- break;
- case 25: /* Cursor on/off */
- vc->vc_deccm = on_off;
- break;
- case 1000:
- vc->vc_report_mouse = on_off ? 2 : 0;
- break;
- }
- } else {
- switch(vc->vc_par[i]) { /* ANSI modes set/reset */
- case 3: /* Monitor (display ctrls) */
- vc->vc_disp_ctrl = on_off;
- break;
- case 4: /* Insert Mode on/off */
- vc->vc_decim = on_off;
- break;
- case 20: /* Lf, Enter == CrLf/Lf */
- if (on_off)
- set_kbd(vc, lnm);
- else
- clr_kbd(vc, lnm);
- break;
- }
- }
+ if (vc->vc_saved_screen != NULL)
+ return; /* Already inside an alt-screen */
+ vc->vc_saved_screen = kmemdup((u16 *)vc->vc_origin, size, GFP_KERNEL);
+ if (vc->vc_saved_screen == NULL)
+ return;
+ vc->vc_saved_rows = vc->vc_rows;
+ vc->vc_saved_cols = vc->vc_cols;
+ save_cur(vc);
+ /* clear entire screen */
+ csi_J(vc, CSI_J_FULL);
}
/* console_lock is held */
-static void setterm_command(struct vc_data *vc)
-{
- switch(vc->vc_par[0]) {
- case 1: /* set color for underline mode */
- if (vc->vc_can_do_color &&
- vc->vc_par[1] < 16) {
- vc->vc_ulcolor = color_table[vc->vc_par[1]];
- if (vc->vc_underline)
- update_attr(vc);
- }
+static void leave_alt_screen(struct vc_data *vc)
+{
+ unsigned int rows = min(vc->vc_saved_rows, vc->vc_rows);
+ unsigned int cols = min(vc->vc_saved_cols, vc->vc_cols);
+ u16 *src, *dest;
+
+ 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);
+ }
+ restore_cur(vc);
+ /* Update the entire screen */
+ if (con_should_update(vc))
+ do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+ kfree(vc->vc_saved_screen);
+ vc->vc_saved_screen = NULL;
+}
+
+enum {
+ CSI_DEC_hl_CURSOR_KEYS = 1, /* CKM: cursor keys send ^[Ox/^[[x */
+ CSI_DEC_hl_132_COLUMNS = 3, /* COLM: 80/132 mode switch */
+ CSI_DEC_hl_REVERSE_VIDEO = 5, /* SCNM */
+ CSI_DEC_hl_ORIGIN_MODE = 6, /* OM: origin relative/absolute */
+ CSI_DEC_hl_AUTOWRAP = 7, /* AWM */
+ CSI_DEC_hl_AUTOREPEAT = 8, /* ARM */
+ CSI_DEC_hl_MOUSE_X10 = 9,
+ CSI_DEC_hl_SHOW_CURSOR = 25, /* TCEM */
+ CSI_DEC_hl_MOUSE_VT200 = 1000,
+ CSI_DEC_hl_ALT_SCREEN = 1049,
+ CSI_DEC_hl_BRACKETED_PASTE = 2004,
+};
+
+/* console_lock is held */
+static void csi_DEC_hl(struct vc_data *vc, bool on_off)
+{
+ unsigned int i;
+
+ for (i = 0; i <= vc->vc_npar; i++)
+ switch (vc->vc_par[i]) {
+ case CSI_DEC_hl_CURSOR_KEYS:
+ if (on_off)
+ set_kbd(vc, decckm);
+ else
+ clr_kbd(vc, decckm);
+ break;
+ case CSI_DEC_hl_132_COLUMNS: /* unimplemented */
+#if 0
+ vc_resize(deccolm ? 132 : 80, vc->vc_rows);
+ /* this alone does not suffice; some user mode
+ utility has to change the hardware regs */
+#endif
break;
- case 2: /* set color for half intensity mode */
- if (vc->vc_can_do_color &&
- vc->vc_par[1] < 16) {
- vc->vc_halfcolor = color_table[vc->vc_par[1]];
- if (vc->vc_intensity == 0)
- update_attr(vc);
+ case CSI_DEC_hl_REVERSE_VIDEO:
+ if (vc->vc_decscnm != on_off) {
+ vc->vc_decscnm = on_off;
+ invert_screen(vc, 0, vc->vc_screenbuf_size,
+ false);
+ update_attr(vc);
}
break;
- case 8: /* store colors as defaults */
- vc->vc_def_color = vc->vc_attr;
- if (vc->vc_hi_font_mask == 0x100)
- vc->vc_def_color >>= 1;
- default_attr(vc);
- update_attr(vc);
+ case CSI_DEC_hl_ORIGIN_MODE:
+ vc->vc_decom = on_off;
+ gotoxay(vc, 0, 0);
break;
- case 9: /* set blanking interval */
- blankinterval = ((vc->vc_par[1] < 60) ? vc->vc_par[1] : 60) * 60;
- poke_blanked_console();
+ case CSI_DEC_hl_AUTOWRAP:
+ vc->vc_decawm = on_off;
break;
- case 10: /* set bell frequency in Hz */
- if (vc->vc_npar >= 1)
- vc->vc_bell_pitch = vc->vc_par[1];
+ case CSI_DEC_hl_AUTOREPEAT:
+ if (on_off)
+ set_kbd(vc, decarm);
else
- vc->vc_bell_pitch = DEFAULT_BELL_PITCH;
+ clr_kbd(vc, decarm);
break;
- case 11: /* set bell duration in msec */
- if (vc->vc_npar >= 1)
- vc->vc_bell_duration = (vc->vc_par[1] < 2000) ?
- vc->vc_par[1] * HZ / 1000 : 0;
- else
- vc->vc_bell_duration = DEFAULT_BELL_DURATION;
+ case CSI_DEC_hl_MOUSE_X10:
+ vc->vc_report_mouse = on_off ? 1 : 0;
+ break;
+ case CSI_DEC_hl_SHOW_CURSOR:
+ vc->vc_deccm = on_off;
break;
- case 12: /* bring specified console to the front */
- if (vc->vc_par[1] >= 1 && vc_cons_allocated(vc->vc_par[1] - 1))
- set_console(vc->vc_par[1] - 1);
+ case CSI_DEC_hl_MOUSE_VT200:
+ vc->vc_report_mouse = on_off ? 2 : 0;
break;
- case 13: /* unblank the screen */
- poke_blanked_console();
+ case CSI_DEC_hl_BRACKETED_PASTE:
+ vc->vc_bracketed_paste = on_off;
+ break;
+ case CSI_DEC_hl_ALT_SCREEN:
+ if (on_off)
+ enter_alt_screen(vc);
+ else
+ leave_alt_screen(vc);
+ break;
+ }
+}
+
+enum {
+ CSI_hl_DISPLAY_CTRL = 3, /* handle ansi control chars */
+ CSI_hl_INSERT = 4, /* IRM: insert/replace */
+ CSI_hl_AUTO_NL = 20, /* LNM: Enter == CrLf/Lf */
+};
+
+/* console_lock is held */
+static void csi_hl(struct vc_data *vc, bool on_off)
+{
+ unsigned int i;
+
+ for (i = 0; i <= vc->vc_npar; i++)
+ switch (vc->vc_par[i]) { /* ANSI modes set/reset */
+ case CSI_hl_DISPLAY_CTRL:
+ vc->vc_disp_ctrl = on_off;
break;
- case 14: /* set vesa powerdown interval */
- vesa_off_interval = ((vc->vc_par[1] < 60) ? vc->vc_par[1] : 60) * 60 * HZ;
+ case CSI_hl_INSERT:
+ vc->vc_decim = on_off;
break;
- case 15: /* activate the previous console */
- set_console(last_console);
+ case CSI_hl_AUTO_NL:
+ if (on_off)
+ set_kbd(vc, lnm);
+ else
+ clr_kbd(vc, lnm);
break;
+ }
+}
+
+enum CSI_right_square_bracket {
+ CSI_RSB_COLOR_FOR_UNDERLINE = 1,
+ CSI_RSB_COLOR_FOR_HALF_BRIGHT = 2,
+ CSI_RSB_MAKE_CUR_COLOR_DEFAULT = 8,
+ CSI_RSB_BLANKING_INTERVAL = 9,
+ CSI_RSB_BELL_FREQUENCY = 10,
+ CSI_RSB_BELL_DURATION = 11,
+ CSI_RSB_BRING_CONSOLE_TO_FRONT = 12,
+ CSI_RSB_UNBLANK = 13,
+ CSI_RSB_VESA_OFF_INTERVAL = 14,
+ CSI_RSB_BRING_PREV_CONSOLE_TO_FRONT = 15,
+ CSI_RSB_CURSOR_BLINK_INTERVAL = 16,
+};
+
+/*
+ * csi_RSB - csi+] (Right Square Bracket) handler
+ *
+ * These are linux console private sequences.
+ *
+ * console_lock is held
+ */
+static void csi_RSB(struct vc_data *vc)
+{
+ switch (vc->vc_par[0]) {
+ case CSI_RSB_COLOR_FOR_UNDERLINE:
+ if (vc->vc_can_do_color && vc->vc_par[1] < 16) {
+ vc->vc_ulcolor = color_table[vc->vc_par[1]];
+ if (vc->state.underline)
+ update_attr(vc);
+ }
+ break;
+ case CSI_RSB_COLOR_FOR_HALF_BRIGHT:
+ if (vc->vc_can_do_color && vc->vc_par[1] < 16) {
+ vc->vc_halfcolor = color_table[vc->vc_par[1]];
+ if (vc->state.intensity == VCI_HALF_BRIGHT)
+ update_attr(vc);
+ }
+ break;
+ case CSI_RSB_MAKE_CUR_COLOR_DEFAULT:
+ vc->vc_def_color = vc->vc_attr;
+ if (vc->vc_hi_font_mask == 0x100)
+ vc->vc_def_color >>= 1;
+ default_attr(vc);
+ update_attr(vc);
+ break;
+ case CSI_RSB_BLANKING_INTERVAL:
+ blankinterval = min(vc->vc_par[1], 60U) * 60;
+ poke_blanked_console();
+ break;
+ case CSI_RSB_BELL_FREQUENCY:
+ if (vc->vc_npar >= 1)
+ vc->vc_bell_pitch = vc->vc_par[1];
+ else
+ vc->vc_bell_pitch = DEFAULT_BELL_PITCH;
+ break;
+ case CSI_RSB_BELL_DURATION:
+ if (vc->vc_npar >= 1)
+ vc->vc_bell_duration = (vc->vc_par[1] < 2000) ?
+ msecs_to_jiffies(vc->vc_par[1]) : 0;
+ else
+ vc->vc_bell_duration = DEFAULT_BELL_DURATION;
+ break;
+ case CSI_RSB_BRING_CONSOLE_TO_FRONT:
+ if (vc->vc_par[1] >= 1 && vc_cons_allocated(vc->vc_par[1] - 1))
+ set_console(vc->vc_par[1] - 1);
+ break;
+ case CSI_RSB_UNBLANK:
+ poke_blanked_console();
+ break;
+ case CSI_RSB_VESA_OFF_INTERVAL:
+ vesa_off_interval = min(vc->vc_par[1], 60U) * 60 * HZ;
+ break;
+ case CSI_RSB_BRING_PREV_CONSOLE_TO_FRONT:
+ set_console(last_console);
+ break;
+ case CSI_RSB_CURSOR_BLINK_INTERVAL:
+ if (vc->vc_npar >= 1 && vc->vc_par[1] >= 50 &&
+ vc->vc_par[1] <= USHRT_MAX)
+ vc->vc_cur_blink_ms = vc->vc_par[1];
+ else
+ vc->vc_cur_blink_ms = DEFAULT_CURSOR_BLINK_MS;
+ break;
}
}
/* console_lock is held */
static void csi_at(struct vc_data *vc, unsigned int nr)
{
- if (nr > vc->vc_cols - vc->vc_x)
- nr = vc->vc_cols - vc->vc_x;
- else if (!nr)
- nr = 1;
+ nr = clamp(nr, 1, vc->vc_cols - vc->state.x);
insert_char(vc, nr);
}
/* console_lock is held */
-static void csi_L(struct vc_data *vc, unsigned int nr)
+static void csi_L(struct vc_data *vc)
{
- if (nr > vc->vc_rows - vc->vc_y)
- nr = vc->vc_rows - vc->vc_y;
- else if (!nr)
- nr = 1;
- scrdown(vc, vc->vc_y, vc->vc_bottom, nr);
+ unsigned int nr = clamp(vc->vc_par[0], 1, vc->vc_rows - vc->state.y);
+
+ con_scroll(vc, vc->state.y, vc->vc_bottom, SM_DOWN, nr);
vc->vc_need_wrap = 0;
}
/* console_lock is held */
-static void csi_P(struct vc_data *vc, unsigned int nr)
+static void csi_P(struct vc_data *vc)
{
- if (nr > vc->vc_cols - vc->vc_x)
- nr = vc->vc_cols - vc->vc_x;
- else if (!nr)
- nr = 1;
+ unsigned int nr = clamp(vc->vc_par[0], 1, vc->vc_cols - vc->state.x);
+
delete_char(vc, nr);
}
/* console_lock is held */
-static void csi_M(struct vc_data *vc, unsigned int nr)
+static void csi_M(struct vc_data *vc)
{
- if (nr > vc->vc_rows - vc->vc_y)
- nr = vc->vc_rows - vc->vc_y;
- else if (!nr)
- nr=1;
- scrup(vc, vc->vc_y, vc->vc_bottom, nr);
+ unsigned int nr = clamp(vc->vc_par[0], 1, vc->vc_rows - vc->state.y);
+
+ con_scroll(vc, vc->state.y, vc->vc_bottom, SM_UP, nr);
vc->vc_need_wrap = 0;
}
/* console_lock is held (except via vc_init->reset_terminal */
static void save_cur(struct vc_data *vc)
{
- vc->vc_saved_x = vc->vc_x;
- vc->vc_saved_y = vc->vc_y;
- vc->vc_s_intensity = vc->vc_intensity;
- vc->vc_s_italic = vc->vc_italic;
- vc->vc_s_underline = vc->vc_underline;
- vc->vc_s_blink = vc->vc_blink;
- vc->vc_s_reverse = vc->vc_reverse;
- vc->vc_s_charset = vc->vc_charset;
- vc->vc_s_color = vc->vc_color;
- vc->vc_saved_G0 = vc->vc_G0_charset;
- vc->vc_saved_G1 = vc->vc_G1_charset;
+ memcpy(&vc->saved_state, &vc->state, sizeof(vc->state));
}
/* console_lock is held */
static void restore_cur(struct vc_data *vc)
{
- gotoxy(vc, vc->vc_saved_x, vc->vc_saved_y);
- vc->vc_intensity = vc->vc_s_intensity;
- vc->vc_italic = vc->vc_s_italic;
- vc->vc_underline = vc->vc_s_underline;
- vc->vc_blink = vc->vc_s_blink;
- vc->vc_reverse = vc->vc_s_reverse;
- vc->vc_charset = vc->vc_s_charset;
- vc->vc_color = vc->vc_s_color;
- vc->vc_G0_charset = vc->vc_saved_G0;
- vc->vc_G1_charset = vc->vc_saved_G1;
- vc->vc_translate = set_translate(vc->vc_charset ? vc->vc_G1_charset : vc->vc_G0_charset, vc);
+ memcpy(&vc->state, &vc->saved_state, sizeof(vc->state));
+
+ gotoxy(vc, vc->state.x, vc->state.y);
+ vc->vc_translate = set_translate(vc->state.Gx_charset[vc->state.charset],
+ vc);
update_attr(vc);
vc->vc_need_wrap = 0;
}
-enum { ESnormal, ESesc, ESsquare, ESgetpars, ESgotpars, ESfunckey,
- EShash, ESsetG0, ESsetG1, ESpercent, ESignore, ESnonstd,
- ESpalette };
+/**
+ * enum vc_ctl_state - control characters state of a vt
+ *
+ * @ESnormal: initial state, no control characters parsed
+ * @ESesc: ESC parsed
+ * @ESsquare: CSI parsed -- modifiers/parameters/ctrl chars expected
+ * @ESgetpars: CSI parsed -- parameters/ctrl chars expected
+ * @ESfunckey: CSI [ parsed
+ * @EShash: ESC # parsed
+ * @ESsetG0: ESC ( parsed
+ * @ESsetG1: ESC ) parsed
+ * @ESpercent: ESC % parsed
+ * @EScsiignore: CSI [0x20-0x3f] parsed
+ * @ESnonstd: OSC parsed
+ * @ESpalette: OSC P parsed
+ * @ESosc: OSC [0-9] parsed
+ * @ESANSI_first: first state for ignoring ansi control sequences
+ * @ESapc: ESC _ parsed
+ * @ESpm: ESC ^ parsed
+ * @ESdcs: ESC P parsed
+ * @ESANSI_last: last state for ignoring ansi control sequences
+ */
+enum vc_ctl_state {
+ ESnormal,
+ ESesc,
+ ESsquare,
+ ESgetpars,
+ ESfunckey,
+ EShash,
+ ESsetG0,
+ ESsetG1,
+ ESpercent,
+ EScsiignore,
+ ESnonstd,
+ ESpalette,
+ ESosc,
+ ESANSI_first = ESosc,
+ ESapc,
+ ESpm,
+ ESdcs,
+ ESANSI_last = ESdcs,
+};
/* console_lock is held (except via vc_init()) */
static void reset_terminal(struct vc_data *vc, int do_clear)
{
+ unsigned int i;
+
vc->vc_top = 0;
vc->vc_bottom = vc->vc_rows;
vc->vc_state = ESnormal;
- vc->vc_ques = 0;
+ vc->vc_priv = EPecma;
vc->vc_translate = set_translate(LAT1_MAP, vc);
- vc->vc_G0_charset = LAT1_MAP;
- vc->vc_G1_charset = GRAF_MAP;
- vc->vc_charset = 0;
+ vc->state.Gx_charset[0] = LAT1_MAP;
+ vc->state.Gx_charset[1] = GRAF_MAP;
+ vc->state.charset = 0;
vc->vc_need_wrap = 0;
vc->vc_report_mouse = 0;
+ vc->vc_bracketed_paste = 0;
vc->vc_utf = default_utf8;
vc->vc_utf_count = 0;
@@ -1608,6 +2230,13 @@ static void reset_terminal(struct vc_data *vc, int do_clear)
vc->vc_deccm = global_cursor_default;
vc->vc_decim = 0;
+ if (vc->vc_saved_screen != NULL) {
+ kfree(vc->vc_saved_screen);
+ vc->vc_saved_screen = NULL;
+ vc->vc_saved_rows = 0;
+ vc->vc_saved_cols = 0;
+ }
+
vt_reset_keyboard(vc->vc_num);
vc->vc_cursor_type = cur_default;
@@ -1616,150 +2245,425 @@ static void reset_terminal(struct vc_data *vc, int do_clear)
default_attr(vc);
update_attr(vc);
- vc->vc_tab_stop[0] = 0x01010100;
- vc->vc_tab_stop[1] =
- vc->vc_tab_stop[2] =
- vc->vc_tab_stop[3] =
- vc->vc_tab_stop[4] =
- vc->vc_tab_stop[5] =
- vc->vc_tab_stop[6] =
- vc->vc_tab_stop[7] = 0x01010101;
+ bitmap_zero(vc->vc_tab_stop, VC_TABSTOPS_COUNT);
+ for (i = 0; i < VC_TABSTOPS_COUNT; i += 8)
+ set_bit(i, vc->vc_tab_stop);
vc->vc_bell_pitch = DEFAULT_BELL_PITCH;
vc->vc_bell_duration = DEFAULT_BELL_DURATION;
+ vc->vc_cur_blink_ms = DEFAULT_CURSOR_BLINK_MS;
gotoxy(vc, 0, 0);
save_cur(vc);
if (do_clear)
- csi_J(vc, 2);
+ csi_J(vc, CSI_J_VISIBLE);
}
-/* console_lock is held */
-static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, int c)
+static void vc_setGx(struct vc_data *vc, unsigned int which, u8 c)
{
- /*
- * Control characters can be used in the _middle_
- * of an escape sequence.
- */
+ unsigned char *charset = &vc->state.Gx_charset[which];
+
switch (c) {
- case 0:
- return;
- case 7:
- if (vc->vc_bell_duration)
+ case '0':
+ *charset = GRAF_MAP;
+ break;
+ case 'B':
+ *charset = LAT1_MAP;
+ break;
+ case 'U':
+ *charset = IBMPC_MAP;
+ break;
+ case 'K':
+ *charset = USER_MAP;
+ break;
+ }
+
+ if (vc->state.charset == which)
+ vc->vc_translate = set_translate(*charset, vc);
+}
+
+static bool ansi_control_string(enum vc_ctl_state state)
+{
+ return state >= ESANSI_first && state <= ESANSI_last;
+}
+
+enum {
+ ASCII_NULL = 0,
+ ASCII_BELL = 7,
+ ASCII_BACKSPACE = 8,
+ ASCII_IGNORE_FIRST = ASCII_BACKSPACE,
+ ASCII_HTAB = 9,
+ ASCII_LINEFEED = 10,
+ ASCII_VTAB = 11,
+ ASCII_FORMFEED = 12,
+ ASCII_CAR_RET = 13,
+ ASCII_IGNORE_LAST = ASCII_CAR_RET,
+ ASCII_SHIFTOUT = 14,
+ ASCII_SHIFTIN = 15,
+ ASCII_CANCEL = 24,
+ ASCII_SUBSTITUTE = 26,
+ ASCII_ESCAPE = 27,
+ ASCII_CSI_IGNORE_FIRST = ' ', /* 0x2x, 0x3a and 0x3c - 0x3f */
+ ASCII_CSI_IGNORE_LAST = '?',
+ ASCII_DEL = 127,
+ ASCII_EXT_CSI = 128 + ASCII_ESCAPE,
+};
+
+/*
+ * Handle ascii characters in control sequences and change states accordingly.
+ * E.g. ESC sets the state of vc to ESesc.
+ *
+ * Returns: true if @c handled.
+ */
+static bool handle_ascii(struct tty_struct *tty, struct vc_data *vc, u8 c)
+{
+ switch (c) {
+ case ASCII_NULL:
+ return true;
+ case ASCII_BELL:
+ if (ansi_control_string(vc->vc_state))
+ vc->vc_state = ESnormal;
+ else if (vc->vc_bell_duration)
kd_mksound(vc->vc_bell_pitch, vc->vc_bell_duration);
- return;
- case 8:
+ return true;
+ case ASCII_BACKSPACE:
bs(vc);
- return;
- case 9:
- vc->vc_pos -= (vc->vc_x << 1);
- while (vc->vc_x < vc->vc_cols - 1) {
- vc->vc_x++;
- if (vc->vc_tab_stop[vc->vc_x >> 5] & (1 << (vc->vc_x & 31)))
- break;
- }
- vc->vc_pos += (vc->vc_x << 1);
+ return true;
+ case ASCII_HTAB:
+ vc->vc_pos -= (vc->state.x << 1);
+
+ vc->state.x = find_next_bit(vc->vc_tab_stop,
+ min(vc->vc_cols - 1, VC_TABSTOPS_COUNT),
+ vc->state.x + 1);
+ if (vc->state.x >= VC_TABSTOPS_COUNT)
+ vc->state.x = vc->vc_cols - 1;
+
+ vc->vc_pos += (vc->state.x << 1);
notify_write(vc, '\t');
- return;
- case 10: case 11: case 12:
+ return true;
+ case ASCII_LINEFEED:
+ case ASCII_VTAB:
+ case ASCII_FORMFEED:
lf(vc);
if (!is_kbd(vc, lnm))
- return;
- case 13:
+ return true;
+ fallthrough;
+ case ASCII_CAR_RET:
cr(vc);
- return;
- case 14:
- vc->vc_charset = 1;
- vc->vc_translate = set_translate(vc->vc_G1_charset, vc);
+ return true;
+ case ASCII_SHIFTOUT:
+ vc->state.charset = 1;
+ vc->vc_translate = set_translate(vc->state.Gx_charset[1], vc);
vc->vc_disp_ctrl = 1;
- return;
- case 15:
- vc->vc_charset = 0;
- vc->vc_translate = set_translate(vc->vc_G0_charset, vc);
+ return true;
+ case ASCII_SHIFTIN:
+ vc->state.charset = 0;
+ vc->vc_translate = set_translate(vc->state.Gx_charset[0], vc);
vc->vc_disp_ctrl = 0;
- return;
- case 24: case 26:
+ return true;
+ case ASCII_CANCEL:
+ case ASCII_SUBSTITUTE:
vc->vc_state = ESnormal;
- return;
- case 27:
+ return true;
+ case ASCII_ESCAPE:
vc->vc_state = ESesc;
- return;
- case 127:
+ return true;
+ case ASCII_DEL:
del(vc);
- return;
- case 128+27:
+ return true;
+ case ASCII_EXT_CSI:
vc->vc_state = ESsquare;
- return;
+ return true;
}
- switch(vc->vc_state) {
- case ESesc:
- vc->vc_state = ESnormal;
- switch (c) {
- case '[':
- vc->vc_state = ESsquare;
- return;
- case ']':
- vc->vc_state = ESnonstd;
- return;
- case '%':
- vc->vc_state = ESpercent;
- return;
- case 'E':
- cr(vc);
- lf(vc);
- return;
- case 'M':
- ri(vc);
- return;
- case 'D':
- lf(vc);
- return;
- case 'H':
- vc->vc_tab_stop[vc->vc_x >> 5] |= (1 << (vc->vc_x & 31));
- return;
- case 'Z':
+
+ return false;
+}
+
+/*
+ * Handle a character (@c) following an ESC (when @vc is in the ESesc state).
+ * E.g. previous ESC with @c == '[' here yields the ESsquare state (that is:
+ * CSI).
+ */
+static void handle_esc(struct tty_struct *tty, struct vc_data *vc, u8 c)
+{
+ vc->vc_state = ESnormal;
+ switch (c) {
+ case '[':
+ vc->vc_state = ESsquare;
+ break;
+ case ']':
+ vc->vc_state = ESnonstd;
+ break;
+ case '_':
+ vc->vc_state = ESapc;
+ break;
+ case '^':
+ vc->vc_state = ESpm;
+ break;
+ case '%':
+ vc->vc_state = ESpercent;
+ break;
+ case 'E':
+ cr(vc);
+ lf(vc);
+ break;
+ case 'M':
+ ri(vc);
+ break;
+ case 'D':
+ lf(vc);
+ break;
+ case 'H':
+ if (vc->state.x < VC_TABSTOPS_COUNT)
+ set_bit(vc->state.x, vc->vc_tab_stop);
+ break;
+ case 'P':
+ vc->vc_state = ESdcs;
+ break;
+ case 'Z':
+ respond_ID(tty);
+ break;
+ case '7':
+ save_cur(vc);
+ break;
+ case '8':
+ restore_cur(vc);
+ break;
+ case '(':
+ vc->vc_state = ESsetG0;
+ break;
+ case ')':
+ vc->vc_state = ESsetG1;
+ break;
+ case '#':
+ vc->vc_state = EShash;
+ break;
+ case 'c':
+ reset_terminal(vc, 1);
+ break;
+ case '>': /* Numeric keypad */
+ clr_kbd(vc, kbdapplic);
+ break;
+ case '=': /* Appl. keypad */
+ set_kbd(vc, kbdapplic);
+ break;
+ }
+}
+
+/*
+ * Handle special DEC control sequences ("ESC [ ? parameters char"). Parameters
+ * are in @vc->vc_par and the char is in @c here.
+ */
+static void csi_DEC(struct tty_struct *tty, struct vc_data *vc, u8 c)
+{
+ switch (c) {
+ case 'h':
+ csi_DEC_hl(vc, true);
+ break;
+ case 'l':
+ csi_DEC_hl(vc, false);
+ break;
+ case 'c':
+ if (vc->vc_par[0])
+ vc->vc_cursor_type = CUR_MAKE(vc->vc_par[0],
+ vc->vc_par[1],
+ vc->vc_par[2]);
+ else
+ vc->vc_cursor_type = cur_default;
+ break;
+ case 'm':
+ clear_selection();
+ if (vc->vc_par[0])
+ vc->vc_complement_mask = vc->vc_par[0] << 8 | vc->vc_par[1];
+ else
+ vc->vc_complement_mask = vc->vc_s_complement_mask;
+ break;
+ case 'n':
+ if (vc->vc_par[0] == 5)
+ status_report(tty);
+ else if (vc->vc_par[0] == 6)
+ cursor_report(vc, tty);
+ break;
+ }
+}
+
+/*
+ * Handle Control Sequence Introducer control characters. That is
+ * "ESC [ parameters char". Parameters are in @vc->vc_par and the char is in
+ * @c here.
+ */
+static void csi_ECMA(struct tty_struct *tty, struct vc_data *vc, u8 c)
+{
+ switch (c) {
+ case 'G':
+ case '`':
+ if (vc->vc_par[0])
+ vc->vc_par[0]--;
+ gotoxy(vc, vc->vc_par[0], vc->state.y);
+ break;
+ case 'A':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ gotoxy(vc, vc->state.x, vc->state.y - vc->vc_par[0]);
+ break;
+ case 'B':
+ case 'e':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ gotoxy(vc, vc->state.x, vc->state.y + vc->vc_par[0]);
+ break;
+ case 'C':
+ case 'a':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ gotoxy(vc, vc->state.x + vc->vc_par[0], vc->state.y);
+ break;
+ case 'D':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ gotoxy(vc, vc->state.x - vc->vc_par[0], vc->state.y);
+ break;
+ case 'E':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ gotoxy(vc, 0, vc->state.y + vc->vc_par[0]);
+ break;
+ case 'F':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ gotoxy(vc, 0, vc->state.y - vc->vc_par[0]);
+ break;
+ case 'd':
+ if (vc->vc_par[0])
+ vc->vc_par[0]--;
+ gotoxay(vc, vc->state.x ,vc->vc_par[0]);
+ break;
+ case 'H':
+ case 'f':
+ if (vc->vc_par[0])
+ vc->vc_par[0]--;
+ if (vc->vc_par[1])
+ vc->vc_par[1]--;
+ gotoxay(vc, vc->vc_par[1], vc->vc_par[0]);
+ break;
+ case 'J':
+ csi_J(vc, vc->vc_par[0]);
+ break;
+ case 'K':
+ csi_K(vc);
+ break;
+ case 'L':
+ csi_L(vc);
+ break;
+ case 'M':
+ csi_M(vc);
+ break;
+ case 'P':
+ csi_P(vc);
+ break;
+ case 'c':
+ if (!vc->vc_par[0])
respond_ID(tty);
- return;
- case '7':
- save_cur(vc);
- return;
- case '8':
- restore_cur(vc);
- return;
- case '(':
- vc->vc_state = ESsetG0;
- return;
- case ')':
- vc->vc_state = ESsetG1;
- return;
- case '#':
- vc->vc_state = EShash;
- return;
- case 'c':
- reset_terminal(vc, 1);
- return;
- case '>': /* Numeric keypad */
- clr_kbd(vc, kbdapplic);
- return;
- case '=': /* Appl. keypad */
- set_kbd(vc, kbdapplic);
- return;
+ break;
+ case 'g':
+ if (!vc->vc_par[0] && vc->state.x < VC_TABSTOPS_COUNT)
+ set_bit(vc->state.x, vc->vc_tab_stop);
+ else if (vc->vc_par[0] == 3)
+ bitmap_zero(vc->vc_tab_stop, VC_TABSTOPS_COUNT);
+ break;
+ case 'h':
+ csi_hl(vc, true);
+ break;
+ case 'l':
+ csi_hl(vc, false);
+ break;
+ case 'm':
+ csi_m(vc);
+ break;
+ case 'n':
+ if (vc->vc_par[0] == 5)
+ status_report(tty);
+ else if (vc->vc_par[0] == 6)
+ cursor_report(vc, tty);
+ break;
+ case 'q': /* DECLL - but only 3 leds */
+ /* map 0,1,2,3 to 0,1,2,4 */
+ if (vc->vc_par[0] < 4)
+ vt_set_led_state(vc->vc_num,
+ (vc->vc_par[0] < 3) ? vc->vc_par[0] : 4);
+ break;
+ case 'r':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ if (!vc->vc_par[1])
+ vc->vc_par[1] = vc->vc_rows;
+ /* Minimum allowed region is 2 lines */
+ if (vc->vc_par[0] < vc->vc_par[1] &&
+ vc->vc_par[1] <= vc->vc_rows) {
+ vc->vc_top = vc->vc_par[0] - 1;
+ vc->vc_bottom = vc->vc_par[1];
+ gotoxay(vc, 0, 0);
}
+ break;
+ case 's':
+ save_cur(vc);
+ break;
+ case 'u':
+ restore_cur(vc);
+ break;
+ case 'X':
+ csi_X(vc);
+ break;
+ case '@':
+ csi_at(vc, vc->vc_par[0]);
+ break;
+ case ']':
+ csi_RSB(vc);
+ break;
+ }
+
+}
+
+static void vc_reset_params(struct vc_data *vc)
+{
+ memset(vc->vc_par, 0, sizeof(vc->vc_par));
+ vc->vc_npar = 0;
+}
+
+/* console_lock is held */
+static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, u8 c)
+{
+ /*
+ * Control characters can be used in the _middle_
+ * of an escape sequence, aside from ANSI control strings.
+ */
+ if (ansi_control_string(vc->vc_state) && c >= ASCII_IGNORE_FIRST &&
+ c <= ASCII_IGNORE_LAST)
+ return;
+
+ if (handle_ascii(tty, vc, c))
return;
- case ESnonstd:
- if (c=='P') { /* palette escape sequence */
- for (vc->vc_npar = 0; vc->vc_npar < NPAR; vc->vc_npar++)
- vc->vc_par[vc->vc_npar] = 0;
- vc->vc_npar = 0;
+
+ switch(vc->vc_state) {
+ case ESesc: /* ESC */
+ handle_esc(tty, vc, c);
+ return;
+ case ESnonstd: /* ESC ] aka OSC */
+ switch (c) {
+ case 'P': /* palette escape sequence */
+ vc_reset_params(vc);
vc->vc_state = ESpalette;
return;
- } else if (c=='R') { /* reset palette */
+ case 'R': /* reset palette */
reset_palette(vc);
- vc->vc_state = ESnormal;
- } else
- vc->vc_state = ESnormal;
+ break;
+ case '0' ... '9':
+ vc->vc_state = ESosc;
+ return;
+ }
+ vc->vc_state = ESnormal;
return;
- case ESpalette:
+ case ESpalette: /* ESC ] P aka OSC P */
if (isxdigit(c)) {
vc->vc_par[vc->vc_npar++] = hex_to_bin(c);
if (vc->vc_npar == 7) {
@@ -1776,190 +2680,67 @@ static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, int c)
} else
vc->vc_state = ESnormal;
return;
- case ESsquare:
- for (vc->vc_npar = 0; vc->vc_npar < NPAR; vc->vc_npar++)
- vc->vc_par[vc->vc_npar] = 0;
- vc->vc_npar = 0;
+ case ESsquare: /* ESC [ aka CSI, parameters or modifiers expected */
+ vc_reset_params(vc);
+
vc->vc_state = ESgetpars;
- if (c == '[') { /* Function key */
- vc->vc_state=ESfunckey;
- return;
- }
- vc->vc_ques = (c == '?');
- if (vc->vc_ques)
+ switch (c) {
+ case '[': /* Function key */
+ vc->vc_state = ESfunckey;
return;
- case ESgetpars:
- if (c == ';' && vc->vc_npar < NPAR - 1) {
- vc->vc_npar++;
+ case '?':
+ vc->vc_priv = EPdec;
return;
- } else if (c>='0' && c<='9') {
- vc->vc_par[vc->vc_npar] *= 10;
- vc->vc_par[vc->vc_npar] += c - '0';
+ case '>':
+ vc->vc_priv = EPgt;
return;
- } else
- vc->vc_state = ESgotpars;
- case ESgotpars:
- vc->vc_state = ESnormal;
- switch(c) {
- case 'h':
- set_mode(vc, 1);
+ case '=':
+ vc->vc_priv = EPeq;
return;
- case 'l':
- set_mode(vc, 0);
+ case '<':
+ vc->vc_priv = EPlt;
return;
- case 'c':
- if (vc->vc_ques) {
- if (vc->vc_par[0])
- vc->vc_cursor_type = vc->vc_par[0] | (vc->vc_par[1] << 8) | (vc->vc_par[2] << 16);
- else
- vc->vc_cursor_type = cur_default;
- return;
- }
- break;
- case 'm':
- if (vc->vc_ques) {
- clear_selection();
- if (vc->vc_par[0])
- vc->vc_complement_mask = vc->vc_par[0] << 8 | vc->vc_par[1];
- else
- vc->vc_complement_mask = vc->vc_s_complement_mask;
+ }
+ vc->vc_priv = EPecma;
+ fallthrough;
+ case ESgetpars: /* ESC [ aka CSI, parameters expected */
+ switch (c) {
+ case ';':
+ if (vc->vc_npar < NPAR - 1) {
+ vc->vc_npar++;
return;
}
break;
- case 'n':
- if (!vc->vc_ques) {
- if (vc->vc_par[0] == 5)
- status_report(tty);
- else if (vc->vc_par[0] == 6)
- cursor_report(vc, tty);
- }
+ case '0' ... '9':
+ vc->vc_par[vc->vc_npar] *= 10;
+ vc->vc_par[vc->vc_npar] += c - '0';
return;
}
- if (vc->vc_ques) {
- vc->vc_ques = 0;
+ if (c >= ASCII_CSI_IGNORE_FIRST && c <= ASCII_CSI_IGNORE_LAST) {
+ vc->vc_state = EScsiignore;
return;
}
- switch(c) {
- case 'G': case '`':
- if (vc->vc_par[0])
- vc->vc_par[0]--;
- gotoxy(vc, vc->vc_par[0], vc->vc_y);
- return;
- case 'A':
- if (!vc->vc_par[0])
- vc->vc_par[0]++;
- gotoxy(vc, vc->vc_x, vc->vc_y - vc->vc_par[0]);
- return;
- case 'B': case 'e':
- if (!vc->vc_par[0])
- vc->vc_par[0]++;
- gotoxy(vc, vc->vc_x, vc->vc_y + vc->vc_par[0]);
- return;
- case 'C': case 'a':
- if (!vc->vc_par[0])
- vc->vc_par[0]++;
- gotoxy(vc, vc->vc_x + vc->vc_par[0], vc->vc_y);
- return;
- case 'D':
- if (!vc->vc_par[0])
- vc->vc_par[0]++;
- gotoxy(vc, vc->vc_x - vc->vc_par[0], vc->vc_y);
- return;
- case 'E':
- if (!vc->vc_par[0])
- vc->vc_par[0]++;
- gotoxy(vc, 0, vc->vc_y + vc->vc_par[0]);
- return;
- case 'F':
- if (!vc->vc_par[0])
- vc->vc_par[0]++;
- gotoxy(vc, 0, vc->vc_y - vc->vc_par[0]);
- return;
- case 'd':
- if (vc->vc_par[0])
- vc->vc_par[0]--;
- gotoxay(vc, vc->vc_x ,vc->vc_par[0]);
- return;
- case 'H': case 'f':
- if (vc->vc_par[0])
- vc->vc_par[0]--;
- if (vc->vc_par[1])
- vc->vc_par[1]--;
- gotoxay(vc, vc->vc_par[1], vc->vc_par[0]);
- return;
- case 'J':
- csi_J(vc, vc->vc_par[0]);
- return;
- case 'K':
- csi_K(vc, vc->vc_par[0]);
- return;
- case 'L':
- csi_L(vc, vc->vc_par[0]);
- return;
- case 'M':
- csi_M(vc, vc->vc_par[0]);
- return;
- case 'P':
- csi_P(vc, vc->vc_par[0]);
- return;
- case 'c':
- if (!vc->vc_par[0])
- respond_ID(tty);
- return;
- case 'g':
- if (!vc->vc_par[0])
- vc->vc_tab_stop[vc->vc_x >> 5] &= ~(1 << (vc->vc_x & 31));
- else if (vc->vc_par[0] == 3) {
- vc->vc_tab_stop[0] =
- vc->vc_tab_stop[1] =
- vc->vc_tab_stop[2] =
- vc->vc_tab_stop[3] =
- vc->vc_tab_stop[4] =
- vc->vc_tab_stop[5] =
- vc->vc_tab_stop[6] =
- vc->vc_tab_stop[7] = 0;
- }
- return;
- case 'm':
- csi_m(vc);
- return;
- case 'q': /* DECLL - but only 3 leds */
- /* map 0,1,2,3 to 0,1,2,4 */
- if (vc->vc_par[0] < 4)
- vt_set_led_state(vc->vc_num,
- (vc->vc_par[0] < 3) ? vc->vc_par[0] : 4);
- return;
- case 'r':
- if (!vc->vc_par[0])
- vc->vc_par[0]++;
- if (!vc->vc_par[1])
- vc->vc_par[1] = vc->vc_rows;
- /* Minimum allowed region is 2 lines */
- if (vc->vc_par[0] < vc->vc_par[1] &&
- vc->vc_par[1] <= vc->vc_rows) {
- vc->vc_top = vc->vc_par[0] - 1;
- vc->vc_bottom = vc->vc_par[1];
- gotoxay(vc, 0, 0);
- }
- return;
- case 's':
- save_cur(vc);
- return;
- case 'u':
- restore_cur(vc);
- return;
- case 'X':
- csi_X(vc, vc->vc_par[0]);
+
+ /* parameters done, handle the control char @c */
+
+ vc->vc_state = ESnormal;
+
+ switch (vc->vc_priv) {
+ case EPdec:
+ csi_DEC(tty, vc, c);
return;
- case '@':
- csi_at(vc, vc->vc_par[0]);
+ case EPecma:
+ csi_ECMA(tty, vc, c);
return;
- case ']': /* setterm functions */
- setterm_command(vc);
+ default:
return;
}
+ case EScsiignore:
+ if (c >= ASCII_CSI_IGNORE_FIRST && c <= ASCII_CSI_IGNORE_LAST)
+ return;
+ vc->vc_state = ESnormal;
return;
- case ESpercent:
+ case ESpercent: /* ESC % */
vc->vc_state = ESnormal;
switch (c) {
case '@': /* defined in ISO 2022 */
@@ -1971,351 +2752,494 @@ static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, int c)
return;
}
return;
- case ESfunckey:
+ case ESfunckey: /* ESC [ [ aka CSI [ */
vc->vc_state = ESnormal;
return;
- case EShash:
+ case EShash: /* ESC # */
vc->vc_state = ESnormal;
if (c == '8') {
/* DEC screen alignment test. kludge :-) */
vc->vc_video_erase_char =
(vc->vc_video_erase_char & 0xff00) | 'E';
- csi_J(vc, 2);
+ 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);
}
return;
- case ESsetG0:
- if (c == '0')
- vc->vc_G0_charset = GRAF_MAP;
- else if (c == 'B')
- vc->vc_G0_charset = LAT1_MAP;
- else if (c == 'U')
- vc->vc_G0_charset = IBMPC_MAP;
- else if (c == 'K')
- vc->vc_G0_charset = USER_MAP;
- if (vc->vc_charset == 0)
- vc->vc_translate = set_translate(vc->vc_G0_charset, vc);
+ case ESsetG0: /* ESC ( */
+ vc_setGx(vc, 0, c);
vc->vc_state = ESnormal;
return;
- case ESsetG1:
- if (c == '0')
- vc->vc_G1_charset = GRAF_MAP;
- else if (c == 'B')
- vc->vc_G1_charset = LAT1_MAP;
- else if (c == 'U')
- vc->vc_G1_charset = IBMPC_MAP;
- else if (c == 'K')
- vc->vc_G1_charset = USER_MAP;
- if (vc->vc_charset == 1)
- vc->vc_translate = set_translate(vc->vc_G1_charset, vc);
+ case ESsetG1: /* ESC ) */
+ vc_setGx(vc, 1, c);
vc->vc_state = ESnormal;
return;
+ case ESapc: /* ESC _ */
+ return;
+ case ESosc: /* ESC ] [0-9] aka OSC [0-9] */
+ return;
+ case ESpm: /* ESC ^ */
+ return;
+ case ESdcs: /* ESC P */
+ return;
default:
vc->vc_state = ESnormal;
}
}
-/* is_double_width() is based on the wcwidth() implementation by
- * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
- * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
- */
-struct interval {
- uint32_t first;
- uint32_t last;
+struct vc_draw_region {
+ unsigned long from, to;
+ int x;
};
-static int bisearch(uint32_t ucs, const struct interval *table, int max)
+static void con_flush(struct vc_data *vc, struct vc_draw_region *draw)
{
- int min = 0;
- int mid;
+ if (draw->x < 0)
+ return;
- if (ucs < table[0].first || ucs > table[max].last)
- return 0;
- while (max >= min) {
- mid = (min + max) / 2;
- if (ucs > table[mid].last)
- min = mid + 1;
- else if (ucs < table[mid].first)
- max = mid - 1;
+ vc->vc_sw->con_putcs(vc, (u16 *)draw->from,
+ (u16 *)draw->to - (u16 *)draw->from, vc->state.y,
+ draw->x);
+ draw->x = -1;
+}
+
+static inline int vc_translate_ascii(const struct vc_data *vc, int c)
+{
+ if (IS_ENABLED(CONFIG_CONSOLE_TRANSLATIONS)) {
+ if (vc->vc_toggle_meta)
+ c |= 0x80;
+
+ return vc->vc_translate[c];
+ }
+
+ return c;
+}
+
+
+/**
+ * vc_sanitize_unicode - Replace invalid Unicode code points with ``U+FFFD``
+ * @c: the received code point
+ */
+static inline int vc_sanitize_unicode(const int c)
+{
+ if (c >= 0xd800 && c <= 0xdfff)
+ return 0xfffd;
+
+ return c;
+}
+
+/**
+ * vc_translate_unicode - Combine UTF-8 into Unicode in &vc_data.vc_utf_char
+ * @vc: virtual console
+ * @c: UTF-8 byte to translate
+ * @rescan: set to true iff @c wasn't consumed here and needs to be re-processed
+ *
+ * * &vc_data.vc_utf_char is the being-constructed Unicode code point.
+ * * &vc_data.vc_utf_count is the number of continuation bytes still expected to
+ * arrive.
+ * * &vc_data.vc_npar is the number of continuation bytes arrived so far.
+ *
+ * Return:
+ * * %-1 - Input OK so far, @c consumed, further bytes expected.
+ * * %0xFFFD - Possibility 1: input invalid, @c may have been consumed (see
+ * desc. of @rescan). Possibility 2: input OK, @c consumed,
+ * ``U+FFFD`` is the resulting code point. ``U+FFFD`` is valid,
+ * ``REPLACEMENT CHARACTER``.
+ * * otherwise - Input OK, @c consumed, resulting code point returned.
+ */
+static int vc_translate_unicode(struct vc_data *vc, int c, bool *rescan)
+{
+ static const u32 utf8_length_changes[] = {0x7f, 0x7ff, 0xffff, 0x10ffff};
+
+ /* Continuation byte received */
+ if ((c & 0xc0) == 0x80) {
+ /* Unexpected continuation byte? */
+ if (!vc->vc_utf_count)
+ goto bad_sequence;
+
+ vc->vc_utf_char = (vc->vc_utf_char << 6) | (c & 0x3f);
+ vc->vc_npar++;
+ if (--vc->vc_utf_count)
+ goto need_more_bytes;
+
+ /* Got a whole character */
+ c = vc->vc_utf_char;
+ /* Reject overlong sequences */
+ if (c <= utf8_length_changes[vc->vc_npar - 1] ||
+ c > utf8_length_changes[vc->vc_npar])
+ goto bad_sequence;
+
+ return vc_sanitize_unicode(c);
+ }
+
+ /* Single ASCII byte or first byte of a sequence received */
+ if (vc->vc_utf_count) {
+ /* A continuation byte was expected */
+ *rescan = true;
+ vc->vc_utf_count = 0;
+ goto bad_sequence;
+ }
+
+ /* Nothing to do if an ASCII byte was received */
+ if (c <= 0x7f)
+ return c;
+
+ /* First byte of a multibyte sequence received */
+ vc->vc_npar = 0;
+ if ((c & 0xe0) == 0xc0) {
+ vc->vc_utf_count = 1;
+ vc->vc_utf_char = (c & 0x1f);
+ } else if ((c & 0xf0) == 0xe0) {
+ vc->vc_utf_count = 2;
+ vc->vc_utf_char = (c & 0x0f);
+ } else if ((c & 0xf8) == 0xf0) {
+ vc->vc_utf_count = 3;
+ vc->vc_utf_char = (c & 0x07);
+ } else {
+ goto bad_sequence;
+ }
+
+need_more_bytes:
+ return -1;
+
+bad_sequence:
+ return 0xfffd;
+}
+
+static int vc_translate(struct vc_data *vc, int *c, bool *rescan)
+{
+ /* Do no translation at all in control states */
+ if (vc->vc_state != ESnormal)
+ return *c;
+
+ if (vc->vc_utf && !vc->vc_disp_ctrl)
+ return *c = vc_translate_unicode(vc, *c, rescan);
+
+ /* no utf or alternate charset mode */
+ return vc_translate_ascii(vc, *c);
+}
+
+static inline unsigned char vc_invert_attr(const struct vc_data *vc)
+{
+ if (!vc->vc_can_do_color)
+ return vc->vc_attr ^ 0x08;
+
+ if (vc->vc_hi_font_mask == 0x100)
+ return (vc->vc_attr & 0x11) |
+ ((vc->vc_attr & 0xe0) >> 4) |
+ ((vc->vc_attr & 0x0e) << 4);
+
+ return (vc->vc_attr & 0x88) |
+ ((vc->vc_attr & 0x70) >> 4) |
+ ((vc->vc_attr & 0x07) << 4);
+}
+
+static bool vc_is_control(struct vc_data *vc, int tc, int c)
+{
+ /*
+ * A bitmap for codes <32. A bit of 1 indicates that the code
+ * corresponding to that bit number invokes some special action (such
+ * as cursor movement) and should not be displayed as a glyph unless
+ * the disp_ctrl mode is explicitly enabled.
+ */
+ static const u32 CTRL_ACTION = BIT(ASCII_NULL) |
+ GENMASK(ASCII_SHIFTIN, ASCII_BELL) | BIT(ASCII_CANCEL) |
+ BIT(ASCII_SUBSTITUTE) | BIT(ASCII_ESCAPE);
+ /* Cannot be overridden by disp_ctrl */
+ static const u32 CTRL_ALWAYS = BIT(ASCII_NULL) | BIT(ASCII_BACKSPACE) |
+ BIT(ASCII_LINEFEED) | BIT(ASCII_SHIFTIN) | BIT(ASCII_SHIFTOUT) |
+ BIT(ASCII_CAR_RET) | BIT(ASCII_FORMFEED) | BIT(ASCII_ESCAPE);
+
+ if (vc->vc_state != ESnormal)
+ return true;
+
+ if (!tc)
+ return true;
+
+ /*
+ * If the original code was a control character we only allow a glyph
+ * to be displayed if the code is not normally used (such as for cursor
+ * movement) or if the disp_ctrl mode has been explicitly enabled.
+ * Certain characters (as given by the CTRL_ALWAYS bitmap) are always
+ * displayed as control characters, as the console would be pretty
+ * useless without them; to display an arbitrary font position use the
+ * direct-to-font zone in UTF-8 mode.
+ */
+ if (c < BITS_PER_TYPE(CTRL_ALWAYS)) {
+ if (vc->vc_disp_ctrl)
+ return CTRL_ALWAYS & BIT(c);
else
- return 1;
+ return vc->vc_utf || (CTRL_ACTION & BIT(c));
+ }
+
+ if (c == ASCII_DEL && !vc->vc_disp_ctrl)
+ return true;
+
+ if (c == ASCII_EXT_CSI)
+ return true;
+
+ return false;
+}
+
+static void vc_con_rewind(struct vc_data *vc)
+{
+ if (vc->state.x && !vc->vc_need_wrap) {
+ vc->vc_pos -= 2;
+ vc->state.x--;
}
+ vc->vc_need_wrap = 0;
+}
+
+#define UCS_ZWS 0x200b /* Zero Width Space */
+#define UCS_VS16 0xfe0f /* Variation Selector 16 */
+#define UCS_REPLACEMENT 0xfffd /* Replacement Character */
+
+static int vc_process_ucs(struct vc_data *vc, int *c, int *tc)
+{
+ u32 prev_c, curr_c = *c;
+
+ if (ucs_is_double_width(curr_c)) {
+ /*
+ * The Unicode screen memory is allocated only when
+ * required. This is one such case as we need to remember
+ * which displayed characters are double-width.
+ */
+ vc_uniscr_check(vc);
+ return 2;
+ }
+
+ if (!ucs_is_zero_width(curr_c))
+ return 1;
+
+ /* From here curr_c is known to be zero-width. */
+
+ if (ucs_is_double_width(vc_uniscr_getc(vc, -2))) {
+ /*
+ * Let's merge this zero-width code point with the preceding
+ * double-width code point by replacing the existing
+ * zero-width space padding. To do so we rewind one column
+ * and pretend this has a width of 1.
+ * We give the legacy display the same initial space padding.
+ */
+ vc_con_rewind(vc);
+ *tc = ' ';
+ return 1;
+ }
+
+ /* From here the preceding character, if any, must be single-width. */
+ prev_c = vc_uniscr_getc(vc, -1);
+
+ if (curr_c == UCS_VS16 && prev_c != 0) {
+ /*
+ * VS16 (U+FE0F) is special. It typically turns the preceding
+ * single-width character into a double-width one. Let it
+ * have a width of 1 effectively making the combination with
+ * the preceding character double-width.
+ */
+ *tc = ' ';
+ return 1;
+ }
+
+ /* try recomposition */
+ prev_c = ucs_recompose(prev_c, curr_c);
+ if (prev_c != 0) {
+ vc_con_rewind(vc);
+ *tc = *c = prev_c;
+ return 1;
+ }
+
+ /* Otherwise zero-width code points are ignored. */
return 0;
}
-static int is_double_width(uint32_t ucs)
+static int vc_get_glyph(struct vc_data *vc, int tc)
{
- static const struct interval double_width[] = {
- { 0x1100, 0x115F }, { 0x2329, 0x232A }, { 0x2E80, 0x303E },
- { 0x3040, 0xA4CF }, { 0xAC00, 0xD7A3 }, { 0xF900, 0xFAFF },
- { 0xFE10, 0xFE19 }, { 0xFE30, 0xFE6F }, { 0xFF00, 0xFF60 },
- { 0xFFE0, 0xFFE6 }, { 0x20000, 0x2FFFD }, { 0x30000, 0x3FFFD }
- };
- return bisearch(ucs, double_width, ARRAY_SIZE(double_width) - 1);
+ int glyph = conv_uni_to_pc(vc, tc);
+ u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+
+ if (!(glyph & ~charmask))
+ 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)) {
+ /*
+ * 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
+ * ASCII characters since a font may lack Unicode mapping info and we don't
+ * want to end up with having question marks only.
+ */
+ return tc;
+ }
+
+ /*
+ * The Unicode screen memory is allocated only when required.
+ * This is one such case: we're about to "cheat" with the displayed
+ * character meaning the simple screen buffer won't hold the original
+ * information, whereas the Unicode screen buffer always does.
+ */
+ vc_uniscr_check(vc);
+
+ /* Try getting a simpler fallback character. */
+ tc = ucs_get_fallback(tc);
+ if (tc)
+ return vc_get_glyph(vc, tc);
+
+ /* Display U+FFFD (Unicode Replacement Character). */
+ return conv_uni_to_pc(vc, UCS_REPLACEMENT);
}
-/* acquires console_lock */
-static int do_con_write(struct tty_struct *tty, const unsigned char *buf, int count)
+static int vc_con_write_normal(struct vc_data *vc, int tc, int c,
+ struct vc_draw_region *draw)
{
-#ifdef VT_BUF_VRAM_ONLY
-#define FLUSH do { } while(0);
-#else
-#define FLUSH if (draw_x >= 0) { \
- vc->vc_sw->con_putcs(vc, (u16 *)draw_from, (u16 *)draw_to - (u16 *)draw_from, vc->vc_y, draw_x); \
- draw_x = -1; \
+ int next_c;
+ unsigned char vc_attr = vc->vc_attr;
+ u16 himask = vc->vc_hi_font_mask;
+ u8 width = 1;
+ bool inverse = false;
+
+ if (vc->vc_utf && !vc->vc_disp_ctrl) {
+ width = vc_process_ucs(vc, &c, &tc);
+ if (!width)
+ goto out;
}
-#endif
- int c, tc, ok, n = 0, draw_x = -1;
+ /* Now try to find out how to display it */
+ tc = vc_get_glyph(vc, tc);
+ if (tc == -1)
+ return -1; /* nothing to display */
+ if (tc < 0) {
+ inverse = true;
+ tc = conv_uni_to_pc(vc, '?');
+ if (tc < 0)
+ tc = '?';
+
+ vc_attr = vc_invert_attr(vc);
+ con_flush(vc, draw);
+ }
+
+ next_c = c;
+ while (1) {
+ if (vc->vc_need_wrap || vc->vc_decim)
+ con_flush(vc, draw);
+ if (vc->vc_need_wrap) {
+ cr(vc);
+ lf(vc);
+ }
+ if (vc->vc_decim)
+ insert_char(vc, 1);
+ vc_uniscr_putc(vc, next_c);
+
+ if (himask)
+ tc = ((tc & 0x100) ? himask : 0) |
+ (tc & 0xff);
+ tc |= (vc_attr << 8) & ~himask;
+
+ scr_writew(tc, (u16 *)vc->vc_pos);
+
+ if (con_should_update(vc) && draw->x < 0) {
+ draw->x = vc->state.x;
+ draw->from = vc->vc_pos;
+ }
+ if (vc->state.x == vc->vc_cols - 1) {
+ vc->vc_need_wrap = vc->vc_decawm;
+ draw->to = vc->vc_pos + 2;
+ } else {
+ vc->state.x++;
+ draw->to = (vc->vc_pos += 2);
+ }
+
+ if (!--width)
+ break;
+
+ /* A space is printed in the second column */
+ tc = conv_uni_to_pc(vc, ' ');
+ if (tc < 0)
+ tc = ' ';
+ /*
+ * Store a zero-width space in the Unicode screen given that
+ * the previous code point is semantically double width.
+ */
+ next_c = UCS_ZWS;
+ }
+
+out:
+ notify_write(vc, c);
+
+ if (inverse)
+ con_flush(vc, draw);
+
+ return 0;
+}
+
+/* acquires console_lock */
+static int do_con_write(struct tty_struct *tty, const u8 *buf, int count)
+{
+ struct vc_draw_region draw = {
+ .x = -1,
+ };
+ int c, tc, n = 0;
unsigned int currcons;
- unsigned long draw_from = 0, draw_to = 0;
- struct vc_data *vc;
- unsigned char vc_attr;
+ struct vc_data *vc = tty->driver_data;
struct vt_notifier_param param;
- uint8_t rescan;
- uint8_t inverse;
- uint8_t width;
- u16 himask, charmask;
+ bool rescan;
if (in_interrupt())
return count;
- might_sleep();
-
- console_lock();
- vc = tty->driver_data;
- if (vc == NULL) {
- printk(KERN_ERR "vt: argh, driver_data is NULL !\n");
- console_unlock();
- return 0;
- }
-
+ guard(console_lock)();
currcons = vc->vc_num;
if (!vc_cons_allocated(currcons)) {
/* could this happen? */
pr_warn_once("con_write: tty %d not allocated\n", currcons+1);
- console_unlock();
return 0;
}
- himask = vc->vc_hi_font_mask;
- charmask = himask ? 0x1ff : 0xff;
/* undraw cursor first */
- if (IS_FG(vc))
+ if (con_is_fg(vc))
hide_cursor(vc);
param.vc = vc;
- while (!tty->stopped && count) {
- int orig = *buf;
- c = orig;
+ while (!tty->flow.stopped && count) {
+ u8 orig = *buf;
buf++;
n++;
count--;
- rescan = 0;
- inverse = 0;
- width = 1;
-
- /* Do no translation at all in control states */
- if (vc->vc_state != ESnormal) {
- tc = c;
- } else if (vc->vc_utf && !vc->vc_disp_ctrl) {
- /* Combine UTF-8 into Unicode in vc_utf_char.
- * vc_utf_count is the number of continuation bytes still
- * expected to arrive.
- * vc_npar is the number of continuation bytes arrived so
- * far
- */
rescan_last_byte:
- if ((c & 0xc0) == 0x80) {
- /* Continuation byte received */
- static const uint32_t utf8_length_changes[] = { 0x0000007f, 0x000007ff, 0x0000ffff, 0x001fffff, 0x03ffffff, 0x7fffffff };
- if (vc->vc_utf_count) {
- vc->vc_utf_char = (vc->vc_utf_char << 6) | (c & 0x3f);
- vc->vc_npar++;
- if (--vc->vc_utf_count) {
- /* Still need some bytes */
- continue;
- }
- /* Got a whole character */
- c = vc->vc_utf_char;
- /* Reject overlong sequences */
- if (c <= utf8_length_changes[vc->vc_npar - 1] ||
- c > utf8_length_changes[vc->vc_npar])
- c = 0xfffd;
- } else {
- /* Unexpected continuation byte */
- vc->vc_utf_count = 0;
- c = 0xfffd;
- }
- } else {
- /* Single ASCII byte or first byte of a sequence received */
- if (vc->vc_utf_count) {
- /* Continuation byte expected */
- rescan = 1;
- vc->vc_utf_count = 0;
- c = 0xfffd;
- } else if (c > 0x7f) {
- /* First byte of a multibyte sequence received */
- vc->vc_npar = 0;
- if ((c & 0xe0) == 0xc0) {
- vc->vc_utf_count = 1;
- vc->vc_utf_char = (c & 0x1f);
- } else if ((c & 0xf0) == 0xe0) {
- vc->vc_utf_count = 2;
- vc->vc_utf_char = (c & 0x0f);
- } else if ((c & 0xf8) == 0xf0) {
- vc->vc_utf_count = 3;
- vc->vc_utf_char = (c & 0x07);
- } else if ((c & 0xfc) == 0xf8) {
- vc->vc_utf_count = 4;
- vc->vc_utf_char = (c & 0x03);
- } else if ((c & 0xfe) == 0xfc) {
- vc->vc_utf_count = 5;
- vc->vc_utf_char = (c & 0x01);
- } else {
- /* 254 and 255 are invalid */
- c = 0xfffd;
- }
- if (vc->vc_utf_count) {
- /* Still need some bytes */
- continue;
- }
- }
- /* Nothing to do if an ASCII byte was received */
- }
- /* End of UTF-8 decoding. */
- /* c is the received character, or U+FFFD for invalid sequences. */
- /* Replace invalid Unicode code points with U+FFFD too */
- if ((c >= 0xd800 && c <= 0xdfff) || c == 0xfffe || c == 0xffff)
- c = 0xfffd;
- tc = c;
- } else { /* no utf or alternate charset mode */
- tc = vc_translate(vc, c);
- }
+ c = orig;
+ rescan = false;
+
+ tc = vc_translate(vc, &c, &rescan);
+ if (tc == -1)
+ continue;
param.c = tc;
if (atomic_notifier_call_chain(&vt_notifier_list, VT_PREWRITE,
&param) == NOTIFY_STOP)
continue;
- /* If the original code was a control character we
- * only allow a glyph to be displayed if the code is
- * not normally used (such as for cursor movement) or
- * if the disp_ctrl mode has been explicitly enabled.
- * Certain characters (as given by the CTRL_ALWAYS
- * bitmap) are always displayed as control characters,
- * as the console would be pretty useless without
- * them; to display an arbitrary font position use the
- * direct-to-font zone in UTF-8 mode.
- */
- ok = tc && (c >= 32 ||
- !(vc->vc_disp_ctrl ? (CTRL_ALWAYS >> c) & 1 :
- vc->vc_utf || ((CTRL_ACTION >> c) & 1)))
- && (c != 127 || vc->vc_disp_ctrl)
- && (c != 128+27);
-
- if (vc->vc_state == ESnormal && ok) {
- if (vc->vc_utf && !vc->vc_disp_ctrl) {
- if (is_double_width(c))
- width = 2;
- }
- /* Now try to find out how to display it */
- tc = conv_uni_to_pc(vc, tc);
- if (tc & ~charmask) {
- if (tc == -1 || tc == -2) {
- continue; /* nothing to display */
- }
- /* Glyph not found */
- if ((!(vc->vc_utf && !vc->vc_disp_ctrl) || c < 128) && !(c & ~charmask)) {
- /* 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 ASCII characters since a font may lack
- Unicode mapping info and we don't want to end up with
- having question marks only. */
- tc = c;
- } else {
- /* Display U+FFFD. If it's not found, display an inverse question mark. */
- tc = conv_uni_to_pc(vc, 0xfffd);
- if (tc < 0) {
- inverse = 1;
- tc = conv_uni_to_pc(vc, '?');
- if (tc < 0) tc = '?';
- }
- }
- }
-
- if (!inverse) {
- vc_attr = vc->vc_attr;
- } else {
- /* invert vc_attr */
- if (!vc->vc_can_do_color) {
- vc_attr = (vc->vc_attr) ^ 0x08;
- } else if (vc->vc_hi_font_mask == 0x100) {
- vc_attr = ((vc->vc_attr) & 0x11) | (((vc->vc_attr) & 0xe0) >> 4) | (((vc->vc_attr) & 0x0e) << 4);
- } else {
- vc_attr = ((vc->vc_attr) & 0x88) | (((vc->vc_attr) & 0x70) >> 4) | (((vc->vc_attr) & 0x07) << 4);
- }
- FLUSH
- }
-
- while (1) {
- if (vc->vc_need_wrap || vc->vc_decim)
- FLUSH
- if (vc->vc_need_wrap) {
- cr(vc);
- lf(vc);
- }
- if (vc->vc_decim)
- insert_char(vc, 1);
- scr_writew(himask ?
- ((vc_attr << 8) & ~himask) + ((tc & 0x100) ? himask : 0) + (tc & 0xff) :
- (vc_attr << 8) + tc,
- (u16 *) vc->vc_pos);
- if (DO_UPDATE(vc) && draw_x < 0) {
- draw_x = vc->vc_x;
- draw_from = vc->vc_pos;
- }
- if (vc->vc_x == vc->vc_cols - 1) {
- vc->vc_need_wrap = vc->vc_decawm;
- draw_to = vc->vc_pos + 2;
- } else {
- vc->vc_x++;
- draw_to = (vc->vc_pos += 2);
- }
-
- if (!--width) break;
-
- tc = conv_uni_to_pc(vc, ' '); /* A space is printed in the second column */
- if (tc < 0) tc = ' ';
- }
- notify_write(vc, c);
-
- if (inverse) {
- FLUSH
- }
-
- if (rescan) {
- rescan = 0;
- inverse = 0;
- width = 1;
- c = orig;
- goto rescan_last_byte;
- }
+ if (vc_is_control(vc, tc, c)) {
+ con_flush(vc, &draw);
+ do_con_trol(tty, vc, orig);
continue;
}
- FLUSH
- do_con_trol(tty, vc, orig);
+
+ if (vc_con_write_normal(vc, tc, c, &draw) < 0)
+ continue;
+
+ if (rescan)
+ goto rescan_last_byte;
}
- FLUSH
+ con_flush(vc, &draw);
console_conditional_schedule();
- console_unlock();
notify_update(vc);
+
return n;
-#undef FLUSH
}
/*
@@ -2329,7 +3253,7 @@ rescan_last_byte:
*/
static void console_callback(struct work_struct *ignored)
{
- console_lock();
+ guard(console_lock)();
if (want_console >= 0) {
if (want_console != fg_console &&
@@ -2349,7 +3273,7 @@ static void console_callback(struct work_struct *ignored)
if (scrollback_delta) {
struct vc_data *vc = vc_cons[fg_console].d;
clear_selection();
- if (vc->vc_mode == KD_TEXT)
+ if (vc->vc_mode == KD_TEXT && vc->vc_sw->con_scrolldelta)
vc->vc_sw->con_scrolldelta(vc, scrollback_delta);
scrollback_delta = 0;
}
@@ -2358,8 +3282,6 @@ static void console_callback(struct work_struct *ignored)
blank_timer_expired = 0;
}
notify_update(vc_cons[fg_console].d);
-
- console_unlock();
}
int set_console(int nr)
@@ -2391,16 +3313,16 @@ struct tty_driver *console_driver;
#ifdef CONFIG_VT_CONSOLE
/**
- * vt_kmsg_redirect() - Sets/gets the kernel message console
- * @new: The new virtual terminal number or -1 if the console should stay
- * unchanged
+ * vt_kmsg_redirect() - sets/gets the kernel message console
+ * @new: the new virtual terminal number or -1 if the console should stay
+ * unchanged
*
* By default, the kernel messages are always printed on the current virtual
* console. However, the user may modify that default with the
- * TIOCL_SETKMSGREDIRECT ioctl call.
+ * %TIOCL_SETKMSGREDIRECT ioctl call.
*
* This function sets the kernel message console to be @new. It returns the old
- * virtual console number. The virtual terminal number 0 (both as parameter and
+ * virtual console number. The virtual terminal number %0 (both as parameter and
* return value) means no redirection (i.e. always printed on the currently
* active console).
*
@@ -2408,8 +3330,8 @@ struct tty_driver *console_driver;
* value is not modified. You may use the macro vt_get_kmsg_redirect() in that
* case to make the code more understandable.
*
- * When the kernel is compiled without CONFIG_VT_CONSOLE, this function ignores
- * the parameter and always returns 0.
+ * When the kernel is compiled without %CONFIG_VT_CONSOLE, this function ignores
+ * the parameter and always returns %0.
*/
int vt_kmsg_redirect(int new)
{
@@ -2433,13 +3355,12 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
unsigned char c;
static DEFINE_SPINLOCK(printing_lock);
const ushort *start;
- ushort cnt = 0;
- ushort myx;
+ ushort start_x, cnt;
int kmsg_console;
- /* console busy or not yet initialized */
- if (!printable)
- return;
+ WARN_CONSOLE_UNLOCKED();
+
+ /* this protects against concurrent oops only */
if (!spin_trylock(&printing_lock))
return;
@@ -2447,71 +3368,56 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
if (kmsg_console && vc_cons_allocated(kmsg_console - 1))
vc = vc_cons[kmsg_console - 1].d;
- /* read `x' only after setting currcons properly (otherwise
- the `x' macro will read the x of the foreground console). */
- myx = vc->vc_x;
-
if (!vc_cons_allocated(fg_console)) {
/* impossible */
/* printk("vt_console_print: tty %d not allocated ??\n", currcons+1); */
goto quit;
}
- if (vc->vc_mode != KD_TEXT && !vt_force_oops_output(vc))
+ if (vc->vc_mode != KD_TEXT)
goto quit;
/* undraw cursor first */
- if (IS_FG(vc))
+ if (con_is_fg(vc))
hide_cursor(vc);
start = (ushort *)vc->vc_pos;
-
- /* Contrived structure to try to emulate original need_wrap behaviour
- * Problems caused when we have need_wrap set on '\n' character */
+ start_x = vc->state.x;
+ cnt = 0;
while (count--) {
c = *b++;
- if (c == 10 || c == 13 || c == 8 || vc->vc_need_wrap) {
- if (cnt > 0) {
- if (CON_IS_VISIBLE(vc))
- vc->vc_sw->con_putcs(vc, start, cnt, vc->vc_y, vc->vc_x);
- vc->vc_x += cnt;
- if (vc->vc_need_wrap)
- vc->vc_x--;
- cnt = 0;
- }
- if (c == 8) { /* backspace */
+ if (c == ASCII_LINEFEED || c == ASCII_CAR_RET ||
+ c == ASCII_BACKSPACE || vc->vc_need_wrap) {
+ if (cnt && con_is_visible(vc))
+ vc->vc_sw->con_putcs(vc, start, cnt, vc->state.y, start_x);
+ cnt = 0;
+ if (c == ASCII_BACKSPACE) {
bs(vc);
start = (ushort *)vc->vc_pos;
- myx = vc->vc_x;
+ start_x = vc->state.x;
continue;
}
- if (c != 13)
+ if (c != ASCII_CAR_RET)
lf(vc);
cr(vc);
start = (ushort *)vc->vc_pos;
- myx = vc->vc_x;
- if (c == 10 || c == 13)
+ start_x = vc->state.x;
+ if (c == ASCII_LINEFEED || c == ASCII_CAR_RET)
continue;
}
+ vc_uniscr_putc(vc, c);
scr_writew((vc->vc_attr << 8) + c, (unsigned short *)vc->vc_pos);
notify_write(vc, c);
cnt++;
- if (myx == vc->vc_cols - 1) {
- vc->vc_need_wrap = 1;
- continue;
- }
- vc->vc_pos += 2;
- myx++;
- }
- if (cnt > 0) {
- if (CON_IS_VISIBLE(vc))
- vc->vc_sw->con_putcs(vc, start, cnt, vc->vc_y, vc->vc_x);
- vc->vc_x += cnt;
- if (vc->vc_x == vc->vc_cols) {
- vc->vc_x--;
+ if (vc->state.x == vc->vc_cols - 1) {
vc->vc_need_wrap = 1;
+ } else {
+ vc->vc_pos += 2;
+ vc->state.x++;
}
}
+ if (cnt && con_is_visible(vc))
+ vc->vc_sw->con_putcs(vc, start, cnt, vc->state.y, start_x);
set_cursor(vc);
notify_update(vc);
@@ -2525,8 +3431,14 @@ static struct tty_driver *vt_console_device(struct console *c, int *index)
return console_driver;
}
+static int vt_console_setup(struct console *co, char *options)
+{
+ return co->index >= MAX_NR_CONSOLES ? -EINVAL : 0;
+}
+
static struct console vt_console_driver = {
.name = "tty",
+ .setup = vt_console_setup,
.write = vt_console_print,
.device = vt_console_device,
.unblank = unblank_screen,
@@ -2547,13 +3459,15 @@ static struct console vt_console_driver = {
* There are some functions which can sleep for arbitrary periods
* (paste_selection) but we don't need the lock there anyway.
*
- * set_selection has locking, and definitely needs it
+ * set_selection_user has locking, and definitely needs it
*/
int tioclinux(struct tty_struct *tty, unsigned long arg)
{
char type, data;
char __user *p = (char __user *)arg;
+ void __user *param_aligned32 = (u32 __user *)arg + 1;
+ void __user *param = (void __user *)arg + 1;
int lines;
int ret;
@@ -2563,94 +3477,80 @@ int tioclinux(struct tty_struct *tty, unsigned long arg)
return -EFAULT;
ret = 0;
- switch (type)
- {
- case TIOCL_SETSEL:
- console_lock();
- ret = set_selection((struct tiocl_selection __user *)(p+1), tty);
- console_unlock();
- break;
- case TIOCL_PASTESEL:
- ret = paste_selection(tty);
- break;
- case TIOCL_UNBLANKSCREEN:
- console_lock();
+ switch (type) {
+ case TIOCL_SETSEL:
+ return set_selection_user(param, tty);
+ case TIOCL_PASTESEL:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ return paste_selection(tty);
+ case TIOCL_UNBLANKSCREEN:
+ scoped_guard(console_lock)
unblank_screen();
- console_unlock();
- break;
- case TIOCL_SELLOADLUT:
- console_lock();
- ret = sel_loadlut(p);
- console_unlock();
- break;
- case TIOCL_GETSHIFTSTATE:
-
- /*
- * Make it possible to react to Shift+Mousebutton.
- * Note that 'shift_state' is an undocumented
- * kernel-internal variable; programs not closely
- * related to the kernel should not use this.
- */
- data = vt_get_shift_state();
- ret = __put_user(data, p);
- break;
- case TIOCL_GETMOUSEREPORTING:
- console_lock(); /* May be overkill */
+ break;
+ case TIOCL_SELLOADLUT:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ return sel_loadlut(param_aligned32);
+ case TIOCL_GETSHIFTSTATE:
+ /*
+ * Make it possible to react to Shift+Mousebutton. Note that
+ * 'shift_state' is an undocumented kernel-internal variable;
+ * programs not closely related to the kernel should not use
+ * this.
+ */
+ data = vt_get_shift_state();
+ return put_user(data, p);
+ case TIOCL_GETMOUSEREPORTING:
+ scoped_guard(console_lock) /* May be overkill */
data = mouse_reporting();
- console_unlock();
- ret = __put_user(data, p);
- break;
- case TIOCL_SETVESABLANK:
- console_lock();
- ret = set_vesa_blanking(p);
- console_unlock();
- break;
- case TIOCL_GETKMSGREDIRECT:
- data = vt_get_kmsg_redirect();
- ret = __put_user(data, p);
- break;
- case TIOCL_SETKMSGREDIRECT:
- if (!capable(CAP_SYS_ADMIN)) {
- ret = -EPERM;
- } else {
- if (get_user(data, p+1))
- ret = -EFAULT;
- else
- vt_kmsg_redirect(data);
- }
- break;
- case TIOCL_GETFGCONSOLE:
- /* No locking needed as this is a transiently
- correct return anyway if the caller hasn't
- disabled switching */
- ret = fg_console;
- break;
- case TIOCL_SCROLLCONSOLE:
- if (get_user(lines, (s32 __user *)(p+4))) {
- ret = -EFAULT;
- } else {
- /* Need the console lock here. Note that lots
- of other calls need fixing before the lock
- is actually useful ! */
- console_lock();
- scrollfront(vc_cons[fg_console].d, lines);
- console_unlock();
- ret = 0;
- }
- break;
- case TIOCL_BLANKSCREEN: /* until explicitly unblanked, not only poked */
- console_lock();
+ return put_user(data, p);
+ case TIOCL_SETVESABLANK:
+ return set_vesa_blanking(param);
+ case TIOCL_GETKMSGREDIRECT:
+ data = vt_get_kmsg_redirect();
+ return put_user(data, p);
+ case TIOCL_SETKMSGREDIRECT:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (get_user(data, p+1))
+ return -EFAULT;
+
+ vt_kmsg_redirect(data);
+
+ break;
+ case TIOCL_GETFGCONSOLE:
+ /*
+ * No locking needed as this is a transiently correct return
+ * anyway if the caller hasn't disabled switching.
+ */
+ return fg_console;
+ case TIOCL_SCROLLCONSOLE:
+ if (get_user(lines, (s32 __user *)param_aligned32))
+ return -EFAULT;
+
+ /*
+ * Needs the console lock here. Note that lots of other calls
+ * need fixing before the lock is actually useful!
+ */
+ scoped_guard(console_lock)
+ scrollfront(vc_cons[fg_console].d, lines);
+ break;
+ case TIOCL_BLANKSCREEN: /* until explicitly unblanked, not only poked */
+ scoped_guard(console_lock) {
ignore_poke = 1;
do_blank_screen(0);
- console_unlock();
- break;
- case TIOCL_BLANKEDSCREEN:
- ret = console_blanked;
- break;
- default:
- ret = -EINVAL;
- break;
+ }
+ break;
+ case TIOCL_BLANKEDSCREEN:
+ return console_blanked;
+ case TIOCL_GETBRACKETEDPASTE:
+ return get_bracketed_paste(tty);
+ default:
+ return -EINVAL;
}
+
return ret;
}
@@ -2658,7 +3558,7 @@ int tioclinux(struct tty_struct *tty, unsigned long arg)
* /dev/ttyN handling
*/
-static int con_write(struct tty_struct *tty, const unsigned char *buf, int count)
+static ssize_t con_write(struct tty_struct *tty, const u8 *buf, size_t count)
{
int retval;
@@ -2668,25 +3568,18 @@ static int con_write(struct tty_struct *tty, const unsigned char *buf, int count
return retval;
}
-static int con_put_char(struct tty_struct *tty, unsigned char ch)
+static int con_put_char(struct tty_struct *tty, u8 ch)
{
- if (in_interrupt())
- return 0; /* n_r3964 calls put_char() from interrupt context */
return do_con_write(tty, &ch, 1);
}
-static int con_write_room(struct tty_struct *tty)
+static unsigned int con_write_room(struct tty_struct *tty)
{
- if (tty->stopped)
+ if (tty->flow.stopped)
return 0;
return 32768; /* No limit, really; we're not buffering */
}
-static int con_chars_in_buffer(struct tty_struct *tty)
-{
- return 0; /* we're not buffering */
-}
-
/*
* con_throttle and con_unthrottle are only used for
* paste_selection(), which has to stuff in a large number of
@@ -2733,17 +3626,13 @@ static void con_start(struct tty_struct *tty)
static void con_flush_chars(struct tty_struct *tty)
{
- struct vc_data *vc;
+ struct vc_data *vc = tty->driver_data;
if (in_interrupt()) /* from flush_to_ldisc */
return;
- /* if we race with con_close(), vt may be null */
- console_lock();
- vc = tty->driver_data;
- if (vc)
- set_cursor(vc);
- console_unlock();
+ guard(console_lock)();
+ set_cursor(vc);
}
/*
@@ -2755,25 +3644,24 @@ static int con_install(struct tty_driver *driver, struct tty_struct *tty)
struct vc_data *vc;
int ret;
- console_lock();
+ guard(console_lock)();
ret = vc_allocate(currcons);
if (ret)
- goto unlock;
+ return ret;
vc = vc_cons[currcons].d;
/* Still being freed */
- if (vc->port.tty) {
- ret = -ERESTARTSYS;
- goto unlock;
- }
+ if (vc->port.tty)
+ return -ERESTARTSYS;
ret = tty_port_install(&vc->port, driver, tty);
if (ret)
- goto unlock;
+ return ret;
tty->driver_data = vc;
vc->port.tty = tty;
+ tty_port_get(&vc->port);
if (!tty->winsize.ws_row && !tty->winsize.ws_col) {
tty->winsize.ws_row = vc_cons[currcons].d->vc_rows;
@@ -2783,9 +3671,8 @@ static int con_install(struct tty_driver *driver, struct tty_struct *tty)
tty->termios.c_iflag |= IUTF8;
else
tty->termios.c_iflag &= ~IUTF8;
-unlock:
- console_unlock();
- return ret;
+
+ return 0;
}
static int con_open(struct tty_struct *tty, struct file *filp)
@@ -2804,26 +3691,38 @@ static void con_shutdown(struct tty_struct *tty)
{
struct vc_data *vc = tty->driver_data;
BUG_ON(vc == NULL);
- console_lock();
+
+ guard(console_lock)();
vc->port.tty = NULL;
- console_unlock();
}
+static void con_cleanup(struct tty_struct *tty)
+{
+ struct vc_data *vc = tty->driver_data;
+
+ tty_port_put(&vc->port);
+}
+
+/*
+ * We can't deal with anything but the N_TTY ldisc,
+ * because we can sleep in our write() routine.
+ */
+static int con_ldisc_ok(struct tty_struct *tty, int ldisc)
+{
+ return ldisc == N_TTY ? 0 : -EINVAL;
+}
+
+static int default_color = 7; /* white */
static int default_italic_color = 2; // green (ASCII)
static int default_underline_color = 3; // cyan (ASCII)
+module_param_named(color, default_color, int, S_IRUGO | S_IWUSR);
module_param_named(italic, default_italic_color, int, S_IRUGO | S_IWUSR);
module_param_named(underline, default_underline_color, int, S_IRUGO | S_IWUSR);
-static void vc_init(struct vc_data *vc, unsigned int rows,
- unsigned int cols, int do_clear)
+static void vc_init(struct vc_data *vc, int do_clear)
{
int j, k ;
- vc->vc_cols = cols;
- vc->vc_rows = rows;
- vc->vc_size_row = cols << 1;
- vc->vc_screenbuf_size = vc->vc_rows * vc->vc_size_row;
-
set_origin(vc);
vc->vc_pos = vc->vc_origin;
reset_vc(vc);
@@ -2832,7 +3731,7 @@ static void vc_init(struct vc_data *vc, unsigned int rows,
vc->vc_palette[k++] = default_grn[j] ;
vc->vc_palette[k++] = default_blu[j] ;
}
- vc->vc_def_color = 0x07; /* white */
+ vc->vc_def_color = default_color;
vc->vc_ulcolor = default_underline_color;
vc->vc_itcolor = default_italic_color;
vc->vc_halfcolor = 0x08; /* grey */
@@ -2854,8 +3753,9 @@ static int __init con_init(void)
console_lock();
- if (conswitchp)
- display_desc = conswitchp->con_startup();
+ if (!conswitchp)
+ conswitchp = &dummy_con;
+ display_desc = conswitchp->con_startup();
if (!display_desc) {
fg_console = 0;
console_unlock();
@@ -2887,22 +3787,21 @@ static int __init con_init(void)
vc_cons[currcons].d = vc = kzalloc(sizeof(struct vc_data), GFP_NOWAIT);
INIT_WORK(&vc_cons[currcons].SAK_work, vc_SAK);
tty_port_init(&vc->port);
- visual_init(vc, currcons, 1);
+ visual_init(vc, currcons, true);
+ /* Assuming vc->vc_{cols,rows,screenbuf_size} are sane here. */
vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_NOWAIT);
- vc_init(vc, vc->vc_rows, vc->vc_cols,
- currcons || !vc->vc_sw->con_save_screen);
+ vc_init(vc, currcons || !vc->vc_sw->con_save_screen);
}
currcons = fg_console = 0;
master_display_fg = vc = vc_cons[currcons].d;
set_origin(vc);
save_screen(vc);
- gotoxy(vc, vc->vc_x, vc->vc_y);
- csi_J(vc, 0);
+ gotoxy(vc, vc->state.x, vc->state.y);
+ csi_J(vc, CSI_J_CURSOR_TO_END);
update_screen(vc);
pr_info("Console: %s %s %dx%d\n",
vc->vc_can_do_color ? "colour" : "mono",
display_desc, vc->vc_cols, vc->vc_rows);
- printable = 1;
console_unlock();
@@ -2921,7 +3820,6 @@ static const struct tty_operations con_ops = {
.write_room = con_write_room,
.put_char = con_put_char,
.flush_chars = con_flush_chars,
- .chars_in_buffer = con_chars_in_buffer,
.ioctl = vt_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = vt_compat_ioctl,
@@ -2931,7 +3829,9 @@ static const struct tty_operations con_ops = {
.throttle = con_throttle,
.unthrottle = con_unthrottle,
.resize = vt_resize,
- .shutdown = con_shutdown
+ .shutdown = con_shutdown,
+ .cleanup = con_cleanup,
+ .ldisc_ok = con_ldisc_ok,
};
static struct cdev vc0_cdev;
@@ -2943,22 +3843,30 @@ static ssize_t show_tty_active(struct device *dev,
}
static DEVICE_ATTR(active, S_IRUGO, show_tty_active, NULL);
+static struct attribute *vt_dev_attrs[] = {
+ &dev_attr_active.attr,
+ NULL
+};
+
+ATTRIBUTE_GROUPS(vt_dev);
+
int __init vty_init(const struct file_operations *console_fops)
{
cdev_init(&vc0_cdev, console_fops);
if (cdev_add(&vc0_cdev, MKDEV(TTY_MAJOR, 0), 1) ||
register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1, "/dev/vc/0") < 0)
panic("Couldn't register /dev/tty0 driver\n");
- tty0dev = device_create(tty_class, NULL, MKDEV(TTY_MAJOR, 0), NULL, "tty0");
+ tty0dev = device_create_with_groups(&tty_class, NULL,
+ MKDEV(TTY_MAJOR, 0), NULL,
+ vt_dev_groups, "tty0");
if (IS_ERR(tty0dev))
tty0dev = NULL;
- else
- WARN_ON(device_create_file(tty0dev, &dev_attr_active) < 0);
vcs_init();
- console_driver = alloc_tty_driver(MAX_NR_CONSOLES);
- if (!console_driver)
+ console_driver = tty_alloc_driver(MAX_NR_CONSOLES, TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_RESET_TERMIOS);
+ if (IS_ERR(console_driver))
panic("Couldn't allocate console driver\n");
console_driver->name = "tty";
@@ -2969,7 +3877,6 @@ int __init vty_init(const struct file_operations *console_fops)
console_driver->init_termios = tty_std_termios;
if (default_utf8)
console_driver->init_termios.c_iflag |= IUTF8;
- console_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS;
tty_set_operations(console_driver, &con_ops);
if (tty_register_driver(console_driver))
panic("Couldn't register console driver\n");
@@ -2981,9 +3888,9 @@ int __init vty_init(const struct file_operations *console_fops)
return 0;
}
-#ifndef VT_SINGLE_DRIVER
-
-static struct class *vtconsole_class;
+static const struct class vtconsole_class = {
+ .name = "vtconsole",
+};
static int do_bind_con_driver(const struct consw *csw, int first, int last,
int deflt)
@@ -3042,7 +3949,7 @@ static int do_bind_con_driver(const struct consw *csw, int first, int last,
j = i;
- if (CON_IS_VISIBLE(vc)) {
+ if (con_is_visible(vc)) {
k = i;
save_screen(vc);
}
@@ -3050,7 +3957,7 @@ static int do_bind_con_driver(const struct consw *csw, int first, int last,
old_was_color = vc->vc_can_do_color;
vc->vc_sw->con_deinit(vc);
vc->vc_origin = (unsigned long)vc->vc_screenbuf;
- visual_init(vc, i, 0);
+ visual_init(vc, i, false);
set_origin(vc);
update_attr(vc);
@@ -3064,20 +3971,21 @@ static int do_bind_con_driver(const struct consw *csw, int first, int last,
pr_info("Console: switching ");
if (!deflt)
- printk("consoles %d-%d ", first+1, last+1);
+ pr_cont("consoles %d-%d ", first + 1, last + 1);
if (j >= 0) {
struct vc_data *vc = vc_cons[j].d;
- printk("to %s %s %dx%d\n",
- vc->vc_can_do_color ? "colour" : "mono",
- desc, vc->vc_cols, vc->vc_rows);
+ pr_cont("to %s %s %dx%d\n",
+ vc->vc_can_do_color ? "colour" : "mono",
+ desc, vc->vc_cols, vc->vc_rows);
if (k >= 0) {
vc = vc_cons[k].d;
update_screen(vc);
}
- } else
- printk("to %s\n", desc);
+ } else {
+ pr_cont("to %s\n", desc);
+ }
retval = 0;
err:
@@ -3087,23 +3995,6 @@ err:
#ifdef CONFIG_VT_HW_CONSOLE_BINDING
-static int con_is_graphics(const struct consw *csw, int first, int last)
-{
- int i, retval = 0;
-
- for (i = first; i <= last; i++) {
- struct vc_data *vc = vc_cons[i].d;
-
- if (vc && vc->vc_mode == KD_GRAPHICS) {
- retval = 1;
- break;
- }
- }
-
- return retval;
-}
-
-/* unlocked version of unbind_con_driver() */
int do_unbind_con_driver(const struct consw *csw, int first, int last, int deflt)
{
struct module *owner = csw->owner;
@@ -3136,8 +4027,7 @@ int do_unbind_con_driver(const struct consw *csw, int first, int last, int deflt
for (i = 0; i < MAX_NR_CON_DRIVER; i++) {
con_back = &registered_con_driver[i];
- if (con_back->con &&
- !(con_back->flag & CON_DRIVER_FLAG_MODULE)) {
+ if (con_back->con && con_back->con != csw) {
defcsw = con_back->con;
retval = 0;
break;
@@ -3189,8 +4079,7 @@ static int vt_bind(struct con_driver *con)
const struct consw *defcsw = NULL, *csw = NULL;
int i, more = 1, first = -1, last = -1, deflt = 0;
- if (!con->con || !(con->flag & CON_DRIVER_FLAG_MODULE) ||
- con_is_graphics(con->con, con->first, con->last))
+ if (!con->con || !(con->flag & CON_DRIVER_FLAG_MODULE))
goto err;
csw = con->con;
@@ -3223,11 +4112,8 @@ static int vt_bind(struct con_driver *con)
if (first == 0 && last == MAX_NR_CONSOLES -1)
deflt = 1;
- if (first != -1) {
- console_lock();
+ if (first != -1)
do_bind_con_driver(csw, first, last, deflt);
- console_unlock();
- }
first = -1;
last = -1;
@@ -3242,9 +4128,9 @@ static int vt_unbind(struct con_driver *con)
{
const struct consw *csw = NULL;
int i, more = 1, first = -1, last = -1, deflt = 0;
+ int ret;
- if (!con->con || !(con->flag & CON_DRIVER_FLAG_MODULE) ||
- con_is_graphics(con->con, con->first, con->last))
+ if (!con->con || !(con->flag & CON_DRIVER_FLAG_MODULE))
goto err;
csw = con->con;
@@ -3266,9 +4152,9 @@ static int vt_unbind(struct con_driver *con)
deflt = 1;
if (first != -1) {
- console_lock();
- do_unbind_con_driver(csw, first, last, deflt);
- console_unlock();
+ ret = do_unbind_con_driver(csw, first, last, deflt);
+ if (ret != 0)
+ return ret;
}
first = -1;
@@ -3296,6 +4182,8 @@ static ssize_t store_bind(struct device *dev, struct device_attribute *attr,
struct con_driver *con = dev_get_drvdata(dev);
int bind = simple_strtoul(buf, NULL, 0);
+ guard(console_lock)();
+
if (bind)
vt_bind(con);
else
@@ -3308,9 +4196,12 @@ static ssize_t show_bind(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct con_driver *con = dev_get_drvdata(dev);
- int bind = con_is_bound(con->con);
+ int bind;
+
+ scoped_guard(console_lock)
+ bind = con_is_bound(con->con);
- return snprintf(buf, PAGE_SIZE, "%i\n", bind);
+ return sysfs_emit(buf, "%i\n", bind);
}
static ssize_t show_name(struct device *dev, struct device_attribute *attr,
@@ -3318,48 +4209,32 @@ static ssize_t show_name(struct device *dev, struct device_attribute *attr,
{
struct con_driver *con = dev_get_drvdata(dev);
- return snprintf(buf, PAGE_SIZE, "%s %s\n",
+ return sysfs_emit(buf, "%s %s\n",
(con->flag & CON_DRIVER_FLAG_MODULE) ? "(M)" : "(S)",
con->desc);
}
-static struct device_attribute device_attrs[] = {
- __ATTR(bind, S_IRUGO|S_IWUSR, show_bind, store_bind),
- __ATTR(name, S_IRUGO, show_name, NULL),
+static DEVICE_ATTR(bind, S_IRUGO|S_IWUSR, show_bind, store_bind);
+static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
+
+static struct attribute *con_dev_attrs[] = {
+ &dev_attr_bind.attr,
+ &dev_attr_name.attr,
+ NULL
};
+ATTRIBUTE_GROUPS(con_dev);
+
static int vtconsole_init_device(struct con_driver *con)
{
- int i;
- int error = 0;
-
con->flag |= CON_DRIVER_FLAG_ATTR;
- dev_set_drvdata(con->dev, con);
- for (i = 0; i < ARRAY_SIZE(device_attrs); i++) {
- error = device_create_file(con->dev, &device_attrs[i]);
- if (error)
- break;
- }
-
- if (error) {
- while (--i >= 0)
- device_remove_file(con->dev, &device_attrs[i]);
- con->flag &= ~CON_DRIVER_FLAG_ATTR;
- }
-
- return error;
+ return 0;
}
static void vtconsole_deinit_device(struct con_driver *con)
{
- int i;
-
- if (con->flag & CON_DRIVER_FLAG_ATTR) {
- for (i = 0; i < ARRAY_SIZE(device_attrs); i++)
- device_remove_file(con->dev, &device_attrs[i]);
- con->flag &= ~CON_DRIVER_FLAG_ATTR;
- }
+ con->flag &= ~CON_DRIVER_FLAG_ATTR;
}
/**
@@ -3369,12 +4244,14 @@ static void vtconsole_deinit_device(struct con_driver *con)
* RETURNS: zero if unbound, nonzero if bound
*
* Drivers can call this and if zero, they should release
- * all resources allocated on con_startup()
+ * all resources allocated on &consw.con_startup()
*/
int con_is_bound(const struct consw *csw)
{
int i, bound = 0;
+ WARN_CONSOLE_UNLOCKED();
+
for (i = 0; i < MAX_NR_CONSOLES; i++) {
if (con_driver_map[i] == csw) {
bound = 1;
@@ -3387,21 +4264,29 @@ int con_is_bound(const struct consw *csw)
EXPORT_SYMBOL(con_is_bound);
/**
+ * con_is_visible - checks whether the current console is visible
+ * @vc: virtual console
+ *
+ * RETURNS: zero if not visible, nonzero if visible
+ */
+bool con_is_visible(const struct vc_data *vc)
+{
+ WARN_CONSOLE_UNLOCKED();
+
+ return *vc->vc_display_fg == vc;
+}
+EXPORT_SYMBOL(con_is_visible);
+
+/**
* con_debug_enter - prepare the console for the kernel debugger
- * @sw: console driver
+ * @vc: virtual console
*
* Called when the console is taken over by the kernel debugger, this
* function needs to save the current console state, then put the console
* into a state suitable for the kernel debugger.
- *
- * RETURNS:
- * Zero on success, nonzero if a failure occurred when trying to prepare
- * the console for the debugger.
*/
-int con_debug_enter(struct vc_data *vc)
+void con_debug_enter(struct vc_data *vc)
{
- int ret = 0;
-
saved_fg_console = fg_console;
saved_last_console = last_console;
saved_want_console = want_console;
@@ -3410,7 +4295,7 @@ int con_debug_enter(struct vc_data *vc)
vc->vc_mode = KD_TEXT;
console_blanked = 0;
if (vc->vc_sw->con_debug_enter)
- ret = vc->vc_sw->con_debug_enter(vc);
+ vc->vc_sw->con_debug_enter(vc);
#ifdef CONFIG_KGDB_KDB
/* Set the initial LINES variable if it is not already set */
if (vc->vc_rows < 999) {
@@ -3440,25 +4325,18 @@ int con_debug_enter(struct vc_data *vc)
}
}
#endif /* CONFIG_KGDB_KDB */
- return ret;
}
EXPORT_SYMBOL_GPL(con_debug_enter);
/**
* con_debug_leave - restore console state
- * @sw: console driver
*
* Restore the console state to what it was before the kernel debugger
* was invoked.
- *
- * RETURNS:
- * Zero on success, nonzero if a failure occurred when trying to restore
- * the console.
*/
-int con_debug_leave(void)
+void con_debug_leave(void)
{
struct vc_data *vc;
- int ret = 0;
fg_console = saved_fg_console;
last_console = saved_last_console;
@@ -3468,8 +4346,7 @@ int con_debug_leave(void)
vc = vc_cons[fg_console].d;
if (vc->vc_sw->con_debug_leave)
- ret = vc->vc_sw->con_debug_leave(vc);
- return ret;
+ vc->vc_sw->con_debug_leave(vc);
}
EXPORT_SYMBOL_GPL(con_debug_leave);
@@ -3478,7 +4355,7 @@ static int do_register_con_driver(const struct consw *csw, int first, int last)
struct module *owner = csw->owner;
struct con_driver *con_driver;
const char *desc;
- int i, retval = 0;
+ int i, retval;
WARN_CONSOLE_UNLOCKED();
@@ -3489,24 +4366,25 @@ static int do_register_con_driver(const struct consw *csw, int first, int last)
con_driver = &registered_con_driver[i];
/* already registered */
- if (con_driver->con == csw)
+ if (con_driver->con == csw) {
retval = -EBUSY;
+ goto err;
+ }
}
- if (retval)
- goto err;
-
desc = csw->con_startup();
-
- if (!desc)
+ if (!desc) {
+ retval = -ENODEV;
goto err;
+ }
retval = -EINVAL;
for (i = 0; i < MAX_NR_CON_DRIVER; i++) {
con_driver = &registered_con_driver[i];
- if (con_driver->con == NULL) {
+ if (con_driver->con == NULL &&
+ !(con_driver->flag & CON_DRIVER_FLAG_ZOMBIE)) {
con_driver->con = csw;
con_driver->desc = desc;
con_driver->node = i;
@@ -3522,15 +4400,14 @@ static int do_register_con_driver(const struct consw *csw, int first, int last)
if (retval)
goto err;
- con_driver->dev = device_create(vtconsole_class, NULL,
- MKDEV(0, con_driver->node),
- NULL, "vtcon%i",
- con_driver->node);
-
+ con_driver->dev =
+ device_create_with_groups(&vtconsole_class, NULL,
+ MKDEV(0, con_driver->node),
+ con_driver, con_dev_groups,
+ "vtcon%i", con_driver->node);
if (IS_ERR(con_driver->dev)) {
- printk(KERN_WARNING "Unable to create device for %s; "
- "errno = %ld\n", con_driver->desc,
- PTR_ERR(con_driver->dev));
+ pr_warn("Unable to create device for %s; errno = %ld\n",
+ con_driver->desc, PTR_ERR(con_driver->dev));
con_driver->dev = NULL;
} else {
vtconsole_init_device(con_driver);
@@ -3555,42 +4432,78 @@ err:
*/
int do_unregister_con_driver(const struct consw *csw)
{
- int i, retval = -ENODEV;
+ int i;
/* cannot unregister a bound driver */
if (con_is_bound(csw))
- goto err;
+ return -EBUSY;
+
+ if (csw == conswitchp)
+ return -EINVAL;
for (i = 0; i < MAX_NR_CON_DRIVER; i++) {
struct con_driver *con_driver = &registered_con_driver[i];
- if (con_driver->con == csw &&
- con_driver->flag & CON_DRIVER_FLAG_MODULE) {
- vtconsole_deinit_device(con_driver);
- device_destroy(vtconsole_class,
- MKDEV(0, con_driver->node));
+ if (con_driver->con == csw) {
+ /*
+ * Defer the removal of the sysfs entries since that
+ * will acquire the kernfs s_active lock and we can't
+ * acquire this lock while holding the console lock:
+ * the unbind sysfs entry imposes already the opposite
+ * order. Reset con already here to prevent any later
+ * lookup to succeed and mark this slot as zombie, so
+ * it won't get reused until we complete the removal
+ * in the deferred work.
+ */
con_driver->con = NULL;
- con_driver->desc = NULL;
- con_driver->dev = NULL;
- con_driver->node = 0;
- con_driver->flag = 0;
- con_driver->first = 0;
- con_driver->last = 0;
- retval = 0;
- break;
+ con_driver->flag = CON_DRIVER_FLAG_ZOMBIE;
+ schedule_work(&con_driver_unregister_work);
+
+ return 0;
}
}
-err:
- return retval;
+
+ return -ENODEV;
}
EXPORT_SYMBOL_GPL(do_unregister_con_driver);
+static void con_driver_unregister_callback(struct work_struct *ignored)
+{
+ int i;
+
+ guard(console_lock)();
+
+ for (i = 0; i < MAX_NR_CON_DRIVER; i++) {
+ struct con_driver *con_driver = &registered_con_driver[i];
+
+ if (!(con_driver->flag & CON_DRIVER_FLAG_ZOMBIE))
+ continue;
+
+ console_unlock();
+
+ vtconsole_deinit_device(con_driver);
+ device_destroy(&vtconsole_class, MKDEV(0, con_driver->node));
+
+ console_lock();
+
+ if (WARN_ON_ONCE(con_driver->con))
+ con_driver->con = NULL;
+ con_driver->desc = NULL;
+ con_driver->dev = NULL;
+ con_driver->node = 0;
+ WARN_ON_ONCE(con_driver->flag != CON_DRIVER_FLAG_ZOMBIE);
+ con_driver->flag = 0;
+ con_driver->first = 0;
+ con_driver->last = 0;
+ }
+}
+
/*
* If we support more console drivers, this function is used
* when a driver wants to take over some existing consoles
* and become default driver for newly opened ones.
*
- * do_take_over_console is basically a register followed by unbind
+ * do_take_over_console is basically a register followed by bind
*/
int do_take_over_console(const struct consw *csw, int first, int last, int deflt)
{
@@ -3618,36 +4531,33 @@ EXPORT_SYMBOL_GPL(do_take_over_console);
*/
void give_up_console(const struct consw *csw)
{
- console_lock();
+ guard(console_lock)();
do_unregister_con_driver(csw);
- console_unlock();
}
+EXPORT_SYMBOL(give_up_console);
static int __init vtconsole_class_init(void)
{
int i;
- vtconsole_class = class_create(THIS_MODULE, "vtconsole");
- if (IS_ERR(vtconsole_class)) {
- printk(KERN_WARNING "Unable to create vt console class; "
- "errno = %ld\n", PTR_ERR(vtconsole_class));
- vtconsole_class = NULL;
- }
+ i = class_register(&vtconsole_class);
+ if (i)
+ pr_warn("Unable to create vt console class; errno = %d\n", i);
/* Add system drivers to sysfs */
for (i = 0; i < MAX_NR_CON_DRIVER; i++) {
struct con_driver *con = &registered_con_driver[i];
if (con->con && !con->dev) {
- con->dev = device_create(vtconsole_class, NULL,
- MKDEV(0, con->node),
- NULL, "vtcon%i",
- con->node);
+ con->dev =
+ device_create_with_groups(&vtconsole_class, NULL,
+ MKDEV(0, con->node),
+ con, con_dev_groups,
+ "vtcon%i", con->node);
if (IS_ERR(con->dev)) {
- printk(KERN_WARNING "Unable to create "
- "device for %s; errno = %ld\n",
- con->desc, PTR_ERR(con->dev));
+ pr_warn("Unable to create device for %s; errno = %ld\n",
+ con->desc, PTR_ERR(con->dev));
con->dev = NULL;
} else {
vtconsole_init_device(con);
@@ -3659,20 +4569,20 @@ static int __init vtconsole_class_init(void)
}
postcore_initcall(vtconsole_class_init);
-#endif
-
/*
* Screen blanking
*/
-static int set_vesa_blanking(char __user *p)
+static int set_vesa_blanking(u8 __user *mode_user)
{
- unsigned int mode;
+ u8 mode;
- if (get_user(mode, p + 1))
+ if (get_user(mode, mode_user))
return -EFAULT;
- vesa_blank_mode = (mode < 4) ? mode : 0;
+ guard(console_lock)();
+ vesa_blank_mode = (mode <= VESA_BLANK_MAX) ? mode : VESA_NO_BLANKING;
+
return 0;
}
@@ -3681,6 +4591,8 @@ void do_blank_screen(int entering_gfx)
struct vc_data *vc = vc_cons[fg_console].d;
int i;
+ might_sleep();
+
WARN_CONSOLE_UNLOCKED();
if (console_blanked) {
@@ -3695,15 +4607,13 @@ void do_blank_screen(int entering_gfx)
if (entering_gfx) {
hide_cursor(vc);
save_screen(vc);
- vc->vc_sw->con_blank(vc, -1, 1);
+ vc->vc_sw->con_blank(vc, VESA_VSYNC_SUSPEND, 1);
console_blanked = fg_console + 1;
blank_state = blank_off;
set_origin(vc);
return;
}
- if (blank_state != blank_normal_wait)
- return;
blank_state = blank_off;
/* don't blank graphics */
@@ -3713,12 +4623,13 @@ void do_blank_screen(int entering_gfx)
}
hide_cursor(vc);
- del_timer_sync(&console_timer);
+ timer_delete_sync(&console_timer);
blank_timer_expired = 0;
save_screen(vc);
/* In case we need to reset origin, blanking hook returns 1 */
- i = vc->vc_sw->con_blank(vc, vesa_off_interval ? 1 : (vesa_blank_mode + 1), 0);
+ i = vc->vc_sw->con_blank(vc, vesa_off_interval ? VESA_VSYNC_SUSPEND :
+ (vesa_blank_mode + 1), 0);
console_blanked = fg_console + 1;
if (i)
set_origin(vc);
@@ -3755,13 +4666,12 @@ void do_unblank_screen(int leaving_gfx)
return;
if (!vc_cons_allocated(fg_console)) {
/* impossible */
- pr_warning("unblank_screen: tty %d not allocated ??\n",
- fg_console+1);
+ pr_warn("unblank_screen: tty %d not allocated ??\n",
+ fg_console + 1);
return;
}
vc = vc_cons[fg_console].d;
- /* Try to unblank in oops case too */
- if (vc->vc_mode != KD_TEXT && !vt_force_oops_output(vc))
+ if (vc->vc_mode != KD_TEXT)
return; /* but leave console_blanked != 0 */
if (blankinterval) {
@@ -3770,7 +4680,7 @@ void do_unblank_screen(int leaving_gfx)
}
console_blanked = 0;
- if (vc->vc_sw->con_blank(vc, 0, leaving_gfx) || vt_force_oops_output(vc))
+ if (vc->vc_sw->con_blank(vc, VESA_NO_BLANKING, leaving_gfx))
/* Low-level driver cannot restore -> do it ourselves */
update_screen(vc);
if (console_blank_hook)
@@ -3778,6 +4688,7 @@ void do_unblank_screen(int leaving_gfx)
set_palette(vc);
set_cursor(vc);
vt_event_post(VT_EVENT_UNBLANK, vc->vc_num, vc->vc_num);
+ notify_update(vc);
}
EXPORT_SYMBOL(do_unblank_screen);
@@ -3787,7 +4698,7 @@ EXPORT_SYMBOL(do_unblank_screen);
* call it with 1 as an argument and so force a mode restore... that may kill
* X or at least garbage the screen but would also make the Oops visible...
*/
-void unblank_screen(void)
+static void unblank_screen(void)
{
do_unblank_screen(0);
}
@@ -3797,12 +4708,8 @@ void unblank_screen(void)
* (console operations can still happen at irq time, but only from printk which
* has the console mutex. Not perfect yet, but better than no locking
*/
-static void blank_screen_t(unsigned long dummy)
+static void blank_screen_t(struct timer_list *unused)
{
- if (unlikely(!keventd_up())) {
- mod_timer(&console_timer, jiffies + (blankinterval * HZ));
- return;
- }
blank_timer_expired = 1;
schedule_work(&console_work);
}
@@ -3820,9 +4727,9 @@ void poke_blanked_console(void)
might_sleep();
/* This isn't perfectly race free, but a race here would be mostly harmless,
- * at worse, we'll do a spurrious blank and it's unlikely
+ * at worst, we'll do a spurious blank and it's unlikely
*/
- del_timer(&console_timer);
+ timer_delete(&console_timer);
blank_timer_expired = 0;
if (ignore_poke || !vc_cons[fg_console].d || vc_cons[fg_console].d->vc_mode == KD_GRAPHICS)
@@ -3843,7 +4750,7 @@ static void set_palette(struct vc_data *vc)
{
WARN_CONSOLE_UNLOCKED();
- if (vc->vc_mode != KD_GRAPHICS)
+ if (vc->vc_mode != KD_GRAPHICS && vc->vc_sw->con_set_palette)
vc->vc_sw->con_set_palette(vc, color_table);
}
@@ -3860,7 +4767,7 @@ int con_set_cmap(unsigned char __user *arg)
if (copy_from_user(colormap, arg, sizeof(colormap)))
return -EFAULT;
- console_lock();
+ guard(console_lock)();
for (i = k = 0; i < 16; i++) {
default_red[i] = colormap[k++];
default_grn[i] = colormap[k++];
@@ -3876,7 +4783,6 @@ int con_set_cmap(unsigned char __user *arg)
}
set_palette(vc_cons[i].d);
}
- console_unlock();
return 0;
}
@@ -3886,13 +4792,12 @@ int con_get_cmap(unsigned char __user *arg)
int i, k;
unsigned char colormap[3*16];
- console_lock();
- for (i = k = 0; i < 16; i++) {
- colormap[k++] = default_red[i];
- colormap[k++] = default_grn[i];
- colormap[k++] = default_blu[i];
- }
- console_unlock();
+ scoped_guard(console_lock)
+ for (i = k = 0; i < 16; i++) {
+ colormap[k++] = default_red[i];
+ colormap[k++] = default_grn[i];
+ colormap[k++] = default_blu[i];
+ }
if (copy_to_user(arg, colormap, sizeof(colormap)))
return -EFAULT;
@@ -3914,125 +4819,104 @@ void reset_palette(struct vc_data *vc)
/*
* Font switching
*
- * Currently we only support fonts up to 32 pixels wide, at a maximum height
- * of 32 pixels. Userspace fontdata is stored with 32 bytes (shorts/ints,
- * depending on width) reserved for each character which is kinda wasty, but
- * this is done in order to maintain compatibility with the EGA/VGA fonts. It
- * is up to the actual low-level console-driver convert data into its favorite
- * format (maybe we should add a `fontoffset' field to the `display'
- * structure so we won't have to convert the fontdata all the time.
+ * Currently we only support fonts up to 128 pixels wide, at a maximum height
+ * of 128 pixels. Userspace fontdata may have to be stored with 32 bytes
+ * (shorts/ints, depending on width) reserved for each character which is
+ * kinda wasty, but this is done in order to maintain compatibility with the
+ * EGA/VGA fonts. It is up to the actual low-level console-driver convert data
+ * into its favorite format (maybe we should add a `fontoffset' field to the
+ * `display' structure so we won't have to convert the fontdata all the time.
* /Jes
*/
-#define max_font_size 65536
+#define max_font_width 64
+#define max_font_height 128
+#define max_font_glyphs 512
+#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)
{
struct console_font font;
- int rc = -EINVAL;
int c;
+ unsigned int vpitch = op->op == KD_FONT_OP_GET_TALL ? op->height : 32;
+ if (vpitch > max_font_height)
+ return -EINVAL;
+
+ void *font_data __free(kvfree) = NULL;
if (op->data) {
- font.data = kmalloc(max_font_size, GFP_KERNEL);
+ font.data = font_data = kvzalloc(max_font_size, GFP_KERNEL);
if (!font.data)
return -ENOMEM;
} else
font.data = NULL;
- console_lock();
- if (vc->vc_mode != KD_TEXT)
- rc = -EINVAL;
- else if (vc->vc_sw->con_font_get)
- rc = vc->vc_sw->con_font_get(vc, &font);
- else
- rc = -ENOSYS;
- console_unlock();
+ scoped_guard(console_lock) {
+ if (vc->vc_mode != KD_TEXT)
+ return -EINVAL;
+ if (!vc->vc_sw->con_font_get)
+ return -ENOSYS;
- if (rc)
- goto out;
+ int ret = vc->vc_sw->con_font_get(vc, &font, vpitch);
+ if (ret)
+ return ret;
+ }
- c = (font.width+7)/8 * 32 * font.charcount;
+ c = DIV_ROUND_UP(font.width, 8) * vpitch * font.charcount;
if (op->data && font.charcount > op->charcount)
- rc = -ENOSPC;
- if (!(op->flags & KD_FONT_FLAG_OLD)) {
- if (font.width > op->width || font.height > op->height)
- rc = -ENOSPC;
- } else {
- if (font.width != 8)
- rc = -EIO;
- else if ((op->height && font.height > op->height) ||
- font.height > 32)
- rc = -ENOSPC;
- }
- if (rc)
- goto out;
+ return -ENOSPC;
+ if (font.width > op->width || font.height > op->height)
+ return -ENOSPC;
op->height = font.height;
op->width = font.width;
op->charcount = font.charcount;
if (op->data && copy_to_user(op->data, font.data, c))
- rc = -EFAULT;
+ return -EFAULT;
-out:
- kfree(font.data);
- return rc;
+ return 0;
}
-static int con_font_set(struct vc_data *vc, struct console_font_op *op)
+static int con_font_set(struct vc_data *vc, const struct console_font_op *op)
{
struct console_font font;
- int rc = -EINVAL;
int size;
+ unsigned int vpitch = op->op == KD_FONT_OP_SET_TALL ? op->height : 32;
- if (vc->vc_mode != KD_TEXT)
- return -EINVAL;
if (!op->data)
return -EINVAL;
- if (op->charcount > 512)
+ if (op->charcount > max_font_glyphs)
return -EINVAL;
- if (!op->height) { /* Need to guess font height [compat] */
- int h, i;
- u8 __user *charmap = op->data;
- u8 tmp;
-
- /* If from KDFONTOP ioctl, don't allow things which can be done in userland,
- so that we can get rid of this soon */
- if (!(op->flags & KD_FONT_FLAG_OLD))
- return -EINVAL;
- for (h = 32; h > 0; h--)
- for (i = 0; i < op->charcount; i++) {
- if (get_user(tmp, &charmap[32*i+h-1]))
- return -EFAULT;
- if (tmp)
- goto nonzero;
- }
+ if (op->width <= 0 || op->width > max_font_width || !op->height ||
+ op->height > max_font_height)
return -EINVAL;
- nonzero:
- op->height = h;
- }
- if (op->width <= 0 || op->width > 32 || op->height > 32)
+ if (vpitch < op->height)
return -EINVAL;
- size = (op->width+7)/8 * 32 * op->charcount;
+ size = DIV_ROUND_UP(op->width, 8) * vpitch * op->charcount;
if (size > max_font_size)
return -ENOSPC;
- font.charcount = op->charcount;
- font.height = op->height;
- font.width = op->width;
- font.data = memdup_user(op->data, size);
+
+ void *font_data __free(kfree) = font.data = memdup_user(op->data, size);
if (IS_ERR(font.data))
return PTR_ERR(font.data);
- console_lock();
+
+ font.charcount = op->charcount;
+ font.width = op->width;
+ font.height = op->height;
+
+ guard(console_lock)();
+
if (vc->vc_mode != KD_TEXT)
- rc = -EINVAL;
- else if (vc->vc_sw->con_font_set)
- rc = vc->vc_sw->con_font_set(vc, &font, op->flags);
- else
- rc = -ENOSYS;
- console_unlock();
- kfree(font.data);
- return rc;
+ return -EINVAL;
+ if (!vc->vc_sw->con_font_set)
+ return -ENOSYS;
+
+ if (vc_is_sel(vc))
+ clear_selection();
+
+ return vc->vc_sw->con_font_set(vc, &font, vpitch, op->flags);
}
static int con_font_default(struct vc_data *vc, struct console_font_op *op)
@@ -4040,8 +4924,6 @@ static int con_font_default(struct vc_data *vc, struct console_font_op *op)
struct console_font font = {.width = op->width, .height = op->height};
char name[MAX_FONT_NAME];
char *s = name;
- int rc;
-
if (!op->data)
s = NULL;
@@ -4050,55 +4932,39 @@ static int con_font_default(struct vc_data *vc, struct console_font_op *op)
else
name[MAX_FONT_NAME - 1] = 0;
- console_lock();
- if (vc->vc_mode != KD_TEXT) {
- console_unlock();
- return -EINVAL;
- }
- if (vc->vc_sw->con_font_default)
- rc = vc->vc_sw->con_font_default(vc, &font, s);
- else
- rc = -ENOSYS;
- console_unlock();
- if (!rc) {
- op->width = font.width;
- op->height = font.height;
- }
- return rc;
-}
+ scoped_guard(console_lock) {
+ if (vc->vc_mode != KD_TEXT)
+ return -EINVAL;
+ if (!vc->vc_sw->con_font_default)
+ return -ENOSYS;
-static int con_font_copy(struct vc_data *vc, struct console_font_op *op)
-{
- int con = op->height;
- int rc;
+ if (vc_is_sel(vc))
+ clear_selection();
+ int ret = vc->vc_sw->con_font_default(vc, &font, s);
+ if (ret)
+ return ret;
+ }
+ op->width = font.width;
+ op->height = font.height;
- console_lock();
- if (vc->vc_mode != KD_TEXT)
- rc = -EINVAL;
- else if (!vc->vc_sw->con_font_copy)
- rc = -ENOSYS;
- else if (con < 0 || !vc_cons_allocated(con))
- rc = -ENOTTY;
- else if (con == vc->vc_num) /* nothing to do */
- rc = 0;
- else
- rc = vc->vc_sw->con_font_copy(vc, con);
- console_unlock();
- return rc;
+ return 0;
}
int con_font_op(struct vc_data *vc, struct console_font_op *op)
{
switch (op->op) {
case KD_FONT_OP_SET:
+ case KD_FONT_OP_SET_TALL:
return con_font_set(vc, op);
case KD_FONT_OP_GET:
+ case KD_FONT_OP_GET_TALL:
return con_font_get(vc, op);
case KD_FONT_OP_SET_DEFAULT:
return con_font_default(vc, op);
case KD_FONT_OP_COPY:
- return con_font_copy(vc, op);
+ /* was buggy and never really used */
+ return -EINVAL;
}
return -ENOSYS;
}
@@ -4108,9 +4974,9 @@ int con_font_op(struct vc_data *vc, struct console_font_op *op)
*/
/* used by selection */
-u16 screen_glyph(struct vc_data *vc, int offset)
+u16 screen_glyph(const struct vc_data *vc, int offset)
{
- u16 w = scr_readw(screenpos(vc, offset, 1));
+ u16 w = scr_readw(screenpos(vc, offset, true));
u16 c = w & 0xff;
if (w & vc->vc_hi_font_mask)
@@ -4119,26 +4985,39 @@ u16 screen_glyph(struct vc_data *vc, int offset)
}
EXPORT_SYMBOL_GPL(screen_glyph);
+u32 screen_glyph_unicode(const struct vc_data *vc, int n)
+{
+ u32 **uni_lines = vc->vc_uni_lines;
+
+ if (uni_lines)
+ return uni_lines[n / vc->vc_cols][n % vc->vc_cols];
+
+ return inverse_translate(vc, screen_glyph(vc, n * 2), true);
+}
+EXPORT_SYMBOL_GPL(screen_glyph_unicode);
+
/* used by vcs - note the word offset */
-unsigned short *screen_pos(struct vc_data *vc, int w_offset, int viewed)
+unsigned short *screen_pos(const struct vc_data *vc, int w_offset, bool viewed)
{
return screenpos(vc, 2 * w_offset, viewed);
}
+EXPORT_SYMBOL_GPL(screen_pos);
-void getconsxy(struct vc_data *vc, unsigned char *p)
+void getconsxy(const struct vc_data *vc, unsigned char xy[static 2])
{
- p[0] = vc->vc_x;
- p[1] = vc->vc_y;
+ /* clamp values if they don't fit */
+ xy[0] = min(vc->state.x, 0xFFu);
+ xy[1] = min(vc->state.y, 0xFFu);
}
-void putconsxy(struct vc_data *vc, unsigned char *p)
+void putconsxy(struct vc_data *vc, unsigned char xy[static const 2])
{
hide_cursor(vc);
- gotoxy(vc, p[0], p[1]);
+ gotoxy(vc, xy[0], xy[1]);
set_cursor(vc);
}
-u16 vcs_scr_readw(struct vc_data *vc, const u16 *org)
+u16 vcs_scr_readw(const struct vc_data *vc, const u16 *org)
{
if ((unsigned long)org == vc->vc_pos && softcursor_original != -1)
return softcursor_original;
@@ -4158,23 +5037,3 @@ void vcs_scr_updated(struct vc_data *vc)
{
notify_update(vc);
}
-
-/*
- * Visible symbols for modules
- */
-
-EXPORT_SYMBOL(color_table);
-EXPORT_SYMBOL(default_red);
-EXPORT_SYMBOL(default_grn);
-EXPORT_SYMBOL(default_blu);
-EXPORT_SYMBOL(update_region);
-EXPORT_SYMBOL(redraw_screen);
-EXPORT_SYMBOL(vc_resize);
-EXPORT_SYMBOL(fg_console);
-EXPORT_SYMBOL(console_blank_hook);
-EXPORT_SYMBOL(console_blanked);
-EXPORT_SYMBOL(vc_cons);
-EXPORT_SYMBOL(global_cursor_default);
-#ifndef VT_SINGLE_DRIVER
-EXPORT_SYMBOL(give_up_console);
-#endif
diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c
index 2bd78e2ac8ec..28993a3d0acb 100644
--- a/drivers/tty/vt/vt_ioctl.c
+++ b/drivers/tty/vt/vt_ioctl.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1992 obz under the linux copyright
*
@@ -10,7 +11,7 @@
#include <linux/types.h>
#include <linux/errno.h>
-#include <linux/sched.h>
+#include <linux/sched/signal.h>
#include <linux/tty.h>
#include <linux/timer.h>
#include <linux/kernel.h>
@@ -29,18 +30,41 @@
#include <linux/timex.h>
#include <asm/io.h>
-#include <asm/uaccess.h>
+#include <linux/uaccess.h>
+
+#include <linux/nospec.h>
#include <linux/kbd_kern.h>
#include <linux/vt_kern.h>
#include <linux/kbd_diacr.h>
#include <linux/selection.h>
-char vt_dont_switch;
-extern struct tty_driver *console_driver;
+bool vt_dont_switch;
+
+static inline bool vt_in_use(unsigned int i)
+{
+ const struct vc_data *vc = vc_cons[i].d;
+
+ /*
+ * console_lock must be held to prevent the vc from being deallocated
+ * while we're checking whether it's in-use.
+ */
+ WARN_CONSOLE_UNLOCKED();
+
+ return vc && kref_read(&vc->port.kref) > 1;
+}
-#define VT_IS_IN_USE(i) (console_driver->ttys[i] && console_driver->ttys[i]->count)
-#define VT_BUSY(i) (VT_IS_IN_USE(i) || i == fg_console || vc_cons[i].d == sel_cons)
+static inline bool vt_busy(int i)
+{
+ if (vt_in_use(i))
+ return true;
+ if (i == fg_console)
+ return true;
+ if (vc_is_sel(vc_cons[i].d))
+ return true;
+
+ return false;
+}
/*
* Console (vt and kd) routines, as defined by USL SVR4 manual, and by
@@ -56,7 +80,7 @@ extern struct tty_driver *console_driver;
*/
#ifdef CONFIG_X86
-#include <linux/syscalls.h>
+#include <asm/syscalls.h>
#endif
static void complete_change_console(struct vc_data *vc);
@@ -157,7 +181,7 @@ static void vt_event_wait(struct vt_event_wait *vw)
/**
* vt_event_wait_ioctl - event ioctl handler
- * @arg: argument to ioctl
+ * @event: argument to ioctl (the event)
*
* Implement the VT_WAITEVENT ioctl using the VT event interface
*/
@@ -184,7 +208,6 @@ static int vt_event_wait_ioctl(struct vt_event __user *event)
/**
* vt_waitactive - active console wait
- * @event: event code
* @n: new console
*
* Helper for event waits. Used to implement the legacy
@@ -217,153 +240,55 @@ int vt_waitactive(int n)
#define GPLAST 0x3df
#define GPNUM (GPLAST - GPFIRST + 1)
-
-
-static inline int
-do_fontx_ioctl(int cmd, struct consolefontdesc __user *user_cfd, int perm, struct console_font_op *op)
-{
- struct consolefontdesc cfdarg;
- int i;
-
- if (copy_from_user(&cfdarg, user_cfd, sizeof(struct consolefontdesc)))
- return -EFAULT;
-
- switch (cmd) {
- case PIO_FONTX:
- if (!perm)
- return -EPERM;
- op->op = KD_FONT_OP_SET;
- op->flags = KD_FONT_FLAG_OLD;
- op->width = 8;
- op->height = cfdarg.charheight;
- op->charcount = cfdarg.charcount;
- op->data = cfdarg.chardata;
- return con_font_op(vc_cons[fg_console].d, op);
- case GIO_FONTX: {
- op->op = KD_FONT_OP_GET;
- op->flags = KD_FONT_FLAG_OLD;
- op->width = 8;
- op->height = cfdarg.charheight;
- op->charcount = cfdarg.charcount;
- op->data = cfdarg.chardata;
- i = con_font_op(vc_cons[fg_console].d, op);
- if (i)
- return i;
- cfdarg.charheight = op->height;
- cfdarg.charcount = op->charcount;
- if (copy_to_user(user_cfd, &cfdarg, sizeof(struct consolefontdesc)))
- return -EFAULT;
- return 0;
- }
- }
- return -EINVAL;
-}
-
-static inline int
-do_unimap_ioctl(int cmd, struct unimapdesc __user *user_ud, int perm, struct vc_data *vc)
-{
- struct unimapdesc tmp;
-
- if (copy_from_user(&tmp, user_ud, sizeof tmp))
- return -EFAULT;
- if (tmp.entries)
- if (!access_ok(VERIFY_WRITE, tmp.entries,
- tmp.entry_ct*sizeof(struct unipair)))
- return -EFAULT;
- switch (cmd) {
- case PIO_UNIMAP:
- if (!perm)
- return -EPERM;
- return con_set_unimap(vc, tmp.entry_ct, tmp.entries);
- 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);
- }
- return 0;
-}
-
-/* deallocate a single console, if possible (leave 0) */
-static int vt_disallocate(unsigned int vc_num)
+/*
+ * currently, setting the mode from KD_TEXT to KD_GRAPHICS doesn't do a whole
+ * lot. i'm not sure if it should do any restoration of modes or what...
+ *
+ * XXX It should at least call into the driver, fbdev's definitely need to
+ * restore their engine state. --BenH
+ *
+ * Called with the console lock held.
+ */
+static int vt_kdsetmode(struct vc_data *vc, unsigned long mode)
{
- struct vc_data *vc = NULL;
- int ret = 0;
-
- console_lock();
- if (VT_BUSY(vc_num))
- ret = -EBUSY;
- else if (vc_num)
- vc = vc_deallocate(vc_num);
- console_unlock();
-
- if (vc && vc_num >= MIN_NR_CONSOLES) {
- tty_port_destroy(&vc->port);
- kfree(vc);
+ switch (mode) {
+ case KD_GRAPHICS:
+ break;
+ case KD_TEXT0:
+ case KD_TEXT1:
+ mode = KD_TEXT;
+ fallthrough;
+ case KD_TEXT:
+ break;
+ default:
+ return -EINVAL;
}
- return ret;
-}
+ if (vc->vc_mode == mode)
+ return 0;
-/* deallocate all unused consoles, but leave 0 */
-static void vt_disallocate_all(void)
-{
- struct vc_data *vc[MAX_NR_CONSOLES];
- int i;
+ vc->vc_mode = mode;
+ if (vc->vc_num != fg_console)
+ return 0;
- console_lock();
- for (i = 1; i < MAX_NR_CONSOLES; i++)
- if (!VT_BUSY(i))
- vc[i] = vc_deallocate(i);
- else
- vc[i] = NULL;
- console_unlock();
+ /* explicitly blank/unblank the screen if switching modes */
+ if (mode == KD_TEXT)
+ do_unblank_screen(1);
+ else
+ do_blank_screen(1);
- for (i = 1; i < MAX_NR_CONSOLES; i++) {
- if (vc[i] && i >= MIN_NR_CONSOLES) {
- tty_port_destroy(&vc[i]->port);
- kfree(vc[i]);
- }
- }
+ return 0;
}
-
-/*
- * We handle the console-specific ioctl's here. We allow the
- * capability to modify any console, not just the fg_console.
- */
-int vt_ioctl(struct tty_struct *tty,
- unsigned int cmd, unsigned long arg)
+static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd,
+ unsigned long arg, bool perm)
{
struct vc_data *vc = tty->driver_data;
- struct console_font_op op; /* used in multiple places here */
- unsigned int console;
- unsigned char ucval;
- unsigned int uival;
void __user *up = (void __user *)arg;
- int i, perm;
- int ret = 0;
-
- console = vc->vc_num;
-
-
- if (!vc_cons_allocated(console)) { /* impossible? */
- ret = -ENOIOCTLCMD;
- goto out;
- }
+ unsigned int console = vc->vc_num;
+ int ret;
-
- /*
- * To have permissions to do most of the vt ioctls, we either have
- * to be the owner of the tty, or have CAP_SYS_TTY_CONFIG.
- */
- perm = 0;
- if (current->signal->tty == tty || capable(CAP_SYS_TTY_CONFIG))
- perm = 1;
-
switch (cmd) {
- case TIOCLINUX:
- ret = tioclinux(tty, arg);
- break;
case KIOCSOUND:
if (!perm)
return -EPERM;
@@ -383,12 +308,12 @@ int vt_ioctl(struct tty_struct *tty,
return -EPERM;
{
unsigned int ticks, count;
-
+
/*
* Generate the tone for the appropriate number of ticks.
* If the time is zero, turn off sound ourselves.
*/
- ticks = HZ * ((arg >> 16) & 0xffff) / 1000;
+ ticks = msecs_to_jiffies((arg >> 16) & 0xffff);
count = ticks ? (arg & 0xffff) : 0;
if (count)
count = PIT_TICK_RATE / count;
@@ -400,9 +325,7 @@ int vt_ioctl(struct tty_struct *tty,
/*
* this is naïve.
*/
- ucval = KB_101;
- ret = put_user(ucval, (char __user *)arg);
- break;
+ return put_user(KB_101, (char __user *)arg);
/*
* These cannot be implemented on any machine that implements
@@ -419,84 +342,46 @@ int vt_ioctl(struct tty_struct *tty,
*
* These are locked internally via sys_ioperm
*/
- if (arg < GPFIRST || arg > GPLAST) {
- ret = -EINVAL;
- break;
- }
- ret = sys_ioperm(arg, 1, (cmd == KDADDIO)) ? -ENXIO : 0;
- break;
+ if (arg < GPFIRST || arg > GPLAST)
+ return -EINVAL;
+
+ return ksys_ioperm(arg, 1, (cmd == KDADDIO)) ? -ENXIO : 0;
case KDENABIO:
case KDDISABIO:
- ret = sys_ioperm(GPFIRST, GPNUM,
+ return ksys_ioperm(GPFIRST, GPNUM,
(cmd == KDENABIO)) ? -ENXIO : 0;
- break;
#endif
/* Linux m68k/i386 interface for setting the keyboard delay/repeat rate */
-
+
case KDKBDREP:
{
struct kbd_repeat kbrep;
-
+
if (!capable(CAP_SYS_TTY_CONFIG))
return -EPERM;
- if (copy_from_user(&kbrep, up, sizeof(struct kbd_repeat))) {
- ret = -EFAULT;
- break;
- }
+ if (copy_from_user(&kbrep, up, sizeof(struct kbd_repeat)))
+ return -EFAULT;
+
ret = kbd_rate(&kbrep);
if (ret)
- break;
+ return ret;
if (copy_to_user(up, &kbrep, sizeof(struct kbd_repeat)))
- ret = -EFAULT;
+ return -EFAULT;
break;
}
- case KDSETMODE:
- /*
- * currently, setting the mode from KD_TEXT to KD_GRAPHICS
- * doesn't do a whole lot. i'm not sure if it should do any
- * restoration of modes or what...
- *
- * XXX It should at least call into the driver, fbdev's definitely
- * need to restore their engine state. --BenH
- */
+ case KDSETMODE: {
if (!perm)
return -EPERM;
- switch (arg) {
- case KD_GRAPHICS:
- break;
- case KD_TEXT0:
- case KD_TEXT1:
- arg = KD_TEXT;
- case KD_TEXT:
- break;
- default:
- ret = -EINVAL;
- goto out;
- }
- /* FIXME: this needs the console lock extending */
- if (vc->vc_mode == (unsigned char) arg)
- break;
- vc->vc_mode = (unsigned char) arg;
- if (console != fg_console)
- break;
- /*
- * explicitly blank/unblank the screen if switching modes
- */
- console_lock();
- if (arg == KD_TEXT)
- do_unblank_screen(1);
- else
- do_blank_screen(1);
- console_unlock();
- break;
+ guard(console_lock)();
+ return vt_kdsetmode(vc, arg);
+ }
case KDGETMODE:
- uival = vc->vc_mode;
- goto setint;
+ return put_user(vc->vc_mode, (int __user *)arg);
case KDMAPDISP:
case KDUNMAPDISP:
@@ -504,51 +389,42 @@ int vt_ioctl(struct tty_struct *tty,
* these work like a combination of mmap and KDENABIO.
* this could be easily finished.
*/
- ret = -EINVAL;
- break;
+ return -EINVAL;
case KDSKBMODE:
if (!perm)
return -EPERM;
ret = vt_do_kdskbmode(console, arg);
- if (ret == 0)
- tty_ldisc_flush(tty);
+ if (ret)
+ return ret;
+ tty_ldisc_flush(tty);
break;
case KDGKBMODE:
- uival = vt_do_kdgkbmode(console);
- ret = put_user(uival, (int __user *)arg);
- break;
+ return put_user(vt_do_kdgkbmode(console), (int __user *)arg);
/* this could be folded into KDSKBMODE, but for compatibility
reasons it is not so easy to fold KDGKBMETA into KDGKBMODE */
case KDSKBMETA:
- ret = vt_do_kdskbmeta(console, arg);
- break;
+ return vt_do_kdskbmeta(console, arg);
case KDGKBMETA:
/* FIXME: should review whether this is worth locking */
- uival = vt_do_kdgkbmeta(console);
- setint:
- ret = put_user(uival, (int __user *)arg);
- break;
+ return put_user(vt_do_kdgkbmeta(console), (int __user *)arg);
case KDGETKEYCODE:
case KDSETKEYCODE:
if(!capable(CAP_SYS_TTY_CONFIG))
perm = 0;
- ret = vt_do_kbkeycode_ioctl(cmd, up, perm);
- break;
+ return vt_do_kbkeycode_ioctl(cmd, up, perm);
case KDGKBENT:
case KDSKBENT:
- ret = vt_do_kdsk_ioctl(cmd, up, perm, console);
- break;
+ return vt_do_kdsk_ioctl(cmd, up, perm, console);
case KDGKBSENT:
case KDSKBSENT:
- ret = vt_do_kdgkb_ioctl(cmd, up, perm);
- break;
+ return vt_do_kdgkb_ioctl(cmd, up, perm);
/* Diacritical processing. Handled in keyboard.c as it has
to operate on the keyboard locks and structures */
@@ -556,8 +432,7 @@ int vt_ioctl(struct tty_struct *tty,
case KDGKBDIACRUC:
case KDSKBDIACR:
case KDSKBDIACRUC:
- ret = vt_do_diacrit(cmd, up, perm);
- break;
+ return vt_do_diacrit(cmd, up, perm);
/* the ioctls below read/set the flags usually shown in the leds */
/* don't use them - they will go away without warning */
@@ -565,8 +440,7 @@ int vt_ioctl(struct tty_struct *tty,
case KDSKBLED:
case KDGETLED:
case KDSETLED:
- ret = vt_do_kdskled(console, cmd, arg, perm);
- break;
+ return vt_do_kdskled(console, cmd, arg, perm);
/*
* A process can indicate its willingness to accept signals
@@ -576,36 +450,319 @@ int vt_ioctl(struct tty_struct *tty,
* See also the kbrequest field of inittab(5).
*/
case KDSIGACCEPT:
- {
if (!perm || !capable(CAP_KILL))
return -EPERM;
if (!valid_signal(arg) || arg < 1 || arg == SIGKILL)
- ret = -EINVAL;
- else {
- spin_lock_irq(&vt_spawn_con.lock);
- put_pid(vt_spawn_con.pid);
- vt_spawn_con.pid = get_pid(task_pid(current));
- vt_spawn_con.sig = arg;
- spin_unlock_irq(&vt_spawn_con.lock);
- }
+ return -EINVAL;
+
+ spin_lock_irq(&vt_spawn_con.lock);
+ put_pid(vt_spawn_con.pid);
+ vt_spawn_con.pid = get_pid(task_pid(current));
+ vt_spawn_con.sig = arg;
+ spin_unlock_irq(&vt_spawn_con.lock);
+ break;
+
+ case KDFONTOP: {
+ struct console_font_op op;
+
+ if (copy_from_user(&op, up, sizeof(op)))
+ return -EFAULT;
+ if (!perm && op.op != KD_FONT_OP_GET)
+ return -EPERM;
+ ret = con_font_op(vc, &op);
+ if (ret)
+ return ret;
+ if (copy_to_user(up, &op, sizeof(op)))
+ return -EFAULT;
+ break;
+ }
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+static inline int do_unimap_ioctl(int cmd, struct unimapdesc __user *user_ud,
+ bool perm, struct vc_data *vc)
+{
+ struct unimapdesc tmp;
+
+ if (copy_from_user(&tmp, user_ud, sizeof tmp))
+ return -EFAULT;
+ switch (cmd) {
+ case PIO_UNIMAP:
+ if (!perm)
+ return -EPERM;
+ return con_set_unimap(vc, tmp.entry_ct, tmp.entries);
+ 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);
+ }
+ return 0;
+}
+
+static int vt_io_ioctl(struct vc_data *vc, unsigned int cmd, void __user *up,
+ bool perm)
+{
+ switch (cmd) {
+ case PIO_CMAP:
+ if (!perm)
+ return -EPERM;
+ return con_set_cmap(up);
+
+ case GIO_CMAP:
+ return con_get_cmap(up);
+
+ case PIO_SCRNMAP:
+ if (!perm)
+ return -EPERM;
+ return con_set_trans_old(up);
+
+ case GIO_SCRNMAP:
+ return con_get_trans_old(up);
+
+ case PIO_UNISCRNMAP:
+ if (!perm)
+ return -EPERM;
+ return con_set_trans_new(up);
+
+ case GIO_UNISCRNMAP:
+ return con_get_trans_new(up);
+
+ case PIO_UNIMAPCLR:
+ if (!perm)
+ return -EPERM;
+ con_clear_unimap(vc);
break;
+
+ case PIO_UNIMAP:
+ case GIO_UNIMAP:
+ return do_unimap_ioctl(cmd, up, perm, vc);
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+static int vt_reldisp(struct vc_data *vc, unsigned int swtch)
+{
+ int newvt, ret;
+
+ if (vc->vt_mode.mode != VT_PROCESS)
+ return -EINVAL;
+
+ /* Switched-to response */
+ if (vc->vt_newvt < 0) {
+ /* If it's just an ACK, ignore it */
+ return swtch == VT_ACKACQ ? 0 : -EINVAL;
+ }
+
+ /* Switching-from response */
+ if (swtch == 0) {
+ /* Switch disallowed, so forget we were trying to do it. */
+ vc->vt_newvt = -1;
+ return 0;
+ }
+
+ /* The current vt has been released, so complete the switch. */
+ newvt = vc->vt_newvt;
+ vc->vt_newvt = -1;
+ ret = vc_allocate(newvt);
+ if (ret)
+ return ret;
+
+ /*
+ * When we actually do the console switch, make sure we are atomic with
+ * respect to other console switches..
+ */
+ complete_change_console(vc_cons[newvt].d);
+
+ return 0;
+}
+
+static int vt_setactivate(struct vt_setactivate __user *sa)
+{
+ struct vt_setactivate vsa;
+ struct vc_data *nvc;
+ int ret;
+
+ if (copy_from_user(&vsa, sa, sizeof(vsa)))
+ return -EFAULT;
+ if (vsa.console == 0 || vsa.console > MAX_NR_CONSOLES)
+ return -ENXIO;
+
+ vsa.console--;
+ vsa.console = array_index_nospec(vsa.console, MAX_NR_CONSOLES);
+ scoped_guard(console_lock) {
+ ret = vc_allocate(vsa.console);
+ if (ret)
+ return ret;
+
+ /*
+ * This is safe providing we don't drop the console sem between
+ * vc_allocate and finishing referencing nvc.
+ */
+ nvc = vc_cons[vsa.console].d;
+ nvc->vt_mode = vsa.mode;
+ nvc->vt_mode.frsig = 0;
+ put_pid(nvc->vt_pid);
+ nvc->vt_pid = get_pid(task_pid(current));
+ }
+
+ /* Commence switch and lock */
+ /* Review set_console locks */
+ set_console(vsa.console);
+
+ return 0;
+}
+
+/* deallocate a single console, if possible (leave 0) */
+static int vt_disallocate(unsigned int vc_num)
+{
+ struct vc_data *vc = NULL;
+
+ scoped_guard(console_lock) {
+ if (vt_busy(vc_num))
+ return -EBUSY;
+ if (vc_num)
+ vc = vc_deallocate(vc_num);
+ }
+
+ if (vc && vc_num >= MIN_NR_CONSOLES)
+ tty_port_put(&vc->port);
+
+ return 0;
+}
+
+/* deallocate all unused consoles, but leave 0 */
+static void vt_disallocate_all(void)
+{
+ struct vc_data *vc[MAX_NR_CONSOLES];
+ int i;
+
+ scoped_guard(console_lock)
+ for (i = 1; i < MAX_NR_CONSOLES; i++)
+ if (!vt_busy(i))
+ vc[i] = vc_deallocate(i);
+ else
+ vc[i] = NULL;
+
+ for (i = 1; i < MAX_NR_CONSOLES; i++) {
+ if (vc[i] && i >= MIN_NR_CONSOLES)
+ tty_port_put(&vc[i]->port);
+ }
+}
+
+static int vt_resizex(struct vc_data *vc, struct vt_consize __user *cs)
+{
+ struct vt_consize v;
+ int i;
+
+ if (copy_from_user(&v, cs, sizeof(struct vt_consize)))
+ return -EFAULT;
+
+ /* FIXME: Should check the copies properly */
+ if (!v.v_vlin)
+ v.v_vlin = vc->vc_scan_lines;
+
+ if (v.v_clin) {
+ int rows = v.v_vlin / v.v_clin;
+ if (v.v_rows != rows) {
+ if (v.v_rows) /* Parameters don't add up */
+ return -EINVAL;
+ v.v_rows = rows;
+ }
+ }
+
+ if (v.v_vcol && v.v_ccol) {
+ int cols = v.v_vcol / v.v_ccol;
+ if (v.v_cols != cols) {
+ if (v.v_cols)
+ return -EINVAL;
+ v.v_cols = cols;
+ }
+ }
+
+ if (v.v_clin > 32)
+ return -EINVAL;
+
+ for (i = 0; i < MAX_NR_CONSOLES; i++) {
+ struct vc_data *vcp;
+
+ if (!vc_cons[i].d)
+ continue;
+ guard(console_lock)();
+ vcp = vc_cons[i].d;
+ if (vcp) {
+ int ret;
+ int save_scan_lines = vcp->vc_scan_lines;
+ int save_cell_height = vcp->vc_cell_height;
+
+ if (v.v_vlin)
+ vcp->vc_scan_lines = v.v_vlin;
+ if (v.v_clin)
+ vcp->vc_cell_height = v.v_clin;
+ ret = __vc_resize(vcp, v.v_cols, v.v_rows, true);
+ if (ret) {
+ vcp->vc_scan_lines = save_scan_lines;
+ vcp->vc_cell_height = save_cell_height;
+ return ret;
+ }
+ }
}
+ return 0;
+}
+
+/*
+ * We handle the console-specific ioctl's here. We allow the
+ * capability to modify any console, not just the fg_console.
+ */
+int vt_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg)
+{
+ struct vc_data *vc = tty->driver_data;
+ void __user *up = (void __user *)arg;
+ int i, perm;
+ int ret;
+
+ /*
+ * To have permissions to do most of the vt ioctls, we either have
+ * to be the owner of the tty, or have CAP_SYS_TTY_CONFIG.
+ */
+ perm = 0;
+ if (current->signal->tty == tty || capable(CAP_SYS_TTY_CONFIG))
+ perm = 1;
+
+ ret = vt_k_ioctl(tty, cmd, arg, perm);
+ if (ret != -ENOIOCTLCMD)
+ return ret;
+
+ ret = vt_io_ioctl(vc, cmd, up, perm);
+ if (ret != -ENOIOCTLCMD)
+ return ret;
+
+ switch (cmd) {
+ case TIOCLINUX:
+ return tioclinux(tty, arg);
case VT_SETMODE:
{
struct vt_mode tmp;
if (!perm)
return -EPERM;
- if (copy_from_user(&tmp, up, sizeof(struct vt_mode))) {
- ret = -EFAULT;
- goto out;
- }
- if (tmp.mode != VT_AUTO && tmp.mode != VT_PROCESS) {
- ret = -EINVAL;
- goto out;
- }
- console_lock();
+ if (copy_from_user(&tmp, up, sizeof(struct vt_mode)))
+ return -EFAULT;
+ if (tmp.mode != VT_AUTO && tmp.mode != VT_PROCESS)
+ return -EINVAL;
+
+ guard(console_lock)();
vc->vt_mode = tmp;
/* the frsig is ignored, so we set it to 0 */
vc->vt_mode.frsig = 0;
@@ -613,7 +770,6 @@ int vt_ioctl(struct tty_struct *tty,
vc->vt_pid = get_pid(task_pid(current));
/* no switch is required -- saw@shade.msu.ru */
vc->vt_newvt = -1;
- console_unlock();
break;
}
@@ -622,13 +778,12 @@ int vt_ioctl(struct tty_struct *tty,
struct vt_mode tmp;
int rc;
- console_lock();
- memcpy(&tmp, &vc->vt_mode, sizeof(struct vt_mode));
- console_unlock();
+ scoped_guard(console_lock)
+ memcpy(&tmp, &vc->vt_mode, sizeof(struct vt_mode));
rc = copy_to_user(up, &tmp, sizeof(struct vt_mode));
if (rc)
- ret = -EFAULT;
+ return -EFAULT;
break;
}
@@ -642,30 +797,27 @@ int vt_ioctl(struct tty_struct *tty,
struct vt_stat __user *vtstat = up;
unsigned short state, mask;
- /* Review: FIXME: Console lock ? */
if (put_user(fg_console + 1, &vtstat->v_active))
- ret = -EFAULT;
- else {
- state = 1; /* /dev/tty0 is always open */
- for (i = 0, mask = 2; i < MAX_NR_CONSOLES && mask;
- ++i, mask <<= 1)
- if (VT_IS_IN_USE(i))
+ return -EFAULT;
+
+ state = 1; /* /dev/tty0 is always open */
+ scoped_guard(console_lock) /* required by vt_in_use() */
+ for (i = 0, mask = 2; i < MAX_NR_CONSOLES && mask; ++i, mask <<= 1)
+ if (vt_in_use(i))
state |= mask;
- ret = put_user(state, &vtstat->v_state);
- }
- break;
+ return put_user(state, &vtstat->v_state);
}
/*
* Returns the first available (non-opened) console.
*/
case VT_OPENQRY:
- /* FIXME: locking ? - but then this is a stupid API */
- for (i = 0; i < MAX_NR_CONSOLES; ++i)
- if (! VT_IS_IN_USE(i))
- break;
- uival = i < MAX_NR_CONSOLES ? (i+1) : -1;
- goto setint;
+ scoped_guard(console_lock) /* required by vt_in_use() */
+ for (i = 0; i < MAX_NR_CONSOLES; ++i)
+ if (!vt_in_use(i))
+ break;
+ i = i < MAX_NR_CONSOLES ? (i+1) : -1;
+ return put_user(i, (int __user *)arg);
/*
* ioctl(fd, VT_ACTIVATE, num) will cause us to switch to vt # num,
@@ -676,56 +828,23 @@ int vt_ioctl(struct tty_struct *tty,
if (!perm)
return -EPERM;
if (arg == 0 || arg > MAX_NR_CONSOLES)
- ret = -ENXIO;
- else {
- arg--;
- console_lock();
+ return -ENXIO;
+
+ arg--;
+ arg = array_index_nospec(arg, MAX_NR_CONSOLES);
+ scoped_guard(console_lock) {
ret = vc_allocate(arg);
- console_unlock();
if (ret)
- break;
- set_console(arg);
+ return ret;
}
+ set_console(arg);
break;
case VT_SETACTIVATE:
- {
- struct vt_setactivate vsa;
-
if (!perm)
return -EPERM;
- if (copy_from_user(&vsa, (struct vt_setactivate __user *)arg,
- sizeof(struct vt_setactivate))) {
- ret = -EFAULT;
- goto out;
- }
- if (vsa.console == 0 || vsa.console > MAX_NR_CONSOLES)
- ret = -ENXIO;
- else {
- vsa.console--;
- console_lock();
- ret = vc_allocate(vsa.console);
- if (ret == 0) {
- struct vc_data *nvc;
- /* This is safe providing we don't drop the
- console sem between vc_allocate and
- finishing referencing nvc */
- nvc = vc_cons[vsa.console].d;
- nvc->vt_mode = vsa.mode;
- nvc->vt_mode.frsig = 0;
- put_pid(nvc->vt_pid);
- nvc->vt_pid = get_pid(task_pid(current));
- }
- console_unlock();
- if (ret)
- break;
- /* Commence switch and lock */
- /* Review set_console locks */
- set_console(vsa.console);
- }
- break;
- }
+ return vt_setactivate(up);
/*
* wait until the specified VT has been activated
@@ -734,10 +853,8 @@ int vt_ioctl(struct tty_struct *tty,
if (!perm)
return -EPERM;
if (arg == 0 || arg > MAX_NR_CONSOLES)
- ret = -ENXIO;
- else
- ret = vt_waitactive(arg);
- break;
+ return -ENXIO;
+ return vt_waitactive(arg);
/*
* If a vt is under process control, the kernel will not switch to it
@@ -750,300 +867,97 @@ int vt_ioctl(struct tty_struct *tty,
* 2: completed switch-to OK
*/
case VT_RELDISP:
+ {
if (!perm)
return -EPERM;
- console_lock();
- if (vc->vt_mode.mode != VT_PROCESS) {
- console_unlock();
- ret = -EINVAL;
- break;
- }
- /*
- * Switching-from response
- */
- if (vc->vt_newvt >= 0) {
- if (arg == 0)
- /*
- * Switch disallowed, so forget we were trying
- * to do it.
- */
- vc->vt_newvt = -1;
-
- else {
- /*
- * The current vt has been released, so
- * complete the switch.
- */
- int newvt;
- newvt = vc->vt_newvt;
- vc->vt_newvt = -1;
- ret = vc_allocate(newvt);
- if (ret) {
- console_unlock();
- break;
- }
- /*
- * When we actually do the console switch,
- * make sure we are atomic with respect to
- * other console switches..
- */
- complete_change_console(vc_cons[newvt].d);
- }
- } else {
- /*
- * Switched-to response
- */
- /*
- * If it's just an ACK, ignore it
- */
- if (arg != VT_ACKACQ)
- ret = -EINVAL;
- }
- console_unlock();
- break;
+ guard(console_lock)();
+ return vt_reldisp(vc, arg);
+ }
/*
* Disallocate memory associated to VT (but leave VT1)
*/
case VT_DISALLOCATE:
- if (arg > MAX_NR_CONSOLES) {
- ret = -ENXIO;
+ if (arg > MAX_NR_CONSOLES)
+ return -ENXIO;
+
+ if (arg == 0) {
+ vt_disallocate_all();
break;
}
- if (arg == 0)
- vt_disallocate_all();
- else
- ret = vt_disallocate(--arg);
- break;
+
+ arg = array_index_nospec(arg - 1, MAX_NR_CONSOLES);
+ return vt_disallocate(arg);
case VT_RESIZE:
{
struct vt_sizes __user *vtsizes = up;
struct vc_data *vc;
-
ushort ll,cc;
+
if (!perm)
return -EPERM;
if (get_user(ll, &vtsizes->v_rows) ||
get_user(cc, &vtsizes->v_cols))
- ret = -EFAULT;
- else {
- console_lock();
- for (i = 0; i < MAX_NR_CONSOLES; i++) {
- vc = vc_cons[i].d;
-
- if (vc) {
- vc->vc_resize_user = 1;
- /* FIXME: review v tty lock */
- vc_resize(vc_cons[i].d, cc, ll);
- }
- }
- console_unlock();
- }
- break;
- }
-
- case VT_RESIZEX:
- {
- struct vt_consize __user *vtconsize = up;
- ushort ll,cc,vlin,clin,vcol,ccol;
- if (!perm)
- return -EPERM;
- if (!access_ok(VERIFY_READ, vtconsize,
- sizeof(struct vt_consize))) {
- ret = -EFAULT;
- break;
- }
- /* FIXME: Should check the copies properly */
- __get_user(ll, &vtconsize->v_rows);
- __get_user(cc, &vtconsize->v_cols);
- __get_user(vlin, &vtconsize->v_vlin);
- __get_user(clin, &vtconsize->v_clin);
- __get_user(vcol, &vtconsize->v_vcol);
- __get_user(ccol, &vtconsize->v_ccol);
- vlin = vlin ? vlin : vc->vc_scan_lines;
- if (clin) {
- if (ll) {
- if (ll != vlin/clin) {
- /* Parameters don't add up */
- ret = -EINVAL;
- break;
- }
- } else
- ll = vlin/clin;
- }
- if (vcol && ccol) {
- if (cc) {
- if (cc != vcol/ccol) {
- ret = -EINVAL;
- break;
- }
- } else
- cc = vcol/ccol;
- }
+ return -EFAULT;
- if (clin > 32) {
- ret = -EINVAL;
- break;
- }
-
+ guard(console_lock)();
for (i = 0; i < MAX_NR_CONSOLES; i++) {
- if (!vc_cons[i].d)
- continue;
- console_lock();
- if (vlin)
- vc_cons[i].d->vc_scan_lines = vlin;
- if (clin)
- vc_cons[i].d->vc_font.height = clin;
- vc_cons[i].d->vc_resize_user = 1;
- vc_resize(vc_cons[i].d, cc, ll);
- console_unlock();
- }
- break;
- }
+ vc = vc_cons[i].d;
- case PIO_FONT: {
- if (!perm)
- return -EPERM;
- op.op = KD_FONT_OP_SET;
- op.flags = KD_FONT_FLAG_OLD | KD_FONT_FLAG_DONT_RECALC; /* Compatibility */
- op.width = 8;
- op.height = 0;
- op.charcount = 256;
- op.data = up;
- ret = con_font_op(vc_cons[fg_console].d, &op);
- break;
- }
-
- case GIO_FONT: {
- op.op = KD_FONT_OP_GET;
- op.flags = KD_FONT_FLAG_OLD;
- op.width = 8;
- op.height = 32;
- op.charcount = 256;
- op.data = up;
- ret = con_font_op(vc_cons[fg_console].d, &op);
- break;
- }
-
- case PIO_CMAP:
- if (!perm)
- ret = -EPERM;
- else
- ret = con_set_cmap(up);
- break;
-
- case GIO_CMAP:
- ret = con_get_cmap(up);
- break;
-
- case PIO_FONTX:
- case GIO_FONTX:
- ret = do_fontx_ioctl(cmd, up, perm, &op);
- break;
-
- case PIO_FONTRESET:
- {
- if (!perm)
- return -EPERM;
-
-#ifdef BROKEN_GRAPHICS_PROGRAMS
- /* With BROKEN_GRAPHICS_PROGRAMS defined, the default
- font is not saved. */
- ret = -ENOSYS;
- break;
-#else
- {
- op.op = KD_FONT_OP_SET_DEFAULT;
- op.data = NULL;
- ret = con_font_op(vc_cons[fg_console].d, &op);
- if (ret)
- break;
- console_lock();
- con_set_default_unimap(vc_cons[fg_console].d);
- console_unlock();
- break;
- }
-#endif
- }
-
- case KDFONTOP: {
- if (copy_from_user(&op, up, sizeof(op))) {
- ret = -EFAULT;
- break;
+ if (vc) {
+ /* FIXME: review v tty lock */
+ ret = __vc_resize(vc_cons[i].d, cc, ll, true);
+ if (ret)
+ return ret;
+ }
}
- if (!perm && op.op != KD_FONT_OP_GET)
- return -EPERM;
- ret = con_font_op(vc, &op);
- if (ret)
- break;
- if (copy_to_user(up, &op, sizeof(op)))
- ret = -EFAULT;
break;
}
- case PIO_SCRNMAP:
- if (!perm)
- ret = -EPERM;
- else
- ret = con_set_trans_old(up);
- break;
-
- case GIO_SCRNMAP:
- ret = con_get_trans_old(up);
- break;
-
- case PIO_UNISCRNMAP:
- if (!perm)
- ret = -EPERM;
- else
- ret = con_set_trans_new(up);
- break;
-
- case GIO_UNISCRNMAP:
- ret = con_get_trans_new(up);
- break;
-
- case PIO_UNIMAPCLR:
- { struct unimapinit ui;
+ case VT_RESIZEX:
if (!perm)
return -EPERM;
- ret = copy_from_user(&ui, up, sizeof(struct unimapinit));
- if (ret)
- ret = -EFAULT;
- else
- con_clear_unimap(vc, &ui);
- break;
- }
- case PIO_UNIMAP:
- case GIO_UNIMAP:
- ret = do_unimap_ioctl(cmd, up, perm, vc);
- break;
+ return vt_resizex(vc, up);
case VT_LOCKSWITCH:
if (!capable(CAP_SYS_TTY_CONFIG))
return -EPERM;
- vt_dont_switch = 1;
+ vt_dont_switch = true;
break;
case VT_UNLOCKSWITCH:
if (!capable(CAP_SYS_TTY_CONFIG))
return -EPERM;
- vt_dont_switch = 0;
+ vt_dont_switch = false;
break;
case VT_GETHIFONTMASK:
- ret = put_user(vc->vc_hi_font_mask,
+ return put_user(vc->vc_hi_font_mask,
(unsigned short __user *)arg);
- break;
case VT_WAITEVENT:
- ret = vt_event_wait_ioctl((struct vt_event __user *)arg);
- break;
+ return vt_event_wait_ioctl((struct vt_event __user *)arg);
+
+ case VT_GETCONSIZECSRPOS:
+ {
+ struct vt_consizecsrpos concsr;
+
+ console_lock();
+ concsr.con_cols = vc->vc_cols;
+ concsr.con_rows = vc->vc_rows;
+ concsr.csr_col = vc->state.x;
+ concsr.csr_row = vc->state.y;
+ console_unlock();
+ if (copy_to_user(up, &concsr, sizeof(concsr)))
+ return -EFAULT;
+ return 0;
+ }
+
default:
- ret = -ENOIOCTLCMD;
+ return -ENOIOCTLCMD;
}
-out:
- return ret;
+
+ return 0;
}
void reset_vc(struct vc_data *vc)
@@ -1058,8 +972,7 @@ void reset_vc(struct vc_data *vc)
put_pid(vc->vt_pid);
vc->vt_pid = NULL;
vc->vt_newvt = -1;
- if (!in_interrupt()) /* Via keyboard.c:SAK() - akpm */
- reset_palette(vc);
+ reset_palette(vc);
}
void vc_SAK(struct work_struct *work)
@@ -1069,70 +982,21 @@ void vc_SAK(struct work_struct *work)
struct vc_data *vc;
struct tty_struct *tty;
- console_lock();
+ guard(console_lock)();
vc = vc_con->d;
- if (vc) {
- /* FIXME: review tty ref counting */
- tty = vc->port.tty;
- /*
- * SAK should also work in all raw modes and reset
- * them properly.
- */
- if (tty)
- __do_SAK(tty);
- reset_vc(vc);
- }
- console_unlock();
+ if (!vc)
+ return;
+
+ /* FIXME: review tty ref counting */
+ tty = vc->port.tty;
+ /* SAK should also work in all raw modes and reset them properly. */
+ if (tty)
+ __do_SAK(tty);
+ reset_vc(vc);
}
#ifdef CONFIG_COMPAT
-struct compat_consolefontdesc {
- unsigned short charcount; /* characters in font (256 or 512) */
- unsigned short charheight; /* scan lines per character (1-32) */
- compat_caddr_t chardata; /* font data in expanded form */
-};
-
-static inline int
-compat_fontx_ioctl(int cmd, struct compat_consolefontdesc __user *user_cfd,
- int perm, struct console_font_op *op)
-{
- struct compat_consolefontdesc cfdarg;
- int i;
-
- if (copy_from_user(&cfdarg, user_cfd, sizeof(struct compat_consolefontdesc)))
- return -EFAULT;
-
- switch (cmd) {
- case PIO_FONTX:
- if (!perm)
- return -EPERM;
- op->op = KD_FONT_OP_SET;
- op->flags = KD_FONT_FLAG_OLD;
- op->width = 8;
- op->height = cfdarg.charheight;
- op->charcount = cfdarg.charcount;
- op->data = compat_ptr(cfdarg.chardata);
- return con_font_op(vc_cons[fg_console].d, op);
- case GIO_FONTX:
- op->op = KD_FONT_OP_GET;
- op->flags = KD_FONT_FLAG_OLD;
- op->width = 8;
- op->height = cfdarg.charheight;
- op->charcount = cfdarg.charcount;
- op->data = compat_ptr(cfdarg.chardata);
- i = con_font_op(vc_cons[fg_console].d, op);
- if (i)
- return i;
- cfdarg.charheight = op->height;
- cfdarg.charcount = op->charcount;
- if (copy_to_user(user_cfd, &cfdarg, sizeof(struct compat_consolefontdesc)))
- return -EFAULT;
- return 0;
- }
- return -EINVAL;
-}
-
struct compat_console_font_op {
compat_uint_t op; /* operation code KD_FONT_OP_* */
compat_uint_t flags; /* KD_FONT_FLAG_* */
@@ -1176,10 +1040,6 @@ compat_unimap_ioctl(unsigned int cmd, struct compat_unimapdesc __user *user_ud,
if (copy_from_user(&tmp, user_ud, sizeof tmp))
return -EFAULT;
tmp_entries = compat_ptr(tmp.entries);
- if (tmp_entries)
- if (!access_ok(VERIFY_WRITE, tmp_entries,
- tmp.entry_ct*sizeof(struct unipair)))
- return -EFAULT;
switch (cmd) {
case PIO_UNIMAP:
if (!perm)
@@ -1198,17 +1058,8 @@ long vt_compat_ioctl(struct tty_struct *tty,
{
struct vc_data *vc = tty->driver_data;
struct console_font_op op; /* used in multiple places here */
- unsigned int console;
- void __user *up = (void __user *)arg;
+ void __user *up = compat_ptr(arg);
int perm;
- int ret = 0;
-
- console = vc->vc_num;
-
- if (!vc_cons_allocated(console)) { /* impossible? */
- ret = -ENOIOCTLCMD;
- goto out;
- }
/*
* To have permissions to do most of the vt ioctls, we either have
@@ -1222,19 +1073,13 @@ long vt_compat_ioctl(struct tty_struct *tty,
/*
* these need special handlers for incompatible data structures
*/
- case PIO_FONTX:
- case GIO_FONTX:
- ret = compat_fontx_ioctl(cmd, up, perm, &op);
- break;
case KDFONTOP:
- ret = compat_kdfontop_ioctl(up, perm, &op, vc);
- break;
+ return compat_kdfontop_ioctl(up, perm, &op, vc);
case PIO_UNIMAP:
case GIO_UNIMAP:
- ret = compat_unimap_ioctl(cmd, up, perm, vc);
- break;
+ return compat_unimap_ioctl(cmd, up, perm, vc);
/*
* all these treat 'arg' as an integer
@@ -1257,23 +1102,15 @@ long vt_compat_ioctl(struct tty_struct *tty,
case VT_WAITACTIVE:
case VT_RELDISP:
case VT_DISALLOCATE:
- case VT_RESIZE:
- case VT_RESIZEX:
- goto fallback;
+ return vt_ioctl(tty, cmd, arg);
/*
* the rest has a compatible data structure behind arg,
* but we have to convert it to a proper 64 bit pointer.
*/
default:
- arg = (unsigned long)compat_ptr(arg);
- goto fallback;
+ return vt_ioctl(tty, cmd, (unsigned long)up);
}
-out:
- return ret;
-
-fallback:
- return vt_ioctl(tty, cmd, arg);
}
@@ -1433,31 +1270,29 @@ int vt_move_to_console(unsigned int vt, int alloc)
{
int prev;
- console_lock();
- /* Graphics mode - up to X */
- if (disable_vt_switch) {
- console_unlock();
- return 0;
- }
- prev = fg_console;
+ scoped_guard(console_lock) {
+ /* Graphics mode - up to X */
+ if (disable_vt_switch)
+ return 0;
- if (alloc && vc_allocate(vt)) {
- /* we can't have a free VC for now. Too bad,
- * we don't want to mess the screen for now. */
- console_unlock();
- return -ENOSPC;
- }
+ prev = fg_console;
- if (set_console(vt)) {
- /*
- * We're unable to switch to the SUSPEND_CONSOLE.
- * Let the calling function know so it can decide
- * what to do.
- */
- console_unlock();
- return -EIO;
+ if (alloc && vc_allocate(vt)) {
+ /*
+ * We can't have a free VC for now. Too bad, we don't want to mess the
+ * screen for now.
+ */
+ return -ENOSPC;
+ }
+
+ if (set_console(vt)) {
+ /*
+ * We're unable to switch to the SUSPEND_CONSOLE. Let the calling function
+ * know so it can decide what to do.
+ */
+ return -EIO;
+ }
}
- console_unlock();
if (vt_waitactive(vt + 1)) {
pr_debug("Suspend: Can't switch VCs.");
return -EINTR;
@@ -1474,8 +1309,7 @@ int vt_move_to_console(unsigned int vt, int alloc)
*/
void pm_set_vt_switch(int do_switch)
{
- console_lock();
+ guard(console_lock)();
disable_vt_switch = !do_switch;
- console_unlock();
}
EXPORT_SYMBOL(pm_set_vt_switch);