linux/drivers/accessibility/braille/braille_console.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Minimalistic braille device kernel support.
   4 *
   5 * By default, shows console messages on the braille device.
   6 * Pressing Insert switches to VC browsing.
   7 *
   8 *  Copyright (C) Samuel Thibault <samuel.thibault@ens-lyon.org>
   9 */
  10
  11#include <linux/kernel.h>
  12#include <linux/module.h>
  13#include <linux/moduleparam.h>
  14#include <linux/console.h>
  15#include <linux/notifier.h>
  16
  17#include <linux/selection.h>
  18#include <linux/vt_kern.h>
  19#include <linux/consolemap.h>
  20
  21#include <linux/keyboard.h>
  22#include <linux/kbd_kern.h>
  23#include <linux/input.h>
  24
  25MODULE_AUTHOR("samuel.thibault@ens-lyon.org");
  26MODULE_DESCRIPTION("braille device");
  27MODULE_LICENSE("GPL");
  28
  29/*
  30 * Braille device support part.
  31 */
  32
  33/* Emit various sounds */
  34static bool sound;
  35module_param(sound, bool, 0);
  36MODULE_PARM_DESC(sound, "emit sounds");
  37
  38static void beep(unsigned int freq)
  39{
  40        if (sound)
  41                kd_mksound(freq, HZ/10);
  42}
  43
  44/* mini console */
  45#define WIDTH 40
  46#define BRAILLE_KEY KEY_INSERT
  47static u16 console_buf[WIDTH];
  48static int console_cursor;
  49
  50/* mini view of VC */
  51static int vc_x, vc_y, lastvc_x, lastvc_y;
  52
  53/* show console ? (or show VC) */
  54static int console_show = 1;
  55/* pending newline ? */
  56static int console_newline = 1;
  57static int lastVC = -1;
  58
  59static struct console *braille_co;
  60
  61/* Very VisioBraille-specific */
  62static void braille_write(u16 *buf)
  63{
  64        static u16 lastwrite[WIDTH];
  65        unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c;
  66        u16 out;
  67        int i;
  68
  69        if (!braille_co)
  70                return;
  71
  72        if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf)))
  73                return;
  74        memcpy(lastwrite, buf, WIDTH * sizeof(*buf));
  75
  76#define SOH 1
  77#define STX 2
  78#define ETX 2
  79#define EOT 4
  80#define ENQ 5
  81        data[0] = STX;
  82        data[1] = '>';
  83        csum ^= '>';
  84        c = &data[2];
  85        for (i = 0; i < WIDTH; i++) {
  86                out = buf[i];
  87                if (out >= 0x100)
  88                        out = '?';
  89                else if (out == 0x00)
  90                        out = ' ';
  91                csum ^= out;
  92                if (out <= 0x05) {
  93                        *c++ = SOH;
  94                        out |= 0x40;
  95                }
  96                *c++ = out;
  97        }
  98
  99        if (csum <= 0x05) {
 100                *c++ = SOH;
 101                csum |= 0x40;
 102        }
 103        *c++ = csum;
 104        *c++ = ETX;
 105
 106        braille_co->write(braille_co, data, c - data);
 107}
 108
 109/* Follow the VC cursor*/
 110static void vc_follow_cursor(struct vc_data *vc)
 111{
 112        vc_x = vc->vc_x - (vc->vc_x % WIDTH);
 113        vc_y = vc->vc_y;
 114        lastvc_x = vc->vc_x;
 115        lastvc_y = vc->vc_y;
 116}
 117
 118/* Maybe the VC cursor moved, if so follow it */
 119static void vc_maybe_cursor_moved(struct vc_data *vc)
 120{
 121        if (vc->vc_x != lastvc_x || vc->vc_y != lastvc_y)
 122                vc_follow_cursor(vc);
 123}
 124
 125/* Show portion of VC at vc_x, vc_y */
 126static void vc_refresh(struct vc_data *vc)
 127{
 128        u16 buf[WIDTH];
 129        int i;
 130
 131        for (i = 0; i < WIDTH; i++) {
 132                u16 glyph = screen_glyph(vc,
 133                                2 * (vc_x + i) + vc_y * vc->vc_size_row);
 134                buf[i] = inverse_translate(vc, glyph, 1);
 135        }
 136        braille_write(buf);
 137}
 138
 139/*
 140 * Link to keyboard
 141 */
 142
 143static int keyboard_notifier_call(struct notifier_block *blk,
 144                                  unsigned long code, void *_param)
 145{
 146        struct keyboard_notifier_param *param = _param;
 147        struct vc_data *vc = param->vc;
 148        int ret = NOTIFY_OK;
 149
 150        if (!param->down)
 151                return ret;
 152
 153        switch (code) {
 154        case KBD_KEYCODE:
 155                if (console_show) {
 156                        if (param->value == BRAILLE_KEY) {
 157                                console_show = 0;
 158                                beep(880);
 159                                vc_maybe_cursor_moved(vc);
 160                                vc_refresh(vc);
 161                                ret = NOTIFY_STOP;
 162                        }
 163                } else {
 164                        ret = NOTIFY_STOP;
 165                        switch (param->value) {
 166                        case KEY_INSERT:
 167                                beep(440);
 168                                console_show = 1;
 169                                lastVC = -1;
 170                                braille_write(console_buf);
 171                                break;
 172                        case KEY_LEFT:
 173                                if (vc_x > 0) {
 174                                        vc_x -= WIDTH;
 175                                        if (vc_x < 0)
 176                                                vc_x = 0;
 177                                } else if (vc_y >= 1) {
 178                                        beep(880);
 179                                        vc_y--;
 180                                        vc_x = vc->vc_cols-WIDTH;
 181                                } else
 182                                        beep(220);
 183                                break;
 184                        case KEY_RIGHT:
 185                                if (vc_x + WIDTH < vc->vc_cols) {
 186                                        vc_x += WIDTH;
 187                                } else if (vc_y + 1 < vc->vc_rows) {
 188                                        beep(880);
 189                                        vc_y++;
 190                                        vc_x = 0;
 191                                } else
 192                                        beep(220);
 193                                break;
 194                        case KEY_DOWN:
 195                                if (vc_y + 1 < vc->vc_rows)
 196                                        vc_y++;
 197                                else
 198                                        beep(220);
 199                                break;
 200                        case KEY_UP:
 201                                if (vc_y >= 1)
 202                                        vc_y--;
 203                                else
 204                                        beep(220);
 205                                break;
 206                        case KEY_HOME:
 207                                vc_follow_cursor(vc);
 208                                break;
 209                        case KEY_PAGEUP:
 210                                vc_x = 0;
 211                                vc_y = 0;
 212                                break;
 213                        case KEY_PAGEDOWN:
 214                                vc_x = 0;
 215                                vc_y = vc->vc_rows-1;
 216                                break;
 217                        default:
 218                                ret = NOTIFY_OK;
 219                                break;
 220                        }
 221                        if (ret == NOTIFY_STOP)
 222                                vc_refresh(vc);
 223                }
 224                break;
 225        case KBD_POST_KEYSYM:
 226        {
 227                unsigned char type = KTYP(param->value) - 0xf0;
 228                if (type == KT_SPEC) {
 229                        unsigned char val = KVAL(param->value);
 230                        int on_off = -1;
 231
 232                        switch (val) {
 233                        case KVAL(K_CAPS):
 234                                on_off = vt_get_leds(fg_console, VC_CAPSLOCK);
 235                                break;
 236                        case KVAL(K_NUM):
 237                                on_off = vt_get_leds(fg_console, VC_NUMLOCK);
 238                                break;
 239                        case KVAL(K_HOLD):
 240                                on_off = vt_get_leds(fg_console, VC_SCROLLOCK);
 241                                break;
 242                        }
 243                        if (on_off == 1)
 244                                beep(880);
 245                        else if (on_off == 0)
 246                                beep(440);
 247                }
 248        }
 249        case KBD_UNBOUND_KEYCODE:
 250        case KBD_UNICODE:
 251        case KBD_KEYSYM:
 252                /* Unused */
 253                break;
 254        }
 255        return ret;
 256}
 257
 258static struct notifier_block keyboard_notifier_block = {
 259        .notifier_call = keyboard_notifier_call,
 260};
 261
 262static int vt_notifier_call(struct notifier_block *blk,
 263                            unsigned long code, void *_param)
 264{
 265        struct vt_notifier_param *param = _param;
 266        struct vc_data *vc = param->vc;
 267        switch (code) {
 268        case VT_ALLOCATE:
 269                break;
 270        case VT_DEALLOCATE:
 271                break;
 272        case VT_WRITE:
 273        {
 274                unsigned char c = param->c;
 275                if (vc->vc_num != fg_console)
 276                        break;
 277                switch (c) {
 278                case '\b':
 279                case 127:
 280                        if (console_cursor > 0) {
 281                                console_cursor--;
 282                                console_buf[console_cursor] = ' ';
 283                        }
 284                        break;
 285                case '\n':
 286                case '\v':
 287                case '\f':
 288                case '\r':
 289                        console_newline = 1;
 290                        break;
 291                case '\t':
 292                        c = ' ';
 293                        /* Fallthrough */
 294                default:
 295                        if (c < 32)
 296                                /* Ignore other control sequences */
 297                                break;
 298                        if (console_newline) {
 299                                memset(console_buf, 0, sizeof(console_buf));
 300                                console_cursor = 0;
 301                                console_newline = 0;
 302                        }
 303                        if (console_cursor == WIDTH)
 304                                memmove(console_buf, &console_buf[1],
 305                                        (WIDTH-1) * sizeof(*console_buf));
 306                        else
 307                                console_cursor++;
 308                        console_buf[console_cursor-1] = c;
 309                        break;
 310                }
 311                if (console_show)
 312                        braille_write(console_buf);
 313                else {
 314                        vc_maybe_cursor_moved(vc);
 315                        vc_refresh(vc);
 316                }
 317                break;
 318        }
 319        case VT_UPDATE:
 320                /* Maybe a VT switch, flush */
 321                if (console_show) {
 322                        if (vc->vc_num != lastVC) {
 323                                lastVC = vc->vc_num;
 324                                memset(console_buf, 0, sizeof(console_buf));
 325                                console_cursor = 0;
 326                                braille_write(console_buf);
 327                        }
 328                } else {
 329                        vc_maybe_cursor_moved(vc);
 330                        vc_refresh(vc);
 331                }
 332                break;
 333        }
 334        return NOTIFY_OK;
 335}
 336
 337static struct notifier_block vt_notifier_block = {
 338        .notifier_call = vt_notifier_call,
 339};
 340
 341/*
 342 * Called from printk.c when console=brl is given
 343 */
 344
 345int braille_register_console(struct console *console, int index,
 346                char *console_options, char *braille_options)
 347{
 348        int ret;
 349
 350        if (!(console->flags & CON_BRL))
 351                return 0;
 352        if (!console_options)
 353                /* Only support VisioBraille for now */
 354                console_options = "57600o8";
 355        if (braille_co)
 356                return -ENODEV;
 357        if (console->setup) {
 358                ret = console->setup(console, console_options);
 359                if (ret != 0)
 360                        return ret;
 361        }
 362        console->flags |= CON_ENABLED;
 363        console->index = index;
 364        braille_co = console;
 365        register_keyboard_notifier(&keyboard_notifier_block);
 366        register_vt_notifier(&vt_notifier_block);
 367        return 1;
 368}
 369
 370int braille_unregister_console(struct console *console)
 371{
 372        if (braille_co != console)
 373                return -EINVAL;
 374        if (!(console->flags & CON_BRL))
 375                return 0;
 376        unregister_keyboard_notifier(&keyboard_notifier_block);
 377        unregister_vt_notifier(&vt_notifier_block);
 378        braille_co = NULL;
 379        return 1;
 380}
 381