Boils down to: - Include headers - Reduce boiler plate with helpers - Swap out explicit usages with core API layer and ensure the right API is configured beforehand
211 lines
4.9 KiB
C
211 lines
4.9 KiB
C
#include <windows.h> /* Usermode API */
|
|
|
|
// clang-format off
|
|
// Don't format because the order is important here
|
|
#include <ntdef.h>
|
|
#include <devioctl.h>
|
|
#include <ntddser.h>
|
|
// clang-format on
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <wchar.h>
|
|
|
|
#include "acioemu/emu.h"
|
|
#include "acioemu/pipe.h"
|
|
|
|
#include "core/log.h"
|
|
|
|
#include "hook/iohook.h"
|
|
|
|
#include "util/str.h"
|
|
|
|
static HRESULT ac_io_emu_open(struct ac_io_emu *emu, struct irp *irp);
|
|
static HRESULT ac_io_emu_close(struct ac_io_emu *emu, struct irp *irp);
|
|
static HRESULT ac_io_emu_read(struct ac_io_emu *emu, struct irp *irp);
|
|
static HRESULT ac_io_emu_write(struct ac_io_emu *emu, struct irp *irp);
|
|
static HRESULT ac_io_emu_ioctl(struct ac_io_emu *emu, struct irp *irp);
|
|
|
|
void ac_io_emu_init(struct ac_io_emu *emu, const wchar_t *filename)
|
|
{
|
|
log_assert(emu != NULL);
|
|
log_assert(filename != NULL);
|
|
|
|
HRESULT hr;
|
|
|
|
memset(emu, 0, sizeof(*emu));
|
|
|
|
hr = iohook_open_nul_fd(&emu->fd);
|
|
|
|
if (hr != S_OK) {
|
|
log_fatal("Opening nul fd failed: %08lx", hr);
|
|
}
|
|
|
|
emu->wfilename = wstr_dup(filename);
|
|
wstr_narrow(filename, &emu->filename);
|
|
ac_io_in_init(&emu->in);
|
|
ac_io_out_init(&emu->out);
|
|
}
|
|
|
|
void ac_io_emu_fini(struct ac_io_emu *emu)
|
|
{
|
|
log_assert(emu != NULL);
|
|
|
|
free(emu->filename);
|
|
free(emu->wfilename);
|
|
|
|
if (emu->fd != NULL) {
|
|
CloseHandle(emu->fd);
|
|
}
|
|
|
|
memset(emu, 0, sizeof(*emu));
|
|
}
|
|
|
|
bool ac_io_emu_match_irp(const struct ac_io_emu *emu, const struct irp *irp)
|
|
{
|
|
log_assert(emu != NULL);
|
|
log_assert(irp != NULL);
|
|
|
|
if (irp->op == IRP_OP_OPEN) {
|
|
return wstr_eq(emu->wfilename, irp->open_filename);
|
|
} else {
|
|
return irp->fd == emu->fd;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
ac_io_emu_dispatch_irp(struct ac_io_emu *emu, struct irp *irp)
|
|
{
|
|
log_assert(irp != NULL);
|
|
|
|
switch (irp->op) {
|
|
case IRP_OP_OPEN:
|
|
return ac_io_emu_open(emu, irp);
|
|
case IRP_OP_CLOSE:
|
|
return ac_io_emu_close(emu, irp);
|
|
case IRP_OP_READ:
|
|
return ac_io_emu_read(emu, irp);
|
|
case IRP_OP_WRITE:
|
|
return ac_io_emu_write(emu, irp);
|
|
case IRP_OP_IOCTL:
|
|
return ac_io_emu_ioctl(emu, irp);
|
|
case IRP_OP_FSYNC:
|
|
return S_FALSE;
|
|
default:
|
|
return E_NOTIMPL;
|
|
}
|
|
}
|
|
|
|
static HRESULT ac_io_emu_open(struct ac_io_emu *emu, struct irp *irp)
|
|
{
|
|
irp->fd = emu->fd;
|
|
log_info("%s: ACIO port opened", emu->filename);
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
static HRESULT ac_io_emu_close(struct ac_io_emu *emu, struct irp *irp)
|
|
{
|
|
log_info("%s: ACIO port closed", emu->filename);
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
static HRESULT ac_io_emu_read(struct ac_io_emu *emu, struct irp *irp)
|
|
{
|
|
ac_io_in_drain(&emu->in, &irp->read);
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
static HRESULT ac_io_emu_write(struct ac_io_emu *emu, struct irp *irp)
|
|
{
|
|
const struct ac_io_message *msg;
|
|
|
|
for (;;) {
|
|
ac_io_out_supply(&emu->out, &irp->write);
|
|
|
|
if (!ac_io_out_have_message(&emu->out)) {
|
|
break;
|
|
}
|
|
|
|
msg = ac_io_out_get_message(&emu->out);
|
|
|
|
if (msg != NULL) {
|
|
break;
|
|
}
|
|
|
|
// this supplies a single SOF byte per byte read
|
|
ac_io_in_supply(&emu->in, NULL, 0);
|
|
ac_io_out_consume_message(&emu->out);
|
|
}
|
|
|
|
return ac_io_out_have_message(&emu->out) ? S_OK : S_FALSE;
|
|
}
|
|
|
|
static HRESULT ac_io_emu_ioctl(struct ac_io_emu *emu, struct irp *irp)
|
|
{
|
|
SERIAL_STATUS *status;
|
|
|
|
log_assert(irp != NULL);
|
|
|
|
switch (irp->ioctl) {
|
|
case IOCTL_SERIAL_GET_COMMSTATUS:
|
|
if (irp->read.bytes == NULL) {
|
|
log_warning(
|
|
"IOCTL_SERIAL_GET_COMMSTATUS: Output buffer is NULL");
|
|
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (irp->read.nbytes < sizeof(*status)) {
|
|
log_warning("IOCTL_SERIAL_GET_COMMSTATUS: Buffer is too small");
|
|
|
|
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
|
}
|
|
|
|
status = (SERIAL_STATUS *) irp->read.bytes;
|
|
status->Errors = 0;
|
|
status->AmountInInQueue = ac_io_in_is_msg_pending(&emu->in);
|
|
|
|
irp->read.pos = sizeof(*status);
|
|
|
|
return S_FALSE;
|
|
|
|
default:
|
|
return S_FALSE;
|
|
}
|
|
}
|
|
|
|
const struct ac_io_message *ac_io_emu_request_peek(const struct ac_io_emu *emu)
|
|
{
|
|
log_assert(emu != NULL);
|
|
|
|
return ac_io_out_get_message(&emu->out);
|
|
}
|
|
|
|
void ac_io_emu_request_pop(struct ac_io_emu *emu)
|
|
{
|
|
log_assert(emu != NULL);
|
|
|
|
ac_io_out_consume_message(&emu->out);
|
|
}
|
|
|
|
void ac_io_emu_response_push(
|
|
struct ac_io_emu *emu, const struct ac_io_message *resp, uint64_t delay_us)
|
|
{
|
|
log_assert(emu != NULL);
|
|
log_assert(resp != NULL);
|
|
|
|
ac_io_in_supply(&emu->in, resp, delay_us);
|
|
}
|
|
|
|
void ac_io_emu_response_push_thunk(
|
|
struct ac_io_emu *emu, ac_io_in_thunk_t thunk, void *ctx, uint64_t delay_us)
|
|
{
|
|
log_assert(emu != NULL);
|
|
log_assert(thunk != NULL);
|
|
|
|
ac_io_in_supply_thunk(&emu->in, thunk, ctx, delay_us);
|
|
}
|