diff --git a/connections/candle_api/README.md b/connections/candle_api/README.md index 2d06def..de68083 100644 --- a/connections/candle_api/README.md +++ b/connections/candle_api/README.md @@ -1,6 +1,6 @@ # candle API (gs_usb / candleLight Windows driver) -Vendored, unmodified copy of the candle Windows API used by SavvyCAN's gs_usb +Vendored copy of the candle Windows API used by SavvyCAN's gs_usb connection (`connections/gs_usb.cpp`). It talks to gs_usb class devices (candleLight, CANable, CANnectivity, cantact, ...) over WinUSB, so no vendor driver is needed — only a WinUSB binding on the device interface (which the @@ -13,6 +13,14 @@ stock candleLight/CANable firmware already declares via WCID descriptors). at commit `ed3088d5393e02a01afe867af2ee8ab0e12db039`, which adds CAN FD, multi-channel and hardware timestamp support — Copyright (c) 2026 Schildkroet +## Local changes + +Kept to the minimum needed to build here; re-apply them when refreshing from upstream. + +- `candle.h` / `candle.c`: the three functions returning a pointer declared the calling + convention before the `*` (`wchar_t __stdcall DLL *candle_dev_get_path`). MinGW accepts + that, MSVC rejects it with C2165, so `__stdcall` moved behind the `*`. + ## License These files are **LGPL-3.0**, not MIT like the rest of SavvyCAN. They are only diff --git a/connections/candle_api/candle.c b/connections/candle_api/candle.c index c698a93..311a4cf 100644 --- a/connections/candle_api/candle.c +++ b/connections/candle_api/candle.c @@ -1,1145 +1,1145 @@ -/* - - Copyright (c) 2016 Hubert Denkmair - - This file is part of the candle windows API. - - This library is free software: you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see . - -*/ - -#include "candle.h" - -#include -#include -#include - -#include "candle_defs.h" -#include "candle_ctrl_req.h" -#include "ch_9.h" - -static bool candle_dev_interal_open(candle_handle hdev); - -candle_log_fn_t candle_log_fn = NULL; -bool candle_log_verbose = false; - -static void candle_logf(const wchar_t *fmt, ...) -{ - if (candle_log_fn == NULL) { - return; - } - - wchar_t buf[512]; - va_list args; - va_start(args, fmt); - HRESULT hr = StringCchVPrintfW(buf, 512, fmt, args); - va_end(args); - - if (SUCCEEDED(hr)) { - candle_log_fn(buf); - } -} - -static void candle_logf_verbose(const wchar_t *fmt, ...) -{ - if (candle_log_fn == NULL || !candle_log_verbose) { - return; - } - - wchar_t buf[512]; - va_list args; - va_start(args, fmt); - HRESULT hr = StringCchVPrintfW(buf, 512, fmt, args); - va_end(args); - - if (SUCCEEDED(hr)) { - candle_log_fn(buf); - } -} - -static bool candle_read_di(HDEVINFO hdi, SP_DEVICE_INTERFACE_DATA interfaceData, candle_device_t *dev) -{ - /* get required length first (this call always fails with an error) */ - ULONG requiredLength=0; - SetupDiGetDeviceInterfaceDetail(hdi, &interfaceData, NULL, 0, &requiredLength, NULL); - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - dev->last_error = CANDLE_ERR_SETUPDI_IF_DETAILS; - return false; - } - - PSP_DEVICE_INTERFACE_DETAIL_DATA detail_data = - (PSP_DEVICE_INTERFACE_DETAIL_DATA) LocalAlloc(LMEM_FIXED, requiredLength); - - if (detail_data != NULL) { - detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); - } else { - dev->last_error = CANDLE_ERR_MALLOC; - return false; - } - - bool retval = true; - ULONG length = requiredLength; - if (!SetupDiGetDeviceInterfaceDetail(hdi, &interfaceData, detail_data, length, &requiredLength, NULL) ) { - dev->last_error = CANDLE_ERR_SETUPDI_IF_DETAILS2; - retval = false; - } else if (FAILED(StringCchCopy(dev->path, sizeof(dev->path), detail_data->DevicePath))) { - dev->last_error = CANDLE_ERR_PATH_LEN; - retval = false; - } - - LocalFree(detail_data); - - if (!retval) { - return false; - } - - /* try to open to read device infos and see if it is avail */ - if (candle_dev_interal_open(dev)) { - dev->state = CANDLE_DEVSTATE_AVAIL; - candle_dev_close(dev); - } else { - dev->state = CANDLE_DEVSTATE_INUSE; - } - - dev->last_error = CANDLE_ERR_OK; - return true; -} - -/* Return true when path already appears in l->dev[0..count-1]. */ -static bool candle_path_exists(const candle_list_t *l, unsigned count, const wchar_t *path) -{ - for (unsigned i = 0; i < count; i++) { - if (wcscmp(l->dev[i].path, path) == 0) - return true; - } - return false; -} - -/* Scan one GUID and append found devices to l->dev[] starting at offset. - * Returns the number of devices appended, or -1 on a hard error (l->last_error set). */ -static int candle_scan_guid(candle_list_t *l, const wchar_t *guid_str, unsigned offset) -{ - GUID guid; - if (CLSIDFromString(guid_str, &guid) != NOERROR) { - l->last_error = CANDLE_ERR_CLSID; - return -1; - } - - HDEVINFO hdi = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); - if (hdi == INVALID_HANDLE_VALUE) { - /* No devices with this GUID present — not a hard error. */ - return 0; - } - - int found = 0; - for (unsigned i = 0; (offset + i) < CANDLE_MAX_DEVICES; i++) { - SP_DEVICE_INTERFACE_DATA interfaceData; - interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); - - if (!SetupDiEnumDeviceInterfaces(hdi, NULL, &guid, i, &interfaceData)) { - if (GetLastError() != ERROR_NO_MORE_ITEMS) { - l->last_error = CANDLE_ERR_SETUPDI_IF_ENUM; - found = -1; - } - break; - } - - if (!candle_read_di(hdi, interfaceData, &l->dev[offset + i])) { - l->last_error = l->dev[offset + i].last_error; - found = -1; - break; - } - found++; - } - - SetupDiDestroyDeviceInfoList(hdi); - return found; -} - -/* Scan for WinUSB devices matching vid:pid whose device interface GUID was not - * covered by the GUID list above. For each matching USB device instance the - * function reads DeviceInterfaceGUIDs (or DeviceInterfaceGUID) from the Windows - * registry, re-uses candle_scan_guid() for each GUID found there, and appends - * only those devices that are not already present in l->dev[0..existing-1]. - * Returns the number of new devices added. */ -static int candle_scan_vidpid(candle_list_t *l, uint16_t vid, uint16_t pid, unsigned existing) -{ - wchar_t hwid_prefix[32]; - _snwprintf(hwid_prefix, 32, L"USB\\VID_%04X&PID_%04X", vid, pid); - - /* Enumerate USB device instances (not interfaces) so we can read hardware IDs. */ - HDEVINFO hdi = SetupDiGetClassDevs(NULL, L"USB", NULL, - DIGCF_ALLCLASSES | DIGCF_PRESENT); - if (hdi == INVALID_HANDLE_VALUE) { - return 0; - } - - int added = 0; - SP_DEVINFO_DATA devInfo; - devInfo.cbSize = sizeof(SP_DEVINFO_DATA); - - for (DWORD i = 0; - SetupDiEnumDeviceInfo(hdi, i, &devInfo) && existing + added < CANDLE_MAX_DEVICES; - i++) - { - - /* Hardware IDs are a REG_MULTI_SZ — check each string for our VID/PID prefix. */ - wchar_t hwids[512]; - memset(hwids, 0, sizeof(hwids)); - if (!SetupDiGetDeviceRegistryPropertyW(hdi, &devInfo, SPDRP_HARDWAREID, - NULL, (PBYTE)hwids, sizeof(hwids) - sizeof(wchar_t), NULL)) { - continue; - } - - bool matches = false; - const wchar_t *p; - for (p = hwids; *p; p += wcslen(p) + 1) { - if (_wcsnicmp(p, hwid_prefix, wcslen(hwid_prefix)) == 0) { - matches = true; - break; - } - } - if (!matches) { - continue; - } - - /* Open the device's software registry key (Device Parameters) and read - * the WinUSB device interface GUID(s) stored by the driver INF. */ - HKEY hKey = SetupDiOpenDevRegKey(hdi, &devInfo, DICS_FLAG_GLOBAL, 0, - DIREG_DEV, KEY_READ); - if (hKey == INVALID_HANDLE_VALUE) { - continue; - } - - wchar_t guid_buf[256]; - memset(guid_buf, 0, sizeof(guid_buf)); - DWORD buf_len = sizeof(guid_buf) - sizeof(wchar_t); - - /* Prefer DeviceInterfaceGUIDs (REG_MULTI_SZ, modern INFs); fall back to - * DeviceInterfaceGUID (REG_SZ, older/zadig-generated INFs). */ - LONG reg_rc = RegQueryValueExW(hKey, L"DeviceInterfaceGUIDs", NULL, NULL, - (LPBYTE)guid_buf, &buf_len); - if (reg_rc != ERROR_SUCCESS) { - buf_len = sizeof(guid_buf) - sizeof(wchar_t); - RegQueryValueExW(hKey, L"DeviceInterfaceGUID", NULL, NULL, - (LPBYTE)guid_buf, &buf_len); - } - RegCloseKey(hKey); - - if (!guid_buf[0]) { - continue; - } - - /* Iterate GUID strings. Both REG_SZ and REG_MULTI_SZ are covered by the - * same NUL-terminated-string walk (REG_SZ just has one entry). */ - const wchar_t *g; - for (g = guid_buf; - *g && existing + added < CANDLE_MAX_DEVICES; - g += wcslen(g) + 1) - { - unsigned base = existing + added; - int n = candle_scan_guid(l, g, base); - if (n <= 0) { - continue; - } - - /* Remove any entries whose path was already found by the GUID scan. */ - for (int ni = 0; ni < n; ) { - if (candle_path_exists(l, base, l->dev[base + ni].path)) { - memmove(&l->dev[base + ni], &l->dev[base + ni + 1], - (unsigned)(n - ni - 1) * sizeof(candle_device_t)); - n--; - } else { - ni++; - } - } - added += n; - } - } - - SetupDiDestroyDeviceInfoList(hdi); - return added; -} - -bool __stdcall candle_list_scan(candle_list_handle *list) -{ - if (list == NULL) { - return false; - } - - candle_list_t *l = (candle_list_t *)calloc(1, sizeof(candle_list_t)); - *list = l; - if (l == NULL) { - return false; - } - - /* GUIDs for gs_usb-compatible devices on Windows. - * candleLight / CANable / most gs_usb devices: */ - static const wchar_t *GUIDS[] = { - L"{c15b4308-04d3-11e6-b3ea-6057189e6443}" /* candleLight / CANable / gs_usb standard */ - }; - static const unsigned NUM_GUIDS = sizeof(GUIDS) / sizeof(GUIDS[0]); - - unsigned total = 0; - for (unsigned g = 0; g < NUM_GUIDS; g++) { - int n = candle_scan_guid(l, GUIDS[g], total); - if (n < 0) { - return false; - } - total += (unsigned)n; - } - - /* VID/PID scan for devices whose device interface GUID is not in the list - * above (e.g. CANnectivity which uses its own registered interface GUID). */ - static const struct { uint16_t vid; uint16_t pid; } VIDPIDS[] = { - { 0x1209, 0xCA01 }, /* CANnectivity (electronut-labs) */ - }; - static const unsigned NUM_VIDPIDS = sizeof(VIDPIDS) / sizeof(VIDPIDS[0]); - - for (unsigned v = 0; v < NUM_VIDPIDS && total < CANDLE_MAX_DEVICES; v++) { - int n = candle_scan_vidpid(l, VIDPIDS[v].vid, VIDPIDS[v].pid, total); - if (n > 0) - total += (unsigned)n; - } - - l->num_devices = (uint8_t)total; - l->last_error = CANDLE_ERR_OK; - return true; -} - -bool __stdcall DLL candle_list_free(candle_list_handle list) -{ - free(list); - return true; -} - -bool __stdcall DLL candle_list_length(candle_list_handle list, uint8_t *len) -{ - candle_list_t *l = (candle_list_t *)list; - *len = l->num_devices; - return true; -} - -bool __stdcall DLL candle_dev_get(candle_list_handle list, uint8_t dev_num, candle_handle *hdev) -{ - candle_list_t *l = (candle_list_t *)list; - if (l==NULL) { - return false; - } - - if (dev_num >= CANDLE_MAX_DEVICES) { - l->last_error = CANDLE_ERR_DEV_OUT_OF_RANGE; - return false; - } - - candle_device_t *dev = calloc(1, sizeof(candle_device_t)); - *hdev = dev; - if (dev==NULL) { - l->last_error = CANDLE_ERR_MALLOC; - return false; - } - - memcpy(dev, &l->dev[dev_num], sizeof(candle_device_t)); - l->last_error = CANDLE_ERR_OK; - dev->last_error = CANDLE_ERR_OK; - return true; -} - - -bool __stdcall DLL candle_dev_get_state(candle_handle hdev, candle_devstate_t *state) -{ - if (hdev==NULL) { - return false; - } else { - candle_device_t *dev = (candle_device_t*)hdev; - *state = dev->state; - return true; - } -} - -wchar_t __stdcall DLL *candle_dev_get_path(candle_handle hdev) -{ - if (hdev==NULL) { - return NULL; - } else { - candle_device_t *dev = (candle_device_t*)hdev; - return dev->path; - } -} - -static bool candle_dev_interal_open(candle_handle hdev) -{ - candle_device_t *dev = (candle_device_t*)hdev; - - memset(dev->rxevents, 0, sizeof(dev->rxevents)); - memset(dev->rxurbs, 0, sizeof(dev->rxurbs)); - - dev->deviceHandle = CreateFile( - dev->path, - GENERIC_WRITE | GENERIC_READ, - FILE_SHARE_WRITE | FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, - NULL - ); - - if (dev->deviceHandle == INVALID_HANDLE_VALUE) { - dev->last_error = CANDLE_ERR_CREATE_FILE; - return false; - } - - if (!WinUsb_Initialize(dev->deviceHandle, &dev->winUSBHandle)) { - dev->last_error = CANDLE_ERR_WINUSB_INITIALIZE; - goto close_handle; - } - - USB_INTERFACE_DESCRIPTOR ifaceDescriptor; - if (!WinUsb_QueryInterfaceSettings(dev->winUSBHandle, 0, &ifaceDescriptor)) { - dev->last_error = CANDLE_ERR_QUERY_INTERFACE; - goto winusb_free; - } - - dev->interfaceNumber = ifaceDescriptor.bInterfaceNumber; - bool has_in = false, has_out = false; - - candle_logf(L"open path=%ls interface=%u endpoints=%u", - dev->path, - dev->interfaceNumber, - ifaceDescriptor.bNumEndpoints); - - for (uint8_t i=0; iwinUSBHandle, 0, i, &pipeInfo)) { - dev->last_error = CANDLE_ERR_QUERY_PIPE; - goto winusb_free; - } - - if (pipeInfo.PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_IN(pipeInfo.PipeId)) { - if (!has_in) { - dev->bulkInPipe = pipeInfo.PipeId; - has_in = true; - candle_logf(L"selected bulk IN pipe=0x%02x maxPacket=%u interval=%u", - pipeInfo.PipeId, - pipeInfo.MaximumPacketSize, - pipeInfo.Interval); - } - } else if (pipeInfo.PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_OUT(pipeInfo.PipeId)) { - if (!has_out) { - dev->bulkOutPipe = pipeInfo.PipeId; - has_out = true; - candle_logf(L"selected bulk OUT pipe=0x%02x maxPacket=%u interval=%u", - pipeInfo.PipeId, - pipeInfo.MaximumPacketSize, - pipeInfo.Interval); - } - } - - } - - if (!has_in || !has_out) { - dev->last_error = CANDLE_ERR_PARSE_IF_DESCR; - goto winusb_free; - } - - char use_raw_io = 1; - if (!WinUsb_SetPipePolicy(dev->winUSBHandle, dev->bulkInPipe, RAW_IO, sizeof(use_raw_io), &use_raw_io)) { - dev->last_error = CANDLE_ERR_SET_PIPE_RAW_IO; - goto winusb_free; - } - - if (!candle_ctrl_set_host_format(dev)) { - goto winusb_free; - } - - if (!candle_ctrl_get_config(dev, &dev->dconf)) { - goto winusb_free; - } - candle_logf(L"device config channels=%u sw=0x%08x hw=0x%08x", - dev->dconf.icount + 1, - dev->dconf.sw_version, - dev->dconf.hw_version); - - if (!candle_ctrl_get_capability(dev, 0, &dev->bt_const)) { - dev->last_error = CANDLE_ERR_GET_BITTIMING_CONST; - goto winusb_free; - } - candle_logf(L"cap ch0 feature=0x%08x fclk=%u tseg1=%u..%u tseg2=%u..%u sjw=%u brp=%u..%u inc=%u", - dev->bt_const.feature, - dev->bt_const.fclk_can, - dev->bt_const.tseg1_min, - dev->bt_const.tseg1_max, - dev->bt_const.tseg2_min, - dev->bt_const.tseg2_max, - dev->bt_const.sjw_max, - dev->bt_const.brp_min, - dev->bt_const.brp_max, - dev->bt_const.brp_inc); - - /* Query capabilities for each channel on multi-channel devices */ - uint8_t num_channels = dev->dconf.icount + 1; - if (num_channels > 8) num_channels = 8; - for (uint8_t ch = 0; ch < num_channels; ch++) { - if (!candle_ctrl_get_capability(dev, ch, &dev->ch_caps[ch])) { - /* Fall back to channel 0 capabilities for this channel */ - memcpy(&dev->ch_caps[ch], &dev->bt_const, sizeof(candle_capability_t)); - candle_logf(L"cap ch%u failed, falling back to ch0", ch); - } else { - candle_logf(L"cap ch%u feature=0x%08x fclk=%u", - ch, - dev->ch_caps[ch].feature, - dev->ch_caps[ch].fclk_can); - } - } - - /* Pre-allocate a manual-reset event for timed overlapped writes. Reusing - * one event per device (writes are serialised by writeMutex) avoids - * per-frame CreateEvent overhead at high CAN frame rates. */ - dev->txEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - if (!dev->txEvent) { - dev->last_error = CANDLE_ERR_MALLOC; - goto winusb_free; - } - - dev->last_error = CANDLE_ERR_OK; - return true; - -winusb_free: - WinUsb_Free(dev->winUSBHandle); - dev->winUSBHandle = NULL; - -close_handle: - CloseHandle(dev->deviceHandle); - dev->deviceHandle = NULL; - return false; - -} - -static bool candle_prepare_read(candle_device_t *dev, unsigned urb_num) -{ - if (dev->rxurbs[urb_num].pending) { - dev->last_error = CANDLE_ERR_PREPARE_READ; - return false; - } - - if (dev->rxurbs[urb_num].ovl.hEvent == NULL) { - dev->last_error = CANDLE_ERR_PREPARE_READ; - return false; - } - - ResetEvent(dev->rxurbs[urb_num].ovl.hEvent); - - BOOL rc = WinUsb_ReadPipe( - dev->winUSBHandle, - dev->bulkInPipe, - dev->rxurbs[urb_num].buf, - sizeof(dev->rxurbs[urb_num].buf), - NULL, - &dev->rxurbs[urb_num].ovl - ); - - if (rc) { - /* Synchronous completion: data is already in buf and the event is - * signaled. WaitForMultipleObjects will return immediately on the - * next call and GetOverlappedResult will succeed, so this is fine. */ - dev->rxurbs[urb_num].pending = true; - dev->last_error = CANDLE_ERR_OK; - return true; - } - - DWORD err = GetLastError(); - if (err == ERROR_IO_PENDING) { - dev->rxurbs[urb_num].pending = true; - dev->last_error = CANDLE_ERR_OK; - return true; - } - - candle_logf(L"prepare read urb=%u failed winerr=%lu", urb_num, err); - dev->last_error = CANDLE_ERR_PREPARE_READ; - return false; -} - -static bool candle_close_rxurbs(candle_device_t *dev) -{ - if (dev->winUSBHandle != NULL) { - WinUsb_AbortPipe(dev->winUSBHandle, dev->bulkInPipe); - } - - for (unsigned i=0; irxurbs[i].pending) { - CancelIoEx(dev->deviceHandle, &dev->rxurbs[i].ovl); - - DWORD bytes_transfered; - WinUsb_GetOverlappedResult(dev->winUSBHandle, - &dev->rxurbs[i].ovl, - &bytes_transfered, - TRUE); - dev->rxurbs[i].pending = false; - } - - if (dev->rxevents[i] != NULL) { - CloseHandle(dev->rxevents[i]); - dev->rxevents[i] = NULL; - memset(&dev->rxurbs[i].ovl, 0, sizeof(dev->rxurbs[i].ovl)); - } - } - return true; -} - -static void candle_release_open_handles(candle_device_t *dev) -{ - candle_close_rxurbs(dev); - - if (dev->txEvent) { - CloseHandle(dev->txEvent); - dev->txEvent = NULL; - } - - if (dev->winUSBHandle) { - WinUsb_Free(dev->winUSBHandle); - dev->winUSBHandle = NULL; - } - - if (dev->deviceHandle && dev->deviceHandle != INVALID_HANDLE_VALUE) { - CloseHandle(dev->deviceHandle); - dev->deviceHandle = NULL; - } -} - - -bool __stdcall DLL candle_dev_open(candle_handle hdev) -{ - candle_device_t *dev = (candle_device_t*)hdev; - - if (candle_dev_interal_open(dev)) { - for (unsigned i=0; ilast_error = CANDLE_ERR_MALLOC; - candle_err_t last_error = dev->last_error; - candle_release_open_handles(dev); - dev->last_error = last_error; - return false; - } - dev->rxevents[i] = ev; - dev->rxurbs[i].ovl.hEvent = ev; - if (!candle_prepare_read(dev, i)) { - candle_err_t last_error = dev->last_error; - candle_release_open_handles(dev); - dev->last_error = last_error; - return false; // keep last_error from prepare_read call - } - } - dev->last_error = CANDLE_ERR_OK; - return true; - } else { - return false; // keep last_error from open_device call - } - -} - -bool __stdcall DLL candle_dev_get_timestamp_us(candle_handle hdev, uint32_t *timestamp_us) -{ - return candle_ctrl_get_timestamp(hdev, timestamp_us); -} - -bool __stdcall DLL candle_dev_close(candle_handle hdev) -{ - candle_device_t *dev = (candle_device_t*)hdev; - - candle_release_open_handles(dev); - - dev->last_error = CANDLE_ERR_OK; - return true; -} - -bool __stdcall DLL candle_dev_free(candle_handle hdev) -{ - free(hdev); - return true; -} - -candle_err_t __stdcall DLL candle_dev_last_error(candle_handle hdev) -{ - candle_device_t *dev = (candle_device_t*)hdev; - return dev->last_error; -} - -bool __stdcall DLL candle_channel_count(candle_handle hdev, uint8_t *num_channels) -{ - // TODO check if info was already read from device; try to do so; throw error... - candle_device_t *dev = (candle_device_t*)hdev; - *num_channels = dev->dconf.icount+1; - return true; -} - -bool __stdcall DLL candle_channel_get_capabilities(candle_handle hdev, uint8_t ch, candle_capability_t *cap) -{ - candle_device_t *dev = (candle_device_t*)hdev; - uint8_t num_channels = dev->dconf.icount + 1; - if (ch < num_channels && ch < 8) { - memcpy(cap, &dev->ch_caps[ch], sizeof(candle_capability_t)); - } else { - memcpy(cap, &dev->bt_const, sizeof(candle_capability_t)); - } - return true; -} - -bool __stdcall DLL candle_channel_get_state(candle_handle hdev, uint8_t ch, candle_can_state_t *state) -{ - candle_device_t *dev = (candle_device_t*)hdev; - candle_device_state_t ds; - if (!candle_ctrl_get_state(dev, ch, &ds)) { - return false; - } - *state = (candle_can_state_t)ds.state; - return true; -} - -bool __stdcall DLL candle_channel_set_timing(candle_handle hdev, uint8_t ch, candle_bittiming_t *data) -{ - // TODO ensure device is open, check channel count.. - candle_device_t *dev = (candle_device_t*)hdev; - return candle_ctrl_set_bittiming(dev, ch, data); -} - -bool __stdcall DLL candle_channel_set_bitrate(candle_handle hdev, uint8_t ch, uint32_t bitrate) -{ - // TODO ensure device is open, check channel count.. - candle_device_t *dev = (candle_device_t*)hdev; - - if (dev->bt_const.fclk_can != 48000000) { - /* this function only works for the candleLight base clock of 48MHz */ - dev->last_error = CANDLE_ERR_BITRATE_FCLK; - return false; - } - - candle_bittiming_t t; - t.prop_seg = 1; - t.sjw = 1; - t.phase_seg1 = 13 - t.prop_seg; - t.phase_seg2 = 2; - - switch (bitrate) { - case 10000: - t.brp = 300; - break; - - case 20000: - t.brp = 150; - break; - - case 50000: - t.brp = 60; - break; - - case 83333: - t.brp = 36; - break; - - case 100000: - t.brp = 30; - break; - - case 125000: - t.brp = 24; - break; - - case 250000: - t.brp = 12; - break; - - case 500000: - t.brp = 6; - break; - - case 800000: - t.brp = 4; - t.phase_seg1 = 12 - t.prop_seg; - t.phase_seg2 = 2; - break; - - case 1000000: - t.brp = 3; - break; - - default: - dev->last_error = CANDLE_ERR_BITRATE_UNSUPPORTED; - return false; - } - - return candle_ctrl_set_bittiming(dev, ch, &t); -} - -bool __stdcall DLL candle_channel_start(candle_handle hdev, uint8_t ch, uint32_t flags) -{ - // TODO ensure device is open, check channel count.. - candle_device_t *dev = (candle_device_t*)hdev; - candle_capability_t *cap = (ch < 8) ? &dev->ch_caps[ch] : &dev->bt_const; - - if (cap->feature & CANDLE_FEATURE_HW_TIMESTAMP) { - flags |= CANDLE_MODE_HW_TIMESTAMP; - } else { - candle_logf(L"channel %u has no HW timestamp capability; starting without timestamp flag", ch); - } - - bool rc = candle_ctrl_set_device_mode(dev, ch, CANDLE_DEVMODE_START, flags); - candle_logf(L"channel %u start flags=0x%08x result=%u err=%u", - ch, - flags, - rc ? 1 : 0, - dev->last_error); - return rc; -} - -bool __stdcall DLL candle_channel_stop(candle_handle hdev, uint8_t ch) -{ - // TODO ensure device is open, check channel count.. - candle_device_t *dev = (candle_device_t*)hdev; - return candle_ctrl_set_device_mode(dev, ch, CANDLE_DEVMODE_RESET, 0); -} - -/* Write len bytes from buf to the OUT pipe, aborting after 300 ms. - * Writes are serialised by writeMutex in CandleApiInterface so dev->txEvent - * is never accessed by two threads simultaneously. */ -static bool candle_write_pipe_timed(candle_device_t *dev, uint8_t *buf, DWORD len) -{ - OVERLAPPED ovl; - memset(&ovl, 0, sizeof(ovl)); - ovl.hEvent = dev->txEvent; - ResetEvent(dev->txEvent); - - BOOL rc = WinUsb_WritePipe(dev->winUSBHandle, dev->bulkOutPipe, - buf, len, NULL, &ovl); - if (rc) { - return true; /* completed synchronously */ - } - if (GetLastError() != ERROR_IO_PENDING) { - return false; /* hard error */ - } - - if (WaitForSingleObject(dev->txEvent, 150) != WAIT_OBJECT_0) { - /* Timed out: cancel the transfer and restore the pipe to a clean state. */ - WinUsb_AbortPipe(dev->winUSBHandle, dev->bulkOutPipe); - DWORD dummy = 0; - WinUsb_GetOverlappedResult(dev->winUSBHandle, &ovl, &dummy, TRUE); - WinUsb_ResetPipe(dev->winUSBHandle, dev->bulkOutPipe); - return false; - } - - DWORD transferred = 0; - return WinUsb_GetOverlappedResult(dev->winUSBHandle, &ovl, &transferred, FALSE) != FALSE; -} - -bool __stdcall DLL candle_frame_send(candle_handle hdev, uint8_t ch, candle_frame_t *frame) -{ - candle_device_t *dev = (candle_device_t*)hdev; - frame->echo_id = 0; - frame->channel = ch; - bool rc = candle_write_pipe_timed(dev, (uint8_t*)frame, sizeof(*frame)); - dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_SEND_FRAME; - return rc; -} - -bool __stdcall DLL candle_frame_read(candle_handle hdev, candle_frame_t *frame, uint32_t timeout_ms) -{ - // TODO ensure device is open.. - candle_device_t *dev = (candle_device_t*)hdev; - - DWORD wait_result = WaitForMultipleObjects(CANDLE_URB_COUNT, dev->rxevents, false, timeout_ms); - if (wait_result == WAIT_TIMEOUT) { - dev->last_error = CANDLE_ERR_READ_TIMEOUT; - return false; - } - - if ( (wait_result < WAIT_OBJECT_0) || (wait_result >= WAIT_OBJECT_0 + CANDLE_URB_COUNT) ) { - dev->last_error = CANDLE_ERR_READ_WAIT; - return false; - } - - DWORD urb_num = wait_result - WAIT_OBJECT_0; - DWORD bytes_transfered; - - if (!WinUsb_GetOverlappedResult(dev->winUSBHandle, &dev->rxurbs[urb_num].ovl, &bytes_transfered, false)) { - DWORD err = GetLastError(); - if (err == ERROR_IO_INCOMPLETE) { - ResetEvent(dev->rxurbs[urb_num].ovl.hEvent); - } else { - dev->rxurbs[urb_num].pending = false; - candle_prepare_read(dev, urb_num); - } - candle_logf(L"classic read result failed urb=%u winerr=%lu", urb_num, err); - dev->last_error = CANDLE_ERR_READ_RESULT; - return false; - } - dev->rxurbs[urb_num].pending = false; - - if (bytes_transfered < sizeof(*frame)-4) { - candle_prepare_read(dev, urb_num); - candle_logf(L"classic read too small urb=%u bytes=%lu min=%u", - urb_num, - bytes_transfered, - (unsigned)(sizeof(*frame) - 4)); - dev->last_error = CANDLE_ERR_READ_SIZE; - return false; - } - - memset(frame, 0, sizeof(*frame)); - DWORD copy_len = (bytes_transfered < sizeof(*frame)) ? bytes_transfered : sizeof(*frame); - memcpy(frame, dev->rxurbs[urb_num].buf, copy_len); - candle_logf_verbose(L"classic read urb=%u bytes=%lu echo=0x%08x can_id=0x%08x dlc=%u ch=%u flags=0x%02x ts=%u", - urb_num, - bytes_transfered, - frame->echo_id, - frame->can_id, - frame->can_dlc, - frame->channel, - frame->flags, - frame->timestamp_us); - - return candle_prepare_read(dev, urb_num); -} - -candle_frametype_t __stdcall DLL candle_frame_type(candle_frame_t *frame) -{ - if (frame->echo_id != 0xFFFFFFFF) { - return CANDLE_FRAMETYPE_ECHO; - }; - - if (frame->can_id & CANDLE_ID_ERR) { - return CANDLE_FRAMETYPE_ERROR; - } - - return CANDLE_FRAMETYPE_RECEIVE; -} - -uint32_t __stdcall DLL candle_frame_id(candle_frame_t *frame) -{ - return frame->can_id & 0x1FFFFFFF; -} - -bool __stdcall DLL candle_frame_is_extended_id(candle_frame_t *frame) -{ - return (frame->can_id & CANDLE_ID_EXTENDED) != 0; -} - -bool __stdcall DLL candle_frame_is_rtr(candle_frame_t *frame) -{ - return (frame->can_id & CANDLE_ID_RTR) != 0; -} - -uint8_t __stdcall DLL candle_frame_dlc(candle_frame_t *frame) -{ - return frame->can_dlc; -} - -uint8_t __stdcall DLL *candle_frame_data(candle_frame_t *frame) -{ - return frame->data; -} - -uint32_t __stdcall DLL candle_frame_timestamp_us(candle_frame_t *frame) -{ - return frame->timestamp_us; -} - -/* ---- CAN FD extensions ---- */ - -bool __stdcall DLL candle_channel_set_data_timing(candle_handle hdev, uint8_t ch, candle_bittiming_t *data) -{ - candle_device_t *dev = (candle_device_t*)hdev; - return candle_ctrl_set_data_bittiming(dev, ch, data); -} - -bool __stdcall DLL candle_fd_frame_send(candle_handle hdev, uint8_t ch, candle_fd_frame_t *frame) -{ - candle_device_t *dev = (candle_device_t*)hdev; - frame->echo_id = 0; - frame->channel = ch; - bool rc = candle_write_pipe_timed(dev, (uint8_t*)frame, sizeof(*frame)); - dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_SEND_FRAME; - return rc; -} - -bool __stdcall DLL candle_fd_frame_read(candle_handle hdev, candle_fd_frame_t *frame, uint32_t timeout_ms) -{ - candle_device_t *dev = (candle_device_t*)hdev; - - DWORD wait_result = WaitForMultipleObjects(CANDLE_URB_COUNT, dev->rxevents, false, timeout_ms); - if (wait_result == WAIT_TIMEOUT) { - dev->last_error = CANDLE_ERR_READ_TIMEOUT; - return false; - } - - if ( (wait_result < WAIT_OBJECT_0) || (wait_result >= WAIT_OBJECT_0 + CANDLE_URB_COUNT) ) { - dev->last_error = CANDLE_ERR_READ_WAIT; - return false; - } - - DWORD urb_num = wait_result - WAIT_OBJECT_0; - DWORD bytes_transfered; - - if (!WinUsb_GetOverlappedResult(dev->winUSBHandle, &dev->rxurbs[urb_num].ovl, &bytes_transfered, false)) { - DWORD err = GetLastError(); - if (err == ERROR_IO_INCOMPLETE) { - ResetEvent(dev->rxurbs[urb_num].ovl.hEvent); - } else { - dev->rxurbs[urb_num].pending = false; - candle_prepare_read(dev, urb_num); - } - candle_logf(L"fd read result failed urb=%u winerr=%lu", urb_num, err); - dev->last_error = CANDLE_ERR_READ_RESULT; - return false; - } - dev->rxurbs[urb_num].pending = false; - - /* Minimum: classic CAN header (12 bytes) + at least 8 data bytes = 20 bytes */ - static const DWORD classic_min = sizeof(candle_frame_t) - 4; - if (bytes_transfered < classic_min) { - candle_prepare_read(dev, urb_num); - candle_logf(L"fd read too small urb=%u bytes=%lu min=%lu", - urb_num, - bytes_transfered, - classic_min); - dev->last_error = CANDLE_ERR_READ_SIZE; - return false; - } - - memset(frame, 0, sizeof(*frame)); - - /* - * Detect frame type from the flags byte (offset 10 in both structs). - * Classic CAN frames: header(12) + data(8) + timestamp(4) = 24 bytes total. - * - * FD frames come in two wire formats: - * - Legacy fixed (candleLight/CANable 1.x): always 80 bytes — header(12) + - * data[64] + timestamp(4). The timestamp is ALWAYS at offset 76, regardless - * of the actual DLC. Identified by bytes_transferred == sizeof(candle_fd_frame_t). - * - Variable-length (CANnectivity/Zephyr): header(12) + actual_data(DLC) + - * timestamp(4). Identified by bytes_transferred < sizeof(candle_fd_frame_t). - */ - bool is_fd_frame = (dev->rxurbs[urb_num].buf[10] & CANDLE_FRAME_FLAG_FD) != 0; - - if (is_fd_frame) { - /* can_dlc is at byte offset 8 in both classic and FD wire frames. */ - const uint8_t raw_dlc = dev->rxurbs[urb_num].buf[8]; - const DWORD data_len = candle_dlc_to_len(raw_dlc); - const DWORD min_size = 12 + data_len; /* header + data, without timestamp */ - - if (bytes_transfered < min_size) { - candle_prepare_read(dev, urb_num); - candle_logf(L"fd read FD frame too small urb=%u bytes=%lu min=%lu flags=0x%02x dlc=%u", - urb_num, - bytes_transfered, - min_size, - dev->rxurbs[urb_num].buf[10], - raw_dlc); - dev->last_error = CANDLE_ERR_READ_SIZE; - return false; - } - - /* Copy the fixed 12-byte header (echo_id … reserved). */ - memcpy(frame, dev->rxurbs[urb_num].buf, 12); - /* Copy data at offset 12 into the struct's data field. */ - memcpy(frame->data, dev->rxurbs[urb_num].buf + 12, data_len); - - /* Timestamp location depends on the wire format (see comment above). */ - const DWORD fixed_ts_offset = (DWORD)(sizeof(candle_fd_frame_t) - sizeof(uint32_t)); /* = 76 */ - const DWORD ts_offset = (bytes_transfered >= (DWORD)sizeof(candle_fd_frame_t)) - ? fixed_ts_offset - : min_size; - if (bytes_transfered >= ts_offset + (DWORD)sizeof(uint32_t)) { - memcpy(&frame->timestamp_us, dev->rxurbs[urb_num].buf + ts_offset, sizeof(uint32_t)); - } - /* else: timestamp stays zero from memset above */ - } else { - /* Classic CAN frame — copy into FD struct, fixing the timestamp position */ - candle_frame_t classic; - DWORD copy_len = (bytes_transfered < sizeof(classic)) ? bytes_transfered : sizeof(classic); - memcpy(&classic, dev->rxurbs[urb_num].buf, copy_len); - - frame->echo_id = classic.echo_id; - frame->can_id = classic.can_id; - frame->can_dlc = classic.can_dlc; - frame->channel = classic.channel; - frame->flags = classic.flags; - frame->reserved = classic.reserved; - memcpy(frame->data, classic.data, 8); - frame->timestamp_us = (bytes_transfered >= sizeof(classic)) ? classic.timestamp_us : 0; - } - candle_logf_verbose(L"fd read urb=%u bytes=%lu is_fd=%u echo=0x%08x can_id=0x%08x dlc=%u ch=%u flags=0x%02x ts=%u", - urb_num, - bytes_transfered, - is_fd_frame ? 1 : 0, - frame->echo_id, - frame->can_id, - frame->can_dlc, - frame->channel, - frame->flags, - frame->timestamp_us); - - return candle_prepare_read(dev, urb_num); -} - -candle_frametype_t __stdcall DLL candle_fd_frame_type(candle_fd_frame_t *frame) -{ - if (frame->echo_id != 0xFFFFFFFF) { - return CANDLE_FRAMETYPE_ECHO; - } - if (frame->can_id & CANDLE_ID_ERR) { - return CANDLE_FRAMETYPE_ERROR; - } - return CANDLE_FRAMETYPE_RECEIVE; -} - -uint32_t __stdcall DLL candle_fd_frame_id(candle_fd_frame_t *frame) -{ - return frame->can_id & 0x1FFFFFFF; -} - -bool __stdcall DLL candle_fd_frame_is_extended_id(candle_fd_frame_t *frame) -{ - return (frame->can_id & CANDLE_ID_EXTENDED) != 0; -} - -bool __stdcall DLL candle_fd_frame_is_rtr(candle_fd_frame_t *frame) -{ - return (frame->can_id & CANDLE_ID_RTR) != 0; -} - -bool __stdcall DLL candle_fd_frame_is_fd(candle_fd_frame_t *frame) -{ - return (frame->flags & CANDLE_FRAME_FLAG_FD) != 0; -} - -bool __stdcall DLL candle_fd_frame_is_brs(candle_fd_frame_t *frame) -{ - return (frame->flags & CANDLE_FRAME_FLAG_BRS) != 0; -} - -uint8_t __stdcall DLL candle_fd_frame_dlc(candle_fd_frame_t *frame) -{ - return frame->can_dlc; -} - -uint8_t __stdcall DLL *candle_fd_frame_data(candle_fd_frame_t *frame) -{ - return frame->data; -} - -uint32_t __stdcall DLL candle_fd_frame_timestamp_us(candle_fd_frame_t *frame) -{ - return frame->timestamp_us; -} +/* + + Copyright (c) 2016 Hubert Denkmair + + This file is part of the candle windows API. + + This library is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . + +*/ + +#include "candle.h" + +#include +#include +#include + +#include "candle_defs.h" +#include "candle_ctrl_req.h" +#include "ch_9.h" + +static bool candle_dev_interal_open(candle_handle hdev); + +candle_log_fn_t candle_log_fn = NULL; +bool candle_log_verbose = false; + +static void candle_logf(const wchar_t *fmt, ...) +{ + if (candle_log_fn == NULL) { + return; + } + + wchar_t buf[512]; + va_list args; + va_start(args, fmt); + HRESULT hr = StringCchVPrintfW(buf, 512, fmt, args); + va_end(args); + + if (SUCCEEDED(hr)) { + candle_log_fn(buf); + } +} + +static void candle_logf_verbose(const wchar_t *fmt, ...) +{ + if (candle_log_fn == NULL || !candle_log_verbose) { + return; + } + + wchar_t buf[512]; + va_list args; + va_start(args, fmt); + HRESULT hr = StringCchVPrintfW(buf, 512, fmt, args); + va_end(args); + + if (SUCCEEDED(hr)) { + candle_log_fn(buf); + } +} + +static bool candle_read_di(HDEVINFO hdi, SP_DEVICE_INTERFACE_DATA interfaceData, candle_device_t *dev) +{ + /* get required length first (this call always fails with an error) */ + ULONG requiredLength=0; + SetupDiGetDeviceInterfaceDetail(hdi, &interfaceData, NULL, 0, &requiredLength, NULL); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + dev->last_error = CANDLE_ERR_SETUPDI_IF_DETAILS; + return false; + } + + PSP_DEVICE_INTERFACE_DETAIL_DATA detail_data = + (PSP_DEVICE_INTERFACE_DETAIL_DATA) LocalAlloc(LMEM_FIXED, requiredLength); + + if (detail_data != NULL) { + detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + } else { + dev->last_error = CANDLE_ERR_MALLOC; + return false; + } + + bool retval = true; + ULONG length = requiredLength; + if (!SetupDiGetDeviceInterfaceDetail(hdi, &interfaceData, detail_data, length, &requiredLength, NULL) ) { + dev->last_error = CANDLE_ERR_SETUPDI_IF_DETAILS2; + retval = false; + } else if (FAILED(StringCchCopy(dev->path, sizeof(dev->path), detail_data->DevicePath))) { + dev->last_error = CANDLE_ERR_PATH_LEN; + retval = false; + } + + LocalFree(detail_data); + + if (!retval) { + return false; + } + + /* try to open to read device infos and see if it is avail */ + if (candle_dev_interal_open(dev)) { + dev->state = CANDLE_DEVSTATE_AVAIL; + candle_dev_close(dev); + } else { + dev->state = CANDLE_DEVSTATE_INUSE; + } + + dev->last_error = CANDLE_ERR_OK; + return true; +} + +/* Return true when path already appears in l->dev[0..count-1]. */ +static bool candle_path_exists(const candle_list_t *l, unsigned count, const wchar_t *path) +{ + for (unsigned i = 0; i < count; i++) { + if (wcscmp(l->dev[i].path, path) == 0) + return true; + } + return false; +} + +/* Scan one GUID and append found devices to l->dev[] starting at offset. + * Returns the number of devices appended, or -1 on a hard error (l->last_error set). */ +static int candle_scan_guid(candle_list_t *l, const wchar_t *guid_str, unsigned offset) +{ + GUID guid; + if (CLSIDFromString(guid_str, &guid) != NOERROR) { + l->last_error = CANDLE_ERR_CLSID; + return -1; + } + + HDEVINFO hdi = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (hdi == INVALID_HANDLE_VALUE) { + /* No devices with this GUID present — not a hard error. */ + return 0; + } + + int found = 0; + for (unsigned i = 0; (offset + i) < CANDLE_MAX_DEVICES; i++) { + SP_DEVICE_INTERFACE_DATA interfaceData; + interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + if (!SetupDiEnumDeviceInterfaces(hdi, NULL, &guid, i, &interfaceData)) { + if (GetLastError() != ERROR_NO_MORE_ITEMS) { + l->last_error = CANDLE_ERR_SETUPDI_IF_ENUM; + found = -1; + } + break; + } + + if (!candle_read_di(hdi, interfaceData, &l->dev[offset + i])) { + l->last_error = l->dev[offset + i].last_error; + found = -1; + break; + } + found++; + } + + SetupDiDestroyDeviceInfoList(hdi); + return found; +} + +/* Scan for WinUSB devices matching vid:pid whose device interface GUID was not + * covered by the GUID list above. For each matching USB device instance the + * function reads DeviceInterfaceGUIDs (or DeviceInterfaceGUID) from the Windows + * registry, re-uses candle_scan_guid() for each GUID found there, and appends + * only those devices that are not already present in l->dev[0..existing-1]. + * Returns the number of new devices added. */ +static int candle_scan_vidpid(candle_list_t *l, uint16_t vid, uint16_t pid, unsigned existing) +{ + wchar_t hwid_prefix[32]; + _snwprintf(hwid_prefix, 32, L"USB\\VID_%04X&PID_%04X", vid, pid); + + /* Enumerate USB device instances (not interfaces) so we can read hardware IDs. */ + HDEVINFO hdi = SetupDiGetClassDevs(NULL, L"USB", NULL, + DIGCF_ALLCLASSES | DIGCF_PRESENT); + if (hdi == INVALID_HANDLE_VALUE) { + return 0; + } + + int added = 0; + SP_DEVINFO_DATA devInfo; + devInfo.cbSize = sizeof(SP_DEVINFO_DATA); + + for (DWORD i = 0; + SetupDiEnumDeviceInfo(hdi, i, &devInfo) && existing + added < CANDLE_MAX_DEVICES; + i++) + { + + /* Hardware IDs are a REG_MULTI_SZ — check each string for our VID/PID prefix. */ + wchar_t hwids[512]; + memset(hwids, 0, sizeof(hwids)); + if (!SetupDiGetDeviceRegistryPropertyW(hdi, &devInfo, SPDRP_HARDWAREID, + NULL, (PBYTE)hwids, sizeof(hwids) - sizeof(wchar_t), NULL)) { + continue; + } + + bool matches = false; + const wchar_t *p; + for (p = hwids; *p; p += wcslen(p) + 1) { + if (_wcsnicmp(p, hwid_prefix, wcslen(hwid_prefix)) == 0) { + matches = true; + break; + } + } + if (!matches) { + continue; + } + + /* Open the device's software registry key (Device Parameters) and read + * the WinUSB device interface GUID(s) stored by the driver INF. */ + HKEY hKey = SetupDiOpenDevRegKey(hdi, &devInfo, DICS_FLAG_GLOBAL, 0, + DIREG_DEV, KEY_READ); + if (hKey == INVALID_HANDLE_VALUE) { + continue; + } + + wchar_t guid_buf[256]; + memset(guid_buf, 0, sizeof(guid_buf)); + DWORD buf_len = sizeof(guid_buf) - sizeof(wchar_t); + + /* Prefer DeviceInterfaceGUIDs (REG_MULTI_SZ, modern INFs); fall back to + * DeviceInterfaceGUID (REG_SZ, older/zadig-generated INFs). */ + LONG reg_rc = RegQueryValueExW(hKey, L"DeviceInterfaceGUIDs", NULL, NULL, + (LPBYTE)guid_buf, &buf_len); + if (reg_rc != ERROR_SUCCESS) { + buf_len = sizeof(guid_buf) - sizeof(wchar_t); + RegQueryValueExW(hKey, L"DeviceInterfaceGUID", NULL, NULL, + (LPBYTE)guid_buf, &buf_len); + } + RegCloseKey(hKey); + + if (!guid_buf[0]) { + continue; + } + + /* Iterate GUID strings. Both REG_SZ and REG_MULTI_SZ are covered by the + * same NUL-terminated-string walk (REG_SZ just has one entry). */ + const wchar_t *g; + for (g = guid_buf; + *g && existing + added < CANDLE_MAX_DEVICES; + g += wcslen(g) + 1) + { + unsigned base = existing + added; + int n = candle_scan_guid(l, g, base); + if (n <= 0) { + continue; + } + + /* Remove any entries whose path was already found by the GUID scan. */ + for (int ni = 0; ni < n; ) { + if (candle_path_exists(l, base, l->dev[base + ni].path)) { + memmove(&l->dev[base + ni], &l->dev[base + ni + 1], + (unsigned)(n - ni - 1) * sizeof(candle_device_t)); + n--; + } else { + ni++; + } + } + added += n; + } + } + + SetupDiDestroyDeviceInfoList(hdi); + return added; +} + +bool __stdcall candle_list_scan(candle_list_handle *list) +{ + if (list == NULL) { + return false; + } + + candle_list_t *l = (candle_list_t *)calloc(1, sizeof(candle_list_t)); + *list = l; + if (l == NULL) { + return false; + } + + /* GUIDs for gs_usb-compatible devices on Windows. + * candleLight / CANable / most gs_usb devices: */ + static const wchar_t *GUIDS[] = { + L"{c15b4308-04d3-11e6-b3ea-6057189e6443}" /* candleLight / CANable / gs_usb standard */ + }; + static const unsigned NUM_GUIDS = sizeof(GUIDS) / sizeof(GUIDS[0]); + + unsigned total = 0; + for (unsigned g = 0; g < NUM_GUIDS; g++) { + int n = candle_scan_guid(l, GUIDS[g], total); + if (n < 0) { + return false; + } + total += (unsigned)n; + } + + /* VID/PID scan for devices whose device interface GUID is not in the list + * above (e.g. CANnectivity which uses its own registered interface GUID). */ + static const struct { uint16_t vid; uint16_t pid; } VIDPIDS[] = { + { 0x1209, 0xCA01 }, /* CANnectivity (electronut-labs) */ + }; + static const unsigned NUM_VIDPIDS = sizeof(VIDPIDS) / sizeof(VIDPIDS[0]); + + for (unsigned v = 0; v < NUM_VIDPIDS && total < CANDLE_MAX_DEVICES; v++) { + int n = candle_scan_vidpid(l, VIDPIDS[v].vid, VIDPIDS[v].pid, total); + if (n > 0) + total += (unsigned)n; + } + + l->num_devices = (uint8_t)total; + l->last_error = CANDLE_ERR_OK; + return true; +} + +bool __stdcall DLL candle_list_free(candle_list_handle list) +{ + free(list); + return true; +} + +bool __stdcall DLL candle_list_length(candle_list_handle list, uint8_t *len) +{ + candle_list_t *l = (candle_list_t *)list; + *len = l->num_devices; + return true; +} + +bool __stdcall DLL candle_dev_get(candle_list_handle list, uint8_t dev_num, candle_handle *hdev) +{ + candle_list_t *l = (candle_list_t *)list; + if (l==NULL) { + return false; + } + + if (dev_num >= CANDLE_MAX_DEVICES) { + l->last_error = CANDLE_ERR_DEV_OUT_OF_RANGE; + return false; + } + + candle_device_t *dev = calloc(1, sizeof(candle_device_t)); + *hdev = dev; + if (dev==NULL) { + l->last_error = CANDLE_ERR_MALLOC; + return false; + } + + memcpy(dev, &l->dev[dev_num], sizeof(candle_device_t)); + l->last_error = CANDLE_ERR_OK; + dev->last_error = CANDLE_ERR_OK; + return true; +} + + +bool __stdcall DLL candle_dev_get_state(candle_handle hdev, candle_devstate_t *state) +{ + if (hdev==NULL) { + return false; + } else { + candle_device_t *dev = (candle_device_t*)hdev; + *state = dev->state; + return true; + } +} + +wchar_t * __stdcall DLL candle_dev_get_path(candle_handle hdev) +{ + if (hdev==NULL) { + return NULL; + } else { + candle_device_t *dev = (candle_device_t*)hdev; + return dev->path; + } +} + +static bool candle_dev_interal_open(candle_handle hdev) +{ + candle_device_t *dev = (candle_device_t*)hdev; + + memset(dev->rxevents, 0, sizeof(dev->rxevents)); + memset(dev->rxurbs, 0, sizeof(dev->rxurbs)); + + dev->deviceHandle = CreateFile( + dev->path, + GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_WRITE | FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, + NULL + ); + + if (dev->deviceHandle == INVALID_HANDLE_VALUE) { + dev->last_error = CANDLE_ERR_CREATE_FILE; + return false; + } + + if (!WinUsb_Initialize(dev->deviceHandle, &dev->winUSBHandle)) { + dev->last_error = CANDLE_ERR_WINUSB_INITIALIZE; + goto close_handle; + } + + USB_INTERFACE_DESCRIPTOR ifaceDescriptor; + if (!WinUsb_QueryInterfaceSettings(dev->winUSBHandle, 0, &ifaceDescriptor)) { + dev->last_error = CANDLE_ERR_QUERY_INTERFACE; + goto winusb_free; + } + + dev->interfaceNumber = ifaceDescriptor.bInterfaceNumber; + bool has_in = false, has_out = false; + + candle_logf(L"open path=%ls interface=%u endpoints=%u", + dev->path, + dev->interfaceNumber, + ifaceDescriptor.bNumEndpoints); + + for (uint8_t i=0; iwinUSBHandle, 0, i, &pipeInfo)) { + dev->last_error = CANDLE_ERR_QUERY_PIPE; + goto winusb_free; + } + + if (pipeInfo.PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_IN(pipeInfo.PipeId)) { + if (!has_in) { + dev->bulkInPipe = pipeInfo.PipeId; + has_in = true; + candle_logf(L"selected bulk IN pipe=0x%02x maxPacket=%u interval=%u", + pipeInfo.PipeId, + pipeInfo.MaximumPacketSize, + pipeInfo.Interval); + } + } else if (pipeInfo.PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_OUT(pipeInfo.PipeId)) { + if (!has_out) { + dev->bulkOutPipe = pipeInfo.PipeId; + has_out = true; + candle_logf(L"selected bulk OUT pipe=0x%02x maxPacket=%u interval=%u", + pipeInfo.PipeId, + pipeInfo.MaximumPacketSize, + pipeInfo.Interval); + } + } + + } + + if (!has_in || !has_out) { + dev->last_error = CANDLE_ERR_PARSE_IF_DESCR; + goto winusb_free; + } + + char use_raw_io = 1; + if (!WinUsb_SetPipePolicy(dev->winUSBHandle, dev->bulkInPipe, RAW_IO, sizeof(use_raw_io), &use_raw_io)) { + dev->last_error = CANDLE_ERR_SET_PIPE_RAW_IO; + goto winusb_free; + } + + if (!candle_ctrl_set_host_format(dev)) { + goto winusb_free; + } + + if (!candle_ctrl_get_config(dev, &dev->dconf)) { + goto winusb_free; + } + candle_logf(L"device config channels=%u sw=0x%08x hw=0x%08x", + dev->dconf.icount + 1, + dev->dconf.sw_version, + dev->dconf.hw_version); + + if (!candle_ctrl_get_capability(dev, 0, &dev->bt_const)) { + dev->last_error = CANDLE_ERR_GET_BITTIMING_CONST; + goto winusb_free; + } + candle_logf(L"cap ch0 feature=0x%08x fclk=%u tseg1=%u..%u tseg2=%u..%u sjw=%u brp=%u..%u inc=%u", + dev->bt_const.feature, + dev->bt_const.fclk_can, + dev->bt_const.tseg1_min, + dev->bt_const.tseg1_max, + dev->bt_const.tseg2_min, + dev->bt_const.tseg2_max, + dev->bt_const.sjw_max, + dev->bt_const.brp_min, + dev->bt_const.brp_max, + dev->bt_const.brp_inc); + + /* Query capabilities for each channel on multi-channel devices */ + uint8_t num_channels = dev->dconf.icount + 1; + if (num_channels > 8) num_channels = 8; + for (uint8_t ch = 0; ch < num_channels; ch++) { + if (!candle_ctrl_get_capability(dev, ch, &dev->ch_caps[ch])) { + /* Fall back to channel 0 capabilities for this channel */ + memcpy(&dev->ch_caps[ch], &dev->bt_const, sizeof(candle_capability_t)); + candle_logf(L"cap ch%u failed, falling back to ch0", ch); + } else { + candle_logf(L"cap ch%u feature=0x%08x fclk=%u", + ch, + dev->ch_caps[ch].feature, + dev->ch_caps[ch].fclk_can); + } + } + + /* Pre-allocate a manual-reset event for timed overlapped writes. Reusing + * one event per device (writes are serialised by writeMutex) avoids + * per-frame CreateEvent overhead at high CAN frame rates. */ + dev->txEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!dev->txEvent) { + dev->last_error = CANDLE_ERR_MALLOC; + goto winusb_free; + } + + dev->last_error = CANDLE_ERR_OK; + return true; + +winusb_free: + WinUsb_Free(dev->winUSBHandle); + dev->winUSBHandle = NULL; + +close_handle: + CloseHandle(dev->deviceHandle); + dev->deviceHandle = NULL; + return false; + +} + +static bool candle_prepare_read(candle_device_t *dev, unsigned urb_num) +{ + if (dev->rxurbs[urb_num].pending) { + dev->last_error = CANDLE_ERR_PREPARE_READ; + return false; + } + + if (dev->rxurbs[urb_num].ovl.hEvent == NULL) { + dev->last_error = CANDLE_ERR_PREPARE_READ; + return false; + } + + ResetEvent(dev->rxurbs[urb_num].ovl.hEvent); + + BOOL rc = WinUsb_ReadPipe( + dev->winUSBHandle, + dev->bulkInPipe, + dev->rxurbs[urb_num].buf, + sizeof(dev->rxurbs[urb_num].buf), + NULL, + &dev->rxurbs[urb_num].ovl + ); + + if (rc) { + /* Synchronous completion: data is already in buf and the event is + * signaled. WaitForMultipleObjects will return immediately on the + * next call and GetOverlappedResult will succeed, so this is fine. */ + dev->rxurbs[urb_num].pending = true; + dev->last_error = CANDLE_ERR_OK; + return true; + } + + DWORD err = GetLastError(); + if (err == ERROR_IO_PENDING) { + dev->rxurbs[urb_num].pending = true; + dev->last_error = CANDLE_ERR_OK; + return true; + } + + candle_logf(L"prepare read urb=%u failed winerr=%lu", urb_num, err); + dev->last_error = CANDLE_ERR_PREPARE_READ; + return false; +} + +static bool candle_close_rxurbs(candle_device_t *dev) +{ + if (dev->winUSBHandle != NULL) { + WinUsb_AbortPipe(dev->winUSBHandle, dev->bulkInPipe); + } + + for (unsigned i=0; irxurbs[i].pending) { + CancelIoEx(dev->deviceHandle, &dev->rxurbs[i].ovl); + + DWORD bytes_transfered; + WinUsb_GetOverlappedResult(dev->winUSBHandle, + &dev->rxurbs[i].ovl, + &bytes_transfered, + TRUE); + dev->rxurbs[i].pending = false; + } + + if (dev->rxevents[i] != NULL) { + CloseHandle(dev->rxevents[i]); + dev->rxevents[i] = NULL; + memset(&dev->rxurbs[i].ovl, 0, sizeof(dev->rxurbs[i].ovl)); + } + } + return true; +} + +static void candle_release_open_handles(candle_device_t *dev) +{ + candle_close_rxurbs(dev); + + if (dev->txEvent) { + CloseHandle(dev->txEvent); + dev->txEvent = NULL; + } + + if (dev->winUSBHandle) { + WinUsb_Free(dev->winUSBHandle); + dev->winUSBHandle = NULL; + } + + if (dev->deviceHandle && dev->deviceHandle != INVALID_HANDLE_VALUE) { + CloseHandle(dev->deviceHandle); + dev->deviceHandle = NULL; + } +} + + +bool __stdcall DLL candle_dev_open(candle_handle hdev) +{ + candle_device_t *dev = (candle_device_t*)hdev; + + if (candle_dev_interal_open(dev)) { + for (unsigned i=0; ilast_error = CANDLE_ERR_MALLOC; + candle_err_t last_error = dev->last_error; + candle_release_open_handles(dev); + dev->last_error = last_error; + return false; + } + dev->rxevents[i] = ev; + dev->rxurbs[i].ovl.hEvent = ev; + if (!candle_prepare_read(dev, i)) { + candle_err_t last_error = dev->last_error; + candle_release_open_handles(dev); + dev->last_error = last_error; + return false; // keep last_error from prepare_read call + } + } + dev->last_error = CANDLE_ERR_OK; + return true; + } else { + return false; // keep last_error from open_device call + } + +} + +bool __stdcall DLL candle_dev_get_timestamp_us(candle_handle hdev, uint32_t *timestamp_us) +{ + return candle_ctrl_get_timestamp(hdev, timestamp_us); +} + +bool __stdcall DLL candle_dev_close(candle_handle hdev) +{ + candle_device_t *dev = (candle_device_t*)hdev; + + candle_release_open_handles(dev); + + dev->last_error = CANDLE_ERR_OK; + return true; +} + +bool __stdcall DLL candle_dev_free(candle_handle hdev) +{ + free(hdev); + return true; +} + +candle_err_t __stdcall DLL candle_dev_last_error(candle_handle hdev) +{ + candle_device_t *dev = (candle_device_t*)hdev; + return dev->last_error; +} + +bool __stdcall DLL candle_channel_count(candle_handle hdev, uint8_t *num_channels) +{ + // TODO check if info was already read from device; try to do so; throw error... + candle_device_t *dev = (candle_device_t*)hdev; + *num_channels = dev->dconf.icount+1; + return true; +} + +bool __stdcall DLL candle_channel_get_capabilities(candle_handle hdev, uint8_t ch, candle_capability_t *cap) +{ + candle_device_t *dev = (candle_device_t*)hdev; + uint8_t num_channels = dev->dconf.icount + 1; + if (ch < num_channels && ch < 8) { + memcpy(cap, &dev->ch_caps[ch], sizeof(candle_capability_t)); + } else { + memcpy(cap, &dev->bt_const, sizeof(candle_capability_t)); + } + return true; +} + +bool __stdcall DLL candle_channel_get_state(candle_handle hdev, uint8_t ch, candle_can_state_t *state) +{ + candle_device_t *dev = (candle_device_t*)hdev; + candle_device_state_t ds; + if (!candle_ctrl_get_state(dev, ch, &ds)) { + return false; + } + *state = (candle_can_state_t)ds.state; + return true; +} + +bool __stdcall DLL candle_channel_set_timing(candle_handle hdev, uint8_t ch, candle_bittiming_t *data) +{ + // TODO ensure device is open, check channel count.. + candle_device_t *dev = (candle_device_t*)hdev; + return candle_ctrl_set_bittiming(dev, ch, data); +} + +bool __stdcall DLL candle_channel_set_bitrate(candle_handle hdev, uint8_t ch, uint32_t bitrate) +{ + // TODO ensure device is open, check channel count.. + candle_device_t *dev = (candle_device_t*)hdev; + + if (dev->bt_const.fclk_can != 48000000) { + /* this function only works for the candleLight base clock of 48MHz */ + dev->last_error = CANDLE_ERR_BITRATE_FCLK; + return false; + } + + candle_bittiming_t t; + t.prop_seg = 1; + t.sjw = 1; + t.phase_seg1 = 13 - t.prop_seg; + t.phase_seg2 = 2; + + switch (bitrate) { + case 10000: + t.brp = 300; + break; + + case 20000: + t.brp = 150; + break; + + case 50000: + t.brp = 60; + break; + + case 83333: + t.brp = 36; + break; + + case 100000: + t.brp = 30; + break; + + case 125000: + t.brp = 24; + break; + + case 250000: + t.brp = 12; + break; + + case 500000: + t.brp = 6; + break; + + case 800000: + t.brp = 4; + t.phase_seg1 = 12 - t.prop_seg; + t.phase_seg2 = 2; + break; + + case 1000000: + t.brp = 3; + break; + + default: + dev->last_error = CANDLE_ERR_BITRATE_UNSUPPORTED; + return false; + } + + return candle_ctrl_set_bittiming(dev, ch, &t); +} + +bool __stdcall DLL candle_channel_start(candle_handle hdev, uint8_t ch, uint32_t flags) +{ + // TODO ensure device is open, check channel count.. + candle_device_t *dev = (candle_device_t*)hdev; + candle_capability_t *cap = (ch < 8) ? &dev->ch_caps[ch] : &dev->bt_const; + + if (cap->feature & CANDLE_FEATURE_HW_TIMESTAMP) { + flags |= CANDLE_MODE_HW_TIMESTAMP; + } else { + candle_logf(L"channel %u has no HW timestamp capability; starting without timestamp flag", ch); + } + + bool rc = candle_ctrl_set_device_mode(dev, ch, CANDLE_DEVMODE_START, flags); + candle_logf(L"channel %u start flags=0x%08x result=%u err=%u", + ch, + flags, + rc ? 1 : 0, + dev->last_error); + return rc; +} + +bool __stdcall DLL candle_channel_stop(candle_handle hdev, uint8_t ch) +{ + // TODO ensure device is open, check channel count.. + candle_device_t *dev = (candle_device_t*)hdev; + return candle_ctrl_set_device_mode(dev, ch, CANDLE_DEVMODE_RESET, 0); +} + +/* Write len bytes from buf to the OUT pipe, aborting after 300 ms. + * Writes are serialised by writeMutex in CandleApiInterface so dev->txEvent + * is never accessed by two threads simultaneously. */ +static bool candle_write_pipe_timed(candle_device_t *dev, uint8_t *buf, DWORD len) +{ + OVERLAPPED ovl; + memset(&ovl, 0, sizeof(ovl)); + ovl.hEvent = dev->txEvent; + ResetEvent(dev->txEvent); + + BOOL rc = WinUsb_WritePipe(dev->winUSBHandle, dev->bulkOutPipe, + buf, len, NULL, &ovl); + if (rc) { + return true; /* completed synchronously */ + } + if (GetLastError() != ERROR_IO_PENDING) { + return false; /* hard error */ + } + + if (WaitForSingleObject(dev->txEvent, 150) != WAIT_OBJECT_0) { + /* Timed out: cancel the transfer and restore the pipe to a clean state. */ + WinUsb_AbortPipe(dev->winUSBHandle, dev->bulkOutPipe); + DWORD dummy = 0; + WinUsb_GetOverlappedResult(dev->winUSBHandle, &ovl, &dummy, TRUE); + WinUsb_ResetPipe(dev->winUSBHandle, dev->bulkOutPipe); + return false; + } + + DWORD transferred = 0; + return WinUsb_GetOverlappedResult(dev->winUSBHandle, &ovl, &transferred, FALSE) != FALSE; +} + +bool __stdcall DLL candle_frame_send(candle_handle hdev, uint8_t ch, candle_frame_t *frame) +{ + candle_device_t *dev = (candle_device_t*)hdev; + frame->echo_id = 0; + frame->channel = ch; + bool rc = candle_write_pipe_timed(dev, (uint8_t*)frame, sizeof(*frame)); + dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_SEND_FRAME; + return rc; +} + +bool __stdcall DLL candle_frame_read(candle_handle hdev, candle_frame_t *frame, uint32_t timeout_ms) +{ + // TODO ensure device is open.. + candle_device_t *dev = (candle_device_t*)hdev; + + DWORD wait_result = WaitForMultipleObjects(CANDLE_URB_COUNT, dev->rxevents, false, timeout_ms); + if (wait_result == WAIT_TIMEOUT) { + dev->last_error = CANDLE_ERR_READ_TIMEOUT; + return false; + } + + if ( (wait_result < WAIT_OBJECT_0) || (wait_result >= WAIT_OBJECT_0 + CANDLE_URB_COUNT) ) { + dev->last_error = CANDLE_ERR_READ_WAIT; + return false; + } + + DWORD urb_num = wait_result - WAIT_OBJECT_0; + DWORD bytes_transfered; + + if (!WinUsb_GetOverlappedResult(dev->winUSBHandle, &dev->rxurbs[urb_num].ovl, &bytes_transfered, false)) { + DWORD err = GetLastError(); + if (err == ERROR_IO_INCOMPLETE) { + ResetEvent(dev->rxurbs[urb_num].ovl.hEvent); + } else { + dev->rxurbs[urb_num].pending = false; + candle_prepare_read(dev, urb_num); + } + candle_logf(L"classic read result failed urb=%u winerr=%lu", urb_num, err); + dev->last_error = CANDLE_ERR_READ_RESULT; + return false; + } + dev->rxurbs[urb_num].pending = false; + + if (bytes_transfered < sizeof(*frame)-4) { + candle_prepare_read(dev, urb_num); + candle_logf(L"classic read too small urb=%u bytes=%lu min=%u", + urb_num, + bytes_transfered, + (unsigned)(sizeof(*frame) - 4)); + dev->last_error = CANDLE_ERR_READ_SIZE; + return false; + } + + memset(frame, 0, sizeof(*frame)); + DWORD copy_len = (bytes_transfered < sizeof(*frame)) ? bytes_transfered : sizeof(*frame); + memcpy(frame, dev->rxurbs[urb_num].buf, copy_len); + candle_logf_verbose(L"classic read urb=%u bytes=%lu echo=0x%08x can_id=0x%08x dlc=%u ch=%u flags=0x%02x ts=%u", + urb_num, + bytes_transfered, + frame->echo_id, + frame->can_id, + frame->can_dlc, + frame->channel, + frame->flags, + frame->timestamp_us); + + return candle_prepare_read(dev, urb_num); +} + +candle_frametype_t __stdcall DLL candle_frame_type(candle_frame_t *frame) +{ + if (frame->echo_id != 0xFFFFFFFF) { + return CANDLE_FRAMETYPE_ECHO; + }; + + if (frame->can_id & CANDLE_ID_ERR) { + return CANDLE_FRAMETYPE_ERROR; + } + + return CANDLE_FRAMETYPE_RECEIVE; +} + +uint32_t __stdcall DLL candle_frame_id(candle_frame_t *frame) +{ + return frame->can_id & 0x1FFFFFFF; +} + +bool __stdcall DLL candle_frame_is_extended_id(candle_frame_t *frame) +{ + return (frame->can_id & CANDLE_ID_EXTENDED) != 0; +} + +bool __stdcall DLL candle_frame_is_rtr(candle_frame_t *frame) +{ + return (frame->can_id & CANDLE_ID_RTR) != 0; +} + +uint8_t __stdcall DLL candle_frame_dlc(candle_frame_t *frame) +{ + return frame->can_dlc; +} + +uint8_t * __stdcall DLL candle_frame_data(candle_frame_t *frame) +{ + return frame->data; +} + +uint32_t __stdcall DLL candle_frame_timestamp_us(candle_frame_t *frame) +{ + return frame->timestamp_us; +} + +/* ---- CAN FD extensions ---- */ + +bool __stdcall DLL candle_channel_set_data_timing(candle_handle hdev, uint8_t ch, candle_bittiming_t *data) +{ + candle_device_t *dev = (candle_device_t*)hdev; + return candle_ctrl_set_data_bittiming(dev, ch, data); +} + +bool __stdcall DLL candle_fd_frame_send(candle_handle hdev, uint8_t ch, candle_fd_frame_t *frame) +{ + candle_device_t *dev = (candle_device_t*)hdev; + frame->echo_id = 0; + frame->channel = ch; + bool rc = candle_write_pipe_timed(dev, (uint8_t*)frame, sizeof(*frame)); + dev->last_error = rc ? CANDLE_ERR_OK : CANDLE_ERR_SEND_FRAME; + return rc; +} + +bool __stdcall DLL candle_fd_frame_read(candle_handle hdev, candle_fd_frame_t *frame, uint32_t timeout_ms) +{ + candle_device_t *dev = (candle_device_t*)hdev; + + DWORD wait_result = WaitForMultipleObjects(CANDLE_URB_COUNT, dev->rxevents, false, timeout_ms); + if (wait_result == WAIT_TIMEOUT) { + dev->last_error = CANDLE_ERR_READ_TIMEOUT; + return false; + } + + if ( (wait_result < WAIT_OBJECT_0) || (wait_result >= WAIT_OBJECT_0 + CANDLE_URB_COUNT) ) { + dev->last_error = CANDLE_ERR_READ_WAIT; + return false; + } + + DWORD urb_num = wait_result - WAIT_OBJECT_0; + DWORD bytes_transfered; + + if (!WinUsb_GetOverlappedResult(dev->winUSBHandle, &dev->rxurbs[urb_num].ovl, &bytes_transfered, false)) { + DWORD err = GetLastError(); + if (err == ERROR_IO_INCOMPLETE) { + ResetEvent(dev->rxurbs[urb_num].ovl.hEvent); + } else { + dev->rxurbs[urb_num].pending = false; + candle_prepare_read(dev, urb_num); + } + candle_logf(L"fd read result failed urb=%u winerr=%lu", urb_num, err); + dev->last_error = CANDLE_ERR_READ_RESULT; + return false; + } + dev->rxurbs[urb_num].pending = false; + + /* Minimum: classic CAN header (12 bytes) + at least 8 data bytes = 20 bytes */ + static const DWORD classic_min = sizeof(candle_frame_t) - 4; + if (bytes_transfered < classic_min) { + candle_prepare_read(dev, urb_num); + candle_logf(L"fd read too small urb=%u bytes=%lu min=%lu", + urb_num, + bytes_transfered, + classic_min); + dev->last_error = CANDLE_ERR_READ_SIZE; + return false; + } + + memset(frame, 0, sizeof(*frame)); + + /* + * Detect frame type from the flags byte (offset 10 in both structs). + * Classic CAN frames: header(12) + data(8) + timestamp(4) = 24 bytes total. + * + * FD frames come in two wire formats: + * - Legacy fixed (candleLight/CANable 1.x): always 80 bytes — header(12) + + * data[64] + timestamp(4). The timestamp is ALWAYS at offset 76, regardless + * of the actual DLC. Identified by bytes_transferred == sizeof(candle_fd_frame_t). + * - Variable-length (CANnectivity/Zephyr): header(12) + actual_data(DLC) + + * timestamp(4). Identified by bytes_transferred < sizeof(candle_fd_frame_t). + */ + bool is_fd_frame = (dev->rxurbs[urb_num].buf[10] & CANDLE_FRAME_FLAG_FD) != 0; + + if (is_fd_frame) { + /* can_dlc is at byte offset 8 in both classic and FD wire frames. */ + const uint8_t raw_dlc = dev->rxurbs[urb_num].buf[8]; + const DWORD data_len = candle_dlc_to_len(raw_dlc); + const DWORD min_size = 12 + data_len; /* header + data, without timestamp */ + + if (bytes_transfered < min_size) { + candle_prepare_read(dev, urb_num); + candle_logf(L"fd read FD frame too small urb=%u bytes=%lu min=%lu flags=0x%02x dlc=%u", + urb_num, + bytes_transfered, + min_size, + dev->rxurbs[urb_num].buf[10], + raw_dlc); + dev->last_error = CANDLE_ERR_READ_SIZE; + return false; + } + + /* Copy the fixed 12-byte header (echo_id … reserved). */ + memcpy(frame, dev->rxurbs[urb_num].buf, 12); + /* Copy data at offset 12 into the struct's data field. */ + memcpy(frame->data, dev->rxurbs[urb_num].buf + 12, data_len); + + /* Timestamp location depends on the wire format (see comment above). */ + const DWORD fixed_ts_offset = (DWORD)(sizeof(candle_fd_frame_t) - sizeof(uint32_t)); /* = 76 */ + const DWORD ts_offset = (bytes_transfered >= (DWORD)sizeof(candle_fd_frame_t)) + ? fixed_ts_offset + : min_size; + if (bytes_transfered >= ts_offset + (DWORD)sizeof(uint32_t)) { + memcpy(&frame->timestamp_us, dev->rxurbs[urb_num].buf + ts_offset, sizeof(uint32_t)); + } + /* else: timestamp stays zero from memset above */ + } else { + /* Classic CAN frame — copy into FD struct, fixing the timestamp position */ + candle_frame_t classic; + DWORD copy_len = (bytes_transfered < sizeof(classic)) ? bytes_transfered : sizeof(classic); + memcpy(&classic, dev->rxurbs[urb_num].buf, copy_len); + + frame->echo_id = classic.echo_id; + frame->can_id = classic.can_id; + frame->can_dlc = classic.can_dlc; + frame->channel = classic.channel; + frame->flags = classic.flags; + frame->reserved = classic.reserved; + memcpy(frame->data, classic.data, 8); + frame->timestamp_us = (bytes_transfered >= sizeof(classic)) ? classic.timestamp_us : 0; + } + candle_logf_verbose(L"fd read urb=%u bytes=%lu is_fd=%u echo=0x%08x can_id=0x%08x dlc=%u ch=%u flags=0x%02x ts=%u", + urb_num, + bytes_transfered, + is_fd_frame ? 1 : 0, + frame->echo_id, + frame->can_id, + frame->can_dlc, + frame->channel, + frame->flags, + frame->timestamp_us); + + return candle_prepare_read(dev, urb_num); +} + +candle_frametype_t __stdcall DLL candle_fd_frame_type(candle_fd_frame_t *frame) +{ + if (frame->echo_id != 0xFFFFFFFF) { + return CANDLE_FRAMETYPE_ECHO; + } + if (frame->can_id & CANDLE_ID_ERR) { + return CANDLE_FRAMETYPE_ERROR; + } + return CANDLE_FRAMETYPE_RECEIVE; +} + +uint32_t __stdcall DLL candle_fd_frame_id(candle_fd_frame_t *frame) +{ + return frame->can_id & 0x1FFFFFFF; +} + +bool __stdcall DLL candle_fd_frame_is_extended_id(candle_fd_frame_t *frame) +{ + return (frame->can_id & CANDLE_ID_EXTENDED) != 0; +} + +bool __stdcall DLL candle_fd_frame_is_rtr(candle_fd_frame_t *frame) +{ + return (frame->can_id & CANDLE_ID_RTR) != 0; +} + +bool __stdcall DLL candle_fd_frame_is_fd(candle_fd_frame_t *frame) +{ + return (frame->flags & CANDLE_FRAME_FLAG_FD) != 0; +} + +bool __stdcall DLL candle_fd_frame_is_brs(candle_fd_frame_t *frame) +{ + return (frame->flags & CANDLE_FRAME_FLAG_BRS) != 0; +} + +uint8_t __stdcall DLL candle_fd_frame_dlc(candle_fd_frame_t *frame) +{ + return frame->can_dlc; +} + +uint8_t * __stdcall DLL candle_fd_frame_data(candle_fd_frame_t *frame) +{ + return frame->data; +} + +uint32_t __stdcall DLL candle_fd_frame_timestamp_us(candle_fd_frame_t *frame) +{ + return frame->timestamp_us; +} diff --git a/connections/candle_api/candle.h b/connections/candle_api/candle.h index 13c129a..5cb7caa 100644 --- a/connections/candle_api/candle.h +++ b/connections/candle_api/candle.h @@ -237,7 +237,7 @@ bool __stdcall DLL candle_list_length(candle_list_handle list, uint8_t *len); bool __stdcall DLL candle_dev_get(candle_list_handle list, uint8_t dev_num, candle_handle *hdev); bool __stdcall DLL candle_dev_get_state(candle_handle hdev, candle_devstate_t *state); -wchar_t __stdcall DLL *candle_dev_get_path(candle_handle hdev); +wchar_t * __stdcall DLL candle_dev_get_path(candle_handle hdev); bool __stdcall DLL candle_dev_open(candle_handle hdev); bool __stdcall DLL candle_dev_get_timestamp_us(candle_handle hdev, uint32_t *timestamp_us); bool __stdcall DLL candle_dev_close(candle_handle hdev); @@ -259,7 +259,7 @@ uint32_t __stdcall DLL candle_frame_id(candle_frame_t *frame); bool __stdcall DLL candle_frame_is_extended_id(candle_frame_t *frame); bool __stdcall DLL candle_frame_is_rtr(candle_frame_t *frame); uint8_t __stdcall DLL candle_frame_dlc(candle_frame_t *frame); -uint8_t __stdcall DLL *candle_frame_data(candle_frame_t *frame); +uint8_t * __stdcall DLL candle_frame_data(candle_frame_t *frame); uint32_t __stdcall DLL candle_frame_timestamp_us(candle_frame_t *frame); /* CAN FD extensions */ @@ -274,7 +274,7 @@ bool __stdcall DLL candle_fd_frame_is_rtr(candle_fd_frame_t *frame); bool __stdcall DLL candle_fd_frame_is_fd(candle_fd_frame_t *frame); bool __stdcall DLL candle_fd_frame_is_brs(candle_fd_frame_t *frame); uint8_t __stdcall DLL candle_fd_frame_dlc(candle_fd_frame_t *frame); -uint8_t __stdcall DLL *candle_fd_frame_data(candle_fd_frame_t *frame); +uint8_t * __stdcall DLL candle_fd_frame_data(candle_fd_frame_t *frame); uint32_t __stdcall DLL candle_fd_frame_timestamp_us(candle_fd_frame_t *frame); candle_err_t __stdcall DLL candle_dev_last_error(candle_handle hdev);