590 lines
16 KiB
C
590 lines
16 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/>.
|
|
|
|
*/
|
|
|
|
#include "candle.h"
|
|
#include <stdlib.h>
|
|
|
|
#include "candle_defs.h"
|
|
#include "candle_ctrl_req.h"
|
|
#include "ch_9.h"
|
|
|
|
static bool candle_dev_interal_open(candle_handle hdev);
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
GUID guid;
|
|
if (CLSIDFromString(L"{c15b4308-04d3-11e6-b3ea-6057189e6443}", &guid) != NOERROR) {
|
|
l->last_error = CANDLE_ERR_CLSID;
|
|
return false;
|
|
}
|
|
|
|
HDEVINFO hdi = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|
if (hdi == INVALID_HANDLE_VALUE) {
|
|
l->last_error = CANDLE_ERR_GET_DEVICES;
|
|
return false;
|
|
}
|
|
|
|
bool rv = false;
|
|
for (unsigned i=0; 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 (!candle_read_di(hdi, interfaceData, &l->dev[i])) {
|
|
l->last_error = l->dev[i].last_error;
|
|
rv = false;
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
|
|
DWORD err = GetLastError();
|
|
if (err==ERROR_NO_MORE_ITEMS) {
|
|
l->num_devices = i;
|
|
l->last_error = CANDLE_ERR_OK;
|
|
rv = true;
|
|
} else {
|
|
l->last_error = CANDLE_ERR_SETUPDI_IF_ENUM;
|
|
rv = false;
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SetupDiDestroyDeviceInfoList(hdi);
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
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;
|
|
unsigned pipes_found = 0;
|
|
|
|
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)) {
|
|
dev->bulkInPipe = pipeInfo.PipeId;
|
|
pipes_found++;
|
|
} else if (pipeInfo.PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_OUT(pipeInfo.PipeId)) {
|
|
dev->bulkOutPipe = pipeInfo.PipeId;
|
|
pipes_found++;
|
|
} else {
|
|
dev->last_error = CANDLE_ERR_PARSE_IF_DESCR;
|
|
goto winusb_free;
|
|
}
|
|
|
|
}
|
|
|
|
if (pipes_found != 2) {
|
|
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;
|
|
}
|
|
|
|
if (!candle_ctrl_get_capability(dev, 0, &dev->bt_const)) {
|
|
dev->last_error = CANDLE_ERR_GET_BITTIMING_CONST;
|
|
goto winusb_free;
|
|
}
|
|
|
|
dev->last_error = CANDLE_ERR_OK;
|
|
return true;
|
|
|
|
winusb_free:
|
|
WinUsb_Free(dev->winUSBHandle);
|
|
|
|
close_handle:
|
|
CloseHandle(dev->deviceHandle);
|
|
return false;
|
|
|
|
}
|
|
|
|
static bool candle_prepare_read(candle_device_t *dev, unsigned urb_num)
|
|
{
|
|
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 || (GetLastError()!=ERROR_IO_PENDING)) {
|
|
dev->last_error = CANDLE_ERR_PREPARE_READ;
|
|
return false;
|
|
} else {
|
|
dev->last_error = CANDLE_ERR_OK;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static bool candle_close_rxurbs(candle_device_t *dev)
|
|
{
|
|
for (unsigned i=0; i<CANDLE_URB_COUNT; i++) {
|
|
if (dev->rxevents[i] != NULL) {
|
|
CloseHandle(dev->rxevents[i]);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
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);
|
|
dev->rxevents[i] = ev;
|
|
dev->rxurbs[i].ovl.hEvent = ev;
|
|
if (!candle_prepare_read(dev, i)) {
|
|
candle_close_rxurbs(dev);
|
|
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_close_rxurbs(dev);
|
|
|
|
WinUsb_Free(dev->winUSBHandle);
|
|
dev->winUSBHandle = NULL;
|
|
CloseHandle(dev->deviceHandle);
|
|
dev->deviceHandle = NULL;
|
|
|
|
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)
|
|
{
|
|
// TODO check if info was already read from device; try to do so; throw error...
|
|
candle_device_t *dev = (candle_device_t*)hdev;
|
|
memcpy(cap, &dev->bt_const.feature, sizeof(candle_capability_t));
|
|
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;
|
|
flags |= CANDLE_MODE_HW_TIMESTAMP;
|
|
return candle_ctrl_set_device_mode(dev, ch, CANDLE_DEVMODE_START, flags);
|
|
}
|
|
|
|
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_frame_send(candle_handle hdev, uint8_t ch, candle_frame_t *frame)
|
|
{
|
|
// TODO ensure device is open, check channel count..
|
|
candle_device_t *dev = (candle_device_t*)hdev;
|
|
|
|
unsigned long bytes_sent = 0;
|
|
|
|
frame->echo_id = 0;
|
|
frame->channel = ch;
|
|
|
|
bool rc = WinUsb_WritePipe(
|
|
dev->winUSBHandle,
|
|
dev->bulkOutPipe,
|
|
(uint8_t*)frame,
|
|
sizeof(*frame),
|
|
&bytes_sent,
|
|
0
|
|
);
|
|
|
|
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)) {
|
|
candle_prepare_read(dev, urb_num);
|
|
dev->last_error = CANDLE_ERR_READ_RESULT;
|
|
return false;
|
|
}
|
|
|
|
if (bytes_transfered < sizeof(*frame)-4) {
|
|
candle_prepare_read(dev, urb_num);
|
|
dev->last_error = CANDLE_ERR_READ_SIZE;
|
|
return false;
|
|
}
|
|
|
|
if (bytes_transfered < sizeof(*frame)) {
|
|
frame->timestamp_us = 0;
|
|
}
|
|
|
|
memcpy(frame, dev->rxurbs[urb_num].buf, sizeof(*frame));
|
|
|
|
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;
|
|
}
|