Linux driver development - USB device driver

Table of contents

1. Introduction to USB protocol

2. Linux USB driver

3. USB device driver example



1. Introduction to USB protocol

        USB (Universal Serial Bus), just like its name, is a universal serial bus used to connect PC peripherals. Plug and play and easy expansion are its biggest features. The so-called plug and play means that the PC can connect to peripherals without powering off, and there is no need to configure the device through jumpers on the hardware. Easy expansion means that it can easily expand more interfaces to connect more peripherals. The USB protocol has mainly gone through three stages: USB1.1, USB2. and USB3.0. Currently, many USB interfaces on PCs support USB3.0, but the USB2.0 protocol is still used on embedded systems, so only the USB2.0 protocol will be discussed later. USB2.0 has three standard rates: low speed 1.5Mb/s, full speed 12Mb/s and high speed 80Mb/s. When the device is connected to the host, it will automatically negotiate and finally determine a rate. Compared with the previous I2C and SPI, the USB protocol is much more complex. This section discusses some related content in the protocol from the perspective of a driver developer. The following figure is a USB topology diagram (quoted from the USB 2.0 protocol, and some of the following figures are also quoted from this protocol).


        USB is also a bus with a master-slave structure. The entire topology is in the shape of a pyramid and is a star connection. The top layer is the host, and the lower layers are the devices connected to the host. They are introduced below.
        USB Host, Host: It initiates USB transmission, and there is also a RootHub, which is usually integrated with the USB host controller. It is a hub that provides the host controller with at least one USB port for connecting devices.

        USB devices are divided into Hub and Function. Hub is a hub, used for bus expansion and providing more USB interfaces. Function is a USB functional device, which is a common USB peripheral. The USB device driver discussed later is also for this Function.
        USB2.0 stipulates that, in addition to the host, the number of USB device layers connected below is up to 6 layers. Each USB device has a unique address. The USB device address uses a 7-bit address, so the address range is 0~127. The 0 address is a special address. When a new USB device is inserted into the USB interface, use this address and The host communicates, and the host then assigns an address to the device. So in theory, a USB host can connect up to 127 USB devices.
        A USB physical device consists of one or more USB logical devices. A USB logical device is embodied as an "interface", and the interface is composed of a set of "endpoints". Next, respectively Be explained.
        According to the description of the protocol, the endpoint is a uniquely identifiable part of the USB device. It is an end point of the communication flow between the host and the device. Each endpoint has an address, which is 4 bits wide. Defined from the perspective of the host, endpoints have two directions: input (data from the device to the host) and output (data from the host to the device), so a USB device has up to 32 endpoints. The host addresses a specific endpoint on a USB device through the device address and endpoint address. Endpoint 0 is a special endpoint that is essential and is mainly used for enumeration of USB devices. There is a large amount of resource information inside the USB device. When a USB device is connected to the USB host, the USB host will actively obtain this information. At this time, endpoint 0 is used.


        Interface is a logical concept. It is a collection of thousands of endpoints used to implement a specific function. If a USB device has multiple interfaces, then it is a multi-functional device.
        In addition, there is also the concept of configuration, which is a collection of multiple interfaces. Only one configuration can be valid at the same time. Ultimately, multiple configurations make up a USB device. Most USB devices have only one configuration and one interface. With the above concepts in mind, it is easy to understand the USB communication flow.
        As shown in the figure below, the client software on the host (Host) passes the buffer (Bufer) and an interface (Interface) of a USB logical device (USB Logical Device). When a certain endpoint transmits data, the communication between the buffer of the client software and the endpoint constitutes a pipe (Pipe). The types of transmission are divided into the following four types.

(1) Control transmission: Burst, aperiodic, communication for request/response. Mainly used for command and status operations, such as data transmission during the enumeration process mentioned earlier. Only endpoint 0 is used for control transmission by default, so endpoint 0 is also called a control endpoint (usually the endpoint used for transmission is called an endpoint, for example, the endpoint used for control transmission is called a control endpoint). The USB protocol defines many standard commands (requests) and responses to these commands. These data are completed through control transmission. In addition, the USB protocol allows manufacturers to customize commands, which are also completed using control transmission.


(2) Isochronous transmission: Some are also called synchronous transmission, which is used for periodic and continuous communication between the host and the device. It is usually used in situations that have high time requirements but do not care much about the correctness of the data, such as audio. In data transmission, if the transmission rate cannot meet the requirements, the sound will pause, but a small amount of data errors will not greatly affect the information provided by the sound.


(3) Interrupt transmission: periodic transmission that ensures that the delay does not exceed a specified value. The interrupt here is not the interrupt we mentioned before, it is more like polling. For example, for an interrupt transmission with a 100ms period, the host will guarantee to initiate a transmission within 100ms. Devices such as keyboards and mice usually use this transmission mode, and the host will periodically obtain the key information of the device.


(4) Block transfer: also called batch transfer, non-periodic large data transfer. It is mainly used for the transmission of large amounts of data without special restrictions on the transmission delay, such as disk devices.
 

        Finally, it should be noted that the protocol stipulates the maximum packet length for various transmissions. Taking full-speed mode as an example, the maximum packet length for control transmission is 64 bytes, isochronous transmission is 1023 bytes, and interrupt transmission is 64 bytes. Bytes, block transfer can be selected among 8, 16, 32, and 64 bytes. Therefore, if a piece of data is larger than the maximum packet length, it must be transmitted several times. In addition, the maximum packet length of each endpoint of the general equipment manufacturer depends on the specific device. It may be smaller than the easy-to-large packet length specified in the protocol, but this information can be obtained during the enumeration phase.


2. Linux USB driver


        The USB driver hierarchy of Linux is similar to the I2C and SPI explained earlier. It is also divided into host controller driver and device driver. Because the host controller driver is usually implemented by the SoC manufacturer, only the USB device driver will be discussed below. As we said before, a USB logical device is embodied as an interface. The structure representing the interface in Linux is

struct usb_interface, which contains a member cur_altsetting, points to the host-side interface description structure struct usb_host_interface. This structure contains the number of endpoints contained in the interface and the detailed information of the configuration descriptor of each endpoint. This information is obtained during the enumeration process when a USB device is connected to the host. These structures have quite a lot of content and will not be listed one by one here. The members used will be explained accordingly in the example code.
        Next, let’s take a look at the structure representing the USB device driver, which is defined as follows (only the members that driver developers care about are listed).

struct usb_driver {
    const char *name;
    int (*probe) (struct usb_interface *intf, const struct usb_device_id *id);
......
    void (*disconnect) (struct usb_interface *intf);
    int (*suspend) (struct usb_interface *intf, pm_message_t message);
    int (*resume) (struct usb_interface *intf);
......
    const struct usb_device_id *id_table;
......
};


        Name: The name of the driver should be unique in the entire USB driver and should be consistent with the name of the module.

        probe: The driver is used to detect whether the interface intf is the interface to be driven. Returning 0 indicates that the interface and driver are successfully bound.
        disconnect: Called when the driver is uninstalled or the device is unplugged.
        Suspend, resume: used for power management.
        id_table: List of USB device IDs supported by the driver. The USB device stores the manufacturer ID and device ID internally. This information will be obtained during the enumeration process. The USB bus driver uses the obtained information to match the ID table in the USB driver. If it matches, the probe function in the driver will be called for further processing. Bind the interface.


        The function prototypes or macros related to USB device drivers and interfaces are as follows.
 

usb_register(driver)
void usb_deregister(struct usb_driver *);
struct usb_device *interface_to_usbdev(struct usb_interface *intf);
void usb_set_intfdata(struct usb_interface *intf, void *data);
void *usb_get_intfdata(struct usb_interface *intf);


        usb_register: Register USB device driver.
        usb_deregister: Unregister the USB device driver.
        interface_to_usbdev: Returns the contained USB device structure struct usb _device object
pointer through the interface intf.
        usb_set_intfdata: Save data to the interface intf.
        usb_get_intfdata: Get the previously saved data pointer from the interface intf. As mentioned before, the communication between the host client software and the device endpoint is through a pipeline. After the driver obtains the endpoint information (including address, type and direction) from the interface, it can construct the pipeline. The corresponding macro is as follows.

usb_sndctrlpipe(dev, endpoint)
usb_rcvctrlpipe(dev, endpoint)
usb_sndisocpipe(dev, endpoint)
usb_rcvisocpipe(dev, endpoint)
usb_sndbulkpipe(dev, endpoint)
usb_rcvbulkpipe(dev, endpoint)
usb_sndintpipe(dev, endpoint)
usb_rcvintpipe(dev, endpoint)



        In addition, you can also use the following function to obtain the maximum packet length of the pipeline.
 

__ul6 usb_maxpacket(struct usb_device *udev, int pipe, int is_out);

        With the pipe in place, the driver can communicate with the endpoint of the USB device. Linux uses struct urb to communicate with USB device endpoints, which is similar to messages on the I2C bus or SPI bus. This structure has many members, so I won’t list them one by one here. The main functions surrounding struct urb are as follows.

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
void usb_free_urb(struct urb *urb);
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
int usb_unlink_urb(struct urb *urb);
void usb_kill_urb(struct urb *urb);
void usb_fill_control_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, unsigned char *setup packet, void *transfer buffer, int buffer_length, usb_complete_t complete_fn, void *context);
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context);
void usb_fill_int_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context, int interval);

        usb_alloc_urb; dynamically allocates a struct urb structure object. iso_packets is the number of packets used by the URB for isochronous transmission. If it is not used for isochronous transmission, it is 0. mem_flags is a mask for memory allocation.

        usb_free_urb; Release URB.
        usb_submit_urb: Submit a URB and initiate USB transfer

        usb_unlink_urb: Undo a submitted URB and do not wait for the URB to be terminated before returning. Used in contexts that cannot sleep.
        usb_kill_urb: Cancel a submitted URB and wait for the URB to be terminated before returning.

        usb fill_control_urb: Fill in a URB for control transmission, urb is the urb to be filled, the object pointer dev is the USB device object pointer to be communicated, pipe is the pipe used for communication, stup_packet is the establishment package of the protocol address, transfer_buffer is the buffer address for transferring data, bufer_length is the buffer length for transferring data, complete_fn points to the callback function after the URB is completed, and context points to the data block that can be set by the USB driver.
        usb_fill_bulk_urb and usb_fill_int_urb are used to fill block transfer URB and interrupt transfer URB respectively. The meaning of the parameters is consistent with the above. The interval parameter of interrupt transfer is used to specify the time interval of interrupt transfer.
        USB transfer is usually performed using the following steps.
 

(1) Use usb_alloc_urb to allocate a URB.
(2) Populate a URB based on the type of transfer. There is no corresponding function for isochronous transmission and it needs to be implemented manually

(3) Use usb_submit_urb to submit a URB to initiate transfer.
(4) Use completion volume or waiting queue to wait for the completion of a URB.
(5) After the URB transmission is completed, the completion callback function is called, where the waiting process is awakened.
(6) After the process is awakened, check the execution results of the URB, including status information and the number of actually completed transmission bytes, etc.

(7) If the URB needs to be revoked midway, use usb_unlink_urb or usb_kill_urb.
(8) URBs that are not used can be released through usb_free_urb.

        An allocated URB can be used multiple times and does not need to be allocated each time, but it must be repopulated before submission. The completion status of the URB is obtained through the status member, and the actual number of transmitted bytes completed is obtained through the actual_length member.
        Using URB to complete USB transfer can achieve more precise control, but it is more complicated to use. The Linux kernel encapsulates some convenient functions, mainly as follows.

int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __ul6 index, void *data, __ul6 size, int timeout);
int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe, void *data,int len, int *actual_length, int timeout);
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout);


        usb_control_msg: used to initiate control transmission. dev is the USB device object pointer to be communicated, pipe is the pipe used for communication, request is the request field in the protocol, requesttype is the type of request, and value and index are also corresponding fields in the protocol. data is a pointer to the buffer, size is the buffer data size, and timeout is
usb_interrupt_msg and usb_bulk_msg are used to initiate interrupt transfer and block transfer respectively. actual_length timeout value. is the actual number of bytes transferred.
 


3. USB device driver example


The development board has an STC89C52RC microcontroller and a PDIUSBD12 USB device-side interface chip. PDIUSBD12 has 4 endpoints in addition to the control endpoint. This book uses the custom device in the development board supporting source code. These 4 endpoints are interrupt input (endpoint address 0x81), interrupt output (endpoint address 0x01), batch input (endpoint address 0x82) and batch output (endpoint address 0x02). The communication protocol of the device application layer is as follows.
(1) The interrupt input endpoint is used to return the values ​​of 8 keys and the count value of key presses and releases. The length is 8 bytes. The meaning of each byte can be found in the following code. .
(2) The interrupt output endpoint is used to control the on and off of 8 LED lights. The length is 8 bytes. The meaning of each byte can be found in the following code.
(3) The batch input endpoint is used to return the data received by the serial port.
(4) The batch output endpoint is used to send data to the serial port. In other words, the batch input endpoint and batch output endpoint complete the transparent transmission of USB and serial port data.
 

        The Linux driver code for this USB device is as follows. In order to highlight the core of the USB driver as much as possible, no code related to concurrency control has been added. In addition, the minor device number of the device is dynamically increased, so the minor device number will change after the device is unplugged and re-inserted. This is also to simplify the code.
 

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/usb.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/uaccess.h>

#include "pdiusbd12.h"

#define PDIUSBD12_MAJOR		256
#define PDIUSBD12_MINOR		10
#define PDIUSBD12_DEV_NAME	"pdiusbd12"

struct pdiusbd12_dev {
	int pipe_ep1_out;
	int pipe_ep1_in;
	int pipe_ep2_out;
	int pipe_ep2_in;
	int maxp_ep1_out;
	int maxp_ep1_in;
	int maxp_ep2_out;
	int maxp_ep2_in;
	struct urb *ep2inurb;
	int errors;
	unsigned int ep2inlen;
	unsigned char ep1inbuf[16];
	unsigned char ep1outbuf[16];
	unsigned char ep2inbuf[64];
	unsigned char ep2outbuf[64];
	struct usb_device *usbdev;
	wait_queue_head_t wq;
	struct cdev cdev;
	dev_t dev;
};

static unsigned int minor = PDIUSBD12_MINOR;

static int pdiusbd12_open(struct inode *inode, struct file *filp)
{
	struct pdiusbd12_dev *pdiusbd12;

	pdiusbd12 = container_of(inode->i_cdev, struct pdiusbd12_dev, cdev);
	filp->private_data = pdiusbd12;

	return 0;
}

static int pdiusbd12_release(struct inode *inode, struct file *filp)
{
	struct pdiusbd12_dev *pdiusbd12;

	pdiusbd12 = container_of(inode->i_cdev, struct pdiusbd12_dev, cdev);
	usb_kill_urb(pdiusbd12->ep2inurb);

	return 0;
}

void usb_read_complete(struct urb * urb)
{
	struct pdiusbd12_dev *pdiusbd12 = urb->context;

	switch (urb->status) {
		case 0:
			pdiusbd12->ep2inlen = urb->actual_length;
			break;
		case -ECONNRESET:
		case -ENOENT:
		case -ESHUTDOWN:
		default:
			pdiusbd12->ep2inlen = 0;
			break;
	}
	pdiusbd12->errors = urb->status;
	wake_up_interruptible(&pdiusbd12->wq);
}

static ssize_t pdiusbd12_read(struct file *filp, char __user *buf, size_t count, loff_t *f_ops)
{
	int ret;
	struct usb_device *usbdev;
	struct pdiusbd12_dev *pdiusbd12 = filp->private_data;

	count = count > sizeof(pdiusbd12->ep2inbuf) ? sizeof(pdiusbd12->ep1inbuf) : count;

	ret = count;
	usbdev = pdiusbd12->usbdev;
	usb_fill_bulk_urb(pdiusbd12->ep2inurb, usbdev, pdiusbd12->pipe_ep2_in, pdiusbd12->ep2inbuf, ret, usb_read_complete, pdiusbd12);
	if (usb_submit_urb(pdiusbd12->ep2inurb, GFP_KERNEL))
		return -EIO;
	interruptible_sleep_on(&pdiusbd12->wq);

	if (pdiusbd12->errors)
		return pdiusbd12->errors;
	else {
		if (copy_to_user(buf, pdiusbd12->ep2inbuf, pdiusbd12->ep2inlen))
			return -EFAULT;
		else
			return pdiusbd12->ep2inlen;
	}
}

static ssize_t pdiusbd12_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{
	int len;
	ssize_t ret = 0;
	struct pdiusbd12_dev *pdiusbd12 = filp->private_data;

	count = count > sizeof(pdiusbd12->ep2outbuf) ? sizeof(pdiusbd12->ep2outbuf) : count;
	if (copy_from_user(pdiusbd12->ep2outbuf, buf, count))
		return -EFAULT;

	ret = usb_bulk_msg(pdiusbd12->usbdev, pdiusbd12->pipe_ep2_out, pdiusbd12->ep2outbuf, count, &len, 10 * HZ);
	if (ret)
		return ret;
	else
		return len;
}

long pdiusbd12_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	int len;
	struct pdiusbd12_dev *pdiusbd12 = filp->private_data;

	if (_IOC_TYPE(cmd) != PDIUSBD12_MAGIC)
		return -ENOTTY;

	switch (cmd) {
	case PDIUSBD12_GET_KEY:
		ret = usb_interrupt_msg(pdiusbd12->usbdev, pdiusbd12->pipe_ep1_in, pdiusbd12->ep1inbuf, 8, &len, 10 * HZ);
		if (ret)
			return ret;
		else {
			if (copy_to_user((unsigned char __user *)arg, pdiusbd12->ep1inbuf, len))
				return -EFAULT;
			else
				return 0;
		}
		break;
	case PDIUSBD12_SET_LED:
		if (copy_from_user(pdiusbd12->ep1outbuf, (unsigned char __user *)arg, 8))
			return -EFAULT;
		ret = usb_interrupt_msg(pdiusbd12->usbdev, pdiusbd12->pipe_ep1_out, pdiusbd12->ep1outbuf, 8, &len, 10 * HZ);
		if (ret)
			return ret;
		else
			return 0;
	default:
		return -ENOTTY;
	}

	return 0;
}

static struct file_operations pdiusbd12_ops = {
	.owner = THIS_MODULE,
	.open = pdiusbd12_open,
	.release = pdiusbd12_release,
	.read = pdiusbd12_read,
	.write = pdiusbd12_write,
	.unlocked_ioctl = pdiusbd12_ioctl,
};

int pdiusbd12_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	static struct pdiusbd12_dev *pdiusbd12;
	struct usb_device *usbdev;
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	int ret = 0;

	pdiusbd12 = kmalloc(sizeof(struct pdiusbd12_dev), GFP_KERNEL);
	if (!pdiusbd12)
		return -ENOMEM;

	usbdev = interface_to_usbdev(intf);
	interface = intf->cur_altsetting;
	if (interface->desc.bNumEndpoints != 4) {
		ret = -ENODEV;
		goto out_no_dev;
	}

	/* EP1 Interrupt IN */
	endpoint = &interface->endpoint[0].desc;
	if (!(endpoint->bEndpointAddress & 0x80)) {	/* IN */
		ret = -ENODEV;
		goto out_no_dev;
	}
	if ((endpoint->bmAttributes & 0x7F) != 3) {	/* Interrupt */
		ret = -ENODEV;
		goto out_no_dev;
	}
	pdiusbd12->pipe_ep1_in = usb_rcvintpipe(usbdev, endpoint->bEndpointAddress);
	pdiusbd12->maxp_ep1_in = usb_maxpacket(usbdev, pdiusbd12->pipe_ep1_in, usb_pipeout(pdiusbd12->pipe_ep1_in));

	/* EP1 Interrupt Out */
	endpoint = &interface->endpoint[1].desc;
	if (endpoint->bEndpointAddress & 0x80) {	/* OUT */
		ret = -ENODEV;
		goto out_no_dev;
	}
	if ((endpoint->bmAttributes & 0x7F) != 3) {	/* Interrupt */
		ret = -ENODEV;
		goto out_no_dev;
	}
	pdiusbd12->pipe_ep1_out = usb_sndintpipe(usbdev, endpoint->bEndpointAddress);
	pdiusbd12->maxp_ep1_out = usb_maxpacket(usbdev, pdiusbd12->pipe_ep1_out, usb_pipeout(pdiusbd12->pipe_ep1_out));

	/* EP2 Bulk IN */
	endpoint = &interface->endpoint[2].desc;
	if (!(endpoint->bEndpointAddress & 0x80)) {	/* IN */
		ret = -ENODEV;
		goto out_no_dev;
	}
	if ((endpoint->bmAttributes & 0x7F) != 2) {	/* Bulk */
		ret = -ENODEV;
		goto out_no_dev;
	}
	pdiusbd12->pipe_ep2_in = usb_rcvintpipe(usbdev, endpoint->bEndpointAddress);
	pdiusbd12->maxp_ep2_in = usb_maxpacket(usbdev, pdiusbd12->pipe_ep2_in, usb_pipeout(pdiusbd12->pipe_ep2_in));

	endpoint = &interface->endpoint[3].desc;
	if (endpoint->bEndpointAddress & 0x80) {	/* OUT */
		ret = -ENODEV;
		goto out_no_dev;
	}
	if ((endpoint->bmAttributes & 0x7F) != 2) {	/* Bulk */
		ret = -ENODEV;
		goto out_no_dev;
	}
	pdiusbd12->pipe_ep2_out = usb_sndintpipe(usbdev, endpoint->bEndpointAddress);
	pdiusbd12->maxp_ep2_out = usb_maxpacket(usbdev, pdiusbd12->pipe_ep2_out, usb_pipeout(pdiusbd12->pipe_ep2_out));

	pdiusbd12->ep2inurb = usb_alloc_urb(0, GFP_KERNEL);
	pdiusbd12->usbdev = usbdev;
	usb_set_intfdata(intf, pdiusbd12);

	pdiusbd12->dev = MKDEV(PDIUSBD12_MAJOR, minor++);
	ret = register_chrdev_region (pdiusbd12->dev, 1, PDIUSBD12_DEV_NAME);
	if (ret < 0)
		goto out_reg_region;

	cdev_init(&pdiusbd12->cdev, &pdiusbd12_ops);
	pdiusbd12->cdev.owner = THIS_MODULE;
	ret = cdev_add(&pdiusbd12->cdev, pdiusbd12->dev, 1);
	if (ret)
		goto out_cdev_add;

	init_waitqueue_head(&pdiusbd12->wq);

	return 0;

out_cdev_add:
	unregister_chrdev_region(pdiusbd12->dev, 1);
out_reg_region:
	usb_free_urb(pdiusbd12->ep2inurb);
out_no_dev:
	kfree(pdiusbd12);
	return ret;
}

void pdiusbd12_disconnect(struct usb_interface *intf)
{
	struct pdiusbd12_dev *pdiusbd12 = usb_get_intfdata(intf);

	cdev_del(&pdiusbd12->cdev);
	unregister_chrdev_region(pdiusbd12->dev, 1);
	usb_kill_urb(pdiusbd12->ep2inurb);
	usb_free_urb(pdiusbd12->ep2inurb);
	kfree(pdiusbd12);
}

static struct usb_device_id id_table [] = {
	{ USB_DEVICE(0x8888, 0x000b) }, 
	{ }
};
MODULE_DEVICE_TABLE(usb, id_table);

static struct usb_driver pdiusbd12_driver = 
{
	.name  = "pdiusbd12",
	.id_table = id_table,
	.probe = pdiusbd12_probe,
	.disconnect = pdiusbd12_disconnect,
};

module_usb_driver(pdiusbd12_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("PDIUSBD12 driver");
#ifndef _PDIUSBD12_H
#define _PDIUSBD12_H

struct d12key {
	unsigned char key[8];
};

struct d12led {
	unsigned char led[8];
};

#define PDIUSBD12_MAGIC   'p'

#define PDIUSBD12_GET_KEY _IOR(PDIUSBD12_MAGIC, 0, struct d12key)
#define PDIUSBD12_SET_LED _IOW(PDIUSBD12_MAGIC, 1, struct d12led)

#endif


        The members of the struct pdiusbd12_dev structure in the code include the pipes corresponding to the 4 endpoints and the maximum packet size of the 4 endpoints. ep2inurb is the urb used by input endpoint 2, which is used to demonstrate the use of urb. errors and ep2inler are the status information of the endpoint and the actual number of bytes obtained after the transmission is completed. Next are the buffers used by the 4 endpoints. wq is the waiting queue head, used to wait for the transmission of input endpoint 2 to be completed.


        Lines 279 to 293 of the code are the definition of the USB driver and the corresponding registration and deregistration. 0x8888 and 0x000b in the id_table are the manufacturer ID and device ID of the device, which can be viewed through the lsusb command after the USB device is inserted. In the pdiusbd12_probe function, use interface_to_usbdev to obtain the USB device object pointer containing the interface through the incoming interface intf. This object pointer will be used multiple times in subsequent USB transmissions. From line 190 to line 200 of the code, obtain the information of the first endpoint (interrupt input endpoint 1), and then determine whether the direction and type of the endpoint are correct. If correct, use usb_rcvintpipe to create a pipe, and use usb_maxpacket to Gets the maximum allowed packet size for the endpoint. From line 201 to line 238 of the code, use the same method to create pipelines corresponding to three other nodes. Line 240 of the code allocates a URB used by bulk input endpoint 2 using usb_alloc_urb, then saves the USB device object pointer and saves the pointer pdiusbd12 to the interface intf using usb_set_intfdata.

        The pdiusbd12_read function first adjusts the number of bytes read, then uses usb_fill_bulk_urb to fill the URB, and specifies the callback function as usb_read_complete. Next, use usb_submit_urb to submit the URB and use interruptible_sleep_on to wait for the callback function to wake up. After the URB transfer is completed, the usb_read_complete function is called. In this function, the status of the transfer and the number of bytes actually transferred are obtained, and then wake_up_interruptible is used to wake up the reading process. The reading process decides whether to copy the data based on the status, and returns an error code or the actual number of bytes read.
The pdiusbd12_write function is simpler. It first copies the user's data into the buffer of output endpoint 2, and then initiates a transfer using usb_bulk_msg. After the function returns, the actual number of bytes sent is stored in the len variable, and 10*HZ specifies a timeout of 10 seconds.
        The method used by the pdiusbd12_ioctl function is similar to that of the pdiusbd12_write function. It uses usb_interrupt_msg to initiate interrupt transmission, obtains the key value and writes data to the device respectively, and controls the on and off of the LED light. The test code is as follows.
 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>

#include "pdiusbd12.h"

int main(int argc, char *argv[])
{
	int fd;
	int ret;
	unsigned char key[8];
	unsigned char led[8];
	unsigned int count, i;

	fd = open("/dev/pdiusbd12", O_RDWR);
	if (fd == -1)
		goto fail;

	while (1) {
		ret = ioctl(fd, PDIUSBD12_GET_KEY, key);
		if (ret == -1) {
			if (errno == ETIMEDOUT)
				continue;
			else
				goto fail;
		}
		switch (key[0]) {
		case 0x00:
			puts("KEYn released");
			break;
		case 0x01:
			puts("KEY2 pressed");
			break;
		case 0x02:
			puts("KEY3 pressed");
			break;
		case 0x04:
			puts("KEY4 pressed");
			break;
		case 0x08:
			puts("KEY5 pressed");
			break;
		case 0x10:
			puts("KEY6 pressed");
			break;
		case 0x20:
			puts("KEY7 pressed");
			break;
		case 0x40:
			puts("KEY8 pressed");
			break;
		case 0x80:
			puts("KEY9 pressed");
			break;
		}

		if (key[0] != 0) {
			led[0] = key[0];
			ret = ioctl(fd, PDIUSBD12_SET_LED, key);
			if (ret == -1)
				goto fail;
		}
	}
fail:
	perror("usb test");
	exit(EXIT_FAILURE);
}


        The above test code only implements the test of the interrupt endpoint. Using the PDIUSBD12_GET_KEY command will trigger the underlying driver to initiate interrupt input transmission. When a key is pressed, the ioctl function will return the key information, otherwise it will return the key information in 10 seconds. After timeout, if timeout occurs, the next cycle will be repeated. The obtained key information is stored in the first byte. Each key corresponds to a bit. When the key is pressed, the corresponding bit position is 1, and other bytes can be ignored. Use the PDIUSBD12_SET_LED command to initiate interrupt output transmission. Only the first byte of the 8 bytes is valid. Each bit of the first byte controls an LED light. If it is 1, it will light up, and if it is 0, it will turn off. This is just fine. Use the key value to control the LED light on and off. So the function implemented by the program is: after pressing a button, a corresponding LED light is lit.
The following are the commands for compilation and testing.

If you want to test endpoint 2, first connect the serial port on the USB device to the host, use the serial port terminal software to open the serial port, the baud rate is 9600. Then use the echo command to write data to the USB device, and the data will be sent to the serial terminal software through the serial port for display. Use the cat command to read the USB device, and the data entered on the serial terminal will be read back and displayed by the cat command.

Guess you like

Origin blog.csdn.net/qq_52479948/article/details/134259397