Добавить очереди операций PBUS/SBUS, общую консоль, настройки Candle и SLCAN, декодеры DSView и обработку плотных графиков. Обновить протокол Бальзам и проверки общих компонентов.
1303 lines
60 KiB
C
1303 lines
60 KiB
C
/*
|
||
|
||
Copyright (c) 2016 Hubert Denkmair <hubert@denkmair.de>
|
||
|
||
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 <http://www.gnu.org/licenses/>.
|
||
|
||
*/
|
||
|
||
/*
|
||
* Доступ к CAN-адаптерам candleLight/gs_usb в Windows через WinUSB. Перечисление устройств,
|
||
* открытие канала и обмен кадрами образуют отдельный транспортный слой; он не интерпретирует
|
||
* прикладные регистры SETProtocol.
|
||
*/
|
||
|
||
#include "candle.h"
|
||
|
||
#include <stdarg.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
|
||
#include "candle_defs.h"
|
||
#include "candle_ctrl_req.h"
|
||
#include "ch_9.h"
|
||
#include <cfgmgr32.h> /* Сопоставляет интерфейсы CAN и DFU по общему родительскому USB-устройству Windows. */
|
||
|
||
static DWORD candle_dfu_parent(const wchar_t *path, DEVINST *parent) /* Возвращает родителя интерфейса, чтобы команда не попала в другой подключённый адаптер. */
|
||
{ /* Начало поиска физического USB-устройства по пути выбранного CAN-интерфейса. */
|
||
HDEVINFO devices = SetupDiCreateDeviceInfoList(NULL, NULL); /* Создаёт временный набор SetupAPI без перечисления посторонних устройств. */
|
||
if (devices == INVALID_HANDLE_VALUE) return GetLastError(); /* Сохраняет причину отказа создания набора Windows. */
|
||
SP_DEVICE_INTERFACE_DATA iface = {0}; /* Подготавливает описание интерфейса для открытия по точному пути. */
|
||
iface.cbSize = sizeof(iface); /* Передаёт SetupAPI размер структуры текущей разрядности процесса. */
|
||
SP_DEVINFO_DATA info = {0}; /* Получает идентификатор узла PnP выбранного интерфейса. */
|
||
info.cbSize = sizeof(info); /* Указывает размер структуры устройства согласно контракту SetupAPI. */
|
||
DWORD size = 0, error = ERROR_SUCCESS; /* Хранит требуемое число байтов и итоговый код Windows. */
|
||
if (!SetupDiOpenDeviceInterfaceW(devices, path, 0, &iface)) error = GetLastError(); /* Открывает только заданный путь, сохраняя ошибку исчезнувшего устройства. */
|
||
if (!error) SetupDiGetDeviceInterfaceDetailW(devices, &iface, NULL, 0, &size, NULL); /* Узнаёт объём буфера переменной длины для пути USB-интерфейса. */
|
||
PSP_DEVICE_INTERFACE_DETAIL_DATA_W detail = size ? malloc(size) : NULL; /* Выделяет буфер только после успешного определения его размера. */
|
||
if (!error && !detail) error = size ? ERROR_NOT_ENOUGH_MEMORY : GetLastError(); /* Отличает нехватку памяти от ошибки поиска описания. */
|
||
if (detail) { /* Обрабатывает описание только при наличии выделенного буфера. */
|
||
detail->cbSize = sizeof(*detail); /* Согласует размер заголовка с архитектурой Win32 или Win64. */
|
||
if (!SetupDiGetDeviceInterfaceDetailW(devices, &iface, detail, size, NULL, &info)) error = GetLastError(); /* Получает узел интерфейса из SetupAPI. */
|
||
else if (CM_Get_Parent(parent, info.DevInst, 0) != CR_SUCCESS) error = ERROR_NOT_FOUND; /* Получает общий родительский узел составного USB-адаптера. */
|
||
free(detail); /* Освобождает описание после получения идентификатора родителя. */
|
||
} /* Завершает работу с описанием интерфейса. */
|
||
SetupDiDestroyDeviceInfoList(devices); /* Освобождает набор SetupAPI при любом результате поиска. */
|
||
return error; /* Возвращает ноль при успехе либо причину отказа Windows. */
|
||
} /* Завершает сопоставление интерфейса с физическим USB-устройством. */
|
||
|
||
uint32_t __stdcall candle_dev_enter_dfu(const wchar_t *can_path, uint8_t status[6]) /* Возвращает код Windows и шестибайтовый DFU_GETSTATUS согласно DFU 1.1. */
|
||
{ /* Начало отдельного сеанса управляющего интерфейса обновления. */
|
||
if (!can_path || !status) return ERROR_INVALID_PARAMETER; /* Запрещает отсутствие точного пути адаптера или выходного буфера статуса. */
|
||
memset(status, 0, 6); /* Исключает использование неинициализированных полей ответа при ошибке USB. */
|
||
DEVINST wanted_parent = 0; /* Запоминает физический адаптер, выбранный пользователем до остановки CAN. */
|
||
DWORD error = candle_dfu_parent(can_path, &wanted_parent); /* Ищет родителя по сохранённому пути CAN, не заменяя его первым найденным устройством. */
|
||
if (error) return error; /* Прерывает переход, если выбранный адаптер уже исчез из PnP. */
|
||
GUID guid = {0xc25b4308, 0x04d3, 0x11e6, {0xb3, 0xea, 0x60, 0x57, 0x18, 0x9e, 0x64, 0x43}}; /* GUID интерфейса обновления из дескриптора CANable 2.5. */
|
||
HDEVINFO devices = SetupDiGetClassDevsW(&guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); /* Перечисляет только подключённые интерфейсы обновления CANable 2.5. */
|
||
if (devices == INVALID_HANDLE_VALUE) return GetLastError(); /* Возвращает отказ SetupAPI без попытки послать команду. */
|
||
error = ERROR_NOT_FOUND; /* Отсутствие интерфейса обновления не считается успешным входом в DFU. */
|
||
for (DWORD index = 0; ; ++index) { /* Проверяет интерфейсы до точного совпадения физического устройства. */
|
||
SP_DEVICE_INTERFACE_DATA iface = {0}; /* Создаёт независимое описание очередного DFU-интерфейса. */
|
||
iface.cbSize = sizeof(iface); /* Задаёт обязательный размер структуры SetupAPI. */
|
||
if (!SetupDiEnumDeviceInterfaces(devices, NULL, &guid, index, &iface)) { /* Завершает обход либо сообщает ошибку перечисления. */
|
||
if (GetLastError() != ERROR_NO_MORE_ITEMS) error = GetLastError(); /* Отличает штатный конец списка от отказа Windows. */
|
||
break; /* Выходит из обхода после исчерпания интерфейсов или ошибки. */
|
||
} /* Завершает проверку результата перечисления. */
|
||
DWORD size = 0; /* Хранит размер описания очередного интерфейса в байтах. */
|
||
SetupDiGetDeviceInterfaceDetailW(devices, &iface, NULL, 0, &size, NULL); /* Определяет буфер для полного пути интерфейса обновления. */
|
||
if (!size) { error = GetLastError(); break; } /* Не продолжает работу с неполным результатом SetupAPI. */
|
||
PSP_DEVICE_INTERFACE_DETAIL_DATA_W detail = malloc(size); /* Выделяет описание с достаточным местом для USB-пути. */
|
||
if (!detail) { error = ERROR_NOT_ENOUGH_MEMORY; break; } /* Не допускает обращения к отсутствующему буферу. */
|
||
detail->cbSize = sizeof(*detail); /* Сохраняет ABI описания интерфейса Windows. */
|
||
if (!SetupDiGetDeviceInterfaceDetailW(devices, &iface, detail, size, NULL, NULL)) { /* Получает путь, который можно передать CreateFileW. */
|
||
error = GetLastError(); free(detail); break; /* Сохраняет ошибку и освобождает буфер перед выходом. */
|
||
} /* Завершает чтение пути интерфейса. */
|
||
DEVINST parent = 0; /* Хранит родителя текущего кандидата на обновление. */
|
||
if (candle_dfu_parent(detail->DevicePath, &parent) || parent != wanted_parent) { /* Исключает каждый интерфейс другого физического адаптера. */
|
||
free(detail); continue; /* Освобождает описание неподходящего кандидата и продолжает поиск. */
|
||
} /* Завершает проверку принадлежности выбранному USB-адаптеру. */
|
||
HANDLE file = CreateFileW(detail->DevicePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); /* Эксклюзивно открывает интерфейс обновления выбранного устройства для WinUSB. */
|
||
free(detail); /* Путь больше не нужен после открытия системного дескриптора. */
|
||
if (file == INVALID_HANDLE_VALUE) { error = GetLastError(); break; } /* Не скрывает отсутствие доступа или занятость интерфейса. */
|
||
WINUSB_INTERFACE_HANDLE usb = NULL; /* Хранит отдельный WinUSB-сеанс интерфейса обновления без CAN bulk endpoint. */
|
||
error = ERROR_SUCCESS; /* Сбрасывает исходное отсутствие интерфейса после точного совпадения устройства. */
|
||
if (!WinUsb_Initialize(file, &usb)) error = GetLastError(); /* Проверяет наличие рабочего WinUSB-драйвера DFU runtime. */
|
||
USB_INTERFACE_DESCRIPTOR descriptor = {0}; /* Получает реальный номер и класс открытого интерфейса. */
|
||
if (!error && !WinUsb_QueryInterfaceSettings(usb, 0, &descriptor)) error = GetLastError(); /* Не предполагает, что произвольный WinUSB-интерфейс поддерживает DFU. */
|
||
if (!error && (descriptor.bInterfaceClass != 0xFE || descriptor.bInterfaceSubClass != 1 || descriptor.bInterfaceProtocol != 1)) error = ERROR_NOT_SUPPORTED; /* Проверяет класс приложения, подкласс DFU и runtime-протокол по DFU 1.1. */
|
||
ULONG timeout = 500, transferred = 0; /* Ограничивает управляющий USB-запрос 500 миллисекундами и учитывает длину ответа. */
|
||
if (!error && !WinUsb_SetPipePolicy(usb, 0, PIPE_TRANSFER_TIMEOUT, sizeof(timeout), &timeout)) error = GetLastError(); /* Устанавливает тайм-аут endpoint 0, чтобы отсутствующий ответ не блокировал программу. */
|
||
WINUSB_SETUP_PACKET packet = {0}; /* Обнуляет поля USB SETUP перед запросом перехода в загрузчик. */
|
||
packet.RequestType = 0x21; packet.Request = 0; packet.Value = 1000; packet.Index = descriptor.bInterfaceNumber; /* DFU_DETACH: class/interface OUT, запрос 0, ожидаемое время отключения 1000 мс. */
|
||
if (!error && !WinUsb_ControlTransfer(usb, packet, NULL, 0, &transferred, NULL)) error = GetLastError(); /* Отправляет DETACH и требует подтверждённой USB-транзакции. */
|
||
packet.RequestType = 0xA1; packet.Request = 3; packet.Value = 0; packet.Length = 6; /* DFU_GETSTATUS: class/interface IN, запрос 3, ответ ровно 6 байт по DFU 1.1. */
|
||
if (!error && !WinUsb_ControlTransfer(usb, packet, status, 6, &transferred, NULL)) error = GetLastError(); /* Проверяет результат до отложенного перехода прошивки через 300 мс. */
|
||
if (!error && transferred != 6) error = ERROR_INVALID_DATA; /* Не трактует укороченный ответ как подтверждение входа в загрузчик. */
|
||
if (usb) WinUsb_Free(usb); /* Освобождает WinUSB до появления загрузчика с другим VID/PID. */
|
||
CloseHandle(file); /* Освобождает файловый дескриптор после успеха и при любой ошибке USB. */
|
||
break; /* Не отправляет DETACH повторно и не переключается на другой адаптер при отказе. */
|
||
} /* Завершает поиск и обработку единственного выбранного адаптера. */
|
||
SetupDiDestroyDeviceInfoList(devices); /* Освобождает перечень интерфейсов при любом результате. */
|
||
return error; /* Статус прошивки возвращается отдельно от системной ошибки USB. */
|
||
} /* Завершает управление DFU runtime без записи flash или изменения образа. */
|
||
|
||
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];
|
||
StringCchPrintfW(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; i<ifaceDescriptor.bNumEndpoints; i++) {
|
||
|
||
WINUSB_PIPE_INFORMATION pipeInfo;
|
||
if (!WinUsb_QueryPipe(dev->winUSBHandle, 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; i<CANDLE_URB_COUNT; i++) {
|
||
if (dev->rxurbs[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; i<CANDLE_URB_COUNT; i++) {
|
||
HANDLE ev = CreateEvent(NULL, true, false, NULL);
|
||
if (ev == NULL) {
|
||
dev->last_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_product(candle_handle hdev, wchar_t *product, uint32_t capacity) /* Читает USB Product String открытого адаптера в буфер UTF-16; capacity измеряется в wchar_t, включая завершающий ноль. */
|
||
{ /* Начало чтения отображаемого имени без изменения режима CAN. */
|
||
if (!product || !capacity) return false; /* Не пишет в отсутствующий либо пустой выходной буфер. */
|
||
product[0] = 0; /* Сохраняет пустую строку при любом отказе чтения дескриптора. */
|
||
candle_device_t *dev = (candle_device_t*)hdev; /* Получает уже открытый WinUSB-сеанс выбранного физического адаптера. */
|
||
if (!dev || !dev->winUSBHandle) return false; /* Не открывает устройство заново и не обращается к закрытому интерфейсу. */
|
||
USB_DEVICE_DESCRIPTOR descriptor = {0}; /* Получает индекс строки продукта из стандартного дескриптора устройства. */
|
||
ULONG received = 0; /* Хранит фактически полученное количество байтов управляющего ответа. */
|
||
if (!WinUsb_GetDescriptor(dev->winUSBHandle, 1, 0, 0, (PUCHAR)&descriptor, sizeof(descriptor), &received) || received != sizeof(descriptor)) return false; /* Тип 1 обозначает Device Descriptor по USB 2.0; неполный ответ не используется. */
|
||
UCHAR string_index = descriptor.iProduct; /* Для обычных адаптеров использует объявленный устройством индекс USB Product String. */
|
||
if (descriptor.idVendor == 0x1D50 && descriptor.idProduct == 0x606F) string_index = 2; /* В CANable Candlelight имя продукта имеет индекс 2; WinUSB может подменять iProduct индексом имени интерфейса, как описано в штатном примере проекта. */
|
||
if (!string_index) return false; /* Нулевой iProduct означает, что устройство не предоставляет имя продукта. */
|
||
UCHAR raw[256] = {0}; /* Буфер вмещает строковый дескриптор USB, чей однобайтовый bLength ограничен 255 байтами. */
|
||
USHORT language = 0x0409; /* Использует английский идентификатор USB как резервный вариант для устройств без списка языков. */
|
||
if (WinUsb_GetDescriptor(dev->winUSBHandle, 3, 0, 0, raw, sizeof(raw), &received) && received >= 4 && raw[0] >= 4 && raw[1] == 3) language = (USHORT)(raw[2] | ((USHORT)raw[3] << 8)); /* Тип 3 и индекс 0 возвращают список LANGID; выбирает первый язык, предоставленный адаптером. */
|
||
if (!WinUsb_GetDescriptor(dev->winUSBHandle, 3, string_index, language, raw, sizeof(raw), &received)) return false; /* Читает именно строку продукта на поддерживаемом устройством языке. */
|
||
if (received < 2 || raw[1] != 3 || raw[0] < 2 || raw[0] > received || (raw[0] & 1)) return false; /* Проверяет тип, размер и чётное число байтов UTF-16 перед копированием в выходной буфер. */
|
||
uint32_t count = (raw[0] - 2) / 2; /* Исключает двухбайтовый заголовок и переводит длину UTF-16LE из байтов в число кодовых единиц. */
|
||
if (count >= capacity) return false; /* Не обрезает индивидуальный номер в конце имени и оставляет место для завершающего нуля. */
|
||
for (uint32_t index = 0; index < count; ++index) product[index] = (wchar_t)(raw[2 + index * 2] | ((USHORT)raw[3 + index * 2] << 8)); /* Явно собирает кодовые единицы UTF-16LE без предположений о выравнивании массива байтов. */
|
||
product[count] = 0; /* Завершает строку для безопасного чтения через ctypes и отображения в Qt. */
|
||
return count > 0; /* Пустое имя оставляет интерфейсу стандартную резервную подпись адаптера. */
|
||
} /* Завершает получение настоящего имени из прошивки USB-адаптера. */
|
||
|
||
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_bus_off_recover(candle_handle hdev, uint8_t ch)
|
||
{
|
||
candle_device_t *dev = (candle_device_t*)hdev;
|
||
return candle_ctrl_bus_off_recover(dev, ch);
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
bool __stdcall DLL candle_channel_start_configured(candle_handle hdev, uint8_t ch, uint32_t flags) /* Новый вход сохраняет прежний ABI start и позволяет отключать HW_TIMESTAMP. */
|
||
{ /* Явно выбранные Python-флаги должны попасть в прошивку без автоматического дополнения. */
|
||
return candle_ctrl_set_device_mode((candle_device_t*)hdev, ch, CANDLE_DEVMODE_START, flags); /* Передаём точную маску после проверки возможностей на уровне транспорта. */
|
||
} /* Завершение запуска с явно заданными параметрами. */
|
||
|
||
bool __stdcall DLL candle_channel_get_data_capabilities(candle_handle hdev, uint8_t ch, candle_capability_t *cap) /* Экспортируем чтение FD-пределов для расчёта тайминга на любой частоте CAN. */
|
||
{ /* Общая форма capability позволяет повторно использовать валидатор номинального тайминга. */
|
||
return candle_ctrl_data_capability((candle_device_t*)hdev, ch, cap); /* USB-запрос возвращает реальные пределы выбранного канала. */
|
||
} /* Завершение получения возможностей быстрой фазы. */
|
||
|
||
bool __stdcall DLL candle_channel_set_termination(candle_handle hdev, uint8_t ch, uint32_t state) /* Значения 0 и 1 соответствуют OFF и ON стандарта gs_usb. */
|
||
{ /* Проверка исключает отправку неопределённого состояния в прошивку. */
|
||
return state <= 1 && candle_ctrl_termination((candle_device_t*)hdev, ch, &state, true); /* Выполняем запись только для допустимого состояния. */
|
||
} /* Завершение переключения терминатора. */
|
||
|
||
bool __stdcall DLL candle_channel_get_termination(candle_handle hdev, uint8_t ch, uint32_t *state) /* Чтение состояния не меняет топологию CAN. */
|
||
{ /* Используем тот же формат 32-битного значения, что и при записи. */
|
||
return candle_ctrl_termination((candle_device_t*)hdev, ch, state, false); /* false выбирает USB_DIR_IN и GET_TERMINATION. */
|
||
} /* Завершение чтения терминатора. */
|
||
|
||
bool __stdcall DLL candle_channel_get_diagnostics(candle_handle hdev, uint8_t ch, uint32_t *state, uint32_t *rxerr, uint32_t *txerr) /* Сохраняем счётчики, которые старый get_state отбрасывал. */
|
||
{ /* Диагностика выполняется только для уже открытого устройства. */
|
||
candle_device_state_t data = {0}; /* Полная структура протокола содержит три слова: состояние, RX и TX. */
|
||
if (!candle_ctrl_get_state((candle_device_t*)hdev, ch, &data)) { /* Не возвращаем нулевые счётчики как будто запрос был успешным. */
|
||
return false; /* Причина USB-отказа уже записана управляющим запросом. */
|
||
} /* Дальнейшее копирование относится только к подтверждённому ответу. */
|
||
*state = data.state; /* Сохраняем состояние Error-active, Warning, Passive, Bus-off, Stopped или Sleeping. */
|
||
*rxerr = data.rxerr; /* Счётчик ошибок приёма измеряется в единицах CAN-контроллера. */
|
||
*txerr = data.txerr; /* Счётчик ошибок передачи помогает отличать отсутствие ACK от ошибок приёма. */
|
||
return true; /* Все три выходных значения относятся к одному USB-ответу. */
|
||
} /* Завершение расширенной диагностики канала. */
|
||
|
||
/* 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;
|
||
|
||
const DWORD header_size = 12; /* Заголовок gs_usb занимает 12 байт в Classic и FD; короткий FD может не содержать восьми байтов данных. */
|
||
if (bytes_transfered < header_size) { /* До чтения flags и DLC проверяем наличие полного заголовка. */
|
||
candle_prepare_read(dev, urb_num);
|
||
candle_logf(L"fd read too small urb=%u bytes=%lu min=%lu",
|
||
urb_num,
|
||
bytes_transfered,
|
||
header_size); /* Минимальная длина здесь относится к заголовку, а payload проверяется после определения формата. */
|
||
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];
|
||
if (raw_dlc > 15) { /* FD DLC является четырёхбитным кодом и не может содержать значения выше 15. */
|
||
candle_prepare_read(dev, urb_num); /* Повреждённый USB-пакет не должен останавливать последующий приём. */
|
||
dev->last_error = CANDLE_ERR_READ_SIZE; /* Неверный DLC считается ошибкой формата входного кадра. */
|
||
return false; /* Не превращаем повреждённый DLC в пустой успешный FD-кадр. */
|
||
} /* Далее длина безопасно вычисляется по таблице CAN FD. */
|
||
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 {
|
||
if (bytes_transfered < sizeof(candle_frame_t) - sizeof(uint32_t)) { /* Classic-пакет обязан содержать весь восьмибайтовый буфер данных, даже при меньшем DLC. */
|
||
candle_prepare_read(dev, urb_num); /* Возобновляем соответствующий URB после отклонения неполного пакета. */
|
||
dev->last_error = CANDLE_ERR_READ_SIZE; /* Не допускаем чтение неполученных данных из локальной структуры. */
|
||
return false; /* Повреждённый Classic-пакет не попадает в Python через FD API. */
|
||
} /* Минимальный размер Classic теперь проверен отдельно от коротких FD. */
|
||
/* 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;
|
||
}
|