📄 rs232.c
字号:
/*==========================================================================*
* rs232.c - serial driver for 8250 and 16450 UARTs *
* Added support for Atari ST M68901 and YM-2149 --kub *
*==========================================================================*/
#include "kernel.h"
#include <termios.h>
#include <signal.h>
#include "tty.h"
#include "proc.h"
#if NR_RS_LINES > 0
#if (MACHINE != IBM_PC) && (MACHINE != ATARI)
#error /* rs232.c only supports PC and Atari ST */
#endif
#if (MACHINE == ATARI)
#include "staddr.h"
#include "stsound.h"
#include "stmfp.h"
#if (NR_RS_LINES > 1)
#error /* Only one physical RS232 line available */
#endif
#endif
#if (MACHINE == IBM_PC) /* PC/AT 8250/16450 chip combination */
/* 8250 constants. */
#define UART_FREQ 115200L /* timer frequency */
/* Interrupt enable bits. */
#define IE_RECEIVER_READY 1
#define IE_TRANSMITTER_READY 2
#define IE_LINE_STATUS_CHANGE 4
#define IE_MODEM_STATUS_CHANGE 8
/* Interrupt status bits. */
#define IS_MODEM_STATUS_CHANGE 0
#define IS_TRANSMITTER_READY 2
#define IS_RECEIVER_READY 4
#define IS_LINE_STATUS_CHANGE 6
/* Line control bits. */
#define LC_2STOP_BITS 0x04
#define LC_PARITY 0x08
#define LC_PAREVEN 0x10
#define LC_BREAK 0x40
#define LC_ADDRESS_DIVISOR 0x80
/* Line status bits. */
#define LS_OVERRUN_ERR 2
#define LS_PARITY_ERR 4
#define LS_FRAMING_ERR 8
#define LS_BREAK_INTERRUPT 0x10
#define LS_TRANSMITTER_READY 0x20
/* Modem control bits. */
#define MC_DTR 1
#define MC_RTS 2
#define MC_OUT2 8 /* required for PC & AT interrupts */
/* Modem status bits. */
#define MS_CTS 0x10
#define MS_RLSD 0x80 /* Received Line Signal Detect */
#define MS_DRLSD 0x08 /* RLSD Delta */
#else /* MACHINE == ATARI */ /* Atari ST 68901 USART */
/* Most of the USART constants are already defined in stmfp.h . The local
* definitions made here are for keeping C code changes smaller. --kub
*/
#define UART_FREQ 19200L /* timer frequency */
/* Line status bits. */
#define LS_OVERRUN_ERR R_OE
#define LS_PARITY_ERR R_PE
#define LS_FRAMING_ERR R_FE
#define LS_BREAK_INTERRUPT R_BREAK
/* Modem status bits. */
#define MS_CTS IO_SCTS /* 0x04 */
#endif /* MACHINE == ATARI */
#define DATA_BITS_SHIFT 8 /* amount data bits shifted in mode */
#define DEF_BAUD 1200 /* default baud rate */
#define RS_IBUFSIZE 1024 /* RS232 input buffer size */
#define RS_OBUFSIZE 1024 /* RS232 output buffer size */
/* Input buffer watermarks.
* The external device is asked to stop sending when the buffer
* exactly reaches high water, or when TTY requests it. Sending restarts
* when the input buffer empties below the low watermark.
*/
#define RS_ILOWWATER (1 * RS_IBUFSIZE / 4)
#define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4)
/* Output buffer low watermark.
* TTY is notified when the output buffer empties below the low watermark, so
* it may continue filling the buffer if doing a large write.
*/
#define RS_OLOWWATER (1 * RS_OBUFSIZE / 4)
#if (MACHINE == IBM_PC)
/* Macros to handle flow control.
* Interrupts must be off when they are used.
* Time is critical - already the function call for out_byte() is annoying.
* If out_byte() can be done in-line, tests to avoid it can be dropped.
* istart() tells external device we are ready by raising RTS.
* istop() tells external device we are not ready by dropping RTS.
* DTR is kept high all the time (it probably should be raised by open and
* dropped by close of the device).
* OUT2 is also kept high all the time.
*/
#define istart(rs) \
(out_byte((rs)->modem_ctl_port, MC_OUT2 | MC_RTS | MC_DTR), \
(rs)->idevready = TRUE)
#define istop(rs) \
(out_byte((rs)->modem_ctl_port, MC_OUT2 | MC_DTR), \
(rs)->idevready = FALSE)
/* Macro to tell if device is ready. The rs->cts field is set to MS_CTS if
* CLOCAL is in effect for a line without a CTS wire.
*/
#define devready(rs) ((in_byte(rs->modem_status_port) | rs->cts) & MS_CTS)
/* Macro to tell if transmitter is ready. */
#define txready(rs) (in_byte(rs->line_status_port) & LS_TRANSMITTER_READY)
/* Macro to tell if carrier has dropped.
* The RS232 Carrier Detect (CD) line is usually connected to the 8250
* Received Line Signal Detect pin, reflected by bit MS_RLSD in the Modem
* Status Register. The MS_DRLSD bit tells if MS_RLSD has just changed state.
* So if MS_DRLSD is set and MS_RLSD cleared, we know that carrier has just
* dropped.
*/
#define devhup(rs) \
(in_byte(rs->modem_status_port) & (MS_RLSD|MS_DRLSD) == MS_DRLSD)
#else /* MACHINE == ATARI */
/* Macros to handle flow control.
* Time is critical - already the function call for lock()/restore() is
* annoying.
* istart() tells external device we are ready by raising RTS.
* istop() tells external device we are not ready by dropping RTS.
* DTR is kept high all the time (it probably should be raised by open and
* dropped by close of the device). NOTE: The modem lines are active low.
*/
#define set_porta(msk,val) { register int s = lock(); \
SOUND->sd_selr = YM_IOA; \
SOUND->sd_wdat = \
SOUND->sd_rdat & (msk) | (val); \
restore(s); }
#define istart(rs) { set_porta( ~(PA_SRTS|PA_SDTR),0 ); \
(rs)->idevready = TRUE; }
#define istop(rs) { set_porta( ~PA_SDTR, PA_SRTS ); \
(rs)->idevready = FALSE; }
/* Macro to tell if device is ready. The rs->cts field is set to MS_CTS if
* CLOCAL is in effect for a line without a CTS wire.
*/
#define devready(rs) ((~MFP->mf_gpip | rs->cts) & MS_CTS)
/* Transmitter ready test */
#define txready(rs) (MFP->mf_tsr & (T_EMPTY | T_UE))
#endif /* MACHINE == ATARI */
/* Types. */
typedef unsigned char bool_t; /* boolean */
/* RS232 device structure, one per device. */
typedef struct rs232 {
tty_t *tty; /* associated TTY structure */
int icount; /* number of bytes in the input buffer */
char *ihead; /* next free spot in input buffer */
char *itail; /* first byte to give to TTY */
bool_t idevready; /* nonzero if we are ready to receive (RTS) */
char cts; /* normally 0, but MS_CTS if CLOCAL is set */
unsigned char ostate; /* combination of flags: */
#define ODONE 1 /* output completed (< output enable bits) */
#define ORAW 2 /* raw mode for xoff disable (< enab. bits) */
#define OWAKEUP 4 /* tty_wakeup() pending (asm code only) */
#define ODEVREADY MS_CTS /* external device hardware ready (CTS) */
#define OQUEUED 0x20 /* output buffer not empty */
#define OSWREADY 0x40 /* external device software ready (no xoff) */
#define ODEVHUP MS_RLSD /* external device has dropped carrier */
#define OSOFTBITS (ODONE | ORAW | OWAKEUP | OQUEUED | OSWREADY)
/* user-defined bits */
#if (OSOFTBITS | ODEVREADY | ODEVHUP) == OSOFTBITS
/* a weak sanity check */
#error /* bits are not unique */
#endif
unsigned char oxoff; /* char to stop output */
bool_t inhibited; /* output inhibited? (follows tty_inhibited) */
bool_t drain; /* if set drain output and reconfigure line */
int ocount; /* number of bytes in the output buffer */
char *ohead; /* next free spot in output buffer */
char *otail; /* next char to output */
#if (MACHINE == IBM_PC)
port_t xmit_port; /* i/o ports */
port_t recv_port;
port_t div_low_port;
port_t div_hi_port;
port_t int_enab_port;
port_t int_id_port;
port_t line_ctl_port;
port_t modem_ctl_port;
port_t line_status_port;
port_t modem_status_port;
#endif
unsigned char lstatus; /* last line status */
unsigned char pad; /* ensure alignment for 16-bit ints */
unsigned framing_errors; /* error counts (no reporting yet) */
unsigned overrun_errors;
unsigned parity_errors;
unsigned break_interrupts;
char ibuf[RS_IBUFSIZE]; /* input buffer */
char obuf[RS_OBUFSIZE]; /* output buffer */
} rs232_t;
PUBLIC rs232_t rs_lines[NR_RS_LINES];
/* Table and macro to translate an RS232 line number to its rs_lines entry. */
PRIVATE rs232_t *p_rs_addr[NR_RS_LINES];
#define rs_addr(line) (p_rs_addr[line])
#if (MACHINE == IBM_PC)
/* 8250 base addresses. */
PRIVATE port_t addr_8250[] = {
0x3F8, /* COM1: (line 0); COM3 might be at 0x3E8 */
0x2F8, /* COM2: (line 1); COM4 might be at 0x2E8 */
};
#endif
FORWARD _PROTOTYPE( int rs232_1handler, (int irq) );
FORWARD _PROTOTYPE( int rs232_2handler, (int irq) );
FORWARD _PROTOTYPE( void in_int, (rs232_t *rs) );
FORWARD _PROTOTYPE( void line_int, (rs232_t *rs) );
FORWARD _PROTOTYPE( void modem_int, (rs232_t *rs) );
FORWARD _PROTOTYPE( void rs_write, (tty_t *tp) );
FORWARD _PROTOTYPE( void rs_echo, (tty_t *tp, int c) );
FORWARD _PROTOTYPE( void rs_ioctl, (tty_t *tp) );
FORWARD _PROTOTYPE( void rs_config, (rs232_t *rs) );
FORWARD _PROTOTYPE( void rs_read, (tty_t *tp) );
FORWARD _PROTOTYPE( void rs_icancel, (tty_t *tp) );
FORWARD _PROTOTYPE( void rs_ocancel, (tty_t *tp) );
FORWARD _PROTOTYPE( void rs_ostart, (rs232_t *rs) );
FORWARD _PROTOTYPE( void rs_break, (tty_t *tp) );
FORWARD _PROTOTYPE( void out_int, (rs232_t *rs) );
/*==========================================================================*
* rs_write *
*==========================================================================*/
PRIVATE void rs_write(tp)
register tty_t *tp;
{
/* (*devwrite)() routine for RS232. */
rs232_t *rs = tp->tty_priv;
int count, ocount;
phys_bytes user_phys;
if (rs->inhibited != tp->tty_inhibited) {
/* Inhibition state has changed. */
lock();
rs->ostate |= OSWREADY;
if (tp->tty_inhibited) rs->ostate &= ~OSWREADY;
unlock();
rs->inhibited = tp->tty_inhibited;
}
if (rs->drain) {
/* Wait for the line to drain then reconfigure and continue output. */
if (rs->ocount > 0) return;
rs->drain = FALSE;
rs_config(rs);
}
/* While there is something to do. */
for (;;) {
ocount = buflen(rs->obuf) - rs->ocount;
count = bufend(rs->obuf) - rs->ohead;
if (count > ocount) count = ocount;
if (count > tp->tty_outleft) count = tp->tty_outleft;
if (count == 0 || tp->tty_inhibited) return;
/* Copy from user space to the RS232 output buffer. */
user_phys = proc_vir2phys(proc_addr(tp->tty_outproc), tp->tty_out_vir);
phys_copy(user_phys, vir2phys(rs->ohead), (phys_bytes) count);
/* Perform output processing on the output buffer. */
out_process(tp, rs->obuf, rs->ohead, bufend(rs->obuf), &count, &ocount);
if (count == 0) break;
/* Assume echoing messed up by output. */
tp->tty_reprint = TRUE;
/* Bookkeeping. */
lock(); /* protect interrupt sensitive rs->ocount */
rs->ocount += ocount;
rs_ostart(rs);
unlock();
if ((rs->ohead += ocount) >= bufend(rs->obuf))
rs->ohead -= buflen(rs->obuf);
tp->tty_out_vir += count;
tp->tty_outcum += count;
if ((tp->tty_outleft -= count) == 0) {
/* Output is finished, reply to the writer. */
tty_reply(tp->tty_outrepcode, tp->tty_outcaller,
tp->tty_outproc, tp->tty_outcum);
tp->tty_outcum = 0;
}
}
}
/*==========================================================================*
* rs_echo *
*==========================================================================*/
PRIVATE void rs_echo(tp, c)
tty_t *tp; /* which TTY */
int c; /* character to echo */
{
/* Echo one character. (Like rs_write, but only one character, optionally.) */
rs232_t *rs = tp->tty_priv;
int count, ocount;
ocount = buflen(rs->obuf) - rs->ocount;
if (ocount == 0) return; /* output buffer full */
count = 1;
*rs->ohead = c; /* add one character */
out_process(tp, rs->obuf, rs->ohead, bufend(rs->obuf), &count, &ocount);
if (count == 0) return;
lock();
rs->ocount += ocount;
rs_ostart(rs);
unlock();
if ((rs->ohead += ocount) >= bufend(rs->obuf)) rs->ohead -= buflen(rs->obuf);
}
/*==========================================================================*
* rs_ioctl *
*==========================================================================*/
PRIVATE void rs_ioctl(tp)
tty_t *tp; /* which TTY */
{
/* Reconfigure the line as soon as the output has drained. */
rs232_t *rs = tp->tty_priv;
rs->drain = TRUE;
}
/*==========================================================================*
* rs_config *
*==========================================================================*/
PRIVATE void rs_config(rs)
rs232_t *rs; /* which line */
{
/* Set various line control parameters for RS232 I/O.
* If DataBits == 5 and StopBits == 2, 8250 will generate 1.5 stop bits.
* The 8250 can't handle split speed, so we use the input speed.
*/
tty_t *tp = rs->tty;
int divisor;
int line_controls;
static struct speed2divisor {
speed_t speed;
int divisor;
} s2d[] = {
#if (MACHINE == IBM_PC)
{ B50, UART_FREQ / 50 },
#endif
{ B75, UART_FREQ / 75 },
{ B110, UART_FREQ / 110 },
{ B134, UART_FREQ * 10 / 1345 },
{ B150, UART_FREQ / 150 },
{ B200, UART_FREQ / 200 },
{ B300, UART_FREQ / 300 },
{ B600, UART_FREQ / 600 },
{ B1200, UART_FREQ / 1200 },
#if (MACHINE == IBM_PC)
{ B1800, UART_FREQ / 1800 },
#endif
{ B2400, UART_FREQ / 2400 },
{ B4800, UART_FREQ / 4800 },
{ B9600, UART_FREQ / 9600 },
{ B19200, UART_FREQ / 19200 },
#if (MACHINE == IBM_PC)
{ B38400, UART_FREQ / 38400 },
{ B57600, UART_FREQ / 57600 },
{ B115200, UART_FREQ / 115200L },
#endif
};
struct speed2divisor *s2dp;
/* RS232 needs to know the xoff character, and if CTS works. */
rs->oxoff = tp->tty_termios.c_cc[VSTOP];
rs->cts = (tp->tty_termios.c_cflag & CLOCAL) ? MS_CTS : 0;
/* Look up the 8250 rate divisor from the output speed. */
divisor = 0;
for (s2dp = s2d; s2dp < s2d + sizeof(s2d)/sizeof(s2d[0]); s2dp++) {
if (s2dp->speed == tp->tty_termios.c_ospeed) divisor = s2dp->divisor;
}
if (divisor == 0) return; /* B0? */
#if (MACHINE == IBM_PC)
/* Compute line control flag bits. */
line_controls = 0;
if (tp->tty_termios.c_cflag & PARENB) {
line_controls |= LC_PARITY;
if (!(tp->tty_termios.c_cflag & PARODD)) line_controls |= LC_PAREVEN;
}
if (divisor >= (UART_FREQ / 110)) line_controls |= LC_2STOP_BITS;
line_controls |= (tp->tty_termios.c_cflag & CSIZE) >> 2;
/* Lock out interrupts while setting the speed. The receiver register is
* going to be hidden by the div_low register, but the input interrupt
* handler relies on reading it to clear the interrupt and avoid looping
* forever.
*/
lock();
/* Select the baud rate divisor registers and change the rate. */
out_byte(rs->line_ctl_port, LC_ADDRESS_DIVISOR);
out_byte(rs->div_low_port, divisor);
out_byte(rs->div_hi_port, divisor >> 8);
/* Change the line controls and reselect the usual registers. */
out_byte(rs->line_ctl_port, line_controls);
rs->ostate |= ORAW;
if ((tp->tty_termios.c_lflag & IXON) && rs->oxoff != _POSIX_VDISABLE)
rs->ostate &= ~ORAW;
unlock();
#else /* MACHINE == ATARI */
line_controls = U_Q16;
if (tp->tty_termios.c_cflag & PARENB) {
line_controls |= U_PAR;
if (!(tp->tty_termios.c_cflag & PARODD)) line_controls |= U_EVEN;
}
line_controls |= (divisor >= (UART_FREQ / 110)) ? U_ST2 : U_ST1;
switch (tp->tty_termios.c_cflag & CSIZE) { /* XXX - are U_Dn like CSn? */
case CS5: line_controls |= U_D5; break;
case CS5: line_controls |= U_D6; break;
case CS5: line_controls |= U_D7; break;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -