code:
#	$OpenBSD: files.ata,v 1.3 2007/02/19 11:44:24 dlg Exp $
#	$NetBSD: files.ata,v 1.3 1998/10/12 16:09:16 bouyer Exp $
#
# Config file and device description for machine-independent devices
# which attach to ATA busses.  Included by ports that need it.  Ports
# that use it must provide their own "major" declarations for the
# appropriate devices.

# ATA disks
device	wd: disk
attach	wd at ata
file	dev/ata/wd.c				wd needs-flag
file	dev/ata/ata_wdc.c			wd & wdc_base

file	dev/ata/ata.c				(ata | atapi) & wdc_base

file	dev/ata/atascsi.c			atascsi


code:
/*
 * Copyright (c) 2009 The DragonFly Project.  All rights reserved.
 *
 * This code is derived from software contributed to The DragonFly Project
 * by Matthew Dillon <dillon@backplane.com>
 *
 * 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.
 * 3. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
 * COPYRIGHT HOLDERS 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.
 */

/*
 * SATA port multiplier registers
 */
#define SATA_PMREG_SSTS		0	/* use SATA_PM_SSTS_ bit defs */
#define SATA_PMREG_SERR		1	/* use SATA_PM_SERR_ bit defs */
#define SATA_PMREG_SCTL		2	/* use SATA_PM_SCTL_ bit defs */
#define SATA_PMREG_SACT		3	/* (not implemented on PM) */

#define  SATA_PM_SSTS_DET		0xf /* Device Detection */
#define  SATA_PM_SSTS_DET_NONE		0x0
#define  SATA_PM_SSTS_DET_DEV_NE	0x1
#define  SATA_PM_SSTS_DET_DEV		0x3
#define  SATA_PM_SSTS_DET_PHYOFFLINE	0x4
#define  SATA_PM_SSTS_SPD		0xf0 /* Current Interface Speed */
#define  SATA_PM_SSTS_SPD_NONE		0x00
#define  SATA_PM_SSTS_SPD_GEN1		0x10
#define  SATA_PM_SSTS_SPD_GEN2		0x20
#define  SATA_PM_SSTS_IPM		0xf00 /* Interface Power Management */
#define  SATA_PM_SSTS_IPM_NONE		0x000
#define  SATA_PM_SSTS_IPM_ACTIVE	0x100
#define  SATA_PM_SSTS_IPM_PARTIAL	0x200
#define  SATA_PM_SSTS_IPM_SLUMBER	0x600

#define  SATA_PM_SCTL_DET		0xf /* Device Detection */
#define  SATA_PM_SCTL_DET_NONE		0x0
#define  SATA_PM_SCTL_DET_INIT		0x1
#define  SATA_PM_SCTL_DET_DISABLE	0x4
#define  SATA_PM_SCTL_SPD		0xf0 /* Speed Allowed */
#define  SATA_PM_SCTL_SPD_ANY		0x00
#define  SATA_PM_SCTL_SPD_GEN1		0x10
#define  SATA_PM_SCTL_SPD_GEN2		0x20
#define  SATA_PM_SCTL_IPM		0xf00 /* Interface Power Management */
#define  SATA_PM_SCTL_IPM_NONE		0x000
#define  SATA_PM_SCTL_IPM_NOPARTIAL	0x100
#define  SATA_PM_SCTL_IPM_NOSLUMBER	0x200
#define  SATA_PM_SCTL_IPM_DISABLED	0x300
#define	 SATA_PM_SCTL_SPM		0xf000	/* Select Power Management */
#define	 SATA_PM_SCTL_SPM_NONE		0x0000
#define	 SATA_PM_SCTL_SPM_NOPARTIAL	0x1000
#define	 SATA_PM_SCTL_SPM_NOSLUMBER	0x2000
#define	 SATA_PM_SCTL_SPM_DISABLED	0x3000
#define  SATA_PM_SCTL_PMP		0xf0000	/* Set PM port for xmit FISes */
#define  SATA_PM_SCTL_PMP_SHIFT		16

#define  SATA_PM_SERR_ERR_I		(1<<0) /* Recovered Data Integrity */
#define  SATA_PM_SERR_ERR_M		(1<<1) /* Recovered Communications */
#define  SATA_PM_SERR_ERR_T		(1<<8) /* Transient Data Integrity */
#define  SATA_PM_SERR_ERR_C		(1<<9) /* Persistent Comm/Data */
#define  SATA_PM_SERR_ERR_P		(1<<10) /* Protocol */
#define  SATA_PM_SERR_ERR_E		(1<<11) /* Internal */
#define  SATA_PM_SERR_DIAG_N		(1<<16) /* PhyRdy Change */
#define  SATA_PM_SERR_DIAG_I		(1<<17) /* Phy Internal Error */
#define  SATA_PM_SERR_DIAG_W		(1<<18) /* Comm Wake */
#define  SATA_PM_SERR_DIAG_B		(1<<19) /* 10B to 8B Decode Error */
#define  SATA_PM_SERR_DIAG_D		(1<<20) /* Disparity Error */
#define  SATA_PM_SERR_DIAG_C		(1<<21) /* CRC Error */
#define  SATA_PM_SERR_DIAG_H		(1<<22) /* Handshake Error */
#define  SATA_PM_SERR_DIAG_S		(1<<23) /* Link Sequence Error */
#define  SATA_PM_SERR_DIAG_T		(1<<24) /* Transport State Trans Err */
#define  SATA_PM_SERR_DIAG_F		(1<<25) /* Unknown FIS Type */
#define  SATA_PM_SERR_DIAG_X		(1<<26) /* Exchanged */

#define  SATA_PFMT_SERR	"\020" 	\
			"\033DIAG.X" "\032DIAG.F" "\031DIAG.T" "\030DIAG.S" \
			"\027DIAG.H" "\026DIAG.C" "\025DIAG.D" "\024DIAG.B" \
			"\023DIAG.W" "\022DIAG.I" "\021DIAG.N"		    \
			"\014ERR.E" "\013ERR.P" "\012ERR.C" "\011ERR.T"	    \
			"\002ERR.M" "\001ERR.I"

/*
 * AHCI port multiplier revision information SCR[1] (see ahci_pm_read)
 *
 * Rev 1.1 is the one that should support async notification.
 */
#define SATA_PMREV_PM1_0	0x00000002
#define SATA_PMREV_PM1_1	0x00000004
#define SATA_PFMT_PM_REV	"\20" "\003PM1.1" "\002PM1.0"

/*
 * GSCR[64] and GSCR[96] - Port Multiplier features available and features
 *			   enabled.
 */
#define SATA_PMREG_FEA		64
#define SATA_PMREG_FEAEN	96		/* (features enabled) */
#define SATA_PMFEA_BIST		0x00000001	/* BIST Support */
#define SATA_PMFEA_PMREQ	0x00000002	/* Can issue PMREQp to host */
#define SATA_PMFEA_DYNSSC	0x00000004	/* Dynamic SSC transmit enab */
#define SATA_PMFEA_ASYNCNOTIFY	0x00000008	/* Async notification */

#define SATA_PFMT_PM_FEA	"\20"			\
				"\004AsyncNotify"	\
				"\003DynamicSSC"	\
				"\002PMREQ"		\
				"\001BIST"

/*
 * Enable generation of async notify events for individual targets
 * via the PMEENA register.  Each bit in PMEINFO is a wire-or of all
 * SERROR bits for that target.  To enable a new notification event
 * the SERROR bits in PMSERROR_REGNO must be cleared.
 */
#define SATA_PMREG_EINFO	32		/* error info 16 ports */
#define SATA_PMREG_EEENA	33		/* error info enable 16 ports */

#define SATA_PMP_MAX_PORTS	16

#define SATA_PMP_CONTROL_PORT	0x0f

license: unknown


code:
/*	$OpenBSD: satareg.h,v 1.2 2008/06/26 05:42:14 ray Exp $	*/
/*	$NetBSD: satareg.h,v 1.3 2004/05/23 23:07:59 wiz Exp $	*/

/*-
 * Copyright (c) 2003 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe of Wasabi Systems, Inc.
 *
 * 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.
 */

#ifndef _DEV_ATA_SATAREG_H_
#define	_DEV_ATA_SATAREG_H_

/*
 * Serial ATA register definitions.
 *
 * Reference:
 *
 *	Serial ATA: High Speed Serialized AT Attachment
 *	Revision 1.0 29-August-2001
 *	Serial ATA Working Group
 */

/*
 * SStatus (SCR0) --
 *	Serial ATA interface status register
 */
	/*
	 * The DET value indicates the interface device detection and
	 * PHY state.
	 */
#define	SStatus_DET_NODEV	(0x0 << 0)	/* no device connected */
#define	SStatus_DET_DEV_NE	(0x1 << 0)	/* device, but PHY comm not
						   established */
#define	SStatus_DET_DEV		(0x3 << 0)	/* device, PHY comm
						   established */
#define	SStatus_DET_OFFLINE	(0x4 << 0)	/* PHY in offline mode */
#define	SStatus_DET_mask	(0xf << 0)
#define	SStatus_DET_shift	0
	/*
	 * The SPD value indicates the negotiated interface communication
	 * speed established.
	 */
#define	SStatus_SPD_NONE	(0x0 << 4)	/* no negotiated speed */
#define	SStatus_SPD_G1		(0x1 << 4)	/* Generation 1 (1.5Gb/s) */
#define	SStatus_SPD_G2		(0x2 << 4)	/* Generation 2 (3.0Gb/s) */
#define	SStatus_SPD_mask	(0xf << 4)
#define	SStatus_SPD_shift	4
	/*
	 * The IPM value indicates the current interface power managemnt
	 * state.
	 */
#define	SStatus_IPM_NODEV	(0x0 << 8)	/* no device connected */
#define	SStatus_IPM_ACTIVE	(0x1 << 8)	/* ACTIVE state */
#define	SStatus_IPM_PARTIAL	(0x2 << 8)	/* PARTIAL pm state */
#define	SStatus_IPM_SLUMBER	(0x6 << 8)	/* SLUMBER pm state */
#define	SStatus_IPM_mask	(0xf << 8)
#define	SStatus_IPM_shift	8

/*
 * SError (SCR1) --
 *	Serial ATA interface error register
 */
#define	SError_ERR_I		(1U << 0)	/* Recovered data integrity
						   error */
#define	SError_ERR_M		(1U << 1)	/* Recovered communications
						   error */
#define	SError_ERR_T		(1U << 8)	/* Non-recovered transient
						   data integrity error */
#define	SError_ERR_C		(1U << 9)	/* Non-recovered persistent
						   communication or data
						   integrity error */
#define	SError_ERR_P		(1U << 10)	/* Protocol error */
#define	SError_ERR_E		(1U << 11)	/* Internal error */
#define	SError_DIAG_N		(1U << 16)	/* PhyRdy change */
#define	SError_DIAG_I		(1U << 17)	/* PHY internal error */
#define	SError_DIAG_W		(1U << 18)	/* Comm Wake */
#define	SError_DIAG_B		(1U << 19)	/* 10b to 8b decode error */
#define	SError_DIAG_D		(1U << 20)	/* Disparity error */
#define	SError_DIAG_C		(1U << 21)	/* CRC error */
#define	SError_DIAG_H		(1U << 22)	/* Handshake error */
#define	SError_DIAG_S		(1U << 23)	/* Link sequence error */
#define	SError_DIAG_T		(1U << 24)	/* Transport state transition
						   error */
#define	SError_DIAG_F		(1U << 25)	/* Unrecognized FIS type */
#define	SError_DIAG_X		(1U << 26)	/* Device Exchanged */

/*
 * SControl (SCR2) --
 *	Serial ATA interface control register
 */
	/*
	 * The DET field controls the host adapter device detection
	 * and interface initialization.
	 */
#define	SControl_DET_NONE	(0x0 << 0)	/* No device detection or
						   initialization action
						   requested */
#define	SControl_DET_INIT	(0x1 << 0)	/* Initialize interface
						   communication (equiv
						   of a hard reset) */
#define	SControl_DET_DISABLE	(0x4 << 0)	/* disable interface and
						   take PHY offline */
	/*
	 * The SPD field represents the highest allowed communication
	 * speed the interface is allowed to negotiate when communication
	 * is established.
	 */
#define	SControl_SPD_ANY	(0x0 << 4)	/* No restrictions */
#define	SControl_SPD_G1		(0x1 << 4)	/* Generation 1 (1.5Gb/s) */
#define	SControl_SPD_G2		(0x2 << 4)	/* Generation 2 (3.0Gb/s) */
	/*
	 * The IPM field represents the enabled interface power management
	 * states that can be invoked via the Serial ATA interface power
	 * management capabilities.
	 */
#define	SControl_IPM_ANY	(0x0 << 8)	/* No restrictions */
#define	SControl_IPM_NOPARTIAL	(0x1 << 8)	/* PARTIAL disabled */
#define	SControl_IPM_NOSLUMBER	(0x2 << 8)	/* SLUMBER disabled */
#define	SControl_IPM_NONE	(0x3 << 8)	/* No power management */
	/*
	 * The SPM field selects a power management state.  A non-zero
	 * value written to this field causes initiation of the selected
	 * power management state.
	 */
#define	SControl_SPM_PARTIAL	(0x1 << 12)	/* transition to PARTIAL */
#define	SControl_SPM_SLUMBER	(0x2 << 12)	/* transition to SLUBMER */
#define	SControl_SPM_ComWake	(0x4 << 12)	/* transition from PM */
	/*
	 * The PMP field identifies the selected Port Multiplier Port
	 * for accessing the SActive register.
	 */
#define	SControl_PMP(x)		((x) << 16)

#endif /* _DEV_ATA_SATAREG_H_ */


code:
/*	$OpenBSD: wd.c,v 1.117 2014/09/14 14:17:24 jsg Exp $ */
/*	$NetBSD: wd.c,v 1.193 1999/02/28 17:15:27 explorer Exp $ */

/*
 * Copyright (c) 1998, 2001 Manuel Bouyer.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *	notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *	notice, this list of conditions and the following disclaimer in the
 *	documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*-
 * 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 and by Onno van der Linden.
 *
 * 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.
 */

#if 0
#include "rnd.h"
#endif

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mutex.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/disklabel.h>
#include <sys/disk.h>
#include <sys/syslog.h>
#include <sys/timeout.h>
#include <sys/vnode.h>
#include <sys/dkio.h>
#include <sys/reboot.h>

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

#include <dev/ata/atareg.h>
#include <dev/ata/atavar.h>
#include <dev/ata/wdvar.h>
#include <dev/ic/wdcreg.h>
#include <dev/ic/wdcvar.h>
#if 0
#include "locators.h"
#endif

#define	LBA48_THRESHOLD		(0xfffffff)	/* 128GB / DEV_BSIZE */

#define	WDIORETRIES_SINGLE 4	/* number of retries before single-sector */
#define	WDIORETRIES	5	/* number of retries before giving up */
#define	RECOVERYTIME hz/2	/* time to wait before retrying a cmd */

#define DEBUG_INTR   0x01
#define DEBUG_XFERS  0x02
#define DEBUG_STATUS 0x04
#define DEBUG_FUNCS  0x08
#define DEBUG_PROBE  0x10
#ifdef WDCDEBUG
extern int wdcdebug_wd_mask; /* init'ed in ata_wdc.c */
#define WDCDEBUG_PRINT(args, level) do {	\
	if ((wdcdebug_wd_mask & (level)) != 0)	\
		printf args;			\
} while (0)
#else
#define WDCDEBUG_PRINT(args, level)
#endif

#define sc_drive sc_wdc_bio.drive
#define sc_mode sc_wdc_bio.mode
#define sc_multi sc_wdc_bio.multi

int	wdprobe(struct device *, void *, void *);
void	wdattach(struct device *, struct device *, void *);
int	wddetach(struct device *, int);
int	wdactivate(struct device *, int);
int	wdprint(void *, char *);

struct cfattach wd_ca = {
	sizeof(struct wd_softc), wdprobe, wdattach,
	wddetach, wdactivate
};

struct cfdriver wd_cd = {
	NULL, "wd", DV_DISK
};

void  wdgetdefaultlabel(struct wd_softc *, struct disklabel *);
int   wdgetdisklabel(dev_t dev, struct wd_softc *, struct disklabel *, int);
void  wdstrategy(struct buf *);
void  wdstart(void *);
void  __wdstart(struct wd_softc*, struct buf *);
void  wdrestart(void *);
int   wd_get_params(struct wd_softc *, u_int8_t, struct ataparams *);
void  wd_flushcache(struct wd_softc *, int);
void  wd_standby(struct wd_softc *, int);

/* XXX: these should go elsewhere */
cdev_decl(wd);
bdev_decl(wd);

#define wdlookup(unit) (struct wd_softc *)disk_lookup(&wd_cd, (unit))

int
wdprobe(struct device *parent, void *match_, void *aux)
{
	struct ata_atapi_attach *aa_link = aux;
	struct cfdata *match = match_;

	if (aa_link == NULL)
		return 0;
	if (aa_link->aa_type != T_ATA)
		return 0;

	if (match->cf_loc[0] != -1 &&
	    match->cf_loc[0] != aa_link->aa_channel)
		return 0;

	if (match->cf_loc[1] != -1 &&
	    match->cf_loc[1] != aa_link->aa_drv_data->drive)
		return 0;

	return 1;
}

void
wdattach(struct device *parent, struct device *self, void *aux)
{
	struct wd_softc *wd = (void *)self;
	struct ata_atapi_attach *aa_link= aux;
	struct wdc_command wdc_c;
	int i, blank;
	char buf[41], c, *p, *q;
	WDCDEBUG_PRINT(("wdattach\n"), DEBUG_FUNCS | DEBUG_PROBE);

	wd->openings = aa_link->aa_openings;
	wd->drvp = aa_link->aa_drv_data;

	strlcpy(wd->drvp->drive_name, wd->sc_dev.dv_xname,
	    sizeof(wd->drvp->drive_name));
	wd->drvp->cf_flags = wd->sc_dev.dv_cfdata->cf_flags;

	if ((NERRS_MAX - 2) > 0)
		wd->drvp->n_dmaerrs = NERRS_MAX - 2;
	else
		wd->drvp->n_dmaerrs = 0;

	/* read our drive info */
	if (wd_get_params(wd, at_poll, &wd->sc_params) != 0) {
		printf("%s: IDENTIFY failed\n", wd->sc_dev.dv_xname);
		return;
	}

	for (blank = 0, p = wd->sc_params.atap_model, q = buf, i = 0;
	    i < sizeof(wd->sc_params.atap_model); i++) {
		c = *p++;
		if (c == '\0')
			break;
		if (c != ' ') {
			if (blank) {
				*q++ = ' ';
				blank = 0;
			}
			*q++ = c;
		} else
			blank = 1;
		}
	*q++ = '\0';

	printf(": <%s>\n", buf);

	wdc_probe_caps(wd->drvp, &wd->sc_params);
	wdc_print_caps(wd->drvp);

	if ((wd->sc_params.atap_multi & 0xff) > 1) {
		wd->sc_multi = wd->sc_params.atap_multi & 0xff;
	} else {
		wd->sc_multi = 1;
	}

	printf("%s: %d-sector PIO,", wd->sc_dev.dv_xname, wd->sc_multi);

	/* use 48-bit LBA if enabled */
	/* XXX: shall we use it if drive capacity < 137Gb? */
	if ((wd->sc_params.atap_cmd2_en & ATAPI_CMD2_48AD) != 0)
		wd->sc_flags |= WDF_LBA48;

	/* Prior to ATA-4, LBA was optional. */
	if ((wd->sc_params.atap_capabilities1 & WDC_CAP_LBA) != 0)
		wd->sc_flags |= WDF_LBA;
#if 0
	/* ATA-4 requires LBA. */
	if (wd->sc_params.atap_ataversion != 0xffff &&
	    wd->sc_params.atap_ataversion >= WDC_VER_ATA4)
		wd->sc_flags |= WDF_LBA;
#endif

	if ((wd->sc_flags & WDF_LBA48) != 0) {
		wd->sc_capacity =
		    (((u_int64_t)wd->sc_params.atap_max_lba[3] << 48) |
		     ((u_int64_t)wd->sc_params.atap_max_lba[2] << 32) |
		     ((u_int64_t)wd->sc_params.atap_max_lba[1] << 16) |
		      (u_int64_t)wd->sc_params.atap_max_lba[0]);
		printf(" LBA48, %lluMB, %llu sectors\n",
		    wd->sc_capacity / (1048576 / DEV_BSIZE),
		    wd->sc_capacity);
	} else if ((wd->sc_flags & WDF_LBA) != 0) {
		wd->sc_capacity =
		    (wd->sc_params.atap_capacity[1] << 16) |
		    wd->sc_params.atap_capacity[0];
		printf(" LBA, %lluMB, %llu sectors\n",
		    wd->sc_capacity / (1048576 / DEV_BSIZE),
		    wd->sc_capacity);
	} else {
		wd->sc_capacity =
		    wd->sc_params.atap_cylinders *
		    wd->sc_params.atap_heads *
		    wd->sc_params.atap_sectors;
		printf(" CHS, %lluMB, %d cyl, %d head, %d sec, %llu sectors\n",
		    wd->sc_capacity / (1048576 / DEV_BSIZE),
		    wd->sc_params.atap_cylinders,
		    wd->sc_params.atap_heads,
		    wd->sc_params.atap_sectors,
		    wd->sc_capacity);
	}
	WDCDEBUG_PRINT(("%s: atap_dmatiming_mimi=%d, atap_dmatiming_recom=%d\n",
	    self->dv_xname, wd->sc_params.atap_dmatiming_mimi,
	    wd->sc_params.atap_dmatiming_recom), DEBUG_PROBE);

	/* use read look ahead if supported */
	if (wd->sc_params.atap_cmd_set1 & WDC_CMD1_AHEAD) {
		bzero(&wdc_c, sizeof(struct wdc_command));
		wdc_c.r_command = SET_FEATURES;
		wdc_c.r_precomp = WDSF_READAHEAD_EN;
		wdc_c.timeout = 1000;
		wdc_c.flags = at_poll;

		if (wdc_exec_command(wd->drvp, &wdc_c) != WDC_COMPLETE) {
			printf("%s: enable look ahead command didn't "
			    "complete\n", wd->sc_dev.dv_xname);
		}
	}

	/* use write cache if supported */
	if (wd->sc_params.atap_cmd_set1 & WDC_CMD1_CACHE) {
		bzero(&wdc_c, sizeof(struct wdc_command));
		wdc_c.r_command = SET_FEATURES;
		wdc_c.r_precomp = WDSF_EN_WR_CACHE;
		wdc_c.timeout = 1000;
		wdc_c.flags = at_poll;

		if (wdc_exec_command(wd->drvp, &wdc_c) != WDC_COMPLETE) {
			printf("%s: enable write cache command didn't "
			    "complete\n", wd->sc_dev.dv_xname);
		}
	}

	/*
	 * FREEZE LOCK the drive so malicous users can't lock it on us.
	 * As there is no harm in issuing this to drives that don't
	 * support the security feature set we just send it, and don't
	 * bother checking if the drive sends a command abort to tell us it
	 * doesn't support it.
	 */
	bzero(&wdc_c, sizeof(struct wdc_command));

	wdc_c.r_command = WDCC_SEC_FREEZE_LOCK;
	wdc_c.timeout = 1000;
	wdc_c.flags = at_poll;
	if (wdc_exec_command(wd->drvp, &wdc_c) != WDC_COMPLETE) {
		printf("%s: freeze lock command didn't complete\n",
		    wd->sc_dev.dv_xname);
	}

	/*
	 * Initialize disk structures.
	 */
	wd->sc_dk.dk_name = wd->sc_dev.dv_xname;
	bufq_init(&wd->sc_bufq, BUFQ_DEFAULT);
	timeout_set(&wd->sc_restart_timeout, wdrestart, wd);

	/* Attach disk. */
	disk_attach(&wd->sc_dev, &wd->sc_dk);
	wd->sc_wdc_bio.lp = wd->sc_dk.dk_label;
}

int
wdactivate(struct device *self, int act)
{
	struct wd_softc *wd = (void *)self;
	int rv = 0;

	switch (act) {
	case DVACT_SUSPEND:
		break;
	case DVACT_POWERDOWN:
		wd_flushcache(wd, AT_POLL);
		if (boothowto & RB_POWERDOWN)
			wd_standby(wd, AT_POLL);
		break;
	case DVACT_RESUME:
		/*
		 * Do two resets separated by a small delay. The
		 * first wakes the controller, the second resets
		 * the channel.
		 */
		wdc_disable_intr(wd->drvp->chnl_softc);
		wdc_reset_channel(wd->drvp, 1);
		delay(10000);
		wdc_reset_channel(wd->drvp, 0);
		wdc_enable_intr(wd->drvp->chnl_softc);
		wd_get_params(wd, at_poll, &wd->sc_params);
		break;
	}
	return (rv);
}

int
wddetach(struct device *self, int flags)
{
	struct wd_softc *sc = (struct wd_softc *)self;

	timeout_del(&sc->sc_restart_timeout);

	bufq_drain(&sc->sc_bufq);

	disk_gone(wdopen, self->dv_unit);

	/* Detach disk. */
	bufq_destroy(&sc->sc_bufq);
	disk_detach(&sc->sc_dk);

	return (0);
}

/*
 * Read/write routine for a buffer.  Validates the arguments and schedules the
 * transfer.  Does not wait for the transfer to complete.
 */
void
wdstrategy(struct buf *bp)
{
	struct wd_softc *wd;
	int s;

	wd = wdlookup(DISKUNIT(bp->b_dev));
	if (wd == NULL) {
		bp->b_error = ENXIO;
		goto bad;
	}

	WDCDEBUG_PRINT(("wdstrategy (%s)\n", wd->sc_dev.dv_xname),
	    DEBUG_XFERS);

	/* If device invalidated (e.g. media change, door open), error. */
	if ((wd->sc_flags & WDF_LOADED) == 0) {
		bp->b_error = EIO;
		goto bad;
	}

	/* Validate the request. */
	if (bounds_check_with_label(bp, wd->sc_dk.dk_label) == -1)
		goto done;

	/* Check that the number of sectors can fit in a byte. */
	if ((bp->b_bcount / wd->sc_dk.dk_label->d_secsize) >= (1 << NBBY)) {
		bp->b_error = EINVAL;
		goto bad;
	}

	/* Queue transfer on drive, activate drive and controller if idle. */
	bufq_queue(&wd->sc_bufq, bp);
	s = splbio();
	wdstart(wd);
	splx(s);
	device_unref(&wd->sc_dev);
	return;

 bad:
	bp->b_flags |= B_ERROR;
	bp->b_resid = bp->b_bcount;
 done:
	s = splbio();
	biodone(bp);
	splx(s);
	if (wd != NULL)
		device_unref(&wd->sc_dev);
}

/*
 * Queue a drive for I/O.
 */
void
wdstart(void *arg)
{
	struct wd_softc *wd = arg;
	struct buf *bp = NULL;

	WDCDEBUG_PRINT(("wdstart %s\n", wd->sc_dev.dv_xname),
	    DEBUG_XFERS);
	while (wd->openings > 0) {

		/* Is there a buf for us ? */
		if ((bp = bufq_dequeue(&wd->sc_bufq)) == NULL)
			return;
		/*
		 * Make the command. First lock the device
		 */
		wd->openings--;

		wd->retries = 0;
		__wdstart(wd, bp);
	}
}

void
__wdstart(struct wd_softc *wd, struct buf *bp)
{
	struct disklabel *lp;
	u_int64_t nsecs;

	lp = wd->sc_dk.dk_label;
	wd->sc_wdc_bio.blkno = DL_BLKTOSEC(lp, bp->b_blkno + DL_SECTOBLK(lp,
	    DL_GETPOFFSET(&lp->d_partitions[DISKPART(bp->b_dev)])));
	wd->sc_wdc_bio.blkdone =0;
	wd->sc_bp = bp;
	/*
	 * If we're retrying, retry in single-sector mode. This will give us
	 * the sector number of the problem, and will eventually allow the
	 * transfer to succeed.
	 */
	if (wd->retries >= WDIORETRIES_SINGLE)
		wd->sc_wdc_bio.flags = ATA_SINGLE;
	else
		wd->sc_wdc_bio.flags = 0;
	nsecs = howmany(bp->b_bcount, lp->d_secsize);
	if ((wd->sc_flags & WDF_LBA48) &&
	    /* use LBA48 only if really need */
	    ((wd->sc_wdc_bio.blkno + nsecs - 1 >= LBA48_THRESHOLD) ||
	     (nsecs > 0xff)))
		wd->sc_wdc_bio.flags |= ATA_LBA48;
	if (wd->sc_flags & WDF_LBA)
		wd->sc_wdc_bio.flags |= ATA_LBA;
	if (bp->b_flags & B_READ)
		wd->sc_wdc_bio.flags |= ATA_READ;
	wd->sc_wdc_bio.bcount = bp->b_bcount;
	wd->sc_wdc_bio.databuf = bp->b_data;
	wd->sc_wdc_bio.wd = wd;
	/* Instrumentation. */
	disk_busy(&wd->sc_dk);
	switch (wdc_ata_bio(wd->drvp, &wd->sc_wdc_bio)) {
	case WDC_TRY_AGAIN:
		timeout_add_sec(&wd->sc_restart_timeout, 1);
		break;
	case WDC_QUEUED:
		break;
	case WDC_COMPLETE:
		/*
		 * This code is never executed because we never set
		 * the ATA_POLL flag above
		 */
#if 0
		if (wd->sc_wdc_bio.flags & ATA_POLL)
			wddone(wd);
#endif
		break;
	default:
		panic("__wdstart: bad return code from wdc_ata_bio()");
	}
}

void
wddone(void *v)
{
	struct wd_softc *wd = v;
	struct buf *bp = wd->sc_bp;
	char buf[256], *errbuf = buf;
	WDCDEBUG_PRINT(("wddone %s\n", wd->sc_dev.dv_xname),
	    DEBUG_XFERS);

	bp->b_resid = wd->sc_wdc_bio.bcount;
	errbuf[0] = '\0';
	switch (wd->sc_wdc_bio.error) {
	case ERR_NODEV:
		bp->b_flags |= B_ERROR;
		bp->b_error = ENXIO;
		break;
	case ERR_DMA:
		errbuf = "DMA error";
		goto retry;
	case ERR_DF:
		errbuf = "device fault";
		goto retry;
	case TIMEOUT:
		errbuf = "device timeout";
		goto retry;
	case ERROR:
		/* Don't care about media change bits */
		if (wd->sc_wdc_bio.r_error != 0 &&
		    (wd->sc_wdc_bio.r_error & ~(WDCE_MC | WDCE_MCR)) == 0)
			goto noerror;
		ata_perror(wd->drvp, wd->sc_wdc_bio.r_error, errbuf,
		    sizeof buf);
retry:
		/* Just reset and retry. Can we do more ? */
		wdc_reset_channel(wd->drvp, 0);
		diskerr(bp, "wd", errbuf, LOG_PRINTF,
		    wd->sc_wdc_bio.blkdone, wd->sc_dk.dk_label);
		if (wd->retries++ < WDIORETRIES) {
			printf(", retrying\n");
			timeout_add(&wd->sc_restart_timeout, RECOVERYTIME);
			return;
		}
		printf("\n");
		bp->b_flags |= B_ERROR;
		bp->b_error = EIO;
		break;
	case NOERROR:
noerror:	if ((wd->sc_wdc_bio.flags & ATA_CORR) || wd->retries > 0)
			printf("%s: soft error (corrected)\n",
			    wd->sc_dev.dv_xname);
	}
	disk_unbusy(&wd->sc_dk, (bp->b_bcount - bp->b_resid),
	    (bp->b_flags & B_READ));
	biodone(bp);
	wd->openings++;
	wdstart(wd);
}

void
wdrestart(void *v)
{
	struct wd_softc *wd = v;
	struct buf *bp = wd->sc_bp;
	struct channel_softc *chnl;
	int s;
	WDCDEBUG_PRINT(("wdrestart %s\n", wd->sc_dev.dv_xname),
	    DEBUG_XFERS);

	chnl = (struct channel_softc *)(wd->drvp->chnl_softc);
	if (chnl->dying)
		return;

	s = splbio();
	disk_unbusy(&wd->sc_dk, 0, (bp->b_flags & B_READ));
	__wdstart(v, bp);
	splx(s);
}

int
wdread(dev_t dev, struct uio *uio, int flags)
{

	WDCDEBUG_PRINT(("wdread\n"), DEBUG_XFERS);
	return (physio(wdstrategy, dev, B_READ, minphys, uio));
}

int
wdwrite(dev_t dev, struct uio *uio, int flags)
{

	WDCDEBUG_PRINT(("wdwrite\n"), DEBUG_XFERS);
	return (physio(wdstrategy, dev, B_WRITE, minphys, uio));
}

int
wdopen(dev_t dev, int flag, int fmt, struct proc *p)
{
	struct wd_softc *wd;
	struct channel_softc *chnl;
	int unit, part;
	int error;

	WDCDEBUG_PRINT(("wdopen\n"), DEBUG_FUNCS);

	unit = DISKUNIT(dev);
	wd = wdlookup(unit);
	if (wd == NULL)
		return ENXIO;
	chnl = (struct channel_softc *)(wd->drvp->chnl_softc);
	if (chnl->dying)
		return (ENXIO);

	/*
	 * If this is the first open of this device, add a reference
	 * to the adapter.
	 */
	if ((error = disk_lock(&wd->sc_dk)) != 0)
		goto bad4;

	if (wd->sc_dk.dk_openmask != 0) {
		/*
		 * If any partition is open, but the disk has been invalidated,
		 * disallow further opens.
		 */
		if ((wd->sc_flags & WDF_LOADED) == 0) {
			error = EIO;
			goto bad3;
		}
	} else {
		if ((wd->sc_flags & WDF_LOADED) == 0) {
			wd->sc_flags |= WDF_LOADED;

			/* Load the physical device parameters. */
			wd_get_params(wd, AT_WAIT, &wd->sc_params);

			/* Load the partition info if not already loaded. */
			if (wdgetdisklabel(dev, wd,
			    wd->sc_dk.dk_label, 0) == EIO) {
				error = EIO;
				goto bad;
			}
		}
	}

	part = DISKPART(dev);

	if ((error = disk_openpart(&wd->sc_dk, part, fmt, 1)) != 0)
		goto bad;

	disk_unlock(&wd->sc_dk);
	device_unref(&wd->sc_dev);
	return 0;

bad:
	if (wd->sc_dk.dk_openmask == 0) {
	}

bad3:
	disk_unlock(&wd->sc_dk);
bad4:
	device_unref(&wd->sc_dev);
	return error;
}

int
wdclose(dev_t dev, int flag, int fmt, struct proc *p)
{
	struct wd_softc *wd;
	int part = DISKPART(dev);

	wd = wdlookup(DISKUNIT(dev));
	if (wd == NULL)
		return ENXIO;

	WDCDEBUG_PRINT(("wdclose\n"), DEBUG_FUNCS);

	disk_lock_nointr(&wd->sc_dk);

	disk_closepart(&wd->sc_dk, part, fmt);

	if (wd->sc_dk.dk_openmask == 0) {
		wd_flushcache(wd, 0);
		/* XXXX Must wait for I/O to complete! */
	}

	disk_unlock(&wd->sc_dk);

	device_unref(&wd->sc_dev);
	return (0);
}

void
wdgetdefaultlabel(struct wd_softc *wd, struct disklabel *lp)
{
	WDCDEBUG_PRINT(("wdgetdefaultlabel\n"), DEBUG_FUNCS);
	bzero(lp, sizeof(struct disklabel));

	lp->d_secsize = DEV_BSIZE;
	DL_SETDSIZE(lp, wd->sc_capacity);
	lp->d_ntracks = wd->sc_params.atap_heads;
	lp->d_nsectors = wd->sc_params.atap_sectors;
	lp->d_secpercyl = lp->d_ntracks * lp->d_nsectors;
	lp->d_ncylinders = DL_GETDSIZE(lp) / lp->d_secpercyl;
	if (wd->drvp->ata_vers == -1) {
		lp->d_type = DTYPE_ST506;
		strncpy(lp->d_typename, "ST506/MFM/RLL", sizeof lp->d_typename);
	} else {
		lp->d_type = DTYPE_ESDI;
		strncpy(lp->d_typename, "ESDI/IDE disk", sizeof lp->d_typename);
	}
	/* XXX - user viscopy() like sd.c */
	strncpy(lp->d_packname, wd->sc_params.atap_model, sizeof lp->d_packname);
	lp->d_flags = 0;
	lp->d_version = 1;

	lp->d_magic = DISKMAGIC;
	lp->d_magic2 = DISKMAGIC;
	lp->d_checksum = dkcksum(lp);
}

/*
 * Fabricate a default disk label, and try to read the correct one.
 */
int
wdgetdisklabel(dev_t dev, struct wd_softc *wd, struct disklabel *lp,
    int spoofonly)
{
	int error;

	WDCDEBUG_PRINT(("wdgetdisklabel\n"), DEBUG_FUNCS);

	wdgetdefaultlabel(wd, lp);

	if (wd->drvp->state > RECAL)
		wd->drvp->drive_flags |= DRIVE_RESET;
	error = readdisklabel(DISKLABELDEV(dev), wdstrategy, lp,
	    spoofonly);
	if (wd->drvp->state > RECAL)
		wd->drvp->drive_flags |= DRIVE_RESET;
	return (error);
}

int
wdioctl(dev_t dev, u_long xfer, caddr_t addr, int flag, struct proc *p)
{
	struct wd_softc *wd;
	struct disklabel *lp;
	int error = 0;

	WDCDEBUG_PRINT(("wdioctl\n"), DEBUG_FUNCS);

	wd = wdlookup(DISKUNIT(dev));
	if (wd == NULL)
		return ENXIO;

	if ((wd->sc_flags & WDF_LOADED) == 0) {
		error = EIO;
		goto exit;
	}

	switch (xfer) {
	case DIOCRLDINFO:
		lp = malloc(sizeof(*lp), M_TEMP, M_WAITOK);
		wdgetdisklabel(dev, wd, lp, 0);
		bcopy(lp, wd->sc_dk.dk_label, sizeof(*lp));
		free(lp, M_TEMP, 0);
		goto exit;

	case DIOCGPDINFO:
		wdgetdisklabel(dev, wd, (struct disklabel *)addr, 1);
		goto exit;

	case DIOCGDINFO:
		*(struct disklabel *)addr = *(wd->sc_dk.dk_label);
		goto exit;

	case DIOCGPART:
		((struct partinfo *)addr)->disklab = wd->sc_dk.dk_label;
		((struct partinfo *)addr)->part =
		    &wd->sc_dk.dk_label->d_partitions[DISKPART(dev)];
		goto exit;

	case DIOCWDINFO:
	case DIOCSDINFO:
		if ((flag & FWRITE) == 0) {
			error = EBADF;
			goto exit;
		}

		if ((error = disk_lock(&wd->sc_dk)) != 0)
			goto exit;

		error = setdisklabel(wd->sc_dk.dk_label,
		    (struct disklabel *)addr, wd->sc_dk.dk_openmask);
		if (error == 0) {
			if (wd->drvp->state > RECAL)
				wd->drvp->drive_flags |= DRIVE_RESET;
			if (xfer == DIOCWDINFO)
				error = writedisklabel(DISKLABELDEV(dev),
				    wdstrategy, wd->sc_dk.dk_label);
		}

		disk_unlock(&wd->sc_dk);
		goto exit;

#ifdef notyet
	case DIOCWFORMAT:
		if ((flag & FWRITE) == 0)
			return EBADF;
		{
		struct format_op *fop;
		struct iovec aiov;
		struct uio auio;

		fop = (struct format_op *)addr;
		aiov.iov_base = fop->df_buf;
		aiov.iov_len = fop->df_count;
		auio.uio_iov = &aiov;
		auio.uio_iovcnt = 1;
		auio.uio_resid = fop->df_count;
		auio.uio_segflg = 0;
		auio.uio_offset =
			fop->df_startblk * wd->sc_dk.dk_label->d_secsize;
		auio.uio_procp = p;
		error = physio(wdformat, dev, B_WRITE, minphys, &auio);
		fop->df_count -= auio.uio_resid;
		fop->df_reg[0] = wdc->sc_status;
		fop->df_reg[1] = wdc->sc_error;
		goto exit;
		}
#endif

	default:
		error = wdc_ioctl(wd->drvp, xfer, addr, flag, p);
		goto exit;
	}

#ifdef DIAGNOSTIC
	panic("wdioctl: impossible");
#endif

 exit:
	device_unref(&wd->sc_dev);
	return (error);
}

#ifdef B_FORMAT
int
wdformat(struct buf *bp)
{

	bp->b_flags |= B_FORMAT;
	return wdstrategy(bp);
}
#endif

daddr_t
wdsize(dev_t dev)
{
	struct wd_softc *wd;
	struct disklabel *lp;
	int part, omask;
	daddr_t size;

	WDCDEBUG_PRINT(("wdsize\n"), DEBUG_FUNCS);

	wd = wdlookup(DISKUNIT(dev));
	if (wd == NULL)
		return (-1);

	part = DISKPART(dev);
	omask = wd->sc_dk.dk_openmask & (1 << part);

	if (omask == 0 && wdopen(dev, 0, S_IFBLK, NULL) != 0) {
		size = -1;
		goto exit;
	}

	lp = wd->sc_dk.dk_label;
	size = DL_SECTOBLK(lp, DL_GETPSIZE(&lp->d_partitions[part]));
	if (omask == 0 && wdclose(dev, 0, S_IFBLK, NULL) != 0)
		size = -1;

 exit:
	device_unref(&wd->sc_dev);
	return (size);
}

/* #define WD_DUMP_NOT_TRUSTED if you just want to watch */
static int wddoingadump = 0;
static int wddumprecalibrated = 0;
static int wddumpmulti = 1;

/*
 * Dump core after a system crash.
 */
int
wddump(dev_t dev, daddr_t blkno, caddr_t va, size_t size)
{
	struct wd_softc *wd;	/* disk unit to do the I/O */
	struct disklabel *lp;   /* disk's disklabel */
	int unit, part;
	int nblks;	/* total number of sectors left to write */
	int nwrt;	/* sectors to write with current i/o. */
	int err;
	char errbuf[256];

	/* Check if recursive dump; if so, punt. */
	if (wddoingadump)
		return EFAULT;
	wddoingadump = 1;

	unit = DISKUNIT(dev);
	wd = wdlookup(unit);
	if (wd == NULL)
		return ENXIO;

	part = DISKPART(dev);

	/* Make sure it was initialized. */
	if (wd->drvp->state < READY)
		return ENXIO;

	/* Convert to disk sectors.  Request must be a multiple of size. */
	lp = wd->sc_dk.dk_label;
	if ((size % lp->d_secsize) != 0)
		return EFAULT;
	nblks = size / lp->d_secsize;
	blkno = blkno / (lp->d_secsize / DEV_BSIZE);

	/* Check transfer bounds against partition size. */
	if ((blkno < 0) || ((blkno + nblks) > DL_GETPSIZE(&lp->d_partitions[part])))
		return EINVAL;

	/* Offset block number to start of partition. */
	blkno += DL_GETPOFFSET(&lp->d_partitions[part]);

	/* Recalibrate, if first dump transfer. */
	if (wddumprecalibrated == 0) {
		wddumpmulti = wd->sc_multi;
		wddumprecalibrated = 1;
		wd->drvp->state = RECAL;
	}

	while (nblks > 0) {
		nwrt = min(nblks, wddumpmulti);
		wd->sc_wdc_bio.blkno = blkno;
		wd->sc_wdc_bio.flags = ATA_POLL;
		if (wd->sc_flags & WDF_LBA48)
			wd->sc_wdc_bio.flags |= ATA_LBA48;
		if (wd->sc_flags & WDF_LBA)
			wd->sc_wdc_bio.flags |= ATA_LBA;
		wd->sc_wdc_bio.bcount = nwrt * lp->d_secsize;
		wd->sc_wdc_bio.databuf = va;
		wd->sc_wdc_bio.wd = wd;
#ifndef WD_DUMP_NOT_TRUSTED
		switch (wdc_ata_bio(wd->drvp, &wd->sc_wdc_bio)) {
		case WDC_TRY_AGAIN:
			panic("wddump: try again");
			break;
		case WDC_QUEUED:
			panic("wddump: polled command has been queued");
			break;
		case WDC_COMPLETE:
			break;
		}
		switch(wd->sc_wdc_bio.error) {
		case TIMEOUT:
			printf("wddump: device timed out");
			err = EIO;
			break;
		case ERR_DF:
			printf("wddump: drive fault");
			err = EIO;
			break;
		case ERR_DMA:
			printf("wddump: DMA error");
			err = EIO;
			break;
		case ERROR:
			errbuf[0] = '\0';
			ata_perror(wd->drvp, wd->sc_wdc_bio.r_error, errbuf,
			    sizeof errbuf);
			printf("wddump: %s", errbuf);
			err = EIO;
			break;
		case NOERROR:
			err = 0;
			break;
		default:
			panic("wddump: unknown error type");
		}
		if (err != 0) {
			printf("\n");
			return err;
		}
#else	/* WD_DUMP_NOT_TRUSTED */
		/* Let's just talk about this first... */
		printf("wd%d: dump addr 0x%x, cylin %d, head %d, sector %d\n",
		    unit, va, cylin, head, sector);
		delay(500 * 1000);	/* half a second */
#endif

		/* update block count */
		nblks -= nwrt;
		blkno += nwrt;
		va += nwrt * lp->d_secsize;
	}

	wddoingadump = 0;
	return 0;
}

int
wd_get_params(struct wd_softc *wd, u_int8_t flags, struct ataparams *params)
{
	switch (ata_get_params(wd->drvp, flags, params)) {
	case CMD_AGAIN:
		return 1;
	case CMD_ERR:
		/* If we already have drive parameters, reuse them. */
		if (wd->sc_params.atap_cylinders != 0) {
			if (params != &wd->sc_params)
				bcopy(&wd->sc_params, params,
				    sizeof(struct ataparams));
			return 0;
		}
		/*
		 * We `know' there's a drive here; just assume it's old.
		 * This geometry is only used to read the MBR and print a
		 * (false) attach message.
		 */
		bzero(params, sizeof(struct ataparams));
		strncpy(params->atap_model, "ST506",
		    sizeof params->atap_model);
		params->atap_config = ATA_CFG_FIXED;
		params->atap_cylinders = 1024;
		params->atap_heads = 8;
		params->atap_sectors = 17;
		params->atap_multi = 1;
		params->atap_capabilities1 = params->atap_capabilities2 = 0;
		wd->drvp->ata_vers = -1; /* Mark it as pre-ATA */
		return 0;
	case CMD_OK:
		return 0;
	default:
		panic("wd_get_params: bad return code from ata_get_params");
		/* NOTREACHED */
	}
}

void
wd_flushcache(struct wd_softc *wd, int flags)
{
	struct wdc_command wdc_c;

	if (wd->drvp->ata_vers < 4) /* WDCC_FLUSHCACHE is here since ATA-4 */
		return;
	bzero(&wdc_c, sizeof(struct wdc_command));
	wdc_c.r_command = (wd->sc_flags & WDF_LBA48 ? WDCC_FLUSHCACHE_EXT :
	    WDCC_FLUSHCACHE);
	wdc_c.r_st_bmask = WDCS_DRDY;
	wdc_c.r_st_pmask = WDCS_DRDY;
	if (flags != 0) {
		wdc_c.flags = AT_POLL;
	} else {
		wdc_c.flags = AT_WAIT;
	}
	wdc_c.timeout = 30000; /* 30s timeout */
	if (wdc_exec_command(wd->drvp, &wdc_c) != WDC_COMPLETE) {
		printf("%s: flush cache command didn't complete\n",
		    wd->sc_dev.dv_xname);
	}
	if (wdc_c.flags & AT_TIMEOU) {
		printf("%s: flush cache command timeout\n",
		    wd->sc_dev.dv_xname);
	}
	if (wdc_c.flags & AT_DF) {
		printf("%s: flush cache command: drive fault\n",
		    wd->sc_dev.dv_xname);
	}
	/*
	 * Ignore error register, it shouldn't report anything else
	 * than COMMAND ABORTED, which means the device doesn't support
	 * flush cache
	 */
}

void
wd_standby(struct wd_softc *wd, int flags)
{
	struct wdc_command wdc_c;

	bzero(&wdc_c, sizeof(struct wdc_command));
	wdc_c.r_command = WDCC_STANDBY_IMMED;
	wdc_c.r_st_bmask = WDCS_DRDY;
	wdc_c.r_st_pmask = WDCS_DRDY;
	if (flags != 0) {
		wdc_c.flags = AT_POLL;
	} else {
		wdc_c.flags = AT_WAIT;
	}
	wdc_c.timeout = 30000; /* 30s timeout */
	if (wdc_exec_command(wd->drvp, &wdc_c) != WDC_COMPLETE) {
		printf("%s: standby command didn't complete\n",
		    wd->sc_dev.dv_xname);
	}
	if (wdc_c.flags & AT_TIMEOU) {
		printf("%s: standby command timeout\n",
		    wd->sc_dev.dv_xname);
	}
	if (wdc_c.flags & AT_DF) {
		printf("%s: standby command: drive fault\n",
		    wd->sc_dev.dv_xname);
	}
	/*
	 * Ignore error register, it shouldn't report anything else
	 * than COMMAND ABORTED, which means the device doesn't support
	 * standby
	 */
}


code:
/*	$OpenBSD: wdvar.h,v 1.21 2014/07/09 12:56:28 mpi Exp $	*/
/*	$NetBSD: wdvar.h,v 1.3 1998/11/11 19:38:27 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_WDVAR_H_
#define _DEV_ATA_WDVAR_H_

/* Params needed by the controller to perform an ATA bio */
struct ata_bio {
    volatile u_int16_t flags; /* cmd flags */
#define ATA_NOSLEEP 0x0001 /* Can't sleep */
#define ATA_POLL    0x0002 /* poll for completion */
#define ATA_ITSDONE 0x0004 /* the transfer is as done as it gets */
#define ATA_SINGLE  0x0008 /* transfer has to be done in single-sector mode */
#define ATA_LBA     0x0010 /* transfer uses LBA addressing */
#define ATA_READ    0x0020 /* transfer is a read (otherwise a write) */
#define ATA_CORR    0x0040 /* transfer had a corrected error */
#define ATA_LBA48   0x0080 /* transfer uses 48-bit LBA addressing */
    int multi; /* number of blocks to transfer in multi-mode */
    struct disklabel *lp; /* pointer to drive's label info */
    daddr_t blkno; /* block addr */
    daddr_t blkdone; /* number of blks transferred */
    daddr_t nblks; /* number of block currently transferring */
    int     nbytes; /* number of bytes currently transferring */
    long    bcount; /* total number of bytes */
    char   *databuf; /* data buffer address */
    volatile int error;
#define NOERROR   0 /* There was no error (r_error invalid) */
#define ERROR     1 /* check r_error */
#define ERR_DF    2 /* Drive fault */
#define ERR_DMA   3 /* DMA error */
#define TIMEOUT   4 /* device timed out */
#define ERR_NODEV 5 /* device bas been detached */
    u_int8_t r_error; /* copy of error register */
    struct wd_softc *wd;
};

struct wd_softc {
	/* General disk infos */
	struct device sc_dev;
	struct disk sc_dk;
	struct bufq sc_bufq;

	/* IDE disk soft states */
	struct ata_bio sc_wdc_bio; /* current transfer */
	struct buf *sc_bp; /* buf being transferred */
	struct ata_drive_datas *drvp; /* Our controller's infos */
	int openings;
	struct ataparams sc_params;/* drive characteristics found */
	int sc_flags;
/*
 * XXX Nothing resets this yet, but disk change sensing will when ATA-4 is
 * more fully implemented.
 */
#define WDF_LOADED	0x10 /* parameters loaded */
#define WDF_WAIT	0x20 /* waiting for resources */
#define WDF_LBA		0x40 /* using LBA mode */
#define WDF_LBA48	0x80 /* using 48-bit LBA mode */

	u_int64_t sc_capacity;
	int cyl; /* actual drive parameters */
	int heads;
	int sectors;
	int retries; /* number of xfer retry */
	struct timeout sc_restart_timeout;
};

/* drive states stored in ata_drive_datas */
#define RECAL          0
#define RECAL_WAIT     1
#define PIOMODE        2
#define PIOMODE_WAIT   3
#define DMAMODE        4
#define DMAMODE_WAIT   5
#define GEOMETRY       6
#define GEOMETRY_WAIT  7
#define MULTIMODE      8
#define MULTIMODE_WAIT 9
#define READY          10

int wdc_ata_bio(struct ata_drive_datas*, struct ata_bio*);
int wd_hibernate_io(dev_t dev, daddr_t blkno, vaddr_t addr, size_t size,
	    int wr, void *page);

void wddone(void *);

#endif	/* !_DEV_ATA_WDVAR_H_ */


code:
/atapiscsi.c/1.103/Sun Sep 14 14:17:24 2014//TOPENBSD_5_8_BASE
/files.atapiscsi/1.8/Wed May 25 03:26:20 2011//TOPENBSD_5_8_BASE
D

path: sys/dev/atapiscsi/CVS/Entries


code: src/sys/dev/atapiscsi

path: sys/dev/atapiscsi/CVS/Repository


code:
/*      $OpenBSD: atapiscsi.c,v 1.103 2014/09/14 14:17:24 jsg Exp $     */

/*
 * This code is derived from code with the copyright below.
 */

/*
 * Copyright (c) 1996, 1998 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 REGENTS 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 REGENTS 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/device.h>
#include <sys/buf.h>
#include <sys/disklabel.h>
#include <sys/malloc.h>
#include <sys/reboot.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/timeout.h>
#include <scsi/scsi_all.h>
#include <scsi/scsi_disk.h>
#include <scsi/scsi_tape.h>
#include <scsi/scsiconf.h>

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

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

/* drive states stored in ata_drive_datas */
enum atapi_drive_states {
	ATAPI_RESET_BASE_STATE = 0,
	ATAPI_DEVICE_RESET_WAIT_STATE = 1,
	ATAPI_IDENTIFY_STATE = 2,
	ATAPI_IDENTIFY_WAIT_STATE = 3,
	ATAPI_PIOMODE_STATE = 4,
	ATAPI_PIOMODE_WAIT_STATE = 5,
	ATAPI_DMAMODE_STATE = 6,
	ATAPI_DMAMODE_WAIT_STATE = 7,
	ATAPI_READY_STATE = 8
};

#define DEBUG_INTR   0x01
#define DEBUG_XFERS  0x02
#define DEBUG_STATUS 0x04
#define DEBUG_FUNCS  0x08
#define DEBUG_PROBE  0x10
#define DEBUG_DSC    0x20
#define DEBUG_POLL   0x40
#define DEBUG_ERRORS 0x80   /* Debug error handling code */

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

/* 10 ms, this is used only before sending a cmd.  */
#define ATAPI_DELAY 10
#define ATAPI_RESET_DELAY 1000
#define ATAPI_RESET_WAIT 2000
#define ATAPI_CTRL_WAIT 4000

/* When polling, let the exponential backoff max out at 1 second's interval. */
#define ATAPI_POLL_MAXTIC (hz)

void  wdc_atapi_start(struct channel_softc *,struct wdc_xfer *);

void  wdc_atapi_timer_handler(void *);

void  wdc_atapi_real_start(struct channel_softc *, struct wdc_xfer *,
    int, struct atapi_return_args *);
void  wdc_atapi_real_start_2(struct channel_softc *, struct wdc_xfer *,
    int, struct atapi_return_args *);
void  wdc_atapi_intr_command(struct channel_softc *, struct wdc_xfer *,
    int, struct atapi_return_args *);
void  wdc_atapi_intr_data(struct channel_softc *, struct wdc_xfer *,
    int, struct atapi_return_args *);
void  wdc_atapi_intr_complete(struct channel_softc *, struct wdc_xfer *,
    int, struct atapi_return_args *);
void  wdc_atapi_pio_intr(struct channel_softc *, struct wdc_xfer *,
    int, struct atapi_return_args *);
void  wdc_atapi_send_packet(struct channel_softc *, struct wdc_xfer *,
    int, struct atapi_return_args *);
void  wdc_atapi_ctrl(struct channel_softc *, struct wdc_xfer *,
    int, struct atapi_return_args *);

char  *wdc_atapi_in_data_phase(struct wdc_xfer *, int, int);

int   wdc_atapi_intr(struct channel_softc *, struct wdc_xfer *, int);
void  wdc_atapi_done(struct channel_softc *, struct wdc_xfer *,
	int, struct atapi_return_args *);
void  wdc_atapi_reset(struct channel_softc *, struct wdc_xfer *,
	int, struct atapi_return_args *);
void  wdc_atapi_reset_2(struct channel_softc *, struct wdc_xfer *,
	int, struct atapi_return_args *);

void  wdc_atapi_tape_done(struct channel_softc *, struct wdc_xfer *,
	int, struct atapi_return_args *);
#define MAX_SIZE MAXPHYS

struct atapiscsi_softc;
struct atapiscsi_xfer;

int	atapiscsi_match(struct device *, void *, void *);
void	atapiscsi_attach(struct device *, struct device *, void *);
int	atapiscsi_activate(struct device *, int);
int	atapiscsi_detach(struct device *, int);
int     atapi_to_scsi_sense(struct scsi_xfer *, u_int8_t);

enum atapi_state { as_none, as_data, as_completed };

struct atapiscsi_softc {
	struct device  sc_dev;
	struct  scsi_link  sc_adapterlink;
	struct channel_softc *chp;
	enum atapi_state protocol_phase;

	int drive;
};

void  wdc_atapi_minphys(struct buf *bp, struct scsi_link *sl);
int   wdc_atapi_ioctl(struct scsi_link *, u_long, caddr_t, int);
void  wdc_atapi_send_cmd(struct scsi_xfer *sc_xfer);

static struct scsi_adapter atapiscsi_switch =
{
	wdc_atapi_send_cmd,
	wdc_atapi_minphys,
	NULL,
	NULL,
	wdc_atapi_ioctl
};

/* Inital version shares bus_link structure so it can easily
   be "attached to current" wdc driver */

struct cfattach atapiscsi_ca = {
	sizeof(struct atapiscsi_softc), atapiscsi_match, atapiscsi_attach,
	    atapiscsi_detach, atapiscsi_activate
};

struct cfdriver atapiscsi_cd = {
	NULL, "atapiscsi", DV_DULL
};

int
atapiscsi_match(struct device *parent, void *match, void *aux)
{
	struct ata_atapi_attach *aa_link = aux;
	struct cfdata *cf = match;

	if (aa_link == NULL)
		return (0);

	if (aa_link->aa_type != T_ATAPI)
		return (0);

	if (cf->cf_loc[0] != aa_link->aa_channel &&
	    cf->cf_loc[0] != -1)
		return (0);

	return (1);
}

void
atapiscsi_attach(struct device *parent, struct device *self, void *aux)
{
	struct atapiscsi_softc *as = (struct atapiscsi_softc *)self;
	struct ata_atapi_attach *aa_link = aux;
	struct scsibus_attach_args saa;
	struct ata_drive_datas *drvp = aa_link->aa_drv_data;
	struct channel_softc *chp = drvp->chnl_softc;
	struct ataparams *id = &drvp->id;
	struct device *child;

	extern struct scsi_iopool wdc_xfer_iopool;

	printf("\n");

	/* Initialize shared data. */
	scsi_init();

#ifdef WDCDEBUG
	if (chp->wdc->sc_dev.dv_cfdata->cf_flags & WDC_OPTION_PROBE_VERBOSE)
		wdcdebug_atapi_mask |= DEBUG_PROBE;
#endif

	as->chp = chp;
	as->drive = drvp->drive;
	as->sc_adapterlink.adapter_softc = as;
	as->sc_adapterlink.adapter_target = 7;
	as->sc_adapterlink.adapter_buswidth = 2;
	as->sc_adapterlink.adapter = &atapiscsi_switch;
	as->sc_adapterlink.luns = 1;
	as->sc_adapterlink.openings = 1;
	as->sc_adapterlink.flags = SDEV_ATAPI;
	as->sc_adapterlink.pool = &wdc_xfer_iopool;

	strlcpy(drvp->drive_name, as->sc_dev.dv_xname,
	    sizeof(drvp->drive_name));
	drvp->cf_flags = as->sc_dev.dv_cfdata->cf_flags;

	wdc_probe_caps(drvp, id);

	WDCDEBUG_PRINT(
		("general config %04x capabilities %04x ",
		    id->atap_config, id->atap_capabilities1),
		    DEBUG_PROBE);

	if ((NERRS_MAX - 2) > 0)
		drvp->n_dmaerrs = NERRS_MAX - 2;
	else
		drvp->n_dmaerrs = 0;
	drvp->drive_flags |= DRIVE_DEVICE_RESET;

	/* Tape drives do funny DSC stuff */
	if (ATAPI_CFG_TYPE(id->atap_config) ==
	    ATAPI_CFG_TYPE_SEQUENTIAL)
		drvp->atapi_cap |= ACAP_DSC;

	if ((id->atap_config & ATAPI_CFG_CMD_MASK) ==
	    ATAPI_CFG_CMD_16)
		drvp->atapi_cap |= ACAP_LEN;

	drvp->atapi_cap |=
	    (id->atap_config & ATAPI_CFG_DRQ_MASK);

	WDCDEBUG_PRINT(("driver caps %04x\n", drvp->atapi_cap),
	    DEBUG_PROBE);

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

	child = config_found((struct device *)as, &saa, scsiprint);

	if (child != NULL) {
		struct scsibus_softc *scsi = (struct scsibus_softc *)child;
		struct scsi_link *link = scsi_get_link(scsi, 0, 0);

		if (link) {
			strlcpy(drvp->drive_name,
			    ((struct device *)(link->device_softc))->dv_xname,
			    sizeof(drvp->drive_name));

			wdc_print_caps(drvp);
		}
	}

#ifdef WDCDEBUG
	if (chp->wdc->sc_dev.dv_cfdata->cf_flags & WDC_OPTION_PROBE_VERBOSE)
		wdcdebug_atapi_mask &= ~DEBUG_PROBE;
#endif
}

int
atapiscsi_activate(struct device *self, int act)
{
	struct atapiscsi_softc *as = (void *)self;
 	struct channel_softc *chp = as->chp;
	struct ata_drive_datas *drvp = &chp->ch_drive[as->drive];

	switch (act) {
	case DVACT_SUSPEND:
		break;
	case DVACT_RESUME:
		/*
		 * Do two resets separated by a small delay. The
		 * first wakes the controller, the second resets
		 * the channel
		 */
		wdc_disable_intr(chp);
		wdc_reset_channel(drvp, 1);
		delay(10000);
		wdc_reset_channel(drvp, 0);
		wdc_enable_intr(chp);
		break;
	}
	return (0);
}

int
atapiscsi_detach(struct device *dev, int flags)
{
	return (config_detach_children(dev, flags));
}

void
wdc_atapi_send_cmd(struct scsi_xfer *sc_xfer)
{
	struct atapiscsi_softc *as = sc_xfer->sc_link->adapter_softc;
 	struct channel_softc *chp = as->chp;
	struct ata_drive_datas *drvp = &chp->ch_drive[as->drive];
	struct wdc_xfer *xfer;
	int s;
	int idx;

	WDCDEBUG_PRINT(("wdc_atapi_send_cmd %s:%d:%d start\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, as->drive), DEBUG_XFERS);

	if (sc_xfer->sc_link->target != 0) {
		sc_xfer->error = XS_DRIVER_STUFFUP;
		scsi_done(sc_xfer);
		return;
	}

	xfer = sc_xfer->io;
	wdc_scrub_xfer(xfer);
	if (sc_xfer->flags & SCSI_POLL)
		xfer->c_flags |= C_POLL;
	xfer->drive = as->drive;
	xfer->c_flags |= C_ATAPI;
	xfer->cmd = sc_xfer;
	xfer->databuf = sc_xfer->data;
	xfer->c_bcount = sc_xfer->datalen;
	xfer->c_start = wdc_atapi_start;
	xfer->c_intr = wdc_atapi_intr;

	timeout_set(&xfer->atapi_poll_to, wdc_atapi_timer_handler, chp);

	WDCDEBUG_PRINT(("wdc_atapi_send_cmd %s:%d:%d ",
	    chp->wdc->sc_dev.dv_xname, chp->channel, as->drive),
	    DEBUG_XFERS | DEBUG_ERRORS);

	for (idx = 0; idx < sc_xfer->cmdlen; idx++) {
		WDCDEBUG_PRINT((" %02x",
				   ((unsigned char *)sc_xfer->cmd)[idx]),
		    DEBUG_XFERS | DEBUG_ERRORS);
	}
	WDCDEBUG_PRINT(("\n"), DEBUG_XFERS | DEBUG_ERRORS);

	s = splbio();

	if (drvp->atapi_cap & ACAP_DSC) {
		WDCDEBUG_PRINT(("about to send cmd 0x%x ",
		    sc_xfer->cmd->opcode), DEBUG_DSC);
		switch (sc_xfer->cmd->opcode) {
		case READ:
		case WRITE:
			xfer->c_flags |= C_MEDIA_ACCESS;

			/* If we are not in buffer availability mode,
			   we limit the first request to 0 bytes, which
			   gets us into buffer availability mode without
			   holding the bus.  */
			if (!(drvp->drive_flags & DRIVE_DSCBA)) {
				xfer->c_bcount = 0;
				xfer->transfer_len =
				  _3btol(((struct scsi_rw_tape *)
					  sc_xfer->cmd)->len);
				_lto3b(0,
				    ((struct scsi_rw_tape *)
				    sc_xfer->cmd)->len);
				xfer->c_done = wdc_atapi_tape_done;
				WDCDEBUG_PRINT(
				    ("R/W in completion mode, do 0 blocks\n"),
				    DEBUG_DSC);
			} else
				WDCDEBUG_PRINT(("R/W %d blocks %d bytes\n",
				    _3btol(((struct scsi_rw_tape *)
					sc_xfer->cmd)->len),
				    sc_xfer->datalen),
				    DEBUG_DSC);

			/* DSC will change to buffer availability mode.
			   We reflect this in wdc_atapi_intr.  */
			break;

		case ERASE:		/* Media access commands */
		case LOAD:
		case REWIND:
		case SPACE:
		case WRITE_FILEMARKS:
#if 0
		case LOCATE:
		case READ_POSITION:
#endif

			xfer->c_flags |= C_MEDIA_ACCESS;
			break;

		default:
			WDCDEBUG_PRINT(("no media access\n"), DEBUG_DSC);
		}
	}

	wdc_exec_xfer(chp, xfer);
	splx(s);
}

void
wdc_atapi_minphys (struct buf *bp, struct scsi_link *sl)
{
	if (bp->b_bcount > MAX_SIZE)
		bp->b_bcount = MAX_SIZE;
	minphys(bp);
}

int
wdc_atapi_ioctl (struct scsi_link *sc_link, u_long cmd, caddr_t addr, int flag)
{
	struct atapiscsi_softc *as = sc_link->adapter_softc;
	struct channel_softc *chp = as->chp;
	struct ata_drive_datas *drvp = &chp->ch_drive[as->drive];

	if (sc_link->target != 0)
		return ENOTTY;

	return (wdc_ioctl(drvp, cmd, addr, flag, curproc));
}

/*
 * Returns 1 if we experienced an ATA-level abort command
 *           (ABRT bit set but no additional sense)
 *         0 if normal command processing
 */
int
atapi_to_scsi_sense(struct scsi_xfer *xfer, u_int8_t flags)
{
	struct scsi_sense_data *sense = &xfer->sense;
	int ret = 0;

	xfer->error = XS_SHORTSENSE;

	sense->error_code = SSD_ERRCODE_VALID | SSD_ERRCODE_CURRENT;
	sense->flags = (flags >> 4);

	WDCDEBUG_PRINT(("Atapi error: %d ", (flags >> 4)), DEBUG_ERRORS);

	if ((flags & 4) && (sense->flags == 0)) {
		sense->flags = SKEY_ABORTED_COMMAND;
		WDCDEBUG_PRINT(("ABRT "), DEBUG_ERRORS);
		ret = 1;
	}

	if (flags & 0x1) {
		sense->flags |= SSD_ILI;
		WDCDEBUG_PRINT(("ILI "), DEBUG_ERRORS);
	}

	if (flags & 0x2) {
		sense->flags |= SSD_EOM;
		WDCDEBUG_PRINT(("EOM "), DEBUG_ERRORS);
	}

	/* Media change requested */
	/* Let's ignore these in version 1 */
	if (flags & 0x8) {
		WDCDEBUG_PRINT(("MCR "), DEBUG_ERRORS);
		if (sense->flags == 0)
			xfer->error = XS_NOERROR;
	}

	WDCDEBUG_PRINT(("\n"), DEBUG_ERRORS);
	return (ret);
}

int wdc_atapi_drive_selected(struct channel_softc *, int);

int
wdc_atapi_drive_selected(struct channel_softc *chp, int drive)
{
	u_int8_t reg = CHP_READ_REG(chp, wdr_sdh);

	WDC_LOG_REG(chp, wdr_sdh, reg);

	return ((reg & 0x10) == (drive << 4));
}

enum atapi_context {
	ctxt_process = 0,
	ctxt_timer = 1,
	ctxt_interrupt = 2
};

void wdc_atapi_the_machine(struct channel_softc *, struct wdc_xfer *,
    enum atapi_context);

void wdc_atapi_the_poll_machine(struct channel_softc *, struct wdc_xfer *);

void
wdc_atapi_start(struct channel_softc *chp, struct wdc_xfer *xfer)
{
	xfer->next = wdc_atapi_real_start;

	wdc_atapi_the_machine(chp, xfer, ctxt_process);
}

void
wdc_atapi_timer_handler(void *arg)
{
	struct channel_softc *chp = arg;
	struct wdc_xfer *xfer;
	int s;

	s = splbio();
	xfer = TAILQ_FIRST(&chp->ch_queue->sc_xfer);
	if (xfer == NULL ||
	    !timeout_triggered(&xfer->atapi_poll_to)) {
		splx(s);
		return;
	}
	xfer->c_flags &= ~C_POLL_MACHINE;
	timeout_del(&xfer->atapi_poll_to);
	chp->ch_flags &= ~WDCF_IRQ_WAIT;
	wdc_atapi_the_machine(chp, xfer, ctxt_timer);
	splx(s);
}

int
wdc_atapi_intr(struct channel_softc *chp, struct wdc_xfer *xfer, int irq)
{
	timeout_del(&chp->ch_timo);

	/* XXX we should consider an alternate signaling regime here */
	if (xfer->c_flags & C_TIMEOU) {
		xfer->c_flags &= ~C_TIMEOU;
		wdc_atapi_the_machine(chp, xfer, ctxt_timer);
		return (0);
	}

	wdc_atapi_the_machine(chp, xfer, ctxt_interrupt);

	return (-1);
}

struct atapi_return_args {
	int timeout;
	int delay;
	int expect_irq;
};

#define ARGS_INIT {-1, 0, 0}

void
wdc_atapi_the_poll_machine(struct channel_softc *chp, struct wdc_xfer *xfer)
{
	int  idx = 0;
	int  current_timeout = 10;

	while (1) {
		struct atapi_return_args retargs = ARGS_INIT;
		idx++;

		(xfer->next)(chp, xfer, (current_timeout * 1000 <= idx),
		    &retargs);

		if (xfer->next == NULL) {
			wdc_free_xfer(chp, xfer);
			wdcstart(chp);
			return;
		}

		if (retargs.timeout != -1) {
			current_timeout = retargs.timeout;
			idx = 0;
		}

		if (retargs.delay != 0) {
			delay (1000 * retargs.delay);
			idx += 1000 * retargs.delay;
		}

		DELAY(1);
	}
}

void
wdc_atapi_the_machine(struct channel_softc *chp, struct wdc_xfer *xfer,
    enum atapi_context ctxt)
{
	int idx = 0;
	extern int ticks;
	int timeout_delay = hz / 10;

	if (xfer->c_flags & C_POLL) {
		wdc_disable_intr(chp);

		if (ctxt != ctxt_process) {
			if (ctxt == ctxt_interrupt)
				xfer->endticks = 1;

			return;
		}

		wdc_atapi_the_poll_machine(chp, xfer);
		return;
	}

	/* Don't go through more than 50 state machine steps
	   before yielding. This tries to limit the amount of time
	   spent at high SPL */
	for (idx = 0; idx < 50; idx++) {
		struct atapi_return_args retargs = ARGS_INIT;

		(xfer->next)(chp, xfer,
		    xfer->endticks && (ticks - xfer->endticks >= 0),
		    &retargs);

		if (retargs.timeout != -1)
			/*
			 * Add 1 tick to compensate for the fact that we
			 * can be just microseconds before the tick changes.
			 */
			xfer->endticks =
			    max((retargs.timeout * hz) / 1000, 1) + 1 + ticks;

		if (xfer->next == NULL) {
			if (xfer->c_flags & C_POLL_MACHINE)
				timeout_del(&xfer->atapi_poll_to);

			wdc_free_xfer(chp, xfer);
			wdcstart(chp);

			return;
		}

		if (retargs.expect_irq) {
			int timeout_period;
			chp->ch_flags |= WDCF_IRQ_WAIT;
			timeout_period =  xfer->endticks - ticks;
			if (timeout_period < 1)
				timeout_period = 1;
			timeout_add(&chp->ch_timo, timeout_period);
			return;
		}

		if (retargs.delay != 0) {
			timeout_delay = max(retargs.delay * hz / 1000, 1);
			break;
		}

		DELAY(1);
	}

	timeout_add(&xfer->atapi_poll_to, timeout_delay);
	xfer->c_flags |= C_POLL_MACHINE;

	return;
}

void wdc_atapi_update_status(struct channel_softc *);

void
wdc_atapi_update_status(struct channel_softc *chp)
{
	chp->ch_status = CHP_READ_REG(chp, wdr_status);

	WDC_LOG_STATUS(chp, chp->ch_status);

	if (chp->ch_status == 0xff && (chp->ch_flags & WDCF_ONESLAVE)) {
		wdc_set_drive(chp, 1);

		chp->ch_status = CHP_READ_REG(chp, wdr_status);
		WDC_LOG_STATUS(chp, chp->ch_status);
	}

	if ((chp->ch_status & (WDCS_BSY | WDCS_ERR)) == WDCS_ERR) {
		chp->ch_error = CHP_READ_REG(chp, wdr_error);
		WDC_LOG_ERROR(chp, chp->ch_error);
	}
}

void
wdc_atapi_real_start(struct channel_softc *chp, struct wdc_xfer *xfer,
    int timeout, struct atapi_return_args *ret)
{
#ifdef WDCDEBUG
	struct scsi_xfer *sc_xfer = xfer->cmd;
#endif
	struct ata_drive_datas *drvp = &chp->ch_drive[xfer->drive];

	/*
	 * Only set the DMA flag if the transfer is reasonably large.
	 * At least one older drive failed to complete a 4 byte DMA transfer.
	 */

	/* Turn off DMA flag on REQUEST SENSE */

	if (!(xfer->c_flags & (C_POLL | C_SENSE | C_MEDIA_ACCESS)) &&
	    (drvp->drive_flags & (DRIVE_DMA | DRIVE_UDMA)) &&
	    (xfer->c_bcount > 100))
		xfer->c_flags |= C_DMA;
	else
		xfer->c_flags &= ~C_DMA;

	wdc_set_drive(chp, xfer->drive);

	DELAY(1);

	xfer->next = wdc_atapi_real_start_2;
	ret->timeout = ATAPI_DELAY;

	WDCDEBUG_PRINT(("wdc_atapi_start %s:%d:%d, scsi flags 0x%x, "
	    "ATA flags 0x%x\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, drvp->drive,
	    sc_xfer->flags, xfer->c_flags), DEBUG_XFERS);

	return;
}

void
wdc_atapi_real_start_2(struct channel_softc *chp, struct wdc_xfer *xfer,
    int timeout, struct atapi_return_args *ret)
{
	struct scsi_xfer *sc_xfer = xfer->cmd;
	struct ata_drive_datas *drvp = &chp->ch_drive[xfer->drive];

	if (timeout) {
		printf("wdc_atapi_start: not ready, st = %02x\n",
		    chp->ch_status);

		sc_xfer->error = XS_TIMEOUT;
		xfer->next = wdc_atapi_reset;
		return;
	} else {
		wdc_atapi_update_status(chp);

		if (chp->ch_status & (WDCS_BSY | WDCS_DRQ))
			return;
	}

	/* Do control operations specially. */
	if (drvp->state < ATAPI_READY_STATE) {
		xfer->next = wdc_atapi_ctrl;
		return;
	}

	xfer->next = wdc_atapi_send_packet;
	return;
}

void
wdc_atapi_send_packet(struct channel_softc *chp, struct wdc_xfer *xfer,
    int timeout, struct atapi_return_args *ret)
{
	struct scsi_xfer *sc_xfer = xfer->cmd;
	struct ata_drive_datas *drvp = &chp->ch_drive[xfer->drive];

	/*
	 * Even with WDCS_ERR, the device should accept a command packet.
	 * Limit length to what can be stuffed into the cylinder register
	 * (16 bits).  Some CD-ROMs seem to interpret '0' as 65536,
	 * but not all devices do that and it's not obvious from the
	 * ATAPI spec that this behaviour should be expected.  If more
	 * data is necessary, multiple data transfer phases will be done.
	 */

	wdccommand(chp, xfer->drive, ATAPI_PKT_CMD,
	    xfer->c_bcount <= 0xfffe ? xfer->c_bcount : 0xfffe,
	    0, 0, 0,
	    (xfer->c_flags & C_DMA) ? ATAPI_PKT_CMD_FTRE_DMA : 0);

	if (xfer->c_flags & C_DMA)
		drvp->n_xfers++;

	DELAY(1);

	xfer->next = wdc_atapi_intr_command;
	ret->timeout = sc_xfer->timeout;

	if ((drvp->atapi_cap & ATAPI_CFG_DRQ_MASK) == ATAPI_CFG_IRQ_DRQ) {
		/* We expect an IRQ to tell us of the next state */
		ret->expect_irq = 1;
	}

	WDCDEBUG_PRINT(("wdc_atapi_send_packet %s:%d:%d command sent\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, drvp->drive
	    ), DEBUG_XFERS);
	return;
}

void
wdc_atapi_intr_command(struct channel_softc *chp, struct wdc_xfer *xfer,
    int timeout, struct atapi_return_args *ret)
{
	struct scsi_xfer *sc_xfer = xfer->cmd;
	struct ata_drive_datas *drvp = &chp->ch_drive[xfer->drive];
	struct atapiscsi_softc *as = sc_xfer->sc_link->adapter_softc;
	int i;
	u_int8_t cmd[16];
	struct scsi_sense *cmd_reqsense;
	int cmdlen = (drvp->atapi_cap & ACAP_LEN) ? 16 : 12;
	int dma_flags = ((sc_xfer->flags & SCSI_DATA_IN) ||
	    (xfer->c_flags & C_SENSE)) ?  WDC_DMA_READ : 0;

	wdc_atapi_update_status(chp);

	if ((chp->ch_status & WDCS_BSY) || !(chp->ch_status & WDCS_DRQ)) {
		if (timeout)
			goto timeout;

		return;
	}

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

	bzero(cmd, sizeof(cmd));

	if (xfer->c_flags & C_SENSE) {
		cmd_reqsense = (struct scsi_sense *)&cmd[0];
		cmd_reqsense->opcode = REQUEST_SENSE;
		cmd_reqsense->length = xfer->c_bcount;
	} else
		bcopy(sc_xfer->cmd, cmd, sc_xfer->cmdlen);

	WDC_LOG_ATAPI_CMD(chp, xfer->drive, xfer->c_flags,
	    cmdlen, cmd);

	for (i = 0; i < 12; i++)
		WDCDEBUG_PRINT(("%02x ", cmd[i]), DEBUG_INTR);
	WDCDEBUG_PRINT((": PHASE_CMDOUT\n"), DEBUG_INTR);

	/* Init the DMA channel if necessary */
	if (xfer->c_flags & C_DMA) {
		if ((*chp->wdc->dma_init)(chp->wdc->dma_arg,
		    chp->channel, xfer->drive, xfer->databuf,
		    xfer->c_bcount, dma_flags) != 0) {
			sc_xfer->error = XS_DRIVER_STUFFUP;

			xfer->next = wdc_atapi_reset;
			return;
		}
	}

	wdc_output_bytes(drvp, cmd, cmdlen);

	/* Start the DMA channel if necessary */
	if (xfer->c_flags & C_DMA) {
		(*chp->wdc->dma_start)(chp->wdc->dma_arg,
		    chp->channel, xfer->drive);
		xfer->next = wdc_atapi_intr_complete;
	} else {
		if (xfer->c_bcount == 0)
			as->protocol_phase = as_completed;
		else
			as->protocol_phase = as_data;

		xfer->next = wdc_atapi_pio_intr;
	}

	ret->expect_irq = 1;

	/* If we read/write to a tape we will get into buffer
	   availability mode.  */
	if (drvp->atapi_cap & ACAP_DSC) {
		if ((sc_xfer->cmd->opcode == READ ||
		       sc_xfer->cmd->opcode == WRITE)) {
			drvp->drive_flags |= DRIVE_DSCBA;
			WDCDEBUG_PRINT(("set DSCBA\n"), DEBUG_DSC);
		} else if ((xfer->c_flags & C_MEDIA_ACCESS) &&
		    (drvp->drive_flags & DRIVE_DSCBA)) {
			/* Clause 3.2.4 of QIC-157 D.

			   Any media access command other than read or
			   write will switch DSC back to completion
			   mode */
			drvp->drive_flags &= ~DRIVE_DSCBA;
			WDCDEBUG_PRINT(("clear DCSBA\n"), DEBUG_DSC);
		}
	}

	return;

 timeout:
	printf ("%s:%d:%d: device timeout waiting to send SCSI packet\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive);

	sc_xfer->error = XS_TIMEOUT;
	xfer->next = wdc_atapi_reset;
	return;
}

char *
wdc_atapi_in_data_phase(struct wdc_xfer *xfer, int len, int ire)
{
	struct scsi_xfer *sc_xfer = xfer->cmd;
	struct atapiscsi_softc *as = sc_xfer->sc_link->adapter_softc;
	char *message;

	if (as->protocol_phase != as_data) {
		message = "unexpected data phase";
		goto unexpected_state;
	}

	if (ire & WDCI_CMD) {
		message = "unexpectedly in command phase";
		goto unexpected_state;
	}

	if (!(xfer->c_flags & C_SENSE)) {
		if (!(sc_xfer->flags & (SCSI_DATA_IN | SCSI_DATA_OUT))) {
			message = "data phase where none expected";
			goto unexpected_state;
		}

		/* Make sure polarities match */
		if (((ire & WDCI_IN) == WDCI_IN) ==
		    ((sc_xfer->flags & SCSI_DATA_OUT) == SCSI_DATA_OUT)) {
			message = "data transfer direction disagreement";
			goto unexpected_state;
		}
	} else {
		if (!(ire & WDCI_IN)) {
			message = "data transfer direction disagreement during sense";
			goto unexpected_state;
		}
	}

	if (len == 0) {
		message = "zero length transfer requested in data phase";
		goto unexpected_state;
	}

	return (0);

 unexpected_state:

	return (message);
}

void
wdc_atapi_intr_data(struct channel_softc *chp, struct wdc_xfer *xfer,
    int timeout, struct atapi_return_args *ret)
{
	struct scsi_xfer *sc_xfer = xfer->cmd;
	struct ata_drive_datas *drvp = &chp->ch_drive[xfer->drive];
	int len, ire;
	char *message;
	int tohost;

	len = (CHP_READ_REG(chp, wdr_cyl_hi) << 8) |
	    CHP_READ_REG(chp, wdr_cyl_lo);
	WDC_LOG_REG(chp, wdr_cyl_lo, len);

	ire = CHP_READ_REG(chp, wdr_ireason);
	WDC_LOG_REG(chp, wdr_ireason, ire);

	if ((message = wdc_atapi_in_data_phase(xfer, len, ire))) {
		/* The drive has dropped BSY before setting up the
		   registers correctly for DATA phase. This drive is
		   not compliant with ATA/ATAPI-4.

		   Give the drive 100ms to get its house in order
		   before we try again.  */
		WDCDEBUG_PRINT(("wdc_atapi_intr: %s\n", message),
		    DEBUG_ERRORS);

		if (!timeout) {
			ret->delay = 100;
			return;
		}
	}

	tohost = ((sc_xfer->flags & SCSI_DATA_IN) != 0 ||
	    (xfer->c_flags & C_SENSE) != 0);

	if (xfer->c_bcount >= len) {
		WDCDEBUG_PRINT(("wdc_atapi_intr: c_bcount %d len %d "
		    "st 0x%b err 0x%x "
		    "ire 0x%x\n", xfer->c_bcount,
		    len, chp->ch_status, WDCS_BITS, chp->ch_error, ire),
		    DEBUG_INTR);

		/* Common case */
		if (!tohost)
			wdc_output_bytes(drvp, (u_int8_t *)xfer->databuf +
			    xfer->c_skip, len);
		else
			wdc_input_bytes(drvp, (u_int8_t *)xfer->databuf +
			    xfer->c_skip, len);

		xfer->c_skip += len;
		xfer->c_bcount -= len;
	} else {
		/* Exceptional case - drive want to transfer more
		   data than we have buffer for */
		if (!tohost) {
			/* Wouldn't it be better to just abort here rather
			   than to write random stuff to drive? */
			printf("wdc_atapi_intr: warning: device requesting "
			    "%d bytes, only %d left in buffer\n", len, xfer->c_bcount);

			wdc_output_bytes(drvp, (u_int8_t *)xfer->databuf +
			    xfer->c_skip, xfer->c_bcount);

			CHP_WRITE_RAW_MULTI_2(chp, NULL,
			    len - xfer->c_bcount);
		} else {
			printf("wdc_atapi_intr: warning: reading only "
			    "%d of %d bytes\n", xfer->c_bcount, len);

			wdc_input_bytes(drvp,
			    (char *)xfer->databuf + xfer->c_skip,
			    xfer->c_bcount);
			wdcbit_bucket(chp, len - xfer->c_bcount);
		}

		xfer->c_skip += xfer->c_bcount;
		xfer->c_bcount = 0;
	}

	ret->expect_irq = 1;
	xfer->next = wdc_atapi_pio_intr;

	return;
}

void
wdc_atapi_intr_complete(struct channel_softc *chp, struct wdc_xfer *xfer,
    int timeout, struct atapi_return_args *ret)
{
	struct scsi_xfer *sc_xfer = xfer->cmd;
	struct ata_drive_datas *drvp = &chp->ch_drive[xfer->drive];
	struct atapiscsi_softc *as = sc_xfer->sc_link->adapter_softc;

	WDCDEBUG_PRINT(("PHASE_COMPLETED\n"), DEBUG_INTR);

	if (xfer->c_flags & C_DMA) {
		int retry;

		if (timeout) {
			sc_xfer->error = XS_TIMEOUT;
			ata_dmaerr(drvp);

			xfer->next = wdc_atapi_reset;
			return;
		}

		for (retry = 5; retry > 0; retry--) {
			wdc_atapi_update_status(chp);
			if ((chp->ch_status & (WDCS_BSY | WDCS_DRQ)) == 0)
				break;
			DELAY(5);
		}
		if (retry == 0) {
			ret->expect_irq = 1;
			return;
		}

		chp->wdc->dma_status =
		    (*chp->wdc->dma_finish)
		    (chp->wdc->dma_arg, chp->channel,
			xfer->drive, 1);

		if (chp->wdc->dma_status & WDC_DMAST_UNDER)
			xfer->c_bcount = 1;
		else
			xfer->c_bcount = 0;
	}

	as->protocol_phase = as_none;

	if (xfer->c_flags & C_SENSE) {
		if (chp->ch_status & WDCS_ERR) {
			if (chp->ch_error & WDCE_ABRT) {
				WDCDEBUG_PRINT(("wdc_atapi_intr: request_sense aborted, "
						"calling wdc_atapi_done()"
					), DEBUG_INTR);
				xfer->next = wdc_atapi_done;
				return;
			}

			/*
			 * request sense failed ! it's not supposed
 			 * to be possible
			 */
			sc_xfer->error = XS_SHORTSENSE;
		} else if (xfer->c_bcount < sizeof(sc_xfer->sense)) {
			/* use the sense we just read */
			sc_xfer->error = XS_SENSE;
		} else {
			/*
			 * command completed, but no data was read.
			 * use the short sense we saved previously.
			 */
			sc_xfer->error = XS_SHORTSENSE;
		}
	} else {
		sc_xfer->resid = xfer->c_bcount;
		if (chp->ch_status & WDCS_ERR) {
			if (!atapi_to_scsi_sense(sc_xfer, chp->ch_error) &&
			    (sc_xfer->sc_link->quirks &
			     ADEV_NOSENSE) == 0) {
				/*
				 * let the driver issue a
				 * 'request sense'
				 */
				xfer->databuf = &sc_xfer->sense;
				xfer->c_bcount = sizeof(sc_xfer->sense);
				xfer->c_skip = 0;
				xfer->c_done = NULL;
				xfer->c_flags |= C_SENSE;
				xfer->next = wdc_atapi_real_start;
				return;
			}
		}
	}

        if ((xfer->c_flags & C_DMA) &&
	    (chp->wdc->dma_status & ~WDC_DMAST_UNDER)) {
		ata_dmaerr(drvp);
		sc_xfer->error = XS_RESET;

		xfer->next = wdc_atapi_reset;
		return;
	}

	if (xfer->c_bcount != 0) {
		WDCDEBUG_PRINT(("wdc_atapi_intr: bcount value is "
				"%d after io\n", xfer->c_bcount), DEBUG_XFERS);
	}
#ifdef DIAGNOSTIC
	if (xfer->c_bcount < 0) {
		printf("wdc_atapi_intr warning: bcount value "
		       "is %d after io\n", xfer->c_bcount);
	}
#endif

	WDCDEBUG_PRINT(("wdc_atapi_intr: wdc_atapi_done() (end), error 0x%x "
			"\n", sc_xfer->error),
		       DEBUG_INTR);

	if (xfer->c_done)
		xfer->next = xfer->c_done;
	else
		xfer->next = wdc_atapi_done;

	return;
}

void
wdc_atapi_pio_intr(struct channel_softc *chp, struct wdc_xfer *xfer,
    int timeout, struct atapi_return_args *ret)
{
	struct scsi_xfer *sc_xfer = xfer->cmd;
	struct atapiscsi_softc *as = sc_xfer->sc_link->adapter_softc;
	u_int8_t ireason;

	wdc_atapi_update_status(chp);

	if (chp->ch_status & WDCS_BSY) {
		if (timeout)
			goto timeout;

		return;
	}

	if (!wdc_atapi_drive_selected(chp, xfer->drive)) {
		WDCDEBUG_PRINT(("wdc_atapi_intr_for_us: wrong drive selected\n"), DEBUG_INTR);
		wdc_set_drive(chp, xfer->drive);
		delay (1);

		if (!timeout)
			return;
	}

	if ((xfer->c_flags & C_MEDIA_ACCESS) &&
	    !(chp->ch_status & (WDCS_DSC | WDCS_DRQ))) {
		if (timeout)
			goto timeout;

		ret->delay = 100;
		return;
	}

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

	ireason = CHP_READ_REG(chp, wdr_ireason);
	WDC_LOG_REG(chp, wdr_ireason, ireason);

	WDCDEBUG_PRINT(("Phase %d, (0x%b, 0x%x) ", as->protocol_phase,
	    chp->ch_status, WDCS_BITS, ireason), DEBUG_INTR );

	switch (as->protocol_phase) {
	case as_data:
		if ((chp->ch_status & WDCS_DRQ) ||
		    (ireason & 3) != 3) {
			if (timeout)
				goto timeout;

			wdc_atapi_intr_data(chp, xfer, timeout, ret);
			return;
		}

	case as_completed:
		if ((chp->ch_status & WDCS_DRQ) ||
		    (ireason & 3) != 3) {
			if (timeout)
				goto timeout;

			ret->delay = 100;
			return;
		}

		wdc_atapi_intr_complete(chp, xfer, timeout, ret);
		return;

	default:
		printf ("atapiscsi: Shouldn't get here\n");
		sc_xfer->error = XS_DRIVER_STUFFUP;
		xfer->next = wdc_atapi_reset;
		return;
	}

	return;
timeout:
	ireason = CHP_READ_REG(chp, wdr_ireason);
	WDC_LOG_REG(chp, wdr_ireason, ireason);

	printf("%s:%d:%d: device timeout, c_bcount=%d, c_skip=%d, "
	    "status=0x%b, ireason=0x%x\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive,
	    xfer->c_bcount, xfer->c_skip, chp->ch_status, WDCS_BITS, ireason);

	sc_xfer->error = XS_TIMEOUT;
	xfer->next = wdc_atapi_reset;
	return;
}

void
wdc_atapi_ctrl(struct channel_softc *chp, struct wdc_xfer *xfer,
    int timeout, struct atapi_return_args *ret)
{
	struct scsi_xfer *sc_xfer = xfer->cmd;
	struct ata_drive_datas *drvp = &chp->ch_drive[xfer->drive];
	char *errstring = NULL;

 	wdc_atapi_update_status(chp);

	if (!timeout) {
		switch (drvp->state) {
		case ATAPI_IDENTIFY_WAIT_STATE:
			if (chp->ch_status & WDCS_BSY)
				return;
			break;
		default:
			if (chp->ch_status & (WDCS_BSY | WDCS_DRQ))
				return;
			break;
		}
	}

	if (!wdc_atapi_drive_selected(chp, xfer->drive))
	{
		wdc_set_drive(chp, xfer->drive);
		delay (1);
	}

	if (timeout) {
		int trigger_timeout = 1;

		switch (drvp->state) {
		case ATAPI_DEVICE_RESET_WAIT_STATE:
			errstring = "Device Reset Wait";
			drvp->drive_flags &= ~DRIVE_DEVICE_RESET;
			break;

		case ATAPI_IDENTIFY_WAIT_STATE:
			errstring = "Identify";
			if (!(chp->ch_status & WDCS_BSY) &&
			    (chp->ch_status & (WDCS_DRQ | WDCS_ERR)))
				trigger_timeout = 0;

			break;

		case ATAPI_PIOMODE_STATE:
			errstring = "Post-Identify";
			if (!(chp->ch_status & (WDCS_BSY | WDCS_DRQ)))
				trigger_timeout = 0;
			break;

		case ATAPI_PIOMODE_WAIT_STATE:
			errstring = "PIOMODE";
			if (chp->ch_status & (WDCS_BSY | WDCS_DRQ))
				drvp->drive_flags &= ~DRIVE_MODE;
			else
				trigger_timeout = 0;
			break;
		case ATAPI_DMAMODE_WAIT_STATE:
			errstring = "dmamode";
			if (chp->ch_status & (WDCS_BSY | WDCS_DRQ))
				drvp->drive_flags &= ~(DRIVE_DMA | DRIVE_UDMA);
			else
				trigger_timeout = 0;
			break;

		default:
			errstring = "unknown state";
			break;
		}

		if (trigger_timeout)
			goto timeout;
	}

	WDCDEBUG_PRINT(("wdc_atapi_ctrl %s:%d:%d state %d\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, drvp->drive, drvp->state),
	    DEBUG_INTR | DEBUG_FUNCS);

	switch (drvp->state) {
		/* My ATAPI slave device likes to assert DASP-/PDIAG- until
		   it is DEVICE RESET. This causes the LED to stay on.

		   There is a trade-off here. This drive will cause any
		   play-back or seeks happening to be interrupted.

		   Note that the bus reset that triggered this state
		   (which may have been caused by the other drive on
		   the chain) need not interrupt this playback. It happens
		   to on my Smart & Friendly CD burner.

		   - csapuntz@
		*/
	case ATAPI_RESET_BASE_STATE:
		if ((drvp->drive_flags & DRIVE_DEVICE_RESET) == 0) {
			drvp->state = ATAPI_IDENTIFY_STATE;
			break;
		}

		wdccommandshort(chp, drvp->drive, ATAPI_DEVICE_RESET);
		drvp->state = ATAPI_DEVICE_RESET_WAIT_STATE;
		ret->delay = ATAPI_RESET_DELAY;
		ret->timeout = ATAPI_RESET_WAIT;
		break;

	case ATAPI_DEVICE_RESET_WAIT_STATE:
		/* FALLTHROUGH */

	case ATAPI_IDENTIFY_STATE:
		wdccommandshort(chp, drvp->drive, ATAPI_IDENTIFY_DEVICE);
		drvp->state = ATAPI_IDENTIFY_WAIT_STATE;
		ret->delay = 10;
		ret->timeout = ATAPI_RESET_WAIT;
		break;

	case ATAPI_IDENTIFY_WAIT_STATE: {
		int idx = 0;

		while ((chp->ch_status & WDCS_DRQ) &&
		    idx++ < 20) {
			wdcbit_bucket(chp, 512);

			DELAY(1);
			wdc_atapi_update_status(chp);
		}

		drvp->state = ATAPI_PIOMODE_STATE;
		/*
		 * Note, we can't go directly to set PIO mode
		 * because the drive is free to assert BSY
		 * after the transfer
		 */
		break;
	}

	case ATAPI_PIOMODE_STATE:
		/* Don't try to set mode if controller can't be adjusted */
		if ((chp->wdc->cap & WDC_CAPABILITY_MODE) == 0)
			goto ready;
		/* Also don't try if the drive didn't report its mode */
		if ((drvp->drive_flags & DRIVE_MODE) == 0)
			goto ready;
		/* SET FEATURES 0x08 is only for PIO mode > 2 */
		if (drvp->PIO_mode <= 2)
			goto ready;
		wdccommand(chp, drvp->drive, SET_FEATURES, 0, 0, 0,
		    0x08 | drvp->PIO_mode, WDSF_SET_MODE);
		drvp->state = ATAPI_PIOMODE_WAIT_STATE;
		ret->timeout = ATAPI_CTRL_WAIT;
		ret->expect_irq = 1;
		break;
	case ATAPI_PIOMODE_WAIT_STATE:
		if (chp->wdc->cap & WDC_CAPABILITY_IRQACK)
			chp->wdc->irqack(chp);
		if (chp->ch_status & WDCS_ERR) {
			/* Downgrade straight to PIO mode 3 */
			drvp->PIO_mode = 3;
			chp->wdc->set_modes(chp);
		}
	/* FALLTHROUGH */

	case ATAPI_DMAMODE_STATE:
		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 ready;
		}
		drvp->state = ATAPI_DMAMODE_WAIT_STATE;

		ret->timeout = ATAPI_CTRL_WAIT;
		ret->expect_irq = 1;
		break;

	case ATAPI_DMAMODE_WAIT_STATE:
		if (chp->wdc->cap & WDC_CAPABILITY_IRQACK)
			chp->wdc->irqack(chp);
		if (chp->ch_status & WDCS_ERR)
			drvp->drive_flags &= ~(DRIVE_DMA | DRIVE_UDMA);
	/* FALLTHROUGH */

	case ATAPI_READY_STATE:
	ready:
		drvp->state = ATAPI_READY_STATE;
		xfer->next = wdc_atapi_real_start;
		break;
	}
	return;

timeout:
	printf("%s:%d:%d: %s timed out\n",
	    chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive, errstring);
	sc_xfer->error = XS_TIMEOUT;
	xfer->next = wdc_atapi_reset;
	return;

}

void
wdc_atapi_tape_done(struct channel_softc *chp, struct wdc_xfer *xfer,
    int timeout, struct atapi_return_args *ret)
{
	struct scsi_xfer *sc_xfer = xfer->cmd;

	if (sc_xfer->error != XS_NOERROR) {
		xfer->next = wdc_atapi_done;
		return;
	}

	_lto3b(xfer->transfer_len,
	    ((struct scsi_rw_tape *)
		sc_xfer->cmd)->len);

	xfer->c_bcount = sc_xfer->datalen;
	xfer->c_done = NULL;
	xfer->c_skip = 0;

	xfer->next = wdc_atapi_real_start;
	return;
}

void
wdc_atapi_done(struct channel_softc *chp, struct wdc_xfer *xfer,
    int timeout, struct atapi_return_args *ret)
{
	struct scsi_xfer *sc_xfer = xfer->cmd;

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

	if (xfer->c_flags & C_POLL)
		wdc_enable_intr(chp);

	scsi_done(sc_xfer);

	xfer->next = NULL;
	return;
}

void
wdc_atapi_reset(struct channel_softc *chp, struct wdc_xfer *xfer,
    int timeout, struct atapi_return_args *ret)
{
	struct ata_drive_datas *drvp = &chp->ch_drive[xfer->drive];

	if (drvp->state == 0) {
		xfer->next = wdc_atapi_done;
		return;
	}

	WDCDEBUG_PRINT(("wdc_atapi_reset\n"), DEBUG_XFERS);
	wdccommandshort(chp, xfer->drive, ATAPI_SOFT_RESET);
	drvp->state = ATAPI_IDENTIFY_STATE;

	drvp->n_resets++;
	/* Some ATAPI devices need extra time to find their
	   brains after a reset
	 */
	xfer->next = wdc_atapi_reset_2;
	ret->delay = ATAPI_RESET_DELAY;
	ret->timeout = ATAPI_RESET_WAIT;
	return;
}

void
wdc_atapi_reset_2(struct channel_softc *chp, struct wdc_xfer *xfer,
    int timeout, struct atapi_return_args *ret)
{
	struct ata_drive_datas *drvp = &chp->ch_drive[xfer->drive];
	struct scsi_xfer *sc_xfer = xfer->cmd;

	if (timeout) {
		printf("%s:%d:%d: soft reset failed\n",
		    chp->wdc->sc_dev.dv_xname, chp->channel,
		    xfer->drive);
		sc_xfer->error = XS_SELTIMEOUT;
		wdc_reset_channel(drvp, 0);

		xfer->next = wdc_atapi_done;
		return;
	}

	wdc_atapi_update_status(chp);

	if (chp->ch_status & (WDCS_BSY | WDCS_DRQ)) {
		return;
	}

	xfer->next = wdc_atapi_done;
	return;
}


code:
#	$OpenBSD: files.atapiscsi,v 1.8 2011/05/25 03:26:20 matthew Exp $
# ATAPI transport for SCSI protocol
#

device	atapiscsi: scsi, wdc_base
attach	atapiscsi at atapi
file	dev/atapiscsi/atapiscsi.c	atapiscsi