summaryrefslogtreecommitdiff
path: root/drivers/staging/speakup/spk_ttyio.c
blob: ee37550e59b309d91d570af870bb025d33e463b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <linux/types.h>
#include <linux/tty.h>

#include "speakup.h"
#include "spk_types.h"

static struct tty_struct *speakup_tty;

static int spk_ttyio_ldisc_open(struct tty_struct *tty)
{
	if (tty->ops->write == NULL)
		return -EOPNOTSUPP;
	speakup_tty = tty;

	return 0;
}

static void spk_ttyio_ldisc_close(struct tty_struct *tty)
{
	speakup_tty = NULL;
}

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,
};

static int spk_ttyio_out(struct spk_synth *in_synth, const char ch);
struct spk_io_ops spk_ttyio_ops = {
	.synth_out = spk_ttyio_out,
};
EXPORT_SYMBOL_GPL(spk_ttyio_ops);

static int spk_ttyio_initialise_ldisc(int ser)
{
	int ret = 0;
	struct tty_struct *tty;

	ret = tty_register_ldisc(N_SPEAKUP, &spk_ttyio_ldisc_ops);
	if (ret) {
		pr_err("Error registering line discipline.\n");
		return ret;
	}

	if (ser < 0 || ser > (255 - 64)) {
		pr_err("speakup: Invalid ser param. Must be between 0 and 191 inclusive.\n");
		return -EINVAL;
	}

	/* TODO: support more than ttyS* */
	tty = tty_open_by_driver(MKDEV(4, (ser +  64)), NULL, NULL);
	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);
	tty_unlock(tty);

	ret = tty_set_ldisc(tty, N_SPEAKUP);

	return ret;
}

static int spk_ttyio_out(struct spk_synth *in_synth, const char ch)
{
	if (in_synth->alive && speakup_tty && speakup_tty->ops->write) {
		int ret = speakup_tty->ops->write(speakup_tty, &ch, 1);
		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;
	}
	return 0;
}

int spk_ttyio_synth_probe(struct spk_synth *synth)
{
	int rv = spk_ttyio_initialise_ldisc(synth->ser);

	if (rv)
		return rv;

	synth->alive = 1;

	return 0;
}
EXPORT_SYMBOL_GPL(spk_ttyio_synth_probe);

void spk_ttyio_release(void)
{
	int idx;

	if (!speakup_tty)
		return;

	tty_lock(speakup_tty);
	idx = speakup_tty->index;

	if (speakup_tty->ops->close)
		speakup_tty->ops->close(speakup_tty, NULL);

	tty_ldisc_flush(speakup_tty);
	tty_unlock(speakup_tty);
	tty_ldisc_release(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);