1; Copyright 2011, Jean-Lo��c Charroud, jcharroud@free.fr
2; Distributed under the terms of the MIT License or LGPL v3
3;
4; Haiku (C) Copyright Haiku 2011
5; MBR Boot code
6;
7; assemble the Master boot record with:
8;	yasm -f bin -O5 -o mbr.bin mbr.S
9;
10; assemble the MBR's code (does not contain the partiton table
11; nor the MAGIC code) with:
12;	yasm -f bin -O5 -o mbrcode.bin mbr.S -dMBR_CODE_ONLY=1
13
14
15;%define	DEBUG			1
16;%define	MBR_CODE_ONLY	1
17
18
19;CONST
20	%assign	DISKSIG			440				; Disk signature offset
21	%assign	PT_OFF			0x1be			; Partition table offset
22	%assign	MAGIC_OFF		0x1fe			; Magic offset
23	%assign	MAGIC			0xaa55			; Magic bootable signature
24	%assign	SECSIZE			0x200			; Size of a single disk sector
25	%assign	FLG_ACTIVE		0x80			; active partition flag
26	%assign	SECTOR_COUNT	0x01			; Number of record to load from
27											; the ctive partition
28
29	%assign	LOAD			0x7c00			; Load address
30	%assign	EXEC			0x600			; Execution address
31	%assign	HEAP			EXEC+SECSIZE	; Execution address
32
33;BIOS calls
34	%assign	BIOS_VIDEO_SERVICES			0x10; ah - function
35	%assign	BIOS_DISK_SERVICES			0x13
36	%assign	BIOS_KEYBOARD_SERVICES		0X16
37	%assign	BIOS_BASIC					0X18
38	%assign	BIOS_REBOOT					0X19
39
40	;BIOS calls parameters
41		; video services
42			%assign	WRITE_CHAR			0x0e; al - char
43											; bh - page?
44
45		; disk services
46			%assign	READ_DISK_SECTORS	0x02; dl	- drive
47											; es:bx	- buffer
48											; dh	- head
49											; ch7:0 - track7:0
50											; cl7:6 - track9:8
51											;	5:0	- sector
52											; al	- sector count
53											; -> al - sectors read
54			%assign	READ_DRV_PARAMETERS	0x08; dl - drive
55											; -> cl - max cylinder 9:8
56											;		- sectors per track
57											;    ch - max cylinder 7:0
58											;    dh - max head
59											;    dl - number of drives (?)
60			%assign	CHK_DISK_EXTENTIONS	0x41; bx - 0x55aa
61											; dl - drive
62											; -> success: carry clear
63											;    ah - extension version
64											;    bx - 0xaa55
65											;    cx - support bit mask
66											;		1 - Device Access using the
67											;			packet structure
68											;		2 - Drive Locking and
69											;			Ejecting
70											;		4 - Enhanced Disk Drive
71											;			Support (EDD)
72											; -> error: carry set
73			%assign	EXTENDED_READ		0x42; dl - drive
74											; ds:si - address packet
75											; -> success: carry clear
76											; -> error: carry set
77
78			%assign FIXED_DSK_SUPPORT	0x1	; flag indicating fixed disk
79											; extension command subset
80
81		; keyboard services
82			%assign	READ_CHAR			0x00; -> al - ASCII char
83
84;MACRO
85	; nicer way to get the size of a structure
86	%define	sizeof(s)	s %+ _size
87
88	; using a structure in a another structure definition
89	%macro	nstruc	1-2		1
90		resb	sizeof(%1) * %2
91	%endmacro
92
93	; nicer way to access GlobalVariables
94	%macro	declare_var	1
95		%define %1	HEAP + GlobalVariables. %+ %1
96	%endmacro
97
98	%macro	puts	1
99		mov		si,		%1
100		call	_puts
101	%endmacro
102
103	%macro	error	1
104		mov		si, %1
105		jmp		_error
106	%endmacro
107
108;TYPEDEFS
109	; 64 bit value
110	struc	quadword
111		.lower			resd	1
112		.upper			resd	1
113	endstruc
114
115	struc	CHS_addr
116		.head			resb	1
117		.sector			resb	1
118		.cylindre		resb	1
119	endstruc
120
121	struc	PartitionEntry
122		.status			resb	1
123		.CHS_first		nstruc	CHS_addr
124		.type			resb	1
125		.CHS_last		nstruc	CHS_addr
126		.LBA_start		resd	1
127		.LBA_size		resd	1
128	endstruc
129
130	; address packet as required by the EXTENDED_READ BIOS call
131	struc	AddressPacket
132		.packet_size	resb	1
133		.reserved		resb	1
134		.block_count	resw	1
135		.buffer			resd	1
136		.sector			nstruc	quadword
137		;.long_buffer	nstruc	quadword
138		;	We don't need the 64 bit buffer pointer. The 32 bit .buffer is more
139		;	than sufficient.
140	endstruc
141
142	; Structure containing the variables that don't need pre-initialization.
143	; this structure will be allocated onto our "heap".
144	struc	GlobalVariables
145		.boot_drive_id				resd	1
146		.boot_partition				resd	1
147		.address_packet				nstruc	AddressPacket
148	endstruc
149
150;alias for easy access to our global variables
151	declare_var	boot_drive_id
152	declare_var	boot_partition
153	declare_var	address_packet
154
155;/////////////////////////////////////////////////////////////////////////
156;// A 512 byte MBR boot manager that simply boots the active partition. //
157;/////////////////////////////////////////////////////////////////////////
158; 16 bit code
159SECTION .text
160BITS 16
161ORG EXEC						; MBR is loaded at 0x7c00 but relocated at 0x600
162start:							; we run the LOADed code
163		cli						; disable interrupts
164		cld						; clear direction flag (for string operations)
165	init:
166		xor		ax,		ax		; Zero
167		mov		es,		ax		; Set up extra segment
168		mov		ds,		ax		; Set up data segment
169		mov		ss,		ax		; Set up stack segment
170		mov		sp,		LOAD	; Set up stack pointer
171
172		;init our heap allocated variables with zeros
173		mov		di,		HEAP	;start adress
174		mov		cx,		sizeof(GlobalVariables)	;size
175		rep						; while(cx--)
176			stosb				;	es[di++]:=al;
177
178	; Relocate ourself to a lower address so that we are out of the way
179	; when we load in the bootstrap from the partition to boot.
180	reloc:
181		mov		si,		LOAD	; Source
182		; init AddressPacket.buffer now since LOAD is in 'si'
183		mov [address_packet+AddressPacket.buffer],si
184		mov byte[address_packet+AddressPacket.packet_size],sizeof(AddressPacket)
185		mov byte[address_packet+AddressPacket.block_count],SECTOR_COUNT
186
187		mov		di,		EXEC	; Destination
188		mov		ch,		1		; count cx:=256 (cl cleared by precedent rep call)
189		rep						; while(cx--)
190			movsw				;   es[di++]:=ds[si++]
191								; //di and si are incremented by sizeof(word)
192		jmp word 0x0000:continue; FAR jump to the relocated "continue" (some
193								; BIOSes initialise CS to 0x07c0 so we must set
194								; CS correctly)
195
196continue:						; Now we run EXEC_based relocated code
197		sti						; enable interrupts
198		%ifdef DEBUG
199		puts	kMsgStart
200		%endif
201
202search_active_partition:
203		mov		si,EXEC+PT_OFF				; point to first table entry
204		mov		al,04						; there are 4 table entries
205	.loop:									; SEARCH FOR AN ACTIVE ENTRY
206		cmp		byte[si],FLG_ACTIVE			; is this the active entry?
207		je		found_active				; yes
208		add		si,	sizeof(PartitionEntry)	; next PartitionEntry
209		dec		al							; decrease remaining entry count
210		jnz		.loop						; loop if entry count > 0
211		jmp		no_bootable_active_partition; last partition reached
212
213found_active:								; active partition (pointed by si)
214		mov		[boot_partition],si			; Save active partition pointer
215
216	.get_read_sector:						; initialise address_packet:
217		mov eax,[si + PartitionEntry.LBA_start]
218		mov [address_packet+AddressPacket.sector],eax
219
220		; if LBA_adress equals 0 then it's not a valid PBR (it is the MBR)
221		; this can append when we only have a CHS adress in the partition entry
222		test	eax,		eax				;if ( LBA_adress == 0 )
223		jz		no_disk_extentions			;then no_disk_extentions()
224
225check_disk_extensions:
226		; Note: this test may be unnecessary since EXTENDED_READ also
227		; set the carry flag when extended calls are not supported
228		%ifdef DEBUG
229		puts	kMsgCheckEx
230		%endif
231
232		mov		ah, CHK_DISK_EXTENTIONS		; set command
233		mov		bx, 0x55aa					; set parameter : hton(MAGIC)
234		; dl has not changed yet, still contains the drive ID
235		int		BIOS_DISK_SERVICES			; if( do_command() <> OK )
236		jc		no_disk_extentions			; then use simple read operation
237											; else use extended read
238disk_extentions:
239		%ifdef DEBUG
240		puts	kMsgRead_Ex
241		%endif
242
243		; load first bloc active partition
244		; dl has not changed yet, still contains the drive ID
245		mov		si,	address_packet			; set command parameters
246		mov		ah, EXTENDED_READ			; set command
247	.read_PBR:
248		int		BIOS_DISK_SERVICES			; if ( do_command() <> OK )
249		jc		no_disk_extentions			; then try CHS_read();
250
251check_for_bootable_partition:
252		cmp		word[LOAD+MAGIC_OFF],MAGIC	; if ( ! volume.isBootable() )
253		jne		no_bootable_active_partition; then error();
254
255jump_PBR:
256		%ifdef DEBUG
257		puts	kMsgBootPBR
258		call	_pause
259		%else
260		puts	kMsgStart
261		%endif
262
263		; jump to 0x7c00 with :
264		;	- CS=0
265		;	- DL=drive number
266		;	- DS:SI=pointer to the selected partition table
267		;			entry (required by some PBR)
268
269		; dl has not changed yet, still contains the drive ID
270		mov		si,		[boot_partition]
271		jmp		LOAD						; jump into partition boot loader
272
273no_disk_extentions:
274		%ifdef DEBUG
275		puts	kMsgNoExtentions
276		%endif
277
278		mov		si,		[boot_partition]	; Restore active partition pointer
279
280		;load CHS PBR sector info
281		mov		dh,		[si+1]				; dh 7:0 = head 7:0   (0 - 255)
282		mov		cx,		[si+2]				; cl 5:0 = sector 7:0 (1 - 63)
283											; cl 7:6 = cylinder 9:8	(0 - 1023)
284											; ch 7:0 = cylinder 7:0
285	.check_Sector:
286		mov		al,		cl
287		and		al,		0x3F				; extract sector
288		test	al,		al;					; if (sector==0)
289		jz		no_bootable_active_partition; then error("invalid sector");
290		cmp		al,		0x3F				; if(  (Sector == 63)
291		jne		.CHS_valid					;
292	.check_Cylinder:
293		mov		ax,		cx
294		shr		ah,		6
295		cmp		ax,		0x03FF				;	and	 (Cylinder == 1023)
296		jne		.CHS_valid
297	.check_Head:
298		cmp		dh,		0xFF				;	and  ( (Head == 255)
299		jne		.CHS_valid
300		cmp		dh,		0xFE				;	    or (Head == 254) )  )
301		je		no_bootable_active_partition; then error("invalid CHS_adress");
302
303	.CHS_valid:
304		; dl has not changed yet, still contains the drive ID
305		mov		bx,		LOAD				; set buffer
306		mov		al,		SECTOR_COUNT		; set Size
307		mov		ah,		READ_DISK_SECTORS	; set read command
308		int		BIOS_DISK_SERVICES			; if ( do_command() == OK )
309		jnc		check_for_bootable_partition; then resume(normal boot sequence)
310											; else continue;
311
312no_bootable_active_partition:
313		mov		si, kMsgNoBootable
314		;jmp _error							; _error is the next line !
315
316_error:
317	; display a non-empty null-terminated string on the screen,
318	; wait for a key pressed and go back to the bios
319	; IN :
320	;	- si = address of string to display
321	; OUT :
322	; DIRTY :
323	;	- ax
324	;	- si
325		call	_puts
326		call	_pause
327		puts	kMsgROMBASIC
328		int		BIOS_BASIC					; BIOS_BASIC give the control back
329											; to the BIOS. Doing so, let some
330											; BIOSes try to boot an alternate
331											; device (PXE/CD/HDD : see your
332											; BIOS' boot device order )
333_pause:
334	; wait for a key pressed
335	; IN :
336	; OUT :
337	; DIRTY :
338	;	- ax
339		mov		ah,		READ_CHAR
340		int		BIOS_KEYBOARD_SERVICES
341		ret
342
343_puts:
344	; display a non-empty null-terminated string on the screen
345	; IN :
346	;	- si = address of string to display
347	; OUT :
348	; DIRTY :
349	;	- ax
350	;	- bx
351	;	- si
352		xor		bx,		bx					; bx:=0
353	.loop:									; do {
354		lodsb								;	al=[si++];
355		mov		ah,		WRITE_CHAR			;
356		int		BIOS_VIDEO_SERVICES			;	WRITE_CHAR(al)
357		or		al, al						; } while (al<>0);
358		jnz		.loop
359		ret
360
361data:
362		kMsgROMBASIC	db	'ROM BASIC',0
363		kMsgNoBootable	db	'No bootable active volume',13,10,0
364		kMsgStart		db	'Loading system',10,13,0
365
366	%ifdef DEBUG
367		kMsgBootPBR		db	'JMP PBR',13,10,0
368		kMsgRead_Ex		db	'Read_ex block',13,10,0
369		kMsgCheckEx		db	'CheckEx',13,10,0
370		kMsgNoExtentions db	'Read block',13,10,0
371		kMsgReloc		db	'reloc MBR',13,10,0
372	%endif
373
374; check whether the code is small enough to fit in the boot code area
375end:
376		;use nasm instead of yasm to check  the code size
377		;%if end - start > DISKSIG
378		;	%error "Code exceeds master boot code area!"
379		;%endif
380
381%ifdef MBR_CODE_ONLY
382	;just build the code.
383	;Do not generate the datas
384%else
385		;use nasm instead of yasm => use %rep instead of times
386		;%rep start + DISKSIG - end
387		;	db	0					;fill the rest of the code area
388		;%endrep
389		times start + DISKSIG - end db 0
390		kMbrDiskID	dd	0			;Disk signature
391		dw 0						;reserved
392		PartitionTable times PartitionEntry_size * 4 db 0
393DiskSignature:
394		;use nasm instead of yasm to check  the MAGIC offset
395		;%if DiskSignature - start <> MAGIC_OFF
396		;	%error "incorrect Disk Signature offset"
397		;%endif
398		kMbrSignature	db	0x55, 0xAA
399%endif
400