xref: /OK3568_Linux_fs/kernel/drivers/accessibility/braille/braille_console.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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