Linux kernel driver is one of the most important components of the Linux system. They are responsible for communicating with hardware devices so that the operating system can correctly identify and use the hardware. However, developing Linux kernel drivers is not an easy task. In this article, we will delve into the implementation method of Linux kernel driver and provide readers with comprehensive understanding and guidance.
In Linux drivers, the bottom layer of the USB driver is the USB host controller hardware. Running on top of it is the USB host controller driver. Above the host controller is the USB core layer, and the upper layer is the USB device driver. layer (insert device drivers such as U disk, mouse, USB to serial port, etc. on the host computer).
Therefore, in the host-side hierarchy, the USB driver to be implemented includes two categories: USB host controller driver and USB device driver. The former controls the USB device inserted into it, and the latter controls how the USB device communicates with the host. The Linux kernel USB core is responsible for the main work of USB driver management and protocol processing. The USB core between the host controller driver and the device driver is very important. Its functions include: by defining some data structures, macros and functions, it provides a programming interface for the device driver upwards and provides a programming interface for the USB host controller driver downwards; Global variables maintain USB device information of the entire system; complete device hot-plug control, bus data transmission control, etc.
The USB device side driver in the Linux kernel is divided into three levels: UDC driver, Gadget API and Gadget driver. The UDC driver directly accesses the hardware, controls the underlying communication between the USB device and the host, and provides callback functions for hardware-related operations to the upper layer. The current Gadget API is a simple wrapper around the UDC driver callback functions. The Gadget driver specifically controls the implementation of USB device functions, allowing the device to display features such as "network connection", "printer" or "USB Mass Storage". It uses the Gadget API to control UDC to implement the above functions. The Gadget API isolates the lower-layer UDC driver from the upper-layer Gadget driver, making it possible to separate the implementation of functions from the underlying communication when writing a USB device-side driver in a Linux system.
A brief description of these four levels is as follows:
Devices typically have one or more configuration
Configurations often have one or more interfaces
The interface has no or more than one endpoint
\4. The most basic form of USB communication is through endpoints (USB endpoints are divided into four types: Interrupt, Bulk, ISO, and Control. Each (different uses), USB endpoints can only transmit data in one direction, from the host to the device or from the device to the host. The endpoint can be regarded as a one-way pipe. The driver registers the driver object with the USB subsystem and later uses the manufacturer and device identification to determine whether the hardware has been installed. The USB core uses a list (a structure containing the manufacturer ID and device ID) to determine which driver to use for a device, and the hotplug script uses this to determine when a specific device is plugged into the system. Which driver's Probe should be automatically executed.
\1) USB device: corresponding data structure struct usb_device
\2) Configuration: struct usb_host_config (only one configuration can take effect at any time)
3) USB interface: struct usb_interface (The USB core passes it to the USB device driver, and the USB device driver is responsible for subsequent control. A USB interface represents a basic function, and each USB driver controls an interface. So a physical Hardware devices on may require more than one driver.)
4) Endpoint: struct usb_host_endpoint, the real endpoint information it contains is in another structure: struct usb_endpoint_descriptor (endpoint descriptor, contains all USB-specific data).
The most basic form of USB communication is through something called an endpoint. A USB endpoint can only transfer data in one direction (from the host to the device (called an output endpoint) or from the device to the host (called an input endpoint)). An endpoint can be thought of as a one-way pipe.
There are 4 different types of USB endpoints, each with different data transmission methods:
1) CONTROL
Control endpoints are used to control access to different parts of a USB device. They are typically used to configure the device, obtain device information, send commands to the device, or obtain device status reports. These endpoints are usually smaller. Every USB device has a control endpoint called "Endpoint 0", which is used by the USB core to configure the device when plugged in. The USB protocol ensures that there is always enough bandwidth left for the control endpoint to transmit data to the device.
2) Interrupt INTERRUPT
Whenever the USB host requests data from the device, the interrupt endpoint transfers a small amount of data at a fixed rate. This is the primary data transfer method for USB keyboards and mice. It is also used to transfer data to USB devices to control the device. Usually not used to transfer large amounts of data. The USB protocol ensures that there is always enough bandwidth left for the interrupt endpoint to transmit data to the device.
3) Batch BULK
Bulk endpoints are used to transfer large amounts of data. These endpoints are usually much larger than interrupt endpoints. They are commonly used in situations where there cannot be any data loss. The USB protocol does not guarantee that the transfer will be completed within a specific time frame. If there is not enough space on the bus to send the entire BULK packet, it is divided into multiple packets for transmission. These endpoints are commonly used on printers, USB Mass Storage, and USB network devices.
4) ISOCHRONOUS
Isochronous endpoints also transfer large amounts of data in batches, but this data is not guaranteed to be delivered. These endpoints are used in devices that can handle data loss and rely more on maintaining a continuous flow of data. Such as audio and video equipment and so on.
Control and bulk endpoints are used for asynchronous data transfer, while interrupt and isochronous endpoints are periodic. This means that these endpoints are set up to transmit data continuously at a fixed time, and the USB core reserves the corresponding bandwidth for them.
struct usb_host_endpoint{ struct usb_endpoint_descriptor desc;//端点描述符 struct list_head urb_list;//此端点的URB对列,由USB核心维护 void *hcpriv; struct ep_device *ep_dev; /* For sysfs info */ unsigned char*extra;/* Extra descriptors */ int extralen; int enabled;};
When the USB device driver is called to call usb_submit_urb to submit an urb request, int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb) will be called to add this urb to the tail of the urb_list. (hcd: Host Controller Driver, corresponding data structure struct usb_hcd)
All USB communications are in request->response mode, and USB devices will not actively send data to the Host. Write data: The USB device driver sends an urb request to the USB device, and the USB device does not need to return data. Read data: The USB device driver sends an urb request to the USB device, and the USB device needs to return data.
USB device driver communicates with all USB devices through urb. urb is described by the struct urb structure (include/linux/usb.h).
urb sends or receives data in an asynchronous manner to a specific endpoint of a specific USB device. A USB device driver can assign multiple urbs to a single endpoint or reuse a single urb for multiple different endpoints, depending on the driver's needs. Each endpoint in the device handles an urb queue, so multiple urbs can be sent to the same endpoint before the queue is emptied.
The typical life cycle of an urb is as follows:
(1) Created;
(2) A specific endpoint assigned to a specific USB device;
(3) Submitted to USB core;
(4) Submitted by the USB core to a specific USB host controller driver for a specific device;
(5) Processed by the USB host controller driver and transmitted to the device;
(6) After the above operations are completed, the USB host controller driver notifies the USB device driver.
urb can also be canceled at any time by the driver that submitted it; if the device is removed, urb can be canceled by the USB core. urbs are created dynamically and contain an internal reference count so that they can be automatically released when the last user releases them.
8.1 Submit urb
Once the urb is properly created and initialized, it can be submitted to the USB core to be sent out to the USB device. This is achieved by calling the function sb_submit_urb.
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
parameter:
struct urb *urb: pointer to the submitted urb
gfp_t mem_flags: Uses the same parameters passed to the kmalloc call to tell the USB core how to allocate memory buffers in a timely manner
Because function usb_submit_urb can be called at any time (including from an interrupt context), the mem_flags variable must be set correctly. Depending on the time usb_submit_urb is called, only 3 valid values are available:
GFP_ATOMIC
This value should be used as long as the following conditions are met:
\1) The caller is in an urb end handler, interrupt handler, bottom half, tasklet or a timer callback function.
\2) The caller holds a spin lock or a read-write lock. Note that if a semaphore is being held, this value is unnecessary.
\3) current->state is not TASK_RUNNING. Unless the driver has changed the current state by itself, the state should always be TASK_RUNNING.
GFP_NOIO
The driver is in block I/O processing. It should also be used in error handling for all storage types.
GFP_KERNEL
All other situations that do not fall into the categories previously mentioned
After the urb is successfully submitted to the USB core, no members of the urb structure can be accessed until the end processing routine function is called
8.2 urb end processing routine
If usb_submit_urb is successfully called and transfers control of the urb to the USB core, the function returns 0; otherwise a negative error code is returned. If the function call is successful, the end processing routine will be called when the urb is ended. Called once. When this function is called, the USB core completes the urb and returns its control to the device driver.
There are only 3 situations to end urb and call the end processing routine:
(1)urb is successfully sent to the device, and the device returns the correct confirmation. If so, the status variable in urb is set to 0.
(2) An error occurs, and the error value is recorded in the status variable in the urb structure.
(3) URB unlink from the USB core. This occurs either when the driver tells the USB core to cancel a submitted urb by calling usb_unlink_urb or usb_kill_urb, or when an urb has been submitted to it and the device is removed from the system.
In the struct usb_driver structure, there are 2 functions that the USB core calls at the appropriate time:
(1) When the device is plugged in, if the USB core thinks that the driver can handle it (the USB core uses a list (a structure containing the manufacturer ID and device number ID) to determine which driver to use for a device) , the probe function is called. The probe function checks the device information passed to it and determines whether the driver is truly suitable for this device.
(2) For some reasons, when the device is removed or the driver no longer controls the device, call the disconnect function and do appropriate cleanup.
Both the detection and disconnection callback functions are called in the USB hub kernel thread context, so it is legal for them to sleep. In order to shorten the USB detection time, most of the work is completed when the device is turned on as much as possible. This is because the USB core is The addition and removal of USB devices is handled in a thread, so any slow device driver may make USB device detection time longer.
9.1 Detection function analysis
In the probe callback function, the USB device driver should initialize any local structures it may use to manage the USB device and save all required device information to the local structures, because it is usually easier to do this at this time. In order to communicate with the device, the USB driver Usually the endpoint address and buffer size of the device are detected.
In this article, we introduce the implementation method of Linux kernel driver in detail, including the kernel driver framework, kernel module writing, device driver registration and unregistration, etc. We believe that through studying this article, readers can have a deeper understanding of the implementation principles of the Linux kernel driver and provide more reference and help for their own development work.
The above is the detailed content of Detailed explanation of Linux USB driver workflow. For more information, please follow other related articles on the PHP Chinese website!