USB协议分析(pl2303)
在hub.c的hub_port_connect_change中,检测到有USB设备插进来后执行该代码。
1. USB协议中规定,插入设备后,主机要至少等待100ms,让设备完成插入以及上电动作;hub_port_debounce(hub, port1)就是起到这个作用。
2. 接着在hub_port_init函数中,USB协议规定,上电后,HUB要响USB设备发送持续10ms的复位信号(D+、D-都拉低), hub_port_reset(hub, port1, udev, delay)完成该动作。
这样,当信号完成后,端口就有效了,设备处于缺省状态(Default state),并且获得主机提供的100mA的电流;主机可以和设备地址0,端点0(即default pipe)通过控制传输进行通讯,端点0比较特殊,可以写入,可以读出;其他端点都是单向的,比如U盘有两个断电,从端点1写入数据,端点2读出数据。
要注意,USB术语中端点的IN和OUT,是从主机端的立场来看的,比如USB鼠标输入数据到电脑,对应的端点即为输入端点。
3.
#define GET_DESCRIPTOR_BUFSIZE 64for (j = 0; j < 3; ++j) {buf->bMaxPacketSize0 = 0;r = usb_control_msg(udev, usb_rcvaddr0pipe(),USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,USB_DT_DEVICE << 8, 0,buf, GET_DESCRIPTOR_BUFSIZE,initial_descriptor_timeout);switch (buf->bMaxPacketSize0) {case 8: case 16: case 32: case 64: case 255:if (buf->bDescriptorType ==USB_DT_DEVICE) {r = 0;break;}/* FALL THROUGH */default:if (r == 0)r = -EPROTO;break;}if (r == 0)break;}
获取设备描述符(device descriptor),请求的长度是64,虽然一般的设备描述符长度只有18,但主机并不在乎,从switch语句看出,它更在乎的是描述符的总长度信息。
4. 获取完设备描述符后,接着hub_port_reset(hub, port1, udev, delay)再次对设备进行复位(USB协议中并没有这一步的要求),这次复位的目的是使设备进入一个确定的状态。
5. 往下,hub_set_address(udev, devnum)给设备分配一个唯一的新地址,设备处于编址状态(Address state).
6. 有了新地址后,再次获取设备描述符,用usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE)来获得,USB_DT_DEVICE_SIZE 为18。这次,主机会认真解析设备描述符的信息,包括断电0的最大包长度、设备所支持的配置个数、设备类型、VID、PID等。hub_port_init函数就完成了。
7. 回到hub_port_connect_change函数继续往下,usb_new_device(udev)函数:
int usb_new_device(struct usb_device *udev)
{
。。。。。。err = usb_enumerate_device(udev); /* Read descriptors */
。。。。。。/* Tell the world! */announce_device(udev);
。。。。。。/* Register the device. The device driver is responsible* for configuring the device and invoking the add-device* notifier chain (used by usbfs and possibly others).*/err = device_add(&udev->dev);
。。。。。。
}
usb_enumerate_device(udev)称为枚举过程,就是读取配置描述符(configuration descriptor)并解析的过程:
static int usb_enumerate_device(struct usb_device *udev)
{int err; if (udev->config == NULL) {err = usb_get_configuration(udev);if (err < 0) { dev_err(&udev->dev, "can't read configurations, error %d\n",err);goto fail;} } if (udev->wusb == 1 && udev->authorized == 0) { udev->product = kstrdup("n/a (unauthorized)", GFP_KERNEL);udev->manufacturer = kstrdup("n/a (unauthorized)", GFP_KERNEL);udev->serial = kstrdup("n/a (unauthorized)", GFP_KERNEL);} else {/* read the standard strings and cache them if present */udev->product = usb_cache_string(udev, udev->descriptor.iProduct);udev->manufacturer = usb_cache_string(udev,udev->descriptor.iManufacturer);udev->serial = usb_cache_string(udev, udev->descriptor.iSerialNumber);} err = usb_enumerate_device_otg(udev);
fail:return err;
}
USB协议规定设备的配置个数不能大于8个,至少有1个;从前面的设备描述符中主机可以知道设备有多少中配置:
ncfg = dev->descriptor.bNumConfigurations
所以usb_get_configuration(udev)作用就是用for循环依次读取配置描述符信息并解析保存起来,要注意,在这里已经获取到了接口描述符和字符串描述符(如果有的话),所以一并解析保存在一个usb_host_config(每个配置描述符创建一个该结构)的结构体中:
struct usb_host_config {struct usb_config_descriptor desc;char *string; /* iConfiguration string, if present *//* List of any Interface Association Descriptors in this* configuration. */struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS];/* the interfaces associated with this configuration,* stored in no particular order */struct usb_interface *interface[USB_MAXINTERFACES];/* Interface information available even when this is not the* active configuration */struct usb_interface_cache *intf_cache[USB_MAXINTERFACES];unsigned char *extra; /* Extra descriptors */int extralen;
};
announce_device(udev)作用是打印信息,包括PID、VID、SN等告诉我们有新设备连接上来了;
device_add(&udev->dev)就把该设备添加到USB总线上了,因为这时候主机已经从设备出获取了足够的信息,USB HUB只能做到这里了,后续的事情就要由具体的设备驱动来完成了。
根据前一篇文章的分析,接着往下走就来到了generic.c的generic_probe方法中:
static int generic_probe(struct usb_device *udev)
{int err, c;/* Choose and set the configuration. This registers the interfaces* with the driver core and lets interface drivers bind to them.*/if (usb_device_is_owned(udev)); /* Don't configure if the device is owned */else if (udev->authorized == 0)dev_err(&udev->dev, "Device is not authorized for usage\n");else {c = usb_choose_configuration(udev);if (c >= 0) {err = usb_set_configuration(udev, c); if (err) {dev_err(&udev->dev, "can't set config #%d, error %d\n",c, err);/* This need not be fatal. The user can try to* set other configurations. */} } } /* USB device state == configured ... usable */usb_notify_add_device(udev);return 0;
}
usb_choose_configuration(udev)会根据一些条件来选择一个配置,比如判断该配置下描述的电流是否过大,然后LINUX根据自己喜好,选择了一个不是USB_CLASS_VENDOR_SPEC的配置,代码片段如下:
/* From the remaining configs, choose the first one whose* first interface is for a non-vendor-specific class.* Reason: Linux is more likely to have a class driver* than a vendor-specific driver. */else if (udev->descriptor.bDeviceClass !=USB_CLASS_VENDOR_SPEC && (desc && desc->bInterfaceClass !=USB_CLASS_VENDOR_SPEC)) {best = c;break;} #define USB_CLASS_VENDOR_SPEC 0xff
USB协议中规定,在 usb_device_descriptor类中bDeviceClass代表的是设备的类型代码,如果是0x01 ~ 0xfe,代表标准设备,如果是0xff代表厂商自定义设备;usb_interface_descriptor的bInterfaceClass也一样代表设备类型。
其实大多数设备只有一种配置而已,这样就返回了配置编号。
接着usb_set_configuration(udev, c)把选择的配置写入设备。在该函数中完成配置后用 usb_enable_interface(dev, intf, true)使接口生效;创建cp->desc.bNumInterfaces个代表该配置下接口的device并用device_add(&intf->dev)添加到USB核心中去;可见,一个接口对应一个驱动,bNumInterfaces个接口就要bNumInterfaces个驱动了;不过由于一个device_driver可以对应多个device,所以像pl2303就是一个driver,但是可以处理多个接口。
下边通过具体的例子来分析这个接口。
把pl2303线插入平板上,在/sys/bus/usb/devices多出了两个设备
3-1
3-1:1.0
3-1代表的就是usb_device结构体中的device,这是在hub_port_connect_change中创建的,名字的由来如下:
dev_set_name(&dev->dev, "%d-%s", bus->busnum, dev->devpath);
3代表这个pl2303挂在第三个控制器上,1代表root hub下的一个控制器,如果pl2303再通过一个外接hub连接到平板上,则为2 ;
3-1:1.0代表的就是pl2303的接口,可见该设备只有一个接口,名字的创建如下:
dev_set_name(&intf->dev, "%d-%s:%d.%d",dev->bus->busnum, dev->devpath,configuration, alt->desc.bInterfaceNumber);
用的是1配置,0接口。进入3-1:1.0目录看到如下信息:
bAlternateSetting
bInterfaceClass
bInterfaceNumber
bInterfaceProtocol
bInterfaceSubClass
bNumEndpoints
driver
ep_02
ep_81
ep_83
modalias
power
subsystem
supports_autosuspend
ttyUSB0
uevent
看到有三个端点,再进入端点目录发现ep_02为bulk传输,方向为out;ep_81为Interrupt传输,方向为in;ep_83为Bulk传输,方向为in; bInterfaceClass为ff,表示是厂商自定义的设备。
而进入3-1目录看到如下信息(有设备描述符和配置描述符的信息):
3-1:1.0
authorized
avoid_reset_quirk
bConfigurationValue
bDeviceClass
bDeviceProtocol
bDeviceSubClass
bMaxPacketSize0
bMaxPower
bNumConfigurations
bNumInterfaces
bcdDevice
bmAttributes
busnum
configuration
descriptors
dev
devnum
devpath
driver
ep_00
idProduct
idVendor
manufacturer
maxchild
power
product
quirks
removable
remove
speed
subsystem
uevent
urbnum
usb_device
version
bNumConfigurations为1,只有一种配置;bNumInterfaces为1,只有一个接口;ep_00代表控制端点0,因为这是每个usb设备都必须有的,作为共性放到了这里。
最后进入pl2303驱动看usb_serial_probe函数的实现。
int usb_serial_probe(struct usb_interface *interface,const struct usb_device_id *id)
{
.........................type = search_serial_device(interface);
........................serial = create_serial(dev, interface, type);
.........................for (i = 0; i < num_bulk_in; ++i) {usb_fill_bulk_urb(port->read_urbs[j], dev,usb_rcvbulkpipe(dev,endpoint->bEndpointAddress),port->bulk_in_buffers[j], buffer_size,serial->type->read_bulk_callback,port);}
}
............................................for (i = 0; i < num_bulk_out; ++i) {usb_fill_bulk_urb(port->write_urbs[j], dev,usb_sndbulkpipe(dev,endpoint->bEndpointAddress),port->bulk_out_buffers[j], buffer_size,serial->type->write_bulk_callback,port);
}
.....................................if (serial->type->read_int_callback) {for (i = 0; i < num_interrupt_in; ++i) { usb_fill_int_urb(port->interrupt_in_urb, dev,usb_rcvintpipe(dev,endpoint->bEndpointAddress),port->interrupt_in_buffer, buffer_size,serial->type->read_int_callback, port,endpoint->bInterval);
}
...............................................if (serial->type->write_int_callback) {for (i = 0; i < num_interrupt_out; ++i) {usb_fill_int_urb(port->interrupt_out_urb, dev,usb_sndintpipe(dev,endpoint->bEndpointAddress),port->interrupt_out_buffer, buffer_size,serial->type->write_int_callback, port,endpoint->bInterval);
}
...................................
}
其实就是用usb_fill_bulk_urb或者usb_fill_int_urb函数来注册中断或者批量传输函数,以后有数据到来或者有数据需要发送,调用相应的函数来处理。
不过要注意,这里还用到serial/generic.c中的相应函数,该文件提供了通用的访问接口,比如pl2303.c中没有定义read_bulk_callback函数,但是在usb-serical.c中有:
#define set_to_generic_if_null(type, function) \do { \if (!type->function) { \type->function = usb_serial_generic_##function; \dbg("Had to override the " #function \" usb serial operation with the generic one.");\} \} while (0)static void fixup_generic(struct usb_serial_driver *device)
{set_to_generic_if_null(device, open);set_to_generic_if_null(device, write);set_to_generic_if_null(device, close);set_to_generic_if_null(device, write_room);set_to_generic_if_null(device, chars_in_buffer);set_to_generic_if_null(device, read_bulk_callback);set_to_generic_if_null(device, write_bulk_callback); set_to_generic_if_null(device, disconnect);set_to_generic_if_null(device, release);set_to_generic_if_null(device, process_read_urb);set_to_generic_if_null(device, prepare_write_buffer);
}
从宏定义可以看出,如果没有定义的话,就用generic.c的来代替。
这样,当串口有数据到来时候,调用read_bulk_callback:
void usb_serial_generic_read_bulk_callback(struct urb *urb)
{struct usb_serial_port *port = urb->context;unsigned char *data = urb->transfer_buffer;unsigned long flags;int i;for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) {if (urb == port->read_urbs[i])break;} set_bit(i, &port->read_urbs_free);dbg("%s - port %d, urb %d, len %d\n", __func__, port->number, i,urb->actual_length);if (urb->status) {dbg("%s - non-zero urb status: %d\n", __func__, urb->status);return;} usb_serial_debug_data(debug, &port->dev, __func__,urb->actual_length, data);port->serial->type->process_read_urb(urb);/* Throttle the device if requested by tty */spin_lock_irqsave(&port->lock, flags);port->throttled = port->throttle_req;if (!port->throttled) {spin_unlock_irqrestore(&port->lock, flags);usb_serial_generic_submit_read_urb(port, i, GFP_ATOMIC);} elsespin_unlock_irqrestore(&port->lock, flags);
}
EXPORT_SYMBOL_GPL(usb_serial_generic_read_bulk_callback);
这时候数据已经在urb结构中了,接着调用pl2303.c的process_read_urb方法:
static void pl2303_process_read_urb(struct urb *urb)
{
。。。。。。。。。。if (line_status & UART_OVERRUN_ERROR)tty_insert_flip_char(tty, 0, TTY_OVERRUN);if (port->port.console && port->sysrq) {for (i = 0; i < urb->actual_length; ++i)if (!usb_serial_handle_sysrq_char(port, data[i]))tty_insert_flip_char(tty, data[i], tty_flag);} else {tty_insert_flip_string_fixed_flag(tty, data, tty_flag,urb->actual_length);}tty_flip_buffer_push(tty);tty_kref_put(tty);
}
根据前面tty驱动的分析可知,tty_flip_buffer_push函数就把数据通过链路规程提交给tty 核心层处理了。
USB协议分析(pl2303)相关推荐
- USB 协议分析之 HID 设备(转)
USB 协议分析之 HID 设备 转载于:https://www.cnblogs.com/LittleTiger/p/10764891.html
- USB 协议分析之 HID 设备
1. 简述 USB HID类是USB设备的一个标准设备类,包括的设备非常多.HID类设备定义它属于人机交互操作的设备,用于控制计算机操作的一些方面,如USB鼠标.USB键盘.USB游戏操纵杆等.但HI ...
- USB 协议分析仪器
在做USB驱动或者设备开发的过程中,遇到问题,需要分析USB协议,如果有USB协议分析仪器的话,就很容易分析出问题所在. 以下转自:http://www.crifan.com/more_example ...
- USB 之三 常用抓包/协议分析工具(Bus Hound、USBlyzer、USBTrace、USB Monitor Pro等)
简介 在学习 USB 时,尝试了许多工具.有些是纯软件工具,有些是需要硬件配合.以下仅仅做个记录. Bus Hound 一个比较轻量级纯软件工具,软件界面看着就像上一个世纪的风格.官网为http: ...
- CC2540 USB dongle 分析蓝牙协议包
一.准备工作 1.将cc2540插入电脑usb端口 2.安装驱动(驱动链接:https://download.csdn.net/download/forget_zhx/12838794) 二.打开抓包 ...
- Linux USB驱动分析(一)----USB2.0协议分析
原文地址:http://blog.chinaunix.net/uid-25445243-id-4040449.html 一.USB硬件介绍 1.1.概述 一条USB传输线分别由地线.电源线.D+和D- ...
- 2021年中国工业互联网安全大赛核能行业赛道writeup之usb流量分析
目录 一.USB协议 二.键盘流量 三.鼠标流量 四.writeup 附件题:usb流量分析 题目描述: 具体描述忘记了o(╯□╰)o 大概意思是有个U盘插到电脑上,然后经过一些操作导致该电脑重启了. ...
- 【linux驱动】USB子系统分析
本文针对Linux内核下USB子系统进行分析,主要会涉及一下几个方面: USB基础知识:介绍USB设备相关的基础知识 Linux USB子系统分析:分析USB系统框架,USB HCD/ROOT HUB ...
- MMS(Manufacturing Message Specification)协议分析
1.简介 MMS(Manufacturing Message Specification)中文翻译为制造报文规范,在介绍MMS之前我们先简单科普一下IEC61850标准. IEC61850是电力系统自 ...
最新文章
- Linux下如何查看硬件信息?
- flink sql client读取hive时卡住
- noi 4982 踩方格
- 反向输出dna序列_蛋白质序列反向(逆向)翻译成DNA序列-在线工具
- mysql升级后乱码_Mysql转换或者升级以后出现乱码情况的说明
- Jmeter Web 性能测试入门 (四):一个小实例带你学会 Jmeter 脚本编写
- windows sdk 学习笔记(8)
- 【Solidity】1.一个Solidity源文件的布局 - 深入理解Solidity 1
- golang学习的点点滴滴:if、switch使用
- php 如何下载,php的包怎么下载
- Excelize 发布 2.6.0 版本,功能强大的 Excel 文档基础库
- 前端关于Base64编码的一些技术分析
- 银行硬件维护维修工单小程序开发制作
- 输入netsh winsock reset 重启电脑生效
- html热区坐标,HTML之六:图像的热区连接
- iphone投屏老是显示无法连接服务器,iPhone 无法投屏到电视如何解决?
- android收集备忘录恢复工具,手机小小备忘录数据恢复大问题轻松恢复文件看这里...
- 微信小程序 | 人脸识别的最终解决方案
- 把TeamTalk(即时通讯项目)中的线程池连接池拆出来单独测试。
- [VT虚拟化驱动]正式启动VT
热门文章
- 在 .NET6.0 中实现 Leaf-segment 分布式 ID 生成系统
- SpringCloud-Ribbon负载均衡
- 数值计算——系数矩阵部分对角线为0时线性方程组求解方法(附程序)
- 巡逻保安───导航守卫
- mysql统计本周、本月、近一年数据并分组
- MD5加密中文—在jar包下和idea下加密结果不一致问题
- 已知一个带有表头的单链表,结点结构为data-link,假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。
- Windows 10的中文用户名怎么改成英文?
- Matlab、ArcGIS、stata、SQL、SPSS、Eviews、R语言和量化投资等的部分安装文件和推荐学习资料
- 30岁的人了,一事无成,还有几十万的外债