diff options
author | Alexander Graf <agraf@suse.de> | 2013-04-22 21:01:00 +0200 |
---|---|---|
committer | Alexander Graf <agraf@suse.de> | 2013-04-26 20:18:24 +0200 |
commit | 1e17c2c15b354a1e6c1d1ad41bb17f7bf146e9ba (patch) | |
tree | fa84b2fe2b87cf57c56597c83cb380c1e2ce3a15 /pc-bios/s390-ccw | |
parent | 0369b2eb0707970c5f031b3ece84ccda953a8375 (diff) |
S390: ccw firmware: Add virtio device drivers
In order to boot, we need to be able to access a virtio-blk device through
the CCW bus. Implement support for this.
Signed-off-by: Alexander Graf <agraf@suse.de>
Diffstat (limited to 'pc-bios/s390-ccw')
-rw-r--r-- | pc-bios/s390-ccw/cio.h | 322 | ||||
-rw-r--r-- | pc-bios/s390-ccw/virtio.c | 274 | ||||
-rw-r--r-- | pc-bios/s390-ccw/virtio.h | 158 |
3 files changed, 754 insertions, 0 deletions
diff --git a/pc-bios/s390-ccw/cio.h b/pc-bios/s390-ccw/cio.h new file mode 100644 index 0000000000..cb5815accd --- /dev/null +++ b/pc-bios/s390-ccw/cio.h @@ -0,0 +1,322 @@ +/* + * Channel IO definitions + * + * Copyright (c) 2013 Alexander Graf <agraf@suse.de> + * + * Inspired by various s390 headers in Linux 3.9. + * + * 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 CIO_H +#define CIO_H + +/* + * path management control word + */ +struct pmcw { + __u32 intparm; /* interruption parameter */ + __u32 qf : 1; /* qdio facility */ + __u32 w : 1; + __u32 isc : 3; /* interruption sublass */ + __u32 res5 : 3; /* reserved zeros */ + __u32 ena : 1; /* enabled */ + __u32 lm : 2; /* limit mode */ + __u32 mme : 2; /* measurement-mode enable */ + __u32 mp : 1; /* multipath mode */ + __u32 tf : 1; /* timing facility */ + __u32 dnv : 1; /* device number valid */ + __u32 dev : 16; /* device number */ + __u8 lpm; /* logical path mask */ + __u8 pnom; /* path not operational mask */ + __u8 lpum; /* last path used mask */ + __u8 pim; /* path installed mask */ + __u16 mbi; /* measurement-block index */ + __u8 pom; /* path operational mask */ + __u8 pam; /* path available mask */ + __u8 chpid[8]; /* CHPID 0-7 (if available) */ + __u32 unused1 : 8; /* reserved zeros */ + __u32 st : 3; /* subchannel type */ + __u32 unused2 : 18; /* reserved zeros */ + __u32 mbfc : 1; /* measurement block format control */ + __u32 xmwme : 1; /* extended measurement word mode enable */ + __u32 csense : 1; /* concurrent sense; can be enabled ...*/ + /* ... per MSCH, however, if facility */ + /* ... is not installed, this results */ + /* ... in an operand exception. */ +} __attribute__ ((packed)); + +/* Target SCHIB configuration. */ +struct schib_config { + __u64 mba; + __u32 intparm; + __u16 mbi; + __u32 isc:3; + __u32 ena:1; + __u32 mme:2; + __u32 mp:1; + __u32 csense:1; + __u32 mbfc:1; +} __attribute__ ((packed)); + +struct scsw { + __u16 flags; + __u16 ctrl; + __u32 cpa; + __u8 dstat; + __u8 cstat; + __u16 count; +} __attribute__ ((packed)); + +#define SCSW_FCTL_CLEAR_FUNC 0x1000 +#define SCSW_FCTL_HALT_FUNC 0x2000 +#define SCSW_FCTL_START_FUNC 0x4000 + +/* + * subchannel information block + */ +struct schib { + struct pmcw pmcw; /* path management control word */ + struct scsw scsw; /* subchannel status word */ + __u64 mba; /* measurement block address */ + __u8 mda[4]; /* model dependent area */ +} __attribute__ ((packed,aligned(4))); + +struct subchannel_id { + __u32 cssid : 8; + __u32 : 4; + __u32 m : 1; + __u32 ssid : 2; + __u32 one : 1; + __u32 sch_no : 16; +} __attribute__ ((packed, aligned(4))); + +/* + * TPI info structure + */ +struct tpi_info { + struct subchannel_id schid; + __u32 intparm; /* interruption parameter */ + __u32 adapter_IO : 1; + __u32 reserved2 : 1; + __u32 isc : 3; + __u32 reserved3 : 12; + __u32 int_type : 3; + __u32 reserved4 : 12; +} __attribute__ ((packed)); + +/* channel command word (type 1) */ +struct ccw1 { + __u8 cmd_code; + __u8 flags; + __u16 count; + __u32 cda; +} __attribute__ ((packed)); + +#define CCW_FLAG_DC 0x80 +#define CCW_FLAG_CC 0x40 +#define CCW_FLAG_SLI 0x20 +#define CCW_FLAG_SKIP 0x10 +#define CCW_FLAG_PCI 0x08 +#define CCW_FLAG_IDA 0x04 +#define CCW_FLAG_SUSPEND 0x02 + +#define CCW_CMD_NOOP 0x03 +#define CCW_CMD_BASIC_SENSE 0x04 +#define CCW_CMD_TIC 0x08 +#define CCW_CMD_SENSE_ID 0xe4 + +#define CCW_CMD_SET_VQ 0x13 +#define CCW_CMD_VDEV_RESET 0x33 +#define CCW_CMD_READ_FEAT 0x12 +#define CCW_CMD_WRITE_FEAT 0x11 +#define CCW_CMD_READ_CONF 0x22 +#define CCW_CMD_WRITE_CONF 0x21 +#define CCW_CMD_WRITE_STATUS 0x31 +#define CCW_CMD_SET_IND 0x43 +#define CCW_CMD_SET_CONF_IND 0x53 +#define CCW_CMD_READ_VQ_CONF 0x32 + +/* + * Command-mode operation request block + */ +struct cmd_orb { + __u32 intparm; /* interruption parameter */ + __u32 key:4; /* flags, like key, suspend control, etc. */ + __u32 spnd:1; /* suspend control */ + __u32 res1:1; /* reserved */ + __u32 mod:1; /* modification control */ + __u32 sync:1; /* synchronize control */ + __u32 fmt:1; /* format control */ + __u32 pfch:1; /* prefetch control */ + __u32 isic:1; /* initial-status-interruption control */ + __u32 alcc:1; /* address-limit-checking control */ + __u32 ssic:1; /* suppress-suspended-interr. control */ + __u32 res2:1; /* reserved */ + __u32 c64:1; /* IDAW/QDIO 64 bit control */ + __u32 i2k:1; /* IDAW 2/4kB block size control */ + __u32 lpm:8; /* logical path mask */ + __u32 ils:1; /* incorrect length */ + __u32 zero:6; /* reserved zeros */ + __u32 orbx:1; /* ORB extension control */ + __u32 cpa; /* channel program address */ +} __attribute__ ((packed, aligned(4))); + +struct ciw { + __u8 type; + __u8 command; + __u16 count; +}; + +/* + * sense-id response buffer layout + */ +struct senseid { + /* common part */ + __u8 reserved; /* always 0x'FF' */ + __u16 cu_type; /* control unit type */ + __u8 cu_model; /* control unit model */ + __u16 dev_type; /* device type */ + __u8 dev_model; /* device model */ + __u8 unused; /* padding byte */ + /* extended part */ + struct ciw ciw[62]; +} __attribute__ ((packed, aligned(4))); + +/* interruption response block */ +struct irb { + struct scsw scsw; + __u32 esw[5]; + __u32 ecw[8]; + __u32 emw[8]; +} __attribute__ ((packed, aligned(4))); + +/* + * Some S390 specific IO instructions as inline + */ + +static inline int stsch_err(struct subchannel_id schid, struct schib *addr) +{ + register struct subchannel_id reg1 asm ("1") = schid; + int ccode = -EIO; + + asm volatile( + " stsch 0(%3)\n" + "0: ipm %0\n" + " srl %0,28\n" + "1:\n" + : "+d" (ccode), "=m" (*addr) + : "d" (reg1), "a" (addr) + : "cc"); + return ccode; +} + +static inline int msch(struct subchannel_id schid, struct schib *addr) +{ + register struct subchannel_id reg1 asm ("1") = schid; + int ccode; + + asm volatile( + " msch 0(%2)\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) + : "d" (reg1), "a" (addr), "m" (*addr) + : "cc"); + return ccode; +} + +static inline int msch_err(struct subchannel_id schid, struct schib *addr) +{ + register struct subchannel_id reg1 asm ("1") = schid; + int ccode = -EIO; + + asm volatile( + " msch 0(%2)\n" + "0: ipm %0\n" + " srl %0,28\n" + "1:\n" + : "+d" (ccode) + : "d" (reg1), "a" (addr), "m" (*addr) + : "cc"); + return ccode; +} + +static inline int tsch(struct subchannel_id schid, struct irb *addr) +{ + register struct subchannel_id reg1 asm ("1") = schid; + int ccode; + + asm volatile( + " tsch 0(%3)\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode), "=m" (*addr) + : "d" (reg1), "a" (addr) + : "cc"); + return ccode; +} + +static inline int ssch(struct subchannel_id schid, struct cmd_orb *addr) +{ + register struct subchannel_id reg1 asm("1") = schid; + int ccode = -EIO; + + asm volatile( + " ssch 0(%2)\n" + "0: ipm %0\n" + " srl %0,28\n" + "1:\n" + : "+d" (ccode) + : "d" (reg1), "a" (addr), "m" (*addr) + : "cc", "memory"); + return ccode; +} + +static inline int csch(struct subchannel_id schid) +{ + register struct subchannel_id reg1 asm("1") = schid; + int ccode; + + asm volatile( + " csch\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) + : "d" (reg1) + : "cc"); + return ccode; +} + +static inline int tpi(struct tpi_info *addr) +{ + int ccode; + + asm volatile( + " tpi 0(%2)\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode), "=m" (*addr) + : "a" (addr) + : "cc"); + return ccode; +} + +static inline int chsc(void *chsc_area) +{ + typedef struct { char _[4096]; } addr_type; + int cc; + + asm volatile( + " .insn rre,0xb25f0000,%2,0\n" + " ipm %0\n" + " srl %0,28\n" + : "=d" (cc), "=m" (*(addr_type *) chsc_area) + : "d" (chsc_area), "m" (*(addr_type *) chsc_area) + : "cc"); + return cc; +} + +#endif /* CIO_H */ diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c new file mode 100644 index 0000000000..79e2941618 --- /dev/null +++ b/pc-bios/s390-ccw/virtio.c @@ -0,0 +1,274 @@ +/* + * Virtio driver bits + * + * Copyright (c) 2013 Alexander Graf <agraf@suse.de> + * + * 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" + +struct vring block; + +static long kvm_hypercall(unsigned long nr, unsigned long param1, + unsigned long param2) +{ + register ulong r_nr asm("1") = nr; + register ulong r_param1 asm("2") = param1; + register ulong r_param2 asm("3") = param2; + register long retval asm("2"); + + asm volatile ("diag 2,4,0x500" + : "=d" (retval) + : "d" (r_nr), "0" (r_param1), "r"(r_param2) + : "memory", "cc"); + + return retval; +} + +static void virtio_notify(struct subchannel_id schid) +{ + kvm_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY, *(u32*)&schid, 0); +} + +/*********************************************** + * Virtio functions * + ***********************************************/ + +static void drain_irqs(struct subchannel_id schid) +{ + struct irb irb = {}; + while (1) { + if (tsch(schid, &irb)) { + return; + } + } +} + +static int run_ccw(struct subchannel_id schid, int cmd, void *ptr, int len) +{ + struct ccw1 ccw = {}; + struct cmd_orb orb = {}; + struct schib schib; + int r; + + /* start command processing */ + stsch_err(schid, &schib); + schib.scsw.ctrl = SCSW_FCTL_START_FUNC; + msch(schid, &schib); + + /* start subchannel command */ + orb.fmt = 1; + orb.cpa = (u32)(long)&ccw; + orb.lpm = 0x80; + + ccw.cmd_code = cmd; + ccw.cda = (long)ptr; + ccw.count = len; + + r = ssch(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 + */ + drain_irqs(schid); + return r; +} + +static void virtio_set_status(struct subchannel_id schid, + unsigned long dev_addr) +{ + unsigned char status = dev_addr; + run_ccw(schid, CCW_CMD_WRITE_STATUS, &status, sizeof(status)); +} + +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->desc = p; + vr->avail = p + num*sizeof(struct vring_desc); + vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + align-1) + & ~(align - 1)); + + /* We're running with interrupts off anyways, so don't bother */ + vr->used->flags = VRING_USED_F_NO_NOTIFY; + + debug_print_addr("init vr", vr); +} + +static void vring_notify(struct subchannel_id schid) +{ + virtio_notify(schid); +} + +static void vring_send_buf(struct 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)) { + vr->avail->ring[vr->avail->idx % vr->num] = vr->next_idx; + } + + vr->desc[vr->next_idx].addr = (ulong)p; + vr->desc[vr->next_idx].len = len; + vr->desc[vr->next_idx].flags = flags & ~VRING_HIDDEN_IS_CHAIN; + vr->desc[vr->next_idx].next = ++vr->next_idx; + + /* Chains only have a single ID */ + if (!(flags & VRING_DESC_F_NEXT)) { + vr->avail->idx++; + } + + vr->used->idx = vr->next_idx; +} + +static u64 get_clock(void) +{ + u64 r; + + asm volatile("stck %0" : "=Q" (r) : : "cc"); + return r; +} + +static ulong get_second(void) +{ + return (get_clock() >> 12) / 1000000; +} + +/* + * Wait for the host to reply. + * + * timeout is in seconds if > 0. + * + * Returns 0 on success, 1 on timeout. + */ +static int vring_wait_reply(struct vring *vr, int timeout) +{ + ulong target_second = get_second() + timeout; + struct subchannel_id schid = vr->schid; + int r = 0; + + while (vr->used->idx == vr->next_idx) { + vring_notify(schid); + if (timeout && (get_second() >= target_second)) { + r = 1; + break; + } + yield(); + } + + vr->next_idx = 0; + vr->desc[0].len = 0; + vr->desc[0].flags = 0; + + return r; +} + +/*********************************************** + * Virtio block * + ***********************************************/ + +static int virtio_read_many(ulong sector, void *load_addr, int sec_num) +{ + struct virtio_blk_outhdr out_hdr; + u8 status; + + /* Tell the host we want to read */ + out_hdr.type = VIRTIO_BLK_T_IN; + out_hdr.ioprio = 99; + out_hdr.sector = sector; + + vring_send_buf(&block, &out_hdr, sizeof(out_hdr), VRING_DESC_F_NEXT); + + /* This is where we want to receive data */ + vring_send_buf(&block, load_addr, SECTOR_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); + + /* Now we can tell the host to read */ + vring_wait_reply(&block, 0); + + drain_irqs(block.schid); + + return status; +} + +unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2, + ulong subchan_id, void *load_addr) +{ + u8 status; + int sec = rec_list1; + int sec_num = (((rec_list2 >> 32)+ 1) & 0xffff); + int sec_len = rec_list2 >> 48; + ulong addr = (ulong)load_addr; + + if (sec_len != SECTOR_SIZE) { + return -1; + } + + sclp_print("."); + status = virtio_read_many(sec, (void*)addr, sec_num); + if (status) { + virtio_panic("I/O Error"); + } + addr += sec_num * SECTOR_SIZE; + + return addr; +} + +int virtio_read(ulong sector, void *load_addr) +{ + return virtio_read_many(sector, load_addr, 1); +} + +void virtio_setup_block(struct subchannel_id schid) +{ + struct vq_info_block info; + + virtio_reset(schid); + + /* XXX need to fetch the 128 from host */ + vring_init(&block, 128, (void*)(100 * 1024 * 1024), + KVM_S390_VIRTIO_RING_ALIGN); + + info.queue = (100ULL * 1024ULL* 1024ULL); + info.align = KVM_S390_VIRTIO_RING_ALIGN; + info.index = 0; + info.num = 128; + block.schid = schid; + + run_ccw(schid, CCW_CMD_SET_VQ, &info, sizeof(info)); + virtio_set_status(schid, VIRTIO_CONFIG_S_DRIVER_OK); +} + +bool virtio_is_blk(struct subchannel_id schid) +{ + int r; + struct senseid senseid = {}; + + /* run sense id command */ + r = run_ccw(schid, CCW_CMD_SENSE_ID, &senseid, sizeof(senseid)); + if (r) { + return false; + } + if ((senseid.cu_type != 0x3832) || (senseid.cu_model != VIRTIO_ID_BLOCK)) { + return false; + } + + return true; +} + diff --git a/pc-bios/s390-ccw/virtio.h b/pc-bios/s390-ccw/virtio.h new file mode 100644 index 0000000000..a33199dd08 --- /dev/null +++ b/pc-bios/s390-ccw/virtio.h @@ -0,0 +1,158 @@ +/* + * Virtio driver bits + * + * Copyright (c) 2013 Alexander Graf <agraf@suse.de> + * + * 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_H +#define VIRTIO_H + +#include "s390-ccw.h" + +/* Status byte for guest to report progress, and synchronize features. */ +/* We have seen device and processed generic fields (VIRTIO_CONFIG_F_VIRTIO) */ +#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1 +/* We have found a driver for the device. */ +#define VIRTIO_CONFIG_S_DRIVER 2 +/* Driver has used its parts of the config, and is happy */ +#define VIRTIO_CONFIG_S_DRIVER_OK 4 +/* We've given up on this device. */ +#define VIRTIO_CONFIG_S_FAILED 0x80 + +enum virtio_dev_type { + VIRTIO_ID_NET = 1, + VIRTIO_ID_BLOCK = 2, + VIRTIO_ID_CONSOLE = 3, + VIRTIO_ID_BALLOON = 5, +}; + +struct virtio_dev_header { + enum virtio_dev_type type : 8; + u8 num_vq; + u8 feature_len; + u8 config_len; + u8 status; + u8 vqconfig[]; +} __attribute__((packed)); + +struct virtio_vqconfig { + u64 token; + u64 address; + u16 num; + u8 pad[6]; +} __attribute__((packed)); + +struct vq_info_block { + u64 queue; + u32 align; + u16 index; + u16 num; +} __attribute__((packed)); + +struct virtio_dev { + struct virtio_dev_header *header; + struct virtio_vqconfig *vqconfig; + char *host_features; + char *guest_features; + char *config; +}; + +#define KVM_S390_VIRTIO_RING_ALIGN 4096 + +#define VRING_USED_F_NO_NOTIFY 1 + +/* This marks a buffer as continuing via the next field. */ +#define VRING_DESC_F_NEXT 1 +/* This marks a buffer as write-only (otherwise read-only). */ +#define VRING_DESC_F_WRITE 2 +/* This means the buffer contains a list of buffer descriptors. */ +#define VRING_DESC_F_INDIRECT 4 + +/* Internal flag to mark follow-up segments as such */ +#define VRING_HIDDEN_IS_CHAIN 256 + +/* Virtio ring descriptors: 16 bytes. These can chain together via "next". */ +struct vring_desc { + /* Address (guest-physical). */ + u64 addr; + /* Length. */ + u32 len; + /* The flags as indicated above. */ + u16 flags; + /* We chain unused descriptors via this, too */ + u16 next; +} __attribute__((packed)); + +struct vring_avail { + u16 flags; + u16 idx; + u16 ring[]; +} __attribute__((packed)); + +/* u32 is used here for ids for padding reasons. */ +struct vring_used_elem { + /* Index of start of used descriptor chain. */ + u32 id; + /* Total length of the descriptor chain which was used (written to) */ + u32 len; +} __attribute__((packed)); + +struct vring_used { + u16 flags; + u16 idx; + struct vring_used_elem ring[]; +} __attribute__((packed)); + +struct vring { + unsigned int num; + int next_idx; + struct vring_desc *desc; + struct vring_avail *avail; + struct vring_used *used; + struct subchannel_id schid; +}; + + +/*********************************************** + * Virtio block * + ***********************************************/ + +/* + * Command types + * + * Usage is a bit tricky as some bits are used as flags and some are not. + * + * Rules: + * VIRTIO_BLK_T_OUT may be combined with VIRTIO_BLK_T_SCSI_CMD or + * VIRTIO_BLK_T_BARRIER. VIRTIO_BLK_T_FLUSH is a command of its own + * and may not be combined with any of the other flags. + */ + +/* These two define direction. */ +#define VIRTIO_BLK_T_IN 0 +#define VIRTIO_BLK_T_OUT 1 + +/* This bit says it's a scsi command, not an actual read or write. */ +#define VIRTIO_BLK_T_SCSI_CMD 2 + +/* Cache flush command */ +#define VIRTIO_BLK_T_FLUSH 4 + +/* Barrier before this op. */ +#define VIRTIO_BLK_T_BARRIER 0x80000000 + +/* This is the first element of the read scatter-gather list. */ +struct virtio_blk_outhdr { + /* VIRTIO_BLK_T* */ + u32 type; + /* io priority. */ + u32 ioprio; + /* Sector (ie. 512 byte offset) */ + u64 sector; +}; + +#endif /* VIRTIO_H */ |