Intel 80x86 processors - Assembler etc 8088 - 80286 80386-80486 The 80386 is probably the most significant of the group so this note is written mostly with that processor in mind The 80386 can operate in "real mode" or in "protected mode" In real mode 80386 appears to be a very powerful 8088. More on protected mode later. 80x86 registers and their functions 31 16 | 15 8 | 7 0 bit number | EAX | | AX | | | AH | AL | | EBX | | BX | | | BH | BL | | ECX | | CX | | | CH | CL | | EDX | | DX | | | DH | DL | Register name operations EAX,AX(AH,AL) accumulator ASCII adjust for addition/subtraction Convert byte to word/word to double word/double word to quad word Unsigned multiplication/division I/O fast shifts Signed divide Load/store flags Load/compare/store string operations Table look up translations EBX,BX(BH,BL) base pointer to a base address in data segment Table look up translations ECX,CX(CH,CL) count count values for repeats, shifts, rotations EDX,DX(DH,DL) data register word/double word multiplications/divisions Indirect I/O 31 16 | 15 0 bit number | ESP | | | SP | | EBP | | | BP | | ESI | | | SI | | EDI | | | DI | BP base pointer pointer to base address in stack segment SP stack pointer stack SI source index source string and index pointer DI destination index destination string and index pointer BP,SI,DI and SP were specialised in pre-80386 but can now be used as data operands, base addresses and index addresses. SP cannot be used as an index. (In the 80386 when used to hold real mode address information the length of the base was always 16bit - how this affects other, 80486 etc, CPU I don't know) CS code segment used with IP, the instruction pointer to find the next m/c code instruction at CS:IP DS data segment SS stack segment stack Used with SP as SS:SP ES extra segment FS extra segment (not present in 8088-80286) GS extra segment (not present in 8088-80286) IP instruction pointer instruction offset Flags 1. Carry flag CF or C. Set if an operation needs a carry or a borrow. Otherwise reset (set to 0) 2. Parity flag PF or P. Set if last operation contains an even number of bits set at 1. Otherwise reset 3. Auxiliary carry flag AF Set if carry or borrow occurs in high nibble low nibble (more below) 4. Zero flag ZF or Z Set if an operation finishes at 0 otherwise reset. 5. Sign flag SF. Copy of Most Significant Byte of a result. 6. Overflow flag OF When set indicates that the signed operation is out of range. Else reset. 7. Interrupt enable IF If set then CPU recognises maskable interrupts 8. Direction DF or D If set then auto decrementation takes place otherwise auto incrementation (for some instructions only) 9. Trap TF If set the CPU only does one instruction at a time. Used in debugging Instruction Examples AX The accumulator OUT 0x70, al ;output the value in al to port 70hex BX Used for temporary storage Used as a pointer to a data object e.g. MOV ax,[bx] ; load acc with the value at address bx It is then possible to increment bx to the next address CX Usually holds the repetition for loops MOV cx, 0x10 ; load cx with 16 decimal = 0x10 start: ; label OUT 0x70,al ; output the value in al through port 0x70 LOOP start ; decrement cx and go back to start DX Mainly used for temporary storage MUL bx ; multiply the contents of bx with the contents of ax. The result is held in dx (high order) AND ax (low order) BP Used like bx,cx and dx. Often bp serves as a pointer to the base of a stack frame. ss is assumed as the assigned segment register i.e. SS:BP is used for memory access. PUSH arg1 ; arg1 onto the stack PUSH arg2 ; and arg2 PUSH arg3 ; and arg 3 CALL sum etc etc Stack looks like this arg1 bp+8 -> arg2 bp+6 -> arg3 bp+4 -> IP bp+2 -> and BP gets put in here by the instruction ** below - bp=sp -> sum PROC NEAR ; this is an assembler directive ; as it is declared NEAR only 2 bytes are ; needed for the old IP (or, if you like, the return ; address) PUSH bp ** MOV bp,sp ; load stack into base pointer, sp MOV ax,[bp + 4] ; load acc with arg3 NB ss is assumed here so the address used is ss:bp+4 ADD ax,[bp + 6] ; add arg2 ADD ax,[bp + 8] ; add arg3 POP bp ; restore bp NB ss is assumed here so the address used is ss:sp RET ; return ENDP ; other part of assembler instruction SI and DI These registers can, of course, be used as temporary storage but the use they are "designed for" is shown in the example. SI points to the source of data to be moved. DI points to the destination of that data string DB 20 DUP ('a@b@c@d@e@f@g@h@i@j@') ; another assembler directive (NB the @ should be ASCII code 1 I cant reproduce that with this word processor) MOV ax,@data ; load string segment into ax MOV ds,ax MOV ax,0xb800 ; load segment of monochrome video RAM into ax CLD ; clear direction flag MOV cx, 0xa ; transfer 10 words of 2 bytes each (character + attribute) MOV si, OFFSET string ; OFFSET is another assembler directive. The address "string" is split into segment + offset (see below) MOV di, 0x00 ; output string in upper left corner of screen REP MOVSW ; ten transfers of one word each REP MOVSW increases DI and SI by two after every transferred word. CLD resets the DF flag. If it is set then SI and DI would be decreased by two. STD sets the DF flag CS usually points to the segment of the program being run. This can be changed if a FAR call is used DS ditto but for data SS ditto but for the stack ES FS GS more segment registers ADDRESSING in 80x86 The address of any byte or word or whatever is split into two parts, a SEGMENT and an OFFSET. Needless to say the reason is historical (hysterical!). A segment consists of 64Kb (=65536bytes = 0x10000 bytes)of memory. The offset is the address of a particular memory location WITHIN THAT SEGMENT. Naturally it should never be greater than or equal to 65536. An address is calculated from address=0x10 * segment + offset ( address=16*segment + offset in decimal) The ONLY real reason for keeping this is backward compatibility. Examples 1F36:0A5D = 1F360 + 0A5D= 1FDBD (=130 493 decimal) 1FB1:02AD = 1FB10 + 02AD = 1FDBD Notice how the same address can be expressed in different segment:offset combinations. Example: CS contains 80B8 and the IP contains 019D. Thus the next instruction is collected from 80B8:019D = 80B80 + 019D = 80D1D A physical address can have many logical addresses e.g. 0x002b:0x0013 is 0x002B0 + 0x0013 = 0x02C3 0x002C:0x0003 is 0x002C0 + 0x0003 = 0x02C3 which is just the same memory location Stack:- The stack address is at SS:SP in real mode. The stack bottom is at SS:0xFFFE the top is at SS:SP and it can grow no bigger than to SS:0x00 (actually these addresss can be changed at will). Each time a word or double word is PUSHed onto the stack SP is decremented by two or four and the lowest part of the word/double word is copied onto the stack .Type of reference Segment used Register Used Default Selection Rule Instructions Code segment CS register Automatic with instruction fetch Stack Stack segment ESS register All stack PUSHes and POPs Any memory reference that uses ESP or EBP as base register Local data Data segment DS register All data references except when relative to the stack or string destination Destination strings E-Space segment ES register Destination of string instructions . . EXTENDED EXAMPLE The example below is the Linux loading code. I have left the original code with its comments (preceeded by a !) and added some of my own to help you see how the nemonics work. My comments are preceeded by a !! ! ! setup.S Copyright (C) 1991, 1992 Linus Torvalds ! ! setup.s is responsible for getting the system data from the BIOS, ! and putting them into the appropriate places in system memory. ! both setup.s and system has been loaded by the bootblock. ! ! This code asks the bios for memory/disk/other parameters, and ! puts them in a "safe" place: 0x90000-0x901FF, ie where the ! boot-block used to be. It is then up to the protected mode ! system to read them from there before the area is overwritten ! for buffer-blocks. ! ! ************************* the comments below detail how the code has developed DAD ! ! Move PS/2 aux init code to psaux.c ! (troyer@saifr00.cfsat.Honeywell.COM) 03Oct92 ! ! some changes and additional features by Christoph Niemann, ! March 1993/June 1994 (Christoph.Niemann@linux.org) ! ! add APM BIOS checking by Stephen Rothwell, May 1994 ! (Stephen.Rothwell@canb.auug.org.au) ! ! High load stuff, initrd support and position independency ! by Hans Lermen & Werner Almesberger, February 1996 ! , ! ! Video handling moved to video.S by Martin Mares, March 1996 ! ! ! Extended memory detection scheme retwiddled by orc@pell.chi.il.us (david ! parsons) to avoid loadlin confusion, July 1997 ! #define __ASSEMBLY__ #include #include #include !! These are other files to be included with this one #include !! Notice the similarity with C #include ! Signature words to ensure LILO loaded us right #define SIG1 0xAA55 !! every time SIG1 appears replace it with 0xAA55 - an assembler directive #define SIG2 0x5A5A !! and another INITSEG = DEF_INITSEG ! 0x9000, we move boot here - out of the way SYSSEG = DEF_SYSSEG ! 0x1000, system loaded at 0x10000 (65536). SETUPSEG = DEF_SETUPSEG ! 0x9020, this is the current segment ! ... and the former contents of CS DELTA_INITSEG = SETUPSEG - INITSEG ! 0x0020 .globl begtext, begdata, begbss, endtext, enddata, endbss .text begtext: .data begdata: .bss begbss: .text entry start start: !! a label jmp start_of_setup !! jump (unconditional) to start_of_setup ! ------------------------ start of header -------------------------------- ! ! SETUP-header, must start at CS:2 (old 0x9020:2) ! .ascii "HdrS" ! Signature for SETUP-header .word 0x0201 ! Version number of header format ! (must be >= 0x0105 ! else old loadlin-1.5 will fail) realmode_swtch: .word 0,0 ! default_switch,SETUPSEG start_sys_seg: .word SYSSEG .word kernel_version ! pointing to kernel version string ! note: above part of header is compatible with loadlin-1.5 (header v1.5), ! must not change it type_of_loader: .byte 0 ! = 0, old one (LILO, Loadlin, ! Bootlin, SYSLX, bootsect...) ! else it is set by the loader: ! 0xTV: T=0 for LILO ! T=1 for Loadlin ! T=2 for bootsect-loader ! T=3 for SYSLX ! T=4 for ETHERBOOT ! V = version loadflags: ! flags, unused bits must be zero (RFU) LOADED_HIGH = 1 ! bit within loadflags, ! if set, then the kernel is loaded high CAN_USE_HEAP = 0x80 ! if set, the loader also has set heap_end_ptr ! to tell how much space behind setup.S | can be used for heap purposes. ! Only the loader knows what is free! #ifndef __BIG_KERNEL__ .byte 0x00 #else .byte LOADED_HIGH #endif setup_move_size: .word 0x8000 ! size to move, when we (setup) are not ! loaded at 0x90000. We will move ourselves ! to 0x90000 then just before jumping into ! the kernel. However, only the loader ! know how much of data behind us also needs ! to be loaded. code32_start: ! here loaders can put a different ! start address for 32-bit code. #ifndef __BIG_KERNEL__ .long 0x1000 ! 0x1000 = default for zImage #else .long 0x100000 ! 0x100000 = default for big kernel #endif ramdisk_image: .long 0 ! address of loaded ramdisk image ! Here the loader (or kernel generator) puts ! the 32-bit address were it loaded the image. ! This only will be interpreted by the kernel. ramdisk_size: .long 0 ! its size in bytes bootsect_kludge: .word bootsect_helper,SETUPSEG heap_end_ptr: .word modelist+1024 ! space from here (exclusive) down to ! end of setup code can be used by setup ! for local heap purposes. ! ------------------------ end of header ---------------------------------- start_of_setup: ! Bootlin depends on this being done early mov ax,#0x01500 !! load ax (bits 15-0) with 0x01500 mov dl,#0x81 !! this is hard disc drive 1 (0=first 1=second etc) int 0x13 !! This uses the BIOS 0x13 with ah=0x15 and dl=81 to determine the type !! of HDD. According to literature ah=3 fixed disc is present. If !! ah=0 no HDD is present @ #ifdef SAFE_RESET_DISK_CONTROLLER !! if SAFE_RESET_DISK_CONTROLLER is defined then the code down to !! #endif is assembled - otherwise it is skipped ! Reset the disk controller. !! bits 31-0 are called eax, 15-0 are called ax, 15-8 are called ah !! 7-0 are called al mov ax,#0x0000 !! 0 is copied to ax so ah=al=0. The rest of eax (bits 31-16) are not !! touched and can be anything mov dl,#0x80 !! 0x80 (80hexadecimal) is copied into dl (bits 7-0 of dx) int 0x13 !! same sort of routine as above but to reset/initialise HardDiscDrive !! 0 (first HDD) #endif ! set DS=CS, we know that SETUPSEG == CS at this point mov ax,cs ! aka #SETUPSEG mov ds,ax !! notice how ds can't be loaded directly ! Check signature at end of setup cmp setup_sig1,#SIG1 !! compares the value setup_sigl with the value(immediate) SIG1 jne bad_sig cmp setup_sig2,#SIG2 jne bad_sig jmp good_sig1 ! Routine to print ASCIIz (!! ie ASCII string terminated by a 0) string at DS:SI prtstr: lodsb !! copy a byte (note b at end of lodsb) using si pointer. si is then !! incremented or decremented depending on the DF flag and al,al !! normal bitwise AND jz fin !! jump if zero flag =1 call prtchr !! call saving return the routine prtchr jmp prtstr !! jump to prtstr fin: ret !! fin is just a label ! Space printing prtsp2: call prtspc ! Print double space prtspc: mov al,#0x20 ! Print single space (fall-thru!) ! Part of above routine, this one just prints ASCII al prtchr: push ax push cx xor bh,bh !! equivalent to making bh=0 which otherwise would need a MOV from !! (say) al mov cx,#0x01 !! copy 1 into cx mov ah,#0x0e !! copy 0x0e into ah int 0x10 !! interrupt 0x10 deals with BIOS video functions. As ah=0x0e this !! will write the code held in al to the video display pop cx pop ax ret beep: mov al,#0x07 !! copy 0x07 to al jmp prtchr !! and jump back to prtchr which "writes" it on the video display !! in fact ASCII 07 causes a beep sound no_sig_mess: .ascii "No setup signature found ..." db 0x00 !! assembler directive to "define byte" value 0x00 good_sig1: jmp good_sig ! We now have to find the rest of the setup code/data bad_sig: mov ax,cs ! aka #SETUPSEG !! note from programmer "cs contains the memory address !! SETUPSEG" sub ax,#DELTA_INITSEG ! aka #INITSEG !! This does the subtraction SETUPSEG - INITSEG mov ds,ax !! and into ds (usually data) xor bh,bh !! puts 0 into bh. mov bl,[497] ! get setup sects from boot sector !! load bl from the contents of 0x497 sub bx,#4 ! LILO loads 4 sectors of setup !! subtracts 4 from bh,bl== 0,[497] shl bx,#8 ! convert to words !! shift logical left by 8 This has the effect of !! multiplying bx by 256. Rotations are carried out !! through the carry flag CF and may/may not be set mov cx,bx shr bx,#3 ! convert to segment !! shift right this time == divide by 8 add bx,#SYSSEG !! and add SYSSEG seg cs !! change to segment cs. This normally works with IP to !! get the next instruction. !! I dont know why this is done here other !! than the possibility of overriding the data segment !! Thus the next instruction could be !! mov cs:start_sys_seg,bx !! other than the default !! mov ds:start_sys_seg,bx mov start_sys_seg,bx !! bx gets copied to the address ds:start_sys_seg !! OR bx gets copied to the address cs:start_sys_seg ! Move rest of setup code/data to here mov di,#2048 ! four sectors loaded by LILO sub si,si !! to zero si but also to change flags !! OF=0; ZF=1; SF=0; and AF,PF,CF are affected mov ax,cs ! aka #SETUPSEG mov es,ax !! this is to copy cs into es - 80386 needs this. mov ax,#SYSSEG mov ds,ax !! to load ds with SYSSEG rep !! top of the repeat loop involving, in this case, movsw movsw !! bulk movment.. MOVeStringWords. Source pointed to by si, !! destination pointed to by di. Controlled by cx mov ax,cs ! aka #SETUPSEG mov ds,ax !! that is copy SETUPSEG into ds cmp setup_sig1,#SIG1 !! this is a good example of how the assembler works !! here setup_sig1 is replaced by an address but the assembler !! substitutes 0xAA55 for #SIG1 and 0xAA55 is a number NB NOT !! AN ADDRESS !! cmp affects OF,SF,ZF,AF,PF AND CF. It does not affect !! anything else jne no_sig !! jump not equal, that is, jump if ZF=0 cmp setup_sig2,#SIG2 jne no_sig jmp good_sig no_sig: lea si,no_sig_mess !! Load Effective Address of "no_sig_mess" into si call prtstr !! Call the routine "prtstr". IP and perhaps CS are saved on !! the stack for a return no_sig_loop: jmp no_sig_loop good_sig: mov ax,cs ! aka #SETUPSEG sub ax,#DELTA_INITSEG ! aka #INITSEG mov ds,ax ! check if an old loader tries to load a big-kernel seg cs test byte ptr loadflags,#LOADED_HIGH ! Have we a big kernel? !! byte ptr tells the assembler to compare bytes (word ptr would alter this to words) !! test works by doing (loadflags AND LOADED_HIGH) It does not change either loadflags or !! LOADED_HIGH It does affect the OF=0; SF ZF PF CF flags are altered and the AF flag !! undefined jz loader_ok ! NO, no danger even for old loaders ! YES, we have a big-kernel seg cs !! overrides ds in next instruction (probably) cmp byte ptr type_of_loader,#0 ! Have we one of the new loaders? jnz loader_ok ! YES, OK ! NO, we have an old loader, must give up push cs pop ds !! one way of copying cs into ds lea si,loader_panic_mess !! programmer has made si point to message to be printed call prtstr jmp no_sig_loop loader_panic_mess: .ascii "Wrong loader: giving up." db 0 !! ASCIIz string. This is the 0 loader_ok: ! Get memory size (extended mem, kB) #ifndef STANDARD_MEMORY_BIOS_CALL push ebx !! extended bx xor ebx,ebx ! preload new memory slot with 0k mov [0x1e0], ebx !! copy ebx to memory at 0x1e0 ie DS:0x1e0 mov ax,#0xe801 int 0x15 !! Deals with system device functions but I don't know what happens in !! this case but it looks like a check of the memory hardware jc oldstylemem ! Memory size is in 1 k chunksizes, to avoid confusing loadlin. ! We store the 0xe801 memory size in a completely different place, ! because it will most likely be longer than 16 bits. ! (use 1e0 because that's what Larry Augustine uses in his ! alternative new memory detection scheme, and it's sensible ! to write everything into the same place.) and ebx, #0xffff ! clear sign extend !! sets bits 31-16 to 0 shl ebx, 6 ! and go from 64k to 1k chunks !! equivalent to multiply by 64 mov [0x1e0],ebx ! store extended memory size and eax, #0xffff ! clear sign extend add [0x1e0],eax ! and add lower memory into total size. ! and fall into the old memory detection code to populate the ! compatibility slot. oldstylemem: pop ebx #else mov dword ptr [0x1e0], #0 !! double word pointer. So memory locations 0x1e0=0 and 0x1e2=0 #endif mov ah,#0x88 !! part of int 0x15 call int 0x15 !! equivalent to "read extended memory size" This leaves the number of !! contiguous 1K blocks in ax. The starting address is 1024K=0x100000 !! (This is held in CMOS 0x30 and 0x31 and int0x15 effectively reads the !! value) mov [2],ax ! Set the keyboard repeat rate to the max !! Before call ah=03 al=05 bh=delay value bl=typematic rate !! e.g. bh=02 so delay is 750 milliseconds !! e.g. bl=09 so rate is 13.3 characters per second mov ax,#0x0305 xor bx,bx ! clear bx !!== 250ms and 30 chars/sec int 0x16 ! Check for video adapter and its parameters and allow the ! user to browse video modes. call video ! NOTE: we need DS pointing to boot sector ! Get hd0 data !! first HDD xor ax,ax ! clear ax mov ds,ax !! ds as well lds si,[4*0x41] !! Load pointer using DS. The [4*0x41] is called "scaling an index" and the !! physical address is worked out using 0x10*DS + 4*0x41 !! LDS transfers 4 bytes to two registers one of which is ds (in this case the !! other is, of course, si. mov ax,cs ! aka #SETUPSEG sub ax,#DELTA_INITSEG ! aka #INITSEG push ax mov es,ax mov di,#0x0080 mov cx,#0x10 push cx !! ** cld !! the DF (also called D) flag is zeroed rep !! repeat movsb !! move from si-> to di-> until cx=0 see up at ** ! Get hd1 data !! second HDD xor ax,ax ! clear ax mov ds,ax lds si,[4*0x46] pop cx pop es mov di,#0x0090 rep movsb ! Check that there IS a hd1 :-) mov ax,#0x01500 mov dl,#0x81 !! 0x81 is code used for second HDD in int0x13 int 0x13 jc no_disk1 !! int 0x13 sets CF=1 if there is an error cmp ah,#3 !! int 0x13 code is put into ah. 00==no drive;01==floppy without !! connection for disk change; 02== floppy with connection for disk !! change; 03 == HDD je is_disk1 !! or jz no_disk1: mov ax,cs ! aka #SETUPSEG sub ax,#DELTA_INITSEG ! aka #INITSEG mov es,ax !! es can't be loaded directly mov di,#0x0090 mov cx,#0x10 xor ax,ax ! clear ax cld !! DF=0 rep !! repeat stosb !! "store string" b==byte operation !! Transfers al contents to destination pointed to by di. If DF=0 di is !! incremented else if DF=1 di is decremented !! repeat ends when cx=0 is_disk1: ! check for Micro Channel (MCA) bus mov ax,cs ! aka #SETUPSEG sub ax,#DELTA_INITSEG ! aka #INITSEG mov ds,ax mov ds,ax xor ax,ax mov [0xa0], ax ! set table length to 0 mov ah, #0xc0 !! code for "system configuration parameters" call stc !! CF=1 int 0x15 ! puts feature table at es:bx jc no_mca push ds mov ax,es mov ds,ax mov ax,cs ! aka #SETUPSEG sub ax, #DELTA_INITSEG ! aka #INITSEG mov es,ax mov si,bx mov di,#0xa0 mov cx,(si) !! the data in memory pointed to by si, not si itself add cx,#2 ! table length is a short cmp cx,#0x10 jc sysdesc_ok mov cx,#0x10 ! we keep only first 16 bytes sysdesc_ok: rep movsb pop ds no_mca: ! Check for PS/2 pointing device !! eg a mouse mov ax,cs ! aka #SETUPSEG sub ax,#DELTA_INITSEG ! aka #INITSEG mov ds,ax !! next instruction is ds:0x1ff mov [0x1ff],#0 ! default is no pointing device int 0x11 ! int 0x11: equipment determination test al,#0x04 ! check if pointing device installed jz no_psmouse mov [0x1ff],#0xaa ! device present no_psmouse: #ifdef CONFIG_APM ! check for APM BIOS ! NOTE: DS is pointing to the boot sector ! mov [64],#0 ! version == 0 means no APM BIOS mov ax,#0x05300 ! APM BIOS installation check xor bx,bx int 0x15 !! sorry I have no info on this call jc done_apm_bios ! error -> no APM BIOS cmp bx,#0x0504d ! check for "PM" signature jne done_apm_bios ! no signature -> no APM BIOS and cx,#0x02 ! Is 32 bit supported? je done_apm_bios ! no ... mov ax,#0x05304 ! Disconnect first just in case xor bx,bx int 0x15 ! ignore return code mov ax,#0x05303 ! 32 bit connect xor bx,bx int 0x15 jc no_32_apm_bios ! error mov [66],ax ! BIOS code segment mov [68],ebx ! BIOS entry point offset mov [72],cx ! BIOS 16 bit code segment mov [74],dx ! BIOS data segment mov [78],esi ! BIOS code segment length mov [82],di ! BIOS data segment length ! ! Redo the installation check as the 32 bit connect ! modifies the flags returned on some BIOSs ! mov ax,#0x05300 ! APM BIOS installation check xor bx,bx int 0x15 jc apm_disconnect ! error -> should not happen, tidy up cmp bx,#0x0504d ! check for "PM" signature jne apm_disconnect ! no signature -> should not happen, tidy up mov [64],ax ! record the APM BIOS version mov [76],cx ! and flags jmp done_apm_bios apm_disconnect: mov ax,#0x05304 ! Disconnect xor bx,bx int 0x15 ! ignore return code jmp done_apm_bios no_32_apm_bios: and [76], #0xfffd ! remove 32 bit support bit done_apm_bios: #endif !! The programming so far has happened in "real mode" But protected mode is far better so... !! Protected mode brings more registers into play /******************************* Protected mode is a multitasking mode - ideal for things like UNIX. The computer executes tasks switching from task to task according to some "fair" schedule set up by the operating system. Each task is allocated some private memory and can access more "global" memory which is shared by all tasks in the system. This global memory can be used by any task in the system when its turn comes so anything that a task needs to keep has to be held in its private "local" memory. (NB that the HDD is considered to be part of the memory.) GDTR (global descriptor table register) is a 47-0 bit register with bits 47-16 pointing to a table and bits 15-0 the length of that table. The table (Global Descriptor Table) holds information about how the memory of the computer is used. It holds info about global and local memory. Each task is allowed access to this table and to its own LDTR (local descriptor table register) Interrupts have their own IDTR (interrupt descriptor table register) and is similar to the GDTR. On power-up the computer is in "real mode" as so before multitasking is possible it has to load the IDTR, GDTR before switching into "protected mode" Other registers are activated 31 16 | 15 8 | 7 0 bit number CR3 | Page Directory page register PDBR | CR2 | Page Fault linear Address | CR1 | Reserved | CR0 |pg |r |ts |em |mp |pe | The bits PE,MP,EM,TS,R and PG are:- PE = 0 at reset. If it changes to 1 the CPU switches into the protected mode MP = 1 if a numeric co-processor is present EM = 1 if software emulation of the co-processor is to be used R = 1 80387 co-processor = 0 80287 co-processor TS is set when the 80386 switches from one task to another. It can be reset by software PG = 1 means that "paged memory" is to be used PE,MP,EM,TS,R are collectively known as the "Machine Status Word Register", MSWR ********************************/ ! Now we want to move to protected mode ... seg cs cmp realmode_swtch,#0 !! the value at realmode_swtch (see below) is compared with 0 jz rmodeswtch_normal seg cs callf far * realmode_swtch !! far * realmode_swtch is a C style far pointer. !! callf (other assemblers use a slightly different syntax) !! makes the assembler use the full address pointed to by !! realmode_swtch. Thus a five byte instruction is generated !! Otherwise the assembler will only generate a 3 byte !!instruction which relies on the present value of cs jmp rmodeswtch_end rmodeswtch_normal: push cs call default_switch rmodeswtch_end: ! we get the code32 start address and modify the below 'jmpi' ! (loader may have changed it) seg cs !! we want to use code32_start which is in the code segment otherwise !! the assembler will assume ds mov eax,code32_start seg cs mov code32,eax ! Now we move the system to its rightful place ! ...but we check, if we have a big-kernel. ! in this case we *must* not move it ... seg cs test byte ptr loadflags,#LOADED_HIGH !! byte ptr tells the assembler that the test is bite !! sized. test only affects the flags jz do_move0 ! we have a normal low loaded zImage ! we have a high loaded big kernel jmp end_move ! ... and we skip moving do_move0: mov ax,#0x100 ! start of destination segment mov bp,cs ! aka #SETUPSEG sub bp,#DELTA_INITSEG ! aka #INITSEG seg cs mov bx,start_sys_seg ! start of source segment cld ! 'direction'=0, movs moves forward !! DF=0 mentioned above do_move: mov es,ax ! destination segment inc ah ! instead of add ax,#0x100 mov ds,bx ! source segment add bx,#0x100 sub di,di sub si,si mov cx,#0x800 rep !! repeat movsw !! move (word sized) using si (source) and di (destination) and cx(counter) cmp bx,bp ! we assume start_sys_seg > 0x200, ! so we will perhaps read one page more than ! needed, but never overwrite INITSEG because ! destination is minimum one page below source jb do_move ! then we load the segment descriptors !! Talking about GDTR etc (see above) end_move: mov ax,cs ! aka #SETUPSEG ! right, forgot this at first. didn't work :-) mov ds,ax ! If we have our code not at 0x90000, we need to move it there now. ! We also then need to move the parameters behind it (command line) ! Because we would overwrite the code on the current IP, we move ! it in two steps, jumping high after the first one. mov ax,cs cmp ax,#SETUPSEG je end_move_self cli ! make sure we really have interrupts disabled ! !! CLear Interrupt enable resets IF ! because after this the stack should not be used sub ax,#DELTA_INITSEG ! aka #INITSEG mov dx,ss cmp dx,ax jb move_self_1 !! CF=1 for the jump add dx,#INITSEG sub dx,ax ! this will be SS after the move move_self_1: mov ds,ax mov ax,#INITSEG ! real INITSEG mov es,ax seg cs mov cx,setup_move_size std ! we have to move up, so we use direction down !! makes DF=1 so si and di decrement ! because the areas may overlap mov di,cx dec di mov si,di sub cx,#move_self_here+0x200 rep !! repeat movsb !! MOVe String - Moving a byte a time jmpi move_self_here,SETUPSEG ! jump to our final place !! I have no idea ???? move_self_here: mov cx,#move_self_here+0x200 rep movsb mov ax,#SETUPSEG mov ds,ax mov ss,dx ! now we are at the right place end_move_self: lidt idt_48 ! load idt with 0,0 !! interrupt descriptor table lgdt gdt_48 ! load gdt with whatever appropriate !! global descriptor table ! that was painless, now we enable A20 !! address line 20 - a bug hangover from previous processors !! using DOS. Linux does not have this bug call empty_8042 mov al,#0xD1 ! command write out #0x64,al !! Output al to port 0x64. Port 0x64 is a command port call empty_8042 mov al,#0xDF ! A20 on out #0x60,al !! Probably the same port call empty_8042 ! wait until a20 really *is* enabled; it can take a fair amount of ! time on certain systems; Toshiba Tecras are known to have this ! problem. The memory location used here is the int 0x1f vector, ! which should be safe to use; any *unused* memory location < 0xfff0 ! should work here. #define TEST_ADDR 0x7c push ds xor ax,ax ! segment 0x0000 mov ds,ax dec ax ! segment 0xffff (HMA) mov gs,ax mov bx,[TEST_ADDR] ! we want to restore the value later a20_wait: inc ax mov [TEST_ADDR],ax seg gs cmp ax,[TEST_ADDR+0x10] je a20_wait ! loop until no longer aliased mov [TEST_ADDR],bx ! restore original value pop ds ! make sure any possible coprocessor is properly reset.. xor ax,ax out #0xf0,al !! co-processor port 0xf0 - 0xff call delay out #0xf1,al call delay ! well, that went ok, I hope. Now we have to reprogram the interrupts :-( ! we put them right after the intel-reserved hardware interrupts, at ! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really ! messed this up with the original PC, and they haven't been able to ! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f, ! which is used for the internal hardware interrupts as well. We just ! have to reprogram the 8259's, and it isn't fun. mov al,#0x11 ! initialization sequence out #0x20,al ! send it to 8259A-1 call delay out #0xA0,al ! and to 8259A-2 call delay mov al,#0x20 ! start of hardware int's (0x20) out #0x21,al call delay mov al,#0x28 ! start of hardware int's 2 (0x28) out #0xA1,al call delay mov al,#0x04 ! 8259-1 is master out #0x21,al call delay mov al,#0x02 ! 8259-2 is slave out #0xA1,al call delay mov al,#0x01 ! 8086 mode for both out #0x21,al call delay out #0xA1,al call delay mov al,#0xFF ! mask off all interrupts for now out #0xA1,al call delay mov al,#0xFB ! mask all irq's but irq2 which out #0x21,al ! is cascaded ! Well, that certainly wasn't fun :-(. Hopefully it works, and we don't ! need no steenking BIOS anyway (except for the initial loading :-). ! The BIOS routine wants lots of unnecessary data, and it's less ! "interesting" anyway. This is how REAL programmers do it. ! ! Well, now's the time to actually move into protected mode. To make ! things as simple as possible, we do no register set-up or anything, ! we let the GNU-compiled 32-bit programs do that. We just jump to ! absolute address 0x1000 (or the loader supplied one), ! in 32-bit protected mode. ! ! Note that the short jump isn't strictly needed, although there are ! reasons why it might be a good idea. It won't hurt in any case. ! mov ax,#1 ! protected mode (PE) bit !! bit 0 in CR0 lmsw ax ! This is it! !! "load machine status word register" ie CR0 !! bits 4-0 (see above) !! NOW IN PROTECTED MODE jmp flush_instr_instr: !! This looks odd to me xor bx,bx ! Flag to indicate a boot ! NOTE: For high loaded big kernels we need a ! jmpi 0x100000,__KERNEL_CS ! ! but we yet haven't reloaded the CS register, so the default size ! of the target offset still is 16 bit. ! However, using an operant prefix (0x66), the CPU will properly ! take our 48 bit far pointer. (INTeL 80386 Programmer's Reference ! Manual, Mixing 16-bit and 32-bit code, page 16-6) !! This hack suggests that jmpi is a "special" - I cant find it in 80386 books db 0x66,0xea ! prefix + jmpi-opcode code32: dd 0x1000 ! will be set to 0x100000 for big kernels dw __KERNEL_CS kernel_version: .ascii UTS_RELEASE .ascii " (" .ascii LINUX_COMPILE_BY .ascii "@" .ascii LINUX_COMPILE_HOST .ascii ") " .ascii UTS_VERSION db 0 ! This is the default real mode switch routine. ! to be called just before protected mode transition default_switch: cli ! no interrupts allowed ! mov al,#0x80 ! disable NMI for the bootup sequence out #0x70,al retf !! return far (inter - segment) ! This routine only gets called, if we get loaded by the simple ! bootsect loader _and_ have a bzImage to load. ! Because there is no place left in the 512 bytes of the boot sector, ! we must emigrate to code space here. ! bootsect_helper: seg cs cmp word ptr bootsect_es,#0 jnz bootsect_second seg cs mov byte ptr type_of_loader,#0x20 mov ax,es shr ax,#4 !! shift right seg cs mov byte ptr bootsect_src_base+2,ah mov ax,es seg cs mov bootsect_es,ax sub ax,#SYSSEG retf ! nothing else to do for now bootsect_second: push cx push si push bx test bx,bx ! 64K full ? !! what is the point of this? - test ALTERS ZF so if it starts as ZF=1 !! this alters it to ZF=0 indicating bx=0 jne bootsect_ex mov cx,#0x8000 ! full 64K move, INT15 moves words push cs pop es mov si,#bootsect_gdt mov ax,#0x8700 int 0x15 !! this moves data between the first 1Mb of memory and extended memory by !! means of the protected mode. (extended memory = memory past the first 1Mb) jc bootsect_panic ! this, if INT15 fails seg cs mov es,bootsect_es ! we reset es to always point to 0x10000 seg cs inc byte ptr bootsect_dst_base+2 !! increment (add 1) the byte "bootsect_dst_base+2" bootsect_ex: seg cs mov ah, byte ptr bootsect_dst_base+2 shl ah,4 ! we now have the number of moved frames in ax xor al,al pop bx pop si pop cx retf bootsect_gdt: .word 0,0,0,0 .word 0,0,0,0 bootsect_src: .word 0xffff bootsect_src_base: .byte 0,0,1 ! base = 0x010000 .byte 0x93 ! typbyte .word 0 ! limit16,base24 =0 bootsect_dst: .word 0xffff bootsect_dst_base: .byte 0,0,0x10 ! base = 0x100000 .byte 0x93 ! typbyte .word 0 ! limit16,base24 =0 .word 0,0,0,0 ! BIOS CS .word 0,0,0,0 ! BIOS DS bootsect_es: .word 0 bootsect_panic: push cs pop ds cld lea si,bootsect_panic_mess call prtstr bootsect_panic_loop: jmp bootsect_panic_loop !! yes it loops back to itself bootsect_panic_mess: .ascii "INT15 refuses to access high memory. Giving up." db 0 ! This routine checks that the keyboard command queue is empty ! (after emptying the output buffers) ! ! Some machines have delusions that the keyboard buffer is always full ! with no keyboard attached... empty_8042: push ecx !! the 32 bit cx mov ecx,#0xFFFFFF empty_8042_loop: dec ecx !! decrement ie subtract 1 jz empty_8042_end_loop call delay in al,#0x64 ! 8042 status port test al,#1 ! output buffer? jz no_output call delay in al,#0x60 ! read it jmp empty_8042_loop no_output: test al,#2 ! is input buffer full? jnz empty_8042_loop ! yes - loop empty_8042_end_loop: pop ecx ret ! ! Read the CMOS clock. Return the seconds in al ! gettime: push cx mov ah,#0x02 int 0x1a mov al,dh ! dh contains the seconds and al,#0x0f mov ah,dh mov cl,#0x04 shr ah,cl aad !! ASCII adjust for division == converts two BCD numbers to a single dividend pop cx ret ! ! Delay is needed after doing I/O ! delay: .word 0x00eb ! jmp $+2 ret ! ! Descriptor tables ! gdt: !! global descriptor table .word 0,0,0,0 ! dummy .word 0,0,0,0 ! unused .word 0xFFFF ! 4Gb - (0x100000*0x1000 = 4Gb) .word 0x0000 ! base address=0 .word 0x9A00 ! code read/exec .word 0x00CF ! granularity=4096, 386 (+5th nibble of limit) .word 0xFFFF ! 4Gb - (0x100000*0x1000 = 4Gb) .word 0x0000 ! base address=0 .word 0x9200 ! data read/write .word 0x00CF ! granularity=4096, 386 (+5th nibble of limit) idt_48: !! interrupt descriptor table register?? .word 0 ! idt limit=0 .word 0,0 ! idt base=0L gdt_48: !! global descriptor table register??? .word 0x800 ! gdt limit=2048, 256 GDT entries .word 512+gdt,0x9 ! gdt base = 0X9xxxx ! ! Include video setup & detection code ! #include "video.S" ! ! Setup signature -- must be last ! setup_sig1: .word SIG1 setup_sig2: .word SIG2 ! ! After this point, there is some free space which is used by the video mode ! handling code to store the temporary mode table (not used by the kernel). ! modelist: .text endtext: .data enddata: .bss endbss: