/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; 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 com_devices[1 + NUM_COM_PORTS]; void Init(const char* devpath, Ref 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(); continue; } char ttyname[TTY_NAME_MAX+1]; snprintf(ttyname, sizeof(ttyname), "com%zu", i); Ref 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