sos-code-article10/drivers/serial.c

293 lines
6.8 KiB
C

/* Copyright (C) 2000 David Decotigny, The KOS Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
*/
#include <sos/errno.h>
#include <hwcore/ioports.h>
#include <sos/klibc.h>
#include <hwcore/irq.h>
#include <drivers/devices.h>
#include "tty.h"
#define SERIAL_BAUDRATE_MAX 115200
/* Default parameters */
#ifndef DEBUG_SERIAL_PORT
# define DEBUG_SERIAL_PORT 0
#endif
#ifndef DEBUG_SERIAL_SPEED
# define DEBUG_SERIAL_SPEED 9600
#endif
/* The offsets of UART registers. */
#define UART_TX 0
#define UART_RX 0
#define UART_DLL 0
#define UART_IER 1
#define UART_DLH 1
#define UART_IIR 2
#define UART_FCR 2
#define UART_LCR 3
#define UART_MCR 4
#define UART_LSR 5
#define UART_MSR 6
#define UART_SR 7
/* For LSR bits. */
#define UART_DATA_READY 0x01
#define UART_EMPTY_TRANSMITTER 0x20
/* The type of parity. */
#define UART_NO_PARITY 0x00
#define UART_ODD_PARITY 0x08
#define UART_EVEN_PARITY 0x18
/* The type of word length. */
#define UART_5BITS_WORD 0x00
#define UART_6BITS_WORD 0x01
#define UART_7BITS_WORD 0x02
#define UART_8BITS_WORD 0x03
/* The type of the length of stop bit. */
#define UART_1_STOP_BIT 0x00
#define UART_2_STOP_BITS 0x04
/* the switch of DLAB. */
#define UART_DLAB 0x80
/* Enable the FIFO. */
#define UART_ENABLE_FIFO 0xC7
/* Turn on DTR, RTS, and OUT2. */
#define UART_ENABLE_MODEM 0x0B
static struct
{
unsigned short iobase;
unsigned short is_enabled;
struct tty_device *tty;
} _serial_config [] = { { 0x3f8, FALSE },
{ 0x2f8, FALSE },
{ 0x3e8, FALSE },
{ 0x2e8, FALSE } };
#define SERIAL_PORT_MAX 4
#define serial_inb inb
#define serial_outb(port,val) outb(val,port)
inline static int
serial_isready (unsigned short port)
{
unsigned char status;
status = serial_inb (port + UART_LSR);
return (status & UART_DATA_READY) ? : -1;
}
static char
serial_getchar (unsigned short unit)
{
if(unit >= SERIAL_PORT_MAX
|| _serial_config[unit].is_enabled == FALSE)
return -1;
/* Wait until data is ready. */
while ((serial_inb (_serial_config[unit].iobase + UART_LSR)
& UART_DATA_READY) == 0)
;
/* Read and return the data. */
return serial_inb (_serial_config[unit].iobase + UART_RX);
}
/* 0 on success */
static int
serial_putchar (unsigned short port, char c)
{
/* Perhaps a timeout is necessary. */
int timeout = 10000;
/* Wait until the transmitter holding register is empty. */
while ((serial_inb (port + UART_LSR) & UART_EMPTY_TRANSMITTER) == 0)
if (--timeout == 0)
/* There is something wrong. But what can I do? */
return -1;
serial_outb (port + UART_TX, c);
return 0;
}
static char serial_printk_buf[1024];
inline static int serial_prints(unsigned short unit, const char *str)
{
const char *c;
unsigned short port;
if(unit >= SERIAL_PORT_MAX
|| _serial_config[unit].is_enabled == FALSE)
return -1;
port = _serial_config[unit].iobase;
for (c = str; *c != '\0'; c++)
serial_putchar(port, *c);
return (int) (c-str);
}
int sos_serial_printf(unsigned short unit, const char *format, ...)
{
va_list args;
int len;
va_start(args, format);
len=vsnprintf(serial_printk_buf,sizeof(serial_printk_buf),format,args);
return serial_prints(unit, serial_printk_buf);
}
static void serial_irq_handler (int irq_level)
{
unsigned char chr;
if (irq_level == SOS_IRQ_COM1)
{
char c[2];
chr = serial_inb (_serial_config[0].iobase + UART_RX);
/* Little hacks to get it to work with Qemu serial port
emulation. */
if (chr == '\r')
chr = '\n';
else if (chr == 127)
chr = '\b';
/* Build a null-terminated string */
c[0] = chr;
c[1] = '\0';
tty_add_chars (_serial_config[0].tty, c);
}
}
static sos_ret_t sos_serial_putchar (char c)
{
sos_ret_t ret;
unsigned int i;
/* The serial port doesn't understand '\b', but requires ANSI
commands instead. So we emulate '\b' by outputing "\e[D \e[D"
which basically goes backward one character, prints a space and
goes backward one character again. */
if (c == '\b')
{
const char *str = "\e[D \e[D";
for (i = 0; i < strlen(str); i++)
{
ret = serial_putchar (_serial_config[0].iobase, str[i]);
if (ret != SOS_OK)
return ret;
}
return SOS_OK;
}
return serial_putchar (_serial_config[0].iobase, c);
}
/* OK On success */
sos_ret_t sos_serial_subsystem_setup (sos_bool_t enable)
{
unsigned short div = 0;
unsigned char status = 0;
unsigned short serial_port;
unsigned short unit = 0;
unsigned int speed = 115200;
int word_len = UART_8BITS_WORD;
int parity = UART_NO_PARITY;
int stop_bit_len = UART_1_STOP_BIT;
if (unit >= SERIAL_PORT_MAX)
return -1;
serial_port = _serial_config[unit].iobase;
/* Turn off the interrupt. */
serial_outb (serial_port + UART_IER, 0);
/* Set DLAB. */
serial_outb (serial_port + UART_LCR, UART_DLAB);
/* Set the baud rate. */
if (speed > SERIAL_BAUDRATE_MAX)
return -1;
div = SERIAL_BAUDRATE_MAX / speed;
serial_outb (serial_port + UART_DLL, div & 0xFF);
serial_outb (serial_port + UART_DLH, div >> 8);
/* Set the line status. */
status |= parity | word_len | stop_bit_len;
serial_outb (serial_port + UART_LCR, status);
/* Enable the FIFO. */
serial_outb (serial_port + UART_FCR, UART_ENABLE_FIFO);
/* Turn on DTR, RTS, and OUT2. */
serial_outb (serial_port + UART_MCR, UART_ENABLE_MODEM);
/* Drain the input buffer. */
while (serial_isready (serial_port) != -1)
(void) serial_getchar (unit);
_serial_config[unit].is_enabled = TRUE;
return SOS_OK;
}
/* Cannot be placed in sos_serial_subsystem_init() because when it
gets called, the IRQ handling subsystem is not yet initialized */
sos_ret_t sos_ttyS0_subsystem_setup (void)
{
sos_ret_t ret;
ret = tty_create (SOS_CHARDEV_SERIAL_MINOR, sos_serial_putchar,
& _serial_config[0].tty);
if (SOS_OK != ret)
return ret;
sos_irq_set_routine (SOS_IRQ_COM1, serial_irq_handler);
/* Enable interrupts */
serial_outb (_serial_config[0].iobase + UART_IER, 1);
return SOS_OK;
}