fatboot.S revision 1.5 1 /* $NetBSD: fatboot.S,v 1.5 2025/02/28 09:07:12 andvar Exp $ */
2
3 /*-
4 * Copyright (c) 2007 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by David Laight.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 /*
33 * i386 partition boot code
34 * This version reads boot directly from a FAT16 filesystem on LBA
35 * Addressable media - eg USB media.
36 *
37 * The code is read to address 0:7c00 by the mbr code (in sector zero).
38 *
39 * We assume that the partition contains a 'boot parameter block' that
40 * correctly identifies the filesystem.
41 *
42 * On entry and exit the BIOS drive number is in %dl.
43 */
44
45 #include <machine/asm.h>
46 #include <sys/bootblock.h>
47
48 #ifndef FAT_ENTRY_SIZE
49 #error FAT_ENTRY_SIZE not defined
50 #endif
51
52 /* Support for FAT32 could be added - but hasn't been yet. */
53 #if FAT_ENTRY_SIZE != 16 && FAT_ENTRY_SIZE != 12
54 #error Unsupported FAT_ENTRY_SIZE value
55 #endif
56
57 #define FAT_SIZE_STR (('0'+ FAT_ENTRY_SIZE / 10) | ('0' + FAT_ENTRY_SIZE % 10) << 8)
58
59 #define PBR_AFTERBPB 62 /* BPB size in floppy master BR */
60
61 #ifdef TERSE_ERROR
62 /*
63 * Error codes. Done this way to save space.
64 */
65 #define ERR_READ 'R' /* Read error */
66 #define ERR_NO_BOOT 'B' /* No /boot */
67 #define ERR_NOT_FAT16 'F' /* Not a FAT16 filesystem */
68 #define ERR_NO_BOOT_MAGIC_2 'M' /* No magic in loaded /boot */
69
70 #define set_err(err) movb $err, %al
71
72 #else
73 #define set_err(err) mov $err, %ax
74 #endif
75
76 .text
77 .code16
78 ENTRY(start)
79 jmp start0
80 nop
81 oem_name: .ascii "NetBSD40" /* 8 bytes */
82 /* FAT16 BIOS/BOOT Parameter Block - see struct mbr_bpbFAT16 in bootblock.h */
83 #define bpb_bytes_per_sec /* .word 0 */ 0x0b /* bytes per sector */
84 #define bpb_sec_per_clust /* .byte 0 */ 0x0d /* sectors per cluster */
85 #define bpb_res_sectors /* .word 0 */ 0x0e /* number of reserved sectors */
86 #define bpb_FATs /* .byte 0 */ 0x10 /* number of FATs */
87 #define bpb_root_dir_ents /* .word 0 */ 0x11 /* number of root dir entries */
88 #define bpb_sectors /* .word 0 */ 0x13 /* total number of sectors */
89 #define bpb_media /* .byte 0 */ 0x15 /* media descriptor */
90 #define bpb_FAT_secs /* .word 0 */ 0x16 /* number of sectors per FAT */
91 #define bpb_sec_per_track /* .word 0 */ 0x18 /* sectors per track */
92 #define bpb_heads /* .word 0 */ 0x1a /* number of heads */
93 #define bpb_hidden_secs /* .long 0 */ 0x1c /* # of hidden sectors */
94 #define bpb_huge_sectors /* .long 0 */ 0x20 /* # of sectors if !bpbSectors*/
95 /* Extended boot area */
96 #define bs_drive_number /* .byte 0 */ 0x24 /* but we believe the BIOS ! */
97 #define bs_reserved_1 /* .byte 0 */ 0x25 /* */
98 #define bs_boot_sig /* .byte 0 */ 0x26 /* */
99 #define bs_volume_id /* .long 0 */ 0x27 /* Volume ID number */
100 #define bs_volume_label /* .space 11*/ 0x2b /* Volume label */
101 #define bs_file_sys_type /* .space 8 */ 0x36 /* "FAT16 " */
102
103 /* Some locals overlaying the end of the above */
104 #define sec_p_cl_w 0x2c /* 16bit bpb_sec_per_clust */
105 #define fat_sector 0x30 /* start of FAT in sectors */
106
107 . = start + PBR_AFTERBPB /* skip BPB */
108 start0:
109 xor %eax, %eax /* don't trust values of ds, es or ss */
110 mov %ax, %ds
111 mov %ax, %es
112 mov %ax, %ss
113 mov $start, %sp
114 mov %sp, %bp /* to access the pbp */
115 push %dx /* save drive at -2(%bp) */
116
117 /* We put the LBA bios command block on stack.
118 * Since we only want a 32bit sector number, stack a zero */
119 push %cs /* %cs is zero */
120 push %cs /* 64-bit for LBA read */
121
122 set_err(ERR_NOT_FAT16)
123 cmpl $'A'|'T'<<8|FAT_SIZE_STR<<16, bs_file_sys_type+1(%bp)
124 jne error
125
126 /* Add 'reserved' (inside ptn) to 'hidden' (ptn offset) */
127 mov bpb_res_sectors(%bp), %ax
128 addl bpb_hidden_secs(%bp), %eax
129 mov %eax, fat_sector(%bp) /* To get first sector of FAT */
130
131 #if FAT_ENTRY_SIZE == 12
132 /* Read the entire FAT */
133 push %eax
134 push %ds
135 push $fat_buffer
136 push $12 /* 12 sectors is assumed 6k */
137 call read_lba
138 #endif
139
140 /* Determine base of root directory */
141 movzbw bpb_FATs(%bp), %ax /* Count of FATs */
142 mulw bpb_FAT_secs(%bp) /* FAT size in %dx:%ax */
143 shl $16,%edx
144 xchg %ax,%dx /* FAT size now in %edx */
145 addl fat_sector(%bp), %edx /* Directory is after FATs */
146 pushl %edx /* Sector number of root dir */
147
148 push $0x1000 /* Read to 0x10000:0 */
149 pop %es /* Which we need in %es later */
150 push %es
151 push %cs /* Offset zero */
152
153 /* Convert the root directory size to sectors */
154 push %dx
155 mov bpb_root_dir_ents(%bp), %ax
156 mov $0x20, %dx
157 mul %dx
158 divw bpb_bytes_per_sec(%bp)
159 add $0xffff, %dx /* Set carry if remainder non-zero */
160 adc $0, %ax /* and round up the division */
161 pop %dx
162
163 /* Read in the entire root directory */
164 push %ax /* Sectors in root directory */
165 cwtl
166 addl %eax, %edx /* %edx now sector of first cluster */
167 call read_lba /* Read entire directory */
168
169 /* Scan directory for our file */
170 xor %di, %di
171 scan_dir:
172 mov $boot_filename, %si
173 mov $11, %cx
174 repz cmpsb
175 je found_boot
176 or $31,%di
177 inc %di
178 cmp %ch, %es:(%di) /* %ch is zero - test end of dir */
179 jz 1f
180 decw bpb_root_dir_ents(%bp)
181 jnz scan_dir
182 1: set_err(ERR_NO_BOOT)
183
184 error:
185 #ifdef TERSE_ERROR
186 movb %al, errcod
187 movw $errtxt, %si
188 call message
189 #else
190 push %ax
191 movw $errtxt, %si
192 call message
193 pop %si
194 call message
195 movw $newline, %si
196 call message
197 #endif
198 1: sti
199 hlt
200 jmp 1b
201
202 found_boot:
203 movzbl bpb_sec_per_clust(%bp), %eax
204 movw %ax, sec_p_cl_w(%bp)
205 add %ax, %ax
206 subl %eax, %edx /* 1st file sector is cluster 2 */
207 1: inc %cl /* Convert power of 2 ... */
208 shr $1, %ax /* ... to shift */
209 jnz 1b
210 dec %cx
211 dec %cx
212 movw %es:(26-11)(%di), %ax /* Cluster number for file start */
213 push %es /* We increment the 'segment' ... */
214 pop %di /* ... after each read, offset is 0 */
215
216 read_data_block:
217 mov %ax, %bx /* Save cluster number */
218 shl %cl, %eax /* Convert to sector number */
219 jz error /* Sanity bail-out */
220 add %edx, %eax
221 pushl %eax /* Sector to read */
222 push %di /* Target address segment! */
223 push $0
224 push sec_p_cl_w(%bp)
225 call read_lba /* Read a cluster */
226
227 /* Update read ptr for next cluster */
228 mov bpb_bytes_per_sec(%bp), %ax
229 shr $4, %ax /* x86 segment count */
230 shl %cl, %ax /* for a cluster */
231 add %ax, %di
232
233 /* Lookup FAT slot number in FAT table */
234 mov %bx, %ax /* Recover cluster number */
235 #if FAT_ENTRY_SIZE == 12
236 shr $1, %ax
237 jc 1f
238 add %ax, %bx
239 mov fat_buffer(%bx), %ax
240 and $0xf,%ah
241 jmp 2f
242 1: add %ax, %bx
243 mov fat_buffer(%bx), %ax
244 shr $4, %ax
245 2:
246 cmp $0x0fff, %ax
247 jb read_data_block
248 #else
249 push %dx
250 xor %dx, %dx
251 divw bpb_bytes_per_sec(%bp)
252 mov %dx, %bx /* Entry in FAT block */
253 pop %dx
254 cmp %ax, fat_cache
255 je lookup_fat
256
257 /* We must read a different chunk of the FAT */
258 mov %ax, fat_cache
259 cwtl
260 shl $1, %ax
261 addl fat_sector(%bp), %eax
262 push %eax
263 push %ds
264 push $fat_buffer
265 push $2 /* Always read 2 sectors of FAT */
266 call read_lba
267
268 /* Now use low part of cluster number to index FAT sector */
269 lookup_fat:
270 add %bx, %bx /* 2 bytes per entry... */
271 movzwl fat_buffer(%bx), %eax /* Next FAT slot */
272 cmp $0xfff0, %ax
273 jb read_data_block
274 #endif
275
276 /* Found end of FAT chain - must be EOF - leap into loaded code */
277 mov $0x1000, %ax
278 mov %ax, %es
279 cmpl $X86_BOOT_MAGIC_2, %es:4
280 je magic_ok
281 set_err(ERR_NO_BOOT_MAGIC_2)
282 err1: jmp error
283
284 /* Set parameters expected by /boot */
285 magic_ok:
286 mov bpb_hidden_secs(%bp), %ebx /* ptn base sector */
287 movb -2(%bp), %dl /* disk number */
288 mov $boot_params + 4, %si
289 push %es
290 push $0
291 lret
292
293 /* Read disk using on-stack int13-extension parameter block */
294 read_lba:
295 pop %ax /* Save rtn addr */
296 pushw $16 /* Stack ctl block length */
297 mov %sp, %si /* Address ctl block */
298 push %ax /* restack rtn addr */
299 pushal /* Save everything except %si and %ax*/
300 mov -2(%bp), %dl /* Disk # saved on entry */
301 movb $0x42, %ah
302 int $0x13
303 popal
304
305 set_err(ERR_READ)
306 jc err1
307 ret $12 /* Discard all except high LBA zeros */
308
309 /*
310 * I hate #including source files, but pbr_magic below has to be at
311 * the correct absolute address.
312 * Clearly this could be done with a linker script.
313 */
314
315 #include <message.S>
316 #if 0
317 #include <dump_eax.S>
318 dump_eax_buff = start
319 #endif
320
321 errtxt: .ascii "Error " /* runs into newline... */
322 errcod: .byte 0 /* ... if errcod set */
323 newline:
324 .asciz "\r\n"
325
326 #ifndef TERSE_ERROR
327 ERR_READ: .asciz "Disk read"
328 ERR_NO_BOOT: .asciz "No /boot"
329 ERR_NOT_FAT16: .ascii "Not FAT"
330 .word FAT_SIZE_STR
331 .asciz " ptn"
332 ERR_NO_BOOT_MAGIC_2: .asciz "No magic in /boot"
333 #endif
334
335 boot_filename: .ascii "BOOT "
336
337 space:
338 pbr_space = boot_params - .
339
340 /*
341 * Add magic number, with a zero sized patchable area - just in case something
342 * finds it and tries to update the area.
343 * Boot options can be set using 'installboot -e boot' so we don't need to
344 * use any of our valuable bytes.
345 */
346
347 . = _C_LABEL(start) + 0x1fe - 2 - 4 - 4
348 boot_params:
349 .long X86_BOOT_MAGIC_FAT
350 .long 1f - .
351 1:
352 fat_cache: .word 0xffff /* Sector number in buffer */
353 . = _C_LABEL(start) + 0x1fe
354 .word 0xaa55
355 fat_buffer: /* 2 sectors worth of FAT table */
356 /* 6k for FAT12 */
357