diff options
Diffstat (limited to 'libaltos/libaltos_windows.c')
-rw-r--r-- | libaltos/libaltos_windows.c | 761 |
1 files changed, 761 insertions, 0 deletions
diff --git a/libaltos/libaltos_windows.c b/libaltos/libaltos_windows.c new file mode 100644 index 00000000..2c3f41e1 --- /dev/null +++ b/libaltos/libaltos_windows.c @@ -0,0 +1,761 @@ +/* + * Copyright © 2016 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include "libaltos_private.h" + +#include <winsock2.h> +#include <windows.h> +#include <setupapi.h> + +struct altos_list { + HDEVINFO dev_info; + int index; + int ftdi; +}; + +#define USB_BUF_SIZE 64 + +struct altos_file_windows { + struct altos_file file; + + BOOL is_winsock; + /* Data used by the regular I/O */ + HANDLE handle; + OVERLAPPED ov_read; + BOOL pend_read; + OVERLAPPED ov_write; + + /* Data used by winsock */ + SOCKET socket; +}; + +#include <stdarg.h> + +static void +log_message(char *fmt, ...) +{ + static FILE *log = NULL; + va_list a; + + if (!log) + log = fopen("\\temp\\altos.txt", "w"); + if (log) { + SYSTEMTIME time; + char buffer[4096]; + + GetLocalTime(&time); + sprintf (buffer, "%4d-%02d-%02d %2d:%02d:%02d. ", + time.wYear, time.wMonth, time.wDay, + time.wHour, time.wMinute, time.wSecond); + va_start(a, fmt); + + vsprintf(buffer + strlen(buffer), fmt, a); + va_end(a); + + fputs(buffer, log); + fflush(log); + fputs(buffer, stdout); + fflush(stdout); + } +} + +static void +_altos_set_last_windows_error(char *file, int line, DWORD error) +{ + TCHAR message[1024]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + 0, + error, + 0, + message, + sizeof (message) / sizeof (TCHAR), + NULL); + if (error != ERROR_SUCCESS) + log_message ("%s:%d (%d) %s\n", file, line, error, message); + altos_set_last_error(error, message); +} + +#define altos_set_last_windows_error() _altos_set_last_windows_error(__FILE__, __LINE__, GetLastError()) +#define altos_set_last_winsock_error() _altos_set_last_windows_error(__FILE__, __LINE__, WSAGetLastError()) + +PUBLIC struct altos_list * +altos_list_start(void) +{ + struct altos_list *list = calloc(1, sizeof (struct altos_list)); + + if (!list) + return NULL; + list->dev_info = SetupDiGetClassDevs(NULL, "USB", NULL, + DIGCF_ALLCLASSES|DIGCF_PRESENT); + if (list->dev_info == INVALID_HANDLE_VALUE) { + altos_set_last_windows_error(); + free(list); + return NULL; + } + list->index = 0; + list->ftdi = 0; + return list; +} + +PUBLIC struct altos_list * +altos_ftdi_list_start(void) +{ + struct altos_list *list = calloc(1, sizeof (struct altos_list)); + + if (!list) + return NULL; + list->dev_info = SetupDiGetClassDevs(NULL, "FTDIBUS", NULL, + DIGCF_ALLCLASSES|DIGCF_PRESENT); + if (list->dev_info == INVALID_HANDLE_VALUE) { + altos_set_last_windows_error(); + free(list); + return NULL; + } + list->index = 0; + list->ftdi = 1; + return list; +} + +PUBLIC int +altos_list_next(struct altos_list *list, struct altos_device *device) +{ + SP_DEVINFO_DATA dev_info_data; + BYTE port[128]; + DWORD port_len; + char friendlyname[256]; + BYTE symbolic[256]; + DWORD symbolic_len; + HKEY dev_key; + unsigned int vid, pid; + int serial; + HRESULT result; + DWORD friendlyname_type; + DWORD friendlyname_len; + char instanceid[1024]; + DWORD instanceid_len; + + dev_info_data.cbSize = sizeof (SP_DEVINFO_DATA); + while(SetupDiEnumDeviceInfo(list->dev_info, list->index, + &dev_info_data)) + { + list->index++; + + dev_key = SetupDiOpenDevRegKey(list->dev_info, &dev_info_data, + DICS_FLAG_GLOBAL, 0, DIREG_DEV, + KEY_READ); + if (dev_key == INVALID_HANDLE_VALUE) { + altos_set_last_windows_error(); + continue; + } + + if (list->ftdi) { + vid = 0x0403; + pid = 0x6015; + serial = 0; + } else { + vid = pid = serial = 0; + /* Fetch symbolic name for this device and parse out + * the vid/pid/serial info */ + symbolic_len = sizeof(symbolic); + result = RegQueryValueEx(dev_key, "SymbolicName", NULL, NULL, + symbolic, &symbolic_len); + if (result != 0) { + altos_set_last_windows_error(); + } else { + sscanf((char *) symbolic + sizeof("\\??\\USB#VID_") - 1, + "%04X", &vid); + sscanf((char *) symbolic + sizeof("\\??\\USB#VID_XXXX&PID_") - 1, + "%04X", &pid); + sscanf((char *) symbolic + sizeof("\\??\\USB#VID_XXXX&PID_XXXX#") - 1, + "%d", &serial); + } + if (vid == 0 || pid == 0 || serial == 0) { + if (SetupDiGetDeviceInstanceId(list->dev_info, + &dev_info_data, + instanceid, + sizeof (instanceid), + &instanceid_len)) { + sscanf((char *) instanceid + sizeof("USB\\VID_") - 1, + "%04X", &vid); + sscanf((char *) instanceid + sizeof("USB\\VID_XXXX&PID_") - 1, + "%04X", &pid); + sscanf((char *) instanceid + sizeof("USB\\VID_XXXX&PID_XXXX\\") - 1, + "%d", &serial); + } else { + altos_set_last_windows_error(); + } + } + if (vid == 0 || pid == 0 || serial == 0) { + RegCloseKey(dev_key); + continue; + } + } + + /* Fetch the com port name */ + port_len = sizeof (port); + result = RegQueryValueEx(dev_key, "PortName", NULL, NULL, + port, &port_len); + RegCloseKey(dev_key); + if (result != 0) { + altos_set_last_windows_error(); + continue; + } + + /* Fetch the device description which is the device name, + * with firmware that has unique USB ids */ + friendlyname_len = sizeof (friendlyname); + if(!SetupDiGetDeviceRegistryProperty(list->dev_info, + &dev_info_data, + SPDRP_FRIENDLYNAME, + &friendlyname_type, + (BYTE *)friendlyname, + sizeof(friendlyname), + &friendlyname_len)) + { + altos_set_last_windows_error(); + continue; + } + device->vendor = vid; + device->product = pid; + device->serial = serial; + strcpy(device->name, friendlyname); + + strcpy(device->path, (char *) port); + return 1; + } + result = GetLastError(); + if (result != ERROR_NO_MORE_ITEMS) + altos_set_last_windows_error(); + return 0; +} + +PUBLIC void +altos_list_finish(struct altos_list *list) +{ + SetupDiDestroyDeviceInfoList(list->dev_info); + free(list); +} + +static int +altos_queue_read(struct altos_file_windows *file) +{ + DWORD got; + if (file->pend_read) + return LIBALTOS_SUCCESS; + + if (!ReadFile(file->handle, file->file.in_data, USB_BUF_SIZE, &got, &file->ov_read)) { + if (GetLastError() != ERROR_IO_PENDING) { + altos_set_last_windows_error(); + return LIBALTOS_ERROR; + } + file->pend_read = TRUE; + } else { + file->pend_read = FALSE; + file->file.in_read = 0; + file->file.in_used = got; + } + return LIBALTOS_SUCCESS; +} + +static int +altos_wait_read(struct altos_file_windows *file, int timeout) +{ + DWORD ret; + DWORD got; + + if (!file->pend_read) + return LIBALTOS_SUCCESS; + + if (!timeout) + timeout = INFINITE; + + ret = WaitForSingleObject(file->ov_read.hEvent, timeout); + switch (ret) { + case WAIT_OBJECT_0: + if (!GetOverlappedResult(file->handle, &file->ov_read, &got, FALSE)) { + if (GetLastError () != ERROR_OPERATION_ABORTED) + altos_set_last_windows_error(); + return LIBALTOS_ERROR; + } + file->pend_read = FALSE; + file->file.in_read = 0; + file->file.in_used = got; + break; + case WAIT_TIMEOUT: + return LIBALTOS_TIMEOUT; + break; + default: + altos_set_last_windows_error(); + return LIBALTOS_ERROR; + } + return LIBALTOS_SUCCESS; +} + +int +altos_fill(struct altos_file *file_common, int timeout) +{ + struct altos_file_windows *file = (struct altos_file_windows *) file_common; + + int ret; + + if (file->file.in_read < file->file.in_used) + return LIBALTOS_SUCCESS; + + file->file.in_read = file->file.in_used = 0; + + if (file->is_winsock) { + + for (;;) { + fd_set readfds; + TIMEVAL timeval; + int thistimeout; + + /* Check to see if the socket has been closed */ + if (file->socket == INVALID_SOCKET) + return LIBALTOS_ERROR; + +#define POLL_TIMEOUT 10000 + + /* Poll to see if the socket has been closed + * as select doesn't abort when that happens + */ + if (timeout) { + thistimeout = timeout; + if (thistimeout > POLL_TIMEOUT) + thistimeout = POLL_TIMEOUT; + } else { + thistimeout = POLL_TIMEOUT; + } + + timeval.tv_sec = thistimeout / 1000; + timeval.tv_usec = (thistimeout % 1000) * 1000; + + FD_ZERO(&readfds); + FD_SET(file->socket, &readfds); + + ret = select(1, &readfds, NULL, NULL, &timeval); + + if (ret == 0) { + if (timeout) { + timeout -= thistimeout; + if (timeout == 0) + return LIBALTOS_TIMEOUT; + } + } else { + if (ret > 0) + break; + + if (ret < 0) { + altos_set_last_winsock_error(); + return LIBALTOS_ERROR; + } + } + } + + if (file->socket == INVALID_SOCKET) { + altos_set_last_winsock_error(); + return LIBALTOS_ERROR; + } + + ret = recv(file->socket, (char *) file->file.in_data, USB_BUF_SIZE, 0); + + if (ret <= 0) { + altos_set_last_winsock_error(); + return LIBALTOS_ERROR; + } + file->file.in_read = 0; + file->file.in_used = ret; + } else { + if (file->handle == INVALID_HANDLE_VALUE) { + altos_set_last_windows_error(); + return LIBALTOS_ERROR; + } + + ret = altos_queue_read(file); + if (ret) + return ret; + ret = altos_wait_read(file, timeout); + if (ret) + return ret; + } + + return LIBALTOS_SUCCESS; +} + +PUBLIC int +altos_flush(struct altos_file *file_common) +{ + struct altos_file_windows *file = (struct altos_file_windows *) file_common; + + unsigned char *data = file->file.out_data; + int used = file->file.out_used; + + while (used) { + if (file->is_winsock) { + int put; + + put = send(file->socket, (char *) data, used, 0); + if (put <= 0) { + altos_set_last_winsock_error(); + return LIBALTOS_ERROR; + } + data += put; + used -= put; + } else { + DWORD put; + DWORD ret; + if (!WriteFile(file->handle, data, used, &put, &file->ov_write)) { + if (GetLastError() != ERROR_IO_PENDING) { + altos_set_last_windows_error(); + return LIBALTOS_ERROR; + } + ret = WaitForSingleObject(file->ov_write.hEvent, INFINITE); + switch (ret) { + case WAIT_OBJECT_0: + if (!GetOverlappedResult(file->handle, &file->ov_write, &put, FALSE)) { + altos_set_last_windows_error(); + return LIBALTOS_ERROR; + } + break; + default: + altos_set_last_windows_error(); + return LIBALTOS_ERROR; + } + } + data += put; + used -= put; + } + } + file->file.out_used = 0; + return LIBALTOS_SUCCESS; +} + +static HANDLE +open_serial(char *full_name) +{ + HANDLE handle; + DCB dcb; + + handle = CreateFile(full_name, GENERIC_READ|GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + + if (handle == INVALID_HANDLE_VALUE) { + altos_set_last_windows_error(); + return INVALID_HANDLE_VALUE; + } + + if (!GetCommState(handle, &dcb)) { + altos_set_last_windows_error(); + CloseHandle(handle); + return INVALID_HANDLE_VALUE; + } + dcb.BaudRate = CBR_9600; + dcb.fBinary = TRUE; + dcb.fParity = FALSE; + dcb.fOutxCtsFlow = FALSE; + dcb.fOutxDsrFlow = FALSE; + dcb.fDtrControl = DTR_CONTROL_ENABLE; + dcb.fDsrSensitivity = FALSE; + dcb.fTXContinueOnXoff = FALSE; + dcb.fOutX = FALSE; + dcb.fInX = FALSE; + dcb.fErrorChar = FALSE; + dcb.fNull = FALSE; + dcb.fRtsControl = RTS_CONTROL_ENABLE; + dcb.fAbortOnError = FALSE; + dcb.XonLim = 10; + dcb.XoffLim = 10; + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + dcb.XonChar = 17; + dcb.XoffChar = 19; +#if 0 + dcb.ErrorChar = 0; + dcb.EofChar = 0; + dcb.EvtChar = 0; +#endif + if (!SetCommState(handle, &dcb)) { + altos_set_last_windows_error(); + CloseHandle(handle); + return INVALID_HANDLE_VALUE; + } + return handle; +} + +PUBLIC struct altos_file * +altos_open(struct altos_device *device) +{ + struct altos_file_windows *file = calloc (1, sizeof (struct altos_file_windows)); + char full_name[64]; + COMMTIMEOUTS timeouts; + int i; + + if (!file) + return NULL; + + strcpy(full_name, "\\\\.\\"); + strcat(full_name, device->path); + + file->handle = INVALID_HANDLE_VALUE; + + for (i = 0; i < 5; i++) { + file->handle = open_serial(full_name); + if (file->handle != INVALID_HANDLE_VALUE) + break; + altos_set_last_windows_error(); + Sleep(100); + } + + if (file->handle == INVALID_HANDLE_VALUE) { + free(file); + return NULL; + } + + /* The FTDI driver doesn't appear to work right unless you open it twice */ + if (device->vendor == 0x0403) { + CloseHandle(file->handle); + file->handle = open_serial(full_name); + if (file->handle == INVALID_HANDLE_VALUE) { + free(file); + return NULL; + } + } + + timeouts.ReadIntervalTimeout = MAXDWORD; + timeouts.ReadTotalTimeoutMultiplier = MAXDWORD; + timeouts.ReadTotalTimeoutConstant = 1 << 30; /* almost forever */ + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + SetCommTimeouts(file->handle, &timeouts); + + file->ov_read.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + file->ov_write.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + return &file->file; +} + +PUBLIC void +altos_close(struct altos_file *file_common) +{ + struct altos_file_windows *file = (struct altos_file_windows *) file_common; + + if (file->is_winsock) { + SOCKET socket = file->socket; + if (socket != INVALID_SOCKET) { + file->socket = INVALID_SOCKET; + closesocket(socket); + } + } else { + HANDLE handle = file->handle; + if (handle != INVALID_HANDLE_VALUE) { + HANDLE ov_read = file->ov_read.hEvent; + HANDLE ov_write = file->ov_write.hEvent; + file->handle = INVALID_HANDLE_VALUE; + file->ov_read.hEvent = INVALID_HANDLE_VALUE; + file->ov_write.hEvent = INVALID_HANDLE_VALUE; + PurgeComm(handle, PURGE_RXABORT|PURGE_RXCLEAR|PURGE_TXABORT|PURGE_TXCLEAR); + Sleep(100); + CloseHandle(handle); + file->handle = INVALID_HANDLE_VALUE; + CloseHandle(ov_read); + CloseHandle(ov_write); + } + } +} + +#include <ws2bth.h> + +#define LUP_SET (LUP_RETURN_NAME| LUP_CONTAINERS | LUP_RETURN_ADDR | LUP_FLUSHCACHE |\ + LUP_RETURN_TYPE | LUP_RETURN_BLOB | LUP_RES_SERVICE) + +struct altos_bt_list { + WSADATA WSAData; + HANDLE lookup; +}; + +struct altos_bt_list * +altos_bt_list_start(int inquiry_time) +{ + struct altos_bt_list *bt_list; + WSAQUERYSET query_set; + int retCode; + + /* Windows provides no way to set the time */ + (void) inquiry_time; + bt_list = calloc(1, sizeof (struct altos_bt_list)); + if (!bt_list) { + altos_set_last_windows_error(); + return NULL; + } + + if ((retCode = WSAStartup(MAKEWORD(2,2),&bt_list->WSAData)) != 0) { + altos_set_last_winsock_error(); + free(bt_list); + return NULL; + } + + memset(&query_set, '\0', sizeof (query_set)); + query_set.dwSize = sizeof(WSAQUERYSET); + query_set.dwNameSpace = NS_BTH; + + retCode = WSALookupServiceBegin(&query_set, LUP_SET, &bt_list->lookup); + + if (retCode != 0) { + altos_set_last_winsock_error(); + free(bt_list); + return NULL; + } + return bt_list; +} + +static unsigned char get_byte(BTH_ADDR ba, int shift) +{ + return (ba >> ((5 - shift) << 3)) & 0xff; +} + +static BTH_ADDR put_byte(unsigned char c, int shift) +{ + return ((BTH_ADDR) c) << ((5 - shift) << 3); +} + +static void +ba2str(BTH_ADDR ba, char *str) +{ + + sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", + get_byte(ba, 0), + get_byte(ba, 1), + get_byte(ba, 2), + get_byte(ba, 3), + get_byte(ba, 4), + get_byte(ba, 5)); +} + +static BTH_ADDR +str2ba(char *str) +{ + unsigned int bytes[6]; + + sscanf(str, "%02x:%02x:%02x:%02x:%02x:%02x", + &bytes[0], + &bytes[1], + &bytes[2], + &bytes[3], + &bytes[4], + &bytes[5]); + return (put_byte(bytes[0], 0) | + put_byte(bytes[1], 1) | + put_byte(bytes[2], 2) | + put_byte(bytes[3], 3) | + put_byte(bytes[4], 4) | + put_byte(bytes[5], 5)); +} + +int +altos_bt_list_next(struct altos_bt_list *bt_list, + struct altos_bt_device *device) +{ + for (;;) { + BYTE buffer[4096]; + DWORD length = sizeof (buffer);; + WSAQUERYSET *results = (WSAQUERYSET *)buffer; + CSADDR_INFO *addr_info; + int retCode; + SOCKADDR_BTH *sockaddr_bth; + + memset(buffer, '\0', sizeof(buffer)); + + retCode = WSALookupServiceNext(bt_list->lookup, LUP_SET, &length, results); + + if (retCode != 0) { + int error = WSAGetLastError(); + if (error != WSAENOMORE && error != WSA_E_NO_MORE) + altos_set_last_winsock_error(); + return 0; + } + + if (results->dwNumberOfCsAddrs > 0) { + + addr_info = results->lpcsaBuffer; + + strncpy(device->name, results->lpszServiceInstanceName, sizeof(device->name)); + device->name[sizeof(device->name)-1] = '\0'; + + sockaddr_bth = (SOCKADDR_BTH *) addr_info->RemoteAddr.lpSockaddr; + + ba2str(sockaddr_bth->btAddr, device->addr); + + return 1; + } + } +} + +void +altos_bt_list_finish(struct altos_bt_list *bt_list) +{ + WSALookupServiceEnd(bt_list->lookup); + free(bt_list); +} + +void +altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device) +{ + strncpy(device->name, name, sizeof (device->name)); + device->name[sizeof(device->name)-1] = '\0'; + strncpy(device->addr, addr, sizeof (device->addr)); + device->addr[sizeof(device->addr)-1] = '\0'; +} + +struct altos_file * +altos_bt_open(struct altos_bt_device *device) +{ + struct altos_file_windows *file; + SOCKADDR_BTH sockaddr_bth; + int ret; + + file = calloc(1, sizeof (struct altos_file_windows)); + if (!file) { + return NULL; + } + + file->is_winsock = TRUE; + file->socket = WSASocket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM, NULL, 0, WSA_FLAG_OVERLAPPED); + + if (file->socket == INVALID_SOCKET) { + altos_set_last_winsock_error(); + free(file); + return NULL; + } + + memset(&sockaddr_bth, '\0', sizeof (sockaddr_bth)); + sockaddr_bth.addressFamily = AF_BTH; + sockaddr_bth.btAddr = str2ba(device->addr); + sockaddr_bth.port = 1; + + ret = connect(file->socket, (SOCKADDR *) &sockaddr_bth, sizeof (sockaddr_bth)); + + if (ret != 0) { + altos_set_last_winsock_error(); + closesocket(file->socket); + free(file); + return NULL; + } + return &file->file; +} + |