xref: /linux/drivers/accessibility/speakup/devsynth.c (revision e3a59056a655975868863a80fc8c05cb25e3b866)
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 		}
112 
113 		count -= bytes;
114 		ptr += bytes;
115 
116 		/* And speak this up */
117 		if (out) {
118 			spin_lock_irqsave(&speakup_info.spinlock, flags);
119 			for (in = 0; in < out; in++)
120 				synth_buffer_add(ubuf[in]);
121 			synth_start();
122 			spin_unlock_irqrestore(&speakup_info.spinlock, flags);
123 		}
124 	}
125 
126 	return (ssize_t)(nbytes - count);
127 }
128 
129 static ssize_t speakup_file_read(struct file *fp, char __user *buf,
130 				 size_t nbytes, loff_t *ppos)
131 {
132 	return 0;
133 }
134 
135 static int speakup_file_open(struct inode *ip, struct file *fp)
136 {
137 	if (!synth)
138 		return -ENODEV;
139 	if (xchg(&dev_opened, 1))
140 		return -EBUSY;
141 	return 0;
142 }
143 
144 static int speakup_file_release(struct inode *ip, struct file *fp)
145 {
146 	dev_opened = 0;
147 	return 0;
148 }
149 
150 static const struct file_operations synth_fops = {
151 	.read = speakup_file_read,
152 	.write = speakup_file_write,
153 	.open = speakup_file_open,
154 	.release = speakup_file_release,
155 };
156 
157 static const struct file_operations synthu_fops = {
158 	.read = speakup_file_read,
159 	.write = speakup_file_writeu,
160 	.open = speakup_file_open,
161 	.release = speakup_file_release,
162 };
163 
164 static struct miscdevice synth_device = {
165 	.minor = MISC_DYNAMIC_MINOR,
166 	.name = "synth",
167 	.fops = &synth_fops,
168 };
169 
170 static struct miscdevice synthu_device = {
171 	.minor = MISC_DYNAMIC_MINOR,
172 	.name = "synthu",
173 	.fops = &synthu_fops,
174 };
175 
176 void speakup_register_devsynth(void)
177 {
178 	if (!synth_registered) {
179 		if (misc_register(&synth_device)) {
180 			pr_warn("Couldn't initialize miscdevice /dev/synth.\n");
181 		} else {
182 			pr_info("initialized device: /dev/synth, node (MAJOR %d, MINOR %d)\n",
183 				MISC_MAJOR, synth_device.minor);
184 			synth_registered = 1;
185 		}
186 	}
187 	if (!synthu_registered) {
188 		if (misc_register(&synthu_device)) {
189 			pr_warn("Couldn't initialize miscdevice /dev/synthu.\n");
190 		} else {
191 			pr_info("initialized device: /dev/synthu, node (MAJOR %d, MINOR %d)\n",
192 				MISC_MAJOR, synthu_device.minor);
193 			synthu_registered = 1;
194 		}
195 	}
196 }
197 
198 void speakup_unregister_devsynth(void)
199 {
200 	if (synth_registered) {
201 		pr_info("speakup: unregistering synth device /dev/synth\n");
202 		misc_deregister(&synth_device);
203 		synth_registered = 0;
204 	}
205 	if (synthu_registered) {
206 		pr_info("speakup: unregistering synth device /dev/synthu\n");
207 		misc_deregister(&synthu_device);
208 		synthu_registered = 0;
209 	}
210 }
211