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; +} +  | 
