summaryrefslogtreecommitdiff
path: root/drivers/staging/speakup/synth.c
diff options
context:
space:
mode:
authorWilliam Hubbs <w.d.hubbs@gmail.com>2010-10-07 13:20:02 -0500
committerGreg Kroah-Hartman <gregkh@suse.de>2010-10-07 19:22:31 -0700
commitc6e3fd22cd538365bfeb82997d5b89562e077d42 (patch)
tree5eaa170c9003abc7b24ab340ccabe889cba47992 /drivers/staging/speakup/synth.c
parente59fe083f683ca2ca56abefad290d110808a6fb5 (diff)
Staging: add speakup to the staging directory
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. The authors and maintainers of this code include the following: Kirk Reiser, Andy Berdan, John Covici, Brian and David Borowski, Christopher Brannon, Samuel Thibault and William Hubbs. Signed-off-by: William Hubbs <w.d.hubbs@gmail.com> Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/staging/speakup/synth.c')
-rw-r--r--drivers/staging/speakup/synth.c458
1 files changed, 458 insertions, 0 deletions
diff --git a/drivers/staging/speakup/synth.c b/drivers/staging/speakup/synth.c
new file mode 100644
index 000000000000..7f74f80be699
--- /dev/null
+++ b/drivers/staging/speakup/synth.c
@@ -0,0 +1,458 @@
+#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"
+
+#define MAXSYNTHS 16 /* Max number of synths in array. */
+static struct spk_synth *synths[MAXSYNTHS];
+struct spk_synth *synth = NULL;
+char pitch_buff[32] = "";
+static int module_status;
+int quiet_boot;
+
+struct speakup_info_t speakup_info = {
+ .spinlock = SPIN_LOCK_UNLOCKED,
+ .flushing = 0,
+};
+EXPORT_SYMBOL_GPL(speakup_info);
+
+static int do_synth_init(struct spk_synth *in_synth);
+
+int serial_synth_probe(struct spk_synth *synth)
+{
+ struct serial_state *ser;
+ int failed = 0;
+
+ if ((synth->ser >= SPK_LO_TTY) && (synth->ser <= SPK_HI_TTY)) {
+ ser = spk_serial_init(synth->ser);
+ if (ser == NULL) {
+ 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(serial_synth_probe);
+
+/* 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 mecanism, the driver can
+ * adapt the loop the way they prefer.
+ */
+void spk_do_catch_up(struct spk_synth *synth)
+{
+ u_char 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;
+
+ jiffy_delta = get_var(JIFFY);
+ full_time = get_var(FULL);
+ delay_time = get_var(DELAY);
+
+ spk_lock(flags);
+ jiffy_delta_val = jiffy_delta->u.n.value;
+ spk_unlock(flags);
+
+ jiff_max = jiffies + jiffy_delta_val;
+ while (!kthread_should_stop()) {
+ spk_lock(flags);
+ if (speakup_info.flushing) {
+ speakup_info.flushing = 0;
+ spk_unlock(flags);
+ synth->flush(synth);
+ continue;
+ }
+ if (synth_buffer_empty()) {
+ spk_unlock(flags);
+ break;
+ }
+ ch = synth_buffer_peek();
+ set_current_state(TASK_INTERRUPTIBLE);
+ full_time_val = full_time->u.n.value;
+ spk_unlock(flags);
+ if (ch == '\n')
+ ch = synth->procspeech;
+ if (!spk_serial_out(ch)) {
+ schedule_timeout(msecs_to_jiffies(full_time_val));
+ continue;
+ }
+ if ((jiffies >= jiff_max) && (ch == SPACE)) {
+ spk_lock(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;
+ spk_unlock(flags);
+ if (spk_serial_out(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);
+ spk_lock(flags);
+ synth_buffer_getc();
+ spk_unlock(flags);
+ }
+ spk_serial_out(synth->procspeech);
+}
+EXPORT_SYMBOL_GPL(spk_do_catch_up);
+
+const char *spk_synth_immediate(struct spk_synth *synth, const char *buff)
+{
+ u_char ch;
+ while ((ch = *buff)) {
+ if (ch == '\n')
+ ch = synth->procspeech;
+ if (wait_for_xmitr())
+ outb(ch, speakup_info.port_tts);
+ else
+ return buff;
+ buff++;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spk_synth_immediate);
+
+void spk_synth_flush(struct spk_synth *synth)
+{
+ spk_serial_out(synth->clear);
+}
+EXPORT_SYMBOL_GPL(spk_synth_flush);
+
+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->alive && wait_for_xmitr() > 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(u_long data)
+{
+ wake_up_interruptible_all(&speakup_event);
+}
+
+static DEFINE_TIMER(thread_timer, thread_wake_up, 0, 0);
+
+void synth_start(void)
+{
+ struct var_t *trigger_time;
+
+ if (!synth->alive) {
+ synth_buffer_clear();
+ return;
+ }
+ trigger_time = get_var(TRIGGER);
+ if (!timer_pending(&thread_timer))
+ mod_timer(&thread_timer, jiffies + msecs_to_jiffies(trigger_time->u.n.value));
+}
+
+void do_flush(void)
+{
+ speakup_info.flushing = 1;
+ synth_buffer_clear();
+ if (synth->alive) {
+ if (pitch_shift) {
+ synth_printf("%s", pitch_buff);
+ 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);
+
+static int index_count = 0;
+static int sentence_count = 0;
+
+void reset_index_count(int sc)
+{
+ static int first = 1;
+ if (first)
+ first = 0;
+ else
+ synth->get_index();
+ index_count = 0;
+ sentence_count = sc;
+}
+
+int synth_supports_indexing(void)
+{
+ if (synth->get_index != NULL)
+ 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 get_index_count(int *linecount, int *sentcount)
+{
+ int ind = synth->get_index();
+ 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 i;
+ int ret = 0;
+ struct spk_synth *synth = NULL;
+
+ if (synth_name == NULL)
+ 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. */
+ for (i = 0; synths[i] != NULL && i < MAXSYNTHS; i++)
+ if (strcmp(synths[i]->name, synth_name) == 0)
+ synth = synths[i];
+
+ /* 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 (!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 == NULL)
+ return;
+ spk_lock(flags);
+ pr_info("releasing synth %s\n", synth->name);
+ synth->alive = 0;
+ del_timer(&thread_timer);
+ spk_unlock(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);
+ stop_serial_interrupt();
+ synth->release();
+ synth = NULL;
+}
+
+/* called by: all_driver_init() */
+int synth_add(struct spk_synth *in_synth)
+{
+ int i;
+ int status = 0;
+ mutex_lock(&spk_mutex);
+ for (i = 0; synths[i] != NULL && i < MAXSYNTHS; i++)
+ /* synth_remove() is responsible for rotating the array down */
+ if (in_synth == synths[i]) {
+ mutex_unlock(&spk_mutex);
+ return 0;
+ }
+ if (i == MAXSYNTHS) {
+ pr_warn("Error: attempting to add a synth past end of array\n");
+ mutex_unlock(&spk_mutex);
+ return -1;
+ }
+ synths[i++] = in_synth;
+ synths[i] = NULL;
+ if (in_synth->startup)
+ status = do_synth_init(in_synth);
+ mutex_unlock(&spk_mutex);
+ return status;
+}
+EXPORT_SYMBOL_GPL(synth_add);
+
+void synth_remove(struct spk_synth *in_synth)
+{
+ int i;
+ mutex_lock(&spk_mutex);
+ if (synth == in_synth)
+ synth_release();
+ for (i = 0; synths[i] != NULL; i++) {
+ if (in_synth == synths[i])
+ break;
+ }
+ for ( ; synths[i] != NULL; i++) /* compress table */
+ synths[i] = synths[i+1];
+ module_status = 0;
+ mutex_unlock(&spk_mutex);
+}
+EXPORT_SYMBOL_GPL(synth_remove);
+
+short punc_masks[] = { 0, SOME, MOST, PUNC, PUNC|B_SYM };