USB拓扑结构不是以总线方式的,而是一棵由几个点对点的连接构成的树,连接线由4根电缆组成(电源,地线,两个数据线)。
USB主控制器负责询问每一个USB设备是否有数据需要发送,也就是说一个USB设备在没有主控制器要求的情况下是不能发送数据的。
USB端点(endpoint):只能往一个方向传送数据。
控制端点:用于配置设备获取设备信息,发送命令到设备或获取设备的状态报告
中断端点:每当USB主控制器要求设备传输数据时,中断端点就以一个固定的速率来传送少量的数据(中断端点和中断不同,一是它无法向USB主控制器发送数据,二是需要等待USB主控制器轮询)
批量端点:用来传输大批量的数据
等时端点:用来传输大批量数据,但数据是否能够到达,则无法保证
每个端点被捆绑为一个USB接口(Interface),一个接口只对应一个逻辑连接
每个USB diver只能处理一个USB接口,所以一个设备也许会对应多个driver,所以USB core在处理USB设备插入时,会针对不同的接口唤醒它认为合适的driver。
一个或多个USB接口被捆绑为配置。一个USB设备可以有多个配置,并且可以在多个配置之间切换。
linux kernel中的USB代码通过urb(USB请求块)与所有的USB设备通信。
HCD(host control driver)提供主控制器驱动的硬件抽象,它只对USB core一个负责,USB core将用户的请求映射到相关的HCD,用户不能直接访问HCD。换句话说USB core就是HCD与USB设备唯一的桥梁。
USB设备驱动只有一个,即usb_generic_driver这个对象,所有USB设备都要绑定到usb_generic_driver上,它的使命可以概括为:为USB选择合适的配置,让设备进入configured状态,Hub集线器用来连接更多USB设备,硬件上实现了USB设备总线枚举过程,软件上实现了USB设备与接口在USB总线上的匹配。
usb_new_device();这时USB设备已经进入了configure状态,调用device_add()在USB总线上寻找驱动,若匹配成功,则加载对应的驱动程序。
USB主机在USB设备和USB主机之间发生传输过程,称为事务。每次事务以2到3个数据包形式进行USB总线传输。数据包:PID,数据/控制信息,CRC校验码。
当USB设备连接到集线器,集线器状态将发生相应变化,并将状态变化信息传递给USB主机。一旦hub集线器的状态发生变化就会产生相应中断,主机端控制器就会执行相应的中断处理函数。
USB主机和设备之间必须支持控制传输,通过端点0进行数据传输。
控制传输分为令牌数据传输和握手阶段。
USB数据传输都以URB请求,URB生成,URB递交,URB释放为主线。
接下来,来看一下linux源码:
首先因为u盘插入的时机是随机的,所以需要开启一个内核线程来检测(内核线程叫做hub_thread,U盘的插入和拔出,USB有四条线路,一条接地,一条接电源电压,一条为D+,一条为D-,这两条差分线分别接下拉电阻,在插入的一瞬间,其中一条差分线会被拉高,就被检测到。
其中核心的函数是hub_events,该函数是一个while循环,其中检测状态的函数为hub_port_status();
该函数的实现:
static int hub_port_status(struct usb_hub *hub, int port1,
u16 *status, u16 *change)
{
int ret;
mutex_lock(&hub->status_mutex);
ret = get_port_status(hub->hdev, port1, &hub->status->port);
if (ret < 4) {
dev_err(hub->intfdev,
"%s failed (err = %d)\n", __func__, ret);
if (ret >= 0)
ret = -EIO;
} else {
*status = le16_to_cpu(hub->status->port.wPortStatus);
*change = le16_to_cpu(hub->status->port.wPortChange);
ret = 0;
}
mutex_unlock(&hub->status_mutex);
return ret;
}
改变的状态值存放在change位中,一开始在内核源码中寻找的相应的结构体成员wPortChange,发现所有涉及该结构体成员,都没有最后编译到内核镜像中,后来继续跟发现是不是把该位显式的赋值的,而是在driver/host/下具体的usb控制器去给它赋值的。所以就需要来看一看get_port_status函数:
static int get_port_status(struct usb_device *hdev, int port1,
struct usb_port_status *data)
{
int i, status = -ETIMEDOUT;
for (i = 0; i < USB_STS_RETRIES &&
(status == -ETIMEDOUT || status == -EPIPE); i++) {
status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port1,
data, sizeof(*data), USB_STS_TIMEOUT);
}
return status;
}
而usb_control_msg是linux内核中实现的自己的一套机制,通过发送不同的命令比如说上面的USB_REQ_GET_STATUS,最终会调用相应的底层usb控制器的驱动注册的回调函数。
当usb连接的状态位usb_change被置位了,会执行相应的函数,该函数为
static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange)
算是最核心的函数了。
其中我认为最重要的是三个函数分别是
struct usb_device *usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
该函数主要是分配了相应的usb_device结构体,初始化device中的设备类型,总线类型等等
static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, int retry_counter)
该函数主要实现的是u盘能够正常工作前的一系列流程:复位设备,分配地址,获取设备描述符。
int usb_new_device(struct usb_device *udev)
该函数主要是进行usb的枚举,枚举就是要获得u盘中的一些信息,比如说分区信息lun数等等,枚举完成之后就需要创建设备节点了。
其实在这之后还有一个比较重要的转折点,如果是U盘的,并不是创建设备节点那么简单了。
因为u盘是一个存储设备,也就是一种块设备,所以会涉及scsi的接口,也就是说u盘涉及了两个linux子系统,usb子系统和scsi子系统。
之后会进入driver/usb/storage/usb.c的storage_probe函数,
static int storage_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct us_data *us;
int result;
/*
* If libusual is configured, let it decide whether a standard
* device should be handled by usb-storage or by ub.
* If the device isn't standard (is handled by a subdriver
* module) then don't accept it.
*/
if (usb_usual_check_type(id, USB_US_TYPE_STOR) ||
usb_usual_ignore_device(intf))
return -ENXIO;
/*
* Call the general probe procedures.
*
* The unusual_dev_list array is parallel to the usb_storage_usb_ids
* table, so we use the index of the id entry to find the
* corresponding unusual_devs entry.
*/
result = usb_stor_probe1(&us, intf, id,
(id - usb_storage_usb_ids) + us_unusual_dev_list);
if (result)
return result;
/* No special transport or protocol settings in the main module */
result = usb_stor_probe2(us);
return result;
}
其中usb_stor_probe1主要是初始化scsi设备,而真正的创建块设备节点是在usb_stor_probe2函数中,
static inline int __must_check scsi_add_host(struct Scsi_Host *host, struct device *dev);
该函数主要创建了名为do_scan_async的线程,该线程会根据lun的数目创建相应的块设备节点。
该函数一层一层调用下去之后,一个关键的函数
static void __scsi_scan_target(struct device *parent, unsigned int channel,
unsigned int id, unsigned int lun, int rescan)
{
struct Scsi_Host *shost = dev_to_shost(parent);
int bflags = 0;
int res;
struct scsi_target *starget;
if (shost->this_id == id)
/*
* Don't scan the host adapter
*/
return;
starget = scsi_alloc_target(parent, channel, id);
if (!starget)
return;
scsi_autopm_get_target(starget);
if (lun != SCAN_WILD_CARD) {
/*
* Scan for a specific host/chan/id/lun.
*/
scsi_probe_and_add_lun(starget, lun, NULL, NULL, rescan, NULL);
goto out_reap;
}
/*
* Scan LUN 0, if there is some response, scan further. Ideally, we
* would not configure LUN 0 until all LUNs are scanned.
*/
res = scsi_probe_and_add_lun(starget, 0, &bflags, NULL, rescan, NULL);
if (res == SCSI_SCAN_LUN_PRESENT || res == SCSI_SCAN_TARGET_PRESENT) {
if (scsi_report_lun_scan(starget, bflags, rescan) != 0)
/*
* The REPORT LUN did not scan the target,
* do a sequential scan.
*/
scsi_sequential_lun_scan(starget, bflags,
starget->scsi_level, rescan);
}
out_reap:
scsi_autopm_put_target(starget);
/* now determine if the target has any children at all
* and if not, nuke it */
scsi_target_reap(starget);
put_device(&starget->dev);
}
其实不论怎么样,程序走到这里都会去创建一个块设备节点,在创建完一次之后,根据之前获得max_lun数目之后再去选在是不是要再创建块设备节点号。
真正添加块设备节点的动作的函数是scsi_add_lun函数中的scsi_sysfs_add_sdev函数。
一旦添加完块设备节点之后,drivers/scsi/sd.c中的sd_probe函数就会被调用。
static int sd_probe(struct device *dev)
{
struct scsi_device *sdp = to_scsi_device(dev);
struct scsi_disk *sdkp;
struct gendisk *gd;
int index;
int error;
error = -ENODEV;
if (sdp->type != TYPE_DISK && sdp->type != TYPE_MOD && sdp->type != TYPE_RBC)
goto out;
SCSI_LOG_HLQUEUE(3, sdev_printk(KERN_INFO, sdp,
"sd_attach\n"));
error = -ENOMEM;
sdkp = kzalloc(sizeof(*sdkp), GFP_KERNEL);
if (!sdkp)
goto out;
gd = alloc_disk(SD_MINORS);
if (!gd)
goto out_free;
do {
if (!ida_pre_get(&sd_index_ida, GFP_KERNEL))
goto out_put;
spin_lock(&sd_index_lock);
error = ida_get_new(&sd_index_ida, &index);
spin_unlock(&sd_index_lock);
} while (error == -EAGAIN);
if (error)
goto out_put;
if (index >= SD_MAX_DISKS) {
error = -ENODEV;
sdev_printk(KERN_WARNING, sdp, "SCSI disk (sd) name space exhausted.\n");
goto out_free_index;
}
error = sd_format_disk_name("sd", index, gd->disk_name, DISK_NAME_LEN);
if (error)
goto out_free_index;
sdkp->device = sdp;
sdkp->driver = &sd_template;
sdkp->disk = gd;
sdkp->index = index;
atomic_set(&sdkp->openers, 0);
if (!sdp->request_queue->rq_timeout) {
if (sdp->type != TYPE_MOD)
blk_queue_rq_timeout(sdp->request_queue, SD_TIMEOUT);
else
blk_queue_rq_timeout(sdp->request_queue,
SD_MOD_TIMEOUT);
}
device_initialize(&sdkp->dev);
sdkp->dev.parent = dev;
sdkp->dev.class = &sd_disk_class;
dev_set_name(&sdkp->dev, dev_name(dev));
if (device_add(&sdkp->dev))
goto out_free_index;
get_device(dev);
dev_set_drvdata(dev, sdkp);
get_device(&sdkp->dev); /* prevent release before async_schedule */
async_schedule(sd_probe_async, sdkp);
return 0;
out_free_index:
spin_lock(&sd_index_lock);
ida_remove(&sd_index_ida, index);
spin_unlock(&sd_index_lock);
out_put:
put_disk(gd);
out_free:
kfree(sdkp);
out:
return error;
}
该函数是实质上创建块设备的函数,包括创建初始化struct gendisk结构体等等。
现在还有两个疑问,1刚刚的过程是u盘中有两个真实的物理分区的,所以max_lun会被设置为1,但是如果只有一个真实的物理分区,但是确认磁盘工具分配为两个虚拟的逻辑分区它的过程是怎样的,还需要继续做实验去跟踪代码。2现在对scsi有了疑问,在板子是没有scsi控制器或者总线的,为什么代码中还要用到scsi接口,scsi到底是什么,是物理的接口还是只是逻辑上被虚拟出来的和platform平台总线一样的?