/* ************************************************************************** */ /* _____ _ */ /* stream/windows.c |_ _|__ _ _| |__ ___ _ _ */ /* | Project: libp7 | |/ _ \| | | | '_ \ / _ \ | | | */ /* | | (_) | |_| | | | | __/ |_| | */ /* By: thomas |_|\___/ \__,_|_| |_|\___|\__, |.fr */ /* Last updated: 2017/01/11 18:36:38 |___/ */ /* */ /* ************************************************************************** */ #include #ifndef P7_DISABLED_WINDOWS # include # include # include # include /* ************************************************************************** */ /* Open a serial device */ /* ************************************************************************** */ /** * p7_wcominit: * Initialize using a MS-Windows serial device. * * @arg handle the handle to make. * @arg flags the flags. * @arg com the com port number. * @return the error code (0 if ok). */ int p7_wcominit(p7_handle_t **handle, unsigned int flags, int com) { /* make up the serial device path */ logr_info("Looking for specific MS-Windows devices"); char path[20]; if (com <= 9) sprintf(path, "COM%d", com); else sprintf(path, "\\\\?\\COM%d", com); /* initialize */ return (p7_winit(handle, flags, path)); } /* ************************************************************************** */ /* Find devices on Microsoft Windows */ /* ************************************************************************** */ /** * wfind: * Find the Microsoft Windows device path. * * @arg vid the vendor ID (0x0000 to 0xffff) * @arg pid the product ID (0x0000 to 0xffff) * @return the allocated path of the device if found, NULL otherwise */ static char *wfind(unsigned int vid, unsigned int pid) { char *devpath = NULL; DWORD werr; /* make the vid/pid string */ char vidpid[20]; sprintf(vidpid, "#vid_%04x&pid_%04x", vid, pid); /* get the device information set (chained list) */ logr_info("Getting the device info set"); HDEVINFO DeviceInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (DeviceInfoSet == INVALID_HANDLE_VALUE) { werr = GetLastError(); logr_fatal("Device info gathering failed! Error 0x%08lX", werr); return (NULL); } /* local vars */ SP_DEVICE_INTERFACE_DATA DeviceInterfaceData; /* browse this set, setup */ DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); logr_info("Enumerating interfaces"); for (int i = 0; SetupDiEnumDeviceInterfaces(DeviceInfoSet, NULL, &GUID_DEVINTERFACE_USB_DEVICE, i, &DeviceInterfaceData); i++) { /* make the local variables */ DWORD RequiredSize = 0; /* get the detail size */ logr_info("Getting interface information detail size"); if (!SetupDiGetDeviceInterfaceDetail(DeviceInfoSet, &DeviceInterfaceData, NULL, 0, &RequiredSize, NULL) && (werr = GetLastError()) != ERROR_INSUFFICIENT_BUFFER) { logr_error("Error getting this size: 0x%08lX", werr); continue; } /* allocate detail space */ logr_info("Allocating space for interface information detail (%luo)", RequiredSize); PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData = malloc(RequiredSize); if (!DeviceInterfaceDetailData) { logr_error("Memory allocation failed. Oh well."); break ; } DeviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); /* get the detail */ logr_info("Getting interface information detail"); if (!SetupDiGetDeviceInterfaceDetail(DeviceInfoSet, &DeviceInterfaceData, DeviceInterfaceDetailData, RequiredSize, NULL, NULL)) { werr = GetLastError(); logr_error("Error getting the interface information detail: 0x%08lX", werr); continue; } /* check if it corresponds */ const char *p = DeviceInterfaceDetailData->DevicePath; logr_info("Stumbled across: %s", p); if (strstr(p, vidpid)) { devpath = strdup(p); if (!devpath) break ; } /* free the allocated detail */ free(DeviceInterfaceDetailData); if (devpath) break ; } /* destroy the device information set */ logr_info("Destroying the device information set"); SetupDiDestroyDeviceInfoList(DeviceInfoSet); return (devpath); } /* ************************************************************************** */ /* Structure of a cookie */ /* ************************************************************************** */ /* here it is * PSP_COOKIE is just there to take the piss out of Microsoft :p */ # define BUFSIZE 2048 typedef struct { HANDLE _handle; unsigned char _buf[BUFSIZE]; ssize_t _start, _end; } win_cookie_t, *PSP_COOKIE; /* ************************************************************************** */ /* Callbacks and initialization functions */ /* ************************************************************************** */ /** * p7_win_read: * Read from an MS-Windows stream. * * @arg vcookie the cookie (uncasted). * @return the error code (0 if ok). */ static int p7_win_read(void *vcookie, unsigned char *dest, size_t size, unsigned int timeout) { win_cookie_t *cookie = (win_cookie_t*)vcookie; /* transmit what's already in the buffer */ if (cookie->_start <= cookie->_end) { size_t tocopy = cookie->_end - cookie->_start + 1; if (tocopy > size) tocopy = size; memcpy(dest, &cookie->_buf[cookie->_start], tocopy); cookie->_start += tocopy; dest += tocopy; size -= tocopy; } /* set the timeout */ SetCommTimeouts(cookie->_handle, (COMMTIMEOUTS[]){{ .ReadIntervalTimeout = timeout }}); /* main receiving loop */ while (size) { /* receive */ DWORD recv; DWORD wsuccess = ReadFile(cookie->_handle, cookie->_buf, BUFSIZE, &recv, NULL); /* check error */ DWORD werr; if (!wsuccess) switch ((werr = GetLastError())) { case ERROR_DEV_NOT_EXIST: logr_error("Device has been disconnected!"); return (p7_error_nocalc); default: logr_fatal("Encountered error 0x%08lX", werr); return (p7_error_unknown); } /* get the current size to copy */ size_t tocopy = (size_t)recv; if (tocopy > size) tocopy = size; /* copy to destination */ memcpy(dest, cookie->_buf, tocopy); dest += tocopy; size -= tocopy; /* correct start and end points */ cookie->_start = tocopy; cookie->_end = (size_t)recv - 1; } /* no error */ return (0); } /** * p7_win_write: * Write to an MS-Windows stream. * * @arg vcookie the cookie (uncasted). * @arg data the source * @arg size the source size * @return the libp7 error (0 if ok). */ static int p7_win_write(void *vcookie, const unsigned char *data, size_t size) { win_cookie_t *cookie = (win_cookie_t*)vcookie; /* make the I/O request */ BOOL wsuccess = TRUE; do { /* write */ DWORD wrt; wsuccess = WriteFile(cookie->_handle, data, size, &wrt, NULL); if (!wsuccess) break; /* go forward */ data += wrt; size -= wrt; } while (size); /* check error */ DWORD werr; if (!wsuccess) switch ((werr = GetLastError())) { case ERROR_DEV_NOT_EXIST: logr_error("Device has been disconnected!"); return (p7_error_nocalc); default: logr_fatal("Encountered error 0x%08lx", werr); return (p7_error_unknown); } /* success! */ return (0); } /** * p7_win_setcomm: * Set communication status of a Microsoft Windows stream. * * @arg vcookie the cookie (uncasted). * @arg speed the speed. * @arg parity the parity. * @arg stopbits the stop bits. * @return the error (0 if ok) */ static int p7_win_setcomm(void *vcookie, int speed, int parity, int stopbits) { win_cookie_t *cookie = (win_cookie_t*)vcookie; /* get communication properties */ DCB dcb = {0}; if (!GetCommState(cookie->_handle, &dcb)) { logr_warn("Failed getting communication settings, nevermind."); return (0); } /* get the speed constant */ switch (speed) { case P7_B9600: default: speed = CBR_9600; break; case P7_B19200: speed = CBR_19200; break; } /* set the DCB thingies */ dcb = (DCB){ .DCBlength = sizeof(dcb), .BaudRate = speed, .fBinary = 1, .fParity = !!parity, .fDtrControl = DTR_CONTROL_ENABLE, .fRtsControl = RTS_CONTROL_ENABLE, .XonLim = 0x4000, .XoffLim = 0x1000, .ByteSize = 8, .Parity = !parity ? NOPARITY : (parity % 2) ? ODDPARITY : EVENPARITY, .StopBits = (stopbits == 1) ? ONESTOPBIT : TWOSTOPBITS, .XonChar = 17, /* DC1, Device Control 1 */ .XoffChar = 19, /* DC3, Device Control 3 */ }; /* save new state */ DWORD wsuccess = SetCommState(cookie->_handle, &dcb); #if LOGLEVEL <= ll_info if (!wsuccess) logr_warn("Failed setting communication settings, nevermind."); #endif return (0); } /** * p7_win_close: * Close a MS-Windows stream. * * @arg vcookie the cookie (uncasted). * @return the error code (0 if ok). */ static int p7_win_close(void *vcookie) { win_cookie_t *cookie = (win_cookie_t*)vcookie; CloseHandle(cookie->_handle); free(cookie); return (0); } /** * winit: * "Real" MS-Windows initialization function. * * Distinguished from the `p7_winit` function because of const/non-const and * dynamically allocated or not problems. * * @arg handle the handle to create. * @arg flags the flags. * @arg path the MS-Windows device path. * @return the error code (0 if ok). */ static int winit(p7_handle_t **handle, unsigned int flags, const char *path) { /* open the file handle - my god, this function is too complex. */ HANDLE fhandle = CreateFile(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); DWORD werr; if (fhandle == INVALID_HANDLE_VALUE) switch ((werr = GetLastError())) { case ERROR_FILE_NOT_FOUND: case ERROR_DEV_NOT_EXIST: return (p7_error_nocalc); case ERROR_ACCESS_DENIED: return (p7_error_noaccess); default: logr_fatal("Error 0x%08lx encountered.", werr); return (p7_error_unknown); } /* make cookie */ win_cookie_t *cookie = malloc(sizeof(win_cookie_t)); if (!cookie) { CloseHandle(handle); return (p7_error_alloc); } /* fill cookie */ *cookie = (win_cookie_t){ ._handle = fhandle, ._start = 0, ._end = -1 }; /* initialize for real */ return (p7_sinit(handle, flags, NULL, (p7_stream_t){ .cookie = cookie, .read = p7_win_read, .write = p7_win_write, .close = p7_win_close, .setcomm = p7_win_setcomm })); } /** * p7_winit: * Initialize libp7 using the Microsoft Windows API. * * @arg handle the handle to create. * @arg flags the flags. * @arg path the MS-Windows device path (NULL if we should find it). * @return the error code (0 if you're a boss). */ int p7_winit(p7_handle_t **handle, unsigned int flags, const char *path) { /* if there is a path, init with it */ if (path) return (winit(handle, flags, path)); logr_info("Let's try and find out a path!"); /* otherwise, find one - TODO: hardcode less */ char *p = wfind(0x07cf, 0x6101); if (!p) return (p7_error_nocalc); /* we found one! call init procedure with it, free it, and go */ int err = winit(handle, flags, p); free(p); return (err); } #endif