braille_console.c (7488B)
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->state.x - (vc->state.x % WIDTH); 113 vc_y = vc->state.y; 114 lastvc_x = vc->state.x; 115 lastvc_y = vc->state.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->state.x != lastvc_x || vc->state.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 229 if (type == KT_SPEC) { 230 unsigned char val = KVAL(param->value); 231 int on_off = -1; 232 233 switch (val) { 234 case KVAL(K_CAPS): 235 on_off = vt_get_leds(fg_console, VC_CAPSLOCK); 236 break; 237 case KVAL(K_NUM): 238 on_off = vt_get_leds(fg_console, VC_NUMLOCK); 239 break; 240 case KVAL(K_HOLD): 241 on_off = vt_get_leds(fg_console, VC_SCROLLOCK); 242 break; 243 } 244 if (on_off == 1) 245 beep(880); 246 else if (on_off == 0) 247 beep(440); 248 } 249 } 250 break; 251 case KBD_UNBOUND_KEYCODE: 252 case KBD_UNICODE: 253 case KBD_KEYSYM: 254 /* Unused */ 255 break; 256 } 257 return ret; 258} 259 260static struct notifier_block keyboard_notifier_block = { 261 .notifier_call = keyboard_notifier_call, 262}; 263 264static int vt_notifier_call(struct notifier_block *blk, 265 unsigned long code, void *_param) 266{ 267 struct vt_notifier_param *param = _param; 268 struct vc_data *vc = param->vc; 269 270 switch (code) { 271 case VT_ALLOCATE: 272 break; 273 case VT_DEALLOCATE: 274 break; 275 case VT_WRITE: 276 { 277 unsigned char c = param->c; 278 279 if (vc->vc_num != fg_console) 280 break; 281 switch (c) { 282 case '\b': 283 case 127: 284 if (console_cursor > 0) { 285 console_cursor--; 286 console_buf[console_cursor] = ' '; 287 } 288 break; 289 case '\n': 290 case '\v': 291 case '\f': 292 case '\r': 293 console_newline = 1; 294 break; 295 case '\t': 296 c = ' '; 297 fallthrough; 298 default: 299 if (c < 32) 300 /* Ignore other control sequences */ 301 break; 302 if (console_newline) { 303 memset(console_buf, 0, sizeof(console_buf)); 304 console_cursor = 0; 305 console_newline = 0; 306 } 307 if (console_cursor == WIDTH) 308 memmove(console_buf, &console_buf[1], 309 (WIDTH-1) * sizeof(*console_buf)); 310 else 311 console_cursor++; 312 console_buf[console_cursor-1] = c; 313 break; 314 } 315 if (console_show) 316 braille_write(console_buf); 317 else { 318 vc_maybe_cursor_moved(vc); 319 vc_refresh(vc); 320 } 321 break; 322 } 323 case VT_UPDATE: 324 /* Maybe a VT switch, flush */ 325 if (console_show) { 326 if (vc->vc_num != lastVC) { 327 lastVC = vc->vc_num; 328 memset(console_buf, 0, sizeof(console_buf)); 329 console_cursor = 0; 330 braille_write(console_buf); 331 } 332 } else { 333 vc_maybe_cursor_moved(vc); 334 vc_refresh(vc); 335 } 336 break; 337 } 338 return NOTIFY_OK; 339} 340 341static struct notifier_block vt_notifier_block = { 342 .notifier_call = vt_notifier_call, 343}; 344 345/* 346 * Called from printk.c when console=brl is given 347 */ 348 349int braille_register_console(struct console *console, int index, 350 char *console_options, char *braille_options) 351{ 352 int ret; 353 354 if (!console_options) 355 /* Only support VisioBraille for now */ 356 console_options = "57600o8"; 357 if (braille_co) 358 return -ENODEV; 359 if (console->setup) { 360 ret = console->setup(console, console_options); 361 if (ret != 0) 362 return ret; 363 } 364 console->flags |= CON_ENABLED; 365 console->index = index; 366 braille_co = console; 367 register_keyboard_notifier(&keyboard_notifier_block); 368 register_vt_notifier(&vt_notifier_block); 369 return 1; 370} 371 372int braille_unregister_console(struct console *console) 373{ 374 if (braille_co != console) 375 return -EINVAL; 376 unregister_keyboard_notifier(&keyboard_notifier_block); 377 unregister_vt_notifier(&vt_notifier_block); 378 braille_co = NULL; 379 return 1; 380}