Commit 866cac91 authored by Maxim Samoylov's avatar Maxim Samoylov Committed by Cornelia Huck
Browse files

pc-bios/s390-ccw: ISO-9660 El Torito boot implementation



This patch enables boot from media formatted according to
ISO-9660 and El Torito bootable CD specification.

We try to boot from device as ISO-9660 media when SCSI IPL failed.

The first boot catalog entry with bootable flag is used.

ISO-9660 media with default 2048-bytes sector size only is supported.

Signed-off-by: default avatarMaxim Samoylov <max7255@linux.vnet.ibm.com>
Reviewed-by: default avatarDavid Hildenbrand <dahi@linux.vnet.ibm.com>
Signed-off-by: default avatarCornelia Huck <cornelia.huck@de.ibm.com>
parent 38150be8
Loading
Loading
Loading
Loading
+107 −0
Original line number Diff line number Diff line
@@ -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");
+191 −0
Original line number Diff line number Diff line
@@ -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 */
+7 −0
Original line number Diff line number Diff line
@@ -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;
+1 −0
Original line number Diff line number Diff line
@@ -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);