162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Minimalistic braille device kernel support.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * By default, shows console messages on the braille device.
662306a36Sopenharmony_ci * Pressing Insert switches to VC browsing.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci *  Copyright (C) Samuel Thibault <samuel.thibault@ens-lyon.org>
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/moduleparam.h>
1462306a36Sopenharmony_ci#include <linux/console.h>
1562306a36Sopenharmony_ci#include <linux/notifier.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <linux/selection.h>
1862306a36Sopenharmony_ci#include <linux/vt_kern.h>
1962306a36Sopenharmony_ci#include <linux/consolemap.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <linux/keyboard.h>
2262306a36Sopenharmony_ci#include <linux/kbd_kern.h>
2362306a36Sopenharmony_ci#include <linux/input.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ciMODULE_AUTHOR("samuel.thibault@ens-lyon.org");
2662306a36Sopenharmony_ciMODULE_DESCRIPTION("braille device");
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci/*
2962306a36Sopenharmony_ci * Braille device support part.
3062306a36Sopenharmony_ci */
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci/* Emit various sounds */
3362306a36Sopenharmony_cistatic bool sound;
3462306a36Sopenharmony_cimodule_param(sound, bool, 0);
3562306a36Sopenharmony_ciMODULE_PARM_DESC(sound, "emit sounds");
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic void beep(unsigned int freq)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	if (sound)
4062306a36Sopenharmony_ci		kd_mksound(freq, HZ/10);
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/* mini console */
4462306a36Sopenharmony_ci#define WIDTH 40
4562306a36Sopenharmony_ci#define BRAILLE_KEY KEY_INSERT
4662306a36Sopenharmony_cistatic u16 console_buf[WIDTH];
4762306a36Sopenharmony_cistatic int console_cursor;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci/* mini view of VC */
5062306a36Sopenharmony_cistatic int vc_x, vc_y, lastvc_x, lastvc_y;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci/* show console ? (or show VC) */
5362306a36Sopenharmony_cistatic int console_show = 1;
5462306a36Sopenharmony_ci/* pending newline ? */
5562306a36Sopenharmony_cistatic int console_newline = 1;
5662306a36Sopenharmony_cistatic int lastVC = -1;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic struct console *braille_co;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci/* Very VisioBraille-specific */
6162306a36Sopenharmony_cistatic void braille_write(u16 *buf)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	static u16 lastwrite[WIDTH];
6462306a36Sopenharmony_ci	unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c;
6562306a36Sopenharmony_ci	u16 out;
6662306a36Sopenharmony_ci	int i;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (!braille_co)
6962306a36Sopenharmony_ci		return;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf)))
7262306a36Sopenharmony_ci		return;
7362306a36Sopenharmony_ci	memcpy(lastwrite, buf, WIDTH * sizeof(*buf));
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci#define SOH 1
7662306a36Sopenharmony_ci#define STX 2
7762306a36Sopenharmony_ci#define ETX 2
7862306a36Sopenharmony_ci#define EOT 4
7962306a36Sopenharmony_ci#define ENQ 5
8062306a36Sopenharmony_ci	data[0] = STX;
8162306a36Sopenharmony_ci	data[1] = '>';
8262306a36Sopenharmony_ci	csum ^= '>';
8362306a36Sopenharmony_ci	c = &data[2];
8462306a36Sopenharmony_ci	for (i = 0; i < WIDTH; i++) {
8562306a36Sopenharmony_ci		out = buf[i];
8662306a36Sopenharmony_ci		if (out >= 0x100)
8762306a36Sopenharmony_ci			out = '?';
8862306a36Sopenharmony_ci		else if (out == 0x00)
8962306a36Sopenharmony_ci			out = ' ';
9062306a36Sopenharmony_ci		csum ^= out;
9162306a36Sopenharmony_ci		if (out <= 0x05) {
9262306a36Sopenharmony_ci			*c++ = SOH;
9362306a36Sopenharmony_ci			out |= 0x40;
9462306a36Sopenharmony_ci		}
9562306a36Sopenharmony_ci		*c++ = out;
9662306a36Sopenharmony_ci	}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if (csum <= 0x05) {
9962306a36Sopenharmony_ci		*c++ = SOH;
10062306a36Sopenharmony_ci		csum |= 0x40;
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci	*c++ = csum;
10362306a36Sopenharmony_ci	*c++ = ETX;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	braille_co->write(braille_co, data, c - data);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci/* Follow the VC cursor*/
10962306a36Sopenharmony_cistatic void vc_follow_cursor(struct vc_data *vc)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	vc_x = vc->state.x - (vc->state.x % WIDTH);
11262306a36Sopenharmony_ci	vc_y = vc->state.y;
11362306a36Sopenharmony_ci	lastvc_x = vc->state.x;
11462306a36Sopenharmony_ci	lastvc_y = vc->state.y;
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci/* Maybe the VC cursor moved, if so follow it */
11862306a36Sopenharmony_cistatic void vc_maybe_cursor_moved(struct vc_data *vc)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	if (vc->state.x != lastvc_x || vc->state.y != lastvc_y)
12162306a36Sopenharmony_ci		vc_follow_cursor(vc);
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci/* Show portion of VC at vc_x, vc_y */
12562306a36Sopenharmony_cistatic void vc_refresh(struct vc_data *vc)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	u16 buf[WIDTH];
12862306a36Sopenharmony_ci	int i;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	for (i = 0; i < WIDTH; i++) {
13162306a36Sopenharmony_ci		u16 glyph = screen_glyph(vc,
13262306a36Sopenharmony_ci				2 * (vc_x + i) + vc_y * vc->vc_size_row);
13362306a36Sopenharmony_ci		buf[i] = inverse_translate(vc, glyph, true);
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci	braille_write(buf);
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci/*
13962306a36Sopenharmony_ci * Link to keyboard
14062306a36Sopenharmony_ci */
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic int keyboard_notifier_call(struct notifier_block *blk,
14362306a36Sopenharmony_ci				  unsigned long code, void *_param)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	struct keyboard_notifier_param *param = _param;
14662306a36Sopenharmony_ci	struct vc_data *vc = param->vc;
14762306a36Sopenharmony_ci	int ret = NOTIFY_OK;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	if (!param->down)
15062306a36Sopenharmony_ci		return ret;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	switch (code) {
15362306a36Sopenharmony_ci	case KBD_KEYCODE:
15462306a36Sopenharmony_ci		if (console_show) {
15562306a36Sopenharmony_ci			if (param->value == BRAILLE_KEY) {
15662306a36Sopenharmony_ci				console_show = 0;
15762306a36Sopenharmony_ci				beep(880);
15862306a36Sopenharmony_ci				vc_maybe_cursor_moved(vc);
15962306a36Sopenharmony_ci				vc_refresh(vc);
16062306a36Sopenharmony_ci				ret = NOTIFY_STOP;
16162306a36Sopenharmony_ci			}
16262306a36Sopenharmony_ci		} else {
16362306a36Sopenharmony_ci			ret = NOTIFY_STOP;
16462306a36Sopenharmony_ci			switch (param->value) {
16562306a36Sopenharmony_ci			case KEY_INSERT:
16662306a36Sopenharmony_ci				beep(440);
16762306a36Sopenharmony_ci				console_show = 1;
16862306a36Sopenharmony_ci				lastVC = -1;
16962306a36Sopenharmony_ci				braille_write(console_buf);
17062306a36Sopenharmony_ci				break;
17162306a36Sopenharmony_ci			case KEY_LEFT:
17262306a36Sopenharmony_ci				if (vc_x > 0) {
17362306a36Sopenharmony_ci					vc_x -= WIDTH;
17462306a36Sopenharmony_ci					if (vc_x < 0)
17562306a36Sopenharmony_ci						vc_x = 0;
17662306a36Sopenharmony_ci				} else if (vc_y >= 1) {
17762306a36Sopenharmony_ci					beep(880);
17862306a36Sopenharmony_ci					vc_y--;
17962306a36Sopenharmony_ci					vc_x = vc->vc_cols-WIDTH;
18062306a36Sopenharmony_ci				} else
18162306a36Sopenharmony_ci					beep(220);
18262306a36Sopenharmony_ci				break;
18362306a36Sopenharmony_ci			case KEY_RIGHT:
18462306a36Sopenharmony_ci				if (vc_x + WIDTH < vc->vc_cols) {
18562306a36Sopenharmony_ci					vc_x += WIDTH;
18662306a36Sopenharmony_ci				} else if (vc_y + 1 < vc->vc_rows) {
18762306a36Sopenharmony_ci					beep(880);
18862306a36Sopenharmony_ci					vc_y++;
18962306a36Sopenharmony_ci					vc_x = 0;
19062306a36Sopenharmony_ci				} else
19162306a36Sopenharmony_ci					beep(220);
19262306a36Sopenharmony_ci				break;
19362306a36Sopenharmony_ci			case KEY_DOWN:
19462306a36Sopenharmony_ci				if (vc_y + 1 < vc->vc_rows)
19562306a36Sopenharmony_ci					vc_y++;
19662306a36Sopenharmony_ci				else
19762306a36Sopenharmony_ci					beep(220);
19862306a36Sopenharmony_ci				break;
19962306a36Sopenharmony_ci			case KEY_UP:
20062306a36Sopenharmony_ci				if (vc_y >= 1)
20162306a36Sopenharmony_ci					vc_y--;
20262306a36Sopenharmony_ci				else
20362306a36Sopenharmony_ci					beep(220);
20462306a36Sopenharmony_ci				break;
20562306a36Sopenharmony_ci			case KEY_HOME:
20662306a36Sopenharmony_ci				vc_follow_cursor(vc);
20762306a36Sopenharmony_ci				break;
20862306a36Sopenharmony_ci			case KEY_PAGEUP:
20962306a36Sopenharmony_ci				vc_x = 0;
21062306a36Sopenharmony_ci				vc_y = 0;
21162306a36Sopenharmony_ci				break;
21262306a36Sopenharmony_ci			case KEY_PAGEDOWN:
21362306a36Sopenharmony_ci				vc_x = 0;
21462306a36Sopenharmony_ci				vc_y = vc->vc_rows-1;
21562306a36Sopenharmony_ci				break;
21662306a36Sopenharmony_ci			default:
21762306a36Sopenharmony_ci				ret = NOTIFY_OK;
21862306a36Sopenharmony_ci				break;
21962306a36Sopenharmony_ci			}
22062306a36Sopenharmony_ci			if (ret == NOTIFY_STOP)
22162306a36Sopenharmony_ci				vc_refresh(vc);
22262306a36Sopenharmony_ci		}
22362306a36Sopenharmony_ci		break;
22462306a36Sopenharmony_ci	case KBD_POST_KEYSYM:
22562306a36Sopenharmony_ci	{
22662306a36Sopenharmony_ci		unsigned char type = KTYP(param->value) - 0xf0;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci		if (type == KT_SPEC) {
22962306a36Sopenharmony_ci			unsigned char val = KVAL(param->value);
23062306a36Sopenharmony_ci			int on_off = -1;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci			switch (val) {
23362306a36Sopenharmony_ci			case KVAL(K_CAPS):
23462306a36Sopenharmony_ci				on_off = vt_get_leds(fg_console, VC_CAPSLOCK);
23562306a36Sopenharmony_ci				break;
23662306a36Sopenharmony_ci			case KVAL(K_NUM):
23762306a36Sopenharmony_ci				on_off = vt_get_leds(fg_console, VC_NUMLOCK);
23862306a36Sopenharmony_ci				break;
23962306a36Sopenharmony_ci			case KVAL(K_HOLD):
24062306a36Sopenharmony_ci				on_off = vt_get_leds(fg_console, VC_SCROLLOCK);
24162306a36Sopenharmony_ci				break;
24262306a36Sopenharmony_ci			}
24362306a36Sopenharmony_ci			if (on_off == 1)
24462306a36Sopenharmony_ci				beep(880);
24562306a36Sopenharmony_ci			else if (on_off == 0)
24662306a36Sopenharmony_ci				beep(440);
24762306a36Sopenharmony_ci		}
24862306a36Sopenharmony_ci	}
24962306a36Sopenharmony_ci		break;
25062306a36Sopenharmony_ci	case KBD_UNBOUND_KEYCODE:
25162306a36Sopenharmony_ci	case KBD_UNICODE:
25262306a36Sopenharmony_ci	case KBD_KEYSYM:
25362306a36Sopenharmony_ci		/* Unused */
25462306a36Sopenharmony_ci		break;
25562306a36Sopenharmony_ci	}
25662306a36Sopenharmony_ci	return ret;
25762306a36Sopenharmony_ci}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cistatic struct notifier_block keyboard_notifier_block = {
26062306a36Sopenharmony_ci	.notifier_call = keyboard_notifier_call,
26162306a36Sopenharmony_ci};
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_cistatic int vt_notifier_call(struct notifier_block *blk,
26462306a36Sopenharmony_ci			    unsigned long code, void *_param)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	struct vt_notifier_param *param = _param;
26762306a36Sopenharmony_ci	struct vc_data *vc = param->vc;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	switch (code) {
27062306a36Sopenharmony_ci	case VT_ALLOCATE:
27162306a36Sopenharmony_ci		break;
27262306a36Sopenharmony_ci	case VT_DEALLOCATE:
27362306a36Sopenharmony_ci		break;
27462306a36Sopenharmony_ci	case VT_WRITE:
27562306a36Sopenharmony_ci	{
27662306a36Sopenharmony_ci		unsigned char c = param->c;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci		if (vc->vc_num != fg_console)
27962306a36Sopenharmony_ci			break;
28062306a36Sopenharmony_ci		switch (c) {
28162306a36Sopenharmony_ci		case '\b':
28262306a36Sopenharmony_ci		case 127:
28362306a36Sopenharmony_ci			if (console_cursor > 0) {
28462306a36Sopenharmony_ci				console_cursor--;
28562306a36Sopenharmony_ci				console_buf[console_cursor] = ' ';
28662306a36Sopenharmony_ci			}
28762306a36Sopenharmony_ci			break;
28862306a36Sopenharmony_ci		case '\n':
28962306a36Sopenharmony_ci		case '\v':
29062306a36Sopenharmony_ci		case '\f':
29162306a36Sopenharmony_ci		case '\r':
29262306a36Sopenharmony_ci			console_newline = 1;
29362306a36Sopenharmony_ci			break;
29462306a36Sopenharmony_ci		case '\t':
29562306a36Sopenharmony_ci			c = ' ';
29662306a36Sopenharmony_ci			fallthrough;
29762306a36Sopenharmony_ci		default:
29862306a36Sopenharmony_ci			if (c < 32)
29962306a36Sopenharmony_ci				/* Ignore other control sequences */
30062306a36Sopenharmony_ci				break;
30162306a36Sopenharmony_ci			if (console_newline) {
30262306a36Sopenharmony_ci				memset(console_buf, 0, sizeof(console_buf));
30362306a36Sopenharmony_ci				console_cursor = 0;
30462306a36Sopenharmony_ci				console_newline = 0;
30562306a36Sopenharmony_ci			}
30662306a36Sopenharmony_ci			if (console_cursor == WIDTH)
30762306a36Sopenharmony_ci				memmove(console_buf, &console_buf[1],
30862306a36Sopenharmony_ci					(WIDTH-1) * sizeof(*console_buf));
30962306a36Sopenharmony_ci			else
31062306a36Sopenharmony_ci				console_cursor++;
31162306a36Sopenharmony_ci			console_buf[console_cursor-1] = c;
31262306a36Sopenharmony_ci			break;
31362306a36Sopenharmony_ci		}
31462306a36Sopenharmony_ci		if (console_show)
31562306a36Sopenharmony_ci			braille_write(console_buf);
31662306a36Sopenharmony_ci		else {
31762306a36Sopenharmony_ci			vc_maybe_cursor_moved(vc);
31862306a36Sopenharmony_ci			vc_refresh(vc);
31962306a36Sopenharmony_ci		}
32062306a36Sopenharmony_ci		break;
32162306a36Sopenharmony_ci	}
32262306a36Sopenharmony_ci	case VT_UPDATE:
32362306a36Sopenharmony_ci		/* Maybe a VT switch, flush */
32462306a36Sopenharmony_ci		if (console_show) {
32562306a36Sopenharmony_ci			if (vc->vc_num != lastVC) {
32662306a36Sopenharmony_ci				lastVC = vc->vc_num;
32762306a36Sopenharmony_ci				memset(console_buf, 0, sizeof(console_buf));
32862306a36Sopenharmony_ci				console_cursor = 0;
32962306a36Sopenharmony_ci				braille_write(console_buf);
33062306a36Sopenharmony_ci			}
33162306a36Sopenharmony_ci		} else {
33262306a36Sopenharmony_ci			vc_maybe_cursor_moved(vc);
33362306a36Sopenharmony_ci			vc_refresh(vc);
33462306a36Sopenharmony_ci		}
33562306a36Sopenharmony_ci		break;
33662306a36Sopenharmony_ci	}
33762306a36Sopenharmony_ci	return NOTIFY_OK;
33862306a36Sopenharmony_ci}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_cistatic struct notifier_block vt_notifier_block = {
34162306a36Sopenharmony_ci	.notifier_call = vt_notifier_call,
34262306a36Sopenharmony_ci};
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci/*
34562306a36Sopenharmony_ci * Called from printk.c when console=brl is given
34662306a36Sopenharmony_ci */
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ciint braille_register_console(struct console *console, int index,
34962306a36Sopenharmony_ci		char *console_options, char *braille_options)
35062306a36Sopenharmony_ci{
35162306a36Sopenharmony_ci	int ret;
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	if (!console_options)
35462306a36Sopenharmony_ci		/* Only support VisioBraille for now */
35562306a36Sopenharmony_ci		console_options = "57600o8";
35662306a36Sopenharmony_ci	if (braille_co)
35762306a36Sopenharmony_ci		return -ENODEV;
35862306a36Sopenharmony_ci	if (console->setup) {
35962306a36Sopenharmony_ci		ret = console->setup(console, console_options);
36062306a36Sopenharmony_ci		if (ret != 0)
36162306a36Sopenharmony_ci			return ret;
36262306a36Sopenharmony_ci	}
36362306a36Sopenharmony_ci	console->flags |= CON_ENABLED;
36462306a36Sopenharmony_ci	console->index = index;
36562306a36Sopenharmony_ci	braille_co = console;
36662306a36Sopenharmony_ci	register_keyboard_notifier(&keyboard_notifier_block);
36762306a36Sopenharmony_ci	register_vt_notifier(&vt_notifier_block);
36862306a36Sopenharmony_ci	return 1;
36962306a36Sopenharmony_ci}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ciint braille_unregister_console(struct console *console)
37262306a36Sopenharmony_ci{
37362306a36Sopenharmony_ci	if (braille_co != console)
37462306a36Sopenharmony_ci		return -EINVAL;
37562306a36Sopenharmony_ci	unregister_keyboard_notifier(&keyboard_notifier_block);
37662306a36Sopenharmony_ci	unregister_vt_notifier(&vt_notifier_block);
37762306a36Sopenharmony_ci	braille_co = NULL;
37862306a36Sopenharmony_ci	return 1;
37962306a36Sopenharmony_ci}
380