当前版本针对Qemu模拟器,增加了UHCI控制器(USB v1.1标准)的驱动程式,以及USB鼠标和键盘的驱动程式。目前只能在Qemu模拟器下实现USB的鼠标键盘功能。这些驱动程式的代码,主要是从pdoane的osdev项目中移植过来的,另外,有部分代码是从Linux-3.2.0的内核源码中移植过来的...

    页面导航:
项目下载地址:

    v3.2.0版本的项目地址:

    github.com地址:https://github.com/zenglong/zenglOX (只包含git提交上来的源代码)

    Dropbox地址:点此进入Dropbox网盘 

    Google Drive地址:点此进入Google Drive云端硬盘

    sourceforge地址:https://sourceforge.net/projects/zenglox/files 

    在上面的三个网盘中(不包括github),v3.2.0版本的相关文件,都位于zenglOX_v3.2.0的文件夹中。在该文件夹里,各文件的作用如下:
  • zenglOX_v3.2.0.zip:当前版本的源代码的压缩包。
  • zenglOX.iso:当前版本的iso镜像。
  • usb11_RemovePassword.pdf:USB v1.1标准的参考手册。
  • uhci11d.pdf:UHCI控制器相关的参考手册。
  • HID1_11.pdf:HID(Human Interface Devices 人体学接口设备)的参考手册,HID包括了USB鼠标和USB键盘在内。
  • readme.txt:当前版本的说明文档。
  • binutils-2.25.tar.bz2:在GCC 4.9的编译环境下,搭建交叉编译工具时,所需的binutils程式的2.25版本的源码压缩包。至于为何在GCC 4.9的编译环境下,需要使用2.25的版本,原因已经在说明文档中做了解释。
  • gcc-4.9.2.tar.bz2:在GCC 4.9的编译环境下,搭建交叉编译工具时,所需的GCC程式的4.9.2版本的源码压缩包。
  • gmp-6.0.0a.tar.bz2:编译GCC时,所需的GMP程式的6.0.0版本的源码压缩包。
  • mpc-1.0.3.tar.gz:编译GCC时,所需的MPC程式的1.0.3版本的源码压缩包。
  • mpfr-3.1.2.tar.bz2:编译GCC时,所需的MPFR程式的3.1.2版本的源码压缩包。
  • qemu-2.3.0.tar.bz2:Qemu模拟器的2.3.0版本的源码压缩包,之所以建议使用该版本的qemu,原因也在说明文档中做了解释。
    这些文件的md5值,如下所示:
  • zenglOX_v3.2.0.zip  [md5]:  782f3e39bebeffa08f0f0bd8ffc2d12d
  • zenglOX.iso  [md5]:  5d04e6812870d8772e5973e7c15b931c
  • usb11_RemovePassword.pdf  [md5]:  b783d557792750ecd1e011a2e09d428f
  • uhci11d.pdf  [md5]:  6b074ed08801eba52d63b5ac9ba0ad6d
  • HID1_11.pdf  [md5]:  5b172587a72b729bbdc69961995853a0
  • readme.txt  [md5]:  26fbffe454441079445218873e10433c
  • binutils-2.25.tar.bz2  [md5]:  d9f3303f802a5b6b0bb73a335ab89d66
  • gcc-4.9.2.tar.bz2  [md5]:  4df8ee253b7f3863ad0b86359cd39c43
  • gmp-6.0.0a.tar.bz2  [md5]:  b7ff2d88cae7f8085bd5006096eed470
  • mpc-1.0.3.tar.gz  [md5]:  d6a1d5f8ddea3abd2cc3e98f58352d26
  • mpfr-3.1.2.tar.bz2  [md5]:  ee2c3ac63bf0c2359bf08fc3ee094c19
  • qemu-2.3.0.tar.bz2  [md5]:  2fab3ea4460de9b57192e5b8b311f221
    在使用代理进行下载时,经常会出现下载不完整的情况,通过md5sum工具计算出下载的文件的md5值,并与上面的值进行比较,就可以判断出下载的文件是否完整了。在官方网站上下载的gcc或qemu之类的文件,在版本相同的情况下,也具有和上面一样的md5值,因为,作者就是从官方站点下载下来的源码压缩包,再上传到网盘上的。

    下面是readme.txt说明文档中的内容:

readme.txt -- 当前版本新增的功能,以及修复的BUG:

    当前版本针对Qemu模拟器,增加了UHCI控制器(USB v1.1标准)的驱动程式,以及USB鼠标和键盘的驱动程式。目前只能在Qemu模拟器下实现USB的鼠标键盘功能。这些驱动程式的代码,主要是从pdoane的osdev项目中移植过来的,另外,有部分代码是从Linux-3.2.0的内核源码中移植过来的。

    pdoane的osdev项目的github地址为:https://github.com/pdoane/osdev

    除了USB相关的驱动程式外,当前版本还增加了klog日志,该日志会将内核通过zlox_monitor_write等函数输出的信息,记录到里面,当然目前最多只能记录10 * 1024即10K字节的内容。这样,在命令行中,就可以通过cat klog或者ee编辑器来查看klog里的内容,从而可以简单的查看下内核输出了些什么信息。

    在内核刚启动时,当zlox_monitor_write输出的内容很多时,会自动滚屏。之前的版本中并没有滚屏相关的函数。

    shell命令行中可以使用Home,End及上下左右键(上下键在之前的版本里就有了,主要是左右键可以移动光标)。

    此外,当前版本实现了真正意义上的多任务,当然,文件读写操作目前同一时间只允许一个任务执行。在之前的版本中,当你执行任何一个程式时,其实是以关闭中断的方式在运行的,除非遇到syscall_idle_cpu系统调用时,才会将CPU资源释放出去,鼠标键盘以及定时器中断才能被处理。因此,之前版本中,当你执行isoget -all命令或者用cat命令显示一个内容很长的文件时,你会发现鼠标键盘都不能使用,必须等这些命令执行完毕才能用。那么,如果某个用户程式陷入死循环的话,那么内核基本上也就挂在那里了。

    当前版本通过将desktop程式里的syscall_execve语句的位置做调整,让桌面启动的其他应用能够以开中断的方式运行,然后通过对zlox_task.c文件中的任务调度算法做调整,让每个应用都能分配到合理的CPU资源。另外,针对一些可能会长时间进行I/O操作的系统调用,比如文件读写相关的函数,以及zlox_cmd_window_put之类的显示输出函数,都在里面加入了zlox_isr_detect_proc_irq函数,该函数会调用_zlox_idle_cpu函数来将控制权释放出去,让别的任务有机会运行,以及让鼠标键盘之类的中断能被响应。

    由于当前版本可以多个任务并行运行,而文件读写操作中,目前有太多全局变量有可能会在并行运行时被修改破坏掉。因此,在zlox_fs.c文件里加入了g_fs_lock的文件锁,当某个任务需要进行文件读写相关的操作时,会先判断该文件锁是否被其他任务加锁了,如果加锁了,它就会释放CPU资源,然后等待解锁。因此,如果多个任务同时都需要进行文件读写操作的话,就可能出现某些任务等待另一些任务的情况。这都是文件锁在起作用。

    在多任务当中,那些主动使用了syscall_idle_cpu系统调用的任务具有最低优先级,其他的有消息需要处理的任务,或者在执行文件读写操作的任务,或者在执行显示输出操作的任务,以及其他没有使用过syscall_idle_cpu系统调用的任务,这些任务都被认为是处于Busy繁忙中的任务,都具有相同的优先级,他们会平分CPU资源。

    如果你觉得某个任务过于繁忙了,一直没停下来的话,或者陷入了死循环的话。可以另启一个终端,用ps命令查看其ID号,再用kill命令将其终止掉。当某个任务被强行终止掉时,如果它执行过文件锁的加锁操作的话,会自动将文件锁解除掉,从而让其他任务可以继续加锁执行。

    当前版本还对某些程式的参数和用法做了些调整:

    例如: testoverflow的参数调整为 testoverflow [-u][-x][-k] 。

    其中,testoverflow -u 还是跟以前一样是抛出一个用户栈溢出的错误。

    testoverflow -x 则会陷入一个for语句的死循环,如果是之前的版本,这样的死循环会当掉系统。当前版本中,可以将其kill掉,或者直接关闭终端窗口,来将其终止掉。

    testoverflow -k 则会通过 syscall_overflow_test 系统调用,以内核栈溢出的方式,抛出double fault异常,这个异常属于严重错误,会当掉系统,然后dump出一些基本的寄存器信息出来。

    因此,testoverflow属于测试用的程式。

    ee编辑器目前为v1.4.0的版本,可以通过 ee -v 命令来查看版本号。主要增加了F3功能键,当按下F3键时,会将编辑器设置为read only只读模式,同时在顶部flags区域显示一个小写的r字母,表示当前位于只读模式,如下所示:


图1

    再按下F3时,可以切换回writable可写模式,同时顶部的flags区域的r字母会变回小数点。

    因此,F3键可以在只读和可写模式之间进行切换,默认是可写模式,在只读模式下,你不能修改文件的内容,当然可以保存文件,这样,当你只需要查看文件内容时,就可以设置为只读模式,来防止一些误修改的操作。因为,ee编辑器里有很多功能都是通过 "ctrl+字符" 来实现的,如果ctrl键没按下的话,那么对应的字符就会误修改文件的内容了。

    zengl程式增加了一个-l参数,只有使用-l或-d参数时,zengl才会将debug info调试信息输出到 hd/zl.log 的文件中。如果zengl后面只跟随了脚本文件名的话,将不会输出调试信息(输出调试信息会增加一些开销,这些开销在Qemu模拟器下表现的比较明显)。

    由于修改了ee和zengl程式,因此,当前版本在执行时,需要先通过 isoget -all 命令来更新磁盘里的文件数据。

    ps程式的参数调整为:ps [shownum][-m][-d [shownum]][-u [shownum]][-x [shownum]] 即ps程式的最后可以跟随一个数字,表示显示前多少条任务信息。这样,当任务数量多的时候,可以将任务信息,一部分一部分的显示出来。

    lspci程式在显示每个PCI设备的信息时,会多显示一个prog_if的信息,通过class,sub_class以及prog_if信息,就可以更准确的判断出设备的类型。比如,UHCI控制器的PCI配置空间里的class为0x0C,sub_class为0x03,prog_if为0x00 ,如下所示:


图2

    cat程式的参数调整为:cat <filename> [show char num] 也就是在cat程式的最后可以提供一个数字,通过该数字来确定要输出前多少个字符,这是个可选参数,当没提供这个显示字符数时,将会把文件里的所有内容都显示出来。当文件内容很多时,通过显示字符数可以将内容一段一段的显示出来,比如提供1000的话,就可以看到前1000个字符底部的数据,提供2000的话,就可以看到前2000个字符的底部数据,因为,term终端没有垂直滚动条,我们目前只能看到一部分输出内容,如下所示:


图3

    当前版本还修复了zlox_kheap.c与zlox_uheap.c文件中的堆算法上的BUG。在zlox_free与zlox_uheap_free函数中,当header指针被contract回收操作完全释放掉了对应的物理内存页面时,再对header指针进行读操作时,就会抛出分页不存在的错误。当前版本就对该BUG进行了处理。

readme.txt -- 在GCC 4.9.x的编译环境中,组建交叉编译工具:

    在ubuntu 15.04的系统中,系统自带的GCC是4.9.2的版本。这个版本的GCC无法对v0.0.1版本中提到的binutils-2.23.1,以及gcc-4.7.2进行编译。这是由于GCC 4.9.2具有更加严格的语法检测机制,以及该系统环境下的texinfo,因为版本的兼容问题,也会导致gcc-4.7.2在编译时报错。

    因此,我们就需要使用更高版本的gcc来构建交叉编译环境。

    gcc-4.9.2的交叉编译工具的组建过程与gcc-4.7.2的组建过程是差不多的,过程如下:

    搭建交叉编译工具,最好是在命令行下切换到root用户,在Ubuntu里默认没有给root设置密码,可以通过以下命令来设置root密码并切换到root用户:

zengl@ubuntu:~$ sudo passwd root
zengl@ubuntu:~$ su -
root@ubuntu:~# 


    上面第一个 sudo passwd root 命令输入后,会提示输入两次root的新密码,设置密码后,就可以用 su - 命令来切换到root用户,下面搭建交叉编译工具的过程都用的是root用户。

    下面先使用mkdir命令新建/mnt/zenglOX/opt/cross目录:

root@ubuntu:~# mkdir -p /mnt/zenglOX/opt/cross

    最后生成的交叉编译工具集都会安装在/mnt/zenglOX/opt/cross目录内,当然你也可以安装到别的目录里,不过如果安装到其他目录中,那么在编译zenglOX时,就需要对makefile文件里的相关路径进行修改。

    我们先来构建binutils-2.25:

    先从网盘中下载binutils-2.25.tar.bz2文件,也可以从
    ftp://ftp.gnu.org/gnu/binutils/ 链接中找到对应的版本进行下载,

    例如binutils-2.25版本的完整ftp地址为:
    ftp://ftp.gnu.org/gnu/binutils/binutils-2.25.tar.bz2

    将下载的压缩包放置到/mnt/zenglOX目录中,然后使用如下命令进行解压:

root@ubuntu:/mnt/zenglOX# tar xvf binutils-2.25.tar.bz2

    接着创建binutils-build目录:

root@ubuntu:/mnt/zenglOX# mkdir binutils-build

    进入该目录:

root@ubuntu:/mnt/zenglOX# cd binutils-build
root@ubuntu:/mnt/zenglOX/binutils-build#

    在用configure进行配置之前,需要先安装texinfo (否则后面编译时,可能会报 `makeinfo' is missing on your system 以及 recipe for target 'bfd.info' failed 的相关错误。另外,如果读者遇到了这个编译错误后,再去安装texinfo的话,就必须重新configure配置一遍,因为只有这样,才能让Makefile文件中与texinfo相关的宏信息修正过来) :

root@ubuntu:/mnt/zenglOX/binutils-build# sudo apt-get install texinfo

    通过前面解压的binutils-2.25目录里的configure脚本来生成Makefile文件:

root@ubuntu:/mnt/zenglOX/binutils-build# ../binutils-2.25/configure --prefix=/mnt/zenglOX/opt/cross --target=i586-elf --disable-nls

    上面configure命令所使用的参数的含义,已经在v0.0.1版本对应的官方文章中介绍过了。

    生成了Makefile后,就可以用make命令来编译了:

root@ubuntu:/mnt/zenglOX/binutils-build# make

    编译成功后,通过make install命令来安装:

root@ubuntu:/mnt/zenglOX/binutils-build# make install

    安装完后,应该就可以在/mnt/zenglOX/opt/cross/bin目录中,看到生成的binutils相关的可执行文件了:

root@ubuntu:/mnt/zenglOX/binutils-build# ls ../opt/cross/bin/
i586-elf-addr2line  i586-elf-elfedit  i586-elf-nm       i586-elf-readelf
i586-elf-ar         i586-elf-gprof    i586-elf-objcopy  i586-elf-size
i586-elf-as         i586-elf-ld       i586-elf-objdump  i586-elf-strings
i586-elf-c++filt    i586-elf-ld.bfd   i586-elf-ranlib   i586-elf-strip


    binutils的交叉工具编译安装好后,就可以继续安装gcc-4.9.2的交叉编译工具了,先从网盘中下载gcc-4.9.2.tar.bz2,gmp-6.0.0a.tar.bz2,mpfr-3.1.2.tar.bz2,以及mpc-1.0.3.tar.gz文件,当然也可以从gnu的ftp站点中下载这些文件,具体的ftp链接地址如下:

ftp://ftp.gnu.org/gnu/gcc/gcc-4.9.2/gcc-4.9.2.tar.bz2
ftp://ftp.gnu.org/gnu/gmp/gmp-6.0.0a.tar.bz2
ftp://ftp.gnu.org/gnu/mpc/mpc-1.0.3.tar.gz
ftp://ftp.gnu.org/gnu/mpfr/mpfr-3.1.2.tar.bz2

    其实也就是 ftp://ftp.gnu.org/gnu/ 这个ftp目录中的各个项目的地址。我们可以在这个ftp站点里看到gcc及其他GNU工具的各个版本。

    将这些文件下载到/mnt/zenglOX目录中后,对这些文件进行解压:

root@ubuntu:/mnt/zenglOX# tar xvf gcc-4.9.2.tar.bz2 
root@ubuntu:/mnt/zenglOX# tar xvf gmp-6.0.0a.tar.bz2
root@ubuntu:/mnt/zenglOX# tar xvf mpfr-3.1.2.tar.bz2
root@ubuntu:/mnt/zenglOX# tar xvf mpc-1.0.3.tar.gz


    由于编译gcc需要用到gmp、mpfr和mpc的源代码,所以首先进入解压后的gcc目录,在该目录里建立一些符号链接来指向之前解压的gmp、mpfr和mpc的源代码的位置:

root@ubuntu:/mnt/zenglOX# cd gcc-4.9.2/
root@ubuntu:/mnt/zenglOX/gcc-4.9.2# ln -s ../gmp-6.0.0 gmp
root@ubuntu:/mnt/zenglOX/gcc-4.9.2# ln -s ../mpc-1.0.3 mpc
root@ubuntu:/mnt/zenglOX/gcc-4.9.2# ln -s ../mpfr-3.1.2 mpfr
root@ubuntu:/mnt/zenglOX/gcc-4.9.2# cd ..
root@ubuntu:/mnt/zenglOX# 


    接着创建gcc-build目录:

root@ubuntu:/mnt/zenglOX# mkdir gcc-build

    进入该目录,并通过之前解压的gcc-4.9.2目录中的configure工具来生成Makefile:

root@ubuntu:/mnt/zenglOX# cd gcc-build
root@ubuntu:/mnt/zenglOX/gcc-build# ../gcc-4.9.2/configure --prefix=/mnt/zenglOX/opt/cross --target=i586-elf --enable-languages=c,c++ --disable-nls --without-headers

    上面configure工具所使用的参数的含义,已经在v0.0.1版本对应的官方文章中介绍过了。

    接着,通过make all-gcc来编译GCC:

root@ubuntu:/mnt/zenglOX/gcc-build# make all-gcc

    编译成功后,通过make install-gcc来安装GCC:

root@ubuntu:/mnt/zenglOX/gcc-build# make install-gcc

    安装成功后,应该就可以在 /mnt/zenglOX/opt/cross/bin 目录中,看到用于交叉编译的4.9.2版本的GCC的相关可执行文件了:

root@ubuntu:/mnt/zenglOX/gcc-build# ls ../opt/cross/bin/
i586-elf-addr2line  i586-elf-g++         i586-elf-gprof    i586-elf-readelf
i586-elf-ar         i586-elf-gcc         i586-elf-ld       i586-elf-size
i586-elf-as         i586-elf-gcc-4.9.2   i586-elf-ld.bfd   i586-elf-strings
i586-elf-c++        i586-elf-gcc-ar      i586-elf-nm       i586-elf-strip
i586-elf-c++filt    i586-elf-gcc-nm      i586-elf-objcopy
i586-elf-cpp        i586-elf-gcc-ranlib  i586-elf-objdump
i586-elf-elfedit    i586-elf-gcov        i586-elf-ranlib


    生成gcc后还需要生成对应的libgcc:

root@ubuntu:/mnt/zenglOX/gcc-build# make all-target-libgcc
root@ubuntu:/mnt/zenglOX/gcc-build# make install-target-libgcc

    上面使用make all-target-libgcc命令来编译libgcc,使用make install-target-libgcc安装libgcc,编译安装后,可以在 opt/cross/lib 目录中查看到生成的libgcc:

root@ubuntu:/mnt/zenglOX/gcc-build# ls ../opt/cross/lib/gcc/i586-elf/4.9.2/ -l
总用量 476
-rw-r--r-- 1 root root   2432  6月  5 00:37 crtbegin.o
-rw-r--r-- 1 root root   1388  6月  5 00:37 crtend.o
drwxr-xr-x 2 root root   4096  6月  5 00:37 include
drwxr-xr-x 2 root root   4096  6月  5 00:27 include-fixed
drwxr-xr-x 3 root root   4096  6月  5 00:34 install-tools
-rw-r--r-- 1 root root 417486  6月  5 00:37 libgcc.a
-rw-r--r-- 1 root root  44900  6月  5 00:37 libgcov.a
drwxr-xr-x 3 root root   4096  6月  5 00:34 plugin
root@ubuntu:/mnt/zenglOX/gcc-build# 


    这样,适用于ubuntu 15.04(也就是gcc 4.9.2的系统环境)的完整的交叉编译工具就生成好了。

readme.txt -- Qemu 2.3.0的安装:

    为什么要使用2.3.0的版本,因为,在之前zenglOX v3.1.0版本的官方文章中,我们提到过,qemu在播放音频数据时,会严重阻塞虚拟CPU指令的执行,当qemu升级到2.3.0版本后,就不存在这个问题了。在qemu 2.3.0的版本中,即使不开启KVM硬件加速功能,也可以正常调试和开发与音频相关的驱动。

    在使用KVM硬件加速功能时,某些断点必须通过gdb的hbreak命令,也就是硬件断点才能中断下来,因此,在开发阶段,关闭KVM可以方便我们的调试(关闭KVM时,直接用软件断点就可以中断下来),在开发结束后,再启用KVM进行测试即可。

    下面看下ubuntu 15.04系统中,Qemu 2.3.0的安装过程:

    先将网盘中的qemu-2.3.0.tar.bz2的压缩包下载到Downloads目录(也可以从Qemu官网下载),然后解压:

zengl@ubuntu:~$ cd Downloads/
zengl@ubuntu:~/Downloads$ ls
qemu-2.3.0.tar.bz2
zengl@ubuntu:~/Downloads$ tar xvf qemu-2.3.0.tar.bz2
zengl@ubuntu:~/Downloads$ ls
qemu-2.3.0  qemu-2.3.0.tar.bz2
zengl@ubuntu:~/Downloads$ 


    创建qemu-build-2.3.0:

zengl@ubuntu:~/Downloads$ mkdir qemu-build-2.3.0

    进入该目录:

zengl@ubuntu:~/Downloads$ cd qemu-build-2.3.0/
zengl@ubuntu:~/Downloads/qemu-build-2.3.0$

    在进行配置之前,需要先安装一些依赖项(如果已经安装过,可以跳过):

zengl@ubuntu:~/Downloads/qemu-build-2.3.0$ sudo apt-get install libsdl1.2-dev
zengl@ubuntu:~/Downloads/qemu-build-2.3.0$ sudo apt-get install autoconf automake libtool
zengl@ubuntu:~/Downloads/qemu-build-2.3.0$ sudo apt-get install zlib1g-dev
zengl@ubuntu:~/Downloads/qemu-build-2.3.0$ sudo apt-get install libglib2.0-dev


    通过之前解压的qemu-2.3.0目录中的configure来进行配置 (配置参数与qemu 2.2.0的配置参数是一样的):

zengl@ubuntu:~/Downloads/qemu-build-2.3.0$ ../qemu-2.3.0/configure --target-list=i386-softmmu --enable-debug --disable-pie --enable-sdl --audio-drv-list='oss alsa sdl'

    接着,通过make和sudo make install命令来编译安装qemu:

zengl@ubuntu:~/Downloads/qemu-build-2.3.0$ make
zengl@ubuntu:~/Downloads/qemu-build-2.3.0$ sudo make install


    安装好后,可以用 qemu-system-i386 --version 命令来查看qemu的版本号信息:

zengl@ubuntu:~/Downloads/qemu-build-2.3.0$ qemu-system-i386 --version
QEMU emulator version 2.3.0, Copyright (c) 2003-2008 Fabrice Bellard
zengl@ubuntu:~/Downloads/qemu-build-2.3.0$ 


    GCC交叉编译工具与Qemu 2.3.0都安装好后,就可以对zenglOX的源代码进行编译测试了:

zengl@ubuntu:~/zenglOX/zenglOX_v3.2.0$ make

    在上面的make命令生成了zenglOX.bin文件后,在执行make iso之前,如果是新安装的ubuntu 15.04的系统,那么,还需要安装xorriso:
(否则会报grub-mkrescue: warning: Your xorriso doesn't support `--grub2-boot-info'.Some features are disabled. Please use xorriso 1.2.9 or later.. 之类的错误信息)

zengl@ubuntu:~/zenglOX/zenglOX_v3.2.0$ sudo apt-get install xorriso

    安装好xorriso程式后,就可以用make iso命令来生成zenglOX.iso的镜像文件了:

zengl@ubuntu:~/zenglOX/zenglOX_v3.2.0$ make iso

    最后,通过startQemu来启动Qemu:

zengl@ubuntu:~/zenglOX/zenglOX_v3.2.0$ chmod +x startQemu
zengl@ubuntu:~/zenglOX/zenglOX_v3.2.0$ ./startQemu

    Qemu启动后,会等待gdb的连接,因此,在另外一个终端中,通过gdb来连接:

zengl@ubuntu:~/zenglOX/zenglOX_v3.2.0$ gdb -q zenglOX.bin
Reading symbols from zenglOX.bin...done.
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x0000fff0 in ?? ()
(gdb) hb zlox_kernel_main 
Hardware assisted breakpoint 1 at 0x10003b: file zlox_kernel.c, line 38.
(gdb) c
Continuing.

Breakpoint 1, zlox_kernel_main (mboot_ptr=0x10000, initial_stack=524032)
    at zlox_kernel.c:38
38	{
(gdb) c
Continuing.


    上面通过 hb zlox_kernel_main 的gdb命令设置了一个硬件断点(hb是hbreak命令的简写版),因为startQemu脚本中默认是开启的kvm硬件加速的。你可以根据需要,在开发时,暂时去掉startQemu里的-enable-kvm参数。

    在startQemu里有两条qemu启动语句,最后一条被注释掉了,这两条启动语句的区别仅在于:第一条开启了kvm,而第二条注释掉的启动语句中,没有开启kvm。因此,可以根据需要,进行选择使用。

readme.txt -- Other:

    在VMware中,请不要在虚拟机的设置中加入USB Controller,因为该虚拟机的USB控制器可能会占用掉Sound Blaster 16声卡的中断线,而且,USB控制器就当前版本而言,对VMware和VirtualBox都没有什么太多的意义。如果VMware里加入了USB Controller的话,可以在虚拟机的设置界面中将其移除掉:


图4

    可以在如上所示的设置界面里选中USB Controller,再通过底部的Remove按钮将其移除掉,最后再点击虚拟机设置界面底部的OK按钮,让这一操作生效。在以后的版本中,如果加入了PCI声卡的话,并加入了APIC(Advanced Programmable Interrupt Controller 高级可编程中断控制器)的相关驱动后,应该就可以解决中断冲突的问题。

    PCI设备除了有interrupt line(中断线)外,还存在interrupt pin(中断引脚),当PCI配置空间里的interrupt pin的值为1时,interrupt line的值就对应为PIC(8259 Programmable Interrupt Controller 普通的8259可编程中断控制器)里的中断号,当interrupt pin的值不为1时,PIC就无法处理相应的中断,此时,只有APIC才能处理这种中断信号。

    之前版本在开发e1000网卡的驱动时,由于e1000网卡的PCI的interrupt pin的值,在各模拟器下都是1,因此,我们的PIC就可以正常的收到和处理e1000网卡的中断信号,在当前版本开发UHCI控制器驱动时,UHCI在qemu里也是PCI设备,并且在qemu中,UHCI的interrupt line与e1000网卡的中断线是相同的,只是e1000网卡的中断引脚为1,UHCI的中断引脚不为1。因此,UHCI控制器和相关的USB设备的中断信号就无法被PIC处理。

    当前版本采用的是poll的方式,也就是在每次的定时器的中断里,循环检测USB设备是否有相关的事件,如果有就进行处理。只有在以后的版本中,引入了APIC的驱动后,作者才会采用中断的方式来处理USB设备的各种请求信号。

    目前,将zenglOX.iso挂载到VMware与VirtualBox中后,也只能测试下除USB以外的其他功能。如果以后加入了OHCI,EHCI(USB 2.0)及xHCI(USB 3.0)之类的USB控制器的驱动的话,有可能会起到作用。

    另外,作者建议读者使用ubuntustudio-15.04-dvd的镜像来安装系统,因为标准的Ubuntu Desktop桌面版系统里,qemu的SDL的渲染效果有点模糊,而且有些渲染上的BUG,比如偶尔会出现一些残留白线的情况。在Ubuntu Studio的系统里,只要不手动调整qemu的窗口尺寸(也就是让它自行调整窗口尺寸),那么SDL就可以很清楚的将图像信息显示出来,也没有渲染上的问题。当然,如果你中间手动调整了qemu窗口的尺寸的话,也会出现图像有点模糊的情况,以及偶尔会出现的残留白线的情况。这当然只是作者的一点经验,原因尚不清楚。

    在zenglOX源码包里,新增了一个startQemu.bat的文件,这是为windows环境下的qemu进行测试用的批处理文件,作者不推荐使用windows版的qemu,因为windows下的qemu也会出现图像显示不清晰,以及偶尔会出现的残留白线的情况,另外执行性能也不如Linux中的qemu。这当然也只是作者的经验。如果读者没有这样的问题的话,那么就是作者的系统和配置问题了。

    在ubuntu studio 15.04的系统中,Gedit编辑器有点问题,也可以说是BUG,光标有时候会消失并且很难恢复过来,因此,你可以选择其他的GUI编辑器,比如:Geany编辑器。

    另外,该系统中,默认的Fcitx输入法系统也工作的不好,读者可以在语言支持中切换到iBus,切换后需要注销并重新登录。

    最后,与USB相关的驱动代码,位于 zlox_usb.c 及 zlox_usb.h 文件中。

    与UHCI控制器相关的驱动代码,位于 zlox_uhci.c 及 zlox_uhci.h 文件中。

    与USB HUB相关的驱动代码,位于 zlox_usb_hub.c 及 zlox_usb_hub.h 文件中。

    与USB键盘相关的驱动代码,位于 zlox_usb_keyboard.c 及 zlox_usb_keyboard.h 文件中。

    与USB鼠标相关的驱动代码,位于 zlox_usb_mouse.c 及 zlox_usb_mouse.h 文件中。

    USB v1.1的相关标准,可以参考网盘里的usb11_RemovePassword.pdf文件(移除了加密保护,方便复制文件的内容来进行翻译)

    UHCI控制器相关的内容,可以参考网盘里的uhci11d.pdf文件。

    USB鼠标与键盘的相关内容,可以参考网盘里的HID1_11.pdf文件。

    注意:如果你想通过U盘来启动你的hobby OS,如果是用的readme.md文件中介绍的grub-install的方式的话,请不要使用UEFI的方式来启动,可以在主板的BIOS中进行设置。UEFI的启动方式下,GRUB2设置的VBE图形界面就会失效,那就什么都看不到了。

    在作者的技嘉主板的台式机中,进入BIOS后,可以通过BIOS Features页面中的OS Type下的Boot Mode Selection来选择,我一般设置为Legacy Only,它还有两个选项,一个是UEFI and Legacy,一个是UEFI Only,当使用UEFI and legacy时,还需要在Boot Option中进行选择。因此,为了简单起见,作者就直接使用的是Legacy Only选项。

源码解析:

    以下是Qemu模拟器中USB控制器与USB设备的连接结构图:


图5

    从上图中可以看到,UHCI(Universal Host Controller Interface 通用主机控制器接口)是属于PCI总线上的设备,该设备是符合USB v1.1标准的主机控制器,在UHCI控制器内部,内嵌了一个Root Hub,该Root Hub对外提供了两个最基本的port(端口),其中第一个端口连接着USB鼠标,第二个端口则连接着一个外置的USB HUB,该HUB又有8个端口,第一个端口上连接着USB键盘,其他端口暂时没使用。

    以上是zenglOX内核在Qemu模拟器下运行时,所检测出来的USB设备的部署情况。从这个结构中可以看出来,USB设备之间存在一个分层的星型拓扑结构,在usb11_RemovePassword.pdf文档的第32页(这是PDF文档顶部分页输入框中的页数),就可以看到这个拓扑结构:
 

图6

    要对这些USB设备进行初始化的话,就需要先从拓扑结构的顶层开始初始化,也就是先对USB的主机控制器进行初始化,在Qemu模拟器中,目前我们所使用的主机控制器为UHCI控制器。

    在上面的图5中,我们提到过,UHCI是属于PCI总线上的设备,从 http://wiki.osdev.org/PCI#Class_Codes 这个链接里,可以看到一个如下所示的表格:

Class Code Subclass Prog IF Description
..................................
0x0C 0x00 0x00 IEEE 1394 Controller (FireWire)
0x10 IEEE 1394 Controller (1394 OpenHCI Spec)
0x01 0x00 ACCESS.bus
0x02 0x00 SSA
0x03 0x00 USB (Universal Host Controller Spec)
0x10 USB (Open Host Controller Spec)
0x20 USB2 Host Controller (Intel Enhanced Host Controller Interface)
0x80 USB
0xFE USB (Not Host Controller)
0x04 0x00 Fibre Channel
0x05 0x00 SMBus
0x06 0x00 InfiniBand
0x07 0x00 IPMI SMIC Interface
0x01 IPMI Kybd Controller Style Interface
0x02 IPMI Block Transfer Interface
0x08 0x00 SERCOS Interface Standard (IEC 61491)
0x09 0x00 CANbus
..................................
                                                表格1

    从上面的表格中可以看到,当PCI设备的配置空间里的Class Code为0x0C,Subclass为0x03时,说明该设备可能是USB主机控制器,具体是哪类控制器,则是由Prog IF来决定的。当Prog IF的值为0x00时,就说明该设备是UHCI控制器。当该值为0x10时,该设备就是OHCI控制器。当该值为0x20时,该设备就属于EHCI控制器。至于这几个USB控制器的区别,以及它们所使用的USB标准,读者可以参考 http://wiki.osdev.org/Universal_Serial_Bus 该链接对应的维基百科文章。

    在zenglOX源码根目录下的zlox_uhci.c的文件里,就有一个zlox_uhci_detect函数,该函数就是利用上面表格中的数据,来检测某个PCI设备是否是UHCI控制器的:

ZLOX_BOOL zlox_uhci_detect(ZLOX_PCI_CONF_HDR * cfg_hdr)
{
	if(cfg_hdr == ZLOX_NULL)
		return ZLOX_FALSE;

	ZLOX_UINT8 class_code = cfg_hdr->class_code;
	ZLOX_UINT8 sub_class = cfg_hdr->sub_class;
	ZLOX_UINT8 prog_if = cfg_hdr->prog_if;
	if(class_code == ZLOX_UHCI_CLASS && 
	   sub_class == ZLOX_UHCI_SUB_CLASS && 
	   prog_if == ZLOX_UHCI_PROG_IF)
	{
		return ZLOX_TRUE;
	}
	return ZLOX_FALSE;
}


    上面函数中的 ZLOX_UHCI_CLASSZLOX_UHCI_SUB_CLASSZLOX_UHCI_PROG_IF 都是定义在zlox_uhci.h头文件里的宏,它们的值分别为 0xc,0x3 及 0x0 ,当检测到PCI设备属于UHCI控制器时,该函数就会返回ZLOX_TRUE,否则返回ZLOX_FALSE 。

    在zlox_usb.c文件的zlox_usb_init函数中,就会调用上面这个zlox_uhci_detect函数来循环对每个PCI设备进行检测:

ZLOX_SINT32 zlox_usb_init()
{
	ZLOX_PCI_DEVCONF_LST * pciconf_lst = zlox_pci_get_devconf_lst_for_kernel();
	if(pciconf_lst->ptr == ZLOX_NULL)
	{
		return -1;
	}
	for(ZLOX_UINT32 i = 0; i < pciconf_lst->count ;i++)
	{
		ZLOX_PCI_CONF_HDR * cfg_hdr = &(pciconf_lst->ptr[i].cfg_hdr);
		if(zlox_uhci_detect(cfg_hdr))
		{
			ZLOX_USB_HCD * usb_hcd = zlox_usb_hcd_alloc(pciconf_lst, i);
			if(usb_hcd == ZLOX_NULL)
			{
				continue;
			}
			ZLOX_VOID * hcd = zlox_uhci_init(usb_hcd);
			if(hcd == ZLOX_NULL)
			{
				zlox_kfree(usb_hcd);
				continue;
			}
			usb_hcd->hcd_priv = hcd;
			usb_hcd->type = ZLOX_USB_HCD_TYPE_UHCI;
			zlox_usb_hcd_lst_append(usb_hcd);
			zlox_usb_print_devinfo(usb_hcd);
		}
	}
	return 0;
}


    上面函数在检测到一个UHCI类型的USB控制器后,就会先创建一个ZLOX_USB_HCD的结构体,每个USB控制器都会分配到一个ZLOX_USB_HCD的结构,该结构定义在zlox_usb.h的头文件中:

typedef enum _ZLOX_USB_HCD_TYPE
{
	ZLOX_USB_HCD_TYPE_NONE,
	ZLOX_USB_HCD_TYPE_UHCI,
	ZLOX_USB_HCD_TYPE_OHCI,
	ZLOX_USB_HCD_TYPE_EHCI,
	ZLOX_USB_HCD_TYPE_XHCI,
}ZLOX_USB_HCD_TYPE;

typedef struct _ZLOX_USB_HCD ZLOX_USB_HCD;

struct _ZLOX_USB_HCD
{
	ZLOX_PCI_DEVCONF_LST * pciconf_lst;
	ZLOX_UINT32 pciconf_lst_idx;
	ZLOX_USB_HCD * next;
	ZLOX_USB_HCD_TYPE type;
	ZLOX_UINT32 irq; /*PCI irq*/
	ZLOX_UINT32 irq_pin; /*PCI interrupt pin*/
	ZLOX_VOID * hcd_priv;
	ZLOX_VOID (*poll)(struct _ZLOX_USB_HCD * usb_hcd);
};


    在该结构里,定义了一些USB控制器的通用属性。

    其中第1个pciconf_lst字段与第2个pciconf_lst_idx字段的值配合起来,就可以获取到当前USB控制器的PCI配置空间中的数据。

    通过第3个next字段,就可以将所有创建的ZLOX_USB_HCD结构都连在一起,以构成一个链表结构,方便进行poll轮询操作。

    第4个type字段用于判断USB控制器的类型,有UHCI,OHCI,EHCI及xHCI这四种类型,分别对应ZLOX_USB_HCD_TYPE_UHCI,ZLOX_USB_HCD_TYPE_OHCI,ZLOX_USB_HCD_TYPE_EHCI及ZLOX_USB_HCD_TYPE_XHCI这4个枚举值。

    第5个irq字段用于存储USB控制器的PCI配置空间里的中断号。

    第6个irq_pin字段用于存储USB控制器的PCI配置空间里的Interrupt pin(中断引脚)值。

    第7个hcd_priv字段则用于存储与控制器类型相对应的结构体指针。

    最后一个poll字段存储的是函数指针,不同的控制器类型的poll函数是不同的,poll函数用于检测该控制器所管理的USB设备是否有事件需要处理,如果有就及时处理掉。例如,USB鼠标的输入事件,以及USB键盘的输入事件等。

    上面的zlox_usb_init函数,在创建了一个ZLOX_USB_HCD的结构体后,接着会调用zlox_uhci_init函数来对UHCI控制器进行具体的初始化操作,并返回一个与UHCI控制器类型相关的结构体指针,作为ZLOX_USB_HCD结构体的hcd_priv字段的值。zlox_uhci_init函数定义在zlox_uhci.c文件里:

ZLOX_VOID * zlox_uhci_init(ZLOX_USB_HCD * usb_hcd)
{
	if(usb_hcd == ZLOX_NULL)
		return ZLOX_NULL;

	ZLOX_USB_GET_EXT_INFO ext_info;
	zlox_usb_get_ext_info(usb_hcd, &ext_info);

	ZLOX_UHCI_HCD * uhci_hcd = (ZLOX_UHCI_HCD *)zlox_kmalloc(sizeof(ZLOX_UHCI_HCD));
	if(uhci_hcd == ZLOX_NULL)
		return ZLOX_NULL;

	zlox_pci_set_master(ext_info.cfg_addr, ZLOX_TRUE);

	zlox_memset((ZLOX_UINT8 *)uhci_hcd, 0, sizeof(ZLOX_UHCI_HCD));
	uhci_hcd->io_addr = ext_info.cfg_hdr->bars[ZLOX_UHCI_BAR_IDX] & 
					ZLOX_PCI_BASE_ADDRESS_IO_MASK;
	uhci_hcd->irq = usb_hcd->irq = (ZLOX_UINT32)ext_info.cfg_hdr->int_line;
	uhci_hcd->irq_pin = usb_hcd->irq_pin = (ZLOX_UINT32)ext_info.cfg_hdr->int_pin;
	uhci_hcd->io_len = zlox_pci_get_bar_size(ext_info.cfg_addr, 
					ZLOX_UHCI_BAR_IDX) + 1;
	uhci_hcd->rh_numports = zlox_uhci_count_ports(uhci_hcd);
	uhci_hcd->usb_hcd = (ZLOX_VOID *)usb_hcd;

	uhci_hcd->frameList = (ZLOX_UINT32 *)zlox_kmalloc_ap(1024 * sizeof(ZLOX_UINT32), 
					&uhci_hcd->frameListPhys);
	uhci_hcd->qhPool = (ZLOX_UHCI_QH *)zlox_kmalloc_ap(sizeof(ZLOX_UHCI_QH) * 
					ZLOX_UHCI_MAX_QH, 
					&uhci_hcd->qhPoolPhys);
	uhci_hcd->tdPool = (ZLOX_UHCI_TD *)zlox_kmalloc_ap(sizeof(ZLOX_UHCI_TD) * 
					ZLOX_UHCI_MAX_TD, 
					&uhci_hcd->tdPoolPhys);
	zlox_memset((ZLOX_UINT8 *)uhci_hcd->qhPool, 0, sizeof(ZLOX_UHCI_QH) * 
					ZLOX_UHCI_MAX_QH);
	zlox_memset((ZLOX_UINT8 *)uhci_hcd->tdPool, 0, sizeof(ZLOX_UHCI_TD) * 
					ZLOX_UHCI_MAX_TD);

	// Frame list setup
	ZLOX_UHCI_QH * qh = zlox_uhci_alloc_qh(uhci_hcd);
	// 在4K大小的页范围内,都可以直接通过线性地址之间的偏移值来确定物理地址
	ZLOX_UINT32 qh_offset = (ZLOX_UINT32)qh - (ZLOX_UINT32)uhci_hcd->qhPool;
	ZLOX_UINT32 qh_phys = uhci_hcd->qhPoolPhys + qh_offset;
	qh->head = ZLOX_UHCI_TD_PTR_TERMINATE;
	qh->element = ZLOX_UHCI_TD_PTR_TERMINATE;
	qh->transfer = 0;
	qh->qhLink.prev = &qh->qhLink;
	qh->qhLink.next = &qh->qhLink;

	uhci_hcd->asyncQH = qh;
	uhci_hcd->asyncQH_Phys = qh_phys;
	for (ZLOX_UINT32 i = 0; i < 1024; ++i)
	{
		uhci_hcd->frameList[i] = ZLOX_UHCI_TD_PTR_QH | qh_phys;
	}

	// Turn off PIRQ enable and SMI enable.  (This also turns off the
	// BIOS's USB Legacy Support.)  Turn off all the R/WC bits too.
	// uhci Revision 1.1 spec --- 5.2.1 LEGSUP -- LEGACY SUPPORT REGISTER 
	// at page 39 (PDF top page is 45)
	zlox_pci_reg_outw(ext_info.cfg_addr, ZLOX_UHCI_PCI_REG_LEGSUP,
					ZLOX_UHCI_PCI_LEGSUP_RWC);

	// Disable interrupts
	// uhci Revision 1.1 spec --- 2.1.3 USBINTR -- USB INTERRUPT ENABLE REGISTER
	// at page 14 (PDF top page is 20)
	zlox_outw(uhci_hcd->io_addr + ZLOX_UHCI_REG_INTR, 0);

	// Assign frame list
	// uhci Revision 1.1 spec --- 2.1.4 FRNUM -- FRAME NUMBER REGISTER
	// at page 14 (PDF top page is 20)
	zlox_outw(uhci_hcd->io_addr + ZLOX_UHCI_REG_FRNUM, 0);
	// uhci Revision 1.1 spec --- 2.1.5 FLBASEADD -- FRAME LIST BASE ADDRESS REGISTER
	// at page 15 (PDF top page is 21)
	zlox_outl(uhci_hcd->io_addr + ZLOX_UHCI_REG_FRBASEADD, 
			uhci_hcd->frameListPhys);
	// uhci Revision 1.1 spec --- 2.1.6 START OF FRAME (SOF) MODIFY REGISTER
	// at page 15 (PDF top page is 21)
	zlox_outb(uhci_hcd->io_addr + ZLOX_UHCI_REG_SOFMOD, 0x40);

	// Clear status
	// uhci Revision 1.1 spec --- 2.1.2 USBSTS -- USB STATUS REGISTER
	// at page 13 (PDF top page is 19)
	zlox_outw(uhci_hcd->io_addr + ZLOX_UHCI_REG_STS, 0xffff);

	// Enable controller
	// uhci Revision 1.1 spec --- 2.1.1 USBCMD -- USB COMMAND REGISTER
	// at page 11 (PDF top page is 17)
	zlox_outw(uhci_hcd->io_addr + ZLOX_UHCI_REG_CMD, ZLOX_UHCI_CMD_RS);

	// Probe devices
	zlox_uhci_probe(uhci_hcd);

	usb_hcd->poll = zlox_uhci_poll;
	return (ZLOX_VOID *)uhci_hcd;
}


    要理解这段代码的含义,首先必须理解UHCI控制器的工作原理,该控制器的工作原理,已经在uhci11d.pdf文档中写的很详细了。在上面的代码注释里,作者也将这些代码在PDF文档中的对应内容的页数也给出来了。例如:uhci Revision 1.1 spec --- 2.1.2 USBSTS -- USB STATUS REGISTER at page 13 (PDF top page is 19),指的是USB状态寄存器的内容位于uhci11d.pdf文档的第13页,这只是页面底部的页数,PDF顶部分页输入框中的页数则为19 。

    从上面的函数中可以看到,与UHCI控制器类型相关的结构体为ZLOX_UHCI_HCD,该结构体定义在zlox_uhci.h的头文件里:

/*
 * The full UHCI controller information:
 */
typedef struct _ZLOX_UHCI_HCD {
	ZLOX_UINT32 io_addr; /* Grabbed from PCI */
	ZLOX_UINT32 io_len;
	ZLOX_UINT32 irq; /*PCI irq*/
	ZLOX_UINT32 irq_pin; /*PCI interrupt pin*/
	ZLOX_SINT32 rh_numports; /* Number of root-hub ports */
	ZLOX_UINT32 * frameList;
	ZLOX_UINT32 frameListPhys; // Physical Address of frameList
	ZLOX_UHCI_QH * qhPool;
	ZLOX_UINT32 qhPoolPhys; // Physical Address of qhPool
	ZLOX_UHCI_TD * tdPool;
	ZLOX_UINT32 tdPoolPhys; // Physical Address of tdPool
	ZLOX_UHCI_QH * asyncQH;
	ZLOX_UINT32 asyncQH_Phys; // Physical Address of asyncQH
	ZLOX_VOID * usb_hcd;
}ZLOX_UHCI_HCD;


    上面结构中的第一个io_addr字段用于存储UHCI控制器里的寄存器的I/O基地址,在uhci11d.pdf文档的第16页(这是PDF顶部分页输入框中的页数),有两个表格,其中Table 3里就指出了PCI配置空间里的0x20到0x23的区域(也就是BAR4部分),存储了这个I/O基地址。在Table 2中可以看到UHCI控制器里各寄存器的I/O地址的偏移值,以及这些寄存器的助记符与相关描述信息。

    至于ZLOX_UHCI_HCD结构体中的frameList,qhPool及tdPool字段,这些都是与UHCI控制器的调度过程相关的。由于USB设备之间是通过星型拓扑结构连接在一起的,一个USB控制器可能需要同时管理很多个USB设备,那么控制器在与这些设备进行数据传输时,就需要一个调度过程。UHCI控制器的调度过程如下图所示(该图在uhci11d.pdf文档的顶部分页输入框中的页数为33页):


图7

    上图中的TD是Transfer Descriptor(传输描述符)的缩写,QH则是Queue Head(传输队列头部)的缩写。上图的Frame List中包含了很多Frame Pointer,当UHCI控制器运行起来后,它会从Frame List中的Frame Pointer里获取到TD或QH的物理地址,具体要传输的数据的物理地址是存储在TD中的,一个TD里还包含了下一次要传输的TD或QH的物理地址,QH则是一个传输队列,它里面存储的是要传输的TD的物理地址,以及当前QH队列里的所有TD都传输完后,下一个要传输的QH或TD的物理地址。

    这样,当Frame Pointer所指向的TD的数据传输完后,就会转到下一个TD或QH去继续执行传输操作。每个Frame Pointer的执行周期大概是1ms,当执行周期结束时,UHCI控制器就会转到下一个Frame Pointer去执行传输操作。当最后一个Frame Pointer执行完后,又回到第一个Frame Pointer继续执行。在zenglOX里,所有的Frame Pointer都指向相同的QH,这样,1ms的执行周期到了后,下一个Frame Pointer还是会继续传输没传完的队列数据。

    之所以要在QH中设置多个TD,是因为每个TD可以传输的数据的字节数是有限制的,另外,每个TD里还存储了要传输的USB设备的地址信息,这样,不同的TD就可以对不同的USB设备进行数据传输了。在usb11_RemovePassword.pdf文档的第53页(这是顶部分页输入框中的页数),可以看到控制类的传输,每个TD可传输的数据的最大尺寸为8,16,32或64字节。

    Frame Pointer的二进制位结构如下图所示(该图在uhci11d.pdf文档的顶部分页输入框中的页数为26页):
 

图8

    其中位4到位31部分存储了TD或QH的物理地址,位2和位3是保留位,位1用于判断当前指向的是TD还是QH,当位1为1时,就表示指向的是QH,为0则表示指向的是TD。位0表示当前Frame Pointer是否指向了一个有效的TD或QH,当为1时,表示当前Frame Pointer没有指向任何QH或TD。

    TD的二进制位结构如下图所示(该图在uhci11d.pdf文档的顶部分页输入框中的页数为27页):
 

图9

    上图中的00-03h部分,也就是第一个4字节部分的Link Pointer字段用于存储下一次要传输的TD或QH的物理地址,当该部分的位1为1时,表示下一次要传输的是QH,否则就是TD 。

    04-07h部分,是可以由UHCI控制器读取和写入的部分,控制器可以向该部分写入数据,来表示传输是否成功,以及实际传输了多少个字节的数据等。

    08-0Bh部分的MaxLen字段表示需要传输多少个字节的数据,EndPt字段表示需要传输的设备的端点号,每个USB设备可以有多个端点,其中EndPt为0的端点是所有USB设备都必须支持的,该端点是用于配置USB设备的,至于其他的端点,则可以用于其他的用途。Device Address字段用于设置要传输的USB设备的地址值,在最开始进行配置时,可以使用地址0来访问第一个未被设置地址的USB设备。PID字段的值可以是0x2D,表示当前属于SETUP包,也可以是0x69,表示当前属于设备到主机的IN(输入)数据包,还可以是0xE1,表示当前属于主机到设备的OUT(输出)数据包。在usb11_RemovePassword.pdf文档的第180页到第181页(这是顶部分页输入框中的页数),可以看到Control Transfers(进行控制传输)时,SETUP,IN及OUT包之间的关系。

    此外,08-0Bh部分的R是Reserved保留位。而D则是Data Toggle位。Data Toggle是用于同步传输和重传的,读者可以在usb11_RemovePassword.pdf文档的第184页的8.6 Data Toggle Synchronization and Retry节查看到相关信息(这里的页数也是分页输入框中的页数)。

    0C-0Fh部分的Buffer Pointer字段,用于指向实际需要传输的数据的物理地址,或者当接收数据时,接收数据的物理内存地址。

    其他没介绍到的字段,请参考uhci11d.pdf文档的第27页到第31页的表格中的内容(这里的页数都是PDF顶部分页输入框中的页数)。

    根据上面图9所显示的TD结构,就有了zlox_uhci.h头文件中ZLOX_UHCI_TD的定义:

// Transfer Descriptor
// uhci Revision 1.1 spec --- 3.2 Transfer Descriptor (TD)
// at page 20 (PDF top page is 26)
typedef struct _ZLOX_UHCI_TD
{
    volatile ZLOX_UINT32 link;
    volatile ZLOX_UINT32 cs;
    volatile ZLOX_UINT32 token;
    volatile ZLOX_UINT32 buffer;

    // internal fields
    ZLOX_UINT32 tdNext;
    ZLOX_UINT8 active;
    ZLOX_UINT8 pad[11];
} ZLOX_PACKED ZLOX_UHCI_TD;


    上面结构体中的tdNext,active及pad字段只是内核所使用的方便操作的内部结构,并非PDF文档中定义过的标准。

    QH的二进制位结构如下图所示(该图在uhci11d.pdf文档的顶部分页输入框中的页数为31页):


图10

    00-03h部分的位4到位31,用于存储当前QH执行完后,下一个需要执行的QH或TD的物理内存地址。当该部分的位1为1时,则说明下一次要传输的是QH,否则就是TD。另外,当该部分的位0为1时,说明当前QH是调度过程中的最后一个QH,即不存在下一个需要执行的QH或TD。

    04-07h部分是UHCI控制器可以读取和写入的部分,在QH执行之前,我们可以先将QH队列中的第一个TD的物理地址写入到该部分的位4到位31(也就是图中的Queue Element Link Pointer),UHCI控制器会从Queue Element Link Pointer中读取出第一个TD的地址并执行,控制器还会将当前正在执行的TD的物理地址写入到此处。通过Queue Element Link Pointer里的值就可以检测出当前UHCI控制器正在执行哪个TD,以及QH是否执行完毕了,当QH中所有TD都执行完毕时,Queue Element Link Pointer里的值将会是0 。

    根据上面图10所显示的QH结构,就有了zlox_uhci.h头文件中ZLOX_UHCI_QH的定义:

// uhci Revision 1.1 spec --- 3.3 Queue Head (QH)
// at page 25 (PDF top page is 31)
typedef struct _ZLOX_UHCI_QH
{
    volatile ZLOX_UINT32 head;
    volatile ZLOX_UINT32 element;

    // internal fields
    ZLOX_USB_TRANSFER * transfer;
    ZLOX_USB_LINK qhLink;
    ZLOX_UINT32 tdHead;
    ZLOX_UINT32 active;
    ZLOX_UINT8 pad[4];
} ZLOX_PACKED ZLOX_UHCI_QH;


    上面除了开头的head与element是PDF文档中定义过的标准字段外,其他几个字段都是内核所使用的internal fields(内部字段)。

    通过上面介绍的Frame List,TD及QH,我们就可以在主机与USB设备之间进行数据传输了。至于数据传输的具体格式,则是由USB v1.1的标准来规定的。例如,在zlox_usb.h的头文件中,定义了一个如下所示的结构:

// USB Device Descriptor
// USB Revision 1.1 spec --- 9.6.1 Device
// at page 196 (PDF top page is 212)
typedef struct  _ZLOX_USB_DEVICE_DESC
{
	ZLOX_UINT8 len;
	ZLOX_UINT8 type;
	ZLOX_UINT16 usbVer;
	ZLOX_UINT8 devClass;
	ZLOX_UINT8 devSubClass;
	ZLOX_UINT8 devProtocol;
	ZLOX_UINT8 maxPacketSize;
	ZLOX_UINT16 vendorId;
	ZLOX_UINT16 productId;
	ZLOX_UINT16 deviceVer;
	ZLOX_UINT8 vendorStr;
	ZLOX_UINT8 productStr;
	ZLOX_UINT8 serialStr;
	ZLOX_UINT8 confCount;
} ZLOX_PACKED ZLOX_USB_DEVICE_DESC;


    从上面注释中可以看到,该结构体是按照usb11_RemovePassword.pdf文档第196页(对应的PDF顶部分页输入框中的页数为212)的9.6.1 Device节的内容来定义的。USB设备是通过Descriptor描述符来反馈自己的属性数据的。上面这个结构体是一个标准的设备描述符。通过向USB设备发送相应的请求,就可以获取到这个描述符。在zlox_usb.c文件的zlox_usb_dev_init函数中,就有获取设备描述符的相关代码:

ZLOX_BOOL zlox_usb_dev_init(ZLOX_USB_DEVICE * dev)
{
	ZLOX_BOOL ret = ZLOX_FALSE;
	ZLOX_USB_DEVICE_DESC devDesc;
	ZLOX_USB_DEVICE_DESC * devDesc_ptr;
	ZLOX_UINT16 * langs = ZLOX_NULL;
	ZLOX_UINT8 * configBuf = ZLOX_NULL;
	devDesc_ptr = &devDesc;
	if(!zlox_usb_detect_phys_continuous((ZLOX_UINT32)devDesc_ptr, 
			sizeof(ZLOX_USB_DEVICE_DESC)))
	{
		devDesc_ptr = zlox_usb_get_cont_phys(sizeof(ZLOX_USB_DEVICE_DESC));
		if(devDesc_ptr == ZLOX_NULL)
			return ZLOX_FALSE;
	}

	// USB Revision 1.1 spec --- Table 9-3. Standard Device Requests
	// at page 186 (PDF top page is 202)
	// the wValue include <Descriptor Type> and <Descriptor Index>
	// so the follow is (ZLOX_USB_DESC_TYPE_DEVICE << 8) | 0
	// Get first 8 bytes of device descriptor
	if (!zlox_usb_dev_request(dev,
		ZLOX_USB_RT_DEV_TO_HOST | ZLOX_USB_RT_STANDARD | ZLOX_USB_RT_DEV,
		ZLOX_USB_REQ_GET_DESC, (ZLOX_USB_DESC_TYPE_DEVICE << 8) | 0, 0,
		8, devDesc_ptr))
	{
		goto failed;
	}
	.....................................
}


    由于接收数据的内存空间必须在物理地址上是连续的,因此,在发送请求之前,需要先调用zlox_usb_detect_phys_continuous函数进行检测,如果不连续,则通过zlox_usb_get_cont_phys函数来尝试获取一段连续的物理地址空间,并返回对应的线性地址。接着,通过zlox_usb_dev_request函数来发送请求,该请求是根据usb11_RemovePassword.pdf文档第186页(对应的PDF顶部分页输入框中的页数为202)的内容来生成的。

    zlox_usb_dev_request函数也定义在zlox_usb.c文件中:

ZLOX_BOOL zlox_usb_dev_request(ZLOX_USB_DEVICE * dev,
    ZLOX_UINT32 type, ZLOX_UINT32 request,
    ZLOX_UINT32 value, ZLOX_UINT32 index,
    ZLOX_UINT32 len, ZLOX_VOID * data)
{
	if(!(data == ZLOX_NULL && len == 0))
	{
		if(!zlox_usb_detect_phys_continuous((ZLOX_UINT32)data, len))
		{
			return ZLOX_FALSE;
		}
	}
	ZLOX_USB_DEV_REQ req;
	ZLOX_USB_DEV_REQ * req_ptr = ZLOX_NULL;
	req_ptr = &req;
	if(!zlox_usb_detect_phys_continuous((ZLOX_UINT32)req_ptr, 
			sizeof(ZLOX_USB_DEV_REQ)))
	{
		req_ptr = zlox_usb_get_cont_phys(sizeof(ZLOX_USB_DEV_REQ));
		if(req_ptr == ZLOX_NULL)
			return ZLOX_FALSE;
	}

	req_ptr->type = type;
	req_ptr->req = request;
	req_ptr->value = value;
	req_ptr->index = index;
	req_ptr->len = len;

	..............................
}


    上面函数里,最主要的就是ZLOX_USB_DEV_REQ结构体,该结构体定义在zlox_usb.h的头文件中:

// USB Device Request
// usb Revision 1.1 spec --- 9.3 USB Device Requests
// at page 183 (PDF top page is 199)
typedef struct _ZLOX_USB_DEV_REQ
{
	ZLOX_UINT8 type;
	ZLOX_UINT8 req;
	ZLOX_UINT16 value;
	ZLOX_UINT16 index;
	ZLOX_UINT16 len;
} ZLOX_PACKED ZLOX_USB_DEV_REQ;


    从注释中可以看到,该结构是根据usb11_RemovePassword.pdf文档第183页(对应的PDF顶部分页输入框中的页数为199)的内容来定义的。从PDF这一页的内容中可以看到,通过在SETUP包中发送该数据结构,USB设备就会根据该数据结构里的请求类型,返回所需的数据给主机。

    由于篇幅的限制,作者不可能将USB标准和相关代码。在这里都描述一遍。读者可以结合注释在PDF文档中找到对应的参考信息。

    和USB HUB相关的内容可以参考usb11_RemovePassword.pdf文档的第11章(PDF顶部分页输入框中的页数为245),不过USB HUB这一块的代码,也就是zlox_usb_hub.c文件里的代码,并没有PDF文档中介绍的那么复杂,在实际的代码中,其实就是先获取HUB的描述符,再根据描述符里的端口数,对每个端口上连接的USB设备进行初始化。

    至于USB鼠标和USB键盘的驱动代码,则是作者根据设备返回数据的实际情况,在之前介绍的osdev项目的基础上,调整出来的。

    在zlox_usb_keyboard.c文件的zlox_usb_kbd_process函数中,有一段注释掉的代码:

static ZLOX_VOID zlox_usb_kbd_process(ZLOX_USB_KBD * kbd)
{
	ZLOX_UINT8 * data = kbd->data;
	ZLOX_SINT32 usage_num = 0;
	ZLOX_SINT32 usage_cur = -1;
	ZLOX_BOOL status_control = ZLOX_FALSE;
	ZLOX_BOOL ctrl_press = ZLOX_FALSE;
	ZLOX_BOOL ctrl_release = ZLOX_FALSE;

	// debug print
	/*for (ZLOX_UINT32 i = 0; i < 8; ++i)
	{
		zlox_monitor_write_hex(data[i]);
		zlox_monitor_write(" ");
	}
	zlox_monitor_write("\n");*/

	...............................................
}


    在进行调试开发时,可以将这段注释暂时取消掉,它会显示出按键的实际数据情况:


图11

    每次按下键或释放按键时,内核都会从USB键盘中获取到8个字节的数据。可以在HID1_11.pdf文档的第70页(这是PDF顶部分页输入框中的页数),看到这些字节的含义:

Byte Description
0 Modifier keys
1 Reserved
2 Keycode 1
3 Keycode 2
4 Keycode 3
5 Keycode 4
6 Keycode 5
7 Keycode 6

    这8个字节的数据会如实的反应出当前键盘上有哪些键被按下了。例如,上面图11里的第一条0x0 0x0 0x4 0x0 0x0 0x0 0x0 0x0数据中的第三个字节为0x4,0x4是按键A的编码,说明当前按下了A键。第二条0x0 0x0 0x4 0x5 0x0 0x0 0x0 0x0数据中,第三个字节为0x4,第四个字节为0x5,0x5为按键B的编码,说明当前同时按下了A和B键,以此类推。

    此外,第一个Modifier keys字节是表示当前是否按下了ctrl,shift或alt键的,可以在HID1_11.pdf文档的第66页(这是PDF顶部分页输入框中的页数),看到该字节中各二进制位的含义:

Bit Key
0 LEFT CTRL
1 LEFT SHIFT
2 LEFT ALT
3 LEFT GUI
4 RIGHT CTRL
5 RIGHT SHIFT
6 RIGHT ALT
7 RIGHT GUI

    因此,上面图11中,当第一个字节为0x4时,表示按下了LEFT ALT即左Alt键。当为0x5时,表示同时按下了LEFT ALT与LEFT CTRL键。当为0x7时,表示同时按下了LEFT ALT,LEFT SHIFT与LEFT CTRL键,以此类推。

    从USB键盘获取的8个字节中的第二个字节是Reserved(保留的),默认为0值。

    当收到的按键数据中,编码数在前一条的基础上增加了时,说明按下了一个新的按键。当编码数减少时,说明释放了某个按键。在新增按键时,zenglOX只会将新增的按键作为消息发送出去,例如上面图11中,在收到第二条数据时,只会将新增的0x5即按键B的ASCII码发送出去。

    另外,无论是否按下Num Lock键,都可以正常使用小键盘(只针对Qemu模拟器的USB键盘而言),因为,作者认为Num Lock没有什么太多的意义,而且加上Num Lock的处理代码,还可能会出现需要按两次Num Lock才能正常使用小键盘的情况,因此,作者就去掉了Num Lock的处理代码,让小键盘始终可用。当然,这只针对Qemu模拟器的USB键盘而言。对于PS/2键盘,还是需要按Num Lock键,才能让小键盘正常使用。

    USB鼠标的数据处理则相对简单一些,在zlox_usb_mouse.c文件的zlox_usb_mouse_process函数中,可以看到相关的处理代码:

static ZLOX_VOID zlox_usb_mouse_process(ZLOX_USB_MOUSE * mouse)
{
	ZLOX_UINT8 * data = mouse->data;

	/*zlox_monitor_write("mouse in: ");
	zlox_monitor_write_hex(data[0]);
	zlox_monitor_write(" dx=");
	zlox_monitor_write_dec((ZLOX_SINT8)data[1]);
	zlox_monitor_write(" dy=");
	zlox_monitor_write_dec((ZLOX_SINT8)data[2]);
	zlox_monitor_write("\n");*/

	ZLOX_TASK_MSG msg = {0};
	msg.type = ZLOX_MT_MOUSE;
	msg.mouse.state = data[0];
	msg.mouse.rel_x = (ZLOX_SINT32)((ZLOX_SINT8)data[1]);
	msg.mouse.rel_y = (ZLOX_SINT32)((ZLOX_SINT8)data[2]);
	msg.mouse.rel_y = -(msg.mouse.rel_y);

	if(mouse_scale_factor > 1.0)
	{
		if(msg.mouse.rel_x > 10 || msg.mouse.rel_x < -10)
			msg.mouse.rel_x = msg.mouse.rel_x * mouse_scale_factor;
		if(msg.mouse.rel_y > 10 || msg.mouse.rel_y < -10)
			msg.mouse.rel_y = msg.mouse.rel_y * mouse_scale_factor;
	}

	if(mywin_list_header != ZLOX_NULL)
	{
		zlox_update_for_mymouse(&msg);
	}
}


    从上面的代码中,可以看到,从USB鼠标设备获取到的数据中,第一个data[0]字节表示鼠标的按键状态,即按下了左键,还是右键等,第二个data[1]字节表示鼠标在水平方向的位移值,第三个data[2]字节则表示鼠标在垂直方向上的位移值。读者可以在HID1_11.pdf文档的第71页(这是PDF文档顶部分页输入框中的页数),查看到这些字节的含义:

Byte Bits Description
0 0 Button 1
1 Button 2
2 Button 3
4 to 7 Device-specific
1 0 to 7 X displacement
2 0 to 7 Y displacement
3 to n 0 to 7 Device specific (optional)

    以上就是和USB相关的部分代码的简要介绍,至于zenglOX在当前版本中新增的其他代码,请通过gdb调试器进行分析。

文章中的相关链接:

    https://github.com/pdoane/osdev   该链接为pdoane的osdev项目的github地址。

    ftp://ftp.gnu.org/gnu/binutils/   从该链接中可以看到binutils各版本的下载地址。

    ftp://ftp.gnu.org/gnu/binutils/binutils-2.25.tar.bz2   binutils-2.25版本的完整ftp地址

    ftp://ftp.gnu.org/gnu/gcc/gcc-4.9.2/gcc-4.9.2.tar.bz2   gcc-4.9.2的ftp下载地址

    ftp://ftp.gnu.org/gnu/gmp/gmp-6.0.0a.tar.bz2   gmp-6.0.0a.tar.bz2的ftp下载地址

    ftp://ftp.gnu.org/gnu/mpc/mpc-1.0.3.tar.gz   mpc-1.0.3.tar.gz的ftp下载地址

    ftp://ftp.gnu.org/gnu/mpfr/mpfr-3.1.2.tar.bz2   mpfr-3.1.2.tar.bz2的ftp下载地址

    ftp://ftp.gnu.org/gnu/   gcc及其他GNU工具的FTP下载站点

    http://wiki.osdev.org/PCI#Class_Codes   该链接中介绍了USB控制器的PCI配置空间里的class,subclass及prog_if的特征值。

    http://wiki.osdev.org/Universal_Serial_Bus   该链接对应的维基文章,对USB的各类控制器及USB的基本工作原理,作了比较详细的介绍。

    如果文章中,有链接地址无法直接访问的话,请使用代理访问。另外,作者建议读者使用上面的ftp地址来下载各GNU工具,因为,这些ftp地址不需要使用代理就可以直接下载,而且还可以在这些FTP站点中下载到其他版本的代码。

    OK,就到这里,休息,休息一下 o(∩_∩)o~~

    你不知道你,所以你是你;你知道了你,你就不是你了。

—— 天道
 
上下篇

下一篇: 暂无

上一篇: zenglOX v3.1.0 Sound Blaster 16

相关文章

zenglOX v0.0.5 分页

zenglOX v1.1.0 通过ATAPI读取光盘里的数据

zenglOX v0.0.10 Keyboard(获取键盘的输入)

zenglOX v0.0.6 使用Heap(堆)动态分配和释放内存

zenglOX v2.3.0 移植zengl嵌入式脚本语言

zenglOX v0.0.3 初始化GDT和IDT