summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sys/conf/files.powerpc1
-rw-r--r--sys/powerpc/pseries/phyp_vscsi.c1003
-rw-r--r--sys/powerpc/pseries/vdevice.c32
3 files changed, 1035 insertions, 1 deletions
diff --git a/sys/conf/files.powerpc b/sys/conf/files.powerpc
index 7dbdf6b..fe9f876 100644
--- a/sys/conf/files.powerpc
+++ b/sys/conf/files.powerpc
@@ -228,6 +228,7 @@ powerpc/ps3/ps3-hvcall.S optional ps3 sc
powerpc/pseries/phyp-hvcall.S optional pseries powerpc64
powerpc/pseries/mmu_phyp.c optional pseries powerpc64
powerpc/pseries/phyp_console.c optional pseries powerpc64
+powerpc/pseries/phyp_vscsi.c optional pseries powerpc64 scbus
powerpc/pseries/platform_chrp.c optional pseries
powerpc/pseries/plpar_iommu.c optional pseries powerpc64
powerpc/pseries/rtas_dev.c optional pseries
diff --git a/sys/powerpc/pseries/phyp_vscsi.c b/sys/powerpc/pseries/phyp_vscsi.c
new file mode 100644
index 0000000..e3a1fec
--- /dev/null
+++ b/sys/powerpc/pseries/phyp_vscsi.c
@@ -0,0 +1,1003 @@
+/*-
+ * Copyright 2013 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/selinfo.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/eventhandler.h>
+#include <sys/rman.h>
+#include <sys/bus_dma.h>
+#include <sys/bio.h>
+#include <sys/ioccom.h>
+#include <sys/uio.h>
+#include <sys/proc.h>
+#include <sys/signalvar.h>
+#include <sys/sysctl.h>
+#include <sys/endian.h>
+#include <sys/vmem.h>
+
+#include <cam/cam.h>
+#include <cam/cam_ccb.h>
+#include <cam/cam_debug.h>
+#include <cam/cam_periph.h>
+#include <cam/cam_sim.h>
+#include <cam/cam_xpt_periph.h>
+#include <cam/cam_xpt_sim.h>
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_message.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+
+#include <powerpc/pseries/phyp-hvcall.h>
+
+struct vscsi_softc;
+
+/* VSCSI CRQ format from table 260 of PAPR spec 2.4 (page 760) */
+struct vscsi_crq {
+ uint8_t valid;
+ uint8_t format;
+ uint8_t reserved;
+ uint8_t status;
+ uint16_t timeout;
+ uint16_t iu_length;
+ uint64_t iu_data;
+};
+
+struct vscsi_xfer {
+ TAILQ_ENTRY(vscsi_xfer) queue;
+ struct vscsi_softc *sc;
+ union ccb *ccb;
+ bus_dmamap_t dmamap;
+ uint64_t tag;
+
+ vmem_addr_t srp_iu_offset;
+ vmem_size_t srp_iu_size;
+};
+
+TAILQ_HEAD(vscsi_xferq, vscsi_xfer);
+
+struct vscsi_softc {
+ device_t dev;
+ struct cam_devq *devq;
+ struct cam_sim *sim;
+ struct cam_path *path;
+ struct mtx io_lock;
+
+ cell_t unit;
+ int bus_initialized;
+ int bus_logged_in;
+ int max_transactions;
+
+ int irqid;
+ struct resource *irq;
+ void *irq_cookie;
+
+ bus_dma_tag_t crq_tag;
+ struct vscsi_crq *crq_queue;
+ int n_crqs, cur_crq;
+ bus_dmamap_t crq_map;
+ bus_addr_t crq_phys;
+
+ vmem_t *srp_iu_arena;
+ void *srp_iu_queue;
+ bus_addr_t srp_iu_phys;
+
+ bus_dma_tag_t data_tag;
+
+ struct vscsi_xfer loginxp;
+ struct vscsi_xfer *xfer;
+ struct vscsi_xferq active_xferq;
+ struct vscsi_xferq free_xferq;
+};
+
+struct srp_login {
+ uint8_t type;
+ uint8_t reserved[7];
+ uint64_t tag;
+ uint64_t max_cmd_length;
+ uint32_t reserved2;
+ uint16_t buffer_formats;
+ uint8_t flags;
+ uint8_t reserved3[5];
+ uint8_t initiator_port_id[16];
+ uint8_t target_port_id[16];
+} __packed;
+
+struct srp_login_rsp {
+ uint8_t type;
+ uint8_t reserved[3];
+ uint32_t request_limit_delta;
+ uint8_t tag;
+ uint32_t max_i_to_t_len;
+ uint32_t max_t_to_i_len;
+ uint16_t buffer_formats;
+ uint8_t flags;
+ /* Some reserved bits follow */
+} __packed;
+
+struct srp_cmd {
+ uint8_t type;
+ uint8_t flags1;
+ uint8_t reserved[3];
+ uint8_t formats;
+ uint8_t out_buffer_count;
+ uint8_t in_buffer_count;
+ uint64_t tag;
+ uint32_t reserved2;
+ uint64_t lun;
+ uint8_t reserved3[3];
+ uint8_t additional_cdb;
+ uint8_t cdb[16];
+ uint8_t data_payload[0];
+} __packed;
+
+struct srp_rsp {
+ uint8_t type;
+ uint8_t reserved[3];
+ uint32_t request_limit_delta;
+ uint64_t tag;
+ uint16_t reserved2;
+ uint8_t flags;
+ uint8_t status;
+ uint32_t data_out_resid;
+ uint32_t data_in_resid;
+ uint32_t sense_data_len;
+ uint32_t response_data_len;
+ uint8_t data_payload[0];
+} __packed;
+
+struct srp_tsk_mgmt {
+ uint8_t type;
+ uint8_t reserved[7];
+ uint64_t tag;
+ uint32_t reserved2;
+ uint64_t lun;
+ uint8_t reserved3[2];
+ uint8_t function;
+ uint8_t reserved4;
+ uint64_t manage_tag;
+ uint64_t reserved5;
+} __packed;
+
+/* Message code type */
+#define SRP_LOGIN_REQ 0x00
+#define SRP_TSK_MGMT 0x01
+#define SRP_CMD 0x02
+#define SRP_I_LOGOUT 0x03
+
+#define SRP_LOGIN_RSP 0xC0
+#define SRP_RSP 0xC1
+#define SRP_LOGIN_REJ 0xC2
+
+#define SRP_T_LOGOUT 0x80
+#define SRP_CRED_REQ 0x81
+#define SRP_AER_REQ 0x82
+
+#define SRP_CRED_RSP 0x41
+#define SRP_AER_RSP 0x41
+
+/* Flags for srp_rsp flags field */
+#define SRP_RSPVALID 0x01
+#define SRP_SNSVALID 0x02
+#define SRP_DOOVER 0x04
+#define SRP_DOUNDER 0x08
+#define SRP_DIOVER 0x10
+#define SRP_DIUNDER 0x20
+
+#define MAD_SUCESS 0x00
+#define MAD_NOT_SUPPORTED 0xf1
+#define MAD_FAILED 0xf7
+
+#define MAD_EMPTY_IU 0x01
+#define MAD_ERROR_LOGGING_REQUEST 0x02
+#define MAD_ADAPTER_INFO_REQUEST 0x03
+#define MAD_CAPABILITIES_EXCHANGE 0x05
+#define MAD_PHYS_ADAP_INFO_REQUEST 0x06
+#define MAD_TAPE_PASSTHROUGH_REQUEST 0x07
+#define MAD_ENABLE_FAST_FAIL 0x08
+
+static int vscsi_probe(device_t);
+static int vscsi_attach(device_t);
+static int vscsi_detach(device_t);
+static void vscsi_cam_action(struct cam_sim *, union ccb *);
+static void vscsi_cam_poll(struct cam_sim *);
+static void vscsi_intr(void *arg);
+static void vscsi_check_response_queue(struct vscsi_softc *sc);
+static void vscsi_setup_bus(struct vscsi_softc *sc);
+
+static void vscsi_srp_login(struct vscsi_softc *sc);
+static void vscsi_crq_load_cb(void *, bus_dma_segment_t *, int, int);
+static void vscsi_scsi_command(void *xxp, bus_dma_segment_t *segs,
+ int nsegs, int err);
+static void vscsi_task_management(struct vscsi_softc *sc, union ccb *ccb);
+static void vscsi_srp_response(struct vscsi_xfer *, struct vscsi_crq *);
+
+static devclass_t vscsi_devclass;
+static device_method_t vscsi_methods[] = {
+ DEVMETHOD(device_probe, vscsi_probe),
+ DEVMETHOD(device_attach, vscsi_attach),
+ DEVMETHOD(device_detach, vscsi_detach),
+
+ DEVMETHOD_END
+};
+static driver_t vscsi_driver = {
+ "vscsi",
+ vscsi_methods,
+ sizeof(struct vscsi_softc)
+};
+DRIVER_MODULE(vscsi, vdevice, vscsi_driver, vscsi_devclass, 0, 0);
+MALLOC_DEFINE(M_VSCSI, "vscsi", "CAM device queue for VSCSI");
+
+static int
+vscsi_probe(device_t dev)
+{
+
+ if (!ofw_bus_is_compatible(dev, "IBM,v-scsi"))
+ return (ENXIO);
+
+ device_set_desc(dev, "POWER Hypervisor Virtual SCSI Bus");
+ return (0);
+}
+
+static int
+vscsi_attach(device_t dev)
+{
+ struct vscsi_softc *sc;
+ struct vscsi_xfer *xp;
+ int error, i;
+
+ sc = device_get_softc(dev);
+ if (sc == NULL)
+ return (EINVAL);
+
+ sc->dev = dev;
+ mtx_init(&sc->io_lock, "vscsi", NULL, MTX_DEF);
+
+ /* Get properties */
+ OF_getprop(ofw_bus_get_node(dev), "reg", &sc->unit, sizeof(sc->unit));
+
+ /* Setup interrupt */
+ sc->irqid = 0;
+ sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid,
+ RF_ACTIVE);
+
+ if (!sc->irq) {
+ device_printf(dev, "Could not allocate IRQ\n");
+ mtx_destroy(&sc->io_lock);
+ return (ENXIO);
+ }
+
+ bus_setup_intr(dev, sc->irq, INTR_TYPE_CAM | INTR_MPSAFE |
+ INTR_ENTROPY, NULL, vscsi_intr, sc, &sc->irq_cookie);
+
+ /* Data DMA */
+ error = bus_dma_tag_create(bus_get_dma_tag(dev), 1, 0,
+ BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BUS_SPACE_MAXSIZE,
+ 256, BUS_SPACE_MAXSIZE_32BIT, 0, busdma_lock_mutex, &sc->io_lock,
+ &sc->data_tag);
+
+ TAILQ_INIT(&sc->active_xferq);
+ TAILQ_INIT(&sc->free_xferq);
+
+ /* First XFER for login data */
+ sc->loginxp.sc = sc;
+ bus_dmamap_create(sc->data_tag, 0, &sc->loginxp.dmamap);
+ TAILQ_INSERT_TAIL(&sc->free_xferq, &sc->loginxp, queue);
+
+ /* CRQ area */
+ error = bus_dma_tag_create(bus_get_dma_tag(dev), PAGE_SIZE, 0,
+ BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, 8*PAGE_SIZE,
+ 1, BUS_SPACE_MAXSIZE, 0, NULL, NULL, &sc->crq_tag);
+ error = bus_dmamem_alloc(sc->crq_tag, (void **)&sc->crq_queue,
+ BUS_DMA_WAITOK | BUS_DMA_ZERO, &sc->crq_map);
+ sc->crq_phys = 0;
+ sc->n_crqs = 0;
+ error = bus_dmamap_load(sc->crq_tag, sc->crq_map, sc->crq_queue,
+ 8*PAGE_SIZE, vscsi_crq_load_cb, sc, 0);
+
+ mtx_lock(&sc->io_lock);
+ vscsi_setup_bus(sc);
+ sc->xfer = malloc(sizeof(sc->xfer[0])*sc->max_transactions, M_VSCSI,
+ M_NOWAIT);
+ for (i = 0; i < sc->max_transactions; i++) {
+ xp = &sc->xfer[i];
+ xp->sc = sc;
+
+ error = bus_dmamap_create(sc->data_tag, 0, &xp->dmamap);
+ if (error) {
+ device_printf(dev, "Could not create DMA map (%d)\n",
+ error);
+ break;
+ }
+
+ TAILQ_INSERT_TAIL(&sc->free_xferq, xp, queue);
+ }
+ mtx_unlock(&sc->io_lock);
+
+ /* Allocate CAM bits */
+ if ((sc->devq = cam_simq_alloc(sc->max_transactions)) == NULL)
+ return (ENOMEM);
+
+ sc->sim = cam_sim_alloc(vscsi_cam_action, vscsi_cam_poll, "vscsi", sc,
+ device_get_unit(dev), &sc->io_lock,
+ sc->max_transactions, sc->max_transactions,
+ sc->devq);
+ if (sc->sim == NULL) {
+ cam_simq_free(sc->devq);
+ sc->devq = NULL;
+ device_printf(dev, "CAM SIM attach failed\n");
+ return (EINVAL);
+ }
+
+
+ mtx_lock(&sc->io_lock);
+ if (xpt_bus_register(sc->sim, dev, 0) != 0) {
+ device_printf(dev, "XPT bus registration failed\n");
+ cam_sim_free(sc->sim, FALSE);
+ sc->sim = NULL;
+ cam_simq_free(sc->devq);
+ sc->devq = NULL;
+ mtx_unlock(&sc->io_lock);
+ return (EINVAL);
+ }
+ mtx_unlock(&sc->io_lock);
+
+ return (0);
+}
+
+static int
+vscsi_detach(device_t dev)
+{
+ struct vscsi_softc *sc;
+
+ sc = device_get_softc(dev);
+ if (sc == NULL)
+ return (EINVAL);
+
+ if (sc->sim != NULL) {
+ mtx_lock(&sc->io_lock);
+ xpt_bus_deregister(cam_sim_path(sc->sim));
+ cam_sim_free(sc->sim, FALSE);
+ sc->sim = NULL;
+ mtx_unlock(&sc->io_lock);
+ }
+
+ if (sc->devq != NULL) {
+ cam_simq_free(sc->devq);
+ sc->devq = NULL;
+ }
+
+ mtx_destroy(&sc->io_lock);
+
+ return (0);
+}
+
+static void
+vscsi_cam_action(struct cam_sim *sim, union ccb *ccb)
+{
+ struct vscsi_softc *sc = cam_sim_softc(sim);
+
+ mtx_assert(&sc->io_lock, MA_OWNED);
+
+ switch (ccb->ccb_h.func_code) {
+ case XPT_PATH_INQ:
+ {
+ struct ccb_pathinq *cpi = &ccb->cpi;
+
+ cpi->version_num = 1;
+ cpi->hba_inquiry = PI_TAG_ABLE;
+ cpi->hba_misc = PIM_EXTLUNS;
+ cpi->target_sprt = 0;
+ cpi->hba_eng_cnt = 0;
+ cpi->max_target = 0;
+ cpi->max_lun = ~(lun_id_t)(0);
+ cpi->initiator_id = ~0;
+ strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
+ strncpy(cpi->hba_vid, "IBM", HBA_IDLEN);
+ strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN);
+ cpi->unit_number = cam_sim_unit(sim);
+ cpi->bus_id = cam_sim_bus(sim);
+ cpi->base_transfer_speed = 150000;
+ cpi->transport = XPORT_SRP;
+ cpi->transport_version = 0;
+ cpi->protocol = PROTO_SCSI;
+ cpi->protocol_version = SCSI_REV_SPC4;
+ cpi->ccb_h.status = CAM_REQ_CMP;
+ break;
+ }
+ case XPT_RESET_BUS:
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ break;
+ case XPT_RESET_DEV:
+ ccb->ccb_h.status = CAM_REQ_INPROG;
+ vscsi_task_management(sc, ccb);
+ return;
+ case XPT_GET_TRAN_SETTINGS:
+ ccb->cts.protocol = PROTO_SCSI;
+ ccb->cts.protocol_version = SCSI_REV_SPC4;
+ ccb->cts.transport = XPORT_SRP;
+ ccb->cts.transport_version = 0;
+ ccb->cts.proto_specific.valid = 0;
+ ccb->cts.xport_specific.valid = 0;
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ break;
+ case XPT_SET_TRAN_SETTINGS:
+ ccb->ccb_h.status = CAM_FUNC_NOTAVAIL;
+ break;
+ case XPT_SCSI_IO:
+ {
+ struct vscsi_xfer *xp;
+
+ ccb->ccb_h.status = CAM_REQ_INPROG;
+
+ xp = TAILQ_FIRST(&sc->free_xferq);
+ if (xp == NULL)
+ panic("SCSI queue flooded");
+ xp->ccb = ccb;
+ TAILQ_REMOVE(&sc->free_xferq, xp, queue);
+ TAILQ_INSERT_TAIL(&sc->active_xferq, xp, queue);
+ bus_dmamap_load_ccb(sc->data_tag, xp->dmamap,
+ ccb, vscsi_scsi_command, xp, 0);
+
+ return;
+ }
+ default:
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ break;
+ }
+
+ xpt_done(ccb);
+ return;
+}
+
+static void
+vscsi_srp_login(struct vscsi_softc *sc)
+{
+ struct vscsi_xfer *xp;
+ struct srp_login *login;
+ struct vscsi_crq crq;
+ int err;
+
+ mtx_assert(&sc->io_lock, MA_OWNED);
+
+ xp = TAILQ_FIRST(&sc->free_xferq);
+ if (xp == NULL)
+ panic("SCSI queue flooded");
+ xp->ccb = NULL;
+ TAILQ_REMOVE(&sc->free_xferq, xp, queue);
+ TAILQ_INSERT_TAIL(&sc->active_xferq, xp, queue);
+
+ /* Set up command */
+ xp->srp_iu_size = crq.iu_length = 64;
+ err = vmem_alloc(xp->sc->srp_iu_arena, xp->srp_iu_size,
+ M_BESTFIT | M_NOWAIT, &xp->srp_iu_offset);
+ if (err)
+ panic("Error during VMEM allocation (%d)", err);
+
+ login = (struct srp_login *)((uint8_t *)xp->sc->srp_iu_queue +
+ (uintptr_t)xp->srp_iu_offset);
+ bzero(login, xp->srp_iu_size);
+ login->type = SRP_LOGIN_REQ;
+ login->tag = (uint64_t)(xp);
+ login->max_cmd_length = htobe64(256);
+ login->buffer_formats = htobe16(0x1 | 0x2); /* Direct and indirect */
+ login->flags = 0;
+
+ /* Create CRQ entry */
+ crq.valid = 0x80;
+ crq.format = 0x01;
+ crq.iu_data = xp->sc->srp_iu_phys + xp->srp_iu_offset;
+ bus_dmamap_sync(sc->crq_tag, sc->crq_map, BUS_DMASYNC_PREWRITE);
+
+ err = phyp_hcall(H_SEND_CRQ, xp->sc->unit, ((uint64_t *)(&crq))[0],
+ ((uint64_t *)(&crq))[1]);
+ if (err != 0)
+ panic("CRQ send failure (%d)", err);
+}
+
+static void
+vscsi_task_management(struct vscsi_softc *sc, union ccb *ccb)
+{
+ struct srp_tsk_mgmt *cmd;
+ struct vscsi_xfer *xp;
+ struct vscsi_crq crq;
+ int err;
+
+ mtx_assert(&sc->io_lock, MA_OWNED);
+
+ xp = TAILQ_FIRST(&sc->free_xferq);
+ if (xp == NULL)
+ panic("SCSI queue flooded");
+ xp->ccb = ccb;
+ TAILQ_REMOVE(&sc->free_xferq, xp, queue);
+ TAILQ_INSERT_TAIL(&sc->active_xferq, xp, queue);
+
+ if (!(ccb->ccb_h.xflags & CAM_EXTLUN_VALID)) {
+ ccb->ccb_h.xflags |= CAM_EXTLUN_VALID;
+ ccb->ccb_h.ext_lun.lun64 = (0x1UL << 62) |
+ ((uint64_t)ccb->ccb_h.target_lun << 48);
+ }
+
+ xp->srp_iu_size = crq.iu_length = sizeof(*cmd);
+ err = vmem_alloc(xp->sc->srp_iu_arena, xp->srp_iu_size,
+ M_BESTFIT | M_NOWAIT, &xp->srp_iu_offset);
+ if (err)
+ panic("Error during VMEM allocation (%d)", err);
+
+ cmd = (struct srp_tsk_mgmt *)((uint8_t *)xp->sc->srp_iu_queue +
+ (uintptr_t)xp->srp_iu_offset);
+ bzero(cmd, xp->srp_iu_size);
+ cmd->type = SRP_TSK_MGMT;
+ cmd->tag = (uint64_t)xp;
+ cmd->lun = ccb->ccb_h.ext_lun.lun64;
+
+ switch (ccb->ccb_h.func_code) {
+ case XPT_RESET_DEV:
+ cmd->function = 0x08;
+ break;
+ default:
+ panic("Unimplemented code %d", ccb->ccb_h.func_code);
+ break;
+ }
+
+ bus_dmamap_sync(xp->sc->crq_tag, xp->sc->crq_map, BUS_DMASYNC_PREWRITE);
+
+ /* Create CRQ entry */
+ crq.valid = 0x80;
+ crq.format = 0x01;
+ crq.iu_data = xp->sc->srp_iu_phys + xp->srp_iu_offset;
+
+ err = phyp_hcall(H_SEND_CRQ, xp->sc->unit, ((uint64_t *)(&crq))[0],
+ ((uint64_t *)(&crq))[1]);
+ if (err != 0)
+ panic("CRQ send failure (%d)", err);
+}
+
+static void
+vscsi_scsi_command(void *xxp, bus_dma_segment_t *segs, int nsegs, int err)
+{
+ struct vscsi_xfer *xp = xxp;
+ uint8_t *cdb;
+ union ccb *ccb = xp->ccb;
+ struct srp_cmd *cmd;
+ uint64_t chunk_addr;
+ uint32_t chunk_size;
+ int desc_start, i;
+ struct vscsi_crq crq;
+
+ KASSERT(err == 0, ("DMA error %d\n", err));
+
+ mtx_assert(&xp->sc->io_lock, MA_OWNED);
+
+ cdb = (ccb->ccb_h.flags & CAM_CDB_POINTER) ?
+ ccb->csio.cdb_io.cdb_ptr : ccb->csio.cdb_io.cdb_bytes;
+
+ if (!(ccb->ccb_h.xflags & CAM_EXTLUN_VALID)) {
+ ccb->ccb_h.xflags |= CAM_EXTLUN_VALID;
+ ccb->ccb_h.ext_lun.lun64 = (0x1UL << 62) |
+ ((uint64_t)ccb->ccb_h.target_lun << 48);
+ }
+
+ /* Command format from Table 20, page 37 of SRP spec */
+ crq.iu_length = 48 + ((nsegs > 1) ? 20 : 16) +
+ ((ccb->csio.cdb_len > 16) ? (ccb->csio.cdb_len - 16) : 0);
+ xp->srp_iu_size = crq.iu_length;
+ if (nsegs > 1)
+ xp->srp_iu_size += nsegs*16;
+ xp->srp_iu_size = roundup(xp->srp_iu_size, 16);
+ err = vmem_alloc(xp->sc->srp_iu_arena, xp->srp_iu_size,
+ M_BESTFIT | M_NOWAIT, &xp->srp_iu_offset);
+ if (err)
+ panic("Error during VMEM allocation (%d)", err);
+
+ cmd = (struct srp_cmd *)((uint8_t *)xp->sc->srp_iu_queue +
+ (uintptr_t)xp->srp_iu_offset);
+ bzero(cmd, xp->srp_iu_size);
+ cmd->type = SRP_CMD;
+ if (ccb->csio.cdb_len > 16)
+ cmd->additional_cdb = (ccb->csio.cdb_len - 16) << 2;
+ memcpy(cmd->cdb, cdb, ccb->csio.cdb_len);
+
+ cmd->tag = (uint64_t)(xp); /* Let the responder find this again */
+ cmd->lun = ccb->ccb_h.ext_lun.lun64;
+
+ if (nsegs > 1) {
+ /* Use indirect descriptors */
+ switch (ccb->ccb_h.flags & CAM_DIR_MASK) {
+ case CAM_DIR_OUT:
+ cmd->formats = (2 << 4);
+ break;
+ case CAM_DIR_IN:
+ cmd->formats = 2;
+ break;
+ default:
+ panic("Does not support bidirectional commands (%d)",
+ ccb->ccb_h.flags & CAM_DIR_MASK);
+ break;
+ }
+
+ desc_start = ((ccb->csio.cdb_len > 16) ?
+ ccb->csio.cdb_len - 16 : 0);
+ chunk_addr = xp->sc->srp_iu_phys + xp->srp_iu_offset + 20 +
+ desc_start + sizeof(*cmd);
+ chunk_size = 16*nsegs;
+ memcpy(&cmd->data_payload[desc_start], &chunk_addr, 8);
+ memcpy(&cmd->data_payload[desc_start+12], &chunk_size, 4);
+ chunk_size = 0;
+ for (i = 0; i < nsegs; i++)
+ chunk_size += segs[i].ds_len;
+ memcpy(&cmd->data_payload[desc_start+16], &chunk_size, 4);
+ desc_start += 20;
+ for (i = 0; i < nsegs; i++) {
+ chunk_addr = segs[i].ds_addr;
+ chunk_size = segs[i].ds_len;
+
+ memcpy(&cmd->data_payload[desc_start + 16*i],
+ &chunk_addr, 8);
+ /* Set handle tag to 0 */
+ memcpy(&cmd->data_payload[desc_start + 16*i + 12],
+ &chunk_size, 4);
+ }
+ } else if (nsegs == 1) {
+ switch (ccb->ccb_h.flags & CAM_DIR_MASK) {
+ case CAM_DIR_OUT:
+ cmd->formats = (1 << 4);
+ break;
+ case CAM_DIR_IN:
+ cmd->formats = 1;
+ break;
+ default:
+ panic("Does not support bidirectional commands (%d)",
+ ccb->ccb_h.flags & CAM_DIR_MASK);
+ break;
+ }
+
+ /*
+ * Memory descriptor:
+ * 8 byte address
+ * 4 byte handle
+ * 4 byte length
+ */
+
+ chunk_addr = segs[0].ds_addr;
+ chunk_size = segs[0].ds_len;
+ desc_start = ((ccb->csio.cdb_len > 16) ?
+ ccb->csio.cdb_len - 16 : 0);
+
+ memcpy(&cmd->data_payload[desc_start], &chunk_addr, 8);
+ /* Set handle tag to 0 */
+ memcpy(&cmd->data_payload[desc_start+12], &chunk_size, 4);
+ KASSERT(xp->srp_iu_size >= 48 + ((ccb->csio.cdb_len > 16) ?
+ ccb->csio.cdb_len : 16), ("SRP IU command length"));
+ } else {
+ cmd->formats = 0;
+ }
+ bus_dmamap_sync(xp->sc->crq_tag, xp->sc->crq_map, BUS_DMASYNC_PREWRITE);
+
+ /* Create CRQ entry */
+ crq.valid = 0x80;
+ crq.format = 0x01;
+ crq.iu_data = xp->sc->srp_iu_phys + xp->srp_iu_offset;
+
+ err = phyp_hcall(H_SEND_CRQ, xp->sc->unit, ((uint64_t *)(&crq))[0],
+ ((uint64_t *)(&crq))[1]);
+ if (err != 0)
+ panic("CRQ send failure (%d)", err);
+}
+
+static void
+vscsi_crq_load_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int err)
+{
+ struct vscsi_softc *sc = xsc;
+
+ sc->crq_phys = segs[0].ds_addr;
+ sc->n_crqs = PAGE_SIZE/sizeof(struct vscsi_crq);
+
+ sc->srp_iu_queue = (uint8_t *)(sc->crq_queue);
+ sc->srp_iu_phys = segs[0].ds_addr;
+ sc->srp_iu_arena = vmem_create("VSCSI SRP IU", PAGE_SIZE,
+ segs[0].ds_len - PAGE_SIZE, 16, 0, M_BESTFIT | M_NOWAIT);
+}
+
+static void
+vscsi_setup_bus(struct vscsi_softc *sc)
+{
+ struct vscsi_crq crq;
+ struct vscsi_xfer *xp;
+ int error;
+
+ struct {
+ uint32_t type;
+ uint16_t status;
+ uint16_t length;
+ uint64_t tag;
+ uint64_t buffer;
+ struct {
+ char srp_version[8];
+ char partition_name[96];
+ uint32_t partition_number;
+ uint32_t mad_version;
+ uint32_t os_type;
+ uint32_t port_max_txu[8];
+ } payload;
+ } mad_adapter_info;
+
+ bzero(&crq, sizeof(crq));
+
+ /* Init message */
+ crq.valid = 0xc0;
+ crq.format = 0x01;
+
+ do {
+ error = phyp_hcall(H_FREE_CRQ, sc->unit);
+ } while (error == H_BUSY);
+
+ /* See initialization sequence page 757 */
+ bzero(sc->crq_queue, sc->n_crqs*sizeof(sc->crq_queue[0]));
+ sc->cur_crq = 0;
+ sc->bus_initialized = 0;
+ sc->bus_logged_in = 0;
+ bus_dmamap_sync(sc->crq_tag, sc->crq_map, BUS_DMASYNC_PREWRITE);
+ error = phyp_hcall(H_REG_CRQ, sc->unit, sc->crq_phys,
+ sc->n_crqs*sizeof(sc->crq_queue[0]));
+ KASSERT(error == 0, ("CRQ registration success"));
+
+ error = phyp_hcall(H_SEND_CRQ, sc->unit, ((uint64_t *)(&crq))[0],
+ ((uint64_t *)(&crq))[1]);
+ if (error != 0)
+ panic("CRQ setup failure (%d)", error);
+
+ while (sc->bus_initialized == 0)
+ vscsi_check_response_queue(sc);
+
+ /* Send MAD adapter info */
+ mad_adapter_info.type = MAD_ADAPTER_INFO_REQUEST;
+ mad_adapter_info.status = 0;
+ mad_adapter_info.length = sizeof(mad_adapter_info.payload);
+
+ strcpy(mad_adapter_info.payload.srp_version, "16.a");
+ strcpy(mad_adapter_info.payload.partition_name, "UNKNOWN");
+ mad_adapter_info.payload.partition_number = -1;
+ mad_adapter_info.payload.mad_version = 1;
+ mad_adapter_info.payload.os_type = 2; /* Claim we are Linux */
+ mad_adapter_info.payload.port_max_txu[0] = 0;
+ /* If this fails, we get the defaults above */
+ OF_getprop(OF_finddevice("/"), "ibm,partition-name",
+ mad_adapter_info.payload.partition_name,
+ sizeof(mad_adapter_info.payload.partition_name));
+ OF_getprop(OF_finddevice("/"), "ibm,partition-no",
+ &mad_adapter_info.payload.partition_number,
+ sizeof(mad_adapter_info.payload.partition_number));
+
+ xp = TAILQ_FIRST(&sc->free_xferq);
+ xp->ccb = NULL;
+ TAILQ_REMOVE(&sc->free_xferq, xp, queue);
+ TAILQ_INSERT_TAIL(&sc->active_xferq, xp, queue);
+ xp->srp_iu_size = crq.iu_length = sizeof(mad_adapter_info);
+ vmem_alloc(xp->sc->srp_iu_arena, xp->srp_iu_size,
+ M_BESTFIT | M_NOWAIT, &xp->srp_iu_offset);
+ mad_adapter_info.buffer = xp->sc->srp_iu_phys + xp->srp_iu_offset + 24;
+ mad_adapter_info.tag = (uint64_t)xp;
+ memcpy((uint8_t *)xp->sc->srp_iu_queue + (uintptr_t)xp->srp_iu_offset,
+ &mad_adapter_info, sizeof(mad_adapter_info));
+ crq.valid = 0x80;
+ crq.format = 0x02;
+ crq.iu_data = xp->sc->srp_iu_phys + xp->srp_iu_offset;
+ bus_dmamap_sync(sc->crq_tag, sc->crq_map, BUS_DMASYNC_PREWRITE);
+ phyp_hcall(H_SEND_CRQ, xp->sc->unit, ((uint64_t *)(&crq))[0],
+ ((uint64_t *)(&crq))[1]);
+
+ while (TAILQ_EMPTY(&sc->free_xferq))
+ vscsi_check_response_queue(sc);
+
+ /* Send SRP login */
+ vscsi_srp_login(sc);
+ while (sc->bus_logged_in == 0)
+ vscsi_check_response_queue(sc);
+
+ error = phyp_hcall(H_VIO_SIGNAL, sc->unit, 1); /* Enable interrupts */
+}
+
+
+static void
+vscsi_intr(void *xsc)
+{
+ struct vscsi_softc *sc = xsc;
+
+ mtx_lock(&sc->io_lock);
+ vscsi_check_response_queue(sc);
+ mtx_unlock(&sc->io_lock);
+}
+
+static void
+vscsi_srp_response(struct vscsi_xfer *xp, struct vscsi_crq *crq)
+{
+ union ccb *ccb = xp->ccb;
+ struct vscsi_softc *sc = xp->sc;
+ struct srp_rsp *rsp;
+ uint32_t sense_len;
+
+ /* SRP response packet in original request */
+ rsp = (struct srp_rsp *)((uint8_t *)sc->srp_iu_queue +
+ (uintptr_t)xp->srp_iu_offset);
+ ccb->csio.scsi_status = rsp->status;
+ if (ccb->csio.scsi_status == SCSI_STATUS_OK)
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ else
+ ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR;
+#ifdef NOTYET
+ /* Collect fast fail codes */
+ if (crq->status != 0)
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR;
+#endif
+
+ if (ccb->ccb_h.status != CAM_REQ_CMP) {
+ ccb->ccb_h.status |= CAM_DEV_QFRZN;
+ xpt_freeze_devq(ccb->ccb_h.path, /*count*/ 1);
+ }
+
+ if (!(rsp->flags & SRP_RSPVALID))
+ rsp->response_data_len = 0;
+ if (!(rsp->flags & SRP_SNSVALID))
+ rsp->sense_data_len = 0;
+ if (!(rsp->flags & (SRP_DOOVER | SRP_DOUNDER)))
+ rsp->data_out_resid = 0;
+ if (!(rsp->flags & (SRP_DIOVER | SRP_DIUNDER)))
+ rsp->data_in_resid = 0;
+
+ if (rsp->flags & SRP_SNSVALID) {
+ bzero(&ccb->csio.sense_data, sizeof(struct scsi_sense_data));
+ ccb->ccb_h.status |= CAM_AUTOSNS_VALID;
+ sense_len = min(be32toh(rsp->sense_data_len),
+ ccb->csio.sense_len);
+ memcpy(&ccb->csio.sense_data,
+ &rsp->data_payload[be32toh(rsp->response_data_len)],
+ sense_len);
+ ccb->csio.sense_resid = ccb->csio.sense_len -
+ be32toh(rsp->sense_data_len);
+ }
+
+ switch (ccb->ccb_h.flags & CAM_DIR_MASK) {
+ case CAM_DIR_OUT:
+ ccb->csio.resid = rsp->data_out_resid;
+ break;
+ case CAM_DIR_IN:
+ ccb->csio.resid = rsp->data_in_resid;
+ break;
+ }
+
+ bus_dmamap_sync(sc->data_tag, xp->dmamap, BUS_DMASYNC_POSTREAD);
+ bus_dmamap_unload(sc->data_tag, xp->dmamap);
+ xpt_done(ccb);
+ xp->ccb = NULL;
+}
+
+static void
+vscsi_login_response(struct vscsi_xfer *xp, struct vscsi_crq *crq)
+{
+ struct vscsi_softc *sc = xp->sc;
+ struct srp_login_rsp *rsp;
+
+ /* SRP response packet in original request */
+ rsp = (struct srp_login_rsp *)((uint8_t *)sc->srp_iu_queue +
+ (uintptr_t)xp->srp_iu_offset);
+ KASSERT(be16toh(rsp->buffer_formats) & 0x3, ("Both direct and indirect "
+ "buffers supported"));
+
+ sc->max_transactions = be32toh(rsp->request_limit_delta);
+ device_printf(sc->dev, "Queue depth %d commands\n",
+ sc->max_transactions);
+ sc->bus_logged_in = 1;
+}
+
+static void
+vscsi_cam_poll(struct cam_sim *sim)
+{
+ struct vscsi_softc *sc = cam_sim_softc(sim);
+
+ vscsi_check_response_queue(sc);
+}
+
+static void
+vscsi_check_response_queue(struct vscsi_softc *sc)
+{
+ struct vscsi_crq *crq;
+ struct vscsi_xfer *xp;
+ int code;
+
+ mtx_assert(&sc->io_lock, MA_OWNED);
+
+ phyp_hcall(H_VIO_SIGNAL, sc->unit, 0);
+ bus_dmamap_sync(sc->crq_tag, sc->crq_map, BUS_DMASYNC_POSTREAD);
+
+ while (sc->crq_queue[sc->cur_crq].valid != 0) {
+ crq = &sc->crq_queue[sc->cur_crq];
+
+ switch (crq->valid) {
+ case 0xc0:
+ if (crq->format == 0x02)
+ sc->bus_initialized = 1;
+ break;
+ case 0x80:
+ /* IU data is set to tag pointer (the XP) */
+ xp = (struct vscsi_xfer *)crq->iu_data;
+
+ switch (crq->format) {
+ case 0x01:
+ code = *((uint8_t *)sc->srp_iu_queue +
+ (uintptr_t)xp->srp_iu_offset);
+ switch (code) {
+ case SRP_RSP:
+ vscsi_srp_response(xp, crq);
+ break;
+ case SRP_LOGIN_RSP:
+ vscsi_login_response(xp, crq);
+ break;
+ default:
+ device_printf(sc->dev, "Unknown SRP "
+ "response code %d\n", code);
+ break;
+ }
+ break;
+ case 0x02:
+ /* Ignore management datagrams */
+ break;
+ default:
+ panic("Unknown CRQ format %d\n", crq->format);
+ break;
+ }
+ vmem_free(sc->srp_iu_arena, xp->srp_iu_offset,
+ xp->srp_iu_size);
+ TAILQ_REMOVE(&sc->active_xferq, xp, queue);
+ TAILQ_INSERT_TAIL(&sc->free_xferq, xp, queue);
+ break;
+ default:
+ device_printf(sc->dev,
+ "Unknown CRQ message type %d\n", crq->valid);
+ break;
+ }
+
+ crq->valid = 0;
+ sc->cur_crq = (sc->cur_crq + 1) % sc->n_crqs;
+ };
+
+ bus_dmamap_sync(sc->crq_tag, sc->crq_map, BUS_DMASYNC_PREWRITE);
+ phyp_hcall(H_VIO_SIGNAL, sc->unit, 1);
+}
+
diff --git a/sys/powerpc/pseries/vdevice.c b/sys/powerpc/pseries/vdevice.c
index 4551106..8ddd531 100644
--- a/sys/powerpc/pseries/vdevice.c
+++ b/sys/powerpc/pseries/vdevice.c
@@ -41,12 +41,17 @@ __FBSDID("$FreeBSD$");
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
+#include <powerpc/pseries/plpar_iommu.h>
+
+#include "iommu_if.h"
+
static int vdevice_probe(device_t);
static int vdevice_attach(device_t);
static const struct ofw_bus_devinfo *vdevice_get_devinfo(device_t dev,
device_t child);
static int vdevice_print_child(device_t dev, device_t child);
-static struct resource_list *vdevice_get_resource_list (device_t, device_t);
+static struct resource_list *vdevice_get_resource_list(device_t, device_t);
+static bus_dma_tag_t vdevice_get_dma_tag(device_t dev, device_t child);
/*
* VDevice devinfo
@@ -54,6 +59,7 @@ static struct resource_list *vdevice_get_resource_list (device_t, device_t);
struct vdevice_devinfo {
struct ofw_bus_devinfo mdi_obdinfo;
struct resource_list mdi_resources;
+ bus_dma_tag_t mdi_dma_tag;
};
static device_method_t vdevice_methods[] = {
@@ -81,6 +87,11 @@ static device_method_t vdevice_methods[] = {
DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node),
DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type),
+ /* IOMMU interface */
+ DEVMETHOD(bus_get_dma_tag, vdevice_get_dma_tag),
+ DEVMETHOD(iommu_map, phyp_iommu_map),
+ DEVMETHOD(iommu_unmap, phyp_iommu_unmap),
+
DEVMETHOD_END
};
@@ -200,3 +211,22 @@ vdevice_get_resource_list (device_t dev, device_t child)
return (&dinfo->mdi_resources);
}
+static bus_dma_tag_t
+vdevice_get_dma_tag(device_t dev, device_t child)
+{
+ struct vdevice_devinfo *dinfo;
+ while (child != NULL && device_get_parent(child) != dev)
+ child = device_get_parent(child);
+ dinfo = device_get_ivars(child);
+
+ if (dinfo->mdi_dma_tag == NULL) {
+ bus_dma_tag_create(bus_get_dma_tag(dev),
+ 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR,
+ NULL, NULL, BUS_SPACE_MAXSIZE, BUS_SPACE_UNRESTRICTED,
+ BUS_SPACE_MAXSIZE, 0, NULL, NULL, &dinfo->mdi_dma_tag);
+ phyp_iommu_set_dma_tag(dev, child, dinfo->mdi_dma_tag);
+ }
+
+ return (dinfo->mdi_dma_tag);
+}
+
OpenPOWER on IntegriCloud