mirror of https://github.com/xqemu/xqemu.git
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:
commit
b68a80139e
Binary file not shown.
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -382,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");
|
||||
|
@ -412,16 +446,26 @@ 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);
|
||||
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,
|
||||
"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) {
|
||||
|
@ -613,7 +657,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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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()) {
|
||||
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 */
|
||||
}
|
||||
|
||||
/* 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();
|
||||
|
||||
virtio_panic("\n* this can never happen *\n");
|
||||
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();
|
||||
}
|
||||
|
||||
sclp_print("Using guessed DASD geometry.\n");
|
||||
virtio_assume_eckd();
|
||||
ipl_eckd();
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* 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_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");
|
||||
}
|
||||
|
|
|
@ -264,15 +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);
|
||||
virtio_panic(" !\n"); /* no return */
|
||||
}
|
||||
}
|
||||
|
||||
static const unsigned char ebc2asc[256] =
|
||||
/* 0123456789abcdef0123456789abcdef */
|
||||
"................................" /* 1F */
|
||||
|
|
|
@ -12,9 +12,8 @@
|
|||
#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 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 +22,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;
|
||||
|
@ -31,14 +30,14 @@ void write_subsystem_identification(void)
|
|||
}
|
||||
|
||||
|
||||
void virtio_panic(const char *string)
|
||||
void panic(const char *string)
|
||||
{
|
||||
sclp_print(string);
|
||||
disabled_wait();
|
||||
while (1) { }
|
||||
}
|
||||
|
||||
static bool find_dev(struct schib *schib, int dev_no)
|
||||
static bool find_dev(Schib *schib, int dev_no)
|
||||
{
|
||||
int i, r;
|
||||
|
||||
|
@ -51,7 +50,7 @@ static bool find_dev(struct 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)) {
|
||||
|
@ -64,7 +63,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;
|
||||
|
@ -92,15 +91,11 @@ static void virtio_setup(uint64_t dev_info)
|
|||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
virtio_panic("No virtio-blk device found!\n");
|
||||
}
|
||||
IPL_assert(found, "No virtio device found");
|
||||
|
||||
virtio_setup_block(blk_schid);
|
||||
virtio_setup_device(blk_schid);
|
||||
|
||||
if (!virtio_ipl_disk_is_valid()) {
|
||||
virtio_panic("No valid hard disk detected.\n");
|
||||
}
|
||||
IPL_assert(virtio_ipl_disk_is_valid(), "No valid IPL device detected");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
|
@ -111,6 +106,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 */
|
||||
}
|
||||
|
|
|
@ -45,15 +45,22 @@ 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);
|
||||
|
||||
/* 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)));
|
||||
extern uint64_t boot_value;
|
||||
|
||||
/* sclp-ascii.c */
|
||||
|
@ -63,10 +70,11 @@ 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_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);
|
||||
|
||||
/* bootmap.c */
|
||||
void zipl_load(void);
|
||||
|
@ -143,4 +151,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 */
|
||||
|
|
|
@ -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 */
|
|
@ -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);
|
||||
}
|
|
@ -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 */
|
|
@ -10,39 +10,68 @@
|
|||
|
||||
#include "s390-ccw.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 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,
|
||||
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(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 *
|
||||
***********************************************/
|
||||
|
||||
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,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 = {};
|
||||
struct cmd_orb orb = {};
|
||||
struct schib schib;
|
||||
Ccw1 ccw = {};
|
||||
CmdOrb orb = {};
|
||||
Schib schib;
|
||||
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;
|
||||
|
@ -80,41 +109,29 @@ static int run_ccw(struct subchannel_id 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(struct subchannel_id schid,
|
||||
unsigned long dev_addr)
|
||||
static void vring_init(VRing *vr, VqInfo *info)
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
void *p = (void *) info->queue;
|
||||
|
||||
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);
|
||||
vr->num = num;
|
||||
vr->id = info->index;
|
||||
vr->num = info->num;
|
||||
vr->desc = p;
|
||||
vr->avail = p + num*sizeof(struct vring_desc);
|
||||
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,16 +142,18 @@ static void vring_init(struct 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(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 */
|
||||
if (!(flags & VRING_HIDDEN_IS_CHAIN)) {
|
||||
|
@ -162,11 +181,26 @@ static u64 get_clock(void)
|
|||
return r;
|
||||
}
|
||||
|
||||
static ulong get_second(void)
|
||||
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,67 +208,92 @@ static 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(void)
|
||||
{
|
||||
ulong target_second = get_second() + timeout;
|
||||
struct subchannel_id schid = vr->schid;
|
||||
int r = 0;
|
||||
ulong target_second = get_second() + vdev.wait_reply_timeout;
|
||||
|
||||
/* 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 < vdev.nr_vqs; i++) {
|
||||
r += vr_poll(&vdev.vrings[i]);
|
||||
}
|
||||
yield();
|
||||
if (r) {
|
||||
return 0;
|
||||
}
|
||||
} while (!vdev.wait_reply_timeout || (get_second() < target_second));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
vr->used_idx = vr->used->idx;
|
||||
vr->next_idx = 0;
|
||||
vr->desc[0].len = 0;
|
||||
vr->desc[0].flags = 0;
|
||||
|
||||
return r;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/***********************************************
|
||||
* 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;
|
||||
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, &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, 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, &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(&block, 0);
|
||||
vring_wait_reply();
|
||||
|
||||
r = drain_irqs(block.schid);
|
||||
if (r) {
|
||||
if (drain_irqs(vr->schid)) {
|
||||
/* Well, whatever status is supposed to contain... */
|
||||
status = 1;
|
||||
}
|
||||
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,
|
||||
ulong subchan_id, void *load_addr)
|
||||
{
|
||||
|
@ -251,7 +310,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();
|
||||
|
||||
|
@ -263,49 +322,6 @@ 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;
|
||||
}
|
||||
|
||||
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.
|
||||
* Note: head count is always 15.
|
||||
|
@ -325,16 +341,91 @@ 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();
|
||||
|
||||
if (guessed_disk_nature) {
|
||||
return (block_size == 4096);
|
||||
if (vdev.guessed_disk_nature == VIRTIO_GDN_DASD) {
|
||||
return true;
|
||||
}
|
||||
return (blk_cfg.geometry.heads == 15)
|
||||
&& (blk_cfg.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));
|
||||
case VIRTIO_ID_SCSI:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool virtio_ipl_disk_is_valid(void)
|
||||
|
@ -344,34 +435,80 @@ bool virtio_ipl_disk_is_valid(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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return blk_cfg.capacity /
|
||||
(virtio_get_block_size() / VIRTIO_SECTOR_SIZE);
|
||||
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 / 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;
|
||||
struct vq_config_block config = {};
|
||||
int i, cfg_size = 0;
|
||||
unsigned char status = VIRTIO_CONFIG_S_DRIVER_OK;
|
||||
|
||||
blk_cfg.blk_size = 0; /* mark "illegal" - setup started... */
|
||||
guessed_disk_nature = false;
|
||||
IPL_assert(virtio_is_supported(vdev->schid), "PE");
|
||||
/* 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
|
||||
|
@ -379,54 +516,84 @@ void virtio_setup_block(struct subchannel_id schid)
|
|||
* expect it.
|
||||
*/
|
||||
|
||||
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");
|
||||
}
|
||||
if (run_ccw(schid, CCW_CMD_READ_CONF, &blk_cfg, sizeof(blk_cfg))) {
|
||||
virtio_panic("Could not get block device configuration\n");
|
||||
}
|
||||
vring_init(&block, config.num, ring_area,
|
||||
KVM_S390_VIRTIO_RING_ALIGN);
|
||||
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,
|
||||
.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(vdev, CCW_CMD_READ_VQ_CONF, &config, sizeof(config)) == 0,
|
||||
"Could not get block device VQ configuration");
|
||||
info.num = config.num;
|
||||
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");
|
||||
}
|
||||
IPL_assert(
|
||||
run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status)) == 0,
|
||||
"Could not write status to host");
|
||||
}
|
||||
|
||||
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));
|
||||
virtio_assume_scsi();
|
||||
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()) {
|
||||
/* 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;
|
||||
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;
|
||||
struct 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:
|
||||
case VIRTIO_ID_SCSI:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -23,49 +23,58 @@
|
|||
/* 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,
|
||||
VIRTIO_ID_SCSI = 8,
|
||||
};
|
||||
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 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
|
||||
|
@ -81,46 +90,53 @@ 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;
|
||||
long cookie;
|
||||
int id;
|
||||
};
|
||||
typedef struct VRing VRing;
|
||||
|
||||
|
||||
/***********************************************
|
||||
|
@ -152,39 +168,49 @@ 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);
|
||||
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);
|
||||
|
@ -199,10 +225,68 @@ 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;
|
||||
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 */
|
||||
|
|
Loading…
Reference in New Issue