Add terminal and interrupt support to com(4).
This commit is contained in:
parent
d1a05c28ab
commit
c6f6aaa285
1 changed files with 118 additions and 96 deletions
214
kernel/com.cpp
214
kernel/com.cpp
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2011, 2012, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
|
||||
* 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
|
||||
|
@ -17,6 +17,8 @@
|
|||
* Handles communication to COM serial ports.
|
||||
*/
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
@ -38,6 +40,7 @@
|
|||
#include <sortix/kernel/thread.h>
|
||||
|
||||
#include "com.h"
|
||||
#include "tty.h"
|
||||
|
||||
extern "C" unsigned char nullpage[4096];
|
||||
|
||||
|
@ -136,17 +139,30 @@ static inline bool CanWriteByte(uint16_t port)
|
|||
return inport8(port + LSR) & LSR_THRE;
|
||||
}
|
||||
|
||||
class DevCOMPort : public AbstractInode
|
||||
class DevCOMPort : public TTY
|
||||
{
|
||||
public:
|
||||
DevCOMPort(dev_t dev, uid_t owner, gid_t group, mode_t mode, uint16_t port);
|
||||
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 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 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;
|
||||
|
@ -154,24 +170,93 @@ private:
|
|||
};
|
||||
|
||||
DevCOMPort::DevCOMPort(dev_t dev, uid_t owner, gid_t group, mode_t mode,
|
||||
uint16_t port)
|
||||
uint16_t port, const char* name) : TTY(dev, ino, mode,
|
||||
owner, group, name)
|
||||
{
|
||||
inode_type = INODE_TYPE_STREAM;
|
||||
this->port = port;
|
||||
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;
|
||||
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);
|
||||
|
@ -179,57 +264,9 @@ int DevCOMPort::sync(ioctx_t* /*ctx*/)
|
|||
return 0;
|
||||
}
|
||||
|
||||
ssize_t DevCOMPort::read(ioctx_t* ctx, uint8_t* dest, size_t count)
|
||||
void DevCOMPort::tty_output(const unsigned char* buffer, size_t length)
|
||||
{
|
||||
ScopedLock lock(&port_lock);
|
||||
|
||||
for ( size_t i = 0; i < count; i++ )
|
||||
{
|
||||
unsigned long attempt = 0;
|
||||
while ( !has_pending_input_byte && !IsLineReady(port) )
|
||||
{
|
||||
attempt++;
|
||||
if ( attempt <= 10 )
|
||||
continue;
|
||||
if ( attempt <= 15 && !(ctx->dflags & O_NONBLOCK) )
|
||||
{
|
||||
kthread_mutex_unlock(&port_lock);
|
||||
kthread_yield();
|
||||
kthread_mutex_lock(&port_lock);
|
||||
continue;
|
||||
}
|
||||
if ( i )
|
||||
return (ssize_t) i;
|
||||
if ( ctx->dflags & O_NONBLOCK )
|
||||
return errno = EWOULDBLOCK, -1;
|
||||
if ( Signal::IsPending() )
|
||||
return errno = EINTR, -1;
|
||||
kthread_mutex_unlock(&port_lock);
|
||||
kthread_yield();
|
||||
kthread_mutex_lock(&port_lock);
|
||||
}
|
||||
|
||||
uint8_t value = has_pending_input_byte ?
|
||||
pending_input_byte :
|
||||
inport8(port + RXR);
|
||||
if ( !ctx->copy_to_dest(dest + i, &value, sizeof(value)) )
|
||||
{
|
||||
has_pending_input_byte = true;
|
||||
pending_input_byte = value;
|
||||
return i ? (ssize_t) i : -1;
|
||||
}
|
||||
|
||||
has_pending_input_byte = false;
|
||||
}
|
||||
|
||||
return (ssize_t) count;
|
||||
}
|
||||
|
||||
ssize_t DevCOMPort::write(ioctx_t* ctx, const uint8_t* src, size_t count)
|
||||
{
|
||||
ScopedLock lock(&port_lock);
|
||||
|
||||
for ( size_t i = 0; i < count; i++ )
|
||||
for ( size_t i = 0; i < length; i++ )
|
||||
{
|
||||
unsigned long attempt = 0;
|
||||
while ( !CanWriteByte(port) )
|
||||
|
@ -237,7 +274,7 @@ ssize_t DevCOMPort::write(ioctx_t* ctx, const uint8_t* src, size_t count)
|
|||
attempt++;
|
||||
if ( attempt <= 10 )
|
||||
continue;
|
||||
if ( attempt <= 15 && !(ctx->dflags & O_NONBLOCK) )
|
||||
if ( attempt <= 15 )
|
||||
{
|
||||
kthread_mutex_unlock(&port_lock);
|
||||
kthread_yield();
|
||||
|
@ -245,20 +282,16 @@ ssize_t DevCOMPort::write(ioctx_t* ctx, const uint8_t* src, size_t count)
|
|||
continue;
|
||||
}
|
||||
if ( i )
|
||||
return (ssize_t) i;
|
||||
if ( ctx->dflags & O_NONBLOCK )
|
||||
return errno = EWOULDBLOCK, -1;
|
||||
return;
|
||||
// TODO: This is problematic.
|
||||
if ( Signal::IsPending() )
|
||||
return errno = EINTR, -1;
|
||||
{
|
||||
errno = EINTR;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t value;
|
||||
if ( !ctx->copy_from_src(&value, src + i, sizeof(value)) )
|
||||
return i ? (ssize_t) i : -1;
|
||||
outport8(port + TXR, value);
|
||||
outport8(port + TXR, buffer[i]);
|
||||
}
|
||||
|
||||
return (ssize_t) count;
|
||||
}
|
||||
|
||||
static Ref<DevCOMPort> com_devices[1 + NUM_COM_PORTS];
|
||||
|
@ -282,21 +315,6 @@ void Init(const char* devpath, Ref<Descriptor> slashdev)
|
|||
|
||||
ioctx_t ctx; SetupKernelIOCtx(&ctx);
|
||||
|
||||
for ( size_t i = 1; i <= NUM_COM_PORTS; i++ )
|
||||
{
|
||||
uint16_t port = com_ports[i];
|
||||
if ( !port )
|
||||
continue;
|
||||
uint8_t interrupts = 0;
|
||||
outport8(port + FCR, 0);
|
||||
outport8(port + LCR, 0x80);
|
||||
outport8(port + DLL, 0xC);
|
||||
outport8(port + DLM, 0x0);
|
||||
outport8(port + LCR, 0x3); // 8n1
|
||||
outport8(port + MCR, 0x3); // DTR + RTS
|
||||
outport8(port + IER, interrupts);
|
||||
}
|
||||
|
||||
for ( size_t i = 1; i <= NUM_COM_PORTS; i++ )
|
||||
{
|
||||
if ( !com_ports[i] )
|
||||
|
@ -304,13 +322,17 @@ void Init(const char* devpath, Ref<Descriptor> slashdev)
|
|||
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] )
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue