Path: blob/master/drivers/accessibility/speakup/synth.c
26282 views
// SPDX-License-Identifier: GPL-2.01#include <linux/types.h>2#include <linux/ctype.h> /* for isdigit() and friends */3#include <linux/fs.h>4#include <linux/mm.h> /* for verify_area */5#include <linux/errno.h> /* for -EBUSY */6#include <linux/ioport.h> /* for check_region, request_region */7#include <linux/interrupt.h>8#include <linux/delay.h> /* for loops_per_sec */9#include <linux/kmod.h>10#include <linux/jiffies.h>11#include <linux/uaccess.h> /* for copy_from_user */12#include <linux/sched.h>13#include <linux/timer.h>14#include <linux/kthread.h>1516#include "spk_priv.h"17#include "speakup.h"18#include "serialio.h"1920static LIST_HEAD(synths);21struct spk_synth *synth;22char spk_pitch_buff[32] = "";23static int module_status;24bool spk_quiet_boot;2526struct speakup_info_t speakup_info = {27/*28* This spinlock is used to protect the entire speakup machinery, and29* must be taken at each kernel->speakup transition and released at30* each corresponding speakup->kernel transition.31*32* The progression thread only interferes with the speakup machinery33* through the synth buffer, so only needs to take the lock34* while tinkering with the buffer.35*36* We use spin_lock/trylock_irqsave and spin_unlock_irqrestore with this37* spinlock because speakup needs to disable the keyboard IRQ.38*/39.spinlock = __SPIN_LOCK_UNLOCKED(speakup_info.spinlock),40.flushing = 0,41};42EXPORT_SYMBOL_GPL(speakup_info);4344static int do_synth_init(struct spk_synth *in_synth);4546/*47* Main loop of the progression thread: keep eating from the buffer48* and push to the serial port, waiting as needed49*50* For devices that have a "full" notification mechanism, the driver can51* adapt the loop the way they prefer.52*/53static void _spk_do_catch_up(struct spk_synth *synth, int unicode)54{55u16 ch;56unsigned long flags;57unsigned long jiff_max;58struct var_t *delay_time;59struct var_t *full_time;60struct var_t *jiffy_delta;61int jiffy_delta_val;62int delay_time_val;63int full_time_val;64int ret;6566jiffy_delta = spk_get_var(JIFFY);67full_time = spk_get_var(FULL);68delay_time = spk_get_var(DELAY);6970spin_lock_irqsave(&speakup_info.spinlock, flags);71jiffy_delta_val = jiffy_delta->u.n.value;72spin_unlock_irqrestore(&speakup_info.spinlock, flags);7374jiff_max = jiffies + jiffy_delta_val;75while (!kthread_should_stop()) {76spin_lock_irqsave(&speakup_info.spinlock, flags);77if (speakup_info.flushing) {78speakup_info.flushing = 0;79spin_unlock_irqrestore(&speakup_info.spinlock, flags);80synth->flush(synth);81continue;82}83if (!unicode)84synth_buffer_skip_nonlatin1();85if (synth_buffer_empty()) {86spin_unlock_irqrestore(&speakup_info.spinlock, flags);87break;88}89ch = synth_buffer_peek();90set_current_state(TASK_INTERRUPTIBLE);91full_time_val = full_time->u.n.value;92spin_unlock_irqrestore(&speakup_info.spinlock, flags);93if (ch == '\n')94ch = synth->procspeech;95if (unicode)96ret = synth->io_ops->synth_out_unicode(synth, ch);97else98ret = synth->io_ops->synth_out(synth, ch);99if (!ret) {100schedule_timeout(msecs_to_jiffies(full_time_val));101continue;102}103if (time_after_eq(jiffies, jiff_max) && (ch == SPACE)) {104spin_lock_irqsave(&speakup_info.spinlock, flags);105jiffy_delta_val = jiffy_delta->u.n.value;106delay_time_val = delay_time->u.n.value;107full_time_val = full_time->u.n.value;108spin_unlock_irqrestore(&speakup_info.spinlock, flags);109if (synth->io_ops->synth_out(synth, synth->procspeech))110schedule_timeout(111msecs_to_jiffies(delay_time_val));112else113schedule_timeout(114msecs_to_jiffies(full_time_val));115jiff_max = jiffies + jiffy_delta_val;116}117set_current_state(TASK_RUNNING);118spin_lock_irqsave(&speakup_info.spinlock, flags);119synth_buffer_getc();120spin_unlock_irqrestore(&speakup_info.spinlock, flags);121}122synth->io_ops->synth_out(synth, synth->procspeech);123}124125void spk_do_catch_up(struct spk_synth *synth)126{127_spk_do_catch_up(synth, 0);128}129EXPORT_SYMBOL_GPL(spk_do_catch_up);130131void spk_do_catch_up_unicode(struct spk_synth *synth)132{133_spk_do_catch_up(synth, 1);134}135EXPORT_SYMBOL_GPL(spk_do_catch_up_unicode);136137void spk_synth_flush(struct spk_synth *synth)138{139synth->io_ops->flush_buffer(synth);140synth->io_ops->synth_out(synth, synth->clear);141}142EXPORT_SYMBOL_GPL(spk_synth_flush);143144unsigned char spk_synth_get_index(struct spk_synth *synth)145{146return synth->io_ops->synth_in_nowait(synth);147}148EXPORT_SYMBOL_GPL(spk_synth_get_index);149150int spk_synth_is_alive_nop(struct spk_synth *synth)151{152synth->alive = 1;153return 1;154}155EXPORT_SYMBOL_GPL(spk_synth_is_alive_nop);156157int spk_synth_is_alive_restart(struct spk_synth *synth)158{159if (synth->alive)160return 1;161if (synth->io_ops->wait_for_xmitr(synth) > 0) {162/* restart */163synth->alive = 1;164synth_printf("%s", synth->init);165return 2; /* reenabled */166}167pr_warn("%s: can't restart synth\n", synth->long_name);168return 0;169}170EXPORT_SYMBOL_GPL(spk_synth_is_alive_restart);171172static void thread_wake_up(struct timer_list *unused)173{174wake_up_interruptible_all(&speakup_event);175}176177static DEFINE_TIMER(thread_timer, thread_wake_up);178179void synth_start(void)180{181struct var_t *trigger_time;182183if (!synth->alive) {184synth_buffer_clear();185return;186}187trigger_time = spk_get_var(TRIGGER);188if (!timer_pending(&thread_timer))189mod_timer(&thread_timer, jiffies +190msecs_to_jiffies(trigger_time->u.n.value));191}192193void spk_do_flush(void)194{195if (!synth)196return;197198speakup_info.flushing = 1;199synth_buffer_clear();200if (synth->alive) {201if (spk_pitch_shift) {202synth_printf("%s", spk_pitch_buff);203spk_pitch_shift = 0;204}205}206wake_up_interruptible_all(&speakup_event);207wake_up_process(speakup_task);208}209210void synth_write(const char *_buf, size_t count)211{212const unsigned char *buf = (const unsigned char *) _buf;213214while (count--)215synth_buffer_add(*buf++);216synth_start();217}218219/* Consume one utf-8 character from buf (that contains up to count bytes),220* returns the unicode codepoint if valid, -1 otherwise.221* In all cases, returns the number of consumed bytes in *consumed,222* and the minimum number of bytes that would be needed for the next character223* in *want.224*/225s32 synth_utf8_get(const char *buf, size_t count, size_t *consumed, size_t *want)226{227unsigned char c = buf[0];228int nbytes = 8 - fls(c ^ 0xff);229u32 value;230size_t i;231232switch (nbytes) {233case 8: /* 0xff */234case 7: /* 0xfe */235case 1: /* 0x80 */236/* Invalid, drop */237*consumed = 1;238*want = 1;239return -1;240241case 0:242/* ASCII, take as such */243*consumed = 1;244*want = 1;245return c;246247default:248/* 2..6-byte UTF-8 */249250if (count < nbytes) {251/* We don't have it all */252*consumed = 0;253*want = nbytes;254return -1;255}256257/* First byte */258value = c & ((1u << (7 - nbytes)) - 1);259260/* Other bytes */261for (i = 1; i < nbytes; i++) {262c = buf[i];263if ((c & 0xc0) != 0x80) {264/* Invalid, drop the head */265*consumed = i;266*want = 1;267return -1;268}269value = (value << 6) | (c & 0x3f);270}271272*consumed = nbytes;273*want = 1;274return value;275}276}277278void synth_writeu(const char *buf, size_t count)279{280size_t i, consumed, want;281282/* Convert to u16 */283for (i = 0; i < count; i++) {284s32 value;285286value = synth_utf8_get(buf + i, count - i, &consumed, &want);287if (value == -1) {288/* Invalid or incomplete */289290if (want > count - i)291/* We don't have it all, stop */292count = i;293294continue;295}296297if (value < 0x10000)298synth_buffer_add(value);299}300301synth_start();302}303304void synth_printf(const char *fmt, ...)305{306va_list args;307unsigned char buf[160];308int r;309310va_start(args, fmt);311r = vsnprintf(buf, sizeof(buf), fmt, args);312va_end(args);313if (r > sizeof(buf) - 1)314r = sizeof(buf) - 1;315316synth_writeu(buf, r);317}318EXPORT_SYMBOL_GPL(synth_printf);319320void synth_putwc(u16 wc)321{322synth_buffer_add(wc);323}324EXPORT_SYMBOL_GPL(synth_putwc);325326void synth_putwc_s(u16 wc)327{328synth_buffer_add(wc);329synth_start();330}331EXPORT_SYMBOL_GPL(synth_putwc_s);332333void synth_putws(const u16 *buf)334{335const u16 *p;336337for (p = buf; *p; p++)338synth_buffer_add(*p);339}340EXPORT_SYMBOL_GPL(synth_putws);341342void synth_putws_s(const u16 *buf)343{344synth_putws(buf);345synth_start();346}347EXPORT_SYMBOL_GPL(synth_putws_s);348349static int index_count;350static int sentence_count;351352void spk_reset_index_count(int sc)353{354static int first = 1;355356if (first)357first = 0;358else359synth->get_index(synth);360index_count = 0;361sentence_count = sc;362}363364int synth_supports_indexing(void)365{366if (synth->get_index)367return 1;368return 0;369}370371void synth_insert_next_index(int sent_num)372{373int out;374375if (synth->alive) {376if (sent_num == 0) {377synth->indexing.currindex++;378index_count++;379if (synth->indexing.currindex >380synth->indexing.highindex)381synth->indexing.currindex =382synth->indexing.lowindex;383}384385out = synth->indexing.currindex * 10 + sent_num;386synth_printf(synth->indexing.command, out, out);387}388}389390void spk_get_index_count(int *linecount, int *sentcount)391{392int ind = synth->get_index(synth);393394if (ind) {395sentence_count = ind % 10;396397if ((ind / 10) <= synth->indexing.currindex)398index_count = synth->indexing.currindex - (ind / 10);399else400index_count = synth->indexing.currindex401- synth->indexing.lowindex402+ synth->indexing.highindex - (ind / 10) + 1;403}404*sentcount = sentence_count;405*linecount = index_count;406}407408static struct resource synth_res;409410int synth_request_region(unsigned long start, unsigned long n)411{412struct resource *parent = &ioport_resource;413414memset(&synth_res, 0, sizeof(synth_res));415synth_res.name = synth->name;416synth_res.start = start;417synth_res.end = start + n - 1;418synth_res.flags = IORESOURCE_BUSY;419return request_resource(parent, &synth_res);420}421EXPORT_SYMBOL_GPL(synth_request_region);422423int synth_release_region(unsigned long start, unsigned long n)424{425return release_resource(&synth_res);426}427EXPORT_SYMBOL_GPL(synth_release_region);428429struct var_t synth_time_vars[] = {430{ DELAY, .u.n = {NULL, 100, 100, 2000, 0, 0, NULL } },431{ TRIGGER, .u.n = {NULL, 20, 10, 2000, 0, 0, NULL } },432{ JIFFY, .u.n = {NULL, 50, 20, 200, 0, 0, NULL } },433{ FULL, .u.n = {NULL, 400, 200, 60000, 0, 0, NULL } },434{ FLUSH, .u.n = {NULL, 4000, 10, 4000, 0, 0, NULL } },435V_LAST_VAR436};437438/* called by: speakup_init() */439int synth_init(char *synth_name)440{441int ret = 0;442struct spk_synth *tmp, *synth = NULL;443444if (!synth_name)445return 0;446447if (strcmp(synth_name, "none") == 0) {448mutex_lock(&spk_mutex);449synth_release();450mutex_unlock(&spk_mutex);451return 0;452}453454mutex_lock(&spk_mutex);455/* First, check if we already have it loaded. */456list_for_each_entry(tmp, &synths, node) {457if (strcmp(tmp->name, synth_name) == 0)458synth = tmp;459}460461/* If we got one, initialize it now. */462if (synth)463ret = do_synth_init(synth);464else465ret = -ENODEV;466mutex_unlock(&spk_mutex);467468return ret;469}470471/* called by: synth_add() */472static int do_synth_init(struct spk_synth *in_synth)473{474struct var_t *var;475476synth_release();477if (in_synth->checkval != SYNTH_CHECK)478return -EINVAL;479synth = in_synth;480synth->alive = 0;481pr_warn("synth probe\n");482if (synth->probe(synth) < 0) {483pr_warn("%s: device probe failed\n", in_synth->name);484synth = NULL;485return -ENODEV;486}487synth_time_vars[0].u.n.value =488synth_time_vars[0].u.n.default_val = synth->delay;489synth_time_vars[1].u.n.value =490synth_time_vars[1].u.n.default_val = synth->trigger;491synth_time_vars[2].u.n.value =492synth_time_vars[2].u.n.default_val = synth->jiffies;493synth_time_vars[3].u.n.value =494synth_time_vars[3].u.n.default_val = synth->full;495synth_time_vars[4].u.n.value =496synth_time_vars[4].u.n.default_val = synth->flush_time;497synth_printf("%s", synth->init);498for (var = synth->vars;499(var->var_id >= 0) && (var->var_id < MAXVARS); var++)500speakup_register_var(var);501if (!spk_quiet_boot)502synth_printf("%s found\n", synth->long_name);503if (synth->attributes.name &&504sysfs_create_group(speakup_kobj, &synth->attributes) < 0)505return -ENOMEM;506synth_flags = synth->flags;507wake_up_interruptible_all(&speakup_event);508if (speakup_task)509wake_up_process(speakup_task);510return 0;511}512513void synth_release(void)514{515struct var_t *var;516unsigned long flags;517518if (!synth)519return;520spin_lock_irqsave(&speakup_info.spinlock, flags);521pr_info("releasing synth %s\n", synth->name);522synth->alive = 0;523timer_delete(&thread_timer);524spin_unlock_irqrestore(&speakup_info.spinlock, flags);525if (synth->attributes.name)526sysfs_remove_group(speakup_kobj, &synth->attributes);527for (var = synth->vars; var->var_id != MAXVARS; var++)528speakup_unregister_var(var->var_id);529synth->release(synth);530synth = NULL;531}532533/* called by: all_driver_init() */534int synth_add(struct spk_synth *in_synth)535{536int status = 0;537struct spk_synth *tmp;538539mutex_lock(&spk_mutex);540541list_for_each_entry(tmp, &synths, node) {542if (tmp == in_synth) {543mutex_unlock(&spk_mutex);544return 0;545}546}547548if (in_synth->startup)549status = do_synth_init(in_synth);550551if (!status)552list_add_tail(&in_synth->node, &synths);553554mutex_unlock(&spk_mutex);555return status;556}557EXPORT_SYMBOL_GPL(synth_add);558559void synth_remove(struct spk_synth *in_synth)560{561mutex_lock(&spk_mutex);562if (synth == in_synth)563synth_release();564list_del(&in_synth->node);565module_status = 0;566mutex_unlock(&spk_mutex);567}568EXPORT_SYMBOL_GPL(synth_remove);569570struct spk_synth *synth_current(void)571{572return synth;573}574EXPORT_SYMBOL_GPL(synth_current);575576short spk_punc_masks[] = { 0, SOME, MOST, PUNC, PUNC | B_SYM };577578579