/*
 * lddisk.S86  -  Load boot sector from a disk
 *
 * Copyright (C) 2003-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: lddisk.S86,v 1.8 2007/01/06 18:31:38 gkminix Exp $
 *
 *====================================================================
 */

#include "rtcommon.i86"
#include "bootsect.i86"
#include "headers/osload.i86"
#include <system/bios.i86>
#include <system/partition.i86>

.file __FILE__
.line __LINE__



/*
 *====================================================================
 *
 * Start of the text segment and definition of all public entry points
 */

	.text

	.global	loaddisk

	.extern	movmem			# externals from runtime library
	.extern	getext
	.extern	brk
	.extern	sbrk



/*
 *====================================================================
 *
 * Load a disk boot sector
 * Input:  1. Arg  -  Length of disk ID string (in AX)
 *         2. Arg  -  Pointer to disk ID string
 * Output: AL      -  Drive number
 *         AH      -  Partition number
 * Registers changed: EAX
 */
loaddisk:

	push	bp
	mov	bp,sp
	push	ebx
	push	ecx			# save all registers
	push	edx
	push	si
	push	di

	mov	word ptr [partbuf],0	# clear buffer offsets
	mov	word ptr [bootbuf],0

/*
 * First analyze the disk ID string and determine the requested drive
 * and partition number. The ID string corresponds to the device name
 * used by Linux.
 */

	mov	cx,ax
	mov	si,[bp + 4]		# get the string address
	call	getdrive		# analyze the string
	jc	8f

/*
 * Now assign us some memory for the disk buffers
 */

	mov	ax,SECTSIZE * 2
	call	sbrk			# get buffer memory
	cmp	ax,-1
	jne	1f			# check for error
	mov	ax,LOADERR_HEAP
	jmp	9f

1:	cld
	mov	[partbuf],ax		# save buffer offsets
	add	ax,SECTSIZE
	mov	[bootbuf],ax

/*
 * Determine the parameters of the requested drive, like the geometry
 */

	call	getparams
	jc	8f

/*
 * If we have to read from a floppy drive, there is no partition
 * table.
 */

	mov	al,[drvnum]
	test	al,0x80
	jnz	3f
2:	cld
	mov	di,[partbuf]		# clear partition table buffer
	xor	ax,ax
	mov	cx,(SECTSIZE / 2) - 1
	rep	stosw
	mov	ax,PART_SIG
	stosw
	mov	word ptr [partofs],0
	mov	eax,1			# read first sector of floppy
	jmp	4f

/*
 * With a hard disk we have to scan through all partition tables. We use
 * a similar notation as the linux kernel: partition 0 is the master boot
 * record, partitions 1 through 4 are the main partitions, and partitions
 * 5 and up are logical partitions.
 */

3:	mov	cx,[partnum]		# for partition 0, read the MBR
	jcxz	2b			# as the boot sector
	call	scanparts		# find the partition table entry
	jc	8f			# of the requested partition
	mov	[partofs],si
	mov	bx,[partbuf]		# get the partition start sector
	mov	eax,part_first_abs_sect[bx + si]

/*
 * Read the boot sector and check that it really is a boot sector
 */

4:	mov	bx,[bootbuf]
	call	readsect
	jc	8f
	cmp	word ptr [bx + BOOT_SIG_OFS],BOOT_SIG
	jne	8f

/*
 * Finally move the buffers into extended memory as required by the
 * boot image load code.
 */

	call	getext			# get amount of extended memory
	cmp	eax,(IMAGE_ADDR - 0x00100000 + 2 * SECTSIZE) / 1024
	mov	ax,LOADERR_MEMORY	# check that we have enough
	jb	9f

	movzx32	ebx,word ptr [partbuf]
	xor	eax,eax
	mov	ax,ds
	shl	eax,4
	add	eax,ebx			# copy both buffers into extended
	mov	ebx,IMAGE_ADDR		# memory
	mov	ecx,SECTSIZE * 2
	call	movmem
	mov	ax,LOADERR_COPY
	jc	9f
	xor	ax,ax			# return without error
	jmp	9f

8:	mov	ax,LOADERR_DISK		# set global error code
9:	push	ax
	mov	ax,[partbuf]
	or	ax,ax			# check if we have assigned the buffers
	jz	5f
	call	brk			# clear all buffer memory
5:	pop	ax

	or	ax,ax			# do not prepare a return value if
	mov	ax,0xFFFF		# we are returning with an error
	jnz	7f
	xor	ah,ah
	mov	bx,[partofs]		# check if we have to return a
	sub	bx,PART_TABLE_OFS	# partition number
	jb	6f
	shr	bx,4			# compute partition number from
	inc	bx			# offset into partition table
	and	bx,0x0007
	mov	ah,bl
6:	mov	al,[drvnum]		# return drive number
7:	pop	di
	pop	si
	pop	edx
	pop	ecx			# restore all registers
	pop	ebx
	pop	bp
	ret



/*
 *====================================================================
 *
 * Analyze the disk ID string and determine the drive and partition
 * numbers.
 * Input:  CX  -  Length of ID string
 *         SI  -  Pointer to ID string
 * Output: Carry flag set if error
 * Registers changed: AX, CX, DX, SI
 */
getdrive:

	cld
	xor	ax,ax
	mov	[drvnum],al
	mov	[partnum],ax
	lodsb				# get first character
	dec	cx
	jz	8f
	xor	ah,ah
	cmp	al,0x46
	je	1f
	cmp	al,0x66			# check for floppy disk
	je	1f
	cmp	al,0x48
	je	6f
	cmp	al,0x68			# check for hard disk
	jne	8f
6:	inc	ah

1:	lodsb				# get second character
	dec	cx
	jz	8f
	cmp	al,0x44
	je	7f
	cmp	al,0x64			# has to be 'd'
	jne	8f
7:	or	ah,ah			# check if we have a hard disk
	jnz	2f

	lodsb				# get drive number
	dec	cx
	jnz	8f			# there should be no more chars
	cmp	al,0x30			# check if its a valid number
	jb	8f
	cmp	al,0x39
	ja	8f
	sub	al,0x30			# save BIOS drive number
	mov	[drvnum],al
	jmp	4f

2:	lodsb				# get drive letter
	cmp	al,0x41
	jb	8f			# has to be between 'a' and 'f'
	cmp	al,0x46
	jbe	5f
	cmp	al,0x61			# check for lower case letter
	jb	8f
	cmp	al,0x66
	ja	8f
	sub	al,0x20
5:	sub	al,0x41			# compute BIOS drive number
	or	al,0x80
	mov	[drvnum],al
	dec	cx			# if there are no more letters,
	jz	4f			# we dont have a partition number

	xor	dx,dx
3:	cmp	dx,3			# check that we dont exceed 30
	ja	8f
	mov	ax,10
	mul	dx			# multiply by 10
	mov	dx,ax
	lodsb				# get next partition number letter
	cmp	al,0x30
	jb	8f			# check for valid digit
	cmp	al,0x39
	ja	8f
	sub	al,0x30			# add to partition number
	xor	ah,ah
	add	dx,ax
	dec	cx			# continue with next number
	jnz	3b
	or	dx,dx			# partition 0 doesnt exist
	jz	8f
	mov	[partnum],dx		# save partition number

4:	clc				# return without error
	jmp	9f

8:	stc				# return with error
9:	ret



/*
 *====================================================================
 *
 * Get the drive parameters
 * Input:  none
 * Output: Carry flag set if error
 * Registers changed: AX, BX, CX, DX, SI, DI
 */
getparams:

/* Check if we can use the Enhanced Disk Drive interface */

	mov	byte ptr [hasedd],0
	mov	dl,[drvnum]
	mov	ah,0x41
	mov	bx,0x55AA
	int	INT_DISK		# call BIOS check function
	jc	1f
	cmp	bx,0xAA55		# is function supported?
	jne	1f
	test	cx,0x0001
	jz	1f
	inc	byte ptr [hasedd]	# set flag
	clc
	jmp	9f

/*
 * If we cant use the Enhanced Disk Drive interface, we have to determine
 * the disk geometry.
 */

1:	push	es
	xor	di,di			# guard against BIOS bugs
	mov	es,di
	mov	dl,[drvnum]
	mov	ah,0x08
	int	INT_DISK		# get the geometry from the BIOS
	pop	es
	jc	2f			# check if we got a valid result
	jcxz	2f

	movzx	ax,dh
	inc	ax			# save number of heads
	mov	[heads],ax

	movzx	ax,cl
	shl	ax,2
	mov	al,ch			# save number of cylinders
	inc	ax
	mov	[cylinders],ax

	and	cx,0x003F
	mov	[sectors],cx		# save number of sectors
	clc
	jmp	9f

/*
 * We couldnt get the disk geometry, so it might be a floppy disk drive.
 * For those we have to determine the geometry the hard way by probing.
 */

2:	test	dl,0x80			# check if we have a floppy drive
	jnz	8f			# at all

	mov	si,offset floppysects
3:	xor	ax,ax
	int	INT_DISK		# reset disk drive
	movzx	cx,byte ptr [si]	# get next number of sectors
	or	cx,cx
	jz	8f			# check if at end of list

	mov	bx,[partbuf]
	movzx	dx,byte ptr [drvnum]	# call BIOS to read last sector of
	mov	ax,0x0201		# first track
	int	INT_DISK
	jnc	4f
	inc	si			# continue with the next sector count
	jmp	3b

4:	xor	ch,ch
	mov	[sectors],cx		# save number of sectors
	mov	word ptr [cylinders],80
	mov	word ptr [heads],2
	clc
	jmp	9f

8:	stc				# return with error
9:	ret



/*
 *====================================================================
 *
 * Load a sector from disk
 * Input:  EAX  -  Sector number
 *         BX   -  Offset to buffer
 * Output: Carry flag set if error
 * Registers changed: EAX, CX, EDX, SI
 */
readsect:

/*
 * Check if we can read the sector using the Enhanced Disk Drive
 * interface.
 */

	test	byte ptr [hasedd],0xFF	# check the EDD flag
	jz	1f

	sub	sp,16
	mov	si,sp
	mov	word ptr [si + 0],0x0010
	mov	word ptr [si + 2],0x0001
	mov	word ptr [si + 4],bx	# setup the disk address packet
	mov	word ptr [si + 6],ds
	mov	dword ptr [si + 8],eax
	mov	dword ptr [si + 12],0
	mov	dl,[drvnum]
	mov	ah,0x42			# call BIOS to do the transfer
	int	INT_DISK
	rcl	al,1			# save carry flag
	add	sp,16			# restore stack
	rcr	al,1			# restore carry flag
	jmp	9f

/*
 * We have to read the sector using the old CHS interface
 */

1:	push	ebx
 	xor	edx,edx
	movzx32	ebx,word ptr [sectors]
	div	ebx			# compute sector number
	mov	cx,dx
	inc	cx
	and	cx,0x003F

	xor	edx,edx
	movzx32	ebx,word ptr [heads]
	div	ebx			# compute head number
	mov	dh,dl

	movzx32	ebx,word ptr [cylinders]
	cmp	eax,ebx			# check if cylinder count exceeded
	pop	ebx
	jae	8f
	mov	ch,al			# set cylinder number
	shr	ax,2
	and	al,0xC0
	or	cl,al

	mov	dl,[drvnum]
	mov	ax,0x0201
	int	INT_DISK		# call BIOS to get sector
	jnc	9f

8:	stc				# return with error
9:	ret



/*
 *====================================================================
 *
 * Scan through all partition tables, and determine the start sector
 * of the selected partition.
 * Input:  none
 * Output: SI  -  Offset to partition entry within partition table
 *         Carry flag set if error
 * Registers changed: EAX, BX, CX, EDX, SI
 */
scanparts:

	mov	word ptr [partcount],1
	xor	eax,eax			# start with MBR
6:	mov	bx,[partbuf]
	call	readsect		# read partition table
	jc	9f
	cmp	word ptr [bx + PART_SIG_OFS],PART_SIG
	jne	8f

	xor	eax,eax
	mov	si,PART_TABLE_OFS
	mov	cx,4			# scan through all partition table
1:	mov	dl,part_type[bx + si]	# entries
	or	dl,dl
	jz	3f			# check for a valid entry
	cmp	dl,PART_TYPE_EXT
	jne	2f			# if this is a logical partition entry,
	or	eax,eax			# save its start sector into EAX
	jnz	8f			# only one such entry is allowed
	mov	eax,part_first_abs_sect[bx + si]
	mov	dx,[partcount]
	cmp	dx,4			# if we are not scanning the MBR,
	ja	4f			# extended partitions dont count
	cmp	dx,[partnum]		# it is an error, if the selected
	je	8f			# partition is a extended partition
	jmp	3f			# count the extended partition

2:	mov	dx,[partcount]		# in case this is a normal partition,
	cmp	dx,[partnum]		# check if its the one we want
	jne	3f
	clc				# return its start sector number
	jmp	9f

3:	inc	word ptr [partcount]
4:	add	si,PART_SIZE		# scan through all partition table
	loop	1b			# entries
	or	eax,eax			# if we are at the end of the table,
	jz	8f			# and there is no further extended
	mov	dx,[partcount]		# partition, we didnt find the correct
	cmp	dx,5			# one
	jae	6b			# continue with reading the next
	mov	dx,5			# partition table. however, the first
	mov	[partcount],dx		# logical partition within the first
	jmp	6b			# extended partition table counts as
					# number 5
8:	stc
9:	ret




/*
 *====================================================================
 *
 * Read-only data segment
 */
	_rodata


/* Number of sectors for floppy probe */

floppysects:
	.byte	36,18,15,9,0




/*
 *====================================================================
 *
 * BSS segment
 */
	.bss

	.lcomm	hasedd,1		# TRUE if EDD can be used
	.lcomm	drvnum,1		# current drive number
	.lcomm	partofs,2		# offset to part entry in part table
	.lcomm	partnum,2		# requested partition number
	.lcomm	partcount,2		# partition counter
	.lcomm	sectors,2		# number of sectors per track
	.lcomm	cylinders,2		# number of cylinders
	.lcomm	heads,2			# number of heads
	.lcomm	partbuf,2		# offset to partition table buffer
	.lcomm	bootbuf,2		# offset to boot sector buffer




/*
 *====================================================================
 */
	.end

