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