2007-07-03 Alex Roman * conf/i386-pc.rmk: Add eltorito.mod * include/grub/i386/pc/biosdisk.h: Add DPTE structure * include/grub/i386/pc/loader.h: Add grub_eltorito_boot function declaration * kern/i386/pc/startup.S: Add grub_eltorito_boot function * disk/i386/pc/biosdisk.c: Initial support for CD-ROM drives as "cd?" * loader/i386/pc/eltorito.c: New file * include/grub/eltorito.h: New file Index: conf/i386-pc.rmk =================================================================== RCS file: /sources/grub/grub2/conf/i386-pc.rmk,v retrieving revision 1.81 diff -u -r1.81 i386-pc.rmk --- conf/i386-pc.rmk 23 Jun 2007 14:44:37 -0000 1.81 +++ conf/i386-pc.rmk 4 Jul 2007 03:06:38 -0000 @@ -131,7 +131,8 @@ pkgdata_MODULES = _chain.mod _linux.mod linux.mod normal.mod \ _multiboot.mod chain.mod multiboot.mod reboot.mod halt.mod \ vbe.mod vbetest.mod vbeinfo.mod video.mod gfxterm.mod \ - videotest.mod play.mod bitmap.mod tga.mod cpuid.mod serial.mod + videotest.mod play.mod bitmap.mod tga.mod cpuid.mod serial.mod \ + eltorito.mod # For _chain.mod. _chain_mod_SOURCES = loader/i386/pc/chainloader.c @@ -153,6 +154,11 @@ linux_mod_CFLAGS = $(COMMON_CFLAGS) linux_mod_LDFLAGS = $(COMMON_LDFLAGS) +# For eltorito.mod. +eltorito_mod_SOURCES = loader/i386/pc/eltorito.c +eltorito_mod_CFLAGS = $(COMMON_CFLAGS) +eltorito_mod_LDFLAGS = $(COMMON_LDFLAGS) + # For normal.mod. normal_mod_DEPENDENCIES = grub_script.tab.c grub_script.tab.h normal_mod_SOURCES = normal/arg.c normal/cmdline.c normal/command.c \ Index: include/grub/i386/pc/biosdisk.h =================================================================== RCS file: /sources/grub/grub2/include/grub/i386/pc/biosdisk.h,v retrieving revision 1.5 diff -u -r1.5 biosdisk.h --- include/grub/i386/pc/biosdisk.h 29 Jul 2006 10:11:01 -0000 1.5 +++ include/grub/i386/pc/biosdisk.h 4 Jul 2007 03:06:38 -0000 @@ -81,6 +81,24 @@ grub_uint64_t block; } __attribute__ ((packed)); +/* Device Parameter Table Extension */ +struct grub_biosdisk_dpte +{ + grub_uint16_t io_port_base; + grub_uint16_t ctrl_port_base; + grub_uint8_t head_register; /* upper nibble only */ + grub_uint8_t bios_vendor_specific; + grub_uint8_t irq_info; + grub_uint8_t block_count; + grub_uint8_t dma_info; + grub_uint8_t pio_info; + grub_uint16_t bios_hw_flags; + grub_uint16_t reserved; + grub_uint8_t revision; + grub_uint8_t checksum; +} __attribute__ ((packed)); + + int EXPORT_FUNC(grub_biosdisk_rw_int13_extensions) (int ah, int drive, void *dap); int EXPORT_FUNC(grub_biosdisk_rw_standard) (int ah, int drive, int coff, int hoff, int soff, int nsec, int segment); Index: include/grub/i386/pc/loader.h =================================================================== RCS file: /sources/grub/grub2/include/grub/i386/pc/loader.h,v retrieving revision 1.7 diff -u -r1.7 loader.h --- include/grub/i386/pc/loader.h 12 Sep 2004 12:20:52 -0000 1.7 +++ include/grub/i386/pc/loader.h 4 Jul 2007 03:06:38 -0000 @@ -39,6 +39,8 @@ struct grub_multiboot_info *mbi) __attribute__ ((noreturn)); + void EXPORT_FUNC(grub_eltorito_boot) (int drive, void *addr, int size) __attribute__ ((noreturn)); + /* It is necessary to export these functions, because normal mode commands reuse rescue mode commands. */ void grub_rescue_cmd_linux (int argc, char *argv[]); Index: kern/i386/pc/startup.S =================================================================== RCS file: /sources/grub/grub2/kern/i386/pc/startup.S,v retrieving revision 1.24 diff -u -r1.24 startup.S --- kern/i386/pc/startup.S 21 Jun 2007 21:01:11 -0000 1.24 +++ kern/i386/pc/startup.S 4 Jul 2007 03:06:38 -0000 @@ -2200,3 +2200,69 @@ popl %ebx popl %ebp ret + +/* + * grub_eltorito_boot ( + * int drive, // %eax + * void *addr. // %edx + * int size // %ecx + * ); + * + * This function will load *size* bytes from *addr* at 7C00h, save drive in + * %dl, clear the segment registers and perform a long jump to 0:7C00h. + * + * The address is a linear address and will be converted to seg:off format. + */ +FUNCTION(grub_eltorito_boot) + + /* Re-organize the contents of registers using the stack */ + pushl %ecx /* size */ + pushl %eax /* drive */ + pushl %edx /* addr */ + + popl %ebx /* addr */ + popl %edx /* drive */ + popl %ecx /* size */ + + xorl %eax, %eax + movl %eax, %edi + movl %eax, %esi + + /* Turn off Gate A20 */ + pusha + xorl %eax, %eax + call EXT_C(grub_gate_a20) + popa + call EXT_C(prot_to_real) + + .code16 + + /* rep movsb will copy %ecx bytes from %ds:%si to %es:%di */ + + /* compute "copy from" address in %ds:%si */ + movw %bx, %si + xorw %bx, %bx + shrl $4, %ebx + movw %bx, %ds + + /* load "copy to" address in %es:%di */ + xorw %ax, %ax + movw %ax, %di + movw $0x07C0, %ax + movw %ax, %es + + /* %ecx contains number of bytes, so go ahead and copy! */ + rep + movsb + + xorw %ax, %ax + movw %ax, %ds + movw %ax, %es + movw %ax, %fs + movw %ax, %gs + movw %ax, %ss + + /* jump to 0:7C00h */ + ljmp $0,$0x7C00 + + .code32 \ No newline at end of file Index: disk/i386/pc/biosdisk.c =================================================================== RCS file: /sources/grub/grub2/disk/i386/pc/biosdisk.c,v retrieving revision 1.9 diff -u -r1.9 biosdisk.c --- disk/i386/pc/biosdisk.c 29 Jul 2006 10:11:01 -0000 1.9 +++ disk/i386/pc/biosdisk.c 4 Jul 2007 03:06:38 -0000 @@ -31,7 +31,7 @@ { unsigned long drive; - if ((name[0] != 'f' && name[0] != 'h') || name[1] != 'd') + if ((name[0] != 'f' && name[0] != 'h' && name[0] != 'c') || name[1] != 'd') goto fail; drive = grub_strtoul (name + 2, 0, 10); @@ -40,6 +40,8 @@ if (name[0] == 'h') drive += 0x80; + else if (name[0] == 'c') + drive += 0xe0; return (int) drive ; @@ -52,8 +54,14 @@ grub_biosdisk_call_hook (int (*hook) (const char *name), int drive) { char name[10]; - - grub_sprintf (name, (drive & 0x80) ? "hd%d" : "fd%d", drive & (~0x80)); + + if (drive & 0xe0) + grub_sprintf (name, "cd%d", drive & (~0xe0)); + else if (drive & 0x80) + grub_sprintf (name, "hd%d", drive & (~0x80)); + else + grub_sprintf (name, "fd%d", drive); + return hook (name); } @@ -80,6 +88,8 @@ return 1; } + /* TODO: What to do for CD drives? */ + return 0; } @@ -94,7 +104,7 @@ if (drive < 0) return grub_errno; - disk->has_partitions = (drive & 0x80); + disk->has_partitions = ((drive & 0x80) && !(drive & 0xe0)); disk->id = drive; data = (struct grub_biosdisk_data *) grub_malloc (sizeof (*data)); @@ -133,17 +143,21 @@ } } - if (grub_biosdisk_get_diskinfo_standard (drive, - &data->cylinders, - &data->heads, - &data->sectors) != 0) - { - grub_free (data); - return grub_error (GRUB_ERR_BAD_DEVICE, "cannot get C/H/S values"); - } - - if (! total_sectors) - total_sectors = data->cylinders * data->heads * data->sectors; + /* We can't do this for CD drives */ + if ( ! (drive & 0xe0) ) + { + if (grub_biosdisk_get_diskinfo_standard (drive, + &data->cylinders, + &data->heads, + &data->sectors) != 0) + { + grub_free (data); + return grub_error (GRUB_ERR_BAD_DEVICE, "cannot get C/H/S values"); + } + + if (! total_sectors) + total_sectors = data->cylinders * data->heads * data->sectors; + } disk->total_sectors = total_sectors; disk->data = data; @@ -167,20 +181,23 @@ unsigned segment) { struct grub_biosdisk_data *data = disk->data; - + if (data->flags & GRUB_BIOSDISK_FLAG_LBA) { struct grub_biosdisk_dap *dap; + /* Place the DAP as the last thing in the scratch buffer */ dap = (struct grub_biosdisk_dap *) (GRUB_MEMORY_MACHINE_SCRATCH_ADDR - + (data->sectors - << GRUB_DISK_SECTOR_BITS)); + + GRUB_MEMORY_MACHINE_SCRATCH_SIZE + - sizeof (struct grub_biosdisk_dap)); + + grub_memset( dap, 0, sizeof (*dap) ); dap->length = sizeof (*dap); dap->reserved = 0; dap->blocks = size; dap->buffer = segment << 16; /* The format SEGMENT:ADDRESS. */ - dap->block = sector; - + dap->block = sector; + if (grub_biosdisk_rw_int13_extensions (cmd + 0x42, data->drive, dap)) { /* Fall back to the CHS mode. */ Index: loader/i386/pc/eltorito.c =================================================================== RCS file: loader/i386/pc/eltorito.c diff -N loader/i386/pc/eltorito.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ loader/i386/pc/eltorito.c 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,301 @@ +/* eltorito.c - ElTorito CD-ROM Boot Standard, Loader */ +/* +* GRUB -- GRand Unified Bootloader +* Copyright (C) 2003 Free Software Foundation, Inc. +* Copyright (C) 2003 NIIBE Yutaka +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * Decode and print a Boot Record Volume Descriptor structure + */ +static void +eltorito_dump_brvd( void * _brvd ) +{ + grub_eltorito_boot_record_vol_descr brvd = _brvd; + char scrap[6]; + + grub_memcpy (scrap, brvd->iso9660_identifier, 5); + scrap[5] = 0; + + grub_printf ("BOOT RECORD VOLUME DESCRIPTOR @ 0x%08x\n", (grub_uint32_t)brvd); + grub_printf ("boot_record_indicator : %d\n", brvd->boot_record_indicator); + grub_printf ("iso9660_identifier : %s\n", scrap); + grub_printf ("version : %d\n", brvd->version); + grub_printf ("boot_system_identifier : %s\n", brvd->boot_system_identifier); + grub_printf ("boot_catalog_pointer : 0x%08x\n", brvd->boot_catalog_pointer); +} + +/* + * Decode and print a Validation Entry structure + */ +static void +eltorito_dump_ve (void *_ve) +{ + grub_eltorito_validation_entry ve = _ve; + grub_printf ("VALIDATION ENTRY @ 0x%08x:\n", (grub_uint32_t)ve); + grub_printf ("header_id : %d\n", ve->header_id); + grub_printf ("platform_id : %d\n", ve->platform_id); + grub_printf ("id_string : %s\n", ve->id_string); + grub_printf ("checksum : 0x%04x\n", ve->checksum); + grub_printf ("key : 0x%04x\n", ve->key); +} + +/* + * Decode and print an Initial/Default Entry structure + */ +static void +eltorito_dump_de (void *_de) +{ + grub_eltorito_default_entry de = _de; + grub_printf ("INITIAL/DEFAULT ENTRY @ 0x%08x:\n", (grub_uint32_t)de); + grub_printf ("boot_indicator : 0x%x\n", de->boot_indicator); + grub_printf ("boot_media_type : %d\n", de->boot_media_type); + grub_printf ("load_segment : 0x%04x\n", de->load_segment); + grub_printf ("system_type: : %d\n", de->system_type); + grub_printf ("sector_count : %d\n", de->sector_count); + grub_printf ("load_rba : 0x%08x\n", de->load_rba); +} + +/* + * Dump a region in memory as hex bytes + */ +static void +eltorito_dump_memory (void *edmp_buf, int count) +{ + int i; + grub_uint8_t *b = (grub_uint8_t *)edmp_buf; + + for (i=0; idisk; + if (!disk) + { + return GRUB_ERR_BAD_DEVICE; + } + + /* + * Attempt to read the Boot Record Volume Descriptor which is located at + * block 0x11, according to spec. + */ + err = disk->dev->read (disk, 0x11, 1, + (char *)GRUB_MEMORY_MACHINE_SCRATCH_ADDR); + if (err != GRUB_ERR_NONE) + { + grub_printf ("Error reading the BRVD (err=0x%x)\n", err); + return GRUB_ERR_READ_ERROR; + } + + brvd = (grub_eltorito_boot_record_vol_descr)GRUB_MEMORY_MACHINE_SCRATCH_ADDR; + + /* For debugging, dump the BRVD */ + eltorito_dump_brvd (brvd); + + /* + * Make sure the BRVD's contents make sense: + * + boot_record_indicator needs to be 0 + * + version needs to be 1 + * + iso9660_identifier needs to be "CD001", not NULL terminated + * + boot_system_identifier needs to be "EL_TORITO_SPECIFICATION" + */ + if ( !(brvd->boot_record_indicator == 0 && + brvd->version == 1 && + grub_memcmp (brvd->iso9660_identifier, "CD001", 5) == 0 && + grub_memcmp (brvd->boot_system_identifier, "EL TORITO SPECIFICATION", 24) == 0) ) + { + grub_printf ("Read bad BRVD. Is the CD bootable?\n"); + grub_printf ("bri = %d\n", brvd->boot_record_indicator); + grub_printf ("version = %d\n", brvd->version); + grub_printf ("%d\n", grub_memcmp (brvd->iso9660_identifier, "CD001", 5)); + grub_printf ("%d\n", grub_memcmp (brvd->boot_system_identifier, "EL TORITO SPECIFICATION", 24)); + return GRUB_ERR_READ_ERROR; + } + + /* + * All good! The BRVD will provide a pointer to the boot catalog. + * Attempt to read that block from the CD device. + */ + err = disk->dev->read (disk, brvd->boot_catalog_pointer, 1, + (char *)GRUB_MEMORY_MACHINE_SCRATCH_ADDR); + if (err != GRUB_ERR_NONE) + { + grub_printf ("Error reading the boot catalog from block 0x%x\n", + brvd->boot_catalog_pointer); + return GRUB_ERR_READ_ERROR; + } + + /* The Validation Entry, VE, is the first entry in the Boot Catalog. */ + ve = (grub_eltorito_validation_entry)GRUB_MEMORY_MACHINE_SCRATCH_ADDR; + + /* For debugging, dump the VE. */ + eltorito_dump_ve (ve); + + /* + * Make sure the VE makes sense: + * + header_id must be 1 + * + reserved field must be 0 + * + key must be 0xaa55 + * TODO: check the checksum + */ + if ( !( ve->header_id == 0x01 && + ve->reserved == 0x00 && + ve->key == 0xaa55 + ) ) + { + grub_printf ("Error parsing the Validation Entry\n"); + return GRUB_ERR_READ_ERROR; + } + + /* Make sure we are running on the right platform. */ + if (ve->platform_id == 0x00) + { + /* All good. */ + } + else + { + /* Don't support non-x86 platforms yet, since it's not been ported yet. */ + return GRUB_ERR_NOT_IMPLEMENTED_YET; + } + + /* + * The Validation Entry makes sense. + * The next entry is the Default/Initial Entry (DE). + */ + de = (grub_eltorito_default_entry)(GRUB_MEMORY_MACHINE_SCRATCH_ADDR + 32); + + /* For debugging, dump the DE.*/ + eltorito_dump_de (de); + + /* + * Make sure the Default Entry is bootable. + * TODO: support entries that point to other entries, etc. + */ + if ( de->boot_indicator != 0x88 ) + { + grub_printf ("Error parsing the Default Entry\n"); + return GRUB_ERR_READ_ERROR; + } + + /* Figure out if we need to set up some sort of emulation. */ + if (de->boot_media_type == 0x00) /* No Emulation */ + { + /* All good, nothing to do. */ + } + else + { + /* Not so good, not sure what to do yet... */ + /* TODO: fail gratiously */ + return GRUB_ERR_NOT_IMPLEMENTED_YET; + } + + /* Figure out at what address we need to load the boot image. */ + if (de->load_segment == 0x00) + { + /* Load at the traditional 0x7c00. */ + } + else + { + /* + * Other segments not supported/tested yet... + * TODO: make this work + */ + return GRUB_ERR_NOT_IMPLEMENTED_YET; + } + + /* All good, let's read the boot image. */ + err = disk->dev->read (disk, de->load_rba, de->sector_count, + (char *)GRUB_MEMORY_MACHINE_SCRATCH_ADDR); + if (err != GRUB_ERR_NONE) + { + grub_printf ("Error reading the boot image from block 0x%x\n", + de->load_rba); + return GRUB_ERR_READ_ERROR; + } + + /* Okay, now let's try to boot... */ + grub_eltorito_boot (disk->id, (char*)GRUB_MEMORY_MACHINE_SCRATCH_ADDR, 2048); + + /* Should never get here... */ + grub_printf ("Something BAD has happened...\n"); + + /* Dump the first 2K of the boot image. */ + eltorito_dump_memory ((void*)0x7c00, 2048); + + return GRUB_ERR_NONE; +} + +GRUB_MOD_INIT(eltorito) +{ + (void)mod; /* To stop warning. */ + grub_register_command ("eltorito", grub_cmd_eltorito, GRUB_COMMAND_FLAG_BOTH, + "eltorito", "ElTorito CD-ROM Booting", 0); +} + +GRUB_MOD_FINI(eltorito) +{ + grub_unregister_command ("eltorito"); +} Index: include/grub/eltorito.h =================================================================== RCS file: include/grub/eltorito.h diff -N include/grub/eltorito.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ include/grub/eltorito.h 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,150 @@ +/* eltorito.h - ElTorito CD-ROM Boot Standard */ +/* +* GRUB -- GRand Unified Bootloader +* Copyright (C) 2003 Free Software Foundation, Inc. +* Copyright (C) 2003 NIIBE Yutaka +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef GRUB_ELTORITO_HEADER +#define GRUB_ELTORITO_HEADER + +struct eltorito_spec_packet_tag +{ + grub_uint8_t size; + grub_uint8_t media_type; + grub_uint8_t drive; + grub_uint8_t controller_index; + grub_uint32_t lba; + grub_uint16_t dev_spec; + grub_uint16_t user_buf; + grub_uint16_t load_segment; + grub_uint16_t sector_count; + grub_uint8_t cylinder_count_ch; + grub_uint8_t cylinder_count_cl; + grub_uint8_t head_count; + +} __attribute__ ((packed)); +typedef struct grub_eltorito_spec_packet_tag * eltorito_spec_packet; + +struct grub_eltorito_cmd_packet_tag +{ + grub_uint8_t size; + grub_uint8_t sector_count; + grub_uint32_t buffer; + grub_uint16_t start_sector; +} __attribute__ ((packed)); +typedef struct grub_eltorito_cmd_packet_tage * grub_eltorito_cmd_packet; + +struct grub_eltorito_boot_record_vol_descr_tag +{ + // Boot Record Indicator, must be 0x00 + grub_uint8_t boot_record_indicator; + + // ISO-9660 Identifier, must be "CD001" + grub_uint8_t iso9660_identifier[5]; + + // Version of this descriptor, must be 1 + grub_uint8_t version; + + // Boot System Identifier, must be "EL TORITO SPECIFICATION" padded + // with 0's + grub_uint8_t boot_system_identifier[23]; + + // Unused, must be 0. + grub_uint8_t unused1[41]; + + // Absolute pointer to first sector of Boot Catalog + grub_uint32_t boot_catalog_pointer; + + // Unused, must be 0. + grub_uint8_t unused2[1973]; +} __attribute__ ((packed)); +typedef struct grub_eltorito_boot_record_vol_descr_tag* grub_eltorito_boot_record_vol_descr; + +struct grub_eltorito_validation_entry_tag +{ + // Header ID, must be 0x01. + grub_uint8_t header_id; + + // Platform ID + // 0 = 80x86 + // 1 = Power PC + // 2 = Mac + grub_uint8_t platform_id; + + // Reserved, must be 0. + grub_uint16_t reserved; + + // ID string. This is intended to identify the manufacturer/developer + // of this CD-ROM. + char id_string[24]; + + // Checksum Word. This sum of all the words in this record should be 0. + grub_int16_t checksum; + + // Key word, must be 0x55AA. This value is included in the checksum + grub_uint16_t key; +} __attribute__ ((packed)); +typedef struct grub_eltorito_validation_entry_tag* grub_eltorito_validation_entry; + +struct grub_eltorito_default_entry_tag +{ + // Boot Indicator. + // 0x88 = Bootable, 0x00 = Not Bootable + grub_uint8_t boot_indicator; + + // Boot media type. This specifies what media the boot image is intended + // to emulate in bits 0-3 as follows, bits 4-7 are reserved and must be 0. + // + // Bits 0-3 count as follows: + // 0 = No Emulation + // 1 = 1.2 meg diskette + // 2 = 2.44 meg diskette + // 3 = 2.88 meg diskette + // 4 = Hard Disk (drive 80) + // + // 5-F Reserved, invalid at this time + grub_uint8_t boot_media_type; + + // Load Segment. This is the load segment for the initial boot image. If + // this value is 0 the system will use the traditional segment of 0x7C0. + // If this value is non-zero the system will use the specified segment. + // This applies to x86 architectures only. For "flat" model architectures + // (such as Motorola) this is the address divided by 0x10. + grub_uint16_t load_segment; + + // System Type. This must be a copy of byte 5 (System Type) from the + // Partition Table found in the boot image. + grub_uint8_t system_type; + + // Unused, must be 0 + grub_uint8_t unused1; + + // Sector Count. This is the number of virtual/emulated sectors the system + // will store at Load Segment during the initial boot procedure + grub_uint16_t sector_count; + + // Load RBA. This is the start address of the virtual disk. CD's use + // Relative/Logical block addressing + grub_uint32_t load_rba; + + // Unused, must be 0 + grub_uint8_t unused2[20]; +} __attribute__ ((packed)); +typedef struct grub_eltorito_default_entry_tag* grub_eltorito_default_entry; + +#endif /* GRUB_ELTORITO_HEADER */