请教linux 3.0 IDE驱动效率问题,block layer request queue相关

lifc0 2011-08-03 06:49:21
说一下背景:手头有块非标ARM(ARM9指令无Thumb,TLB/MMU/Cache不标准,没cp15等)板,是早些年给一客户设计的,对方想搞个完全自主产权SOC拿去拉投资。后来项目告吹没拿到开发费用,板就一直扔在我这里。刚好用了10年的老本本6月挂了(82430MX桥IDE部分虚焊),家里又需要一个家庭网关限制共享上网带宽(邻居小孩放假狂下载,淘宝卖改64M路由100多),就想到把这旧东西翻出来废物利用。从开始移植到今天过了快一个月时间,超过当初设想的2周,克服诸多困难目前又遇到一个麻烦,希望对linux 2.6 block layer request queue熟悉的朋友能够指点迷津。

后面叙述一下移植经过供参考。
...全文
263 4 打赏 收藏 转发到动态 举报
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
wuyangyong 2013-02-24
  • 打赏
  • 举报
回复
膜拜,虽然完全看不懂,但有些知识还是可以通用到别的地方。
lifc0 2011-08-03
  • 打赏
  • 举报
回复
照这个思路修改再测,原来想的太简单了。控制器每次探测到IDE Interrupt就会中断DMA传输,如果内核mask掉IDE中断DMA就永远没机会restart,也就永远不会完成。

上层mask不行就从底层入手,修改IDE硬盘的nIEN。谁知这盘有性格,一动nIEN就死盘,重上电才能激活发RST都没用。无聊中随意翻看内核代码,一瞥之下似乎看到一段关于DMA和PIO模式访问的注释,再回去找就不见了(头脑发昏典型症状)。好像说某些硬盘DMA进行中PIO模式访问寄存器会死机(原话不记得了),还列举某WD盘做为反面典型。联想到之前通过IDE register改nIEN死盘,或许这块富士通也有这毛病?于是把屏蔽中断指令放在0xC8(LBA28 READ)和使能DMA前,再测试还是那么多IDE中断!原来这块盘收到读写命令会自己打开IEN……

既然盘不行就换一块,拆台式机硬盘来测,还没等测试屏蔽IRQ居然发现一扇区一中断的毛病自己好了。换回富士通10G又是老样子,原来是这个10G老盘固件bug:无论设不设multiple sector,每传递一个sector都固执的来一次IRQ request。现在想想最开始就怀疑自己的控制器出问题是典型的自信心不足表现。

虽然换过硬盘不再出现大量IDE中断,但读效率仍没多少提升,还维持在可怜的4.8MB/s(desktop硬盘快了丁点)。于是又想到会不会是io scheduler问题?不过这个怀疑很快又被否定了,不用想hdparm -t发给内核的request很单纯,只有read没有write,而且lba地址都是顺序递增的,不会出现减少或者跳跃。为了放心还是把几种io scheduler轮番测了一遍,结果不出所料——不存在明显差异。

接下来几天被这个问题搞的寝食难安,睡梦中几度看到两行IO访问宏在眼前飘过,似乎就是这两行关键代码导致性能奇差,但无论怎么努力就是无法看清。更稀奇的是,半夜上过厕所接着睡,还能回到先前的梦境,再一次看到模糊的代码轮廓。
平时是3点起床,结果那天被这几行代码折磨的2点出头就睡不着。爬起然后用gvim打开驱动四处乱看,又怀疑是时序设置搞乱了,因为对2.6的架构不熟悉,或许哪个设置安插的时机不对,被后来的其他操作给冲掉了?

为了协助解决这个问题,决定把这个控制器接口用QEMU实现,然后用QEMU仿真器来调试。git clone拉下来最新的qemu.git(又追新了),用ARM920做框架,几十行代码实现UART桥(主体用QEMU的serial.c,FPGA实现的串口和标准16550A兼容),然后屏蔽掉内核中TLB/Cache/MMU相关代码,配置成ARM926重新编译,再改几处内存地址定义,SD卡和网口也换成QEMU支持的标准硬件,总算在QEMU里成功启动了。只是QEMU的user mode network有点麻烦,NFS Root启动有问题,换成tun模式折腾到天亮(6点左右)成功启动了。

接下来开始实现QEMU的IDE模块,注册设备对应的内存范围,截获FPGA IDE Controller端口R/W,再定义几个IRQ,backend用文件。然后加入我的DMA controller仿真实现:这部分比较简单,填SRC/DST/CTL/WIDTH/COUNT等几个寄存器,然后设置CTL.1启动DMA,QEMU里截获DMAC地址访问,把关键操作翻译成memcpy,完成后触发DMA IRQ通知target OS,全部不到一百行代码。

之后测试了PIO,能工作但是奇慢,仿真器就怕for+insw类的指令(rep insw还好),这个没办法了。再测试DMA,QEMU垮了。原来memcpy地址计算有问题,不能实现SRC或者DST的FIX模式,改成for/copy好了。但是为了debug还要改QEMU的驱动仿真。每次改仿真代码再编译QEMU然后重新运行target kernel实在麻烦,想了一下把lua jit放进去,每次raid controller访问交给lua模块过滤(类似wireshark),需要做什么动作动态修改lua代码,在qemu console里加个新命令重新reload脚本。

这里说明一下,如果只为了调试raid控制器,直接改kernel module加入printk/gpio输出也可以,但从长远来看用仿真器调试更方便。其实最开始移植TLB/MMU/Cache那部分代码时最需要仿真器,反复按Reset真的很累……只是当时对困难估计不足,认为个把星期就可以结束战斗回家过圣诞节,没必要去移植QEMU CPU。

通过lua钩子模块把相关IDE Command(主要是Feature 0x3设置传输模式和timing寄存器访问)送出来分析,但结果令人失望:没有发现什么不对的地方。接下来的一个白天脑筋狂转,又想到会不会内存cache配置有问题?这个想法马上被枪毙了,DMA访问是忽略D-Cache的。再接着又怀疑DDR参数配置有误,DMA引擎写入DDR耽误了太多时间?看过DMAC的相关参数,虽然不是特别优化但也不会慢成这样,何况在l4系统下DDR的参数和linux 2.6应该是一样的。不过为了保险起见,还是写一个空模块反复调用DMA拷贝数据,证实在两个系统下都是60MB/s左右的吞吐率(比较慢,不过这个内存控制器当年用的开源IP Core,暂时没能力也不准备去优化它,最多将来改一些访问参数)。

转眼快到7月底了,这东西卡在这里没进展,一气之下(病急乱投医)决定把全部改动弄回2.4测试(因为2.6的IDE子系统受框架限制,我的某些操作实现时机可能也不对)。回2.4要重新编译一堆东西,glibc、busybox、gcc…… crosstool-ng的OABI支持基本不能用了,编译哪个版本的glibc或者gcc、binutils都错。手工编译toolchain也问题多多,因为我现在的开发系统太新,make、gcc等都不适合那个年代的toolchain。后来上网找了一套别人编译好的gcc 2.9.x,包括gcc和glibc。再用它们编译低版本busybox也很顺,于是开始内核反移植(第一次从高往低移植,而且是2.6.39.3->2.4.21 rmk2这么大跨度)。

用1天把CPU相关改动移植回2.4,接下来2天将ide驱动也搞回了2.4,发现2.4唯一好处就是用一堆switch/case实现dma相关操作比较紧凑,不像2.6定义一堆struct嵌套struct,再去重载每个struct的某个function pointer。
不好的地方是缺少2.6的platform机制,2.6下像Windows驱动一样通过注册表添加新驱动和资源描述。而2.4就只好硬改源码依靠一堆#ifdef暴力解决,看起来没那么清爽优雅,不过为测试也就不考虑那么多了(管杀不管埋)。
这中间还有个插曲,比较新的busybox无论怎样编译modules utils部分都没办法在2.4.21下正常加载模块,去debian源找了个非常旧(2002年左右)的.deb包解开得到modprobe/insmod/lsmod/rmmod几个工具暂时解决问题。

结果真是一测吓一跳。2.4下UDMA 100吞吐率和我在l4系统下的驱动基本一致,可以到25MB左右。不是说2.6的block queue更高效吗?这个结论基本可以说明是2.4和2.6内核的某些差异导致了IDE子系统效率偏低,但究竟问题出在哪里还有待研究。

话说到了月底,这几天感觉状态不不佳提不起精神(7月太拼了),上网看新闻放松偶然看到linux kernel 3.0已经在7月22号正式发布。也想过直接用2.4算了,可是看到3.0都出了,我这东西虽说是个玩具,但回头去用2.4内核心理总是不太舒服。再说EABI和iptables等一些新功能2.4都不支持,包括无线网卡什么的都要打补丁,还是坚定信念要继续搞定3.0(2.6都看不上了)。

于是抱着侥幸心理下补丁,把2.6.39.3降级回2.6.39,再升级到2.6.30,编译内核遇到小小错误(没找到我定义的MACH_MACHINE_xxxx),因为arch/arm/tools/mach-types这个定义是机器生成的,需要重新加入我的定义。修改后成功编译,顺利启动系统。再加载IDE驱动,hdparm -t /dev/hda还是可怜的4.xMB(侥幸是不行的)。

或许之前太累,也可能打击太大,又或者空调太冷,总之7月最后一天成功病倒。刚好幼儿园8月放假,老婆带儿子回娘家半个月,留下俺一个人在家病的晕晕乎乎,天昏地暗睡了几天。

周二起来情况有所好转,收拾一下补充点能量继续研究。改内核配置,加入'Support for tracing block IO actions'选项,对3.0内核做探测。结果出人意料,hdparm -t执行过程内核多数时间居然是花在scatter-list的分配和释放。配置错误?内核bug?探测不准确?因为这个结果有点意外,加上烧还没完全退,只好吃点东西继续睡。

一觉到天黑,爬起来想想似乎睡觉时脑袋冒出过某种灵感却怎么都想不起,进入内核配置找了一圈,看到Choose SLAB allocator这里感觉或许有戏,我目前的选择是SLOB(因为是低端嵌入式系统就选了这个),之前看过SLOB的代码似乎优化考虑比较少(但代码简单总应该快吧,最多内存、cache效率地点),暂且换成SLUB试试看。

重编内核再测,果然UDMA 100的读取速度从4.8MB提高到8.4MB,仔细看看确定不是眼花看颠倒了。按说速度提升了接近一倍该高兴,可是想想和27MB/s差距实在太遥远,怎么也乐不起。

今天(周三)3点起床继续。想到优化有了进展家用勉强可以,再说主要问题不在我的程序,于是决定暂时放下效率,先实现UDMA写入。因为控制器是自己搞的,移植过几种操作系统(l4/qnx/vxworks/dos/ucos2),所以相对比较熟,不用看手册(当年项目没完,手册我还没写,想看也没有)。啃点面包,早餐半个小时左右加上DMA写入支持,原理和DMA读一样,只是走的DMA通道不同,其实如果不是反复初始化通道比较麻烦,复用一个通道也可以,反正DMA读写不会同时发生。

写入测试同样不顺,数据量稍大(单个请求>16K)就卡死,ide_dma_intr调ide_error输出错误信息是busy。想了一下是这样:控制器内部实现了buffer,os通过dma把数据丢给控制器,再收到dma完毕中断就不管了,控制器负责将buffer数据写入IDE硬盘。但2.6的ide_dma_intr调用dma_end后又读了一次IDE status,判断IDE驱动器状态是否正常(if (OK_STAT(stat, DRIVE_READY, drive->bad_wstat | ATA_DRQ))),而此时控制器还没完全消化掉肚子里(Buffer and/or FIFO)的数据,所以BUSY位肯定还在。而我之前搞的系统恰好在一次dma结束到下次dma开始之间插入的足够的其它操作(其实属于隐含bug),恰好没暴露问题。

开始认为容易解决,在dma_end里设个mask,下次ide_dma_intr调用read_status时mask一次ATA_BUSY再清空mask。按这个思路修改结果不行,会随机死。分析原因,当盘busy时我欺骗内核说已经ready,内核接下来就去做IDE访问(说谎很累,立马穿帮),这时某些IDE盘就挂了(规范也说下命令之前要确定不busy,busy时状态位其它都无效)。解决方法是改控制器逻辑,加个write fifo empty中断事件,但为了自己用懒得去动fpga逻辑(不如改软件方便),直接在ide_dma_end加个while (read_altstatus() & ATA_BUSY) {} 忙等,fifo排空之后再返回给ide_dma_intr就好了,等待时间大概在纳秒级,因为从控制器buffer到硬盘cache传几个数据持续时间不会太长,而且一次DMA请求最多16380*256=4MB-1K,忙等出现不会太频繁。

改完开始测,发现数据写多会CRC错。研究一通没找到真正原因,还好这次学聪明了,自我怀疑之余也审视一下别人。进入原来系统执行zero fill,数据一多同样CRC错,监测功能看(之前系统是做RAID控制器,存储管理方面相对完善)硬盘日志里也有CRC错,而且还给我加了好几十个扇区到glist。鉴于那块硬盘上还有重要数据,只好找个盗版dos版pc3k小心翼翼修复(笔记本硬盘的pc3k模块难找,还好盘比较老,新盘模块买都不容易)。

折腾半天glist总算消掉了,通过硬盘盒挂在pc上导出重要数据,再插回l4的raid系统下用ATA 0x50命令做一次全盘格式化,然后回到linux 3.0继续调。谁知没写几次又CRC错误跑出来了,因为此盘之前在笔记本上用就有问题(82430MX只支持UDMA33,偶尔还会报错),现在跑UDMA100会不会有点高了(虽然0xC2识别命令返回信息表明支持UDMA 100)?

改驱动把速度降到UDMA 66(hdparm -X死机,估计哪里没mask中断,暂不研究了),多写还会错。再降到UDMA 44(有些人没听过,和UDMA 66只差一项时钟参数,不过没怎么实际应用过)果然就没问题,写入速度8.5MB和读取持平(好低)。不过这个UDMA 44看了不舒服,而且速度这么慢用上也是鸡肋。

再换上台式机硬盘,用swap分区做写入测试,UDMA 6(迈拓盘支持自定义的UDMA 6=UDMA 133)工作正常,读写速度18MB/s,而原来系统和2.4xits(写入功能也挪过去了)都能写到>=52MB/s(PC写入58MB/s)。再次启用内核profile,还是瓶颈还是在ide_irq_intr里,调用ide_complete_rq再调用ide_end_rq时消耗了大量时间,说到底还是scatter-list的分配和释放。
lifc0 2011-08-03
  • 打赏
  • 举报
回复
上手后很快发现上当了,现在2.6的IDE和当年的2.4差异有点大,从OO角度来说它把很多通用的东西重构提取到了基类(ide*.c)中,派生类只需要实现一些底层函数。但此类重构对怪胎硬件是弊大于利,因为写死的逻辑多了,不像2.4时什么都可以自己来。非要定制就只好override一个很大的virtual method,然后照抄95%的实现,再加上自己的差异部分。

而且2.6的bio架构02年在2.5上做过点研究,现在也忘的差不多了。再加上过时的IDE框架没文档,libata内核里还包括了一份简要说明。不过这里要感谢licl2008的linux IDE驱动系列文章(http://blog.csdn.net/licl2008/article/category/742028),帮我省了不少时间,更坚定了一错到底的决心。

PIO容易实现,大概用了半天,再花一天半调试,主要问题集中在cmd/reg timing设置。
搞好后用坏掉老笔记本的10G Fujitsu硬盘PIO 4模式测试,读写4MB/s左右。奇怪为何比我自己用l4搞的那个os还慢不少(8MB/s)?因为最终目标是UDMA 4,所以没对PIO 4做进一步分析,直接进入UDMA实现阶段。

UDMA timing照搬l4系统的(这个控制器为专业存储设计,可配的参数比较多),配置fpga的AHB DMA做数据苦力,DMA完成中断里设置标志位,然后激发默认IDE IRQ Handler,在dma_test_irq里反馈中断完成标志。实现后发现读少量数据没问题,大块数据系统挂起。仔细读一下ide*.c,原来我的块合并算法有问题,思路还停留在2.4时代,现在的block queue layer已经帮我做好了合并,不用像2.4那样自己把request head下面的物理地址连续buffer合并成一个dma request。

但是2.6这个合并有点聪明过头,传给我的数据块很多是16384字节,而这个DMA引擎当初设计每个请求最大是0xfff个32bit word,也就是说最大16380字节(设计时为省io空间),当初如果定义0代表0x1000就好了……没办法,继续研究block request层,找到可以调用blk_queue_max_segment_size限制每个请求最大数据量,但没找到合适的插入时机,只好在dma_check里面动了点手脚。

问题出现了。驱动搞好后开始测试,看到结果心凉了:速度才4MB/s,和PIO 4一样。当年基于l4的系统UDMA 4测试块盘读取27MB/s,linux 2.6不会这么不济吧?难道这个UDMA 4是假象?

大胆怀疑小心求证,看了当初的设计,确实硬盘无论用PIO还是DMA控制器都可以通过DMA界面操作。改FPGA一个输出管脚,收到硬盘的DMA请求亮一个LED,发现灯不亮。静下来研究临时IO配置有问题,加上延迟就看到灯闪烁了(持续时间太短,LED亮度太低)。既然已经是UDMA为什么如此慢?难道mli/tack/cyc/cvs那几个timing register设置有误?用调试命令dump出控制器内部的寄存器,数值也是正确的。

接下来怀疑和IRQ处理有关,会不会某个IRQ没有mask,导致内核额外处理开销?看过/proc/interrupts还真有收获,IDE中断次数非常高,用hdparm -t读取14M测试数据,IDE中断增长了28700+,平均传输每个512字节(扇区)就中断一次,这时候想当然的认为大量的IRQ拖慢了IDE传输率(因为我这个控制器每次收到中断就会暂停DMA,需要在IRQ handler里面continue)。

这是DMA模式,全部DMA链表执行结束才送一个IDE中断,为何现在一扇区一中断?难道是multiple-sector设置没有生效?又去修改自定义的tf_load和exec_command增加调试点,同时dump出控制器内部状态,再执行hdparm -m16 /dev/hda,确认这块硬盘支持multiple-sector,而且已经设置成了16。

既然遇到了怪事就要考虑是哪个环节最可能出问题,可找了一天也没什么结果。到了下午头脑发混准备睡觉(平时3点起床,12点睡觉),后来老婆提醒,跑原来系统有没有问题?刷掉ARM Boot换上之前的系统,居然也一个毛病:DMA模式下每传一个sector就中断一次!但是可能l4的中断开销比较低,我的实现比较优化(想当然了)所以对传输率没什么影响。

确定了现象开始找原因,最先怀疑自己的控制器逻辑实现有bug。那个设计当年用了100多万门,IDE核心是从网上download的,自己实现了一些raid和控制逻辑,想想就头大+全身没力。冷静下来想想,有些硬件问题可以软件屏蔽,DMA开始前mask掉IDE中断,最后一个DMA执行完再打开不就行了?
lifc0 2011-08-03
  • 打赏
  • 举报
回复
客户当年要求开发一套实时操作系统,当时正在研究l4和l4-hurd/l4-linux,所以就用l4实现了一套micro-kernel,包括基本posix api和tcp/ip bsd协议栈以及elf支持,之后移植了fpga加速的mpeg4/264编解码器。但眼下这些垃圾我完全用不上,自己用还是linux最方便,所以决定移植Linux做NFS server+PXE server+router,顺便24小时跑帮老婆搞的网游脱机挂。

7月10号左右开始做,一周左右完成了Linux的TLB/MMU/Cache和分支预测移植,因为目前linux不支持ARM的tickless(估计要做大手术),所以节能和tickless稍候再议。

IRQ和Timer有点烦,之前设计IRQC时总想着优化把外设中断都映射到了FIQ,而Linux的FIQ支持不全,改了2天内核感觉方向不对。之所以开始没优先改fpga,因为我只有linux,不想换windows再装fpga开发环境。而eda工具很多要破解,在linux下用wine未必能运行。权衡后认为有必要搞定开发环境,因为后面估计还要用到。好在现在wine比较成熟,尝试装不同版本最后总算找了能用的组合。

接下来到串口、网口和SD卡模块(fpga实现),这上面又折腾了几天,还好没遇到大的困难。

再来到用户层。为了能跑go语言(6月搞了个脱机挂,用了go+lua jit开发的)必须搞定EABI(go的OABI支持缺少维护和测试),但这个ARM core不支持Thumb。这上面耗了好几天,主要是改gcc、binutils、glibc和crosstool-ng,否则-march=armv4 -mno-thumb-interwork还会生成讨厌的bx字节码,那几天走楼梯都看到objdump -d闪过bx的身影……

go运行环境基本不用改,设置GOARCH=arm,build出来就能用。只是lua jit作者没考虑ARMv5之前的架构(给赞助他才开发),还好他实现了双数体系,自己用这个ARM的SIMD实现了64位浮点和32位整数运算,测试脱机挂没发现明显bug,这部分工作又花了2、3天。

20号左右开始实现存储驱动,硬件fpga实现通过AHB连ARM。用户当年准备把网络存储做为典型应用来推广,要求存储模块在其他环境(比如fpga内部的8051/196 IP core独立控制)也能运作,同时支持多个IDE、SATA硬盘和几种RAID模式。考虑到减少主机参与,所以这部分逻辑当时实现有点复杂。主机和存储模块通过ring buffer通讯,仿照scsi实现了几个queue。可以直接从内部网口收发数据(iSCSI协议),外挂DDR做Buffer。以至于后来导出的AHB Slave接口和标准IDE基本没相似之处。

按道理这类硬件驱动应该首选libata或scsi框架,但出于熟悉考虑(毕竟放在自己家里用,怎么快怎么来)还是选了defrecated的IDE子系统,因为2.4时代搞过一些IDE驱动,而对2.6的libata只是大概了解。因为控制器和标准IDE差距太大(更类似3ware的阵列卡),很多东西只能自己实现。经评估认为方案可行,2.6的IDE框架够灵活,要定制的功能都可以override,我这里重定义了ide_port_info、dma_ops、port_ops、tp_ops,然后通过ide_host_add注册。

4,436

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 内核源代码研究区
社区管理员
  • 内核源代码研究区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧