1/*        $NetBSD: cdboot.S,v 1.13 2021/12/05 02:47:01 msaitoh Exp $  */
2
3/*-
4 * Copyright (c) 2005 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Bang Jun-Young.
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 * This is a primary boot loader that loads a secondary boot loader
34 * directly from CD without performing floppy/hard disk emulation as
35 * described by the El Torito specification.
36 */
37
38#include <machine/asm.h>
39#include <sys/bootblock.h>
40
41#define BOOT_ADDR   0x7c00
42#define BLOCK_SIZE  2048                /* Default for ISO 9660 */
43#define VD_LBA                16                  /* LBA of Volume Descriptor (VD) */
44#define PVD_ADDR    end                 /* Where Primary VD is loaded */
45#define ROOTDIR_ADDR          end+BLOCK_SIZE      /* Where Root Directory is loaded */
46#define LOADER_ADDR SECONDARY_LOAD_ADDRESS
47
48#ifdef BOOT_FROM_FAT
49#define MBR_AFTERBPB          90                  /* BPB size in FAT32 partition BR */
50#else
51#define MBR_AFTERBPB          62                  /* BPB size in floppy master BR */
52#endif
53
54/*
55 * See src/sys/sys/bootblock.h for details.
56 */
57#define MBR_PART_COUNT        4
58#define MBR_PART_OFFSET       446
59#define MBR_PART_SIZE         16                  /* sizeof(struct mbr_partition) */
60
61/*
62 * Disk error codes
63 */
64#define ERROR_TIMEOUT         0x80
65
66/*
67 * Volume Descriptor types.
68 */
69#define VD_PRIMARY            1
70#define VD_SUPPLEMENTARY      2
71#define VD_TERMINATOR                   255
72
73/* Only actually used entries are listed below */
74
75/*
76 * Format of Primary Volume Descriptor (8.4)
77 */
78#define PVD_ROOT_DR 156       /* Offset of Root Directory Record */
79
80/*
81 * Format of Directory Record (9.1)
82 */
83#define DR_LEN                0
84#define DR_EXTENT   2
85#define DR_DATA_LEN 10
86#define DR_NAME_LEN 32
87#define DR_NAME               33
88
89          .text
90          .code16
91ENTRY(start)
92          jmp       start1
93
94          . = start + MBR_AFTERBPB      /* skip BPB */
95          . = start + MBR_DSN_OFFSET
96          .long     0
97
98/* mbr_bootsel_magic (not used here) */
99          . = start + MBR_BS_MAGIC_OFFSET
100          .word     0
101
102          . = start + MBR_PART_OFFSET
103          . = start + MBR_MAGIC_OFFSET
104pbr_magic:
105          .word     MBR_MAGIC
106          .fill     512                           /* reserve space for disklabel */
107start1:
108          jmp       1f
109          .balign   4
110          .long     X86_BOOT_MAGIC_1    /* checked by installboot & pbr code */
111boot_params:                                      /* space for patchable variables */
112          .long     1f - boot_params    /* length of this data area */
113#include <boot_params.S>
114          . = start1 + 0x80             /* Space for patching unknown params */
115
1161:        xorw      %ax, %ax
117          movw      %ax, %ds
118          movw      %ax, %es
119          movw      %ax, %ss
120          movw      $BOOT_ADDR, %sp
121          movw      %sp, %si
122          movw      $start, %di
123          movw      $BLOCK_SIZE/2, %cx
124          rep
125          movsw
126          ljmp      $0, $real_start
127
128real_start:
129          movb      %dl, boot_drive               /* Save boot drive number */
130
131#ifndef DISABLE_KEYPRESS
132          /*
133           * We can skip boot wait when:
134           *  - there's no hard disk present.
135           *  - there's no active partition in the MBR of the 1st hard disk.
136           */
137
138          /*
139           * Check presence of hard disks.
140           */
141          movw      $0x475, %si
142          movb      (%si), %al
143          testb     %al, %al
144          jz        boot_cdrom
145
146          /*
147           * Find the active partition from the MBR.
148           */
149          movw      $0x0201, %ax                  /* %al = number of sectors to read */
150          movw      $BOOT_ADDR, %bx               /* %es:%bx = data buffer */
151          movw      $0x0001, %cx                  /* %ch = low 8 bits of cylinder no */
152                                                  /* %cl = high 2 bits of cyl no & */
153                                                  /*       sector number */
154          movw      $0x0080, %dx                  /* %dh = head number */
155                                                  /* %dl = disk number */
156          int       $0x13                         /* Read MBR into memory */
157          jc        boot_cdrom                    /* CF set on error */
158
159          movb      $1, mbr_loaded
160          movb      $MBR_PART_COUNT, %cl
161          movw      $BOOT_ADDR+MBR_PART_OFFSET, %si
1621:
163          movb      (%si), %al
164          testb     $0x80, %al
165          jnz       found_active
166          addw      $MBR_PART_SIZE, %si
167          decb      %cl
168          testb     %cl, %cl
169          jnz       1b                            /* If 0, no active partition found */
170          jmp       boot_cdrom
171
172found_active:
173          movw      $str_press_key, %si
174          call      message
175next_second:
176          movw      $str_dot, %si
177          call      message
178          decb      wait_count
179          jz        boot_hard_disk
180          xorb      %ah, %ah            /* Get system time */
181          int       $0x1a
182          movw      %dx, %di            /* %cx:%dx = number of clock ticks */
183          addw      $19, %di            /* 19 ~= 18.2 Hz */
184wait_key:
185          movb      $1, %ah                       /* Check for keystroke */
186          int       $0x16
187          jz        not_avail           /* ZF clear if keystroke available */
188          xorb      %ah, %ah            /* Read key to flush keyboard buf */
189          int       $0x16
190          jmp       boot_cdrom
191not_avail:
192          xorb      %ah, %ah            /* Get system time */
193          int       $0x1a
194          cmpw      %dx, %di            /* Compare with saved time */
195          jnz       wait_key
196          jmp       next_second
197
198boot_hard_disk:
199          movw      $str_crlf, %si
200          call      message
201          cmpb      $1, mbr_loaded
202          jz        1f
203          movw      $0x0201, %ax                  /* %al = number of sectors to read */
204          movw      $BOOT_ADDR, %bx               /* %es:%bx = data buffer */
205          movw      $0x0001, %cx                  /* %ch = low 8 bits of cylinder no */
206                                                  /* %cl = high 2 bits of cyl no & */
207                                                  /*       sector number */
208          movw      $0x0080, %dx                  /* %dh = head number */
209                                                  /* %dl = disk number */
210          int       $0x13                         /* Read MBR into memory */
211          jc        panic                         /* CF set on error */
2121:
213          movw      %cs, %ax            /* Restore initial state */
214          movw      %ax, %ds
215          movw      %ax, %es
216          movw      $0x0080, %dx                  /* %dl = boot drive number */
217          jmp       $0, $BOOT_ADDR                /* Jump to MBR! */
218          jmp       panic                         /* This should be never executed */
219#endif /* !DISABLE_KEYPRESS */
220
221boot_cdrom:
222          movw      $str_banner, %si
223          call      message
224
225/* Read volume descriptor sectors until Primary descriptor found */
226          movl      $VD_LBA, %eax
227next_block:
228          movb      $1, %dh                       /* Number of sectors to read */
229          movl      $PVD_ADDR, %ebx
230          call      read_sectors
231          cmpb      $VD_PRIMARY, (%bx)  /* Is it Primary Volume Descriptor? */
232          jz        pvd_found
233          incl      %eax
234          cmpb      $VD_TERMINATOR, (%bx)
235          jnz       next_block
236          movw      $str_no_pvd, %si
237          call      message
238          jmp       panic
239
240/* Read all of root directory */
241pvd_found:
242          movw      $PVD_ADDR+PVD_ROOT_DR, %bx
243          movl      DR_EXTENT(%bx), %eax          /* LBA of the root directory */
244          movl      DR_DATA_LEN(%bx), %edx
245          shrl      $11, %edx           /* Convert to number of sectors */
246          movb      %dl, %dh            /*  ... and load it to %dh */
247          movl      $ROOTDIR_ADDR, %ebx
248          call      read_sectors
249
250/* Scan directory entries searching for /boot */
251next_entry:
252          cmpb      $0, DR_LEN(%bx)
253          jz        last_entry
254          movw      %bx, %si
255          addw      $DR_NAME, %si
256          movb      DR_NAME_LEN(%bx), %cl
257          movw      $str_loader, %di
2581:
259          movb      (%si), %al
260          cmpb      %al, (%di)
261          jnz       fail
262          incw      %si
263          incw      %di
264          decb      %cl
265          jnz       1b
266          jmp       load_loader
267fail:
268          addw      DR_LEN(%bx), %bx
269          jmp       next_entry
270last_entry:
271          movw      $str_no_loader, %si
272          call      message
273          jmp       panic
274
275/* Found /boot, read contents to 0x1000:0 */
276load_loader:
277          movl      DR_EXTENT(%bx), %eax
278          movl      DR_DATA_LEN(%bx), %edx
279          addl      $(BLOCK_SIZE-1), %edx         /* Convert file length to */
280          shrl      $11, %edx           /*  ... number of sectors */
281          movb      %dl, %dh
282          movl      $LOADER_ADDR, %ebx
283          call      read_sectors
284
285/* Finally call into code of /boot */
286          movl      $boot_params, %esi  /* Provide boot_params */
287          xorl      %edx, %edx
288          movb      boot_drive, %dl
289          xorl      %ebx, %ebx                    /* Zero sector number */
290          lcall     $LOADER_ADDR/16, $0
291          /* fall through on load failure */
292panic:
293          hlt
294          jmp       panic
295
296/*
297 * Read disk sector(s) into memory
298 *
299 * %eax = LBA of starting sector
300 * %ebx = buffer to store sectors
301 * %dh = number of sectors to read
302 *
303 * Long transfers are split onto multiple 64k reads
304 */
305#define MAX_SECTORS (0x10000/BLOCK_SIZE)
306read_sectors:
307          pushal
308          movl      %eax, edd_lba
309          shrl      $4, %ebx            /* Convert buffer addr to seg:0 */
310          movw      %bx, edd_segment
3111:        movb      %dh, edd_nsecs
312          cmpb      $MAX_SECTORS, %dh
313          jle       2f                            /* j if less than 64k */
314          movb      $MAX_SECTORS, edd_nsecs       /* Read 32 sectors - 64k bytes */
3152:        movb      boot_drive, %dl
316          movw      $edd_packet, %si
317read_again:
318          movb      $0x42, %ah
319          push      %dx                           /* bios shouldn't kill %dh, but ... */
320          int       $0x13
321          pop       %dx                           /* ... better safe than sorry! */
322          jc        read_fail
323          addw      $0x1000, edd_segment          /* Advance segment addr by 64k bytes */
324          addl      $MAX_SECTORS, edd_lba         /* And sector number to match */
325          sub       edd_nsecs, %dh                /* Number of sectors remaining */
326          jnz       1b
327          popal
328          ret
329
330read_fail:
331          cmpb      $ERROR_TIMEOUT, %ah
332          jz        read_again
333          movw      $str_read_error, %si
334          call      message
335          jmp       panic
336
337#include <message.S>
338
339edd_packet:
340edd_len:  .word     16
341edd_nsecs:          .word     0                   /* Number of sectors to transfer */
342edd_offset:         .word     0
343edd_segment:        .word     0
344edd_lba:  .quad     0
345
346wait_count:         .byte     6
347boot_drive:         .byte     0
348mbr_loaded:         .byte     0
349
350str_banner:         .ascii    "\r\nNetBSD/x86 cd9660 Primary Bootstrap"
351str_crlf: .asciz    "\r\n"
352str_press_key:      .asciz    "\r\nPress any key to boot from CD"
353str_dot:  .asciz    "."
354str_read_error:     .asciz    "Can't read CD"
355str_no_pvd:         .asciz    "Can't find Primary Volume Descriptor"
356str_no_loader:      .asciz    "Can't find /boot"
357str_loader:         .asciz    "BOOT.;1"
358
359/* Used to calculate free bytes */
360free_space = end - .
361
362          . = start + BLOCK_SIZE
363end:
364