diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c index b678d5ebb8..7c8dce154a 100644 --- a/pc-bios/s390-ccw/bootmap.c +++ b/pc-bios/s390-ccw/bootmap.c @@ -444,6 +444,107 @@ static void ipl_scsi(void) zipl_run(prog_table_entry); /* no return */ } +/*********************************************************************** + * IPL El Torito ISO9660 image or DVD + */ + +static bool is_iso_bc_entry_compatible(IsoBcSection *s) +{ + return true; +} + +static void load_iso_bc_entry(IsoBcSection *load) +{ + IsoBcSection s = *load; + /* + * According to spec, extent for each file + * is padded and ISO_SECTOR_SIZE bytes aligned + */ + uint32_t blks_to_load = bswap16(s.sector_count) >> ET_SECTOR_SHIFT; + + read_iso_boot_image(bswap32(s.load_rba), + (void *)((uint64_t)bswap16(s.load_segment)), + blks_to_load); + + /* Trying to get PSW at zero address */ + if (*((uint64_t *)0) & IPL_PSW_MASK) { + jump_to_IPL_code((*((uint64_t *)0)) & 0x7fffffff); + } + + /* Try default linux start address */ + jump_to_IPL_code(KERN_IMAGE_START); +} + +static uint32_t find_iso_bc(void) +{ + IsoVolDesc *vd = (IsoVolDesc *)sec; + uint32_t block_num = ISO_PRIMARY_VD_SECTOR; + + if (virtio_read_many(block_num++, sec, 1)) { + /* If primary vd cannot be read, there is no boot catalog */ + return 0; + } + + while (is_iso_vd_valid(vd) && vd->type != VOL_DESC_TERMINATOR) { + if (vd->type == VOL_DESC_TYPE_BOOT) { + IsoVdElTorito *et = &vd->vd.boot; + + if (!_memcmp(&et->el_torito[0], el_torito_magic, 32)) { + return bswap32(et->bc_offset); + } + } + read_iso_sector(block_num++, sec, + "Failed to read ISO volume descriptor"); + } + + return 0; +} + +static IsoBcSection *find_iso_bc_entry(void) +{ + IsoBcEntry *e = (IsoBcEntry *)sec; + uint32_t offset = find_iso_bc(); + int i; + + if (!offset) { + return NULL; + } + + read_iso_sector(offset, sec, "Failed to read El Torito boot catalog"); + + if (!is_iso_bc_valid(e)) { + /* The validation entry is mandatory */ + virtio_panic("No valid boot catalog found!\n"); + return NULL; + } + + /* + * Each entry has 32 bytes size, so one sector cannot contain > 64 entries. + * We consider only boot catalogs with no more than 64 entries. + */ + for (i = 1; i < ISO_BC_ENTRY_PER_SECTOR; i++) { + if (e[i].id == ISO_BC_BOOTABLE_SECTION) { + if (is_iso_bc_entry_compatible(&e[i].body.sect)) { + return &e[i].body.sect; + } + } + } + + virtio_panic("No suitable boot entry found on ISO-9660 media!\n"); + + return NULL; +} + +static void ipl_iso_el_torito(void) +{ + IsoBcSection *s = find_iso_bc_entry(); + + if (s) { + load_iso_bc_entry(s); + /* no return */ + } +} + /*********************************************************************** * IPL starts here */ @@ -463,6 +564,12 @@ void zipl_load(void) ipl_scsi(); /* no return */ } + /* Check if we can boot as ISO media */ + if (virtio_guessed_disk_nature()) { + virtio_assume_iso9660(); + } + ipl_iso_el_torito(); + /* We have failed to follow the SCSI scheme, so */ if (virtio_guessed_disk_nature()) { sclp_print("Using guessed DASD geometry.\n"); diff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h index ab132e3579..4f6f767584 100644 --- a/pc-bios/s390-ccw/bootmap.h +++ b/pc-bios/s390-ccw/bootmap.h @@ -341,4 +341,195 @@ static inline bool magic_match(const void *data, const void *magic) return *((uint32_t *)data) == *((uint32_t *)magic); } +static inline int _memcmp(const void *s1, const void *s2, size_t n) +{ + int i; + const uint8_t *p1 = s1, *p2 = s2; + + for (i = 0; i < n; i++) { + if (p1[i] != p2[i]) { + return p1[i] > p2[i] ? 1 : -1; + } + } + + return 0; +} + +/* from include/qemu/bswap.h */ + +/* El Torito is always little-endian */ +static inline uint16_t bswap16(uint16_t x) +{ + return ((x & 0x00ff) << 8) | ((x & 0xff00) >> 8); +} + +static inline uint32_t bswap32(uint32_t x) +{ + return ((x & 0x000000ffU) << 24) | ((x & 0x0000ff00U) << 8) | + ((x & 0x00ff0000U) >> 8) | ((x & 0xff000000U) >> 24); +} + +static inline uint64_t bswap64(uint64_t x) +{ + return ((x & 0x00000000000000ffULL) << 56) | + ((x & 0x000000000000ff00ULL) << 40) | + ((x & 0x0000000000ff0000ULL) << 24) | + ((x & 0x00000000ff000000ULL) << 8) | + ((x & 0x000000ff00000000ULL) >> 8) | + ((x & 0x0000ff0000000000ULL) >> 24) | + ((x & 0x00ff000000000000ULL) >> 40) | + ((x & 0xff00000000000000ULL) >> 56); +} + +#define ISO_SECTOR_SIZE 2048 +/* El Torito specifies boot image size in 512 byte blocks */ +#define ET_SECTOR_SHIFT 2 +#define KERN_IMAGE_START 0x010000UL +#define PSW_MASK_64 0x0000000100000000ULL +#define PSW_MASK_32 0x0000000080000000ULL +#define IPL_PSW_MASK (PSW_MASK_32 | PSW_MASK_64) + +#define ISO_PRIMARY_VD_SECTOR 16 + +static inline void read_iso_sector(uint32_t block_offset, void *buf, + const char *errmsg) +{ + IPL_assert(virtio_read_many(block_offset, buf, 1) == 0, errmsg); +} + +static inline void read_iso_boot_image(uint32_t block_offset, void *load_addr, + uint32_t blks_to_load) +{ + IPL_assert(virtio_read_many(block_offset, load_addr, blks_to_load) == 0, + "Failed to read boot image!"); +} + +const uint8_t el_torito_magic[] = "EL TORITO SPECIFICATION" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + +typedef struct IsoDirHdr { + uint8_t dr_len; + uint8_t ear_len; + uint64_t ext_loc; + uint64_t data_len; + uint8_t recording_datetime[7]; + uint8_t file_flags; + uint8_t file_unit_size; + uint8_t gap_size; + uint32_t vol_seqnum; + uint8_t fileid_len; +} __attribute__((packed)) IsoDirHdr; + +typedef struct IsoVdElTorito { + uint8_t el_torito[32]; /* must contain el_torito_magic value */ + uint8_t unused0[32]; + uint32_t bc_offset; + uint8_t unused1[1974]; +} __attribute__((packed)) IsoVdElTorito; + +typedef struct IsoVdPrimary { + uint8_t unused1; + uint8_t sys_id[32]; + uint8_t vol_id[32]; + uint8_t unused2[8]; + uint64_t vol_space_size; + uint8_t unused3[32]; + uint32_t vol_set_size; + uint32_t vol_seqnum; + uint32_t log_block_size; + uint64_t path_table_size; + uint32_t l_path_table; + uint32_t opt_l_path_table; + uint32_t m_path_table; + uint32_t opt_m_path_table; + IsoDirHdr rootdir; + uint8_t root_null; + uint8_t reserved2[1858]; +} __attribute__((packed)) IsoVdPrimary; + +typedef struct IsoVolDesc { + uint8_t type; + uint8_t ident[5]; + uint8_t version; + union { + IsoVdElTorito boot; + IsoVdPrimary primary; + } vd; +} __attribute__((packed)) IsoVolDesc; + +const uint8_t vol_desc_magic[] = "CD001"; +#define VOL_DESC_TYPE_BOOT 0 +#define VOL_DESC_TYPE_PRIMARY 1 +#define VOL_DESC_TYPE_SUPPLEMENT 2 +#define VOL_DESC_TYPE_PARTITION 3 +#define VOL_DESC_TERMINATOR 255 + +static inline bool is_iso_vd_valid(IsoVolDesc *vd) +{ + return !_memcmp(&vd->ident[0], vol_desc_magic, 5) && + vd->version == 0x1 && + vd->type <= VOL_DESC_TYPE_PARTITION; +} + +typedef struct IsoBcValid { + uint8_t platform_id; + uint16_t reserved; + uint8_t id[24]; + uint16_t checksum; + uint8_t key[2]; +} __attribute__((packed)) IsoBcValid; + +typedef struct IsoBcSection { + uint8_t boot_type; + uint16_t load_segment; + uint8_t sys_type; + uint8_t unused; + uint16_t sector_count; + uint32_t load_rba; + uint8_t selection[20]; +} __attribute__((packed)) IsoBcSection; + +typedef struct IsoBcHdr { + uint8_t platform_id; + uint16_t sect_num; + uint8_t id[28]; +} __attribute__((packed)) IsoBcHdr; + +typedef struct IsoBcEntry { + uint8_t id; + union { + IsoBcValid valid; /* id == 0x01 */ + IsoBcSection sect; /* id == 0x88 || id == 0x0 */ + IsoBcHdr hdr; /* id == 0x90 || id == 0x91 */ + } body; +} __attribute__((packed)) IsoBcEntry; + +#define ISO_BC_ENTRY_PER_SECTOR (ISO_SECTOR_SIZE / sizeof(IsoBcEntry)) +#define ISO_BC_HDR_VALIDATION 0x01 +#define ISO_BC_BOOTABLE_SECTION 0x88 +#define ISO_BC_MAGIC_55 0x55 +#define ISO_BC_MAGIC_AA 0xaa +#define ISO_BC_PLATFORM_X86 0x0 +#define ISO_BC_PLATFORM_PPC 0x1 +#define ISO_BC_PLATFORM_MAC 0x2 + +static inline bool is_iso_bc_valid(IsoBcEntry *e) +{ + IsoBcValid *v = &e->body.valid; + + if (e->id != ISO_BC_HDR_VALIDATION) { + return false; + } + + if (v->platform_id != ISO_BC_PLATFORM_X86 && + v->platform_id != ISO_BC_PLATFORM_PPC && + v->platform_id != ISO_BC_PLATFORM_MAC) { + return false; + } + + return v->key[0] == ISO_BC_MAGIC_55 && + v->key[1] == ISO_BC_MAGIC_AA && + v->reserved == 0x0; +} + #endif /* _PC_BIOS_S390_CCW_BOOTMAP_H */ diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c index 57ff1b07ee..87aed38a95 100644 --- a/pc-bios/s390-ccw/virtio.c +++ b/pc-bios/s390-ccw/virtio.c @@ -278,6 +278,13 @@ void virtio_assume_scsi(void) blk_cfg.physical_block_exp = 0; } +void virtio_assume_iso9660(void) +{ + guessed_disk_nature = true; + blk_cfg.blk_size = 2048; + blk_cfg.physical_block_exp = 0; +} + void virtio_assume_eckd(void) { guessed_disk_nature = true; diff --git a/pc-bios/s390-ccw/virtio.h b/pc-bios/s390-ccw/virtio.h index 76c42f119d..afa01a885b 100644 --- a/pc-bios/s390-ccw/virtio.h +++ b/pc-bios/s390-ccw/virtio.h @@ -187,6 +187,7 @@ typedef struct VirtioBlkConfig { bool virtio_guessed_disk_nature(void); void virtio_assume_scsi(void); void virtio_assume_eckd(void); +void virtio_assume_iso9660(void); extern bool virtio_disk_is_scsi(void); extern bool virtio_disk_is_eckd(void);