1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* speakup_soft.c - speakup driver to register and make available |
3 | * a user space device for software synthesizers. written by: Kirk |
4 | * Reiser <kirk@braille.uwo.ca> |
5 | * |
6 | * Copyright (C) 2003 Kirk Reiser. |
7 | * |
8 | * this code is specifically written as a driver for the speakup screenreview |
9 | * package and is not a general device driver. |
10 | */ |
11 | |
12 | #include <linux/unistd.h> |
13 | #include <linux/miscdevice.h> /* for misc_register, and MISC_DYNAMIC_MINOR */ |
14 | #include <linux/poll.h> /* for poll_wait() */ |
15 | |
16 | /* schedule(), signal_pending(), TASK_INTERRUPTIBLE */ |
17 | #include <linux/sched/signal.h> |
18 | |
19 | #include "spk_priv.h" |
20 | #include "speakup.h" |
21 | |
22 | #define DRV_VERSION "2.6" |
23 | #define PROCSPEECH 0x0d |
24 | #define CLEAR_SYNTH 0x18 |
25 | |
26 | static int softsynth_probe(struct spk_synth *synth); |
27 | static void softsynth_release(struct spk_synth *synth); |
28 | static int softsynth_is_alive(struct spk_synth *synth); |
29 | static int softsynth_adjust(struct spk_synth *synth, struct st_var_header *var); |
30 | static unsigned char get_index(struct spk_synth *synth); |
31 | |
32 | static struct miscdevice synth_device, synthu_device; |
33 | static int init_pos; |
34 | static int misc_registered; |
35 | |
36 | |
37 | enum default_vars_id { |
38 | DIRECT_ID = 0, CAPS_START_ID, CAPS_STOP_ID, |
39 | PAUSE_ID, RATE_ID, PITCH_ID, INFLECTION_ID, |
40 | VOL_ID, TONE_ID, PUNCT_ID, VOICE_ID, |
41 | FREQUENCY_ID, V_LAST_VAR_ID, |
42 | NB_ID |
43 | }; |
44 | |
45 | |
46 | static struct var_t vars[NB_ID] = { |
47 | |
48 | [DIRECT_ID] = { DIRECT, .u.n = {NULL, 0, 0, 1, 0, 0, NULL } }, |
49 | [CAPS_START_ID] = { .var_id: CAPS_START, .u.s = {"\x01+3p" } }, |
50 | [CAPS_STOP_ID] = { .var_id: CAPS_STOP, .u.s = {"\x01-3p" } }, |
51 | [PAUSE_ID] = { .var_id: PAUSE, .u.n = {"\x01P" } }, |
52 | [RATE_ID] = { .var_id: RATE, .u.n = {"\x01%ds" , 2, 0, 9, 0, 0, NULL } }, |
53 | [PITCH_ID] = { .var_id: PITCH, .u.n = {"\x01%dp" , 5, 0, 9, 0, 0, NULL } }, |
54 | [INFLECTION_ID] = { .var_id: INFLECTION, .u.n = {"\x01%dr" , 5, 0, 9, 0, 0, NULL } }, |
55 | [VOL_ID] = { .var_id: VOL, .u.n = {"\x01%dv" , 5, 0, 9, 0, 0, NULL } }, |
56 | [TONE_ID] = { .var_id: TONE, .u.n = {"\x01%dx" , 1, 0, 2, 0, 0, NULL } }, |
57 | [PUNCT_ID] = { .var_id: PUNCT, .u.n = {"\x01%db" , 0, 0, 3, 0, 0, NULL } }, |
58 | [VOICE_ID] = { .var_id: VOICE, .u.n = {"\x01%do" , 0, 0, 7, 0, 0, NULL } }, |
59 | [FREQUENCY_ID] = { .var_id: FREQUENCY, .u.n = {"\x01%df" , 5, 0, 9, 0, 0, NULL } }, |
60 | V_LAST_VAR |
61 | }; |
62 | |
63 | /* These attributes will appear in /sys/accessibility/speakup/soft. */ |
64 | |
65 | static struct kobj_attribute caps_start_attribute = |
66 | __ATTR(caps_start, 0644, spk_var_show, spk_var_store); |
67 | static struct kobj_attribute caps_stop_attribute = |
68 | __ATTR(caps_stop, 0644, spk_var_show, spk_var_store); |
69 | static struct kobj_attribute freq_attribute = |
70 | __ATTR(freq, 0644, spk_var_show, spk_var_store); |
71 | static struct kobj_attribute pitch_attribute = |
72 | __ATTR(pitch, 0644, spk_var_show, spk_var_store); |
73 | static struct kobj_attribute inflection_attribute = |
74 | __ATTR(inflection, 0644, spk_var_show, spk_var_store); |
75 | static struct kobj_attribute punct_attribute = |
76 | __ATTR(punct, 0644, spk_var_show, spk_var_store); |
77 | static struct kobj_attribute rate_attribute = |
78 | __ATTR(rate, 0644, spk_var_show, spk_var_store); |
79 | static struct kobj_attribute tone_attribute = |
80 | __ATTR(tone, 0644, spk_var_show, spk_var_store); |
81 | static struct kobj_attribute voice_attribute = |
82 | __ATTR(voice, 0644, spk_var_show, spk_var_store); |
83 | static struct kobj_attribute vol_attribute = |
84 | __ATTR(vol, 0644, spk_var_show, spk_var_store); |
85 | |
86 | /* |
87 | * We should uncomment the following definition, when we agree on a |
88 | * method of passing a language designation to the software synthesizer. |
89 | * static struct kobj_attribute lang_attribute = |
90 | * __ATTR(lang, 0644, spk_var_show, spk_var_store); |
91 | */ |
92 | |
93 | static struct kobj_attribute delay_time_attribute = |
94 | __ATTR(delay_time, 0644, spk_var_show, spk_var_store); |
95 | static struct kobj_attribute direct_attribute = |
96 | __ATTR(direct, 0644, spk_var_show, spk_var_store); |
97 | static struct kobj_attribute full_time_attribute = |
98 | __ATTR(full_time, 0644, spk_var_show, spk_var_store); |
99 | static struct kobj_attribute jiffy_delta_attribute = |
100 | __ATTR(jiffy_delta, 0644, spk_var_show, spk_var_store); |
101 | static struct kobj_attribute trigger_time_attribute = |
102 | __ATTR(trigger_time, 0644, spk_var_show, spk_var_store); |
103 | |
104 | /* |
105 | * Create a group of attributes so that we can create and destroy them all |
106 | * at once. |
107 | */ |
108 | static struct attribute *synth_attrs[] = { |
109 | &caps_start_attribute.attr, |
110 | &caps_stop_attribute.attr, |
111 | &freq_attribute.attr, |
112 | /* &lang_attribute.attr, */ |
113 | &pitch_attribute.attr, |
114 | &inflection_attribute.attr, |
115 | &punct_attribute.attr, |
116 | &rate_attribute.attr, |
117 | &tone_attribute.attr, |
118 | &voice_attribute.attr, |
119 | &vol_attribute.attr, |
120 | &delay_time_attribute.attr, |
121 | &direct_attribute.attr, |
122 | &full_time_attribute.attr, |
123 | &jiffy_delta_attribute.attr, |
124 | &trigger_time_attribute.attr, |
125 | NULL, /* need to NULL terminate the list of attributes */ |
126 | }; |
127 | |
128 | static struct spk_synth synth_soft = { |
129 | .name = "soft" , |
130 | .version = DRV_VERSION, |
131 | .long_name = "software synth" , |
132 | .init = "\01@\x01\x31y\n" , |
133 | .procspeech = PROCSPEECH, |
134 | .delay = 0, |
135 | .trigger = 0, |
136 | .jiffies = 0, |
137 | .full = 0, |
138 | .startup = SYNTH_START, |
139 | .checkval = SYNTH_CHECK, |
140 | .vars = vars, |
141 | .io_ops = NULL, |
142 | .probe = softsynth_probe, |
143 | .release = softsynth_release, |
144 | .synth_immediate = NULL, |
145 | .catch_up = NULL, |
146 | .flush = NULL, |
147 | .is_alive = softsynth_is_alive, |
148 | .synth_adjust = softsynth_adjust, |
149 | .read_buff_add = NULL, |
150 | .get_index = get_index, |
151 | .indexing = { |
152 | .command = "\x01%di" , |
153 | .lowindex = 1, |
154 | .highindex = 5, |
155 | .currindex = 1, |
156 | }, |
157 | .attributes = { |
158 | .attrs = synth_attrs, |
159 | .name = "soft" , |
160 | }, |
161 | }; |
162 | |
163 | static char *get_initstring(void) |
164 | { |
165 | static char buf[40]; |
166 | char *cp; |
167 | struct var_t *var; |
168 | size_t len; |
169 | size_t n; |
170 | |
171 | memset(buf, 0, sizeof(buf)); |
172 | cp = buf; |
173 | len = sizeof(buf); |
174 | |
175 | var = synth_soft.vars; |
176 | while (var->var_id != MAXVARS) { |
177 | if (var->var_id != CAPS_START && var->var_id != CAPS_STOP && |
178 | var->var_id != PAUSE && var->var_id != DIRECT) { |
179 | n = scnprintf(buf: cp, size: len, fmt: var->u.n.synth_fmt, |
180 | var->u.n.value); |
181 | cp = cp + n; |
182 | len = len - n; |
183 | } |
184 | var++; |
185 | } |
186 | cp = cp + scnprintf(buf: cp, size: len, fmt: "\n" ); |
187 | return buf; |
188 | } |
189 | |
190 | static int softsynth_open(struct inode *inode, struct file *fp) |
191 | { |
192 | unsigned long flags; |
193 | /*if ((fp->f_flags & O_ACCMODE) != O_RDONLY) */ |
194 | /* return -EPERM; */ |
195 | spin_lock_irqsave(&speakup_info.spinlock, flags); |
196 | if (synth_soft.alive) { |
197 | spin_unlock_irqrestore(lock: &speakup_info.spinlock, flags); |
198 | return -EBUSY; |
199 | } |
200 | synth_soft.alive = 1; |
201 | spin_unlock_irqrestore(lock: &speakup_info.spinlock, flags); |
202 | return 0; |
203 | } |
204 | |
205 | static int softsynth_close(struct inode *inode, struct file *fp) |
206 | { |
207 | unsigned long flags; |
208 | |
209 | spin_lock_irqsave(&speakup_info.spinlock, flags); |
210 | synth_soft.alive = 0; |
211 | init_pos = 0; |
212 | spin_unlock_irqrestore(lock: &speakup_info.spinlock, flags); |
213 | /* Make sure we let applications go before leaving */ |
214 | speakup_start_ttys(); |
215 | return 0; |
216 | } |
217 | |
218 | static ssize_t softsynthx_read(struct file *fp, char __user *buf, size_t count, |
219 | loff_t *pos, int unicode) |
220 | { |
221 | int chars_sent = 0; |
222 | char __user *cp; |
223 | char *init; |
224 | size_t bytes_per_ch = unicode ? 3 : 1; |
225 | u16 ch; |
226 | int empty; |
227 | unsigned long flags; |
228 | DEFINE_WAIT(wait); |
229 | |
230 | if (count < bytes_per_ch) |
231 | return -EINVAL; |
232 | |
233 | spin_lock_irqsave(&speakup_info.spinlock, flags); |
234 | synth_soft.alive = 1; |
235 | while (1) { |
236 | prepare_to_wait(wq_head: &speakup_event, wq_entry: &wait, TASK_INTERRUPTIBLE); |
237 | if (synth_current() == &synth_soft) { |
238 | if (!unicode) |
239 | synth_buffer_skip_nonlatin1(); |
240 | if (!synth_buffer_empty() || speakup_info.flushing) |
241 | break; |
242 | } |
243 | spin_unlock_irqrestore(lock: &speakup_info.spinlock, flags); |
244 | if (fp->f_flags & O_NONBLOCK) { |
245 | finish_wait(wq_head: &speakup_event, wq_entry: &wait); |
246 | return -EAGAIN; |
247 | } |
248 | if (signal_pending(current)) { |
249 | finish_wait(wq_head: &speakup_event, wq_entry: &wait); |
250 | return -ERESTARTSYS; |
251 | } |
252 | schedule(); |
253 | spin_lock_irqsave(&speakup_info.spinlock, flags); |
254 | } |
255 | finish_wait(wq_head: &speakup_event, wq_entry: &wait); |
256 | |
257 | cp = buf; |
258 | init = get_initstring(); |
259 | |
260 | /* Keep 3 bytes available for a 16bit UTF-8-encoded character */ |
261 | while (chars_sent <= count - bytes_per_ch) { |
262 | if (synth_current() != &synth_soft) |
263 | break; |
264 | if (speakup_info.flushing) { |
265 | speakup_info.flushing = 0; |
266 | ch = '\x18'; |
267 | } else if (init[init_pos]) { |
268 | ch = init[init_pos++]; |
269 | } else { |
270 | if (!unicode) |
271 | synth_buffer_skip_nonlatin1(); |
272 | if (synth_buffer_empty()) |
273 | break; |
274 | ch = synth_buffer_getc(); |
275 | } |
276 | spin_unlock_irqrestore(lock: &speakup_info.spinlock, flags); |
277 | |
278 | if ((!unicode && ch < 0x100) || (unicode && ch < 0x80)) { |
279 | u_char c = ch; |
280 | |
281 | if (copy_to_user(to: cp, from: &c, n: 1)) |
282 | return -EFAULT; |
283 | |
284 | chars_sent++; |
285 | cp++; |
286 | } else if (unicode && ch < 0x800) { |
287 | u_char s[2] = { |
288 | 0xc0 | (ch >> 6), |
289 | 0x80 | (ch & 0x3f) |
290 | }; |
291 | |
292 | if (copy_to_user(to: cp, from: s, n: sizeof(s))) |
293 | return -EFAULT; |
294 | |
295 | chars_sent += sizeof(s); |
296 | cp += sizeof(s); |
297 | } else if (unicode) { |
298 | u_char s[3] = { |
299 | 0xe0 | (ch >> 12), |
300 | 0x80 | ((ch >> 6) & 0x3f), |
301 | 0x80 | (ch & 0x3f) |
302 | }; |
303 | |
304 | if (copy_to_user(to: cp, from: s, n: sizeof(s))) |
305 | return -EFAULT; |
306 | |
307 | chars_sent += sizeof(s); |
308 | cp += sizeof(s); |
309 | } |
310 | |
311 | spin_lock_irqsave(&speakup_info.spinlock, flags); |
312 | } |
313 | *pos += chars_sent; |
314 | empty = synth_buffer_empty(); |
315 | spin_unlock_irqrestore(lock: &speakup_info.spinlock, flags); |
316 | if (empty) { |
317 | speakup_start_ttys(); |
318 | *pos = 0; |
319 | } |
320 | return chars_sent; |
321 | } |
322 | |
323 | static ssize_t softsynth_read(struct file *fp, char __user *buf, size_t count, |
324 | loff_t *pos) |
325 | { |
326 | return softsynthx_read(fp, buf, count, pos, unicode: 0); |
327 | } |
328 | |
329 | static ssize_t softsynthu_read(struct file *fp, char __user *buf, size_t count, |
330 | loff_t *pos) |
331 | { |
332 | return softsynthx_read(fp, buf, count, pos, unicode: 1); |
333 | } |
334 | |
335 | static int last_index; |
336 | |
337 | static ssize_t softsynth_write(struct file *fp, const char __user *buf, |
338 | size_t count, loff_t *pos) |
339 | { |
340 | unsigned long supplied_index = 0; |
341 | int converted; |
342 | |
343 | converted = kstrtoul_from_user(s: buf, count, base: 0, res: &supplied_index); |
344 | |
345 | if (converted < 0) |
346 | return converted; |
347 | |
348 | last_index = supplied_index; |
349 | return count; |
350 | } |
351 | |
352 | static __poll_t softsynth_poll(struct file *fp, struct poll_table_struct *wait) |
353 | { |
354 | unsigned long flags; |
355 | __poll_t ret = 0; |
356 | |
357 | poll_wait(filp: fp, wait_address: &speakup_event, p: wait); |
358 | |
359 | spin_lock_irqsave(&speakup_info.spinlock, flags); |
360 | if (synth_current() == &synth_soft && |
361 | (!synth_buffer_empty() || speakup_info.flushing)) |
362 | ret = EPOLLIN | EPOLLRDNORM; |
363 | spin_unlock_irqrestore(lock: &speakup_info.spinlock, flags); |
364 | return ret; |
365 | } |
366 | |
367 | static unsigned char get_index(struct spk_synth *synth) |
368 | { |
369 | int rv; |
370 | |
371 | rv = last_index; |
372 | last_index = 0; |
373 | return rv; |
374 | } |
375 | |
376 | static const struct file_operations softsynth_fops = { |
377 | .owner = THIS_MODULE, |
378 | .poll = softsynth_poll, |
379 | .read = softsynth_read, |
380 | .write = softsynth_write, |
381 | .open = softsynth_open, |
382 | .release = softsynth_close, |
383 | }; |
384 | |
385 | static const struct file_operations softsynthu_fops = { |
386 | .owner = THIS_MODULE, |
387 | .poll = softsynth_poll, |
388 | .read = softsynthu_read, |
389 | .write = softsynth_write, |
390 | .open = softsynth_open, |
391 | .release = softsynth_close, |
392 | }; |
393 | |
394 | static int softsynth_probe(struct spk_synth *synth) |
395 | { |
396 | if (misc_registered != 0) |
397 | return 0; |
398 | memset(&synth_device, 0, sizeof(synth_device)); |
399 | synth_device.minor = MISC_DYNAMIC_MINOR; |
400 | synth_device.name = "softsynth" ; |
401 | synth_device.fops = &softsynth_fops; |
402 | if (misc_register(misc: &synth_device)) { |
403 | pr_warn("Couldn't initialize miscdevice /dev/softsynth.\n" ); |
404 | return -ENODEV; |
405 | } |
406 | |
407 | memset(&synthu_device, 0, sizeof(synthu_device)); |
408 | synthu_device.minor = MISC_DYNAMIC_MINOR; |
409 | synthu_device.name = "softsynthu" ; |
410 | synthu_device.fops = &softsynthu_fops; |
411 | if (misc_register(misc: &synthu_device)) { |
412 | misc_deregister(misc: &synth_device); |
413 | pr_warn("Couldn't initialize miscdevice /dev/softsynthu.\n" ); |
414 | return -ENODEV; |
415 | } |
416 | |
417 | misc_registered = 1; |
418 | pr_info("initialized device: /dev/softsynth, node (MAJOR 10, MINOR %d)\n" , |
419 | synth_device.minor); |
420 | pr_info("initialized device: /dev/softsynthu, node (MAJOR 10, MINOR %d)\n" , |
421 | synthu_device.minor); |
422 | return 0; |
423 | } |
424 | |
425 | static void softsynth_release(struct spk_synth *synth) |
426 | { |
427 | misc_deregister(misc: &synth_device); |
428 | misc_deregister(misc: &synthu_device); |
429 | misc_registered = 0; |
430 | pr_info("unregistered /dev/softsynth\n" ); |
431 | pr_info("unregistered /dev/softsynthu\n" ); |
432 | } |
433 | |
434 | static int softsynth_is_alive(struct spk_synth *synth) |
435 | { |
436 | if (synth_soft.alive) |
437 | return 1; |
438 | return 0; |
439 | } |
440 | |
441 | static int softsynth_adjust(struct spk_synth *synth, struct st_var_header *var) |
442 | { |
443 | struct st_var_header *punc_level_var; |
444 | struct var_t *var_data; |
445 | |
446 | if (var->var_id != PUNC_LEVEL) |
447 | return 0; |
448 | |
449 | /* We want to set the the speech synthesis punctuation level |
450 | * accordingly, so it properly tunes speaking A_PUNC characters */ |
451 | var_data = var->data; |
452 | if (!var_data) |
453 | return 0; |
454 | punc_level_var = spk_get_var_header(var_id: PUNCT); |
455 | if (!punc_level_var) |
456 | return 0; |
457 | spk_set_num_var(val: var_data->u.n.value, var: punc_level_var, how: E_SET); |
458 | |
459 | return 1; |
460 | } |
461 | |
462 | module_param_named(start, synth_soft.startup, short, 0444); |
463 | module_param_named(direct, vars[DIRECT_ID].u.n.default_val, int, 0444); |
464 | module_param_named(rate, vars[RATE_ID].u.n.default_val, int, 0444); |
465 | module_param_named(pitch, vars[PITCH_ID].u.n.default_val, int, 0444); |
466 | module_param_named(inflection, vars[INFLECTION_ID].u.n.default_val, int, 0444); |
467 | module_param_named(vol, vars[VOL_ID].u.n.default_val, int, 0444); |
468 | module_param_named(tone, vars[TONE_ID].u.n.default_val, int, 0444); |
469 | module_param_named(punct, vars[PUNCT_ID].u.n.default_val, int, 0444); |
470 | module_param_named(voice, vars[VOICE_ID].u.n.default_val, int, 0444); |
471 | module_param_named(frequency, vars[FREQUENCY_ID].u.n.default_val, int, 0444); |
472 | |
473 | |
474 | |
475 | MODULE_PARM_DESC(start, "Start the synthesizer once it is loaded." ); |
476 | MODULE_PARM_DESC(direct, "Set the direct variable on load." ); |
477 | MODULE_PARM_DESC(rate, "Sets the rate of the synthesizer." ); |
478 | MODULE_PARM_DESC(pitch, "Sets the pitch of the synthesizer." ); |
479 | MODULE_PARM_DESC(inflection, "Sets the inflection of the synthesizer." ); |
480 | MODULE_PARM_DESC(vol, "Sets the volume of the speech synthesizer." ); |
481 | MODULE_PARM_DESC(tone, "Sets the tone of the speech synthesizer." ); |
482 | MODULE_PARM_DESC(punct, "Sets the amount of punctuation spoken by the synthesizer." ); |
483 | MODULE_PARM_DESC(voice, "Sets the voice used by the synthesizer." ); |
484 | MODULE_PARM_DESC(frequency, "Sets the frequency of speech synthesizer." ); |
485 | |
486 | module_spk_synth(synth_soft); |
487 | |
488 | MODULE_AUTHOR("Kirk Reiser <kirk@braille.uwo.ca>" ); |
489 | MODULE_DESCRIPTION("Speakup userspace software synthesizer support" ); |
490 | MODULE_LICENSE("GPL" ); |
491 | MODULE_VERSION(DRV_VERSION); |
492 | |