xref: /linux/drivers/accessibility/speakup/devsynth.c (revision 71dfa617ea9f18e4585fe78364217cd32b1fc382)
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(buf, ptr, bytes))
28 			return -EFAULT;
29 		count -= bytes;
30 		ptr += bytes;
31 		spin_lock_irqsave(&speakup_info.spinlock, flags);
32 		synth_write(buf, bytes);
33 		spin_unlock_irqrestore(&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(buf, ptr, 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(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(ubuf[in]);
122 			synth_start();
123 			spin_unlock_irqrestore(&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(&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(&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(&synth_device);
204 		synth_registered = 0;
205 	}
206 	if (synthu_registered) {
207 		pr_info("speakup: unregistering synth device /dev/synthu\n");
208 		misc_deregister(&synthu_device);
209 		synthu_registered = 0;
210 	}
211 }
212