2018-08-22 22:02:55

by Remi Pommarel

[permalink] [raw]
Subject: [RFC PATCH 0/4] Add specific vt input's key map

This patchset adds a way to have a specific keyboard config (i.e.
keycode to keysym map) for a vt attached input.

Because one can have different keyboards with different layouts on the
same VT, it can be useful to be able to associate a different keymap
with a different input. In order to do so this patchset introduces
three new ioctls:

1) KDGKBIENT
Get an input key map's entry. If the input does not have a
specific key map the requested entry is fetched from global
key map.

2) KDSKBIENT
Set an input key map's entry. If the input does not yet have a
specific key map, a new one is created with the current global
key map content copied in.

3) KDSKBIRST
Reset an input key map. The input does not use a specific key
map anymore and keycode translations are done with global key
map.

In order to keep old behavior compatibility and not waste memory, an
input uses the global key_maps[] array by default and KDGKBENT/KDSKBENT
ioctls still get/set entries in this global key map. The specific key
map is only allocated on the first call to KDSKBIENT.

A patch for loadkeys is ready to be sent to the ML if this patchset
seems sane to you.

Here are some questions I had while doing this patchset:

- Maybe there is a way, I am not aware of, that can allow one to use
different keyboard layout on same VT ?

- Is it ok to add new ioctl to old legacy code ? I added those ioctl
the old way in order to match the header style. Maybe it is better to
use the __IO* macros ?

- Should key_down bitmap be really input specific (i.e. PATCH 3/4) ?

- I am not quite sure about the meaning of keymap_count. IIUC, it is a
counter of used keymap function arrays statically or dynamically
allocated. If it is not the case, the modificiation in kc_setent() that
decrements keymap_count even if a statically keymap function is removed
with "K_NOSUCHMAP" (in PATCH 2/4) may be wrong ?

- kbd_detach_conf is a bit clumsy because it tries to copy a shared sparse
pointer array without using GFP_ATOMIC.

- Is this patchset even a sensible idea ?

Remi Pommarel (4):
drivers/tty/vt/keyboard.c: refactor getting/setting a keymap entry
drivers/tty/vt/keyboard.c: add keyboard config for each vt's input
drivers/tty/vt/keyboard.c: Make key_down[] bitmap input dependent
drivers/tty/vt: add ioctl to manage input specific keyboard configs

drivers/tty/vt/keyboard.c | 531 ++++++++++++++++++++++++++++++--------
drivers/tty/vt/vt_ioctl.c | 9 +
fs/compat_ioctl.c | 3 +
include/linux/vt_kern.h | 4 +
include/uapi/linux/kd.h | 9 +
5 files changed, 452 insertions(+), 104 deletions(-)

--
2.18.0



2018-08-22 21:45:40

by Remi Pommarel

[permalink] [raw]
Subject: [RFC PATCH 3/4] drivers/tty/vt/keyboard.c: Make key_down[] bitmap input dependent

A kbd_handle input can have a specific keyboard config that makes
keycode to keysym mapping array different from another one. Because
key_down[] bitmap stores currently pressed key as keycode, it should be
associated with an input keymap in order to retrieve the associated
keysym.

Allocating this array in each input allows to retrieve its associated
keymap.

Signed-off-by: Remi Pommarel <[email protected]>
Tested-by: Elie Roudninski <[email protected]>
---
drivers/tty/vt/keyboard.c | 46 +++++++++++++++++++++++++++------------
1 file changed, 32 insertions(+), 14 deletions(-)

diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
index 4f09331ad5c3..7272e1828838 100644
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -122,6 +122,7 @@ struct kbd_handle {
struct kref ref;
struct kbd_conf *conf;
struct input_handle handle;
+ unsigned long key_down[BITS_TO_LONGS(KEY_CNT)];/* keyboard key bitmap */
};
#define hdl_to_kbd_handle(h) (container_of(h, struct kbd_handle, handle))

@@ -140,7 +141,6 @@ static const int NR_TYPES = ARRAY_SIZE(max_vals);
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 unsigned char shift_down[NR_SHIFT]; /* shift state counters.. */
static bool dead_key_next;
static int npadch = -1; /* -1 or number assembled on pad */
@@ -378,20 +378,18 @@ static void to_utf8(struct vc_data *vc, uint c)

/*
* Called after returning from RAW mode or when changing consoles - recompute
- * shift_down[] and shift_state from key_down[] maybe called when keymap is
- * undefined, so that shiftkey release is seen. The caller must hold the
- * kbd_event_lock.
+ * shift_down[] and shift_state from each input's key_down[] maybe called when
+ * keymap is undefined, so that shiftkey release is seen. The caller must hold
+ * the kbd_event_lock.
*/

-static void do_compute_shiftstate(void)
+static int do_compute_input_shiftstate(struct input_handle *handle, void *data)
{
+ struct kbd_handle *kh = hdl_to_kbd_handle(handle);
unsigned int k, sym, val;

- shift_state = 0;
- memset(shift_down, 0, sizeof(shift_down));
-
- for_each_set_bit(k, key_down, min(NR_KEYS, KEY_CNT)) {
- sym = U(key_maps[0][k]);
+ for_each_set_bit(k, kh->key_down, min(NR_KEYS, KEY_CNT)) {
+ sym = U(kh->conf->maps[0][k]);
if (KTYP(sym) != KT_SHIFT && KTYP(sym) != KT_SLOCK)
continue;

@@ -402,6 +400,15 @@ static void do_compute_shiftstate(void)
shift_down[val]++;
shift_state |= BIT(val);
}
+ return 0;
+}
+
+static void do_compute_shiftstate(void)
+{
+ shift_state = 0;
+ memset(shift_down, 0, sizeof(shift_down));
+ input_handler_for_each_handle(&kbd_handler, NULL,
+ do_compute_input_shiftstate);
}

/* We still have to export this method to vt.c */
@@ -1278,6 +1285,17 @@ static int sparc_l1_a_state;
extern void sun_do_break(void);
#endif

+static int is_alt_down(struct input_handle *handle, void *data)
+{
+ struct kbd_handle *kh = hdl_to_kbd_handle(handle);
+
+ if (test_bit(KEY_LEFTALT, kh->key_down) ||
+ test_bit(KEY_RIGHTALT, kh->key_down))
+ return 1;
+
+ return 0;
+}
+
static int emulate_raw(struct vc_data *vc, unsigned int keycode,
unsigned char up_flag)
{
@@ -1308,8 +1326,8 @@ static int emulate_raw(struct vc_data *vc, unsigned int keycode,
* pressing PrtSc/SysRq alone, but simply 0x54
* when pressing Alt+PrtSc/SysRq.
*/
- if (test_bit(KEY_LEFTALT, key_down) ||
- test_bit(KEY_RIGHTALT, key_down)) {
+ if (input_handler_for_each_handle(&kbd_handler, NULL,
+ is_alt_down)) {
put_queue(vc, 0x54 | up_flag);
} else {
put_queue(vc, 0xe0);
@@ -1424,9 +1442,9 @@ static void kbd_keycode(struct kbd_handle *kh, unsigned int keycode, int down,
}

if (down)
- set_bit(keycode, key_down);
+ set_bit(keycode, kh->key_down);
else
- clear_bit(keycode, key_down);
+ clear_bit(keycode, kh->key_down);

if (rep &&
(!vc_kbd_mode(kbd, VC_REPEAT) ||
--
2.18.0


2018-08-22 22:01:35

by Remi Pommarel

[permalink] [raw]
Subject: [RFC PATCH 2/4] drivers/tty/vt/keyboard.c: add keyboard config for each vt's input

Each connected kbd_handle holds a keyboard config that contains the
keymap used to translate keycode into keysym. At init and until the
kbd_handle gets detached it uses the global keyboard config
(aka key_maps[]) array to translate keycode into keysym.

For now, it is not possible for the user to detach a kbd_handle from
global config. Some new ioctl will be introduced to do so.

Signed-off-by: Remi Pommarel <[email protected]>
Tested-by: Elie Roudninski <[email protected]>
---
drivers/tty/vt/keyboard.c | 198 ++++++++++++++++++++++++++++++++------
1 file changed, 171 insertions(+), 27 deletions(-)

diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
index 89fabb8ae04e..4f09331ad5c3 100644
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -108,6 +108,23 @@ struct vt_spawn_console vt_spawn_con = {
* Internal Data.
*/

+/* Input handle's specific key configuration */
+struct kbd_conf {
+ ushort **maps;
+ unsigned int keymap_count;
+};
+
+static struct kbd_conf _gkeyconf;
+#define KC_IS_GLOBAL(kc) ((kc) == &_gkeyconf)
+
+/* Input handle */
+struct kbd_handle {
+ struct kref ref;
+ struct kbd_conf *conf;
+ struct input_handle handle;
+};
+#define hdl_to_kbd_handle(h) (container_of(h, struct kbd_handle, handle))
+
static struct kbd_struct kbd_table[MAX_NR_CONSOLES];
static struct kbd_struct *kbd = kbd_table;

@@ -1343,9 +1360,11 @@ static void kbd_rawcode(unsigned char data)
put_queue(vc, data);
}

-static void kbd_keycode(unsigned int keycode, int down, int hw_raw)
+static void kbd_keycode(struct kbd_handle *kh, unsigned int keycode, int down,
+ int hw_raw)
{
struct vc_data *vc = vc_cons[fg_console].d;
+ struct kbd_conf *kconf = kh->conf;
unsigned short keysym, *key_map;
unsigned char type;
bool raw_mode;
@@ -1422,7 +1441,7 @@ static void kbd_keycode(unsigned int keycode, int down, int hw_raw)

param.shift = shift_final = (shift_state | kbd->slockstate) ^ kbd->lockstate;
param.ledstate = kbd->ledflagstate;
- key_map = key_maps[shift_final];
+ key_map = kconf->maps[shift_final];

rc = atomic_notifier_call_chain(&keyboard_notifier_list,
KBD_KEYCODE, &param);
@@ -1458,7 +1477,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 = kconf->maps[shift_final ^ (1 << KG_SHIFT)];
if (key_map)
keysym = key_map[keycode];
}
@@ -1485,13 +1504,15 @@ static void kbd_keycode(unsigned int keycode, int down, int hw_raw)
static void kbd_event(struct input_handle *handle, unsigned int event_type,
unsigned int event_code, int value)
{
+ struct kbd_handle *kh = hdl_to_kbd_handle(handle);
+
/* 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));
+ kbd_keycode(kh, event_code, value, HW_RAW(handle->dev));

spin_unlock(&kbd_event_lock);

@@ -1519,6 +1540,114 @@ static bool kbd_match(struct input_handler *handler, struct input_dev *dev)
return false;
}

+/*
+ * Keyconf functions
+ */
+
+static void kbd_init_conf(struct kbd_handle *kh)
+{
+ kh->conf = &_gkeyconf;
+}
+
+/*
+ * Detach an input from global keyboard config. If keyboard config is already
+ * detached, nothing is modified.
+ */
+static int kbd_detach_conf(struct kbd_handle *kh)
+{
+ struct kbd_conf *kconf;
+ ushort **tmp, **maps = NULL;
+ size_t i, tmpcnt = 0;
+ unsigned long flags;
+ unsigned int count = 0;
+ int err = -ENOMEM;
+
+ kconf = kmalloc(sizeof(*kconf), GFP_KERNEL);
+ if (kconf == NULL)
+ goto reterr;
+
+ maps = kcalloc(MAX_NR_KEYMAPS, sizeof(*maps), GFP_KERNEL);
+ if (maps == NULL)
+ goto reterr;
+
+ tmp = kmalloc_array(MAX_NR_KEYMAPS, sizeof(*tmp), GFP_KERNEL);
+ if (tmp == NULL)
+ goto reterr;
+
+ spin_lock_irqsave(&kbd_event_lock, flags);
+
+ if (!KC_IS_GLOBAL(kh->conf))
+ goto out; /* We have been raced at keymap creation */
+ count = kh->conf->keymap_count;
+
+ /* Pre-alloc enough tmp buffer to avoid allocating with lock held */
+ while (tmpcnt < count) {
+ spin_unlock_irqrestore(&kbd_event_lock, flags);
+ for (; tmpcnt < count; ++tmpcnt) {
+ tmp[tmpcnt] = kmalloc(sizeof(plain_map), GFP_KERNEL);
+ if (tmp[tmpcnt] == NULL)
+ goto freetmp;
+ }
+ spin_lock_irqsave(&kbd_event_lock, flags);
+
+ if (!KC_IS_GLOBAL(kh->conf))
+ goto out; /* We have been raced at keymap creation */
+ count = kh->conf->keymap_count;
+ }
+
+ /*
+ * Here at least enough tmp pointers have been allocated and lock is
+ * held.
+ *
+ * If more than enough pointers have been allocated, the extra ones will
+ * be freed after (see freetmp).
+ */
+ if (count >= MAX_NR_OF_USER_KEYMAPS &&
+ !capable(CAP_SYS_RESOURCE)) {
+ err = -EPERM;
+ goto unlock;
+ }
+ for (i = 0; i < MAX_NR_KEYMAPS; ++i) {
+ if (kh->conf->maps[i]) {
+ --tmpcnt;
+ memcpy(tmp[tmpcnt], kh->conf->maps[i],
+ sizeof(plain_map));
+ tmp[tmpcnt][0] = U(K_ALLOCATED);
+ maps[i] = tmp[tmpcnt];
+ }
+ }
+ kconf->maps = maps;
+ kconf->keymap_count = count;
+ kh->conf = kconf;
+ kconf = NULL;
+ maps = NULL;
+out:
+ err = 0;
+unlock:
+ spin_unlock_irqrestore(&kbd_event_lock, flags);
+freetmp:
+ for (; tmpcnt > 0; --tmpcnt) /* Free extra pointers */
+ kfree(tmp[tmpcnt - 1]);
+ kfree(tmp);
+reterr:
+ kfree(maps);
+ kfree(kconf);
+ return err;
+}
+
+static void kbd_destroy_conf(struct kbd_handle *kh)
+{
+ size_t i;
+
+ if (KC_IS_GLOBAL(kh->conf))
+ return;
+
+ for (i = 0; i < MAX_NR_KEYMAPS; ++i)
+ kfree(kh->conf->maps[i]);
+ kfree(kh->conf->maps);
+ kfree(kh->conf);
+}
+
/*
* When a keyboard (or other input device) is found, the kbd_connect
* function is called. The function then looks at the device, and if it
@@ -1528,13 +1657,17 @@ 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 kbd_handle *kh;
struct input_handle *handle;
int error;

- handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
- if (!handle)
+ kh = kzalloc(sizeof(struct kbd_handle), GFP_KERNEL);
+ if (!kh)
return -ENOMEM;

+ kref_init(&kh->ref);
+ kbd_init_conf(kh);
+ handle = &kh->handle;
handle->dev = dev;
handle->handler = handler;
handle->name = "kbd";
@@ -1552,15 +1685,25 @@ static int kbd_connect(struct input_handler *handler, struct input_dev *dev,
err_unregister_handle:
input_unregister_handle(handle);
err_free_handle:
- kfree(handle);
+ kfree(kh);
return error;
}

+static void kbd_destroy(struct kref *ref)
+{
+ struct kbd_handle *kh = container_of(ref, struct kbd_handle, ref);
+
+ kbd_destroy_conf(kh);
+ kfree(kh);
+}
+
static void kbd_disconnect(struct input_handle *handle)
{
+ struct kbd_handle *kh = hdl_to_kbd_handle(handle);
+
input_close_device(handle);
input_unregister_handle(handle);
- kfree(handle);
+ kref_put(&kh->ref, kbd_destroy);
}

/*
@@ -1619,6 +1762,8 @@ int __init kbd_init(void)
}

kbd_init_leds();
+ _gkeyconf.maps = key_maps;
+ _gkeyconf.keymap_count = keymap_count;

error = input_register_handler(&kbd_handler);
if (error)
@@ -1879,21 +2024,21 @@ int vt_do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc,
#define v (kbe->kb_value)

/*
- * Get a keysym value from a key map
+ * Get a keysym value from a keyconf's keymap
*
* @kb: Virtual console structure
- * @kmaps: Key map array
+ * @kconf: Keyboard configuration
* @kbe : Entry to find in key map
*/
-static ushort __kdsk_getent(struct kbd_struct const *kb, ushort **kmaps,
- struct kbentry const *kbe)
+static ushort kc_getent(struct kbd_struct const *kb,
+ struct kbd_conf const *kconf, struct kbentry const *kbe)
{
ushort *key_map, val;
unsigned long flags;

/* Ensure another thread doesn't free it under us */
spin_lock_irqsave(&kbd_event_lock, flags);
- key_map = kmaps[s];
+ key_map = kconf->maps[s];
if (key_map) {
val = U(key_map[i]);
if (kb->kbdmode != VC_UNICODE && KTYP(val) >= NR_TYPES)
@@ -1906,13 +2051,13 @@ static ushort __kdsk_getent(struct kbd_struct const *kb, ushort **kmaps,
}

/*
- * Set a keysym value in a key map
+ * Set a keysym value in a keyboard configuration's keymap
*
* @kb: Virtual console structure
- * @kmaps: Key map array
+ * @kconf: Keyboard configuration
* @kbe : Entry to set in key map
*/
-static int __kdsk_setent(struct kbd_struct const *kb, ushort **kmaps,
+static int kc_setent(struct kbd_struct const *kb, struct kbd_conf *kconf,
struct kbentry const *kbe)
{
ushort *key_map, *new_map, ov;
@@ -1921,13 +2066,12 @@ static int __kdsk_setent(struct kbd_struct const *kb, ushort **kmaps,
if (!i && v == K_NOSUCHMAP) {
spin_lock_irqsave(&kbd_event_lock, flags);
/* deallocate map */
- key_map = kmaps[s];
+ key_map = kconf->maps[s];
if (s && key_map) {
- kmaps[s] = NULL;
- if (key_map[0] == U(K_ALLOCATED)) {
+ kconf->maps[s] = NULL;
+ if (key_map[0] == U(K_ALLOCATED))
kfree(key_map);
- keymap_count--;
- }
+ kconf->keymap_count--;
}
spin_unlock_irqrestore(&kbd_event_lock, flags);
return 0;
@@ -1950,22 +2094,22 @@ static int __kdsk_setent(struct kbd_struct const *kb, ushort **kmaps,
if (!new_map)
return -ENOMEM;
spin_lock_irqsave(&kbd_event_lock, flags);
- key_map = kmaps[s];
+ key_map = kconf->maps[s];
if (key_map == NULL) {
int j;

- if (keymap_count >= MAX_NR_OF_USER_KEYMAPS &&
+ if (kconf->keymap_count >= MAX_NR_OF_USER_KEYMAPS &&
!capable(CAP_SYS_RESOURCE)) {
spin_unlock_irqrestore(&kbd_event_lock, flags);
kfree(new_map);
return -EPERM;
}
- kmaps[s] = new_map;
+ kconf->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++;
+ kconf->keymap_count++;
} else
kfree(new_map);

@@ -2006,13 +2150,13 @@ int vt_do_kdsk_ioctl(int cmd, struct kbentry __user *user_kbe, int perm,

switch (cmd) {
case KDGKBENT:
- val = __kdsk_getent(kb, key_maps, &tmp);
+ val = kc_getent(kb, &_gkeyconf, &tmp);
ret = put_user(val, &user_kbe->kb_value);
break;
case KDSKBENT:
if (!perm)
return -EPERM;
- ret = __kdsk_setent(kb, key_maps, &tmp);
+ ret = kc_setent(kb, &_gkeyconf, &tmp);
break;
}
return ret;
--
2.18.0


2018-08-22 22:01:41

by Remi Pommarel

[permalink] [raw]
Subject: [RFC PATCH 1/4] drivers/tty/vt/keyboard.c: refactor getting/setting a keymap entry

In order to ease the addition of the ability of an input to use a
different key configuration (keycode to keysym map), the operations of
getting and setting an entry in a key map is moved in respective
functions.

Signed-off-by: Remi Pommarel <[email protected]>
Tested-by: Elie Roudninski <[email protected]>
---
drivers/tty/vt/keyboard.c | 201 ++++++++++++++++++++++----------------
1 file changed, 118 insertions(+), 83 deletions(-)

diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
index 88312c6c92cc..89fabb8ae04e 100644
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -1874,115 +1874,150 @@ int vt_do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc,
return kc;
}

-#define i (tmp.kb_index)
-#define s (tmp.kb_table)
-#define v (tmp.kb_value)
+#define i (kbe->kb_index)
+#define s (kbe->kb_table)
+#define v (kbe->kb_value)

-int vt_do_kdsk_ioctl(int cmd, struct kbentry __user *user_kbe, int perm,
- int console)
+/*
+ * Get a keysym value from a key map
+ *
+ * @kb: Virtual console structure
+ * @kmaps: Key map array
+ * @kbe : Entry to find in key map
+ */
+static ushort __kdsk_getent(struct kbd_struct const *kb, ushort **kmaps,
+ struct kbentry const *kbe)
{
- struct kbd_struct *kb = kbd_table + console;
- struct kbentry tmp;
- ushort *key_map, *new_map, val, ov;
+ ushort *key_map, val;
unsigned long flags;

- if (copy_from_user(&tmp, user_kbe, sizeof(struct kbentry)))
- return -EFAULT;
+ /* Ensure another thread doesn't free it under us */
+ spin_lock_irqsave(&kbd_event_lock, flags);
+ key_map = kmaps[s];
+ if (key_map) {
+ val = U(key_map[i]);
+ if (kb->kbdmode != VC_UNICODE && KTYP(val) >= NR_TYPES)
+ val = K_HOLE;
+ } else
+ val = (i ? K_HOLE : K_NOSUCHMAP);
+ spin_unlock_irqrestore(&kbd_event_lock, flags);

- if (!capable(CAP_SYS_TTY_CONFIG))
- perm = 0;
+ return val;
+}

- switch (cmd) {
- case KDGKBENT:
- /* Ensure another thread doesn't free it under us */
+/*
+ * Set a keysym value in a key map
+ *
+ * @kb: Virtual console structure
+ * @kmaps: Key map array
+ * @kbe : Entry to set in key map
+ */
+static int __kdsk_setent(struct kbd_struct const *kb, ushort **kmaps,
+ struct kbentry const *kbe)
+{
+ ushort *key_map, *new_map, ov;
+ unsigned long flags;
+
+ if (!i && v == K_NOSUCHMAP) {
spin_lock_irqsave(&kbd_event_lock, flags);
- key_map = key_maps[s];
- if (key_map) {
- val = U(key_map[i]);
- if (kb->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--;
- }
+ /* deallocate map */
+ key_map = kmaps[s];
+ if (s && key_map) {
+ kmaps[s] = NULL;
+ if (key_map[0] == U(K_ALLOCATED)) {
+ kfree(key_map);
+ keymap_count--;
}
- spin_unlock_irqrestore(&kbd_event_lock, flags);
- break;
}
+ spin_unlock_irqrestore(&kbd_event_lock, flags);
+ return 0;
+ }

- if (KTYP(v) < NR_TYPES) {
- if (KVAL(v) > max_vals[KTYP(v)])
- return -EINVAL;
- } else
- if (kb->kbdmode != VC_UNICODE)
- return -EINVAL;
+ if (KTYP(v) < NR_TYPES) {
+ if (KVAL(v) > max_vals[KTYP(v)])
+ return -EINVAL;
+ } else if (kb->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 (!i)
+ 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);
+ new_map = kmalloc(sizeof(plain_map), GFP_KERNEL);
+ if (!new_map)
+ return -ENOMEM;
+ spin_lock_irqsave(&kbd_event_lock, flags);
+ key_map = kmaps[s];
+ if (key_map == NULL) {
+ int j;

- ov = U(key_map[i]);
- if (v == ov)
- goto out;
- /*
- * Attention Key.
- */
- if (((ov == K_SAK) || (v == K_SAK)) && !capable(CAP_SYS_ADMIN)) {
+ 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_map[i] = U(v);
- if (!s && (KTYP(ov) == KT_SHIFT || KTYP(v) == KT_SHIFT))
- do_compute_shiftstate();
-out:
+ kmaps[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);
- break;
+ 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);
return 0;
}
#undef i
#undef s
#undef v

+int vt_do_kdsk_ioctl(int cmd, struct kbentry __user *user_kbe, int perm,
+ int console)
+{
+ struct kbd_struct *kb = kbd_table + console;
+ struct kbentry tmp;
+ ushort val;
+ int ret = 0;
+
+ if (copy_from_user(&tmp, user_kbe, sizeof(struct kbentry)))
+ return -EFAULT;
+
+ if (!capable(CAP_SYS_TTY_CONFIG))
+ perm = 0;
+
+ switch (cmd) {
+ case KDGKBENT:
+ val = __kdsk_getent(kb, key_maps, &tmp);
+ ret = put_user(val, &user_kbe->kb_value);
+ break;
+ case KDSKBENT:
+ if (!perm)
+ return -EPERM;
+ ret = __kdsk_setent(kb, key_maps, &tmp);
+ break;
+ }
+ return ret;
+}
+
/* FIXME: This one needs untangling and locking */
int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm)
{
--
2.18.0


2018-08-22 22:02:34

by Remi Pommarel

[permalink] [raw]
Subject: [RFC PATCH 4/4] drivers/tty/vt: add ioctl to manage input specific keyboard configs

Because user can use different keyboards with different layouts on the
same tty, an input could have a different key map from another one. In
order to use and modify this specific key map the user can call three
new ioctls:

1) KDGKBIENT
Get an input key map's entry. If the input does not have a
specific keyboard config the requested entry is fetched from
global key map.

2) KDSKBIENT
Set an input key map's entry. If the input does not yet have a
specific keyboard config, a new one is created with the current
global config (the one modified with KDSKBENT) content copied
in.

3) KDSKBIRST
Reset an input key map. The input does not use a specific
keyboard config anymore and keycode translations are done with
global key map.

In order to remain compatible with old behavior, an input uses global
key map by default and KDGKENT/KDSKENT still get/set entries in the
global key map.

Below is an example of this ioctl usage:
------------------------- 8< -----------------------------
struct kbientry kbi = {
.id = { /* Input is found by its input_id */
.bustype = 0x0003,
.product = 0x00B2,
.vendor = 0x2319,
.version = 0x0200,
},
.entry = { /* Entry to set or get */
.kb_table = 0,
.kb_index = 0x10,
.kb_value = 0x0B61,
},
};
int ret;

/* First set new entry for input 0003:00B2:2319:0200 */
ret = ioctl(fd, KDSKBIENT, &kbi); /* fd is a tty open file */
if(ret < 0)
/* Error handling */

/* Get specific entry, on success kbi.entry.kb_value will be set */
ret = ioctl(fd, KDSKBIENT, &kbi);
if(ret < 0)
/* Error handling */

/* Reset input key map so that global key_maps is used */
ret = ioctl(fd, KDSKBIRST, &kbi.id);
if(ret < 0)
/* Error handling */
------------------------- 8< -----------------------------

Signed-off-by: Remi Pommarel <[email protected]>
Tested-by: Elie Roudninski <[email protected]>
---
drivers/tty/vt/keyboard.c | 126 ++++++++++++++++++++++++++++++++++++++
drivers/tty/vt/vt_ioctl.c | 9 +++
fs/compat_ioctl.c | 3 +
include/linux/vt_kern.h | 4 ++
include/uapi/linux/kd.h | 9 +++
5 files changed, 151 insertions(+)

diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
index 7272e1828838..9707d1ebeab4 100644
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -2180,6 +2180,132 @@ int vt_do_kdsk_ioctl(int cmd, struct kbentry __user *user_kbe, int perm,
return ret;
}

+/*
+ * Input keymap helper functions
+ */
+struct kbd_lookup_data {
+ struct input_id *id;
+ struct kbd_handle *kh;
+};
+
+static int _kbd_get_helper(struct input_handle *handle, void *data)
+{
+ struct kbd_handle *kh = hdl_to_kbd_handle(handle);
+ struct kbd_lookup_data *d = data;
+
+ if (memcmp(d->id, &handle->dev->id, sizeof(*d->id)) != 0)
+ return 0;
+
+ d->kh = kh;
+ kref_get(&kh->ref);
+ return 1;
+}
+
+/*
+ * Get an input specific handle.
+ * The caller will hold a reference on the found kbd_handle, if it exists, and
+ * should deref it after usage (with kbd_put).
+ */
+static int kbd_get(struct kbd_handle **kh, struct input_id *id)
+{
+ struct kbd_lookup_data d = {
+ .id = id,
+ };
+ int ret;
+
+ ret = input_handler_for_each_handle(&kbd_handler, &d, _kbd_get_helper);
+ *kh = d.kh;
+
+ return (ret == 0);
+}
+
+static void kbd_put(struct kbd_handle *kh)
+{
+ kref_put(&kh->ref, kbd_destroy);
+}
+
+int vt_do_kdski_ioctl(int cmd, struct kbientry __user *user_kbie, int perm,
+ int console)
+{
+ struct kbd_struct *kb = kbd_table + console;
+ struct kbd_handle *kh;
+ struct kbientry tmp;
+ ushort val;
+ int ret = 0;
+
+ if (copy_from_user(&tmp, user_kbie, sizeof(struct kbientry))) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ if (!capable(CAP_SYS_TTY_CONFIG))
+ perm = 0;
+
+ ret = kbd_get(&kh, &tmp.id);
+ if (ret != 0) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ switch (cmd) {
+ case KDGKBIENT:
+ val = kc_getent(kb, kh->conf, &tmp.entry);
+ ret = put_user(val, &user_kbie->entry.kb_value);
+ break;
+ case KDSKBIENT:
+ if (!perm) {
+ ret = -EPERM;
+ break;
+ }
+ /* A keyconf's keymap is created only if global one is used */
+ ret = kbd_detach_conf(kh);
+ if (ret != 0)
+ break;
+
+ ret = kc_setent(kb, kh->conf, &tmp.entry);
+ break;
+ }
+
+ kbd_put(kh);
+out:
+ return ret;
+}
+
+int vt_do_kdskirst_ioctl(int cmd, struct input_id __user *user_iid, int perm,
+ int console)
+{
+ struct kbd_handle *kh;
+ struct input_id tmp;
+ unsigned long flags;
+ int ret;
+
+ if (copy_from_user(&tmp, user_iid, sizeof(struct input_id)))
+ return -EFAULT;
+
+ if (!capable(CAP_SYS_TTY_CONFIG))
+ perm = 0;
+
+ if (!perm)
+ return -EPERM;
+
+ ret = kbd_get(&kh, &tmp);
+ if (ret != 0) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Restore keyboard's configuration to global one */
+ spin_lock_irqsave(&kbd_event_lock, flags);
+ kbd_destroy_conf(kh);
+ kbd_init_conf(kh);
+ spin_unlock_irqrestore(&kbd_event_lock, flags);
+
+ kbd_put(kh);
+
+out:
+ return ret;
+}
+
/* FIXME: This one needs untangling and locking */
int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm)
{
diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c
index a78ad10a119b..ec688d81bab3 100644
--- a/drivers/tty/vt/vt_ioctl.c
+++ b/drivers/tty/vt/vt_ioctl.c
@@ -542,6 +542,15 @@ int vt_ioctl(struct tty_struct *tty,
ret = vt_do_kdsk_ioctl(cmd, up, perm, console);
break;

+ case KDGKBIENT:
+ case KDSKBIENT:
+ ret = vt_do_kdski_ioctl(cmd, up, perm, console);
+ break;
+
+ case KDSKBIRST:
+ ret = vt_do_kdskirst_ioctl(cmd, up, perm, console);
+ break;
+
case KDGKBSENT:
case KDSKBSENT:
ret = vt_do_kdgkb_ioctl(cmd, up, perm);
diff --git a/fs/compat_ioctl.c b/fs/compat_ioctl.c
index a9b00942e87d..7863b142234c 100644
--- a/fs/compat_ioctl.c
+++ b/fs/compat_ioctl.c
@@ -789,6 +789,9 @@ COMPATIBLE_IOCTL(KDGKBDIACR)
COMPATIBLE_IOCTL(KDSKBDIACR)
COMPATIBLE_IOCTL(KDGKBDIACRUC)
COMPATIBLE_IOCTL(KDSKBDIACRUC)
+COMPATIBLE_IOCTL(KDGKBIENT)
+COMPATIBLE_IOCTL(KDSKBIENT)
+COMPATIBLE_IOCTL(KDSKBIRST)
COMPATIBLE_IOCTL(KDKBDREP)
COMPATIBLE_IOCTL(KDGKBLED)
COMPATIBLE_IOCTL(KDGETLED)
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
index 3fd07912909c..0f17d305b840 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -175,6 +175,10 @@ extern int vt_do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc,
int perm);
extern int vt_do_kdsk_ioctl(int cmd, struct kbentry __user *user_kbe,
int perm, int console);
+extern int vt_do_kdski_ioctl(int cmd, struct kbientry __user *user_kbie,
+ int perm, int console);
+extern int vt_do_kdskirst_ioctl(int cmd, struct input_id __user *user_iid,
+ int perm, int console);
extern int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb,
int perm);
extern int vt_do_kdskled(int console, int cmd, unsigned long arg, int perm);
diff --git a/include/uapi/linux/kd.h b/include/uapi/linux/kd.h
index 4616b31f84da..be048e9e3f02 100644
--- a/include/uapi/linux/kd.h
+++ b/include/uapi/linux/kd.h
@@ -3,6 +3,7 @@
#define _UAPI_LINUX_KD_H
#include <linux/types.h>
#include <linux/compiler.h>
+#include <linux/input.h>

/* 0x4B is 'K', to avoid collision with termios and vt */

@@ -137,6 +138,14 @@ struct kbdiacrsuc {
#define KDGKBDIACRUC 0x4BFA /* read kernel accent table - UCS */
#define KDSKBDIACRUC 0x4BFB /* write kernel accent table - UCS */

+struct kbientry {
+ struct input_id id;
+ struct kbentry entry;
+};
+#define KDGKBIENT 0x4B53 /* Get one entry in input's translation table */
+#define KDSKBIENT 0x4B54 /* Set one entry in input's translation table */
+#define KDSKBIRST 0x4B55 /* Set input to use global translation table */
+
struct kbkeycode {
unsigned int scancode, keycode;
};
--
2.18.0


2018-09-05 17:43:39

by Remi Pommarel

[permalink] [raw]
Subject: Re: [RFC PATCH 0/4] Add specific vt input's key map

Hi,

Just a gentle ping.

Any comments on this patchset that adds a way to have multiple
keyboards with different keymap on a VT would be nice.

Thanks

--
Remi

2018-09-11 18:48:52

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [RFC PATCH 0/4] Add specific vt input's key map

On Wed, Sep 05, 2018 at 07:20:23PM +0200, Remi Pommarel wrote:
> Hi,
>
> Just a gentle ping.
>
> Any comments on this patchset that adds a way to have multiple
> keyboards with different keymap on a VT would be nice.

Normally I do not review "RFC" patches as it implies the submitter does
not think they are a valid solution. How about resending them as if you
think this is something ready to be merged?

thanks,

greg k-h

2018-09-12 08:17:19

by Remi Pommarel

[permalink] [raw]
Subject: Re: [RFC PATCH 0/4] Add specific vt input's key map

Hi,

On Tue, Sep 11, 2018 at 08:47:55PM +0200, Greg Kroah-Hartman wrote:
>
> Normally I do not review "RFC" patches as it implies the submitter does
> not think they are a valid solution. How about resending them as if you
> think this is something ready to be merged?

I had used "RFC" here because I had one or two questions about the
implementation choices I made on those patches and not really on the
solution itself. So, yes, I don't mind sending another round of those
patches without RFC.

Thanks,

--
Remi