usbip:(二)从linux内核了解usb
一、前言
1、首先了解一下EHCI、UHCI和OHCI。
从硬件上来说,usb 设备要想工作,除了外设本身,必须依赖于 usb host controller.一般来说,一个电脑里有一个 usb host controller就可以了,她就可以控制很多个设备了,比如 u 盘,比如 usb 键盘,比如 usb 鼠标.所 有 的外设都把自己的请求提交给usb host controller.然后让 usb host controller 统一来调度.
现在一般的USB桥接器模块有两种类型,UHCI和OHCI。在决定插入那一个桥接器模块时,可以察看/proc/pci文件来决定。一般而言,UHCI类型的桥接器它的插入模块是uhci或usb-uhci(由内核版本决定);而对于OHCI类型的桥接器它的插入模块是ohci或usb-ohci。
uhci(universal host controller interface): Intel用在自家芯片组上的usb 1.1主控制器(host controller)的硬件实例
ehci(enhanced host controller interface): usb 2.0的主控制器标准接口。
ohci(open host controller inferface):一个不仅仅是usb用的主控制器接口标准。主要是遵循csr (configuration space register)标准。是其他厂商在设计usb host controller时遵循的标准,如via, nec, ali, 包括nvidia等等。
ehci是满足usb 2.0 specification里面对usb host controller (high speed)的要求的硬件设计。
就是主机控制器的接口。从硬件上来说,USB 设备要想工作,除了外设本身,必须还有一个 USB 主机控制器。一般来说,一个电脑里有一个 USB 主机控制器就可以了,它就可以控制很多个设备了,比如 U 盘,USB 键盘,USB 鼠标。所有的外设都把自己的请求提交给 USB主机控制器。然后让 USB 主机控制器统一来调度。而设备怎么连到主机控制器上?哎,这就是我们故事的主角,Hub,“乳名”叫做集线器。
2、准备工作
linux内核源码下载,可以根据自己喜好选择对应的版本。
wget https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.145.tar.gz
以usb_hub,来了解usb工作
localhost:/usr/src/linux-2.6.22.1/drivers/usb/core # wc -l hub.c
二、API
1、Root_Hub
USB 设备的初始化都是 Hub 这边发起的,通常我们写 USB 设备驱动程序都是在已经得到了一个 struct usb_interface 指针的情况下开始 probe 工作。可是我要问你,你的 struct usb_interface 从哪来的?老实说,要想知道从 USB 设备插入 USB 口的那一刻开始,这个世界发生了什么,你必须知道 Hub 是怎么工作的,Linux 中 Hub 驱动程序是怎么工作的。要说 USB Hub,那得从 Root Hub 说起。什么是 Root Hub?冤有头债有主,不管你的电脑里连了多少个 USB 设备,它们最终是有根的。所有的 USB 设备最终都是连接到了一个叫做 Root Hub 的设备上,或者说所有的根源都是从这里开始的。
Root Hub 上可以连接别的设备,可以连接 U 盘,可以连接 USB 鼠标,同样也可以连接另一个 Hub。所谓 Hub,就是用来级连。但是普通的 Hub,它一头接上级 Hub,另一头可以有多个口,多个口就可以级连多个设备,也可以只有一个口,一个口的就像大学宿舍里常用的那种延长线。而 Root Hub 呢?它比较特殊,它当然也是一种 USB 设备,但是它属于一人之下万人之上的角色,她只属于主机控制器,换言之,通常做芯片的人会把主机控制器和 Root Hub 集成在一起。特别是 PC 主机上,通常你就只能看到接口,看不到 Root Hub,因为她在主机控制器里。
2、usb_init
综上所述,整个USB子系统在初始化的时候,会先执行初始化Root Hub。
来自drivers/usb/core/usb.c:
982 static int __init usb_init(void)
983 {
......
1007 if (retval)
1008 goto usb_devio_init_failed;
1009 retval = usb_hub_init();
......
1032 }
在众多名叫*init*的函数之中,是有一个属于 Hub 的,它在这里初始化,换言之,随着 USB Core 的初
始化,Hub 也就开始了它的初始化之路,usb_hub_init()函数得到调用。
来自drivers/usb/core/hub.c:
5600 int usb_hub_init(void)
5601 {
5602 if (usb_register(&hub_driver) < 0) {
5603 printk(KERN_ERR "%s: can't register hub driver\n",
5604 usbcore_name);
5605 return -1;
5606 }
5607
5608 /*
5609 * The workqueue needs to be freezable to avoid interfering with
5610 * USB-PERSIST port handover. Otherwise it might see that a full-speed
5611 * device was gone before the EHCI controller had handed its port
5612 * over to the companion full-speed controller.
5613 */
5614 hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);
5615 if (hub_wq)
5616 return 0;
5617
5618 /* Fall through if kernel_thread failed */
5619 usb_deregister(&hub_driver);
5620 pr_err("%s: can't allocate workqueue for usb hub\n", usbcore_name);
5621
5622 return -1;
5623 }
usb_register()这个函数是用来向 USB 核心层,即 USB Core,注册一个 USB 设备驱动的,而这里我们注册的是注册的是 Hub 的驱动程序所对应的 struct usb_driver 结构体变量。定义于 drivers/usb/core/hub.c 中。
5586 static struct usb_driver hub_driver = {
5587 .name = "hub",
5588 .probe = hub_probe,
5589 .disconnect = hub_disconnect,
5590 .suspend = hub_suspend,
5591 .resume = hub_resume,
5592 .reset_resume = hub_reset_resume,
5593 .pre_reset = hub_pre_reset,
5594 .post_reset = hub_post_reset,
5595 .unlocked_ioctl = hub_ioctl,
5596 .id_table = hub_id_table,
5597 .supports_autosuspend = 1,
5598 };
这里面最重要的一个函数就是 hub_probe,很多事情都在这期间发生了。每个 USB 设备(或者说所有设备)的驱动都会有一个 probe 函数,比如 U 盘的 probe 函数就是 storage_probe(),不过 storage_probe()被调用需要两个前提,第一个 usb-storage 被加载了,第二个 U 盘等设备插入了被检测到了。而 Hub,说它特别,我可绝不是忽悠你。Hub 本身就有两种,一种是普通的 Hub,一种是 Root Hub。对于普通 Hub,它完全可能也是和 U 盘一样,在某个时刻被你插入,然后这种情况下 hub_probe 被调用,但是对于 Root Hub 就不需要这么多废话了,Root Hub 肯定是有的,只要你有 USB 主机控制器,就一定会有 Root Hub,所以 hub_probe()基本上是很自然地就被调用了,不用说非得等待某个插入事件的发生,没这个必要。当然没有 USB 主机控制器就没有 USB 设备能工作。那么 USB Core 这整个模块你就没有必要分析了。所以,只要你有 USB 主机控制器,那么在 USB 主机控制器的驱动程序初始化的过程中,它就会调用 hub_probe()来探测 RootHub,不管你的主机控制器是 OHCI、UHCI 还是 EHCI 的接口。如果 register 一切顺利的话,那么返回值为 0。如果返回值为负数,就说明出错了。现在假设这一步没有出错。
Ok,usb_hub_init()的5614行,这行代码其实是很有技术含量的,不过对于写驱动的人来说,其作用就和当年的kernel_thread()相当。不过kernel_thread()返回值是一个int型的,而kthread_run()返回的却是structtask_struct 结构体指针。这里等号左边的 khubd_task 是我们自己定义的一个 struct task_struct 指针。
56 /* workqueue to process hub events */
57 static struct workqueue_struct *hub_wq;
58 static void hub_event(struct work_struct *work);
定义与 kernel/workqueue.c
235 /*
236 * The externally visible workqueue. It relays the issued work items to
237 * the appropriate worker_pool through its pool_workqueues.
238 */
239 struct workqueue_struct {
240 struct list_head pwqs; /* WR: all pwqs of this wq */
241 struct list_head list; /* PR: list of all workqueues */
242
243 struct mutex mutex; /* protects this wq */
244 int work_color; /* WQ: current work color */
245 int flush_color; /* WQ: current flush color */
246 atomic_t nr_pwqs_to_flush; /* flush in progress */
247 struct wq_flusher *first_flusher; /* WQ: first flusher */
248 struct list_head flusher_queue; /* WQ: flush waiters */
249 struct list_head flusher_overflow; /* WQ: flush overflow list */
250
251 struct list_head maydays; /* MD: pwqs requesting rescue */
252 struct worker *rescuer; /* I: rescue worker */
253
254 int nr_drainers; /* WQ: drain in progress */
255 int saved_max_active; /* WQ: saved pwq max_active */
256
257 struct workqueue_attrs *unbound_attrs; /* PW: only for unbound wqs */
258 struct pool_workqueue *dfl_pwq; /* PW: only for unbound wqs */
259
260 #ifdef CONFIG_SYSFS
261 struct wq_device *wq_dev; /* I: for sysfs interface */
262 #endif
263 #ifdef CONFIG_LOCKDEP
264 char *lock_name;
265 struct lock_class_key key;
266 struct lockdep_map lockdep_map;
267 #endif
268 char name[WQ_NAME_LEN]; /* I: workqueue name */
269
270 /*
271 * Destruction of workqueue_struct is RCU protected to allow walking
272 * the workqueues list without grabbing wq_pool_mutex.
273 * This is used to dump all workqueues from sysrq.
274 */
275 struct rcu_head rcu;
276
277 /* hot fields used during command issue, aligned to cacheline */
278 unsigned int flags ____cacheline_aligned; /* WQ: WQ_* flags */
279 struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
280 struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */
281 };
5615行,判断hub_wq,
IS_ERR 是一个宏,用来判断指针的。当你创建了一个进程,你当然想知道这个进程创建成功了没有。以前我们注意到每次申请内存时都会做一次判断,你说创建进程是不是也要申请内存?不申请内存谁来记录 struct task_struct?很显然,要判断。以前我们判断的是指针是否为空。以后接触代码多了你会发现,其实 Linux 内核中有很多种内存申请的方式,而这些方式所返回的内存地址也是不一样的,所以并不是每一次我们都只要判断指针是否为空就可以了。事实上,每一次调用 kthread_run()之后,我们都会用一个IS_ERR()来判断指针是否有效。IS_ERR()为 1 就表示指针有错,或者准确一点说叫做指针无效。
如果 IS_ERR()返回值是 0,那么说明没有问题,于是返回值为 0,也就是说 usb_hub_init()就这么结束了。反之,就会执行 usb_deregister()。
usbip:(二)从linux内核了解usb的更多相关文章
- Linux内核剖析(二)Linux内核绪论
什么是内核 内核是操作系统最基本的部分.它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并且内核决定一个程序在什么时候对某部分硬件操作多长时间.内核的分类可分为单内核和双内 ...
- 第三十二课 linux内核链表剖析
__builtin_prefetch是gcc扩展的,用来提高访问效率,需要硬件的支持. 在标准C语言中是不允许static inline联合使用的. 删除依赖的头文件,将相应的结构拷贝到LinuxLi ...
- OMAP4之DSP核(Tesla)软件开发学习(二)Linux内核驱动支持OMAP4 DSP核
注:必须是Linux/arm 3.0以上内核才支持RPMSG,在此使用的是.config - Linux/arm 3.0.31 Kernel Configuration.(soure code fro ...
- (二)linux内核准备及编译
1. 内核下载地址 linux内核网站,可以拿到最新的和最近的稳定版本内核: https://www.kernel.org/ 通过网站下载压缩包后解压或者使用git下载到本地: git clone h ...
- Linux 内核使用 USB 数据函数
USB 核心中的几个帮忙函数可用来从所有的 USB 设备中存取标准信息. 这些函数不能从 中断上下文或者持有自旋锁时调用. 函数 usb_get_descriptor 获取指定的 USB 描述符从特定 ...
- linux内核启动以及文件系统的加载过程
Linux 内核启动及文件系统加载过程 当u-boot 开始执行 bootcmd 命令,就进入 Linux 内核启动阶段.普通 Linux 内核的启动过程也可以分为两个阶段.本文以项目中使用的 lin ...
- Linux内核启动及根文件系统载入过程
上接博文<u-boot之u-boot-2009.11启动过程分析> Linux内核启动及文件系统载入过程 当u-boot開始运行bootcmd命令,就进入Linux内核启动阶段.与u-bo ...
- Linux内核和根文件系统引导加载程序
续博文<u-boot之u-boot-2009.11启动过程分析> Linux内核启动及文件系统载入过程 当u-boot開始运行bootcmd命令.就进入Linux内核启动阶段,与u-boo ...
- linux内核Makefile整体分析
转自:http://www.cnblogs.com/amanlikethis/p/3675486.html <请阅读原文> 一.概述 1.本文的意义 众多的资料(<嵌入式Linux应 ...
- linux内核被加载的过程
二,linux内核被加载的过程 一,linux安装时遇到的概念解析 内核必须模块vmlinz(5M左右)不认识硬盘,原本是需要写跟loader中一样的内容,来加载非必要模块. 内核非必要的功能被编译为 ...
随机推荐
- Xrdp服务安装配置实现Linux远程桌面访问以及问题处理
0x00 基础介绍 0x01 安装桌面环境 Ubuntu 系列 0x02 Xrdp 安装使用 How to Install xrdp on Ubuntu ? How to Install xrdp t ...
- 【Linux】ntpdate与ntpd区别
前两天遇到时间显示的问题,整理记录下来. 问题描述:开机程序startA自己统计自己的运行时间,每次运行时间显示异常,类似17713d45h54m. 有一些猜测:1.计算异常,出现负数:2.获取时间异 ...
- web安全学习笔记(2022/8/26)
网络安全Web学习笔记 @author: lamaper @email: lamaper@qq.com @blog: lamaper - 博客园 (cnblogs.com) @date: Aug.26 ...
- js 俄罗斯方块 canvas
俄罗斯方块背景- canvans 第一次写不知道说些什么好,直接上代码了@_@... jquery引入 <script src="https://cdn.bootcdn.net/aja ...
- hexo部署和优化记录
title: hexo部署和优化记录 date: 2020-06-14 09:00:03 前端 tags: hexo summary: Repository_Pages使用.Github仓库打造网页群 ...
- keeplive 双击热备方案 (对haproxy负载均衡 )双击热备方案
1.安装 keepalived ,必选安装在haproxy 容器之内 1.进入容器:docker exec -it h1 bash 后执行下面步骤 1.更新apt-get apt-get upd ...
- ES-索引库
数据准备 本次学习涵盖ES简单查询,聚合查询,所以在创建测试库时会可以涵盖一些个性化字段,用于学习搜索用法 索引创建 几个疑问 1.能否用中文命名 安排:我用"蓝闪test",中英 ...
- [转并修改]C#编程中跨线程访问控件
C#编程中跨线程访问控件 一.简述 二.Winforms中跨线程访问控件 三.WPF中跨线程访问控件 参考文档 一.简述 C#中不允许跨线程直接访问界面控件,即一个线程中如主线程创建的控件不允许被其他 ...
- boolean布尔型盲注
mysql中limit的详细用法 1.用于强制返回指定的记录行数 在查询中,经常要返回前几条或者中间某几行数据时,用到limit 语法如下 select * from table_name limit ...
- 【记录】 iSCSI服务器的搭建与使用[Debian]
序言 更换系统后需要一个网络文件存储用于备份文件,本想用NFS多方便,但是timeshift不支持网络存储,备份路径必须是一个块存储设备, 但是你还必须分好文件系统,这不是多此一举???反正我只用rs ...