/*- * SPDX-License-Identifier: BSD-2-Clause * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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_getencprop(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 = 0; cpi->initiator_id = ~0; strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strlcpy(cpi->hba_vid, "IBM", HBA_IDLEN); strlcpy(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 = 64; crq.iu_length = htobe16(xp->srp_iu_size); 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 = htobe64(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, be64toh(((uint64_t *)(&crq))[0]), be64toh(((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); xp->srp_iu_size = sizeof(*cmd); crq.iu_length = htobe16(xp->srp_iu_size); 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 = htobe64(CAM_EXTLUN_BYTE_SWIZZLE(ccb->ccb_h.target_lun)); 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 = htobe64(xp->sc->srp_iu_phys + xp->srp_iu_offset); err = phyp_hcall(H_SEND_CRQ, xp->sc->unit, be64toh(((uint64_t *)(&crq))[0]), be64toh(((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; /* Command format from Table 20, page 37 of SRP spec */ xp->srp_iu_size = 48 + ((nsegs > 1) ? 20 : 16) + ((ccb->csio.cdb_len > 16) ? (ccb->csio.cdb_len - 16) : 0); crq.iu_length = htobe16(xp->srp_iu_size); 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 = htobe64(CAM_EXTLUN_BYTE_SWIZZLE(ccb->ccb_h.target_lun)); 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 = htobe64(xp->sc->srp_iu_phys + xp->srp_iu_offset + 20 + desc_start + sizeof(*cmd)); chunk_size = htobe32(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; chunk_size = htobe32(chunk_size); memcpy(&cmd->data_payload[desc_start+16], &chunk_size, 4); desc_start += 20; for (i = 0; i < nsegs; i++) { chunk_addr = htobe64(segs[i].ds_addr); chunk_size = htobe32(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 = htobe64(segs[0].ds_addr); chunk_size = htobe32(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 = htobe64(xp->sc->srp_iu_phys + xp->srp_iu_offset); err = phyp_hcall(H_SEND_CRQ, xp->sc->unit, be64toh(((uint64_t *)(&crq))[0]), be64toh(((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, be64toh(((uint64_t *)(&crq))[0]), be64toh(((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 = htobe32(MAD_ADAPTER_INFO_REQUEST); mad_adapter_info.status = 0; mad_adapter_info.length = htobe16(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 = htobe32(1); mad_adapter_info.payload.os_type = htobe32(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 = sizeof(mad_adapter_info); crq.iu_length = htobe16(xp->srp_iu_size); vmem_alloc(xp->sc->srp_iu_arena, xp->srp_iu_size, M_BESTFIT | M_NOWAIT, &xp->srp_iu_offset); mad_adapter_info.buffer = htobe64(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 = htobe64(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, be64toh(((uint64_t *)(&crq))[0]), be64toh(((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); while (sc->crq_queue[sc->cur_crq].valid != 0) { /* The hypercalls at both ends of this are not optimal */ phyp_hcall(H_VIO_SIGNAL, sc->unit, 0); bus_dmamap_sync(sc->crq_tag, sc->crq_map, BUS_DMASYNC_POSTREAD); 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); } }