Akaban操作系统(4)-----中断控制器的初始化,来自UHCI的中断
作者:互联网
相信你一定读了我的上一篇文章(总之应该只有IO APIC才能接受来自PCI的中断),没错,只要这次将中断驱动写出来就可以正式开始PCI的开发了!
APIC相对于8259A中断处理器那性能可谓是差了十万八千里,先不对比实际性能,先说说它们诞生的年代
8259A:
是为8085A和8086/8088这类16位,寻址能力只有20位(1MB左右)的处理器设计的,其中我所能查到的8086处理器(最大主频只有8MB的屑CPU),出场的年代也到达了1978年,这,啊,我知道了,改革开放的时候!显然,在运行速度已经快到惊人的现在几乎早已退出了世界舞台(当然大部分CPU和主板为了兼容古老的操作系统硬着头皮把它保留了下来)
再看看PCI的诞生时间1991年,由intel提出的
所以显然,要么8259a完全不兼容PCI,要么就要使用复杂的中断路由机制才能被处理器接收到,就算能,也会在中断大量爆发时使中断的传输性能呈指数级下降
所以,顺应时代潮流APIC(Advanced Programmable Interrupt Controller)诞生了!看,这名字听起来就很高级,就比pic多了个A就变得高级了不少,接下来在看看APIC的诞生年代:
自 1994 年的 Pentium P54c 开始Intel 已经将本机 APIC 建置在它们的处理器中。(摘自百度百科)
虽然也不知道为什么将local翻作本机而不是本地,但是,可以肯定,它比8259A晚16年推出!这简直是中断控制器的一次伟大飞跃
所以,综上所述,现代的操作系统几乎很少用8259a这种古老的中断控制器
那么,开始我们的apic驱动的开发吧!
像所有设备的驱动一样,运行初期必须要做一件事情,那便是,检测电脑是否存在这个设备,怎么检测呢?简单,只需要用CPUID指令检测即可,于是,我就又写了一个新的LIB,这个lib添加在开发文件夹的/lib中,名字叫libAsys.a(没有被官方认证或正式发布的库或头文件的名字都含大写字母!)其中的函数被包含在/include/SysAsmLib.h中,这里我简单地将cpuid的汇编指令封装了一下,返回的参数都用 (寄存器)_ 保存,虽然没有32位寄存器,但是可以完全可以直接用64位寄存器将内部的值读出来,以下便是cpuid_c的源码
;摘自./lib/reg_op.asm
cpuid_c: ;cpuid
push rax
push rbx
push rcx
push rdx
mov rax,rdi
cpuid
mov [rel rax_],rax
mov [rel rbx_],rbx
mov [rel rcx_],rcx
mov [rel rdx_],rdx
pop rdx
pop rcx
pop rbx
pop rax
ret
之后我们便可以使用它在c语言环境中读取cpuid了,当然除此之外还顺便将bts,btr,bt也打包成了c语言指令,原型如下
;./libs/bitoperate.asm
[bits 64]
test_bit:
bt rdi,rsi
jc bt_carry
mov rax,0
ret
bt_carry:
mov rax,1
ret
set_bit:
bts rdi,rsi
mov rax,rdi
ret
reset_bit:
btr rdi,rsi
mov rax,rdi
ret
有了这几个函数我们便可以开始apic(x)和x2apic的检测了,检测函数如下所示
bool init_apic(void)
{
cpuid_c(0x80000001);
state_printk(PRINT_OK,"CPUID 0x80000001 returns ECX 0x%X EDX 0x%X\n",rcx_,rdx_);
if(test_bit(rdx_,9))
{
state_printk(PRINT_OK,"APIC Support --- YES\n");
if(test_bit(rcx_,21))
{
state_printk(PRINT_OK,"x2APIC Support --- YES\n");
}
else
{
//此处省略若干行
}
return true;
}
else state_printk(PRINT_ERR,"APIC Support --- NO\n");
return false;
}
但正当我兴高采烈地将它放到虚拟机上运行后,它却告诉我
啥玩意?没有版本?没事,这都不算什么(杰哥名言)。虚拟机算个啥,我还有这不真机没实验嘛。
于是带着期待的心情我重启了电脑,但是突然
Pawloader显示了一条error(这里不方便拍照就先用文字描述好了),不支持APIC,当然这时我是不信的,好端端一个64位的LGA775cpu怎么就不支持apic啦?于是,我又把CPUID抄录了下来0x1和0x20100800,带着怀疑的心情进入了ubuntu然后用计算器的程序员模式一看:
确实不支持APIC
***(此处屏蔽一些文明用语),小丑竟是我自己,真的有64位支持pci的设备不支持APIC
不是吧,难怪这台电脑上的GT610正常跑分6500分到我的电脑上就只有3000多分,原来没有apic,性能打了50%的折扣(尽管如此还是能在低分辨率下流畅玩原神,原神YYDS!),G41主板YYDS!
但总不可能自己设计的系统自己的电脑都不支持吧?于是我又翻了一眼书,找到了一些奇妙的玩意
好家伙!
这么一说PCI的中断是可以被8259a接收到的,并且会被分配到irq3,4,5,6,7,9,10,11,12,14,15这几根中断线上,具体分配到哪根只需要去读PCI配置空间即可,这里挂一下PCI设备配置空间的结构图
根据我在虚拟机和真机上的实验,我发现interrupt line 基本上都是3,4,5,6,7,9,10,11,12,14,15这一类数字(排除真机上出现的0x255这个特例),所以我大胆猜测,这玩意就是链接8259a的中断线号,哦,这意味着我们要准备共享中断号了!啊,我的头发,再多写几次这样超高难度的代码我的头发就要薅得和化学老师一样秃了(虽然我的头发仍然浓密得不得了,自然程序的bug哦不,特性就比较多)。但是我又在网上翻到了一些不得了的玩意,共享中断要轮询设备驱动效率高得感人,但这也没办法,谁叫我的电脑是几十年前的G41 pcie协议甚至都没有3.0(意味着不能使用MSI和MSI-X),
8259a也是支持PCI的可是上一章图像里8259a明明不支持pci中断,但是顺藤摸瓜,我又去网上搜了一下PIRQ MAPPING,更刺激的出现了
没错,田宇书上的表省略了共享中断引脚的设备,之后再根据这篇文章
https://blog.csdn.net/weixin_30300225/article/details/99421416
我们便可以得知pci配置空间的pin实际上对应着哪个引脚,PIRQABCD...等引脚按次序依次从表中的irq3,4,5,6,7,9,10,11,12,14,15,绝,实在是绝,但是在我电脑bios启动时期我看到了bios给所有pci设备列了表,并分配了irq,也就是说我们只需要将pci配置空间的pci intr_line的值读出来就知道是哪个pirq了
当然具体pci的PIRQABCD是如何与irq链接的请见下面的文章(不能保证N年后它仍存在)
总之,这一章必须要让PCI设备给爷触发中断
于是,我就到网上翻了一段时间(拖更的原因出现了...),翻了个寂寞,开玩笑的,翻到了两个好宝贝UHCI Design guide和EHCI Design guide,两本手册在usb官网文档库中找不到,是在豆丁网还有另一个网站上找到的,上面介绍了UHCI(即USB1.1)的运行机制,数据结构和IO寄存器的使用方法(EHCI同理),没错,我今天就要让UHCI给我触发中断,于是我就先行将UHCI探测(初始化)程序写出来了,代码如下所示
//path = minefunction/PawLoader/driver/usb/uhci.c
bool probe_uhci(struct pci_info *dev)
{
u32 class = in_pci_conf32(dev,PCI_REG_CLASS); //在minefunction/PawLoader/driver/pci/pci_conf.c中
class >>=8;
if(class!=PCI_USB_DEV) return false; //判断设备的class值来判断是否为USB根集线器
driver_printk(PRINT_OK,DRIVER_NAME,"Hello UHCI !\n");
//根据UHCI design guid的第10面可知 pci_config_space的bar4存着uhci的io基地址
//注意,这个值一般在开机时就已经被bios初始化了,但是目前不能确定efi启动后是否会为其分配io地址
u32 tmp_bit_map = in_pci_conf32(dev,PCI_REG_BAR_4_);
if(test_bit(tmp_bit_map,0)!=true) return false; //如果不占用io资源,则不是该程序支持的uhci
tmp_bit_map >>= 4;
tmp_bit_map <<= 4; //把它的属性洗掉
if(tmp_bit_map == 0) return false; //暂不支持对pci设备的资源分配
struct hc_device hc_dev;
hc_dev.io_base = (u16)tmp_bit_map; //记下io寄存器的地址
driver_printk(PRINT_OK,DRIVER_NAME,"UHCI IO PORT 0x%x\n",hc_dev.io_base);
u16 intr_op = io_in2b(UHCI_REG_OFF_INTR+hc_dev.io_base); //将中断控制寄存器读入
intr_op = set_bit(intr_op,UHCI_INTR_SPIE_BIT); //管它啥中断,一股脑开启就完了
intr_op = set_bit(intr_op,UHCI_INTR_IOCE_BIT);
intr_op = set_bit(intr_op,UHCI_INTR_RIE_BIT);
intr_op = set_bit(intr_op,UHCI_INTR_CRCIE_BIT);
io_out2b(UHCI_REG_OFF_INTR+hc_dev.io_base,intr_op); //写回
u16 cmd_reg = io_in2b(UHCI_REG_OFF_CMD+hc_dev.io_base); //同理,开启设备
cmd_reg = set_bit(cmd_reg,UHCI_CMD_RS_BIT);
io_out2b(UHCI_REG_OFF_CMD+hc_dev.io_base,cmd_reg);
u16 *debug = (u16 *)0x500; //debug数据指针,可以指向任何分配了的空闲内存
for(int i=0;i<0x20;i++) debug[i] = io_in2b(hc_dev.io_base+i*2);
state_printk(PRINT_OK,"IO DATA : 0x%X 0x%X 0x%X 0x%X\n",debug[0],debug[1],debug[2],debug[3]);
state_printk(PRINT_OK,"IO DATA : 0x%X 0x%X 0x%X 0x%X\n",debug[4],debug[5],debug[6],debug[7]);
PAUSE; //在bochs中运行到这会停止,之后用xp/40xw 0x500看uhci io寄存器的内容
hc_dev.int_line = in_pci_conf32(dev,PCI_INT_LINE)&0xff; //记下中断号
//将设备注册
register_uhci_dev(&hc_dev);
return true;
}
代码中还有一些宏,大部分定义在了usb.h中,可以自行阅读
当然在源码minefunction/usb_specific中我将20块钱买来的(不知道值不值),两份文档存在了里面,在baidu网盘的链接根目录也有,可下载下来自行阅读(虽然是全英文的但是有道翻译就很香)
于是我就将源码编译,运行了一遍,在虚拟机中结果如下
虚拟机都不算什么,看看真机,啊,完美......的重启(后来发现是不是HCD的问题)了,Fantastic(Doctor狂喜)!怎么回事呢,请听下回分解......
就怪了,BUG,哦不,特性不好好修理一番不像我的风格(尽管还是有一大把特性),但是没有关系,无论如何先将uhci完全初始化(下一章细讲)一下康康
然后嘛,初始化是全都完成了,但是虚拟机上没有半点中断,如下图所示
但是,虚拟机,能代表什么呢?重要的是真机!
啊,成功啦!个头啊!
随便写的HCD要是能成功我都能去当半仙算卦去了,细看图上STS_REG:0x30,啥意思?UHCI设备挂起,并且HC检查到了一个致命的错误,需要我们将设备停止防止HC使用错误的信息继续产生错误中断
哈,就知道要么是我的程序出错要么是bochs出错,这次不一样,都出错了,负负得正,就"成功"了,不过没关系,错误的中断总比没有好,下一章把它TD的队列头写好估计就没问题了
顺便把源码链接挂一下
链接: https://pan.baidu.com/s/1m9WZVL4zFlnsb7t2aL5kng 提取码: sdpk
标签:PCI,Akaban,中断,pci,-----,APIC,bit,rax 来源: https://blog.csdn.net/qq_57339294/article/details/122566719