Path: blob/master/drivers/accessibility/speakup/speakup_soft.c
26282 views
// SPDX-License-Identifier: GPL-2.0+1/* speakup_soft.c - speakup driver to register and make available2* a user space device for software synthesizers. written by: Kirk3* Reiser <[email protected]>4*5* Copyright (C) 2003 Kirk Reiser.6*7* this code is specifically written as a driver for the speakup screenreview8* package and is not a general device driver.9*/1011#include <linux/unistd.h>12#include <linux/miscdevice.h> /* for misc_register, and MISC_DYNAMIC_MINOR */13#include <linux/poll.h> /* for poll_wait() */1415/* schedule(), signal_pending(), TASK_INTERRUPTIBLE */16#include <linux/sched/signal.h>1718#include "spk_priv.h"19#include "speakup.h"2021#define DRV_VERSION "2.6"22#define PROCSPEECH 0x0d23#define CLEAR_SYNTH 0x182425static int softsynth_probe(struct spk_synth *synth);26static void softsynth_release(struct spk_synth *synth);27static int softsynth_is_alive(struct spk_synth *synth);28static int softsynth_adjust(struct spk_synth *synth, struct st_var_header *var);29static unsigned char get_index(struct spk_synth *synth);3031static struct miscdevice synth_device, synthu_device;32static int init_pos;33static int misc_registered;343536enum default_vars_id {37DIRECT_ID = 0, CAPS_START_ID, CAPS_STOP_ID,38PAUSE_ID, RATE_ID, PITCH_ID, INFLECTION_ID,39VOL_ID, TONE_ID, PUNCT_ID, VOICE_ID,40FREQUENCY_ID, V_LAST_VAR_ID,41NB_ID42};434445static struct var_t vars[NB_ID] = {4647[DIRECT_ID] = { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } },48[CAPS_START_ID] = { CAPS_START, .u.s = {"\x01+3p" } },49[CAPS_STOP_ID] = { CAPS_STOP, .u.s = {"\x01-3p" } },50[PAUSE_ID] = { PAUSE, .u.n = {"\x01P" } },51[RATE_ID] = { RATE, .u.n = {"\x01%ds", 2, 0, 9, 0, 0, NULL } },52[PITCH_ID] = { PITCH, .u.n = {"\x01%dp", 5, 0, 9, 0, 0, NULL } },53[INFLECTION_ID] = { INFLECTION, .u.n = {"\x01%dr", 5, 0, 9, 0, 0, NULL } },54[VOL_ID] = { VOL, .u.n = {"\x01%dv", 5, 0, 9, 0, 0, NULL } },55[TONE_ID] = { TONE, .u.n = {"\x01%dx", 1, 0, 2, 0, 0, NULL } },56[PUNCT_ID] = { PUNCT, .u.n = {"\x01%db", 0, 0, 3, 0, 0, NULL } },57[VOICE_ID] = { VOICE, .u.n = {"\x01%do", 0, 0, 7, 0, 0, NULL } },58[FREQUENCY_ID] = { FREQUENCY, .u.n = {"\x01%df", 5, 0, 9, 0, 0, NULL } },59V_LAST_VAR60};6162/* These attributes will appear in /sys/accessibility/speakup/soft. */6364static struct kobj_attribute caps_start_attribute =65__ATTR(caps_start, 0644, spk_var_show, spk_var_store);66static struct kobj_attribute caps_stop_attribute =67__ATTR(caps_stop, 0644, spk_var_show, spk_var_store);68static struct kobj_attribute freq_attribute =69__ATTR(freq, 0644, spk_var_show, spk_var_store);70static struct kobj_attribute pitch_attribute =71__ATTR(pitch, 0644, spk_var_show, spk_var_store);72static struct kobj_attribute inflection_attribute =73__ATTR(inflection, 0644, spk_var_show, spk_var_store);74static struct kobj_attribute punct_attribute =75__ATTR(punct, 0644, spk_var_show, spk_var_store);76static struct kobj_attribute rate_attribute =77__ATTR(rate, 0644, spk_var_show, spk_var_store);78static struct kobj_attribute tone_attribute =79__ATTR(tone, 0644, spk_var_show, spk_var_store);80static struct kobj_attribute voice_attribute =81__ATTR(voice, 0644, spk_var_show, spk_var_store);82static struct kobj_attribute vol_attribute =83__ATTR(vol, 0644, spk_var_show, spk_var_store);8485/*86* We should uncomment the following definition, when we agree on a87* method of passing a language designation to the software synthesizer.88* static struct kobj_attribute lang_attribute =89* __ATTR(lang, 0644, spk_var_show, spk_var_store);90*/9192static struct kobj_attribute delay_time_attribute =93__ATTR(delay_time, 0644, spk_var_show, spk_var_store);94static struct kobj_attribute direct_attribute =95__ATTR(direct, 0644, spk_var_show, spk_var_store);96static struct kobj_attribute full_time_attribute =97__ATTR(full_time, 0644, spk_var_show, spk_var_store);98static struct kobj_attribute jiffy_delta_attribute =99__ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store);100static struct kobj_attribute trigger_time_attribute =101__ATTR(trigger_time, 0644, spk_var_show, spk_var_store);102103/*104* Create a group of attributes so that we can create and destroy them all105* at once.106*/107static struct attribute *synth_attrs[] = {108&caps_start_attribute.attr,109&caps_stop_attribute.attr,110&freq_attribute.attr,111/* &lang_attribute.attr, */112&pitch_attribute.attr,113&inflection_attribute.attr,114&punct_attribute.attr,115&rate_attribute.attr,116&tone_attribute.attr,117&voice_attribute.attr,118&vol_attribute.attr,119&delay_time_attribute.attr,120&direct_attribute.attr,121&full_time_attribute.attr,122&jiffy_delta_attribute.attr,123&trigger_time_attribute.attr,124NULL, /* need to NULL terminate the list of attributes */125};126127static struct spk_synth synth_soft = {128.name = "soft",129.version = DRV_VERSION,130.long_name = "software synth",131.init = "\01@\x01\x31y\n",132.procspeech = PROCSPEECH,133.delay = 0,134.trigger = 0,135.jiffies = 0,136.full = 0,137.startup = SYNTH_START,138.checkval = SYNTH_CHECK,139.vars = vars,140.io_ops = NULL,141.probe = softsynth_probe,142.release = softsynth_release,143.synth_immediate = NULL,144.catch_up = NULL,145.flush = NULL,146.is_alive = softsynth_is_alive,147.synth_adjust = softsynth_adjust,148.read_buff_add = NULL,149.get_index = get_index,150.indexing = {151.command = "\x01%di",152.lowindex = 1,153.highindex = 5,154.currindex = 1,155},156.attributes = {157.attrs = synth_attrs,158.name = "soft",159},160};161162static char *get_initstring(void)163{164static char buf[40];165char *cp;166struct var_t *var;167size_t len;168size_t n;169170memset(buf, 0, sizeof(buf));171cp = buf;172len = sizeof(buf);173174var = synth_soft.vars;175while (var->var_id != MAXVARS) {176if (var->var_id != CAPS_START && var->var_id != CAPS_STOP &&177var->var_id != PAUSE && var->var_id != DIRECT) {178n = scnprintf(cp, len, var->u.n.synth_fmt,179var->u.n.value);180cp = cp + n;181len = len - n;182}183var++;184}185cp = cp + scnprintf(cp, len, "\n");186return buf;187}188189static int softsynth_open(struct inode *inode, struct file *fp)190{191unsigned long flags;192/*if ((fp->f_flags & O_ACCMODE) != O_RDONLY) */193/* return -EPERM; */194spin_lock_irqsave(&speakup_info.spinlock, flags);195if (synth_soft.alive) {196spin_unlock_irqrestore(&speakup_info.spinlock, flags);197return -EBUSY;198}199synth_soft.alive = 1;200spin_unlock_irqrestore(&speakup_info.spinlock, flags);201return 0;202}203204static int softsynth_close(struct inode *inode, struct file *fp)205{206unsigned long flags;207208spin_lock_irqsave(&speakup_info.spinlock, flags);209synth_soft.alive = 0;210init_pos = 0;211spin_unlock_irqrestore(&speakup_info.spinlock, flags);212/* Make sure we let applications go before leaving */213speakup_start_ttys();214return 0;215}216217static ssize_t softsynthx_read(struct file *fp, char __user *buf, size_t count,218loff_t *pos, int unicode)219{220int chars_sent = 0;221char __user *cp;222char *init;223size_t bytes_per_ch = unicode ? 3 : 1;224u16 ch;225int empty;226unsigned long flags;227DEFINE_WAIT(wait);228229if (count < bytes_per_ch)230return -EINVAL;231232spin_lock_irqsave(&speakup_info.spinlock, flags);233synth_soft.alive = 1;234while (1) {235prepare_to_wait(&speakup_event, &wait, TASK_INTERRUPTIBLE);236if (synth_current() == &synth_soft) {237if (!unicode)238synth_buffer_skip_nonlatin1();239if (!synth_buffer_empty() || speakup_info.flushing)240break;241}242spin_unlock_irqrestore(&speakup_info.spinlock, flags);243if (fp->f_flags & O_NONBLOCK) {244finish_wait(&speakup_event, &wait);245return -EAGAIN;246}247if (signal_pending(current)) {248finish_wait(&speakup_event, &wait);249return -ERESTARTSYS;250}251schedule();252spin_lock_irqsave(&speakup_info.spinlock, flags);253}254finish_wait(&speakup_event, &wait);255256cp = buf;257init = get_initstring();258259/* Keep 3 bytes available for a 16bit UTF-8-encoded character */260while (chars_sent <= count - bytes_per_ch) {261if (synth_current() != &synth_soft)262break;263if (speakup_info.flushing) {264speakup_info.flushing = 0;265ch = '\x18';266} else if (init[init_pos]) {267ch = init[init_pos++];268} else {269if (!unicode)270synth_buffer_skip_nonlatin1();271if (synth_buffer_empty())272break;273ch = synth_buffer_getc();274}275spin_unlock_irqrestore(&speakup_info.spinlock, flags);276277if ((!unicode && ch < 0x100) || (unicode && ch < 0x80)) {278u_char c = ch;279280if (copy_to_user(cp, &c, 1))281return -EFAULT;282283chars_sent++;284cp++;285} else if (unicode && ch < 0x800) {286u_char s[2] = {2870xc0 | (ch >> 6),2880x80 | (ch & 0x3f)289};290291if (copy_to_user(cp, s, sizeof(s)))292return -EFAULT;293294chars_sent += sizeof(s);295cp += sizeof(s);296} else if (unicode) {297u_char s[3] = {2980xe0 | (ch >> 12),2990x80 | ((ch >> 6) & 0x3f),3000x80 | (ch & 0x3f)301};302303if (copy_to_user(cp, s, sizeof(s)))304return -EFAULT;305306chars_sent += sizeof(s);307cp += sizeof(s);308}309310spin_lock_irqsave(&speakup_info.spinlock, flags);311}312*pos += chars_sent;313empty = synth_buffer_empty();314spin_unlock_irqrestore(&speakup_info.spinlock, flags);315if (empty) {316speakup_start_ttys();317*pos = 0;318}319return chars_sent;320}321322static ssize_t softsynth_read(struct file *fp, char __user *buf, size_t count,323loff_t *pos)324{325return softsynthx_read(fp, buf, count, pos, 0);326}327328static ssize_t softsynthu_read(struct file *fp, char __user *buf, size_t count,329loff_t *pos)330{331return softsynthx_read(fp, buf, count, pos, 1);332}333334static int last_index;335336static ssize_t softsynth_write(struct file *fp, const char __user *buf,337size_t count, loff_t *pos)338{339unsigned long supplied_index = 0;340int converted;341342converted = kstrtoul_from_user(buf, count, 0, &supplied_index);343344if (converted < 0)345return converted;346347last_index = supplied_index;348return count;349}350351static __poll_t softsynth_poll(struct file *fp, struct poll_table_struct *wait)352{353unsigned long flags;354__poll_t ret = 0;355356poll_wait(fp, &speakup_event, wait);357358spin_lock_irqsave(&speakup_info.spinlock, flags);359if (synth_current() == &synth_soft &&360(!synth_buffer_empty() || speakup_info.flushing))361ret = EPOLLIN | EPOLLRDNORM;362spin_unlock_irqrestore(&speakup_info.spinlock, flags);363return ret;364}365366static unsigned char get_index(struct spk_synth *synth)367{368int rv;369370rv = last_index;371last_index = 0;372return rv;373}374375static const struct file_operations softsynth_fops = {376.owner = THIS_MODULE,377.poll = softsynth_poll,378.read = softsynth_read,379.write = softsynth_write,380.open = softsynth_open,381.release = softsynth_close,382};383384static const struct file_operations softsynthu_fops = {385.owner = THIS_MODULE,386.poll = softsynth_poll,387.read = softsynthu_read,388.write = softsynth_write,389.open = softsynth_open,390.release = softsynth_close,391};392393static int softsynth_probe(struct spk_synth *synth)394{395if (misc_registered != 0)396return 0;397memset(&synth_device, 0, sizeof(synth_device));398synth_device.minor = MISC_DYNAMIC_MINOR;399synth_device.name = "softsynth";400synth_device.fops = &softsynth_fops;401if (misc_register(&synth_device)) {402pr_warn("Couldn't initialize miscdevice /dev/softsynth.\n");403return -ENODEV;404}405406memset(&synthu_device, 0, sizeof(synthu_device));407synthu_device.minor = MISC_DYNAMIC_MINOR;408synthu_device.name = "softsynthu";409synthu_device.fops = &softsynthu_fops;410if (misc_register(&synthu_device)) {411misc_deregister(&synth_device);412pr_warn("Couldn't initialize miscdevice /dev/softsynthu.\n");413return -ENODEV;414}415416misc_registered = 1;417pr_info("initialized device: /dev/softsynth, node (MAJOR 10, MINOR %d)\n",418synth_device.minor);419pr_info("initialized device: /dev/softsynthu, node (MAJOR 10, MINOR %d)\n",420synthu_device.minor);421return 0;422}423424static void softsynth_release(struct spk_synth *synth)425{426misc_deregister(&synth_device);427misc_deregister(&synthu_device);428misc_registered = 0;429pr_info("unregistered /dev/softsynth\n");430pr_info("unregistered /dev/softsynthu\n");431}432433static int softsynth_is_alive(struct spk_synth *synth)434{435if (synth_soft.alive)436return 1;437return 0;438}439440static int softsynth_adjust(struct spk_synth *synth, struct st_var_header *var)441{442struct st_var_header *punc_level_var;443struct var_t *var_data;444445if (var->var_id != PUNC_LEVEL)446return 0;447448/* We want to set the the speech synthesis punctuation level449* accordingly, so it properly tunes speaking A_PUNC characters */450var_data = var->data;451if (!var_data)452return 0;453punc_level_var = spk_get_var_header(PUNCT);454if (!punc_level_var)455return 0;456spk_set_num_var(var_data->u.n.value, punc_level_var, E_SET);457458return 1;459}460461module_param_named(start, synth_soft.startup, short, 0444);462module_param_named(direct, vars[DIRECT_ID].u.n.default_val, int, 0444);463module_param_named(rate, vars[RATE_ID].u.n.default_val, int, 0444);464module_param_named(pitch, vars[PITCH_ID].u.n.default_val, int, 0444);465module_param_named(inflection, vars[INFLECTION_ID].u.n.default_val, int, 0444);466module_param_named(vol, vars[VOL_ID].u.n.default_val, int, 0444);467module_param_named(tone, vars[TONE_ID].u.n.default_val, int, 0444);468module_param_named(punct, vars[PUNCT_ID].u.n.default_val, int, 0444);469module_param_named(voice, vars[VOICE_ID].u.n.default_val, int, 0444);470module_param_named(frequency, vars[FREQUENCY_ID].u.n.default_val, int, 0444);471472473474MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded.");475MODULE_PARM_DESC(direct, "Set the direct variable on load.");476MODULE_PARM_DESC(rate, "Sets the rate of the synthesizer.");477MODULE_PARM_DESC(pitch, "Sets the pitch of the synthesizer.");478MODULE_PARM_DESC(inflection, "Sets the inflection of the synthesizer.");479MODULE_PARM_DESC(vol, "Sets the volume of the speech synthesizer.");480MODULE_PARM_DESC(tone, "Sets the tone of the speech synthesizer.");481MODULE_PARM_DESC(punct, "Sets the amount of punctuation spoken by the synthesizer.");482MODULE_PARM_DESC(voice, "Sets the voice used by the synthesizer.");483MODULE_PARM_DESC(frequency, "Sets the frequency of speech synthesizer.");484485module_spk_synth(synth_soft);486487MODULE_AUTHOR("Kirk Reiser <[email protected]>");488MODULE_DESCRIPTION("Speakup userspace software synthesizer support");489MODULE_LICENSE("GPL");490MODULE_VERSION(DRV_VERSION);491492493