xref: /linux/drivers/accessibility/speakup/devsynth.c (revision c771600c6af14749609b49565ffb4cac2959710d)
12067fd92SSamuel Thibault // SPDX-License-Identifier: GPL-2.0
22067fd92SSamuel Thibault #include <linux/errno.h>
32067fd92SSamuel Thibault #include <linux/miscdevice.h>	/* for misc_register, and MISC_DYNAMIC_MINOR */
42067fd92SSamuel Thibault #include <linux/types.h>
52067fd92SSamuel Thibault #include <linux/uaccess.h>
62067fd92SSamuel Thibault 
72067fd92SSamuel Thibault #include "speakup.h"
82067fd92SSamuel Thibault #include "spk_priv.h"
92067fd92SSamuel Thibault 
1080797726SSamuel Thibault static int synth_registered, synthu_registered;
112067fd92SSamuel Thibault static int dev_opened;
122067fd92SSamuel Thibault 
1380797726SSamuel Thibault /* Latin1 version */
142067fd92SSamuel Thibault static ssize_t speakup_file_write(struct file *fp, const char __user *buffer,
152067fd92SSamuel Thibault 				  size_t nbytes, loff_t *ppos)
162067fd92SSamuel Thibault {
172067fd92SSamuel Thibault 	size_t count = nbytes;
182067fd92SSamuel Thibault 	const char __user *ptr = buffer;
192067fd92SSamuel Thibault 	size_t bytes;
202067fd92SSamuel Thibault 	unsigned long flags;
212067fd92SSamuel Thibault 	u_char buf[256];
222067fd92SSamuel Thibault 
232067fd92SSamuel Thibault 	if (!synth)
242067fd92SSamuel Thibault 		return -ENODEV;
252067fd92SSamuel Thibault 	while (count > 0) {
262067fd92SSamuel Thibault 		bytes = min(count, sizeof(buf));
272067fd92SSamuel Thibault 		if (copy_from_user(buf, ptr, bytes))
282067fd92SSamuel Thibault 			return -EFAULT;
292067fd92SSamuel Thibault 		count -= bytes;
302067fd92SSamuel Thibault 		ptr += bytes;
312067fd92SSamuel Thibault 		spin_lock_irqsave(&speakup_info.spinlock, flags);
322067fd92SSamuel Thibault 		synth_write(buf, bytes);
332067fd92SSamuel Thibault 		spin_unlock_irqrestore(&speakup_info.spinlock, flags);
342067fd92SSamuel Thibault 	}
352067fd92SSamuel Thibault 	return (ssize_t)nbytes;
362067fd92SSamuel Thibault }
372067fd92SSamuel Thibault 
3880797726SSamuel Thibault /* UTF-8 version */
3980797726SSamuel Thibault static ssize_t speakup_file_writeu(struct file *fp, const char __user *buffer,
4080797726SSamuel Thibault 				   size_t nbytes, loff_t *ppos)
4180797726SSamuel Thibault {
42*4bc4634eSSamuel Thibault 	size_t count = nbytes, consumed, want;
4380797726SSamuel Thibault 	const char __user *ptr = buffer;
4480797726SSamuel Thibault 	size_t bytes;
4580797726SSamuel Thibault 	unsigned long flags;
4680797726SSamuel Thibault 	unsigned char buf[256];
4780797726SSamuel Thibault 	u16 ubuf[256];
48*4bc4634eSSamuel Thibault 	size_t in, out;
4980797726SSamuel Thibault 
5080797726SSamuel Thibault 	if (!synth)
5180797726SSamuel Thibault 		return -ENODEV;
5280797726SSamuel Thibault 
5380797726SSamuel Thibault 	want = 1;
5480797726SSamuel Thibault 	while (count >= want) {
5580797726SSamuel Thibault 		/* Copy some UTF-8 piece from userland */
5680797726SSamuel Thibault 		bytes = min(count, sizeof(buf));
5780797726SSamuel Thibault 		if (copy_from_user(buf, ptr, bytes))
5880797726SSamuel Thibault 			return -EFAULT;
5980797726SSamuel Thibault 
6080797726SSamuel Thibault 		/* Convert to u16 */
61*4bc4634eSSamuel Thibault 		for (in = 0, out = 0; in < bytes; in += consumed) {
62*4bc4634eSSamuel Thibault 			s32 value;
6380797726SSamuel Thibault 
64*4bc4634eSSamuel Thibault 			value = synth_utf8_get(buf + in, bytes - in, &consumed, &want);
65*4bc4634eSSamuel Thibault 			if (value == -1) {
66*4bc4634eSSamuel Thibault 				/* Invalid or incomplete */
6780797726SSamuel Thibault 
68*4bc4634eSSamuel Thibault 				if (want > bytes - in)
6980797726SSamuel Thibault 					/* We don't have it all yet, stop here
7080797726SSamuel Thibault 					 * and wait for the rest
7180797726SSamuel Thibault 					 */
7280797726SSamuel Thibault 					bytes = in;
73*4bc4634eSSamuel Thibault 
7480797726SSamuel Thibault 				continue;
7580797726SSamuel Thibault 			}
7680797726SSamuel Thibault 
7780797726SSamuel Thibault 			if (value < 0x10000)
7880797726SSamuel Thibault 				ubuf[out++] = value;
7980797726SSamuel Thibault 		}
8080797726SSamuel Thibault 
8180797726SSamuel Thibault 		count -= bytes;
8280797726SSamuel Thibault 		ptr += bytes;
8380797726SSamuel Thibault 
8480797726SSamuel Thibault 		/* And speak this up */
8580797726SSamuel Thibault 		if (out) {
8680797726SSamuel Thibault 			spin_lock_irqsave(&speakup_info.spinlock, flags);
8780797726SSamuel Thibault 			for (in = 0; in < out; in++)
8880797726SSamuel Thibault 				synth_buffer_add(ubuf[in]);
8980797726SSamuel Thibault 			synth_start();
9080797726SSamuel Thibault 			spin_unlock_irqrestore(&speakup_info.spinlock, flags);
9180797726SSamuel Thibault 		}
9280797726SSamuel Thibault 	}
9380797726SSamuel Thibault 
9480797726SSamuel Thibault 	return (ssize_t)(nbytes - count);
9580797726SSamuel Thibault }
9680797726SSamuel Thibault 
972067fd92SSamuel Thibault static ssize_t speakup_file_read(struct file *fp, char __user *buf,
982067fd92SSamuel Thibault 				 size_t nbytes, loff_t *ppos)
992067fd92SSamuel Thibault {
1002067fd92SSamuel Thibault 	return 0;
1012067fd92SSamuel Thibault }
1022067fd92SSamuel Thibault 
1032067fd92SSamuel Thibault static int speakup_file_open(struct inode *ip, struct file *fp)
1042067fd92SSamuel Thibault {
1052067fd92SSamuel Thibault 	if (!synth)
1062067fd92SSamuel Thibault 		return -ENODEV;
1072067fd92SSamuel Thibault 	if (xchg(&dev_opened, 1))
1082067fd92SSamuel Thibault 		return -EBUSY;
1092067fd92SSamuel Thibault 	return 0;
1102067fd92SSamuel Thibault }
1112067fd92SSamuel Thibault 
1122067fd92SSamuel Thibault static int speakup_file_release(struct inode *ip, struct file *fp)
1132067fd92SSamuel Thibault {
1142067fd92SSamuel Thibault 	dev_opened = 0;
1152067fd92SSamuel Thibault 	return 0;
1162067fd92SSamuel Thibault }
1172067fd92SSamuel Thibault 
1182067fd92SSamuel Thibault static const struct file_operations synth_fops = {
1192067fd92SSamuel Thibault 	.read = speakup_file_read,
1202067fd92SSamuel Thibault 	.write = speakup_file_write,
1212067fd92SSamuel Thibault 	.open = speakup_file_open,
1222067fd92SSamuel Thibault 	.release = speakup_file_release,
1232067fd92SSamuel Thibault };
1242067fd92SSamuel Thibault 
12580797726SSamuel Thibault static const struct file_operations synthu_fops = {
12680797726SSamuel Thibault 	.read = speakup_file_read,
12780797726SSamuel Thibault 	.write = speakup_file_writeu,
12880797726SSamuel Thibault 	.open = speakup_file_open,
12980797726SSamuel Thibault 	.release = speakup_file_release,
13080797726SSamuel Thibault };
13180797726SSamuel Thibault 
1322067fd92SSamuel Thibault static struct miscdevice synth_device = {
1332067fd92SSamuel Thibault 	.minor = MISC_DYNAMIC_MINOR,
1342067fd92SSamuel Thibault 	.name = "synth",
1352067fd92SSamuel Thibault 	.fops = &synth_fops,
1362067fd92SSamuel Thibault };
1372067fd92SSamuel Thibault 
13880797726SSamuel Thibault static struct miscdevice synthu_device = {
13980797726SSamuel Thibault 	.minor = MISC_DYNAMIC_MINOR,
14080797726SSamuel Thibault 	.name = "synthu",
14180797726SSamuel Thibault 	.fops = &synthu_fops,
14280797726SSamuel Thibault };
14380797726SSamuel Thibault 
1442067fd92SSamuel Thibault void speakup_register_devsynth(void)
1452067fd92SSamuel Thibault {
14680797726SSamuel Thibault 	if (!synth_registered) {
1472067fd92SSamuel Thibault 		if (misc_register(&synth_device)) {
1482067fd92SSamuel Thibault 			pr_warn("Couldn't initialize miscdevice /dev/synth.\n");
1492067fd92SSamuel Thibault 		} else {
1502067fd92SSamuel Thibault 			pr_info("initialized device: /dev/synth, node (MAJOR %d, MINOR %d)\n",
1512067fd92SSamuel Thibault 				MISC_MAJOR, synth_device.minor);
15280797726SSamuel Thibault 			synth_registered = 1;
15380797726SSamuel Thibault 		}
15480797726SSamuel Thibault 	}
15580797726SSamuel Thibault 	if (!synthu_registered) {
15680797726SSamuel Thibault 		if (misc_register(&synthu_device)) {
15780797726SSamuel Thibault 			pr_warn("Couldn't initialize miscdevice /dev/synthu.\n");
15880797726SSamuel Thibault 		} else {
15980797726SSamuel Thibault 			pr_info("initialized device: /dev/synthu, node (MAJOR %d, MINOR %d)\n",
16080797726SSamuel Thibault 				MISC_MAJOR, synthu_device.minor);
16180797726SSamuel Thibault 			synthu_registered = 1;
16280797726SSamuel Thibault 		}
1632067fd92SSamuel Thibault 	}
1642067fd92SSamuel Thibault }
1652067fd92SSamuel Thibault 
1662067fd92SSamuel Thibault void speakup_unregister_devsynth(void)
1672067fd92SSamuel Thibault {
16880797726SSamuel Thibault 	if (synth_registered) {
1692067fd92SSamuel Thibault 		pr_info("speakup: unregistering synth device /dev/synth\n");
1702067fd92SSamuel Thibault 		misc_deregister(&synth_device);
17180797726SSamuel Thibault 		synth_registered = 0;
17280797726SSamuel Thibault 	}
17380797726SSamuel Thibault 	if (synthu_registered) {
17480797726SSamuel Thibault 		pr_info("speakup: unregistering synth device /dev/synthu\n");
17580797726SSamuel Thibault 		misc_deregister(&synthu_device);
17680797726SSamuel Thibault 		synthu_registered = 0;
17780797726SSamuel Thibault 	}
1782067fd92SSamuel Thibault }
179