293 lines
6.8 KiB
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;
|
|
}
|