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 | |
10 | static int synth_registered, synthu_registered; |
11 | static int dev_opened; |
12 | |
13 | /* Latin1 version */ |
14 | static 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 */ |
39 | static 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 | } |
110 | drop: |
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 | |
130 | static ssize_t speakup_file_read(struct file *fp, char __user *buf, |
131 | size_t nbytes, loff_t *ppos) |
132 | { |
133 | return 0; |
134 | } |
135 | |
136 | static 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 | |
145 | static int speakup_file_release(struct inode *ip, struct file *fp) |
146 | { |
147 | dev_opened = 0; |
148 | return 0; |
149 | } |
150 | |
151 | static 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 | |
158 | static 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 | |
165 | static struct miscdevice synth_device = { |
166 | .minor = MISC_DYNAMIC_MINOR, |
167 | .name = "synth" , |
168 | .fops = &synth_fops, |
169 | }; |
170 | |
171 | static struct miscdevice synthu_device = { |
172 | .minor = MISC_DYNAMIC_MINOR, |
173 | .name = "synthu" , |
174 | .fops = &synthu_fops, |
175 | }; |
176 | |
177 | void 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 | |
199 | void 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 | |