Module Name: src Committed By: jmcneill Date: Wed Feb 12 11:33:35 UTC 2025
Modified Files: src/sys/arch/evbppc/conf: WII files.wii Added Files: src/sys/arch/evbppc/wii/dev: di.c Log Message: wii: Add support for Wii DVD drive. This adds a virtual SCSI HBA driver that is able to read DVD video discs inserted in the Wii. To generate a diff of this commit: cvs rdiff -u -r1.7 -r1.8 src/sys/arch/evbppc/conf/WII cvs rdiff -u -r1.4 -r1.5 src/sys/arch/evbppc/conf/files.wii cvs rdiff -u -r0 -r1.1 src/sys/arch/evbppc/wii/dev/di.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/arch/evbppc/conf/WII diff -u src/sys/arch/evbppc/conf/WII:1.7 src/sys/arch/evbppc/conf/WII:1.8 --- src/sys/arch/evbppc/conf/WII:1.7 Sun Jan 19 00:29:28 2025 +++ src/sys/arch/evbppc/conf/WII Wed Feb 12 11:33:34 2025 @@ -1,4 +1,4 @@ -# $NetBSD: WII,v 1.7 2025/01/19 00:29:28 jmcneill Exp $ +# $NetBSD: WII,v 1.8 2025/02/12 11:33:34 jmcneill Exp $ # # Nintendo Wii # @@ -143,7 +143,6 @@ gpioiic0 at gpio0 offset 0 mask 0xc000 f iic0 at gpioiic0 avenc0 at iic0 addr 0x70 # A/V Encoder -#iosipc0 at hollywood0 addr 0x0d000000 irq 30 # IOS IPC resetbtn0 at hollywood0 irq 17 # Reset button ehci0 at hollywood0 addr 0x0d040000 irq 4 # EHCI @@ -157,6 +156,8 @@ sdmmc* at sdmmcbus? ld* at sdmmc? bwi* at sdmmc? # WLAN +di0 at hollywood0 addr 0x0d806000 irq 18 # Drive interface + include "dev/usb/usbdevices.config" include "dev/bluetooth/bluetoothdevices.config" Index: src/sys/arch/evbppc/conf/files.wii diff -u src/sys/arch/evbppc/conf/files.wii:1.4 src/sys/arch/evbppc/conf/files.wii:1.5 --- src/sys/arch/evbppc/conf/files.wii:1.4 Thu Jan 25 11:47:53 2024 +++ src/sys/arch/evbppc/conf/files.wii Wed Feb 12 11:33:34 2025 @@ -1,4 +1,4 @@ -# $NetBSD: files.wii,v 1.4 2024/01/25 11:47:53 jmcneill Exp $ +# $NetBSD: files.wii,v 1.5 2025/02/12 11:33:34 jmcneill Exp $ # # maxpartitions 16 @@ -81,3 +81,7 @@ file arch/evbppc/wii/dev/sdhc_hollywood. device avenc attach avenc at iic file arch/evbppc/wii/dev/avenc.c avenc + +device di: scsi +attach di at hollywood +file arch/evbppc/wii/dev/di.c di Added files: Index: src/sys/arch/evbppc/wii/dev/di.c diff -u /dev/null src/sys/arch/evbppc/wii/dev/di.c:1.1 --- /dev/null Wed Feb 12 11:33:35 2025 +++ src/sys/arch/evbppc/wii/dev/di.c Wed Feb 12 11:33:34 2025 @@ -0,0 +1,700 @@ +/* $NetBSD: di.c,v 1.1 2025/02/12 11:33:34 jmcneill Exp $ */ + +/*- + * Copyright (c) 2025 Jared McNeill <jmcne...@invisible.ca> + * 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 ``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 <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: di.c,v 1.1 2025/02/12 11:33:34 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/systm.h> +#include <sys/callout.h> +#include <sys/buf.h> +#include <sys/dvdio.h> + +#include <uvm/uvm_extern.h> + +#include <dev/scsipi/scsi_all.h> +#include <dev/scsipi/scsi_disk.h> +#include <dev/scsipi/scsipi_all.h> +#include <dev/scsipi/scsipi_cd.h> +#include <dev/scsipi/scsipi_disk.h> +#include <dev/scsipi/scsiconf.h> + +#include <machine/wii.h> +#include <machine/pio.h> +#include "hollywood.h" + +#ifdef DI_DEBUG +#define DPRINTF(dv, fmt, ...) device_printf(dv, fmt, ## __VA_ARGS__) +#else +#define DPRINTF(dv, fmt, ...) +#endif + +#define DI_REG_SIZE 0x40 + +#define DISR 0x00 +#define DISR_BRKINT __BIT(6) +#define DISR_BRKINTMASK __BIT(5) +#define DISR_TCINT __BIT(4) +#define DISR_TCINTMASK __BIT(3) +#define DISR_DEINT __BIT(2) +#define DISR_DEINTMASK __BIT(1) +#define DISR_BRK __BIT(0) +#define DICVR 0x04 +#define DICVR_CVRINT __BIT(2) +#define DICVR_CVRINTMASK __BIT(1) +#define DICVR_CVR __BIT(0) +#define DICMDBUF0 0x08 +#define DICMDBUF1 0x0c +#define DICMDBUF2 0x10 +#define DIMAR 0x14 +#define DILENGTH 0x18 +#define DICR 0x1c +#define DICR_DMA __BIT(1) +#define DICR_TSTART __BIT(0) +#define DIMMBUF 0x20 +#define DICFG 0x24 + +#define DI_CMD_INQUIRY 0x12000000 +#define DI_CMD_REPORT_KEY(x) (0xa4000000 | ((uint32_t)(x) << 16)) +#define DI_CMD_READ_DVD_STRUCT(x) (0xad000000 | ((uint32_t)(x) << 24)) +#define DI_CMD_READ_DVD 0xd0000000 +#define DI_CMD_REQUEST_ERROR 0xe0000000 +#define DI_CMD_STOP_MOTOR 0xe3000000 + +#define DVDBLOCKSIZE 2048 + +#define DI_IDLE_TIMEOUT_MS 30000 + +struct di_softc; + +static int di_match(device_t, cfdata_t, void *); +static void di_attach(device_t, device_t, void *); + +static bool di_shutdown(device_t, int); + +static int di_intr(void *); +static void di_timeout(void *); +static void di_idle(void *); + +static void di_request(struct scsipi_channel *, scsipi_adapter_req_t, + void *); +static void di_init_regs(struct di_softc *); +static void di_reset(struct di_softc *, bool); + +struct di_response_inquiry { + uint16_t revision_level; + uint16_t device_code; + uint32_t release_date; + uint8_t padding[24]; +} __aligned(4); +CTASSERT(sizeof(struct di_response_inquiry) == 0x20); + +struct di_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + bus_dma_tag_t sc_dmat; + + struct scsipi_adapter sc_adapter; + struct scsipi_channel sc_channel; + + struct scsipi_xfer *sc_cur_xs; + callout_t sc_timeout; + callout_t sc_idle; + int sc_pamr; + + bus_dmamap_t sc_dma_map; + void *sc_dma_addr; + size_t sc_dma_size; + bus_dma_segment_t sc_dma_segs[1]; +}; + +#define WR4(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) +#define RD4(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) + +CFATTACH_DECL_NEW(di, sizeof(struct di_softc), + di_match, di_attach, NULL, NULL); + +static int +di_match(device_t parent, cfdata_t cf, void *aux) +{ + return 1; +} + +static void +di_attach(device_t parent, device_t self, void *aux) +{ + struct hollywood_attach_args *haa = aux; + struct di_softc *sc = device_private(self); + struct scsipi_adapter *adapt = &sc->sc_adapter; + struct scsipi_channel *chan = &sc->sc_channel; + int error, nsegs; + + sc->sc_dev = self; + sc->sc_dmat = haa->haa_dmat; + sc->sc_bst = haa->haa_bst; + error = bus_space_map(sc->sc_bst, haa->haa_addr, DI_REG_SIZE, + 0, &sc->sc_bsh); + if (error != 0) { + aprint_error(": couldn't map registers (%d)\n", error); + return; + } + + aprint_naive("\n"); + aprint_normal(": Drive Interface\n"); + + callout_init(&sc->sc_timeout, 0); + callout_setfunc(&sc->sc_timeout, di_timeout, sc); + callout_init(&sc->sc_idle, 0); + callout_setfunc(&sc->sc_idle, di_idle, sc); + + sc->sc_dma_size = MAXPHYS; + error = bus_dmamem_alloc(sc->sc_dmat, sc->sc_dma_size, PAGE_SIZE, 0, + sc->sc_dma_segs, 1, &nsegs, BUS_DMA_WAITOK); + if (error != 0) { + aprint_error_dev(self, "bus_dmamem_alloc failed: %d\n", error); + return; + } + error = bus_dmamem_map(sc->sc_dmat, sc->sc_dma_segs, nsegs, + sc->sc_dma_size, &sc->sc_dma_addr, BUS_DMA_WAITOK); + if (error != 0) { + aprint_error_dev(self, "bus_dmamem_map failed: %d\n", error); + return; + } + error = bus_dmamap_create(sc->sc_dmat, sc->sc_dma_size, nsegs, + sc->sc_dma_size, 0, BUS_DMA_WAITOK, &sc->sc_dma_map); + if (error != 0) { + aprint_error_dev(self, "bus_dmamap_create failed: %d\n", error); + return; + } + error = bus_dmamap_load(sc->sc_dmat, sc->sc_dma_map, sc->sc_dma_addr, + sc->sc_dma_size, NULL, BUS_DMA_WAITOK); + if (error != 0) { + aprint_error_dev(self, "bus_dmamap_load failed: %d\n", error); + return; + } + + memset(adapt, 0, sizeof(*adapt)); + adapt->adapt_nchannels = 1; + adapt->adapt_request = di_request; + adapt->adapt_minphys = minphys; + adapt->adapt_dev = self; + adapt->adapt_max_periph = 1; + adapt->adapt_openings = 1; + + memset(chan, 0, sizeof(*chan)); + chan->chan_bustype = &scsi_bustype; + chan->chan_ntargets = 2; + chan->chan_nluns = 1; + chan->chan_id = 0; + chan->chan_flags = SCSIPI_CHAN_NOSETTLE; + chan->chan_adapter = adapt; + + config_found(self, chan, scsiprint, CFARGS(.iattr = "scsi")); + + hollywood_intr_establish(haa->haa_irq, IPL_BIO, di_intr, sc, + device_xname(self)); + + di_init_regs(sc); + callout_schedule(&sc->sc_idle, mstohz(DI_IDLE_TIMEOUT_MS)); + + pmf_device_register1(self, NULL, NULL, di_shutdown); +} + +static bool +di_shutdown(device_t dev, int how) +{ + struct di_softc *sc = device_private(dev); + + di_reset(sc, false); + + return true; +} + +static void +di_sense(struct scsipi_xfer *xs, uint8_t skey, uint8_t asc, uint8_t ascq) +{ + struct scsi_sense_data *sense = &xs->sense.scsi_sense; + + xs->error = XS_SENSE; + sense->response_code = SSD_RCODE_CURRENT | SSD_RCODE_VALID; + sense->flags = skey; + sense->asc = asc; + sense->ascq = ascq; +} + +static void +di_request_error_sync(struct di_softc *sc, struct scsipi_xfer *xs) +{ + uint32_t imm; + int s; + + s = splbio(); + WR4(sc, DICMDBUF0, DI_CMD_REQUEST_ERROR); + WR4(sc, DICMDBUF1, 0); + WR4(sc, DICMDBUF2, 0); + WR4(sc, DILENGTH, 4); + WR4(sc, DICR, DICR_TSTART); + while (((RD4(sc, DISR) & DISR_TCINT)) == 0) { + delay(1); + } + imm = RD4(sc, DIMMBUF); + splx(s); + + DPRINTF(sc->sc_dev, "ERR IMMBUF = 0x%08x\n", imm); + di_sense(xs, (imm >> 16) & 0xff, (imm >> 8) & 0xff, imm & 0xff); +} + +static int +di_transfer_error(struct di_softc *sc, struct scsipi_xfer *xs) +{ + if (xs == NULL) { + return 0; + } + + DPRINTF(sc->sc_dev, "transfer error\n"); + + callout_stop(&sc->sc_timeout); + di_request_error_sync(sc, xs); + sc->sc_cur_xs = NULL; + scsipi_done(xs); + + return 1; +} + +static int +di_transfer_complete(struct di_softc *sc, struct scsipi_xfer *xs) +{ + struct scsipi_generic *cmd; + struct scsipi_inquiry_data *inqbuf; + struct scsipi_read_cd_cap_data *cdcap; + struct di_response_inquiry *rinq; + uint32_t imm; + uint8_t *data; + char buf[5]; + + if (xs == NULL) { + DPRINTF(sc->sc_dev, "no active transfer\n"); + return 0; + } + + KASSERT(sc->sc_cur_xs == xs); + + cmd = xs->cmd; + + switch (cmd->opcode) { + case INQUIRY: + inqbuf = (struct scsipi_inquiry_data *)xs->data; + rinq = sc->sc_dma_addr; + + bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, 0, sizeof(*rinq), + BUS_DMASYNC_POSTREAD); + + DPRINTF(sc->sc_dev, "revision_level %#x " + "device_code %#x " + "release_date %#x\n", + rinq->revision_level, + rinq->device_code, + rinq->release_date); + + memset(inqbuf, 0, sizeof(*inqbuf)); + inqbuf->device = T_CDROM; + inqbuf->dev_qual2 = SID_REMOVABLE; + strncpy(inqbuf->vendor, "NINTENDO", sizeof(inqbuf->vendor)); + snprintf(inqbuf->product, sizeof(inqbuf->product), "%08x", + rinq->release_date); + snprintf(buf, sizeof(buf), "%04x", rinq->revision_level); + memcpy(inqbuf->revision, buf, sizeof(inqbuf->revision)); + xs->resid = 0; + break; + + case SCSI_TEST_UNIT_READY: + case SCSI_REQUEST_SENSE: + imm = RD4(sc, DIMMBUF); + DPRINTF(sc->sc_dev, "TUR IMMBUF = 0x%08x\n", imm); + switch ((imm >> 24) & 0xff) { + case 0: + di_sense(xs, (imm >> 16) & 0xff, (imm >> 8) & 0xff, + imm & 0xff); + break; + default: + di_sense(xs, SKEY_MEDIUM_ERROR, 0, 0); + break; + } + break; + + case SCSI_READ_6_COMMAND: + case READ_10: + case GPCMD_REPORT_KEY: + bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, 0, xs->datalen, + BUS_DMASYNC_POSTREAD); + memcpy(xs->data, sc->sc_dma_addr, xs->datalen); + xs->resid = 0; + break; + + case GPCMD_READ_DVD_STRUCTURE: + bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, 0, DVDBLOCKSIZE, + BUS_DMASYNC_POSTREAD); + memcpy(xs->data + 4, sc->sc_dma_addr, xs->datalen - 4); + xs->resid = 0; + break; + + case READ_CD_CAPACITY: + cdcap = (struct scsipi_read_cd_cap_data *)xs->data; + + bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, 0, DVDBLOCKSIZE, + BUS_DMASYNC_POSTREAD); + data = sc->sc_dma_addr; + _lto4b(DVDBLOCKSIZE, cdcap->length); + memcpy(cdcap->addr, &data[8], sizeof(cdcap->addr)); + break; + } + + sc->sc_cur_xs = NULL; + scsipi_done(xs); + + return 1; +} + +static int +di_intr(void *priv) +{ + struct di_softc *sc = priv; + uint32_t sr, cvr; + int ret = 0; + + sr = RD4(sc, DISR); + cvr = RD4(sc, DICVR); + + if ((sr & DISR_DEINT) != 0) { + ret |= di_transfer_error(sc, sc->sc_cur_xs); + } else if ((sr & DISR_TCINT) != 0) { + ret |= di_transfer_complete(sc, sc->sc_cur_xs); + } + + if ((cvr & DICVR_CVRINT) != 0) { + DPRINTF(sc->sc_dev, "drive %s\n", + (cvr & DICVR_CVR) == 0 ? "closed" : "opened"); + ret |= 1; + } + + WR4(sc, DISR, sr); + WR4(sc, DICVR, cvr); + + return ret; +} + +static void +di_timeout(void *priv) +{ + struct di_softc *sc = priv; + int s; + + s = splbio(); + if (sc->sc_cur_xs != NULL) { + struct scsipi_xfer *xs = sc->sc_cur_xs; + + DPRINTF(sc->sc_dev, "command %#x timeout, DISR = %#x\n", + xs->cmd->opcode, RD4(sc, DISR)); + xs->error = XS_TIMEOUT; + scsipi_done(xs); + + sc->sc_cur_xs = NULL; + } + splx(s); +} + +static void +di_idle(void *priv) +{ + struct di_softc *sc = priv; + + if ((RD4(sc, DICVR) & DICVR_CVR) != 0) { + /* Cover is opened, nothing to do. */ + return; + } + + di_reset(sc, false); +} + +static void +di_start_request(struct di_softc *sc, struct scsipi_xfer *xs) +{ + KASSERT(sc->sc_cur_xs == NULL); + sc->sc_cur_xs = xs; + if (xs->timeout != 0) { + callout_schedule(&sc->sc_timeout, mstohz(xs->timeout) + 1); + } else { + DPRINTF(sc->sc_dev, "WARNING: xfer with no timeout!\n"); + callout_schedule(&sc->sc_timeout, mstohz(15000)); + } +} + +static void +di_init_regs(struct di_softc *sc) +{ + WR4(sc, DISR, DISR_BRKINT | + DISR_TCINT | DISR_TCINTMASK | + DISR_DEINT | DISR_DEINTMASK); + WR4(sc, DICVR, DICVR_CVRINT | DICVR_CVRINTMASK); +} + +static void +di_reset(struct di_softc *sc, bool spinup) +{ + uint32_t val; + int s; + + DPRINTF(sc->sc_dev, "reset spinup=%d\n", spinup); + + s = splhigh(); + + if (spinup) { + out32(HW_GPIOB_OUT, in32(HW_GPIOB_OUT) & ~__BIT(GPIO_DI_SPIN)); + } else { + out32(HW_GPIOB_OUT, in32(HW_GPIOB_OUT) | __BIT(GPIO_DI_SPIN)); + } + + val = in32(HW_RESETS); + out32(HW_RESETS, val & ~RSTB_IODI); + delay(12); + out32(HW_RESETS, val | RSTB_IODI); + + WR4(sc, DISR, DISR_BRKINT | + DISR_TCINT | DISR_TCINTMASK | + DISR_DEINT | DISR_DEINTMASK); + WR4(sc, DICVR, DICVR_CVRINT | DICVR_CVRINTMASK); + + splx(s); +} + +static void +di_stop_motor(struct di_softc *sc, struct scsipi_xfer *xs, bool eject) +{ + uint32_t cmdflags = 0; + int s; + + if (eject) { + cmdflags |= 1 << 17; + } + + s = splbio(); + WR4(sc, DICMDBUF0, DI_CMD_STOP_MOTOR | cmdflags); + WR4(sc, DICMDBUF1, 0); + WR4(sc, DICMDBUF2, 0); + WR4(sc, DILENGTH, 4); + WR4(sc, DICR, DICR_TSTART); + di_start_request(sc, xs); + splx(s); +} + +static void +di_request(struct scsipi_channel *chan, scsipi_adapter_req_t req, void *arg) +{ + struct di_softc *sc = device_private(chan->chan_adapter->adapt_dev); + struct scsipi_xfer *xs; + struct scsipi_generic *cmd; + struct scsipi_start_stop *ss; + struct scsi_prevent_allow_medium_removal *pamr; + uint32_t blkno; + int s; + + if (req != ADAPTER_REQ_RUN_XFER) { + return; + } + + callout_stop(&sc->sc_idle); + + KASSERT(sc->sc_cur_xs == NULL); + + xs = arg; + cmd = xs->cmd; + + switch (cmd->opcode) { + case INQUIRY: + bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, + 0, sizeof(struct di_response_inquiry), + BUS_DMASYNC_PREREAD); + + s = splbio(); + WR4(sc, DICMDBUF0, DI_CMD_INQUIRY); + WR4(sc, DICMDBUF1, 0); + WR4(sc, DILENGTH, sizeof(struct di_response_inquiry)); + WR4(sc, DIMAR, sc->sc_dma_segs[0].ds_addr); + WR4(sc, DICR, DICR_TSTART | DICR_DMA); + di_start_request(sc, xs); + splx(s); + break; + + case SCSI_TEST_UNIT_READY: + case SCSI_REQUEST_SENSE: + s = splbio(); + WR4(sc, DICMDBUF0, DI_CMD_REQUEST_ERROR); + WR4(sc, DICMDBUF1, 0); + WR4(sc, DICMDBUF2, 0); + WR4(sc, DILENGTH, 4); + WR4(sc, DICR, DICR_TSTART); + di_start_request(sc, xs); + splx(s); + break; + + case SCSI_READ_6_COMMAND: + case READ_10: + if (cmd->opcode == SCSI_READ_6_COMMAND) { + blkno = _3btol(((struct scsi_rw_6 *)cmd)->addr); + } else { + KASSERT(cmd->opcode == READ_10); + blkno = _4btol(((struct scsipi_rw_10 *)cmd)->addr); + } + + if (xs->datalen == 0) { + xs->error = XS_DRIVER_STUFFUP; + scsipi_done(xs); + break; + } + + bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, + 0, xs->datalen, BUS_DMASYNC_PREREAD); + + s = splbio(); + WR4(sc, DICMDBUF0, DI_CMD_READ_DVD); + WR4(sc, DICMDBUF1, blkno); + WR4(sc, DICMDBUF2, howmany(xs->datalen, DVDBLOCKSIZE)); + WR4(sc, DILENGTH, roundup(xs->datalen, DVDBLOCKSIZE)); + WR4(sc, DIMAR, sc->sc_dma_segs[0].ds_addr); + WR4(sc, DICR, DICR_TSTART | DICR_DMA); + di_start_request(sc, xs); + splx(s); + break; + + case GPCMD_READ_DVD_STRUCTURE: + if (xs->datalen == 0) { + DPRINTF(sc->sc_dev, "zero datalen\n"); + xs->error = XS_DRIVER_STUFFUP; + scsipi_done(xs); + break; + } + + bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, + 0, xs->datalen, BUS_DMASYNC_PREREAD); + + s = splbio(); + WR4(sc, DICMDBUF0, DI_CMD_READ_DVD_STRUCT(cmd->bytes[6])); + WR4(sc, DICMDBUF1, 0); + WR4(sc, DICMDBUF2, 0); + WR4(sc, DILENGTH, roundup(xs->datalen, DVDBLOCKSIZE)); + WR4(sc, DIMAR, sc->sc_dma_segs[0].ds_addr); + WR4(sc, DICR, DICR_TSTART | DICR_DMA); + di_start_request(sc, xs); + splx(s); + break; + + case GPCMD_REPORT_KEY: + if (xs->datalen == 0) { + DPRINTF(sc->sc_dev, "zero datalen\n"); + xs->error = XS_DRIVER_STUFFUP; + scsipi_done(xs); + break; + } + + bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, + 0, xs->datalen, BUS_DMASYNC_PREREAD); + + s = splbio(); + WR4(sc, DICMDBUF0, DI_CMD_REPORT_KEY(cmd->bytes[9] >> 2)); + WR4(sc, DICMDBUF1, _4btol(&cmd->bytes[1])); + WR4(sc, DICMDBUF2, 0); + WR4(sc, DILENGTH, roundup(xs->datalen, 0x20)); + WR4(sc, DIMAR, sc->sc_dma_segs[0].ds_addr); + WR4(sc, DICR, DICR_TSTART | DICR_DMA); + di_start_request(sc, xs); + splx(s); + break; + + case READ_CD_CAPACITY: + bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_map, + 0, DVDBLOCKSIZE, BUS_DMASYNC_PREREAD); + + s = splbio(); + WR4(sc, DICMDBUF0, DI_CMD_READ_DVD_STRUCT(DVD_STRUCT_PHYSICAL)); + WR4(sc, DICMDBUF1, 0); + WR4(sc, DICMDBUF2, 0); + WR4(sc, DILENGTH, DVDBLOCKSIZE); + WR4(sc, DIMAR, sc->sc_dma_segs[0].ds_addr); + WR4(sc, DICR, DICR_TSTART | DICR_DMA); + di_start_request(sc, xs); + splx(s); + break; + + case GET_CONFIGURATION: + memset(xs->data, 0, sizeof(struct scsipi_get_conf_data)); + xs->resid = 0; + scsipi_done(xs); + break; + + case READ_TOC: + memset(xs->data, 0, sizeof(struct scsipi_toc_header)); + xs->resid = 0; + scsipi_done(xs); + break; + + case READ_TRACKINFO: + case READ_DISCINFO: + di_sense(xs, SKEY_ILLEGAL_REQUEST, 0, 0); + scsipi_done(xs); + break; + + case START_STOP: + ss = (struct scsipi_start_stop *)cmd; + if (ss->how == SSS_START) { + di_reset(sc, true); + scsipi_done(xs); + } else { + di_stop_motor(sc, xs, (ss->how & SSS_LOEJ) != 0); + } + break; + + case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL: + pamr = (struct scsi_prevent_allow_medium_removal *)cmd; + sc->sc_pamr = pamr->how; + scsipi_done(xs); + break; + + default: + DPRINTF(sc->sc_dev, "unsupported opcode %#x\n", cmd->opcode); + scsipi_done(xs); + } + + if (!sc->sc_pamr) { + callout_schedule(&sc->sc_idle, mstohz(DI_IDLE_TIMEOUT_MS)); + } +}