Files
celrs/celrs/platform/serial_win.c
T
portersky 794ee9989a feat: implement Windows serial platform backend
Add cel_serial_platform_open/close/read/write/flush for
Windows using CreateFileA, DCB for baud/8N1, and
SetCommTimeouts for non-blocking reads.

serial.c now delegates all operations to the platform
backend via a cel_serial_platform_handle, and
test_serial.c mocks that backend with CMock.
2026-06-14 20:37:38 +02:00

100 lines
3.3 KiB
C

#include "celrs/platform/serial_internal.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
cel_serial_platform_handle cel_serial_platform_open(char const* path, int baud_rate) {
char full_path[300];
snprintf(full_path, sizeof(full_path), "\\\\.\\%s", path);
HANDLE h = CreateFileA(full_path, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) return CEL_SERIAL_PLATFORM_INVALID_HANDLE;
DCB dcb = {0};
dcb.DCBlength = sizeof(dcb);
if (!GetCommState(h, &dcb)) {
CloseHandle(h);
return CEL_SERIAL_PLATFORM_INVALID_HANDLE;
}
dcb.BaudRate = (DWORD)baud_rate;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
if (!SetCommState(h, &dcb)) {
CloseHandle(h);
return CEL_SERIAL_PLATFORM_INVALID_HANDLE;
}
/* Non-blocking reads: ReadFile returns immediately with whatever
* bytes are already buffered (possibly zero). */
COMMTIMEOUTS timeouts = {0};
timeouts.ReadIntervalTimeout = MAXDWORD;
if (!SetCommTimeouts(h, &timeouts)) {
CloseHandle(h);
return CEL_SERIAL_PLATFORM_INVALID_HANDLE;
}
return (cel_serial_platform_handle)(intptr_t)h;
}
void cel_serial_platform_close(cel_serial_platform_handle handle) {
CloseHandle((HANDLE)(intptr_t)handle);
}
size_t cel_serial_platform_read(cel_serial_platform_handle handle, uint8_t* buf, size_t len) {
DWORD bytes_read = 0;
if (!ReadFile((HANDLE)(intptr_t)handle, buf, (DWORD)len, &bytes_read, NULL)) return 0;
return (size_t)bytes_read;
}
size_t cel_serial_platform_write(cel_serial_platform_handle handle, uint8_t const* buf, size_t len) {
DWORD bytes_written = 0;
if (!WriteFile((HANDLE)(intptr_t)handle, buf, (DWORD)len, &bytes_written, NULL)) return 0;
return (size_t)bytes_written;
}
void cel_serial_platform_flush(cel_serial_platform_handle handle) {
FlushFileBuffers((HANDLE)(intptr_t)handle);
}
/* Enumerate active COM ports via HKLM\HARDWARE\DEVICEMAP\SERIALCOMM.
* Windows keeps one value per active port in this key, so this is a
* single registry read instead of probing COM1..COM255 with CreateFile. */
int cel_serial_platform_list_ports(char*** out_ports, int max_ports) {
char** ports = (char**)calloc(max_ports, sizeof(char*));
if (ports == NULL) return -1;
int count = 0;
HKEY key;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM",
0, KEY_READ, &key) == ERROR_SUCCESS) {
for (DWORD index = 0; count < max_ports; index++) {
char value_name[256];
char port_name[256];
DWORD value_name_len = sizeof(value_name);
DWORD port_name_len = sizeof(port_name);
DWORD type;
LONG result = RegEnumValueA(key, index, value_name, &value_name_len,
NULL, &type, (BYTE*)port_name, &port_name_len);
if (result != ERROR_SUCCESS) break;
if (type == REG_SZ) {
ports[count] = _strdup(port_name);
if (ports[count] != NULL) count++;
}
}
RegCloseKey(key);
}
*out_ports = ports;
return count;
}