页面导航:
项目下载地址:
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_CLASS,
ZLOX_UHCI_SUB_CLASS 及
ZLOX_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~~
你不知道你,所以你是你;你知道了你,你就不是你了。
—— 天道