I recently want to create a project on LTE. The drivers provided by the module manufacturers include USB-to-serial-to-USB Nic drivers. The project has not been approved yet, so it is relatively empty, the idle egg pains have been studied by the USB-to-serial drive. This article tries its best to use a picture to illustrate things in a whitelist and dedicate it to the tangle of children's shoes.
First, let's talk about the Linux style.
1. Linux size struct is actually an object-oriented method (if struct is compared to a class, kmalloc is the instantiation of the class, and the function pointer in the struct is the method, as well as refactoring and polymorphism)
2. in Linux, the device is an object, the driver is an object, and the two are separated.
Now let's look at the overall driver architecture.
The USB _ serial device is a common serial port device.
We can see several main source code files in the driver.
Main realization of usb-serial.c Module
Bus. c usb_serial bus driver, drivers and devices must be registered on this bus
Generic. c generic user driver. If you write your own driver, you only need to replace the generic. C function with your own implementation. Generally, this driver can adapt to most devices.
Now let's take a look at the initialization process of the usb_serial module.
static int __init usb_serial_init(void){int i;int result;usb_serial_tty_driver = alloc_tty_driver(SERIAL_TTY_MINORS);if (!usb_serial_tty_driver)return -ENOMEM;/* Initialize our global data */for (i = 0; i < SERIAL_TTY_MINORS; ++i)serial_table[i] = NULL;result = bus_register(&usb_serial_bus_type);if (result) {printk(KERN_ERR "usb-serial: %s - registering bus driver " "failed\n", __func__);goto exit_bus;}usb_serial_tty_driver->owner = THIS_MODULE;usb_serial_tty_driver->driver_name = "usbserial";usb_serial_tty_driver->name = "ttyUSB";usb_serial_tty_driver->major = SERIAL_TTY_MAJOR;usb_serial_tty_driver->minor_start = 0;usb_serial_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;usb_serial_tty_driver->subtype = SERIAL_TYPE_NORMAL;usb_serial_tty_driver->flags = TTY_DRIVER_REAL_RAW |TTY_DRIVER_DYNAMIC_DEV;usb_serial_tty_driver->init_termios = tty_std_termios;usb_serial_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD| HUPCL | CLOCAL;usb_serial_tty_driver->init_termios.c_ispeed = 9600;usb_serial_tty_driver->init_termios.c_ospeed = 9600;tty_set_operations(usb_serial_tty_driver, &serial_ops);result = tty_register_driver(usb_serial_tty_driver);if (result) {printk(KERN_ERR "usb-serial: %s - tty_register_driver failed\n", __func__);goto exit_reg_driver;}/* register the USB driver */result = usb_register(&usb_serial_driver);if (result < 0) {printk(KERN_ERR "usb-serial: %s - usb_register failed\n", __func__);goto exit_tty;}/* register the generic driver, if we should */result = usb_serial_generic_register(debug);if (result < 0) {printk(KERN_ERR "usb-serial: %s - registering generic " "driver failed\n", __func__);goto exit_generic;}printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n");return result;exit_generic:usb_deregister(&usb_serial_driver);exit_tty:tty_unregister_driver(usb_serial_tty_driver);exit_reg_driver:bus_unregister(&usb_serial_bus_type);exit_bus:printk(KERN_ERR "usb-serial: %s - returning with error %d\n", __func__, result);put_tty_driver(usb_serial_tty_driver);return result;}
Simple
The first step is to register the TTY driver of usb_seria into the TTY driver list. Then, call open, write, and read to call the functions in the TTY driver first, then the function pointer will point to the driver defined by the user. This should be an application of polymorphism.
Step 2 register the usb_seria driver into the driver list in usb_core.
Only the usb_serial module driver works, and the device does not work properly.
Layer 1: USB _ serial driver, most of the implementations of the device are here
Level 2: user-driven, without having to know too much details, you can implement the entire driver function by implementing several callback functions.
Generic. C is a common user-driven model and is compatible with most devices.
The module initialization function of generic. C below
int usb_serial_generic_register(int _debug){int retval = 0;debug = _debug;#ifdef CONFIG_USB_SERIAL_GENERICgeneric_device_ids[0].idVendor = vendor;generic_device_ids[0].idProduct = product;generic_device_ids[0].match_flags =USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT;/* register our generic driver with ourselves */retval = usb_serial_register(&usb_serial_generic_device);if (retval)goto exit;retval = usb_register(&generic_driver);if (retval)usb_serial_deregister(&usb_serial_generic_device);exit:#endifreturn retval;}
The first step is to determine your own signature. Here, the vendor and product are used to indicate the driver of the device.
Step 2: register the user driver into the usb_serial_bus bus. Linux prefers to do this ......, Use bus to manage a class of devices
Step 3 register the USB driver into the USB _ core driver list
Someone may ask why USB will automatically find the corresponding driver. This is actually a matching process. In Linux, it is called probe.
The first step is to determine the matching value of the driver. The matching value of the driver is registered in both the second and third steps.
In fact, the Code retval = usb_register (& generic_driver) in the third step is to register the generic driver into the USB driver list. When a device is inserted, It is poll to it, then the driver probe will be called, which is the following function
static int generic_probe(struct usb_interface *interface, const struct usb_device_id *id){const struct usb_device_id *id_pattern;id_pattern = usb_match_id(interface, generic_device_ids);if (id_pattern != NULL)return usb_serial_probe(interface, id);return -ENODEV;}
The most important thing is to call the usb_serial_probe (interface, ID) function.
Now let's look at the matching process of usb_serial_probe ().
Through probe, we finally bind the/dev/ttysn device to the usb_serial_port object. We operate on the/dev/ttysn device, corresponding to
static const struct tty_operations serial_ops = {.open =serial_open,.close =serial_close,.write =serial_write,.hangup = serial_hangup,.write_room =serial_write_room,.ioctl =serial_ioctl,.set_termios =serial_set_termios,.throttle =serial_throttle,.unthrottle =serial_unthrottle,.break_ctl =serial_break,.chars_in_buffer =serial_chars_in_buffer,.tiocmget =serial_tiocmget,.tiocmset =serial_tiocmset,.cleanup = serial_cleanup,.install = serial_install,.proc_fops =&serial_proc_fops,};
For example, we write/dev/ttysn, write-> serial_write
static int serial_write(struct tty_struct *tty, const unsigned char *buf,int count){struct usb_serial_port *port = tty->driver_data;int retval = -ENODEV;if (port->serial->dev->state == USB_STATE_NOTATTACHED)goto exit;dbg("%s - port %d, %d byte(s)", __func__, port->number, count);/* pass on to the driver specific version of this function */retval = port->serial->type->write(tty, port, buf, count);exit:return retval;}
Struct usb_serial_port * Port = tty-> driver_data; the port object corresponding to this tty object is found,
The port object contains the driver information and the urb buffer information. The final calling is the write method in the user drive we write.
The user driver sends the data to be sent through urb.
Summary:
By analyzing the USB serial port driver, we can deduce the general working mode of other USB devices. The next step is to analyze the driver of the USB Nic.
The complex code structure of Linux is actually an object-oriented idea.