/* 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 #include #include #include #include #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; }