380 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*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");27 28/*29 * Braille device support part.30 */31 32/* Emit various sounds */33static bool sound;34module_param(sound, bool, 0);35MODULE_PARM_DESC(sound, "emit sounds");36 37static void beep(unsigned int freq)38{39 if (sound)40 kd_mksound(freq, HZ/10);41}42 43/* mini console */44#define WIDTH 4045#define BRAILLE_KEY KEY_INSERT46static u16 console_buf[WIDTH];47static int console_cursor;48 49/* mini view of VC */50static int vc_x, vc_y, lastvc_x, lastvc_y;51 52/* show console ? (or show VC) */53static int console_show = 1;54/* pending newline ? */55static int console_newline = 1;56static int lastVC = -1;57 58static struct console *braille_co;59 60/* Very VisioBraille-specific */61static void braille_write(u16 *buf)62{63 static u16 lastwrite[WIDTH];64 unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c;65 u16 out;66 int i;67 68 if (!braille_co)69 return;70 71 if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf)))72 return;73 memcpy(lastwrite, buf, WIDTH * sizeof(*buf));74 75#define SOH 176#define STX 277#define ETX 278#define EOT 479#define ENQ 580 data[0] = STX;81 data[1] = '>';82 csum ^= '>';83 c = &data[2];84 for (i = 0; i < WIDTH; i++) {85 out = buf[i];86 if (out >= 0x100)87 out = '?';88 else if (out == 0x00)89 out = ' ';90 csum ^= out;91 if (out <= 0x05) {92 *c++ = SOH;93 out |= 0x40;94 }95 *c++ = out;96 }97 98 if (csum <= 0x05) {99 *c++ = SOH;100 csum |= 0x40;101 }102 *c++ = csum;103 *c++ = ETX;104 105 braille_co->write(braille_co, data, c - data);106}107 108/* Follow the VC cursor*/109static void vc_follow_cursor(struct vc_data *vc)110{111 vc_x = vc->state.x - (vc->state.x % WIDTH);112 vc_y = vc->state.y;113 lastvc_x = vc->state.x;114 lastvc_y = vc->state.y;115}116 117/* Maybe the VC cursor moved, if so follow it */118static void vc_maybe_cursor_moved(struct vc_data *vc)119{120 if (vc->state.x != lastvc_x || vc->state.y != lastvc_y)121 vc_follow_cursor(vc);122}123 124/* Show portion of VC at vc_x, vc_y */125static void vc_refresh(struct vc_data *vc)126{127 u16 buf[WIDTH];128 int i;129 130 for (i = 0; i < WIDTH; i++) {131 u16 glyph = screen_glyph(vc,132 2 * (vc_x + i) + vc_y * vc->vc_size_row);133 buf[i] = inverse_translate(vc, glyph, true);134 }135 braille_write(buf);136}137 138/*139 * Link to keyboard140 */141 142static int keyboard_notifier_call(struct notifier_block *blk,143 unsigned long code, void *_param)144{145 struct keyboard_notifier_param *param = _param;146 struct vc_data *vc = param->vc;147 int ret = NOTIFY_OK;148 149 if (!param->down)150 return ret;151 152 switch (code) {153 case KBD_KEYCODE:154 if (console_show) {155 if (param->value == BRAILLE_KEY) {156 console_show = 0;157 beep(880);158 vc_maybe_cursor_moved(vc);159 vc_refresh(vc);160 ret = NOTIFY_STOP;161 }162 } else {163 ret = NOTIFY_STOP;164 switch (param->value) {165 case KEY_INSERT:166 beep(440);167 console_show = 1;168 lastVC = -1;169 braille_write(console_buf);170 break;171 case KEY_LEFT:172 if (vc_x > 0) {173 vc_x -= WIDTH;174 if (vc_x < 0)175 vc_x = 0;176 } else if (vc_y >= 1) {177 beep(880);178 vc_y--;179 vc_x = vc->vc_cols-WIDTH;180 } else181 beep(220);182 break;183 case KEY_RIGHT:184 if (vc_x + WIDTH < vc->vc_cols) {185 vc_x += WIDTH;186 } else if (vc_y + 1 < vc->vc_rows) {187 beep(880);188 vc_y++;189 vc_x = 0;190 } else191 beep(220);192 break;193 case KEY_DOWN:194 if (vc_y + 1 < vc->vc_rows)195 vc_y++;196 else197 beep(220);198 break;199 case KEY_UP:200 if (vc_y >= 1)201 vc_y--;202 else203 beep(220);204 break;205 case KEY_HOME:206 vc_follow_cursor(vc);207 break;208 case KEY_PAGEUP:209 vc_x = 0;210 vc_y = 0;211 break;212 case KEY_PAGEDOWN:213 vc_x = 0;214 vc_y = vc->vc_rows-1;215 break;216 default:217 ret = NOTIFY_OK;218 break;219 }220 if (ret == NOTIFY_STOP)221 vc_refresh(vc);222 }223 break;224 case KBD_POST_KEYSYM:225 {226 unsigned char type = KTYP(param->value) - 0xf0;227 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 break;250 case KBD_UNBOUND_KEYCODE:251 case KBD_UNICODE:252 case KBD_KEYSYM:253 /* Unused */254 break;255 }256 return ret;257}258 259static struct notifier_block keyboard_notifier_block = {260 .notifier_call = keyboard_notifier_call,261};262 263static int vt_notifier_call(struct notifier_block *blk,264 unsigned long code, void *_param)265{266 struct vt_notifier_param *param = _param;267 struct vc_data *vc = param->vc;268 269 switch (code) {270 case VT_ALLOCATE:271 break;272 case VT_DEALLOCATE:273 break;274 case VT_WRITE:275 {276 unsigned char c = param->c;277 278 if (vc->vc_num != fg_console)279 break;280 switch (c) {281 case '\b':282 case 127:283 if (console_cursor > 0) {284 console_cursor--;285 console_buf[console_cursor] = ' ';286 }287 break;288 case '\n':289 case '\v':290 case '\f':291 case '\r':292 console_newline = 1;293 break;294 case '\t':295 c = ' ';296 fallthrough;297 default:298 if (c < 32)299 /* Ignore other control sequences */300 break;301 if (console_newline) {302 memset(console_buf, 0, sizeof(console_buf));303 console_cursor = 0;304 console_newline = 0;305 }306 if (console_cursor == WIDTH)307 memmove(console_buf, &console_buf[1],308 (WIDTH-1) * sizeof(*console_buf));309 else310 console_cursor++;311 console_buf[console_cursor-1] = c;312 break;313 }314 if (console_show)315 braille_write(console_buf);316 else {317 vc_maybe_cursor_moved(vc);318 vc_refresh(vc);319 }320 break;321 }322 case VT_UPDATE:323 /* Maybe a VT switch, flush */324 if (console_show) {325 if (vc->vc_num != lastVC) {326 lastVC = vc->vc_num;327 memset(console_buf, 0, sizeof(console_buf));328 console_cursor = 0;329 braille_write(console_buf);330 }331 } else {332 vc_maybe_cursor_moved(vc);333 vc_refresh(vc);334 }335 break;336 }337 return NOTIFY_OK;338}339 340static struct notifier_block vt_notifier_block = {341 .notifier_call = vt_notifier_call,342};343 344/*345 * Called from printk.c when console=brl is given346 */347 348int braille_register_console(struct console *console, int index,349 char *console_options, char *braille_options)350{351 int ret;352 353 if (!console_options)354 /* Only support VisioBraille for now */355 console_options = "57600o8";356 if (braille_co)357 return -ENODEV;358 if (console->setup) {359 ret = console->setup(console, console_options);360 if (ret != 0)361 return ret;362 }363 console->flags |= CON_ENABLED;364 console->index = index;365 braille_co = console;366 register_keyboard_notifier(&keyboard_notifier_block);367 register_vt_notifier(&vt_notifier_block);368 return 1;369}370 371int braille_unregister_console(struct console *console)372{373 if (braille_co != console)374 return -EINVAL;375 unregister_keyboard_notifier(&keyboard_notifier_block);376 unregister_vt_notifier(&vt_notifier_block);377 braille_co = NULL;378 return 1;379}380