;
; ATADRV.ASM - (experimental) Low-level ATA (IDE) driver
;
; Written by Gerard Barilleaux
;
; Ok, a couple of things.  First, this driver is ONLY FOR THE FIRST
; IDE CONTROLLER, IDE 1!!! The "master" drive on the controller is
; 0, and the "slave" is 1!! 80h will not work!!
;
; This is all 16-bit code written to be assembled under the A86 assembler.
; Note!! This is all PIO mode stuff, compliant (hopefully) with the original
; IDE standard.

	cli

; Save old int 76h vector

	xor ax,ax
	mov es,ax
	es:
	mov ax,[01d8h]
	mov old76ofs,ax
	es:
	mov ax,[01dah]
	mov old76seg,ax

; Take over INT 76h (hard disk interface)

	mov ax,offset IDE_ISR
	es:
	mov [01d8h],ax
	mov ax,cs
	es:
	mov [01dah],ax

	sti                 ; enable interrupts

	mov ax,cs
	mov es,ax

	xor dl,dl           ; drive 0, head 0
	xor cx,cx           ; cylinder 0
	mov dh,01           ; sector 1
	mov bx,0900h        ; wherever! (es is already good)
	mov al,1            ; 1 sector

	call IDE_READ_DISK
	
	mov byte [09CAh],4Fh     ; change "o" to "O"

	xor dl,dl
	xor cx,cx
	mov dh,01
	mov bx,0900h
	mov ax,ds
	mov es,ax
	mov al,1
	
	call IDE_WRITE_DISK

	; Ok,now we restore int vector
	cli
	xor ax,ax
	mov es,ax
	mov ax,old76ofs
	es:
	mov [01d8],ax
	mov ax,old76seg
	es:
	mov [01da],ax
	sti

	int 20h

;
; IDE_RESET_DISK
;
;   dl = Drive (0=master,1=slave)
;
;   Note: Some disks will respond to the reset command if the other is
;   given it.
;
IDE_RESET_DISK:

	and dl,01           ; isolate bit 0
	mov al,dl           ; al = dl
	shl al,4            ; move drive selector into bit 4
	or al,0A0h          ; make bits 7 and 5 a 1
	mov dx,01F6h        ; dx = drive/head regsiter port address
	out dx,al           ; write to drive/head register
	mov al,10h          ; Command 1xh: Recalibrate
	inc dx              ; dx is now 01F7h, port address of command register
	mov byte lastcommand,10h ; Tell our ISR what we are doing
	out dx,al           ; Recalibrate!

	mov byte commanddone,0  ; init commanddone with 0

again:
	cmp commanddone,1
	je resetisdone
	jmp again

resetisdone:
	cli
	mov byte lastcommand,01h ; set this in case of unexpected command
	sti
	clc                 ; its all good
	ret                 ; outta here

noreset:
	cli
	mov byte lastcommand,01h ; set this in case of unexpected command
	sti
	stc                 ; error
	ret                 ; outta here
; end of IDE_RESET_DISK


;
; IDE_READ_DISK
;
;   dl = drive/head: bit 4 = drive, bits 3 to 0 = head number
;   dh = sector number
;   cx = cylinder number
;   ah = number of sectors to read (0 = 256,0 thru 255 = 0 thru 255)
;   es:bx = buffer address
;
IDE_READ_DISK:

	; First lets save all this information
	mov xfer_count,al 
	and dl,0BFh             ; Make sure bit 6 is 0
	or dl,0A0h              ; Make sure bits 7 and 5 are 1
	mov xfer_drivehead,dl
	mov xfer_sector,dh
	mov xfer_cylinder,cx
	mov xfer_es,es
	mov xfer_bx,bx

	; Load the drive registers through the ports
	mov dx,01F2h            ; dx = sector count register
	; al is already set at count
	out dx,al               ; only 1 sector to read
	inc dx                  ; sector number
	mov al,xfer_sector
	out dx,al
	inc dx                  ; cyl low
	mov al,cl
	out dx,al
	inc dx                  ; cyl high
	mov al,ch
	out dx,al
	inc dx                  ; drive/head
	mov al,xfer_drivehead
	out dx,al

	; Ok setup before we issue the command
	mov byte lastcommand,21h     ; Tell our ISR what we are doing
	; Issue the command!
	inc dx
	mov al,21h
	out dx,al               ; issued!!

	mov byte commanddone,0  ; init commanddone with 0
	mov al,xfer_count    
	
rd_again:               ; wait loop!
	; waste time
	jcxz rd_next1
rd_next1:
	jcxz rd_next2
rd_next2:
	jcxz rd_next3
rd_next3:
	jcxz rd_next4
rd_next4:

	cmp commanddone,1
	je readisdone
	jmp rd_again    

readisdone:
	cli ; (should already be cleared but oh well)
	mov byte lastcommand,01h ; set this in case of unexpected command
	sti
	cmp byte commandok,1
	je read_ok
	stc                 ; error!
	ret
read_ok:
	clc                 ; its all good
	ret                 ; outta here

noread:
	cli
	mov byte lastcommand,01h ; set this in case of unexpected command
	sti
	stc                 ; error
	ret                 ; outta here
; end of IDE_READ_DISK

;
; IDE_WRITE_DISK
;
;   dl = drive/head: bit 4 = drive, bits 3 to 0 = head number
;   dh = sector number
;   cx = cylinder number
; NOT IMPLEMENTED: ah = number of sectors to write (0 = 256,0 thru 255 = 0 thru 255)
;   es:bx = buffer address
;
IDE_WRITE_DISK:
	
	; First lets save all this information
	;mov xfer_count,al 
	and dl,0BFh             ; Make sure bit 6 is 0
	or dl,0A0h              ; Make sure bits 7 and 5 are 1
	mov xfer_drivehead,dl
	mov xfer_sector,dh
	mov xfer_cylinder,cx
	mov xfer_es,es
	mov xfer_bx,bx
	
	; Load the drive registers through the ports
	mov dx,01F2h            ; dx = sector count register
	mov al,01               ; 1 sector (for now!)
	out dx,al               ; only 1 sector to read
	inc dx                  ; sector number
	mov al,xfer_sector
	out dx,al
	inc dx                  ; cyl low
	mov al,cl
	out dx,al
	inc dx                  ; cyl high
	mov al,ch
	out dx,al
	inc dx                  ; drive/head
	mov al,xfer_drivehead
	out dx,al

	; Ok setup before we issue the command
	mov byte lastcommand,31h     ; Tell our ISR what we are doing
	; Issue the command!
	inc dx
	mov al,31h
	out dx,al               ; issued!!
   
	; Ok, now we poll the controller until it is ready to receive data
wr_check_ready:    
	in al,dx                ; read in status port
	shr al,3                ; shift right 3 times (bit 3 is now bit 0)
	and al,01h              ; get rid of all the bits execept bit 0
	cmp al,1
	jne wr_check_ready

	; Ok, the controller wants the data, lets send it through
	cld                         ; make sure we go forward
	mov cx,100h                 ; 100h words to transfer (512 bytes)
	mov dx,01F0h                ; dx = data register port
	mov si,xfer_bx              ; si points to source buffer offset
	mov ax,ds                   ; save ds
	mov ds,xfer_es              ; ds points to source buffer segment address
	rep outsw                   ; write data!!
	mov ds,ax                   ; restore ds

	mov byte commanddone,0      ; init commanddone with 0
   
wr_again:               ; wait loop!
	; waste time
	jcxz wr_next1
wr_next1:
	jcxz wr_next2
wr_next2:
	jcxz wr_next3
wr_next3:
	jcxz wr_next4
wr_next4:

	cmp commanddone,1
	je writeisdone
	jmp rd_again    

writeisdone:
	cli ; (should already be cleared but oh well)
	mov byte lastcommand,01h ; set this in case of unexpected command
	sti
	cmp byte commandok,1
	je write_ok
	stc                 ; error!
write_ok:
	clc                 ; its all good
	ret                 ; outta here

nowrite:
	cli
	mov byte lastcommand,01h ; set this in case of unexpected command
	sti
	stc                 ; error
	ret                 ; outta here
; end of IDE_WRITE_DISK

;
; IDE_ISR
;
;   Attached to INT 76h. This is an interrupt service routine. Be careful!
;
;   This is the workhorse.  Its services all the disk interrupts.
;   Its decides what to do by reading lastcommand to see what needs
;   to be done.
;
;   Note!! Ok, currently, we save all the registers, except the stack. So,
;   DON'T USE THE STACK!! We don't know where it is or what state its in.
;
IDE_ISR:

	; First we gotta make sure we don't mess anything up
	cs: mov user_ax,ax      ; save ax (seg override until ds = cs)
	cs: mov user_ds,ds      ; save ds (ditto)
	mov ax,cs               ; ax = cs
	mov ds,ax               ; ds = ax = cs
	mov user_bx,bx          ; save bx
	mov user_cx,cx          ; save cx
	mov user_dx,dx          ; ...
	mov user_di,di
	mov user_si,si
	mov user_bp,bp
	mov user_es,es

	;mov ax,cs               ; this will make...
	;mov es,ax               ; ...es = cs

	; Ok, now we need to send an EOI to the PICs
	mov al,20h
	out 0A0h,al         ; nonspecific EOI to PIC 2
	jcxz next           ; "breathing room"
next:
	jcxz next2
next2:
	out 20h,al          ; nonspecific EOI to PIC 1

	; Lets check with the IDE interface and see how its doing
	mov dx,01F7h        ; dx = status register port
	in al,dx            ; read in status register (clears interrupt)

	test al,01          ; bit 0 is a 1?
	jnz diskerror       ; there was an error

	; Ok, we're good to go. Lets figure out what this interrupt is all about.
	mov al,lastcommand

	cmp al,01h
	je handle_unexpected_int
	cmp al,10h
	je handle_disk_reset
	cmp al,21h
	je handle_disk_read
	cmp al,31h
	je handle_disk_write
	jmp isr_all_done        ; unkown! ignore it I guess

handle_disk_reset:
	mov commanddone,01  ; Thats all we need to do.
	mov byte commandok,1     ; command was completed
	jmp isr_all_done

handle_disk_read:
	mov di,xfer_bx      ; setup buffer
	mov es,xfer_es

	cld                         ; make sure we go forward
	mov cx,100h                 ; 100h words to transfer (512 bytes)
	mov dx,01F0h                ; dx = data register port
	rep insw                    ; read data!
	dec xfer_count          
	cmp xfer_count,0
	jne hdle_dr_not_done
	
	mov byte commandok,1        ; command was completed
	mov commanddone,01          ; Thats all we need to do.
	jmp isr_all_done            ; done!
	
hdle_dr_not_done:    
	add xfer_bx,200h        ; increment buffer position
	jmp isr_all_done

handle_disk_write:
	mov byte commandok,1        ; command was completed
	mov commanddone,01          ; thats it
	jmp isr_all_done

handle_unexpected_int:
	jmp isr_all_done    ; will have to do for now

diskerror:
	sti
	mov al,21h
	mov  ah,0Eh
	mov  bx,7
	int  10h                 ; Do the int        
	cli
	mov byte commandok,0     ; not completed
	jmp isr_all_done

isr_all_done:
	
	mov es,user_es
	mov bp,user_bp
	mov si,user_si
	mov di,user_di
	mov dx,user_dx
	mov cx,user_cx
	mov bx,user_bx
	mov ds,user_ds
	cs: mov ax,user_ax

	iret                ; That was fuckin' easy!
; end of IDE_ISR

; Here's were the ISR keeps its stuff:
	user_ax dw ?
	user_bx dw ?
	user_cx dw ?
	user_dx dw ?
	user_di dw ?
	user_si dw ?
	user_bp dw ?
	user_ds dw ?
	user_es dw ?
; end of ISR's stuff

; Transfer information
	xfer_count      db ?
	xfer_drivehead  db ?
	xfer_sector     db ?
	xfer_cylinder   dw ?
	xfer_es         dw ?
	xfer_bx         dw ?

lastcommand db ?                ; the last command given to the disk
commanddone db ?                ; command done flag
commandok db ?                  ; command completed properly flag

old76ofs dw ?
old76seg dw ?

