diff options
author | hpa <hpa> | 1999-09-06 03:26:26 +0000 |
---|---|---|
committer | hpa <hpa> | 1999-09-06 03:26:26 +0000 |
commit | 74d9eb1782a62e84ae50bc7804d399d823203e49 (patch) | |
tree | edecfae07205f2bf3e370e2ecd54a6977e76a733 | |
parent | 87bab38fdcb4ecef4cf1b37cd2ad9f77e234e0c6 (diff) | |
download | syslinux.git-74d9eb1782a62e84ae50bc7804d399d823203e49.tar.gz syslinux.git-74d9eb1782a62e84ae50bc7804d399d823203e49.tar.xz syslinux.git-74d9eb1782a62e84ae50bc7804d399d823203e49.zip |
Beginnings of a PXE network boot loader (PXELINUX).
-rw-r--r-- | Makefile | 11 | ||||
-rw-r--r-- | pxelinux.asm | 3014 |
2 files changed, 3021 insertions, 4 deletions
@@ -33,7 +33,7 @@ VERSION = $(shell cat version) # want to recompile the installers (ITARGET). # SOURCES = ldlinux.asm syslinux.asm syslinux.c copybs.asm -BTARGET = bootsect.bin ldlinux.sys ldlinux.bin ldlinux.lst +BTARGET = bootsect.bin ldlinux.sys ldlinux.bin ldlinux.lst pxelinux.bin ITARGET = syslinux.com syslinux copybs.com DOCS = COPYING NEWS README TODO *.doc OTHER = Makefile bin2c.pl now.pl genstupid.pl keytab-lilo.pl version \ @@ -59,6 +59,9 @@ ldlinux.bin: ldlinux.asm $(NASM) -f bin -dVERSION="'$(VERSION)'" -dDATE_STR="'$(DATE)'" -dHEXDATE="$(HEXDATE)" -l ldlinux.lst -o ldlinux.bin ldlinux.asm perl genstupid.pl < ldlinux.lst +pxelinux.bin: pxelinux.asm + $(NASM) -f bin -dVERSION="'$(VERSION)'" -dDATE_STR="'$(DATE)'" -dHEXDATE="$(HEXDATE)" -l pxelinux.lst -o pxelinux.bin pxelinux.asm + bootsect.bin: ldlinux.bin dd if=ldlinux.bin of=bootsect.bin bs=512 count=1 @@ -101,12 +104,12 @@ tidy: clean: tidy rm -f $(ITARGET) -spotless: clean - rm -f $(BTARGET) - dist: tidy rm -f *~ \#* +spotless: clean dist + rm -f $(BTARGET) + # # This should only be used by the maintainer to generate official binaries # for release. Please do not "make official" and distribute the binaries, diff --git a/pxelinux.asm b/pxelinux.asm new file mode 100644 index 00000000..f726831a --- /dev/null +++ b/pxelinux.asm @@ -0,0 +1,3014 @@ +; -*- fundamental -*- (asm-mode sucks) +; $Id$ +; **************************************************************************** +; +; pxelinux.asm +; +; A program to boot Linux kernels off a TFTP server using the Intel PXE +; network booting API. It is based on the SYSLINUX boot loader for +; MS-DOS floppies. +; +; Copyright (C) 1994-1999 H. Peter Anvin +; +; This program is free software; you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA 02139, +; USA; either version 2 of the License, or (at your option) any later +; version; incorporated herein by reference. +; +; **************************************************************************** + +; +; Some semi-configurable constants... change on your own risk. Most are imposed +; by the kernel. +; +max_cmd_len equ 255 ; Must be odd; 255 is the kernel limit +MAX_FILENAME equ 32 ; Including final null; should be a power of 2 +retry_count equ 6 ; How patient are we with the disk? +HIGHMEM_MAX equ 038000000h ; Highest address for an initrd +DEFAULT_BAUD equ 9600 ; Default baud rate for serial port +BAUD_DIVISOR equ 115200 ; Serial port parameter +; +; Should be updated with every release to avoid bootsector/SYS file mismatch +; +%define version_str VERSION ; Must be 4 characters long! +%define date DATE_STR ; Defined from the Makefile +%define year '1999' +; +; Debgging stuff +; +; %define debug 1 ; Uncomment to enable debugging +; +; ID for SYSLINUX (reported to kernel) +; +syslinux_id equ 032h ; SYSLINUX (3) 2 = PXELINUX +; +; Segments used by Linux +; +; Note: the real_mode_seg is supposed to be 9000h, but PXE uses that +; memory. Therefore, we load it at 8000h and copy it before starting +; the Linux kernel. +; +real_mode_seg equ 8000h + struc real_mode_seg_t + resb 20h-($-$$) ; org 20h +kern_cmd_magic resw 1 ; Magic # for command line +kern_cmd_offset resw 1 ; Offset for kernel command line + resb 497-($-$$) ; org 497d +bs_setupsecs resb 1 ; Sectors for setup code (0 -> 4) +bs_rootflags resw 1 ; Root readonly flag +bs_syssize resw 1 +bs_swapdev resw 1 ; Swap device (obsolete) +bs_ramsize resw 1 ; Ramdisk flags, formerly ramdisk size +bs_vidmode resw 1 ; Video mode +bs_rootdev resw 1 ; Root device +bs_bootsign resw 1 ; Boot sector signature (0AA55h) +su_jump resb 1 ; 0EBh +su_jump2 resb 1 +su_header resd 1 ; New setup code: header +su_version resw 1 ; See linux/arch/i386/boot/setup.S +su_switch resw 1 +su_setupseg resw 1 +su_startsys resw 1 +su_kver resw 1 ; Kernel version pointer +su_loader resb 1 ; Loader ID +su_loadflags resb 1 ; Load high flag +su_movesize resw 1 +su_code32start resd 1 ; Start of code loaded high +su_ramdiskat resd 1 ; Start of initial ramdisk +su_ramdisklen equ $ ; Length of initial ramdisk +su_ramdisklen1 resw 1 +su_ramdisklen2 resw 1 +su_bsklugeoffs resw 1 +su_bsklugeseg resw 1 +su_heapend resw 1 + resb (8000h-12)-($-$$) ; Were bootsect.S puts it... +linux_stack equ $ +linux_fdctab equ $ + resb 8000h-($-$$) +cmd_line_here equ $ ; Should be out of the way + endstruc + +setup_seg equ 9020h + struc setup_seg_t + org 0h ; as 9020:0000, not 9000:0200 +setup_entry equ $ + endstruc + +; +; Magic number of su_header field +; +HEADER_ID equ 'HdrS' ; HdrS (in littleendian hex) +; +; Flags for the su_loadflags field +; +LOAD_HIGH equ 01h ; Large kernel, load high +CAN_USE_HEAP equ 80h ; Boot loader reports heap size +; +; The following structure is used for "virtual kernels"; i.e. LILO-style +; option labels. The options we permit here are `kernel' and `append +; Since there is no room in the bottom 64K for all of these, we +; stick them at vk_seg:0000 and copy them down before we need them. +; +; Note: this structure can be added to, but it must +; +%define vk_power 7 ; log2(max number of vkernels) +%define max_vk (1 << vk_power) ; Maximum number of vkernels +%define vk_shift (16-vk_power) ; Number of bits to shift +%define vk_size (1 << vk_shift) ; Size of a vkernel buffer + + struc vkernel +vk_vname: resb MAX_FILENAME ; Virtual name **MUST BE FIRST!** +vk_rname: resb MAX_FILENAME ; Real name +vk_appendlen: resw 1 + alignb 4 +vk_append: resb max_cmd_len+1 ; Command line + alignb 4 +vk_end: equ $ ; Should be <= vk_size + endstruc + +%if (vk_end > vk_size) || (vk_size*max_vk > 65536) +%error "Too many vkernels defined, reduce vk_power" +%endif + +; +; Segment assignments in the bottom 640K +; 0000h - main code/data segment (and BIOS segment) +; 8000h - real_mode_seg +; +vk_seg equ 7000h ; This is where we stick'em +xfer_buf_seg equ 6000h ; Bounce buffer for I/O to high mem +fat_seg equ 4000h ; 128K area for FAT (2x64K) +comboot_seg equ 2000h ; COMBOOT image loading zone + +; +; For our convenience: define macros for jump-over-unconditinal jumps +; +%macro jmpz 1 + jnz %%skip + jmp %1 +%%skip: +%endmacro + +%macro jmpnz 1 + jz %%skip + jmp %1 +%%skip: +%endmacro + +%macro jmpe 1 + jne %%skip + jmp %1 +%%skip: +%endmacro + +%macro jmpne 1 + je %%skip + jmp %1 +%%skip: +%endmacro + +%macro jmpc 1 + jnc %%skip + jmp %1 +%%skip: +%endmacro + +%macro jmpnc 1 + jc %%skip + jmp %1 +%%skip: +%endmacro + +%macro jmpb 1 + jnb %%skip + jmp %1 +%%skip: +%endmacro + +%macro jmpnb 1 + jb %%skip + jmp %1 +%%skip: +%endmacro + +; +; Macros similar to res[bwd], but which works in the code segment (after +; section .text) +; +%macro zb 1 + times %1 db 0 +%endmacro + +%macro zw 1 + times %1 dw 0 +%endmacro + +%macro zd 1 + times %1 dd 0 +%endmacro + +; --------------------------------------------------------------------------- +; BEGIN THE BIOS/CODE/DATA SEGMENT +; --------------------------------------------------------------------------- + + absolute 0400h +serial_base resw 4 ; Base addresses for 4 serial ports + + absolute 0484h +BIOS_vidrows resb 1 ; Number of screen rows + +; +; Memory below this point is reserved for the BIOS and the MBR +; + absolute 1000h +trackbuf equ $ ; Track buffer goes here +trackbufsize equ 16384 ; Safe size of track buffer +; trackbuf ends at 5000h + + absolute 6000h ; Here we keep our BSS stuff +StackBuf equ $ ; Start the stack here (grow down - 4K) +VKernelBuf: resb vk_size ; "Current" vkernel + alignb 4 +AppendBuf resb max_cmd_len+1 ; append= +KbdMap resb 256 ; Keyboard map +FKeyName resb 10*MAX_FILENAME ; File names for F-key help +NumBuf resb 16 ; Buffer to load number +NumBufEnd equ NumBuf+15 ; Pointer to last byte in NumBuf + alignb 4 +KernelName resb MAX_FILENAME ; Mangled name for kernel +PartInfo resb 16 ; Partition table entry +InitRDat resd 1 ; Load address (linear) for initrd +HiLoadAddr resd 1 ; Address pointer for high load loop +HighMemSize resd 1 ; End of memory pointer (bytes) +KernelSize resd 1 ; Size of kernel (bytes) +Stack resd 1 ; Pointer to reset stack +RootDir equ $ ; Location of root directory +RootDir1 resw 1 +RootDir2 resw 1 +DataArea equ $ ; Location of data area +DataArea1 resw 1 +DataArea2 resw 1 +FBytes equ $ ; Used by open/getc +FBytes1 resw 1 +FBytes2 resw 1 +RootDirSize resw 1 ; Root dir size in sectors +DirScanCtr resw 1 ; Used while searching directory +DirBlocksLeft resw 1 ; Ditto +EndofDirSec resw 1 ; = trackbuf+bsBytesPerSec-31 +RunLinClust resw 1 ; Cluster # for LDLINUX.SYS +ClustSize resw 1 ; Bytes/cluster +SecPerClust resw 1 ; Same as bsSecPerClust, but a word +NextCluster resw 1 ; Pointer to "nextcluster" routine +BufSafe resw 1 ; Clusters we can load into trackbuf +BufSafeSec resw 1 ; = how many sectors? +BufSafeBytes resw 1 ; = how many bytes? +EndOfGetCBuf resw 1 ; = getcbuf+BufSafeBytes +KernelClust resw 1 ; Kernel size in clusters +InitRDClust resw 1 ; Ramdisk size in clusters +ClustPerMoby resw 1 ; Clusters per 64K +FClust resw 1 ; Number of clusters in open/getc file +FNextClust resw 1 ; Pointer to next cluster in d:o +FPtr resw 1 ; Pointer to next char in buffer +CmdOptPtr resw 1 ; Pointer to first option on cmd line +KernelCNameLen resw 1 ; Length of unmangled kernel name +InitRDCNameLen resw 1 ; Length of unmangled initrd name +NextCharJump resw 1 ; Routine to interpret next print char +SetupSecs resw 1 ; Number of setup sectors +SavedSP resw 1 ; Our SP while running a COMBOOT image +A20Test resw 1 ; Counter for testing status of A20 +TextAttrBX equ $ +TextAttribute resb 1 ; Text attribute for message file +TextPage resb 1 ; Active display page +CursorDX equ $ +CursorCol resb 1 ; Cursor column for message file +CursorRow resb 1 ; Cursor row for message file +ScreenSize equ $ +VidCols resb 1 ; Columns on screen-1 +VidRows resb 1 ; Rows on screen-1 +RetryCount resb 1 ; Used for disk access retries +KbdFlags resb 1 ; Check for keyboard escapes +LoadFlags resb 1 ; Loadflags from kernel +A20Tries resb 1 ; Times until giving up on A20 +FuncFlag resb 1 ; == 1 if <Ctrl-F> pressed +KernelCName resb MAX_FILENAME ; Unmangled kernel name +InitRDCName resb MAX_FILENAME ; Unmangled initrd name +MNameBuf resb 11 ; OBSOLETE +InitRD resb 11 ; OBSOLETE + + section .text + org 7C00h +; +; Primary entry point. +; +bootsec equ $ +_start: + jmp 0:_start1 ; Canonicalize address +_start1: + mov bp,sp + les bx,[ss:bp+4] ; Initial !PXE structure pointer + + push cs + pop ds + sti ; Stack already set up by PXE + cld ; Copy upwards + + push ds + mov [Stack],sp + push ss + pop word [Stack+2] + +; +; Now we need to find the !PXE structure. It's *supposed* to be pointed +; to by SS:[SP+4], but support INT 1Ah, AX=5650h method as well. +; + cmp dword [es:bx], '!PXE' + je have_pxe + + ; Uh-oh, not there... try plan B + mov ax, 5650h + int 1Ah + jc no_pxe + cmp ax,564Eh + jne no_pxe + + ; Okay, that gave us the PXENV+ structure, find !PXE + ; structure from that + cmp dword [es:bx], 'PXEN' + jne no_pxe + cmp word [es:bx+4], 'V+' + jne no_pxe + cmp word [es:bx+6], 0201h ; API version 2.1 or higher + jb no_pxe + les bx,[es:bx+26] ; !PXE structure pointer + cmp dword [es:bx],'!PXE' + je have_pxe + +no_pxe: mov si,err_nopxe + call writestr + jmp kaboom + +have_pxe: + +; +; Tell the user we got this far +; + mov si,pxelinux_banner + call writestr + +.enough: jmp short .enough + +; +; ----------------------------------------------------------------------------- +; Subroutines that have to be in the first sector +; ----------------------------------------------------------------------------- +; +; getfssec: Get multiple clusters from a file, given the starting cluster. +; +; This routine makes sure the subtransfers do not cross a 64K boundary, +; and will correct the situation if it does, UNLESS *sectors* cross +; 64K boundaries. +; +; ES:BX -> Buffer +; SI -> Starting cluster number (2-based) +; CX -> Cluster count (0FFFFh = until end of file) +; + ; 386 check +getfssec: +%if 0 +getfragment: xor bp,bp ; Fragment sector count + mov ax,si ; Get sector address + dec ax ; Convert to 0-based + dec ax + mul word [SecPerClust] + add ax,[DataArea1] + adc dx,[DataArea2] +getseccnt: ; See if we can read > 1 clust + add bp,[SecPerClust] + dec cx ; Reduce clusters left to find + mov di,si ; Predict next cluster + inc di + call [NextCluster] + jc gfs_eof ; At EOF? + jcxz endfragment ; Or was it the last we wanted? + cmp si,di ; Is file continuous? + jz getseccnt ; Yes, we can get +endfragment: clc ; Not at EOF +gfs_eof: pushf ; Remember EOF or not + push si + push cx +gfs_getchunk: + push ax + push dx + mov ax,es ; Check for 64K boundaries. + mov cl,4 + shl ax,cl + add ax,bx + xor dx,dx + neg ax + jnz gfs_partseg + inc dx ; Full 64K segment +gfs_partseg: + div word [bsBytesPerSec] ; How many sectors fit? + mov si,bp + sub si,ax ; Compute remaining sectors + jbe gfs_lastchunk + mov bp,ax + pop dx + pop ax + call getlinsecsr + add ax,bp + adc dx,byte 0 + mov bp,si ; Remaining sector count + jmp short gfs_getchunk +gfs_lastchunk: pop dx + pop ax + call getlinsec + pop cx + pop si + popf + jcxz gfs_return ; If we hit the count limit + jnc getfragment ; If we didn't hit EOF +gfs_return: ret + +; +; getlinsecsr: save registers, call getlinsec, restore registers +; +getlinsecsr: push ax + push dx + push cx + push bp + push si + push di + call getlinsec + pop di + pop si + pop bp + pop cx + pop dx + pop ax + ret +%endif + +; +; nextcluster: Advance a cluster pointer in SI to the next cluster +; pointed at in the FAT tables (note: FAT12 assumed) +; Sets CF on return if end of file. +; +; The variable NextCluster gets set to the appropriate +; value here. +; +nextcluster_fat12: + push ax + push ds + mov ax,fat_seg + mov ds,ax + mov ax,si ; Multiply by 3/2 + shr ax,1 + pushf ; CF now set if odd + add si,ax + mov si,[si] + popf + jnc nc_even + shr si,1 ; Needed for odd only + shr si,1 + shr si,1 + shr si,1 +nc_even: + and si,0FFFh + cmp si,0FF0h ; Clears CF if at end of file + cmc ; But we want it SET... + pop ds + pop ax +nc_return: ret + +; +; FAT16 decoding routine. Note that a 16-bit FAT can be up to 128K, +; so we have to decide if we're in the "low" or the "high" 64K-segment... +; +nextcluster_fat16: + push ax + push ds + mov ax,fat_seg + shl si,1 + jnc .seg0 + mov ax,fat_seg+1000h +.seg0: mov ds,ax + mov si,[si] + cmp si,0FFF0h + cmc + pop ds + pop ax + ret +; +; Debug routine +; +%ifdef debug +safedumpregs: + cmp word [Debug_Magic],0D00Dh + jnz nc_return + jmp dumpregs +%endif + +rl_checkpt equ $ ; Must be <= 400h + +; ---------------------------------------------------------------------------- +; End of code and data that have to be in the first sector +; ---------------------------------------------------------------------------- + +all_read: +; +; Let the user (and programmer!) know we got this far. This used to be +; in Sector 1, but makes a lot more sense here. +; + mov si,copyright_str + call writestr +; +; Check that no moron is trying to boot Linux on a 286 or so. According +; to Intel, the way to check is to see if the high 4 bits of the FLAGS +; register are either all stuck at 1 (8086/8088) or all stuck at 0 +; (286 in real mode), if not it is a 386 or higher. They didn't +; say how to check for a 186/188, so I *hope* it falls out as a 8086 +; or 286 in this test. +; +; Also, provide an escape route in case it doesn't work. +; +check_escapes: + mov ah,02h ; Check keyboard flags + int 16h + mov [KbdFlags],al ; Save for boot prompt check + test al,04h ; Ctrl->skip 386 check + jnz skip_checks +test_8086: + pushf ; Get flags + pop ax + and ax,0FFFh ; Clear top 4 bits + push ax ; Load into FLAGS + popf + pushf ; And load back + pop ax + and ax,0F000h ; Get top 4 bits + cmp ax,0F000h ; If set -> 8086/8088 + je not_386 +test_286: + pushf ; Get flags + pop ax + or ax,0F000h ; Set top 4 bits + push ax + popf + pushf + pop ax + and ax,0F000h ; Get top 4 bits + jnz is_386 ; If not clear -> 386 +not_386: + mov si,err_not386 + call writestr + jmp kaboom +is_386: + ; Now we know it's a 386 or higher +; +; Now check that there is at least 608K of low (DOS) memory +; (608K = 9800h segments) +; + int 12h + cmp ax,608 + jae enough_ram + mov si,err_noram + call writestr + jmp kaboom +enough_ram: +skip_checks: +; +; Check if we're 386 (as opposed to 486+); if so we need to blank out +; the WBINVD instruction +; +; We check for 486 by setting EFLAGS.AC +; + pushfd ; Save the good flags + pushfd + pop eax + mov ebx,eax + xor eax,(1 << 18) ; AC bit + push eax + popfd + pushfd + pop eax + popfd ; Restore the original flags + xor eax,ebx + jnz is_486 +; +; 386 - Looks like we better blot out the WBINVD instruction +; + mov byte [try_wbinvd],0c3h ; Near RET +is_486: + +; +; Initialization that does not need to go into the any of the pre-load +; areas +; + call adjust_screen +; +; Now, everything is "up and running"... patch kaboom for more +; verbosity and using the full screen system +; + mov byte [kaboom.patch],0e9h ; JMP NEAR + mov word [kaboom.patch+1],kaboom2-(kaboom.patch+3) + +; +; Now we're all set to start with our *real* business. First load the +; configuration file (if any) and parse it. +; +; In previous versions I avoided using 32-bit registers because of a +; rumour some BIOSes clobbered the upper half of 32-bit registers at +; random. I figure, though, that if there are any of those still left +; they probably won't be trying to install Linux on them... +; +; The code is still ripe with 16-bitisms, though. Not worth the hassle +; to take'm out. In fact, we may want to put them back if we're going +; to boot ELKS at some point. +; + mov si,linuxauto_cmd ; Default command: "linux auto" + mov di,default_cmd + mov cx,linuxauto_len + rep movsb + + mov di,KbdMap ; Default keymap 1:1 + xor al,al + mov cx,256 +mkkeymap: stosb + inc al + loop mkkeymap + +; +; Load configuration file +; + mov di,syslinux_cfg + call open + jz near no_config_file +parse_config: + call getkeyword + jc near end_config_file ; Config file loaded + cmp ax,'de' ; DEfault + je pc_default + cmp ax,'ap' ; APpend + je pc_append + cmp ax,'ti' ; TImeout + je near pc_timeout + cmp ax,'pr' ; PRompt + je near pc_prompt + cmp ax,'fo' ; FOnt + je near pc_font + cmp ax,'kb' ; KBd + je near pc_kbd + cmp ax,'di' ; DIsplay + je near pc_display + cmp ax,'la' ; LAbel + je near pc_label + cmp ax,'ke' ; KErnel + je pc_kernel + cmp ax,'im' ; IMplicit + je near pc_implicit + cmp ax,'se' ; SErial + je near pc_serial + cmp al,'f' ; F-key + jne parse_config + jmp pc_fkey + +pc_default: mov di,default_cmd ; "default" command + call getline + mov si,auto_cmd ; add "auto"+null + mov cx,auto_len + rep movsb + jmp short parse_config + +pc_append: cmp word [VKernelCtr],byte 0 ; "append" command + ja pc_append_vk + mov di,AppendBuf + call getline + sub di,AppendBuf +pc_app1: mov [AppendLen],di + jmp short parse_config +pc_append_vk: mov di,VKernelBuf+vk_append ; "append" command (vkernel) + call getline + sub di,VKernelBuf+vk_append + cmp di,byte 2 + jne pc_app2 + cmp byte [VKernelBuf+vk_append],'-' + jne pc_app2 + mov di,0 ; If "append -" -> null string +pc_app2: mov [VKernelBuf+vk_appendlen],di + jmp short parse_config_2 + +pc_kernel: cmp word [VKernelCtr],byte 0 ; "kernel" command + je near parse_config ; ("label" section only) + mov di,trackbuf + push di + call getline + pop si + mov di,VKernelBuf+vk_rname + call mangle_name + jmp short parse_config_2 + +pc_timeout: call getint ; "timeout" command + jc parse_config_2 + mov ax,0D215h ; There are approx 1.D215h + mul bx ; clock ticks per 1/10 s + add bx,dx + mov [KbdTimeOut],bx + jmp short parse_config_2 + +pc_display: call pc_getfile ; "display" command + jz parse_config_2 ; File not found? + call get_msg_file ; Load and display file +parse_config_2: jmp parse_config + +pc_prompt: call getint ; "prompt" command + jc parse_config_2 + mov [ForcePrompt],bx + jmp short parse_config_2 + +pc_implicit: call getint ; "implicit" command + jc parse_config_2 + mov [AllowImplicit],bx + jmp short parse_config_2 + +pc_serial: call getint ; "serial" command + jc parse_config_2 + push bx ; Serial port # + call skipspace + jc parse_config_2 + call ungetc + call getint + jnc .valid_baud + mov ebx,DEFAULT_BAUD ; No baud rate given +.valid_baud: pop di ; Serial port # + cmp ebx,byte 75 + jb parse_config_2 ; < 75 baud == bogus + mov eax,BAUD_DIVISOR + cdq + div ebx + push ax ; Baud rate divisor + mov dx,di + shl di,1 + mov ax,[di+serial_base] + mov [SerialPort],ax + push ax ; Serial port base + mov ax,00e3h ; INT 14h init parameters + int 14h ; Init serial port + pop bx ; Serial port base + lea dx,[bx+3] + mov al,83h ; Enable DLAB + call slow_out + pop ax ; Divisor + mov dx,bx + call slow_out + inc dx + mov al,ah + call slow_out + mov al,03h ; Disable DLAB + add dx,byte 2 + call slow_out + sub dx,byte 2 + xor al,al ; IRQ disable + call slow_out + + ; Show some life + mov si,pxelinux_banner + call write_serial_str + mov si,copyright_str + call write_serial_str + + jmp short parse_config_3 + +pc_fkey: sub ah,'1' + jnb pc_fkey1 + mov ah,9 ; F10 +pc_fkey1: xor cx,cx + mov cl,ah + push cx + mov ax,1 + shl ax,cl + or [FKeyMap], ax ; Mark that we have this loaded + mov di,trackbuf + push di + call getline ; Get filename to display + pop si + pop di + shl di,4 ; Multiply number by 16 + add di,FKeyName + call mangle_name ; Mangle file name + jmp short parse_config_3 + +pc_label: call commit_vk ; Commit any current vkernel + mov di,trackbuf ; Get virtual filename + push di + call getline + pop si + mov di,VKernelBuf+vk_vname + call mangle_name ; Mangle virtual name + inc word [VKernelCtr] ; One more vkernel + mov si,VKernelBuf+vk_vname ; By default, rname == vname + mov di,VKernelBuf+vk_rname + mov cx,11 + rep movsb + mov si,AppendBuf ; Default append==global append + mov di,VKernelBuf+vk_append + mov cx,[AppendLen] + mov [VKernelBuf+vk_appendlen],cx + rep movsb + jmp near parse_config_3 + +pc_font: call pc_getfile ; "font" command + jz parse_config_3 ; File not found? + call loadfont ; Load and install font + jmp short parse_config_3 + +pc_kbd: call pc_getfile ; "kbd" command + jz parse_config_3 + call loadkeys +parse_config_3: jmp parse_config + +; +; pc_getfile: For command line options that take file argument, this +; routine decodes the file argument and runs it through searchdir +; +pc_getfile: mov di,trackbuf + push di + call getline + pop si + mov di,MNameBuf + push di + call mangle_name + pop di + jmp searchdir ; Tailcall + +; +; commit_vk: Store the current VKernelBuf into buffer segment +; +commit_vk: + cmp word [VKernelCtr],byte 0 + je cvk_ret ; No VKernel = return + cmp word [VKernelCtr],max_vk ; Above limit? + ja cvk_overflow + mov di,[VKernelCtr] + dec di + shl di,vk_shift + mov si,VKernelBuf + mov cx,(vk_size >> 2) + push es + push word vk_seg + pop es + rep movsd ; Copy to buffer segment + pop es +cvk_ret: ret +cvk_overflow: mov word [VKernelCtr],max_vk ; No more than max_vk, please + ret + +; +; End of configuration file +; +end_config_file: + call commit_vk ; Commit any current vkernel +no_config_file: +; +; Check whether or not we are supposed to display the boot prompt. +; +check_for_key: + cmp word [ForcePrompt],byte 0 ; Force prompt? + jnz enter_command + test byte [KbdFlags],5Bh ; Caps, Scroll, Shift, Alt + jz near auto_boot ; If neither, default boot + +enter_command: + mov si,boot_prompt + call cwritestr + + mov byte [FuncFlag],0 ; <Ctrl-F> not pressed + mov di,command_line +; +; get the very first character -- we can either time +; out, or receive a character press at this time. Some dorky BIOSes stuff +; a return in the buffer on bootup, so wipe the keyboard buffer first. +; +clear_buffer: mov ah,1 ; Check for pending char + int 16h + jz get_char_time + xor ax,ax ; Get char + int 16h + jmp short clear_buffer +get_char_time: mov cx,[KbdTimeOut] + and cx,cx + jz get_char ; Timeout == 0 -> no timeout + inc cx ; The first loop will happen + ; immediately as we don't + ; know the appropriate DX value +time_loop: push cx +tick_loop: push dx + call pollchar + jnz get_char_pop + xor ax,ax + int 1Ah ; Get time "of day" + pop ax + cmp dx,ax ; Has the timer advanced? + je tick_loop + pop cx + loop time_loop ; If so, decrement counter + jmp command_done ; Timeout! + +get_char_pop: pop eax ; Clear stack +get_char: call getchar + and al,al + jz func_key + +got_ascii: cmp al,7Fh ; <DEL> == <BS> + je backspace + cmp al,' ' ; ASCII? + jb not_ascii + ja enter_char + cmp di,command_line ; Space must not be first + je get_char +enter_char: test byte [FuncFlag],1 + jz .not_ctrl_f + mov byte [FuncFlag],0 + cmp al,'0' + jb .not_ctrl_f + je ctrl_f_0 + cmp al,'9' + jbe ctrl_f +.not_ctrl_f: cmp di,max_cmd_len+command_line ; Check there's space + jnb get_char + stosb ; Save it + call writechr ; Echo to screen +get_char_2: jmp short get_char +not_ascii: mov byte [FuncFlag],0 + cmp al,0Dh ; Enter + je command_done + cmp al,06h ; <Ctrl-F> + je set_func_flag + cmp al,08h ; Backspace + jne get_char +backspace: cmp di,command_line ; Make sure there is anything + je get_char ; to erase + dec di ; Unstore one character + mov si,wipe_char ; and erase it from the screen + call cwritestr + jmp short get_char_2 + +set_func_flag: + mov byte [FuncFlag],1 + jmp short get_char_2 + +ctrl_f_0: add al,10 ; <Ctrl-F>0 == F10 +ctrl_f: push di + sub al,'1' + xor ah,ah + jmp short show_help + +func_key: + push di + cmp ah,68 ; F10 + ja get_char_2 + sub ah,59 ; F1 + jb get_char_2 + shr ax,8 +show_help: ; AX = func key # (0 = F1, 9 = F10) + mov cl,al + shl ax,4 ; Convert to x16 + mov bx,1 + shl bx,cl + and bx,[FKeyMap] + jz get_char_2 ; Undefined F-key + mov di,ax + add di,FKeyName + call searchdir + jz fk_nofile + call get_msg_file + jmp short fk_wrcmd +fk_nofile: + mov si,crlf_msg + call cwritestr +fk_wrcmd: + mov si,boot_prompt + call cwritestr + pop di ; Command line write pointer + push di + mov byte [di],0 ; Null-terminate command line + mov si,command_line + call cwritestr ; Write command line so far + pop di + jmp short get_char_2 +auto_boot: + mov si,default_cmd + mov di,command_line + mov cx,(max_cmd_len+4) >> 2 + rep movsd + jmp short load_kernel +command_done: + mov si,crlf_msg + call cwritestr + cmp di,command_line ; Did we just hit return? + je auto_boot + xor al,al ; Store a final null + stosb + +load_kernel: ; Load the kernel now +; +; First we need to mangle the kernel name the way DOS would... +; + mov si,command_line + mov di,KernelName + push si + push di + call mangle_name + pop di + pop si +; +; Fast-forward to first option (we start over from the beginning, since +; mangle_name doesn't necessarily return a consistent ending state.) +; +clin_non_wsp: lodsb + cmp al,' ' + ja clin_non_wsp +clin_is_wsp: and al,al + jz clin_opt_ptr + lodsb + cmp al,' ' + jbe clin_is_wsp +clin_opt_ptr: dec si ; Point to first nonblank + mov [CmdOptPtr],si ; Save ptr to first option +; +; Now check if it is a "virtual kernel" +; + mov cx,[VKernelCtr] + push ds + push word vk_seg + pop ds + cmp cx,byte 0 + je not_vk + xor si,si ; Point to first vkernel +vk_check: pusha + mov cx,11 + repe cmpsb ; Is this it? + je near vk_found + popa + add si,vk_size + loop vk_check +not_vk: pop ds +; +; Not a "virtual kernel" - check that's OK and construct the command line +; + cmp word [AllowImplicit],byte 0 + je bad_implicit + push es + push si + push di + mov di,real_mode_seg + mov es,di + mov si,AppendBuf + mov di,cmd_line_here + mov cx,[AppendLen] + rep movsb + mov [CmdLinePtr],di + pop di + pop si + pop es + mov bx,exten_count << 2 ; Alternates to try +; +; Find the kernel on disk +; +get_kernel: mov byte [KernelName+11],0 ; Zero-terminate filename/extension + mov eax,[KernelName+8] ; Save initial extension + mov [OrigKernelExt],eax +.search_loop: push bx + mov di,KernelName ; Search on disk + call searchdir + pop bx + jnz kernel_good + mov eax,[exten_table+bx] ; Try a different extension + mov [KernelName+8],eax + sub bx,byte 4 + jnb .search_loop +bad_kernel: + mov si,KernelName + mov di,KernelCName + push di + call unmangle_name ; Get human form + mov si,err_notfound ; Complain about missing kernel + call cwritestr + pop si ; KernelCName + call cwritestr + mov si,crlf_msg + jmp abort_load ; Ask user for clue +; +; bad_implicit: The user entered a nonvirtual kernel name, with "implicit 0" +; +bad_implicit: mov si,KernelName ; For the error message + mov di,KernelCName + call unmangle_name + jmp short bad_kernel +; +; vk_found: We *are* using a "virtual kernel" +; +vk_found: popa + push di + mov di,VKernelBuf + mov cx,vk_size >> 2 + rep movsd + push es ; Restore old DS + pop ds + push es + push word real_mode_seg + pop es + mov di,cmd_line_here + mov si,VKernelBuf+vk_append + mov cx,[VKernelBuf+vk_appendlen] + rep movsb + mov [CmdLinePtr],di ; Where to add rest of cmd + pop es + pop di ; DI -> KernelName + push di + mov si,VKernelBuf+vk_rname + mov cx,11 ; We need ECX == CX later + rep movsb + pop di + xor bx,bx ; Try only one version + jmp get_kernel +; +; kernel_corrupt: Called if the kernel file does not seem healthy +; +kernel_corrupt: mov si,err_notkernel + jmp abort_load +; +; This is it! We have a name (and location on the disk)... let's load +; that sucker!! First we have to decide what kind of file this is; base +; that decision on the file extension. The following extensions are +; recognized: +; +; .COM - COMBOOT image +; .CBT - COMBOOT image +; .BS - Boot sector +; .BSS - Boot sector, but transfer over DOS superblock +; +; Anything else is assumed to be a Linux kernel. +; +kernel_good: + pusha + mov si,KernelName + mov di,KernelCName + call unmangle_name ; Get human form + sub di,KernelCName + mov [KernelCNameLen],di + popa + + mov ecx,[KernelName+8] ; Get (mangled) extension + cmp ecx,'COM' + je near is_comboot_image + cmp ecx,'CBT' + je near is_comboot_image + cmp ecx,'BS ' + je near is_bootsector + cmp ecx,'BSS' + je near is_bss_sector + ; Otherwise Linux kernel +; +; A Linux kernel consists of three parts: boot sector, setup code, and +; kernel code. The boot sector is never executed when using an external +; booting utility, but it contains some status bytes that are necessary. +; The boot sector and setup code together form exactly 5 sectors that +; should be loaded at 9000:0. The subsequent code should be loaded +; at 1000:0. For simplicity, we load the whole thing at 0F60:0, and +; copy the latter stuff afterwards. +; +; NOTE: In the previous code I have avoided making any assumptions regarding +; the size of a sector, in case this concept ever gets extended to other +; media like CD-ROM (not that a CD-ROM would be bloody likely to use a FAT +; filesystem, of course). However, a "sector" when it comes to Linux booting +; stuff means 512 bytes *no matter what*, so here I am using that piece +; of knowledge. +; +; First check that our kernel is at least 64K and less than 8M (if it is +; more than 8M, we need to change the logic for loading it anyway...) +; +is_linux_kernel: + cmp dx,80h ; 8 megs + ja kernel_corrupt + and dx,dx + jz kernel_corrupt +kernel_sane: push ax + push dx + push si + mov si,loading_msg + call cwritestr +; +; Now start transferring the kernel +; + push word real_mode_seg + pop es + + push ax + push dx + div word [ClustSize] ; # of clusters total + and dx,dx ; Round up + setnz dl + movzx dx,dl + add ax,dx + mov [KernelClust],ax + pop dx + pop ax + mov [KernelSize],ax + mov [KernelSize+2],dx +; +; Now, if we transfer these straight, we'll hit 64K boundaries. Hence we +; have to see if we're loading more than 64K, and if so, load it step by +; step. +; + mov dx,1 ; 10000h + xor ax,ax + div word [ClustSize] + mov [ClustPerMoby],ax ; Clusters/64K +; +; Start by loading the bootsector/setup code, to see if we need to +; do something funky. It should fit in the first 32K (loading 64K won't +; work since we might have funny stuff up near the end of memory). +; If we have larger than 32K clusters, yes, we're hosed. +; + call abort_check ; Check for abort key + mov cx,[ClustPerMoby] + shr cx,1 ; Half a moby + sub [KernelClust],cx + xor bx,bx + pop si ; Cluster pointer on stack + call getfssec + jc near kernel_corrupt ; Failure in first 32K + cmp word [es:bs_bootsign],0AA55h + jne near kernel_corrupt ; Boot sec signature missing +; +; Get the BIOS' idea of what the size of high memory is +; + push si ; Save our cluster pointer! + + mov ax,0e801h ; Query high memory (semi-recent) + int 15h + jc no_e801 + cmp ax,3c00h + ja no_e801 ; > 3C00h something's wrong with this call + jb e801_hole ; If memory hole we can only use low part + + mov ax,bx + shl eax,16 ; 64K chunks + add eax,(16 << 20) ; Add first 16M + jmp short got_highmem + +no_e801: + mov ah,88h ; Query high memory (oldest) + int 15h + cmp ax,14*1024 ; Don't trust memory >15M + jna e801_hole + mov ax,14*1024 +e801_hole: + and eax,0ffffh + shl eax,10 ; Convert from kilobytes + add eax,(1 << 20) ; First megabyte +got_highmem: + mov [HighMemSize],eax +; +; Construct the command line (append options have already been copied) +; + mov word [es:kern_cmd_magic],0A33Fh ; Command line magic no + mov word [es:kern_cmd_offset],cmd_line_here + mov di,[CmdLinePtr] + mov si,boot_image ; BOOT_IMAGE= + mov cx,boot_image_len + rep movsb + mov si,KernelCName ; Unmangled kernel name + mov cx,[KernelCNameLen] + rep movsb + mov al,' ' ; Space + stosb + mov si,[CmdOptPtr] ; Options from user input + mov cx,(kern_cmd_len+3) >> 2 + rep movsd +; +%ifdef debug + push ds ; DEBUG DEBUG DEBUG + push es + pop ds + mov si,offset cmd_line_here + call cwritestr + pop ds + mov si,offset crlf_msg + call cwritestr +%endif +; +; Scan through the command line for anything that looks like we might be +; interested in. The original version of this code automatically assumed +; the first option was BOOT_IMAGE=, but that is no longer certain. +; + mov si,cmd_line_here + mov byte [initrd_flag],0 + push es ; Set DS <- real_mode_seg + pop ds +get_next_opt: lodsb + and al,al + jz near cmdline_end + cmp al,' ' + jbe get_next_opt + dec si + mov eax,[si] + cmp eax,'vga=' + je is_vga_cmd + cmp eax,'mem=' + je is_mem_cmd + push es ; Save ES -> real_mode_seg + push ss + pop es ; Set ES <- normal DS + mov di,initrd_cmd + mov cx,initrd_cmd_len + repe cmpsb + jne not_initrd + mov di,InitRD + push si ; mangle_dir mangles si + call mangle_name ; Mangle ramdisk name + pop si + cmp byte [es:InitRD],' ' ; Null filename? + seta byte [es:initrd_flag] ; Set flag if not +not_initrd: pop es ; Restore ES -> real_mode_seg +skip_this_opt: lodsb ; Load from command line + cmp al,' ' + ja skip_this_opt + dec si + jmp short get_next_opt +is_vga_cmd: + add si,byte 4 + mov eax,[si] + mov bx,-1 + cmp eax, 'norm' ; vga=normal + je vc0 + and eax,0ffffffh ; 3 bytes + mov bx,-2 + cmp eax, 'ext' ; vga=ext + je vc0 + mov bx,-3 + cmp eax, 'ask' ; vga=ask + je vc0 + call parseint ; vga=<number> + jc skip_this_opt ; Not an integer +vc0: mov [bs_vidmode],bx ; Set video mode + jmp short skip_this_opt +is_mem_cmd: + add si,byte 4 + call parseint + jc skip_this_opt ; Not an integer + mov [ss:HighMemSize],ebx + jmp short skip_this_opt +cmdline_end: + push ss ; Restore standard DS + pop ds +; +; Now check if we have a large kernel, which needs to be loaded high +; + cmp dword [es:su_header],HEADER_ID ; New setup code ID + jne near old_kernel ; Old kernel, load low + cmp word [es:su_version],0200h ; Setup code version 2.0 + jb near old_kernel ; Old kernel, load low + cmp word [es:su_version],0201h ; Version 2.01+? + jb new_kernel ; If 2.00, skip this step + mov word [es:su_heapend],linux_stack ; Set up the heap + or byte [es:su_loadflags],80h ; Let the kernel know we care +; +; We definitely have a new-style kernel. Let the kernel know who we are, +; and that we are clueful +; +new_kernel: + mov byte [es:su_loader],syslinux_id ; Show some ID + movzx ax,byte [es:bs_setupsecs] ; Variable # of setup sectors + mov [SetupSecs],ax +; +; Now see if we have an initial RAMdisk; if so, do requisite computation +; + test byte [initrd_flag],1 + jz nk_noinitrd + push es ; ES->real_mode_seg + push ds + pop es ; We need ES==DS + mov si,InitRD + mov di,InitRDCName + call unmangle_name ; Create human-readable name + sub di,InitRDCName + mov [InitRDCNameLen],di + mov di,InitRD + call searchdir ; Look for it in directory + pop es + jz initrd_notthere + mov [initrd_ptr],si ; Save cluster pointer + mov [es:su_ramdisklen1],ax ; Ram disk length + mov [es:su_ramdisklen2],dx + div word [ClustSize] + and dx,dx ; Round up + setnz dl + movzx dx,dl + add ax,dx + mov [InitRDClust],ax ; Ramdisk clusters + mov edx,[HighMemSize] ; End of memory + mov eax,HIGHMEM_MAX ; Limit imposed by kernel + cmp edx,eax + jna memsize_ok + mov edx,eax ; Adjust to fit inside limit +memsize_ok: + sub edx,[es:su_ramdisklen] ; Subtract size of ramdisk + xor dx,dx ; Round down to 64K boundary + mov [InitRDat],edx ; Load address + call loadinitrd ; Load initial ramdisk + jmp short initrd_end + +initrd_notthere: + mov si,err_noinitrd + call cwritestr + mov si,InitRDCName + call cwritestr + mov si,crlf_msg + jmp abort_load + +no_high_mem: mov si,err_nohighmem ; Error routine + jmp abort_load +; +; About to load the kernel. This is a modern kernel, so use the boot flags +; we were provided. +; +nk_noinitrd: +initrd_end: + mov al,[es:su_loadflags] + mov [LoadFlags],al +; +; Load the kernel. We always load it at 100000h even if we're supposed to +; load it "low"; for a "low" load we copy it down to low memory right before +; jumping to it. +; +read_kernel: + mov si,KernelCName ; Print kernel name part of + call cwritestr ; "Loading" message + mov si,dotdot_msg ; Print dots + call cwritestr + + mov eax,[HighMemSize] + sub eax,100000h ; Load address + cmp eax,[KernelSize] + jb no_high_mem ; Not enough high memory +; +; Move the stuff beyond the setup code to high memory at 100000h +; + movzx esi,word [SetupSecs] ; Setup sectors + inc esi ; plus 1 boot sector + shl esi,9 ; Convert to bytes + mov ecx,108000h ; 108000h = 1M + 32K + sub ecx,esi ; Adjust pointer to 2nd block + mov [HiLoadAddr],ecx + sub ecx,100000h ; Turn into a counter + shr ecx,2 ; Convert to dwords + add esi,90000h ; Pointer to source + mov edi,100000h ; Copy to address 100000h + call bcopy ; Transfer to high memory +; + push word xfer_buf_seg ; Segment 7000h is xfer buffer + pop es +high_load_loop: + mov si,dot_msg ; Progress report + call cwritestr + call abort_check + mov cx,[KernelClust] + cmp cx,[ClustPerMoby] + jna high_last_moby + mov cx,[ClustPerMoby] +high_last_moby: + sub [KernelClust],cx + xor bx,bx ; Load at offset 0 + pop si ; Restore cluster pointer + call getfssec + push si ; Save cluster pointer + pushf ; Save EOF + xor bx,bx + mov esi,(xfer_buf_seg << 4) + mov edi,[HiLoadAddr] ; Destination address + mov ecx,4000h ; Cheating - transfer 64K + call bcopy ; Transfer to high memory + mov [HiLoadAddr],edi ; Point to next target area + popf ; Restore EOF + jc high_load_done ; If EOF we are done + cmp word [KernelClust],byte 0 ; Are we done? + jne high_load_loop ; Apparently not +high_load_done: + pop si ; No longer needed + mov ax,real_mode_seg ; Set to real mode seg + mov es,ax + + mov si,dot_msg + call cwritestr +; +; Abandon hope, ye that enter here! We do no longer permit aborts. +; + call abort_check ; Last chance!! + +; +; Some kernels in the 1.2 ballpark but pre-bzImage have more than 4 +; setup sectors, but the boot protocol had not yet been defined. They +; rely on a signature to figure out if they need to copy stuff from +; the "protected mode" kernel area. Unfortunately, we used that area +; as a transfer buffer, so it's going to find the signature there. +; Hence, zero the low 32K beyond the setup area. +; + mov di,[SetupSecs] + inc di ; Setup + boot sector + mov cx,32768/512 ; Sectors/32K + sub cx,di ; Remaining sectors + shl di,9 ; Sectors -> bytes + shl cx,7 ; Sectors -> dwords + xor eax,eax + rep stosd ; Clear region +; +; Now, if we were supposed to load "low", copy the kernel down to 10000h +; + test byte [LoadFlags],LOAD_HIGH + jnz in_proper_place ; If high load, we're done + + mov ecx,[KernelSize] + add ecx,3 ; Round upwards + shr ecx,2 ; Bytes -> dwords + mov esi,100000h + mov edi,10000h + call bcopy +in_proper_place: +; +; If the default root device is set to FLOPPY (0000h), change to +; /dev/fd0 (0200h) +; + cmp word [es:bs_rootdev],byte 0 + jne root_not_floppy + mov word [es:bs_rootdev],0200h +root_not_floppy: +; +; Linux wants the floppy motor shut off before starting the kernel, +; at least bootsect.S seems to imply so +; +kill_motor: + mov dx,03F2h + xor al,al + call slow_out +; +; Now we're as close to be done as we can be and still use our normal +; routines, print a CRLF to end the row of dots +; + mov si,crlf_msg + call cwritestr +; +; If we're debugging, wait for a keypress so we can read any debug messages +; +%ifdef debug + xor ax,ax + int 16h +%endif +; +; Set up segment registers and the Linux real-mode stack +; + mov ax,real_mode_seg + mov ds,ax + mov es,ax + mov fs,ax + mov gs,ax + cli + mov ss,ax + mov sp,linux_stack + sti +; +; We're done... now RUN THAT KERNEL!!!! +; + jmp setup_seg:setup_entry +; +; Load an older kernel. Older kernels always have 4 setup sectors, can't have +; initrd, and are always loaded low. +; +old_kernel: + test byte [initrd_flag],1 ; Old kernel can't have initrd + jz load_old_kernel + mov si,err_oldkernel + jmp abort_load +load_old_kernel: + mov word [SetupSecs],4 ; Always 4 setup sectors + mov byte [LoadFlags],0 ; Always low + jmp read_kernel + +; +; Load a COMBOOT image. A COMBOOT image is basically a DOS .COM file, +; except that it may, of course, not contain any DOS system calls. We +; do, however, allow the execution of INT 20h to return to SYSLINUX. +; +is_comboot_image: + and dx,dx + jnz comboot_too_large + cmp ax,0ff00h ; Max size in bytes + jae comboot_too_large + + ; + ; Set up the DOS vectors in the IVT (INT 20h-3fh) + ; + mov dword [4*0x20],comboot_return ; INT 20h vector + mov eax,comboot_bogus + mov di,4*0x21 + mov cx,31 ; All remaining DOS vectors + rep stosd + + mov cx,comboot_seg + mov es,cx + + mov bx,100h ; Load at <seg>:0100h + + mov cx,[ClustPerMoby] ; Absolute maximum # of clusters + call getfssec + + xor di,di + mov cx,64 ; 256 bytes (size of PSP) + xor eax,eax ; Clear PSP + rep stosd + + mov word [es:0], 020CDh ; INT 20h instruction + ; First non-free paragraph + mov word [es:02h], comboot_seg+1000h + + ; Copy the command line from high memory + mov cx,125 ; Max cmdline len (minus space and CR) + mov si,[CmdOptPtr] + mov di,081h ; Offset in PSP for command line + mov al,' ' ; DOS command lines begin with a space + stosb + +comboot_cmd_cp: lodsb + and al,al + jz comboot_end_cmd + stosb + loop comboot_cmd_cp +comboot_end_cmd: mov al,0Dh ; CR after last character + stosb + mov al,126 ; Include space but not CR + sub al,cl + mov [es:80h], al ; Store command line length + + mov ax,es + mov ds,ax + mov ss,ax + xor sp,sp + push word 0 ; Return to address 0 -> exit + + jmp comboot_seg:100h ; Run it + +; Looks like a COMBOOT image but too large +comboot_too_large: + mov si,err_comlarge + call cwritestr +cb_enter: jmp enter_command + +; Proper return vector +comboot_return: cli ; Don't trust anyone + xor ax,ax + mov ss,ax + mov sp,[ss:SavedSP] + mov ds,ax + mov es,ax + sti + cld + jmp short cb_enter + +; Attempted to execute DOS system call +comboot_bogus: cli ; Don't trust anyone + xor ax,ax + mov ss,ax + mov sp,[ss:SavedSP] + mov ds,ax + mov es,ax + sti + cld + mov si,KernelCName + call cwritestr + mov si,err_notdos + call cwritestr + jmp short cb_enter + +; +; Load a boot sector +; +is_bootsector: +is_bss_sector: + ; Can't load these from the network, dang it! +.badness: jmp short .badness + +; +; 32-bit bcopy routine for real mode +; +; We enter protected mode, set up a flat 32-bit environment, run rep movsd +; and then exit. IMPORTANT: This code assumes cs == ss == 0. +; +; This code is probably excessively anal-retentive in its handling of +; segments, but this stuff is painful enough as it is without having to rely +; on everything happening "as it ought to." +; + align 4 +bcopy_gdt: dw bcopy_gdt_size-1 ; Null descriptor - contains GDT + dd bcopy_gdt ; pointer for LGDT instruction + dw 0 + dd 0000ffffh ; Code segment, use16, readable, + dd 00009b00h ; present, dpl 0, cover 64K + dd 0000ffffh ; Data segment, use16, read/write, + dd 008f9300h ; present, dpl 0, cover all 4G + dd 0000ffffh ; Data segment, use16, read/write, + dd 00009300h ; present, dpl 0, cover 64K +bcopy_gdt_size: equ $-bcopy_gdt + +bcopy: push eax + pushf ; Saves, among others, the IF flag + push gs + push fs + push ds + push es + + cli + call enable_a20 + + o32 lgdt [bcopy_gdt] + mov eax,cr0 + or al,1 + mov cr0,eax ; Enter protected mode + jmp 08h:.in_pm + +.in_pm: mov ax,10h ; Data segment selector + mov es,ax + mov ds,ax + + mov al,18h ; "Real-mode-like" data segment + mov ss,ax + mov fs,ax + mov gs,ax + + a32 rep movsd ; Do our business + + mov es,ax ; Set to "real-mode-like" + mov ds,ax + + mov eax,cr0 + and al,~1 + mov cr0,eax ; Disable protected mode + jmp 0:.in_rm + +.in_rm: xor ax,ax ; Back in real mode + mov ss,ax + pop es + pop ds + pop fs + pop gs + call disable_a20 + + popf ; Re-enables interrupts + pop eax + ret + +; +; Routines to enable and disable (yuck) A20. These routines are gathered +; from tips from a couple of sources, including the Linux kernel and +; http://www.x86.org/. The need for the delay to be as large as given here +; is indicated by Donnie Barnes of RedHat, the problematic system being an +; IBM ThinkPad 760EL. +; +; We typically toggle A20 twice for every 64K transferred. +; +%define io_delay call _io_delay +%define IO_DELAY_PORT 80h ; Invalid port (we hope!) +%define delaytime 1024 ; 4 x ISA bus cycles (@ 1.5 µs) + +slow_out: out dx, al ; Fall through + +_io_delay: push ax + in ax,IO_DELAY_PORT + in ax,IO_DELAY_PORT + in ax,IO_DELAY_PORT + in ax,IO_DELAY_PORT + pop ax + ret + +enable_a20: + mov byte [ss:A20Tries],255 ; Times to try to make this work + +try_enable_a20: +; +; Flush the caches +; + call try_wbinvd + +; +; Enable the "fast A20 gate" +; + in al, 092h + or al,02h + out 092h, al +; +; Enable the keyboard controller A20 gate +; + call empty_8042 + mov al,0D1h ; Command write + out 064h, al + call empty_8042 + mov al,0DFh ; A20 on + out 060h, al + call kbc_delay + ; Verify that A20 actually is enabled. Do that by + ; observing a word in low memory and the same word in + ; the HMA until they are no longer coherent. Note that + ; we don't do the same check in the disable case, because + ; we don't want to *require* A20 masking (SYSLINUX should + ; work fine without it, if the BIOS does.) + push es + push cx + mov cx,0FFFFh ; Times to try, also HMA + mov es,cx ; HMA = segment 0FFFFh +.a20_wait: inc word [ss:A20Test] + call try_wbinvd + mov ax,[es:A20Test+10h] + cmp ax,[ss:A20Test] + jne .a20_done + loop .a20_wait +; +; Oh bugger. A20 is not responding. Try frobbing it again; eventually give up +; and report failure to the user. +; + pop cx + pop es + + dec byte [ss:A20Tries] + jnz try_enable_a20 + + mov si, err_a20 + jmp abort_load +; +; A20 unmasked, proceed... +; +.a20_done: pop cx + pop es + ret + +disable_a20: +; +; Flush the caches +; + call try_wbinvd +; +; Disable the "fast A20 gate" +; + in al, 092h + and al,~02h + out 092h, al +; +; Disable the keyboard controller A20 gate +; + call empty_8042 + mov al,0D1h + out 064h, al ; Command write + call empty_8042 + mov al,0DDh ; A20 off + out 060h, al + ; Fall through/tailcall to kbc_delay + +kbc_delay: call empty_8042 + push cx + mov cx, delaytime +.delayloop: io_delay + loop .delayloop + pop cx + ret + +empty_8042: + io_delay + in al, 064h ; Status port + test al,1 + jz .no_output + io_delay + in al, 060h ; Read input + jmp short empty_8042 +.no_output: + test al,2 + jnz empty_8042 + io_delay + ret + +; +; WBINVD instruction; gets auto-eliminated on 386 CPUs +; +try_wbinvd: + wbinvd + ret + +; +; Load RAM disk into high memory +; +loadinitrd: + push es ; Save ES on entry + mov ax,real_mode_seg + mov es,ax + mov si,[initrd_ptr] + mov edi,[InitRDat] ; initrd load address + mov [es:su_ramdiskat],edi ; Offset for ram disk + push si + mov si,InitRDCName ; Write ramdisk name + call cwritestr + mov si,dotdot_msg ; Write dots + call cwritestr +rd_load_loop: + mov si,dot_msg ; Progress report + call cwritestr + pop si ; Restore cluster pointer + call abort_check + mov cx,[InitRDClust] + cmp cx,[ClustPerMoby] + jna rd_last_moby + mov cx,[ClustPerMoby] +rd_last_moby: + sub [InitRDClust],cx + xor bx,bx ; Load at offset 0 + push word xfer_buf_seg ; Bounce buffer segment + pop es + push cx + call getfssec + pop cx + push si ; Save cluster pointer + mov esi,(xfer_buf_seg << 4) + mov edi,[InitRDat] + mov ecx,4000h ; Copy 64K + call bcopy ; Does not change flags!! + jc rd_load_done ; EOF? + add dword [InitRDat],10000h ; Point to next 64K + cmp word [InitRDClust],byte 0 ; Are we done? + jne rd_load_loop ; Apparently not +rd_load_done: + pop si ; Clean up the stack + mov si,crlf_msg + call cwritestr + mov si,loading_msg ; Write new "Loading " for + call cwritestr ; the benefit of the kernel + pop es ; Restore original ES + ret + +; +; abort_check: let the user abort with <ESC> or <Ctrl-C> +; +abort_check: + call pollchar + jz ac_ret1 + pusha + call getchar + cmp al,27 ; <ESC> + je ac_kill + cmp al,3 ; <Ctrl-C> + jne ac_ret2 +ac_kill: mov si,aborted_msg + +; +; abort_load: Called by various routines which wants to print a fatal +; error message and return to the command prompt. Since this +; may happen at just about any stage of the boot process, assume +; our state is messed up, and just reset the segment registers +; and the stack forcibly. +; +; SI = offset (in _text) of error message to print +; +abort_load: + mov ax,cs ; Restore CS = DS = ES + mov ds,ax + mov es,ax + cli + mov sp,StackBuf-2*3 ; Reset stack + mov ss,ax ; Just in case... + sti + call cwritestr ; Expects SI -> error msg +al_ok: jmp enter_command ; Return to command prompt +; +; End of abort_check +; +ac_ret2: popa +ac_ret1: ret + + +; +; kaboom: write a message and bail out. +; +kaboom: + lss sp,[cs:Stack] + pop ds +.patch: mov si,bailmsg + call writestr ; Returns with AL = 0 + cbw ; AH <- 0 + int 16h ; Wait for keypress + int 19h ; And try once more to boot... +.norge: jmp short .norge ; If int 19h returned; this is the end + +; +; +; writestr: write a null-terminated string to the console +; +writestr: +wstr_1: lodsb + and al,al + jz .return + mov ah,0Eh ; Write to screen as TTY + mov bx,0007h ; White on black, current page + int 10h + jmp short wstr_1 +.return: ret + +; +; searchdir: Search the root directory for a pre-mangled filename in +; DS:DI. This routine is similar to the one in the boot +; sector, but is a little less Draconian when it comes to +; error handling, plus it reads the root directory in +; larger chunks than a sector at a time (which is probably +; a waste of coding effort, but I like to do things right). +; +; FIXME: usually we can load the entire root dir in memory, +; and files are usually at the beginning anyway. It probably +; would be worthwhile to remember if we have the first chunk +; in memory and skip the load if that (it would speed up online +; help, mainly.) +; +; NOTE: This file considers finding a zero-length file an +; error. This is so we don't have to deal with that special +; case elsewhere in the program (most loops have the test +; at the end). +; +; If successful: +; ZF clear +; SI = cluster # for the first cluster +; DX:AX = file length in bytes +; If unsuccessful +; ZF set +; + +searchdir: + ; No longer applicable +dir_return: + ret + +; +; adjust_screen: Set the internal variables associated with the screen size. +; This is a subroutine in case we're loading a custom font. +; +adjust_screen: + mov al,[BIOS_vidrows] + and al,al + jnz vidrows_is_ok + mov al,24 ; No vidrows in BIOS, assume 25 + ; (Remember: vidrows == rows-1) +vidrows_is_ok: mov [VidRows],al + mov ah,0fh + int 10h ; Read video state + mov [TextPage],bh + dec ah ; Store count-1 (same as rows) + mov [VidCols],ah +bf_ret: ret + +; +; loadfont: Load a .psf font file and install it onto the VGA console +; (if we're not on a VGA screen then ignore.) It is called with +; SI and DX:AX set by routine searchdir +; +loadfont: + mov bx,trackbuf ; The trackbuf is >= 16K; the part + mov cx,[BufSafe] ; of a PSF file we care about is no + call getfssec ; more than 8K+4 bytes + + mov ax,[trackbuf] ; Magic number + cmp ax,0436h + jne bf_ret + + mov al,[trackbuf+2] ; File mode + cmp al,3 ; Font modes 0-3 supported + ja bf_ret + + mov bh,byte [trackbuf+3] ; Height of font + cmp bh,2 ; VGA minimum + jb bf_ret + cmp bh,32 ; VGA maximum + ja bf_ret + + mov bp,trackbuf+4 ; Address of font data + xor bl,bl + mov cx,256 + xor dx,dx + mov ax,1110h + int 10h ; Load into VGA RAM + + xor bl,bl + mov ax,1103h ; Select page 0 + int 10h + + jmp short adjust_screen + +; +; loadkeys: Load a LILO-style keymap; SI and DX:AX set by searchdir +; +loadkeys: + and dx,dx ; Should be 256 bytes exactly + jne loadkeys_ret + cmp ax,256 + jne loadkeys_ret + + mov bx,trackbuf + mov cx,1 ; 1 cluster should be >= 256 bytes + call getfssec + + mov si,trackbuf + mov di,KbdMap + mov cx,256 >> 2 + rep movsd + +loadkeys_ret: ret + +; +; get_msg_file: Load a text file and write its contents to the screen, +; interpreting color codes. Is called with SI and DX:AX +; set by routine searchdir +; +get_msg_file: + mov word [NextCharJump],msg_putchar ; State machine for color + mov byte [TextAttribute],07h ; Default grey on white + pusha + mov bh,[TextPage] + mov ah,03h ; Read cursor position + int 10h + mov [CursorDX],dx + popa +get_msg_chunk: push ax ; DX:AX = length of file + push dx + mov bx,trackbuf + mov cx,[BufSafe] + call getfssec + pop dx + pop ax + push si ; Save current cluster + mov si,trackbuf + mov cx,[BufSafeBytes] ; No more than many bytes +print_msg_file: push cx + push ax + push dx + lodsb + cmp al,1Ah ; ASCII EOF? + je msg_done_pop + call [NextCharJump] ; Do what shall be done + pop dx + pop ax + pop cx + sub ax,byte 1 + sbb dx,byte 0 + mov bx,ax + or bx,dx + jz msg_done + loop print_msg_file + pop si + jmp short get_msg_chunk +msg_done_pop: + add sp,byte 6 ; Lose 3 words on the stack +msg_done: + pop si + ret +msg_putchar: ; Normal character + cmp al,0Fh ; ^O = color code follows + je msg_ctrl_o + cmp al,0Dh ; Ignore <CR> + je msg_ignore + cmp al,0Ah ; <LF> = newline + je msg_newline + cmp al,0Ch ; <FF> = clear screen + je msg_formfeed + +msg_normal: call write_serial ; Write to serial port + mov bx,[TextAttrBX] + mov ah,09h ; Write character/attribute + mov cx,1 ; One character only + int 10h ; Write to screen + mov al,[CursorCol] + inc ax + cmp al,[VidCols] + ja msg_newline + mov [CursorCol],al + +msg_gotoxy: mov bh,[TextPage] + mov dx,[CursorDX] + mov ah,02h ; Set cursor position + int 10h +msg_ignore: ret +msg_ctrl_o: ; ^O = color code follows + mov word [NextCharJump],msg_setbg + ret +msg_newline: ; Newline char or end of line + push si + mov si,crlf_msg + call write_serial_str + pop si + mov byte [CursorCol],0 + mov al,[CursorRow] + inc ax + cmp al,[VidRows] + ja msg_scroll + mov [CursorRow],al + jmp short msg_gotoxy +msg_scroll: xor cx,cx ; Upper left hand corner + mov dx,[ScreenSize] + mov [CursorRow],dh ; New cursor at the bottom + mov bh,[TextAttribute] + mov ax,0601h ; Scroll up one line + int 10h + jmp short msg_gotoxy +msg_formfeed: ; Form feed character + push si + mov si,crff_msg + call write_serial_str + pop si + xor cx,cx + mov [CursorDX],cx ; Upper lefthand corner + mov dx,[ScreenSize] + mov bh,[TextAttribute] + mov ax,0600h ; Clear screen region + int 10h + jmp short msg_gotoxy +msg_setbg: ; Color background character + call unhexchar + jc msg_color_bad + shl al,4 + mov [TextAttribute],al + mov word [NextCharJump],msg_setfg + ret +msg_setfg: ; Color foreground character + call unhexchar + jc msg_color_bad + or [TextAttribute],al ; setbg set foreground to 0 + mov word [NextCharJump],msg_putchar + ret +msg_color_bad: + mov byte [TextAttribute],07h ; Default attribute + mov word [NextCharJump],msg_putchar + ret + +; +; write_serial: If serial output is enabled, write character on serial port +; +write_serial: + pusha + mov bx,[SerialPort] + and bx,bx + je .noserial + push ax +.waitspace: lea dx,[bx+5] ; Wait for space in transmit reg + in al,dx + test al,20h + jz .waitspace + xchg dx,bx + pop ax + call slow_out ; Send data +.noserial: popa + ret + +; +; write_serial_str: write_serial for strings +; +write_serial_str: +.loop lodsb + and al,al + jz .end + call write_serial + jmp short .loop +.end: ret + +; +; writechr: Write a single character in AL to the console without +; mangling any registers +; +writechr: + call write_serial ; write to serial port if needed + pusha + mov ah,0Eh + mov bx,0007h ; white text on this page + int 10h + popa + ret + +; +; cwritestr: write a null-terminated string to the console, saving +; registers on entry. +; +cwritestr: + pusha +.top: lodsb + and al,al + jz .end + call writechr + jmp short .top +.end: popa + ret + +; +; pollchar: check if we have an input character pending (ZF = 0) +; +pollchar: + pusha + mov ah,1 ; Poll keyboard + int 16h + jnz .done ; Keyboard response + mov dx,[SerialPort] + and dx,dx + jz .done ; No serial port -> no input + add dx,byte 5 ; Serial status register + in al,dx + test al,1 ; ZF = 0 if traffic +.done: popa + ret + +; +; getchar: Read a character from keyboard or serial port +; +getchar: +.again: mov ah,1 ; Poll keyboard + int 16h + jnz .kbd ; Keyboard input? + mov bx,[SerialPort] + and bx,bx + jz .again + lea dx,[bx+5] ; Serial status register + in al,dx + test al,1 + jz .again +.serial: xor ah,ah ; Avoid confusion + xchg dx,bx ; Data port + in al,dx + ret +.kbd: xor ax,ax ; Get keyboard input + int 16h + and al,al + jz .func_key + mov bx,KbdMap ; Convert character sets + xlatb +.func_key: ret + +; +; +; kaboom2: once everything is loaded, replace the part of kaboom +; starting with "kaboom.patch" with this part + +kaboom2: + mov si,err_bootfailed + call cwritestr + call getchar + int 19h ; And try once more to boot... +.norge: jmp short .norge ; If int 19h returned; this is the end + +; +; open,getc: Load a file a character at a time for parsing in a manner +; similar to the C library getc routine. Only one simultaneous +; use is supported. Note: "open" trashes the trackbuf. +; +; open: Input: mangled filename in DS:DI +; Output: ZF set on file not found or zero length +; +; getc: Output: CF set on end of file +; Character loaded in AL +; +open: + call searchdir + jz open_return + pushf + mov [FBytes1],ax + mov [FBytes2],dx + add ax,[ClustSize] + adc dx,byte 0 + sub ax,byte 1 + sbb dx,byte 0 + div word [ClustSize] + mov [FClust],ax ; Number of clusters + mov [FNextClust],si ; Cluster pointer + mov ax,[EndOfGetCBuf] ; Pointer at end of buffer -> + mov [FPtr],ax ; nothing loaded yet + popf ; Restore no ZF +open_return: ret + +; +getc: + stc ; If we exit here -> EOF + mov ecx,[FBytes] + jecxz getc_ret + mov si,[FPtr] + cmp si,[EndOfGetCBuf] + jb getc_loaded + ; Buffer empty -- load another set + mov cx,[FClust] + cmp cx,[BufSafe] + jna getc_oksize + mov cx,[BufSafe] +getc_oksize: sub [FClust],cx ; Reduce remaining clusters + mov si,[FNextClust] + mov bx,getcbuf + push bx + push es ; ES may be != DS, save old ES + push ds ; Trackbuf is in DS, not ES + pop es + call getfssec ; Load a trackbuf full of data + mov [FNextClust],si ; Store new next pointer + pop es ; Restore ES + pop si ; SI -> newly loaded data +getc_loaded: lodsb ; Load a byte + mov [FPtr],si ; Update next byte pointer + dec dword [FBytes] ; Update bytes left counter (CF = 1) + clc ; Not EOF +getc_ret: ret + +; +; ungetc: Push a character (in AL) back into the getc buffer +; Note: if more than one byte is pushed back, this may cause +; bytes to be written below the getc buffer boundary. If there +; is a risk for this to occur, the getcbuf base address should +; be moved up. +; +ungetc: + mov si,[FPtr] + dec si + mov [si],al + mov [FPtr],si + inc dword [FBytes] + ret + +; +; skipspace: Skip leading whitespace using "getc". If we hit end-of-line +; or end-of-file, return with carry set; ZF = true of EOF +; ZF = false for EOLN; otherwise CF = ZF = 0. +; +; Otherwise AL = first character after whitespace +; +skipspace: +skipspace_loop: call getc + jc skipspace_eof + cmp al,1Ah ; DOS EOF + je skipspace_eof + cmp al,0Ah + je skipspace_eoln + cmp al,' ' + jbe skipspace_loop + ret ; CF = ZF = 0 +skipspace_eof: cmp al,al ; Set ZF + stc ; Set CF + ret +skipspace_eoln: add al,0FFh ; Set CF, clear ZF + ret + +; +; getkeyword: Get a keyword from the current "getc" file; only the two +; first characters are considered significant. +; +; Lines beginning with ASCII characters 33-47 are treated +; as comments and ignored; other lines are checked for +; validity by scanning through the keywd_table. +; +; The keyword and subsequent whitespace is skipped. +; +; On EOF, CF = 1; otherwise, CF = 0, AL:AH = lowercase char pair +; +getkeyword: +gkw_find: call skipspace + jz gkw_eof ; end of file + jc gkw_find ; end of line: try again + cmp al,'0' + jb gkw_skipline ; skip comment line + push ax + call getc + pop bx + jc gkw_eof + mov bh,al ; Move character pair into BL:BH + or bx,2020h ; Lower-case it + mov si,keywd_table +gkw_check: lodsw + and ax,ax + jz gkw_badline ; Bad keyword, write message + cmp ax,bx + jne gkw_check + push ax +gkw_skiprest: + call getc + jc gkw_eof_pop + cmp al,'0' + ja gkw_skiprest + call ungetc + call skipspace + jz gkw_eof_pop + jc gkw_missingpar ; Missing parameter after keyword + call ungetc ; Return character to buffer + clc ; Successful return +gkw_eof_pop: pop ax +gkw_eof: ret ; CF = 1 on all EOF conditions +gkw_missingpar: pop ax + mov si,err_noparm + call cwritestr + jmp gkw_find +gkw_badline_pop: pop ax +gkw_badline: mov si,err_badcfg + call cwritestr + jmp short gkw_find +gkw_skipline: cmp al,10 ; Scan for LF + je gkw_find + call getc + jc gkw_eof + jmp short gkw_skipline + +; +; getint: Load an integer from the getc file. +; Return CF if error; otherwise return integer in EBX +; +getint: + mov di,NumBuf +gi_getnum: cmp di,NumBufEnd ; Last byte in NumBuf + jae gi_loaded + push di + call getc + pop di + jc gi_loaded + stosb + cmp al,'-' + jnb gi_getnum + call ungetc ; Unget non-numeric +gi_loaded: mov byte [di],0 + mov si,NumBuf + ; Fall through to parseint + +; +; parseint: Convert an integer to a number in EBX +; Get characters from string in DS:SI +; Return CF on error +; DS:SI points to first character after number +; +; Syntaxes accepted: [-]dec, [-]0+oct, [-]0x+hex, val+K, val+M +; +parseint: + push eax + push ecx + push bp + xor eax,eax ; Current digit (keep eax == al) + mov ebx,eax ; Accumulator + mov ecx,ebx ; Base + xor bp,bp ; Used for negative flag +pi_begin: lodsb + cmp al,'-' + jne pi_not_minus + xor bp,1 ; Set unary minus flag + jmp short pi_begin +pi_not_minus: + cmp al,'0' + jb pi_err + je pi_octhex + cmp al,'9' + ja pi_err + mov cl,10 ; Base = decimal + jmp short pi_foundbase +pi_octhex: + lodsb + cmp al,'0' + jb pi_km ; Value is zero + or al,20h ; Downcase + cmp al,'x' + je pi_ishex + cmp al,'7' + ja pi_err + mov cl,8 ; Base = octal + jmp short pi_foundbase +pi_ishex: + mov al,'0' ; No numeric value accrued yet + mov cl,16 ; Base = hex +pi_foundbase: + call unhexchar + jc pi_km ; Not a (hex) digit + cmp al,cl + jae pi_km ; Invalid for base + imul ebx,ecx ; Multiply accumulated by base + add ebx,eax ; Add current digit + lodsb + jmp short pi_foundbase +pi_km: + dec si ; Back up to last non-numeric + lodsb + or al,20h + cmp al,'k' + je pi_isk + cmp al,'m' + je pi_ism + dec si ; Back up +pi_fini: and bp,bp + jz pi_ret ; CF=0! + neg ebx ; Value was negative +pi_done: clc +pi_ret: pop bp + pop ecx + pop eax + ret +pi_err: stc + jmp short pi_ret +pi_isk: shl ebx,10 ; x 2^10 + jmp short pi_done +pi_ism: shl ebx,20 ; x 2^20 + jmp short pi_done + +; +; unhexchar: Convert a hexadecimal digit in AL to the equivalent number; +; return CF=1 if not a hex digit +; +unhexchar: + cmp al,'0' + jb uxc_ret ; If failure, CF == 1 already + cmp al,'9' + ja uxc_1 + sub al,'0' ; CF <- 0 + ret +uxc_1: or al,20h ; upper case -> lower case + cmp al,'a' + jb uxc_ret ; If failure, CF == 1 already + cmp al,'f' + ja uxc_err + sub al,'a'-10 ; CF <- 0 + ret +uxc_err: stc +uxc_ret: ret + +; +; +; getline: Get a command line, converting control characters to spaces +; and collapsing streches to one; a space is appended to the +; end of the string, unless the line is empty. +; The line is terminated by ^J, ^Z or EOF and is written +; to ES:DI. On return, DI points to first char after string. +; CF is set if we hit EOF. +; +getline: + call skipspace + mov dl,1 ; Empty line -> empty string. + jz gl_eof ; eof + jc gl_eoln ; eoln + call ungetc +gl_fillloop: push dx + push di + call getc + pop di + pop dx + jc gl_ret ; CF set! + cmp al,' ' + jna gl_ctrl + xor dx,dx +gl_store: stosb + jmp short gl_fillloop +gl_ctrl: cmp al,10 + je gl_ret ; CF clear! + cmp al,26 + je gl_eof + and dl,dl + jnz gl_fillloop ; Ignore multiple spaces + mov al,' ' ; Ctrl -> space + inc dx + jmp short gl_store +gl_eoln: clc ; End of line is not end of file + jmp short gl_ret +gl_eof: stc +gl_ret: pushf ; We want the last char to be space! + and dl,dl + jnz gl_xret + mov al,' ' + stosb +gl_xret: popf + ret + + +%ifdef debug ; This code for debugging only +; +; dumpregs: Dumps the contents of all registers +; + assume ds:_text, es:NOTHING, fs:NOTHING, gs:NOTHING +dumpregs proc near ; When calling, IP is on stack + pushf ; Store flags + pusha + push ds + push es + push fs + push gs + push cs ; Set DS <- CS + pop ds + cld ; Clear direction flag + mov si,offset crlf_msg + call cwritestr + mov bx,sp + add bx,byte 26 + mov si,offset regnames + mov cx,2 ; 2*7 registers to dump +dump_line: push cx + mov cx,7 ; 7 registers per line +dump_reg: push cx + mov cx,4 ; 4 characters/register name +wr_reg_name: lodsb + call writechr + loop wr_reg_name + mov ax,ss:[bx] + dec bx + dec bx + call writehex + pop cx + loop dump_reg + mov al,0Dh ; <CR> + call writechr + mov al,0Ah ; <LF> + call writechr + pop cx + loop dump_line + pop gs + pop fs + pop es + pop ds + popa ; Restore the remainder + popf ; Restore flags + ret +dumpregs endp + +regnames db ' IP: FL: AX: CX: DX: BX: SP: BP: SI: DI: DS: ES: FS: GS:' + +; +; writehex: Writes a 16-bit hexadecimal number (in AX) +; +writehex proc near + push bx + push cx + mov cx,4 ; 4 numbers +write_hexdig: xor bx,bx + push cx + mov cx,4 ; 4 bits/digit +xfer_digit: shl ax,1 + rcl bx,1 + loop xfer_digit + push ax + mov ax,bx + or al,'0' + cmp al,'9' + jna ok_digit + add al,'A'-'0'-10 +ok_digit: call writechr + pop ax + pop cx + loop write_hexdig + pop cx + pop bx + ret +writehex endp + +debug_magic dw 0D00Dh + +%endif ; debug +; +; mangle_name: Mangle a DOS filename pointed to by DS:SI into a buffer pointed +; to by ES:DI; ends on encountering any whitespace +; + +mangle_name: + mov cx,11 ; # of bytes to write +mn_loop: + lodsb + cmp al,' ' ; If control or space, end + jna mn_end + cmp al,'.' ; Period -> space-fill + je mn_is_period + cmp al,'a' + jb mn_not_lower + cmp al,'z' + ja mn_not_uslower + sub al,020h + jmp short mn_not_lower +mn_is_period: mov al,' ' ; We need to space-fill +mn_period_loop: cmp cx,3 ; If <= 3 characters left + jbe mn_loop ; Just ignore it + stosb ; Otherwise, write a period + loop mn_period_loop ; Dec CX and (always) jump +mn_not_uslower: cmp al,ucase_low + jb mn_not_lower + cmp al,ucase_high + ja mn_not_lower + mov bx,ucase_tab-ucase_low + cs xlatb +mn_not_lower: stosb + loop mn_loop ; Don't continue if too long +mn_end: + mov al,' ' ; Space-fill name + rep stosb ; Doesn't do anything if CX=0 + ret ; Done + +; +; Upper-case table for extended characters; this is technically code page 865, +; but code page 437 users will probably not miss not being able to use the +; cent sign in kernel images too much :-) +; +; The table only covers the range 129 to 164; the rest we can deal with. +; +ucase_low equ 129 +ucase_high equ 164 +ucase_tab db 154, 144, 'A', 142, 'A', 143, 128, 'EEEIII' + db 142, 143, 144, 146, 146, 'O', 153, 'OUUY', 153, 154 + db 157, 156, 157, 158, 159, 'AIOU', 165 + +; +; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled +; filename to the conventional representation. This is needed +; for the BOOT_IMAGE= parameter for the kernel. +; NOTE: A 13-byte buffer is mandatory, even if the string is +; known to be shorter. +; +; DS:SI -> input mangled file name +; ES:DI -> output buffer +; +; On return, DI points to the first byte after the output name, +; which is set to a null byte. +; +unmangle_name: + push si ; Save pointer to original name + mov cx,8 + mov bp,di +un_copy_body: lodsb + call lower_case + stosb + cmp al,' ' + jbe un_cb_space + mov bp,di ; Position of last nonblank+1 +un_cb_space: loop un_copy_body + mov di,bp + mov al,'.' ; Don't save + stosb + mov cx,3 +un_copy_ext: lodsb + call lower_case + stosb + cmp al,' ' + jbe un_ce_space + mov bp,di +un_ce_space: loop un_copy_ext + mov di,bp + mov byte [es:di], 0 + pop si + ret + +; +; lower_case: Lower case a character in AL +; +lower_case: + cmp al,'A' + jb lc_ret + cmp al,'Z' + ja lc_1 + or al,20h + ret +lc_1: cmp al,lcase_low + jb lc_ret + cmp al,lcase_high + ja lc_ret + push bx + mov bx,lcase_tab-lcase_low + cs xlatb + pop bx +lc_ret: ret + +; +; Lower-case table for codepage 865 +; +lcase_low equ 128 +lcase_high equ 165 +lcase_tab db 135, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138 + db 139, 140, 141, 132, 134, 130, 145, 145, 147, 148, 149 + db 150, 151, 152, 148, 129, 155, 156, 155, 158, 159, 160 + db 161, 162, 163, 164, 164 +; +; Various initialized or semi-initialized variables +; +copyright_str db ' Copyright (C) 1994-', year, ' H. Peter Anvin' + db 0Dh, 0Ah, 0 +boot_prompt db 'boot: ', 0 +wipe_char db 08h, ' ', 08h, 0 +err_notfound db 'Could not find kernel image: ',0 +err_notkernel db 0Dh, 0Ah, 'Invalid or corrupt kernel image.', 0Dh, 0Ah, 0 +err_not386 db 'It appears your computer uses a 286 or lower CPU.' + db 0Dh, 0Ah + db 'You cannot run Linux unless you have a 386 or higher CPU' + db 0Dh, 0Ah + db 'in your machine. If you get this message in error, hold' + db 0Dh, 0Ah + db 'down the Ctrl key while booting, and I will take your' + db 0Dh, 0Ah + db 'word for it.', 0Dh, 0Ah, 0 +err_noram db 'It appears your computer has less than 608K of low ("DOS")' + db 0Dh, 0Ah + db 'RAM. Linux needs at least this amount to boot. If you get' + db 0Dh, 0Ah + db 'this message in error, hold down the Ctrl key while' + db 0Dh, 0Ah + db 'booting, and I will take your word for it.', 0Dh, 0Ah, 0 +err_badcfg db 'Unknown keyword in syslinux.cfg.', 0Dh, 0Ah, 0 +err_noparm db 'Missing parameter in syslinux.cfg.', 0Dh, 0Ah, 0 +err_noinitrd db 0Dh, 0Ah, 'Could not find ramdisk image: ', 0 +err_nohighmem db 'Not enough memory to load specified kernel.', 0Dh, 0Ah, 0 +err_highload db 0Dh, 0Ah, 'Kernel transfer failure.', 0Dh, 0Ah, 0 +err_oldkernel db 'Cannot load a ramdisk with an old kernel image.' + db 0Dh, 0Ah, 0 +err_notdos db ': attempted DOS system call', 0Dh, 0Ah, 0 +err_comlarge db 'COMBOOT image too large.', 0Dh, 0Ah, 0 +err_bootsec db 'Invalid or corrupt boot sector image.', 0Dh, 0Ah, 0 +err_a20 db 0Dh, 0Ah, 'A20 gate not responding!', 0Dh, 0Ah, 0 +err_bootfailed db 0Dh, 0Ah, 'Boot failed: please change disks and press ' + db 'a key to continue.', 0Dh, 0Ah, 0 +bailmsg equ err_bootfailed +err_nopxe db 'Cannot find !PXE structure, I want my mommy!' ,0Dh, 0Ah, 0 +loading_msg db 'Loading ', 0 +dotdot_msg db '.' +dot_msg db '.', 0 +aborted_msg db ' aborted.' ; Fall through to crlf_msg! +crlf_msg db 0Dh, 0Ah, 0 +crff_msg db 0Dh, 0Ch, 0 +syslinux_cfg db 'SYSLINUXCFG' +pxelinux_banner db 0Dh, 0Ah, 'PXELINUX ', version_str, ' ', date, ' ', 0 + db 0Dh, 0Ah, 1Ah ; EOF if we "type" this in DOS + +; +; Command line options we'd like to take a look at +; +; mem= and vga= are handled as normal 32-bit integer values +initrd_cmd db 'initrd=' +initrd_cmd_len equ 7 +; +; Config file keyword table +; + align 2 +keywd_table db 'ap' ; append + db 'de' ; default + db 'ti' ; timeout + db 'fo' ; font + db 'kb' ; kbd + db 'di' ; display + db 'pr' ; prompt + db 'la' ; label + db 'im' ; implicit + db 'ke' ; kernel + db 'se' ; serial + db 'f1' ; F1 + db 'f2' ; F2 + db 'f3' ; F3 + db 'f4' ; F4 + db 'f5' ; F5 + db 'f6' ; F6 + db 'f7' ; F7 + db 'f8' ; F8 + db 'f9' ; F9 + db 'f0' ; F10 + dw 0 +; +; Extensions to search for (in *reverse* order). Note that the last +; (lexically first) entry in the table is a placeholder for the original +; extension, needed for error messages. The exten_table is shifted so +; the table is 1-based; this is because a "loop" cx is used as index. +; +exten_table: +OrigKernelExt: dd 0 ; Original extension + db 'COM',0 ; COMBOOT (same as DOS) + db 'BS ',0 ; Boot Sector + db 'BSS',0 ; Boot Sector (add superblock) + db 'CBT',0 ; COMBOOT (specific) + +exten_count equ (($-exten_table) >> 2) - 1 ; Number of alternates +; +; Misc initialized (data) variables +; +AppendLen dw 0 ; Bytes in append= command +KbdTimeOut dw 0 ; Keyboard timeout (if any) +FKeyMap dw 0 ; Bitmap for F-keys loaded +CmdLinePtr dw cmd_line_here ; Command line advancing pointer +initrd_flag equ $ +initrd_ptr dw 0 ; Initial ramdisk pointer/flag +VKernelCtr dw 0 ; Number of registered vkernels +ForcePrompt dw 0 ; Force prompt +AllowImplicit dw 1 ; Allow implicit kernels +SerialPort dw 0 ; Serial port base (or 0 for no serial port) +; +; Stuff for the command line; we do some trickery here with equ to avoid +; tons of zeros appended to our file and wasting space +; +linuxauto_cmd db 'linux ' +auto_cmd db 'auto',0 +linuxauto_len equ $-linuxauto_cmd +auto_len equ $-auto_cmd +boot_image db 'BOOT_IMAGE=' +boot_image_len equ $-boot_image + align 4, db 0 ; For the good of REP MOVSD +command_line equ $ +default_cmd equ $+(max_cmd_len+2) +ldlinux_end equ default_cmd+(max_cmd_len+1) +kern_cmd_len equ ldlinux_end-command_line +; +; Put the getcbuf right after the code, aligned on a sector boundary +; +end_of_code equ (ldlinux_end-bootsec)+7C00h +getcbuf equ (end_of_code + 511) & 0FE00h |