Files
Gl0w1amp 35169a5057 Remove global Sleep(1) from uart_handle_read (#6)
This removes the global Sleep(1) workaround that was originally added to prevent memory leaks caused by tight polling loops.

While it helped with the mai2 memory leak, introducing artificial latency here negatively impacts other UART devices (e.g., breaking slider inputs in chusan). The proper fix has now been implemented downstream in `segatools` by rate-limiting the specific `led15070` board instead. Therefore, this global hack is no longer needed and can be safely removed.

Reviewed-on: https://gitea.tendokyu.moe/TeamTofuShop/capnhook/pulls/6
Co-authored-by: Gl0w1amp <gl0w1amp@noreply.gitea.tendokyu.moe>
Co-committed-by: Gl0w1amp <gl0w1amp@noreply.gitea.tendokyu.moe>
2026-03-09 16:33:23 +00:00

307 lines
8.6 KiB
C

#include <windows.h>
#ifdef __GNUC__
#include <ntdef.h>
#else
#include <winnt.h>
#endif
#include <devioctl.h>
#include <ntddser.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include "hook/iobuf.h"
#include "hook/iohook.h"
#include "hooklib/uart.h"
static HRESULT uart_handle_open(struct uart *uart, struct irp *irp);
static HRESULT uart_handle_close(struct uart *uart, struct irp *irp);
static HRESULT uart_handle_read(struct uart *uart, struct irp *irp);
static HRESULT uart_handle_write(struct uart *uart, struct irp *irp);
static HRESULT uart_handle_ioctl(struct uart *uart, struct irp *irp);
void uart_init(struct uart *uart, unsigned int port_no)
{
assert(uart != NULL);
assert(port_no > 0);
uart->fd = NULL;
uart->port_no = port_no;
uart->baud.BaudRate = 115200;
memset(&uart->status, 0, sizeof(uart->status));
uart->chars.EofChar = 0x00;
uart->chars.ErrorChar = 0x00;
uart->chars.BreakChar = 0x00;
uart->chars.EventChar = 0x00;
uart->chars.XonChar = 0x11;
uart->chars.XoffChar = 0x13;
memset(&uart->handflow, 0, sizeof(uart->handflow));
uart->line.StopBits = STOP_BIT_1;
uart->line.Parity = NO_PARITY;
uart->line.WordLength = 8;
memset(&uart->timeouts, 0, sizeof(uart->timeouts));
uart->mask = 0;
memset(&uart->written, 0, sizeof(uart->written));
memset(&uart->readable, 0, sizeof(uart->readable));
}
void uart_fini(struct uart *uart)
{
struct irp irp;
assert(uart != NULL);
if (uart->fd != NULL) {
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_CLOSE;
irp.fd = uart->fd;
/* Not much we can do if this fails */
iohook_invoke_next(&irp);
}
}
bool uart_match_irp(const struct uart *uart, const struct irp *irp)
{
const wchar_t *path;
unsigned int port_no;
wchar_t wc;
size_t i;
if (irp->op == IRP_OP_OPEN) {
/* Win32 device nodes can unfortunately be identified using a variety
of different syntax */
path = irp->open_filename;
if ( wcsncmp(path, L"\\\\.\\", 4) == 0 ||
wcsncmp(path, L"\\\\?\\", 4) == 0 ||
wcsncmp(path, L"\\??\\", 4) == 0 ) {
/* NT style */
path = path + 4;
if ( (path[0] & ~0x20) != L'C' ||
(path[1] & ~0x20) != L'O' ||
(path[2] & ~0x20) != L'M' ) {
return false;
}
port_no = 0;
for (i = 3 ; path[i] ; i++) {
wc = path[i];
if (wc < L'0' || wc > L'9') {
return false;
}
port_no *= 10;
port_no += wc - L'0';
}
} else {
/* DOS style. Only COM1 through COM9 are supported. */
if ( (path[0] & ~0x20) != L'C' ||
(path[1] & ~0x20) != L'O' ||
(path[2] & ~0x20) != L'M' ||
(path[3] < L'1' || path[3] > L'9') ) {
return false;
}
/* DOS-style COM port names are allowed to have an optional trailing
colon, as if this wasn't complicated enough. */
if ( (path[4] != L'\0') &&
(path[4] != L':' || path[5] != L'\0') ) {
return false;
}
port_no = path[3] - L'0';
}
return port_no == uart->port_no;
} else {
/* All other IRPs are matched by checking the file descriptor. */
return irp->fd == uart->fd;
}
}
HRESULT uart_handle_irp(struct uart *uart, struct irp *irp)
{
assert(uart != NULL);
assert(irp != NULL);
switch (irp->op) {
case IRP_OP_OPEN: return uart_handle_open(uart, irp);
case IRP_OP_CLOSE: return uart_handle_close(uart, irp);
case IRP_OP_READ: return uart_handle_read(uart, irp);
case IRP_OP_WRITE: return uart_handle_write(uart, irp);
case IRP_OP_IOCTL: return uart_handle_ioctl(uart, irp);
case IRP_OP_FSYNC: return S_OK;
default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
}
}
static HRESULT uart_handle_open(struct uart *uart, struct irp *irp)
{
HRESULT hr;
if (uart->fd != NULL) {
/* Windows only allows one handle to be open for each COM port at a
time. Strangely enough it returns an Access Denied error in this
situation instead of something more appropriate. */
return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
}
/* Transform this open call so that it opens the NUL device, then pass it
on. This gives us a real Win32 HANDLE distinct from all other open
HANDLEs. */
irp->open_filename = L"NUL";
irp->open_access = GENERIC_READ | GENERIC_WRITE;
irp->open_share = FILE_SHARE_READ | FILE_SHARE_WRITE;
irp->open_sa = NULL;
irp->open_creation = OPEN_EXISTING;
irp->open_flags = FILE_FLAG_OVERLAPPED;
irp->open_tmpl = NULL;
hr = iohook_invoke_next(irp);
if (SUCCEEDED(hr)) {
uart->fd = irp->fd;
}
return hr;
}
static HRESULT uart_handle_close(struct uart *uart, struct irp *irp)
{
uart->fd = NULL;
return iohook_invoke_next(irp);
}
static HRESULT uart_handle_read(struct uart *uart, struct irp *irp)
{
int requested = irp->read.nbytes;
if (uart->readable.pos < requested && uart->readable.pos == 0) {
return E_PENDING;
}
/* This does a memmove() under the covers. Less efficient than a ring
buffer, but I don't expect this to matter in the common case where the
entire buffer gets drained, particularly since UARTs are decidedly
low-speed devices anyway. */
iobuf_shift(&irp->read, &uart->readable);
return S_OK;
}
static HRESULT uart_handle_write(struct uart *uart, struct irp *irp)
{
iobuf_move(&uart->written, &irp->write);
return S_OK;
}
static HRESULT uart_handle_ioctl(struct uart *uart, struct irp *irp)
{
switch (irp->ioctl) {
case IOCTL_SERIAL_GET_BAUD_RATE:
return iobuf_write(&irp->read, &uart->baud, sizeof(uart->baud));
case IOCTL_SERIAL_GET_CHARS:
return iobuf_write(&irp->read, &uart->chars, sizeof(uart->chars));
case IOCTL_SERIAL_GET_COMMSTATUS:
uart->status.AmountInInQueue = uart->readable.pos;
uart->status.AmountInOutQueue = uart->written.pos;
return iobuf_write(&irp->read, &uart->status, sizeof(uart->status));
case IOCTL_SERIAL_GET_HANDFLOW:
return iobuf_write(&irp->read, &uart->handflow, sizeof(uart->handflow));
case IOCTL_SERIAL_GET_LINE_CONTROL:
return iobuf_write(&irp->read, &uart->line, sizeof(uart->line));
case IOCTL_SERIAL_GET_TIMEOUTS:
return iobuf_write(&irp->read, &uart->timeouts, sizeof(uart->timeouts));
case IOCTL_SERIAL_GET_WAIT_MASK:
return iobuf_write(&irp->read, &uart->mask, sizeof(uart->mask));
case IOCTL_SERIAL_GET_MODEM_CONTROL:
return iobuf_write(&irp->read, &uart->modem_control, sizeof(uart->modem_control));
case IOCTL_SERIAL_GET_MODEMSTATUS:
ULONG status = (uart->cts_on << 4) | (uart->dsr_on << 5) | (uart->ring_on << 6) | (uart->rlsd_on << 7);
return iobuf_write(&irp->read, &status, sizeof(status));
case IOCTL_SERIAL_SET_BAUD_RATE:
return iobuf_read(&irp->write, &uart->baud, sizeof(uart->baud));
case IOCTL_SERIAL_SET_CHARS:
return iobuf_read(&irp->write, &uart->chars, sizeof(uart->chars));
case IOCTL_SERIAL_SET_HANDFLOW:
return iobuf_read(&irp->write, &uart->handflow, sizeof(uart->handflow));
case IOCTL_SERIAL_SET_LINE_CONTROL:
return iobuf_read(&irp->write, &uart->line, sizeof(uart->line));
case IOCTL_SERIAL_SET_TIMEOUTS:
return iobuf_read(&irp->write, &uart->timeouts, sizeof(uart->timeouts));
case IOCTL_SERIAL_SET_WAIT_MASK:
return iobuf_read(&irp->write, &uart->mask, sizeof(uart->mask));
case IOCTL_SERIAL_SET_MODEM_CONTROL:
return iobuf_read(&irp->write, &uart->modem_control, sizeof(uart->modem_control));
case IOCTL_SERIAL_CLR_DTR:
uart->dtr_on = false;
return S_OK;
case IOCTL_SERIAL_CLR_RTS:
uart->rts_on = false;
return S_OK;
case IOCTL_SERIAL_SET_DTR:
uart->dtr_on = true;
return S_OK;
case IOCTL_SERIAL_SET_RTS:
uart->rts_on = true;
return S_OK;
/* These can be safely ignored */
case IOCTL_SERIAL_SET_BREAK_ON:
case IOCTL_SERIAL_SET_BREAK_OFF:
case IOCTL_SERIAL_SET_XOFF:
case IOCTL_SERIAL_SET_XON:
case IOCTL_SERIAL_PURGE:
case IOCTL_SERIAL_SET_QUEUE_SIZE:
return S_OK;
default:
return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
}
}