Add native Windows build of meshtasticd (#11031)

This commit is contained in:
Thomas Göttgens
2026-07-19 23:31:19 +02:00
committed by GitHub
co-authored by GitHub
parent 4726375282
commit a808e992a1
16 changed files with 910 additions and 21 deletions
+12 -3
View File
@@ -319,7 +319,10 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
#else
rtc.initI2C();
#endif
tm *t = gmtime(&tv->tv_sec);
// tv_sec is a long, which is not time_t everywhere: on Windows
// time_t is 64-bit while long is 32-bit. Copy before taking &.
time_t setSecs = tv->tv_sec;
tm *t = gmtime(&setSecs);
rtc.setTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_wday, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
LOG_DEBUG("RV3028_RTC setTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
@@ -341,7 +344,10 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
#else
rtc.begin(Wire);
#endif
tm *t = gmtime(&tv->tv_sec);
// tv_sec is a long, which is not time_t everywhere: on Windows
// time_t is 64-bit while long is 32-bit. Copy before taking &.
time_t setSecs = tv->tv_sec;
tm *t = gmtime(&setSecs);
rtc.setDateTime(*t);
LOG_DEBUG("%s setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", rtc.getChipName(), t->tm_year + 1900, t->tm_mon + 1,
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
@@ -355,7 +361,10 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
#else
ArtronShop_RX8130CE rtc(&Wire);
#endif
tm *t = gmtime(&tv->tv_sec);
// tv_sec is a long, which is not time_t everywhere: on Windows
// time_t is 64-bit while long is 32-bit. Copy before taking &.
time_t setSecs = tv->tv_sec;
tm *t = gmtime(&setSecs);
if (rtc.setTime(*t)) {
LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1,
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
+4
View File
@@ -428,10 +428,14 @@ void setup()
#if ARCH_PORTDUINO
RTCQuality ourQuality = RTCQualityDevice;
#ifdef __linux__
// timedatectl is systemd-only, so macOS, Windows and WASM stay at
// RTCQualityDevice rather than claim NTP quality we have not verified.
std::string timeCommandResult = exec("timedatectl status | grep synchronized | grep yes -c");
if (timeCommandResult[0] == '1') {
ourQuality = RTCQualityNTP;
}
#endif
struct timeval tv;
tv.tv_sec = time(NULL);
+12
View File
@@ -22,6 +22,12 @@ extern Adafruit_nRFCrypto nRFCrypto;
#include <unistd.h>
#ifdef __linux__
#include <sys/random.h> // getrandom()
#elif defined(_WIN32)
// Order is load-bearing, hence the blank line: bcrypt.h uses LONG/ULONG from
// windows.h and does not include it itself.
#include <windows.h>
#include <bcrypt.h> // BCryptGenRandom()
#else
#include <stdlib.h> // arc4random_buf() on Darwin/BSD
#endif
@@ -128,6 +134,12 @@ bool fill(uint8_t *buffer, size_t length, bool useRadioEntropy)
if (generated == static_cast<ssize_t>(length)) {
filled = true;
}
#elif defined(_WIN32)
// No getrandom/arc4random on Windows; BCryptGenRandom is the documented CSPRNG.
// Preferred over std::random_device, whose libstdc++ Windows backend reports entropy() == 0.
if (BCryptGenRandom(NULL, buffer, static_cast<ULONG>(length), BCRYPT_USE_SYSTEM_PREFERRED_RNG) == 0) { // STATUS_SUCCESS
filled = true;
}
#elif defined(__EMSCRIPTEN__)
// Browser/wasm: no getrandom/arc4random - fall through to std::random_device,
// which emscripten backs with crypto.getRandomValues().
+4
View File
@@ -33,7 +33,11 @@
#include "IPAddress.h"
#if defined(ARCH_PORTDUINO)
#if defined(_WIN32)
#include <winsock2.h> // ntohl()
#else
#include <netinet/in.h>
#endif
#elif !defined(ntohl)
#include <machine/endian.h>
#define ntohl __ntohl
+92 -11
View File
@@ -3,13 +3,92 @@
#include "GpsdSerial.h"
#include "configuration.h"
#include <arpa/inet.h>
#include <cerrno>
#ifdef _WIN32
#include <mutex>
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
namespace
{
// Winsock needs explicit init, closesocket(), ioctlsocket() and WSAGetLastError().
// SOCKET fits the header's `int` on Win64, and INVALID_SOCKET narrows to -1.
#ifdef _WIN32
// Done lazily to keep the dependency local to the one file that needs it.
void initSocketsOnce()
{
static std::once_flag flag;
std::call_once(flag, [] {
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
});
}
void closeSocket(int fd)
{
::closesocket(static_cast<SOCKET>(fd));
}
void setNonBlocking(int fd)
{
u_long mode = 1;
::ioctlsocket(static_cast<SOCKET>(fd), FIONBIO, &mode);
}
// Winsock has no MSG_DONTWAIT, but the socket is already non-blocking so a plain
// recv() has the same semantics.
int recvNonBlocking(int fd, void *buf, size_t len)
{
return ::recv(static_cast<SOCKET>(fd), static_cast<char *>(buf), static_cast<int>(len), 0);
}
int sendAll(int fd, const void *buf, size_t len)
{
return ::send(static_cast<SOCKET>(fd), static_cast<const char *>(buf), static_cast<int>(len), 0);
}
bool lastErrorWasWouldBlock()
{
return WSAGetLastError() == WSAEWOULDBLOCK;
}
#else
void initSocketsOnce() {}
void closeSocket(int fd)
{
::close(fd);
}
void setNonBlocking(int fd)
{
::fcntl(fd, F_SETFL, O_NONBLOCK);
}
int recvNonBlocking(int fd, void *buf, size_t len)
{
return static_cast<int>(::recv(fd, buf, len, MSG_DONTWAIT));
}
int sendAll(int fd, const void *buf, size_t len)
{
return static_cast<int>(::write(fd, buf, len));
}
bool lastErrorWasWouldBlock()
{
return errno == EAGAIN || errno == EWOULDBLOCK;
}
#endif
} // namespace
namespace arduino
{
@@ -28,6 +107,8 @@ bool GpsdSerial::connectToGpsd()
if (_host.empty())
return false;
initSocketsOnce();
struct addrinfo hints = {}, *res = nullptr;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
@@ -41,12 +122,12 @@ bool GpsdSerial::connectToGpsd()
// Try every address returned by getaddrinfo (e.g. ::1 before 127.0.0.1).
int fd = -1;
for (struct addrinfo *rp = res; rp != nullptr; rp = rp->ai_next) {
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
fd = static_cast<int>(socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol));
if (fd < 0)
continue;
if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0)
if (connect(fd, rp->ai_addr, static_cast<int>(rp->ai_addrlen)) == 0)
break; // connected
::close(fd);
closeSocket(fd);
fd = -1;
}
freeaddrinfo(res);
@@ -57,11 +138,11 @@ bool GpsdSerial::connectToGpsd()
}
// Switch to non-blocking so available()/read() never stall the GPS thread.
fcntl(fd, F_SETFL, O_NONBLOCK);
setNonBlocking(fd);
// Ask gpsd to stream raw NMEA sentences.
const char watchCmd[] = "?WATCH={\"enable\":true,\"nmea\":true}\n";
::write(fd, watchCmd, sizeof(watchCmd) - 1);
sendAll(fd, watchCmd, sizeof(watchCmd) - 1);
_sockfd = fd;
_rxBuf.clear();
@@ -80,7 +161,7 @@ void GpsdSerial::begin(unsigned long /*baud*/, uint16_t /*config*/)
void GpsdSerial::end()
{
if (_sockfd >= 0) {
::close(_sockfd);
closeSocket(_sockfd);
_sockfd = -1;
}
_rxBuf.clear();
@@ -96,18 +177,18 @@ void GpsdSerial::fillBuffer()
return;
uint8_t tmp[256];
ssize_t n;
while (_rxBuf.size() < RX_BUF_MAX && (n = recv(_sockfd, tmp, sizeof(tmp), MSG_DONTWAIT)) > 0) {
int n;
while (_rxBuf.size() < RX_BUF_MAX && (n = recvNonBlocking(_sockfd, tmp, sizeof(tmp))) > 0) {
size_t space = RX_BUF_MAX - _rxBuf.size();
size_t toCopy = (static_cast<size_t>(n) < space) ? static_cast<size_t>(n) : space;
for (size_t i = 0; i < toCopy; i++)
_rxBuf.push_back(tmp[i]);
}
if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) {
if (n == 0 || (n < 0 && !lastErrorWasWouldBlock())) {
// gpsd closed the connection or a real error occurred.
LOG_WARN("gpsdSerial: disconnected, will retry");
::close(_sockfd);
closeSocket(_sockfd);
_sockfd = -1;
_rxBuf.clear();
}
+18 -2
View File
@@ -21,8 +21,12 @@
#include <memory>
#include <set>
#include <stdexcept>
#include <sys/ioctl.h>
#include <unistd.h>
#ifndef _WIN32
// Only the PORTDUINO_LINUX_HARDWARE block below calls ioctl() (HCIGETDEVINFO,
// for the BlueZ-derived MAC address); Windows has no <sys/ioctl.h>.
#include <sys/ioctl.h>
#endif
#ifdef PORTDUINO_LINUX_HARDWARE
#include "linux/gpio/LinuxGPIOPin.h"
@@ -34,6 +38,12 @@
#include <cxxabi.h>
#endif
#ifdef _WIN32
// Defined in WindowsMacAddr.cpp, which keeps <iphlpapi.h> out of this TU: it
// pulls in RPC/OLE headers that collide with the Arduino API.
bool portduinoWindowsPrimaryMac(uint8_t *dmac);
#endif
#ifdef __APPLE__
// Used by getMacAddr()'s macOS fallback to read the en0 link-layer address.
// `getifaddrs()` is the BSD-portable way; `<net/if_dl.h>` provides the
@@ -193,6 +203,10 @@ void getMacAddr(uint8_t *dmac)
}
freeifaddrs(ifap);
}
#elif defined(_WIN32)
// No BlueZ on Windows; the host's primary adapter MAC is the equivalent
// stable identifier. On failure dmac is untouched and the blank-MAC check fires.
portduinoWindowsPrimaryMac(dmac);
#else
// No platform-specific MAC source; leave dmac at its default. Caller
// can override via the --hwid CLI flag or the YAML config.
@@ -292,7 +306,9 @@ void portduinoSetup()
std::filesystem::directory_iterator{portduino_config.config_directory}) {
if (ends_with(entry.path().string(), ".yaml")) {
std::cout << "Also using " << entry << " as additional config file" << std::endl;
loadConfig(entry.path().c_str());
// .string() rather than .c_str(): path::value_type is wchar_t on
// Windows, and loadConfig() takes a const char *.
loadConfig(entry.path().string().c_str());
}
}
}
+1
View File
@@ -8,6 +8,7 @@
#include <cstring>
#include <iostream>
#include <libpinedio-usb.h>
#include <sys/time.h> // gettimeofday(), previously pulled in via libusb.h
#include <unistd.h>
extern uint32_t rebootAtMsec;
+54
View File
@@ -0,0 +1,54 @@
#if defined(ARCH_PORTDUINO) && defined(_WIN32)
// Host-MAC lookup for getMacAddr(), replacing BlueZ on Linux and en0 on macOS.
// Isolated TU: <iphlpapi.h> needs the header trims the env sets, and undoes them here.
#undef WIN32_LEAN_AND_MEAN
#undef NOUSER
#undef NOGDI
// Order is load-bearing, hence the blank lines: winsock2.h must precede
// windows.h, which would otherwise pull in the colliding winsock v1 header.
#include <winsock2.h>
#include <windows.h>
#include <iphlpapi.h>
#include <cstring>
#include <memory>
#include <stdint.h>
// Fill dmac with the first up, non-loopback adapter's physical address, else
// return false. Adapter order is stable across reboots, so the identity persists.
bool portduinoWindowsPrimaryMac(uint8_t *dmac)
{
const ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME;
ULONG bufLen = 15000; // starting size recommended by the API docs
std::unique_ptr<char[]> buf(new char[bufLen]);
auto *addrs = reinterpret_cast<IP_ADAPTER_ADDRESSES *>(buf.get());
ULONG ret = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, addrs, &bufLen);
if (ret == ERROR_BUFFER_OVERFLOW) {
// bufLen now holds the required size; retry once.
buf.reset(new char[bufLen]);
addrs = reinterpret_cast<IP_ADAPTER_ADDRESSES *>(buf.get());
ret = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, addrs, &bufLen);
}
if (ret != NO_ERROR)
return false;
for (auto *a = addrs; a != nullptr; a = a->Next) {
if (a->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
continue;
if (a->OperStatus != IfOperStatusUp)
continue;
if (a->PhysicalAddressLength != 6)
continue;
std::memcpy(dmac, a->PhysicalAddress, 6);
return true;
}
return false;
}
#endif // ARCH_PORTDUINO && _WIN32
@@ -0,0 +1,75 @@
// Windows drop-in for libch341-spi-userspace's public header, mirroring the wasm
// one in ../../wasm/include/. Same API as upstream, but backed by CH341DLL, not libusb.
#ifndef PINEDIO_USB_CH341DLL_H
#define PINEDIO_USB_CH341DLL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
enum pinedio_int_pin {
PINEDIO_PIN_D0,
PINEDIO_PIN_D1,
PINEDIO_PIN_D2,
PINEDIO_PIN_D3,
PINEDIO_PIN_D4,
PINEDIO_PIN_D5,
PINEDIO_PIN_D6,
PINEDIO_PIN_D7,
PINEDIO_PIN_ERR,
PINEDIO_PIN_PEMP,
PINEDIO_PIN_INT,
PINEDIO_INT_PIN_MAX
};
enum pinedio_int_mode {
PINEDIO_INT_MODE_RISING = 0x01,
PINEDIO_INT_MODE_FALLING = 0x02,
};
enum pinedio_option {
PINEDIO_OPTION_AUTO_CS,
PINEDIO_OPTION_SEARCH_SERIAL,
PINEDIO_OPTION_VID,
PINEDIO_OPTION_PID,
PINEDIO_OPTION_MAX
};
struct pinedio_inst_int {
uint8_t previous_state;
enum pinedio_int_mode mode;
void (*callback)(void);
};
// Ch341Hal embeds this by value and touches in_error, serial_number,
// product_string and options[], so those must keep their names.
struct pinedio_inst {
bool in_error;
struct pinedio_inst_int interrupts[PINEDIO_INT_PIN_MAX];
uint32_t options[PINEDIO_OPTION_MAX];
char serial_number[9];
char product_string[97];
};
typedef struct pinedio_inst pinedio_inst;
int32_t pinedio_init(struct pinedio_inst *inst, void *driver);
int32_t pinedio_set_option(struct pinedio_inst *inst, enum pinedio_option option, uint32_t value);
int32_t pinedio_set_pin_mode(struct pinedio_inst *inst, uint32_t pin, uint32_t mode);
int32_t pinedio_digital_write(struct pinedio_inst *inst, uint32_t pin, bool active);
int32_t pinedio_set_cs(struct pinedio_inst *inst, bool active);
int32_t pinedio_write_read(struct pinedio_inst *inst, uint8_t *writearr, uint32_t writecnt, uint8_t *readarr, uint32_t readcnt);
int32_t pinedio_transceive(struct pinedio_inst *inst, uint8_t *write_buf, uint8_t *read_buf, uint32_t count);
int32_t pinedio_digital_read(struct pinedio_inst *inst, uint32_t pin);
int32_t pinedio_get_irq_state(struct pinedio_inst *inst, uint32_t pin);
int32_t pinedio_attach_interrupt(struct pinedio_inst *inst, enum pinedio_int_pin int_pin, enum pinedio_int_mode int_mode,
void (*callback)(void));
int32_t pinedio_deattach_interrupt(struct pinedio_inst *inst, enum pinedio_int_pin int_pin);
void pinedio_deinit(struct pinedio_inst *inst);
#ifdef __cplusplus
}
#endif
#endif // PINEDIO_USB_CH341DLL_H
@@ -0,0 +1,400 @@
// libpinedio API over WCH's CH341DLL, replacing the libusb backend on Windows:
// libusb would need Zadig to rebind the driver, CH341DLL ships with CH341PAR.
#if defined(ARCH_PORTDUINO) && defined(_WIN32)
#include "libpinedio-usb.h"
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
// Pins are the CH341's D0-D7: CS 0, Reset 2, SCK 3, MOSI 5, IRQ 6, MISO 7.
// CH341Set_D5_D0 reaches only the outputs; D6/D7 are read via CH341GetInput.
#define CH341_MAX_OUTPUT_PIN 5
// iMode bit 7: SPI bit order, 1 = MSB first. The SX1262 is MSB-first, and doing
// it in hardware avoids upstream's per-byte reverse_byte() over the whole buffer.
#define CH341_STREAM_MODE_SPI_MSB_FIRST 0x80
// Matches upstream's poll interval, so IRQ latency is the same as on Linux.
#define PIN_POLL_INTERVAL_MS (1000 / 30)
typedef HANDLE(WINAPI *ch341_open_t)(ULONG);
typedef VOID(WINAPI *ch341_close_t)(ULONG);
typedef BOOL(WINAPI *ch341_set_stream_t)(ULONG, ULONG);
typedef BOOL(WINAPI *ch341_stream_spi4_t)(ULONG, ULONG, ULONG, PVOID);
typedef BOOL(WINAPI *ch341_set_d5_d0_t)(ULONG, ULONG, ULONG);
typedef BOOL(WINAPI *ch341_get_input_t)(ULONG, PULONG);
typedef PVOID(WINAPI *ch341_get_device_name_t)(ULONG);
static struct {
HMODULE dll;
ch341_open_t open;
ch341_close_t close;
ch341_set_stream_t set_stream;
ch341_stream_spi4_t stream_spi4;
ch341_set_d5_d0_t set_d5_d0;
ch341_get_input_t get_input;
ch341_get_device_name_t get_device_name;
} ch341;
// Single adapter, matching Ch341Hal's spiChannel of 0. Multiple sticks would
// need this and the device index threaded through pinedio_inst.
static ULONG ch341_index = 0;
// D0-D5 direction and output state, applied together by CH341Set_D5_D0.
static ULONG pin_dir_out = 0;
static ULONG pin_state = 0;
// Serializes DLL access between the caller and the poll thread.
static pthread_mutex_t usb_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_t poll_thread;
static volatile bool poll_thread_exit = false;
static int int_running_cnt = 0;
// CH341PAR installs the 64-bit library as CH341DLLA64.DLL and the 32-bit one as
// CH341DLL.DLL, so try the name matching this process first.
static const char *const ch341_dll_names[] = {
#ifdef _WIN64
"CH341DLLA64.DLL",
"CH341DLL.DLL",
#else
"CH341DLL.DLL",
"CH341DLLA64.DLL",
#endif
};
// The DLL is not redistributable, so it is resolved at runtime: without it
// pinedio_init() fails and Ch341Hal throws, as it does for a missing device.
static bool load_dll(void)
{
if (ch341.dll)
return true;
for (size_t i = 0; i < sizeof(ch341_dll_names) / sizeof(ch341_dll_names[0]); i++) {
ch341.dll = LoadLibraryA(ch341_dll_names[i]);
if (ch341.dll)
break;
}
if (!ch341.dll) {
fprintf(stderr, "CH341DLL not found; install the WCH CH341PAR driver\n");
return false;
}
ch341.open = (ch341_open_t)(void *)GetProcAddress(ch341.dll, "CH341OpenDevice");
ch341.close = (ch341_close_t)(void *)GetProcAddress(ch341.dll, "CH341CloseDevice");
ch341.set_stream = (ch341_set_stream_t)(void *)GetProcAddress(ch341.dll, "CH341SetStream");
ch341.stream_spi4 = (ch341_stream_spi4_t)(void *)GetProcAddress(ch341.dll, "CH341StreamSPI4");
ch341.set_d5_d0 = (ch341_set_d5_d0_t)(void *)GetProcAddress(ch341.dll, "CH341Set_D5_D0");
ch341.get_input = (ch341_get_input_t)(void *)GetProcAddress(ch341.dll, "CH341GetInput");
ch341.get_device_name = (ch341_get_device_name_t)(void *)GetProcAddress(ch341.dll, "CH341GetDeviceName");
if (!ch341.open || !ch341.close || !ch341.set_stream || !ch341.stream_spi4 || !ch341.set_d5_d0 || !ch341.get_input) {
fprintf(stderr, "CH341DLL is missing expected exports\n");
FreeLibrary(ch341.dll);
ch341.dll = NULL;
return false;
}
return true;
}
static int32_t apply_pins(void)
{
if (!ch341.set_d5_d0(ch341_index, pin_dir_out, pin_state))
return -1;
return 0;
}
int32_t pinedio_set_option(struct pinedio_inst *inst, enum pinedio_option option, uint32_t value)
{
if (option < PINEDIO_OPTION_MAX)
inst->options[option] = value;
return 0;
}
int32_t pinedio_init(struct pinedio_inst *inst, void *driver)
{
(void)driver;
inst->in_error = false;
for (int i = 0; i < PINEDIO_INT_PIN_MAX; i++)
inst->interrupts[i].callback = NULL;
if (!load_dll())
return -1;
if (ch341.open(ch341_index) == INVALID_HANDLE_VALUE) {
fprintf(stderr, "CH341OpenDevice(%lu) failed; is the adapter plugged in?\n", ch341_index);
return -2;
}
// Default speed (bits 0-1 = 01) plus MSB-first.
if (!ch341.set_stream(ch341_index, 0x01 | CH341_STREAM_MODE_SPI_MSB_FIRST)) {
fprintf(stderr, "CH341SetStream failed\n");
ch341.close(ch341_index);
return -3;
}
pin_dir_out = 0;
pin_state = 0;
// CH341DLL exposes no USB serial string. Leaving it empty is safe: on Windows
// getMacAddr() uses the host adapter, and the device name stands in for the product.
inst->serial_number[0] = '\0';
inst->product_string[0] = '\0';
if (ch341.get_device_name) {
const char *name = (const char *)ch341.get_device_name(ch341_index);
if (name) {
strncpy(inst->product_string, name, sizeof(inst->product_string) - 1);
inst->product_string[sizeof(inst->product_string) - 1] = '\0';
}
}
return 0;
}
int32_t pinedio_set_pin_mode(struct pinedio_inst *inst, uint32_t pin, uint32_t mode)
{
(void)inst;
if (pin > CH341_MAX_OUTPUT_PIN)
return 0; // D6/D7 are input-only; nothing to configure
// Under the lock: the poll thread's callback can drive GPIO concurrently.
pthread_mutex_lock(&usb_mutex);
if (mode)
pin_dir_out |= (1u << pin);
else
pin_dir_out &= ~(1u << pin);
pthread_mutex_unlock(&usb_mutex);
// Upstream defers the device write to the next digital_write; do the same so
// the direction and level land together.
return 0;
}
int32_t pinedio_digital_write(struct pinedio_inst *inst, uint32_t pin, bool active)
{
if (pin > CH341_MAX_OUTPUT_PIN)
return -1;
// Mutate and apply as one critical section, or a concurrent write loses its
// update when apply_pins() reads a half-updated pin_state.
pthread_mutex_lock(&usb_mutex);
if (active)
pin_state |= (1u << pin);
else
pin_state &= ~(1u << pin);
int32_t ret = apply_pins();
pthread_mutex_unlock(&usb_mutex);
if (ret < 0)
inst->in_error = true;
return ret;
}
int32_t pinedio_set_cs(struct pinedio_inst *inst, bool active)
{
return pinedio_digital_write(inst, 0, active); // D0 is CS
}
static int32_t read_input(uint32_t *out)
{
ULONG status = 0;
if (!ch341.get_input(ch341_index, &status))
return -1;
*out = (uint32_t)status;
return 0;
}
int32_t pinedio_digital_read(struct pinedio_inst *inst, uint32_t pin)
{
uint32_t status = 0;
pthread_mutex_lock(&usb_mutex);
int32_t ret = read_input(&status);
pthread_mutex_unlock(&usb_mutex);
if (ret < 0) {
inst->in_error = true;
return ret;
}
return (status & (1u << pin)) ? 1 : 0; // bits 7-0 are D7-D0
}
int32_t pinedio_get_irq_state(struct pinedio_inst *inst, uint32_t pin)
{
return pinedio_digital_read(inst, pin);
}
int32_t pinedio_transceive(struct pinedio_inst *inst, uint8_t *write_buf, uint8_t *read_buf, uint32_t count)
{
if (count == 0)
return 0;
// CH341StreamSPI4 is full duplex over one in/out buffer.
uint8_t stack_buf[64];
uint8_t *buf = count <= sizeof(stack_buf) ? stack_buf : (uint8_t *)malloc(count);
if (!buf)
return -1;
memcpy(buf, write_buf, count);
// Bit 7 clear: leave chip select alone. Ch341Hal sets PINEDIO_OPTION_AUTO_CS
// to 0 and lets RadioLib drive NSS through digitalWrite.
ULONG cs = 0;
if (inst->options[PINEDIO_OPTION_AUTO_CS])
cs = 0x80; // enable, D0 active low
pthread_mutex_lock(&usb_mutex);
BOOL ok = ch341.stream_spi4(ch341_index, cs, count, buf);
pthread_mutex_unlock(&usb_mutex);
if (ok && read_buf)
memcpy(read_buf, buf, count);
if (buf != stack_buf)
free(buf);
if (!ok) {
inst->in_error = true;
return -1;
}
return 0;
}
int32_t pinedio_write_read(struct pinedio_inst *inst, uint8_t *writearr, uint32_t writecnt, uint8_t *readarr, uint32_t readcnt)
{
uint32_t total = writecnt + readcnt;
uint8_t stack_buf[64];
uint8_t *buf = total <= sizeof(stack_buf) ? stack_buf : (uint8_t *)malloc(total);
if (!buf)
return -1;
memcpy(buf, writearr, writecnt);
memset(buf + writecnt, 0, readcnt);
int32_t ret = pinedio_transceive(inst, buf, buf, total);
if (ret == 0)
memcpy(readarr, buf + writecnt, readcnt);
if (buf != stack_buf)
free(buf);
return ret;
}
// CH341SetIntRoutine only fires on the INT# pin, but the adapter wires DIO1 to
// D6, so poll D6 instead, at upstream's rate and edge semantics.
static void *pin_poll_thread_fn(void *arg)
{
struct pinedio_inst *inst = (struct pinedio_inst *)arg;
bool should_exit = false;
while (!should_exit) {
uint32_t input = 0;
pthread_mutex_lock(&usb_mutex);
int32_t ret = read_input(&input);
pthread_mutex_unlock(&usb_mutex);
if (ret < 0) {
inst->in_error = true;
fprintf(stderr, "CH341 poll: failed to read input\n");
break;
}
inst->in_error = false;
pthread_mutex_lock(&usb_mutex);
for (uint8_t int_pin = 0; int_pin < PINEDIO_INT_PIN_MAX; int_pin++) {
struct pinedio_inst_int *inst_int = &inst->interrupts[int_pin];
// Copy under the lock: pinedio_deattach_interrupt() could NULL it
// once we drop the lock to make the call.
void (*cb)(void) = inst_int->callback;
if (cb == NULL)
continue;
uint8_t state = (input & (1u << int_pin)) != 0;
if (inst_int->previous_state != 255 && inst_int->previous_state != state) {
enum pinedio_int_mode mode =
(!inst_int->previous_state && state) ? PINEDIO_INT_MODE_RISING : PINEDIO_INT_MODE_FALLING;
if (inst_int->mode & mode) {
// Callback re-enters this library, so drop the lock first.
pthread_mutex_unlock(&usb_mutex);
cb();
pthread_mutex_lock(&usb_mutex);
}
}
inst_int->previous_state = state;
}
should_exit = poll_thread_exit;
pthread_mutex_unlock(&usb_mutex);
Sleep(PIN_POLL_INTERVAL_MS);
}
return NULL;
}
int32_t pinedio_attach_interrupt(struct pinedio_inst *inst, enum pinedio_int_pin int_pin, enum pinedio_int_mode int_mode,
void (*callback)(void))
{
if (int_pin >= PINEDIO_INT_PIN_MAX)
return -1;
int32_t res = 0;
pthread_mutex_lock(&usb_mutex);
bool was_attached = inst->interrupts[int_pin].callback != NULL;
inst->interrupts[int_pin].previous_state = 255;
inst->interrupts[int_pin].mode = int_mode;
inst->interrupts[int_pin].callback = callback;
// Only a new attachment changes the refcount. Re-attaching an armed pin
// (RadioLib does this) must not reach 0 and spawn a second poll thread.
if (!was_attached) {
if (int_running_cnt == 0) {
poll_thread_exit = false;
res = pthread_create(&poll_thread, NULL, pin_poll_thread_fn, inst);
if (res != 0) {
fprintf(stderr, "CH341: failed to start poll thread: %d\n", res);
inst->interrupts[int_pin].callback = NULL;
pthread_mutex_unlock(&usb_mutex);
return res;
}
}
int_running_cnt++;
}
pthread_mutex_unlock(&usb_mutex);
return res;
}
int32_t pinedio_deattach_interrupt(struct pinedio_inst *inst, enum pinedio_int_pin int_pin)
{
if (int_pin >= PINEDIO_INT_PIN_MAX)
return -1;
pthread_t thread_to_join;
pthread_mutex_lock(&usb_mutex);
bool was_attached = inst->interrupts[int_pin].callback != NULL;
inst->interrupts[int_pin].callback = NULL;
if (was_attached)
int_running_cnt--;
bool stop = was_attached && int_running_cnt == 0;
if (stop) {
poll_thread_exit = true;
// Copy the handle: a concurrent attach could overwrite poll_thread once
// the lock is dropped, and we would join the wrong thread.
thread_to_join = poll_thread;
}
pthread_mutex_unlock(&usb_mutex);
// Joining under the lock would deadlock against the poll thread taking it.
if (stop && !pthread_equal(thread_to_join, pthread_self()))
pthread_join(thread_to_join, NULL);
return 0;
}
void pinedio_deinit(struct pinedio_inst *inst)
{
pthread_t thread_to_join;
pthread_mutex_lock(&usb_mutex);
bool stop = int_running_cnt > 0;
poll_thread_exit = true;
int_running_cnt = 0;
if (stop)
thread_to_join = poll_thread; // copy before dropping the lock, as above
pthread_mutex_unlock(&usb_mutex);
if (stop && !pthread_equal(thread_to_join, pthread_self()))
pthread_join(thread_to_join, NULL);
if (ch341.dll)
ch341.close(ch341_index);
inst->in_error = false;
}
#endif // ARCH_PORTDUINO && _WIN32
+11 -3
View File
@@ -1,19 +1,27 @@
#include "PowerHAL.h"
// PE/COFF has no ELF-style weak definitions, so a weak default is an undefined
// reference on Windows. Only nrf52 and nrf54l15 override these; define them strongly.
#ifdef _WIN32
#define POWERHAL_WEAK_DEFAULT __attribute__((noinline))
#else
#define POWERHAL_WEAK_DEFAULT __attribute__((weak, noinline))
#endif
void powerHAL_init()
{
return powerHAL_platformInit();
}
__attribute__((weak, noinline)) void powerHAL_platformInit() {}
POWERHAL_WEAK_DEFAULT void powerHAL_platformInit() {}
__attribute__((weak, noinline)) bool powerHAL_isPowerLevelSafe()
POWERHAL_WEAK_DEFAULT bool powerHAL_isPowerLevelSafe()
{
return true;
}
__attribute__((weak, noinline)) bool powerHAL_isVBUSConnected()
POWERHAL_WEAK_DEFAULT bool powerHAL_isVBUSConnected()
{
return false;
}