summaryrefslogtreecommitdiff
path: root/drivers/s390/char/keyboard.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390/char/keyboard.c')
-rw-r--r--drivers/s390/char/keyboard.c519
1 files changed, 519 insertions, 0 deletions
diff --git a/drivers/s390/char/keyboard.c b/drivers/s390/char/keyboard.c
new file mode 100644
index 000000000000..fd43d99b45a3
--- /dev/null
+++ b/drivers/s390/char/keyboard.c
@@ -0,0 +1,519 @@
+/*
+ * drivers/s390/char/keyboard.c
+ * ebcdic keycode functions for s390 console drivers
+ *
+ * S390 version
+ * Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/sysrq.h>
+
+#include <linux/kbd_kern.h>
+#include <linux/kbd_diacr.h>
+#include <asm/uaccess.h>
+
+#include "keyboard.h"
+
+/*
+ * Handler Tables.
+ */
+#define K_HANDLERS\
+ k_self, k_fn, k_spec, k_ignore,\
+ k_dead, k_ignore, k_ignore, k_ignore,\
+ k_ignore, k_ignore, k_ignore, k_ignore,\
+ k_ignore, k_ignore, k_ignore, k_ignore
+
+typedef void (k_handler_fn)(struct kbd_data *, unsigned char);
+static k_handler_fn K_HANDLERS;
+static k_handler_fn *k_handler[16] = { K_HANDLERS };
+
+/* maximum values each key_handler can handle */
+static const int kbd_max_vals[] = {
+ 255, ARRAY_SIZE(func_table) - 1, NR_FN_HANDLER - 1, 0,
+ NR_DEAD - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+static const int KBD_NR_TYPES = ARRAY_SIZE(kbd_max_vals);
+
+static unsigned char ret_diacr[NR_DEAD] = {
+ '`', '\'', '^', '~', '"', ','
+};
+
+/*
+ * Alloc/free of kbd_data structures.
+ */
+struct kbd_data *
+kbd_alloc(void) {
+ struct kbd_data *kbd;
+ int i, len;
+
+ kbd = kmalloc(sizeof(struct kbd_data), GFP_KERNEL);
+ if (!kbd)
+ goto out;
+ memset(kbd, 0, sizeof(struct kbd_data));
+ kbd->key_maps = kmalloc(sizeof(key_maps), GFP_KERNEL);
+ if (!key_maps)
+ goto out_kbd;
+ memset(kbd->key_maps, 0, sizeof(key_maps));
+ for (i = 0; i < ARRAY_SIZE(key_maps); i++) {
+ if (key_maps[i]) {
+ kbd->key_maps[i] =
+ kmalloc(sizeof(u_short)*NR_KEYS, GFP_KERNEL);
+ if (!kbd->key_maps[i])
+ goto out_maps;
+ memcpy(kbd->key_maps[i], key_maps[i],
+ sizeof(u_short)*NR_KEYS);
+ }
+ }
+ kbd->func_table = kmalloc(sizeof(func_table), GFP_KERNEL);
+ if (!kbd->func_table)
+ goto out_maps;
+ memset(kbd->func_table, 0, sizeof(func_table));
+ for (i = 0; i < ARRAY_SIZE(func_table); i++) {
+ if (func_table[i]) {
+ len = strlen(func_table[i]) + 1;
+ kbd->func_table[i] = kmalloc(len, GFP_KERNEL);
+ if (!kbd->func_table[i])
+ goto out_func;
+ memcpy(kbd->func_table[i], func_table[i], len);
+ }
+ }
+ kbd->fn_handler =
+ kmalloc(sizeof(fn_handler_fn *) * NR_FN_HANDLER, GFP_KERNEL);
+ if (!kbd->fn_handler)
+ goto out_func;
+ memset(kbd->fn_handler, 0, sizeof(fn_handler_fn *) * NR_FN_HANDLER);
+ kbd->accent_table =
+ kmalloc(sizeof(struct kbdiacr)*MAX_DIACR, GFP_KERNEL);
+ if (!kbd->accent_table)
+ goto out_fn_handler;
+ memcpy(kbd->accent_table, accent_table,
+ sizeof(struct kbdiacr)*MAX_DIACR);
+ kbd->accent_table_size = accent_table_size;
+ return kbd;
+
+out_fn_handler:
+ kfree(kbd->fn_handler);
+out_func:
+ for (i = 0; i < ARRAY_SIZE(func_table); i++)
+ if (kbd->func_table[i])
+ kfree(kbd->func_table[i]);
+ kfree(kbd->func_table);
+out_maps:
+ for (i = 0; i < ARRAY_SIZE(key_maps); i++)
+ if (kbd->key_maps[i])
+ kfree(kbd->key_maps[i]);
+ kfree(kbd->key_maps);
+out_kbd:
+ kfree(kbd);
+out:
+ return 0;
+}
+
+void
+kbd_free(struct kbd_data *kbd)
+{
+ int i;
+
+ kfree(kbd->accent_table);
+ kfree(kbd->fn_handler);
+ for (i = 0; i < ARRAY_SIZE(func_table); i++)
+ if (kbd->func_table[i])
+ kfree(kbd->func_table[i]);
+ kfree(kbd->func_table);
+ for (i = 0; i < ARRAY_SIZE(key_maps); i++)
+ if (kbd->key_maps[i])
+ kfree(kbd->key_maps[i]);
+ kfree(kbd->key_maps);
+ kfree(kbd);
+}
+
+/*
+ * Generate ascii -> ebcdic translation table from kbd_data.
+ */
+void
+kbd_ascebc(struct kbd_data *kbd, unsigned char *ascebc)
+{
+ unsigned short *keymap, keysym;
+ int i, j, k;
+
+ memset(ascebc, 0x40, 256);
+ for (i = 0; i < ARRAY_SIZE(key_maps); i++) {
+ keymap = kbd->key_maps[i];
+ if (!keymap)
+ continue;
+ for (j = 0; j < NR_KEYS; j++) {
+ k = ((i & 1) << 7) + j;
+ keysym = keymap[j];
+ if (KTYP(keysym) == (KT_LATIN | 0xf0) ||
+ KTYP(keysym) == (KT_LETTER | 0xf0))
+ ascebc[KVAL(keysym)] = k;
+ else if (KTYP(keysym) == (KT_DEAD | 0xf0))
+ ascebc[ret_diacr[KVAL(keysym)]] = k;
+ }
+ }
+}
+
+/*
+ * Generate ebcdic -> ascii translation table from kbd_data.
+ */
+void
+kbd_ebcasc(struct kbd_data *kbd, unsigned char *ebcasc)
+{
+ unsigned short *keymap, keysym;
+ int i, j, k;
+
+ memset(ebcasc, ' ', 256);
+ for (i = 0; i < ARRAY_SIZE(key_maps); i++) {
+ keymap = kbd->key_maps[i];
+ if (!keymap)
+ continue;
+ for (j = 0; j < NR_KEYS; j++) {
+ keysym = keymap[j];
+ k = ((i & 1) << 7) + j;
+ if (KTYP(keysym) == (KT_LATIN | 0xf0) ||
+ KTYP(keysym) == (KT_LETTER | 0xf0))
+ ebcasc[k] = KVAL(keysym);
+ else if (KTYP(keysym) == (KT_DEAD | 0xf0))
+ ebcasc[k] = ret_diacr[KVAL(keysym)];
+ }
+ }
+}
+
+/*
+ * We have a combining character DIACR here, followed by the character CH.
+ * If the combination occurs in the table, return the corresponding value.
+ * Otherwise, if CH is a space or equals DIACR, return DIACR.
+ * Otherwise, conclude that DIACR was not combining after all,
+ * queue it and return CH.
+ */
+static unsigned char
+handle_diacr(struct kbd_data *kbd, unsigned char ch)
+{
+ int i, d;
+
+ d = kbd->diacr;
+ kbd->diacr = 0;
+
+ for (i = 0; i < kbd->accent_table_size; i++) {
+ if (kbd->accent_table[i].diacr == d &&
+ kbd->accent_table[i].base == ch)
+ return kbd->accent_table[i].result;
+ }
+
+ if (ch == ' ' || ch == d)
+ return d;
+
+ kbd_put_queue(kbd->tty, d);
+ return ch;
+}
+
+/*
+ * Handle dead key.
+ */
+static void
+k_dead(struct kbd_data *kbd, unsigned char value)
+{
+ value = ret_diacr[value];
+ kbd->diacr = (kbd->diacr ? handle_diacr(kbd, value) : value);
+}
+
+/*
+ * Normal character handler.
+ */
+static void
+k_self(struct kbd_data *kbd, unsigned char value)
+{
+ if (kbd->diacr)
+ value = handle_diacr(kbd, value);
+ kbd_put_queue(kbd->tty, value);
+}
+
+/*
+ * Special key handlers
+ */
+static void
+k_ignore(struct kbd_data *kbd, unsigned char value)
+{
+}
+
+/*
+ * Function key handler.
+ */
+static void
+k_fn(struct kbd_data *kbd, unsigned char value)
+{
+ if (kbd->func_table[value])
+ kbd_puts_queue(kbd->tty, kbd->func_table[value]);
+}
+
+static void
+k_spec(struct kbd_data *kbd, unsigned char value)
+{
+ if (value >= NR_FN_HANDLER)
+ return;
+ if (kbd->fn_handler[value])
+ kbd->fn_handler[value](kbd);
+}
+
+/*
+ * Put utf8 character to tty flip buffer.
+ * UTF-8 is defined for words of up to 31 bits,
+ * but we need only 16 bits here
+ */
+static void
+to_utf8(struct tty_struct *tty, ushort c)
+{
+ if (c < 0x80)
+ /* 0******* */
+ kbd_put_queue(tty, c);
+ else if (c < 0x800) {
+ /* 110***** 10****** */
+ kbd_put_queue(tty, 0xc0 | (c >> 6));
+ kbd_put_queue(tty, 0x80 | (c & 0x3f));
+ } else {
+ /* 1110**** 10****** 10****** */
+ kbd_put_queue(tty, 0xe0 | (c >> 12));
+ kbd_put_queue(tty, 0x80 | ((c >> 6) & 0x3f));
+ kbd_put_queue(tty, 0x80 | (c & 0x3f));
+ }
+}
+
+/*
+ * Process keycode.
+ */
+void
+kbd_keycode(struct kbd_data *kbd, unsigned int keycode)
+{
+ unsigned short keysym;
+ unsigned char type, value;
+
+ if (!kbd || !kbd->tty)
+ return;
+
+ if (keycode >= 384)
+ keysym = kbd->key_maps[5][keycode - 384];
+ else if (keycode >= 256)
+ keysym = kbd->key_maps[4][keycode - 256];
+ else if (keycode >= 128)
+ keysym = kbd->key_maps[1][keycode - 128];
+ else
+ keysym = kbd->key_maps[0][keycode];
+
+ type = KTYP(keysym);
+ if (type >= 0xf0) {
+ type -= 0xf0;
+ if (type == KT_LETTER)
+ type = KT_LATIN;
+ value = KVAL(keysym);
+#ifdef CONFIG_MAGIC_SYSRQ /* Handle the SysRq Hack */
+ if (kbd->sysrq) {
+ if (kbd->sysrq == K(KT_LATIN, '-')) {
+ kbd->sysrq = 0;
+ handle_sysrq(value, 0, kbd->tty);
+ return;
+ }
+ if (value == '-') {
+ kbd->sysrq = K(KT_LATIN, '-');
+ return;
+ }
+ /* Incomplete sysrq sequence. */
+ (*k_handler[KTYP(kbd->sysrq)])(kbd, KVAL(kbd->sysrq));
+ kbd->sysrq = 0;
+ } else if ((type == KT_LATIN && value == '^') ||
+ (type == KT_DEAD && ret_diacr[value] == '^')) {
+ kbd->sysrq = K(type, value);
+ return;
+ }
+#endif
+ (*k_handler[type])(kbd, value);
+ } else
+ to_utf8(kbd->tty, keysym);
+}
+
+/*
+ * Ioctl stuff.
+ */
+static int
+do_kdsk_ioctl(struct kbd_data *kbd, struct kbentry __user *user_kbe,
+ int cmd, int perm)
+{
+ struct kbentry tmp;
+ ushort *key_map, val, ov;
+
+ if (copy_from_user(&tmp, user_kbe, sizeof(struct kbentry)))
+ return -EFAULT;
+#if NR_KEYS < 256
+ if (tmp.kb_index >= NR_KEYS)
+ return -EINVAL;
+#endif
+#if MAX_NR_KEYMAPS < 256
+ if (tmp.kb_table >= MAX_NR_KEYMAPS)
+ return -EINVAL;
+#endif
+
+ switch (cmd) {
+ case KDGKBENT:
+ key_map = kbd->key_maps[tmp.kb_table];
+ if (key_map) {
+ val = U(key_map[tmp.kb_index]);
+ if (KTYP(val) >= KBD_NR_TYPES)
+ val = K_HOLE;
+ } else
+ val = (tmp.kb_index ? K_HOLE : K_NOSUCHMAP);
+ return put_user(val, &user_kbe->kb_value);
+ case KDSKBENT:
+ if (!perm)
+ return -EPERM;
+ if (!tmp.kb_index && tmp.kb_value == K_NOSUCHMAP) {
+ /* disallocate map */
+ key_map = kbd->key_maps[tmp.kb_table];
+ if (key_map) {
+ kbd->key_maps[tmp.kb_table] = 0;
+ kfree(key_map);
+ }
+ break;
+ }
+
+ if (KTYP(tmp.kb_value) >= KBD_NR_TYPES)
+ return -EINVAL;
+ if (KVAL(tmp.kb_value) > kbd_max_vals[KTYP(tmp.kb_value)])
+ return -EINVAL;
+
+ if (!(key_map = kbd->key_maps[tmp.kb_table])) {
+ int j;
+
+ key_map = (ushort *) kmalloc(sizeof(plain_map),
+ GFP_KERNEL);
+ if (!key_map)
+ return -ENOMEM;
+ kbd->key_maps[tmp.kb_table] = key_map;
+ for (j = 0; j < NR_KEYS; j++)
+ key_map[j] = U(K_HOLE);
+ }
+ ov = U(key_map[tmp.kb_index]);
+ if (tmp.kb_value == ov)
+ break; /* nothing to do */
+ /*
+ * Attention Key.
+ */
+ if (((ov == K_SAK) || (tmp.kb_value == K_SAK)) &&
+ !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ key_map[tmp.kb_index] = U(tmp.kb_value);
+ break;
+ }
+ return 0;
+}
+
+static int
+do_kdgkb_ioctl(struct kbd_data *kbd, struct kbsentry __user *u_kbs,
+ int cmd, int perm)
+{
+ unsigned char kb_func;
+ char *p;
+ int len;
+
+ /* Get u_kbs->kb_func. */
+ if (get_user(kb_func, &u_kbs->kb_func))
+ return -EFAULT;
+#if MAX_NR_FUNC < 256
+ if (kb_func >= MAX_NR_FUNC)
+ return -EINVAL;
+#endif
+
+ switch (cmd) {
+ case KDGKBSENT:
+ p = kbd->func_table[kb_func];
+ if (p) {
+ len = strlen(p);
+ if (len >= sizeof(u_kbs->kb_string))
+ len = sizeof(u_kbs->kb_string) - 1;
+ if (copy_to_user(u_kbs->kb_string, p, len))
+ return -EFAULT;
+ } else
+ len = 0;
+ if (put_user('\0', u_kbs->kb_string + len))
+ return -EFAULT;
+ break;
+ case KDSKBSENT:
+ if (!perm)
+ return -EPERM;
+ len = strnlen_user(u_kbs->kb_string,
+ sizeof(u_kbs->kb_string) - 1);
+ p = kmalloc(len, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ if (copy_from_user(p, u_kbs->kb_string, len)) {
+ kfree(p);
+ return -EFAULT;
+ }
+ p[len] = 0;
+ if (kbd->func_table[kb_func])
+ kfree(kbd->func_table[kb_func]);
+ kbd->func_table[kb_func] = p;
+ break;
+ }
+ return 0;
+}
+
+int
+kbd_ioctl(struct kbd_data *kbd, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct kbdiacrs __user *a;
+ void __user *argp;
+ int ct, perm;
+
+ argp = (void __user *)arg;
+
+ /*
+ * 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 = current->signal->tty == kbd->tty || capable(CAP_SYS_TTY_CONFIG);
+ switch (cmd) {
+ case KDGKBTYPE:
+ return put_user(KB_101, (char __user *)argp);
+ case KDGKBENT:
+ case KDSKBENT:
+ return do_kdsk_ioctl(kbd, argp, cmd, perm);
+ case KDGKBSENT:
+ case KDSKBSENT:
+ return do_kdgkb_ioctl(kbd, argp, cmd, perm);
+ case KDGKBDIACR:
+ a = argp;
+
+ if (put_user(kbd->accent_table_size, &a->kb_cnt))
+ return -EFAULT;
+ ct = kbd->accent_table_size;
+ if (copy_to_user(a->kbdiacr, kbd->accent_table,
+ ct * sizeof(struct kbdiacr)))
+ return -EFAULT;
+ return 0;
+ case KDSKBDIACR:
+ a = argp;
+ if (!perm)
+ return -EPERM;
+ if (get_user(ct, &a->kb_cnt))
+ return -EFAULT;
+ if (ct >= MAX_DIACR)
+ return -EINVAL;
+ kbd->accent_table_size = ct;
+ if (copy_from_user(kbd->accent_table, a->kbdiacr,
+ ct * sizeof(struct kbdiacr)))
+ return -EFAULT;
+ return 0;
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+EXPORT_SYMBOL(kbd_ioctl);
+EXPORT_SYMBOL(kbd_ascebc);
+EXPORT_SYMBOL(kbd_free);
+EXPORT_SYMBOL(kbd_alloc);
+EXPORT_SYMBOL(kbd_keycode);