grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: GRUB device names wrt. ieee1275


From: phcoder
Subject: Re: GRUB device names wrt. ieee1275
Date: Sun, 22 Mar 2009 01:41:52 +0100
User-agent: Thunderbird 2.0.0.21 (X11/20090318)

Device name in boot block will anyway be written in it's complete raw OFW form, no matter how the rest of the grub2 accessing the disks. Using UUIDs in this place would be nice. On IRC you said that we have 120 bytes for the name. UUID is 16 or 32 bytes depending on FS. Is it possible to fit something like <read first sector> of every disk, <compare data at given offset>? I don't agree that all devices should be accessed exclusively by UUID. It makes things difficult to maintain because of internal naming. IMO quoting and escaping offers the best universal solution. I don't see why sth like
(/address@hidden,600000/address@hidden/address@hidden/address@hidden/address@hidden/address@hidden,1)
is a problem. Anyway you don't type such a thing manually and probably use tab-completition instead


David Miller wrote:
From: David Miller <address@hidden>
Date: Wed, 18 Mar 2009 13:55:22 -0700 (PDT)

From: Robert Millan <address@hidden>
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 <address@hidden>
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
address@hidden
http://lists.gnu.org/mailman/listinfo/grub-devel


--

Regards
Vladimir 'phcoder' Serbinenko




reply via email to

[Prev in Thread] Current Thread [Next in Thread]