NEC_98=1 Page ,132 TITLE BOOT SECTOR 1 OF TRACK 0 - BOOT LOADER ;++ ; ;Module Name: ; ; fat32nec.asm ; ;Abstract: ; ; The ROM in NEC PC-9800 starts the boot process by performing a hardware ; initialization and a verification of all external devices. If all goes ; well, it will then load from the boot drive the sector from track 0, head 0, ; sector 1. This sector is placed at physical address 1FE00h. ; ; We need to load NTLDR at 2000:0. But the bootcode of FAT32 is 600h bytes. ; If we start at physical address 1FE0:0, the bootcode is broken by loaded NTLDR. ; At first, we move the bootcode to 0:7C00 from 1FE0:0 by oneself. ; The next, we jump to new Segment:Offset and start real bootcode procedure. ; ; The code is responsible for locating NTLDR and for placing the directory sector ; with this information. ; ; If WINBOOT.SYS is not found, an error message is displayed and the user is ; prompted to insert another disk. If there is a disk error during the ; process, a message is displayed and things are halted. ; ; At the beginning of the boot sector, there is a table which describes the ; structure of the media. This is equivalent to the BPB with some ; additional information describing the physical layout of the driver (heads, ; tracks, sectors) ; ;Author: ; ; ;Environment: ; ; Real mode ; FAT32 file system ; ;Revision History: ; ; 7/01/97 Tochizawa(NEC) support FAT32 ; ;-- .xlist include bpb.inc include bootsec.inc include dirent.inc ;include version.inc .list ; ========================================================================== ORIGIN EQU 7C00H ; Origin of bootstrap LOADER BIO_SEG EQU 2000H ; Destination segment of BIOS BIO_OFFSET EQU 0000H ; Offset of bios SECTOR_SIZE EQU 512 ; Sector size in bytes DIR_ENTRY_SIZE EQU SIZE DIR_ENTRY ; Size of directory entry in bytes ROM_DISKRD EQU 2 SIZEBIGBOOTINSEC EQU 3 ifdef NEC_98 BOOTCODE_SEG EQU 0000H ; Destination segment of BOOTCODE DAUA EQU 584h endif ; ========================================================================== SEGBIOS SEGMENT AT BIO_SEG ; Define the destination segment of the BIOS, including the ; initialization label ORG BIO_OFFSET WINLOAD LABEL BYTE SEGBIOS ENDS ; ========================================================================== ; Local (on stack) Data storage between temp stack and start of ; boot sector CurrBuffFatSecL EQU -12 CurrBuffFatSecH EQU -10 Int13Sectors EQU -8 DataSecL EQU -4 DataSecH EQU -2 ; ========================================================================== CODE SEGMENT ASSUME CS:CODE,DS:NOTHING,ES:NOTHING,SS:NOTHING ORG ORIGIN Public $START $START Label byte jmp short Main GotXint13: nop ; used to store xint13 flag GotXint13Offset = (offset GotXint13 - offset $START) ; ========================================================================== ; Start of BPB area of the boot record OsName DB "MSWIN" OsVersion DB "4.1" ; Windows version number BytesPerSector DW SECTOR_SIZE ; Size of a physical sector SecsPerClust DB 2 ; Sectors per allocation unit ReservedSecs DW 8 ; Number of reserved sectors NumFats DB 1 ; Number of fats NumDirEntries DW 1024 ; Number of direc entries TotalSectors DW 0 ; Number of sectors - number of hidden ; sectors (0 when 32 bit sector number) MediaByte DB 0F8H ; MediaByte byte NumFatSecs DW 0 ; Number of fat sectors (0 when 32 bit) SecPerTrack DW 17 ; Sectors per track NumHeads DW 4 ; Number of drive heads HiddenSecs DD 1 ; Number of hidden sectors BigTotalSecs DD 00200000h ; 32 bit version of number of sectors BigNumFatSecs DD 00001FE0h ; 32 bit version of number of FAT sectors ExtFlags DW 0 BPBReserved1 DW 0 RootStrtClus DD 0 FSInfoSec dw ((FSInfoSecSig - $START) / SECTOR_SIZE) BkUpBootSec dw MBR_BOOTFAILBACKUP BPBReserved2 DD 3 DUP (0) .errnz ($-BytesPerSector) NE SIZE BIGFATBPB BootDrv DB 80h CurrentHead DB 0h ; Current Head ExtBootSig DB 41 SerialNum DD 0 VolumeLabel DB 'NO NAME ' FatId DB 'FAT32 ' .errnz ($-$START) NE SIZE BIGFATBOOTSEC ; ========================================================================= ; ; First thing is to reset the stack to a better and more known ; place. The ROM may change, but we'd like to get the stack ; in the correct place. ; Main: ifdef NEC_98 push si ; Save Partition Information ; ; We moved bootcode to 0:ORIGIN from 1fe0:0. ; push di mov ax, cs mov ds, ax ; DS = CS mov si, 0 ; DS:SI = 1fe0:0 cld mov cx, 256 xor ax, ax mov es, ax mov di, ORIGIN ; ES:DI = 0:7c00 rep movsw pop di pop si ; Restore Partition Information ; ; We jump to 0:ORIGIN+RealBootStart ; mov ax, BOOTCODE_SEG push ax mov ax, ORIGIN add ax, RealBootStart push ax retf RealBootStart = (offset $ - offset $START) endif xor CX,CX mov SS,CX ;Work in stack just below this routine mov SP,ORIGIN+CurrBuffFatSecL mov es,cx mov ds,cx ; DS = ES = SS = 0 ASSUME DS:CODE,ES:CODE,SS:CODE mov BP,ORIGIN IFDEF NEC_98 .386 push si ; Save Partition Information mov al, ds:[DAUA] ; mov [BP].bgbsDriveNumber, al ; Save DriveNumber else ; ; Determine the number of sectors addressable via ; conventional int13. If we can't get drive params for some reason ; then something is very wrong -- we'll try to force the caller ; to use conventional int13 by maxing out the sector count. ; mov [bp].GotXint13Offset,cl ; no xint13 yet mov dl,[bp].bgbsDriveNumber ; int13 unit number mov ah,8 ; get drive params int 13h ; call BIOS jnc @f ; no error, procede mov cx,-1 ; strange case, fake registers to force mov dh,cl ; use of standard int13 (set all vals to max) @@: .386 movzx eax,dh ; eax = max head # (0-255) inc ax ; eax = heads (1-256) movzx edx,cl ; edx = sectors per track + cyl bits and dl,3fh ; edx = sectors per track (1-63) mul dx ; eax = sectors per cylinder, edx = 0 xchg cl,ch shr ch,6 ; cx = max cylinder # (0-1023) inc cx ; cx = cylinders (1-1024) movzx ecx,cx ; ecx = cylinders (1-1024) mul ecx ; eax = sectors visible via int13, edx = 0 mov [bp].Int13Sectors,eax ; save # sectors addressable via int13 ENDIF .8086 ; ; The MBR (or boot ROM) only reads one boot sector. Thus the first order ; of business is to read the rest of ourself in by reading the second ; boot sector of the 2-sector boot record. ; ; The second sector in the NT case is at sector 12. This preserves ; the bootsect.dos logic and eliminates a special case for fat32. ; ReadBoot: cmp [BP].bgbsBPB.oldBPB.BPB_SectorsPerFAT,0 ; FAT32 BPB? jne short NoSysMsg ; No, invalid, messed up cmp [BP].bgbsBPB.BGBPB_FS_Version,FAT32_Curr_FS_Version ja short NoSysMsg ; boot code too old for this volume .386 mov eax,dword ptr [BP].bgbsBPB.oldBPB.BPB_HiddenSectors add eax,12 ; read in the second boot sector .8086 mov BX,ORIGIN + (SECTOR_SIZE * 2) mov cx,1 call DoRead ; doesn't return if err jmp DirRead ; no error, continue boot in sector 2 DiskError: mov al,byte ptr [MSGOFF_IOERROR] ; ; Al is the offset - 256 of the message within the boot sector. ; So we first calculate the real segment-based offset to the message ; and stick it in si so lodsb will work later. ; DisplayError: .ERRNZ ORIGIN MOD 256 mov ah,(ORIGIN / 256) + 1 ifdef NEC_98 add ax, 2 mov di, 0 endif mov si,ax DisplayError1: ifdef NEC_98 mov ax, 0a000h ; set V-RAM mov es, ax xor ah, ah lodsb ; Get next character cmp AL,0Dh ; end of message? jz WaitForKey ; yes cmp AL,0FFh ; end of sub-message? je DisplayWait ; yes, switch to final message now stosw ; move to vram jmp short DisplayError1 else lodsb ; get next character test AL,AL ; end of message? jz WaitForKey ; yes cmp AL,0FFh ; end of sub-message? je DisplayWait ; yes, switch to final message now mov AH,14 ; write character & attribute mov BX,7 ; attribute (white char on black) int 10h ; print the character jmp short DisplayError1 endif DisplayWait: mov al,byte ptr [MSGOFF_COMMON] ifdef NEC_98 mov ah,(ORIGIN / 256) + 1 add ax, 2 mov si,ax mov di, 160 jmp short DisplayError1 else jmp short DisplayError endif NoSysMsg: mov al,byte ptr [MSGOFF_NOSYS] ; point to no system file message jmp short DisplayError WaitForKey: ifdef NEC_98 mov ax, 0h int 18h mov al, 0h out 0f0h, al else cbw ;warning assumes al is zero! int 16h ; get character from keyboard int 19h ; Continue in loop till good disk endif ; ========================================================================= ; ; Read disk sector(s). This routine cannot transfer more than 64K! ; ; Inputs: EAX == physical sector # ; CL == # sectors (CH == 0) ; ES:BX == transfer address ; ; Outputs: EAX next physical sector # ; CX == 0 ; ES:BX -> byte after last byte of read ; Does not return if error ; ; Reads sectors, switching to extended int13 if necessary and ; available. The note below is for the conventional int13 case. ; ; Notes: Reads sectors one at a time in case they straddle a ; track boundary. Performs full 32-bit division on the ; first decomposition (of logical sector into track+sector) ; but not on the second (of track into cylinder+head), ; since (A) we don't have room for it, and (B) the results ; of that division must yield a quotient < 1024 anyway, because ; the CHS-style INT 13h interface can't deal with cylinders ; larger than that. ; ; ========================================================================= DoRead: .386 pushad IFDEF NEC_98 shld edx,eax,16 ; EAX -> DX:AX mov CX,AX ; We've gotten Phy.Sector# in DX:CX mov bp,bx ; Buffer addr -> bp mov bx,SECTOR_SIZE ; Sector size -> bx push ds xor ax,ax mov ds,ax ; DS = 0 mov al,ds:[DAUA] ; set booting da/ua pop ds ; Restore DS and al,7fh ; strip high 1 bit ; SCSI HD mov ah,06h ; set read command int 1bh else ; ; Determine if the sector we're about to read is available via ; conventional int13. ; cmp eax,[bp].Int13Sectors ; determine if standard int13 is ok jb stdint13 ; ; Need extended int13. First set up parameter packet on stack. ; Then, if we don't know whether xint13 is available for the drive yet, ; find out. If not, error out since we know we can't read the sector ; we need. ; db 66h ; hand-coded 32-bit push of 8-bit immediate push 0 ; high 32 bits of sector # push eax ; low 32 bits of sector # push es push bx ; transfer address push dword ptr 10010h ; transfer 1 sector, packet size = 16 cmp byte ptr [bp].GotXint13Offset,0 ; have xint13? jnz xint13ok ; yes, do the read mov ah,41h mov bx,055aah mov dl,[bp].bgbsDriveNumber int 13h ; check availability jc xint13err ; error from int13 means no xint13 cmp bx,0aa55h ; absence of sig means no xint13 jne xint13err test cl,1 ; bit 0 off means no xint13 jz xint13err inc byte ptr [bp].GotXint13Offset ; have xint13, remember for next time xint13ok: mov ah,42h ; extended read mov dl,[bp].bgbsDriveNumber ; dl = int13 unit # mov si,sp ; ds:si -> param packet int 13h ; perform the read db 0b0h ; HACK: avoid stc by making next ; byte part of mov al,xx instruction xint13err: stc ; this instruction MUST follow previous byte! pop eax ; throw away param packet without pop eax ; clobbering carry flag pop eax pop eax jmp short did_read stdint13: ; ; Read via conventional int13 ; xor edx,edx ; edx:eax = absolute sector number movzx ecx,[bp].bgbsBPB.oldBPB.BPB_SectorsPerTrack ; ecx = sectors per track div ecx ; eax = track, edx = sector within track (0-62) inc dl ; dl = sector within track (1-63) mov cl,dl ; cl = sector within track mov edx,eax shr edx,16 ; dx:ax = track div [bp].bgbsBPB.oldBPB.BPB_Heads ; ax = cylinder (0-1023), dx = head (0-255) xchg dl,dh ; dh = head mov dl,[bp].bgbsDriveNumber ; dl = int13 unit # mov ch,al ; ch = bits 0-7 of cylinder shl ah,6 or cl,ah ; bits 6-7 of cl = bits 8-9 of cylinder mov ax,201h ; read 1 sector int 13h did_read: ENDIF popad jc DiskError add bx,SECTOR_SIZE ; advance transfer address inc eax ; next sector number dec cx ; loop instruction is out of range, jnz DoRead ; have to do it manually ret .8086 Public WinBoot ; System boot file (11 bytes) WinBoot DB "NTLDR " ; ; Message table. ; ; We put English messages here as a placeholder only, so that in case ; anyone uses bootf32.h without patching new messages in, things will ; still be correct (in English, but at least functional). ; .errnz ($-$START) GT 1ACH ORG ORIGIN + 01ACH ; shift message to coincide with that in FAT ; this will help driver to think the MBR ; is empty when dealing with FAT32 superfloppy include msgstub.inc ; ; Now build a table with the low byte of the offset to each message. ; Code that patches the boot sector messages updates this table. ; .errnz ($-$START) GT (SECTOR_SIZE-7) ORG ORIGIN + SECTOR_SIZE - 7 MSGOFF_NOSYS: db OFFSET (MSG_NOSYS - ORIGIN) - 256 MSGOFF_IOERROR: db OFFSET (MSG_IOERROR - ORIGIN) - 256 MSGOFF_COMMON: db OFFSET (MSG_COMMON - ORIGIN) - 256 ORG ORIGIN + (SECTOR_SIZE - 4) DD BOOTSECTRAILSIG ; Boot sector signature (4 bytes) .errnz ($-$START) NE SECTOR_SIZE SecndSecStart label byte ; ; The second boot sector contains nothing but data. This sector is re-written ; by MS-DOS with a fairly high frequency due to changes made to the fsinfo ; structure. We don't want the actual boot code to get accidentally corrupted. ; FSInfoSecSig label byte .errnz ($-SecndSecStart) NE 0 DD SECONDBOOTSECSIG db (SECTOR_SIZE - ($-FSInfoSecSig) - 4 - (SIZE BIGFATBOOTFSINFO)) DUP (0) .errnz ($-SecndSecStart) NE (OFFSETFSINFOFRMSECSTRT) fsinfo BIGFATBOOTFSINFO .errnz ($-FSInfoSecSig) NE OFFSETTRLSIG DD BOOTSECTRAILSIG ; Boot sector signature (4 bytes) .errnz ($-$START) NE (SECTOR_SIZE * 2) StrtThirdBootSector LABEL BYTE DirRead: .386 movzx eax,[BP].bgbsBPB.oldBPB.BPB_NumberOfFATs ; Determine sector dir starts on (NumFats) mov ecx,dword ptr [BP].bgbsBPB.BGBPB_BigSectorsPerFat mul ecx ; EAX = (NumFatSecs) add EAX,dword ptr [BP].bgbsBPB.oldBPB.BPB_HiddenSectors ; (HiddenSecs) movzx edx,[BP].bgbsBPB.oldBPB.BPB_ReservedSectors ;(ReservedSecs) add EAX,EDX ; ; EAX = NumFats * NumFatSecs + ReservedSecs + cSecHid ; (first physical sector of cluster area) ; mov dword ptr [BP].DataSecL,EAX mov dword ptr [BP].CurrBuffFatSecL,0FFFFFFFFh DirReRead: mov eax,dword ptr [BP].bgbsBPB.BGBPB_RootDirStrtClus cmp eax,2 jb NoSysMsg cmp eax,00FFFFFF8h jae NoSysMsg ; EAX is starting cluster of root directory DirCluster: push eax ; save starting cluster number sub eax,2 ; Convert to 0 based cluster # movzx EBX,[BP].bgbsBPB.oldBPB.BPB_SectorsPerCluster mov si,bx ; Sector count to SI for sector loop mul EBX ; compute logical sector in EAX add EAX,dword ptr [BP].DataSecL ; Add data start bias DirSector: mov BX,ORIGIN+(SIZEBIGBOOTINSEC*SECTOR_SIZE) mov DI,BX ; save address in DI for comparisons mov CX,1 call DoRead ; doesn't return if error ; relies on return cx=0 DirEntry: cmp byte ptr [di],ch ; empty, NUL directory entry? je short MissingFile ; yes, that's the end mov CL,11 push SI mov si,offset WinBoot repz cmpsb ; see if the same pop SI jz short DoLoad ; if so, continue booting add DI,CX ; Finish advance to end of name field add DI,DIR_ENTRY_SIZE-11 ; Next dir entry cmp DI,BX ; exhausted this root dir sector yet? jb DirEntry ; no, check next entry dec SI ; decrement # dir sectors jnz DirSector ; More dir sectors in this cluster pop eax ; recover current root dir cluster call GetNextFatEntry jc DirCluster ; Do next Root dir cluster MissingFile: add sp,4 ; Discard EAX saved on stack jmp NoSysMsg CurrentSegment dw BIO_SEG ; ; We now load NTLDR ; ; All we have to do is multiply the file's starting cluster ; (whose directory entry is at DS:DI-11) by sectors per cluster and ; add that to the disk's starting data sector. We read ntldr into ; 2000:0, and begin execution there. ; DoLoad: add sp,4 ; Discard DX:AX saved on stack above mov si,[DI-11].DIR_FIRSTHIGH mov di,[DI-11].DIR_FIRST ; SI:DI = NTLDR starting cluster mov ax,si shl eax,16 mov ax,di ; EAX = NTLDR starting cluster cmp eax,2 ; valid cluster #? jb NoSysMsg ; NO! cmp eax,00FFFFFF8h jae NoSysMsg ; NO! ReadAcluster: push eax ; save cluster number sub eax,2 ; Subtract first 2 reserved clusters movzx ecx,[BP].bgbsBPB.oldBPB.BPB_SectorsPerCluster ; ECX = Sectors per cluster (SecsPerClust) mul ecx ; EAX = logical sector # add eax,dword ptr [BP].DataSecL ; EAX = physical sector # mov bx,BIO_OFFSET push es mov es,CurrentSegment ; ES:BX = destination for read call DoRead ; read all sectors in cluster, doesn't return if error pop es pop eax ; recover current 0-based cluster# shr bx,4 ; updated offset -> paragraphs add CurrentSegment,bx ; update segment for next read call GetNextFatEntry ; get 2-based successor cluster in EAX jnc StartItUp ; if end of cluster chain reached jc ReadACluster ; keep sucking up clusters .8086 ; ; NTLDR requires the following input conditions: ; ; DL = boot drive # ; StartItUp: mov DL,[BP].bgbsDriveNumber ifdef NEC_98 lea si,[BP].bgbsBPB pop bp ; Restore Partition Information endif jmp FAR PTR WINLOAD ; CRANK UP THE WINDOWS NT BOOT LOADER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; GetNextFatEntry ; ; Given the last cluster found, this will return the next cluster ; of a chain of clusters. If the last cluster is (ffff)(f)ff8 - (ffff)(f)fff, ; then the final cluster has been loaded. ; ; INPUTS: ; EAX = CurrentCluster (0 based cluster #) ; ; OUTPUTS: ; EAX = Next cluster (2 based cluster #) ; Carry CLEAR if all done, SET if not ; ; USES: ; EAX,EBX,ECX,EDX,ESI,DI es ; GetNextFatEntry PROC NEAR ; NOTE For following... FAT32 cluster numbers are 28 bits not 32, ; so we know the following multiply (shl DX:AX by 2) will never ; overflow into carry. .386 shl eax,2 call GetFatSector mov EAX,dword ptr ES:[DI+BX] and EAX,0FFFFFFFh ; Mask to valid FAT32 cluster # bits cmp EAX,00FFFFFF8h ; carry CLEAR if all done, SET if not .8086 ret GetNextFatEntry ENDP ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; GetFatSector ; ; Read the corresponding FAT sector into the second boot sector ; ; INPUTS: ; EAX == offset (from FAT entry 0) of entry to find ; ; OUTPUTS: ; ES:DI+BX -> corresponding FAT entry in the FAT sector ; ; USES: ; BX,EAX,ECX,EDX,DI ; GetFatSector PROC NEAR .386 mov DI,ORIGIN + SECTOR_SIZE movzx ECX,[BP].bgbsBPB.oldBPB.BPB_BytesPerSector xor edx,edx div ECX ; EAX = Sector number, (E)DX = Offset cmp EAX,dword ptr [BP].CurrBuffFatSecL ; The same fat sector? je short SetRet ; Don't need to read it again mov dword ptr [BP].CurrBuffFatSecL,EAX add EAX,dword ptr [BP].bgbsBPB.oldBPB.BPB_HiddenSectors movzx ecx,[BP].bgbsBPB.oldBPB.BPB_ReservedSectors add eax,ecx ; Point at 1st (0th) FAT movzx ebx,[BP].bgbsBPB.BGBPB_ExtFlags and bx,BGBPB_F_ACTIVEFATMSK jz short GotFatSec cmp bl,[BP].bgbsBPB.oldBPB.BPB_NumberOfFATs jae NoSysMsg push dx ; Save offset of cluster in the FAT sec mov ecx,eax ; Save FAT sector # in 0th FAT mov eax,dword ptr [BP].bgbsBPB.BGBPB_BigSectorsPerFat mul ebx ; EAX = Sector offset to active FAT ; from 0th FAT add eax,ecx pop dx GotFatSec: push dx ; Save offset of cluster in the FAT sec mov BX,DI mov CX,1 call DoRead ; do the disk read, doesn't return if error pop dx SetRet: mov BX,DX ; set BX to the offset of the cluster .8086 ret GetFatSector ENDP db ((SECTOR_SIZE - ($-StrtThirdBootSector)) - 4) DUP (0) .errnz ($-StrtThirdBootSector) NE OFFSETTRLSIG DD BOOTSECTRAILSIG ; Boot sector signature (4 bytes) .errnz ($-$START) NE (SECTOR_SIZE * 3) .errnz SIZEBIGBOOTINSEC NE 3 $BigEnd label byte CODE ENDS END