1. /* 
  2.  * Prepare the machine for transition to protected mode. 
  3.  * 从实模式向保护模式跳转 
  4.  * 文档: 
  5.  * /arch/x86/include/asm/segment.h 【1】 
  6.  * arch/x86/boot/pm.c【2】本文 
  7.  * arch/x86/boot/a20.c【3】 
  8.  */  
  9.   
  10. #include "boot.h"  
  11. #include <asm/segment.h>  
  12.   
  13. /* 
  14. * Invoke the realmode switch hook if present; otherwise 
  15. * disable all interrupts. 
  16. * 
  17. * boot loader hooks 
  18. * 当加载器运行的环境不能布置标准内存布局时,会用到加载器hooks。这种hooks尽量不要用。 
  19. * realmode_swtch:在转到保护模式前最后时刻执行,它是16位的(默认的例程是用来禁用NMI, 
  20. * 不可屏蔽中断)。 
  21. * code32_start : 这个字段可被用作hook,在转到保护模式后第一时刻执行,并且是32位的, 
  22. * 它的执行在内核解压前。实模式开始偏移512处是内核的开始。此时ds=es=ss=实模式代码加 
  23. * 载地址。推荐是fs=gs=ds=es=ss。一般情况下加载器会将它指向正常的、被压缩内核代码处。 
  24. */  
  25. static inline void io_delay(void)  
  26. {     
  27.     //boot.h:向0x80端口写al值来消耗时间  
  28.     const u16 DELAY_PORT = 0x80;  
  29.     asm volatile("outb %%al,%0" : : "dN" (DELAY_PORT));  
  30. }  
  31. static void realmode_switch_hook(void)  
  32. {  
  33.     /* 
  34.      * 调用hook,一般hook是不会设置的,所以走else流程 
  35.      * 向0x70商品写0x80关NMI 
  36.      */  
  37.     if (boot_params.hdr.realmode_swtch) {  
  38.         asm volatile("lcallw *%0"  
  39.             : : "m" (boot_params.hdr.realmode_swtch)  
  40.             : "eax""ebx""ecx""edx");  
  41.     } else {  
  42.         asm volatile("cli");  
  43.         outb(0x80, 0x70); /* Disable NMI */  
  44.         io_delay();  
  45.     }  
  46. }  
  47.   
  48. /* 
  49.  * Disable all interrupts at the legacy PIC. 
  50.  * PIC相关 
  51.  * 端口范围:0x20-0x3f   8259  可编程中断控制器1 
  52.  * 端口范围:0xa0-0xbf   8259  可编程中断控制器2 
  53.  *  
  54.  */  
  55. static void mask_all_interrupts(void)  
  56. {     
  57.     outb(0xff, 0xa1);       /* Mask all interrupts on the secondary PIC */  
  58.     io_delay();  
  59.     outb(0xfb, 0x21);       /* Mask all but cascade on the primary PIC */  
  60.     io_delay();  
  61. }  
  62.   
  63. /* 
  64.  * Reset IGNNE# if asserted in the FPU. 
  65.  * 端口范围:0x0f0-0x0ff 数学协处理器访问端口 
  66.  */  
  67. static void reset_coprocessor(void)  
  68. {  
  69.     outb(0, 0xf0);  
  70.     io_delay();  
  71.     outb(0, 0xf1);  
  72.     io_delay();  
  73. }  
  74.   
  75. /* 
  76. * Set up the GDT 
  77. */  
  78.   
  79. struct gdt_ptr {  
  80.     u16 len;    // 0-15:GDT Lmit     大小  
  81.     u32 ptr;    //16-47:Base address 基址  
  82. } __attribute__((packed));  
  83. /* 
  84.  * 启动时使用的gdt 
  85.  * 仅设置了3项cs,ds,tss,代码段cs和数据段ds的段基址都设置为0 
  86.  * 任务状态段段基址设置为4096 
  87.  * 由文档【1】中可知:   
  88.  *  GDT_ENTRY_BOOT_CS=2 
  89.  *  GDT_ENTRY_BOOT_DS=3 
  90.  *  GDT_ENTRY_BOOT_TSS=4 
  91.  * 这些值表示在表中的索引,分别处于临时gdt的索引2、3、4项 
  92.  * 分别有__BOOT_CS=16,__BOOT_DS=24,__BOOT_TSS=32等 
  93.  * 段选择子与段描述符对应 
  94.  */  
  95. static void setup_gdt(void)  
  96. {  
  97.     /* There are machines which are known to not boot with the GDT 
  98.     being 8-byte unaligned.  Intel recommends 16 byte alignment. */  
  99.     static const u64 boot_gdt[] __attribute__((aligned(16))) = {  
  100.         /* CS: code, read/execute, 4 GB, base 0 */  
  101.         [GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),  
  102.         /* DS: data, read/write, 4 GB, base 0 */  
  103.         [GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),  
  104.         /* TSS: 32-bit tss, 104 bytes, base 4096 */  
  105.         /* We only have a TSS here to keep Intel VT happy; 
  106.         we don't actually use it for anything. */  
  107.         [GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103),  
  108.     };  
  109.     /* Xen HVM incorrectly stores a pointer to the gdt_ptr, instead 
  110.     of the gdt_ptr contents.  Thus, make it static so it will 
  111.     stay in memory, at least long enough that we switch to the 
  112.     proper kernel GDT. */  
  113.     static struct gdt_ptr gdt;  
  114.   
  115.     gdt.len = sizeof(boot_gdt)-1;  
  116.     gdt.ptr = (u32)&boot_gdt + (ds() << 4);  
  117.     //通过lgdtl将48位的gdt放入gdtr寄存器  
  118.     asm volatile("lgdtl %0" : : "m" (gdt));  
  119. }  
  120.   
  121. /* 
  122. * Set up the IDT 
  123. * 中断描述符表初始化为0,先不使用 
  124. */  
  125. static void setup_idt(void)  
  126. {  
  127.     static const struct gdt_ptr null_idt = {0, 0};  
  128.     asm volatile("lidtl %0" : : "m" (null_idt));  
  129. }  
  130.   
  131. /* 
  132.  * Actual invocation sequence 
  133.  * 这个函数是从boot/main()跳过来执行的,它是pm.c中最主要的函数,它控制着保护模式 
  134.  * 相关函数的调用顺序。它的主要工作有: 
  135.  * 1.在离开实模式的最后时刻调用加载器hook 
  136.  * 2.打开A20Gate 
  137.  * 3.重置数学协处理器 
  138.  * 4.屏蔽所有中断 
  139.  * 5.设置idt 
  140.  * 6.设置gdt 
  141.  * 7.执行protected_mode_jump(code32_start hook) 
  142.  * 对于code32_start(document/x86/boot.txt),前面说过,它也能作为加载器hook,只是它是在进入保护模式第一时间 
  143.  * 执行的。但一般情况下,加载器会把它指向未解压的内核地址,hook比较少用。 
  144.  */  
  145. void go_to_protected_mode(void)  
  146. {  
  147.     /* Hook before leaving real mode, also disables interrupts */  
  148.     realmode_switch_hook();  
  149.   
  150.     /* Enable the A20 gate */  
  151.     if (enable_a20()) {  
  152.         puts("A20 gate not responding, unable to boot...\n");  
  153.         die();  
  154.     }  
  155.   
  156.     /* Reset coprocessor (IGNNE#) */  
  157.     reset_coprocessor();  
  158.   
  159.     /* Mask all interrupts in the PIC */  
  160.     mask_all_interrupts();  
  161.   
  162.     /* Actual transition to protected mode... */  
  163.     setup_idt();  
  164.     setup_gdt();  
  165.     /* 
  166.      * boot_params在boot/main.c中定义 
  167.      * ds从header.S到现在还没变,还是指向内核加载基址X所在的段。 
  168.      * ds<<4+boot_params就是为boot_params找到实际的地址。 
  169.      * 内核中的函数都是fastcall的,则参数1被放在eax中,参数2被放入edx中。 
  170.      */  
  171.     protected_mode_jump(boot_params.hdr.code32_start,  
  172.         (u32)&boot_params + (ds() << 4));  
  173. }  
  174. ////////////////////////////////a20.c//////////////////////////////////////////  
  175. /* 
  176.  * Enable A20 gate (return -1 on failure) 
  177.  * 在80286以前,intel的CPU只有20条地址线,可访问最高地址F000:FFFF=FFFFF,这种情况 
  178.  * 下20根地址线全1。80286时,有24条地址线,可访问FFFF:FFFF=10FFEFh地址(地址格式没 
  179.  * 变,只是范围大了),1M以上是extend memory,1M~10FFEFh为high memory。在80268以前 
  180.  * 如果给的地址大于F000:FFFF,如FFFF:FFFF=10FFEFh,那么因为地址线只有20根,高于20 
  181.  * 的地址部分将被弃掉,所以访问高于FFFF:FFFF实际上还是访问0FFEF。20根地址线最高 
  182.  * 是1M内存地址,所以要访问逻辑地址高于1M,都会访问1M以下的地址,这种情况被称作是 
  183.  * wraparound。而80286却能访问1M以上的地址。在80286实模式访问高端地址与8086不 
  184.  * 一致,这像是一个bug。所以为了它们一致,人们引入了A20Gate:利用键盘控制器8042空 
  185.  * 出的一个引脚和第20根地址线相与,以控制第20根。在80286实模式时,键盘控制器的这 
  186.  * 个引脚是0。 
  187.  * 注意一下,逻辑地址最高是FFFF:FFFF=FFFF0+FFFF=10FFEFh,在80286实模式下,就算访 
  188.  * 问这个地址,因为它被人为地置0了,就与8086一样了。最高4位,由于逻辑地址限制,最 
  189.  * 大值是0001,所以只要控制最高4位的的最低1位(A20)即可,因最高3位超出了逻辑地址 
  190.  * 表达的范围,它们永远是0。当然,80286以后的CPU也保留着实模式(与8088)的一致性, 
  191.  * 所以A20Gate一直存在。 
  192.  * 但对于80286及其以后的CPU,有了保护模式,寻址范围变大,如32位的CPU,20位以上的 
  193.  * 地址不再是仅有第20位是1、其它位是0了。而A20又成了遗留问题,进保护模式后,如果 
  194.  * A20还保持着实模式时的值---0,那么会导致一个地址范围找不到。所以在切换到实 
  195.  * 模式之间需要将A20Gate置为1。 
  196.  * A20打开的方法不限于8042一种,随着技术的发展,厂商设计出了若干种打开A20的方法 
  197.  * 下面会用到8042、0x92端口、bois(int15 ax=240x) 
  198.  */  
  199.     
  200. #include "boot.h"  
  201.   
  202. #define MAX_8042_LOOPS  100000  
  203. #define MAX_8042_FF     32  
  204.   
  205.   
  206. /* 
  207.  * 0x64:状态寄存器、命令寄存器 
  208.  * 0x60:数据寄存器 
  209.  * 如果要读8042状态,直接读0x64端口,如果要写则将命令写到0x64,将参数 
  210.  * 写到0x60,然后再读0x60端口取得返回值。 
  211.  * 0x60 R   读输出缓冲区 
  212.  * 0x60 W   写输入缓冲区(8042 Data&8048 Command) 
  213.  * 0x64 R   读状态寄存器 
  214.  * 0x64 W   写输入缓冲区(8042 Command) 
  215.  * 端口0x64读出的状态(8位):  
  216.  * 0 0x60端口有数据,系统应该将它读出 
  217.  * 1 在输入寄存器中对8042有输入,来自0x60或0x64 
  218.  * 2 0:reset 
  219.  * 3 输入寄存器中的值是命令(1)还是数据(0) 
  220.  * 4 键盘使能(1)禁用(0) 
  221.  * 5 传输超时 
  222.  * 6 接收超时 
  223.  * 7 奇偶校验位 1=偶,0=奇,应该是奇 
  224.  */  
  225. static int empty_8042(void)  
  226. {  
  227.     u8 status;  
  228.     int loops = MAX_8042_LOOPS;  
  229.     int ffs   = MAX_8042_FF;  
  230.   
  231.     while (loops--) {  
  232.         io_delay();  
  233.   
  234.         status = inb(0x64);  
  235.         if (status == 0xff) {  
  236.             /* FF is a plausible, but very unlikely status */  
  237.             //返回ff说明8042控制器可能不存在  
  238.             if (!--ffs)  
  239.                 return -1; /* Assume no KBC present */  
  240.         }  
  241.         /* 
  242.          * 最低位是1说明60h端口有数据,将其读出       
  243.          */  
  244.         if (status & 1) {  
  245.             /* Read and discard input data */  
  246.             io_delay();  
  247.             (void)inb(0x60);  
  248.         } else if (!(status & 2)) {  
  249.             /* Buffers empty, finished! */  
  250.             /* 
  251.              * if句过滤最低位1,到此说明没有数据待读,最低位=0 
  252.              * 此句又过滤掉bit1的1,那status最低2位应该是00 
  253.              * 00表示0x60端口既没数据待读,系统也没往8042输入 
  254.              * 数据或命令,8042empty了。 
  255.              */  
  256.             return 0;  
  257.         }  
  258.     }  
  259.   
  260.     return -1;  
  261. }  
  262.   
  263. /* Returns nonzero if the A20 line is enabled.  The memory address 
  264. used as a test is the int $0x80 vector, which should be safe. */  
  265.   
  266. #define A20_TEST_ADDR   (4*0x80)  
  267. #define A20_TEST_SHORT  32  
  268. #define A20_TEST_LONG   2097152 /* 2^21 */  
  269.   
  270. static int a20_test(int loops)  
  271. {  
  272.     int ok = 0;  
  273.     int saved, ctr;  
  274.   
  275.     set_fs(0x0000);  
  276.     set_gs(0xffff);  
  277.     /* 
  278.      * fs:A20_TEST_ADDR     =00000+4*0x80    =0000:0200 
  279.      * gs:A20_TEST_ADDR+0x10=ffff0+4*0x80+0x10=0x100200 
  280.      * wrfs32向fs写32位值,rdfs32从fs:addr处读出值 
  281.      * wrfs32,rdgs32类似 
  282.      * 
  283.      * 上面说过80286实模式的wraparound现象,就是访问高端地址 
  284.      * 时,由于A20的原因,最高位的1会被抛弃。那么,上面两个地 
  285.      * 址,如果A20没打开的时候,读出的数据应该是相同的,异或时 
  286.      * 结果等于0(ok=0)。对于下面的代码,分别从这两个地址读数 
  287.      * 据,如果异或不是0,说明A20已经打开ok!=0,如果ok=0,说明 
  288.      * A20没打开。当ok!=0时,会试loops次,确保不是偶然现象,代 
  289.      * 码会要求loops的每一次都保证ok!=0(A20开启)。 
  290.      */  
  291.     saved = ctr = rdfs32(A20_TEST_ADDR);  
  292.   
  293.     while (loops--) {  
  294.         wrfs32(++ctr, A20_TEST_ADDR);  
  295.         io_delay();     /* Serialize and make delay constant */  
  296.         ok = rdgs32(A20_TEST_ADDR+0x10) ^ ctr;  
  297.         if (ok)  
  298.             break;  
  299.     }  
  300.     //将rdfs32处的值恢复原状  
  301.     wrfs32(saved, A20_TEST_ADDR);  
  302.     return ok;  
  303. }  
  304.   
  305. /* 
  306.  * Quick test to see if A20 is already enabled  
  307.  * 用一个较短的循环来探测 
  308.  */  
  309. static int a20_test_short(void)  
  310. {  
  311.     return a20_test(A20_TEST_SHORT);  
  312. }  
  313.   
  314.   
  315.   
  316.   
  317. /*  
  318.  * Longer test that actually waits for A20 to come on line; this 
  319.  * is useful when dealing with the KBC or other slow external circuitry.  
  320.  * 用一个较长的循环来探测,当用慢速设备(8042)打开A20时,需要用这种方法探测 
  321.  */  
  322. static int a20_test_long(void)  
  323. {  
  324.     return a20_test(A20_TEST_LONG);  
  325. }  
  326. /* 
  327.  * int15也可用来打开A20, 
  328.  * ax=0x2401 禁用 
  329.  * ax=0x2402 启用 
  330.  * ax=0x2403 查询A20状态 
  331.  * ax=0x2404 查询A20支持情况(8042还是0x92端口) 
  332.  */  
  333. static void enable_a20_bios(void)  
  334. {  
  335.     struct biosregs ireg;  
  336.   
  337.     initregs(&ireg);  
  338.     ireg.ax = 0x2401;  
  339.     intcall(0x15, &ireg, NULL);  
  340. }  
  341.   
  342. static void enable_a20_kbc(void)  
  343. {  
  344.     empty_8042();  
  345.   
  346.     outb(0xd1, 0x64);       /* Command write写命令 */  
  347.     empty_8042();  
  348.   
  349.     outb(0xdf, 0x60);       /* A20 on,在0x60端口写入命令值(开A20Gate)*/  
  350.     empty_8042();  
  351.   
  352.     outb(0xff, 0x64);       /* Null command, but UHCI wants it */  
  353.     empty_8042();  
  354. }  
  355. /* 
  356.  * 0x92端口是快速的A20Gate,当系统没有键盘控制器时,就用这个端口 
  357.  * bit 1:1,enable; 0,disable 
  358.  * bit 0:1 reset;  0,no reset 
  359.  */  
  360. static void enable_a20_fast(void)  
  361. {  
  362.     u8 port_a;  
  363.     port_a = inb(0x92);     /* Configuration port A */  
  364.     port_a |=  0x02;        /* Enable A20  */  
  365.     port_a &= ~0x01;        /* Do not reset machine */  
  366.     outb(port_a, 0x92);  
  367. }  
  368.   
  369. /* 
  370. * Actual routine to enable A20; return 0 on ok, -1 on failure 
  371. */  
  372.   
  373. #define A20_ENABLE_LOOPS 255    /* Number of times to try */  
  374.   
  375. /* 
  376.  * 这个函数会用三种方法来尝试打开A20Gate有一个能打开就成 
  377.  */  
  378. int enable_a20(void)  
  379. {  
  380.     int loops = A20_ENABLE_LOOPS;  
  381.     int kbc_err;  
  382.   
  383.     while (loops--) {  
  384.         /* First, check to see if A20 is already enabled 
  385.         (legacy free, etc.) */  
  386.         if (a20_test_short())  
  387.             return 0;  
  388.   
  389.         /* Next, try the BIOS (INT 0x15, AX=0x2401) */  
  390.         enable_a20_bios();  
  391.         if (a20_test_short())  
  392.             return 0;  
  393.   
  394.         /* Try enabling A20 through the keyboard controller */  
  395.         kbc_err = empty_8042();  
  396.   
  397.         //这句测试是防止bios打开A20的延时  
  398.         if (a20_test_short())  
  399.             return 0; /* BIOS worked, but with delayed reaction */  
  400.   
  401.         if (!kbc_err) {  
  402.             enable_a20_kbc();  
  403.             if (a20_test_long())  
  404.                 return 0;  
  405.         }  
  406.   
  407.         /* Finally, try enabling the "fast A20 gate" */  
  408.         enable_a20_fast();  
  409.         if (a20_test_long())  
  410.             return 0;  
  411.     }  
  412.   
  413.     return -1;  
  414. }  
  415.   
  416. //////////////////////////////////////////////////////////////////////////  

相关文章:

  • 2022-12-23
  • 2021-04-13
  • 2022-03-10
  • 2022-12-23
  • 2022-12-23
  • 2021-11-09
  • 2021-07-21
  • 2022-12-23
猜你喜欢
  • 2021-11-13
  • 2022-01-08
  • 2022-12-23
  • 2022-12-23
  • 2021-11-25
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案