Path: blob/master/drivers/accessibility/speakup/spk_ttyio.c
26282 views
// SPDX-License-Identifier: GPL-2.01#include <linux/types.h>2#include <linux/tty.h>3#include <linux/tty_flip.h>4#include <linux/slab.h>56#include "speakup.h"7#include "spk_types.h"8#include "spk_priv.h"910struct spk_ldisc_data {11char buf;12struct completion completion;13bool buf_free;14struct spk_synth *synth;15};1617/*18* This allows to catch within spk_ttyio_ldisc_open whether it is getting set19* on for a speakup-driven device.20*/21static struct tty_struct *speakup_tty;22/* This mutex serializes the use of such global speakup_tty variable */23static DEFINE_MUTEX(speakup_tty_mutex);2425static int ser_to_dev(int ser, dev_t *dev_no)26{27if (ser < 0 || ser > (255 - 64)) {28pr_err("speakup: Invalid ser param. Must be between 0 and 191 inclusive.\n");29return -EINVAL;30}3132*dev_no = MKDEV(4, (64 + ser));33return 0;34}3536static int get_dev_to_use(struct spk_synth *synth, dev_t *dev_no)37{38/* use ser only when dev is not specified */39if (strcmp(synth->dev_name, SYNTH_DEFAULT_DEV) ||40synth->ser == SYNTH_DEFAULT_SER)41return tty_dev_name_to_number(synth->dev_name, dev_no);4243return ser_to_dev(synth->ser, dev_no);44}4546static int spk_ttyio_ldisc_open(struct tty_struct *tty)47{48struct spk_ldisc_data *ldisc_data;4950if (tty != speakup_tty)51/* Somebody tried to use this line discipline outside speakup */52return -ENODEV;5354if (!tty->ops->write)55return -EOPNOTSUPP;5657ldisc_data = kmalloc(sizeof(*ldisc_data), GFP_KERNEL);58if (!ldisc_data)59return -ENOMEM;6061init_completion(&ldisc_data->completion);62ldisc_data->buf_free = true;63tty->disc_data = ldisc_data;6465return 0;66}6768static void spk_ttyio_ldisc_close(struct tty_struct *tty)69{70kfree(tty->disc_data);71}7273static size_t spk_ttyio_receive_buf2(struct tty_struct *tty, const u8 *cp,74const u8 *fp, size_t count)75{76struct spk_ldisc_data *ldisc_data = tty->disc_data;77struct spk_synth *synth = ldisc_data->synth;7879if (synth->read_buff_add) {80unsigned int i;8182for (i = 0; i < count; i++)83synth->read_buff_add(cp[i]);8485return count;86}8788if (!ldisc_data->buf_free)89/* ttyio_in will tty_flip_buffer_push */90return 0;9192/* Make sure the consumer has read buf before we have seen93* buf_free == true and overwrite buf94*/95mb();9697ldisc_data->buf = cp[0];98ldisc_data->buf_free = false;99complete(&ldisc_data->completion);100101return 1;102}103104static struct tty_ldisc_ops spk_ttyio_ldisc_ops = {105.owner = THIS_MODULE,106.num = N_SPEAKUP,107.name = "speakup_ldisc",108.open = spk_ttyio_ldisc_open,109.close = spk_ttyio_ldisc_close,110.receive_buf2 = spk_ttyio_receive_buf2,111};112113static int spk_ttyio_out(struct spk_synth *in_synth, const char ch);114static int spk_ttyio_out_unicode(struct spk_synth *in_synth, u16 ch);115static void spk_ttyio_send_xchar(struct spk_synth *in_synth, char ch);116static void spk_ttyio_tiocmset(struct spk_synth *in_synth, unsigned int set, unsigned int clear);117static unsigned char spk_ttyio_in(struct spk_synth *in_synth);118static unsigned char spk_ttyio_in_nowait(struct spk_synth *in_synth);119static void spk_ttyio_flush_buffer(struct spk_synth *in_synth);120static int spk_ttyio_wait_for_xmitr(struct spk_synth *in_synth);121122struct spk_io_ops spk_ttyio_ops = {123.synth_out = spk_ttyio_out,124.synth_out_unicode = spk_ttyio_out_unicode,125.send_xchar = spk_ttyio_send_xchar,126.tiocmset = spk_ttyio_tiocmset,127.synth_in = spk_ttyio_in,128.synth_in_nowait = spk_ttyio_in_nowait,129.flush_buffer = spk_ttyio_flush_buffer,130.wait_for_xmitr = spk_ttyio_wait_for_xmitr,131};132EXPORT_SYMBOL_GPL(spk_ttyio_ops);133134static inline void get_termios(struct tty_struct *tty,135struct ktermios *out_termios)136{137down_read(&tty->termios_rwsem);138*out_termios = tty->termios;139up_read(&tty->termios_rwsem);140}141142static int spk_ttyio_initialise_ldisc(struct spk_synth *synth)143{144int ret = 0;145struct tty_struct *tty;146struct ktermios tmp_termios;147dev_t dev;148149ret = get_dev_to_use(synth, &dev);150if (ret)151return ret;152153tty = tty_kopen_exclusive(dev);154if (IS_ERR(tty))155return PTR_ERR(tty);156157if (tty->ops->open)158ret = tty->ops->open(tty, NULL);159else160ret = -ENODEV;161162if (ret) {163tty_unlock(tty);164return ret;165}166167clear_bit(TTY_HUPPED, &tty->flags);168/* ensure hardware flow control is enabled */169get_termios(tty, &tmp_termios);170if (!(tmp_termios.c_cflag & CRTSCTS)) {171tmp_termios.c_cflag |= CRTSCTS;172tty_set_termios(tty, &tmp_termios);173/*174* check c_cflag to see if it's updated as tty_set_termios175* may not return error even when no tty bits are176* changed by the request.177*/178get_termios(tty, &tmp_termios);179if (!(tmp_termios.c_cflag & CRTSCTS))180pr_warn("speakup: Failed to set hardware flow control\n");181}182183tty_unlock(tty);184185mutex_lock(&speakup_tty_mutex);186speakup_tty = tty;187ret = tty_set_ldisc(tty, N_SPEAKUP);188speakup_tty = NULL;189mutex_unlock(&speakup_tty_mutex);190191if (!ret) {192/* Success */193struct spk_ldisc_data *ldisc_data = tty->disc_data;194195ldisc_data->synth = synth;196synth->dev = tty;197return 0;198}199200pr_err("speakup: Failed to set N_SPEAKUP on tty\n");201202tty_lock(tty);203if (tty->ops->close)204tty->ops->close(tty, NULL);205tty_unlock(tty);206207tty_kclose(tty);208209return ret;210}211212void spk_ttyio_register_ldisc(void)213{214if (tty_register_ldisc(&spk_ttyio_ldisc_ops))215pr_warn("speakup: Error registering line discipline. Most synths won't work.\n");216}217218void spk_ttyio_unregister_ldisc(void)219{220tty_unregister_ldisc(&spk_ttyio_ldisc_ops);221}222223static int spk_ttyio_out(struct spk_synth *in_synth, const char ch)224{225struct tty_struct *tty = in_synth->dev;226int ret;227228if (!in_synth->alive || !tty->ops->write)229return 0;230231ret = tty->ops->write(tty, &ch, 1);232233if (ret == 0)234/* No room */235return 0;236237if (ret > 0)238/* Success */239return 1;240241pr_warn("%s: I/O error, deactivating speakup\n",242in_synth->long_name);243/* No synth any more, so nobody will restart TTYs,244* and we thus need to do it ourselves. Now that there245* is no synth we can let application flood anyway246*/247in_synth->alive = 0;248speakup_start_ttys();249return 0;250}251252static int spk_ttyio_out_unicode(struct spk_synth *in_synth, u16 ch)253{254int ret;255256if (ch < 0x80) {257ret = spk_ttyio_out(in_synth, ch);258} else if (ch < 0x800) {259ret = spk_ttyio_out(in_synth, 0xc0 | (ch >> 6));260ret &= spk_ttyio_out(in_synth, 0x80 | (ch & 0x3f));261} else {262ret = spk_ttyio_out(in_synth, 0xe0 | (ch >> 12));263ret &= spk_ttyio_out(in_synth, 0x80 | ((ch >> 6) & 0x3f));264ret &= spk_ttyio_out(in_synth, 0x80 | (ch & 0x3f));265}266return ret;267}268269static void spk_ttyio_send_xchar(struct spk_synth *in_synth, char ch)270{271struct tty_struct *tty = in_synth->dev;272273if (tty->ops->send_xchar)274tty->ops->send_xchar(tty, ch);275}276277static void spk_ttyio_tiocmset(struct spk_synth *in_synth, unsigned int set, unsigned int clear)278{279struct tty_struct *tty = in_synth->dev;280281if (tty->ops->tiocmset)282tty->ops->tiocmset(tty, set, clear);283}284285static int spk_ttyio_wait_for_xmitr(struct spk_synth *in_synth)286{287return 1;288}289290static unsigned char ttyio_in(struct spk_synth *in_synth, int timeout)291{292struct tty_struct *tty = in_synth->dev;293struct spk_ldisc_data *ldisc_data = tty->disc_data;294char rv;295296if (!timeout) {297if (!try_wait_for_completion(&ldisc_data->completion))298return 0xff;299} else if (wait_for_completion_timeout(&ldisc_data->completion,300usecs_to_jiffies(timeout)) == 0) {301pr_warn("spk_ttyio: timeout (%d) while waiting for input\n",302timeout);303return 0xff;304}305306rv = ldisc_data->buf;307/* Make sure we have read buf before we set buf_free to let308* the producer overwrite it309*/310mb();311ldisc_data->buf_free = true;312/* Let TTY push more characters */313tty_flip_buffer_push(tty->port);314315return rv;316}317318static unsigned char spk_ttyio_in(struct spk_synth *in_synth)319{320return ttyio_in(in_synth, SPK_SYNTH_TIMEOUT);321}322323static unsigned char spk_ttyio_in_nowait(struct spk_synth *in_synth)324{325u8 rv = ttyio_in(in_synth, 0);326327return (rv == 0xff) ? 0 : rv;328}329330static void spk_ttyio_flush_buffer(struct spk_synth *in_synth)331{332struct tty_struct *tty = in_synth->dev;333334if (tty->ops->flush_buffer)335tty->ops->flush_buffer(tty);336}337338int spk_ttyio_synth_probe(struct spk_synth *synth)339{340int rv = spk_ttyio_initialise_ldisc(synth);341342if (rv)343return rv;344345synth->alive = 1;346347return 0;348}349EXPORT_SYMBOL_GPL(spk_ttyio_synth_probe);350351void spk_ttyio_release(struct spk_synth *in_synth)352{353struct tty_struct *tty = in_synth->dev;354355if (tty == NULL)356return;357358tty_lock(tty);359360if (tty->ops->close)361tty->ops->close(tty, NULL);362363tty_ldisc_flush(tty);364tty_unlock(tty);365tty_kclose(tty);366367in_synth->dev = NULL;368}369EXPORT_SYMBOL_GPL(spk_ttyio_release);370371const char *spk_ttyio_synth_immediate(struct spk_synth *in_synth, const char *buff)372{373struct tty_struct *tty = in_synth->dev;374u_char ch;375376while ((ch = *buff)) {377if (ch == '\n')378ch = in_synth->procspeech;379if (tty_write_room(tty) < 1 ||380!in_synth->io_ops->synth_out(in_synth, ch))381return buff;382buff++;383}384return NULL;385}386EXPORT_SYMBOL_GPL(spk_ttyio_synth_immediate);387388389