在操作系统中,声卡、网卡之类的设备驱动并不像硬盘、鼠标、键盘等等驱动直接编写就行了。它们是建立在内核PCI驱动基础上的,也就是说这类设备通过PCI总线与系统通信。所以要编写这类的驱动首先要构造一个PCI设备的内核驱动,这样我们才能继续正常的使用连接在PCI总线的设备。
关于PCI设备的详细介绍,还是请大家参考网上诸位大侠的文章,笔者这里就不班门弄斧了。这中间的原因当然是笔者根本就看不懂,主要是PCI设备相较于普通的设备复杂了很多,不仔细研究个十天八天的真的是弄不懂啊。
那么笔者既然不懂,又是怎么编写出的PCI驱动程序呢。起始笔者还真没有那个本事。这次笔者是又一次发挥了复制粘贴的本领。干脆直接把linux内核的PCI驱动粘贴进自己的代码中,本来是只是抱着试试看的想法,没想真的奏效了,看来这些日子的运气还是不错的。
https://elixir.bootlin.com/
通过上图大家看到了吧。笔者这次居然敢去碰linux内核了。在这里就是PCI设备驱动的内核部分。而且笔者还可以把网页翻译成了中文。
关于文件的详情,笔者这里也没完全弄懂,所以还是请大家自主观看吧。这里要告诉大家的是其实linux为我们展示了3种PCI访问方式。笔者只是拿来了一种作为测试代码。前两种是通过输入输出指令直接探测设备,而第3种是调用BIOS的代码来实现的。哦对了,起始PCI设备也不是专为intel处理器设计的,所以我们参考的代码是与体系结构相关的,也就是i386架构的。
上图具体定位了真正的激活PCI设备的函数,由于linux代码的架构性太强,笔者有些代码根本就看不懂。好在笔者还是比较善于改编的,这不笔者就通过少量的修改,形成了自己系统的代码。
unsigned int tmp;
void out8_(unsigned char data, unsigned short port);
void out16_(unsigned short data, unsigned short port);
void out32_(unsigned int data, unsigned short port);
unsigned char in8_(unsigned short port);
unsigned short in16_(unsigned short port);
unsigned int in32_(unsigned short port);
#define inb in8_
#define inw in16_
#define inl in32_
#define outb out8_
#define outw out16_
#define outl out32_
#define CONFIG_CMD(bus, device_fn, where) (0x80000000 | (bus << 16) \
| (device_fn << 8) | (where & ~3))
#define PCIBIOS_SUCCESSFUL 0x00
int pci_conf1_read_config_byte(unsigned char bus, unsigned char device_fn,
unsigned char where, unsigned char *value)
{
outl(CONFIG_CMD(bus,device_fn,where), 0xCF8);
*value = inb(0xCFC + (where&3));
return PCIBIOS_SUCCESSFUL;
}
int pci_conf1_read_config_word (unsigned char bus,
unsigned char device_fn, unsigned char where, unsigned short *value)
{
outl(CONFIG_CMD(bus,device_fn,where), 0xCF8);
*value = inw(0xCFC + (where&2));
return PCIBIOS_SUCCESSFUL;
}
int pci_conf1_read_config_dword (unsigned char bus, unsigned char device_fn,
unsigned char where, unsigned int *value)
{
outl(CONFIG_CMD(bus,device_fn,where), 0xCF8);
*value = inl(0xCFC);
return PCIBIOS_SUCCESSFUL;
}
int pci_conf1_write_config_byte (unsigned char bus, unsigned char device_fn,
unsigned char where, unsigned char value)
{
outl(CONFIG_CMD(bus,device_fn,where), 0xCF8);
outb(value, 0xCFC + (where&3));
return PCIBIOS_SUCCESSFUL;
}
int pci_conf1_write_config_word (unsigned char bus, unsigned char device_fn,
unsigned char where, unsigned short value)
{
outl(CONFIG_CMD(bus,device_fn,where), 0xCF8);
outw(value, 0xCFC + (where&2));
return PCIBIOS_SUCCESSFUL;
}
int pci_conf1_write_config_dword (unsigned char bus, unsigned char device_fn,
unsigned char where, unsigned int value)
{
outl(CONFIG_CMD(bus,device_fn,where), 0xCF8);
outl(value, 0xCFC);
return PCIBIOS_SUCCESSFUL;
}
#define PCI_CLASS_DEVICE 0x0a /* Device class */
#define PCI_CLASS_DISPLAY_VGA 0x0300
#define PCI_CLASS_BRIDGE_HOST 0x0600
#define PCI_VENDOR_ID 0x00 /* 16 bits */
#define PCI_VENDOR_ID_INTEL 0x8086
#define PCI_VENDOR_ID_COMPAQ 0x0e11
typedef unsigned short u16;
int pci_sanity_check(void)
{
u16 dfn, x;
for(dfn=0; dfn < 0x100; dfn++)
if ((!pci_conf1_read_config_word(0, dfn, PCI_CLASS_DEVICE, &x) &&
(x == PCI_CLASS_BRIDGE_HOST || x == PCI_CLASS_DISPLAY_VGA)) ||
(!pci_conf1_read_config_word(0, dfn, PCI_VENDOR_ID, &x) &&
(x == PCI_VENDOR_ID_INTEL || x == PCI_VENDOR_ID_COMPAQ)))
return 1;
return 0;
}
void pci_check_direct(void)
{
unsigned int tmp;
unsigned long flags;
outb (0x01, 0xCFB);
tmp = inl (0xCF8);
outl (0x80000000, 0xCF8);
if (inl (0xCF8) == 0x80000000 && pci_sanity_check()) {
outl (tmp, 0xCF8);
return;
}
outl (tmp, 0xCF8);
}
pci_check_direct();
#undef inb
#undef inw
#undef inl
#undef outb
#undef outw
#undef outl
for(i = 0x28; i < 0x28 + 1; i++) {
pci_conf1_read_config_dword(0, i, 0, &tmp);
printf_(" %x %x ", i, tmp);
}
unsigned short nambar, nabmbar;
pci_conf1_read_config_dword(0, 0x28, 0x10, &tmp);
nambar = tmp & 0xfffe;
pci_conf1_read_config_dword(0, 0x28, 0x14, &tmp);
nabmbar = tmp & 0xffc0;
printf_("|NAMBAR=%x, NABMBAR=%x|", nambar, nabmbar);
下边是汇编的部分
global _out8, _out16, _out32, _in8, _in16, _in32
global _out8_, _out16_, _out32_, _in8_, _in16_, _in32_
align 16
_out8: ; void out8(unsigned short port, unsigned char data);
push edx
mov dx, [esp + 2 * 4]
mov al, [esp + 3 * 4]
out dx, al
pop edx
ret
align 16
_out16: ; void out16(unsigned short port, unsigned short data);
push edx
mov dx, [esp + 2 * 4]
mov ax, [esp + 3 * 4]
out dx, ax
pop edx
ret
align 16
_out32: ; void out32(unsigned short port, unsigned int data);
push edx
mov dx, [esp + 2 * 4]
mov eax, [esp + 3 * 4]
out dx, eax
pop edx
ret
align 16
_out8_: ; void out8_(unsigned char data, unsigned short port);
push edx
mov dx, [esp + 3 * 4]
mov al, [esp + 2 * 4]
out dx, al
pop edx
ret
align 16
_out16_: ; void out16_(unsigned short data, unsigned short port);
push edx
mov dx, [esp + 3 * 4]
mov ax, [esp + 2 * 4]
out dx, ax
pop edx
ret
align 16
_out32_: ; void out32_(unsigned int data, unsigned short port);
push edx
mov dx, [esp + 3 * 4]
mov eax, [esp + 2 * 4]
out dx, eax
pop edx
ret
align 16
_in8_:
_in8: ; unsigned char in8(unsigned short port);
push edx
mov dx, [esp + 2 * 4]
in al, dx
pop edx
ret
align 16
_in16_:
_in16: ; unsigned short in16(unsigned short port);
push edx
mov dx, [esp + 2 * 4]
in ax, dx
pop edx
ret
align 16
_in32_:
_in32: ; unsigned int in32(unsigned short port);
push edx
mov dx, [esp + 2 * 4]
in eax, dx
pop edx
ret
下边是探测到的ICH ac97声卡的设备号和音频混音器的寄存器的端口基地址以及音频总线主控制器的端口基地址。当然在Snail OS中为了看清探测的结果,已经关闭了所有中断。
结合虚拟机的调试信息大家可以知道,探测的设备信息完全正确。
VBoxDbg> info pci
00:00.0 i440FX: 8086-1237 PIIX3
Class base/sub: 0600 (bridge device)
Command: 0000, Status: 0000
Bus master: No
00:01.0 PIIX3: 8086-7000 PIIX3
Class base/sub: 0601 (bridge device)
Command: 0007, Status: 0200
Bus master: Yes
00:01.1 piix3ide: 8086-7111 PIIX3
Class base/sub: 0101 (mass storage controller)
IO region #4: d000..d00f
Command: 0007, Status: 0000
Bus master: Yes
00:02.0 vga: 80ee-beef PIIX3 IRQ10 (INTA#->IRQ18)
Class base/sub: 0300 (display controller)
MMIO32 PREFETCH region #0: e0000000..e07fffff
Command: 0003, Status: 0000
Bus master: No
00:03.0 pcnet: 1022-2000 PIIX3 IRQ9 (INTA#->IRQ19)
Class base/sub: 0200 (network controller)
IO region #0: d020..d03f
MMIO32 region #1: f0000000..f0000fff
Command: 0007, Status: 0280
Bus master: Yes
00:04.0 VMMDev: 80ee-cafe PIIX3 IRQ11 (INTA#->IRQ20)
Class base/sub: 0880 (base system peripherals)
IO region #0: d040..d05f
MMIO32 region #1: f0400000..f07fffff
MMIO32 PREFETCH region #2: f0800000..f0803fff
Command: 0003, Status: 0000
Bus master: No
00:05.0 ichac97: 8086-2415 PIIX3 IRQ11 (INTA#->IRQ21)
Class base/sub: 0401 (multimedia controller)
IO region #0: d100..d1ff
IO region #1: d200..d23f
Command: 0001, Status: 0280
Bus master: No
00:06.0 usb-ohci: 106b-003f PIIX3 IRQ10 (INTA#->IRQ22)
Class base/sub: 0c03 (serial bus controllers)
MMIO32 region #0: f0804000..f0804fff
Command: 0002, Status: 0010
Bus master: No
00:07.0 acpi: 8086-7113 PIIX3 IRQ9 (INTA#->IRQ23)
Class base/sub: 0680 (bridge device)
Command: 0001, Status: 0280
Bus master: No
VBoxDbg>
总线上PCI设备可接256种之多,所以大家只要把代码中的for语句从0号设备开始循环查询就能够看到所有设备的全貌了。
下图是笔者探测的前64个,不知道大家是否能和调试信息对号入座吗?