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
beep(unsigned int freq)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 */
braille_write(u16 * buf)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*/
vc_follow_cursor(struct vc_data * vc)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 */
vc_maybe_cursor_moved(struct vc_data * vc)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 */
vc_refresh(struct vc_data * vc)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
keyboard_notifier_call(struct notifier_block * blk,unsigned long code,void * _param)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
vt_notifier_call(struct notifier_block * blk,unsigned long code,void * _param)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
braille_register_console(struct console * console,int index,char * console_options,char * braille_options)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
braille_unregister_console(struct console * console)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