momo zone

调核人的blog

Monthly Archives: 一月 2011

AppStream —跨发行版打包机制

终于在最近的 FreeDesktop 会议上,Red Hat, Canonical, Novell, Debian, Mageia 等重要的 Linux 发行版/厂商们坐下来开始实现跨发行版的软件安装机制

简单来说,AppStream 跨发行版安装机制有四个部分组成:

  • 打包服务器:从打包文件的desktop文件中抽取元信息,将包括软件图标在内的信息提交给镜像。
  • 镜像:在各个仓库已有镜像的基础上添加 app-data.xml 数据文件和包含软件图标的 app-data-icons.tar.gz 文件。这些文件将会被客户端访问并使用。
  • 客户端:以 Ubuntu Software Center 为界面基础,使用 PackageKit 为后端执行软件包管理操作,并连接本地 xapian 文本搜索数据库实现内容搜索,利用Zeitgeist 实现软件使用统计
  • OCS 服务器:通过 OAuth 与客户端联系,提供社会化评论及评分功能。(应用商店模式?? apple 的模式真的很成功啊)

AppStream 会

  • 为各大 Linux 发行版提供便捷统一的安装流程,用户在一个发行版上的安装经验可以平缓迁移到其他发行版。
  • 提供一个统一的元数据、评论、评分分享平台

AppStream 不会

  • 取代现有发行版的打包机制,而是利用 PackageKit 的多后端支持将后台的模式封装起来。
  • 为镜像服务器带来额外同步负荷,小尺寸图标文件和描述元信息很小。

尽管看起来 很令人振奋,但也并不是非常乐观,因为之前就有讨论过这个问题而且拿出实际的行动可是用户并不买账。不过在android和 iphone商店模式的压力下这次看起来希望大一些。

bcm4312 开源驱动是条龙,官方驱动是条虫

在我印象中bcm不是什么折腾的主,起码我的DDWRT上面就是bcm 的方案。但见到了bcm4312 后改变了这种看法。 其实bcm4312自从2.6.24 之后都是可以由内核直接支持不需要额外编译模块了。不过这里关键的问题是这个驱动不是bcm授权的,所以firmware 并不直接提供,需要自己想办法。 linux wireless 提供的方法是: 用b43-fwcutter

wget http://bu3sch.de/b43/fwcutter/b43-fwcutter-013.tar.bz2
tar xjf b43-fwcutter-013.tar.bz2
cd b43-fwcutter-013
make
cd ..

从windows驱动中裁切出来firmware ,

export FIRMWARE_INSTALL_DIR="/lib/firmware"
wget http://downloads.openwrt.org/sources/broadcom-wl-4.80.53.0.tar.bz2
tar xjf broadcom-wl-4.80.53.0.tar.bz2
cd broadcom-wl-4.80.53.0/kmod
sudo ../../b43-fwcutter-013/b43-fwcutter -w "$FIRMWARE_INSTALL_DIR" wl_apsta.o

而且对kernel 版本有要求, 上面的是2.6.24 , 2.6.25 要用broadcom-wl-4.150.10.5.tar.bz2 2.6.32 要用broadcom-wl-4.178.10.4.tar.bz2 实践证明对于2.6.37 ,上述的broadcom-wl 都无效,只能自己想办法折腾了 最后在一个打包站点装了b43-firmware-4.174.64.19-3.pm.3.1.noarch.rpm ,试了里面的firmware 才搞定, 一切正常,也很稳定,注入模式也没问题。

Update: 对于openSuSE ,上述的方法都是浮云,因为它提供一个自动的firmware 下载脚本,直接执行install_bcm43xx_firmware  就可以了,然后开启网卡万事ok。

再看看 bcm的官方驱动:

802.11 Linux STA driver

STA  是什么?  不知道,我真不知道。 纳尼, 1MB , bcm 你有诚意吗? 为什么windows下的wl_apsta.o 就有5MB了? 下来试试吧。 编译完了,rmmod b43 , modprobe wl ,ifconfig :

eth1 Link encap:Ethernet  HWaddr 0C:EE:E6:9F:DC:A5
inet6 addr: fe80::eee:e6ff:fe9f:dca5/64 Scope:Link
UP BROADCAST MULTICAST  MTU:1500  Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)
Interrupt:17

看到这个eth1 我就晕了,这个驱动要玩完,这个驱动肯定不依赖 ieee80211 模块(lsmod 看一下就知道), 也就是说他不是使用kernel的驱动架构,而是自己搞了一套。再见吧bcm sta 驱动,我真的不想再见到你…

重大改进,BKL被移除

linux kernel的又一个重大历史性改进,大内核锁被彻底移除,这将极大提高多核心处理器的效能。内核开发者真是越来越给力了。

android 内核也要马上跟进了,手机上多核心CPU指日可待。

The Big Kernel Lock is a giant lock that was introduced in Linux 2.0, when Alan Cox introduced SMP support for first time. But it was just an step to achieve SMP scalability – only one process can run kernel code at the same time in Linux 2.0, long term the BKL must be replaced by fine-grained locking to allow multiple processes running kernel code in parallel. In this version, it is possible to compile a kernel completely free of BKL support. Note that this doesn’t have performance impact: all the critical Linux codepaths have been BKL-free for a long time. It still was used in many non-performance critical places -ioctls, drivers, non-mainstream filesystems, etc-, which are the ones that are being cleaned up in this version. But the BKL is being replaced in these places with mutexes, which doesn’t improve parallelism (these places are not performance critical anyway).

21 files changed:

 

drivers/gpu/drm/Kconfig patchblobhistory
drivers/media/Kconfig patchblobhistory
drivers/net/appletalk/Kconfig patchblobhistory
drivers/staging/cx25821/Kconfig patchblobhistory
drivers/staging/easycap/Kconfig patchblobhistory
drivers/staging/go7007/Kconfig patchblobhistory
drivers/staging/usbip/Kconfig patchblobhistory
fs/Kconfig patchblobhistory
fs/adfs/Kconfig patchblobhistory
fs/autofs/Kconfig patchblobhistory
fs/hpfs/Kconfig patchblobhistory
fs/nfs/Kconfig patchblobhistory
fs/nfsd/Kconfig patchblobhistory
fs/smbfs/Kconfig patchblobhistory
fs/udf/Kconfig patchblobhistory
fs/ufs/Kconfig patchblobhistory
include/linux/smp_lock.h patchblobhistory
init/Kconfig patchblobhistory
lib/Kconfig.debug patchblobhistory
net/ipx/Kconfig patchblobhistory
net/x25/Kconfig patchblobhistory


关于大内核锁

大内核锁这个简单且不常用的内核加锁机制一直是内核开发者之间颇具争议的话题。它在早期linux版本里的广泛使用,从2.4内核开始逐渐被各种各样的自旋锁替代,可是直到现在还不能完全将它抛弃;它曾经使用自旋锁实现,到了2.6.11版修改为信号量,可是在2.6.26-rc2又退回到使用自旋锁的老路上;它甚至引发了linux的创始人Linus Torvalds和著名的完全公平调度(CFS)算法的贡献者Ingo Molnar之间的一场争议。这究竟是怎么回事呢?

1.1         应运而生,特立独行

使用过自旋锁或信号量这些内核互斥机制的人几乎不会想到还有大内核锁这个东西。和自旋锁或信号量一样,大内核锁也是用来保护临界区资源,避免出现多个处理器上的进程同时访问同一区域的。但这把锁独特的地方是,它不象自旋锁或信号量一样可以创建许多实例或者叫对象,每个对象保护特定的临界区。事实上整个内核只有一把这样的锁,一旦一个进程获得大内核锁,进入了被它保护的临界区,不但该临界区被锁住,所有被它保护的其它临界区都将无法访问,直到该进程释放大内核锁。这看似不可思议:一个进程在一个处理器上操作一个全局的链表,怎么可能导致其它进程无法访问另一个全局数组呢?使用两个自旋锁,一个保护链表,另一个保护数组不就解决了吗?可是如果你使用大内核锁,效果就是这样的。

大内核锁的产生是有其历史原因的。早期linux版本对对称多处理(SMP)器的支持非常有限,为了保证可靠性,对处理器之间的互斥采取了‘宁可错杀三千,不可放过一个’的方式:在内核入口处安装一把‘巨大’的锁,一旦一个处理器进入内核态就立刻上锁,其它将要进入内核态的进程只能在门口等待,以此保证每次只有一个进程处于内核态运行。这把锁就是大内核锁。有了大内核锁保护的系统当然可以安全地运行在多处理器上:由于同时只有一个处理器在运行内核代码,内核的执行本质上和单处理器没有什么区别;而多个处理器同时运行于进程的用户态也是安全的,因为每个进程有自己独立的地址空间。但是这样粗鲁地加锁其缺点也是显而易见的:多处理器对性能的提示只能体现在用户态的并行处理上,而在内核态下还是单线执行,完全无法发挥多处理器的威力。于是内核开发者就开始想办法逐步缩小这把锁保护的范围。实际上内核大部分代码是多处理器安全的,只有少数全局资源需要需要在做互斥加以保护,所以没必要限制同时运行于内核态处理器的个数。所有处理器都可随时进入内核态运行,只要把这些需要保护的资源一一挑出来,限制同时访问这些资源的处理器个数就可以了。这样一来,大内核锁从保护整个内核态缩小为零散地保护内核态某些关键片段。这是一个进步,可步伐还不够大,仍有上面提到的,‘锁了卧室厨房也没法进’的毛病。随着自旋锁的广泛应用,新的内核代码里已经不再有人使用大内核锁了。

1.2      食之无味,挥之不去

既然已经有了替代物,大内核锁应该可以‘光荣下岗’了。可事实上没这么简单。如果大内核锁仅仅是‘只有一个实例’的自旋锁,睿智的内核开发者早就把它替换掉了:为每一种处于自旋锁保护下的资源创建一把自旋锁,把大内核锁加锁/解锁替换成相应的自旋锁的加锁/解锁就可以了。但如今的大内核锁就象一个被宠坏的孩子,内核在一些关键点给予了它许多额外关照,使得大内核锁的替换变得有点烦。下面是Ingo Molnar在一封名为 ’kill the Big Kernel Lock (BKL)’的邮件里的抱怨:

The biggest technical complication is that the BKL is unlike any other lock: it “self-releases” when schedule() is called. This makes the BKL spinlock very “sticky”, “invisible” and viral: it’s very easy to add it to a piece of code (even unknowingly) and you never really know whether it’s held or not. PREEMPT_BKL made it even more invisible, because it made its effects even less visible to ordinary users.

这段话的大意是:最大的技术难点是大内核锁的与众不同:它在调用schedule()时能够‘自动释放’。这一点使得大内核锁非常麻烦和隐蔽:它使你能够非常容易地添加一段代码而几乎从不知道它锁上与否。PREEMPT_BKL选项使得它更加隐蔽,因为这导致它的效果在普通用户面前更加‘遁形’。

翻译linux开发者的话比看懂他们写的代码更难,但有一点很明白:是schedule()函数里对于大内核锁的自动释放导致了问题的复杂化。那就看看schedule()里到底对大内核锁执行了什么操作:

1 /*

2  * schedule() is the main scheduler function.

3  */

4 asmlinkage void __sched schedule(void)

5 {

19     release_kernel_lock(prev);

55         context_switch(rq, prev, next); /* unlocks the rq */

67     if (unlikely(reacquire_kernel_lock(current) < 0)) {

68         prev = rq->curr;

69         switch_count = &prev->nivcsw;

70         goto need_resched_nonpreemptible;

71     }

code 1.2‑1 linux_2.6.34/kernel/sched.c

在第19行release_kernel_lock(prev)函数释放当前进程(prev)所占据的大内核锁,接着在第55行执行进程的切换,从当前进程prev 切换到了下一个进程next。context_switch()可以看做一个超级函数,调用它不是去执行一段代码,而是去执行另一个进程。系统的多任务切换就是依靠这个超级函数从一个进程切换到另一个进程,从另一个进程再切换下一个进程,如此连续不断地轮转。只要被切走的进程还处于就绪状态,总有一天还会有机会调度回来继续运行,效果看起来就象函数context_switch()运行完毕返回到了schedule()。继续运行到第67行,调用函数reacquire_kernel_lock()。这是和release_kernel_lock()配对的函数,将前面释放的大内核锁又重新锁起来。If语句测试为真表示对大内核锁尝试加锁失败,这时可以做一些优化。正常的加锁应该是‘原地踏步’,在同一个地方反复查询大内核锁的状态,直到其它进程释放为止。但这样做会浪费宝贵的处理器时间,尤其是当运行队列里有进程在等待运行时。所以release_lernel_lock()只是做了’try_lock’的工作,即假如没人把持大内核锁就把它锁住,返回0表示成功;假如已经被锁住就立即返回-1表示失败。一旦失败就重新执行一遍schedule()的主体部分,检查运行队列,挑选一个合适的进程运行,等到下一次被调度运行时可能锁就解开了。这样做利用另一个进程(假如有进程在排队等候)的运行代替了原地死等,提高了处理器利用率。

除了在schedule()中的‘照顾’,大内核锁还有另外的优待:在同一进程中你可以对它反复嵌套加锁解锁,只要加锁个数和解锁个数能配上对就不会有任何问题,这是自旋锁望尘莫及的,同一进程里自旋锁如果发生嵌套加锁就会死锁。为此在进程控制块(PCB)中专门为大内核锁开辟了加锁计数器,即task_struct中的lock_depth域。该域的初始值为-1,表示进程没有获得大内核锁。每次加锁时lock_depth都会加1,再检查如果lock_depth为0就执行真正的加锁操作,这样保证在加了一次锁以后所有嵌套的加锁操作都会被忽略,从而避免了死锁。解锁过程正好相反,每次都将lock_depth减1,直到发现其值变为-1时就执行真正的解锁操作。

内核对大内核锁的偏袒导致开发者在锁住了它,进入被它保护的临界区后,执行了不该执行的代码却还无法察觉。其一:程序在锁住临界区后必须尽快退出,否则会阻塞其它将要进入临界区的进程。所以在临界区里绝对不可以调用schedule()函数,否则一旦发生进程切换何时能解锁就变得遥遥无期。另外在使用自旋锁保护的临界区中做进程切换很容易造成死锁。比如一个进程锁住了一把自旋锁,期间调用schedule()切换到另一个进程,而这个进程又要获得这把锁,这是系统就会挂死在这个进程等待解锁的自旋处。这个问题在大内核锁保护的临界区是不存在的,因为schedule()函数在调度到新进程之前会自动解锁已经获得的大内核锁;在切回该进程时又会自动将大内核锁锁住。用户在锁住了大内核锁后,几乎无法察觉期间是否用过schedule()函数。这一点就是上面Ingo Molnar提到的’technical complication’:将大内核锁替换成自旋锁后,万一在加锁过程中调用了schedule(),会造成不可预估的,灾难性的后果。当然作为一个训练有素的程序员,即使大内核锁放宽了约束条件,也不会在临界区中有意识地调用schedule()函数的。可是如果是调用陌生模块的代码,再高超的程序员也无法保证其中不会调用到该函数。其二就是上面提到的,在临界区中不能再次获得保护该临界区的锁,否则会死锁。可是由于大内核锁有加锁计数器的保护,怎样嵌套也不会有事。这也是一个’technical complication’:将大内核锁替换成自旋锁后,万一发生了同一把自旋锁的嵌套加锁后果也是灾难性的。同schedule()函数一样,训练有素的程序员是不会有意识地多次锁住大内核锁,但在获得自旋锁后调用了陌生模块的代码就无法保证这些模块中不会再次使用大内核锁。这种情况在开发大型系统时非常常见:每个人都很小心地避免自己模块的死锁,可谁也无法避免当调用其它模块时可能引入的死锁问题。

Ingo Molnar还提到了大内核锁的另一弊端:大内核锁没有被lockdep所覆盖。lockdep是linux内核的一个调试模块,用来检查内核互斥机制尤其是自旋锁潜在的死锁问题。自旋锁由于是查询方式等待,不释放处理器,比一般的互斥机制更容易死锁,故引入lockdep检查以下几种情况可能的死锁(lockdep将有专门的文章详细介绍,在此只是简单列举):

  • 同一个进程递归地加锁同一把锁;
  • 一把锁既在中断(或中断下半部)使能的情况下执行过加锁操作,又在中断(或中断下半部)里执行过加锁操作。这样该锁有可能在锁定时由于中断发生又试图在同一处理器上加锁;
  • 加锁后导致依赖图产生成闭环,这是典型的死锁现象。

由于大内核锁游离于lockdep之外,它自身以及和其它互斥机制之间的依赖关系没有受到监控,可能会导致死锁的场景也无法被记录下来,使得它的使用越来越混乱,处于失控状态。

如此看来,大内核锁已经成了内核的鸡肋,而且不能与时俱进,到了非整改不可的地步。可是将大内核锁完全从内核中移除将要面临重重挑战,对于那些散落在‘年久失修’,多年无人问津的代码里的大内核锁,更是没人敢去动它们。既然完全移除希望不大,那就想办法优化它也不失为一种权宜之计。

1.3      一改再改:无奈的选择

早些时候大内核锁是在自旋锁的基础上实现的。自旋锁是处理器之间临界区互斥常用的机制。当临界区非常短暂,比如只改变几个变量的值时,自旋锁是一种简单高效的互斥手段。但自旋锁的缺点是会增大系统负荷,因为在自旋等待过程中进程依旧占据处理器,这部分等待时间是在做无用功。尤其是使用大内核锁时,一把锁管所有临界区,发生‘碰撞’的机会就更大了。另外为了使进程能够尽快全速‘冲’出临界区,自旋锁在加锁的同时关闭了内核抢占式调度。因此锁住自旋锁就意味着在一个处理器上制造了一个调度‘禁区’:期间既不被其它进程抢占,又不允许调用schedule()进行自主进程切换。也就是说,一旦处理器上某个进程获得了自旋锁,该处理器就只能一直运行该进程,即便有高优先级的实时进程就绪也只能排队等候。调度禁区的出现增加了调度延时,降低了系统实时反应的速度,这与大家一直努力从事的内核实时化改造是背道而驰的。于是在2.6.7版本的linux中对自旋锁做了彻底改造,放弃了自旋锁改用信号量。信号量没有上面提到的两个问题:在等待信号量空闲时进程不占用处理器,处于阻塞状态;在获得信号量后内核抢占依旧是使能的,不会出现调度盲区。这样的解决方案应该毫无争议了。可任何事情都是有利有弊的。信号量最大的缺陷是太复杂了,每次阻塞一个进程时都要产生费时的进程上下文切换,信号量就绪唤醒等待的进程时又有一次上下文切换。除了上下文切换耗时,进程切换造成的TLB刷新,cache冷却等都有较大开销。如果阻塞时间比较长,达到毫秒级,这样的切换是值得的。但是大部分情况下只需在临界区入口等候几十上百个指令循环另一个进程就可以交出临界区,这时候这种切换就有点牛刀杀鸡了。这就好象去医院看普通门诊,当医生正在为病人看病时,别的病人在门口等待一会就会轮到了,不必留下电话号码回家睡觉,直到医生空闲了打电话通知再匆匆赶往医院。

由于使用信号量引起的进程频繁切换导致大内核锁在某些情况下出现严重性能问题, Linus Torvalds不得不考虑将大内核锁的实现改回自旋锁,自然调度延时问题也会跟着回来。这使得以‘延时迷(latency junkie)’自居的Ingo Molnar不太高兴。但linux还是Linus Torvalds说了算,于是在2.6.26-rc2版大内核锁又变成了自旋锁,直到现在。总的来说Linus Torvalds的改动是有道理的。使用繁琐,重量级的信号量保护短暂的临界区确实不值得;而且Linux也不是以实时性见长的操作系统,不应该片面追求实时信而牺牲了整体性能。

1.4      日薄西山:谢幕在即

改回自旋锁并不意味着Linus Torvalds不关心调度延时,相反他真正的观点是有朝一日彻底铲除大内核锁,这一点他和Ingo Molnar是英雄所见略同。可是由于铲除大内核锁的难度和风险巨大,Ingo Molnar觉得‘在当前的游戏规则下解决大内核锁是不现实的’必须使用新的游戏规则。他专门建立一个版本分支叫做kill-the-BLK,在这个分支上将大内核锁替换为新的互斥机制,一步一步解决这个问题:

  • 解决所有已知的,利用到了大内核锁自动解锁机制的临界区;也就是说,消除使用大内核锁的代码对自动解锁机制的依赖,使其更加接近普通的互斥机制;
  • 添加许多调试设施用来警告那些在新互斥机制下不再有效的假设;
  • 将大内核锁转换为普通的互斥体,并删除遗留在调度器里的自动解锁代码;
  • 添加lockdep对它的监控;
  • 极大简化大内核锁代码,最终将它从内核里删除。

 

kernel 2.6.36 ,ioctl 变更

kernel 2.6.35 及之前的版本中struct file_operations 一共有3个ioctl :
ioctl,unlocked_ioctl和compat_ioctl
现在只有unlocked_ioctl和compat_ioctl 了
在kernel 2.6.36 中已经完全删除了struct file_operations 中的ioctl 函数指针,取而代之的是unlocked_ioctl 。起初还以为是compat_ioctl ,后来写了一段测试程序才发现不是。
这个指针函数变了之后最大的影响是参数中少了inode , 不过这个不是问题,因为用户程序中的ioctl对应的系统调用接口没有变化,所以用户程序不需要改变,一切都交给内核处理了,如果想在unlocked_ioctl中获得inode 等信息可以用如下方法:
struct inode *inode = file->f_mapping->host;
struct block_device *bdev = inode->i_bdev;
struct gendisk *disk = bdev->bd_disk;
fmode_t mode = file->f_mode;
struct backing_dev_info *bdi;

这次内核函数的变化引出了一个问题,从ioctl系统调用往后,真正的ioctl调用顺序是什么?为什么compat_ioctl 不被调用?
compat_ioctl被使用在用户空间为32位模式,而内核运行在64位模式时。这时候,需要将64位转成32位。
以下是2.6.36的情况:
SYSCALL_DEFINE3(ioctl ...) compat_sys_ioctl (是否直接调用compat_ioctl 取决于compat_ioctl 是否存在)
| | |-----> compat_ioctl
|   |
|------>do_vfs_ioctl (下一步的调用取决于file->f_path.dentry->d_inode->i_node)
|            |------>file_ioctl
| |
|-------------------------------->vfs_ioctl
|------->unlock_ioctl
其实compat_ioctl 没有被调用的原因是compat_sys_ioctl 没有被调用,而它没有被调用的原因似乎是压根就没有编译到内核中,因为我没有找到调用这个函数的代码。
unlocked_ioctl 实际上取代了用了很久的ioctl,主要的改进就是不再需要上大内核锁 (调用之前不再先调用lock_kernel()然后再unlock_kernel())
总的来说kernel 开发者正在试图朝移除大内核锁的方向努力,ioctl的移除就是被革命了。相信以后越来越多的内核函数会摆脱大内核锁的依赖,并且大内核锁最终会被移除。

关于usb pipe 的问题

usb pipe 主要会被这两个函数中被使用 :

usb_fill_XXXXX_urb , usb_XXXX_msg

而pipe 要使用下面的几个宏来创建:

#define usb_sndctrlpipe(dev, endpoint) \
((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint))
#define usb_rcvctrlpipe(dev, endpoint) \
((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndisocpipe(dev, endpoint) \
((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint))
#define usb_rcvisocpipe(dev, endpoint) \
((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndbulkpipe(dev, endpoint) \
((PIPE_BULK << 30) | __create_pipe(dev, endpoint))
#define usb_rcvbulkpipe(dev, endpoint) \
((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndintpipe(dev, endpoint) \
((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint))
#define usb_rcvintpipe(dev, endpoint) \
((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
其中
 

#define PIPE_ISOCHRONOUS 0
#define PIPE_INTERRUPT 1
#define PIPE_CONTROL 2
#define PIPE_BULK 3
#define USB_DIR_OUT 0 /* to device */
#define USB_DIR_IN 0x80 /* to host */
static inline unsigned int __create_pipe(struct usb_device *dev,
unsigned int endpoint)
{
return (dev->devnum << 8) | (endpoint << 15);
}

 

这个些宏让人迷惑的是endpoint是什么 , 其实就是endpoint的地址,也就是endpoint->bEndpointAddress 。转了半天其实就是把端点地址再做一系列位运算得到的值。
未完待续~