summaryrefslogtreecommitdiffstats
path: root/contrib/syslinux-4.02/core/diskstart.inc
blob: c0ba52a0dc3316c15927665ffc0a8f5884330cee (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
; -----------------------------------------------------------------------
;
;   Copyright 1994-2009 H. Peter Anvin - All Rights Reserved
;   Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin
;
;   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.
;
; -----------------------------------------------------------------------

;
; diskstart.inc
;
; Common early-bootstrap code for harddisk-based Syslinux derivatives.
;

		section .init
;
; Some of the things that have to be saved very early are saved
; "close" to the initial stack pointer offset, in order to
; reduce the code size...
;

StackBuf	equ STACK_TOP-44-92	; Start the stack here (grow down - 4K)
PartInfo	equ StackBuf
.mbr		equ PartInfo
.gptlen		equ PartInfo+16
.gpt		equ PartInfo+20
FloppyTable	equ PartInfo+76
; Total size of PartInfo + FloppyTable == 76+16 = 92 bytes
Hidden		equ StackBuf-20		; Partition offset
OrigFDCTabPtr	equ StackBuf-12		; The 2nd high dword on the stack
OrigESDI	equ StackBuf-8		; The high dword on the stack
DriveNumber	equ StackBuf-4		; Drive number
StackHome	equ Hidden		; The start of the canonical stack

;
; Primary entry point.  Tempting as though it may be, we can't put the
; initial "cli" here; the jmp opcode in the first byte is part of the
; "magic number" (using the term very loosely) for the DOS superblock.
;
bootsec		equ $
_start:		jmp short start		; 2 bytes
		nop			; 1 byte
;
; "Superblock" follows -- it's in the boot sector, so it's already
; loaded and ready for us
;
bsOemName	db MY_NAME		; The SYS command sets this, so...
		zb 8-($-bsOemName)

;
; These are the fields we actually care about.  We end up expanding them
; all to dword size early in the code, so generate labels for both
; the expanded and unexpanded versions.
;
%macro		superb 1
bx %+ %1	equ SuperInfo+($-superblock)*8+4
bs %+ %1	equ $
		zb 1
%endmacro
%macro		superw 1
bx %+ %1	equ SuperInfo+($-superblock)*8
bs %+ %1	equ $
		zw 1
%endmacro
%macro		superd 1
bx %+ %1	equ $			; no expansion for dwords
bs %+ %1	equ $
		zd 1
%endmacro
superblock	equ $
		superw BytesPerSec
		superb SecPerClust
		superw ResSectors
		superb FATs
		superw RootDirEnts
		superw Sectors
		superb Media
		superw FATsecs
		superw SecPerTrack
		superw Heads
superinfo_size	equ ($-superblock)-1	; How much to expand
		superd Hidden
		superd HugeSectors
		;
		; This is as far as FAT12/16 and FAT32 are consistent
		;
		; FAT12/16 need 26 more bytes,
		; FAT32 need 54 more bytes
		;
superblock_len_fat16	equ $-superblock+26
superblock_len_fat32	equ $-superblock+54
		zb 54			; Maximum needed size
superblock_max	equ $-superblock

		global SecPerClust
SecPerClust	equ bxSecPerClust

;
; Note we don't check the constraints above now; we did that at install
; time (we hope!)
;
start:
		cli			; No interrupts yet, please
		cld			; Copy upwards
;
; Set up the stack
;
		xor cx,cx
		mov ss,cx
		mov sp,StackBuf-2	; Just below BSS (-2 for alignment)
		push dx			; Save drive number (in DL)
		push es			; Save initial ES:DI -> $PnP pointer
		push di
		mov es,cx

;
; DS:SI may contain a partition table entry and possibly a GPT entry.
; Preserve it for us.  This saves 56 bytes of the GPT entry, which is
; currently the maximum we care about.  Total is 76 bytes.
;
		mov cl,(16+4+56)/2	; Save partition info
		mov di,PartInfo
		rep movsw		; This puts CX back to zero

		mov ds,cx		; Now we can initialize DS...

;
; Now sautee the BIOS floppy info block to that it will support decent-
; size transfers; the floppy block is 11 bytes and is stored in the
; INT 1Eh vector (brilliant waste of resources, eh?)
;
; Of course, if BIOSes had been properly programmed, we wouldn't have
; had to waste precious space with this code.
;
		mov bx,fdctab
		lfs si,[bx]		; FS:SI -> original fdctab
		push fs			; Save on stack in case we need to bail
		push si

		; Save the old fdctab even if hard disk so the stack layout
		; is the same.  The instructions above do not change the flags
		and dl,dl		; If floppy disk (00-7F), assume no
					; partition table
		js harddisk

floppy:
		xor ax,ax
		mov cl,6		; 12 bytes (CX == 0)
		; es:di -> FloppyTable already
		; This should be safe to do now, interrupts are off...
		mov [bx],di		; FloppyTable
		mov [bx+2],ax		; Segment 0
		fs rep movsw		; Faster to move words
		mov cl,[bsSecPerTrack]  ; Patch the sector count
		mov [di-76+8],cl

		push ax			; Partition offset == 0
		push ax
		push ax
		push ax

		int 13h			; Some BIOSes need this
		jmp short not_harddisk
;
; The drive number and possibly partition information was passed to us
; by the BIOS or previous boot loader (MBR).  Current "best practice" is to
; trust that rather than what the superblock contains.
;
; Note: di points to beyond the end of PartInfo
;
harddisk:
		test byte [di-76],7Fh	; Sanity check: "active flag" should
		jnz .no_partition	; be 00 or 80
		cmp [di-76+4],cl	; Sanity check: partition type != 0
		je .no_partition
		cmp eax,'!GPT'		; !GPT signature?
		jne .mbr
		cmp byte [di-76+4],0EDh	; Synthetic GPT partition entry?
		jne .mbr
.gpt:					; GPT-style partition info
		push dword [di-76+20+36]
		push dword [di-76+20+32]
		jmp .gotoffs
.mbr:					; MBR-style partition info
		push cx			; Upper half partition offset == 0
		push cx
		push dword [di-76+8]	; Partition offset (dword)
		jmp .gotoffs
.no_partition:
;
; No partition table given... assume that the Hidden field in the boot sector
; tells the truth (in particular, is zero if this is an unpartitioned disk.)
;
		push cx
		push cx
		push dword [bsHidden]
.gotoffs:
;
; Get disk drive parameters (don't trust the superblock.)  Don't do this for
; floppy drives -- INT 13:08 on floppy drives will (may?) return info about
; what the *drive* supports, not about the *media*.  Fortunately floppy disks
; tend to have a fixed, well-defined geometry which is stored in the superblock.
;
		; DL == drive # still
		mov ah,08h
		int 13h
		jc no_driveparm
		and ah,ah
		jnz no_driveparm
		shr dx,8
		inc dx			; Contains # of heads - 1
		mov [bsHeads],dx
		and cx,3fh
		mov [bsSecPerTrack],cx
no_driveparm:
not_harddisk:
;
; Ready to enable interrupts, captain
;
		sti

;
; Do we have EBIOS (EDD)?
;
eddcheck:
		mov bx,55AAh
		mov ah,41h		; EDD existence query
		mov dl,[DriveNumber]
		int 13h
		jc .noedd
		cmp bx,0AA55h
		jne .noedd
		test cl,1		; Extended disk access functionality set
		jz .noedd
		;
		; We have EDD support...
		;
		mov byte [getonesec.jmp+1],(getonesec_ebios-(getonesec.jmp+2))
.noedd:

;
; Load the first sector of LDLINUX.SYS; this used to be all proper
; with parsing the superblock and root directory; it doesn't fit
; together with EBIOS support, unfortunately.
;
		mov eax,strict dword 0xdeadbeef
Sect1Ptr0	equ $-4
		mov edx,strict dword 0xfeedface
Sect1Ptr1	equ $-4
		mov bx,ldlinux_sys	; Where to load it
		call getonesec

		; Some modicum of integrity checking
		cmp dword [ldlinux_magic+4],LDLINUX_MAGIC^HEXDATE
		jne kaboom

		; Go for it...
		jmp 0:ldlinux_ent


;
; getonesec: load a single disk linear sector EDX:EAX into the buffer
;	     at ES:BX.
;
;            This routine assumes CS == DS == SS, and trashes most registers.
;
; Stylistic note: use "xchg" instead of "mov" when the source is a register
; that is dead from that point; this saves space.  However, please keep
; the order to dst,src to keep things sane.
;
getonesec:
		add eax,[Hidden]		; Add partition offset
		adc edx,[Hidden+4]
		mov cx,retry_count
.jmp:		jmp strict short getonesec_cbios

;
; getonesec_ebios:
;
; getonesec implementation for EBIOS (EDD)
;
getonesec_ebios:
.retry:
		; Form DAPA on stack
		push edx
		push eax
		push es
		push bx
		push word 1
		push word 16
		mov si,sp
		pushad
                mov ah,42h                      ; Extended Read
		call xint13
		popad
		lea sp,[si+16]			; Remove DAPA
		jc .error
                ret

.error:
		; Some systems seem to get "stuck" in an error state when
		; using EBIOS.  Doesn't happen when using CBIOS, which is
		; good, since some other systems get timeout failures
		; waiting for the floppy disk to spin up.

		pushad				; Try resetting the device
		xor ax,ax
		call xint13
		popad
		loop .retry			; CX-- and jump if not zero

		; Total failure.  Try falling back to CBIOS.
		mov byte [getonesec.jmp+1],(getonesec_cbios-(getonesec.jmp+2))

;
; getonesec_cbios:
;
; getlinsec implementation for legacy CBIOS
;
getonesec_cbios:
.retry:
		pushad

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

		cmp eax,1023		; Outside the CHS range?
		ja kaboom

		;
		; Now we have AX = cyl, DX = head, CX = sector (0-based),
		; 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 ax,0201h		; Read one sector
		call xint13
		popad
		jc .error
		ret

.error:
		loop .retry
		; Fall through to disk_error

;
; kaboom: write a message and bail out.
;
		global kaboom
disk_error:
kaboom:
		xor si,si
		mov ss,si
		mov sp,OrigFDCTabPtr	; Reset stack
		mov ds,si		; Reset data segment
		pop dword [fdctab]	; Restore FDC table
.patch:					; When we have full code, intercept here
		mov si,bailmsg
		call writestr_early

		xor ax,ax
.again:		int 16h			; Wait for keypress
					; NB: replaced by int 18h if
					; chosen at install time..
		int 19h			; And try once more to boot...
.norge:		hlt			; If int 19h returned; this is the end
		jmp short .norge

;
;
; writestr_early: write a null-terminated string to the console
;	    This assumes we're on page 0.  This is only used for early
;           messages, so it should be OK.
;
writestr_early:
		pushad
.loop:		lodsb
		and al,al
                jz .return
		mov ah,0Eh		; Write to screen as TTY
		mov bx,0007h		; Attribute
		int 10h
		jmp short .loop
.return:	popad
		ret

;
; INT 13h wrapper function
;
xint13:
                mov dl,[DriveNumber]
		int 13h
		ret

;
; Error message on failure
;
bailmsg:	db 'Boot error', 0Dh, 0Ah, 0

		; This fails if the boot sector overflowsg
		zb 1FEh-($-$$)

bootsignature	dw 0xAA55

;
; ===========================================================================
;  End of boot sector
; ===========================================================================
;  Start of LDLINUX.SYS
; ===========================================================================

LDLINUX_SYS	equ ($-$$)+TEXT_START
ldlinux_sys:

syslinux_banner	db CR, LF, MY_NAME, ' ', VERSION_STR, ' ', DATE_STR, ' ', 0
		db CR, LF, 1Ah	; EOF if we "type" this in DOS

		alignz 8
ldlinux_magic	dd LDLINUX_MAGIC
		dd LDLINUX_MAGIC^HEXDATE

;
; This area is patched by the installer.  It is found by looking for
; LDLINUX_MAGIC, plus 8 bytes.
;
SUBVOL_MAX	equ 256
CURRENTDIR_MAX	equ FILENAME_MAX

patch_area:
DataSectors	dw 0		; Number of sectors (not including bootsec)
ADVSectors	dw 0		; Additional sectors for ADVs
LDLDwords	dd 0		; Total dwords starting at ldlinux_sys,
CheckSum	dd 0		; Checksum starting at ldlinux_sys
				; value = LDLINUX_MAGIC - [sum of dwords]
MaxTransfer	dw 127		; Max sectors to transfer
EPAPtr		dw EPA - LDLINUX_SYS	; Pointer to the extended patch area

;
; Extended patch area -- this is in .data16 so it doesn't occupy space in
; the first sector.  Use this structure for anything that isn't used by
; the first sector itself.
;
		section .data16
		alignz 2
EPA:
ADVSecPtr	dw ADVSec0 - LDLINUX_SYS
CurrentDirPtr	dw CurrentDirName-LDLINUX_SYS	; Current directory name string
CurrentDirLen	dw CURRENTDIR_MAX
SubvolPtr	dw SubvolName-LDLINUX_SYS
SubvolLen	dw SUBVOL_MAX
SecPtrOffset	dw SectorPtrs-LDLINUX_SYS
SecPtrCnt	dw (SectorPtrsEnd - SectorPtrs)/10

;
; Boot sector patch pointers
;
Sect1Ptr0Ptr	dw Sect1Ptr0 - bootsec		; Pointers to Sector 1 location
Sect1Ptr1Ptr	dw Sect1Ptr1 - bootsec
RAIDPatchPtr	dw kaboom.again - bootsec	; Patch to INT 18h in RAID mode

;
; Base directory name and subvolume, if applicable.
;
%define HAVE_CURRENTDIRNAME
		global CurrentDirName, SubvolName
CurrentDirName	times CURRENTDIR_MAX db 0
SubvolName	times SUBVOL_MAX db 0

		section .init
ldlinux_ent:
;
; Note that some BIOSes are buggy and run the boot sector at 07C0:0000
; instead of 0000:7C00 and the like.  We don't want to add anything
; more to the boot sector, so it is written to not assume a fixed
; value in CS, but we don't want to deal with that anymore from now
; on.
;
		sti		; In case of broken INT 13h BIOSes

;
; Tell the user we got this far
;
		mov si,syslinux_banner
		call writestr_early

;
; Checksum data thus far
;
		mov si,ldlinux_sys
		mov cx,SECTOR_SIZE >> 2
		mov edx,-LDLINUX_MAGIC
.checksum:
		lodsd
		add edx,eax
		loop .checksum
		mov [CheckSum],edx		; Save intermediate result

;
; Tell the user if we're using EBIOS or CBIOS
;
print_bios:
		mov si,cbios_name
		cmp byte [getonesec.jmp+1],(getonesec_ebios-(getonesec.jmp+2))
		jne .cbios
		mov si,ebios_name
		mov byte [getlinsec.jmp+1],(getlinsec_ebios-(getlinsec.jmp+2))
.cbios:
		mov [BIOSName],si
		call writestr_early

		section .earlybss
%define	HAVE_BIOSNAME 1
BIOSName	resw 1

		section .init
;
; Now we read the rest of LDLINUX.SYS.
;
load_rest:
		lea esi,[SectorPtrs]
		mov ebx,TEXT_START+2*SECTOR_SIZE ; Where we start loading
		mov cx,[DataSectors]
		dec cx				; Minus this sector

.get_chunk:
		jcxz .done
		mov eax,[si]
		mov edx,[si+4]
		movzx ebp,word [si+8]
		sub cx,bp
		push ebx
		shr ebx,4			; Convert to a segment
		mov es,bx
		xor bx,bx
		call getlinsec
		pop ebx
		shl ebp,SECTOR_SHIFT
		add ebx,ebp
		add si,10
		jmp .get_chunk

.done:

;
; All loaded up, verify that we got what we needed.
; Note: the checksum field is embedded in the checksum region, so
; by the time we get to the end it should all cancel out.
;
verify_checksum:
		mov si,ldlinux_sys + SECTOR_SIZE
		mov ecx,[LDLDwords]
		sub ecx,SECTOR_SIZE >> 2
		mov eax,[CheckSum]
.checksum:
		add eax,[si]
		add si,4
		jnz .nowrap
		; Handle segment wrap
		mov dx,ds
		add dx,1000h
		mov ds,dx
.nowrap:
		dec ecx
		jnz .checksum

		and eax,eax			; Should be zero
		jz all_read			; We're cool, go for it!

;
; Uh-oh, something went bad...
;
		mov si,checksumerr_msg
		call writestr_early
		jmp kaboom

;
; -----------------------------------------------------------------------------
; Subroutines that have to be in the first sector
; -----------------------------------------------------------------------------



;
; getlinsec: load a sequence of BP floppy sector given by the linear sector
;	     number in EAX into the buffer at ES:BX.  We try to optimize
;	     by loading up to a whole track at a time, but the user
;	     is responsible for not crossing a 64K boundary.
;	     (Yes, BP is weird for a count, but it was available...)
;
;	     On return, BX points to the first byte after the transferred
;	     block.
;
;            This routine assumes CS == DS.
;
		global getlinsec
getlinsec:
		pushad
		add eax,[Hidden]		; Add partition offset
		adc edx,[Hidden+4]
.jmp:		jmp strict short getlinsec_cbios

;
; getlinsec_ebios:
;
; getlinsec implementation for EBIOS (EDD)
;
getlinsec_ebios:
.loop:
                push bp                         ; Sectors left
.retry2:
		call maxtrans			; Enforce maximum transfer size
		movzx edi,bp			; Sectors we are about to read
		mov cx,retry_count
.retry:

		; Form DAPA on stack
		push edx
		push eax
		push es
		push bx
		push di
		push word 16
		mov si,sp
		pushad
                mov ah,42h                      ; Extended Read
		push ds
		push ss
		pop ds
		call xint13
		pop ds
		popad
		lea sp,[si+16]			; Remove DAPA
		jc .error
		pop bp
		add eax,edi			; Advance sector pointer
		adc edx,0
		sub bp,di			; Sectors left
                shl di,SECTOR_SHIFT		; 512-byte sectors
                add bx,di			; Advance buffer pointer
                and bp,bp
                jnz .loop

		popad
                ret

.error:
		; Some systems seem to get "stuck" in an error state when
		; using EBIOS.  Doesn't happen when using CBIOS, which is
		; good, since some other systems get timeout failures
		; waiting for the floppy disk to spin up.

		pushad				; Try resetting the device
		xor ax,ax
		mov dl,[DriveNumber]
		int 13h
		popad
		loop .retry			; CX-- and jump if not zero

		;shr word [MaxTransfer],1	; Reduce the transfer size
		;jnz .retry2

		; Total failure.  Try falling back to CBIOS.
		mov byte [getlinsec.jmp+1],(getlinsec_cbios-(getlinsec.jmp+2))
		;mov byte [MaxTransfer],63	; Max possibe CBIOS transfer

		pop bp
		; ... fall through ...

;
; getlinsec_cbios:
;
; getlinsec implementation for legacy CBIOS
;
getlinsec_cbios:
.loop:
		push edx
		push eax
		push bp
		push bx

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

		cmp eax,1023		; Outside the CHS range?
		ja kaboom

		;
		; Now we have AX = cyl, DX = head, CX = sector (0-based),
		; BP = sectors to transfer, SI = bsSecPerTrack,
		; ES:BX = data target
		;

		call maxtrans			; Enforce maximum transfer size

		; Must not cross track boundaries, so BP <= SI-CX
		sub si,cx
		cmp bp,si
		jna .bp_ok
		mov bp,si
.bp_ok:

		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
		xchg ax,bp		; Sector to transfer count
		mov ah,02h		; Read sectors
		mov bp,retry_count
.retry:
		pushad
		call xint13
		popad
		jc .error
.resume:
		movzx ecx,al		; ECX <- sectors transferred
		shl ax,SECTOR_SHIFT	; Convert sectors in AL to bytes in AX
		pop bx
		add bx,ax
		pop bp
		pop eax
		pop edx
		add eax,ecx
		sub bp,cx
		jnz .loop
		popad
		ret

.error:
		dec bp
		jnz .retry

		xchg ax,bp		; Sectors transferred <- 0
		shr word [MaxTransfer],1
		jnz .resume
		jmp kaboom

maxtrans:
		cmp bp,[MaxTransfer]
		jna .ok
		mov bp,[MaxTransfer]
.ok:		ret

;
; Checksum error message
;
checksumerr_msg	db ' Load error - ', 0	; Boot failed appended

;
; BIOS type string
;
cbios_name	db 'CHS', 0			; CHS/CBIOS
ebios_name	db 'EDD', 0			; EDD/EBIOS

;
; Debug routine
;
%ifdef debug
safedumpregs:
		cmp word [Debug_Magic],0D00Dh
		jnz nc_return
		jmp dumpregs
%endif

rl_checkpt	equ $				; Must be <= 8000h

rl_checkpt_off	equ ($-$$)
%ifndef DEPEND
 %if rl_checkpt_off > 3F6h			; Need one extent
  %assign rl_checkpt_overflow rl_checkpt_off - 3F6h
  %error Sector 1 overflow by rl_checkpt_overflow bytes
 %endif
%endif

;
; Extent pointers... each extent contains an 8-byte LBA and an 2-byte
; sector count.  In most cases, we will only ever need a handful of
; extents, but we have to assume a maximally fragmented system where each
; extent contains only one sector.
;
		alignz 2
MaxInitDataSize	equ 96 << 10
MaxLMA		equ TEXT_START+SECTOR_SIZE+MaxInitDataSize
SectorPtrs	zb 10*(MaxInitDataSize >> SECTOR_SHIFT)
SectorPtrsEnd	equ $

; ----------------------------------------------------------------------------
;  End of code and data that have to be in the first sector
; ----------------------------------------------------------------------------

		section .text16
all_read:
		; We enter here with both DS and ES scrambled...
		xor ax,ax
		mov ds,ax
		mov es,ax
;
; Let the user (and programmer!) know we got this far.  This used to be
; in Sector 1, but makes a lot more sense here.
;
		mov si,copyright_str
		call writestr_early


;
; Insane hack to expand the DOS superblock to dwords
;
expand_super:
		xor eax,eax
		mov si,superblock
		mov di,SuperInfo
		mov cx,superinfo_size
.loop:
		lodsw
		dec si
		stosd				; Store expanded word
		xor ah,ah
		stosd				; Store expanded byte
		loop .loop


;
; Common initialization code
;
%include "init.inc"
		
		pushad
		mov eax,ROOT_FS_OPS
		movzx dx,byte [DriveNumber]
		; DH = 0: we are boot from disk not CDROM
		mov ecx,[Hidden]
		mov ebx,[Hidden+4]
		mov si,[bsHeads]
		mov di,[bsSecPerTrack]
		movzx ebp,word [MaxTransfer]
		pm_call fs_init
		popad

		section .bss16
SuperInfo	resq 16			; The first 16 bytes expanded 8 times

		section .text16