Path: blob/master/drivers/accessibility/braille/braille_console.c
26282 views
// SPDX-License-Identifier: GPL-2.0-or-later1/*2* Minimalistic braille device kernel support.3*4* By default, shows console messages on the braille device.5* Pressing Insert switches to VC browsing.6*7* Copyright (C) Samuel Thibault <[email protected]>8*/910#include <linux/kernel.h>11#include <linux/module.h>12#include <linux/moduleparam.h>13#include <linux/console.h>14#include <linux/notifier.h>1516#include <linux/selection.h>17#include <linux/vt_kern.h>18#include <linux/consolemap.h>1920#include <linux/keyboard.h>21#include <linux/kbd_kern.h>22#include <linux/input.h>2324MODULE_AUTHOR("[email protected]");25MODULE_DESCRIPTION("braille device");2627/*28* Braille device support part.29*/3031/* Emit various sounds */32static bool sound;33module_param(sound, bool, 0);34MODULE_PARM_DESC(sound, "emit sounds");3536static void beep(unsigned int freq)37{38if (sound)39kd_mksound(freq, HZ/10);40}4142/* mini console */43#define WIDTH 4044#define BRAILLE_KEY KEY_INSERT45static u16 console_buf[WIDTH];46static int console_cursor;4748/* mini view of VC */49static int vc_x, vc_y, lastvc_x, lastvc_y;5051/* show console ? (or show VC) */52static int console_show = 1;53/* pending newline ? */54static int console_newline = 1;55static int lastVC = -1;5657static struct console *braille_co;5859/* Very VisioBraille-specific */60static void braille_write(u16 *buf)61{62static u16 lastwrite[WIDTH];63unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c;64u16 out;65int i;6667if (!braille_co)68return;6970if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf)))71return;72memcpy(lastwrite, buf, WIDTH * sizeof(*buf));7374#define SOH 175#define STX 276#define ETX 277#define EOT 478#define ENQ 579data[0] = STX;80data[1] = '>';81csum ^= '>';82c = &data[2];83for (i = 0; i < WIDTH; i++) {84out = buf[i];85if (out >= 0x100)86out = '?';87else if (out == 0x00)88out = ' ';89csum ^= out;90if (out <= 0x05) {91*c++ = SOH;92out |= 0x40;93}94*c++ = out;95}9697if (csum <= 0x05) {98*c++ = SOH;99csum |= 0x40;100}101*c++ = csum;102*c++ = ETX;103104braille_co->write(braille_co, data, c - data);105}106107/* Follow the VC cursor*/108static void vc_follow_cursor(struct vc_data *vc)109{110vc_x = vc->state.x - (vc->state.x % WIDTH);111vc_y = vc->state.y;112lastvc_x = vc->state.x;113lastvc_y = vc->state.y;114}115116/* Maybe the VC cursor moved, if so follow it */117static void vc_maybe_cursor_moved(struct vc_data *vc)118{119if (vc->state.x != lastvc_x || vc->state.y != lastvc_y)120vc_follow_cursor(vc);121}122123/* Show portion of VC at vc_x, vc_y */124static void vc_refresh(struct vc_data *vc)125{126u16 buf[WIDTH];127int i;128129for (i = 0; i < WIDTH; i++) {130u16 glyph = screen_glyph(vc,1312 * (vc_x + i) + vc_y * vc->vc_size_row);132buf[i] = inverse_translate(vc, glyph, true);133}134braille_write(buf);135}136137/*138* Link to keyboard139*/140141static int keyboard_notifier_call(struct notifier_block *blk,142unsigned long code, void *_param)143{144struct keyboard_notifier_param *param = _param;145struct vc_data *vc = param->vc;146int ret = NOTIFY_OK;147148if (!param->down)149return ret;150151switch (code) {152case KBD_KEYCODE:153if (console_show) {154if (param->value == BRAILLE_KEY) {155console_show = 0;156beep(880);157vc_maybe_cursor_moved(vc);158vc_refresh(vc);159ret = NOTIFY_STOP;160}161} else {162ret = NOTIFY_STOP;163switch (param->value) {164case KEY_INSERT:165beep(440);166console_show = 1;167lastVC = -1;168braille_write(console_buf);169break;170case KEY_LEFT:171if (vc_x > 0) {172vc_x -= WIDTH;173if (vc_x < 0)174vc_x = 0;175} else if (vc_y >= 1) {176beep(880);177vc_y--;178vc_x = vc->vc_cols-WIDTH;179} else180beep(220);181break;182case KEY_RIGHT:183if (vc_x + WIDTH < vc->vc_cols) {184vc_x += WIDTH;185} else if (vc_y + 1 < vc->vc_rows) {186beep(880);187vc_y++;188vc_x = 0;189} else190beep(220);191break;192case KEY_DOWN:193if (vc_y + 1 < vc->vc_rows)194vc_y++;195else196beep(220);197break;198case KEY_UP:199if (vc_y >= 1)200vc_y--;201else202beep(220);203break;204case KEY_HOME:205vc_follow_cursor(vc);206break;207case KEY_PAGEUP:208vc_x = 0;209vc_y = 0;210break;211case KEY_PAGEDOWN:212vc_x = 0;213vc_y = vc->vc_rows-1;214break;215default:216ret = NOTIFY_OK;217break;218}219if (ret == NOTIFY_STOP)220vc_refresh(vc);221}222break;223case KBD_POST_KEYSYM:224{225unsigned char type = KTYP(param->value) - 0xf0;226227if (type == KT_SPEC) {228unsigned char val = KVAL(param->value);229int on_off = -1;230231switch (val) {232case KVAL(K_CAPS):233on_off = vt_get_leds(fg_console, VC_CAPSLOCK);234break;235case KVAL(K_NUM):236on_off = vt_get_leds(fg_console, VC_NUMLOCK);237break;238case KVAL(K_HOLD):239on_off = vt_get_leds(fg_console, VC_SCROLLOCK);240break;241}242if (on_off == 1)243beep(880);244else if (on_off == 0)245beep(440);246}247}248break;249case KBD_UNBOUND_KEYCODE:250case KBD_UNICODE:251case KBD_KEYSYM:252/* Unused */253break;254}255return ret;256}257258static struct notifier_block keyboard_notifier_block = {259.notifier_call = keyboard_notifier_call,260};261262static int vt_notifier_call(struct notifier_block *blk,263unsigned long code, void *_param)264{265struct vt_notifier_param *param = _param;266struct vc_data *vc = param->vc;267268switch (code) {269case VT_ALLOCATE:270break;271case VT_DEALLOCATE:272break;273case VT_WRITE:274{275unsigned char c = param->c;276277if (vc->vc_num != fg_console)278break;279switch (c) {280case '\b':281case 127:282if (console_cursor > 0) {283console_cursor--;284console_buf[console_cursor] = ' ';285}286break;287case '\n':288case '\v':289case '\f':290case '\r':291console_newline = 1;292break;293case '\t':294c = ' ';295fallthrough;296default:297if (c < 32)298/* Ignore other control sequences */299break;300if (console_newline) {301memset(console_buf, 0, sizeof(console_buf));302console_cursor = 0;303console_newline = 0;304}305if (console_cursor == WIDTH)306memmove(console_buf, &console_buf[1],307(WIDTH-1) * sizeof(*console_buf));308else309console_cursor++;310console_buf[console_cursor-1] = c;311break;312}313if (console_show)314braille_write(console_buf);315else {316vc_maybe_cursor_moved(vc);317vc_refresh(vc);318}319break;320}321case VT_UPDATE:322/* Maybe a VT switch, flush */323if (console_show) {324if (vc->vc_num != lastVC) {325lastVC = vc->vc_num;326memset(console_buf, 0, sizeof(console_buf));327console_cursor = 0;328braille_write(console_buf);329}330} else {331vc_maybe_cursor_moved(vc);332vc_refresh(vc);333}334break;335}336return NOTIFY_OK;337}338339static struct notifier_block vt_notifier_block = {340.notifier_call = vt_notifier_call,341};342343/*344* Called from printk.c when console=brl is given345*/346347int braille_register_console(struct console *console, int index,348char *console_options, char *braille_options)349{350int ret;351352if (!console_options)353/* Only support VisioBraille for now */354console_options = "57600o8";355if (braille_co)356return -ENODEV;357if (console->setup) {358ret = console->setup(console, console_options);359if (ret != 0)360return ret;361}362console->flags |= CON_ENABLED;363console->index = index;364braille_co = console;365register_keyboard_notifier(&keyboard_notifier_block);366register_vt_notifier(&vt_notifier_block);367return 1;368}369370int braille_unregister_console(struct console *console)371{372if (braille_co != console)373return -EINVAL;374unregister_keyboard_notifier(&keyboard_notifier_block);375unregister_vt_notifier(&vt_notifier_block);376braille_co = NULL;377return 1;378}379380381