summaryrefslogtreecommitdiffstats
path: root/drivers/accessibility
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
commit5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch)
treea94efe259b9009378be6d90eb30d2b019d95c194 /drivers/accessibility
parentInitial commit. (diff)
downloadlinux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz
linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/accessibility')
-rw-r--r--drivers/accessibility/Kconfig36
-rw-r--r--drivers/accessibility/Makefile3
-rw-r--r--drivers/accessibility/braille/Makefile2
-rw-r--r--drivers/accessibility/braille/braille_console.c376
-rw-r--r--drivers/accessibility/speakup/DefaultKeyAssignments46
-rw-r--r--drivers/accessibility/speakup/Kconfig205
-rw-r--r--drivers/accessibility/speakup/Makefile32
-rw-r--r--drivers/accessibility/speakup/TODO22
-rw-r--r--drivers/accessibility/speakup/buffers.c124
-rw-r--r--drivers/accessibility/speakup/devsynth.c92
-rw-r--r--drivers/accessibility/speakup/fakekey.c87
-rw-r--r--drivers/accessibility/speakup/i18n.c625
-rw-r--r--drivers/accessibility/speakup/i18n.h235
-rw-r--r--drivers/accessibility/speakup/keyhelp.c209
-rw-r--r--drivers/accessibility/speakup/kobjects.c1056
-rw-r--r--drivers/accessibility/speakup/main.c2459
-rw-r--r--drivers/accessibility/speakup/selection.c141
-rw-r--r--drivers/accessibility/speakup/serialio.c318
-rw-r--r--drivers/accessibility/speakup/serialio.h41
-rw-r--r--drivers/accessibility/speakup/speakup.h120
-rw-r--r--drivers/accessibility/speakup/speakup_acnt.h19
-rw-r--r--drivers/accessibility/speakup/speakup_acntpc.c319
-rw-r--r--drivers/accessibility/speakup/speakup_acntsa.c144
-rw-r--r--drivers/accessibility/speakup/speakup_apollo.c208
-rw-r--r--drivers/accessibility/speakup/speakup_audptr.c171
-rw-r--r--drivers/accessibility/speakup/speakup_bns.c128
-rw-r--r--drivers/accessibility/speakup/speakup_decext.c240
-rw-r--r--drivers/accessibility/speakup/speakup_decpc.c495
-rw-r--r--drivers/accessibility/speakup/speakup_dectlk.c312
-rw-r--r--drivers/accessibility/speakup/speakup_dtlk.c390
-rw-r--r--drivers/accessibility/speakup/speakup_dtlk.h63
-rw-r--r--drivers/accessibility/speakup/speakup_dummy.c134
-rw-r--r--drivers/accessibility/speakup/speakup_keypc.c318
-rw-r--r--drivers/accessibility/speakup/speakup_ltlk.c175
-rw-r--r--drivers/accessibility/speakup/speakup_soft.c430
-rw-r--r--drivers/accessibility/speakup/speakup_spkout.c139
-rw-r--r--drivers/accessibility/speakup/speakup_txprt.c127
-rw-r--r--drivers/accessibility/speakup/speakupmap.h66
-rw-r--r--drivers/accessibility/speakup/speakupmap.map93
-rw-r--r--drivers/accessibility/speakup/spk_priv.h83
-rw-r--r--drivers/accessibility/speakup/spk_priv_keyinfo.h100
-rw-r--r--drivers/accessibility/speakup/spk_ttyio.c412
-rw-r--r--drivers/accessibility/speakup/spk_types.h226
-rw-r--r--drivers/accessibility/speakup/synth.c490
-rw-r--r--drivers/accessibility/speakup/thread.c62
-rw-r--r--drivers/accessibility/speakup/varhandlers.c339
46 files changed, 11912 insertions, 0 deletions
diff --git a/drivers/accessibility/Kconfig b/drivers/accessibility/Kconfig
new file mode 100644
index 000000000..6b2f79d1f
--- /dev/null
+++ b/drivers/accessibility/Kconfig
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0
+menuconfig ACCESSIBILITY
+ bool "Accessibility support"
+ help
+ Accessibility handles all special kinds of hardware devices or
+ software adapters which help people with disabilities (e.g.
+ blindness) to use computers.
+
+ That includes braille devices, speech synthesis, keyboard
+ remapping, etc.
+
+ Say Y here to get to see options for accessibility.
+ This option alone does not add any kernel code.
+
+ If you say N, all options in this submenu will be skipped and disabled.
+
+ If unsure, say N.
+
+if ACCESSIBILITY
+config A11Y_BRAILLE_CONSOLE
+ bool "Console on braille device"
+ depends on VT
+ depends on SERIAL_CORE_CONSOLE
+ help
+ Enables console output on a braille device connected to a 8250
+ serial port. For now only the VisioBraille device is supported.
+
+ To actually enable it, you need to pass option
+ console=brl,ttyS0
+ to the kernel. Options are the same as for serial console.
+
+ If unsure, say N.
+
+source "drivers/accessibility/speakup/Kconfig"
+
+endif # ACCESSIBILITY
diff --git a/drivers/accessibility/Makefile b/drivers/accessibility/Makefile
new file mode 100644
index 000000000..833603086
--- /dev/null
+++ b/drivers/accessibility/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-y += braille/
+obj-$(CONFIG_SPEAKUP) += speakup/
diff --git a/drivers/accessibility/braille/Makefile b/drivers/accessibility/braille/Makefile
new file mode 100644
index 000000000..fa4b87338
--- /dev/null
+++ b/drivers/accessibility/braille/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_A11Y_BRAILLE_CONSOLE) += braille_console.o
diff --git a/drivers/accessibility/braille/braille_console.c b/drivers/accessibility/braille/braille_console.c
new file mode 100644
index 000000000..9861302cc
--- /dev/null
+++ b/drivers/accessibility/braille/braille_console.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Minimalistic braille device kernel support.
+ *
+ * By default, shows console messages on the braille device.
+ * Pressing Insert switches to VC browsing.
+ *
+ * Copyright (C) Samuel Thibault <samuel.thibault@ens-lyon.org>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/console.h>
+#include <linux/notifier.h>
+
+#include <linux/selection.h>
+#include <linux/vt_kern.h>
+#include <linux/consolemap.h>
+
+#include <linux/keyboard.h>
+#include <linux/kbd_kern.h>
+#include <linux/input.h>
+
+MODULE_AUTHOR("samuel.thibault@ens-lyon.org");
+MODULE_DESCRIPTION("braille device");
+MODULE_LICENSE("GPL");
+
+/*
+ * Braille device support part.
+ */
+
+/* Emit various sounds */
+static bool sound;
+module_param(sound, bool, 0);
+MODULE_PARM_DESC(sound, "emit sounds");
+
+static void beep(unsigned int freq)
+{
+ if (sound)
+ kd_mksound(freq, HZ/10);
+}
+
+/* mini console */
+#define WIDTH 40
+#define BRAILLE_KEY KEY_INSERT
+static u16 console_buf[WIDTH];
+static int console_cursor;
+
+/* mini view of VC */
+static int vc_x, vc_y, lastvc_x, lastvc_y;
+
+/* show console ? (or show VC) */
+static int console_show = 1;
+/* pending newline ? */
+static int console_newline = 1;
+static int lastVC = -1;
+
+static struct console *braille_co;
+
+/* Very VisioBraille-specific */
+static void braille_write(u16 *buf)
+{
+ static u16 lastwrite[WIDTH];
+ unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c;
+ u16 out;
+ int i;
+
+ if (!braille_co)
+ return;
+
+ if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf)))
+ return;
+ memcpy(lastwrite, buf, WIDTH * sizeof(*buf));
+
+#define SOH 1
+#define STX 2
+#define ETX 2
+#define EOT 4
+#define ENQ 5
+ data[0] = STX;
+ data[1] = '>';
+ csum ^= '>';
+ c = &data[2];
+ for (i = 0; i < WIDTH; i++) {
+ out = buf[i];
+ if (out >= 0x100)
+ out = '?';
+ else if (out == 0x00)
+ out = ' ';
+ csum ^= out;
+ if (out <= 0x05) {
+ *c++ = SOH;
+ out |= 0x40;
+ }
+ *c++ = out;
+ }
+
+ if (csum <= 0x05) {
+ *c++ = SOH;
+ csum |= 0x40;
+ }
+ *c++ = csum;
+ *c++ = ETX;
+
+ braille_co->write(braille_co, data, c - data);
+}
+
+/* Follow the VC cursor*/
+static void vc_follow_cursor(struct vc_data *vc)
+{
+ vc_x = vc->state.x - (vc->state.x % WIDTH);
+ vc_y = vc->state.y;
+ lastvc_x = vc->state.x;
+ lastvc_y = vc->state.y;
+}
+
+/* Maybe the VC cursor moved, if so follow it */
+static void vc_maybe_cursor_moved(struct vc_data *vc)
+{
+ if (vc->state.x != lastvc_x || vc->state.y != lastvc_y)
+ vc_follow_cursor(vc);
+}
+
+/* Show portion of VC at vc_x, vc_y */
+static void vc_refresh(struct vc_data *vc)
+{
+ u16 buf[WIDTH];
+ int i;
+
+ for (i = 0; i < WIDTH; i++) {
+ u16 glyph = screen_glyph(vc,
+ 2 * (vc_x + i) + vc_y * vc->vc_size_row);
+ buf[i] = inverse_translate(vc, glyph, 1);
+ }
+ braille_write(buf);
+}
+
+/*
+ * Link to keyboard
+ */
+
+static int keyboard_notifier_call(struct notifier_block *blk,
+ unsigned long code, void *_param)
+{
+ struct keyboard_notifier_param *param = _param;
+ struct vc_data *vc = param->vc;
+ int ret = NOTIFY_OK;
+
+ if (!param->down)
+ return ret;
+
+ switch (code) {
+ case KBD_KEYCODE:
+ if (console_show) {
+ if (param->value == BRAILLE_KEY) {
+ console_show = 0;
+ beep(880);
+ vc_maybe_cursor_moved(vc);
+ vc_refresh(vc);
+ ret = NOTIFY_STOP;
+ }
+ } else {
+ ret = NOTIFY_STOP;
+ switch (param->value) {
+ case KEY_INSERT:
+ beep(440);
+ console_show = 1;
+ lastVC = -1;
+ braille_write(console_buf);
+ break;
+ case KEY_LEFT:
+ if (vc_x > 0) {
+ vc_x -= WIDTH;
+ if (vc_x < 0)
+ vc_x = 0;
+ } else if (vc_y >= 1) {
+ beep(880);
+ vc_y--;
+ vc_x = vc->vc_cols-WIDTH;
+ } else
+ beep(220);
+ break;
+ case KEY_RIGHT:
+ if (vc_x + WIDTH < vc->vc_cols) {
+ vc_x += WIDTH;
+ } else if (vc_y + 1 < vc->vc_rows) {
+ beep(880);
+ vc_y++;
+ vc_x = 0;
+ } else
+ beep(220);
+ break;
+ case KEY_DOWN:
+ if (vc_y + 1 < vc->vc_rows)
+ vc_y++;
+ else
+ beep(220);
+ break;
+ case KEY_UP:
+ if (vc_y >= 1)
+ vc_y--;
+ else
+ beep(220);
+ break;
+ case KEY_HOME:
+ vc_follow_cursor(vc);
+ break;
+ case KEY_PAGEUP:
+ vc_x = 0;
+ vc_y = 0;
+ break;
+ case KEY_PAGEDOWN:
+ vc_x = 0;
+ vc_y = vc->vc_rows-1;
+ break;
+ default:
+ ret = NOTIFY_OK;
+ break;
+ }
+ if (ret == NOTIFY_STOP)
+ vc_refresh(vc);
+ }
+ break;
+ case KBD_POST_KEYSYM:
+ {
+ unsigned char type = KTYP(param->value) - 0xf0;
+ if (type == KT_SPEC) {
+ unsigned char val = KVAL(param->value);
+ int on_off = -1;
+
+ switch (val) {
+ case KVAL(K_CAPS):
+ on_off = vt_get_leds(fg_console, VC_CAPSLOCK);
+ break;
+ case KVAL(K_NUM):
+ on_off = vt_get_leds(fg_console, VC_NUMLOCK);
+ break;
+ case KVAL(K_HOLD):
+ on_off = vt_get_leds(fg_console, VC_SCROLLOCK);
+ break;
+ }
+ if (on_off == 1)
+ beep(880);
+ else if (on_off == 0)
+ beep(440);
+ }
+ }
+ case KBD_UNBOUND_KEYCODE:
+ case KBD_UNICODE:
+ case KBD_KEYSYM:
+ /* Unused */
+ break;
+ }
+ return ret;
+}
+
+static struct notifier_block keyboard_notifier_block = {
+ .notifier_call = keyboard_notifier_call,
+};
+
+static int vt_notifier_call(struct notifier_block *blk,
+ unsigned long code, void *_param)
+{
+ struct vt_notifier_param *param = _param;
+ struct vc_data *vc = param->vc;
+ switch (code) {
+ case VT_ALLOCATE:
+ break;
+ case VT_DEALLOCATE:
+ break;
+ case VT_WRITE:
+ {
+ unsigned char c = param->c;
+ if (vc->vc_num != fg_console)
+ break;
+ switch (c) {
+ case '\b':
+ case 127:
+ if (console_cursor > 0) {
+ console_cursor--;
+ console_buf[console_cursor] = ' ';
+ }
+ break;
+ case '\n':
+ case '\v':
+ case '\f':
+ case '\r':
+ console_newline = 1;
+ break;
+ case '\t':
+ c = ' ';
+ fallthrough;
+ default:
+ if (c < 32)
+ /* Ignore other control sequences */
+ break;
+ if (console_newline) {
+ memset(console_buf, 0, sizeof(console_buf));
+ console_cursor = 0;
+ console_newline = 0;
+ }
+ if (console_cursor == WIDTH)
+ memmove(console_buf, &console_buf[1],
+ (WIDTH-1) * sizeof(*console_buf));
+ else
+ console_cursor++;
+ console_buf[console_cursor-1] = c;
+ break;
+ }
+ if (console_show)
+ braille_write(console_buf);
+ else {
+ vc_maybe_cursor_moved(vc);
+ vc_refresh(vc);
+ }
+ break;
+ }
+ case VT_UPDATE:
+ /* Maybe a VT switch, flush */
+ if (console_show) {
+ if (vc->vc_num != lastVC) {
+ lastVC = vc->vc_num;
+ memset(console_buf, 0, sizeof(console_buf));
+ console_cursor = 0;
+ braille_write(console_buf);
+ }
+ } else {
+ vc_maybe_cursor_moved(vc);
+ vc_refresh(vc);
+ }
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+static struct notifier_block vt_notifier_block = {
+ .notifier_call = vt_notifier_call,
+};
+
+/*
+ * Called from printk.c when console=brl is given
+ */
+
+int braille_register_console(struct console *console, int index,
+ char *console_options, char *braille_options)
+{
+ int ret;
+
+ if (!console_options)
+ /* Only support VisioBraille for now */
+ console_options = "57600o8";
+ if (braille_co)
+ return -ENODEV;
+ if (console->setup) {
+ ret = console->setup(console, console_options);
+ if (ret != 0)
+ return ret;
+ }
+ console->flags |= CON_ENABLED;
+ console->index = index;
+ braille_co = console;
+ register_keyboard_notifier(&keyboard_notifier_block);
+ register_vt_notifier(&vt_notifier_block);
+ return 1;
+}
+
+int braille_unregister_console(struct console *console)
+{
+ if (braille_co != console)
+ return -EINVAL;
+ unregister_keyboard_notifier(&keyboard_notifier_block);
+ unregister_vt_notifier(&vt_notifier_block);
+ braille_co = NULL;
+ return 1;
+}
diff --git a/drivers/accessibility/speakup/DefaultKeyAssignments b/drivers/accessibility/speakup/DefaultKeyAssignments
new file mode 100644
index 000000000..101c803b2
--- /dev/null
+++ b/drivers/accessibility/speakup/DefaultKeyAssignments
@@ -0,0 +1,46 @@
+This file is intended to give you an overview of the default keys used
+by speakup for it's review functions. You may change them to be
+anything you want but that will take some familiarity with key
+mapping.
+
+We have remapped the insert or zero key on the keypad to act as a
+shift key. Well, actually as an altgr key. So in the following list
+InsKeyPad-period means hold down the insert key like a shift key and
+hit the keypad period.
+
+KeyPad-8 Say current Line
+InsKeyPad-8 say from top of screen to reading cursor.
+KeyPad-7 Say Previous Line (UP one line)
+KeyPad-9 Say Next Line (down one line)
+KeyPad-5 Say Current Word
+InsKeyPad-5 Spell Current Word
+KeyPad-4 Say Previous Word (left one word)
+InsKeyPad-4 say from left edge of line to reading cursor.
+KeyPad-6 Say Next Word (right one word)
+InsKeyPad-6 Say from reading cursor to right edge of line.
+KeyPad-2 Say Current Letter
+InsKeyPad-2 say current letter phonetically
+KeyPad-1 Say Previous Character (left one letter)
+KeyPad-3 Say Next Character (right one letter)
+KeyPad-plus Say Entire Screen
+InsKeyPad-plus Say from reading cursor line to bottom of screen.
+KeyPad-Minus Park reading cursor (toggle)
+InsKeyPad-minus Say character hex and decimal value.
+KeyPad-period Say Position (current line, position and console)
+InsKeyPad-period say colour attributes of current position.
+InsKeyPad-9 Move reading cursor to top of screen (insert pgup)
+InsKeyPad-3 Move reading cursor to bottom of screen (insert pgdn)
+InsKeyPad-7 Move reading cursor to left edge of screen (insert home)
+InsKeyPad-1 Move reading cursor to right edge of screen (insert end)
+ControlKeyPad-1 Move reading cursor to last character on current line.
+KeyPad-Enter Shut Up (until another key is hit) and sync reading cursor
+InsKeyPad-Enter Shut Up (until toggled back on).
+InsKeyPad-star n<x|y> go to line (y) or column (x). Where 'n' is any
+ allowed value for the row or column for your current screen.
+KeyPad-/ Mark and Cut screen region.
+InsKeyPad-/ Paste screen region into any console.
+
+Hitting any key while speakup is outputting speech will quiet the
+synth until it has caught up with what is being printed on the
+console.
+
diff --git a/drivers/accessibility/speakup/Kconfig b/drivers/accessibility/speakup/Kconfig
new file mode 100644
index 000000000..07ecbbde0
--- /dev/null
+++ b/drivers/accessibility/speakup/Kconfig
@@ -0,0 +1,205 @@
+# SPDX-License-Identifier: GPL-2.0
+menu "Speakup console speech"
+
+config SPEAKUP
+ depends on VT
+ tristate "Speakup core"
+ help
+ This is the Speakup screen reader. Think of it as a
+ video console for blind people. If built in to the
+ kernel, it can speak everything on the text console from
+ boot up to shutdown. For more information on Speakup,
+ point your browser at <http://www.linux-speakup.org/>.
+ There is also a mailing list at the above url that you
+ can subscribe to.
+
+ Supported synthesizers are accent sa, accent pc,
+ appollo II., Auddapter, Braille 'n Speak, Dectalk
+ external (old), Dectalk PC (full length isa board),
+ Dectalk express, Doubletalk, Doubletalk LT or
+ Litetalk, Keynote Gold internal PC, software
+ synthesizers, Speakout, transport, and a dummy module
+ that can be used with a plain text terminal.
+
+ Speakup can either be built in or compiled as a module
+ by answering y or m. If you answer y here, then you
+ must answer either y or m to at least one of the
+ synthesizer drivers below. If you answer m here, then
+ the synthesizer drivers below can only be built as
+ modules.
+
+ These drivers are not standalone drivers, but must be
+ used in conjunction with Speakup. Think of them as
+ video cards for blind people.
+
+
+ The Dectalk pc driver can only be built as a module, and
+ requires software to be pre-loaded on to the card before
+ the module can be loaded. See the decpc choice below
+ for more details.
+
+ If you are not a blind person, or don't have access to
+ one of the listed synthesizers, you should say n.
+
+if SPEAKUP
+
+config SPEAKUP_SERIALIO
+ def_bool y
+ depends on ISA || COMPILE_TEST
+
+config SPEAKUP_SYNTH_ACNTSA
+ tristate "Accent SA synthesizer support"
+ help
+ This is the Speakup driver for the Accent SA
+ synthesizer. You can say y to build it into the kernel,
+ or m to build it as a module. See the configuration
+ help on the Speakup choice above for more info.
+
+config SPEAKUP_SYNTH_ACNTPC
+ tristate "Accent PC synthesizer support"
+ depends on SPEAKUP_SERIALIO
+ help
+ This is the Speakup driver for the accent pc
+ synthesizer. You can say y to build it into the kernel,
+ or m to build it as a module. See the configuration
+ help on the Speakup choice above for more info.
+
+config SPEAKUP_SYNTH_APOLLO
+ tristate "Apollo II synthesizer support"
+ help
+ This is the Speakup driver for the Apollo II
+ synthesizer. You can say y to build it into the kernel,
+ or m to build it as a module. See the configuration
+ help on the Speakup choice above for more info.
+
+config SPEAKUP_SYNTH_AUDPTR
+ tristate "Audapter synthesizer support"
+ help
+ This is the Speakup driver for the Audapter synthesizer.
+ You can say y to build it into the kernel, or m to
+ build it as a module. See the configuration help on the
+ Speakup choice above for more info.
+
+config SPEAKUP_SYNTH_BNS
+ tristate "Braille 'n' Speak synthesizer support"
+ help
+ This is the Speakup driver for the Braille 'n' Speak
+ synthesizer. You can say y to build it into the kernel,
+ or m to build it as a module. See the configuration
+ help on the Speakup choice above for more info.
+
+config SPEAKUP_SYNTH_DECTLK
+ tristate "DECtalk Express synthesizer support"
+ help
+
+ This is the Speakup driver for the DecTalk Express
+ synthesizer. You can say y to build it into the kernel,
+ or m to build it as a module. See the configuration
+ help on the Speakup choice above for more info.
+
+config SPEAKUP_SYNTH_DECEXT
+ tristate "DECtalk External (old) synthesizer support"
+ help
+
+ This is the Speakup driver for the DecTalk External
+ (old) synthesizer. You can say y to build it into the
+ kernel, or m to build it as a module. See the
+ configuration help on the Speakup choice above for more
+ info.
+
+config SPEAKUP_SYNTH_DECPC
+ depends on m
+ depends on SPEAKUP_SERIALIO
+ tristate "DECtalk PC (big ISA card) synthesizer support"
+ help
+
+ This is the Speakup driver for the DecTalk PC (full
+ length ISA) synthesizer. You can say m to build it as
+ a module. See the configuration help on the Speakup
+ choice above for more info.
+
+ In order to use the DecTalk PC driver, you must download
+ the dec_pc.tgz file from linux-speakup.org. It is in
+ the pub/linux/goodies directory. The dec_pc.tgz file
+ contains the software which must be pre-loaded on to the
+ DecTalk PC board in order to use it with this driver.
+ This driver must be built as a module, and can not be
+ loaded until the file system is mounted and the DecTalk
+ PC software has been pre-loaded on to the board.
+
+ See the README file in the dec_pc.tgz file for more
+ details.
+
+config SPEAKUP_SYNTH_DTLK
+ tristate "DoubleTalk PC synthesizer support"
+ depends on SPEAKUP_SERIALIO
+ help
+
+ This is the Speakup driver for the internal DoubleTalk
+ PC synthesizer. You can say y to build it into the
+ kernel, or m to build it as a module. See the
+ configuration help on the Speakup choice above for more
+ info.
+
+config SPEAKUP_SYNTH_KEYPC
+ tristate "Keynote Gold PC synthesizer support"
+ depends on SPEAKUP_SERIALIO
+ help
+
+ This is the Speakup driver for the Keynote Gold
+ PC synthesizer. You can say y to build it into the
+ kernel, or m to build it as a module. See the
+ configuration help on the Speakup choice above for more
+ info.
+
+config SPEAKUP_SYNTH_LTLK
+ tristate "DoubleTalk LT/LiteTalk synthesizer support"
+help
+
+ This is the Speakup driver for the LiteTalk/DoubleTalk
+ LT synthesizer. You can say y to build it into the
+ kernel, or m to build it as a module. See the
+ configuration help on the Speakup choice above for more
+ info.
+
+config SPEAKUP_SYNTH_SOFT
+ tristate "Userspace software synthesizer support"
+ help
+
+ This is the software synthesizer device node. It will
+ register a device /dev/softsynth which midware programs
+ and speech daemons may open and read to provide kernel
+ output to software synths such as espeak, festival,
+ flite and so forth. You can select 'y' or 'm' to have
+ it built-in to the kernel or loaded as a module.
+
+config SPEAKUP_SYNTH_SPKOUT
+ tristate "Speak Out synthesizer support"
+ help
+
+ This is the Speakup driver for the Speakout synthesizer.
+ You can say y to build it into the kernel, or m to
+ build it as a module. See the configuration help on the
+ Speakup choice above for more info.
+
+config SPEAKUP_SYNTH_TXPRT
+ tristate "Transport synthesizer support"
+ help
+
+ This is the Speakup driver for the Transport
+ synthesizer. You can say y to build it into the kernel,
+ or m to build it as a module. See the configuration
+ help on the Speakup choice above for more info.
+
+config SPEAKUP_SYNTH_DUMMY
+ tristate "Dummy synthesizer driver (for testing)"
+ help
+
+ This is a dummy Speakup driver for plugging a mere serial
+ terminal. This is handy if you want to test speakup but
+ don't have the hardware. You can say y to build it into
+ the kernel, or m to build it as a module. See the
+ configuration help on the Speakup choice above for more info.
+
+endif # SPEAKUP
+endmenu
diff --git a/drivers/accessibility/speakup/Makefile b/drivers/accessibility/speakup/Makefile
new file mode 100644
index 000000000..6e4bfac8a
--- /dev/null
+++ b/drivers/accessibility/speakup/Makefile
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_SPEAKUP_SYNTH_ACNTSA) += speakup_acntsa.o
+obj-$(CONFIG_SPEAKUP_SYNTH_ACNTPC) += speakup_acntpc.o
+obj-$(CONFIG_SPEAKUP_SYNTH_APOLLO) += speakup_apollo.o
+obj-$(CONFIG_SPEAKUP_SYNTH_AUDPTR) += speakup_audptr.o
+obj-$(CONFIG_SPEAKUP_SYNTH_BNS) += speakup_bns.o
+obj-$(CONFIG_SPEAKUP_SYNTH_DECTLK) += speakup_dectlk.o
+obj-$(CONFIG_SPEAKUP_SYNTH_DECEXT) += speakup_decext.o
+obj-$(CONFIG_SPEAKUP_SYNTH_DECPC) += speakup_decpc.o
+obj-$(CONFIG_SPEAKUP_SYNTH_DTLK) += speakup_dtlk.o
+obj-$(CONFIG_SPEAKUP_SYNTH_KEYPC) += speakup_keypc.o
+obj-$(CONFIG_SPEAKUP_SYNTH_LTLK) += speakup_ltlk.o
+obj-$(CONFIG_SPEAKUP_SYNTH_SOFT) += speakup_soft.o
+obj-$(CONFIG_SPEAKUP_SYNTH_SPKOUT) += speakup_spkout.o
+obj-$(CONFIG_SPEAKUP_SYNTH_TXPRT) += speakup_txprt.o
+obj-$(CONFIG_SPEAKUP_SYNTH_DUMMY) += speakup_dummy.o
+
+obj-$(CONFIG_SPEAKUP) += speakup.o
+speakup-y := \
+ buffers.o \
+ devsynth.o \
+ i18n.o \
+ fakekey.o \
+ main.o \
+ keyhelp.o \
+ kobjects.o \
+ selection.o \
+ spk_ttyio.o \
+ synth.o \
+ thread.o \
+ varhandlers.o
+speakup-$(CONFIG_SPEAKUP_SERIALIO) += serialio.o
diff --git a/drivers/accessibility/speakup/TODO b/drivers/accessibility/speakup/TODO
new file mode 100644
index 000000000..d4ca093bf
--- /dev/null
+++ b/drivers/accessibility/speakup/TODO
@@ -0,0 +1,22 @@
+Speakup project home: http://www.linux-speakup.org
+
+Mailing List: speakup@linux-speakup.org
+
+Speakup is a kernel based screen review package for the linux operating
+system. It allows blind users to interact with applications on the
+linux console by means of synthetic speech.
+
+Currently, speakup has one issue we know of.
+
+It seems to only happen on SMP systems. It seems that text in the output buffer
+gets garbled because a lock is not set. This bug happens regularly, but no one
+has been able to find a situation which produces it consistently.
+
+Patches, suggestions, corrections, etc, are definitely welcome.
+
+We prefer that you contact us on the mailing list; however, if you do
+not want to subscribe to a mailing list, send your email to all of the
+following:
+
+okash.khawaja@gmail.com, w.d.hubbs@gmail.com, chris@the-brannons.com,
+kirk@reisers.ca and samuel.thibault@ens-lyon.org.
diff --git a/drivers/accessibility/speakup/buffers.c b/drivers/accessibility/speakup/buffers.c
new file mode 100644
index 000000000..1371ced2f
--- /dev/null
+++ b/drivers/accessibility/speakup/buffers.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/console.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include "speakup.h"
+#include "spk_priv.h"
+
+#define SYNTH_BUF_SIZE 8192 /* currently 8K bytes */
+
+static u16 synth_buffer[SYNTH_BUF_SIZE]; /* guess what this is for! */
+static u16 *buff_in = synth_buffer;
+static u16 *buff_out = synth_buffer;
+static u16 *buffer_end = synth_buffer + SYNTH_BUF_SIZE - 1;
+
+/* These try to throttle applications by stopping the TTYs
+ * Note: we need to make sure that we will restart them eventually, which is
+ * usually not possible to do from the notifiers. TODO: it should be possible
+ * starting from linux 2.6.26.
+ *
+ * So we only stop when we know alive == 1 (else we discard the data anyway),
+ * and the alive synth will eventually call start_ttys from the thread context.
+ */
+void speakup_start_ttys(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_NR_CONSOLES; i++) {
+ if (speakup_console[i] && speakup_console[i]->tty_stopped)
+ continue;
+ if (vc_cons[i].d && vc_cons[i].d->port.tty)
+ start_tty(vc_cons[i].d->port.tty);
+ }
+}
+EXPORT_SYMBOL_GPL(speakup_start_ttys);
+
+static void speakup_stop_ttys(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_NR_CONSOLES; i++)
+ if (vc_cons[i].d && vc_cons[i].d->port.tty)
+ stop_tty(vc_cons[i].d->port.tty);
+}
+
+static int synth_buffer_free(void)
+{
+ int chars_free;
+
+ if (buff_in >= buff_out)
+ chars_free = SYNTH_BUF_SIZE - (buff_in - buff_out);
+ else
+ chars_free = buff_out - buff_in;
+ return chars_free;
+}
+
+int synth_buffer_empty(void)
+{
+ return (buff_in == buff_out);
+}
+EXPORT_SYMBOL_GPL(synth_buffer_empty);
+
+void synth_buffer_add(u16 ch)
+{
+ if (!synth->alive) {
+ /* This makes sure that we won't stop TTYs if there is no synth
+ * to restart them
+ */
+ return;
+ }
+ if (synth_buffer_free() <= 100) {
+ synth_start();
+ speakup_stop_ttys();
+ }
+ if (synth_buffer_free() <= 1)
+ return;
+ *buff_in++ = ch;
+ if (buff_in > buffer_end)
+ buff_in = synth_buffer;
+ /* We have written something to the speech synthesis, so we are not
+ * paused any more.
+ */
+ spk_paused = false;
+}
+
+u16 synth_buffer_getc(void)
+{
+ u16 ch;
+
+ if (buff_out == buff_in)
+ return 0;
+ ch = *buff_out++;
+ if (buff_out > buffer_end)
+ buff_out = synth_buffer;
+ return ch;
+}
+EXPORT_SYMBOL_GPL(synth_buffer_getc);
+
+u16 synth_buffer_peek(void)
+{
+ if (buff_out == buff_in)
+ return 0;
+ return *buff_out;
+}
+EXPORT_SYMBOL_GPL(synth_buffer_peek);
+
+void synth_buffer_skip_nonlatin1(void)
+{
+ while (buff_out != buff_in) {
+ if (*buff_out < 0x100)
+ return;
+ buff_out++;
+ if (buff_out > buffer_end)
+ buff_out = synth_buffer;
+ }
+}
+EXPORT_SYMBOL_GPL(synth_buffer_skip_nonlatin1);
+
+void synth_buffer_clear(void)
+{
+ buff_in = synth_buffer;
+ buff_out = synth_buffer;
+}
+EXPORT_SYMBOL_GPL(synth_buffer_clear);
diff --git a/drivers/accessibility/speakup/devsynth.c b/drivers/accessibility/speakup/devsynth.c
new file mode 100644
index 000000000..d30571663
--- /dev/null
+++ b/drivers/accessibility/speakup/devsynth.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/errno.h>
+#include <linux/miscdevice.h> /* for misc_register, and MISC_DYNAMIC_MINOR */
+#include <linux/types.h>
+#include <linux/uaccess.h>
+
+#include "speakup.h"
+#include "spk_priv.h"
+
+static int misc_registered;
+static int dev_opened;
+
+static ssize_t speakup_file_write(struct file *fp, const char __user *buffer,
+ size_t nbytes, loff_t *ppos)
+{
+ size_t count = nbytes;
+ const char __user *ptr = buffer;
+ size_t bytes;
+ unsigned long flags;
+ u_char buf[256];
+
+ if (!synth)
+ return -ENODEV;
+ while (count > 0) {
+ bytes = min(count, sizeof(buf));
+ if (copy_from_user(buf, ptr, bytes))
+ return -EFAULT;
+ count -= bytes;
+ ptr += bytes;
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ synth_write(buf, bytes);
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ }
+ return (ssize_t)nbytes;
+}
+
+static ssize_t speakup_file_read(struct file *fp, char __user *buf,
+ size_t nbytes, loff_t *ppos)
+{
+ return 0;
+}
+
+static int speakup_file_open(struct inode *ip, struct file *fp)
+{
+ if (!synth)
+ return -ENODEV;
+ if (xchg(&dev_opened, 1))
+ return -EBUSY;
+ return 0;
+}
+
+static int speakup_file_release(struct inode *ip, struct file *fp)
+{
+ dev_opened = 0;
+ return 0;
+}
+
+static const struct file_operations synth_fops = {
+ .read = speakup_file_read,
+ .write = speakup_file_write,
+ .open = speakup_file_open,
+ .release = speakup_file_release,
+};
+
+static struct miscdevice synth_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "synth",
+ .fops = &synth_fops,
+};
+
+void speakup_register_devsynth(void)
+{
+ if (misc_registered != 0)
+ return;
+/* zero it so if register fails, deregister will not ref invalid ptrs */
+ if (misc_register(&synth_device)) {
+ pr_warn("Couldn't initialize miscdevice /dev/synth.\n");
+ } else {
+ pr_info("initialized device: /dev/synth, node (MAJOR %d, MINOR %d)\n",
+ MISC_MAJOR, synth_device.minor);
+ misc_registered = 1;
+ }
+}
+
+void speakup_unregister_devsynth(void)
+{
+ if (!misc_registered)
+ return;
+ pr_info("speakup: unregistering synth device /dev/synth\n");
+ misc_deregister(&synth_device);
+ misc_registered = 0;
+}
diff --git a/drivers/accessibility/speakup/fakekey.c b/drivers/accessibility/speakup/fakekey.c
new file mode 100644
index 000000000..cd0299684
--- /dev/null
+++ b/drivers/accessibility/speakup/fakekey.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* fakekey.c
+ * Functions for simulating keypresses.
+ *
+ * Copyright (C) 2010 the Speakup Team
+ */
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/preempt.h>
+#include <linux/percpu.h>
+#include <linux/input.h>
+
+#include "speakup.h"
+
+#define PRESSED 1
+#define RELEASED 0
+
+static DEFINE_PER_CPU(int, reporting_keystroke);
+
+static struct input_dev *virt_keyboard;
+
+int speakup_add_virtual_keyboard(void)
+{
+ int err;
+
+ virt_keyboard = input_allocate_device();
+
+ if (!virt_keyboard)
+ return -ENOMEM;
+
+ virt_keyboard->name = "Speakup";
+ virt_keyboard->id.bustype = BUS_VIRTUAL;
+ virt_keyboard->phys = "speakup/input0";
+ virt_keyboard->dev.parent = NULL;
+
+ __set_bit(EV_KEY, virt_keyboard->evbit);
+ __set_bit(KEY_DOWN, virt_keyboard->keybit);
+
+ err = input_register_device(virt_keyboard);
+ if (err) {
+ input_free_device(virt_keyboard);
+ virt_keyboard = NULL;
+ }
+
+ return err;
+}
+
+void speakup_remove_virtual_keyboard(void)
+{
+ if (virt_keyboard) {
+ input_unregister_device(virt_keyboard);
+ virt_keyboard = NULL;
+ }
+}
+
+/*
+ * Send a simulated down-arrow to the application.
+ */
+void speakup_fake_down_arrow(void)
+{
+ unsigned long flags;
+
+ /* disable keyboard interrupts */
+ local_irq_save(flags);
+ /* don't change CPU */
+ preempt_disable();
+
+ __this_cpu_write(reporting_keystroke, true);
+ input_report_key(virt_keyboard, KEY_DOWN, PRESSED);
+ input_report_key(virt_keyboard, KEY_DOWN, RELEASED);
+ input_sync(virt_keyboard);
+ __this_cpu_write(reporting_keystroke, false);
+
+ /* reenable preemption */
+ preempt_enable();
+ /* reenable keyboard interrupts */
+ local_irq_restore(flags);
+}
+
+/*
+ * Are we handling a simulated keypress on the current CPU?
+ * Returns a boolean.
+ */
+bool speakup_fake_key_pressed(void)
+{
+ return this_cpu_read(reporting_keystroke);
+}
diff --git a/drivers/accessibility/speakup/i18n.c b/drivers/accessibility/speakup/i18n.c
new file mode 100644
index 000000000..ee240d36f
--- /dev/null
+++ b/drivers/accessibility/speakup/i18n.c
@@ -0,0 +1,625 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Internationalization implementation. Includes definitions of English
+ * string arrays, and the i18n pointer.
+ */
+
+#include <linux/slab.h> /* For kmalloc. */
+#include <linux/ctype.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include "speakup.h"
+#include "spk_priv.h"
+
+static char *speakup_msgs[MSG_LAST_INDEX];
+static char *speakup_default_msgs[MSG_LAST_INDEX] = {
+ [MSG_BLANK] = "blank",
+ [MSG_IAM_ALIVE] = "I'm aLive!",
+ [MSG_YOU_KILLED_SPEAKUP] = "You killed speakup!",
+ [MSG_HEY_THATS_BETTER] = "hey. That's better!",
+ [MSG_YOU_TURNED_ME_OFF] = "You turned me off!",
+ [MSG_PARKED] = "parked!",
+ [MSG_UNPARKED] = "unparked!",
+ [MSG_MARK] = "mark",
+ [MSG_CUT] = "cut",
+ [MSG_MARK_CLEARED] = "mark, cleared",
+ [MSG_PASTE] = "paste",
+ [MSG_BRIGHT] = "bright",
+ [MSG_ON_BLINKING] = "on blinking",
+ [MSG_OFF] = "off",
+ [MSG_ON] = "on",
+ [MSG_NO_WINDOW] = "no window",
+ [MSG_CURSORING_OFF] = "cursoring off",
+ [MSG_CURSORING_ON] = "cursoring on",
+ [MSG_HIGHLIGHT_TRACKING] = "highlight tracking",
+ [MSG_READ_WINDOW] = "read windo",
+ [MSG_READ_ALL] = "read all",
+ [MSG_EDIT_DONE] = "edit done",
+ [MSG_WINDOW_ALREADY_SET] = "window already set, clear then reset",
+ [MSG_END_BEFORE_START] = "error end before start",
+ [MSG_WINDOW_CLEARED] = "window cleared",
+ [MSG_WINDOW_SILENCED] = "window silenced",
+ [MSG_WINDOW_SILENCE_DISABLED] = "window silence disabled",
+ [MSG_ERROR] = "error",
+ [MSG_GOTO_CANCELED] = "goto canceled",
+ [MSG_GOTO] = "go to?",
+ [MSG_LEAVING_HELP] = "leaving help",
+ [MSG_IS_UNASSIGNED] = "is unassigned",
+ [MSG_HELP_INFO] =
+ "press space to exit, up or down to scroll, or a letter to go to a command",
+ [MSG_EDGE_TOP] = "top,",
+ [MSG_EDGE_BOTTOM] = "bottom,",
+ [MSG_EDGE_LEFT] = "left,",
+ [MSG_EDGE_RIGHT] = "right,",
+ [MSG_NUMBER] = "number",
+ [MSG_SPACE] = "space",
+ [MSG_START] = "start",
+ [MSG_END] = "end",
+ [MSG_CTRL] = "control-",
+ [MSG_DISJUNCTION] = "or",
+
+/* Messages with embedded format specifiers. */
+ [MSG_POS_INFO] = "line %ld, col %ld, t t y %d",
+ [MSG_CHAR_INFO] = "hex %02x, decimal %d",
+ [MSG_REPEAT_DESC] = "times %d .",
+ [MSG_REPEAT_DESC2] = "repeated %d .",
+ [MSG_WINDOW_LINE] = "window is line %d",
+ [MSG_WINDOW_BOUNDARY] = "%s at line %d, column %d",
+ [MSG_EDIT_PROMPT] = "edit %s, press space when done",
+ [MSG_NO_COMMAND] = "no commands for %c",
+ [MSG_KEYDESC] = "is %s",
+
+ /* Control keys. */
+ /* Most of these duplicate the entries in state names. */
+ [MSG_CTL_SHIFT] = "shift",
+ [MSG_CTL_ALTGR] = "altgr",
+ [MSG_CTL_CONTROL] = "control",
+ [MSG_CTL_ALT] = "alt",
+ [MSG_CTL_LSHIFT] = "l shift",
+ [MSG_CTL_SPEAKUP] = "speakup",
+ [MSG_CTL_LCONTROL] = "l control",
+ [MSG_CTL_RCONTROL] = "r control",
+ [MSG_CTL_CAPSSHIFT] = "caps shift",
+
+ /* Color names. */
+ [MSG_COLOR_BLACK] = "black",
+ [MSG_COLOR_BLUE] = "blue",
+ [MSG_COLOR_GREEN] = "green",
+ [MSG_COLOR_CYAN] = "cyan",
+ [MSG_COLOR_RED] = "red",
+ [MSG_COLOR_MAGENTA] = "magenta",
+ [MSG_COLOR_YELLOW] = "yellow",
+ [MSG_COLOR_WHITE] = "white",
+ [MSG_COLOR_GREY] = "grey",
+
+ /* Names of key states. */
+ [MSG_STATE_DOUBLE] = "double",
+ [MSG_STATE_SPEAKUP] = "speakup",
+ [MSG_STATE_ALT] = "alt",
+ [MSG_STATE_CONTROL] = "ctrl",
+ [MSG_STATE_ALTGR] = "altgr",
+ [MSG_STATE_SHIFT] = "shift",
+
+ /* Key names. */
+ [MSG_KEYNAME_ESC] = "escape",
+ [MSG_KEYNAME_1] = "1",
+ [MSG_KEYNAME_2] = "2",
+ [MSG_KEYNAME_3] = "3",
+ [MSG_KEYNAME_4] = "4",
+ [MSG_KEYNAME_5] = "5",
+ [MSG_KEYNAME_6] = "6",
+ [MSG_KEYNAME_7] = "7",
+ [MSG_KEYNAME_8] = "8",
+ [MSG_KEYNAME_9] = "9",
+ [MSG_KEYNAME_0] = "0",
+ [MSG_KEYNAME_DASH] = "minus",
+ [MSG_KEYNAME_EQUAL] = "equal",
+ [MSG_KEYNAME_BS] = "back space",
+ [MSG_KEYNAME_TAB] = "tab",
+ [MSG_KEYNAME_Q] = "q",
+ [MSG_KEYNAME_W] = "w",
+ [MSG_KEYNAME_E] = "e",
+ [MSG_KEYNAME_R] = "r",
+ [MSG_KEYNAME_T] = "t",
+ [MSG_KEYNAME_Y] = "y",
+ [MSG_KEYNAME_U] = "u",
+ [MSG_KEYNAME_I] = "i",
+ [MSG_KEYNAME_O] = "o",
+ [MSG_KEYNAME_P] = "p",
+ [MSG_KEYNAME_LEFTBRACE] = "left brace",
+ [MSG_KEYNAME_RIGHTBRACE] = "right brace",
+ [MSG_KEYNAME_ENTER] = "enter",
+ [MSG_KEYNAME_LEFTCTRL] = "left control",
+ [MSG_KEYNAME_A] = "a",
+ [MSG_KEYNAME_S] = "s",
+ [MSG_KEYNAME_D] = "d",
+ [MSG_KEYNAME_F] = "f",
+ [MSG_KEYNAME_G] = "g",
+ [MSG_KEYNAME_H] = "h",
+ [MSG_KEYNAME_J] = "j",
+ [MSG_KEYNAME_K] = "k",
+ [MSG_KEYNAME_L] = "l",
+ [MSG_KEYNAME_SEMICOLON] = "semicolon",
+ [MSG_KEYNAME_SINGLEQUOTE] = "apostrophe",
+ [MSG_KEYNAME_GRAVE] = "accent",
+ [MSG_KEYNAME_LEFTSHFT] = "left shift",
+ [MSG_KEYNAME_BACKSLASH] = "back slash",
+ [MSG_KEYNAME_Z] = "z",
+ [MSG_KEYNAME_X] = "x",
+ [MSG_KEYNAME_C] = "c",
+ [MSG_KEYNAME_V] = "v",
+ [MSG_KEYNAME_B] = "b",
+ [MSG_KEYNAME_N] = "n",
+ [MSG_KEYNAME_M] = "m",
+ [MSG_KEYNAME_COMMA] = "comma",
+ [MSG_KEYNAME_DOT] = "dot",
+ [MSG_KEYNAME_SLASH] = "slash",
+ [MSG_KEYNAME_RIGHTSHFT] = "right shift",
+ [MSG_KEYNAME_KPSTAR] = "keypad asterisk",
+ [MSG_KEYNAME_LEFTALT] = "left alt",
+ [MSG_KEYNAME_SPACE] = "space",
+ [MSG_KEYNAME_CAPSLOCK] = "caps lock",
+ [MSG_KEYNAME_F1] = "f1",
+ [MSG_KEYNAME_F2] = "f2",
+ [MSG_KEYNAME_F3] = "f3",
+ [MSG_KEYNAME_F4] = "f4",
+ [MSG_KEYNAME_F5] = "f5",
+ [MSG_KEYNAME_F6] = "f6",
+ [MSG_KEYNAME_F7] = "f7",
+ [MSG_KEYNAME_F8] = "f8",
+ [MSG_KEYNAME_F9] = "f9",
+ [MSG_KEYNAME_F10] = "f10",
+ [MSG_KEYNAME_NUMLOCK] = "num lock",
+ [MSG_KEYNAME_SCROLLLOCK] = "scroll lock",
+ [MSG_KEYNAME_KP7] = "keypad 7",
+ [MSG_KEYNAME_KP8] = "keypad 8",
+ [MSG_KEYNAME_KP9] = "keypad 9",
+ [MSG_KEYNAME_KPMINUS] = "keypad minus",
+ [MSG_KEYNAME_KP4] = "keypad 4",
+ [MSG_KEYNAME_KP5] = "keypad 5",
+ [MSG_KEYNAME_KP6] = "keypad 6",
+ [MSG_KEYNAME_KPPLUS] = "keypad plus",
+ [MSG_KEYNAME_KP1] = "keypad 1",
+ [MSG_KEYNAME_KP2] = "keypad 2",
+ [MSG_KEYNAME_KP3] = "keypad 3",
+ [MSG_KEYNAME_KP0] = "keypad 0",
+ [MSG_KEYNAME_KPDOT] = "keypad dot",
+ [MSG_KEYNAME_103RD] = "103rd",
+ [MSG_KEYNAME_F13] = "f13",
+ [MSG_KEYNAME_102ND] = "102nd",
+ [MSG_KEYNAME_F11] = "f11",
+ [MSG_KEYNAME_F12] = "f12",
+ [MSG_KEYNAME_F14] = "f14",
+ [MSG_KEYNAME_F15] = "f15",
+ [MSG_KEYNAME_F16] = "f16",
+ [MSG_KEYNAME_F17] = "f17",
+ [MSG_KEYNAME_F18] = "f18",
+ [MSG_KEYNAME_F19] = "f19",
+ [MSG_KEYNAME_F20] = "f20",
+ [MSG_KEYNAME_KPENTER] = "keypad enter",
+ [MSG_KEYNAME_RIGHTCTRL] = "right control",
+ [MSG_KEYNAME_KPSLASH] = "keypad slash",
+ [MSG_KEYNAME_SYSRQ] = "sysrq",
+ [MSG_KEYNAME_RIGHTALT] = "right alt",
+ [MSG_KEYNAME_LF] = "line feed",
+ [MSG_KEYNAME_HOME] = "home",
+ [MSG_KEYNAME_UP] = "up",
+ [MSG_KEYNAME_PGUP] = "page up",
+ [MSG_KEYNAME_LEFT] = "left",
+ [MSG_KEYNAME_RIGHT] = "right",
+ [MSG_KEYNAME_END] = "end",
+ [MSG_KEYNAME_DOWN] = "down",
+ [MSG_KEYNAME_PGDN] = "page down",
+ [MSG_KEYNAME_INS] = "insert",
+ [MSG_KEYNAME_DEL] = "delete",
+ [MSG_KEYNAME_MACRO] = "macro",
+ [MSG_KEYNAME_MUTE] = "mute",
+ [MSG_KEYNAME_VOLDOWN] = "volume down",
+ [MSG_KEYNAME_VOLUP] = "volume up",
+ [MSG_KEYNAME_POWER] = "power",
+ [MSG_KEYNAME_KPEQUAL] = "keypad equal",
+ [MSG_KEYNAME_KPPLUSDASH] = "keypad plusminus",
+ [MSG_KEYNAME_PAUSE] = "pause",
+ [MSG_KEYNAME_F21] = "f21",
+ [MSG_KEYNAME_F22] = "f22",
+ [MSG_KEYNAME_F23] = "f23",
+ [MSG_KEYNAME_F24] = "f24",
+ [MSG_KEYNAME_KPCOMMA] = "keypad comma",
+ [MSG_KEYNAME_LEFTMETA] = "left meta",
+ [MSG_KEYNAME_RIGHTMETA] = "right meta",
+ [MSG_KEYNAME_COMPOSE] = "compose",
+ [MSG_KEYNAME_STOP] = "stop",
+ [MSG_KEYNAME_AGAIN] = "again",
+ [MSG_KEYNAME_PROPS] = "props",
+ [MSG_KEYNAME_UNDO] = "undo",
+ [MSG_KEYNAME_FRONT] = "front",
+ [MSG_KEYNAME_COPY] = "copy",
+ [MSG_KEYNAME_OPEN] = "open",
+ [MSG_KEYNAME_PASTE] = "paste",
+ [MSG_KEYNAME_FIND] = "find",
+ [MSG_KEYNAME_CUT] = "cut",
+ [MSG_KEYNAME_HELP] = "help",
+ [MSG_KEYNAME_MENU] = "menu",
+ [MSG_KEYNAME_CALC] = "calc",
+ [MSG_KEYNAME_SETUP] = "setup",
+ [MSG_KEYNAME_SLEEP] = "sleep",
+ [MSG_KEYNAME_WAKEUP] = "wakeup",
+ [MSG_KEYNAME_FILE] = "file",
+ [MSG_KEYNAME_SENDFILE] = "send file",
+ [MSG_KEYNAME_DELFILE] = "delete file",
+ [MSG_KEYNAME_XFER] = "transfer",
+ [MSG_KEYNAME_PROG1] = "prog1",
+ [MSG_KEYNAME_PROG2] = "prog2",
+ [MSG_KEYNAME_WWW] = "www",
+ [MSG_KEYNAME_MSDOS] = "msdos",
+ [MSG_KEYNAME_COFFEE] = "coffee",
+ [MSG_KEYNAME_DIRECTION] = "direction",
+ [MSG_KEYNAME_CYCLEWINDOWS] = "cycle windows",
+ [MSG_KEYNAME_MAIL] = "mail",
+ [MSG_KEYNAME_BOOKMARKS] = "bookmarks",
+ [MSG_KEYNAME_COMPUTER] = "computer",
+ [MSG_KEYNAME_BACK] = "back",
+ [MSG_KEYNAME_FORWARD] = "forward",
+ [MSG_KEYNAME_CLOSECD] = "close cd",
+ [MSG_KEYNAME_EJECTCD] = "eject cd",
+ [MSG_KEYNAME_EJECTCLOSE] = "eject close cd",
+ [MSG_KEYNAME_NEXTSONG] = "next song",
+ [MSG_KEYNAME_PLAYPAUSE] = "play pause",
+ [MSG_KEYNAME_PREVSONG] = "previous song",
+ [MSG_KEYNAME_STOPCD] = "stop cd",
+ [MSG_KEYNAME_RECORD] = "record",
+ [MSG_KEYNAME_REWIND] = "rewind",
+ [MSG_KEYNAME_PHONE] = "phone",
+ [MSG_KEYNAME_ISO] = "iso",
+ [MSG_KEYNAME_CONFIG] = "config",
+ [MSG_KEYNAME_HOMEPG] = "home page",
+ [MSG_KEYNAME_REFRESH] = "refresh",
+ [MSG_KEYNAME_EXIT] = "exit",
+ [MSG_KEYNAME_MOVE] = "move",
+ [MSG_KEYNAME_EDIT] = "edit",
+ [MSG_KEYNAME_SCROLLUP] = "scroll up",
+ [MSG_KEYNAME_SCROLLDN] = "scroll down",
+ [MSG_KEYNAME_KPLEFTPAR] = "keypad left paren",
+ [MSG_KEYNAME_KPRIGHTPAR] = "keypad right paren",
+
+ /* Function names. */
+ [MSG_FUNCNAME_ATTRIB_BLEEP_DEC] = "attribute bleep decrement",
+ [MSG_FUNCNAME_ATTRIB_BLEEP_INC] = "attribute bleep increment",
+ [MSG_FUNCNAME_BLEEPS_DEC] = "bleeps decrement",
+ [MSG_FUNCNAME_BLEEPS_INC] = "bleeps increment",
+ [MSG_FUNCNAME_CHAR_FIRST] = "character, first",
+ [MSG_FUNCNAME_CHAR_LAST] = "character, last",
+ [MSG_FUNCNAME_CHAR_CURRENT] = "character, say current",
+ [MSG_FUNCNAME_CHAR_HEX_AND_DEC] = "character, say hex and decimal",
+ [MSG_FUNCNAME_CHAR_NEXT] = "character, say next",
+ [MSG_FUNCNAME_CHAR_PHONETIC] = "character, say phonetic",
+ [MSG_FUNCNAME_CHAR_PREVIOUS] = "character, say previous",
+ [MSG_FUNCNAME_CURSOR_PARK] = "cursor park",
+ [MSG_FUNCNAME_CUT] = "cut",
+ [MSG_FUNCNAME_EDIT_DELIM] = "edit delimiters",
+ [MSG_FUNCNAME_EDIT_EXNUM] = "edit exnum",
+ [MSG_FUNCNAME_EDIT_MOST] = "edit most",
+ [MSG_FUNCNAME_EDIT_REPEATS] = "edit repeats",
+ [MSG_FUNCNAME_EDIT_SOME] = "edit some",
+ [MSG_FUNCNAME_GOTO] = "go to",
+ [MSG_FUNCNAME_GOTO_BOTTOM] = "go to bottom edge",
+ [MSG_FUNCNAME_GOTO_LEFT] = "go to left edge",
+ [MSG_FUNCNAME_GOTO_RIGHT] = "go to right edge",
+ [MSG_FUNCNAME_GOTO_TOP] = "go to top edge",
+ [MSG_FUNCNAME_HELP] = "help",
+ [MSG_FUNCNAME_LINE_SAY_CURRENT] = "line, say current",
+ [MSG_FUNCNAME_LINE_SAY_NEXT] = "line, say next",
+ [MSG_FUNCNAME_LINE_SAY_PREVIOUS] = "line, say previous",
+ [MSG_FUNCNAME_LINE_SAY_WITH_INDENT] = "line, say with indent",
+ [MSG_FUNCNAME_PASTE] = "paste",
+ [MSG_FUNCNAME_PITCH_DEC] = "pitch decrement",
+ [MSG_FUNCNAME_PITCH_INC] = "pitch increment",
+ [MSG_FUNCNAME_PUNC_DEC] = "punctuation decrement",
+ [MSG_FUNCNAME_PUNC_INC] = "punctuation increment",
+ [MSG_FUNCNAME_PUNC_LEVEL_DEC] = "punc level decrement",
+ [MSG_FUNCNAME_PUNC_LEVEL_INC] = "punc level increment",
+ [MSG_FUNCNAME_QUIET] = "quiet",
+ [MSG_FUNCNAME_RATE_DEC] = "rate decrement",
+ [MSG_FUNCNAME_RATE_INC] = "rate increment",
+ [MSG_FUNCNAME_READING_PUNC_DEC] = "reading punctuation decrement",
+ [MSG_FUNCNAME_READING_PUNC_INC] = "reading punctuation increment",
+ [MSG_FUNCNAME_SAY_ATTRIBUTES] = "say attributes",
+ [MSG_FUNCNAME_SAY_FROM_LEFT] = "say from left",
+ [MSG_FUNCNAME_SAY_FROM_TOP] = "say from top",
+ [MSG_FUNCNAME_SAY_POSITION] = "say position",
+ [MSG_FUNCNAME_SAY_SCREEN] = "say screen",
+ [MSG_FUNCNAME_SAY_TO_BOTTOM] = "say to bottom",
+ [MSG_FUNCNAME_SAY_TO_RIGHT] = "say to right",
+ [MSG_FUNCNAME_SPEAKUP] = "speakup",
+ [MSG_FUNCNAME_SPEAKUP_LOCK] = "speakup lock",
+ [MSG_FUNCNAME_SPEAKUP_OFF] = "speakup off",
+ [MSG_FUNCNAME_SPEECH_KILL] = "speech kill",
+ [MSG_FUNCNAME_SPELL_DELAY_DEC] = "spell delay decrement",
+ [MSG_FUNCNAME_SPELL_DELAY_INC] = "spell delay increment",
+ [MSG_FUNCNAME_SPELL_WORD] = "spell word",
+ [MSG_FUNCNAME_SPELL_WORD_PHONETICALLY] = "spell word phonetically",
+ [MSG_FUNCNAME_TONE_DEC] = "tone decrement",
+ [MSG_FUNCNAME_TONE_INC] = "tone increment",
+ [MSG_FUNCNAME_VOICE_DEC] = "voice decrement",
+ [MSG_FUNCNAME_VOICE_INC] = "voice increment",
+ [MSG_FUNCNAME_VOLUME_DEC] = "volume decrement",
+ [MSG_FUNCNAME_VOLUME_INC] = "volume increment",
+ [MSG_FUNCNAME_WINDOW_CLEAR] = "window, clear",
+ [MSG_FUNCNAME_WINDOW_SAY] = "window, say",
+ [MSG_FUNCNAME_WINDOW_SET] = "window, set",
+ [MSG_FUNCNAME_WINDOW_SILENCE] = "window, silence",
+ [MSG_FUNCNAME_WORD_SAY_CURRENT] = "word, say current",
+ [MSG_FUNCNAME_WORD_SAY_NEXT] = "word, say next",
+ [MSG_FUNCNAME_WORD_SAY_PREVIOUS] = "word, say previous",
+};
+
+static struct msg_group_t all_groups[] = {
+ {
+ .name = "ctl_keys",
+ .start = MSG_CTL_START,
+ .end = MSG_CTL_END,
+ },
+ {
+ .name = "colors",
+ .start = MSG_COLORS_START,
+ .end = MSG_COLORS_END,
+ },
+ {
+ .name = "formatted",
+ .start = MSG_FORMATTED_START,
+ .end = MSG_FORMATTED_END,
+ },
+ {
+ .name = "function_names",
+ .start = MSG_FUNCNAMES_START,
+ .end = MSG_FUNCNAMES_END,
+ },
+ {
+ .name = "key_names",
+ .start = MSG_KEYNAMES_START,
+ .end = MSG_KEYNAMES_END,
+ },
+ {
+ .name = "announcements",
+ .start = MSG_ANNOUNCEMENTS_START,
+ .end = MSG_ANNOUNCEMENTS_END,
+ },
+ {
+ .name = "states",
+ .start = MSG_STATES_START,
+ .end = MSG_STATES_END,
+ },
+};
+
+static const int num_groups = ARRAY_SIZE(all_groups);
+
+char *spk_msg_get(enum msg_index_t index)
+{
+ return speakup_msgs[index];
+}
+
+/*
+ * Function: next_specifier
+ * Finds the start of the next format specifier in the argument string.
+ * Return value: pointer to start of format
+ * specifier, or NULL if no specifier exists.
+ */
+static char *next_specifier(char *input)
+{
+ int found = 0;
+ char *next_percent = input;
+
+ while (next_percent && !found) {
+ next_percent = strchr(next_percent, '%');
+ if (next_percent) {
+ /* skip over doubled percent signs */
+ while (next_percent[0] == '%' &&
+ next_percent[1] == '%')
+ next_percent += 2;
+ if (*next_percent == '%')
+ found = 1;
+ else if (*next_percent == '\0')
+ next_percent = NULL;
+ }
+ }
+
+ return next_percent;
+}
+
+/* Skip over 0 or more flags. */
+static char *skip_flags(char *input)
+{
+ while ((*input != '\0') && strchr(" 0+-#", *input))
+ input++;
+ return input;
+}
+
+/* Skip over width.precision, if it exists. */
+static char *skip_width(char *input)
+{
+ while (isdigit(*input))
+ input++;
+ if (*input == '.') {
+ input++;
+ while (isdigit(*input))
+ input++;
+ }
+ return input;
+}
+
+/*
+ * Skip past the end of the conversion part.
+ * Note that this code only accepts a handful of conversion specifiers:
+ * c d s x and ld. Not accidental; these are exactly the ones used in
+ * the default group of formatted messages.
+ */
+static char *skip_conversion(char *input)
+{
+ if ((input[0] == 'l') && (input[1] == 'd'))
+ input += 2;
+ else if ((*input != '\0') && strchr("cdsx", *input))
+ input++;
+ return input;
+}
+
+/*
+ * Function: find_specifier_end
+ * Return a pointer to the end of the format specifier.
+ */
+static char *find_specifier_end(char *input)
+{
+ input++; /* Advance over %. */
+ input = skip_flags(input);
+ input = skip_width(input);
+ input = skip_conversion(input);
+ return input;
+}
+
+/*
+ * Function: compare_specifiers
+ * Compare the format specifiers pointed to by *input1 and *input2.
+ * Return true if they are the same, false otherwise.
+ * Advance *input1 and *input2 so that they point to the character following
+ * the end of the specifier.
+ */
+static bool compare_specifiers(char **input1, char **input2)
+{
+ bool same = false;
+ char *end1 = find_specifier_end(*input1);
+ char *end2 = find_specifier_end(*input2);
+ size_t length1 = end1 - *input1;
+ size_t length2 = end2 - *input2;
+
+ if ((length1 == length2) && !memcmp(*input1, *input2, length1))
+ same = true;
+
+ *input1 = end1;
+ *input2 = end2;
+ return same;
+}
+
+/*
+ * Function: fmt_validate
+ * Check that two format strings contain the same number of format specifiers,
+ * and that the order of specifiers is the same in both strings.
+ * Return true if the condition holds, false if it doesn't.
+ */
+static bool fmt_validate(char *template, char *user)
+{
+ bool valid = true;
+ bool still_comparing = true;
+ char *template_ptr = template;
+ char *user_ptr = user;
+
+ while (still_comparing && valid) {
+ template_ptr = next_specifier(template_ptr);
+ user_ptr = next_specifier(user_ptr);
+ if (template_ptr && user_ptr) {
+ /* Both have at least one more specifier. */
+ valid = compare_specifiers(&template_ptr, &user_ptr);
+ } else {
+ /* No more format specifiers in one or both strings. */
+ still_comparing = false;
+ /* See if one has more specifiers than the other. */
+ if (template_ptr || user_ptr)
+ valid = false;
+ }
+ }
+ return valid;
+}
+
+/*
+ * Function: msg_set
+ * Description: Add a user-supplied message to the user_messages array.
+ * The message text is copied to a memory area allocated with kmalloc.
+ * If the function fails, then user_messages is untouched.
+ * Arguments:
+ * - index: a message number, as found in i18n.h.
+ * - text: text of message. Not NUL-terminated.
+ * - length: number of bytes in text.
+ * Failure conditions:
+ * -EINVAL - Invalid format specifiers in formatted message or illegal index.
+ * -ENOMEM - Unable to allocate memory.
+ */
+ssize_t spk_msg_set(enum msg_index_t index, char *text, size_t length)
+{
+ char *newstr = NULL;
+ unsigned long flags;
+
+ if ((index < MSG_FIRST_INDEX) || (index >= MSG_LAST_INDEX))
+ return -EINVAL;
+
+ newstr = kmalloc(length + 1, GFP_KERNEL);
+ if (!newstr)
+ return -ENOMEM;
+
+ memcpy(newstr, text, length);
+ newstr[length] = '\0';
+ if (index >= MSG_FORMATTED_START &&
+ index <= MSG_FORMATTED_END &&
+ !fmt_validate(speakup_default_msgs[index], newstr)) {
+ kfree(newstr);
+ return -EINVAL;
+ }
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (speakup_msgs[index] != speakup_default_msgs[index])
+ kfree(speakup_msgs[index]);
+ speakup_msgs[index] = newstr;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return 0;
+}
+
+/*
+ * Find a message group, given its name. Return a pointer to the structure
+ * if found, or NULL otherwise.
+ */
+struct msg_group_t *spk_find_msg_group(const char *group_name)
+{
+ struct msg_group_t *group = NULL;
+ int i;
+
+ for (i = 0; i < num_groups; i++) {
+ if (!strcmp(all_groups[i].name, group_name)) {
+ group = &all_groups[i];
+ break;
+ }
+ }
+ return group;
+}
+
+void spk_reset_msg_group(struct msg_group_t *group)
+{
+ unsigned long flags;
+ enum msg_index_t i;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+
+ for (i = group->start; i <= group->end; i++) {
+ if (speakup_msgs[i] != speakup_default_msgs[i])
+ kfree(speakup_msgs[i]);
+ speakup_msgs[i] = speakup_default_msgs[i];
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+}
+
+/* Called at initialization time, to establish default messages. */
+void spk_initialize_msgs(void)
+{
+ memcpy(speakup_msgs, speakup_default_msgs,
+ sizeof(speakup_default_msgs));
+}
+
+/* Free user-supplied strings when module is unloaded: */
+void spk_free_user_msgs(void)
+{
+ enum msg_index_t index;
+ unsigned long flags;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ for (index = MSG_FIRST_INDEX; index < MSG_LAST_INDEX; index++) {
+ if (speakup_msgs[index] != speakup_default_msgs[index]) {
+ kfree(speakup_msgs[index]);
+ speakup_msgs[index] = speakup_default_msgs[index];
+ }
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+}
diff --git a/drivers/accessibility/speakup/i18n.h b/drivers/accessibility/speakup/i18n.h
new file mode 100644
index 000000000..2ec6e659d
--- /dev/null
+++ b/drivers/accessibility/speakup/i18n.h
@@ -0,0 +1,235 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef I18N_H
+#define I18N_H
+/* Internationalization declarations */
+
+enum msg_index_t {
+ MSG_FIRST_INDEX,
+ MSG_ANNOUNCEMENTS_START = MSG_FIRST_INDEX,
+ MSG_BLANK = MSG_ANNOUNCEMENTS_START,
+ MSG_IAM_ALIVE,
+ MSG_YOU_KILLED_SPEAKUP,
+ MSG_HEY_THATS_BETTER,
+ MSG_YOU_TURNED_ME_OFF,
+ MSG_PARKED,
+ MSG_UNPARKED,
+ MSG_MARK,
+ MSG_CUT,
+ MSG_MARK_CLEARED,
+ MSG_PASTE,
+ MSG_BRIGHT,
+ MSG_ON_BLINKING,
+ MSG_STATUS_START,
+ MSG_OFF = MSG_STATUS_START,
+ MSG_ON,
+ MSG_NO_WINDOW,
+ MSG_CURSOR_MSGS_START,
+ MSG_CURSORING_OFF = MSG_CURSOR_MSGS_START,
+ MSG_CURSORING_ON,
+ MSG_HIGHLIGHT_TRACKING,
+ MSG_READ_WINDOW,
+ MSG_READ_ALL,
+ MSG_EDIT_DONE,
+ MSG_WINDOW_ALREADY_SET,
+ MSG_END_BEFORE_START,
+ MSG_WINDOW_CLEARED,
+ MSG_WINDOW_SILENCED,
+ MSG_WINDOW_SILENCE_DISABLED,
+ MSG_ERROR,
+ MSG_GOTO_CANCELED,
+ MSG_GOTO,
+ MSG_LEAVING_HELP,
+ MSG_IS_UNASSIGNED,
+ MSG_HELP_INFO,
+ MSG_EDGE_MSGS_START,
+ MSG_EDGE_TOP = MSG_EDGE_MSGS_START,
+ MSG_EDGE_BOTTOM,
+ MSG_EDGE_LEFT,
+ MSG_EDGE_RIGHT,
+ MSG_NUMBER,
+ MSG_SPACE,
+ MSG_START, /* A little confusing, given our convention. */
+ MSG_END, /* A little confusing, given our convention. */
+ MSG_CTRL,
+
+/* A message containing the single word "or". */
+ MSG_DISJUNCTION,
+ MSG_ANNOUNCEMENTS_END = MSG_DISJUNCTION,
+
+/* Messages with format specifiers. */
+ MSG_FORMATTED_START,
+ MSG_POS_INFO = MSG_FORMATTED_START,
+ MSG_CHAR_INFO,
+ MSG_REPEAT_DESC,
+ MSG_REPEAT_DESC2,
+ MSG_WINDOW_LINE,
+ MSG_WINDOW_BOUNDARY,
+ MSG_EDIT_PROMPT,
+ MSG_NO_COMMAND,
+ MSG_KEYDESC,
+ MSG_FORMATTED_END = MSG_KEYDESC,
+
+ /* Control keys. */
+ MSG_CTL_START,
+ MSG_CTL_SHIFT = MSG_CTL_START,
+ MSG_CTL_ALTGR,
+ MSG_CTL_CONTROL,
+ MSG_CTL_ALT,
+ MSG_CTL_LSHIFT,
+ MSG_CTL_SPEAKUP,
+ MSG_CTL_LCONTROL,
+ MSG_CTL_RCONTROL,
+ MSG_CTL_CAPSSHIFT,
+ MSG_CTL_END = MSG_CTL_CAPSSHIFT,
+
+ /* Colors. */
+ MSG_COLORS_START,
+ MSG_COLOR_BLACK = MSG_COLORS_START,
+ MSG_COLOR_BLUE,
+ MSG_COLOR_GREEN,
+ MSG_COLOR_CYAN,
+ MSG_COLOR_RED,
+ MSG_COLOR_MAGENTA,
+ MSG_COLOR_YELLOW,
+ MSG_COLOR_WHITE,
+ MSG_COLOR_GREY,
+ MSG_COLORS_END = MSG_COLOR_GREY,
+
+ MSG_STATES_START,
+ MSG_STATE_DOUBLE = MSG_STATES_START,
+ MSG_STATE_SPEAKUP,
+ MSG_STATE_ALT,
+ MSG_STATE_CONTROL,
+ MSG_STATE_ALTGR,
+ MSG_STATE_SHIFT,
+ MSG_STATES_END = MSG_STATE_SHIFT,
+
+ MSG_KEYNAMES_START,
+ MSG_KEYNAME_ESC = MSG_KEYNAMES_START,
+ MSG_KEYNAME_1, MSG_KEYNAME_2, MSG_KEYNAME_3, MSG_KEYNAME_4,
+ MSG_KEYNAME_5, MSG_KEYNAME_6, MSG_KEYNAME_7, MSG_KEYNAME_8,
+ MSG_KEYNAME_9,
+ MSG_KEYNAME_0, MSG_KEYNAME_DASH, MSG_KEYNAME_EQUAL, MSG_KEYNAME_BS,
+ MSG_KEYNAME_TAB,
+ MSG_KEYNAME_Q, MSG_KEYNAME_W, MSG_KEYNAME_E, MSG_KEYNAME_R,
+ MSG_KEYNAME_T, MSG_KEYNAME_Y, MSG_KEYNAME_U, MSG_KEYNAME_I,
+ MSG_KEYNAME_O, MSG_KEYNAME_P,
+ MSG_KEYNAME_LEFTBRACE, MSG_KEYNAME_RIGHTBRACE, MSG_KEYNAME_ENTER,
+ MSG_KEYNAME_LEFTCTRL, MSG_KEYNAME_A,
+ MSG_KEYNAME_S, MSG_KEYNAME_D, MSG_KEYNAME_F, MSG_KEYNAME_G,
+ MSG_KEYNAME_H, MSG_KEYNAME_J, MSG_KEYNAME_K, MSG_KEYNAME_L,
+ MSG_KEYNAME_SEMICOLON,
+ MSG_KEYNAME_SINGLEQUOTE, MSG_KEYNAME_GRAVE,
+ MSG_KEYNAME_LEFTSHFT, MSG_KEYNAME_BACKSLASH, MSG_KEYNAME_Z,
+ MSG_KEYNAME_X, MSG_KEYNAME_C, MSG_KEYNAME_V, MSG_KEYNAME_B,
+ MSG_KEYNAME_N, MSG_KEYNAME_M, MSG_KEYNAME_COMMA, MSG_KEYNAME_DOT,
+ MSG_KEYNAME_SLASH, MSG_KEYNAME_RIGHTSHFT,
+ MSG_KEYNAME_KPSTAR,
+ MSG_KEYNAME_LEFTALT, MSG_KEYNAME_SPACE, MSG_KEYNAME_CAPSLOCK,
+ MSG_KEYNAME_F1, MSG_KEYNAME_F2,
+ MSG_KEYNAME_F3, MSG_KEYNAME_F4, MSG_KEYNAME_F5, MSG_KEYNAME_F6,
+ MSG_KEYNAME_F7,
+ MSG_KEYNAME_F8, MSG_KEYNAME_F9, MSG_KEYNAME_F10, MSG_KEYNAME_NUMLOCK,
+ MSG_KEYNAME_SCROLLLOCK,
+ MSG_KEYNAME_KP7, MSG_KEYNAME_KP8, MSG_KEYNAME_KP9, MSG_KEYNAME_KPMINUS,
+ MSG_KEYNAME_KP4,
+ MSG_KEYNAME_KP5, MSG_KEYNAME_KP6, MSG_KEYNAME_KPPLUS, MSG_KEYNAME_KP1,
+ MSG_KEYNAME_KP2,
+ MSG_KEYNAME_KP3, MSG_KEYNAME_KP0, MSG_KEYNAME_KPDOT, MSG_KEYNAME_103RD,
+ MSG_KEYNAME_F13,
+ MSG_KEYNAME_102ND, MSG_KEYNAME_F11, MSG_KEYNAME_F12, MSG_KEYNAME_F14,
+ MSG_KEYNAME_F15,
+ MSG_KEYNAME_F16, MSG_KEYNAME_F17, MSG_KEYNAME_F18, MSG_KEYNAME_F19,
+ MSG_KEYNAME_F20,
+ MSG_KEYNAME_KPENTER, MSG_KEYNAME_RIGHTCTRL, MSG_KEYNAME_KPSLASH,
+ MSG_KEYNAME_SYSRQ, MSG_KEYNAME_RIGHTALT,
+ MSG_KEYNAME_LF, MSG_KEYNAME_HOME, MSG_KEYNAME_UP, MSG_KEYNAME_PGUP,
+ MSG_KEYNAME_LEFT,
+ MSG_KEYNAME_RIGHT, MSG_KEYNAME_END, MSG_KEYNAME_DOWN, MSG_KEYNAME_PGDN,
+ MSG_KEYNAME_INS,
+ MSG_KEYNAME_DEL, MSG_KEYNAME_MACRO, MSG_KEYNAME_MUTE,
+ MSG_KEYNAME_VOLDOWN, MSG_KEYNAME_VOLUP,
+ MSG_KEYNAME_POWER, MSG_KEYNAME_KPEQUAL, MSG_KEYNAME_KPPLUSDASH,
+ MSG_KEYNAME_PAUSE, MSG_KEYNAME_F21, MSG_KEYNAME_F22, MSG_KEYNAME_F23,
+ MSG_KEYNAME_F24, MSG_KEYNAME_KPCOMMA, MSG_KEYNAME_LEFTMETA,
+ MSG_KEYNAME_RIGHTMETA, MSG_KEYNAME_COMPOSE, MSG_KEYNAME_STOP,
+ MSG_KEYNAME_AGAIN, MSG_KEYNAME_PROPS,
+ MSG_KEYNAME_UNDO, MSG_KEYNAME_FRONT, MSG_KEYNAME_COPY, MSG_KEYNAME_OPEN,
+ MSG_KEYNAME_PASTE,
+ MSG_KEYNAME_FIND, MSG_KEYNAME_CUT, MSG_KEYNAME_HELP, MSG_KEYNAME_MENU,
+ MSG_KEYNAME_CALC,
+ MSG_KEYNAME_SETUP, MSG_KEYNAME_SLEEP, MSG_KEYNAME_WAKEUP,
+ MSG_KEYNAME_FILE, MSG_KEYNAME_SENDFILE,
+ MSG_KEYNAME_DELFILE, MSG_KEYNAME_XFER, MSG_KEYNAME_PROG1,
+ MSG_KEYNAME_PROG2, MSG_KEYNAME_WWW,
+ MSG_KEYNAME_MSDOS, MSG_KEYNAME_COFFEE, MSG_KEYNAME_DIRECTION,
+ MSG_KEYNAME_CYCLEWINDOWS, MSG_KEYNAME_MAIL,
+ MSG_KEYNAME_BOOKMARKS, MSG_KEYNAME_COMPUTER, MSG_KEYNAME_BACK,
+ MSG_KEYNAME_FORWARD, MSG_KEYNAME_CLOSECD,
+ MSG_KEYNAME_EJECTCD, MSG_KEYNAME_EJECTCLOSE, MSG_KEYNAME_NEXTSONG,
+ MSG_KEYNAME_PLAYPAUSE, MSG_KEYNAME_PREVSONG,
+ MSG_KEYNAME_STOPCD, MSG_KEYNAME_RECORD, MSG_KEYNAME_REWIND,
+ MSG_KEYNAME_PHONE, MSG_KEYNAME_ISO,
+ MSG_KEYNAME_CONFIG, MSG_KEYNAME_HOMEPG, MSG_KEYNAME_REFRESH,
+ MSG_KEYNAME_EXIT, MSG_KEYNAME_MOVE,
+ MSG_KEYNAME_EDIT, MSG_KEYNAME_SCROLLUP, MSG_KEYNAME_SCROLLDN,
+ MSG_KEYNAME_KPLEFTPAR, MSG_KEYNAME_KPRIGHTPAR,
+ MSG_KEYNAMES_END = MSG_KEYNAME_KPRIGHTPAR,
+
+ MSG_FUNCNAMES_START,
+ MSG_FUNCNAME_ATTRIB_BLEEP_DEC = MSG_FUNCNAMES_START,
+ MSG_FUNCNAME_ATTRIB_BLEEP_INC,
+ MSG_FUNCNAME_BLEEPS_DEC, MSG_FUNCNAME_BLEEPS_INC,
+ MSG_FUNCNAME_CHAR_FIRST, MSG_FUNCNAME_CHAR_LAST,
+ MSG_FUNCNAME_CHAR_CURRENT, MSG_FUNCNAME_CHAR_HEX_AND_DEC,
+ MSG_FUNCNAME_CHAR_NEXT,
+ MSG_FUNCNAME_CHAR_PHONETIC, MSG_FUNCNAME_CHAR_PREVIOUS,
+ MSG_FUNCNAME_CURSOR_PARK, MSG_FUNCNAME_CUT,
+ MSG_FUNCNAME_EDIT_DELIM, MSG_FUNCNAME_EDIT_EXNUM,
+ MSG_FUNCNAME_EDIT_MOST, MSG_FUNCNAME_EDIT_REPEATS,
+ MSG_FUNCNAME_EDIT_SOME,
+ MSG_FUNCNAME_GOTO, MSG_FUNCNAME_GOTO_BOTTOM, MSG_FUNCNAME_GOTO_LEFT,
+ MSG_FUNCNAME_GOTO_RIGHT, MSG_FUNCNAME_GOTO_TOP, MSG_FUNCNAME_HELP,
+ MSG_FUNCNAME_LINE_SAY_CURRENT, MSG_FUNCNAME_LINE_SAY_NEXT,
+ MSG_FUNCNAME_LINE_SAY_PREVIOUS, MSG_FUNCNAME_LINE_SAY_WITH_INDENT,
+ MSG_FUNCNAME_PASTE, MSG_FUNCNAME_PITCH_DEC, MSG_FUNCNAME_PITCH_INC,
+ MSG_FUNCNAME_PUNC_DEC, MSG_FUNCNAME_PUNC_INC,
+ MSG_FUNCNAME_PUNC_LEVEL_DEC, MSG_FUNCNAME_PUNC_LEVEL_INC,
+ MSG_FUNCNAME_QUIET,
+ MSG_FUNCNAME_RATE_DEC, MSG_FUNCNAME_RATE_INC,
+ MSG_FUNCNAME_READING_PUNC_DEC, MSG_FUNCNAME_READING_PUNC_INC,
+ MSG_FUNCNAME_SAY_ATTRIBUTES,
+ MSG_FUNCNAME_SAY_FROM_LEFT, MSG_FUNCNAME_SAY_FROM_TOP,
+ MSG_FUNCNAME_SAY_POSITION, MSG_FUNCNAME_SAY_SCREEN,
+ MSG_FUNCNAME_SAY_TO_BOTTOM, MSG_FUNCNAME_SAY_TO_RIGHT,
+ MSG_FUNCNAME_SPEAKUP, MSG_FUNCNAME_SPEAKUP_LOCK,
+ MSG_FUNCNAME_SPEAKUP_OFF, MSG_FUNCNAME_SPEECH_KILL,
+ MSG_FUNCNAME_SPELL_DELAY_DEC, MSG_FUNCNAME_SPELL_DELAY_INC,
+ MSG_FUNCNAME_SPELL_WORD, MSG_FUNCNAME_SPELL_WORD_PHONETICALLY,
+ MSG_FUNCNAME_TONE_DEC, MSG_FUNCNAME_TONE_INC,
+ MSG_FUNCNAME_VOICE_DEC, MSG_FUNCNAME_VOICE_INC,
+ MSG_FUNCNAME_VOLUME_DEC, MSG_FUNCNAME_VOLUME_INC,
+ MSG_FUNCNAME_WINDOW_CLEAR, MSG_FUNCNAME_WINDOW_SAY,
+ MSG_FUNCNAME_WINDOW_SET, MSG_FUNCNAME_WINDOW_SILENCE,
+ MSG_FUNCNAME_WORD_SAY_CURRENT, MSG_FUNCNAME_WORD_SAY_NEXT,
+ MSG_FUNCNAME_WORD_SAY_PREVIOUS,
+ MSG_FUNCNAMES_END = MSG_FUNCNAME_WORD_SAY_PREVIOUS,
+
+ /* all valid indices must be above this */
+ MSG_LAST_INDEX
+};
+
+struct msg_group_t {
+ char *name;
+ enum msg_index_t start;
+ enum msg_index_t end;
+};
+
+char *spk_msg_get(enum msg_index_t index);
+ssize_t spk_msg_set(enum msg_index_t index, char *text, size_t length);
+struct msg_group_t *spk_find_msg_group(const char *group_name);
+void spk_reset_msg_group(struct msg_group_t *group);
+void spk_initialize_msgs(void);
+void spk_free_user_msgs(void);
+
+#endif
diff --git a/drivers/accessibility/speakup/keyhelp.c b/drivers/accessibility/speakup/keyhelp.c
new file mode 100644
index 000000000..822ceac83
--- /dev/null
+++ b/drivers/accessibility/speakup/keyhelp.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* speakup_keyhelp.c
+ * help module for speakup
+ *
+ *written by David Borowski.
+ *
+ * Copyright (C) 2003 David Borowski.
+ */
+
+#include <linux/keyboard.h>
+#include "spk_priv.h"
+#include "speakup.h"
+
+#define MAXFUNCS 130
+#define MAXKEYS 256
+static const int num_key_names = MSG_KEYNAMES_END - MSG_KEYNAMES_START + 1;
+static u_short key_offsets[MAXFUNCS], key_data[MAXKEYS];
+static u_short masks[] = { 32, 16, 8, 4, 2, 1 };
+
+static short letter_offsets[26] = {
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1 };
+
+static u_char funcvals[] = {
+ ATTRIB_BLEEP_DEC, ATTRIB_BLEEP_INC, BLEEPS_DEC, BLEEPS_INC,
+ SAY_FIRST_CHAR, SAY_LAST_CHAR, SAY_CHAR, SAY_CHAR_NUM,
+ SAY_NEXT_CHAR, SAY_PHONETIC_CHAR, SAY_PREV_CHAR, SPEAKUP_PARKED,
+ SPEAKUP_CUT, EDIT_DELIM, EDIT_EXNUM, EDIT_MOST,
+ EDIT_REPEAT, EDIT_SOME, SPEAKUP_GOTO, BOTTOM_EDGE,
+ LEFT_EDGE, RIGHT_EDGE, TOP_EDGE, SPEAKUP_HELP,
+ SAY_LINE, SAY_NEXT_LINE, SAY_PREV_LINE, SAY_LINE_INDENT,
+ SPEAKUP_PASTE, PITCH_DEC, PITCH_INC, PUNCT_DEC,
+ PUNCT_INC, PUNC_LEVEL_DEC, PUNC_LEVEL_INC, SPEAKUP_QUIET,
+ RATE_DEC, RATE_INC, READING_PUNC_DEC, READING_PUNC_INC,
+ SAY_ATTRIBUTES, SAY_FROM_LEFT, SAY_FROM_TOP, SAY_POSITION,
+ SAY_SCREEN, SAY_TO_BOTTOM, SAY_TO_RIGHT, SPK_KEY,
+ SPK_LOCK, SPEAKUP_OFF, SPEECH_KILL, SPELL_DELAY_DEC,
+ SPELL_DELAY_INC, SPELL_WORD, SPELL_PHONETIC, TONE_DEC,
+ TONE_INC, VOICE_DEC, VOICE_INC, VOL_DEC,
+ VOL_INC, CLEAR_WIN, SAY_WIN, SET_WIN,
+ ENABLE_WIN, SAY_WORD, SAY_NEXT_WORD, SAY_PREV_WORD, 0
+};
+
+static u_char *state_tbl;
+static int cur_item, nstates;
+
+static void build_key_data(void)
+{
+ u_char *kp, counters[MAXFUNCS], ch, ch1;
+ u_short *p_key, key;
+ int i, offset = 1;
+
+ nstates = (int)(state_tbl[-1]);
+ memset(counters, 0, sizeof(counters));
+ memset(key_offsets, 0, sizeof(key_offsets));
+ kp = state_tbl + nstates + 1;
+ while (*kp++) {
+ /* count occurrences of each function */
+ for (i = 0; i < nstates; i++, kp++) {
+ if (!*kp)
+ continue;
+ if ((state_tbl[i] & 16) != 0 && *kp == SPK_KEY)
+ continue;
+ counters[*kp]++;
+ }
+ }
+ for (i = 0; i < MAXFUNCS; i++) {
+ if (counters[i] == 0)
+ continue;
+ key_offsets[i] = offset;
+ offset += (counters[i] + 1);
+ if (offset >= MAXKEYS)
+ break;
+ }
+/* leave counters set so high keycodes come first.
+ * this is done so num pad and other extended keys maps are spoken before
+ * the alpha with speakup type mapping.
+ */
+ kp = state_tbl + nstates + 1;
+ while ((ch = *kp++)) {
+ for (i = 0; i < nstates; i++) {
+ ch1 = *kp++;
+ if (!ch1)
+ continue;
+ if ((state_tbl[i] & 16) != 0 && ch1 == SPK_KEY)
+ continue;
+ key = (state_tbl[i] << 8) + ch;
+ counters[ch1]--;
+ offset = key_offsets[ch1];
+ if (!offset)
+ continue;
+ p_key = key_data + offset + counters[ch1];
+ *p_key = key;
+ }
+ }
+}
+
+static void say_key(int key)
+{
+ int i, state = key >> 8;
+
+ key &= 0xff;
+ for (i = 0; i < 6; i++) {
+ if (state & masks[i])
+ synth_printf(" %s", spk_msg_get(MSG_STATES_START + i));
+ }
+ if ((key > 0) && (key <= num_key_names))
+ synth_printf(" %s\n",
+ spk_msg_get(MSG_KEYNAMES_START + (key - 1)));
+}
+
+static int help_init(void)
+{
+ char start = SPACE;
+ int i;
+ int num_funcs = MSG_FUNCNAMES_END - MSG_FUNCNAMES_START + 1;
+
+ state_tbl = spk_our_keys[0] + SHIFT_TBL_SIZE + 2;
+ for (i = 0; i < num_funcs; i++) {
+ char *cur_funcname = spk_msg_get(MSG_FUNCNAMES_START + i);
+
+ if (start == *cur_funcname)
+ continue;
+ start = *cur_funcname;
+ letter_offsets[(start & 31) - 1] = i;
+ }
+ return 0;
+}
+
+int spk_handle_help(struct vc_data *vc, u_char type, u_char ch, u_short key)
+{
+ int i, n;
+ char *name;
+ u_char func, *kp;
+ u_short *p_keys, val;
+
+ if (letter_offsets[0] == -1)
+ help_init();
+ if (type == KT_LATIN) {
+ if (ch == SPACE) {
+ spk_special_handler = NULL;
+ synth_printf("%s\n", spk_msg_get(MSG_LEAVING_HELP));
+ return 1;
+ }
+ ch |= 32; /* lower case */
+ if (ch < 'a' || ch > 'z')
+ return -1;
+ if (letter_offsets[ch - 'a'] == -1) {
+ synth_printf(spk_msg_get(MSG_NO_COMMAND), ch);
+ synth_printf("\n");
+ return 1;
+ }
+ cur_item = letter_offsets[ch - 'a'];
+ } else if (type == KT_CUR) {
+ if (ch == 0 &&
+ (MSG_FUNCNAMES_START + cur_item + 1) <= MSG_FUNCNAMES_END)
+ cur_item++;
+ else if (ch == 3 && cur_item > 0)
+ cur_item--;
+ else
+ return -1;
+ } else if (type == KT_SPKUP && ch == SPEAKUP_HELP &&
+ !spk_special_handler) {
+ spk_special_handler = spk_handle_help;
+ synth_printf("%s\n", spk_msg_get(MSG_HELP_INFO));
+ build_key_data(); /* rebuild each time in case new mapping */
+ return 1;
+ } else {
+ name = NULL;
+ if ((type != KT_SPKUP) && (key > 0) && (key <= num_key_names)) {
+ synth_printf("%s\n",
+ spk_msg_get(MSG_KEYNAMES_START + key - 1));
+ return 1;
+ }
+ for (i = 0; funcvals[i] != 0 && !name; i++) {
+ if (ch == funcvals[i])
+ name = spk_msg_get(MSG_FUNCNAMES_START + i);
+ }
+ if (!name)
+ return -1;
+ kp = spk_our_keys[key] + 1;
+ for (i = 0; i < nstates; i++) {
+ if (ch == kp[i])
+ break;
+ }
+ key += (state_tbl[i] << 8);
+ say_key(key);
+ synth_printf(spk_msg_get(MSG_KEYDESC), name);
+ synth_printf("\n");
+ return 1;
+ }
+ name = spk_msg_get(MSG_FUNCNAMES_START + cur_item);
+ func = funcvals[cur_item];
+ synth_printf("%s", name);
+ if (key_offsets[func] == 0) {
+ synth_printf(" %s\n", spk_msg_get(MSG_IS_UNASSIGNED));
+ return 1;
+ }
+ p_keys = key_data + key_offsets[func];
+ for (n = 0; p_keys[n]; n++) {
+ val = p_keys[n];
+ if (n > 0)
+ synth_printf("%s ", spk_msg_get(MSG_DISJUNCTION));
+ say_key(val);
+ }
+ return 1;
+}
diff --git a/drivers/accessibility/speakup/kobjects.c b/drivers/accessibility/speakup/kobjects.c
new file mode 100644
index 000000000..41ae24ab5
--- /dev/null
+++ b/drivers/accessibility/speakup/kobjects.c
@@ -0,0 +1,1056 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Speakup kobject implementation
+ *
+ * Copyright (C) 2009 William Hubbs
+ *
+ * This code is based on kobject-example.c, which came with linux 2.6.x.
+ *
+ * Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2007 Novell Inc.
+ *
+ * Released under the GPL version 2 only.
+ *
+ */
+#include <linux/slab.h> /* For kmalloc. */
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/string.h>
+#include <linux/string_helpers.h>
+#include <linux/sysfs.h>
+#include <linux/ctype.h>
+
+#include "speakup.h"
+#include "spk_priv.h"
+
+/*
+ * This is called when a user reads the characters or chartab sys file.
+ */
+static ssize_t chars_chartab_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int i;
+ int len = 0;
+ char *cp;
+ char *buf_pointer = buf;
+ size_t bufsize = PAGE_SIZE;
+ unsigned long flags;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ *buf_pointer = '\0';
+ for (i = 0; i < 256; i++) {
+ if (bufsize <= 1)
+ break;
+ if (strcmp("characters", attr->attr.name) == 0) {
+ len = scnprintf(buf_pointer, bufsize, "%d\t%s\n",
+ i, spk_characters[i]);
+ } else { /* show chartab entry */
+ if (IS_TYPE(i, B_CTL))
+ cp = "B_CTL";
+ else if (IS_TYPE(i, WDLM))
+ cp = "WDLM";
+ else if (IS_TYPE(i, A_PUNC))
+ cp = "A_PUNC";
+ else if (IS_TYPE(i, PUNC))
+ cp = "PUNC";
+ else if (IS_TYPE(i, NUM))
+ cp = "NUM";
+ else if (IS_TYPE(i, A_CAP))
+ cp = "A_CAP";
+ else if (IS_TYPE(i, ALPHA))
+ cp = "ALPHA";
+ else if (IS_TYPE(i, B_CAPSYM))
+ cp = "B_CAPSYM";
+ else if (IS_TYPE(i, B_SYM))
+ cp = "B_SYM";
+ else
+ cp = "0";
+ len =
+ scnprintf(buf_pointer, bufsize, "%d\t%s\n", i, cp);
+ }
+ bufsize -= len;
+ buf_pointer += len;
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return buf_pointer - buf;
+}
+
+/*
+ * Print informational messages or warnings after updating
+ * character descriptions or chartab entries.
+ */
+static void report_char_chartab_status(int reset, int received, int used,
+ int rejected, int do_characters)
+{
+ static char const *object_type[] = {
+ "character class entries",
+ "character descriptions",
+ };
+ int len;
+ char buf[80];
+
+ if (reset) {
+ pr_info("%s reset to defaults\n", object_type[do_characters]);
+ } else if (received) {
+ len = snprintf(buf, sizeof(buf),
+ " updated %d of %d %s\n",
+ used, received, object_type[do_characters]);
+ if (rejected)
+ snprintf(buf + (len - 1), sizeof(buf) - (len - 1),
+ " with %d reject%s\n",
+ rejected, rejected > 1 ? "s" : "");
+ pr_info("%s", buf);
+ }
+}
+
+/*
+ * This is called when a user changes the characters or chartab parameters.
+ */
+static ssize_t chars_chartab_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ char *cp = (char *)buf;
+ char *end = cp + count; /* the null at the end of the buffer */
+ char *linefeed = NULL;
+ char keyword[MAX_DESC_LEN + 1];
+ char *outptr = NULL; /* Will hold keyword or desc. */
+ char *temp = NULL;
+ char *desc = NULL;
+ ssize_t retval = count;
+ unsigned long flags;
+ unsigned long index = 0;
+ int charclass = 0;
+ int received = 0;
+ int used = 0;
+ int rejected = 0;
+ int reset = 0;
+ int do_characters = !strcmp(attr->attr.name, "characters");
+ size_t desc_length = 0;
+ int i;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ while (cp < end) {
+ while ((cp < end) && (*cp == ' ' || *cp == '\t'))
+ cp++;
+
+ if (cp == end)
+ break;
+ if ((*cp == '\n') || strchr("dDrR", *cp)) {
+ reset = 1;
+ break;
+ }
+ received++;
+
+ linefeed = strchr(cp, '\n');
+ if (!linefeed) {
+ rejected++;
+ break;
+ }
+
+ if (!isdigit(*cp)) {
+ rejected++;
+ cp = linefeed + 1;
+ continue;
+ }
+
+ /*
+ * Do not replace with kstrtoul:
+ * here we need temp to be updated
+ */
+ index = simple_strtoul(cp, &temp, 10);
+ if (index > 255) {
+ rejected++;
+ cp = linefeed + 1;
+ continue;
+ }
+
+ while ((temp < linefeed) && (*temp == ' ' || *temp == '\t'))
+ temp++;
+
+ desc_length = linefeed - temp;
+ if (desc_length > MAX_DESC_LEN) {
+ rejected++;
+ cp = linefeed + 1;
+ continue;
+ }
+ if (do_characters) {
+ desc = kmalloc(desc_length + 1, GFP_ATOMIC);
+ if (!desc) {
+ retval = -ENOMEM;
+ reset = 1; /* just reset on error. */
+ break;
+ }
+ outptr = desc;
+ } else {
+ outptr = keyword;
+ }
+
+ for (i = 0; i < desc_length; i++)
+ outptr[i] = temp[i];
+ outptr[desc_length] = '\0';
+
+ if (do_characters) {
+ if (spk_characters[index] != spk_default_chars[index])
+ kfree(spk_characters[index]);
+ spk_characters[index] = desc;
+ used++;
+ } else {
+ charclass = spk_chartab_get_value(keyword);
+ if (charclass == 0) {
+ rejected++;
+ cp = linefeed + 1;
+ continue;
+ }
+ if (charclass != spk_chartab[index]) {
+ spk_chartab[index] = charclass;
+ used++;
+ }
+ }
+ cp = linefeed + 1;
+ }
+
+ if (reset) {
+ if (do_characters)
+ spk_reset_default_chars();
+ else
+ spk_reset_default_chartab();
+ }
+
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ report_char_chartab_status(reset, received, used, rejected,
+ do_characters);
+ return retval;
+}
+
+/*
+ * This is called when a user reads the keymap parameter.
+ */
+static ssize_t keymap_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ char *cp = buf;
+ int i;
+ int n;
+ int num_keys;
+ int nstates;
+ u_char *cp1;
+ u_char ch;
+ unsigned long flags;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ cp1 = spk_key_buf + SHIFT_TBL_SIZE;
+ num_keys = (int)(*cp1);
+ nstates = (int)cp1[1];
+ cp += sprintf(cp, "%d, %d, %d,\n", KEY_MAP_VER, num_keys, nstates);
+ cp1 += 2; /* now pointing at shift states */
+ /* dump num_keys+1 as first row is shift states + flags,
+ * each subsequent row is key + states
+ */
+ for (n = 0; n <= num_keys; n++) {
+ for (i = 0; i <= nstates; i++) {
+ ch = *cp1++;
+ cp += sprintf(cp, "%d,", (int)ch);
+ *cp++ = (i < nstates) ? SPACE : '\n';
+ }
+ }
+ cp += sprintf(cp, "0, %d\n", KEY_MAP_VER);
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return (int)(cp - buf);
+}
+
+/*
+ * This is called when a user changes the keymap parameter.
+ */
+static ssize_t keymap_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int i;
+ ssize_t ret = count;
+ char *in_buff = NULL;
+ char *cp;
+ u_char *cp1;
+ unsigned long flags;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ in_buff = kmemdup(buf, count + 1, GFP_ATOMIC);
+ if (!in_buff) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return -ENOMEM;
+ }
+ if (strchr("dDrR", *in_buff)) {
+ spk_set_key_info(spk_key_defaults, spk_key_buf);
+ pr_info("keymap set to default values\n");
+ kfree(in_buff);
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return count;
+ }
+ if (in_buff[count - 1] == '\n')
+ in_buff[count - 1] = '\0';
+ cp = in_buff;
+ cp1 = (u_char *)in_buff;
+ for (i = 0; i < 3; i++) {
+ cp = spk_s2uchar(cp, cp1);
+ cp1++;
+ }
+ i = (int)cp1[-2] + 1;
+ i *= (int)cp1[-1] + 1;
+ i += 2; /* 0 and last map ver */
+ if (cp1[-3] != KEY_MAP_VER || cp1[-1] > 10 ||
+ i + SHIFT_TBL_SIZE + 4 >= sizeof(spk_key_buf)) {
+ pr_warn("i %d %d %d %d\n", i,
+ (int)cp1[-3], (int)cp1[-2], (int)cp1[-1]);
+ kfree(in_buff);
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return -EINVAL;
+ }
+ while (--i >= 0) {
+ cp = spk_s2uchar(cp, cp1);
+ cp1++;
+ if (!(*cp))
+ break;
+ }
+ if (i != 0 || cp1[-1] != KEY_MAP_VER || cp1[-2] != 0) {
+ ret = -EINVAL;
+ pr_warn("end %d %d %d %d\n", i,
+ (int)cp1[-3], (int)cp1[-2], (int)cp1[-1]);
+ } else {
+ if (spk_set_key_info(in_buff, spk_key_buf)) {
+ spk_set_key_info(spk_key_defaults, spk_key_buf);
+ ret = -EINVAL;
+ pr_warn("set key failed\n");
+ }
+ }
+ kfree(in_buff);
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return ret;
+}
+
+/*
+ * This is called when a user changes the value of the silent parameter.
+ */
+static ssize_t silent_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int len;
+ struct vc_data *vc = vc_cons[fg_console].d;
+ char ch = 0;
+ char shut;
+ unsigned long flags;
+
+ len = strlen(buf);
+ if (len > 0 && len < 3) {
+ ch = buf[0];
+ if (ch == '\n')
+ ch = '0';
+ }
+ if (ch < '0' || ch > '7') {
+ pr_warn("silent value '%c' not in range (0,7)\n", ch);
+ return -EINVAL;
+ }
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (ch & 2) {
+ shut = 1;
+ spk_do_flush();
+ } else {
+ shut = 0;
+ }
+ if (ch & 4)
+ shut |= 0x40;
+ if (ch & 1)
+ spk_shut_up |= shut;
+ else
+ spk_shut_up &= ~shut;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return count;
+}
+
+/*
+ * This is called when a user reads the synth setting.
+ */
+static ssize_t synth_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ int rv;
+
+ if (!synth)
+ rv = sprintf(buf, "%s\n", "none");
+ else
+ rv = sprintf(buf, "%s\n", synth->name);
+ return rv;
+}
+
+/*
+ * This is called when a user requests to change synthesizers.
+ */
+static ssize_t synth_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int len;
+ char new_synth_name[10];
+
+ len = strlen(buf);
+ if (len < 2 || len > 9)
+ return -EINVAL;
+ memcpy(new_synth_name, buf, len);
+ if (new_synth_name[len - 1] == '\n')
+ len--;
+ new_synth_name[len] = '\0';
+ spk_strlwr(new_synth_name);
+ if (synth && !strcmp(new_synth_name, synth->name)) {
+ pr_warn("%s already in use\n", new_synth_name);
+ } else if (synth_init(new_synth_name) != 0) {
+ pr_warn("failed to init synth %s\n", new_synth_name);
+ return -ENODEV;
+ }
+ return count;
+}
+
+/*
+ * This is called when text is sent to the synth via the synth_direct file.
+ */
+static ssize_t synth_direct_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ u_char tmp[256];
+ int len;
+ int bytes;
+ const char *ptr = buf;
+ unsigned long flags;
+
+ if (!synth)
+ return -EPERM;
+
+ len = strlen(buf);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ while (len > 0) {
+ bytes = min_t(size_t, len, 250);
+ strncpy(tmp, ptr, bytes);
+ tmp[bytes] = '\0';
+ string_unescape_any_inplace(tmp);
+ synth_printf("%s", tmp);
+ ptr += bytes;
+ len -= bytes;
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return count;
+}
+
+/*
+ * This function is called when a user reads the version.
+ */
+static ssize_t version_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ char *cp;
+
+ cp = buf;
+ cp += sprintf(cp, "Speakup version %s\n", SPEAKUP_VERSION);
+ if (synth)
+ cp += sprintf(cp, "%s synthesizer driver version %s\n",
+ synth->name, synth->version);
+ return cp - buf;
+}
+
+/*
+ * This is called when a user reads the punctuation settings.
+ */
+static ssize_t punc_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ int i;
+ char *cp = buf;
+ struct st_var_header *p_header;
+ struct punc_var_t *var;
+ struct st_bits_data *pb;
+ short mask;
+ unsigned long flags;
+
+ p_header = spk_var_header_by_name(attr->attr.name);
+ if (!p_header) {
+ pr_warn("p_header is null, attr->attr.name is %s\n",
+ attr->attr.name);
+ return -EINVAL;
+ }
+
+ var = spk_get_punc_var(p_header->var_id);
+ if (!var) {
+ pr_warn("var is null, p_header->var_id is %i\n",
+ p_header->var_id);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ pb = (struct st_bits_data *)&spk_punc_info[var->value];
+ mask = pb->mask;
+ for (i = 33; i < 128; i++) {
+ if (!(spk_chartab[i] & mask))
+ continue;
+ *cp++ = (char)i;
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return cp - buf;
+}
+
+/*
+ * This is called when a user changes the punctuation settings.
+ */
+static ssize_t punc_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int x;
+ struct st_var_header *p_header;
+ struct punc_var_t *var;
+ char punc_buf[100];
+ unsigned long flags;
+
+ x = strlen(buf);
+ if (x < 1 || x > 99)
+ return -EINVAL;
+
+ p_header = spk_var_header_by_name(attr->attr.name);
+ if (!p_header) {
+ pr_warn("p_header is null, attr->attr.name is %s\n",
+ attr->attr.name);
+ return -EINVAL;
+ }
+
+ var = spk_get_punc_var(p_header->var_id);
+ if (!var) {
+ pr_warn("var is null, p_header->var_id is %i\n",
+ p_header->var_id);
+ return -EINVAL;
+ }
+
+ memcpy(punc_buf, buf, x);
+
+ while (x && punc_buf[x - 1] == '\n')
+ x--;
+ punc_buf[x] = '\0';
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+
+ if (*punc_buf == 'd' || *punc_buf == 'r')
+ x = spk_set_mask_bits(NULL, var->value, 3);
+ else
+ x = spk_set_mask_bits(punc_buf, var->value, 3);
+
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return count;
+}
+
+/*
+ * This function is called when a user reads one of the variable parameters.
+ */
+ssize_t spk_var_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ int rv = 0;
+ struct st_var_header *param;
+ struct var_t *var;
+ char *cp1;
+ char *cp;
+ char ch;
+ unsigned long flags;
+
+ param = spk_var_header_by_name(attr->attr.name);
+ if (!param)
+ return -EINVAL;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ var = (struct var_t *)param->data;
+ switch (param->var_type) {
+ case VAR_NUM:
+ case VAR_TIME:
+ if (var)
+ rv = sprintf(buf, "%i\n", var->u.n.value);
+ else
+ rv = sprintf(buf, "0\n");
+ break;
+ case VAR_STRING:
+ if (var) {
+ cp1 = buf;
+ *cp1++ = '"';
+ for (cp = (char *)param->p_val; (ch = *cp); cp++) {
+ if (ch >= ' ' && ch < '~')
+ *cp1++ = ch;
+ else
+ cp1 += sprintf(cp1, "\\x%02x", ch);
+ }
+ *cp1++ = '"';
+ *cp1++ = '\n';
+ *cp1 = '\0';
+ rv = cp1 - buf;
+ } else {
+ rv = sprintf(buf, "\"\"\n");
+ }
+ break;
+ default:
+ rv = sprintf(buf, "Bad parameter %s, type %i\n",
+ param->name, param->var_type);
+ break;
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return rv;
+}
+EXPORT_SYMBOL_GPL(spk_var_show);
+
+/*
+ * Used to reset either default_pitch or default_vol.
+ */
+static inline void spk_reset_default_value(char *header_name,
+ int *synth_default_value, int idx)
+{
+ struct st_var_header *param;
+
+ if (synth && synth_default_value) {
+ param = spk_var_header_by_name(header_name);
+ if (param) {
+ spk_set_num_var(synth_default_value[idx],
+ param, E_NEW_DEFAULT);
+ spk_set_num_var(0, param, E_DEFAULT);
+ pr_info("%s reset to default value\n", param->name);
+ }
+ }
+}
+
+/*
+ * This function is called when a user echos a value to one of the
+ * variable parameters.
+ */
+ssize_t spk_var_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct st_var_header *param;
+ int ret;
+ int len;
+ char *cp;
+ struct var_t *var_data;
+ long value;
+ unsigned long flags;
+
+ param = spk_var_header_by_name(attr->attr.name);
+ if (!param)
+ return -EINVAL;
+ if (!param->data)
+ return 0;
+ ret = 0;
+ cp = (char *)buf;
+ string_unescape_any_inplace(cp);
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ switch (param->var_type) {
+ case VAR_NUM:
+ case VAR_TIME:
+ if (*cp == 'd' || *cp == 'r' || *cp == '\0')
+ len = E_DEFAULT;
+ else if (*cp == '+' || *cp == '-')
+ len = E_INC;
+ else
+ len = E_SET;
+ if (kstrtol(cp, 10, &value) == 0)
+ ret = spk_set_num_var(value, param, len);
+ else
+ pr_warn("overflow or parsing error has occurred");
+ if (ret == -ERANGE) {
+ var_data = param->data;
+ pr_warn("value for %s out of range, expect %d to %d\n",
+ param->name,
+ var_data->u.n.low, var_data->u.n.high);
+ }
+
+ /*
+ * If voice was just changed, we might need to reset our default
+ * pitch and volume.
+ */
+ if (param->var_id == VOICE && synth &&
+ (ret == 0 || ret == -ERESTART)) {
+ var_data = param->data;
+ value = var_data->u.n.value;
+ spk_reset_default_value("pitch", synth->default_pitch,
+ value);
+ spk_reset_default_value("vol", synth->default_vol,
+ value);
+ }
+ break;
+ case VAR_STRING:
+ len = strlen(cp);
+ if ((len >= 1) && (cp[len - 1] == '\n'))
+ --len;
+ if ((len >= 2) && (cp[0] == '"') && (cp[len - 1] == '"')) {
+ ++cp;
+ len -= 2;
+ }
+ cp[len] = '\0';
+ ret = spk_set_string_var(cp, param, len);
+ if (ret == -E2BIG)
+ pr_warn("value too long for %s\n",
+ param->name);
+ break;
+ default:
+ pr_warn("%s unknown type %d\n",
+ param->name, (int)param->var_type);
+ break;
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+
+ if (ret == -ERESTART)
+ pr_info("%s reset to default value\n", param->name);
+ return count;
+}
+EXPORT_SYMBOL_GPL(spk_var_store);
+
+/*
+ * Functions for reading and writing lists of i18n messages. Incomplete.
+ */
+
+static ssize_t message_show_helper(char *buf, enum msg_index_t first,
+ enum msg_index_t last)
+{
+ size_t bufsize = PAGE_SIZE;
+ char *buf_pointer = buf;
+ int printed;
+ enum msg_index_t cursor;
+ int index = 0;
+ *buf_pointer = '\0'; /* buf_pointer always looking at a NUL byte. */
+
+ for (cursor = first; cursor <= last; cursor++, index++) {
+ if (bufsize <= 1)
+ break;
+ printed = scnprintf(buf_pointer, bufsize, "%d\t%s\n",
+ index, spk_msg_get(cursor));
+ buf_pointer += printed;
+ bufsize -= printed;
+ }
+
+ return buf_pointer - buf;
+}
+
+static void report_msg_status(int reset, int received, int used,
+ int rejected, char *groupname)
+{
+ int len;
+ char buf[160];
+
+ if (reset) {
+ pr_info("i18n messages from group %s reset to defaults\n",
+ groupname);
+ } else if (received) {
+ len = snprintf(buf, sizeof(buf),
+ " updated %d of %d i18n messages from group %s\n",
+ used, received, groupname);
+ if (rejected)
+ snprintf(buf + (len - 1), sizeof(buf) - (len - 1),
+ " with %d reject%s\n",
+ rejected, rejected > 1 ? "s" : "");
+ pr_info("%s", buf);
+ }
+}
+
+static ssize_t message_store_helper(const char *buf, size_t count,
+ struct msg_group_t *group)
+{
+ char *cp = (char *)buf;
+ char *end = cp + count;
+ char *linefeed = NULL;
+ char *temp = NULL;
+ ssize_t msg_stored = 0;
+ ssize_t retval = count;
+ size_t desc_length = 0;
+ unsigned long index = 0;
+ int received = 0;
+ int used = 0;
+ int rejected = 0;
+ int reset = 0;
+ enum msg_index_t firstmessage = group->start;
+ enum msg_index_t lastmessage = group->end;
+ enum msg_index_t curmessage;
+
+ while (cp < end) {
+ while ((cp < end) && (*cp == ' ' || *cp == '\t'))
+ cp++;
+
+ if (cp == end)
+ break;
+ if (strchr("dDrR", *cp)) {
+ reset = 1;
+ break;
+ }
+ received++;
+
+ linefeed = strchr(cp, '\n');
+ if (!linefeed) {
+ rejected++;
+ break;
+ }
+
+ if (!isdigit(*cp)) {
+ rejected++;
+ cp = linefeed + 1;
+ continue;
+ }
+
+ /*
+ * Do not replace with kstrtoul:
+ * here we need temp to be updated
+ */
+ index = simple_strtoul(cp, &temp, 10);
+
+ while ((temp < linefeed) && (*temp == ' ' || *temp == '\t'))
+ temp++;
+
+ desc_length = linefeed - temp;
+ curmessage = firstmessage + index;
+
+ /*
+ * Note the check (curmessage < firstmessage). It is not
+ * redundant. Suppose that the user gave us an index
+ * equal to ULONG_MAX - 1. If firstmessage > 1, then
+ * firstmessage + index < firstmessage!
+ */
+
+ if ((curmessage < firstmessage) || (curmessage > lastmessage)) {
+ rejected++;
+ cp = linefeed + 1;
+ continue;
+ }
+
+ msg_stored = spk_msg_set(curmessage, temp, desc_length);
+ if (msg_stored < 0) {
+ retval = msg_stored;
+ if (msg_stored == -ENOMEM)
+ reset = 1;
+ break;
+ }
+
+ used++;
+
+ cp = linefeed + 1;
+ }
+
+ if (reset)
+ spk_reset_msg_group(group);
+
+ report_msg_status(reset, received, used, rejected, group->name);
+ return retval;
+}
+
+static ssize_t message_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ ssize_t retval = 0;
+ struct msg_group_t *group = spk_find_msg_group(attr->attr.name);
+ unsigned long flags;
+
+ if (WARN_ON(!group))
+ return -EINVAL;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ retval = message_show_helper(buf, group->start, group->end);
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return retval;
+}
+
+static ssize_t message_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct msg_group_t *group = spk_find_msg_group(attr->attr.name);
+
+ if (WARN_ON(!group))
+ return -EINVAL;
+
+ return message_store_helper(buf, count, group);
+}
+
+/*
+ * Declare the attributes.
+ */
+static struct kobj_attribute keymap_attribute =
+ __ATTR_RW(keymap);
+static struct kobj_attribute silent_attribute =
+ __ATTR_WO(silent);
+static struct kobj_attribute synth_attribute =
+ __ATTR_RW(synth);
+static struct kobj_attribute synth_direct_attribute =
+ __ATTR_WO(synth_direct);
+static struct kobj_attribute version_attribute =
+ __ATTR_RO(version);
+
+static struct kobj_attribute delimiters_attribute =
+ __ATTR(delimiters, 0644, punc_show, punc_store);
+static struct kobj_attribute ex_num_attribute =
+ __ATTR(ex_num, 0644, punc_show, punc_store);
+static struct kobj_attribute punc_all_attribute =
+ __ATTR(punc_all, 0644, punc_show, punc_store);
+static struct kobj_attribute punc_most_attribute =
+ __ATTR(punc_most, 0644, punc_show, punc_store);
+static struct kobj_attribute punc_some_attribute =
+ __ATTR(punc_some, 0644, punc_show, punc_store);
+static struct kobj_attribute repeats_attribute =
+ __ATTR(repeats, 0644, punc_show, punc_store);
+
+static struct kobj_attribute attrib_bleep_attribute =
+ __ATTR(attrib_bleep, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute bell_pos_attribute =
+ __ATTR(bell_pos, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute bleep_time_attribute =
+ __ATTR(bleep_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute bleeps_attribute =
+ __ATTR(bleeps, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute cursor_time_attribute =
+ __ATTR(cursor_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute key_echo_attribute =
+ __ATTR(key_echo, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute no_interrupt_attribute =
+ __ATTR(no_interrupt, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute punc_level_attribute =
+ __ATTR(punc_level, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute reading_punc_attribute =
+ __ATTR(reading_punc, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute say_control_attribute =
+ __ATTR(say_control, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute say_word_ctl_attribute =
+ __ATTR(say_word_ctl, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute spell_delay_attribute =
+ __ATTR(spell_delay, 0644, spk_var_show, spk_var_store);
+
+/*
+ * These attributes are i18n related.
+ */
+static struct kobj_attribute announcements_attribute =
+ __ATTR(announcements, 0644, message_show, message_store);
+static struct kobj_attribute characters_attribute =
+ __ATTR(characters, 0644, chars_chartab_show,
+ chars_chartab_store);
+static struct kobj_attribute chartab_attribute =
+ __ATTR(chartab, 0644, chars_chartab_show,
+ chars_chartab_store);
+static struct kobj_attribute ctl_keys_attribute =
+ __ATTR(ctl_keys, 0644, message_show, message_store);
+static struct kobj_attribute colors_attribute =
+ __ATTR(colors, 0644, message_show, message_store);
+static struct kobj_attribute formatted_attribute =
+ __ATTR(formatted, 0644, message_show, message_store);
+static struct kobj_attribute function_names_attribute =
+ __ATTR(function_names, 0644, message_show, message_store);
+static struct kobj_attribute key_names_attribute =
+ __ATTR(key_names, 0644, message_show, message_store);
+static struct kobj_attribute states_attribute =
+ __ATTR(states, 0644, message_show, message_store);
+
+/*
+ * Create groups of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *main_attrs[] = {
+ &keymap_attribute.attr,
+ &silent_attribute.attr,
+ &synth_attribute.attr,
+ &synth_direct_attribute.attr,
+ &version_attribute.attr,
+ &delimiters_attribute.attr,
+ &ex_num_attribute.attr,
+ &punc_all_attribute.attr,
+ &punc_most_attribute.attr,
+ &punc_some_attribute.attr,
+ &repeats_attribute.attr,
+ &attrib_bleep_attribute.attr,
+ &bell_pos_attribute.attr,
+ &bleep_time_attribute.attr,
+ &bleeps_attribute.attr,
+ &cursor_time_attribute.attr,
+ &key_echo_attribute.attr,
+ &no_interrupt_attribute.attr,
+ &punc_level_attribute.attr,
+ &reading_punc_attribute.attr,
+ &say_control_attribute.attr,
+ &say_word_ctl_attribute.attr,
+ &spell_delay_attribute.attr,
+ NULL,
+};
+
+static struct attribute *i18n_attrs[] = {
+ &announcements_attribute.attr,
+ &characters_attribute.attr,
+ &chartab_attribute.attr,
+ &ctl_keys_attribute.attr,
+ &colors_attribute.attr,
+ &formatted_attribute.attr,
+ &function_names_attribute.attr,
+ &key_names_attribute.attr,
+ &states_attribute.attr,
+ NULL,
+};
+
+/*
+ * An unnamed attribute group will put all of the attributes directly in
+ * the kobject directory. If we specify a name, a subdirectory will be
+ * created for the attributes with the directory being the name of the
+ * attribute group.
+ */
+static const struct attribute_group main_attr_group = {
+ .attrs = main_attrs,
+};
+
+static const struct attribute_group i18n_attr_group = {
+ .attrs = i18n_attrs,
+ .name = "i18n",
+};
+
+static struct kobject *accessibility_kobj;
+struct kobject *speakup_kobj;
+
+int speakup_kobj_init(void)
+{
+ int retval;
+
+ /*
+ * Create a simple kobject with the name of "accessibility",
+ * located under /sys/
+ *
+ * As this is a simple directory, no uevent will be sent to
+ * userspace. That is why this function should not be used for
+ * any type of dynamic kobjects, where the name and number are
+ * not known ahead of time.
+ */
+ accessibility_kobj = kobject_create_and_add("accessibility", NULL);
+ if (!accessibility_kobj) {
+ retval = -ENOMEM;
+ goto out;
+ }
+
+ speakup_kobj = kobject_create_and_add("speakup", accessibility_kobj);
+ if (!speakup_kobj) {
+ retval = -ENOMEM;
+ goto err_acc;
+ }
+
+ /* Create the files associated with this kobject */
+ retval = sysfs_create_group(speakup_kobj, &main_attr_group);
+ if (retval)
+ goto err_speakup;
+
+ retval = sysfs_create_group(speakup_kobj, &i18n_attr_group);
+ if (retval)
+ goto err_group;
+
+ goto out;
+
+err_group:
+ sysfs_remove_group(speakup_kobj, &main_attr_group);
+err_speakup:
+ kobject_put(speakup_kobj);
+err_acc:
+ kobject_put(accessibility_kobj);
+out:
+ return retval;
+}
+
+void speakup_kobj_exit(void)
+{
+ sysfs_remove_group(speakup_kobj, &i18n_attr_group);
+ sysfs_remove_group(speakup_kobj, &main_attr_group);
+ kobject_put(speakup_kobj);
+ kobject_put(accessibility_kobj);
+}
diff --git a/drivers/accessibility/speakup/main.c b/drivers/accessibility/speakup/main.c
new file mode 100644
index 000000000..63c5444f0
--- /dev/null
+++ b/drivers/accessibility/speakup/main.c
@@ -0,0 +1,2459 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* speakup.c
+ * review functions for the speakup screen review package.
+ * originally written by: Kirk Reiser and Andy Berdan.
+ *
+ * extensively modified by David Borowski.
+ *
+ ** Copyright (C) 1998 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ */
+
+#include <linux/kernel.h>
+#include <linux/vt.h>
+#include <linux/tty.h>
+#include <linux/mm.h> /* __get_free_page() and friends */
+#include <linux/vt_kern.h>
+#include <linux/ctype.h>
+#include <linux/selection.h>
+#include <linux/unistd.h>
+#include <linux/jiffies.h>
+#include <linux/kthread.h>
+#include <linux/keyboard.h> /* for KT_SHIFT */
+#include <linux/kbd_kern.h> /* for vc_kbd_* and friends */
+#include <linux/input.h>
+#include <linux/kmod.h>
+
+/* speakup_*_selection */
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/consolemap.h>
+
+#include <linux/spinlock.h>
+#include <linux/notifier.h>
+
+#include <linux/uaccess.h> /* copy_from|to|user() and others */
+
+#include "spk_priv.h"
+#include "speakup.h"
+
+#define MAX_DELAY msecs_to_jiffies(500)
+#define MINECHOCHAR SPACE
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("Daniel Drake <dsd@gentoo.org>");
+MODULE_DESCRIPTION("Speakup console speech");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(SPEAKUP_VERSION);
+
+char *synth_name;
+module_param_named(synth, synth_name, charp, 0444);
+module_param_named(quiet, spk_quiet_boot, bool, 0444);
+
+MODULE_PARM_DESC(synth, "Synth to start if speakup is built in.");
+MODULE_PARM_DESC(quiet, "Do not announce when the synthesizer is found.");
+
+special_func spk_special_handler;
+
+short spk_pitch_shift, synth_flags;
+static u16 buf[256];
+int spk_attrib_bleep, spk_bleeps, spk_bleep_time = 10;
+int spk_no_intr, spk_spell_delay;
+int spk_key_echo, spk_say_word_ctl;
+int spk_say_ctrl, spk_bell_pos;
+short spk_punc_mask;
+int spk_punc_level, spk_reading_punc;
+char spk_str_caps_start[MAXVARLEN + 1] = "\0";
+char spk_str_caps_stop[MAXVARLEN + 1] = "\0";
+char spk_str_pause[MAXVARLEN + 1] = "\0";
+bool spk_paused;
+const struct st_bits_data spk_punc_info[] = {
+ {"none", "", 0},
+ {"some", "/$%&@", SOME},
+ {"most", "$%&#()=+*/@^<>|\\", MOST},
+ {"all", "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", PUNC},
+ {"delimiters", "", B_WDLM},
+ {"repeats", "()", CH_RPT},
+ {"extended numeric", "", B_EXNUM},
+ {"symbols", "", B_SYM},
+ {NULL, NULL}
+};
+
+static char mark_cut_flag;
+#define MAX_KEY 160
+static u_char *spk_shift_table;
+u_char *spk_our_keys[MAX_KEY];
+u_char spk_key_buf[600];
+const u_char spk_key_defaults[] = {
+#include "speakupmap.h"
+};
+
+/* Speakup Cursor Track Variables */
+static int cursor_track = 1, prev_cursor_track = 1;
+
+/* cursor track modes, must be ordered same as cursor_msgs */
+enum {
+ CT_Off = 0,
+ CT_On,
+ CT_Highlight,
+ CT_Window,
+ CT_Max
+};
+
+#define read_all_mode CT_Max
+
+static struct tty_struct *tty;
+
+static void spkup_write(const u16 *in_buf, int count);
+
+static char *phonetic[] = {
+ "alfa", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel",
+ "india", "juliett", "keelo", "leema", "mike", "november", "oscar",
+ "papa",
+ "keh beck", "romeo", "sierra", "tango", "uniform", "victer", "whiskey",
+ "x ray", "yankee", "zulu"
+};
+
+/* array of 256 char pointers (one for each character description)
+ * initialized to default_chars and user selectable via
+ * /proc/speakup/characters
+ */
+char *spk_characters[256];
+
+char *spk_default_chars[256] = {
+/*000*/ "null", "^a", "^b", "^c", "^d", "^e", "^f", "^g",
+/*008*/ "^h", "^i", "^j", "^k", "^l", "^m", "^n", "^o",
+/*016*/ "^p", "^q", "^r", "^s", "^t", "^u", "^v", "^w",
+/*024*/ "^x", "^y", "^z", "control", "control", "control", "control",
+ "control",
+/*032*/ "space", "bang!", "quote", "number", "dollar", "percent", "and",
+ "tick",
+/*040*/ "left paren", "right paren", "star", "plus", "comma", "dash",
+ "dot",
+ "slash",
+/*048*/ "zero", "one", "two", "three", "four", "five", "six", "seven",
+ "eight", "nine",
+/*058*/ "colon", "semmy", "less", "equals", "greater", "question", "at",
+/*065*/ "EIGH", "B", "C", "D", "E", "F", "G",
+/*072*/ "H", "I", "J", "K", "L", "M", "N", "O",
+/*080*/ "P", "Q", "R", "S", "T", "U", "V", "W", "X",
+/*089*/ "Y", "ZED", "left bracket", "backslash", "right bracket",
+ "caret",
+ "line",
+/*096*/ "accent", "a", "b", "c", "d", "e", "f", "g",
+/*104*/ "h", "i", "j", "k", "l", "m", "n", "o",
+/*112*/ "p", "q", "r", "s", "t", "u", "v", "w",
+/*120*/ "x", "y", "zed", "left brace", "bar", "right brace", "tihlduh",
+/*127*/ "del", "control", "control", "control", "control", "control",
+ "control", "control", "control", "control", "control",
+/*138*/ "control", "control", "control", "control", "control",
+ "control", "control", "control", "control", "control",
+ "control", "control",
+/*150*/ "control", "control", "control", "control", "control",
+ "control", "control", "control", "control", "control",
+/*160*/ "nbsp", "inverted bang",
+/*162*/ "cents", "pounds", "currency", "yen", "broken bar", "section",
+/*168*/ "diaeresis", "copyright", "female ordinal", "double left angle",
+/*172*/ "not", "soft hyphen", "registered", "macron",
+/*176*/ "degrees", "plus or minus", "super two", "super three",
+/*180*/ "acute accent", "micro", "pilcrow", "middle dot",
+/*184*/ "cedilla", "super one", "male ordinal", "double right angle",
+/*188*/ "one quarter", "one half", "three quarters",
+ "inverted question",
+/*192*/ "A GRAVE", "A ACUTE", "A CIRCUMFLEX", "A TILDE", "A OOMLAUT",
+ "A RING",
+/*198*/ "AE", "C CIDELLA", "E GRAVE", "E ACUTE", "E CIRCUMFLEX",
+ "E OOMLAUT",
+/*204*/ "I GRAVE", "I ACUTE", "I CIRCUMFLEX", "I OOMLAUT", "ETH",
+ "N TILDE",
+/*210*/ "O GRAVE", "O ACUTE", "O CIRCUMFLEX", "O TILDE", "O OOMLAUT",
+/*215*/ "multiplied by", "O STROKE", "U GRAVE", "U ACUTE",
+ "U CIRCUMFLEX",
+/*220*/ "U OOMLAUT", "Y ACUTE", "THORN", "sharp s", "a grave",
+/*225*/ "a acute", "a circumflex", "a tilde", "a oomlaut", "a ring",
+/*230*/ "ae", "c cidella", "e grave", "e acute",
+/*234*/ "e circumflex", "e oomlaut", "i grave", "i acute",
+ "i circumflex",
+/*239*/ "i oomlaut", "eth", "n tilde", "o grave", "o acute",
+ "o circumflex",
+/*245*/ "o tilde", "o oomlaut", "divided by", "o stroke", "u grave",
+ "u acute",
+/* 251 */ "u circumflex", "u oomlaut", "y acute", "thorn", "y oomlaut"
+};
+
+/* array of 256 u_short (one for each character)
+ * initialized to default_chartab and user selectable via
+ * /sys/module/speakup/parameters/chartab
+ */
+u_short spk_chartab[256];
+
+static u_short default_chartab[256] = {
+ B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, /* 0-7 */
+ B_CTL, B_CTL, A_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, /* 8-15 */
+ B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, /*16-23 */
+ B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, B_CTL, /* 24-31 */
+ WDLM, A_PUNC, PUNC, PUNC, PUNC, PUNC, PUNC, A_PUNC, /* !"#$%&' */
+ PUNC, PUNC, PUNC, PUNC, A_PUNC, A_PUNC, A_PUNC, PUNC, /* ()*+, -./ */
+ NUM, NUM, NUM, NUM, NUM, NUM, NUM, NUM, /* 01234567 */
+ NUM, NUM, A_PUNC, PUNC, PUNC, PUNC, PUNC, A_PUNC, /* 89:;<=>? */
+ PUNC, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, /* @ABCDEFG */
+ A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, /* HIJKLMNO */
+ A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, /* PQRSTUVW */
+ A_CAP, A_CAP, A_CAP, PUNC, PUNC, PUNC, PUNC, PUNC, /* XYZ[\]^_ */
+ PUNC, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, /* `abcdefg */
+ ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, /* hijklmno */
+ ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, /* pqrstuvw */
+ ALPHA, ALPHA, ALPHA, PUNC, PUNC, PUNC, PUNC, 0, /* xyz{|}~ */
+ B_CAPSYM, B_CAPSYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 128-134 */
+ B_SYM, /* 135 */
+ B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 136-142 */
+ B_CAPSYM, /* 143 */
+ B_CAPSYM, B_CAPSYM, B_SYM, B_CAPSYM, B_SYM, B_SYM, B_SYM, /* 144-150 */
+ B_SYM, /* 151 */
+ B_SYM, B_SYM, B_CAPSYM, B_CAPSYM, B_SYM, B_SYM, B_SYM, /*152-158 */
+ B_SYM, /* 159 */
+ WDLM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_CAPSYM, /* 160-166 */
+ B_SYM, /* 167 */
+ B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 168-175 */
+ B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 176-183 */
+ B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, B_SYM, /* 184-191 */
+ A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, /* 192-199 */
+ A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, /* 200-207 */
+ A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, B_SYM, /* 208-215 */
+ A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, A_CAP, ALPHA, /* 216-223 */
+ ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, /* 224-231 */
+ ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, /* 232-239 */
+ ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, B_SYM, /* 240-247 */
+ ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA /* 248-255 */
+};
+
+struct task_struct *speakup_task;
+struct bleep spk_unprocessed_sound;
+static int spk_keydown;
+static u16 spk_lastkey;
+static u_char spk_close_press, keymap_flags;
+static u_char last_keycode, this_speakup_key;
+static u_long last_spk_jiffy;
+
+struct st_spk_t *speakup_console[MAX_NR_CONSOLES];
+
+DEFINE_MUTEX(spk_mutex);
+
+static int keyboard_notifier_call(struct notifier_block *,
+ unsigned long code, void *param);
+
+static struct notifier_block keyboard_notifier_block = {
+ .notifier_call = keyboard_notifier_call,
+};
+
+static int vt_notifier_call(struct notifier_block *,
+ unsigned long code, void *param);
+
+static struct notifier_block vt_notifier_block = {
+ .notifier_call = vt_notifier_call,
+};
+
+static unsigned char get_attributes(struct vc_data *vc, u16 *pos)
+{
+ pos = screen_pos(vc, pos - (u16 *)vc->vc_origin, true);
+ return (scr_readw(pos) & ~vc->vc_hi_font_mask) >> 8;
+}
+
+static void speakup_date(struct vc_data *vc)
+{
+ spk_x = spk_cx = vc->state.x;
+ spk_y = spk_cy = vc->state.y;
+ spk_pos = spk_cp = vc->vc_pos;
+ spk_old_attr = spk_attr;
+ spk_attr = get_attributes(vc, (u_short *)spk_pos);
+}
+
+static void bleep(u_short val)
+{
+ static const short vals[] = {
+ 350, 370, 392, 414, 440, 466, 491, 523, 554, 587, 619, 659
+ };
+ short freq;
+ int time = spk_bleep_time;
+
+ freq = vals[val % 12];
+ if (val > 11)
+ freq *= (1 << (val / 12));
+ spk_unprocessed_sound.freq = freq;
+ spk_unprocessed_sound.jiffies = msecs_to_jiffies(time);
+ spk_unprocessed_sound.active = 1;
+ /* We can only have 1 active sound at a time. */
+}
+
+static void speakup_shut_up(struct vc_data *vc)
+{
+ if (spk_killed)
+ return;
+ spk_shut_up |= 0x01;
+ spk_parked &= 0xfe;
+ speakup_date(vc);
+ if (synth)
+ spk_do_flush();
+}
+
+static void speech_kill(struct vc_data *vc)
+{
+ char val = synth->is_alive(synth);
+
+ if (val == 0)
+ return;
+
+ /* re-enables synth, if disabled */
+ if (val == 2 || spk_killed) {
+ /* dead */
+ spk_shut_up &= ~0x40;
+ synth_printf("%s\n", spk_msg_get(MSG_IAM_ALIVE));
+ } else {
+ synth_printf("%s\n", spk_msg_get(MSG_YOU_KILLED_SPEAKUP));
+ spk_shut_up |= 0x40;
+ }
+}
+
+static void speakup_off(struct vc_data *vc)
+{
+ if (spk_shut_up & 0x80) {
+ spk_shut_up &= 0x7f;
+ synth_printf("%s\n", spk_msg_get(MSG_HEY_THATS_BETTER));
+ } else {
+ spk_shut_up |= 0x80;
+ synth_printf("%s\n", spk_msg_get(MSG_YOU_TURNED_ME_OFF));
+ }
+ speakup_date(vc);
+}
+
+static void speakup_parked(struct vc_data *vc)
+{
+ if (spk_parked & 0x80) {
+ spk_parked = 0;
+ synth_printf("%s\n", spk_msg_get(MSG_UNPARKED));
+ } else {
+ spk_parked |= 0x80;
+ synth_printf("%s\n", spk_msg_get(MSG_PARKED));
+ }
+}
+
+static void speakup_cut(struct vc_data *vc)
+{
+ static const char err_buf[] = "set selection failed";
+ int ret;
+
+ if (!mark_cut_flag) {
+ mark_cut_flag = 1;
+ spk_xs = (u_short)spk_x;
+ spk_ys = (u_short)spk_y;
+ spk_sel_cons = vc;
+ synth_printf("%s\n", spk_msg_get(MSG_MARK));
+ return;
+ }
+ spk_xe = (u_short)spk_x;
+ spk_ye = (u_short)spk_y;
+ mark_cut_flag = 0;
+ synth_printf("%s\n", spk_msg_get(MSG_CUT));
+
+ ret = speakup_set_selection(tty);
+
+ switch (ret) {
+ case 0:
+ break; /* no error */
+ case -EFAULT:
+ pr_warn("%sEFAULT\n", err_buf);
+ break;
+ case -EINVAL:
+ pr_warn("%sEINVAL\n", err_buf);
+ break;
+ case -ENOMEM:
+ pr_warn("%sENOMEM\n", err_buf);
+ break;
+ }
+}
+
+static void speakup_paste(struct vc_data *vc)
+{
+ if (mark_cut_flag) {
+ mark_cut_flag = 0;
+ synth_printf("%s\n", spk_msg_get(MSG_MARK_CLEARED));
+ } else {
+ synth_printf("%s\n", spk_msg_get(MSG_PASTE));
+ speakup_paste_selection(tty);
+ }
+}
+
+static void say_attributes(struct vc_data *vc)
+{
+ int fg = spk_attr & 0x0f;
+ int bg = spk_attr >> 4;
+
+ if (fg > 8) {
+ synth_printf("%s ", spk_msg_get(MSG_BRIGHT));
+ fg -= 8;
+ }
+ synth_printf("%s", spk_msg_get(MSG_COLORS_START + fg));
+ if (bg > 7) {
+ synth_printf(" %s ", spk_msg_get(MSG_ON_BLINKING));
+ bg -= 8;
+ } else {
+ synth_printf(" %s ", spk_msg_get(MSG_ON));
+ }
+ synth_printf("%s\n", spk_msg_get(MSG_COLORS_START + bg));
+}
+
+enum {
+ edge_top = 1,
+ edge_bottom,
+ edge_left,
+ edge_right,
+ edge_quiet
+};
+
+static void announce_edge(struct vc_data *vc, int msg_id)
+{
+ if (spk_bleeps & 1)
+ bleep(spk_y);
+ if ((spk_bleeps & 2) && (msg_id < edge_quiet))
+ synth_printf("%s\n",
+ spk_msg_get(MSG_EDGE_MSGS_START + msg_id - 1));
+}
+
+static void speak_char(u16 ch)
+{
+ char *cp;
+ struct var_t *direct = spk_get_var(DIRECT);
+
+ if (ch >= 0x100 || (direct && direct->u.n.value)) {
+ if (ch < 0x100 && IS_CHAR(ch, B_CAP)) {
+ spk_pitch_shift++;
+ synth_printf("%s", spk_str_caps_start);
+ }
+ synth_putwc_s(ch);
+ if (ch < 0x100 && IS_CHAR(ch, B_CAP))
+ synth_printf("%s", spk_str_caps_stop);
+ return;
+ }
+
+ cp = spk_characters[ch];
+ if (!cp) {
+ pr_info("%s: cp == NULL!\n", __func__);
+ return;
+ }
+ if (IS_CHAR(ch, B_CAP)) {
+ spk_pitch_shift++;
+ synth_printf("%s %s %s",
+ spk_str_caps_start, cp, spk_str_caps_stop);
+ } else {
+ if (*cp == '^') {
+ cp++;
+ synth_printf(" %s%s ", spk_msg_get(MSG_CTRL), cp);
+ } else {
+ synth_printf(" %s ", cp);
+ }
+ }
+}
+
+static u16 get_char(struct vc_data *vc, u16 *pos, u_char *attribs)
+{
+ u16 ch = ' ';
+
+ if (vc && pos) {
+ u16 w;
+ u16 c;
+
+ pos = screen_pos(vc, pos - (u16 *)vc->vc_origin, true);
+ w = scr_readw(pos);
+ c = w & 0xff;
+
+ if (w & vc->vc_hi_font_mask) {
+ w &= ~vc->vc_hi_font_mask;
+ c |= 0x100;
+ }
+
+ ch = inverse_translate(vc, c, 1);
+ *attribs = (w & 0xff00) >> 8;
+ }
+ return ch;
+}
+
+static void say_char(struct vc_data *vc)
+{
+ u16 ch;
+
+ spk_old_attr = spk_attr;
+ ch = get_char(vc, (u_short *)spk_pos, &spk_attr);
+ if (spk_attr != spk_old_attr) {
+ if (spk_attrib_bleep & 1)
+ bleep(spk_y);
+ if (spk_attrib_bleep & 2)
+ say_attributes(vc);
+ }
+ speak_char(ch);
+}
+
+static void say_phonetic_char(struct vc_data *vc)
+{
+ u16 ch;
+
+ spk_old_attr = spk_attr;
+ ch = get_char(vc, (u_short *)spk_pos, &spk_attr);
+ if (ch <= 0x7f && isalpha(ch)) {
+ ch &= 0x1f;
+ synth_printf("%s\n", phonetic[--ch]);
+ } else {
+ if (ch < 0x100 && IS_CHAR(ch, B_NUM))
+ synth_printf("%s ", spk_msg_get(MSG_NUMBER));
+ speak_char(ch);
+ }
+}
+
+static void say_prev_char(struct vc_data *vc)
+{
+ spk_parked |= 0x01;
+ if (spk_x == 0) {
+ announce_edge(vc, edge_left);
+ return;
+ }
+ spk_x--;
+ spk_pos -= 2;
+ say_char(vc);
+}
+
+static void say_next_char(struct vc_data *vc)
+{
+ spk_parked |= 0x01;
+ if (spk_x == vc->vc_cols - 1) {
+ announce_edge(vc, edge_right);
+ return;
+ }
+ spk_x++;
+ spk_pos += 2;
+ say_char(vc);
+}
+
+/* get_word - will first check to see if the character under the
+ * reading cursor is a space and if spk_say_word_ctl is true it will
+ * return the word space. If spk_say_word_ctl is not set it will check to
+ * see if there is a word starting on the next position to the right
+ * and return that word if it exists. If it does not exist it will
+ * move left to the beginning of any previous word on the line or the
+ * beginning off the line whichever comes first..
+ */
+
+static u_long get_word(struct vc_data *vc)
+{
+ u_long cnt = 0, tmpx = spk_x, tmp_pos = spk_pos;
+ u16 ch;
+ u16 attr_ch;
+ u_char temp;
+
+ spk_old_attr = spk_attr;
+ ch = get_char(vc, (u_short *)tmp_pos, &temp);
+
+/* decided to take out the sayword if on a space (mis-information */
+ if (spk_say_word_ctl && ch == SPACE) {
+ *buf = '\0';
+ synth_printf("%s\n", spk_msg_get(MSG_SPACE));
+ return 0;
+ } else if (tmpx < vc->vc_cols - 2 &&
+ (ch == SPACE || ch == 0 || (ch < 0x100 && IS_WDLM(ch))) &&
+ get_char(vc, (u_short *)tmp_pos + 1, &temp) > SPACE) {
+ tmp_pos += 2;
+ tmpx++;
+ } else {
+ while (tmpx > 0) {
+ ch = get_char(vc, (u_short *)tmp_pos - 1, &temp);
+ if ((ch == SPACE || ch == 0 ||
+ (ch < 0x100 && IS_WDLM(ch))) &&
+ get_char(vc, (u_short *)tmp_pos, &temp) > SPACE)
+ break;
+ tmp_pos -= 2;
+ tmpx--;
+ }
+ }
+ attr_ch = get_char(vc, (u_short *)tmp_pos, &spk_attr);
+ buf[cnt++] = attr_ch;
+ while (tmpx < vc->vc_cols - 1) {
+ tmp_pos += 2;
+ tmpx++;
+ ch = get_char(vc, (u_short *)tmp_pos, &temp);
+ if (ch == SPACE || ch == 0 ||
+ (buf[cnt - 1] < 0x100 && IS_WDLM(buf[cnt - 1]) &&
+ ch > SPACE))
+ break;
+ buf[cnt++] = ch;
+ }
+ buf[cnt] = '\0';
+ return cnt;
+}
+
+static void say_word(struct vc_data *vc)
+{
+ u_long cnt = get_word(vc);
+ u_short saved_punc_mask = spk_punc_mask;
+
+ if (cnt == 0)
+ return;
+ spk_punc_mask = PUNC;
+ buf[cnt++] = SPACE;
+ spkup_write(buf, cnt);
+ spk_punc_mask = saved_punc_mask;
+}
+
+static void say_prev_word(struct vc_data *vc)
+{
+ u_char temp;
+ u16 ch;
+ u_short edge_said = 0, last_state = 0, state = 0;
+
+ spk_parked |= 0x01;
+
+ if (spk_x == 0) {
+ if (spk_y == 0) {
+ announce_edge(vc, edge_top);
+ return;
+ }
+ spk_y--;
+ spk_x = vc->vc_cols;
+ edge_said = edge_quiet;
+ }
+ while (1) {
+ if (spk_x == 0) {
+ if (spk_y == 0) {
+ edge_said = edge_top;
+ break;
+ }
+ if (edge_said != edge_quiet)
+ edge_said = edge_left;
+ if (state > 0)
+ break;
+ spk_y--;
+ spk_x = vc->vc_cols - 1;
+ } else {
+ spk_x--;
+ }
+ spk_pos -= 2;
+ ch = get_char(vc, (u_short *)spk_pos, &temp);
+ if (ch == SPACE || ch == 0)
+ state = 0;
+ else if (ch < 0x100 && IS_WDLM(ch))
+ state = 1;
+ else
+ state = 2;
+ if (state < last_state) {
+ spk_pos += 2;
+ spk_x++;
+ break;
+ }
+ last_state = state;
+ }
+ if (spk_x == 0 && edge_said == edge_quiet)
+ edge_said = edge_left;
+ if (edge_said > 0 && edge_said < edge_quiet)
+ announce_edge(vc, edge_said);
+ say_word(vc);
+}
+
+static void say_next_word(struct vc_data *vc)
+{
+ u_char temp;
+ u16 ch;
+ u_short edge_said = 0, last_state = 2, state = 0;
+
+ spk_parked |= 0x01;
+ if (spk_x == vc->vc_cols - 1 && spk_y == vc->vc_rows - 1) {
+ announce_edge(vc, edge_bottom);
+ return;
+ }
+ while (1) {
+ ch = get_char(vc, (u_short *)spk_pos, &temp);
+ if (ch == SPACE || ch == 0)
+ state = 0;
+ else if (ch < 0x100 && IS_WDLM(ch))
+ state = 1;
+ else
+ state = 2;
+ if (state > last_state)
+ break;
+ if (spk_x >= vc->vc_cols - 1) {
+ if (spk_y == vc->vc_rows - 1) {
+ edge_said = edge_bottom;
+ break;
+ }
+ state = 0;
+ spk_y++;
+ spk_x = 0;
+ edge_said = edge_right;
+ } else {
+ spk_x++;
+ }
+ spk_pos += 2;
+ last_state = state;
+ }
+ if (edge_said > 0)
+ announce_edge(vc, edge_said);
+ say_word(vc);
+}
+
+static void spell_word(struct vc_data *vc)
+{
+ static char const *delay_str[] = { "", ",", ".", ". .", ". . ." };
+ u16 *cp = buf;
+ char *cp1;
+ char *str_cap = spk_str_caps_stop;
+ char *last_cap = spk_str_caps_stop;
+ struct var_t *direct = spk_get_var(DIRECT);
+ u16 ch;
+
+ if (!get_word(vc))
+ return;
+ while ((ch = *cp)) {
+ if (cp != buf)
+ synth_printf(" %s ", delay_str[spk_spell_delay]);
+ /* FIXME: Non-latin1 considered as lower case */
+ if (ch < 0x100 && IS_CHAR(ch, B_CAP)) {
+ str_cap = spk_str_caps_start;
+ if (*spk_str_caps_stop)
+ spk_pitch_shift++;
+ else /* synth has no pitch */
+ last_cap = spk_str_caps_stop;
+ } else {
+ str_cap = spk_str_caps_stop;
+ }
+ if (str_cap != last_cap) {
+ synth_printf("%s", str_cap);
+ last_cap = str_cap;
+ }
+ if (ch >= 0x100 || (direct && direct->u.n.value)) {
+ synth_putwc_s(ch);
+ } else if (this_speakup_key == SPELL_PHONETIC &&
+ ch <= 0x7f && isalpha(ch)) {
+ ch &= 0x1f;
+ cp1 = phonetic[--ch];
+ synth_printf("%s", cp1);
+ } else {
+ cp1 = spk_characters[ch];
+ if (*cp1 == '^') {
+ synth_printf("%s", spk_msg_get(MSG_CTRL));
+ cp1++;
+ }
+ synth_printf("%s", cp1);
+ }
+ cp++;
+ }
+ if (str_cap != spk_str_caps_stop)
+ synth_printf("%s", spk_str_caps_stop);
+}
+
+static int get_line(struct vc_data *vc)
+{
+ u_long tmp = spk_pos - (spk_x * 2);
+ int i = 0;
+ u_char tmp2;
+
+ spk_old_attr = spk_attr;
+ spk_attr = get_attributes(vc, (u_short *)spk_pos);
+ for (i = 0; i < vc->vc_cols; i++) {
+ buf[i] = get_char(vc, (u_short *)tmp, &tmp2);
+ tmp += 2;
+ }
+ for (--i; i >= 0; i--)
+ if (buf[i] != SPACE)
+ break;
+ return ++i;
+}
+
+static void say_line(struct vc_data *vc)
+{
+ int i = get_line(vc);
+ u16 *cp;
+ u_short saved_punc_mask = spk_punc_mask;
+
+ if (i == 0) {
+ synth_printf("%s\n", spk_msg_get(MSG_BLANK));
+ return;
+ }
+ buf[i++] = '\n';
+ if (this_speakup_key == SAY_LINE_INDENT) {
+ cp = buf;
+ while (*cp == SPACE)
+ cp++;
+ synth_printf("%zd, ", (cp - buf) + 1);
+ }
+ spk_punc_mask = spk_punc_masks[spk_reading_punc];
+ spkup_write(buf, i);
+ spk_punc_mask = saved_punc_mask;
+}
+
+static void say_prev_line(struct vc_data *vc)
+{
+ spk_parked |= 0x01;
+ if (spk_y == 0) {
+ announce_edge(vc, edge_top);
+ return;
+ }
+ spk_y--;
+ spk_pos -= vc->vc_size_row;
+ say_line(vc);
+}
+
+static void say_next_line(struct vc_data *vc)
+{
+ spk_parked |= 0x01;
+ if (spk_y == vc->vc_rows - 1) {
+ announce_edge(vc, edge_bottom);
+ return;
+ }
+ spk_y++;
+ spk_pos += vc->vc_size_row;
+ say_line(vc);
+}
+
+static int say_from_to(struct vc_data *vc, u_long from, u_long to,
+ int read_punc)
+{
+ int i = 0;
+ u_char tmp;
+ u_short saved_punc_mask = spk_punc_mask;
+
+ spk_old_attr = spk_attr;
+ spk_attr = get_attributes(vc, (u_short *)from);
+ while (from < to) {
+ buf[i++] = get_char(vc, (u_short *)from, &tmp);
+ from += 2;
+ if (i >= vc->vc_size_row)
+ break;
+ }
+ for (--i; i >= 0; i--)
+ if (buf[i] != SPACE)
+ break;
+ buf[++i] = SPACE;
+ buf[++i] = '\0';
+ if (i < 1)
+ return i;
+ if (read_punc)
+ spk_punc_mask = spk_punc_info[spk_reading_punc].mask;
+ spkup_write(buf, i);
+ if (read_punc)
+ spk_punc_mask = saved_punc_mask;
+ return i - 1;
+}
+
+static void say_line_from_to(struct vc_data *vc, u_long from, u_long to,
+ int read_punc)
+{
+ u_long start = vc->vc_origin + (spk_y * vc->vc_size_row);
+ u_long end = start + (to * 2);
+
+ start += from * 2;
+ if (say_from_to(vc, start, end, read_punc) <= 0)
+ if (cursor_track != read_all_mode)
+ synth_printf("%s\n", spk_msg_get(MSG_BLANK));
+}
+
+/* Sentence Reading Commands */
+
+static int currsentence;
+static int numsentences[2];
+static u16 *sentbufend[2];
+static u16 *sentmarks[2][10];
+static int currbuf;
+static int bn;
+static u16 sentbuf[2][256];
+
+static int say_sentence_num(int num, int prev)
+{
+ bn = currbuf;
+ currsentence = num + 1;
+ if (prev && --bn == -1)
+ bn = 1;
+
+ if (num > numsentences[bn])
+ return 0;
+
+ spkup_write(sentmarks[bn][num], sentbufend[bn] - sentmarks[bn][num]);
+ return 1;
+}
+
+static int get_sentence_buf(struct vc_data *vc, int read_punc)
+{
+ u_long start, end;
+ int i, bn;
+ u_char tmp;
+
+ currbuf++;
+ if (currbuf == 2)
+ currbuf = 0;
+ bn = currbuf;
+ start = vc->vc_origin + ((spk_y) * vc->vc_size_row);
+ end = vc->vc_origin + ((spk_y) * vc->vc_size_row) + vc->vc_cols * 2;
+
+ numsentences[bn] = 0;
+ sentmarks[bn][0] = &sentbuf[bn][0];
+ i = 0;
+ spk_old_attr = spk_attr;
+ spk_attr = get_attributes(vc, (u_short *)start);
+
+ while (start < end) {
+ sentbuf[bn][i] = get_char(vc, (u_short *)start, &tmp);
+ if (i > 0) {
+ if (sentbuf[bn][i] == SPACE &&
+ sentbuf[bn][i - 1] == '.' &&
+ numsentences[bn] < 9) {
+ /* Sentence Marker */
+ numsentences[bn]++;
+ sentmarks[bn][numsentences[bn]] =
+ &sentbuf[bn][i];
+ }
+ }
+ i++;
+ start += 2;
+ if (i >= vc->vc_size_row)
+ break;
+ }
+
+ for (--i; i >= 0; i--)
+ if (sentbuf[bn][i] != SPACE)
+ break;
+
+ if (i < 1)
+ return -1;
+
+ sentbuf[bn][++i] = SPACE;
+ sentbuf[bn][++i] = '\0';
+
+ sentbufend[bn] = &sentbuf[bn][i];
+ return numsentences[bn];
+}
+
+static void say_screen_from_to(struct vc_data *vc, u_long from, u_long to)
+{
+ u_long start = vc->vc_origin, end;
+
+ if (from > 0)
+ start += from * vc->vc_size_row;
+ if (to > vc->vc_rows)
+ to = vc->vc_rows;
+ end = vc->vc_origin + (to * vc->vc_size_row);
+ for (from = start; from < end; from = to) {
+ to = from + vc->vc_size_row;
+ say_from_to(vc, from, to, 1);
+ }
+}
+
+static void say_screen(struct vc_data *vc)
+{
+ say_screen_from_to(vc, 0, vc->vc_rows);
+}
+
+static void speakup_win_say(struct vc_data *vc)
+{
+ u_long start, end, from, to;
+
+ if (win_start < 2) {
+ synth_printf("%s\n", spk_msg_get(MSG_NO_WINDOW));
+ return;
+ }
+ start = vc->vc_origin + (win_top * vc->vc_size_row);
+ end = vc->vc_origin + (win_bottom * vc->vc_size_row);
+ while (start <= end) {
+ from = start + (win_left * 2);
+ to = start + (win_right * 2);
+ say_from_to(vc, from, to, 1);
+ start += vc->vc_size_row;
+ }
+}
+
+static void top_edge(struct vc_data *vc)
+{
+ spk_parked |= 0x01;
+ spk_pos = vc->vc_origin + 2 * spk_x;
+ spk_y = 0;
+ say_line(vc);
+}
+
+static void bottom_edge(struct vc_data *vc)
+{
+ spk_parked |= 0x01;
+ spk_pos += (vc->vc_rows - spk_y - 1) * vc->vc_size_row;
+ spk_y = vc->vc_rows - 1;
+ say_line(vc);
+}
+
+static void left_edge(struct vc_data *vc)
+{
+ spk_parked |= 0x01;
+ spk_pos -= spk_x * 2;
+ spk_x = 0;
+ say_char(vc);
+}
+
+static void right_edge(struct vc_data *vc)
+{
+ spk_parked |= 0x01;
+ spk_pos += (vc->vc_cols - spk_x - 1) * 2;
+ spk_x = vc->vc_cols - 1;
+ say_char(vc);
+}
+
+static void say_first_char(struct vc_data *vc)
+{
+ int i, len = get_line(vc);
+ u16 ch;
+
+ spk_parked |= 0x01;
+ if (len == 0) {
+ synth_printf("%s\n", spk_msg_get(MSG_BLANK));
+ return;
+ }
+ for (i = 0; i < len; i++)
+ if (buf[i] != SPACE)
+ break;
+ ch = buf[i];
+ spk_pos -= (spk_x - i) * 2;
+ spk_x = i;
+ synth_printf("%d, ", ++i);
+ speak_char(ch);
+}
+
+static void say_last_char(struct vc_data *vc)
+{
+ int len = get_line(vc);
+ u16 ch;
+
+ spk_parked |= 0x01;
+ if (len == 0) {
+ synth_printf("%s\n", spk_msg_get(MSG_BLANK));
+ return;
+ }
+ ch = buf[--len];
+ spk_pos -= (spk_x - len) * 2;
+ spk_x = len;
+ synth_printf("%d, ", ++len);
+ speak_char(ch);
+}
+
+static void say_position(struct vc_data *vc)
+{
+ synth_printf(spk_msg_get(MSG_POS_INFO), spk_y + 1, spk_x + 1,
+ vc->vc_num + 1);
+ synth_printf("\n");
+}
+
+/* Added by brianb */
+static void say_char_num(struct vc_data *vc)
+{
+ u_char tmp;
+ u16 ch = get_char(vc, (u_short *)spk_pos, &tmp);
+
+ synth_printf(spk_msg_get(MSG_CHAR_INFO), ch, ch);
+}
+
+/* these are stub functions to keep keyboard.c happy. */
+
+static void say_from_top(struct vc_data *vc)
+{
+ say_screen_from_to(vc, 0, spk_y);
+}
+
+static void say_to_bottom(struct vc_data *vc)
+{
+ say_screen_from_to(vc, spk_y, vc->vc_rows);
+}
+
+static void say_from_left(struct vc_data *vc)
+{
+ say_line_from_to(vc, 0, spk_x, 1);
+}
+
+static void say_to_right(struct vc_data *vc)
+{
+ say_line_from_to(vc, spk_x, vc->vc_cols, 1);
+}
+
+/* end of stub functions. */
+
+static void spkup_write(const u16 *in_buf, int count)
+{
+ static int rep_count;
+ static u16 ch = '\0', old_ch = '\0';
+ static u_short char_type, last_type;
+ int in_count = count;
+
+ spk_keydown = 0;
+ while (count--) {
+ if (cursor_track == read_all_mode) {
+ /* Insert Sentence Index */
+ if ((in_buf == sentmarks[bn][currsentence]) &&
+ (currsentence <= numsentences[bn]))
+ synth_insert_next_index(currsentence++);
+ }
+ ch = *in_buf++;
+ if (ch < 0x100)
+ char_type = spk_chartab[ch];
+ else
+ char_type = ALPHA;
+ if (ch == old_ch && !(char_type & B_NUM)) {
+ if (++rep_count > 2)
+ continue;
+ } else {
+ if ((last_type & CH_RPT) && rep_count > 2) {
+ synth_printf(" ");
+ synth_printf(spk_msg_get(MSG_REPEAT_DESC),
+ ++rep_count);
+ synth_printf(" ");
+ }
+ rep_count = 0;
+ }
+ if (ch == spk_lastkey) {
+ rep_count = 0;
+ if (spk_key_echo == 1 && ch >= MINECHOCHAR)
+ speak_char(ch);
+ } else if (char_type & B_ALPHA) {
+ if ((synth_flags & SF_DEC) && (last_type & PUNC))
+ synth_buffer_add(SPACE);
+ synth_putwc_s(ch);
+ } else if (char_type & B_NUM) {
+ rep_count = 0;
+ synth_putwc_s(ch);
+ } else if (char_type & spk_punc_mask) {
+ speak_char(ch);
+ char_type &= ~PUNC; /* for dec nospell processing */
+ } else if (char_type & SYNTH_OK) {
+ /* these are usually puncts like . and , which synth
+ * needs for expression.
+ * suppress multiple to get rid of long pauses and
+ * clear repeat count
+ * so if someone has
+ * repeats on you don't get nothing repeated count
+ */
+ if (ch != old_ch)
+ synth_putwc_s(ch);
+ else
+ rep_count = 0;
+ } else {
+/* send space and record position, if next is num overwrite space */
+ if (old_ch != ch)
+ synth_buffer_add(SPACE);
+ else
+ rep_count = 0;
+ }
+ old_ch = ch;
+ last_type = char_type;
+ }
+ spk_lastkey = 0;
+ if (in_count > 2 && rep_count > 2) {
+ if (last_type & CH_RPT) {
+ synth_printf(" ");
+ synth_printf(spk_msg_get(MSG_REPEAT_DESC2),
+ ++rep_count);
+ synth_printf(" ");
+ }
+ rep_count = 0;
+ }
+}
+
+static const int NUM_CTL_LABELS = (MSG_CTL_END - MSG_CTL_START + 1);
+
+static void read_all_doc(struct vc_data *vc);
+static void cursor_done(struct timer_list *unused);
+static DEFINE_TIMER(cursor_timer, cursor_done);
+
+static void do_handle_shift(struct vc_data *vc, u_char value, char up_flag)
+{
+ unsigned long flags;
+
+ if (!synth || up_flag || spk_killed)
+ return;
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (cursor_track == read_all_mode) {
+ switch (value) {
+ case KVAL(K_SHIFT):
+ del_timer(&cursor_timer);
+ spk_shut_up &= 0xfe;
+ spk_do_flush();
+ read_all_doc(vc);
+ break;
+ case KVAL(K_CTRL):
+ del_timer(&cursor_timer);
+ cursor_track = prev_cursor_track;
+ spk_shut_up &= 0xfe;
+ spk_do_flush();
+ break;
+ }
+ } else {
+ spk_shut_up &= 0xfe;
+ spk_do_flush();
+ }
+ if (spk_say_ctrl && value < NUM_CTL_LABELS)
+ synth_printf("%s", spk_msg_get(MSG_CTL_START + value));
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+}
+
+static void do_handle_latin(struct vc_data *vc, u_char value, char up_flag)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (up_flag) {
+ spk_lastkey = 0;
+ spk_keydown = 0;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return;
+ }
+ if (!synth || spk_killed) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return;
+ }
+ spk_shut_up &= 0xfe;
+ spk_lastkey = value;
+ spk_keydown++;
+ spk_parked &= 0xfe;
+ if (spk_key_echo == 2 && value >= MINECHOCHAR)
+ speak_char(value);
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+}
+
+int spk_set_key_info(const u_char *key_info, u_char *k_buffer)
+{
+ int i = 0, states, key_data_len;
+ const u_char *cp = key_info;
+ u_char *cp1 = k_buffer;
+ u_char ch, version, num_keys;
+
+ version = *cp++;
+ if (version != KEY_MAP_VER) {
+ pr_debug("version found %d should be %d\n",
+ version, KEY_MAP_VER);
+ return -EINVAL;
+ }
+ num_keys = *cp;
+ states = (int)cp[1];
+ key_data_len = (states + 1) * (num_keys + 1);
+ if (key_data_len + SHIFT_TBL_SIZE + 4 >= sizeof(spk_key_buf)) {
+ pr_debug("too many key_infos (%d over %u)\n",
+ key_data_len + SHIFT_TBL_SIZE + 4,
+ (unsigned int)(sizeof(spk_key_buf)));
+ return -EINVAL;
+ }
+ memset(k_buffer, 0, SHIFT_TBL_SIZE);
+ memset(spk_our_keys, 0, sizeof(spk_our_keys));
+ spk_shift_table = k_buffer;
+ spk_our_keys[0] = spk_shift_table;
+ cp1 += SHIFT_TBL_SIZE;
+ memcpy(cp1, cp, key_data_len + 3);
+ /* get num_keys, states and data */
+ cp1 += 2; /* now pointing at shift states */
+ for (i = 1; i <= states; i++) {
+ ch = *cp1++;
+ if (ch >= SHIFT_TBL_SIZE) {
+ pr_debug("(%d) not valid shift state (max_allowed = %d)\n",
+ ch, SHIFT_TBL_SIZE);
+ return -EINVAL;
+ }
+ spk_shift_table[ch] = i;
+ }
+ keymap_flags = *cp1++;
+ while ((ch = *cp1)) {
+ if (ch >= MAX_KEY) {
+ pr_debug("(%d), not valid key, (max_allowed = %d)\n",
+ ch, MAX_KEY);
+ return -EINVAL;
+ }
+ spk_our_keys[ch] = cp1;
+ cp1 += states + 1;
+ }
+ return 0;
+}
+
+static struct var_t spk_vars[] = {
+ /* bell must be first to set high limit */
+ {BELL_POS, .u.n = {NULL, 0, 0, 0, 0, 0, NULL} },
+ {SPELL_DELAY, .u.n = {NULL, 0, 0, 4, 0, 0, NULL} },
+ {ATTRIB_BLEEP, .u.n = {NULL, 1, 0, 3, 0, 0, NULL} },
+ {BLEEPS, .u.n = {NULL, 3, 0, 3, 0, 0, NULL} },
+ {BLEEP_TIME, .u.n = {NULL, 30, 1, 200, 0, 0, NULL} },
+ {PUNC_LEVEL, .u.n = {NULL, 1, 0, 4, 0, 0, NULL} },
+ {READING_PUNC, .u.n = {NULL, 1, 0, 4, 0, 0, NULL} },
+ {CURSOR_TIME, .u.n = {NULL, 120, 50, 600, 0, 0, NULL} },
+ {SAY_CONTROL, TOGGLE_0},
+ {SAY_WORD_CTL, TOGGLE_0},
+ {NO_INTERRUPT, TOGGLE_0},
+ {KEY_ECHO, .u.n = {NULL, 1, 0, 2, 0, 0, NULL} },
+ V_LAST_VAR
+};
+
+static void toggle_cursoring(struct vc_data *vc)
+{
+ if (cursor_track == read_all_mode)
+ cursor_track = prev_cursor_track;
+ if (++cursor_track >= CT_Max)
+ cursor_track = 0;
+ synth_printf("%s\n", spk_msg_get(MSG_CURSOR_MSGS_START + cursor_track));
+}
+
+void spk_reset_default_chars(void)
+{
+ int i;
+
+ /* First, free any non-default */
+ for (i = 0; i < 256; i++) {
+ if (spk_characters[i] &&
+ (spk_characters[i] != spk_default_chars[i]))
+ kfree(spk_characters[i]);
+ }
+
+ memcpy(spk_characters, spk_default_chars, sizeof(spk_default_chars));
+}
+
+void spk_reset_default_chartab(void)
+{
+ memcpy(spk_chartab, default_chartab, sizeof(default_chartab));
+}
+
+static const struct st_bits_data *pb_edit;
+
+static int edit_bits(struct vc_data *vc, u_char type, u_char ch, u_short key)
+{
+ short mask = pb_edit->mask, ch_type = spk_chartab[ch];
+
+ if (type != KT_LATIN || (ch_type & B_NUM) || ch < SPACE)
+ return -1;
+ if (ch == SPACE) {
+ synth_printf("%s\n", spk_msg_get(MSG_EDIT_DONE));
+ spk_special_handler = NULL;
+ return 1;
+ }
+ if (mask < PUNC && !(ch_type & PUNC))
+ return -1;
+ spk_chartab[ch] ^= mask;
+ speak_char(ch);
+ synth_printf(" %s\n",
+ (spk_chartab[ch] & mask) ? spk_msg_get(MSG_ON) :
+ spk_msg_get(MSG_OFF));
+ return 1;
+}
+
+/* Allocation concurrency is protected by the console semaphore */
+static int speakup_allocate(struct vc_data *vc, gfp_t gfp_flags)
+{
+ int vc_num;
+
+ vc_num = vc->vc_num;
+ if (!speakup_console[vc_num]) {
+ speakup_console[vc_num] = kzalloc(sizeof(*speakup_console[0]),
+ gfp_flags);
+ if (!speakup_console[vc_num])
+ return -ENOMEM;
+ speakup_date(vc);
+ } else if (!spk_parked) {
+ speakup_date(vc);
+ }
+
+ return 0;
+}
+
+static void speakup_deallocate(struct vc_data *vc)
+{
+ int vc_num;
+
+ vc_num = vc->vc_num;
+ kfree(speakup_console[vc_num]);
+ speakup_console[vc_num] = NULL;
+}
+
+static u_char is_cursor;
+static u_long old_cursor_pos, old_cursor_x, old_cursor_y;
+static int cursor_con;
+
+static void reset_highlight_buffers(struct vc_data *);
+
+static int read_all_key;
+
+static int in_keyboard_notifier;
+
+static void start_read_all_timer(struct vc_data *vc, int command);
+
+enum {
+ RA_NOTHING,
+ RA_NEXT_SENT,
+ RA_PREV_LINE,
+ RA_NEXT_LINE,
+ RA_PREV_SENT,
+ RA_DOWN_ARROW,
+ RA_TIMER,
+ RA_FIND_NEXT_SENT,
+ RA_FIND_PREV_SENT,
+};
+
+static void kbd_fakekey2(struct vc_data *vc, int command)
+{
+ del_timer(&cursor_timer);
+ speakup_fake_down_arrow();
+ start_read_all_timer(vc, command);
+}
+
+static void read_all_doc(struct vc_data *vc)
+{
+ if ((vc->vc_num != fg_console) || !synth || spk_shut_up)
+ return;
+ if (!synth_supports_indexing())
+ return;
+ if (cursor_track != read_all_mode)
+ prev_cursor_track = cursor_track;
+ cursor_track = read_all_mode;
+ spk_reset_index_count(0);
+ if (get_sentence_buf(vc, 0) == -1) {
+ del_timer(&cursor_timer);
+ if (!in_keyboard_notifier)
+ speakup_fake_down_arrow();
+ start_read_all_timer(vc, RA_DOWN_ARROW);
+ } else {
+ say_sentence_num(0, 0);
+ synth_insert_next_index(0);
+ start_read_all_timer(vc, RA_TIMER);
+ }
+}
+
+static void stop_read_all(struct vc_data *vc)
+{
+ del_timer(&cursor_timer);
+ cursor_track = prev_cursor_track;
+ spk_shut_up &= 0xfe;
+ spk_do_flush();
+}
+
+static void start_read_all_timer(struct vc_data *vc, int command)
+{
+ struct var_t *cursor_timeout;
+
+ cursor_con = vc->vc_num;
+ read_all_key = command;
+ cursor_timeout = spk_get_var(CURSOR_TIME);
+ mod_timer(&cursor_timer,
+ jiffies + msecs_to_jiffies(cursor_timeout->u.n.value));
+}
+
+static void handle_cursor_read_all(struct vc_data *vc, int command)
+{
+ int indcount, sentcount, rv, sn;
+
+ switch (command) {
+ case RA_NEXT_SENT:
+ /* Get Current Sentence */
+ spk_get_index_count(&indcount, &sentcount);
+ /*printk("%d %d ", indcount, sentcount); */
+ spk_reset_index_count(sentcount + 1);
+ if (indcount == 1) {
+ if (!say_sentence_num(sentcount + 1, 0)) {
+ kbd_fakekey2(vc, RA_FIND_NEXT_SENT);
+ return;
+ }
+ synth_insert_next_index(0);
+ } else {
+ sn = 0;
+ if (!say_sentence_num(sentcount + 1, 1)) {
+ sn = 1;
+ spk_reset_index_count(sn);
+ } else {
+ synth_insert_next_index(0);
+ }
+ if (!say_sentence_num(sn, 0)) {
+ kbd_fakekey2(vc, RA_FIND_NEXT_SENT);
+ return;
+ }
+ synth_insert_next_index(0);
+ }
+ start_read_all_timer(vc, RA_TIMER);
+ break;
+ case RA_PREV_SENT:
+ break;
+ case RA_NEXT_LINE:
+ read_all_doc(vc);
+ break;
+ case RA_PREV_LINE:
+ break;
+ case RA_DOWN_ARROW:
+ if (get_sentence_buf(vc, 0) == -1) {
+ kbd_fakekey2(vc, RA_DOWN_ARROW);
+ } else {
+ say_sentence_num(0, 0);
+ synth_insert_next_index(0);
+ start_read_all_timer(vc, RA_TIMER);
+ }
+ break;
+ case RA_FIND_NEXT_SENT:
+ rv = get_sentence_buf(vc, 0);
+ if (rv == -1)
+ read_all_doc(vc);
+ if (rv == 0) {
+ kbd_fakekey2(vc, RA_FIND_NEXT_SENT);
+ } else {
+ say_sentence_num(1, 0);
+ synth_insert_next_index(0);
+ start_read_all_timer(vc, RA_TIMER);
+ }
+ break;
+ case RA_FIND_PREV_SENT:
+ break;
+ case RA_TIMER:
+ spk_get_index_count(&indcount, &sentcount);
+ if (indcount < 2)
+ kbd_fakekey2(vc, RA_DOWN_ARROW);
+ else
+ start_read_all_timer(vc, RA_TIMER);
+ break;
+ }
+}
+
+static int pre_handle_cursor(struct vc_data *vc, u_char value, char up_flag)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (cursor_track == read_all_mode) {
+ spk_parked &= 0xfe;
+ if (!synth || up_flag || spk_shut_up) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return NOTIFY_STOP;
+ }
+ del_timer(&cursor_timer);
+ spk_shut_up &= 0xfe;
+ spk_do_flush();
+ start_read_all_timer(vc, value + 1);
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return NOTIFY_STOP;
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return NOTIFY_OK;
+}
+
+static void do_handle_cursor(struct vc_data *vc, u_char value, char up_flag)
+{
+ unsigned long flags;
+ struct var_t *cursor_timeout;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ spk_parked &= 0xfe;
+ if (!synth || up_flag || spk_shut_up || cursor_track == CT_Off) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return;
+ }
+ spk_shut_up &= 0xfe;
+ if (spk_no_intr)
+ spk_do_flush();
+/* the key press flushes if !no_inter but we want to flush on cursor
+ * moves regardless of no_inter state
+ */
+ is_cursor = value + 1;
+ old_cursor_pos = vc->vc_pos;
+ old_cursor_x = vc->state.x;
+ old_cursor_y = vc->state.y;
+ speakup_console[vc->vc_num]->ht.cy = vc->state.y;
+ cursor_con = vc->vc_num;
+ if (cursor_track == CT_Highlight)
+ reset_highlight_buffers(vc);
+ cursor_timeout = spk_get_var(CURSOR_TIME);
+ mod_timer(&cursor_timer,
+ jiffies + msecs_to_jiffies(cursor_timeout->u.n.value));
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+}
+
+static void update_color_buffer(struct vc_data *vc, const u16 *ic, int len)
+{
+ int i, bi, hi;
+ int vc_num = vc->vc_num;
+
+ bi = (vc->vc_attr & 0x70) >> 4;
+ hi = speakup_console[vc_num]->ht.highsize[bi];
+
+ i = 0;
+ if (speakup_console[vc_num]->ht.highsize[bi] == 0) {
+ speakup_console[vc_num]->ht.rpos[bi] = vc->vc_pos;
+ speakup_console[vc_num]->ht.rx[bi] = vc->state.x;
+ speakup_console[vc_num]->ht.ry[bi] = vc->state.y;
+ }
+ while ((hi < COLOR_BUFFER_SIZE) && (i < len)) {
+ if (ic[i] > 32) {
+ speakup_console[vc_num]->ht.highbuf[bi][hi] = ic[i];
+ hi++;
+ } else if ((ic[i] == 32) && (hi != 0)) {
+ if (speakup_console[vc_num]->ht.highbuf[bi][hi - 1] !=
+ 32) {
+ speakup_console[vc_num]->ht.highbuf[bi][hi] =
+ ic[i];
+ hi++;
+ }
+ }
+ i++;
+ }
+ speakup_console[vc_num]->ht.highsize[bi] = hi;
+}
+
+static void reset_highlight_buffers(struct vc_data *vc)
+{
+ int i;
+ int vc_num = vc->vc_num;
+
+ for (i = 0; i < 8; i++)
+ speakup_console[vc_num]->ht.highsize[i] = 0;
+}
+
+static int count_highlight_color(struct vc_data *vc)
+{
+ int i, bg;
+ int cc;
+ int vc_num = vc->vc_num;
+ u16 ch;
+ u16 *start = (u16 *)vc->vc_origin;
+
+ for (i = 0; i < 8; i++)
+ speakup_console[vc_num]->ht.bgcount[i] = 0;
+
+ for (i = 0; i < vc->vc_rows; i++) {
+ u16 *end = start + vc->vc_cols * 2;
+ u16 *ptr;
+
+ for (ptr = start; ptr < end; ptr++) {
+ ch = get_attributes(vc, ptr);
+ bg = (ch & 0x70) >> 4;
+ speakup_console[vc_num]->ht.bgcount[bg]++;
+ }
+ start += vc->vc_size_row;
+ }
+
+ cc = 0;
+ for (i = 0; i < 8; i++)
+ if (speakup_console[vc_num]->ht.bgcount[i] > 0)
+ cc++;
+ return cc;
+}
+
+static int get_highlight_color(struct vc_data *vc)
+{
+ int i, j;
+ unsigned int cptr[8];
+ int vc_num = vc->vc_num;
+
+ for (i = 0; i < 8; i++)
+ cptr[i] = i;
+
+ for (i = 0; i < 7; i++)
+ for (j = i + 1; j < 8; j++)
+ if (speakup_console[vc_num]->ht.bgcount[cptr[i]] >
+ speakup_console[vc_num]->ht.bgcount[cptr[j]])
+ swap(cptr[i], cptr[j]);
+
+ for (i = 0; i < 8; i++)
+ if (speakup_console[vc_num]->ht.bgcount[cptr[i]] != 0)
+ if (speakup_console[vc_num]->ht.highsize[cptr[i]] > 0)
+ return cptr[i];
+ return -1;
+}
+
+static int speak_highlight(struct vc_data *vc)
+{
+ int hc, d;
+ int vc_num = vc->vc_num;
+
+ if (count_highlight_color(vc) == 1)
+ return 0;
+ hc = get_highlight_color(vc);
+ if (hc != -1) {
+ d = vc->state.y - speakup_console[vc_num]->ht.cy;
+ if ((d == 1) || (d == -1))
+ if (speakup_console[vc_num]->ht.ry[hc] != vc->state.y)
+ return 0;
+ spk_parked |= 0x01;
+ spk_do_flush();
+ spkup_write(speakup_console[vc_num]->ht.highbuf[hc],
+ speakup_console[vc_num]->ht.highsize[hc]);
+ spk_pos = spk_cp = speakup_console[vc_num]->ht.rpos[hc];
+ spk_x = spk_cx = speakup_console[vc_num]->ht.rx[hc];
+ spk_y = spk_cy = speakup_console[vc_num]->ht.ry[hc];
+ return 1;
+ }
+ return 0;
+}
+
+static void cursor_done(struct timer_list *unused)
+{
+ struct vc_data *vc = vc_cons[cursor_con].d;
+ unsigned long flags;
+
+ del_timer(&cursor_timer);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (cursor_con != fg_console) {
+ is_cursor = 0;
+ goto out;
+ }
+ speakup_date(vc);
+ if (win_enabled) {
+ if (vc->state.x >= win_left && vc->state.x <= win_right &&
+ vc->state.y >= win_top && vc->state.y <= win_bottom) {
+ spk_keydown = 0;
+ is_cursor = 0;
+ goto out;
+ }
+ }
+ if (cursor_track == read_all_mode) {
+ handle_cursor_read_all(vc, read_all_key);
+ goto out;
+ }
+ if (cursor_track == CT_Highlight) {
+ if (speak_highlight(vc)) {
+ spk_keydown = 0;
+ is_cursor = 0;
+ goto out;
+ }
+ }
+ if (cursor_track == CT_Window)
+ speakup_win_say(vc);
+ else if (is_cursor == 1 || is_cursor == 4)
+ say_line_from_to(vc, 0, vc->vc_cols, 0);
+ else
+ say_char(vc);
+ spk_keydown = 0;
+ is_cursor = 0;
+out:
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+}
+
+/* called by: vt_notifier_call() */
+static void speakup_bs(struct vc_data *vc)
+{
+ unsigned long flags;
+
+ if (!speakup_console[vc->vc_num])
+ return;
+ if (!spin_trylock_irqsave(&speakup_info.spinlock, flags))
+ /* Speakup output, discard */
+ return;
+ if (!spk_parked)
+ speakup_date(vc);
+ if (spk_shut_up || !synth) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return;
+ }
+ if (vc->vc_num == fg_console && spk_keydown) {
+ spk_keydown = 0;
+ if (!is_cursor)
+ say_char(vc);
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+}
+
+/* called by: vt_notifier_call() */
+static void speakup_con_write(struct vc_data *vc, u16 *str, int len)
+{
+ unsigned long flags;
+
+ if ((vc->vc_num != fg_console) || spk_shut_up || !synth)
+ return;
+ if (!spin_trylock_irqsave(&speakup_info.spinlock, flags))
+ /* Speakup output, discard */
+ return;
+ if (spk_bell_pos && spk_keydown && (vc->state.x == spk_bell_pos - 1))
+ bleep(3);
+ if ((is_cursor) || (cursor_track == read_all_mode)) {
+ if (cursor_track == CT_Highlight)
+ update_color_buffer(vc, str, len);
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return;
+ }
+ if (win_enabled) {
+ if (vc->state.x >= win_left && vc->state.x <= win_right &&
+ vc->state.y >= win_top && vc->state.y <= win_bottom) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return;
+ }
+ }
+
+ spkup_write(str, len);
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+}
+
+static void speakup_con_update(struct vc_data *vc)
+{
+ unsigned long flags;
+
+ if (!speakup_console[vc->vc_num] || spk_parked || !synth)
+ return;
+ if (!spin_trylock_irqsave(&speakup_info.spinlock, flags))
+ /* Speakup output, discard */
+ return;
+ speakup_date(vc);
+ if (vc->vc_mode == KD_GRAPHICS && !spk_paused && spk_str_pause[0]) {
+ synth_printf("%s", spk_str_pause);
+ spk_paused = true;
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+}
+
+static void do_handle_spec(struct vc_data *vc, u_char value, char up_flag)
+{
+ unsigned long flags;
+ int on_off = 2;
+ char *label;
+
+ if (!synth || up_flag || spk_killed)
+ return;
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ spk_shut_up &= 0xfe;
+ if (spk_no_intr)
+ spk_do_flush();
+ switch (value) {
+ case KVAL(K_CAPS):
+ label = spk_msg_get(MSG_KEYNAME_CAPSLOCK);
+ on_off = vt_get_leds(fg_console, VC_CAPSLOCK);
+ break;
+ case KVAL(K_NUM):
+ label = spk_msg_get(MSG_KEYNAME_NUMLOCK);
+ on_off = vt_get_leds(fg_console, VC_NUMLOCK);
+ break;
+ case KVAL(K_HOLD):
+ label = spk_msg_get(MSG_KEYNAME_SCROLLLOCK);
+ on_off = vt_get_leds(fg_console, VC_SCROLLOCK);
+ if (speakup_console[vc->vc_num])
+ speakup_console[vc->vc_num]->tty_stopped = on_off;
+ break;
+ default:
+ spk_parked &= 0xfe;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return;
+ }
+ if (on_off < 2)
+ synth_printf("%s %s\n",
+ label, spk_msg_get(MSG_STATUS_START + on_off));
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+}
+
+static int inc_dec_var(u_char value)
+{
+ struct st_var_header *p_header;
+ struct var_t *var_data;
+ char num_buf[32];
+ char *cp = num_buf;
+ char *pn;
+ int var_id = (int)value - VAR_START;
+ int how = (var_id & 1) ? E_INC : E_DEC;
+
+ var_id = var_id / 2 + FIRST_SET_VAR;
+ p_header = spk_get_var_header(var_id);
+ if (!p_header)
+ return -1;
+ if (p_header->var_type != VAR_NUM)
+ return -1;
+ var_data = p_header->data;
+ if (spk_set_num_var(1, p_header, how) != 0)
+ return -1;
+ if (!spk_close_press) {
+ for (pn = p_header->name; *pn; pn++) {
+ if (*pn == '_')
+ *cp = SPACE;
+ else
+ *cp++ = *pn;
+ }
+ }
+ snprintf(cp, sizeof(num_buf) - (cp - num_buf), " %d ",
+ var_data->u.n.value);
+ synth_printf("%s", num_buf);
+ return 0;
+}
+
+static void speakup_win_set(struct vc_data *vc)
+{
+ char info[40];
+
+ if (win_start > 1) {
+ synth_printf("%s\n", spk_msg_get(MSG_WINDOW_ALREADY_SET));
+ return;
+ }
+ if (spk_x < win_left || spk_y < win_top) {
+ synth_printf("%s\n", spk_msg_get(MSG_END_BEFORE_START));
+ return;
+ }
+ if (win_start && spk_x == win_left && spk_y == win_top) {
+ win_left = 0;
+ win_right = vc->vc_cols - 1;
+ win_bottom = spk_y;
+ snprintf(info, sizeof(info), spk_msg_get(MSG_WINDOW_LINE),
+ (int)win_top + 1);
+ } else {
+ if (!win_start) {
+ win_top = spk_y;
+ win_left = spk_x;
+ } else {
+ win_bottom = spk_y;
+ win_right = spk_x;
+ }
+ snprintf(info, sizeof(info), spk_msg_get(MSG_WINDOW_BOUNDARY),
+ (win_start) ?
+ spk_msg_get(MSG_END) : spk_msg_get(MSG_START),
+ (int)spk_y + 1, (int)spk_x + 1);
+ }
+ synth_printf("%s\n", info);
+ win_start++;
+}
+
+static void speakup_win_clear(struct vc_data *vc)
+{
+ win_top = 0;
+ win_bottom = 0;
+ win_left = 0;
+ win_right = 0;
+ win_start = 0;
+ synth_printf("%s\n", spk_msg_get(MSG_WINDOW_CLEARED));
+}
+
+static void speakup_win_enable(struct vc_data *vc)
+{
+ if (win_start < 2) {
+ synth_printf("%s\n", spk_msg_get(MSG_NO_WINDOW));
+ return;
+ }
+ win_enabled ^= 1;
+ if (win_enabled)
+ synth_printf("%s\n", spk_msg_get(MSG_WINDOW_SILENCED));
+ else
+ synth_printf("%s\n", spk_msg_get(MSG_WINDOW_SILENCE_DISABLED));
+}
+
+static void speakup_bits(struct vc_data *vc)
+{
+ int val = this_speakup_key - (FIRST_EDIT_BITS - 1);
+
+ if (spk_special_handler || val < 1 || val > 6) {
+ synth_printf("%s\n", spk_msg_get(MSG_ERROR));
+ return;
+ }
+ pb_edit = &spk_punc_info[val];
+ synth_printf(spk_msg_get(MSG_EDIT_PROMPT), pb_edit->name);
+ spk_special_handler = edit_bits;
+}
+
+static int handle_goto(struct vc_data *vc, u_char type, u_char ch, u_short key)
+{
+ static u_char goto_buf[8];
+ static int num;
+ int maxlen;
+ char *cp;
+ u16 wch;
+
+ if (type == KT_SPKUP && ch == SPEAKUP_GOTO)
+ goto do_goto;
+ if (type == KT_LATIN && ch == '\n')
+ goto do_goto;
+ if (type != 0)
+ goto oops;
+ if (ch == 8) {
+ u16 wch;
+
+ if (num == 0)
+ return -1;
+ wch = goto_buf[--num];
+ goto_buf[num] = '\0';
+ spkup_write(&wch, 1);
+ return 1;
+ }
+ if (ch < '+' || ch > 'y')
+ goto oops;
+ wch = ch;
+ goto_buf[num++] = ch;
+ goto_buf[num] = '\0';
+ spkup_write(&wch, 1);
+ maxlen = (*goto_buf >= '0') ? 3 : 4;
+ if ((ch == '+' || ch == '-') && num == 1)
+ return 1;
+ if (ch >= '0' && ch <= '9' && num < maxlen)
+ return 1;
+ if (num < maxlen - 1 || num > maxlen)
+ goto oops;
+ if (ch < 'x' || ch > 'y') {
+oops:
+ if (!spk_killed)
+ synth_printf(" %s\n", spk_msg_get(MSG_GOTO_CANCELED));
+ goto_buf[num = 0] = '\0';
+ spk_special_handler = NULL;
+ return 1;
+ }
+
+ /* Do not replace with kstrtoul: here we need cp to be updated */
+ goto_pos = simple_strtoul(goto_buf, &cp, 10);
+
+ if (*cp == 'x') {
+ if (*goto_buf < '0')
+ goto_pos += spk_x;
+ else if (goto_pos > 0)
+ goto_pos--;
+
+ if (goto_pos >= vc->vc_cols)
+ goto_pos = vc->vc_cols - 1;
+ goto_x = 1;
+ } else {
+ if (*goto_buf < '0')
+ goto_pos += spk_y;
+ else if (goto_pos > 0)
+ goto_pos--;
+
+ if (goto_pos >= vc->vc_rows)
+ goto_pos = vc->vc_rows - 1;
+ goto_x = 0;
+ }
+ goto_buf[num = 0] = '\0';
+do_goto:
+ spk_special_handler = NULL;
+ spk_parked |= 0x01;
+ if (goto_x) {
+ spk_pos -= spk_x * 2;
+ spk_x = goto_pos;
+ spk_pos += goto_pos * 2;
+ say_word(vc);
+ } else {
+ spk_y = goto_pos;
+ spk_pos = vc->vc_origin + (goto_pos * vc->vc_size_row);
+ say_line(vc);
+ }
+ return 1;
+}
+
+static void speakup_goto(struct vc_data *vc)
+{
+ if (spk_special_handler) {
+ synth_printf("%s\n", spk_msg_get(MSG_ERROR));
+ return;
+ }
+ synth_printf("%s\n", spk_msg_get(MSG_GOTO));
+ spk_special_handler = handle_goto;
+}
+
+static void speakup_help(struct vc_data *vc)
+{
+ spk_handle_help(vc, KT_SPKUP, SPEAKUP_HELP, 0);
+}
+
+static void do_nothing(struct vc_data *vc)
+{
+ return; /* flush done in do_spkup */
+}
+
+static u_char key_speakup, spk_key_locked;
+
+static void speakup_lock(struct vc_data *vc)
+{
+ if (!spk_key_locked) {
+ spk_key_locked = 16;
+ key_speakup = 16;
+ } else {
+ spk_key_locked = 0;
+ key_speakup = 0;
+ }
+}
+
+typedef void (*spkup_hand) (struct vc_data *);
+static spkup_hand spkup_handler[] = {
+ /* must be ordered same as defines in speakup.h */
+ do_nothing, speakup_goto, speech_kill, speakup_shut_up,
+ speakup_cut, speakup_paste, say_first_char, say_last_char,
+ say_char, say_prev_char, say_next_char,
+ say_word, say_prev_word, say_next_word,
+ say_line, say_prev_line, say_next_line,
+ top_edge, bottom_edge, left_edge, right_edge,
+ spell_word, spell_word, say_screen,
+ say_position, say_attributes,
+ speakup_off, speakup_parked, say_line, /* this is for indent */
+ say_from_top, say_to_bottom,
+ say_from_left, say_to_right,
+ say_char_num, speakup_bits, speakup_bits, say_phonetic_char,
+ speakup_bits, speakup_bits, speakup_bits,
+ speakup_win_set, speakup_win_clear, speakup_win_enable, speakup_win_say,
+ speakup_lock, speakup_help, toggle_cursoring, read_all_doc, NULL
+};
+
+static void do_spkup(struct vc_data *vc, u_char value)
+{
+ if (spk_killed && value != SPEECH_KILL)
+ return;
+ spk_keydown = 0;
+ spk_lastkey = 0;
+ spk_shut_up &= 0xfe;
+ this_speakup_key = value;
+ if (value < SPKUP_MAX_FUNC && spkup_handler[value]) {
+ spk_do_flush();
+ (*spkup_handler[value]) (vc);
+ } else {
+ if (inc_dec_var(value) < 0)
+ bleep(9);
+ }
+}
+
+static const char *pad_chars = "0123456789+-*/\015,.?()";
+
+static int
+speakup_key(struct vc_data *vc, int shift_state, int keycode, u_short keysym,
+ int up_flag)
+{
+ unsigned long flags;
+ int kh;
+ u_char *key_info;
+ u_char type = KTYP(keysym), value = KVAL(keysym), new_key = 0;
+ u_char shift_info, offset;
+ int ret = 0;
+
+ if (!synth)
+ return 0;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ tty = vc->port.tty;
+ if (type >= 0xf0)
+ type -= 0xf0;
+ if (type == KT_PAD &&
+ (vt_get_leds(fg_console, VC_NUMLOCK))) {
+ if (up_flag) {
+ spk_keydown = 0;
+ goto out;
+ }
+ value = pad_chars[value];
+ spk_lastkey = value;
+ spk_keydown++;
+ spk_parked &= 0xfe;
+ goto no_map;
+ }
+ if (keycode >= MAX_KEY)
+ goto no_map;
+ key_info = spk_our_keys[keycode];
+ if (!key_info)
+ goto no_map;
+ /* Check valid read all mode keys */
+ if ((cursor_track == read_all_mode) && (!up_flag)) {
+ switch (value) {
+ case KVAL(K_DOWN):
+ case KVAL(K_UP):
+ case KVAL(K_LEFT):
+ case KVAL(K_RIGHT):
+ case KVAL(K_PGUP):
+ case KVAL(K_PGDN):
+ break;
+ default:
+ stop_read_all(vc);
+ break;
+ }
+ }
+ shift_info = (shift_state & 0x0f) + key_speakup;
+ offset = spk_shift_table[shift_info];
+ if (offset) {
+ new_key = key_info[offset];
+ if (new_key) {
+ ret = 1;
+ if (new_key == SPK_KEY) {
+ if (!spk_key_locked)
+ key_speakup = (up_flag) ? 0 : 16;
+ if (up_flag || spk_killed)
+ goto out;
+ spk_shut_up &= 0xfe;
+ spk_do_flush();
+ goto out;
+ }
+ if (up_flag)
+ goto out;
+ if (last_keycode == keycode &&
+ time_after(last_spk_jiffy + MAX_DELAY, jiffies)) {
+ spk_close_press = 1;
+ offset = spk_shift_table[shift_info + 32];
+ /* double press? */
+ if (offset && key_info[offset])
+ new_key = key_info[offset];
+ }
+ last_keycode = keycode;
+ last_spk_jiffy = jiffies;
+ type = KT_SPKUP;
+ value = new_key;
+ }
+ }
+no_map:
+ if (type == KT_SPKUP && !spk_special_handler) {
+ do_spkup(vc, new_key);
+ spk_close_press = 0;
+ ret = 1;
+ goto out;
+ }
+ if (up_flag || spk_killed || type == KT_SHIFT)
+ goto out;
+ spk_shut_up &= 0xfe;
+ kh = (value == KVAL(K_DOWN)) ||
+ (value == KVAL(K_UP)) ||
+ (value == KVAL(K_LEFT)) ||
+ (value == KVAL(K_RIGHT));
+ if ((cursor_track != read_all_mode) || !kh)
+ if (!spk_no_intr)
+ spk_do_flush();
+ if (spk_special_handler) {
+ if (type == KT_SPEC && value == 1) {
+ value = '\n';
+ type = KT_LATIN;
+ } else if (type == KT_LETTER) {
+ type = KT_LATIN;
+ } else if (value == 0x7f) {
+ value = 8; /* make del = backspace */
+ }
+ ret = (*spk_special_handler) (vc, type, value, keycode);
+ spk_close_press = 0;
+ if (ret < 0)
+ bleep(9);
+ goto out;
+ }
+ last_keycode = 0;
+out:
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return ret;
+}
+
+static int keyboard_notifier_call(struct notifier_block *nb,
+ unsigned long code, void *_param)
+{
+ struct keyboard_notifier_param *param = _param;
+ struct vc_data *vc = param->vc;
+ int up = !param->down;
+ int ret = NOTIFY_OK;
+ static int keycode; /* to hold the current keycode */
+
+ in_keyboard_notifier = 1;
+
+ if (vc->vc_mode == KD_GRAPHICS)
+ goto out;
+
+ /*
+ * First, determine whether we are handling a fake keypress on
+ * the current processor. If we are, then return NOTIFY_OK,
+ * to pass the keystroke up the chain. This prevents us from
+ * trying to take the Speakup lock while it is held by the
+ * processor on which the simulated keystroke was generated.
+ * Also, the simulated keystrokes should be ignored by Speakup.
+ */
+
+ if (speakup_fake_key_pressed())
+ goto out;
+
+ switch (code) {
+ case KBD_KEYCODE:
+ /* speakup requires keycode and keysym currently */
+ keycode = param->value;
+ break;
+ case KBD_UNBOUND_KEYCODE:
+ /* not used yet */
+ break;
+ case KBD_UNICODE:
+ /* not used yet */
+ break;
+ case KBD_KEYSYM:
+ if (speakup_key(vc, param->shift, keycode, param->value, up))
+ ret = NOTIFY_STOP;
+ else if (KTYP(param->value) == KT_CUR)
+ ret = pre_handle_cursor(vc, KVAL(param->value), up);
+ break;
+ case KBD_POST_KEYSYM:{
+ unsigned char type = KTYP(param->value) - 0xf0;
+ unsigned char val = KVAL(param->value);
+
+ switch (type) {
+ case KT_SHIFT:
+ do_handle_shift(vc, val, up);
+ break;
+ case KT_LATIN:
+ case KT_LETTER:
+ do_handle_latin(vc, val, up);
+ break;
+ case KT_CUR:
+ do_handle_cursor(vc, val, up);
+ break;
+ case KT_SPEC:
+ do_handle_spec(vc, val, up);
+ break;
+ }
+ break;
+ }
+ }
+out:
+ in_keyboard_notifier = 0;
+ return ret;
+}
+
+static int vt_notifier_call(struct notifier_block *nb,
+ unsigned long code, void *_param)
+{
+ struct vt_notifier_param *param = _param;
+ struct vc_data *vc = param->vc;
+
+ switch (code) {
+ case VT_ALLOCATE:
+ if (vc->vc_mode == KD_TEXT)
+ speakup_allocate(vc, GFP_ATOMIC);
+ break;
+ case VT_DEALLOCATE:
+ speakup_deallocate(vc);
+ break;
+ case VT_WRITE:
+ if (param->c == '\b') {
+ speakup_bs(vc);
+ } else {
+ u16 d = param->c;
+
+ speakup_con_write(vc, &d, 1);
+ }
+ break;
+ case VT_UPDATE:
+ speakup_con_update(vc);
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+/* called by: module_exit() */
+static void __exit speakup_exit(void)
+{
+ int i;
+
+ unregister_keyboard_notifier(&keyboard_notifier_block);
+ unregister_vt_notifier(&vt_notifier_block);
+ speakup_unregister_devsynth();
+ speakup_cancel_selection();
+ speakup_cancel_paste();
+ del_timer_sync(&cursor_timer);
+ kthread_stop(speakup_task);
+ speakup_task = NULL;
+ mutex_lock(&spk_mutex);
+ synth_release();
+ mutex_unlock(&spk_mutex);
+ spk_ttyio_unregister_ldisc();
+
+ speakup_kobj_exit();
+
+ for (i = 0; i < MAX_NR_CONSOLES; i++)
+ kfree(speakup_console[i]);
+
+ speakup_remove_virtual_keyboard();
+
+ for (i = 0; i < MAXVARS; i++)
+ speakup_unregister_var(i);
+
+ for (i = 0; i < 256; i++) {
+ if (spk_characters[i] != spk_default_chars[i])
+ kfree(spk_characters[i]);
+ }
+
+ spk_free_user_msgs();
+}
+
+/* call by: module_init() */
+static int __init speakup_init(void)
+{
+ int i;
+ long err = 0;
+ struct vc_data *vc = vc_cons[fg_console].d;
+ struct var_t *var;
+
+ /* These first few initializations cannot fail. */
+ spk_initialize_msgs(); /* Initialize arrays for i18n. */
+ spk_reset_default_chars();
+ spk_reset_default_chartab();
+ spk_strlwr(synth_name);
+ spk_vars[0].u.n.high = vc->vc_cols;
+ for (var = spk_vars; var->var_id != MAXVARS; var++)
+ speakup_register_var(var);
+ for (var = synth_time_vars;
+ (var->var_id >= 0) && (var->var_id < MAXVARS); var++)
+ speakup_register_var(var);
+ for (i = 1; spk_punc_info[i].mask != 0; i++)
+ spk_set_mask_bits(NULL, i, 2);
+
+ spk_set_key_info(spk_key_defaults, spk_key_buf);
+
+ /* From here on out, initializations can fail. */
+ err = speakup_add_virtual_keyboard();
+ if (err)
+ goto error_virtkeyboard;
+
+ for (i = 0; i < MAX_NR_CONSOLES; i++)
+ if (vc_cons[i].d) {
+ err = speakup_allocate(vc_cons[i].d, GFP_KERNEL);
+ if (err)
+ goto error_kobjects;
+ }
+
+ if (spk_quiet_boot)
+ spk_shut_up |= 0x01;
+
+ err = speakup_kobj_init();
+ if (err)
+ goto error_kobjects;
+
+ spk_ttyio_register_ldisc();
+ synth_init(synth_name);
+ speakup_register_devsynth();
+ /*
+ * register_devsynth might fail, but this error is not fatal.
+ * /dev/synth is an extra feature; the rest of Speakup
+ * will work fine without it.
+ */
+
+ err = register_keyboard_notifier(&keyboard_notifier_block);
+ if (err)
+ goto error_kbdnotifier;
+ err = register_vt_notifier(&vt_notifier_block);
+ if (err)
+ goto error_vtnotifier;
+
+ speakup_task = kthread_create(speakup_thread, NULL, "speakup");
+
+ if (IS_ERR(speakup_task)) {
+ err = PTR_ERR(speakup_task);
+ goto error_task;
+ }
+
+ set_user_nice(speakup_task, 10);
+ wake_up_process(speakup_task);
+
+ pr_info("speakup %s: initialized\n", SPEAKUP_VERSION);
+ pr_info("synth name on entry is: %s\n", synth_name);
+ goto out;
+
+error_task:
+ unregister_vt_notifier(&vt_notifier_block);
+
+error_vtnotifier:
+ unregister_keyboard_notifier(&keyboard_notifier_block);
+ del_timer(&cursor_timer);
+
+error_kbdnotifier:
+ speakup_unregister_devsynth();
+ mutex_lock(&spk_mutex);
+ synth_release();
+ mutex_unlock(&spk_mutex);
+ speakup_kobj_exit();
+
+error_kobjects:
+ for (i = 0; i < MAX_NR_CONSOLES; i++)
+ kfree(speakup_console[i]);
+
+ speakup_remove_virtual_keyboard();
+
+error_virtkeyboard:
+ for (i = 0; i < MAXVARS; i++)
+ speakup_unregister_var(i);
+
+ for (i = 0; i < 256; i++) {
+ if (spk_characters[i] != spk_default_chars[i])
+ kfree(spk_characters[i]);
+ }
+
+ spk_free_user_msgs();
+
+out:
+ return err;
+}
+
+module_init(speakup_init);
+module_exit(speakup_exit);
diff --git a/drivers/accessibility/speakup/selection.c b/drivers/accessibility/speakup/selection.c
new file mode 100644
index 000000000..7df7afad5
--- /dev/null
+++ b/drivers/accessibility/speakup/selection.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/slab.h> /* for kmalloc */
+#include <linux/consolemap.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/device.h> /* for dev_warn */
+#include <linux/selection.h>
+#include <linux/workqueue.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/atomic.h>
+#include <linux/console.h>
+
+#include "speakup.h"
+
+unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */
+struct vc_data *spk_sel_cons;
+
+struct speakup_selection_work {
+ struct work_struct work;
+ struct tiocl_selection sel;
+ struct tty_struct *tty;
+};
+
+static void __speakup_set_selection(struct work_struct *work)
+{
+ struct speakup_selection_work *ssw =
+ container_of(work, struct speakup_selection_work, work);
+
+ struct tty_struct *tty;
+ struct tiocl_selection sel;
+
+ sel = ssw->sel;
+
+ /* this ensures we copy sel before releasing the lock below */
+ rmb();
+
+ /* release the lock by setting tty of the struct to NULL */
+ tty = xchg(&ssw->tty, NULL);
+
+ if (spk_sel_cons != vc_cons[fg_console].d) {
+ spk_sel_cons = vc_cons[fg_console].d;
+ pr_warn("Selection: mark console not the same as cut\n");
+ goto unref;
+ }
+
+ console_lock();
+ clear_selection();
+ console_unlock();
+
+ set_selection_kernel(&sel, tty);
+
+unref:
+ tty_kref_put(tty);
+}
+
+static struct speakup_selection_work speakup_sel_work = {
+ .work = __WORK_INITIALIZER(speakup_sel_work.work,
+ __speakup_set_selection)
+};
+
+int speakup_set_selection(struct tty_struct *tty)
+{
+ /* we get kref here first in order to avoid a subtle race when
+ * cancelling selection work. getting kref first establishes the
+ * invariant that if speakup_sel_work.tty is not NULL when
+ * speakup_cancel_selection() is called, it must be the case that a put
+ * kref is pending.
+ */
+ tty_kref_get(tty);
+ if (cmpxchg(&speakup_sel_work.tty, NULL, tty)) {
+ tty_kref_put(tty);
+ return -EBUSY;
+ }
+ /* now we have the 'lock' by setting tty member of
+ * speakup_selection_work. wmb() ensures that writes to
+ * speakup_sel_work don't happen before cmpxchg() above.
+ */
+ wmb();
+
+ speakup_sel_work.sel.xs = spk_xs + 1;
+ speakup_sel_work.sel.ys = spk_ys + 1;
+ speakup_sel_work.sel.xe = spk_xe + 1;
+ speakup_sel_work.sel.ye = spk_ye + 1;
+ speakup_sel_work.sel.sel_mode = TIOCL_SELCHAR;
+
+ schedule_work_on(WORK_CPU_UNBOUND, &speakup_sel_work.work);
+
+ return 0;
+}
+
+void speakup_cancel_selection(void)
+{
+ struct tty_struct *tty;
+
+ cancel_work_sync(&speakup_sel_work.work);
+ /* setting to null so that if work fails to run and we cancel it,
+ * we can run it again without getting EBUSY forever from there on.
+ * we need to use xchg here to avoid race with speakup_set_selection()
+ */
+ tty = xchg(&speakup_sel_work.tty, NULL);
+ if (tty)
+ tty_kref_put(tty);
+}
+
+static void __speakup_paste_selection(struct work_struct *work)
+{
+ struct speakup_selection_work *ssw =
+ container_of(work, struct speakup_selection_work, work);
+ struct tty_struct *tty = xchg(&ssw->tty, NULL);
+
+ paste_selection(tty);
+ tty_kref_put(tty);
+}
+
+static struct speakup_selection_work speakup_paste_work = {
+ .work = __WORK_INITIALIZER(speakup_paste_work.work,
+ __speakup_paste_selection)
+};
+
+int speakup_paste_selection(struct tty_struct *tty)
+{
+ tty_kref_get(tty);
+ if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) {
+ tty_kref_put(tty);
+ return -EBUSY;
+ }
+
+ schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work);
+ return 0;
+}
+
+void speakup_cancel_paste(void)
+{
+ struct tty_struct *tty;
+
+ cancel_work_sync(&speakup_paste_work.work);
+ tty = xchg(&speakup_paste_work.tty, NULL);
+ if (tty)
+ tty_kref_put(tty);
+}
diff --git a/drivers/accessibility/speakup/serialio.c b/drivers/accessibility/speakup/serialio.c
new file mode 100644
index 000000000..403b01d66
--- /dev/null
+++ b/drivers/accessibility/speakup/serialio.c
@@ -0,0 +1,318 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+
+#include "spk_types.h"
+#include "speakup.h"
+#include "spk_priv.h"
+#include "serialio.h"
+
+#include <linux/serial_core.h>
+/* WARNING: Do not change this to <linux/serial.h> without testing that
+ * SERIAL_PORT_DFNS does get defined to the appropriate value.
+ */
+#include <asm/serial.h>
+
+#ifndef SERIAL_PORT_DFNS
+#define SERIAL_PORT_DFNS
+#endif
+
+static void start_serial_interrupt(int irq);
+
+static const struct old_serial_port rs_table[] = {
+ SERIAL_PORT_DFNS
+};
+
+static const struct old_serial_port *serstate;
+static int timeouts;
+
+static int spk_serial_out(struct spk_synth *in_synth, const char ch);
+static void spk_serial_send_xchar(char ch);
+static void spk_serial_tiocmset(unsigned int set, unsigned int clear);
+static unsigned char spk_serial_in(void);
+static unsigned char spk_serial_in_nowait(void);
+static void spk_serial_flush_buffer(void);
+static int spk_serial_wait_for_xmitr(struct spk_synth *in_synth);
+
+struct spk_io_ops spk_serial_io_ops = {
+ .synth_out = spk_serial_out,
+ .send_xchar = spk_serial_send_xchar,
+ .tiocmset = spk_serial_tiocmset,
+ .synth_in = spk_serial_in,
+ .synth_in_nowait = spk_serial_in_nowait,
+ .flush_buffer = spk_serial_flush_buffer,
+ .wait_for_xmitr = spk_serial_wait_for_xmitr,
+};
+EXPORT_SYMBOL_GPL(spk_serial_io_ops);
+
+const struct old_serial_port *spk_serial_init(int index)
+{
+ int baud = 9600, quot = 0;
+ unsigned int cval = 0;
+ int cflag = CREAD | HUPCL | CLOCAL | B9600 | CS8;
+ const struct old_serial_port *ser;
+ int err;
+
+ if (index >= ARRAY_SIZE(rs_table)) {
+ pr_info("no port info for ttyS%d\n", index);
+ return NULL;
+ }
+ ser = rs_table + index;
+
+ /* Divisor, bytesize and parity */
+ quot = ser->baud_base / baud;
+ cval = cflag & (CSIZE | CSTOPB);
+#if defined(__powerpc__) || defined(__alpha__)
+ cval >>= 8;
+#else /* !__powerpc__ && !__alpha__ */
+ cval >>= 4;
+#endif /* !__powerpc__ && !__alpha__ */
+ if (cflag & PARENB)
+ cval |= UART_LCR_PARITY;
+ if (!(cflag & PARODD))
+ cval |= UART_LCR_EPAR;
+ if (synth_request_region(ser->port, 8)) {
+ /* try to take it back. */
+ pr_info("Ports not available, trying to steal them\n");
+ __release_region(&ioport_resource, ser->port, 8);
+ err = synth_request_region(ser->port, 8);
+ if (err) {
+ pr_warn("Unable to allocate port at %x, errno %i",
+ ser->port, err);
+ return NULL;
+ }
+ }
+
+ /* Disable UART interrupts, set DTR and RTS high
+ * and set speed.
+ */
+ outb(cval | UART_LCR_DLAB, ser->port + UART_LCR); /* set DLAB */
+ outb(quot & 0xff, ser->port + UART_DLL); /* LS of divisor */
+ outb(quot >> 8, ser->port + UART_DLM); /* MS of divisor */
+ outb(cval, ser->port + UART_LCR); /* reset DLAB */
+
+ /* Turn off Interrupts */
+ outb(0, ser->port + UART_IER);
+ outb(UART_MCR_DTR | UART_MCR_RTS, ser->port + UART_MCR);
+
+ /* If we read 0xff from the LSR, there is no UART here. */
+ if (inb(ser->port + UART_LSR) == 0xff) {
+ synth_release_region(ser->port, 8);
+ serstate = NULL;
+ return NULL;
+ }
+
+ mdelay(1);
+ speakup_info.port_tts = ser->port;
+ serstate = ser;
+
+ start_serial_interrupt(ser->irq);
+
+ return ser;
+}
+
+static irqreturn_t synth_readbuf_handler(int irq, void *dev_id)
+{
+ unsigned long flags;
+ int c;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ while (inb_p(speakup_info.port_tts + UART_LSR) & UART_LSR_DR) {
+ c = inb_p(speakup_info.port_tts + UART_RX);
+ synth->read_buff_add((u_char)c);
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return IRQ_HANDLED;
+}
+
+static void start_serial_interrupt(int irq)
+{
+ int rv;
+
+ if (!synth->read_buff_add)
+ return;
+
+ rv = request_irq(irq, synth_readbuf_handler, IRQF_SHARED,
+ "serial", (void *)synth_readbuf_handler);
+
+ if (rv)
+ pr_err("Unable to request Speakup serial I R Q\n");
+ /* Set MCR */
+ outb(UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2,
+ speakup_info.port_tts + UART_MCR);
+ /* Turn on Interrupts */
+ outb(UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI,
+ speakup_info.port_tts + UART_IER);
+ inb(speakup_info.port_tts + UART_LSR);
+ inb(speakup_info.port_tts + UART_RX);
+ inb(speakup_info.port_tts + UART_IIR);
+ inb(speakup_info.port_tts + UART_MSR);
+ outb(1, speakup_info.port_tts + UART_FCR); /* Turn FIFO On */
+}
+
+static void spk_serial_send_xchar(char ch)
+{
+ int timeout = SPK_XMITR_TIMEOUT;
+
+ while (spk_serial_tx_busy()) {
+ if (!--timeout)
+ break;
+ udelay(1);
+ }
+ outb(ch, speakup_info.port_tts);
+}
+
+static void spk_serial_tiocmset(unsigned int set, unsigned int clear)
+{
+ int old = inb(speakup_info.port_tts + UART_MCR);
+
+ outb((old & ~clear) | set, speakup_info.port_tts + UART_MCR);
+}
+
+int spk_serial_synth_probe(struct spk_synth *synth)
+{
+ const struct old_serial_port *ser;
+ int failed = 0;
+
+ if ((synth->ser >= SPK_LO_TTY) && (synth->ser <= SPK_HI_TTY)) {
+ ser = spk_serial_init(synth->ser);
+ if (!ser) {
+ failed = -1;
+ } else {
+ outb_p(0, ser->port);
+ mdelay(1);
+ outb_p('\r', ser->port);
+ }
+ } else {
+ failed = -1;
+ pr_warn("ttyS%i is an invalid port\n", synth->ser);
+ }
+ if (failed) {
+ pr_info("%s: not found\n", synth->long_name);
+ return -ENODEV;
+ }
+ pr_info("%s: ttyS%i, Driver Version %s\n",
+ synth->long_name, synth->ser, synth->version);
+ synth->alive = 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spk_serial_synth_probe);
+
+void spk_stop_serial_interrupt(void)
+{
+ if (speakup_info.port_tts == 0)
+ return;
+
+ if (!synth->read_buff_add)
+ return;
+
+ /* Turn off interrupts */
+ outb(0, speakup_info.port_tts + UART_IER);
+ /* Free IRQ */
+ free_irq(serstate->irq, (void *)synth_readbuf_handler);
+}
+EXPORT_SYMBOL_GPL(spk_stop_serial_interrupt);
+
+static int spk_serial_wait_for_xmitr(struct spk_synth *in_synth)
+{
+ int tmout = SPK_XMITR_TIMEOUT;
+
+ if ((in_synth->alive) && (timeouts >= NUM_DISABLE_TIMEOUTS)) {
+ pr_warn("%s: too many timeouts, deactivating speakup\n",
+ in_synth->long_name);
+ in_synth->alive = 0;
+ /* No synth any more, so nobody will restart TTYs, and we thus
+ * need to do it ourselves. Now that there is no synth we can
+ * let application flood anyway
+ */
+ speakup_start_ttys();
+ timeouts = 0;
+ return 0;
+ }
+ while (spk_serial_tx_busy()) {
+ if (--tmout == 0) {
+ pr_warn("%s: timed out (tx busy)\n",
+ in_synth->long_name);
+ timeouts++;
+ return 0;
+ }
+ udelay(1);
+ }
+ tmout = SPK_CTS_TIMEOUT;
+ while (!((inb_p(speakup_info.port_tts + UART_MSR)) & UART_MSR_CTS)) {
+ /* CTS */
+ if (--tmout == 0) {
+ timeouts++;
+ return 0;
+ }
+ udelay(1);
+ }
+ timeouts = 0;
+ return 1;
+}
+
+static unsigned char spk_serial_in(void)
+{
+ int tmout = SPK_SERIAL_TIMEOUT;
+
+ while (!(inb_p(speakup_info.port_tts + UART_LSR) & UART_LSR_DR)) {
+ if (--tmout == 0) {
+ pr_warn("time out while waiting for input.\n");
+ return 0xff;
+ }
+ udelay(1);
+ }
+ return inb_p(speakup_info.port_tts + UART_RX);
+}
+
+static unsigned char spk_serial_in_nowait(void)
+{
+ unsigned char lsr;
+
+ lsr = inb_p(speakup_info.port_tts + UART_LSR);
+ if (!(lsr & UART_LSR_DR))
+ return 0;
+ return inb_p(speakup_info.port_tts + UART_RX);
+}
+
+static void spk_serial_flush_buffer(void)
+{
+ /* TODO: flush the UART 16550 buffer */
+}
+
+static int spk_serial_out(struct spk_synth *in_synth, const char ch)
+{
+ if (in_synth->alive && spk_serial_wait_for_xmitr(in_synth)) {
+ outb_p(ch, speakup_info.port_tts);
+ return 1;
+ }
+ return 0;
+}
+
+const char *spk_serial_synth_immediate(struct spk_synth *synth,
+ const char *buff)
+{
+ u_char ch;
+
+ while ((ch = *buff)) {
+ if (ch == '\n')
+ ch = synth->procspeech;
+ if (spk_serial_wait_for_xmitr(synth))
+ outb(ch, speakup_info.port_tts);
+ else
+ return buff;
+ buff++;
+ }
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(spk_serial_synth_immediate);
+
+void spk_serial_release(void)
+{
+ spk_stop_serial_interrupt();
+ if (speakup_info.port_tts == 0)
+ return;
+ synth_release_region(speakup_info.port_tts, 8);
+ speakup_info.port_tts = 0;
+}
+EXPORT_SYMBOL_GPL(spk_serial_release);
diff --git a/drivers/accessibility/speakup/serialio.h b/drivers/accessibility/speakup/serialio.h
new file mode 100644
index 000000000..6f8f86f16
--- /dev/null
+++ b/drivers/accessibility/speakup/serialio.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SPEAKUP_SERIAL_H
+#define _SPEAKUP_SERIAL_H
+
+#include <linux/serial.h> /* for rs_table, serial constants */
+#include <linux/serial_reg.h> /* for more serial constants */
+#include <linux/serial_core.h>
+
+#include "spk_priv.h"
+
+/*
+ * this is cut&paste from 8250.h. Get rid of the structure, the definitions
+ * and this whole broken driver.
+ */
+struct old_serial_port {
+ unsigned int uart; /* unused */
+ unsigned int baud_base;
+ unsigned int port;
+ unsigned int irq;
+ upf_t flags; /* unused */
+};
+
+/* countdown values for serial timeouts in us */
+#define SPK_SERIAL_TIMEOUT SPK_SYNTH_TIMEOUT
+/* countdown values transmitter/dsr timeouts in us */
+#define SPK_XMITR_TIMEOUT 100000
+/* countdown values cts timeouts in us */
+#define SPK_CTS_TIMEOUT 100000
+/* check ttyS0 ... ttyS3 */
+#define SPK_LO_TTY 0
+#define SPK_HI_TTY 3
+/* # of timeouts permitted before disable */
+#define NUM_DISABLE_TIMEOUTS 3
+/* buffer timeout in ms */
+#define SPK_TIMEOUT 100
+#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE)
+
+#define spk_serial_tx_busy() \
+ ((inb(speakup_info.port_tts + UART_LSR) & BOTH_EMPTY) != BOTH_EMPTY)
+
+#endif
diff --git a/drivers/accessibility/speakup/speakup.h b/drivers/accessibility/speakup/speakup.h
new file mode 100644
index 000000000..33594f5a7
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup.h
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SPEAKUP_H
+#define _SPEAKUP_H
+
+#include "spk_types.h"
+#include "i18n.h"
+
+#define SPEAKUP_VERSION "3.1.6"
+#define KEY_MAP_VER 119
+#define SHIFT_TBL_SIZE 64
+#define MAX_DESC_LEN 72
+
+#define TOGGLE_0 .u.n = {NULL, 0, 0, 1, 0, 0, NULL }
+#define TOGGLE_1 .u.n = {NULL, 1, 0, 1, 0, 0, NULL }
+#define MAXVARLEN 15
+
+#define SYNTH_OK 0x0001
+#define B_ALPHA 0x0002
+#define ALPHA 0x0003
+#define B_CAP 0x0004
+#define A_CAP 0x0007
+#define B_NUM 0x0008
+#define NUM 0x0009
+#define ALPHANUM (B_ALPHA | B_NUM)
+#define SOME 0x0010
+#define MOST 0x0020
+#define PUNC 0x0040
+#define A_PUNC 0x0041
+#define B_WDLM 0x0080
+#define WDLM 0x0081
+#define B_EXNUM 0x0100
+#define CH_RPT 0x0200
+#define B_CTL 0x0400
+#define A_CTL (B_CTL + SYNTH_OK)
+#define B_SYM 0x0800
+#define B_CAPSYM (B_CAP | B_SYM)
+
+/* FIXME: u16 */
+#define IS_WDLM(x) (spk_chartab[((u_char)x)] & B_WDLM)
+#define IS_CHAR(x, type) (spk_chartab[((u_char)x)] & type)
+#define IS_TYPE(x, type) ((spk_chartab[((u_char)x)] & type) == type)
+
+int speakup_thread(void *data);
+void spk_reset_default_chars(void);
+void spk_reset_default_chartab(void);
+void synth_start(void);
+void synth_insert_next_index(int sent_num);
+void spk_reset_index_count(int sc);
+void spk_get_index_count(int *linecount, int *sentcount);
+int spk_set_key_info(const u_char *key_info, u_char *k_buffer);
+char *spk_strlwr(char *s);
+char *spk_s2uchar(char *start, char *dest);
+int speakup_kobj_init(void);
+void speakup_kobj_exit(void);
+int spk_chartab_get_value(char *keyword);
+void speakup_register_var(struct var_t *var);
+void speakup_unregister_var(enum var_id_t var_id);
+struct st_var_header *spk_get_var_header(enum var_id_t var_id);
+struct st_var_header *spk_var_header_by_name(const char *name);
+struct punc_var_t *spk_get_punc_var(enum var_id_t var_id);
+int spk_set_num_var(int val, struct st_var_header *var, int how);
+int spk_set_string_var(const char *page, struct st_var_header *var, int len);
+int spk_set_mask_bits(const char *input, const int which, const int how);
+extern special_func spk_special_handler;
+int spk_handle_help(struct vc_data *vc, u_char type, u_char ch, u_short key);
+int synth_init(char *name);
+void synth_release(void);
+
+void spk_do_flush(void);
+void speakup_start_ttys(void);
+void synth_buffer_add(u16 ch);
+void synth_buffer_clear(void);
+int speakup_set_selection(struct tty_struct *tty);
+void speakup_cancel_selection(void);
+int speakup_paste_selection(struct tty_struct *tty);
+void speakup_cancel_paste(void);
+void speakup_register_devsynth(void);
+void speakup_unregister_devsynth(void);
+void synth_write(const char *buf, size_t count);
+int synth_supports_indexing(void);
+
+extern struct vc_data *spk_sel_cons;
+extern unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */
+
+extern wait_queue_head_t speakup_event;
+extern struct kobject *speakup_kobj;
+extern struct task_struct *speakup_task;
+extern const u_char spk_key_defaults[];
+
+/* Protect speakup synthesizer list */
+extern struct mutex spk_mutex;
+extern struct st_spk_t *speakup_console[];
+extern struct spk_synth *synth;
+extern char spk_pitch_buff[];
+extern u_char *spk_our_keys[];
+extern short spk_punc_masks[];
+extern char spk_str_caps_start[], spk_str_caps_stop[], spk_str_pause[];
+extern bool spk_paused;
+extern const struct st_bits_data spk_punc_info[];
+extern u_char spk_key_buf[600];
+extern char *spk_characters[];
+extern char *spk_default_chars[];
+extern u_short spk_chartab[];
+extern int spk_no_intr, spk_say_ctrl, spk_say_word_ctl, spk_punc_level;
+extern int spk_reading_punc, spk_attrib_bleep, spk_bleeps;
+extern int spk_bleep_time, spk_bell_pos;
+extern int spk_spell_delay, spk_key_echo;
+extern short spk_punc_mask;
+extern short spk_pitch_shift, synth_flags;
+extern bool spk_quiet_boot;
+extern char *synth_name;
+extern struct bleep spk_unprocessed_sound;
+
+/* Prototypes from fakekey.c. */
+int speakup_add_virtual_keyboard(void);
+void speakup_remove_virtual_keyboard(void);
+void speakup_fake_down_arrow(void);
+bool speakup_fake_key_pressed(void);
+
+#endif
diff --git a/drivers/accessibility/speakup/speakup_acnt.h b/drivers/accessibility/speakup/speakup_acnt.h
new file mode 100644
index 000000000..cffa938ae
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_acnt.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* speakup_acntpc.h - header file for speakups Accent-PC driver. */
+
+#define SYNTH_IO_EXTENT 0x02
+
+#define SYNTH_CLEAR 0x18 /* stops speech */
+
+ /* Port Status Flags */
+#define SYNTH_READABLE 0x01 /* mask for bit which is nonzero if a
+ * byte can be read from the data port
+ */
+#define SYNTH_WRITABLE 0x02 /* mask for RDY bit, which when set to
+ * 1, indicates the data port is ready
+ * to accept a byte of data.
+ */
+#define SYNTH_QUIET 'S' /* synth is not speaking */
+#define SYNTH_FULL 'F' /* synth is full. */
+#define SYNTH_ALMOST_EMPTY 'M' /* synth has less than 2 seconds of text left */
+#define SYNTH_SPEAKING 's' /* synth is speaking and has a fare way to go */
diff --git a/drivers/accessibility/speakup/speakup_acntpc.c b/drivers/accessibility/speakup/speakup_acntpc.c
new file mode 100644
index 000000000..c94328a5b
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_acntpc.c
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * written by: Kirk Reiser <kirk@braille.uwo.ca>
+ * this version considerably modified by David Borowski, david575@rogers.com
+ *
+ * Copyright (C) 1998-99 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ *
+ * this code is specificly written as a driver for the speakup screenreview
+ * package and is not a general device driver.
+ * This driver is for the Aicom Acent PC internal synthesizer.
+ */
+
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/kthread.h>
+
+#include "spk_priv.h"
+#include "serialio.h"
+#include "speakup.h"
+#include "speakup_acnt.h" /* local header file for Accent values */
+
+#define DRV_VERSION "2.10"
+#define PROCSPEECH '\r'
+
+static int synth_probe(struct spk_synth *synth);
+static void accent_release(void);
+static const char *synth_immediate(struct spk_synth *synth, const char *buf);
+static void do_catch_up(struct spk_synth *synth);
+static void synth_flush(struct spk_synth *synth);
+
+static int synth_port_control;
+static int port_forced;
+static unsigned int synth_portlist[] = { 0x2a8, 0 };
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"\033P8" } },
+ { CAPS_STOP, .u.s = {"\033P5" } },
+ { RATE, .u.n = {"\033R%c", 9, 0, 17, 0, 0, "0123456789abcdefgh" } },
+ { PITCH, .u.n = {"\033P%d", 5, 0, 9, 0, 0, NULL } },
+ { VOL, .u.n = {"\033A%d", 5, 0, 9, 0, 0, NULL } },
+ { TONE, .u.n = {"\033V%d", 5, 0, 9, 0, 0, NULL } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/*
+ * These attributes will appear in /sys/accessibility/speakup/acntpc.
+ */
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute tone_attribute =
+ __ATTR(tone, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &pitch_attribute.attr,
+ &rate_attribute.attr,
+ &tone_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_acntpc = {
+ .name = "acntpc",
+ .version = DRV_VERSION,
+ .long_name = "Accent PC",
+ .init = "\033=X \033Oi\033T2\033=M\033N1\n",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 500,
+ .trigger = 50,
+ .jiffies = 50,
+ .full = 1000,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_serial_io_ops,
+ .probe = synth_probe,
+ .release = accent_release,
+ .synth_immediate = synth_immediate,
+ .catch_up = do_catch_up,
+ .flush = synth_flush,
+ .is_alive = spk_synth_is_alive_nop,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = NULL,
+ .indexing = {
+ .command = NULL,
+ .lowindex = 0,
+ .highindex = 0,
+ .currindex = 0,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "acntpc",
+ },
+};
+
+static inline bool synth_writable(void)
+{
+ return inb_p(synth_port_control) & SYNTH_WRITABLE;
+}
+
+static inline bool synth_full(void)
+{
+ return inb_p(speakup_info.port_tts + UART_RX) == 'F';
+}
+
+static const char *synth_immediate(struct spk_synth *synth, const char *buf)
+{
+ u_char ch;
+
+ while ((ch = *buf)) {
+ int timeout = SPK_XMITR_TIMEOUT;
+
+ if (ch == '\n')
+ ch = PROCSPEECH;
+ if (synth_full())
+ return buf;
+ while (synth_writable()) {
+ if (!--timeout)
+ return buf;
+ udelay(1);
+ }
+ outb_p(ch, speakup_info.port_tts);
+ buf++;
+ }
+ return NULL;
+}
+
+static void do_catch_up(struct spk_synth *synth)
+{
+ u_char ch;
+ unsigned long flags;
+ unsigned long jiff_max;
+ int timeout;
+ int delay_time_val;
+ int jiffy_delta_val;
+ int full_time_val;
+ struct var_t *delay_time;
+ struct var_t *full_time;
+ struct var_t *jiffy_delta;
+
+ jiffy_delta = spk_get_var(JIFFY);
+ delay_time = spk_get_var(DELAY);
+ full_time = spk_get_var(FULL);
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+
+ jiff_max = jiffies + jiffy_delta_val;
+ while (!kthread_should_stop()) {
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (speakup_info.flushing) {
+ speakup_info.flushing = 0;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ synth->flush(synth);
+ continue;
+ }
+ synth_buffer_skip_nonlatin1();
+ if (synth_buffer_empty()) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ break;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ full_time_val = full_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (synth_full()) {
+ schedule_timeout(msecs_to_jiffies(full_time_val));
+ continue;
+ }
+ set_current_state(TASK_RUNNING);
+ timeout = SPK_XMITR_TIMEOUT;
+ while (synth_writable()) {
+ if (!--timeout)
+ break;
+ udelay(1);
+ }
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ ch = synth_buffer_getc();
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (ch == '\n')
+ ch = PROCSPEECH;
+ outb_p(ch, speakup_info.port_tts);
+ if (time_after_eq(jiffies, jiff_max) && ch == SPACE) {
+ timeout = SPK_XMITR_TIMEOUT;
+ while (synth_writable()) {
+ if (!--timeout)
+ break;
+ udelay(1);
+ }
+ outb_p(PROCSPEECH, speakup_info.port_tts);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ delay_time_val = delay_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ schedule_timeout(msecs_to_jiffies(delay_time_val));
+ jiff_max = jiffies + jiffy_delta_val;
+ }
+ }
+ timeout = SPK_XMITR_TIMEOUT;
+ while (synth_writable()) {
+ if (!--timeout)
+ break;
+ udelay(1);
+ }
+ outb_p(PROCSPEECH, speakup_info.port_tts);
+}
+
+static void synth_flush(struct spk_synth *synth)
+{
+ outb_p(SYNTH_CLEAR, speakup_info.port_tts);
+}
+
+static int synth_probe(struct spk_synth *synth)
+{
+ unsigned int port_val = 0;
+ int i = 0;
+
+ pr_info("Probing for %s.\n", synth->long_name);
+ if (port_forced) {
+ speakup_info.port_tts = port_forced;
+ pr_info("probe forced to %x by kernel command line\n",
+ speakup_info.port_tts);
+ if (synth_request_region(speakup_info.port_tts - 1,
+ SYNTH_IO_EXTENT)) {
+ pr_warn("sorry, port already reserved\n");
+ return -EBUSY;
+ }
+ port_val = inw(speakup_info.port_tts - 1);
+ synth_port_control = speakup_info.port_tts - 1;
+ } else {
+ for (i = 0; synth_portlist[i]; i++) {
+ if (synth_request_region(synth_portlist[i],
+ SYNTH_IO_EXTENT)) {
+ pr_warn
+ ("request_region: failed with 0x%x, %d\n",
+ synth_portlist[i], SYNTH_IO_EXTENT);
+ continue;
+ }
+ port_val = inw(synth_portlist[i]) & 0xfffc;
+ if (port_val == 0x53fc) {
+ /* 'S' and out&input bits */
+ synth_port_control = synth_portlist[i];
+ speakup_info.port_tts = synth_port_control + 1;
+ break;
+ }
+ }
+ }
+ port_val &= 0xfffc;
+ if (port_val != 0x53fc) {
+ /* 'S' and out&input bits */
+ pr_info("%s: not found\n", synth->long_name);
+ synth_release_region(synth_port_control, SYNTH_IO_EXTENT);
+ synth_port_control = 0;
+ return -ENODEV;
+ }
+ pr_info("%s: %03x-%03x, driver version %s,\n", synth->long_name,
+ synth_port_control, synth_port_control + SYNTH_IO_EXTENT - 1,
+ synth->version);
+ synth->alive = 1;
+ return 0;
+}
+
+static void accent_release(void)
+{
+ spk_stop_serial_interrupt();
+ if (speakup_info.port_tts)
+ synth_release_region(speakup_info.port_tts - 1,
+ SYNTH_IO_EXTENT);
+ speakup_info.port_tts = 0;
+}
+
+module_param_hw_named(port, port_forced, int, ioport, 0444);
+module_param_named(start, synth_acntpc.startup, short, 0444);
+
+MODULE_PARM_DESC(port, "Set the port for the synthesizer (override probing).");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_acntpc);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for Accent PC synthesizer");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakup_acntsa.c b/drivers/accessibility/speakup/speakup_acntsa.c
new file mode 100644
index 000000000..3a863dc61
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_acntsa.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * originally written by: Kirk Reiser <kirk@braille.uwo.ca>
+ * this version considerably modified by David Borowski, david575@rogers.com
+ *
+ * Copyright (C) 1998-99 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ *
+ * this code is specificly written as a driver for the speakup screenreview
+ * package and is not a general device driver.
+ */
+
+#include "spk_priv.h"
+#include "speakup.h"
+#include "speakup_acnt.h" /* local header file for Accent values */
+
+#define DRV_VERSION "2.11"
+#define PROCSPEECH '\r'
+
+static int synth_probe(struct spk_synth *synth);
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"\033P8" } },
+ { CAPS_STOP, .u.s = {"\033P5" } },
+ { RATE, .u.n = {"\033R%c", 9, 0, 17, 0, 0, "0123456789abcdefgh" } },
+ { PITCH, .u.n = {"\033P%d", 5, 0, 9, 0, 0, NULL } },
+ { VOL, .u.n = {"\033A%d", 9, 0, 9, 0, 0, NULL } },
+ { TONE, .u.n = {"\033V%d", 5, 0, 9, 0, 0, NULL } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/*
+ * These attributes will appear in /sys/accessibility/speakup/acntsa.
+ */
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute tone_attribute =
+ __ATTR(tone, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &pitch_attribute.attr,
+ &rate_attribute.attr,
+ &tone_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_acntsa = {
+ .name = "acntsa",
+ .version = DRV_VERSION,
+ .long_name = "Accent-SA",
+ .init = "\033T2\033=M\033Oi\033N1\n",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 400,
+ .trigger = 50,
+ .jiffies = 30,
+ .full = 40000,
+ .dev_name = SYNTH_DEFAULT_DEV,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_ttyio_ops,
+ .probe = synth_probe,
+ .release = spk_ttyio_release,
+ .synth_immediate = spk_ttyio_synth_immediate,
+ .catch_up = spk_do_catch_up,
+ .flush = spk_synth_flush,
+ .is_alive = spk_synth_is_alive_restart,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = NULL,
+ .indexing = {
+ .command = NULL,
+ .lowindex = 0,
+ .highindex = 0,
+ .currindex = 0,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "acntsa",
+ },
+};
+
+static int synth_probe(struct spk_synth *synth)
+{
+ int failed;
+
+ failed = spk_ttyio_synth_probe(synth);
+ if (failed == 0) {
+ synth->synth_immediate(synth, "\033=R\r");
+ mdelay(100);
+ }
+ synth->alive = !failed;
+ return failed;
+}
+
+module_param_named(ser, synth_acntsa.ser, int, 0444);
+module_param_named(dev, synth_acntsa.dev_name, charp, 0444);
+module_param_named(start, synth_acntsa.startup, short, 0444);
+
+MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based).");
+MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer.");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_acntsa);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for Accent SA synthesizer");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakup_apollo.c b/drivers/accessibility/speakup/speakup_apollo.c
new file mode 100644
index 000000000..0877b4044
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_apollo.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * originally written by: Kirk Reiser <kirk@braille.uwo.ca>
+ * this version considerably modified by David Borowski, david575@rogers.com
+ *
+ * Copyright (C) 1998-99 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ *
+ * this code is specificly written as a driver for the speakup screenreview
+ * package and is not a general device driver.
+ */
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/kthread.h>
+#include <linux/serial_reg.h> /* for UART_MCR* constants */
+
+#include "spk_priv.h"
+#include "speakup.h"
+
+#define DRV_VERSION "2.21"
+#define SYNTH_CLEAR 0x18
+#define PROCSPEECH '\r'
+
+static void do_catch_up(struct spk_synth *synth);
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"cap, " } },
+ { CAPS_STOP, .u.s = {"" } },
+ { RATE, .u.n = {"@W%d", 6, 1, 9, 0, 0, NULL } },
+ { PITCH, .u.n = {"@F%x", 10, 0, 15, 0, 0, NULL } },
+ { VOL, .u.n = {"@A%x", 10, 0, 15, 0, 0, NULL } },
+ { VOICE, .u.n = {"@V%d", 1, 1, 6, 0, 0, NULL } },
+ { LANG, .u.n = {"@=%d,", 1, 1, 4, 0, 0, NULL } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/*
+ * These attributes will appear in /sys/accessibility/speakup/apollo.
+ */
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute lang_attribute =
+ __ATTR(lang, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute voice_attribute =
+ __ATTR(voice, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &lang_attribute.attr,
+ &pitch_attribute.attr,
+ &rate_attribute.attr,
+ &voice_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_apollo = {
+ .name = "apollo",
+ .version = DRV_VERSION,
+ .long_name = "Apollo",
+ .init = "@R3@D0@K1\r",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 500,
+ .trigger = 50,
+ .jiffies = 50,
+ .full = 40000,
+ .dev_name = SYNTH_DEFAULT_DEV,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_ttyio_ops,
+ .probe = spk_ttyio_synth_probe,
+ .release = spk_ttyio_release,
+ .synth_immediate = spk_ttyio_synth_immediate,
+ .catch_up = do_catch_up,
+ .flush = spk_synth_flush,
+ .is_alive = spk_synth_is_alive_restart,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = NULL,
+ .indexing = {
+ .command = NULL,
+ .lowindex = 0,
+ .highindex = 0,
+ .currindex = 0,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "apollo",
+ },
+};
+
+static void do_catch_up(struct spk_synth *synth)
+{
+ u_char ch;
+ unsigned long flags;
+ unsigned long jiff_max;
+ struct var_t *jiffy_delta;
+ struct var_t *delay_time;
+ struct var_t *full_time;
+ int full_time_val = 0;
+ int delay_time_val = 0;
+ int jiffy_delta_val = 0;
+
+ jiffy_delta = spk_get_var(JIFFY);
+ delay_time = spk_get_var(DELAY);
+ full_time = spk_get_var(FULL);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ jiff_max = jiffies + jiffy_delta_val;
+
+ while (!kthread_should_stop()) {
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ full_time_val = full_time->u.n.value;
+ delay_time_val = delay_time->u.n.value;
+ if (speakup_info.flushing) {
+ speakup_info.flushing = 0;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ synth->flush(synth);
+ continue;
+ }
+ synth_buffer_skip_nonlatin1();
+ if (synth_buffer_empty()) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ break;
+ }
+ ch = synth_buffer_peek();
+ set_current_state(TASK_INTERRUPTIBLE);
+ full_time_val = full_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (!synth->io_ops->synth_out(synth, ch)) {
+ synth->io_ops->tiocmset(0, UART_MCR_RTS);
+ synth->io_ops->tiocmset(UART_MCR_RTS, 0);
+ schedule_timeout(msecs_to_jiffies(full_time_val));
+ continue;
+ }
+ if (time_after_eq(jiffies, jiff_max) && (ch == SPACE)) {
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ full_time_val = full_time->u.n.value;
+ delay_time_val = delay_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (synth->io_ops->synth_out(synth, synth->procspeech))
+ schedule_timeout(msecs_to_jiffies
+ (delay_time_val));
+ else
+ schedule_timeout(msecs_to_jiffies
+ (full_time_val));
+ jiff_max = jiffies + jiffy_delta_val;
+ }
+ set_current_state(TASK_RUNNING);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ synth_buffer_getc();
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ }
+ synth->io_ops->synth_out(synth, PROCSPEECH);
+}
+
+module_param_named(ser, synth_apollo.ser, int, 0444);
+module_param_named(dev, synth_apollo.dev_name, charp, 0444);
+module_param_named(start, synth_apollo.startup, short, 0444);
+
+MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based).");
+MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer.");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_apollo);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for Apollo II synthesizer");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakup_audptr.c b/drivers/accessibility/speakup/speakup_audptr.c
new file mode 100644
index 000000000..e6a6a9665
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_audptr.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * originally written by: Kirk Reiser <kirk@braille.uwo.ca>
+ * this version considerably modified by David Borowski, david575@rogers.com
+ *
+ * Copyright (C) 1998-99 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ *
+ * specificly written as a driver for the speakup screenreview
+ * s not a general device driver.
+ */
+#include "spk_priv.h"
+#include "speakup.h"
+
+#define DRV_VERSION "2.11"
+#define SYNTH_CLEAR 0x18 /* flush synth buffer */
+#define PROCSPEECH '\r' /* start synth processing speech char */
+
+static int synth_probe(struct spk_synth *synth);
+static void synth_flush(struct spk_synth *synth);
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"\x05[f99]" } },
+ { CAPS_STOP, .u.s = {"\x05[f80]" } },
+ { RATE, .u.n = {"\x05[r%d]", 10, 0, 20, 100, -10, NULL } },
+ { PITCH, .u.n = {"\x05[f%d]", 80, 39, 4500, 0, 0, NULL } },
+ { VOL, .u.n = {"\x05[g%d]", 21, 0, 40, 0, 0, NULL } },
+ { TONE, .u.n = {"\x05[s%d]", 9, 0, 63, 0, 0, NULL } },
+ { PUNCT, .u.n = {"\x05[A%c]", 0, 0, 3, 0, 0, "nmsa" } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/*
+ * These attributes will appear in /sys/accessibility/speakup/audptr.
+ */
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute punct_attribute =
+ __ATTR(punct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute tone_attribute =
+ __ATTR(tone, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &pitch_attribute.attr,
+ &punct_attribute.attr,
+ &rate_attribute.attr,
+ &tone_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_audptr = {
+ .name = "audptr",
+ .version = DRV_VERSION,
+ .long_name = "Audapter",
+ .init = "\x05[D1]\x05[Ol]",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 400,
+ .trigger = 50,
+ .jiffies = 30,
+ .full = 18000,
+ .dev_name = SYNTH_DEFAULT_DEV,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_ttyio_ops,
+ .probe = synth_probe,
+ .release = spk_ttyio_release,
+ .synth_immediate = spk_ttyio_synth_immediate,
+ .catch_up = spk_do_catch_up,
+ .flush = synth_flush,
+ .is_alive = spk_synth_is_alive_restart,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = NULL,
+ .indexing = {
+ .command = NULL,
+ .lowindex = 0,
+ .highindex = 0,
+ .currindex = 0,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "audptr",
+ },
+};
+
+static void synth_flush(struct spk_synth *synth)
+{
+ synth->io_ops->flush_buffer();
+ synth->io_ops->send_xchar(SYNTH_CLEAR);
+ synth->io_ops->synth_out(synth, PROCSPEECH);
+}
+
+static void synth_version(struct spk_synth *synth)
+{
+ unsigned char test = 0;
+ char synth_id[40] = "";
+
+ synth->synth_immediate(synth, "\x05[Q]");
+ synth_id[test] = synth->io_ops->synth_in();
+ if (synth_id[test] == 'A') {
+ do {
+ /* read version string from synth */
+ synth_id[++test] = synth->io_ops->synth_in();
+ } while (synth_id[test] != '\n' && test < 32);
+ synth_id[++test] = 0x00;
+ }
+ if (synth_id[0] == 'A')
+ pr_info("%s version: %s", synth->long_name, synth_id);
+}
+
+static int synth_probe(struct spk_synth *synth)
+{
+ int failed;
+
+ failed = spk_ttyio_synth_probe(synth);
+ if (failed == 0)
+ synth_version(synth);
+ synth->alive = !failed;
+ return 0;
+}
+
+module_param_named(ser, synth_audptr.ser, int, 0444);
+module_param_named(dev, synth_audptr.dev_name, charp, 0444);
+module_param_named(start, synth_audptr.startup, short, 0444);
+
+MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based).");
+MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer.");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_audptr);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for Audapter synthesizer");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakup_bns.c b/drivers/accessibility/speakup/speakup_bns.c
new file mode 100644
index 000000000..76dfa3f7c
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_bns.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * originally written by: Kirk Reiser <kirk@braille.uwo.ca>
+ * this version considerably modified by David Borowski, david575@rogers.com
+ *
+ * Copyright (C) 1998-99 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ *
+ * this code is specificly written as a driver for the speakup screenreview
+ * package and is not a general device driver.
+ */
+#include "spk_priv.h"
+#include "speakup.h"
+
+#define DRV_VERSION "2.11"
+#define SYNTH_CLEAR 0x18
+#define PROCSPEECH '\r'
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"\x05\x31\x32P" } },
+ { CAPS_STOP, .u.s = {"\x05\x38P" } },
+ { RATE, .u.n = {"\x05%dE", 8, 1, 16, 0, 0, NULL } },
+ { PITCH, .u.n = {"\x05%dP", 8, 0, 16, 0, 0, NULL } },
+ { VOL, .u.n = {"\x05%dV", 8, 0, 16, 0, 0, NULL } },
+ { TONE, .u.n = {"\x05%dT", 8, 0, 16, 0, 0, NULL } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/*
+ * These attributes will appear in /sys/accessibility/speakup/bns.
+ */
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute tone_attribute =
+ __ATTR(tone, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &pitch_attribute.attr,
+ &rate_attribute.attr,
+ &tone_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_bns = {
+ .name = "bns",
+ .version = DRV_VERSION,
+ .long_name = "Braille 'N Speak",
+ .init = "\x05Z\x05\x43",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 500,
+ .trigger = 50,
+ .jiffies = 50,
+ .full = 40000,
+ .dev_name = SYNTH_DEFAULT_DEV,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_ttyio_ops,
+ .probe = spk_ttyio_synth_probe,
+ .release = spk_ttyio_release,
+ .synth_immediate = spk_ttyio_synth_immediate,
+ .catch_up = spk_do_catch_up,
+ .flush = spk_synth_flush,
+ .is_alive = spk_synth_is_alive_restart,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = NULL,
+ .indexing = {
+ .command = NULL,
+ .lowindex = 0,
+ .highindex = 0,
+ .currindex = 0,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "bns",
+ },
+};
+
+module_param_named(ser, synth_bns.ser, int, 0444);
+module_param_named(dev, synth_bns.dev_name, charp, 0444);
+module_param_named(start, synth_bns.startup, short, 0444);
+
+MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based).");
+MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer.");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_bns);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for Braille 'n Speak synthesizers");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakup_decext.c b/drivers/accessibility/speakup/speakup_decext.c
new file mode 100644
index 000000000..7408eb29c
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_decext.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * originally written by: Kirk Reiser <kirk@braille.uwo.ca>
+ * this version considerably modified by David Borowski, david575@rogers.com
+ *
+ * Copyright (C) 1998-99 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ *
+ * specificly written as a driver for the speakup screenreview
+ * s not a general device driver.
+ */
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/kthread.h>
+
+#include "spk_priv.h"
+#include "speakup.h"
+
+#define DRV_VERSION "2.14"
+#define SYNTH_CLEAR 0x03
+#define PROCSPEECH 0x0b
+
+static volatile unsigned char last_char;
+
+static void read_buff_add(u_char ch)
+{
+ last_char = ch;
+}
+
+static inline bool synth_full(void)
+{
+ return last_char == 0x13;
+}
+
+static void do_catch_up(struct spk_synth *synth);
+static void synth_flush(struct spk_synth *synth);
+
+static int in_escape;
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"[:dv ap 222]" } },
+ { CAPS_STOP, .u.s = {"[:dv ap 100]" } },
+ { RATE, .u.n = {"[:ra %d]", 7, 0, 9, 150, 25, NULL } },
+ { PITCH, .u.n = {"[:dv ap %d]", 100, 0, 100, 0, 0, NULL } },
+ { INFLECTION, .u.n = {"[:dv pr %d] ", 100, 0, 10000, 0, 0, NULL } },
+ { VOL, .u.n = {"[:dv gv %d]", 13, 0, 16, 0, 5, NULL } },
+ { PUNCT, .u.n = {"[:pu %c]", 0, 0, 2, 0, 0, "nsa" } },
+ { VOICE, .u.n = {"[:n%c]", 0, 0, 9, 0, 0, "phfdburwkv" } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/*
+ * These attributes will appear in /sys/accessibility/speakup/decext.
+ */
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute inflection_attribute =
+ __ATTR(inflection, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute punct_attribute =
+ __ATTR(punct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute voice_attribute =
+ __ATTR(voice, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &pitch_attribute.attr,
+ &inflection_attribute.attr,
+ &punct_attribute.attr,
+ &rate_attribute.attr,
+ &voice_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_decext = {
+ .name = "decext",
+ .version = DRV_VERSION,
+ .long_name = "Dectalk External",
+ .init = "[:pe -380]",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 500,
+ .trigger = 50,
+ .jiffies = 50,
+ .full = 40000,
+ .flags = SF_DEC,
+ .dev_name = SYNTH_DEFAULT_DEV,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_ttyio_ops,
+ .probe = spk_ttyio_synth_probe,
+ .release = spk_ttyio_release,
+ .synth_immediate = spk_ttyio_synth_immediate,
+ .catch_up = do_catch_up,
+ .flush = synth_flush,
+ .is_alive = spk_synth_is_alive_restart,
+ .synth_adjust = NULL,
+ .read_buff_add = read_buff_add,
+ .get_index = NULL,
+ .indexing = {
+ .command = NULL,
+ .lowindex = 0,
+ .highindex = 0,
+ .currindex = 0,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "decext",
+ },
+};
+
+static void do_catch_up(struct spk_synth *synth)
+{
+ u_char ch;
+ static u_char last = '\0';
+ unsigned long flags;
+ unsigned long jiff_max;
+ struct var_t *jiffy_delta;
+ struct var_t *delay_time;
+ int jiffy_delta_val = 0;
+ int delay_time_val = 0;
+
+ jiffy_delta = spk_get_var(JIFFY);
+ delay_time = spk_get_var(DELAY);
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ jiff_max = jiffies + jiffy_delta_val;
+
+ while (!kthread_should_stop()) {
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (speakup_info.flushing) {
+ speakup_info.flushing = 0;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ synth->flush(synth);
+ continue;
+ }
+ synth_buffer_skip_nonlatin1();
+ if (synth_buffer_empty()) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ break;
+ }
+ ch = synth_buffer_peek();
+ set_current_state(TASK_INTERRUPTIBLE);
+ delay_time_val = delay_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (ch == '\n')
+ ch = 0x0D;
+ if (synth_full() || !synth->io_ops->synth_out(synth, ch)) {
+ schedule_timeout(msecs_to_jiffies(delay_time_val));
+ continue;
+ }
+ set_current_state(TASK_RUNNING);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ synth_buffer_getc();
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (ch == '[') {
+ in_escape = 1;
+ } else if (ch == ']') {
+ in_escape = 0;
+ } else if (ch <= SPACE) {
+ if (!in_escape && strchr(",.!?;:", last))
+ synth->io_ops->synth_out(synth, PROCSPEECH);
+ if (time_after_eq(jiffies, jiff_max)) {
+ if (!in_escape)
+ synth->io_ops->synth_out(synth,
+ PROCSPEECH);
+ spin_lock_irqsave(&speakup_info.spinlock,
+ flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ delay_time_val = delay_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock,
+ flags);
+ schedule_timeout(msecs_to_jiffies
+ (delay_time_val));
+ jiff_max = jiffies + jiffy_delta_val;
+ }
+ }
+ last = ch;
+ }
+ if (!in_escape)
+ synth->io_ops->synth_out(synth, PROCSPEECH);
+}
+
+static void synth_flush(struct spk_synth *synth)
+{
+ in_escape = 0;
+ synth->io_ops->flush_buffer();
+ synth->synth_immediate(synth, "\033P;10z\033\\");
+}
+
+module_param_named(ser, synth_decext.ser, int, 0444);
+module_param_named(dev, synth_decext.dev_name, charp, 0444);
+module_param_named(start, synth_decext.startup, short, 0444);
+
+MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based).");
+MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer.");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_decext);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for DECtalk External synthesizers");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakup_decpc.c b/drivers/accessibility/speakup/speakup_decpc.c
new file mode 100644
index 000000000..96f24c848
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_decpc.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * This is the DECtalk PC speakup driver
+ *
+ * Some constants from DEC's DOS driver:
+ * Copyright (c) by Digital Equipment Corp.
+ *
+ * 386BSD DECtalk PC driver:
+ * Copyright (c) 1996 Brian Buhrow <buhrow@lothlorien.nfbcal.org>
+ *
+ * Linux DECtalk PC driver:
+ * Copyright (c) 1997 Nicolas Pitre <nico@cam.org>
+ *
+ * speakup DECtalk PC Internal driver:
+ * Copyright (c) 2003 David Borowski <david575@golden.net>
+ *
+ * All rights reserved.
+ */
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/kthread.h>
+
+#include "spk_priv.h"
+#include "speakup.h"
+
+#define MODULE_init 0x0dec /* module in boot code */
+#define MODULE_self_test 0x8800 /* module in self-test */
+#define MODULE_reset 0xffff /* reinit the whole module */
+
+#define MODE_mask 0xf000 /* mode bits in high nibble */
+#define MODE_null 0x0000
+#define MODE_test 0x2000 /* in testing mode */
+#define MODE_status 0x8000
+#define STAT_int 0x0001 /* running in interrupt mode */
+#define STAT_tr_char 0x0002 /* character data to transmit */
+#define STAT_rr_char 0x0004 /* ready to receive char data */
+#define STAT_cmd_ready 0x0008 /* ready to accept commands */
+#define STAT_dma_ready 0x0010 /* dma command ready */
+#define STAT_digitized 0x0020 /* spc in digitized mode */
+#define STAT_new_index 0x0040 /* new last index ready */
+#define STAT_new_status 0x0080 /* new status posted */
+#define STAT_dma_state 0x0100 /* dma state toggle */
+#define STAT_index_valid 0x0200 /* indexs are valid */
+#define STAT_flushing 0x0400 /* flush in progress */
+#define STAT_self_test 0x0800 /* module in self test */
+#define MODE_ready 0xc000 /* module ready for next phase */
+#define READY_boot 0x0000
+#define READY_kernel 0x0001
+#define MODE_error 0xf000
+
+#define CMD_mask 0xf000 /* mask for command nibble */
+#define CMD_null 0x0000 /* post status */
+#define CMD_control 0x1000 /* hard control command */
+#define CTRL_mask 0x0F00 /* mask off control nibble */
+#define CTRL_data 0x00FF /* mask to get data byte */
+#define CTRL_null 0x0000 /* null control */
+#define CTRL_vol_up 0x0100 /* increase volume */
+#define CTRL_vol_down 0x0200 /* decrease volume */
+#define CTRL_vol_set 0x0300 /* set volume */
+#define CTRL_pause 0x0400 /* pause spc */
+#define CTRL_resume 0x0500 /* resume spc clock */
+#define CTRL_resume_spc 0x0001 /* resume spc soft pause */
+#define CTRL_flush 0x0600 /* flush all buffers */
+#define CTRL_int_enable 0x0700 /* enable status change ints */
+#define CTRL_buff_free 0x0800 /* buffer remain count */
+#define CTRL_buff_used 0x0900 /* buffer in use */
+#define CTRL_speech 0x0a00 /* immediate speech change */
+#define CTRL_SP_voice 0x0001 /* voice change */
+#define CTRL_SP_rate 0x0002 /* rate change */
+#define CTRL_SP_comma 0x0003 /* comma pause change */
+#define CTRL_SP_period 0x0004 /* period pause change */
+#define CTRL_SP_rate_delta 0x0005 /* delta rate change */
+#define CTRL_SP_get_param 0x0006 /* return the desired parameter */
+#define CTRL_last_index 0x0b00 /* get last index spoken */
+#define CTRL_io_priority 0x0c00 /* change i/o priority */
+#define CTRL_free_mem 0x0d00 /* get free paragraphs on module */
+#define CTRL_get_lang 0x0e00 /* return bitmask of loaded languages */
+#define CMD_test 0x2000 /* self-test request */
+#define TEST_mask 0x0F00 /* isolate test field */
+#define TEST_null 0x0000 /* no test requested */
+#define TEST_isa_int 0x0100 /* assert isa irq */
+#define TEST_echo 0x0200 /* make data in == data out */
+#define TEST_seg 0x0300 /* set peek/poke segment */
+#define TEST_off 0x0400 /* set peek/poke offset */
+#define TEST_peek 0x0500 /* data out == *peek */
+#define TEST_poke 0x0600 /* *peek == data in */
+#define TEST_sub_code 0x00FF /* user defined test sub codes */
+#define CMD_id 0x3000 /* return software id */
+#define ID_null 0x0000 /* null id */
+#define ID_kernel 0x0100 /* kernel code executing */
+#define ID_boot 0x0200 /* boot code executing */
+#define CMD_dma 0x4000 /* force a dma start */
+#define CMD_reset 0x5000 /* reset module status */
+#define CMD_sync 0x6000 /* kernel sync command */
+#define CMD_char_in 0x7000 /* single character send */
+#define CMD_char_out 0x8000 /* single character get */
+#define CHAR_count_1 0x0100 /* one char in cmd_low */
+#define CHAR_count_2 0x0200 /* the second in data_low */
+#define CHAR_count_3 0x0300 /* the third in data_high */
+#define CMD_spc_mode 0x9000 /* change spc mode */
+#define CMD_spc_to_text 0x0100 /* set to text mode */
+#define CMD_spc_to_digit 0x0200 /* set to digital mode */
+#define CMD_spc_rate 0x0400 /* change spc data rate */
+#define CMD_error 0xf000 /* severe error */
+
+enum { PRIMARY_DIC = 0, USER_DIC, COMMAND_DIC, ABBREV_DIC };
+
+#define DMA_single_in 0x01
+#define DMA_single_out 0x02
+#define DMA_buff_in 0x03
+#define DMA_buff_out 0x04
+#define DMA_control 0x05
+#define DT_MEM_ALLOC 0x03
+#define DT_SET_DIC 0x04
+#define DT_START_TASK 0x05
+#define DT_LOAD_MEM 0x06
+#define DT_READ_MEM 0x07
+#define DT_DIGITAL_IN 0x08
+#define DMA_sync 0x06
+#define DMA_sync_char 0x07
+
+#define DRV_VERSION "2.12"
+#define PROCSPEECH 0x0b
+#define SYNTH_IO_EXTENT 8
+
+static int synth_probe(struct spk_synth *synth);
+static void dtpc_release(void);
+static const char *synth_immediate(struct spk_synth *synth, const char *buf);
+static void do_catch_up(struct spk_synth *synth);
+static void synth_flush(struct spk_synth *synth);
+
+static int synth_portlist[] = { 0x340, 0x350, 0x240, 0x250, 0 };
+static int in_escape, is_flushing;
+static int dt_stat, dma_state;
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"[:dv ap 200]" } },
+ { CAPS_STOP, .u.s = {"[:dv ap 100]" } },
+ { RATE, .u.n = {"[:ra %d]", 9, 0, 18, 150, 25, NULL } },
+ { PITCH, .u.n = {"[:dv ap %d]", 80, 0, 100, 20, 0, NULL } },
+ { INFLECTION, .u.n = {"[:dv pr %d] ", 100, 0, 10000, 0, 0, NULL } },
+ { VOL, .u.n = {"[:vo se %d]", 5, 0, 9, 5, 10, NULL } },
+ { PUNCT, .u.n = {"[:pu %c]", 0, 0, 2, 0, 0, "nsa" } },
+ { VOICE, .u.n = {"[:n%c]", 0, 0, 9, 0, 0, "phfdburwkv" } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/*
+ * These attributes will appear in /sys/accessibility/speakup/decpc.
+ */
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute inflection_attribute =
+ __ATTR(inflection, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute punct_attribute =
+ __ATTR(punct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute voice_attribute =
+ __ATTR(voice, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &pitch_attribute.attr,
+ &inflection_attribute.attr,
+ &punct_attribute.attr,
+ &rate_attribute.attr,
+ &voice_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_dec_pc = {
+ .name = "decpc",
+ .version = DRV_VERSION,
+ .long_name = "Dectalk PC",
+ .init = "[:pe -380]",
+ .procspeech = PROCSPEECH,
+ .delay = 500,
+ .trigger = 50,
+ .jiffies = 50,
+ .full = 1000,
+ .flags = SF_DEC,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_serial_io_ops,
+ .probe = synth_probe,
+ .release = dtpc_release,
+ .synth_immediate = synth_immediate,
+ .catch_up = do_catch_up,
+ .flush = synth_flush,
+ .is_alive = spk_synth_is_alive_nop,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = NULL,
+ .indexing = {
+ .command = NULL,
+ .lowindex = 0,
+ .highindex = 0,
+ .currindex = 0,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "decpc",
+ },
+};
+
+static int dt_getstatus(void)
+{
+ dt_stat = inb_p(speakup_info.port_tts) |
+ (inb_p(speakup_info.port_tts + 1) << 8);
+ return dt_stat;
+}
+
+static void dt_sendcmd(u_int cmd)
+{
+ outb_p(cmd & 0xFF, speakup_info.port_tts);
+ outb_p((cmd >> 8) & 0xFF, speakup_info.port_tts + 1);
+}
+
+static int dt_waitbit(int bit)
+{
+ int timeout = 100;
+
+ while (--timeout > 0) {
+ if ((dt_getstatus() & bit) == bit)
+ return 1;
+ udelay(50);
+ }
+ return 0;
+}
+
+static int dt_wait_dma(void)
+{
+ int timeout = 100, state = dma_state;
+
+ if (!dt_waitbit(STAT_dma_ready))
+ return 0;
+ while (--timeout > 0) {
+ if ((dt_getstatus() & STAT_dma_state) == state)
+ return 1;
+ udelay(50);
+ }
+ dma_state = dt_getstatus() & STAT_dma_state;
+ return 1;
+}
+
+static int dt_ctrl(u_int cmd)
+{
+ int timeout = 10;
+
+ if (!dt_waitbit(STAT_cmd_ready))
+ return -1;
+ outb_p(0, speakup_info.port_tts + 2);
+ outb_p(0, speakup_info.port_tts + 3);
+ dt_getstatus();
+ dt_sendcmd(CMD_control | cmd);
+ outb_p(0, speakup_info.port_tts + 6);
+ while (dt_getstatus() & STAT_cmd_ready) {
+ udelay(20);
+ if (--timeout == 0)
+ break;
+ }
+ dt_sendcmd(CMD_null);
+ return 0;
+}
+
+static void synth_flush(struct spk_synth *synth)
+{
+ int timeout = 10;
+
+ if (is_flushing)
+ return;
+ is_flushing = 4;
+ in_escape = 0;
+ while (dt_ctrl(CTRL_flush)) {
+ if (--timeout == 0)
+ break;
+ udelay(50);
+ }
+ for (timeout = 0; timeout < 10; timeout++) {
+ if (dt_waitbit(STAT_dma_ready))
+ break;
+ udelay(50);
+ }
+ outb_p(DMA_sync, speakup_info.port_tts + 4);
+ outb_p(0, speakup_info.port_tts + 4);
+ udelay(100);
+ for (timeout = 0; timeout < 10; timeout++) {
+ if (!(dt_getstatus() & STAT_flushing))
+ break;
+ udelay(50);
+ }
+ dma_state = dt_getstatus() & STAT_dma_state;
+ dma_state ^= STAT_dma_state;
+ is_flushing = 0;
+}
+
+static int dt_sendchar(char ch)
+{
+ if (!dt_wait_dma())
+ return -1;
+ if (!(dt_stat & STAT_rr_char))
+ return -2;
+ outb_p(DMA_single_in, speakup_info.port_tts + 4);
+ outb_p(ch, speakup_info.port_tts + 4);
+ dma_state ^= STAT_dma_state;
+ return 0;
+}
+
+static int testkernel(void)
+{
+ int status = 0;
+
+ if (dt_getstatus() == 0xffff) {
+ status = -1;
+ goto oops;
+ }
+ dt_sendcmd(CMD_sync);
+ if (!dt_waitbit(STAT_cmd_ready))
+ status = -2;
+ else if (dt_stat & 0x8000)
+ return 0;
+ else if (dt_stat == 0x0dec)
+ pr_warn("dec_pc at 0x%x, software not loaded\n",
+ speakup_info.port_tts);
+ status = -3;
+oops: synth_release_region(speakup_info.port_tts, SYNTH_IO_EXTENT);
+ speakup_info.port_tts = 0;
+ return status;
+}
+
+static void do_catch_up(struct spk_synth *synth)
+{
+ u_char ch;
+ static u_char last;
+ unsigned long flags;
+ unsigned long jiff_max;
+ struct var_t *jiffy_delta;
+ struct var_t *delay_time;
+ int jiffy_delta_val;
+ int delay_time_val;
+
+ jiffy_delta = spk_get_var(JIFFY);
+ delay_time = spk_get_var(DELAY);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ jiff_max = jiffies + jiffy_delta_val;
+
+ while (!kthread_should_stop()) {
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (speakup_info.flushing) {
+ speakup_info.flushing = 0;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ synth->flush(synth);
+ continue;
+ }
+ synth_buffer_skip_nonlatin1();
+ if (synth_buffer_empty()) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ break;
+ }
+ ch = synth_buffer_peek();
+ set_current_state(TASK_INTERRUPTIBLE);
+ delay_time_val = delay_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (ch == '\n')
+ ch = 0x0D;
+ if (dt_sendchar(ch)) {
+ schedule_timeout(msecs_to_jiffies(delay_time_val));
+ continue;
+ }
+ set_current_state(TASK_RUNNING);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ synth_buffer_getc();
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (ch == '[') {
+ in_escape = 1;
+ } else if (ch == ']') {
+ in_escape = 0;
+ } else if (ch <= SPACE) {
+ if (!in_escape && strchr(",.!?;:", last))
+ dt_sendchar(PROCSPEECH);
+ if (time_after_eq(jiffies, jiff_max)) {
+ if (!in_escape)
+ dt_sendchar(PROCSPEECH);
+ spin_lock_irqsave(&speakup_info.spinlock,
+ flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ delay_time_val = delay_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock,
+ flags);
+ schedule_timeout(msecs_to_jiffies
+ (delay_time_val));
+ jiff_max = jiffies + jiffy_delta_val;
+ }
+ }
+ last = ch;
+ ch = 0;
+ }
+ if (!in_escape)
+ dt_sendchar(PROCSPEECH);
+}
+
+static const char *synth_immediate(struct spk_synth *synth, const char *buf)
+{
+ u_char ch;
+
+ while ((ch = *buf)) {
+ if (ch == '\n')
+ ch = PROCSPEECH;
+ if (dt_sendchar(ch))
+ return buf;
+ buf++;
+ }
+ return NULL;
+}
+
+static int synth_probe(struct spk_synth *synth)
+{
+ int i = 0, failed = 0;
+
+ pr_info("Probing for %s.\n", synth->long_name);
+ for (i = 0; synth_portlist[i]; i++) {
+ if (synth_request_region(synth_portlist[i], SYNTH_IO_EXTENT)) {
+ pr_warn("request_region: failed with 0x%x, %d\n",
+ synth_portlist[i], SYNTH_IO_EXTENT);
+ continue;
+ }
+ speakup_info.port_tts = synth_portlist[i];
+ failed = testkernel();
+ if (failed == 0)
+ break;
+ }
+ if (failed) {
+ pr_info("%s: not found\n", synth->long_name);
+ return -ENODEV;
+ }
+ pr_info("%s: %03x-%03x, Driver Version %s,\n", synth->long_name,
+ speakup_info.port_tts, speakup_info.port_tts + 7,
+ synth->version);
+ synth->alive = 1;
+ return 0;
+}
+
+static void dtpc_release(void)
+{
+ spk_stop_serial_interrupt();
+ if (speakup_info.port_tts)
+ synth_release_region(speakup_info.port_tts, SYNTH_IO_EXTENT);
+ speakup_info.port_tts = 0;
+}
+
+module_param_named(start, synth_dec_pc.startup, short, 0444);
+
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_dec_pc);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for DECtalk PC synthesizers");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
diff --git a/drivers/accessibility/speakup/speakup_dectlk.c b/drivers/accessibility/speakup/speakup_dectlk.c
new file mode 100644
index 000000000..d689ec5e2
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_dectlk.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * originally written by: Kirk Reiser <kirk@braille.uwo.ca>
+ * this version considerably modified by David Borowski, david575@rogers.com
+ *
+ * Copyright (C) 1998-99 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ *
+ * specificly written as a driver for the speakup screenreview
+ * s not a general device driver.
+ */
+#include <linux/unistd.h>
+#include <linux/proc_fs.h>
+#include <linux/jiffies.h>
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/kthread.h>
+#include "speakup.h"
+#include "spk_priv.h"
+
+#define DRV_VERSION "2.20"
+#define SYNTH_CLEAR 0x03
+#define PROCSPEECH 0x0b
+static int xoff;
+
+static inline int synth_full(void)
+{
+ return xoff;
+}
+
+static void do_catch_up(struct spk_synth *synth);
+static void synth_flush(struct spk_synth *synth);
+static void read_buff_add(u_char c);
+static unsigned char get_index(struct spk_synth *synth);
+
+static int in_escape;
+static int is_flushing;
+
+static DEFINE_SPINLOCK(flush_lock);
+static DECLARE_WAIT_QUEUE_HEAD(flush);
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"[:dv ap 160] " } },
+ { CAPS_STOP, .u.s = {"[:dv ap 100 ] " } },
+ { RATE, .u.n = {"[:ra %d] ", 180, 75, 650, 0, 0, NULL } },
+ { PITCH, .u.n = {"[:dv ap %d] ", 122, 50, 350, 0, 0, NULL } },
+ { INFLECTION, .u.n = {"[:dv pr %d] ", 100, 0, 10000, 0, 0, NULL } },
+ { VOL, .u.n = {"[:dv g5 %d] ", 86, 60, 86, 0, 0, NULL } },
+ { PUNCT, .u.n = {"[:pu %c] ", 0, 0, 2, 0, 0, "nsa" } },
+ { VOICE, .u.n = {"[:n%c] ", 0, 0, 9, 0, 0, "phfdburwkv" } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/*
+ * These attributes will appear in /sys/accessibility/speakup/dectlk.
+ */
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute inflection_attribute =
+ __ATTR(inflection, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute punct_attribute =
+ __ATTR(punct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute voice_attribute =
+ __ATTR(voice, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &pitch_attribute.attr,
+ &inflection_attribute.attr,
+ &punct_attribute.attr,
+ &rate_attribute.attr,
+ &voice_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static int ap_defaults[] = {122, 89, 155, 110, 208, 240, 200, 106, 306};
+static int g5_defaults[] = {86, 81, 86, 84, 81, 80, 83, 83, 73};
+
+static struct spk_synth synth_dectlk = {
+ .name = "dectlk",
+ .version = DRV_VERSION,
+ .long_name = "Dectalk Express",
+ .init = "[:error sp :name paul :rate 180 :tsr off] ",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 500,
+ .trigger = 50,
+ .jiffies = 50,
+ .full = 40000,
+ .dev_name = SYNTH_DEFAULT_DEV,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .default_pitch = ap_defaults,
+ .default_vol = g5_defaults,
+ .io_ops = &spk_ttyio_ops,
+ .probe = spk_ttyio_synth_probe,
+ .release = spk_ttyio_release,
+ .synth_immediate = spk_ttyio_synth_immediate,
+ .catch_up = do_catch_up,
+ .flush = synth_flush,
+ .is_alive = spk_synth_is_alive_restart,
+ .synth_adjust = NULL,
+ .read_buff_add = read_buff_add,
+ .get_index = get_index,
+ .indexing = {
+ .command = "[:in re %d ] ",
+ .lowindex = 1,
+ .highindex = 8,
+ .currindex = 1,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "dectlk",
+ },
+};
+
+static int is_indnum(u_char *ch)
+{
+ if ((*ch >= '0') && (*ch <= '9')) {
+ *ch = *ch - '0';
+ return 1;
+ }
+ return 0;
+}
+
+static u_char lastind;
+
+static unsigned char get_index(struct spk_synth *synth)
+{
+ u_char rv;
+
+ rv = lastind;
+ lastind = 0;
+ return rv;
+}
+
+static void read_buff_add(u_char c)
+{
+ static int ind = -1;
+
+ if (c == 0x01) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&flush_lock, flags);
+ is_flushing = 0;
+ wake_up_interruptible(&flush);
+ spin_unlock_irqrestore(&flush_lock, flags);
+ } else if (c == 0x13) {
+ xoff = 1;
+ } else if (c == 0x11) {
+ xoff = 0;
+ } else if (is_indnum(&c)) {
+ if (ind == -1)
+ ind = c;
+ else
+ ind = ind * 10 + c;
+ } else if ((c > 31) && (c < 127)) {
+ if (ind != -1)
+ lastind = (u_char)ind;
+ ind = -1;
+ }
+}
+
+static void do_catch_up(struct spk_synth *synth)
+{
+ int synth_full_val = 0;
+ static u_char ch;
+ static u_char last = '\0';
+ unsigned long flags;
+ unsigned long jiff_max;
+ unsigned long timeout = msecs_to_jiffies(4000);
+ DEFINE_WAIT(wait);
+ struct var_t *jiffy_delta;
+ struct var_t *delay_time;
+ int jiffy_delta_val;
+ int delay_time_val;
+
+ jiffy_delta = spk_get_var(JIFFY);
+ delay_time = spk_get_var(DELAY);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ jiff_max = jiffies + jiffy_delta_val;
+
+ while (!kthread_should_stop()) {
+ /* if no ctl-a in 4, send data anyway */
+ spin_lock_irqsave(&flush_lock, flags);
+ while (is_flushing && timeout) {
+ prepare_to_wait(&flush, &wait, TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(&flush_lock, flags);
+ timeout = schedule_timeout(timeout);
+ spin_lock_irqsave(&flush_lock, flags);
+ }
+ finish_wait(&flush, &wait);
+ is_flushing = 0;
+ spin_unlock_irqrestore(&flush_lock, flags);
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (speakup_info.flushing) {
+ speakup_info.flushing = 0;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ synth->flush(synth);
+ continue;
+ }
+ synth_buffer_skip_nonlatin1();
+ if (synth_buffer_empty()) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ break;
+ }
+ ch = synth_buffer_peek();
+ set_current_state(TASK_INTERRUPTIBLE);
+ delay_time_val = delay_time->u.n.value;
+ synth_full_val = synth_full();
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (ch == '\n')
+ ch = 0x0D;
+ if (synth_full_val || !synth->io_ops->synth_out(synth, ch)) {
+ schedule_timeout(msecs_to_jiffies(delay_time_val));
+ continue;
+ }
+ set_current_state(TASK_RUNNING);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ synth_buffer_getc();
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (ch == '[') {
+ in_escape = 1;
+ } else if (ch == ']') {
+ in_escape = 0;
+ } else if (ch <= SPACE) {
+ if (!in_escape && strchr(",.!?;:", last))
+ synth->io_ops->synth_out(synth, PROCSPEECH);
+ if (time_after_eq(jiffies, jiff_max)) {
+ if (!in_escape)
+ synth->io_ops->synth_out(synth,
+ PROCSPEECH);
+ spin_lock_irqsave(&speakup_info.spinlock,
+ flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ delay_time_val = delay_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock,
+ flags);
+ schedule_timeout(msecs_to_jiffies
+ (delay_time_val));
+ jiff_max = jiffies + jiffy_delta_val;
+ }
+ }
+ last = ch;
+ }
+ if (!in_escape)
+ synth->io_ops->synth_out(synth, PROCSPEECH);
+}
+
+static void synth_flush(struct spk_synth *synth)
+{
+ if (in_escape)
+ /* if in command output ']' so we don't get an error */
+ synth->io_ops->synth_out(synth, ']');
+ in_escape = 0;
+ is_flushing = 1;
+ synth->io_ops->flush_buffer();
+ synth->io_ops->synth_out(synth, SYNTH_CLEAR);
+}
+
+module_param_named(ser, synth_dectlk.ser, int, 0444);
+module_param_named(dev, synth_dectlk.dev_name, charp, 0444);
+module_param_named(start, synth_dectlk.startup, short, 0444);
+
+MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based).");
+MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer.");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_dectlk);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for DECtalk Express synthesizers");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakup_dtlk.c b/drivers/accessibility/speakup/speakup_dtlk.c
new file mode 100644
index 000000000..dbebed0ee
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_dtlk.c
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * originally written by: Kirk Reiser <kirk@braille.uwo.ca>
+ * this version considerably modified by David Borowski, david575@rogers.com
+ *
+ * Copyright (C) 1998-99 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ *
+ * specificly written as a driver for the speakup screenreview
+ * package it's not a general device driver.
+ * This driver is for the RC Systems DoubleTalk PC internal synthesizer.
+ */
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/kthread.h>
+
+#include "spk_priv.h"
+#include "serialio.h"
+#include "speakup_dtlk.h" /* local header file for DoubleTalk values */
+#include "speakup.h"
+
+#define DRV_VERSION "2.10"
+#define PROCSPEECH 0x00
+
+static int synth_probe(struct spk_synth *synth);
+static void dtlk_release(void);
+static const char *synth_immediate(struct spk_synth *synth, const char *buf);
+static void do_catch_up(struct spk_synth *synth);
+static void synth_flush(struct spk_synth *synth);
+
+static int synth_lpc;
+static int port_forced;
+static unsigned int synth_portlist[] = {
+ 0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0
+};
+
+static u_char synth_status;
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"\x01+35p" } },
+ { CAPS_STOP, .u.s = {"\x01-35p" } },
+ { RATE, .u.n = {"\x01%ds", 8, 0, 9, 0, 0, NULL } },
+ { PITCH, .u.n = {"\x01%dp", 50, 0, 99, 0, 0, NULL } },
+ { VOL, .u.n = {"\x01%dv", 5, 0, 9, 0, 0, NULL } },
+ { TONE, .u.n = {"\x01%dx", 1, 0, 2, 0, 0, NULL } },
+ { PUNCT, .u.n = {"\x01%db", 7, 0, 15, 0, 0, NULL } },
+ { VOICE, .u.n = {"\x01%do", 0, 0, 7, 0, 0, NULL } },
+ { FREQUENCY, .u.n = {"\x01%df", 5, 0, 9, 0, 0, NULL } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/*
+ * These attributes will appear in /sys/accessibility/speakup/dtlk.
+ */
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute freq_attribute =
+ __ATTR(freq, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute punct_attribute =
+ __ATTR(punct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute tone_attribute =
+ __ATTR(tone, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute voice_attribute =
+ __ATTR(voice, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &freq_attribute.attr,
+ &pitch_attribute.attr,
+ &punct_attribute.attr,
+ &rate_attribute.attr,
+ &tone_attribute.attr,
+ &voice_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_dtlk = {
+ .name = "dtlk",
+ .version = DRV_VERSION,
+ .long_name = "DoubleTalk PC",
+ .init = "\x01@\x01\x31y",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 500,
+ .trigger = 30,
+ .jiffies = 50,
+ .full = 1000,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_serial_io_ops,
+ .probe = synth_probe,
+ .release = dtlk_release,
+ .synth_immediate = synth_immediate,
+ .catch_up = do_catch_up,
+ .flush = synth_flush,
+ .is_alive = spk_synth_is_alive_nop,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = spk_synth_get_index,
+ .indexing = {
+ .command = "\x01%di",
+ .lowindex = 1,
+ .highindex = 5,
+ .currindex = 1,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "dtlk",
+ },
+};
+
+static inline bool synth_readable(void)
+{
+ synth_status = inb_p(speakup_info.port_tts + UART_RX);
+ return (synth_status & TTS_READABLE) != 0;
+}
+
+static inline bool synth_writable(void)
+{
+ synth_status = inb_p(speakup_info.port_tts + UART_RX);
+ return (synth_status & TTS_WRITABLE) != 0;
+}
+
+static inline bool synth_full(void)
+{
+ synth_status = inb_p(speakup_info.port_tts + UART_RX);
+ return (synth_status & TTS_ALMOST_FULL) != 0;
+}
+
+static void spk_out(const char ch)
+{
+ int timeout = SPK_XMITR_TIMEOUT;
+
+ while (!synth_writable()) {
+ if (!--timeout)
+ break;
+ udelay(1);
+ }
+ outb_p(ch, speakup_info.port_tts);
+ timeout = SPK_XMITR_TIMEOUT;
+ while (synth_writable()) {
+ if (!--timeout)
+ break;
+ udelay(1);
+ }
+}
+
+static void do_catch_up(struct spk_synth *synth)
+{
+ u_char ch;
+ unsigned long flags;
+ unsigned long jiff_max;
+ struct var_t *jiffy_delta;
+ struct var_t *delay_time;
+ int jiffy_delta_val;
+ int delay_time_val;
+
+ jiffy_delta = spk_get_var(JIFFY);
+ delay_time = spk_get_var(DELAY);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ jiff_max = jiffies + jiffy_delta_val;
+ while (!kthread_should_stop()) {
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (speakup_info.flushing) {
+ speakup_info.flushing = 0;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ synth->flush(synth);
+ continue;
+ }
+ synth_buffer_skip_nonlatin1();
+ if (synth_buffer_empty()) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ break;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ delay_time_val = delay_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (synth_full()) {
+ schedule_timeout(msecs_to_jiffies(delay_time_val));
+ continue;
+ }
+ set_current_state(TASK_RUNNING);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ ch = synth_buffer_getc();
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (ch == '\n')
+ ch = PROCSPEECH;
+ spk_out(ch);
+ if (time_after_eq(jiffies, jiff_max) && (ch == SPACE)) {
+ spk_out(PROCSPEECH);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ delay_time_val = delay_time->u.n.value;
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ schedule_timeout(msecs_to_jiffies(delay_time_val));
+ jiff_max = jiffies + jiffy_delta_val;
+ }
+ }
+ spk_out(PROCSPEECH);
+}
+
+static const char *synth_immediate(struct spk_synth *synth, const char *buf)
+{
+ u_char ch;
+
+ while ((ch = (u_char)*buf)) {
+ if (synth_full())
+ return buf;
+ if (ch == '\n')
+ ch = PROCSPEECH;
+ spk_out(ch);
+ buf++;
+ }
+ return NULL;
+}
+
+static void synth_flush(struct spk_synth *synth)
+{
+ outb_p(SYNTH_CLEAR, speakup_info.port_tts);
+ while (synth_writable())
+ cpu_relax();
+}
+
+static char synth_read_tts(void)
+{
+ u_char ch;
+
+ while (!synth_readable())
+ cpu_relax();
+ ch = synth_status & 0x7f;
+ outb_p(ch, speakup_info.port_tts);
+ while (synth_readable())
+ cpu_relax();
+ return (char)ch;
+}
+
+/* interrogate the DoubleTalk PC and return its settings */
+static struct synth_settings *synth_interrogate(struct spk_synth *synth)
+{
+ u_char *t;
+ static char buf[sizeof(struct synth_settings) + 1];
+ int total, i;
+ static struct synth_settings status;
+
+ synth_immediate(synth, "\x18\x01?");
+ for (total = 0, i = 0; i < 50; i++) {
+ buf[total] = synth_read_tts();
+ if (total > 2 && buf[total] == 0x7f)
+ break;
+ if (total < sizeof(struct synth_settings))
+ total++;
+ }
+ t = buf;
+ /* serial number is little endian */
+ status.serial_number = t[0] + t[1] * 256;
+ t += 2;
+ for (i = 0; *t != '\r'; t++) {
+ status.rom_version[i] = *t;
+ if (i < sizeof(status.rom_version) - 1)
+ i++;
+ }
+ status.rom_version[i] = 0;
+ t++;
+ status.mode = *t++;
+ status.punc_level = *t++;
+ status.formant_freq = *t++;
+ status.pitch = *t++;
+ status.speed = *t++;
+ status.volume = *t++;
+ status.tone = *t++;
+ status.expression = *t++;
+ status.ext_dict_loaded = *t++;
+ status.ext_dict_status = *t++;
+ status.free_ram = *t++;
+ status.articulation = *t++;
+ status.reverb = *t++;
+ status.eob = *t++;
+ return &status;
+}
+
+static int synth_probe(struct spk_synth *synth)
+{
+ unsigned int port_val = 0;
+ int i = 0;
+ struct synth_settings *sp;
+
+ pr_info("Probing for DoubleTalk.\n");
+ if (port_forced) {
+ speakup_info.port_tts = port_forced;
+ pr_info("probe forced to %x by kernel command line\n",
+ speakup_info.port_tts);
+ if ((port_forced & 0xf) != 0xf)
+ pr_info("warning: port base should probably end with f\n");
+ if (synth_request_region(speakup_info.port_tts - 1,
+ SYNTH_IO_EXTENT)) {
+ pr_warn("sorry, port already reserved\n");
+ return -EBUSY;
+ }
+ port_val = inw(speakup_info.port_tts - 1);
+ synth_lpc = speakup_info.port_tts - 1;
+ } else {
+ for (i = 0; synth_portlist[i]; i++) {
+ if (synth_request_region(synth_portlist[i],
+ SYNTH_IO_EXTENT))
+ continue;
+ port_val = inw(synth_portlist[i]) & 0xfbff;
+ if (port_val == 0x107f) {
+ synth_lpc = synth_portlist[i];
+ speakup_info.port_tts = synth_lpc + 1;
+ break;
+ }
+ synth_release_region(synth_portlist[i],
+ SYNTH_IO_EXTENT);
+ }
+ }
+ port_val &= 0xfbff;
+ if (port_val != 0x107f) {
+ pr_info("DoubleTalk PC: not found\n");
+ if (synth_lpc)
+ synth_release_region(synth_lpc, SYNTH_IO_EXTENT);
+ return -ENODEV;
+ }
+ while (inw_p(synth_lpc) != 0x147f)
+ cpu_relax(); /* wait until it's ready */
+ sp = synth_interrogate(synth);
+ pr_info("%s: %03x-%03x, ROM ver %s, s/n %u, driver: %s\n",
+ synth->long_name, synth_lpc, synth_lpc + SYNTH_IO_EXTENT - 1,
+ sp->rom_version, sp->serial_number, synth->version);
+ synth->alive = 1;
+ return 0;
+}
+
+static void dtlk_release(void)
+{
+ spk_stop_serial_interrupt();
+ if (speakup_info.port_tts)
+ synth_release_region(speakup_info.port_tts - 1,
+ SYNTH_IO_EXTENT);
+ speakup_info.port_tts = 0;
+}
+
+module_param_hw_named(port, port_forced, int, ioport, 0444);
+module_param_named(start, synth_dtlk.startup, short, 0444);
+
+MODULE_PARM_DESC(port, "Set the port for the synthesizer (override probing).");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_dtlk);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for DoubleTalk PC synthesizers");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakup_dtlk.h b/drivers/accessibility/speakup/speakup_dtlk.h
new file mode 100644
index 000000000..9c378b580
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_dtlk.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* speakup_dtlk.h - header file for speakups DoubleTalk driver. */
+
+#define SYNTH_IO_EXTENT 0x02
+#define SYNTH_CLEAR 0x18 /* stops speech */
+ /* TTS Port Status Flags */
+#define TTS_READABLE 0x80 /* mask for bit which is nonzero if a
+ * byte can be read from the TTS port
+ */
+#define TTS_SPEAKING 0x40 /* mask for SYNC bit, which is nonzero
+ * while DoubleTalk is producing
+ * output with TTS, PCM or CVSD
+ * synthesizers or tone generators
+ * (that is, all but LPC)
+ */
+#define TTS_SPEAKING2 0x20 /* mask for SYNC2 bit,
+ * which falls to zero up to 0.4 sec
+ * before speech stops
+ */
+#define TTS_WRITABLE 0x10 /* mask for RDY bit, which when set to
+ * 1, indicates the TTS port is ready
+ * to accept a byte of data. The RDY
+ * bit goes zero 2-3 usec after
+ * writing, and goes 1 again 180-190
+ * usec later.
+ */
+#define TTS_ALMOST_FULL 0x08 /* mask for AF bit: When set to 1,
+ * indicates that less than 300 bytes
+ * are available in the TTS input
+ * buffer. AF is always 0 in the PCM,
+ * TGN and CVSD modes.
+ */
+#define TTS_ALMOST_EMPTY 0x04 /* mask for AE bit: When set to 1,
+ * indicates that less than 300 bytes
+ * are remaining in DoubleTalk's input
+ * (TTS or PCM) buffer. AE is always 1
+ * in the TGN and CVSD modes.
+ */
+
+ /* data returned by Interrogate command */
+struct synth_settings {
+ u_short serial_number; /* 0-7Fh:0-7Fh */
+ u_char rom_version[24]; /* null terminated string */
+ u_char mode; /* 0=Character; 1=Phoneme; 2=Text */
+ u_char punc_level; /* nB; 0-7 */
+ u_char formant_freq; /* nF; 0-9 */
+ u_char pitch; /* nP; 0-99 */
+ u_char speed; /* nS; 0-9 */
+ u_char volume; /* nV; 0-9 */
+ u_char tone; /* nX; 0-2 */
+ u_char expression; /* nE; 0-9 */
+ u_char ext_dict_loaded; /* 1=exception dictionary loaded */
+ u_char ext_dict_status; /* 1=exception dictionary enabled */
+ u_char free_ram; /* # pages (truncated) remaining for
+ * text buffer
+ */
+ u_char articulation; /* nA; 0-9 */
+ u_char reverb; /* nR; 0-9 */
+ u_char eob; /* 7Fh value indicating end of
+ * parameter block
+ */
+ u_char has_indexing; /* nonzero if indexing is implemented */
+};
diff --git a/drivers/accessibility/speakup/speakup_dummy.c b/drivers/accessibility/speakup/speakup_dummy.c
new file mode 100644
index 000000000..e393438af
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_dummy.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * originally written by: Kirk Reiser <kirk@braille.uwo.ca>
+ * this version considerably modified by David Borowski, david575@rogers.com
+ * eventually modified by Samuel Thibault <samuel.thibault@ens-lyon.org>
+ *
+ * Copyright (C) 1998-99 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ * Copyright (C) 2007 Samuel Thibault.
+ *
+ * specificly written as a driver for the speakup screenreview
+ * s not a general device driver.
+ */
+#include "spk_priv.h"
+#include "speakup.h"
+
+#define PROCSPEECH '\n'
+#define DRV_VERSION "2.11"
+#define SYNTH_CLEAR '!'
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"CAPS_START\n" } },
+ { CAPS_STOP, .u.s = {"CAPS_STOP\n" } },
+ { PAUSE, .u.s = {"PAUSE\n"} },
+ { RATE, .u.n = {"RATE %d\n", 8, 1, 16, 0, 0, NULL } },
+ { PITCH, .u.n = {"PITCH %d\n", 8, 0, 16, 0, 0, NULL } },
+ { INFLECTION, .u.n = {"INFLECTION %d\n", 8, 0, 16, 0, 0, NULL } },
+ { VOL, .u.n = {"VOL %d\n", 8, 0, 16, 0, 0, NULL } },
+ { TONE, .u.n = {"TONE %d\n", 8, 0, 16, 0, 0, NULL } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/*
+ * These attributes will appear in /sys/accessibility/speakup/dummy.
+ */
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute inflection_attribute =
+ __ATTR(inflection, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute tone_attribute =
+ __ATTR(tone, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &pitch_attribute.attr,
+ &inflection_attribute.attr,
+ &rate_attribute.attr,
+ &tone_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_dummy = {
+ .name = "dummy",
+ .version = DRV_VERSION,
+ .long_name = "Dummy",
+ .init = "Speakup\n",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 500,
+ .trigger = 50,
+ .jiffies = 50,
+ .full = 40000,
+ .dev_name = SYNTH_DEFAULT_DEV,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_ttyio_ops,
+ .probe = spk_ttyio_synth_probe,
+ .release = spk_ttyio_release,
+ .synth_immediate = spk_ttyio_synth_immediate,
+ .catch_up = spk_do_catch_up_unicode,
+ .flush = spk_synth_flush,
+ .is_alive = spk_synth_is_alive_restart,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = NULL,
+ .indexing = {
+ .command = NULL,
+ .lowindex = 0,
+ .highindex = 0,
+ .currindex = 0,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "dummy",
+ },
+};
+
+module_param_named(ser, synth_dummy.ser, int, 0444);
+module_param_named(dev, synth_dummy.dev_name, charp, 0444);
+module_param_named(start, synth_dummy.startup, short, 0444);
+
+MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based).");
+MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer.");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_dummy);
+
+MODULE_AUTHOR("Samuel Thibault <samuel.thibault@ens-lyon.org>");
+MODULE_DESCRIPTION("Speakup support for text console");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakup_keypc.c b/drivers/accessibility/speakup/speakup_keypc.c
new file mode 100644
index 000000000..414827e88
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_keypc.c
@@ -0,0 +1,318 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * written by David Borowski
+ *
+ * Copyright (C) 2003 David Borowski.
+ *
+ * specificly written as a driver for the speakup screenreview
+ * package it's not a general device driver.
+ * This driver is for the Keynote Gold internal synthesizer.
+ */
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/kthread.h>
+#include <linux/serial_reg.h>
+
+#include "spk_priv.h"
+#include "speakup.h"
+
+#define DRV_VERSION "2.10"
+#define SYNTH_IO_EXTENT 0x04
+#define SWAIT udelay(70)
+#define PROCSPEECH 0x1f
+#define SYNTH_CLEAR 0x03
+
+static int synth_probe(struct spk_synth *synth);
+static void keynote_release(void);
+static const char *synth_immediate(struct spk_synth *synth, const char *buf);
+static void do_catch_up(struct spk_synth *synth);
+static void synth_flush(struct spk_synth *synth);
+
+static int synth_port;
+static int port_forced;
+static unsigned int synth_portlist[] = { 0x2a8, 0 };
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"[f130]" } },
+ { CAPS_STOP, .u.s = {"[f90]" } },
+ { RATE, .u.n = {"\04%c ", 8, 0, 10, 81, -8, NULL } },
+ { PITCH, .u.n = {"[f%d]", 5, 0, 9, 40, 10, NULL } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/*
+ * These attributes will appear in /sys/accessibility/speakup/keypc.
+ */
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &pitch_attribute.attr,
+ &rate_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_keypc = {
+ .name = "keypc",
+ .version = DRV_VERSION,
+ .long_name = "Keynote PC",
+ .init = "[t][n7,1][n8,0]",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 500,
+ .trigger = 50,
+ .jiffies = 50,
+ .full = 1000,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_serial_io_ops,
+ .probe = synth_probe,
+ .release = keynote_release,
+ .synth_immediate = synth_immediate,
+ .catch_up = do_catch_up,
+ .flush = synth_flush,
+ .is_alive = spk_synth_is_alive_nop,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = NULL,
+ .indexing = {
+ .command = NULL,
+ .lowindex = 0,
+ .highindex = 0,
+ .currindex = 0,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "keypc",
+ },
+};
+
+static inline bool synth_writable(void)
+{
+ return (inb_p(synth_port + UART_RX) & 0x10) != 0;
+}
+
+static inline bool synth_full(void)
+{
+ return (inb_p(synth_port + UART_RX) & 0x80) == 0;
+}
+
+static char *oops(void)
+{
+ int s1, s2, s3, s4;
+
+ s1 = inb_p(synth_port);
+ s2 = inb_p(synth_port + 1);
+ s3 = inb_p(synth_port + 2);
+ s4 = inb_p(synth_port + 3);
+ pr_warn("synth timeout %d %d %d %d\n", s1, s2, s3, s4);
+ return NULL;
+}
+
+static const char *synth_immediate(struct spk_synth *synth, const char *buf)
+{
+ u_char ch;
+ int timeout;
+
+ while ((ch = *buf)) {
+ if (ch == '\n')
+ ch = PROCSPEECH;
+ if (synth_full())
+ return buf;
+ timeout = 1000;
+ while (synth_writable())
+ if (--timeout <= 0)
+ return oops();
+ outb_p(ch, synth_port);
+ udelay(70);
+ buf++;
+ }
+ return NULL;
+}
+
+static void do_catch_up(struct spk_synth *synth)
+{
+ u_char ch;
+ int timeout;
+ unsigned long flags;
+ unsigned long jiff_max;
+ struct var_t *jiffy_delta;
+ struct var_t *delay_time;
+ struct var_t *full_time;
+ int delay_time_val;
+ int full_time_val;
+ int jiffy_delta_val;
+
+ jiffy_delta = spk_get_var(JIFFY);
+ delay_time = spk_get_var(DELAY);
+ full_time = spk_get_var(FULL);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+
+ jiff_max = jiffies + jiffy_delta_val;
+ while (!kthread_should_stop()) {
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (speakup_info.flushing) {
+ speakup_info.flushing = 0;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ synth->flush(synth);
+ continue;
+ }
+ synth_buffer_skip_nonlatin1();
+ if (synth_buffer_empty()) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ break;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ full_time_val = full_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (synth_full()) {
+ schedule_timeout(msecs_to_jiffies(full_time_val));
+ continue;
+ }
+ set_current_state(TASK_RUNNING);
+ timeout = 1000;
+ while (synth_writable())
+ if (--timeout <= 0)
+ break;
+ if (timeout <= 0) {
+ oops();
+ break;
+ }
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ ch = synth_buffer_getc();
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (ch == '\n')
+ ch = PROCSPEECH;
+ outb_p(ch, synth_port);
+ SWAIT;
+ if (time_after_eq(jiffies, jiff_max) && (ch == SPACE)) {
+ timeout = 1000;
+ while (synth_writable())
+ if (--timeout <= 0)
+ break;
+ if (timeout <= 0) {
+ oops();
+ break;
+ }
+ outb_p(PROCSPEECH, synth_port);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ delay_time_val = delay_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ schedule_timeout(msecs_to_jiffies(delay_time_val));
+ jiff_max = jiffies + jiffy_delta_val;
+ }
+ }
+ timeout = 1000;
+ while (synth_writable())
+ if (--timeout <= 0)
+ break;
+ if (timeout <= 0)
+ oops();
+ else
+ outb_p(PROCSPEECH, synth_port);
+}
+
+static void synth_flush(struct spk_synth *synth)
+{
+ outb_p(SYNTH_CLEAR, synth_port);
+}
+
+static int synth_probe(struct spk_synth *synth)
+{
+ unsigned int port_val = 0;
+ int i = 0;
+
+ pr_info("Probing for %s.\n", synth->long_name);
+ if (port_forced) {
+ synth_port = port_forced;
+ pr_info("probe forced to %x by kernel command line\n",
+ synth_port);
+ if (synth_request_region(synth_port - 1, SYNTH_IO_EXTENT)) {
+ pr_warn("sorry, port already reserved\n");
+ return -EBUSY;
+ }
+ port_val = inb(synth_port);
+ } else {
+ for (i = 0; synth_portlist[i]; i++) {
+ if (synth_request_region(synth_portlist[i],
+ SYNTH_IO_EXTENT)) {
+ pr_warn
+ ("request_region: failed with 0x%x, %d\n",
+ synth_portlist[i], SYNTH_IO_EXTENT);
+ continue;
+ }
+ port_val = inb(synth_portlist[i]);
+ if (port_val == 0x80) {
+ synth_port = synth_portlist[i];
+ break;
+ }
+ }
+ }
+ if (port_val != 0x80) {
+ pr_info("%s: not found\n", synth->long_name);
+ synth_release_region(synth_port, SYNTH_IO_EXTENT);
+ synth_port = 0;
+ return -ENODEV;
+ }
+ pr_info("%s: %03x-%03x, driver version %s,\n", synth->long_name,
+ synth_port, synth_port + SYNTH_IO_EXTENT - 1,
+ synth->version);
+ synth->alive = 1;
+ return 0;
+}
+
+static void keynote_release(void)
+{
+ spk_stop_serial_interrupt();
+ if (synth_port)
+ synth_release_region(synth_port, SYNTH_IO_EXTENT);
+ synth_port = 0;
+}
+
+module_param_hw_named(port, port_forced, int, ioport, 0444);
+module_param_named(start, synth_keypc.startup, short, 0444);
+
+MODULE_PARM_DESC(port, "Set the port for the synthesizer (override probing).");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_keypc);
+
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for Keynote Gold PC synthesizers");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakup_ltlk.c b/drivers/accessibility/speakup/speakup_ltlk.c
new file mode 100644
index 000000000..3c59519a8
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_ltlk.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * originally written by: Kirk Reiser <kirk@braille.uwo.ca>
+ * this version considerably modified by David Borowski, david575@rogers.com
+ *
+ * Copyright (C) 1998-99 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ *
+ * specificly written as a driver for the speakup screenreview
+ * s not a general device driver.
+ */
+#include "speakup.h"
+#include "spk_priv.h"
+#include "speakup_dtlk.h" /* local header file for LiteTalk values */
+
+#define DRV_VERSION "2.11"
+#define PROCSPEECH 0x0d
+
+static int synth_probe(struct spk_synth *synth);
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"\x01+35p" } },
+ { CAPS_STOP, .u.s = {"\x01-35p" } },
+ { RATE, .u.n = {"\x01%ds", 8, 0, 9, 0, 0, NULL } },
+ { PITCH, .u.n = {"\x01%dp", 50, 0, 99, 0, 0, NULL } },
+ { VOL, .u.n = {"\x01%dv", 5, 0, 9, 0, 0, NULL } },
+ { TONE, .u.n = {"\x01%dx", 1, 0, 2, 0, 0, NULL } },
+ { PUNCT, .u.n = {"\x01%db", 7, 0, 15, 0, 0, NULL } },
+ { VOICE, .u.n = {"\x01%do", 0, 0, 7, 0, 0, NULL } },
+ { FREQUENCY, .u.n = {"\x01%df", 5, 0, 9, 0, 0, NULL } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/*
+ * These attributes will appear in /sys/accessibility/speakup/ltlk.
+ */
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute freq_attribute =
+ __ATTR(freq, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute punct_attribute =
+ __ATTR(punct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute tone_attribute =
+ __ATTR(tone, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute voice_attribute =
+ __ATTR(voice, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &freq_attribute.attr,
+ &pitch_attribute.attr,
+ &punct_attribute.attr,
+ &rate_attribute.attr,
+ &tone_attribute.attr,
+ &voice_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_ltlk = {
+ .name = "ltlk",
+ .version = DRV_VERSION,
+ .long_name = "LiteTalk",
+ .init = "\01@\x01\x31y\n\0",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 500,
+ .trigger = 50,
+ .jiffies = 50,
+ .full = 40000,
+ .dev_name = SYNTH_DEFAULT_DEV,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_ttyio_ops,
+ .probe = synth_probe,
+ .release = spk_ttyio_release,
+ .synth_immediate = spk_ttyio_synth_immediate,
+ .catch_up = spk_do_catch_up,
+ .flush = spk_synth_flush,
+ .is_alive = spk_synth_is_alive_restart,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = spk_synth_get_index,
+ .indexing = {
+ .command = "\x01%di",
+ .lowindex = 1,
+ .highindex = 5,
+ .currindex = 1,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "ltlk",
+ },
+};
+
+/* interrogate the LiteTalk and print its settings */
+static void synth_interrogate(struct spk_synth *synth)
+{
+ unsigned char *t, i;
+ unsigned char buf[50], rom_v[20];
+
+ synth->synth_immediate(synth, "\x18\x01?");
+ for (i = 0; i < 50; i++) {
+ buf[i] = synth->io_ops->synth_in();
+ if (i > 2 && buf[i] == 0x7f)
+ break;
+ }
+ t = buf + 2;
+ for (i = 0; *t != '\r'; t++) {
+ rom_v[i] = *t;
+ if (++i >= 19)
+ break;
+ }
+ rom_v[i] = 0;
+ pr_info("%s: ROM version: %s\n", synth->long_name, rom_v);
+}
+
+static int synth_probe(struct spk_synth *synth)
+{
+ int failed = 0;
+
+ failed = spk_ttyio_synth_probe(synth);
+ if (failed == 0)
+ synth_interrogate(synth);
+ synth->alive = !failed;
+ return failed;
+}
+
+module_param_named(ser, synth_ltlk.ser, int, 0444);
+module_param_named(dev, synth_ltlk.dev_name, charp, 0444);
+module_param_named(start, synth_ltlk.startup, short, 0444);
+
+MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based).");
+MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer.");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_ltlk);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for DoubleTalk LT/LiteTalk synthesizers");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakup_soft.c b/drivers/accessibility/speakup/speakup_soft.c
new file mode 100644
index 000000000..9a7029539
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_soft.c
@@ -0,0 +1,430 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* speakup_soft.c - speakup driver to register and make available
+ * a user space device for software synthesizers. written by: Kirk
+ * Reiser <kirk@braille.uwo.ca>
+ *
+ * Copyright (C) 2003 Kirk Reiser.
+ *
+ * this code is specificly written as a driver for the speakup screenreview
+ * package and is not a general device driver.
+ */
+
+#include <linux/unistd.h>
+#include <linux/miscdevice.h> /* for misc_register, and MISC_DYNAMIC_MINOR */
+#include <linux/poll.h> /* for poll_wait() */
+
+/* schedule(), signal_pending(), TASK_INTERRUPTIBLE */
+#include <linux/sched/signal.h>
+
+#include "spk_priv.h"
+#include "speakup.h"
+
+#define DRV_VERSION "2.6"
+#define PROCSPEECH 0x0d
+#define CLEAR_SYNTH 0x18
+
+static int softsynth_probe(struct spk_synth *synth);
+static void softsynth_release(void);
+static int softsynth_is_alive(struct spk_synth *synth);
+static unsigned char get_index(struct spk_synth *synth);
+
+static struct miscdevice synth_device, synthu_device;
+static int init_pos;
+static int misc_registered;
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"\x01+3p" } },
+ { CAPS_STOP, .u.s = {"\x01-3p" } },
+ { PAUSE, .u.n = {"\x01P" } },
+ { RATE, .u.n = {"\x01%ds", 2, 0, 9, 0, 0, NULL } },
+ { PITCH, .u.n = {"\x01%dp", 5, 0, 9, 0, 0, NULL } },
+ { INFLECTION, .u.n = {"\x01%dr", 5, 0, 9, 0, 0, NULL } },
+ { VOL, .u.n = {"\x01%dv", 5, 0, 9, 0, 0, NULL } },
+ { TONE, .u.n = {"\x01%dx", 1, 0, 2, 0, 0, NULL } },
+ { PUNCT, .u.n = {"\x01%db", 0, 0, 2, 0, 0, NULL } },
+ { VOICE, .u.n = {"\x01%do", 0, 0, 7, 0, 0, NULL } },
+ { FREQUENCY, .u.n = {"\x01%df", 5, 0, 9, 0, 0, NULL } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/* These attributes will appear in /sys/accessibility/speakup/soft. */
+
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute freq_attribute =
+ __ATTR(freq, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute inflection_attribute =
+ __ATTR(inflection, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute punct_attribute =
+ __ATTR(punct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute tone_attribute =
+ __ATTR(tone, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute voice_attribute =
+ __ATTR(voice, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+/*
+ * We should uncomment the following definition, when we agree on a
+ * method of passing a language designation to the software synthesizer.
+ * static struct kobj_attribute lang_attribute =
+ * __ATTR(lang, 0644, spk_var_show, spk_var_store);
+ */
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &freq_attribute.attr,
+/* &lang_attribute.attr, */
+ &pitch_attribute.attr,
+ &inflection_attribute.attr,
+ &punct_attribute.attr,
+ &rate_attribute.attr,
+ &tone_attribute.attr,
+ &voice_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_soft = {
+ .name = "soft",
+ .version = DRV_VERSION,
+ .long_name = "software synth",
+ .init = "\01@\x01\x31y\n",
+ .procspeech = PROCSPEECH,
+ .delay = 0,
+ .trigger = 0,
+ .jiffies = 0,
+ .full = 0,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = NULL,
+ .probe = softsynth_probe,
+ .release = softsynth_release,
+ .synth_immediate = NULL,
+ .catch_up = NULL,
+ .flush = NULL,
+ .is_alive = softsynth_is_alive,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = get_index,
+ .indexing = {
+ .command = "\x01%di",
+ .lowindex = 1,
+ .highindex = 5,
+ .currindex = 1,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "soft",
+ },
+};
+
+static char *get_initstring(void)
+{
+ static char buf[40];
+ char *cp;
+ struct var_t *var;
+
+ memset(buf, 0, sizeof(buf));
+ cp = buf;
+ var = synth_soft.vars;
+ while (var->var_id != MAXVARS) {
+ if (var->var_id != CAPS_START && var->var_id != CAPS_STOP &&
+ var->var_id != PAUSE && var->var_id != DIRECT)
+ cp = cp + sprintf(cp, var->u.n.synth_fmt,
+ var->u.n.value);
+ var++;
+ }
+ cp = cp + sprintf(cp, "\n");
+ return buf;
+}
+
+static int softsynth_open(struct inode *inode, struct file *fp)
+{
+ unsigned long flags;
+ /*if ((fp->f_flags & O_ACCMODE) != O_RDONLY) */
+ /* return -EPERM; */
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (synth_soft.alive) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return -EBUSY;
+ }
+ synth_soft.alive = 1;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return 0;
+}
+
+static int softsynth_close(struct inode *inode, struct file *fp)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ synth_soft.alive = 0;
+ init_pos = 0;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ /* Make sure we let applications go before leaving */
+ speakup_start_ttys();
+ return 0;
+}
+
+static ssize_t softsynthx_read(struct file *fp, char __user *buf, size_t count,
+ loff_t *pos, int unicode)
+{
+ int chars_sent = 0;
+ char __user *cp;
+ char *init;
+ size_t bytes_per_ch = unicode ? 3 : 1;
+ u16 ch;
+ int empty;
+ unsigned long flags;
+ DEFINE_WAIT(wait);
+
+ if (count < bytes_per_ch)
+ return -EINVAL;
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ synth_soft.alive = 1;
+ while (1) {
+ prepare_to_wait(&speakup_event, &wait, TASK_INTERRUPTIBLE);
+ if (synth_current() == &synth_soft) {
+ if (!unicode)
+ synth_buffer_skip_nonlatin1();
+ if (!synth_buffer_empty() || speakup_info.flushing)
+ break;
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (fp->f_flags & O_NONBLOCK) {
+ finish_wait(&speakup_event, &wait);
+ return -EAGAIN;
+ }
+ if (signal_pending(current)) {
+ finish_wait(&speakup_event, &wait);
+ return -ERESTARTSYS;
+ }
+ schedule();
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ }
+ finish_wait(&speakup_event, &wait);
+
+ cp = buf;
+ init = get_initstring();
+
+ /* Keep 3 bytes available for a 16bit UTF-8-encoded character */
+ while (chars_sent <= count - bytes_per_ch) {
+ if (synth_current() != &synth_soft)
+ break;
+ if (speakup_info.flushing) {
+ speakup_info.flushing = 0;
+ ch = '\x18';
+ } else if (init[init_pos]) {
+ ch = init[init_pos++];
+ } else {
+ if (!unicode)
+ synth_buffer_skip_nonlatin1();
+ if (synth_buffer_empty())
+ break;
+ ch = synth_buffer_getc();
+ }
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+
+ if ((!unicode && ch < 0x100) || (unicode && ch < 0x80)) {
+ u_char c = ch;
+
+ if (copy_to_user(cp, &c, 1))
+ return -EFAULT;
+
+ chars_sent++;
+ cp++;
+ } else if (unicode && ch < 0x800) {
+ u_char s[2] = {
+ 0xc0 | (ch >> 6),
+ 0x80 | (ch & 0x3f)
+ };
+
+ if (copy_to_user(cp, s, sizeof(s)))
+ return -EFAULT;
+
+ chars_sent += sizeof(s);
+ cp += sizeof(s);
+ } else if (unicode) {
+ u_char s[3] = {
+ 0xe0 | (ch >> 12),
+ 0x80 | ((ch >> 6) & 0x3f),
+ 0x80 | (ch & 0x3f)
+ };
+
+ if (copy_to_user(cp, s, sizeof(s)))
+ return -EFAULT;
+
+ chars_sent += sizeof(s);
+ cp += sizeof(s);
+ }
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ }
+ *pos += chars_sent;
+ empty = synth_buffer_empty();
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (empty) {
+ speakup_start_ttys();
+ *pos = 0;
+ }
+ return chars_sent;
+}
+
+static ssize_t softsynth_read(struct file *fp, char __user *buf, size_t count,
+ loff_t *pos)
+{
+ return softsynthx_read(fp, buf, count, pos, 0);
+}
+
+static ssize_t softsynthu_read(struct file *fp, char __user *buf, size_t count,
+ loff_t *pos)
+{
+ return softsynthx_read(fp, buf, count, pos, 1);
+}
+
+static int last_index;
+
+static ssize_t softsynth_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ unsigned long supplied_index = 0;
+ int converted;
+
+ converted = kstrtoul_from_user(buf, count, 0, &supplied_index);
+
+ if (converted < 0)
+ return converted;
+
+ last_index = supplied_index;
+ return count;
+}
+
+static __poll_t softsynth_poll(struct file *fp, struct poll_table_struct *wait)
+{
+ unsigned long flags;
+ __poll_t ret = 0;
+
+ poll_wait(fp, &speakup_event, wait);
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (synth_current() == &synth_soft &&
+ (!synth_buffer_empty() || speakup_info.flushing))
+ ret = EPOLLIN | EPOLLRDNORM;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ return ret;
+}
+
+static unsigned char get_index(struct spk_synth *synth)
+{
+ int rv;
+
+ rv = last_index;
+ last_index = 0;
+ return rv;
+}
+
+static const struct file_operations softsynth_fops = {
+ .owner = THIS_MODULE,
+ .poll = softsynth_poll,
+ .read = softsynth_read,
+ .write = softsynth_write,
+ .open = softsynth_open,
+ .release = softsynth_close,
+};
+
+static const struct file_operations softsynthu_fops = {
+ .owner = THIS_MODULE,
+ .poll = softsynth_poll,
+ .read = softsynthu_read,
+ .write = softsynth_write,
+ .open = softsynth_open,
+ .release = softsynth_close,
+};
+
+static int softsynth_probe(struct spk_synth *synth)
+{
+ if (misc_registered != 0)
+ return 0;
+ memset(&synth_device, 0, sizeof(synth_device));
+ synth_device.minor = MISC_DYNAMIC_MINOR;
+ synth_device.name = "softsynth";
+ synth_device.fops = &softsynth_fops;
+ if (misc_register(&synth_device)) {
+ pr_warn("Couldn't initialize miscdevice /dev/softsynth.\n");
+ return -ENODEV;
+ }
+
+ memset(&synthu_device, 0, sizeof(synthu_device));
+ synthu_device.minor = MISC_DYNAMIC_MINOR;
+ synthu_device.name = "softsynthu";
+ synthu_device.fops = &softsynthu_fops;
+ if (misc_register(&synthu_device)) {
+ pr_warn("Couldn't initialize miscdevice /dev/softsynthu.\n");
+ return -ENODEV;
+ }
+
+ misc_registered = 1;
+ pr_info("initialized device: /dev/softsynth, node (MAJOR 10, MINOR %d)\n",
+ synth_device.minor);
+ pr_info("initialized device: /dev/softsynthu, node (MAJOR 10, MINOR %d)\n",
+ synthu_device.minor);
+ return 0;
+}
+
+static void softsynth_release(void)
+{
+ misc_deregister(&synth_device);
+ misc_deregister(&synthu_device);
+ misc_registered = 0;
+ pr_info("unregistered /dev/softsynth\n");
+ pr_info("unregistered /dev/softsynthu\n");
+}
+
+static int softsynth_is_alive(struct spk_synth *synth)
+{
+ if (synth_soft.alive)
+ return 1;
+ return 0;
+}
+
+module_param_named(start, synth_soft.startup, short, 0444);
+
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_soft);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_DESCRIPTION("Speakup userspace software synthesizer support");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
diff --git a/drivers/accessibility/speakup/speakup_spkout.c b/drivers/accessibility/speakup/speakup_spkout.c
new file mode 100644
index 000000000..6e933bf1d
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_spkout.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * originally written by: Kirk Reiser <kirk@braille.uwo.ca>
+ * this version considerably modified by David Borowski, david575@rogers.com
+ *
+ * Copyright (C) 1998-99 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ *
+ * specificly written as a driver for the speakup screenreview
+ * s not a general device driver.
+ */
+#include "spk_priv.h"
+#include "speakup.h"
+
+#define DRV_VERSION "2.11"
+#define SYNTH_CLEAR 0x18
+#define PROCSPEECH '\r'
+
+static void synth_flush(struct spk_synth *synth);
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"\x05P+" } },
+ { CAPS_STOP, .u.s = {"\x05P-" } },
+ { RATE, .u.n = {"\x05R%d", 7, 0, 9, 0, 0, NULL } },
+ { PITCH, .u.n = {"\x05P%d", 3, 0, 9, 0, 0, NULL } },
+ { VOL, .u.n = {"\x05V%d", 9, 0, 9, 0, 0, NULL } },
+ { TONE, .u.n = {"\x05T%c", 8, 0, 25, 65, 0, NULL } },
+ { PUNCT, .u.n = {"\x05M%c", 0, 0, 3, 0, 0, "nsma" } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/* These attributes will appear in /sys/accessibility/speakup/spkout. */
+
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute punct_attribute =
+ __ATTR(punct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute tone_attribute =
+ __ATTR(tone, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &pitch_attribute.attr,
+ &punct_attribute.attr,
+ &rate_attribute.attr,
+ &tone_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_spkout = {
+ .name = "spkout",
+ .version = DRV_VERSION,
+ .long_name = "Speakout",
+ .init = "\005W1\005I2\005C3",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 500,
+ .trigger = 50,
+ .jiffies = 50,
+ .full = 40000,
+ .dev_name = SYNTH_DEFAULT_DEV,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_ttyio_ops,
+ .probe = spk_ttyio_synth_probe,
+ .release = spk_ttyio_release,
+ .synth_immediate = spk_ttyio_synth_immediate,
+ .catch_up = spk_do_catch_up,
+ .flush = synth_flush,
+ .is_alive = spk_synth_is_alive_restart,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = spk_synth_get_index,
+ .indexing = {
+ .command = "\x05[%c",
+ .lowindex = 1,
+ .highindex = 5,
+ .currindex = 1,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "spkout",
+ },
+};
+
+static void synth_flush(struct spk_synth *synth)
+{
+ synth->io_ops->flush_buffer();
+ synth->io_ops->send_xchar(SYNTH_CLEAR);
+}
+
+module_param_named(ser, synth_spkout.ser, int, 0444);
+module_param_named(dev, synth_spkout.dev_name, charp, 0444);
+module_param_named(start, synth_spkout.startup, short, 0444);
+
+MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based).");
+MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer.");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_spkout);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for Speak Out synthesizers");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakup_txprt.c b/drivers/accessibility/speakup/speakup_txprt.c
new file mode 100644
index 000000000..a7326f226
--- /dev/null
+++ b/drivers/accessibility/speakup/speakup_txprt.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * originally written by: Kirk Reiser <kirk@braille.uwo.ca>
+ * this version considerably modified by David Borowski, david575@rogers.com
+ *
+ * Copyright (C) 1998-99 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ *
+ * specificly written as a driver for the speakup screenreview
+ * s not a general device driver.
+ */
+#include "spk_priv.h"
+#include "speakup.h"
+
+#define DRV_VERSION "2.11"
+#define SYNTH_CLEAR 0x18
+#define PROCSPEECH '\r' /* process speech char */
+
+static struct var_t vars[] = {
+ { CAPS_START, .u.s = {"\x05P8" } },
+ { CAPS_STOP, .u.s = {"\x05P5" } },
+ { RATE, .u.n = {"\x05R%d", 5, 0, 9, 0, 0, NULL } },
+ { PITCH, .u.n = {"\x05P%d", 5, 0, 9, 0, 0, NULL } },
+ { VOL, .u.n = {"\x05V%d", 5, 0, 9, 0, 0, NULL } },
+ { TONE, .u.n = {"\x05T%c", 12, 0, 25, 61, 0, NULL } },
+ { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },
+ V_LAST_VAR
+ };
+
+/* These attributes will appear in /sys/accessibility/speakup/txprt. */
+
+static struct kobj_attribute caps_start_attribute =
+ __ATTR(caps_start, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute caps_stop_attribute =
+ __ATTR(caps_stop, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute pitch_attribute =
+ __ATTR(pitch, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute rate_attribute =
+ __ATTR(rate, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute tone_attribute =
+ __ATTR(tone, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute vol_attribute =
+ __ATTR(vol, 0644, spk_var_show, spk_var_store);
+
+static struct kobj_attribute delay_time_attribute =
+ __ATTR(delay_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute direct_attribute =
+ __ATTR(direct, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute full_time_attribute =
+ __ATTR(full_time, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute jiffy_delta_attribute =
+ __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);
+static struct kobj_attribute trigger_time_attribute =
+ __ATTR(trigger_time, 0644, spk_var_show, spk_var_store);
+
+/*
+ * Create a group of attributes so that we can create and destroy them all
+ * at once.
+ */
+static struct attribute *synth_attrs[] = {
+ &caps_start_attribute.attr,
+ &caps_stop_attribute.attr,
+ &pitch_attribute.attr,
+ &rate_attribute.attr,
+ &tone_attribute.attr,
+ &vol_attribute.attr,
+ &delay_time_attribute.attr,
+ &direct_attribute.attr,
+ &full_time_attribute.attr,
+ &jiffy_delta_attribute.attr,
+ &trigger_time_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct spk_synth synth_txprt = {
+ .name = "txprt",
+ .version = DRV_VERSION,
+ .long_name = "Transport",
+ .init = "\x05N1",
+ .procspeech = PROCSPEECH,
+ .clear = SYNTH_CLEAR,
+ .delay = 500,
+ .trigger = 50,
+ .jiffies = 50,
+ .full = 40000,
+ .dev_name = SYNTH_DEFAULT_DEV,
+ .startup = SYNTH_START,
+ .checkval = SYNTH_CHECK,
+ .vars = vars,
+ .io_ops = &spk_ttyio_ops,
+ .probe = spk_ttyio_synth_probe,
+ .release = spk_ttyio_release,
+ .synth_immediate = spk_ttyio_synth_immediate,
+ .catch_up = spk_do_catch_up,
+ .flush = spk_synth_flush,
+ .is_alive = spk_synth_is_alive_restart,
+ .synth_adjust = NULL,
+ .read_buff_add = NULL,
+ .get_index = NULL,
+ .indexing = {
+ .command = NULL,
+ .lowindex = 0,
+ .highindex = 0,
+ .currindex = 0,
+ },
+ .attributes = {
+ .attrs = synth_attrs,
+ .name = "txprt",
+ },
+};
+
+module_param_named(ser, synth_txprt.ser, int, 0444);
+module_param_named(dev, synth_txprt.dev_name, charp, 0444);
+module_param_named(start, synth_txprt.startup, short, 0444);
+
+MODULE_PARM_DESC(ser, "Set the serial port for the synthesizer (0-based).");
+MODULE_PARM_DESC(dev, "Set the device e.g. ttyUSB0, for the synthesizer.");
+MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");
+
+module_spk_synth(synth_txprt);
+
+MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>");
+MODULE_AUTHOR("David Borowski");
+MODULE_DESCRIPTION("Speakup support for Transport synthesizers");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
diff --git a/drivers/accessibility/speakup/speakupmap.h b/drivers/accessibility/speakup/speakupmap.h
new file mode 100644
index 000000000..c60d7339b
--- /dev/null
+++ b/drivers/accessibility/speakup/speakupmap.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+ 119, 62, 6,
+ 0, 16, 20, 17, 32, 48, 0,
+ 2, 0, 78, 0, 0, 0, 0,
+ 3, 0, 79, 0, 0, 0, 0,
+ 4, 0, 76, 0, 0, 0, 0,
+ 5, 0, 77, 0, 0, 0, 0,
+ 6, 0, 74, 0, 0, 0, 0,
+ 7, 0, 75, 0, 0, 0, 0,
+ 9, 0, 5, 46, 0, 0, 0,
+ 10, 0, 4, 0, 0, 0, 0,
+ 11, 0, 0, 1, 0, 0, 0,
+ 12, 0, 27, 0, 33, 0, 0,
+ 19, 0, 47, 0, 0, 0, 0,
+ 21, 0, 29, 17, 0, 0, 0,
+ 22, 0, 15, 0, 0, 0, 0,
+ 23, 0, 14, 0, 0, 0, 28,
+ 24, 0, 16, 0, 0, 0, 0,
+ 25, 0, 30, 18, 0, 0, 0,
+ 28, 0, 3, 26, 0, 0, 0,
+ 35, 0, 31, 0, 0, 0, 0,
+ 36, 0, 12, 0, 0, 0, 0,
+ 37, 0, 11, 0, 0, 0, 22,
+ 38, 0, 13, 0, 0, 0, 0,
+ 39, 0, 32, 7, 0, 0, 0,
+ 40, 0, 23, 0, 0, 0, 0,
+ 44, 0, 44, 0, 0, 0, 0,
+ 49, 0, 24, 0, 0, 0, 0,
+ 50, 0, 9, 19, 6, 0, 0,
+ 51, 0, 8, 0, 0, 0, 36,
+ 52, 0, 10, 20, 0, 0, 0,
+ 53, 0, 25, 0, 0, 0, 0,
+ 55, 46, 1, 0, 0, 0, 0,
+ 58, 128, 128, 0, 0, 0, 0,
+ 59, 0, 45, 0, 0, 0, 0,
+ 60, 0, 40, 0, 0, 0, 0,
+ 61, 0, 41, 0, 0, 0, 0,
+ 62, 0, 42, 0, 0, 0, 0,
+ 63, 0, 34, 0, 0, 0, 0,
+ 64, 0, 35, 0, 0, 0, 0,
+ 65, 0, 37, 0, 0, 0, 0,
+ 66, 0, 38, 0, 0, 0, 0,
+ 67, 0, 66, 0, 39, 0, 0,
+ 68, 0, 67, 0, 0, 0, 0,
+ 71, 15, 19, 0, 0, 0, 0,
+ 72, 14, 29, 0, 0, 28, 0,
+ 73, 16, 17, 0, 0, 0, 0,
+ 74, 27, 33, 0, 0, 0, 0,
+ 75, 12, 31, 0, 0, 0, 0,
+ 76, 11, 21, 0, 0, 22, 0,
+ 77, 13, 32, 0, 0, 0, 0,
+ 78, 23, 43, 0, 0, 0, 0,
+ 79, 9, 20, 0, 0, 0, 0,
+ 80, 8, 30, 0, 0, 36, 0,
+ 81, 10, 18, 0, 0, 0, 0,
+ 82, 128, 128, 0, 0, 0, 0,
+ 83, 24, 25, 0, 0, 0, 0,
+ 87, 0, 68, 0, 0, 0, 0,
+ 88, 0, 69, 0, 0, 0, 0,
+ 96, 3, 26, 0, 0, 0, 0,
+ 98, 4, 5, 0, 0, 0, 0,
+ 99, 2, 0, 0, 0, 0, 0,
+ 104, 0, 6, 0, 0, 0, 0,
+ 109, 0, 7, 0, 0, 0, 0,
+ 125, 128, 128, 0, 0, 0, 0,
+ 0, 119
diff --git a/drivers/accessibility/speakup/speakupmap.map b/drivers/accessibility/speakup/speakupmap.map
new file mode 100644
index 000000000..f10d44cf5
--- /dev/null
+++ b/drivers/accessibility/speakup/speakupmap.map
@@ -0,0 +1,93 @@
+spk key_f9 = punc_level_dec
+spk key_f10 = punc_level_inc
+spk key_f11 = reading_punc_dec
+spk key_f12 = reading_punc_inc
+spk key_1 = vol_dec
+spk key_2 = vol_inc
+spk key_3 = pitch_dec
+spk key_4 = pitch_inc
+spk key_5 = rate_dec
+spk key_6 = rate_inc
+key_kpasterisk = toggle_cursoring
+ctrl spk key_8 = toggle_cursoring
+spk key_kpasterisk = speakup_goto
+spk key_f1 = speakup_help
+spk key_f2 = set_win
+spk key_f3 = clear_win
+spk key_f4 = enable_win
+spk key_f5 = edit_some
+spk key_f6 = edit_most
+spk key_f7 = edit_delim
+spk key_f8 = edit_repeat
+shift spk key_f9 = edit_exnum
+ key_kp7 = say_prev_line
+spk key_kp7 = left_edge
+ key_kp8 = say_line
+double key_kp8 = say_line_indent
+spk key_kp8 = say_from_top
+ key_kp9 = say_next_line
+spk key_kp9 = top_edge
+ key_kpminus = speakup_parked
+spk key_kpminus = say_char_num
+ key_kp4 = say_prev_word
+spk key_kp4 = say_from_left
+ key_kp5 = say_word
+double key_kp5 = spell_word
+spk key_kp5 = spell_phonetic
+ key_kp6 = say_next_word
+spk key_kp6 = say_to_right
+ key_kpplus = say_screen
+spk key_kpplus = say_win
+ key_kp1 = say_prev_char
+spk key_kp1 = right_edge
+ key_kp2 = say_char
+spk key_kp2 = say_to_bottom
+double key_kp2 = say_phonetic_char
+ key_kp3 = say_next_char
+spk key_kp3 = bottom_edge
+ key_kp0 = spk_key
+ key_kpdot = say_position
+spk key_kpdot = say_attributes
+key_kpenter = speakup_quiet
+spk key_kpenter = speakup_off
+key_sysrq = speech_kill
+ key_kpslash = speakup_cut
+spk key_kpslash = speakup_paste
+spk key_pageup = say_first_char
+spk key_pagedown = say_last_char
+key_capslock = spk_key
+ spk key_z = spk_lock
+key_leftmeta = spk_key
+ctrl spk key_0 = speakup_goto
+spk key_u = say_prev_line
+spk key_i = say_line
+double spk key_i = say_line_indent
+spk key_o = say_next_line
+spk key_minus = speakup_parked
+shift spk key_minus = say_char_num
+spk key_j = say_prev_word
+spk key_k = say_word
+double spk key_k = spell_word
+spk key_l = say_next_word
+spk key_m = say_prev_char
+spk key_comma = say_char
+double spk key_comma = say_phonetic_char
+spk key_dot = say_next_char
+spk key_n = say_position
+ ctrl spk key_m = left_edge
+ ctrl spk key_y = top_edge
+ ctrl spk key_dot = right_edge
+ctrl spk key_p = bottom_edge
+spk key_apostrophe = say_screen
+spk key_h = say_from_left
+spk key_y = say_from_top
+spk key_semicolon = say_to_right
+spk key_p = say_to_bottom
+spk key_slash = say_attributes
+ spk key_enter = speakup_quiet
+ ctrl spk key_enter = speakup_off
+ spk key_9 = speakup_cut
+spk key_8 = speakup_paste
+shift spk key_m = say_first_char
+ ctrl spk key_semicolon = say_last_char
+spk key_r = read_all_doc
diff --git a/drivers/accessibility/speakup/spk_priv.h b/drivers/accessibility/speakup/spk_priv.h
new file mode 100644
index 000000000..0f4bcbe5d
--- /dev/null
+++ b/drivers/accessibility/speakup/spk_priv.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* spk_priv.h
+ * review functions for the speakup screen review package.
+ * originally written by: Kirk Reiser and Andy Berdan.
+ *
+ * extensively modified by David Borowski.
+ *
+ * Copyright (C) 1998 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ */
+#ifndef _SPEAKUP_PRIVATE_H
+#define _SPEAKUP_PRIVATE_H
+
+#include <linux/printk.h>
+
+#include "spk_types.h"
+#include "spk_priv_keyinfo.h"
+
+#define V_LAST_VAR { MAXVARS }
+#define SPACE 0x20
+#define SYNTH_CHECK 20030716 /* today's date ought to do for check value */
+/* synth flags, for odd synths */
+#define SF_DEC 1 /* to fiddle puncs in alpha strings so it doesn't spell */
+#ifdef MODULE
+#define SYNTH_START 1
+#else
+#define SYNTH_START 0
+#endif
+
+#define KT_SPKUP 15
+#define SPK_SYNTH_TIMEOUT 100000 /* in micro-seconds */
+#define SYNTH_DEFAULT_DEV "ttyS0"
+#define SYNTH_DEFAULT_SER 0
+
+const struct old_serial_port *spk_serial_init(int index);
+void spk_stop_serial_interrupt(void);
+void spk_serial_release(void);
+void spk_ttyio_release(void);
+void spk_ttyio_register_ldisc(void);
+void spk_ttyio_unregister_ldisc(void);
+
+void synth_buffer_skip_nonlatin1(void);
+u16 synth_buffer_getc(void);
+u16 synth_buffer_peek(void);
+int synth_buffer_empty(void);
+struct var_t *spk_get_var(enum var_id_t var_id);
+ssize_t spk_var_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf);
+ssize_t spk_var_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count);
+
+int spk_serial_synth_probe(struct spk_synth *synth);
+int spk_ttyio_synth_probe(struct spk_synth *synth);
+const char *spk_serial_synth_immediate(struct spk_synth *synth,
+ const char *buff);
+const char *spk_ttyio_synth_immediate(struct spk_synth *synth,
+ const char *buff);
+void spk_do_catch_up(struct spk_synth *synth);
+void spk_do_catch_up_unicode(struct spk_synth *synth);
+void spk_synth_flush(struct spk_synth *synth);
+unsigned char spk_synth_get_index(struct spk_synth *synth);
+int spk_synth_is_alive_nop(struct spk_synth *synth);
+int spk_synth_is_alive_restart(struct spk_synth *synth);
+__printf(1, 2)
+void synth_printf(const char *buf, ...);
+void synth_putwc(u16 wc);
+void synth_putwc_s(u16 wc);
+void synth_putws(const u16 *buf);
+void synth_putws_s(const u16 *buf);
+int synth_request_region(unsigned long start, unsigned long n);
+int synth_release_region(unsigned long start, unsigned long n);
+int synth_add(struct spk_synth *in_synth);
+void synth_remove(struct spk_synth *in_synth);
+struct spk_synth *synth_current(void);
+
+extern struct speakup_info_t speakup_info;
+
+extern struct var_t synth_time_vars[];
+
+extern struct spk_io_ops spk_serial_io_ops;
+extern struct spk_io_ops spk_ttyio_ops;
+
+#endif
diff --git a/drivers/accessibility/speakup/spk_priv_keyinfo.h b/drivers/accessibility/speakup/spk_priv_keyinfo.h
new file mode 100644
index 000000000..1f789bd1c
--- /dev/null
+++ b/drivers/accessibility/speakup/spk_priv_keyinfo.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* spk_priv.h
+ * review functions for the speakup screen review package.
+ * originally written by: Kirk Reiser and Andy Berdan.
+ *
+ * extensively modified by David Borowski.
+ *
+ * Copyright (C) 1998 Kirk Reiser.
+ * Copyright (C) 2003 David Borowski.
+ */
+
+#ifndef _SPEAKUP_KEYINFO_H
+#define _SPEAKUP_KEYINFO_H
+
+#define FIRST_SYNTH_VAR RATE
+/* 0 is reserved for no remap */
+#define SPEAKUP_GOTO 0x01
+#define SPEECH_KILL 0x02
+#define SPEAKUP_QUIET 0x03
+#define SPEAKUP_CUT 0x04
+#define SPEAKUP_PASTE 0x05
+#define SAY_FIRST_CHAR 0x06
+#define SAY_LAST_CHAR 0x07
+#define SAY_CHAR 0x08
+#define SAY_PREV_CHAR 0x09
+#define SAY_NEXT_CHAR 0x0a
+#define SAY_WORD 0x0b
+#define SAY_PREV_WORD 0x0c
+#define SAY_NEXT_WORD 0x0d
+#define SAY_LINE 0x0e
+#define SAY_PREV_LINE 0x0f
+#define SAY_NEXT_LINE 0x10
+#define TOP_EDGE 0x11
+#define BOTTOM_EDGE 0x12
+#define LEFT_EDGE 0x13
+#define RIGHT_EDGE 0x14
+#define SPELL_PHONETIC 0x15
+#define SPELL_WORD 0x16
+#define SAY_SCREEN 0x17
+#define SAY_POSITION 0x18
+#define SAY_ATTRIBUTES 0x19
+#define SPEAKUP_OFF 0x1a
+#define SPEAKUP_PARKED 0x1b
+#define SAY_LINE_INDENT 0x1c
+#define SAY_FROM_TOP 0x1d
+#define SAY_TO_BOTTOM 0x1e
+#define SAY_FROM_LEFT 0x1f
+#define SAY_TO_RIGHT 0x20
+#define SAY_CHAR_NUM 0x21
+#define EDIT_SOME 0x22
+#define EDIT_MOST 0x23
+#define SAY_PHONETIC_CHAR 0x24
+#define EDIT_DELIM 0x25
+#define EDIT_REPEAT 0x26
+#define EDIT_EXNUM 0x27
+#define SET_WIN 0x28
+#define CLEAR_WIN 0x29
+#define ENABLE_WIN 0x2a
+#define SAY_WIN 0x2b
+#define SPK_LOCK 0x2c
+#define SPEAKUP_HELP 0x2d
+#define TOGGLE_CURSORING 0x2e
+#define READ_ALL_DOC 0x2f
+
+/* one greater than the last func handler */
+#define SPKUP_MAX_FUNC 0x30
+
+#define SPK_KEY 0x80
+#define FIRST_EDIT_BITS 0x22
+#define FIRST_SET_VAR SPELL_DELAY
+
+/* increase if adding more than 0x3f functions */
+#define VAR_START 0x40
+
+/* keys for setting variables, must be ordered same as the enum for var_ids */
+/* with dec being even and inc being 1 greater */
+#define SPELL_DELAY_DEC (VAR_START + 0)
+#define SPELL_DELAY_INC (SPELL_DELAY_DEC + 1)
+#define PUNC_LEVEL_DEC (SPELL_DELAY_DEC + 2)
+#define PUNC_LEVEL_INC (PUNC_LEVEL_DEC + 1)
+#define READING_PUNC_DEC (PUNC_LEVEL_DEC + 2)
+#define READING_PUNC_INC (READING_PUNC_DEC + 1)
+#define ATTRIB_BLEEP_DEC (READING_PUNC_DEC + 2)
+#define ATTRIB_BLEEP_INC (ATTRIB_BLEEP_DEC + 1)
+#define BLEEPS_DEC (ATTRIB_BLEEP_DEC + 2)
+#define BLEEPS_INC (BLEEPS_DEC + 1)
+#define RATE_DEC (BLEEPS_DEC + 2)
+#define RATE_INC (RATE_DEC + 1)
+#define PITCH_DEC (RATE_DEC + 2)
+#define PITCH_INC (PITCH_DEC + 1)
+#define VOL_DEC (PITCH_DEC + 2)
+#define VOL_INC (VOL_DEC + 1)
+#define TONE_DEC (VOL_DEC + 2)
+#define TONE_INC (TONE_DEC + 1)
+#define PUNCT_DEC (TONE_DEC + 2)
+#define PUNCT_INC (PUNCT_DEC + 1)
+#define VOICE_DEC (PUNCT_DEC + 2)
+#define VOICE_INC (VOICE_DEC + 1)
+
+#endif
diff --git a/drivers/accessibility/speakup/spk_ttyio.c b/drivers/accessibility/speakup/spk_ttyio.c
new file mode 100644
index 000000000..da3a24557
--- /dev/null
+++ b/drivers/accessibility/speakup/spk_ttyio.c
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/types.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/slab.h>
+
+#include "speakup.h"
+#include "spk_types.h"
+#include "spk_priv.h"
+
+struct spk_ldisc_data {
+ char buf;
+ struct completion completion;
+ bool buf_free;
+};
+
+static struct spk_synth *spk_ttyio_synth;
+static struct tty_struct *speakup_tty;
+/* mutex to protect against speakup_tty disappearing from underneath us while
+ * we are using it. this can happen when the device physically unplugged,
+ * while in use. it also serialises access to speakup_tty.
+ */
+static DEFINE_MUTEX(speakup_tty_mutex);
+
+static int ser_to_dev(int ser, dev_t *dev_no)
+{
+ if (ser < 0 || ser > (255 - 64)) {
+ pr_err("speakup: Invalid ser param. Must be between 0 and 191 inclusive.\n");
+ return -EINVAL;
+ }
+
+ *dev_no = MKDEV(4, (64 + ser));
+ return 0;
+}
+
+static int get_dev_to_use(struct spk_synth *synth, dev_t *dev_no)
+{
+ /* use ser only when dev is not specified */
+ if (strcmp(synth->dev_name, SYNTH_DEFAULT_DEV) ||
+ synth->ser == SYNTH_DEFAULT_SER)
+ return tty_dev_name_to_number(synth->dev_name, dev_no);
+
+ return ser_to_dev(synth->ser, dev_no);
+}
+
+static int spk_ttyio_ldisc_open(struct tty_struct *tty)
+{
+ struct spk_ldisc_data *ldisc_data;
+
+ if (tty != speakup_tty)
+ /* Somebody tried to use this line discipline outside speakup */
+ return -ENODEV;
+
+ if (!tty->ops->write)
+ return -EOPNOTSUPP;
+
+ ldisc_data = kmalloc(sizeof(*ldisc_data), GFP_KERNEL);
+ if (!ldisc_data)
+ return -ENOMEM;
+
+ init_completion(&ldisc_data->completion);
+ ldisc_data->buf_free = true;
+ tty->disc_data = ldisc_data;
+
+ return 0;
+}
+
+static void spk_ttyio_ldisc_close(struct tty_struct *tty)
+{
+ mutex_lock(&speakup_tty_mutex);
+ kfree(speakup_tty->disc_data);
+ speakup_tty = NULL;
+ mutex_unlock(&speakup_tty_mutex);
+}
+
+static int spk_ttyio_receive_buf2(struct tty_struct *tty,
+ const unsigned char *cp, char *fp, int count)
+{
+ struct spk_ldisc_data *ldisc_data = tty->disc_data;
+
+ if (spk_ttyio_synth->read_buff_add) {
+ int i;
+
+ for (i = 0; i < count; i++)
+ spk_ttyio_synth->read_buff_add(cp[i]);
+
+ return count;
+ }
+
+ if (!ldisc_data->buf_free)
+ /* ttyio_in will tty_flip_buffer_push */
+ return 0;
+
+ /* Make sure the consumer has read buf before we have seen
+ * buf_free == true and overwrite buf
+ */
+ mb();
+
+ ldisc_data->buf = cp[0];
+ ldisc_data->buf_free = false;
+ complete(&ldisc_data->completion);
+
+ return 1;
+}
+
+static struct tty_ldisc_ops spk_ttyio_ldisc_ops = {
+ .owner = THIS_MODULE,
+ .magic = TTY_LDISC_MAGIC,
+ .name = "speakup_ldisc",
+ .open = spk_ttyio_ldisc_open,
+ .close = spk_ttyio_ldisc_close,
+ .receive_buf2 = spk_ttyio_receive_buf2,
+};
+
+static int spk_ttyio_out(struct spk_synth *in_synth, const char ch);
+static int spk_ttyio_out_unicode(struct spk_synth *in_synth, u16 ch);
+static void spk_ttyio_send_xchar(char ch);
+static void spk_ttyio_tiocmset(unsigned int set, unsigned int clear);
+static unsigned char spk_ttyio_in(void);
+static unsigned char spk_ttyio_in_nowait(void);
+static void spk_ttyio_flush_buffer(void);
+static int spk_ttyio_wait_for_xmitr(struct spk_synth *in_synth);
+
+struct spk_io_ops spk_ttyio_ops = {
+ .synth_out = spk_ttyio_out,
+ .synth_out_unicode = spk_ttyio_out_unicode,
+ .send_xchar = spk_ttyio_send_xchar,
+ .tiocmset = spk_ttyio_tiocmset,
+ .synth_in = spk_ttyio_in,
+ .synth_in_nowait = spk_ttyio_in_nowait,
+ .flush_buffer = spk_ttyio_flush_buffer,
+ .wait_for_xmitr = spk_ttyio_wait_for_xmitr,
+};
+EXPORT_SYMBOL_GPL(spk_ttyio_ops);
+
+static inline void get_termios(struct tty_struct *tty,
+ struct ktermios *out_termios)
+{
+ down_read(&tty->termios_rwsem);
+ *out_termios = tty->termios;
+ up_read(&tty->termios_rwsem);
+}
+
+static int spk_ttyio_initialise_ldisc(struct spk_synth *synth)
+{
+ int ret = 0;
+ struct tty_struct *tty;
+ struct ktermios tmp_termios;
+ dev_t dev;
+
+ ret = get_dev_to_use(synth, &dev);
+ if (ret)
+ return ret;
+
+ tty = tty_kopen(dev);
+ if (IS_ERR(tty))
+ return PTR_ERR(tty);
+
+ if (tty->ops->open)
+ ret = tty->ops->open(tty, NULL);
+ else
+ ret = -ENODEV;
+
+ if (ret) {
+ tty_unlock(tty);
+ return ret;
+ }
+
+ clear_bit(TTY_HUPPED, &tty->flags);
+ /* ensure hardware flow control is enabled */
+ get_termios(tty, &tmp_termios);
+ if (!(tmp_termios.c_cflag & CRTSCTS)) {
+ tmp_termios.c_cflag |= CRTSCTS;
+ tty_set_termios(tty, &tmp_termios);
+ /*
+ * check c_cflag to see if it's updated as tty_set_termios
+ * may not return error even when no tty bits are
+ * changed by the request.
+ */
+ get_termios(tty, &tmp_termios);
+ if (!(tmp_termios.c_cflag & CRTSCTS))
+ pr_warn("speakup: Failed to set hardware flow control\n");
+ }
+
+ tty_unlock(tty);
+
+ mutex_lock(&speakup_tty_mutex);
+ speakup_tty = tty;
+ ret = tty_set_ldisc(tty, N_SPEAKUP);
+ if (ret)
+ speakup_tty = NULL;
+ mutex_unlock(&speakup_tty_mutex);
+
+ if (!ret)
+ /* Success */
+ return 0;
+
+ pr_err("speakup: Failed to set N_SPEAKUP on tty\n");
+
+ tty_lock(tty);
+ if (tty->ops->close)
+ tty->ops->close(tty, NULL);
+ tty_unlock(tty);
+
+ tty_kclose(tty);
+
+ return ret;
+}
+
+void spk_ttyio_register_ldisc(void)
+{
+ if (tty_register_ldisc(N_SPEAKUP, &spk_ttyio_ldisc_ops))
+ pr_warn("speakup: Error registering line discipline. Most synths won't work.\n");
+}
+
+void spk_ttyio_unregister_ldisc(void)
+{
+ if (tty_unregister_ldisc(N_SPEAKUP))
+ pr_warn("speakup: Couldn't unregister ldisc\n");
+}
+
+static int spk_ttyio_out(struct spk_synth *in_synth, const char ch)
+{
+ mutex_lock(&speakup_tty_mutex);
+ if (in_synth->alive && speakup_tty && speakup_tty->ops->write) {
+ int ret = speakup_tty->ops->write(speakup_tty, &ch, 1);
+
+ mutex_unlock(&speakup_tty_mutex);
+ if (ret == 0)
+ /* No room */
+ return 0;
+ if (ret < 0) {
+ pr_warn("%s: I/O error, deactivating speakup\n",
+ in_synth->long_name);
+ /* No synth any more, so nobody will restart TTYs,
+ * and we thus need to do it ourselves. Now that there
+ * is no synth we can let application flood anyway
+ */
+ in_synth->alive = 0;
+ speakup_start_ttys();
+ return 0;
+ }
+ return 1;
+ }
+
+ mutex_unlock(&speakup_tty_mutex);
+ return 0;
+}
+
+static int spk_ttyio_out_unicode(struct spk_synth *in_synth, u16 ch)
+{
+ int ret;
+
+ if (ch < 0x80) {
+ ret = spk_ttyio_out(in_synth, ch);
+ } else if (ch < 0x800) {
+ ret = spk_ttyio_out(in_synth, 0xc0 | (ch >> 6));
+ ret &= spk_ttyio_out(in_synth, 0x80 | (ch & 0x3f));
+ } else {
+ ret = spk_ttyio_out(in_synth, 0xe0 | (ch >> 12));
+ ret &= spk_ttyio_out(in_synth, 0x80 | ((ch >> 6) & 0x3f));
+ ret &= spk_ttyio_out(in_synth, 0x80 | (ch & 0x3f));
+ }
+ return ret;
+}
+
+static int check_tty(struct tty_struct *tty)
+{
+ if (!tty) {
+ pr_warn("%s: I/O error, deactivating speakup\n",
+ spk_ttyio_synth->long_name);
+ /* No synth any more, so nobody will restart TTYs, and we thus
+ * need to do it ourselves. Now that there is no synth we can
+ * let application flood anyway
+ */
+ spk_ttyio_synth->alive = 0;
+ speakup_start_ttys();
+ return 1;
+ }
+
+ return 0;
+}
+
+static void spk_ttyio_send_xchar(char ch)
+{
+ mutex_lock(&speakup_tty_mutex);
+ if (check_tty(speakup_tty)) {
+ mutex_unlock(&speakup_tty_mutex);
+ return;
+ }
+
+ if (speakup_tty->ops->send_xchar)
+ speakup_tty->ops->send_xchar(speakup_tty, ch);
+ mutex_unlock(&speakup_tty_mutex);
+}
+
+static void spk_ttyio_tiocmset(unsigned int set, unsigned int clear)
+{
+ mutex_lock(&speakup_tty_mutex);
+ if (check_tty(speakup_tty)) {
+ mutex_unlock(&speakup_tty_mutex);
+ return;
+ }
+
+ if (speakup_tty->ops->tiocmset)
+ speakup_tty->ops->tiocmset(speakup_tty, set, clear);
+ mutex_unlock(&speakup_tty_mutex);
+}
+
+static int spk_ttyio_wait_for_xmitr(struct spk_synth *in_synth)
+{
+ return 1;
+}
+
+static unsigned char ttyio_in(int timeout)
+{
+ struct spk_ldisc_data *ldisc_data = speakup_tty->disc_data;
+ char rv;
+
+ if (!timeout) {
+ if (!try_wait_for_completion(&ldisc_data->completion))
+ return 0xff;
+ } else if (wait_for_completion_timeout(&ldisc_data->completion,
+ usecs_to_jiffies(timeout)) == 0) {
+ pr_warn("spk_ttyio: timeout (%d) while waiting for input\n",
+ timeout);
+ return 0xff;
+ }
+
+ rv = ldisc_data->buf;
+ /* Make sure we have read buf before we set buf_free to let
+ * the producer overwrite it
+ */
+ mb();
+ ldisc_data->buf_free = true;
+ /* Let TTY push more characters */
+ tty_flip_buffer_push(speakup_tty->port);
+
+ return rv;
+}
+
+static unsigned char spk_ttyio_in(void)
+{
+ return ttyio_in(SPK_SYNTH_TIMEOUT);
+}
+
+static unsigned char spk_ttyio_in_nowait(void)
+{
+ u8 rv = ttyio_in(0);
+
+ return (rv == 0xff) ? 0 : rv;
+}
+
+static void spk_ttyio_flush_buffer(void)
+{
+ mutex_lock(&speakup_tty_mutex);
+ if (check_tty(speakup_tty)) {
+ mutex_unlock(&speakup_tty_mutex);
+ return;
+ }
+
+ if (speakup_tty->ops->flush_buffer)
+ speakup_tty->ops->flush_buffer(speakup_tty);
+
+ mutex_unlock(&speakup_tty_mutex);
+}
+
+int spk_ttyio_synth_probe(struct spk_synth *synth)
+{
+ int rv = spk_ttyio_initialise_ldisc(synth);
+
+ if (rv)
+ return rv;
+
+ synth->alive = 1;
+ spk_ttyio_synth = synth;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spk_ttyio_synth_probe);
+
+void spk_ttyio_release(void)
+{
+ if (!speakup_tty)
+ return;
+
+ tty_lock(speakup_tty);
+
+ if (speakup_tty->ops->close)
+ speakup_tty->ops->close(speakup_tty, NULL);
+
+ tty_ldisc_flush(speakup_tty);
+ tty_unlock(speakup_tty);
+ tty_kclose(speakup_tty);
+}
+EXPORT_SYMBOL_GPL(spk_ttyio_release);
+
+const char *spk_ttyio_synth_immediate(struct spk_synth *synth, const char *buff)
+{
+ u_char ch;
+
+ while ((ch = *buff)) {
+ if (ch == '\n')
+ ch = synth->procspeech;
+ if (tty_write_room(speakup_tty) < 1 ||
+ !synth->io_ops->synth_out(synth, ch))
+ return buff;
+ buff++;
+ }
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(spk_ttyio_synth_immediate);
diff --git a/drivers/accessibility/speakup/spk_types.h b/drivers/accessibility/speakup/spk_types.h
new file mode 100644
index 000000000..91fca3033
--- /dev/null
+++ b/drivers/accessibility/speakup/spk_types.h
@@ -0,0 +1,226 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef SPEAKUP_TYPES_H
+#define SPEAKUP_TYPES_H
+
+/* This file includes all of the typedefs and structs used in speakup. */
+
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/wait.h> /* for wait_queue */
+#include <linux/init.h> /* for __init */
+#include <linux/module.h>
+#include <linux/vt_kern.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/io.h> /* for inb_p, outb_p, inb, outb, etc... */
+#include <linux/device.h>
+
+enum var_type_t {
+ VAR_NUM = 0,
+ VAR_TIME,
+ VAR_STRING,
+ VAR_PROC
+};
+
+enum {
+ E_DEFAULT = 0,
+ E_SET,
+ E_INC,
+ E_DEC,
+ E_NEW_DEFAULT,
+};
+
+/*
+ * Note: add new members at the end, speakupmap.h depends on the values of the
+ * enum starting from SPELL_DELAY (see inc_dec_var)
+ */
+enum var_id_t {
+ VERSION = 0, SYNTH, SILENT, SYNTH_DIRECT,
+ KEYMAP, CHARS,
+ PUNC_SOME, PUNC_MOST, PUNC_ALL,
+ DELIM, REPEATS, EXNUMBER,
+ DELAY, TRIGGER, JIFFY, FULL, /* all timers must be together */
+ BLEEP_TIME, CURSOR_TIME, BELL_POS,
+ SAY_CONTROL, SAY_WORD_CTL, NO_INTERRUPT, KEY_ECHO,
+ SPELL_DELAY, PUNC_LEVEL, READING_PUNC,
+ ATTRIB_BLEEP, BLEEPS,
+ RATE, PITCH, VOL, TONE, PUNCT, VOICE, FREQUENCY, LANG,
+ DIRECT, PAUSE,
+ CAPS_START, CAPS_STOP, CHARTAB, INFLECTION,
+ MAXVARS
+};
+
+typedef int (*special_func)(struct vc_data *vc, u_char type, u_char ch,
+ u_short key);
+
+#define COLOR_BUFFER_SIZE 160
+
+struct spk_highlight_color_track {
+ /* Count of each background color */
+ unsigned int bgcount[8];
+ /* Buffer for characters drawn with each background color */
+ u16 highbuf[8][COLOR_BUFFER_SIZE];
+ /* Current index into highbuf */
+ unsigned int highsize[8];
+ /* Reading Position for each color */
+ u_long rpos[8], rx[8], ry[8];
+ /* Real Cursor Y Position */
+ ulong cy;
+};
+
+struct st_spk_t {
+ u_long reading_x, cursor_x;
+ u_long reading_y, cursor_y;
+ u_long reading_pos, cursor_pos;
+ u_long go_x, go_pos;
+ u_long w_top, w_bottom, w_left, w_right;
+ u_char w_start, w_enabled;
+ u_char reading_attr, old_attr;
+ char parked, shut_up;
+ struct spk_highlight_color_track ht;
+ int tty_stopped;
+};
+
+/* now some defines to make these easier to use. */
+#define spk_shut_up (speakup_console[vc->vc_num]->shut_up)
+#define spk_killed (speakup_console[vc->vc_num]->shut_up & 0x40)
+#define spk_x (speakup_console[vc->vc_num]->reading_x)
+#define spk_cx (speakup_console[vc->vc_num]->cursor_x)
+#define spk_y (speakup_console[vc->vc_num]->reading_y)
+#define spk_cy (speakup_console[vc->vc_num]->cursor_y)
+#define spk_pos (speakup_console[vc->vc_num]->reading_pos)
+#define spk_cp (speakup_console[vc->vc_num]->cursor_pos)
+#define goto_pos (speakup_console[vc->vc_num]->go_pos)
+#define goto_x (speakup_console[vc->vc_num]->go_x)
+#define win_top (speakup_console[vc->vc_num]->w_top)
+#define win_bottom (speakup_console[vc->vc_num]->w_bottom)
+#define win_left (speakup_console[vc->vc_num]->w_left)
+#define win_right (speakup_console[vc->vc_num]->w_right)
+#define win_start (speakup_console[vc->vc_num]->w_start)
+#define win_enabled (speakup_console[vc->vc_num]->w_enabled)
+#define spk_attr (speakup_console[vc->vc_num]->reading_attr)
+#define spk_old_attr (speakup_console[vc->vc_num]->old_attr)
+#define spk_parked (speakup_console[vc->vc_num]->parked)
+
+struct st_var_header {
+ char *name;
+ enum var_id_t var_id;
+ enum var_type_t var_type;
+ void *p_val; /* ptr to programs variable to store value */
+ void *data; /* ptr to the vars data */
+};
+
+struct num_var_t {
+ char *synth_fmt;
+ int default_val;
+ int low;
+ int high;
+ short offset, multiplier; /* for fiddling rates etc. */
+ char *out_str; /* if synth needs char representation of number */
+ int value; /* current value */
+};
+
+struct punc_var_t {
+ enum var_id_t var_id;
+ short value;
+};
+
+struct string_var_t {
+ char *default_val;
+};
+
+struct var_t {
+ enum var_id_t var_id;
+ union {
+ struct num_var_t n;
+ struct string_var_t s;
+ } u;
+};
+
+struct st_bits_data { /* punc, repeats, word delim bits */
+ char *name;
+ char *value;
+ short mask;
+};
+
+struct synth_indexing {
+ char *command;
+ unsigned char lowindex;
+ unsigned char highindex;
+ unsigned char currindex;
+};
+
+struct spk_synth;
+
+struct spk_io_ops {
+ int (*synth_out)(struct spk_synth *synth, const char ch);
+ int (*synth_out_unicode)(struct spk_synth *synth, u16 ch);
+ void (*send_xchar)(char ch);
+ void (*tiocmset)(unsigned int set, unsigned int clear);
+ unsigned char (*synth_in)(void);
+ unsigned char (*synth_in_nowait)(void);
+ void (*flush_buffer)(void);
+ int (*wait_for_xmitr)(struct spk_synth *synth);
+};
+
+struct spk_synth {
+ struct list_head node;
+
+ const char *name;
+ const char *version;
+ const char *long_name;
+ const char *init;
+ char procspeech;
+ char clear;
+ int delay;
+ int trigger;
+ int jiffies;
+ int full;
+ int ser;
+ char *dev_name;
+ short flags;
+ short startup;
+ const int checkval; /* for validating a proper synth module */
+ struct var_t *vars;
+ int *default_pitch;
+ int *default_vol;
+ struct spk_io_ops *io_ops;
+ int (*probe)(struct spk_synth *synth);
+ void (*release)(void);
+ const char *(*synth_immediate)(struct spk_synth *synth,
+ const char *buff);
+ void (*catch_up)(struct spk_synth *synth);
+ void (*flush)(struct spk_synth *synth);
+ int (*is_alive)(struct spk_synth *synth);
+ int (*synth_adjust)(struct st_var_header *var);
+ void (*read_buff_add)(u_char c);
+ unsigned char (*get_index)(struct spk_synth *synth);
+ struct synth_indexing indexing;
+ int alive;
+ struct attribute_group attributes;
+};
+
+/*
+ * module_spk_synth() - Helper macro for registering a speakup driver
+ * @__spk_synth: spk_synth struct
+ * Helper macro for speakup drivers which do not do anything special in module
+ * init/exit. This eliminates a lot of boilerplate. Each module may only
+ * use this macro once, and calling it replaces module_init() and module_exit()
+ */
+#define module_spk_synth(__spk_synth) \
+ module_driver(__spk_synth, synth_add, synth_remove)
+
+struct speakup_info_t {
+ spinlock_t spinlock;
+ int port_tts;
+ int flushing;
+};
+
+struct bleep {
+ short freq;
+ unsigned long jiffies;
+ int active;
+};
+#endif
diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c
new file mode 100644
index 000000000..ac47dbac7
--- /dev/null
+++ b/drivers/accessibility/speakup/synth.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/types.h>
+#include <linux/ctype.h> /* for isdigit() and friends */
+#include <linux/fs.h>
+#include <linux/mm.h> /* for verify_area */
+#include <linux/errno.h> /* for -EBUSY */
+#include <linux/ioport.h> /* for check_region, request_region */
+#include <linux/interrupt.h>
+#include <linux/delay.h> /* for loops_per_sec */
+#include <linux/kmod.h>
+#include <linux/jiffies.h>
+#include <linux/uaccess.h> /* for copy_from_user */
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/kthread.h>
+
+#include "spk_priv.h"
+#include "speakup.h"
+#include "serialio.h"
+
+static LIST_HEAD(synths);
+struct spk_synth *synth;
+char spk_pitch_buff[32] = "";
+static int module_status;
+bool spk_quiet_boot;
+
+struct speakup_info_t speakup_info = {
+ /*
+ * This spinlock is used to protect the entire speakup machinery, and
+ * must be taken at each kernel->speakup transition and released at
+ * each corresponding speakup->kernel transition.
+ *
+ * The progression thread only interferes with the speakup machinery
+ * through the synth buffer, so only needs to take the lock
+ * while tinkering with the buffer.
+ *
+ * We use spin_lock/trylock_irqsave and spin_unlock_irqrestore with this
+ * spinlock because speakup needs to disable the keyboard IRQ.
+ */
+ .spinlock = __SPIN_LOCK_UNLOCKED(speakup_info.spinlock),
+ .flushing = 0,
+};
+EXPORT_SYMBOL_GPL(speakup_info);
+
+static int do_synth_init(struct spk_synth *in_synth);
+
+/*
+ * Main loop of the progression thread: keep eating from the buffer
+ * and push to the serial port, waiting as needed
+ *
+ * For devices that have a "full" notification mechanism, the driver can
+ * adapt the loop the way they prefer.
+ */
+static void _spk_do_catch_up(struct spk_synth *synth, int unicode)
+{
+ u16 ch;
+ unsigned long flags;
+ unsigned long jiff_max;
+ struct var_t *delay_time;
+ struct var_t *full_time;
+ struct var_t *jiffy_delta;
+ int jiffy_delta_val;
+ int delay_time_val;
+ int full_time_val;
+ int ret;
+
+ jiffy_delta = spk_get_var(JIFFY);
+ full_time = spk_get_var(FULL);
+ delay_time = spk_get_var(DELAY);
+
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+
+ jiff_max = jiffies + jiffy_delta_val;
+ while (!kthread_should_stop()) {
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ if (speakup_info.flushing) {
+ speakup_info.flushing = 0;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ synth->flush(synth);
+ continue;
+ }
+ if (!unicode)
+ synth_buffer_skip_nonlatin1();
+ if (synth_buffer_empty()) {
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ break;
+ }
+ ch = synth_buffer_peek();
+ set_current_state(TASK_INTERRUPTIBLE);
+ full_time_val = full_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (ch == '\n')
+ ch = synth->procspeech;
+ if (unicode)
+ ret = synth->io_ops->synth_out_unicode(synth, ch);
+ else
+ ret = synth->io_ops->synth_out(synth, ch);
+ if (!ret) {
+ schedule_timeout(msecs_to_jiffies(full_time_val));
+ continue;
+ }
+ if (time_after_eq(jiffies, jiff_max) && (ch == SPACE)) {
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ delay_time_val = delay_time->u.n.value;
+ full_time_val = full_time->u.n.value;
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (synth->io_ops->synth_out(synth, synth->procspeech))
+ schedule_timeout(
+ msecs_to_jiffies(delay_time_val));
+ else
+ schedule_timeout(
+ msecs_to_jiffies(full_time_val));
+ jiff_max = jiffies + jiffy_delta_val;
+ }
+ set_current_state(TASK_RUNNING);
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ synth_buffer_getc();
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ }
+ synth->io_ops->synth_out(synth, synth->procspeech);
+}
+
+void spk_do_catch_up(struct spk_synth *synth)
+{
+ _spk_do_catch_up(synth, 0);
+}
+EXPORT_SYMBOL_GPL(spk_do_catch_up);
+
+void spk_do_catch_up_unicode(struct spk_synth *synth)
+{
+ _spk_do_catch_up(synth, 1);
+}
+EXPORT_SYMBOL_GPL(spk_do_catch_up_unicode);
+
+void spk_synth_flush(struct spk_synth *synth)
+{
+ synth->io_ops->flush_buffer();
+ synth->io_ops->synth_out(synth, synth->clear);
+}
+EXPORT_SYMBOL_GPL(spk_synth_flush);
+
+unsigned char spk_synth_get_index(struct spk_synth *synth)
+{
+ return synth->io_ops->synth_in_nowait();
+}
+EXPORT_SYMBOL_GPL(spk_synth_get_index);
+
+int spk_synth_is_alive_nop(struct spk_synth *synth)
+{
+ synth->alive = 1;
+ return 1;
+}
+EXPORT_SYMBOL_GPL(spk_synth_is_alive_nop);
+
+int spk_synth_is_alive_restart(struct spk_synth *synth)
+{
+ if (synth->alive)
+ return 1;
+ if (synth->io_ops->wait_for_xmitr(synth) > 0) {
+ /* restart */
+ synth->alive = 1;
+ synth_printf("%s", synth->init);
+ return 2; /* reenabled */
+ }
+ pr_warn("%s: can't restart synth\n", synth->long_name);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spk_synth_is_alive_restart);
+
+static void thread_wake_up(struct timer_list *unused)
+{
+ wake_up_interruptible_all(&speakup_event);
+}
+
+static DEFINE_TIMER(thread_timer, thread_wake_up);
+
+void synth_start(void)
+{
+ struct var_t *trigger_time;
+
+ if (!synth->alive) {
+ synth_buffer_clear();
+ return;
+ }
+ trigger_time = spk_get_var(TRIGGER);
+ if (!timer_pending(&thread_timer))
+ mod_timer(&thread_timer, jiffies +
+ msecs_to_jiffies(trigger_time->u.n.value));
+}
+
+void spk_do_flush(void)
+{
+ if (!synth)
+ return;
+
+ speakup_info.flushing = 1;
+ synth_buffer_clear();
+ if (synth->alive) {
+ if (spk_pitch_shift) {
+ synth_printf("%s", spk_pitch_buff);
+ spk_pitch_shift = 0;
+ }
+ }
+ wake_up_interruptible_all(&speakup_event);
+ wake_up_process(speakup_task);
+}
+
+void synth_write(const char *buf, size_t count)
+{
+ while (count--)
+ synth_buffer_add(*buf++);
+ synth_start();
+}
+
+void synth_printf(const char *fmt, ...)
+{
+ va_list args;
+ unsigned char buf[160], *p;
+ int r;
+
+ va_start(args, fmt);
+ r = vsnprintf(buf, sizeof(buf), fmt, args);
+ va_end(args);
+ if (r > sizeof(buf) - 1)
+ r = sizeof(buf) - 1;
+
+ p = buf;
+ while (r--)
+ synth_buffer_add(*p++);
+ synth_start();
+}
+EXPORT_SYMBOL_GPL(synth_printf);
+
+void synth_putwc(u16 wc)
+{
+ synth_buffer_add(wc);
+}
+EXPORT_SYMBOL_GPL(synth_putwc);
+
+void synth_putwc_s(u16 wc)
+{
+ synth_buffer_add(wc);
+ synth_start();
+}
+EXPORT_SYMBOL_GPL(synth_putwc_s);
+
+void synth_putws(const u16 *buf)
+{
+ const u16 *p;
+
+ for (p = buf; *p; p++)
+ synth_buffer_add(*p);
+}
+EXPORT_SYMBOL_GPL(synth_putws);
+
+void synth_putws_s(const u16 *buf)
+{
+ synth_putws(buf);
+ synth_start();
+}
+EXPORT_SYMBOL_GPL(synth_putws_s);
+
+static int index_count;
+static int sentence_count;
+
+void spk_reset_index_count(int sc)
+{
+ static int first = 1;
+
+ if (first)
+ first = 0;
+ else
+ synth->get_index(synth);
+ index_count = 0;
+ sentence_count = sc;
+}
+
+int synth_supports_indexing(void)
+{
+ if (synth->get_index)
+ return 1;
+ return 0;
+}
+
+void synth_insert_next_index(int sent_num)
+{
+ int out;
+
+ if (synth->alive) {
+ if (sent_num == 0) {
+ synth->indexing.currindex++;
+ index_count++;
+ if (synth->indexing.currindex >
+ synth->indexing.highindex)
+ synth->indexing.currindex =
+ synth->indexing.lowindex;
+ }
+
+ out = synth->indexing.currindex * 10 + sent_num;
+ synth_printf(synth->indexing.command, out, out);
+ }
+}
+
+void spk_get_index_count(int *linecount, int *sentcount)
+{
+ int ind = synth->get_index(synth);
+
+ if (ind) {
+ sentence_count = ind % 10;
+
+ if ((ind / 10) <= synth->indexing.currindex)
+ index_count = synth->indexing.currindex - (ind / 10);
+ else
+ index_count = synth->indexing.currindex
+ - synth->indexing.lowindex
+ + synth->indexing.highindex - (ind / 10) + 1;
+ }
+ *sentcount = sentence_count;
+ *linecount = index_count;
+}
+
+static struct resource synth_res;
+
+int synth_request_region(unsigned long start, unsigned long n)
+{
+ struct resource *parent = &ioport_resource;
+
+ memset(&synth_res, 0, sizeof(synth_res));
+ synth_res.name = synth->name;
+ synth_res.start = start;
+ synth_res.end = start + n - 1;
+ synth_res.flags = IORESOURCE_BUSY;
+ return request_resource(parent, &synth_res);
+}
+EXPORT_SYMBOL_GPL(synth_request_region);
+
+int synth_release_region(unsigned long start, unsigned long n)
+{
+ return release_resource(&synth_res);
+}
+EXPORT_SYMBOL_GPL(synth_release_region);
+
+struct var_t synth_time_vars[] = {
+ { DELAY, .u.n = {NULL, 100, 100, 2000, 0, 0, NULL } },
+ { TRIGGER, .u.n = {NULL, 20, 10, 2000, 0, 0, NULL } },
+ { JIFFY, .u.n = {NULL, 50, 20, 200, 0, 0, NULL } },
+ { FULL, .u.n = {NULL, 400, 200, 60000, 0, 0, NULL } },
+ V_LAST_VAR
+};
+
+/* called by: speakup_init() */
+int synth_init(char *synth_name)
+{
+ int ret = 0;
+ struct spk_synth *tmp, *synth = NULL;
+
+ if (!synth_name)
+ return 0;
+
+ if (strcmp(synth_name, "none") == 0) {
+ mutex_lock(&spk_mutex);
+ synth_release();
+ mutex_unlock(&spk_mutex);
+ return 0;
+ }
+
+ mutex_lock(&spk_mutex);
+ /* First, check if we already have it loaded. */
+ list_for_each_entry(tmp, &synths, node) {
+ if (strcmp(tmp->name, synth_name) == 0)
+ synth = tmp;
+ }
+
+ /* If we got one, initialize it now. */
+ if (synth)
+ ret = do_synth_init(synth);
+ else
+ ret = -ENODEV;
+ mutex_unlock(&spk_mutex);
+
+ return ret;
+}
+
+/* called by: synth_add() */
+static int do_synth_init(struct spk_synth *in_synth)
+{
+ struct var_t *var;
+
+ synth_release();
+ if (in_synth->checkval != SYNTH_CHECK)
+ return -EINVAL;
+ synth = in_synth;
+ synth->alive = 0;
+ pr_warn("synth probe\n");
+ if (synth->probe(synth) < 0) {
+ pr_warn("%s: device probe failed\n", in_synth->name);
+ synth = NULL;
+ return -ENODEV;
+ }
+ synth_time_vars[0].u.n.value =
+ synth_time_vars[0].u.n.default_val = synth->delay;
+ synth_time_vars[1].u.n.value =
+ synth_time_vars[1].u.n.default_val = synth->trigger;
+ synth_time_vars[2].u.n.value =
+ synth_time_vars[2].u.n.default_val = synth->jiffies;
+ synth_time_vars[3].u.n.value =
+ synth_time_vars[3].u.n.default_val = synth->full;
+ synth_printf("%s", synth->init);
+ for (var = synth->vars;
+ (var->var_id >= 0) && (var->var_id < MAXVARS); var++)
+ speakup_register_var(var);
+ if (!spk_quiet_boot)
+ synth_printf("%s found\n", synth->long_name);
+ if (synth->attributes.name &&
+ sysfs_create_group(speakup_kobj, &synth->attributes) < 0)
+ return -ENOMEM;
+ synth_flags = synth->flags;
+ wake_up_interruptible_all(&speakup_event);
+ if (speakup_task)
+ wake_up_process(speakup_task);
+ return 0;
+}
+
+void synth_release(void)
+{
+ struct var_t *var;
+ unsigned long flags;
+
+ if (!synth)
+ return;
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ pr_info("releasing synth %s\n", synth->name);
+ synth->alive = 0;
+ del_timer(&thread_timer);
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (synth->attributes.name)
+ sysfs_remove_group(speakup_kobj, &synth->attributes);
+ for (var = synth->vars; var->var_id != MAXVARS; var++)
+ speakup_unregister_var(var->var_id);
+ synth->release();
+ synth = NULL;
+}
+
+/* called by: all_driver_init() */
+int synth_add(struct spk_synth *in_synth)
+{
+ int status = 0;
+ struct spk_synth *tmp;
+
+ mutex_lock(&spk_mutex);
+
+ list_for_each_entry(tmp, &synths, node) {
+ if (tmp == in_synth) {
+ mutex_unlock(&spk_mutex);
+ return 0;
+ }
+ }
+
+ if (in_synth->startup)
+ status = do_synth_init(in_synth);
+
+ if (!status)
+ list_add_tail(&in_synth->node, &synths);
+
+ mutex_unlock(&spk_mutex);
+ return status;
+}
+EXPORT_SYMBOL_GPL(synth_add);
+
+void synth_remove(struct spk_synth *in_synth)
+{
+ mutex_lock(&spk_mutex);
+ if (synth == in_synth)
+ synth_release();
+ list_del(&in_synth->node);
+ module_status = 0;
+ mutex_unlock(&spk_mutex);
+}
+EXPORT_SYMBOL_GPL(synth_remove);
+
+struct spk_synth *synth_current(void)
+{
+ return synth;
+}
+EXPORT_SYMBOL_GPL(synth_current);
+
+short spk_punc_masks[] = { 0, SOME, MOST, PUNC, PUNC | B_SYM };
diff --git a/drivers/accessibility/speakup/thread.c b/drivers/accessibility/speakup/thread.c
new file mode 100644
index 000000000..2fc75e60f
--- /dev/null
+++ b/drivers/accessibility/speakup/thread.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kthread.h>
+#include <linux/wait.h>
+
+#include "spk_types.h"
+#include "speakup.h"
+#include "spk_priv.h"
+
+DECLARE_WAIT_QUEUE_HEAD(speakup_event);
+EXPORT_SYMBOL_GPL(speakup_event);
+
+int speakup_thread(void *data)
+{
+ unsigned long flags;
+ int should_break;
+ struct bleep our_sound;
+
+ our_sound.active = 0;
+ our_sound.freq = 0;
+ our_sound.jiffies = 0;
+
+ mutex_lock(&spk_mutex);
+ while (1) {
+ DEFINE_WAIT(wait);
+
+ while (1) {
+ spin_lock_irqsave(&speakup_info.spinlock, flags);
+ our_sound = spk_unprocessed_sound;
+ spk_unprocessed_sound.active = 0;
+ prepare_to_wait(&speakup_event, &wait,
+ TASK_INTERRUPTIBLE);
+ should_break = kthread_should_stop() ||
+ our_sound.active ||
+ (synth && synth->catch_up && synth->alive &&
+ (speakup_info.flushing ||
+ !synth_buffer_empty()));
+ spin_unlock_irqrestore(&speakup_info.spinlock, flags);
+ if (should_break)
+ break;
+ mutex_unlock(&spk_mutex);
+ schedule();
+ mutex_lock(&spk_mutex);
+ }
+ finish_wait(&speakup_event, &wait);
+ if (kthread_should_stop())
+ break;
+
+ if (our_sound.active)
+ kd_mksound(our_sound.freq, our_sound.jiffies);
+ if (synth && synth->catch_up && synth->alive) {
+ /*
+ * It is up to the callee to take the lock, so that it
+ * can sleep whenever it likes
+ */
+ synth->catch_up(synth);
+ }
+
+ speakup_start_ttys();
+ }
+ mutex_unlock(&spk_mutex);
+ return 0;
+}
diff --git a/drivers/accessibility/speakup/varhandlers.c b/drivers/accessibility/speakup/varhandlers.c
new file mode 100644
index 000000000..d7f6bec7f
--- /dev/null
+++ b/drivers/accessibility/speakup/varhandlers.c
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ctype.h>
+#include "spk_types.h"
+#include "spk_priv.h"
+#include "speakup.h"
+
+static struct st_var_header var_headers[] = {
+ { "version", VERSION, VAR_PROC, NULL, NULL },
+ { "synth_name", SYNTH, VAR_PROC, NULL, NULL },
+ { "keymap", KEYMAP, VAR_PROC, NULL, NULL },
+ { "silent", SILENT, VAR_PROC, NULL, NULL },
+ { "punc_some", PUNC_SOME, VAR_PROC, NULL, NULL },
+ { "punc_most", PUNC_MOST, VAR_PROC, NULL, NULL },
+ { "punc_all", PUNC_ALL, VAR_PROC, NULL, NULL },
+ { "delimiters", DELIM, VAR_PROC, NULL, NULL },
+ { "repeats", REPEATS, VAR_PROC, NULL, NULL },
+ { "ex_num", EXNUMBER, VAR_PROC, NULL, NULL },
+ { "characters", CHARS, VAR_PROC, NULL, NULL },
+ { "synth_direct", SYNTH_DIRECT, VAR_PROC, NULL, NULL },
+ { "caps_start", CAPS_START, VAR_STRING, spk_str_caps_start, NULL },
+ { "caps_stop", CAPS_STOP, VAR_STRING, spk_str_caps_stop, NULL },
+ { "delay_time", DELAY, VAR_TIME, NULL, NULL },
+ { "trigger_time", TRIGGER, VAR_TIME, NULL, NULL },
+ { "jiffy_delta", JIFFY, VAR_TIME, NULL, NULL },
+ { "full_time", FULL, VAR_TIME, NULL, NULL },
+ { "spell_delay", SPELL_DELAY, VAR_NUM, &spk_spell_delay, NULL },
+ { "bleeps", BLEEPS, VAR_NUM, &spk_bleeps, NULL },
+ { "attrib_bleep", ATTRIB_BLEEP, VAR_NUM, &spk_attrib_bleep, NULL },
+ { "bleep_time", BLEEP_TIME, VAR_TIME, &spk_bleep_time, NULL },
+ { "cursor_time", CURSOR_TIME, VAR_TIME, NULL, NULL },
+ { "punc_level", PUNC_LEVEL, VAR_NUM, &spk_punc_level, NULL },
+ { "reading_punc", READING_PUNC, VAR_NUM, &spk_reading_punc, NULL },
+ { "say_control", SAY_CONTROL, VAR_NUM, &spk_say_ctrl, NULL },
+ { "say_word_ctl", SAY_WORD_CTL, VAR_NUM, &spk_say_word_ctl, NULL },
+ { "no_interrupt", NO_INTERRUPT, VAR_NUM, &spk_no_intr, NULL },
+ { "key_echo", KEY_ECHO, VAR_NUM, &spk_key_echo, NULL },
+ { "bell_pos", BELL_POS, VAR_NUM, &spk_bell_pos, NULL },
+ { "rate", RATE, VAR_NUM, NULL, NULL },
+ { "pitch", PITCH, VAR_NUM, NULL, NULL },
+ { "inflection", INFLECTION, VAR_NUM, NULL, NULL },
+ { "vol", VOL, VAR_NUM, NULL, NULL },
+ { "tone", TONE, VAR_NUM, NULL, NULL },
+ { "punct", PUNCT, VAR_NUM, NULL, NULL },
+ { "voice", VOICE, VAR_NUM, NULL, NULL },
+ { "freq", FREQUENCY, VAR_NUM, NULL, NULL },
+ { "lang", LANG, VAR_NUM, NULL, NULL },
+ { "chartab", CHARTAB, VAR_PROC, NULL, NULL },
+ { "direct", DIRECT, VAR_NUM, NULL, NULL },
+ { "pause", PAUSE, VAR_STRING, spk_str_pause, NULL },
+};
+
+static struct st_var_header *var_ptrs[MAXVARS] = { NULL, NULL, NULL };
+
+static struct punc_var_t punc_vars[] = {
+ { PUNC_SOME, 1 },
+ { PUNC_MOST, 2 },
+ { PUNC_ALL, 3 },
+ { DELIM, 4 },
+ { REPEATS, 5 },
+ { EXNUMBER, 6 },
+ { -1, -1 },
+};
+
+int spk_chartab_get_value(char *keyword)
+{
+ int value = 0;
+
+ if (!strcmp(keyword, "ALPHA"))
+ value = ALPHA;
+ else if (!strcmp(keyword, "B_CTL"))
+ value = B_CTL;
+ else if (!strcmp(keyword, "WDLM"))
+ value = WDLM;
+ else if (!strcmp(keyword, "A_PUNC"))
+ value = A_PUNC;
+ else if (!strcmp(keyword, "PUNC"))
+ value = PUNC;
+ else if (!strcmp(keyword, "NUM"))
+ value = NUM;
+ else if (!strcmp(keyword, "A_CAP"))
+ value = A_CAP;
+ else if (!strcmp(keyword, "B_CAPSYM"))
+ value = B_CAPSYM;
+ else if (!strcmp(keyword, "B_SYM"))
+ value = B_SYM;
+ return value;
+}
+
+void speakup_register_var(struct var_t *var)
+{
+ static char nothing[2] = "\0";
+ int i;
+ struct st_var_header *p_header;
+
+ BUG_ON(!var || var->var_id < 0 || var->var_id >= MAXVARS);
+ if (!var_ptrs[0]) {
+ for (i = 0; i < MAXVARS; i++) {
+ p_header = &var_headers[i];
+ var_ptrs[p_header->var_id] = p_header;
+ p_header->data = NULL;
+ }
+ }
+ p_header = var_ptrs[var->var_id];
+ if (p_header->data)
+ return;
+ p_header->data = var;
+ switch (p_header->var_type) {
+ case VAR_STRING:
+ spk_set_string_var(nothing, p_header, 0);
+ break;
+ case VAR_NUM:
+ case VAR_TIME:
+ spk_set_num_var(0, p_header, E_DEFAULT);
+ break;
+ default:
+ break;
+ }
+}
+
+void speakup_unregister_var(enum var_id_t var_id)
+{
+ struct st_var_header *p_header;
+
+ BUG_ON(var_id < 0 || var_id >= MAXVARS);
+ p_header = var_ptrs[var_id];
+ p_header->data = NULL;
+}
+
+struct st_var_header *spk_get_var_header(enum var_id_t var_id)
+{
+ struct st_var_header *p_header;
+
+ if (var_id < 0 || var_id >= MAXVARS)
+ return NULL;
+ p_header = var_ptrs[var_id];
+ if (!p_header->data)
+ return NULL;
+ return p_header;
+}
+
+struct st_var_header *spk_var_header_by_name(const char *name)
+{
+ int i;
+
+ if (!name)
+ return NULL;
+
+ for (i = 0; i < MAXVARS; i++) {
+ if (strcmp(name, var_ptrs[i]->name) == 0)
+ return var_ptrs[i];
+ }
+ return NULL;
+}
+
+struct var_t *spk_get_var(enum var_id_t var_id)
+{
+ BUG_ON(var_id < 0 || var_id >= MAXVARS);
+ BUG_ON(!var_ptrs[var_id]);
+ return var_ptrs[var_id]->data;
+}
+EXPORT_SYMBOL_GPL(spk_get_var);
+
+struct punc_var_t *spk_get_punc_var(enum var_id_t var_id)
+{
+ struct punc_var_t *rv = NULL;
+ struct punc_var_t *where;
+
+ where = punc_vars;
+ while ((where->var_id != -1) && (!rv)) {
+ if (where->var_id == var_id)
+ rv = where;
+ else
+ where++;
+ }
+ return rv;
+}
+
+/* handlers for setting vars */
+int spk_set_num_var(int input, struct st_var_header *var, int how)
+{
+ int val;
+ int *p_val = var->p_val;
+ char buf[32];
+ char *cp;
+ struct var_t *var_data = var->data;
+
+ if (!var_data)
+ return -ENODATA;
+
+ val = var_data->u.n.value;
+ switch (how) {
+ case E_NEW_DEFAULT:
+ if (input < var_data->u.n.low || input > var_data->u.n.high)
+ return -ERANGE;
+ var_data->u.n.default_val = input;
+ return 0;
+ case E_DEFAULT:
+ val = var_data->u.n.default_val;
+ break;
+ case E_SET:
+ val = input;
+ break;
+ case E_INC:
+ val += input;
+ break;
+ case E_DEC:
+ val -= input;
+ break;
+ }
+
+ if (val < var_data->u.n.low || val > var_data->u.n.high)
+ return -ERANGE;
+
+ var_data->u.n.value = val;
+ if (var->var_type == VAR_TIME && p_val) {
+ *p_val = msecs_to_jiffies(val);
+ return 0;
+ }
+ if (p_val)
+ *p_val = val;
+ if (var->var_id == PUNC_LEVEL) {
+ spk_punc_mask = spk_punc_masks[val];
+ return 0;
+ }
+ if (var_data->u.n.multiplier != 0)
+ val *= var_data->u.n.multiplier;
+ val += var_data->u.n.offset;
+ if (var->var_id < FIRST_SYNTH_VAR || !synth)
+ return 0;
+ if (synth->synth_adjust)
+ return synth->synth_adjust(var);
+
+ if (!var_data->u.n.synth_fmt)
+ return 0;
+ if (var->var_id == PITCH)
+ cp = spk_pitch_buff;
+ else
+ cp = buf;
+ if (!var_data->u.n.out_str)
+ sprintf(cp, var_data->u.n.synth_fmt, (int)val);
+ else
+ sprintf(cp, var_data->u.n.synth_fmt,
+ var_data->u.n.out_str[val]);
+ synth_printf("%s", cp);
+ return 0;
+}
+
+int spk_set_string_var(const char *page, struct st_var_header *var, int len)
+{
+ struct var_t *var_data = var->data;
+
+ if (!var_data)
+ return -ENODATA;
+ if (len > MAXVARLEN)
+ return -E2BIG;
+ if (!len) {
+ if (!var_data->u.s.default_val)
+ return 0;
+ if (!var->p_val)
+ var->p_val = var_data->u.s.default_val;
+ if (var->p_val != var_data->u.s.default_val)
+ strcpy((char *)var->p_val, var_data->u.s.default_val);
+ return -ERESTART;
+ } else if (var->p_val) {
+ strcpy((char *)var->p_val, page);
+ } else {
+ return -E2BIG;
+ }
+ return 0;
+}
+
+/*
+ * spk_set_mask_bits sets or clears the punc/delim/repeat bits,
+ * if input is null uses the defaults.
+ * values for how: 0 clears bits of chars supplied,
+ * 1 clears allk, 2 sets bits for chars
+ */
+int spk_set_mask_bits(const char *input, const int which, const int how)
+{
+ u_char *cp;
+ short mask = spk_punc_info[which].mask;
+
+ if (how & 1) {
+ for (cp = (u_char *)spk_punc_info[3].value; *cp; cp++)
+ spk_chartab[*cp] &= ~mask;
+ }
+ cp = (u_char *)input;
+ if (!cp) {
+ cp = spk_punc_info[which].value;
+ } else {
+ for (; *cp; cp++) {
+ if (*cp < SPACE)
+ break;
+ if (mask < PUNC) {
+ if (!(spk_chartab[*cp] & PUNC))
+ break;
+ } else if (spk_chartab[*cp] & B_NUM) {
+ break;
+ }
+ }
+ if (*cp)
+ return -EINVAL;
+ cp = (u_char *)input;
+ }
+ if (how & 2) {
+ for (; *cp; cp++)
+ if (*cp > SPACE)
+ spk_chartab[*cp] |= mask;
+ } else {
+ for (; *cp; cp++)
+ if (*cp > SPACE)
+ spk_chartab[*cp] &= ~mask;
+ }
+ return 0;
+}
+
+char *spk_strlwr(char *s)
+{
+ char *p;
+
+ if (!s)
+ return NULL;
+
+ for (p = s; *p; p++)
+ *p = tolower(*p);
+ return s;
+}
+
+char *spk_s2uchar(char *start, char *dest)
+{
+ int val;
+
+ /* Do not replace with kstrtoul: here we need start to be updated */
+ val = simple_strtoul(skip_spaces(start), &start, 10);
+ if (*start == ',')
+ start++;
+ *dest = (u_char)val;
+ return start;
+}