1
0
Fork 0
mirror of https://gitlab.com/sortix/sortix.git synced 2023-02-13 20:55:38 -05:00

Clean up COM driver.

This commit is contained in:
Jonas 'Sortie' Termansen 2014-10-09 17:32:17 +02:00
parent 3ad7ab4fc3
commit 67cbc0715c
2 changed files with 133 additions and 284 deletions

View file

@ -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. This file is part of Sortix.
@ -23,6 +23,9 @@
*******************************************************************************/ *******************************************************************************/
#include <errno.h> #include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <sortix/fcntl.h> #include <sortix/fcntl.h>
#include <sortix/stat.h> #include <sortix/stat.h>
@ -44,115 +47,92 @@
namespace Sortix { namespace Sortix {
namespace COM { namespace COM {
// It appears this code is unable to get interrupts working correctly. Somehow static const uint16_t TXR = 0; // Transmit register
// we don't get interrupts upon receiving data, at least under VirtualBox. This static const uint16_t RXR = 0; // Receive register
// hack changes the code such that it polls occasionally instead. Hopefully this static const uint16_t IER = 1; // Interrupt Enable
// won't cause data loss, but I suspect that it will. static const uint16_t IIR = 2; // Interrupt ID
// TODO: It appears that this code causes kernel instability, possibly due to static const uint16_t FCR = 2; // FIFO control
// the broken way blocking system calls are implemented in Sortix. static const uint16_t LCR = 3; // Line control
#define POLL_HACK 1 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 static const uint8_t LCR_DLAB = 0x80; // Divisor latch access bit
// manner. While this may give nicer transfer speeds and less data loss, it static const uint8_t LCR_SBC = 0x40; // Set break control
// locks up the whole system. static const uint8_t LCR_SPAR = 0x20; // Stick parity (?)
#define POLL_BLOCKING 0 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- static const uint8_t LSR_TEMT = 0x40; // Transmitter empty
// space call retry, rather than relying on the broken syscall interstructure. static const uint8_t LSR_THRE = 0x20; // Transmit-hold-register empty
#define POLL_EAGAIN 1 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 static const uint8_t IIR_NO_INTERRUPT = 1 << 0;
#error The interrupt-based code was broken in the kthread branch. static const uint8_t IIR_INTERRUPT_TYPE = 1 << 1 | 1 << 2 | 1 << 3;
#error You need to port this to the new thread/interrupt API. static const uint8_t IIR_TIMEOUT = 1 << 2 | 1 << 3;
#warning Oh, and fix the above mentioned bugs too. static const uint8_t IIR_RECV_LINE_STATUS = 1 << 1 | 1 << 2;
#endif 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 static const uint8_t IER_DATA = 1 << 0;
const uint16_t RXR = 0; // Receive register static const uint8_t IER_SENT = 1 << 1;
const uint16_t IER = 1; // Interrupt Enable static const uint8_t IER_LINE_STATUS = 1 << 2;
const uint16_t IIR = 2; // Interrupt ID static const uint8_t IER_MODEM_STATUS = 1 << 3;
const uint16_t FCR = 2; // FIFO control static const uint8_t IER_SLEEP_MODE = 1 << 4;
const uint16_t LCR = 3; // Line control static const uint8_t IER_LOW_POWER = 1 << 5;
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 static const unsigned BASE_BAUD = 1843200 / 16;
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 static const unsigned int UART_8250 = 1;
const uint8_t LSR_THRE = 0x20; // Transmit-hold-register empty static const unsigned int UART_16450 = 2;
const uint8_t LSR_READY = 0x1; // Data received static const unsigned int UART_16550 = 3;
const uint8_t LSR_BOTH_EMPTY = LSR_TEMT | LSR_THRE; static const unsigned int UART_16550A = 4;
static const unsigned int UART_16750 = 5;
const uint8_t IIR_NO_INTERRUPT = (1U<<0U); static const size_t NUM_COM_PORTS = 4;
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. // 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. // 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. // 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. // Set the value "0xE7" to the FCR to test the status of the FIFO flags.
outport8(port + FCR, 0xE7); outport8(port + FCR, 0xE7);
uint8_t iir = inport8(port + IIR); uint8_t iir = inport8(port + IIR);
if ( iir & (1U<<6U) ) if ( iir & (1 << 6) )
{ {
if ( iir & (1<<7U) ) if ( iir & (1 << 7) )
{ return iir & (1 << 5) ? UART_16750 : UART_16550A;
return (iir & (1U<<5U)) ? UART16750 : UART16550A; return UART_16550;
}
return UART16550;
} }
// See if the scratch register returns what we write into it. The 8520 // 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 // doesn't do it. This is technically undefined behavior, but it is useful
// to detect hardware versions. // to detect hardware versions.
uint16_t anyvalue = 0x2A; uint16_t any_value = 0x2A;
outport8(port + SCR, anyvalue); outport8(port + SCR, any_value);
return inport8(port + SCR) == anyvalue ? UART16450 : UART8250; return inport8(port + SCR) == any_value ? UART_16450 : UART_8250;
} }
static inline void WaitForEmptyBuffers(uint16_t port) 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) static inline bool IsLineReady(uint16_t port)
@ -165,53 +145,17 @@ static inline bool CanWriteByte(uint16_t port)
return inport8(port + LSR) & LSR_THRE; 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() void EarlyInit()
{ {
// We can fetch COM port information from the BIOS Data Area. // 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 ( !(com_ports[i] = bioscom_ports[i-1]) )
if ( !comports[i] ) { continue; } continue;
hwversion[i] = HardwareProbe(comports[i]); hw_version[i] = HardwareProbe(com_ports[i]);
outport8(comports[i] + IER, 0x0); 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 read(ioctx_t* ctx, uint8_t* buf, size_t count);
virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count); virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count);
public:
void OnInterrupt();
private: private:
kthread_mutex_t portlock; kthread_mutex_t port_lock;
uint16_t port; 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; inode_type = INODE_TYPE_STREAM;
this->port = port; this->port = port;
this->portlock = KTHREAD_MUTEX_INITIALIZER; this->port_lock = KTHREAD_MUTEX_INITIALIZER;
this->stat_uid = owner; this->stat_uid = owner;
this->stat_gid = group; this->stat_gid = group;
this->type = S_IFCHR; this->type = S_IFCHR;
this->stat_mode = (mode & S_SETABLE) | this->type; this->stat_mode = (mode & S_SETABLE) | this->type;
this->dev = dev; this->dev = dev;
this->ino = (ino_t) this; this->ino = (ino_t) this;
this->has_pending_input_byte = false;
} }
DevCOMPort::~DevCOMPort() DevCOMPort::~DevCOMPort()
@ -253,37 +197,48 @@ DevCOMPort::~DevCOMPort()
int DevCOMPort::sync(ioctx_t* /*ctx*/) int DevCOMPort::sync(ioctx_t* /*ctx*/)
{ {
// TODO: Not implemented yet, please wait for all outstanding requests. ScopedLock lock(&port_lock);
WaitForEmptyBuffers(port);
return 0; return 0;
} }
#if POLL_HACK
ssize_t DevCOMPort::read(ioctx_t* ctx, uint8_t* dest, size_t count) 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++ ) for ( size_t i = 0; i < count; i++ )
{ {
int tries = 0; unsigned long attempt = 0;
while ( !IsLineReady(port) ) while ( !has_pending_input_byte && !IsLineReady(port) )
{ {
if ( ++tries < 10 ) attempt++;
if ( attempt <= 10 )
continue; continue;
if ( attempt <= 15 && !(ctx->dflags & O_NONBLOCK) )
{
kthread_yield();
continue;
}
if ( i ) if ( i )
return (ssize_t) i; return (ssize_t) i;
if ( ctx->dflags & O_NONBLOCK ) if ( ctx->dflags & O_NONBLOCK )
return errno = EWOULDBLOCK, -1; return errno = EWOULDBLOCK, -1;
if ( Signal::IsPending() ) if ( Signal::IsPending() )
return errno = EINTR, -1; return errno = EINTR, -1;
kthread_yield();
} }
uint8_t val = inport8(port + RXR); uint8_t value = has_pending_input_byte ?
if ( !ctx->copy_to_dest(dest + i, &val, sizeof(val)) ) 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; return i ? (ssize_t) i : -1;
} }
has_pending_input_byte = false;
} }
return (ssize_t) count; 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) 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++ ) for ( size_t i = 0; i < count; i++ )
{ {
int tries = 0; unsigned long attempt = 0;
while ( !CanWriteByte(port) ) while ( !CanWriteByte(port) )
{ {
if ( ++tries < 10 ) attempt++;
if ( attempt <= 10 )
continue; continue;
if ( attempt <= 15 && !(ctx->dflags & O_NONBLOCK) )
{
kthread_yield();
continue;
}
if ( i ) if ( i )
return (ssize_t) i; return (ssize_t) i;
if ( ctx->dflags & O_NONBLOCK ) 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; return errno = EINTR, -1;
} }
uint8_t val; uint8_t value;
if ( !ctx->copy_from_src(&val, src + i, sizeof(val)) ) if ( !ctx->copy_from_src(&value, src + i, sizeof(value)) )
return i ? (ssize_t) i : -1; return i ? (ssize_t) i : -1;
outport8(port + TXR, val); outport8(port + TXR, value);
} }
return (ssize_t) count; return (ssize_t) count;
} }
#else static Ref<DevCOMPort> com_devices[1 + NUM_COM_PORTS];
#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<DevCOMPort> 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;
void Init(const char* devpath, Ref<Descriptor> slashdev) void Init(const char* devpath, Ref<Descriptor> slashdev)
{ {
ioctx_t ctx; SetupKernelIOCtx(&ctx); 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<DevCOMPort>(); continue; } uint16_t port = com_ports[i];
comdevices[i] = Ref<DevCOMPort> if ( !port )
(new DevCOMPort(slashdev->dev, 0, 0, 0660, comports[i])); continue;
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
uint8_t interrupts = 0; 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 + FCR, 0);
outport8(port + LCR, 0x80); outport8(port + LCR, 0x80);
outport8(port + DLL, 0xC); outport8(port + DLL, 0xC);
@ -464,6 +298,22 @@ void Init(const char* devpath, Ref<Descriptor> slashdev)
outport8(port + MCR, 0x3); // DTR + RTS outport8(port + MCR, 0x3); // DTR + RTS
outport8(port + IER, interrupts); outport8(port + IER, interrupts);
} }
for ( size_t i = 1; i <= NUM_COM_PORTS; i++ )
{
if ( !com_ports[i] )
{
com_devices[i] = Ref<DevCOMPort>();
continue;
}
com_devices[i] = Ref<DevCOMPort>(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 } // namespace COM

View file

@ -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. This file is part of Sortix.
@ -25,17 +25,16 @@
#ifndef SORTIX_COM_H #ifndef SORTIX_COM_H
#define SORTIX_COM_H #define SORTIX_COM_H
#include <sortix/kernel/descriptor.h>
#include <sortix/kernel/refcount.h>
namespace Sortix { namespace Sortix {
class Descriptor;
namespace COM { namespace COM {
void EarlyInit(); void EarlyInit();
void Init(const char* devpath, Ref<Descriptor> slashdev); void Init(const char* devpath, Ref<Descriptor> slashdev);
} // namespace COM } // namespace COM
} // namespace Sortix } // namespace Sortix
#endif #endif