From b1be0972f9d0e7b93a8906c977f26aca96a70e6a Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Thu, 17 Sep 2015 12:41:36 +0200 Subject: [PATCH 01/14] pc-bios/s390-ccw: add more disk layout checks Experiments showed possibility of few more "misconfigurations" in disk layout. They are reported now. Acked-by: Cornelia Huck Reviewed-by: David Hildenbrand Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/bootmap.c | 4 ++++ pc-bios/s390-ccw/bootmap.h | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c index 492530275d..ca60c333e3 100644 --- a/pc-bios/s390-ccw/bootmap.c +++ b/pc-bios/s390-ccw/bootmap.c @@ -415,7 +415,11 @@ static void ipl_scsi(void) /* The 0-th block (MBR) was already read into sec[] */ sclp_print("Using SCSI scheme.\n"); + debug_print_int("MBR Version", mbr->version_id); + IPL_check(mbr->version_id == 1, + "Unknown MBR layout version, assuming version 1"); debug_print_int("program table", mbr->blockptr.blockno); + IPL_assert(mbr->blockptr.blockno, "No Program Table"); /* Parse the program table */ read_block(mbr->blockptr.blockno, sec, diff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h index f98765b841..07e3b20b7a 100644 --- a/pc-bios/s390-ccw/bootmap.h +++ b/pc-bios/s390-ccw/bootmap.h @@ -273,6 +273,15 @@ static inline void IPL_assert(bool term, const char *message) } } +static inline void IPL_check(bool term, const char *message) +{ + if (!term) { + sclp_print("\n! WARNING: "); + sclp_print(message); + sclp_print(" !\n"); + } +} + static const unsigned char ebc2asc[256] = /* 0123456789abcdef0123456789abcdef */ "................................" /* 1F */ From c9262e8a84a29f22fbb5edde5d17f4f6166d5ae1 Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Thu, 17 Sep 2015 12:47:27 +0200 Subject: [PATCH 02/14] pc-bios/s390-ccw: virtio_panic -> panic This function has nothing to do with virtio. Reviewed-by: Cornelia Huck Reviewed-by: David Hildenbrand Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/bootmap.c | 8 ++++---- pc-bios/s390-ccw/bootmap.h | 2 +- pc-bios/s390-ccw/main.c | 8 ++++---- pc-bios/s390-ccw/s390-ccw.h | 2 +- pc-bios/s390-ccw/virtio.c | 8 ++++---- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c index ca60c333e3..711a518fb8 100644 --- a/pc-bios/s390-ccw/bootmap.c +++ b/pc-bios/s390-ccw/bootmap.c @@ -72,7 +72,7 @@ static void jump_to_IPL_code(uint64_t address) asm volatile("lghi 1,1\n\t" "diag 1,1,0x308\n\t" : : : "1", "memory"); - virtio_panic("\n! IPL returns !\n"); + panic("\n! IPL returns !\n"); } /*********************************************************************** @@ -617,7 +617,7 @@ static IsoBcSection *find_iso_bc_entry(void) if (!is_iso_bc_valid(e)) { /* The validation entry is mandatory */ - virtio_panic("No valid boot catalog found!\n"); + panic("No valid boot catalog found!\n"); return NULL; } @@ -633,7 +633,7 @@ static IsoBcSection *find_iso_bc_entry(void) } } - virtio_panic("No suitable boot entry found on ISO-9660 media!\n"); + panic("No suitable boot entry found on ISO-9660 media!\n"); return NULL; } @@ -701,5 +701,5 @@ void zipl_load(void) */ ipl_eckd_cdl(); - virtio_panic("\n* this can never happen *\n"); + panic("\n* this can never happen *\n"); } diff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h index 07e3b20b7a..e074587665 100644 --- a/pc-bios/s390-ccw/bootmap.h +++ b/pc-bios/s390-ccw/bootmap.h @@ -269,7 +269,7 @@ static inline void IPL_assert(bool term, const char *message) if (!term) { sclp_print("\n! "); sclp_print(message); - virtio_panic(" !\n"); /* no return */ + panic(" !\n"); /* no return */ } } diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c index d5fe4cea1d..11cb803e7b 100644 --- a/pc-bios/s390-ccw/main.c +++ b/pc-bios/s390-ccw/main.c @@ -31,7 +31,7 @@ void write_subsystem_identification(void) } -void virtio_panic(const char *string) +void panic(const char *string) { sclp_print(string); disabled_wait(); @@ -93,13 +93,13 @@ static void virtio_setup(uint64_t dev_info) } if (!found) { - virtio_panic("No virtio-blk device found!\n"); + panic("No virtio-blk device found!\n"); } virtio_setup_block(blk_schid); if (!virtio_ipl_disk_is_valid()) { - virtio_panic("No valid hard disk detected.\n"); + panic("No valid hard disk detected.\n"); } } @@ -111,6 +111,6 @@ int main(void) zipl_load(); /* no return */ - virtio_panic("Failed to load OS from hard disk\n"); + panic("Failed to load OS from hard disk\n"); return 0; /* make compiler happy */ } diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h index 5484c2a45c..c24f7202e4 100644 --- a/pc-bios/s390-ccw/s390-ccw.h +++ b/pc-bios/s390-ccw/s390-ccw.h @@ -50,7 +50,7 @@ void disabled_wait(void); void consume_sclp_int(void); /* main.c */ -void virtio_panic(const char *string); +void panic(const char *string); void write_subsystem_identification(void); extern char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE))); extern char ring_area[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE))); diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c index 87aed38a95..2d27b1d500 100644 --- a/pc-bios/s390-ccw/virtio.c +++ b/pc-bios/s390-ccw/virtio.c @@ -97,7 +97,7 @@ static void virtio_set_status(struct subchannel_id schid, { unsigned char status = dev_addr; if (run_ccw(schid, CCW_CMD_WRITE_STATUS, &status, sizeof(status))) { - virtio_panic("Could not write status to host!\n"); + panic("Could not write status to host!\n"); } } @@ -251,7 +251,7 @@ unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2, sclp_print("."); status = virtio_read_many(sec, (void *)addr, sec_num); if (status) { - virtio_panic("I/O Error"); + panic("I/O Error"); } addr += sec_num * virtio_get_block_size(); @@ -381,10 +381,10 @@ void virtio_setup_block(struct subchannel_id schid) config.index = 0; if (run_ccw(schid, CCW_CMD_READ_VQ_CONF, &config, sizeof(config))) { - virtio_panic("Could not get block device VQ configuration\n"); + panic("Could not get block device VQ configuration\n"); } if (run_ccw(schid, CCW_CMD_READ_CONF, &blk_cfg, sizeof(blk_cfg))) { - virtio_panic("Could not get block device configuration\n"); + panic("Could not get block device configuration\n"); } vring_init(&block, config.num, ring_area, KVM_S390_VIRTIO_RING_ALIGN); From dc25e843f67fbc9cab5d9525ba1b9283783fc11b Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Tue, 27 Oct 2015 09:49:27 +0100 Subject: [PATCH 03/14] pc-bios/s390-ccw: add utility functions and "export" some others Add several utility functions, make IPL_check and IPL_assert generally available, etc. Acked-by: Cornelia Huck Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/bootmap.h | 18 ----------------- pc-bios/s390-ccw/s390-ccw.h | 39 +++++++++++++++++++++++++++++++++++++ pc-bios/s390-ccw/virtio.c | 2 +- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h index e074587665..bea168714b 100644 --- a/pc-bios/s390-ccw/bootmap.h +++ b/pc-bios/s390-ccw/bootmap.h @@ -264,24 +264,6 @@ typedef enum { /* utility code below */ -static inline void IPL_assert(bool term, const char *message) -{ - if (!term) { - sclp_print("\n! "); - sclp_print(message); - panic(" !\n"); /* no return */ - } -} - -static inline void IPL_check(bool term, const char *message) -{ - if (!term) { - sclp_print("\n! WARNING: "); - sclp_print(message); - sclp_print(" !\n"); - } -} - static const unsigned char ebc2asc[256] = /* 0123456789abcdef0123456789abcdef */ "................................" /* 1F */ diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h index c24f7202e4..51359113ae 100644 --- a/pc-bios/s390-ccw/s390-ccw.h +++ b/pc-bios/s390-ccw/s390-ccw.h @@ -67,6 +67,7 @@ bool virtio_is_blk(struct subchannel_id schid); void virtio_setup_block(struct subchannel_id schid); int virtio_read(ulong sector, void *load_addr); int enable_mss_facility(void); +ulong get_second(void); /* bootmap.c */ void zipl_load(void); @@ -143,4 +144,42 @@ static inline void yield(void) #define MAX_SECTOR_SIZE 4096 +static inline void sleep(unsigned int seconds) +{ + ulong target = get_second() + seconds; + + while (get_second() < target) { + yield(); + } +} + +static inline void *memcpy(void *s1, const void *s2, size_t n) +{ + uint8_t *p1 = s1; + const uint8_t *p2 = s2; + + while (n--) { + p1[n] = p2[n]; + } + return s1; +} + +static inline void IPL_assert(bool term, const char *message) +{ + if (!term) { + sclp_print("\n! "); + sclp_print(message); + panic(" !\n"); /* no return */ + } +} + +static inline void IPL_check(bool term, const char *message) +{ + if (!term) { + sclp_print("\n! WARNING: "); + sclp_print(message); + sclp_print(" !\n"); + } +} + #endif /* S390_CCW_H */ diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c index 2d27b1d500..da51fb7be0 100644 --- a/pc-bios/s390-ccw/virtio.c +++ b/pc-bios/s390-ccw/virtio.c @@ -162,7 +162,7 @@ static u64 get_clock(void) return r; } -static ulong get_second(void) +ulong get_second(void) { return (get_clock() >> 12) / 1000000; } From b88d7fa5900d5309cc908901e7fed233a6eaa560 Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Tue, 10 Nov 2015 14:10:20 +0100 Subject: [PATCH 04/14] pc-bios/s390-ccw: qemuize types Turn [the most of] existing declarations from struct type_name { ... }; into struct TypeName { ... }; typedef struct TypeName TypeName; and make use of them. Also switch u{8,16,32,64} to uint{8,16,32,64}_t. Acked-by: Cornelia Huck Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/main.c | 8 +- pc-bios/s390-ccw/s390-ccw.h | 12 ++- pc-bios/s390-ccw/virtio.c | 46 ++++++------ pc-bios/s390-ccw/virtio.h | 143 ++++++++++++++++++++---------------- 4 files changed, 115 insertions(+), 94 deletions(-) diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c index 11cb803e7b..7f192f3a6d 100644 --- a/pc-bios/s390-ccw/main.c +++ b/pc-bios/s390-ccw/main.c @@ -14,7 +14,7 @@ char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE))); char ring_area[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE))); uint64_t boot_value; -static struct subchannel_id blk_schid = { .one = 1 }; +static SubChannelId blk_schid = { .one = 1 }; /* * Priniciples of Operations (SA22-7832-09) chapter 17 requires that @@ -23,7 +23,7 @@ static struct subchannel_id blk_schid = { .one = 1 }; */ void write_subsystem_identification(void) { - struct subchannel_id *schid = (struct subchannel_id *) 184; + SubChannelId *schid = (SubChannelId *) 184; uint32_t *zeroes = (uint32_t *) 188; *schid = blk_schid; @@ -38,7 +38,7 @@ void panic(const char *string) while (1) { } } -static bool find_dev(struct schib *schib, int dev_no) +static bool find_dev(Schib *schib, int dev_no) { int i, r; @@ -64,7 +64,7 @@ static bool find_dev(struct schib *schib, int dev_no) static void virtio_setup(uint64_t dev_info) { - struct schib schib; + Schib schib; int ssid; bool found = false; uint16_t dev_no; diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h index 51359113ae..a5c0684c00 100644 --- a/pc-bios/s390-ccw/s390-ccw.h +++ b/pc-bios/s390-ccw/s390-ccw.h @@ -45,6 +45,14 @@ typedef unsigned long long __u64; #include "cio.h" +typedef struct irb Irb; +typedef struct ccw1 Ccw1; +typedef struct cmd_orb CmdOrb; +typedef struct schib Schib; +typedef struct chsc_area_sda ChscAreaSda; +typedef struct senseid SenseId; +typedef struct subchannel_id SubChannelId; + /* start.s */ void disabled_wait(void); void consume_sclp_int(void); @@ -63,8 +71,8 @@ void sclp_setup(void); /* virtio.c */ unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2, ulong subchan_id, void *load_addr); -bool virtio_is_blk(struct subchannel_id schid); -void virtio_setup_block(struct subchannel_id schid); +bool virtio_is_blk(SubChannelId schid); +void virtio_setup_block(SubChannelId schid); int virtio_read(ulong sector, void *load_addr); int enable_mss_facility(void); ulong get_second(void); diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c index da51fb7be0..64c6e070de 100644 --- a/pc-bios/s390-ccw/virtio.c +++ b/pc-bios/s390-ccw/virtio.c @@ -11,7 +11,7 @@ #include "s390-ccw.h" #include "virtio.h" -static struct vring block; +static VRing block; static char chsc_page[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE))); @@ -31,7 +31,7 @@ static long kvm_hypercall(unsigned long nr, unsigned long param1, return retval; } -static void virtio_notify(struct subchannel_id schid) +static void virtio_notify(SubChannelId schid) { kvm_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY, *(u32 *)&schid, 0); } @@ -40,9 +40,9 @@ static void virtio_notify(struct subchannel_id schid) * Virtio functions * ***********************************************/ -static int drain_irqs(struct subchannel_id schid) +static int drain_irqs(SubChannelId schid) { - struct irb irb = {}; + Irb irb = {}; int r = 0; while (1) { @@ -59,11 +59,11 @@ static int drain_irqs(struct subchannel_id schid) } } -static int run_ccw(struct subchannel_id schid, int cmd, void *ptr, int len) +static int run_ccw(SubChannelId schid, int cmd, void *ptr, int len) { - struct ccw1 ccw = {}; - struct cmd_orb orb = {}; - struct schib schib; + Ccw1 ccw = {}; + CmdOrb orb = {}; + Schib schib; int r; /* start command processing */ @@ -92,7 +92,7 @@ static int run_ccw(struct subchannel_id schid, int cmd, void *ptr, int len) return r; } -static void virtio_set_status(struct subchannel_id schid, +static void virtio_set_status(SubChannelId schid, unsigned long dev_addr) { unsigned char status = dev_addr; @@ -101,18 +101,18 @@ static void virtio_set_status(struct subchannel_id schid, } } -static void virtio_reset(struct subchannel_id schid) +static void virtio_reset(SubChannelId schid) { run_ccw(schid, CCW_CMD_VDEV_RESET, NULL, 0); } -static void vring_init(struct vring *vr, unsigned int num, void *p, +static void vring_init(VRing *vr, unsigned int num, void *p, unsigned long align) { debug_print_addr("init p", p); vr->num = num; vr->desc = p; - vr->avail = p + num*sizeof(struct vring_desc); + vr->avail = p + num * sizeof(VRingDesc); vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + align-1) & ~(align - 1)); @@ -129,12 +129,12 @@ static void vring_init(struct vring *vr, unsigned int num, void *p, debug_print_addr("init vr", vr); } -static void vring_notify(struct subchannel_id schid) +static void vring_notify(SubChannelId schid) { virtio_notify(schid); } -static void vring_send_buf(struct vring *vr, void *p, int len, int flags) +static void vring_send_buf(VRing *vr, void *p, int len, int flags) { /* For follow-up chains we need to keep the first entry point */ if (!(flags & VRING_HIDDEN_IS_CHAIN)) { @@ -174,10 +174,10 @@ ulong get_second(void) * * Returns 0 on success, 1 on timeout. */ -static int vring_wait_reply(struct vring *vr, int timeout) +static int vring_wait_reply(VRing *vr, int timeout) { ulong target_second = get_second() + timeout; - struct subchannel_id schid = vr->schid; + SubChannelId schid = vr->schid; int r = 0; /* Wait until the used index has moved. */ @@ -204,7 +204,7 @@ static int vring_wait_reply(struct vring *vr, int timeout) int virtio_read_many(ulong sector, void *load_addr, int sec_num) { - struct virtio_blk_outhdr out_hdr; + VirtioBlkOuthdr out_hdr; u8 status; int r; @@ -363,10 +363,10 @@ uint64_t virtio_get_blocks(void) (virtio_get_block_size() / VIRTIO_SECTOR_SIZE); } -void virtio_setup_block(struct subchannel_id schid) +void virtio_setup_block(SubChannelId schid) { - struct vq_info_block info; - struct vq_config_block config = {}; + VqInfo info; + VqConfig config = {}; blk_cfg.blk_size = 0; /* mark "illegal" - setup started... */ guessed_disk_nature = false; @@ -406,10 +406,10 @@ void virtio_setup_block(struct subchannel_id schid) } } -bool virtio_is_blk(struct subchannel_id schid) +bool virtio_is_blk(SubChannelId schid) { int r; - struct senseid senseid = {}; + SenseId senseid = {}; /* run sense id command */ r = run_ccw(schid, CCW_CMD_SENSE_ID, &senseid, sizeof(senseid)); @@ -426,7 +426,7 @@ bool virtio_is_blk(struct subchannel_id schid) int enable_mss_facility(void) { int ret; - struct chsc_area_sda *sda_area = (struct chsc_area_sda *) chsc_page; + ChscAreaSda *sda_area = (ChscAreaSda *) chsc_page; memset(sda_area, 0, PAGE_SIZE); sda_area->request.length = 0x0400; diff --git a/pc-bios/s390-ccw/virtio.h b/pc-bios/s390-ccw/virtio.h index afa01a885b..af6e142637 100644 --- a/pc-bios/s390-ccw/virtio.h +++ b/pc-bios/s390-ccw/virtio.h @@ -23,48 +23,54 @@ /* We've given up on this device. */ #define VIRTIO_CONFIG_S_FAILED 0x80 -enum virtio_dev_type { +enum VirtioDevType { VIRTIO_ID_NET = 1, VIRTIO_ID_BLOCK = 2, VIRTIO_ID_CONSOLE = 3, VIRTIO_ID_BALLOON = 5, }; +typedef enum VirtioDevType VirtioDevType; -struct virtio_dev_header { - enum virtio_dev_type type : 8; - u8 num_vq; - u8 feature_len; - u8 config_len; - u8 status; - u8 vqconfig[]; +struct VirtioDevHeader { + VirtioDevType type:8; + uint8_t num_vq; + uint8_t feature_len; + uint8_t config_len; + uint8_t status; + uint8_t vqconfig[]; } __attribute__((packed)); +typedef struct VirtioDevHeader VirtioDevHeader; -struct virtio_vqconfig { - u64 token; - u64 address; - u16 num; - u8 pad[6]; +struct VirtioVqConfig { + uint64_t token; + uint64_t address; + uint16_t num; + uint8_t pad[6]; } __attribute__((packed)); +typedef struct VirtioVqConfig VirtioVqConfig; -struct vq_info_block { - u64 queue; - u32 align; - u16 index; - u16 num; +struct VqInfo { + uint64_t queue; + uint32_t align; + uint16_t index; + uint16_t num; } __attribute__((packed)); +typedef struct VqInfo VqInfo; -struct vq_config_block { - u16 index; - u16 num; +struct VqConfig { + uint16_t index; + uint16_t num; } __attribute__((packed)); +typedef struct VqConfig VqConfig; -struct virtio_dev { - struct virtio_dev_header *header; - struct virtio_vqconfig *vqconfig; +struct VirtioDev { + VirtioDevHeader *header; + VirtioVqConfig *vqconfig; char *host_features; char *guest_features; char *config; }; +typedef struct VirtioDev VirtioDev; #define KVM_S390_VIRTIO_RING_ALIGN 4096 @@ -81,46 +87,51 @@ struct virtio_dev { #define VRING_HIDDEN_IS_CHAIN 256 /* Virtio ring descriptors: 16 bytes. These can chain together via "next". */ -struct vring_desc { +struct VRingDesc { /* Address (guest-physical). */ - u64 addr; + uint64_t addr; /* Length. */ - u32 len; + uint32_t len; /* The flags as indicated above. */ - u16 flags; + uint16_t flags; /* We chain unused descriptors via this, too */ - u16 next; + uint16_t next; } __attribute__((packed)); +typedef struct VRingDesc VRingDesc; -struct vring_avail { - u16 flags; - u16 idx; - u16 ring[]; +struct VRingAvail { + uint16_t flags; + uint16_t idx; + uint16_t ring[]; } __attribute__((packed)); +typedef struct VRingAvail VRingAvail; -/* u32 is used here for ids for padding reasons. */ -struct vring_used_elem { +/* uint32_t is used here for ids for padding reasons. */ +struct VRingUsedElem { /* Index of start of used descriptor chain. */ - u32 id; + uint32_t id; /* Total length of the descriptor chain which was used (written to) */ - u32 len; + uint32_t len; } __attribute__((packed)); +typedef struct VRingUsedElem VRingUsedElem; -struct vring_used { - u16 flags; - u16 idx; - struct vring_used_elem ring[]; +struct VRingUsed { + uint16_t flags; + uint16_t idx; + VRingUsedElem ring[]; } __attribute__((packed)); +typedef struct VRingUsed VRingUsed; -struct vring { +struct VRing { unsigned int num; int next_idx; int used_idx; - struct vring_desc *desc; - struct vring_avail *avail; - struct vring_used *used; - struct subchannel_id schid; + VRingDesc *desc; + VRingAvail *avail; + VRingUsed *used; + SubChannelId schid; }; +typedef struct VRing VRing; /*********************************************** @@ -152,37 +163,39 @@ struct vring { #define VIRTIO_BLK_T_BARRIER 0x80000000 /* This is the first element of the read scatter-gather list. */ -struct virtio_blk_outhdr { +struct VirtioBlkOuthdr { /* VIRTIO_BLK_T* */ - u32 type; + uint32_t type; /* io priority. */ - u32 ioprio; + uint32_t ioprio; /* Sector (ie. 512 byte offset) */ - u64 sector; + uint64_t sector; }; +typedef struct VirtioBlkOuthdr VirtioBlkOuthdr; -typedef struct VirtioBlkConfig { - u64 capacity; /* in 512-byte sectors */ - u32 size_max; /* max segment size (if VIRTIO_BLK_F_SIZE_MAX) */ - u32 seg_max; /* max number of segments (if VIRTIO_BLK_F_SEG_MAX) */ +struct VirtioBlkConfig { + uint64_t capacity; /* in 512-byte sectors */ + uint32_t size_max; /* max segment size (if VIRTIO_BLK_F_SIZE_MAX) */ + uint32_t seg_max; /* max number of segments (if VIRTIO_BLK_F_SEG_MAX) */ - struct virtio_blk_geometry { - u16 cylinders; - u8 heads; - u8 sectors; + struct VirtioBlkGeometry { + uint16_t cylinders; + uint8_t heads; + uint8_t sectors; } geometry; /* (if VIRTIO_BLK_F_GEOMETRY) */ - u32 blk_size; /* block size of device (if VIRTIO_BLK_F_BLK_SIZE) */ + uint32_t blk_size; /* block size of device (if VIRTIO_BLK_F_BLK_SIZE) */ /* the next 4 entries are guarded by VIRTIO_BLK_F_TOPOLOGY */ - u8 physical_block_exp; /* exponent for physical block per logical block */ - u8 alignment_offset; /* alignment offset in logical blocks */ - u16 min_io_size; /* min I/O size without performance penalty + uint8_t physical_block_exp; /* exponent for physical blk per logical blk */ + uint8_t alignment_offset; /* alignment offset in logical blocks */ + uint16_t min_io_size; /* min I/O size without performance penalty in logical blocks */ - u32 opt_io_size; /* optimal sustained I/O size in logical blocks */ + uint32_t opt_io_size; /* optimal sustained I/O size in logical blks */ - u8 wce; /* writeback mode (if VIRTIO_BLK_F_CONFIG_WCE) */ -} __attribute__((packed)) VirtioBlkConfig; + uint8_t wce; /* writeback mode (if VIRTIO_BLK_F_CONFIG_WCE) */ +} __attribute__((packed)); +typedef struct VirtioBlkConfig VirtioBlkConfig; bool virtio_guessed_disk_nature(void); void virtio_assume_scsi(void); From 8512989143909bf7a32c96a52a8530e30df87ad7 Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Tue, 10 Nov 2015 15:13:36 +0100 Subject: [PATCH 05/14] pc-bios/s390-ccw: update virtio implementation to allow up to 3 vrings Add ability to work with up to 3 vrings, which is required for virtio-scsi implementation. Implement the optional cookie to speed up processing of virtio notifications. Reviewed-by: Cornelia Huck Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/main.c | 1 - pc-bios/s390-ccw/s390-ccw.h | 1 - pc-bios/s390-ccw/virtio.c | 140 ++++++++++++++++++++++-------------- pc-bios/s390-ccw/virtio.h | 4 ++ 4 files changed, 90 insertions(+), 56 deletions(-) diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c index 7f192f3a6d..6bf44a7fa0 100644 --- a/pc-bios/s390-ccw/main.c +++ b/pc-bios/s390-ccw/main.c @@ -12,7 +12,6 @@ #include "virtio.h" char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE))); -char ring_area[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE))); uint64_t boot_value; static SubChannelId blk_schid = { .one = 1 }; diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h index a5c0684c00..3e00d42eb8 100644 --- a/pc-bios/s390-ccw/s390-ccw.h +++ b/pc-bios/s390-ccw/s390-ccw.h @@ -61,7 +61,6 @@ void consume_sclp_int(void); void panic(const char *string); void write_subsystem_identification(void); extern char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE))); -extern char ring_area[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE))); extern uint64_t boot_value; /* sclp-ascii.c */ diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c index 64c6e070de..d366aa32e1 100644 --- a/pc-bios/s390-ccw/virtio.c +++ b/pc-bios/s390-ccw/virtio.c @@ -11,29 +11,35 @@ #include "s390-ccw.h" #include "virtio.h" -static VRing block; +static VRing block[VIRTIO_MAX_VQS]; +static char ring_area[VIRTIO_RING_SIZE * VIRTIO_MAX_VQS] + __attribute__((__aligned__(PAGE_SIZE))); +static int nr_vqs = 1; static char chsc_page[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE))); +/* virtio spec v1.0 para 4.3.3.2 */ static long kvm_hypercall(unsigned long nr, unsigned long param1, - unsigned long param2) + unsigned long param2, unsigned long param3) { register ulong r_nr asm("1") = nr; register ulong r_param1 asm("2") = param1; register ulong r_param2 asm("3") = param2; + register ulong r_param3 asm("4") = param3; register long retval asm("2"); asm volatile ("diag 2,4,0x500" : "=d" (retval) - : "d" (r_nr), "0" (r_param1), "r"(r_param2) + : "d" (r_nr), "0" (r_param1), "r"(r_param2), "d"(r_param3) : "memory", "cc"); return retval; } -static void virtio_notify(SubChannelId schid) +static long virtio_notify(SubChannelId schid, int vq_idx, long cookie) { - kvm_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY, *(u32 *)&schid, 0); + return kvm_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY, *(u32 *)&schid, + vq_idx, cookie); } /*********************************************** @@ -106,15 +112,17 @@ static void virtio_reset(SubChannelId schid) run_ccw(schid, CCW_CMD_VDEV_RESET, NULL, 0); } -static void vring_init(VRing *vr, unsigned int num, void *p, - unsigned long align) +static void vring_init(VRing *vr, VqInfo *info) { + void *p = (void *) info->queue; + debug_print_addr("init p", p); - vr->num = num; + vr->id = info->index; + vr->num = info->num; vr->desc = p; - vr->avail = p + num * sizeof(VRingDesc); - vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + align-1) - & ~(align - 1)); + vr->avail = p + info->num * sizeof(VRingDesc); + vr->used = (void *)(((unsigned long)&vr->avail->ring[info->num] + + info->align - 1) & ~(info->align - 1)); /* Zero out all relevant field */ vr->avail->flags = 0; @@ -125,13 +133,15 @@ static void vring_init(VRing *vr, unsigned int num, void *p, vr->used->idx = 0; vr->used_idx = 0; vr->next_idx = 0; + vr->cookie = 0; debug_print_addr("init vr", vr); } -static void vring_notify(SubChannelId schid) +static bool vring_notify(VRing *vr) { - virtio_notify(schid); + vr->cookie = virtio_notify(vr->schid, vr->id, vr->cookie); + return vr->cookie >= 0; } static void vring_send_buf(VRing *vr, void *p, int len, int flags) @@ -167,6 +177,21 @@ ulong get_second(void) return (get_clock() >> 12) / 1000000; } +static int vr_poll(VRing *vr) +{ + if (vr->used->idx == vr->used_idx) { + vring_notify(vr); + yield(); + return 0; + } + + vr->used_idx = vr->used->idx; + vr->next_idx = 0; + vr->desc[0].len = 0; + vr->desc[0].flags = 0; + return 1; /* vr has been updated */ +} + /* * Wait for the host to reply. * @@ -174,28 +199,24 @@ ulong get_second(void) * * Returns 0 on success, 1 on timeout. */ -static int vring_wait_reply(VRing *vr, int timeout) +static int vring_wait_reply(int timeout) { ulong target_second = get_second() + timeout; - SubChannelId schid = vr->schid; - int r = 0; - /* Wait until the used index has moved. */ - while (vr->used->idx == vr->used_idx) { - vring_notify(schid); - if (timeout && (get_second() >= target_second)) { - r = 1; - break; + /* Wait for any queue to be updated by the host */ + do { + int i, r = 0; + + for (i = 0; i < nr_vqs; i++) { + r += vr_poll(&block[i]); } yield(); - } + if (r) { + return 0; + } + } while (!timeout || (get_second() < target_second)); - vr->used_idx = vr->used->idx; - vr->next_idx = 0; - vr->desc[0].len = 0; - vr->desc[0].flags = 0; - - return r; + return 1; } /*********************************************** @@ -213,21 +234,21 @@ int virtio_read_many(ulong sector, void *load_addr, int sec_num) out_hdr.ioprio = 99; out_hdr.sector = virtio_sector_adjust(sector); - vring_send_buf(&block, &out_hdr, sizeof(out_hdr), VRING_DESC_F_NEXT); + vring_send_buf(&block[0], &out_hdr, sizeof(out_hdr), VRING_DESC_F_NEXT); /* This is where we want to receive data */ - vring_send_buf(&block, load_addr, virtio_get_block_size() * sec_num, + vring_send_buf(&block[0], load_addr, virtio_get_block_size() * sec_num, VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN | VRING_DESC_F_NEXT); /* status field */ - vring_send_buf(&block, &status, sizeof(u8), VRING_DESC_F_WRITE | + vring_send_buf(&block[0], &status, sizeof(u8), VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN); /* Now we can tell the host to read */ - vring_wait_reply(&block, 0); + vring_wait_reply(0); - r = drain_irqs(block.schid); + r = drain_irqs(block[0].schid); if (r) { /* Well, whatever status is supposed to contain... */ status = 1; @@ -363,15 +384,18 @@ uint64_t virtio_get_blocks(void) (virtio_get_block_size() / VIRTIO_SECTOR_SIZE); } -void virtio_setup_block(SubChannelId schid) +static void virtio_setup_ccw(SubChannelId schid, + int nvr, void *cfg, int cfg_size) { - VqInfo info; - VqConfig config = {}; + int i; blk_cfg.blk_size = 0; /* mark "illegal" - setup started... */ + nr_vqs = nvr; guessed_disk_nature = false; virtio_reset(schid); + IPL_assert(run_ccw(schid, CCW_CMD_READ_CONF, cfg, cfg_size) == 0, + "Could not get block device configuration"); /* * Skipping CCW_CMD_READ_FEAT. We're not doing anything fancy, and @@ -379,25 +403,33 @@ void virtio_setup_block(SubChannelId schid) * expect it. */ - config.index = 0; - if (run_ccw(schid, CCW_CMD_READ_VQ_CONF, &config, sizeof(config))) { - panic("Could not get block device VQ configuration\n"); - } - if (run_ccw(schid, CCW_CMD_READ_CONF, &blk_cfg, sizeof(blk_cfg))) { - panic("Could not get block device configuration\n"); - } - vring_init(&block, config.num, ring_area, - KVM_S390_VIRTIO_RING_ALIGN); + for (i = 0; i < nr_vqs; i++) { + VqInfo info = { + .queue = (unsigned long long) ring_area + (i * VIRTIO_RING_SIZE), + .align = KVM_S390_VIRTIO_RING_ALIGN, + .index = i, + .num = 0, + }; + VqConfig config = { + .index = i, + .num = 0, + }; - info.queue = (unsigned long long) ring_area; - info.align = KVM_S390_VIRTIO_RING_ALIGN; - info.index = 0; - info.num = config.num; - block.schid = schid; - - if (!run_ccw(schid, CCW_CMD_SET_VQ, &info, sizeof(info))) { - virtio_set_status(schid, VIRTIO_CONFIG_S_DRIVER_OK); + IPL_assert( + run_ccw(schid, CCW_CMD_READ_VQ_CONF, &config, sizeof(config)) == 0, + "Could not get block device VQ configuration"); + info.num = config.num; + vring_init(&block[i], &info); + block[i].schid = schid; + IPL_assert(run_ccw(schid, CCW_CMD_SET_VQ, &info, sizeof(info)) == 0, + "Cannot set VQ info"); } + virtio_set_status(schid, VIRTIO_CONFIG_S_DRIVER_OK); +} + +void virtio_setup_block(SubChannelId schid) +{ + virtio_setup_ccw(schid, 1, &blk_cfg, sizeof(blk_cfg)); if (!virtio_ipl_disk_is_valid()) { /* make sure all getters but blocksize return 0 for invalid IPL disk */ diff --git a/pc-bios/s390-ccw/virtio.h b/pc-bios/s390-ccw/virtio.h index af6e142637..d17b1350d4 100644 --- a/pc-bios/s390-ccw/virtio.h +++ b/pc-bios/s390-ccw/virtio.h @@ -72,6 +72,8 @@ struct VirtioDev { }; typedef struct VirtioDev VirtioDev; +#define VIRTIO_RING_SIZE (PAGE_SIZE * 8) +#define VIRTIO_MAX_VQS 3 #define KVM_S390_VIRTIO_RING_ALIGN 4096 #define VRING_USED_F_NO_NOTIFY 1 @@ -130,6 +132,8 @@ struct VRing { VRingAvail *avail; VRingUsed *used; SubChannelId schid; + long cookie; + int id; }; typedef struct VRing VRing; From 69429682c6887bfe7911b26766bedcf3bcabb281 Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Mon, 26 Oct 2015 15:47:24 +0100 Subject: [PATCH 06/14] pc-bios/s390-ccw: add vdev object to store all device details Add VDev "object" as a container for all device-related items. The default object is static. Leverage dependency on many different device-related globals. Make them syntactically visible. Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/virtio.c | 181 +++++++++++++++++++++----------------- pc-bios/s390-ccw/virtio.h | 18 ++++ 2 files changed, 116 insertions(+), 83 deletions(-) diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c index d366aa32e1..6bf0c38d87 100644 --- a/pc-bios/s390-ccw/virtio.c +++ b/pc-bios/s390-ccw/virtio.c @@ -11,13 +11,34 @@ #include "s390-ccw.h" #include "virtio.h" +#define VRING_WAIT_REPLY_TIMEOUT 3 + static VRing block[VIRTIO_MAX_VQS]; static char ring_area[VIRTIO_RING_SIZE * VIRTIO_MAX_VQS] __attribute__((__aligned__(PAGE_SIZE))); -static int nr_vqs = 1; static char chsc_page[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE))); +static VDev vdev = { + .nr_vqs = 1, + .vrings = block, + .cmd_vr_idx = 0, + .ring_area = ring_area, + .wait_reply_timeout = VRING_WAIT_REPLY_TIMEOUT, + .guessed_disk_nature = false, + .schid = { .one = 1 }, +}; + +VDev *virtio_get_device(void) +{ + return &vdev; +} + +VirtioDevType virtio_get_device_type(void) +{ + return vdev.senseid.cu_model; +} + /* virtio spec v1.0 para 4.3.3.2 */ static long kvm_hypercall(unsigned long nr, unsigned long param1, unsigned long param2, unsigned long param3) @@ -65,7 +86,7 @@ static int drain_irqs(SubChannelId schid) } } -static int run_ccw(SubChannelId schid, int cmd, void *ptr, int len) +static int run_ccw(VDev *vdev, int cmd, void *ptr, int len) { Ccw1 ccw = {}; CmdOrb orb = {}; @@ -73,9 +94,9 @@ static int run_ccw(SubChannelId schid, int cmd, void *ptr, int len) int r; /* start command processing */ - stsch_err(schid, &schib); + stsch_err(vdev->schid, &schib); schib.scsw.ctrl = SCSW_FCTL_START_FUNC; - msch(schid, &schib); + msch(vdev->schid, &schib); /* start subchannel command */ orb.fmt = 1; @@ -86,32 +107,18 @@ static int run_ccw(SubChannelId schid, int cmd, void *ptr, int len) ccw.cda = (long)ptr; ccw.count = len; - r = ssch(schid, &orb); + r = ssch(vdev->schid, &orb); /* * XXX Wait until device is done processing the CCW. For now we can * assume that a simple tsch will have finished the CCW processing, * but the architecture allows for asynchronous operation */ if (!r) { - r = drain_irqs(schid); + r = drain_irqs(vdev->schid); } return r; } -static void virtio_set_status(SubChannelId schid, - unsigned long dev_addr) -{ - unsigned char status = dev_addr; - if (run_ccw(schid, CCW_CMD_WRITE_STATUS, &status, sizeof(status))) { - panic("Could not write status to host!\n"); - } -} - -static void virtio_reset(SubChannelId schid) -{ - run_ccw(schid, CCW_CMD_VDEV_RESET, NULL, 0); -} - static void vring_init(VRing *vr, VqInfo *info) { void *p = (void *) info->queue; @@ -199,22 +206,22 @@ static int vr_poll(VRing *vr) * * Returns 0 on success, 1 on timeout. */ -static int vring_wait_reply(int timeout) +static int vring_wait_reply(void) { - ulong target_second = get_second() + timeout; + ulong target_second = get_second() + vdev.wait_reply_timeout; /* Wait for any queue to be updated by the host */ do { int i, r = 0; - for (i = 0; i < nr_vqs; i++) { - r += vr_poll(&block[i]); + for (i = 0; i < vdev.nr_vqs; i++) { + r += vr_poll(&vdev.vrings[i]); } yield(); if (r) { return 0; } - } while (!timeout || (get_second() < target_second)); + } while (!vdev.wait_reply_timeout || (get_second() < target_second)); return 1; } @@ -227,29 +234,28 @@ int virtio_read_many(ulong sector, void *load_addr, int sec_num) { VirtioBlkOuthdr out_hdr; u8 status; - int r; + VRing *vr = &vdev.vrings[vdev.cmd_vr_idx]; /* Tell the host we want to read */ out_hdr.type = VIRTIO_BLK_T_IN; out_hdr.ioprio = 99; out_hdr.sector = virtio_sector_adjust(sector); - vring_send_buf(&block[0], &out_hdr, sizeof(out_hdr), VRING_DESC_F_NEXT); + vring_send_buf(vr, &out_hdr, sizeof(out_hdr), VRING_DESC_F_NEXT); /* This is where we want to receive data */ - vring_send_buf(&block[0], load_addr, virtio_get_block_size() * sec_num, + vring_send_buf(vr, load_addr, virtio_get_block_size() * sec_num, VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN | VRING_DESC_F_NEXT); /* status field */ - vring_send_buf(&block[0], &status, sizeof(u8), VRING_DESC_F_WRITE | - VRING_HIDDEN_IS_CHAIN); + vring_send_buf(vr, &status, sizeof(u8), + VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN); /* Now we can tell the host to read */ - vring_wait_reply(0); + vring_wait_reply(); - r = drain_irqs(block[0].schid); - if (r) { + if (drain_irqs(vr->schid)) { /* Well, whatever status is supposed to contain... */ status = 1; } @@ -284,46 +290,43 @@ int virtio_read(ulong sector, void *load_addr) return virtio_read_many(sector, load_addr, 1); } -static VirtioBlkConfig blk_cfg = {}; -static bool guessed_disk_nature; - bool virtio_guessed_disk_nature(void) { - return guessed_disk_nature; + return vdev.guessed_disk_nature; } void virtio_assume_scsi(void) { - guessed_disk_nature = true; - blk_cfg.blk_size = 512; - blk_cfg.physical_block_exp = 0; + vdev.guessed_disk_nature = true; + vdev.config.blk.blk_size = 512; + vdev.config.blk.physical_block_exp = 0; } void virtio_assume_iso9660(void) { - guessed_disk_nature = true; - blk_cfg.blk_size = 2048; - blk_cfg.physical_block_exp = 0; + vdev.guessed_disk_nature = true; + vdev.config.blk.blk_size = 2048; + vdev.config.blk.physical_block_exp = 0; } void virtio_assume_eckd(void) { - guessed_disk_nature = true; - blk_cfg.blk_size = 4096; - blk_cfg.physical_block_exp = 0; + vdev.guessed_disk_nature = true; + vdev.config.blk.blk_size = 4096; + vdev.config.blk.physical_block_exp = 0; /* this must be here to calculate code segment position */ - blk_cfg.geometry.heads = 15; - blk_cfg.geometry.sectors = 12; + vdev.config.blk.geometry.heads = 15; + vdev.config.blk.geometry.sectors = 12; } bool virtio_disk_is_scsi(void) { - if (guessed_disk_nature) { + if (vdev.guessed_disk_nature) { return (virtio_get_block_size() == 512); } - return (blk_cfg.geometry.heads == 255) - && (blk_cfg.geometry.sectors == 63) + return (vdev.config.blk.geometry.heads == 255) + && (vdev.config.blk.geometry.sectors == 63) && (virtio_get_block_size() == 512); } @@ -350,11 +353,11 @@ bool virtio_disk_is_eckd(void) { const int block_size = virtio_get_block_size(); - if (guessed_disk_nature) { + if (vdev.guessed_disk_nature) { return (block_size == 4096); } - return (blk_cfg.geometry.heads == 15) - && (blk_cfg.geometry.sectors == + return (vdev.config.blk.geometry.heads == 15) + && (vdev.config.blk.geometry.sectors == virtio_eckd_sectors_for_block_size(block_size)); } @@ -365,36 +368,45 @@ bool virtio_ipl_disk_is_valid(void) int virtio_get_block_size(void) { - return blk_cfg.blk_size << blk_cfg.physical_block_exp; + return vdev.config.blk.blk_size << vdev.config.blk.physical_block_exp; } uint8_t virtio_get_heads(void) { - return blk_cfg.geometry.heads; + return vdev.config.blk.geometry.heads; } uint8_t virtio_get_sectors(void) { - return blk_cfg.geometry.sectors; + return vdev.config.blk.geometry.sectors; } uint64_t virtio_get_blocks(void) { - return blk_cfg.capacity / + return vdev.config.blk.capacity / (virtio_get_block_size() / VIRTIO_SECTOR_SIZE); } -static void virtio_setup_ccw(SubChannelId schid, - int nvr, void *cfg, int cfg_size) +static void virtio_setup_ccw(VDev *vdev) { - int i; + int i, cfg_size; + unsigned char status = VIRTIO_CONFIG_S_DRIVER_OK; - blk_cfg.blk_size = 0; /* mark "illegal" - setup started... */ - nr_vqs = nvr; - guessed_disk_nature = false; + vdev->config.blk.blk_size = 0; /* mark "illegal" - setup started... */ + vdev->guessed_disk_nature = false; - virtio_reset(schid); - IPL_assert(run_ccw(schid, CCW_CMD_READ_CONF, cfg, cfg_size) == 0, + run_ccw(vdev, CCW_CMD_VDEV_RESET, NULL, 0); + + switch (vdev->senseid.cu_model) { + case VIRTIO_ID_BLOCK: + vdev->nr_vqs = 1; + vdev->cmd_vr_idx = 0; + cfg_size = sizeof(vdev->config.blk); + break; + default: + panic("Unsupported virtio device\n"); + } + IPL_assert(run_ccw(vdev, CCW_CMD_READ_CONF, &vdev->config, cfg_size) == 0, "Could not get block device configuration"); /* @@ -403,7 +415,7 @@ static void virtio_setup_ccw(SubChannelId schid, * expect it. */ - for (i = 0; i < nr_vqs; i++) { + for (i = 0; i < vdev->nr_vqs; i++) { VqInfo info = { .queue = (unsigned long long) ring_area + (i * VIRTIO_RING_SIZE), .align = KVM_S390_VIRTIO_RING_ALIGN, @@ -416,43 +428,46 @@ static void virtio_setup_ccw(SubChannelId schid, }; IPL_assert( - run_ccw(schid, CCW_CMD_READ_VQ_CONF, &config, sizeof(config)) == 0, + run_ccw(vdev, CCW_CMD_READ_VQ_CONF, &config, sizeof(config)) == 0, "Could not get block device VQ configuration"); info.num = config.num; - vring_init(&block[i], &info); - block[i].schid = schid; - IPL_assert(run_ccw(schid, CCW_CMD_SET_VQ, &info, sizeof(info)) == 0, + vring_init(&vdev->vrings[i], &info); + vdev->vrings[i].schid = vdev->schid; + IPL_assert(run_ccw(vdev, CCW_CMD_SET_VQ, &info, sizeof(info)) == 0, "Cannot set VQ info"); } - virtio_set_status(schid, VIRTIO_CONFIG_S_DRIVER_OK); + IPL_assert( + run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status)) == 0, + "Could not write status to host"); } void virtio_setup_block(SubChannelId schid) { - virtio_setup_ccw(schid, 1, &blk_cfg, sizeof(blk_cfg)); + vdev.schid = schid; + virtio_setup_ccw(&vdev); if (!virtio_ipl_disk_is_valid()) { /* make sure all getters but blocksize return 0 for invalid IPL disk */ - memset(&blk_cfg, 0, sizeof(blk_cfg)); + memset(&vdev.config.blk, 0, sizeof(vdev.config.blk)); virtio_assume_scsi(); } } bool virtio_is_blk(SubChannelId schid) { - int r; - SenseId senseid = {}; - + vdev.schid = schid; + memset(&vdev.senseid, 0, sizeof(vdev.senseid)); /* run sense id command */ - r = run_ccw(schid, CCW_CMD_SENSE_ID, &senseid, sizeof(senseid)); - if (r) { + if (run_ccw(&vdev, CCW_CMD_SENSE_ID, &vdev.senseid, sizeof(vdev.senseid))) { return false; } - if ((senseid.cu_type != 0x3832) || (senseid.cu_model != VIRTIO_ID_BLOCK)) { - return false; + if (vdev.senseid.cu_type == 0x3832) { + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return true; + } } - - return true; + return false; } int enable_mss_facility(void) diff --git a/pc-bios/s390-ccw/virtio.h b/pc-bios/s390-ccw/virtio.h index d17b1350d4..b0034aa07f 100644 --- a/pc-bios/s390-ccw/virtio.h +++ b/pc-bios/s390-ccw/virtio.h @@ -222,4 +222,22 @@ static inline ulong virtio_sector_adjust(ulong sector) return sector * (virtio_get_block_size() / VIRTIO_SECTOR_SIZE); } +struct VDev { + int nr_vqs; + VRing *vrings; + int cmd_vr_idx; + void *ring_area; + long wait_reply_timeout; + bool guessed_disk_nature; + SubChannelId schid; + SenseId senseid; + union { + VirtioBlkConfig blk; + } config; +}; +typedef struct VDev VDev; + +VDev *virtio_get_device(void); +VirtioDevType virtio_get_device_type(void); + #endif /* VIRTIO_H */ From a1102cebbfa0c9b3f66641ab97cf2c602c98d220 Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Mon, 26 Oct 2015 16:55:16 +0100 Subject: [PATCH 07/14] pc-bios/s390-ccw: make provisions for different backends Add dispatching code to make room for non virtio-blk boot devices. Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/main.c | 4 +- pc-bios/s390-ccw/s390-ccw.h | 4 +- pc-bios/s390-ccw/virtio.c | 130 +++++++++++++++++++++++++----------- pc-bios/s390-ccw/virtio.h | 12 +++- 4 files changed, 106 insertions(+), 44 deletions(-) diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c index 6bf44a7fa0..69a02feb87 100644 --- a/pc-bios/s390-ccw/main.c +++ b/pc-bios/s390-ccw/main.c @@ -50,7 +50,7 @@ static bool find_dev(Schib *schib, int dev_no) if (!schib->pmcw.dnv) { continue; } - if (!virtio_is_blk(blk_schid)) { + if (!virtio_is_supported(blk_schid)) { continue; } if ((dev_no < 0) || (schib->pmcw.dev == dev_no)) { @@ -95,7 +95,7 @@ static void virtio_setup(uint64_t dev_info) panic("No virtio-blk device found!\n"); } - virtio_setup_block(blk_schid); + virtio_setup_device(blk_schid); if (!virtio_ipl_disk_is_valid()) { panic("No valid hard disk detected.\n"); diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h index 3e00d42eb8..616d96738d 100644 --- a/pc-bios/s390-ccw/s390-ccw.h +++ b/pc-bios/s390-ccw/s390-ccw.h @@ -70,8 +70,8 @@ void sclp_setup(void); /* virtio.c */ unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2, ulong subchan_id, void *load_addr); -bool virtio_is_blk(SubChannelId schid); -void virtio_setup_block(SubChannelId schid); +bool virtio_is_supported(SubChannelId schid); +void virtio_setup_device(SubChannelId schid); int virtio_read(ulong sector, void *load_addr); int enable_mss_facility(void); ulong get_second(void); diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c index 6bf0c38d87..56734aff44 100644 --- a/pc-bios/s390-ccw/virtio.c +++ b/pc-bios/s390-ccw/virtio.c @@ -25,7 +25,6 @@ static VDev vdev = { .cmd_vr_idx = 0, .ring_area = ring_area, .wait_reply_timeout = VRING_WAIT_REPLY_TIMEOUT, - .guessed_disk_nature = false, .schid = { .one = 1 }, }; @@ -230,11 +229,12 @@ static int vring_wait_reply(void) * Virtio block * ***********************************************/ -int virtio_read_many(ulong sector, void *load_addr, int sec_num) +static int virtio_blk_read_many(VDev *vdev, + ulong sector, void *load_addr, int sec_num) { VirtioBlkOuthdr out_hdr; u8 status; - VRing *vr = &vdev.vrings[vdev.cmd_vr_idx]; + VRing *vr = &vdev->vrings[vdev->cmd_vr_idx]; /* Tell the host we want to read */ out_hdr.type = VIRTIO_BLK_T_IN; @@ -262,6 +262,16 @@ int virtio_read_many(ulong sector, void *load_addr, int sec_num) return status; } +int virtio_read_many(ulong sector, void *load_addr, int sec_num) +{ + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return virtio_blk_read_many(&vdev, sector, load_addr, sec_num); + } + panic("\n! No readable IPL device !\n"); + return -1; +} + unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2, ulong subchan_id, void *load_addr) { @@ -290,44 +300,60 @@ int virtio_read(ulong sector, void *load_addr) return virtio_read_many(sector, load_addr, 1); } -bool virtio_guessed_disk_nature(void) +VirtioGDN virtio_guessed_disk_nature(void) { return vdev.guessed_disk_nature; } void virtio_assume_scsi(void) { - vdev.guessed_disk_nature = true; - vdev.config.blk.blk_size = 512; - vdev.config.blk.physical_block_exp = 0; + vdev.guessed_disk_nature = VIRTIO_GDN_SCSI; + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + vdev.config.blk.blk_size = 512; + vdev.config.blk.physical_block_exp = 0; + break; + } } void virtio_assume_iso9660(void) { - vdev.guessed_disk_nature = true; - vdev.config.blk.blk_size = 2048; - vdev.config.blk.physical_block_exp = 0; + vdev.guessed_disk_nature = VIRTIO_GDN_CDROM; + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + vdev.config.blk.blk_size = 2048; + vdev.config.blk.physical_block_exp = 0; + break; + } } void virtio_assume_eckd(void) { - vdev.guessed_disk_nature = true; - vdev.config.blk.blk_size = 4096; - vdev.config.blk.physical_block_exp = 0; + vdev.guessed_disk_nature = VIRTIO_GDN_DASD; + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + vdev.config.blk.blk_size = 4096; + vdev.config.blk.physical_block_exp = 0; - /* this must be here to calculate code segment position */ - vdev.config.blk.geometry.heads = 15; - vdev.config.blk.geometry.sectors = 12; + /* this must be here to calculate code segment position */ + vdev.config.blk.geometry.heads = 15; + vdev.config.blk.geometry.sectors = 12; + break; + } } bool virtio_disk_is_scsi(void) { - if (vdev.guessed_disk_nature) { - return (virtio_get_block_size() == 512); + if (vdev.guessed_disk_nature == VIRTIO_GDN_SCSI) { + return true; } - return (vdev.config.blk.geometry.heads == 255) - && (vdev.config.blk.geometry.sectors == 63) - && (virtio_get_block_size() == 512); + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return (vdev.config.blk.geometry.heads == 255) + && (vdev.config.blk.geometry.sectors == 63) + && (virtio_get_block_size() == 512); + } + return false; } /* @@ -353,12 +379,16 @@ bool virtio_disk_is_eckd(void) { const int block_size = virtio_get_block_size(); - if (vdev.guessed_disk_nature) { - return (block_size == 4096); + if (vdev.guessed_disk_nature == VIRTIO_GDN_DASD) { + return true; } - return (vdev.config.blk.geometry.heads == 15) - && (vdev.config.blk.geometry.sectors == - virtio_eckd_sectors_for_block_size(block_size)); + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return (vdev.config.blk.geometry.heads == 15) + && (vdev.config.blk.geometry.sectors == + virtio_eckd_sectors_for_block_size(block_size)); + } + return false; } bool virtio_ipl_disk_is_valid(void) @@ -368,23 +398,39 @@ bool virtio_ipl_disk_is_valid(void) int virtio_get_block_size(void) { - return vdev.config.blk.blk_size << vdev.config.blk.physical_block_exp; + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return vdev.config.blk.blk_size << vdev.config.blk.physical_block_exp; + } + return 0; } uint8_t virtio_get_heads(void) { - return vdev.config.blk.geometry.heads; + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return vdev.config.blk.geometry.heads; + } + return 0; } uint8_t virtio_get_sectors(void) { - return vdev.config.blk.geometry.sectors; + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return vdev.config.blk.geometry.sectors; + } + return 0; } uint64_t virtio_get_blocks(void) { - return vdev.config.blk.capacity / - (virtio_get_block_size() / VIRTIO_SECTOR_SIZE); + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return vdev.config.blk.capacity / + (virtio_get_block_size() / VIRTIO_SECTOR_SIZE); + } + return 0; } static void virtio_setup_ccw(VDev *vdev) @@ -393,7 +439,7 @@ static void virtio_setup_ccw(VDev *vdev) unsigned char status = VIRTIO_CONFIG_S_DRIVER_OK; vdev->config.blk.blk_size = 0; /* mark "illegal" - setup started... */ - vdev->guessed_disk_nature = false; + vdev->guessed_disk_nature = VIRTIO_GDN_NONE; run_ccw(vdev, CCW_CMD_VDEV_RESET, NULL, 0); @@ -441,19 +487,27 @@ static void virtio_setup_ccw(VDev *vdev) "Could not write status to host"); } -void virtio_setup_block(SubChannelId schid) +void virtio_setup_device(SubChannelId schid) { vdev.schid = schid; virtio_setup_ccw(&vdev); - if (!virtio_ipl_disk_is_valid()) { - /* make sure all getters but blocksize return 0 for invalid IPL disk */ - memset(&vdev.config.blk, 0, sizeof(vdev.config.blk)); - virtio_assume_scsi(); + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + if (!virtio_ipl_disk_is_valid()) { + /* make sure all getters but blocksize return 0 for + * invalid IPL disk + */ + memset(&vdev.config.blk, 0, sizeof(vdev.config.blk)); + virtio_assume_scsi(); + } + break; + default: + panic("\n! No IPL device available !\n"); } } -bool virtio_is_blk(SubChannelId schid) +bool virtio_is_supported(SubChannelId schid) { vdev.schid = schid; memset(&vdev.senseid, 0, sizeof(vdev.senseid)); diff --git a/pc-bios/s390-ccw/virtio.h b/pc-bios/s390-ccw/virtio.h index b0034aa07f..7b227db7b8 100644 --- a/pc-bios/s390-ccw/virtio.h +++ b/pc-bios/s390-ccw/virtio.h @@ -201,7 +201,15 @@ struct VirtioBlkConfig { } __attribute__((packed)); typedef struct VirtioBlkConfig VirtioBlkConfig; -bool virtio_guessed_disk_nature(void); +enum guessed_disk_nature_type { + VIRTIO_GDN_NONE = 0, + VIRTIO_GDN_DASD = 1, + VIRTIO_GDN_CDROM = 2, + VIRTIO_GDN_SCSI = 3, +}; +typedef enum guessed_disk_nature_type VirtioGDN; + +VirtioGDN virtio_guessed_disk_nature(void); void virtio_assume_scsi(void); void virtio_assume_eckd(void); void virtio_assume_iso9660(void); @@ -228,7 +236,7 @@ struct VDev { int cmd_vr_idx; void *ring_area; long wait_reply_timeout; - bool guessed_disk_nature; + VirtioGDN guessed_disk_nature; SubChannelId schid; SenseId senseid; union { From 8944edc3dd9928b10f39194350b035cac0b40d0d Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Wed, 28 Oct 2015 11:12:13 +0100 Subject: [PATCH 08/14] pc-bios/s390-ccw: add simplified virtio call Add virtio_run(VirtioCmd) call to use simple declarative approach. Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/virtio.c | 17 +++++++++++++++++ pc-bios/s390-ccw/virtio.h | 9 +++++++++ 2 files changed, 26 insertions(+) diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c index 56734aff44..4ab4d475c0 100644 --- a/pc-bios/s390-ccw/virtio.c +++ b/pc-bios/s390-ccw/virtio.c @@ -225,6 +225,23 @@ static int vring_wait_reply(void) return 1; } +int virtio_run(VDev *vdev, int vqid, VirtioCmd *cmd) +{ + VRing *vr = &vdev->vrings[vqid]; + int i = 0; + + do { + vring_send_buf(vr, cmd[i].data, cmd[i].size, + cmd[i].flags | (i ? VRING_HIDDEN_IS_CHAIN : 0)); + } while (cmd[i++].flags & VRING_DESC_F_NEXT); + + vring_wait_reply(); + if (drain_irqs(vr->schid)) { + return -1; + } + return 0; +} + /*********************************************** * Virtio block * ***********************************************/ diff --git a/pc-bios/s390-ccw/virtio.h b/pc-bios/s390-ccw/virtio.h index 7b227db7b8..57c71a2c93 100644 --- a/pc-bios/s390-ccw/virtio.h +++ b/pc-bios/s390-ccw/virtio.h @@ -248,4 +248,13 @@ typedef struct VDev VDev; VDev *virtio_get_device(void); VirtioDevType virtio_get_device_type(void); +struct VirtioCmd { + void *data; + int size; + int flags; +}; +typedef struct VirtioCmd VirtioCmd; + +int virtio_run(VDev *vdev, int vqid, VirtioCmd *cmd); + #endif /* VIRTIO_H */ From f791561476e19516385bf13c5a8f885c9bebe81d Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Tue, 10 Nov 2015 15:35:45 +0100 Subject: [PATCH 09/14] pc-bios/s390-ccw: add scsi definitions Add scsi.h to provide basic definitions for SCSI. Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/scsi.h | 184 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 pc-bios/s390-ccw/scsi.h diff --git a/pc-bios/s390-ccw/scsi.h b/pc-bios/s390-ccw/scsi.h new file mode 100644 index 0000000000..fc830f7e52 --- /dev/null +++ b/pc-bios/s390-ccw/scsi.h @@ -0,0 +1,184 @@ +/* + * SCSI definitions for s390 machine loader for qemu + * + * Copyright 2015 IBM Corp. + * Author: Eugene "jno" Dvurechenski + * + * This work is licensed under the terms of the GNU GPL, version 2 or (at + * your option) any later version. See the COPYING file in the top-level + * directory. + */ + +#ifndef SCSI_H +#define SCSI_H + +#include "s390-ccw.h" + +#define SCSI_DEFAULT_CDB_SIZE 32 +#define SCSI_DEFAULT_SENSE_SIZE 96 + +#define CDB_STATUS_GOOD 0 +#define CDB_STATUS_CHECK_CONDITION 0x02U +#define CDB_STATUS_VALID(status) (((status) & ~0x3eU) == 0) + +#define SCSI_SENSE_CODE_MASK 0x7fU +#define SCSI_SENSE_KEY_MASK 0x0fU +#define SCSI_SENSE_KEY_NO_SENSE 0 +#define SCSI_SENSE_KEY_UNIT_ATTENTION 6 + +union ScsiLun { + uint64_t v64; /* numeric shortcut */ + uint8_t v8[8]; /* generic 8 bytes representation */ + uint16_t v16[4]; /* 4-level big-endian LUN as specified by SAM-2 */ +}; +typedef union ScsiLun ScsiLun; + +struct ScsiSense70 { + uint8_t b0; /* b0 & 7f = resp code (0x70 or 0x71) */ + uint8_t b1, b2; /* b2 & 0f = sense key */ + uint8_t u1[1 * 4 + 1 + 1 * 4]; /* b7 = N - 7 */ + uint8_t additional_sense_code; /* b12 */ + uint8_t additional_sense_code_qualifier; /* b13 */ + uint8_t u2[1 + 3 + 0]; /* up to N (<=252) bytes */ +} __attribute__((packed)); +typedef struct ScsiSense70 ScsiSense70; + +/* don't confuse with virtio-scsi response/status fields! */ + +static inline uint8_t scsi_sense_response(const void *p) +{ + return ((const ScsiSense70 *)p)->b0 & SCSI_SENSE_CODE_MASK; +} + +static inline uint8_t scsi_sense_key(const void *p) +{ + return ((const ScsiSense70 *)p)->b2 & SCSI_SENSE_KEY_MASK; +} + +#define SCSI_INQ_RDT_CDROM 0x05 + +struct ScsiInquiryStd { + uint8_t peripheral_qdt; /* b0, use (b0 & 0x1f) to get SCSI_INQ_RDT */ + uint8_t b1; /* Removable Media Bit = b1 & 0x80 */ + uint8_t spc_version; /* b2 */ + uint8_t b3; /* b3 & 0x0f == resp_data_fmt == 2, must! */ + uint8_t u1[1 + 1 + 1 + 1 + 8]; /* b4..b15 unused, b4 = (N - 1) */ + char prod_id[16]; /* "QEMU CD-ROM" is here */ + uint8_t u2[4 /* b32..b35 unused, mandatory */ + + 8 + 12 + 1 + 1 + 8 * 2 + 22 /* b36..95 unused, optional*/ + + 0]; /* b96..bN unused, vendor specific */ + /* byte N */ +} __attribute__((packed)); +typedef struct ScsiInquiryStd ScsiInquiryStd; + +struct ScsiCdbInquiry { + uint8_t command; /* b0, == 0x12 */ + uint8_t b1; /* b1, |= 0x01 (evpd) */ + uint8_t b2; /* b2; if evpd==1 */ + uint16_t alloc_len; /* b3, b4 */ + uint8_t control; /* b5 */ +} __attribute__((packed)); +typedef struct ScsiCdbInquiry ScsiCdbInquiry; + +struct ScsiCdbRead10 { + uint8_t command; /* =0x28 */ + uint8_t b1; + uint32_t lba; + uint8_t b6; + uint16_t xfer_length; + uint8_t control; +} __attribute__((packed)); +typedef struct ScsiCdbRead10 ScsiCdbRead10; + +struct ScsiCdbTestUnitReady { + uint8_t command; /* =0x00 */ + uint8_t b1_b4[4]; + uint8_t control; +} __attribute__((packed)); +typedef struct ScsiCdbTestUnitReady ScsiCdbTestUnitReady; + +struct ScsiCdbReportLuns { + uint8_t command; /* =0xa0 */ + uint8_t b1; + uint8_t select_report; /* =0x02, "all" */ + uint8_t b3_b5[3]; + uint32_t alloc_len; + uint8_t b10; + uint8_t control; +} __attribute__((packed)); +typedef struct ScsiCdbReportLuns ScsiCdbReportLuns; + +struct ScsiLunReport { + uint32_t lun_list_len; + uint32_t b4_b7; + ScsiLun lun[1]; /* space for at least 1 lun must be allocated */ +} __attribute__((packed)); +typedef struct ScsiLunReport ScsiLunReport; + +struct ScsiCdbReadCapacity16 { + uint8_t command; /* =0x9e = "service action in 16" */ + uint8_t service_action; /* 5 bits, =0x10 = "read capacity 16" */ + uint64_t b2_b9; + uint32_t alloc_len; + uint8_t b14; + uint8_t control; +} __attribute__((packed)); +typedef struct ScsiCdbReadCapacity16 ScsiCdbReadCapacity16; + +struct ScsiReadCapacity16Data { + uint64_t ret_lba; /* get it, 0..7 */ + uint32_t lb_len; /* bytes, 8..11 */ + uint8_t u1[2 + 1 * 2 + 16]; /* b12..b31, unused */ +} __attribute__((packed)); +typedef struct ScsiReadCapacity16Data ScsiReadCapacity16Data; + +static inline ScsiLun make_lun(uint16_t channel, uint16_t target, uint32_t lun) +{ + ScsiLun r = { .v64 = 0 }; + + /* See QEMU code to choose the way to handle LUNs. + * + * So, a valid LUN must have (always channel #0): + * lun[0] == 1 + * lun[1] - target, any value + * lun[2] == 0 or (LUN, MSB, 0x40 set, 0x80 clear) + * lun[3] - LUN, LSB, any value + */ + r.v8[0] = 1; + r.v8[1] = target & 0xffU; + r.v8[2] = (lun >> 8) & 0x3fU; + if (r.v8[2]) { + r.v8[2] |= 0x40; + } + r.v8[3] = lun & 0xffU; + + return r; +} + +static inline const char *scsi_cdb_status_msg(uint8_t status) +{ + static char err_msg[] = "STATUS=XX"; + uint8_t v = status & 0x3eU; + + fill_hex_val(err_msg + 7, &v, 1); + return err_msg; +} + +static inline const char *scsi_cdb_asc_msg(const void *s) +{ + static char err_msg[] = "RSPN=XX KEY=XX CODE=XX QLFR=XX"; + const ScsiSense70 *p = s; + uint8_t sr = scsi_sense_response(s); + uint8_t sk = scsi_sense_key(s); + uint8_t ac = p->additional_sense_code; + uint8_t cq = p->additional_sense_code_qualifier; + + fill_hex_val(err_msg + 5, &sr, 1); + fill_hex_val(err_msg + 12, &sk, 1); + fill_hex_val(err_msg + 20, &ac, 1); + fill_hex_val(err_msg + 28, &cq, 1); + + return err_msg; +} + +#endif /* SCSI_H */ From 86aec22d482f9514ff24ea8efbbae2bd3d7203d0 Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Tue, 10 Nov 2015 15:36:13 +0100 Subject: [PATCH 10/14] pc-bios/s390-ccw: add virtio-scsi implementation Add virtio-scsi.[ch] with primary implementation of virtio-scsi. Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/virtio-scsi.c | 342 +++++++++++++++++++++++++++++++++ pc-bios/s390-ccw/virtio-scsi.h | 72 +++++++ 2 files changed, 414 insertions(+) create mode 100644 pc-bios/s390-ccw/virtio-scsi.c create mode 100644 pc-bios/s390-ccw/virtio-scsi.h diff --git a/pc-bios/s390-ccw/virtio-scsi.c b/pc-bios/s390-ccw/virtio-scsi.c new file mode 100644 index 0000000000..3bb48e917e --- /dev/null +++ b/pc-bios/s390-ccw/virtio-scsi.c @@ -0,0 +1,342 @@ +/* + * Virtio-SCSI implementation for s390 machine loader for qemu + * + * Copyright 2015 IBM Corp. + * Author: Eugene "jno" Dvurechenski + * + * This work is licensed under the terms of the GNU GPL, version 2 or (at + * your option) any later version. See the COPYING file in the top-level + * directory. + */ + +#include "s390-ccw.h" +#include "virtio.h" +#include "scsi.h" +#include "virtio-scsi.h" + +static ScsiDevice default_scsi_device; +static VirtioScsiCmdReq req; +static VirtioScsiCmdResp resp; + +static uint8_t scsi_inquiry_std_response[256]; + +static inline void vs_assert(bool term, const char **msgs) +{ + if (!term) { + int i = 0; + + sclp_print("\n! "); + while (msgs[i]) { + sclp_print(msgs[i++]); + } + panic(" !\n"); + } +} + +static void virtio_scsi_verify_response(VirtioScsiCmdResp *resp, + const char *title) +{ + const char *mr[] = { + title, ": response ", virtio_scsi_response_msg(resp), 0 + }; + const char *ms[] = { + title, + CDB_STATUS_VALID(resp->status) ? ": " : ": invalid ", + scsi_cdb_status_msg(resp->status), + resp->status == CDB_STATUS_CHECK_CONDITION ? " " : 0, + resp->sense_len ? scsi_cdb_asc_msg(resp->sense) + : "no sense data", + scsi_sense_response(resp->sense) == 0x70 ? ", sure" : "?", + 0 + }; + + vs_assert(resp->response == VIRTIO_SCSI_S_OK, mr); + vs_assert(resp->status == CDB_STATUS_GOOD, ms); +} + +static void prepare_request(VDev *vdev, const void *cdb, int cdb_size, + void *data, uint32_t data_size) +{ + const ScsiDevice *sdev = vdev->scsi_device; + + memset(&req, 0, sizeof(req)); + req.lun = make_lun(sdev->channel, sdev->target, sdev->lun); + memcpy(&req.cdb, cdb, cdb_size); + + memset(&resp, 0, sizeof(resp)); + resp.status = 0xff; /* set invalid */ + resp.response = 0xff; /* */ + + if (data && data_size) { + memset(data, 0, data_size); + } +} + +static inline void vs_io_assert(bool term, const char *msg) +{ + if (!term) { + virtio_scsi_verify_response(&resp, msg); + } +} + +static void vs_run(const char *title, VirtioCmd *cmd, VDev *vdev, + const void *cdb, int cdb_size, + void *data, uint32_t data_size) +{ + prepare_request(vdev, cdb, cdb_size, data, data_size); + vs_io_assert(virtio_run(vdev, VR_REQUEST, cmd) == 0, title); +} + +/* SCSI protocol implementation routines */ + +static bool scsi_inquiry(VDev *vdev, void *data, uint32_t data_size) +{ + ScsiCdbInquiry cdb = { + .command = 0x12, + .alloc_len = data_size < 65535 ? data_size : 65535, + }; + VirtioCmd inquiry[] = { + { &req, sizeof(req), VRING_DESC_F_NEXT }, + { &resp, sizeof(resp), VRING_DESC_F_WRITE | VRING_DESC_F_NEXT }, + { data, data_size, VRING_DESC_F_WRITE }, + }; + + vs_run("inquiry", inquiry, vdev, &cdb, sizeof(cdb), data, data_size); + + return virtio_scsi_response_ok(&resp); +} + +static bool scsi_test_unit_ready(VDev *vdev) +{ + ScsiCdbTestUnitReady cdb = { + .command = 0x00, + }; + VirtioCmd test_unit_ready[] = { + { &req, sizeof(req), VRING_DESC_F_NEXT }, + { &resp, sizeof(resp), VRING_DESC_F_WRITE }, + }; + + prepare_request(vdev, &cdb, sizeof(cdb), 0, 0); + virtio_run(vdev, VR_REQUEST, test_unit_ready); /* ignore errors here */ + + return virtio_scsi_response_ok(&resp); +} + +static bool scsi_report_luns(VDev *vdev, void *data, uint32_t data_size) +{ + ScsiCdbReportLuns cdb = { + .command = 0xa0, + .select_report = 0x02, /* REPORT ALL */ + .alloc_len = data_size, + }; + VirtioCmd report_luns[] = { + { &req, sizeof(req), VRING_DESC_F_NEXT }, + { &resp, sizeof(resp), VRING_DESC_F_WRITE | VRING_DESC_F_NEXT }, + { data, data_size, VRING_DESC_F_WRITE }, + }; + + vs_run("report luns", report_luns, + vdev, &cdb, sizeof(cdb), data, data_size); + + return virtio_scsi_response_ok(&resp); +} + +static bool scsi_read_10(VDev *vdev, + ulong sector, int sectors, void *data) +{ + int f = vdev->blk_factor; + unsigned int data_size = sectors * virtio_get_block_size() * f; + ScsiCdbRead10 cdb = { + .command = 0x28, + .lba = sector * f, + .xfer_length = sectors * f, + }; + VirtioCmd read_10[] = { + { &req, sizeof(req), VRING_DESC_F_NEXT }, + { &resp, sizeof(resp), VRING_DESC_F_WRITE | VRING_DESC_F_NEXT }, + { data, data_size * f, VRING_DESC_F_WRITE }, + }; + + debug_print_int("read_10 sector", sector); + debug_print_int("read_10 sectors", sectors); + + vs_run("read(10)", read_10, vdev, &cdb, sizeof(cdb), data, data_size); + + return virtio_scsi_response_ok(&resp); +} + +static bool scsi_read_capacity(VDev *vdev, + void *data, uint32_t data_size) +{ + ScsiCdbReadCapacity16 cdb = { + .command = 0x9e, /* SERVICE_ACTION_IN_16 */ + .service_action = 0x10, /* SA_READ_CAPACITY */ + .alloc_len = data_size, + }; + VirtioCmd read_capacity_16[] = { + { &req, sizeof(req), VRING_DESC_F_NEXT }, + { &resp, sizeof(resp), VRING_DESC_F_WRITE | VRING_DESC_F_NEXT }, + { data, data_size, VRING_DESC_F_WRITE }, + }; + + vs_run("read capacity", read_capacity_16, + vdev, &cdb, sizeof(cdb), data, data_size); + + return virtio_scsi_response_ok(&resp); +} + +/* virtio-scsi routines */ + +static void virtio_scsi_locate_device(VDev *vdev) +{ + const uint16_t channel = 0; /* again, it's what QEMU does */ + uint16_t target; + static uint8_t data[16 + 8 * 63]; + ScsiLunReport *r = (void *) data; + ScsiDevice *sdev = vdev->scsi_device; + int i, luns; + + /* QEMU has hardcoded channel #0 in many places. + * If this hardcoded value is ever changed, we'll need to add code for + * vdev->config.scsi.max_channel != 0 here. + */ + debug_print_int("config.scsi.max_channel", vdev->config.scsi.max_channel); + debug_print_int("config.scsi.max_target ", vdev->config.scsi.max_target); + debug_print_int("config.scsi.max_lun ", vdev->config.scsi.max_lun); + + for (target = 0; target <= vdev->config.scsi.max_target; target++) { + sdev->channel = channel; + sdev->target = target; /* sdev->lun will be 0 here */ + if (!scsi_report_luns(vdev, data, sizeof(data))) { + if (resp.response == VIRTIO_SCSI_S_BAD_TARGET) { + continue; + } + print_int("target", target); + virtio_scsi_verify_response(&resp, "SCSI cannot report LUNs"); + } + if (r->lun_list_len == 0) { + print_int("no LUNs for target", target); + continue; + } + luns = r->lun_list_len / 8; + debug_print_int("LUNs reported", luns); + if (luns == 1) { + /* There is no ",lun=#" arg for -device or ",lun=0" given. + * Hence, the only LUN reported. + * Usually, it's 0. + */ + sdev->lun = r->lun[0].v16[0]; /* it's returned this way */ + debug_print_int("Have to use LUN", sdev->lun); + return; /* we have to use this device */ + } + for (i = 0; i < luns; i++) { + if (r->lun[i].v64) { + /* Look for non-zero LUN - we have where to choose from */ + sdev->lun = r->lun[i].v16[0]; + debug_print_int("Will use LUN", sdev->lun); + return; /* we have found a device */ + } + } + } + panic("\n! Cannot locate virtio-scsi device !\n"); +} + +int virtio_scsi_read_many(VDev *vdev, + ulong sector, void *load_addr, int sec_num) +{ + if (!scsi_read_10(vdev, sector, sec_num, load_addr)) { + virtio_scsi_verify_response(&resp, "virtio-scsi:read_many"); + } + + return 0; +} + +static bool virtio_scsi_inquiry_response_is_cdrom(void *data) +{ + const ScsiInquiryStd *response = data; + const int resp_data_fmt = response->b3 & 0x0f; + int i; + + IPL_check(resp_data_fmt == 2, "Wrong INQUIRY response format"); + if (resp_data_fmt != 2) { + return false; /* cannot decode */ + } + + if ((response->peripheral_qdt & 0x1f) == SCSI_INQ_RDT_CDROM) { + return true; + } + + for (i = 0; i < sizeof(response->prod_id); i++) { + if (response->prod_id[i] != QEMU_CDROM_SIGNATURE[i]) { + return false; + } + } + return true; +} + +static void scsi_parse_capacity_report(void *data, + uint64_t *last_lba, uint32_t *lb_len) +{ + ScsiReadCapacity16Data *p = data; + + if (last_lba) { + *last_lba = p->ret_lba; + } + + if (lb_len) { + *lb_len = p->lb_len; + } +} + +void virtio_scsi_setup(VDev *vdev) +{ + int retry_test_unit_ready = 3; + uint8_t data[256]; + uint32_t data_size = sizeof(data); + + vdev->scsi_device = &default_scsi_device; + virtio_scsi_locate_device(vdev); + + /* We have to "ping" the device before it becomes readable */ + while (!scsi_test_unit_ready(vdev)) { + + if (!virtio_scsi_response_ok(&resp)) { + uint8_t code = resp.sense[0] & SCSI_SENSE_CODE_MASK; + uint8_t sense_key = resp.sense[2] & SCSI_SENSE_KEY_MASK; + + IPL_assert(resp.sense_len != 0, "virtio-scsi:setup: no SENSE data"); + + IPL_assert(retry_test_unit_ready && code == 0x70 && + sense_key == SCSI_SENSE_KEY_UNIT_ATTENTION, + "virtio-scsi:setup: cannot retry"); + + /* retry on CHECK_CONDITION/UNIT_ATTENTION as it + * may not designate a real error, but it may be + * a result of device reset, etc. + */ + retry_test_unit_ready--; + sleep(1); + continue; + } + + virtio_scsi_verify_response(&resp, "virtio-scsi:setup"); + } + + /* read and cache SCSI INQUIRY response */ + if (!scsi_inquiry(vdev, scsi_inquiry_std_response, + sizeof(scsi_inquiry_std_response))) { + virtio_scsi_verify_response(&resp, "virtio-scsi:setup:inquiry"); + } + + if (virtio_scsi_inquiry_response_is_cdrom(scsi_inquiry_std_response)) { + sclp_print("SCSI CD-ROM detected.\n"); + vdev->is_cdrom = true; + vdev->scsi_block_size = VIRTIO_ISO_BLOCK_SIZE; + } + + if (!scsi_read_capacity(vdev, data, data_size)) { + virtio_scsi_verify_response(&resp, "virtio-scsi:setup:read_capacity"); + } + scsi_parse_capacity_report(data, &vdev->scsi_last_block, + (uint32_t *) &vdev->scsi_block_size); +} diff --git a/pc-bios/s390-ccw/virtio-scsi.h b/pc-bios/s390-ccw/virtio-scsi.h new file mode 100644 index 0000000000..f50b38b18b --- /dev/null +++ b/pc-bios/s390-ccw/virtio-scsi.h @@ -0,0 +1,72 @@ +/* + * Virtio-SCSI definitions for s390 machine loader for qemu + * + * Copyright 2015 IBM Corp. + * Author: Eugene "jno" Dvurechenski + * + * This work is licensed under the terms of the GNU GPL, version 2 or (at + * your option) any later version. See the COPYING file in the top-level + * directory. + */ + +#ifndef VIRTIO_SCSI_H +#define VIRTIO_SCSI_H + +#include "s390-ccw.h" +#include "virtio.h" +#include "scsi.h" + +#define VIRTIO_SCSI_CDB_SIZE SCSI_DEFAULT_CDB_SIZE +#define VIRTIO_SCSI_SENSE_SIZE SCSI_DEFAULT_SENSE_SIZE + +/* command-specific response values */ +#define VIRTIO_SCSI_S_OK 0x00 +#define VIRTIO_SCSI_S_BAD_TARGET 0x03 + +#define QEMU_CDROM_SIGNATURE "QEMU CD-ROM " + +enum virtio_scsi_vq_id { + VR_CONTROL = 0, + VR_EVENT = 1, + VR_REQUEST = 2, +}; + +struct VirtioScsiCmdReq { + ScsiLun lun; + uint64_t id; + uint8_t task_attr; /* = 0 = VIRTIO_SCSI_S_SIMPLE */ + uint8_t prio; + uint8_t crn; /* = 0 */ + uint8_t cdb[VIRTIO_SCSI_CDB_SIZE]; +} __attribute__((packed)); +typedef struct VirtioScsiCmdReq VirtioScsiCmdReq; + +struct VirtioScsiCmdResp { + uint32_t sense_len; + uint32_t residual; + uint16_t status_qualifier; + uint8_t status; /* first check for .response */ + uint8_t response; /* then for .status */ + uint8_t sense[VIRTIO_SCSI_SENSE_SIZE]; +} __attribute__((packed)); +typedef struct VirtioScsiCmdResp VirtioScsiCmdResp; + +static inline const char *virtio_scsi_response_msg(const VirtioScsiCmdResp *r) +{ + static char err_msg[] = "VS RESP=XX"; + uint8_t v = r->response; + + fill_hex_val(err_msg + 8, &v, 1); + return err_msg; +} + +static inline bool virtio_scsi_response_ok(const VirtioScsiCmdResp *r) +{ + return r->response == VIRTIO_SCSI_S_OK && r->status == CDB_STATUS_GOOD; +} + +void virtio_scsi_setup(VDev *vdev); +int virtio_scsi_read_many(VDev *vdev, + ulong sector, void *load_addr, int sec_num); + +#endif /* VIRTIO_SCSI_H */ From 80ba3e249b1f817a0f5b50258c6ce0620300360e Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Tue, 10 Nov 2015 15:37:22 +0100 Subject: [PATCH 11/14] pc-bios/s390-ccw: enable virtio-scsi Make the code added before to work. Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/Makefile | 2 +- pc-bios/s390-ccw/main.c | 8 +- pc-bios/s390-ccw/virtio.c | 167 ++++++++++++++++++++++++-------------- pc-bios/s390-ccw/virtio.h | 32 ++++++++ 4 files changed, 143 insertions(+), 66 deletions(-) diff --git a/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile index 11c5dd4799..4208cb4295 100644 --- a/pc-bios/s390-ccw/Makefile +++ b/pc-bios/s390-ccw/Makefile @@ -9,7 +9,7 @@ $(call set-vpath, $(SRC_PATH)/pc-bios/s390-ccw) .PHONY : all clean build-all -OBJECTS = start.o main.o bootmap.o sclp-ascii.o virtio.o +OBJECTS = start.o main.o bootmap.o sclp-ascii.o virtio.o virtio-scsi.o CFLAGS += -fPIE -fno-stack-protector -ffreestanding -march=z900 CFLAGS += -fno-delete-null-pointer-checks -msoft-float LDFLAGS += -Wl,-pie -nostdlib diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c index 69a02feb87..1c9e0791ab 100644 --- a/pc-bios/s390-ccw/main.c +++ b/pc-bios/s390-ccw/main.c @@ -91,15 +91,11 @@ static void virtio_setup(uint64_t dev_info) } } - if (!found) { - panic("No virtio-blk device found!\n"); - } + IPL_assert(found, "No virtio device found"); virtio_setup_device(blk_schid); - if (!virtio_ipl_disk_is_valid()) { - panic("No valid hard disk detected.\n"); - } + IPL_assert(virtio_ipl_disk_is_valid(), "No valid IPL device detected"); } int main(void) diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c index 4ab4d475c0..1d34e8c1aa 100644 --- a/pc-bios/s390-ccw/virtio.c +++ b/pc-bios/s390-ccw/virtio.c @@ -10,6 +10,7 @@ #include "s390-ccw.h" #include "virtio.h" +#include "virtio-scsi.h" #define VRING_WAIT_REPLY_TIMEOUT 3 @@ -26,6 +27,8 @@ static VDev vdev = { .ring_area = ring_area, .wait_reply_timeout = VRING_WAIT_REPLY_TIMEOUT, .schid = { .one = 1 }, + .scsi_block_size = VIRTIO_SCSI_BLOCK_SIZE, + .blk_factor = 1, }; VDev *virtio_get_device(void) @@ -284,6 +287,8 @@ int virtio_read_many(ulong sector, void *load_addr, int sec_num) switch (vdev.senseid.cu_model) { case VIRTIO_ID_BLOCK: return virtio_blk_read_many(&vdev, sector, load_addr, sec_num); + case VIRTIO_ID_SCSI: + return virtio_scsi_read_many(&vdev, sector, load_addr, sec_num); } panic("\n! No readable IPL device !\n"); return -1; @@ -317,62 +322,6 @@ int virtio_read(ulong sector, void *load_addr) return virtio_read_many(sector, load_addr, 1); } -VirtioGDN virtio_guessed_disk_nature(void) -{ - return vdev.guessed_disk_nature; -} - -void virtio_assume_scsi(void) -{ - vdev.guessed_disk_nature = VIRTIO_GDN_SCSI; - switch (vdev.senseid.cu_model) { - case VIRTIO_ID_BLOCK: - vdev.config.blk.blk_size = 512; - vdev.config.blk.physical_block_exp = 0; - break; - } -} - -void virtio_assume_iso9660(void) -{ - vdev.guessed_disk_nature = VIRTIO_GDN_CDROM; - switch (vdev.senseid.cu_model) { - case VIRTIO_ID_BLOCK: - vdev.config.blk.blk_size = 2048; - vdev.config.blk.physical_block_exp = 0; - break; - } -} - -void virtio_assume_eckd(void) -{ - vdev.guessed_disk_nature = VIRTIO_GDN_DASD; - switch (vdev.senseid.cu_model) { - case VIRTIO_ID_BLOCK: - vdev.config.blk.blk_size = 4096; - vdev.config.blk.physical_block_exp = 0; - - /* this must be here to calculate code segment position */ - vdev.config.blk.geometry.heads = 15; - vdev.config.blk.geometry.sectors = 12; - break; - } -} - -bool virtio_disk_is_scsi(void) -{ - if (vdev.guessed_disk_nature == VIRTIO_GDN_SCSI) { - return true; - } - switch (vdev.senseid.cu_model) { - case VIRTIO_ID_BLOCK: - return (vdev.config.blk.geometry.heads == 255) - && (vdev.config.blk.geometry.sectors == 63) - && (virtio_get_block_size() == 512); - } - return false; -} - /* * Other supported value pairs, if any, would need to be added here. * Note: head count is always 15. @@ -392,6 +341,75 @@ static inline u8 virtio_eckd_sectors_for_block_size(int size) return 0; } +VirtioGDN virtio_guessed_disk_nature(void) +{ + return vdev.guessed_disk_nature; +} + +void virtio_assume_scsi(void) +{ + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + vdev.guessed_disk_nature = VIRTIO_GDN_SCSI; + vdev.config.blk.blk_size = VIRTIO_SCSI_BLOCK_SIZE; + vdev.config.blk.physical_block_exp = 0; + vdev.blk_factor = 1; + break; + case VIRTIO_ID_SCSI: + vdev.scsi_block_size = VIRTIO_SCSI_BLOCK_SIZE; + break; + } +} + +void virtio_assume_iso9660(void) +{ + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + vdev.guessed_disk_nature = VIRTIO_GDN_SCSI; + vdev.config.blk.blk_size = VIRTIO_ISO_BLOCK_SIZE; + vdev.config.blk.physical_block_exp = 0; + vdev.blk_factor = VIRTIO_ISO_BLOCK_SIZE / VIRTIO_SECTOR_SIZE; + break; + case VIRTIO_ID_SCSI: + vdev.scsi_block_size = VIRTIO_ISO_BLOCK_SIZE; + break; + } +} + +void virtio_assume_eckd(void) +{ + vdev.guessed_disk_nature = VIRTIO_GDN_DASD; + vdev.blk_factor = 1; + vdev.config.blk.physical_block_exp = 0; + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + vdev.config.blk.blk_size = 4096; + break; + case VIRTIO_ID_SCSI: + vdev.config.blk.blk_size = vdev.scsi_block_size; + break; + } + vdev.config.blk.geometry.heads = 15; + vdev.config.blk.geometry.sectors = + virtio_eckd_sectors_for_block_size(vdev.config.blk.blk_size); +} + +bool virtio_disk_is_scsi(void) +{ + if (vdev.guessed_disk_nature == VIRTIO_GDN_SCSI) { + return true; + } + switch (vdev.senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return (vdev.config.blk.geometry.heads == 255) + && (vdev.config.blk.geometry.sectors == 63) + && (virtio_get_block_size() == VIRTIO_SCSI_BLOCK_SIZE); + case VIRTIO_ID_SCSI: + return true; + } + return false; +} + bool virtio_disk_is_eckd(void) { const int block_size = virtio_get_block_size(); @@ -404,6 +422,8 @@ bool virtio_disk_is_eckd(void) return (vdev.config.blk.geometry.heads == 15) && (vdev.config.blk.geometry.sectors == virtio_eckd_sectors_for_block_size(block_size)); + case VIRTIO_ID_SCSI: + return false; } return false; } @@ -418,6 +438,8 @@ int virtio_get_block_size(void) switch (vdev.senseid.cu_model) { case VIRTIO_ID_BLOCK: return vdev.config.blk.blk_size << vdev.config.blk.physical_block_exp; + case VIRTIO_ID_SCSI: + return vdev.scsi_block_size; } return 0; } @@ -427,6 +449,9 @@ uint8_t virtio_get_heads(void) switch (vdev.senseid.cu_model) { case VIRTIO_ID_BLOCK: return vdev.config.blk.geometry.heads; + case VIRTIO_ID_SCSI: + return vdev.guessed_disk_nature == VIRTIO_GDN_DASD + ? vdev.config.blk.geometry.heads : 255; } return 0; } @@ -436,25 +461,33 @@ uint8_t virtio_get_sectors(void) switch (vdev.senseid.cu_model) { case VIRTIO_ID_BLOCK: return vdev.config.blk.geometry.sectors; + case VIRTIO_ID_SCSI: + return vdev.guessed_disk_nature == VIRTIO_GDN_DASD + ? vdev.config.blk.geometry.sectors : 63; } return 0; } uint64_t virtio_get_blocks(void) { + const uint64_t factor = virtio_get_block_size() / VIRTIO_SECTOR_SIZE; switch (vdev.senseid.cu_model) { case VIRTIO_ID_BLOCK: - return vdev.config.blk.capacity / - (virtio_get_block_size() / VIRTIO_SECTOR_SIZE); + return vdev.config.blk.capacity / factor; + case VIRTIO_ID_SCSI: + return vdev.scsi_last_block / factor; } return 0; } static void virtio_setup_ccw(VDev *vdev) { - int i, cfg_size; + int i, cfg_size = 0; unsigned char status = VIRTIO_CONFIG_S_DRIVER_OK; + IPL_assert(virtio_is_supported(vdev->schid), "PE"); + /* device ID has been established now */ + vdev->config.blk.blk_size = 0; /* mark "illegal" - setup started... */ vdev->guessed_disk_nature = VIRTIO_GDN_NONE; @@ -466,6 +499,11 @@ static void virtio_setup_ccw(VDev *vdev) vdev->cmd_vr_idx = 0; cfg_size = sizeof(vdev->config.blk); break; + case VIRTIO_ID_SCSI: + vdev->nr_vqs = 3; + vdev->cmd_vr_idx = VR_REQUEST; + cfg_size = sizeof(vdev->config.scsi); + break; default: panic("Unsupported virtio device\n"); } @@ -511,6 +549,7 @@ void virtio_setup_device(SubChannelId schid) switch (vdev.senseid.cu_model) { case VIRTIO_ID_BLOCK: + sclp_print("Using virtio-blk.\n"); if (!virtio_ipl_disk_is_valid()) { /* make sure all getters but blocksize return 0 for * invalid IPL disk @@ -519,6 +558,15 @@ void virtio_setup_device(SubChannelId schid) virtio_assume_scsi(); } break; + case VIRTIO_ID_SCSI: + IPL_assert(vdev.config.scsi.sense_size == VIRTIO_SCSI_SENSE_SIZE, + "Config: sense size mismatch"); + IPL_assert(vdev.config.scsi.cdb_size == VIRTIO_SCSI_CDB_SIZE, + "Config: CDB size mismatch"); + + sclp_print("Using virtio-scsi.\n"); + virtio_scsi_setup(&vdev); + break; default: panic("\n! No IPL device available !\n"); } @@ -535,6 +583,7 @@ bool virtio_is_supported(SubChannelId schid) if (vdev.senseid.cu_type == 0x3832) { switch (vdev.senseid.cu_model) { case VIRTIO_ID_BLOCK: + case VIRTIO_ID_SCSI: return true; } } diff --git a/pc-bios/s390-ccw/virtio.h b/pc-bios/s390-ccw/virtio.h index 57c71a2c93..3c6e91510e 100644 --- a/pc-bios/s390-ccw/virtio.h +++ b/pc-bios/s390-ccw/virtio.h @@ -28,6 +28,7 @@ enum VirtioDevType { VIRTIO_ID_BLOCK = 2, VIRTIO_ID_CONSOLE = 3, VIRTIO_ID_BALLOON = 5, + VIRTIO_ID_SCSI = 8, }; typedef enum VirtioDevType VirtioDevType; @@ -224,12 +225,35 @@ extern uint64_t virtio_get_blocks(void); extern int virtio_read_many(ulong sector, void *load_addr, int sec_num); #define VIRTIO_SECTOR_SIZE 512 +#define VIRTIO_ISO_BLOCK_SIZE 2048 +#define VIRTIO_SCSI_BLOCK_SIZE 512 static inline ulong virtio_sector_adjust(ulong sector) { return sector * (virtio_get_block_size() / VIRTIO_SECTOR_SIZE); } +struct VirtioScsiConfig { + uint32_t num_queues; + uint32_t seg_max; + uint32_t max_sectors; + uint32_t cmd_per_lun; + uint32_t event_info_size; + uint32_t sense_size; + uint32_t cdb_size; + uint16_t max_channel; + uint16_t max_target; + uint32_t max_lun; +} __attribute__((packed)); +typedef struct VirtioScsiConfig VirtioScsiConfig; + +struct ScsiDevice { + uint16_t channel; /* Always 0 in QEMU */ + uint16_t target; /* will be scanned over */ + uint32_t lun; /* will be reported */ +}; +typedef struct ScsiDevice ScsiDevice; + struct VDev { int nr_vqs; VRing *vrings; @@ -241,7 +265,15 @@ struct VDev { SenseId senseid; union { VirtioBlkConfig blk; + VirtioScsiConfig scsi; } config; + ScsiDevice *scsi_device; + bool is_cdrom; + int scsi_block_size; + int blk_factor; + uint64_t scsi_last_block; + uint32_t scsi_dev_cyls; + uint8_t scsi_dev_heads; }; typedef struct VDev VDev; From f0386820446a4677fd9ae9f6e02af0d1f4fbcbf6 Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Fri, 30 Oct 2015 17:28:48 +0100 Subject: [PATCH 12/14] pc-bios/s390-ccw: enhance bootmap detection Improve the algorithm that tries to guess the disk layout: 1. Use CD-ROMs to read ISO only 2. Make explicit paths for -scsi and -blk virtio Acked-by: Maxim Samoylov Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/bootmap.c | 111 +++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 35 deletions(-) diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c index 711a518fb8..54e0f179ed 100644 --- a/pc-bios/s390-ccw/bootmap.c +++ b/pc-bios/s390-ccw/bootmap.c @@ -315,6 +315,40 @@ static void print_eckd_msg(void) sclp_print(msg); } +static void ipl_eckd(void) +{ + ScsiMbr *mbr = (void *)sec; + LDL_VTOC *vlbl = (void *)sec; + + print_eckd_msg(); + + /* Grab the MBR again */ + memset(sec, FREE_SPACE_FILLER, sizeof(sec)); + read_block(0, mbr, "Cannot read block 0 on DASD"); + + if (magic_match(mbr->magic, IPL1_MAGIC)) { + ipl_eckd_cdl(); /* no return */ + } + + /* LDL/CMS? */ + memset(sec, FREE_SPACE_FILLER, sizeof(sec)); + read_block(2, vlbl, "Cannot read block 2"); + + if (magic_match(vlbl->magic, CMS1_MAGIC)) { + ipl_eckd_ldl(ECKD_CMS); /* no return */ + } + if (magic_match(vlbl->magic, LNX1_MAGIC)) { + ipl_eckd_ldl(ECKD_LDL); /* no return */ + } + + ipl_eckd_ldl(ECKD_LDL_UNLABELED); /* it still may return */ + /* + * Ok, it is not a LDL by any means. + * It still might be a CDL with zero record keys for IPL1 and IPL2 + */ + ipl_eckd_cdl(); +} + /*********************************************************************** * IPL a SCSI disk */ @@ -412,7 +446,13 @@ static void ipl_scsi(void) const int pte_len = sizeof(ScsiBlockPtr); ScsiBlockPtr *prog_table_entry; - /* The 0-th block (MBR) was already read into sec[] */ + /* Grab the MBR */ + memset(sec, FREE_SPACE_FILLER, sizeof(sec)); + read_block(0, mbr, "Cannot read block 0"); + + if (!magic_match(mbr->magic, ZIPL_MAGIC)) { + return; + } sclp_print("Using SCSI scheme.\n"); debug_print_int("MBR Version", mbr->version_id); @@ -649,57 +689,58 @@ static void ipl_iso_el_torito(void) } /*********************************************************************** - * IPL starts here + * Bus specific IPL sequences */ -void zipl_load(void) +static void zipl_load_vblk(void) { - ScsiMbr *mbr = (void *)sec; - LDL_VTOC *vlbl = (void *)sec; - - /* Grab the MBR */ - memset(sec, FREE_SPACE_FILLER, sizeof(sec)); - read_block(0, mbr, "Cannot read block 0"); - - dputs("checking magic\n"); - - if (magic_match(mbr->magic, ZIPL_MAGIC)) { - 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"); virtio_assume_eckd(); } - print_eckd_msg(); - if (magic_match(mbr->magic, IPL1_MAGIC)) { - ipl_eckd_cdl(); /* no return */ + ipl_eckd(); +} + +static void zipl_load_vscsi(void) +{ + if (virtio_get_block_size() == VIRTIO_ISO_BLOCK_SIZE) { + /* Is it an ISO image in non-CD drive? */ + ipl_iso_el_torito(); } - /* LDL/CMS? */ - memset(sec, FREE_SPACE_FILLER, sizeof(sec)); - read_block(2, vlbl, "Cannot read block 2"); + sclp_print("Using guessed DASD geometry.\n"); + virtio_assume_eckd(); + ipl_eckd(); +} - if (magic_match(vlbl->magic, CMS1_MAGIC)) { - ipl_eckd_ldl(ECKD_CMS); /* no return */ - } - if (magic_match(vlbl->magic, LNX1_MAGIC)) { - ipl_eckd_ldl(ECKD_LDL); /* no return */ +/*********************************************************************** + * IPL starts here + */ + +void zipl_load(void) +{ + if (virtio_get_device()->is_cdrom) { + ipl_iso_el_torito(); + panic("\n! Cannot IPL this ISO image !\n"); } - ipl_eckd_ldl(ECKD_LDL_UNLABELED); /* it still may return */ - /* - * Ok, it is not a LDL by any means. - * It still might be a CDL with zero record keys for IPL1 and IPL2 - */ - ipl_eckd_cdl(); + ipl_scsi(); + + switch (virtio_get_device_type()) { + case VIRTIO_ID_BLOCK: + zipl_load_vblk(); + break; + case VIRTIO_ID_SCSI: + zipl_load_vscsi(); + break; + default: + panic("\n! Unknown IPL device type !\n"); + } panic("\n* this can never happen *\n"); } From 688e697fa4dbb1751219a88ae9028fa3b63e9116 Mon Sep 17 00:00:00 2001 From: "Eugene (jno) Dvurechenski" Date: Mon, 23 Nov 2015 15:11:55 +0100 Subject: [PATCH 13/14] pc-bios/s390-ccw: disambiguation of "No zIPL magic" message Don't indicate the same error message for different conditions. Signed-off-by: Eugene (jno) Dvurechenski Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw/bootmap.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c index 54e0f179ed..611102e3ef 100644 --- a/pc-bios/s390-ccw/bootmap.c +++ b/pc-bios/s390-ccw/bootmap.c @@ -84,7 +84,7 @@ static const int max_bprs_entries = sizeof(_bprs) / sizeof(ExtEckdBlockPtr); static inline void verify_boot_info(BootInfo *bip) { - IPL_assert(magic_match(bip->magic, ZIPL_MAGIC), "No zIPL magic"); + IPL_assert(magic_match(bip->magic, ZIPL_MAGIC), "No zIPL sig in BootInfo"); IPL_assert(bip->version == BOOT_INFO_VERSION, "Wrong zIPL version"); IPL_assert(bip->bp_type == BOOT_INFO_BP_TYPE_IPL, "DASD is not for IPL"); IPL_assert(bip->dev_type == BOOT_INFO_DEV_TYPE_ECKD, "DASD is not ECKD"); @@ -416,7 +416,7 @@ static void zipl_run(ScsiBlockPtr *pte) read_block(pte->blockno, tmp_sec, "Cannot read header"); header = (ComponentHeader *)tmp_sec; - IPL_assert(magic_match(tmp_sec, ZIPL_MAGIC), "No zIPL magic"); + IPL_assert(magic_match(tmp_sec, ZIPL_MAGIC), "No zIPL magic in header"); IPL_assert(header->type == ZIPL_COMP_HEADER_IPL, "Bad header type"); dputs("start loading images\n"); @@ -465,7 +465,7 @@ static void ipl_scsi(void) read_block(mbr->blockptr.blockno, sec, "Error reading Program Table"); - IPL_assert(magic_match(sec, ZIPL_MAGIC), "No zIPL magic"); + IPL_assert(magic_match(sec, ZIPL_MAGIC), "No zIPL magic in PT"); ns_end = sec + virtio_get_block_size(); for (ns = (sec + pte_len); (ns + pte_len) < ns_end; ns += pte_len) { From ce11b0622268dc8133bb4954c1e3fae0c23b6609 Mon Sep 17 00:00:00 2001 From: Cornelia Huck Date: Fri, 18 Mar 2016 13:04:38 +0100 Subject: [PATCH 14/14] s390-ccw.img: rebuild image Contains the following changes: pc-bios/s390-ccw: add more disk layout checks pc-bios/s390-ccw: virtio_panic -> panic pc-bios/s390-ccw: add utility functions and "export" some others pc-bios/s390-ccw: qemuize types pc-bios/s390-ccw: update virtio implementation to allow up to 3 vrings pc-bios/s390-ccw: add vdev object to store all device details pc-bios/s390-ccw: make provisions for different backends pc-bios/s390-ccw: add simplified virtio call pc-bios/s390-ccw: add scsi definitions pc-bios/s390-ccw: add virtio-scsi implementation pc-bios/s390-ccw: enable virtio-scsi pc-bios/s390-ccw: enhance bootmap detection pc-bios/s390-ccw: disambiguation of "No zIPL magic" message Signed-off-by: Cornelia Huck --- pc-bios/s390-ccw.img | Bin 17760 -> 26424 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/pc-bios/s390-ccw.img b/pc-bios/s390-ccw.img index bd8f21050fd7c396eb0e37213d284fe557caa01e..d3978ba0506cb24050f596b2e4e710dec3486fba 100644 GIT binary patch literal 26424 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 literal 17760 zcmeHvdw5jUx%WGlWF}X3Lck$#>`n;EMZ*xNAlN!X5+oR45YV6=Nk|3~$*q$C!K0@+ z%4ea++L#)vt<|Xl8*QsGr9R^2m{tTt)B&;DR*#nftBvBIC@9E2zxUmH&rBko?>x`> z{`mfIKhMls>s{|{y|=aYn!pb$tLND5HcC7W8i~FMNUJsBKBM)$Ev=7cPzL!ZpYkwH z$0tqT1eoy*X>BJAu+@?qV+TG?18$#j=9Y#T@I=dc@yC*9P7iIS*BTJ3Rd~pBtSg?2 zjfNHuX>HR4VB2rPh0cz_|BcSXH_b+}K3&nB3fO!L-tjw$dpw%{&V5aa^)s zDgFB1=ifZA{g!dAD^EpxMophQ?BDV)GMRA0zUPPIfFEmY>S5X2m^{Px&~>tp_(_cm z=E-pJyBptW>bhG^7~{Dra(aL9;j5LiZ|IIaIi}_ZuRr_!H*&6-c+-oR`)?mGHuZKk zrXZe!Xffewohfh|;JL;)V8oX(C;;4uZ@Zd;PVNMp2H{UfvYGkNZsK=xJ1RdRH@%+$ zcnWEGu%RwoN6VM5=;&D95v~h`0cvfawN0ULQ`_?Py4I$8s&8xUSkn?*-cjG&zPzb5 zOrfUMRmE|jYP4j+t#t>+4~DnXzCXqtfo>@!OC~dibsK1wJ+|ErZ<<%&?v}L6J z#mM{sdOHees$Y$7W@@@4zZkT$YSdohv9g4}haDE^1>AT2rXL#T?Go(6+Fd5!yZn*( zX6CESAP4gW>rsA<$oobHos(rU`w03Jnkt#@%U;r%P|L%;d!s+vBhnW1Vxr zWrKaB?@3bJ@s&m4I_<)PNVjzY-Fh5D#(>NGzz^dypuf&6tNE!V=v8#`65KO z59Fhty=A!`F9>W3P5?8gnhn~k{7U?IOo{hWiJ#*AF^X@4RVUD2{KavKsc}TH)?^#+Ux|@Y zdlq$RXOkUtPLX+aOs$2@V!gE=+zHo#hY3_sotcRDKxk+lo!Q6ttrudAk0- zaQzXlD2Jpn4Ci$X`x`?tA3-hoi}W56=u5oX(^1%r^%Sjk*omvmZT)XUyVcs=h}pN} z5ojC0_$sZ6NPCdUrpG0$vuhvq8HP zBEyLqakJo*#JmB-6LUq}T*aDY&*uozCQIH;gzdp8rYcGOoV3W)@giduycOw{aNHt3 zMrj(k5|S2xUll&LL+Vyp^-{6G1nIX(f2{N`5o=WaH!{Qe9?0-%6Y|{tyjtQY*il49 z$aJs2wpLAvfekt#7Hn3$4I9mJGCmeLRnq@J`Z*$V7$OBRBK>jN$vk(=hrD-6lyhBy z2Xnt<2|~F~DE~?NyJYSkO!?J^SYf}656Rr)(wC}~(I@>s3*PJ6vC};=jtbsp>F<;N zz0&`a;Ozuny-{b9`IfhXV|(xA$ao*DItA9^oEQ?t9EE!;m}Az{5`~g=xp#?-W|473 z`YWaXW07|&_xDbb{sG}I_KbWz!uR$RzRkkRC%pDbf2Q=W5ni=1FLXo{d8bpCCtsT^ zIoi$M+78skcA~zK2F<{eICJG7mL3OveeVpzM_=8HmEX@2`s~^$ z75S@@Hp$3e%uHn6AN@?U{65zp+RD2Sc8XsP+|7C7U&dL@Sq1EIqT`j!(TIARL;DXK zP6_&cGZH(MG`gL+We*#5lNC`ppD@I2_=5~7RvCb%JhG?T3 z^;o{M5xF-Za{ryzk@JFZxKFIsA^p1r{AxfWa_PegSa}oh=CK$2KA#P(oQyKoiXUd17L6!ahxQOWhW(s5WUc7Cj3(~z&g5fU7|D4Uzj|to*lo`N#xW*gbE>Y>!W|A#{k#-fSMw}v!em^i;;?jugu}U(o zH=N#r(}9!Y`02c;+lrQKssFA=M9x}~`5^bRUu2mlUpC`BAbB#5;~W`}-M0T2X5YYh zLiT&)d;sg}(@N!Zw5t2!_~!1?_?GU>_||TUYk;HT*$PF<)qEP>Gh1YJ2t?|xt0ksJ z7sC2tQPfBuC`!?O0$slVbQrj9DahY4n!K}>_+~`cX0Ee1!)_B^^Eu`?cZW$$+cO-y zPI}Cb#dMWd1ZcKDLK1<~_2ba#VUaAg(&go-5FV2RdY1IZ!Cq;s>+TOZZ+Cw%wB|qq zl9@LcKVvCJtZ|CS_6i3lbIX=|Og}CCVzFw8_8(%W8@OU-pHqVq)G{U7o}kWS>h!}x zQ7HYlr0)>wv>sbDY~f~|l=+usUUu;G=eeKt5#xUOaSuh;AotdyU5oY_&|a6I#n}&X z9u?Zxr7!OZ#%c1){jq5DMx(xDyfx}Tf(m;8sO}Uhd80`05{cVI;$z(3{Zr|m;_p*O zabi;uH;){}UX4Ag7ux;yRY$bF7ga(3RQQC2XS2ME;60(c4E{Z)D>645yUqI|;r|HM zGJ#$v{lx-DA8TC?if$SIK;|k1Z<+Kj7C3SectPM@-#vvToPNtZud<)kBZ_PKBSgDo zXG|Z*QDOKzS;gUV8~c3MyUf>pRnq5KQcuz|Me5}uRs5fYcRom+$iB|(+{y#+1T%l27(MXf&fwNOQAeAZYRiP>uD)E2jr%d(5XFU{I0bfg<-qkOh zri;!SB}a$s$XJnGX4f+(*C)vpWugW^yI+eO$=9@BazFDKk@H81-nlhVL~j(iWyXG{ zobuCd5vq%XL#I%oqCvZ@z(svD@+9*fnQs@|8PdO2aBmabF3xCwG#=rs&e_c~yYNmr zIIE+nS-q8UvdV;0C02XB^ko0fI#2pD0iDF$Pi{e6;%tQoI3aP#D_bHsIX5jj^3J@@2PazsD(S45ASdB=Kr|K_;CXjbY8K=w+0OOUGZ+#i|0Yi5eQrT#mm z{%65&NEfO9nNt71$T0R7erJ8|ftrZ%Zk*{_h8`;ASHf*HxP@)+LWaUVeeS0?K})ox z+j)l0#k*!6bI8fyH>2l%*dIYZJyy&6lkC|SQd;?rhUe#`oU7M)uOVy~VmC)7T{AsKUf#US%;f<)1rD3;X z3wZ~6aV?p-;})8rjn?&8gt2yX(r+*~dq)QL-VB(FV(NGj$y+sW^L5@*hrB+f21^@ff+=l6gsbd>X3*7c&(1H9g}oy_I5#CX>C z#R97Z{yr-6>)JZ96^4ZSc`PR(;a4Id&2uiWw(&aMQn?)Sxt|#jK9>j_+_3%@!Tgy( z8f12cU`oAplnEU58kjX~^JhOZbQi6|y4GXIdvuBZK+jU;Y{b-kJr610LHo;R916$S z&-){w88AD3Ut3{k=yI;op^2LDP-?OonZPBBxCRO{n{-ww&5q)Hz zI6e}qGOExb^!^EcwX<#~7oheG!w6Q!=n{?q%e6y&@sR{S5KDy;!EK|DWf} z{bW1QL%-t)dguwhOMPg!5u1VDcB+X)?#HQ^0+Ct@AgB3OH`dPgN2q$Zmy&(R34FZa z?kJ>RBhxofEAG&c{i4NI?QA#u`~FxUVkhUaK>R7}BiEuXSK*ccdstz*dMk=?z`pQ!L~x+xL4No&o&~`z_X@MWq!$Zh4nk3!G%WM z7Tui#1t^g^9X&CQyXf0v0NY*u!;DG^sKuYP^v#s3FycC1SnI zGyh3citDr|Tv9h(`?3Bkp_vTKPCnW3Ni(AWzlV(Bz2NPSeO~OZcPfsq4dJ?!r`pL+kHM?fVvSk-GzZnv{^nS;6~i+q+wE_e8ADlZ7R*S`HU0r z1^SmY3Vx|jX#})UH>##KzY-msmQKR`X2{A#l%|8LNbym?ZApSH-i*cL>x{cycMnds z^xE<`&OUzJ9pU$o?|S+75CxIK=Z6jYi5`kSh?Bx_+4Z_&;7K>_a8TYrk(}-DOGqq! zbRA9~r?v5j&tv)t^WGqX2;4o!ov*e{*FE{KWyQC}w|O#gQ?cFuWSsh@^|CbDNci=| z?IRz>5A>A@tQY5bX@BIb(-9O;qI%!%1HU;btn-rC_DqSDNXQC`X zN)T_51N!$R-lmd@+Nz>RF7y0s%boBO_EomoSMR`j6Y=5SDrSil$7n@_Do=Sy8O$FR z9|fFCaa-w!hH@v6{t{Ch9l&^i^=1F@-QDRgd1U;)_}&e2Vk+QBVGZ63b5y<8fm&zl zCa!hReY~A5DL6vMc(sDJd@!E->v+|IcWEkK_b?@28?R7zwlaJW-vCYl2Hz>qOvc+J zYf_4}4(riDfxUx$3TqU*PWN1_3`gz!B8Ga5xPx|^R4-kj21qI9w@*~S?--ayT~D7O z+*~mqja(6fF;%q`yIY*u!XES%m0V}75>ksF7ZF(uf-Va zlQFXLFiwp=wy@$W;5b9guONl1Ld=V4u4H-0XRd@rh`+I@^2iZmjLD>?zrD4x|K#M&zNkl*Iz*I&t2`SqJAF<96XZ%Jx@c z$5G^C_fPdnJRUnF5mbV>#jiDrf;u=$aj@;TOvmkp7bYt4ArfIl)G|X~)gFi_{M1+=E{#;@hwSc_TR0Bxf!kujce|S(KQHYfWWY z`f9;@UhwuJ9_zcE@dpHd3N(8F*ixZRjS=iW2=;El{yngN$MI$Sy#agE`ET0E)3vYZ ze2&2C4uL~NTV@llo;)e*$*)o45%@*FLXFbETc1GJ!FkLUGT>GWvj?3uoXa1M(ncwK zqcnDqEj&*654r83DQNAi=>o@EhlfvNhZv{7nfkyInxOyadtV$>srvJ8h=rA z{)*<+YC~{sQ+-ff*|w&&p(vAxXX~1q8q~(RP=ngg)WIm>V0}2)P?|||>YAE^4QjYe zZEmY;P!}#%SBBbJlDJ^NkAuH5i`1$>wHgYB*MwR-R4@mG%IjKN+rof?yb)i)5YhF( z6Up3BTk2Nf@0?~Q=GE}}_MqSxLOX)1T7s=%b*1P+bVaDGb(IQ8Xg#aGCdg~3tf?$F zUw{$A;g7k(O|5I{@aJ8vhGHgB zeOp7&BpSqPY3u6Nw(DBe4iRQ9TI`dQQdxd!g<99p5Tf$=i>bPzn$)$yPzQ^k%NJHJ zuDtAfR}uX)T$SnzLpLv5XjQ0gg}Q>*)zrGOt(0g@YjfR-AUhu15Cw+C#aSk29B2HC zE7YbAm7Tk?EyS)L1Y@n($Hr{95vX;c`bP0{(k_sw3f~Sy9BbURQVll-)hQhuGWBht zhEgi8s2-v_WR;oI>{Nr@lw?F%%)mSawe)`=dk{LbZ#`eEYbFgF{!AJVrb@N)vX{z6b)T)wyp zVbd6F3BrSwp%8q;p~Z87P#b*SqAsah(Hx}8miF-aA+R}#yq9pSTD;KGts!W{VjJ>! z2v%jYx}+`Cgou)0s>dHUHn*)Biq(YG232+uH1cxpcC2X%vnB?ak=Rn7R0IS>pMT*6 z7kJf{U_(O3*S&@o5 zbZtqdMP&MQ)tX>O2QpLgZ&k3ZC5So*4?q7JN7UEjp^TnA_%3E%dG+u3&YXz!LMqnR zwW_T_=!jC@-X3gKCuXuIjBrf#USgv#qDr#Ch$?k_N*Yh(XTo_Z?*ryz{vE9K_pmg~ zX3gMIrZd^6mBzCQ_YMqoknNUx?Kt+-;)sE^kz1enV1*T4?-Z)6R+lWitZK`G&t+{=uy7-Jp&&Vrw{W-9~!dtg{M`FxK^F-9}WQYUOxlNYK| zKd3wrwX1?w*)>b!<%?8oZY!HstqDnTamD^->>;R0%&NiKSVoC*I(U?_6D?1p}hm`8))A^dkih!Apg7sYaN63AlhnO z|0{MT`>neEpWN=#^>>^IJ12h8!{5ID7JNU(*qMzM z@}1}5ofUkX&1fOdxmnjg0YB$cXu-#M5H0vRKST?DX^`{D9cag(1%28yUH^15+G@0i z&_eDp(5FG}G0>;|7HtXIooFFH4e~#4z?)|Q+MQ@CU=!eBrPp6ZwspC-(b>ZyX0GuZ z{9?E?Ar5E?Tu=n{ryK^J9*;%38 zd!FC>!oC+@dKp9JEc_c_Zxwy^f%U)+1bqcwNOLTX)-;u%caN^`--h;cnlo7TyxbF3 zoU#5`-zz83%39F!!}M6Mz-wtviq1=*vZWo-d00K{dJt!8>ZCcTD_p=UTxl#YK>&Yn z+#KwLKd+@Zx8#1^GV^k`jdj?gnV_jom}l4QEJz??`mnCQ2D>^=pP4?8`?lTTcsEI! z5Dxljpx>_Re``nl^$*s!A~$Mx*v~Ot0wPnS$Ynaj(VMW7y^N|;*IALf$L_G5I5Qpe zwU_AnTcFE0_)YrW<#5>VPlm#-6xr~J9sd8I3tFTWS@_=$|9`Lt@Wcu^Zy5Uw z``LHCuoola!FnA9n13Ahx@;&v_{CP7YNyLH6VYxe1v@*I==z6Iu>0-MxaFL=!Lrlf zg}_dZTTwUWf(}(H@i2Td;66Zd6HtkD$5VjM0IYZj{GiEaC>;L(s0O$b2je@PfFqvh z@*(gsfcFB1-%t_{?CqR}dKLiW`^mh$605?3=4H)GroPZXe<%EZq!yz>ff!#5hyRZ> zVmM@7&O1!}p>Wi*BdmX_p3}ew{y%aT{B>xkzg~yGT2uX%RDjoUO4sr4R*0^#>chO; zerG%5V<`)==B1P|t}SWj0e>Cv!$~&Z_P({iw-)%;0^eHTTMK+^f&Wh};578I^BITQ z^wXc2S!B2+K81F-E z_;X=%EO+5%|NmjL@Edrg_Zt&wokk1u;GOM~^75JL_{B>XSE|*xa9%e-omM)d^t(i* z9gVmO!*v^#;vW+SL+wu99XhV`u->syRA9HP=t zu(^(bR2mMh3uD|S|Ax6V*tmQpt`vjI8*vYv7ztfn3$FJ|SG57#z{NjzrqcSh7QWRt zl>Xm-voDP|RlFD&%!dAd1&MnoOO}Pt+!MlH~q*a;m0T6`0`pEv$g2C z{a1X3(wq1`i!OD&%p!HX0b`4wDZiF+@c8gm-6_k%|Ukxkh>N={TG%vUitt4