From: David Miller <da...@davemloft.net> Date: Wed, 18 Mar 2009 13:55:22 -0700 (PDT)
> From: Robert Millan <r...@aybabtu.com> > Date: Wed, 18 Mar 2009 11:18:46 +0100 > > > On Sun, Mar 15, 2009 at 03:41:16PM -0700, David Miller wrote: > > > From: Robert Millan <r...@aybabtu.com> > > > Date: Sun, 15 Mar 2009 16:45:31 +0100 > > > > > > > It's not an absolute must that device names are unique. You can still > > > > identify partitions by their filesystem UUID or label, and in fact this > > > > is what our default scripts (grub-mkconfig) do anyway. > > > > > > Things like UUID and labels are not an option for the 512-byte > > > boot block where I have to know the exact OBP path of the boot > > > device, and this is what the GRUB kernel fetches from the > > > 'bootpath' environment variable to compose the root device > > > and path. > > > > What 512-byte boot block? > > The one I add in my sparc64 support changes. > > Included here for reference: BTW, I explained exactly how this 512 byte boot block works, in extreme detail, when I first posted my grub sparc64 patches. I am including it here _again_, please read it, it's informative, I promise, and I didn't post it the first time for my health! Thanks! -------------------- The first task is to get the first stage boot block going. There are two choices on how to do this. When you type "boot" for a block device, the firmware loads 7.5K starting at the second 512-byte block. If this block device is the third partition (the "all disk" partition in the Sun disk label) this bootblock starts after the disk label. Under Linux we really can only use 512 bytes of that boot block area, because it's possible for the filesystem superblock to show up as early as the very next 512 byte block. The firmware lets you put in the block some sparc executable image (tried by the firmware as 64-bit ELF, then 32-bit ELF, and finally as A.OUT) or tokenized forth. Since 1) we only have 512 bytes and 2) there are no fully GPL'd forth tokenizer implementations (the openbios folks use something that is BSD and MIT licensed) we'll need to go the sparc image route. The task of this 512 byte sequence of code is to load the next stage of the bootloader. For GRUB I've choosen a multi-tiered scheme similar to how the x86 stuff works. The first stage bootloader loads a single block from the disk and jumps to it. This block we load is actually a header block of the main boot loader binary, a second stage loader, which loads the rest of the image. This first stage loader therefore needs a few parameters. It needs to know the OF device path where the second stage header block resides. It needs to know the block to read, and finally it needs to know where to load that block and thus where to jump to it for execution. We'd also like to print some status messages to the screen while this happens and have at least some minimal error handling. Not a small feat in 512 bytes. We put everything in the text section, and the first thing we do is jump over our embedded data bits: .text .align 4 .globl _start _start: /* OF CIF entry point arrives in %o4 */ pic_base: call boot_continue mov %o4, CIF_REG The "CIF" is the client interface to openfirmware. Calls are made by initializing an array of cells (64-bits on sparc64) in memory which describe the call to be made, the input arguments, and the return values (if any). This value provided in %o4 is the OF entry point we jump to when making calls. The only register argument goes in %o0 and is the base of the aforementioned array of cells. The offsets into the bits coming up are defined in a GRUB boot.h header file so that tools can patch in values during bootblock installation. . = _start + GRUB_BOOT_MACHINE_VER_MAJ boot_version: .byte GRUB_BOOT_VERSION_MAJOR, GRUB_BOOT_VERSION_MINOR boot_path: . = _start + GRUB_BOOT_MACHINE_KERNEL_ADDRESS kernel_sector: .xword 2 kernel_address: .word GRUB_BOOT_MACHINE_KERNEL_ADDR The boot_version is just a version blob that various tools and sub-bootloaders could validate for compatibility if they wanted to. It is unused currently. The boot_path will be filled in by the boot block installation tools with the boot device OF path. kernel_sector and kernel_address tell where to load the image from the device into memory. Next, we have string constants we'll need to make OF calls and put messages onto the console: prom_finddev_name: .asciz "finddevice" prom_chosen_path: .asciz "/chosen" prom_getprop_name: .asciz "getprop" prom_stdout_name: .asciz "stdout" prom_write_name: .asciz "write" prom_bootpath_name: .asciz "bootpath" prom_open_name: .asciz "open" prom_seek_name: .asciz "seek" prom_read_name: .asciz "read" prom_exit_name: .asciz "exit" grub_name: .asciz "GRUB " To simplify things we'll write all of our code as position independent. There are other macros in the boot.h header which describe these register name macros such as CIF_REG, PIC_REG, etc. most are local registers which are not volatils across OF calls, so we can keep stable values in them. It also defines macros to compute absolute addresses of a symbol using this PIC_REG, into a register. The next chunk of code are helpers for doing OF calls with various sets of input and output arguments. prom_open_error: GET_ABS(prom_open_name, %o2) call console_write mov 4, %o3 /* fallthru */ prom_error: GET_ABS(prom_exit_name, %o0) /* fallthru */ /* %o0: OF call name * %o1: input arg 1 */ prom_call_1_1: mov 1, %g1 ba prom_call mov 1, %o5 /* %o2: message string * %o3: message length */ console_write: GET_ABS(prom_write_name, %o0) mov STDOUT_NODE_REG, %o1 /* fallthru */ /* %o0: OF call name * %o1: input arg 1 * %o2: input arg 2 * %o3: input arg 3 */ prom_call_3_1: mov 3, %g1 mov 1, %o5 /* fallthru */ /* %o0: OF call name * %g1: num inputs * %o5: num outputs * %o1-%o4: inputs */ prom_call: stx %o0, [%l1 + 0x00] stx %g1, [%l1 + 0x08] stx %o5, [%l1 + 0x10] stx %o1, [%l1 + 0x18] stx %o2, [%l1 + 0x20] stx %o3, [%l1 + 0x28] stx %o4, [%l1 + 0x30] jmpl CIF_REG, %g0 mov %l1, %o0 All of those routines are "call" invoked, and we do the CIF OF call using "jmpl" with no return register write in order to make the CIF OF call return to the stub caller, ie. a tail call. This way we don't need to allocate a register window here or anything complicated like that. Now for the code we jumped to at the _start header. Our first task is to save away the PIC_REG (%o7 was set by the initial "call" instruction, and will be equal to _start, we use it to reference our data blobs PC relative). Also we setup register %l1 which holds the base of the OF cell argument data block used above. SCRATCH_PAD is defined to 0x10000, which is guarenteed to be mapped by OF and outside of where we will be executing (which is at 0x4000). boot_continue: mov %o7, PIC_REG /* PIC base */ sethi %hi(SCRATCH_PAD), %l1 /* OF argument slots */ We need the console stdout handle to write console messages, this is done by finding the "/chosen" directory in the OF device tree, and fetching from there the "stdout" property which is a 32-bit handle. GET_ABS(prom_finddev_name, %o0) GET_ABS(prom_chosen_path, %o1) call prom_call_1_1 clr %o2 ldx [%l1 + 0x20], CHOSEN_NODE_REG brz CHOSEN_NODE_REG, prom_error GET_ABS(prom_getprop_name, %o0) mov 4, %g1 mov 1, %o5 mov CHOSEN_NODE_REG, %o1 GET_ABS(prom_stdout_name, %o2) add %l1, 256, %o3 mov 1024, %o4 call prom_call stx %g1, [%l1 + 256] lduw [%l1 + 256], STDOUT_NODE_REG brz,pn STDOUT_NODE_REG, prom_error Since we have very little space, the error handling has to be small. The "clr %o2" in the prom_call_1_1 invocation causes the return value cell slot (at %l1 + 0x20) to be cleared by the prom_call code. Zero is not a prom handle we'll get back, so if the slot stays zero we took an error. In the getprop call to get the 'stdout' handle, we rely upon OF providing us with zero'd memory outside of the boot block image. Now that we have the console output cookie, we can print out a message: GET_ABS(grub_name, %o2) call console_write mov 5, %o3 Next, we open up the boot device: GET_ABS(prom_open_name, %o0) GET_ABS(boot_path, %o1) call prom_call_1_1 clr %o2 ldx [%l1 + 0x20], BOOTDEV_REG brz,pn BOOTDEV_REG, prom_open_error We now seek to the appropriate block: GET_ABS(prom_seek_name, %o0) mov BOOTDEV_REG, %o1 clr %o2 LDX_ABS(kernel_sector, 0x00, %o3) call prom_call_3_1 sllx %o3, 9, %o3 and finally read the block into memory: GET_ABS(prom_read_name, %o0) mov BOOTDEV_REG, %o1 LDUW_ABS(kernel_address, 0x00, %o2) call prom_call_3_1 mov 512, %o3 This image will be an A.OUT image as well, so we jump into it right past the A.OUT header: LDUW_ABS(kernel_address, 0x00, %o2) jmpl %o2 + GRUB_BOOT_AOUT_HEADER_SIZE, %o7 nop 1: ba,a 1b Just for fun we put an endless loop after the jump in case it does return for some reason. We could, alternatively, call prom_error instead. Now skip to 4 bytes right before the end of the 512-byte block and write a signature cookie. . = _start + GRUB_BOOT_MACHINE_CODE_END /* the last 4 bytes in the sector 0 contain the signature */ .word GRUB_BOOT_MACHINE_SIGNATURE And that's the whole boot block implementation. This file is compiled normally into first an ELF image using GCC. Then we strip out all the symbols and other junk, and finally use objcopy to output an A.OUT object. It is exactly 512 bytes in size and the bootblock installer verifies this. The second stage header block code now can take advantage of all of the values the first stage has computed, such as the stdout handle, the handle for the boot device, etc. In the second stage image, it begins with assembler just like the first stage does above. But, it implements a block + length tuple list which grows down from the end of the block. This tells us where to read in the rest of the GRUB kernel from the actual file on the disk. The GRUB installer program links in with modules that understand how various filesystems work. It has a block traverser that can be run on arbitrary files. This is the mechanism used to build the block list which is patched into this second stage loader block list. I've tested both of these using a Sun LDOM guest, which is the fastest way to test low-level stuff like this since resetting (which you need to do every test boot attempt) is nearly instantaneous. The current task is to flesh out the installer programs and make sure the rest of the GRUB ieee1275 code is going to work properly on sparc. I already know some bits that will need some tweaking. For example, partitions on OF paths are indicated (aparently) by adding ":N" where N is a partition number. On sparc this "N" is instead a letter. _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org http://lists.gnu.org/mailman/listinfo/grub-devel