/* $NetBSD: umass_isdata.c,v 1.42.4.1 2022/12/30 14:39:10 martin Exp $ */ /* * TODO: * get ATA registers on any kind of error * implement more commands (what is needed) */ /* * Copyright (c) 2001 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 __KERNEL_RCSID(0, "$NetBSD: umass_isdata.c,v 1.42.4.1 2022/12/30 14:39:10 martin Exp $"); #ifdef _KERNEL_OPT #include "opt_usb.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include int umass_wd_attach(struct umass_softc *); #include #include /* * XXX This driver likely doesn't work after ATA NCQ changes. * XXX Need to confirm if the ata_channel kludge works */ /* XXX move this */ struct isd200_config { uByte EventNotification; uByte ExternalClock; uByte ATAInitTimeout; uByte ATAMisc1; #define ATATiming 0x0f #define ATAPIReset 0x10 #define MasterSlaveSelection 0x20 #define ATAPICommandBlockSize 0xc0 uByte ATAMajorCommand; uByte ATAMinorCommand; uByte ATAMisc2; #define LastLUNIdentifier 0x07 #define DescriptOverride 0x08 #define ATA3StateSuspend 0x10 #define SkipDeviceBoot 0x20 #define ConfigDescriptor2 0x40 #define InitStatus 0x80 uByte ATAMisc3; #define SRSTEnable 0x01 }; struct uisdata_softc { struct umassbus_softc base; struct ata_drive_datas sc_drv_data; struct ata_channel sc_channel; struct isd200_config sc_isd_config; u_long sc_skip; }; #define CH2SELF(chnl_softc) ((void *)chnl_softc->atabus) #undef DPRINTF #undef DPRINTFN #ifdef UISDATA_DEBUG #define DPRINTF(x) if (uisdatadebug) printf x #define DPRINTFN(n,x) if (uisdatadebug>(n)) printf x int uisdatadebug = 0; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif void uisdata_bio(struct ata_drive_datas *, struct ata_xfer *); void uisdata_bio1(struct ata_drive_datas *, struct ata_xfer *); void uisdata_reset_drive(struct ata_drive_datas *, int, uint32_t *); void uisdata_reset_channel(struct ata_channel *, int); void uisdata_exec_command(struct ata_drive_datas *, struct ata_xfer *); int uisdata_get_params(struct ata_drive_datas *, uint8_t, struct ataparams *); int uisdata_addref(struct ata_drive_datas *); void uisdata_delref(struct ata_drive_datas *); void uisdata_kill_pending(struct ata_drive_datas *); void uisdata_bio_cb(struct umass_softc *, void *, int, int); void uisdata_exec_cb(struct umass_softc *, void *, int, int); int uwdprint(void *, const char *); const struct ata_bustype uisdata_bustype = { SCSIPI_BUSTYPE_ATA, uisdata_bio, uisdata_reset_drive, uisdata_reset_channel, uisdata_exec_command, uisdata_get_params, uisdata_addref, uisdata_delref, uisdata_kill_pending, NULL, }; struct ata_cmd { uint8_t ac_signature0; uint8_t ac_signature1; uint8_t ac_action_select; #define AC_ReadRegisterAccess 0x01 #define AC_NoDeviceSelectionBit 0x02 #define AC_NoBSYPollBit 0x04 #define AC_IgnorePhaseErrorBit 0x08 #define AC_IgnoreDeviceErrorBit 0x10 uint8_t ac_register_select; #define AC_SelectAlternateStatus 0x01 /* R */ #define AC_SelectDeviceControl 0x01 /* W */ #define AC_SelectError 0x02 /* R */ #define AC_SelectFeatures 0x02 /* W */ #define AC_SelectSectorCount 0x04 /* RW */ #define AC_SelectSectorNumber 0x08 /* RW */ #define AC_SelectCylinderLow 0x10 /* RW */ #define AC_SelectCylinderHigh 0x20 /* RW */ #define AC_SelectDeviceHead 0x40 /* RW */ #define AC_SelectStatus 0x80 /* R */ #define AC_SelectCommand 0x80 /* W */ uint8_t ac_transfer_blocksize; uint8_t ac_alternate_status; #define ac_device_control ac_alternate_status uint8_t ac_error; #define ac_features ac_error uint8_t ac_sector_count; uint8_t ac_sector_number; uint8_t ac_cylinder_low; uint8_t ac_cylinder_high; uint8_t ac_device_head; uint8_t ac_status; #define ac_command ac_status uint8_t ac_reserved[3]; }; #define ATA_DELAY 10000 /* 10s for a drive I/O */ int umass_isdata_attach(struct umass_softc *sc) { usb_device_request_t req; usbd_status err; struct ata_device adev; struct uisdata_softc *scbus; struct isd200_config *cf; scbus = kmem_zalloc(sizeof(*scbus), KM_SLEEP); sc->bus = &scbus->base; cf = &scbus->sc_isd_config; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = 0x02; USETW(req.wValue, 0); USETW(req.wIndex, 2); USETW(req.wLength, sizeof(*cf)); err = usbd_do_request(sc->sc_udev, &req, cf); if (err) { sc->bus = NULL; kmem_free(scbus, sizeof(*scbus)); return EIO; } DPRINTF(("umass_wd_attach info:\n EventNotification=0x%02x " "ExternalClock=0x%02x ATAInitTimeout=0x%02x\n" " ATAMisc1=0x%02x ATAMajorCommand=0x%02x " "ATAMinorCommand=0x%02x\n" " ATAMisc2=0x%02x ATAMisc3=0x%02x\n", cf->EventNotification, cf->ExternalClock, cf->ATAInitTimeout, cf->ATAMisc1, cf->ATAMajorCommand, cf->ATAMinorCommand, cf->ATAMisc2, cf->ATAMisc3)); memset(&adev, 0, sizeof(struct ata_device)); adev.adev_bustype = &uisdata_bustype; adev.adev_channel = 1; /* XXX */ adev.adev_drv_data = &scbus->sc_drv_data; /* Fake ATA channel so wd(4) ata_{get,free}_xfer() work */ ata_channel_init(&scbus->sc_channel); scbus->sc_channel.atabus = (device_t)scbus; scbus->sc_drv_data.drive_type = ATA_DRIVET_ATA; scbus->sc_drv_data.chnl_softc = &scbus->sc_channel; scbus->base.sc_child = config_found(sc->sc_dev, &adev, uwdprint); return 0; } void umass_isdata_detach(struct umass_softc *sc) { struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus; ata_channel_destroy(&scbus->sc_channel); kmem_free(scbus, sizeof(*scbus)); sc->bus = NULL; } void uisdata_bio_cb(struct umass_softc *sc, void *priv, int residue, int status) { struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus; struct ata_xfer *xfer = priv; struct ata_bio *ata_bio = &xfer->c_bio; int s; DPRINTF(("%s: residue=%d status=%d\n", __func__, residue, status)); s = splbio(); if (status != STATUS_CMD_OK) ata_bio->error = ERR_DF; /* ??? */ else ata_bio->error = NOERROR; ata_bio->flags |= ATA_ITSDONE; ata_bio->blkdone += ata_bio->nblks; ata_bio->blkno += ata_bio->nblks; ata_bio->bcount -= ata_bio->nbytes; scbus->sc_skip += ata_bio->nbytes; if (residue != 0) { ata_bio->bcount += residue; } else if (ata_bio->bcount > 0) { DPRINTF(("%s: continue\n", __func__)); uisdata_bio1(&scbus->sc_drv_data, xfer); /*XXX save drv*/ splx(s); return; } if (ata_bio->flags & ATA_POLL) { DPRINTF(("%s: wakeup %p\n", __func__, ata_bio)); wakeup(ata_bio); } else { (*scbus->sc_drv_data.drv_done)(scbus->sc_drv_data.drv_softc, xfer); } splx(s); } void uisdata_bio(struct ata_drive_datas *drv, struct ata_xfer *xfer) { struct umass_softc *sc = CH2SELF(drv->chnl_softc); struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus; scbus->sc_skip = 0; uisdata_bio1(drv, xfer); } void uisdata_bio1(struct ata_drive_datas *drv, struct ata_xfer *xfer) { struct umass_softc *sc = CH2SELF(drv->chnl_softc); struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus; struct isd200_config *cf = &scbus->sc_isd_config; struct ata_bio *ata_bio = &xfer->c_bio; struct ata_cmd ata; uint16_t cyl; uint8_t head, sect; int dir; long nbytes; u_int nblks; DPRINTF(("%s\n", __func__)); /* XXX */ if (ata_bio->flags & ATA_POLL) { printf("%s: ATA_POLL not supported\n", __func__); ata_bio->error = TIMEOUT; ata_bio->flags |= ATA_ITSDONE; } if (ata_bio->flags & ATA_LBA) { sect = (ata_bio->blkno >> 0) & 0xff; cyl = (ata_bio->blkno >> 8) & 0xffff; head = (ata_bio->blkno >> 24) & 0x0f; head |= WDSD_LBA; } else { int blkno = ata_bio->blkno; sect = blkno % drv->lp->d_nsectors; sect++; /* Sectors begin with 1, not 0. */ blkno /= drv->lp->d_nsectors; head = blkno % drv->lp->d_ntracks; blkno /= drv->lp->d_ntracks; cyl = blkno; head |= WDSD_CHS; } nbytes = ata_bio->bcount; if (ata_bio->flags & ATA_SINGLE) nblks = 1; else nblks = uimin(drv->multi, nbytes / drv->lp->d_secsize); nbytes = nblks * drv->lp->d_secsize; ata_bio->nblks = nblks; ata_bio->nbytes = nbytes; memset(&ata, 0, sizeof(ata)); ata.ac_signature0 = cf->ATAMajorCommand; ata.ac_signature1 = cf->ATAMinorCommand; ata.ac_transfer_blocksize = 1; ata.ac_sector_count = nblks; ata.ac_sector_number = sect; ata.ac_cylinder_high = cyl >> 8; ata.ac_cylinder_low = cyl; ata.ac_device_head = head; ata.ac_register_select = AC_SelectSectorCount | AC_SelectSectorNumber | AC_SelectCylinderLow | AC_SelectCylinderHigh | AC_SelectDeviceHead | AC_SelectCommand; dir = DIR_NONE; if (ata_bio->bcount != 0) { if (ata_bio->flags & ATA_READ) dir = DIR_IN; else dir = DIR_OUT; } if (ata_bio->flags & ATA_READ) { ata.ac_command = WDCC_READ; } else { ata.ac_command = WDCC_WRITE; } DPRINTF(("%s: bno=%" PRId64 " LBA=%d cyl=%d head=%d sect=%d " "count=%d multi=%d\n", __func__, ata_bio->blkno, (ata_bio->flags & ATA_LBA) != 0, cyl, head, sect, ata.ac_sector_count, drv->multi)); DPRINTF((" data=%p bcount=%ld, drive=%d\n", ata_bio->databuf, ata_bio->bcount, drv->drive)); sc->sc_methods->wire_xfer(sc, drv->drive, &ata, sizeof(ata), ata_bio->databuf + scbus->sc_skip, nbytes, dir, ATA_DELAY, 0, uisdata_bio_cb, xfer); while (ata_bio->flags & ATA_POLL) { DPRINTF(("%s: tsleep %p\n", __func__, ata_bio)); if (tsleep(ata_bio, PZERO, "uisdatabl", 0)) { ata_bio->error = TIMEOUT; ata_bio->flags |= ATA_ITSDONE; } } } void uisdata_reset_drive(struct ata_drive_datas *drv, int flags, uint32_t *sigp) { DPRINTFN(-1,("%s\n", __func__)); KASSERT(sigp == NULL); /* XXX what? */ } void uisdata_reset_channel(struct ata_channel *chp, int flags) { DPRINTFN(-1,("%s\n", __func__)); /* XXX what? */ } void uisdata_exec_cb(struct umass_softc *sc, void *priv, int residue, int status) { struct ata_command *cmd = priv; DPRINTF(("%s: status=%d\n", __func__, status)); if (status != STATUS_CMD_OK) cmd->flags |= AT_DF; /* XXX */ cmd->flags |= AT_DONE; if (cmd->flags & (AT_READ | AT_WRITE)) cmd->flags |= AT_XFDONE; if (cmd->flags & (AT_POLL | AT_WAIT)) { DPRINTF(("%s: wakeup %p\n", __func__, cmd)); wakeup(cmd); } } void uisdata_exec_command(struct ata_drive_datas *drv, struct ata_xfer *xfer) { struct umass_softc *sc = CH2SELF(drv->chnl_softc); struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus; struct isd200_config *cf = &scbus->sc_isd_config; int dir; struct ata_command *cmd = &xfer->c_ata_c; struct ata_cmd ata; DPRINTF(("%s\n", __func__)); DPRINTF((" r_command=0x%02x timeout=%d flags=0x%x bcount=%d\n", cmd->r_command, cmd->timeout, cmd->flags, cmd->bcount)); dir = DIR_NONE; if (cmd->bcount != 0) { if (cmd->flags & AT_READ) dir = DIR_IN; else dir = DIR_OUT; } if (cmd->bcount > UMASS_MAX_TRANSFER_SIZE) { printf("uisdata_exec_command: large datalen %d\n", cmd->bcount); cmd->flags |= AT_ERROR; return; } memset(&ata, 0, sizeof(ata)); ata.ac_signature0 = cf->ATAMajorCommand; ata.ac_signature1 = cf->ATAMinorCommand; ata.ac_transfer_blocksize = 1; switch (cmd->r_command) { case WDCC_IDENTIFY: ata.ac_register_select |= AC_SelectCommand; ata.ac_command = WDCC_IDENTIFY; break; default: printf("uisdata_exec_command: bad command 0x%02x\n", cmd->r_command); cmd->flags |= AT_ERROR; return; } DPRINTF(("%s: execute ATA command 0x%02x, drive=%d\n", __func__, ata.ac_command, drv->drive)); sc->sc_methods->wire_xfer(sc, drv->drive, &ata, sizeof(ata), cmd->data, cmd->bcount, dir, cmd->timeout, 0, uisdata_exec_cb, cmd); if (cmd->flags & (AT_POLL | AT_WAIT)) { #if 0 if (cmd->flags & AT_POLL) printf("%s: AT_POLL not supported\n", __func__); #endif DPRINTF(("%s: tsleep %p\n", __func__, cmd)); if (tsleep(cmd, PZERO, "uisdataex", 0)) { cmd->flags |= AT_ERROR; return; } } } int uisdata_addref(struct ata_drive_datas *drv) { DPRINTF(("%s\n", __func__)); /* Nothing to do */ return 0; } void uisdata_delref(struct ata_drive_datas *drv) { DPRINTF(("%s\n", __func__)); /* Nothing to do */ } void uisdata_kill_pending(struct ata_drive_datas *drv) { struct ata_channel *chp = drv->chnl_softc; struct umass_softc *sc = CH2SELF(chp); struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus; struct ata_xfer *xfer = ata_queue_get_active_xfer(chp); struct ata_bio *ata_bio = &xfer->c_bio; DPRINTFN(-1,("%s\n", __func__)); if (xfer == NULL) return; ata_bio->flags |= ATA_ITSDONE; ata_bio->error = ERR_NODEV; ata_bio->r_error = WDCE_ABRT; (*scbus->sc_drv_data.drv_done)(scbus->sc_drv_data.drv_softc, xfer); } int uisdata_get_params(struct ata_drive_datas *drvp, uint8_t flags, struct ataparams *prms) { char tb[DEV_BSIZE]; struct ata_xfer *xfer; int rv; #if BYTE_ORDER == LITTLE_ENDIAN int i; uint16_t *p; #endif DPRINTF(("%s\n", __func__)); memset(tb, 0, DEV_BSIZE); memset(prms, 0, sizeof(struct ataparams)); xfer = ata_get_xfer(drvp->chnl_softc, false); if (!xfer) { rv = CMD_AGAIN; goto out; } xfer->c_ata_c.r_command = WDCC_IDENTIFY; xfer->c_ata_c.timeout = 1000; /* 1s */ xfer->c_ata_c.flags = AT_READ | flags; xfer->c_ata_c.data = tb; xfer->c_ata_c.bcount = DEV_BSIZE; uisdata_exec_command(drvp, xfer); ata_wait_cmd(drvp->chnl_softc, xfer); if (xfer->c_ata_c.flags & (AT_ERROR | AT_TIMEOU | AT_DF)) { DPRINTF(("uisdata_get_parms: ata_c.flags=0x%x\n", xfer->c_ata_c.flags)); rv = CMD_ERR; goto out; } /* Read in parameter block. */ memcpy(prms, tb, sizeof(struct ataparams)); #if BYTE_ORDER == LITTLE_ENDIAN /* XXX copied from ata.c */ /* * Shuffle string byte order. * ATAPI Mitsumi and NEC drives don't need this. */ if (prms->atap_config != WDC_CFG_CFA_MAGIC && (prms->atap_config & WDC_CFG_ATAPI) && ((prms->atap_model[0] == 'N' && prms->atap_model[1] == 'E') || (prms->atap_model[0] == 'F' && prms->atap_model[1] == 'X'))) { rv = 0; goto out; } for (i = 0; i < sizeof(prms->atap_model); i += 2) { p = (u_short *)(prms->atap_model + i); *p = ntohs(*p); } for (i = 0; i < sizeof(prms->atap_serial); i += 2) { p = (u_short *)(prms->atap_serial + i); *p = ntohs(*p); } for (i = 0; i < sizeof(prms->atap_revision); i += 2) { p = (u_short *)(prms->atap_revision + i); *p = ntohs(*p); } #endif rv = CMD_OK; out: ata_free_xfer(drvp->chnl_softc, xfer); return rv; } /* XXX join with wdc.c routine? */ int uwdprint(void *aux, const char *pnp) { //struct ata_device *adev = aux; if (pnp) aprint_normal("wd at %s", pnp); #if 0 aprint_normal(" channel %d drive %d", adev->adev_channel, adev->adev_drv_data->drive); #endif return UNCONF; } #if 0 int umass_wd_attach(struct umass_softc *); #if NWD > 0 case UMASS_CPROTO_ISD_ATA: return umass_wd_attach(sc); #endif #endif