; ----------------------------------------------------------------------- ; ; Copyright 1994-2009 H. Peter Anvin - All Rights Reserved ; Copyright 2009-2010 Intel Corporation; author: 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., 51 Franklin St, Fifth Floor, ; Boston MA 02110-1301, USA; either version 2 of the License, or ; (at your option) any later version; incorporated herein by reference. ; ; ----------------------------------------------------------------------- ; ; diskstart.inc ; ; Common early-bootstrap code for harddisk-based Syslinux derivatives. ; section .init ; ; Some of the things that have to be saved very early are saved ; "close" to the initial stack pointer offset, in order to ; reduce the code size... ; StackBuf equ STACK_TOP-44-92 ; Start the stack here (grow down - 4K) PartInfo equ StackBuf .mbr equ PartInfo .gptlen equ PartInfo+16 .gpt equ PartInfo+20 FloppyTable equ PartInfo+76 ; Total size of PartInfo + FloppyTable == 76+16 = 92 bytes Hidden equ StackBuf-20 ; Partition offset OrigFDCTabPtr equ StackBuf-12 ; The 2nd high dword on the stack OrigESDI equ StackBuf-8 ; The high dword on the stack DriveNumber equ StackBuf-4 ; Drive number StackHome equ Hidden ; The start of the canonical stack ; ; Primary entry point. Tempting as though it may be, we can't put the ; initial "cli" here; the jmp opcode in the first byte is part of the ; "magic number" (using the term very loosely) for the DOS superblock. ; bootsec equ $ _start: jmp short start ; 2 bytes nop ; 1 byte ; ; "Superblock" follows -- it's in the boot sector, so it's already ; loaded and ready for us ; bsOemName db MY_NAME ; The SYS command sets this, so... zb 8-($-bsOemName) ; ; These are the fields we actually care about. We end up expanding them ; all to dword size early in the code, so generate labels for both ; the expanded and unexpanded versions. ; %macro superb 1 bx %+ %1 equ SuperInfo+($-superblock)*8+4 bs %+ %1 equ $ zb 1 %endmacro %macro superw 1 bx %+ %1 equ SuperInfo+($-superblock)*8 bs %+ %1 equ $ zw 1 %endmacro %macro superd 1 bx %+ %1 equ $ ; no expansion for dwords bs %+ %1 equ $ zd 1 %endmacro superblock equ $ superw BytesPerSec superb SecPerClust superw ResSectors superb FATs superw RootDirEnts superw Sectors superb Media superw FATsecs superw SecPerTrack superw Heads superinfo_size equ ($-superblock)-1 ; How much to expand superd Hidden superd HugeSectors ; ; This is as far as FAT12/16 and FAT32 are consistent ; ; FAT12/16 need 26 more bytes, ; FAT32 need 54 more bytes ; superblock_len_fat16 equ $-superblock+26 superblock_len_fat32 equ $-superblock+54 zb 54 ; Maximum needed size superblock_max equ $-superblock global SecPerClust SecPerClust equ bxSecPerClust ; ; Note we don't check the constraints above now; we did that at install ; time (we hope!) ; start: cli ; No interrupts yet, please cld ; Copy upwards ; ; Set up the stack ; xor cx,cx mov ss,cx mov sp,StackBuf-2 ; Just below BSS (-2 for alignment) push dx ; Save drive number (in DL) push es ; Save initial ES:DI -> $PnP pointer push di mov es,cx ; ; DS:SI may contain a partition table entry and possibly a GPT entry. ; Preserve it for us. This saves 56 bytes of the GPT entry, which is ; currently the maximum we care about. Total is 76 bytes. ; mov cl,(16+4+56)/2 ; Save partition info mov di,PartInfo rep movsw ; This puts CX back to zero mov ds,cx ; Now we can initialize DS... ; ; Now sautee the BIOS floppy info block to that it will support decent- ; size transfers; the floppy block is 11 bytes and is stored in the ; INT 1Eh vector (brilliant waste of resources, eh?) ; ; Of course, if BIOSes had been properly programmed, we wouldn't have ; had to waste precious space with this code. ; mov bx,fdctab lfs si,[bx] ; FS:SI -> original fdctab push fs ; Save on stack in case we need to bail push si ; Save the old fdctab even if hard disk so the stack layout ; is the same. The instructions above do not change the flags and dl,dl ; If floppy disk (00-7F), assume no ; partition table js harddisk floppy: xor ax,ax mov cl,6 ; 12 bytes (CX == 0) ; es:di -> FloppyTable already ; This should be safe to do now, interrupts are off... mov [bx],di ; FloppyTable mov [bx+2],ax ; Segment 0 fs rep movsw ; Faster to move words mov cl,[bsSecPerTrack] ; Patch the sector count mov [di-76+8],cl push ax ; Partition offset == 0 push ax push ax push ax int 13h ; Some BIOSes need this jmp short not_harddisk ; ; The drive number and possibly partition information was passed to us ; by the BIOS or previous boot loader (MBR). Current "best practice" is to ; trust that rather than what the superblock contains. ; ; Note: di points to beyond the end of PartInfo ; harddisk: test byte [di-76],7Fh ; Sanity check: "active flag" should jnz .no_partition ; be 00 or 80 cmp [di-76+4],cl ; Sanity check: partition type != 0 je .no_partition cmp eax,'!GPT' ; !GPT signature? jne .mbr cmp byte [di-76+4],0EDh ; Synthetic GPT partition entry? jne .mbr .gpt: ; GPT-style partition info push dword [di-76+20+36] push dword [di-76+20+32] jmp .gotoffs .mbr: ; MBR-style partition info push cx ; Upper half partition offset == 0 push cx push dword [di-76+8] ; Partition offset (dword) jmp .gotoffs .no_partition: ; ; No partition table given... assume that the Hidden field in the boot sector ; tells the truth (in particular, is zero if this is an unpartitioned disk.) ; push cx push cx push dword [bsHidden] .gotoffs: ; ; Get disk drive parameters (don't trust the superblock.) Don't do this for ; floppy drives -- INT 13:08 on floppy drives will (may?) return info about ; what the *drive* supports, not about the *media*. Fortunately floppy disks ; tend to have a fixed, well-defined geometry which is stored in the superblock. ; ; DL == drive # still mov ah,08h int 13h jc no_driveparm and ah,ah jnz no_driveparm shr dx,8 inc dx ; Contains # of heads - 1 mov [bsHeads],dx and cx,3fh mov [bsSecPerTrack],cx no_driveparm: not_harddisk: ; ; Ready to enable interrupts, captain ; sti ; ; Do we have EBIOS (EDD)? ; eddcheck: mov bx,55AAh mov ah,41h ; EDD existence query mov dl,[DriveNumber] int 13h jc .noedd cmp bx,0AA55h jne .noedd test cl,1 ; Extended disk access functionality set jz .noedd ; ; We have EDD support... ; mov byte [getonesec.jmp+1],(getonesec_ebios-(getonesec.jmp+2)) .noedd: ; ; Load the first sector of LDLINUX.SYS; this used to be all proper ; with parsing the superblock and root directory; it doesn't fit ; together with EBIOS support, unfortunately. ; mov eax,strict dword 0xdeadbeef Sect1Ptr0 equ $-4 mov edx,strict dword 0xfeedface Sect1Ptr1 equ $-4 mov bx,ldlinux_sys ; Where to load it call getonesec ; Some modicum of integrity checking cmp dword [ldlinux_magic+4],LDLINUX_MAGIC^HEXDATE jne kaboom ; Go for it... jmp 0:ldlinux_ent ; ; getonesec: load a single disk linear sector EDX:EAX into the buffer ; at ES:BX. ; ; This routine assumes CS == DS == SS, and trashes most registers. ; ; Stylistic note: use "xchg" instead of "mov" when the source is a register ; that is dead from that point; this saves space. However, please keep ; the order to dst,src to keep things sane. ; getonesec: add eax,[Hidden] ; Add partition offset adc edx,[Hidden+4] mov cx,retry_count .jmp: jmp strict short getonesec_cbios ; ; getonesec_ebios: ; ; getonesec implementation for EBIOS (EDD) ; getonesec_ebios: .retry: ; Form DAPA on stack push edx push eax push es push bx push word 1 push word 16 mov si,sp pushad mov ah,42h ; Extended Read call xint13 popad lea sp,[si+16] ; Remove DAPA jc .error ret .error: ; Some systems seem to get "stuck" in an error state when ; using EBIOS. Doesn't happen when using CBIOS, which is ; good, since some other systems get timeout failures ; waiting for the floppy disk to spin up. pushad ; Try resetting the device xor ax,ax call xint13 popad loop .retry ; CX-- and jump if not zero ; Total failure. Try falling back to CBIOS. mov byte [getonesec.jmp+1],(getonesec_cbios-(getonesec.jmp+2)) ; ; getonesec_cbios: ; ; getlinsec implementation for legacy CBIOS ; getonesec_cbios: .retry: pushad movzx esi,word [bsSecPerTrack] movzx edi,word [bsHeads] ; ; Dividing by sectors to get (track,sector): we may have ; up to 2^18 tracks, so we need to use 32-bit arithmetric. ; div esi xor cx,cx xchg cx,dx ; CX <- sector index (0-based) ; EDX <- 0 ; eax = track # div edi ; Convert track to head/cyl cmp eax,1023 ; Outside the CHS range? ja kaboom ; ; Now we have AX = cyl, DX = head, CX = sector (0-based), ; SI = bsSecPerTrack, ES:BX = data target ; shl ah,6 ; Because IBM was STOOPID ; and thought 8 bits were enough ; then thought 10 bits were enough... inc cx ; Sector numbers are 1-based, sigh or cl,ah mov ch,al mov dh,dl mov ax,0201h ; Read one sector call xint13 popad jc .error ret .error: loop .retry ; Fall through to disk_error ; ; kaboom: write a message and bail out. ; global kaboom disk_error: kaboom: xor si,si mov ss,si mov sp,OrigFDCTabPtr ; Reset stack mov ds,si ; Reset data segment pop dword [fdctab] ; Restore FDC table .patch: ; When we have full code, intercept here mov si,bailmsg call writestr_early xor ax,ax .again: int 16h ; Wait for keypress ; NB: replaced by int 18h if ; chosen at install time.. int 19h ; And try once more to boot... .norge: hlt ; If int 19h returned; this is the end jmp short .norge ; ; ; writestr_early: write a null-terminated string to the console ; This assumes we're on page 0. This is only used for early ; messages, so it should be OK. ; writestr_early: pushad .loop: lodsb and al,al jz .return mov ah,0Eh ; Write to screen as TTY mov bx,0007h ; Attribute int 10h jmp short .loop .return: popad ret ; ; INT 13h wrapper function ; xint13: mov dl,[DriveNumber] int 13h ret ; ; Error message on failure ; bailmsg: db 'Boot error', 0Dh, 0Ah, 0 ; This fails if the boot sector overflowsg zb 1FEh-($-$$) bootsignature dw 0xAA55 ; ; =========================================================================== ; End of boot sector ; =========================================================================== ; Start of LDLINUX.SYS ; =========================================================================== LDLINUX_SYS equ ($-$$)+TEXT_START ldlinux_sys: syslinux_banner db CR, LF, MY_NAME, ' ', VERSION_STR, ' ', DATE_STR, ' ', 0 db CR, LF, 1Ah ; EOF if we "type" this in DOS alignz 8 ldlinux_magic dd LDLINUX_MAGIC dd LDLINUX_MAGIC^HEXDATE ; ; This area is patched by the installer. It is found by looking for ; LDLINUX_MAGIC, plus 8 bytes. ; SUBVOL_MAX equ 256 CURRENTDIR_MAX equ FILENAME_MAX patch_area: DataSectors dw 0 ; Number of sectors (not including bootsec) ADVSectors dw 0 ; Additional sectors for ADVs LDLDwords dd 0 ; Total dwords starting at ldlinux_sys, CheckSum dd 0 ; Checksum starting at ldlinux_sys ; value = LDLINUX_MAGIC - [sum of dwords] MaxTransfer dw 127 ; Max sectors to transfer EPAPtr dw EPA - LDLINUX_SYS ; Pointer to the extended patch area ; ; Extended patch area -- this is in .data16 so it doesn't occupy space in ; the first sector. Use this structure for anything that isn't used by ; the first sector itself. ; section .data16 alignz 2 EPA: ADVSecPtr dw ADVSec0 - LDLINUX_SYS CurrentDirPtr dw CurrentDirName-LDLINUX_SYS ; Current directory name string CurrentDirLen dw CURRENTDIR_MAX SubvolPtr dw SubvolName-LDLINUX_SYS SubvolLen dw SUBVOL_MAX SecPtrOffset dw SectorPtrs-LDLINUX_SYS SecPtrCnt dw (SectorPtrsEnd - SectorPtrs)/10 ; ; Boot sector patch pointers ; Sect1Ptr0Ptr dw Sect1Ptr0 - bootsec ; Pointers to Sector 1 location Sect1Ptr1Ptr dw Sect1Ptr1 - bootsec RAIDPatchPtr dw kaboom.again - bootsec ; Patch to INT 18h in RAID mode ; ; Base directory name and subvolume, if applicable. ; %define HAVE_CURRENTDIRNAME global CurrentDirName, SubvolName CurrentDirName times CURRENTDIR_MAX db 0 SubvolName times SUBVOL_MAX db 0 section .init ldlinux_ent: ; ; Note that some BIOSes are buggy and run the boot sector at 07C0:0000 ; instead of 0000:7C00 and the like. We don't want to add anything ; more to the boot sector, so it is written to not assume a fixed ; value in CS, but we don't want to deal with that anymore from now ; on. ; sti ; In case of broken INT 13h BIOSes ; ; Tell the user we got this far ; mov si,syslinux_banner call writestr_early ; ; Checksum data thus far ; mov si,ldlinux_sys mov cx,SECTOR_SIZE >> 2 mov edx,-LDLINUX_MAGIC .checksum: lodsd add edx,eax loop .checksum mov [CheckSum],edx ; Save intermediate result ; ; Tell the user if we're using EBIOS or CBIOS ; print_bios: mov si,cbios_name cmp byte [getonesec.jmp+1],(getonesec_ebios-(getonesec.jmp+2)) jne .cbios mov si,ebios_name mov byte [getlinsec.jmp+1],(getlinsec_ebios-(getlinsec.jmp+2)) .cbios: mov [BIOSName],si call writestr_early section .earlybss %define HAVE_BIOSNAME 1 BIOSName resw 1 section .init ; ; Now we read the rest of LDLINUX.SYS. ; load_rest: lea esi,[SectorPtrs] mov ebx,TEXT_START+2*SECTOR_SIZE ; Where we start loading mov cx,[DataSectors] dec cx ; Minus this sector .get_chunk: jcxz .done mov eax,[si] mov edx,[si+4] movzx ebp,word [si+8] sub cx,bp push ebx shr ebx,4 ; Convert to a segment mov es,bx xor bx,bx call getlinsec pop ebx shl ebp,SECTOR_SHIFT add ebx,ebp add si,10 jmp .get_chunk .done: ; ; All loaded up, verify that we got what we needed. ; Note: the checksum field is embedded in the checksum region, so ; by the time we get to the end it should all cancel out. ; verify_checksum: mov si,ldlinux_sys + SECTOR_SIZE mov ecx,[LDLDwords] sub ecx,SECTOR_SIZE >> 2 mov eax,[CheckSum] .checksum: add eax,[si] add si,4 jnz .nowrap ; Handle segment wrap mov dx,ds add dx,1000h mov ds,dx .nowrap: dec ecx jnz .checksum and eax,eax ; Should be zero jz all_read ; We're cool, go for it! ; ; Uh-oh, something went bad... ; mov si,checksumerr_msg call writestr_early jmp kaboom ; ; ----------------------------------------------------------------------------- ; Subroutines that have to be in the first sector ; ----------------------------------------------------------------------------- ; ; getlinsec: load a sequence of BP floppy sector given by the linear sector ; number in EAX into the buffer at ES:BX. We try to optimize ; by loading up to a whole track at a time, but the user ; is responsible for not crossing a 64K boundary. ; (Yes, BP is weird for a count, but it was available...) ; ; On return, BX points to the first byte after the transferred ; block. ; ; This routine assumes CS == DS. ; global getlinsec getlinsec: pushad add eax,[Hidden] ; Add partition offset adc edx,[Hidden+4] .jmp: jmp strict short getlinsec_cbios ; ; getlinsec_ebios: ; ; getlinsec implementation for EBIOS (EDD) ; getlinsec_ebios: .loop: push bp ; Sectors left .retry2: call maxtrans ; Enforce maximum transfer size movzx edi,bp ; Sectors we are about to read mov cx,retry_count .retry: ; Form DAPA on stack push edx push eax push es push bx push di push word 16 mov si,sp pushad mov ah,42h ; Extended Read push ds push ss pop ds call xint13 pop ds popad lea sp,[si+16] ; Remove DAPA jc .error pop bp add eax,edi ; Advance sector pointer adc edx,0 sub bp,di ; Sectors left shl di,SECTOR_SHIFT ; 512-byte sectors add bx,di ; Advance buffer pointer and bp,bp jnz .loop popad ret .error: ; Some systems seem to get "stuck" in an error state when ; using EBIOS. Doesn't happen when using CBIOS, which is ; good, since some other systems get timeout failures ; waiting for the floppy disk to spin up. pushad ; Try resetting the device xor ax,ax mov dl,[DriveNumber] int 13h popad loop .retry ; CX-- and jump if not zero ;shr word [MaxTransfer],1 ; Reduce the transfer size ;jnz .retry2 ; Total failure. Try falling back to CBIOS. mov byte [getlinsec.jmp+1],(getlinsec_cbios-(getlinsec.jmp+2)) ;mov byte [MaxTransfer],63 ; Max possibe CBIOS transfer pop bp ; ... fall through ... ; ; getlinsec_cbios: ; ; getlinsec implementation for legacy CBIOS ; getlinsec_cbios: .loop: push edx push eax push bp push bx movzx esi,word [bsSecPerTrack] movzx edi,word [bsHeads] ; ; Dividing by sectors to get (track,sector): we may have ; up to 2^18 tracks, so we need to use 32-bit arithmetric. ; div esi xor cx,cx xchg cx,dx ; CX <- sector index (0-based) ; EDX <- 0 ; eax = track # div edi ; Convert track to head/cyl cmp eax,1023 ; Outside the CHS range? ja kaboom ; ; Now we have AX = cyl, DX = head, CX = sector (0-based), ; BP = sectors to transfer, SI = bsSecPerTrack, ; ES:BX = data target ; call maxtrans ; Enforce maximum transfer size ; Must not cross track boundaries, so BP <= SI-CX sub si,cx cmp bp,si jna .bp_ok mov bp,si .bp_ok: shl ah,6 ; Because IBM was STOOPID ; and thought 8 bits were enough ; then thought 10 bits were enough... inc cx ; Sector numbers are 1-based, sigh or cl,ah mov ch,al mov dh,dl xchg ax,bp ; Sector to transfer count mov ah,02h ; Read sectors mov bp,retry_count .retry: pushad call xint13 popad jc .error .resume: movzx ecx,al ; ECX <- sectors transferred shl ax,SECTOR_SHIFT ; Convert sectors in AL to bytes in AX pop bx add bx,ax pop bp pop eax pop edx add eax,ecx sub bp,cx jnz .loop popad ret .error: dec bp jnz .retry xchg ax,bp ; Sectors transferred <- 0 shr word [MaxTransfer],1 jnz .resume jmp kaboom maxtrans: cmp bp,[MaxTransfer] jna .ok mov bp,[MaxTransfer] .ok: ret ; ; Checksum error message ; checksumerr_msg db ' Load error - ', 0 ; Boot failed appended ; ; BIOS type string ; cbios_name db 'CHS', 0 ; CHS/CBIOS ebios_name db 'EDD', 0 ; EDD/EBIOS ; ; Debug routine ; %ifdef debug safedumpregs: cmp word [Debug_Magic],0D00Dh jnz nc_return jmp dumpregs %endif rl_checkpt equ $ ; Must be <= 8000h rl_checkpt_off equ ($-$$) %ifndef DEPEND %if rl_checkpt_off > 3F6h ; Need one extent %assign rl_checkpt_overflow rl_checkpt_off - 3F6h %error Sector 1 overflow by rl_checkpt_overflow bytes %endif %endif ; ; Extent pointers... each extent contains an 8-byte LBA and an 2-byte ; sector count. In most cases, we will only ever need a handful of ; extents, but we have to assume a maximally fragmented system where each ; extent contains only one sector. ; alignz 2 MaxInitDataSize equ 96 << 10 MaxLMA equ TEXT_START+SECTOR_SIZE+MaxInitDataSize SectorPtrs zb 10*(MaxInitDataSize >> SECTOR_SHIFT) SectorPtrsEnd equ $ ; ---------------------------------------------------------------------------- ; End of code and data that have to be in the first sector ; ---------------------------------------------------------------------------- section .text16 all_read: ; We enter here with both DS and ES scrambled... xor ax,ax mov ds,ax mov es,ax ; ; 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_early ; ; Insane hack to expand the DOS superblock to dwords ; expand_super: xor eax,eax mov si,superblock mov di,SuperInfo mov cx,superinfo_size .loop: lodsw dec si stosd ; Store expanded word xor ah,ah stosd ; Store expanded byte loop .loop ; ; Common initialization code ; %include "init.inc" pushad mov eax,ROOT_FS_OPS movzx dx,byte [DriveNumber] ; DH = 0: we are boot from disk not CDROM mov ecx,[Hidden] mov ebx,[Hidden+4] mov si,[bsHeads] mov di,[bsSecPerTrack] movzx ebp,word [MaxTransfer] pm_call fs_init popad section .bss16 SuperInfo resq 16 ; The first 16 bytes expanded 8 times section .text16