/*- * Copyright (C) 2010 Nathan Whitehorn * Copyright (C) 2011 glevand * 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, * without modification, immediately at the beginning of the file. * 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 ``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 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 __FBSDID("$FreeBSD$"); #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 "ps3bus.h" #include "ps3-hvcall.h" #define PS3CDROM_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->sc_dev), "ps3cdrom", \ MTX_DEF) #define PS3CDROM_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); #define PS3CDROM_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define PS3CDROM_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define PS3CDROM_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); #define PS3CDROM_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); #define PS3CDROM_MAX_XFERS 3 #define LV1_STORAGE_SEND_ATAPI_COMMAND 0x01 struct ps3cdrom_softc; struct ps3cdrom_xfer { TAILQ_ENTRY(ps3cdrom_xfer) x_queue; struct ps3cdrom_softc *x_sc; union ccb *x_ccb; bus_dmamap_t x_dmamap; uint64_t x_tag; }; TAILQ_HEAD(ps3cdrom_xferq, ps3cdrom_xfer); struct ps3cdrom_softc { device_t sc_dev; struct mtx sc_mtx; uint64_t sc_blksize; uint64_t sc_nblocks; int sc_irqid; struct resource *sc_irq; void *sc_irqctx; bus_dma_tag_t sc_dmatag; struct cam_sim *sc_sim; struct cam_path *sc_path; struct ps3cdrom_xfer sc_xfer[PS3CDROM_MAX_XFERS]; struct ps3cdrom_xferq sc_active_xferq; struct ps3cdrom_xferq sc_free_xferq; }; enum lv1_ata_proto { NON_DATA_PROTO = 0x00, PIO_DATA_IN_PROTO = 0x01, PIO_DATA_OUT_PROTO = 0x02, DMA_PROTO = 0x03 }; enum lv1_ata_in_out { DIR_WRITE = 0x00, DIR_READ = 0x01 }; struct lv1_atapi_cmd { uint8_t pkt[32]; uint32_t pktlen; uint32_t nblocks; uint32_t blksize; uint32_t proto; /* enum lv1_ata_proto */ uint32_t in_out; /* enum lv1_ata_in_out */ uint64_t buf; uint32_t arglen; }; static void ps3cdrom_action(struct cam_sim *sim, union ccb *ccb); static void ps3cdrom_poll(struct cam_sim *sim); static void ps3cdrom_async(void *callback_arg, u_int32_t code, struct cam_path* path, void *arg); static void ps3cdrom_intr(void *arg); static void ps3cdrom_transfer(void *arg, bus_dma_segment_t *segs, int nsegs, int error); static int ps3cdrom_decode_lv1_status(uint64_t status, u_int8_t *sense_key, u_int8_t *asc, u_int8_t *ascq); static int ps3cdrom_probe(device_t dev) { if (ps3bus_get_bustype(dev) != PS3_BUSTYPE_STORAGE || ps3bus_get_devtype(dev) != PS3_DEVTYPE_CDROM) return (ENXIO); device_set_desc(dev, "Playstation 3 CDROM"); return (BUS_PROBE_SPECIFIC); } static int ps3cdrom_attach(device_t dev) { struct ps3cdrom_softc *sc = device_get_softc(dev); struct cam_devq *devq; struct ps3cdrom_xfer *xp; struct ccb_setasync csa; int i, err; sc->sc_dev = dev; PS3CDROM_LOCK_INIT(sc); /* Setup interrupt handler */ sc->sc_irqid = 0; sc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_irqid, RF_ACTIVE); if (!sc->sc_irq) { device_printf(dev, "Could not allocate IRQ\n"); err = ENXIO; goto fail_destroy_lock; } err = bus_setup_intr(dev, sc->sc_irq, INTR_TYPE_CAM | INTR_MPSAFE | INTR_ENTROPY, NULL, ps3cdrom_intr, sc, &sc->sc_irqctx); if (err) { device_printf(dev, "Could not setup IRQ\n"); err = ENXIO; goto fail_release_intr; } /* Setup DMA */ err = bus_dma_tag_create(bus_get_dma_tag(dev), 4096, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BUS_SPACE_UNRESTRICTED, 1, PAGE_SIZE, 0, busdma_lock_mutex, &sc->sc_mtx, &sc->sc_dmatag); if (err) { device_printf(dev, "Could not create DMA tag\n"); err = ENXIO; goto fail_teardown_intr; } /* Setup transfer queues */ TAILQ_INIT(&sc->sc_active_xferq); TAILQ_INIT(&sc->sc_free_xferq); for (i = 0; i < PS3CDROM_MAX_XFERS; i++) { xp = &sc->sc_xfer[i]; xp->x_sc = sc; err = bus_dmamap_create(sc->sc_dmatag, BUS_DMA_COHERENT, &xp->x_dmamap); if (err) { device_printf(dev, "Could not create DMA map (%d)\n", err); goto fail_destroy_dmamap; } TAILQ_INSERT_TAIL(&sc->sc_free_xferq, xp, x_queue); } /* Setup CAM */ devq = cam_simq_alloc(PS3CDROM_MAX_XFERS - 1); if (!devq) { device_printf(dev, "Could not allocate SIM queue\n"); err = ENOMEM; goto fail_destroy_dmatag; } sc->sc_sim = cam_sim_alloc(ps3cdrom_action, ps3cdrom_poll, "ps3cdrom", sc, device_get_unit(dev), &sc->sc_mtx, PS3CDROM_MAX_XFERS - 1, 0, devq); if (!sc->sc_sim) { device_printf(dev, "Could not allocate SIM\n"); cam_simq_free(devq); err = ENOMEM; goto fail_destroy_dmatag; } /* Setup XPT */ PS3CDROM_LOCK(sc); err = xpt_bus_register(sc->sc_sim, dev, 0); if (err != CAM_SUCCESS) { device_printf(dev, "Could not register XPT bus\n"); err = ENXIO; PS3CDROM_UNLOCK(sc); goto fail_free_sim; } err = xpt_create_path(&sc->sc_path, NULL, cam_sim_path(sc->sc_sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD); if (err != CAM_REQ_CMP) { device_printf(dev, "Could not create XPT path\n"); err = ENOMEM; PS3CDROM_UNLOCK(sc); goto fail_unregister_xpt_bus; } xpt_setup_ccb(&csa.ccb_h, sc->sc_path, 5); csa.ccb_h.func_code = XPT_SASYNC_CB; csa.event_enable = AC_LOST_DEVICE; csa.callback = ps3cdrom_async; csa.callback_arg = sc->sc_sim; xpt_action((union ccb *) &csa); CAM_DEBUG(sc->sc_path, CAM_DEBUG_TRACE, ("registered SIM for ps3cdrom%d\n", device_get_unit(dev))); PS3CDROM_UNLOCK(sc); return (BUS_PROBE_SPECIFIC); fail_unregister_xpt_bus: xpt_bus_deregister(cam_sim_path(sc->sc_sim)); fail_free_sim: cam_sim_free(sc->sc_sim, TRUE); fail_destroy_dmamap: while ((xp = TAILQ_FIRST(&sc->sc_free_xferq))) { TAILQ_REMOVE(&sc->sc_free_xferq, xp, x_queue); bus_dmamap_destroy(sc->sc_dmatag, xp->x_dmamap); } fail_destroy_dmatag: bus_dma_tag_destroy(sc->sc_dmatag); fail_teardown_intr: bus_teardown_intr(dev, sc->sc_irq, sc->sc_irqctx); fail_release_intr: bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irqid, sc->sc_irq); fail_destroy_lock: PS3CDROM_LOCK_DESTROY(sc); return (err); } static int ps3cdrom_detach(device_t dev) { struct ps3cdrom_softc *sc = device_get_softc(dev); int i; xpt_async(AC_LOST_DEVICE, sc->sc_path, NULL); xpt_free_path(sc->sc_path); xpt_bus_deregister(cam_sim_path(sc->sc_sim)); cam_sim_free(sc->sc_sim, TRUE); for (i = 0; i < PS3CDROM_MAX_XFERS; i++) bus_dmamap_destroy(sc->sc_dmatag, sc->sc_xfer[i].x_dmamap); bus_dma_tag_destroy(sc->sc_dmatag); bus_teardown_intr(dev, sc->sc_irq, sc->sc_irqctx); bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irqid, sc->sc_irq); PS3CDROM_LOCK_DESTROY(sc); return (0); } static void ps3cdrom_action(struct cam_sim *sim, union ccb *ccb) { struct ps3cdrom_softc *sc = (struct ps3cdrom_softc *)cam_sim_softc(sim); device_t dev = sc->sc_dev; struct ps3cdrom_xfer *xp; int err; PS3CDROM_ASSERT_LOCKED(sc); CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("function code 0x%02x\n", ccb->ccb_h.func_code)); switch (ccb->ccb_h.func_code) { case XPT_SCSI_IO: if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_INPROG) break; if(ccb->ccb_h.target_id > 0) { ccb->ccb_h.status = CAM_TID_INVALID; break; } if(ccb->ccb_h.target_lun > 0) { ccb->ccb_h.status = CAM_LUN_INVALID; break; } xp = TAILQ_FIRST(&sc->sc_free_xferq); KASSERT(xp != NULL, ("no free transfers")); xp->x_ccb = ccb; TAILQ_REMOVE(&sc->sc_free_xferq, xp, x_queue); err = bus_dmamap_load(sc->sc_dmatag, xp->x_dmamap, ccb->csio.data_ptr, ccb->csio.dxfer_len, ps3cdrom_transfer, xp, 0); if (err && err != EINPROGRESS) { device_printf(dev, "Could not load DMA map (%d)\n", err); xp->x_ccb = NULL; TAILQ_INSERT_TAIL(&sc->sc_free_xferq, xp, x_queue); ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; break; } return; case XPT_SET_TRAN_SETTINGS: ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; break; case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings *cts = &ccb->cts; cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; cts->transport_version = 2; cts->proto_specific.valid = 0; cts->xport_specific.valid = 0; ccb->ccb_h.status = CAM_REQ_CMP; break; } case XPT_RESET_BUS: case XPT_RESET_DEV: ccb->ccb_h.status = CAM_REQ_CMP; break; case XPT_CALC_GEOMETRY: cam_calc_geometry(&ccb->ccg, 1); break; case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; cpi->hba_inquiry = 0; cpi->target_sprt = 0; cpi->hba_inquiry = PI_SDTR_ABLE; cpi->hba_misc = PIM_NOBUSRESET | PIM_SEQSCAN | PIM_NO_6_BYTE; cpi->hba_eng_cnt = 0; bzero(cpi->vuhba_flags, sizeof(cpi->vuhba_flags)); cpi->max_target = 0; cpi->max_lun = 0; cpi->initiator_id = 7; cpi->bus_id = cam_sim_bus(sim); cpi->unit_number = cam_sim_unit(sim); cpi->base_transfer_speed = 150000; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "Sony", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->transport = XPORT_SPI; cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->maxio = PAGE_SIZE; cpi->ccb_h.status = CAM_REQ_CMP; break; } default: CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("unsupported function code 0x%02x\n", ccb->ccb_h.func_code)); ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); } static void ps3cdrom_poll(struct cam_sim *sim) { ps3cdrom_intr(cam_sim_softc(sim)); } static void ps3cdrom_async(void *callback_arg, u_int32_t code, struct cam_path* path, void *arg) { switch (code) { case AC_LOST_DEVICE: xpt_print_path(path); break; default: break; } } static void ps3cdrom_intr(void *arg) { struct ps3cdrom_softc *sc = (struct ps3cdrom_softc *) arg; device_t dev = sc->sc_dev; uint64_t devid = ps3bus_get_device(dev); struct ps3cdrom_xfer *xp; union ccb *ccb; u_int8_t *cdb, sense_key, asc, ascq; uint64_t tag, status; if (lv1_storage_get_async_status(devid, &tag, &status) != 0) return; PS3CDROM_LOCK(sc); /* Find transfer with the returned tag */ TAILQ_FOREACH(xp, &sc->sc_active_xferq, x_queue) { if (xp->x_tag == tag) break; } if (xp) { ccb = xp->x_ccb; cdb = (ccb->ccb_h.flags & CAM_CDB_POINTER) ? ccb->csio.cdb_io.cdb_ptr : ccb->csio.cdb_io.cdb_bytes; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("ATAPI command 0x%02x tag 0x%016lx completed (0x%016lx)\n", cdb[0], tag, status)); if (!status) { ccb->csio.scsi_status = SCSI_STATUS_OK; ccb->csio.resid = 0; ccb->ccb_h.status = CAM_REQ_CMP; } else { ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; if (!ps3cdrom_decode_lv1_status(status, &sense_key, &asc, &ascq)) { struct scsi_sense_data sense_data; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("sense key 0x%02x asc 0x%02x ascq 0x%02x\n", sense_key, asc, ascq)); bzero(&sense_data, sizeof(sense_data)); sense_data.error_code = SSD_CURRENT_ERROR; sense_data.flags |= sense_key; sense_data.extra_len = 0xa; sense_data.add_sense_code = asc; sense_data.add_sense_code_qual = ascq; ccb->csio.sense_len = sizeof(sense_data); bcopy(&sense_data, &ccb->csio.sense_data, ccb->csio.sense_len); ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID; } if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) ccb->csio.resid = ccb->csio.dxfer_len; } if (ccb->ccb_h.flags & CAM_DIR_IN) bus_dmamap_sync(sc->sc_dmatag, xp->x_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->sc_dmatag, xp->x_dmamap); xp->x_ccb = NULL; TAILQ_REMOVE(&sc->sc_active_xferq, xp, x_queue); TAILQ_INSERT_TAIL(&sc->sc_free_xferq, xp, x_queue); xpt_done(ccb); } else { device_printf(dev, "Could not find transfer with tag 0x%016lx\n", tag); } PS3CDROM_UNLOCK(sc); } static void ps3cdrom_transfer(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { struct ps3cdrom_xfer *xp = (struct ps3cdrom_xfer *) arg; struct ps3cdrom_softc *sc = xp->x_sc; device_t dev = sc->sc_dev; uint64_t devid = ps3bus_get_device(dev); union ccb *ccb = xp->x_ccb; u_int8_t *cdb; uint64_t start_sector, block_count; int err; KASSERT(nsegs == 1, ("invalid number of DMA segments")); PS3CDROM_ASSERT_LOCKED(sc); if (error) { device_printf(dev, "Could not load DMA map (%d)\n", error); xp->x_ccb = NULL; TAILQ_INSERT_TAIL(&sc->sc_free_xferq, xp, x_queue); ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; xpt_done(ccb); return; } cdb = (ccb->ccb_h.flags & CAM_CDB_POINTER) ? ccb->csio.cdb_io.cdb_ptr : ccb->csio.cdb_io.cdb_bytes; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("ATAPI command 0x%02x cdb_len %d dxfer_len %d\n ", cdb[0], ccb->csio.cdb_len, ccb->csio.dxfer_len)); switch (cdb[0]) { case READ_10: start_sector = (cdb[2] << 24) | (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; block_count = (cdb[7] << 8) | cdb[8]; err = lv1_storage_read(devid, 0 /* region id */, start_sector, block_count, 0 /* flags */, segs[0].ds_addr, &xp->x_tag); bus_dmamap_sync(sc->sc_dmatag, xp->x_dmamap, BUS_DMASYNC_POSTREAD); break; case WRITE_10: start_sector = (cdb[2] << 24) | (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; block_count = (cdb[7] << 8) | cdb[8]; bus_dmamap_sync(sc->sc_dmatag, xp->x_dmamap, BUS_DMASYNC_PREWRITE); err = lv1_storage_write(devid, 0 /* region id */, start_sector, block_count, 0 /* flags */, segs[0].ds_addr, &xp->x_tag); break; default: { struct lv1_atapi_cmd atapi_cmd; bzero(&atapi_cmd, sizeof(atapi_cmd)); atapi_cmd.pktlen = 12; bcopy(cdb, atapi_cmd.pkt, ccb->csio.cdb_len); if (ccb->ccb_h.flags & CAM_DIR_IN) { atapi_cmd.in_out = DIR_READ; atapi_cmd.proto = (ccb->csio.dxfer_len >= 2048) ? DMA_PROTO : PIO_DATA_IN_PROTO; } else if (ccb->ccb_h.flags & CAM_DIR_OUT) { atapi_cmd.in_out = DIR_WRITE; atapi_cmd.proto = (ccb->csio.dxfer_len >= 2048) ? DMA_PROTO : PIO_DATA_OUT_PROTO; } else { atapi_cmd.proto = NON_DATA_PROTO; } atapi_cmd.nblocks = atapi_cmd.arglen = segs[0].ds_len; atapi_cmd.blksize = 1; atapi_cmd.buf = segs[0].ds_addr; if (ccb->ccb_h.flags & CAM_DIR_OUT) bus_dmamap_sync(sc->sc_dmatag, xp->x_dmamap, BUS_DMASYNC_PREWRITE); err = lv1_storage_send_device_command(devid, LV1_STORAGE_SEND_ATAPI_COMMAND, vtophys(&atapi_cmd), sizeof(atapi_cmd), atapi_cmd.buf, atapi_cmd.arglen, &xp->x_tag); break; } } if (err) { struct scsi_sense_data sense_data; device_printf(dev, "ATAPI command 0x%02x failed (%d)\n", cdb[0], err); bus_dmamap_unload(sc->sc_dmatag, xp->x_dmamap); xp->x_ccb = NULL; TAILQ_INSERT_TAIL(&sc->sc_free_xferq, xp, x_queue); bzero(&sense_data, sizeof(sense_data)); sense_data.error_code = SSD_CURRENT_ERROR; sense_data.flags |= SSD_KEY_ILLEGAL_REQUEST; ccb->csio.sense_len = sizeof(sense_data); bcopy(&sense_data, &ccb->csio.sense_data, ccb->csio.sense_len); ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID; xpt_done(ccb); } else { CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("ATAPI command 0x%02x tag 0x%016lx submitted\n ", cdb[0], xp->x_tag)); TAILQ_INSERT_TAIL(&sc->sc_active_xferq, xp, x_queue); ccb->ccb_h.status |= CAM_SIM_QUEUED; } } static int ps3cdrom_decode_lv1_status(uint64_t status, u_int8_t *sense_key, u_int8_t *asc, u_int8_t *ascq) { if (((status >> 24) & 0xff) != SCSI_STATUS_CHECK_COND) return -1; *sense_key = (status >> 16) & 0xff; *asc = (status >> 8) & 0xff; *ascq = status & 0xff; return (0); } static device_method_t ps3cdrom_methods[] = { DEVMETHOD(device_probe, ps3cdrom_probe), DEVMETHOD(device_attach, ps3cdrom_attach), DEVMETHOD(device_detach, ps3cdrom_detach), {0, 0}, }; static driver_t ps3cdrom_driver = { "ps3cdrom", ps3cdrom_methods, sizeof(struct ps3cdrom_softc), }; static devclass_t ps3cdrom_devclass; DRIVER_MODULE(ps3cdrom, ps3bus, ps3cdrom_driver, ps3cdrom_devclass, 0, 0); MODULE_DEPEND(ps3cdrom, cam, 1, 1, 1);