30天自制操作系统吧 关注:1,380贴子:4,760
  • 17回复贴,共1

铁娃娃OS上进行读U盘实验。

只看楼主收藏回复

《在铁娃娃OS上进行读U盘的实验》
一,简介。
利用国庆长假的时间,尝试做读取U盘数据的试验。
首要目标: 读取指定U盘位置上的数据,如指定某个扇区的512字节。
最终目标: 是读取U盘上的FAT32格式的一个文件。
硬件环境: 采用chipset ICH9的笔记本电脑。
参考文献:
1,《Intel® I/O Controller Hub 9 (ICH9) Family》
2,《Enhanced Host Controller Interface Specification for Universal Serial Bus》(EHCI USB 2.0)
3,《Information technology -SCSI Primary Commands - 3 (SPC-3)》
4,《Universal Serial BusSpecification 2.0》(备用)
5,《USB技术应用与开发之第六讲:制作U盘实例》(哔哩哔哩网站有视频)
工具软件: WINHEX.exe
可从网上下载的免费试用软件。用以读取U盘的扇区内容,与实验读到的数据进行比对。
类似Hedit.exe 可以显示16进制数,有搜索功能。不同的是Hedit.exe是看文件的内容,
WINHEX是看U盘的每个扇区的内容。大概也可以看移动硬盘的内容。
(待续)


IP属地:上海1楼2023-10-06 17:46回复
    三,EHCI Memory-Mapped I/O Registers。
    EHCI的寄存器组分两大类,一个是能力寄存器,一个是操作寄存器。
    用内存基地址值 + 寄存器偏移量,就能访问这些寄存器。见下图:

    有红点标注的寄存器是主要关键要用的,我把这些主要寄存器的读出数值显示出来,
    调试时便于随时观察。如下图所示:

    以上是常规操作,接下去就将进入USB的EHCI世界了。
    规范书的内容很多,很繁琐。开始没有方向,只好都去看看,也不得要领。
    b站上有USB的技术应用与开发的讲座的视频,我觉得不错。女教师备课认真,
    讲话流畅,口齿清晰。其中第六讲是讲U盘的,大约两节课的时间。看完后
    大致了解了读写U盘需要经历哪些步骤。
    (待续)
    10.06


    IP属地:上海3楼2023-10-06 20:07
    收起回复
      二,搜索EHCI在PCI中的配置寄存器空间(补)
      目的是要找到EHCI的基地址值和中断号。
      下图是ICH9 Spec中的EHCI的配置寄存器表:

      搜索的方法与UHCI相同的。我根据SPEC的要求D=29,F=7,搜索到整个EHCI配置空间
      截图如下:

      实际有用的配置基本寄存器都集中在0x00~0x3F里。
      基地址值=0xFF9FE000。中断号=0x0B。
      与之前用到的UHCI的区别是基地址所指不是I/O口了,而是内存的地址。
      只要用这个基地址+偏移量,就可以读取EHCI的各个I/O寄存器了。
      (补完)


      IP属地:上海4楼2023-10-08 17:13
      回复
        四,关键的QH与qTD
        QH是队列头,qTD是队列头带领下的一个或几个元素描述符。描述符就是告诉硬件要怎么
        去做。读取U盘就要靠如何正确掌握使用它们了。
        QH的数据结构图:

        qTD的数据结构:

        详细的说明,可以看前面介绍的参考资料。看上去比UHCI要复杂得多,我也有好多地方
        也没有吃透,也不敢随便瞎解释。
        与UHCI类似EHCI的硬件也会根据内存中的QH与qTD的设置,自行完成一些列的USB操作。
        CPU则在中断响应中,做一些软件的处理。OS软件开始的主要任务是如何搭建这些QH与qTD。
        AsyncListAddr与QH组合示意图:

        据书上说,读写U盘是属于异步传输,特点是对时间要求不严格,但要求数据的准确性最高。
        AsyncListAddr寄存器(0x38)的内容是指向QH的指针,见上图。右面的QH是循环工作的。
        六边形是什么意思? 可以查查网上的解说。
        QH与qTD的异步调度示意图:

        读取U盘的操作,要参考这张图。比如,主机向U盘发送一个命令,主机接收U盘的数据,
        主机确认数据正常传输完了。
        第四节基本是把EHCI SPEC的内容搬过来。软件具体究竟应该怎么搭建呢?最好是有个
        简易的例子。在网上找了找,有Linux的的代码和解释,我既看不懂,也没有耐心。
        于是,我想到一个方法:在内存里寻找硬件在识别插入U盘时的一串操作后的残留痕迹。像
        之前在做UHCI实验时,看插入USB键盘后内存里留下的痕迹一样。
        (待续)
        10.08


        IP属地:上海5楼2023-10-08 21:27
        回复
          五,探秘QH与qTD是如何编排的
          看过前面的一些屏幕截图,会看到一个特殊数值: 0xB79Bxxxx。我在UHCI里面也遇到的这个地址数值。
          于是我就读取 0xB79B0000~0xB79BFFFF里面的内容,一页一页看,我看到了一些蛛丝马迹:

          上面的截图里,我已经把几个关键的数据区域的地址及其内容提取出来并显示在桌面上。
          0xB79B0000~0xB79BFFFF里的大部分内容都是0x00000000,就不作显示。
          1,看0xB79B8100,0xB79B8180,0xB79B8200里的内容,它们极像是QH的数据结构。
          2,看0xB79B82c0里的内容,它们极像是qTD的数据结构。
          3,看右面的0xB79B86F0~0xB79B86FC的内容,"55534253"这不是CSW的“签名”吗? ("USBS")。
          这就是当U盘插入笔记本电脑后,留在内存0xB79B0000~0xB79BFFFF里的关键内容。
          对我来说,如获至宝。有了这些数据后,接下来就是顺藤摸瓜,找出这些数据及命令的
          先后顺序,及相互关系。
          下面试着破译解读第一段 0xB79B8100里面的QH的含义:
          0xB79B8182: 是下一个水平链接指针,指针地址是0xB79B8180,末尾的2是指有效QH指针。
          0x02006202: 装置的地址=0x02,端点号=02,高速USB,最大传输字长0x200。
          0x40000000: 暂不理睬它。
          0xB79B82C0:当前qTD的指针。
          0x00000001:覆盖工作区,末尾的1表示这个qTD的地址是无效的。
          0x80008D00:dt=1,下一个传输要用dt=0(要遵从DATA0,DATA1交替传输),传输方向为IN。
          0xB79B86FD:读到内容存放的地址,即"55534253 00000000 00000000 00",最后一个
          0x00表示读取OK。存放的地址原本是0xB79B86F0,因为存放0x0C字节后,
          当前的偏移量就指向到0xB79B86FD了。
          0xB79B82C0里面qTD的含义:
          0xB79B82C0: 是qTD。与上面的QH覆盖区域的内容很相似,这就是硬件自动复制的结果。下面一行的
          0xB79B9000 0xB79BA000 0xB79BB000 0xB79BC000是BufferPointerPage1~4。
          右面的0xB79B8704的内容:经过我艰苦的查证是一个SCSI(0x5A)读取U盘的Mode Sense(10)的参数
          由于覆盖区域的数据是不断地被新的qTD执行结果所覆盖,所以上面的内容是硬件执行最后一个qTD留
          给福尔摩斯的宝贵现场。
          前面出现的CSW,SCSI的名词,我在看b站的USB讲座时已经有所耳闻,所以除了激动之外并不感到
          茫然。下一节,应该把b站的《USB讲座》的内容搬过来,再作补充说明。
          10.08.


          IP属地:上海6楼2023-10-08 23:02
          回复
            六,读取U盘的基本步骤
            我就借用《USB技术应用与开发之第六讲:制作U盘实例》的图片复制如下,具体的讲解
            请去看b站的视频吧。
            1,U盘设备传输协议,重点是传输协议和SCSI命令集。

            2, 传输协议框图:
            读取U盘三部曲:
            第一步,发送CBW命令给U盘;
            第二步,读取U盘数据(Data In);
            第三步,接收CSW。

            3, CBW命令格式:
            一共有0x1F个字节组成。

            4,CSW格式:
            一共有0x0C个字节组成

            说明一下,CBW比较好理解,即向U盘发出命令,要求U盘按CBW里面的要求传送数据出来。
            最初我对CSW命令出现理解上的困惑,它是主控设备发给U盘的命令呢?还是U盘自己在完成
            Data In之后,紧接着发给主控设备的呢?按以前看书上说USB永远是主控设备发起,USB装置
            被动响应的意思去理解,应该是主控设备发起的。那么就按CBW的方式发送给U盘了?
            最后经过实验操作才搞清楚,"55534253 00000000 00000000 00"是U盘发过来的。
            (待续)
            10.08.


            IP属地:上海7楼2023-10-08 23:38
            收起回复
              七,SCSI命令。
              以前我曾经用过SCSI接口的移动硬盘和外接光盘驱动器,那都是上个世纪的事情了。
              后来都用USB接口的移动硬盘了,没有想到现在读U盘依然是要涉及SCSI知识的。
              SCSI的命令很多,本实验只用到CBW(0x28),读取U盘物理扇区的命令块。

              根据上面的定义,套在CBW命令的后面,具体的CBW(0x28)的命令形式如下:
              0x43425355
              0x00000001;//记录号
              0x00000002;//希望传输0x200字节
              0x280c0080;//命令块码0x28,命令块长度0x0C,传输方向:0x80
              0x00000000;//U盘扇区号,0是Boot扇区。
              0x01000000;//读取一个扇区。
              0x00000000;//
              0x00000000;//
              CSW命令是U盘传输给主控的,不用自己组织。
              对照第四节的QH,qTD的数据结构,下面是我在运用QH和qTD时一些理解:
              .QH的第一个DWord是水平链接后面的QH指针。
              .QH下面由一个或数个相同传输方向的qTD组成,比如都是OUT传输事务,或都是IN事务。
              CBW是OUT事务,DATA IN与CSW都是IN事务,可以放在同一个QH下面。
              .QH的第二个DWord中有DeviceAddress,是设备地址。共有127个地址。我这里的地址
              是0x02。EndPt是端点的意思,OUT与IN的端点是不一样的,OUT事务的端点是0x01,IN
              事务的端点是0x02。这就是前面硬件执行几个SCSI命令后留下的残留现场告诉我的。
              .QH第四至十二DW覆盖区域,上图中涂黄色的部分。硬件可以读写黄色部分的内容,我
              理解为QH的临时工作寄存器。
              .qTD的数据结构由8个DW构成,第一DW是指向下一个qTD的指针。bit0=1,表示无效指针。
              也可以理解后面没有qTD了,本次QH宣告结束,转去水平链接的QH。
              .第三DW是token(队列元素描述符的令牌)是qTD设置中最重要最复杂的部分。
              Status中有开始起动位,PID code有SETUP,OUT,IN 三种令牌代码。在UHCI中,
              SETUP的PID是0x2D,OUT的PID是0xE1,IN的PID是0x69。而在EHCI中则改成是:
              OUT Token=0x00,IN Token=0x01,SETUP Token=0x10。传输时会自动变换的,不去操心。
              设置CBW和DATA IN 要用到的。
              . ioc,提示在本qTD完成时,主机控制器会发出中断。
              . Total byte to transfer,本qTD要传输的字节数。比如要读取一个扇区,就设置为0x200。
              . dt是用于DATA0与DATA1变换用的。OUT事务的dt与IN事务的dt要分开单独使用。
              . 第三DW的CurrentOffset,类似UHCI的BufferPoint。它指向要传输出去或传输进来的数据的位置,
              . 在一个QH开始工作时,硬件会先把当前qTD的内容复制到QH的黄色部分里,然后根据里面
              的设置要求进行处理与数据传输,完成后的结果也会在黄色部分体现出来(临时寄存器么)。而
              且,还会回写到qTD的黄色部分里面。
              我开始看SPEC时,是看不懂QH与qTD这一套繁杂的叙述的。后来慢慢摸索,形成了上面的
              理解文字,也只是摸到一些皮毛,还可能理解错了。剩下的最好是看SPEC原文吧,它是不会
              错的。
              10.09


              IP属地:上海8楼2023-10-09 00:13
              收起回复
                八,实际读取U盘第一个扇区Boot扇区。
                我用工具软件WINHEX.exe读取U盘的Boot扇区:

                再用我编排的CBW命令,DATA IN命令试读到的U盘第一个扇区数据。
                0xB79B86F0是读取到的CSW,0xB79B8700里是一个完整扇区的内容:

                两相对照,一模一样。
                这就是国庆长假的收获。
                这只是读取U盘的第一步,以后的任务还很艰难,道路还很长。
                补遗:我破译的EHCI硬件执行的几个SCSI命令如下:
                1,CBW(0x12) INQUIRY DATA Format。读到24个字节。
                2,CBW(0x00) 查询磁盘Ready。读到0字节。
                3,CBW(0x25) Read Capacity Command,读到8个字节。
                4,CBW(0x5A) 读取 Mode Sense(10),读到40个字节。
                这4个CBW命令很有用的,我正是参考这4个命令格式,自己摸索着做了CBW(0x28)。
                时间不早了,本文就这样结束吧。以后有时间再把FAT32文件系统考虑进去。
                (完)
                10.09 0:40


                IP属地:上海9楼2023-10-09 00:40
                回复
                  想把硬盘的FAT32文件系统的代码,适当修改一下移植过来。
                  起初以为问题不大,结果暴露出许多疑难杂症。这大概是软件与硬件没有很好相容性有关吧?
                  1,FAT1扇区,FAT2的扇区无法读出,很奇怪硬盘没有这种问题。引入FAT32的第一步就走不下去。
                  2,连续每秒读U盘BOOT扇区,几分钟后会失败。是发热引起吗?还是软件时序冲突?
                  3,猜想U盘中的FAT32的BOOT,FAT,ROOT扇区,是访问最频繁的扇区。是不是有
                  寿命问题? 是不是要像读取软盘那样,要考虑可连续尝试读5次?
                  4,换一个不同牌子(都是USB 2.0)容量相同的U盘,无法读取扇区。
                  5,QH与qTD的区域是有硬件参与读写的,这时软件应该回避,避免造成冲突。
                  10.15


                  IP属地:上海10楼2023-10-15 11:17
                  收起回复
                    今天完成了“铁娃娃”OS从FAT32格式的U盘里读取文件的操作部分。
                    先上图:

                    0,活用已经完成的读取U盘的指定扇区的512字节数据的函数。
                    1,在命令行窗口console.c 里加了一个dir3的命令。dir是查看软盘里文件,dir2是查看硬盘
                    中的文件。dir3就是查看U盘中的文件。U盘是按FAT32格式进行格式化。只放了四个文件:
                    一个文本文件,两个jpg图片,一个音频文件。我没有像MSDOS那样用C: D: E:盘的方式,
                    原因就是为了简单化。
                    dir3命令,是完全按照dir命令的方式做的。
                    2,要打开FAT32格式的文件,首先要读取FAT1的数据,根目录的数据,和指定文件的位置
                    的数据,这三个关键部分。我选择打开一个图片文件2.jpg。上周提到我使用的U盘无法正常
                    读取FAT1位置的扇区数据,临时解决的办法是用winhex软件把FAT1的扇区内容复制到前面
                    可以读取的扇区里。但也遇到相同的问题,即原先可以读取的扇区变得不可读取了。很奇怪
                    ,显然不是扇区的物理寿命问题,似乎与里面的数据有关?于是我只复制0x100部分看看可
                    不可以读取,结果是可以的。因为U盘的里的文件不多只有四个,每个文件只用到4byte,所
                    以不阻碍影响实验。至于为什么FAT1第一个扇区里的数据无法全部读出,放到以后再仔细
                    学习思考了。
                    上面的图片有14KB大小,需要连续读30个扇区。也是参考file.c的方法。把30个扇区的数
                    据复制到filebuf。jpg图像的解码与显示是参考了gvier.c。
                    能打开图片了,播放音乐文件,放映电影文件问题就不会太难了。还是采用边读边放的方
                    式进行。
                    存在的问题:
                    a. 现在手头上只有一个U盘可以正常工作,其他的U盘皆不响应。用usbview软件查看,
                    它们的差异是:可以工作的U盘有三个端点,bInterval=0x01;不响应的U盘有两个端点,
                    bInterval =0x00。我猜测可能是bInterval(时间间隔)原因。
                    我比较走运,一开始碰巧用了可以走通的U盘。这个U盘是某展览会上作为礼品送的,
                    选择它的理由是没有金属外罩,容易插拔。
                    b. FAT1和FAT2的扇区读取,存在障碍。原因不明。我现在是在别的扇区里复制FAT1前面
                    一小部分的数据。暂时起个名字是FAT0。这样就不是标准的FAT32了。所以现在别人的U盘
                    还不能用,只能用我自己改造过的U盘。
                    10.22


                    IP属地:上海11楼2023-10-22 20:19
                    回复
                      解决了之前不能读出U盘中的FAT数据的问题。
                      之前一直是按读取U盘的Boot扇区进行程序调试的。读取成功后,就习惯于按读取扇区为基本
                      单位读取FAT表,根目录,文件数据等扇区。因为U盘的文件不多,就只读FAT表的第一扇区。偏
                      偏就是读不出,而根目录却是可以读出的。一直不得其解。
                      现在,发现如果按读一个磁簇的方法,FAT表的内容是可以读出的。这样我就能名正言顺地使用
                      FAT表了,而不是自己伪造一个FAT,放在其他位置了。
                      记得有些单片机的烧写HEX代码时,也是要按页写入flashROM的规定,不是按byte单位写入。
                      软盘,硬盘是按扇区(512byte)为单位读写的。U盘如果按磁簇为单位读写入的话,容易成功。
                      是什么原理?我没有深入追究。
                      这是用gview命令,打开U盘中的一个图片的桌面照片:

                      下面是打开mp2播放器,播放U盘的mp2格式的音频文件的桌面照片。

                      还剩下最后一个难题:
                      就是只有手头上唯一的一个老牌子U盘可以完成上面的所有操作,而新购买的U盘都无法
                      正常工作。也就是说,现在还不能打开别人U盘中的文件。
                      10.29


                      IP属地:上海12楼2023-10-29 23:01
                      收起回复
                        剩下最后一个使用正规U盘的问题,也解决了。
                        原因是在设置读取一个扇区的数据长度0x0200,我在CBW参数填写0002。用非卖品
                        U盘做实验时,是能够读出了一个完整扇区的数据,所以没有怀疑有问题。现在发现
                        0002实际是0x0002字节,所以用正品的U盘,当然怎么调试都是出错的。即qTD的
                        Token 的Status.5=1,Data Buffer Error。
                        不是三个端点与两个端点的问题(想复杂了)。当我改为0200后,一切都迎刃而解,
                        不分新旧U盘,都能正常读取。
                        现在才明白为什么展会送这种U盘了,因为它不能作为正品U盘出售。
                        铁娃娃OS读取U盘文件的实验,到此结束。
                        11.04


                        IP属地:上海13楼2023-11-04 23:27
                        收起回复