add stage 0 for BIOS boot

main
anna 12 months ago
parent ed4c57a773
commit ecf4c94da4
Signed by: fef
GPG Key ID: 2585C2DC6D79B485

@ -0,0 +1,207 @@
use std::{env, ffi::OsStr, io, io::Write, path::Path, process::Command};
// We have to build stage 0 completely separately because it won't be part of
// the final binary per se (it's what loads the final binary in the first place).
// That's why we have this not-at-all overengineered custom build system.
fn main() {
let make = Make::from_env();
let stage0_srcs = ["src/boot/stage0.s"];
let stage0_objs = make.all(stage0_srcs);
make.link(Some("config/stage0.ld"), stage0_objs, "stage0.elf");
make.objcopy("stage0.elf", "mbr.bin");
make.end();
}
struct Make {
/// Base directory for all source files
src_base: String,
/// Base directory for all generated object files
obj_base: String,
/// Base directory for finished, pre-linked binaries
root_dir: String,
/// Command to use for assembling
cmd_as: String,
/// Command to use for linking
cmd_ld: String,
}
impl Make {
pub fn from_env() -> Self {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let out_dir = env::var("OUT_DIR").unwrap();
// XXX this will probably be removed in the final version, i'm only throwing
// the finished binaries in the target root directory because that's easier
// to access from gdb and various helper scripts to set up a disk image
let root_dir = Path::new(&out_dir)
.parent()
.and_then(Path::parent)
.and_then(Path::parent)
.and_then(Path::to_str)
.map(String::from)
.unwrap();
eprintln!("=== BussyBuild starting ===");
Self {
src_base: manifest_dir,
obj_base: out_dir,
root_dir,
cmd_as: env::var("AS").unwrap_or_else(|_| String::from("as")),
cmd_ld: env::var("LD")
.or_else(|_| env::var("RUSTC_LINKER"))
.unwrap_or_else(|_| "ld".to_owned()),
}
}
/// Build the specified source file (relative to `CARGO_MANIFEST_DIR`)
/// and return the absolute path to the object file.
/// The program used for building depends on the file extension:
///
/// | extension | program |
/// |:---------:|:-----------|
/// | `*.c` | C compiler |
/// | `*.s` | assembler |
pub fn make(&self, src_file: &str) -> String {
assert!(Path::new(src_file).is_relative());
let src_path = Path::new(&self.src_base).join(src_file);
let obj_path = Path::new(&self.obj_base).join(format!("{src_file}.o"));
println!("cargo:rerun-if-changed={}", src_path.to_str().unwrap());
let obj_dir = obj_path.parent().expect("output file has no parent??");
if !obj_dir.exists() {
std::fs::create_dir_all(obj_dir).unwrap();
}
if !obj_dir.is_dir() {
panic!("invalid output path");
}
let extension = src_path
.extension()
.expect("source file has no extension")
.to_str()
.expect("invalid (non-Unicode?) source file extension");
match extension {
"c" => unimplemented!(),
"s" => self.assemble(src_path.as_os_str(), obj_path.as_os_str()),
_ => panic!("unsupported source file extension \".{extension}\""),
}
String::from(obj_path.to_str().unwrap())
}
pub fn all<I, S>(&self, src_files: I) -> Vec<String>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
src_files
.into_iter()
.map(|f| self.make(f.as_ref()))
.collect()
}
pub fn link<I, S>(&self, script: Option<&str>, obj_files: I, out: &str)
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
eprintln!("=> LD {out}");
let mut cmd = Command::new(&self.cmd_ld);
if let Some(script) = script {
let script_path = Path::new(script);
let script_path = if script_path.is_relative() {
Path::new(&self.src_base).join(script_path)
} else {
script_path.to_path_buf()
};
let script = script_path.as_os_str().to_str().unwrap();
cmd.arg(format!("-T{script}"));
println!("cargo:rerun-if-changed={script}");
}
cmd.arg("-melf_i386")
.arg("-static")
.arg("-nostdlib")
.arg("-o")
.arg(Path::new(&self.root_dir).join(out));
for file in obj_files.into_iter() {
cmd.arg(file.as_ref());
}
eprintln!(" {cmd:?}");
let output = cmd.output().expect("failed to execute linker");
self.stdout_raw(output.stdout.as_ref());
if !output.status.success() {
self.stdout_raw(output.stderr.as_ref());
self.cmd_failed(&self.cmd_ld, output.status.code());
}
}
pub fn objcopy(&self, elf: &str, out: &str) {
eprintln!("=> OBJCOPY {elf}");
let elf = Path::new(&self.root_dir).join(elf);
let bin = Path::new(&self.root_dir).join(out);
let output = Command::new("objcopy")
.arg("-O")
.arg("binary")
.arg(&elf)
.arg(&bin)
.output()
.unwrap();
self.stdout_raw(output.stdout.as_ref());
if !output.status.success() {
self.stdout_raw(output.stderr.as_ref());
self.cmd_failed(&self.cmd_ld, output.status.code());
}
}
pub fn end(self) {
eprintln!("=== BussyBuild finished ===");
}
/// Assemble a source file to an object file.
fn assemble(&self, src_file: &OsStr, obj_file: &OsStr) {
eprintln!("=> AS {}", obj_file.to_str().unwrap());
let mut cmd = Command::new(&self.cmd_as);
cmd.arg("--32")
// Without this option, the linker mysteriously appends exactly 40 bytes
// of nonsense to the final output binary, even after stripping the ELF.
// I have no explanation for this, other than the GNU people being sadists.
.arg("-mx86-used-note=no")
.arg("-g")
.arg("-o")
.arg(obj_file)
.arg(src_file);
eprintln!(" {cmd:?}");
let output = cmd.output().expect("failed to execute assembler");
self.stdout_raw(output.stdout.as_ref());
if !output.status.success() {
self.stdout_raw(output.stderr.as_ref());
self.cmd_failed(&self.cmd_as, output.status.code());
}
}
fn cmd_failed(&self, cmd: &str, code: Option<i32>) -> ! {
let code = code
.map(|code| format!("{code}"))
.unwrap_or_else(|| "<unknown>".to_owned());
panic!("{cmd} exited with status code {code}",)
}
fn stdout_raw(&self, buf: &[u8]) {
io::stdout().write_all(buf).unwrap();
}
}

@ -0,0 +1,624 @@
/*
* fef
*
* S T A G E Z E R O
*
* a dystopian novel
*/
/*
* PREAMBLE
*
* This place is a message ...
* and part of a system of messages ...
* pay attention to it!
*
* Sending this message was important to us.
* We considered ourselves to be a powerful culture.
*
* This place is not a place of honor ...
* no highly esteemed deed is commemorated here ...
* nothing valued is here.
*
* What is here was dangerous and repulsive to us.
* This message is a warning about danger.
*
* The danger is still present, in your time, as it was in ours.
*
* This place is best shunned and left uninhabited.
*/
/*
* PROLOGUE
*
* Shitposting aside (and ignoring the fact that this entire file is a shitpost
* to begin with), bootsectors are kind of awkward because we have to squeeze
* quite a lot of stuff into a lousy 440 bytes.
* In the case of bussy, we do ALL of the following, in that order:
*
* 1. relocate ourselves to the end of low memory (0x7800:7c00 = 0x7fc00)
* 2. do some rudimentary sanity checks
* 3. read the boot drive's GPT header and validate its magic and CRC
* 4. read the boot drive's GPT and validate its CRC
* 5. search the GPT for the partition containing stage1
* 6. copy stage1 to the beginning of low memory (0x0000:0500 = 0x00500)
* 7. validate stage1's magic number and CRC
* 8. jump to the stage 1 entry point
*
* What's worse, all of that has to happen in Real Mode x86 assembly. Ugh.
* And account for all the stupid BIOS bugs since the 80s. Double ugh.
*
* These constraints force us to resort to some advanced space saving techniques
* because literally every single byte counts. As such, we reuse register
* values and side effects, replace instructions that take up multiple bytes
* with smaller ones, combine multiple operations into one, reorder code to be
* as compact as humanly possible, and so much more. At the very least, any CPU
* manufactured in this millennium will easily crunch through all of this within
* a matter of milliseconds at most, so performance is a non-issue.
*
* I tried to keep this code itself location-agnostic and only work with
* addresses defined in the linker script, but there are several places where
* that's just not possible. And since this is in no way useful other than the
* highly specific IBM PC environment, i figured it doesn't really matter if i
* hardcode some stuff. Sorry :)
*
* Keeping track of the stack can become rather difficult, because some values
* stay there for quite a while and there is NO %bp (see "error_slide").
* Therefore, i annotated all stack transactions with "lifetimes" in a separate
* comment towards the far end of the line. The number indicates the current
* stack height AFTER a push and BEFORE a pop. The `v` and `^` are "arrows"
* that denote whether it is a push or pop respectively:
* (1) they indicate the direction in which you have to scroll to get to the
* corresponding counterpart to the instruction
* (2) if you imagine memory as some sort of pillar where addresses grow in the
* upward direction, the "arrows" point in the direction that %sp moves
* (3) if you imagine a physical stack of things, the "arrows" match the
* direction of putting things on the stack (down) or lifting them off (up)
*
* Note that this MBR is also used for booting from ZFS drives (when ZFS is
* given control of the entire drive, rather than just a partition) because the
* ZFS folks had enough foresight to reserve the first couple of sectors, and
* OpenZFS is even nice enough to write a GPT to them when creating a pool.
* That table contains two entries; one for the ZFS stuff itself and one for
* 8 MiB of reserved space that ZFS appears to just leave alone and not touch
* at all. 8 MiB are way more than we'll ever need,
*/
.code16
.macro LOCAL name, type=function
.type \name , %\type
\name :
.endm
.macro GLOBL name, type
.global \name
LOCAL \name , \type
.endm
.macro END name
.size \name , . - \name
.endm
/*
* CHAPTER 1
*
* "Bootstrap Routine"
*
* 0x7c00
*/
.text
GLOBL _start
cli
cld
/*
* Step 1: Relocate to 0x7fc00
*
* Since we have to use segments to access anything above 0xffff anyway,
* this binary is statically linked against the physical load address of
* 0x7c00. That's not too bad though, because we have to initialize all
* segment registers one way or the other (can't rely on the BIOS doing
* that for us, especially because some of them appear to have this fun
* quirk where they jump to 0x07c0:0000 instead of 0x0000:7c00).
*
* This assumes that the entire memory used by stage0 (including .bss
* and stack) is <= 0x400 bytes in size, which is a pretty conservative
* guess. In practice, it's probably more like 0x300 at most.
*/
xor %cx, %cx
mov %cx, %ds
mov $0x7800, %bx /* (0x7fc00 - 0x7c00) >> 4 */
mov %bx, %es
/* %di should really be 0x7c00 (we're setting it to 0x7800 here), but a
* `mov` between two registers is 1 byte smaller than one with an imm16 */
mov %bx, %di
mov $3, %ch /* 0x300 words (0x7800 to 0x7e00) */
1: mov (%di), %ax /* use %ds (0x0000) for reading */
stosw /* use %es (0x7800) for writing */
loop 1b
/* this loop ends with 0xaa55 (MBR magic) in %ax, which we reuse later */
mov %bx, %ds
mov %bx, %ss
/* XXX gdb gets a stroke when we relocate ourselves */
ljmp $0x7800, $2f
/*
* Step 2: Rudimentary sanity checks
*/
/* Clear .bss and initialize %sp to the end of low memory. The previous
* loop ended with %di = 0x7e00, %cx = 0x0000, and %bx = 0x7800.
* `xchg %ax, r16` takes up only 1 byte as opposed to `xor r16, r16`
* which takes 2. It also moves the 0xaa55 to %bx, where we need it. */
2: xchg %ax, %bx
mov $2, %ch /* 0x200 bytes (0x7e00 to 0x8000) */
rep stosb
mov %di, %sp /* %sp = 0x7800:8000 = 0x80000 */
/* say hello */
mov $msg_loader_info, %si
call print
/* we kinda abuse %bp, see the "error slide" below */
mov $'1', %bp
/* check and save our boot drive number */
test $0x80, %dl /* only accept drives 0x80-0xff */
jz err_bad_boot_drive
/* check whether the BIOS supports IBM int13 extensions */
mov $0x41, %ah
not %bx /* %bx = 0x55aa */
int $0x13
mov $0, %dh /* now is the perfect opportunity to clear %dh */
jc err_no_int13_extensions
xor $0xaa55, %bx /* clear %bx on success */
jnz err_bad_boot_drive
/*
* Step 3: Read and validate the boot drive's GPT header
*/
/* get the boot drive's logical sector size */
mov $0x48, %ah
mov $drive_params, %si
movb $(drive_params_end - drive_params), (%si)
int $0x13 /* int13/48 clears %ah and CF on success */
mov $0, %al
adc %ax, %ax
jnz err_bad_boot_drive
/* read LBA 1 (where the GPT header is supposed to be) */
mov $0x50, %al /* we just checked that %ah is 0 */
mov %ax, %fs
xor %eax, %eax
xor %di, %di
inc %ax /* %eax = 1 */
mov %ax, %cx /* %cx = 1 */
call read_lba /* LBA = 0x00000001, dest = 0x0050:0000 */
/*
* Step 4: validate the GPT's magic number and CRCs
*/
/* we'll work with %fs quite a lot over the next lines, so much in fact
* that copying it to %ds and saving the segment overrides is worth it */
push %fs
pop %ds
/* check if the GPT signature is correct (%di points to sector) */
xor %si, %si
mov $gpt_magic, %di
mov $(gpt_magic_end - gpt_magic), %cl /* %cx was 1 */
repe cmpsb %es:(%di), %ds:(%si)
jne err_no_gpt
dec %ax /* %eax = 0 */
xchg 0x08(%si), %eax /* load CRC and replace it with zeroes */
push %ax /* save CRC[15:0] */ /* '1 v */
xor %si, %si
mov $0x5c, %cl
call crc32
pop %ax /* restore CRC[15:0] */ /* '1 ^ */
test %eax, %ebx
jnz err_bad_gpt_csum
/* store everything we need to rember from the GPT header (we assert there
* to be no more than 65535 partitions and that each entry is no more than
* 65535 bytes, because if that's not the case we're screwed anyway) */
mov $0x0048, %si
lods (%si), %eax /* first LBA of partition table */
mov (%si), %bx /* first LBA of partition table, high bits */
mov 0x04(%si), %cx /* number of partition entries */
mov 0x0c(%si), %edi /* CRC32 of the partition entry array */
mov 0x08(%si), %si /* size of each partition entry in bytes */
/* restore %ds to 0x7800 */
push %es
pop %ds
/* Calculate how many sectors we have to read for the partition table.
* This assumes that the entire partition table is less than 64 KiB. */
xchg %ax, %si /* %ax = entry size, %si = LBA[15:0] */
push %ax /* save entry size */ /* '1 v */
push %di /* save crc[15:0] */ /* '2 v */
push %dx /* save drive number */ /* '3 v */
mul %cx /* %dx:%ax = entry size * entry count */
jno start2 /* fail if %dx != 0 (`mul` overflowed %ax) */
/* pardon the sudden interruption; we have to insert the error slide
* here because that way we maximize the number of jumps with 1-byte
* relative addresses, which saves a couple of bytes here and there */
END _start
/*
* Welcome to the "error slide"!
* This ... thing relies on the fact that we don't quite use %bp for
* its intended purpose and instead always keep it at 0x31 (ASCII '1').
* The reason for this is that errors require a jump anyway, which
* takes up at least 2 bytes, while `inc %bp` requires only 1 byte.
* Therefore, we quite literally "encode" the error code through the
* jump instruction by means of jumping to the respective offset.
*/
LOCAL error_slide
/* err_gpt_too_big is carefully placed so that the `jno` above falls
* through exactly here on failure, avoiding an unconditional jump */
err_gpt_too_big:
inc %bp
err_bad_stage1_csum:
inc %bp
err_bad_stage1_magic:
inc %bp
err_bad_boot_drive:
inc %bp
err_no_int13_extensions:
inc %bp
err_bad_gpt_csum:
inc %bp
err_no_gpt:
inc %bp
err_read_failed:
inc %bp
err_no_stage1:
mov $msg_error, %si
/* 6 is the offset of the error number character within the error
* message, see the definition of msg_error below. We use %es
* because that's the only segment always pointing to 0x7800. */
or %bp, %es:6(%si)
call print
/*
* The GRUB uses int18 ("diskless boot hook" aka "start cassette BASIC")
* in its error routine, so we do the same. Issuing this interupt does
* any of the following (see <http://www.ctyme.com/intr/rb-2241.htm>):
* - start BASIC from the integrated ROM (on the OG IBM PC)
* - reboot the system
* - display some kind of error message
* - attempt a network boot
* - try some other boot device
* - nothing lmao
*/
int $0x18
/* if we make it here, it's over for good */
die: cli
hlt
jmp die
END error_slide
LOCAL start2
mov %ax, %di /* %di = table size in bytes */
divw (drive_params + 0x18) /* %ax = table size / sector size */
test %dx, %dx /* check remainder */
jz 1f
/* this increment can't overflow because we already checked that the
* dividend is <= 0xffff, so unless we're dealing with a 1-byte sector
* disk we can't possibly get this anywhere near the 16-bit limit */
inc %ax /* round up if modulus > 0 */
1: pop %dx /* restore drive number */ /* '3 ^ */
xchg %ax, %cx /* %ax = entry count, %cx = sector count */
xchg %ax, %si /* %ax = LBA[15:0], %si = entry count */
push %di /* save table size in bytes */ /* '3 v */
xor %di, %di
call read_lba
/* check the partition table array's CRC */
pop %cx /* restore table size in bytes */ /* '3 ^ */
push %si /* save entry count */ /* '3 v */
xor %si, %si
call crc32
pop %cx /* restore entry count */ /* '3 ^ */
pop %di /* restore CRC[15:0] */ /* '2 ^ */
and %ebx, %edi /* %edi = 0 (crc32 returns the inverted value) */
jnz err_bad_gpt_csum
/* search the partition table for our GUID */
pop %bx /* restore entry size */ /* '1 ^ */
sub $(bussy_guid_end - bussy_guid), %bx /* entry size -= sizeof(bussy_guid) */
/* this loop uses %ax for temporarily storing `entry count` and
* `sizeof(bussy_guid)` because xchg with %ax is only one byte */
1: push %cx /* save remaining entry count */ /* '1 v */
mov $bussy_guid, %si
mov $(bussy_guid_end - bussy_guid), %cx
repe cmpsb %es:(%di), %fs:(%si)
pop %cx /* restore remaining entry count */ /* '1 ^ */
je 2f /* found it */
add %bx, %di /* %di += entry size */
loopne 1b /* only loop if %di didn't wrap around */
jmp err_no_stage1
/* gotcha! now load the first 64 K from the partition */
2: push %dx /* save drive number */ /* '1 v */
xor %ax, %ax
mov $1, %dl /* %dh is known to be 0x00 */
divw (drive_params + 0x18) /* %ax = 0x10000 / sector size = sector count */
xchg %ax, %cx /* %cx = sector count (xchg saves 1 byte) */
mov %fs:0x20(%di), %eax /* %eax = LBA[31:0] */
mov %fs:0x24(%di), %bx /* %bx = LBA[47:32] */
xor %di, %di
pop %dx /* restore drive number */ /* '1 ^ */
call read_lba
/* check the stage1 header */
lods %fs:(%di), %ax /* offset 0x00: magic number */
cmp $0xacab, %ax
jne err_bad_stage1_magic
lods %fs:(%di), %ax /* offset 0x02: byte count for CRC */
xchg %cx, %ax
xor %eax, %eax
xchg %fs:(%di), %eax /* offset 0x04: CRC (replace with 0) */
push %ax /* save CRC[15:0] (crc32 clobbers %al) */ /* '1 v */
call crc32
pop %ax /* restore CRC[15:0] */ /* '1 ^ */
and %ebx, %eax /* check CRC and clear %eax */
jnz err_bad_stage1_csum
/*
* Step 8: Jump to stage1 (finally)
*/
ljmp $0x0000, $0x0508 /* entry point is right after the 8-byte header */
END start2
/*
* CHAPTER 2
*
* "Utility Subroutines"
*
* 0x7c00 + N
*/
/*
* Load %cl sectors, starting at LBA %bx:%eax, from disk %dl to %fs:%di.
* This subroutine preserves all registers except FLAGS,
* and automatically jumps to err_read_failed on failures.
*
* %eax: logical block address (sector number), bits 31:0
* %bx: logical block address (sector number), bits 47:32
* %cx: # of sectors to read (should be < 128)
* %dl: disk number
* %di: destination
* %fs: destination segment
*/
LOCAL read_lba
pusha
mov $dap, %si
movb $(dap_end - dap), (%si)
mov %cx, 2(%si)
mov %di, 4(%si)
mov %fs, %cx
mov %cx, 6(%si)
movl %eax, 8(%si)
mov %bx, 12(%si)
mov $0x42, %ah
int $0x13
/* You're supposed to retry several times if this fails because
* *floppies* tend to be quite unreliable. We just assume that
* nobody in their right mind would boot off of a floppy anymore
* unless they're into retro tech, in which case they wouldn't use
* a modern bootloader on their machine to begin with. HDDs/SSDs
* should be reliable enough that we can assume it always works. */
mov $0, %al
adc %ax, %ax /* CF and %ah are 0 on success */
jnz err_read_failed
popa
ret
END read_lba
/*
* Do a CCITT32 ANSI CRC (polynomial 0x04c11db7) of %cx bytes at %fs:%si
* and return THE COMPLEMENT (i.e. bitwise NOT of the CRC) in %ebx.
* Clobbers %al, %cx, and %si. Does not check for overflows (%si + %cx
* must be <= 0x10000). If %cx is 0, the size is 64 KiB (0x10000), in
* which case %si should also be 0 because of the missing wrap check.
*
* Stolen from "Hacker's Delight", second edition by Henry S. Warren, Jr.
* and painstakingly ported to x86 assembly with focus on minimum size.
* I have zero clue how CRC *actually* works, so there may be room for
* optimizations.
*
* %al: clobber
* %ebx: return value (ATTENTION: COMPLEMENT of CRC)
* %cx: byte count (clobber)
* %si: data (clobber)
* %fs: data segment
*/
LOCAL crc32
/* this is expressed as an imm8 w/ sign extension */
or $-1, %ebx /* %ebx = 0xffffffff */
1: lods %fs:(%si), %al
xor %al, %bl
push %cx
mov $8, %cx
2: shr %ebx
jnc 3f
xor $0xedb88320, %ebx
3: loop 2b
pop %cx
loop 1b
ret
END crc32
/*
* %si: address of string (terminated with last char | 0x80)
* %es: segment of string
*/
LOCAL print
/* according to <http://www.ctyme.com/intr/rb-0106.htm>, some BIOSes
* destroy %bp when the write causes the screen to scroll (???) */
pusha
1: lods %es:(%si), %al
/* Strings are ASCII only, so we know bit 7 (MSB) is always 0.
* We use that as a terminator to save space (stolen from BSD). */
btr $7, %ax
mov $0x0001, %bx /* page = 00, foreground color = 01 */
mov $0x0e, %ah /* print char, teletype mode */
int $0x10
jnc 1b /* XXX this breaks if BIOS trashes CF */
popa
ret
END print
/*
* CHAPTER 3
*
* "Constants and Variables"
*
* 0x7c00 + N + M
*/
.data
.macro TERMINATOR char
.byte \char | 0x80
.endm
/*
* The error_slide subroutine will replace the `\0` character with the
* actual error code. ATTENTION: the 6-byte offset is hardcoded!
*/
LOCAL msg_error, object
.ascii "error \0\r"
TERMINATOR '\n'
END msg_error
.section .rodata
LOCAL msg_loader_info, object
.ascii "BUSSY"
TERMINATOR ' '
END msg_loader_info
LOCAL gpt_magic, object
.ascii "EFI PART"
gpt_magic_end:
END gpt_magic
/* bussy boot partition GUID (61476542-4479-436f-7269-6d6521557755) */
LOCAL bussy_guid, object
.ascii "BeGayDoCrime!UwU"
bussy_guid_end:
END bussy_guid
/*
* CHAPTER 4
*
* "MBR Header"
*
* 0x7db8
*/
.section .header, "a", "progbits"
GLOBL mbr_uid, object
.long 0xffffffff
END mbr_uid
GLOBL mbr_rsvd, object
.word 0
END mbr_rsvd
/* first MBR Partition Table Entry contains the protective thingy */
GLOBL mbr_pte1, object
/* 0 */ .byte 0x80 /* boot indicator flag */
/* 1 */ .byte 0x02 /* starting head */
/* 2 */ .word 0x0002 /* starting cylinder[15:6]/sector[5:0] */
/* 4 */ .byte 0xee /* system id (GPT protective) */
/* 5 */ .byte 0xff /* ending head */
/* 6 */ .word 0xffff /* ending cylinder[15:6]/sector[5:0] */
/* 8 */ .word 0x0001 /* starting LBA, low word */
/* a */ .word 0x0000 /* starting LBA, high word */
/* c */ .word 0xffff /* number of sectors, low word */
/* e */ .word 0xffff /* number of sectors, high word */
END mbr_pte1
.macro PTE_EMPTY num
GLOBL mbr_pte\num , object
.rep 16
.byte 0
.endr
END mbr_pte\num
.endm
PTE_EMPTY 2
PTE_EMPTY 3
PTE_EMPTY 4
GLOBL mbr_magic, object
.word 0xaa55
END mbr_magic
mbr_end:
/*
* CHAPTER 5
*
* "Scratch and Stack Space"
*
* 0x7e00
*/
.section .bss
bss_start:
/* "Disk Address Packet" (for int13/42 "extended read") */
.align 8
LOCAL dap, object
/* 0 */ .byte 0 /* size of DAP (must be initialized to 0x10) */
/* 1 */ .byte 0 /* always 0 */
/* 2 */ .word 0 /* number of sectors to transfer (<= 127) */
/* 4 */ .word 0 /* destination offset (within segment, must be word aligned) */
/* 6 */ .word 0 /* destination segment */
/* 8 */ .quad 0 /* first LBA (48-bit) */
dap_end:
END dap
/*
* ATTENTION: this must stay the last .bss member because apparently some
* buggy BIOSes ignore the buffer size and write beyond the data structure
*/
.align 8
LOCAL drive_params, object
/* 00 */.word 0 /* size of buffer (must be initialized to 0x1a) */
/* 02 */.word 0 /* information flags (must be 0) */
/* 04 */.long 0 /* number of cylinders */
/* 08 */.long 0 /* number of heads */
/* 0c */.long 0 /* number of sectors per track */
/* 10 */.quad 0 /* number of total logical sectors */
/* 18 */.word 0 /* bytes per logical sector */
drive_params_end:
END drive_params
/* stack is initialized to 0x8000 */
Loading…
Cancel
Save