340 lines
9.5 KiB
C++
340 lines
9.5 KiB
C++
/*
|
|
* Copyright (c) 2011, 2012, 2014, 2015, 2016, 2023 Jonas 'Sortie' Termansen.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* com.cpp
|
|
* Handles communication to COM serial ports.
|
|
*/
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
|
|
#include <sortix/fcntl.h>
|
|
#include <sortix/stat.h>
|
|
|
|
#include <sortix/kernel/descriptor.h>
|
|
#include <sortix/kernel/inode.h>
|
|
#include <sortix/kernel/interlock.h>
|
|
#include <sortix/kernel/interrupt.h>
|
|
#include <sortix/kernel/ioctx.h>
|
|
#include <sortix/kernel/ioport.h>
|
|
#include <sortix/kernel/kernel.h>
|
|
#include <sortix/kernel/kthread.h>
|
|
#include <sortix/kernel/process.h>
|
|
#include <sortix/kernel/refcount.h>
|
|
#include <sortix/kernel/thread.h>
|
|
|
|
#include "com.h"
|
|
#include "tty.h"
|
|
|
|
extern "C" unsigned char nullpage[4096];
|
|
|
|
namespace Sortix {
|
|
namespace COM {
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
static const unsigned BASE_BAUD = 1843200 / 16;
|
|
|
|
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;
|
|
|
|
static const size_t NUM_COM_PORTS = 4;
|
|
|
|
// Uses various characteristics of the UART chips to determine the hardware.
|
|
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 & (1 << 6) )
|
|
{
|
|
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 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 )
|
|
{
|
|
}
|
|
}
|
|
|
|
static inline bool IsLineReady(uint16_t port)
|
|
{
|
|
return inport8(port + LSR) & LSR_READY;
|
|
}
|
|
|
|
static inline bool CanWriteByte(uint16_t port)
|
|
{
|
|
return inport8(port + LSR) & LSR_THRE;
|
|
}
|
|
|
|
class DevCOMPort : public TTY
|
|
{
|
|
public:
|
|
DevCOMPort(dev_t dev, uid_t owner, gid_t group, mode_t mode, uint16_t port,
|
|
const char* name);
|
|
virtual ~DevCOMPort();
|
|
virtual int ioctl(ioctx_t* ctx, int cmd, uintptr_t arg);
|
|
virtual int sync(ioctx_t* ctx);
|
|
virtual void tty_output(const unsigned char* buffer, size_t length);
|
|
|
|
public:
|
|
bool Initialize(int interrupt);
|
|
|
|
private:
|
|
static void InterruptHandler(struct interrupt_context*, void*);
|
|
static void InterruptWorkHandler(void* context);
|
|
void OnInterrupt();
|
|
void InterruptWork();
|
|
|
|
private:
|
|
kthread_mutex_t port_lock;
|
|
struct interrupt_handler irq_registration;
|
|
struct interrupt_work interrupt_work;
|
|
struct winsize ws;
|
|
uint16_t port;
|
|
uint8_t pending_input_byte;
|
|
bool has_pending_input_byte;
|
|
|
|
};
|
|
|
|
DevCOMPort::DevCOMPort(dev_t dev, uid_t owner, gid_t group, mode_t mode,
|
|
uint16_t port, const char* name) : TTY(dev, ino, mode,
|
|
owner, group, name)
|
|
{
|
|
this->port = port;
|
|
this->port_lock = KTHREAD_MUTEX_INITIALIZER;
|
|
this->has_pending_input_byte = false;
|
|
interrupt_work.handler = InterruptWorkHandler;
|
|
interrupt_work.context = this;
|
|
}
|
|
|
|
DevCOMPort::~DevCOMPort()
|
|
{
|
|
}
|
|
|
|
bool DevCOMPort::Initialize(int interrupt)
|
|
{
|
|
uint8_t interrupts = 1;
|
|
// TODO: This was 9600.
|
|
tio.c_ispeed = B19200;
|
|
tio.c_ospeed = B19200;
|
|
uint16_t divisor = 115200 / tio.c_ispeed;
|
|
outport8(port + FCR, 0);
|
|
outport8(port + LCR, LCR_DLAB);
|
|
outport8(port + DLL, divisor & 0xFF);
|
|
outport8(port + DLM, divisor >> 8);
|
|
outport8(port + LCR, LCR_WLEN8); // 8n1
|
|
outport8(port + MCR, 0x1 /* DTR */ | 0x2 /* RTS */);
|
|
outport8(port + IER, interrupts);
|
|
irq_registration.handler = DevCOMPort::InterruptHandler;
|
|
irq_registration.context = this;
|
|
Interrupt::RegisterHandler(interrupt, &irq_registration);
|
|
return true;
|
|
}
|
|
|
|
void DevCOMPort::InterruptHandler(struct interrupt_context*, void* user)
|
|
{
|
|
((DevCOMPort*) user)->OnInterrupt();
|
|
}
|
|
|
|
void DevCOMPort::OnInterrupt()
|
|
{
|
|
if ( !IsLineReady(port) )
|
|
return;
|
|
Interrupt::ScheduleWork(&interrupt_work);
|
|
}
|
|
|
|
void DevCOMPort::InterruptWorkHandler(void* context)
|
|
{
|
|
((DevCOMPort*) context)->InterruptWork();
|
|
}
|
|
|
|
void DevCOMPort::InterruptWork()
|
|
{
|
|
ScopedLock lock1(&termlock);
|
|
ScopedLock lock2(&port_lock);
|
|
while ( IsLineReady(port) )
|
|
{
|
|
unsigned char byte = inport8(port + RXR);
|
|
if ( tio.c_cflag & CREAD )
|
|
ProcessByte(byte);
|
|
}
|
|
}
|
|
|
|
int DevCOMPort::ioctl(ioctx_t* ctx, int cmd, uintptr_t arg)
|
|
{
|
|
ScopedLock lock(&termlock);
|
|
if ( hungup )
|
|
return errno = EIO, -1;
|
|
if ( cmd == TIOCGWINSZ )
|
|
{
|
|
struct winsize* user_ws = (struct winsize*) arg;
|
|
if ( !ctx->copy_to_dest(user_ws, &ws, sizeof(ws)) )
|
|
return -1;
|
|
return 0;
|
|
}
|
|
else if ( cmd == TIOCSWINSZ )
|
|
{
|
|
const struct winsize* user_ws = (const struct winsize*) arg;
|
|
if ( !ctx->copy_from_src(&ws, user_ws, sizeof(ws)) )
|
|
return -1;
|
|
winch();
|
|
return 0;
|
|
}
|
|
lock.Reset();
|
|
return TTY::ioctl(ctx, cmd, arg);
|
|
}
|
|
|
|
int DevCOMPort::sync(ioctx_t* /*ctx*/)
|
|
{
|
|
ScopedLock lock(&port_lock);
|
|
WaitForEmptyBuffers(port);
|
|
return 0;
|
|
}
|
|
|
|
void DevCOMPort::tty_output(const unsigned char* buffer, size_t length)
|
|
{
|
|
for ( size_t i = 0; i < length; i++ )
|
|
{
|
|
unsigned long attempt = 0;
|
|
while ( !CanWriteByte(port) )
|
|
{
|
|
attempt++;
|
|
if ( attempt <= 10 )
|
|
continue;
|
|
if ( attempt <= 15 )
|
|
{
|
|
kthread_mutex_unlock(&port_lock);
|
|
kthread_yield();
|
|
kthread_mutex_lock(&port_lock);
|
|
continue;
|
|
}
|
|
if ( i )
|
|
return;
|
|
// TODO: This is problematic.
|
|
if ( Signal::IsPending() )
|
|
{
|
|
errno = EINTR;
|
|
return;
|
|
}
|
|
}
|
|
outport8(port + TXR, buffer[i]);
|
|
}
|
|
}
|
|
|
|
static Ref<DevCOMPort> com_devices[1 + NUM_COM_PORTS];
|
|
|
|
void Init(const char* devpath, Ref<Descriptor> slashdev)
|
|
{
|
|
uint16_t com_ports[1 + NUM_COM_PORTS];
|
|
unsigned int hw_version[1 + NUM_COM_PORTS];
|
|
|
|
const uint16_t* bioscom_ports = (const uint16_t*) (nullpage + 0x400);
|
|
|
|
for ( size_t i = 1; i <= NUM_COM_PORTS; i++ )
|
|
{
|
|
if ( !(com_ports[i] = bioscom_ports[i-1]) )
|
|
continue;
|
|
hw_version[i] = HardwareProbe(com_ports[i]);
|
|
outport8(com_ports[i] + IER, 0x0);
|
|
}
|
|
|
|
(void) hw_version;
|
|
|
|
ioctx_t ctx; SetupKernelIOCtx(&ctx);
|
|
|
|
for ( size_t i = 1; i <= NUM_COM_PORTS; i++ )
|
|
{
|
|
if ( !com_ports[i] )
|
|
{
|
|
com_devices[i] = Ref<DevCOMPort>();
|
|
continue;
|
|
}
|
|
char ttyname[TTY_NAME_MAX+1];
|
|
snprintf(ttyname, sizeof(ttyname), "com%zu", i);
|
|
Ref<DevCOMPort> com(
|
|
new DevCOMPort(slashdev->dev, 0, 0, 0660, com_ports[i], ttyname));
|
|
if ( !com )
|
|
PanicF("Unable to allocate device for COM port %zu", i);
|
|
com_devices[i] = com;
|
|
int interrupt = i == 1 || i == 3 ? Interrupt::IRQ4 : Interrupt::IRQ3;
|
|
com->Initialize(interrupt);
|
|
if ( LinkInodeInDir(&ctx, slashdev, ttyname, com) != 0 )
|
|
PanicF("Unable to link %s/%s to COM port driver.", devpath, ttyname);
|
|
}
|
|
}
|
|
|
|
} // namespace COM
|
|
} // namespace Sortix
|