# first.S86 - primary boot loader # # Copyright (C) 1995-2003 Gero Kuhlmann # Copyright (C) 1996,1997 Gero Kuhlmann # and Markus Gutschke # # 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; either version 2 of the License, or # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # $Id: first.S86,v 1.1 2003/03/09 00:43:10 gkminix Exp $ # #==================================================================== # #include "common.i86" #include "first.i86" .file "first.S86" .line 32 # # Care for debugging symbols # .ifndef ASM_DEBUG ASM_DEBUG = 0 .endif .ifndef ASM_DEBUG_VERBOSE ASM_DEBUG_VERBOSE = 0 .endif .ifndef ASM_FREEZE_AFTER_INIT ASM_FREEZE_AFTER_INIT = 0 .endif .ifndef ASM_KEYPRESS_AFTER_INIT ASM_KEYPRESS_AFTER_INIT = 0 .endif .ifgt ASM_DEBUG_VERBOSE ASM_DEBUG = 1 .endif .ifgt ASM_KEYPRESS_AFTER_INIT ASM_FREEZE_AFTER_INIT = 0 .endif # #==================================================================== # # Define some external routines from the i386 library # \(.text) .extern nbentry # routines in nbentry.s86 .extern nbexit .extern doerr .extern ldrlen # routines in ldrec.s86 .extern ldradr .extern ldrfnd .extern btptag # routines in bootp.s86 .extern strlen # routines in string.s86 .extern strstr .extern prnstr # routines in scnio.s86 .extern prcrlf .extern prndwd .extern prnwrd .extern prnbyt .extern getext # routines in misc.s86 .extern getmem .extern movmem # #==================================================================== # # Now start the game # .global _start _start: jmp start1 .word __text_end # size of text segment .word __bss_end + STACK_SIZE # specify total size of data and # BSS segments # Call the i386 library startup routine. It will automatically care # for all arguments given by the bootrom, save all registers and setup # the load record and BOOTP information. start1: mov ax,offset sigmsg mov dx,offset vmagic call nbentry # setup everything # Get the size of the base memory from the BIOS. We need at least # 616 kB. call getmem # get memory size in kB .ifgt ASM_DEBUG mov dx,ax mov si,offset memmsg call prnstr mov ax,dx # let the user know about the amount call prnwrd # of memory available call prcrlf mov ax,dx .endif mov si,offset memerr # check if enough cmp ax,MIN_MEM jb doerr # Get the size of extended memory from the BIOS. This is needed for computing # the address of a loaded ramdisk image. call getext .ifgt ASM_DEBUG mov edx,eax mov si,offset extmsg call prnstr mov eax,edx # let the user know about the amount call prndwd # of memory available call prcrlf mov eax,edx .endif mov si,offset memerr cmp eax,0x00000C00 # we need at least 4MB jb doerr # Now we can check that all load segments for the lower memory including the # stack are within a range of 40kB, which is required by the Linux kernel # setup code. lfs di,[header] xor edx,edx # EDX contains the highest block found xor ecx,ecx # ECX contains the lowest block found dec ecx chkm1: call ldrlen # get the pointer to first load record add di,ax call ldradr # get the load address jc chkm3 test eax,0xFFF00000 # only allow addresses in lower 1MB jnz chkm3 cmp eax,ecx # check for lowest memory address jae chkm2 mov ecx,eax chkm2: add eax,fs:BOOT_LD_MLENGTH[di] cmp eax,edx # check for highest memory address jbe chkm3 mov edx,eax chkm3: test byte ptr fs:BOOT_LD_FLAGS[di],BOOT_FLAG_EOF jz chkm1 # check for last record mov si,offset recerr sub edx,ecx # compute used memory size jbe doerr mov eax,edx cmp eax,MAX_STATIC_SIZE # should not be too large ja doerr mov [lowsiz],dx # save size for later test ecx,0xFFF0000F # lowest address has to be on a jnz doerr # segment boundary shr ecx,4 # convert to segment mov [lowmem],cx # and save for later .ifgt ASM_DEBUG mov si,offset lowmsg call prnstr mov ax,cx # let the user know about the call prnwrd # segment and size of used memory mov al,0x28 call prnchr mov ax,dx call prnwrd mov al,0x29 call prnchr call prcrlf .endif # Next we can check that the Linux floppy boot sector is the first # segment in lower memory. This is required by the kernel. mov si,offset recerr mov al,VENDOR_INIT call ldrfnd # find load record for INIT seg jc doerr # isnt there? call ldradr # get address jc doerr test eax,0xFFF0000F # must be on a segment boundary jnz doerr shr eax,4 cmp ax,cx # compare base memory segment jne doerr cmp dword ptr fs:BOOT_LD_MLENGTH[di],SECTSIZE jne doerr # has to have a size of one sector mov gs,ax mov ax,word ptr gs:[VGA_OFFSET] mov [vgamode],ax # save video mode for later # Determine the kernel setup entry address. It has to immediately follow # the floppy boot sector and has to contain the proper signature. Also # check that we have enough free memory in the setup segment to place the # kernel heap into it. mov si,offset recerr mov al,VENDOR_SETUP call ldrfnd # find load record for kernel jc doerr # setup module call ldradr jc doerr test eax,0xFFF0000F # module address must be reach- jnz doerr # able and on paragraph boundary shr eax,4 # get segment of setup code mov gs,ax sub ax,SECTSIZE / 16 cmp cx,ax # has to follow the floppy jne doerr # boot sector mov si,offset seterr cmp dword ptr gs:[SU_MAGIC_ADDR],SU_MAGIC jne doerr # check magic number mov si,offset olderr cmp word ptr gs:[SU_VERSION_ADDR],SU_VERSION_OLD jb doerr # check version number mov ax,word ptr gs:[SU_SYS_SEG] mov [krnseg],ax # get kernel segment mov si,offset recerr mov edx,fs:BOOT_LD_MLENGTH[di] sub edx,fs:BOOT_LD_ILENGTH[di] jbe doerr cmp edx,LINUX_HEAP # check if its enough for jb doerr # the heap # The command line has been placed into our memory area by the bootrom # already, so that we can determine its offset from the beginning of the # floppy boot sector. Note that the segment of the floppy boot sector # should still be in CX. mov si,offset recerr mov al,VENDOR_CMDL call ldrfnd # find load record for command line jc doerr call ldradr # get address of command line jc doerr mov bx,cx shl ecx,4 # CX still contains the segment of and ecx,0x000FFFF0 # the floppy boot sector sub eax,ecx # compute offset to command line jbe doerr cmp eax,0x00010000 - CMDL_SIZE ja doerr # should not be larger than 64kB mov [cmdofs],ax # save offset ot command line cmp dword ptr fs:BOOT_LD_MLENGTH[di],CMDL_SIZE jb doerr # check maximum size of command line push ds mov si,ax mov ds,bx call strlen # determine actual length of string cmp cx,CMDL_SIZE - 2 jbe stcmd1 # command line is not allowed to be mov cx,CMDL_SIZE - 2 # longer than maximum stcmd1: mov bx,cx mov byte ptr [bx + si],0 # terminate command line with zero pop ds mov [cmdlen],cx # save command line length .ifgt ASM_DEBUG mov dx,ax mov si,offset cmdmsg call prnstr mov ax,dx # let the user know about the call prnwrd # segment and size of command line mov al,0x28 call prnchr mov ax,cx call prnwrd mov al,0x29 call prnchr call prcrlf .endif # Now check that the kernel has been loaded at the correct place and get # the kernel size. mov si,offset recerr mov al,VENDOR_KERNEL call ldrfnd # find load record for kernel jc doerr # image call ldradr jc doerr cmp eax,KERNEL_LD_HIGH # kernel has to be loaded into jne doerr # high memory mov bl,fs:BOOT_LD_LENGTH[di] test bl,0xF0 jz doerr and bx,0x000F # get offset of vendor infor- shl bx,2 # mation within load record mov bl,fs:[bx + di] # get kernel vendor flags mov [krnflg],bl mov eax,fs:BOOT_LD_ILENGTH[di] # get kernel size test bl,KRN_LOW # size doesnt matter if the jz krnck1 # kernel remains loaded into mov dx,LINUX_MEM # high memory sub dx,[krnseg] # compute number of available jbe doerr # segments and edx,0x0000FFFF # convert number of segments shl edx,4 # into number of bytes cmp eax,edx # check that the kernel is ja doerr # not too large for low memory krnck1: mov [krnsiz],eax # Find a preloaded ram disk image. If the image is not already loaded at # a fixed address or at the end of physical memory, copy it there. mov al,VENDOR_RAMDISK call ldrfnd # find load record for ramdisk jc docopy # no ramdisk image found mov eax,fs:BOOT_LD_MLENGTH[di] mov [rdsize],eax # save size of ramdisk image call ldradr # get address of ramdisk image mov edx,eax mov bl,fs:BOOT_LD_LENGTH[di] test bl,0xF0 jz doerr and bx,0x000F # get offset of vendor infor- shl bx,2 # mation within load record cmp byte ptr fs:[bx + di],RD_AUTO jne rddone # accept fixed address # otherwise relocate ramdisk to call getext # end of physical memory add eax,0x00000400 # add size of conventional shl eax,10 # memory and multiply by 1024 sub eax,[rdsize] # compute new start of ramdisk mov ebx,eax # needs to be relocated to end xchg eax,edx # of memory mov ecx,fs:BOOT_LD_ILENGTH[di] call movmem # move ramdisk mov si,offset moverr jc doerr # check if error rddone: mov [ramdisk],edx # save ramdisk address .ifgt ASM_DEBUG mov si,offset rdmsg call prnstr mov eax,edx call prndwd # let the user know mov al,0x28 call prnchr mov eax,[rdsize] call prndwd mov ax,0x29 call prnchr call prcrlf .endif # The bootrom moved all modules required to be loaded into conventional # memory below 0x80000, because this is required by the netboot spec. # However, the linux kernel expects the command line and the stack in a # segment starting at 0x90000. Also, the 32-bit kernel has been loaded # into high memory by the bootrom. Some kernels dont like to be started # high, and therefore have to be copied into low memory. The memory area # from 0x10000 up to 0x90000 is reserved for the Linux kernel. # The linux kernel expects the floppy boot sector to start at 0x90000, the # 16-bit startup code at 0x90200 and the heap, command line and stack # thereafter. However, everything has to be below 0x9A000 (at least for # SETUP versions below 2.02). # With the code above we have made sure that we really have enough base # memory up to 0x9A000. Also it has been checked that the floppy boot # sector, the kernel setup, the command line and this loader program are # within a size limit of 0x0A000 bytes. And since we dont need the boot- # rom anymore (all error conditions have been checked for) we can now # copy everything up to 0x90000, setup the stack there and jump to our # own copy. # From this point on, it is no longer possible to return to the bootrom. docopy: cli # disable interrupts during copying mov dx,cs # and setup stack. we cant use the mov cx,[lowsiz] # stack during coying because it might mov ax,[lowmem] # get scrambled by the copying. push ds mov ds,ax pop ax mov bx,LINUX_MEM mov es,bx xor si,si xor di,di # copy everything up rep movsb mov ss,bx # set the stack mov sp,HIGH_MEM mov ds,ax sti # it is safe again mov cx,dx sub dx,[lowmem] # compute new address of this program add dx,bx push dx sub ax,cx # compute new data segment add dx,ax mov ds,dx # set new segment registers mov es,dx mov ax,offset conthigh push ax lret # call new copy # At this point the memory layout should look like this: # # 0x090000-0x0901FF 0.5 kB linux floppy boot sector # 0x090200-0x0941FF 16.0 kB kernel setup, including heap # 0x094200-0x0949FF 2.0 kB kernel command line # 0x094C00-0x098BFF 16.0 kB primary boot loader - this program # 0x098C00-0x099FFF 5.5 kB stack # 0x100000-0x1FFFFF 1024.0 kB kernel # 0xxxxxxx-0xxxxxxx x.x kB ramdisk # # The remaining part of this program just deals with setting up the # command line, then copy the kernel where it belongs and call it. conthigh: .ifgt ASM_DEBUG_VERBOSE mov ax,[cmdlen] call prnwrd mov al,0x20 call prnchr push ds mov si,[cmdofs] mov ax,LINUX_MEM mov ds,ax call prnstr # print the command line for pop ds # debugging call prcrlf .endif # Now determine the name of the root directory on the server by looking # into the BOOTP record. If a boot image file name was found, only leave # the directory name by removing everything after the last slash. There # is also the possibility to define a root directory name in the vendor # area of the BOOTP record, so check for that also. # Newer kernels also need to specify the IP address of the NFS server # as part of the root directory path name. We will use the IP address # of the TFTP or BOOTP server in case the root directory name has been # built out of the boot image file name. However, if we can find a root # directory option in the BOOTP record, we assume that the directory name # string found in that option contains an NFS server IP address, if appli- # cable. cld push bp xor dx,dx mov bp,offset pathbuf test byte ptr [krnflg],KRN_NFS_IP jz bootp7 # do we have to have an NFS server addr mov di,bp mov si,offset bootpbuf + BOOTP_SIADDR call cvtadr # put address into destination string mov al,0x3A # add ":" call cvtchr mov bp,di # save pointer to end of IP address bootp7: mov al,BOOTP_RFC_FILE call btptag # check for boot image file name jcxz bootp3 mov si,di # get boot image file name from BOOTP jmp bootp6 # record bootp3: xor bx,bx mov si,offset bootpbuf + BOOTP_FILE call strlen # determine length of file name jcxz bootp2 # no file name, dont touch path bootp6: mov di,bp add cx,dx mov dx,MAX_PATH_LEN - 2 # keep space in path name for cmp cx,dx # trailing 0 jbe bootp1 mov cx,dx # get length of path name bootp1: mov ax,bp sub ax,offset pathbuf # compute length of directory name sub cx,ax # without IP address string mov bx,cx # save length for later rep movsb # copy path name to its new location xor al,al stosb # save trailling 0 bootp2: xor dx,dx or bx,bx # check if we have a file name jz bootp4 std mov di,offset pathbuf mov ax,bp sub ax,di add bx,ax # compute length of whole string mov cx,bx # including IP address add di,bx # point DI to end of string mov al,0x2F # search backwards for last occurrence repne scasb # of a slash jnz bootp4 # not found -> no path mov byte ptr [di + 1],0 # replace the slash with 0 mov dx,cx inc dx bootp4: mov [pathlen],dx # store length of directory name cld mov al,BOOTP_RFC_ROOT # there is still another possibility: call btptag # get the directory name from vendor jcxz bootp9 # field of bootp record mov dx,cx # in that case we can overwrite the mov si,di # IP address previously added, so mov di,offset pathbuf # copy the directory name from the rep movsb # bootp record directly at the be- xor al,al # ginning of the path name buffer stosb # and terminate the string with 0 mov [pathlen],dx bootp9: pop bp .ifgt ASM_DEBUG_VERBOSE mov ax,[pathlen] call prnwrd mov al,0x20 call prnchr mov si,offset pathbuf # print out the path call prnstr call prcrlf .endif # Build the IP address string by looking into the bootp record. Get # all relevant IP addresses out of the record. xor dx,dx mov di,offset ipaddrs mov si,offset bootpbuf + BOOTP_YIADDR call cvtadr # put address into destination mov al,0x3A # string add ":" call cvtchr add si,BOOTP_SIADDR - BOOTP_YIADDR call cvtadr # put server address into dest mov al,0x3A # string add ":" call cvtchr push di mov al,BOOTP_RFC_GWY # we have to take the gateway address call btptag # from the vendor info area mov si,di pop di jcxz noadd1 # no netmask found call cvtadr # put gateway address into dest string noadd1: mov al,0x3A # add ":" call cvtchr push di mov al,BOOTP_RFC_MSK # we have to take the netmask from the call btptag # vendor info area mov si,di pop di jcxz noadd2 # no netmask found call cvtadr # put netmask address into dest string noadd2: mov al,0x3A # add ":" call cvtchr push di mov al,BOOTP_RFC_HNAME # get the host name from the vendor call btptag # info area mov si,di pop di jcxz noadd3 # no host name found add dx,cx # update string length rep movsb # move host name into dest string noadd3: push di mov al,BOOTP_RFC_RDEV # get the name for the network device call btptag # by which to reach the root FS mov si,di pop di jcxz noaddr # no device name found mov al,0x3A # only add ":" if there is a device name call cvtchr add dx,cx # update string length rep movsb # move device name into dest string noaddr: xor al,al # terminate string with 0 stosb mov [ipadrlen],dx # save string length .ifgt ASM_DEBUG_VERBOSE mov ax,dx call prnwrd mov al,0x20 call prnchr mov si,offset ipaddrs # let the user know call prnstr call prcrlf .endif # Process the command line by inserting the root directory name and the # IP address string. mov si,offset rtstr mov di,offset pathbuf mov ax,[pathlen] # first handle root directory name or ax,ax jnz docmd1 mov di,ax docmd1: call procv # setup command line test byte ptr [krnflg],KRN_USE_IP jz docmd4 # use "nfsaddrs" command line option? mov si,offset ipstr mov di,offset ipaddrs mov ax,[ipadrlen] # then handle the IP address string or ax,ax jnz docmd2 mov di,ax docmd2: call procv # setup command line jmp docmd5 docmd4: mov si,offset adrstr mov di,offset ipaddrs mov ax,[ipadrlen] # then handle the IP address string or ax,ax jnz docmd3 mov di,ax docmd3: call procv # setup command line docmd5: .ifgt ASM_DEBUG_VERBOSE mov ax,[cmdlen] call prnwrd mov al,0x20 call prnchr push ds mov si,[cmdofs] mov ax,LINUX_MEM mov ds,ax # Print the new command line for call prnstr # debugging pop ds call prcrlf .endif # Add any additional command line extensions from the BOOTP record and # check for a VGA mode command line option. mov al,BOOTP_RFC_CMDAD # check for application vendor tag call btptag jcxz dovga # tag not available mov si,offset nulstr mov bx,cx call addarg # add string to end of command line dovga: call vga # process "vga=" argument .ifgt ASM_DEBUG_VERBOSE mov ax,[cmdlen] call prnwrd mov al,0x20 call prnchr push ds mov si,[cmdofs] mov ax,LINUX_MEM mov ds,ax # Print the new command line for call prnstr # debugging pop ds call prcrlf .endif # Setup the kernel header by setting the pointer to the command line, # the load flags, the ramdisk and the kernel start address. mov ax,LINUX_MEM mov gs,ax # Let GS:SI point to kernel setup mov si,SECTSIZE # header, and GS:0 to floppy boot mov eax,[ramdisk] # sector mov dword ptr gs:SU_RAMDISK_START[si],eax mov eax,[rdsize] mov dword ptr gs:SU_RAMDISK_SIZE[si],eax mov byte ptr gs:SU_TYPE_OF_LOADER[si],SU_MY_TYPE_OF_LOADER mov byte ptr gs:SU_LOAD_FLAGS[si],SU_LDFLAGS_HEAP mov ebx,KERNEL_LD_LOW test byte ptr [krnflg],KRN_LOW jnz setup1 or byte ptr gs:SU_LOAD_FLAGS[si],SU_LDFLAGS_HIGH mov ebx,KERNEL_LD_HIGH setup1: mov dword ptr gs:SU_32_START[si],ebx cmp word ptr gs:SU_VERSION_ADDR[si],SU_VERSION_NEW jb setup2 movzx32 eax,word ptr [cmdofs] # new setup header uses linear address add eax,LINUX_MEM * 16 # for command line mov dword ptr gs:SU_CMDL_PTR[si],eax jmp setup3 setup2: mov ax,word ptr [cmdofs] # old setup header uses offset mov word ptr gs:[CL_MAGIC_ADDR],CL_MAGIC mov word ptr gs:[CL_OFFSET],ax setup3: mov ax,[vgamode] mov word ptr gs:[VGA_OFFSET],ax # The Linux kernel occupies up to 512 kB of memory. Since the bootrom needs # the space above 0x80000 this doesnt leave enough memory left for this boot # loader if the kernel gets loaded into low memory. Therefore, the bootrom # loads the kernel into high memory in any case. Then after we have made # sure that we dont have to return to the bootrom, we copied ourselves up # into the bootrom area. Now we can copy the kernel down into low memory if # required. Note that we have the final kernel address in EBX already. test byte ptr [krnflg],KRN_LOW jz setup9 mov eax,KERNEL_LD_HIGH movzx32 ebx,word ptr [krnseg] shl ebx,4 mov ecx,[krnsiz] # copy the kernel from high to call movmem # low memory # Finally we can start the game by calling the kernel setup. setup9: .ifgt ASM_DEBUG mov si,offset dbgmsg call prnstr call prcrlf .endif .ifgt ASM_FREEZE_AFTER_INIT lop1: jmp lop1 # this is a feature, not a bug... .else .ifgt ASM_KEYPRESS_AFTER_INIT mov si,offset keymsg call prnstr xor ah,ah int 0x16 call prcrlf .endif mov ax,SETUP_SEG # set segment registers mov ds,ax mov es,ax xor ax,ax mov fs,ax mov gs,ax ljmp SETUP_SEG,0 # finally call the kernel .endif # #==================================================================== # # Convert an IP address into a string. The length of the string # gets accumulated. # Input: DS:SI - Pointer to IP address # ES:DI - Target string buffer # DX - String length up to character pointed to by ES:DI # Output: ES:DI - First character after converted string # DX - New total length of string # Changed registers: AX, DX, DI # cvtadr: push bx mov ax,word ptr [si + 0] or ax,word ptr [si + 2] # if address is 0.0.0.0, dont jz cvtad9 # convert it xor bx,bx cvtad1: mov al,byte ptr [bx + si] call cvtdec # convert each byte in turn into a cmp bx,3 # decimal number jae cvtad9 # abort if at last byte of address mov al,0x2E # add "." call cvtchr inc bx # proceed with next byte jmp cvtad1 cvtad9: pop bx ret # #==================================================================== # # Convert a byte into a decimal number string. The string length # gets accumulated. # Input: AL - Byte value # ES:DI - Target string buffer # DX - String length up to character pointed to by ES:DI # Output: ES:DI - First character after converted string # DX - New total length of string # Changed registers: AX, DX, DI # cvtdec: push cx xor ah,ah xor ch,ch mov cl,100 div al,cl # get 100th or ch,al # dont print if zero jz cvtd1 call cvtd7 # convert digit into ascii character cvtd1: mov al,ah xor ah,ah mov cl,10 div al,cl # get 10th or ch,al # dont print if zero jz cvtd2 call cvtd7 # convert digit into ascii character cvtd2: mov al,ah # get 1th pop cx cvtd7: add al,0x30 # convert digit into ascii character cvtchr: cmp dx,MAX_ADDRS_LEN # dont exceed limit of address string jae cvtch9 # buffer stosb inc dx cvtch9: ret # #==================================================================== # # Process an argument on the command line. This routine handles # basically three cases: # - the argument value is "rom", which means to use the value # delivered by the boot rom # - the argument value is "kernel", which means to just remove # the argument from the command line and then let the kernel # use its defaults # - the argument value contains neither "rom" nor "kernel", so # dont touch it. # # Input: DS:SI - Pointer to name of option to look for in command line # DS:DI - Pointer to bootrom value, or NULL if no value # Output: none # Changed registers: AX, BX, CX, DX, SI, DI, ES # procv: push bp push es push di mov ax,LINUX_MEM mov es,ax mov di,[cmdofs] mov dx,di # load pointer to command line and call strstr # find option name jcxz procv3 # option not found in cmd line mov bx,di # ES:BX points to option name add di,cx # ES:DI points to argument add dx,[cmdlen] # DX contains remaining cmd line sub dx,di jle procv2 # length of remaining cmd line is zero mov cx,dx mov dx,di mov al,0x20 # find end of argument (terminated by repne scasb # blank) or end of string (e.g. CX=0) jcxz procv1 # argument goes to end of string dec di procv1: sub di,dx xchg dx,di # compute length of argument in DX jg procv4 # argument length is greater than zero procv2: mov dx,si mov di,bx # if we got here, we found the option call rmarg # name but no argument, so just remove mov si,dx # the option from the cmd line. procv3: xor dx,dx procv4: pop bp # get pointer to boot rom value # At this point the registers are allocated as follows: # DS:SI points to name of option # DS:BP points to boot rom value # ES:BX points to option name in command line # ES:DI points to argument in command line # DX contains length of argument; if 0, BX and DI are invalid # # After all that we have a couple of choices for constructing the kernel # command line: # # argument not given in command line: # - if there is no value from the boot rom, just do nothing and let # the kernel decide # - if there is a value from the boot rom, add a new argument at the # end of the command line using the boot rom value # # argument given in command line: # - if the value of the argument is not empty and not "rom" or "kernel", # just do nothing # - if the value of the argument is "rom", then remove the argument # from the command line and add a new one with the boot rom value, but # only if a boot rom value is available # - if the value of the argument is "kernel", then remove the argument # from the command line and do nothing, thus letting the kernel decide or dx,dx # check if argument found in command jnz isarg # line or bp,bp jz procv9 # if no boot rom value is available, mov al,ds:[bp] # e.g. the string pointer is NULL or or al,al # the string length is zero, then jz procv9 # just return and do nothing. mov di,bp xor bx,bx # if not, add new option with boot rom call addarg # argument to end of command line procv9: pop es pop bp ret # Original command line contains the option we are looking for. Check whether # the argument is one of the strings "rom" or "kernel" isarg: cld push si mov ax,di mov cx,dx cmp cx,offset romend - offset romstr jne norom mov si,offset romstr repe cmpsb # compare for "rom" mov di,ax jz dorom norom: mov cx,dx cmp cx,offset kerend - offset kerstr jne nokern mov si,offset kerstr repe cmpsb # compare for "kernel" mov di,ax jz dokern nokern: pop si # not found --> do nothing jmp procv9 # the argument is "rom", so remove the existing argument from the # command line, and add the boot rom value dorom: mov di,bx call rmarg # remove old option containing pop si # "rom" argument mov di,bp or di,di jz procv9 # if no boot rom value is available, mov al,[di] # e.g. the string pointer is NULL or or al,al # the string length is zero, then jz procv9 # just return and do nothing. xor bx,bx call addarg # add new option with boot rom value jmp procv9 # as argument # the argument is "kernel", so just remove the existing argument from # the command line and let the kernel decide by itself. dokern: mov di,bx call rmarg # remove old option containing pop si # "kernel" argument jmp procv9 # #==================================================================== # # Add a new option to the end of the command line. # Input: DS:SI - Pointer to name of command line option # DS:DI - Pointer to argument value # BX - Length of argument value, or 0 if zero-terminated string # Output: ES:DI - Pointer to trailing zero of command line # AX - New length of command line # Changed registers: AX, SI, DI, ES # addarg: push cx push dx call strlen # first compute the new length of mov dx,cx # the command line. if the new or bx,bx # argument will not fit into the mov cx,bx # memory provided, then just return jnz addar5 # and leave the command line xchg si,di # unchanged. call strlen xchg si,di addar5: add cx,dx pop dx add cx,[cmdlen] inc cx cmp cx,CMDL_SIZE # check size of new command line pop cx jae addar9 cld push bx push cx push di mov cx,bx mov ax,LINUX_MEM mov es,ax mov di,[cmdofs] mov bx,di add di,[cmdlen] mov al,0x20 # add a blank stosb addar1: lodsb # add the option name or al,al jz addar2 stosb jmp addar1 addar2: pop si # load the argument pointer addar3: lodsb # add the argument or al,al jz addar4 stosb loop addar3 addar4: xor al,al # add trailing 0 stosb dec di mov ax,di sub ax,bx mov [cmdlen],ax # adjust length of command line pop cx pop bx addar9: ret # #==================================================================== # # Remove an argument from the command line. # Input: ES:DI - Pointer to argument name within command line # Output: ES:DI - Pointer to trailing 0 of command line # AX - New length of command line # Changed registers: AX, SI, DI, ES # rmarg: cld push bx push cx push dx push ds mov si,[cmdofs] # get beginning of command line mov ax,es mov ds,ax mov bx,si mov si,di # get remaining length of cmd line call strlen # find end of argument in command line mov ax,0x20 # DS:SI points to first character after repne scasb # the argument string dec di # ES:DI points to start of argument xchg si,di # ES:BX points to start of cmd line or cx,cx # argument is not at the end of the jnz rmarg2 # command line rmarg1: cmp di,bx je rmarg4 # empty command line dec di # remove leading blanks cmp byte ptr es:[di],0x20 je rmarg1 inc di # point to first blank after preceeding jmp rmarg4 # argument and make it end of string rmarg2: lodsb cmp al,0x20 je rmarg2 # remove trailing blanks rmarg3: or al,al jz rmarg1 # end of command line... remove all stosb # trailing blanks. Otherwise copy lodsb # the remainder of the command line jmp rmarg3 # over the argument rmarg4: xor al,al stosb # add trailing 0 pop ds pop dx pop cx dec di # point to trailing 0 mov ax,di sub ax,bx # adjust length of command line mov [cmdlen],ax pop bx ret # #==================================================================== # # Handle any "vga=" argument that might be on the command line # Input: none # Output: none # Changed registers: AX, BX, CX, DX, SI, DI # vga: push es mov bx,[vgamode] mov ax,LINUX_MEM mov es,ax vgalop: mov di,[cmdofs] mov si,offset vgastr call strstr # ES:DI = strstr(cmdptr,"vga=") jcxz isret # not found # Check if the argument to the "vga=" option does contain any of the # special key words, or a hex or decimal number. push di add di,cx # ES:DI += strlen("vga=") mov ax,di xor bx,bx # BX gets the VGA command line value cld mov cx,offset askend - offset askstr mov si,offset askstr # strncmp(ES:DI,"ASK",3) repe cmpsb mov di,ax jz doask mov cx,offset extend - offset extstr mov si,offset extstr # strncmp(ES:DI,"EXTENDED",8) repe cmpsb mov di,ax jz doext mov cx,offset nrmend - offset nrmstr mov si,offset nrmstr # strncmp(ES:DI,"NORMAL",6) repe cmpsb mov di,ax jz donrm mov cx,offset hexend - offset hexstr mov si,offset hexstr # strncmp(ES:DI,"0x",2) repe cmpsb mov di,ax jz dohex jmp dodez # Return after storing the VGA mode and printing out the result isret: pop es mov [vgamode],bx .ifgt ASM_DEBUG_VERBOSE mov si,offset vgastr call prnstr mov ax,bx call prnwrd call prcrlf .endif ret # Handle any of the special key words. Then remove the argument from the # command line and continue looking for another vga option. doask: dec bx # ASK == -3 doext: dec bx # EXTENDED == -2 donrm: dec bx # NORMAL == -1 vgaset: pop di call rmarg # remove argument jmp vgalop # handle next argument (if any) # Decode a hex number dohex: xor dh,dh inc di hexlop: inc di # ES:DI++ mov dl,es:[di] sub dl,0x30 # if (*ES:DI < "0") break jc vgaset cmp dl,0x09 # if (*ES:DI > "9") goto nodigit jbe hexl2 sub dl,0x11 # if (*ES:DI < "A") break jc vgaset cmp dl,0x05 # if (*ES:DI > "F") goto nocapital jbe hexl1 sub dl,0x20 # if (*ES:DI < "a") break jc vgaset cmp dl,0x05 # if (*ES:DI > "f") break ja vgaset hexl1: add dl,0x0A hexl2: shl bx,4 # BX = 16*BX + digit add bx,dx jmp hexlop # Decode a decimal number vgasgn: or al,al # if (sign) BX = -BX jz vgaset neg bx jmp vgaset dodez: xor al,al xor dh,dh cmp byte ptr es:[di],0x2D # if (*ES:DI == "-") sign++ jne dezcnv inc al # AL = sign dezlop: inc di # ES:DI++ dezcnv: mov dl,es:[di] sub dl,0x30 # if (*ES:DI < "0") break jc vgasgn cmp dl,0x09 # if (*ES:DI > "9") break ja vgasgn add bx,bx # BX = 10*BX + *ES:DI - "0" mov cx,bx shl bx,2 add bx,cx add bx,dx jmp dezlop # #==================================================================== # # String and constants definitions # \(.data) # Startup signature sigmsg: .byte 0x0D, 0x0A .ascii "Linux Net Boot Image Loader " .ascii "\&VERSION" .byte 0x0D, 0x0A .ascii "Copyright (C) 1996,1997 G. Kuhlmann and M. Gutschke" .byte 0x0D, 0x0A .ascii "\©RIGHT" .byte 0x0D, 0x0A .byte 0x0D, 0x0A .byte 0 # Boot image header vendor magic ID vmagic: .asciz "\&VENDOR_MAGIC" # Possible option names for command line rtstr: .asciz "nfsroot=" adrstr: .asciz "nfsaddrs=" ipstr: .asciz "ip=" vgastr: .asciz "vga=" askstr: .ascii "ASK" askend: .byte 0 extstr: .ascii "EXTENDED" extend: .byte 0 nrmstr: .ascii "NORMAL" nrmend: .byte 0 hexstr: .ascii "0x" hexend: .byte 0 nulstr: .byte 0 # Possible arguments for command line options romstr: .ascii "rom" romend: .byte 0 kerstr: .ascii "kernel" kerend: .byte 0 # Error messages memerr: .asciz "Not enough memory" recerr: .asciz "Error in load record data" seterr: .asciz "Error in kernel setup header" olderr: .asciz "Linux kernel too old" moverr: .asciz "Failed to move ramdisk image to end of memory" .ifgt ASM_DEBUG # Some debugging messages memmsg: .asciz "Base memory = " extmsg: .asciz "Extended memory = " lowmsg: .asciz "Low memory = " cmdmsg: .asciz "Command line offset = " rdmsg: .asciz "Ramdisk start = " .ifgt ASM_KEYPRESS_AFTER_INIT keymsg: .asciz "Press any key to continue" .endif .ifgt ASM_FREEZE_AFTER_INIT dbgmsg: .asciz "Freezing!" .else dbgmsg: .asciz "Calling Linux kernel" .endif .endif # #==================================================================== # # Variable definitions # \(.bss) .extern bootpbuf # space to hold BOOTP record .extern header # pointer to load record header .lcomm lowmem,2 # lowest used segment .lcomm lowsiz,2 # used memory size in lower memory .lcomm ramdisk,4 # pointer to start of ramdisk (32bit) .lcomm rdsize,4 # size of ramdisk .lcomm cmdofs,2 # pointer to start of command line .lcomm cmdlen,2 # length of command line .lcomm vgamode,2 # VGA mode .lcomm krnsiz,4 # size of high-loaded kernel image .lcomm krnseg,2 # segment of low-loaded kernel image .lcomm krnflg,1 # kernel vendor flag .align 2 .lcomm ipadrlen,2 # length of IP address string .lcomm ipaddrs,MAX_ADDRS_LEN # space to store IP addrs string .lcomm pathlen,2 # length of path string .lcomm pathbuf,MAX_PATH_LEN # space to hole path name # #==================================================================== # .end