Merge from master.

This commit is contained in:
dg
2023-05-06 22:45:46 +00:00
31 changed files with 316 additions and 249 deletions

View File

@@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 2.8.11)
cmake_minimum_required (VERSION 3.10.0)
# Fix behavior of CMAKE_CXX_STANDARD when targeting macOS.
if (POLICY CMP0025)
@@ -18,7 +18,7 @@ endif ()
project (libusbp)
set (LIBUSBP_VERSION_MAJOR 1)
set (LIBUSBP_VERSION_MINOR 2)
set (LIBUSBP_VERSION_MINOR 3)
set (LIBUSBP_VERSION_PATCH 0)
# Make 'Release' be the default build type, since the debug builds
@@ -49,28 +49,8 @@ set(VBOX_LINUX_ON_WINDOWS FALSE CACHE BOOL
set(ENABLE_GCOV FALSE CACHE BOOL
"Compile with special options needed for gcov.")
# Our C code uses features from the C99 standard.
macro(use_c99)
if (CMAKE_VERSION VERSION_LESS "3.1")
if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
set (CMAKE_C_FLAGS "--std=gnu99 ${CMAKE_C_FLAGS}")
endif ()
else ()
set (CMAKE_C_STANDARD 99)
endif ()
endmacro(use_c99)
# Our C++ code uses features from the C++11 standard.
macro(use_cxx11)
if (CMAKE_VERSION VERSION_LESS "3.1")
if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
# Use --std=gnu++0x instead of --std=gnu++11 in order to support GCC 4.6.
set (CMAKE_CXX_FLAGS "--std=gnu++0x ${CMAKE_C_FLAGS}")
endif ()
else ()
set (CMAKE_CXX_STANDARD 11)
endif ()
endmacro(use_cxx11)
set (CMAKE_C_STANDARD 99)
set (CMAKE_CXX_STANDARD 11)
set (LIBUSBP_VERSION ${LIBUSBP_VERSION_MAJOR}.${LIBUSBP_VERSION_MINOR}.${LIBUSBP_VERSION_PATCH})

View File

@@ -1,7 +1,5 @@
# libusbp: Pololu USB Library
Version: 1.2.0<br/>
Release date: 2020-11-16<br/>
[www.pololu.com](https://www.pololu.com/)
The **Pololu USB Library** (also known as **libusbp**) is a cross-platform C library for accessing USB devices.
@@ -17,7 +15,7 @@ The **Pololu USB Library** (also known as **libusbp**) is a cross-platform C lib
- Provides detailed error information to the caller.
- Each error includes one or more English sentences describing the error, including error codes from underlying APIs.
- Some errors have libusbp-defined error codes that can be used to programmatically decide how to handle the error.
- Provides an object-oriented C++ wrapper (using features of C++11).
- Provides an object-oriented C++ wrapper.
- Provides access to underlying identifiers, handles, and file descriptors.
@@ -139,9 +137,9 @@ If you are using GCC and a shell that supports Bash-like syntax, here is an exam
gcc program.c `pkg-config --cflags --libs libusbp-1`
Here is an equivalent command for C++. Note that we use the `--std=gnu++11` option because the libusbp C++ API requires features from C++11:
Here is an equivalent command for C++:
g++ --std=gnu++11 program.cpp `pkg-config --cflags --libs libusbp-1`
g++ program.cpp `pkg-config --cflags --libs libusbp-1`
The order of the arguments above matters: the user program must come before libusbp because it relies on symbols that are defined by libusbp.
@@ -167,6 +165,9 @@ For detailed documentation of this library, see the header files `libusb.h` and
## Version history
* 1.3.0 (2023-01-02):
* Windows: Added support for detecting FTDI serial ports. (FTDI devices with more than one port have not been tested and the interface for detecting them might change in the future.)
* macOS: Fixed the detection of serial ports for devices that are not CDC ACM.
* 1.2.0 (2020-11-16):
* Linux: Made the library work with devices attached to the cp210x driver.
* Windows: Made the library work with devices that have lowercase letters in their hardware IDs.

View File

@@ -1,2 +1,3 @@
This was taken from https://github.com/pololu/libusbp on 2021-12-11.
This is version 1.3.0 taken from https://github.com/pololu/libusbp on
2023-05-06.

View File

@@ -1,5 +1,3 @@
use_cxx11()
add_executable(async_in async_in.cpp)
include_directories (

View File

@@ -1,5 +1,3 @@
use_cxx11()
add_executable(lsport lsport.cpp)
include_directories (

View File

@@ -1,5 +1,3 @@
use_cxx11()
add_executable(lsusb lsusb.cpp)
include_directories (

View File

@@ -1,5 +1,3 @@
use_cxx11()
add_executable(port_name port_name.cpp)
include_directories (

View File

@@ -41,7 +41,6 @@ extern "C" {
#ifdef LIBUSBP_STATIC
# define LIBUSBP_API
#else
#error not static
# ifdef LIBUSBP_EXPORTS
# define LIBUSBP_API LIBUSBP_DLL_EXPORT
# else

View File

@@ -107,7 +107,7 @@ namespace libusbp
{
public:
/*! Constructor that takes a pointer. */
explicit unique_pointer_wrapper(T * p = nullptr) noexcept
explicit unique_pointer_wrapper(T * p = NULL) noexcept
: pointer(p)
{
}
@@ -133,9 +133,9 @@ namespace libusbp
/*! Implicit conversion to bool. Returns true if the underlying pointer
* is not NULL. */
explicit operator bool() const noexcept
operator bool() const noexcept
{
return pointer != nullptr;
return pointer != NULL;
}
/*! Returns the underlying pointer. */
@@ -146,19 +146,19 @@ namespace libusbp
/*! 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
void pointer_reset(T * p = NULL) 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
* resetting the underlying pointer of this object to NULL. The caller
* is responsible for freeing the returned pointer if it is not NULL. */
T * pointer_release() noexcept
{
T * p = pointer;
pointer = nullptr;
pointer = NULL;
return p;
}
@@ -193,14 +193,14 @@ namespace libusbp
{
public:
/*! Constructor that takes a pointer. */
explicit unique_pointer_wrapper_with_copy(T * p = nullptr) noexcept
explicit unique_pointer_wrapper_with_copy(T * p = NULL) noexcept
: unique_pointer_wrapper<T>(p)
{
}
/*! Move constructor. */
unique_pointer_wrapper_with_copy(
unique_pointer_wrapper_with_copy && other) noexcept = default;
unique_pointer_wrapper_with_copy && other) = default;
/*! Copy constructor */
unique_pointer_wrapper_with_copy(
@@ -228,13 +228,14 @@ namespace libusbp
{
public:
/*! Constructor that takes a pointer. */
explicit error(libusbp_error * p = nullptr) noexcept
explicit error(libusbp_error * p = NULL) noexcept
: unique_pointer_wrapper_with_copy(p)
{
}
/*! Wrapper for libusbp_error_get_message(). */
const char * what() const noexcept override {
virtual const char * what() const noexcept
{
return libusbp_error_get_message(pointer);
}
@@ -255,7 +256,7 @@ namespace libusbp
/*! \cond */
inline void throw_if_needed(libusbp_error * err)
{
if (err != nullptr)
if (err != NULL)
{
throw error(err);
}
@@ -267,7 +268,7 @@ namespace libusbp
{
public:
/*! Constructor that takes a pointer. */
explicit async_in_pipe(libusbp_async_in_pipe * pointer = nullptr)
explicit async_in_pipe(libusbp_async_in_pipe * pointer = NULL)
: unique_pointer_wrapper(pointer)
{
}
@@ -303,8 +304,8 @@ namespace libusbp
bool handle_finished_transfer(void * buffer, size_t * transferred,
error * transfer_error)
{
libusbp_error ** error_out = nullptr;
if (transfer_error != nullptr)
libusbp_error ** error_out = NULL;
if (transfer_error != NULL)
{
transfer_error->pointer_reset();
error_out = transfer_error->pointer_to_pointer_get();
@@ -328,7 +329,7 @@ namespace libusbp
{
public:
/*! Constructor that takes a pointer. */
explicit device(libusbp_device * pointer = nullptr) :
explicit device(libusbp_device * pointer = NULL) :
unique_pointer_wrapper_with_copy(pointer)
{
}
@@ -387,7 +388,7 @@ namespace libusbp
std::vector<device> vector;
for(size_t i = 0; i < size; i++)
{
vector.emplace_back(device_list[i]);
vector.push_back(device(device_list[i]));
}
libusbp_list_free(device_list);
return vector;
@@ -408,13 +409,13 @@ namespace libusbp
public:
/*! Constructor that takes a pointer. This object will free the pointer
* when it is destroyed. */
explicit generic_interface(libusbp_generic_interface * pointer = nullptr)
explicit generic_interface(libusbp_generic_interface * pointer = NULL)
: unique_pointer_wrapper_with_copy(pointer)
{
}
/*! Wrapper for libusbp_generic_interface_create. */
explicit generic_interface(const device & device,
generic_interface(const device & device,
uint8_t interface_number = 0, bool composite = false)
{
throw_if_needed(libusbp_generic_interface_create(
@@ -448,13 +449,13 @@ namespace libusbp
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
explicit generic_handle(libusbp_generic_handle * pointer = NULL) noexcept
: unique_pointer_wrapper(pointer)
{
}
/*! Wrapper for libusbp_generic_handle_open(). */
explicit generic_handle(const generic_interface & gi)
generic_handle(const generic_interface & gi)
{
throw_if_needed(libusbp_generic_handle_open(gi.pointer_get(), &pointer));
}
@@ -486,9 +487,9 @@ namespace libusbp
uint8_t bRequest,
uint16_t wValue,
uint16_t wIndex,
void * buffer = nullptr,
void * buffer = NULL,
uint16_t wLength = 0,
size_t * transferred = nullptr)
size_t * transferred = NULL)
{
throw_if_needed(libusbp_control_transfer(pointer,
bmRequestType, bRequest, wValue, wIndex,
@@ -542,13 +543,13 @@ namespace libusbp
public:
/*! Constructor that takes a pointer. This object will free the pointer
* when it is destroyed. */
explicit serial_port(libusbp_serial_port * pointer = nullptr)
explicit serial_port(libusbp_serial_port * pointer = NULL)
: unique_pointer_wrapper_with_copy(pointer)
{
}
/*! Wrapper for libusbp_serial_port_create(). */
explicit serial_port(const device & device,
serial_port(const device & device,
uint8_t interface_number = 0, bool composite = false)
{
throw_if_needed(libusbp_serial_port_create(

View File

@@ -1,5 +1,3 @@
use_c99()
add_library (install_helper SHARED install_helper_windows.c dll.def)
target_link_libraries (install_helper setupapi msi)

View File

@@ -1,5 +1,3 @@
use_cxx11()
add_executable(test_async_in test_async_in.cpp)
include_directories (

View File

@@ -1,5 +1,3 @@
use_cxx11()
add_executable(test_long_read test_long_read.cpp)
include_directories (

View File

@@ -1,5 +1,3 @@
use_cxx11()
add_executable(test_long_write test_long_write.cpp)
include_directories (

View File

@@ -1,5 +1,3 @@
use_cxx11()
add_executable(test_transitions test_transitions.cpp)
include_directories (

View File

@@ -1,5 +1,3 @@
use_c99()
# Settings for GCC
if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
# By default, symbols are not visible outside of the library.

View File

@@ -124,7 +124,7 @@ libusbp_error * error_add_v(libusbp_error * error, const char * format, va_list
int result = vsnprintf(x, 0, format, ap2);
if (result > 0)
{
outer_message_length = (size_t) result;
outer_message_length = result;
}
va_end(ap2);
}

View File

@@ -37,7 +37,10 @@ libusbp_error * libusbp_find_device_with_vid_pid(
libusbp_device ** new_list = NULL;
size_t size = 0;
error = libusbp_list_connected_devices(&new_list, &size);
if (error == NULL)
{
error = libusbp_list_connected_devices(&new_list, &size);
}
assert(error != NULL || new_list != NULL);

View File

@@ -37,6 +37,7 @@
#include <usbioctl.h>
#include <stringapiset.h>
#include <winusb.h>
#include <ntddmodm.h>
#endif
#ifdef __linux__

View File

@@ -51,11 +51,14 @@ libusbp_error * async_in_transfer_create(
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)
async_in_transfer * new_transfer = NULL;
if (error == NULL)
{
error = &error_no_memory;
new_transfer = calloc(1, sizeof(async_in_transfer));
if (new_transfer == NULL)
{
error = &error_no_memory;
}
}
// Allocate memory for the buffer.

View File

@@ -25,9 +25,14 @@ libusbp_error * create_device(io_service_t service, libusbp_device ** device)
assert(service != MACH_PORT_NULL);
assert(device != NULL);
libusbp_error * error = NULL;
// Allocate the device.
libusbp_device * new_device = NULL;
libusbp_error * error = device_allocate(&new_device);
if (error == NULL)
{
error = device_allocate(&new_device);
}
// Get the numeric IDs.
if (error == NULL)
@@ -84,7 +89,10 @@ libusbp_error * libusbp_device_copy(const libusbp_device * source, libusbp_devic
// Allocate the device.
libusbp_device * new_device = NULL;
error = device_allocate(&new_device);
if (error == 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.

View File

@@ -63,9 +63,8 @@ static libusbp_error * process_pipe_properties(libusbp_generic_handle * handle)
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);
kern_return_t kr = (*handle->ioh)->GetPipeProperties(handle->ioh, 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);
@@ -75,11 +74,11 @@ static libusbp_error * process_pipe_properties(libusbp_generic_handle * handle)
{
if (direction)
{
handle->in_pipe_index[endpoint_number] = (uint8_t) i;
handle->in_pipe_index[endpoint_number] = i;
}
else
{
handle->out_pipe_index[endpoint_number] = (uint8_t) i;
handle->out_pipe_index[endpoint_number] = i;
}
}
}
@@ -96,11 +95,14 @@ static libusbp_error * set_configuration(io_service_t service)
// 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);
if (error == NULL)
{
error = service_to_interface(service,
kIOUSBDeviceUserClientTypeID,
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID197),
(void **)&dev_handle,
&plug_in);
}
uint8_t config_num = 0;
if (error == NULL)
@@ -170,7 +172,10 @@ static libusbp_error * set_configuration_and_get_service(
// 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);
if (error == NULL)
{
error = service_get_from_id(device_id, &device_service);
}
// Set the configruation to 1 if it is not set.
if (error == NULL)
@@ -209,11 +214,13 @@ libusbp_error * libusbp_generic_handle_open(
// Allocate memory for the handle.
libusbp_generic_handle * new_handle = NULL;
new_handle = calloc(1, sizeof(libusbp_generic_handle));
if (new_handle == NULL)
if (error == NULL)
{
error = &error_no_memory;
new_handle = calloc(1, sizeof(libusbp_generic_handle));
if (new_handle == NULL)
{
error = &error_no_memory;
}
}
// Get the io_service_t representing the IOUSBInterface.
@@ -323,11 +330,14 @@ libusbp_error * libusbp_generic_handle_set_timeout(
libusbp_error * error = NULL;
error = check_pipe_id(pipe_id);
if (error == NULL)
{
error = check_pipe_id(pipe_id);
}
if (error == NULL)
{
uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER;
uint8_t endpoint_number = pipe_id & MAX_ENDPOINT_NUMBER;
if (pipe_id & 0x80)
{
@@ -401,7 +411,7 @@ libusbp_error * libusbp_read_pipe(
libusbp_error * error = NULL;
if (size == 0)
if (error == NULL && size == 0)
{
error = error_create("Transfer size 0 is not allowed.");
}
@@ -423,12 +433,12 @@ libusbp_error * libusbp_read_pipe(
if (error == NULL)
{
uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER;
uint8_t endpoint_number = pipe_id & 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 iokit_size = size;
uint32_t pipe_index = handle->in_pipe_index[endpoint_number];
kern_return_t kr = (*handle->ioh)->ReadPipeTO(handle->ioh, (UInt8) pipe_index,
kern_return_t kr = (*handle->ioh)->ReadPipeTO(handle->ioh, pipe_index,
buffer, &iokit_size, no_data_timeout, completion_timeout);
if (transferred != NULL) { *transferred = iokit_size; }
if (kr != KERN_SUCCESS)
@@ -464,7 +474,7 @@ libusbp_error * libusbp_write_pipe(
libusbp_error * error = NULL;
if (size > UINT32_MAX)
if (error == NULL && size > UINT32_MAX)
{
error = error_create("Transfer size is too large.");
}
@@ -481,12 +491,12 @@ libusbp_error * libusbp_write_pipe(
if (error == NULL)
{
uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER;
uint8_t endpoint_number = pipe_id & 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);
kern_return_t kr = (*handle->ioh)->WritePipeTO(handle->ioh, pipe_index,
(void *)buffer, size, no_data_timeout, completion_timeout);
if (kr != KERN_SUCCESS)
{
error = error_create_mach(kr, "");
@@ -588,7 +598,7 @@ IOUSBInterfaceInterface182 ** generic_handle_get_ioh(const libusbp_generic_handl
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;
uint8_t endpoint_number = pipe_id & MAX_ENDPOINT_NUMBER;
if (pipe_id & 0x80)
{
return handle->in_pipe_index[endpoint_number];

View File

@@ -82,7 +82,10 @@ libusbp_error * libusbp_generic_interface_create(
{
// 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);
if (error == 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;
@@ -146,7 +149,10 @@ libusbp_error * libusbp_generic_interface_copy(
// Allocate the generic interface.
libusbp_generic_interface * new_gi = NULL;
error = generic_interface_allocate(&new_gi);
if (error == NULL)
{
error = generic_interface_allocate(&new_gi);
}
// Copy the simple fields.
if (error == NULL)

View File

@@ -42,12 +42,14 @@ libusbp_error * service_get_usb_interface(io_service_t service,
libusbp_error * error = NULL;
io_iterator_t iterator = MACH_PORT_NULL;
kern_return_t result = IORegistryEntryGetChildIterator(
service, kIOServicePlane, &iterator);
if (result != KERN_SUCCESS)
if (error == NULL)
{
error = error_create_mach(result, "Failed to get child iterator.");
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.
@@ -57,7 +59,7 @@ libusbp_error * service_get_usb_interface(io_service_t service,
if (candidate == MACH_PORT_NULL) { break; }
// Filter out candidates that are not of class IOUSBInterface.
bool conforms = (bool) IOObjectConformsTo(candidate, kIOUSBInterfaceClassName);
bool conforms = IOObjectConformsTo(candidate, kIOUSBInterfaceClassName);
if (!conforms)
{
IOObjectRelease(candidate);
@@ -88,54 +90,6 @@ libusbp_error * service_get_usb_interface(io_service_t service,
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,
@@ -154,13 +108,15 @@ libusbp_error * service_to_interface(
// 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)
if (error == NULL)
{
error = error_create_mach(kr, "Failed to create plug-in interface.");
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.
@@ -223,7 +179,7 @@ libusbp_error * get_string(io_registry_entry_t entry, CFStringRef name, char **
libusbp_error * error = NULL;
if (CFGetTypeID(cf_value) != CFStringGetTypeID())
if (error == NULL && CFGetTypeID(cf_value) != CFStringGetTypeID())
{
error = error_create("Property is not a string.");
}
@@ -244,7 +200,7 @@ libusbp_error * get_string(io_registry_entry_t entry, CFStringRef name, char **
error = string_copy(buffer, value);
}
CFRelease(cf_value);
if (cf_value != NULL) { CFRelease(cf_value); }
return error;
}
@@ -258,11 +214,14 @@ libusbp_error * get_int32(io_registry_entry_t entry, CFStringRef name, int32_t *
libusbp_error * error = NULL;
CFTypeRef cf_value = IORegistryEntryCreateCFProperty(entry, name, kCFAllocatorDefault, 0);
if (cf_value == NULL)
CFTypeRef cf_value = NULL;
if (error == NULL)
{
error = error_create("Failed to get int32 property from IORegistryEntry.");
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())
@@ -292,13 +251,16 @@ libusbp_error * get_uint16(io_registry_entry_t entry, CFStringRef name, uint16_t
libusbp_error * error = NULL;
int32_t tmp;
error = get_int32(entry, name, &tmp);
if (error == NULL)
{
error = get_int32(entry, name, &tmp);
}
if (error == NULL)
{
// There is an unchecked conversion of an int32_t to a uint16_t here but
// There is an implicit conversion of an int32_t to a uint16_t here but
// we don't expect any data to be lost.
*value = (uint16_t) tmp;
*value = tmp;
}
return error;

View File

@@ -37,10 +37,13 @@ libusbp_error * libusbp_list_connected_devices(
// Create a dictionary that says "IOProviderClass" => "IOUSBDevice"
// This dictionary is CFReleased by IOServiceGetMatchingServices.
CFMutableDictionaryRef dict = NULL;
dict = IOServiceMatching("IOUSBHostDevice");
if (dict == NULL)
if (error == NULL)
{
error = error_create("IOServiceMatching returned null.");
dict = IOServiceMatching("IOUSBHostDevice");
if (dict == NULL)
{
error = error_create("IOServiceMatching returned null.");
}
}
// Create an iterator for all the connected USB devices.

View File

@@ -2,9 +2,6 @@
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;
};
@@ -29,23 +26,16 @@ libusbp_error * libusbp_serial_port_create(
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)
libusbp_serial_port * new_port = NULL;
if (error == NULL)
{
error = &error_no_memory;
new_port = calloc(1, sizeof(libusbp_serial_port));
if (new_port == NULL)
{
error = &error_no_memory;
}
}
// Get the ID for the physical device.
@@ -62,19 +52,67 @@ libusbp_error * libusbp_serial_port_create(
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;
io_iterator_t iterator = MACH_PORT_NULL;
if (error == NULL)
{
error = service_get_usb_interface(device_service, interface_number, &interface_service);
kern_return_t result = IORegistryEntryCreateIterator(
device_service, kIOServicePlane, kIORegistryIterateRecursively, &iterator);
if (result != KERN_SUCCESS)
{
error = error_create_mach(result, "Failed to get recursive iterator.");
}
}
// Get an io_service_t for the IOSerialBSDClient
io_service_t serial_service = MACH_PORT_NULL;
if (error == NULL)
int32_t current_interface = -1;
int32_t last_acm_control_interface_with_no_port = -1;
int32_t last_acm_data_interface = -1;
while (error == NULL)
{
error = service_get_child_by_class(interface_service,
kIOSerialBSDServiceValue, &serial_service);
io_service_t service = IOIteratorNext(iterator);
if (service == MACH_PORT_NULL) { break; }
if (IOObjectConformsTo(service, kIOUSBHostInterfaceClassName))
{
error = get_int32(service, CFSTR("bInterfaceNumber"), &current_interface);
}
else if (IOObjectConformsTo(service, "AppleUSBACMControl"))
{
last_acm_control_interface_with_no_port = current_interface;
}
else if (IOObjectConformsTo(service, "AppleUSBACMData"))
{
last_acm_data_interface = current_interface;
}
else if (IOObjectConformsTo(service, kIOSerialBSDServiceValue))
{
int32_t fixed_interface = current_interface;
if (last_acm_data_interface == current_interface &&
last_acm_control_interface_with_no_port >= 0)
{
// We found an ACM control interface with no serial port, then
// an ACM data interface with a serial port. For consistency with
// other operating systems, we will consider this serial port to
// actually be associated with the control interface instead of the
// data interface.
fixed_interface = last_acm_control_interface_with_no_port;
}
last_acm_control_interface_with_no_port = -1;
if (fixed_interface == interface_number)
{
// We found the serial port the user is looking for.
serial_service = service;
break;
}
}
IOObjectRelease(service);
}
if (error == NULL && serial_service == MACH_PORT_NULL)
{
error = error_create("Could not find entry with class IOSerialBSDClient.");
error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
}
// Get the port name.
@@ -91,7 +129,7 @@ libusbp_error * libusbp_serial_port_create(
}
if (serial_service != MACH_PORT_NULL) { IOObjectRelease(serial_service); }
if (interface_service != MACH_PORT_NULL) { IOObjectRelease(interface_service); }
if (iterator != MACH_PORT_NULL) { IOObjectRelease(iterator); }
if (device_service != MACH_PORT_NULL) { IOObjectRelease(device_service); }
libusbp_serial_port_free(new_port);
@@ -125,11 +163,14 @@ libusbp_error * libusbp_serial_port_copy(const libusbp_serial_port * source,
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)
libusbp_serial_port * new_port = NULL;
if (error == NULL)
{
error = &error_no_memory;
new_port = calloc(1, sizeof(libusbp_serial_port));
if (new_port == NULL)
{
error = &error_no_memory;
}
}
// Copy the port name.

View File

@@ -113,42 +113,79 @@ static libusbp_error * get_interface_composite(
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.");
}
unsigned int list_index = 0;
HDEVINFO new_list = INVALID_HANDLE_VALUE;
DWORD i = 0;
// Iterate through the list until we find a device whose
// Iterate through various device lists until we find a device whose
// parent device is ours and which controls the interface
// specified by the caller.
for (DWORD i = 0; ; i++)
while (true)
{
if (new_list == INVALID_HANDLE_VALUE)
{
if (list_index == 0)
{
// Get a list of all the USB-related devices.
// It includes native USB interfaces and usbser.sys ports but
// not FTDI ports.
new_list = SetupDiGetClassDevs(NULL,
"USB", NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT);
}
else if (list_index == 1)
{
// Get a list of all the COM port devices.
// This includes FTDI and usbser.sys ports.
new_list = SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT,
NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
}
else if (list_index == 2)
{
// Get a list of all modem devices.
// Rationale: https://github.com/pyserial/pyserial/commit/7bb1dcc5aea16ca1c957690cb5276df33af1c286
new_list = SetupDiGetClassDevs(&GUID_DEVINTERFACE_MODEM,
NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
}
else
{
// Could not find the child interface in any list.
// This could be a temporary condition.
libusbp_error * error = error_create("Could not find interface %d.",
interface_number);
error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
return error;
}
if (new_list == INVALID_HANDLE_VALUE)
{
return error_create_winapi(
"Failed to list devices to find an interface (%u).",
list_index);
}
i = 0;
}
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);
// This list is done. Try the next list.
SetupDiDestroyDeviceInfoList(new_list);
new_list = INVALID_HANDLE_VALUE;
list_index++;
continue;
}
else
{
error = error_create_winapi(
"Failed to get device info while finding an interface.");
libusbp_error * error = error_create_winapi(
"Failed to get device info to find an interface.");
SetupDiDestroyDeviceInfoList(new_list);
return error;
}
SetupDiDestroyDeviceInfoList(new_list);
return error;
}
DEVINST parent_dev_inst;
@@ -162,6 +199,7 @@ static libusbp_error * get_interface_composite(
if (parent_dev_inst != dev_inst)
{
// This device is not a child of our device.
i++;
continue;
}
@@ -179,9 +217,21 @@ static libusbp_error * get_interface_composite(
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)
if (result != 1)
{
result = sscanf(device_id, "FTDIBUS\\%*[^\\]\\%x",
&actual_interface_number);
if (result != 1)
{
// Could not figure out the interface number.
i++;
continue;
}
}
if (actual_interface_number != interface_number)
{
// This is not the right interface.
i++;
continue;
}

View File

@@ -124,6 +124,26 @@ libusbp_error * libusbp_serial_port_create(
libusbp_string_free(usb_device_id);
libusbp_serial_port_free(new_sp);
// FTDI devices like the FT232RL aren't actually composite but they look
// like it on Windows because the serial port device is a child of the USB
// device. On Linux and macOS, those devices can be detected with
// composite=false (or composite=true and interface_number=0).
// This workaround allows that to work on Windows too, and it might make
// this API easier to use for some non-FTDI devices too.
if (error && !composite)
{
libusbp_error * error2 = libusbp_serial_port_create(device, 0, true, port);
if (error2)
{
libusbp_error_free(error2);
}
else
{
libusbp_error_free(error);
return NULL;
}
}
return error;
}

View File

@@ -1,9 +1,8 @@
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.
# If catch.hpp is not present, we want to simply skip compiling the tests.
# Download catch.hpp and put it in this directory:
# https://raw.githubusercontent.com/catchorg/Catch2/v2.x/single_include/catch2/catch.hpp
set (CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}")
CHECK_INCLUDE_FILE_CXX (catch.hpp HAVE_CATCH_FRAMEWORK)
if (NOT HAVE_CATCH_FRAMEWORK)
@@ -11,8 +10,6 @@ if (NOT HAVE_CATCH_FRAMEWORK)
return ()
endif ()
use_cxx11 ()
set(USE_TEST_DEVICE_A FALSE CACHE BOOL
"Run tests that require Test Device A.")

View File

@@ -486,6 +486,9 @@ TEST_CASE("async_in_pipe for an interrupt endpoint")
// ms, a three-packet transfer will quickly receive those two packets
// and then keep waiting for more.
// Previous versions of macOS returned one packet instead of two,
// but this is no longer true on Darwin Kernel 22.1.0 (Oct 2022).
// Pause the ADC for 100 ms.
handle.control_transfer(0x40, 0xA0, 100, 0);
@@ -508,9 +511,6 @@ TEST_CASE("async_in_pipe for an interrupt endpoint")
#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);
@@ -662,9 +662,10 @@ TEST_CASE("async_in_pipe for an interrupt endpoint")
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;
// This request results in an error in Linux after some data is transferred.
// On some older Linux systems, expected_transferred was transfer_size + 1.
// With Linux 5.15 on a Raspberry Pi 4, expected_transferred is transfer_size.
expected_transferred = transfer_size;
expected_message = "Asynchronous IN transfer failed. "
"The transfer overflowed. Error code 75.";
#elif defined(__APPLE__)

View File

@@ -107,8 +107,8 @@ Bytes greaseWeazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock)
if (event)
{
uint32_t index_fl = (index_gw * clock) / NS_PER_TICK;
uint32_t ticks_fl = (ticks_gw * clock) / NS_PER_TICK;
uint32_t index_fl = round((index_gw * clock) / NS_PER_TICK);
uint32_t ticks_fl = round((ticks_gw * clock) / NS_PER_TICK);
if (index_gw != ~0)
{
if (index_fl < ticks_fl)

View File

@@ -2,7 +2,7 @@
$(OBJDIR)/tests/%.log: $(OBJDIR)/tests/%.exe
@mkdir -p $(dir $@)
@echo TEST $*
@$< > $@
@$<
declare-test = $(eval $(declare-test-impl))
define declare-test-impl