Support for booting from virtio-scsi devices in the s390-ccw bios.

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1.4.11 (GNU/Linux)
 
 iQIcBAABAgAGBQJW86HdAAoJEN7Pa5PG8C+vku4QAI+ROiW5uMC/fVsWMtY88hzk
 WHAO4uB0xgHE/RRQrzv9yvfakb9iC/Dve40IAxocMv7r6/oY8n7vzkjaj6zujxT1
 3nIuLV35gRXdV9cgxhJHfOt4vcJ/e2rWzzhlsiuGYNk76VoXtF13QLSdPf2iQx1Y
 oSlLnU2c0OWM5itDisBpd5s+ACCYyYgdU0aIg4IHRsZCRenRx/s/N0/HJKkgvEKQ
 pk9CQhzu9kSGnmhXA6k6R0KRk8QXMVP0WmYPubVPYeDTdhdQsF8kcoFmNACzzBta
 Z3BY1NgJJLbDKOo94NIP7GbOnhgeYxywDB4Vl9rnFnhoCwhGAxwWhxcuuy1S64Nv
 z5gxdh1wCRBtCXR6RyC2zc4H0SuzBvZClAl0yqFe2GjWo2KPsdYA4zy1KI2m387T
 1odOZyGyzH3tTVb4m/uZnwrYV2Q6Rzl1irgNVzi0cWNYDVMxqion45yNjViQhM6X
 mrS5zmeKMJ2liBazl2b+0qpxa6TA6IOXD9AeM7gzx5bCSULLhX2be64MXQkLLyvr
 AVRjJOjaVnKr1BN3Llsp3/QoniHoSi1YFAzCg3Nu1zVYE8YW/M5foZ3UJlYjTnZp
 v9treg4U6bu6b/jYgZrf5oJxln1K7/vd1LwrQiJHVAgeB4m9uWusH8O5lWrf0NyC
 o61lldewYnwx+OLPVxHb
 =9nWt
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/cohuck/tags/s390x-20160324' into staging

Support for booting from virtio-scsi devices in the s390-ccw bios.

# gpg: Signature made Thu 24 Mar 2016 08:14:21 GMT using RSA key ID C6F02FAF
# gpg: Good signature from "Cornelia Huck <huckc@linux.vnet.ibm.com>"
# gpg:                 aka "Cornelia Huck <cornelia.huck@de.ibm.com>"

* remotes/cohuck/tags/s390x-20160324:
  s390-ccw.img: rebuild image
  pc-bios/s390-ccw: disambiguation of "No zIPL magic" message
  pc-bios/s390-ccw: enhance bootmap detection
  pc-bios/s390-ccw: enable virtio-scsi
  pc-bios/s390-ccw: add virtio-scsi implementation
  pc-bios/s390-ccw: add scsi definitions
  pc-bios/s390-ccw: add simplified virtio call
  pc-bios/s390-ccw: make provisions for different backends
  pc-bios/s390-ccw: add vdev object to store all device details
  pc-bios/s390-ccw: update virtio implementation to allow up to 3 vrings
  pc-bios/s390-ccw: qemuize types
  pc-bios/s390-ccw: add utility functions and "export" some others
  pc-bios/s390-ccw: virtio_panic -> panic
  pc-bios/s390-ccw: add more disk layout checks

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2016-03-24 16:24:02 +00:00
commit b68a80139e
11 changed files with 1226 additions and 300 deletions

Binary file not shown.

View File

@ -9,7 +9,7 @@ $(call set-vpath, $(SRC_PATH)/pc-bios/s390-ccw)
.PHONY : all clean build-all .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 += -fPIE -fno-stack-protector -ffreestanding -march=z900
CFLAGS += -fno-delete-null-pointer-checks -msoft-float CFLAGS += -fno-delete-null-pointer-checks -msoft-float
LDFLAGS += -Wl,-pie -nostdlib LDFLAGS += -Wl,-pie -nostdlib

View File

@ -72,7 +72,7 @@ static void jump_to_IPL_code(uint64_t address)
asm volatile("lghi 1,1\n\t" asm volatile("lghi 1,1\n\t"
"diag 1,1,0x308\n\t" "diag 1,1,0x308\n\t"
: : : "1", "memory"); : : : "1", "memory");
virtio_panic("\n! IPL returns !\n"); panic("\n! IPL returns !\n");
} }
/*********************************************************************** /***********************************************************************
@ -84,7 +84,7 @@ static const int max_bprs_entries = sizeof(_bprs) / sizeof(ExtEckdBlockPtr);
static inline void verify_boot_info(BootInfo *bip) 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->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->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"); IPL_assert(bip->dev_type == BOOT_INFO_DEV_TYPE_ECKD, "DASD is not ECKD");
@ -315,6 +315,40 @@ static void print_eckd_msg(void)
sclp_print(msg); 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 * IPL a SCSI disk
*/ */
@ -382,7 +416,7 @@ static void zipl_run(ScsiBlockPtr *pte)
read_block(pte->blockno, tmp_sec, "Cannot read header"); read_block(pte->blockno, tmp_sec, "Cannot read header");
header = (ComponentHeader *)tmp_sec; 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"); IPL_assert(header->type == ZIPL_COMP_HEADER_IPL, "Bad header type");
dputs("start loading images\n"); dputs("start loading images\n");
@ -412,16 +446,26 @@ static void ipl_scsi(void)
const int pte_len = sizeof(ScsiBlockPtr); const int pte_len = sizeof(ScsiBlockPtr);
ScsiBlockPtr *prog_table_entry; 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"); 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); debug_print_int("program table", mbr->blockptr.blockno);
IPL_assert(mbr->blockptr.blockno, "No Program Table");
/* Parse the program table */ /* Parse the program table */
read_block(mbr->blockptr.blockno, sec, read_block(mbr->blockptr.blockno, sec,
"Error reading Program Table"); "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(); ns_end = sec + virtio_get_block_size();
for (ns = (sec + pte_len); (ns + pte_len) < ns_end; ns += pte_len) { for (ns = (sec + pte_len); (ns + pte_len) < ns_end; ns += pte_len) {
@ -613,7 +657,7 @@ static IsoBcSection *find_iso_bc_entry(void)
if (!is_iso_bc_valid(e)) { if (!is_iso_bc_valid(e)) {
/* The validation entry is mandatory */ /* The validation entry is mandatory */
virtio_panic("No valid boot catalog found!\n"); panic("No valid boot catalog found!\n");
return NULL; return NULL;
} }
@ -629,7 +673,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; return NULL;
} }
@ -645,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()) { if (virtio_guessed_disk_nature()) {
virtio_assume_iso9660(); virtio_assume_iso9660();
} }
ipl_iso_el_torito(); ipl_iso_el_torito();
/* We have failed to follow the SCSI scheme, so */
if (virtio_guessed_disk_nature()) { if (virtio_guessed_disk_nature()) {
sclp_print("Using guessed DASD geometry.\n"); sclp_print("Using guessed DASD geometry.\n");
virtio_assume_eckd(); virtio_assume_eckd();
} }
print_eckd_msg(); ipl_eckd();
if (magic_match(mbr->magic, IPL1_MAGIC)) {
ipl_eckd_cdl(); /* no return */
} }
/* LDL/CMS? */ static void zipl_load_vscsi(void)
memset(sec, FREE_SPACE_FILLER, sizeof(sec)); {
read_block(2, vlbl, "Cannot read block 2"); if (virtio_get_block_size() == VIRTIO_ISO_BLOCK_SIZE) {
/* Is it an ISO image in non-CD drive? */
if (magic_match(vlbl->magic, CMS1_MAGIC)) { ipl_iso_el_torito();
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 */ sclp_print("Using guessed DASD geometry.\n");
/* virtio_assume_eckd();
* Ok, it is not a LDL by any means. ipl_eckd();
* It still might be a CDL with zero record keys for IPL1 and IPL2 }
/***********************************************************************
* IPL starts here
*/ */
ipl_eckd_cdl();
virtio_panic("\n* this can never happen *\n"); void zipl_load(void)
{
if (virtio_get_device()->is_cdrom) {
ipl_iso_el_torito();
panic("\n! Cannot IPL this ISO image !\n");
}
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");
} }

View File

@ -264,15 +264,6 @@ typedef enum {
/* utility code below */ /* utility code below */
static inline void IPL_assert(bool term, const char *message)
{
if (!term) {
sclp_print("\n! ");
sclp_print(message);
virtio_panic(" !\n"); /* no return */
}
}
static const unsigned char ebc2asc[256] = static const unsigned char ebc2asc[256] =
/* 0123456789abcdef0123456789abcdef */ /* 0123456789abcdef0123456789abcdef */
"................................" /* 1F */ "................................" /* 1F */

View File

@ -12,9 +12,8 @@
#include "virtio.h" #include "virtio.h"
char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE))); char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE)));
char ring_area[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE)));
uint64_t boot_value; 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 * Priniciples of Operations (SA22-7832-09) chapter 17 requires that
@ -23,7 +22,7 @@ static struct subchannel_id blk_schid = { .one = 1 };
*/ */
void write_subsystem_identification(void) void write_subsystem_identification(void)
{ {
struct subchannel_id *schid = (struct subchannel_id *) 184; SubChannelId *schid = (SubChannelId *) 184;
uint32_t *zeroes = (uint32_t *) 188; uint32_t *zeroes = (uint32_t *) 188;
*schid = blk_schid; *schid = blk_schid;
@ -31,14 +30,14 @@ void write_subsystem_identification(void)
} }
void virtio_panic(const char *string) void panic(const char *string)
{ {
sclp_print(string); sclp_print(string);
disabled_wait(); disabled_wait();
while (1) { } while (1) { }
} }
static bool find_dev(struct schib *schib, int dev_no) static bool find_dev(Schib *schib, int dev_no)
{ {
int i, r; int i, r;
@ -51,7 +50,7 @@ static bool find_dev(struct schib *schib, int dev_no)
if (!schib->pmcw.dnv) { if (!schib->pmcw.dnv) {
continue; continue;
} }
if (!virtio_is_blk(blk_schid)) { if (!virtio_is_supported(blk_schid)) {
continue; continue;
} }
if ((dev_no < 0) || (schib->pmcw.dev == dev_no)) { if ((dev_no < 0) || (schib->pmcw.dev == dev_no)) {
@ -64,7 +63,7 @@ static bool find_dev(struct schib *schib, int dev_no)
static void virtio_setup(uint64_t dev_info) static void virtio_setup(uint64_t dev_info)
{ {
struct schib schib; Schib schib;
int ssid; int ssid;
bool found = false; bool found = false;
uint16_t dev_no; uint16_t dev_no;
@ -92,15 +91,11 @@ static void virtio_setup(uint64_t dev_info)
} }
} }
if (!found) { IPL_assert(found, "No virtio device found");
virtio_panic("No virtio-blk device found!\n");
}
virtio_setup_block(blk_schid); virtio_setup_device(blk_schid);
if (!virtio_ipl_disk_is_valid()) { IPL_assert(virtio_ipl_disk_is_valid(), "No valid IPL device detected");
virtio_panic("No valid hard disk detected.\n");
}
} }
int main(void) int main(void)
@ -111,6 +106,6 @@ int main(void)
zipl_load(); /* no return */ 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 */ return 0; /* make compiler happy */
} }

View File

@ -45,15 +45,22 @@ typedef unsigned long long __u64;
#include "cio.h" #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 */ /* start.s */
void disabled_wait(void); void disabled_wait(void);
void consume_sclp_int(void); void consume_sclp_int(void);
/* main.c */ /* main.c */
void virtio_panic(const char *string); void panic(const char *string);
void write_subsystem_identification(void); void write_subsystem_identification(void);
extern char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE))); 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; extern uint64_t boot_value;
/* sclp-ascii.c */ /* sclp-ascii.c */
@ -63,10 +70,11 @@ void sclp_setup(void);
/* virtio.c */ /* virtio.c */
unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2, unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
ulong subchan_id, void *load_addr); ulong subchan_id, void *load_addr);
bool virtio_is_blk(struct subchannel_id schid); bool virtio_is_supported(SubChannelId schid);
void virtio_setup_block(struct subchannel_id schid); void virtio_setup_device(SubChannelId schid);
int virtio_read(ulong sector, void *load_addr); int virtio_read(ulong sector, void *load_addr);
int enable_mss_facility(void); int enable_mss_facility(void);
ulong get_second(void);
/* bootmap.c */ /* bootmap.c */
void zipl_load(void); void zipl_load(void);
@ -143,4 +151,42 @@ static inline void yield(void)
#define MAX_SECTOR_SIZE 4096 #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 */ #endif /* S390_CCW_H */

184
pc-bios/s390-ccw/scsi.h Normal file
View File

@ -0,0 +1,184 @@
/*
* SCSI definitions for s390 machine loader for qemu
*
* Copyright 2015 IBM Corp.
* Author: Eugene "jno" Dvurechenski <jno@linux.vnet.ibm.com>
*
* 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 */

View File

@ -0,0 +1,342 @@
/*
* Virtio-SCSI implementation for s390 machine loader for qemu
*
* Copyright 2015 IBM Corp.
* Author: Eugene "jno" Dvurechenski <jno@linux.vnet.ibm.com>
*
* 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);
}

View File

@ -0,0 +1,72 @@
/*
* Virtio-SCSI definitions for s390 machine loader for qemu
*
* Copyright 2015 IBM Corp.
* Author: Eugene "jno" Dvurechenski <jno@linux.vnet.ibm.com>
*
* 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 */

View File

@ -10,39 +10,68 @@
#include "s390-ccw.h" #include "s390-ccw.h"
#include "virtio.h" #include "virtio.h"
#include "virtio-scsi.h"
static struct vring block; #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 char chsc_page[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE))); 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,
.schid = { .one = 1 },
.scsi_block_size = VIRTIO_SCSI_BLOCK_SIZE,
.blk_factor = 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, 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_nr asm("1") = nr;
register ulong r_param1 asm("2") = param1; register ulong r_param1 asm("2") = param1;
register ulong r_param2 asm("3") = param2; register ulong r_param2 asm("3") = param2;
register ulong r_param3 asm("4") = param3;
register long retval asm("2"); register long retval asm("2");
asm volatile ("diag 2,4,0x500" asm volatile ("diag 2,4,0x500"
: "=d" (retval) : "=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"); : "memory", "cc");
return retval; return retval;
} }
static void virtio_notify(struct subchannel_id 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);
} }
/*********************************************** /***********************************************
* Virtio functions * * Virtio functions *
***********************************************/ ***********************************************/
static int drain_irqs(struct subchannel_id schid) static int drain_irqs(SubChannelId schid)
{ {
struct irb irb = {}; Irb irb = {};
int r = 0; int r = 0;
while (1) { while (1) {
@ -59,17 +88,17 @@ 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(VDev *vdev, int cmd, void *ptr, int len)
{ {
struct ccw1 ccw = {}; Ccw1 ccw = {};
struct cmd_orb orb = {}; CmdOrb orb = {};
struct schib schib; Schib schib;
int r; int r;
/* start command processing */ /* start command processing */
stsch_err(schid, &schib); stsch_err(vdev->schid, &schib);
schib.scsw.ctrl = SCSW_FCTL_START_FUNC; schib.scsw.ctrl = SCSW_FCTL_START_FUNC;
msch(schid, &schib); msch(vdev->schid, &schib);
/* start subchannel command */ /* start subchannel command */
orb.fmt = 1; orb.fmt = 1;
@ -80,41 +109,29 @@ static int run_ccw(struct subchannel_id schid, int cmd, void *ptr, int len)
ccw.cda = (long)ptr; ccw.cda = (long)ptr;
ccw.count = len; 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 * XXX Wait until device is done processing the CCW. For now we can
* assume that a simple tsch will have finished the CCW processing, * assume that a simple tsch will have finished the CCW processing,
* but the architecture allows for asynchronous operation * but the architecture allows for asynchronous operation
*/ */
if (!r) { if (!r) {
r = drain_irqs(schid); r = drain_irqs(vdev->schid);
} }
return r; return r;
} }
static void virtio_set_status(struct subchannel_id schid, static void vring_init(VRing *vr, VqInfo *info)
unsigned long dev_addr)
{ {
unsigned char status = dev_addr; void *p = (void *) info->queue;
if (run_ccw(schid, CCW_CMD_WRITE_STATUS, &status, sizeof(status))) {
virtio_panic("Could not write status to host!\n");
}
}
static void virtio_reset(struct subchannel_id schid)
{
run_ccw(schid, CCW_CMD_VDEV_RESET, NULL, 0);
}
static void vring_init(struct vring *vr, unsigned int num, void *p,
unsigned long align)
{
debug_print_addr("init p", p); debug_print_addr("init p", p);
vr->num = num; vr->id = info->index;
vr->num = info->num;
vr->desc = p; vr->desc = p;
vr->avail = p + num*sizeof(struct vring_desc); vr->avail = p + info->num * sizeof(VRingDesc);
vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + align-1) vr->used = (void *)(((unsigned long)&vr->avail->ring[info->num]
& ~(align - 1)); + info->align - 1) & ~(info->align - 1));
/* Zero out all relevant field */ /* Zero out all relevant field */
vr->avail->flags = 0; vr->avail->flags = 0;
@ -125,16 +142,18 @@ static void vring_init(struct vring *vr, unsigned int num, void *p,
vr->used->idx = 0; vr->used->idx = 0;
vr->used_idx = 0; vr->used_idx = 0;
vr->next_idx = 0; vr->next_idx = 0;
vr->cookie = 0;
debug_print_addr("init vr", vr); debug_print_addr("init vr", vr);
} }
static void vring_notify(struct subchannel_id 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(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 */ /* For follow-up chains we need to keep the first entry point */
if (!(flags & VRING_HIDDEN_IS_CHAIN)) { if (!(flags & VRING_HIDDEN_IS_CHAIN)) {
@ -162,11 +181,26 @@ static u64 get_clock(void)
return r; return r;
} }
static ulong get_second(void) ulong get_second(void)
{ {
return (get_clock() >> 12) / 1000000; 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. * Wait for the host to reply.
* *
@ -174,67 +208,92 @@ static ulong get_second(void)
* *
* Returns 0 on success, 1 on timeout. * Returns 0 on success, 1 on timeout.
*/ */
static int vring_wait_reply(struct vring *vr, int timeout) static int vring_wait_reply(void)
{ {
ulong target_second = get_second() + timeout; ulong target_second = get_second() + vdev.wait_reply_timeout;
struct subchannel_id schid = vr->schid;
int r = 0;
/* Wait until the used index has moved. */ /* Wait for any queue to be updated by the host */
while (vr->used->idx == vr->used_idx) { do {
vring_notify(schid); int i, r = 0;
if (timeout && (get_second() >= target_second)) {
r = 1; for (i = 0; i < vdev.nr_vqs; i++) {
break; r += vr_poll(&vdev.vrings[i]);
} }
yield(); yield();
if (r) {
return 0;
}
} while (!vdev.wait_reply_timeout || (get_second() < target_second));
return 1;
} }
vr->used_idx = vr->used->idx; int virtio_run(VDev *vdev, int vqid, VirtioCmd *cmd)
vr->next_idx = 0; {
vr->desc[0].len = 0; VRing *vr = &vdev->vrings[vqid];
vr->desc[0].flags = 0; int i = 0;
return r; 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 * * 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)
{ {
struct virtio_blk_outhdr out_hdr; VirtioBlkOuthdr out_hdr;
u8 status; u8 status;
int r; VRing *vr = &vdev->vrings[vdev->cmd_vr_idx];
/* Tell the host we want to read */ /* Tell the host we want to read */
out_hdr.type = VIRTIO_BLK_T_IN; out_hdr.type = VIRTIO_BLK_T_IN;
out_hdr.ioprio = 99; out_hdr.ioprio = 99;
out_hdr.sector = virtio_sector_adjust(sector); out_hdr.sector = virtio_sector_adjust(sector);
vring_send_buf(&block, &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 */ /* This is where we want to receive data */
vring_send_buf(&block, 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_WRITE | VRING_HIDDEN_IS_CHAIN |
VRING_DESC_F_NEXT); VRING_DESC_F_NEXT);
/* status field */ /* status field */
vring_send_buf(&block, &status, sizeof(u8), VRING_DESC_F_WRITE | vring_send_buf(vr, &status, sizeof(u8),
VRING_HIDDEN_IS_CHAIN); VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN);
/* Now we can tell the host to read */ /* Now we can tell the host to read */
vring_wait_reply(&block, 0); vring_wait_reply();
r = drain_irqs(block.schid); if (drain_irqs(vr->schid)) {
if (r) {
/* Well, whatever status is supposed to contain... */ /* Well, whatever status is supposed to contain... */
status = 1; status = 1;
} }
return status; 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);
case VIRTIO_ID_SCSI:
return virtio_scsi_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, unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
ulong subchan_id, void *load_addr) ulong subchan_id, void *load_addr)
{ {
@ -251,7 +310,7 @@ unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
sclp_print("."); sclp_print(".");
status = virtio_read_many(sec, (void *)addr, sec_num); status = virtio_read_many(sec, (void *)addr, sec_num);
if (status) { if (status) {
virtio_panic("I/O Error"); panic("I/O Error");
} }
addr += sec_num * virtio_get_block_size(); addr += sec_num * virtio_get_block_size();
@ -263,49 +322,6 @@ int virtio_read(ulong sector, void *load_addr)
return virtio_read_many(sector, load_addr, 1); 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;
}
void virtio_assume_scsi(void)
{
guessed_disk_nature = true;
blk_cfg.blk_size = 512;
blk_cfg.physical_block_exp = 0;
}
void virtio_assume_iso9660(void)
{
guessed_disk_nature = true;
blk_cfg.blk_size = 2048;
blk_cfg.physical_block_exp = 0;
}
void virtio_assume_eckd(void)
{
guessed_disk_nature = true;
blk_cfg.blk_size = 4096;
blk_cfg.physical_block_exp = 0;
/* this must be here to calculate code segment position */
blk_cfg.geometry.heads = 15;
blk_cfg.geometry.sectors = 12;
}
bool virtio_disk_is_scsi(void)
{
if (guessed_disk_nature) {
return (virtio_get_block_size() == 512);
}
return (blk_cfg.geometry.heads == 255)
&& (blk_cfg.geometry.sectors == 63)
&& (virtio_get_block_size() == 512);
}
/* /*
* Other supported value pairs, if any, would need to be added here. * Other supported value pairs, if any, would need to be added here.
* Note: head count is always 15. * Note: head count is always 15.
@ -325,16 +341,91 @@ static inline u8 virtio_eckd_sectors_for_block_size(int size)
return 0; 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) bool virtio_disk_is_eckd(void)
{ {
const int block_size = virtio_get_block_size(); const int block_size = virtio_get_block_size();
if (guessed_disk_nature) { if (vdev.guessed_disk_nature == VIRTIO_GDN_DASD) {
return (block_size == 4096); return true;
} }
return (blk_cfg.geometry.heads == 15) switch (vdev.senseid.cu_model) {
&& (blk_cfg.geometry.sectors == case VIRTIO_ID_BLOCK:
return (vdev.config.blk.geometry.heads == 15)
&& (vdev.config.blk.geometry.sectors ==
virtio_eckd_sectors_for_block_size(block_size)); virtio_eckd_sectors_for_block_size(block_size));
case VIRTIO_ID_SCSI:
return false;
}
return false;
} }
bool virtio_ipl_disk_is_valid(void) bool virtio_ipl_disk_is_valid(void)
@ -344,34 +435,80 @@ bool virtio_ipl_disk_is_valid(void)
int virtio_get_block_size(void) int virtio_get_block_size(void)
{ {
return blk_cfg.blk_size << blk_cfg.physical_block_exp; 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;
} }
uint8_t virtio_get_heads(void) uint8_t virtio_get_heads(void)
{ {
return blk_cfg.geometry.heads; 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;
} }
uint8_t virtio_get_sectors(void) uint8_t virtio_get_sectors(void)
{ {
return blk_cfg.geometry.sectors; 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) uint64_t virtio_get_blocks(void)
{ {
return blk_cfg.capacity / const uint64_t factor = virtio_get_block_size() / VIRTIO_SECTOR_SIZE;
(virtio_get_block_size() / VIRTIO_SECTOR_SIZE); switch (vdev.senseid.cu_model) {
case VIRTIO_ID_BLOCK:
return vdev.config.blk.capacity / factor;
case VIRTIO_ID_SCSI:
return vdev.scsi_last_block / factor;
}
return 0;
} }
void virtio_setup_block(struct subchannel_id schid) static void virtio_setup_ccw(VDev *vdev)
{ {
struct vq_info_block info; int i, cfg_size = 0;
struct vq_config_block config = {}; unsigned char status = VIRTIO_CONFIG_S_DRIVER_OK;
blk_cfg.blk_size = 0; /* mark "illegal" - setup started... */ IPL_assert(virtio_is_supported(vdev->schid), "PE");
guessed_disk_nature = false; /* device ID has been established now */
virtio_reset(schid); vdev->config.blk.blk_size = 0; /* mark "illegal" - setup started... */
vdev->guessed_disk_nature = VIRTIO_GDN_NONE;
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;
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");
}
IPL_assert(run_ccw(vdev, CCW_CMD_READ_CONF, &vdev->config, cfg_size) == 0,
"Could not get block device configuration");
/* /*
* Skipping CCW_CMD_READ_FEAT. We're not doing anything fancy, and * Skipping CCW_CMD_READ_FEAT. We're not doing anything fancy, and
@ -379,54 +516,84 @@ void virtio_setup_block(struct subchannel_id schid)
* expect it. * expect it.
*/ */
config.index = 0; for (i = 0; i < vdev->nr_vqs; i++) {
if (run_ccw(schid, CCW_CMD_READ_VQ_CONF, &config, sizeof(config))) { VqInfo info = {
virtio_panic("Could not get block device VQ configuration\n"); .queue = (unsigned long long) ring_area + (i * VIRTIO_RING_SIZE),
} .align = KVM_S390_VIRTIO_RING_ALIGN,
if (run_ccw(schid, CCW_CMD_READ_CONF, &blk_cfg, sizeof(blk_cfg))) { .index = i,
virtio_panic("Could not get block device configuration\n"); .num = 0,
} };
vring_init(&block, config.num, ring_area, VqConfig config = {
KVM_S390_VIRTIO_RING_ALIGN); .index = i,
.num = 0,
};
info.queue = (unsigned long long) ring_area; IPL_assert(
info.align = KVM_S390_VIRTIO_RING_ALIGN; run_ccw(vdev, CCW_CMD_READ_VQ_CONF, &config, sizeof(config)) == 0,
info.index = 0; "Could not get block device VQ configuration");
info.num = config.num; info.num = config.num;
block.schid = schid; vring_init(&vdev->vrings[i], &info);
vdev->vrings[i].schid = vdev->schid;
if (!run_ccw(schid, CCW_CMD_SET_VQ, &info, sizeof(info))) { IPL_assert(run_ccw(vdev, CCW_CMD_SET_VQ, &info, sizeof(info)) == 0,
virtio_set_status(schid, VIRTIO_CONFIG_S_DRIVER_OK); "Cannot set VQ info");
}
IPL_assert(
run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status)) == 0,
"Could not write status to host");
} }
void virtio_setup_device(SubChannelId schid)
{
vdev.schid = schid;
virtio_setup_ccw(&vdev);
switch (vdev.senseid.cu_model) {
case VIRTIO_ID_BLOCK:
sclp_print("Using virtio-blk.\n");
if (!virtio_ipl_disk_is_valid()) { if (!virtio_ipl_disk_is_valid()) {
/* make sure all getters but blocksize return 0 for invalid IPL disk */ /* make sure all getters but blocksize return 0 for
memset(&blk_cfg, 0, sizeof(blk_cfg)); * invalid IPL disk
*/
memset(&vdev.config.blk, 0, sizeof(vdev.config.blk));
virtio_assume_scsi(); 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");
}
} }
bool virtio_is_blk(struct subchannel_id schid) bool virtio_is_supported(SubChannelId schid)
{ {
int r; vdev.schid = schid;
struct senseid senseid = {}; memset(&vdev.senseid, 0, sizeof(vdev.senseid));
/* run sense id command */ /* run sense id command */
r = run_ccw(schid, CCW_CMD_SENSE_ID, &senseid, sizeof(senseid)); if (run_ccw(&vdev, CCW_CMD_SENSE_ID, &vdev.senseid, sizeof(vdev.senseid))) {
if (r) {
return false; return false;
} }
if ((senseid.cu_type != 0x3832) || (senseid.cu_model != VIRTIO_ID_BLOCK)) { if (vdev.senseid.cu_type == 0x3832) {
return false; switch (vdev.senseid.cu_model) {
} case VIRTIO_ID_BLOCK:
case VIRTIO_ID_SCSI:
return true; return true;
} }
}
return false;
}
int enable_mss_facility(void) int enable_mss_facility(void)
{ {
int ret; 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); memset(sda_area, 0, PAGE_SIZE);
sda_area->request.length = 0x0400; sda_area->request.length = 0x0400;

View File

@ -23,49 +23,58 @@
/* We've given up on this device. */ /* We've given up on this device. */
#define VIRTIO_CONFIG_S_FAILED 0x80 #define VIRTIO_CONFIG_S_FAILED 0x80
enum virtio_dev_type { enum VirtioDevType {
VIRTIO_ID_NET = 1, VIRTIO_ID_NET = 1,
VIRTIO_ID_BLOCK = 2, VIRTIO_ID_BLOCK = 2,
VIRTIO_ID_CONSOLE = 3, VIRTIO_ID_CONSOLE = 3,
VIRTIO_ID_BALLOON = 5, VIRTIO_ID_BALLOON = 5,
VIRTIO_ID_SCSI = 8,
}; };
typedef enum VirtioDevType VirtioDevType;
struct virtio_dev_header { struct VirtioDevHeader {
enum virtio_dev_type type : 8; VirtioDevType type:8;
u8 num_vq; uint8_t num_vq;
u8 feature_len; uint8_t feature_len;
u8 config_len; uint8_t config_len;
u8 status; uint8_t status;
u8 vqconfig[]; uint8_t vqconfig[];
} __attribute__((packed)); } __attribute__((packed));
typedef struct VirtioDevHeader VirtioDevHeader;
struct virtio_vqconfig { struct VirtioVqConfig {
u64 token; uint64_t token;
u64 address; uint64_t address;
u16 num; uint16_t num;
u8 pad[6]; uint8_t pad[6];
} __attribute__((packed)); } __attribute__((packed));
typedef struct VirtioVqConfig VirtioVqConfig;
struct vq_info_block { struct VqInfo {
u64 queue; uint64_t queue;
u32 align; uint32_t align;
u16 index; uint16_t index;
u16 num; uint16_t num;
} __attribute__((packed)); } __attribute__((packed));
typedef struct VqInfo VqInfo;
struct vq_config_block { struct VqConfig {
u16 index; uint16_t index;
u16 num; uint16_t num;
} __attribute__((packed)); } __attribute__((packed));
typedef struct VqConfig VqConfig;
struct virtio_dev { struct VirtioDev {
struct virtio_dev_header *header; VirtioDevHeader *header;
struct virtio_vqconfig *vqconfig; VirtioVqConfig *vqconfig;
char *host_features; char *host_features;
char *guest_features; char *guest_features;
char *config; char *config;
}; };
typedef struct VirtioDev VirtioDev;
#define VIRTIO_RING_SIZE (PAGE_SIZE * 8)
#define VIRTIO_MAX_VQS 3
#define KVM_S390_VIRTIO_RING_ALIGN 4096 #define KVM_S390_VIRTIO_RING_ALIGN 4096
#define VRING_USED_F_NO_NOTIFY 1 #define VRING_USED_F_NO_NOTIFY 1
@ -81,46 +90,53 @@ struct virtio_dev {
#define VRING_HIDDEN_IS_CHAIN 256 #define VRING_HIDDEN_IS_CHAIN 256
/* Virtio ring descriptors: 16 bytes. These can chain together via "next". */ /* Virtio ring descriptors: 16 bytes. These can chain together via "next". */
struct vring_desc { struct VRingDesc {
/* Address (guest-physical). */ /* Address (guest-physical). */
u64 addr; uint64_t addr;
/* Length. */ /* Length. */
u32 len; uint32_t len;
/* The flags as indicated above. */ /* The flags as indicated above. */
u16 flags; uint16_t flags;
/* We chain unused descriptors via this, too */ /* We chain unused descriptors via this, too */
u16 next; uint16_t next;
} __attribute__((packed)); } __attribute__((packed));
typedef struct VRingDesc VRingDesc;
struct vring_avail { struct VRingAvail {
u16 flags; uint16_t flags;
u16 idx; uint16_t idx;
u16 ring[]; uint16_t ring[];
} __attribute__((packed)); } __attribute__((packed));
typedef struct VRingAvail VRingAvail;
/* u32 is used here for ids for padding reasons. */ /* uint32_t is used here for ids for padding reasons. */
struct vring_used_elem { struct VRingUsedElem {
/* Index of start of used descriptor chain. */ /* Index of start of used descriptor chain. */
u32 id; uint32_t id;
/* Total length of the descriptor chain which was used (written to) */ /* Total length of the descriptor chain which was used (written to) */
u32 len; uint32_t len;
} __attribute__((packed)); } __attribute__((packed));
typedef struct VRingUsedElem VRingUsedElem;
struct vring_used { struct VRingUsed {
u16 flags; uint16_t flags;
u16 idx; uint16_t idx;
struct vring_used_elem ring[]; VRingUsedElem ring[];
} __attribute__((packed)); } __attribute__((packed));
typedef struct VRingUsed VRingUsed;
struct vring { struct VRing {
unsigned int num; unsigned int num;
int next_idx; int next_idx;
int used_idx; int used_idx;
struct vring_desc *desc; VRingDesc *desc;
struct vring_avail *avail; VRingAvail *avail;
struct vring_used *used; VRingUsed *used;
struct subchannel_id schid; SubChannelId schid;
long cookie;
int id;
}; };
typedef struct VRing VRing;
/*********************************************** /***********************************************
@ -152,39 +168,49 @@ struct vring {
#define VIRTIO_BLK_T_BARRIER 0x80000000 #define VIRTIO_BLK_T_BARRIER 0x80000000
/* This is the first element of the read scatter-gather list. */ /* This is the first element of the read scatter-gather list. */
struct virtio_blk_outhdr { struct VirtioBlkOuthdr {
/* VIRTIO_BLK_T* */ /* VIRTIO_BLK_T* */
u32 type; uint32_t type;
/* io priority. */ /* io priority. */
u32 ioprio; uint32_t ioprio;
/* Sector (ie. 512 byte offset) */ /* Sector (ie. 512 byte offset) */
u64 sector; uint64_t sector;
}; };
typedef struct VirtioBlkOuthdr VirtioBlkOuthdr;
typedef struct VirtioBlkConfig { struct VirtioBlkConfig {
u64 capacity; /* in 512-byte sectors */ uint64_t capacity; /* in 512-byte sectors */
u32 size_max; /* max segment size (if VIRTIO_BLK_F_SIZE_MAX) */ uint32_t size_max; /* max segment size (if VIRTIO_BLK_F_SIZE_MAX) */
u32 seg_max; /* max number of segments (if VIRTIO_BLK_F_SEG_MAX) */ uint32_t seg_max; /* max number of segments (if VIRTIO_BLK_F_SEG_MAX) */
struct virtio_blk_geometry { struct VirtioBlkGeometry {
u16 cylinders; uint16_t cylinders;
u8 heads; uint8_t heads;
u8 sectors; uint8_t sectors;
} geometry; /* (if VIRTIO_BLK_F_GEOMETRY) */ } 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 */ /* the next 4 entries are guarded by VIRTIO_BLK_F_TOPOLOGY */
u8 physical_block_exp; /* exponent for physical block per logical block */ uint8_t physical_block_exp; /* exponent for physical blk per logical blk */
u8 alignment_offset; /* alignment offset in logical blocks */ uint8_t alignment_offset; /* alignment offset in logical blocks */
u16 min_io_size; /* min I/O size without performance penalty uint16_t min_io_size; /* min I/O size without performance penalty
in logical blocks */ 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) */ uint8_t wce; /* writeback mode (if VIRTIO_BLK_F_CONFIG_WCE) */
} __attribute__((packed)) 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_scsi(void);
void virtio_assume_eckd(void); void virtio_assume_eckd(void);
void virtio_assume_iso9660(void); void virtio_assume_iso9660(void);
@ -199,10 +225,68 @@ extern uint64_t virtio_get_blocks(void);
extern int virtio_read_many(ulong sector, void *load_addr, int sec_num); extern int virtio_read_many(ulong sector, void *load_addr, int sec_num);
#define VIRTIO_SECTOR_SIZE 512 #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) static inline ulong virtio_sector_adjust(ulong sector)
{ {
return sector * (virtio_get_block_size() / VIRTIO_SECTOR_SIZE); 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;
int cmd_vr_idx;
void *ring_area;
long wait_reply_timeout;
VirtioGDN guessed_disk_nature;
SubChannelId schid;
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;
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 */ #endif /* VIRTIO_H */