]>
Commit | Line | Data |
---|---|---|
1ab92da3 OK |
1 | #include <linux/types.h> |
2 | #include <linux/tty.h> | |
6b9ad1c7 OK |
3 | #include <linux/tty_flip.h> |
4 | #include <linux/slab.h> | |
1ab92da3 OK |
5 | |
6 | #include "speakup.h" | |
7 | #include "spk_types.h" | |
6b9ad1c7 | 8 | #include "spk_priv.h" |
1ab92da3 | 9 | |
6b9ad1c7 OK |
10 | struct spk_ldisc_data { |
11 | char buf; | |
12 | struct semaphore sem; | |
13 | bool buf_free; | |
14 | }; | |
15 | ||
16 | static struct spk_synth *spk_ttyio_synth; | |
1ab92da3 OK |
17 | static struct tty_struct *speakup_tty; |
18 | ||
19 | static int spk_ttyio_ldisc_open(struct tty_struct *tty) | |
20 | { | |
6b9ad1c7 OK |
21 | struct spk_ldisc_data *ldisc_data; |
22 | ||
1ab92da3 OK |
23 | if (tty->ops->write == NULL) |
24 | return -EOPNOTSUPP; | |
25 | speakup_tty = tty; | |
26 | ||
6b9ad1c7 OK |
27 | ldisc_data = kmalloc(sizeof(struct spk_ldisc_data), GFP_KERNEL); |
28 | if (!ldisc_data) { | |
29 | pr_err("speakup: Failed to allocate ldisc_data.\n"); | |
30 | return -ENOMEM; | |
31 | } | |
32 | ||
33 | sema_init(&ldisc_data->sem, 0); | |
34 | ldisc_data->buf_free = true; | |
35 | speakup_tty->disc_data = ldisc_data; | |
36 | ||
1ab92da3 OK |
37 | return 0; |
38 | } | |
39 | ||
40 | static void spk_ttyio_ldisc_close(struct tty_struct *tty) | |
41 | { | |
6b9ad1c7 | 42 | kfree(speakup_tty->disc_data); |
1ab92da3 OK |
43 | speakup_tty = NULL; |
44 | } | |
45 | ||
6b9ad1c7 OK |
46 | static int spk_ttyio_receive_buf2(struct tty_struct *tty, |
47 | const unsigned char *cp, char *fp, int count) | |
48 | { | |
49 | struct spk_ldisc_data *ldisc_data = tty->disc_data; | |
50 | ||
51 | if (spk_ttyio_synth->read_buff_add) { | |
52 | int i; | |
650b175d | 53 | |
6b9ad1c7 OK |
54 | for (i = 0; i < count; i++) |
55 | spk_ttyio_synth->read_buff_add(cp[i]); | |
56 | ||
57 | return count; | |
58 | } | |
59 | ||
60 | if (!ldisc_data->buf_free) | |
61 | /* ttyio_in will tty_schedule_flip */ | |
62 | return 0; | |
63 | ||
64 | /* Make sure the consumer has read buf before we have seen | |
fc0f0bd6 | 65 | * buf_free == true and overwrite buf */ |
6b9ad1c7 OK |
66 | mb(); |
67 | ||
68 | ldisc_data->buf = cp[0]; | |
69 | ldisc_data->buf_free = false; | |
70 | up(&ldisc_data->sem); | |
71 | ||
72 | return 1; | |
73 | } | |
74 | ||
1ab92da3 OK |
75 | static struct tty_ldisc_ops spk_ttyio_ldisc_ops = { |
76 | .owner = THIS_MODULE, | |
77 | .magic = TTY_LDISC_MAGIC, | |
78 | .name = "speakup_ldisc", | |
79 | .open = spk_ttyio_ldisc_open, | |
80 | .close = spk_ttyio_ldisc_close, | |
6b9ad1c7 | 81 | .receive_buf2 = spk_ttyio_receive_buf2, |
1ab92da3 OK |
82 | }; |
83 | ||
84 | static int spk_ttyio_out(struct spk_synth *in_synth, const char ch); | |
6b9ad1c7 OK |
85 | static void spk_ttyio_send_xchar(char ch); |
86 | static void spk_ttyio_tiocmset(unsigned int set, unsigned int clear); | |
87 | static unsigned char spk_ttyio_in(void); | |
88 | static unsigned char spk_ttyio_in_nowait(void); | |
1c597367 | 89 | static void spk_ttyio_flush_buffer(void); |
6b9ad1c7 | 90 | |
1ab92da3 OK |
91 | struct spk_io_ops spk_ttyio_ops = { |
92 | .synth_out = spk_ttyio_out, | |
6b9ad1c7 OK |
93 | .send_xchar = spk_ttyio_send_xchar, |
94 | .tiocmset = spk_ttyio_tiocmset, | |
95 | .synth_in = spk_ttyio_in, | |
96 | .synth_in_nowait = spk_ttyio_in_nowait, | |
1c597367 | 97 | .flush_buffer = spk_ttyio_flush_buffer, |
1ab92da3 OK |
98 | }; |
99 | EXPORT_SYMBOL_GPL(spk_ttyio_ops); | |
100 | ||
1c597367 OK |
101 | static inline void get_termios(struct tty_struct *tty, struct ktermios *out_termios) |
102 | { | |
103 | down_read(&tty->termios_rwsem); | |
104 | *out_termios = tty->termios; | |
105 | up_read(&tty->termios_rwsem); | |
106 | } | |
107 | ||
1ab92da3 OK |
108 | static int spk_ttyio_initialise_ldisc(int ser) |
109 | { | |
110 | int ret = 0; | |
111 | struct tty_struct *tty; | |
1c597367 | 112 | struct ktermios tmp_termios; |
1ab92da3 OK |
113 | |
114 | ret = tty_register_ldisc(N_SPEAKUP, &spk_ttyio_ldisc_ops); | |
115 | if (ret) { | |
116 | pr_err("Error registering line discipline.\n"); | |
117 | return ret; | |
118 | } | |
119 | ||
120 | if (ser < 0 || ser > (255 - 64)) { | |
121 | pr_err("speakup: Invalid ser param. Must be between 0 and 191 inclusive.\n"); | |
122 | return -EINVAL; | |
123 | } | |
124 | ||
125 | /* TODO: support more than ttyS* */ | |
126 | tty = tty_open_by_driver(MKDEV(4, (ser + 64)), NULL, NULL); | |
127 | if (IS_ERR(tty)) | |
128 | return PTR_ERR(tty); | |
129 | ||
130 | if (tty->ops->open) | |
131 | ret = tty->ops->open(tty, NULL); | |
132 | else | |
133 | ret = -ENODEV; | |
134 | ||
135 | if (ret) { | |
136 | tty_unlock(tty); | |
137 | return ret; | |
138 | } | |
139 | ||
140 | clear_bit(TTY_HUPPED, &tty->flags); | |
1c597367 OK |
141 | /* ensure hardware flow control is enabled */ |
142 | get_termios(tty, &tmp_termios); | |
143 | if (!(tmp_termios.c_cflag & CRTSCTS)) { | |
144 | tmp_termios.c_cflag |= CRTSCTS; | |
145 | tty_set_termios(tty, &tmp_termios); | |
146 | /* | |
147 | * check c_cflag to see if it's updated as tty_set_termios may not return | |
148 | * error even when no tty bits are changed by the request. | |
149 | */ | |
150 | get_termios(tty, &tmp_termios); | |
151 | if (!(tmp_termios.c_cflag & CRTSCTS)) | |
152 | pr_warn("speakup: Failed to set hardware flow control\n"); | |
153 | } | |
154 | ||
1ab92da3 OK |
155 | tty_unlock(tty); |
156 | ||
157 | ret = tty_set_ldisc(tty, N_SPEAKUP); | |
158 | ||
159 | return ret; | |
160 | } | |
161 | ||
162 | static int spk_ttyio_out(struct spk_synth *in_synth, const char ch) | |
163 | { | |
164 | if (in_synth->alive && speakup_tty && speakup_tty->ops->write) { | |
165 | int ret = speakup_tty->ops->write(speakup_tty, &ch, 1); | |
650b175d | 166 | |
1ab92da3 OK |
167 | if (ret == 0) |
168 | /* No room */ | |
169 | return 0; | |
170 | if (ret < 0) { | |
171 | pr_warn("%s: I/O error, deactivating speakup\n", in_synth->long_name); | |
172 | /* No synth any more, so nobody will restart TTYs, and we thus | |
173 | * need to do it ourselves. Now that there is no synth we can | |
174 | * let application flood anyway | |
175 | */ | |
176 | in_synth->alive = 0; | |
177 | speakup_start_ttys(); | |
178 | return 0; | |
179 | } | |
180 | return 1; | |
181 | } | |
182 | return 0; | |
183 | } | |
184 | ||
6b9ad1c7 OK |
185 | static void spk_ttyio_send_xchar(char ch) |
186 | { | |
187 | speakup_tty->ops->send_xchar(speakup_tty, ch); | |
188 | } | |
189 | ||
190 | static void spk_ttyio_tiocmset(unsigned int set, unsigned int clear) | |
191 | { | |
192 | speakup_tty->ops->tiocmset(speakup_tty, set, clear); | |
193 | } | |
194 | ||
195 | static unsigned char ttyio_in(int timeout) | |
196 | { | |
197 | struct spk_ldisc_data *ldisc_data = speakup_tty->disc_data; | |
198 | char rv; | |
199 | ||
200 | if (down_timeout(&ldisc_data->sem, usecs_to_jiffies(timeout)) == -ETIME) { | |
201 | if (timeout) | |
202 | pr_warn("spk_ttyio: timeout (%d) while waiting for input\n", | |
203 | timeout); | |
204 | return 0xff; | |
205 | } | |
206 | ||
207 | rv = ldisc_data->buf; | |
208 | /* Make sure we have read buf before we set buf_free to let | |
209 | * the producer overwrite it */ | |
210 | mb(); | |
211 | ldisc_data->buf_free = true; | |
212 | /* Let TTY push more characters */ | |
213 | tty_schedule_flip(speakup_tty->port); | |
214 | ||
215 | return rv; | |
216 | } | |
217 | ||
218 | static unsigned char spk_ttyio_in(void) | |
219 | { | |
220 | return ttyio_in(SPK_SYNTH_TIMEOUT); | |
221 | } | |
222 | ||
223 | static unsigned char spk_ttyio_in_nowait(void) | |
224 | { | |
e45423d7 | 225 | u8 rv = ttyio_in(0); |
6b9ad1c7 OK |
226 | |
227 | return (rv == 0xff) ? 0 : rv; | |
228 | } | |
229 | ||
1c597367 OK |
230 | static void spk_ttyio_flush_buffer(void) |
231 | { | |
011cca55 OK |
232 | if (speakup_tty->ops->flush_buffer) |
233 | speakup_tty->ops->flush_buffer(speakup_tty); | |
1c597367 OK |
234 | } |
235 | ||
1ab92da3 OK |
236 | int spk_ttyio_synth_probe(struct spk_synth *synth) |
237 | { | |
238 | int rv = spk_ttyio_initialise_ldisc(synth->ser); | |
239 | ||
240 | if (rv) | |
241 | return rv; | |
242 | ||
243 | synth->alive = 1; | |
6b9ad1c7 | 244 | spk_ttyio_synth = synth; |
1ab92da3 OK |
245 | |
246 | return 0; | |
247 | } | |
248 | EXPORT_SYMBOL_GPL(spk_ttyio_synth_probe); | |
249 | ||
250 | void spk_ttyio_release(void) | |
251 | { | |
1ab92da3 OK |
252 | if (!speakup_tty) |
253 | return; | |
254 | ||
255 | tty_lock(speakup_tty); | |
1ab92da3 OK |
256 | |
257 | if (speakup_tty->ops->close) | |
258 | speakup_tty->ops->close(speakup_tty, NULL); | |
259 | ||
260 | tty_ldisc_flush(speakup_tty); | |
261 | tty_unlock(speakup_tty); | |
262 | tty_ldisc_release(speakup_tty); | |
263 | } | |
264 | EXPORT_SYMBOL_GPL(spk_ttyio_release); | |
265 | ||
266 | const char *spk_ttyio_synth_immediate(struct spk_synth *synth, const char *buff) | |
267 | { | |
268 | u_char ch; | |
269 | ||
270 | while ((ch = *buff)) { | |
271 | if (ch == '\n') | |
272 | ch = synth->procspeech; | |
273 | if (tty_write_room(speakup_tty) < 1 || !synth->io_ops->synth_out(synth, ch)) | |
274 | return buff; | |
275 | buff++; | |
276 | } | |
277 | return NULL; | |
278 | } | |
279 | EXPORT_SYMBOL_GPL(spk_ttyio_synth_immediate); |