xref: /linux/drivers/accessibility/braille/braille_console.c (revision c83e3d2912432107148cc5e288ddd153a84c0f43)
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 
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 */
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*/
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 */
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 */
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 
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 
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 
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 
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