Commit 80797726 authored by Samuel Thibault's avatar Samuel Thibault Committed by Greg Kroah-Hartman
Browse files

speakup: Add /dev/synthu device



/dev/synth has always been 8bit, but applications nowadays mostly
expect to be using utf-8 encoding.  This adds /dev/synthu to be able
to synthesize non-latin1 characters.  This however remains limited
to 16bit unicode like the rest of speakup.  Any odd input or input
beyond 16bit is just discarded.

Signed-off-by: default avatarSamuel Thibault <samuel.thibault@ens-lyon.org>

===================================================================
Link: https://lore.kernel.org/r/20240204155825.ditstifsbqndnce3@begin



Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent b6c8dafc
Loading
Loading
Loading
Loading
+133 −15
Original line number Diff line number Diff line
@@ -7,9 +7,10 @@
#include "speakup.h"
#include "spk_priv.h"

static int misc_registered;
static int synth_registered, synthu_registered;
static int dev_opened;

/* Latin1 version */
static ssize_t speakup_file_write(struct file *fp, const char __user *buffer,
				  size_t nbytes, loff_t *ppos)
{
@@ -34,6 +35,97 @@ static ssize_t speakup_file_write(struct file *fp, const char __user *buffer,
	return (ssize_t)nbytes;
}

/* UTF-8 version */
static ssize_t speakup_file_writeu(struct file *fp, const char __user *buffer,
				   size_t nbytes, loff_t *ppos)
{
	size_t count = nbytes, want;
	const char __user *ptr = buffer;
	size_t bytes;
	unsigned long flags;
	unsigned char buf[256];
	u16 ubuf[256];
	size_t in, in2, out;

	if (!synth)
		return -ENODEV;

	want = 1;
	while (count >= want) {
		/* Copy some UTF-8 piece from userland */
		bytes = min(count, sizeof(buf));
		if (copy_from_user(buf, ptr, bytes))
			return -EFAULT;

		/* Convert to u16 */
		for (in = 0, out = 0; in < bytes; in++) {
			unsigned char c = buf[in];
			int nbytes = 8 - fls(c ^ 0xff);
			u32 value;

			switch (nbytes) {
			case 8: /* 0xff */
			case 7: /* 0xfe */
			case 1: /* 0x80 */
				/* Invalid, drop */
				goto drop;

			case 0:
				/* ASCII, copy */
				ubuf[out++] = c;
				continue;

			default:
				/* 2..6-byte UTF-8 */

				if (bytes - in < nbytes) {
					/* We don't have it all yet, stop here
					 * and wait for the rest
					 */
					bytes = in;
					want = nbytes;
					continue;
				}

				/* First byte */
				value = c & ((1u << (7 - nbytes)) - 1);

				/* Other bytes */
				for (in2 = 2; in2 <= nbytes; in2++) {
					c = buf[in + 1];
					if ((c & 0xc0) != 0x80)	{
						/* Invalid, drop the head */
						want = 1;
						goto drop;
					}
					value = (value << 6) | (c & 0x3f);
					in++;
				}

				if (value < 0x10000)
					ubuf[out++] = value;
				want = 1;
				break;
			}
drop:
		}

		count -= bytes;
		ptr += bytes;

		/* And speak this up */
		if (out) {
			spin_lock_irqsave(&speakup_info.spinlock, flags);
			for (in = 0; in < out; in++)
				synth_buffer_add(ubuf[in]);
			synth_start();
			spin_unlock_irqrestore(&speakup_info.spinlock, flags);
		}
	}

	return (ssize_t)(nbytes - count);
}

static ssize_t speakup_file_read(struct file *fp, char __user *buf,
				 size_t nbytes, loff_t *ppos)
{
@@ -62,31 +154,57 @@ static const struct file_operations synth_fops = {
	.release = speakup_file_release,
};

static const struct file_operations synthu_fops = {
	.read = speakup_file_read,
	.write = speakup_file_writeu,
	.open = speakup_file_open,
	.release = speakup_file_release,
};

static struct miscdevice synth_device = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "synth",
	.fops = &synth_fops,
};

static struct miscdevice synthu_device = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "synthu",
	.fops = &synthu_fops,
};

void speakup_register_devsynth(void)
{
	if (misc_registered != 0)
		return;
/* zero it so if register fails, deregister will not ref invalid ptrs */
	if (!synth_registered) {
		if (misc_register(&synth_device)) {
			pr_warn("Couldn't initialize miscdevice /dev/synth.\n");
		} else {
			pr_info("initialized device: /dev/synth, node (MAJOR %d, MINOR %d)\n",
				MISC_MAJOR, synth_device.minor);
		misc_registered = 1;
			synth_registered = 1;
		}
	}
	if (!synthu_registered) {
		if (misc_register(&synthu_device)) {
			pr_warn("Couldn't initialize miscdevice /dev/synthu.\n");
		} else {
			pr_info("initialized device: /dev/synthu, node (MAJOR %d, MINOR %d)\n",
				MISC_MAJOR, synthu_device.minor);
			synthu_registered = 1;
		}
	}
}

void speakup_unregister_devsynth(void)
{
	if (!misc_registered)
		return;
	if (synth_registered) {
		pr_info("speakup: unregistering synth device /dev/synth\n");
		misc_deregister(&synth_device);
	misc_registered = 0;
		synth_registered = 0;
	}
	if (synthu_registered) {
		pr_info("speakup: unregistering synth device /dev/synthu\n");
		misc_deregister(&synthu_device);
		synthu_registered = 0;
	}
}