; -*- 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-2008 H. Peter Anvin - All Rights Reserved
;
;  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
FILENAME_MAX_LG2 equ 8			; log2(Max filename size Including final null)
FILENAME_MAX	equ (1 << FILENAME_MAX_LG2)
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
MAX_OPEN_LG2	equ 6			; log2(Max number of open files)
MAX_OPEN	equ (1 << MAX_OPEN_LG2)
SECTOR_SHIFT	equ 11			; 2048 bytes/sector (El Torito requirement)
SECTOR_SIZE	equ (1 << SECTOR_SHIFT)

;
; This is what we need to do when idle
;
%macro	RESET_IDLE 0
	; Nothing
%endmacro
%macro	DO_IDLE 0
	; Nothing
%endmacro

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

;
; Segment assignments in the bottom 640K
; 0000h - main code/data segment (and BIOS segment)
;
real_mode_seg	equ 2000h
xfer_buf_seg	equ 1000h		; Bounce buffer for I/O to high mem
comboot_seg	equ real_mode_seg	; COMBOOT image loading zone

;
; 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_left	resd 1			; Number of sectors left
		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

		struc dir_t
dir_lba		resd 1			; Directory start (LBA)
dir_len		resd 1			; Length in bytes
dir_clust	resd 1			; Length in clusters
		endstruc

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

;
; Memory below this point is reserved for the BIOS and the MBR
;
		section .earlybss
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 .uibss.
		section .bss1
		alignb 4
ISOFileName	resb 64			; ISO filename canonicalization buffer
ISOFileNameEnd	equ $
CurDir		resb dir_t_size		; Current directory
RootDir		resb dir_t_size		; Root directory
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
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

_spec_start	equ $

;
; El Torito spec packet
;

		alignb 8
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

		alignb open_file_t_size
Files		resb MAX_OPEN*open_file_t_size

		section .text
;;
;; 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 $-44		; 44 bytes needed for
					; the bootsector chainloading
					; code!
OrigESDI	equ StackBuf-4          ; The high dword on the stack

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

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

		cld
		; Show signs of life
		mov si,syslinux_banner
		call writestr
%ifdef DEBUG_MESSAGES
		mov si,copyright_str
		call writestr
%endif

		;
		; Before modifying any memory, get the checksum of bytes
		; 64-2048
		;
initial_csum:	xor edi,edi
		mov si,_start1
		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]

		; 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
		shr eax,2			; bytes->dwords
		mov [ImageDwords],eax		; boot file dwords
		add eax,(2047 >> 2)
		shr eax,9			; 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

		; Just in case some BIOSes have problems with
		; segment wraparound, use the normalized address
		mov bx,((7C00h+2048) >> 4)
		mov es,bx
		xor bx,bx
		mov bp,[ImageSectors]
%ifdef DEBUG_MESSAGES
		push ax
		mov si,size_msg
		call writemsg
		mov ax,bp
		call writehex4
		call crlf
		pop ax
%endif
		call getlinsec

		push ds
		pop es

%ifdef DEBUG_MESSAGES
		mov si,loaded_msg
		call writemsg
%endif

		; Verify the checksum on the loaded image.
verify_image:
		mov si,7C00h+2048
		mov bx,es
		mov ecx,[ImageDwords]
		mov edi,[FirstSecSum]		; First sector checksum
.loop		es lodsd
		add edi,eax
		dec ecx
		jz .done
		and si,si
		jnz .loop
		; SI wrapped around, advance ES
		add bx,1000h
		mov es,bx
		jmp short .loop
.done:		mov ax,ds
		mov es,ax
		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		;
%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		;
%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
		pop si
		call writestr
		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:
		jmp near writechr_simple	; 3-byte jump

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

		push ds
		push es
		push fs
		push gs
		int 13h
		pop gs
		pop fs
		pop es
		pop ds
		ret

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

;
; Get linear sectors - EBIOS LBA addressing, 2048-byte sectors.
;
; Note that we can't always do this as a single request, because at least
; Phoenix BIOSes has a 127-sector limit.  To be on the safe side, stick
; to 32 sectors (64K) per request.
;
; Input:
;	EAX	- Linear sector number
;	ES:BX	- Target buffer
;	BP	- Sector count
;
getlinsec:
		mov si,dapa			; Load up the DAPA
		mov [si+4],bx
		mov bx,es
		mov [si+6],bx
		mov [si+8],eax
.loop:
		push bp				; Sectors left
		cmp bp,[MaxTransfer]
		jbe .bp_ok
		mov bp,[MaxTransfer]
.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 [MaxTransfer],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
		mov ax,[DiskSys]
		call writehex4
		mov si,ondrive_str
		call writestr
		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.
;
kaboom:
		RESET_STACK_AND_SEGS AX
		mov si,err_bootfailed
		call cwritestr
		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	equ cwritestr
%include "writehex.inc"		; Hexadecimal output

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

syslinux_banner	db CR, LF, 'ISOLINUX ', version_str, ' ', date, ' ', 0
copyright_str   db ' Copyright (C) 1994-', year, ' H. Peter Anvin'
		db 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 appears to be ', 0
offset_msg:	db 'Loading main image from LBA = ', 0
size_msg:	db 'Sectors to load = ', 0
loaded_msg:	db 'Loaded boot image, verifying...', CR, LF, 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 like it might be right, continuing...', CR, LF, 0
nospec_msg	db 'Extremely broken BIOS detected, last ditch 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

		alignb 4, db 0
MaxTransfer	dw 32				; Max sectors per transfer

rl_checkpt	equ $				; Must be <= 800h

rl_checkpt_off	equ ($-$$)
;%ifndef DEPEND
;%if rl_checkpt_off > 0x800
;%error "Sector 0 overflow"
;%endif
;%endif

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

all_read:

; Test tracers
		TRACER 'T'
		TRACER '>'

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

		; Patch the writechr routine to point to the full code
		mov word [writechr+1], writechr_full-(writechr+3)

; Tell the user we got this far...
%ifndef DEBUG_MESSAGES			; Gets messy with debugging on
		mov si,copyright_str
		call writestr
%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.
;
get_fs_structures:
		mov eax,[bi_pvd]
		mov bx,trackbuf
		call getonesec

		mov eax,[trackbuf+156+2]
		mov [RootDir+dir_lba],eax
		mov [CurDir+dir_lba],eax
%ifdef DEBUG_MESSAGES
		mov si,dbg_rootdir_msg
		call writemsg
		call writehex8
		call crlf
%endif
		mov eax,[trackbuf+156+10]
		mov [RootDir+dir_len],eax
		mov [CurDir+dir_len],eax
		add eax,SECTOR_SIZE-1
		shr eax,SECTOR_SHIFT
		mov [RootDir+dir_clust],eax
		mov [CurDir+dir_clust],eax

		; Look for an isolinux directory, and if found,
		; make it the current directory instead of the root
		; directory.
		mov di,boot_dir			; Search for /boot/isolinux
		mov al,02h
		call searchdir_iso
		jnz .found_dir
		mov di,isolinux_dir
		mov al,02h			; Search for /isolinux
		call searchdir_iso
		jz .no_isolinux_dir
.found_dir:
		mov [CurDir+dir_len],eax
		mov eax,[si+file_left]
		mov [CurDir+dir_clust],eax
		xor eax,eax			; Free this file pointer entry
		xchg eax,[si+file_sector]
		mov [CurDir+dir_lba],eax
%ifdef DEBUG_MESSAGES
		push si
		mov si,dbg_isodir_msg
		call writemsg
		pop si
		call writehex8
		call crlf
%endif
.no_isolinux_dir:

;
; Locate the configuration file
;
load_config:
%ifdef DEBUG_MESSAGES
		mov si,dbg_config_msg
		call writemsg
%endif

		mov si,config_name
		mov di,ConfigName
		call strcpy

		mov di,ConfigName
		call open
		jz no_config_file		; Not found or empty

%ifdef DEBUG_MESSAGES
		mov si,dbg_configok_msg
		call writemsg
%endif

;
; 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 ':'

		shl edx,16
		mov dx,ax			; Set EDX <- 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
		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 cwritestr
		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

;
; Boot a specified local disk.  AX specifies the BIOS disk number; or
; 0xFFFF in case we should execute INT 18h ("next device.")
;
local_boot:
		call vgaclearmode
		lss sp,[cs:Stack]		; Restore stack pointer
		xor dx,dx
		mov ds,dx
		mov es,dx
		mov fs,dx
		mov gs,dx
		mov si,localboot_msg
		call writestr
		cmp ax,-1
		je .int18

		; Load boot sector from the specified BIOS device and jump to it.
		mov dl,al
		xor dh,dh
		push dx
		xor ax,ax			; Reset drive
		call xint13
		mov ax,0201h			; Read one sector
		mov cx,0001h			; C/H/S = 0/0/1 (first sector)
		mov bx,trackbuf
		call xint13
		pop dx
		cli				; Abandon hope, ye who enter here
		mov si,trackbuf
		mov di,07C00h
		mov cx,512			; Probably overkill, but should be safe
		rep movsd
		lss sp,[cs:InitStack]
		jmp 0:07C00h			; Jump to new boot sector

.int18:
		int 18h				; Hope this does the right thing...
		jmp kaboom			; If we returned, oh boy...

;
; close_file:
;	     Deallocates a file structure (pointer in SI)
;	     Assumes CS == DS.
;
close_file:
		and si,si
		jz .closed
		mov dword [si],0		; First dword == file_left
.closed:	ret

;
; searchdir:
;
;	Open a file
;
;	     On entry:
;		DS:DI	= filename
;	     If successful:
;		ZF clear
;		SI		= file pointer
;		DX:AX or EAX	= file length in bytes
;	     If unsuccessful
;		ZF set
;
; Assumes CS == DS == ES, and trashes BX and CX.
;
; searchdir_iso is a special entry point for ISOLINUX only.  In addition
; to the above, searchdir_iso passes a file flag mask in AL.  This is useful
; for searching for directories.
;
alloc_failure:
		xor ax,ax			; ZF <- 1
		ret

searchdir:
		xor al,al
searchdir_iso:
		mov [ISOFlags],al
		TRACER 'S'
		call allocate_file		; Temporary file structure for directory
		jnz alloc_failure
		push es
		push ds
		pop es				; ES = DS
		mov si,CurDir
		cmp byte [di],'/'		; If filename begins with slash
		jne .not_rooted
		inc di				; Skip leading slash
		mov si,RootDir			; Reference root directory instead
.not_rooted:
		mov eax,[si+dir_clust]
		mov [bx+file_left],eax
		mov eax,[si+dir_lba]
		mov [bx+file_sector],eax
		mov edx,[si+dir_len]

.look_for_slash:
		mov ax,di
.scan:
		mov cl,[di]
		inc di
		and cl,cl
		jz .isfile
		cmp cl,'/'
		jne .scan
		mov [di-1],byte 0		; Terminate at directory name
		mov cl,02h			; Search for directory
		xchg cl,[ISOFlags]

		push di				; Save these...
		push cx

		; Create recursion stack frame...
		push word .resume		; Where to "return" to
		push es
.isfile:	xchg ax,di

.getsome:
		; Get a chunk of the directory
		; This relies on the fact that ISOLINUX doesn't change SI
		mov si,trackbuf
		TRACER 'g'
		pushad
		xchg bx,si
		mov cx,[BufSafe]
		call getfssec
		popad

.compare:
		movzx eax,byte [si]		; Length of directory entry
		cmp al,33
		jb .next_sector
		TRACER 'c'
		mov cl,[si+25]
		xor cl,[ISOFlags]
		test cl, byte 8Eh		; Unwanted file attributes!
		jnz .not_file
		pusha
		movzx cx,byte [si+32]		; File identifier length
		add si,byte 33			; File identifier offset
		TRACER 'i'
		call iso_compare_names
		popa
		je .success
.not_file:
		sub edx,eax			; Decrease bytes left
		jbe .failure
		add si,ax			; Advance pointer

.check_overrun:
		; Did we finish the buffer?
		cmp si,trackbuf+trackbufsize
		jb .compare			; No, keep going

		jmp short .getsome		; Get some more directory

.next_sector:
		; Advance to the beginning of next sector
		lea ax,[si+SECTOR_SIZE-1]
		and ax,~(SECTOR_SIZE-1)
		sub ax,si
		jmp short .not_file		; We still need to do length checks

.failure:	xor eax,eax			; ZF = 1
		mov [bx+file_sector],eax
		pop es
		ret

.success:
		mov eax,[si+2]			; Location of extent
		mov [bx+file_sector],eax
		mov eax,[si+10]			; Data length
		push eax
		add eax,SECTOR_SIZE-1
		shr eax,SECTOR_SHIFT
		mov [bx+file_left],eax
		pop eax
		mov edx,eax
		shr edx,16
		and bx,bx			; ZF = 0
		mov si,bx
		pop es
		ret

.resume:	; We get here if we were only doing part of a lookup
		; This relies on the fact that .success returns bx == si
		xchg edx,eax			; Directory length in edx
		pop cx				; Old ISOFlags
		pop di				; Next filename pointer
		mov byte [di-1], '/'		; Restore slash
		mov [ISOFlags],cl		; Restore the flags
		jz .failure			; Did we fail?  If so fail for real!
		jmp .look_for_slash		; Otherwise, next level

;
; allocate_file: Allocate a file structure
;
;		If successful:
;		  ZF set
;		  BX = file pointer
;		In unsuccessful:
;		  ZF clear
;
allocate_file:
		TRACER 'a'
		push cx
		mov bx,Files
		mov cx,MAX_OPEN
.check:		cmp dword [bx], byte 0
		je .found
		add bx,open_file_t_size		; ZF = 0
		loop .check
		; ZF = 0 if we fell out of the loop
.found:		pop cx
		ret

;
; iso_compare_names:
;	Compare the names DS:SI and DS:DI and report if they are
;	equal from an ISO 9660 perspective.  SI is the name from
;	the filesystem; CX indicates its length, and ';' terminates.
;	DI is expected to end with a null.
;
;	Note: clobbers AX, CX, SI, DI; assumes DS == ES == base segment
;

iso_compare_names:
		; First, terminate and canonicalize input filename
		push di
		mov di,ISOFileName
.canon_loop:	jcxz .canon_end
		lodsb
		dec cx
		cmp al,';'
		je .canon_end
		and al,al
		je .canon_end
		stosb
		cmp di,ISOFileNameEnd-1		; Guard against buffer overrun
		jb .canon_loop
.canon_end:
		cmp di,ISOFileName
		jbe .canon_done
		cmp byte [di-1],'.'		; Remove terminal dots
		jne .canon_done
		dec di
		jmp short .canon_end
.canon_done:
		mov [di],byte 0			; Null-terminate string
		pop di
		mov si,ISOFileName
.compare:
		lodsb
		mov ah,[di]
		inc di
		and ax,ax
		jz .success			; End of string for both
		and al,al			; Is either one end of string?
		jz .failure			; If so, failure
		and ah,ah
		jz .failure
		or ax,2020h			; Convert to lower case
		cmp al,ah
		je .compare
.failure:	and ax,ax			; ZF = 0 (at least one will be nonzero)
.success:	ret

;
; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed
;	       to by ES:DI; ends on encountering any whitespace.
;	       DI is preserved.
;
;	       This verifies that a filename is < FILENAME_MAX characters,
;	       doesn't contain whitespace, zero-pads the output buffer,
;	       and removes trailing dots and redundant slashes,
;	       so "repe cmpsb" can do a compare, and the
;	       path-searching routine gets a bit of an easier job.
;
mangle_name:
		push di
		push bx
		xor ax,ax
		mov cx,FILENAME_MAX-1
		mov bx,di

.mn_loop:
		lodsb
		cmp al,' '			; If control or space, end
		jna .mn_end
		cmp al,ah			; Repeated slash?
		je .mn_skip
		xor ah,ah
		cmp al,'/'
		jne .mn_ok
		mov ah,al
.mn_ok		stosb
.mn_skip:	loop .mn_loop
.mn_end:
		cmp bx,di			; At the beginning of the buffer?
		jbe .mn_zero
		cmp byte [es:di-1],'.'		; Terminal dot?
		je .mn_kill
		cmp byte [es:di-1],'/'		; Terminal slash?
		jne .mn_zero
.mn_kill:	dec di				; If so, remove it
		inc cx
		jmp short .mn_end
.mn_zero:
		inc cx				; At least one null byte
		xor ax,ax			; Zero-fill name
		rep stosb
		pop bx
		pop di
		ret				; Done

;
; 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.
;
;                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:	call strcpy
		dec di				; Point to final null byte
		ret

;
; getfssec: Get multiple clusters from a file, given the file pointer.
;
;  On entry:
;	ES:BX	-> Buffer
;	SI	-> File pointer
;	CX	-> Cluster count
;  On exit:
;	SI	-> File pointer (or 0 on EOF)
;	CF = 1	-> Hit EOF
;
getfssec:
		TRACER 'F'

		push ds
		push cs
		pop ds				; DS <- CS

		movzx ecx,cx
		cmp ecx,[si+file_left]
		jna .ok_size
		mov ecx,[si+file_left]
.ok_size:

		mov bp,cx
		push cx
		push si
		mov eax,[si+file_sector]
		TRACER 'l'
		call getlinsec
		xor ecx,ecx
		pop si
		pop cx

		add [si+file_sector],ecx
		sub [si+file_left],ecx
		ja .not_eof			; CF = 0

		xor ecx,ecx
		mov [si+file_sector],ecx	; Mark as unused
		xor si,si
		stc

.not_eof:
		pop ds
		TRACER 'f'
		ret

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

%include "getc.inc"		; getc et al
%include "conio.inc"		; Console I/O
%include "configinit.inc"	; Initialize configuration
%include "parseconfig.inc"	; High-level config file handling
%include "parsecmd.inc"		; Low-level config file handling
%include "bcopy32.inc"		; 32-bit bcopy
%include "loadhigh.inc"		; Load a file into high memory
%include "font.inc"		; VGA font stuff
%include "graphics.inc"		; VGA graphics
%include "highmem.inc"		; High memory sizing
%include "strcpy.inc"		; strcpy()
%include "rawcon.inc"		; Console I/O w/o using the console functions
%include "adv.inc"		; Auxillary Data Vector

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

		section .data

localboot_msg	db 'Booting from local disk...', CR, LF, 0
default_str	db 'default', 0
default_len	equ ($-default_str)
boot_dir	db '/boot'			; /boot/isolinux
isolinux_dir	db '/isolinux', 0
config_name	db 'isolinux.cfg', 0
err_disk_image	db 'Cannot load disk image (invalid file)?', CR, LF, 0

%ifdef DEBUG_MESSAGES
dbg_rootdir_msg	db 'Root directory at LBA = ', 0
dbg_isodir_msg	db 'isolinux directory at LBA = ', 0
dbg_config_msg	db 'About to load config file...', CR, LF, 0
dbg_configok_msg	db 'Configuration file opened...', CR, LF, 0
%endif
;
; 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
;
%include "keywords.inc"

;
; Extensions to search for (in *forward* order).
;
		align 4, db 0
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
;
		align 4, db 0
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

;
; Misc initialized (data) variables
;

;
; Variables that are uninitialized in SYSLINUX but initialized here
;
; **** ISOLINUX:: We may have to make this flexible, based on what the
; **** BIOS expects our "sector size" to be.
;
		alignb 4, db 0
BufSafe		dw trackbufsize/SECTOR_SIZE	; Clusters we can load into trackbuf
BufSafeBytes	dw trackbufsize		; = how many bytes?
%ifndef DEPEND
%if ( trackbufsize % SECTOR_SIZE ) != 0
%error trackbufsize must be a multiple of SECTOR_SIZE
%endif
%endif
