Connecting to the USB stack
As you're communicating with a separate process when querying the USB stack on QNX, the driver-side setup is quite different than the kernel module setup that a Linux driver would have. Here are some similarities and differences:
When a driver identifies itself to the USB stack on Linux, it registers itself with that part of the kernel. On QNX, a driver establishes a connection with the USB stack process. This connection is handled by a separate thread of your process, which is responsible for handling IPC and triggering callbacks when needed (whereas the kernel is simply calling another part of itself).
This means more information must be provided when connecting, as both registration and IPC messaging information is needed.
Any data accesses both in a callback and elsewhere in the code must be thread-safe (i.e., protected by a mutex or similar).
Device interest is handled differently across both platforms. Linux provides these as a list of VIDs and PIDs, which your driver may be interested in. QNX provides a filter device profile (including VID, PID, class, subclass, and protocol) and checks against it for matching devices. You can find more details in the following section.
The QNX implementation provides less callbacks. Only insertion and removal, which correlate to Linux's probe and disconnect, are available. More details in the following section.
Data structures
There are three structures associated with connecting to the USB Stack on QNX. They are as follows:
USBD device ID filter
/* In startup, acts as a filter for devices that this driver will connect to */
/* Later, this structure is used to store device information from the USB Stack */
typedef struct usbd_device_ident {
uint32_t vendor;
uint32_t device;
uint32_t dclass;
uint32_t subclass;
uint32_t protocol;
} usbd_device_ident_t;
The usbd_device_ident_t
structure stores identifying information about a USB device. When
connecting to the stack, an instance is passed in to act as a filter
specifying which devices the driver would want to connect to. Unlike
Linux, you can only provide one filter (although the identifiers it
checks against are more broad).
You must set all of the above to either USBD_CONNECT_WILDCARD
or an
explicit value. The USB stack checks all existing and incoming
connections against this filter for exact matches in explicit values and
accepting any value against USBD_CONNECT_WILDCARD
.
Examples:
Set
vendor
to0x046d
and the rest toUSBD_CONNECT_WILDCARD
to match any Logitech devices.Set
dclass
to any defined class code to filter by class, e.g.,0x03
for HID Devices or0xFF
for vendor-specific.
USBD function callbacks
/* Contains pointers to USBD Callbacks */
typedef struct usbd_funcs {
uint32_t nentries;
void (*insertion)(struct usbd_connection *, usbd_device_instance_t *instance);
void (*removal)(struct usbd_connection *, usbd_device_instance_t *instance);
void (*event)(struct usbd_connection *, usbd_device_instance_t *instance, uint16_t type);
} usbd_funcs_t;
The usbd_funcs_t
structure passes references to user-defined callback functions,
which it uses in the management of device connections.
The
nentries
parameter represents the number of callback functions in total, and should always be set to_USBDI_NFUNCS
The
insertion
andremoval
parameters are pointers to functions of the formvoid example (struct usbd_connection *, usbd_device_instance_t *)
, and are called on insertion or removal of USB devices that match the device filter set in theusbd_device_ident
structure seen above. Theinsertion
parameter is roughly equivalent to the probe() callback in Linux, andremoval
is roughly equivalent to thedisconnect
callback in Linux.The
event
parameter is a reserved function and should be set as a placeholder (orNULL
) for now.
The insertion
and removal
parameters also control the mode in which you connect to
devices.
If they're set, you attach to devices with exclusive access and can perform I/O.
If they aren't set, you attach with shared access and cannot perform I/O but can read USB configuration values.
The following section provides examples of insertion and removal callbacks.
USBD connection parameters
/* Specifies general settings for the connection, and contains
references to the usbd_device_ident_t and usbd_funcs_t structures */
typedef struct usbd_connect_parm {
const char *path;
uint16_t vusb;
uint16_t vusbd;
uint32_t flags;
int argc;
char **argv;
uint32_t evtbufsz;
usbd_device_ident_t *ident;
usbd_funcs_t *funcs;
uint16_t connect_wait;
uint8_t evtprio;
uint8_t _reserved[13];
} usbd_connect_parm_t;
The usbd_connect_parm_t
structure is the actual structure passed in when connecting
to the USB stack and contains both various parameters for starting the
IPC connection and references to the callback and identity filter
structures above.
- path
The path to the USB stack. You can pass the path as NULL, in which case it defaults to /dev/io-usb/io-usb. (This is similar to the dev folder in Linux)
- vusb, vusbd
Versions of the USB stack and USBD interface, which should be set to
USB_VERSION
andUSBD_VERSION
. This is required as if the USB stack gets updated, it needs to be able to refuse a connection prior to parsing structures; otherwise it may access unallocated memory.- flags
A placeholder for connection flags. Set this to
0
, as none are currently available.- argc, argv
Command line arguments that you want to be available for query at device insertion or attach time.
- evtbufsz
Controls the size of the buffer used by the USB stack to store insertion, removal, and other events queued to be sent to the driver. Can be set at
0
for default.- ident,funcs
References to the structures listed above, used for filtering and callback storage. The funcs parameter can be set to NULL to indicate that this utility is not interested in asynchonous I/O and will not receive insert, removal, or event callbacks.
- connect_wait
A number in seconds to wait for a connection to be established. Can be set to
USBD_CONNECT_WAIT
to wait for a connection indefinitely. If you are unsure thatio-usb
is running when your driver is, make sure to set this parameter!- evtprio
Allows class drivers to set the priority of the event handling thread.
- reserved
Reserved for future functionality.
Registration settings in Linux
* this includes a name, ID table, and a number of callback
* functions on specific events. */
/* NOTE: This code is in a Kernel Module (.ko) */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb.h>
/* Interest is declared via an interest table */
/* Note the available macros for setting up the table */
/* Note the terminating entry */
static struct usb_device_id interest_table[] =
{
{ USB_DEVICE(0x046d,0xc21d) }, /* Logitech F310 */
{} /* Terminating Entry */
};
MODULE_DEVICE_TABLE(usb, interest_table);
/* All callbacks and the id_table are in the struct */
static struct usb_driver example_driver = {
.name = "example",
.probe = callback_probe, /* Called for all potential devices */
.disconnect = callback_disconnect, /* Called for removed devices */
.suspend = callback_suspend,
.resume = callback_resume,
.pre_reset = callback_pre_reset,
.post_reset = callback_post_reset,
.id_table = interest_table,
.supports_autosuspend = 1,
};
Connection settings in QNX
/* QNX uses a set of three structs to pass in connection settings.
* these are: a usbd_device_ident_t which is used as a filter for
* potential matching devices, a usbd_funcs_t which contains a list
* of callbacks, and a usbd_connect_parm_t which holds references
* to the above and IPC related parameters */
/* NOTE: This code is in its own process (binary file) */
#include <errno.h>
#include <sys/usbdi.h>
/* Interest is declared via a filter struct */
/* Note USBD_CONNECT_WILDCARD is available as a catch-all */
usbd_device_ident_t filter = {
.vendor = 0x046d, /* Logitech's VID */
.device = 0xc21d, /* F310 Gamepad */
.dclass = 0xFF, /* 0xFF is Vendor-Specific */
.subclass = USBD_CONNECT_WILDCARD, /* Any Subclass */
.protocol = USBD_CONNECT_WILDCARD /* Any Protocol */
};
/* Callbacks are declared. Correlations are listed: */
usbd_funcs_t funcs = {
.nentries = _USBDI_N_FUNCS, /* MUST be set to this */
.insertion = on_usbd_insert, /* Works like probe in Linux */
.removal = on_usbd_remove, /* Works like disconnect in Linux */
.event = NULL /* Reserved */
};
/* The final connection settings struct is populated */
usbd_connect_parms_t connect_parms = {
.path = NULL, /* Indicate that the default io-usb path is correct */
.vusb = USB_VERSION,
.vusbd = USBD_VERSION,
.flags = 0, /* No flags to be passed */
.argc = 0, /* Set as argc in your main function */
.argv = NULL, /* Set as argv in main */
.evtbufsz = 0, /* Default event buffer size */
.ident = &interest
.funcs = &funcs
.connect_wait = USBD_CONNECT_WAIT /* No Timeout */
};
Initializing a connection
Once you've populated these structures, we can pass the
usbd_connect_parm
struct into the usbd_connect() function to
request a connection. You should also allocate space for an opaque
handle to the USBD connection, represented by struct
usbd_connection*
. This handle will later be used by
usbd_disconnect() to disconnect from the USB Stack.
You can think of usbd_connect() and usbd_disconnect() as
analogues to the usb_register() and usb_deregister() functions,
which would be used in a Linux kernel module. Except, rather than being
place in your module's __init
and __exit
functions,
usbd_connect() is typically placed in the main() function of the
program, with usbd_disconnect() also placed there or in signal
handling functions.
If your connection needs to perform I/O, the insertion and
removal callbacks MUST be set in your usbd_funcs_t
.
/* Available via #include <sys/usbdi.h> on QNX */
struct usbd_connection; /* opaque */
int usbd_connect( usbd_connect_parm_t *parm,
struct usbd_connection **connection );
int usbd_disconnect( struct usbd_connection *connection );
As mentioned previously,usbd_connect() creates a separate thread to monitor insertion, removal, or other events via IPC. Make sure any resources shared between the main thread and any callback functions or other threads are properly protected.
The following example extends the settings example from above to actually connect or register:
Registering a USB driver in Linux
/* Continued from above settings */
/* Initialization */
static int __init driver_init(void){
int result;
/* Register the driver */
if((result = usb_register(&example_driver))){
printk(KERN_ERR "usbd_register failed with errno %d", result);
}
return result;
}
/* Exiting */
static void __exit driver_exit(void){
/* Deregister */
usb_deregister(&example_driver);
}
/* Module init/exit info */
module_init(driver_init);
module_exit(driver_exit);
Connecting to the USB stack in QNX
/* Continued from above settings */
struct usbd_connection *usb_conn;
/* Exiting when Signals received */
void signal_handler(int signo){
usbd_disconnect(usb_conn);
_exit(0);
}
/* Initialization and setting up signal handling */
void main(int argc, char* argv[]){
/* Set up argc and argv in connect parms*/
connect_parms.argc = argc;
connect_parms.argv = argv;
/* Set up signal handling to be the exit condition */
signal (SIGHUP, SIG_IGN);
signal (SIGPWR, SIG_IGN);
signal (SIGTERM, signal_handler);
signal (SIGKILL, signal_handler);
/* Connect */
int result;
if((result = usbd_connect(&usb_parms ,&usb_conn))){
printf("Failed to connect with errno %d\n", result);
return 0;
}
/* Loop until disconnect */
for( ; ; ) sleep(60);
/* Unreachable, but here in case we edit the for loop */
usbd_disconnect(usb_conn);
return 0;
}