| /* |
| * Copyright (C) 2007 Michael Brown <[email protected]>. |
| * |
| * 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 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. |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER ); |
| |
| /** |
| * @file |
| * |
| * El Torito bootable ISO image format |
| * |
| */ |
| |
| #include <stdint.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <realmode.h> |
| #include <bootsector.h> |
| #include <int13.h> |
| #include <gpxe/uaccess.h> |
| #include <gpxe/image.h> |
| #include <gpxe/segment.h> |
| #include <gpxe/ramdisk.h> |
| #include <gpxe/init.h> |
| |
| #define ISO9660_BLKSIZE 2048 |
| #define ELTORITO_VOL_DESC_OFFSET ( 17 * ISO9660_BLKSIZE ) |
| |
| /** An El Torito Boot Record Volume Descriptor */ |
| struct eltorito_vol_desc { |
| /** Boot record indicator; must be 0 */ |
| uint8_t record_indicator; |
| /** ISO-9660 identifier; must be "CD001" */ |
| uint8_t iso9660_id[5]; |
| /** Version, must be 1 */ |
| uint8_t version; |
| /** Boot system indicator; must be "EL TORITO SPECIFICATION" */ |
| uint8_t system_indicator[32]; |
| /** Unused */ |
| uint8_t unused[32]; |
| /** Boot catalog sector */ |
| uint32_t sector; |
| } __attribute__ (( packed )); |
| |
| /** An El Torito Boot Catalog Validation Entry */ |
| struct eltorito_validation_entry { |
| /** Header ID; must be 1 */ |
| uint8_t header_id; |
| /** Platform ID |
| * |
| * 0 = 80x86 |
| * 1 = PowerPC |
| * 2 = Mac |
| */ |
| uint8_t platform_id; |
| /** Reserved */ |
| uint16_t reserved; |
| /** ID string */ |
| uint8_t id_string[24]; |
| /** Checksum word */ |
| uint16_t checksum; |
| /** Signature; must be 0xaa55 */ |
| uint16_t signature; |
| } __attribute__ (( packed )); |
| |
| /** A bootable entry in the El Torito Boot Catalog */ |
| struct eltorito_boot_entry { |
| /** Boot indicator |
| * |
| * Must be @c ELTORITO_BOOTABLE for a bootable ISO image |
| */ |
| uint8_t indicator; |
| /** Media type |
| * |
| */ |
| uint8_t media_type; |
| /** Load segment */ |
| uint16_t load_segment; |
| /** System type */ |
| uint8_t filesystem; |
| /** Unused */ |
| uint8_t reserved_a; |
| /** Sector count */ |
| uint16_t length; |
| /** Starting sector */ |
| uint32_t start; |
| /** Unused */ |
| uint8_t reserved_b[20]; |
| } __attribute__ (( packed )); |
| |
| /** Boot indicator for a bootable ISO image */ |
| #define ELTORITO_BOOTABLE 0x88 |
| |
| /** El Torito media types */ |
| enum eltorito_media_type { |
| /** No emulation */ |
| ELTORITO_NO_EMULATION = 0, |
| }; |
| |
| struct image_type eltorito_image_type __image_type ( PROBE_NORMAL ); |
| |
| /** |
| * Calculate 16-bit word checksum |
| * |
| * @v data Data to checksum |
| * @v len Length (in bytes, must be even) |
| * @ret sum Checksum |
| */ |
| static unsigned int word_checksum ( void *data, size_t len ) { |
| uint16_t *words; |
| uint16_t sum = 0; |
| |
| for ( words = data ; len ; words++, len -= 2 ) { |
| sum += *words; |
| } |
| return sum; |
| } |
| |
| /** |
| * Execute El Torito image |
| * |
| * @v image El Torito image |
| * @ret rc Return status code |
| */ |
| static int eltorito_exec ( struct image *image ) { |
| struct ramdisk ramdisk; |
| struct int13_drive int13_drive; |
| unsigned int load_segment = image->priv.ul; |
| unsigned int load_offset = ( load_segment ? 0 : 0x7c00 ); |
| int rc; |
| |
| memset ( &ramdisk, 0, sizeof ( ramdisk ) ); |
| init_ramdisk ( &ramdisk, image->data, image->len, ISO9660_BLKSIZE ); |
| |
| memset ( &int13_drive, 0, sizeof ( int13_drive ) ); |
| int13_drive.blockdev = &ramdisk.blockdev; |
| register_int13_drive ( &int13_drive ); |
| |
| if ( ( rc = call_bootsector ( load_segment, load_offset, |
| int13_drive.drive ) ) != 0 ) { |
| DBGC ( image, "ElTorito %p boot failed: %s\n", |
| image, strerror ( rc ) ); |
| goto err; |
| } |
| |
| rc = -ECANCELED; /* -EIMPOSSIBLE */ |
| err: |
| unregister_int13_drive ( &int13_drive ); |
| return rc; |
| } |
| |
| /** |
| * Read and verify El Torito Boot Record Volume Descriptor |
| * |
| * @v image El Torito file |
| * @ret catalog_offset Offset of Boot Catalog |
| * @ret rc Return status code |
| */ |
| static int eltorito_read_voldesc ( struct image *image, |
| unsigned long *catalog_offset ) { |
| static const struct eltorito_vol_desc vol_desc_signature = { |
| .record_indicator = 0, |
| .iso9660_id = "CD001", |
| .version = 1, |
| .system_indicator = "EL TORITO SPECIFICATION", |
| }; |
| struct eltorito_vol_desc vol_desc; |
| |
| /* Sanity check */ |
| if ( image->len < ( ELTORITO_VOL_DESC_OFFSET + ISO9660_BLKSIZE ) ) { |
| DBGC ( image, "ElTorito %p too short\n", image ); |
| return -ENOEXEC; |
| } |
| |
| /* Read and verify Boot Record Volume Descriptor */ |
| copy_from_user ( &vol_desc, image->data, ELTORITO_VOL_DESC_OFFSET, |
| sizeof ( vol_desc ) ); |
| if ( memcmp ( &vol_desc, &vol_desc_signature, |
| offsetof ( typeof ( vol_desc ), sector ) ) != 0 ) { |
| DBGC ( image, "ElTorito %p invalid Boot Record Volume " |
| "Descriptor\n", image ); |
| return -ENOEXEC; |
| } |
| *catalog_offset = ( vol_desc.sector * ISO9660_BLKSIZE ); |
| |
| DBGC ( image, "ElTorito %p boot catalog at offset %#lx\n", |
| image, *catalog_offset ); |
| |
| return 0; |
| } |
| |
| /** |
| * Read and verify El Torito Boot Catalog |
| * |
| * @v image El Torito file |
| * @v catalog_offset Offset of Boot Catalog |
| * @ret boot_entry El Torito boot entry |
| * @ret rc Return status code |
| */ |
| static int eltorito_read_catalog ( struct image *image, |
| unsigned long catalog_offset, |
| struct eltorito_boot_entry *boot_entry ) { |
| struct eltorito_validation_entry validation_entry; |
| |
| /* Sanity check */ |
| if ( image->len < ( catalog_offset + ISO9660_BLKSIZE ) ) { |
| DBGC ( image, "ElTorito %p bad boot catalog offset %#lx\n", |
| image, catalog_offset ); |
| return -ENOEXEC; |
| } |
| |
| /* Read and verify the Validation Entry of the Boot Catalog */ |
| copy_from_user ( &validation_entry, image->data, catalog_offset, |
| sizeof ( validation_entry ) ); |
| if ( word_checksum ( &validation_entry, |
| sizeof ( validation_entry ) ) != 0 ) { |
| DBGC ( image, "ElTorito %p bad Validation Entry checksum\n", |
| image ); |
| return -ENOEXEC; |
| } |
| |
| /* Read and verify the Initial/Default entry */ |
| copy_from_user ( boot_entry, image->data, |
| ( catalog_offset + sizeof ( validation_entry ) ), |
| sizeof ( *boot_entry ) ); |
| if ( boot_entry->indicator != ELTORITO_BOOTABLE ) { |
| DBGC ( image, "ElTorito %p not bootable\n", image ); |
| return -ENOEXEC; |
| } |
| if ( boot_entry->media_type != ELTORITO_NO_EMULATION ) { |
| DBGC ( image, "ElTorito %p cannot support media type %d\n", |
| image, boot_entry->media_type ); |
| return -ENOTSUP; |
| } |
| |
| DBGC ( image, "ElTorito %p media type %d segment %04x\n", |
| image, boot_entry->media_type, boot_entry->load_segment ); |
| |
| return 0; |
| } |
| |
| /** |
| * Load El Torito virtual disk image into memory |
| * |
| * @v image El Torito file |
| * @v boot_entry El Torito boot entry |
| * @ret rc Return status code |
| */ |
| static int eltorito_load_disk ( struct image *image, |
| struct eltorito_boot_entry *boot_entry ) { |
| unsigned long start = ( boot_entry->start * ISO9660_BLKSIZE ); |
| unsigned long length = ( boot_entry->length * ISO9660_BLKSIZE ); |
| unsigned int load_segment; |
| userptr_t buffer; |
| int rc; |
| |
| /* Sanity check */ |
| if ( image->len < ( start + length ) ) { |
| DBGC ( image, "ElTorito %p virtual disk lies outside image\n", |
| image ); |
| return -ENOEXEC; |
| } |
| DBGC ( image, "ElTorito %p virtual disk at %#lx+%#lx\n", |
| image, start, length ); |
| |
| /* Calculate load address */ |
| load_segment = boot_entry->load_segment; |
| buffer = real_to_user ( load_segment, ( load_segment ? 0 : 0x7c00 ) ); |
| |
| /* Verify and prepare segment */ |
| if ( ( rc = prep_segment ( buffer, length, length ) ) != 0 ) { |
| DBGC ( image, "ElTorito %p could not prepare segment: %s\n", |
| image, strerror ( rc ) ); |
| return rc; |
| } |
| |
| /* Copy image to segment */ |
| memcpy_user ( buffer, 0, image->data, start, length ); |
| |
| return 0; |
| } |
| |
| /** |
| * Load El Torito image into memory |
| * |
| * @v image El Torito file |
| * @ret rc Return status code |
| */ |
| static int eltorito_load ( struct image *image ) { |
| struct eltorito_boot_entry boot_entry; |
| unsigned long bootcat_offset; |
| int rc; |
| |
| /* Read Boot Record Volume Descriptor, if present */ |
| if ( ( rc = eltorito_read_voldesc ( image, &bootcat_offset ) ) != 0 ) |
| return rc; |
| |
| /* This is an El Torito image, valid or otherwise */ |
| if ( ! image->type ) |
| image->type = &eltorito_image_type; |
| |
| /* Read Boot Catalog */ |
| if ( ( rc = eltorito_read_catalog ( image, bootcat_offset, |
| &boot_entry ) ) != 0 ) |
| return rc; |
| |
| /* Load Virtual Disk image */ |
| if ( ( rc = eltorito_load_disk ( image, &boot_entry ) ) != 0 ) |
| return rc; |
| |
| /* Record load segment in image private data field */ |
| image->priv.ul = boot_entry.load_segment; |
| |
| return 0; |
| } |
| |
| /** El Torito image type */ |
| struct image_type eltorito_image_type __image_type ( PROBE_NORMAL ) = { |
| .name = "El Torito", |
| .load = eltorito_load, |
| .exec = eltorito_exec, |
| }; |