📄 serial.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 + -