1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Minimalistic braille device kernel support.
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * By default, shows console messages on the braille device.
6*4882a593Smuzhiyun * Pressing Insert switches to VC browsing.
7*4882a593Smuzhiyun *
8*4882a593Smuzhiyun * Copyright (C) Samuel Thibault <samuel.thibault@ens-lyon.org>
9*4882a593Smuzhiyun */
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun #include <linux/kernel.h>
12*4882a593Smuzhiyun #include <linux/module.h>
13*4882a593Smuzhiyun #include <linux/moduleparam.h>
14*4882a593Smuzhiyun #include <linux/console.h>
15*4882a593Smuzhiyun #include <linux/notifier.h>
16*4882a593Smuzhiyun
17*4882a593Smuzhiyun #include <linux/selection.h>
18*4882a593Smuzhiyun #include <linux/vt_kern.h>
19*4882a593Smuzhiyun #include <linux/consolemap.h>
20*4882a593Smuzhiyun
21*4882a593Smuzhiyun #include <linux/keyboard.h>
22*4882a593Smuzhiyun #include <linux/kbd_kern.h>
23*4882a593Smuzhiyun #include <linux/input.h>
24*4882a593Smuzhiyun
25*4882a593Smuzhiyun MODULE_AUTHOR("samuel.thibault@ens-lyon.org");
26*4882a593Smuzhiyun MODULE_DESCRIPTION("braille device");
27*4882a593Smuzhiyun MODULE_LICENSE("GPL");
28*4882a593Smuzhiyun
29*4882a593Smuzhiyun /*
30*4882a593Smuzhiyun * Braille device support part.
31*4882a593Smuzhiyun */
32*4882a593Smuzhiyun
33*4882a593Smuzhiyun /* Emit various sounds */
34*4882a593Smuzhiyun static bool sound;
35*4882a593Smuzhiyun module_param(sound, bool, 0);
36*4882a593Smuzhiyun MODULE_PARM_DESC(sound, "emit sounds");
37*4882a593Smuzhiyun
beep(unsigned int freq)38*4882a593Smuzhiyun static void beep(unsigned int freq)
39*4882a593Smuzhiyun {
40*4882a593Smuzhiyun if (sound)
41*4882a593Smuzhiyun kd_mksound(freq, HZ/10);
42*4882a593Smuzhiyun }
43*4882a593Smuzhiyun
44*4882a593Smuzhiyun /* mini console */
45*4882a593Smuzhiyun #define WIDTH 40
46*4882a593Smuzhiyun #define BRAILLE_KEY KEY_INSERT
47*4882a593Smuzhiyun static u16 console_buf[WIDTH];
48*4882a593Smuzhiyun static int console_cursor;
49*4882a593Smuzhiyun
50*4882a593Smuzhiyun /* mini view of VC */
51*4882a593Smuzhiyun static int vc_x, vc_y, lastvc_x, lastvc_y;
52*4882a593Smuzhiyun
53*4882a593Smuzhiyun /* show console ? (or show VC) */
54*4882a593Smuzhiyun static int console_show = 1;
55*4882a593Smuzhiyun /* pending newline ? */
56*4882a593Smuzhiyun static int console_newline = 1;
57*4882a593Smuzhiyun static int lastVC = -1;
58*4882a593Smuzhiyun
59*4882a593Smuzhiyun static struct console *braille_co;
60*4882a593Smuzhiyun
61*4882a593Smuzhiyun /* Very VisioBraille-specific */
braille_write(u16 * buf)62*4882a593Smuzhiyun static void braille_write(u16 *buf)
63*4882a593Smuzhiyun {
64*4882a593Smuzhiyun static u16 lastwrite[WIDTH];
65*4882a593Smuzhiyun unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c;
66*4882a593Smuzhiyun u16 out;
67*4882a593Smuzhiyun int i;
68*4882a593Smuzhiyun
69*4882a593Smuzhiyun if (!braille_co)
70*4882a593Smuzhiyun return;
71*4882a593Smuzhiyun
72*4882a593Smuzhiyun if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf)))
73*4882a593Smuzhiyun return;
74*4882a593Smuzhiyun memcpy(lastwrite, buf, WIDTH * sizeof(*buf));
75*4882a593Smuzhiyun
76*4882a593Smuzhiyun #define SOH 1
77*4882a593Smuzhiyun #define STX 2
78*4882a593Smuzhiyun #define ETX 2
79*4882a593Smuzhiyun #define EOT 4
80*4882a593Smuzhiyun #define ENQ 5
81*4882a593Smuzhiyun data[0] = STX;
82*4882a593Smuzhiyun data[1] = '>';
83*4882a593Smuzhiyun csum ^= '>';
84*4882a593Smuzhiyun c = &data[2];
85*4882a593Smuzhiyun for (i = 0; i < WIDTH; i++) {
86*4882a593Smuzhiyun out = buf[i];
87*4882a593Smuzhiyun if (out >= 0x100)
88*4882a593Smuzhiyun out = '?';
89*4882a593Smuzhiyun else if (out == 0x00)
90*4882a593Smuzhiyun out = ' ';
91*4882a593Smuzhiyun csum ^= out;
92*4882a593Smuzhiyun if (out <= 0x05) {
93*4882a593Smuzhiyun *c++ = SOH;
94*4882a593Smuzhiyun out |= 0x40;
95*4882a593Smuzhiyun }
96*4882a593Smuzhiyun *c++ = out;
97*4882a593Smuzhiyun }
98*4882a593Smuzhiyun
99*4882a593Smuzhiyun if (csum <= 0x05) {
100*4882a593Smuzhiyun *c++ = SOH;
101*4882a593Smuzhiyun csum |= 0x40;
102*4882a593Smuzhiyun }
103*4882a593Smuzhiyun *c++ = csum;
104*4882a593Smuzhiyun *c++ = ETX;
105*4882a593Smuzhiyun
106*4882a593Smuzhiyun braille_co->write(braille_co, data, c - data);
107*4882a593Smuzhiyun }
108*4882a593Smuzhiyun
109*4882a593Smuzhiyun /* Follow the VC cursor*/
vc_follow_cursor(struct vc_data * vc)110*4882a593Smuzhiyun static void vc_follow_cursor(struct vc_data *vc)
111*4882a593Smuzhiyun {
112*4882a593Smuzhiyun vc_x = vc->state.x - (vc->state.x % WIDTH);
113*4882a593Smuzhiyun vc_y = vc->state.y;
114*4882a593Smuzhiyun lastvc_x = vc->state.x;
115*4882a593Smuzhiyun lastvc_y = vc->state.y;
116*4882a593Smuzhiyun }
117*4882a593Smuzhiyun
118*4882a593Smuzhiyun /* Maybe the VC cursor moved, if so follow it */
vc_maybe_cursor_moved(struct vc_data * vc)119*4882a593Smuzhiyun static void vc_maybe_cursor_moved(struct vc_data *vc)
120*4882a593Smuzhiyun {
121*4882a593Smuzhiyun if (vc->state.x != lastvc_x || vc->state.y != lastvc_y)
122*4882a593Smuzhiyun vc_follow_cursor(vc);
123*4882a593Smuzhiyun }
124*4882a593Smuzhiyun
125*4882a593Smuzhiyun /* Show portion of VC at vc_x, vc_y */
vc_refresh(struct vc_data * vc)126*4882a593Smuzhiyun static void vc_refresh(struct vc_data *vc)
127*4882a593Smuzhiyun {
128*4882a593Smuzhiyun u16 buf[WIDTH];
129*4882a593Smuzhiyun int i;
130*4882a593Smuzhiyun
131*4882a593Smuzhiyun for (i = 0; i < WIDTH; i++) {
132*4882a593Smuzhiyun u16 glyph = screen_glyph(vc,
133*4882a593Smuzhiyun 2 * (vc_x + i) + vc_y * vc->vc_size_row);
134*4882a593Smuzhiyun buf[i] = inverse_translate(vc, glyph, 1);
135*4882a593Smuzhiyun }
136*4882a593Smuzhiyun braille_write(buf);
137*4882a593Smuzhiyun }
138*4882a593Smuzhiyun
139*4882a593Smuzhiyun /*
140*4882a593Smuzhiyun * Link to keyboard
141*4882a593Smuzhiyun */
142*4882a593Smuzhiyun
keyboard_notifier_call(struct notifier_block * blk,unsigned long code,void * _param)143*4882a593Smuzhiyun static int keyboard_notifier_call(struct notifier_block *blk,
144*4882a593Smuzhiyun unsigned long code, void *_param)
145*4882a593Smuzhiyun {
146*4882a593Smuzhiyun struct keyboard_notifier_param *param = _param;
147*4882a593Smuzhiyun struct vc_data *vc = param->vc;
148*4882a593Smuzhiyun int ret = NOTIFY_OK;
149*4882a593Smuzhiyun
150*4882a593Smuzhiyun if (!param->down)
151*4882a593Smuzhiyun return ret;
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun switch (code) {
154*4882a593Smuzhiyun case KBD_KEYCODE:
155*4882a593Smuzhiyun if (console_show) {
156*4882a593Smuzhiyun if (param->value == BRAILLE_KEY) {
157*4882a593Smuzhiyun console_show = 0;
158*4882a593Smuzhiyun beep(880);
159*4882a593Smuzhiyun vc_maybe_cursor_moved(vc);
160*4882a593Smuzhiyun vc_refresh(vc);
161*4882a593Smuzhiyun ret = NOTIFY_STOP;
162*4882a593Smuzhiyun }
163*4882a593Smuzhiyun } else {
164*4882a593Smuzhiyun ret = NOTIFY_STOP;
165*4882a593Smuzhiyun switch (param->value) {
166*4882a593Smuzhiyun case KEY_INSERT:
167*4882a593Smuzhiyun beep(440);
168*4882a593Smuzhiyun console_show = 1;
169*4882a593Smuzhiyun lastVC = -1;
170*4882a593Smuzhiyun braille_write(console_buf);
171*4882a593Smuzhiyun break;
172*4882a593Smuzhiyun case KEY_LEFT:
173*4882a593Smuzhiyun if (vc_x > 0) {
174*4882a593Smuzhiyun vc_x -= WIDTH;
175*4882a593Smuzhiyun if (vc_x < 0)
176*4882a593Smuzhiyun vc_x = 0;
177*4882a593Smuzhiyun } else if (vc_y >= 1) {
178*4882a593Smuzhiyun beep(880);
179*4882a593Smuzhiyun vc_y--;
180*4882a593Smuzhiyun vc_x = vc->vc_cols-WIDTH;
181*4882a593Smuzhiyun } else
182*4882a593Smuzhiyun beep(220);
183*4882a593Smuzhiyun break;
184*4882a593Smuzhiyun case KEY_RIGHT:
185*4882a593Smuzhiyun if (vc_x + WIDTH < vc->vc_cols) {
186*4882a593Smuzhiyun vc_x += WIDTH;
187*4882a593Smuzhiyun } else if (vc_y + 1 < vc->vc_rows) {
188*4882a593Smuzhiyun beep(880);
189*4882a593Smuzhiyun vc_y++;
190*4882a593Smuzhiyun vc_x = 0;
191*4882a593Smuzhiyun } else
192*4882a593Smuzhiyun beep(220);
193*4882a593Smuzhiyun break;
194*4882a593Smuzhiyun case KEY_DOWN:
195*4882a593Smuzhiyun if (vc_y + 1 < vc->vc_rows)
196*4882a593Smuzhiyun vc_y++;
197*4882a593Smuzhiyun else
198*4882a593Smuzhiyun beep(220);
199*4882a593Smuzhiyun break;
200*4882a593Smuzhiyun case KEY_UP:
201*4882a593Smuzhiyun if (vc_y >= 1)
202*4882a593Smuzhiyun vc_y--;
203*4882a593Smuzhiyun else
204*4882a593Smuzhiyun beep(220);
205*4882a593Smuzhiyun break;
206*4882a593Smuzhiyun case KEY_HOME:
207*4882a593Smuzhiyun vc_follow_cursor(vc);
208*4882a593Smuzhiyun break;
209*4882a593Smuzhiyun case KEY_PAGEUP:
210*4882a593Smuzhiyun vc_x = 0;
211*4882a593Smuzhiyun vc_y = 0;
212*4882a593Smuzhiyun break;
213*4882a593Smuzhiyun case KEY_PAGEDOWN:
214*4882a593Smuzhiyun vc_x = 0;
215*4882a593Smuzhiyun vc_y = vc->vc_rows-1;
216*4882a593Smuzhiyun break;
217*4882a593Smuzhiyun default:
218*4882a593Smuzhiyun ret = NOTIFY_OK;
219*4882a593Smuzhiyun break;
220*4882a593Smuzhiyun }
221*4882a593Smuzhiyun if (ret == NOTIFY_STOP)
222*4882a593Smuzhiyun vc_refresh(vc);
223*4882a593Smuzhiyun }
224*4882a593Smuzhiyun break;
225*4882a593Smuzhiyun case KBD_POST_KEYSYM:
226*4882a593Smuzhiyun {
227*4882a593Smuzhiyun unsigned char type = KTYP(param->value) - 0xf0;
228*4882a593Smuzhiyun if (type == KT_SPEC) {
229*4882a593Smuzhiyun unsigned char val = KVAL(param->value);
230*4882a593Smuzhiyun int on_off = -1;
231*4882a593Smuzhiyun
232*4882a593Smuzhiyun switch (val) {
233*4882a593Smuzhiyun case KVAL(K_CAPS):
234*4882a593Smuzhiyun on_off = vt_get_leds(fg_console, VC_CAPSLOCK);
235*4882a593Smuzhiyun break;
236*4882a593Smuzhiyun case KVAL(K_NUM):
237*4882a593Smuzhiyun on_off = vt_get_leds(fg_console, VC_NUMLOCK);
238*4882a593Smuzhiyun break;
239*4882a593Smuzhiyun case KVAL(K_HOLD):
240*4882a593Smuzhiyun on_off = vt_get_leds(fg_console, VC_SCROLLOCK);
241*4882a593Smuzhiyun break;
242*4882a593Smuzhiyun }
243*4882a593Smuzhiyun if (on_off == 1)
244*4882a593Smuzhiyun beep(880);
245*4882a593Smuzhiyun else if (on_off == 0)
246*4882a593Smuzhiyun beep(440);
247*4882a593Smuzhiyun }
248*4882a593Smuzhiyun }
249*4882a593Smuzhiyun case KBD_UNBOUND_KEYCODE:
250*4882a593Smuzhiyun case KBD_UNICODE:
251*4882a593Smuzhiyun case KBD_KEYSYM:
252*4882a593Smuzhiyun /* Unused */
253*4882a593Smuzhiyun break;
254*4882a593Smuzhiyun }
255*4882a593Smuzhiyun return ret;
256*4882a593Smuzhiyun }
257*4882a593Smuzhiyun
258*4882a593Smuzhiyun static struct notifier_block keyboard_notifier_block = {
259*4882a593Smuzhiyun .notifier_call = keyboard_notifier_call,
260*4882a593Smuzhiyun };
261*4882a593Smuzhiyun
vt_notifier_call(struct notifier_block * blk,unsigned long code,void * _param)262*4882a593Smuzhiyun static int vt_notifier_call(struct notifier_block *blk,
263*4882a593Smuzhiyun unsigned long code, void *_param)
264*4882a593Smuzhiyun {
265*4882a593Smuzhiyun struct vt_notifier_param *param = _param;
266*4882a593Smuzhiyun struct vc_data *vc = param->vc;
267*4882a593Smuzhiyun switch (code) {
268*4882a593Smuzhiyun case VT_ALLOCATE:
269*4882a593Smuzhiyun break;
270*4882a593Smuzhiyun case VT_DEALLOCATE:
271*4882a593Smuzhiyun break;
272*4882a593Smuzhiyun case VT_WRITE:
273*4882a593Smuzhiyun {
274*4882a593Smuzhiyun unsigned char c = param->c;
275*4882a593Smuzhiyun if (vc->vc_num != fg_console)
276*4882a593Smuzhiyun break;
277*4882a593Smuzhiyun switch (c) {
278*4882a593Smuzhiyun case '\b':
279*4882a593Smuzhiyun case 127:
280*4882a593Smuzhiyun if (console_cursor > 0) {
281*4882a593Smuzhiyun console_cursor--;
282*4882a593Smuzhiyun console_buf[console_cursor] = ' ';
283*4882a593Smuzhiyun }
284*4882a593Smuzhiyun break;
285*4882a593Smuzhiyun case '\n':
286*4882a593Smuzhiyun case '\v':
287*4882a593Smuzhiyun case '\f':
288*4882a593Smuzhiyun case '\r':
289*4882a593Smuzhiyun console_newline = 1;
290*4882a593Smuzhiyun break;
291*4882a593Smuzhiyun case '\t':
292*4882a593Smuzhiyun c = ' ';
293*4882a593Smuzhiyun fallthrough;
294*4882a593Smuzhiyun default:
295*4882a593Smuzhiyun if (c < 32)
296*4882a593Smuzhiyun /* Ignore other control sequences */
297*4882a593Smuzhiyun break;
298*4882a593Smuzhiyun if (console_newline) {
299*4882a593Smuzhiyun memset(console_buf, 0, sizeof(console_buf));
300*4882a593Smuzhiyun console_cursor = 0;
301*4882a593Smuzhiyun console_newline = 0;
302*4882a593Smuzhiyun }
303*4882a593Smuzhiyun if (console_cursor == WIDTH)
304*4882a593Smuzhiyun memmove(console_buf, &console_buf[1],
305*4882a593Smuzhiyun (WIDTH-1) * sizeof(*console_buf));
306*4882a593Smuzhiyun else
307*4882a593Smuzhiyun console_cursor++;
308*4882a593Smuzhiyun console_buf[console_cursor-1] = c;
309*4882a593Smuzhiyun break;
310*4882a593Smuzhiyun }
311*4882a593Smuzhiyun if (console_show)
312*4882a593Smuzhiyun braille_write(console_buf);
313*4882a593Smuzhiyun else {
314*4882a593Smuzhiyun vc_maybe_cursor_moved(vc);
315*4882a593Smuzhiyun vc_refresh(vc);
316*4882a593Smuzhiyun }
317*4882a593Smuzhiyun break;
318*4882a593Smuzhiyun }
319*4882a593Smuzhiyun case VT_UPDATE:
320*4882a593Smuzhiyun /* Maybe a VT switch, flush */
321*4882a593Smuzhiyun if (console_show) {
322*4882a593Smuzhiyun if (vc->vc_num != lastVC) {
323*4882a593Smuzhiyun lastVC = vc->vc_num;
324*4882a593Smuzhiyun memset(console_buf, 0, sizeof(console_buf));
325*4882a593Smuzhiyun console_cursor = 0;
326*4882a593Smuzhiyun braille_write(console_buf);
327*4882a593Smuzhiyun }
328*4882a593Smuzhiyun } else {
329*4882a593Smuzhiyun vc_maybe_cursor_moved(vc);
330*4882a593Smuzhiyun vc_refresh(vc);
331*4882a593Smuzhiyun }
332*4882a593Smuzhiyun break;
333*4882a593Smuzhiyun }
334*4882a593Smuzhiyun return NOTIFY_OK;
335*4882a593Smuzhiyun }
336*4882a593Smuzhiyun
337*4882a593Smuzhiyun static struct notifier_block vt_notifier_block = {
338*4882a593Smuzhiyun .notifier_call = vt_notifier_call,
339*4882a593Smuzhiyun };
340*4882a593Smuzhiyun
341*4882a593Smuzhiyun /*
342*4882a593Smuzhiyun * Called from printk.c when console=brl is given
343*4882a593Smuzhiyun */
344*4882a593Smuzhiyun
braille_register_console(struct console * console,int index,char * console_options,char * braille_options)345*4882a593Smuzhiyun int braille_register_console(struct console *console, int index,
346*4882a593Smuzhiyun char *console_options, char *braille_options)
347*4882a593Smuzhiyun {
348*4882a593Smuzhiyun int ret;
349*4882a593Smuzhiyun
350*4882a593Smuzhiyun if (!console_options)
351*4882a593Smuzhiyun /* Only support VisioBraille for now */
352*4882a593Smuzhiyun console_options = "57600o8";
353*4882a593Smuzhiyun if (braille_co)
354*4882a593Smuzhiyun return -ENODEV;
355*4882a593Smuzhiyun if (console->setup) {
356*4882a593Smuzhiyun ret = console->setup(console, console_options);
357*4882a593Smuzhiyun if (ret != 0)
358*4882a593Smuzhiyun return ret;
359*4882a593Smuzhiyun }
360*4882a593Smuzhiyun console->flags |= CON_ENABLED;
361*4882a593Smuzhiyun console->index = index;
362*4882a593Smuzhiyun braille_co = console;
363*4882a593Smuzhiyun register_keyboard_notifier(&keyboard_notifier_block);
364*4882a593Smuzhiyun register_vt_notifier(&vt_notifier_block);
365*4882a593Smuzhiyun return 1;
366*4882a593Smuzhiyun }
367*4882a593Smuzhiyun
braille_unregister_console(struct console * console)368*4882a593Smuzhiyun int braille_unregister_console(struct console *console)
369*4882a593Smuzhiyun {
370*4882a593Smuzhiyun if (braille_co != console)
371*4882a593Smuzhiyun return -EINVAL;
372*4882a593Smuzhiyun unregister_keyboard_notifier(&keyboard_notifier_block);
373*4882a593Smuzhiyun unregister_vt_notifier(&vt_notifier_block);
374*4882a593Smuzhiyun braille_co = NULL;
375*4882a593Smuzhiyun return 1;
376*4882a593Smuzhiyun }
377