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 */
speakup_file_write(struct file * fp,const char __user * buffer,size_t nbytes,loff_t * ppos)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 */
speakup_file_writeu(struct file * fp,const char __user * buffer,size_t nbytes,loff_t * ppos)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
speakup_file_read(struct file * fp,char __user * buf,size_t nbytes,loff_t * ppos)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
speakup_file_open(struct inode * ip,struct file * fp)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
speakup_file_release(struct inode * ip,struct file * fp)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
speakup_register_devsynth(void)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
speakup_unregister_devsynth(void)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