diff options
Diffstat (limited to 'drivers/s390/char/keyboard.c')
-rw-r--r-- | drivers/s390/char/keyboard.c | 519 |
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); |