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 MODULE_LICENSE("GPL"); 28 29 /* 30 * Braille device support part. 31 */ 32 33 /* Emit various sounds */ 34 static bool sound; 35 module_param(sound, bool, 0); 36 MODULE_PARM_DESC(sound, "emit sounds"); 37 38 static 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 47 static u16 console_buf[WIDTH]; 48 static int console_cursor; 49 50 /* mini view of VC */ 51 static int vc_x, vc_y, lastvc_x, lastvc_y; 52 53 /* show console ? (or show VC) */ 54 static int console_show = 1; 55 /* pending newline ? */ 56 static int console_newline = 1; 57 static int lastVC = -1; 58 59 static struct console *braille_co; 60 61 /* Very VisioBraille-specific */ 62 static 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*/ 110 static 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 */ 119 static 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 */ 126 static 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 143 static 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 258 static struct notifier_block keyboard_notifier_block = { 259 .notifier_call = keyboard_notifier_call, 260 }; 261 262 static 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 337 static 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 345 int 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 370 int 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