NetBSD-5.0.2/sys/dev/usb/umass_isdata.c

Compare this file to the similar file:
Show the results in this format:

/*	$NetBSD: umass_isdata.c,v 1.18 2008/05/24 16:40:58 cube 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: umass_isdata.c,v 1.18 2008/05/24 16:40:58 cube Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/buf.h>
#include <sys/device.h>
#include <sys/proc.h>
#include <sys/disklabel.h>
#include <sys/malloc.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>

#include <dev/usb/umassvar.h>
#include <dev/usb/umass_isdata.h>

int umass_wd_attach(struct umass_softc *);

#include <dev/ata/atareg.h>
#include <dev/ata/atavar.h>

/* 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 isd200_config	sc_isd_config;
	void			*sc_ata_bio;
	u_long			sc_skip;
};

#undef DPRINTF
#undef DPRINTFN
#ifdef UISDATA_DEBUG
#define DPRINTF(x)	if (uisdatadebug) logprintf x
#define DPRINTFN(n,x)	if (uisdatadebug>(n)) logprintf x
int	uisdatadebug = 0;
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif

int  uisdata_bio(struct ata_drive_datas *, struct ata_bio *);
int  uisdata_bio1(struct ata_drive_datas *, struct ata_bio *);
void uisdata_reset_drive(struct ata_drive_datas *, int);
void uisdata_reset_channel(struct ata_channel *, int);
int  uisdata_exec_command(struct ata_drive_datas *, struct ata_command *);
int  uisdata_get_params(struct ata_drive_datas *, u_int8_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,
};

struct ata_cmd {
	u_int8_t ac_signature0;
	u_int8_t ac_signature1;

	u_int8_t ac_action_select;
#define AC_ReadRegisterAccess		0x01
#define AC_NoDeviceSelectionBit		0x02
#define AC_NoBSYPollBit			0x04
#define AC_IgnorePhaseErrorBit		0x08
#define AC_IgnoreDeviceErrorBit		0x10

	u_int8_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 */

	u_int8_t ac_transfer_blocksize;

	u_int8_t ac_alternate_status;
#define ac_device_control ac_alternate_status
	u_int8_t ac_error;
#define ac_features ac_error

	u_int8_t ac_sector_count;
	u_int8_t ac_sector_number;
	u_int8_t ac_cylinder_low;
	u_int8_t ac_cylinder_high;
	u_int8_t ac_device_head;

	u_int8_t ac_status;
#define ac_command ac_status

	u_int8_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 = malloc(sizeof *scbus, M_DEVBUF, M_WAITOK | M_ZERO);
	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)
		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_openings = 1;
	adev.adev_drv_data = &scbus->sc_drv_data;
	scbus->sc_drv_data.drive_flags = DRIVE_ATA;
	scbus->sc_drv_data.chnl_softc = sc;
	scbus->base.sc_child = config_found(sc->sc_dev, &adev, uwdprint);

	return (0);
}


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_bio *ata_bio = priv;
	int s;

	DPRINTF(("%s: residue=%d status=%d\n", __func__, residue, status));

	s = splbio();
	scbus->sc_ata_bio = NULL;
	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__));
		(void)uisdata_bio1(&scbus->sc_drv_data, ata_bio); /*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);
	}
	splx(s);
}

int
uisdata_bio(struct ata_drive_datas *drv, struct ata_bio *ata_bio)
{
	struct umass_softc *sc = drv->chnl_softc;
	struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus;

	scbus->sc_skip = 0;
	return (uisdata_bio1(drv, ata_bio));
}

int
uisdata_bio1(struct ata_drive_datas *drv, struct ata_bio *ata_bio)
{
	struct umass_softc *sc = drv->chnl_softc;
	struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus;
	struct isd200_config *cf = &scbus->sc_isd_config;
	struct ata_cmd ata;
	u_int16_t cyl;
	u_int8_t head, sect;
	int dir;
	long nbytes;
	u_int nblks;

	DPRINTF(("%s\n", __func__));
	/* XXX */

	if (ata_bio->flags & ATA_NOSLEEP) {
		printf("%s: ATA_NOSLEEP not supported\n", __func__);
		ata_bio->error = TIMEOUT;
		ata_bio->flags |= ATA_ITSDONE;
		return (ATACMD_COMPLETE);
	}

	if (scbus->sc_ata_bio != NULL) {
		printf("%s: multiple uisdata_bio\n", __func__);
		return (ATACMD_TRY_AGAIN);
	} else
		scbus->sc_ata_bio = ata_bio;

	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 % ata_bio->lp->d_nsectors;
		sect++;    /* Sectors begin with 1, not 0. */
		blkno /= ata_bio->lp->d_nsectors;
		head = blkno % ata_bio->lp->d_ntracks;
		blkno /= ata_bio->lp->d_ntracks;
		cyl = blkno;
		head |= WDSD_CHS;
	}

	nbytes = ata_bio->bcount;
	if (ata_bio->flags & ATA_SINGLE)
		nblks = 1;
	else
		nblks = min(ata_bio->multi, nbytes / ata_bio->lp->d_secsize);
	nbytes = nblks * ata_bio->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, ata_bio->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, uisdata_bio_cb, ata_bio);

	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;
			return (ATACMD_COMPLETE);
		}
	}

	return (ata_bio->flags & ATA_ITSDONE) ? ATACMD_COMPLETE : ATACMD_QUEUED;
}

void
uisdata_reset_drive(struct ata_drive_datas *drv, int flags)
{
	DPRINTFN(-1,("%s\n", __func__));
	/* 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);
	}
}

int
uisdata_exec_command(struct ata_drive_datas *drv, struct ata_command *cmd)
{
	struct umass_softc *sc = drv->chnl_softc;
	struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus;
	struct isd200_config *cf = &scbus->sc_isd_config;
	int dir;
	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;
		goto done;
	}

	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;
		goto done;
	}

	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, 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;
			goto done;
		}
	}

done:
	return (ATACMD_COMPLETE);
}

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 umass_softc *sc = drv->chnl_softc;
	struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus;
	struct ata_bio *ata_bio = scbus->sc_ata_bio;

	DPRINTFN(-1,("%s\n", __func__));

	if (ata_bio == NULL)
		return;
	scbus->sc_ata_bio = NULL;
	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);
}

int
uisdata_get_params(struct ata_drive_datas *drvp, u_int8_t flags,
		struct ataparams *prms)
{
	char tb[DEV_BSIZE];
	struct ata_command ata_c;

#if BYTE_ORDER == LITTLE_ENDIAN
	int i;
	u_int16_t *p;
#endif

	DPRINTF(("%s\n", __func__));

	memset(tb, 0, DEV_BSIZE);
	memset(prms, 0, sizeof(struct ataparams));
	memset(&ata_c, 0, sizeof(struct ata_command));

	ata_c.r_command = WDCC_IDENTIFY;
	ata_c.timeout = 1000; /* 1s */
	ata_c.flags = AT_READ | flags;
	ata_c.data = tb;
	ata_c.bcount = DEV_BSIZE;
	if (uisdata_exec_command(drvp, &ata_c) != ATACMD_COMPLETE) {
		DPRINTF(("uisdata_get_parms: wdc_exec_command failed\n"));
		return (CMD_AGAIN);
	}
	if (ata_c.flags & (AT_ERROR | AT_TIMEOU | AT_DF)) {
		DPRINTF(("uisdata_get_parms: ata_c.flags=0x%x\n",
			 ata_c.flags));
		return (CMD_ERR);
	} else {
		/* 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_ATAPI_MASK) ==
		    WDC_CFG_ATAPI &&
		    ((prms->atap_model[0] == 'N' &&
			prms->atap_model[1] == 'E') ||
		     (prms->atap_model[0] == 'F' &&
			 prms->atap_model[1] == 'X')))
			return 0;
		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
		return CMD_OK;
	}
}


/* 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