code:
/*      $OpenBSD: ata_wdc.c,v 1.49 2015/04/04 13:03:44 mpi Exp $	*/
/*	$NetBSD: ata_wdc.c,v 1.21 1999/08/09 09:43:11 bouyer Exp $	*/

/*
 * Copyright (c) 1998, 2001 Manuel Bouyer.
 *
 * 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.
 *
 */

/*-
 * Copyright (c) 1998 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Charles M. Hannum, by Onno van der Linden and by Manuel Bouyer.
 *
 * 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/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/buf.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/disklabel.h>
#include <sys/disk.h>
#include <sys/syslog.h>

#include <machine/intr.h>
#include <machine/bus.h>

#include <dev/ata/atavar.h>
#include <dev/ic/wdcreg.h>
#include <dev/ic/wdcvar.h>
#include <dev/ata/wdvar.h>

#ifdef HIBERNATE
#include <sys/hibernate.h>
#endif

#define DEBUG_INTR   0x01
#define DEBUG_XFERS  0x02
#define DEBUG_STATUS 0x04
#define DEBUG_FUNCS  0x08
#define DEBUG_PROBE  0x10

#ifdef WDCDEBUG
#ifndef WDCDEBUG_WD_MASK
#define WDCDEBUG_WD_MASK 0x00
#endif
int wdcdebug_wd_mask = WDCDEBUG_WD_MASK;
#define WDCDEBUG_PRINT(args, level) do {	\
	if ((wdcdebug_wd_mask & (level)) != 0)	\
		printf args;			\
} while (0)
#else
#define WDCDEBUG_PRINT(args, level)
#endif

#define ATA_DELAY 45000 /* 45s for a drive I/O */

void  wdc_ata_bio_start(struct channel_softc *, struct wdc_xfer *);
void  _wdc_ata_bio_start(struct channel_softc *, struct wdc_xfer *);
int   wdc_ata_bio_intr(struct channel_softc *, struct wdc_xfer *, int);
void  wdc_ata_bio_kill_xfer(struct channel_softc *, struct wdc_xfer *);
void  wdc_ata_bio_done(struct channel_softc *, struct wdc_xfer *);
int   wdc_ata_ctrl_intr(struct channel_softc *, struct wdc_xfer *, int);
int   wdc_ata_err(struct ata_drive_datas *, struct ata_bio *);
#define WDC_ATA_NOERR 0x00 /* Drive doesn't report an error */
#define WDC_ATA_RECOV 0x01 /* There was a recovered error */
#define WDC_ATA_ERR   0x02 /* Drive reports an error */

#ifdef HIBERNATE
int
wd_hibernate_io(dev_t dev, daddr_t blkno, vaddr_t addr, size_t size, int op, void *page)
{
	struct {
		struct wd_softc wd;
		struct wdc_xfer xfer;
		struct channel_softc chp;
		daddr_t poffset;
		size_t psize;
	} *my = page;
	struct wd_softc *real_wd, *wd = &my->wd;
	struct wdc_xfer *xfer = &my->xfer;
	struct channel_softc *chp = &my->chp;
	struct ata_bio *ata_bio;
	extern struct cfdriver wd_cd;

	/* early call for initialization */
	if (op == HIB_INIT) {
		my->poffset = blkno;
		my->psize = size;
		return(0);
	}

	real_wd = (struct wd_softc *)disk_lookup(&wd_cd, DISKUNIT(dev));
	if (real_wd == NULL)
		return (ENODEV);

	if (op == HIB_DONE) {
		struct wdc_softc *wdc = chp->wdc;
		config_suspend(&wdc->sc_dev, DVACT_RESUME);
		return (0);
	}

	if (blkno > my->psize)
		return (E2BIG);
	blkno += my->poffset;

	/*
	 * Craft a fake set of softc and related structures
	 * which we think the driver modifies.  Some of these will
	 * have pointers which reach to unsafe places, but..
	 */
	bcopy(real_wd->drvp->chnl_softc, &my->chp, sizeof my->chp);
	chp->ch_drive[0].chnl_softc = chp;
	chp->ch_drive[1].chnl_softc = chp;

	bcopy(real_wd, &my->wd, sizeof my->wd);
	ata_bio = &wd->sc_wdc_bio;
	ata_bio->wd = wd;		/* fixup ata_bio->wd */
	wd->drvp = &chp->ch_drive[real_wd->drvp->drive];

	/* Fill the request and submit it */
	wd->sc_wdc_bio.blkno = blkno;
	wd->sc_wdc_bio.flags = ATA_POLL | ATA_LBA48;
	if (op == HIB_R)
		wd->sc_wdc_bio.flags |= ATA_READ;
	wd->sc_wdc_bio.bcount = size;
	wd->sc_wdc_bio.databuf = (caddr_t)addr;
	wd->sc_wdc_bio.wd = wd;

	bzero(&my->xfer, sizeof my->xfer);
	xfer->c_flags |= C_PRIVATEXFER;	/* Our xfer is totally private */
	xfer->c_flags |= C_POLL;
	xfer->drive = wd->drvp->drive;
	xfer->cmd = ata_bio;
	xfer->databuf = ata_bio->databuf;
	xfer->c_bcount = ata_bio->bcount;
	xfer->c_start = wdc_ata_bio_start;
	xfer->c_intr = wdc_ata_bio_intr;
	xfer->c_kill_xfer = wdc_ata_bio_kill_xfer;
	wdc_exec_xfer(chp, xfer);
	return (ata_bio->flags & ATA_ITSDONE) ? 0 : EIO;
}
#endif /* HIBERNATE */

/*
 * Handle block I/O operation. Return WDC_COMPLETE, WDC_QUEUED, or
 * WDC_TRY_AGAIN. Must be called at splbio().
 */
int
wdc_ata_bio(struct ata_drive_datas *drvp, struct ata_bio *ata_bio)
{
	struct wdc_xfer *xfer;
	struct channel_softc *chp = drvp->chnl_softc;

	xfer = wdc_get_xfer(WDC_NOSLEEP);
	if (xfer == NULL)
		return WDC_TRY_AGAIN;
	if (ata_bio->flags & ATA_POLL)
		xfer->c_flags |= C_POLL;
	if (!(ata_bio->flags & ATA_POLL) &&
	    (drvp->drive_flags & (DRIVE_DMA | DRIVE_UDMA)) &&
	    (ata_bio->flags & ATA_SINGLE) == 0 &&
	    (ata_bio->bcount > 512 ||
	    (chp->wdc->quirks & WDC_QUIRK_NOSHORTDMA) == 0))
		xfer->c_flags |= C_DMA;
	xfer->drive = drvp->drive;
	xfer->cmd = ata_bio;
	xfer->databuf = ata_bio->databuf;
	xfer->c_bcount = ata_bio->bcount;
	xfer->c_start = wdc_ata_bio_start;
	xfer->c_intr = wdc_ata_bio_intr;
	xfer->c_kill_xfer = wdc_ata_bio_kill_xfer;
	wdc_exec_xfer(chp, xfer);
	return (ata_bio->flags & ATA_ITSDONE) ? WDC_COMPLETE : WDC_QUEUED;
}

void
wdc_ata_bio_start(struct channel_softc *chp, struct wdc_xfer *xfer)
{
	struct ata_bio *ata_bio = xfer->cmd;
	WDCDEBUG_PRINT(("wdc_ata_bio_start %s:%d:%d\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive),
	    DEBUG_XFERS);

	/* start timeout machinery */
	if ((ata_bio->flags & ATA_POLL) == 0)
		timeout_add_msec(&chp->ch_timo, ATA_DELAY);
	_wdc_ata_bio_start(chp, xfer);
}

void
_wdc_ata_bio_start(struct channel_softc *chp, struct wdc_xfer *xfer)
{
	struct ata_bio *ata_bio = xfer->cmd;
	struct ata_drive_datas *drvp = &chp->ch_drive[xfer->drive];
	u_int16_t cyl;
	u_int8_t head, sect, cmd = 0;
	int nblks;
	int ata_delay;
	int error, dma_flags = 0;

	WDCDEBUG_PRINT(("_wdc_ata_bio_start %s:%d:%d\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive),
	    DEBUG_INTR | DEBUG_XFERS);
	/* Do control operations specially. */
	if (drvp->state < READY) {
		/*
		 * Actually, we want to be careful not to mess with the control
		 * state if the device is currently busy, but we can assume
		 * that we never get to this point if that's the case.
		 */
		/* at this point, we should only be in RECAL state */
		if (drvp->state != RECAL) {
			printf("%s:%d:%d: bad state %d in _wdc_ata_bio_start\n",
			    chp->wdc->sc_dev.dv_xname, chp->channel,
			    xfer->drive, drvp->state);
			panic("_wdc_ata_bio_start: bad state");
		}
		xfer->c_intr = wdc_ata_ctrl_intr;
		wdc_set_drive(chp, xfer->drive);
		if (wdcwait(chp, WDCS_DRDY, WDCS_DRDY, ATA_DELAY) != 0)
			goto timeout;
		wdccommandshort(chp, xfer->drive, WDCC_RECAL);
		drvp->state = RECAL_WAIT;
		if ((ata_bio->flags & ATA_POLL) == 0) {
			chp->ch_flags |= WDCF_IRQ_WAIT;
		} else {
			/* Wait for at last 400ns for status bit to be valid */
			DELAY(1);
			wdc_ata_ctrl_intr(chp, xfer, 0);
		}
		return;
	}

	if (xfer->c_flags & C_DMA) {
		if (drvp->n_xfers <= NXFER)
			drvp->n_xfers++;
		dma_flags = (ata_bio->flags & ATA_READ) ?  WDC_DMA_READ : 0;
		if (ata_bio->flags & ATA_LBA48)
			dma_flags |= WDC_DMA_LBA48;
	}
	if (ata_bio->flags & ATA_SINGLE)
		ata_delay = ATA_DELAY;
	else
		ata_delay = ATA_DELAY;
again:
	/*
	 *
	 * When starting a multi-sector transfer, or doing single-sector
	 * transfers...
	 */
	if (xfer->c_skip == 0 || (ata_bio->flags & ATA_SINGLE) != 0) {
		if (ata_bio->flags & ATA_SINGLE)
			nblks = 1;
		else
			nblks = xfer->c_bcount / ata_bio->lp->d_secsize;
		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;
		}
		if (xfer->c_flags & C_DMA) {
			ata_bio->nblks = nblks;
			ata_bio->nbytes = xfer->c_bcount;
			if (ata_bio->flags & ATA_LBA48)
				cmd = (ata_bio->flags & ATA_READ) ?
				    WDCC_READDMA_EXT : WDCC_WRITEDMA_EXT;
			else
				cmd = (ata_bio->flags & ATA_READ) ?
				    WDCC_READDMA : WDCC_WRITEDMA;
	    		/* Init the DMA channel. */
			error = (*chp->wdc->dma_init)(chp->wdc->dma_arg,
			    chp->channel, xfer->drive,
			    (char *)xfer->databuf + xfer->c_skip,
			    ata_bio->nbytes, dma_flags);
			if (error) {
				if (error == EINVAL) {
					/*
					 * We can't do DMA on this transfer
					 * for some reason.  Fall back to
					 * PIO.
					 */
					xfer->c_flags &= ~C_DMA;
					error = 0;
					goto do_pio;
				}
				ata_bio->error = ERR_DMA;
				ata_bio->r_error = 0;
				wdc_ata_bio_done(chp, xfer);
				return;
			}
			/* Initiate command */
			wdc_set_drive(chp, xfer->drive);
			if (wait_for_ready(chp, ata_delay) < 0)
				goto timeout;

			/* start the DMA channel (before) */
			if (chp->ch_flags & WDCF_DMA_BEFORE_CMD)
				(*chp->wdc->dma_start)(chp->wdc->dma_arg,
				    chp->channel, xfer->drive);

			if (ata_bio->flags & ATA_LBA48) {
				wdccommandext(chp, xfer->drive, cmd,
				    (u_int64_t)ata_bio->blkno, nblks);
			} else {
				wdccommand(chp, xfer->drive, cmd, cyl,
				    head, sect, nblks, 0);
			}

			/* start the DMA channel (after) */
			if ((chp->ch_flags & WDCF_DMA_BEFORE_CMD) == 0)
				(*chp->wdc->dma_start)(chp->wdc->dma_arg,
				    chp->channel, xfer->drive);

			chp->ch_flags |= WDCF_DMA_WAIT;
			/* wait for irq */
			goto intr;
		} /* else not DMA */
 do_pio:
		ata_bio->nblks = min(nblks, ata_bio->multi);
		ata_bio->nbytes = ata_bio->nblks * ata_bio->lp->d_secsize;
		KASSERT(nblks == 1 || (ata_bio->flags & ATA_SINGLE) == 0);
		if (ata_bio->nblks > 1) {
			if (ata_bio->flags & ATA_LBA48)
				cmd = (ata_bio->flags & ATA_READ) ?
				    WDCC_READMULTI_EXT : WDCC_WRITEMULTI_EXT;
			else
				cmd = (ata_bio->flags & ATA_READ) ?
				    WDCC_READMULTI : WDCC_WRITEMULTI;
		} else {
			if (ata_bio->flags & ATA_LBA48)
				cmd = (ata_bio->flags & ATA_READ) ?
				    WDCC_READ_EXT : WDCC_WRITE_EXT;
			else
				cmd = (ata_bio->flags & ATA_READ) ?
				    WDCC_READ : WDCC_WRITE;
		}
		/* Initiate command! */
		wdc_set_drive(chp, xfer->drive);
		if (wait_for_ready(chp, ata_delay) < 0)
			goto timeout;
		if (ata_bio->flags & ATA_LBA48) {
			wdccommandext(chp, xfer->drive, cmd,
			    (u_int64_t)ata_bio->blkno, nblks);
		} else {
			wdccommand(chp, xfer->drive, cmd, cyl,
			    head, sect, nblks,
			    (ata_bio->lp->d_type == DTYPE_ST506) ?
			    ata_bio->lp->d_precompcyl / 4 : 0);
		}
	} else if (ata_bio->nblks > 1) {
		/* The number of blocks in the last stretch may be smaller. */
		nblks = xfer->c_bcount / ata_bio->lp->d_secsize;
		if (ata_bio->nblks > nblks) {
			ata_bio->nblks = nblks;
			ata_bio->nbytes = xfer->c_bcount;
		}
	}
	/* If this was a write and not using DMA, push the data. */
	if ((ata_bio->flags & ATA_READ) == 0) {
		if (wait_for_drq(chp, ata_delay) != 0) {
			printf("%s:%d:%d: timeout waiting for DRQ, "
			    "st=0x%b, err=0x%02x\n",
			    chp->wdc->sc_dev.dv_xname, chp->channel,
			    xfer->drive, chp->ch_status, WDCS_BITS,
			    chp->ch_error);
			if (wdc_ata_err(drvp, ata_bio) != WDC_ATA_ERR)
				ata_bio->error = TIMEOUT;
			wdc_ata_bio_done(chp, xfer);
			return;
		}
		if (wdc_ata_err(drvp, ata_bio) == WDC_ATA_ERR) {
			wdc_ata_bio_done(chp, xfer);
			return;
		}
		wdc_output_bytes(drvp, (char *)xfer->databuf + xfer->c_skip,
		    ata_bio->nbytes);
	}

intr:	/* Wait for IRQ (either real or polled) */
	if ((ata_bio->flags & ATA_POLL) == 0) {
		chp->ch_flags |= WDCF_IRQ_WAIT;
	} else {
		/* Wait for at last 400ns for status bit to be valid */
		delay(1);
		if (chp->ch_flags & WDCF_DMA_WAIT) {
			wdc_dmawait(chp, xfer, ATA_DELAY);
			chp->ch_flags &= ~WDCF_DMA_WAIT;
		}
		wdc_ata_bio_intr(chp, xfer, 0);
		if ((ata_bio->flags & ATA_ITSDONE) == 0)
			goto again;
	}
	return;
timeout:
	if (chp->ch_status == 0xff)
		return;
	printf("%s:%d:%d: not ready, st=0x%b, err=0x%02x\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive,
	    chp->ch_status, WDCS_BITS, chp->ch_error);
	if (wdc_ata_err(drvp, ata_bio) != WDC_ATA_ERR)
		ata_bio->error = TIMEOUT;
	wdc_ata_bio_done(chp, xfer);
	return;
}

int
wdc_ata_bio_intr(struct channel_softc *chp, struct wdc_xfer *xfer, int irq)
{
	struct ata_bio *ata_bio = xfer->cmd;
	struct ata_drive_datas *drvp = &chp->ch_drive[xfer->drive];
	int drv_err;

	WDCDEBUG_PRINT(("wdc_ata_bio_intr %s:%d:%d\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive),
	    DEBUG_INTR | DEBUG_XFERS);

	/* Is it not a transfer, but a control operation? */
	if (drvp->state < READY) {
		printf("%s:%d:%d: bad state %d in wdc_ata_bio_intr\n",
		    chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive,
		    drvp->state);
		panic("wdc_ata_bio_intr: bad state");
	}

	/*
	 * reset on timeout. This will cause extra resets in the case
	 * of occasional lost interrupts
	 */
	if (xfer->c_flags & C_TIMEOU)
		goto timeout;

	/* Ack interrupt done by wait_for_unbusy */
	if (wait_for_unbusy(chp,
	    (irq == 0) ? ATA_DELAY : 0) < 0) {
		if (irq)
			return 0; /* IRQ was not for us */
		printf("%s:%d:%d: device timeout, c_bcount=%d, c_skip%d\n",
		    chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive,
		    xfer->c_bcount, xfer->c_skip);

		goto timeout;
	}
	if (chp->wdc->cap & WDC_CAPABILITY_IRQACK)
		chp->wdc->irqack(chp);

	drv_err = wdc_ata_err(drvp, ata_bio);

	if (xfer->c_flags & C_DMA) {
		if (chp->wdc->dma_status != 0) {
			if (drv_err != WDC_ATA_ERR) {
				ata_bio->error = ERR_DMA;
				drv_err = WDC_ATA_ERR;
			}
		}
		if (chp->ch_status & WDCS_DRQ) {
			if (drv_err != WDC_ATA_ERR) {
				printf("%s:%d:%d: intr with DRQ (st=0x%b)\n",
				    chp->wdc->sc_dev.dv_xname, chp->channel,
				    xfer->drive, chp->ch_status, WDCS_BITS);
				ata_bio->error = TIMEOUT;
				drv_err = WDC_ATA_ERR;
			}
		}
		if (drv_err != WDC_ATA_ERR)
			goto end;
		ata_dmaerr(drvp);
	}

	/* if we had an error, end */
	if (drv_err == WDC_ATA_ERR) {
		wdc_ata_bio_done(chp, xfer);
		return 1;
	}

	/* If this was a read and not using DMA, fetch the data. */
	if ((ata_bio->flags & ATA_READ) != 0) {
		if ((chp->ch_status & WDCS_DRQ) != WDCS_DRQ) {
			printf("%s:%d:%d: read intr before drq\n",
			    chp->wdc->sc_dev.dv_xname, chp->channel,
			    xfer->drive);
			ata_bio->error = TIMEOUT;
			wdc_ata_bio_done(chp, xfer);
			return 1;
		}
		wdc_input_bytes(drvp, (char *)xfer->databuf + xfer->c_skip,
		    ata_bio->nbytes);
	}
end:
	ata_bio->blkno += ata_bio->nblks;
	ata_bio->blkdone += ata_bio->nblks;
	xfer->c_skip += ata_bio->nbytes;
	xfer->c_bcount -= ata_bio->nbytes;
	/* See if this transfer is complete. */
	if (xfer->c_bcount > 0) {
		if ((ata_bio->flags & ATA_POLL) == 0) {
			/* Start the next operation */
			_wdc_ata_bio_start(chp, xfer);
		} else {
			/* Let _wdc_ata_bio_start do the loop */
			return 1;
		}
	} else { /* Done with this transfer */
		ata_bio->error = NOERROR;
		wdc_ata_bio_done(chp, xfer);
	}
	return 1;

timeout:
	if (xfer->c_flags & C_DMA)
		ata_dmaerr(drvp);

	ata_bio->error = TIMEOUT;
	wdc_ata_bio_done(chp, xfer);
	return 1;
}

void
wdc_ata_bio_kill_xfer(struct channel_softc *chp, struct wdc_xfer *xfer)
{
	struct ata_bio *ata_bio = xfer->cmd;

	timeout_del(&chp->ch_timo);
	/* remove this command from xfer queue */
	wdc_free_xfer(chp, xfer);

	ata_bio->flags |= ATA_ITSDONE;
	ata_bio->error = ERR_NODEV;
	ata_bio->r_error = WDCE_ABRT;
	if ((ata_bio->flags & ATA_POLL) == 0) {
		WDCDEBUG_PRINT(("wdc_ata_done: wddone\n"), DEBUG_XFERS);
		wddone(ata_bio->wd);
	}
}

void
wdc_ata_bio_done(struct channel_softc *chp, struct wdc_xfer *xfer)
{
	struct ata_bio *ata_bio = xfer->cmd;

	WDCDEBUG_PRINT(("wdc_ata_bio_done %s:%d:%d: flags 0x%x\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive,
	    (u_int)xfer->c_flags),
	    DEBUG_XFERS);

	if ((xfer->c_flags & C_PRIVATEXFER) == 0)
		timeout_del(&chp->ch_timo);

	/* feed back residual bcount to our caller */
	ata_bio->bcount = xfer->c_bcount;

	/* remove this command from xfer queue */
	wdc_free_xfer(chp, xfer);

	ata_bio->flags |= ATA_ITSDONE;
	if ((ata_bio->flags & ATA_POLL) == 0) {
		WDCDEBUG_PRINT(("wdc_ata_done: wddone\n"), DEBUG_XFERS);
		wddone(ata_bio->wd);
	}
	WDCDEBUG_PRINT(("wdcstart from wdc_ata_done, flags 0x%x\n",
	    chp->ch_flags), DEBUG_XFERS);
	wdcstart(chp);
}

/*
 * Implement operations needed before read/write.
 */
int
wdc_ata_ctrl_intr(struct channel_softc *chp, struct wdc_xfer *xfer, int irq)
{
	struct ata_bio *ata_bio = xfer->cmd;
	struct ata_drive_datas *drvp = &chp->ch_drive[xfer->drive];
	char *errstring = NULL;
	int delay = (irq == 0) ? ATA_DELAY : 0;

	WDCDEBUG_PRINT(("wdc_ata_ctrl_intr: state %d\n", drvp->state),
	    DEBUG_FUNCS);

again:
	switch (drvp->state) {
	case RECAL:    /* Should not be in this state here */
		panic("wdc_ata_ctrl_intr: state==RECAL");
		break;

	case RECAL_WAIT:
		errstring = "recal";
		if (wdcwait(chp, WDCS_DRDY, WDCS_DRDY, delay))
			goto timeout;
		if (chp->wdc->cap & WDC_CAPABILITY_IRQACK)
			chp->wdc->irqack(chp);
		if (chp->ch_status & (WDCS_ERR | WDCS_DWF))
			goto error;
	/* FALLTHROUGH */

	case PIOMODE:
		/* Don't try to set modes if controller can't be adjusted */
		if ((chp->wdc->cap & WDC_CAPABILITY_MODE) == 0)
			goto geometry;
		/* Also don't try if the drive didn't report its mode */
		if ((drvp->drive_flags & DRIVE_MODE) == 0)
			goto geometry;
		/* SET FEATURES 0x08 is only for PIO mode > 2 */
		if (drvp->PIO_mode <= 2)
			goto geometry;
		wdccommand(chp, drvp->drive, SET_FEATURES, 0, 0, 0,
		    0x08 | drvp->PIO_mode, WDSF_SET_MODE);
		drvp->state = PIOMODE_WAIT;
		break;

	case PIOMODE_WAIT:
		errstring = "piomode";
		if (wdcwait(chp, WDCS_DRDY, WDCS_DRDY, delay))
			goto timeout;
		if (chp->wdc->cap & WDC_CAPABILITY_IRQACK)
			chp->wdc->irqack(chp);
		if (chp->ch_status & (WDCS_ERR | WDCS_DWF))
			goto error;
	/* FALLTHROUGH */

	case DMAMODE:
		if (drvp->drive_flags & DRIVE_UDMA) {
			wdccommand(chp, drvp->drive, SET_FEATURES, 0, 0, 0,
			    0x40 | drvp->UDMA_mode, WDSF_SET_MODE);
		} else if (drvp->drive_flags & DRIVE_DMA) {
			wdccommand(chp, drvp->drive, SET_FEATURES, 0, 0, 0,
			    0x20 | drvp->DMA_mode, WDSF_SET_MODE);
		} else {
			goto geometry;
		}
		drvp->state = DMAMODE_WAIT;
		break;
	case DMAMODE_WAIT:
		errstring = "dmamode";
		if (wdcwait(chp, WDCS_DRDY, WDCS_DRDY, delay))
			goto timeout;
		if (chp->wdc->cap & WDC_CAPABILITY_IRQACK)
			chp->wdc->irqack(chp);
		if (chp->ch_status & (WDCS_ERR | WDCS_DWF))
			goto error;
	/* FALLTHROUGH */

	case GEOMETRY:
	geometry:
		if (ata_bio->flags & ATA_LBA)
			goto multimode;
		wdccommand(chp, xfer->drive, WDCC_IDP,
		    ata_bio->lp->d_ncylinders,
		    ata_bio->lp->d_ntracks - 1, 0, ata_bio->lp->d_nsectors,
		    (ata_bio->lp->d_type == DTYPE_ST506) ?
			ata_bio->lp->d_precompcyl / 4 : 0);
		drvp->state = GEOMETRY_WAIT;
		break;

	case GEOMETRY_WAIT:
		errstring = "geometry";
		if (wdcwait(chp, WDCS_DRDY, WDCS_DRDY, delay))
			goto timeout;
		if (chp->wdc->cap & WDC_CAPABILITY_IRQACK)
			chp->wdc->irqack(chp);
		if (chp->ch_status & (WDCS_ERR | WDCS_DWF))
			goto error;
		/* FALLTHROUGH */

	case MULTIMODE:
	multimode:
		if (ata_bio->multi == 1)
			goto ready;
		wdccommand(chp, xfer->drive, WDCC_SETMULTI, 0, 0, 0,
		    ata_bio->multi, 0);
		drvp->state = MULTIMODE_WAIT;
		break;

	case MULTIMODE_WAIT:
		errstring = "setmulti";
		if (wdcwait(chp, WDCS_DRDY, WDCS_DRDY, delay))
			goto timeout;
		if (chp->wdc->cap & WDC_CAPABILITY_IRQACK)
			chp->wdc->irqack(chp);
		if (chp->ch_status & (WDCS_ERR | WDCS_DWF))
			goto error;
		/* FALLTHROUGH */

	case READY:
	ready:
		drvp->state = READY;
		/*
		 * The drive is usable now
		 */
		xfer->c_intr = wdc_ata_bio_intr;
		_wdc_ata_bio_start(chp, xfer);
		return 1;
	}

	if ((ata_bio->flags & ATA_POLL) == 0) {
		chp->ch_flags |= WDCF_IRQ_WAIT;
	} else {
		goto again;
	}
	return 1;

timeout:
	if (irq && (xfer->c_flags & C_TIMEOU) == 0) {
		return 0; /* IRQ was not for us */
	}
	printf("%s:%d:%d: %s timed out\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive, errstring);
	ata_bio->error = TIMEOUT;
	drvp->state = 0;
	wdc_ata_bio_done(chp, xfer);
	return 0;
error:
	printf("%s:%d:%d: %s ",
	    chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive,
	    errstring);
	if (chp->ch_status & WDCS_DWF) {
		printf("drive fault\n");
		ata_bio->error = ERR_DF;
	} else {
		printf("error (%x)\n", chp->ch_error);
		ata_bio->r_error = chp->ch_error;
		ata_bio->error = ERROR;
	}
	drvp->state = 0;
	wdc_ata_bio_done(chp, xfer);
	return 1;
}

int
wdc_ata_err(struct ata_drive_datas *drvp, struct ata_bio *ata_bio)
{
	struct channel_softc *chp = drvp->chnl_softc;
	ata_bio->error = 0;

	if (chp->ch_status == 0xff) {
		ata_bio->error = ERR_NODEV;
		return WDC_ATA_ERR;
	}
	if (chp->ch_status & WDCS_BSY) {
		ata_bio->error = TIMEOUT;
		return WDC_ATA_ERR;
	}

	if (chp->ch_status & WDCS_DWF) {
		ata_bio->error = ERR_DF;
		return WDC_ATA_ERR;
	}

	if (chp->ch_status & WDCS_ERR) {
		ata_bio->error = ERROR;
		ata_bio->r_error = chp->ch_error;
		if (drvp->drive_flags & DRIVE_UDMA &&
		    (ata_bio->r_error & WDCE_CRC)) {
			/*
			 * Record the CRC error, to avoid downgrading to
			 * multiword DMA
			 */
			drvp->drive_flags |= DRIVE_DMAERR;
		}
		if (ata_bio->r_error & (WDCE_BBK | WDCE_UNC | WDCE_IDNF |
		    WDCE_ABRT | WDCE_TK0NF | WDCE_AMNF))
			return WDC_ATA_ERR;
		return WDC_ATA_NOERR;
	}

	if (chp->ch_status & WDCS_CORR)
		ata_bio->flags |= ATA_CORR;
	return WDC_ATA_NOERR;
}

#if 0
int
wdc_ata_addref(drvp)
	struct ata_drive_datas *drvp;
{
	struct channel_softc *chp = drvp->chnl_softc;

	return (wdc_addref(chp));
}

void
wdc_ata_delref(drvp)
	struct ata_drive_datas *drvp;
{
	struct channel_softc *chp = drvp->chnl_softc;

	wdc_delref(chp);
}
#endif


code:
/*	$OpenBSD: atareg.h,v 1.14 2010/07/23 07:47:13 jsg Exp $	*/
/*	$NetBSD: atareg.h,v 1.5 1999/01/18 20:06:24 bouyer Exp $	*/

/*
 * Copyright (c) 1998, 2001 Manuel Bouyer.
 *
 * 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.
 */

#ifndef _DEV_ATA_ATAREG_H_
#define _DEV_ATA_ATAREG_H_

/*
 * Drive parameter structure for ATA/ATAPI.
 * Bit fields: WDC_* : common to ATA/ATAPI
 *             ATA_* : ATA only
 *             ATAPI_* : ATAPI only.
 */
struct ataparams {
    /* drive info */
    u_int16_t	atap_config;		/* 0: general configuration */
#define WDC_CFG_ATAPI_MASK		0xc000
#define WDC_CFG_ATAPI			0x8000
#define ATA_CFG_REMOVABLE		0x0080
#define ATA_CFG_FIXED			0x0040
#define ATAPI_CFG_TYPE_MASK		0x1f00
#define ATAPI_CFG_TYPE(x)		(((x) & ATAPI_CFG_TYPE_MASK) >> 8)
#define ATAPI_CFG_TYPE_DIRECT		0x00
#define ATAPI_CFG_TYPE_SEQUENTIAL	0x01
#define ATAPI_CFG_TYPE_CDROM		0x05
#define ATAPI_CFG_TYPE_OPTICAL		0x07
#define ATAPI_CFG_TYPE_NODEVICE		0x1F
#define ATAPI_CFG_REMOV			0x0080
#define ATAPI_CFG_DRQ_MASK		0x0060
#define ATAPI_CFG_STD_DRQ		0x0000
#define ATAPI_CFG_IRQ_DRQ		0x0020
#define ATAPI_CFG_ACCEL_DRQ		0x0040
#define ATAPI_CFG_CMD_MASK		0x0003
#define ATAPI_CFG_CMD_12		0x0000
#define ATAPI_CFG_CMD_16		0x0001
/* words 1-9 are ATA only */
    u_int16_t	atap_cylinders;		/* 1: # of non-removable cylinders */
    u_int16_t	__reserved1;
    u_int16_t	atap_heads;		/* 3: # of heads */
    u_int16_t	__retired1[2];		/* 4-5: # of unform. bytes/track */
    u_int16_t	atap_sectors;		/* 6: # of sectors */
    u_int16_t	__retired2[3];

    u_int8_t	atap_serial[20];	/* 10-19: serial number */
    u_int16_t	__retired3[2];
    u_int16_t	__obsolete1;
    u_int8_t	atap_revision[8];	/* 23-26: firmware revision */
    u_int8_t	atap_model[40];		/* 27-46: model number */
    u_int16_t	atap_multi;		/* 47: maximum sectors per irq (ATA) */
    u_int16_t	__reserved2;
    u_int16_t	atap_capabilities1;	/* 49: capability flags */
#define WDC_CAP_IORDY	0x0800
#define WDC_CAP_IORDY_DSBL 0x0400
#define WDC_CAP_LBA	0x0200
#define WDC_CAP_DMA	0x0100
#define ATA_CAP_STBY	0x2000
#define ATAPI_CAP_INTERL_DMA	0x8000
#define ATAPI_CAP_CMD_QUEUE	0x4000
#define ATAPI_CAP_OVERLP	0x2000
#define ATAPI_CAP_ATA_RST	0x1000
    u_int16_t	atap_capabilities2;	/* 50: capability flags (ATA) */
#if BYTE_ORDER == LITTLE_ENDIAN
    u_int8_t	__junk2;
    u_int8_t	atap_oldpiotiming;	/* 51: old PIO timing mode */
    u_int8_t	__junk3;
    u_int8_t	atap_olddmatiming;	/* 52: old DMA timing mode (ATA) */
#else
    u_int8_t	atap_oldpiotiming;	/* 51: old PIO timing mode */
    u_int8_t	__junk2;
    u_int8_t	atap_olddmatiming;	/* 52: old DMA timing mode (ATA) */
    u_int8_t	__junk3;
#endif
    u_int16_t	atap_extensions;	/* 53: extensions supported */
#define WDC_EXT_UDMA_MODES	0x0004
#define WDC_EXT_MODES		0x0002
#define WDC_EXT_GEOM		0x0001
/* words 54-62 are ATA only */
    u_int16_t	atap_curcylinders;	/* 54: current logical cylinders */
    u_int16_t	atap_curheads;		/* 55: current logical heads */
    u_int16_t	atap_cursectors;	/* 56: current logical sectors/tracks */
    u_int16_t	atap_curcapacity[2];	/* 57-58: current capacity */
    u_int16_t	atap_curmulti;		/* 59: current multi-sector setting */
#define WDC_MULTI_VALID 0x0100
#define WDC_MULTI_MASK  0x00ff
    u_int16_t	atap_capacity[2];	/* 60-61: total capacity (LBA only) */
    u_int16_t	__retired4;
#if BYTE_ORDER == LITTLE_ENDIAN
    u_int8_t	atap_dmamode_supp;	/* 63: multiword DMA mode supported */
    u_int8_t	atap_dmamode_act;	/*     multiword DMA mode active */
    u_int8_t	atap_piomode_supp;	/* 64: PIO mode supported */
    u_int8_t	__junk4;
#else
    u_int8_t	atap_dmamode_act;	/*     multiword DMA mode active */
    u_int8_t	atap_dmamode_supp;	/* 63: multiword DMA mode supported */
    u_int8_t	__junk4;
    u_int8_t	atap_piomode_supp;	/* 64: PIO mode supported */
#endif
    u_int16_t	atap_dmatiming_mimi;	/* 65: minimum DMA cycle time */
    u_int16_t	atap_dmatiming_recom;	/* 66: recommended DMA cycle time */
    u_int16_t	atap_piotiming;		/* 67: mini PIO cycle time without FC */
    u_int16_t	atap_piotiming_iordy;	/* 68: mini PIO cycle time with IORDY FC */
    u_int16_t	__reserved3[2];
/* words 71-72 are ATAPI only */
    u_int16_t	atap_pkt_br;		/* 71: time (ns) to bus release */
    u_int16_t	atap_pkt_bsyclr;	/* 72: tme to clear BSY after service */
    u_int16_t	__reserved4[2];
    u_int16_t	atap_queuedepth;	/* 75: */
#define WDC_QUEUE_DEPTH_MASK 0x1f
    u_int16_t	atap_sata_caps;		/* 76: SATA capabilities */
#define SATA_SIGNAL_GEN1	0x0002	/* SATA Gen-1 signaling speed */
#define SATA_SIGNAL_GEN2	0x0004	/* SATA Gen-2 signaling speed */
#define SATA_NATIVE_CMDQ	0x0100	/* native command queuing */
#define SATA_HOST_PWR_MGMT	0x0200	/* power management (host) */
    u_int16_t	atap_sata_reserved;	/* 77: reserved */
    u_int16_t	atap_sata_features_supp;/* 78: SATA features supported */
#define SATA_NONZERO_OFFSETS	0x0002	/* non-zero buffer offsets */
#define SATA_DMA_SETUP_AUTO	0x0004	/* DMA setup auto-activate */
#define SATA_DRIVE_PWR_MGMT	0x0008	/* power management (device) */
    u_int16_t	atap_sata_features_en;	/* 79: SATA features enabled */
    u_int16_t	atap_ata_major;		/* 80: Major version number */
#define WDC_VER_ATA1	0x0002
#define WDC_VER_ATA2	0x0004
#define WDC_VER_ATA3	0x0008
#define WDC_VER_ATA4	0x0010
#define WDC_VER_ATA5	0x0020
#define WDC_VER_ATA6	0x0040
#define WDC_VER_ATA7	0x0080
#define WDC_VER_ATA8	0x0100
#define WDC_VER_ATA9	0x0200
#define WDC_VER_ATA10	0x0400
#define WDC_VER_ATA11	0x0800
#define WDC_VER_ATA12	0x1000
#define WDC_VER_ATA13	0x2000
#define WDC_VER_ATA14	0x4000
    u_int16_t	atap_ata_minor;		/* 81: Minor version number */
    u_int16_t	atap_cmd_set1;		/* 82: command set supported */
#define WDC_CMD1_NOP	0x4000
#define WDC_CMD1_RB	0x2000
#define WDC_CMD1_WB	0x1000
#define WDC_CMD1_HPA	0x0400
#define WDC_CMD1_DVRST	0x0200
#define WDC_CMD1_SRV	0x0100
#define WDC_CMD1_RLSE	0x0080
#define WDC_CMD1_AHEAD	0x0040
#define WDC_CMD1_CACHE	0x0020
#define WDC_CMD1_PKT	0x0010
#define WDC_CMD1_PM	0x0008
#define WDC_CMD1_REMOV	0x0004
#define WDC_CMD1_SEC	0x0002
#define WDC_CMD1_SMART	0x0001
    u_int16_t	atap_cmd_set2;		/* 83: command set supported */
#define ATAPI_CMD2_FCE	0x2000 /* Flush Cache Ext supported */
#define ATAPI_CMD2_FC	0x1000 /* Flush Cache supported */
#define ATAPI_CMD2_DCO	0x0800 /* Device Configuration Overlay supported */
#define ATAPI_CMD2_48AD	0x0400 /* 48bit address supported */
#define ATAPI_CMD2_AAM	0x0200 /* Automatic Acoustic Management supported */
#define ATAPI_CMD2_SM	0x0100 /* Set Max security extension supported */
#define ATAPI_CMD2_SF	0x0040 /* Set Features subcommand required */
#define ATAPI_CMD2_PUIS	0x0020 /* Power up in standby supported */
#define WDC_CMD2_RMSN	0x0010
#define ATA_CMD2_APM	0x0008
#define ATA_CMD2_CFA	0x0004
#define ATA_CMD2_RWQ	0x0002
#define WDC_CMD2_DM	0x0001 /* Download Microcode supported */
    u_int16_t	atap_cmd_ext;		/* 84: command/features supp. ext. */
#define ATAPI_CMDE_IIUF	0x2000 /* IDLE IMMEDIATE with UNLOAD FEATURE */
#define ATAPI_CMDE_MSER	0x0004 /* Media serial number supported */
#define ATAPI_CMDE_TEST	0x0002 /* SMART self-test supported */
#define ATAPI_CMDE_SLOG	0x0001 /* SMART error logging supported */
    u_int16_t	atap_cmd1_en;		/* 85: cmd/features enabled */
/* bits are the same as atap_cmd_set1 */
    u_int16_t	atap_cmd2_en;		/* 86: cmd/features enabled */
/* bits are the same as atap_cmd_set2 */
    u_int16_t	atap_cmd_def;		/* 87: cmd/features default */
/* bits are NOT the same as atap_cmd_ext */
#if BYTE_ORDER == LITTLE_ENDIAN
    u_int8_t	atap_udmamode_supp;	/* 88: Ultra-DMA mode supported */
    u_int8_t	atap_udmamode_act;	/*     Ultra-DMA mode active */
#else
    u_int8_t	atap_udmamode_act;	/*     Ultra-DMA mode active */
    u_int8_t	atap_udmamode_supp;	/* 88: Ultra-DMA mode supported */
#endif
/* 89-92 are ATA-only */
    u_int16_t	atap_seu_time;		/* 89: Sec. Erase Unit compl. time */
    u_int16_t	atap_eseu_time;		/* 90: Enhanced SEU compl. time */
    u_int16_t	atap_apm_val;		/* 91: current APM value */
    u_int16_t	atap_mpasswd_rev;	/* 92: Master Password revision */
    u_int16_t	atap_hwreset_res;	/* 93: Hardware reset value */
#define ATA_HWRES_CBLID    0x2000  /* CBLID above Vih */
#define ATA_HWRES_D1_PDIAG 0x0800  /* Device 1 PDIAG detect OK */
#define ATA_HWRES_D1_CSEL  0x0400  /* Device 1 used CSEL for address */
#define ATA_HWRES_D1_JUMP  0x0200  /* Device 1 jumpered to address */
#define ATA_HWRES_D0_SEL   0x0040  /* Device 0 responds when Dev 1 selected */
#define ATA_HWRES_D0_DASP  0x0020  /* Device 0 DASP detect OK */
#define ATA_HWRES_D0_PDIAG 0x0010  /* Device 0 PDIAG detect OK */
#define ATA_HWRES_D0_DIAG  0x0008  /* Device 0 diag OK */
#define ATA_HWRES_D0_CSEL  0x0004  /* Device 0 used CSEL for address */
#define ATA_HWRES_D0_JUMP  0x0002  /* Device 0 jumpered to address */
#if BYTE_ORDER == LITTLE_ENDIAN
    u_int8_t	atap_acoustic_val;	/* 94: Current acoustic level */
    u_int8_t	atap_acoustic_def;	/*     recommended level */
#else
    u_int8_t	atap_acoustic_def;	/*     recommended level */
    u_int8_t	atap_acoustic_val;	/* 94: Current acoustic level */
#endif
    u_int16_t	__reserved6[5];		/* 95-99: reserved */
    u_int16_t	atap_max_lba[4];	/* 100-103: Max. user LBA add */
    u_int16_t	__reserved7[23];	/* 104-126: reserved */
    u_int16_t	atap_rmsn_supp;		/* 127: remov. media status notif. */
#define WDC_RMSN_SUPP_MASK 0x0003
#define WDC_RMSN_SUPP 0x0001
    u_int16_t	atap_sec_st;		/* 128: security status */
#define WDC_SEC_LEV_MAX	0x0100
#define WDC_SEC_ESE_SUPP 0x0020
#define WDC_SEC_EXP	0x0010
#define WDC_SEC_FROZEN	0x0008
#define WDC_SEC_LOCKED	0x0004
#define WDC_SEC_EN	0x0002
#define WDC_SEC_SUPP	0x0001
    u_int16_t	__reserved8[31];	/* 129-159: vendor specific */
    u_int16_t	atap_cfa_power;		/* 160: CFA powermode */
#define ATAPI_CFA_MAX_MASK  0x0FFF
#define ATAPI_CFA_MODE1_DIS 0x1000 /* CFA Mode 1 Disabled */
#define ATAPI_CFA_MODE1_REQ 0x2000 /* CFA Mode 1 Required */
#define ATAPI_CFA_WORD160   0x8000 /* Word 160 supported */
    u_int16_t	__reserved9[15];	/* 161-175: reserved for CFA */
    u_int8_t	atap_media_serial[60];	/* 176-205: media serial number */
    u_int16_t	__reserved10[49];	/* 206-254: reserved */
#if BYTE_ORDER == LITTLE_ENDIAN
    u_int8_t	atap_signature;		/* 255: Signature */
    u_int8_t	atap_checksum;		/*      Checksum */
#else
    u_int8_t	atap_checksum;		/*      Checksum */
    u_int8_t	atap_signature;		/* 255: Signature */
#endif
};

#endif	/* !_DEV_ATA_ATAREG_H_ */

license: unknown


code:
/*	$OpenBSD: atascsi.c,v 1.124 2015/05/15 10:54:26 dlg Exp $ */

/*
 * Copyright (c) 2007 David Gwynne <dlg@openbsd.org>
 * Copyright (c) 2010 Conformal Systems LLC <info@conformal.com>
 * Copyright (c) 2010 Jonathan Matthew <jonathan@d14n.org>
 *
 * 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.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/queue.h>
#include <sys/pool.h>

#include <scsi/scsi_all.h>
#include <scsi/scsi_disk.h>
#include <scsi/scsiconf.h>

#include <dev/ata/atascsi.h>
#include <dev/ata/pmreg.h>

struct atascsi_port;

struct atascsi {
	struct device		*as_dev;
	void			*as_cookie;

	struct atascsi_host_port **as_host_ports;

	struct atascsi_methods	*as_methods;
	struct scsi_adapter	as_switch;
	struct scsi_link	as_link;
	struct scsibus_softc	*as_scsibus;

	int			as_capability;
	int			as_ncqdepth;
};

/*
 * atascsi_host_port is a port attached to the host controller, and
 * only holds the details relevant to the host controller.
 * atascsi_port is any port, including ports on port multipliers, and
 * it holds details of the device attached to the port.
 *
 * When there is a port multiplier attached to a port, the ahp_ports
 * array in the atascsi_host_port struct contains one atascsi_port for
 * each port, and one for the control port (port 15).  The index into
 * the array is the LUN used to address the port.  For the control port,
 * the LUN is 0, and for the port multiplier ports, the LUN is the
 * port number plus one.
 *
 * When there is no port multiplier attached to a port, the ahp_ports
 * array contains a single entry for the device.  The LUN and port number
 * for this entry are both 0.
 */

struct atascsi_host_port {
	struct scsi_iopool	ahp_iopool;
	struct atascsi		*ahp_as;
	int			ahp_port;
	int			ahp_nports;

	struct atascsi_port	**ahp_ports;
};

struct atascsi_port {
	struct ata_identify	ap_identify;
	struct atascsi_host_port *ap_host_port;
	struct atascsi		*ap_as;
	int			ap_pmp_port;
	int			ap_type;
	int			ap_ncqdepth;
	int			ap_features;
#define ATA_PORT_F_NCQ			0x1
#define ATA_PORT_F_TRIM			0x2
};

void		atascsi_cmd(struct scsi_xfer *);
int		atascsi_probe(struct scsi_link *);
void		atascsi_free(struct scsi_link *);

/* template */
struct scsi_adapter atascsi_switch = {
	atascsi_cmd,		/* scsi_cmd */
	scsi_minphys,		/* scsi_minphys */
	atascsi_probe,		/* dev_probe */
	atascsi_free,		/* dev_free */
	NULL,			/* ioctl */
};

void		ata_swapcopy(void *, void *, size_t);

void		atascsi_disk_cmd(struct scsi_xfer *);
void		atascsi_disk_cmd_done(struct ata_xfer *);
void		atascsi_disk_inq(struct scsi_xfer *);
void		atascsi_disk_inquiry(struct scsi_xfer *);
void		atascsi_disk_vpd_supported(struct scsi_xfer *);
void		atascsi_disk_vpd_serial(struct scsi_xfer *);
void		atascsi_disk_vpd_ident(struct scsi_xfer *);
void		atascsi_disk_vpd_ata(struct scsi_xfer *);
void		atascsi_disk_vpd_limits(struct scsi_xfer *);
void		atascsi_disk_vpd_info(struct scsi_xfer *);
void		atascsi_disk_vpd_thin(struct scsi_xfer *);
void		atascsi_disk_write_same_16(struct scsi_xfer *);
void		atascsi_disk_write_same_16_done(struct ata_xfer *);
void		atascsi_disk_unmap(struct scsi_xfer *);
void		atascsi_disk_unmap_task(void *);
void		atascsi_disk_unmap_done(struct ata_xfer *);
void		atascsi_disk_capacity(struct scsi_xfer *);
void		atascsi_disk_capacity16(struct scsi_xfer *);
void		atascsi_disk_sync(struct scsi_xfer *);
void		atascsi_disk_sync_done(struct ata_xfer *);
void		atascsi_disk_sense(struct scsi_xfer *);
void		atascsi_disk_start_stop(struct scsi_xfer *);
void		atascsi_disk_start_stop_done(struct ata_xfer *);

void		atascsi_atapi_cmd(struct scsi_xfer *);
void		atascsi_atapi_cmd_done(struct ata_xfer *);

void		atascsi_pmp_cmd(struct scsi_xfer *);
void		atascsi_pmp_cmd_done(struct ata_xfer *);
void		atascsi_pmp_sense(struct scsi_xfer *xs);
void		atascsi_pmp_inq(struct scsi_xfer *xs);

void		atascsi_passthru_12(struct scsi_xfer *);
void		atascsi_passthru_16(struct scsi_xfer *);
int		atascsi_passthru_map(struct scsi_xfer *, u_int8_t, u_int8_t);
void		atascsi_passthru_done(struct ata_xfer *);

void		atascsi_done(struct scsi_xfer *, int);

void		ata_exec(struct atascsi *, struct ata_xfer *);

void		ata_polled_complete(struct ata_xfer *);
int		ata_polled(struct ata_xfer *);

u_int64_t	ata_identify_blocks(struct ata_identify *);
u_int		ata_identify_blocksize(struct ata_identify *);
u_int		ata_identify_block_l2p_exp(struct ata_identify *);
u_int		ata_identify_block_logical_align(struct ata_identify *);

void		*atascsi_io_get(void *);
void		atascsi_io_put(void *, void *);
struct atascsi_port * atascsi_lookup_port(struct scsi_link *);

int		atascsi_port_identify(struct atascsi_port *,
		    struct ata_identify *);
int		atascsi_port_set_features(struct atascsi_port *, int, int);

struct atascsi *
atascsi_attach(struct device *self, struct atascsi_attach_args *aaa)
{
	struct scsibus_attach_args	saa;
	struct atascsi			*as;

	as = malloc(sizeof(*as), M_DEVBUF, M_WAITOK | M_ZERO);

	as->as_dev = self;
	as->as_cookie = aaa->aaa_cookie;
	as->as_methods = aaa->aaa_methods;
	as->as_capability = aaa->aaa_capability;
	as->as_ncqdepth = aaa->aaa_ncmds;

	/* copy from template and modify for ourselves */
	as->as_switch = atascsi_switch;
	if (aaa->aaa_minphys != NULL)
		as->as_switch.scsi_minphys = aaa->aaa_minphys;

	/* fill in our scsi_link */
	as->as_link.adapter = &as->as_switch;
	as->as_link.adapter_softc = as;
	as->as_link.adapter_buswidth = aaa->aaa_nports;
	as->as_link.luns = SATA_PMP_MAX_PORTS;
	as->as_link.adapter_target = aaa->aaa_nports;
	as->as_link.openings = 1;

	as->as_host_ports = mallocarray(aaa->aaa_nports,
	    sizeof(struct atascsi_host_port *),	M_DEVBUF, M_WAITOK | M_ZERO);

	bzero(&saa, sizeof(saa));
	saa.saa_sc_link = &as->as_link;

	/* stash the scsibus so we can do hotplug on it */
	as->as_scsibus = (struct scsibus_softc *)config_found(self, &saa,
	    scsiprint);

	return (as);
}

int
atascsi_detach(struct atascsi *as, int flags)
{
	int				rv;

	rv = config_detach((struct device *)as->as_scsibus, flags);
	if (rv != 0)
		return (rv);

	free(as->as_host_ports, M_DEVBUF, 0);
	free(as, M_DEVBUF, 0);

	return (0);
}

int
atascsi_probe_dev(struct atascsi *as, int port, int lun)
{
	if (lun == 0) {
		return (scsi_probe_target(as->as_scsibus, port));
	} else {
		return (scsi_probe_lun(as->as_scsibus, port, lun));
	}
}

int
atascsi_detach_dev(struct atascsi *as, int port, int lun, int flags)
{
	if (lun == 0) {
		return (scsi_detach_target(as->as_scsibus, port, flags));
	} else {
		return (scsi_detach_lun(as->as_scsibus, port, lun, flags));
	}
}

struct atascsi_port *
atascsi_lookup_port(struct scsi_link *link)
{
	struct atascsi 			*as = link->adapter_softc;
	struct atascsi_host_port 	*ahp;

	if (link->target >= as->as_link.adapter_buswidth)
		return (NULL);

	ahp = as->as_host_ports[link->target];
	if (link->lun >= ahp->ahp_nports)
		return (NULL);

	return (ahp->ahp_ports[link->lun]);
}

int
atascsi_probe(struct scsi_link *link)
{
	struct atascsi			*as = link->adapter_softc;
	struct atascsi_host_port 	*ahp;
	struct atascsi_port		*ap;
	struct ata_xfer			*xa;
	struct ata_identify		*identify;
	int				port, type, qdepth;
	int				rv;
	u_int16_t			cmdset;

	port = link->target;
	if (port >= as->as_link.adapter_buswidth)
		return (ENXIO);

	/* if this is a PMP port, check it's valid */
	if (link->lun > 0) {
		if (link->lun >= as->as_host_ports[port]->ahp_nports)
			return (ENXIO);
	}

	type = as->as_methods->ata_probe(as->as_cookie, port, link->lun);
	switch (type) {
	case ATA_PORT_T_DISK:
		break;
	case ATA_PORT_T_ATAPI:
		link->flags |= SDEV_ATAPI;
		link->quirks |= SDEV_ONLYBIG;
		break;
	case ATA_PORT_T_PM:
		if (link->lun != 0) {
			printf("%s.%d.%d: Port multipliers cannot be nested\n",
			    as->as_dev->dv_xname, port, link->lun);
			rv = ENODEV;
			goto unsupported;
		}
		break;
	default:
		rv = ENODEV;
		goto unsupported;
	}

	ap = malloc(sizeof(*ap), M_DEVBUF, M_WAITOK | M_ZERO);
	ap->ap_as = as;

	if (link->lun == 0) {
		ahp = malloc(sizeof(*ahp), M_DEVBUF, M_WAITOK | M_ZERO);
		ahp->ahp_as = as;
		ahp->ahp_port = port;

		scsi_iopool_init(&ahp->ahp_iopool, ahp, atascsi_io_get,
		    atascsi_io_put);

		as->as_host_ports[port] = ahp;

		if (type == ATA_PORT_T_PM) {
			ahp->ahp_nports = SATA_PMP_MAX_PORTS;
			ap->ap_pmp_port = SATA_PMP_CONTROL_PORT;
		} else {
			ahp->ahp_nports = 1;
			ap->ap_pmp_port = 0;
		}
		ahp->ahp_ports = mallocarray(ahp->ahp_nports,
		    sizeof(struct atascsi_port *), M_DEVBUF, M_WAITOK | M_ZERO);
	} else {
		ahp = as->as_host_ports[port];
		ap->ap_pmp_port = link->lun - 1;
	}

	ap->ap_host_port = ahp;
	ap->ap_type = type;

	link->pool = &ahp->ahp_iopool;

	/* fetch the device info, except for port multipliers */
	if (type != ATA_PORT_T_PM) {

		/* devices attached to port multipliers tend not to be
		 * spun up at this point, and sometimes this prevents
		 * identification from working, so we retry a few times
		 * with a fairly long delay.
		 */

		identify = dma_alloc(sizeof(*identify), PR_WAITOK | PR_ZERO);

		int count = (link->lun > 0) ? 6 : 2;
		while (count--) {
			rv = atascsi_port_identify(ap, identify);
			if (rv == 0) {
				ap->ap_identify = *identify;
				break;
			}
			if (count > 0)
				delay(5000000);
		}

		dma_free(identify, sizeof(*identify));

		if (rv != 0) {
			goto error;
		}
	}

	ahp->ahp_ports[link->lun] = ap;

	if (type != ATA_PORT_T_DISK)
		return (0);

	if (as->as_capability & ASAA_CAP_NCQ &&
	    ISSET(letoh16(ap->ap_identify.satacap), ATA_SATACAP_NCQ) &&
	    (link->lun == 0 || as->as_capability & ASAA_CAP_PMP_NCQ)) {
		ap->ap_ncqdepth = ATA_QDEPTH(letoh16(ap->ap_identify.qdepth));
		qdepth = MIN(ap->ap_ncqdepth, as->as_ncqdepth);
		if (ISSET(as->as_capability, ASAA_CAP_NEEDS_RESERVED))
			qdepth--;

		if (qdepth > 1) {
			SET(ap->ap_features, ATA_PORT_F_NCQ);

			/* Raise the number of openings */
			link->openings = qdepth;

			/*
			 * XXX for directly attached devices, throw away any xfers
			 * that have tag numbers higher than what the device supports.
			 */
			if (link->lun == 0) {
				while (qdepth--) {
					xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP);
					if (xa->tag < link->openings) {
						xa->state = ATA_S_COMPLETE;
						scsi_io_put(&ahp->ahp_iopool, xa);
					}
				}
			}
		}
	}

	if (ISSET(letoh16(ap->ap_identify.data_set_mgmt),
	    ATA_ID_DATA_SET_MGMT_TRIM))
		SET(ap->ap_features, ATA_PORT_F_TRIM);

	cmdset = letoh16(ap->ap_identify.cmdset82);

	/* Enable write cache if supported */
	if (ISSET(cmdset, ATA_IDENTIFY_WRITECACHE)) {
		/* We don't care if it fails. */
		(void)atascsi_port_set_features(ap, ATA_SF_WRITECACHE_EN, 0);
	}

	/* Enable read lookahead if supported */
	if (ISSET(cmdset, ATA_IDENTIFY_LOOKAHEAD)) {
		/* We don't care if it fails. */
		(void)atascsi_port_set_features(ap, ATA_SF_LOOKAHEAD_EN, 0);
	}

	/*
	 * FREEZE LOCK the device so malicous users can't lock it on us.
	 * As there is no harm in issuing this to devices that don't
	 * support the security feature set we just send it, and don't bother
	 * checking if the device sends a command abort to tell us it doesn't
	 * support it
	 */
	xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP);
	if (xa == NULL)
		panic("no free xfers on a new port");
	xa->fis->command = ATA_C_SEC_FREEZE_LOCK;
	xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
	xa->flags = ATA_F_POLL;
	xa->timeout = 1000;
	xa->complete = ata_polled_complete;
	xa->pmp_port = ap->ap_pmp_port;
	xa->atascsi_private = &ahp->ahp_iopool;
	ata_exec(as, xa);
	ata_polled(xa); /* we dont care if it doesnt work */

	return (0);
error:
	free(ap, M_DEVBUF, 0);
unsupported:

	as->as_methods->ata_free(as->as_cookie, port, link->lun);
	return (rv);
}

void
atascsi_free(struct scsi_link *link)
{
	struct atascsi			*as = link->adapter_softc;
	struct atascsi_host_port	*ahp;
	struct atascsi_port		*ap;
	int				port;

	port = link->target;
	if (port >= as->as_link.adapter_buswidth)
		return;

	ahp = as->as_host_ports[port];
	if (ahp == NULL)
		return;

	if (link->lun >= ahp->ahp_nports)
		return;

	ap = ahp->ahp_ports[link->lun];
	free(ap, M_DEVBUF, 0);
	ahp->ahp_ports[link->lun] = NULL;

	as->as_methods->ata_free(as->as_cookie, port, link->lun);

	if (link->lun == ahp->ahp_nports - 1) {
		/* we've already freed all of ahp->ahp_ports, now
		 * free ahp itself.  this relies on the order luns are
		 * detached in scsi_detach_target().
		 */
		free(ahp, M_DEVBUF, 0);
		as->as_host_ports[port] = NULL;
	}
}

void
atascsi_cmd(struct scsi_xfer *xs)
{
	struct scsi_link	*link = xs->sc_link;
	struct atascsi_port	*ap;

	ap = atascsi_lookup_port(link);
	if (ap == NULL) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	switch (ap->ap_type) {
	case ATA_PORT_T_DISK:
		atascsi_disk_cmd(xs);
		break;
	case ATA_PORT_T_ATAPI:
		atascsi_atapi_cmd(xs);
		break;
	case ATA_PORT_T_PM:
		atascsi_pmp_cmd(xs);
		break;

	case ATA_PORT_T_NONE:
	default:
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		break;
	}
}

void
atascsi_disk_cmd(struct scsi_xfer *xs)
{
	struct scsi_link	*link = xs->sc_link;
	struct atascsi		*as = link->adapter_softc;
	struct atascsi_port	*ap;
	struct ata_xfer		*xa = xs->io;
	int			flags = 0;
	struct ata_fis_h2d	*fis;
	u_int64_t		lba;
	u_int32_t		sector_count;

	ap = atascsi_lookup_port(link);

	switch (xs->cmd->opcode) {
	case READ_COMMAND:
	case READ_BIG:
	case READ_12:
	case READ_16:
		flags = ATA_F_READ;
		break;
	case WRITE_COMMAND:
	case WRITE_BIG:
	case WRITE_12:
	case WRITE_16:
		flags = ATA_F_WRITE;
		/* deal with io outside the switch */
		break;

	case WRITE_SAME_16:
		atascsi_disk_write_same_16(xs);
		return;
	case UNMAP:
		atascsi_disk_unmap(xs);
		return;

	case SYNCHRONIZE_CACHE:
		atascsi_disk_sync(xs);
		return;
	case REQUEST_SENSE:
		atascsi_disk_sense(xs);
		return;
	case INQUIRY:
		atascsi_disk_inq(xs);
		return;
	case READ_CAPACITY:
		atascsi_disk_capacity(xs);
		return;
	case READ_CAPACITY_16:
		atascsi_disk_capacity16(xs);
		return;

	case ATA_PASSTHRU_12:
		atascsi_passthru_12(xs);
		return;
	case ATA_PASSTHRU_16:
		atascsi_passthru_16(xs);
		return;

	case START_STOP:
		atascsi_disk_start_stop(xs);
		return;

	case TEST_UNIT_READY:
	case PREVENT_ALLOW:
		atascsi_done(xs, XS_NOERROR);
		return;

	default:
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	xa->flags = flags;
	scsi_cmd_rw_decode(xs->cmd, &lba, &sector_count);
	if ((lba >> 48) != 0 || (sector_count >> 16) != 0) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	fis = xa->fis;

	fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
	fis->lba_low = lba & 0xff;
	fis->lba_mid = (lba >> 8) & 0xff;
	fis->lba_high = (lba >> 16) & 0xff;

	if (ISSET(ap->ap_features, ATA_PORT_F_NCQ) &&
	    (xa->tag < ap->ap_ncqdepth) &&
	    !(xs->flags & SCSI_POLL)) {
		/* Use NCQ */
		xa->flags |= ATA_F_NCQ;
		fis->command = (xa->flags & ATA_F_WRITE) ?
		    ATA_C_WRITE_FPDMA : ATA_C_READ_FPDMA;
		fis->device = ATA_H2D_DEVICE_LBA;
		fis->lba_low_exp = (lba >> 24) & 0xff;
		fis->lba_mid_exp = (lba >> 32) & 0xff;
		fis->lba_high_exp = (lba >> 40) & 0xff;
		fis->sector_count = xa->tag << 3;
		fis->features = sector_count & 0xff;
		fis->features_exp = (sector_count >> 8) & 0xff;
	} else if (sector_count > 0x100 || lba > 0xfffffff) {
		/* Use LBA48 */
		fis->command = (xa->flags & ATA_F_WRITE) ?
		    ATA_C_WRITEDMA_EXT : ATA_C_READDMA_EXT;
		fis->device = ATA_H2D_DEVICE_LBA;
		fis->lba_low_exp = (lba >> 24) & 0xff;
		fis->lba_mid_exp = (lba >> 32) & 0xff;
		fis->lba_high_exp = (lba >> 40) & 0xff;
		fis->sector_count = sector_count & 0xff;
		fis->sector_count_exp = (sector_count >> 8) & 0xff;
	} else {
		/* Use LBA */
		fis->command = (xa->flags & ATA_F_WRITE) ?
		    ATA_C_WRITEDMA : ATA_C_READDMA;
		fis->device = ATA_H2D_DEVICE_LBA | ((lba >> 24) & 0x0f);
		fis->sector_count = sector_count & 0xff;
	}

	xa->data = xs->data;
	xa->datalen = xs->datalen;
	xa->complete = atascsi_disk_cmd_done;
	xa->timeout = xs->timeout;
	xa->pmp_port = ap->ap_pmp_port;
	xa->atascsi_private = xs;
	if (xs->flags & SCSI_POLL)
		xa->flags |= ATA_F_POLL;

	ata_exec(as, xa);
}

void
atascsi_disk_cmd_done(struct ata_xfer *xa)
{
	struct scsi_xfer	*xs = xa->atascsi_private;

	switch (xa->state) {
	case ATA_S_COMPLETE:
		xs->error = XS_NOERROR;
		break;
	case ATA_S_ERROR:
		/* fake sense? */
		xs->error = XS_DRIVER_STUFFUP;
		break;
	case ATA_S_TIMEOUT:
		xs->error = XS_TIMEOUT;
		break;
	default:
		panic("atascsi_disk_cmd_done: unexpected ata_xfer state (%d)",
		    xa->state);
	}

	xs->resid = xa->resid;

	scsi_done(xs);
}

void
atascsi_disk_inq(struct scsi_xfer *xs)
{
	struct scsi_inquiry	*inq = (struct scsi_inquiry *)xs->cmd;

	if (xs->cmdlen != sizeof(*inq)) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	if (ISSET(inq->flags, SI_EVPD)) {
		switch (inq->pagecode) {
		case SI_PG_SUPPORTED:
			atascsi_disk_vpd_supported(xs);
			break;
		case SI_PG_SERIAL:
			atascsi_disk_vpd_serial(xs);
			break;
		case SI_PG_DEVID:
			atascsi_disk_vpd_ident(xs);
			break;
		case SI_PG_ATA:
			atascsi_disk_vpd_ata(xs);
			break;
		case SI_PG_DISK_LIMITS:
			atascsi_disk_vpd_limits(xs);
			break;
		case SI_PG_DISK_INFO:
			atascsi_disk_vpd_info(xs);
			break;
		case SI_PG_DISK_THIN:
			atascsi_disk_vpd_thin(xs);
			break;
		default:
			atascsi_done(xs, XS_DRIVER_STUFFUP);
			break;
		}
	} else
		atascsi_disk_inquiry(xs);
}

void
atascsi_disk_inquiry(struct scsi_xfer *xs)
{
	struct scsi_inquiry_data inq;
	struct scsi_link        *link = xs->sc_link;
	struct atascsi_port	*ap;

	ap = atascsi_lookup_port(link);

	bzero(&inq, sizeof(inq));

	inq.device = T_DIRECT;
	inq.version = 0x05; /* SPC-3 */
	inq.response_format = 2;
	inq.additional_length = 32;
	inq.flags |= SID_CmdQue;
	bcopy("ATA     ", inq.vendor, sizeof(inq.vendor));
	ata_swapcopy(ap->ap_identify.model, inq.product,
	    sizeof(inq.product));
	ata_swapcopy(ap->ap_identify.firmware, inq.revision,
	    sizeof(inq.revision));

	bcopy(&inq, xs->data, MIN(sizeof(inq), xs->datalen));

	atascsi_done(xs, XS_NOERROR);
}

void
atascsi_disk_vpd_supported(struct scsi_xfer *xs)
{
	struct {
		struct scsi_vpd_hdr	hdr;
		u_int8_t		list[7];
	}			pg;
	struct scsi_link        *link = xs->sc_link;
	struct atascsi_port	*ap;
	int			fat;

	ap = atascsi_lookup_port(link);
	fat = ISSET(ap->ap_features, ATA_PORT_F_TRIM) ? 0 : 1;

	bzero(&pg, sizeof(pg));

	pg.hdr.device = T_DIRECT;
	pg.hdr.page_code = SI_PG_SUPPORTED;
	_lto2b(sizeof(pg.list) - fat, pg.hdr.page_length);
	pg.list[0] = SI_PG_SUPPORTED;
	pg.list[1] = SI_PG_SERIAL;
	pg.list[2] = SI_PG_DEVID;
	pg.list[3] = SI_PG_ATA;
	pg.list[4] = SI_PG_DISK_LIMITS;
	pg.list[5] = SI_PG_DISK_INFO;
	pg.list[6] = SI_PG_DISK_THIN; /* "trimmed" if fat. get it? tehe. */

	bcopy(&pg, xs->data, MIN(sizeof(pg) - fat, xs->datalen));

	atascsi_done(xs, XS_NOERROR);
}

void
atascsi_disk_vpd_serial(struct scsi_xfer *xs)
{
	struct scsi_link        *link = xs->sc_link;
	struct atascsi_port	*ap;
	struct scsi_vpd_serial	pg;

	ap = atascsi_lookup_port(link);
	bzero(&pg, sizeof(pg));

	pg.hdr.device = T_DIRECT;
	pg.hdr.page_code = SI_PG_SERIAL;
	_lto2b(sizeof(ap->ap_identify.serial), pg.hdr.page_length);
	ata_swapcopy(ap->ap_identify.serial, pg.serial,
	    sizeof(ap->ap_identify.serial));

	bcopy(&pg, xs->data, MIN(sizeof(pg), xs->datalen));

	atascsi_done(xs, XS_NOERROR);
}

void
atascsi_disk_vpd_ident(struct scsi_xfer *xs)
{
	struct scsi_link        *link = xs->sc_link;
	struct atascsi_port	*ap;
	struct {
		struct scsi_vpd_hdr	hdr;
		struct scsi_vpd_devid_hdr devid_hdr;
		u_int8_t		devid[68];
	}			pg;
	u_int8_t		*p;
	size_t			pg_len;

	ap = atascsi_lookup_port(link);
	bzero(&pg, sizeof(pg));
	if (letoh16(ap->ap_identify.features87) & ATA_ID_F87_WWN) {
		pg_len = 8;

		pg.devid_hdr.pi_code = VPD_DEVID_CODE_BINARY;
		pg.devid_hdr.flags = VPD_DEVID_ASSOC_LU | VPD_DEVID_TYPE_NAA;

		ata_swapcopy(&ap->ap_identify.naa_ieee_oui, pg.devid, pg_len);
	} else {
		pg_len = 68;

		pg.devid_hdr.pi_code = VPD_DEVID_CODE_ASCII;
		pg.devid_hdr.flags = VPD_DEVID_ASSOC_LU | VPD_DEVID_TYPE_T10;

		p = pg.devid;
		bcopy("ATA     ", p, 8);
		p += 8;
		ata_swapcopy(ap->ap_identify.model, p,
		    sizeof(ap->ap_identify.model));
		p += sizeof(ap->ap_identify.model);
		ata_swapcopy(ap->ap_identify.serial, p,
		    sizeof(ap->ap_identify.serial));
	}

	pg.devid_hdr.len = pg_len;
	pg_len += sizeof(pg.devid_hdr);

	pg.hdr.device = T_DIRECT;
	pg.hdr.page_code = SI_PG_DEVID;
	_lto2b(pg_len, pg.hdr.page_length);
	pg_len += sizeof(pg.hdr);

	bcopy(&pg, xs->data, MIN(pg_len, xs->datalen));

	atascsi_done(xs, XS_NOERROR);
}

void
atascsi_disk_vpd_ata(struct scsi_xfer *xs)
{
	struct scsi_link        *link = xs->sc_link;
	struct atascsi_port	*ap;
	struct scsi_vpd_ata	pg;

	ap = atascsi_lookup_port(link);
	bzero(&pg, sizeof(pg));

	pg.hdr.device = T_DIRECT;
	pg.hdr.page_code = SI_PG_ATA;
	_lto2b(sizeof(pg) - sizeof(pg.hdr), pg.hdr.page_length);

	memset(pg.sat_vendor, ' ', sizeof(pg.sat_vendor));
	memcpy(pg.sat_vendor, "OpenBSD",
	    MIN(strlen("OpenBSD"), sizeof(pg.sat_vendor)));
	memset(pg.sat_product, ' ', sizeof(pg.sat_product));
	memcpy(pg.sat_product, "atascsi",
	    MIN(strlen("atascsi"), sizeof(pg.sat_product)));
	memset(pg.sat_revision, ' ', sizeof(pg.sat_revision));
	memcpy(pg.sat_revision, osrelease,
	    MIN(strlen(osrelease), sizeof(pg.sat_product)));

	/* XXX device signature */

	switch (ap->ap_type) {
	case ATA_PORT_T_DISK:
		pg.command_code = VPD_ATA_COMMAND_CODE_ATA;
		break;
	case ATA_PORT_T_ATAPI:
		pg.command_code = VPD_ATA_COMMAND_CODE_ATAPI;
		break;
	}

	memcpy(pg.identify, &ap->ap_identify, sizeof(pg.identify));

	bcopy(&pg, xs->data, MIN(sizeof(pg), xs->datalen));

	atascsi_done(xs, XS_NOERROR);
}

void
atascsi_disk_vpd_limits(struct scsi_xfer *xs)
{
	struct scsi_link        *link = xs->sc_link;
	struct atascsi_port	*ap;
	struct scsi_vpd_disk_limits pg;

	ap = atascsi_lookup_port(link);
	bzero(&pg, sizeof(pg));
	pg.hdr.device = T_DIRECT;
	pg.hdr.page_code = SI_PG_DISK_LIMITS;
	_lto2b(SI_PG_DISK_LIMITS_LEN_THIN, pg.hdr.page_length);

	_lto2b(1 << ata_identify_block_l2p_exp(&ap->ap_identify),
	    pg.optimal_xfer_granularity);

	if (ISSET(ap->ap_features, ATA_PORT_F_TRIM)) {
		/*
		 * ATA only supports 65535 blocks per TRIM descriptor, so
		 * avoid having to split UNMAP descriptors and overflow the page
		 * limit by using that as a max.
		 */
		_lto4b(ATA_DSM_TRIM_MAX_LEN, pg.max_unmap_lba_count);
		_lto4b(512 / 8, pg.max_unmap_desc_count);
        }

	bcopy(&pg, xs->data, MIN(sizeof(pg), xs->datalen));

	atascsi_done(xs, XS_NOERROR);
}

void
atascsi_disk_vpd_info(struct scsi_xfer *xs)
{
	struct scsi_link        *link = xs->sc_link;
	struct atascsi_port	*ap;
	struct scsi_vpd_disk_info pg;

	ap = atascsi_lookup_port(link);
	bzero(&pg, sizeof(pg));
	pg.hdr.device = T_DIRECT;
	pg.hdr.page_code = SI_PG_DISK_INFO;
	_lto2b(sizeof(pg) - sizeof(pg.hdr), pg.hdr.page_length);

	_lto2b(letoh16(ap->ap_identify.rpm), pg.rpm);
	pg.form_factor = letoh16(ap->ap_identify.form) & ATA_ID_FORM_MASK;

	bcopy(&pg, xs->data, MIN(sizeof(pg), xs->datalen));

	atascsi_done(xs, XS_NOERROR);
}

void
atascsi_disk_vpd_thin(struct scsi_xfer *xs)
{
	struct scsi_link        *link = xs->sc_link;
	struct atascsi_port	*ap;
	struct scsi_vpd_disk_thin pg;

	ap = atascsi_lookup_port(link);
	if (!ISSET(ap->ap_features, ATA_PORT_F_TRIM)) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	bzero(&pg, sizeof(pg));
	pg.hdr.device = T_DIRECT;
	pg.hdr.page_code = SI_PG_DISK_THIN;
	_lto2b(sizeof(pg) - sizeof(pg.hdr), pg.hdr.page_length);

	pg.flags = VPD_DISK_THIN_TPU | VPD_DISK_THIN_TPWS;

	bcopy(&pg, xs->data, MIN(sizeof(pg), xs->datalen));

	atascsi_done(xs, XS_NOERROR);
}

void
atascsi_disk_write_same_16(struct scsi_xfer *xs)
{
	struct scsi_link	*link = xs->sc_link;
	struct atascsi		*as = link->adapter_softc;
	struct atascsi_port	*ap;
	struct scsi_write_same_16 *cdb;
	struct ata_xfer		*xa = xs->io;
	struct ata_fis_h2d	*fis;
	u_int64_t		lba;
	u_int32_t		length;
	u_int64_t		desc;

	if (xs->cmdlen != sizeof(*cdb)) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	ap = atascsi_lookup_port(link);
	cdb = (struct scsi_write_same_16 *)xs->cmd;

	if (!ISSET(cdb->flags, WRITE_SAME_F_UNMAP) ||
	   !ISSET(ap->ap_features, ATA_PORT_F_TRIM)) {
		/* generate sense data */
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	if (xs->datalen < 512) {
		/* generate sense data */
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	lba = _8btol(cdb->lba);
	length = _4btol(cdb->length);

	if (length > ATA_DSM_TRIM_MAX_LEN) {
		/* XXX we dont support requests over 65535 blocks */
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	xa->data = xs->data;
	xa->datalen = 512;
	xa->flags = ATA_F_WRITE;
	xa->pmp_port = ap->ap_pmp_port;
	if (xs->flags & SCSI_POLL)
		xa->flags |= ATA_F_POLL;
	xa->complete = atascsi_disk_write_same_16_done;
	xa->atascsi_private = xs;
	xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout;

	/* TRIM sends a list of blocks to discard in the databuf. */
	memset(xa->data, 0, xa->datalen);
	desc = htole64(ATA_DSM_TRIM_DESC(lba, length));
	memcpy(xa->data, &desc, sizeof(desc));

	fis = xa->fis;
	fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
	fis->command = ATA_C_DSM;
	fis->features = ATA_DSM_TRIM;
	fis->sector_count = 1;

	ata_exec(as, xa);
}

void
atascsi_disk_write_same_16_done(struct ata_xfer *xa)
{
	struct scsi_xfer	*xs = xa->atascsi_private;

	switch (xa->state) {
	case ATA_S_COMPLETE:
		xs->error = XS_NOERROR;
		break;
	case ATA_S_ERROR:
		xs->error = XS_DRIVER_STUFFUP;
		break;
	case ATA_S_TIMEOUT:
		xs->error = XS_TIMEOUT;
		break;

	default:
		panic("atascsi_disk_write_same_16_done: "
		    "unexpected ata_xfer state (%d)", xa->state);
	}

	scsi_done(xs);
}

void
atascsi_disk_unmap(struct scsi_xfer *xs)
{
	struct ata_xfer		*xa = xs->io;
	struct scsi_unmap	*cdb;
	struct scsi_unmap_data	*unmap;
	u_int			len;

	if (ISSET(xs->flags, SCSI_POLL) || xs->cmdlen != sizeof(*cdb))
		atascsi_done(xs, XS_DRIVER_STUFFUP);

	cdb = (struct scsi_unmap *)xs->cmd;
	len = _2btol(cdb->list_len);
	if (xs->datalen != len || len < sizeof(*unmap)) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	unmap = (struct scsi_unmap_data *)xs->data;
	if (_2btol(unmap->data_length) != len) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	len = _2btol(unmap->desc_length);
	if (len != xs->datalen - sizeof(*unmap)) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	if (len < sizeof(struct scsi_unmap_desc)) {
		/* no work, no error according to sbc3 */
		atascsi_done(xs, XS_NOERROR);
	}

	if (len > sizeof(struct scsi_unmap_desc) * 64) {
		/* more work than we advertised */
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	/* let's go */
	if (ISSET(xs->flags, SCSI_NOSLEEP)) {
		task_set(&xa->task, atascsi_disk_unmap_task, xs);
		task_add(systq, &xa->task);
	} else {
		/* we can already sleep for memory */
		atascsi_disk_unmap_task(xs);
	}
}

void
atascsi_disk_unmap_task(void *xxs)
{
	struct scsi_xfer	*xs = xxs;
	struct scsi_link	*link = xs->sc_link;
	struct atascsi		*as = link->adapter_softc;
	struct atascsi_port	*ap;
	struct ata_xfer		*xa = xs->io;
	struct ata_fis_h2d	*fis;
	struct scsi_unmap_data	*unmap;
	struct scsi_unmap_desc	*descs, *d;
	u_int64_t		*trims;
	u_int			len, i;

	trims = dma_alloc(512, PR_WAITOK | PR_ZERO);

	ap = atascsi_lookup_port(link);
	unmap = (struct scsi_unmap_data *)xs->data;
	descs = (struct scsi_unmap_desc *)(unmap + 1);

	len = _2btol(unmap->desc_length) / sizeof(*d);
	for (i = 0; i < len; i++) {
		d = &descs[i];
		if (_4btol(d->logical_blocks) > ATA_DSM_TRIM_MAX_LEN)
			goto fail;

		trims[i] = htole64(ATA_DSM_TRIM_DESC(_8btol(d->logical_addr),
		    _4btol(d->logical_blocks)));
	}

	xa->data = trims;
	xa->datalen = 512;
	xa->flags = ATA_F_WRITE;
	xa->pmp_port = ap->ap_pmp_port;
	xa->complete = atascsi_disk_unmap_done;
	xa->atascsi_private = xs;
	xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout;

	fis = xa->fis;
	fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
	fis->command = ATA_C_DSM;
	fis->features = ATA_DSM_TRIM;
	fis->sector_count = 1;

	ata_exec(as, xa);
	return;

 fail:
	dma_free(xa->data, 512);
	atascsi_done(xs, XS_DRIVER_STUFFUP);
}

void
atascsi_disk_unmap_done(struct ata_xfer *xa)
{
	struct scsi_xfer	*xs = xa->atascsi_private;

	dma_free(xa->data, 512);

	switch (xa->state) {
	case ATA_S_COMPLETE:
		xs->error = XS_NOERROR;
		break;
	case ATA_S_ERROR:
		xs->error = XS_DRIVER_STUFFUP;
		break;
	case ATA_S_TIMEOUT:
		xs->error = XS_TIMEOUT;
		break;

	default:
		panic("atascsi_disk_unmap_done: "
		    "unexpected ata_xfer state (%d)", xa->state);
	}

	scsi_done(xs);
}

void
atascsi_disk_sync(struct scsi_xfer *xs)
{
	struct scsi_link	*link = xs->sc_link;
	struct atascsi		*as = link->adapter_softc;
	struct atascsi_port	*ap;
	struct ata_xfer		*xa = xs->io;

	if (xs->cmdlen != sizeof(struct scsi_synchronize_cache)) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	ap = atascsi_lookup_port(link);
	xa->datalen = 0;
	xa->flags = ATA_F_READ;
	xa->complete = atascsi_disk_sync_done;
	/* Spec says flush cache can take >30 sec, so give it at least 45. */
	xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout;
	xa->atascsi_private = xs;
	xa->pmp_port = ap->ap_pmp_port;
	if (xs->flags & SCSI_POLL)
		xa->flags |= ATA_F_POLL;

	xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
	xa->fis->command = ATA_C_FLUSH_CACHE;
	xa->fis->device = 0;

	ata_exec(as, xa);
}

void
atascsi_disk_sync_done(struct ata_xfer *xa)
{
	struct scsi_xfer	*xs = xa->atascsi_private;

	switch (xa->state) {
	case ATA_S_COMPLETE:
		xs->error = XS_NOERROR;
		break;

	case ATA_S_ERROR:
	case ATA_S_TIMEOUT:
		printf("atascsi_disk_sync_done: %s\n",
		    xa->state == ATA_S_TIMEOUT ? "timeout" : "error");
		xs->error = (xa->state == ATA_S_TIMEOUT ? XS_TIMEOUT :
		    XS_DRIVER_STUFFUP);
		break;

	default:
		panic("atascsi_disk_sync_done: unexpected ata_xfer state (%d)",
		    xa->state);
	}

	scsi_done(xs);
}

u_int64_t
ata_identify_blocks(struct ata_identify *id)
{
	u_int64_t		blocks = 0;
	int			i;

	if (letoh16(id->cmdset83) & 0x0400) {
		/* LBA48 feature set supported */
		for (i = 3; i >= 0; --i) {
			blocks <<= 16;
			blocks += letoh16(id->addrsecxt[i]);
		}
	} else {
		blocks = letoh16(id->addrsec[1]);
		blocks <<= 16;
		blocks += letoh16(id->addrsec[0]);
	}

	return (blocks - 1);
}

u_int
ata_identify_blocksize(struct ata_identify *id)
{
	u_int			blocksize = 512;
	u_int16_t		p2l_sect = letoh16(id->p2l_sect);

	if ((p2l_sect & ATA_ID_P2L_SECT_MASK) == ATA_ID_P2L_SECT_VALID &&
	    ISSET(p2l_sect, ATA_ID_P2L_SECT_SIZESET)) {
		blocksize = letoh16(id->words_lsec[1]);
		blocksize <<= 16;
		blocksize += letoh16(id->words_lsec[0]);
		blocksize <<= 1;
	}

	return (blocksize);
}

u_int
ata_identify_block_l2p_exp(struct ata_identify *id)
{
	u_int			exponent = 0;
	u_int16_t		p2l_sect = letoh16(id->p2l_sect);

	if ((p2l_sect & ATA_ID_P2L_SECT_MASK) == ATA_ID_P2L_SECT_VALID &&
	    ISSET(p2l_sect, ATA_ID_P2L_SECT_SET)) {
		exponent = (p2l_sect & ATA_ID_P2L_SECT_SIZE);
	}

	return (exponent);
}

u_int
ata_identify_block_logical_align(struct ata_identify *id)
{
	u_int			align = 0;
	u_int16_t		p2l_sect = letoh16(id->p2l_sect);
	u_int16_t		logical_align = letoh16(id->logical_align);

	if ((p2l_sect & ATA_ID_P2L_SECT_MASK) == ATA_ID_P2L_SECT_VALID &&
	    ISSET(p2l_sect, ATA_ID_P2L_SECT_SET) &&
	    (logical_align & ATA_ID_LALIGN_MASK) == ATA_ID_LALIGN_VALID)
		align = logical_align & ATA_ID_LALIGN;

	return (align);
}

void
atascsi_disk_capacity(struct scsi_xfer *xs)
{
	struct scsi_link	*link = xs->sc_link;
	struct atascsi_port	*ap;
	struct scsi_read_cap_data rcd;
	u_int64_t		capacity;

	ap = atascsi_lookup_port(link);
	if (xs->cmdlen != sizeof(struct scsi_read_capacity)) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	bzero(&rcd, sizeof(rcd));
	capacity = ata_identify_blocks(&ap->ap_identify);
	if (capacity > 0xffffffff)
		capacity = 0xffffffff;

	_lto4b(capacity, rcd.addr);
	_lto4b(ata_identify_blocksize(&ap->ap_identify), rcd.length);

	bcopy(&rcd, xs->data, MIN(sizeof(rcd), xs->datalen));

	atascsi_done(xs, XS_NOERROR);
}

void
atascsi_disk_capacity16(struct scsi_xfer *xs)
{
	struct scsi_link	*link = xs->sc_link;
	struct atascsi_port	*ap;
	struct scsi_read_cap_data_16 rcd;
	u_int			align;
	u_int16_t		lowest_aligned = 0;

	ap = atascsi_lookup_port(link);
	if (xs->cmdlen != sizeof(struct scsi_read_capacity_16)) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	bzero(&rcd, sizeof(rcd));

	_lto8b(ata_identify_blocks(&ap->ap_identify), rcd.addr);
	_lto4b(ata_identify_blocksize(&ap->ap_identify), rcd.length);
	rcd.logical_per_phys = ata_identify_block_l2p_exp(&ap->ap_identify);
	align = ata_identify_block_logical_align(&ap->ap_identify);
	if (align > 0)
		lowest_aligned = (1 << rcd.logical_per_phys) - align;

	if (ISSET(ap->ap_features, ATA_PORT_F_TRIM)) {
		SET(lowest_aligned, READ_CAP_16_TPE);

		if (ISSET(letoh16(ap->ap_identify.add_support),
		    ATA_ID_ADD_SUPPORT_DRT))
			SET(lowest_aligned, READ_CAP_16_TPRZ);
	}
	_lto2b(lowest_aligned, rcd.lowest_aligned);

	bcopy(&rcd, xs->data, MIN(sizeof(rcd), xs->datalen));

	atascsi_done(xs, XS_NOERROR);
}

int
atascsi_passthru_map(struct scsi_xfer *xs, u_int8_t count_proto, u_int8_t flags)
{
	struct ata_xfer		*xa = xs->io;

	xa->data = xs->data;
	xa->datalen = xs->datalen;
	xa->timeout = xs->timeout;
	xa->flags = 0;
	if (xs->flags & SCSI_DATA_IN)
		xa->flags |= ATA_F_READ;
	if (xs->flags & SCSI_DATA_OUT)
		xa->flags |= ATA_F_WRITE;
	if (xs->flags & SCSI_POLL)
		xa->flags |= ATA_F_POLL;

	switch (count_proto & ATA_PASSTHRU_PROTO_MASK) {
	case ATA_PASSTHRU_PROTO_NON_DATA:
	case ATA_PASSTHRU_PROTO_PIO_DATAIN:
	case ATA_PASSTHRU_PROTO_PIO_DATAOUT:
		xa->flags |= ATA_F_PIO;
		break;
	default:
		/* we dont support this yet */
		return (1);
	}

	xa->atascsi_private = xs;
	xa->complete = atascsi_passthru_done;

	return (0);
}

void
atascsi_passthru_12(struct scsi_xfer *xs)
{
	struct scsi_link	*link = xs->sc_link;
	struct atascsi		*as = link->adapter_softc;
	struct atascsi_port	*ap;
	struct ata_xfer		*xa = xs->io;
	struct scsi_ata_passthru_12 *cdb;
	struct ata_fis_h2d	*fis;

	if (xs->cmdlen != sizeof(*cdb)) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	cdb = (struct scsi_ata_passthru_12 *)xs->cmd;
	/* validate cdb */

	if (atascsi_passthru_map(xs, cdb->count_proto, cdb->flags) != 0) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	ap = atascsi_lookup_port(link);
	fis = xa->fis;
	fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
	fis->command = cdb->command;
	fis->features = cdb->features;
	fis->lba_low = cdb->lba_low;
	fis->lba_mid = cdb->lba_mid;
	fis->lba_high = cdb->lba_high;
	fis->device = cdb->device;
	fis->sector_count = cdb->sector_count;
	xa->pmp_port = ap->ap_pmp_port;

	ata_exec(as, xa);
}

void
atascsi_passthru_16(struct scsi_xfer *xs)
{
	struct scsi_link	*link = xs->sc_link;
	struct atascsi		*as = link->adapter_softc;
	struct atascsi_port	*ap;
	struct ata_xfer		*xa = xs->io;
	struct scsi_ata_passthru_16 *cdb;
	struct ata_fis_h2d	*fis;

	if (xs->cmdlen != sizeof(*cdb)) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	cdb = (struct scsi_ata_passthru_16 *)xs->cmd;
	/* validate cdb */

	if (atascsi_passthru_map(xs, cdb->count_proto, cdb->flags) != 0) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	ap = atascsi_lookup_port(link);
	fis = xa->fis;
	fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
	fis->command = cdb->command;
	fis->features = cdb->features[1];
	fis->lba_low = cdb->lba_low[1];
	fis->lba_mid = cdb->lba_mid[1];
	fis->lba_high = cdb->lba_high[1];
	fis->device = cdb->device;
	fis->lba_low_exp = cdb->lba_low[0];
	fis->lba_mid_exp = cdb->lba_mid[0];
	fis->lba_high_exp = cdb->lba_high[0];
	fis->features_exp = cdb->features[0];
	fis->sector_count = cdb->sector_count[1];
	fis->sector_count_exp = cdb->sector_count[0];
	xa->pmp_port = ap->ap_pmp_port;

	ata_exec(as, xa);
}

void
atascsi_passthru_done(struct ata_xfer *xa)
{
	struct scsi_xfer	*xs = xa->atascsi_private;

	/*
	 * XXX need to generate sense if cdb wants it
	 */

	switch (xa->state) {
	case ATA_S_COMPLETE:
		xs->error = XS_NOERROR;
		break;
	case ATA_S_ERROR:
		xs->error = XS_DRIVER_STUFFUP;
		break;
	case ATA_S_TIMEOUT:
		printf("atascsi_passthru_done, timeout\n");
		xs->error = XS_TIMEOUT;
		break;
	default:
		panic("atascsi_atapi_cmd_done: unexpected ata_xfer state (%d)",
		    xa->state);
	}

	xs->resid = xa->resid;

	scsi_done(xs);
}

void
atascsi_disk_sense(struct scsi_xfer *xs)
{
	struct scsi_sense_data	*sd = (struct scsi_sense_data *)xs->data;

	bzero(xs->data, xs->datalen);
	/* check datalen > sizeof(struct scsi_sense_data)? */
	sd->error_code = SSD_ERRCODE_CURRENT;
	sd->flags = SKEY_NO_SENSE;

	atascsi_done(xs, XS_NOERROR);
}

void
atascsi_disk_start_stop(struct scsi_xfer *xs)
{
	struct scsi_link	*link = xs->sc_link;
	struct atascsi		*as = link->adapter_softc;
	struct atascsi_port	*ap;
	struct ata_xfer		*xa = xs->io;
	struct scsi_start_stop	*ss = (struct scsi_start_stop *)xs->cmd;

	if (xs->cmdlen != sizeof(*ss)) {
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	if (ss->how != SSS_STOP) {
		atascsi_done(xs, XS_NOERROR);
		return;
	}

	/*
	 * A SCSI START STOP UNIT command with the START bit set to
	 * zero gets translated into an ATA FLUSH CACHE command
	 * followed by an ATA STANDBY IMMEDIATE command.
	 */
	ap = atascsi_lookup_port(link);
	xa->datalen = 0;
	xa->flags = ATA_F_READ;
	xa->complete = atascsi_disk_start_stop_done;
	/* Spec says flush cache can take >30 sec, so give it at least 45. */
	xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout;
	xa->pmp_port = ap->ap_pmp_port;
	xa->atascsi_private = xs;
	if (xs->flags & SCSI_POLL)
		xa->flags |= ATA_F_POLL;

	xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
	xa->fis->command = ATA_C_FLUSH_CACHE;
	xa->fis->device = 0;

	ata_exec(as, xa);
}

void
atascsi_disk_start_stop_done(struct ata_xfer *xa)
{
	struct scsi_xfer	*xs = xa->atascsi_private;
	struct scsi_link	*link = xs->sc_link;
	struct atascsi		*as = link->adapter_softc;
	struct atascsi_port	*ap;

	switch (xa->state) {
	case ATA_S_COMPLETE:
		break;

	case ATA_S_ERROR:
	case ATA_S_TIMEOUT:
		xs->error = (xa->state == ATA_S_TIMEOUT ? XS_TIMEOUT :
		    XS_DRIVER_STUFFUP);
		xs->resid = xa->resid;
		scsi_done(xs);
		return;

	default:
		panic("atascsi_disk_start_stop_done: unexpected ata_xfer state (%d)",
		    xa->state);
	}

	/*
	 * The FLUSH CACHE command completed succesfully; now issue
	 * the STANDBY IMMEDATE command.
	 */
	ap = atascsi_lookup_port(link);
	xa->datalen = 0;
	xa->flags = ATA_F_READ;
	xa->state = ATA_S_SETUP;
	xa->complete = atascsi_disk_cmd_done;
	/* Spec says flush cache can take >30 sec, so give it at least 45. */
	xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout;
	xa->pmp_port = ap->ap_pmp_port;
	xa->atascsi_private = xs;
	if (xs->flags & SCSI_POLL)
		xa->flags |= ATA_F_POLL;

	xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
	xa->fis->command = ATA_C_STANDBY_IMMED;
	xa->fis->device = 0;

	ata_exec(as, xa);
}

void
atascsi_atapi_cmd(struct scsi_xfer *xs)
{
	struct scsi_link	*link = xs->sc_link;
	struct atascsi		*as = link->adapter_softc;
	struct atascsi_port	*ap;
	struct ata_xfer		*xa = xs->io;
	struct ata_fis_h2d	*fis;

	switch (xs->flags & (SCSI_DATA_IN | SCSI_DATA_OUT)) {
	case SCSI_DATA_IN:
		xa->flags = ATA_F_PACKET | ATA_F_READ;
		break;
	case SCSI_DATA_OUT:
		xa->flags = ATA_F_PACKET | ATA_F_WRITE;
		break;
	default:
		xa->flags = ATA_F_PACKET;
	}
	xa->flags |= ATA_F_GET_RFIS;

	ap = atascsi_lookup_port(link);
	xa->data = xs->data;
	xa->datalen = xs->datalen;
	xa->complete = atascsi_atapi_cmd_done;
	xa->timeout = xs->timeout;
	xa->pmp_port = ap->ap_pmp_port;
	xa->atascsi_private = xs;
	if (xs->flags & SCSI_POLL)
		xa->flags |= ATA_F_POLL;

	fis = xa->fis;
	fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
	fis->command = ATA_C_PACKET;
	fis->device = 0;
	fis->sector_count = xa->tag << 3;
	fis->features = ATA_H2D_FEATURES_DMA | ((xa->flags & ATA_F_WRITE) ?
	    ATA_H2D_FEATURES_DIR_WRITE : ATA_H2D_FEATURES_DIR_READ);
	fis->lba_mid = 0x00;
	fis->lba_high = 0x20;

	/* Copy SCSI command into ATAPI packet. */
	memcpy(xa->packetcmd, xs->cmd, xs->cmdlen);

	ata_exec(as, xa);
}

void
atascsi_atapi_cmd_done(struct ata_xfer *xa)
{
	struct scsi_xfer	*xs = xa->atascsi_private;
	struct scsi_sense_data  *sd = &xs->sense;

	switch (xa->state) {
	case ATA_S_COMPLETE:
		xs->error = XS_NOERROR;
		break;
	case ATA_S_ERROR:
		/* Return PACKET sense data */
		sd->error_code = SSD_ERRCODE_CURRENT;
		sd->flags = (xa->rfis.error & 0xf0) >> 4;
		if (xa->rfis.error & 0x04)
			sd->flags = SKEY_ILLEGAL_REQUEST;
		if (xa->rfis.error & 0x02)
			sd->flags |= SSD_EOM;
		if (xa->rfis.error & 0x01)
			sd->flags |= SSD_ILI;
		xs->error = XS_SENSE;
		break;
	case ATA_S_TIMEOUT:
		printf("atascsi_atapi_cmd_done, timeout\n");
		xs->error = XS_TIMEOUT;
		break;
	default:
		panic("atascsi_atapi_cmd_done: unexpected ata_xfer state (%d)",
		    xa->state);
	}

	xs->resid = xa->resid;

	scsi_done(xs);
}

void
atascsi_pmp_cmd(struct scsi_xfer *xs)
{
	switch (xs->cmd->opcode) {
	case REQUEST_SENSE:
		atascsi_pmp_sense(xs);
		return;
	case INQUIRY:
		atascsi_pmp_inq(xs);
		return;

	case TEST_UNIT_READY:
	case PREVENT_ALLOW:
		atascsi_done(xs, XS_NOERROR);
		return;

	default:
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}
}

void
atascsi_pmp_sense(struct scsi_xfer *xs)
{
	struct scsi_sense_data *sd = (struct scsi_sense_data *)xs->data;

	bzero(xs->data, xs->datalen);
	sd->error_code = SSD_ERRCODE_CURRENT;
	sd->flags = SKEY_NO_SENSE;

	atascsi_done(xs, XS_NOERROR);
}

void
atascsi_pmp_inq(struct scsi_xfer *xs)
{
	struct scsi_inquiry_data inq;
	struct scsi_inquiry *in_inq = (struct scsi_inquiry *)xs->cmd;

	if (ISSET(in_inq->flags, SI_EVPD)) {
		/* any evpd pages we need to support here? */
		atascsi_done(xs, XS_DRIVER_STUFFUP);
		return;
	}

	bzero(&inq, sizeof(inq));
	inq.device = 0x1E;	/* "well known logical unit" seems reasonable */
	inq.version = 0x05;	/* SPC-3? */
	inq.response_format = 2;
	inq.additional_length = 32;
	inq.flags |= SID_CmdQue;
	bcopy("ATA     ", inq.vendor, sizeof(inq.vendor));

	/* should use the data from atascsi_pmp_identify here?
	 * not sure how useful the chip id is, but maybe it'd be
	 * nice to include the number of ports.
	 */
	bcopy("Port Multiplier", inq.product, sizeof(inq.product));
	bcopy("    ", inq.revision, sizeof(inq.revision));

	bcopy(&inq, xs->data, MIN(sizeof(inq), xs->datalen));
	atascsi_done(xs, XS_NOERROR);
}

void
atascsi_done(struct scsi_xfer *xs, int error)
{
	xs->error = error;
	scsi_done(xs);
}

void
ata_exec(struct atascsi *as, struct ata_xfer *xa)
{
	as->as_methods->ata_cmd(xa);
}

void *
atascsi_io_get(void *cookie)
{
	struct atascsi_host_port	*ahp = cookie;
	struct atascsi			*as = ahp->ahp_as;
	struct ata_xfer			*xa;

	xa = as->as_methods->ata_get_xfer(as->as_cookie, ahp->ahp_port);
	if (xa != NULL)
		xa->fis->type = ATA_FIS_TYPE_H2D;

	return (xa);
}

void
atascsi_io_put(void *cookie, void *io)
{
	struct atascsi_host_port	*ahp = cookie;
	struct atascsi			*as = ahp->ahp_as;
	struct ata_xfer			*xa = io;

	xa->state = ATA_S_COMPLETE; /* XXX this state machine is dumb */
	as->as_methods->ata_put_xfer(xa);
}

void
ata_polled_complete(struct ata_xfer *xa)
{
	/* do nothing */
}

int
ata_polled(struct ata_xfer *xa)
{
	int			rv;

	if (!ISSET(xa->flags, ATA_F_DONE))
		panic("ata_polled: xa isnt complete");

	switch (xa->state) {
	case ATA_S_COMPLETE:
		rv = 0;
		break;
	case ATA_S_ERROR:
	case ATA_S_TIMEOUT:
		rv = EIO;
		break;
	default:
		panic("ata_polled: xa state (%d)",
		    xa->state);
	}

	scsi_io_put(xa->atascsi_private, xa);

	return (rv);
}

void
ata_complete(struct ata_xfer *xa)
{
	SET(xa->flags, ATA_F_DONE);
	xa->complete(xa);
}

void
ata_swapcopy(void *src, void *dst, size_t len)
{
	u_int16_t *s = src, *d = dst;
	int i;

	len /= 2;

	for (i = 0; i < len; i++)
		d[i] = swap16(s[i]);
}

int
atascsi_port_identify(struct atascsi_port *ap, struct ata_identify *identify)
{
	struct atascsi			*as = ap->ap_as;
	struct atascsi_host_port	*ahp = ap->ap_host_port;
	struct ata_xfer			*xa;

	xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP);
	if (xa == NULL)
		panic("no free xfers on a new port");
	xa->pmp_port = ap->ap_pmp_port;
	xa->data = identify;
	xa->datalen = sizeof(*identify);
	xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
	xa->fis->command = (ap->ap_type == ATA_PORT_T_DISK) ?
	    ATA_C_IDENTIFY : ATA_C_IDENTIFY_PACKET;
	xa->fis->device = 0;
	xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL;
	xa->timeout = 1000;
	xa->complete = ata_polled_complete;
	xa->atascsi_private = &ahp->ahp_iopool;
	ata_exec(as, xa);
	return (ata_polled(xa));
}

int
atascsi_port_set_features(struct atascsi_port *ap, int subcommand, int arg)
{
	struct atascsi			*as = ap->ap_as;
	struct atascsi_host_port	*ahp = ap->ap_host_port;
	struct ata_xfer			*xa;

	xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP);
	if (xa == NULL)
		panic("no free xfers on a new port");
	xa->fis->command = ATA_C_SET_FEATURES;
	xa->fis->features = subcommand;
	xa->fis->sector_count = arg;
	xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
	xa->flags = ATA_F_POLL;
	xa->timeout = 1000;
	xa->complete = ata_polled_complete;
	xa->pmp_port = ap->ap_pmp_port;
	xa->atascsi_private = &ahp->ahp_iopool;
	ata_exec(as, xa);
	return (ata_polled(xa));
}


code:
/*	$OpenBSD: atascsi.h,v 1.49 2015/05/15 10:54:26 dlg Exp $ */

/*
 * Copyright (c) 2007 David Gwynne <dlg@openbsd.org>
 * Copyright (c) 2010 Conformal Systems LLC <info@conformal.com>
 * Copyright (c) 2010 Jonathan Matthew <jonathan@d14n.org>
 *
 * 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.
 */

#ifndef _DEV_ATA_ATASCSI_H_
#define _DEV_ATA_ATASCSI_H_

#include <sys/task.h>

struct atascsi;
struct scsi_link;

/*
 * ATA commands
 */

#define ATA_C_READDMA_EXT	0x25
#define ATA_C_READ_LOG_EXT	0x2f
#define ATA_C_WRITEDMA_EXT	0x35
#define ATA_C_READ_FPDMA	0x60
#define ATA_C_WRITE_FPDMA	0x61
#define ATA_C_PACKET		0xa0
#define ATA_C_IDENTIFY_PACKET	0xa1
#define ATA_C_READDMA		0xc8
#define ATA_C_WRITEDMA		0xca
#define ATA_C_STANDBY_IMMED	0xe0
#define ATA_C_READ_PM		0xe4
#define ATA_C_WRITE_PM		0xe8
#define ATA_C_FLUSH_CACHE	0xe7
#define ATA_C_FLUSH_CACHE_EXT	0xea /* lba48 */
#define ATA_C_IDENTIFY		0xec
#define ATA_C_SET_FEATURES	0xef
#define ATA_C_SEC_FREEZE_LOCK	0xf5
#define ATA_C_DSM		0x06

/*
 * ATA SET FEATURES subcommands
 */
#define ATA_SF_WRITECACHE_EN	0x02
#define ATA_SF_LOOKAHEAD_EN	0xaa

struct ata_identify {
	u_int16_t	config;		/*   0 */
	u_int16_t	ncyls;		/*   1 */
	u_int16_t	reserved1;	/*   2 */
	u_int16_t	nheads;		/*   3 */
	u_int16_t	track_size;	/*   4 */
	u_int16_t	sector_size;	/*   5 */
	u_int16_t	nsectors;	/*   6 */
	u_int16_t	reserved2[3];	/*   7 vendor unique */
	u_int8_t	serial[20];	/*  10 */
	u_int16_t	buffer_type;	/*  20 */
	u_int16_t	buffer_size;	/*  21 */
	u_int16_t	ecc;		/*  22 */
	u_int8_t	firmware[8];	/*  23 */
	u_int8_t	model[40];	/*  27 */
	u_int16_t	multi;		/*  47 */
	u_int16_t	dwcap;		/*  48 */
	u_int16_t	cap;		/*  49 */
	u_int16_t	reserved3;	/*  50 */
	u_int16_t	piomode;	/*  51 */
	u_int16_t	dmamode;	/*  52 */
	u_int16_t	validinfo;	/*  53 */
	u_int16_t	curcyls;	/*  54 */
	u_int16_t	curheads;	/*  55 */
	u_int16_t	cursectrk;	/*  56 */
	u_int16_t	curseccp[2];	/*  57 */
	u_int16_t	mult2;		/*  59 */
	u_int16_t	addrsec[2];	/*  60 */
	u_int16_t	worddma;	/*  62 */
	u_int16_t	dworddma;	/*  63 */
	u_int16_t	advpiomode;	/*  64 */
	u_int16_t	minmwdma;	/*  65 */
	u_int16_t	recmwdma;	/*  66 */
	u_int16_t	minpio;		/*  67 */
	u_int16_t	minpioflow;	/*  68 */
	u_int16_t	add_support;	/*  69 */
#define ATA_ID_ADD_SUPPORT_DRT	0x4000
	u_int16_t	reserved4;	/*  70 */
	u_int16_t	typtime[2];	/*  71 */
	u_int16_t	reserved5[2];	/*  73 */
	u_int16_t	qdepth;		/*  75 */
#define ATA_QDEPTH(_q)		(((_q) & 0x1f) + 1)
	u_int16_t	satacap;	/*  76 */
#define ATA_SATACAP_GEN1	0x0002
#define ATA_SATACAP_GEN2	0x0004
#define ATA_SATACAP_NCQ		0x0100
#define ATA_SATACAP_PWRMGMT	0x0200
	u_int16_t	reserved6;	/*  77 */
	u_int16_t	satafsup;	/*  78 */
	u_int16_t	satafen;	/*  79 */
	u_int16_t	majver;		/*  80 */
	u_int16_t	minver;		/*  81 */
	u_int16_t	cmdset82;	/*  82 */
	u_int16_t	cmdset83;	/*  83 */
	u_int16_t	cmdset84;	/*  84 */
	u_int16_t	features85;	/*  85 */
	u_int16_t	features86;	/*  86 */
	u_int16_t	features87;	/*  87 */
#define ATA_ID_F87_WWN		(1<<8)
	u_int16_t	ultradma;	/*  88 */
	u_int16_t	erasetime;	/*  89 */
	u_int16_t	erasetimex;	/*  90 */
	u_int16_t	apm;		/*  91 */
	u_int16_t	masterpw;	/*  92 */
	u_int16_t	hwreset;	/*  93 */
	u_int16_t	acoustic;	/*  94 */
	u_int16_t	stream_min;	/*  95 */
	u_int16_t	stream_xfer_d;	/*  96 */
	u_int16_t	stream_lat;	/*  97 */
	u_int16_t	streamperf[2];	/*  98 */
	u_int16_t	addrsecxt[4];	/* 100 */
	u_int16_t	stream_xfer_p;	/* 104 */
	u_int16_t	padding1;	/* 105 */
	u_int16_t	p2l_sect;	/* 106 */
#define ATA_ID_P2L_SECT_MASK	0xc000
#define ATA_ID_P2L_SECT_VALID	0x4000
#define ATA_ID_P2L_SECT_SET	0x2000
#define ATA_ID_P2L_SECT_SIZESET	0x1000
#define ATA_ID_P2L_SECT_SIZE	0x000f
	u_int16_t	seek_delay;	/* 107 */
	u_int16_t	naa_ieee_oui;	/* 108 */
	u_int16_t	ieee_oui_uid;	/* 109 */
	u_int16_t	uid_mid;	/* 110 */
	u_int16_t	uid_low;	/* 111 */
	u_int16_t	resv_wwn[4];	/* 112 */
	u_int16_t	incits;		/* 116 */
	u_int16_t	words_lsec[2];	/* 117 */
	u_int16_t	cmdset119;	/* 119 */
	u_int16_t	features120;	/* 120 */
	u_int16_t	padding2[6];
	u_int16_t	rmsn;		/* 127 */
	u_int16_t	securestatus;	/* 128 */
	u_int16_t	vendor[31];	/* 129 */
	u_int16_t	padding3[8];	/* 160 */
	u_int16_t	form;		/* 168 */
#define ATA_ID_FORM_MASK	0x000f
	u_int16_t	data_set_mgmt;	/* 169 */
#define ATA_ID_DATA_SET_MGMT_TRIM 0x0001
	u_int16_t	padding4[6];	/* 170 */
	u_int16_t	curmedser[30];	/* 176 */
	u_int16_t	sctsupport;	/* 206 */
	u_int16_t	rpm;		/* 207 */
	u_int16_t	padding5[1];	/* 208 */
	u_int16_t	logical_align;	/* 209 */
#define ATA_ID_LALIGN_MASK	0xc000
#define ATA_ID_LALIGN_VALID	0x4000
#define ATA_ID_LALIGN		0x3fff
	u_int16_t	padding6[45];	/* 210 */
	u_int16_t	integrity;	/* 255 */
} __packed;

/*
 * IDENTIFY DEVICE data
 */
#define ATA_IDENTIFY_WRITECACHE		(1 << 5)
#define ATA_IDENTIFY_LOOKAHEAD		(1 << 6)

/*
 * ATA DSM (Data Set Management) subcommands
 */
#define ATA_DSM_TRIM		0x01

#define ATA_DSM_TRIM_DESC(_lba, _len)	((_lba) | ((u_int64_t)(_len) << 48))
#define ATA_DSM_TRIM_MAX_LEN		0xffff

/*
 * Frame Information Structures
 */

#define ATA_FIS_LENGTH		20

struct ata_fis_h2d {
	u_int8_t		type;
#define ATA_FIS_TYPE_H2D		0x27
	u_int8_t		flags;
#define ATA_H2D_FLAGS_CMD		(1<<7)
	u_int8_t		command;
	u_int8_t		features;
#define ATA_H2D_FEATURES_DMA		(1<<0)
#define ATA_H2D_FEATURES_DIR		(1<<2)
#define ATA_H2D_FEATURES_DIR_READ	(1<<2)
#define ATA_H2D_FEATURES_DIR_WRITE	(0<<2)

	u_int8_t		lba_low;
	u_int8_t		lba_mid;
	u_int8_t		lba_high;
	u_int8_t		device;
#define ATA_H2D_DEVICE_LBA		0x40

	u_int8_t		lba_low_exp;
	u_int8_t		lba_mid_exp;
	u_int8_t		lba_high_exp;
	u_int8_t		features_exp;

	u_int8_t		sector_count;
	u_int8_t		sector_count_exp;
	u_int8_t		reserved0;
	u_int8_t		control;
#define ATA_FIS_CONTROL_SRST		0x04
#define ATA_FIS_CONTROL_4BIT		0x08

	u_int8_t		reserved1;
	u_int8_t		reserved2;
	u_int8_t		reserved3;
	u_int8_t		reserved4;
} __packed;

struct ata_fis_d2h {
	u_int8_t		type;
#define ATA_FIS_TYPE_D2H		0x34
	u_int8_t		flags;
#define ATA_D2H_FLAGS_INTR		(1<<6)
	u_int8_t		status;
	u_int8_t		error;

	u_int8_t		lba_low;
	u_int8_t		lba_mid;
	u_int8_t		lba_high;
	u_int8_t		device;

	u_int8_t		lba_low_exp;
	u_int8_t		lba_mid_exp;
	u_int8_t		lba_high_exp;
	u_int8_t		reserved0;

	u_int8_t		sector_count;
	u_int8_t		sector_count_exp;
	u_int8_t		reserved1;
	u_int8_t		reserved2;

	u_int8_t		reserved3;
	u_int8_t		reserved4;
	u_int8_t		reserved5;
	u_int8_t		reserved6;
} __packed;

/*
 * SATA log page 10h -
 * looks like a D2H FIS, with errored tag number in first byte.
 */
struct ata_log_page_10h {
	struct ata_fis_d2h	err_regs;
#define ATA_LOG_10H_TYPE_NOTQUEUED	0x80
#define ATA_LOG_10H_TYPE_TAG_MASK	0x1f
	u_int8_t		reserved[256 - sizeof(struct ata_fis_d2h)];
	u_int8_t		vendor_specific[255];
	u_int8_t		checksum;
} __packed;

/*
 * SATA registers
 */

#define SATA_SStatus_DET		0x00f
#define SATA_SStatus_DET_NODEV		0x000
#define SATA_SStatus_DET_NOPHY		0x001
#define SATA_SStatus_DET_DEV		0x003
#define SATA_SStatus_DET_OFFLINE	0x008

#define SATA_SStatus_SPD		0x0f0
#define SATA_SStatus_SPD_NONE		0x000
#define SATA_SStatus_SPD_1_5		0x010
#define SATA_SStatus_SPD_3_0		0x020

#define SATA_SStatus_IPM		0xf00
#define SATA_SStatus_IPM_NODEV		0x000
#define SATA_SStatus_IPM_ACTIVE		0x100
#define SATA_SStatus_IPM_PARTIAL	0x200
#define SATA_SStatus_IPM_SLUMBER	0x600

#define SATA_SIGNATURE_PORT_MULTIPLIER	0x96690101
#define SATA_SIGNATURE_ATAPI		0xeb140101
#define SATA_SIGNATURE_DISK		0x00000101

/*
 * ATA interface
 */

struct ata_xfer {
	struct ata_fis_h2d	*fis;
	struct ata_fis_d2h	rfis;
	u_int8_t		*packetcmd;
	u_int8_t		tag;

	void			*data;
	size_t			datalen;
	size_t			resid;

	void			(*complete)(struct ata_xfer *);
	struct task		task;
	struct timeout		stimeout;
	u_int			timeout;

	int			flags;
#define ATA_F_READ			(1<<0)
#define ATA_F_WRITE			(1<<1)
#define ATA_F_NOWAIT			(1<<2)
#define ATA_F_POLL			(1<<3)
#define ATA_F_PIO			(1<<4)
#define ATA_F_PACKET			(1<<5)
#define ATA_F_NCQ			(1<<6)
#define ATA_F_DONE			(1<<7)
#define ATA_F_GET_RFIS			(1<<8)
#define ATA_FMT_FLAGS			"\020" "\011GET_RFIS" "\010DONE" \
					"\007NCQ" "\006PACKET" "\005PIO" \
					"\004POLL" "\003NOWAIT" "\002WRITE" \
					"\001READ"

	volatile int		state;
#define ATA_S_SETUP			0
#define ATA_S_PENDING			1
#define ATA_S_COMPLETE			2
#define ATA_S_ERROR			3
#define ATA_S_TIMEOUT			4
#define ATA_S_ONCHIP			5
#define ATA_S_PUT			6
#define ATA_S_DONE			7

	void			*atascsi_private;

	int			pmp_port;
};

/*
 * atascsi
 */

struct atascsi_methods {
	int			(*ata_probe)(void *, int, int);
	void			(*ata_free)(void *, int, int);
	struct ata_xfer *	(*ata_get_xfer)(void *, int);
	void			(*ata_put_xfer)(struct ata_xfer *);
	void			(*ata_cmd)(struct ata_xfer *);
};

struct atascsi_attach_args {
	void			*aaa_cookie;

	struct atascsi_methods	*aaa_methods;
	void			(*aaa_minphys)(struct buf *,
				    struct scsi_link *);
	int			aaa_nports;
	int			aaa_ncmds;
	int			aaa_capability;
#define ASAA_CAP_NCQ		(1 << 0)
#define ASAA_CAP_NEEDS_RESERVED	(1 << 1)
#define ASAA_CAP_PMP_NCQ	(1 << 2)
};

#define ATA_PORT_T_NONE		0
#define ATA_PORT_T_DISK		1
#define ATA_PORT_T_ATAPI	2
#define ATA_PORT_T_PM		3

struct atascsi	*atascsi_attach(struct device *, struct atascsi_attach_args *);
int		atascsi_detach(struct atascsi *, int);

int		atascsi_probe_dev(struct atascsi *, int, int);
int		atascsi_detach_dev(struct atascsi *, int, int, int);

void		ata_complete(struct ata_xfer *);

#endif /* _DEV_ATA_ATASCSI_H_ */


code:
/*	$OpenBSD: atavar.h,v 1.20 2011/04/15 20:53:28 miod Exp $	*/
/*	$NetBSD: atavar.h,v 1.13 1999/03/10 13:11:43 bouyer Exp $	*/

/*
 * Copyright (c) 1998, 2001 Manuel Bouyer.
 *
 * 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.
 *
 */

#ifndef _DEV_ATA_ATAVAR_H_
#define _DEV_ATA_ATAVAR_H_

/* High-level functions and structures used by both ATA and ATAPI devices */
#include <dev/ata/atareg.h>

/* Datas common to drives and controller drivers */
struct ata_drive_datas {
	u_int8_t drive; /* drive number */
	int8_t ata_vers; /* ATA version supported */
	u_int16_t drive_flags; /* bitmask for drives present/absent and cap */
#define DRIVE_ATA	0x0001
#define DRIVE_ATAPI	0x0002
#define DRIVE_OLD	0x0004
#define DRIVE (DRIVE_ATA|DRIVE_ATAPI|DRIVE_OLD)
#define DRIVE_CAP32	0x0008
#define DRIVE_DMA	0x0010
#define DRIVE_UDMA	0x0020
#define DRIVE_MODE	0x0040 /* the drive reported its mode */
#define DRIVE_RESET	0x0080 /* reset the drive state at next xfer */
#define DRIVE_DMAERR	0x0100 /* Udma transfer had crc error, don't try DMA */
#define DRIVE_DSCBA	0x0200 /* DSC in buffer availability mode */
#define DRIVE_DSCWAIT	0x0400 /* In wait for DSC to be asserted */
#define DRIVE_DEVICE_RESET 0x0800 /* Drive supports DEVICE RESET command */
#define DRIVE_SATA	0x1000 /* SATA drive */
	/*
	 * Current setting of drive's PIO, DMA and UDMA modes.
	 * Is initialised by the disks drivers at attach time, and may be
	 * changed later by the controller's code if needed
	 */
	u_int8_t PIO_mode; /* Current setting of drive's PIO mode */
	u_int8_t DMA_mode; /* Current setting of drive's DMA mode */
	u_int8_t UDMA_mode; /* Current setting of drive's UDMA mode */
	/* Supported modes for this drive */
	u_int8_t PIO_cap; /* supported drive's PIO mode */
	u_int8_t DMA_cap; /* supported drive's DMA mode */
	u_int8_t UDMA_cap; /* supported drive's UDMA mode */
	/*
	 * Drive state. This is drive-type (ATA or ATAPI) dependant
	 * This is reset to 0 after a channel reset.
	 */
	u_int8_t state;

#define ACAP_LEN            0x01  /* 16 byte commands */
#define ACAP_DSC            0x02  /* use DSC signalling */
	/* 0x20-0x40 reserved for ATAPI_CFG_DRQ_MASK */
	u_int8_t atapi_cap;

	/* Keeps track of the number of resets that have occurred in a row
	   without a successful command completion. */
	u_int8_t n_resets;
	u_int8_t n_dmaerrs;
	u_int32_t n_xfers;
#define NERRS_MAX 4
#define NXFER 1000

	char drive_name[31];
	int  cf_flags;
	void *chnl_softc; /* channel softc */

	struct ataparams id;
};

/* ATA/ATAPI common attachement datas */
struct ata_atapi_attach {
    u_int8_t aa_type; /* Type of device */
#define T_ATA 0
#define T_ATAPI 1
    u_int8_t aa_channel; /* controller's channel */
    u_int8_t aa_openings; /* Number of simultaneous commands possible */
    struct ata_drive_datas *aa_drv_data;
    void *aa_bus_private; /* infos specifics to this bus */
};

/* User config flags that force (or disable) the use of a mode */
#define ATA_CONFIG_PIO_MODES	0x0007
#define ATA_CONFIG_PIO_SET	0x0008
#define ATA_CONFIG_PIO_OFF	0
#define ATA_CONFIG_DMA_MODES	0x0070
#define ATA_CONFIG_DMA_SET	0x0080
#define ATA_CONFIG_DMA_DISABLE	0x0070
#define ATA_CONFIG_DMA_OFF	4
#define ATA_CONFIG_UDMA_MODES	0x0700
#define ATA_CONFIG_UDMA_SET	0x0800
#define ATA_CONFIG_UDMA_DISABLE	0x0700
#define ATA_CONFIG_UDMA_OFF	8

/*
 * ATA/ATAPI commands description
 *
 * This structure defines the interface between the ATA/ATAPI device driver
 * and the controller for short commands. It contains the command's parameter,
 * the len of data's to read/write (if any), and a function to call upon
 * completion.
 * If no sleep is allowed, the driver can poll for command completion.
 * Once the command completed, if the error registed is valid, the flag
 * AT_ERROR is set and the error register value is copied to r_error .
 * A separate interface is needed for read/write or ATAPI packet commands
 * (which need multiple interrupts per commands).
 */
struct wdc_command {
    u_int8_t r_command;  /* Parameters to upload to registers */
    u_int8_t r_head;
    u_int16_t r_cyl;
    u_int8_t r_sector;
    u_int8_t r_count;
    u_int8_t r_precomp;
    u_int8_t r_st_bmask; /* status register mask to wait for before command */
    u_int8_t r_st_pmask; /* status register mask to wait for after command */
    u_int8_t r_error;    /* error register after command done */
    volatile u_int16_t flags;
#define AT_READ     0x0001 /* There is data to read */
#define AT_WRITE    0x0002 /* There is data to write (excl. with AT_READ) */
#define AT_WAIT     0x0008 /* wait in controller code for command completion */
#define AT_POLL     0x0010 /* poll for command completion (no interrupts) */
#define AT_DONE     0x0020 /* command is done */
#define AT_ERROR    0x0040 /* command is done with error */
#define AT_TIMEOU   0x0080 /* command timed out */
#define AT_DF       0x0100 /* Drive fault */
#define AT_READREG  0x0200 /* Read registers on completion */
    int timeout;         /* timeout (in ms) */
    void *data;          /* Data buffer address */
    int bcount;          /* number of bytes to transfer */
    void (*callback)(void *); /* command to call once command completed */
    void *callback_arg;  /* argument passed to *callback() */
};

extern int at_poll;

int wdc_exec_command(struct ata_drive_datas *, struct wdc_command*);
#define WDC_COMPLETE  0x01
#define WDC_QUEUED    0x02
#define WDC_TRY_AGAIN 0x03

void wdc_probe_caps(struct ata_drive_datas*, struct ataparams *);
void wdc_print_caps(struct ata_drive_datas*);
int  wdc_downgrade_mode(struct ata_drive_datas*);

void wdc_reset_channel(struct ata_drive_datas *, int);

int wdc_ata_addref(struct ata_drive_datas *);
void wdc_ata_delref(struct ata_drive_datas *);
void wdc_ata_kill_pending(struct ata_drive_datas *);

int ata_get_params(struct ata_drive_datas*, u_int8_t,
	struct ataparams *);
int ata_set_mode(struct ata_drive_datas*, u_int8_t, u_int8_t);
/* return code for these cmds */
#define CMD_OK    0
#define CMD_ERR   1
#define CMD_AGAIN 2

void ata_dmaerr(struct ata_drive_datas *);
void ata_perror(struct ata_drive_datas *, int, char *, size_t);

#endif	/* !_DEV_ATA_ATAVAR_H_ */