;; -----------------------------------------------------------------------
;;
;;   Copyright 2007-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., 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.
;;
;; -----------------------------------------------------------------------

;;
;; adv.inc
;;
;; The auxillary data vector and its routines
;;
;; The auxillary data vector is a 512-byte aligned block that on the
;; disk-based derivatives can be part of the syslinux file itself.  It
;; exists in two copies; when written, both copies are written (with a
;; sync in between, if from the operating system.)  The first two
;; dwords are magic number and inverse checksum, then follows the data
;; area as a tagged array similar to BOOTP/DHCP, finally a tail
;; signature.
;;
;; Note that unlike BOOTP/DHCP, zero terminates the chain, and FF
;; has no special meaning.
;;

;;
;; List of ADV tags...
;;
ADV_BOOTONCE	equ 1

;;
;; Other ADV data...
;;
ADV_MAGIC1	equ 0x5a2d2fa5			; Head signature
ADV_MAGIC2	equ 0xa3041767			; Total checksum
ADV_MAGIC3	equ 0xdd28bf64			; Tail signature

ADV_LEN		equ 500				; Data bytes

adv_retries	equ 6				; Disk retries

		section .adv
		; Introduce the ADVs to valid but blank
adv0:
.head		resd 1
.csum		resd 1
.data		resb ADV_LEN
.tail		resd 1
.end		equ $
adv1:
.head		resd 1
.csum		resd 1
.data		resb ADV_LEN
.tail		resd 1
.end		equ $
		section .text16

		;
		; This is called after config file parsing, so we know
		; the intended location of the ADV
		;
adv_init:
		cmp byte [ADVDrive],-1
		jne adv_read

%if IS_SYSLINUX || IS_EXTLINUX
		cmp word [ADVSectors],2		; Not present?
		jb adv_verify

		mov eax,[Hidden]
		mov edx,[Hidden+4]
		add [ADVSec0],eax
		adc [ADVSec0+4],edx
		add [ADVSec1],eax
		adc [ADVSec1+4],edx
		mov al,[DriveNumber]
		mov [ADVDrive],al
		jmp adv_read
%endif

		;
		; Initialize the ADV data structure in memory
		;
adv_verify:
		cmp byte [ADVDrive],-1		; No ADV configured, still?
		je .reset			; Then unconditionally reset

		mov si,adv0
		call .check_adv
		jz .ok				; Primary ADV okay
		mov si,adv1
		call .check_adv
		jz .adv1ok

		; Neither ADV is usable; initialize to blank
.reset:
		mov di,adv0
		mov eax,ADV_MAGIC1
		stosd
		mov eax,ADV_MAGIC2
		stosd
		xor eax,eax
		mov cx,ADV_LEN/4
		rep stosd
		mov eax,ADV_MAGIC3
		stosd

.ok:
		ret

		; The primary ADV is bad, but the backup is OK
.adv1ok:
		mov di,adv0
		mov cx,512/4
		rep movsd
		ret


		; SI points to the putative ADV; unchanged by routine
		; ZF=1 on return if good
.check_adv:
		push si
		lodsd
		cmp eax,ADV_MAGIC1
		jne .done			; ZF=0, i.e. bad
		xor edx,edx
		mov cx,ADV_LEN/4+1		; Remaining dwords
.csum:
		lodsd
		add edx,eax
		loop .csum
		cmp edx,ADV_MAGIC2
		jne .done
		lodsd
		cmp eax,ADV_MAGIC3
.done:
		pop si
		ret

;
; adv_get: find an ADV string if present
;
; Input:	DL	= ADV ID
; Output:	CX	= byte count (zero on not found)
;		SI	= pointer to data
;		DL	= unchanged
;
; Assumes CS == DS.
;

adv_get:
		push ax
		mov si,adv0.data
		xor ax,ax			; Keep AH=0 at all times
.loop:
		lodsb				; Read ID
		cmp al,dl
		je .found
		and al,al
		jz .end
		lodsb				; Read length
		add si,ax
		cmp si,adv0.tail
		jb .loop
		jmp .end

.found:
		lodsb
		mov cx,ax
		add ax,si			; Make sure it fits
		cmp ax,adv0.tail
		jbe .ok
.end:
		xor cx,cx
.ok:
		pop ax
		ret

;
; adv_set: insert a string into the ADV in memory
;
; Input:	DL	= ADV ID
;		FS:BX	= input buffer
;		CX	= byte count (max = 255!)
; Output:	CF=1 on error
;		CX	clobbered
;
; Assumes CS == DS == ES.
;
adv_set:
		push ax
		push si
		push di
		and ch,ch
		jnz .overflow

		push cx
		mov si,adv0.data
		xor ax,ax
.loop:
		lodsb
		cmp al,dl
		je .found
		and al,al
		jz .endz
		lodsb
		add si,ax
		cmp si,adv0.tail
		jb .loop
		jmp .end

.found:		; Found, need to delete old copy
		lodsb
		lea di,[si-2]
		push di
		add si,ax
		mov cx,adv0.tail
		sub cx,si
		jb .nukeit
		rep movsb			; Remove the old one
		mov [di],ah			; Termination zero
		pop si
		jmp .loop
.nukeit:
		pop si
		jmp .end
.endz:
		dec si
.end:
		; Now SI points to where we want to put our data
		pop cx
		mov di,si
		jcxz .empty
		add si,cx
		cmp si,adv0.tail-2
		jae .overflow			; CF=0

		mov si,bx
		mov al,dl
		stosb
		mov al,cl
		stosb
		fs rep movsb

.empty:
		mov cx,adv0.tail
		sub cx,di
		xor ax,ax
		rep stosb			; Zero-fill remainder

		clc
.done:
		pop di
		pop si
		pop ax
		ret
.overflow:
		stc
		jmp .done

;
; adv_cleanup:	checksum adv0 and copy to adv1
;		Assumes CS == DS == ES.
;
adv_cleanup:
		pushad
		mov si,adv0.data
		mov cx,ADV_LEN/4
		xor edx,edx
.loop:
		lodsd
		add edx,eax
		loop .loop
		mov eax,ADV_MAGIC2
		sub eax,edx
		lea di,[si+4]			; adv1
		mov si,adv0
		mov [si+4],eax			; Store checksum
		mov cx,(ADV_LEN+12)/4
		rep movsd
		popad
		ret

;
; adv_write:	write the ADV to disk.
;
;		Location is in memory variables.
;		Assumes CS == DS == ES.
;
;		Returns CF=1 if the ADV cannot be written.
;
adv_write:
		push eax
		mov eax,[ADVSec0]
		or eax,[ADVSec0+4]
		je .bad
		mov eax,[ADVSec1]
		or eax,[ADVSec1+4]
		je .bad
		cmp byte [ADVDrive],-1
		je .bad

		call adv_cleanup
		mov ah,3			; Write
		call adv_read_write

		clc
		pop eax
		ret
.bad:						; No location for ADV set
		stc
		pop eax
		ret

;
; adv_read:	read the ADV from disk
;
;		Location is in memory variables.
;		Assumes CS == DS == ES.
;
adv_read:
		push ax
		mov ah,2			; Read
		call adv_read_write
		call adv_verify
		pop ax
		ret

;
; adv_read_write: disk I/O for the ADV
;
;		On input, AH=2 for read, AH=3 for write.
;		Assumes CS == DS == ES.
;
adv_read_write:
		mov [ADVOp],ah
		pushad

		; Check for EDD
		mov bx,55AAh
		mov ah,41h			; EDD existence query
		mov dl,[ADVDrive]
		int 13h
		mov si,.cbios
		jc .noedd
		cmp bx,0AA55h
		jne .noedd
		test cl,1
		jz .noedd
		mov si,.ebios
.noedd:

		mov eax,[ADVSec0]
		mov edx,[ADVSec0+4]
		mov bx,adv0
		call .doone

		mov eax,[ADVSec1]
		mov edx,[ADVSec1+4]
		mov bx,adv1
		call .doone

		popad
		ret

.doone:
		push si
		jmp si

.ebios:
		mov cx,adv_retries
.eb_retry:
		; Form DAPA on stack
		push edx
		push eax
		push es
		push bx
		push word 1			; Sector count
		push word 16			; DAPA size
		mov si,sp
		pushad
		mov dl,[ADVDrive]
		mov ax,4000h
		or ah,[ADVOp]
		push ds
		push ss
		pop ds
		int 13h
		pop ds
		popad
		lea sp,[si+16]			; Remove DAPA
		jc .eb_error
		pop si
		ret
.eb_error:
		loop .eb_retry
		stc
		pop si
		ret

.cbios:
		push edx
		push eax
		push bp

		and edx,edx			; > 2 TiB not possible
		jnz .cb_overflow

		mov dl,[ADVDrive]
		and dl,dl
		; Floppies: can't trust INT 13h 08h, we better know
		; the geometry a priori, which means it better be our
		; boot device...
		jns .noparm			; Floppy drive... urk

		mov ah,08h			; Get disk parameters
		int 13h
		jc .noparm
		and ah,ah
		jnz .noparm
		shr dx,8
		inc dx
		movzx edi,dx			; EDI = heads
		and cx,3fh
		movzx esi,cx			; ESI = sectors/track
		jmp .parmok

.noparm:
		; No CHS info... this better be our boot drive, then
%if IS_SYSLINUX || IS_EXTLINUX
		cmp dl,[DriveNumber]
		jne .cb_overflow		; Fatal error!
		movzx esi,word [bsSecPerTrack]
		movzx edi,word [bsHeads]
%else
		; Not a disk-based derivative... there is no hope
		jmp .cb_overflow
%endif

.parmok:
                ;
                ; Dividing by sectors to get (track,sector): we may have
                ; up to 2^18 tracks, so we need to use 32-bit arithmetric.
                ;
		xor edx,edx
                div esi
                xor cx,cx
                xchg cx,dx              ; CX <- sector index (0-based)
                                        ; EDX <- 0
                ; eax = track #
                div edi                 ; Convert track to head/cyl

		; Watch out for overflow, we might be writing!
		cmp eax,1023
                ja .cb_overflow

                ;
                ; Now we have AX = cyl, DX = head, CX = sector (0-based),
                ; BP = sectors to transfer, 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 dl,[ADVDrive]
		mov al,01h		; Transfer one sector
                mov ah,[ADVOp]		; Operation

		mov bp,adv_retries
.cb_retry:
                pushad
                int 13h
                popad
                jc .cb_error

.cb_done:
                pop bp
                pop eax
                pop edx
		pop si
                ret

.cb_error:
                dec bp
                jnz .cb_retry
.cb_overflow:
		stc
		jmp .cb_done

		section .data16
		alignz 8
ADVSec0		dq 0			; Not specified
ADVSec1		dq 0			; Not specified
ADVDrive	db -1			; No ADV defined
ADVCHSInfo	db -1			; We have CHS info for this drive

		section .bss16
ADVOp		resb 1

		section .text16
