; -*- fundamental -*- (asm-mode sucks)
; ****************************************************************************
;
;  isolinux.asm
;
;  A program to boot Linux kernels off a CD-ROM using the El Torito
;  boot standard in "no emulation" mode, making the entire filesystem
;  available.  It is based on the SYSLINUX boot loader for MS-DOS
;  floppies.
;
;   Copyright 1994-2009 H. Peter Anvin - All Rights Reserved
;   Copyright 2009 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., 53 Temple Place Ste 330,
;  Boston MA 02111-1307, USA; either version 2 of the License, or
;  (at your option) any later version; incorporated herein by reference.
;
; ****************************************************************************

%define IS_ISOLINUX 1
%include "head.inc"

;
; Some semi-configurable constants... change on your own risk.
;
my_id		equ isolinux_id
NULLFILE	equ 0			; Zero byte == null file name
NULLOFFSET	equ 0			; Position in which to look
retry_count	equ 6			; How patient are we with the BIOS?
%assign HIGHMEM_SLOP 128*1024		; Avoid this much memory near the top
SECTOR_SHIFT	equ 11			; 2048 bytes/sector (El Torito requirement)
SECTOR_SIZE	equ (1 << SECTOR_SHIFT)

ROOT_DIR_WORD	equ 0x002F

;
; 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 in high memory and copy them down before we need them.
;
		struc vkernel
vk_vname:	resb FILENAME_MAX	; Virtual name **MUST BE FIRST!**
vk_rname:	resb FILENAME_MAX	; Real name
vk_appendlen:	resw 1
vk_type:	resb 1			; Type of file
		alignb 4
vk_append:	resb max_cmd_len+1	; Command line
		alignb 4
vk_end:		equ $			; Should be <= vk_size
		endstruc

;
; File structure.  This holds the information for each currently open file.
;
		struc open_file_t
file_sector	resd 1			; Sector pointer (0 = structure free)
file_bytesleft	resd 1			; Number of bytes left
file_left	resd 1			; Number of sectors left
		resd 1			; Unused
		endstruc

%ifndef DEPEND
%if (open_file_t_size & (open_file_t_size-1))
%error "open_file_t is not a power of 2"
%endif
%endif

; ---------------------------------------------------------------------------
;   BEGIN CODE
; ---------------------------------------------------------------------------

;
; Memory below this point is reserved for the BIOS and the MBR
;
		section .earlybss
		global trackbuf
trackbufsize	equ 8192
trackbuf	resb trackbufsize	; Track buffer goes here
;		ends at 2800h

		; Some of these are touched before the whole image
		; is loaded.  DO NOT move this to .bss16/.uibss.
		section .earlybss
		alignb 4
FirstSecSum	resd 1			; Checksum of bytes 64-2048
ImageDwords	resd 1			; isolinux.bin size, dwords
InitStack	resd 1			; Initial stack pointer (SS:SP)
DiskSys		resw 1			; Last INT 13h call
ImageSectors	resw 1			; isolinux.bin size, sectors
; These following two are accessed as a single dword...
GetlinsecPtr	resw 1			; The sector-read pointer
BIOSName	resw 1			; Display string for BIOS type
%define HAVE_BIOSNAME 1
BIOSType	resw 1
DiskError	resb 1			; Error code for disk I/O
DriveNumber	resb 1			; CD-ROM BIOS drive number
ISOFlags	resb 1			; Flags for ISO directory search
RetryCount      resb 1			; Used for disk access retries

		alignb 8
Hidden		resq 1			; Used in hybrid mode
bsSecPerTrack	resw 1			; Used in hybrid mode
bsHeads		resw 1			; Used in hybrid mode


;
; El Torito spec packet
;

		alignb 8
_spec_start	equ $
spec_packet:	resb 1				; Size of packet
sp_media:	resb 1				; Media type
sp_drive:	resb 1				; Drive number
sp_controller:	resb 1				; Controller index
sp_lba:		resd 1				; LBA for emulated disk image
sp_devspec:	resw 1				; IDE/SCSI information
sp_buffer:	resw 1				; User-provided buffer
sp_loadseg:	resw 1				; Load segment
sp_sectors:	resw 1				; Sector count
sp_chs:		resb 3				; Simulated CHS geometry
sp_dummy:	resb 1				; Scratch, safe to overwrite

;
; EBIOS drive parameter packet
;
		alignb 8
drive_params:	resw 1				; Buffer size
dp_flags:	resw 1				; Information flags
dp_cyl:		resd 1				; Physical cylinders
dp_head:	resd 1				; Physical heads
dp_sec:		resd 1				; Physical sectors/track
dp_totalsec:	resd 2				; Total sectors
dp_secsize:	resw 1				; Bytes per sector
dp_dpte:	resd 1				; Device Parameter Table
dp_dpi_key:	resw 1				; 0BEDDh if rest valid
dp_dpi_len:	resb 1				; DPI len
		resb 1
		resw 1
dp_bus:		resb 4				; Host bus type
dp_interface:	resb 8				; Interface type
db_i_path:	resd 2				; Interface path
db_d_path:	resd 2				; Device path
		resb 1
db_dpi_csum:	resb 1				; Checksum for DPI info

;
; EBIOS disk address packet
;
		alignb 8
dapa:		resw 1				; Packet size
.count:		resw 1				; Block count
.off:		resw 1				; Offset of buffer
.seg:		resw 1				; Segment of buffer
.lba:		resd 2				; LBA (LSW, MSW)

;
; Spec packet for disk image emulation
;
		alignb 8
dspec_packet:	resb 1				; Size of packet
dsp_media:	resb 1				; Media type
dsp_drive:	resb 1				; Drive number
dsp_controller:	resb 1				; Controller index
dsp_lba:	resd 1				; LBA for emulated disk image
dsp_devspec:	resw 1				; IDE/SCSI information
dsp_buffer:	resw 1				; User-provided buffer
dsp_loadseg:	resw 1				; Load segment
dsp_sectors:	resw 1				; Sector count
dsp_chs:	resb 3				; Simulated CHS geometry
dsp_dummy:	resb 1				; Scratch, safe to overwrite

		alignb 4
_spec_end	equ $
_spec_len	equ _spec_end - _spec_start

		section .init
;;
;; Primary entry point.  Because BIOSes are buggy, we only load the first
;; CD-ROM sector (2K) of the file, so the number one priority is actually
;; loading the rest.
;;
StackBuf	equ STACK_TOP-44	; 44 bytes needed for
					; the bootsector chainloading
					; code!
OrigESDI	equ StackBuf-4          ; The high dword on the stack
StackHome	equ OrigESDI

bootsec		equ $

_start:		; Far jump makes sure we canonicalize the address
		cli
		jmp 0:_start1
		times 8-($-$$) nop		; Pad to file offset 8

		; This table hopefully gets filled in by mkisofs using the
		; -boot-info-table option.  If not, the values in this
		; table are default values that we can use to get us what
		; we need, at least under a certain set of assumptions.
		global iso_boot_info
iso_boot_info:
bi_pvd:		dd 16				; LBA of primary volume descriptor
bi_file:	dd 0				; LBA of boot file
bi_length:	dd 0xdeadbeef			; Length of boot file
bi_csum:	dd 0xdeadbeef			; Checksum of boot file
bi_reserved:	times 10 dd 0xdeadbeef		; Reserved
bi_end:

		; Custom entry point for the hybrid-mode disk.
		; The following values will have been pushed onto the
		; entry stack:
		;	- partition offset (qword)
		;	- ES
		;	- DI
		;	- DX (including drive number)
		;	- CBIOS Heads
		;	- CBIOS Sectors
		;	- EBIOS flag
		;       (top of stack)
		;
		; If we had an old isohybrid, the partition offset will
		; be missing; we can check for that with sp >= 0x7c00.
		; Serious hack alert.
%ifndef DEBUG_MESSAGES
_hybrid_signature:
	       dd 0x7078c0fb			; An arbitrary number...

_start_hybrid:
		pop cx				; EBIOS flag
		pop word [cs:bsSecPerTrack]
		pop word [cs:bsHeads]
		pop dx
		pop di
		pop es
		xor eax,eax
		xor ebx,ebx
		cmp sp,7C00h
		jae .nooffset
		pop eax
		pop ebx
.nooffset:
		mov si,bios_cbios
		jcxz _start_common
		mov si,bios_ebios
		jmp _start_common
%endif

_start1:
		mov si,bios_cdrom
		xor eax,eax
		xor ebx,ebx
_start_common:
		mov [cs:InitStack],sp	; Save initial stack pointer
		mov [cs:InitStack+2],ss
		xor cx,cx
		mov ss,cx
		mov sp,StackBuf		; Set up stack
		push es			; Save initial ES:DI -> $PnP pointer
		push di
		mov ds,cx
		mov es,cx
		mov fs,cx
		mov gs,cx
		sti
		cld

		mov [Hidden],eax
		mov [Hidden+4],ebx

		mov [BIOSType],si
		mov eax,[si]
		mov [GetlinsecPtr],eax

		; Show signs of life
		mov si,syslinux_banner
		call writestr_early
%ifdef DEBUG_MESSAGES
		mov si,copyright_str
%else
		mov si,[BIOSName]
%endif
		call writestr_early

		;
		; Before modifying any memory, get the checksum of bytes
		; 64-2048
		;
initial_csum:	xor edi,edi
		mov si,bi_end
		mov cx,(SECTOR_SIZE-64) >> 2
.loop:		lodsd
		add edi,eax
		loop .loop
		mov [FirstSecSum],edi

		mov [DriveNumber],dl
%ifdef DEBUG_MESSAGES
		mov si,startup_msg
		call writemsg
		mov al,dl
		call writehex2
		call crlf
%endif
		;
		; Initialize spec packet buffers
		;
		mov di,_spec_start
		mov cx,_spec_len >> 2
		xor eax,eax
		rep stosd

		; Initialize length field of the various packets
		mov byte [spec_packet],13h
		mov byte [drive_params],30
		mov byte [dapa],16
		mov byte [dspec_packet],13h

		; Other nonzero fields
		inc word [dsp_sectors]

		; Are we just pretending to be a CD-ROM?
		cmp word [BIOSType],bios_cdrom
		jne found_drive			; If so, no spec packet...

		; Now figure out what we're actually doing
		; Note: use passed-in DL value rather than 7Fh because
		; at least some BIOSes will get the wrong value otherwise
		mov ax,4B01h			; Get disk emulation status
		mov dl,[DriveNumber]
		mov si,spec_packet
		call int13
		jc award_hack			; changed for BrokenAwardHack
		mov dl,[DriveNumber]
		cmp [sp_drive],dl		; Should contain the drive number
		jne spec_query_failed

%ifdef DEBUG_MESSAGES
		mov si,spec_ok_msg
		call writemsg
		mov al,byte [sp_drive]
		call writehex2
		call crlf
%endif

found_drive:
		; Alright, we have found the drive.  Now, try to find the
		; boot file itself.  If we have a boot info table, life is
		; good; if not, we have to make some assumptions, and try
		; to figure things out ourselves.  In particular, the
		; assumptions we have to make are:
		; - single session only
		; - only one boot entry (no menu or other alternatives)

		cmp dword [bi_file],0		; Address of code to load
		jne found_file			; Boot info table present :)

%ifdef DEBUG_MESSAGES
		mov si,noinfotable_msg
		call writemsg
%endif

		; No such luck.  See if the spec packet contained one.
		mov eax,[sp_lba]
		and eax,eax
		jz set_file			; Good enough

%ifdef DEBUG_MESSAGES
		mov si,noinfoinspec_msg
		call writemsg
%endif

		; No such luck.  Get the Boot Record Volume, assuming single
		; session disk, and that we're the first entry in the chain.
		mov eax,17			; Assumed address of BRV
		mov bx,trackbuf
		call getonesec

		mov eax,[trackbuf+47h]		; Get boot catalog address
		mov bx,trackbuf
		call getonesec			; Get boot catalog

		mov eax,[trackbuf+28h]		; First boot entry
		; And hope and pray this is us...

		; Some BIOSes apparently have limitations on the size
		; that may be loaded (despite the El Torito spec being very
		; clear on the fact that it must all be loaded.)  Therefore,
		; we load it ourselves, and *bleep* the BIOS.

set_file:
		mov [bi_file],eax

found_file:
		; Set up boot file sizes
		mov eax,[bi_length]
		sub eax,SECTOR_SIZE-3		; ... minus sector loaded
		shr eax,2			; bytes->dwords
		mov [ImageDwords],eax		; boot file dwords
		add eax,((SECTOR_SIZE-1) >> 2)
		shr eax,SECTOR_SHIFT-2		; dwords->sectors
		mov [ImageSectors],ax		; boot file sectors

		mov eax,[bi_file]		; Address of code to load
		inc eax				; Don't reload bootstrap code
%ifdef DEBUG_MESSAGES
		mov si,offset_msg
		call writemsg
		call writehex8
		call crlf
%endif

		; Load the rest of the file.  However, just in case there
		; are still BIOSes with 64K wraparound problems, we have to
		; take some extra precautions.  Since the normal load
		; address (TEXT_START) is *not* 2K-sector-aligned, we round
		; the target address upward to a sector boundary,
		; and then move the entire thing down as a unit.
MaxLMA		equ 384*1024		; Reasonable limit (384K)

		mov bx,((TEXT_START+2*SECTOR_SIZE-1) & ~(SECTOR_SIZE-1)) >> 4
		mov bp,[ImageSectors]
		push bx			; Load segment address

.more:
		push bx			; Segment address
		push bp			; Sector count
		mov es,bx
		mov cx,0xfff
		and bx,cx
		inc cx
		sub cx,bx
		shr cx,SECTOR_SHIFT - 4
		jnz .notaligned
		mov cx,0x10000 >> SECTOR_SHIFT	; Full 64K segment possible
.notaligned:
		cmp bp,cx
		jbe .ok
		mov bp,cx
.ok:
		xor bx,bx
		push bp
		call getlinsec
		pop cx
		mov dx,cx
		pop bp
		pop bx

		shl cx,SECTOR_SHIFT - 4
		add bx,cx
		sub bp,dx
		jnz .more

		; Move the image into place, and also verify the
		; checksum
		pop ax				; Load segment address
		mov bx,(TEXT_START + SECTOR_SIZE) >> 4
		mov ecx,[ImageDwords]
		mov edi,[FirstSecSum]		; First sector checksum
		xor si,si

move_verify_image:
.setseg:
		mov ds,ax
		mov es,bx
.loop:
		mov edx,[si]
		add edi,edx
		dec ecx
		mov [es:si],edx
		jz .done
		add si,4
		jnz .loop
		add ax,1000h
		add bx,1000h
		jmp .setseg
.done:
		mov ax,cs
		mov ds,ax
		mov es,ax

		; Verify the checksum on the loaded image.
		cmp [bi_csum],edi
		je integrity_ok

		mov si,checkerr_msg
		call writemsg
		jmp kaboom

integrity_ok:
%ifdef DEBUG_MESSAGES
		mov si,allread_msg
		call writemsg
%endif
		jmp all_read			; Jump to main code

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Start of BrokenAwardHack --- 10-nov-2002           Knut_Petersen@t-online.de
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; There is a problem with certain versions of the AWARD BIOS ...
;; the boot sector will be loaded and executed correctly, but, because the
;; int 13 vector points to the wrong code in the BIOS, every attempt to
;; load the spec packet will fail. We scan for the equivalent of
;;
;;	mov	ax,0201h
;;	mov	bx,7c00h
;;	mov	cx,0006h
;;	mov	dx,0180h
;;	pushf
;;	call	<direct far>
;;
;; and use <direct far> as the new vector for int 13. The code above is
;; used to load the boot code into ram, and there should be no reason
;; for anybody to change it now or in the future. There are no opcodes
;; that use encodings relativ to IP, so scanning is easy. If we find the
;; code above in the BIOS code we can be pretty sure to run on a machine
;; with an broken AWARD BIOS ...
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
									     ;;
%ifdef DEBUG_MESSAGES							     ;;
									     ;;
award_notice	db	"Trying BrokenAwardHack first ...",CR,LF,0	     ;;
award_not_orig	db	"BAH: Original Int 13 vector   : ",0		     ;;
award_not_new	db	"BAH: Int 13 vector changed to : ",0		     ;;
award_not_succ	db	"BAH: SUCCESS",CR,LF,0				     ;;
award_not_fail	db	"BAH: FAILURE"					     ;;
award_not_crlf	db	CR,LF,0						     ;;
									     ;;
%endif									     ;;
									     ;;
award_oldint13	dd	0						     ;;
award_string	db	0b8h,1,2,0bbh,0,7ch,0b9h,6,0,0bah,80h,1,09ch,09ah    ;;
									     ;;
						;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
award_hack:	mov	si,spec_err_msg		; Moved to this place from
		call	writemsg		; spec_query_faild
						;
%ifdef DEBUG_MESSAGES				;
						;
		mov	si,award_notice		; display our plan
		call	writemsg		;
		mov	si,award_not_orig	; display original int 13
		call	writemsg		; vector
%endif						;
		mov	eax,[13h*4]		;
		mov	[award_oldint13],eax	;
						;
%ifdef DEBUG_MESSAGES				;
						;
		call	writehex8		;
		mov	si,award_not_crlf	;
		call	writestr_early		;
%endif						;
		push	es			; save ES
		mov	ax,0f000h		; ES = BIOS Seg
		mov	es,ax			;
		cld				;
		xor	di,di			; start at ES:DI = f000:0
award_loop:	push	di			; save DI
		mov	si,award_string		; scan for award_string
		mov	cx,7			; length of award_string = 7dw
		repz	cmpsw			; compare
		pop	di			; restore DI
		jcxz	award_found		; jmp if found
		inc	di			; not found, inc di
		jno	award_loop		;
						;
award_failed:	pop	es			; No, not this way :-((
award_fail2:					;
						;
%ifdef DEBUG_MESSAGES				;
						;
		mov	si,award_not_fail	; display failure ...
		call	writemsg		;
%endif						;
		mov	eax,[award_oldint13]	; restore the original int
		or	eax,eax			; 13 vector if there is one
		jz	spec_query_failed	; and try other workarounds
		mov	[13h*4],eax		;
		jmp	spec_query_failed	;
						;
award_found:	mov	eax,[es:di+0eh]		; load possible int 13 addr
		pop	es			; restore ES
						;
		cmp	eax,[award_oldint13]	; give up if this is the
		jz	award_failed		; active int 13 vector,
		mov	[13h*4],eax		; otherwise change 0:13h*4
						;
						;
%ifdef DEBUG_MESSAGES				;
						;
		push	eax			; display message and
		mov	si,award_not_new	; new vector address
		call	writemsg		;
		pop	eax			;
		call	writehex8		;
		mov	si,award_not_crlf	;
		call	writestr_early		;
%endif						;
		mov	ax,4B01h		; try to read the spec packet
		mov	dl,[DriveNumber]	; now ... it should not fail
		mov	si,spec_packet		; any longer
		int	13h			;
		jc	award_fail2		;
						;
%ifdef DEBUG_MESSAGES				;
						;
		mov	si,award_not_succ	; display our SUCCESS
		call	writemsg		;
%endif						;
		jmp	found_drive		; and leave error recovery code
						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; End of BrokenAwardHack ----            10-nov-2002 Knut_Petersen@t-online.de
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


		; INT 13h, AX=4B01h, DL=<passed in value> failed.
		; Try to scan the entire 80h-FFh from the end.

spec_query_failed:

		; some code moved to BrokenAwardHack

		mov dl,0FFh
.test_loop:	pusha
		mov ax,4B01h
		mov si,spec_packet
		mov byte [si],13h		; Size of buffer
		call int13
		popa
		jc .still_broken

		mov si,maybe_msg
		call writemsg
		mov al,dl
		call writehex2
		call crlf

		cmp byte [sp_drive],dl
		jne .maybe_broken

		; Okay, good enough...
		mov si,alright_msg
		call writemsg
.found_drive0:	mov [DriveNumber],dl
.found_drive:	jmp found_drive

		; Award BIOS 4.51 apparently passes garbage in sp_drive,
		; but if this was the drive number originally passed in
		; DL then consider it "good enough"
.maybe_broken:
		mov al,[DriveNumber]
		cmp al,dl
		je .found_drive

		; Intel Classic R+ computer with Adaptec 1542CP BIOS 1.02
		; passes garbage in sp_drive, and the drive number originally
		; passed in DL does not have 80h bit set.
		or al,80h
		cmp al,dl
		je .found_drive0

.still_broken:	dec dx
		cmp dl, 80h
		jnb .test_loop

		; No spec packet anywhere.  Some particularly pathetic
		; BIOSes apparently don't even implement function
		; 4B01h, so we can't query a spec packet no matter
		; what.  If we got a drive number in DL, then try to
		; use it, and if it works, then well...
		mov dl,[DriveNumber]
		cmp dl,81h			; Should be 81-FF at least
		jb fatal_error			; If not, it's hopeless

		; Write a warning to indicate we're on *very* thin ice now
		mov si,nospec_msg
		call writemsg
		mov al,dl
		call writehex2
		call crlf
		mov si,trysbm_msg
		call writemsg
		jmp .found_drive		; Pray that this works...

fatal_error:
		mov si,nothing_msg
		call writemsg

.norge:		jmp short .norge

		; Information message (DS:SI) output
		; Prefix with "isolinux: "
		;
writemsg:	push ax
		push si
		mov si,isolinux_str
		call writestr_early
		pop si
		call writestr_early
		pop ax
		ret

;
; Write a character to the screen.  There is a more "sophisticated"
; version of this in the subsequent code, so we patch the pointer
; when appropriate.
;

writechr:
.simple:
		pushfd
		pushad
		mov ah,0Eh
		xor bx,bx
		int 10h
		popad
		popfd
		ret

;
; int13: save all the segment registers and call INT 13h.
;	 Some CD-ROM BIOSes have been found to corrupt segment registers
;	 and/or disable interrupts.
;
int13:
		pushf
		push bp
		push ds
		push es
		push fs
		push gs
		int 13h
		mov bp,sp
		setc [bp+10]		; Propagate CF to the caller
		pop gs
		pop fs
		pop es
		pop ds
		pop bp
		popf
		ret

;
; Get one sector.  Convenience entry point.
;
getonesec:
		mov bp,1
		; Fall through to getlinsec

;
; Get linear sectors - EBIOS LBA addressing, 2048-byte sectors.
;
; Input:
;	EAX	- Linear sector number
;	ES:BX	- Target buffer
;	BP	- Sector count
;
		global getlinsec
getlinsec:	jmp word [cs:GetlinsecPtr]

%ifndef DEBUG_MESSAGES

;
; First, the variants that we use when actually loading off a disk
; (hybrid mode.)  These are adapted versions of the equivalent routines
; in ldlinux.asm.
;

;
; getlinsec_ebios:
;
; getlinsec implementation for floppy/HDD EBIOS (EDD)
;
getlinsec_ebios:
		xor edx,edx
		shld edx,eax,2
		shl eax,2			; Convert to HDD sectors
		add eax,[Hidden]
		adc edx,[Hidden+4]
		shl bp,2

.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 dl,[DriveNumber]
		push ds
		push ss
		pop ds				; DS <- SS
		mov ah,42h			; Extended Read
		call int13
		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,9			; 512-byte sectors
                add bx,di			; Advance buffer pointer
                and bp,bp
                jnz .loop

                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]
		call int13
		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 word [GetlinsecPtr], getlinsec_cbios
		;mov byte [MaxTransfer],63	; Max possibe CBIOS transfer

		pop bp
		jmp getlinsec_cbios.loop

;
; getlinsec_cbios:
;
; getlinsec implementation for legacy CBIOS
;
getlinsec_cbios:
		xor edx,edx
		shl eax,2			; Convert to HDD sectors
		add eax,[Hidden]
		shl bp,2

.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

		; We should test this, but it doesn't fit...
		; cmp eax,1023
		; ja .error

		;
		; 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
		mov dl,[DriveNumber]
		xchg ax,bp		; Sector to transfer count
		mov ah,02h		; Read sectors
		mov bp,retry_count
.retry:
		pushad
		call int13
		popad
		jc .error
.resume:
		movzx ecx,al		; ECX <- sectors transferred
		shl ax,9		; 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
		ret

.error:
		dec bp
		jnz .retry

		xchg ax,bp		; Sectors transferred <- 0
		shr word [MaxTransfer],1
		jnz .resume
		jmp disk_error

;
; Truncate BP to MaxTransfer
;
maxtrans:
		cmp bp,[MaxTransfer]
		jna .ok
		mov bp,[MaxTransfer]
.ok:		ret

%endif

;
; This is the variant we use for real CD-ROMs:
; LBA, 2K sectors, some special error handling.
;
getlinsec_cdrom:
		mov si,dapa			; Load up the DAPA
		mov [si+4],bx
		mov [si+6],es
		mov [si+8],eax
.loop:
		push bp				; Sectors left
		cmp bp,[MaxTransferCD]
		jbe .bp_ok
		mov bp,[MaxTransferCD]
.bp_ok:
		mov [si+2],bp
		push si
		mov dl,[DriveNumber]
		mov ah,42h			; Extended Read
		call xint13
		pop si
		pop bp
		movzx eax,word [si+2]		; Sectors we read
		add [si+8],eax			; Advance sector pointer
		sub bp,ax			; Sectors left
		shl ax,SECTOR_SHIFT-4		; 2048-byte sectors -> segment
		add [si+6],ax			; Advance buffer pointer
		and bp,bp
		jnz .loop
		mov eax,[si+8]			; Next sector
		ret

		; INT 13h with retry
xint13:		mov byte [RetryCount],retry_count
.try:		pushad
		call int13
		jc .error
		add sp,byte 8*4			; Clean up stack
		ret
.error:
		mov [DiskError],ah		; Save error code
		popad
		mov [DiskSys],ax		; Save system call number
		dec byte [RetryCount]
		jz .real_error
		push ax
		mov al,[RetryCount]
		mov ah,[dapa+2]			; Sector transfer count
		cmp al,2			; Only 2 attempts left
		ja .nodanger
		mov ah,1			; Drop transfer size to 1
		jmp short .setsize
.nodanger:
		cmp al,retry_count-2
		ja .again			; First time, just try again
		shr ah,1			; Otherwise, try to reduce
		adc ah,0			; the max transfer size, but not to 0
.setsize:
		mov [MaxTransferCD],ah
		mov [dapa+2],ah
.again:
		pop ax
		jmp .try

.real_error:	mov si,diskerr_msg
		call writemsg
		mov al,[DiskError]
		call writehex2
		mov si,oncall_str
		call writestr_early
		mov ax,[DiskSys]
		call writehex4
		mov si,ondrive_str
		call writestr_early
		mov al,dl
		call writehex2
		call crlf
		; Fall through to kaboom

;
; kaboom: write a message and bail out.  Wait for a user keypress,
;	  then do a hard reboot.
;
		global kaboom
disk_error:
kaboom:
		RESET_STACK_AND_SEGS AX
		mov si,err_bootfailed
		call writestr
		call getchar
		cli
		mov word [BIOS_magic],0	; Cold reboot
		jmp 0F000h:0FFF0h	; Reset vector address

; -----------------------------------------------------------------------------
;  Common modules needed in the first sector
; -----------------------------------------------------------------------------

%include "writestr.inc"		; String output
writestr_early	equ writestr
%include "writehex.inc"		; Hexadecimal output

; -----------------------------------------------------------------------------
; Data that needs to be in the first sector
; -----------------------------------------------------------------------------

syslinux_banner	db CR, LF, MY_NAME, ' ', VERSION_STR, ' ', DATE_STR, ' ', 0
copyright_str   db ' Copyright (C) 1994-'
		asciidec YEAR
		db ' H. Peter Anvin et al', CR, LF, 0
isolinux_str	db 'isolinux: ', 0
%ifdef DEBUG_MESSAGES
startup_msg:	db 'Starting up, DL = ', 0
spec_ok_msg:	db 'Loaded spec packet OK, drive = ', 0
secsize_msg:	db 'Sector size ', 0
offset_msg:	db 'Main image LBA = ', 0
verify_msg:	db 'Image checksum verified.', CR, LF, 0
allread_msg	db 'Main image read, jumping to main code...', CR, LF, 0
%endif
noinfotable_msg	db 'No boot info table, assuming single session disk...', CR, LF, 0
noinfoinspec_msg db 'Spec packet missing LBA information, trying to wing it...', CR, LF, 0
spec_err_msg:	db 'Loading spec packet failed, trying to wing it...', CR, LF, 0
maybe_msg:	db 'Found something at drive = ', 0
alright_msg:	db 'Looks reasonable, continuing...', CR, LF, 0
nospec_msg	db 'Extremely broken BIOS detected, last attempt with drive = ', 0
nothing_msg:	db 'Failed to locate CD-ROM device; boot failed.', CR, LF
trysbm_msg	db 'See http://syslinux.zytor.com/sbm for more information.', CR, LF, 0
diskerr_msg:	db 'Disk error ', 0
oncall_str:	db ', AX = ',0
ondrive_str:	db ', drive ', 0
checkerr_msg:	db 'Image checksum error, sorry...', CR, LF, 0

err_bootfailed	db CR, LF, 'Boot failed: press a key to retry...'
bailmsg		equ err_bootfailed
crlf_msg	db CR, LF
null_msg	db 0

bios_cdrom_str	db 'ETCD', 0
%ifndef DEBUG_MESSAGES
bios_cbios_str	db 'CHDD', 0
bios_ebios_str	db 'EHDD' ,0
%endif

		alignz 4
bios_cdrom:	dw getlinsec_cdrom, bios_cdrom_str
%ifndef DEBUG_MESSAGES
bios_cbios:	dw getlinsec_cbios, bios_cbios_str
bios_ebios:	dw getlinsec_ebios, bios_ebios_str
%endif

; Maximum transfer size
MaxTransfer	dw 127				; Hard disk modes
MaxTransferCD	dw 32				; CD mode

rl_checkpt	equ $				; Must be <= 800h

		; This pads to the end of sector 0 and errors out on
		; overflow.
		times 2048-($-$$) db 0

; ----------------------------------------------------------------------------
;  End of code and data that have to be in the first sector
; ----------------------------------------------------------------------------

		section .text16

all_read:

; Test tracers
		TRACER 'T'
		TRACER '>'

;
; Common initialization code
;
%include "init.inc"

		; Patch the writechr routine to point to the full code
		mov di,writechr
		mov al,0e9h
		stosb
		mov ax,writechr_full-2
		sub ax,di
		stosw

; Tell the user we got this far...
%ifndef DEBUG_MESSAGES			; Gets messy with debugging on
		mov si,copyright_str
		call writestr_early
%endif

;
; 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.
;

;
; Now, we need to sniff out the actual filesystem data structures.
; mkisofs gave us a pointer to the primary volume descriptor
; (which will be at 16 only for a single-session disk!); from the PVD
; we should be able to find the rest of what we need to know.
;
init_fs:
		pushad
	        mov eax,ROOT_FS_OPS
	        mov dl,[DriveNumber]
               	cmp word [BIOSType],bios_cdrom
                sete dh                        ; 1 for cdrom, 0 for hybrid mode
		jne .hybrid
		movzx ebp,word [MaxTransferCD]
		jmp .common
.hybrid:
		movzx ebp,word [MaxTransfer]
.common:
	        mov ecx,[Hidden]
	        mov ebx,[Hidden+4]
                mov si,[bsHeads]
		mov di,[bsSecPerTrack]
		pm_call fs_init
		popad

		section .rodata
		alignz 4
ROOT_FS_OPS:
		extern iso_fs_ops
		dd iso_fs_ops
		dd 0

		section .text16

;
; Locate the configuration file
;
		pm_call pm_load_config
		jz no_config_file

;
; Now we have the config file open.  Parse the config file and
; run the user interface.
;
%include "ui.inc"

;
; Enable disk emulation.  The kind of disk we emulate is dependent on the
; size of the file: 1200K, 1440K or 2880K floppy, otherwise harddisk.
;
is_disk_image:
		TRACER CR
		TRACER LF
		TRACER 'D'
		TRACER ':'

		mov edx,eax			; File size
		mov di,img_table
		mov cx,img_table_count
		mov eax,[si+file_sector]	; Starting LBA of file
		mov [dsp_lba],eax		; Location of file
		mov byte [dsp_drive], 0		; 00h floppy, 80h hard disk
.search_table:
		TRACER 't'
		mov eax,[di+4]
		cmp edx,[di]
		je .type_found
		add di,8
		loop .search_table

		; Hard disk image.  Need to examine the partition table
		; in order to deduce the C/H/S geometry.  Sigh.
.hard_disk_image:
		TRACER 'h'
		cmp edx,512
		jb .bad_image

		mov bx,trackbuf
		mov cx,1			; Load 1 sector
		pm_call getfssec

		cmp word [trackbuf+510],0aa55h	; Boot signature
		jne .bad_image		; Image not bootable

		mov cx,4			; 4 partition entries
		mov di,trackbuf+446		; Start of partition table

		xor ax,ax			; Highest sector(al) head(ah)

.part_scan:
		cmp byte [di+4], 0
		jz .part_loop
		lea si,[di+1]
		call .hs_check
		add si,byte 4
		call .hs_check
.part_loop:
		add di,byte 16
		loop .part_scan

		push eax			; H/S
		push edx			; File size
		mov bl,ah
		xor bh,bh
		inc bx				; # of heads in BX
		xor ah,ah			; # of sectors in AX
		cwde				; EAX[31:16] <- 0
		mul bx
		shl eax,9			; Convert to bytes
		; Now eax contains the number of bytes per cylinder
		pop ebx				; File size
		xor edx,edx
		div ebx
		and edx,edx
		jz .no_remainder
		inc eax				; Fractional cylinder...
		; Now (e)ax contains the number of cylinders
.no_remainder:	cmp eax,1024
		jna .ok_cyl
		mov ax,1024			; Max possible #
.ok_cyl:	dec ax				; Convert to max cylinder no
		pop ebx				; S(bl) H(bh)
		shl ah,6
		or bl,ah
		xchg ax,bx
		shl eax,16
		mov ah,bl
		mov al,4			; Hard disk boot
		mov byte [dsp_drive], 80h	; Drive 80h = hard disk

.type_found:
		TRACER 'T'
		mov bl,[sp_media]
		and bl,0F0h			; Copy controller info bits
		or al,bl
		mov [dsp_media],al		; Emulation type
		shr eax,8
		mov [dsp_chs],eax		; C/H/S geometry
		mov ax,[sp_devspec]		; Copy device spec
		mov [dsp_devspec],ax
		mov al,[sp_controller]		; Copy controller index
		mov [dsp_controller],al

		TRACER 'V'
		call vgaclearmode		; Reset video

		mov ax,4C00h			; Enable emulation and boot
		mov si,dspec_packet
		mov dl,[DriveNumber]
		lss sp,[InitStack]
		TRACER 'X'

		call int13

		; If this returns, we have problems
.bad_image:
		mov si,err_disk_image
		call writestr
		jmp enter_command

;
; Look for the highest seen H/S geometry
; We compute cylinders separately
;
.hs_check:
		mov bl,[si]			; Head #
		cmp bl,ah
		jna .done_track
		mov ah,bl			; New highest head #
.done_track:	mov bl,[si+1]
		and bl,3Fh			; Sector #
		cmp bl,al
		jna .done_sector
		mov al,bl
.done_sector:	ret



; -----------------------------------------------------------------------------
;  Common modules
; -----------------------------------------------------------------------------

%include "common.inc"		; Universal modules
%include "rawcon.inc"		; Console I/O w/o using the console functions
%include "localboot.inc"	; Disk-based local boot

; -----------------------------------------------------------------------------
;  Begin data section
; -----------------------------------------------------------------------------

		section .data16
err_disk_image	db 'Cannot load disk image (invalid file)?', CR, LF, 0

;
; Config file keyword table
;
%include "keywords.inc"

;
; Extensions to search for (in *forward* order).
;
		alignz 4
exten_table:	db '.cbt'		; COMBOOT (specific)
		db '.img'		; Disk image
		db '.bin'		; CD boot sector
		db '.com'		; COMBOOT (same as DOS)
		db '.c32'		; COM32
exten_table_end:
		dd 0, 0			; Need 8 null bytes here

;
; Floppy image table
;
		alignz 4
img_table_count	equ 3
img_table:
		dd 1200*1024		; 1200K floppy
		db 1			; Emulation type
		db 80-1			; Max cylinder
		db 15			; Max sector
		db 2-1			; Max head

		dd 1440*1024		; 1440K floppy
		db 2			; Emulation type
		db 80-1			; Max cylinder
		db 18			; Max sector
		db 2-1			; Max head

		dd 2880*1024		; 2880K floppy
		db 3			; Emulation type
		db 80-1			; Max cylinder
		db 36			; Max sector
		db 2-1			; Max head
