From 0364ce6f55f9b54d83477fa393742b8fa21c4a1f Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sat, 17 Mar 2012 15:18:03 +0100 Subject: [PATCH] Added a new COM Port driver. Any detected COM ports available as /dev/comN. It currently utilizes AGAIN to do polling in user-space. This prevents it from locking up the whole system and makes it respond to the SIGINT hack. There is also a more reliable and faster polling-blocking mode, but it locks up the entire system. The main interrupt mode is broken, perhaps by a bug in VirtualBox. --- sortix/Makefile | 1 + sortix/com.cpp | 455 ++++++++++++++++++++++++++++++++++++++++++++++ sortix/com.h | 37 ++++ sortix/kernel.cpp | 6 + 4 files changed, 499 insertions(+) create mode 100644 sortix/com.cpp create mode 100644 sortix/com.h diff --git a/sortix/Makefile b/sortix/Makefile index 00af442c..bf17150c 100644 --- a/sortix/Makefile +++ b/sortix/Makefile @@ -111,6 +111,7 @@ scheduler.o \ syscall.o \ sound.o \ pci.o \ +com.o \ uart.o \ terminal.o \ linebuffer.o \ diff --git a/sortix/com.cpp b/sortix/com.cpp new file mode 100644 index 00000000..70a483e3 --- /dev/null +++ b/sortix/com.cpp @@ -0,0 +1,455 @@ +/******************************************************************************* + + COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011, 2012. + + This file is part of Sortix. + + Sortix 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 3 of the License, or (at your option) any later + version. + + Sortix 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 + Sortix. If not, see . + + com.cpp + Handles communication to COM serial ports. + +*******************************************************************************/ + +#include "platform.h" +#include +#include "interrupt.h" +#include "event.h" +#include "stream.h" +#include "syscall.h" +#include "thread.h" +#include "fs/devfs.h" +#include "com.h" + +using namespace Maxsi; + +namespace Sortix { +namespace COM { + +// It appears this code is unable to get interrupts working correctly. Somehow +// we don't get interrupts upon receiving data, at least under VirtualBox. This +// hack changes the code such that it polls occasionally instead. Hopefully this +// won't cause data loss, but I suspect that it will. +// TODO: It appears that this code causes kernel instability, possibly due to +// the broken way blocking system calls are implemented in Sortix. +#define POLL_HACK 1 + +// Another alternative is to use the polling code in a completely blocking +// manner. While this may give nicer transfer speeds and less data loss, it +// locks up the whole system. +#define POLL_BLOCKING 0 + +// Yet another alternative is to use POLL_HACK, but return EGAIN and let user- +// space call retry, rather than relying on the broken syscall interstructure. +#define POLL_EAGAIN 1 + +const uint16_t TXR = 0; // Transmit register +const uint16_t RXR = 0; // Receive register +const uint16_t IER = 1; // Interrupt Enable +const uint16_t IIR = 2; // Interrupt ID +const uint16_t FCR = 2; // FIFO control +const uint16_t LCR = 3; // Line control +const uint16_t MCR = 4; // Modem control +const uint16_t LSR = 5; // Line Status +const uint16_t MSR = 6; // Modem Status +const uint16_t SCR = 7; // Scratch Register +const uint16_t DLL = 0; // Divisor Latch Low +const uint16_t DLM = 1; // Divisor latch High + +const uint8_t LCR_DLAB = 0x80; // Divisor latch access bit +const uint8_t LCR_SBC = 0x40; // Set break control +const uint8_t LCR_SPAR = 0x20; // Stick parity (?) +const uint8_t LCR_EPAR = 0x10; // Even parity select +const uint8_t LCR_PARITY = 0x08; // Parity Enable +const uint8_t LCR_STOP = 0x04; // Stop bits: 0=1 bit, 1=2 bits +const uint8_t LCR_WLEN5 = 0x00; // Wordlength: 5 bits +const uint8_t LCR_WLEN6 = 0x01; // Wordlength: 6 bits +const uint8_t LCR_WLEN7 = 0x02; // Wordlength: 7 bits +const uint8_t LCR_WLEN8 = 0x03; // Wordlength: 8 bits + +const uint8_t LSR_TEMT = 0x40; // Transmitter empty +const uint8_t LSR_THRE = 0x20; // Transmit-hold-register empty +const uint8_t LSR_READY = 0x1; // Data received +const uint8_t LSR_BOTH_EMPTY = LSR_TEMT | LSR_THRE; + +const uint8_t IIR_NO_INTERRUPT = (1U<<0U); +const uint8_t IIR_INTERRUPT_TYPE = ((1U<<1U) | (1U<<2U) | (1U<<3U)); +const uint8_t IIR_TIMEOUT = ((1U<<2U) | (1U<<3U)); +const uint8_t IIR_RECV_LINE_STATUS = ((1U<<1U) | (1U<<2U)); +const uint8_t IIR_RECV_DATA = (1U<<2U); +const uint8_t IIR_SENT_DATA = (1U<<1U); +const uint8_t IIR_MODEM_STATUS = 0; + +const uint8_t IER_DATA = (1U<<0U); +const uint8_t IER_SENT = (1U<<1U); +const uint8_t IER_LINE_STATUS = (1U<<2U); +const uint8_t IER_MODEM_STATUS = (1U<<3U); +const uint8_t IER_SLEEP_MODE = (1U<<4U); +const uint8_t IER_LOW_POWER = (1U<<5U); + +const unsigned BASE_BAUD = 1843200/16; + +const unsigned UART8250 = 1; +const unsigned UART16450 = 2; +const unsigned UART16550 = 3; +const unsigned UART16550A = 4; +const unsigned UART16750 = 5; + +const size_t NUMCOMPORTS = 4; + +// The IO base ports of each COM port. +static uint16_t comports[1+NUMCOMPORTS]; + +// The results of running HardwareProbe on each COM port. +unsigned hwversion[1+NUMCOMPORTS]; + +// Uses various characteristics of the UART chips to determine the hardware. +static unsigned HardwareProbe(uint16_t port) +{ + // Set the value "0xE7" to the FCR to test the status of the FIFO flags. + CPU::OutPortB(port + FCR, 0xE7); + uint8_t iir = CPU::InPortB(port + IIR); + if ( iir & (1U<<6U) ) + { + if ( iir & (1<<7U) ) + { + return (iir & (1U<<5U)) ? UART16750 : UART16550A; + } + return UART16550; + } + + // See if the scratch register returns what we write into it. The 8520 + // doesn't do it. This is technically undefined behavior, but it is useful + // to detect hardware versions. + uint16_t anyvalue = 0x2A; + CPU::OutPortB(port + SCR, anyvalue); + return CPU::InPortB(port + SCR) == anyvalue ? UART16450 : UART8250; +} + +static inline void WaitForEmptyBuffers(uint16_t port) +{ + while ( (CPU::InPortB(port + LSR) & LSR_BOTH_EMPTY) != LSR_BOTH_EMPTY ) { } +} + +static inline bool IsLineReady(uint16_t port) +{ + return CPU::InPortB(port + LSR) & LSR_READY; +} + +static inline bool CanWriteByte(uint16_t port) +{ + return CPU::InPortB(port + LSR) & LSR_THRE; +} + +ssize_t ReadBlocking(uint16_t port, void* buf, size_t size) +{ + if ( SSIZE_MAX < size ) { size = SSIZE_MAX; } + uint8_t* buffer = (uint8_t*) buf; + uint8_t interruptsenabled = CPU::InPortB(port + IER); + CPU::OutPortB(port + IER, 0); + + for ( size_t i = 0; i < size; i++ ) + { + while ( !IsLineReady(port) ) { } + buffer[i] = CPU::InPortB(port + RXR); + } + + WaitForEmptyBuffers(port); + CPU::OutPortB(port + IER, interruptsenabled); + return size; +} + +ssize_t WriteBlocking(uint16_t port, const void* buf, size_t size) +{ + if ( SSIZE_MAX < size ) { size = SSIZE_MAX; } + const uint8_t* buffer = (const uint8_t*) buf; + uint8_t interruptsenabled = CPU::InPortB(port + IER); + CPU::OutPortB(port + IER, 0); + + for ( size_t i = 0; i < size; i++ ) + { + while ( !CanWriteByte(port) ) { } + CPU::OutPortB(port + TXR, buffer[i]); + } + + WaitForEmptyBuffers(port); + CPU::OutPortB(port + IER, interruptsenabled); + return size; +} + +void EarlyInit() +{ + // We can fetch COM port information from the BIOS Data Area. + volatile uint16_t* const bioscomports = (uint16_t* const) 0x0400UL; + + for ( size_t i = 1; i <= NUMCOMPORTS; i++ ) + { + comports[i] = bioscomports[i-1]; + if ( !comports[i] ) { continue; } + hwversion[i] = HardwareProbe(comports[i]); + CPU::OutPortB(comports[i] + IER, 0x0); + } +} + +class DevCOMPort : public DevStream +{ +public: + typedef DevStream BaseClass; + +public: + DevCOMPort(uint16_t port); + virtual ~DevCOMPort(); + +public: + virtual ssize_t Read(byte* dest, size_t count); + virtual ssize_t Write(const byte* src, size_t count); + virtual bool IsReadable(); + virtual bool IsWritable(); + +public: + void OnInterrupt(); + +private: + uint16_t port; + Event dataevent; + Event sentevent; + +}; + +DevCOMPort::DevCOMPort(uint16_t port) +{ + this->port = port; +} + +DevCOMPort::~DevCOMPort() +{ +} + +bool DevCOMPort::IsReadable() { return true; } +bool DevCOMPort::IsWritable() { return true; } + +#if POLL_HACK + +const unsigned TRIES = 1000; + +ssize_t DevCOMPort::Read(byte* dest, size_t count) +{ + if ( !count ) { return 0; } + if ( SSIZE_MAX < count ) { count = SSIZE_MAX; } + + uint8_t lsr; + for ( unsigned i = 0; i < TRIES; i++ ) + { + lsr = CPU::InPortB(port + LSR); + if ( lsr & LSR_READY ) { break; } + } + + if ( !(lsr & LSR_READY) ) + { +#if POLL_EAGAIN + Error::Set(EAGAIN); +#else + Error::Set(EBLOCKING); + Syscall::Yield(); +#endif + return -1; + } + + size_t sofar = 0; + do + { + if ( count <= sofar ) { break; } + dest[sofar++] = CPU::InPortB(port + RXR); + } while ( CPU::InPortB(port + LSR) & LSR_READY); + + return sofar; +} + +ssize_t DevCOMPort::Write(const byte* src, size_t count) +{ + if ( !count ) { return 0; } + if ( SSIZE_MAX < count ) { count = SSIZE_MAX; }; + + uint8_t lsr; + for ( unsigned i = 0; i < TRIES; i++ ) + { + lsr = CPU::InPortB(port + LSR); + if ( lsr & LSR_THRE ) { break; } + } + + if ( !(lsr & LSR_THRE) ) + { +#if POLL_EAGAIN + Error::Set(EAGAIN); +#else + Error::Set(EBLOCKING); + Syscall::Yield(); +#endif + return -1; + } + + size_t sofar = 0; + do + { + if ( count <= sofar ) { break; } + CPU::OutPortB(port + TXR, src[sofar++]); + } while ( CPU::InPortB(port + LSR) & LSR_THRE ); + + return sofar; +} + +#else + +ssize_t DevCOMPort::Read(byte* dest, size_t count) +{ + if ( !count ) { return 0; } + if ( SSIZE_MAX < count ) { count = SSIZE_MAX; } +#if POLL_BLOCKING + return ReadBlocking(port, dest, 1); +#endif + uint8_t lsr = CPU::InPortB(port + LSR); + if ( !(lsr & LSR_READY) ) + { + dataevent.Register(); + Error::Set(EBLOCKING); + return -1; + } + + size_t sofar = 0; + do + { + if ( count <= sofar ) { break; } + dest[sofar++] = CPU::InPortB(port + RXR); + } while ( CPU::InPortB(port + LSR) & LSR_READY); + + return sofar; +} + +ssize_t DevCOMPort::Write(const byte* src, size_t count) +{ + if ( !count ) { return 0; } + if ( SSIZE_MAX < count ) { count = SSIZE_MAX; }; +#if POLL_BLOCKING + return WriteBlocking(port, src, 1); +#endif + uint8_t lsr = CPU::InPortB(port + LSR); + if ( !(lsr & LSR_THRE) ) + { + sentevent.Register(); + Error::Set(EBLOCKING); + return -1; + } + + size_t sofar = 0; + do + { + if ( count <= sofar ) { break; } + CPU::OutPortB(port + TXR, src[sofar++]); + } while ( CPU::InPortB(port + LSR) & LSR_THRE ); + + return sofar; +} + +#endif + +void DevCOMPort::OnInterrupt() +{ +#if POLL_HACK || POLL_BLOCKING + return; +#endif + + uint8_t iir = CPU::InPortB(port + IIR); + if ( iir & IIR_NO_INTERRUPT ) { return; } + uint8_t intrtype = iir & IIR_INTERRUPT_TYPE; + switch ( intrtype ) + { + case IIR_TIMEOUT: + CPU::InPortB(port + RXR); + break; + case IIR_RECV_LINE_STATUS: + // TODO: Proper error handling! + CPU::InPortB(port + LSR); + break; + case IIR_RECV_DATA: + dataevent.Signal(); + break; + case IIR_SENT_DATA: + sentevent.Signal(); + CPU::InPortB(port + IIR); + break; + case IIR_MODEM_STATUS: + CPU::InPortB(port + MSR); + break; + } +} + +DevCOMPort* comdevices[1+NUMCOMPORTS]; + +static void UARTIRQHandler(CPU::InterruptRegisters* /*regs*/, void* /*user*/) +{ + for ( size_t i = 1; i <= NUMCOMPORTS; i++ ) + { + if ( !comdevices[i] ) { continue; } + comdevices[i]->OnInterrupt(); + } +} + +void Init() +{ + for ( size_t i = 1; i <= NUMCOMPORTS; i++ ) + { + if ( !comports[i] ) { comdevices[i] = NULL; continue; } + comdevices[i] = new DevCOMPort(comports[i]); + if ( !comdevices[i] ) + { + PanicF("Unable to allocate device for COM port %zu at 0x%x", i, + comports[i]); + } + char name[5] = "comN"; + name[3] = '0' + i; + if ( !DeviceFS::RegisterDevice(name, comdevices[i]) ) + { + PanicF("Unable to register device /dev/%s", name); + } + } + + Interrupt::RegisterHandler(Interrupt::IRQ3, UARTIRQHandler, NULL); + Interrupt::RegisterHandler(Interrupt::IRQ4, UARTIRQHandler, NULL); + + // Initialize the ports so we can transfer data. + for ( size_t i = 1; i <= NUMCOMPORTS; i++ ) + { + uint16_t port = comports[i]; + if ( !port ) { continue; } +#if POLL_HACK || POLL_BLOCKING + uint8_t interrupts = 0; +#else + uint8_t interrupts = IER_DATA + | IER_SENT + | IER_LINE_STATUS + | IER_MODEM_STATUS; +#endif + CPU::OutPortB(port + FCR, 0); + CPU::OutPortB(port + LCR, 0x80); + CPU::OutPortB(port + DLL, 0xC); + CPU::OutPortB(port + DLM, 0x0); + CPU::OutPortB(port + LCR, 0x3); // 8n1 + CPU::OutPortB(port + MCR, 0x3); // DTR + RTS + CPU::OutPortB(port + IER, interrupts); + } +} + +} // namespace COM +} // namespace Sortix + diff --git a/sortix/com.h b/sortix/com.h new file mode 100644 index 00000000..c1ef13a9 --- /dev/null +++ b/sortix/com.h @@ -0,0 +1,37 @@ +/******************************************************************************* + + COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011, 2012. + + This file is part of Sortix. + + Sortix 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 3 of the License, or (at your option) any later + version. + + Sortix 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 + Sortix. If not, see . + + com.cpp + Handles communication to COM serial ports. + +*******************************************************************************/ + +#ifndef SORTIX_COM_H +#define SORTIX_COM_H + +namespace Sortix { +namespace COM { + +void EarlyInit(); +void Init(); + +} // namespace COM +} // namespace Sortix + +#endif diff --git a/sortix/kernel.cpp b/sortix/kernel.cpp index 803225cb..f6c2a771 100644 --- a/sortix/kernel.cpp +++ b/sortix/kernel.cpp @@ -40,6 +40,7 @@ #include "scheduler.h" #include "syscall.h" #include "pci.h" +#include "com.h" #include "uart.h" #include "terminal.h" #include "serialterminal.h" @@ -184,6 +185,8 @@ namespace Sortix if ( BootInfo == NULL ) { Panic("kernel.cpp: The bootinfo structure was NULL. Are your bootloader multiboot compliant?"); } + COM::EarlyInit(); + addr_t initrd = NULL; size_t initrdsize = 0; @@ -221,6 +224,9 @@ namespace Sortix // Initialize the list of kernel devices. DeviceFS::Init(); + // Initialize the COM ports. + COM::Init(); + // Initialize the keyboard. Keyboard::Init();