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
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

View File

@ -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");
}

View File

@ -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 */

View File

@ -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 */
}

View File

@ -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 */

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 "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;

View File

@ -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 */