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->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 */ 119 static 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 */ 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 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 case KBD_UNBOUND_KEYCODE: 251 case KBD_UNICODE: 252 case KBD_KEYSYM: 253 /* Unused */ 254 break; 255 } 256 return ret; 257 } 258 259 static struct notifier_block keyboard_notifier_block = { 260 .notifier_call = keyboard_notifier_call, 261 }; 262 263 static 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 else 310 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 340 static struct notifier_block vt_notifier_block = { 341 .notifier_call = vt_notifier_call, 342 }; 343 344 /* 345 * Called from printk.c when console=brl is given 346 */ 347 348 int 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 371 int 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