1 ; -*- fundamental -*- (asm-mode sucks)
2 ; ****************************************************************************
6 ; A program to emulate an INT 13h disk BIOS from a "disk" in extended
9 ; Copyright 2001-2009 H. Peter Anvin - All Rights Reserved
10 ; Copyright 2009 Intel Corporation; author: H. Peter Anvin
12 ; This program is free software; you can redistribute it and/or modify
13 ; it under the terms of the GNU General Public License as published by
14 ; the Free Software Foundation, Inc., 53 Temple Place Ste 330,
15 ; Boston MA 02111-1307, USA; either version 2 of the License, or
16 ; (at your option) any later version; incorporated herein by reference.
18 ; ****************************************************************************
20 %include "../version.gen"
22 ; %define DEBUG_TRACERS ; Uncomment to get debugging tracers
36 %endif ; DEBUG_TRACERS
38 ; Flags we test our configuration against
39 %define CONFIG_READONLY 0x01
40 %define CONFIG_RAW 0x02
41 %define CONFIG_SAFEINT 0x04
42 %define CONFIG_BIGRAW 0x08 ; MUST be 8!
46 %define SECTORSIZE_LG2 9 ; log2(sector size)
47 %define SECTORSIZE (1 << SECTORSIZE_LG2)
49 ; Parameter registers definition; this is the definition
51 %define P_DS word [bp+34]
52 %define P_ES word [bp+32]
53 %define P_EAX dword [bp+28]
54 %define P_HAX word [bp+30]
55 %define P_AX word [bp+28]
56 %define P_AL byte [bp+28]
57 %define P_AH byte [bp+29]
58 %define P_ECX dword [bp+24]
59 %define P_HCX word [bp+26]
60 %define P_CX word [bp+24]
61 %define P_CL byte [bp+24]
62 %define P_CH byte [bp+25]
63 %define P_EDX dword [bp+20]
64 %define P_HDX word [bp+22]
65 %define P_DX word [bp+20]
66 %define P_DL byte [bp+20]
67 %define P_DH byte [bp+21]
68 %define P_EBX dword [bp+16]
69 %define P_HBX word [bp+18]
70 %define P_HBXL byte [bp+18]
71 %define P_BX word [bp+16]
72 %define P_BL byte [bp+16]
73 %define P_BH byte [bp+17]
74 %define P_EBP dword [bp+8]
75 %define P_BP word [bp+8]
76 %define P_ESI dword [bp+4]
77 %define P_SI word [bp+4]
78 %define P_EDI dword [bp]
79 %define P_DI word [bp]
82 ; These pointers are used by the installer and
83 ; must be first in the binary
84 Pointers: dw Int13Start
90 IretPtr equ Int13Start.iret
92 cmp word [cs:Recursive],0
103 ; See if DL points to our class of device (FD, HD)
108 js .nomatch ; If SF=0, we have a class match here
109 ; 0x00 the sign bit for FD
110 ; 0x80 the sign bit for HD
111 jz our_drive ; If ZF=1, we have an exact match
113 jb .nomatch ; Drive < Our drive
114 dec dl ; Drive > Our drive, adjust drive #
117 inc word [cs:Recursive]
119 call far [cs:OldInt13]
121 dec word [cs:Recursive]
124 cmp byte [cs:SavedAX+1],08h ; Get drive params function?
125 je .norestoredl ; DL = number of drives
126 cmp byte [cs:SavedAX+1],15h ; Get disk type function?
127 je .norestoredl ; CX:DX = size of device
134 mov ax,[bp+2] ; Flags
136 mov [bx+4],al ; Arithmetic flags
145 jmp far [cs:OldInt13]
148 ; Set up standard entry frame
155 mov bp,sp ; Point BP to the entry stack frame
157 ; Note: AX == P_AX here
158 cmp ah,Int13FuncsCnt-1
160 xor al,al ; AL = 0 is standard entry condition
162 shr di,7 ; Convert AH to an offset in DI
165 Done: ; Standard routine for return
172 mov [es:bx],ah ; Save status
176 ; This sets the low byte (the arithmetic flags) of the
177 ; FLAGS on stack to either 00h (no flags) or 01h (CF)
178 ; depending on if AH was zero or not.
179 setnz [bx+4] ; Set CF iff error
187 ; Reset affects multiple drives, so we need to pass it on
189 xor ax,ax ; Bottom of memory
191 test dl,dl ; Always pass it on if we are
193 js .hard_disk ; Bit 7 set
194 ; Some BIOSes get very unhappy if we pass a reset floppy
195 ; command to them and don't actually have any floppies.
196 ; This is a bug, but we have to deal with it nontheless.
197 ; Therefore, if we are the *ONLY* floppy drive, and the
198 ; user didn't request HD reset, then just drop the command.
199 ; BIOS equipment byte, top two bits + 1 == total # of floppies
200 test byte [es:0x410],0C0h
202 jmp .pass_on ; ... otherwise pass it to the BIOS
204 ; ... same thing for hard disks, sigh ...
205 cmp byte [es:0x475],1 ; BIOS variable for number of hard
210 pop ax ; Drop return address
211 popad ; Restore all registers
214 lss esp,[cs:Stack] ; Restore the stack
215 and dl,80h ; Clear all but the type bit
220 pop dx ; Drop return address
223 mov ah,01h ; Unsupported function
227 test byte [DriveNo],80h
228 mov bl,02h ; Type 02h = floppy with changeline
233 mov dx,[DiskSize] ; Return the disk size in sectors
237 mov P_AH,bl ; 02h floppy, 03h hard disk
238 pop ax ; Drop return address
239 xor ax,ax ; Success...
240 jmp DoneWeird ; But don't stick it into P_AX
246 mov ah,[bx] ; Copy last status
258 movzx ax,P_AL ; AH = 0, AL = transfer count
265 test byte [ConfigFlags],CONFIG_READONLY
268 xchg esi,edi ; Opposite direction of a Read!
270 .readonly: mov ah,03h ; Write protected medium
273 ; Verify integrity; just bounds-check
276 call setup_regs ; Returns error if appropriate
277 ; And fall through to success
279 CheckIfReady: ; These are always-successful noop functions
287 xor ax,ax ; Always successful
292 mov dl,[DriveCnt] ; Cached data
294 test byte [DriveNo],80h
302 dec ax ; We report the highest #, not the count
312 ; Is this MEMDISK installation check?
323 ; MEMDISK installation check...
329 mov P_DI,MemDisk_Info
335 ; EDD functions -- only if enabled
344 mov P_BX,0AA55h ; EDD signature
345 mov P_AX,03000h ; EDD 3.0
346 mov P_CX,0003h ; Bit 0 - Fixed disk access subset
347 ; Bit 1 - Locking and ejecting subset
348 pop ax ; Drop return address
350 jmp DoneWeird ; Success, but AH != 0, sigh...
366 xchg esi,edi ; Opposite direction of a Read!
373 call edd_setup_regs ; Just bounds checking
385 lodsw ; Length of our DPT
387 cmp cx,26 ; Minimum size
409 ; Set up registers as for a "Read", and compares against disk
411 ; WARNING: This fails immediately, even if we can transfer some
412 ; sectors. This isn't really the correct behaviour.
415 ; Convert a CHS address in P_CX/P_DH into an LBA in eax
417 ; CL[0:5] = sector (1-based) CL[7:6] = cyl[9:8]
420 movzx ebx,cl ; Sector number
422 dec ebx ; Sector number is 1-based
425 movzx edi,P_DH ; Head number
426 movzx eax,word [Heads]
430 xchg cl,ch ; Now (E)CX <- cylinder number
431 mul ecx ; eax <- Heads*cyl# (edx <- 0)
435 ; Now eax = LBA, edx = 0
438 ; setup_regs continues...
440 ; Note: edi[31:16] and ecx[31:16] = 0 already
441 mov di,P_BX ; Get linear address of target buffer
444 add edi,ecx ; EDI = address to fetch to
445 movzx ecx,P_AL ; Sector count
447 add eax,ecx ; LBA of final sector + 1
448 shl esi,SECTORSIZE_LG2 ; LBA -> byte offset
449 add esi,[DiskBuf] ; Get address in high memory
450 cmp eax,[DiskSize] ; Check the high mark against limit
452 shl ecx,SECTORSIZE_LG2-2 ; Convert count to dwords
455 .overrun: pop ax ; Drop setup_regs return address
456 mov ax,0200h ; Missing address mark
459 ; Set up registers as for an EDD Read, and compares against disk size.
463 mov si,P_SI ; DS:SI -> DAPA
470 cmp dword [es:si+4],-1
473 movzx ebx,word [es:si+4] ; Offset
474 movzx edi,word [es:si+6] ; Segment
480 cmp dx,24 ; Must be large enough to hold
484 cmp dword [es:si+20],0 ; > 4 GB addresses not supported
485 mov ax,0900h ; "Data boundary error" - bogus, but
486 ; no really better code available
492 cmp dword [es:si+12],0 ; LBA too large?
495 movzx ecx, word [es:si+2] ; Sectors to transfer
496 mov esi,[es:si+8] ; Starting sector
503 shl ecx,SECTORSIZE_LG2-2 ; Convert to dwords
504 shl esi,SECTORSIZE_LG2 ; Convert to an offset
511 mov ax,0100h ; Invalid command
513 pop ax ; Drop setup_regs return address
517 mov ax,0200h ; "Address mark not found" =
518 ; LBA beyond end of disk
520 and word [es:si+2],0 ; No sectors transferred
526 mov ax,0B200h ; Volume Not Removable
533 ; INT 15h intercept routines
536 cmp edx,534D4150h ; "SMAP"
538 cmp ecx,20 ; Need 20 bytes
548 add bx,12 ; Advance to next
549 mov eax,[bx-4] ; Type
550 and eax,eax ; Null type?
551 jz .renew ; If so advance to next
553 mov eax,[bx-12] ; Start addr (low)
554 mov edx,[bx-8] ; Start addr (high)
557 mov eax,[bx] ; End addr (low)
558 mov edx,[bx+4] ; End addr (high)
559 sub eax,[bx-12] ; Derive the length
561 mov [es:di+8],eax ; Length (low)
562 mov [es:di+12],edx ; Length (high)
563 cmp dword [bx+8],-1 ; Type of next = end?
565 xor ebx,ebx ; Done with table
568 mov edx,eax ; Some systems expect eax = edx = SMAP
569 mov ecx,20 ; Bytes loaded
572 mov byte [bp+6], 02h ; Clear CF
577 mov byte [bp+6], 03h ; Set CF
594 jmp far [cs:OldInt15]
596 int15_e801: ; Get mem size for > 64 MB config
601 jmp short int15_success
603 int15_e881: ; Get mem size for > 64 MB config
609 jmp short int15_success
611 int15_88: ; Get extended mem size
612 mov ax,[cs:MemInt1588]
613 jmp short int15_success
616 ; Routine to copy in/out of high memory
617 ; esi = linear source address
618 ; edi = linear target address
619 ; ecx = 32-bit word count
621 ; Assumes cs = ds = es
629 mov bx, real_int15_stub
631 test byte [ConfigFlags], CONFIG_RAW|CONFIG_SAFEINT
632 jz .anymode ; Always do the real INT 15h
634 smsw ax ; Unprivileged!
636 jnz .protmode ; Protmode -> do real INT 15h
639 ; Raw or Safeint mode, and we're in real mode...
641 test byte [ConfigFlags], CONFIG_SAFEINT
646 ; We're in real mode, do it outselves
661 ; Test to see if A20 is enabled or not
678 push dx ; <D> Save A20 status
681 mov ax,2401h ; Enable A20
687 ; DX = 16 for BIGRAW, 8 for RAW
688 ; 8 is selector for a 64K flat segment,
689 ; 16 is selector for a 4GB flat segment.
696 mov bx,16 ; Large flat segment
702 ; DX has the appropriate value to put in
703 ; the registers on return
710 pop dx ; <D> A20 status
716 mov ax,2400h ; Disable A20
723 ; We're in real mode with CONFIG_SAFEINT, invoke the
724 ; original INT 15h vector. We used to test for the
725 ; INT 15h vector being unchanged here, but that is
726 ; *us*; however, the test was wrong for years (always
727 ; negative) so instead of fixing the test do what we
728 ; tested and don't bother probing.
729 mov bx, fake_int15_stub
743 push ecx ; Transfer size this cycle
747 mov [Mover_src1+2], al
752 mov [Mover_dst1+2], al
756 shl cx,1 ; Convert to 16-bit words
757 call bx ; INT 15h stub
758 pop eax ; Transfer size this cycle
778 cli ; Some BIOSes enable interrupts on INT 15h
837 Int13Funcs dw Reset ; 00h - RESET
838 dw GetStatus ; 01h - GET STATUS
840 dw Write ; 03h - WRITE
841 dw Verify ; 04h - VERIFY
842 dw Invalid ; 05h - FORMAT TRACK
843 dw Invalid ; 06h - FORMAT TRACK AND SET BAD FLAGS
844 dw Invalid ; 07h - FORMAT DRIVE AT TRACK
845 dw GetParms ; 08h - GET PARAMETERS
846 dw InitWithParms ; 09h - INITIALIZE CONTROLLER WITH
850 dw Seek ; 0Ch - SEEK TO CYLINDER
851 dw Reset ; 0Dh - RESET HARD DISKS
854 dw CheckIfReady ; 10h - CHECK IF READY
855 dw Recalibrate ; 11h - RECALIBRATE
859 dw GetDriveType ; 15h - GET DRIVE TYPE
860 dw DetectChange ; 16h - DETECT DRIVE CHANGE
872 dw ReadMult ; 21h - READ MULTIPLE
873 dw WriteMult ; 22h - WRITE MULTIPLE
874 dw SetMode ; 23h - SET CONTROLLER FEATURES
875 dw SetMode ; 24h - SET MULTIPLE MODE
876 dw Invalid ; 25h - IDENTIFY DRIVE
904 dw EDDPresence ; 41h - EDD PRESENCE DETECT
905 dw EDDRead ; 42h - EDD READ
906 dw EDDWrite ; 43h - EDD WRITE
907 dw EDDVerify ; 44h - EDD VERIFY
908 dw EDDLock ; 45h - EDD LOCK/UNLOCK MEDIA
909 dw EDDEject ; 46h - EDD EJECT
910 dw EDDSeek ; 47h - EDD SEEK
911 dw EDDGetParms ; 48h - EDD GET PARAMETERS
912 dw EDDDetectChange ; 49h - EDD MEDIA CHANGE STATUS
916 Int13FuncsCnt equ (Int13FuncsEnd-Int13Funcs) >> 1
920 Shaker dw ShakerEnd-$-1 ; Descriptor table limit
921 dd 0 ; Pointer to self
924 Shaker_RMDS: dd 0x0000ffff ; 64K data segment
927 Shaker_DS: dd 0x0000ffff ; 4GB data segment
934 Mover dd 0, 0, 0, 0 ; Must be zero
935 dw 0ffffh ; 64 K segment size
936 Mover_src1: db 0, 0, 0 ; Low 24 bits of source addy
937 db 93h ; Access rights
938 db 00h ; Extended access rights
939 Mover_src2: db 0 ; High 8 bits of source addy
940 dw 0ffffh ; 64 K segment size
941 Mover_dst1: db 0, 0, 0 ; Low 24 bits of target addy
942 db 93h ; Access rights
943 db 00h ; Extended access rights
944 Mover_dst2: db 0 ; High 8 bits of source addy
945 Mover_dummy2: dd 0, 0, 0, 0 ; More space for the BIOS
948 MemDisk_Info equ $ ; Pointed to by installation check
949 MDI_Bytes dw MDI_Len ; Total bytes in MDI structure
950 MDI_Version db VERSION_MINOR, VERSION_MAJOR ; MEMDISK version
952 PatchArea equ $ ; This gets filled in by the installer
954 DiskBuf dd 0 ; Linear address of high memory disk
955 DiskSize dd 0 ; Size of disk in blocks
956 CommandLine dw 0, 0 ; Far pointer to saved command line
958 OldInt13 dd 0 ; INT 13h in chain
959 OldInt15 dd 0 ; INT 15h in chain
961 OldDosMem dw 0 ; Old position of DOS mem end
962 BootLoaderID db 0 ; Boot loader ID from header
965 DPT_ptr dw 0 ; If nonzero, pointer to DPT
966 ; Original DPT pointer follows
968 MDI_Len equ $-MemDisk_Info
970 ; ---- MDI structure ends here ---
971 MemInt1588 dw 0 ; 1MB-65MB memory amount (1K)
973 Cylinders dw 0 ; Cylinder count
974 Heads dw 0 ; Head count
975 Sectors dd 0 ; Sector count (zero-extended)
977 Mem1MB dd 0 ; 1MB-16MB memory amount (1K)
978 Mem16MB dd 0 ; 16MB-4G memory amount (64K)
980 DriveNo db 0 ; Our drive number
981 DriveType db 0 ; Our drive type (floppies)
982 DriveCnt db 0 ; Drive count (from the BIOS)
984 ConfigFlags db 0 ; Bit 0 - readonly
986 MyStack dw 0 ; Offset of stack
987 StatusPtr dw 0 ; Where to save status (zeroseg ptr)
989 DPT times 16 db 0 ; BIOS parameter table pointer (floppies)
990 OldInt1E dd 0 ; Previous INT 1E pointer (DPT)
996 ; Bit 0 - DMA boundaries handled transparently
997 ; Bit 3 - Device supports write verify
998 ; Bit 5 - Media is lockable
999 .cylinders dd 0 ; Filled in by installer
1000 .heads dd 0 ; Filled in by installer
1001 .sectors dd 0 ; Filled in by installer
1002 .totalsize dd 0, 0 ; Filled in by installer
1003 .bytespersec dw SECTORSIZE
1004 .eddtable dw -1, -1 ; Invalid DPTE pointer
1005 .dpikey dw 0BEDDh ; Device Path Info magic
1006 .dpilen db 2ch ; DPI len
1007 .res1 db 0 ; Reserved
1008 .res2 dw 0 ; Reserved
1009 .bustype equ 'MEM ' ; Host bus type (4 bytes, space padded)
1010 .inttype equ 'MEMORY ' ; Interface type (8 bytes, spc. padded)
1011 .intpath dd 0, 0 ; Interface path
1012 .devpath dd 0, 0, 0, 0 ; Device path
1013 .res3 db 0 ; Reserved
1014 .chksum db 0 ; DPI checksum
1020 Stack dd 0 ; Saved SS:ESP on invocation
1022 SavedAX dw 0 ; AX saved on invocation
1023 Recursive dw 0 ; Recursion counter
1025 alignb 4, db 0 ; We *MUST* end on a dword boundary
1027 E820Table equ $ ; The installer loads the E820 table here
1028 TotalSize equ $ ; End pointer