diff options
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/ata/ata-usb.c | 980 |
1 files changed, 980 insertions, 0 deletions
diff --git a/sys/dev/ata/ata-usb.c b/sys/dev/ata/ata-usb.c new file mode 100644 index 0000000..01a442a --- /dev/null +++ b/sys/dev/ata/ata-usb.c @@ -0,0 +1,980 @@ +/*- + * Copyright (c) 2006 Søren Schmidt <sos@FreeBSD.org> + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_ata.h" +#include <sys/param.h> +#include <sys/conf.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/mutex.h> +#include <sys/ata.h> +#include <sys/bus.h> +#include <sys/endian.h> +#include <sys/sysctl.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/sema.h> +#include <sys/taskqueue.h> +#include <vm/uma.h> +#include <machine/resource.h> +#include <machine/bus.h> +#include <sys/rman.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include <dev/ata/ata-all.h> +#include <ata_if.h> + +/* Command Block Wrapper */ +struct bbb_cbw { + u_int8_t signature[4]; +#define CBWSIGNATURE 0x43425355 + + u_int8_t tag[4]; + u_int8_t transfer_length[4]; + u_int8_t flags; +#define CBWFLAGS_OUT 0x00 +#define CBWFLAGS_IN 0x80 + + u_int8_t lun; + u_int8_t length; +#define CBWCDBLENGTH 16 + + u_int8_t cdb[CBWCDBLENGTH]; +}; + +/* Command Status Wrapper */ +struct bbb_csw { + u_int8_t signature[4]; +#define CSWSIGNATURE 0x53425355 + + u_int8_t tag[4]; + u_int8_t residue[4]; + u_int8_t status; +#define CSWSTATUS_GOOD 0x0 +#define CSWSTATUS_FAILED 0x1 +#define CSWSTATUS_PHASE 0x2 +}; + +/* USB-ATA 'controller' softc */ +struct atausb_softc { + USBBASEDEVICE dev; /* base device */ + usbd_interface_handle iface; /* interface */ + int ifaceno; /* interface number */ + u_int8_t bulkin; /* endpoint address's */ + u_int8_t bulkout; + u_int8_t bulkirq; + usbd_pipe_handle bulkin_pipe; /* pipe handle's */ + usbd_pipe_handle bulkout_pipe; + usbd_pipe_handle bulkirq_pipe; + int maxlun; + int timeout; + struct ata_request *ata_request; + usb_device_request_t usb_request; + struct bbb_cbw cbw; + struct bbb_csw csw; + +#define ATAUSB_T_BBB_CBW 0 +#define ATAUSB_T_BBB_DATA 1 +#define ATAUSB_T_BBB_DCLEAR 2 +#define ATAUSB_T_BBB_CSW1 3 +#define ATAUSB_T_BBB_CSW2 4 +#define ATAUSB_T_BBB_SCLEAR 5 +#define ATAUSB_T_BBB_RESET1 6 +#define ATAUSB_T_BBB_RESET2 7 +#define ATAUSB_T_BBB_RESET3 8 +#define ATAUSB_T_MAX 9 + usbd_xfer_handle transfer[ATAUSB_T_MAX]; + + int state; +#define ATAUSB_S_ATTACH 0 +#define ATAUSB_S_IDLE 1 +#define ATAUSB_S_BBB_COMMAND 2 +#define ATAUSB_S_BBB_DATA 3 +#define ATAUSB_S_BBB_DCLEAR 4 +#define ATAUSB_S_BBB_STATUS1 5 +#define ATAUSB_S_BBB_SCLEAR 6 +#define ATAUSB_S_BBB_STATUS2 7 +#define ATAUSB_S_BBB_RESET1 8 +#define ATAUSB_S_BBB_RESET2 9 +#define ATAUSB_S_BBB_RESET3 10 +#define ATAUSB_S_DETACH 11 + + struct mtx locked_mtx; + struct ata_channel *locked_ch; + struct ata_channel *restart_ch; +}; + +static int atausbdebug = 0; + +/* prototypes*/ +static usbd_status atausb_start(struct atausb_softc *sc, usbd_pipe_handle pipe, void *buffer, int buflen, int flags, usbd_xfer_handle xfer); +static usbd_status atausb_ctl_start(struct atausb_softc *sc, usbd_device_handle udev, usb_device_request_t *req, void *buffer, int buflen, int flags, usbd_xfer_handle xfer); +static void atausb_clear_stall(struct atausb_softc *sc, u_int8_t endpt, usbd_pipe_handle pipe, int state, usbd_xfer_handle xfer); +static void atausb_bbb_reset(struct atausb_softc *sc); +static int atausb_bbb_start(struct ata_request *request); +static void atausb_bbb_finish(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status err); +int ata_usbchannel_begin_transaction(struct ata_request *request); +int ata_usbchannel_end_transaction(struct ata_request *request); + + +/* + * USB frontend part + */ +USB_DECLARE_DRIVER(atausb); +DRIVER_MODULE(atausb, uhub, atausb_driver, atausb_devclass, 0, 0); +MODULE_VERSION(atausb, 1); + +static int +atausb_match(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + usb_interface_descriptor_t *id; + + if (uaa->iface == NULL) + return UMATCH_NONE; + + id = usbd_get_interface_descriptor(uaa->iface); + if (!id || id->bInterfaceClass != UICLASS_MASS) + return UMATCH_NONE; + + switch (id->bInterfaceSubClass) { + case UISUBCLASS_QIC157: + case UISUBCLASS_RBC: + case UISUBCLASS_SCSI: + case UISUBCLASS_SFF8020I: + case UISUBCLASS_SFF8070I: + case UISUBCLASS_UFI: + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_CBI: + case UIPROTO_MASS_CBI_I: + case UIPROTO_MASS_BBB: + case UIPROTO_MASS_BBB_OLD: + return UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO; + default: + return UMATCH_IFACECLASS_IFACESUBCLASS; + } + break; + default: + return UMATCH_IFACECLASS; + } +} + +static int +atausb_attach(device_t dev) +{ + struct atausb_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + usbd_device_handle udev; + usb_device_request_t request; + char devinfo[1024], *proto, *subclass; + u_int8_t maxlun; + int err, i; + + sc->dev = dev; + usbd_devinfo(uaa->device, 0, devinfo); + device_set_desc_copy(dev, devinfo); + sc->bulkin = sc->bulkout = sc->bulkirq = -1; + sc->bulkin_pipe = sc->bulkout_pipe= sc->bulkirq_pipe = NULL; + sc->iface = uaa->iface; + sc->ifaceno = uaa->ifaceno; + sc->maxlun = 0; + sc->timeout = 5000; + sc->locked_ch = NULL; + sc->restart_ch = NULL; + mtx_init(&sc->locked_mtx, "ATAUSB lock", NULL, MTX_DEF); + + id = usbd_get_interface_descriptor(sc->iface); + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_BBB: + case UIPROTO_MASS_BBB_OLD: + proto = "Bulk-Only"; + break; + case UIPROTO_MASS_CBI: + proto = "CBI"; + break; + case UIPROTO_MASS_CBI_I: + proto = "CBI with CCI"; + break; + default: + proto = "Unknown"; + } + switch (id->bInterfaceSubClass) { + case UISUBCLASS_RBC: + subclass = "RBC"; + break; + case UISUBCLASS_QIC157: + case UISUBCLASS_SFF8020I: + case UISUBCLASS_SFF8070I: + subclass = "ATAPI"; + break; + case UISUBCLASS_SCSI: + subclass = "SCSI"; + break; + case UISUBCLASS_UFI: + subclass = "UFI"; + break; + default: + subclass = "Unknown"; + } + device_printf(dev, "using %s over %s\n", subclass, proto); + if (strcmp(proto, "Bulk-Only") || + (strcmp(subclass, "ATAPI") && strcmp(subclass, "SCSI"))) + return ENXIO; + + for (i = 0 ; i < id->bNumEndpoints ; i++) { + if (!(ed = usbd_interface2endpoint_descriptor(sc->iface, i))) { + device_printf(sc->dev, "could not read endpoint descriptor\n"); + return ENXIO; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + sc->bulkin = ed->bEndpointAddress; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + sc->bulkout = ed->bEndpointAddress; + } + if (id->bInterfaceProtocol == UIPROTO_MASS_CBI_I && + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + (ed->bmAttributes & UE_XFERTYPE) == UE_INTERRUPT) { + sc->bulkirq = ed->bEndpointAddress; + } + } + + /* check whether we found at least the endpoints we need */ + if (!sc->bulkin || !sc->bulkout) { + device_printf(sc->dev, "needed endpoints not found (%d,%d)\n", + sc->bulkin, sc->bulkout); + atausb_detach(dev); + return ENXIO; + } + + /* open the pipes */ + if (usbd_open_pipe(sc->iface, sc->bulkout, + USBD_EXCLUSIVE_USE, &sc->bulkout_pipe)) { + device_printf(sc->dev, "cannot open bulkout pipe (%d)\n", sc->bulkout); + atausb_detach(dev); + return ENXIO; + } + if (usbd_open_pipe(sc->iface, sc->bulkin, + USBD_EXCLUSIVE_USE, &sc->bulkin_pipe)) { + device_printf(sc->dev, "cannot open bulkin pipe (%d)\n", sc->bulkin); + atausb_detach(dev); + return ENXIO; + } + if (id->bInterfaceProtocol == UIPROTO_MASS_CBI_I) { + if (usbd_open_pipe(sc->iface, sc->bulkirq, + USBD_EXCLUSIVE_USE, &sc->bulkirq_pipe)) { + device_printf(sc->dev, "cannot open bulkirq pipe (%d)\n", + sc->bulkirq); + atausb_detach(dev); + return ENXIO; + } + } + sc->state = ATAUSB_S_ATTACH; + + /* alloc needed number of transfer handles */ + for (i = 0; i < ATAUSB_T_MAX; i++) { + sc->transfer[i] = usbd_alloc_xfer(uaa->device); + if (!sc->transfer[i]) { + device_printf(sc->dev, "out of memory\n"); + atausb_detach(dev); + return ENXIO; + } + } + + /* driver is ready to process requests here */ + sc->state = ATAUSB_S_IDLE; + + /* get number of devices so we can add matching channels */ + usbd_interface2device_handle(sc->iface, &udev); + request.bmRequestType = UT_READ_CLASS_INTERFACE; + request.bRequest = 0xfe; //GET_MAX_LUN; + USETW(request.wValue, 0); + USETW(request.wIndex, sc->ifaceno); + USETW(request.wLength, sizeof(maxlun)); + switch ((err = usbd_do_request(udev, &request, &maxlun))) { + case USBD_NORMAL_COMPLETION: + if (bootverbose) + device_printf(sc->dev, "maxlun=%d\n", maxlun); + sc->maxlun = maxlun; + break; + default: + if (bootverbose) + device_printf(sc->dev, "get maxlun not supported %s\n", + usbd_errstr(err)); + } + + /* ata channels are children to this USB control device */ + for (i = 0; i <= sc->maxlun; i++) { + if (!device_add_child(sc->dev, "ata", + devclass_find_free_unit(ata_devclass, 2))) { + device_printf(sc->dev, "failed to attach ata child device\n"); + atausb_detach(dev); + return ENXIO; + } + } + bus_generic_attach(sc->dev); + return 0; +} + +static int +atausb_detach(device_ptr_t dev) +{ + struct atausb_softc *sc = device_get_softc(dev); + usbd_device_handle udev; + device_t *children; + int nchildren, i; + + /* signal that device is going away */ + sc->state = ATAUSB_S_DETACH; + + /* abort all the pipes in case there are active transfers */ + usbd_interface2device_handle(sc->iface, &udev); + usbd_abort_default_pipe(udev); + if (sc->bulkout_pipe) + usbd_abort_pipe(sc->bulkout_pipe); + if (sc->bulkin_pipe) + usbd_abort_pipe(sc->bulkin_pipe); + if (sc->bulkirq_pipe) + usbd_abort_pipe(sc->bulkirq_pipe); + + /* detach & delete all children */ + if (!device_get_children(dev, &children, &nchildren)) { + for (i = 0; i < nchildren; i++) + device_delete_child(dev, children[i]); + free(children, M_TEMP); + } + + /* free the transfers */ + for (i = 0; i < ATAUSB_T_MAX; i++) + if (sc->transfer[i]) + usbd_free_xfer(sc->transfer[i]); + + /* remove all the pipes */ + if (sc->bulkout_pipe) + usbd_close_pipe(sc->bulkout_pipe); + if (sc->bulkin_pipe) + usbd_close_pipe(sc->bulkin_pipe); + if (sc->bulkirq_pipe) + usbd_close_pipe(sc->bulkirq_pipe); + + mtx_destroy(&sc->locked_mtx); + return 0; +} + + +/* + * Generic USB transfer routines + */ +static usbd_status +atausb_start(struct atausb_softc *sc, usbd_pipe_handle pipe, + void *buffer, int buflen, int flags, usbd_xfer_handle xfer) +{ + usbd_status err; + + if (sc->state == ATAUSB_S_DETACH) + return USBD_NOT_STARTED; + + usbd_setup_xfer(xfer, pipe, (void *)sc, buffer, buflen, flags, + sc->timeout, atausb_bbb_finish); + err = usbd_transfer(xfer); + if (err && (err != USBD_IN_PROGRESS)) { + if (atausbdebug) + device_printf(sc->dev, "failed to setup transfer, %s\n", + usbd_errstr(err)); + return err; + } + return USBD_NORMAL_COMPLETION; +} + +static usbd_status +atausb_ctl_start(struct atausb_softc *sc, usbd_device_handle udev, + usb_device_request_t *req, void *buffer, int buflen, int flags, + usbd_xfer_handle xfer) +{ + usbd_status err; + + if (sc->state == ATAUSB_S_DETACH) + return USBD_NOT_STARTED; + + usbd_setup_default_xfer(xfer, udev, (void *)sc, sc->timeout, req, + buffer, buflen, flags, atausb_bbb_finish); + err = usbd_transfer(xfer); + if (err && (err != USBD_IN_PROGRESS)) { + if (atausbdebug) + device_printf(sc->dev, "failed to setup ctl transfer, %s\n", + usbd_errstr(err)); + return err; + } + return USBD_NORMAL_COMPLETION; +} + +static void +atausb_clear_stall(struct atausb_softc *sc, u_int8_t endpt, + usbd_pipe_handle pipe, int state, usbd_xfer_handle xfer) +{ + usbd_device_handle udev; + + if (atausbdebug) + device_printf(sc->dev, "clear endpoint 0x%02x stall\n", endpt); + usbd_interface2device_handle(sc->iface, &udev); + sc->state = state; + usbd_clear_endpoint_toggle(pipe); + sc->usb_request.bmRequestType = UT_WRITE_ENDPOINT; + sc->usb_request.bRequest = UR_CLEAR_FEATURE; + USETW(sc->usb_request.wValue, UF_ENDPOINT_HALT); + USETW(sc->usb_request.wIndex, endpt); + USETW(sc->usb_request.wLength, 0); + atausb_ctl_start(sc, udev, &sc->usb_request, NULL, 0, 0, xfer); +} + + +/* + * Bulk-Only transport part + */ +static void +atausb_bbb_reset(struct atausb_softc *sc) +{ + usbd_device_handle udev; + + if (atausbdebug) + device_printf(sc->dev, "Bulk Reset\n"); + sc->timeout = 5000; + sc->state = ATAUSB_S_BBB_RESET1; + usbd_interface2device_handle(sc->iface, &udev); + sc->usb_request.bmRequestType = UT_WRITE_CLASS_INTERFACE; + sc->usb_request.bRequest = 0xff; /* bulk-only reset */ + USETW(sc->usb_request.wValue, 0); + USETW(sc->usb_request.wIndex, sc->ifaceno); + USETW(sc->usb_request.wLength, 0); + atausb_ctl_start(sc, udev, &sc->usb_request, NULL, + 0, 0, sc->transfer[ATAUSB_T_BBB_RESET1]); +} + +static int +atausb_bbb_start(struct ata_request *request) +{ + struct atausb_softc *sc = + device_get_softc(device_get_parent(request->parent)); + struct ata_channel *ch = device_get_softc(request->parent); + + sc->timeout = (request->timeout * 1000) + 5000; + USETDW(sc->cbw.signature, CBWSIGNATURE); + USETDW(sc->cbw.tag, UGETDW(sc->cbw.tag) + 1); + USETDW(sc->cbw.transfer_length, request->bytecount); + sc->cbw.flags = (request->flags & ATA_R_READ) ? CBWFLAGS_IN : CBWFLAGS_OUT; + sc->cbw.lun = ch->unit; + sc->cbw.length = 16; + bzero(sc->cbw.cdb, 16); + bcopy(request->u.atapi.ccb, sc->cbw.cdb, 12); /* XXX SOS */ + sc->state = ATAUSB_S_BBB_COMMAND; + if (atausb_start(sc, sc->bulkout_pipe, &sc->cbw, sizeof(struct bbb_cbw), + 0, sc->transfer[ATAUSB_T_BBB_CBW])) { + request->result = EIO; + if (atausbdebug) + device_printf(request->dev, "cannot setup USB transfer\n"); + atausb_bbb_reset(sc); + return ATA_OP_FINISHED; + } + return ATA_OP_CONTINUES; +} + +static void +atausb_bbb_finish(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status err) +{ + struct atausb_softc *sc = (struct atausb_softc *)priv; + struct ata_request *request = sc->ata_request; + usbd_xfer_handle next_xfer; + + //device_printf(sc->dev, "BBB state %d: %s\n", sc->state, usbd_errstr(err)); + + if (sc->state == ATAUSB_S_DETACH) { + device_printf(sc->dev, "WARNING - device has been removed\n"); + return; + } + + switch (sc->state) { + case ATAUSB_S_BBB_COMMAND: /* command transport phase */ + if (err) { + if (atausbdebug) + device_printf(sc->dev, "failed to send CBW\n"); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + + /* next is data transport phase, setup transfer */ + sc->state = ATAUSB_S_BBB_DATA; + if (request->flags & ATA_R_READ) { + if (atausb_start(sc, sc->bulkin_pipe, + request->data, request->bytecount, + USBD_SHORT_XFER_OK, + sc->transfer[ATAUSB_T_BBB_DATA])) { + request->result = EIO; + atausb_bbb_reset(sc); + } + return; + } + if (request->flags & ATA_R_WRITE) { + if (atausb_start(sc, sc->bulkout_pipe, + request->data, request->bytecount, + 0, sc->transfer[ATAUSB_T_BBB_DATA])) { + request->result = EIO; + atausb_bbb_reset(sc); + } + return; + } + /* FALLTHROUGH */ + + case ATAUSB_S_BBB_DATA: /* data transport phase */ + if (request->flags & (ATA_R_READ | ATA_R_WRITE)) { + usbd_get_xfer_status(xfer, NULL, NULL, &request->donecount, NULL); + if (err) { + if (atausbdebug) + device_printf(sc->dev, "data %s count %d failed: %s\n", + (request->flags & ATA_R_READ?"read":"write"), + request->bytecount, usbd_errstr(err)); + if (err == USBD_STALLED) { + atausb_clear_stall(sc, + (request->flags & ATA_R_READ ? + sc->bulkin : sc->bulkout), + (request->flags & ATA_R_READ ? + sc->bulkin_pipe : sc->bulkout_pipe), + ATAUSB_S_BBB_DCLEAR, + sc->transfer[ATAUSB_T_BBB_DCLEAR]); + } + else { + request->result = EIO; + atausb_bbb_reset(sc); + } + return; + } + } + /* FALLTHROUGH */ + + case ATAUSB_S_BBB_DCLEAR: /* stall clear after data phase */ + case ATAUSB_S_BBB_SCLEAR: /* stall clear after status phase */ + if (err) { + if (atausbdebug) + device_printf(sc->dev, "bulk%s stall clear failed %s\n", + (request->flags & ATA_R_READ ? "in" : "out"), + usbd_errstr(err)); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + + if (sc->state == ATAUSB_S_BBB_COMMAND || + sc->state == ATAUSB_S_BBB_DATA || + sc->state == ATAUSB_S_BBB_DCLEAR) { + /* first attempt on status transport phase setup transfer */ + sc->state = ATAUSB_S_BBB_STATUS1; + next_xfer = sc->transfer[ATAUSB_T_BBB_CSW1]; + } + else { + /* second attempt of fetching status */ + sc->state = ATAUSB_S_BBB_STATUS2; + next_xfer = sc->transfer[ATAUSB_T_BBB_CSW2]; + } + if (atausb_start(sc, sc->bulkin_pipe, &sc->csw, sizeof(struct bbb_csw), + USBD_SHORT_XFER_OK, next_xfer)) { + request->result = EIO; + atausb_bbb_reset(sc); + } + return; + + case ATAUSB_S_BBB_STATUS1: /* status transfer first attempt */ + case ATAUSB_S_BBB_STATUS2: /* status transfer second attempt */ + if (err) { + if (atausbdebug) + device_printf(sc->dev, "cannot get CSW, %s%s\n", + usbd_errstr(err), + sc->state == ATAUSB_S_BBB_STATUS1 ? ", retry":""); + if (sc->state == ATAUSB_S_BBB_STATUS1) { + atausb_clear_stall(sc, sc->bulkin, sc->bulkin_pipe, + ATAUSB_S_BBB_SCLEAR, + sc->transfer[ATAUSB_T_BBB_SCLEAR]); + } + else { + request->result = EIO; + atausb_bbb_reset(sc); + } + return; + } + + int residue = UGETDW(sc->csw.residue); + + if (!residue && + (request->bytecount - request->donecount)) + residue = request->bytecount - request->donecount; + + /* check CSW and handle eventual error */ + if (UGETDW(sc->csw.signature) != CSWSIGNATURE) { + if (atausbdebug) + device_printf(sc->dev, "bad CSW signature 0x%08x != 0x%08x\n", + UGETDW(sc->csw.signature), CSWSIGNATURE); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + else if (UGETDW(sc->csw.tag) != UGETDW(sc->cbw.tag)) { + if (atausbdebug) + device_printf(sc->dev, "bad CSW tag %d != %d\n", + UGETDW(sc->csw.tag), UGETDW(sc->cbw.tag)); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + else if (sc->csw.status > CSWSTATUS_PHASE) { + if (atausbdebug) + device_printf(sc->dev, "bad CSW status %d > %d\n", + sc->csw.status, CSWSTATUS_PHASE); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + else if (sc->csw.status == CSWSTATUS_PHASE) { + if (atausbdebug) + device_printf(sc->dev, "phase error residue = %d\n", residue); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + else if (request->donecount > request->bytecount) { + if (atausbdebug) + device_printf(sc->dev, "buffer overrun %d > %d", + request->donecount, request->bytecount); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + else if (sc->csw.status == CSWSTATUS_FAILED) { + if (atausbdebug) + device_printf(sc->dev, "CSWSTATUS_FAILED\n"); + //request->result = ENODEV; + request->error = ATA_SK_RESERVED; + sc->state = ATAUSB_S_IDLE; + ata_interrupt(device_get_softc(request->parent)); + return; + } + else { + sc->state = ATAUSB_S_IDLE; + ata_interrupt(device_get_softc(request->parent)); + return; + } + /* NOT REACHED */ + + case ATAUSB_S_BBB_RESET1: + if (err) + if (atausbdebug) + device_printf(sc->dev, + "BBB reset failure: %s\n", usbd_errstr(err)); + atausb_clear_stall(sc, sc->bulkin, sc->bulkin_pipe, + ATAUSB_S_BBB_RESET2, + sc->transfer[ATAUSB_T_BBB_RESET2]); + return; + + case ATAUSB_S_BBB_RESET2: + if (err) + if (atausbdebug) + device_printf(sc->dev, "BBB bulkin clear stall failure: %s\n", + usbd_errstr(err)); + atausb_clear_stall(sc, sc->bulkout, sc->bulkout_pipe, + ATAUSB_S_BBB_RESET3, + sc->transfer[ATAUSB_T_BBB_RESET3]); + return; + + case ATAUSB_S_BBB_RESET3: + if (err) + if (atausbdebug) + device_printf(sc->dev, "BBB bulk-out clear stall failure: %s\n", + usbd_errstr(err)); + sc->state = ATAUSB_S_IDLE; + if (request) { + if (err) + request->result = ENXIO; + else + request->result = EIO; + ata_interrupt(device_get_softc(request->parent)); + } + return; + + default: + if (atausbdebug) + device_printf(sc->dev, "unknown state %d", sc->state); + } +} + + +/* + * ATA backend part + */ +struct atapi_inquiry { + u_int8_t device_type; + u_int8_t device_modifier; + u_int8_t version; + u_int8_t response_format; + u_int8_t length; + u_int8_t reserved[2]; + u_int8_t flags; + u_int8_t vendor[8]; + u_int8_t product[16]; + u_int8_t revision[4]; + //u_int8_t crap[60]; +}; + +int +ata_usbchannel_begin_transaction(struct ata_request *request) +{ + struct atausb_softc *sc = + device_get_softc(device_get_parent(request->parent)); + + if (atausbdebug > 1) + device_printf(request->dev, "begin_transaction %s\n", + ata_cmd2str(request)); + + /* sanity just in case */ + if (sc->state != ATAUSB_S_IDLE) { + printf("begin is busy (%d)\n", sc->state); + request->result = EBUSY; + return ATA_OP_FINISHED; + } + + /* XXX SOS convert the request into the format used, only BBB for now*/ + sc->ata_request = request; + + /* ATA/ATAPI IDENTIFY needs special treatment */ + if (!(request->flags & ATA_R_ATAPI)) { + if (request->u.ata.command != ATA_ATAPI_IDENTIFY) { + device_printf(request->dev,"%s unsupported\n",ata_cmd2str(request)); + request->result = EIO; + return ATA_OP_FINISHED; + } + request->flags |= ATA_R_ATAPI; + bzero(request->u.atapi.ccb, 16); + request->u.atapi.ccb[0] = ATAPI_INQUIRY; + request->u.atapi.ccb[4] = 255; //sizeof(struct atapi_inquiry); + request->data += 256; /* arbitrary offset into ata_param */ + request->bytecount = 255; //sizeof(struct atapi_inquiry); + } + return atausb_bbb_start(request); +} + +int +ata_usbchannel_end_transaction(struct ata_request *request) +{ + if (atausbdebug > 1) + device_printf(request->dev, "end_transaction %s\n", + ata_cmd2str(request)); + + /* XXX SOS convert the request from the format used, only BBB for now*/ + + /* ATA/ATAPI IDENTIFY needs special treatment */ + if (request->u.atapi.ccb[0] == ATAPI_INQUIRY) { + struct ata_device *atadev = device_get_softc(request->dev); + struct atapi_inquiry *inquiry = (struct atapi_inquiry *)request->data; + u_int16_t *ptr; + + /* convert inquiry data into simple ata_param like format */ + atadev->param.config = ATA_PROTO_ATAPI | ATA_PROTO_ATAPI_12; + atadev->param.config |= (inquiry->device_type & 0x1f) << 8; + bzero(atadev->param.model, sizeof(atadev->param.model)); + strncpy(atadev->param.model, inquiry->vendor, 8); + strcpy(atadev->param.model, " "); + strncpy(atadev->param.model, inquiry->product, 16); + ptr = (u_int16_t*)(atadev->param.model + sizeof(atadev->param.model)); + while (--ptr >= (u_int16_t*)atadev->param.model) + *ptr = ntohs(*ptr); + strncpy(atadev->param.revision, inquiry->revision, 4); + ptr=(u_int16_t*)(atadev->param.revision+sizeof(atadev->param.revision)); + while (--ptr >= (u_int16_t*)atadev->param.revision) + *ptr = ntohs(*ptr); + request->result = 0; + } + if (request->u.atapi.ccb[0] == ATAPI_REQUEST_SENSE) { + request->u.atapi.sense_key = request->u.atapi.sense_data.sense_key; + if ((request->u.atapi.sense_key & ATA_E_MASK) == ATA_E_NM) + request->result = ENODEV; + } + return ATA_OP_FINISHED; +} + +static int +ata_usbchannel_probe(device_t dev) +{ + struct ata_channel *ch = device_get_softc(dev); + device_t *children; + int count, i; + char buffer[32]; + + /* take care of green memory */ + bzero(ch, sizeof(struct ata_channel)); + + /* find channel number on this controller */ + device_get_children(device_get_parent(dev), &children, &count); + for (i = 0; i < count; i++) { + if (children[i] == dev) + ch->unit = i; + } + free(children, M_TEMP); + + sprintf(buffer, "USB lun %d", ch->unit); + device_set_desc_copy(dev, buffer); + + return 0; +} + +static int +ata_usbchannel_attach(device_t dev) +{ + struct ata_channel *ch = device_get_softc(dev); + + /* initialize the softc basics */ + ch->dev = dev; + ch->state = ATA_IDLE; + ch->hw.begin_transaction = ata_usbchannel_begin_transaction; + ch->hw.end_transaction = ata_usbchannel_end_transaction; + ch->hw.status = NULL; + ch->hw.command = NULL; + bzero(&ch->state_mtx, sizeof(struct mtx)); + mtx_init(&ch->state_mtx, "ATA state lock", NULL, MTX_DEF); + bzero(&ch->queue_mtx, sizeof(struct mtx)); + mtx_init(&ch->queue_mtx, "ATA queue lock", NULL, MTX_DEF); + TAILQ_INIT(&ch->ata_queue); + + /* XXX SOS reset the controller HW, the channel and device(s) */ + //ATA_RESET(dev); + + /* probe and attach device on this channel */ + ch->devices = ATA_ATAPI_MASTER; + if (!ata_delayed_attach) + ata_identify(dev); + return 0; +} + +static int +ata_usbchannel_detach(device_t dev) +{ + struct ata_channel *ch = device_get_softc(dev); + device_t *children; + int nchildren, i; + + /* detach & delete all children */ + if (!device_get_children(dev, &children, &nchildren)) { + for (i = 0; i < nchildren; i++) + if (children[i]) + device_delete_child(dev, children[i]); + free(children, M_TEMP); + } + mtx_destroy(&ch->state_mtx); + mtx_destroy(&ch->queue_mtx); + return 0; +} + +static void +ata_usbchannel_setmode(device_t parent, device_t dev) +{ + struct atausb_softc *sc = device_get_softc(GRANDPARENT(dev)); + struct ata_device *atadev = device_get_softc(dev); + usbd_device_handle udev; + + usbd_interface2device_handle(sc->iface, &udev); + if (usbd_get_speed(udev) == USB_SPEED_HIGH) + atadev->mode = ATA_USB2; + else + atadev->mode = ATA_USB1; +} + +static int +ata_usbchannel_locking(device_t dev, int flags) +{ + struct atausb_softc *sc = device_get_softc(device_get_parent(dev)); + struct ata_channel *ch = device_get_softc(dev); + int res = -1; + + + mtx_lock(&sc->locked_mtx); + switch (flags) { + case ATA_LF_LOCK: + if (sc->locked_ch == NULL) + sc->locked_ch = ch; + if (sc->locked_ch != ch) + sc->restart_ch = ch; + break; + + case ATA_LF_UNLOCK: + if (sc->locked_ch == ch) { + sc->locked_ch = NULL; + if (sc->restart_ch) { + ch = sc->restart_ch; + sc->restart_ch = NULL; + mtx_unlock(&sc->locked_mtx); + ata_start(ch->dev); + return res; + } + } + break; + + case ATA_LF_WHICH: + break; + } + if (sc->locked_ch) + res = sc->locked_ch->unit; + mtx_unlock(&sc->locked_mtx); + return res; +} + +static device_method_t ata_usbchannel_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, ata_usbchannel_probe), + DEVMETHOD(device_attach, ata_usbchannel_attach), + DEVMETHOD(device_detach, ata_usbchannel_detach), + + /* ATA methods */ + DEVMETHOD(ata_setmode, ata_usbchannel_setmode), + DEVMETHOD(ata_locking, ata_usbchannel_locking), + //DEVMETHOD(ata_reset, ata_usbchannel_reset), + + { 0, 0 } +}; + +static driver_t ata_usbchannel_driver = { + "ata", + ata_usbchannel_methods, + sizeof(struct ata_channel), +}; + +DRIVER_MODULE(ata, atausb, ata_usbchannel_driver, ata_devclass, 0, 0); +MODULE_DEPEND(atausb, ata, 1, 1, 1); |