1/*	$NetBSD$ */
2
3/*
4 * Copyright (c) 2009 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to the NetBSD Foundation
8 * by Mike M. Volokhov, based on the mbr.S code by Wolfgang Solfrank,
9 * Frank van der Linden, and David Laight.
10 * Development of this software was supported by the
11 * Google Summer of Code program.
12 * The GSoC project was mentored by Allen Briggs and Joerg Sonnenberger.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
16 * are met:
17 * 1. Redistributions of source code must retain the above copyright
18 *    notice, this list of conditions and the following disclaimer.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 *    notice, this list of conditions and the following disclaimer in the
21 *    documentation and/or other materials provided with the distribution.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
24 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
25 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
27 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 * POSSIBILITY OF SUCH DAMAGE.
34 */
35
36/*
37 * x86 PC BIOS master boot code - GUID partition table format
38 */
39
40/* Compile options:
41 * COM_PORT	- do serial io to specified port number
42 *		  0..3 => bios port, otherwise actual io_addr
43 * COM_BAUD	- initialise serial port baud rate
44 *
45 * NO_LBA_CHECK	- no check if bios supports LBA reads
46 * NO_CRC_CHECK	- disable crc checks for GPT
47 * NO_BANNER    - do not output title line 'banner'
48 */
49
50#ifdef COM_PORT
51#if COM_PORT < 4
52/* The first 4 items in the 40:xx segment are the serial port base addresses */
53#define COM_PORT_VAL (0x400 + (COM_PORT * 2))
54#else
55#define COM_PORT_VAL $COM_PORT
56#endif
57
58#if !defined(COM_FREQ)
59#define COM_FREQ 1843200
60#endif
61#endif
62
63#include <machine/asm.h>
64#include <sys/bootblock.h>
65
66#define BOOTADDR	0x7c00
67#define GPTBUFADDR	0xa000		/* GPT buffer (both header & array) */
68
69/*
70 * GPT header structure, offsets
71 */
72#define GPTHDR_SIG_O	0		/* header signature, 8 b */
73#define GPTHDR_REV_O	8		/* GPT revision, 4 b */
74#define GPTHDR_SIZE_O	12		/* header size, 4 b */
75#define GPTHDR_CRC_O	16		/* header CRC32, 4 b */
76#define GPTHDR_RSVD_O	20		/* reserved, 4 b */
77#define GPTHDR_MYLBA_O	24		/* this header LBA, 8 b */
78#define GPTHDR_ALTLBA_O	32		/* alternate header LBA, 8 b */
79#define GPTHDR_SLBA_O	40		/* first usable LBA, 8 b */
80#define GPTHDR_ELBA_O	48		/* last usable LBA, 8 b */
81#define GPTHDR_GUID_O	56		/* disk GUID, 16 b */
82#define GPTHDR_PTNLBA_O	72		/* ptn. entry LBA, 8 b */
83#define GPTHDR_PTNN_O	80		/* number of ptns, 4 b */
84#define GPTHDR_PTNSZ_O	84		/* partition size, 4 b */
85#define GPTHDR_PTNCRC_O	88		/* ptn. array CRC32, 4 b */
86
87/*
88 * GPT partition entry structure, offsets
89 */
90#define GPTPTN_TYPE_O	0		/* ptn. type GUID, 16 b */
91#define GPTPTN_GUID_O	16		/* ptn. unique GUID, 16 b */
92#define GPTPTN_SLBA_O	32		/* ptn. starting LBA, 8 b */
93#define GPTPTN_ELBA_O	40		/* ptn. ending LBA, 8 b */
94#define GPTPTN_ATTR_O	48		/* ptn. attributes, 8 b */
95#define GPTPTN_NAME_O	56		/* ptn. name, UTF-16, 72 b */
96
97/*
98 * Default values of generic disk partitioning
99 */
100#ifndef SECTOR_SIZE
101#define SECTOR_SIZE		512		/* 8192 bytes max */
102#endif
103#define GPTHDR_BLKNO		1
104#define GPTHDR_SIZE		92		/* size of GPT header */
105#define GPTHDR_LBASZ		1		/* default LBAs for header */
106#define GPTHDR_PTNSZ		128		/* size of a partition entry */
107#define GPTHDR_PTNN_MIN		128		/* minimum number of ptns */
108#define GPTPTN_SIZE		(GPTHDR_PTNSZ * GPTHDR_PTNN_MIN)
109#if GPTPTN_SIZE % SECTOR_SIZE == 0
110#define GPTPTN_LBASZ		(GPTPTN_SIZE / SECTOR_SIZE)
111#else
112#define GPTPTN_LBASZ		(GPTPTN_SIZE / SECTOR_SIZE + 1)
113#endif
114#define GPT_LBASZ		(GPTHDR_LBASZ + GPTPTN_LBASZ)
115
116/*
117 * Minimum and maximum drive number that is considered to be valid.
118 */
119#define MINDRV		0x80
120#define MAXDRV		0x8f
121
122/*
123 * Error codes. Done this way to save space.
124 */
125#define ERR_INVPART	'P'		/* Invalid partition table */
126#define ERR_READ	'R'		/* Read error */
127#define ERR_NOOS	'S'		/* Magic no. check failed for part. */
128#define ERR_NO_LBA	'L'		/* Sector above chs limit */
129
130#define set_err(err)	movb	$err, %al
131
132	.text
133	.code16
134/*
135 * Move ourselves out of the way first.
136 * (to the address we are linked at)
137 * and zero our bss
138 */
139ENTRY(start)
140	xor	%ax, %ax
141	mov	%ax, %ss
142	movw	$BOOTADDR, %sp
143	mov	%ax, %es
144	mov	%ax, %ds
145	movw	$mbr, %di
146	mov     $BOOTADDR + (mbr - start), %si
147	push	%ax			/* zero for %cs of lret */
148	push	%di
149	movw	$(bss_start - mbr), %cx
150	rep
151	movsb				/* relocate code (zero %cx on exit) */
152	mov	$(bss_end - bss_start + 511)/512, %ch
153	rep
154	stosw				/* zero bss */
155	lret				/* Ensures %cs == 0 */
156
157/*
158 * Sanity check the drive number passed by the BIOS. Some BIOSs may not
159 * do this and pass garbage.
160 */
161mbr:
162	cmpb	$MAXDRV, %dl		/* relies on MINDRV being 0x80 */
163	jle	1f
164	movb	$MINDRV, %dl		/* garbage in, boot disk 0 */
1651:
166	push	%dx			/* save drive number */
167	push	%dx			/* twice - for err_msg loop */
168
169#if defined(COM_PORT) && defined(COM_BAUD)
170	mov	$com_args, %si
171	mov	$num_com_args, %cl	/* %ch is zero from above */
172	mov	COM_PORT_VAL, %dx
1731:	lodsw
174	add	%ah, %dl
175	outb	%dx
176	loop	1b
177#endif
178
179#ifndef NO_BANNER
180	mov	$banner, %si
181	call	message
182#endif
183
184/*
185 * Read and validate GUID partition tables
186 *
187 * Register use:
188 * %ax		temp
189 * %bx		temp
190 * %cx		counters (%ch was preset to zero via %cx before)
191 * %dx		disk pointer (inherited from BIOS or the check above)
192 * %bp		GPT header pointer
193 */
194#ifndef NO_LBA_CHECK
195/*
196 * Determine whether we have int13-extensions, by calling int 13, function 41.
197 * Check for the magic number returned, and the disk packet capability.
198 * On entry %dx should contain BIOS disk number.
199 */
200lba_check:
201	movw	$0x55aa, %bx
202	movb	$0x41, %ah
203	int	$0x13
204	set_err(ERR_NO_LBA)
205	jc	1f			/* no int13 extensions */
206	cmpw	$0xaa55, %bx
207	jnz	1f
208	testb	$1, %cl
209	jnz	gpt
2101:	jmp	err_msg
211#endif
212
213gpt:
214	/*
215	 * Read GPT header
216	 */
217	movw	$GPTBUFADDR, %bp	/* start from primary GPT layout */
218read_gpt:
219	call	do_lba_read		/* read GPT header */
220	jc	try_gpt2
221
222	/*
223	 * Verify GPT header
224	 */
225	movw	$efihdr_sign, %si	/* verify GPT signature... */
226	movw	%bp, %di		/* ptn signature is at offset 0 */
227	movb	$sizeof_efihdr_sign, %cl /* signature length */
228	repe
229	cmpsb				/* compare */
230	jne	try_gpt2		/* if not equal - try GPT2 */
231
232#ifndef NO_CRC_CHECK
233	mov	%bp, %si		/* verify CRC32 of the header... */
234	mov	$GPTHDR_SIZE, %di	/* header boundary */
235	add	%bp, %di
236	xor	%eax, %eax
237	xchgl	%eax, GPTHDR_CRC_O(%bp)	/* save and reset header CRC */
238	call	crc32
239	cmp	%eax, %ebx		/* is CRC correct? */
240	jne	try_gpt2
241#endif
242
243#if 0	/* XXX: weak check - safely disabled due to space constraints */
244	movw	$lba_sector, %si	/* verify GPT location... */
245	mov	$GPTHDR_MYLBA_O, %di
246	add	%bp, %di
247	movb	$8, %cl			/* LBA size */
248	repe
249	cmpsb				/* compare */
250	jne	try_gpt2		/* if not equal - try GPT2 */
251#endif
252
253	/*
254	 * All header checks passed - now verify GPT partitions array
255	 */
256#ifndef NO_CRC_CHECK
257	movl	GPTHDR_PTNCRC_O(%bp), %eax	/* original array checksum */
258	push	%bp			/* save header pointer for try_gpt2 */
259#endif
260
261	/*
262	 * point %bp to GPT partitions array location
263	 */
264	cmp	$GPTBUFADDR, %bp
265	je	1f
266	mov	$GPTBUFADDR, %bp
267	jmp	2f
2681:	mov	$GPTBUFADDR + GPTPTN_LBASZ * SECTOR_SIZE, %bp
2692:
270
271#ifndef NO_CRC_CHECK
272	mov	%bp, %si		/* array location for CRC32 check */
273	mov	$GPTPTN_SIZE, %di	/* array boundary */
274	add	%bp, %di
275	call	crc32
276	cmp	%eax, %ebx		/* is CRC correct? */
277	jne	1f			/* if no - try GPT2 */
278	pop	%ax			/* restore stack consistency */
279#endif
280	jmp	gpt_parse
281
282#ifndef NO_CRC_CHECK
2831:	pop	%bp			/* restore after unsucc. array check */
284#endif
285try_gpt2:
286	cmp	$GPTBUFADDR, %bp	/* is this GPT1? */
287	set_err(ERR_INVPART)
288	jne	err_msg			/* if no - we just tried GPT2. Stop. */
289
290	mov	%bp, %si		/* use [%bp] as tmp buffer */
291	movb	$0x1a, (%si)		/* init buffer size (per v.1.0) */
292	movb	$0x48, %ah		/* request extended LBA status */
293	int	$0x13			/* ... to get GPT2 location */
294	set_err(ERR_NO_LBA)
295	jc	err_msg			/* on error - stop */
296#define LBA_DKINFO_OFFSET 16		/* interested offset in out buffer */
297	addw	$LBA_DKINFO_OFFSET, %si	/* ... contains number of disk LBAs */
298#undef LBA_DKINFO_OFFSET
299	movw	$lba_sector, %di
300	movb	$8, %cl			/* LBA size */
301	rep
302	movsb				/* do get */
303	subl	$GPT_LBASZ, lba_sector	/* calculate location of GPT2 */
304	sbbl	$0, lba_sector + 4	/* 64-bit LBA correction */
305
306	movw	$GPTBUFADDR + GPTPTN_LBASZ * SECTOR_SIZE, %bp
307					/* the GPT2 header location */
308	jmp	read_gpt		/* try once again */
309
310/*
311 * GPT header validation done.
312 * Now parse GPT partitions and try to boot from appropriate.
313 * Register use:
314 * %bx		partition counter
315 * %bp		partition entry pointer (already initialized on entry)
316 * %di		partition entry moving pointer
317 * %si		variables pointer
318 * %cx		counter
319 */
320
321gpt_parse:
322	movw	$BOOTADDR, lba_rbuff	/* from now we will read boot code */
323	movb	$1, lba_count		/* read PBR only */
324	mov	$GPTHDR_PTNN_MIN, %bx	/* number of GUID partitions to parse */
325do_gpt_parse:
326	movw	$bootptn_guid, %si	/* lookup the boot partition GUID */
327	movb	$0x10, %cl		/* sizeof GUID */
328	movw	%bp, %di		/* set pointer to partition entry */
329	add	%cx, %di		/* partition GUID at offset 16 */
330	repe
331	cmpsb				/* do compare */
332	jne	try_nextptn		/* doesn't seem appropriate ptn */
333
334	/*
335	 * Read partition boot record
336	 */
337	mov	$GPTPTN_SLBA_O, %si	/* point %si to partition LBA */
338	add	%bp, %si
339	movw	$lba_sector, %di
340	movb	$8, %cl			/* LBA size */
341	rep
342	movsb				/* set read pointer to LBA of PBR */
343	call	do_lba_read		/* read PBR */
344	jz	try_nextptn
345
346	/*
347	 * Check signature for valid bootcode and try to boot
348	 */
349	movb	BOOTADDR, %al		/* first byte non-zero */
350	testb	%al, %al
351	jz	1f
352	movw	BOOTADDR + MBR_MAGIC_OFFSET, %ax
3531:	cmp	$MBR_MAGIC, %ax
354	jne	try_nextptn
355do_boot:
356	pop	%dx			/* ... %dx - drive # */
357	movw	$lba_sector, %sp	/* ... %ecx:%ebx - boot partition LBA */
358	pop	%ebx
359	pop	%ecx
360	movl 	crc32_poly, %eax	/* X86_MBR_GPT_MAGIC */
361	jmp	BOOTADDR
362					/* THE END */
363
364try_nextptn:
365	addw	$GPTHDR_PTNSZ, %bp	/* move to next partition */
366	dec	%bx			/* ptncounter-- */
367	jnz	do_gpt_parse
368	set_err(ERR_NOOS)		/* no bootable partitions were found */
369	/* jmp	err_msg */		/* stop */
370
371/* Something went wrong...
372 * Output error code,
373 * reset disk subsystem - needed after read failure,
374 * and wait for user key
375 */
376err_msg:
377	movb	%al, errcod
378	movw	$errtxt, %si
379	call	message
380	pop	%dx			/* drive we errored on */
381	xor	%ax,%ax			/* only need %ah = 0 */
382	int	$0x13			/* reset disk subsystem */
383	int	$0x18			/* BIOS might ask for a key */
384					/* press and retry boot seq. */
3851:	sti
386	hlt
387	jmp	1b
388
389/*
390 * I hate #including source files, but the stuff below has to be at
391 * the correct absolute address.
392 * Clearly this could be done with a linker script.
393 */
394
395#if defined(COM_PORT) && defined(COM_BAUD)
396message:
397	pusha
398message_1:
399	lodsb
400	test	%al, %al
401	jz	3f
402	mov	COM_PORT_VAL, %dx
403	outb	%al, %dx
404	add	$5, %dl
4052:	inb	%dx
406	test	$0x40, %al
407	jz	2b
408	jmp	message_1
4093:	popa
410	ret
411#else
412#include <message.S>
413#endif
414
415#if 0
416#include <dump_eax.S>
417#endif
418
419#ifndef NO_CRC_CHECK
420/*
421 * The CRC32 calculation
422 *
423 * %si		address of block to hash
424 * %di		stop address
425 * %ax		scratch (but restored after exit)
426 * %dx		scratch (but restored after exit)
427 * %cx		counter
428 * %ebx		crc (returned)
429 */
430crc32:
431	push	%dx			/* preserve drive number */
432	push	%ax			/* preserve original CRC */
433
434	xorl	%ebx, %ebx
435	decl	%ebx			/* init value */
4361:
437	lodsb				/* load next message byte to %al */
438	movb	$8, %cl			/* set bit counter */
4392:
440	movb	%al, %dl
441	xorb	%bl, %dl		/* xoring with previous result */
442	shrl	$1, %ebx
443	shrb	$1, %al
444	testb	$1, %dl
445	jz	3f
446crc32_poly = . + 3			/* gross, but saves a few bytes */
447	xorl	$0xedb88320, %ebx	/* EFI CRC32 Polynomial */
4483:
449	loop	2b			/* loop over bits */
450	cmp	%di, %si		/* do we reached end of message? */
451	jne	1b
452	notl	%ebx			/* result correction */
453
454	pop	%ax
455	pop	%dx
456	ret
457#endif
458
459do_lba_read:
460	movw	$lba_dap, %si
461	movb	$0x42, %ah
462	int	$0x13			/* read */
463	ret
464
465/*
466 * Data definition block
467 */
468
469errtxt: .ascii	"Error "		/* runs into crlf if errcod set */
470errcod: .byte	0
471crlf:	.asciz	"\r\n"
472
473#ifndef NO_BANNER
474banner:	.asciz	"NetBSD GPT\r\n"
475#endif
476
477#if defined(COM_PORT) && defined(COM_BAUD)
478#define COM_DIVISOR (((COM_FREQ / COM_BAUD) + 8) / 16)
479com_args:
480	.byte	0x80			/* divisor latch enable */
481	.byte	+3			/* io_port + 3 */
482	.byte	COM_DIVISOR & 0xff
483	.byte	-3			/* io_port */
484	.byte	COM_DIVISOR >> 8	/* high baud */
485	.byte	+1			/* io_port + 1 */
486	.byte	0x03			/* 8 bit no parity */
487	.byte	+2			/* io_port + 3 */
488num_com_args = (. - com_args)/2
489#endif
490
491/*
492 * Control block for int-13 LBA read - Disk Address Packet
493 */
494lba_dap:
495	.byte	0x10			/* control block length */
496	.byte	0			/* reserved */
497lba_count:
498	.word	GPT_LBASZ		/* sector count */
499lba_rbuff:
500	.word	GPTBUFADDR		/* offset in segment */
501	.word	0			/* segment */
502lba_sector:
503	.quad	GPTHDR_BLKNO		/* sector # goes here... */
504
505efihdr_sign:
506	.ascii	"EFI PART"		/* GPT header signature */
507	.long	0x00010000		/* GPT header revision */
508sizeof_efihdr_sign = . - efihdr_sign
509
510/*
511 * Stuff from here on is overwritten by gpt/fdisk - the offset must not change
512 *
513 * Get amount of space to makefile can report it.
514 * (Unfortunately I can't seem to get the value reported when it is -ve)
515 */
516mbr_space = bootptn_guid - .
517
518/*
519 * GUID of the bootable partition. Patchable area.
520 * Default GUID used by installer for safety checks.
521 */
522	. = start + MBR_GPT_GUID_OFFSET
523bootptn_guid:
524	/* MBR_GPT_GUID_DEFAULT */
525	.long 0xeee69d04
526	.word 0x02f4
527	.word 0x11e0
528	.byte 0x8f,0x5d
529	.byte 0x00,0xe0,0x81,0x52,0x9a,0x6b
530
531/* space for mbr_dsn */
532	. = start + MBR_DSN_OFFSET
533	.long	0
534
535/* mbr_bootsel_magic */
536	. = start + MBR_BS_MAGIC_OFFSET
537	.word	0
538
539/* mbr partition table */
540	. = start + MBR_PART_OFFSET
541parttab:
542	.fill	0x40, 0x01, 0x00
543
544	. = start + MBR_MAGIC_OFFSET
545	.word	MBR_MAGIC
546
547/* zeroed data space */
548bss_off = 0
549bss_start = .
550#define BSS(name, size) name = bss_start + bss_off; bss_off = bss_off + size
551	BSS(dump_eax_buff, 16)
552	BSS(bss_end, 0)
553