qiuhan 2007.10.13 我们并没有实际去研读物理bios的代码,而是查看了bochs中的bios虚拟实现,主要出于如下考虑: 1 对于物理机的bios需要特殊的硬件进行调试,我们没有 2 对于不同的系统构架,bios很可能不同,我们偏重于功能性的研读bios,对于理解os的实现来说, 已经足够了;而且,这也会是理解物理bios的一个很好的起点。 另外,我们更多的是关注bios中对PCI和ACPI的实现,对于POST流程,我们只说了一个大概。 下面我们就开始介绍一些基础知识: the layout and contents of the first Meg of memory[1][2]: 0x0 - 0x3ff: 256个bios中断向量 0x400 - 0x4ff: 255B BDA(BIOS Data Area) 保存bios检测的结果,如: 0x40E: LPT4 I/O base address 或者 EBDA(Extended Bios Data Area) 如果存在EBDA,其值为0x9FC0,查找RSDP的一种方法就是从EBDA查找(AcpiTbFindRsdp) 0x410: Equipment Word 0x472: Soft reset flag 系统启动时会在该地址写入1234h告诉bios下次跳过内存检测 参见i386/i386/locore.s 0x475: Number of hard disk drives(参见boot0) 0x500 - 0x9Fbff: dos, etc 0x9FC00 - 0x9feff: EBDA(Extended Bios Data Area) 768B 0x9ff00 -- 0x9ffff: boot device tables 256B 0xA0000 - 0xAffff: Graphics Video memory (EGA and above) 0xB0000 - 0xBffff: Graphics area for EGA and up 0xC0000 - 0xCffff: additional ROM-BIOS & video memory 0xD0000 - 0xDffff: ROM cartridges 0xE0000 - 0xEffff: ROM cartridges 0xF0000 - 0xFDfff: IBM PC ROM BASIC 0xFE000 - 0xFFFEF: ORIGINAL IBM PC ROM BIOS 0xfe05b : POST Entry Point 0xFFFF0 - 0xFFFF4: Power-up Entry Point(RESET JUMP) 0xffff5 : ASCII Date ROM was built - 8 characters in MM/DD/YY 0xffffe : System Model ID bios的作用不仅仅只是POST,在os启动之后还会为os提供支持,做幕后英雄。以BSD为例: 1 pci_routing_table_structure: "$PIR" pci_pir_open调用bios_sigsearch在0xe0000-0x100000之间查找"$PIR"(或者"_PIR")。 2 bios32_structure: "_32_" signature bios32入口,在BSD启动中bios32_init调用bios_sigsearch在0xe0000-0x100000 之间查找"_32_",得到入口地址bios32_entry_point保存在bios32_SDCI中。 然后以0x49435024("$PCI")为参数调用bios32_SDlookup,实际上会调用bios32_entry_point来得到pcibios的入口地址pcibios_protected,保存在PCIbios中。 pci_pir_biosroute中会通过bios32调用pcibios_protected来为一个特定的设备路由中断。 pci配置寄存器的读写: 每个 PCI 外设有一个总线号, 一个设备号, 一个功能号标识. PCI 规范允许单个系统占用多达 256 个总线, 但是因为 256 个总线对许多大系统是不够的, Linux 现在支持 PCI 域. 每个 PCI 域可以占用多达 256 个总线. 每个总线占用 32 个设备, 每个设备可以是一个多功能卡(例如一个声音设备, 带有一个附加的 CD-ROM 驱动)有最多 8 个功能. 因此, 每个功能可在硬件层次被一个 16-位地址或者 key , 标识. 所有的 PCI 设备都有一个至少 256-字节配置寄存器地址空间, 前 64 字节是标准的, 而剩下的是依赖设备的(ldd3:ch12). 第一步是将寄存器的地址写入配置地址寄存器(0xcf8)中,寄存器的地址格式如下(pci22: p52): 31:配置使能位,在进行配置操作时必须将该位设置为1。 30-24:是保留位; 23-16:位是总线号,8位,最大256个总线 15-11位是设备号,5位,最大32个设备 10-8位是功能号,3位,最大8个功能,对于单功能设备,其值为0。 7-2是外部PCI设备的PCI配置空间寄存器偏移量, 6位,最大64个字节??? 1-0: 只读,读时必须返回0 注意这里的Bit7-2寄存器偏移量占用6位,只能表示64个,不是有256个字节吗? 这里用到一个技巧,我们接下来会看看到。 第二步是对配置数据寄存器(0xcfc)进行读或写。 我们来回答上面的问题: 假设我们读的是long数据(4字节),那很容易,直接把Bit7-2左移2位 就可以得到8位的寄存器偏移了; 假设我们读的是word数据(2字节)或者就是一个字节,那么末2位从哪里得到呢? pci把这个偏移给了配置数据寄存器。 我们来看实例:下例是读pci设备d上偏移为addr的配置寄存器: static uint32_t pci_config_readw(PCIDevice *d, uint32_t addr) { outl(0xcf8, 0x80000000 | (d->bus << 16) | (d->devfn << 8) | (addr & 0xfc)); return inw(0xcfc + (addr & 2)); } 读的是一个word,那么addr的最后一位肯定为0.我们把addr用0xfc进行mask得到的是addr的Bit7-2. 丢失的是Bit1, Bit0一定为0; 然后我们读配置数据寄存器时,把Bit1传给了它(addr & 2). 我们来看pci是怎么来理解的: static uint32_t pci_host_data_readw(void* opaque, pci_addr_t addr) { PCIHostState *s = opaque; uint32_t val; if (!(s->config_reg & (1 << 31))) return 0xffff; val = pci_data_read(s->bus, s->config_reg | (addr & 3), 2); #ifdef TARGET_WORDS_BIGENDIAN val = bswap16(val); #endif return val; } 这里参数addr是我们传给inw的值,s->config_reg是我们先前通过 outl(0xcf8, 0x80000000 | (d->bus << 16) | (d->devfn << 8) | (addr & 0xfc)); 传给的值,由于传给的值含有0x80000000,所以在if时不会给我们返回0xffff; 另外,通过s->config_reg | (addr & 3)把地址给补齐了。 下面我们开始使用bochs来调试bios(我们讲述的只是post,不涉及setup): # bochs -f /root/.bochsrc (0) [0xfffffff0] f000:fff0 (unk. ctxt): jmp far f000:e05b ; ea5be000f0 这里是cpu的第一条指令f000:fff0(对应的物理地址为0xffff0),很简单,就是一个跳转到0xfe05b的指令。 我们来看一下此时cpu寄存器的状态: <bochs:1> dump_cpu eax:0x00000000, ebx:0x00000000, ecx:0x00000000, edx:0x00000543 ebp:0x00000000, esp:0x00000000, esi:0x00000000, edi:0x00000000 eip:0x0000fff0, eflags:0x00000002, inhibit_mask:0 cs:s=0xf000, dl=0x0000ffff, dh=0xff009bff, valid=1 ss:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1 ds:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1 es:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1 fs:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1 gs:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1 ldtr:s=0x0000, dl=0x0000ffff, dh=0x00008200, valid=1 tr:s=0x0000, dl=0x0000ffff, dh=0x00008300, valid=1 gdtr:base=0x00000000, limit=0xffff idtr:base=0x00000000, limit=0xffff dr0:0x00000000, dr1:0x00000000, dr2:0x00000000 dr3:0x00000000, dr6:0xffff0ff0, dr7:0x00000400 cr0:0x00000010, cr1:0x00000000, cr2:0x00000000 cr3:0x00000000, cr4:0x00000000 done 这里很多值都为0,为什么edx的值为0x543呢?我也不知道[FIXME]。 我们比较关心的是eflags,eip, cs和idt. 我们继续看跳转后的代码: <bochs:2> disassemble 0xfe05b 0xfe060 000fe05b: ( ): xor ax, ax ; 31c0 000fe05d: ( ): out 0x0d, al ; e60d 000fe05f: ( ): out 0xda, al ; e6da 为了查看方便,我们还是使用源码bochs-2.3.5/bios/rombios.c: .org 0xe05b ; POST Entry Point bios_table_area_end: post: xor ax, ax ;; first reset the DMA controllers out 0x0d,al out 0xda,al ;; then initialize the DMA controllers mov al, #0xC0 out 0xD6, al ; cascade mode of channel 4 enabled mov al, #0x00 out 0xD4, al ; unmask channel 4 这里初始化DMA控制器 ;; Examine CMOS shutdown status. mov AL, #0x0f out 0x70, AL in AL, 0x71 ;; backup status mov bl, al ;; Reset CMOS shutdown status. mov AL, #0x0f out 0x70, AL ; select CMOS register Fh mov AL, #0x00 out 0x71, AL ; set shutdown action to normal ;; Examine CMOS shutdown status. mov al, bl ;; 0x00, 0x09, 0x0D+ = normal startup cmp AL, #0x00 jz normal_post cmp AL, #0x0d jae normal_post cmp AL, #0x09 je normal_post 检查CMOS关机状态,这里我们会跳转到normal_post normal_post: ; case 0: normal startup cli mov ax, #0xfffe mov sp, ax xor ax, ax mov ds, ax mov ss, ax ;; zero out BIOS data area (40:00..40:ff) mov es, ax mov cx, #0x0080 ;; 128 words mov di, #0x0400 cld rep stosw 对BDA段(0x400-0x4ff)清0,接下来的post过程会在此段内存写入很多控制和状态信息。 ;; set all interrupts to default handler xor bx, bx ;; offset index mov cx, #0x0100 ;; counter (256 interrupts) mov ax, #dummy_iret_handler mov dx, #0xF000 post_default_ints: mov [bx], ax inc bx inc bx mov [bx], dx inc bx inc bx loop post_default_ints 在0x0-0x3ff段写入256个缺省的中断向量(0xf000ff53,处理函数为dummy_iret_handler),此时中断是关闭的。 ;; set vector 0x79 to zero ;; this is used by 'gardian angel' protection system SET_INT_VECTOR(0x79, #0, #0) 重设0x79号中断 ;; base memory in K 40:13 (word) mov ax, #BASE_MEM_IN_K mov 0x0413, ax 在0x413处写入Memory size in Kb(0x027f,639) ;; Manufacturing Test 40:12 ;; zerod out above ;; Warm Boot Flag 0040:0072 ;; value of 1234h = skip memory checks ;; zerod out above 对于0x412和0x472的检测,并没有实现。 ;; Printer Services vector SET_INT_VECTOR(0x17, #0xF000, #int17_handler) ;; Bootstrap failure vector SET_INT_VECTOR(0x18, #0xF000, #int18_handler) ;; Bootstrap Loader vector SET_INT_VECTOR(0x19, #0xF000, #int19_handler) ;; User Timer Tick vector SET_INT_VECTOR(0x1c, #0xF000, #int1c_handler) ;; Memory Size Check vector SET_INT_VECTOR(0x12, #0xF000, #int12_handler) ;; Equipment Configuration Check vector SET_INT_VECTOR(0x11, #0xF000, #int11_handler) ;; System Services SET_INT_VECTOR(0x15, #0xF000, #int15_handler) 设置中断向量,其中int19_handler是启动函数,post的最后阶段就是调用它; 而int18_handler是启动失败的处理函数,设备引导失败时会调用它把控制权重新交给bios ;; EBDA setup call ebda_post 在0x40E写入0x9FC0,标志EBDA的起始地址。 ;; PIT setup SET_INT_VECTOR(0x08, #0xF000, #int08_handler) ;; int 1C already points at dummy_iret_handler (above) mov al, #0x34 ; timer0: binary count, 16bit count, mode 2 out 0x43, al mov al, #0x00 ; maximum count of 0000H = 18.2Hz out 0x40, al out 0x40, al 注册System Timer ISR Entry Point xor ax, ax mov ds, ax mov 0x0417, al /* keyboard shift flags, set 1 */ mov 0x0418, al /* keyboard shift flags, set 2 */ mov 0x0419, al /* keyboard alt-numpad work area */ mov 0x0471, al /* keyboard ctrl-break flag */ mov 0x0497, al /* keyboard status flags 4 */ mov al, #0x10 mov 0x0496, al /* keyboard status flags 3 */ /* keyboard head of buffer pointer */ mov bx, #0x001E mov 0x041A, bx /* keyboard end of buffer pointer */ mov 0x041C, bx /* keyboard pointer to start of buffer */ mov bx, #0x001E mov 0x0480, bx /* keyboard pointer to end of buffer */ mov bx, #0x003E mov 0x0482, bx /* init the keyboard */ call _keyboard_init 初始化键盘 ;; mov CMOS Equipment Byte to BDA Equipment Word mov ax, 0x0410 mov al, #0x14 out 0x70, al in al, 0x71 mov 0x0410, ax 从cmos中读出并在0x410处写入Equipment Word ;; Parallel setup SET_INT_VECTOR(0x0F, #0xF000, #dummy_iret_handler) xor ax, ax mov ds, ax xor bx, bx mov cl, #0x14 ; timeout value mov dx, #0x378 ; Parallel I/O address, port 1 call detect_parport mov dx, #0x278 ; Parallel I/O address, port 2 call detect_parport shl bx, #0x0e mov ax, 0x410 ; Equipment word bits 14..15 determing # parallel ports and ax, #0x3fff or ax, bx ; set number of parallel ports mov 0x410, ax 并口的设置 ;; Serial setup SET_INT_VECTOR(0x0C, #0xF000, #dummy_iret_handler) SET_INT_VECTOR(0x14, #0xF000, #int14_handler) xor bx, bx mov cl, #0x0a ; timeout value mov dx, #0x03f8 ; Serial I/O address, port 1 call detect_serial mov dx, #0x02f8 ; Serial I/O address, port 2 call detect_serial mov dx, #0x03e8 ; Serial I/O address, port 3 call detect_serial mov dx, #0x02e8 ; Serial I/O address, port 4 call detect_serial shl bx, #0x09 mov ax, 0x410 ; Equipment word bits 9..11 determing # serial ports and ax, #0xf1ff or ax, bx ; set number of serial port mov 0x410, ax 串口的设置 ;; CMOS RTC SET_INT_VECTOR(0x1A, #0xF000, #int1a_handler) SET_INT_VECTOR(0x4A, #0xF000, #dummy_iret_handler) SET_INT_VECTOR(0x70, #0xF000, #int70_handler) ;; BIOS DATA AREA 0x4CE ??? call timer_tick_post 为CMOS RTC设置中断向量,并检测timer_tick ;; PS/2 mouse setup SET_INT_VECTOR(0x74, #0xF000, #int74_handler) ;; IRQ13 (FPU exception) setup SET_INT_VECTOR(0x75, #0xF000, #int75_handler) ;; Video setup SET_INT_VECTOR(0x10, #0xF000, #int10_handler) ;; PIC mov al, #0x11 ; send initialisation commands out 0x20, al out 0xa0, al mov al, #0x08 out 0x21, al mov al, #0x70 out 0xa1, al mov al, #0x04 out 0x21, al mov al, #0x02 out 0xa1, al mov al, #0x01 out 0x21, al out 0xa1, al mov al, #0xb8 out 0x21, AL ;master pic: unmask IRQ 0, 1, 2, 6 #if BX_USE_PS2_MOUSE mov al, #0x8f #else mov al, #0x9f #endif out 0xa1, AL ;slave pic: unmask IRQ 12, 13, 14 初始化两个8259A中断控制器 call rombios32_init 这里是配置pci的关键代码,后面我们会着重介绍它。 call _init_boot_vectors call rom_scan 从0xC0000到0xE0000(包含),以2KB递增,检测是否以0xAA55起始并满足校验, 然后调用ROM initialization entry point,这里的调用使用: mov bp, sp ;; Call ROM init routine using seg:off on stack db 0xff ;; call_far ss:[bp+0] db 0x5e db 0 很有意思[FIXME]。 call _print_bios_banner ;; ;; Floppy setup ;; call floppy_drive_post ;; ;; Hard Drive setup ;; call hard_drive_post ;; ;; ATA/ATAPI driver setup ;; call _ata_init call _ata_detect sti ;; enable interrupts int #0x19 使能中断,并调用int19h,完成启动。 至此,我们对bios的大致流程有了一个大概的认识。 下面是我们的重点:rombios32_init。 它即是一段汇编码又是一段c代码:汇编码实现使能A20,设置idt,gdt以及段寄存器,并切换到保护模式, 然后把c代码的rombios32_init拷贝到0x40000,接着调用它;调用完成后把0x40000开始的内存清0, 重设段寄存器,恢复到实模式,重设idt 我们来看c代码的rombios32_init,源码位于: bochs-2.3.5/bios/rombios32.c void rombios32_init(void) { BX_INFO("Starting rombios32\n"); ram_probe(); cpu_probe(); smp_probe(); pci_bios_init(); if (bios_table_cur_addr != 0) { mptable_init(); if (acpi_enabled) acpi_bios_init(); bios_lock_shadow_ram(); } } ram_probe负责内存容量的检测,cpu_probe负责cpu的检测,smp_probe通过发送SIPI来检测系统中的cpu个数 void smp_probe(void) { uint32_t val, sipi_vector; smp_cpus = 1; if (cpuid_features & CPUID_APIC) { /* enable local APIC */ val = readl(APIC_BASE + APIC_SVR); val |= APIC_ENABLED; writel(APIC_BASE + APIC_SVR, val); writew((void *)CPU_COUNT_ADDR, 1); /* copy AP boot code */ memcpy((void *)AP_BOOT_ADDR, &smp_ap_boot_code_start, &smp_ap_boot_code_end - &smp_ap_boot_code_start); /* broadcast SIPI */ writel(APIC_BASE + APIC_ICR_LOW, 0x000C4500); sipi_vector = AP_BOOT_ADDR >> 12; writel(APIC_BASE + APIC_ICR_LOW, 0x000C4600 | sipi_vector); delay_ms(10); smp_cpus = readw((void *)CPU_COUNT_ADDR); } BX_INFO("Found %d cpu(s)\n", smp_cpus); } SVR: Spurious Interrupt Vector Register AP: Application Processor BSP: Boot-Strap Processor IPI: interprocessor interrupts SIPI: Startup IPI ICR: Interrupt Command Register 首先向local APIC的SVR写入APIC_ENABLED标志位使能; 在CPU_COUNT_ADDR(0xf000)处写入1标志自己是一个cpu; 然后在AP_BOOT_ADDR(0x10000)处拷贝入ap启动代码,该代码位于bios/rombios32start.S:42 .code16 smp_ap_boot_code_start: xor %ax, %ax mov %ax, %ds incw CPU_COUNT_ADDR 1: hlt jmp 1b smp_ap_boot_code_end: 代码很简单,就是在CPU_COUNT_ADDR处加1,然后hlt;那么ap怎么找到这个入口的呢? 首先向其它的ap(不包括自己)发送一条INIT(Intel v3:p288)中断,初始化(apic_init_ipi)lapic的寄存器; 然后以AP_BOOT_ADDR >> 12为中断向量发送SIPI,然后其它ap会通过apic_startup设置eip为0,cs_base为AP_BOOT_ADDR
qiuhanty 回复于:2007-11-25 13:26:08 我们来看pci_bios_init: void pci_bios_init(void) { pci_bios_io_addr = 0xc000; pci_bios_mem_addr = 0xf0000000; pci_bios_bigmem_addr = ram_size; if (pci_bios_bigmem_addr < 0x90000000) pci_bios_bigmem_addr = 0x90000000; pci_for_each_device(pci_bios_init_bridges); pci_for_each_device(pci_bios_init_device); } 它负责对pci总线上的bridge和device进行初始化。 void pci_for_each_device(void (*init_func)(PCIDevice *d)) { PCIDevice d1, *d = &d1; int bus, devfn; uint16_t vendor_id, device_id; for(bus = 0; bus < 1; bus++) { for(devfn = 0; devfn < 256; devfn++) { d->bus = bus; d->devfn = devfn; vendor_id = pci_config_readw(d, PCI_VENDOR_ID); device_id = pci_config_readw(d, PCI_DEVICE_ID); if (vendor_id != 0xffff || device_id != 0xffff) { init_func(d); } } } } 我们只有一条pci总线。由于现在我们不知道总线上有什么,所以我们就对可能的256个功能号进行穷举,分别读取PCI_VENDOR_ID和PCI_DEVICE_ID.如果该功能号真的存在,则调用初始化函数(先是pci_bios_init_bridges,而后是pci_bios_init_device)。那么为什么不能在同一个循环中依次调用pci_bios_init_bridges和pci_bios_init_device呢?[FIXME] static void pci_bios_init_bridges(PCIDevice *d) { uint16_t vendor_id, device_id; vendor_id = pci_config_readw(d, PCI_VENDOR_ID); device_id = pci_config_readw(d, PCI_DEVICE_ID); if (vendor_id == 0x8086 && device_id == 0x7000) { int i, irq; uint8_t elcr[2]; /* PIIX3 bridge */ elcr[0] = 0x00; elcr[1] = 0x00; for(i = 0; i < 4; i++) { irq = pci_irqs; /* set to trigger level */ elcr[irq >> 3] |= (1 << (irq & 7)); /* activate irq remapping in PIIX */ pci_config_writeb(d, 0x60 + i, irq); } outb(0x4d0, elcr[0]); outb(0x4d1, elcr[1]); BX_INFO("PIIX3 init: elcr=%02x %02x\n", elcr[0], elcr[1]); } else if (vendor_id == 0x8086 && device_id == 0x1237) { /* i440 PCI bridge */ bios_shadow_init(d); } } 这里简单的支持了2种bridge, PIIX3是PCI-to-ISA bridge,i440FX是PCI bridge。 由于isa的中断是边缘触发(trigger edge),而pci规范要求pci的中断必须是level sensitive, 所以这里有一个elcr。在BSD源码的init386中会调用elcr_probe来检测。 static uint8_t pci_irqs[4] = { 11, 9, 11, 9 } 对这几个irq号使用trigger level pci_bios_init_device函数比较长,主要是通过设置设备的配置寄存器来使能设备或者为设备分配总线资源,包括内存段, 中断线。 static void pci_bios_init_device(PCIDevice *d) { int class; uint32_t *paddr; int i, pin, pic_irq, vendor_id, device_id; class = pci_config_readw(d, PCI_CLASS_DEVICE); vendor_id = pci_config_readw(d, PCI_VENDOR_ID); device_id = pci_config_readw(d, PCI_DEVICE_ID); BX_INFO("PCI: bus=%d devfn=0x%02x: vendor_id=0x%04x device_id=0x%04x\n", d->bus, d->devfn, vendor_id, device_id); switch(class) { case 0x0101://代表IDE controller[5] if (vendor_id == 0x8086 && device_id == 0x7010) { /* PIIX3 IDE */ pci_config_writew(d, 0x40, 0x8000); // enable IDE0 pci_config_writew(d, 0x42, 0x8000); // enable IDE1 goto default_map; } else { /* IDE: we map it as in ISA mode */ pci_set_io_region_addr(d, 0, 0x1f0); pci_set_io_region_addr(d, 1, 0x3f4); pci_set_io_region_addr(d, 2, 0x170); pci_set_io_region_addr(d, 3, 0x374); } break; case 0x0300://Display controller[5] if (vendor_id != 0x1234) goto default_map; /* VGA: map frame buffer to default Bochs VBE address */ pci_set_io_region_addr(d, 0, 0xE0000000); break; case 0x0800://Base system peripherals[5] /* PIC */ if (vendor_id == 0x1014) { /* IBM */ if (device_id == 0x0046 || device_id == 0xFFFF) { /* MPIC & MPIC2 */ pci_set_io_region_addr(d, 0, 0x80800000 + 0x00040000); } } break; case 0xff00://Misc[5] if (vendor_id == 0x0106b && (device_id == 0x0017 || device_id == 0x0022)) { /* macio bridge */ pci_set_io_region_addr(d, 0, 0x80800000); } break; default: default_map: /* default memory mappings */ for(i = 0; i < PCI_NUM_REGIONS; i++) { int ofs; uint32_t val, size ; if (i == PCI_ROM_SLOT) ofs = 0x30; else ofs = 0x10 + i * 4; pci_config_writel(d, ofs, 0xffffffff);//pci22:p224 val = pci_config_readl(d, ofs); if (val != 0) { size = (~(val & ~0xf)) + 1; if (val & PCI_ADDRESS_SPACE_IO) paddr = &pci_bios_io_addr; else if (size >= 0x04000000) paddr = &pci_bios_bigmem_addr; else paddr = &pci_bios_mem_addr; *paddr = (*paddr + size - 1) & ~(size - 1); pci_set_io_region_addr(d, i, *paddr); *paddr += size; } } break; } /* map the interrupt */ pin = pci_config_readb(d, PCI_INTERRUPT_PIN); if (pin != 0) { pin = pci_slot_get_pirq(d, pin - 1); pic_irq = pci_irqs[pin]; pci_config_writeb(d, PCI_INTERRUPT_LINE, pic_irq); } if (vendor_id == 0x8086 && device_id == 0x7113) { /* PIIX4 Power Management device (for ACPI) */ pm_io_base = PM_IO_BASE; pci_config_writel(d, 0x40, pm_io_base | 1); pci_config_writeb(d, 0x80, 0x01); /* enable PM io space */ smb_io_base = SMB_IO_BASE; pci_config_writel(d, 0x90, smb_io_base | 1); pci_config_writeb(d, 0xd2, 0x09); /* enable SMBus io space */ pm_sci_int = pci_config_readb(d, PCI_INTERRUPT_LINE); #ifdef BX_USE_SMM smm_init(d); #endif acpi_enabled = 1; } } pci配置寄存器中含有6个Base Address Registers(pci22:p221,ch6.2.5),起始地址为0x10 ,连续分布;还有一个Expansion ROM Base Address Register(pci22:p224),起始地址为0x30 系统利用这些寄存器为PCI接口芯片的配置寄存器分配一段PCI地址空间,通过这段地址我们可以以内存映射或者I/O的形式访问PCI接口芯片的配置寄存器[6]。我们可以通过向该寄存器写入0xffffffff再读出,在经过size = (~(val & ~0xf)) + 1运算得到该空间的大小(pci22:p224)。 pin = pci_config_readb(d, PCI_INTERRUPT_PIN);得到的是本功能号连接的中断引脚号, 然后加上设备号模4加1得到pci_irqs数组的下标,进而得到系统的irq号。这是为了平衡irq的使用。例如同是连接在不同设备上的INTA的功能使用的irq很可能不同。最后是对piix4_pm的处理。 static int pci_slot_get_pirq(PCIDevice *pci_dev, int irq_num) { int slot_addend; slot_addend = (pci_dev->devfn >> 3) - 1; return (irq_num + slot_addend) & 3; } 我们回到rombios32_init接着看。对于APIC有两种枚举(enumerator)方式:mptable(1)和madt(acpi),本文是给教授做铺垫,所以我们忽略mptable_init,而介绍acpi_bios_init。为了简明,我们省略了一下代码。 void acpi_bios_init(void) { /* reserve memory space for tables */ #ifdef BX_USE_EBDA_TABLES ebda_cur_addr = align(ebda_cur_addr, 16); rsdp = (void *)(ebda_cur_addr); ebda_cur_addr += sizeof(*rsdp); #else bios_table_cur_addr = align(bios_table_cur_addr, 16); rsdp = (void *)(bios_table_cur_addr); bios_table_cur_addr += sizeof(*rsdp); #endif 前面我们说过,查找rsdp(Root System Description Pointer)的方法有两种,一种是通过ebda,另外一种是在0xE0000-0xFFFFF中查找(ACPIspec30b:p108,AcpiTbFindRsdp) 这里就是根据宏的定义与非来进行的选择。 addr = base_addr = ram_size - ACPI_DATA_SIZE; rsdt_addr = addr; rsdt = (void *)(addr); addr += sizeof(*rsdt); 这里把内存中的最后64k分配给acpi,假设内存大小为0x8000000(128MB),那么rsdt的起始地址为0x7ff0000, 这就是我们在《init386》getmemsize中看到内存的上限为0x7ff0000的原因。 注意:这里仅仅是本bios如此实现,并不是所有的bios都会这么做。 fadt_addr = addr; fadt = (void *)(addr); addr += sizeof(*fadt); 接着,为fadt分配空间。 /* XXX: FACS should be in RAM */ addr = (addr + 63) & ~63; /* 64 byte alignment for FACS */ facs_addr = addr; facs = (void *)(addr); addr += sizeof(*facs); 为facs分配空间。 dsdt_addr = addr; dsdt = (void *)(addr); addr += sizeof(AmlCode); 为dsdt分配空间。这里的AmlCode是一个字符数组,它定于于: bochs-2.3.5/bios/acpi-dsdt.hex 而该文件是使用iasl(8)对同目录下的acpi-dsdt.dsl编译的结果 addr = (addr + 7) & ~7; madt_addr = addr; madt_size = sizeof(*madt) + sizeof(struct madt_processor_apic) * smp_cpus + sizeof(struct madt_io_apic); madt = (void *)(addr); addr += madt_size; 为madt分配内存。 acpi_tables_size = addr - base_addr; /* RSDP */ memset(rsdp, 0, sizeof(*rsdp)); memcpy(rsdp->signature, "RSD PTR ", 8); #ifdef BX_QEMU memcpy(rsdp->oem_id, "QEMU ", 6); #else memcpy(rsdp->oem_id, "BOCHS ", 6); #endif rsdp->rsdt_physical_address = cpu_to_le32(rsdt_addr); rsdp->checksum = acpi_checksum((void *)rsdp, 20); 设置RSDP的内容,最重要的是标识"RSD PTR "和rsdt的物理地址。 接下来设置FADT和FACS,可参照ACPIspec30b阅读。 /* DSDT */ memcpy(dsdt, AmlCode, sizeof(AmlCode)); 把编译后的AmlCode拷贝到DSDT。 /* MADT */ { struct madt_processor_apic *apic; struct madt_io_apic *io_apic; memset(madt, 0, madt_size); madt->local_apic_address = cpu_to_le32(0xfee00000); madt->flags = cpu_to_le32(1); apic = (void *)(madt + 1); for(i=0;i<smp_cpus;i++) { apic->type = APIC_PROCESSOR; apic->length = sizeof(*apic); apic->processor_id = i; apic->local_apic_id = i; apic->flags = cpu_to_le32(1); apic++; } io_apic = (void *)apic; io_apic->type = APIC_IO; io_apic->length = sizeof(*io_apic); io_apic->io_apic_id = smp_cpus; io_apic->address = cpu_to_le32(0xfec00000); io_apic->interrupt = cpu_to_le32(0); acpi_build_table_header((struct acpi_table_header *)madt, "APIC", madt_size, 1); } 设置MADT,比较重要的是local_apic_address(0xfee00000)和ioapic address(0xfec00000) 行文至此,不知有没有说清楚,其实自己的了解也很有限。 [1]http://www.frontiernet.net/~fys/rombios.htm [2]http://www.bioscentral.com/misc/bda.htm [3]Advanced Configuration and Power Interface Specification Revision 3.0b October 10, 2006 [4]PCI Local Bus Specification Revision 2.2 December 18, 1998 [5]Class Code Table http://www.acm.uiuc.edu/sigops/roll_your_own/7.c.1.html [6]PCI设备Windows NT平台驱动程序设计 http://blog.donews.com/rightwind/archive/2005/09/07/544174.aspx |