1// SPDX-License-Identifier: GPL-2.0
2#include <linux/errno.h>
3#include <linux/miscdevice.h> /* for misc_register, and MISC_DYNAMIC_MINOR */
4#include <linux/types.h>
5#include <linux/uaccess.h>
6
7#include "speakup.h"
8#include "spk_priv.h"
9
10static int synth_registered, synthu_registered;
11static int dev_opened;
12
13/* Latin1 version */
14static ssize_t speakup_file_write(struct file *fp, const char __user *buffer,
15 size_t nbytes, loff_t *ppos)
16{
17 size_t count = nbytes;
18 const char __user *ptr = buffer;
19 size_t bytes;
20 unsigned long flags;
21 u_char buf[256];
22
23 if (!synth)
24 return -ENODEV;
25 while (count > 0) {
26 bytes = min(count, sizeof(buf));
27 if (copy_from_user(to: buf, from: ptr, n: bytes))
28 return -EFAULT;
29 count -= bytes;
30 ptr += bytes;
31 spin_lock_irqsave(&speakup_info.spinlock, flags);
32 synth_write(buf, count: bytes);
33 spin_unlock_irqrestore(lock: &speakup_info.spinlock, flags);
34 }
35 return (ssize_t)nbytes;
36}
37
38/* UTF-8 version */
39static ssize_t speakup_file_writeu(struct file *fp, const char __user *buffer,
40 size_t nbytes, loff_t *ppos)
41{
42 size_t count = nbytes, want;
43 const char __user *ptr = buffer;
44 size_t bytes;
45 unsigned long flags;
46 unsigned char buf[256];
47 u16 ubuf[256];
48 size_t in, in2, out;
49
50 if (!synth)
51 return -ENODEV;
52
53 want = 1;
54 while (count >= want) {
55 /* Copy some UTF-8 piece from userland */
56 bytes = min(count, sizeof(buf));
57 if (copy_from_user(to: buf, from: ptr, n: bytes))
58 return -EFAULT;
59
60 /* Convert to u16 */
61 for (in = 0, out = 0; in < bytes; in++) {
62 unsigned char c = buf[in];
63 int nbytes = 8 - fls(x: c ^ 0xff);
64 u32 value;
65
66 switch (nbytes) {
67 case 8: /* 0xff */
68 case 7: /* 0xfe */
69 case 1: /* 0x80 */
70 /* Invalid, drop */
71 goto drop;
72
73 case 0:
74 /* ASCII, copy */
75 ubuf[out++] = c;
76 continue;
77
78 default:
79 /* 2..6-byte UTF-8 */
80
81 if (bytes - in < nbytes) {
82 /* We don't have it all yet, stop here
83 * and wait for the rest
84 */
85 bytes = in;
86 want = nbytes;
87 continue;
88 }
89
90 /* First byte */
91 value = c & ((1u << (7 - nbytes)) - 1);
92
93 /* Other bytes */
94 for (in2 = 2; in2 <= nbytes; in2++) {
95 c = buf[in + 1];
96 if ((c & 0xc0) != 0x80) {
97 /* Invalid, drop the head */
98 want = 1;
99 goto drop;
100 }
101 value = (value << 6) | (c & 0x3f);
102 in++;
103 }
104
105 if (value < 0x10000)
106 ubuf[out++] = value;
107 want = 1;
108 break;
109 }
110drop:
111 /* empty statement */;
112 }
113
114 count -= bytes;
115 ptr += bytes;
116
117 /* And speak this up */
118 if (out) {
119 spin_lock_irqsave(&speakup_info.spinlock, flags);
120 for (in = 0; in < out; in++)
121 synth_buffer_add(ch: ubuf[in]);
122 synth_start();
123 spin_unlock_irqrestore(lock: &speakup_info.spinlock, flags);
124 }
125 }
126
127 return (ssize_t)(nbytes - count);
128}
129
130static ssize_t speakup_file_read(struct file *fp, char __user *buf,
131 size_t nbytes, loff_t *ppos)
132{
133 return 0;
134}
135
136static int speakup_file_open(struct inode *ip, struct file *fp)
137{
138 if (!synth)
139 return -ENODEV;
140 if (xchg(&dev_opened, 1))
141 return -EBUSY;
142 return 0;
143}
144
145static int speakup_file_release(struct inode *ip, struct file *fp)
146{
147 dev_opened = 0;
148 return 0;
149}
150
151static const struct file_operations synth_fops = {
152 .read = speakup_file_read,
153 .write = speakup_file_write,
154 .open = speakup_file_open,
155 .release = speakup_file_release,
156};
157
158static const struct file_operations synthu_fops = {
159 .read = speakup_file_read,
160 .write = speakup_file_writeu,
161 .open = speakup_file_open,
162 .release = speakup_file_release,
163};
164
165static struct miscdevice synth_device = {
166 .minor = MISC_DYNAMIC_MINOR,
167 .name = "synth",
168 .fops = &synth_fops,
169};
170
171static struct miscdevice synthu_device = {
172 .minor = MISC_DYNAMIC_MINOR,
173 .name = "synthu",
174 .fops = &synthu_fops,
175};
176
177void speakup_register_devsynth(void)
178{
179 if (!synth_registered) {
180 if (misc_register(misc: &synth_device)) {
181 pr_warn("Couldn't initialize miscdevice /dev/synth.\n");
182 } else {
183 pr_info("initialized device: /dev/synth, node (MAJOR %d, MINOR %d)\n",
184 MISC_MAJOR, synth_device.minor);
185 synth_registered = 1;
186 }
187 }
188 if (!synthu_registered) {
189 if (misc_register(misc: &synthu_device)) {
190 pr_warn("Couldn't initialize miscdevice /dev/synthu.\n");
191 } else {
192 pr_info("initialized device: /dev/synthu, node (MAJOR %d, MINOR %d)\n",
193 MISC_MAJOR, synthu_device.minor);
194 synthu_registered = 1;
195 }
196 }
197}
198
199void speakup_unregister_devsynth(void)
200{
201 if (synth_registered) {
202 pr_info("speakup: unregistering synth device /dev/synth\n");
203 misc_deregister(misc: &synth_device);
204 synth_registered = 0;
205 }
206 if (synthu_registered) {
207 pr_info("speakup: unregistering synth device /dev/synthu\n");
208 misc_deregister(misc: &synthu_device);
209 synthu_registered = 0;
210 }
211}
212

source code of linux/drivers/accessibility/speakup/devsynth.c