the header, mm and smx modules had it right also fixes p3io responses being the wrong size in 2 cases
439 lines
9.6 KiB
C
439 lines
9.6 KiB
C
#define LOG_MODULE "p3ioemu-uart"
|
|
|
|
// clang-format off
|
|
// Don't format because the order is important here
|
|
#include <windows.h>
|
|
#include <devioctl.h>
|
|
#include <ntdef.h>
|
|
#include <ntddser.h>
|
|
// clang-format on
|
|
|
|
#include <string.h>
|
|
|
|
#include "hook/iohook.h"
|
|
|
|
#include "p3io/cmd.h"
|
|
|
|
#include "p3ioemu/uart.h"
|
|
|
|
#include "util/iobuf.h"
|
|
#include "util/log.h"
|
|
|
|
static HRESULT
|
|
p3io_uart_open(const wchar_t *path, uint32_t baud_rate, HANDLE *fd);
|
|
static HRESULT p3io_uart_close(HANDLE fd);
|
|
static HRESULT p3io_uart_read(HANDLE fd, struct iobuf *iobuf);
|
|
static HRESULT p3io_uart_write(HANDLE fd, struct const_iobuf *iobuf);
|
|
|
|
static const wchar_t *p3io_uart_paths[2];
|
|
static HANDLE p3io_uart_fds[2];
|
|
|
|
static const uint32_t p3io_uart_baud_codes[] = {0, 0, 19200, 38400, 57600};
|
|
|
|
void p3io_uart_set_path(size_t uart_no, const wchar_t *path)
|
|
{
|
|
log_assert(uart_no <= 1);
|
|
|
|
p3io_uart_paths[uart_no] = path;
|
|
}
|
|
|
|
void p3io_uart_cmd_open_close(
|
|
const struct p3io_req_rs232_open_close *req, struct p3io_resp_u8 *resp)
|
|
{
|
|
const wchar_t *path;
|
|
uint32_t baud_rate;
|
|
HRESULT hr;
|
|
|
|
log_assert(req != NULL);
|
|
log_assert(resp != NULL);
|
|
|
|
if (req->port_no > 1) {
|
|
log_warning("Invalid UART number %i", req->port_no);
|
|
hr = E_INVALIDARG;
|
|
|
|
goto end;
|
|
}
|
|
|
|
switch (req->subcmd) {
|
|
case P3IO_RS232_CMD_OPEN:
|
|
log_info("Opening remote RS232 port #%d", req->port_no);
|
|
|
|
if (req->baud_code < lengthof(p3io_uart_baud_codes)) {
|
|
baud_rate = p3io_uart_baud_codes[req->baud_code];
|
|
} else {
|
|
baud_rate = 0;
|
|
}
|
|
|
|
if (baud_rate == 0) {
|
|
log_warning("Invalid baud rate code: %02x", req->baud_code);
|
|
hr = E_FAIL;
|
|
|
|
goto end;
|
|
}
|
|
|
|
path = p3io_uart_paths[req->port_no];
|
|
|
|
if (path == NULL) {
|
|
log_warning("UART #%i: No downstream connection", req->port_no);
|
|
hr = E_FAIL;
|
|
|
|
goto end;
|
|
}
|
|
|
|
hr = p3io_uart_open(path, baud_rate, &p3io_uart_fds[req->port_no]);
|
|
|
|
if (FAILED(hr)) {
|
|
log_warning("p3io_uart_open() failed: %x", (int) hr);
|
|
}
|
|
|
|
break;
|
|
|
|
case P3IO_RS232_CMD_CLOSE:
|
|
log_info("Closing remote RS232 port #%d", req->port_no);
|
|
|
|
hr = p3io_uart_close(p3io_uart_fds[req->port_no]);
|
|
p3io_uart_fds[req->port_no] = NULL;
|
|
|
|
break;
|
|
|
|
default:
|
|
log_warning("Unknown subcommand %02x", req->subcmd);
|
|
hr = E_FAIL;
|
|
|
|
break;
|
|
}
|
|
|
|
end:
|
|
p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr);
|
|
resp->status = 0;
|
|
resp->u8 = FAILED(hr);
|
|
}
|
|
|
|
void p3io_uart_cmd_read(
|
|
const struct p3io_req_rs232_read *req, struct p3io_resp_rs232_read *resp)
|
|
{
|
|
struct iobuf iobuf;
|
|
HRESULT hr;
|
|
|
|
log_assert(req != NULL);
|
|
log_assert(resp != NULL);
|
|
|
|
iobuf.bytes = resp->bytes;
|
|
iobuf.nbytes = req->nbytes;
|
|
iobuf.pos = 0;
|
|
|
|
if (req->port_no > 1) {
|
|
log_warning("Invalid UART number %i", req->port_no);
|
|
hr = E_INVALIDARG;
|
|
|
|
goto end;
|
|
}
|
|
|
|
if (req->nbytes > sizeof(resp->bytes)) {
|
|
log_warning("Excessive read %i", req->nbytes);
|
|
hr = E_INVALIDARG;
|
|
|
|
goto end;
|
|
}
|
|
|
|
hr = p3io_uart_read(p3io_uart_fds[req->port_no], &iobuf);
|
|
|
|
end:
|
|
/* Variable-length response, init the header manually */
|
|
|
|
resp->hdr.nbytes = iobuf.pos + 3;
|
|
resp->hdr.seq_no = req->hdr.seq_no;
|
|
resp->status = FAILED(hr);
|
|
resp->nbytes = iobuf.pos;
|
|
}
|
|
|
|
void p3io_uart_cmd_write(
|
|
const struct p3io_req_rs232_write *req, struct p3io_resp_rs232_write *resp)
|
|
{
|
|
struct const_iobuf iobuf;
|
|
HRESULT hr;
|
|
|
|
iobuf.bytes = req->bytes;
|
|
iobuf.nbytes = req->nbytes;
|
|
iobuf.pos = 0;
|
|
|
|
if (req->port_no > 1) {
|
|
log_warning("Invalid UART number %i", req->port_no);
|
|
hr = E_INVALIDARG;
|
|
|
|
goto end;
|
|
}
|
|
|
|
hr = p3io_uart_write(p3io_uart_fds[req->port_no], &iobuf);
|
|
|
|
end:
|
|
p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr);
|
|
resp->status = FAILED(hr);
|
|
resp->nbytes = iobuf.pos;
|
|
}
|
|
|
|
static HRESULT
|
|
p3io_uart_open(const wchar_t *path, uint32_t baud_rate, HANDLE *fd)
|
|
{
|
|
struct irp irp;
|
|
uint32_t comm_mask;
|
|
uint32_t flags;
|
|
SERIAL_QUEUE_SIZE qs;
|
|
SERIAL_TIMEOUTS timeouts;
|
|
SERIAL_LINE_CONTROL lc;
|
|
SERIAL_BAUD_RATE baud;
|
|
SERIAL_HANDFLOW handflow;
|
|
HRESULT hr;
|
|
|
|
if (*fd != NULL) {
|
|
log_warning("Port is already open");
|
|
|
|
return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
|
|
}
|
|
|
|
memset(&irp, 0, sizeof(irp));
|
|
irp.op = IRP_OP_OPEN;
|
|
irp.open_filename = path;
|
|
irp.open_access = GENERIC_READ | GENERIC_WRITE;
|
|
irp.open_share = 0;
|
|
irp.open_sa = NULL;
|
|
irp.open_creation = OPEN_EXISTING;
|
|
irp.open_flags = 0;
|
|
irp.open_tmpl = NULL;
|
|
|
|
hr = irp_invoke_next(&irp);
|
|
|
|
if (FAILED(hr)) {
|
|
goto fail;
|
|
}
|
|
|
|
*fd = irp.fd;
|
|
|
|
memset(&irp, 0, sizeof(irp));
|
|
irp.op = IRP_OP_IOCTL;
|
|
irp.fd = *fd;
|
|
irp.ioctl = IOCTL_SERIAL_SET_WAIT_MASK;
|
|
irp.write.bytes = (const void *) &comm_mask;
|
|
irp.write.nbytes = sizeof(comm_mask);
|
|
comm_mask = EV_RXCHAR;
|
|
|
|
hr = irp_invoke_next(&irp);
|
|
|
|
if (FAILED(hr)) {
|
|
goto fail;
|
|
}
|
|
|
|
memset(&irp, 0, sizeof(irp));
|
|
irp.op = IRP_OP_IOCTL;
|
|
irp.fd = *fd;
|
|
irp.ioctl = IOCTL_SERIAL_SET_QUEUE_SIZE;
|
|
irp.write.bytes = (const void *) &qs;
|
|
irp.write.nbytes = sizeof(qs);
|
|
qs.InSize = 0x4000;
|
|
qs.OutSize = 0x4000;
|
|
|
|
hr = irp_invoke_next(&irp);
|
|
|
|
if (FAILED(hr)) {
|
|
goto fail;
|
|
}
|
|
|
|
memset(&irp, 0, sizeof(irp));
|
|
irp.op = IRP_OP_IOCTL;
|
|
irp.fd = *fd;
|
|
irp.ioctl = IOCTL_SERIAL_PURGE;
|
|
irp.write.bytes = (const void *) &flags;
|
|
irp.write.nbytes = sizeof(flags);
|
|
flags = PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR;
|
|
|
|
hr = irp_invoke_next(&irp);
|
|
|
|
if (FAILED(hr)) {
|
|
goto fail;
|
|
}
|
|
|
|
memset(&irp, 0, sizeof(irp));
|
|
irp.op = IRP_OP_IOCTL;
|
|
irp.fd = *fd;
|
|
irp.ioctl = IOCTL_SERIAL_SET_TIMEOUTS;
|
|
irp.write.bytes = (const void *) &timeouts;
|
|
irp.write.nbytes = sizeof(timeouts);
|
|
timeouts.ReadIntervalTimeout = -1;
|
|
timeouts.ReadTotalTimeoutMultiplier = 10;
|
|
timeouts.ReadTotalTimeoutConstant = 0;
|
|
timeouts.WriteTotalTimeoutMultiplier = 100;
|
|
timeouts.WriteTotalTimeoutConstant = 0;
|
|
|
|
hr = irp_invoke_next(&irp);
|
|
|
|
if (FAILED(hr)) {
|
|
goto fail;
|
|
}
|
|
|
|
memset(&irp, 0, sizeof(irp));
|
|
irp.op = IRP_OP_IOCTL;
|
|
irp.fd = *fd;
|
|
irp.ioctl = IOCTL_SERIAL_SET_LINE_CONTROL;
|
|
irp.write.bytes = (const void *) &lc;
|
|
irp.write.nbytes = sizeof(lc);
|
|
lc.WordLength = 8;
|
|
lc.Parity = NO_PARITY;
|
|
lc.StopBits = STOP_BIT_1;
|
|
|
|
hr = irp_invoke_next(&irp);
|
|
|
|
if (FAILED(hr)) {
|
|
goto fail;
|
|
}
|
|
|
|
memset(&irp, 0, sizeof(irp));
|
|
irp.op = IRP_OP_IOCTL;
|
|
irp.fd = *fd;
|
|
irp.ioctl = IOCTL_SERIAL_SET_BAUD_RATE;
|
|
irp.write.bytes = (const void *) &baud;
|
|
irp.write.nbytes = sizeof(baud);
|
|
baud.BaudRate = baud_rate;
|
|
|
|
hr = irp_invoke_next(&irp);
|
|
|
|
if (FAILED(hr)) {
|
|
goto fail;
|
|
}
|
|
|
|
memset(&irp, 0, sizeof(irp));
|
|
irp.op = IRP_OP_IOCTL;
|
|
irp.fd = *fd;
|
|
irp.ioctl = IOCTL_SERIAL_SET_HANDFLOW;
|
|
irp.write.bytes = (const void *) &handflow;
|
|
irp.write.nbytes = sizeof(handflow);
|
|
handflow.ControlHandShake = 0;
|
|
handflow.FlowReplace = 0;
|
|
handflow.XonLimit = 0;
|
|
handflow.XoffLimit = 0;
|
|
|
|
hr = irp_invoke_next(&irp);
|
|
|
|
if (FAILED(hr)) {
|
|
goto fail;
|
|
}
|
|
|
|
return S_OK;
|
|
|
|
fail:
|
|
if (*fd != NULL) {
|
|
irp.op = IRP_OP_CLOSE;
|
|
irp.fd = *fd;
|
|
|
|
irp_invoke_next(&irp);
|
|
|
|
*fd = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT p3io_uart_close(HANDLE fd)
|
|
{
|
|
struct irp irp;
|
|
HRESULT hr;
|
|
|
|
if (fd != NULL) {
|
|
memset(&irp, 0, sizeof(irp));
|
|
irp.op = IRP_OP_CLOSE;
|
|
irp.fd = fd;
|
|
|
|
hr = irp_invoke_next(&irp);
|
|
|
|
if (FAILED(hr)) {
|
|
log_warning("Error closing port: %x", (int) hr);
|
|
}
|
|
} else {
|
|
log_warning("Port is already closed");
|
|
hr = S_OK;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT p3io_uart_read(HANDLE fd, struct iobuf *iobuf)
|
|
{
|
|
struct irp irp;
|
|
SERIAL_STATUS status;
|
|
HRESULT hr;
|
|
|
|
if (fd == NULL) {
|
|
log_warning("Read from unopened port");
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
/* Peek RX buffer */
|
|
|
|
memset(&irp, 0, sizeof(irp));
|
|
irp.op = IRP_OP_IOCTL;
|
|
irp.fd = fd;
|
|
irp.ioctl = IOCTL_SERIAL_GET_COMMSTATUS;
|
|
irp.read.bytes = (uint8_t *) &status;
|
|
irp.read.nbytes = sizeof(status);
|
|
|
|
hr = irp_invoke_next(&irp);
|
|
|
|
if (FAILED(hr)) {
|
|
log_warning("UART FIFO peek failed: %x", (int) hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/* Return immediately if no data available */
|
|
|
|
if (status.AmountInInQueue == 0) {
|
|
return S_OK;
|
|
}
|
|
|
|
/* Issue read */
|
|
|
|
memset(&irp, 0, sizeof(irp));
|
|
irp.op = IRP_OP_READ;
|
|
irp.fd = fd;
|
|
memcpy(&irp.read, iobuf, sizeof(*iobuf));
|
|
|
|
hr = irp_invoke_next(&irp);
|
|
|
|
if (FAILED(hr)) {
|
|
log_warning("Read error: %x", (int) hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
memcpy(iobuf, &irp.read, sizeof(*iobuf));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT p3io_uart_write(HANDLE fd, struct const_iobuf *iobuf)
|
|
{
|
|
struct irp irp;
|
|
HRESULT hr;
|
|
|
|
if (fd == NULL) {
|
|
log_warning("Write to unopened port");
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
memset(&irp, 0, sizeof(irp));
|
|
irp.op = IRP_OP_WRITE;
|
|
irp.fd = fd;
|
|
memcpy(&irp.write, iobuf, sizeof(*iobuf));
|
|
|
|
hr = irp_invoke_next(&irp);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
memcpy(iobuf, &irp.write, sizeof(*iobuf));
|
|
} else {
|
|
log_warning("Write error: %x", (int) hr);
|
|
}
|
|
|
|
return hr;
|
|
}
|