⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 serial.c

📁 pebble
💻 C
字号:
/* 
 * Copyright 1999, 2000, 2001, 2002 Lucent Technologies Inc.
 * All Rights Reserved.
 * Information Sciences Research Center, Bell Labs.
 *
 * LUCENT TECHNOLOGIES DOES NOT CLAIM MERCHANTABILITY OF THIS SOFTWARE 
 * OR THE SUITABILITY OF THIS SOFTWARE FOR ANY PARTICULAR PURPOSE. The
 * software is provided "as is" without expressed or implied warranty 
 * of any kind.
 *
 * These notices must be retained in any copies of any part of this
 * software.
 *
 */

/*
 * Serial device driver for UART
 *
 * Runs in user mode with interrupts enabled.
 *
 * Note: must flush control registers to ensure correct operation.
 */

#include "string.h"
#include "unistd.h"		/* for write */
#include "machine/cpu.h"
#if defined(P5064) || defined(P4032)
#include "sbd.h"
#else
#include "qube.h"
#endif
#include <ns16550.h>
#include <st16650.h>
#include <assert.h>
#include "pebble.h"
#include "mem.h"
#include "time.h"
#include "synch.h"
#include "diag.h"

/* select UART0 or UART1 */
#define UART_BASE UART0_BASE	/* i.e., COM1 */
#define DIVISOR 12

#define BYTE_MASK       0xFF

/* cyclic buffer size */
#define	QSIZE	1000

typedef	struct	{
	Lock	mutex;
	int	sem;
	int	head;
	int	tail;
	int	pending;
	char	buf[QSIZE];
} Q;

static int serial_flags = 0;

volatile ns16550dev *dev = (ns16550dev *)
                ((unsigned long) IO_BASE + (unsigned long) UART_BASE);
static Q in, out;		/* buffers */
static int intr_sem, sent_sem;
int write_sem, read_sem;

static int lcd = 0;	/* portal # of lcd_write portal */
static int led = 0;	/* portal # of led_write portal */

uint
read_lsr(void)
{
#ifdef	Cobalt
	return ST16650_READ_USER(com_lsr);
#else
	return dev->lsr;
#endif
}

char
read_data()
{
#ifdef	Cobalt
	return ST16650_READ_USER(com_data);
#else
	return dev->data;
#endif
}


void
write_data(char c)
{
#ifdef Cobalt
	ST16650_WRITE_USER(com_data, c);
#else
	dev->data = c;
#endif
}

void
led_write(const char *s)
{
	if (led > 0)
		write(led, s, strlen(s));
}


void
lcd_write(int row, int col, const char *fmt, int val)
{
	char msg[NAMELEN];

	if (lcd <= 0)
		return;

	sprintf(msg, fmt, val);
	call_portal(lcd, row, col, msg);
}

int
sys_serial_get(Thread *t)
{
	return (BYTE_MASK & dev->cfcr);
}

int
sys_serial_set(Thread *t, int byte)
{
	dev->cfcr = (uchar) BYTE_MASK & byte;
	wbflush();
	return (BYTE_MASK & dev->cfcr);
}

int
sys_serial_ctl(Thread *t, u_int32_t flags)
{
	u_int32_t old_flags;
	old_flags = serial_flags;
	serial_flags = flags;
	return old_flags;
}

static void
q_init(Q *q, int init_val)
{
	mutex_init(&q->mutex, MUTEX_ERRORCHECK);
	q->sem = sem_create(init_val);
	if (q->sem < 0)
		panic("sem_create failed");
	DIAG(SERIAL_DIAG, ("q_init: q=%p sem=%d\n", q, q->sem));

	/* we have QSIZE-1 empty slots to distinguish between empty and */
	/* full conditions by comparing the head and tail ptrs */
	q->head = 0;
	q->tail = 0;
	q->pending = 0;

	/* should not strictly be necessary, but to be safe... */
	memset(q->buf, 0, QSIZE);
}

/* put a single char into the queue */
static void
putch(int ch)
{
	static int count = 0;	/* used only in DIAG mode  */

	sem_wait(out.sem);	/* wait for free space */
	mutex_lock_safe(&out.mutex, "out.mutex");
	out.buf[out.head] = ch; /* add char to buffer */
	out.head = (out.head + 1) % QSIZE;
	++out.pending;		/* one more char pending */

	if (SERIAL_DIAG)
		lcd_write(1, 0, "p%04d", ++count);
	mutex_unlock_safe(&out.mutex, "out.mutex");
	sem_post(intr_sem);	/* wake interrupt handler */
}

/* get a single char from the queue */
static int
getch()
{
	int ch;

	sem_wait(in.sem);	/* wait for data available */
	mutex_lock_safe(&in.mutex, "in.mutex");
	ch = in.buf[in.tail];	/* grab next char */
	in.tail = (in.tail + 1) % QSIZE;
	--in.pending;		/* one less char pending */
	mutex_unlock_safe(&in.mutex, "in.mutex");
	sem_post(intr_sem);	/* wake interrupt handler */

	return ch;
}

/* backspace over a character */
void bs()
{
	putch(010); 
	putch(' '); 
	putch(010);
}

/*
 * this is called once a second. It wakes the serial driver handler thread and 
 * then schedules itself again itself again.
 */

static void
watchdog_handler(void *val)
{
	sem_post(intr_sem);
	timeout(watchdog_handler, 0, hrtime() + sec2ticks());
	return;
}

/* 
 * write a string on the serial port
 * We use a semaphore to protect against concurrent writes.
 * We must use a semaphore and not a mutex here, since
 * the lock manager may be called if the mutex is not free,
 * which in turn may print something else, causing a deadlock.
 *
 * Note that putch() may suspend the calling thread!
 */
void	putchar(char c);
int
sys_serial_write(Thread *t, const char *s, int len)
{
	int i;
  
	/* if buffer is OK, we can write contents to serial line */
	param_check(s, len, 0);
  
	if (SERIAL_DIAG)
		led_write(s);

	sem_wait(write_sem);

	for (i = 0; i < len; i++)
		putch(s[i]);

	sem_post(write_sem);

	return len;
}

/*
 * read a string from the serial port
 */
int
sys_serial_read(Thread *t, char *s, int len)
{
	int i, j, ch = '\0', stop = 0, save, echo;
  
	j = 0;
	stop = 0;

	/* if buffer is OK, we can read into it */
	param_check(s, len, 0);
  
	sem_wait(write_sem);
	for (i = 0; i < len - 1; i++) {
		save = 1;
		echo = (serial_flags & SER_CTL_ECHO);

		ch = getch();

		if (serial_flags & SER_CTL_EDIT) {
			/* stop when we get a newline */
			switch (ch) {
			case '\n':
			case '\r':
				stop = 1;
				break;

			case 010:	/* ^H */
			case 0177:	/* DEL */
				if (i > 0) {
					bs();
					i -= 2;
				}
				echo = save = 0;
				break;

			case 025:	/* ^U */
				for (j = 0; j < i; ++j)
					bs();
				i = -1;
				echo = save = 0;
				break;

			default:
				break;
			}
		}
		if (stop)
			break;
		if (echo)
			putch(ch);
		if (save)
			s[i] = ch;
	}
	if (ch == '\r' || ch == '\n') {
		putch('\r'); putch('\n');
	}
	s[i] = '\0';
	sem_post(write_sem);

	return i;
}

/*
 * flush serial driver's queue by taking characters explicitly from the
 * queue and printing them.
 * this routine works also when the serial driver is deadlocked.
 * This routine tries to lock the output queue. But is it is locked,
 * it continues anyhow.
 */
int
sys_serial_flush_queue(void)
{
	int locked = 0;

	do {
		if (locked == 0 && mutex_trylock(&out.mutex) >= 0)
			locked = 1;

		if ((read_lsr() & LSR_TXRDY) && out.pending > 0) {
			write_data(out.buf[out.tail]);
			out.tail = (out.tail + 1) % QSIZE;
			--out.pending;
			wbflush();
			sem_post(out.sem);
		}
	} while(out.pending > 0);

	if (locked)
		mutex_unlock(&out.mutex);

	return 0;
}

/*
 * interrupt handler thread for serial line. 
 * this is the low part of the driver.
 * Note: the semaphore intr_sem may remember ``old'' events,
 * so we must check the status of the serial line again.
 */
static void
serial_intr_handler(int dummy)
{
	static int count = 0;		/* used only in DIAG mode */
	static int iter = 0;		/* used only in DIAG mode */

	intr_enable(HW_INTR_COM1);
	dummy = dev->iir;		/* reset interrupt at the device */

	while (1) {
		if (SERIAL_DIAG)
			lcd_write(1,10,"intr%02d", ++iter);

#ifdef	Cobalt
		if (out.pending > 0)
			usleep(SEC2USEC/2000);
		else
			usleep(SEC2USEC/100);
#else
		sem_wait(intr_sem);

		intr_enable(HW_INTR_COM1);
		dummy = dev->iir;	/* reset interrupt at the device */

		/* wait until device is really ready */
		while ((read_lsr() & (LSR_TXRDY|LSR_RXRDY)) == 0) {
			sem_wait(intr_sem);
			dummy = dev->iir;
			intr_enable(HW_INTR_COM1);
		}
#endif

		/* handle pending output, if possible */
		if (SERIAL_DIAG)
			lcd_write(1,10,"omtx",0);

		mutex_lock_safe(&out.mutex, "out.mutex");
		while ((read_lsr() & LSR_TXRDY) && out.pending > 0) {
			write_data(out.buf[out.tail]);
			out.tail = (out.tail + 1) % QSIZE;
			--out.pending;
			wbflush();

			if (SERIAL_DIAG)
				lcd_write(1,5,">%04d", ++count);

			sem_post(out.sem);
		}
		mutex_unlock_safe(&out.mutex, "out.mutex");

		/* handle pending input, if possible */
		if (SERIAL_DIAG)
			lcd_write(1,10,"imtx",0);

		mutex_lock_safe(&in.mutex, "in.mutex");

		if ((read_lsr() & LSR_RXRDY) && in.pending < QSIZE) {
			in.buf[in.head] = read_data();
			in.head = (in.head + 1) % QSIZE;
			in.pending++;
			sem_post(in.sem);
		}
		mutex_unlock_safe(&in.mutex, "in.mutex");

	}
}

#ifdef Cobalt
/* this code is no longer needed */
void
putchar(char c)
{
	int timo;

	/* wait for any pending transmission to finish */
	timo = 150000;
	while (!(ST16650_READ_USER(com_lsr) & LSR_TXRDY) && --timo)
		continue;

	ST16650_WRITE_USER(com_data, c);
	wbflush();

	/* wait for this transmission to complete */
	timo = 1500000;
	while (!(ST16650_READ_USER(com_lsr) & LSR_TXRDY) && --timo)
		continue;	
}
#endif

#define	COM_TOLERANCE	30	/* baud rate tolerance, in 0.1% units */

int
comspeed(speed, frequency)
	long speed, frequency;
{
#define	divrnd(n, q)	(((n)*2/(q)+1)/2)	/* divide and round off */

	int x, err;

#if 0
	if (speed == 0)
		return (0);
#endif
	if (speed <= 0)
		return (-1);
	x = divrnd(frequency / 16, speed);
	if (x <= 0)
		return (-1);
	err = divrnd(((quad_t)frequency) * 1000 / 16, speed * x) - 1000;
	if (err < 0)
		err = -err;
	if (err > COM_TOLERANCE)
		return (-1);
	return (x);

#undef	divrnd
}

/*
 * initialize serial line
 * Need to fix this to work on Cobalt and on the previous boards.
 */
static void
serial_init(void)
{
	int rate;

	/* initialize UART */

	ST16650_WRITE(com_cfcr, LCR_DLAB);	/* select latch */
	wbflush();

	rate = comspeed(115200, 18432000);

	ST16650_WRITE(com_dlbl, rate);
	wbflush();
	ST16650_WRITE(com_dlbh, rate >> 8);
	wbflush();

	/* set 8 bits character, no parity, 1 stop bit */
	ST16650_WRITE(com_cfcr, LCR_8BITS);
	wbflush();

	ST16650_WRITE(com_mcr, MCR_DTR | MCR_RTS);
	wbflush();

	ST16650_WRITE(com_fifo, FIFO_ENABLE | FIFO_RCV_RST | FIFO_XMT_RST |
			FIFO_TRIGGER_1); 
	wbflush();

	/* enable receive and transmit interrupts */ 
	/* we are not ready for interrupts yet on the Cobalt.. */
#if 0
	ST16650_WRITE(com_ier, IER_ERXRDY|IER_ETXRDY); */
#else
	ST16650_WRITE(com_ier, 0);
#endif

	wbflush();
}


/* driver initialization thread */
int main()
{
	/* verify that we are running in user mode with interrupts enabled */ 
	if (!check_psw(1,1)) {
		printf("serial driver: invalid processor status: %08lx\n",
			get_psw());
		task_exit(1);
	}

	printf("serial driver active\n");

	led_write("initializing serial driver\n");
	/*
	 * The output queue in the UART is probably not empty at this moment.
	 * Wait a little to allow it to drain before initialization.
	 */
	us_delay(SEC2USEC/10);	/* sleep for 1/10 second */
	serial_init();
	q_init(&in, 0);
	q_init(&out, QSIZE);

	if (SERIAL_DIAG) {
		lcd = portal_open("lcd", 1);
		led = fd_open("led");
		/* clear error message if LCD or LED not found */
		set_error(NULL);
	}

	if ((write_sem = sem_create(1)) < 0)
		panic("sem_create for write_sem failed");
	if ((read_sem = sem_create(1)) < 0)
		panic("sem_create for read_sem failed");
	DIAG(SERIAL_DIAG, ("write_sem=%d read_sem=%d\n", write_sem, read_sem));
	DIAG(SERIAL_DIAG, ("out.mutex@%p in.mutex@%p\n",
		&out.mutex, &in.mutex));

	if ((sent_sem = sem_create(0)) < 0)
		panic("sem_create failed for sent_sem");
	DIAG(SERIAL_DIAG, ("sent_sem=%d\n", sent_sem));

#ifdef Cobalt
	if ((intr_sem = intr_define(HW_INTR_COM)) < 0)
		panic("intr_define failed");
#else
	if ((intr_sem = intr_define(HW_INTR_COM1)) < 0)
		panic("intr_define failed");
#endif
	if (thr_spawn(serial_intr_handler, 0) < 0)
		panic("spawn serial_intr_handler failed");

	DIAG(SERIAL_DIAG, ("serial driver replacing read fd=0\n"));
	if (portal_create(READ2PORT(0), "smtiii", 0, sys_serial_read, 0) < 0)
		panic("portal_create for serial_write() failed");

	DIAG(SERIAL_DIAG, ("serial driver replacing write fd=1\n"));
	if (portal_create(WRITE2PORT(1), "smtiii", 0, sys_serial_write, 0) < 0)
		panic("portal_create for serial_write() failed");

	if (portal_create_pair("serial", sys_serial_read, sys_serial_write) < 0)
		panic("portal_create_pair for serial driver failed");
	
	if (portal_create(SYS_SERIAL_GET, "smtiii", 0, sys_serial_get, 0) < 0)
		panic("portal_create for serial_get() failed");
	
	if (portal_create(SYS_SERIAL_SET, "smtiii", 0, sys_serial_set, 0) < 0)
		panic("portal_create for serial_set() failed");

	if (portal_create(SYS_SERIAL_CTL, "smtiii", 0, sys_serial_ctl, 0) < 0)
		panic("potal_create for serial_ctl() failed");

	if (portal_create(SYS_SERIAL_FLUSH_QUEUE, "smtiii", 0,
	    sys_serial_flush_queue, 0) < 0)
		panic("potal_create for serial_flush_queue() failed");

	/* by default, echo and edit */
	serial_flags = SER_CTL_ECHO|SER_CTL_EDIT;

	if (0)
	if (timeout(watchdog_handler, 0, hrtime() + sec2ticks()) < 0)
		panic("timeout() for watchdog handler failed");
	/*
	 * return to initialization code.
	 * cannot just "return", since the startup code (crt0.S) calls
	 * exit when main routine terminates.
	 */
	call_portal(SYS_RTN_RPC);
	return(1);
}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -