bios分析

 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

代码部分来自Google的重建IBMPC BIOS项目(https://sites.google.com/site/pcdosretro/ibmpcbios),其中的BIOS镜像(*.rom)可用于各种IBM PC模拟器,可按情况使用。源代码可以用masm编译,站内英文说明文件如下: IBM PC BIOS source code reconstruction This is a reconstruction of the IBM PC, PC XT, PC AT and PC XT 286 BIOS source code using scanning and transcription of the BIOS listings found in the IBM Technical Reference manuals. This historically relevant source code is presented here for software preservation. The following BIOS source code has been reconstructed: IBM PC version 1 04/21/81 IBM PC version 2 10/19/81 IBM PC version 3 10/27/82 IBM PC XT version 1 11/08/82 (also used on the Portable PC) IBM PC XT version 2 01/10/86 IBM PC XT version 3 05/09/86 IBM PC AT version 1 01/10/84 IBM PC AT version 2 06/10/85 IBM PC AT version 3 11/15/85 (used on the PC AT models 319 and 339) IBM PC XT 286 04/21/86 Notes: • All 3 versions of the IBM PC BIOS and the first version of the IBM PC XT BIOS were built using Intel ASM86 on an Intel development system. In each case the BIOS source code is a single large file and the BIOS code is 8KB which resides at F000:E000 • The IBM PC AT version 1 BIOS was built using IBM MASM 1.0 on DOS. This is the first IBM BIOS which uses multiple source files. Since IBM MASM 1.0 did not support the 80286 there is a macro file (IAPX286.MAC) which is used to generate the necessary opcodes. This is also the first BIOS to be split into two parts: the main BIOS code resides at F000:0000 and the compatibility section (ORGS.ASM) resides at F000:E000. An additional file FILL.ASM has been added to define the area between the end of the main BIOS code and the compatibility section to allow the BIOS to be linked properly. It is currently unknown how this was originally handled. • The IBM PC AT version 2 and 3 BIOS and the IBM PC XT 286 BIOS were built using IBM MASM 2.0 on DOS. These are similar to the PC AT version 1 BIOS but there are fewer source files as some files were combined and a bit of cleanup was done. IAPX286.INC is used to generate the protected-mode 80286 opcodes which IBM MASM 2.0 did not support. FILL.ASM serves the same purpose as it does for the PC AT version 1 BIOS though in each case the file is specific to the particular BIOS being built. • The IBM PC XT version 2 and 3 BIOS were built using IBM MASM 2.0 on DOS. The later PC XT BIOS code was restructured to be similar to the PC AT BIOS code so there are multiple source files. Like the PC AT BIOS the code is split into two parts though the compatibility section is in the file POST.ASM. Again the additional file FILL.ASM is used to define the area between the end of the main BIOS code and the compatibility section. • The following code is present in all versions of the PC AT BIOS and the PC XT 286 BIOS but does not appear in the published listings. It is inferred from the public symbols in ORGS.ASM and code disassembly. It is unknown what purpose this code serves. .XLIST ;;- ORG 0FF5AH ORG 01F5AH HRD PROC FAR CALL DISK_SETUP RET HRD ENDP FLOPPY PROC FAR CALL DSKETTE_SETUP RET FLOPPY ENDP SEEKS_1 PROC FAR CALL SEEK RET SEEKS_1 ENDP TUTOR: JMP K16 .LIST • In all cases the 32KB ROM BASIC code which resides at F6000 is not available as its source code was never published. • Versions of MASM later than 4.0 cannot be used to build the IBM BIOS source code since older constructs and macros are used. More information about functionality changes in the IBM PC BIOS code is listed here: IBM PC BIOS version history
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值