diff --git a/src/main/extiodrv/Module.mk b/src/main/extiodrv/Module.mk new file mode 100644 index 0000000..5b4eacf --- /dev/null +++ b/src/main/extiodrv/Module.mk @@ -0,0 +1,5 @@ +libs += extiodrv + +src_extiodrv := \ + device.c \ + extio.c \ diff --git a/src/main/extiodrv/device.c b/src/main/extiodrv/device.c new file mode 100644 index 0000000..ba14fa7 --- /dev/null +++ b/src/main/extiodrv/device.c @@ -0,0 +1,182 @@ +#define LOG_MODULE "extiodrv-device" + +#include "device.h" + +#include "util/log.h" + +HRESULT extiodrv_device_open(const char *port, HANDLE *handle) +{ + HRESULT hr; + COMMTIMEOUTS ct; + DCB dcb; + + log_assert(port); + log_assert(handle); + + log_info("Opening extio on %s", port); + + *handle = CreateFile( + port, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + FILE_FLAG_WRITE_THROUGH | FILE_ATTRIBUTE_NORMAL, + NULL); + + if (*handle == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("Failed to open %s: %lX", port, hr); + + goto early_fail; + } + + if (!SetCommMask(*handle, EV_RXCHAR)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("SetCommMask failed: %lX", hr); + + goto fail; + } + + if (!SetupComm(*handle, 0x4000u, 0x4000u)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("SetupComm failed: %lX", hr); + + goto fail; + } + + if (!PurgeComm( + *handle, + PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("PurgeComm failed: %lX", hr); + + goto fail; + } + + ct.ReadTotalTimeoutConstant = 0; + ct.WriteTotalTimeoutConstant = 0; + ct.ReadIntervalTimeout = -1; + ct.ReadTotalTimeoutMultiplier = 10; + ct.WriteTotalTimeoutMultiplier = 100; + + if (!SetCommTimeouts(*handle, &ct)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("SetCommTimeouts failed: %lX", hr); + + goto fail; + } + + dcb.DCBlength = sizeof(dcb); + + if (!GetCommState(*handle, &dcb)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("GetCommState failed: %lX", hr); + + goto fail; + } + + dcb.BaudRate = 38400; + dcb.fBinary = TRUE; + dcb.fParity = FALSE; + dcb.fDtrControl = DTR_CONTROL_ENABLE; + dcb.fDsrSensitivity = FALSE; + dcb.fOutX = FALSE; + dcb.fInX = FALSE; + dcb.fErrorChar = FALSE; + dcb.fNull = FALSE; + dcb.fRtsControl = RTS_CONTROL_ENABLE; + dcb.fAbortOnError = FALSE; + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + dcb.XonChar = 17; + dcb.XoffChar = 19; + dcb.XonLim = 100; + dcb.XoffLim = 100; + + if (!SetCommState(*handle, &dcb)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("SetCommState failed: %lX", hr); + + goto fail; + } + + log_info("[%p] Opened extio device on %s", *handle, port); + + return S_OK; + +fail: + CloseHandle(*handle); + *handle = INVALID_HANDLE_VALUE; + +early_fail: + return hr; +} + +HRESULT extiodrv_device_close(HANDLE *handle) +{ + HRESULT hr; + + log_assert(handle); + + if (CloseHandle(*handle) == FALSE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; + } + + *handle = INVALID_HANDLE_VALUE; + + return S_OK; +} + +HRESULT extiodrv_device_read( + HANDLE handle, void *bytes, size_t nbytes, size_t *read_bytes) +{ + HRESULT hr; + DWORD dw_read_bytes; + + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(bytes); + log_assert(read_bytes); + + if (!ClearCommError(handle, NULL, NULL)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("[%p] ClearCommError failed: %lX", handle, hr); + + return hr; + } + + if (!ReadFile(handle, bytes, nbytes, &dw_read_bytes, NULL)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("[%p] ReadFile failed: %lX", handle, hr); + + return hr; + } + + *read_bytes = dw_read_bytes; + + return S_OK; +} + +HRESULT extiodrv_device_write( + HANDLE handle, const void *bytes, size_t nbytes, size_t *written_bytes) +{ + HRESULT hr; + DWORD dw_written_bytes; + + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(bytes); + log_assert(written_bytes); + + if (!WriteFile(handle, bytes, nbytes, &dw_written_bytes, NULL)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("[%p] WriteFile failed: %lX", handle, hr); + + return hr; + } + + *written_bytes = dw_written_bytes; + + return S_OK; +} \ No newline at end of file diff --git a/src/main/extiodrv/device.h b/src/main/extiodrv/device.h new file mode 100644 index 0000000..4121295 --- /dev/null +++ b/src/main/extiodrv/device.h @@ -0,0 +1,57 @@ +#ifndef EXTIODRV_DEVICE_H +#define EXTIODRV_DEVICE_H + +#include + +#include +#include + +/** + * Open an EXTIO device connected to a com port. + * + * @param port Com port the device is connected to, e.g. "COM1" + * @param handle Pointer to a HANDLE variable to return the handle to when + * succesfully opened + * @return S_OK on success with the handle returned in handle, other HRESULT + * value on error. + */ +HRESULT extiodrv_device_open(const char *port, HANDLE *handle); + +/** + * Close an opened EXTIO device + * + * @param handle Pointer to a handle variable that stores a valid and opened + * EXTIO handle. + * @return S_OK on success and the handle value is set to INVALID_HANDLE_VALUE, + * other HRESULT value on error. + */ +HRESULT extiodrv_device_close(HANDLE *handle); + +/** + * Read data from the EXTIO device. This call blocks until data is available. + * + * @param handle A valid handle to an opened EXTIO device. + * @param bytes Pointer to a buffer to read the data into + * @param nbytes (Maximum) number of bytes to read + * @param read_bytes Pointer to a variable to store the resulting read size to + * @return S_OK on success with read_bytes populated with the number of bytes + * read, other HRESULT value on error. + */ +HRESULT extiodrv_device_read( + HANDLE handle, void *bytes, size_t nbytes, size_t *read_bytes); + +/** + * WRite data to the EXTIO device. This call blocks until the data is written. + * + * @param handle A valid handle to an opened EXTIO device. + * @param bytes Pointer to a buffer with data to write to the device + * @param nbytes (Maximum) number of bytes to write + * @param written_bytes Pointer to a variable to store the resulting write size + * to + * @return S_OK on success with written_bytes populated with the number of bytes + * written, other HRESULT value on error. + */ +HRESULT extiodrv_device_write( + HANDLE handle, const void *bytes, size_t nbytes, size_t *written_bytes); + +#endif \ No newline at end of file diff --git a/src/main/extiodrv/extio.c b/src/main/extiodrv/extio.c new file mode 100644 index 0000000..8c50de2 --- /dev/null +++ b/src/main/extiodrv/extio.c @@ -0,0 +1,110 @@ +#define LOG_MODULE "extiodrv-extio" + +#include "extio.h" + +#include "util/log.h" + +// static uint8_t _extiodrv_extio_sensor_read_mode_map[5] = { +// 1, // all +// 2, // up +// 3, // down +// 4, // left +// 5, // right +// }; + +HRESULT extiodrv_extio_transfer( + HANDLE handle, + enum extiodrv_extio_sensor_read_mode sensor_read_mode, + const struct extiodrv_extio_pad_lights + pad_lights[EXTIO_PAD_LIGHT_MAX_PLAYERS], + bool neons) +{ + HRESULT hr; + struct extio_cmd_write write; + struct extio_cmd_read read; + size_t bytes_written; + size_t bytes_read; + const uint8_t *raw; + + log_assert(handle != INVALID_HANDLE_VALUE); + + memset(&write, 0, sizeof(write)); + + // TODO this doesn't seem to work, yet + // write.sensor_read_mode = + // 0;//_extiodrv_extio_sensor_read_mode_map[sensor_read_mode]; 0 = all 1 = + // mess? 2 = mess? 3 = mess? 4 = mess? 5 = mess? 6 = right sensor only 7 = + // right sensor only 8 = right sensor only + write.sensor_read_mode = 0; + + for (uint8_t i = 0; i < EXTIO_PAD_LIGHT_MAX_PLAYERS; i++) { + write.pad_lights[i].up = pad_lights[i].up ? 1 : 0; + write.pad_lights[i].down = pad_lights[i].down ? 1 : 0; + write.pad_lights[i].left = pad_lights[i].left ? 1 : 0; + write.pad_lights[i].right = pad_lights[i].right ? 1 : 0; + } + + write.neons = neons ? 1 : 0; + + // This MUST be set but only on the p1 packet + // Not setting it or also setting it on the p2 packet results in the EXTIO + // not responding at all + write.pad_lights[0].unknown_80 = 1; + + write.checksum = extio_cmd_checksum(&write); + + raw = (uint8_t *) &write; + + log_misc("Raw write paket: %X %X %X %X", raw[0], raw[1], raw[2], raw[3]); + + hr = extiodrv_device_write(handle, &write, sizeof(write), &bytes_written); + + if (FAILED(hr)) { + log_warning("Writing extio device failed"); + return hr; + } + + if (bytes_written != sizeof(write)) { + log_warning( + "Writing extio device failed, expected bytes %d != actual bytes %d", + (uint32_t) sizeof(write), + (uint32_t) bytes_written); + return HRESULT_FROM_WIN32(ERROR_BAD_LENGTH); + } + + // Read loop to keep reading until a full message is read + // There are various occasions, especially if polling from the host side is + // faster than the device can put the response to the wire. this can result + // in empty reads + while (true) { + hr = extiodrv_device_read(handle, &read, sizeof(read), &bytes_read); + + if (FAILED(hr)) { + log_warning("Reading extio device failed"); + return hr; + } + + if (bytes_read < sizeof(read)) { + log_misc("Empty read, retry read"); + continue; + } + + if (bytes_read != sizeof(read)) { + log_warning( + "Reading extio device failed, expected bytes %d != actual " + "bytes %d", + (uint32_t) sizeof(read), + (uint32_t) bytes_read); + return HRESULT_FROM_WIN32(ERROR_BAD_LENGTH); + } + + break; + } + + if (read.status != EXTIO_STATUS_OK) { + log_warning("Status not ok: 0x%X", read.status); + return HRESULT_FROM_WIN32(ERROR_GEN_FAILURE); + } + + return S_OK; +} \ No newline at end of file diff --git a/src/main/extiodrv/extio.h b/src/main/extiodrv/extio.h new file mode 100644 index 0000000..bc48c82 --- /dev/null +++ b/src/main/extiodrv/extio.h @@ -0,0 +1,45 @@ +#ifndef EXTIODRV_EXTIO_H +#define EXTIODRV_EXTIO_H + +#include + +#include + +#include "extio/cmd.h" + +#include "device.h" + +enum extiodrv_extio_sensor_read_mode { + EXTIODRV_EXTIO_SENSOR_READ_MODE_ALL = 0, + EXTIODRV_EXTIO_SENSOR_READ_MODE_UP = 1, + EXTIODRV_EXTIO_SENSOR_READ_MODE_DOWN = 2, + EXTIODRV_EXTIO_SENSOR_READ_MODE_LEFT = 3, + EXTIODRV_EXTIO_SENSOR_READ_MODE_RIGHT = 4 +}; + +struct extiodrv_extio_pad_lights { + bool up; + bool down; + bool left; + bool right; +}; + +/** + * Execute a data transfer (write + read) to the EXTIO device + * + * @param handle A valid and opened handle to an EXTIO device + * @param sensor_read_mode The sensor read mode to set on the EXTIO device. + * This only needs to be set once that input reads return the configured + * sensor read setting. + * @param pad_lights Pad light ouptut state to set + * @param neons Neon output state to set + * @return S_OK on success, other HRESULT value on error + */ +HRESULT extiodrv_extio_transfer( + HANDLE handle, + enum extiodrv_extio_sensor_read_mode sensor_read_mode, + const struct extiodrv_extio_pad_lights + pad_lights[EXTIO_PAD_LIGHT_MAX_PLAYERS], + bool neons); + +#endif \ No newline at end of file