+#endif
+
+#ifdef _WIN32
+# define LIBUSBP_DLL_EXPORT __declspec(dllexport)
+# define LIBUSBP_DLL_IMPORT __declspec(dllimport)
+#else
+# define LIBUSBP_DLL_IMPORT __attribute__((visibility ("default")))
+# define LIBUSBP_DLL_EXPORT __attribute__((visibility ("default")))
+#endif
+
+#ifdef _MSC_VER
+#define LIBUSBP_WARN_UNUSED _Check_return_
+#else
+#define LIBUSBP_WARN_UNUSED __attribute__((warn_unused_result))
+#endif
+
+#ifdef LIBUSBP_STATIC
+# define LIBUSBP_API
+#else
+#error not static
+# ifdef LIBUSBP_EXPORTS
+# define LIBUSBP_API LIBUSBP_DLL_EXPORT
+# else
+# define LIBUSBP_API LIBUSBP_DLL_IMPORT
+# endif
+#endif
+
+/*! Some functions in this library return strings to the caller via a char **
+ * argument. If the function call was successful, it is the caller's
+ * responsibility to free those strings by passing them to this function.
+ * Passing the NULL pointer to this function is OK. Do not pass any strings to
+ * this function unless they were previously returned by a call to this
+ * library. Do not free the same non-NULL string twice. */
+LIBUSBP_API
+void libusbp_string_free(char *);
+
+
+/** libusbp_error **************************************************************/
+
+/*! A libusbp_error object represents an error that occurred in the library.
+ * Many functions return a libusbp_error pointer as a return value. The
+ * convention is that a NULL pointer indicates success. If the pointer is not
+ * NULL, the caller needs to free it at some point by calling
+ * libusbp_error_free().
+ *
+ * NULL is a valid value for a libusbp_error pointer, and can be passed to any
+ * function in this library that takes a libusbp_error pointer. */
+typedef struct libusbp_error
+ libusbp_error;
+
+/*! Each ::libusbp_error can have 0 or more error codes that give additional
+ * information about the error that might help the caller take the right action
+ * when the error occurs. This enum defines which error codes are possible. */
+enum libusbp_error_code
+{
+ /*! There were problems allocating memory. A memory shortage might be the
+ * root cause of the error, or there might be another error that is masked
+ * by the memory problems. */
+ LIBUSBP_ERROR_MEMORY = 1,
+
+ /*! It is possible that the error was caused by a temporary condition, such
+ * as the operating system taking some time to initialize drivers. Any
+ * function that could return this error will say so explicitly in its
+ * documentation so you do not have to worry about handling it in too many
+ * places. */
+ LIBUSBP_ERROR_NOT_READY = 2,
+
+ /*! Access was denied. A common cause of this error on Windows is that
+ * another application has a handle open to the same device. */
+ LIBUSBP_ERROR_ACCESS_DENIED = 3,
+
+ /*! The device does not have a serial number. */
+ LIBUSBP_ERROR_NO_SERIAL_NUMBER = 4,
+
+ /*! The device took too long to respond to a request or transfer data. */
+ LIBUSBP_ERROR_TIMEOUT = 5,
+
+ /*! The error might have been caused by the device being disconnected, but
+ * it is possible it was caused by something else. */
+ LIBUSBP_ERROR_DEVICE_DISCONNECTED = 6,
+
+ /*! The error might have been caused by the host receiving a STALL packet
+ * from the device, but it is possible it was caused by something else. */
+ LIBUSBP_ERROR_STALL = 7,
+
+ /*! The error might have been caused by the transfer getting cancelled from
+ * the host side. Some data might have been transferred anyway. */
+ LIBUSBP_ERROR_CANCELLED = 8,
+};
+
+/*! Attempts to copy an error. If you copy a NULL ::libusbp_error
+ * pointer, the result will also be NULL. If you copy a non-NULL ::libusbp_error
+ * pointer, the result will be non-NULL, but if there are issues allocating
+ * memory, then the copied error might have different properties than the
+ * original error, and it will have the ::LIBUSBP_ERROR_MEMORY code.
+ *
+ * It is the caller's responsibility to free the copied error. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_error_copy(const libusbp_error *);
+
+/*! Frees a returned error object. Passing the NULL pointer to this function is
+ * OK. Do not free the same non-NULL error twice. */
+LIBUSBP_API
+void libusbp_error_free(libusbp_error *);
+
+/*! Returns true if the error has specified error code. The error codes are
+ * listed in the ::libusbp_error_code enum. */
+LIBUSBP_API bool libusbp_error_has_code(const libusbp_error *, uint32_t code);
+
+/*! Returns an English-language ASCII-encoded string describing the error. The
+ * message consists of one or more sentences. If there are multiple sentences,
+ * the earlier ones will typically explain the context that the error happened
+ * in.
+ *
+ * The returned pointer will be valid until the error is freed, at which point
+ * it might become invalid. Do not pass the returned pointer to
+ * libusbp_string_free(). */
+LIBUSBP_API const char * libusbp_error_get_message(const libusbp_error *);
+
+
+/** libusbp_async_in_pipe ******************************************************/
+
+/*! A libusbp_async_in_pipe is an object that holds the memory and other data
+ * structures for a set of asynchronous USB requests to read data from a
+ * non-zero endpoint. It can be used to read data from a bulk or IN endpoint
+ * with high throughput. */
+typedef struct libusbp_async_in_pipe
+ libusbp_async_in_pipe;
+
+/*! Closes the pipe immediately. Note that if the pipe has any
+ * pending transfers, then it is possible that they cannot be freed
+ * by this function. Freeing a pipe with pending transfers could
+ * cause a memory leak, but is otherwise safe. */
+LIBUSBP_API
+void libusbp_async_in_pipe_close(libusbp_async_in_pipe *);
+
+/*! Allocates buffers and other data structures for performing multiple
+ * concurrent transfers on the pipe.
+ *
+ * The @a transfer_count parameter specifies how many transfers to allocate. You
+ * can also think of this as the maximum number of concurrent transfers that can
+ * be active at the same time.
+ *
+ * The @a transfer_size parameter specifies how large each transfer's buffer should
+ * be, and is also the number of bytes that will be requested from the operating
+ * system when the transfer is submitted.
+ *
+ * It is best to set the transfer size to a multiple of the maximum packet size
+ * of the endpoint. Otherwise, you might get an error when the device sends
+ * more data than can fit in the transfer's buffer. This type of error is
+ * called an overflow.
+ *
+ * If you want to be reading the pipe every millisecond without gaps, you should
+ * set the transfer count high enough so that it would take about 100 ms to 250
+ * ms to finish all the transfers. As long as the operating system runs your
+ * process that often, you should be able to keep the USB host controller busy
+ * permanently. (Though we have observed gaps in the transfers when trying to
+ * do this inside a VirtualBox machine.)
+ *
+ * You should not set the transfer count too high, or else it might end up
+ * taking a long time to cancel the transfers when you are closing the pipe. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_async_in_pipe_allocate_transfers(
+ libusbp_async_in_pipe *,
+ size_t transfer_count,
+ size_t transfer_size);
+
+/*! Starts reading data from the pipe. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_async_in_pipe_start_endless_transfers(
+ libusbp_async_in_pipe *);
+
+/*! Checks for new events, such as a transfer completing. This
+ * function and libusbp_async_in_pipe_handle_finished_transfer() should
+ * be called regularly in order to get data from the pipe. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_async_in_pipe_handle_events(libusbp_async_in_pipe *);
+
+/*! Retrieves a boolean saying whether there are any pending
+ * transfers. A pending transfer is a transfer that was submitted to
+ * the operating system, and it may have been completed, but it has
+ * not been passed to the caller yet via
+ * libusbp_async_in_pipe_handle_finished_transfer(). */
+LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error *
+libusbp_async_in_pipe_has_pending_transfers(
+ libusbp_async_in_pipe *,
+ bool * result);
+
+/*! Checks to see if there is a finished transfer that can be handled.
+ * If there is one, then this function retrieves the data from the
+ * transfer, the number of bytes transferred, and any error that might
+ * have occurred related to the transfer.
+ *
+ * @param finished An optional output pointer used to return a pointer that
+ * indicates whether a transfer was finished. If the returned value is false,
+ * then no transfer was finished, and there is no transfer_error or data to
+ * handle.
+ *
+ * @param buffer An optional output pointer used to return the data
+ * from the transfer. The buffer must be at least as large as the
+ * transfer size specifed when
+ * libusbp_async_in_pipe_allocate_transfers was called.
+ *
+ * @param transferred An optional output pointer used to return the
+ * number of bytes transferred.
+ *
+ * @param transfer_error An optional pointer used to return an error
+ * related to the transfer, such as a timeout or a cancellation. If
+ * this pointer is provided, and a non-NULL error is returned via it,
+ * then the error must later be freed with libusbp_error_free(). There
+ * will never be a non-NULL transfer error if there is a regular error
+ * returned as the return value of this function. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error *
+libusbp_async_in_pipe_handle_finished_transfer(
+ libusbp_async_in_pipe *,
+ bool * finished,
+ void * buffer,
+ size_t * transferred,
+ libusbp_error ** transfer_error);
+
+/*! Cancels all the transfers for this pipe. The cancellation is
+ * asynchronous, so it won't have an immediate effect. If you want
+ * to actually make sure that all the transfers get cancelled, you
+ * will need to call libusbp_async_in_pipe_handle_events() and
+ * libusbp_async_in_pipe_handle_finished_transfer() repeatedly until
+ * libusbp_async_in_pipe_has_pending_transfers() indicates there are
+ * no pending transfers left. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_async_in_pipe_cancel_transfers(libusbp_async_in_pipe *);
+
+
+/** libusbp_device *************************************************************/
+
+/*! Represents a single USB device. A composite device with multiple functions
+ * is represented by a single libusbp_device object.
+ *
+ * A NULL libusbp_device pointer is valid and can be passed to any function in
+ * this library that takes such pointers. */
+typedef struct libusbp_device
+ libusbp_device;
+
+/*! Finds all the USB devices connected to the computer and returns a list of them.
+ *
+ * The optional @a device_count parameter is used to return the number of
+ * devices in the list. The list is actually one element larger because it ends
+ * with a NULL pointer.
+ *
+ * If this function is successful (the returned error pointer is NULL), then you
+ * must later free each device by calling libusbp_device_free() and free the
+ * list by calling libusbp_list_free(). The order in which the retrieved
+ * objects are freed does not matter. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_list_connected_devices(
+ libusbp_device *** device_list,
+ size_t * device_count);
+
+/*! Frees a device list returned by libusbp_list_connected_device(). */
+LIBUSBP_API
+void libusbp_list_free(libusbp_device ** list);
+
+/*! Finds a device with the specified vendor ID and product ID and returns a
+ * pointer to it. If no device can be found, returns a NULL pointer. If the
+ * retrieved device pointer is not NULL, you must free it later by calling
+ * libusbp_device_free(). The retrieved device pointer will always be NULL if
+ * an error is returned. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_find_device_with_vid_pid(
+ uint16_t vendor_id,
+ uint16_t product_id,
+ libusbp_device ** device);
+
+/*! Makes a copy of a device object. If this function is successful, you will
+ * need to free the copy by calling libusbp_device_free() at some point. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_device_copy(
+ const libusbp_device * source,
+ libusbp_device ** dest);
+
+/*! Frees a device object. Passing a NULL pointer to this function is OK. Do
+ * not free the same non-NULL device twice. */
+LIBUSBP_API void libusbp_device_free(libusbp_device *);
+
+/*! Gets the USB vendor ID of the device (idVendor). */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_device_get_vendor_id(
+ const libusbp_device *,
+ uint16_t * vendor_id);
+
+/*! Gets the USB product ID of the device (idProduct). */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_device_get_product_id(
+ const libusbp_device *,
+ uint16_t * product_id);
+
+/*! Gets the USB revision code of the device (bcdDevice). */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_device_get_revision(
+ const libusbp_device *,
+ uint16_t * revision);
+
+/*! Gets the serial number of the device as an ASCII-encoded string.
+ *
+ * On Windows, this just returns the segment of the Device Instance ID after the
+ * last slash. If the device does not have a serial number, it will return some
+ * other type of identifier that contains andpersands (&). Windows ignores
+ * serial numbers with invalid characters in them. For more information, see:
+ *
+ * https://msdn.microsoft.com/en-us/library/windows/hardware/dn423379#usbsn
+ *
+ * On other systems, if the device does not have a serial number, then this
+ * function returns an error with the code ::LIBUSBP_ERROR_NO_SERIAL_NUMBER.
+ *
+ * (Most applications should only call this function on specific USB devices
+ * that are already known to have serial numbers, in which case the lack of a
+ * serial number really does indicate a failure.)
+ *
+ * You should free the returned string by calling libusbp_string_free(). */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_device_get_serial_number(
+ const libusbp_device *,
+ char ** serial_number);
+
+/*! Gets an operating system-specific string that identifies the device.
+ *
+ * Note that the level of specificity provided by the ID depends on the system
+ * you are on, and whether your device has a USB serial number. As long as the
+ * device remains connected to the bus, this ID is not expected to change and
+ * there should be no other devices that have the same ID. However, if the
+ * device gets disconnected from the bus, it may be possible for the ID to be
+ * reused by another device.
+ *
+ * @b Windows: This will be a device instance ID, and it will look something
+ * like this:
+ *
+ *
+ * USB\\VID_1FFB&PID_DA01\6&11A23516&18&0000
+ *
+ *
+ * If your device has a serial number, the part after the slash will be the
+ * serial number. Otherwise, it will be a string with andpersands in it.
+ *
+ * @b Linux: This will be a sysfs path, and it will look like something like
+ * this:
+ *
+ *
+ * /sys/devices/pci0000:00/0000:00:06.0/usb1/1-2
+ *
+ *
+ * macOS: This will be an integer from
+ * IORegistryEntryGetRegistryEntryID, formatted as a lower-case hex number with
+ * no leading zeros. It will look something like this:
+ *
+ *
+ * 10000021a
+ *
+ */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_device_get_os_id(
+ const libusbp_device *,
+ char ** id);
+
+
+/** libusbp_generic_interface **************************************************/
+
+/*! Represents a generic or vendor-defined interface of a USB device. A null
+ * libusbp_generic_interface pointer is valid and can be passed to any function
+ * in this library that takes such pointers. */
+typedef struct libusbp_generic_interface
+ libusbp_generic_interface;
+
+/*! Creates a generic interface object for a specified interface of the
+ * specified USB device. This function does as many checks as possible to make
+ * sure that a handle to the interface could be opened, without actually opening
+ * it yet.
+ *
+ * On all platforms, if a record of the interface cannot be found, then an error
+ * is returned with the code LIBUSBP_ERROR_NOT_READY, because this could just be
+ * a temporary condition that happens right after the device is plugged in.
+ *
+ * On Windows, the generic interface must use the WinUSB driver, or this
+ * function will fail. If it is using no driver, that could be a temporary
+ * condition, and the error returned will use the LIBUSBP_ERROR_NOT_READY error
+ * code.
+ *
+ * On Linux, if the corresponding devnode file does not exist, an error with
+ * code LIBUSBP_ERROR_NOT_READY is returned. If the interface is assigned to a
+ * driver that is not "usbfs", an error is returned.
+ *
+ * On macOS, we do not have any additional checks beyond just making sure
+ * that an entry for the interface is found. For non-composite devices, that
+ * check is deferred until a handle is opened.
+ *
+ * @param interface_number The lowest @a bInterfaceNumber for the interfaces in
+ * the USB function you want to use.
+ *
+ * @param composite Should be true if the device is composite, and false
+ * otherwise.
+ *
+ * The returned object must be freed with libusbp_generic_interface_free(). */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_generic_interface_create(
+ const libusbp_device *,
+ uint8_t interface_number,
+ bool composite,
+ libusbp_generic_interface **);
+
+/*! Frees the specified generic interface object. Passing the NULL pointer to
+ * this function is OK. Do not free the same non-NULL pointer twice. */
+LIBUSBP_API void libusbp_generic_interface_free(libusbp_generic_interface *);
+
+/*! Makes a copy of the generic interface object. The copy must be freed with
+ * libusbp_generic_interface_free(). */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_generic_interface_copy(
+ const libusbp_generic_interface * source,
+ libusbp_generic_interface ** dest);
+
+/*! Returns an operating system-specific string that can be used to uniquely
+ * identify this generic interface.
+ *
+ * Windows: This will be a device instance ID specific to this interface, and
+ * it will look something like this:
+ *
+ *
+ * USB\\VID_1FFB&PID_DA01&MI_00\6&11A23516&18&0000
+ *
+ *
+ * Linux: This will be a sysfs path specific to this interface, and it will
+ * look like something like this:
+ *
+ *
+ * /sys/devices/pci0000:00/0000:00:06.0/usb1/1-2/1-2:1.0
+ *
+ *
+ * macOS: This will be an integer from
+ * IORegistryEntryGetRegistryEntryID, formatted as a lower-case hex number with
+ * no leading zeros. It will look something like this:
+ *
+ *
+ * 10000021a
+ *
+ */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_generic_interface_get_os_id(
+ const libusbp_generic_interface *,
+ char ** id);
+
+/*! Returns an operating system-specific filename corresponding to this
+ * interface.
+ *
+ * Windows: This will be the name of a file you can use with CreateFile to
+ * access the device, and it will look something like this:
+ *
+ *
+ * \\\\?\\usb#vid_1ffb&pid_da01&mi_00#6&11a23516&18&0000#{99c4bbb0-e925-4397-afee-981cd0702163}
+ *
+ *
+ * Linux: this will return a device node file name that represents the
+ * overall USB device. It will look something like:
+ *
+ *
+ * /dev/bus/usb/001/007
+ *
+ *
+ * macOS: This will be an integer from
+ * IORegistryEntryGetRegistryEntryID, formatted as a lower-case hex number with
+ * no leading zeros. It will look something like this:
+ *
+ *
+ * 10000021a
+ *
+ */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_generic_interface_get_os_filename(
+ const libusbp_generic_interface *,
+ char ** filename);
+
+
+/** libusbp_generic_handle *****************************************************/
+
+/*! Represents a generic handle to a USB device. This handle can be used to
+ * perform operations such as control transfers and reading and writing data
+ * from non-zero endpoints.
+ *
+ * NULL is a valid value for a libusbp_generic_handle pointer, and can be passed
+ * in any functions of this library that take a libusbp_generic_handle
+ * pointer. */
+typedef struct libusbp_generic_handle
+ libusbp_generic_handle;
+
+/*! Opens a generic handle to the specified interface of a USB device which can
+ * be used to perform USB I/O operations.
+ *
+ * The handle must later be closed with libusbp_generic_handle_close().
+ *
+ * On Windows, for devices using WinUSB, if another application has a handle
+ * open already when this function is called, then this function will fail and
+ * the returned error will have code ::LIBUSBP_ERROR_ACCESS_DENIED.
+ *
+ * On macOS, this function will set the device's configuration to 1 as a
+ * side effect in case it is not already configured. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_generic_handle_open(
+ const libusbp_generic_interface *,
+ libusbp_generic_handle **);
+
+/*! Closes and frees the specified generic handle. It is OK to pass NULL to
+ * this function. Do not close the same non-NULL handle twice. All
+ * ::libusbp_async_in_pipe objects created by the handle must be closed before
+ * closing the handle. */
+LIBUSBP_API
+void libusbp_generic_handle_close(
+ libusbp_generic_handle *);
+
+/*! Creates a new asynchronous pipe object for reading data in from the device
+ * on one of its bulk or interrupt IN endpoints.
+ *
+ * The behavior of this library is unspecified if you use both an asynchronous
+ * IN pipe and synchronous reads with libusbp_read_pipe() on the same pipe of
+ * the same generic handle. One reason for that is because for WinUSB devices,
+ * this function enables RAW_IO for the pipe, and it does not turn off RAW_IO
+ * again after the pipe is closed. So the behavior of libusbp_read_pipe() could
+ * change depending on whether an asynchronous IN pipe has been used. */
+LIBUSBP_API
+libusbp_error * libusbp_generic_handle_open_async_in_pipe(
+ libusbp_generic_handle *,
+ uint8_t pipe_id,
+ libusbp_async_in_pipe ** async_in_pipe);
+
+/*! Sets a timeout for a particular pipe on the USB device.
+ *
+ * The @a pipe_id should either be 0 to specify control transfers on endpoint 0, or
+ * should be a bEndpointAddress value from one of the device's endpoint
+ * descriptors. Specifying an invalid pipe might result in an error.
+ *
+ * The timeout value is specified in milliseconds, and a value of 0 means no
+ * timeout (wait forever). If this function is not called, the default behavior
+ * of the handle is to have no timeout.
+ *
+ * The behavior of this function is unspecified if there is any data being
+ * transferred on the pipe while this function is running.
+ *
+ * It is unspecified whether this timeout has an effect on asynchronous
+ * transfers. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_generic_handle_set_timeout(
+ libusbp_generic_handle *,
+ uint8_t pipe_id,
+ uint32_t timeout);
+
+/*! Performs a synchronous (blocking) control transfer on endpoint 0.
+ *
+ * Under Linux, this blocking transfer unfortunately cannot be interrupted with
+ * Ctrl+C.
+ *
+ * The @a buffer parameter should point to a buffer that is at least @a wLength
+ * bytes long.
+ *
+ * The @a transferred pointer is optional, and is used to return the number of
+ * bytes that were actually transferred.
+ *
+ * The direction of the transfer is determined by the @a bmRequestType parameter.
+ */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_control_transfer(
+ libusbp_generic_handle *,
+ uint8_t bmRequestType,
+ uint8_t bRequest,
+ uint16_t wValue,
+ uint16_t wIndex,
+ void * buffer,
+ uint16_t wLength,
+ size_t * transferred);
+
+/*! Performs a synchronous (blocking) write of data to a bulk or interrupt
+ * endpoint.
+ *
+ * Under Linux, this blocking transfer unfortunately cannot be interrupted with
+ * Ctrl+C.
+ *
+ * The @a pipe_id parameter specifies which endpoint to use. This argument
+ * should be bEndpointAddress value from one of the device's IN endpoint
+ * descriptors. (Its most significant bit must be 0.)
+ *
+ * The @a transferred parameter is an optional pointer to a variable that will
+ * receive the number of bytes transferred. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_write_pipe(
+ libusbp_generic_handle *,
+ uint8_t pipe_id,
+ const void * buffer,
+ size_t size,
+ size_t * transferred);
+
+/*! Performs a synchronous (blocking) read of data from a bulk or interrupt
+ * endpoint.
+ *
+ * It is best to set the buffer size to a multiple of the maximum
+ * packet size of the endpoint. Otherwise, this function might return
+ * an error when the device sends more data than can fit in the
+ * buffer. This type of error is called an overflow.
+ *
+ * Under Linux, this blocking transfer unfortunately cannot be interrupted with
+ * Ctrl+C.
+ *
+ * The @a pipe_id parameter specifies which endpoint to use. This argument
+ * should be bEndpointAddress value from one of the device's IN endpoint
+ * descriptors. (Its most significant bit must be 1.)
+ *
+ * The @a transferred parameter is an optional pointer to a variable that will
+ * receive the number of bytes transferred. */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_read_pipe(
+ libusbp_generic_handle *,
+ uint8_t pipe_id,
+ void * buffer,
+ size_t size,
+ size_t * transferred);
+
+#ifdef __linux__
+/*! Gets the underlying file descriptor of the generic handle. This function is
+ * only available on Linux, and is intended for advanced users. The returned
+ * file descriptor will remain open and valid as long as the handle is open and
+ * has not been closed. */
+LIBUSBP_API
+int libusbp_generic_handle_get_fd(libusbp_generic_handle *);
+#endif
+
+#ifdef _WIN32
+/*! Gets the underlying WinUSB handle for the generic handle. This function is
+ * only available on Windows, and is intended for advanced users. The returned
+ * WinUSB handle will remain open and valid as long as the generic handle is
+ * open and has not been closed. */
+LIBUSBP_API
+HANDLE libusbp_generic_handle_get_winusb_handle(libusbp_generic_handle *);
+#endif
+
+#ifdef __APPLE__
+/*! Gets the underlying IOCFPlugInInterface object representing the interface.
+ * You can cast the returned pointer to a `IOCFPlugInInterface **` and then use
+ * `QueryInterface` to get the corresponding `IOUSBInterfaceInterface **`.
+ * There is an example of this in generic_handle_test.cpp. */
+LIBUSBP_API
+void ** libusbp_generic_handle_get_cf_plug_in(libusbp_generic_handle *);
+#endif
+
+
+/** libusbp_serial_port ********************************************************/
+
+/*! Represents a serial port. A null libusbp_serial_port pointer is valid and
+ * can be passed to any function in this library that takes such pointers. */
+typedef struct libusbp_serial_port
+ libusbp_serial_port;
+
+/*! Creates a serial port object for a specified interface of the
+ * specified USB device.
+ *
+ * On all platforms, if a record of the interface cannot be found, then an error
+ * is returned with the code LIBUSBP_ERROR_NOT_READY, because this could just be
+ * a temporary condition that happens right after the device is plugged in.
+ *
+ * On macOS, it is assumed that the interface with a @a bInterfaceNumber one
+ * greater than @a interface_number is the interface that the IOSerialBSDClient
+ * will attach to. This should be true if the device implements the USB CDC ACM
+ * class and has ordered its interfaces so that the control interface is right
+ * before the data interface.
+ *
+ * @param interface_number The lowest @a bInterfaceNumber for the USB interfaces
+ * that comprise the serial port.
+ *
+ * @param composite Should be true if the device is composite, and false
+ * otherwise.
+ *
+ * The returned object must be freed with libusbp_generic_interface_free(). */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_serial_port_create(
+ const libusbp_device *,
+ uint8_t interface_number,
+ bool composite,
+ libusbp_serial_port **);
+
+/*! Frees the specified serial port object. Passing the NULL pointer to
+ * this function is OK. Do not free the same non-NULL pointer twice. */
+LIBUSBP_API void libusbp_serial_port_free(libusbp_serial_port *);
+
+/*! Makes a copy of the generic interface object. The copy must be freed with
+ * libusbp_generic_interface_free(). */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_serial_port_copy(
+ const libusbp_serial_port * source,
+ libusbp_serial_port ** dest);
+
+/*! Gets the user-friendly name of the COM port
+ * that could be used to open a handle.
+ *
+ * On Windows, this will be something like "COM12".
+ *
+ * On Linux, it will be something like "/dev/ttyACM0".
+ *
+ * On macOS, it will be something like "/dev/cu.usbmodem012345".
+ * Specifically, it will be a call-out device, not a dial-in device.
+ *
+ * You should free the returned string by calling libusbp_string_free().
+ */
+LIBUSBP_API LIBUSBP_WARN_UNUSED
+libusbp_error * libusbp_serial_port_get_name(
+ const libusbp_serial_port *,
+ char ** name);
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/dep/libusbp/include/libusbp.hpp b/dep/libusbp/include/libusbp.hpp
new file mode 100644
index 00000000..88c26d1a
--- /dev/null
+++ b/dep/libusbp/include/libusbp.hpp
@@ -0,0 +1,569 @@
+// Copyright (C) Pololu Corporation. See www.pololu.com for details.
+
+/*! \file libusbp.hpp
+ *
+ * This header files provides the C++ API for libusbp. The classes and
+ * functions here are just thin wrappers around the C API, so you should see
+ * libusbp.h for full documentation. */
+
+#pragma once
+
+#include "libusbp.h"
+#include
+#include
+#include
+#include
+#include
+
+/** Display a nice error if C++11 is not enabled (e.g. --std=c++11 or --std=gnu++11).
+ * The __GXX_EXPERIMENTAL_CXX0X__ check is needed for GCC 4.6, which defines __cplusplus as 1.
+ * The _MSC_VER check is needed for Visual Studio 2015. */
+#if (!defined(__cplusplus) || (__cplusplus < 201103L)) && !defined(__GXX_EXPERIMENTAL_CXX0X__) && !defined(_MSC_VER)
+#error This header requires features from C++11.
+#endif
+
+namespace libusbp
+{
+ /*! \cond */
+ inline void throw_if_needed(libusbp_error * err);
+ /*! \endcond */
+
+ /*! Wrapper for libusbp_error_free(). */
+ inline void pointer_free(libusbp_error * p) noexcept
+ {
+ libusbp_error_free(p);
+ }
+
+ /*! Wrapper for libusbp_error_copy(). */
+ inline libusbp_error * pointer_copy(libusbp_error * p) noexcept
+ {
+ return libusbp_error_copy(p);
+ }
+
+ /*! Wrapper for libusbp_async_in_pipe_close(). */
+ inline void pointer_free(libusbp_async_in_pipe * p) noexcept
+ {
+ libusbp_async_in_pipe_close(p);
+ }
+
+ /*! Wrapper for libusbp_device_free(). */
+ inline void pointer_free(libusbp_device * p) noexcept
+ {
+ libusbp_device_free(p);
+ }
+
+ /*! Wrapper for libusbp_device_copy(). */
+ inline libusbp_device * pointer_copy(libusbp_device * pointer)
+ {
+ libusbp_device * copy;
+ throw_if_needed(libusbp_device_copy(pointer, ©));
+ return copy;
+ }
+
+ /*! Wrapper for libusbp_generic_interface_free(). */
+ inline void pointer_free(libusbp_generic_interface * p) noexcept
+ {
+ libusbp_generic_interface_free(p);
+ }
+
+ /*! Wrapper for libusbp_generic_interface_copy(). */
+ inline libusbp_generic_interface * pointer_copy(libusbp_generic_interface * pointer)
+ {
+ libusbp_generic_interface * copy;
+ throw_if_needed(libusbp_generic_interface_copy(pointer, ©));
+ return copy;
+ }
+
+ /*! Wrapper for libusbp_generic_handle_free(). */
+ inline void pointer_free(libusbp_generic_handle * p) noexcept
+ {
+ libusbp_generic_handle_close(p);
+ }
+
+ /*! Wrapper for libusbp_serial_port_copy(). */
+ inline libusbp_serial_port * pointer_copy(libusbp_serial_port * pointer)
+ {
+ libusbp_serial_port * copy;
+ throw_if_needed(libusbp_serial_port_copy(pointer, ©));
+ return copy;
+ }
+
+ /*! Wrapper for libusbp_serial_port_free(). */
+ inline void pointer_free(libusbp_serial_port * p) noexcept
+ {
+ libusbp_serial_port_free(p);
+ }
+
+ /*! This class is not part of the public API of the library and you should
+ * not use it directly, but you can use the public methods it provides to
+ * the classes that inherit from it.
+ *
+ * For any type T, if you define pointer_free(T *), then
+ * unique_pointer_wrapper will be a well-behaved C++ class that provides
+ * a constructor, implicit conversion to a bool, C++ move operations,
+ * pointer operations, and forbids C++ copy operations. */
+ template
+ class unique_pointer_wrapper
+ {
+ public:
+ /*! Constructor that takes a pointer. */
+ explicit unique_pointer_wrapper(T * p = nullptr) noexcept
+ : pointer(p)
+ {
+ }
+
+ /*! Move constructor. */
+ unique_pointer_wrapper(unique_pointer_wrapper && other) noexcept
+ {
+ pointer = other.pointer_release();
+ }
+
+ /*! Move assignment operator. */
+ unique_pointer_wrapper & operator=(unique_pointer_wrapper && other) noexcept
+ {
+ pointer_reset(other.pointer_release());
+ return *this;
+ }
+
+ /*! Destructor. */
+ ~unique_pointer_wrapper() noexcept
+ {
+ pointer_reset();
+ }
+
+ /*! Implicit conversion to bool. Returns true if the underlying pointer
+ * is not NULL. */
+ explicit operator bool() const noexcept
+ {
+ return pointer != nullptr;
+ }
+
+ /*! Returns the underlying pointer. */
+ T * pointer_get() const noexcept
+ {
+ return pointer;
+ }
+
+ /*! Sets the underlying pointer to the specified value, freeing the
+ * previous pointer and taking ownership of the specified one. */
+ void pointer_reset(T * p = nullptr) noexcept
+ {
+ pointer_free(pointer);
+ pointer = p;
+ }
+
+ /*! Releases the pointer, transferring ownership of it to the caller and
+ * resetting the underlying pointer of this object to nullptr. The caller
+ * is responsible for freeing the returned pointer if it is not NULL. */
+ T * pointer_release() noexcept
+ {
+ T * p = pointer;
+ pointer = nullptr;
+ return p;
+ }
+
+ /*! Returns a pointer to the underlying pointer. */
+ T ** pointer_to_pointer_get() noexcept
+ {
+ return &pointer;
+ }
+
+ /*! Copy constructor: forbid. */
+ unique_pointer_wrapper(const unique_pointer_wrapper & other) = delete;
+
+ /*! Copy assignment operator: forbid. */
+ unique_pointer_wrapper & operator=(const unique_pointer_wrapper & other) = delete;
+
+ protected:
+ /*! The underlying pointer that is being wrapped. This pointer will be
+ * freed when the object is destroyed. */
+ T * pointer;
+ };
+
+ /*! This class is not part of the public API of the library and you should
+ * not use it directly, but you can use the public methods it provides to
+ * the classes that inherit from it.
+ *
+ * For any type T, if you define pointer_free(T *) and pointer_copy(T *), then
+ * unique_pointer_wrapper_with_copy will be a well-behaved C++ class that provides
+ * a constructor, implicit conversion to a bool, C++ move operations, C++ copy operations,
+ * and pointer operations. */
+ template
+ class unique_pointer_wrapper_with_copy : public unique_pointer_wrapper
+ {
+ public:
+ /*! Constructor that takes a pointer. */
+ explicit unique_pointer_wrapper_with_copy(T * p = nullptr) noexcept
+ : unique_pointer_wrapper(p)
+ {
+ }
+
+ /*! Move constructor. */
+ unique_pointer_wrapper_with_copy(
+ unique_pointer_wrapper_with_copy && other) noexcept = default;
+
+ /*! Copy constructor */
+ unique_pointer_wrapper_with_copy(
+ const unique_pointer_wrapper_with_copy & other)
+ : unique_pointer_wrapper()
+ {
+ this->pointer = pointer_copy(other.pointer);
+ }
+
+ /*! Copy assignment operator. */
+ unique_pointer_wrapper_with_copy & operator=(
+ const unique_pointer_wrapper_with_copy & other)
+ {
+ this->pointer_reset(pointer_copy(other.pointer));
+ return *this;
+ }
+
+ /*! Move assignment operator. */
+ unique_pointer_wrapper_with_copy & operator=(
+ unique_pointer_wrapper_with_copy && other) = default;
+ };
+
+ /*! Wrapper for a ::libusbp_error pointer. */
+ class error : public unique_pointer_wrapper_with_copy, public std::exception
+ {
+ public:
+ /*! Constructor that takes a pointer. */
+ explicit error(libusbp_error * p = nullptr) noexcept
+ : unique_pointer_wrapper_with_copy(p)
+ {
+ }
+
+ /*! Wrapper for libusbp_error_get_message(). */
+ const char * what() const noexcept override {
+ return libusbp_error_get_message(pointer);
+ }
+
+ /*! Wrapper for libusbp_error_get_message() that returns a
+ * std::string. */
+ std::string message() const
+ {
+ return what();
+ }
+
+ /*! Wrapper for libusbp_error_has_code(). */
+ bool has_code(uint32_t error_code) const noexcept
+ {
+ return libusbp_error_has_code(pointer, error_code);
+ }
+ };
+
+ /*! \cond */
+ inline void throw_if_needed(libusbp_error * err)
+ {
+ if (err != nullptr)
+ {
+ throw error(err);
+ }
+ }
+ /*! \endcond */
+
+ /*! Wrapper for a ::libusbp_async_in_pipe pointer. */
+ class async_in_pipe : public unique_pointer_wrapper
+ {
+ public:
+ /*! Constructor that takes a pointer. */
+ explicit async_in_pipe(libusbp_async_in_pipe * pointer = nullptr)
+ : unique_pointer_wrapper(pointer)
+ {
+ }
+
+ /*! Wrapper for libusbp_async_in_pipe_allocate_transfers(). */
+ void allocate_transfers(size_t transfer_count, size_t transfer_size)
+ {
+ throw_if_needed(libusbp_async_in_pipe_allocate_transfers(
+ pointer, transfer_count, transfer_size));
+ }
+
+ /*! Wrapper for libusbp_async_in_pipe_start_endless_transfers(). */
+ void start_endless_transfers()
+ {
+ throw_if_needed(libusbp_async_in_pipe_start_endless_transfers(pointer));
+ }
+
+ /*! Wrapper for libusbp_async_in_pipe_handle_events(). */
+ void handle_events()
+ {
+ throw_if_needed(libusbp_async_in_pipe_handle_events(pointer));
+ }
+
+ /*! Wrapper for libusbp_async_in_pipe_has_pending_transfers(). */
+ bool has_pending_transfers()
+ {
+ bool result;
+ throw_if_needed(libusbp_async_in_pipe_has_pending_transfers(pointer, &result));
+ return result;
+ }
+
+ /*! Wrapper for libusbp_async_in_pipe_handle_finished_transfer(). */
+ bool handle_finished_transfer(void * buffer, size_t * transferred,
+ error * transfer_error)
+ {
+ libusbp_error ** error_out = nullptr;
+ if (transfer_error != nullptr)
+ {
+ transfer_error->pointer_reset();
+ error_out = transfer_error->pointer_to_pointer_get();
+ }
+
+ bool finished;
+ throw_if_needed(libusbp_async_in_pipe_handle_finished_transfer(
+ pointer, &finished, buffer, transferred, error_out));
+ return finished;
+ }
+
+ /*! Wrapper for libusbp_async_in_pipe_cancel_transfers(). */
+ void cancel_transfers()
+ {
+ throw_if_needed(libusbp_async_in_pipe_cancel_transfers(pointer));
+ }
+ };
+
+ /*! Wrapper for a ::libusbp_device pointer. */
+ class device : public unique_pointer_wrapper_with_copy
+ {
+ public:
+ /*! Constructor that takes a pointer. */
+ explicit device(libusbp_device * pointer = nullptr) :
+ unique_pointer_wrapper_with_copy(pointer)
+ {
+ }
+
+ /*! Wrapper for libusbp_device_get_vendor_id(). */
+ uint16_t get_vendor_id() const
+ {
+ uint16_t id;
+ throw_if_needed(libusbp_device_get_vendor_id(pointer, &id));
+ return id;
+ }
+
+ /*! Wrapper for libusbp_device_get_product_id(). */
+ uint16_t get_product_id() const
+ {
+ uint16_t id;
+ throw_if_needed(libusbp_device_get_product_id(pointer, &id));
+ return id;
+ }
+
+ /*! Wrapper for libusbp_device_get_revision(). */
+ uint16_t get_revision() const
+ {
+ uint16_t r;
+ throw_if_needed(libusbp_device_get_revision(pointer, &r));
+ return r;
+ }
+
+ /*! Wrapper for libusbp_device_get_serial_number(). */
+ std::string get_serial_number() const
+ {
+ char * str;
+ throw_if_needed(libusbp_device_get_serial_number(pointer, &str));
+ std::string serial_number = str;
+ libusbp_string_free(str);
+ return serial_number;
+ }
+
+ /*! Wrapper for libusbp_device_get_os_id(). */
+ std::string get_os_id() const
+ {
+ char * str;
+ throw_if_needed(libusbp_device_get_os_id(pointer, &str));
+ std::string serial_number = str;
+ libusbp_string_free(str);
+ return serial_number;
+ }
+ };
+
+ /*! Wrapper for libusbp_list_connected_devices(). */
+ inline std::vector list_connected_devices()
+ {
+ libusbp_device ** device_list;
+ size_t size;
+ throw_if_needed(libusbp_list_connected_devices(&device_list, &size));
+ std::vector vector;
+ for(size_t i = 0; i < size; i++)
+ {
+ vector.emplace_back(device_list[i]);
+ }
+ libusbp_list_free(device_list);
+ return vector;
+ }
+
+ /*! Wrapper for libusbp_find_device_with_vid_pid(). */
+ inline libusbp::device find_device_with_vid_pid(uint16_t vendor_id, uint16_t product_id)
+ {
+ libusbp_device * device_pointer;
+ throw_if_needed(libusbp_find_device_with_vid_pid(
+ vendor_id, product_id, &device_pointer));
+ return device(device_pointer);
+ }
+
+ /*! Wrapper for a ::libusbp_generic_interface pointer. */
+ class generic_interface : public unique_pointer_wrapper_with_copy
+ {
+ public:
+ /*! Constructor that takes a pointer. This object will free the pointer
+ * when it is destroyed. */
+ explicit generic_interface(libusbp_generic_interface * pointer = nullptr)
+ : unique_pointer_wrapper_with_copy(pointer)
+ {
+ }
+
+ /*! Wrapper for libusbp_generic_interface_create. */
+ explicit generic_interface(const device & device,
+ uint8_t interface_number = 0, bool composite = false)
+ {
+ throw_if_needed(libusbp_generic_interface_create(
+ device.pointer_get(), interface_number, composite, &pointer));
+ }
+
+ /*! Wrapper for libusbp_generic_interface_get_os_id(). */
+ std::string get_os_id() const
+ {
+ char * str;
+ throw_if_needed(libusbp_generic_interface_get_os_id(pointer, &str));
+ std::string id = str;
+ libusbp_string_free(str);
+ return id;
+ }
+
+ /*! Wrapper for libusbp_generic_interface_get_os_filename(). */
+ std::string get_os_filename() const
+ {
+ char * str;
+ throw_if_needed(libusbp_generic_interface_get_os_filename(pointer, &str));
+ std::string filename = str;
+ libusbp_string_free(str);
+ return filename;
+ }
+ };
+
+ /*! Wrapper for a ::libusbp_generic_handle pointer. */
+ class generic_handle : public unique_pointer_wrapper
+ {
+ public:
+ /*! Constructor that takes a pointer. This object will free the pointer
+ * when it is destroyed. */
+ explicit generic_handle(libusbp_generic_handle * pointer = nullptr) noexcept
+ : unique_pointer_wrapper(pointer)
+ {
+ }
+
+ /*! Wrapper for libusbp_generic_handle_open(). */
+ explicit generic_handle(const generic_interface & gi)
+ {
+ throw_if_needed(libusbp_generic_handle_open(gi.pointer_get(), &pointer));
+ }
+
+ /*! Wrapper for libusbp_generic_handle_close(). */
+ void close() noexcept
+ {
+ pointer_reset();
+ }
+
+ /*! Wrapper for libusbp_generic_handle_open_async_in_pipe(). */
+ async_in_pipe open_async_in_pipe(uint8_t pipe_id)
+ {
+ libusbp_async_in_pipe * pipe;
+ throw_if_needed(libusbp_generic_handle_open_async_in_pipe(
+ pointer, pipe_id, &pipe));
+ return async_in_pipe(pipe);
+ }
+
+ /*! Wrapper for libusbp_generic_handle_set_timeout(). */
+ void set_timeout(uint8_t pipe_id, uint32_t timeout)
+ {
+ throw_if_needed(libusbp_generic_handle_set_timeout(pointer, pipe_id, timeout));
+ }
+
+ /*! Wrapper for libusbp_control_transfer(). */
+ void control_transfer(
+ uint8_t bmRequestType,
+ uint8_t bRequest,
+ uint16_t wValue,
+ uint16_t wIndex,
+ void * buffer = nullptr,
+ uint16_t wLength = 0,
+ size_t * transferred = nullptr)
+ {
+ throw_if_needed(libusbp_control_transfer(pointer,
+ bmRequestType, bRequest, wValue, wIndex,
+ buffer, wLength, transferred));
+ }
+
+ /*! Wrapper for libusbp_write_pipe(). */
+ void write_pipe(uint8_t pipe_id, const void * buffer,
+ size_t size, size_t * transferred)
+ {
+ throw_if_needed(libusbp_write_pipe(pointer,
+ pipe_id, buffer, size, transferred));
+ }
+
+ /*! Wrapper for libusbp_read_pipe(). */
+ void read_pipe(uint8_t pipe_id, void * buffer,
+ size_t size, size_t * transferred)
+ {
+ throw_if_needed(libusbp_read_pipe(pointer,
+ pipe_id, buffer, size, transferred));
+ }
+
+ #ifdef _WIN32
+ /*! Wrapper for libusbp_generic_handle_get_winusb_handle(). */
+ HANDLE get_winusb_handle()
+ {
+ return libusbp_generic_handle_get_winusb_handle(pointer);
+ }
+ #endif
+
+ #ifdef __linux__
+ /*! Wrapper for libusbp_generic_handle_get_fd(). */
+ int get_fd()
+ {
+ return libusbp_generic_handle_get_fd(pointer);
+ }
+ #endif
+
+ #ifdef __APPLE__
+ /*! Wrapper for libusbp_generic_handle_get_cf_plug_in(). */
+ void ** get_cf_plug_in()
+ {
+ return libusbp_generic_handle_get_cf_plug_in(pointer);
+ }
+ #endif
+ };
+
+ /*! Wrapper for a ::libusbp_serial_port pointer. */
+ class serial_port : public unique_pointer_wrapper_with_copy
+ {
+ public:
+ /*! Constructor that takes a pointer. This object will free the pointer
+ * when it is destroyed. */
+ explicit serial_port(libusbp_serial_port * pointer = nullptr)
+ : unique_pointer_wrapper_with_copy(pointer)
+ {
+ }
+
+ /*! Wrapper for libusbp_serial_port_create(). */
+ explicit serial_port(const device & device,
+ uint8_t interface_number = 0, bool composite = false)
+ {
+ throw_if_needed(libusbp_serial_port_create(
+ device.pointer_get(), interface_number, composite, &pointer));
+ }
+
+ /*! Wrapper for libusbp_serial_port_get_name(). */
+ std::string get_name() const
+ {
+ char * str;
+ throw_if_needed(libusbp_serial_port_get_name(pointer, &str));
+ std::string id = str;
+ libusbp_string_free(str);
+ return id;
+ }
+ };
+}
+
diff --git a/dep/libusbp/include/libusbp_config.h b/dep/libusbp/include/libusbp_config.h
new file mode 100644
index 00000000..9f5f11ee
--- /dev/null
+++ b/dep/libusbp/include/libusbp_config.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#define BUILD_SYSTEM_LIBUSBP_VERSION_MAJOR 1
+#define LIBUSBP_STATIC
+#undef LIBUSBP_LOG
+#undef VBOX_LINUX_ON_WINDOWS
+#undef USE_TEST_DEVICE_A
+#undef USE_TEST_DEVICE_B
diff --git a/dep/libusbp/install_helper/CMakeLists.txt b/dep/libusbp/install_helper/CMakeLists.txt
new file mode 100644
index 00000000..54c6e733
--- /dev/null
+++ b/dep/libusbp/install_helper/CMakeLists.txt
@@ -0,0 +1,15 @@
+use_c99()
+
+add_library (install_helper SHARED install_helper_windows.c dll.def)
+
+target_link_libraries (install_helper setupapi msi)
+
+set_target_properties (install_helper PROPERTIES
+ OUTPUT_NAME usbp-install-helper-${LIBUSBP_VERSION_MAJOR}
+ LINK_FLAGS "-Wl,--enable-stdcall-fixup -static"
+)
+
+install (TARGETS install_helper
+ RUNTIME DESTINATION bin
+ LIBRARY DESTINATION lib
+ ARCHIVE DESTINATION lib)
diff --git a/dep/libusbp/install_helper/README.md b/dep/libusbp/install_helper/README.md
new file mode 100644
index 00000000..b05f6442
--- /dev/null
+++ b/dep/libusbp/install_helper/README.md
@@ -0,0 +1,3 @@
+The files in this directory compile a separate, statically linked DLL named
+`libusbp-install-helper-.dll` that can be useful in installers of USB
+software.
\ No newline at end of file
diff --git a/dep/libusbp/install_helper/dll.def b/dep/libusbp/install_helper/dll.def
new file mode 100644
index 00000000..d0309535
--- /dev/null
+++ b/dep/libusbp/install_helper/dll.def
@@ -0,0 +1,5 @@
+EXPORTS
+ libusbp_install_inf
+ libusbp_install_infW
+ libusbp_broadcast_setting_change
+ libusbp_broadcast_setting_changeW
diff --git a/dep/libusbp/install_helper/install_helper_windows.c b/dep/libusbp/install_helper/install_helper_windows.c
new file mode 100644
index 00000000..59aaf307
--- /dev/null
+++ b/dep/libusbp/install_helper/install_helper_windows.c
@@ -0,0 +1,167 @@
+/* This file contains special functions that can help install an application
+ * that uses USB. The functions can be used from an MSI custom action or from
+ * rundll32. */
+
+#include
+#include
+#include
+#include
+#include
+
+#define LIBUSBP_UNUSED(param_name) (void)param_name;
+
+typedef struct install_context
+{
+ HWND owner;
+ MSIHANDLE install;
+} install_context;
+
+static void log_message(install_context * context, LPCWSTR message)
+{
+ if (context->install == 0)
+ {
+ // The MSI handle is not available so just ignore log messages.
+ }
+ else
+ {
+ // Report the log message through MSI, which will put it in the log
+ // file.
+ MSIHANDLE record = MsiCreateRecord(1);
+ MsiRecordSetStringW(record, 0, message);
+ MsiProcessMessage(context->install, INSTALLMESSAGE_INFO, record);
+ MsiCloseHandle(record);
+ }
+}
+
+// Adds an error message to the Windows Installer log file and displays it
+// to the user in a dialog box with an OK button.
+static void error_message(install_context * context, LPCWSTR message)
+{
+ if (context->install == 0)
+ {
+ // The MSIHANDLE is not available, so just display a dialog box.
+ MessageBoxW(context->owner, message, L"Installation Error", MB_ICONERROR);
+ }
+ else
+ {
+ // Report the error through MSI, which will in turn display the dialog
+ // box.
+ MSIHANDLE record = MsiCreateRecord(1);
+ MsiRecordSetStringW(record, 0, message);
+ MsiProcessMessage(context->install, INSTALLMESSAGE_ERROR, record);
+ MsiCloseHandle(record);
+ }
+}
+
+/* You might need to call this function after modifying the PATH in order to
+ * notify other programs about the change. This allows a newly launched Command
+ * Prompt to see that the PATH has changed and start using it. */
+static void broadcast_setting_change_core(install_context * context)
+{
+ DWORD_PTR result2 = 0;
+ LRESULT result = SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
+ (LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, &result2);
+ if (result == 0)
+ {
+ WCHAR message[1024];
+ StringCbPrintfW(message, sizeof(message),
+ L"SendMessageTimeout failed: Error code 0x%lx. Result %d",
+ GetLastError(), result2);
+ error_message(context, message);
+ }
+}
+
+// Usage: rundll32 libusbp*.dll libusbp_broadcast_setting_change
+void __stdcall libusbp_broadcast_setting_changeW(
+ HWND owner, HINSTANCE hinst, LPWSTR args, int n)
+{
+ LIBUSBP_UNUSED(hinst);
+ LIBUSBP_UNUSED(args);
+ LIBUSBP_UNUSED(n);
+ install_context context = {0};
+ context.owner = owner;
+ broadcast_setting_change_core(&context);
+}
+
+// Usage: make a Custom Action in an MSI with this function as the entry point.
+UINT __stdcall libusbp_broadcast_setting_change(MSIHANDLE install)
+{
+ install_context context = {0};
+ context.install = install;
+ log_message(&context, L"libusbp_broadcast_setting_change: Begin.");
+ broadcast_setting_change_core(&context);
+ log_message(&context, L"libusbp_broadcast_setting_change: End.");
+ return 0; // Always return success.
+}
+
+/* Calls SetupCopyOEMInf to install the specified INF file. The user may be
+ * prompted to accept the driver software, and if everything works then the file
+ * will be copied to the C:\Windows\inf directory. */
+static void install_inf_core(install_context * context, LPWSTR filename)
+{
+ BOOL success = SetupCopyOEMInfW(filename, NULL, SPOST_PATH, 0, NULL, 0, NULL, NULL);
+
+ if (!success)
+ {
+ WCHAR message[1024];
+
+ // NOTE: newlines do not show up in the MSI log, but they do show up in
+ // the MSI error dialog box.
+
+ StringCbPrintfW(message, sizeof(message),
+ L"There was an error installing the driver file %s. \n"
+ L"You might have to manually install this file by right-clicking it "
+ L"and selecting \"Install\". \n"
+ L"Error code 0x%lx.", filename, GetLastError());
+ error_message(context, message);
+ }
+}
+
+// Usage: rundll32 libusbp*.dll libusbp_install_inf path
+void __stdcall libusbp_install_infW(HWND owner, HINSTANCE hinst, LPWSTR args, int n)
+{
+ LIBUSBP_UNUSED(hinst);
+ LIBUSBP_UNUSED(args);
+ LIBUSBP_UNUSED(n);
+ install_context context = {0};
+ context.owner = owner;
+ install_inf_core(&context, args);
+}
+
+// Usage: make a Custom Action in your installer with a "CustomActionData"
+// property set equal to the full path of the inf file.
+UINT __stdcall libusbp_install_inf(MSIHANDLE install)
+{
+ install_context context = {0};
+ context.install = install;
+ broadcast_setting_change_core(&context);
+
+ log_message(&context, L"libusbp_install_inf: Begin.");
+
+ WCHAR message[1024];
+
+ // Get the name of inf file.
+ WCHAR filename[1024];
+ DWORD length = 1024;
+ UINT result = MsiGetPropertyW(install, L"CustomActionData", filename, &length);
+ if (result != ERROR_SUCCESS)
+ {
+ StringCbPrintfW(message, sizeof(message),
+ L"libusbp_install_inf: Unable to get filename parameter. Error code %d.", result);
+ error_message(&context, message);
+ return 0; // Return success anyway.
+ }
+
+ StringCbPrintfW(message, sizeof(message), L"libusbp_install_inf: filename=%s", filename);
+ log_message(&context, message);
+
+ install_inf_core(&context, filename);
+
+ StringCbPrintfW(message, sizeof(message), L"libusbp_install_inf: End. result2=%d", result);
+ log_message(&context, message);
+
+ // Always return 0 even if there was an error, because we don't want to roll
+ // back the rest of the installation just because this part fails. The user
+ // can either manually install the INF files or try again.
+ return 0;
+}
diff --git a/dep/libusbp/manual_tests/CMakeLists.txt b/dep/libusbp/manual_tests/CMakeLists.txt
new file mode 100644
index 00000000..f6919f7e
--- /dev/null
+++ b/dep/libusbp/manual_tests/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_subdirectory(test_async_in)
+add_subdirectory(test_long_read)
+add_subdirectory(test_long_write)
+add_subdirectory(test_transitions)
diff --git a/dep/libusbp/manual_tests/test_async_in/CMakeLists.txt b/dep/libusbp/manual_tests/test_async_in/CMakeLists.txt
new file mode 100644
index 00000000..a66fde85
--- /dev/null
+++ b/dep/libusbp/manual_tests/test_async_in/CMakeLists.txt
@@ -0,0 +1,9 @@
+use_cxx11()
+
+add_executable(test_async_in test_async_in.cpp)
+
+include_directories (
+ "${CMAKE_SOURCE_DIR}/include"
+)
+
+target_link_libraries(test_async_in usbp)
diff --git a/dep/libusbp/manual_tests/test_async_in/test_async_in.cpp b/dep/libusbp/manual_tests/test_async_in/test_async_in.cpp
new file mode 100644
index 00000000..647d3dee
--- /dev/null
+++ b/dep/libusbp/manual_tests/test_async_in/test_async_in.cpp
@@ -0,0 +1,107 @@
+/* Tests that libusbp is capable of reliably reading data from an IN endpoint on
+ * every frame using asynchronous transfers. It prints to the standard output
+ * as it successfully receives data, and prints to the standard error if there
+ * was a gap in the data (a USB frame where the device did not generate a new
+ * packet for the host).
+ *
+ * You can also check the CPU usage while running this function to make
+ * sure libusbp is not doing anything too inefficient.
+ */
+
+#include
+#include
+#include
+#ifdef _MSC_VER
+#define usleep(x) Sleep(((x) + 999) / 1000)
+#else
+#include
+#endif
+
+const uint16_t vendor_id = 0x1FFB;
+const uint16_t product_id = 0xDA01;
+const uint8_t interface_number = 0;
+const bool composite = true;
+const uint8_t endpoint_address = 0x82;
+const size_t packet_size = 5;
+const size_t transfer_size = packet_size;
+const size_t transfer_count = 250;
+
+int main_with_exceptions()
+{
+ libusbp::device device = libusbp::find_device_with_vid_pid(vendor_id, product_id);
+ if (!device)
+ {
+ std::cerr << "Device not found." << std::endl;
+ return 1;
+ }
+
+ libusbp::generic_interface gi(device, interface_number, composite);
+ libusbp::generic_handle handle(gi);
+ libusbp::async_in_pipe pipe = handle.open_async_in_pipe(endpoint_address);
+ pipe.allocate_transfers(transfer_count, transfer_size);
+
+ pipe.start_endless_transfers();
+
+ uint8_t last_f = 0;
+
+ uint32_t finish_count = 0;
+ while(true)
+ {
+ uint8_t buffer[transfer_size];
+ size_t transferred;
+ libusbp::error transfer_error;
+ while(pipe.handle_finished_transfer(buffer, &transferred, &transfer_error))
+ {
+ if (transfer_error)
+ {
+ fprintf(stderr, "Transfer error.\n");
+ throw transfer_error;
+ }
+
+ if (transferred != transfer_size)
+ {
+ fprintf(stderr, "Got %d bytes instead of %d.\n",
+ (int)transferred, (int)transfer_size);
+ }
+
+ uint8_t f = buffer[0];
+ if (f != (uint8_t)(last_f + transfer_size/packet_size))
+ {
+ // If this happens, it indicates there was a USB frame where the
+ // device did not generate a new packet for the host, which is
+ // bad. However, you should expect to see a few of these at the
+ // very beginning of the test because there will be some old
+ // packets queued up in the device from earlier, and because
+ // last_f always starts at 0.
+ fprintf(stderr, "Frame number gap: %d to %d\n", last_f, f);
+ }
+ last_f = f;
+
+ if ((++finish_count % 4096) == 0)
+ {
+ printf("Another 4096 transfers done.\n");
+ fflush(stdout);
+ }
+ }
+
+ pipe.handle_events();
+ usleep(20000);
+ }
+}
+
+int main(int argc, char ** argv)
+{
+ // Suppress unused parameter warnings.
+ (void)argc;
+ (void)argv;
+
+ try
+ {
+ return main_with_exceptions();
+ }
+ catch(const std::exception & error)
+ {
+ std::cerr << "Error: " << error.what() << std::endl;
+ }
+ return 1;
+}
diff --git a/dep/libusbp/manual_tests/test_long_read/CMakeLists.txt b/dep/libusbp/manual_tests/test_long_read/CMakeLists.txt
new file mode 100644
index 00000000..610dd508
--- /dev/null
+++ b/dep/libusbp/manual_tests/test_long_read/CMakeLists.txt
@@ -0,0 +1,9 @@
+use_cxx11()
+
+add_executable(test_long_read test_long_read.cpp)
+
+include_directories (
+ "${CMAKE_SOURCE_DIR}/include"
+)
+
+target_link_libraries(test_long_read usbp)
diff --git a/dep/libusbp/manual_tests/test_long_read/test_long_read.cpp b/dep/libusbp/manual_tests/test_long_read/test_long_read.cpp
new file mode 100644
index 00000000..1bd95e20
--- /dev/null
+++ b/dep/libusbp/manual_tests/test_long_read/test_long_read.cpp
@@ -0,0 +1,90 @@
+/* Tests what happens if we do a synchronous read from an IN endpoint that
+ * takes a long time to complete. This can be used to check that pressing
+ * Ctrl+C is able to interrupt the read. */
+
+#include
+#include
+#include
+
+const uint16_t vendor_id = 0x1FFB;
+const uint16_t product_id = 0xDA01;
+const uint8_t interface_number = 0;
+const bool composite = true;
+const uint8_t endpoint_address = 0x82;
+const size_t packet_size = 5;
+const size_t transfer_size = packet_size * 10000;
+
+void long_read(libusbp::generic_handle & handle)
+{
+ uint8_t buffer[transfer_size];
+ size_t transferred;
+
+ printf("Reading %d bytes...\n", (unsigned int)transfer_size);
+ fflush(stdout);
+ handle.read_pipe(endpoint_address, buffer, sizeof(buffer), &transferred);
+ if (transferred == transfer_size)
+ {
+ printf("Transfer successful.\n");
+ }
+ else
+ {
+ printf("Transferred only %d bytes out of %d.\n",
+ (unsigned int)transferred, (unsigned int)transfer_size);
+ }
+ fflush(stdout);
+}
+
+void long_control_read(libusbp::generic_handle & handle)
+{
+ uint8_t buffer[5];
+ size_t transferred;
+
+ printf("Performing a slow control read...\n");
+ fflush(stdout);
+ handle.control_transfer(0xC0, 0x91, 10000, 5, buffer, sizeof(buffer), &transferred);
+ if (transferred == 5)
+ {
+ printf("Control read successful.\n");
+ }
+ else
+ {
+ printf("Transferred only %d bytes out of %d.\n",
+ (unsigned int)transferred, (unsigned int)sizeof(buffer));
+ }
+ fflush(stdout);
+}
+
+int main_with_exceptions()
+{
+ libusbp::device device = libusbp::find_device_with_vid_pid(vendor_id, product_id);
+ if (!device)
+ {
+ std::cerr << "Device not found." << std::endl;
+ return 1;
+ }
+
+ libusbp::generic_interface gi(device, interface_number, composite);
+ libusbp::generic_handle handle(gi);
+
+ long_read(handle);
+ long_control_read(handle);
+
+ return 0;
+}
+
+int main(int argc, char ** argv)
+{
+ // Suppress unused parameter warnings.
+ (void)argc;
+ (void)argv;
+
+ try
+ {
+ return main_with_exceptions();
+ }
+ catch(const std::exception & error)
+ {
+ std::cerr << "Error: " << error.what() << std::endl;
+ }
+ return 1;
+}
diff --git a/dep/libusbp/manual_tests/test_long_write/CMakeLists.txt b/dep/libusbp/manual_tests/test_long_write/CMakeLists.txt
new file mode 100644
index 00000000..3dd6babd
--- /dev/null
+++ b/dep/libusbp/manual_tests/test_long_write/CMakeLists.txt
@@ -0,0 +1,9 @@
+use_cxx11()
+
+add_executable(test_long_write test_long_write.cpp)
+
+include_directories (
+ "${CMAKE_SOURCE_DIR}/include"
+)
+
+target_link_libraries(test_long_write usbp)
diff --git a/dep/libusbp/manual_tests/test_long_write/test_long_write.cpp b/dep/libusbp/manual_tests/test_long_write/test_long_write.cpp
new file mode 100644
index 00000000..40830548
--- /dev/null
+++ b/dep/libusbp/manual_tests/test_long_write/test_long_write.cpp
@@ -0,0 +1,70 @@
+/* Tests what happens if we do a synchronous write that takes a long time to
+ * complete. This can be used to check that pressing Ctrl+C is able to
+ * interrupt the write. */
+
+#include
+#include
+#include
+
+const uint16_t vendor_id = 0x1FFB;
+const uint16_t product_id = 0xDA01;
+const uint8_t interface_number = 0;
+const bool composite = true;
+const uint8_t endpoint_address = 0x03;
+const size_t packet_size = 32;
+const size_t transfer_size = packet_size * 4;
+
+void long_write(libusbp::generic_handle & handle)
+{
+ // First packet causes a delay of 4 seconds (0x0FA0 ms)
+ uint8_t buffer[transfer_size] = { 0xDE, 0xA0, 0x0F };
+ size_t transferred;
+
+ printf("Writing...\n");
+ fflush(stdout);
+ handle.write_pipe(endpoint_address, buffer, sizeof(buffer), &transferred);
+ if (transferred == transfer_size)
+ {
+ printf("Transfer successful.\n");
+ }
+ else
+ {
+ printf("Transferred only %d bytes out of %d.\n",
+ (unsigned int)transferred, (unsigned int)transfer_size);
+ }
+}
+
+int main_with_exceptions()
+{
+ libusbp::device device = libusbp::find_device_with_vid_pid(
+ vendor_id, product_id);
+ if (!device)
+ {
+ std::cerr << "Device not found." << std::endl;
+ return 1;
+ }
+
+ libusbp::generic_interface gi(device, interface_number, composite);
+ libusbp::generic_handle handle(gi);
+
+ long_write(handle);
+
+ return 0;
+}
+
+int main(int argc, char ** argv)
+{
+ // Suppress unused parameter warnings.
+ (void)argc;
+ (void)argv;
+
+ try
+ {
+ return main_with_exceptions();
+ }
+ catch(const std::exception & error)
+ {
+ std::cerr << "Error: " << error.what() << std::endl;
+ }
+ return 1;
+}
diff --git a/dep/libusbp/manual_tests/test_transitions/CMakeLists.txt b/dep/libusbp/manual_tests/test_transitions/CMakeLists.txt
new file mode 100644
index 00000000..b8dd8c1e
--- /dev/null
+++ b/dep/libusbp/manual_tests/test_transitions/CMakeLists.txt
@@ -0,0 +1,9 @@
+use_cxx11()
+
+add_executable(test_transitions test_transitions.cpp)
+
+include_directories (
+ "${CMAKE_SOURCE_DIR}/include"
+)
+
+target_link_libraries(test_transitions usbp)
\ No newline at end of file
diff --git a/dep/libusbp/manual_tests/test_transitions/test_transitions.cpp b/dep/libusbp/manual_tests/test_transitions/test_transitions.cpp
new file mode 100644
index 00000000..2b55c601
--- /dev/null
+++ b/dep/libusbp/manual_tests/test_transitions/test_transitions.cpp
@@ -0,0 +1,98 @@
+/* This program helps us test the transitions that a USB device goes through as
+ * it gets connected or disconnected from a computer. It helps us identify
+ * errors that might occur so we can assign code to them such as
+ * LIBUSBP_ERROR_NOT_READY. */
+
+#include
+#include
+#include
+#include
+#ifdef _MSC_VER
+#define usleep(x) Sleep(((x) + 999) / 1000)
+#else
+#include
+#endif
+
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 6
+typedef std::chrono::monotonic_clock clock_type;
+#else
+typedef std::chrono::steady_clock clock_type;
+#endif
+
+std::ostream & log()
+{
+ return std::cout
+ << clock_type::now().time_since_epoch().count()
+ << ": ";
+}
+
+void check_test_device_a(libusbp::device device)
+{
+ static std::string current_status;
+ std::string status;
+
+ if (device)
+ {
+ status = "Found " + device.get_serial_number() + ".";
+
+ // Try to connect to the generic interface.
+ try
+ {
+ libusbp::generic_interface gi(device, 0, true);
+ libusbp::generic_handle handle(gi);
+ status += " Interface 0 works.";
+ }
+ catch(const libusbp::error & error)
+ {
+ status += " Interface 0 error: " + error.message();
+ if (!error.has_code(LIBUSBP_ERROR_NOT_READY))
+ {
+ status += " Lacks code LIBUSBP_ERROR_NOT_READY!";
+ }
+ }
+ }
+ else
+ {
+ status = "Not found.";
+ }
+
+ if (current_status != status)
+ {
+ log() << "Test device A: " << status << std::endl;
+ current_status = status;
+ }
+}
+
+int main_with_exceptions()
+{
+ std::cout
+ << "Clock tick period: "
+ << clock_type::period::num
+ << "/"
+ << clock_type::period::den
+ << " seconds" << std::endl;
+
+ while(1)
+ {
+ check_test_device_a(libusbp::find_device_with_vid_pid(0x1FFB, 0xDA01));
+ usleep(10);
+ }
+ return 0;
+}
+
+int main(int argc, char ** argv)
+{
+ // Suppress unused parameter warnings.
+ (void)argc;
+ (void)argv;
+
+ try
+ {
+ return main_with_exceptions();
+ }
+ catch(const std::exception & error)
+ {
+ std::cerr << "Error: " << error.what() << std::endl;
+ }
+ return 1;
+}
diff --git a/dep/libusbp/src/CMakeLists.txt b/dep/libusbp/src/CMakeLists.txt
new file mode 100644
index 00000000..8ab25b97
--- /dev/null
+++ b/dep/libusbp/src/CMakeLists.txt
@@ -0,0 +1,116 @@
+use_c99()
+
+# Settings for GCC
+if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
+ # By default, symbols are not visible outside of the library.
+ set (CMAKE_C_FLAGS "-fvisibility=hidden ${CMAKE_C_FLAGS}")
+
+ # Avoid tons of errors from strsafe.h.
+ set (CMAKE_C_FLAGS "-fgnu89-inline ${CMAKE_C_FLAGS}")
+endif ()
+
+# Define cross-platform source files.
+set (sources
+ async_in_pipe.c
+ error.c
+ error_hresult.c
+ find_device.c
+ list.c
+ pipe_id.c
+ string.c)
+
+# Define operating system-specific source files.
+if (WIN32)
+ set (sources ${sources}
+ windows/error_windows.c
+ windows/device_windows.c
+ windows/interface_windows.c
+ windows/device_instance_id_windows.c
+ windows/generic_interface_windows.c
+ windows/list_windows.c
+ windows/generic_handle_windows.c
+ windows/async_in_transfer_windows.c
+ windows/serial_port_windows.c
+ ${CMAKE_CURRENT_BINARY_DIR}/info.rc)
+elseif (LINUX)
+ set (sources ${sources}
+ linux/list_linux.c
+ linux/device_linux.c
+ linux/generic_interface_linux.c
+ linux/generic_handle_linux.c
+ linux/error_linux.c
+ linux/udev_linux.c
+ linux/usbfd_linux.c
+ linux/async_in_transfer_linux.c
+ linux/serial_port_linux.c)
+elseif (APPLE)
+ set (sources ${sources}
+ mac/list_mac.c
+ mac/device_mac.c
+ mac/error_mac.c
+ mac/generic_interface_mac.c
+ mac/generic_handle_mac.c
+ mac/async_in_transfer_mac.c
+ mac/serial_port_mac.c
+ mac/iokit_mac.c)
+endif ()
+
+add_library (usbp ${sources})
+
+include_directories (
+ "${CMAKE_SOURCE_DIR}/include"
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+ "${CMAKE_CURRENT_BINARY_DIR}"
+)
+
+if (WIN32)
+ target_link_libraries (usbp setupapi winusb uuid ole32)
+ if (NOT BUILD_SHARED_LIBS)
+ set (PC_MORE_LIBS "-lsetupapi -lwinusb -luuid -lole32")
+ endif ()
+elseif (LINUX)
+ pkg_check_modules(LIBUDEV REQUIRED libudev)
+ string (REPLACE ";" " " LIBUDEV_CFLAGS "${LIBUDEV_CFLAGS}")
+ set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${LIBUDEV_CFLAGS}")
+ target_link_libraries (usbp udev)
+ if (NOT BUILD_SHARED_LIBS)
+ set (PC_REQUIRES "libudev")
+ endif ()
+elseif (APPLE)
+ set (link_flags "-framework IOKit -framework CoreFoundation ${link_flags}")
+ if (NOT BUILD_SHARED_LIBS)
+ set (PC_MORE_LIBS "${link_flags}")
+ endif ()
+endif ()
+
+set_target_properties(usbp PROPERTIES
+ OUTPUT_NAME usbp-${LIBUSBP_VERSION_MAJOR}
+ SOVERSION ${LIBUSBP_VERSION}
+ VERSION ${LIBUSBP_VERSION}
+ DEFINE_SYMBOL LIBUSBP_EXPORTS
+ LINK_FLAGS "${link_flags}"
+)
+
+configure_file (
+ "libusbp_config.h.in"
+ "libusbp_config.h"
+)
+
+configure_file (
+ "info.rc.in"
+ "info.rc"
+)
+
+configure_file (
+ "libusbp.pc.in"
+ "libusbp-${LIBUSBP_VERSION_MAJOR}.pc"
+ @ONLY
+)
+
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libusbp-${LIBUSBP_VERSION_MAJOR}.pc"
+ DESTINATION lib/pkgconfig)
+
+install(TARGETS usbp
+ RUNTIME DESTINATION bin
+ LIBRARY DESTINATION lib
+ ARCHIVE DESTINATION lib)
diff --git a/dep/libusbp/src/async_in_pipe.c b/dep/libusbp/src/async_in_pipe.c
new file mode 100644
index 00000000..4955a723
--- /dev/null
+++ b/dep/libusbp/src/async_in_pipe.c
@@ -0,0 +1,343 @@
+#include
+
+struct libusbp_async_in_pipe
+{
+ libusbp_generic_handle * handle;
+ uint8_t pipe_id;
+ async_in_transfer ** transfer_array;
+ size_t transfer_size;
+ size_t transfer_count;
+
+ bool endless_transfers_enabled;
+
+ // The number of transfers that are pending, meaning that they were
+ // submitted (and possibly completed by the kernel) but not finished (handed
+ // to the user of the pipe) yet. This variable allows us to distinguish
+ // between the state where no transfers are pending and the state where all
+ // of them are pending. Note that this definition of pending is different
+ // than the definition of pending in the async_in_transfer struct.
+ size_t pending_count;
+
+ // The index of the transfer that should finish next. That transfer will be
+ // pending (and thus able to be checked) if pending_count > 0.
+ size_t next_finish;
+
+ // The index of the transfer that will be submitted next when we need to
+ // submit more transfers. That transfer can be submitted if pending_count <
+ // transfer_count.
+ size_t next_submit;
+};
+
+static inline size_t increment_and_wrap_size(size_t n, size_t bound)
+{
+ n++;
+ return n >= bound ? 0 : n;
+}
+
+static void async_in_transfer_array_free(async_in_transfer ** array, size_t transfer_count)
+{
+ if (array == NULL) { return; }
+
+ for (size_t i = 0; i < transfer_count; i++)
+ {
+ async_in_transfer_free(array[i]);
+ }
+ free(array);
+}
+
+void libusbp_async_in_pipe_close(libusbp_async_in_pipe * pipe)
+{
+ if (pipe != NULL)
+ {
+ async_in_transfer_array_free(pipe->transfer_array, pipe->transfer_count);
+ free(pipe);
+ }
+}
+
+libusbp_error * async_in_pipe_create(libusbp_generic_handle * handle,
+ uint8_t pipe_id, libusbp_async_in_pipe ** pipe)
+{
+ // Check the pipe output pointer.
+ if (pipe == NULL)
+ {
+ return error_create("Pipe output pointer is null.");
+ }
+
+ *pipe = NULL;
+
+ if (handle == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ // Check the pipe_id parameter.
+ if (error == NULL)
+ {
+ error = check_pipe_id(pipe_id);
+ }
+ if (error == NULL && !(pipe_id & 0x80))
+ {
+ error = error_create("Asynchronous pipes for OUT endpoints are not supported.");
+ }
+
+ // Perform OS-specific setup.
+ if (error == NULL)
+ {
+ error = async_in_pipe_setup(handle, pipe_id);
+ }
+
+ libusbp_async_in_pipe * new_pipe = NULL;
+ if (error == NULL)
+ {
+ new_pipe = calloc(1, sizeof(libusbp_async_in_pipe));
+ if (new_pipe == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ if (error == NULL)
+ {
+ new_pipe->handle = handle;
+ new_pipe->pipe_id = pipe_id;
+ *pipe = new_pipe;
+ new_pipe = NULL;
+ }
+
+ free(new_pipe);
+ return error;
+}
+
+libusbp_error * libusbp_async_in_pipe_allocate_transfers(
+ libusbp_async_in_pipe * pipe,
+ size_t transfer_count,
+ size_t transfer_size)
+{
+ if (pipe == NULL)
+ {
+ return error_create("Pipe argument is null.");
+ }
+
+ if (pipe->transfer_array != NULL)
+ {
+ return error_create("Transfers were already allocated for this pipe.");
+ }
+
+ if (transfer_count == 0)
+ {
+ return error_create("Transfer count cannot be zero.");
+ }
+
+ if (transfer_size == 0)
+ {
+ return error_create("Transfer size cannot be zero.");
+ }
+
+ libusbp_error * error = NULL;
+
+ async_in_transfer ** new_transfer_array = NULL;
+ if (error == NULL)
+ {
+ new_transfer_array = calloc(transfer_count, sizeof(async_in_transfer *));
+ if (new_transfer_array == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ for(size_t i = 0; error == NULL && i < transfer_count; i++)
+ {
+ error = async_in_transfer_create(pipe->handle, pipe->pipe_id,
+ transfer_size, &new_transfer_array[i]);
+ }
+
+ // Put the new array and the information about it into the pipe.
+ if (error == NULL)
+ {
+ pipe->transfer_array = new_transfer_array;
+ pipe->transfer_count = transfer_count;
+ pipe->transfer_size = transfer_size;
+ new_transfer_array = NULL;
+ }
+
+ async_in_transfer_array_free(new_transfer_array, transfer_count);
+
+ if (error != NULL)
+ {
+ error = error_add(error, "Failed to allocate transfers for asynchronous IN pipe.");
+ }
+ return error;
+}
+
+static void async_in_pipe_submit_next_transfer(libusbp_async_in_pipe * pipe)
+{
+ assert(pipe != NULL);
+ assert(pipe->pending_count < pipe->transfer_count);
+
+ // Submit the next transfer.
+ async_in_transfer_submit(pipe->transfer_array[pipe->next_submit]);
+
+ // Update the counts and indices.
+ pipe->pending_count++;
+ pipe->next_submit = increment_and_wrap_size(pipe->next_submit, pipe->transfer_count);
+}
+
+libusbp_error * libusbp_async_in_pipe_start_endless_transfers(
+ libusbp_async_in_pipe * pipe)
+{
+ if (pipe == NULL)
+ {
+ return error_create("Pipe argument is null.");
+ }
+
+ if (pipe->transfer_array == NULL)
+ {
+ return error_create("Pipe transfers have not been allocated yet.");
+ }
+
+ pipe->endless_transfers_enabled = true;
+
+ while(pipe->pending_count < pipe->transfer_count)
+ {
+ async_in_pipe_submit_next_transfer(pipe);
+ }
+
+ return NULL;
+}
+
+libusbp_error * libusbp_async_in_pipe_handle_events(libusbp_async_in_pipe * pipe)
+{
+ if (pipe == NULL)
+ {
+ return error_create("Pipe argument is null.");
+ }
+
+ return generic_handle_events(pipe->handle);
+}
+
+libusbp_error * libusbp_async_in_pipe_has_pending_transfers(
+ libusbp_async_in_pipe * pipe,
+ bool * result)
+{
+ libusbp_error * error = NULL;
+
+ if (error == NULL && result == NULL)
+ {
+ error = error_create("Boolean output pointer is null.");
+ }
+
+ if (error == NULL)
+ {
+ *result = false;
+ }
+
+ if (error == NULL && pipe == NULL)
+ {
+ error = error_create("Pipe argument is null.");
+ }
+
+ if (error == NULL)
+ {
+ *result = pipe->pending_count ? 1 : 0;
+ }
+
+ return error;
+}
+
+libusbp_error * libusbp_async_in_pipe_handle_finished_transfer(
+ libusbp_async_in_pipe * pipe,
+ bool * finished,
+ void * buffer,
+ size_t * transferred,
+ libusbp_error ** transfer_error)
+{
+ if (finished != NULL)
+ {
+ *finished = false;
+ }
+
+ if (transferred != NULL)
+ {
+ *transferred = 0;
+ }
+
+ if (transfer_error != NULL)
+ {
+ *transfer_error = NULL;
+ }
+
+ if (pipe == NULL)
+ {
+ return error_create("Pipe argument is null.");
+ }
+
+ if (pipe->pending_count == 0)
+ {
+ // There are no pending transfers that we could check for completion.
+ return NULL;
+ }
+
+ async_in_transfer * transfer = pipe->transfer_array[pipe->next_finish];
+
+ if (async_in_transfer_pending(transfer))
+ {
+ // The next transfer we expect to finish is still pending;
+ // the kernel has not told us that it is done.
+ return NULL;
+ }
+
+ libusbp_error * error = async_in_transfer_get_results(transfer, buffer,
+ transferred, transfer_error);
+
+ if (error == NULL)
+ {
+ if (finished != NULL)
+ {
+ *finished = true;
+ }
+
+ pipe->pending_count--;
+ pipe->next_finish = increment_and_wrap_size(pipe->next_finish, pipe->transfer_count);
+ }
+
+ if (error == NULL && pipe->endless_transfers_enabled)
+ {
+ async_in_pipe_submit_next_transfer(pipe);
+ }
+
+ return error;
+}
+
+libusbp_error * libusbp_async_in_pipe_cancel_transfers(libusbp_async_in_pipe * pipe)
+{
+ if (pipe == NULL)
+ {
+ return error_create("Pipe argument is null.");
+ }
+
+ pipe->endless_transfers_enabled = false;
+
+ libusbp_error * error = NULL;
+
+ #ifdef __linux__
+ // In Linux, transfers need to be cancelled individually.
+ for (size_t i = 0; error == NULL && i < pipe->transfer_count; i++)
+ {
+ error = async_in_transfer_cancel(pipe->transfer_array[i]);
+
+ // This doesn't help the performance issue in this function:
+ //if (error == NULL) { error = generic_handle_events(pipe->handle); }
+ }
+
+ #else
+
+ // On other platforms, any of the transfers has all the information needed
+ // to cancel the others.
+ error = async_in_transfer_cancel(pipe->transfer_array[0]);
+
+ #endif
+
+ return error;
+}
diff --git a/dep/libusbp/src/error.c b/dep/libusbp/src/error.c
new file mode 100644
index 00000000..99283535
--- /dev/null
+++ b/dep/libusbp/src/error.c
@@ -0,0 +1,223 @@
+#include
+
+#ifdef _WIN32
+#if (defined(__GNUC__) && !defined(__USE_MINGW_ANSI_STDIO)) || (defined(_MSC_VER) && _MSC_VER < 1900)
+#error This code depends on vsnprintf returning a number of characters.
+#endif
+#endif
+
+struct libusbp_error
+{
+ bool do_not_free;
+ char * message;
+ size_t code_count;
+ uint32_t * code_array;
+};
+
+static uint32_t mem_error_code_array[1] = { LIBUSBP_ERROR_MEMORY };
+
+static char error_no_memory_msg[] = "Failed to allocate memory.";
+libusbp_error error_no_memory =
+{
+ .do_not_free = true,
+ .message = error_no_memory_msg,
+ .code_count = 1,
+ .code_array = mem_error_code_array,
+};
+
+static char error_masked_by_no_memory_msg[] = "Failed to allocate memory for reporting an error.";
+static libusbp_error error_masked_by_no_memory =
+{
+ .do_not_free = true,
+ .message = error_masked_by_no_memory_msg,
+ .code_count = 1,
+ .code_array = mem_error_code_array,
+};
+
+static libusbp_error error_blank =
+{
+ .do_not_free = true,
+ .message = NULL,
+ .code_count = 0,
+ .code_array = NULL,
+};
+
+void libusbp_error_free(libusbp_error * error)
+{
+ if (error != NULL && !error->do_not_free)
+ {
+ free(error->message);
+ free(error->code_array);
+ free(error);
+ }
+}
+
+// Copies the error. If the input is not NULL, the output will always
+// be not NULL, but it might be a immutable error (do_not_free=1).
+libusbp_error * libusbp_error_copy(const libusbp_error * src_error)
+{
+ if (src_error == NULL) { return NULL; }
+
+ const char * src_message = src_error->message;
+ if (src_message == NULL) { src_message = ""; }
+ size_t message_length = strlen(src_message);
+
+ size_t code_count = src_error->code_count;
+ if (src_error->code_array == NULL) { code_count = 0; }
+
+ // Allocate memory.
+ libusbp_error * new_error = malloc(sizeof(libusbp_error));
+ char * new_message = malloc(message_length + 1);
+ uint32_t * new_code_array = malloc(code_count * sizeof(uint32_t));
+ if (new_error == NULL || new_message == NULL ||
+ (code_count != 0 && new_code_array == NULL))
+ {
+ free(new_error);
+ free(new_message);
+ free(new_code_array);
+ return &error_masked_by_no_memory;
+ }
+
+ if (code_count != 0)
+ {
+ memcpy(new_code_array, src_error->code_array, code_count * sizeof(uint32_t));
+ }
+ strncpy(new_message, src_message, message_length + 1);
+ new_error->do_not_free = false;
+ new_error->message = new_message;
+ new_error->code_count = code_count;
+ new_error->code_array = new_code_array;
+ return new_error;
+}
+
+// Tries to make the error mutable. Always returns a non-NULL error,
+// but it might return an immutable error (do_not_free == 1) if memory
+// cannot be allocated.
+static libusbp_error * error_make_mutable(libusbp_error * error)
+{
+ if (error == NULL) { error = &error_blank; }
+ if (error->do_not_free)
+ {
+ error = libusbp_error_copy(error);
+ }
+ return error;
+}
+
+// Tries to add a message to the error. After calling this, you
+// should not use the error you passed as an input, because it might
+// have been freed.
+libusbp_error * error_add_v(libusbp_error * error, const char * format, va_list ap)
+{
+ if (format == NULL) { return error; }
+
+ error = error_make_mutable(error);
+ if (error == NULL || error->do_not_free) { return error; }
+
+ if (error->message == NULL) { error->message = ""; }
+
+ // Determine all the string lengths.
+ size_t outer_message_length = 0;
+ {
+ char x[1];
+ va_list ap2;
+ va_copy(ap2, ap);
+ int result = vsnprintf(x, 0, format, ap2);
+ if (result > 0)
+ {
+ outer_message_length = (size_t) result;
+ }
+ va_end(ap2);
+ }
+ size_t inner_message_length = strlen(error->message);
+ size_t separator_length = (inner_message_length && outer_message_length) ? 2 : 0;
+ size_t message_length = outer_message_length + separator_length + inner_message_length;
+
+ char * message = malloc(message_length + 1);
+ if (message == NULL)
+ {
+ libusbp_error_free(error);
+ return &error_masked_by_no_memory;
+ }
+
+ // Assemble the message.
+ vsnprintf(message, outer_message_length + 1, format, ap);
+ if (separator_length)
+ {
+ strncpy(message + outer_message_length, " ", separator_length + 1);
+ }
+ strncpy(message + outer_message_length + separator_length,
+ error->message, inner_message_length + 1);
+ message[message_length] = 0;
+
+ free(error->message);
+ error->message = message;
+ return error;
+}
+
+
+// Tries to add the specified code to the error.
+// This is just like error_add_v in terms of pointer ownership.
+libusbp_error * error_add_code(libusbp_error * error, uint32_t code)
+{
+ error = error_make_mutable(error);
+ if (error == NULL || error->do_not_free) { return error; }
+ if (error->code_count >= SIZE_MAX / sizeof(uint32_t)) { return error; }
+
+ size_t size = (error->code_count + 1) * sizeof(uint32_t);
+ uint32_t * new_array = realloc(error->code_array, size);
+ if (new_array == NULL)
+ {
+ libusbp_error_free(error);
+ return &error_masked_by_no_memory;
+ }
+ error->code_array = new_array;
+ error->code_array[error->code_count++] = code;
+ return error;
+}
+
+// Variadic version of error_add_v.
+libusbp_error * error_add(libusbp_error * error, const char * format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+ error = error_add_v(error, format, ap);
+ va_end(ap);
+ return error;
+}
+
+// Creates a new error.
+libusbp_error * error_create(const char * format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+ libusbp_error * error = error_add_v(NULL, format, ap);
+ va_end(ap);
+ return error;
+}
+
+bool libusbp_error_has_code(const libusbp_error * error, uint32_t code)
+{
+ if (error == NULL) { return false; }
+
+ for (size_t i = 0; i < error->code_count; i++)
+ {
+ if (error->code_array[i] == code)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+const char * libusbp_error_get_message(const libusbp_error * error)
+{
+ if (error == NULL)
+ {
+ return "No error.";
+ }
+ if (error->message == NULL)
+ {
+ return "";
+ }
+ return error->message;
+}
diff --git a/dep/libusbp/src/error_hresult.c b/dep/libusbp/src/error_hresult.c
new file mode 100644
index 00000000..817d598f
--- /dev/null
+++ b/dep/libusbp/src/error_hresult.c
@@ -0,0 +1,21 @@
+#include
+
+#if defined(_WIN32) || defined(__APPLE__)
+
+libusbp_error * error_create_hr(HRESULT hr, const char * format, ...)
+{
+ // HRESULT should be an int32_t on the systems we care about (Mac OS X,
+ // Win32, Win64), but let's assert it here in case that ever changes.
+ assert(sizeof(HRESULT) == 4);
+ assert((HRESULT)-1 < (HRESULT)0);
+
+ libusbp_error * error = error_create("HRESULT error code 0x%x.", (int32_t)hr);
+
+ va_list ap;
+ va_start(ap, format);
+ error = error_add_v(error, format, ap);
+ va_end(ap);
+ return error;
+}
+
+#endif
diff --git a/dep/libusbp/src/find_device.c b/dep/libusbp/src/find_device.c
new file mode 100644
index 00000000..b8a3dfc5
--- /dev/null
+++ b/dep/libusbp/src/find_device.c
@@ -0,0 +1,70 @@
+#include
+
+static libusbp_error * check_device_vid_pid(const libusbp_device * device,
+ uint16_t vendor_id, uint16_t product_id, bool * matches)
+{
+ assert(matches != NULL);
+
+ *matches = false;
+
+ libusbp_error * error;
+
+ uint16_t device_vid;
+ error = libusbp_device_get_vendor_id(device, &device_vid);
+ if (error != NULL) { return error; }
+ if (device_vid != vendor_id) { return NULL; }
+
+ uint16_t device_pid;
+ error = libusbp_device_get_product_id(device, &device_pid);
+ if (error != NULL) { return error; }
+ if (device_pid != product_id) { return NULL; }
+
+ *matches = true;
+ return NULL;
+}
+
+libusbp_error * libusbp_find_device_with_vid_pid(
+ uint16_t vendor_id, uint16_t product_id, libusbp_device ** device)
+{
+ if (device == NULL)
+ {
+ return error_create("Device output pointer is null.");
+ }
+
+ *device = NULL;
+
+ libusbp_error * error = NULL;
+
+ libusbp_device ** new_list = NULL;
+ size_t size = 0;
+ error = libusbp_list_connected_devices(&new_list, &size);
+
+ assert(error != NULL || new_list != NULL);
+
+ for(size_t i = 0; error == NULL && i < size; i++)
+ {
+ // Each iteration of this loop checks one candidate device
+ // and either passes it to the caller or frees it.
+
+ libusbp_device * candidate = new_list[i];
+
+ if (*device == NULL)
+ {
+ bool matches;
+ error = check_device_vid_pid(candidate, vendor_id, product_id, &matches);
+ if (error != NULL) { break; }
+
+ if (matches)
+ {
+ // Return the device to the caller.
+ *device = candidate;
+ candidate = NULL;
+ }
+ }
+
+ libusbp_device_free(candidate);
+ }
+
+ libusbp_list_free(new_list);
+ return error;
+}
diff --git a/dep/libusbp/src/info.rc.in b/dep/libusbp/src/info.rc.in
new file mode 100644
index 00000000..7ca6f303
--- /dev/null
+++ b/dep/libusbp/src/info.rc.in
@@ -0,0 +1,28 @@
+#define VERSION ${LIBUSBP_VERSION_MAJOR},${LIBUSBP_VERSION_MINOR},${LIBUSBP_VERSION_PATCH},0
+#define VERSION_STR "${LIBUSBP_VERSION_MAJOR}.${LIBUSBP_VERSION_MINOR}.${LIBUSBP_VERSION_PATCH}"
+
+#include
+VS_VERSION_INFO VERSIONINFO
+FILEVERSION VERSION
+PRODUCTVERSION VERSION
+FILEOS VOS_NT_WINDOWS32
+FILETYPE VFT_DLL
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ BEGIN
+ VALUE "CompanyName", "Pololu Corporation"
+ VALUE "FileDescription", "Pololu USB Library"
+ VALUE "FileVersion", VERSION_STR
+ VALUE "ProductName", "Pololu USB Library (${CMAKE_BUILD_TYPE})"
+ VALUE "ProductVersion", VERSION_STR
+ VALUE "LegalCopyright", "Copyright (C) ${YEAR} Pololu Corporation"
+ END
+ END
+
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
diff --git a/dep/libusbp/src/libusbp.pc.in b/dep/libusbp/src/libusbp.pc.in
new file mode 100644
index 00000000..acd49216
--- /dev/null
+++ b/dep/libusbp/src/libusbp.pc.in
@@ -0,0 +1,10 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+libdir=${prefix}/lib
+includedir=${prefix}/include
+
+Name: libusbp
+Version: @LIBUSBP_VERSION@
+Description: Library for accessing USB devices.
+Requires: @PC_REQUIRES@
+Libs: -L${libdir} -lusbp-@LIBUSBP_VERSION_MAJOR@ @PC_MORE_LIBS@
+Cflags: -I${includedir}/libusbp-@LIBUSBP_VERSION_MAJOR@ @PC_MORE_CFLAGS@
diff --git a/dep/libusbp/src/libusbp_config.h.in b/dep/libusbp/src/libusbp_config.h.in
new file mode 100644
index 00000000..aa29a9b4
--- /dev/null
+++ b/dep/libusbp/src/libusbp_config.h.in
@@ -0,0 +1,11 @@
+#pragma once
+
+#define BUILD_SYSTEM_LIBUSBP_VERSION_MAJOR @LIBUSBP_VERSION_MAJOR@
+
+#cmakedefine LIBUSBP_LOG
+
+#cmakedefine VBOX_LINUX_ON_WINDOWS
+
+#cmakedefine USE_TEST_DEVICE_A
+
+#cmakedefine USE_TEST_DEVICE_B
diff --git a/dep/libusbp/src/libusbp_internal.h b/dep/libusbp/src/libusbp_internal.h
new file mode 100644
index 00000000..6ed27f00
--- /dev/null
+++ b/dep/libusbp/src/libusbp_internal.h
@@ -0,0 +1,367 @@
+#ifdef LIBUSBP_DROP_IN
+#include "libusbp.h"
+#else
+#pragma once
+#include
+#include
+#if BUILD_SYSTEM_LIBUSBP_VERSION_MAJOR != LIBUSBP_VERSION_MAJOR
+#error Major version in libusbp.h disagrees with build system.
+#endif
+#endif
+
+// Don't warn about zero-length format strings, which we sometimes use when
+// constructing error objects.
+#ifdef __GNUC__
+#pragma GCC diagnostic ignored "-Wformat-zero-length"
+#endif
+
+// Silence some warnings from the Microsoft C Compiler.
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_WARNINGS
+#define strdup _strdup
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef _WIN32
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+
+#ifdef __linux__
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+
+#ifdef __APPLE__
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+
+// For debug builds (NDEBUG undefined), functions and variables declared with
+// LIBUSBP_TEST_API will be exported in the library so that they can be unit
+// tested, but they are not part of the public API and they might change at any
+// time. Those symbols can cause name collisions with symbols defined by the
+// user, so this type of build is not suitable for making a general release.
+#ifdef NDEBUG
+#define LIBUSBP_TEST_API
+#else
+#define LIBUSBP_TEST_API LIBUSBP_API
+#endif
+
+#ifdef _MSC_VER
+#define LIBUSBP_PRINTF(f, a)
+#else
+#define LIBUSBP_PRINTF(f, a) __attribute__((format (printf, f, a)))
+#endif
+
+// Suppresses unused parameter warnings.
+#define LIBUSBP_UNUSED(param_name) (void)param_name;
+
+#define MAX_ENDPOINT_NUMBER 15
+
+typedef struct libusbp_setup_packet
+{
+ uint8_t bmRequestType;
+ uint8_t bRequest;
+ uint16_t wValue;
+ uint16_t wIndex;
+ uint16_t wLength;
+} libusbp_setup_packet;
+
+LIBUSBP_TEST_API extern libusbp_error error_no_memory;
+
+LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED libusbp_error * error_create(
+ const char * format, ...) LIBUSBP_PRINTF(1, 2);
+
+LIBUSBP_WARN_UNUSED libusbp_error * error_add_v(
+ libusbp_error * error, const char * format, va_list ap);
+
+LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED libusbp_error * error_add(
+ libusbp_error * error, const char * format, ...)
+ LIBUSBP_PRINTF(2, 3);
+
+LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED libusbp_error * error_add_code(
+ libusbp_error * error, uint32_t code);
+
+LIBUSBP_WARN_UNUSED libusbp_error * string_copy(
+ const char * input_string,
+ char ** output_string);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * check_pipe_id(uint8_t pipe_id);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * check_pipe_id_in(uint8_t pipe_id);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * check_pipe_id_out(uint8_t pipe_id);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * device_list_create(libusbp_device *** device_list);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * device_list_append(libusbp_device *** device_list,
+ size_t * device_count, libusbp_device * device);
+
+void free_devices_and_list(libusbp_device ** device_list);
+
+typedef struct async_in_transfer
+ async_in_transfer;
+
+void async_in_transfer_handle_completion(async_in_transfer *);
+
+void async_in_transfer_free(async_in_transfer * transfer);
+
+libusbp_error * async_in_transfer_create(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ size_t transfer_size,
+ async_in_transfer ** transfer);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * async_in_pipe_create(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ libusbp_async_in_pipe ** pipe);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * async_in_pipe_setup(libusbp_generic_handle * gh, uint8_t pipe_id);
+
+void async_in_transfer_submit(async_in_transfer * transfer);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * async_in_transfer_get_results(async_in_transfer * transfer,
+ void * buffer, size_t * transferred, libusbp_error ** transfer_error);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * async_in_transfer_cancel(async_in_transfer * transfer);
+
+bool async_in_transfer_pending(async_in_transfer * transfer);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * generic_handle_events(libusbp_generic_handle * handle);
+
+#ifdef _WIN32
+
+LIBUSBP_WARN_UNUSED libusbp_error * create_device(HDEVINFO list,
+ PSP_DEVINFO_DATA info, libusbp_device ** device);
+
+LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(1, 2)
+libusbp_error * error_create_winapi(const char * format, ...);
+
+LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(1, 2)
+libusbp_error * error_create_overlapped(const char * format, ...);
+
+LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(2, 3)
+libusbp_error * error_create_cr(CONFIGRET, const char * format, ...);
+
+LIBUSBP_WARN_UNUSED libusbp_error * get_interface(
+ const char * device_instance_id,
+ uint8_t interface_number,
+ bool composite,
+ HDEVINFO * list,
+ PSP_DEVINFO_DATA info);
+
+LIBUSBP_WARN_UNUSED libusbp_error * create_id_string(
+ HDEVINFO list, PSP_DEVINFO_DATA info, char ** id);
+
+LIBUSBP_WARN_UNUSED libusbp_error * get_filename_from_devinst_and_guid(
+ DEVINST devinst,
+ const GUID * guid,
+ char ** filename);
+
+#endif
+
+#ifdef __linux__
+
+LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(1, 2)
+libusbp_error * error_create_errno(const char * format, ...);
+
+LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(2, 3)
+libusbp_error * error_create_udev(int error_code, const char * format, ...);
+
+LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED
+libusbp_error * error_from_urb_status(struct usbdevfs_urb * urb);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * device_create(struct udev_device * dev, libusbp_device ** device);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * generic_interface_get_device_copy(
+ const libusbp_generic_interface * gi, libusbp_device ** device);
+
+/** udevw **********************************************************************/
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_create_context(struct udev **);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_create_usb_list(struct udev * context, struct udev_enumerate ** list);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_get_device_from_syspath(struct udev *,
+ const char * syspath, struct udev_device ** dev);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_get_device_type(struct udev_device *, const char ** devtype);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_get_sysattr_uint8(
+ struct udev_device * dev, const char * name, uint8_t * value);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_get_sysattr_uint16(
+ struct udev_device * dev, const char * name, uint16_t * value);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_get_sysattr_if_exists_copy(
+ struct udev_device * dev, const char * name, char ** value);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_get_interface(
+ struct udev * udev,
+ const char * device_syspath,
+ uint8_t interface_number,
+ struct udev_device ** device);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_get_tty(
+ struct udev * udev,
+ struct udev_device * parent,
+ struct udev_device ** device);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_get_syspath(struct udev_device * device, const char ** syspath);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_get_syspath_copy(struct udev_device * device, char ** devnode);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_get_devnode(struct udev_device * device, const char ** devnode);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_get_devnode_copy(struct udev_device * device, char ** devnode);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * udevw_get_devnode_copy_from_syspath(const char * syspath, char ** devnode);
+
+
+/** usbfd **********************************************************************/
+
+LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED
+libusbp_error * usbfd_check_existence(const char * filename);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * usbfd_open(const char * path, int * fd);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * usbfd_get_device_descriptor(int fd, struct usb_device_descriptor * desc);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * usbfd_control_transfer(int fd, libusbp_setup_packet setup,
+ uint32_t timeout, void * data, size_t * transferred);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * usbfd_control_transfer_async(int fd, void * combined_buffer,
+ size_t size, uint32_t timeout, void * user_context);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * usbfd_bulk_or_interrupt_transfer(int fd, uint8_t pipe, uint32_t timeout,
+ void * buffer, size_t size, size_t * transferred);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * usbfd_submit_urb(int fd, struct usbdevfs_urb * urb);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * usbfd_reap_urb(int fd, struct usbdevfs_urb ** urb);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * usbfd_discard_urb(int fd, struct usbdevfs_urb * urb);
+
+#endif
+
+#ifdef __APPLE__
+
+LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(2, 3)
+libusbp_error * error_create_mach(kern_return_t error_code, const char * format, ...);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * create_device(io_service_t service, libusbp_device ** device);
+
+uint64_t device_get_id(const libusbp_device * device);
+
+bool generic_interface_get_interface_id(const libusbp_generic_interface * gi, uint64_t * id);
+uint64_t generic_interface_get_device_id(const libusbp_generic_interface * gi);
+uint8_t generic_interface_get_interface_number(const libusbp_generic_interface * gi);
+
+IOUSBInterfaceInterface182 ** generic_handle_get_ioh(const libusbp_generic_handle * handle);
+
+uint8_t generic_handle_get_pipe_index(const libusbp_generic_handle * handle, uint8_t pipe_id);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * iokit_id_to_string(uint64_t id, char ** str);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * service_get_from_id(uint64_t id, io_service_t *);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * service_get_usb_interface(io_service_t service,
+ uint8_t interface_number, io_service_t * interface_service);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * service_get_child_by_class(io_service_t service,
+ const char * class_name, io_service_t * interface_service);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * service_to_interface(io_service_t, CFUUIDRef pluginType, REFIID rid,
+ void **, IOCFPlugInInterface ***);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * get_id(io_registry_entry_t entry, uint64_t * id);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * get_string(io_registry_entry_t entry,
+ CFStringRef name, char ** value);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * get_int32(io_registry_entry_t entry,
+ CFStringRef name, int32_t * value);
+
+LIBUSBP_WARN_UNUSED
+libusbp_error * get_uint16(io_registry_entry_t entry,
+ CFStringRef name, uint16_t * value);
+
+#endif
+
+#if defined(_WIN32) || defined(__APPLE__)
+
+LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(2, 3)
+libusbp_error * error_create_hr(HRESULT, const char * format, ...);
+
+#endif
diff --git a/dep/libusbp/src/linux/async_in_transfer_linux.c b/dep/libusbp/src/linux/async_in_transfer_linux.c
new file mode 100644
index 00000000..e8ab6bc1
--- /dev/null
+++ b/dep/libusbp/src/linux/async_in_transfer_linux.c
@@ -0,0 +1,187 @@
+#include
+
+struct async_in_transfer
+{
+ struct usbdevfs_urb urb;
+ bool pending;
+ libusbp_error * error;
+ int fd;
+};
+
+libusbp_error * async_in_pipe_setup(libusbp_generic_handle * handle, uint8_t pipe_id)
+{
+ LIBUSBP_UNUSED(handle);
+ LIBUSBP_UNUSED(pipe_id);
+ return NULL;
+}
+
+libusbp_error * async_in_transfer_create(
+ libusbp_generic_handle * handle, uint8_t pipe_id, size_t transfer_size,
+ async_in_transfer ** transfer)
+{
+ assert(transfer_size != 0);
+ assert(transfer != NULL);
+
+ if (transfer_size > INT_MAX)
+ {
+ // usbdevfs_urb uses ints to represent sizes.
+ return error_create("Transfer size is too large.");
+ }
+
+ libusbp_error * error = NULL;
+
+ // Allocate the transfer struct.
+ async_in_transfer * new_transfer = NULL;
+ if (error == NULL)
+ {
+ new_transfer = calloc(1, sizeof(async_in_transfer));
+ if (new_transfer == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ // Allocate the buffer for the transfer.
+ void * new_buffer = NULL;
+ if (error == NULL)
+ {
+ new_buffer = malloc(transfer_size);
+ if (new_buffer == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ // Assemble the transfer and pass it to the caller.
+ if (error == NULL)
+ {
+ new_transfer->fd = libusbp_generic_handle_get_fd(handle);
+
+ new_transfer->urb.usercontext = new_transfer;
+ new_transfer->urb.buffer_length = transfer_size;
+ new_transfer->urb.type = USBDEVFS_URB_TYPE_BULK;
+ new_transfer->urb.endpoint = pipe_id;
+
+ new_transfer->urb.buffer = new_buffer;
+ new_buffer = NULL;
+
+ *transfer = new_transfer;
+ new_transfer = NULL;
+ }
+
+ free(new_buffer);
+ free(new_transfer);
+ return error;
+}
+
+void async_in_transfer_free(async_in_transfer * transfer)
+{
+ if (transfer == NULL) { return; }
+
+ if (transfer->pending)
+ {
+ // Unfortunately, this transfer is still pending, so we cannot free it;
+ // the kernel needs to be able to write to this transfer's memory when
+ // it completes. We could just abort the process here, but instead we
+ // we choose to have a memory leak, since it can be easily detected and
+ // might be small enough to be harmless.
+ return;
+ }
+
+ libusbp_error_free(transfer->error);
+ free(transfer->urb.buffer);
+ free(transfer);
+}
+
+void async_in_transfer_submit(async_in_transfer * transfer)
+{
+ assert(transfer != NULL);
+ assert(transfer->pending == false);
+
+ libusbp_error_free(transfer->error);
+ transfer->error = NULL;
+ transfer->pending = true;
+
+ libusbp_error * error = usbfd_submit_urb(transfer->fd, &transfer->urb);
+ if (error != NULL)
+ {
+ transfer->pending = false;
+ transfer->error = error;
+ }
+}
+
+void async_in_transfer_handle_completion(async_in_transfer * transfer)
+{
+ assert(transfer != NULL);
+
+ #ifdef LIBUSBP_LOG
+ fprintf(stderr, "URB completed: %p, status=%d, actual_length=%d\n",
+ transfer, transfer->urb.status, transfer->urb.actual_length);
+ #endif
+
+ libusbp_error * error = NULL;
+
+ if (error == NULL)
+ {
+ error = error_from_urb_status(&transfer->urb);
+ }
+
+ if (error == NULL && transfer->urb.error_count != 0)
+ {
+ error = error_create("Non-zero error count for USB request: %d.",
+ transfer->urb.error_count);
+ }
+
+ if (error != NULL)
+ {
+ error = error_add(error, "Asynchronous IN transfer failed.");
+ }
+
+ transfer->pending = false;
+ transfer->error = error;
+}
+
+libusbp_error * async_in_transfer_get_results(async_in_transfer * transfer,
+ void * buffer, size_t * transferred, libusbp_error ** transfer_error)
+{
+ assert(transfer != NULL);
+ assert(!transfer->pending);
+
+ size_t tmp_transferred = transfer->urb.actual_length;
+
+ // Make sure we don't overflow the user's buffer.
+ if (tmp_transferred > (size_t)transfer->urb.buffer_length)
+ {
+ tmp_transferred = transfer->urb.buffer_length;
+ }
+
+ if (buffer != NULL)
+ {
+ memcpy(buffer, transfer->urb.buffer, tmp_transferred);
+ }
+
+ if (transferred != NULL)
+ {
+ *transferred = tmp_transferred;
+ }
+
+ if (transfer_error != NULL)
+ {
+ *transfer_error = libusbp_error_copy(transfer->error);
+ }
+
+ return NULL;
+}
+
+libusbp_error * async_in_transfer_cancel(async_in_transfer * transfer)
+{
+ if (transfer == NULL) { return NULL; }
+
+ return usbfd_discard_urb(transfer->fd, &transfer->urb);
+}
+
+bool async_in_transfer_pending(async_in_transfer * transfer)
+{
+ assert(transfer != NULL);
+ return transfer->pending;
+}
diff --git a/dep/libusbp/src/linux/device_linux.c b/dep/libusbp/src/linux/device_linux.c
new file mode 100644
index 00000000..74bd1e25
--- /dev/null
+++ b/dep/libusbp/src/linux/device_linux.c
@@ -0,0 +1,256 @@
+#include
+
+struct libusbp_device
+{
+ char * syspath;
+ char * serial_number; // may be NULL
+ uint16_t product_id;
+ uint16_t vendor_id;
+ uint16_t revision;
+};
+
+libusbp_error * device_create(struct udev_device * dev, libusbp_device ** device)
+{
+ assert(dev != NULL);
+ assert(device != NULL);
+
+ libusbp_error * error = NULL;
+
+ // Allocate memory for the device.
+ libusbp_device * new_device = NULL;
+ if (error == NULL)
+ {
+ new_device = malloc(sizeof(libusbp_device));
+ if (new_device == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ // Get the syspath.
+ char * new_syspath = NULL;
+ if (error == NULL)
+ {
+ error = udevw_get_syspath_copy(dev, &new_syspath);
+ }
+
+ // Get the vendor ID.
+ uint16_t vendor_id;
+ if (error == NULL)
+ {
+ error = udevw_get_sysattr_uint16(dev, "idVendor", &vendor_id);
+ }
+
+ // Get the product ID.
+ uint16_t product_id;
+ if (error == NULL)
+ {
+ error = udevw_get_sysattr_uint16(dev, "idProduct", &product_id);
+ }
+
+ // Get the revision.
+ uint16_t revision;
+ if (error == NULL)
+ {
+ error = udevw_get_sysattr_uint16(dev, "bcdDevice", &revision);
+ }
+
+ // Get the serial number.
+ char * new_serial_number = NULL;
+ if (error == NULL)
+ {
+ error = udevw_get_sysattr_if_exists_copy(dev, "serial", &new_serial_number);
+ }
+
+ // Populate the new device and give it to the caller.
+ if (error == NULL)
+ {
+ new_device->syspath = new_syspath;
+ new_device->serial_number = new_serial_number;
+ new_device->vendor_id = vendor_id;
+ new_device->product_id = product_id;
+ new_device->revision = revision;
+ *device = new_device;
+
+ new_syspath = NULL;
+ new_serial_number = NULL;
+ new_device = NULL;
+ }
+
+ free(new_serial_number);
+ free(new_syspath);
+ free(new_device);
+ return error;
+}
+
+void libusbp_device_free(libusbp_device * device)
+{
+ if (device != NULL)
+ {
+ free(device->serial_number);
+ free(device->syspath);
+ free(device);
+ }
+}
+
+libusbp_error * libusbp_device_copy(
+ const libusbp_device * source, libusbp_device ** dest)
+{
+ if (dest == NULL)
+ {
+ return error_create("Device output pointer is null.");
+ }
+
+ *dest = NULL;
+
+ if (source == NULL) { return NULL; }
+
+ libusbp_error * error = NULL;
+
+ // Allocate memory for the device.
+ libusbp_device * new_device = NULL;
+ if (error == NULL)
+ {
+ new_device = malloc(sizeof(libusbp_device));
+ if (new_device == NULL) { error = &error_no_memory; }
+ }
+
+ // Copy the syspath.
+ char * new_syspath = NULL;
+ if (error == NULL)
+ {
+ assert(source->syspath != NULL);
+ error = string_copy(source->syspath, &new_syspath);
+ }
+
+ // Copy the serial number if it is not NULL.
+ char * new_serial_number = NULL;
+ if (error == NULL && source->serial_number != NULL)
+ {
+ error = string_copy(source->serial_number, &new_serial_number);
+ }
+
+ // Assemble the new device and give it to the caller.
+ if (error == NULL)
+ {
+ memcpy(new_device, source, sizeof(libusbp_device));
+ new_device->syspath = new_syspath;
+ new_device->serial_number = new_serial_number;
+ *dest = new_device;
+
+ new_device = NULL;
+ new_syspath = NULL;
+ new_serial_number = NULL;
+ }
+
+ // Clean up and return.
+ free(new_device);
+ free(new_syspath);
+ free(new_serial_number);
+ return error;
+}
+
+libusbp_error * libusbp_device_get_vendor_id(
+ const libusbp_device * device,
+ uint16_t * vendor_id)
+{
+ if (vendor_id == NULL)
+ {
+ return error_create("Vendor ID output pointer is null.");
+ }
+
+ *vendor_id = 0;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ *vendor_id = device->vendor_id;
+ return NULL;
+}
+
+libusbp_error * libusbp_device_get_product_id(
+ const libusbp_device * device,
+ uint16_t * product_id)
+{
+ if (product_id == NULL)
+ {
+ return error_create("Product ID output pointer is null.");
+ }
+
+ *product_id = 0;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ *product_id = device->product_id;
+ return NULL;
+}
+
+libusbp_error * libusbp_device_get_revision(
+ const libusbp_device * device,
+ uint16_t * revision)
+{
+ if (revision == NULL)
+ {
+ return error_create("Device revision output pointer is null.");
+ }
+
+ *revision = 0;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ *revision = device->revision;
+ return NULL;
+}
+
+libusbp_error * libusbp_device_get_serial_number(
+ const libusbp_device * device,
+ char ** serial_number)
+{
+ if (serial_number == NULL)
+ {
+ return error_create("Serial number output pointer is null.");
+ }
+
+ *serial_number = NULL;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ if (device->serial_number == NULL)
+ {
+ libusbp_error * error = error_create("Device does not have a serial number.");
+ error = error_add_code(error, LIBUSBP_ERROR_NO_SERIAL_NUMBER);
+ return error;
+ }
+
+ return string_copy(device->serial_number, serial_number);
+}
+
+libusbp_error * libusbp_device_get_os_id(
+ const libusbp_device * device,
+ char ** id)
+{
+ if (id == NULL)
+ {
+ return error_create("Device OS ID output pointer is null.");
+ }
+
+ *id = NULL;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ return string_copy(device->syspath, id);
+}
diff --git a/dep/libusbp/src/linux/error_linux.c b/dep/libusbp/src/linux/error_linux.c
new file mode 100644
index 00000000..7bcf1cc3
--- /dev/null
+++ b/dep/libusbp/src/linux/error_linux.c
@@ -0,0 +1,123 @@
+/* This file has functions for converting Windows error codes libusbp_error
+ * objects. See error_message_test.cpp for documentation/justification of the
+ * conversions that are performed. */
+
+#include
+
+libusbp_error * error_create_errno(const char * format, ...)
+{
+ int error_code = errno;
+
+ libusbp_error * error = error_create("Error code %d.", error_code);
+
+ bool skip_standard_message = false;
+
+ switch(error_code)
+ {
+ case EACCES:
+ error = error_add_code(error, LIBUSBP_ERROR_ACCESS_DENIED);
+ break;
+
+ case ENOMEM:
+ error = error_add_code(error, LIBUSBP_ERROR_MEMORY);
+ break;
+
+ case EPIPE:
+ skip_standard_message = true;
+ error = error_add(error,
+ "The request was invalid or there was an I/O problem.");
+ error = error_add_code(error, LIBUSBP_ERROR_STALL);
+ error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
+ break;
+
+ case ENODEV:
+ case ESHUTDOWN:
+ skip_standard_message = true;
+ error = error_add(error, "The device was removed.");
+ error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
+ break;
+
+ case EPROTO:
+ case ETIME:
+ error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
+ break;
+
+ case ETIMEDOUT:
+ skip_standard_message = true;
+ error = error_add(error, "The operation timed out.");
+ error = error_add_code(error, LIBUSBP_ERROR_TIMEOUT);
+ break;
+
+ case EOVERFLOW:
+ skip_standard_message = true;
+ error = error_add(error, "The transfer overflowed.");
+ break;
+
+ case EILSEQ:
+ skip_standard_message = true;
+ error = error_add(error,
+ "Illegal byte sequence: the device may have been disconnected or the request may have been cancelled.");
+ error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
+ error = error_add_code(error, LIBUSBP_ERROR_CANCELLED);
+ break;
+ }
+
+ if (!skip_standard_message)
+ {
+ // We use strerror_r because strerror is not guaranteed to be
+ // thread-safe. Also note that strerror_r does depend on the
+ // locale.
+ char buffer[256];
+ int result = strerror_r(error_code, buffer, sizeof(buffer) - 1);
+ if (result == 0)
+ {
+ error = error_add(error, "%s.", buffer);
+ }
+ }
+
+ // Finally, add the context message provided by the caller.
+ va_list ap;
+ va_start(ap, format);
+ error = error_add_v(error, format, ap);
+ va_end(ap);
+
+ return error;
+}
+
+libusbp_error * error_from_urb_status(struct usbdevfs_urb * urb)
+{
+ libusbp_error * error = NULL;
+
+ int error_code = -urb->status;
+
+ switch(error_code)
+ {
+ case 0: // Success
+ break;
+
+ case ENOENT:
+ error = error_create("Error code %d.", error_code);
+ error = error_add(error, "The operation was cancelled.");
+ error = error_add_code(error, LIBUSBP_ERROR_CANCELLED);
+ break;
+
+ default:
+ errno = error_code;
+ error = error_create_errno("");
+ break;
+ }
+
+ return error;
+}
+
+libusbp_error * error_create_udev(int error_code, const char * format, ...)
+{
+ libusbp_error * error = error_create("Error from libudev: %d.", error_code);
+
+ va_list ap;
+ va_start(ap, format);
+ error = error_add_v(error, format, ap);
+ va_end(ap);
+
+ return error;
+}
diff --git a/dep/libusbp/src/linux/generic_handle_linux.c b/dep/libusbp/src/linux/generic_handle_linux.c
new file mode 100644
index 00000000..f65b51c8
--- /dev/null
+++ b/dep/libusbp/src/linux/generic_handle_linux.c
@@ -0,0 +1,389 @@
+#include
+
+struct libusbp_generic_handle
+{
+ libusbp_device * device;
+ int fd;
+
+ // Timeouts are stored in milliseconds. 0 is forever.
+ uint32_t in_timeout[MAX_ENDPOINT_NUMBER + 1];
+ uint32_t out_timeout[MAX_ENDPOINT_NUMBER + 1];
+};
+
+// Allocates memory structures and opens the device file, but does read or write
+// anything.
+static libusbp_error * generic_handle_setup(
+ const libusbp_generic_interface * gi,
+ libusbp_generic_handle ** handle)
+{
+ assert(gi != NULL);
+ assert(handle != NULL);
+
+ libusbp_error * error = NULL;
+
+ // Allocate memory for the handle.
+ libusbp_generic_handle * new_handle = NULL;
+ if (error == NULL)
+ {
+ new_handle = calloc(1, sizeof(libusbp_generic_handle));
+ if (handle == NULL) { error = &error_no_memory; }
+ }
+
+ // Copy the device.
+ libusbp_device * new_device = NULL;
+ if (error == NULL)
+ {
+ error = generic_interface_get_device_copy(gi, &new_device);
+ }
+
+ // Get the filename.
+ char * new_filename = NULL;
+ if (error == NULL)
+ {
+ error = libusbp_generic_interface_get_os_filename(gi, &new_filename);
+ }
+
+ // Open the file.
+ int new_fd = -1;
+ if (error == NULL)
+ {
+ error = usbfd_open(new_filename, &new_fd);
+ }
+
+ // Assemble the handle and pass it to the caller.
+ if (error == NULL)
+ {
+ new_handle->fd = new_fd;
+ new_fd = -1;
+
+ new_handle->device = new_device;
+ new_device = NULL;
+
+ *handle = new_handle;
+ new_handle = NULL;
+ }
+
+ if (new_fd != -1) { close(new_fd); }
+ libusbp_string_free(new_filename);
+ libusbp_device_free(new_device);
+ free(new_handle);
+ return error;
+}
+
+// Reads the device descriptor from the device and makes sure that certain
+// fields in it match what we were expecting. This should help detect the
+// situation where devices were changed between the time that the libusbp_device
+// object was created and the time that this handle was created.
+static libusbp_error * check_device_descriptor(
+ libusbp_generic_handle * handle)
+{
+ assert(handle != NULL);
+
+ libusbp_error * error = NULL;
+
+ struct usb_device_descriptor desc;
+ if (error == NULL)
+ {
+ error = usbfd_get_device_descriptor(handle->fd, &desc);
+ }
+
+ uint16_t vendor_id;
+ if (error == NULL)
+ {
+ error = libusbp_device_get_vendor_id(handle->device, &vendor_id);
+ }
+ if (error == NULL && desc.idVendor != vendor_id)
+ {
+ error = error_create("Vendor ID mismatch: 0x%04x != 0x%04x.",
+ desc.idVendor, vendor_id);
+ }
+
+ uint16_t product_id;
+ if (error == NULL)
+ {
+ error = libusbp_device_get_product_id(handle->device, &product_id);
+ }
+ if (error == NULL && desc.idProduct != product_id)
+ {
+ error = error_create("Product ID mismatch: 0x%04x != 0x%04x.",
+ desc.idProduct, product_id);
+ }
+
+ uint16_t revision;
+ if (error == NULL)
+ {
+ error = libusbp_device_get_revision(handle->device, &revision);
+ }
+ if (error == NULL && desc.bcdDevice != revision)
+ {
+ error = error_create("Device revision mismatch: 0x%04x != 0x%04x.",
+ desc.bcdDevice, revision);
+ }
+
+ return error;
+}
+
+libusbp_error * libusbp_generic_handle_open(
+ const libusbp_generic_interface * gi,
+ libusbp_generic_handle ** handle)
+{
+ if (handle == NULL)
+ {
+ return error_create("Generic handle output pointer is null.");
+ }
+
+ *handle = NULL;
+
+ if (gi == NULL)
+ {
+ return error_create("Generic interface is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ // Set up the memory structures and open the file handle.
+ libusbp_generic_handle * new_handle = NULL;
+ if (error == NULL)
+ {
+ error = generic_handle_setup(gi, &new_handle);
+ }
+
+ // Check that the device descriptor is consistent.
+ if (error == NULL)
+ {
+ error = check_device_descriptor(new_handle);
+ }
+
+ // Pass the handle to the caller.
+ if (error == NULL)
+ {
+ *handle = new_handle;
+ new_handle = NULL;
+ }
+
+ libusbp_generic_handle_close(new_handle);
+ return error;
+}
+
+void libusbp_generic_handle_close(libusbp_generic_handle * handle)
+{
+ if (handle != NULL)
+ {
+ close(handle->fd);
+ libusbp_device_free(handle->device);
+ free(handle);
+ }
+}
+
+libusbp_error * libusbp_generic_handle_open_async_in_pipe(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ libusbp_async_in_pipe ** pipe)
+{
+ return async_in_pipe_create(handle, pipe_id, pipe);
+}
+
+libusbp_error * libusbp_generic_handle_set_timeout(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ uint32_t timeout)
+{
+ if (handle == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ if (error == NULL)
+ {
+ error = check_pipe_id(pipe_id);
+ }
+
+ if (error == NULL)
+ {
+ uint8_t endpoint_number = pipe_id & MAX_ENDPOINT_NUMBER;
+
+ if (pipe_id & 0x80)
+ {
+ handle->in_timeout[endpoint_number] = timeout;
+ }
+ else
+ {
+ handle->out_timeout[endpoint_number] = timeout;
+ }
+ }
+
+ return error;
+}
+
+libusbp_error * libusbp_control_transfer(
+ libusbp_generic_handle * handle,
+ uint8_t bmRequestType,
+ uint8_t bRequest,
+ uint16_t wValue,
+ uint16_t wIndex,
+ void * data,
+ uint16_t wLength,
+ size_t * transferred)
+{
+ if (transferred != NULL)
+ {
+ *transferred = 0;
+ }
+
+ if (handle == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ struct libusbp_setup_packet setup;
+ setup.bmRequestType = bmRequestType;
+ setup.bRequest = bRequest;
+ setup.wValue = wValue;
+ setup.wIndex = wIndex;
+ setup.wLength = wLength;
+
+ return usbfd_control_transfer(handle->fd, setup,
+ handle->out_timeout[0], data, transferred);
+}
+
+libusbp_error * libusbp_read_pipe(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ void * data,
+ size_t size,
+ size_t * transferred)
+{
+ if (transferred != NULL)
+ {
+ *transferred = 0;
+ }
+
+ if (handle == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ if (error == NULL)
+ {
+ error = check_pipe_id_in(pipe_id);
+ }
+
+ if (error == NULL)
+ {
+ uint8_t endpoint_number = pipe_id & MAX_ENDPOINT_NUMBER;
+ uint32_t timeout = handle->in_timeout[endpoint_number];
+ error = usbfd_bulk_or_interrupt_transfer(
+ handle->fd, pipe_id, timeout, data, size, transferred);
+ }
+
+ if (error != NULL)
+ {
+ error = error_add(error, "Failed to read from pipe.");
+ }
+ return error;
+}
+
+libusbp_error * libusbp_write_pipe(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ const void * data,
+ size_t size,
+ size_t * transferred)
+{
+ if (transferred != NULL)
+ {
+ *transferred = 0;
+ }
+
+ if (handle == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ if (error == NULL)
+ {
+ error = check_pipe_id_out(pipe_id);
+ }
+
+ if (error == NULL)
+ {
+ uint8_t endpoint_number = pipe_id & MAX_ENDPOINT_NUMBER;
+ uint32_t timeout = handle->out_timeout[endpoint_number];
+ error = usbfd_bulk_or_interrupt_transfer(
+ handle->fd, pipe_id, timeout, (void *)data, size, transferred);
+ }
+
+ if (error != NULL)
+ {
+ error = error_add(error, "Failed to write to pipe.");
+ }
+ return error;
+}
+
+LIBUSBP_WARN_UNUSED
+static libusbp_error * handle_completed_urb(struct usbdevfs_urb * urb)
+{
+ if (urb->usercontext == NULL)
+ {
+ return error_create("A completed USB request block has a NULL usercontext.");
+ }
+
+ if (urb->type == USBDEVFS_URB_TYPE_BULK && (urb->endpoint & 0x80))
+ {
+ async_in_transfer * transfer = urb->usercontext;
+ async_in_transfer_handle_completion(transfer);
+ return NULL;
+ }
+ else
+ {
+ return error_create("A completed USB request block was unrecognized.");
+ }
+}
+
+libusbp_error * generic_handle_events(libusbp_generic_handle * handle)
+{
+ if (handle == NULL)
+ {
+ return error_create("Generic handle argument is null.");
+ }
+
+ while(true)
+ {
+ struct usbdevfs_urb * urb;
+ libusbp_error * error = usbfd_reap_urb(handle->fd, &urb);
+ if (error != NULL)
+ {
+ // There was some problem, like the device being disconnected.
+ return error;
+ }
+
+ if (urb == NULL)
+ {
+ // No more URBs left to reap.
+ return NULL;
+ }
+
+ error = handle_completed_urb(urb);
+ if (error != NULL)
+ {
+ return error;
+ }
+ }
+
+ return NULL;
+}
+
+int libusbp_generic_handle_get_fd(libusbp_generic_handle * handle)
+{
+ if (handle == NULL)
+ {
+ return -1;
+ }
+ return handle->fd;
+}
diff --git a/dep/libusbp/src/linux/generic_interface_linux.c b/dep/libusbp/src/linux/generic_interface_linux.c
new file mode 100644
index 00000000..06c9e823
--- /dev/null
+++ b/dep/libusbp/src/linux/generic_interface_linux.c
@@ -0,0 +1,276 @@
+#include
+
+struct libusbp_generic_interface
+{
+ libusbp_device * device;
+
+ uint8_t interface_number;
+
+ // A sysfs path like "/sys/devices/pci0000:00/0000:00:06.0/usb1/1-2/1-2:1.0"
+ char * syspath;
+
+ // A filename like "/dev/bus/usb/001/007"
+ char * filename;
+};
+
+libusbp_error * check_driver_installation(struct udev_device * device)
+{
+ assert(device != NULL);
+ const char * driver_name = udev_device_get_driver(device);
+ if (driver_name != NULL && strcmp(driver_name, "usbfs") && strcmp(driver_name, "cp210x"))
+ {
+ return error_create("Device is attached to an incorrect driver: %s.", driver_name);
+ }
+ return NULL;
+}
+
+libusbp_error * libusbp_generic_interface_create(
+ const libusbp_device * device,
+ uint8_t interface_number,
+ bool composite __attribute__((unused)),
+ libusbp_generic_interface ** gi)
+{
+ if (gi == NULL)
+ {
+ return error_create("Generic interface output pointer is null.");
+ }
+
+ *gi = NULL;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ // Allocate memory for the interface.
+ libusbp_generic_interface * new_gi = NULL;
+ if (error == NULL)
+ {
+ new_gi = malloc(sizeof(libusbp_generic_interface));
+ if (new_gi == NULL) { error = &error_no_memory; }
+ }
+
+ // Make a copy of the device (since the original device could be freed
+ // before this generic interface is freed.)
+ libusbp_device * new_device = NULL;
+ if (error == NULL)
+ {
+ error = libusbp_device_copy(device, &new_device);
+ }
+
+ // Get the syspath of the device.
+ char * new_device_syspath = NULL;
+ if (error == NULL)
+ {
+ error = libusbp_device_get_os_id(new_device, &new_device_syspath);
+ }
+
+ // Get a udev context.
+ struct udev * new_udev = NULL;
+ if (error == NULL)
+ {
+ error = udevw_create_context(&new_udev);
+ }
+
+ // Get the device for the interface.
+ struct udev_device * new_dev = NULL;
+ if (error == NULL)
+ {
+ error = udevw_get_interface(new_udev, new_device_syspath,
+ interface_number, &new_dev);
+ }
+
+ // Make sure it is not attached to a kernel driver.
+ // Note: This step might be inappropriate, since libusbp can operate
+ // on some devices that are attached to a kernel driver, like the cp210x
+ // driver.
+ if (error == NULL)
+ {
+ error = check_driver_installation(new_dev);
+ }
+
+ // Get the syspath of the interface.
+ char * new_interface_syspath = NULL;
+ if (error == NULL)
+ {
+ error = udevw_get_syspath_copy(new_dev, &new_interface_syspath);
+ }
+
+ // Get the filename.
+ char * new_filename = NULL;
+ if (error == NULL)
+ {
+ error = udevw_get_devnode_copy_from_syspath(new_device_syspath, &new_filename);
+ }
+
+ // Check that the file exists yet, but don't check to see if we have permission
+ // to open it. This behavior was chosen to be as similar to the Windows
+ // behavior as possible.
+ if (error == NULL)
+ {
+ error = usbfd_check_existence(new_filename);
+ }
+
+ // Assemble the new generic interface and pass it to the caller.
+ if (error == NULL)
+ {
+ new_gi->interface_number = interface_number;
+
+ new_gi->device = new_device;
+ new_device = NULL;
+
+ new_gi->syspath = new_interface_syspath;
+ new_interface_syspath = NULL;
+
+ new_gi->filename = new_filename;
+ new_filename = NULL;
+
+ *gi = new_gi;
+ new_gi = NULL;
+ }
+
+ if (error != NULL)
+ {
+ error = error_add(error, "Failed to initialize generic interface.");
+ }
+
+ libusbp_string_free(new_filename);
+ libusbp_string_free(new_interface_syspath);
+ libusbp_string_free(new_device_syspath);
+ libusbp_device_free(new_device);
+ free(new_gi);
+ if (new_dev != NULL) { udev_device_unref(new_dev); }
+ if (new_udev != NULL) { udev_unref(new_udev); }
+ return error;
+}
+
+void libusbp_generic_interface_free(libusbp_generic_interface * gi)
+{
+ if (gi != NULL)
+ {
+ libusbp_device_free(gi->device);
+ libusbp_string_free(gi->syspath);
+ libusbp_string_free(gi->filename);
+ free(gi);
+ }
+}
+
+libusbp_error * libusbp_generic_interface_copy(
+ const libusbp_generic_interface * source,
+ libusbp_generic_interface ** dest)
+{
+ if (dest == NULL)
+ {
+ return error_create("Generic interface output pointer is null.");
+ }
+
+ *dest = NULL;
+
+ if (source == NULL)
+ {
+ return NULL;
+ }
+
+ libusbp_error * error = NULL;
+
+ // Allocate memory for the generic interface.
+ libusbp_generic_interface * new_gi = NULL;
+ if (error == NULL)
+ {
+ new_gi = malloc(sizeof(libusbp_generic_interface));
+ if (new_gi == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ // Copy the device.
+ libusbp_device * new_device = NULL;
+ if (error == NULL)
+ {
+ error = libusbp_device_copy(source->device, &new_device);
+ }
+
+ // Copy the syspath.
+ char * new_syspath = NULL;
+ if (error == NULL)
+ {
+ error = string_copy(source->syspath, &new_syspath);
+ }
+
+ // Copy the filename.
+ char * new_filename = NULL;
+ if (error == NULL)
+ {
+ error = string_copy(source->filename, &new_filename);
+ }
+
+ // Assemble the new interface and return it to the caller.
+ if (error == NULL)
+ {
+ memcpy(new_gi, source, sizeof(libusbp_generic_interface));
+ new_gi->device = new_device;
+ new_gi->syspath = new_syspath;
+ new_gi->filename = new_filename;
+ *dest = new_gi;
+
+ new_filename = NULL;
+ new_syspath = NULL;
+ new_device = NULL;
+ new_gi = NULL;
+ }
+
+ libusbp_string_free(new_filename);
+ libusbp_string_free(new_syspath);
+ libusbp_device_free(new_device);
+ free(new_gi);
+ return NULL;
+}
+
+libusbp_error * libusbp_generic_interface_get_os_id(
+ const libusbp_generic_interface * gi, char ** id)
+{
+ if (id == NULL)
+ {
+ return error_create("String output pointer is null.");
+ }
+
+ *id = NULL;
+
+ if (gi == NULL)
+ {
+ return error_create("Generic interface is null.");
+ }
+
+ assert(gi->syspath != NULL);
+ return string_copy(gi->syspath, id);
+}
+
+libusbp_error * libusbp_generic_interface_get_os_filename(
+ const libusbp_generic_interface * gi, char ** filename)
+{
+ if (filename == NULL)
+ {
+ return error_create("String output pointer is null.");
+ }
+
+ *filename = NULL;
+
+ if (gi == NULL)
+ {
+ return error_create("Generic interface is null.");
+ }
+
+ assert(gi->filename != NULL);
+ return string_copy(gi->filename, filename);
+}
+
+libusbp_error * generic_interface_get_device_copy(
+ const libusbp_generic_interface * gi, libusbp_device ** device)
+{
+ assert(gi != NULL);
+ assert(device != NULL);
+ return libusbp_device_copy(gi->device, device);
+}
diff --git a/dep/libusbp/src/linux/list_linux.c b/dep/libusbp/src/linux/list_linux.c
new file mode 100644
index 00000000..875c8bd3
--- /dev/null
+++ b/dep/libusbp/src/linux/list_linux.c
@@ -0,0 +1,165 @@
+#include
+
+// Adds a device to the list, while maintaining the loop invariants for the loop
+// in libusbp_list_connected_devices.
+static libusbp_error * add_udev_device_to_list(
+ struct udev_device * dev, libusbp_device *** device_list, size_t * device_count)
+{
+ assert(dev != NULL);
+ assert(device_list != NULL);
+ assert(device_count != NULL);
+
+ libusbp_error * error = NULL;
+
+ // Create a new device.
+ libusbp_device * new_device;
+ error = device_create(dev, &new_device);
+ if (error != NULL) { return error; }
+
+ // Add the device to the list.
+ error = device_list_append(device_list, device_count, new_device);
+ if (error != NULL)
+ {
+ libusbp_device_free(new_device);
+ return error;
+ }
+ return NULL;
+}
+
+static libusbp_error * add_udev_device_to_list_if_needed(
+ struct udev * udev, const char * syspath,
+ libusbp_device *** device_list, size_t * device_count
+)
+{
+ assert(udev != NULL);
+ assert(syspath != NULL);
+ assert(device_list != NULL);
+ assert(device_count != NULL);
+
+ libusbp_error * error = NULL;
+
+ // Get the udev device.
+ struct udev_device * dev = NULL;
+ if (error == NULL)
+ {
+ error = udevw_get_device_from_syspath(udev, syspath, &dev);
+ }
+
+ // Get the devtype, which is typically usb_device or usb_interface.
+ const char * devtype = NULL;
+ if (error == NULL)
+ {
+ error = udevw_get_device_type(dev, &devtype);
+ }
+
+ bool skip = false;
+
+ // Skip the interfaces, just add the actual USB devices.
+ if (error == NULL && !skip && strcmp(devtype, "usb_device") != 0)
+ {
+ skip = true;
+ }
+
+ // Skip devices that have not been initialized yet, because the udev rules
+ // might not be fully applied. They might have the wrong permissions.
+ if (error == NULL && !skip && !udev_device_get_is_initialized(dev))
+ {
+ skip = true;
+ }
+
+ if (error == NULL && !skip)
+ {
+ // This is a USB device, so we do want to add it to the list.
+ error = add_udev_device_to_list(dev, device_list, device_count);
+ }
+
+ if (dev != NULL)
+ {
+ udev_device_unref(dev);
+ }
+ return error;
+}
+
+libusbp_error * libusbp_list_connected_devices(
+ libusbp_device *** device_list, size_t * device_count)
+{
+ if (device_count != NULL)
+ {
+ *device_count = 0;
+ }
+
+ if (device_list == NULL)
+ {
+ return error_create("Device list output pointer is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ // Create a udev context.
+ struct udev * udev = NULL;
+ if (error == NULL)
+ {
+ error = udevw_create_context(&udev);
+ }
+
+ // Create a list of USB devices and interfaces.
+ struct udev_enumerate * enumerate = NULL;
+ if (error == NULL)
+ {
+ error = udevw_create_usb_list(udev, &enumerate);
+ }
+
+ // Allocate a new list.
+ libusbp_device ** new_list = NULL;
+ size_t count = 0;
+ if (error == NULL)
+ {
+ error = device_list_create(&new_list);
+ }
+
+ // Iterate over the devices and add them to the list.
+ if (error == NULL)
+ {
+ struct udev_list_entry * first_entry = udev_enumerate_get_list_entry(enumerate);
+ struct udev_list_entry * list_entry;
+ udev_list_entry_foreach(list_entry, first_entry)
+ {
+ const char * path = udev_list_entry_get_name(list_entry);
+ assert(path != NULL);
+
+ error = add_udev_device_to_list_if_needed(udev, path, &new_list, &count);
+ if (error != NULL)
+ {
+ // Something went wrong when getting information about the
+ // device. When unplugging a device, we often see
+ // udev_device_new_from_syspath return NULL, which could cause
+ // this error. To make the library more robust and usable, we
+ // ignore this error and continue.
+ #ifdef LIBUSBP_LOG
+ fprintf(stderr, "Problem adding device to list: %s\n",
+ libusbp_error_get_message(error));
+ #endif
+ libusbp_error_free(error);
+ error = NULL;
+ }
+ }
+ }
+
+ // Pass the list and the count to the caller.
+ if (error == NULL)
+ {
+ *device_list = new_list;
+ new_list = NULL;
+
+ if (device_count != NULL)
+ {
+ *device_count = count;
+ }
+ }
+
+ // Clean up everything we used.
+ if (enumerate != NULL) { udev_enumerate_unref(enumerate); }
+ if (udev != NULL) { udev_unref(udev); }
+ free_devices_and_list(new_list);
+ return error;
+}
diff --git a/dep/libusbp/src/linux/serial_port_linux.c b/dep/libusbp/src/linux/serial_port_linux.c
new file mode 100644
index 00000000..db68c2a7
--- /dev/null
+++ b/dep/libusbp/src/linux/serial_port_linux.c
@@ -0,0 +1,191 @@
+#include
+
+struct libusbp_serial_port
+{
+ // A sysfs path like "/sys/devices/pci0000:00/0000:00:06.0/usb1/1-2/1-2:1.0/tty/ttyACM0".
+ // It corresponds to a udev device with subsystem "tty".
+ char * syspath;
+
+ // A port filename like "/dev/ttyACM0".
+ char * port_name;
+};
+
+libusbp_error * libusbp_serial_port_create(
+ const libusbp_device * device,
+ uint8_t interface_number,
+ bool composite,
+ libusbp_serial_port ** port)
+{
+ LIBUSBP_UNUSED(composite);
+
+ if (port == NULL)
+ {
+ return error_create("Serial port output pointer is null.");
+ }
+
+ *port = NULL;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ libusbp_serial_port * new_port = NULL;
+ if (error == NULL)
+ {
+ new_port = calloc(1, sizeof(libusbp_serial_port));
+ if (new_port == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ // Get the syspath of the physical device.
+ char * new_device_syspath = NULL;
+ if (error == NULL)
+ {
+ error = libusbp_device_get_os_id(device, &new_device_syspath);
+ }
+
+ // Get a udev context.
+ struct udev * new_udev = NULL;
+ if (error == NULL)
+ {
+ error = udevw_create_context(&new_udev);
+ }
+
+ // Get the USB interface device.
+ struct udev_device * new_interface_dev = NULL;
+ if (error == NULL)
+ {
+ error = udevw_get_interface(new_udev, new_device_syspath,
+ interface_number, &new_interface_dev);
+ }
+
+ // Get the tty device.
+ struct udev_device * new_tty_dev = NULL;
+ if (error == NULL)
+ {
+ error = udevw_get_tty(new_udev, new_interface_dev, &new_tty_dev);
+ }
+
+ // Get the syspath of the tty device.
+ if (error == NULL)
+ {
+ error = udevw_get_syspath_copy(new_tty_dev, &new_port->syspath);
+ }
+
+ // Get the port name (e.g. /dev/ttyACM0)
+ const char * port_name = NULL;
+ if (error == NULL)
+ {
+ port_name = udev_device_get_property_value(new_tty_dev, "DEVNAME");
+ if (port_name == NULL)
+ {
+ error = error_create("The DEVNAME property does not exist.");
+ }
+ }
+
+ // Copy the port name to the new serial port object.
+ if (error == NULL)
+ {
+ error = string_copy(port_name, &new_port->port_name);
+ }
+
+ // Pass the new object to the caller.
+ if (error == NULL)
+ {
+ *port = new_port;
+ new_port = NULL;
+ }
+
+ if (new_tty_dev != NULL) { udev_device_unref(new_tty_dev); }
+ if (new_interface_dev != NULL) { udev_device_unref(new_interface_dev); }
+ if (new_udev != NULL) { udev_unref(new_udev); }
+ libusbp_string_free(new_device_syspath);
+ libusbp_serial_port_free(new_port);
+
+ return error;
+}
+
+void libusbp_serial_port_free(libusbp_serial_port * port)
+{
+ if (port != NULL)
+ {
+ libusbp_string_free(port->syspath);
+ libusbp_string_free(port->port_name);
+ free(port);
+ }
+}
+
+libusbp_error * libusbp_serial_port_copy(const libusbp_serial_port * source,
+ libusbp_serial_port ** dest)
+{
+ if (dest == NULL)
+ {
+ return error_create("Serial port output pointer is null.");
+ }
+
+ *dest = NULL;
+
+ if (source == NULL)
+ {
+ return NULL;
+ }
+
+ libusbp_error * error = NULL;
+
+ // Allocate memory for the new object.
+ libusbp_serial_port * new_port = NULL;
+ if (error == NULL)
+ {
+ new_port = calloc(1, sizeof(libusbp_serial_port));
+ if (new_port == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ // Copy the syspath.
+ if (error == NULL)
+ {
+ error = string_copy(source->syspath, &new_port->syspath);
+ }
+
+ // Copy the port name.
+ if (error == NULL)
+ {
+ error = string_copy(source->port_name, &new_port->port_name);
+ }
+
+ // Pass the new object to the caller.
+ if (error == NULL)
+ {
+ *dest = new_port;
+ new_port = NULL;
+ }
+
+ libusbp_serial_port_free(new_port);
+ return error;
+}
+
+libusbp_error * libusbp_serial_port_get_name(
+ const libusbp_serial_port * port,
+ char ** name)
+{
+ if (name == NULL)
+ {
+ return error_create("String output pointer is null.");
+ }
+
+ *name = NULL;
+
+ if (port == NULL)
+ {
+ return error_create("Serial port is null.");
+ }
+
+ return string_copy(port->port_name, name);
+}
diff --git a/dep/libusbp/src/linux/udev_linux.c b/dep/libusbp/src/linux/udev_linux.c
new file mode 100644
index 00000000..8eda8d49
--- /dev/null
+++ b/dep/libusbp/src/linux/udev_linux.c
@@ -0,0 +1,540 @@
+/* Wrapper functions for libudev that group together certain operations and
+ * provide error handling. */
+
+#include
+
+// Creates a new udev context (struct udev *). If there is no error, the caller
+// must call udev_unref at some point.
+libusbp_error * udevw_create_context(struct udev ** context)
+{
+ assert(context != NULL);
+ *context = udev_new();
+ if (*context == NULL)
+ {
+ return error_create("Failed to create a udev context.");
+ }
+ return NULL;
+}
+
+// Creates a list (struct udev_enumerate *) of all devices
+// that are children of a given parent device.
+static libusbp_error * udevw_create_child_list(
+ struct udev * udev,
+ struct udev_device * parent,
+ struct udev_enumerate ** list)
+{
+ assert(udev != NULL);
+ assert(parent != NULL);
+ assert(list != NULL);
+
+ *list = NULL;
+
+ libusbp_error * error = NULL;
+
+ struct udev_enumerate * new_list = NULL;
+ if (error == NULL)
+ {
+ new_list = udev_enumerate_new(udev);
+ if (new_list == NULL)
+ {
+ error = error_create("Failed to create a udev enumeration context.");
+ }
+ }
+
+ if (error == NULL)
+ {
+ int result = udev_enumerate_add_match_parent(new_list, parent);
+ if (result != 0)
+ {
+ error = error_create_udev(result, "Failed to match by parent device.");
+ }
+ }
+
+ if (error == NULL)
+ {
+ int result = udev_enumerate_scan_devices(new_list);
+ if (result != 0)
+ {
+ error = error_create_udev(result, "Failed to scan devices.");
+ }
+ }
+
+ if (error == NULL)
+ {
+ *list = new_list;
+ new_list = NULL;
+ }
+
+ if (new_list != NULL) { udev_enumerate_unref(new_list); }
+ return error;
+}
+
+// Creates a list (struct udev_enumerate *) of all devices in the "usb"
+// subsystem. This includes overall USB devices (devtype == "usb_device") and
+// also interfaces (devtype == "usb_interface"). If there is no error, the
+// caller must use udev_enumerate_unref at some point.
+libusbp_error * udevw_create_usb_list(struct udev * udev, struct udev_enumerate ** list)
+{
+ assert(udev != NULL);
+ assert(list != NULL);
+
+ *list = NULL;
+
+ libusbp_error * error = NULL;
+
+ struct udev_enumerate * new_list = NULL;
+ if (error == NULL)
+ {
+ new_list = udev_enumerate_new(udev);
+ if (new_list == NULL)
+ {
+ error = error_create("Failed to create a udev enumeration context.");
+ }
+ }
+
+ if (error == NULL)
+ {
+ int result = udev_enumerate_add_match_subsystem(new_list, "usb");
+ if (result != 0)
+ {
+ error = error_create_udev(result, "Failed to add a subsystem match.");
+ }
+ }
+
+ if (error == NULL)
+ {
+ int result = udev_enumerate_scan_devices(new_list);
+ if (result != 0)
+ {
+ error = error_create_udev(result, "Failed to scan devices.");
+ }
+ }
+
+ if (error == NULL)
+ {
+ *list = new_list;
+ new_list = NULL;
+ }
+
+ if (new_list != NULL) { udev_enumerate_unref(new_list); }
+ return error;
+}
+
+// Gets a udev device corresponding to the given syspath. The syspath is the
+// unique identifier that we store in order to refer to devices.
+libusbp_error * udevw_get_device_from_syspath(
+ struct udev * context, const char * syspath, struct udev_device ** dev)
+{
+ assert(context != NULL);
+ assert(dev != NULL);
+ assert(syspath != NULL);
+
+ *dev = udev_device_new_from_syspath(context, syspath);
+ if (*dev == NULL)
+ {
+ // This error can happen when the device is being unplugged.
+ return error_create("Failed to get udev device from syspath: %s.", syspath);
+ }
+
+ return NULL;
+}
+
+// Get the device type of a device, typically "usb_device" or "usb_interface".
+// The device type is returned as a string that you cannot modify and you do not
+// have to free; it is owned by the device.
+libusbp_error * udevw_get_device_type(struct udev_device * dev, const char ** devtype)
+{
+ assert(dev != NULL);
+ assert(devtype != NULL);
+
+ *devtype = udev_device_get_devtype(dev);
+ if (*devtype == NULL)
+ {
+ return error_create("Failed to get device type.");
+ }
+ return NULL;
+}
+
+// Gets a sysattr with the specified name and parses it as a hex uint8_t.
+libusbp_error * udevw_get_sysattr_uint8(
+ struct udev_device * dev, const char * name, uint8_t * value)
+{
+ assert(dev != NULL);
+ assert(name != NULL);
+ assert(value != NULL);
+
+ const char * str = udev_device_get_sysattr_value(dev, name);
+ if (str == NULL)
+ {
+ return error_create("Device does not have sysattr %s.", name);
+ }
+
+ int result = sscanf(str, "%4hhx\n", value);
+ if (result != 1)
+ {
+ return error_create("Failed to parse sysattr %s.", name);
+ }
+ return NULL;
+}
+
+// Gets a sysattr with the specified name and parses it as a hex uint16_t.
+libusbp_error * udevw_get_sysattr_uint16(
+ struct udev_device * dev, const char * name, uint16_t * value)
+{
+ assert(dev != NULL);
+ assert(name != NULL);
+ assert(value != NULL);
+
+ const char * str = udev_device_get_sysattr_value(dev, name);
+ if (str == NULL)
+ {
+ return error_create("Device does not have sysattr %s.", name);
+ }
+
+ int result = sscanf(str, "%4hx\n", value);
+ if (result != 1)
+ {
+ return error_create("Failed to parse sysattr %s.", name);
+ }
+ return NULL;
+}
+
+// Gets a sysattr string and makes a copy of it. The string must be freed with
+// libusbp_string_free(). If the sysattr does not exists, returns a NULL string
+// instead of raising an error.
+libusbp_error * udevw_get_sysattr_if_exists_copy(
+ struct udev_device * dev, const char * name, char ** value)
+{
+ assert(dev != NULL);
+ assert(name != NULL);
+ assert(value != NULL);
+
+ *value = NULL;
+
+ const char * str = udev_device_get_sysattr_value(dev, name);
+ if (str == NULL) { return NULL; }
+ return string_copy(str, value);
+}
+
+// Get the USB device of which this device is a child. This is intended to be
+// run on devices with devtype == "usb_interface" in order to get information
+// about the overall USB device.
+static libusbp_error * udevw_get_parent_usb_device(
+ struct udev_device * dev, struct udev_device ** parent)
+{
+ assert(dev != NULL);
+ assert(parent != NULL);
+ *parent = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
+ if (*parent == NULL)
+ {
+ return error_create("Failed to get parent USB device.");
+ }
+ return NULL;
+}
+
+// Helper function for udevw_get_interface.
+static libusbp_error * udevw_check_if_device_is_specific_interface(
+ struct udev_device * dev, const char * device_syspath,
+ uint8_t interface_number, bool * result)
+{
+ assert(dev != NULL);
+ assert(device_syspath != NULL);
+ assert(result != NULL);
+
+ *result = false;
+
+ libusbp_error * error;
+
+ // Check that the devtype is equal to "usb_interface"
+ const char * devtype;
+ error = udevw_get_device_type(dev, &devtype);
+ if (error != NULL) { return error; }
+ if (strcmp(devtype, "usb_interface")) { return NULL; }
+
+ // Check that bInterfaceNumber matches our interface number.
+ uint8_t actual_interface_number;
+ error = udevw_get_sysattr_uint8(dev, "bInterfaceNumber", &actual_interface_number);
+ if (error != NULL) { return error; }
+ if (actual_interface_number != interface_number) { return NULL; }
+
+ // Get the overall USB device.
+ struct udev_device * parent;
+ error = udevw_get_parent_usb_device(dev, &parent);
+ if (error != NULL) { return error; }
+
+ // Check that the overall USB device is the one we are interested in.
+ const char * parent_syspath = udev_device_get_syspath(parent);
+ assert(parent_syspath != NULL);
+ if (strcmp(parent_syspath, device_syspath)) { return NULL; }
+
+ *result = true;
+ return NULL;
+}
+
+// Finds a udev device of type "usb_interface" that is a child of the specified
+// device and has the specified bInterfaceNumber.
+libusbp_error * udevw_get_interface(
+ struct udev * udev,
+ const char * device_syspath,
+ uint8_t interface_number,
+ struct udev_device ** device)
+{
+ assert(udev != NULL);
+ assert(device_syspath != NULL);
+ assert(device != NULL);
+
+ *device = NULL;
+
+ libusbp_error * error = NULL;
+
+ struct udev_enumerate * list = NULL;
+ if (error == NULL)
+ {
+ // Note: It would probably be better to create a list using
+ // udev_enumerate_add_match_parent so the list is smaller.
+ error = udevw_create_usb_list(udev, &list);
+ }
+
+ // Loop over the list to find the device.
+ struct udev_device * found_device = NULL;
+ if (error == NULL)
+ {
+ struct udev_list_entry * first_entry = udev_enumerate_get_list_entry(list);
+ struct udev_list_entry * list_entry;
+ udev_list_entry_foreach(list_entry, first_entry)
+ {
+ const char * path = udev_list_entry_get_name(list_entry);
+ assert(path != NULL);
+
+ // Get the udev device.
+ struct udev_device * dev = NULL;
+ error = udevw_get_device_from_syspath(udev, path, &dev);
+ if (error != NULL) { break; }
+
+ // See if it is the right device.
+ bool correct_device = false;
+ error = udevw_check_if_device_is_specific_interface(dev, device_syspath,
+ interface_number, &correct_device);
+ if (error != NULL)
+ {
+ udev_device_unref(dev);
+ break;
+ }
+
+ // If it is the right device, stop looping.
+ if (correct_device)
+ {
+ found_device = dev;
+ break;
+ }
+
+ udev_device_unref(dev);
+ }
+ }
+
+ // Make sure we found the device.
+ if (error == NULL && found_device == NULL)
+ {
+ // We did not find it. Maybe the interface is just not ready yet and it
+ // would be ready in a few milliseconds. (Note: We have not seen this
+ // happen, but adding the error code here makes the behavior consistent
+ // with how the Windows code behaves.)
+ error = error_create("Could not find interface %d.", interface_number);
+ error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
+ }
+
+ // Pass the device back to the caller.
+ if (error == NULL)
+ {
+ *device = found_device;
+ }
+
+ if (list != NULL) { udev_enumerate_unref(list); }
+ return error;
+}
+
+// Finds a udev device of type "usb_interface" that is a child of the specified
+// device and has the specified bInterfaceNumber.
+libusbp_error * udevw_get_tty(
+ struct udev * udev,
+ struct udev_device * parent,
+ struct udev_device ** device)
+{
+ assert(udev != NULL);
+ assert(parent != NULL);
+ assert(device != NULL);
+
+ *device = NULL;
+
+ libusbp_error * error = NULL;
+
+ struct udev_enumerate * list = NULL;
+ if (error == NULL)
+ {
+ error = udevw_create_child_list(udev, parent, &list);
+ }
+
+ // Loop over the list to find the device.
+ struct udev_device * found_device = NULL;
+ if (error == NULL)
+ {
+ struct udev_list_entry * first_entry = udev_enumerate_get_list_entry(list);
+ struct udev_list_entry * list_entry;
+ udev_list_entry_foreach(list_entry, first_entry)
+ {
+ const char * path = udev_list_entry_get_name(list_entry);
+ assert(path != NULL);
+
+ // Get the udev device.
+ struct udev_device * dev = NULL;
+ error = udevw_get_device_from_syspath(udev, path, &dev);
+ if (error != NULL) { break; }
+
+ // See if it is in the "tty" subsystem.
+ bool correct_device = false;
+ const char * subsystem = udev_device_get_subsystem(dev);
+ if (subsystem != NULL && 0 == strcmp(subsystem, "tty"))
+ {
+ correct_device = true;
+ }
+
+ // If it is the right device, stop looping.
+ if (correct_device)
+ {
+ found_device = dev;
+ break;
+ }
+
+ udev_device_unref(dev);
+ }
+ }
+
+ // Make sure we found the device.
+ if (error == NULL && found_device == NULL)
+ {
+ // We did not find it. Maybe the interface is just not ready yet and it
+ // would be ready in a few milliseconds. (Note: We have not seen this
+ // happen, but adding the error code here makes the behavior consistent
+ // with how the Windows code behaves.)
+ error = error_create("Could not find tty device.");
+ error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
+ }
+
+ // Pass the device back to the caller.
+ if (error == NULL)
+ {
+ *device = found_device;
+ }
+
+ if (list != NULL) { udev_enumerate_unref(list); }
+ return error;
+}
+
+// Gets the devnode path of the specified udev device. The returned string is
+// owned by the device.
+libusbp_error * udevw_get_syspath(struct udev_device * device, const char ** syspath)
+{
+ assert(device != NULL);
+ assert(syspath != NULL);
+
+ *syspath = udev_device_get_syspath(device);
+ assert(*syspath != NULL);
+ return NULL;
+}
+
+// Gets the syspath of the specified udev device. The returned string must
+// be freed with libusbp_string_free.
+libusbp_error * udevw_get_syspath_copy(struct udev_device * device, char ** syspath)
+{
+ assert(device != NULL);
+ assert(syspath != NULL);
+
+ *syspath = NULL;
+
+ libusbp_error * error = NULL;
+
+ const char * tmp = NULL;
+ if (error == NULL)
+ {
+ error = udevw_get_syspath(device, &tmp);
+ }
+
+ if (error == NULL)
+ {
+ error = string_copy(tmp, syspath);
+ }
+ return error;
+}
+
+// Gets the devnode path of the specified udev device. The returned string is
+// owned by the device.
+libusbp_error * udevw_get_devnode(struct udev_device * device, const char ** devnode)
+{
+ assert(device != NULL);
+ assert(devnode != NULL);
+
+ *devnode = udev_device_get_devnode(device);
+ if (*devnode == NULL)
+ {
+ return error_create("No device node exists.");
+ }
+ return NULL;
+}
+
+
+// Gets the devnode of the specified udev device. The returned string must
+// be freed with libusbp_string_free.
+libusbp_error * udevw_get_devnode_copy(struct udev_device * device, char ** devnode)
+{
+ assert(device != NULL);
+ assert(devnode != NULL);
+
+ *devnode = NULL;
+
+ libusbp_error * error = NULL;
+
+ const char * tmp = NULL;
+ if (error == NULL)
+ {
+ error = udevw_get_devnode(device, &tmp);
+ }
+
+ if (error == NULL)
+ {
+ error = string_copy(tmp, devnode);
+ }
+ return error;
+}
+
+// Takes a syspath as input and gets the corresponding devpath. The returned
+// string must be freed with libusbp_string_free if there were no errors.
+libusbp_error * udevw_get_devnode_copy_from_syspath(const char * syspath, char ** devnode)
+{
+ assert(syspath != NULL);
+ assert(devnode != NULL);
+
+ *devnode = NULL;
+
+ libusbp_error * error = NULL;
+
+ struct udev * context = NULL;
+ if (error == NULL)
+ {
+ error = udevw_create_context(&context);
+ }
+
+ struct udev_device * device = NULL;
+ if (error == NULL)
+ {
+ error = udevw_get_device_from_syspath(context, syspath, &device);
+ }
+
+ if (error == NULL)
+ {
+ error = udevw_get_devnode_copy(device, devnode);
+ }
+
+ if (device != NULL) { udev_device_unref(device); }
+ if (context != NULL) { udev_unref(context); }
+ return error;
+}
diff --git a/dep/libusbp/src/linux/usbfd_linux.c b/dep/libusbp/src/linux/usbfd_linux.c
new file mode 100644
index 00000000..8d846ab4
--- /dev/null
+++ b/dep/libusbp/src/linux/usbfd_linux.c
@@ -0,0 +1,222 @@
+/* Functions for working with a USB device node file
+ * (e.g. "/dev/bus/usb/001/002"). */
+
+#include
+
+libusbp_error * usbfd_check_existence(const char * filename)
+{
+ libusbp_error * error = NULL;
+ int result = access(filename, F_OK);
+ if (result != 0)
+ {
+ if (errno == ENOENT)
+ {
+ // The file does not exist. This might just be a temporary
+ // condition, so use the LIBUSBP_ERROR_NOT_READY code.
+ error = error_create("File does not exist: %s.", filename);
+ error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
+ }
+ else
+ {
+ // Something actually went wrong when checking if the file
+ // exists.
+ error = error_create_errno("Failed to check file: %s.", filename);
+ }
+ }
+ return error;
+}
+
+libusbp_error * usbfd_open(const char * filename, int * fd)
+{
+ assert(filename != NULL);
+ assert(fd != NULL);
+
+ *fd = open(filename, O_RDWR | O_CLOEXEC);
+ if (*fd == -1)
+ {
+ libusbp_error * error = error_create_errno("Failed to open USB device %s.", filename);
+ return error;
+ }
+
+ return NULL;
+}
+
+// Reads the device descriptor. This is not thread-safe, because it is possible
+// that another thread might change the position of the file descriptor after
+// lseek() and before read().
+//
+// The kernel code that provides the device descriptor can be found in
+// usbdev_read() in devio.c.
+libusbp_error * usbfd_get_device_descriptor(int fd, struct usb_device_descriptor * desc)
+{
+ assert(desc != NULL);
+
+ // Seek to the beginning of the file, where the device descriptor lives.
+ off_t offset = lseek(fd, 0, SEEK_SET);
+ if (offset == -1)
+ {
+ return error_create_errno("Failed to go to beginning of USB device file.");
+ }
+
+ ssize_t expected_size = sizeof(struct usb_device_descriptor);
+ ssize_t size = read(fd, desc, expected_size);
+ if (size == -1)
+ {
+ return error_create_errno("Failed to read device descriptor.");
+ }
+ if (size != expected_size)
+ {
+ return error_create("Failed to read device descriptor. "
+ "Expected %zd-byte device descriptor, read %zd bytes.",
+ expected_size, size);
+ }
+ return NULL;
+}
+
+libusbp_error * usbfd_control_transfer(int fd, libusbp_setup_packet setup,
+ uint32_t timeout, void * data, size_t * transferred)
+{
+ struct usbdevfs_ctrltransfer transfer = {0};
+ transfer.bRequestType = setup.bmRequestType;
+ transfer.bRequest = setup.bRequest;
+ transfer.wValue = setup.wValue;
+ transfer.wIndex = setup.wIndex;
+ transfer.wLength = setup.wLength;
+ transfer.timeout = timeout;
+ transfer.data = data;
+
+ if (transferred != NULL)
+ {
+ *transferred = 0;
+ }
+
+ int result = ioctl(fd, USBDEVFS_CONTROL, &transfer);
+ if (result < 0)
+ {
+ return error_create_errno("Control transfer failed.");
+ }
+
+ if (transferred != NULL)
+ {
+ *transferred = result;
+ }
+
+ return NULL;
+}
+
+/* Performs a bulk or interrupt transfer on the specified endpoint.
+ *
+ * Despite the name, USBDEVFS_BULK does actually work for interrupt endpoints.
+ * The function usbdev_do_ioctl in devio.c calls proc_bulk in devio.c, which
+ * calls usb_bulk_msg in message.c, which explicitly detects if it was called on
+ * an interrupt endpoint and handle that situation properly. */
+libusbp_error * usbfd_bulk_or_interrupt_transfer(int fd, uint8_t pipe,
+ uint32_t timeout, void * buffer, size_t size, size_t * transferred)
+{
+ if (transferred != NULL)
+ {
+ *transferred = 0;
+ }
+
+ // A buffer size of 0 for IN transfers (or at least interrupt IN
+ // transfers in a VirtualBox Linux guest running on a Windows
+ // host) seems to put the Linux USB drivers in some weird state
+ // where every subsequent request times out.
+ if (size == 0 && (pipe & 0x80))
+ {
+ return error_create("Transfer size 0 is not allowed.");
+ }
+
+ // A size greater than UINT_MAX will not fit into the struct below.
+ if (size > UINT_MAX)
+ {
+ return error_create("Transfer size is too large.");
+ }
+
+ if (buffer == NULL && size)
+ {
+ return error_create("Buffer is null.");
+ }
+
+ struct usbdevfs_bulktransfer transfer = {0};
+ transfer.ep = pipe;
+ transfer.len = size;
+ transfer.timeout = timeout;
+ transfer.data = buffer;
+
+ int result = ioctl(fd, USBDEVFS_BULK, &transfer);
+ if (result < 0)
+ {
+ return error_create_errno("");
+ }
+ if (transferred != NULL)
+ {
+ *transferred = result;
+ }
+ return NULL;
+}
+
+libusbp_error * usbfd_submit_urb(int fd, struct usbdevfs_urb * urb)
+{
+ assert(urb != NULL);
+
+ int result = ioctl(fd, USBDEVFS_SUBMITURB, urb);
+ if (result < 0)
+ {
+ return error_create_errno("Submitting USB request block failed.");
+ }
+
+ return NULL;
+}
+
+/*! Checks to see if there is a finished asynchronous request. If there is,
+ * this function "reaps" the request and retrieves a pointer to its URB. The
+ * kernel writes to the URB and its associated buffer when you call this
+ * function, allowing you to get the results of the operation.
+ *
+ * If nothing is available to be reaped at the moment, the retrieved URB pointer
+ * will be NULL.
+ *
+ * Note: For Linux kernels older than 4.0, this function will return an error
+ * if the USB device happens to be disconnected. In 4.0 and later, you will be
+ * able to reap URBs from disconnected devices thanks to commit 3f2cee73b from
+ * Alan Stern on 2015-01-29. */
+libusbp_error * usbfd_reap_urb(int fd, struct usbdevfs_urb ** urb)
+{
+ assert(urb != NULL);
+
+ int result = ioctl(fd, USBDEVFS_REAPURBNDELAY, urb);
+ if (result < 0)
+ {
+ *urb = NULL;
+
+ if (errno == EAGAIN)
+ {
+ // No URBs are available to be reaped right now.
+ return NULL;
+ }
+
+ return error_create_errno("Failed to reap an asynchronous transfer.");
+ }
+ return NULL;
+}
+
+/*! Cancels an URB that was already submitted. */
+libusbp_error * usbfd_discard_urb(int fd, struct usbdevfs_urb * urb)
+{
+ assert(urb != NULL);
+
+ int result = ioctl(fd, USBDEVFS_DISCARDURB, urb);
+ if (result < 0)
+ {
+ if (errno == EINVAL)
+ {
+ // This error code happens if the URB was already completed. This
+ // is not an error.
+ return NULL;
+ }
+
+ return error_create_errno("Failed to cancel asynchronous transfer.");
+ }
+ return NULL;
+}
diff --git a/dep/libusbp/src/list.c b/dep/libusbp/src/list.c
new file mode 100644
index 00000000..ae437fba
--- /dev/null
+++ b/dep/libusbp/src/list.c
@@ -0,0 +1,72 @@
+/** This file provides libusbp_list_free as well as internal functions that help
+ * construct lists of devices.
+ *
+ * At all times, a list maintained by these functions will be NULL terminated.
+ */
+
+#include
+
+libusbp_error * device_list_create(libusbp_device *** device_list)
+{
+ assert(device_list != NULL);
+
+ *device_list = NULL;
+
+ libusbp_device ** new_list = malloc(sizeof(libusbp_device *));
+ if (new_list == NULL)
+ {
+ return &error_no_memory;
+ }
+
+ new_list[0] = NULL;
+
+ *device_list = new_list;
+ return NULL;
+}
+
+libusbp_error * device_list_append(libusbp_device *** device_list,
+ size_t * count, libusbp_device * device)
+{
+ assert(device_list != NULL);
+ assert(count != NULL);
+ assert(device != NULL);
+
+ size_t new_count = *count + 1;
+ libusbp_device ** expanded_list = realloc(*device_list,
+ (new_count + 1) * sizeof(libusbp_device *));
+
+ if (expanded_list == NULL)
+ {
+ // Expanding the list failed, so we return an error and leave the
+ // list in its original state.
+ return &error_no_memory;
+ }
+
+ expanded_list[new_count - 1] = device;
+ expanded_list[new_count] = NULL;
+
+ *count = new_count;
+ *device_list = expanded_list;
+ return NULL;
+}
+
+void free_devices_and_list(libusbp_device ** device_list)
+{
+ if (device_list != NULL)
+ {
+ libusbp_device ** device = device_list;
+ while(*device != NULL)
+ {
+ libusbp_device_free(*device);
+ device++;
+ }
+ libusbp_list_free(device_list);
+ }
+}
+
+void libusbp_list_free(libusbp_device ** device_list)
+{
+ if (device_list == NULL) { return; }
+
+ free(device_list);
+}
diff --git a/dep/libusbp/src/mac/async_in_transfer_mac.c b/dep/libusbp/src/mac/async_in_transfer_mac.c
new file mode 100644
index 00000000..dfdcc97a
--- /dev/null
+++ b/dep/libusbp/src/mac/async_in_transfer_mac.c
@@ -0,0 +1,188 @@
+#include
+
+struct async_in_transfer
+{
+ IOUSBInterfaceInterface182 ** ioh;
+ uint8_t pipe_index;
+ void * buffer;
+ uint32_t size;
+ bool pending;
+ size_t transferred;
+ libusbp_error * error;
+};
+
+static void async_in_transfer_callback(void * context, kern_return_t kr, void * arg0)
+{
+ #ifdef LIBUSBP_LOG
+ printf("async_in_transfer_callback (%p): %p, %#x, %p, %s\n",
+ async_in_transfer_callback, context, kr, arg0, mach_error_string(kr));
+ #endif
+
+ async_in_transfer * transfer = (async_in_transfer *)context;
+ assert(transfer != NULL);
+ assert(transfer->pending);
+ assert(transfer->error == NULL);
+ assert(transfer->transferred == 0);
+
+ libusbp_error * error = NULL;
+
+ if (kr != KERN_SUCCESS)
+ {
+ error = error_create_mach(kr, "Asynchronous IN transfer failed.");
+ }
+
+ transfer->transferred = (size_t)arg0;
+
+ transfer->pending = false;
+ transfer->error = error;
+}
+
+libusbp_error * async_in_transfer_create(
+ libusbp_generic_handle * handle, uint8_t pipe_id, size_t transfer_size,
+ async_in_transfer ** transfer)
+{
+ assert(handle != NULL);
+
+ if (transfer_size > UINT32_MAX)
+ {
+ return error_create("Transfer size is too large.");
+ }
+
+ libusbp_error * error = NULL;
+
+ // Allocate memory for the transfer struct.
+ async_in_transfer * new_transfer = calloc(1, sizeof(async_in_transfer));
+
+ if (new_transfer == NULL)
+ {
+ error = &error_no_memory;
+ }
+
+ // Allocate memory for the buffer.
+ if (error == NULL)
+ {
+ new_transfer->size = transfer_size;
+ new_transfer->buffer = malloc(transfer_size);
+ if (new_transfer->buffer == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ // Get needed information from the generic handle.
+ if (error == NULL)
+ {
+ new_transfer->ioh = generic_handle_get_ioh(handle);
+ new_transfer->pipe_index = generic_handle_get_pipe_index(handle, pipe_id);
+ }
+
+ // Pass the transfer to the caller.
+ if (error == NULL)
+ {
+ *transfer = new_transfer;
+ new_transfer = NULL;
+ }
+
+ async_in_transfer_free(new_transfer);
+ return error;
+}
+
+libusbp_error * async_in_pipe_setup(libusbp_generic_handle * gh, uint8_t pipe_id)
+{
+ LIBUSBP_UNUSED(gh);
+ LIBUSBP_UNUSED(pipe_id);
+ return NULL;
+}
+
+void async_in_transfer_free(async_in_transfer * transfer)
+{
+ if (transfer == NULL) { return; }
+
+ if (transfer->pending)
+ {
+ // Unfortunately, this transfer is still pending, so we cannot free it;
+ // the kernel needs to be able to write to this transfer's memory when
+ // it completes. We could just abort the process here, but instead we
+ // we choose to have a memory leak.
+ return;
+ }
+
+ libusbp_error_free(transfer->error);
+ free(transfer->buffer);
+ free(transfer);
+}
+
+void async_in_transfer_submit(async_in_transfer * transfer)
+{
+ assert(transfer != NULL);
+ assert(transfer->pending == false);
+
+ libusbp_error_free(transfer->error);
+ transfer->error = NULL;
+ transfer->transferred = 0;
+ transfer->pending = true;
+
+ kern_return_t kr = (*transfer->ioh)->ReadPipeAsync(transfer->ioh,
+ transfer->pipe_index, transfer->buffer, transfer->size,
+ async_in_transfer_callback, transfer);
+ if (kr != KERN_SUCCESS)
+ {
+ transfer->pending = false;
+ transfer->error = error_create_mach(kr, "Failed to submit asynchronous read transfer.");
+ }
+}
+
+libusbp_error * async_in_transfer_get_results(async_in_transfer * transfer,
+ void * buffer, size_t * transferred, libusbp_error ** transfer_error)
+{
+ assert(transfer != NULL);
+ assert(transfer->pending == false);
+
+ size_t tmp_transferred = transfer->transferred;
+
+ // Make sure we don't overflow the user's buffer.
+ if (tmp_transferred > transfer->size)
+ {
+ assert(0);
+ tmp_transferred = transfer->size;
+ }
+
+ if (buffer != NULL)
+ {
+ memcpy(buffer, transfer->buffer, tmp_transferred);
+ }
+
+ if (transferred != NULL)
+ {
+ *transferred = tmp_transferred;
+ }
+
+ if (transfer_error != NULL)
+ {
+ *transfer_error = libusbp_error_copy(transfer->error);
+ }
+
+ return NULL;
+}
+
+// This cancels all of the transfers for the whole pipe. It is not possible to
+// cancel an individual transfer on Mac OS X, which is one of the reasons
+// individual transfers are not provided as first-class objects by the libusbp
+// API.
+libusbp_error * async_in_transfer_cancel(async_in_transfer * transfer)
+{
+ if (transfer == NULL) { return NULL; }
+
+ kern_return_t kr = (*transfer->ioh)->AbortPipe(transfer->ioh, transfer->pipe_index);
+ if (kr != KERN_SUCCESS)
+ {
+ return error_create_mach(kr, "Failed to cancel transfers.");
+ }
+ return NULL;
+}
+
+bool async_in_transfer_pending(async_in_transfer * transfer)
+{
+ assert(transfer != NULL);
+ return transfer->pending;
+}
diff --git a/dep/libusbp/src/mac/device_mac.c b/dep/libusbp/src/mac/device_mac.c
new file mode 100644
index 00000000..84c0fc52
--- /dev/null
+++ b/dep/libusbp/src/mac/device_mac.c
@@ -0,0 +1,233 @@
+#include
+
+struct libusbp_device
+{
+ uint64_t id;
+ uint16_t product_id;
+ uint16_t vendor_id;
+ uint16_t revision;
+ char * serial_number;
+};
+
+static libusbp_error * device_allocate(libusbp_device ** device)
+{
+ assert(device != NULL);
+ *device = calloc(1, sizeof(libusbp_device));
+ if (*device == NULL)
+ {
+ return &error_no_memory;
+ }
+ return NULL;
+}
+
+libusbp_error * create_device(io_service_t service, libusbp_device ** device)
+{
+ assert(service != MACH_PORT_NULL);
+ assert(device != NULL);
+
+ // Allocate the device.
+ libusbp_device * new_device = NULL;
+ libusbp_error * error = device_allocate(&new_device);
+
+ // Get the numeric IDs.
+ if (error == NULL)
+ {
+ error = get_uint16(service, CFSTR(kUSBVendorID), &new_device->vendor_id);
+ }
+ if (error == NULL)
+ {
+ error = get_uint16(service, CFSTR(kUSBProductID), &new_device->product_id);
+ }
+ if (error == NULL)
+ {
+ error = get_uint16(service, CFSTR(kUSBDeviceReleaseNumber), &new_device->revision);
+ }
+
+ // Get the serial number.
+ if (error == NULL)
+ {
+ error = get_string(service, CFSTR(kUSBSerialNumberString), &new_device->serial_number);
+ }
+
+ // Get the ID.
+ if (error == NULL)
+ {
+ error = get_id(service, &new_device->id);
+ }
+
+ // Pass the device to the caller.
+ if (error == NULL)
+ {
+ *device = new_device;
+ new_device = NULL;
+ }
+
+ libusbp_device_free(new_device);
+ return error;
+}
+
+libusbp_error * libusbp_device_copy(const libusbp_device * source, libusbp_device ** dest)
+{
+ if (dest == NULL)
+ {
+ return error_create("Device output pointer is null.");
+ }
+
+ *dest = NULL;
+
+ if (source == NULL)
+ {
+ return NULL;
+ }
+
+ libusbp_error * error = NULL;
+
+ // Allocate the device.
+ libusbp_device * new_device = NULL;
+ error = device_allocate(&new_device);
+
+ // Copy the simple fields, while leaving the pointers owned by the
+ // device NULL so that libusbp_device_free is still OK to call.
+ if (error == NULL)
+ {
+ memcpy(new_device, source, sizeof(libusbp_device));
+ new_device->serial_number = NULL;
+ }
+
+ // Copy the serial number.
+ if (error == NULL && source->serial_number != NULL)
+ {
+ error = string_copy(source->serial_number, &new_device->serial_number);
+ }
+
+ // Pass the device to the caller.
+ if (error == NULL)
+ {
+ *dest = new_device;
+ new_device = NULL;
+ }
+
+ libusbp_device_free(new_device);
+ return error;
+}
+
+void libusbp_device_free(libusbp_device * device)
+{
+ if (device != NULL)
+ {
+ libusbp_string_free(device->serial_number);
+ free(device);
+ }
+}
+
+uint64_t device_get_id(const libusbp_device * device)
+{
+ assert(device != NULL);
+ return device->id;
+}
+
+libusbp_error * libusbp_device_get_vendor_id(
+ const libusbp_device * device,
+ uint16_t * vendor_id)
+{
+ if (vendor_id == NULL)
+ {
+ return error_create("Vendor ID output pointer is null.");
+ }
+
+ *vendor_id = 0;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ *vendor_id = device->vendor_id;
+ return NULL;
+}
+
+
+libusbp_error * libusbp_device_get_product_id(
+ const libusbp_device * device,
+ uint16_t * product_id)
+{
+ if (product_id == NULL)
+ {
+ return error_create("Product ID output pointer is null.");
+ }
+
+ *product_id = 0;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ *product_id = device->product_id;
+ return NULL;
+}
+
+libusbp_error * libusbp_device_get_revision(
+ const libusbp_device * device,
+ uint16_t * revision)
+{
+ if (revision == NULL)
+ {
+ return error_create("Device revision output pointer is null.");
+ }
+
+ *revision = 0;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ *revision = device->revision;
+ return NULL;
+}
+
+libusbp_error * libusbp_device_get_serial_number(
+ const libusbp_device * device,
+ char ** serial_number)
+{
+ if (serial_number == NULL)
+ {
+ return error_create("Serial number output pointer is null.");
+ }
+
+ *serial_number = NULL;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ if (device->serial_number == NULL)
+ {
+ libusbp_error * error = error_create("Device does not have a serial number.");
+ error = error_add_code(error, LIBUSBP_ERROR_NO_SERIAL_NUMBER);
+ return error;
+ }
+
+ return string_copy(device->serial_number, serial_number);
+}
+
+libusbp_error * libusbp_device_get_os_id(
+ const libusbp_device * device,
+ char ** id)
+{
+ if (id == NULL)
+ {
+ return error_create("Device OS ID output pointer is null.");
+ }
+
+ *id = NULL;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ return iokit_id_to_string(device->id, id);
+}
diff --git a/dep/libusbp/src/mac/error_mac.c b/dep/libusbp/src/mac/error_mac.c
new file mode 100644
index 00000000..96a5066f
--- /dev/null
+++ b/dep/libusbp/src/mac/error_mac.c
@@ -0,0 +1,61 @@
+#include
+
+libusbp_error * error_create_mach(kern_return_t error_code, const char * format, ...)
+{
+ // Add the numerical error code.
+ libusbp_error * error = error_create("Error code 0x%x.", error_code);
+
+ bool skip_standard_message = false;
+
+ switch(error_code)
+ {
+ case kIOUSBPipeStalled:
+ skip_standard_message = true;
+ error = error_add(error, "The request was invalid or there was an I/O problem.");
+ error = error_add_code(error, LIBUSBP_ERROR_STALL);
+ error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
+ break;
+
+ case kIOReturnNoDevice:
+ error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
+ break;
+
+ case kIOUSBTransactionTimeout:
+ skip_standard_message = true;
+ error = error_add(error, "The operation timed out.");
+ error = error_add_code(error, LIBUSBP_ERROR_TIMEOUT);
+ break;
+
+ case kIOReturnExclusiveAccess:
+ skip_standard_message = true;
+ error = error_add(error,
+ "Access is denied. Try closing all other programs that are using the device.");
+ error = error_add_code(error, LIBUSBP_ERROR_ACCESS_DENIED);
+ break;
+
+ case kIOReturnOverrun:
+ skip_standard_message = true;
+ error = error_add(error, "The transfer overflowed.");
+ break;
+
+ case kIOReturnAborted:
+ skip_standard_message = true;
+ error = error_add(error, "The operation was cancelled.");
+ error = error_add_code(error, LIBUSBP_ERROR_CANCELLED);
+ break;
+ }
+
+ if (!skip_standard_message)
+ {
+ // Add a message from the system.
+ error = error_add(error, "%s.", mach_error_string(error_code));
+ }
+
+ // Finally, add the context provided by the caller.
+ va_list ap;
+ va_start(ap, format);
+ error = error_add_v(error, format, ap);
+ va_end(ap);
+
+ return error;
+}
diff --git a/dep/libusbp/src/mac/generic_handle_mac.c b/dep/libusbp/src/mac/generic_handle_mac.c
new file mode 100644
index 00000000..1fbfc15e
--- /dev/null
+++ b/dep/libusbp/src/mac/generic_handle_mac.c
@@ -0,0 +1,600 @@
+#include
+#include
+
+struct libusbp_generic_handle
+{
+ // ioh is short for "IOKit Handle"
+ IOUSBInterfaceInterface182 ** ioh;
+
+ IOCFPlugInInterface ** plug_in;
+
+ mach_port_t async_port;
+
+ // Timeouts are stored in milliseconds. 0 is forever.
+ uint32_t out_timeout[MAX_ENDPOINT_NUMBER + 1];
+ uint32_t in_timeout[MAX_ENDPOINT_NUMBER + 1];
+
+ // These arrays allow us to convert from a normal USB endpoint address like
+ // 0x82 (Endpoint 2 IN) to the pipe index needed by IOUSBInterface functions.
+ uint8_t out_pipe_index[MAX_ENDPOINT_NUMBER + 1];
+ uint8_t in_pipe_index[MAX_ENDPOINT_NUMBER + 1];
+};
+
+#ifdef LIBUSBP_LOG
+void log_mach_msg(mach_msg_return_t mr, mach_msg_header_t * header)
+{
+ uint64_t t = mach_absolute_time();
+ size_t main_payload_size = header->msgh_size - sizeof(mach_msg_header_t);
+ assert(main_payload_size < 1024); // avoid buffer overruns
+ uint8_t * payload = (uint8_t *)(header + 1);
+ mach_msg_trailer_t * trailer = (mach_msg_trailer_t *)(payload + main_payload_size);
+ size_t payload_size = main_payload_size + trailer->msgh_trailer_size;
+ assert(payload_size < 1024); // avoid buffer overruns
+
+ printf("mach_msg at %lld: %#x, %s, %d\n", t, mr, mach_error_string(mr), header->msgh_size);
+ printf(" bits: %#x\n", header->msgh_bits);
+ printf(" ports: %d, %d, %d\n", header->msgh_remote_port,
+ header->msgh_local_port, header->msgh_voucher_port);
+ printf(" id: %d\n", header->msgh_id);
+ for (size_t i = 0; i < payload_size; i++)
+ {
+ if ((i % 8) == 0) { printf(" "); }
+ printf("%02x ", payload[i]);
+ if ((i % 8) == 7 || i == payload_size - 1) { printf("\n"); }
+ }
+}
+#endif
+
+// Gets the properties of all the pipes and uses that to populate the
+// out_pipe_index and in_pipe_index arrays.
+static libusbp_error * process_pipe_properties(libusbp_generic_handle * handle)
+{
+ uint8_t endpoint_count;
+ kern_return_t kr = (*handle->ioh)->GetNumEndpoints(handle->ioh, &endpoint_count);
+ if (kr != KERN_SUCCESS)
+ {
+ return error_create_mach(kr, "Failed to get number of endpoints.");
+ }
+
+ for(uint32_t i = 1; i <= endpoint_count; i++)
+ {
+ uint8_t direction;
+ uint8_t endpoint_number;
+ uint8_t transfer_type;
+ uint16_t max_packet_size;
+ uint8_t interval;
+ kr = (*handle->ioh)->GetPipeProperties(handle->ioh, (UInt8) i,
+ &direction, &endpoint_number, &transfer_type, &max_packet_size, &interval);
+
+ if (kr != KERN_SUCCESS)
+ {
+ return error_create_mach(kr, "Failed to get pipe properties for pipe %d.", i);
+ }
+
+ if (endpoint_number <= MAX_ENDPOINT_NUMBER)
+ {
+ if (direction)
+ {
+ handle->in_pipe_index[endpoint_number] = (uint8_t) i;
+ }
+ else
+ {
+ handle->out_pipe_index[endpoint_number] = (uint8_t) i;
+ }
+ }
+ }
+ return NULL;
+}
+
+// Sets the configruation of a device to 1 if it is not configured.
+static libusbp_error * set_configuration(io_service_t service)
+{
+ assert(service != MACH_PORT_NULL);
+
+ libusbp_error * error = NULL;
+
+ // Turn io_service_t into something we can actually use.
+ IOUSBDeviceInterface ** dev_handle = NULL;
+ IOCFPlugInInterface ** plug_in = NULL;
+ error = service_to_interface(service,
+ kIOUSBDeviceUserClientTypeID,
+ CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID197),
+ (void **)&dev_handle,
+ &plug_in);
+
+ uint8_t config_num = 0;
+ if (error == NULL)
+ {
+ kern_return_t kr = (*dev_handle)->GetConfiguration(dev_handle, &config_num);
+ if (kr != KERN_SUCCESS)
+ {
+ // We failed to get the current configuration. The documentation of
+ // GetConfiguration doesn't state whether it actually does I/O on
+ // the device or not, but if it does do I/O then one possible reason
+ // for GetConfiguration to fail is that the device simply doesn't
+ // support the request. Let's just assume the device is not
+ // configured and set it to configuration 1.
+ config_num = 0;
+ }
+ }
+
+ // Open the device for exclusive access.
+ if (error == NULL && config_num == 0)
+ {
+ kern_return_t kr = (*dev_handle)->USBDeviceOpen(dev_handle);
+ if (kr != KERN_SUCCESS)
+ {
+ error = error_create_mach(kr, "Failed to open handle to device.");
+ }
+ }
+
+ // Set the configuration.
+ if (error == NULL && config_num == 0)
+ {
+ uint8_t new_config_num = 1;
+ kern_return_t kr = (*dev_handle)->SetConfiguration(dev_handle, new_config_num);
+ if (kr != KERN_SUCCESS)
+ {
+ error = error_create_mach(kr, "Failed to set configuration to 1.");
+ }
+ }
+
+ // Clean up.
+ if (dev_handle != NULL)
+ {
+ (*dev_handle)->USBDeviceClose(dev_handle);
+ (*dev_handle)->Release(dev_handle);
+ dev_handle = NULL;
+ (*plug_in)->Release(plug_in);
+ plug_in = NULL;
+ }
+
+ return error;
+}
+
+// Sets the configruation of the device to 1 if it is not set, and then
+// retrieves the io_service_t representing the specific interface we want
+// to talk to.
+static libusbp_error * set_configuration_and_get_service(
+ const libusbp_generic_interface * gi,
+ io_service_t * service)
+{
+ assert(gi != NULL);
+ assert(service != NULL);
+ *service = MACH_PORT_NULL;
+
+ uint64_t device_id = generic_interface_get_device_id(gi);
+ uint8_t interface_number = generic_interface_get_interface_number(gi);
+
+ libusbp_error * error = NULL;
+
+ // Get an io_service_t for the physical device.
+ io_service_t device_service = MACH_PORT_NULL;
+ error = service_get_from_id(device_id, &device_service);
+
+ // Set the configruation to 1 if it is not set.
+ if (error == NULL)
+ {
+ error = set_configuration(device_service);
+ }
+
+ // Get the io_service_t for the interface.
+ if (error == NULL)
+ {
+ error = service_get_usb_interface(device_service, interface_number, service);
+ }
+
+ if (device_service != MACH_PORT_NULL) { IOObjectRelease(device_service); }
+
+ return error;
+}
+
+libusbp_error * libusbp_generic_handle_open(
+ const libusbp_generic_interface * gi,
+ libusbp_generic_handle ** handle)
+{
+ if (handle == NULL)
+ {
+ return error_create("Generic handle output pointer is null.");
+ }
+
+ *handle = NULL;
+
+ if (gi == NULL)
+ {
+ return error_create("Generic interface is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ // Allocate memory for the handle.
+ libusbp_generic_handle * new_handle = NULL;
+ new_handle = calloc(1, sizeof(libusbp_generic_handle));
+
+ if (new_handle == NULL)
+ {
+ error = &error_no_memory;
+ }
+
+ // Get the io_service_t representing the IOUSBInterface.
+ io_service_t service = MACH_PORT_NULL;
+ if (error == NULL)
+ {
+ uint64_t interface_id;
+ bool has_interface_id = generic_interface_get_interface_id(gi, &interface_id);
+ if (has_interface_id)
+ {
+ // This generic interface has an I/O Registry ID for a specific USB interface,
+ // so lets just get the corresponding io_service_t.
+ error = service_get_from_id(interface_id, &service);
+ }
+ else
+ {
+ // This generic interface does not have an ID for the specific USB interface yet,
+ // probably because it is a non-composite device and we need to put it into the
+ // right configuration.
+
+ error = set_configuration_and_get_service(gi, &service);
+ }
+ }
+
+ // Get the IOInterfaceInterface
+ if (error == NULL)
+ {
+ error = service_to_interface(service,
+ kIOUSBInterfaceUserClientTypeID,
+ CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID182),
+ (void **)&new_handle->ioh,
+ &new_handle->plug_in);
+ }
+
+ // Open the interface for exclusive access.
+ // (Otherwise, we can't read from non-zero pipes.)
+ if (error == NULL)
+ {
+ kern_return_t kr = (*new_handle->ioh)->USBInterfaceOpen(new_handle->ioh);
+ if (kr != KERN_SUCCESS)
+ {
+ error = error_create_mach(kr, "Failed to open generic handle.");
+ }
+ }
+
+ // Retrieve and store important information from the pipe properties.
+ if (error == NULL)
+ {
+ error = process_pipe_properties(new_handle);
+ }
+
+ // Create the async port.
+ if (error == NULL)
+ {
+ kern_return_t kr = (*new_handle->ioh)->CreateInterfaceAsyncPort(new_handle->ioh,
+ &new_handle->async_port);
+ if (kr != KERN_SUCCESS)
+ {
+ error = error_create_mach(kr, "Failed to create asynchronous port.");
+ }
+ }
+
+ // Pass the handle to the caller.
+ if (error == NULL)
+ {
+ *handle = new_handle;
+ new_handle = NULL;
+ }
+
+ // Clean up.
+ libusbp_generic_handle_close(new_handle);
+ if (service != MACH_PORT_NULL) { IOObjectRelease(service); }
+ return error;
+}
+
+void libusbp_generic_handle_close(libusbp_generic_handle * handle)
+{
+ if (handle != NULL)
+ {
+ if (handle->ioh != NULL)
+ {
+ (*handle->ioh)->USBInterfaceClose(handle->ioh);
+ (*handle->ioh)->Release(handle->ioh);
+ (*handle->plug_in)->Release(handle->plug_in);
+ }
+ free(handle);
+ }
+}
+
+libusbp_error * libusbp_generic_handle_open_async_in_pipe(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ libusbp_async_in_pipe ** pipe)
+{
+ return async_in_pipe_create(handle, pipe_id, pipe);
+}
+
+libusbp_error * libusbp_generic_handle_set_timeout(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ uint32_t timeout)
+{
+ if (handle == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ error = check_pipe_id(pipe_id);
+
+ if (error == NULL)
+ {
+ uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER;
+
+ if (pipe_id & 0x80)
+ {
+ handle->in_timeout[endpoint_number] = timeout;
+ }
+ else
+ {
+ handle->out_timeout[endpoint_number] = timeout;
+ }
+ }
+
+ return error;
+}
+
+libusbp_error * libusbp_control_transfer(
+ libusbp_generic_handle * handle,
+ uint8_t bmRequestType,
+ uint8_t bRequest,
+ uint16_t wValue,
+ uint16_t wIndex,
+ void * buffer,
+ uint16_t wLength,
+ size_t * transferred)
+{
+ if (transferred != NULL)
+ {
+ *transferred = 0;
+ }
+
+ if (handle == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ IOUSBDevRequestTO request;
+ request.bmRequestType = bmRequestType;
+ request.bRequest = bRequest;
+ request.wValue = wValue;
+ request.wIndex = wIndex;
+ request.wLength = wLength;
+ request.pData = buffer;
+ request.completionTimeout = handle->out_timeout[0];
+ request.wLenDone = 0;
+
+ kern_return_t kr = (*handle->ioh)->ControlRequestTO(handle->ioh, 0, &request);
+ if (transferred != NULL) { *transferred = request.wLenDone; }
+ if (kr != KERN_SUCCESS)
+ {
+ return error_create_mach(kr, "Control transfer failed.");
+ }
+
+ return NULL;
+}
+
+libusbp_error * libusbp_read_pipe(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ void * buffer,
+ size_t size,
+ size_t * transferred)
+{
+ if (transferred != NULL)
+ {
+ *transferred = 0;
+ }
+
+ if (handle == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ if (size == 0)
+ {
+ error = error_create("Transfer size 0 is not allowed.");
+ }
+
+ if (error == NULL && size > UINT32_MAX)
+ {
+ error = error_create("Transfer size is too large.");
+ }
+
+ if (error == NULL && buffer == NULL)
+ {
+ error = error_create("Buffer is null.");
+ }
+
+ if (error == NULL)
+ {
+ error = check_pipe_id_in(pipe_id);
+ }
+
+ if (error == NULL)
+ {
+ uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER;
+ uint32_t no_data_timeout = 0;
+ uint32_t completion_timeout = handle->in_timeout[endpoint_number];
+ uint32_t iokit_size = (uint32_t) size;
+ uint32_t pipe_index = handle->in_pipe_index[endpoint_number];
+ kern_return_t kr = (*handle->ioh)->ReadPipeTO(handle->ioh, (UInt8) pipe_index,
+ buffer, &iokit_size, no_data_timeout, completion_timeout);
+ if (transferred != NULL) { *transferred = iokit_size; }
+ if (kr != KERN_SUCCESS)
+ {
+ error = error_create_mach(kr, "");
+ }
+ }
+
+ if (error != NULL)
+ {
+ error = error_add(error, "Failed to read from pipe.");
+ }
+
+ return error;
+}
+
+libusbp_error * libusbp_write_pipe(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ const void * buffer,
+ size_t size,
+ size_t * transferred)
+{
+ if (transferred != NULL)
+ {
+ *transferred = 0;
+ }
+
+ if (handle == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ if (size > UINT32_MAX)
+ {
+ error = error_create("Transfer size is too large.");
+ }
+
+ if (error == NULL && buffer == NULL && size)
+ {
+ error = error_create("Buffer is null.");
+ }
+
+ if (error == NULL)
+ {
+ error = check_pipe_id_out(pipe_id);
+ }
+
+ if (error == NULL)
+ {
+ uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER;
+ uint32_t no_data_timeout = 0;
+ uint32_t completion_timeout = handle->out_timeout[endpoint_number];
+ uint32_t pipe_index = handle->out_pipe_index[endpoint_number];
+ kern_return_t kr = (*handle->ioh)->WritePipeTO(handle->ioh, (UInt8) pipe_index,
+ (void *)buffer, (UInt32) size, no_data_timeout, completion_timeout);
+ if (kr != KERN_SUCCESS)
+ {
+ error = error_create_mach(kr, "");
+ }
+ }
+
+ if (error == NULL && transferred != NULL)
+ {
+ // There was no error, so just assume the entire amount was transferred.
+ // WritePipeTO does not give us a number.
+ *transferred = size;
+ }
+
+ if (error != NULL)
+ {
+ error = error_add(error, "Failed to write to pipe.");
+ }
+
+ return error;
+}
+
+#pragma pack(4)
+typedef struct
+{
+ mach_msg_header_t header;
+ uint8_t buffer[1024];
+} our_msg;
+
+libusbp_error * generic_handle_events(libusbp_generic_handle * handle)
+{
+ assert(handle != NULL);
+
+ while(1)
+ {
+ // Check for messages on this handle's async port using mach_msg().
+ our_msg msg;
+ mach_msg_option_t option = MACH_RCV_MSG | MACH_RCV_TIMEOUT;
+ option |= MACH_RCV_LARGE; // tmphax to receive large messages
+ mach_msg_size_t send_size = 0;
+ mach_msg_size_t rcv_size = sizeof(msg); // might need to make this bigger
+ mach_msg_timeout_t timeout = 0; // non-blocking
+ mach_port_t notify = MACH_PORT_NULL;
+ mach_msg_return_t mr = mach_msg(&msg.header, option, send_size, rcv_size,
+ handle->async_port, timeout, notify);
+
+ if (mr != MACH_MSG_SUCCESS)
+ {
+ if (mr == MACH_RCV_TIMED_OUT)
+ {
+ // There was no message available on the port.
+ return NULL;
+ }
+ else if (mr == MACH_RCV_TOO_LARGE)
+ {
+ return error_create("Mach message received was too large: %d > %d\n",
+ msg.header.msgh_size, (unsigned int)sizeof(msg));
+ }
+ else
+ {
+ return error_create_mach(mr, "Failed to receive mach message.");
+ }
+ }
+
+ #ifdef LIBUSBP_LOG
+ log_mach_msg(mr, &msg.header);
+ #endif
+
+ // We have received a message from the mach port that contains a blob of
+ // binary data. The blob seems to include a pointer to the
+ // async_in_transfer, a kern_return_t code for the transfer, and a pointer
+ // to the callback we specified when we submitted the transfer. We aren't
+ // supposed to process that data ourselves; we are supposed to call
+ // IODispatchCalloutFromMessage.
+ //
+ // The third argument to IODispatchCalloutFromMessage is supposed to be a
+ // IONotificationPortRef, but we don't have access to the
+ // IONotificationPortRef, which is a protected member of IOUSBInterface
+ // class with no accessors. Passing NULL (or any arbitary pointer) seems to
+ // work. IODispatchCalloutFromMessage has no return value.
+ IODispatchCalloutFromMessage(0, &msg.header, NULL);
+ }
+}
+
+void ** libusbp_generic_handle_get_cf_plug_in(libusbp_generic_handle * handle)
+{
+ if (handle == NULL)
+ {
+ return NULL;
+ }
+
+ return (void **)handle->plug_in;
+}
+
+IOUSBInterfaceInterface182 ** generic_handle_get_ioh(const libusbp_generic_handle * handle)
+{
+ assert(handle != NULL);
+ return handle->ioh;
+}
+
+uint8_t generic_handle_get_pipe_index(const libusbp_generic_handle * handle, uint8_t pipe_id)
+{
+ uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER;
+ if (pipe_id & 0x80)
+ {
+ return handle->in_pipe_index[endpoint_number];
+ }
+ else
+ {
+ return handle->out_pipe_index[endpoint_number];
+ }
+}
diff --git a/dep/libusbp/src/mac/generic_interface_mac.c b/dep/libusbp/src/mac/generic_interface_mac.c
new file mode 100644
index 00000000..2ec10276
--- /dev/null
+++ b/dep/libusbp/src/mac/generic_interface_mac.c
@@ -0,0 +1,224 @@
+#include
+
+/* Composite devices on all OSes and non-composite vendor-defined devices on
+ * Windows and Linux will automatically be set to configuration 1, which means
+ * we can find device nodes for the specific interface we are interested in
+ * using in libusbp_generic_interface_create, and return error code
+ * LIBUSBP_ERROR_NOT_READY if that node is not found. However, non-composite
+ * devices on Mac OS X will not automatically get configured unless someone
+ * tells them what configuration to use.
+ *
+ * For composite devices on Mac OS X:
+ * libusbp_generic_interface_create() finds the correct device node.
+ * libusbp_generic_handle_open() simply opens it.
+ *
+ * For non-composite devices on Mac OS X:
+ * libusbp_generic_interface_create() just records information from the user.
+ * libusbp_generic_handle_open() ensures the device is set to configuration 1,
+ * then finds the correct interface and opens it.
+ */
+
+struct libusbp_generic_interface
+{
+ uint64_t device_id;
+ uint8_t interface_number;
+ bool has_interface_id;
+ uint64_t interface_id;
+};
+
+libusbp_error * generic_interface_allocate(libusbp_generic_interface ** gi)
+{
+ *gi = calloc(1, sizeof(libusbp_generic_interface));
+ if (*gi == NULL)
+ {
+ return &error_no_memory;
+ }
+ return NULL;
+}
+
+libusbp_error * libusbp_generic_interface_create(
+ const libusbp_device * device,
+ uint8_t interface_number,
+ bool composite,
+ libusbp_generic_interface ** gi)
+{
+ if (gi == NULL)
+ {
+ return error_create("Generic interface output pointer is null.");
+ }
+
+ *gi = NULL;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ libusbp_generic_interface * new_gi = NULL;
+ if (error == NULL)
+ {
+ error = generic_interface_allocate(&new_gi);
+ }
+
+ // Record the I/O registry ID for the device.
+ if (error == NULL)
+ {
+ new_gi->device_id = device_get_id(device);
+ }
+
+ // Record the interface number.
+ if (error == NULL)
+ {
+ new_gi->interface_number = interface_number;
+ }
+
+ // If this is a composite device, get the id for the specific interface we
+ // are interested in. If it is non-composite, we wait until later becuase
+ // the device might be unconfigured and its interface registry entries might
+ // not even exist yet.
+ if (error == NULL && composite)
+ {
+ // Get an io_service_t for the physical device.
+ io_service_t device_service = MACH_PORT_NULL;
+ error = service_get_from_id(new_gi->device_id, &device_service);
+
+ // Get the io_service_t for the interface.
+ io_service_t interface_service = MACH_PORT_NULL;
+ if (error == NULL)
+ {
+ error = service_get_usb_interface(device_service, interface_number, &interface_service);
+ }
+
+ // Get the registry entry ID for the interface.
+ if (error == NULL)
+ {
+ assert(interface_service != MACH_PORT_NULL);
+ error = get_id(interface_service, &new_gi->interface_id);
+ }
+
+ // Record the fact that we have an ID for the interface.
+ if (error == NULL)
+ {
+ assert(new_gi->interface_id);
+ assert(new_gi->interface_id != new_gi->device_id);
+ new_gi->has_interface_id = true;
+ }
+
+ if (device_service != MACH_PORT_NULL) { IOObjectRelease(device_service); }
+ if (interface_service != MACH_PORT_NULL) { IOObjectRelease(interface_service); }
+ }
+
+ // Pass the new generic interface to the caller.
+ if (error == NULL)
+ {
+ *gi = new_gi;
+ new_gi = NULL;
+ }
+
+ libusbp_generic_interface_free(new_gi);
+ return error;
+}
+
+void libusbp_generic_interface_free(libusbp_generic_interface * gi)
+{
+ free(gi);
+}
+
+libusbp_error * libusbp_generic_interface_copy(
+ const libusbp_generic_interface * source,
+ libusbp_generic_interface ** dest)
+{
+ if (dest == NULL)
+ {
+ return error_create("Generic interface output pointer is null.");
+ }
+
+ *dest = NULL;
+
+ if (source == NULL)
+ {
+ return NULL;
+ }
+
+ libusbp_error * error = NULL;
+
+ // Allocate the generic interface.
+ libusbp_generic_interface * new_gi = NULL;
+ error = generic_interface_allocate(&new_gi);
+
+ // Copy the simple fields.
+ if (error == NULL)
+ {
+ memcpy(new_gi, source, sizeof(libusbp_generic_interface));
+ }
+
+ // Pass the generic interface to the caller.
+ if (error == NULL)
+ {
+ *dest = new_gi;
+ new_gi = NULL;
+ }
+
+ libusbp_generic_interface_free(new_gi);
+ return error;
+}
+
+uint64_t generic_interface_get_device_id(const libusbp_generic_interface * gi)
+{
+ assert(gi != NULL);
+ return gi->device_id;
+}
+
+uint8_t generic_interface_get_interface_number(const libusbp_generic_interface * gi)
+{
+ assert(gi != NULL);
+ return gi->interface_number;
+}
+
+bool generic_interface_get_interface_id(const libusbp_generic_interface * gi, uint64_t * id)
+{
+ assert(gi != NULL);
+ if (gi->has_interface_id)
+ {
+ *id = gi->interface_id;
+ return 1;
+ }
+ else
+ {
+ *id = 0;
+ return 0;
+ }
+}
+
+libusbp_error * libusbp_generic_interface_get_os_id(
+ const libusbp_generic_interface * gi,
+ char ** id)
+{
+ if (id == NULL)
+ {
+ return error_create("String output pointer is null.");
+ }
+
+ *id = NULL;
+
+ if (gi == NULL)
+ {
+ return error_create("Generic interface is null.");
+ }
+
+ // Some information is being lost here, unfortunately.
+ uint64_t idnum = gi->has_interface_id ? gi->interface_id : gi->device_id;
+
+ return iokit_id_to_string(idnum, id);
+}
+
+libusbp_error * libusbp_generic_interface_get_os_filename(
+ const libusbp_generic_interface * gi,
+ char ** filename)
+{
+ return libusbp_generic_interface_get_os_id(gi, filename);
+}
+
+
diff --git a/dep/libusbp/src/mac/iokit_mac.c b/dep/libusbp/src/mac/iokit_mac.c
new file mode 100644
index 00000000..24923a82
--- /dev/null
+++ b/dep/libusbp/src/mac/iokit_mac.c
@@ -0,0 +1,306 @@
+#include
+
+libusbp_error * iokit_id_to_string(uint64_t id, char ** str)
+{
+ char buffer[17];
+ snprintf(buffer, sizeof(buffer), "%" PRIx64, id);
+ return string_copy(buffer, str);
+}
+
+libusbp_error * service_get_from_id(uint64_t id, io_service_t * service)
+{
+ assert(service != NULL);
+
+ // Create a dictionary specifying this ID. This dictionary will be
+ // CFReleased by IOServiceGetMatchingService.
+ CFMutableDictionaryRef dict = IORegistryEntryIDMatching(id);
+ if (dict == NULL)
+ {
+ return error_create("Failed to create a dictionary matching the ID.");
+ }
+
+ *service = IOServiceGetMatchingService(kIOMasterPortDefault, dict);
+ if (*service == MACH_PORT_NULL)
+ {
+ return error_create("Failed to find service with ID 0x%" PRIx64 ".", id);
+ }
+
+ return NULL;
+}
+
+// Takes a service representing a physical, composite USB device.
+// Returns a service representing the specified USB interface of the device.
+// The returned service will conform to the class IOUSBInterface.
+libusbp_error * service_get_usb_interface(io_service_t service,
+ uint8_t interface_number, io_service_t * interface_service)
+{
+ assert(service != MACH_PORT_NULL);
+ assert(interface_service != NULL);
+
+ *interface_service = MACH_PORT_NULL;
+
+ libusbp_error * error = NULL;
+
+ io_iterator_t iterator = MACH_PORT_NULL;
+ kern_return_t result = IORegistryEntryGetChildIterator(
+ service, kIOServicePlane, &iterator);
+
+ if (result != KERN_SUCCESS)
+ {
+ error = error_create_mach(result, "Failed to get child iterator.");
+ }
+
+ // Loop through the devices to find the right one.
+ while (error == NULL)
+ {
+ io_service_t candidate = IOIteratorNext(iterator);
+ if (candidate == MACH_PORT_NULL) { break; }
+
+ // Filter out candidates that are not of class IOUSBInterface.
+ bool conforms = (bool) IOObjectConformsTo(candidate, kIOUSBInterfaceClassName);
+ if (!conforms)
+ {
+ IOObjectRelease(candidate);
+ continue;
+ }
+
+ // Get bInterfaceNumber.
+ int32_t actual_num;
+ error = get_int32(candidate, CFSTR("bInterfaceNumber"), &actual_num);
+
+ if (error == NULL && actual_num == interface_number)
+ {
+ // This is the right one. Pass it to the caller.
+ *interface_service = candidate;
+ break;
+ }
+
+ IOObjectRelease(candidate);
+ }
+
+ if (error == NULL && *interface_service == MACH_PORT_NULL)
+ {
+ error = error_create("Could not find interface %d.", interface_number);
+ error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
+ }
+
+ if (iterator != MACH_PORT_NULL) { IOObjectRelease(iterator); }
+ return error;
+}
+
+libusbp_error * service_get_child_by_class(io_service_t service,
+ const char * class_name, io_service_t * interface_service)
+{
+ assert(service != MACH_PORT_NULL);
+ assert(interface_service != NULL);
+
+ *interface_service = MACH_PORT_NULL;
+
+ libusbp_error * error = NULL;
+
+ io_iterator_t iterator = MACH_PORT_NULL;
+ kern_return_t result = IORegistryEntryCreateIterator(
+ service, kIOServicePlane, kIORegistryIterateRecursively, &iterator);
+
+ if (result != KERN_SUCCESS)
+ {
+ error = error_create_mach(result, "Failed to get recursive iterator.");
+ }
+
+ // Loop through the devices to find the right one.
+ while (error == NULL)
+ {
+ io_service_t candidate = IOIteratorNext(iterator);
+ if (candidate == MACH_PORT_NULL) { break; }
+
+ // Filter out candidates that are not the right class.
+ bool conforms = (bool) IOObjectConformsTo(candidate, class_name);
+ if (!conforms)
+ {
+ IOObjectRelease(candidate);
+ continue;
+ }
+
+ // This is the right one. Pass it to the caller.
+ *interface_service = candidate;
+ break;
+ }
+
+ if (error == NULL && *interface_service == MACH_PORT_NULL)
+ {
+ error = error_create("Could not find entry with class %s.", class_name);
+ error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
+ }
+
+ if (iterator != MACH_PORT_NULL) { IOObjectRelease(iterator); }
+ return error;
+}
+
+libusbp_error * service_to_interface(
+ io_service_t service,
+ CFUUIDRef pluginType,
+ REFIID rid,
+ void ** object,
+ IOCFPlugInInterface *** plug_in)
+{
+ assert(service != MACH_PORT_NULL);
+ assert(object != NULL);
+
+ *object = NULL;
+
+ int32_t score;
+
+ libusbp_error * error = NULL;
+
+ // Create the plug-in interface.
+ IOCFPlugInInterface ** new_plug_in = NULL;
+ kern_return_t kr = IOCreatePlugInInterfaceForService(service,
+ pluginType, kIOCFPlugInInterfaceID,
+ &new_plug_in, &score);
+
+ if (kr != KERN_SUCCESS)
+ {
+ error = error_create_mach(kr, "Failed to create plug-in interface.");
+ }
+
+ // Create the device interface and pass it to the caller.
+ if (error == NULL)
+ {
+ HRESULT hr = (*new_plug_in)->QueryInterface(new_plug_in, rid, object);
+ if (hr)
+ {
+ error = error_create_hr(hr, "Failed to query interface.");
+ }
+ }
+
+ // Also pass the plug-in interface to the caller if they want it.
+ if (error == NULL && plug_in != NULL)
+ {
+ *plug_in = new_plug_in;
+ new_plug_in = NULL;
+ }
+
+ // Clean up.
+ if (new_plug_in != NULL)
+ {
+ (*new_plug_in)->Release(new_plug_in);
+ }
+ return error;
+}
+
+
+libusbp_error * get_id(io_registry_entry_t entry, uint64_t * id)
+{
+ assert(entry != MACH_PORT_NULL);
+ assert(id != NULL);
+
+ *id = 0;
+
+ kern_return_t result = IORegistryEntryGetRegistryEntryID(entry, id);
+ if (result != KERN_SUCCESS)
+ {
+ return error_create_mach(result, "Failed to get registry entry ID.");
+ }
+ return NULL;
+}
+
+// Returns NULL if the string is not present.
+// The returned string should be freed with libusbp_string_free.
+libusbp_error * get_string(io_registry_entry_t entry, CFStringRef name, char ** value)
+{
+ assert(entry != MACH_PORT_NULL);
+ assert(name != NULL);
+ assert(value != NULL);
+
+ *value = NULL;
+
+ CFTypeRef cf_value = IORegistryEntryCreateCFProperty(entry, name, kCFAllocatorDefault, 0);
+ if (cf_value == NULL)
+ {
+ // The string probably does not exist, so just return.
+ return NULL;
+ }
+
+ libusbp_error * error = NULL;
+
+ if (CFGetTypeID(cf_value) != CFStringGetTypeID())
+ {
+ error = error_create("Property is not a string.");
+ }
+
+ char buffer[256];
+ if (error == NULL)
+ {
+ bool success = CFStringGetCString(cf_value, buffer, sizeof(buffer),
+ kCFStringEncodingASCII);
+ if (!success)
+ {
+ error = error_create("Failed to convert property to C string.");
+ }
+ }
+
+ if (error == NULL)
+ {
+ error = string_copy(buffer, value);
+ }
+
+ CFRelease(cf_value);
+ return error;
+}
+
+libusbp_error * get_int32(io_registry_entry_t entry, CFStringRef name, int32_t * value)
+{
+ assert(entry != MACH_PORT_NULL);
+ assert(name != NULL);
+ assert(value != NULL);
+
+ *value = 0;
+
+ libusbp_error * error = NULL;
+
+ CFTypeRef cf_value = IORegistryEntryCreateCFProperty(entry, name, kCFAllocatorDefault, 0);
+
+ if (cf_value == NULL)
+ {
+ error = error_create("Failed to get int32 property from IORegistryEntry.");
+ }
+
+ if (error == NULL && CFGetTypeID(cf_value) != CFNumberGetTypeID())
+ {
+ error = error_create("Property is not a number.");
+ }
+
+ if (error == NULL)
+ {
+ bool success = CFNumberGetValue(cf_value, kCFNumberSInt32Type, value);
+ if (!success)
+ {
+ error = error_create("Failed to convert property to C integer.");
+ }
+ }
+
+ if (cf_value != NULL) { CFRelease(cf_value); }
+ return error;
+}
+
+libusbp_error * get_uint16(io_registry_entry_t entry, CFStringRef name, uint16_t * value)
+{
+ assert(entry != MACH_PORT_NULL);
+ assert(name != NULL);
+ assert(value != NULL);
+
+ libusbp_error * error = NULL;
+
+ int32_t tmp;
+ error = get_int32(entry, name, &tmp);
+
+ if (error == NULL)
+ {
+ // There is an unchecked conversion of an int32_t to a uint16_t here but
+ // we don't expect any data to be lost.
+ *value = (uint16_t) tmp;
+ }
+
+ return error;
+}
+
diff --git a/dep/libusbp/src/mac/list_mac.c b/dep/libusbp/src/mac/list_mac.c
new file mode 100644
index 00000000..98d889b0
--- /dev/null
+++ b/dep/libusbp/src/mac/list_mac.c
@@ -0,0 +1,103 @@
+#include
+
+static void try_create_device(io_service_t service, libusbp_device ** device)
+{
+ libusbp_error * error = create_device(service, device);
+ if (error != NULL)
+ {
+ assert(*device == NULL);
+
+ // Something went wrong. To make the library more robust and usable, we
+ // ignore this error and continue.
+ #ifdef LIBUSBP_LOG
+ fprintf(stderr, "Problem creating device: %s\n",
+ libusbp_error_get_message(error));
+ #endif
+
+ libusbp_error_free(error);
+ }
+}
+
+libusbp_error * libusbp_list_connected_devices(
+ libusbp_device *** device_list,
+ size_t * device_count)
+{
+ if (device_count != NULL)
+ {
+ *device_count = 0;
+ }
+
+ if (device_list == NULL)
+ {
+ return error_create("Device list output pointer is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ // Create a dictionary that says "IOProviderClass" => "IOUSBDevice"
+ // This dictionary is CFReleased by IOServiceGetMatchingServices.
+ CFMutableDictionaryRef dict = NULL;
+ dict = IOServiceMatching("IOUSBHostDevice");
+ if (dict == NULL)
+ {
+ error = error_create("IOServiceMatching returned null.");
+ }
+
+ // Create an iterator for all the connected USB devices.
+ io_iterator_t iterator = MACH_PORT_NULL;
+ if (error == NULL)
+ {
+ // IOServiceGetMatchingServices consumes one reference to dict,
+ // so we don't have to CFRelease it.
+ kern_return_t result = IOServiceGetMatchingServices(
+ kIOMasterPortDefault, dict, &iterator);
+ dict = NULL;
+ if (result != KERN_SUCCESS)
+ {
+ error = error_create_mach(result, "Failed to get matching services.");
+ }
+ }
+
+ // Allocate a new list.
+ libusbp_device ** new_list = NULL;
+ size_t count = 0;
+ if (error == NULL)
+ {
+ error = device_list_create(&new_list);
+ }
+
+ // Loop through the devices and add them to our list.
+ while(error == NULL)
+ {
+ io_service_t service = IOIteratorNext(iterator);
+ if (service == MACH_PORT_NULL) { break; }
+
+ libusbp_device * device = NULL;
+ try_create_device(service, &device);
+ if (device != NULL)
+ {
+ error = device_list_append(&new_list, &count, device);
+ }
+
+ IOObjectRelease(service);
+ }
+
+ // Pass the list and the count to the caller.
+ if (error == NULL)
+ {
+ *device_list = new_list;
+ new_list = NULL;
+
+ if (device_count != NULL)
+ {
+ *device_count = count;
+ }
+ }
+
+ // Clean up.
+ assert(dict == NULL);
+ if (dict != NULL) { CFRelease(dict); } // makes the code less brittle
+ if (iterator != MACH_PORT_NULL) { IOObjectRelease(iterator); }
+ free_devices_and_list(new_list);
+ return error;
+}
diff --git a/dep/libusbp/src/mac/serial_port_mac.c b/dep/libusbp/src/mac/serial_port_mac.c
new file mode 100644
index 00000000..a5a153a5
--- /dev/null
+++ b/dep/libusbp/src/mac/serial_port_mac.c
@@ -0,0 +1,169 @@
+#include
+
+struct libusbp_serial_port
+{
+ // The I/O Registry ID of the IOBSDSerialClient.
+ uint64_t id;
+
+ // A port filename like "/dev/cu.usbmodemFD123".
+ char * port_name;
+};
+
+libusbp_error * libusbp_serial_port_create(
+ const libusbp_device * device,
+ uint8_t interface_number,
+ bool composite,
+ libusbp_serial_port ** port)
+{
+ LIBUSBP_UNUSED(composite);
+
+ if (port == NULL)
+ {
+ return error_create("Serial port output pointer is null.");
+ }
+
+ *port = NULL;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ // Add one to the interface number because that is what we need for the
+ // typical case: The user specifies the lower of the two interface numbers,
+ // which corresponds to the control interface of a CDC ACM device. We
+ // actually need the data interface because that is the one that the
+ // IOSerialBSDClient lives under. If this +1 causes any problems, it is
+ // easy for the user to address it using an an ifdef. Also, we might make
+ // this function more flexible in the future if we need to handle different
+ // types of serial devices with different drivers or interface layouts.
+ interface_number += 1;
+
+ libusbp_error * error = NULL;
+
+ libusbp_serial_port * new_port = calloc(1, sizeof(libusbp_serial_port));
+
+ if (new_port == NULL)
+ {
+ error = &error_no_memory;
+ }
+
+ // Get the ID for the physical device.
+ uint64_t device_id;
+ if (error == NULL)
+ {
+ device_id = device_get_id(device);
+ }
+
+ // Get an io_service_t for the physical device.
+ io_service_t device_service = MACH_PORT_NULL;
+ if (error == NULL)
+ {
+ error = service_get_from_id(device_id, &device_service);
+ }
+
+ // Get an io_service_t for the interface.
+ io_service_t interface_service = MACH_PORT_NULL;
+ if (error == NULL)
+ {
+ error = service_get_usb_interface(device_service, interface_number, &interface_service);
+ }
+
+ // Get an io_service_t for the IOSerialBSDClient
+ io_service_t serial_service = MACH_PORT_NULL;
+ if (error == NULL)
+ {
+ error = service_get_child_by_class(interface_service,
+ kIOSerialBSDServiceValue, &serial_service);
+ }
+
+ // Get the port name.
+ if (error == NULL)
+ {
+ error = get_string(serial_service, CFSTR(kIOCalloutDeviceKey), &new_port->port_name);
+ }
+
+ // Pass the new object to the caller.
+ if (error == NULL)
+ {
+ *port = new_port;
+ new_port = NULL;
+ }
+
+ if (serial_service != MACH_PORT_NULL) { IOObjectRelease(serial_service); }
+ if (interface_service != MACH_PORT_NULL) { IOObjectRelease(interface_service); }
+ if (device_service != MACH_PORT_NULL) { IOObjectRelease(device_service); }
+ libusbp_serial_port_free(new_port);
+
+ return error;
+}
+
+void libusbp_serial_port_free(libusbp_serial_port * port)
+{
+ if (port != NULL)
+ {
+ libusbp_string_free(port->port_name);
+ free(port);
+ }
+}
+
+libusbp_error * libusbp_serial_port_copy(const libusbp_serial_port * source,
+ libusbp_serial_port ** dest)
+{
+ if (dest == NULL)
+ {
+ return error_create("Serial port output pointer is null.");
+ }
+
+ *dest = NULL;
+
+ if (source == NULL)
+ {
+ return NULL;
+ }
+
+ libusbp_error * error = NULL;
+
+ // Allocate memory for the new object.
+ libusbp_serial_port * new_port = calloc(1, sizeof(libusbp_serial_port));
+
+ if (new_port == NULL)
+ {
+ error = &error_no_memory;
+ }
+
+ // Copy the port name.
+ if (error == NULL)
+ {
+ error = string_copy(source->port_name, &new_port->port_name);
+ }
+
+ // Pass the new object to the caller.
+ if (error == NULL)
+ {
+ *dest = new_port;
+ new_port = NULL;
+ }
+
+ libusbp_serial_port_free(new_port);
+ return error;
+}
+
+libusbp_error * libusbp_serial_port_get_name(
+ const libusbp_serial_port * port,
+ char ** name)
+{
+ if (name == NULL)
+ {
+ return error_create("String output pointer is null.");
+ }
+
+ *name = NULL;
+
+ if (port == NULL)
+ {
+ return error_create("Serial port is null.");
+ }
+
+ return string_copy(port->port_name, name);
+}
diff --git a/dep/libusbp/src/pipe_id.c b/dep/libusbp/src/pipe_id.c
new file mode 100644
index 00000000..0bbedf31
--- /dev/null
+++ b/dep/libusbp/src/pipe_id.c
@@ -0,0 +1,33 @@
+#include
+
+static libusbp_error * error_invalid_pipe_id(uint8_t pipe_id)
+{
+ return error_create("Invalid pipe ID 0x%02x.", pipe_id);
+}
+
+libusbp_error * check_pipe_id(uint8_t pipe_id)
+{
+ if ((pipe_id & ~0x80) > MAX_ENDPOINT_NUMBER || pipe_id == 0x80)
+ {
+ return error_invalid_pipe_id(pipe_id);
+ }
+ return NULL;
+}
+
+libusbp_error * check_pipe_id_in(uint8_t pipe_id)
+{
+ if (!(pipe_id & 0x80))
+ {
+ return error_invalid_pipe_id(pipe_id);
+ }
+ return check_pipe_id(pipe_id);
+}
+
+libusbp_error * check_pipe_id_out(uint8_t pipe_id)
+{
+ if (pipe_id & 0x80)
+ {
+ return error_invalid_pipe_id(pipe_id);
+ }
+ return check_pipe_id(pipe_id);
+}
diff --git a/dep/libusbp/src/string.c b/dep/libusbp/src/string.c
new file mode 100644
index 00000000..eb04a869
--- /dev/null
+++ b/dep/libusbp/src/string.c
@@ -0,0 +1,25 @@
+#include
+
+// Simple wrapper around strdup to make a copy of a string, either for internal
+// use or for returning the string to the user.
+libusbp_error * string_copy(const char * input_string, char ** output_string)
+{
+ assert(input_string != NULL);
+ assert(output_string != NULL);
+
+ *output_string = NULL;
+
+ char * new_string = strdup(input_string);
+ if (new_string == NULL)
+ {
+ return &error_no_memory;
+ }
+
+ *output_string = new_string;
+ return NULL;
+}
+
+void libusbp_string_free(char * string)
+{
+ free(string);
+}
diff --git a/dep/libusbp/src/windows/async_in_transfer_windows.c b/dep/libusbp/src/windows/async_in_transfer_windows.c
new file mode 100644
index 00000000..80b74dd4
--- /dev/null
+++ b/dep/libusbp/src/windows/async_in_transfer_windows.c
@@ -0,0 +1,239 @@
+#include
+
+struct async_in_transfer
+{
+ HANDLE winusb_handle;
+ uint8_t pipe_id;
+ void * buffer;
+ size_t buffer_size;
+ OVERLAPPED overlapped;
+ ULONG transferred;
+ bool pending;
+ libusbp_error * error;
+};
+
+libusbp_error * async_in_pipe_setup(libusbp_generic_handle * handle, uint8_t pipe_id)
+{
+ assert(handle != NULL);
+
+ HANDLE winusb_handle = libusbp_generic_handle_get_winusb_handle(handle);
+
+ // Turn on raw I/O, because without it the transfers will not be efficient
+ // enough in Windows Vista and Windows 7, as tested by test_async_in.
+ UCHAR raw_io = 1;
+ BOOL success = WinUsb_SetPipePolicy(winusb_handle, pipe_id, RAW_IO,
+ sizeof(raw_io), &raw_io);
+ if (!success)
+ {
+ return error_create_winapi("Failed to enable raw I/O for pipe 0x%x.", pipe_id);
+ }
+ return NULL;
+}
+
+void async_in_transfer_free(async_in_transfer * transfer)
+{
+ if (transfer == NULL) { return; }
+
+ if (transfer->pending)
+ {
+ // Unfortunately, this transfer is still pending, so we cannot free it;
+ // the kernel needs to be able to write to this transfer's memory when
+ // it completes. We could just abort the process here, but instead we
+ // we choose to have a memory leak, since it can be easily detected and
+ // might be small enough to be harmless.
+ return;
+ }
+
+ libusbp_error_free(transfer->error);
+ CloseHandle(transfer->overlapped.hEvent);
+ free(transfer->buffer);
+ free(transfer);
+}
+
+void async_in_transfer_submit(async_in_transfer * transfer)
+{
+ assert(transfer != NULL);
+ assert(transfer->pending == false);
+
+ libusbp_error_free(transfer->error);
+ transfer->error = NULL;
+ transfer->pending = true;
+ transfer->transferred = 0;
+
+ BOOL success = WinUsb_ReadPipe(
+ transfer->winusb_handle,
+ transfer->pipe_id,
+ transfer->buffer,
+ transfer->buffer_size,
+ &transfer->transferred,
+ &transfer->overlapped);
+ if (success)
+ {
+ // The transfer completed immediately.
+ transfer->pending = false;
+ }
+ else if (GetLastError() == ERROR_IO_PENDING)
+ {
+ // The transfer is pending.
+ }
+ else
+ {
+ // An error happened.
+ transfer->error = error_create_winapi("Failed to submit asynchronous in transfer.");
+ transfer->pending = false;
+ }
+}
+
+libusbp_error * async_in_transfer_create(
+ libusbp_generic_handle * handle, uint8_t pipe_id, size_t transfer_size,
+ async_in_transfer ** transfer)
+{
+ assert(transfer_size != 0);
+ assert(transfer != NULL);
+
+ if (transfer_size > ULONG_MAX)
+ {
+ // WinUSB uses ULONGs to represent sizes.
+ return error_create("Transfer size is too large.");
+ }
+
+ libusbp_error * error = NULL;
+
+ // Allocate the transfer struct.
+ async_in_transfer * new_transfer = NULL;
+ if (error == NULL)
+ {
+ new_transfer = calloc(1, sizeof(async_in_transfer));
+ if (new_transfer == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ // Allocate the buffer for the transfer.
+ void * new_buffer = NULL;
+ if (error == NULL)
+ {
+ new_buffer = malloc(transfer_size);
+ if (new_buffer == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ // Create an event.
+ HANDLE new_event = INVALID_HANDLE_VALUE;
+ if (error == NULL)
+ {
+ new_event = CreateEvent(NULL, false, false, NULL);
+ if (new_event == NULL)
+ {
+ error = error_create_winapi(
+ "Failed to create event for asynchronous in transfer.");
+ }
+ }
+
+ // Assemble the transfer and pass it to the caller.
+ if (error == NULL)
+ {
+ new_transfer->winusb_handle = libusbp_generic_handle_get_winusb_handle(handle);
+ new_transfer->buffer_size = transfer_size;
+ new_transfer->pipe_id = pipe_id;
+
+ new_transfer->buffer = new_buffer;
+ new_buffer = NULL;
+
+ new_transfer->overlapped.hEvent = new_event;
+ new_event = INVALID_HANDLE_VALUE;
+
+ *transfer = new_transfer;
+ new_transfer = NULL;
+ }
+
+ if (new_event != INVALID_HANDLE_VALUE) { CloseHandle(new_event); }
+ free(new_buffer);
+ free(new_transfer);
+ return error;
+}
+
+libusbp_error * async_in_transfer_get_results(async_in_transfer * transfer,
+ void * buffer, size_t * transferred, libusbp_error ** transfer_error)
+{
+ assert(transfer != NULL);
+ assert(!transfer->pending);
+
+ size_t tmp_transferred = transfer->transferred;
+
+ // Make sure we don't overflow the user's buffer.
+ if (tmp_transferred > transfer->buffer_size)
+ {
+ tmp_transferred = transfer->buffer_size;
+ }
+
+ if (buffer != NULL)
+ {
+ memcpy(buffer, transfer->buffer, tmp_transferred);
+ }
+
+ if (transferred != NULL)
+ {
+ *transferred = tmp_transferred;
+ }
+
+ if (transfer_error != NULL)
+ {
+ *transfer_error = libusbp_error_copy(transfer->error);
+ }
+
+ return NULL;
+}
+
+// Cancels all of the transfers on this pipe, given one of the transfers.
+libusbp_error * async_in_transfer_cancel(async_in_transfer * transfer)
+{
+ if (transfer == NULL)
+ {
+ return error_create("Transfer to cancel is null.");
+ }
+
+ BOOL success = WinUsb_AbortPipe(transfer->winusb_handle, transfer->pipe_id);
+ if (!success)
+ {
+ return error_create_winapi("Failed to cancel transfers on pipe 0x%x.",
+ transfer->pipe_id);
+ }
+
+ return NULL;
+}
+
+bool async_in_transfer_pending(async_in_transfer * transfer)
+{
+ assert(transfer != NULL);
+
+ if (!transfer->pending)
+ {
+ return false;
+ }
+
+ DWORD transferred = 0;
+ BOOL success = WinUsb_GetOverlappedResult(
+ transfer->winusb_handle, &transfer->overlapped, &transferred, false);
+
+ transfer->transferred = transferred;
+
+ if (success)
+ {
+ transfer->pending = false;
+ }
+ else if (GetLastError() == ERROR_IO_INCOMPLETE)
+ {
+ // The transfer is still pending.
+ }
+ else
+ {
+ transfer->error = error_create_overlapped("Asynchronous IN transfer failed.");
+ transfer->pending = false;
+ }
+
+ return transfer->pending;
+}
diff --git a/dep/libusbp/src/windows/device_instance_id_windows.c b/dep/libusbp/src/windows/device_instance_id_windows.c
new file mode 100644
index 00000000..21afb997
--- /dev/null
+++ b/dep/libusbp/src/windows/device_instance_id_windows.c
@@ -0,0 +1,25 @@
+#include
+
+libusbp_error * create_id_string(HDEVINFO list, PSP_DEVINFO_DATA info, char ** id)
+{
+ assert(list != INVALID_HANDLE_VALUE);
+ assert(info != NULL);
+ assert(id != NULL);
+
+ DWORD size = MAX_DEVICE_ID_LEN + 1;
+ char * new_id = malloc(size);
+ if (new_id == NULL)
+ {
+ return &error_no_memory;
+ }
+
+ bool success = SetupDiGetDeviceInstanceId(list, info, new_id, size, NULL);
+ if (!success)
+ {
+ free(new_id);
+ return error_create_winapi("Error getting device instance ID.");
+ }
+
+ *id = new_id;
+ return NULL;
+}
diff --git a/dep/libusbp/src/windows/device_windows.c b/dep/libusbp/src/windows/device_windows.c
new file mode 100644
index 00000000..ab20ff06
--- /dev/null
+++ b/dep/libusbp/src/windows/device_windows.c
@@ -0,0 +1,362 @@
+#include
+
+struct libusbp_device
+{
+ // A string like: USB\VID_xxxx&PID_xxxx\idthing
+ char * device_instance_id;
+ uint16_t product_id;
+ uint16_t vendor_id;
+ uint16_t revision;
+};
+
+static libusbp_error * device_allocate(libusbp_device ** device)
+{
+ assert(device != NULL);
+ *device = calloc(1, sizeof(libusbp_device));
+ if (*device == NULL)
+ {
+ return &error_no_memory;
+ }
+ return NULL;
+}
+
+// Gets the hardware IDs of the device, in ASCII REG_MULTI_SZ format. If there
+// is no error, the returned IDs must be freed with libusbp_string_free.
+// This function converts the IDs to uppercase before returning them.
+static libusbp_error * device_get_hardware_ids(
+ HDEVINFO list, PSP_DEVINFO_DATA info, char ** ids)
+{
+ assert(list != INVALID_HANDLE_VALUE);
+ assert(info != NULL);
+ assert(ids != NULL);
+
+ *ids = NULL;
+
+ libusbp_error * error = NULL;
+
+ // Get the size of the hardware IDs.
+ DWORD data_type = 0;
+ DWORD size = 0;
+ if (error == NULL)
+ {
+ BOOL success = SetupDiGetDeviceRegistryProperty(list, info,
+ SPDRP_HARDWAREID, &data_type, NULL, 0, &size);
+ if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ {
+ error = error_create_winapi("Failed to get size of hardware IDs.");
+ }
+ }
+
+ // Allocate memory.
+ char * new_ids = NULL;
+ if (error == NULL)
+ {
+ new_ids = malloc(size);
+ if (new_ids == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ // Get the actual hardware IDs.
+ if (error == NULL)
+ {
+ BOOL success = SetupDiGetDeviceRegistryProperty(list, info,
+ SPDRP_HARDWAREID, &data_type, (unsigned char *)new_ids, size, NULL);
+ if (!success)
+ {
+ error = error_create_winapi("Failed to get hardware IDs.");
+ }
+ }
+
+ // Check the data type.
+ if (error == NULL && data_type != REG_MULTI_SZ)
+ {
+ error = error_create("Hardware IDs are wrong data type: %ld.", data_type);
+ }
+ if (error == NULL && (size < 2 || new_ids[size - 2] != 0 || new_ids[size - 1] != 0))
+ {
+ error = error_create("Hardware IDs are empty or not terminated correctly.");
+ }
+
+ // Capitalize the hardware IDs because some drivers create USB IDs with the
+ // wrong capitalization (i.e. "Vid" instead of "VID").
+ if (error == NULL)
+ {
+ for (DWORD i = 0; i < size; i++)
+ {
+ if (new_ids[i] >= 'a' && new_ids[i] <= 'z')
+ {
+ new_ids[i] -= 'a' - 'A';
+ }
+ }
+ }
+
+ // Pass the IDs to the caller.
+ if (error == NULL)
+ {
+ *ids = new_ids;
+ new_ids = NULL;
+ }
+
+ free(new_ids);
+ return error;
+}
+
+// This function extracts the product ID, vendor ID, and revision code
+// from the hardware IDs.
+// The "ids" parmaeter must be a pointer to a REG_MULTI_SZ data structure.
+// (null-terminated ASCII strings, ending with an empty string).
+static libusbp_error * device_take_info_from_hardware_ids(
+ libusbp_device * device, const char * ids)
+{
+ assert(ids != NULL);
+ assert(device != NULL);
+
+ device->vendor_id = 0;
+ device->product_id = 0;
+ device->revision = 0;
+
+ for(; *ids; ids += strlen(ids) + 1)
+ {
+ uint16_t vendor_id, product_id, revision;
+ int result = sscanf(ids, "USB\\VID_%4hx&PID_%4hx&REV_%4hx",
+ &vendor_id, &product_id, &revision);
+ if (result == 3)
+ {
+ device->vendor_id = vendor_id;
+ device->product_id = product_id;
+ device->revision = revision;
+ return NULL;
+ }
+ }
+
+ return error_create("Device has no hardware ID with the correct format.");
+}
+
+libusbp_error * create_device(HDEVINFO list, PSP_DEVINFO_DATA info, libusbp_device ** device)
+{
+ assert(list != INVALID_HANDLE_VALUE);
+ assert(info != NULL);
+ assert(info->cbSize == sizeof(SP_DEVINFO_DATA));
+ assert(device != NULL);
+
+ *device = NULL;
+
+ libusbp_error * error = NULL;
+
+ // Allocate memory for the device itself.
+ libusbp_device * new_device = NULL;
+ if (error == NULL)
+ {
+ error = device_allocate(&new_device);
+ }
+
+ // Get the device instance ID.
+ if (error == NULL)
+ {
+ error = create_id_string(list, info, &new_device->device_instance_id);
+ }
+
+ // Get the vendor ID, product ID, and revision from the hardware IDs.
+ char * new_ids = NULL;
+ if (error == NULL)
+ {
+ error = device_get_hardware_ids(list, info, &new_ids);
+ }
+ if (error == NULL)
+ {
+ error = device_take_info_from_hardware_ids(new_device, new_ids);
+ }
+
+ // Return the device to the caller.
+ if (error == NULL)
+ {
+ *device = new_device;
+ new_device = NULL;
+ }
+
+ libusbp_string_free(new_ids);
+ libusbp_device_free(new_device);
+ return error;
+}
+
+libusbp_error * libusbp_device_copy(const libusbp_device * source, libusbp_device ** dest)
+{
+ if (dest == NULL)
+ {
+ return error_create("Device output pointer is null.");
+ }
+
+ *dest = NULL;
+
+ if (source == NULL)
+ {
+ return NULL;
+ }
+
+ libusbp_error * error = NULL;
+
+ // Allocate memory for the device itself.
+ libusbp_device * new_device = NULL;
+ if (error == NULL)
+ {
+ error = device_allocate(&new_device);
+ }
+
+ // Copy the device instance ID.
+ if (error == NULL)
+ {
+ assert(source->device_instance_id != NULL);
+ size_t size = strlen(source->device_instance_id) + 1;
+ char * new_id = malloc(size);
+ if (new_id == NULL)
+ {
+ error = &error_no_memory;
+ }
+ else
+ {
+ memcpy(new_id, source->device_instance_id, size);
+ new_device->device_instance_id = new_id;
+ }
+ }
+
+ if (error == NULL)
+ {
+ new_device->vendor_id = source->vendor_id;
+ new_device->product_id = source->product_id;
+ new_device->revision = source->revision;
+ }
+
+ // Pass the device to the caller.
+ if (error == NULL)
+ {
+ *dest = new_device;
+ new_device = NULL;
+ }
+
+ libusbp_device_free(new_device);
+ return error;
+}
+
+// Note: Some pointers in the device might be null when we are
+// freeing, because this can be called from the library to clean up a
+// failed device creation.
+void libusbp_device_free(libusbp_device * device)
+{
+ if (device != NULL)
+ {
+ free(device->device_instance_id);
+ free(device);
+ }
+}
+
+libusbp_error * libusbp_device_get_vendor_id(
+ const libusbp_device * device, uint16_t * vendor_id)
+{
+ if (vendor_id == NULL)
+ {
+ return error_create("Vendor ID output pointer is null.");
+ }
+
+ *vendor_id = 0;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ *vendor_id = device->vendor_id;
+ return NULL;
+}
+
+libusbp_error * libusbp_device_get_product_id(
+ const libusbp_device * device, uint16_t * product_id)
+{
+ if (product_id == NULL)
+ {
+ return error_create("Product ID output pointer is null.");
+ }
+
+ *product_id = 0;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ *product_id = device->product_id;
+ return NULL;
+}
+
+libusbp_error * libusbp_device_get_revision(
+ const libusbp_device * device, uint16_t * revision)
+{
+ if (revision == NULL)
+ {
+ return error_create("Device revision output pointer is null.");
+ }
+
+ *revision = 0;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ *revision = device->revision;
+ return NULL;
+}
+
+libusbp_error * libusbp_device_get_serial_number(
+ const libusbp_device * device, char ** serial_number)
+{
+ if (serial_number == NULL)
+ {
+ return error_create("Serial number output pointer is null.");
+ }
+
+ *serial_number = NULL;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ if (error == NULL && strlen(device->device_instance_id) < 22)
+ {
+ error = error_create("Device instance ID is too short.");
+ }
+
+ if (error == NULL)
+ {
+ error = string_copy(device->device_instance_id + 22, serial_number);
+ }
+
+ if (error != NULL)
+ {
+ error = error_add(error, "Failed to get serial number.");
+ }
+ return error;
+}
+
+libusbp_error * libusbp_device_get_os_id(
+ const libusbp_device * device, char ** id)
+{
+ if (id == NULL)
+ {
+ return error_create("Device OS ID output pointer is null.");
+ }
+
+ *id = NULL;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ return string_copy(device->device_instance_id, id);
+}
+
diff --git a/dep/libusbp/src/windows/error_windows.c b/dep/libusbp/src/windows/error_windows.c
new file mode 100644
index 00000000..756170de
--- /dev/null
+++ b/dep/libusbp/src/windows/error_windows.c
@@ -0,0 +1,111 @@
+/* This file has functions for converting Windows error codes libusbp_error
+ * objects. See error_message_test.cpp for documentation/justification of the
+ * conversions that are performed. */
+
+#include
+
+static libusbp_error * error_create_winapi_v(
+ DWORD error_code, const char * format, va_list ap)
+{
+ libusbp_error * error = error_create("Windows error code 0x%lx.", error_code);
+
+ bool skip_windows_message = false;
+
+ // Convert certain Windows error codes into libusbp error codes.
+ switch(error_code)
+ {
+ case ERROR_ACCESS_DENIED:
+ error = error_add(error, "Try closing all other programs that are using the device.");
+ error = error_add_code(error, LIBUSBP_ERROR_ACCESS_DENIED);
+ break;
+ case ERROR_OUTOFMEMORY:
+ case ERROR_NOT_ENOUGH_MEMORY:
+ error = error_add_code(error, LIBUSBP_ERROR_MEMORY);
+ break;
+ case ERROR_GEN_FAILURE:
+ skip_windows_message = true;
+ error = error_add(error, "The request was invalid or there was an I/O problem.");
+ error = error_add_code(error, LIBUSBP_ERROR_STALL);
+ error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
+ break;
+ case ERROR_SEM_TIMEOUT:
+ skip_windows_message = true;
+ error = error_add(error, "The operation timed out.");
+ error = error_add_code(error, LIBUSBP_ERROR_TIMEOUT);
+ break;
+ case ERROR_OPERATION_ABORTED:
+ skip_windows_message = true;
+ error = error_add(error, "The operation was cancelled.");
+ error = error_add_code(error, LIBUSBP_ERROR_CANCELLED);
+ break;
+ }
+
+ // Get an English sentence from Windows to help describe the error.
+ if (!skip_windows_message)
+ {
+ char buffer[256];
+ DWORD size = FormatMessage(
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_MAX_WIDTH_MASK,
+ NULL, error_code,
+ MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), buffer,
+ sizeof(buffer), NULL);
+ if (size != 0)
+ {
+ // Remove the leading space and then add the message to the error.
+ if (buffer[size - 1] == ' ') { buffer[--size] = 0; }
+ error = error_add(error, "%s", buffer);
+ }
+ }
+
+ // Finally, add the context message provided by the caller.
+ return error_add_v(error, format, ap);
+}
+
+libusbp_error * error_create_winapi(const char * format, ...)
+{
+ DWORD error_code = GetLastError();
+ va_list ap;
+ va_start(ap, format);
+ libusbp_error * error = error_create_winapi_v(error_code, format, ap);
+ va_end(ap);
+ return error;
+}
+
+// This is for reporting an error from WinUsb_GetOverlappedResult. This
+// function is necessary because ERROR_FILE_NOT_FOUND has a special meaning if
+// it is the result of an asynchronous USB transfer.
+libusbp_error * error_create_overlapped(const char * format, ...)
+{
+ DWORD error_code = GetLastError();
+ va_list ap;
+ va_start(ap, format);
+ libusbp_error * error = NULL;
+
+ switch(error_code)
+ {
+ case ERROR_FILE_NOT_FOUND:
+ error = error_create("Windows error code 0x%lx.", error_code);
+ error = error_add(error, "The device was disconnected.");
+ error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
+ error = error_add_v(error, format, ap);
+ break;
+
+ default:
+ error = error_create_winapi_v(error_code, format, ap);
+ break;
+ }
+
+ va_end(ap);
+ return error;
+}
+
+libusbp_error * error_create_cr(CONFIGRET cr, const char * format, ...)
+{
+ libusbp_error * error = error_create("CONFIGRET error code 0x%lx.", cr);
+
+ va_list ap;
+ va_start(ap, format);
+ error = error_add_v(error, format, ap);
+ va_end(ap);
+ return error;
+}
diff --git a/dep/libusbp/src/windows/generic_handle_windows.c b/dep/libusbp/src/windows/generic_handle_windows.c
new file mode 100644
index 00000000..fadb271b
--- /dev/null
+++ b/dep/libusbp/src/windows/generic_handle_windows.c
@@ -0,0 +1,316 @@
+#include
+
+// TODO: add a test for the issue where WinUSB cannot send data from read-only
+// memory, then add a note about it to the documentation for
+// libusbp_control_transfer and PLATFORM_NOTES.md.
+
+struct libusbp_generic_handle
+{
+ HANDLE file_handle;
+ WINUSB_INTERFACE_HANDLE winusb_handle;
+};
+
+libusbp_error * libusbp_generic_handle_open(
+ const libusbp_generic_interface * gi,
+ libusbp_generic_handle ** gh
+)
+{
+ if (gh == NULL)
+ {
+ return error_create("Generic handle output pointer is null.");
+ }
+
+ *gh = NULL;
+
+ if (gi == NULL)
+ {
+ return error_create("Generic interface is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ // Allocate memory for the struct.
+ libusbp_generic_handle * new_gh = malloc(sizeof(libusbp_generic_handle));
+ if (new_gh == NULL)
+ {
+ error = &error_no_memory;
+ }
+
+ // Initialize the handles so we can clean up properly in case an error
+ // happens.
+ if (error == NULL)
+ {
+ new_gh->file_handle = INVALID_HANDLE_VALUE;
+ new_gh->winusb_handle = INVALID_HANDLE_VALUE;
+ }
+
+ // Get the filename.
+ char * filename = NULL;
+ if (error == NULL)
+ {
+ error = libusbp_generic_interface_get_os_filename(gi, &filename);
+ }
+
+ // Open the file. We have observed this step failing with error code
+ // ERROR_NOT_FOUND if the device was recently unplugged.
+ if (error == NULL)
+ {
+ new_gh->file_handle = CreateFile(filename,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+
+ if (new_gh->file_handle == INVALID_HANDLE_VALUE)
+ {
+ error = error_create_winapi("Failed to open generic handle.");
+ }
+ }
+
+ // Initialize WinUSB.
+ if (error == NULL)
+ {
+ BOOL success = WinUsb_Initialize(new_gh->file_handle, &new_gh->winusb_handle);
+ if (!success)
+ {
+ error = error_create_winapi("Failed to initialize WinUSB.");
+ }
+ }
+
+ if (error == NULL)
+ {
+ // Success.
+ *gh = new_gh;
+ new_gh = NULL;
+ }
+
+ libusbp_string_free(filename);
+ libusbp_generic_handle_close(new_gh);
+ return error;
+}
+
+void libusbp_generic_handle_close(libusbp_generic_handle * gh)
+{
+ if (gh != NULL)
+ {
+ if (gh->winusb_handle != INVALID_HANDLE_VALUE)
+ {
+ WinUsb_Free(gh->winusb_handle);
+ }
+
+ if (gh->file_handle != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(gh->file_handle);
+ }
+
+ free(gh);
+ }
+}
+
+libusbp_error * libusbp_generic_handle_open_async_in_pipe(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ libusbp_async_in_pipe ** pipe)
+{
+ return async_in_pipe_create(handle, pipe_id, pipe);
+}
+
+libusbp_error * libusbp_generic_handle_set_timeout(
+ libusbp_generic_handle * gh,
+ uint8_t pipe_id,
+ uint32_t timeout)
+{
+ if (gh == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ ULONG winusb_timeout = timeout;
+ BOOL success = WinUsb_SetPipePolicy(gh->winusb_handle, pipe_id,
+ PIPE_TRANSFER_TIMEOUT, sizeof(winusb_timeout), &winusb_timeout);
+ if (!success)
+ {
+ return error_create_winapi("Failed to set timeout.");
+ }
+ return NULL;
+}
+
+libusbp_error * libusbp_control_transfer(
+ libusbp_generic_handle * gh,
+ uint8_t bmRequestType,
+ uint8_t bRequest,
+ uint16_t wValue,
+ uint16_t wIndex,
+ void * data,
+ uint16_t wLength,
+ size_t * transferred)
+{
+ if (transferred != NULL)
+ {
+ *transferred = 0;
+ }
+
+ if (gh == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ if (wLength != 0 && data == NULL)
+ {
+ return error_create("Control transfer buffer was null while wLength was non-zero.");
+ }
+
+ WINUSB_SETUP_PACKET packet;
+ packet.RequestType = bmRequestType;
+ packet.Request = bRequest;
+ packet.Value = wValue;
+ packet.Index = wIndex;
+ packet.Length = wLength;
+
+ ULONG winusb_transferred;
+ BOOL success = WinUsb_ControlTransfer(gh->winusb_handle, packet, data,
+ wLength, &winusb_transferred, NULL);
+ if (!success)
+ {
+ return error_create_winapi("Control transfer failed.");
+ }
+
+ if (transferred)
+ {
+ *transferred = winusb_transferred;
+ }
+
+ return NULL;
+}
+
+libusbp_error * libusbp_write_pipe(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ const void * data,
+ size_t size,
+ size_t * transferred)
+{
+ if (transferred != NULL)
+ {
+ *transferred = 0;
+ }
+
+ if (handle == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ if (error == NULL)
+ {
+ error = check_pipe_id_out(pipe_id);
+ }
+
+ if (error == NULL && size > ULONG_MAX)
+ {
+ error = error_create("Transfer size is too large.");
+ }
+
+ if (error == NULL && data == NULL && size)
+ {
+ error = error_create("Buffer is null.");
+ }
+
+ ULONG winusb_transferred = 0;
+ if (error == NULL)
+ {
+ BOOL success = WinUsb_WritePipe(handle->winusb_handle, pipe_id,
+ (uint8_t *)data, size, &winusb_transferred, NULL);
+ if (!success)
+ {
+ error = error_create_winapi("");
+ }
+ }
+
+ if (transferred)
+ {
+ *transferred = winusb_transferred;
+ }
+
+ if (error != NULL)
+ {
+ error = error_add(error, "Failed to write to pipe.");
+ }
+
+ return error;
+}
+
+libusbp_error * libusbp_read_pipe(
+ libusbp_generic_handle * handle,
+ uint8_t pipe_id,
+ void * data,
+ size_t size,
+ size_t * transferred)
+{
+ if (transferred != NULL)
+ {
+ *transferred = 0;
+ }
+
+ if (handle == NULL)
+ {
+ return error_create("Generic handle is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ if (error == NULL)
+ {
+ error = check_pipe_id_in(pipe_id);
+ }
+
+ if (error == NULL && size == 0)
+ {
+ error = error_create("Transfer size 0 is not allowed.");
+ }
+
+ if (error == NULL && size > ULONG_MAX)
+ {
+ error = error_create("Transfer size is too large.");
+ }
+
+ if (error == NULL && data == NULL)
+ {
+ error = error_create("Buffer is null.");
+ }
+
+ ULONG winusb_transferred = 0;
+ if (error == NULL)
+ {
+ BOOL success = WinUsb_ReadPipe(handle->winusb_handle, pipe_id, data,
+ size, &winusb_transferred, NULL);
+ if (!success)
+ {
+ error = error_create_winapi("");
+ }
+ }
+
+ if (transferred)
+ {
+ *transferred = winusb_transferred;
+ }
+
+ if (error != NULL)
+ {
+ error = error_add(error, "Failed to read from pipe.");
+ }
+
+ return error;
+}
+
+HANDLE libusbp_generic_handle_get_winusb_handle(libusbp_generic_handle * handle)
+{
+ if (handle == NULL) { return INVALID_HANDLE_VALUE; }
+ return handle->winusb_handle;
+}
+
+libusbp_error * generic_handle_events(libusbp_generic_handle * handle)
+{
+ LIBUSBP_UNUSED(handle);
+ return NULL;
+}
diff --git a/dep/libusbp/src/windows/generic_interface_windows.c b/dep/libusbp/src/windows/generic_interface_windows.c
new file mode 100644
index 00000000..70d1d2a3
--- /dev/null
+++ b/dep/libusbp/src/windows/generic_interface_windows.c
@@ -0,0 +1,366 @@
+#include
+
+struct libusbp_generic_interface
+{
+ uint8_t interface_number;
+ char * device_instance_id;
+ char * filename;
+};
+
+// Returns NULL via the driver_name parameter if no driver is installed.
+// Otherwise returns the driver name.
+static libusbp_error * get_driver_name(
+ HDEVINFO list, PSP_DEVINFO_DATA info, char ** driver_name)
+{
+ assert(list != INVALID_HANDLE_VALUE);
+ assert(info != NULL);
+ assert(driver_name != NULL);
+
+ *driver_name = NULL;
+
+ // Get the "Service" key from the registry, which will say what
+ // Windows service is assigned to this device.
+ CHAR service[64];
+ DWORD data_type;
+ bool success = SetupDiGetDeviceRegistryProperty(list, info, SPDRP_SERVICE,
+ &data_type, (BYTE *)&service, sizeof(service), NULL);
+ if (!success)
+ {
+ if (GetLastError() == ERROR_INVALID_DATA)
+ {
+ // The registry key isn't present, so the device does not
+ // have have a driver.
+ return NULL;
+ }
+ else
+ {
+ return error_create_winapi("Failed to get the service name.");
+ }
+ }
+ if (data_type != REG_SZ)
+ {
+ return error_create("Service name is the wrong data type: %ld.", data_type);
+ }
+
+ return string_copy(service, driver_name);
+}
+
+// Checks to see if a suitable driver for a USB generic interface is installed
+// by reading the Service registry key.
+static libusbp_error * check_driver_installation(
+ HDEVINFO list, PSP_DEVINFO_DATA info,
+ bool * good_driver_installed, bool * bad_driver_installed,
+ char ** driver_name)
+{
+ assert(list != INVALID_HANDLE_VALUE);
+ assert(info != NULL);
+ assert(good_driver_installed != NULL);
+ assert(bad_driver_installed != NULL);
+ assert(driver_name != NULL);
+
+ *good_driver_installed = false;
+ *bad_driver_installed = false;
+ *driver_name = NULL;
+
+ libusbp_error * error;
+
+ error = get_driver_name(list, info, driver_name);
+ if (error != NULL)
+ {
+ return error;
+ }
+
+ if (*driver_name == NULL)
+ {
+ // No driver installed.
+ return NULL;
+ }
+
+ // We really don't want this comparison to be affected by the user's locale,
+ // since that will probably just mess things up.
+ int result = CompareString(LOCALE_INVARIANT, NORM_IGNORECASE,
+ *driver_name, -1, "winusb", -1);
+ if (result == CSTR_EQUAL)
+ {
+ *good_driver_installed = true;
+ return NULL;
+ }
+
+ *bad_driver_installed = true;
+ return NULL;
+}
+
+static libusbp_error * get_first_device_interface_guid(HDEVINFO list,
+ PSP_DEVINFO_DATA info, GUID * guid)
+{
+ assert(list != INVALID_HANDLE_VALUE);
+ assert(info != NULL);
+ assert(guid != NULL);
+
+ HANDLE key = SetupDiOpenDevRegKey(list, info, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
+ if (key == INVALID_HANDLE_VALUE)
+ {
+ return error_create_winapi(
+ "Failed to get device registry key in order to find its device interface GUIDs.");
+ }
+
+ // Get the size of the DeviceInterfaceGUIDs key.
+ // (Partial reads are not allowed.)
+ DWORD size;
+ LONG reg_result;
+ reg_result = RegQueryValueExW(key, L"DeviceInterfaceGUIDs", NULL, NULL, NULL, &size);
+ if (reg_result != ERROR_SUCCESS)
+ {
+ if (reg_result == ERROR_FILE_NOT_FOUND)
+ {
+ RegCloseKey(key);
+ return error_create("DeviceInterfaceGUIDs key does not exist.");
+ }
+ else
+ {
+ RegCloseKey(key);
+ SetLastError(reg_result);
+ return error_create_winapi("Failed to get DeviceInterfaceGUIDs key size.");
+ }
+ }
+
+ WCHAR * guids = malloc(size);
+ if (guids == NULL)
+ {
+ return &error_no_memory;
+ }
+
+ DWORD reg_type;
+ reg_result = RegQueryValueExW(key, L"DeviceInterfaceGUIDs",
+ NULL, ®_type, (BYTE *)guids, &size);
+ RegCloseKey(key);
+ if (reg_result)
+ {
+ free(guids);
+ SetLastError(reg_result);
+ return error_create_winapi("Failed to get DeviceInterfaceGUIDs key.");
+ }
+
+ if (reg_type != REG_MULTI_SZ)
+ {
+ free(guids);
+ return error_create(
+ "Expected DeviceInterfaceGUIDs key to be a REG_MULTI_SZ (0x%x), got 0x%lx.",
+ REG_MULTI_SZ, reg_type);
+ }
+
+ HRESULT hr = IIDFromString(guids, guid);
+ free(guids);
+ if (FAILED(hr))
+ {
+ return error_create_hr(hr, "Failed to parse device interface GUID.");
+ }
+
+ return NULL;
+}
+
+static libusbp_error * generic_interface_initialize(libusbp_generic_interface * gi,
+ const libusbp_device * device, uint8_t interface_number, bool composite)
+{
+ assert(gi != NULL);
+ assert(device != NULL);
+
+ // First, initialize everything so that the struct can safely be
+ // freed if something goes wrong.
+ gi->interface_number = interface_number;
+ gi->device_instance_id = NULL;
+ gi->filename = NULL;
+
+ libusbp_error * error = NULL;
+
+ // Get the device instance ID of the overall USB device.
+ char * usb_device_id;
+ if (error == NULL)
+ {
+ error = libusbp_device_get_os_id(device, &usb_device_id);
+ }
+
+ // Access the generic interface device node with SetupAPI.
+ HDEVINFO list = INVALID_HANDLE_VALUE;
+ SP_DEVINFO_DATA device_info_data;
+ if (error == NULL)
+ {
+ error = get_interface(usb_device_id, interface_number,
+ composite, &list, &device_info_data);
+ }
+
+ // Record the device instance ID.
+ if (error == NULL)
+ {
+ error = create_id_string(list, &device_info_data, &gi->device_instance_id);
+ }
+
+ // Check the driver situation.
+ char * driver_name = NULL;
+ if (error == NULL)
+ {
+ bool good_driver_installed = false;
+ bool bad_driver_installed = false;
+ error = check_driver_installation(list, &device_info_data,
+ &good_driver_installed, &bad_driver_installed, &driver_name);
+ if (error == NULL && bad_driver_installed)
+ {
+ error = error_create("Device is attached to an incorrect driver: %s.", driver_name);
+ }
+ if (error == NULL && !good_driver_installed)
+ {
+ error = error_create("Device is not using any driver.");
+ error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
+ }
+ }
+
+ // Get the first device interface GUID.
+ GUID guid;
+ if (error == NULL)
+ {
+ error = get_first_device_interface_guid(list, &device_info_data, &guid);
+ }
+
+ // Use that GUID to get an actual filename that we can later open
+ // with CreateFile to access the device.
+ if (error == NULL)
+ {
+ error = get_filename_from_devinst_and_guid(device_info_data.DevInst,
+ &guid, &gi->filename);
+ }
+
+ // Clean up.
+
+ if (list != INVALID_HANDLE_VALUE)
+ {
+ SetupDiDestroyDeviceInfoList(list);
+ }
+ libusbp_string_free(driver_name);
+ libusbp_string_free(usb_device_id);
+ if (error != NULL)
+ {
+ error = error_add(error, "Failed to initialize generic interface.");
+ }
+ return error;
+}
+
+libusbp_error * libusbp_generic_interface_create(
+ const libusbp_device * device,
+ uint8_t interface_number,
+ bool composite,
+ libusbp_generic_interface ** gi)
+{
+ if (gi == NULL)
+ {
+ return error_create("Generic interface output pointer is null.");
+ }
+
+ *gi = NULL;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ libusbp_generic_interface * new_gi = malloc(sizeof(libusbp_generic_interface));
+ if (new_gi == NULL)
+ {
+ return &error_no_memory;
+ }
+
+ libusbp_error * error = generic_interface_initialize(new_gi, device,
+ interface_number, composite);
+ if (error)
+ {
+ libusbp_generic_interface_free(new_gi);
+ return error;
+ }
+ *gi = new_gi;
+
+ return NULL;
+}
+
+libusbp_error * libusbp_generic_interface_copy(
+ const libusbp_generic_interface * source,
+ libusbp_generic_interface ** dest
+)
+{
+ if (dest == NULL)
+ {
+ return error_create("Generic interface output pointer is null.");
+ }
+
+ *dest = NULL;
+
+ if (source == NULL)
+ {
+ return NULL;
+ }
+
+ assert(source->device_instance_id);
+ assert(source->filename);
+
+ libusbp_generic_interface * new_gi = calloc(1, sizeof(libusbp_generic_interface));
+ char * id = strdup(source->device_instance_id);
+ char * filename = strdup(source->filename);
+ if (new_gi == NULL || id == NULL || filename == NULL)
+ {
+ free(new_gi);
+ free(id);
+ free(filename);
+ return &error_no_memory;
+ }
+
+ new_gi->interface_number = source->interface_number;
+ new_gi->device_instance_id = id;
+ new_gi->filename = filename;
+ *dest = new_gi;
+
+ return NULL;
+}
+
+void libusbp_generic_interface_free(libusbp_generic_interface * gi)
+{
+ if (gi != NULL)
+ {
+ free(gi->device_instance_id);
+ free(gi->filename);
+ free(gi);
+ }
+}
+
+libusbp_error * libusbp_generic_interface_get_os_id(
+ const libusbp_generic_interface * gi,
+ char ** id)
+{
+ if (id == NULL)
+ {
+ return error_create("String output pointer is null.");
+ }
+
+ *id = NULL;
+
+ if (gi == NULL)
+ {
+ return error_create("Generic interface is null.");
+ }
+ return string_copy(gi->device_instance_id, id);
+}
+
+libusbp_error * libusbp_generic_interface_get_os_filename(
+ const libusbp_generic_interface * gi,
+ char ** filename)
+{
+ if (filename == NULL)
+ {
+ return error_create("String output pointer is null.");
+ }
+
+ *filename = NULL;
+
+ if (gi == NULL)
+ {
+ return error_create("Generic interface is null.");
+ }
+ return string_copy(gi->filename, filename);
+}
diff --git a/dep/libusbp/src/windows/interface_windows.c b/dep/libusbp/src/windows/interface_windows.c
new file mode 100644
index 00000000..cea24fa1
--- /dev/null
+++ b/dep/libusbp/src/windows/interface_windows.c
@@ -0,0 +1,322 @@
+// If we are compiling under MSVC, this is the file that will define GUID
+// symbols we need such as GUID_DEVINTERFACE_USB_DEVICE. This prevents
+// an unresolved symbol error at link time.
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_WARNINGS
+#include
+#endif
+
+#include
+
+static libusbp_error * get_interface_non_composite(
+ const char * device_instance_id,
+ HDEVINFO * list,
+ PSP_DEVINFO_DATA info)
+{
+ *list = INVALID_HANDLE_VALUE;
+
+ // This is a non-composite device, so we just want to find a device
+ // with the same device instance ID.
+ libusbp_error * error = NULL;
+
+ char id[MAX_DEVICE_ID_LEN + 1];
+
+ HDEVINFO new_list = INVALID_HANDLE_VALUE;
+ if (error == NULL)
+ {
+ new_list = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0,
+ DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
+ if (list == INVALID_HANDLE_VALUE)
+ {
+ error = error_create_winapi("Failed to list all USB devices.");
+ }
+ }
+
+ if (error == NULL)
+ {
+ for(DWORD i = 0; ; i++)
+ {
+ SP_DEVINFO_DATA device_info_data;
+ device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
+ bool success = SetupDiEnumDeviceInfo(new_list, i, &device_info_data);
+ if (!success)
+ {
+ if (GetLastError() == ERROR_NO_MORE_ITEMS)
+ {
+ error = error_create("Failed to find device node.");
+ }
+ else
+ {
+ error = error_create_winapi("Failed to enumerate device info.");
+ }
+ break;
+ }
+
+ success = SetupDiGetDeviceInstanceId(new_list, &device_info_data,
+ id, sizeof(id), NULL);
+ if (!success)
+ {
+ error = error_create_winapi("Failed to get device instance ID.");
+ break;
+ }
+
+ if (strcmp(id, device_instance_id) == 0)
+ {
+ // We found the device; success.
+ *list = new_list;
+ new_list = INVALID_HANDLE_VALUE;
+ *info = device_info_data;
+ break;
+ }
+ }
+ }
+
+ if (new_list != INVALID_HANDLE_VALUE)
+ {
+ SetupDiDestroyDeviceInfoList(new_list);
+ }
+
+ if (error != NULL)
+ {
+ error = error_add(error, "Failed to find non-composite device node.");
+ }
+ return error;
+}
+
+static libusbp_error * get_interface_composite(
+ const char * device_instance_id,
+ uint8_t interface_number,
+ HDEVINFO * list,
+ PSP_DEVINFO_DATA info)
+{
+ // This is a composite device, and we need to find a device
+ // whose parent has the specified device_instance_id, and
+ // whose own device instance ID has "MI_xx" where xx is the
+ // hex representation of interface_number.
+
+ *list = INVALID_HANDLE_VALUE;
+
+ // Get the DEVINST for this device.
+ CONFIGRET cr;
+ DEVINST dev_inst;
+ cr = CM_Locate_DevNode(&dev_inst, (char *)device_instance_id, CM_LOCATE_DEVNODE_NORMAL);
+ if (cr != CR_SUCCESS)
+ {
+ libusbp_error * error = error_create_cr(cr,
+ "Failed to get device node in order to find an interface.");
+
+ // NOTE: if cr == CR_NO_SUCH_DEVNODE, that means
+ // The device instance ID has a valid format, but either
+ // the device it was referring to is unplugged or it was
+ // never plugged into the computer in the first place.
+ // This error has been seen when unplugging a USB device.
+ return error;
+ }
+
+ // Get a list of all the USB-related devices.
+ HDEVINFO new_list = SetupDiGetClassDevs(NULL, "USB", NULL,
+ DIGCF_ALLCLASSES | DIGCF_PRESENT);
+ if (new_list == INVALID_HANDLE_VALUE)
+ {
+ return error_create_winapi(
+ "Failed to get list of all USB devices while finding an interface.");
+ }
+
+ // Iterate through the list until we find a device whose
+ // parent device is ours and which controls the interface
+ // specified by the caller.
+ for (DWORD i = 0; ; i++)
+ {
+ SP_DEVINFO_DATA device_info_data;
+ device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
+ bool success = SetupDiEnumDeviceInfo(new_list, i, &device_info_data);
+ if (!success)
+ {
+ libusbp_error * error;
+
+ if (GetLastError() == ERROR_NO_MORE_ITEMS)
+ {
+ // Could not find the child interface. This could be
+ // a temporary condition.
+ error = error_create("Could not find interface %d.",
+ interface_number);
+ error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
+ }
+ else
+ {
+ error = error_create_winapi(
+ "Failed to get device info while finding an interface.");
+ }
+ SetupDiDestroyDeviceInfoList(new_list);
+ return error;
+ }
+
+ DEVINST parent_dev_inst;
+ cr = CM_Get_Parent(&parent_dev_inst, device_info_data.DevInst, 0);
+ if (cr != CR_SUCCESS)
+ {
+ SetupDiDestroyDeviceInfoList(new_list);
+ return error_create_cr(cr, "Failed to get parent of an interface.");
+ }
+
+ if (parent_dev_inst != dev_inst)
+ {
+ // This device is not a child of our device.
+ continue;
+ }
+
+ // Get the device instance ID.
+ char device_id[MAX_DEVICE_ID_LEN + 1];
+ cr = CM_Get_Device_ID(device_info_data.DevInst, device_id, sizeof(device_id), 0);
+ if (cr != CR_SUCCESS)
+ {
+ libusbp_error * error = error_create_cr(cr,
+ "Failed to get device instance ID while finding an interface.");
+ SetupDiDestroyDeviceInfoList(new_list);
+ return error;
+ }
+
+ unsigned int actual_interface_number;
+ int result = sscanf(device_id, "USB\\VID_%*4x&PID_%*4x&MI_%2x\\",
+ &actual_interface_number);
+ if (result != 1 || actual_interface_number != interface_number)
+ {
+ // This is not the right interface.
+ continue;
+ }
+
+ // Found the interface.
+ *list = new_list;
+ *info = device_info_data;
+ return NULL;
+ }
+}
+
+libusbp_error * get_interface(
+ const char * device_instance_id,
+ uint8_t interface_number,
+ bool composite,
+ HDEVINFO * list,
+ PSP_DEVINFO_DATA info)
+{
+ assert(device_instance_id != NULL);
+ assert(list != NULL);
+ assert(info != NULL);
+
+ if (composite)
+ {
+ return get_interface_composite(device_instance_id, interface_number,
+ list, info);
+ }
+ else
+ {
+ return get_interface_non_composite(device_instance_id, list, info);
+ }
+}
+
+libusbp_error * get_filename_from_devinst_and_guid(
+ DEVINST devinst,
+ const GUID * guid,
+ char ** filename
+ )
+{
+ assert(guid != NULL);
+ assert(filename != NULL);
+
+ BOOL success;
+
+ // Make a list of devices that have the specified device interface GUID.
+ HDEVINFO list = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
+ if (list == INVALID_HANDLE_VALUE)
+ {
+ return error_create_winapi(
+ "Failed to create a list of devices to find a device filename.");
+ }
+
+ // Iterate through the list looking for one that matches the given DEVINST.
+ SP_DEVINFO_DATA device_info_data;
+ for(DWORD index = 0; ; index++)
+ {
+ device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
+ success = SetupDiEnumDeviceInfo(list, index, &device_info_data);
+ if (!success)
+ {
+ if (GetLastError() == ERROR_NO_MORE_ITEMS)
+ {
+ // We reached the end of the list.
+ SetupDiDestroyDeviceInfoList(list);
+ libusbp_error * error = error_create(
+ "Could not find matching device in order to get its filename.");
+
+ // This is one of the errors we see when plugging in a device,
+ // so it could indicate that the devce is just not ready and
+ // Windows is still setting it up.
+ error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
+ return error;
+ }
+
+ libusbp_error * error = error_create_winapi(
+ "Failed to enumerate list item to find matching device.");
+ SetupDiDestroyDeviceInfoList(list);
+ return error;
+ }
+
+ if (device_info_data.DevInst == devinst)
+ {
+ // We found the matching device, so break.
+ break;
+ }
+ }
+
+ // Get the DeviceInterfaceData struct.
+ SP_DEVICE_INTERFACE_DATA device_interface_data;
+ device_interface_data.cbSize = sizeof(device_interface_data);
+ success = SetupDiEnumDeviceInterfaces(list, &device_info_data, guid, 0, &device_interface_data);
+ if (!success)
+ {
+ libusbp_error * error = error_create_winapi("Failed to get device interface data.");
+ SetupDiDestroyDeviceInfoList(list);
+ return error;
+ }
+
+ // Get the DeviceInterfaceDetailData struct size.
+ DWORD size;
+ success = SetupDiGetDeviceInterfaceDetail(list, &device_interface_data, NULL, 0, &size, NULL);
+ if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ {
+ libusbp_error * error = error_create_winapi("Failed to get the size of the device interface details.");
+ SetupDiDestroyDeviceInfoList(list);
+ return error;
+ }
+
+ // Get the DeviceInterfaceDetailData struct data
+ SP_DEVICE_INTERFACE_DETAIL_DATA_A * device_interface_detail_data = malloc(size);
+ if (device_interface_detail_data == NULL)
+ {
+ return &error_no_memory;
+ }
+ device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
+ success = SetupDiGetDeviceInterfaceDetail(list, &device_interface_data,
+ device_interface_detail_data, size, NULL, NULL);
+ if (!success)
+ {
+ libusbp_error * error = error_create_winapi("Failed to get the device interface details.");
+ free(device_interface_detail_data);
+ SetupDiDestroyDeviceInfoList(list);
+ return error;
+ }
+
+ char * new_string = strdup(device_interface_detail_data->DevicePath);
+ if (new_string == NULL)
+ {
+ free(device_interface_detail_data);
+ SetupDiDestroyDeviceInfoList(list);
+ return &error_no_memory;
+ }
+
+ *filename = new_string;
+
+ free(device_interface_detail_data);
+ SetupDiDestroyDeviceInfoList(list);
+ return NULL;
+}
diff --git a/dep/libusbp/src/windows/list_windows.c b/dep/libusbp/src/windows/list_windows.c
new file mode 100644
index 00000000..6d3facbe
--- /dev/null
+++ b/dep/libusbp/src/windows/list_windows.c
@@ -0,0 +1,109 @@
+#include
+
+#if SIZE_MAX < DWORD_MAX
+#error The code in get_list_length assumes a size_t can hold any DWORD.
+#endif
+
+static void try_create_device(HDEVINFO list, PSP_DEVINFO_DATA info,
+ libusbp_device ** device)
+{
+ libusbp_error * error = create_device(list, info, device);
+ if (error != NULL)
+ {
+ assert(*device == NULL);
+
+ // Something went wrong. For example, one of the devices might have
+ // lacked a hardware ID with the proper format. To make the library
+ // more robust and usable, we ignore this error and continue.
+ #ifdef LIBUSBP_LOG
+ fprintf(stderr, "Problem creating device: %s\n",
+ libusbp_error_get_message(error));
+ #endif
+
+ libusbp_error_free(error);
+ }
+}
+
+libusbp_error * libusbp_list_connected_devices(libusbp_device *** device_list,
+ size_t * device_count)
+{
+ // Return 0 for the device count by default, just to be safe.
+ if (device_count != NULL)
+ {
+ *device_count = 0;
+ }
+
+ if (device_list == NULL)
+ {
+ return error_create("Device list output pointer is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ // Get a list of all USB devices from Windows.
+ HDEVINFO handle = INVALID_HANDLE_VALUE;
+ if (error == NULL)
+ {
+ handle = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0,
+ DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
+ if (handle == INVALID_HANDLE_VALUE)
+ {
+ error = error_create_winapi("Failed to list all USB devices.");
+ }
+ }
+
+ // Start a new device list and keep track of how many devices are in it.
+ libusbp_device ** new_list = NULL;
+ size_t count = 0;
+ if (error == NULL)
+ {
+ error = device_list_create(&new_list);
+ }
+
+ // Each iteration of this loop attempts to set up a new device.
+ DWORD index = 0;
+ while(error == NULL)
+ {
+ // See if we have reached the end of the list.
+ SP_DEVINFO_DATA info;
+ info.cbSize = sizeof(info);
+ BOOL success = SetupDiEnumDeviceInfo(handle, index, &info);
+ if (!success)
+ {
+ if (GetLastError() == ERROR_NO_MORE_ITEMS)
+ {
+ // We have reached the end of the list.
+ break;
+ }
+
+ // An unexpected error happened.
+ error = error_create_winapi("Failed to test for the end of the USB device list.");
+ break;
+ }
+
+ libusbp_device * device = NULL;
+ try_create_device(handle, &info, &device);
+ if (device != NULL)
+ {
+ error = device_list_append(&new_list, &count, device);
+ }
+ index++;
+ }
+
+ // Give the list and count to the caller.
+ if (error == NULL)
+ {
+ *device_list = new_list;
+ new_list = NULL;
+
+ if (device_count != NULL)
+ {
+ *device_count = count;
+ }
+ }
+
+ // Clean up.
+ if (handle != INVALID_HANDLE_VALUE) { SetupDiDestroyDeviceInfoList(handle); }
+ if (new_list != NULL) { free_devices_and_list(new_list); }
+ return error;
+}
diff --git a/dep/libusbp/src/windows/serial_port_windows.c b/dep/libusbp/src/windows/serial_port_windows.c
new file mode 100644
index 00000000..9dc24501
--- /dev/null
+++ b/dep/libusbp/src/windows/serial_port_windows.c
@@ -0,0 +1,207 @@
+#include
+
+struct libusbp_serial_port
+{
+ char * device_instance_id;
+ char * port_name; // e.g. "COM4"
+};
+
+libusbp_error * libusbp_serial_port_create(
+ const libusbp_device * device,
+ uint8_t interface_number,
+ bool composite,
+ libusbp_serial_port ** port)
+{
+ if (port == NULL)
+ {
+ return error_create("Serial port output pointer is null.");
+ }
+
+ *port = NULL;
+
+ if (device == NULL)
+ {
+ return error_create("Device is null.");
+ }
+
+ libusbp_error * error = NULL;
+
+ libusbp_serial_port * new_sp = NULL;
+ if (error == NULL)
+ {
+ new_sp = calloc(1, sizeof(libusbp_serial_port));
+ if (new_sp == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ // Get the device instance ID of the overall USB device.
+ char * usb_device_id = NULL;
+ if (error == NULL)
+ {
+ error = libusbp_device_get_os_id(device, &usb_device_id);
+ }
+
+ // Access the serial port interface device node with SetupAPI.
+ HDEVINFO list = INVALID_HANDLE_VALUE;
+ SP_DEVINFO_DATA device_info_data;
+ if (error == NULL)
+ {
+ error = get_interface(usb_device_id, interface_number,
+ composite, &list, &device_info_data);
+ }
+
+ // Record the device instance ID.
+ if (error == NULL)
+ {
+ error = create_id_string(list, &device_info_data, &new_sp->device_instance_id);
+ }
+
+ // Open the registry key for device-specific configuration information.
+ HANDLE keyDev = INVALID_HANDLE_VALUE;
+ if (error == NULL)
+ {
+ keyDev = SetupDiOpenDevRegKey(list, &device_info_data,
+ DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
+ if (keyDev == INVALID_HANDLE_VALUE)
+ {
+ error = error_create_winapi(
+ "Failed to get device registry key in order to find its COM port name.");
+ }
+ }
+
+ // Get the port name from the registry (e.g. "COM8").
+ char portName[128];
+ if (error == NULL)
+ {
+ DWORD size = sizeof(portName);
+ DWORD reg_type;
+ LONG reg_result = RegQueryValueEx(keyDev, "PortName",
+ NULL, ®_type, (BYTE *)portName, &size);
+ if (reg_result != ERROR_SUCCESS)
+ {
+ if (reg_result == ERROR_FILE_NOT_FOUND)
+ {
+ error = error_create("The PortName key was not found.");
+ }
+ else
+ {
+ SetLastError(reg_result);
+ error = error_create_winapi("Failed to get PortName key.");
+ }
+ }
+ else if (reg_type != REG_SZ)
+ {
+ error = error_create(
+ "Expected PortName key to be a REG_SZ (0x%x), got 0x%lx.",
+ REG_SZ, reg_type);
+ }
+ }
+
+ // Copy the port name into the serial port object.
+ if (error == NULL)
+ {
+ error = string_copy(portName, &new_sp->port_name);
+ }
+
+ // Give the new serial port to the caller.
+ if (error == NULL)
+ {
+ *port = new_sp;
+ new_sp = NULL;
+ }
+
+ // Clean up.
+ if (keyDev != INVALID_HANDLE_VALUE)
+ {
+ RegCloseKey(keyDev);
+ }
+ if (list != INVALID_HANDLE_VALUE)
+ {
+ SetupDiDestroyDeviceInfoList(list);
+ }
+ libusbp_string_free(usb_device_id);
+ libusbp_serial_port_free(new_sp);
+
+ return error;
+}
+
+void libusbp_serial_port_free(libusbp_serial_port * port)
+{
+ if (port == NULL) { return; }
+ libusbp_string_free(port->device_instance_id);
+ libusbp_string_free(port->port_name);
+ free(port);
+}
+
+libusbp_error * libusbp_serial_port_copy(const libusbp_serial_port * source,
+ libusbp_serial_port ** dest)
+{
+ if (dest == NULL)
+ {
+ return error_create("Serial port output pointer is null.");
+ }
+
+ *dest = NULL;
+
+ if (source == NULL)
+ {
+ return NULL;
+ }
+
+ assert(source->device_instance_id);
+ assert(source->port_name);
+
+ libusbp_error * error = NULL;
+
+ libusbp_serial_port * new_sp = NULL;
+ if (error == NULL)
+ {
+ new_sp = calloc(1, sizeof(libusbp_serial_port));
+ if (new_sp == NULL)
+ {
+ error = &error_no_memory;
+ }
+ }
+
+ if (error == NULL)
+ {
+ error = string_copy(source->device_instance_id, &new_sp->device_instance_id);
+ }
+
+ if (error == NULL)
+ {
+ error = string_copy(source->port_name, &new_sp->port_name);
+ }
+
+ if (error == NULL)
+ {
+ *dest = new_sp;
+ new_sp = NULL;
+ }
+
+ libusbp_serial_port_free(new_sp);
+
+ return error;
+}
+
+libusbp_error * libusbp_serial_port_get_name(
+ const libusbp_serial_port * port,
+ char ** name)
+{
+ if (name == NULL)
+ {
+ return error_create("String output pointer is null.");
+ }
+
+ *name = NULL;
+
+ if (port == NULL)
+ {
+ return error_create("Serial port is null.");
+ }
+
+ return string_copy(port->port_name, name);
+}
+
diff --git a/dep/libusbp/test/CMakeLists.txt b/dep/libusbp/test/CMakeLists.txt
new file mode 100644
index 00000000..8164eec9
--- /dev/null
+++ b/dep/libusbp/test/CMakeLists.txt
@@ -0,0 +1,41 @@
+INCLUDE (CheckIncludeFileCXX)
+
+# If catch.hpp is not present, we want to simply skip compiling the tests. This
+# allows someone to compile and install libusbp without having catch installed.
+# The header can either be installed in this directory or in a standard system
+# location.
+set (CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}")
+CHECK_INCLUDE_FILE_CXX (catch.hpp HAVE_CATCH_FRAMEWORK)
+if (NOT HAVE_CATCH_FRAMEWORK)
+ message (STATUS "The test suite will not be built.")
+ return ()
+endif ()
+
+use_cxx11 ()
+
+set(USE_TEST_DEVICE_A FALSE CACHE BOOL
+ "Run tests that require Test Device A.")
+
+set(USE_TEST_DEVICE_B FALSE CACHE BOOL
+ "Run tests that require Test Device B.")
+
+file(GLOB test_sources *.cpp)
+
+if (APPLE)
+ set (link_flags "-framework CoreFoundation ${link_flags}")
+endif ()
+
+add_executable(run_test ${test_sources})
+
+set_target_properties(run_test PROPERTIES
+ LINK_FLAGS "${link_flags}"
+)
+
+include_directories (
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+ "${CMAKE_SOURCE_DIR}/include"
+ "${CMAKE_SOURCE_DIR}/src"
+ "${CMAKE_BINARY_DIR}/src"
+)
+
+target_link_libraries(run_test usbp)
diff --git a/dep/libusbp/test/async_in_pipe_test.cpp b/dep/libusbp/test/async_in_pipe_test.cpp
new file mode 100644
index 00000000..38eb1053
--- /dev/null
+++ b/dep/libusbp/test/async_in_pipe_test.cpp
@@ -0,0 +1,749 @@
+#include
+
+// Note: A lot of these tests make specific assumptions about the timing of
+// various operations. If the tests fail intermittently, some parameters may
+// need to be adjusted.
+
+const uint8_t pipe_id = 0x82;
+
+#ifdef USE_TEST_DEVICE_A
+static void check_error_for_cancelled_transfer(const libusbp::error & error)
+{
+ if (!error)
+ {
+ // The transfer actually completed successfully.
+ return;
+ }
+
+ if (error.has_code(LIBUSBP_ERROR_CANCELLED))
+ {
+ // This is expected.
+ }
+ else
+ {
+ // Some other error happened that we didn't expect.
+ throw error;
+ }
+}
+
+static void clean_up_async_in_pipe(libusbp::async_in_pipe & pipe)
+{
+ pipe.cancel_transfers();
+ test_timeout timeout(500);
+ while(pipe.has_pending_transfers())
+ {
+ pipe.handle_events();
+ libusbp::error transfer_error;
+ while(pipe.handle_finished_transfer(NULL, NULL, &transfer_error))
+ {
+ check_error_for_cancelled_transfer(transfer_error);
+ }
+ timeout.check();
+ sleep_quick();
+ }
+}
+
+static void clean_up_async_in_pipe_and_expect_a_success(libusbp::async_in_pipe & pipe)
+{
+ pipe.cancel_transfers();
+
+ test_timeout timeout(500);
+ uint32_t success_count = 0;
+ while(pipe.has_pending_transfers())
+ {
+ pipe.handle_events();
+ libusbp::error transfer_error;
+ uint8_t buffer[64] = {0};
+ size_t transferred;
+ while(pipe.handle_finished_transfer(buffer, &transferred, &transfer_error))
+ {
+ check_error_for_cancelled_transfer(transfer_error);
+ if (!transfer_error)
+ {
+ REQUIRE(buffer[4] == 0xAB);
+ REQUIRE(transferred == 5);
+ success_count++;
+ }
+ }
+ timeout.check();
+ sleep_quick();
+ }
+ REQUIRE(success_count > 0);
+}
+#endif
+
+TEST_CASE("async_in_pipe traits")
+{
+ libusbp::async_in_pipe pipe, pipe2;
+
+ SECTION("is not copy-constructible")
+ {
+ REQUIRE(std::is_copy_constructible::value == false);
+
+ // Should not compile:
+ // libusbp::async_in_pipe pipe3(pipe);
+ }
+
+ SECTION("is not copy-assignable")
+ {
+ REQUIRE(std::is_copy_assignable::value == false);
+
+ // Should not compile:
+ // pipe2 = pipe;
+ }
+}
+
+#ifdef USE_TEST_DEVICE_A
+
+TEST_CASE("async_in_pipe basic properties")
+{
+ libusbp::device device = find_test_device_a();
+ libusbp::generic_interface gi(device, 0, true);
+ libusbp::generic_handle handle(gi);
+ libusbp::async_in_pipe pipe = handle.open_async_in_pipe(pipe_id);
+
+ SECTION("is present")
+ {
+ REQUIRE(pipe);
+ }
+
+ SECTION("is movable")
+ {
+ libusbp::async_in_pipe pipe2 = std::move(pipe);
+ REQUIRE(pipe2);
+ REQUIRE_FALSE(pipe);
+ }
+
+ SECTION("is move-assignable")
+ {
+ libusbp::async_in_pipe pipe2;
+ pipe2 = std::move(pipe);
+ REQUIRE(pipe2);
+ REQUIRE_FALSE(pipe);
+ }
+}
+
+TEST_CASE("null async_in_pipe")
+{
+ libusbp::async_in_pipe pipe;
+ std::string expected_message = "Pipe argument is null.";
+
+ SECTION("is not present")
+ {
+ CHECK_FALSE(pipe);
+ }
+
+ SECTION("cannot allocate transfers")
+ {
+ try
+ {
+ pipe.allocate_transfers(4, 5);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == expected_message);
+ }
+ }
+
+ SECTION("cannot start endless transfers")
+ {
+ try
+ {
+ pipe.start_endless_transfers();
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == expected_message);
+ }
+ }
+
+ SECTION("cannot handle events")
+ {
+ try
+ {
+ pipe.handle_events();
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == expected_message);
+ }
+ }
+
+ SECTION("cannot say if it has pending transfers")
+ {
+ // Test it this way to make sure the C++ wrapper throws an exception
+ // instead of ignoring it.
+ try
+ {
+ pipe.has_pending_transfers();
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == expected_message);
+ }
+
+ // Also test it this way so we can check the "result" output parameter
+ // in the C API.
+ bool result = true;
+ libusbp::error error(libusbp_async_in_pipe_has_pending_transfers(NULL, &result));
+ REQUIRE(error.message() == expected_message);
+ REQUIRE_FALSE(result);
+ }
+
+ SECTION("cannot handle a finished transfer")
+ {
+ uint8_t buffer[] = "hi there";
+ size_t transferred = 10;
+ libusbp::error error = get_some_error();
+ try
+ {
+ pipe.handle_finished_transfer(buffer, &transferred, &error);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == expected_message);
+ }
+ CHECK(buffer[0] == 'h');
+ CHECK(transferred == 0);
+ CHECK_FALSE(error);
+ }
+
+ SECTION("cannot cancel all transfers")
+ {
+ try
+ {
+ pipe.cancel_transfers();
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == expected_message);
+ }
+ }
+}
+
+TEST_CASE("async_in_pipe parameter validation and state checks")
+{
+ libusbp::device device = find_test_device_a();
+ libusbp::generic_interface gi(device, 0, true);
+ libusbp::generic_handle handle(gi);
+ libusbp::async_in_pipe pipe = handle.open_async_in_pipe(0x82);
+
+ SECTION("allocate_transfers")
+ {
+ SECTION("cannot be called twice on the same pipe")
+ {
+ pipe.allocate_transfers(1, 64);
+ try
+ {
+ pipe.allocate_transfers(1, 64);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Transfers were already allocated for this pipe.");
+ }
+ }
+
+ SECTION("does not allow transfer_count to be 0")
+ {
+ // Set the size to 0 also so we can test that the count is checked
+ // before the size (it's nice if the little details stay consistent
+ // over time and across platforms).
+ try
+ {
+ pipe.allocate_transfers(0, 0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Transfer count cannot be zero.");
+ }
+ }
+
+ SECTION("does not allow transfer_size to be 0")
+ {
+ try
+ {
+ pipe.allocate_transfers(64, 0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Transfer size cannot be zero.");
+ }
+ }
+
+ SECTION("rejects transfer sizes too large for the underlying APIs")
+ {
+ #ifdef _WIN32
+ // On Windows, URB buffer sizes are represented by ULONGs.
+ if (SIZE_MAX <= ULONG_MAX) { return; }
+ size_t too_large_size = (size_t)ULONG_MAX + 1;
+ #endif
+
+ #ifdef __linux__
+ // On Linux, URB buffer sizes are represented by ints.
+ if (SIZE_MAX <= INT_MAX) { return; }
+ size_t too_large_size = (size_t)INT_MAX + 1;
+ #endif
+
+ #ifdef __APPLE__
+ size_t too_large_size = (size_t)UINT32_MAX + 1;
+ #endif
+
+ try
+ {
+ pipe.allocate_transfers(1, too_large_size);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() ==
+ "Failed to allocate transfers for asynchronous IN pipe. "
+ "Transfer size is too large.");
+ }
+ }
+ }
+
+ SECTION("start_endless_transfers")
+ {
+ SECTION("complains if transfers were not allocated")
+ {
+ try
+ {
+ pipe.start_endless_transfers();
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Pipe transfers have not been allocated yet.");
+ }
+ }
+ }
+
+ SECTION("has_pending_transfers")
+ {
+ SECTION("works even if transfers were not allocated")
+ {
+ REQUIRE_FALSE(pipe.has_pending_transfers());
+ }
+
+ SECTION("complains if the output pointer is NULL")
+ {
+ libusbp::error error(libusbp_async_in_pipe_has_pending_transfers(
+ pipe.pointer_get(), NULL));
+ REQUIRE(error.message() == "Boolean output pointer is null.");
+ }
+ }
+}
+
+TEST_CASE("async_in_pipe for an interrupt endpoint")
+{
+ // If it works for an IN endpoint, it should work for a bulk endpoint too
+ // because the underlying APIs that libusbp uses allow us to treat those
+ // types of endpoints the same.
+
+ libusbp::device device = find_test_device_a();
+ libusbp::generic_interface gi(device, 0, true);
+ libusbp::generic_handle handle(gi);
+ libusbp::async_in_pipe pipe = handle.open_async_in_pipe(0x82);
+ const size_t transfer_size = 5;
+
+ test_timeout timeout(500);
+
+ SECTION("can do continuous transfers and then cancel them")
+ {
+ pipe.allocate_transfers(5, transfer_size);
+ pipe.start_endless_transfers();
+
+ size_t finish_count = 0;
+ while(finish_count < 12)
+ {
+ // Don't use REQUIRE or CATCH here because then the number of
+ // assertions printed at the end of the tests will be unpredictable.
+ if (!pipe.has_pending_transfers()) { throw "No pending transfers."; }
+
+ uint8_t buffer[transfer_size] = {0};
+ size_t transferred;
+ libusbp::error transfer_error;
+ if (pipe.handle_finished_transfer(buffer, &transferred, &transfer_error))
+ {
+ if (transfer_error) { throw transfer_error; }
+
+ REQUIRE(buffer[4] == 0xAB);
+ finish_count++;
+ }
+
+ pipe.handle_events();
+ timeout.check();
+ sleep_quick();
+ }
+
+ REQUIRE(pipe.has_pending_transfers());
+
+ clean_up_async_in_pipe(pipe);
+ }
+
+ SECTION("cancelling a transfer before it is completed")
+ {
+ // Event order tested here: submit, cancel, "reap"
+
+ // Make a lot of transfers and cancel then immediately to make sure at
+ // least one was cancelled before it was completed.
+
+ #ifdef __linux__
+
+ // On a normal Linux machine it takes about 1 ms per transfer to cancel
+ // transfers. (Maybe that is because we are using an endpoint with a 1
+ // ms polling interval.) On a VirtualBox machine running on a Windows
+ // guest, it takes about much longer: 20 ms per transfer. To work
+ // around this slowness, we only allocate 20 transfers at a time in
+ // Linux.
+
+ // Maybe later we should we provide a workaround that lets
+ // people close the generic_handle and all its pipes at the same time,
+ // thus saving their users from this painful wait, without causing
+ // memory leaks. If the generic handle fd gets closed and replaced with
+ // -1, then it would be safe to just free all the resources for the URBs
+ // without actually cancelling them.
+
+ // We would like to make the transfer count larger (500) to make this
+ // test more resilient to lag caused by the operating system, but that
+ // slows down the development process too much.
+ const size_t transfer_count = 20;
+
+ #else
+
+ const size_t transfer_count = 500;
+
+ #endif
+
+ pipe.allocate_transfers(transfer_count, transfer_size);
+ pipe.start_endless_transfers();
+
+ //printf("Cancelling %d transfers\n", (int)transfer_count);
+ test_timeout cancel_timer(10000);
+ pipe.cancel_transfers();
+ //printf("Done cancelling, took %d ms\n", cancel_timer.get_milliseconds());
+
+ size_t cancel_count = 0;
+ test_timeout timeout(10000);
+ while(pipe.has_pending_transfers())
+ {
+ pipe.handle_events();
+ libusbp::error transfer_error;
+ while(pipe.handle_finished_transfer(NULL, NULL, &transfer_error))
+ {
+ check_error_for_cancelled_transfer(transfer_error);
+
+ if (transfer_error.has_code(LIBUSBP_ERROR_CANCELLED))
+ {
+ cancel_count++;
+ }
+ }
+ timeout.check();
+ sleep_quick();
+ }
+
+ // Make sure at least one error from the cancelled transfers had the
+ // right error code.
+ //printf("Cancel count: %d\n", (int)cancel_count);
+ REQUIRE(cancel_count > 5);
+ }
+
+ SECTION("cancelling a transfer after it is completed but before the libusbp knows")
+ {
+ // Event order tested here: submit, complete, cancel, "reap"
+
+ pipe.allocate_transfers(1, transfer_size);
+ pipe.start_endless_transfers();
+ sleep_ms(30);
+ clean_up_async_in_pipe_and_expect_a_success(pipe);
+ }
+
+ SECTION("cancelling a transfer after it is completed and the event is handled")
+ {
+ // Event order tested here: submit, complete, "reap", cancel
+
+ pipe.allocate_transfers(1, transfer_size);
+ pipe.start_endless_transfers();
+ sleep_ms(30);
+ pipe.handle_events();
+ clean_up_async_in_pipe_and_expect_a_success(pipe);
+ }
+
+ SECTION("cancelling a partially completed transfer")
+ {
+ // Note: This test seems to always fail on Windows Vista and Windows 7
+ // because WinUSB reports that 0 bytes have been transferred instead of
+ // 10. It might be that older versions of WinUSB or of the USB stack
+ // didn't support returning data from a cancelled, partially completed
+ // transfer.
+
+ // Assumption: there will be two packets queued up by Test Device A in
+ // its ping-pong buffers. So when we tell it to pause the ADC for 100
+ // ms, a three-packet transfer will quickly receive those two packets
+ // and then keep waiting for more.
+
+ // Pause the ADC for 100 ms.
+ handle.control_transfer(0x40, 0xA0, 100, 0);
+
+ pipe.allocate_transfers(1, transfer_size * 3);
+ pipe.start_endless_transfers();
+ sleep_ms(20);
+
+ pipe.cancel_transfers();
+ test_timeout timeout(500);
+ while(pipe.has_pending_transfers())
+ {
+ pipe.handle_events();
+ libusbp::error transfer_error;
+ uint8_t buffer[transfer_size * 3];
+ size_t transferred;
+ while(pipe.handle_finished_transfer(buffer, &transferred, &transfer_error))
+ {
+ CHECK(transfer_error);
+ check_error_for_cancelled_transfer(transfer_error);
+
+ #if defined(VBOX_LINUX_ON_WINDOWS)
+ CHECK(transferred == 0);
+ #elif defined(__APPLE__)
+ CHECK(transferred == transfer_size);
+ CHECK(buffer[4] == 0xAB);
+ #else
+ CHECK(transferred == transfer_size * 2);
+ CHECK(buffer[4] == 0xAB);
+ CHECK(buffer[9] == 0xAB);
+ #endif
+ }
+ timeout.check();
+ sleep_quick();
+ }
+
+ // Unpause the ADC.
+ handle.control_transfer(0x40, 0xA0, 0, 0);
+ }
+
+ SECTION("cancelling transfers twice is okay")
+ {
+ pipe.allocate_transfers(1, transfer_size);
+ pipe.start_endless_transfers();
+ pipe.cancel_transfers();
+ clean_up_async_in_pipe(pipe);
+ }
+
+ SECTION("might respect the time out from set_timeout")
+ {
+ #ifdef __linux__
+ // On Linux, asynchronous requests cannot have a timeout, so this test
+ // will not pass. If we want to implement a timeout feature, we would
+ // need to make some kind of timer like libusb does, and cancel the
+ // requests when the time is up. It's probably fine for the user to
+ // just implement a timeout themselves using a separate time library.
+ return;
+ #endif
+
+ #ifdef __APPLE__
+ // On Mac OS X, interrupt endpoints cannot have a timeout, and our
+ // library simply doesn't use a timeout (we use ReadPipeAsync instead of
+ // ReadPipeAsyncTO).
+ return;
+ #endif
+
+ // Pause the ADC for 200 ms.
+ handle.control_transfer(0x40, 0xA0, 200, 0);
+
+ handle.set_timeout(pipe_id, 10);
+ pipe.allocate_transfers(5, transfer_size);
+ pipe.start_endless_transfers();
+
+ uint32_t timeout_count = 0;
+ uint32_t success_count = 0;
+ test_timeout timeout(500);
+ while(timeout_count < 8)
+ {
+ pipe.handle_events();
+ libusbp::error transfer_error;
+ while(pipe.handle_finished_transfer(NULL, NULL, &transfer_error))
+ {
+ // We expect there to be 0 to 2 successes, and the rest of the
+ // transfers will be timeouts.
+ if (transfer_error == false)
+ {
+ // Successful transfer.
+ success_count++;
+
+ if (timeout_count != 0)
+ {
+ throw "Got a success after a timeout.";
+ }
+ if (success_count > 2)
+ {
+ throw "Got too many successes.";
+ }
+ }
+ else if (transfer_error.has_code(LIBUSBP_ERROR_TIMEOUT))
+ {
+ // Timeout.
+
+ const char * expected =
+ "Asynchronous IN transfer failed. "
+ "The operation timed out. "
+ #ifdef _WIN32
+ "Windows error code 0x79."
+ #endif
+ ;
+ REQUIRE(transfer_error.message() == expected);
+ timeout_count++;
+ }
+ else
+ {
+ // Unexpected error.
+ throw transfer_error;
+ }
+ }
+ timeout.check();
+ sleep_quick();
+ }
+
+ // Clean up the pipe, while accepting that there might be more transfers
+ // finishing with a cancellation error.
+ pipe.cancel_transfers();
+ test_timeout timeout_cleanup(500);
+ while(pipe.has_pending_transfers())
+ {
+ pipe.handle_events();
+ libusbp::error transfer_error;
+ while(pipe.handle_finished_transfer(NULL, NULL, &transfer_error))
+ {
+ if (transfer_error
+ && !transfer_error.has_code(LIBUSBP_ERROR_CANCELLED)
+ && !transfer_error.has_code(LIBUSBP_ERROR_TIMEOUT))
+ {
+ throw transfer_error;
+ }
+ }
+ timeout_cleanup.check();
+ sleep_quick();
+ }
+
+ // Unpause the ADC.
+ handle.control_transfer(0x40, 0xA0, 0, 0);
+ }
+
+ SECTION("overflow")
+ {
+ #ifdef VBOX_LINUX_ON_WINDOWS
+ // This test fails and then puts the USB device into a weird state
+ // if run on Linux inside VirtualBox on a Windows host.
+ std::cerr << "Skipping asynchronous IN pipe overflow test.\n";
+ return;
+ #endif
+
+ pipe.allocate_transfers(1, transfer_size + 1);
+ pipe.start_endless_transfers();
+ sleep_ms(10);
+ pipe.handle_events();
+
+ uint8_t buffer[transfer_size] = {0};
+ size_t transferred;
+ libusbp::error transfer_error;
+ bool finished = pipe.handle_finished_transfer(buffer,
+ &transferred, &transfer_error);
+ REQUIRE(finished);
+
+ std::string expected_message;
+ size_t expected_transferred;
+ #ifdef _WIN32
+ // This request is an error in WinUSB since we are using RAW_IO. The
+ // error is detected before any data is transferred.
+ expected_transferred = 0;
+ expected_message = "Asynchronous IN transfer failed. "
+ "Incorrect function. Windows error code 0x1.";
+ #elif defined(__linux__)
+ // This request results in an error in Linux but it is only detected
+ // after some data is transferred.
+ expected_transferred = transfer_size + 1;
+ expected_message = "Asynchronous IN transfer failed. "
+ "The transfer overflowed. Error code 75.";
+ #elif defined(__APPLE__)
+ // On Mac OS X, this results in an error after some data is transferred.
+ expected_transferred = transfer_size;
+ expected_message = "Asynchronous IN transfer failed. "
+ "The transfer overflowed. Error code 0xe00002e8.";
+ #else
+ REQUIRE(0);
+ #endif
+
+ CHECK(transferred == expected_transferred);
+ CHECK(transfer_error.message() == expected_message);
+
+ // We can't just call clean_up_async_in_pipe because we might be running
+ // on a slower computer and the next transfer has actually already been
+ // queued.
+ pipe.cancel_transfers();
+ test_timeout timeout(500);
+ while(pipe.has_pending_transfers())
+ {
+ pipe.handle_events();
+ libusbp::error transfer_error;
+ while(pipe.handle_finished_transfer(NULL, NULL, &transfer_error))
+ {
+ if (!transfer_error)
+ {
+ #if defined(__linux__)
+ // Overflowing is always an error, none of the transfers
+ // should have succeeded.
+ throw "Expected to get a transfer error.";
+ #endif
+ }
+ else if (transfer_error.message() == expected_message)
+ {
+ // This is fine; the error indicates an overflow.
+ }
+ #ifdef __APPLE__
+ else if (transfer_error.has_code(LIBUSBP_ERROR_STALL))
+ {
+ // Mac OS X considers the pipe to be "stalled" after an
+ // overflow happens and we cannot actually submit any more
+ // transfers.
+ }
+ #endif
+ else if (transfer_error.has_code(LIBUSBP_ERROR_CANCELLED))
+ {
+ // This is fine; the transfer was cancelled.
+ }
+ else
+ {
+ // Unexpected error.
+ throw transfer_error;
+ }
+ }
+ timeout.check();
+ sleep_quick();
+ }
+
+ clean_up_async_in_pipe(pipe);
+ }
+
+ #ifdef __linux__
+ SECTION("does not prevent the creation of another generic_interface object")
+ {
+ // It seems that the "usbfs" driver gets attached to the device
+ // while we are doing asynchronous transfers. This tests that our
+ // code is OK with that and we can still open up other handles
+ // to the device, at least in Linux.
+
+ pipe.allocate_transfers(1, transfer_size);
+ pipe.start_endless_transfers();
+ libusbp::generic_interface gi2(device, 0, true);
+ libusbp::generic_interface gi3(device, 0, true);
+ libusbp::generic_handle handle2(gi3);
+ handle2.control_transfer(0x40, 0x90, 1, 0);
+ clean_up_async_in_pipe(pipe);
+ }
+ #endif
+}
+
+#endif
diff --git a/dep/libusbp/test/control_sync_test.cpp b/dep/libusbp/test/control_sync_test.cpp
new file mode 100644
index 00000000..31f77580
--- /dev/null
+++ b/dep/libusbp/test/control_sync_test.cpp
@@ -0,0 +1,204 @@
+#include
+
+TEST_CASE("control transfer corner cases")
+{
+ SECTION("sets transferred to zero if possible")
+ {
+ size_t transferred = 1;
+ libusbp::error error(libusbp_control_transfer(NULL,
+ 0, 0, 0, 0, NULL, 0, &transferred));
+ REQUIRE(transferred == 0);
+ }
+}
+
+#ifdef USE_TEST_DEVICE_A
+
+TEST_CASE("control transfers (synchronous) for Test Device A", "[ctstda]")
+{
+ libusbp::device device = find_test_device_a();
+ libusbp::generic_interface gi(device, 0, true);
+ libusbp::generic_handle gih(gi);
+ size_t transferred = 0xF12F;
+ gih.set_timeout(0, 300);
+
+ SECTION("request without a data stage")
+ {
+ // Turn on the LED.
+ gih.control_transfer(0x40, 0x90, 1, 0, NULL, 0, &transferred);
+ REQUIRE(transferred == 0);
+ }
+
+ SECTION("writing and reading data")
+ {
+ char buffer1[40] = "hello there";
+ size_t size = strlen(buffer1) + 1;
+
+ // Transfer data to the device.
+ gih.control_transfer(0x40, 0x92, 0, 0, buffer1, size, &transferred);
+ REQUIRE(transferred == size);
+
+ // Read the data back.
+ char buffer2[40];
+ gih.control_transfer(0xC0, 0x91, 0, 12, buffer2, 20, &transferred);
+ REQUIRE(transferred == size);
+ REQUIRE(std::string(buffer2) == buffer1);
+ }
+
+ SECTION("invalid request")
+ {
+ try
+ {
+ gih.control_transfer(0x40, 0x48, 0, 0);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ const char * expected =
+ "Control transfer failed. "
+ "The request was invalid or there was an I/O problem. "
+ #if defined(_WIN32)
+ "Windows error code 0x1f."
+ #elif defined(__linux__)
+ "Error code 32."
+ #elif defined(__APPLE__)
+ "Error code 0xe000404f."
+ #endif
+ ;
+ REQUIRE(std::string(error.what()) == expected);
+ REQUIRE(error.has_code(LIBUSBP_ERROR_STALL));
+ }
+ }
+}
+
+TEST_CASE("control transfers that time out for Test Device A")
+{
+ libusbp::device device = find_test_device_a();
+ libusbp::generic_interface gi(device, 0, true);
+ libusbp::generic_handle gih(gi);
+ size_t transferred = 0xFFFF;
+ gih.set_timeout(0, 1);
+
+ // Figure out what delay is necessary to trigger a control transfer timeout.
+ #ifdef __APPLE__
+ // Mac OS X seems to have really inaccurate timers and it does not detect a
+ // timeout unless the device delays for over roughly one second. To detect it
+ // reliably, the delay needs to be even longer, definitely slows down the tests
+ // and makes our device not compliant with the USB specification during that
+ // time, because it cannot response to SETUP packets.
+ const uint32_t required_delay = 2000;
+ #else
+ const uint32_t required_delay = 100;
+ #endif
+
+ std::string timeout_message = "Control transfer failed. "
+ "The operation timed out. "
+ #if defined(_WIN32)
+ "Windows error code 0x79."
+ #elif defined(__linux__)
+ "Error code 110."
+ #elif defined(__APPLE__)
+ "Error code 0xe0004051."
+ #endif
+ ;
+
+ SECTION("write request that times out")
+ {
+ size_t transferred = 0xFFFF;
+ char buffer[] = "hi";
+ try
+ {
+ gih.control_transfer(0x40, 0x92, required_delay, 0,
+ buffer, sizeof(buffer), &transferred);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(std::string(error.what()) == timeout_message);
+ REQUIRE(error.has_code(LIBUSBP_ERROR_TIMEOUT));
+ REQUIRE(transferred == 0);
+ }
+ }
+
+ SECTION("read request that times out")
+ {
+ size_t transferred = 0xFFFF;
+ char buffer[3];
+ try
+ {
+ gih.control_transfer(0xC0, 0x91, required_delay, 0,
+ buffer, sizeof(buffer), &transferred);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(std::string(error.what()) == timeout_message);
+ REQUIRE(error.has_code(LIBUSBP_ERROR_TIMEOUT));
+ REQUIRE(transferred == 0);
+ }
+ }
+
+ #ifdef _WIN32
+ SECTION("write request with no data stage that times out (no error raised)")
+ {
+ // For some reason, WinUSB does not seem to report timeouts for a
+ // control transfer with no data stage that times out, which is bad. I
+ // am not sure why, because it used to work.
+ gih.control_transfer(0x40, 0x92, required_delay, 0, NULL, 0, &transferred);
+ }
+ #else
+
+ SECTION("write request with no data stage that times out (error raised)")
+ {
+ try
+ {
+ gih.control_transfer(0x40, 0x92, required_delay, 0, NULL, 0, &transferred);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(std::string(error.what()) == timeout_message);
+ REQUIRE(error.has_code(LIBUSBP_ERROR_TIMEOUT));
+ REQUIRE(transferred == 0);
+ }
+ }
+ #endif
+}
+
+#endif
+
+
+#ifdef USE_TEST_DEVICE_B
+
+TEST_CASE("control transfers (synchronous) for Test Device B", "[ctstda]")
+{
+ libusbp::device device = find_test_device_b();
+ libusbp::generic_interface gi(device, 0, false);
+ libusbp::generic_handle gih(gi);
+ size_t transferred;
+ gih.set_timeout(0, 300);
+
+ SECTION("request without a data stage")
+ {
+ // Turn on the LED.
+ gih.control_transfer(0x40, 0x90, 1, 0, NULL, 0, &transferred);
+ REQUIRE(transferred == 0);
+ }
+
+ SECTION("writing and reading data")
+ {
+ char buffer1[40] = "hello there";
+ size_t size = strlen(buffer1) + 1;
+
+ // Transfer data to the device.
+ gih.control_transfer(0x40, 0x92, 0, 0, buffer1, size, &transferred);
+ REQUIRE(transferred == size);
+
+ // Read the data back.
+ char buffer2[40];
+ gih.control_transfer(0xC0, 0x91, 0, 12, buffer2, 20, &transferred);
+ REQUIRE(transferred == size);
+ REQUIRE(std::string(buffer2) == buffer1);
+ }
+}
+
+#endif
diff --git a/dep/libusbp/test/device_test.cpp b/dep/libusbp/test/device_test.cpp
new file mode 100644
index 00000000..bd1810b3
--- /dev/null
+++ b/dep/libusbp/test/device_test.cpp
@@ -0,0 +1,251 @@
+#include
+
+static void check_null_device_error(const libusbp::error & error)
+{
+ REQUIRE(error.message() == "Device is null.");
+}
+
+TEST_CASE("null device")
+{
+ libusbp::device device;
+
+ SECTION("is not present")
+ {
+ REQUIRE_FALSE(device);
+ }
+
+ SECTION("can be copied")
+ {
+ libusbp::device device2 = device;
+ REQUIRE_FALSE(device2);
+ }
+
+ SECTION("cannot return a vendor ID")
+ {
+ try
+ {
+ device.get_vendor_id();
+ }
+ catch(const libusbp::error & error)
+ {
+ check_null_device_error(error);
+ }
+ }
+
+ SECTION("cannot return a product ID")
+ {
+ try
+ {
+ device.get_product_id();
+ }
+ catch(const libusbp::error & error)
+ {
+ check_null_device_error(error);
+ }
+ }
+
+ SECTION("cannot return a revision")
+ {
+ try
+ {
+ device.get_revision();
+ }
+ catch(const libusbp::error & error)
+ {
+ check_null_device_error(error);
+ }
+ }
+
+ SECTION("cannot return a serial number")
+ {
+ try
+ {
+ device.get_serial_number();
+ }
+ catch(const libusbp::error & error)
+ {
+ check_null_device_error(error);
+ }
+ }
+
+ SECTION("cannot return an OS id")
+ {
+ try
+ {
+ device.get_os_id();
+ }
+ catch(const libusbp::error & error)
+ {
+ check_null_device_error(error);
+ }
+ }
+}
+
+TEST_CASE("device parameter checks and corner cases")
+{
+ SECTION("libusbp_device_copy complains about a null output pointer")
+ {
+ libusbp::error error(libusbp_device_copy(NULL, NULL));
+ REQUIRE(error.message() == "Device output pointer is null.");
+ }
+
+ SECTION("libusbp_device_copy sets the output pointer to 0 by default")
+ {
+ void * p = &p;
+ libusbp::error error(libusbp_device_copy(NULL, (libusbp_device**)&p));
+ REQUIRE((p == NULL));
+ }
+
+ SECTION("libusbp_device_get_vendor_id complains about a null output pointer")
+ {
+ libusbp::error error(libusbp_device_get_vendor_id(NULL, NULL));
+ REQUIRE(error.message() == "Vendor ID output pointer is null.");
+ }
+
+ SECTION("libusbp_device_get_vendor_id sets the output to 0 by default")
+ {
+ uint16_t x = 1;
+ libusbp::error error(libusbp_device_get_vendor_id(NULL, &x));
+ REQUIRE(x == 0);
+ }
+
+ SECTION("libusbp_device_get_product_id complains about a null output pointer")
+ {
+ libusbp::error error(libusbp_device_get_product_id(NULL, NULL));
+ REQUIRE(error.message() == "Product ID output pointer is null.");
+ }
+
+ SECTION("libusbp_device_get_product_id sets the output to 0 by default")
+ {
+ uint16_t x = 1;
+ libusbp::error error(libusbp_device_get_product_id(NULL, &x));
+ REQUIRE(x == 0);
+ }
+
+ SECTION("libusbp_device_get_revision complains about a null output pointer")
+ {
+ libusbp::error error(libusbp_device_get_revision(NULL, NULL));
+ REQUIRE(error.message() == "Device revision output pointer is null.");
+ }
+
+ SECTION("libusbp_device_get_revision sets the output to 0 by default")
+ {
+ uint16_t x = 1;
+ libusbp::error error(libusbp_device_get_revision(NULL, &x));
+ REQUIRE(x == 0);
+ }
+
+ SECTION("libusbp_device_get_serial_number complains about a null output pointer")
+ {
+ libusbp::error error(libusbp_device_get_serial_number(NULL, NULL));
+ REQUIRE(error.message() == "Serial number output pointer is null.");
+ }
+
+ SECTION("libusbp_device_get_serial_number sets the output to NULL by default")
+ {
+ void * p = &p;
+ libusbp::error error(libusbp_device_get_serial_number(NULL, (char **)&p));
+ REQUIRE((p == NULL));
+ }
+
+ SECTION("libusbp_device_get_os_id complains about a null output pointer")
+ {
+ libusbp::error error(libusbp_device_get_os_id(NULL, NULL));
+ REQUIRE(error.message() == "Device OS ID output pointer is null.");
+ }
+
+ SECTION("libusbp_device_get_os_id sets the output to NULL by default")
+ {
+ void * p = &p;
+ libusbp::error error(libusbp_device_get_os_id(NULL, (char **)&p));
+ REQUIRE((p == NULL));
+ }
+}
+
+TEST_CASE("basic checks on all devices", "[device_basic]")
+{
+ const bool print_devices = false;
+
+ std::vector list = libusbp::list_connected_devices();
+ for (auto it = list.begin(); it != list.end(); ++it)
+ {
+ libusbp::device device = *it;
+
+ uint16_t vendor_id = device.get_vendor_id();
+ uint16_t product_id = device.get_product_id();
+ uint16_t revision = device.get_revision();
+
+ std::string serial;
+ try
+ {
+ serial = device.get_serial_number();
+ }
+ catch(const libusbp::error & error)
+ {
+ if (!error.has_code(LIBUSBP_ERROR_NO_SERIAL_NUMBER)) { throw; }
+ }
+
+ std::string id = device.get_os_id();
+ CHECK_FALSE(id.empty());
+
+ if (print_devices)
+ {
+ printf("Device: %04x:%04x:%04x %-32s %s\n",
+ vendor_id, product_id, revision, serial.c_str(), id.c_str());
+ }
+ }
+}
+
+#ifdef USE_TEST_DEVICE_A
+TEST_CASE("Test Device A", "[tda]")
+{
+ libusbp::device device = find_test_device_a();
+
+ SECTION("present")
+ {
+ REQUIRE(device);
+ }
+
+ SECTION("revision code")
+ {
+ // If this test fails, you should probably update
+ // your Test Device A with the latest firmware.
+ REQUIRE(device.get_revision() == 0x0007);
+ }
+
+ SECTION("device instance id")
+ {
+ std::string id = device.get_os_id();
+ REQUIRE_FALSE(id.empty());
+ #ifdef _WIN32
+ REQUIRE(id.find("USB") == 0);
+ #endif
+ }
+
+ SECTION("serial number")
+ {
+ std::string serial_number = device.get_serial_number();
+ CHECK(serial_number.size() == 11);
+ CHECK(serial_number[2] == '-');
+ }
+}
+#endif
+
+#ifdef USE_TEST_DEVICE_B
+TEST_CASE("Test Device B", "[tdb]")
+{
+ libusbp::device device = find_test_device_b();
+
+ SECTION("present")
+ {
+ REQUIRE(device);
+ }
+
+ SECTION("revision code")
+ {
+ // If this test fails, you should probably update
+ // your Test Device B with the latest firmware.
+ REQUIRE(device.get_revision() == 0x0007);
+ }
+}
+#endif
diff --git a/dep/libusbp/test/drivers/pololu.cat b/dep/libusbp/test/drivers/pololu.cat
new file mode 100644
index 00000000..ec13f020
Binary files /dev/null and b/dep/libusbp/test/drivers/pololu.cat differ
diff --git a/dep/libusbp/test/drivers/usb_test_a_native.inf b/dep/libusbp/test/drivers/usb_test_a_native.inf
new file mode 100644
index 00000000..519d5a2b
--- /dev/null
+++ b/dep/libusbp/test/drivers/usb_test_a_native.inf
@@ -0,0 +1,56 @@
+; Copyright (C) 2015 Pololu Corporation
+
+; This driver file is not needed on Windows 8.1 and later because each device
+; implements Microsoft OS 2.0 Descriptors.
+
+[Strings]
+DriverPackageDisplayName="USB Test Device A Native USB Interface Driver"
+ManufacturerName="Pololu Corporation"
+ClassName="Universal Serial Bus devices"
+DeviceInterfaceGUID="{99c4bbb0-e925-4397-afee-981cd0702163}"
+pDA01="USB Test Device A Interface 0"
+
+[DefaultInstall]
+CopyINF=usb_test_a_native.inf
+
+[Version]
+DriverVer=12/23/2015,2.0.5
+Signature=$Windows NT$
+Class=USBDevice
+ClassGuid={88BAE032-5A81-49F0-BC3D-A4FF138216D6}
+Provider=%ManufacturerName%
+CatalogFile=pololu.cat
+DriverPackageDisplayName=%DriverPackageDisplayName%
+
+[Manufacturer]
+%ManufacturerName%=Models,NTamd64
+
+[ClassInstall32]
+AddReg=ClassInstall_AddReg
+
+[ClassInstall_AddReg]
+HKR,,,0,%ClassName%
+HKR,,IconPath,%REG_MULTI_SZ%,"%systemroot%\system32\setupapi.dll,-20"
+HKR,,NoInstallClass,,1
+HKR,,BootCritical,,0
+HKR,,Configurable,,1
+
+[Models]
+%pDA01%=USB_Install, USB\VID_1FFB&PID_DA01&MI_00
+
+[Models.NTamd64]
+%pDA01%=USB_Install, USB\VID_1FFB&PID_DA01&MI_00
+
+[USB_Install]
+Include = Winusb.inf
+Needs = WINUSB.NT
+
+[USB_Install.Services]
+Include = Winusb.inf
+Needs = WINUSB.NT.Services
+
+[USB_Install.HW]
+AddReg = Dev_AddReg
+
+[Dev_AddReg]
+HKR,,DeviceInterfaceGUIDs,0x00010000,%DeviceInterfaceGUID%
diff --git a/dep/libusbp/test/drivers/usb_test_a_serial.inf b/dep/libusbp/test/drivers/usb_test_a_serial.inf
new file mode 100644
index 00000000..78708d1d
--- /dev/null
+++ b/dep/libusbp/test/drivers/usb_test_a_serial.inf
@@ -0,0 +1,54 @@
+; Copyright 2015 Pololu Corporation
+
+[Strings]
+DriverPackageDisplayName="USB Test Device A Serial Port Driver"
+ManufacturerName="Pololu Corporation"
+ServiceName="USB RS-232 Emulation Driver"
+pDA01.Port.Name="USB Test Device A Port"
+
+[DefaultInstall]
+CopyINF=usb_test_a_serial.inf
+
+[Version]
+Class=Ports
+ClassGuid={4D36E978-E325-11CE-BFC1-08002BE10318}
+Signature="$Windows NT$"
+Provider=%ManufacturerName%
+CatalogFile=pololu.cat
+DriverVer=07/09/2015,1.0.1.0
+DriverPackageDisplayName=%DriverPackageDisplayName%
+
+[Manufacturer]
+%ManufacturerName%=DeviceList, NTamd64
+
+[DestinationDirs]
+FakeModemCopyFileSection=12
+DefaultDestDir=12
+
+[DeviceList]
+%pDA01.Port.Name%=DriverInstall, USB\VID_1FFB&PID_DA01&MI_02
+
+[DeviceList.NTamd64]
+%pDA01.Port.Name%=DriverInstall, USB\VID_1FFB&PID_DA01&MI_02
+
+[DriverInstall]
+include=mdmcpq.inf,usb.inf
+CopyFiles = FakeModemCopyFileSection
+AddReg=DriverAddReg
+
+[DriverAddReg]
+HKR,,DevLoader,,*ntkern
+HKR,,NTMPDriver,,usbser.sys
+HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider"
+
+[DriverInstall.Services]
+include=mdmcpq.inf
+AddService=usbser, 0x00000002, DriverService
+
+[DriverService]
+DisplayName=%ServiceName%
+ServiceType=1
+StartType=3
+ErrorControl=1
+ServiceBinary=%12%\usbser.sys
+LoadOrderGroup=Base
diff --git a/dep/libusbp/test/drivers/usb_test_b_native.inf b/dep/libusbp/test/drivers/usb_test_b_native.inf
new file mode 100644
index 00000000..9c022d54
--- /dev/null
+++ b/dep/libusbp/test/drivers/usb_test_b_native.inf
@@ -0,0 +1,56 @@
+; Copyright (C) 2015 Pololu Corporation
+
+; This driver file is not needed on Windows 8.1 and later because each device
+; implements Microsoft OS 2.0 Descriptors.
+
+[Strings]
+DriverPackageDisplayName="USB Test Device B Driver"
+ManufacturerName="Pololu Corporation"
+ClassName="Universal Serial Bus devices"
+DeviceInterfaceGUID="{99c4bbb0-e925-4397-afee-981cd0702163}"
+pDA02="USB Test Device B"
+
+[DefaultInstall]
+CopyINF=usb_test_b_native.inf
+
+[Version]
+DriverVer=12/23/2015,1.0.1
+Signature=$Windows NT$
+Class=USBDevice
+ClassGuid={88BAE032-5A81-49F0-BC3D-A4FF138216D6}
+Provider=%ManufacturerName%
+CatalogFile=pololu.cat
+DriverPackageDisplayName=%DriverPackageDisplayName%
+
+[Manufacturer]
+%ManufacturerName%=Models,NTamd64
+
+[ClassInstall32]
+AddReg=ClassInstall_AddReg
+
+[ClassInstall_AddReg]
+HKR,,,0,%ClassName%
+HKR,,IconPath,%REG_MULTI_SZ%,"%systemroot%\system32\setupapi.dll,-20"
+HKR,,NoInstallClass,,1
+HKR,,BootCritical,,0
+HKR,,Configurable,,1
+
+[Models]
+%pDA02%=USB_Install, USB\VID_1FFB&PID_DA02
+
+[Models.NTamd64]
+%pDA02%=USB_Install, USB\VID_1FFB&PID_DA02
+
+[USB_Install]
+Include = Winusb.inf
+Needs = WINUSB.NT
+
+[USB_Install.Services]
+Include = Winusb.inf
+Needs = WINUSB.NT.Services
+
+[USB_Install.HW]
+AddReg = Dev_AddReg
+
+[Dev_AddReg]
+HKR,,DeviceInterfaceGUIDs,0x00010000,%DeviceInterfaceGUID%
diff --git a/dep/libusbp/test/error_message_test.cpp b/dep/libusbp/test/error_message_test.cpp
new file mode 100644
index 00000000..01c5d1da
--- /dev/null
+++ b/dep/libusbp/test/error_message_test.cpp
@@ -0,0 +1,456 @@
+/* This file tests the conversion of errors from libusbp's underlying APIs to
+ * libusbp_error objects. It also documents and justifies the explicit mappings
+ * that are in the error handling code. */
+
+#include
+
+#ifndef NDEBUG
+
+// Here is a list of the messages we want to use consistently across platforms
+// to describe certain types of errors:
+#define STR_TIMEOUT "The operation timed out."
+#define STR_CANCELLED "The operation was cancelled."
+#define STR_GENERAL_FAILURE "The request was invalid or there was an I/O problem."
+#define STR_REMOVED "The device was removed."
+#define STR_OVERFLOW "The transfer overflowed."
+
+#ifdef _WIN32
+
+TEST_CASE("error_create_winapi", "[error_create_winapi]")
+{
+ libusbp::error error;
+
+ SECTION("includes the error code from GetLastError and its message")
+ {
+ SetLastError(0x1F4);
+ error.pointer_reset(error_create_winapi("Something failed."));
+ REQUIRE(error.message() == "Something failed. "
+ "User profile cannot be loaded. "
+ "Windows error code 0x1f4.");
+ }
+
+ SECTION("still works if Windows does not have a message")
+ {
+ SetLastError(0x1F431892);
+ error.pointer_reset(error_create_winapi("Something failed."));
+ REQUIRE(error.message() == "Something failed. "
+ "Windows error code 0x1f431892.");
+ }
+
+ SECTION("access denied errors")
+ {
+ // ERROR_ACCESS_DENIED can happen when running CreateFile to open a
+ // handle to a WinUSB device node that is already being used by another
+ // application. This is one of the few cases where a libusbp error
+ // message actually contains a troubleshooting step for the user to try.
+ // It is included in the library because it is a universally useful piece
+ // of advice for any WinUSB device.
+ //
+ // This feature is part of error_create_winapi, which means we are
+ // assuming that we will only ERROR_ACCESS_DENIED as a result of trying
+ // to open a device that is being used. If we expand the library to do
+ // other things that might have their access denied, this feature should
+ // maybe move to a different function.
+ SetLastError(ERROR_ACCESS_DENIED);
+ error.pointer_reset(error_create_winapi("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
+ CHECK(error.message() ==
+ "Hi. "
+ "Access is denied. "
+ "Try closing all other programs that are using the device. "
+ "Windows error code 0x5.");
+ }
+
+ SECTION("out of memory errors")
+ {
+ // We haven't specifically seen these errors happen before, but it seems
+ // like either of them might be used to indicate that the system is out
+ // memory.
+ SetLastError(ERROR_OUTOFMEMORY);
+ error.pointer_reset(error_create_winapi("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_MEMORY));
+
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ error.pointer_reset(error_create_winapi("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_MEMORY));
+ }
+
+ SECTION("general failure")
+ {
+ // We have seen ERROR_GEN_FAILURE occur in multiple different
+ // situations.
+ //
+ // It could mean that the device was disconnected from the computer
+ // in the middle of or during a synchronous USB transfer.
+ //
+ // It could mean that the host just sent a control transfer
+ // request that the device does not support, and the device has returned
+ // a STALL packet, which is a perfectly valid way for the device to
+ // behave.
+ //
+ // It probably also applies to STALL packets during IN and OUT
+ // transfers.
+ //
+ // The default message is "A device attached to the system is not
+ // functioning." This places too much blame on the USB device, so we
+ // definitely want libusbp to use STR_GENERAL_FAILURE instead.
+ SetLastError(ERROR_GEN_FAILURE);
+ error.pointer_reset(error_create_winapi("Hey."));
+ CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
+ CHECK(error.has_code(LIBUSBP_ERROR_STALL));
+ CHECK(error.message() == "Hey. "
+ STR_GENERAL_FAILURE
+ " Windows error code 0x1f.");
+ }
+
+ SECTION("timeout error")
+ {
+ // The have seen WinUSB return ERROR_SEM_TIMEOUT for both synchronous
+ // and asynchronous operations that time out.
+ SetLastError(ERROR_SEM_TIMEOUT);
+ error.pointer_reset(error_create_winapi("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_TIMEOUT));
+ CHECK(error.message() == "Hi. " STR_TIMEOUT " Windows error code 0x79.");
+ }
+
+ SECTION("cancelled error")
+ {
+ // GetOverlappedResult returns this if the asynchronous operation was
+ // cancelled.
+ SetLastError(ERROR_OPERATION_ABORTED);
+ error.pointer_reset(error_create_winapi("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_CANCELLED));
+ CHECK(error.message() == "Hi. " STR_CANCELLED " Windows error code 0x3e3.");
+ }
+}
+
+TEST_CASE("error_create_overlapped")
+{
+ libusbp::error error;
+
+ SECTION("usually just calls error_create_winapi")
+ {
+ SetLastError(ERROR_SEM_TIMEOUT);
+ error.pointer_reset(error_create_overlapped("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_TIMEOUT));
+ }
+
+ SECTION("device disconnect error")
+ {
+ // WinUsb_GetOverlappedResult (and presumably also GetOverlappedResult)
+ // returns this error code when you are checking the status of an
+ // asynchronous operation for a device that has been disconnected from
+ // the system. This is inconsistent with the synchronous operations,
+ // which return ERROR_GEN_FAILURE, but it makes sense because
+ // GetOverlappedResult is a more general thing that applies to any type
+ // of file that can have asynchronous operations performed on it.
+ //
+ // It might be OK to put this feature into error_create_winapi, but in
+ // general, ERROR_FILE_NOT_FOUND does not mean a device was
+ // disconnected, so we have put this behavior in a special function
+ // named error_create_overlapped (instead of error_create_winapi).
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ error.pointer_reset(error_create_overlapped("Hey."));
+ CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
+ CHECK(error.message() == "Hey. "
+ "The device was disconnected. "
+ "Windows error code 0x2.");
+ }
+}
+
+TEST_CASE("error_create_cr", "[error_create_cr]")
+{
+ SECTION("returns the right message")
+ {
+ libusbp::error error(error_create_cr(CR_NO_SUCH_DEVNODE, "Hi."));
+ REQUIRE(error.message() == "Hi. CONFIGRET error code 0xd.");
+ }
+}
+
+#endif
+
+
+#ifdef __linux__
+
+TEST_CASE("error_create_errno", "[error_create_errno]")
+{
+ libusbp::error error;
+
+ SECTION("returns the right message")
+ {
+ errno = ENOENT;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK(error.message() ==
+ "Hi. No such file or directory. Error code 2.");
+ }
+
+ SECTION("still works when no message is available")
+ {
+ errno = -122344;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK(error.message() == "Hi. Error code -122344.");
+ }
+
+ SECTION("access denied error")
+ {
+ // EACCES is the error we see when calling open() on a USB device file
+ // that we don't have read-write permissions for.
+ errno = EACCES;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
+
+ // One reason we might not have permissions is that the udev rules might
+ // not have been applied yet, so usbfd_open adds the
+ // LIBUSBP_ERROR_NOT_READY code to EACCES errors. (That is NOT tested
+ // here though.)
+ CHECK_FALSE(error.has_code(LIBUSBP_ERROR_NOT_READY));
+ }
+
+ SECTION("EPERM error")
+ {
+ // EPERM is described in errno-base.h as "operation not permitted". The
+ // file error-codes.txt says "submission failed because urb->reject was
+ // set". libusb does not do anything special with this error, and I am
+ // not sure when we would receive it, so for now let's NOT map it to the
+ // LIBUSBP_ERROR_ACCESS_DENIED code.
+ errno = EPERM;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK_FALSE(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
+ }
+
+ SECTION("memory errors")
+ {
+ // This is described by error-codes.txt as "no memory for allocation of
+ // internal structures", and it will probably be used by other non-USB
+ // system calls as well.
+ errno = ENOMEM;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_MEMORY));
+ }
+
+ SECTION("general failure")
+ {
+ // error-codes.txt says that EPIPE (broken pipe) can either mean
+ // "endpoint stalled", a device disconnect, or the pipe type specified
+ // in the URB doesn't match the endpoint's actual type.
+ //
+ // It's interesting that both Windows and Linux return just a single
+ // error code which could either mean a STALL packet or a device
+ // disconnect. Maybe it's hard to tell the difference between those two
+ // cases because of the design of the host controller interface.
+ errno = EPIPE;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_STALL));
+ CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
+ CHECK(error.message() == "Hi. " STR_GENERAL_FAILURE " Error code 32.");
+ }
+
+ SECTION("device disconnects")
+ {
+ // error-codes.txt documents ENODEV (no such device) to mean "specified
+ // USB-device or bus doesn't exist" or "device was removed". In
+ // devio.c, we can see that most ioctls will return ENODEV if the device
+ // is not connected. Also read() will return ENODEV if the device is
+ // not connected, which might be a handy way to check if the device is
+ // connected.
+ errno = ENODEV;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
+ CHECK(error.message() == "Hi. " STR_REMOVED " Error code 19.");
+
+ // error-codes.txt says ESHUTDOWN (cannot send after transport endpoint
+ // shutdown) means "The device or host controller has been disabled due
+ // to some problem that could not be worked around, such as a physical
+ // disconnect." libusb has four places where it handles ESHUTDOWN, and
+ // all four of them say that the device was removed. ESHUTDOWN is the
+ // error we typically see from a synchronous operation when the device
+ // is removed in the middle of the operation.
+ errno = ESHUTDOWN;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
+ CHECK(error.message() == "Hi. " STR_REMOVED " Error code 108.");
+
+ // error-codes.txt says EPROTO (protocol error) and ETIME (timer
+ // expired) (and EPIPE and EILSEQ) are codes that different kinds of
+ // host controller use to indicate a transfer has failed because of
+ // device disconnect.
+
+ errno = EPROTO;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
+
+ errno = ETIME;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
+ }
+
+ SECTION("timeout errors")
+ {
+ // error-codes.txt says that ETIMEDOUT indicates a timeout in a
+ // synchronous USB message.
+ errno = ETIMEDOUT;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_TIMEOUT));
+ CHECK(error.message() == "Hi. " STR_TIMEOUT " Error code 110.");
+ }
+
+ SECTION("overflow errors")
+ {
+ // error-codes.txt says that EOVERFLOW (Value too large for defined data
+ // type) means the amount of data reutnred by the endpoint was greater
+ // than either the max packet size or the remaining buffer size. We
+ // don't have a libusbp error code for that, but we want to fix the
+ // misleading message from Linux.
+ errno = EOVERFLOW;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK(error.message() == "Hi. " STR_OVERFLOW " Error code 75.");
+ }
+
+ SECTION("EILSEQ")
+ {
+ // error-codes.txt says that EILSEQ is one of "several codes that
+ // different kinds of host controller use to indicate a transfer has
+ // failed because of device disconnect".
+ //
+ // We have also seen EILSEQ happen sometimes in VirtualBox when
+ // cancelling an URB, or when removing a device.
+ //
+ // The default message for EILSEQ is "Invalid or incomplete multibyte or
+ // wide character." which is atrocious because it makes me think there
+ // is some bug in the software, instead of this being a USB hardware
+ // thing.
+ errno = EILSEQ;
+ error.pointer_reset(error_create_errno("Hi."));
+ CHECK(error.has_code(LIBUSBP_ERROR_CANCELLED));
+ CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
+ CHECK(error.message() == "Hi. Illegal byte sequence: "
+ "the device may have been disconnected or "
+ "the request may have been cancelled. "
+ "Error code 84.");
+ }
+}
+
+TEST_CASE("error_from_urb_status")
+{
+ struct usbdevfs_urb urb;
+ memset(&urb, 0, sizeof(urb));
+ libusbp::error error;
+
+ SECTION("gives a good message for cancellation (ENOENT)")
+ {
+ // error-codes.txt says the ENOENT in an URB status means that the URB
+ // was synchronously unlinked with usb_unlink_urb. This is the error we
+ // typically see for a cancelled asynchronous request (which actually
+ // uses usb_kill_urb, which is similar but probably slower).
+ urb.status = -ENOENT;
+ error.pointer_reset(error_from_urb_status(&urb));
+ REQUIRE(error.message() == STR_CANCELLED " Error code 2.");
+ }
+
+ SECTION("does report an error for EREMOTEIO")
+ {
+ // error-codes.txt says that EREMOTEIO means that the data read from the
+ // endpoint did not fill the specified buffer, and URB_SHORT_NOT_OK was
+ // set in urb->transfer_flags. We don't indend to set that flag, and if
+ // we did set that flag, then we should probably interpret a short
+ // transfer as an error.
+ //
+ // libusb does NOT treat EREMOTEIO as an error but that is probably
+ // because libusb could actually submit multiple URBs per transfer and
+ // some of them might not be used.
+ urb.status = -EREMOTEIO;
+ error.pointer_reset(error_from_urb_status(&urb));
+ REQUIRE(error.message() == "Remote I/O error. Error code 121.");
+ }
+}
+
+TEST_CASE("error_create_udev", "[error_create_errno]")
+{
+ SECTION("returns the right message")
+ {
+ libusbp::error error(error_create_udev(123, "Hi."));
+ REQUIRE(error.message() == "Hi. Error from libudev: 123.");
+ }
+}
+#endif
+
+#ifdef __APPLE__
+
+TEST_CASE("error_create_mach")
+{
+ SECTION("returns the right message")
+ {
+ libusbp::error error(error_create_mach(1, "Hi."));
+ REQUIRE(error.message() == "Hi. (os/kern) invalid address. Error code 0x1.");
+ }
+
+ SECTION("stall error")
+ {
+ // We encounter this error if a control transfer ends with a STALL
+ // packet. This is tested for in control_sync_test.cpp.
+ kern_return_t kr = kIOUSBPipeStalled;
+ libusbp::error error(error_create_mach(kr, "Hey."));
+ REQUIRE(error.message() == "Hey. " STR_GENERAL_FAILURE " Error code 0xe000404f.");
+ REQUIRE(error.has_code(LIBUSBP_ERROR_STALL));
+ }
+
+ SECTION("timeout")
+ {
+ // We encounter this error if a control transfer times out.
+ // This is tested for in control_sync_test.cpp.
+ kern_return_t kr = kIOUSBTransactionTimeout;
+ libusbp::error error(error_create_mach(kr, "Hey."));
+ REQUIRE(error.message() == "Hey. " STR_TIMEOUT " Error code 0xe0004051.");
+ REQUIRE(error.has_code(LIBUSBP_ERROR_TIMEOUT));
+ }
+
+ SECTION("access denied")
+ {
+ // We encounter this error if an IOUSBInterface is already open for
+ // exclusive access when we try to open it for exclusive access.
+ // This is tested for in generic_handle_test.cpp.
+ kern_return_t kr = kIOReturnExclusiveAccess;
+ libusbp::error error(error_create_mach(kr, "Hey."));
+ REQUIRE(error.message() == "Hey. Access is denied. "
+ "Try closing all other programs that are using the device. "
+ "Error code 0xe00002c5.");
+ REQUIRE(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
+ }
+
+ SECTION("overflow")
+ {
+ // This is an error we see when we read from a pipe using an
+ // IOUSBInterface and the device returns more data than can fit in our
+ // buffer.
+ kern_return_t kr = kIOReturnOverrun;
+ libusbp::error error(error_create_mach(kr, "Hi."));
+ REQUIRE(error.message() == "Hi. " STR_OVERFLOW " Error code 0xe00002e8.");
+ }
+
+ SECTION("cancellation")
+ {
+ // This is an error we see when we call ReadPipeAsync and
+ // then cancel it with AbortPipe().
+ kern_return_t kr = kIOReturnAborted;
+ libusbp::error error(error_create_mach(kr, "Hi."));
+ REQUIRE(error.message() == "Hi. " STR_CANCELLED " Error code 0xe00002eb.");
+ REQUIRE(error.has_code(LIBUSBP_ERROR_CANCELLED));
+ }
+}
+
+#endif
+
+#if defined(_WIN32) || defined(__APPLE__)
+
+TEST_CASE("error_create_hr", "[error_create_hr]")
+{
+ SECTION("returns the right message")
+ {
+ libusbp::error error(error_create_hr(0x80070057, "Hi."));
+ REQUIRE(error.message() == "Hi. HRESULT error code 0x80070057.");
+ }
+}
+
+#endif
+
+#endif // !NDEBUG
diff --git a/dep/libusbp/test/error_test.cpp b/dep/libusbp/test/error_test.cpp
new file mode 100644
index 00000000..ff1d2077
--- /dev/null
+++ b/dep/libusbp/test/error_test.cpp
@@ -0,0 +1,166 @@
+/* Tests the generic code for dealing with error objects.
+ * These tests don't really have anything to do with USB. */
+
+#include
+
+#ifndef NDEBUG
+
+TEST_CASE("error class basic properties")
+{
+ SECTION("can be caught as a const std::exception &")
+ {
+ try
+ {
+ throw libusbp::error();
+ REQUIRE(0);
+ }
+ catch(const std::exception & e)
+ {
+ REQUIRE(1);
+ CHECK(std::string(e.what()) == "No error.");
+ }
+ }
+
+ SECTION("can be moved without getting copied")
+ {
+ // This isn't really necessary for a light-weight error object, but it
+ // is a feature that unique_pointer_wrapper_with_copy is supposed to
+ // provide, so we want to test it on at least one of the classes that
+ // uses unique_pointer_wrapper.
+
+ libusbp::error error(error_create("hi"));
+ libusbp_error * p = error.pointer_get();
+ REQUIRE(p);
+
+ // Move constructor.
+ libusbp::error error2 = std::move(error);
+ REQUIRE(error2.pointer_get() == p);
+ REQUIRE_FALSE(error);
+
+ // Move assignment.
+ error = std::move(error2);
+ REQUIRE(error.pointer_get() == p);
+ REQUIRE_FALSE(error2);
+ }
+}
+
+
+TEST_CASE("null error", "[null_error]")
+{
+ libusbp::error error;
+
+ SECTION("can also be constructed by passing in NULL")
+ {
+ libusbp::error error(NULL);
+ }
+
+ SECTION("has a default message")
+ {
+ REQUIRE(error.message() == "No error.");
+ }
+
+ SECTION("is not present")
+ {
+ REQUIRE_FALSE(error);
+ }
+
+ SECTION("can be copied")
+ {
+ libusbp::error error2 = error;
+ REQUIRE_FALSE(error2);
+ }
+
+ SECTION("has no error codes, and you can not add codes to it")
+ {
+ REQUIRE_FALSE(error.has_code(1));
+ }
+}
+
+TEST_CASE("error_create", "[error_create]")
+{
+ SECTION("creates a non-null error")
+ {
+ libusbp::error error(error_create("Error1 %d.", 123));
+ REQUIRE(error);
+ }
+
+ SECTION("properly formats its input")
+ {
+ libusbp::error error(error_create("Error1 %d.", 123));
+ REQUIRE(error.message() == "Error1 123.");
+ }
+}
+
+TEST_CASE("error_add", "[error_add]")
+{
+ SECTION("works with NULL")
+ {
+ libusbp::error error(error_add(NULL, "hi"));
+ REQUIRE(error.message() == "hi");
+ }
+
+ SECTION("preserves the message and error codes of errors passed to it")
+ {
+ libusbp::error error(error_add(error_add_code(error_create("hi1"), 7), "hi2"));
+ CHECK(error.message() == "hi2 hi1");
+ CHECK(error.has_code(7));
+ }
+}
+
+TEST_CASE("error_add_code", "[error_add_code]")
+{
+ SECTION("works with NULL")
+ {
+ libusbp::error error(error_add_code(NULL, 4));
+ CHECK(error.message() == "");
+ CHECK(error.has_code(4));
+ }
+
+ SECTION("preserves the message and error codes of errors passed to it")
+ {
+ libusbp::error error(error_add_code(error_add_code(error_create("hi1"), 7), 9));
+ CHECK(error.message() == "hi1");
+ CHECK(error.has_code(7));
+ CHECK(error.has_code(9));
+ }
+}
+
+TEST_CASE("error_no_memory")
+{
+ libusbp::error error(&error_no_memory);
+
+ SECTION("has the right message")
+ {
+ REQUIRE(error.message() == "Failed to allocate memory.");
+ }
+
+ SECTION("has the right code")
+ {
+ REQUIRE(error.has_code(LIBUSBP_ERROR_MEMORY));
+ }
+}
+
+TEST_CASE("libusbp_error_copy")
+{
+ libusbp_error * error = error_create("X.");
+ error = error_add(error, "Y.");
+ error = error_add_code(error, 7);
+ error = error_add_code(error, 8);
+ libusbp::error error1(error);
+
+ // Test the C++ copy constructor and libusbp_error_copy at the same time.
+ libusbp::error error2 = error1;
+
+ SECTION("copies the message")
+ {
+ REQUIRE(error2.message() == "Y. X.");
+ }
+
+ SECTION("copies codes")
+ {
+ CHECK(error2.has_code(7));
+ CHECK(error2.has_code(8));
+ }
+}
+
+#endif
diff --git a/dep/libusbp/test/firmware/wixel/.gitignore b/dep/libusbp/test/firmware/wixel/.gitignore
new file mode 100644
index 00000000..4b1f0381
--- /dev/null
+++ b/dep/libusbp/test/firmware/wixel/.gitignore
@@ -0,0 +1,16 @@
+/wixel-sdk
+*.hex
+*.ihx
+*.rel
+*.d
+*.sym
+*.cdb
+*.mem
+*.rst
+*.map
+*.lst
+*.lk
+*.asm
+*.omf
+*.adb
+*.cdb
\ No newline at end of file
diff --git a/dep/libusbp/test/firmware/wixel/Makefile b/dep/libusbp/test/firmware/wixel/Makefile
new file mode 100644
index 00000000..5948d61c
--- /dev/null
+++ b/dep/libusbp/test/firmware/wixel/Makefile
@@ -0,0 +1,52 @@
+APP_NAME = test_firmware
+APP_LIBS = gpio usb wixel dma adc
+
+# Path to a Wixel SDK where the libraries have been built.
+WIXEL_SDK := wixel-sdk
+
+# Command-line utilities
+CC := sdcc
+MV := mv
+WIXELCMD := wixelcmd
+
+INCDIRS += $(WIXEL_SDK)/libraries/include
+
+# C_FLAGS += -DTEST_DEVICE_B
+C_FLAGS += -Wp,-MD,$(@:%.rel=%.d),-MT,$@,-MP
+C_FLAGS += --disable-warning 110
+C_FLAGS += $(patsubst %,-I%,$(INCDIRS))
+C_FLAGS += -Wa,-p
+C_FLAGS += --model-medium
+LD_FLAGS += --model-medium
+C_FLAGS += --debug
+LD_FLAGS += --debug
+LD_FLAGS += --code-loc 0x0400 --code-size 0x7400
+LD_FLAGS += --iram-size 0x0100 --xram-loc 0xF000 --xram-size 0xF00
+LD_FLAGS += -o $(@:%.hex=%.ihx)
+LD_FLAGS += -L$(WIXEL_SDK)/libraries/lib
+LD_FLAGS += $(foreach lib,$(APP_LIBS),-l$(lib))
+LD_FLAGS += $(WIXEL_SDK)/libraries/xpage/xpage.rel
+
+RELs := $(patsubst %.c,%.rel, $(wildcard *.c))
+
+.DEFAULT_GOAL = app
+.PHONY: app
+app: $(APP_NAME).hex
+
+$(APP_NAME).hex: $(RELs)
+ $(CC) $(LD_FLAGS) $^
+ $(MV) -f $(@:%.hex=%.ihx) $@
+
+%.rel: %.c
+ $(CC) -c $< $(C_FLAGS) -o $@
+
+.PHONY: load
+load: $(APP_NAME).hex
+ $(WIXELCMD) write $<
+
+clean:
+ @rm -fv *.hex *.ihx *.rel *.d *.sym *.cdb *.mem *.rst *.map *.lst *.lk *.asm *.omf *.adb *.cdb
+
+# Include all the dependency files generated during compilation so that Make
+# knows which .rel files to recompile when a .h file changes.
+-include $(RELs:%.rel=%.d)
diff --git a/dep/libusbp/test/firmware/wixel/cdc_acm_constants.h b/dep/libusbp/test/firmware/wixel/cdc_acm_constants.h
new file mode 100644
index 00000000..b1377d5a
--- /dev/null
+++ b/dep/libusbp/test/firmware/wixel/cdc_acm_constants.h
@@ -0,0 +1,34 @@
+#pragma once
+
+// USB Class Codes
+#define CDC_CLASS 2 // (CDC 1.20 Section 4.1: Communications Device Class Code).
+#define CDC_DATA_INTERFACE_CLASS 0xA // (CDC 1.20 Section 4.5: Data Class Interface Codes).
+
+// USB Subclass Codes
+#define CDC_SUBCLASS_ACM 2 // (CDC 1.20 Section 4.3: Communications Class Subclass Codes). Refer to USBPSTN1.2.
+
+// USB Protocol Codes
+#define CDC_PROTOCOL_V250 1 // (CDC 1.20 Section 4.4: Communications Class Protocol Codes).
+
+// USB Descriptor types from CDC 1.20 Section 5.2.3, Table 12
+#define CDC_DESCRIPTOR_TYPE_CS_INTERFACE 0x24
+#define CDC_DESCRIPTOR_TYPE_CS_ENDPOINT 0x25
+
+// USB Descriptor sub-types from CDC 1.20 Table 13: bDescriptor SubType in Communications Class Functional Descriptors
+#define CDC_DESCRIPTOR_SUBTYPE_HEADER 0
+#define CDC_DESCRIPTOR_SUBTYPE_CALL_MANAGEMENT 1
+#define CDC_DESCRIPTOR_SUBTYPE_ABSTRACT_CONTROL_MANAGEMENT 2
+#define CDC_DESCRIPTOR_SUBTYPE_UNION 6
+
+// Request Codes from CDC 1.20 Section 6.2: Management Element Requests.
+#define ACM_GET_ENCAPSULATED_RESPONSE 0
+#define ACM_SEND_ENCAPSULATED_COMMAND 1
+
+// Request Codes from PSTN 1.20 Table 13.
+#define ACM_REQUEST_SET_LINE_CODING 0x20
+#define ACM_REQUEST_GET_LINE_CODING 0x21
+#define ACM_REQUEST_SET_CONTROL_LINE_STATE 0x22
+
+// Notification Codes from PSTN 1.20 Table 30.
+#define ACM_NOTIFICATION_RESPONSE_AVAILABLE 0x01
+#define ACM_NOTIFICATION_SERIAL_STATE 0x20
diff --git a/dep/libusbp/test/firmware/wixel/main.c b/dep/libusbp/test/firmware/wixel/main.c
new file mode 100644
index 00000000..c2a2e4e7
--- /dev/null
+++ b/dep/libusbp/test/firmware/wixel/main.c
@@ -0,0 +1,736 @@
+// This is test firmware that runs on a Wixel and can be used for
+// testing generic USB PC software.
+//
+// By default, it compiles USB Test Device A, a composite device with two
+// vendor-defined USB interfaces and a serial port.
+//
+// When compiled with the TEST_DEVICE_B option, it compiles USB Test Device B,
+// which is a non-composite device with a single vendor-defined USB interface.
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "cdc_acm_constants.h"
+
+#ifdef TEST_DEVICE_B
+#define TEST_DEVICE_LETTER 'B'
+#else
+#define TEST_DEVICE_LETTER 'A'
+#define COMPOSITE
+#endif
+
+//#define USE_MS_OS_10
+#define USE_MS_OS_20
+
+#define NATIVE_INTERFACE_0 0
+#define ADC_DATA_ENDPOINT 2
+#define ADC_DATA_FIFO USBF2
+#define ADC_DATA_PACKET_SIZE 5
+#define CMD_ENDPOINT 3
+#define CMD_PACKET_SIZE 32
+#define CMD_FIFO USBF3
+
+#ifdef COMPOSITE
+#define NATIVE_INTERFACE_1 1
+
+#define CDC_OUT_PACKET_SIZE 64
+#define CDC_IN_PACKET_SIZE 64
+#define CDC_CONTROL_INTERFACE 2
+#define CDC_DATA_INTERFACE 3
+#define CDC_NOTIFICATION_ENDPOINT 1
+#define CDC_NOTIFICATION_FIFO USBF1
+#define CDC_NOTIFICATION_PACKET_SIZE 10
+#define CDC_DATA_ENDPOINT 4
+#define CDC_DATA_FIFO USBF4
+#endif
+
+#define REQUEST_GET_MS_DESCRIPTOR 0x20
+
+// Wireless USB Specification 1.1, Table 7-1
+#define USB_DESCRIPTOR_TYPE_SECURITY 12
+#define USB_DESCRIPTOR_TYPE_KEY 13
+#define USB_DESCRIPTOR_TYPE_ENCRYPTION_TYPE 14
+#define USB_DESCRIPTOR_TYPE_BOS 15
+#define USB_DESCRIPTOR_TYPE_DEVICE_CAPABILITY 16
+#define USB_DESCRIPTOR_TYPE_WIRELESS_ENDPOINT_COMPANION 17
+
+// Microsoft OS 2.0 Descriptors, Table 1
+#define USB_DEVICE_CAPABILITY_TYPE_PLATFORM 5
+
+// Microsoft OS 2.0 Descriptors, Table 8
+#define MS_OS_20_DESCRIPTOR_INDEX 7
+#define MS_OS_20_SET_ALT_ENUMERATION 8
+
+// Microsoft OS 2.0 Descriptors, Table 9
+#define MS_OS_20_SET_HEADER_DESCRIPTOR 0x00
+#define MS_OS_20_SUBSET_HEADER_CONFIGURATION 0x01
+#define MS_OS_20_SUBSET_HEADER_FUNCTION 0x02
+#define MS_OS_20_FEATURE_COMPATIBLE_ID 0x03
+#define MS_OS_20_FEATURE_REG_PROPERTY 0x04
+#define MS_OS_20_FEATURE_MIN_RESUME_TIME 0x05
+#define MS_OS_20_FEATURE_MODEL_ID 0x06
+#define MS_OS_20_FEATURE_CCGP_DEVICE 0x07
+
+uint8_t CODE usbStringDescriptorCount = 7;
+DEFINE_STRING_DESCRIPTOR(languagesString, 1, USB_LANGUAGE_EN_US)
+DEFINE_STRING_DESCRIPTOR(manufacturerString, 18, 'P','o','l','o','l','u',' ','C','o','r','p','o','r','a','t','i','o','n')
+DEFINE_STRING_DESCRIPTOR(productString, 17, 'U','S','B',' ','T','e','s','t',' ','D','e','v','i','c','e',' ',TEST_DEVICE_LETTER)
+DEFINE_STRING_DESCRIPTOR(interface0String, 29, 'U','S','B',' ','T','e','s','t',' ','D','e','v','i','c','e',' ',TEST_DEVICE_LETTER,' ','I','n','t','e','r','f','a','c','e',' ','0')
+DEFINE_STRING_DESCRIPTOR(interface1String, 29, 'U','S','B',' ','T','e','s','t',' ','D','e','v','i','c','e',' ',TEST_DEVICE_LETTER,' ','I','n','t','e','r','f','a','c','e',' ','1')
+DEFINE_STRING_DESCRIPTOR(interface2String, 22, 'U','S','B',' ','T','e','s','t',' ','D','e','v','i','c','e',' ',TEST_DEVICE_LETTER,' ','P','o','r','t')
+#ifdef USE_MS_OS_10
+DEFINE_STRING_DESCRIPTOR(osString, 8, 'M','S','F','T','1','0','0', REQUEST_GET_MS_DESCRIPTOR)
+#endif
+
+uint16_t CODE * CODE usbStringDescriptors[] =
+{
+ languagesString,
+ manufacturerString,
+ productString,
+ serialNumberStringDescriptor,
+ interface0String,
+ interface1String,
+ interface2String
+};
+
+// See https://msdn.microsoft.com/en-us/library/ff540054.aspx
+USB_DESCRIPTOR_DEVICE CODE usbDeviceDescriptor =
+{
+ sizeof(USB_DESCRIPTOR_DEVICE),
+ USB_DESCRIPTOR_TYPE_DEVICE,
+#ifdef USE_MS_OS_20
+ 0x0201, // USB version: 2.0 with LPM ECN
+#else
+ 0x0200, // USB version: 2.0
+#endif
+#ifdef COMPOSITE
+ 0xEF, // Class Code
+ 0x02, // Subclass code
+ 0x01, // Protocol code
+#else
+ 0xFF, // Class Code
+ 0x00, // Subclass code
+ 0x00, // Protocol code
+#endif
+ USB_EP0_PACKET_SIZE, // Max packet size for Endpoint 0
+ USB_VENDOR_ID_POLOLU, // Vendor ID
+#ifdef TEST_DEVICE_B
+ 0xDA02, // Product ID: USB Test Device B
+#else
+ 0xDA01, // Product ID: USB Test Device A
+#endif
+ 0x0007, // Device release number in BCD format
+ 1, // Index of Manufacturer String Descriptor
+ 2, // Index of Product String Descriptor
+ 3, // Index of Serial Number String Descriptor
+ 1 // Number of possible configurations.
+};
+
+#ifdef COMPOSITE
+
+// Composite configuration descriptor.
+struct CONFIG1 {
+ USB_DESCRIPTOR_CONFIGURATION configuration;
+ USB_DESCRIPTOR_INTERFACE nativeInterface0;
+ USB_DESCRIPTOR_ENDPOINT adcDataIn;
+ USB_DESCRIPTOR_ENDPOINT cmdOut;
+ USB_DESCRIPTOR_ENDPOINT cmdIn;
+ USB_DESCRIPTOR_INTERFACE nativeInterface1;
+ USB_DESCRIPTOR_INTERFACE_ASSOCIATION portFunction;
+ USB_DESCRIPTOR_INTERFACE portCommunicationInterface;
+ uint8_t portClassSpecific[19];
+ USB_DESCRIPTOR_ENDPOINT portNotificationElement;
+ USB_DESCRIPTOR_INTERFACE portDataInterface;
+ USB_DESCRIPTOR_ENDPOINT portDataOut;
+ USB_DESCRIPTOR_ENDPOINT portDataIn;
+} usbConfigurationDescriptor =
+{
+ {
+ sizeof(USB_DESCRIPTOR_CONFIGURATION),
+ USB_DESCRIPTOR_TYPE_CONFIGURATION,
+ sizeof(struct CONFIG1), // wTotalLength
+ 4, // bNumInterfaces
+ 1, // bConfigurationValue
+ 0, // STRING: iConfiguration
+ 0xC0, // bmAttributes: self power capable
+ 50, // bMaxPower (in units of 2 mA)
+ },
+
+ {
+ sizeof(USB_DESCRIPTOR_INTERFACE),
+ USB_DESCRIPTOR_TYPE_INTERFACE,
+ NATIVE_INTERFACE_0, // bInterfaceNumber
+ 0, // bAlternateSetting
+ 3, // bNumEndpoints
+ 0xFF, // bInterfaceClass: Vendor Specific
+ 0x00, // bInterfaceSubClass
+ 0x00, // bInterfaceProtocol
+ 4 // STRING: iInterface
+ },
+
+ {
+ sizeof(USB_DESCRIPTOR_ENDPOINT),
+ USB_DESCRIPTOR_TYPE_ENDPOINT,
+ USB_ENDPOINT_ADDRESS_IN | ADC_DATA_ENDPOINT,
+ USB_TRANSFER_TYPE_INTERRUPT,
+ ADC_DATA_PACKET_SIZE,
+ 1,
+ },
+
+ {
+ sizeof(USB_DESCRIPTOR_ENDPOINT),
+ USB_DESCRIPTOR_TYPE_ENDPOINT,
+ USB_ENDPOINT_ADDRESS_OUT | CMD_ENDPOINT,
+ USB_TRANSFER_TYPE_BULK,
+ CMD_PACKET_SIZE,
+ 0,
+ },
+
+ {
+ sizeof(USB_DESCRIPTOR_ENDPOINT),
+ USB_DESCRIPTOR_TYPE_ENDPOINT,
+ USB_ENDPOINT_ADDRESS_IN | CMD_ENDPOINT,
+ USB_TRANSFER_TYPE_BULK,
+ CMD_PACKET_SIZE,
+ 0,
+ },
+
+ {
+ sizeof(USB_DESCRIPTOR_INTERFACE),
+ USB_DESCRIPTOR_TYPE_INTERFACE,
+ NATIVE_INTERFACE_1, // bInterfaceNumber
+ 0, // bAlternateSetting
+ 0, // bNumEndpoints
+ 0xFF, // bInterfaceClass: Vendor Specific
+ 0x00, // bInterfaceSubClass
+ 0x00, // bInterfaceProtocol
+ 5 // STRING: iInterface
+ },
+
+ { // Port Interface Association Descriptor
+ sizeof(USB_DESCRIPTOR_INTERFACE_ASSOCIATION),
+ USB_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION,
+ CDC_CONTROL_INTERFACE, // first interface number
+ 2, // interface count
+ CDC_CLASS, // class
+ CDC_SUBCLASS_ACM, // subclass
+ CDC_PROTOCOL_V250, // protocol (enables automatic Linux support)
+ 6, // STRING
+ },
+
+ { // Communications Interface: Defines a virtual COM port.
+ sizeof(USB_DESCRIPTOR_INTERFACE),
+ USB_DESCRIPTOR_TYPE_INTERFACE,
+ CDC_CONTROL_INTERFACE, // bInterfaceNumber
+ 0, // bAlternateSetting
+ 1, // bNumEndpoints
+ CDC_CLASS, // bInterfaceClass
+ CDC_SUBCLASS_ACM, // bInterfaceSubClass
+ CDC_PROTOCOL_V250, // bInterfaceProtocol
+ 0 // STRING: iInterface
+ },
+ { // CDC Class-Specific Descriptors describing the virtual COM port.
+ 5,
+ CDC_DESCRIPTOR_TYPE_CS_INTERFACE,
+ CDC_DESCRIPTOR_SUBTYPE_HEADER,
+ 0x20,0x01, // bcdCDC. We conform to CDC 1.20
+
+ 4,
+ CDC_DESCRIPTOR_TYPE_CS_INTERFACE,
+ CDC_DESCRIPTOR_SUBTYPE_ABSTRACT_CONTROL_MANAGEMENT,
+ 2, // bmCapabilities. See USBPSTN1.2 Table 4.
+
+ 5,
+ CDC_DESCRIPTOR_TYPE_CS_INTERFACE,
+ CDC_DESCRIPTOR_SUBTYPE_UNION,
+ CDC_CONTROL_INTERFACE, // index of the control interface
+ CDC_DATA_INTERFACE, // index of the subordinate interface
+
+ 5,
+ CDC_DESCRIPTOR_TYPE_CS_INTERFACE,
+ CDC_DESCRIPTOR_SUBTYPE_CALL_MANAGEMENT,
+ 0x00, // bmCapabilities. USBPSTN1.2 Table 3.
+ CDC_DATA_INTERFACE
+ },
+ { // USB Command Port notification endpoint.
+ sizeof(USB_DESCRIPTOR_ENDPOINT),
+ USB_DESCRIPTOR_TYPE_ENDPOINT,
+ USB_ENDPOINT_ADDRESS_IN | CDC_NOTIFICATION_ENDPOINT, // bEndpointAddress
+ USB_TRANSFER_TYPE_INTERRUPT, // bmAttributes
+ CDC_NOTIFICATION_PACKET_SIZE, // wMaxPacketSize
+ 1, // bInterval
+ },
+ { // Data interface: for sending data on the virtual COM port.
+ sizeof(USB_DESCRIPTOR_INTERFACE),
+ USB_DESCRIPTOR_TYPE_INTERFACE,
+ CDC_DATA_INTERFACE, // bInterfaceNumber
+ 0, // bAlternateSetting
+ 2, // bNumEndpoints
+ CDC_DATA_INTERFACE_CLASS, // bInterfaceClass
+ 0, // bInterfaceSubClass
+ 0, // bInterfaceProtocol
+ 0 // STIRNG: iInterface
+ },
+ { // Command port data OUT.
+ sizeof(USB_DESCRIPTOR_ENDPOINT),
+ USB_DESCRIPTOR_TYPE_ENDPOINT,
+ USB_ENDPOINT_ADDRESS_OUT | CDC_DATA_ENDPOINT, // bEndpointAddress
+ USB_TRANSFER_TYPE_BULK, // bmAttributes
+ CDC_OUT_PACKET_SIZE, // wMaxPacketSize
+ 0, // bInterval
+ },
+ { // Command port data IN.
+ sizeof(USB_DESCRIPTOR_ENDPOINT),
+ USB_DESCRIPTOR_TYPE_ENDPOINT,
+ USB_ENDPOINT_ADDRESS_IN | CDC_DATA_ENDPOINT, // bEndpointAddress
+ USB_TRANSFER_TYPE_BULK, // bmAttributes
+ CDC_IN_PACKET_SIZE, // wMaxPacketSize
+ 0, // bInterval
+ },
+};
+
+#else
+
+// Non-composite configuration descriptor
+struct CONFIG1 {
+ USB_DESCRIPTOR_CONFIGURATION configuration;
+ USB_DESCRIPTOR_INTERFACE nativeInterface0;
+ USB_DESCRIPTOR_ENDPOINT adcDataIn;
+} usbConfigurationDescriptor =
+{
+ {
+ sizeof(USB_DESCRIPTOR_CONFIGURATION),
+ USB_DESCRIPTOR_TYPE_CONFIGURATION,
+ sizeof(struct CONFIG1), // wTotalLength
+ 1, // bNumInterfaces
+ 1, // bConfigurationValue
+ 0, // STRING: iConfiguration
+ 0xC0, // bmAttributes: self power capable
+ 50, // bMaxPower (in units of 2 mA)
+ },
+
+ {
+ sizeof(USB_DESCRIPTOR_INTERFACE),
+ USB_DESCRIPTOR_TYPE_INTERFACE,
+ NATIVE_INTERFACE_0, // bInterfaceNumber
+ 0, // bAlternateSetting
+ 1, // bNumEndpoints
+ 0xFF, // bInterfaceClass: Vendor Specific
+ 0x00, // bInterfaceSubClass
+ 0x00, // bInterfaceProtocol
+ 4 // STRING: iInterface
+ },
+
+ {
+ sizeof(USB_DESCRIPTOR_ENDPOINT),
+ USB_DESCRIPTOR_TYPE_ENDPOINT,
+ USB_ENDPOINT_ADDRESS_IN | ADC_DATA_ENDPOINT,
+ USB_TRANSFER_TYPE_INTERRUPT,
+ ADC_DATA_PACKET_SIZE,
+ 1,
+ },
+};
+#endif
+
+#ifdef USE_MS_OS_10
+
+XDATA uint8 compatIdDescriptor[0x28] =
+{
+ 0x28, 0x00, 0x00, 0x00, // dwLength
+ 0x00, 0x01, // bcdVersion: 1.00
+ 0x04, 0x00, // wIndex: Compatibility ID
+ 0x01, // bCount (number of sections)
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x00, 0x00, 0x00, // reserved
+ 0x00, // bFirstInterfaceNumber
+ 0x01, // reserved
+ 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, // compatibleID
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // subCompatibleID
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
+};
+
+XDATA uint8 extendedPropertiesDescriptor[0x92] =
+{
+ 0x92, 0x00, 0x00, 0x00, // dwLength
+ 0x00, 0x01, // bcdVersion: 1.00
+ 0x05, 0x00, // wIndex: extended properties
+ 0x01, 0x00, // wCount (number of sections)
+ 0x88, 0x00, 0x00, 0x00, // dwSize of first section
+ 0x07, 0x00, 0x00, 0x00, // dwPropertyDataType: REG_MULTI_SZ
+ 0x2a, 0x00, // wPropertyNameLength
+ 'D',0,'e',0,'v',0,'i',0,'c',0,'e',0,'I',0,'n',0,'t',0,'e',0,'r',0,
+ 'f',0,'a',0,'c',0,'e',0,'G',0,'U',0,'I',0,'D',0,'s',0,0,0,
+ 0x50, 0x00, 0x00, 0x00, // dwPropertyDataLength
+ '{',0,'9',0,'9',0,'c',0,'4',0,'b',0,'b',0,'b',0,'0',0,'-',0,
+ 'e',0,'9',0,'2',0,'5',0,'-',0,'4',0,'3',0,'9',0,'7',0,'-',0,
+ 'a',0,'f',0,'e',0,'e',0,'-',0,'9',0,'8',0,'1',0,'c',0,'d',0,
+ '0',0,'7',0,'0',0,'2',0,'1',0,'6',0,'3',0,'}',0,0,0,0,0,
+};
+
+#endif
+
+#ifdef USE_MS_OS_20
+
+#ifdef COMPOSITE
+
+#define MS_OS_20_LENGTH 0xB2
+
+// Micrsoft OS 2.0 Descriptor Set for a composite device.
+XDATA uint8 msOs20DescriptorSet[MS_OS_20_LENGTH] =
+{
+ // Microsoft OS 2.0 Descriptor Set header (Table 10)
+ 0x0A, 0x00, // wLength
+ MS_OS_20_SET_HEADER_DESCRIPTOR, 0x00,
+ 0x00, 0x00, 0x03, 0x06, // dwWindowsVersion: Windows 8.1 (NTDDI_WINBLUE)
+ MS_OS_20_LENGTH, 0x00, // wTotalLength
+
+ // Microsoft OS 2.0 configuration subset (Table 11)
+ 0x08, 0x00, // wLength of this header
+ MS_OS_20_SUBSET_HEADER_CONFIGURATION, 0x00, // wDescriptorType
+ 0, // configuration index
+ 0x00, // bReserved
+ 0xA8, 0x00, // wTotalLength of this subset
+
+ // Microsoft OS 2.0 function subset header (Table 12)
+ 0x08, 0x00, // wLength
+ MS_OS_20_SUBSET_HEADER_FUNCTION, 0x00, // wDescriptorType
+ 0, // bFirstInterface
+ 0x00, // bReserved,
+ 0xA0, 0x00, // wSubsetLength
+
+ // Microsoft OS 2.0 compatible ID descriptor (Table 13)
+ 0x14, 0x00, // wLength
+ MS_OS_20_FEATURE_COMPATIBLE_ID, 0x00, // wDescriptorType
+ 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, // compatibleID
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // subCompatibleID
+
+ // Microsoft OS 2.0 registry property descriptor (Table 14)
+ 0x84, 0x00, // wLength
+ MS_OS_20_FEATURE_REG_PROPERTY, 0x00,
+ 0x07, 0x00, // wPropertyDataType: REG_MULTI_SZ
+ 0x2a, 0x00, // wPropertyNameLength
+ 'D',0,'e',0,'v',0,'i',0,'c',0,'e',0,'I',0,'n',0,'t',0,'e',0,'r',0,
+ 'f',0,'a',0,'c',0,'e',0,'G',0,'U',0,'I',0,'D',0,'s',0,0,0,
+ 0x50, 0x00, // wPropertyDataLength
+ '{',0,'9',0,'9',0,'c',0,'4',0,'b',0,'b',0,'b',0,'0',0,'-',0,
+ 'e',0,'9',0,'2',0,'5',0,'-',0,'4',0,'3',0,'9',0,'7',0,'-',0,
+ 'a',0,'f',0,'e',0,'e',0,'-',0,'9',0,'8',0,'1',0,'c',0,'d',0,
+ '0',0,'7',0,'0',0,'2',0,'1',0,'6',0,'3',0,'}',0,0,0,0,0,
+};
+
+#else
+
+#define MS_OS_20_LENGTH 0xA2
+
+// Micrsoft OS 2.0 Descriptor Set for a non-composite device.
+XDATA uint8 msOs20DescriptorSet[MS_OS_20_LENGTH] =
+{
+ // Microsoft OS 2.0 Descriptor Set header (Table 10)
+ 0x0A, 0x00, // wLength
+ MS_OS_20_SET_HEADER_DESCRIPTOR, 0x00,
+ 0x00, 0x00, 0x03, 0x06, // dwWindowsVersion: Windows 8.1 (NTDDI_WINBLUE)
+ MS_OS_20_LENGTH, 0x00, // wTotalLength
+
+ // Microsoft OS 2.0 compatible ID descriptor (Table 13)
+ 0x14, 0x00, // wLength
+ MS_OS_20_FEATURE_COMPATIBLE_ID, 0x00, // wDescriptorType
+ 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, // compatibleID
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // subCompatibleID
+
+ // Microsoft OS 2.0 registry property descriptor (Table 14)
+ 0x84, 0x00, // wLength
+ MS_OS_20_FEATURE_REG_PROPERTY, 0x00,
+ 0x07, 0x00, // wPropertyDataType: REG_MULTI_SZ
+ 0x2a, 0x00, // wPropertyNameLength
+ 'D',0,'e',0,'v',0,'i',0,'c',0,'e',0,'I',0,'n',0,'t',0,'e',0,'r',0,
+ 'f',0,'a',0,'c',0,'e',0,'G',0,'U',0,'I',0,'D',0,'s',0,0,0,
+ 0x50, 0x00, // wPropertyDataLength
+ '{',0,'9',0,'9',0,'c',0,'4',0,'b',0,'b',0,'b',0,'0',0,'-',0,
+ 'e',0,'9',0,'2',0,'5',0,'-',0,'4',0,'3',0,'9',0,'7',0,'-',0,
+ 'a',0,'f',0,'e',0,'e',0,'-',0,'9',0,'8',0,'1',0,'c',0,'d',0,
+ '0',0,'7',0,'0',0,'2',0,'1',0,'6',0,'3',0,'}',0,0,0,0,0,
+};
+
+#endif
+
+XDATA uint8 bosDescriptor[0x21] =
+{
+ 0x05, // bLength of this descriptor
+ USB_DESCRIPTOR_TYPE_BOS,
+ 0x21, 0x00, // wLength
+ 0x01, // bNumDeviceCaps
+
+ 0x1C, // bLength of this first device capability descriptor
+ USB_DESCRIPTOR_TYPE_DEVICE_CAPABILITY,
+ USB_DEVICE_CAPABILITY_TYPE_PLATFORM,
+ 0x00, // bReserved
+ // Microsoft OS 2.0 descriptor platform capability UUID
+ // from Microsoft OS 2.0 Descriptors, Table 3.
+ 0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C,
+ 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F,
+
+ 0x00, 0x00, 0x03, 0x06, // dwWindowsVersion: Windows 8.1 (NTDDI_WINBLUE)
+ MS_OS_20_LENGTH, 0x00, // wMSOSDescriptorSetTotalLength
+ REQUEST_GET_MS_DESCRIPTOR,
+ 0, // bAltEnumCode
+};
+
+#endif
+
+// Make a buffer that takes 4 packets to transfer.
+XDATA uint8_t dataBuffer[USB_EP0_PACKET_SIZE * 3 + 4];
+
+bool adcPaused = 0;
+uint16_t adcPauseStartTime;
+uint16_t adcPauseDuration;
+
+bool yellowOn = 0;
+
+void usbCallbackInitEndpoints()
+{
+ usbInitEndpointIn(ADC_DATA_ENDPOINT, ADC_DATA_PACKET_SIZE);
+ usbInitEndpointOut(CMD_ENDPOINT, CMD_PACKET_SIZE);
+ usbInitEndpointIn(CMD_ENDPOINT, CMD_PACKET_SIZE);
+}
+
+void usbCallbackSetupHandler()
+{
+ switch(usbSetupPacket.bRequest)
+ {
+ case REQUEST_GET_MS_DESCRIPTOR:
+ #ifdef USE_MS_OS_20
+ if (usbSetupPacket.bmRequestType == 0xC0 &&
+ usbSetupPacket.wIndex == MS_OS_20_DESCRIPTOR_INDEX)
+ {
+ // Request for Microsoft OS 2.0 Descriptor Set.
+ usbControlRead(sizeof(msOs20DescriptorSet),
+ (uint8 XDATA *)&msOs20DescriptorSet);
+ }
+ #endif
+
+ #ifdef USE_MS_OS_10
+ if (usbSetupPacket.bmRequestType == 0xC0 &&
+ usbSetupPacket.wIndex == 4)
+ {
+ // Request for an extended compat ID, as defined in Microsoft
+ // OS Descriptors 1.0. This descriptor applies to the whole
+ // device, so we expect wValue to be 0 here but we do not
+ // check it.
+ usbControlRead(sizeof(compatIdDescriptor),
+ (uint8 XDATA *)&compatIdDescriptor);
+ }
+
+ if (usbSetupPacket.bmRequestType == 0xC1 &&
+ usbSetupPacket.wValue == 0 &&
+ usbSetupPacket.wIndex == 5)
+ {
+ // Request for extended properties on interface 0.
+ usbControlRead(sizeof(extendedPropertiesDescriptor),
+ (uint8 XDATA *)&extendedPropertiesDescriptor);
+ }
+ #endif
+ return;
+
+ case 0x90: // Set LED
+ yellowOn = usbSetupPacket.wValue & 1;
+ usbControlAcknowledge();
+ return;
+
+ case 0x91: // Read buffer
+ // The length of the device's response will be equal to wIndex
+ // so this requrest can be used to simulate what happens when
+ // the device returns less data than expected.
+ if (usbSetupPacket.wLength > sizeof(dataBuffer))
+ {
+ return; // bad size
+ }
+ if (usbSetupPacket.wIndex > usbSetupPacket.wLength)
+ {
+ return; // bad size
+ }
+ delayMs(usbSetupPacket.wValue);
+ usbControlRead(usbSetupPacket.wIndex, (XDATA uint8_t *)&dataBuffer);
+ return;
+
+ case 0x92: // Write buffer
+ if (usbSetupPacket.wLength > sizeof(dataBuffer))
+ {
+ return; // bad size
+ }
+ delayMs(usbSetupPacket.wValue);
+
+ if (usbSetupPacket.wLength > 0)
+ {
+ usbControlWrite(usbSetupPacket.wLength, (XDATA uint8_t *)&dataBuffer);
+ }
+ else
+ {
+ usbControlAcknowledge();
+ }
+ return;
+
+ case 0xA0: // Pause or unpause the ADC data stream.
+ if (usbSetupPacket.wValue == 0)
+ {
+ adcPaused = 0;
+ }
+ else
+ {
+ adcPaused = 1;
+ adcPauseStartTime = getMs();
+ adcPauseDuration = usbSetupPacket.wValue;
+ }
+ usbControlAcknowledge();
+ return;
+ }
+}
+
+void usbCallbackClassDescriptorHandler()
+{
+#ifdef USE_MS_OS_10
+ if (usbSetupPacket.wValue == 0x03EE)
+ {
+ // Microsoft OS String descriptor
+ usbControlRead(sizeof(osString), (uint8 XDATA *)&osString);
+ }
+#endif
+
+#ifdef USE_MS_OS_20
+ if (usbSetupPacket.wValue == 0x0f00)
+ {
+ // BOS Descriptor
+ usbControlRead(sizeof(bosDescriptor), bosDescriptor);
+ }
+#endif
+}
+
+void usbCallbackControlWriteHandler()
+{
+}
+
+// Sends ADC data to the computer with a USB endpoint.
+void adcDataTx()
+{
+ if (usbDeviceState != USB_STATE_CONFIGURED)
+ {
+ // We have not reached the Configured state yet, so we should not be
+ // touching the non-zero endpoints.
+ return;
+ }
+
+ if (adcPaused)
+ {
+ if ((uint16_t)(getMs() - adcPauseStartTime) >= adcPauseDuration)
+ {
+ adcPaused = 0;
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ USBINDEX = ADC_DATA_ENDPOINT;
+
+ if (!(USBCSIL & USBCSIL_INPKT_RDY))
+ {
+ uint16_t reading;
+
+ // There is buffer space available, so queue up an IN
+ // packet with some stuff in it.
+
+ // Send the USB frame number.
+ ADC_DATA_FIFO = USBFRML;
+ ADC_DATA_FIFO = USBFRMH;
+
+ // Send the ADC reading.
+ reading = adcRead(5); // P0_5
+ ADC_DATA_FIFO = reading & 0xFF;
+ ADC_DATA_FIFO = reading >> 8 & 0xFF;
+
+ // Send a constant byte so that we have some data that can actually be
+ // checked for correctness in an automated test.
+ ADC_DATA_FIFO = 0xAB;
+
+ USBCSIL |= USBCSIL_INPKT_RDY;
+
+ // Notify the USB library that some activity has occurred.
+ usbActivityFlag = 1;
+ }
+}
+
+void cmdService()
+{
+ uint8_t count, cmd, i;
+ uint16_t delay;
+
+ if (usbDeviceState != USB_STATE_CONFIGURED) { return; }
+
+ USBINDEX = CMD_ENDPOINT;
+ if (!(USBCSOL & USBCSOL_OUTPKT_RDY))
+ {
+ // No command packet available right now.
+ return;
+ }
+
+ count = USBCNTL;
+
+ if (count == 0)
+ {
+ // Empty packet
+ dataBuffer[0] = 0x66;
+ }
+
+ if (count >= 2)
+ {
+ cmd = CMD_FIFO;
+
+ // Command 0x92: Set a byte in dataBuffer
+ if (cmd == 0x92)
+ {
+ dataBuffer[0] = CMD_FIFO;
+ }
+
+ // Command 0xDE: Delay
+ if (cmd == 0xDE)
+ {
+ delay = CMD_FIFO;
+ delay += CMD_FIFO << 8;
+ delayMs(delay);
+ }
+ }
+
+ for (i = 0; i < count; i++)
+ {
+ CMD_FIFO;
+ }
+
+ USBCSOL &= ~USBCSOL_OUTPKT_RDY; // Done with this packet.
+}
+
+void main()
+{
+ uint8_t x = 0;
+ setDigitalInput(0, 1);
+ systemInit();
+ usbInit();
+ while(1)
+ {
+ boardService();
+ usbShowStatusWithGreenLed();
+ usbPoll();
+ adcDataTx();
+ cmdService();
+ LED_YELLOW(yellowOn);
+
+ if (!isPinHigh(0))
+ {
+ boardStartBootloader();
+ }
+ }
+}
+
diff --git a/dep/libusbp/test/firmware/wixel/prepare_sdk.sh b/dep/libusbp/test/firmware/wixel/prepare_sdk.sh
new file mode 100644
index 00000000..cbf5f19b
--- /dev/null
+++ b/dep/libusbp/test/firmware/wixel/prepare_sdk.sh
@@ -0,0 +1,4 @@
+set -e
+git clone git@github.com:pololu/wixel-sdk
+cd wixel-sdk
+make libs
diff --git a/dep/libusbp/test/generic_handle_test.cpp b/dep/libusbp/test/generic_handle_test.cpp
new file mode 100644
index 00000000..3d07e41c
--- /dev/null
+++ b/dep/libusbp/test/generic_handle_test.cpp
@@ -0,0 +1,290 @@
+#include
+
+TEST_CASE("generic_handle traits")
+{
+ libusbp::generic_handle handle, handle2;
+
+ SECTION("is not copy-constructible")
+ {
+ REQUIRE_FALSE(std::is_copy_constructible::value);
+
+ // Should not compile:
+ // libusbp::generic_handle handle3(handle);
+ }
+
+ SECTION("is not copy-assignable")
+ {
+ REQUIRE_FALSE(std::is_copy_assignable::value);
+
+ // Should not compile:
+ // handle2 = handle;
+ }
+}
+
+TEST_CASE("generic handles cannot be created from a NULL generic interface")
+{
+ try
+ {
+ libusbp::generic_interface gi;
+ libusbp::generic_handle handle(gi);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(std::string(error.what()) == "Generic interface is null.");
+ }
+}
+
+TEST_CASE("null generic_handle")
+{
+ libusbp::generic_handle handle;
+
+ SECTION("is not present")
+ {
+ REQUIRE_FALSE(handle);
+ }
+
+ SECTION("can be closed (but it makes no difference)")
+ {
+ handle.close();
+ REQUIRE_FALSE(handle);
+ }
+
+ SECTION("cannot open async pipes")
+ {
+ try
+ {
+ handle.open_async_in_pipe(0x82);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Generic handle is null.");
+ }
+ }
+
+ SECTION("cannot set timeouts")
+ {
+ try
+ {
+ handle.set_timeout(0, 0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Generic handle is null.");
+ }
+ }
+
+ SECTION("cannot do control transfers")
+ {
+ try
+ {
+ handle.control_transfer(0x40, 0x90, 0, 0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Generic handle is null.");
+ }
+ }
+
+ SECTION("cannot read a pipe")
+ {
+ try
+ {
+ handle.read_pipe(0x82, NULL, 0, NULL);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Generic handle is null.");
+ }
+ }
+
+ SECTION("cannot open an asynchronous IN pipe")
+ {
+ try
+ {
+ handle.open_async_in_pipe(0x82);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Generic handle is null.");
+ }
+ }
+
+ SECTION("exports invalid underlying handles")
+ {
+#if defined(_WIN32)
+ REQUIRE(handle.get_winusb_handle() == INVALID_HANDLE_VALUE);
+#elif defined(__linux__)
+ REQUIRE(handle.get_fd() == -1);
+#elif defined(__APPLE__)
+ REQUIRE(handle.get_cf_plug_in() == NULL);
+#else
+ REQUIRE(0);
+#endif
+ }
+}
+
+TEST_CASE("generic handle parameter validation and corner cases")
+{
+ SECTION("libusbp_generic_handle_open")
+ {
+ SECTION("complains if the output pointer is null")
+ {
+ libusbp::error error(libusbp_generic_handle_open(NULL, NULL));
+ REQUIRE(error.message() == "Generic handle output pointer is null.");
+ }
+
+ SECTION("sets the output to NULL is possible")
+ {
+ libusbp_generic_handle * p = (libusbp_generic_handle *)1;
+ libusbp::error error(libusbp_generic_handle_open(NULL, &p));
+ REQUIRE((p == NULL));
+ }
+ }
+
+ SECTION("libusbp_generic_handle_open_async_in_pipe")
+ {
+ SECTION("complains if the output pointer is null")
+ {
+ libusbp::error error(libusbp_generic_handle_open_async_in_pipe(NULL, 0, NULL));
+ REQUIRE(error.message() == "Pipe output pointer is null.");
+ }
+
+ SECTION("sets the output pointer to NULL if possible")
+ {
+ libusbp_async_in_pipe * p = (libusbp_async_in_pipe *)1;
+ libusbp::error error(libusbp_generic_handle_open_async_in_pipe(NULL, 0, &p));
+ REQUIRE((p == NULL));
+ }
+ }
+}
+
+#ifdef USE_TEST_DEVICE_A
+TEST_CASE("generic_handle instance", "[ghitda]")
+{
+ libusbp::device device = find_test_device_a();
+ libusbp::generic_interface gi(device, 0, true);
+ libusbp::generic_handle handle(gi);
+
+ SECTION("is present")
+ {
+ REQUIRE(handle);
+ }
+
+ SECTION("is movable")
+ {
+ libusbp::generic_handle handle2 = std::move(handle);
+ REQUIRE(handle2);
+ REQUIRE_FALSE(handle);
+ }
+
+ SECTION("is move-assignable")
+ {
+ libusbp::generic_handle handle2;
+ handle2 = std::move(handle);
+ REQUIRE(handle2);
+ REQUIRE_FALSE(handle);
+ }
+
+ SECTION("can be closed")
+ {
+ handle.close();
+ REQUIRE_FALSE(handle);
+ }
+
+ SECTION("can export its underlying handles")
+ {
+#if defined(_WIN32)
+ REQUIRE((handle.get_winusb_handle() != NULL));
+ REQUIRE((handle.get_winusb_handle() != INVALID_HANDLE_VALUE));
+#elif defined(__linux__)
+ REQUIRE(handle.get_fd() > 2);
+#elif defined(__APPLE__)
+ IOCFPlugInInterface ** plug_in = (IOCFPlugInInterface **)handle.get_cf_plug_in();
+ REQUIRE(plug_in != NULL);
+ IOUSBInterfaceInterface197 ** ioh;
+ HRESULT hr = (*plug_in)->QueryInterface(plug_in,
+ CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID197), (void **)&ioh);
+ REQUIRE(!hr);
+ (*ioh)->Release(ioh);
+#else
+ REQUIRE(0); // this platform is missing a way to export its underlying handles
+#endif
+ }
+}
+
+TEST_CASE("generic_handle creation for Test Device A")
+{
+ libusbp::device device = find_test_device_a();
+ libusbp::generic_interface gi(device, 0, true);
+
+ SECTION("can be created and closed quickly several times")
+ {
+ for(unsigned int i = 0; i < 10; i++)
+ {
+ libusbp::generic_handle handle(gi);
+ }
+ }
+
+#if defined(_WIN32) || defined(__APPLE__)
+ SECTION("only one per interface can be open at a time")
+ {
+ libusbp::generic_handle handle1(gi);
+ try
+ {
+ libusbp::generic_handle handle2(gi);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ CHECK(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
+ }
+ }
+#else
+ SECTION("multiple handles to the same interface can be open at one time")
+ {
+ libusbp::generic_handle handle1(gi);
+ libusbp::generic_handle handle2(gi);
+ }
+#endif
+}
+#endif
+
+#ifdef USE_TEST_DEVICE_B
+TEST_CASE("generic_handle creation for Test Device B")
+{
+ libusbp::device device = find_test_device_b();
+ libusbp::generic_interface gi(device, 0, false);
+
+ SECTION("can be created and closed quickly several times")
+ {
+ for(unsigned int i = 0; i < 10; i++)
+ {
+ libusbp::generic_handle handle(gi);
+ }
+ }
+
+#if defined(_WIN32) || defined(__APPLE__)
+ SECTION("only one per interface can be open at a time")
+ {
+ libusbp::generic_handle handle1(gi);
+ try
+ {
+ libusbp::generic_handle handle2(gi);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ CHECK(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
+ }
+ }
+#else
+ SECTION("multiple handles to the same interface can be open at one time")
+ {
+ libusbp::generic_handle handle1(gi);
+ libusbp::generic_handle handle2(gi);
+ }
+#endif
+}
+#endif
diff --git a/dep/libusbp/test/generic_interface_test.cpp b/dep/libusbp/test/generic_interface_test.cpp
new file mode 100644
index 00000000..958fbe86
--- /dev/null
+++ b/dep/libusbp/test/generic_interface_test.cpp
@@ -0,0 +1,325 @@
+#include
+
+TEST_CASE("generic interface cannot be created from a NULL device")
+{
+ try
+ {
+ libusbp::device device;
+ libusbp::generic_interface gi(device, 0, true);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(std::string(error.what()) == "Device is null.");
+ }
+}
+
+static void check_null_gi_error(const libusbp::error & error)
+{
+ CHECK(error.message() == "Generic interface is null.");
+}
+
+TEST_CASE("null generic interface")
+{
+ libusbp::generic_interface gi;
+
+ SECTION("is not present")
+ {
+ REQUIRE_FALSE(gi);
+ }
+
+ SECTION("is copyable")
+ {
+ libusbp::generic_interface gi2 = gi;
+ REQUIRE_FALSE(gi2);
+ }
+
+ SECTION("get_os_id returns an error")
+ {
+ try
+ {
+ gi.get_os_id();
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ check_null_gi_error(error);
+ }
+ }
+
+ SECTION("get_os_filename returns an error")
+ {
+ try
+ {
+ gi.get_os_filename();
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ check_null_gi_error(error);
+ }
+ }
+}
+
+#ifdef USE_TEST_DEVICE_A
+TEST_CASE("generic interface parameter validation and corner cases")
+{
+ SECTION("libusbp_generic_interface_create")
+ {
+ SECTION("complains if the output pointer is NULL")
+ {
+ try
+ {
+ libusbp::throw_if_needed(libusbp_generic_interface_create(
+ NULL, 0, true, NULL));
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Generic interface output pointer is null.");
+ }
+ }
+
+ SECTION("sets the output to NULL if there is an error")
+ {
+ libusbp_generic_interface * ptr = (libusbp_generic_interface *)-1;
+ libusbp::error error(libusbp_generic_interface_create(NULL, 0, true, &ptr));
+ REQUIRE((ptr == NULL));
+ }
+ }
+
+ SECTION("libusbp_generic_interface_copy")
+ {
+ SECTION("complains if the output pointer is NULL")
+ {
+ try
+ {
+ libusbp::throw_if_needed(libusbp_generic_interface_copy(NULL, NULL));
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Generic interface output pointer is null.");
+ }
+ }
+
+ SECTION("sets the output pointer to NULL when copying NULL")
+ {
+ libusbp_generic_interface * gi = (libusbp_generic_interface *)-1;
+ libusbp::throw_if_needed(libusbp_generic_interface_copy(NULL, &gi));
+ REQUIRE((gi == NULL));
+ }
+ }
+
+ SECTION("libusbp_generic_interface_get_os_id")
+ {
+ SECTION("complains if the output pointer is NULL")
+ {
+ try
+ {
+ libusbp::throw_if_needed(libusbp_generic_interface_get_os_id(NULL, NULL));
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "String output pointer is null.");
+ }
+ }
+
+ SECTION("sets the output string to NULL if it can't return anything")
+ {
+ char * s = (char *)-1;
+ libusbp::error error(libusbp_generic_interface_get_os_id(NULL, &s));
+ REQUIRE((s == NULL));
+ }
+ }
+
+ SECTION("libusbp_generic_interface_get_os_filename")
+ {
+ SECTION("complains if the output pointer is NULL")
+ {
+ try
+ {
+ libusbp::throw_if_needed(
+ libusbp_generic_interface_get_os_filename(NULL, NULL));
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "String output pointer is null.");
+ }
+ }
+
+ SECTION("sets the output string to NULL if it can't return anything")
+ {
+ char * s = (char *)-1;
+ libusbp::error error(libusbp_generic_interface_get_os_filename(NULL, &s));
+ REQUIRE((s == NULL));
+ }
+ }
+}
+#endif
+
+#ifdef USE_TEST_DEVICE_A
+static void assert_not_ready(
+ const libusbp::device & device, uint8_t interface_number, bool composite)
+{
+ try
+ {
+ libusbp::generic_interface gi(device, interface_number, composite);
+ CHECK(gi);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ if (!error.has_code(LIBUSBP_ERROR_NOT_READY)) { throw; }
+ }
+}
+
+__attribute__((__unused__))
+static void assert_incorrect_driver(
+ const libusbp::device & device,
+ uint8_t interface_number,
+ bool composite,
+ std::string driver_name)
+{
+ std::string expected =
+ std::string("Failed to initialize generic interface. ") +
+ "Device is attached to an incorrect driver: "
+ + driver_name + ".";
+
+ try
+ {
+ libusbp::generic_interface gi(device, interface_number, composite);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ CHECK_FALSE(error.has_code(LIBUSBP_ERROR_NOT_READY));
+ CHECK(error.message() == expected);
+ }
+}
+
+#ifdef _WIN32
+static void assert_no_driver(
+ const libusbp::device & device, uint8_t interface_number, bool composite)
+{
+ try
+ {
+ libusbp::generic_interface gi(device, interface_number, composite);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ CHECK(error.has_code(LIBUSBP_ERROR_NOT_READY));
+ CHECK(std::string(error.what()) ==
+ "Failed to initialize generic interface. "
+ "Device is not using any driver.");
+ }
+}
+#endif
+#endif
+
+#ifdef USE_TEST_DEVICE_A
+TEST_CASE("Test Device A generic interface ", "[tdagi]")
+{
+ libusbp::device device = find_test_device_a();
+
+ SECTION("interface 0 with incorrect hint composite=false")
+ {
+ #ifdef _WIN32
+ // Causes a problem in Windows.
+ assert_incorrect_driver(device, 0, false, "usbccgp");
+ #else
+ // Works fine in Linux, which ignores that hint.
+ libusbp::generic_interface gi(device, 0, false);
+ #endif
+ }
+
+ SECTION("interface 0")
+ {
+ libusbp::generic_interface gi(device, 0, true);
+
+ SECTION("has a good OS id")
+ {
+ std::string id = gi.get_os_id();
+ #ifdef _WIN32
+ REQUIRE(id.substr(0, 12) == "USB\\VID_1FFB");
+ #elif defined(__linux__)
+ REQUIRE(id.substr(0, 13) == "/sys/devices/");
+ #else
+ REQUIRE(id.size() > 1);
+ for(size_t i = 0; i < id.size(); i++)
+ {
+ char c = id[i];
+ if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')))
+ {
+ throw id + " is a bad OS ID for a generic interface";
+ }
+ }
+ #endif
+ REQUIRE(id != device.get_os_id());
+ }
+
+ SECTION("has a good OS filename")
+ {
+ std::string filename = gi.get_os_filename();
+ #ifdef _WIN32
+ REQUIRE(filename.substr(0, 16) == "\\\\?\\usb#vid_1ffb");
+ #elif defined(__linux__)
+ REQUIRE(filename.substr(0, 13) == "/dev/bus/usb/");
+ #else
+ REQUIRE(filename.size() > 0);
+ for(size_t i = 0; i < filename.size(); i++)
+ {
+ char c = filename[i];
+ if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')))
+ {
+ throw filename + " is a bad OS ID for a generic interface";
+ }
+ }
+ #endif
+
+ REQUIRE(filename != device.get_os_id());
+ }
+ }
+
+ SECTION("interface 1 (native interface without Windows drivers)")
+ {
+ #ifdef _WIN32
+ assert_no_driver(device, 1, true);
+ #else
+ libusbp::generic_interface gi(device, 1, true);
+ #endif
+ }
+
+ SECTION("interface 2 (serial port)")
+ {
+ // See comments for libusbp_generic_interface_create for information
+ // about why these are different.
+ #if defined(_WIN32)
+ assert_incorrect_driver(device, 2, true, "usbser");
+ #elif defined(__linux__)
+ assert_incorrect_driver(device, 2, true, "cdc_acm");
+ #elif defined(__APPLE__)
+ libusbp::generic_interface(device, 2, true);
+ #else
+ REQUIRE(0);
+ #endif
+ }
+
+ SECTION("interface 44 (does not exist)")
+ {
+ assert_not_ready(device, 44, true);
+ }
+
+ SECTION("interfaces can be copied")
+ {
+ libusbp::generic_interface gi(device, 0, true);
+ libusbp::generic_interface gi2 = gi;
+ REQUIRE(gi2);
+ REQUIRE(gi.get_os_id() == gi2.get_os_id());
+ REQUIRE(gi.get_os_filename() == gi2.get_os_filename());
+ }
+}
+#endif
diff --git a/dep/libusbp/test/list_test.cpp b/dep/libusbp/test/list_test.cpp
new file mode 100644
index 00000000..d00eb7e6
--- /dev/null
+++ b/dep/libusbp/test/list_test.cpp
@@ -0,0 +1,99 @@
+/* Tests the functions we provide for finding/listing devices. */
+
+#include
+
+TEST_CASE("list_connected_device (C++)")
+{
+ #ifdef USE_TEST_DEVICE_A
+ SECTION("can find Test Device A")
+ {
+ libusbp::device found_device;
+ std::vector list = libusbp::list_connected_devices();
+ for (auto it = list.begin(); it != list.end(); ++it)
+ {
+ libusbp::device device = *it;
+ if (!device)
+ {
+ throw "A null device was returned by list_connected_devices.";
+ }
+ if (device.get_vendor_id() == 0x1FFB && device.get_product_id() == 0xDA01)
+ {
+ return;
+ }
+ }
+ throw "Test Device A not found.";
+ }
+ #endif
+}
+
+TEST_CASE("list_connected_devices (C)")
+{
+ libusbp_device ** list = NULL;
+ size_t device_count = 4444;
+
+ SECTION("gives a null-terminated list")
+ {
+ libusbp_error * error = libusbp_list_connected_devices(&list, &device_count);
+ if (error != NULL) { throw libusbp::error(error); }
+ for (size_t i = 0; i < device_count; i++)
+ {
+ CHECK(list[i]);
+ }
+ CHECK_FALSE(list[device_count]);
+ }
+
+ SECTION("does not crash if called with a NULL device_list argument")
+ {
+ libusbp::error error(libusbp_list_connected_devices(NULL, &device_count));
+ REQUIRE(error.message() == "Device list output pointer is null.");
+ REQUIRE(device_count == 0);
+ }
+
+ SECTION("does not complain if called with a NULL device_count argument")
+ {
+ libusbp_error * error = libusbp_list_connected_devices(&list, NULL);
+ if (error != NULL) { throw libusbp::error(error); }
+ }
+
+ if (list != NULL)
+ {
+ libusbp_device ** device = list;
+ while(*device)
+ {
+ libusbp_device_free(*device);
+ device++;
+ }
+ libusbp_list_free(list);
+ }
+}
+
+TEST_CASE("find_device_with_vid_pid (C++)")
+{
+ #ifdef USE_TEST_DEVICE_A
+ SECTION("can find Test Device A")
+ {
+ libusbp::device device = libusbp::find_device_with_vid_pid(0x1FFB, 0xDA01);
+ REQUIRE(device);
+ REQUIRE(device.get_vendor_id() == 0x1FFB);
+ REQUIRE(device.get_product_id() == 0xDA01);
+ }
+ #endif
+
+ SECTION("returns a null device if the specified one is not found")
+ {
+ // It's not an error when a device you are looking for isn't connected
+ // to the system, it's normal and expected, and you should be checking
+ // for it and handling it explicitly.
+ libusbp::device device = libusbp::find_device_with_vid_pid(0xABCD, 0x1234);
+ REQUIRE_FALSE(device);
+ }
+}
+
+TEST_CASE("find_device_with_vid_pid (C)")
+{
+ SECTION("complains if the output pointer is NULL")
+ {
+ libusbp::error error(libusbp_find_device_with_vid_pid(0xABCD, 0x1234, NULL));
+ REQUIRE(error.message() == "Device output pointer is null.");
+ }
+}
diff --git a/dep/libusbp/test/main_test.cpp b/dep/libusbp/test/main_test.cpp
new file mode 100644
index 00000000..d0395ee4
--- /dev/null
+++ b/dep/libusbp/test/main_test.cpp
@@ -0,0 +1,39 @@
+#define CATCH_CONFIG_RUNNER
+#include
+
+int main (int argc, char ** argv)
+{
+ #ifndef USE_TEST_DEVICE_A
+ std::cerr << "Warning: Tests involving Test Device A will be skipped," << std::endl;
+ std::cerr << " so a lot of the library's features will not be tested." << std::endl;
+ #endif
+
+ #ifndef USE_TEST_DEVICE_B
+ std::cerr << "Warning: Tests involving Test Device B will be skipped," << std::endl;
+ std::cerr << " so bugs related to non-composite devices might be missed." << std::endl;
+ #endif
+
+ #ifdef NDEBUG
+ std::cerr << "Warning: skipping unit tests because this is not a debug build.\n";
+ #endif
+
+ // If the last argument is "-p", then pause after the tests are run.
+ // This allows us to run "leaks" on Mac OS X to check for memory leaks.
+ bool pause_after_test = false;
+ if (argc && std::string(argv[argc - 1]) == "-p")
+ {
+ pause_after_test = true;
+ argc--;
+ }
+
+ int result = Catch::Session().run(argc, argv);
+
+ if (pause_after_test)
+ {
+ printf("Press enter to continue.");
+ std::string s;
+ std::cin >> s;
+ }
+
+ return result;
+}
diff --git a/dep/libusbp/test/read_pipe_test.cpp b/dep/libusbp/test/read_pipe_test.cpp
new file mode 100644
index 00000000..ea3dbdfb
--- /dev/null
+++ b/dep/libusbp/test/read_pipe_test.cpp
@@ -0,0 +1,256 @@
+#include
+
+#ifdef USE_TEST_DEVICE_A
+TEST_CASE("read_pipe parameter checking")
+{
+ libusbp::device device = find_test_device_a();
+ libusbp::generic_interface gi(device, 0, true);
+ libusbp::generic_handle handle(gi);
+ const uint8_t pipe = 0x82;
+ size_t transferred = 0xFFFF;
+
+ SECTION("sets transferred to zero if possible")
+ {
+ size_t transferred = 1;
+ libusbp::error error(libusbp_read_pipe(NULL,
+ 0, NULL, 0, &transferred));
+ REQUIRE(transferred == 0);
+ }
+
+ SECTION("requires the size to be non-zero")
+ {
+ // The corner case of the transfer size being zero seems to put the USB
+ // drivers in Linux in a weird state, so let's just not allow it.
+ try
+ {
+ handle.read_pipe(pipe, NULL, 0, &transferred);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(std::string(error.what()) ==
+ "Failed to read from pipe. "
+ "Transfer size 0 is not allowed.");
+ REQUIRE(transferred == 0);
+ }
+ }
+
+ SECTION("requires the buffer to be non-NULL")
+ {
+ try
+ {
+ handle.read_pipe(pipe, NULL, 2, &transferred);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(std::string(error.what()) ==
+ "Failed to read from pipe. "
+ "Buffer is null.");
+ REQUIRE(transferred == 0);
+ }
+ }
+
+ SECTION("requires the direction bit to be correct")
+ {
+ try
+ {
+ uint8_t buffer[5];
+ handle.read_pipe(0x02, buffer, sizeof(buffer), &transferred);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(std::string(error.what()) ==
+ "Failed to read from pipe. "
+ "Invalid pipe ID 0x02.");
+ REQUIRE(transferred == 0);
+ }
+ }
+
+ SECTION("checks the size")
+ {
+ uint8_t buffer[5];
+
+ #if defined(_WIN32)
+ size_t too_large_size = (size_t)ULONG_MAX + 1;
+ #elif defined(__linux__)
+ size_t too_large_size = (size_t)UINT_MAX + 1;
+ #elif defined(__APPLE__)
+ size_t too_large_size = (size_t)UINT32_MAX + 1;
+ #else
+ #error add a case for this OS
+ #endif
+
+ if (too_large_size == 0) { return; }
+
+ try
+ {
+ handle.read_pipe(pipe, buffer, too_large_size, NULL);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Failed to read from pipe. Transfer size is too large.");
+ }
+ }
+}
+
+TEST_CASE("read_pipe (synchronous) on an interrupt endpoint ", "[rpi]")
+{
+ // We assume that if read_pipe works on an interrupt endpoint, it will also
+ // work on a bulk endpoint because of the details of the underlying APIs
+ // that libusbp uses.
+
+ libusbp::device device = find_test_device_a();
+ libusbp::generic_interface gi(device, 0, true);
+ libusbp::generic_handle handle(gi);
+ const uint8_t pipe = 0x82;
+ size_t transferred = 0xFFFF;
+ const size_t packet_size = 5;
+
+ // Unpause the ADC if it was paused by some previous test.
+ handle.control_transfer(0x40, 0xA0, 0, 0);
+
+ SECTION("can read one packet")
+ {
+ uint8_t buffer[packet_size];
+ handle.read_pipe(pipe, buffer, sizeof(buffer), &transferred);
+ REQUIRE(transferred == packet_size);
+ REQUIRE(buffer[4] == 0xAB);
+ }
+
+ SECTION("can read two packets")
+ {
+ uint8_t buffer[packet_size * 2];
+ handle.read_pipe(pipe, buffer, sizeof(buffer), &transferred);
+ REQUIRE(transferred == packet_size * 2);
+ REQUIRE(buffer[4] == 0xAB);
+ REQUIRE(buffer[4 + packet_size] == 0xAB);
+ }
+
+ SECTION("can read without returning the size transferred")
+ {
+ // But this is bad; you should be checking how many bytes are
+ // transferred.
+ uint8_t buffer[packet_size];
+ handle.read_pipe(pipe, buffer, sizeof(buffer), NULL);
+ REQUIRE(buffer[4] == 0xAB);
+ }
+
+ SECTION("overflows when transfer size is not a multiple of the packet size")
+ {
+ #ifdef VBOX_LINUX_ON_WINDOWS
+ // This test fails and then puts the USB device into a weird state
+ // if run on Linux inside VirtualBox on a Windows host.
+ std::cerr << "Skipping synchronous IN pipe overflow test.\n";
+ return;
+ #endif
+
+ uint8_t buffer[packet_size + 1];
+ size_t transferred;
+ try
+ {
+ handle.read_pipe(pipe, buffer, sizeof(buffer), &transferred);
+ }
+ catch(const libusbp::error & error)
+ {
+ const char * expected =
+ "Failed to read from pipe. "
+ "The transfer overflowed. "
+ #if defined(_WIN32)
+ #elif defined(__linux__)
+ "Error code 75."
+ #elif defined(__APPLE__)
+ "Error code 0xe00002e8."
+ #endif
+ ;
+ REQUIRE(error.message() == expected);
+
+ #ifdef __APPLE__
+ REQUIRE(transferred == packet_size + 1);
+ #else
+ REQUIRE(transferred == 0);
+ #endif
+ }
+ }
+
+
+ #ifdef __APPLE__
+ SECTION("does not have an adjustable timeout for interrupt endpoints")
+ {
+ handle.set_timeout(pipe, 1);
+
+ uint8_t buffer[packet_size];
+ try
+ {
+ handle.read_pipe(pipe, buffer, sizeof(buffer), NULL);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ // A copy of this error message is in the README, so be sure
+ // to update the README if you have to update this.
+ REQUIRE(error.message() == "Failed to read from pipe. "
+ "(iokit/common) invalid argument. Error code 0xe00002c2.");
+ }
+ }
+ #else
+ SECTION("has an adjustable timeout")
+ {
+ // Pause the ADC for 50 ms, then try to read data from it with a 1 ms
+ // timeout and observe that it fails.
+
+ handle.set_timeout(pipe, 1);
+ handle.control_transfer(0x40, 0xA0, 50, 0);
+
+ uint8_t buffer[packet_size];
+ try
+ {
+ // We use two extra reads to clear out packets that might have been
+ // queued on the device before we stopped the pipe.
+ handle.read_pipe(pipe, buffer, sizeof(buffer), &transferred);
+ handle.read_pipe(pipe, buffer, sizeof(buffer), &transferred);
+
+ // This last read will hopefully time out.
+ handle.read_pipe(pipe, buffer, sizeof(buffer), &transferred);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ const char * expected =
+ "Failed to read from pipe. "
+ "The operation timed out. "
+ #ifdef _WIN32
+ "Windows error code 0x79."
+ #elif defined(__linux__)
+ "Error code 110."
+ #endif
+ ;
+ REQUIRE(std::string(error.what()) == expected);
+ REQUIRE(error.has_code(LIBUSBP_ERROR_TIMEOUT));
+
+ #ifdef _WIN32
+ // Sometimes WinUSB transfers 5 bytes successfully but it still
+ // reports a timeout. This seems to depend on what computer you are
+ // running the tests on; on some computers it happens about half the
+ // time, and on some computers it never happens. When this was
+ // first observed, libusbp was not using RAW_IO mode.
+ REQUIRE((transferred == 0 || transferred == 5));
+ #else
+ REQUIRE(transferred == 0);
+ #endif
+ }
+
+ // Do the same thing with a higher timeout and observe that it works.
+ handle.set_timeout(pipe, 500);
+ handle.control_transfer(0x40, 0xA0, 50, 0);
+ for (uint8_t i = 0; i < 3; i++)
+ {
+ handle.read_pipe(pipe, buffer, sizeof(buffer), &transferred);
+ REQUIRE(transferred == 5);
+ }
+ }
+ #endif
+}
+#endif
diff --git a/dep/libusbp/test/serial_port_test.cpp b/dep/libusbp/test/serial_port_test.cpp
new file mode 100644
index 00000000..948ebb67
--- /dev/null
+++ b/dep/libusbp/test/serial_port_test.cpp
@@ -0,0 +1,181 @@
+#include
+
+TEST_CASE("serial port cannot be created from a NULL device")
+{
+ try
+ {
+ libusbp::device device;
+ libusbp::serial_port sp(device, 0, true);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Device is null.");
+ }
+}
+
+static void check_null_sp_error(const libusbp::error & error)
+{
+ CHECK(error.message() == "Serial port is null.");
+}
+
+TEST_CASE("null serial port")
+{
+ libusbp::serial_port sp;
+
+ SECTION("is not present")
+ {
+ REQUIRE_FALSE(sp);
+ }
+
+ SECTION("is copyable")
+ {
+ libusbp::serial_port sp2 = sp;
+ REQUIRE_FALSE(sp2);
+ }
+
+ SECTION("get_name returns an error")
+ {
+ try
+ {
+ sp.get_name();
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ check_null_sp_error(error);
+ }
+ }
+}
+
+TEST_CASE("serial port parameter validation and corner cases")
+{
+ SECTION("libusbp_serial_port_create")
+ {
+ SECTION("complains if the output pointer is NULL")
+ {
+ try
+ {
+ libusbp::throw_if_needed(libusbp_serial_port_create(
+ NULL, 0, true, NULL));
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Serial port output pointer is null.");
+ }
+ }
+
+ SECTION("sets the output to NULL if there is an error")
+ {
+ libusbp_serial_port * ptr = (libusbp_serial_port *)-1;
+ libusbp::error error(libusbp_serial_port_create(NULL, 0, true, &ptr));
+ REQUIRE((ptr == NULL));
+ }
+ }
+
+ SECTION("libusbp_serial_port_copy")
+ {
+ SECTION("complains if the output pointer is NULL")
+ {
+ try
+ {
+ libusbp::throw_if_needed(libusbp_serial_port_copy(NULL, NULL));
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Serial port output pointer is null.");
+ }
+ }
+
+ SECTION("sets the output pointer to NULL when copying NULL")
+ {
+ libusbp_serial_port * sp = (libusbp_serial_port *)-1;
+ libusbp::throw_if_needed(libusbp_serial_port_copy(NULL, &sp));
+ REQUIRE((sp == NULL));
+ }
+ }
+
+ SECTION("libusbp_serial_port_get_name")
+ {
+ SECTION("complains if the output pointer is NULL")
+ {
+ try
+ {
+ libusbp::throw_if_needed(libusbp_serial_port_get_name(NULL, NULL));
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "String output pointer is null.");
+ }
+ }
+
+ SECTION("sets the output string to NULL if it can't return anything")
+ {
+ char * s = (char *)-1;
+ libusbp::error error(libusbp_serial_port_get_name(NULL, &s));
+ REQUIRE((s == NULL));
+ }
+ }
+}
+
+#ifdef USE_TEST_DEVICE_A
+TEST_CASE("test device A serial port")
+{
+ libusbp::device device = find_test_device_a();
+
+ SECTION("interface 2")
+ {
+ libusbp::serial_port sp(device, 2, true);
+
+ SECTION("has a name that looks good")
+ {
+ std::string name = sp.get_name();
+
+ #if defined(_WIN32)
+ REQUIRE(name.substr(0, 3) == "COM");
+ REQUIRE(name.size() >= 4);
+ #elif defined(__linux__)
+ REQUIRE(name.substr(0, 11) == "/dev/ttyACM");
+ REQUIRE(name.size() >= 11);
+ #elif defined(__APPLE__)
+ REQUIRE(name.substr(0, 16) == "/dev/cu.usbmodem");
+ REQUIRE(name.size() >= 17);
+ #else
+ REQUIRE(0);
+ #endif
+ }
+
+ }
+
+ SECTION("incorrect parameters interface=0 and composite=false")
+ {
+ try
+ {
+ libusbp::serial_port sp(device, 0, true);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ #if defined(_WIN32)
+ REQUIRE(error.message() == "The PortName key was not found.");
+ #elif defined(__linux__)
+ REQUIRE(error.message() == "Could not find tty device.");
+ #elif defined(__APPLE__)
+ REQUIRE(error.message() == "Could not find entry with class IOSerialBSDClient.");
+ #else
+ REQUIRE(error.message() == "?");
+ #endif
+ }
+ }
+
+ SECTION("serial ports can be copied")
+ {
+ libusbp::serial_port sp(device, 2, true);
+ libusbp::serial_port sp2 = sp;
+ REQUIRE(sp2.get_name() == sp.get_name());
+ }
+}
+#endif
diff --git a/dep/libusbp/test/test_helper.cpp b/dep/libusbp/test/test_helper.cpp
new file mode 100644
index 00000000..c4c922ab
--- /dev/null
+++ b/dep/libusbp/test/test_helper.cpp
@@ -0,0 +1,25 @@
+#include
+
+libusbp::device find_by_vendor_id_product_id(uint16_t vendor_id, uint16_t product_id)
+{
+ std::vector list = libusbp::list_connected_devices();
+ for (auto it = list.begin(); it != list.end(); ++it)
+ {
+ libusbp::device device = *it;
+ if (device.get_vendor_id() == vendor_id && device.get_product_id() == product_id)
+ {
+ return device;
+ }
+ }
+ throw "Device not found.";
+}
+
+libusbp::device find_test_device_a()
+{
+ return find_by_vendor_id_product_id(0x1FFB, 0xDA01);
+}
+
+libusbp::device find_test_device_b()
+{
+ return find_by_vendor_id_product_id(0x1FFB, 0xDA02);
+}
diff --git a/dep/libusbp/test/test_helper.h b/dep/libusbp/test/test_helper.h
new file mode 100644
index 00000000..71833543
--- /dev/null
+++ b/dep/libusbp/test/test_helper.h
@@ -0,0 +1,78 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+extern "C"
+{
+ #include
+}
+
+libusbp::device find_by_vendor_id_product_id(uint16_t vendor_id, uint16_t product_id);
+libusbp::device find_test_device_a();
+libusbp::device find_test_device_b();
+
+class test_timeout
+{
+ // monotonic_clock was renamed to steady_clock, but GCC 4.6 only defines
+ // monotonic_clock, and later versions only define steady_clock.
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 6
+ typedef std::chrono::monotonic_clock clock;
+#else
+ typedef std::chrono::steady_clock clock;
+#endif
+
+public:
+ test_timeout(uint32_t timeout_ms)
+ {
+ start = clock::now();
+ this->timeout_ms = timeout_ms;
+ }
+
+ void check()
+ {
+ if (get_milliseconds() > timeout_ms)
+ {
+ throw "Timeout";
+ }
+ }
+
+ uint32_t get_milliseconds()
+ {
+ clock::time_point now = clock::now();
+ return std::chrono::duration_cast(now - start).count();
+ }
+
+private:
+ uint32_t timeout_ms;
+ clock::time_point start;
+};
+
+#ifdef _WIN32
+void inline sleep_quick()
+{
+ Sleep(1);
+}
+
+void inline sleep_ms(uint32_t ms)
+{
+ Sleep(ms);
+}
+#else
+void inline sleep_quick()
+{
+ usleep(100);
+}
+
+void inline sleep_ms(uint32_t ms)
+{
+ usleep((useconds_t)1000 * ms);
+}
+#endif
+
+inline libusbp::error get_some_error()
+{
+ return libusbp::error(libusbp_device_copy(NULL, NULL));
+}
diff --git a/dep/libusbp/test/usbfd_test.cpp b/dep/libusbp/test/usbfd_test.cpp
new file mode 100644
index 00000000..2cef4904
--- /dev/null
+++ b/dep/libusbp/test/usbfd_test.cpp
@@ -0,0 +1,20 @@
+#include
+
+#if defined(__linux__) && !defined(NDEBUG)
+
+TEST_CASE("usbfd_check_existence")
+{
+ SECTION("gives a nice error if the file does not exist")
+ {
+ libusbp::error error(usbfd_check_existence("doesnt-exist-%%%"));
+ REQUIRE(error.has_code(LIBUSBP_ERROR_NOT_READY));
+ }
+
+ SECTION("gives a nice error if part of the file path does not exist")
+ {
+ libusbp::error error(usbfd_check_existence("doesnt-exist-%%%/xx"));
+ REQUIRE(error.has_code(LIBUSBP_ERROR_NOT_READY));
+ }
+}
+
+#endif
diff --git a/dep/libusbp/test/windows_test.cpp b/dep/libusbp/test/windows_test.cpp
new file mode 100644
index 00000000..a86db105
--- /dev/null
+++ b/dep/libusbp/test/windows_test.cpp
@@ -0,0 +1,41 @@
+#include
+
+#ifdef _WIN32
+
+TEST_CASE("CM_Locate_DevNode", "[cmld]")
+{
+ CONFIGRET cr;
+ DEVINST dev_inst;
+
+ SECTION("totally invalid id")
+ {
+ char id[] = "totally invalid";
+ cr = CM_Locate_DevNode(&dev_inst, id, CM_LOCATE_DEVNODE_NORMAL);
+ REQUIRE(cr == CR_INVALID_DEVICE_ID);
+ }
+
+ SECTION("non-existent ID")
+ {
+ char id[] = "USB\\VID_1FFB&PID_0001\\01eeca1";
+ cr = CM_Locate_DevNode(&dev_inst, id, CM_LOCATE_DEVNODE_NORMAL);
+ REQUIRE(cr == CR_NO_SUCH_DEVNODE);
+ }
+
+ SECTION("unplugged USB device")
+ {
+ // To make this test do something, set the id below to the
+ // device instance ID of a USB device that is currently
+ // unplugged from your computer, but was plugged in at one
+ // point. Also, uncomment the early return.
+
+ char id[] = "USB\\VID_1FFB&PID_0100\\6E-D7-65-51";
+ return;
+
+ cr = CM_Locate_DevNode(&dev_inst, id, CM_LOCATE_DEVNODE_NORMAL);
+ CHECK(cr == CR_NO_SUCH_DEVNODE);
+ cr = CM_Locate_DevNode(&dev_inst, id, CM_LOCATE_DEVNODE_PHANTOM);
+ CHECK(cr == CR_SUCCESS);
+ }
+}
+
+#endif
diff --git a/dep/libusbp/test/write_pipe_test.cpp b/dep/libusbp/test/write_pipe_test.cpp
new file mode 100644
index 00000000..a47cacae
--- /dev/null
+++ b/dep/libusbp/test/write_pipe_test.cpp
@@ -0,0 +1,156 @@
+#include
+
+#ifdef USE_TEST_DEVICE_A
+TEST_CASE("write_pipe parameter checking")
+{
+ libusbp::device device = find_test_device_a();
+ libusbp::generic_interface gi(device, 0, true);
+ libusbp::generic_handle handle(gi);
+ const uint8_t pipe = 0x03;
+ size_t transferred = 0xFFFF;
+
+ SECTION("sets transferred to zero if possible")
+ {
+ size_t transferred = 1;
+ libusbp::error error(libusbp_write_pipe(NULL,
+ 0, NULL, 0, &transferred));
+ REQUIRE(transferred == 0);
+ }
+
+ SECTION("requires the buffer to be non-NULL")
+ {
+ try
+ {
+ handle.write_pipe(pipe, NULL, 2, &transferred);
+ REQUIRE(0);
+ }
+ catch(const libusbp::error & error)
+ {
+ REQUIRE(std::string(error.what()) ==
+ "Failed to write to pipe. "
+ "Buffer is null.");
+ REQUIRE(transferred == 0);
+ }
+ }
+
+ SECTION("requires the direction bit to be correct")
+ {
+ try
+ {
+ uint8_t buffer[5];
+ handle.write_pipe(0x83, buffer, sizeof(buffer), &transferred);
+ REQUIRE(0);
+ }
+ catch (const libusbp::error & error)
+ {
+ REQUIRE(std::string(error.what()) ==
+ "Failed to write to pipe. "
+ "Invalid pipe ID 0x83.");
+ REQUIRE(transferred == 0);
+ }
+ }
+
+ SECTION("checks the size")
+ {
+ uint8_t buffer[5];
+
+ #if defined(_WIN32)
+ size_t too_large_size = (size_t)ULONG_MAX + 1;
+ #elif defined(__linux__)
+ size_t too_large_size = (size_t)UINT_MAX + 1;
+ #elif defined(__APPLE__)
+ size_t too_large_size = (size_t)UINT32_MAX + 1;
+ #else
+ #error add a case for this OS
+ #endif
+
+ if (too_large_size == 0) { return; }
+
+ try
+ {
+ handle.write_pipe(pipe, buffer, too_large_size, NULL);
+ REQUIRE(0);
+ }
+ catch (const libusbp::error & error)
+ {
+ REQUIRE(error.message() == "Failed to write to pipe. "
+ "Transfer size is too large.");
+ }
+ }
+}
+
+TEST_CASE("write_pipe (synchronous) on a bulk endpoint ", "[wpi]")
+{
+ // We assume that if write_pipe works on a bulk endpoint, it will also
+ // work on an interrupt endpoint because of the details of the underlying
+ // APIs that libusbp uses.
+
+ libusbp::device device = find_test_device_a();
+ libusbp::generic_interface gi(device, 0, true);
+ libusbp::generic_handle handle(gi);
+ const uint8_t pipe = 0x03;
+ handle.set_timeout(pipe, 100);
+ size_t transferred = 0xFFFF;
+
+ SECTION("can write one small packet")
+ {
+ uint8_t buffer[32] = { 0x92, 0x44 };
+ handle.write_pipe(pipe, buffer, sizeof(buffer), &transferred);
+ REQUIRE(transferred == sizeof(buffer));
+
+ // Read the data back.
+ uint8_t buffer2[1];
+ handle.control_transfer(0xC0, 0x91, 0, 1, buffer2, 1, &transferred);
+ REQUIRE(transferred == 1);
+ REQUIRE(buffer2[0] == 0x44);
+ }
+
+ SECTION("can write one packet with null transferred pointer")
+ {
+ uint8_t buffer[32] = { 0x92, 0x55 };
+ handle.write_pipe(pipe, buffer, sizeof(buffer), NULL);
+
+ // Read the data back.
+ uint8_t buffer2[1];
+ handle.control_transfer(0xC0, 0x91, 0, 1, buffer2, 1, &transferred);
+ REQUIRE(transferred == 1);
+ REQUIRE(buffer2[0] == 0x55);
+
+ // Reading back 0x55 confirms that we sent the packet and confirms
+ // we did not send any zero-length packets at the end.
+ }
+
+ SECTION("can send zero-length trasnfers")
+ {
+ handle.write_pipe(pipe, NULL, 0, NULL);
+
+ // Expect dataBuffer to contain 0x66
+ uint8_t buffer2[1];
+ handle.control_transfer(0xC0, 0x91, 0, 1, buffer2, 1, &transferred);
+ REQUIRE(transferred == 1);
+ REQUIRE(buffer2[0] == 0x66);
+ }
+
+ SECTION("can time out")
+ {
+ // First packet causes a long delay, so this transfer will timeout after
+ // a partial data transfer. Need three packets because of double
+ // buffering on the device.
+ uint8_t buffer[32 * 3] = { 0xDE, 150, 0 };
+ try
+ {
+ handle.write_pipe(pipe, buffer, sizeof(buffer), &transferred);
+ REQUIRE(0);
+ }
+ catch (const libusbp::error & e)
+ {
+ REQUIRE(e.has_code(LIBUSBP_ERROR_TIMEOUT));
+ #if defined(__linux__) || defined(__APPLE__)
+ REQUIRE(transferred == 0); // bad
+ #else
+ REQUIRE(transferred == 64);
+ #endif
+ }
+ }
+}
+#endif
diff --git a/lib/usb/fluxengineusb.cc b/lib/usb/fluxengineusb.cc
index af9a570e..d40c08ee 100644
--- a/lib/usb/fluxengineusb.cc
+++ b/lib/usb/fluxengineusb.cc
@@ -3,10 +3,11 @@
#include "protocol.h"
#include "fluxmap.h"
#include "bytes.h"
-#include
+#include "libusbp_config.h"
+#include "libusbp.hpp"
#include "fmt/format.h"
-#define TIMEOUT 5000
+#define MAX_TRANSFER (32*1024)
/* Hacky: the board always operates in little-endian mode. */
static uint16_t read_short_from_usb(uint16_t usb)
@@ -20,59 +21,55 @@ class FluxEngineUsb : public USB
private:
uint8_t _buffer[FRAME_SIZE];
- int usb_cmd_send(void* ptr, int len)
+ void usb_cmd_send(void* ptr, size_t len)
{
- //std::cerr << "send:\n";
- //hexdump(std::cerr, Bytes((const uint8_t*)ptr, len));
- int i = libusb_interrupt_transfer(_device, FLUXENGINE_CMD_OUT_EP,
- (uint8_t*) ptr, len, &len, TIMEOUT);
- if (i < 0)
- Error() << "failed to send command: " << usberror(i);
- return len;
+ size_t rlen;
+ _handle.write_pipe(FLUXENGINE_CMD_OUT_EP, ptr, len, &rlen);
}
- void usb_cmd_recv(void* ptr, int len)
+ void usb_cmd_recv(void* ptr, size_t len)
{
- int i = libusb_interrupt_transfer(_device, FLUXENGINE_CMD_IN_EP,
- (uint8_t*) ptr, len, &len, TIMEOUT);
- if (i < 0)
- Error() << "failed to receive command reply: " << usberror(i);
- //std::cerr << "recv:\n";
- //hexdump(std::cerr, Bytes((const uint8_t*)ptr, len));
+ size_t rlen;
+ _handle.read_pipe(FLUXENGINE_CMD_IN_EP, ptr, len, &rlen);
}
- int large_bulk_transfer(int ep, Bytes& bytes)
+ void usb_data_send(const Bytes& bytes)
{
- if (bytes.size() == 0)
- return 0;
-
- int len;
- int i = libusb_bulk_transfer(_device, ep, bytes.begin(), bytes.size(), &len, TIMEOUT);
- if (i < 0)
- Error() << fmt::format("data transfer failed at {} bytes: {}", len, usberror(i));
- return len;
- }
-
-public:
- FluxEngineUsb(libusb_device* device)
- {
- int i = libusb_open(device, &_device);
- if (i < 0)
- Error() << "cannot open USB device: " << libusb_strerror((libusb_error) i);
-
- int cfg = -1;
- libusb_get_configuration(_device, &cfg);
- if (cfg != 1)
+ size_t ptr = 0;
+ while (ptr < bytes.size())
{
- i = libusb_set_configuration(_device, 1);
- if (i < 0)
- Error() << "the FluxEngine would not accept configuration: " << usberror(i);
+ size_t rlen = bytes.size() - ptr;
+ if (rlen > MAX_TRANSFER)
+ rlen = MAX_TRANSFER;
+ _handle.write_pipe(FLUXENGINE_DATA_OUT_EP, bytes.cbegin() + ptr, rlen, &rlen);
+ ptr += rlen;
+ }
+ }
+
+ void usb_data_recv(Bytes& bytes)
+ {
+ size_t ptr = 0;
+ while (ptr < bytes.size())
+ {
+ size_t rlen = bytes.size() - ptr;
+ if (rlen > MAX_TRANSFER)
+ rlen = MAX_TRANSFER;
+ _handle.read_pipe(FLUXENGINE_DATA_IN_EP, bytes.begin() + ptr, rlen, &rlen);
+ ptr += rlen;
+ if (rlen < MAX_TRANSFER)
+ break;
}
- i = libusb_claim_interface(_device, 0);
- if (i < 0)
- Error() << "could not claim interface: " << usberror(i);
+ bytes.resize(ptr);
+ }
+
+public:
+ FluxEngineUsb(libusbp::device& device):
+ _device(device),
+ _interface(_device, 0, false),
+ _handle(_interface)
+ {
int version = getVersion();
if (version != FLUXENGINE_VERSION)
Error() << "your FluxEngine firmware is at version " << version
@@ -80,6 +77,11 @@ public:
<< "; please upgrade";
}
+private:
+ libusbp::device _device;
+ libusbp::generic_interface _interface;
+ libusbp::generic_handle _handle;
+
private:
void bad_reply(void)
{
@@ -167,14 +169,15 @@ public:
const int YSIZE = 256;
const int ZSIZE = 64;
+ std::cout << "Reading data: " << std::flush;
Bytes bulk_buffer(XSIZE*YSIZE*ZSIZE);
double start_time = getCurrentTime();
- large_bulk_transfer(FLUXENGINE_DATA_IN_EP, bulk_buffer);
+ usb_data_recv(bulk_buffer);
double elapsed_time = getCurrentTime() - start_time;
- std::cout << "Transferred "
+ std::cout << "transferred "
<< bulk_buffer.size()
- << " bytes from FluxEngine -> PC in "
+ << " bytes from device -> PC in "
<< int(elapsed_time * 1000.0)
<< " ms ("
<< int((bulk_buffer.size() / 1024.0) / elapsed_time)
@@ -215,13 +218,14 @@ public:
bulk_buffer[offset] = uint8_t(x+y+z);
}
+ std::cout << "Writing data: " << std::flush;
double start_time = getCurrentTime();
- large_bulk_transfer(FLUXENGINE_DATA_OUT_EP, bulk_buffer);
+ usb_data_send(bulk_buffer);
double elapsed_time = getCurrentTime() - start_time;
- std::cout << "Transferred "
+ std::cout << "transferred "
<< bulk_buffer.size()
- << " bytes from PC -> FluxEngine in "
+ << " bytes from PC -> device in "
<< int(elapsed_time * 1000.0)
<< " ms ("
<< int((bulk_buffer.size() / 1024.0) / elapsed_time)
@@ -248,8 +252,7 @@ public:
auto fluxmap = std::unique_ptr(new Fluxmap);
Bytes buffer(1024*1024);
- int len = large_bulk_transfer(FLUXENGINE_DATA_IN_EP, buffer);
- buffer.resize(len);
+ usb_data_recv(buffer);
await_reply(F_FRAME_READ_REPLY);
return buffer;
@@ -271,7 +274,7 @@ public:
((uint8_t*)&f.bytes_to_write)[3] = safelen >> 24;
usb_cmd_send(&f, f.f.size);
- large_bulk_transfer(FLUXENGINE_DATA_OUT_EP, safeBytes);
+ usb_data_send(safeBytes);
await_reply(F_FRAME_WRITE_REPLY);
}
@@ -327,7 +330,7 @@ public:
}
};
-USB* createFluxengineUsb(libusb_device* device)
+USB* createFluxengineUsb(libusbp::device& device)
{
return new FluxEngineUsb(device);
}
diff --git a/lib/usb/usb.cc b/lib/usb/usb.cc
index e0af9b0d..1b0c25d0 100644
--- a/lib/usb/usb.cc
+++ b/lib/usb/usb.cc
@@ -6,7 +6,6 @@
#include "bytes.h"
#include "proto.h"
#include "usbfinder.h"
-#include
#include "fmt/format.h"
static USB* usb = NULL;
@@ -14,11 +13,6 @@ static USB* usb = NULL;
USB::~USB()
{}
-std::string USB::usberror(int i)
-{
- return libusb_strerror((libusb_error) i);
-}
-
USB* get_usb_impl()
{
switch (config.usb().device_case())
diff --git a/lib/usb/usb.h b/lib/usb/usb.h
index 6e283f8a..28605770 100644
--- a/lib/usb/usb.h
+++ b/lib/usb/usb.h
@@ -5,9 +5,7 @@
#include "flags.h"
class Fluxmap;
-class libusb_device;
-class libusb_device_handle;
-class libusb_device_descriptor;
+namespace libusbp { class device; }
class USB
{
@@ -30,13 +28,11 @@ public:
protected:
std::string usberror(int i);
-
- libusb_device_handle* _device;
};
extern USB& getUsb();
-extern USB* createFluxengineUsb(libusb_device* device);
+extern USB* createFluxengineUsb(libusbp::device& device);
extern USB* createGreaseWeazleUsb(const std::string& serialPort);
static inline int usbGetVersion() { return getUsb().getVersion(); }
diff --git a/lib/usb/usbfinder.cc b/lib/usb/usbfinder.cc
index 1ac057a2..853008a8 100644
--- a/lib/usb/usbfinder.cc
+++ b/lib/usb/usbfinder.cc
@@ -4,52 +4,38 @@
#include "bytes.h"
#include "fmt/format.h"
#include "usbfinder.h"
-#include
+#include "libusbp.hpp"
-static const std::string get_serial_number(libusb_device* device, libusb_device_descriptor* desc)
+static const std::string get_serial_number(const libusbp::device& device)
{
- std::string serial;
-
- libusb_device_handle* handle;
- if (libusb_open(device, &handle) == 0)
+ try
{
- unsigned char buffer[64];
- libusb_get_string_descriptor_ascii(handle, desc->iSerialNumber, buffer, sizeof(buffer));
- serial = (const char*) buffer;
- libusb_close(handle);
+ return device.get_serial_number();
+ }
+ catch (const libusbp::error& e)
+ {
+ if (e.has_code(LIBUSBP_ERROR_NO_SERIAL_NUMBER))
+ return "n/a";
+ throw;
}
-
- return serial;
}
std::vector> findUsbDevices(uint32_t candidateId)
{
- int i = libusb_init(NULL);
- if (i < 0)
- Error() << "could not start libusb: " << libusb_strerror((libusb_error) i);
-
- libusb_device** devices;
- int numdevices = libusb_get_device_list(NULL, &devices);
- if (numdevices < 0)
- Error() << "could not enumerate USB bus: " << libusb_strerror((libusb_error) numdevices);
-
std::vector> candidates;
- for (int i=0; i candidate(new CandidateDevice());
- candidate->device = devices[i];
- (void) libusb_get_device_descriptor(candidate->device, &candidate->desc);
+ auto candidate = std::make_unique();
+ candidate->device = it;
- uint32_t id = (candidate->desc.idVendor << 16) | candidate->desc.idProduct;
+ uint32_t id = (it.get_vendor_id() << 16) | it.get_product_id();
if (id == candidateId)
{
- libusb_ref_device(candidate->device);
candidate->id = candidateId;
- candidate->serial = get_serial_number(candidate->device, &candidate->desc);
+ candidate->serial = get_serial_number(it);
candidates.push_back(std::move(candidate));
}
}
- libusb_free_device_list(devices, true);
return candidates;
}
diff --git a/lib/usb/usbfinder.h b/lib/usb/usbfinder.h
index cfe15e82..eb5e82f3 100644
--- a/lib/usb/usbfinder.h
+++ b/lib/usb/usbfinder.h
@@ -1,12 +1,12 @@
#ifndef USBSERIAL_H
#define USBSERIAL_H
-#include
+#include "libusbp_config.h"
+#include "libusbp.hpp"
struct CandidateDevice
{
- libusb_device* device;
- libusb_device_descriptor desc;
+ libusbp::device device;
uint32_t id;
std::string serial;
};
diff --git a/mkninja.sh b/mkninja.sh
index 27bbc0d4..d926e134 100644
--- a/mkninja.sh
+++ b/mkninja.sh
@@ -3,11 +3,23 @@ set -e
cat < \$def) && $PROTOC \$flags \$in
description = PROTO \$in
@@ -31,6 +43,10 @@ rule link
command = $CXX $LDFLAGS -o \$out \$in \$flags $LIBS
description = LINK \$in
+rule linkgui
+ command = $CXX $LDFLAGS $GUILDFLAGS -o \$out \$in \$flags $LIBS $GUILIBS
+ description = LINK-OBJC \$in
+
rule test
command = \$in && touch \$out
description = TEST \$in
@@ -82,17 +98,41 @@ buildlibrary() {
dobjs=
for src in "$@"; do
local obj
+ local dobj
obj="$OBJDIR/opt/${src%%.c*}.o"
oobjs="$oobjs $obj"
+ dobj="$OBJDIR/dbg/${src%%.c*}.o"
+ dobjs="$dobjs $dobj"
- echo "build $obj : cxx $src | $deps"
- echo " flags=$flags $COPTFLAGS"
- obj="$OBJDIR/dbg/${src%%.c*}.o"
- dobjs="$dobjs $obj"
+ case "${src##*.}" in
+ m)
+ echo "build $obj : cobjc $src | $deps"
+ echo " flags=$flags $COPTFLAGS"
+ echo "build $dobj : cobjc $src | $deps"
+ echo " flags=$flags $CDBGFLAGS"
+ ;;
+
+ c)
+ echo "build $obj : cc $src | $deps"
+ echo " flags=$flags $COPTFLAGS"
+ echo "build $dobj : cc $src | $deps"
+ echo " flags=$flags $CDBGFLAGS"
+ ;;
+
+ cc|cpp)
+ echo "build $obj : cxx $src | $deps"
+ echo " flags=$flags $COPTFLAGS"
+ echo "build $dobj : cxx $src | $deps"
+ echo " flags=$flags $CDBGFLAGS"
+ ;;
+
+ *)
+ echo "Unknown file extension" >&2
+ exit 1
+ ;;
+ esac
- echo "build $obj : cxx $src | $deps"
- echo " flags=$flags $CDBGFLAGS"
done
echo build $OBJDIR/opt/$lib : library $oobjs
@@ -166,8 +206,16 @@ buildprogram() {
local flags
flags=
+ local rule
+ rule=link
while true; do
case $1 in
+ -rule)
+ rule=$2
+ shift
+ shift
+ ;;
+
-*)
flags="$flags $1"
shift
@@ -187,10 +235,10 @@ buildprogram() {
dobjs="$dobjs $OBJDIR/dbg/$src"
done
- echo build $prog-debug$EXTENSION : link $dobjs
+ echo build $prog-debug$EXTENSION : $rule $dobjs
echo " flags=$flags $LDDBGFLAGS"
- echo build $prog$EXTENSION-unstripped : link $oobjs
+ echo build $prog$EXTENSION-unstripped : $rule $oobjs
echo " flags=$flags $LDOPTFLAGS"
echo build $prog$EXTENSION : strip $prog$EXTENSION-unstripped
@@ -277,6 +325,32 @@ buildlibrary libagg.a \
dep/stb/stb_image_write.c \
dep/agg/src/*.cpp
+case "$(uname)" in
+ Darwin)
+ buildlibrary libusbp.a \
+ -Idep/libusbp/include \
+ -Idep/libusbp/src \
+ dep/libusbp/src/*.c \
+ dep/libusbp/src/mac/*.c
+ ;;
+
+ MINGW*)
+ buildlibrary libusbp.a \
+ -Idep/libusbp/include \
+ -Idep/libusbp/src \
+ dep/libusbp/src/*.c \
+ dep/libusbp/src/windows/*.c
+ ;;
+
+ *)
+ buildlibrary libusbp.a \
+ -Idep/libusbp/include \
+ -Idep/libusbp/src \
+ dep/libusbp/src/*.c \
+ dep/libusbp/src/linux/*.c
+ ;;
+esac
+
buildlibrary libfmt.a \
dep/fmt/format.cc \
dep/fmt/os.cc \
@@ -312,6 +386,7 @@ buildproto libfl2.a \
buildlibrary libbackend.a \
-I$OBJDIR/proto \
+ -Idep/libusbp/include \
-d $OBJDIR/proto/libconfig.def \
-d $OBJDIR/proto/libfl2.def \
arch/aeslanier/decoder.cc \
@@ -453,11 +528,15 @@ done
buildmktable formats $OBJDIR/formats.cc $FORMATS
-buildlibrary libfrontend.a \
+buildlibrary libformats.a \
-I$OBJDIR/proto \
-d $OBJDIR/proto/libconfig.def \
$(for a in $FORMATS; do echo $OBJDIR/proto/src/formats/$a.cc; done) \
$OBJDIR/formats.cc \
+
+buildlibrary libfrontend.a \
+ -I$OBJDIR/proto \
+ -d $OBJDIR/proto/libconfig.def \
src/fe-analysedriveresponse.cc \
src/fe-analyselayout.cc \
src/fe-inspect.cc \
@@ -474,9 +553,11 @@ buildlibrary libfrontend.a \
buildprogram fluxengine \
libfrontend.a \
+ libformats.a \
libbackend.a \
libconfig.a \
libfl2.a \
+ libusbp.a \
libfmt.a \
libagg.a \
diff --git a/protocol.h b/protocol.h
index ee36579c..edf0a5f7 100644
--- a/protocol.h
+++ b/protocol.h
@@ -3,7 +3,7 @@
enum
{
- FLUXENGINE_VERSION = 15,
+ FLUXENGINE_VERSION = 16,
FLUXENGINE_VID = 0x1209,
FLUXENGINE_PID = 0x6e00,