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