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 25 MODULE_AUTHOR("samuel.thibault@ens-lyon.org"); 26 MODULE_DESCRIPTION("braille device"); 27 28 /* 29 * Braille device support part. 30 */ 31 32 /* Emit various sounds */ 33 static bool sound; 34 module_param(sound, bool, 0); 35 MODULE_PARM_DESC(sound, "emit sounds"); 36 37 static void beep(unsigned int freq) 38 { 39 if (sound) 40 kd_mksound(freq, HZ/10); 41 } 42 43 /* mini console */ 44 #define WIDTH 40 45 #define BRAILLE_KEY KEY_INSERT 46 static u16 console_buf[WIDTH]; 47 static int console_cursor; 48 49 /* mini view of VC */ 50 static int vc_x, vc_y, lastvc_x, lastvc_y; 51 52 /* show console ? (or show VC) */ 53 static int console_show = 1; 54 /* pending newline ? */ 55 static int console_newline = 1; 56 static int lastVC = -1; 57 58 static struct console *braille_co; 59 60 /* Very VisioBraille-specific */ 61 static 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 struct nbcon_write_context wctxt = { }; 66 unsigned long flags; 67 u16 out; 68 int i; 69 70 if (!braille_co) 71 return; 72 73 /* 74 * Braille console is not registered in console_list. Instead, it 75 * is integrated with VT and shows what appears on the graphical 76 * console under console_lock(). From this POV it is a legacy 77 * console. But it calls serial console driver which might be 78 * converted to the NBCON API. It is similar to 79 * nbcon_legacy_emit_next_record() except that we should try 80 * harder to get the lock. Otherwise, the Braille device won't show 81 * everything what is displayed on the terminal. 82 * 83 * In short, simulate the original locking using NBCON API. 84 */ 85 if (braille_co->flags & CON_NBCON) { 86 if (panic_on_this_cpu()) { 87 /* 88 * This should be good enough in practice. Most/all 89 * serial console drivers have the atomic callback. 90 */ 91 if (!braille_co->write_atomic) 92 return; 93 94 local_irq_save(flags); 95 /* NBCON API strictly requires the ownership. */ 96 if (!nbcon_braille_try_acquire(braille_co, &wctxt)) { 97 local_irq_restore(flags); 98 return; 99 } 100 } else { 101 braille_co->device_lock(braille_co, &flags); 102 while (!nbcon_braille_try_acquire(braille_co, &wctxt)) 103 cpu_relax(); 104 } 105 } 106 107 if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf))) 108 goto unlock_nbcon; 109 memcpy(lastwrite, buf, WIDTH * sizeof(*buf)); 110 111 #define SOH 1 112 #define STX 2 113 #define ETX 2 114 #define EOT 4 115 #define ENQ 5 116 data[0] = STX; 117 data[1] = '>'; 118 csum ^= '>'; 119 c = &data[2]; 120 for (i = 0; i < WIDTH; i++) { 121 out = buf[i]; 122 if (out >= 0x100) 123 out = '?'; 124 else if (out == 0x00) 125 out = ' '; 126 csum ^= out; 127 if (out <= 0x05) { 128 *c++ = SOH; 129 out |= 0x40; 130 } 131 *c++ = out; 132 } 133 134 if (csum <= 0x05) { 135 *c++ = SOH; 136 csum |= 0x40; 137 } 138 *c++ = csum; 139 *c++ = ETX; 140 141 if (braille_co->flags & CON_NBCON) { 142 nbcon_write_context_set_buf(&wctxt, (char *)data, c - data); 143 if (panic_on_this_cpu()) 144 braille_co->write_atomic(braille_co, &wctxt); 145 else 146 braille_co->write_thread(braille_co, &wctxt); 147 } else { 148 braille_co->write(braille_co, data, c - data); 149 } 150 151 unlock_nbcon: 152 if (braille_co->flags & CON_NBCON) { 153 nbcon_braille_release(&wctxt); 154 if (panic_on_this_cpu()) 155 local_irq_restore(flags); 156 else 157 braille_co->device_unlock(braille_co, flags); 158 } 159 } 160 161 /* Follow the VC cursor*/ 162 static void vc_follow_cursor(struct vc_data *vc) 163 { 164 vc_x = vc->state.x - (vc->state.x % WIDTH); 165 vc_y = vc->state.y; 166 lastvc_x = vc->state.x; 167 lastvc_y = vc->state.y; 168 } 169 170 /* Maybe the VC cursor moved, if so follow it */ 171 static void vc_maybe_cursor_moved(struct vc_data *vc) 172 { 173 if (vc->state.x != lastvc_x || vc->state.y != lastvc_y) 174 vc_follow_cursor(vc); 175 } 176 177 /* Show portion of VC at vc_x, vc_y */ 178 static void vc_refresh(struct vc_data *vc) 179 { 180 u16 buf[WIDTH]; 181 int i; 182 183 for (i = 0; i < WIDTH; i++) { 184 u16 glyph = screen_glyph(vc, 185 2 * (vc_x + i) + vc_y * vc->vc_size_row); 186 buf[i] = inverse_translate(vc, glyph, true); 187 } 188 braille_write(buf); 189 } 190 191 /* 192 * Link to keyboard 193 */ 194 195 static int keyboard_notifier_call(struct notifier_block *blk, 196 unsigned long code, void *_param) 197 { 198 struct keyboard_notifier_param *param = _param; 199 struct vc_data *vc = param->vc; 200 int ret = NOTIFY_OK; 201 202 if (!param->down) 203 return ret; 204 205 switch (code) { 206 case KBD_KEYCODE: 207 if (console_show) { 208 if (param->value == BRAILLE_KEY) { 209 console_show = 0; 210 beep(880); 211 vc_maybe_cursor_moved(vc); 212 vc_refresh(vc); 213 ret = NOTIFY_STOP; 214 } 215 } else { 216 ret = NOTIFY_STOP; 217 switch (param->value) { 218 case KEY_INSERT: 219 beep(440); 220 console_show = 1; 221 lastVC = -1; 222 braille_write(console_buf); 223 break; 224 case KEY_LEFT: 225 if (vc_x > 0) { 226 vc_x -= WIDTH; 227 if (vc_x < 0) 228 vc_x = 0; 229 } else if (vc_y >= 1) { 230 beep(880); 231 vc_y--; 232 vc_x = vc->vc_cols-WIDTH; 233 } else 234 beep(220); 235 break; 236 case KEY_RIGHT: 237 if (vc_x + WIDTH < vc->vc_cols) { 238 vc_x += WIDTH; 239 } else if (vc_y + 1 < vc->vc_rows) { 240 beep(880); 241 vc_y++; 242 vc_x = 0; 243 } else 244 beep(220); 245 break; 246 case KEY_DOWN: 247 if (vc_y + 1 < vc->vc_rows) 248 vc_y++; 249 else 250 beep(220); 251 break; 252 case KEY_UP: 253 if (vc_y >= 1) 254 vc_y--; 255 else 256 beep(220); 257 break; 258 case KEY_HOME: 259 vc_follow_cursor(vc); 260 break; 261 case KEY_PAGEUP: 262 vc_x = 0; 263 vc_y = 0; 264 break; 265 case KEY_PAGEDOWN: 266 vc_x = 0; 267 vc_y = vc->vc_rows-1; 268 break; 269 default: 270 ret = NOTIFY_OK; 271 break; 272 } 273 if (ret == NOTIFY_STOP) 274 vc_refresh(vc); 275 } 276 break; 277 case KBD_POST_KEYSYM: 278 { 279 unsigned char type = KTYP(param->value) - 0xf0; 280 281 if (type == KT_SPEC) { 282 unsigned char val = KVAL(param->value); 283 int on_off = -1; 284 285 switch (val) { 286 case KVAL(K_CAPS): 287 on_off = vt_get_leds(fg_console, VC_CAPSLOCK); 288 break; 289 case KVAL(K_NUM): 290 on_off = vt_get_leds(fg_console, VC_NUMLOCK); 291 break; 292 case KVAL(K_HOLD): 293 on_off = vt_get_leds(fg_console, VC_SCROLLOCK); 294 break; 295 } 296 if (on_off == 1) 297 beep(880); 298 else if (on_off == 0) 299 beep(440); 300 } 301 } 302 break; 303 case KBD_UNBOUND_KEYCODE: 304 case KBD_UNICODE: 305 case KBD_KEYSYM: 306 /* Unused */ 307 break; 308 } 309 return ret; 310 } 311 312 static struct notifier_block keyboard_notifier_block = { 313 .notifier_call = keyboard_notifier_call, 314 }; 315 316 static int vt_notifier_call(struct notifier_block *blk, 317 unsigned long code, void *_param) 318 { 319 struct vt_notifier_param *param = _param; 320 struct vc_data *vc = param->vc; 321 322 switch (code) { 323 case VT_ALLOCATE: 324 break; 325 case VT_DEALLOCATE: 326 break; 327 case VT_WRITE: 328 { 329 unsigned char c = param->c; 330 331 if (vc->vc_num != fg_console) 332 break; 333 switch (c) { 334 case '\b': 335 case 127: 336 if (console_cursor > 0) { 337 console_cursor--; 338 console_buf[console_cursor] = ' '; 339 } 340 break; 341 case '\n': 342 case '\v': 343 case '\f': 344 case '\r': 345 console_newline = 1; 346 break; 347 case '\t': 348 c = ' '; 349 fallthrough; 350 default: 351 if (c < 32) 352 /* Ignore other control sequences */ 353 break; 354 if (console_newline) { 355 memset(console_buf, 0, sizeof(console_buf)); 356 console_cursor = 0; 357 console_newline = 0; 358 } 359 if (console_cursor == WIDTH) 360 memmove(console_buf, &console_buf[1], 361 (WIDTH-1) * sizeof(*console_buf)); 362 else 363 console_cursor++; 364 console_buf[console_cursor-1] = c; 365 break; 366 } 367 if (console_show) 368 braille_write(console_buf); 369 else { 370 vc_maybe_cursor_moved(vc); 371 vc_refresh(vc); 372 } 373 break; 374 } 375 case VT_UPDATE: 376 /* Maybe a VT switch, flush */ 377 if (console_show) { 378 if (vc->vc_num != lastVC) { 379 lastVC = vc->vc_num; 380 memset(console_buf, 0, sizeof(console_buf)); 381 console_cursor = 0; 382 braille_write(console_buf); 383 } 384 } else { 385 vc_maybe_cursor_moved(vc); 386 vc_refresh(vc); 387 } 388 break; 389 } 390 return NOTIFY_OK; 391 } 392 393 static struct notifier_block vt_notifier_block = { 394 .notifier_call = vt_notifier_call, 395 }; 396 397 /* 398 * Called from printk.c when console=brl is given 399 */ 400 401 int braille_register_console(struct console *console, int index, 402 char *console_options, char *braille_options) 403 { 404 int ret; 405 406 if (!console_options) 407 /* Only support VisioBraille for now */ 408 console_options = "57600o8"; 409 if (braille_co) 410 return -ENODEV; 411 if (console->setup) { 412 ret = console->setup(console, console_options); 413 if (ret != 0) 414 return ret; 415 } 416 console->flags |= CON_ENABLED; 417 console->index = index; 418 braille_co = console; 419 register_keyboard_notifier(&keyboard_notifier_block); 420 register_vt_notifier(&vt_notifier_block); 421 return 1; 422 } 423 424 int braille_unregister_console(struct console *console) 425 { 426 if (braille_co != console) 427 return -EINVAL; 428 unregister_keyboard_notifier(&keyboard_notifier_block); 429 unregister_vt_notifier(&vt_notifier_block); 430 braille_co = NULL; 431 return 1; 432 } 433