因为小可并非硬件编程出身,汇编基础又比较差。。。所以刚开始理解利用IOAPIC重定位技术的时候相当困难。
何为IOAPIC?
首先,必须认识到它是一个硬件,可编程的硬件。我理解的它在整个流程中的作用如图:
首先,必须创建一个新的中断项,也就是在IDT表中搜索到一个空闲的项,代码如下
P2C_U8 p2cGetIdleIdtVec() { P2C_U8 i; PP2C_IDTENTRY idt_addr = (PP2C_IDTENTRY)p2cGetIdt(); // 从vec20搜索到2a即可。 for(i=0x20;i<0x2a;i++) { // 如果类型为0说明是空闲位置,返回即可。 if(idt_addr[i].type == 0) { return i; } } return 0; }
复制中断表中索引为0x93的项到新项
P2C_U8 p2cCopyANewIdt93(P2C_U8 id,void *interrupt_proc) { // 我们写入一个新的中断门。这个门完全拷贝原来的0x93 // 上的idtentry,只是中断处理函数的地址不同。 PP2C_IDTENTRY idt_addr = (PP2C_IDTENTRY)p2cGetIdt(); idt_addr[id] = idt_addr[0x93]; idt_addr[id].offset_low = P2C_LOW16_OF_32(interrupt_proc); idt_addr[id].offset_high = P2C_HIGH16_OF_32(interrupt_proc); return id; }
然后将ioapic重定位表中关于键盘中断IRQ1的处理地址替换掉,修改ioapic重定位表需要使用两个寄存器。
一个是IOREGSEL寄存器(只低8位有用) 一个是IOWIN寄存器(32位)
首先在IOREGSEL寄存器指定要访问的ioapic寄存器的编号,然后修改IOWIN寄存器中的内容即可达到目的
Windows把这两个寄存器分别映射到内存的物理地址0xFEC00000和0xFEC00010处
获取IOREGSEL寄存器的代码为
P2C_U8 *io_reg_sel; PHYSICAL_ADDRESS phys ; PVOID paddr; RtlZeroMemory(&phys,sizeof(PHYSICAL_ADDRESS)); phys.u.LowPart = 0xfec00000; paddr = MmMapIoSpace(phys, 0x14, MmNonCached);
IOWIN寄存器往后偏移0x10
P2C_U32 *io_win; io_win = (P2C_U32 *)((P2C_U8 *)(paddr) + 0x10);
然后使用IOREGSEL寄存器选择第0x12项,为IRQ1的项
*io_reg_sel = 0x12;
设置新的中断号
ch1 = *io_win; ch1 &= 0xffffff00; ch1 |= (P2C_U32)new_ch; *io_win = ch1;
完成!