diff --git a/kernel/com.cpp b/kernel/com.cpp index 8cd8cd57..8323262c 100644 --- a/kernel/com.cpp +++ b/kernel/com.cpp @@ -1,6 +1,6 @@ /******************************************************************************* - Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2014. This file is part of Sortix. @@ -23,6 +23,9 @@ *******************************************************************************/ #include +#include +#include +#include #include #include @@ -44,115 +47,92 @@ 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 +static const uint16_t TXR = 0; // Transmit register +static const uint16_t RXR = 0; // Receive register +static const uint16_t IER = 1; // Interrupt Enable +static const uint16_t IIR = 2; // Interrupt ID +static const uint16_t FCR = 2; // FIFO control +static const uint16_t LCR = 3; // Line control +static const uint16_t MCR = 4; // Modem control +static const uint16_t LSR = 5; // Line Status +static const uint16_t MSR = 6; // Modem Status +static const uint16_t SCR = 7; // Scratch Register +static const uint16_t DLL = 0; // Divisor Latch Low +static const uint16_t DLM = 1; // Divisor latch High -// 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 +static const uint8_t LCR_DLAB = 0x80; // Divisor latch access bit +static const uint8_t LCR_SBC = 0x40; // Set break control +static const uint8_t LCR_SPAR = 0x20; // Stick parity (?) +static const uint8_t LCR_EPAR = 0x10; // Even parity select +static const uint8_t LCR_PARITY = 0x08; // Parity Enable +static const uint8_t LCR_STOP = 0x04; // Stop bits: 0=1 bit, 1=2 bits +static const uint8_t LCR_WLEN5 = 0x00; // Wordlength: 5 bits +static const uint8_t LCR_WLEN6 = 0x01; // Wordlength: 6 bits +static const uint8_t LCR_WLEN7 = 0x02; // Wordlength: 7 bits +static const uint8_t LCR_WLEN8 = 0x03; // Wordlength: 8 bits -// 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 +static const uint8_t LSR_TEMT = 0x40; // Transmitter empty +static const uint8_t LSR_THRE = 0x20; // Transmit-hold-register empty +static const uint8_t LSR_READY = 0x01; // Data received +static const uint8_t LSR_BOTH_EMPTY = LSR_TEMT | LSR_THRE; -#if !POLL_EAGAIN && !POLL_HACK -#error The interrupt-based code was broken in the kthread branch. -#error You need to port this to the new thread/interrupt API. -#warning Oh, and fix the above mentioned bugs too. -#endif +static const uint8_t IIR_NO_INTERRUPT = 1 << 0; +static const uint8_t IIR_INTERRUPT_TYPE = 1 << 1 | 1 << 2 | 1 << 3; +static const uint8_t IIR_TIMEOUT = 1 << 2 | 1 << 3; +static const uint8_t IIR_RECV_LINE_STATUS = 1 << 1 | 1 << 2; +static const uint8_t IIR_RECV_DATA = 1 << 2; +static const uint8_t IIR_SENT_DATA = 1 << 1; +static const uint8_t IIR_MODEM_STATUS = 0; -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 +static const uint8_t IER_DATA = 1 << 0; +static const uint8_t IER_SENT = 1 << 1; +static const uint8_t IER_LINE_STATUS = 1 << 2; +static const uint8_t IER_MODEM_STATUS = 1 << 3; +static const uint8_t IER_SLEEP_MODE = 1 << 4; +static const uint8_t IER_LOW_POWER = 1 << 5; -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 +static const unsigned BASE_BAUD = 1843200 / 16; -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; +static const unsigned int UART_8250 = 1; +static const unsigned int UART_16450 = 2; +static const unsigned int UART_16550 = 3; +static const unsigned int UART_16550A = 4; +static const unsigned int UART_16750 = 5; -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; +static const size_t NUM_COM_PORTS = 4; // The IO base ports of each COM port. -static uint16_t comports[1+NUMCOMPORTS]; +static uint16_t com_ports[1 + NUM_COM_PORTS]; // The results of running HardwareProbe on each COM port. -unsigned hwversion[1+NUMCOMPORTS]; +static unsigned int hw_version[1 + NUM_COM_PORTS]; // Uses various characteristics of the UART chips to determine the hardware. -static unsigned HardwareProbe(uint16_t port) +static unsigned int HardwareProbe(uint16_t port) { // Set the value "0xE7" to the FCR to test the status of the FIFO flags. outport8(port + FCR, 0xE7); uint8_t iir = inport8(port + IIR); - if ( iir & (1U<<6U) ) + if ( iir & (1 << 6) ) { - if ( iir & (1<<7U) ) - { - return (iir & (1U<<5U)) ? UART16750 : UART16550A; - } - return UART16550; + if ( iir & (1 << 7) ) + return iir & (1 << 5) ? UART_16750 : UART_16550A; + return UART_16550; } // 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; - outport8(port + SCR, anyvalue); - return inport8(port + SCR) == anyvalue ? UART16450 : UART8250; + uint16_t any_value = 0x2A; + outport8(port + SCR, any_value); + return inport8(port + SCR) == any_value ? UART_16450 : UART_8250; } static inline void WaitForEmptyBuffers(uint16_t port) { - while ( (inport8(port + LSR) & LSR_BOTH_EMPTY) != LSR_BOTH_EMPTY ) { } + while ( (inport8(port + LSR) & LSR_BOTH_EMPTY) != LSR_BOTH_EMPTY ) + { + } } static inline bool IsLineReady(uint16_t port) @@ -165,53 +145,17 @@ static inline bool CanWriteByte(uint16_t port) return inport8(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 = inport8(port + IER); - outport8(port + IER, 0); - - for ( size_t i = 0; i < size; i++ ) - { - while ( !IsLineReady(port) ) { } - buffer[i] = inport8(port + RXR); - } - - WaitForEmptyBuffers(port); - outport8(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 = inport8(port + IER); - outport8(port + IER, 0); - - for ( size_t i = 0; i < size; i++ ) - { - while ( !CanWriteByte(port) ) { } - outport8(port + TXR, buffer[i]); - } - - WaitForEmptyBuffers(port); - outport8(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; + const uint16_t* bioscom_ports = (const uint16_t*) 0x0400UL; - for ( size_t i = 1; i <= NUMCOMPORTS; i++ ) + for ( size_t i = 1; i <= NUM_COM_PORTS; i++ ) { - comports[i] = bioscomports[i-1]; - if ( !comports[i] ) { continue; } - hwversion[i] = HardwareProbe(comports[i]); - outport8(comports[i] + IER, 0x0); + if ( !(com_ports[i] = bioscom_ports[i-1]) ) + continue; + hw_version[i] = HardwareProbe(com_ports[i]); + outport8(com_ports[i] + IER, 0x0); } } @@ -224,12 +168,11 @@ public: virtual ssize_t read(ioctx_t* ctx, uint8_t* buf, size_t count); virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count); -public: - void OnInterrupt(); - private: - kthread_mutex_t portlock; + kthread_mutex_t port_lock; uint16_t port; + uint8_t pending_input_byte; + bool has_pending_input_byte; }; @@ -238,13 +181,14 @@ DevCOMPort::DevCOMPort(dev_t dev, uid_t owner, gid_t group, mode_t mode, { inode_type = INODE_TYPE_STREAM; this->port = port; - this->portlock = KTHREAD_MUTEX_INITIALIZER; + this->port_lock = KTHREAD_MUTEX_INITIALIZER; this->stat_uid = owner; this->stat_gid = group; this->type = S_IFCHR; this->stat_mode = (mode & S_SETABLE) | this->type; this->dev = dev; this->ino = (ino_t) this; + this->has_pending_input_byte = false; } DevCOMPort::~DevCOMPort() @@ -253,37 +197,48 @@ DevCOMPort::~DevCOMPort() int DevCOMPort::sync(ioctx_t* /*ctx*/) { - // TODO: Not implemented yet, please wait for all outstanding requests. + ScopedLock lock(&port_lock); + WaitForEmptyBuffers(port); return 0; } -#if POLL_HACK - ssize_t DevCOMPort::read(ioctx_t* ctx, uint8_t* dest, size_t count) { - ScopedLock lock(&portlock); + ScopedLock lock(&port_lock); for ( size_t i = 0; i < count; i++ ) { - int tries = 0; - while ( !IsLineReady(port) ) + unsigned long attempt = 0; + while ( !has_pending_input_byte && !IsLineReady(port) ) { - if ( ++tries < 10 ) + attempt++; + if ( attempt <= 10 ) continue; + if ( attempt <= 15 && !(ctx->dflags & O_NONBLOCK) ) + { + kthread_yield(); + continue; + } if ( i ) return (ssize_t) i; if ( ctx->dflags & O_NONBLOCK ) return errno = EWOULDBLOCK, -1; if ( Signal::IsPending() ) return errno = EINTR, -1; + kthread_yield(); } - uint8_t val = inport8(port + RXR); - if ( !ctx->copy_to_dest(dest + i, &val, sizeof(val)) ) + uint8_t value = has_pending_input_byte ? + pending_input_byte : + inport8(port + RXR); + if ( !ctx->copy_to_dest(dest + i, &value, sizeof(value)) ) { - // TODO: The byte is lost in this case! + has_pending_input_byte = true; + pending_input_byte = value; return i ? (ssize_t) i : -1; } + + has_pending_input_byte = false; } return (ssize_t) count; @@ -291,15 +246,21 @@ ssize_t DevCOMPort::read(ioctx_t* ctx, uint8_t* dest, size_t count) ssize_t DevCOMPort::write(ioctx_t* ctx, const uint8_t* src, size_t count) { - ScopedLock lock(&portlock); + ScopedLock lock(&port_lock); for ( size_t i = 0; i < count; i++ ) { - int tries = 0; + unsigned long attempt = 0; while ( !CanWriteByte(port) ) { - if ( ++tries < 10 ) + attempt++; + if ( attempt <= 10 ) continue; + if ( attempt <= 15 && !(ctx->dflags & O_NONBLOCK) ) + { + kthread_yield(); + continue; + } if ( i ) return (ssize_t) i; if ( ctx->dflags & O_NONBLOCK ) @@ -308,154 +269,27 @@ ssize_t DevCOMPort::write(ioctx_t* ctx, const uint8_t* src, size_t count) return errno = EINTR, -1; } - uint8_t val; - if ( !ctx->copy_from_src(&val, src + i, sizeof(val)) ) + uint8_t value; + if ( !ctx->copy_from_src(&value, src + i, sizeof(value)) ) return i ? (ssize_t) i : -1; - outport8(port + TXR, val); + outport8(port + TXR, value); } return (ssize_t) count; } -#else - -#error Yeah, please port these to the new IO interface. - -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 = inport8(port + LSR); - if ( !(lsr & LSR_READY) ) - { - Panic("Can't wait for com data receive event"); - Error::Set(EBLOCKING); - return -1; - } - - size_t sofar = 0; - do - { - if ( count <= sofar ) { break; } - dest[sofar++] = inport8(port + RXR); - } while ( inport8(port + LSR) & LSR_READY); - - return sofar; -} - -ssize_t DevCOMPort::Write(const uint8_t* 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 = inport8(port + LSR); - if ( !(lsr & LSR_THRE) ) - { - Panic("Can't wait for com data sent event"); - Error::Set(EBLOCKING); - return -1; - } - - size_t sofar = 0; - do - { - if ( count <= sofar ) { break; } - outport8(port + TXR, src[sofar++]); - } while ( inport8(port + LSR) & LSR_THRE ); - - return sofar; -} - -#endif - -void DevCOMPort::OnInterrupt() -{ -#if POLL_HACK || POLL_BLOCKING - return; -#endif - - uint8_t iir = inport8(port + IIR); - if ( iir & IIR_NO_INTERRUPT ) { return; } - uint8_t intrtype = iir & IIR_INTERRUPT_TYPE; - switch ( intrtype ) - { - case IIR_TIMEOUT: - inport8(port + RXR); - break; - case IIR_RECV_LINE_STATUS: - // TODO: Proper error handling! - inport8(port + LSR); - break; - case IIR_RECV_DATA: - Panic("Can't wait for com data sent event"); - break; - case IIR_SENT_DATA: - Panic("Can't wait for com data sent event"); - inport8(port + IIR); - break; - case IIR_MODEM_STATUS: - inport8(port + MSR); - break; - } -} - -Ref comdevices[1+NUMCOMPORTS]; - -static void UARTIRQHandler(struct interrupt_context* /*intctx*/, void* /*user*/) -{ - for ( size_t i = 1; i <= NUMCOMPORTS; i++ ) - { - if ( !comdevices[i] ) { continue; } - comdevices[i]->OnInterrupt(); - } -} - -static struct interrupt_handler irq3_handler; -static struct interrupt_handler irq4_handler; +static Ref com_devices[1 + NUM_COM_PORTS]; void Init(const char* devpath, Ref slashdev) { ioctx_t ctx; SetupKernelIOCtx(&ctx); - for ( size_t i = 1; i <= NUMCOMPORTS; i++ ) + + for ( size_t i = 1; i <= NUM_COM_PORTS; i++ ) { - if ( !comports[i] ) { comdevices[i] = Ref(); continue; } - comdevices[i] = Ref - (new DevCOMPort(slashdev->dev, 0, 0, 0660, 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 ( LinkInodeInDir(&ctx, slashdev, name, comdevices[i]) != 0 ) - PanicF("Unable to link %s/%s to COM port driver.", devpath, name); - } - - irq3_handler.handler = UARTIRQHandler; - irq4_handler.handler = UARTIRQHandler; - - Interrupt::RegisterHandler(Interrupt::IRQ3, &irq3_handler); - Interrupt::RegisterHandler(Interrupt::IRQ4, &irq4_handler); - - // 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 + uint16_t port = com_ports[i]; + if ( !port ) + continue; uint8_t interrupts = 0; -#else - uint8_t interrupts = IER_DATA - | IER_SENT - | IER_LINE_STATUS - | IER_MODEM_STATUS; -#endif outport8(port + FCR, 0); outport8(port + LCR, 0x80); outport8(port + DLL, 0xC); @@ -464,6 +298,22 @@ void Init(const char* devpath, Ref slashdev) outport8(port + MCR, 0x3); // DTR + RTS outport8(port + IER, interrupts); } + + for ( size_t i = 1; i <= NUM_COM_PORTS; i++ ) + { + if ( !com_ports[i] ) + { + com_devices[i] = Ref(); + continue; + } + com_devices[i] = Ref(new DevCOMPort(slashdev->dev, 0, 0, 0660, com_ports[i])); + if ( !com_devices[i] ) + PanicF("Unable to allocate device for COM port %zu", i); + char name[3 + sizeof(size_t) * 3]; + snprintf(name, sizeof(name), "com%zu", i); + if ( LinkInodeInDir(&ctx, slashdev, name, com_devices[i]) != 0 ) + PanicF("Unable to link %s/%s to COM port driver.", devpath, name); + } } } // namespace COM diff --git a/kernel/com.h b/kernel/com.h index 7400b1bf..76b85bf6 100644 --- a/kernel/com.h +++ b/kernel/com.h @@ -1,6 +1,6 @@ /******************************************************************************* - Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2014. This file is part of Sortix. @@ -25,17 +25,16 @@ #ifndef SORTIX_COM_H #define SORTIX_COM_H +#include +#include + namespace Sortix { - -class Descriptor; - namespace COM { void EarlyInit(); void Init(const char* devpath, Ref slashdev); } // namespace COM - } // namespace Sortix #endif