From de528c108fdc1925aca222a58229cbe14081415e Mon Sep 17 00:00:00 2001 From: Decaf Code Date: Wed, 7 Nov 2018 20:01:15 -0500 Subject: [PATCH] hooklib/uart.c: Add emulated UART --- hooklib/meson.build | 2 + hooklib/uart.c | 274 ++++++++++++++++++++++++++++++++++++++++++++ hooklib/uart.h | 30 +++++ 3 files changed, 306 insertions(+) create mode 100644 hooklib/uart.c create mode 100644 hooklib/uart.h diff --git a/hooklib/meson.build b/hooklib/meson.build index ed4eacf..e6e62ae 100644 --- a/hooklib/meson.build +++ b/hooklib/meson.build @@ -5,6 +5,8 @@ hooklib_lib = static_library( sources : [ 'serial.c', 'serial.h', + 'uart.c', + 'uart.h', ], ) diff --git a/hooklib/uart.c b/hooklib/uart.c new file mode 100644 index 0000000..bb0f23e --- /dev/null +++ b/hooklib/uart.c @@ -0,0 +1,274 @@ +#include + +#include +#include + +#include +#include +#include + +#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) +{ + /* 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_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)); + + /* These can be safely ignored */ + case IOCTL_SERIAL_SET_BREAK_ON: + case IOCTL_SERIAL_SET_BREAK_OFF: + case IOCTL_SERIAL_CLR_DTR: + case IOCTL_SERIAL_CLR_RTS: + case IOCTL_SERIAL_SET_DTR: + case IOCTL_SERIAL_SET_RTS: + 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); + } +} diff --git a/hooklib/uart.h b/hooklib/uart.h new file mode 100644 index 0000000..2790bac --- /dev/null +++ b/hooklib/uart.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include +#include + +#include + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +struct uart { + HANDLE fd; + unsigned int port_no; + SERIAL_BAUD_RATE baud; + SERIAL_STATUS status; + SERIAL_CHARS chars; + SERIAL_HANDFLOW handflow; + SERIAL_LINE_CONTROL line; + SERIAL_TIMEOUTS timeouts; + DWORD mask; + struct iobuf written; + struct iobuf readable; +}; + +void uart_init(struct uart *uart, unsigned int port_no); +void uart_fini(struct uart *uart); +bool uart_match_irp(const struct uart *uart, const struct irp *irp); +HRESULT uart_handle_irp(struct uart *uart, struct irp *irp);