Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 1 | .. _writing-usb-driver: |
| 2 | |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 3 | ========================== |
| 4 | Writing USB Device Drivers |
| 5 | ========================== |
| 6 | |
| 7 | :Author: Greg Kroah-Hartman |
| 8 | |
| 9 | Introduction |
| 10 | ============ |
| 11 | |
| 12 | The Linux USB subsystem has grown from supporting only two different |
| 13 | types of devices in the 2.2.7 kernel (mice and keyboards), to over 20 |
| 14 | different types of devices in the 2.4 kernel. Linux currently supports |
| 15 | almost all USB class devices (standard types of devices like keyboards, |
| 16 | mice, modems, printers and speakers) and an ever-growing number of |
| 17 | vendor-specific devices (such as USB to serial converters, digital |
| 18 | cameras, Ethernet devices and MP3 players). For a full list of the |
| 19 | different USB devices currently supported, see Resources. |
| 20 | |
| 21 | The remaining kinds of USB devices that do not have support on Linux are |
| 22 | almost all vendor-specific devices. Each vendor decides to implement a |
| 23 | custom protocol to talk to their device, so a custom driver usually |
| 24 | needs to be created. Some vendors are open with their USB protocols and |
| 25 | help with the creation of Linux drivers, while others do not publish |
| 26 | them, and developers are forced to reverse-engineer. See Resources for |
| 27 | some links to handy reverse-engineering tools. |
| 28 | |
| 29 | Because each different protocol causes a new driver to be created, I |
| 30 | have written a generic USB driver skeleton, modelled after the |
| 31 | pci-skeleton.c file in the kernel source tree upon which many PCI |
| 32 | network drivers have been based. This USB skeleton can be found at |
| 33 | drivers/usb/usb-skeleton.c in the kernel source tree. In this article I |
| 34 | will walk through the basics of the skeleton driver, explaining the |
| 35 | different pieces and what needs to be done to customize it to your |
| 36 | specific device. |
| 37 | |
| 38 | Linux USB Basics |
| 39 | ================ |
| 40 | |
| 41 | If you are going to write a Linux USB driver, please become familiar |
| 42 | with the USB protocol specification. It can be found, along with many |
| 43 | other useful documents, at the USB home page (see Resources). An |
| 44 | excellent introduction to the Linux USB subsystem can be found at the |
| 45 | USB Working Devices List (see Resources). It explains how the Linux USB |
| 46 | subsystem is structured and introduces the reader to the concept of USB |
| 47 | urbs (USB Request Blocks), which are essential to USB drivers. |
| 48 | |
| 49 | The first thing a Linux USB driver needs to do is register itself with |
| 50 | the Linux USB subsystem, giving it some information about which devices |
| 51 | the driver supports and which functions to call when a device supported |
| 52 | by the driver is inserted or removed from the system. All of this |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 53 | information is passed to the USB subsystem in the :c:type:`usb_driver` |
| 54 | structure. The skeleton driver declares a :c:type:`usb_driver` as:: |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 55 | |
| 56 | static struct usb_driver skel_driver = { |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 57 | .name = "skeleton", |
| 58 | .probe = skel_probe, |
| 59 | .disconnect = skel_disconnect, |
| 60 | .fops = &skel_fops, |
| 61 | .minor = USB_SKEL_MINOR_BASE, |
| 62 | .id_table = skel_table, |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 63 | }; |
| 64 | |
| 65 | |
| 66 | The variable name is a string that describes the driver. It is used in |
| 67 | informational messages printed to the system log. The probe and |
| 68 | disconnect function pointers are called when a device that matches the |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 69 | information provided in the ``id_table`` variable is either seen or |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 70 | removed. |
| 71 | |
| 72 | The fops and minor variables are optional. Most USB drivers hook into |
| 73 | another kernel subsystem, such as the SCSI, network or TTY subsystem. |
| 74 | These types of drivers register themselves with the other kernel |
| 75 | subsystem, and any user-space interactions are provided through that |
| 76 | interface. But for drivers that do not have a matching kernel subsystem, |
| 77 | such as MP3 players or scanners, a method of interacting with user space |
| 78 | is needed. The USB subsystem provides a way to register a minor device |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 79 | number and a set of :c:type:`file_operations` function pointers that enable |
| 80 | this user-space interaction. The skeleton driver needs this kind of |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 81 | interface, so it provides a minor starting number and a pointer to its |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 82 | :c:type:`file_operations` functions. |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 83 | |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 84 | The USB driver is then registered with a call to :c:func:`usb_register`, |
| 85 | usually in the driver's init function, as shown here:: |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 86 | |
| 87 | static int __init usb_skel_init(void) |
| 88 | { |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 89 | int result; |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 90 | |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 91 | /* register this driver with the USB subsystem */ |
| 92 | result = usb_register(&skel_driver); |
| 93 | if (result < 0) { |
| 94 | err("usb_register failed for the "__FILE__ "driver." |
| 95 | "Error number %d", result); |
| 96 | return -1; |
| 97 | } |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 98 | |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 99 | return 0; |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 100 | } |
| 101 | module_init(usb_skel_init); |
| 102 | |
| 103 | |
| 104 | When the driver is unloaded from the system, it needs to deregister |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 105 | itself with the USB subsystem. This is done with the :c:func:`usb_deregister` |
| 106 | function:: |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 107 | |
| 108 | static void __exit usb_skel_exit(void) |
| 109 | { |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 110 | /* deregister this driver with the USB subsystem */ |
| 111 | usb_deregister(&skel_driver); |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 112 | } |
| 113 | module_exit(usb_skel_exit); |
| 114 | |
| 115 | |
| 116 | To enable the linux-hotplug system to load the driver automatically when |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 117 | the device is plugged in, you need to create a ``MODULE_DEVICE_TABLE``. |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 118 | The following code tells the hotplug scripts that this module supports a |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 119 | single device with a specific vendor and product ID:: |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 120 | |
| 121 | /* table of devices that work with this driver */ |
| 122 | static struct usb_device_id skel_table [] = { |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 123 | { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, |
| 124 | { } /* Terminating entry */ |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 125 | }; |
| 126 | MODULE_DEVICE_TABLE (usb, skel_table); |
| 127 | |
| 128 | |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 129 | There are other macros that can be used in describing a struct |
| 130 | :c:type:`usb_device_id` for drivers that support a whole class of USB |
| 131 | drivers. See :ref:`usb.h <usb_header>` for more information on this. |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 132 | |
| 133 | Device operation |
| 134 | ================ |
| 135 | |
| 136 | When a device is plugged into the USB bus that matches the device ID |
| 137 | pattern that your driver registered with the USB core, the probe |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 138 | function is called. The :c:type:`usb_device` structure, interface number and |
| 139 | the interface ID are passed to the function:: |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 140 | |
| 141 | static int skel_probe(struct usb_interface *interface, |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 142 | const struct usb_device_id *id) |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 143 | |
| 144 | |
| 145 | The driver now needs to verify that this device is actually one that it |
| 146 | can accept. If so, it returns 0. If not, or if any error occurs during |
| 147 | initialization, an errorcode (such as ``-ENOMEM`` or ``-ENODEV``) is |
| 148 | returned from the probe function. |
| 149 | |
| 150 | In the skeleton driver, we determine what end points are marked as |
| 151 | bulk-in and bulk-out. We create buffers to hold the data that will be |
| 152 | sent and received from the device, and a USB urb to write data to the |
| 153 | device is initialized. |
| 154 | |
| 155 | Conversely, when the device is removed from the USB bus, the disconnect |
| 156 | function is called with the device pointer. The driver needs to clean |
| 157 | any private data that has been allocated at this time and to shut down |
| 158 | any pending urbs that are in the USB system. |
| 159 | |
| 160 | Now that the device is plugged into the system and the driver is bound |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 161 | to the device, any of the functions in the :c:type:`file_operations` structure |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 162 | that were passed to the USB subsystem will be called from a user program |
| 163 | trying to talk to the device. The first function called will be open, as |
| 164 | the program tries to open the device for I/O. We increment our private |
| 165 | usage count and save a pointer to our internal structure in the file |
| 166 | structure. This is done so that future calls to file operations will |
| 167 | enable the driver to determine which device the user is addressing. All |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 168 | of this is done with the following code:: |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 169 | |
| 170 | /* increment our usage count for the module */ |
| 171 | ++skel->open_count; |
| 172 | |
| 173 | /* save our object in the file's private structure */ |
| 174 | file->private_data = dev; |
| 175 | |
| 176 | |
| 177 | After the open function is called, the read and write functions are |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 178 | called to receive and send data to the device. In the ``skel_write`` |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 179 | function, we receive a pointer to some data that the user wants to send |
| 180 | to the device and the size of the data. The function determines how much |
| 181 | data it can send to the device based on the size of the write urb it has |
| 182 | created (this size depends on the size of the bulk out end point that |
| 183 | the device has). Then it copies the data from user space to kernel |
| 184 | space, points the urb to the data and submits the urb to the USB |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 185 | subsystem. This can be seen in the following code:: |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 186 | |
| 187 | /* we can only write as much as 1 urb will hold */ |
| 188 | bytes_written = (count > skel->bulk_out_size) ? skel->bulk_out_size : count; |
| 189 | |
| 190 | /* copy the data from user space into our urb */ |
| 191 | copy_from_user(skel->write_urb->transfer_buffer, buffer, bytes_written); |
| 192 | |
| 193 | /* set up our urb */ |
| 194 | usb_fill_bulk_urb(skel->write_urb, |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 195 | skel->dev, |
| 196 | usb_sndbulkpipe(skel->dev, skel->bulk_out_endpointAddr), |
| 197 | skel->write_urb->transfer_buffer, |
| 198 | bytes_written, |
| 199 | skel_write_bulk_callback, |
| 200 | skel); |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 201 | |
| 202 | /* send the data out the bulk port */ |
| 203 | result = usb_submit_urb(skel->write_urb); |
| 204 | if (result) { |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 205 | err("Failed submitting write urb, error %d", result); |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 206 | } |
| 207 | |
| 208 | |
| 209 | When the write urb is filled up with the proper information using the |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 210 | :c:func:`usb_fill_bulk_urb` function, we point the urb's completion callback |
| 211 | to call our own ``skel_write_bulk_callback`` function. This function is |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 212 | called when the urb is finished by the USB subsystem. The callback |
| 213 | function is called in interrupt context, so caution must be taken not to |
| 214 | do very much processing at that time. Our implementation of |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 215 | ``skel_write_bulk_callback`` merely reports if the urb was completed |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 216 | successfully or not and then returns. |
| 217 | |
| 218 | The read function works a bit differently from the write function in |
| 219 | that we do not use an urb to transfer data from the device to the |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 220 | driver. Instead we call the :c:func:`usb_bulk_msg` function, which can be used |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 221 | to send or receive data from a device without having to create urbs and |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 222 | handle urb completion callback functions. We call the :c:func:`usb_bulk_msg` |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 223 | function, giving it a buffer into which to place any data received from |
| 224 | the device and a timeout value. If the timeout period expires without |
| 225 | receiving any data from the device, the function will fail and return an |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 226 | error message. This can be shown with the following code:: |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 227 | |
| 228 | /* do an immediate bulk read to get data from the device */ |
| 229 | retval = usb_bulk_msg (skel->dev, |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 230 | usb_rcvbulkpipe (skel->dev, |
| 231 | skel->bulk_in_endpointAddr), |
| 232 | skel->bulk_in_buffer, |
| 233 | skel->bulk_in_size, |
| 234 | &count, HZ*10); |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 235 | /* if the read was successful, copy the data to user space */ |
| 236 | if (!retval) { |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 237 | if (copy_to_user (buffer, skel->bulk_in_buffer, count)) |
| 238 | retval = -EFAULT; |
| 239 | else |
| 240 | retval = count; |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 241 | } |
| 242 | |
| 243 | |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 244 | The :c:func:`usb_bulk_msg` function can be very useful for doing single reads |
| 245 | or writes to a device; however, if you need to read or write constantly to |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 246 | a device, it is recommended to set up your own urbs and submit them to |
| 247 | the USB subsystem. |
| 248 | |
| 249 | When the user program releases the file handle that it has been using to |
| 250 | talk to the device, the release function in the driver is called. In |
| 251 | this function we decrement our private usage count and wait for possible |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 252 | pending writes:: |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 253 | |
| 254 | /* decrement our usage count for the device */ |
| 255 | --skel->open_count; |
| 256 | |
| 257 | |
| 258 | One of the more difficult problems that USB drivers must be able to |
| 259 | handle smoothly is the fact that the USB device may be removed from the |
| 260 | system at any point in time, even if a program is currently talking to |
| 261 | it. It needs to be able to shut down any current reads and writes and |
| 262 | notify the user-space programs that the device is no longer there. The |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 263 | following code (function ``skel_delete``) is an example of how to do |
| 264 | this:: |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 265 | |
| 266 | static inline void skel_delete (struct usb_skel *dev) |
| 267 | { |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 268 | kfree (dev->bulk_in_buffer); |
| 269 | if (dev->bulk_out_buffer != NULL) |
| 270 | usb_free_coherent (dev->udev, dev->bulk_out_size, |
| 271 | dev->bulk_out_buffer, |
| 272 | dev->write_urb->transfer_dma); |
| 273 | usb_free_urb (dev->write_urb); |
| 274 | kfree (dev); |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 275 | } |
| 276 | |
| 277 | |
| 278 | If a program currently has an open handle to the device, we reset the |
| 279 | flag ``device_present``. For every read, write, release and other |
| 280 | functions that expect a device to be present, the driver first checks |
| 281 | this flag to see if the device is still present. If not, it releases |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 282 | that the device has disappeared, and a ``-ENODEV`` error is returned to the |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 283 | user-space program. When the release function is eventually called, it |
| 284 | determines if there is no device and if not, it does the cleanup that |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 285 | the ``skel_disconnect`` function normally does if there are no open files |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 286 | on the device (see Listing 5). |
| 287 | |
| 288 | Isochronous Data |
| 289 | ================ |
| 290 | |
| 291 | This usb-skeleton driver does not have any examples of interrupt or |
| 292 | isochronous data being sent to or from the device. Interrupt data is |
| 293 | sent almost exactly as bulk data is, with a few minor exceptions. |
| 294 | Isochronous data works differently with continuous streams of data being |
| 295 | sent to or from the device. The audio and video camera drivers are very |
| 296 | good examples of drivers that handle isochronous data and will be useful |
| 297 | if you also need to do this. |
| 298 | |
| 299 | Conclusion |
| 300 | ========== |
| 301 | |
| 302 | Writing Linux USB device drivers is not a difficult task as the |
| 303 | usb-skeleton driver shows. This driver, combined with the other current |
| 304 | USB drivers, should provide enough examples to help a beginning author |
| 305 | create a working driver in a minimal amount of time. The linux-usb-devel |
| 306 | mailing list archives also contain a lot of helpful information. |
| 307 | |
| 308 | Resources |
| 309 | ========= |
| 310 | |
| 311 | The Linux USB Project: |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 312 | http://www.linux-usb.org/ |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 313 | |
| 314 | Linux Hotplug Project: |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 315 | http://linux-hotplug.sourceforge.net/ |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 316 | |
| 317 | Linux USB Working Devices List: |
Mauro Carvalho Chehab | 0e8c46d | 2017-04-05 10:23:00 -0300 | [diff] [blame] | 318 | http://www.qbik.ch/usb/devices/ |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 319 | |
| 320 | linux-usb-devel Mailing List Archives: |
| 321 | http://marc.theaimsgroup.com/?l=linux-usb-devel |
| 322 | |
| 323 | Programming Guide for Linux USB Device Drivers: |
Stefan Tatschner | 3ece780 | 2017-12-08 01:12:09 +0100 | [diff] [blame] | 324 | http://lmu.web.psi.ch/docu/manuals/software_manuals/linux_sl/usb_linux_programming_guide.pdf |
Mauro Carvalho Chehab | 4ad4b21 | 2017-04-05 10:22:57 -0300 | [diff] [blame] | 325 | |
| 326 | USB Home Page: http://www.usb.org |