This is a creation in Article, where the information may have evolved or changed.
/* * Prepare the transition to protected mode. * from the actual mode to protected mode * Document: */arch/x86/include/asm/segment.h "1" * arch/x86/boot/pm.c "2" this article * arch/x86/boot/a20.c "3" */#include "b Oot.h "#include <asm/segment.h>/** Invoke the realmode switch hook if present; otherwise* Disable all interrupts.** boot loader hooks* when the loader is running in an environment that cannot lay out a standard memory layout, the loader hooks is used. This kind of hooks try not to use. * Realmode_swtch: Last time before going to protected mode, it is 16 bits (the default routine is to disable the nmi,* non-blocking interrupt). * Code32_start: This field can be used as a hook, executed at the first time after the switch to protected mode, and is 32 bits, * its execution before the kernel decompression. Real mode starts at offset 512 at the beginning of the kernel. At this time ds=es=ss= real mode code plus * Load address. The recommendation is Fs=gs=ds=es=ss. In general, the loader will point it at normal, compressed kernel code. */static inline void Io_delay (void) {//boot.h: Writes an AL value to the 0x80 port to consume the time const u16 Delay_port = 0x80; ASM volatile ("Outb%%al,%0":: "DN" (Delay_port));} static void Realmode_switch_hook (void) {/* * calls the hook, the general hook is not set, so go to the else process * to 0x70 merchandise write 0x80-off nmi */if (boot_ Params.hdr.realmode_swtch) {asm volatile ("Lcallw *%0":: "M" (boot_params.hdr.realmode_swtch): "EAX", "ebx", "ecx", "edx ");} else {ASM volatile ("CLI"); Outb (0x80, 0x70); /* Disable NMI */io_delay ();}} /* * Disable all interrupts at the legacy PIC. * PIC-related * Port range: 0x20-0x3f 8259 Programmable Interrupt Controller 1 * port range: 0XA0-0XBF 8259 Programmable Interrupt Controller 2 * */static void mask_all_interrupts (void) {O UTB (0xFF, 0XA1); /* Mask all interrupts on the secondary PIC */io_delay (); Outb (0XFB, 0x21); /* Mask all but cascade on the primary PIC */io_delay ();} /* * Reset ignne# if asserted in the FPU. * Port range: 0X0F0-0X0FF math coprocessor access port */static void Reset_coprocessor (void) {OUTB (0, 0xf0); Io_delay (); Outb (0, 0xf1); Io_delay () ;} /** Set up the gdt*/struct gdt_ptr {U16 len;//0-15:gdt lmit size u32 ptr;//16-47:base address base} __attribute__ ((packed));/* * The GDT used at startup is set to only 3 Cs,ds,tss, and the segment base of the code snippet CS and data segment DS is set to 0 * The task status segment base address is set to 4096 * by the document "1": *gdt_entry_boot_cs=2 *gdt_entry_boot_ Ds=3 *gdt_entry_boot_tss=4 * These values represent indexes in the table, respectively, in the index 2, 3, 4 of the temporary GDT * respectively __boot_cs=16,__boot_ds=24,__boot_tss=32, etc. * Segment selector corresponds to segment descriptor */static void Setup_gdt (void) {/* There is machines which is known to ' boot with the gdtbeing 8-byte Unali GnEd. Intel recommends byte alignment. */static Const U64 boot_gdt[] __ATTRIBUTE__ ((aligned ()) = {/* Cs:code, Read/execute, 4 GB, base 0 */[gdt_entry_boot_cs ] = Gdt_entry (0xc09b, 0, 0XFFFFF),/* Ds:data, Read/write, 4 GB, base 0 */[gdt_entry_boot_ds] = gdt_entry (0xc093, 0, 0XFFF FF),/* tss:32-bit TSS, 104 bytes, base 4096 *//* We only has a TSS here to keep Intel VT happy;we don ' t actually use it For anything. */[GDT_ENTRY_BOOT_TSS] = Gdt_entry (0x0089, 4096, 103),};/* Xen HVM incorrectly stores a pointer to the GDT_PTR, insteadof The gdt_ptr contents. Thus, make it static so it willstay in memory, at least long enough, the we switch to Theproper kernel GDT. */static struct Gdt_ptr gdt;gdt.len = sizeof (BOOT_GDT) -1;gdt.ptr = (u32) &boot_gdt + (DS () << 4);//via LGDTL 48-bit G DT puts the GDTR register ASM volatile ("Lgdtl%0":: "M" (GDT));} /** Set up the idt* Interrupt descriptor descriptor is initialized to 0, first without using */static void Setup_idt (void) {static const struct Gdt_ptr Null_idt = {0, 0};asm Volati Le ("Lidtl%0":: "M" (Null_idt));} /* * Actual invocation sequence * This function jumps from Boot/main (), which is the most important function in PM.C, which controls the order in which protection mode * related functions are called. Its main work is: * 1. Call Loader hook * 2 when leaving real mode. Open A20gate * 3. Reset the math coprocessor * 4. Block all interrupts * 5. Set up IDT * 6. Set GDT * 7. Execute Protected_mode_jump (code 32_start Hook) * for Code32_start (Document/x86/boot.txt), as mentioned earlier, it can also be used as the loader hook, but it is in the first time to enter the protection mode * execution. But in general, the loader will point it to the uncompressed kernel address, the hook is less used. */void Go_to_protected_mode (void) {/* Hook before leaving real mode, also disables interrupts */realmode_switch_hook ();/* Enable the A20 Gate */if (enable_a20 ()) {puts ("A20 Gate not responding, unable to boot...\n");d ie (); /* Reset coprocessor (ignne#) */reset_coprocessor ();/* Mask all interrupts in the PIC */mask_all_interrupts ();/* Actual TR Ansition to Protected mode ... */setup_idt (); Setup_gdt ();/* * Boot_params defined in BOOT/MAIN.C * ds from header. S has not changed yet, or points to the kernel loading the segment where base x resides. * Ds<<4+boot_params is to find the actual address for boot_params. * The function in the kernel is fastcall, then parameter 1 is placed in eax, and parameter 2 is placed in edx. */protected_mode_jump (Boot_params.hdr.code32_start, (u32) &boot_params + (DS () << 4));} a20.c///////////////////////////////////////////* * Enable A20 Gate (return-1 on Failure) * Before 80286, Intel's CPU had only 20 address lines, with access to the highest address f000:ffff=fffff, in which case 20 address lines were all 1. 80286, there are 24 address lines, access to FFFF:FFFF=10FFEFH address (address format does not change, only the range is large), 1M or more is extend MEMORY,1M~10FFEFH for high memory. Before 80268 * If the address given is greater than f000:ffff, such as FFFF:FFFF=10FFEFH, then because the address line only 20, more than 20 * of the address portion will be discarded, so access is higher than ffff:ffff actually access 0FFEF. 20 Address Line Maximum * is 1 m memory address, so to access the logical address above 1M, will access the address below 1M, this situation is called * wraparound. and 80286 can access more than 1M address. In 80286 real mode access to the high-end address is not consistent with 8086, which is like a bug. So for them to be consistent, people introduced the A20gate: using the keyboard controller 8042 empty * out of a pin and the 20th Address line phase to control the 20th root. In 80286 real mode, this * pin of the keyboard controller is 0. * Note that the maximum logical address is FFFF:FFFF=FFFF0+FFFF=10FFEFH, in 80286 real mode, even if the interview * ask this address, because it was artificially set 0, and 80,861 like. Up to 4 bits, because of the logical address limit, the maximum value is 0001, so as long as the maximum 4 bits of the minimum 1 bits (A20) can be controlled, because the highest 3 bits beyond the logical address * expression range, they are always 0. Of course, the CPU after 80286 also retains the true pattern (with 8088) consistency, * so a20gate always exists. * But for 80286 and later CPUs, there is a protection mode, the addressing range becomes larger, such as the 32-bit cpu,20 bit more than the * address is no longer only the 20th bit is 1, the other bit is 0. And A20 has become a legacy problem, after the protection mode, if * A20 still maintain the true mode value---0, it will result in an address range can not be found. So you need to set A20gate to 1 between switching to real * mode. * A20 Open Method not limited to 80421 kinds, with the development of technology, the manufacturers have designed several ways to open A20 * below will use 8042, 0x92 port, Bois (int15 ax=240x) */#include "boot.h" #define MAX_8042_LOOPS 100000 #define MAX_8042_FF 32/* * 0x64: status Register, Command Register * 0x60: Data register * If you want to read 8042 status, read the 0x64 port directly, write the command to 0x64 if you want to write, write the parameter * to 0x60, and then read the 0x60 port Gets the return value. * 0x60r Read Output buffer * 0x60w Write input buffer (8042 data&8048 command) * 0x64r Read status register * 0x64w write input buffer (8042 Command) * Port 0x64 Readout status (8-bit): * 0 0X60 Port has data, the system should read it out * 1 in the input register to 8042 have input, from 0x60 or 0x64 * 2 0:reset * 3 Input Register value is the command (1) or data (0) * 4 keyboard enable (1) disabled (0) * 5 Transmission Timeout * 6 receive Super When * 7 parity bit 1= even, 0= odd, should be odd */static int empty_8042 (void) {U8 Status;int loops = max_8042_loops;int ffs = Max_8042_ff;while (loops--) {Io_delay (); status = INB (0x64), if (status = = 0xFF) {/* FF is a plausible, but very unlikely status *///return FF description 8042 controller may not exist I F (!--FFS) return-1; /* Assume no KBC present */}/* * Lowest bit is 1 description 60h port has data, read it out */if (status & 1) {/* Read and discard input data */io_delay (); (void) inb (0x60);} else if (! ( Status & 2)) {/* buffers empty, finished! *//* * If clause filter lowest bit 1, to this note no data to read, lowest bit =0 * This sentence also filtered out the Bit1 1, the status of the lowest 2 bits should be 00 * 00 means that the 0x60 port has no data to read, the system also did not go to 8042 input * data or command, 8042empty. */return 0;}} return-1;} /* Returns Nonzero if the A20 is enabled. The memory addressused as a test is the Int. $0x80 vector, which should be safe. */#define A20_TEST_ADDR (4*0x80) #define A20_test_short 32#define a20_test_long 2097152/* 2^21 */static int a20_test ( int loops) {int ok = 0;int saved, ctr;set_fs (0x0000); Set_gs (0XFFFF);/* * fs:a20_test_addr=00000+4*0x80 =0000:0200 * gs:a20 _test_addr+0x10=ffff0+4*0x80+0x10=0x100200 * Wrfs32 Write 32-bit value to FS, Rdfs32 read value from FS:ADDR * WRFS32,RDGS32 similar to * * Above said 80286 real mode wraparound phenomenon, is to visit high-end address *, because of A20, the highest bit of 1 will be discarded. So, the above two ground * address, if the A20 is not open, read the data should be the same, XOR * result is equal to 0 (ok=0). For the following code, read from these two addresses respectively * according to, if XOR is not 0, the description A20 has opened ok!=0, if ok=0, description * A20 not open. When Ok!=0, will try to loops times, to ensure that it is not accidental phenomenon, generation * code would require loops every time to ensure that ok!=0 (A20 open). */saved = CTR = Rdfs32 (A20_TEST_ADDR), while (loops--) {wrfs32 (++ctr, a20_test_addr); Io_delay (); /* Serialize and make delay constant */ok = rdgs32 (a20_test_addr+0x10) ^ ctr;if (OK)break;} Restore the value of the RDFS32 at the original wrfs32 (saved, a20_test_addr); return OK;} /* * Quick test to see if A20 is already enabled * uses a shorter loop to probe */static int a20_test_short (void) {return a20_test (a20_test _short);} /* * Longer test, actually waits for A20 to come on line; This * was useful when dealing with the KBC or other slow external circuitry. * With a long loop to detect, when using a slow device (8042) to open A20, it is necessary to use this method to detect */static int A20_test_long (void) {return a20_test (A20_test_long);} /* * int15 can also be used to open A20, * ax=0x2401 Disable * ax=0x2402 enable * ax=0x2403 query A20 status * ax=0x2404 query A20 support case (8042 or 0x92 port) */static void E Nable_a20_bios (void) {struct Biosregs ireg;initregs (&ireg); ireg.ax = 0x2401;intcall (0x15, &ireg, NULL);} static void Enable_a20_kbc (void) {empty_8042 (); Outb (0xd1, 0x64); /* command write to write commands */empty_8042 (); Outb (0XDF, 0x60); /* A20 on, write command value on 0x60 port (open a20gate) */empty_8042 (); Outb (0xFF, 0x64); /* Null command, but UHCI wants it */empty_8042 ();} /* * 0x92 port is fast a20gate, when the system does not have a keyboard controller, use this port * bit 1:1,enable; 0,disable * bit 0:1 reset; 0,no reset */static void Enable_a20_fast (void) {U8 port_a;port_a = INB (0x92); /* Configuration Port A */port_a |= 0x02; /* Enable A20 */port_a &= ~0x01; /* Don't reset machine */outb (port_a, 0x92);} /** Actual routine to enable A20; return 0 on OK, 1 on failure*/#define A20_ENABLE_LOOPS 255/* Number of times to try *//* * This function will attempt to open a20gate in three ways there is a Open as */int enable_a20 (void) {int loops = A20_enable_loops;int Kbc_err;while (loops--) {/* First, check to see if A20 is ALR Eady enabled (Legacy free, etc) */if (A20_test_short ()) return 0;/* Next, try the BIOS (INT 0x15, ax=0x2401) */enable_a20_b iOS (); if (A20_test_short ()) return 0;/* Try enabling A20 through the keyboard controller */kbc_err = empty_8042 ();//This test is to prevent The BIOS turns on the A20 delay if (A20_test_short ()) return 0; /* BIOS worked, but with delayed reaction */if (!kbc_err) {ENABLE_A20_KBC (); if (A20_test_long ()) return 0;} /* Finally, try enabling the "Fast A20 Gate" */enable_a20_fast (); if (A20_test_long ()) return0;} return-1;} //////////////////////////////////////////////////////////////////////////