cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

dtlk.c (16685B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*                                              -*- linux-c -*-
      3 * dtlk.c - DoubleTalk PC driver for Linux
      4 *
      5 * Original author: Chris Pallotta <chris@allmedia.com>
      6 * Current maintainer: Jim Van Zandt <jrv@vanzandt.mv.com>
      7 * 
      8 * 2000-03-18 Jim Van Zandt: Fix polling.
      9 *  Eliminate dtlk_timer_active flag and separate dtlk_stop_timer
     10 *  function.  Don't restart timer in dtlk_timer_tick.  Restart timer
     11 *  in dtlk_poll after every poll.  dtlk_poll returns mask (duh).
     12 *  Eliminate unused function dtlk_write_byte.  Misc. code cleanups.
     13 */
     14
     15/* This driver is for the DoubleTalk PC, a speech synthesizer
     16   manufactured by RC Systems (http://www.rcsys.com/).  It was written
     17   based on documentation in their User's Manual file and Developer's
     18   Tools disk.
     19
     20   The DoubleTalk PC contains four voice synthesizers: text-to-speech
     21   (TTS), linear predictive coding (LPC), PCM/ADPCM, and CVSD.  It
     22   also has a tone generator.  Output data for LPC are written to the
     23   LPC port, and output data for the other modes are written to the
     24   TTS port.
     25
     26   Two kinds of data can be read from the DoubleTalk: status
     27   information (in response to the "\001?" interrogation command) is
     28   read from the TTS port, and index markers (which mark the progress
     29   of the speech) are read from the LPC port.  Not all models of the
     30   DoubleTalk PC implement index markers.  Both the TTS and LPC ports
     31   can also display status flags.
     32
     33   The DoubleTalk PC generates no interrupts.
     34
     35   These characteristics are mapped into the Unix stream I/O model as
     36   follows:
     37
     38   "write" sends bytes to the TTS port.  It is the responsibility of
     39   the user program to switch modes among TTS, PCM/ADPCM, and CVSD.
     40   This driver was written for use with the text-to-speech
     41   synthesizer.  If LPC output is needed some day, other minor device
     42   numbers can be used to select among output modes.
     43
     44   "read" gets index markers from the LPC port.  If the device does
     45   not implement index markers, the read will fail with error EINVAL.
     46
     47   Status information is available using the DTLK_INTERROGATE ioctl.
     48
     49 */
     50
     51#include <linux/module.h>
     52
     53#define KERNEL
     54#include <linux/types.h>
     55#include <linux/fs.h>
     56#include <linux/mm.h>
     57#include <linux/errno.h>	/* for -EBUSY */
     58#include <linux/ioport.h>	/* for request_region */
     59#include <linux/delay.h>	/* for loops_per_jiffy */
     60#include <linux/sched.h>
     61#include <linux/mutex.h>
     62#include <asm/io.h>		/* for inb_p, outb_p, inb, outb, etc. */
     63#include <linux/uaccess.h>	/* for get_user, etc. */
     64#include <linux/wait.h>		/* for wait_queue */
     65#include <linux/init.h>		/* for __init, module_{init,exit} */
     66#include <linux/poll.h>		/* for EPOLLIN, etc. */
     67#include <linux/dtlk.h>		/* local header file for DoubleTalk values */
     68
     69#ifdef TRACING
     70#define TRACE_TEXT(str) printk(str);
     71#define TRACE_RET printk(")")
     72#else				/* !TRACING */
     73#define TRACE_TEXT(str) ((void) 0)
     74#define TRACE_RET ((void) 0)
     75#endif				/* TRACING */
     76
     77static DEFINE_MUTEX(dtlk_mutex);
     78static void dtlk_timer_tick(struct timer_list *unused);
     79
     80static int dtlk_major;
     81static int dtlk_port_lpc;
     82static int dtlk_port_tts;
     83static int dtlk_busy;
     84static int dtlk_has_indexing;
     85static unsigned int dtlk_portlist[] =
     86{0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0};
     87static wait_queue_head_t dtlk_process_list;
     88static DEFINE_TIMER(dtlk_timer, dtlk_timer_tick);
     89
     90/* prototypes for file_operations struct */
     91static ssize_t dtlk_read(struct file *, char __user *,
     92			 size_t nbytes, loff_t * ppos);
     93static ssize_t dtlk_write(struct file *, const char __user *,
     94			  size_t nbytes, loff_t * ppos);
     95static __poll_t dtlk_poll(struct file *, poll_table *);
     96static int dtlk_open(struct inode *, struct file *);
     97static int dtlk_release(struct inode *, struct file *);
     98static long dtlk_ioctl(struct file *file,
     99		       unsigned int cmd, unsigned long arg);
    100
    101static const struct file_operations dtlk_fops =
    102{
    103	.owner		= THIS_MODULE,
    104	.read		= dtlk_read,
    105	.write		= dtlk_write,
    106	.poll		= dtlk_poll,
    107	.unlocked_ioctl	= dtlk_ioctl,
    108	.open		= dtlk_open,
    109	.release	= dtlk_release,
    110	.llseek		= no_llseek,
    111};
    112
    113/* local prototypes */
    114static int dtlk_dev_probe(void);
    115static struct dtlk_settings *dtlk_interrogate(void);
    116static int dtlk_readable(void);
    117static char dtlk_read_lpc(void);
    118static char dtlk_read_tts(void);
    119static int dtlk_writeable(void);
    120static char dtlk_write_bytes(const char *buf, int n);
    121static char dtlk_write_tts(char);
    122/*
    123   static void dtlk_handle_error(char, char, unsigned int);
    124 */
    125
    126static ssize_t dtlk_read(struct file *file, char __user *buf,
    127			 size_t count, loff_t * ppos)
    128{
    129	unsigned int minor = iminor(file_inode(file));
    130	char ch;
    131	int i = 0, retries;
    132
    133	TRACE_TEXT("(dtlk_read");
    134	/*  printk("DoubleTalk PC - dtlk_read()\n"); */
    135
    136	if (minor != DTLK_MINOR || !dtlk_has_indexing)
    137		return -EINVAL;
    138
    139	for (retries = 0; retries < loops_per_jiffy; retries++) {
    140		while (i < count && dtlk_readable()) {
    141			ch = dtlk_read_lpc();
    142			/*        printk("dtlk_read() reads 0x%02x\n", ch); */
    143			if (put_user(ch, buf++))
    144				return -EFAULT;
    145			i++;
    146		}
    147		if (i)
    148			return i;
    149		if (file->f_flags & O_NONBLOCK)
    150			break;
    151		msleep_interruptible(100);
    152	}
    153	if (retries == loops_per_jiffy)
    154		printk(KERN_ERR "dtlk_read times out\n");
    155	TRACE_RET;
    156	return -EAGAIN;
    157}
    158
    159static ssize_t dtlk_write(struct file *file, const char __user *buf,
    160			  size_t count, loff_t * ppos)
    161{
    162	int i = 0, retries = 0, ch;
    163
    164	TRACE_TEXT("(dtlk_write");
    165#ifdef TRACING
    166	printk(" \"");
    167	{
    168		int i, ch;
    169		for (i = 0; i < count; i++) {
    170			if (get_user(ch, buf + i))
    171				return -EFAULT;
    172			if (' ' <= ch && ch <= '~')
    173				printk("%c", ch);
    174			else
    175				printk("\\%03o", ch);
    176		}
    177		printk("\"");
    178	}
    179#endif
    180
    181	if (iminor(file_inode(file)) != DTLK_MINOR)
    182		return -EINVAL;
    183
    184	while (1) {
    185		while (i < count && !get_user(ch, buf) &&
    186		       (ch == DTLK_CLEAR || dtlk_writeable())) {
    187			dtlk_write_tts(ch);
    188			buf++;
    189			i++;
    190			if (i % 5 == 0)
    191				/* We yield our time until scheduled
    192				   again.  This reduces the transfer
    193				   rate to 500 bytes/sec, but that's
    194				   still enough to keep up with the
    195				   speech synthesizer. */
    196				msleep_interruptible(1);
    197			else {
    198				/* the RDY bit goes zero 2-3 usec
    199				   after writing, and goes 1 again
    200				   180-190 usec later.  Here, we wait
    201				   up to 250 usec for the RDY bit to
    202				   go nonzero. */
    203				for (retries = 0;
    204				     retries < loops_per_jiffy / (4000/HZ);
    205				     retries++)
    206					if (inb_p(dtlk_port_tts) &
    207					    TTS_WRITABLE)
    208						break;
    209			}
    210			retries = 0;
    211		}
    212		if (i == count)
    213			return i;
    214		if (file->f_flags & O_NONBLOCK)
    215			break;
    216
    217		msleep_interruptible(1);
    218
    219		if (++retries > 10 * HZ) { /* wait no more than 10 sec
    220					      from last write */
    221			printk("dtlk: write timeout.  "
    222			       "inb_p(dtlk_port_tts) = 0x%02x\n",
    223			       inb_p(dtlk_port_tts));
    224			TRACE_RET;
    225			return -EBUSY;
    226		}
    227	}
    228	TRACE_RET;
    229	return -EAGAIN;
    230}
    231
    232static __poll_t dtlk_poll(struct file *file, poll_table * wait)
    233{
    234	__poll_t mask = 0;
    235	unsigned long expires;
    236
    237	TRACE_TEXT(" dtlk_poll");
    238	/*
    239	   static long int j;
    240	   printk(".");
    241	   printk("<%ld>", jiffies-j);
    242	   j=jiffies;
    243	 */
    244	poll_wait(file, &dtlk_process_list, wait);
    245
    246	if (dtlk_has_indexing && dtlk_readable()) {
    247	        del_timer(&dtlk_timer);
    248		mask = EPOLLIN | EPOLLRDNORM;
    249	}
    250	if (dtlk_writeable()) {
    251	        del_timer(&dtlk_timer);
    252		mask |= EPOLLOUT | EPOLLWRNORM;
    253	}
    254	/* there are no exception conditions */
    255
    256	/* There won't be any interrupts, so we set a timer instead. */
    257	expires = jiffies + 3*HZ / 100;
    258	mod_timer(&dtlk_timer, expires);
    259
    260	return mask;
    261}
    262
    263static void dtlk_timer_tick(struct timer_list *unused)
    264{
    265	TRACE_TEXT(" dtlk_timer_tick");
    266	wake_up_interruptible(&dtlk_process_list);
    267}
    268
    269static long dtlk_ioctl(struct file *file,
    270		       unsigned int cmd,
    271		       unsigned long arg)
    272{
    273	char __user *argp = (char __user *)arg;
    274	struct dtlk_settings *sp;
    275	char portval;
    276	TRACE_TEXT(" dtlk_ioctl");
    277
    278	switch (cmd) {
    279
    280	case DTLK_INTERROGATE:
    281		mutex_lock(&dtlk_mutex);
    282		sp = dtlk_interrogate();
    283		mutex_unlock(&dtlk_mutex);
    284		if (copy_to_user(argp, sp, sizeof(struct dtlk_settings)))
    285			return -EINVAL;
    286		return 0;
    287
    288	case DTLK_STATUS:
    289		portval = inb_p(dtlk_port_tts);
    290		return put_user(portval, argp);
    291
    292	default:
    293		return -EINVAL;
    294	}
    295}
    296
    297/* Note that nobody ever sets dtlk_busy... */
    298static int dtlk_open(struct inode *inode, struct file *file)
    299{
    300	TRACE_TEXT("(dtlk_open");
    301
    302	switch (iminor(inode)) {
    303	case DTLK_MINOR:
    304		if (dtlk_busy)
    305			return -EBUSY;
    306		return stream_open(inode, file);
    307
    308	default:
    309		return -ENXIO;
    310	}
    311}
    312
    313static int dtlk_release(struct inode *inode, struct file *file)
    314{
    315	TRACE_TEXT("(dtlk_release");
    316
    317	switch (iminor(inode)) {
    318	case DTLK_MINOR:
    319		break;
    320
    321	default:
    322		break;
    323	}
    324	TRACE_RET;
    325	
    326	del_timer_sync(&dtlk_timer);
    327
    328	return 0;
    329}
    330
    331static int __init dtlk_init(void)
    332{
    333	int err;
    334
    335	dtlk_port_lpc = 0;
    336	dtlk_port_tts = 0;
    337	dtlk_busy = 0;
    338	dtlk_major = register_chrdev(0, "dtlk", &dtlk_fops);
    339	if (dtlk_major < 0) {
    340		printk(KERN_ERR "DoubleTalk PC - cannot register device\n");
    341		return dtlk_major;
    342	}
    343	err = dtlk_dev_probe();
    344	if (err) {
    345		unregister_chrdev(dtlk_major, "dtlk");
    346		return err;
    347	}
    348	printk(", MAJOR %d\n", dtlk_major);
    349
    350	init_waitqueue_head(&dtlk_process_list);
    351
    352	return 0;
    353}
    354
    355static void __exit dtlk_cleanup (void)
    356{
    357	dtlk_write_bytes("goodbye", 8);
    358	msleep_interruptible(500);		/* nap 0.50 sec but
    359						   could be awakened
    360						   earlier by
    361						   signals... */
    362
    363	dtlk_write_tts(DTLK_CLEAR);
    364	unregister_chrdev(dtlk_major, "dtlk");
    365	release_region(dtlk_port_lpc, DTLK_IO_EXTENT);
    366}
    367
    368module_init(dtlk_init);
    369module_exit(dtlk_cleanup);
    370
    371/* ------------------------------------------------------------------------ */
    372
    373static int dtlk_readable(void)
    374{
    375#ifdef TRACING
    376	printk(" dtlk_readable=%u@%u", inb_p(dtlk_port_lpc) != 0x7f, jiffies);
    377#endif
    378	return inb_p(dtlk_port_lpc) != 0x7f;
    379}
    380
    381static int dtlk_writeable(void)
    382{
    383	/* TRACE_TEXT(" dtlk_writeable"); */
    384#ifdef TRACINGMORE
    385	printk(" dtlk_writeable=%u", (inb_p(dtlk_port_tts) & TTS_WRITABLE)!=0);
    386#endif
    387	return inb_p(dtlk_port_tts) & TTS_WRITABLE;
    388}
    389
    390static int __init dtlk_dev_probe(void)
    391{
    392	unsigned int testval = 0;
    393	int i = 0;
    394	struct dtlk_settings *sp;
    395
    396	if (dtlk_port_lpc | dtlk_port_tts)
    397		return -EBUSY;
    398
    399	for (i = 0; dtlk_portlist[i]; i++) {
    400#if 0
    401		printk("DoubleTalk PC - Port %03x = %04x\n",
    402		       dtlk_portlist[i], (testval = inw_p(dtlk_portlist[i])));
    403#endif
    404
    405		if (!request_region(dtlk_portlist[i], DTLK_IO_EXTENT, 
    406			       "dtlk"))
    407			continue;
    408		testval = inw_p(dtlk_portlist[i]);
    409		if ((testval &= 0xfbff) == 0x107f) {
    410			dtlk_port_lpc = dtlk_portlist[i];
    411			dtlk_port_tts = dtlk_port_lpc + 1;
    412
    413			sp = dtlk_interrogate();
    414			printk("DoubleTalk PC at %03x-%03x, "
    415			       "ROM version %s, serial number %u",
    416			       dtlk_portlist[i], dtlk_portlist[i] +
    417			       DTLK_IO_EXTENT - 1,
    418			       sp->rom_version, sp->serial_number);
    419
    420                        /* put LPC port into known state, so
    421			   dtlk_readable() gives valid result */
    422			outb_p(0xff, dtlk_port_lpc); 
    423
    424                        /* INIT string and index marker */
    425			dtlk_write_bytes("\036\1@\0\0012I\r", 8);
    426			/* posting an index takes 18 msec.  Here, we
    427			   wait up to 100 msec to see whether it
    428			   appears. */
    429			msleep_interruptible(100);
    430			dtlk_has_indexing = dtlk_readable();
    431#ifdef TRACING
    432			printk(", indexing %d\n", dtlk_has_indexing);
    433#endif
    434#ifdef INSCOPE
    435			{
    436/* This macro records ten samples read from the LPC port, for later display */
    437#define LOOK					\
    438for (i = 0; i < 10; i++)			\
    439  {						\
    440    buffer[b++] = inb_p(dtlk_port_lpc);		\
    441    __delay(loops_per_jiffy/(1000000/HZ));             \
    442  }
    443				char buffer[1000];
    444				int b = 0, i, j;
    445
    446				LOOK
    447				outb_p(0xff, dtlk_port_lpc);
    448				buffer[b++] = 0;
    449				LOOK
    450				dtlk_write_bytes("\0012I\r", 4);
    451				buffer[b++] = 0;
    452				__delay(50 * loops_per_jiffy / (1000/HZ));
    453				outb_p(0xff, dtlk_port_lpc);
    454				buffer[b++] = 0;
    455				LOOK
    456
    457				printk("\n");
    458				for (j = 0; j < b; j++)
    459					printk(" %02x", buffer[j]);
    460				printk("\n");
    461			}
    462#endif				/* INSCOPE */
    463
    464#ifdef OUTSCOPE
    465			{
    466/* This macro records ten samples read from the TTS port, for later display */
    467#define LOOK					\
    468for (i = 0; i < 10; i++)			\
    469  {						\
    470    buffer[b++] = inb_p(dtlk_port_tts);		\
    471    __delay(loops_per_jiffy/(1000000/HZ));  /* 1 us */ \
    472  }
    473				char buffer[1000];
    474				int b = 0, i, j;
    475
    476				mdelay(10);	/* 10 ms */
    477				LOOK
    478				outb_p(0x03, dtlk_port_tts);
    479				buffer[b++] = 0;
    480				LOOK
    481				LOOK
    482
    483				printk("\n");
    484				for (j = 0; j < b; j++)
    485					printk(" %02x", buffer[j]);
    486				printk("\n");
    487			}
    488#endif				/* OUTSCOPE */
    489
    490			dtlk_write_bytes("Double Talk found", 18);
    491
    492			return 0;
    493		}
    494		release_region(dtlk_portlist[i], DTLK_IO_EXTENT);
    495	}
    496
    497	printk(KERN_INFO "DoubleTalk PC - not found\n");
    498	return -ENODEV;
    499}
    500
    501/*
    502   static void dtlk_handle_error(char op, char rc, unsigned int minor)
    503   {
    504   printk(KERN_INFO"\nDoubleTalk PC - MINOR: %d, OPCODE: %d, ERROR: %d\n", 
    505   minor, op, rc);
    506   return;
    507   }
    508 */
    509
    510/* interrogate the DoubleTalk PC and return its settings */
    511static struct dtlk_settings *dtlk_interrogate(void)
    512{
    513	unsigned char *t;
    514	static char buf[sizeof(struct dtlk_settings) + 1];
    515	int total, i;
    516	static struct dtlk_settings status;
    517	TRACE_TEXT("(dtlk_interrogate");
    518	dtlk_write_bytes("\030\001?", 3);
    519	for (total = 0, i = 0; i < 50; i++) {
    520		buf[total] = dtlk_read_tts();
    521		if (total > 2 && buf[total] == 0x7f)
    522			break;
    523		if (total < sizeof(struct dtlk_settings))
    524			total++;
    525	}
    526	/*
    527	   if (i==50) printk("interrogate() read overrun\n");
    528	   for (i=0; i<sizeof(buf); i++)
    529	   printk(" %02x", buf[i]);
    530	   printk("\n");
    531	 */
    532	t = buf;
    533	status.serial_number = t[0] + t[1] * 256; /* serial number is
    534						     little endian */
    535	t += 2;
    536
    537	i = 0;
    538	while (*t != '\r') {
    539		status.rom_version[i] = *t;
    540		if (i < sizeof(status.rom_version) - 1)
    541			i++;
    542		t++;
    543	}
    544	status.rom_version[i] = 0;
    545	t++;
    546
    547	status.mode = *t++;
    548	status.punc_level = *t++;
    549	status.formant_freq = *t++;
    550	status.pitch = *t++;
    551	status.speed = *t++;
    552	status.volume = *t++;
    553	status.tone = *t++;
    554	status.expression = *t++;
    555	status.ext_dict_loaded = *t++;
    556	status.ext_dict_status = *t++;
    557	status.free_ram = *t++;
    558	status.articulation = *t++;
    559	status.reverb = *t++;
    560	status.eob = *t++;
    561	status.has_indexing = dtlk_has_indexing;
    562	TRACE_RET;
    563	return &status;
    564}
    565
    566static char dtlk_read_tts(void)
    567{
    568	int portval, retries = 0;
    569	char ch;
    570	TRACE_TEXT("(dtlk_read_tts");
    571
    572	/* verify DT is ready, read char, wait for ACK */
    573	do {
    574		portval = inb_p(dtlk_port_tts);
    575	} while ((portval & TTS_READABLE) == 0 &&
    576		 retries++ < DTLK_MAX_RETRIES);
    577	if (retries > DTLK_MAX_RETRIES)
    578		printk(KERN_ERR "dtlk_read_tts() timeout\n");
    579
    580	ch = inb_p(dtlk_port_tts);	/* input from TTS port */
    581	ch &= 0x7f;
    582	outb_p(ch, dtlk_port_tts);
    583
    584	retries = 0;
    585	do {
    586		portval = inb_p(dtlk_port_tts);
    587	} while ((portval & TTS_READABLE) != 0 &&
    588		 retries++ < DTLK_MAX_RETRIES);
    589	if (retries > DTLK_MAX_RETRIES)
    590		printk(KERN_ERR "dtlk_read_tts() timeout\n");
    591
    592	TRACE_RET;
    593	return ch;
    594}
    595
    596static char dtlk_read_lpc(void)
    597{
    598	int retries = 0;
    599	char ch;
    600	TRACE_TEXT("(dtlk_read_lpc");
    601
    602	/* no need to test -- this is only called when the port is readable */
    603
    604	ch = inb_p(dtlk_port_lpc);	/* input from LPC port */
    605
    606	outb_p(0xff, dtlk_port_lpc);
    607
    608	/* acknowledging a read takes 3-4
    609	   usec.  Here, we wait up to 20 usec
    610	   for the acknowledgement */
    611	retries = (loops_per_jiffy * 20) / (1000000/HZ);
    612	while (inb_p(dtlk_port_lpc) != 0x7f && --retries > 0);
    613	if (retries == 0)
    614		printk(KERN_ERR "dtlk_read_lpc() timeout\n");
    615
    616	TRACE_RET;
    617	return ch;
    618}
    619
    620/* write n bytes to tts port */
    621static char dtlk_write_bytes(const char *buf, int n)
    622{
    623	char val = 0;
    624	/*  printk("dtlk_write_bytes(\"%-*s\", %d)\n", n, buf, n); */
    625	TRACE_TEXT("(dtlk_write_bytes");
    626	while (n-- > 0)
    627		val = dtlk_write_tts(*buf++);
    628	TRACE_RET;
    629	return val;
    630}
    631
    632static char dtlk_write_tts(char ch)
    633{
    634	int retries = 0;
    635#ifdef TRACINGMORE
    636	printk("  dtlk_write_tts(");
    637	if (' ' <= ch && ch <= '~')
    638		printk("'%c'", ch);
    639	else
    640		printk("0x%02x", ch);
    641#endif
    642	if (ch != DTLK_CLEAR)	/* no flow control for CLEAR command */
    643		while ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0 &&
    644		       retries++ < DTLK_MAX_RETRIES)	/* DT ready? */
    645			;
    646	if (retries > DTLK_MAX_RETRIES)
    647		printk(KERN_ERR "dtlk_write_tts() timeout\n");
    648
    649	outb_p(ch, dtlk_port_tts);	/* output to TTS port */
    650	/* the RDY bit goes zero 2-3 usec after writing, and goes
    651	   1 again 180-190 usec later.  Here, we wait up to 10
    652	   usec for the RDY bit to go zero. */
    653	for (retries = 0; retries < loops_per_jiffy / (100000/HZ); retries++)
    654		if ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0)
    655			break;
    656
    657#ifdef TRACINGMORE
    658	printk(")\n");
    659#endif
    660	return 0;
    661}
    662
    663MODULE_LICENSE("GPL");