xref: /linux/drivers/accessibility/speakup/devsynth.c (revision 36ec807b627b4c0a0a382f0ae48eac7187d14b2b)
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, consumed, 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, 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 += consumed) {
62 			s32 value;
63 
64 			value = synth_utf8_get(buf + in, bytes - in, &consumed, &want);
65 			if (value == -1) {
66 				/* Invalid or incomplete */
67 
68 				if (want > bytes - in)
69 					/* We don't have it all yet, stop here
70 					 * and wait for the rest
71 					 */
72 					bytes = in;
73 
74 				continue;
75 			}
76 
77 			if (value < 0x10000)
78 				ubuf[out++] = value;
79 		}
80 
81 		count -= bytes;
82 		ptr += bytes;
83 
84 		/* And speak this up */
85 		if (out) {
86 			spin_lock_irqsave(&speakup_info.spinlock, flags);
87 			for (in = 0; in < out; in++)
88 				synth_buffer_add(ubuf[in]);
89 			synth_start();
90 			spin_unlock_irqrestore(&speakup_info.spinlock, flags);
91 		}
92 	}
93 
94 	return (ssize_t)(nbytes - count);
95 }
96 
97 static ssize_t speakup_file_read(struct file *fp, char __user *buf,
98 				 size_t nbytes, loff_t *ppos)
99 {
100 	return 0;
101 }
102 
103 static int speakup_file_open(struct inode *ip, struct file *fp)
104 {
105 	if (!synth)
106 		return -ENODEV;
107 	if (xchg(&dev_opened, 1))
108 		return -EBUSY;
109 	return 0;
110 }
111 
112 static int speakup_file_release(struct inode *ip, struct file *fp)
113 {
114 	dev_opened = 0;
115 	return 0;
116 }
117 
118 static const struct file_operations synth_fops = {
119 	.read = speakup_file_read,
120 	.write = speakup_file_write,
121 	.open = speakup_file_open,
122 	.release = speakup_file_release,
123 };
124 
125 static const struct file_operations synthu_fops = {
126 	.read = speakup_file_read,
127 	.write = speakup_file_writeu,
128 	.open = speakup_file_open,
129 	.release = speakup_file_release,
130 };
131 
132 static struct miscdevice synth_device = {
133 	.minor = MISC_DYNAMIC_MINOR,
134 	.name = "synth",
135 	.fops = &synth_fops,
136 };
137 
138 static struct miscdevice synthu_device = {
139 	.minor = MISC_DYNAMIC_MINOR,
140 	.name = "synthu",
141 	.fops = &synthu_fops,
142 };
143 
144 void speakup_register_devsynth(void)
145 {
146 	if (!synth_registered) {
147 		if (misc_register(&synth_device)) {
148 			pr_warn("Couldn't initialize miscdevice /dev/synth.\n");
149 		} else {
150 			pr_info("initialized device: /dev/synth, node (MAJOR %d, MINOR %d)\n",
151 				MISC_MAJOR, synth_device.minor);
152 			synth_registered = 1;
153 		}
154 	}
155 	if (!synthu_registered) {
156 		if (misc_register(&synthu_device)) {
157 			pr_warn("Couldn't initialize miscdevice /dev/synthu.\n");
158 		} else {
159 			pr_info("initialized device: /dev/synthu, node (MAJOR %d, MINOR %d)\n",
160 				MISC_MAJOR, synthu_device.minor);
161 			synthu_registered = 1;
162 		}
163 	}
164 }
165 
166 void speakup_unregister_devsynth(void)
167 {
168 	if (synth_registered) {
169 		pr_info("speakup: unregistering synth device /dev/synth\n");
170 		misc_deregister(&synth_device);
171 		synth_registered = 0;
172 	}
173 	if (synthu_registered) {
174 		pr_info("speakup: unregistering synth device /dev/synthu\n");
175 		misc_deregister(&synthu_device);
176 		synthu_registered = 0;
177 	}
178 }
179