/* $OpenBSD: sdmmc_scsi.c,v 1.63 2023/04/19 01:46:10 dlg Exp $ */ /* * Copyright (c) 2006 Uwe Stuehler * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* A SCSI adapter emulation to access SD/MMC memory cards */ #include #include #include #include #include #include #include #include #include #include #include #ifdef HIBERNATE #include #include #include #include #endif #define SDMMC_SCSIID_HOST 0x00 #define SDMMC_SCSIID_MAX 0x0f #define SDMMC_SCSI_MAXCMDS 8 struct sdmmc_scsi_target { struct sdmmc_function *card; }; struct sdmmc_ccb { struct sdmmc_scsi_softc *ccb_scbus; struct scsi_xfer *ccb_xs; int ccb_flags; #define SDMMC_CCB_F_ERR 0x0001 u_int32_t ccb_blockno; u_int32_t ccb_blockcnt; volatile enum { SDMMC_CCB_FREE, SDMMC_CCB_READY, SDMMC_CCB_QUEUED } ccb_state; struct sdmmc_command ccb_cmd; struct sdmmc_task ccb_task; TAILQ_ENTRY(sdmmc_ccb) ccb_link; }; TAILQ_HEAD(sdmmc_ccb_list, sdmmc_ccb); struct sdmmc_scsi_softc { struct device *sc_child; struct sdmmc_scsi_target *sc_tgt; int sc_ntargets; struct sdmmc_ccb *sc_ccbs; /* allocated ccbs */ int sc_nccbs; struct sdmmc_ccb_list sc_ccb_freeq; /* free ccbs */ struct sdmmc_ccb_list sc_ccb_runq; /* queued ccbs */ struct mutex sc_ccb_mtx; struct scsi_iopool sc_iopool; }; int sdmmc_alloc_ccbs(struct sdmmc_scsi_softc *, int); void sdmmc_free_ccbs(struct sdmmc_scsi_softc *); void *sdmmc_ccb_alloc(void *); void sdmmc_ccb_free(void *, void *); void sdmmc_scsi_cmd(struct scsi_xfer *); void sdmmc_inquiry(struct scsi_xfer *); void sdmmc_start_xs(struct sdmmc_softc *, struct sdmmc_ccb *); void sdmmc_complete_xs(void *); void sdmmc_done_xs(struct sdmmc_ccb *); void sdmmc_stimeout(void *); void sdmmc_minphys(struct buf *, struct scsi_link *); const struct scsi_adapter sdmmc_switch = { sdmmc_scsi_cmd, sdmmc_minphys, NULL, NULL, NULL }; #ifdef SDMMC_DEBUG #define DPRINTF(s) printf s #else #define DPRINTF(s) /**/ #endif void sdmmc_scsi_attach(struct sdmmc_softc *sc) { struct sdmmc_attach_args saa; struct sdmmc_scsi_softc *scbus; struct sdmmc_function *sf; rw_assert_wrlock(&sc->sc_lock); scbus = malloc(sizeof *scbus, M_DEVBUF, M_WAITOK | M_ZERO); scbus->sc_tgt = mallocarray(sizeof(*scbus->sc_tgt), (SDMMC_SCSIID_MAX+1), M_DEVBUF, M_WAITOK | M_ZERO); /* * Each card that sent us a CID in the identification stage * gets a SCSI ID > 0, whether it is a memory card or not. */ scbus->sc_ntargets = 1; SIMPLEQ_FOREACH(sf, &sc->sf_head, sf_list) { if (scbus->sc_ntargets >= SDMMC_SCSIID_MAX+1) break; scbus->sc_tgt[scbus->sc_ntargets].card = sf; scbus->sc_ntargets++; } /* Preallocate some CCBs and initialize the CCB lists. */ if (sdmmc_alloc_ccbs(scbus, SDMMC_SCSI_MAXCMDS) != 0) { printf("%s: can't allocate ccbs\n", sc->sc_dev.dv_xname); goto free_sctgt; } sc->sc_scsibus = scbus; saa.sf = NULL; saa.saa.saa_adapter_target = SDMMC_SCSIID_HOST; saa.saa.saa_adapter_buswidth = scbus->sc_ntargets; saa.saa.saa_adapter_softc = sc; saa.saa.saa_luns = 1; saa.saa.saa_adapter = &sdmmc_switch; saa.saa.saa_openings = 1; saa.saa.saa_pool = &scbus->sc_iopool; saa.saa.saa_quirks = saa.saa.saa_flags = 0; saa.saa.saa_wwpn = saa.saa.saa_wwnn = 0; scbus->sc_child = config_found(&sc->sc_dev, &saa, scsiprint); if (scbus->sc_child == NULL) { printf("%s: can't attach scsibus\n", sc->sc_dev.dv_xname); goto free_ccbs; } return; free_ccbs: sc->sc_scsibus = NULL; sdmmc_free_ccbs(scbus); free_sctgt: free(scbus->sc_tgt, M_DEVBUF, sizeof(*scbus->sc_tgt) * (SDMMC_SCSIID_MAX+1)); free(scbus, M_DEVBUF, sizeof *scbus); } void sdmmc_scsi_detach(struct sdmmc_softc *sc) { struct sdmmc_scsi_softc *scbus; struct sdmmc_ccb *ccb; int s; rw_assert_wrlock(&sc->sc_lock); scbus = sc->sc_scsibus; if (scbus == NULL) return; /* Complete all open scsi xfers. */ s = splbio(); for (ccb = TAILQ_FIRST(&scbus->sc_ccb_runq); ccb != NULL; ccb = TAILQ_FIRST(&scbus->sc_ccb_runq)) sdmmc_stimeout(ccb); splx(s); if (scbus->sc_child != NULL) config_detach(scbus->sc_child, DETACH_FORCE); if (scbus->sc_tgt != NULL) free(scbus->sc_tgt, M_DEVBUF, sizeof(*scbus->sc_tgt) * (SDMMC_SCSIID_MAX+1)); sdmmc_free_ccbs(scbus); free(scbus, M_DEVBUF, sizeof *scbus); sc->sc_scsibus = NULL; } /* * CCB management */ int sdmmc_alloc_ccbs(struct sdmmc_scsi_softc *scbus, int nccbs) { struct sdmmc_ccb *ccb; int i; scbus->sc_ccbs = mallocarray(nccbs, sizeof(struct sdmmc_ccb), M_DEVBUF, M_NOWAIT); if (scbus->sc_ccbs == NULL) return 1; scbus->sc_nccbs = nccbs; TAILQ_INIT(&scbus->sc_ccb_freeq); TAILQ_INIT(&scbus->sc_ccb_runq); mtx_init(&scbus->sc_ccb_mtx, IPL_BIO); scsi_iopool_init(&scbus->sc_iopool, scbus, sdmmc_ccb_alloc, sdmmc_ccb_free); for (i = 0; i < nccbs; i++) { ccb = &scbus->sc_ccbs[i]; ccb->ccb_scbus = scbus; ccb->ccb_state = SDMMC_CCB_FREE; ccb->ccb_flags = 0; ccb->ccb_xs = NULL; TAILQ_INSERT_TAIL(&scbus->sc_ccb_freeq, ccb, ccb_link); } return 0; } void sdmmc_free_ccbs(struct sdmmc_scsi_softc *scbus) { if (scbus->sc_ccbs != NULL) { free(scbus->sc_ccbs, M_DEVBUF, scbus->sc_nccbs * sizeof(struct sdmmc_ccb)); scbus->sc_ccbs = NULL; } } void * sdmmc_ccb_alloc(void *xscbus) { struct sdmmc_scsi_softc *scbus = xscbus; struct sdmmc_ccb *ccb; mtx_enter(&scbus->sc_ccb_mtx); ccb = TAILQ_FIRST(&scbus->sc_ccb_freeq); if (ccb != NULL) { TAILQ_REMOVE(&scbus->sc_ccb_freeq, ccb, ccb_link); ccb->ccb_state = SDMMC_CCB_READY; } mtx_leave(&scbus->sc_ccb_mtx); return ccb; } void sdmmc_ccb_free(void *xscbus, void *xccb) { struct sdmmc_scsi_softc *scbus = xscbus; struct sdmmc_ccb *ccb = xccb; int s; s = splbio(); if (ccb->ccb_state == SDMMC_CCB_QUEUED) TAILQ_REMOVE(&scbus->sc_ccb_runq, ccb, ccb_link); splx(s); ccb->ccb_state = SDMMC_CCB_FREE; ccb->ccb_flags = 0; ccb->ccb_xs = NULL; mtx_enter(&scbus->sc_ccb_mtx); TAILQ_INSERT_TAIL(&scbus->sc_ccb_freeq, ccb, ccb_link); mtx_leave(&scbus->sc_ccb_mtx); } /* * SCSI command emulation */ /* XXX move to some sort of "scsi emulation layer". */ static void sdmmc_scsi_decode_rw(struct scsi_xfer *xs, u_int32_t *blocknop, u_int32_t *blockcntp) { struct scsi_rw *rw; struct scsi_rw_10 *rw10; if (xs->cmdlen == 6) { rw = (struct scsi_rw *)&xs->cmd; *blocknop = _3btol(rw->addr) & (SRW_TOPADDR << 16 | 0xffff); *blockcntp = rw->length ? rw->length : 0x100; } else { rw10 = (struct scsi_rw_10 *)&xs->cmd; *blocknop = _4btol(rw10->addr); *blockcntp = _2btol(rw10->length); } } void sdmmc_scsi_cmd(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; struct sdmmc_softc *sc = link->bus->sb_adapter_softc; struct sdmmc_scsi_softc *scbus = sc->sc_scsibus; struct sdmmc_scsi_target *tgt = &scbus->sc_tgt[link->target]; struct scsi_read_cap_data rcd; u_int32_t blockno; u_int32_t blockcnt; struct sdmmc_ccb *ccb; if (link->target >= scbus->sc_ntargets || tgt->card == NULL || link->lun != 0) { DPRINTF(("%s: sdmmc_scsi_cmd: no target %d\n", DEVNAME(sc), link->target)); /* XXX should be XS_SENSE and sense filled out */ xs->error = XS_DRIVER_STUFFUP; scsi_done(xs); return; } DPRINTF(("%s: scsi cmd target=%d opcode=%#x proc=\"%s\" (poll=%#x)\n", DEVNAME(sc), link->target, xs->cmd.opcode, curproc ? curproc->p_p->ps_comm : "", xs->flags & SCSI_POLL)); xs->error = XS_NOERROR; switch (xs->cmd.opcode) { case READ_COMMAND: case READ_10: case WRITE_COMMAND: case WRITE_10: /* Deal with I/O outside the switch. */ break; case INQUIRY: sdmmc_inquiry(xs); return; case TEST_UNIT_READY: case START_STOP: case SYNCHRONIZE_CACHE: scsi_done(xs); return; case READ_CAPACITY: bzero(&rcd, sizeof rcd); _lto4b(tgt->card->csd.capacity - 1, rcd.addr); _lto4b(tgt->card->csd.sector_size, rcd.length); bcopy(&rcd, xs->data, MIN(xs->datalen, sizeof rcd)); scsi_done(xs); return; default: DPRINTF(("%s: unsupported scsi command %#x\n", DEVNAME(sc), xs->cmd.opcode)); xs->error = XS_DRIVER_STUFFUP; scsi_done(xs); return; } /* A read or write operation. */ sdmmc_scsi_decode_rw(xs, &blockno, &blockcnt); if (blockno >= tgt->card->csd.capacity || blockno + blockcnt > tgt->card->csd.capacity) { DPRINTF(("%s: out of bounds %u-%u >= %u\n", DEVNAME(sc), blockno, blockcnt, tgt->card->csd.capacity)); xs->error = XS_DRIVER_STUFFUP; scsi_done(xs); return; } ccb = xs->io; ccb->ccb_xs = xs; ccb->ccb_blockcnt = blockcnt; ccb->ccb_blockno = blockno; sdmmc_start_xs(sc, ccb); } void sdmmc_inquiry(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; struct sdmmc_softc *sc = link->bus->sb_adapter_softc; struct sdmmc_scsi_softc *scbus = sc->sc_scsibus; struct sdmmc_scsi_target *tgt = &scbus->sc_tgt[link->target]; struct scsi_inquiry_data inq; struct scsi_inquiry *cdb = (struct scsi_inquiry *)&xs->cmd; char vendor[sizeof(inq.vendor) + 1]; char product[sizeof(inq.product) + 1]; char revision[sizeof(inq.revision) + 1]; if (xs->cmdlen != sizeof(*cdb)) { xs->error = XS_DRIVER_STUFFUP; goto done; } if (ISSET(cdb->flags, SI_EVPD)) { xs->error = XS_DRIVER_STUFFUP; goto done; } memset(vendor, 0, sizeof(vendor)); memset(product, 0, sizeof(product)); memset(revision, 0, sizeof(revision)); switch (tgt->card->cid.mid) { case 0x02: case 0x03: case 0x45: strlcpy(vendor, "Sandisk", sizeof(vendor)); break; case 0x11: strlcpy(vendor, "Toshiba", sizeof(vendor)); break; case 0x13: strlcpy(vendor, "Micron", sizeof(vendor)); break; case 0x15: strlcpy(vendor, "Samsung", sizeof(vendor)); break; case 0x27: strlcpy(vendor, "Apacer", sizeof(vendor)); break; case 0x70: strlcpy(vendor, "Kingston", sizeof(vendor)); break; case 0x90: strlcpy(vendor, "Hynix", sizeof(vendor)); break; default: strlcpy(vendor, "SD/MMC", sizeof(vendor)); break; } strlcpy(product, tgt->card->cid.pnm, sizeof(product)); snprintf(revision, sizeof(revision), "%04X", tgt->card->cid.rev); memset(&inq, 0, sizeof inq); inq.device = T_DIRECT; if (!ISSET(sc->sc_caps, SMC_CAPS_NONREMOVABLE)) inq.dev_qual2 = SID_REMOVABLE; inq.version = SCSI_REV_2; inq.response_format = SID_SCSI2_RESPONSE; inq.additional_length = SID_SCSI2_ALEN; memcpy(inq.vendor, vendor, sizeof(inq.vendor)); memcpy(inq.product, product, sizeof(inq.product)); memcpy(inq.revision, revision, sizeof(inq.revision)); scsi_copy_internal_data(xs, &inq, sizeof(inq)); done: scsi_done(xs); } void sdmmc_start_xs(struct sdmmc_softc *sc, struct sdmmc_ccb *ccb) { struct sdmmc_scsi_softc *scbus = sc->sc_scsibus; struct scsi_xfer *xs = ccb->ccb_xs; int s; timeout_set(&xs->stimeout, sdmmc_stimeout, ccb); sdmmc_init_task(&ccb->ccb_task, sdmmc_complete_xs, ccb); s = splbio(); TAILQ_INSERT_TAIL(&scbus->sc_ccb_runq, ccb, ccb_link); ccb->ccb_state = SDMMC_CCB_QUEUED; splx(s); if (ISSET(xs->flags, SCSI_POLL)) { sdmmc_complete_xs(ccb); return; } timeout_add_msec(&xs->stimeout, xs->timeout); sdmmc_add_task(sc, &ccb->ccb_task); } void sdmmc_complete_xs(void *arg) { struct sdmmc_ccb *ccb = arg; struct scsi_xfer *xs = ccb->ccb_xs; struct scsi_link *link = xs->sc_link; struct sdmmc_softc *sc = link->bus->sb_adapter_softc; struct sdmmc_scsi_softc *scbus = sc->sc_scsibus; struct sdmmc_scsi_target *tgt = &scbus->sc_tgt[link->target]; int error; int s; DPRINTF(("%s: scsi cmd target=%d opcode=%#x proc=\"%s\" (poll=%#x)" " complete\n", DEVNAME(sc), link->target, xs->cmd.opcode, curproc ? curproc->p_p->ps_comm : "", xs->flags & SCSI_POLL)); s = splbio(); if (ISSET(xs->flags, SCSI_DATA_IN)) error = sdmmc_mem_read_block(tgt->card, ccb->ccb_blockno, xs->data, ccb->ccb_blockcnt * DEV_BSIZE); else error = sdmmc_mem_write_block(tgt->card, ccb->ccb_blockno, xs->data, ccb->ccb_blockcnt * DEV_BSIZE); if (error != 0) xs->error = XS_DRIVER_STUFFUP; sdmmc_done_xs(ccb); splx(s); } void sdmmc_done_xs(struct sdmmc_ccb *ccb) { struct scsi_xfer *xs = ccb->ccb_xs; #ifdef SDMMC_DEBUG struct scsi_link *link = xs->sc_link; struct sdmmc_softc *sc = link->bus->sb_adapter_softc; #endif timeout_del(&xs->stimeout); DPRINTF(("%s: scsi cmd target=%d opcode=%#x proc=\"%s\" (error=%#x)" " done\n", DEVNAME(sc), link->target, xs->cmd.opcode, curproc ? curproc->p_p->ps_comm : "", xs->error)); xs->resid = 0; if (ISSET(ccb->ccb_flags, SDMMC_CCB_F_ERR)) xs->error = XS_DRIVER_STUFFUP; scsi_done(xs); } void sdmmc_stimeout(void *arg) { struct sdmmc_ccb *ccb = arg; int s; s = splbio(); ccb->ccb_flags |= SDMMC_CCB_F_ERR; if (sdmmc_task_pending(&ccb->ccb_task)) { sdmmc_del_task(&ccb->ccb_task); sdmmc_done_xs(ccb); } splx(s); } void sdmmc_minphys(struct buf *bp, struct scsi_link *sl) { struct sdmmc_softc *sc = sl->bus->sb_adapter_softc; struct sdmmc_scsi_softc *scbus = sc->sc_scsibus; struct sdmmc_scsi_target *tgt = &scbus->sc_tgt[sl->target]; struct sdmmc_function *sf = tgt->card; /* limit to max. transfer size supported by card/host */ if (sc->sc_max_xfer != 0 && bp->b_bcount > sf->csd.sector_size * sc->sc_max_xfer) bp->b_bcount = sf->csd.sector_size * sc->sc_max_xfer; else minphys(bp); } #ifdef HIBERNATE int sdmmc_scsi_hibernate_io(dev_t dev, daddr_t blkno, vaddr_t addr, size_t size, int op, void *page) { struct { struct sdmmc_softc sdmmc_sc; struct sdmmc_function sdmmc_sf; daddr_t poffset; size_t psize; struct sdmmc_function *orig_sf; char chipset_softc[0]; /* size depends on the chipset layer */ } *state = page; extern struct cfdriver sd_cd; struct device *disk, *scsibus, *chip, *sdmmc; struct scsibus_softc *bus_sc; struct sdmmc_scsi_softc *scsi_sc; struct scsi_link *link; struct sdmmc_function *sf; struct sdmmc_softc *sc; int error; switch (op) { case HIB_INIT: /* find device (sdmmc_softc, sdmmc_function) */ disk = disk_lookup(&sd_cd, DISKUNIT(dev)); if (disk == NULL) return (ENOTTY); scsibus = disk->dv_parent; sdmmc = scsibus->dv_parent; chip = sdmmc->dv_parent; bus_sc = (struct scsibus_softc *)scsibus; scsi_sc = (struct sdmmc_scsi_softc *)scsibus; sc = NULL; SLIST_FOREACH(link, &bus_sc->sc_link_list, bus_list) { if (link->device_softc == disk) { sc = link->bus->sb_adapter_softc; scsi_sc = sc->sc_scsibus; sf = scsi_sc->sc_tgt[link->target].card; } } if (sc == NULL || sf == NULL) return (ENOTTY); /* if the chipset doesn't do hibernate, bail out now */ sc = (struct sdmmc_softc *)sdmmc; if (sc->sct->hibernate_init == NULL) return (ENOTTY); state->sdmmc_sc = *sc; state->sdmmc_sf = *sf; state->sdmmc_sf.sc = &state->sdmmc_sc; /* pretend we own the lock */ state->sdmmc_sc.sc_lock.rwl_owner = (((long)curproc) & ~RWLOCK_MASK) | RWLOCK_WRLOCK; /* build chip layer fake softc */ error = state->sdmmc_sc.sct->hibernate_init(state->sdmmc_sc.sch, &state->chipset_softc); if (error) return (error); state->sdmmc_sc.sch = state->chipset_softc; /* make sure we're talking to the right target */ state->orig_sf = sc->sc_card; error = sdmmc_select_card(&state->sdmmc_sc, &state->sdmmc_sf); if (error) return (error); state->poffset = blkno; state->psize = size; return (0); case HIB_W: if (blkno > state->psize) return (E2BIG); return (sdmmc_mem_hibernate_write(&state->sdmmc_sf, blkno + state->poffset, (u_char *)addr, size)); case HIB_DONE: /* * bring the hardware state back into line with the real * softc by operating on the fake one */ return (sdmmc_select_card(&state->sdmmc_sc, state->orig_sf)); } return (EINVAL); } #endif