diff options
Diffstat (limited to 'usr/src')
48 files changed, 14430 insertions, 58 deletions
| diff --git a/usr/src/tools/findunref/exception_list b/usr/src/tools/findunref/exception_list index 11c071c7cd..bb24cb9562 100644 --- a/usr/src/tools/findunref/exception_list +++ b/usr/src/tools/findunref/exception_list @@ -48,7 +48,6 @@  ./src/cmd/oawk/EXPLAIN  ./src/cmd/rpcsvc/nis/rpc.nisd/resolv_server/DNS_FWD  ./src/cmd/vi/port/ex.news -./closed/uts/common/io/dktp/controller/ata/capacity.notes  ./src/cmd/ssh/doc/*  # diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files index 4a9f3d19b8..b025f1d7c6 100644 --- a/usr/src/uts/common/Makefile.files +++ b/usr/src/uts/common/Makefile.files @@ -499,7 +499,7 @@ CLONE_OBJS +=	clone.o  CN_OBJS +=	cons.o  DLD_OBJS +=	dld_drv.o dld_proto.o dld_str.o -		 +  DLS_OBJS +=	dls.o dls_link.o dls_mod.o dls_stat.o dls_vlan.o dls_soft_ring.o  GLD_OBJS +=     gld.o gldutil.o @@ -674,14 +674,6 @@ VUIDPS2_OBJS += vuidmice.o vuidps2.o  SYSINIT_OBJS +=	sysinit.o sysinit_ddi.o -DADK_OBJS +=	dadk.o - -GDA_OBJS +=	gda.o - -STRATEGY_OBJS += strategy.o - -CMDK_OBJS +=	cmdk.o -  HPCSVC_OBJS += hpcsvc.o  PCIHPNEXUS_OBJS += pcihp.o diff --git a/usr/src/uts/common/Makefile.rules b/usr/src/uts/common/Makefile.rules index 24ab2e0f46..d224eabeb8 100644 --- a/usr/src/uts/common/Makefile.rules +++ b/usr/src/uts/common/Makefile.rules @@ -444,18 +444,6 @@ $(OBJS_DIR)/%.o: 		$(UTSBASE)/common/io/bge/%.c  	$(COMPILE.c) -o $@ $<  	$(CTFCONVERT_O) -$(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/dktp/dcdev/%.c -	$(COMPILE.c) -o $@ $< -	$(CTFCONVERT_O) - -$(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/dktp/disk/%.c -	$(COMPILE.c) -o $@ $< -	$(CTFCONVERT_O) - -$(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/dktp/drvobj/%.c -	$(COMPILE.c) -o $@ $< -	$(CTFCONVERT_O) -  $(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/dld/%.c  	$(COMPILE.c) -o $@ $<  	$(CTFCONVERT_O) @@ -1180,15 +1168,6 @@ $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/audio/sada/drv/audiots/%.c  $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/bge/%.c  	@($(LHEAD) $(LINT.c) $< $(LTAIL)) -$(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/dktp/dcdev/%.c -	@($(LHEAD) $(LINT.c) $< $(LTAIL)) - -$(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/dktp/disk/%.c -	@($(LHEAD) $(LINT.c) $< $(LTAIL)) - -$(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/dktp/drvobj/%.c -	@($(LHEAD) $(LINT.c) $< $(LTAIL)) -  $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/dld/%.c  	@($(LHEAD) $(LINT.c) $< $(LTAIL)) diff --git a/usr/src/uts/i86pc/Makefile.files b/usr/src/uts/i86pc/Makefile.files index 730f9e34ad..d86d50e539 100644 --- a/usr/src/uts/i86pc/Makefile.files +++ b/usr/src/uts/i86pc/Makefile.files @@ -129,6 +129,13 @@ AGPGART_OBJS	+=	agpgart.o \  AGPTARGET_OBJS	+=	agptarget.o  AMD64GART_OBJS	+=	amd64_gart.o +GHD_OBJS +=	ghd.o ghd_debug.o ghd_dma.o ghd_queue.o ghd_scsa.o \ +		ghd_scsi.o ghd_timer.o ghd_waitq.o ghd_gcmd.o + +ATA_OBJS +=	$(GHD_OBJS) ata_blacklist.o ata_common.o ata_disk.o \ +		ata_dma.o atapi.o atapi_fsm.o ata_debug.o \ +		sil3xxx.o +  include $(SRC)/common/mc/mc-amd/Makefile.mcamd  MCAMD_OBJS	+= \  	$(MCAMD_CMN_OBJS) \ diff --git a/usr/src/uts/i86pc/Makefile.i86pc.shared b/usr/src/uts/i86pc/Makefile.i86pc.shared index 2f8605084d..74b267b9e3 100644 --- a/usr/src/uts/i86pc/Makefile.i86pc.shared +++ b/usr/src/uts/i86pc/Makefile.i86pc.shared @@ -245,6 +245,7 @@ DRV_KMODS	+= pci  DRV_KMODS	+= pcie_pci  DRV_KMODS	+= npe +DRV_KMODS	+= ata  DRV_KMODS	+= fd  DRV_KMODS	+= fdc  DRV_KMODS	+= kb8042 @@ -264,7 +265,6 @@ DRV_KMODS	+= cpc  DRV_KMODS	+= mc-amd  DRV_KMODS	+= power -$(CLOSED_BUILD)CLOSED_DRV_KMODS		+= ata  $(CLOSED_BUILD)CLOSED_DRV_KMODS		+= audiovia823x  $(CLOSED_BUILD)CLOSED_DRV_KMODS		+= audioens  $(CLOSED_BUILD)CLOSED_DRV_KMODS		+= audioixp diff --git a/usr/src/uts/i86pc/ata/Makefile b/usr/src/uts/i86pc/ata/Makefile new file mode 100644 index 0000000000..e371b5bc50 --- /dev/null +++ b/usr/src/uts/i86pc/ata/Makefile @@ -0,0 +1,92 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# uts/i86pc/ata/Makefile +# +# Copyright 2006 Sun Microsystems, Inc.  All rights reserved. +# Use is subject to license terms. +# +#pragma ident	"%Z%%M%	%I%	%E% SMI" +# +#	This makefile drives the production of the ata "drv" +#	kernel module. +# +#	i86pc implementation architecture dependent +# + +# +#	Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE	  = ../.. + +# +#	Define the module and object file sets. +# +MODULE		= ata +OBJECTS		= $(ATA_OBJS:%=$(OBJS_DIR)/%) +LINTS		= $(ATA_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE	= $(ROOT_PSM_DRV_DIR)/$(MODULE) +CONF_SRCDIR	= $(UTSBASE)/intel/io/dktp/controller/ata + +# +#	Include common rules. +# +include $(UTSBASE)/i86pc/Makefile.i86pc + +# +#	Define targets +# +ALL_TARGET	= $(BINARY) $(CONFMOD) +LINT_TARGET	= $(MODULE).lint +INSTALL_TARGET	= $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE) + +# +#	Overrides. +# +#DEBUG_FLGS	= -DATA_DEBUG -DGHD_DEBUG -DDEBUG +DEBUG_FLGS	=  +DEBUG_DEFS	+= $(DEBUG_FLGS) +INC_PATH	+= -I$(UTSBASE)/intel/io/dktp/hba/ghd + +# +#	Default build targets. +# +.KEEP_STATE: + +def:		$(DEF_DEPS) + +all:		$(ALL_DEPS) + +clean:		$(CLEAN_DEPS) + +clobber:	$(CLOBBER_DEPS) + +lint:		$(LINT_DEPS) + +modlintlib:	$(MODLINTLIB_DEPS) + +clean.lint:	$(CLEAN_LINT_DEPS) + +install:	$(INSTALL_DEPS) + +# +#	Include common targets. +# +include $(UTSBASE)/i86pc/Makefile.targ diff --git a/usr/src/uts/intel/Makefile.files b/usr/src/uts/intel/Makefile.files index e4eda3b320..87d14b8eb7 100644 --- a/usr/src/uts/intel/Makefile.files +++ b/usr/src/uts/intel/Makefile.files @@ -111,10 +111,18 @@ XMEMFS_OBJS +=		\  #  #	Driver modules  # -SD_OBJS += sd.o sd_xbuf.o +CMDK_OBJS +=	cmdk.o  CMLB_OBJS += cmlb.o +DADK_OBJS +=	dadk.o + +GDA_OBJS +=	gda.o + +SD_OBJS += sd.o sd_xbuf.o + +STRATEGY_OBJS += strategy.o +  VGATEXT_OBJS += vgatext.o vgasubr.o  # diff --git a/usr/src/uts/intel/Makefile.rules b/usr/src/uts/intel/Makefile.rules index 8ea1d6a4b1..467289ca7f 100644 --- a/usr/src/uts/intel/Makefile.rules +++ b/usr/src/uts/intel/Makefile.rules @@ -20,7 +20,7 @@  # CDDL HEADER END  #  # -# Copyright 2005 Sun Microsystems, Inc.  All rights reserved. +# Copyright 2006 Sun Microsystems, Inc.  All rights reserved.  # Use is subject to license terms.  #  # ident	"%Z%%M%	%I%	%E% SMI" @@ -89,6 +89,26 @@ $(OBJS_DIR)/%.o:		$(UTSBASE)/intel/io/amr/%.c  	$(COMPILE.c) -o $@ $<  	$(CTFCONVERT_O) +$(OBJS_DIR)/%.o:		$(UTSBASE)/intel/io/dktp/controller/ata/%.c +	$(COMPILE.c) -o $@ $< +	$(CTFCONVERT_O) + +$(OBJS_DIR)/%.o:		$(UTSBASE)/intel/io/dktp/dcdev/%.c +	$(COMPILE.c) -o $@ $< +	$(CTFCONVERT_O) + +$(OBJS_DIR)/%.o:		$(UTSBASE)/intel/io/dktp/disk/%.c +	$(COMPILE.c) -o $@ $< +	$(CTFCONVERT_O) + +$(OBJS_DIR)/%.o:		$(UTSBASE)/intel/io/dktp/drvobj/%.c +	$(COMPILE.c) -o $@ $< +	$(CTFCONVERT_O) + +$(OBJS_DIR)/%.o:		$(UTSBASE)/intel/io/dktp/hba/ghd/%.c +	$(COMPILE.c) -o $@ $< +	$(CTFCONVERT_O) +  $(OBJS_DIR)/%.o:		$(UTSBASE)/intel/io/scsi/targets/%.c  	$(COMPILE.c) -o $@ $<  	$(CTFCONVERT_O) @@ -163,6 +183,21 @@ $(LINTS_DIR)/%.ln:		$(UTSBASE)/intel/io/aac/%.c  $(LINTS_DIR)/%.ln:		$(UTSBASE)/intel/io/amr/%.c  	@($(LHEAD) $(LINT.c) $< $(LTAIL)) +$(LINTS_DIR)/%.ln:		$(UTSBASE)/intel/io/dktp/controller/ata/%.c +	@($(LHEAD) $(LINT.c) $< $(LTAIL)) + +$(LINTS_DIR)/%.ln:		$(UTSBASE)/intel/io/dktp/dcdev/%.c +	@($(LHEAD) $(LINT.c) $< $(LTAIL)) + +$(LINTS_DIR)/%.ln:		$(UTSBASE)/intel/io/dktp/disk/%.c +	@($(LHEAD) $(LINT.c) $< $(LTAIL)) + +$(LINTS_DIR)/%.ln:		$(UTSBASE)/intel/io/dktp/drvobj/%.c +	@($(LHEAD) $(LINT.c) $< $(LTAIL)) + +$(LINTS_DIR)/%.ln:		$(UTSBASE)/intel/io/dktp/hba/ghd/%.c +	@($(LHEAD) $(LINT.c) $< $(LTAIL)) +  $(LINTS_DIR)/%.ln:		$(UTSBASE)/intel/io/scsi/targets/%.c  	@($(LHEAD) $(LINT.c) $< $(LTAIL)) diff --git a/usr/src/uts/intel/io/dktp/controller/ata/ata.conf b/usr/src/uts/intel/io/dktp/controller/ata/ata.conf new file mode 100644 index 0000000000..c92fce214a --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/ata.conf @@ -0,0 +1,61 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License").   +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2004 Sun Microsystems, Inc.  All rights reserved. +# Use is subject to license terms. +# + +#ident	"%Z%%M%	%I%	%E% SMI" + +# +# don't change these +# +device_type="ide"; +flow_control="dmult"; +queue="qfifo"; +max_transfer=0x100; + +# Enable dma +ata-options=0x1; + +# +# for PIO performance upgrade - set block factor to 0x10 +# +drive0_block_factor=0x1; +drive1_block_factor=0x1; + +# +# some laptop systems require setting this flag +# +timing_flags=0x0; + +# +# To cause the driver to initiailize the drives to automatically +# enter standby mode, the following property sets the drives +# standby timer. The units are seconds, rounded up to the drive's +# timer resolution.   +# +# 	standby=-1	don't modify the drive's current setting +#	standby=0	disable standby timer +#	standby=n	n == number of seconds to set the timer to +# + +#standby=900; diff --git a/usr/src/uts/intel/io/dktp/controller/ata/ata_blacklist.c b/usr/src/uts/intel/io/dktp/controller/ata/ata_blacklist.c new file mode 100644 index 0000000000..7a0e65ffc8 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/ata_blacklist.c @@ -0,0 +1,86 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2005 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/debug.h> +#include <sys/pci.h> + +#include "ata_blacklist.h" + +pcibl_t	ata_pciide_blacklist[] = { +	/* +	 * The Nat SEMI PC87415 doesn't handle data and status byte +	 * synchornization correctly if an I/O error occurs that +	 * stops the request before the last sector.  I think it can +	 * cause lockups. See section 7.4.5.3 of the PC87415 spec. +	 * It's also rumored to be a "single fifo" type chip that can't +	 * DMA on both channels correctly. +	 */ +	{ 0x100b, 0xffff, 0x2, 0xffff, ATA_BL_BOGUS}, + +	/* +	 * The CMD chip 0x646 does not support the use of interrupt bit +	 * in the busmaster ide status register when PIO is used. +	 * DMA is explicitly disabled for this legacy chip +	 */ +	{ 0x1095, 0xffff, 0x0646, 0xffff, ATA_BL_BMSTATREG_PIO_BROKEN | +							ATA_BL_NODMA}, + +	/* +	 * Ditto for Serverworks CSB5 and CSB6 chips, but we can +	 * handle DMA.  Also, when emulating OSB4 mode, the simplex +	 * bit lies! +	 */ +	{ 0x1166, 0xffff, 0x0212, 0xffff, ATA_BL_BMSTATREG_PIO_BROKEN| +							ATA_BL_NO_SIMPLEX}, +	{ 0x1166, 0xffff, 0x0213, 0xffff, ATA_BL_BMSTATREG_PIO_BROKEN}, + +	{ 0, 0, 0, 0, 0 } +}; + +/* + * add drives that have DMA or other problems to this list + */ + +atabl_t	ata_drive_blacklist[] = { +	{ "NEC CD-ROM DRIVE:260",	ATA_BL_1SECTOR }, +	{ "NEC CD-ROM DRIVE:272",	ATA_BL_1SECTOR }, +	{ "NEC CD-ROM DRIVE:273",	ATA_BL_1SECTOR }, + +	{ /* Mitsumi */ "FX001DE",	ATA_BL_1SECTOR }, + +	{ "fubar", +		(ATA_BL_NODMA | +		ATA_BL_1SECTOR | +		ATA_BL_NORVRT | +		ATA_BL_BOGUS | +		ATA_BL_BMSTATREG_PIO_BROKEN) +	}, +	NULL +}; diff --git a/usr/src/uts/intel/io/dktp/controller/ata/ata_blacklist.h b/usr/src/uts/intel/io/dktp/controller/ata/ata_blacklist.h new file mode 100644 index 0000000000..57f6d110b8 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/ata_blacklist.h @@ -0,0 +1,90 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2005 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ATA_BLACKLIST_H +#define	_ATA_BLACKLIST_H + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#ifdef	__cplusplus +extern "C" { +#endif + +/* + * This is the PCI-IDE chip blacklist + */ +typedef struct { +	uint_t	b_vendorid; +	uint_t	b_vmask; +	uint_t	b_deviceid; +	uint_t	b_dmask; +	uint_t	b_flags; +} pcibl_t; + +extern	pcibl_t	ata_pciide_blacklist[]; + +/* + * This is the drive blacklist + */ +typedef	struct { +	char	*b_model; +	uint_t	 b_flags; +} atabl_t; + +extern	atabl_t	ata_drive_blacklist[]; + +/* + * use the same flags for both lists + */ +#define	ATA_BL_BOGUS	0x1	/* only use in compatibility mode */ +#define	ATA_BL_NODMA	0x2	/* don't use DMA on this one */ +#define	ATA_BL_1SECTOR	0x4	/* limit PIO transfers to 1 sector */ +#define	ATA_BL_BMSTATREG_PIO_BROKEN	0x8 + +				/* +				 * do not use bus master ide status register +				 * if not doing dma, or if it does not work +				 * properly when doing DMA (for example, on +				 * some lx50's!) +				 */ + + +#define	ATA_BL_NORVRT	0x10 +				/* +				 * Don't enable revert to power-on +				 * defaults before rebooting +				 */ + +#define	ATA_BL_NO_SIMPLEX	0x20 +				/* +				 * Ignore simplex bit on this device +				 * if set +				 */ +#ifdef	__cplusplus +} +#endif + +#endif /* _ATA_BLACKLIST_H */ diff --git a/usr/src/uts/intel/io/dktp/controller/ata/ata_cmd.h b/usr/src/uts/intel/io/dktp/controller/ata/ata_cmd.h new file mode 100644 index 0000000000..9483f1e9b0 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/ata_cmd.h @@ -0,0 +1,89 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 1996 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ATA_CMD_H +#define	_ATA_CMD_H + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#ifdef	__cplusplus +extern "C" { +#endif + +/* + * Common ATA commands. + */ +#define	ATC_DIAG	0x90    /* diagnose command 			*/ +#define	ATC_RECAL	0x10	/* restore cmd, bottom 4 bits step rate */ +#define	ATC_FORMAT	0x50	/* format track command 		*/ +#define	ATC_SET_FEAT	0xef	/* set features				*/ +#define	ATC_IDLE_IMMED	0xe1	/* idle immediate			*/ +#define	ATC_STANDBY_IM	0xe0	/* standby immediate			*/ +#define	ATC_DOOR_LOCK	0xde	/* door lock				*/ +#define	ATC_DOOR_UNLOCK	0xdf	/* door unlock				*/ +#define	ATC_IDLE	0xe3	/* idle					*/ + +/* + * ATA/ATAPI-4 disk commands. + */ +#define	ATC_DEVICE_RESET	0x08    /* ATAPI device reset */ +#define	ATC_EJECT		0xed	/* media eject */ +#define	ATC_FLUSH_CACHE		0xe7	/* flush write-cache */ +#define	ATC_ID_DEVICE		0xec    /* IDENTIFY DEVICE */ +#define	ATC_ID_PACKET_DEVICE	0xa1	/* ATAPI identify packet device */ +#define	ATC_INIT_DEVPARMS	0x91	/* initialize device parameters */ +#define	ATC_PACKET		0xa0	/* ATAPI packet */ +#define	ATC_RDMULT		0xc4	/* read multiple */ +#define	ATC_RDSEC		0x20    /* read sector */ +#define	ATC_RDVER		0x40	/* read verify */ +#define	ATC_READ_DMA		0xc8	/* read (multiple) w/DMA */ +#define	ATC_SEEK		0x70    /* seek */ +#define	ATC_SERVICE		0xa2	/* queued/overlap service */ +#define	ATC_SETMULT		0xc6	/* set multiple mode */ +#define	ATC_WRITE_DMA		0xca	/* write (multiple) w/DMA */ +#define	ATC_WRMULT		0xc5	/* write multiple */ +#define	ATC_WRSEC		0x30    /* write sector */ + +/* + * Low bits for Read/Write commands... + */ +#define	ATCM_ECCRETRY	0x01    /* Enable ECC and RETRY by controller 	*/ +				/* enabled if bit is CLEARED!!! 	*/ +#define	ATCM_LONGMODE	0x02    /* Use Long Mode (get/send data & ECC) 	*/ + + +/* + * Obsolete ATA commands. + */ + +#define	ATC_RDLONG	0x23    /* read long without retry	*/ +#define	ATC_ACK_MC	0xdb	/* acknowledge media change		*/ + +#ifdef	__cplusplus +} +#endif + +#endif /* _ATA_CMD_H */ diff --git a/usr/src/uts/intel/io/dktp/controller/ata/ata_common.c b/usr/src/uts/intel/io/dktp/controller/ata/ata_common.c new file mode 100644 index 0000000000..c6a89ed795 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/ata_common.c @@ -0,0 +1,3377 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2005 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include <sys/types.h> +#include <sys/modctl.h> +#include <sys/debug.h> +#include <sys/promif.h> +#include <sys/pci.h> +#include <sys/errno.h> +#include <sys/open.h> +#include <sys/uio.h> +#include <sys/cred.h> + +#include "ata_common.h" +#include "ata_disk.h" +#include "atapi.h" +#include "ata_blacklist.h" +#include "sil3xxx.h" + +/* + * Solaris Entry Points. + */ + +static	int	ata_probe(dev_info_t *dip); +static	int	ata_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); +static	int	ata_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); +static	int	ata_bus_ctl(dev_info_t *d, dev_info_t *r, ddi_ctl_enum_t o, +			void *a, void *v); +static	uint_t	ata_intr(caddr_t arg); + +/* + * GHD Entry points + */ + +static	int	ata_get_status(void *hba_handle, void *intr_status); +static	void	ata_process_intr(void *hba_handle, void *intr_status); +static	int	ata_hba_start(void *handle, gcmd_t *gcmdp); +static	void	ata_hba_complete(void *handle, gcmd_t *gcmdp, int do_callback); +static	int	ata_timeout_func(void *hba_handle, gcmd_t  *gcmdp, +			gtgt_t *gtgtp, gact_t  action, int calltype); + +/* + * Local Function Prototypes + */ +static int ata_prop_lookup_int(dev_t match_dev, dev_info_t *dip, +		    uint_t flags, char *name, int defvalue); +static	int	ata_ctlr_fsm(uchar_t fsm_func, ata_ctl_t *ata_ctlp, +			ata_drv_t *ata_drvp, ata_pkt_t *ata_pktp, +				int *DoneFlgp); +static	void	ata_destroy_controller(dev_info_t *dip); +static	int	ata_drive_type(uchar_t drvhd, +			ddi_acc_handle_t io_hdl1, caddr_t ioaddr1, +			ddi_acc_handle_t io_hdl2, caddr_t ioaddr2, +			struct ata_id *ata_id_bufp); +static	ata_ctl_t *ata_init_controller(dev_info_t *dip); +static	ata_drv_t *ata_init_drive(ata_ctl_t *ata_ctlp, +			uchar_t targ, uchar_t lun); +static	int	ata_init_drive_pcidma(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +			dev_info_t *tdip); +static	int	ata_flush_cache(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp); +static	void	ata_init_pciide(dev_info_t *dip, ata_ctl_t *ata_ctlp); +static	int	ata_reset_bus(ata_ctl_t *ata_ctlp); +static	int	ata_setup_ioaddr(dev_info_t *dip, +			ddi_acc_handle_t *iohandle1, caddr_t *ioaddr1p, +			ddi_acc_handle_t *iohandle2, caddr_t *ioaddr2p, +			ddi_acc_handle_t *bm_hdlp, caddr_t *bm_addrp); +static	int	ata_software_reset(ata_ctl_t *ata_ctlp); +static	int	ata_start_arq(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +			ata_pkt_t *ata_pktp); +static	int	ata_strncmp(char *p1, char *p2, int cnt); +static	void	ata_uninit_drive(ata_drv_t *ata_drvp); + +static	int	ata_check_pciide_blacklist(dev_info_t *dip, uint_t flags); +static	int	ata_check_revert_to_defaults(ata_drv_t *ata_drvp); +static  void	ata_show_transfer_mode(ata_ctl_t *, ata_drv_t *); +static	int	ata_spec_init_controller(dev_info_t *dip); + + +/* + * Local static data + */ +static	void	*ata_state; + +static	tmr_t	ata_timer_conf; /* single timeout list for all instances */ +static	int	ata_watchdog_usec = 100000; /* check timeouts every 100 ms */ + +int	ata_hba_start_watchdog = 1000; +int	ata_process_intr_watchdog = 1000; +int	ata_reset_bus_watchdog = 1000; + + +/* + * number of seconds to wait during various operations + */ +int	ata_flush_delay = 5 * 1000000; +uint_t	ata_set_feature_wait = 4 * 1000000; +uint_t	ata_flush_cache_wait = 60 * 1000000;	/* may take a long time */ + +/* + * Change this for SFF-8070i support. Currently SFF-8070i is + * using a field in the IDENTIFY PACKET DEVICE response which + * already seems to be in use by some vendor's drives. I suspect + * SFF will either move their laslun field or provide a reliable + * way to validate it. + */ +int	ata_enable_atapi_luns = FALSE; + +/* + * set this to disable all DMA requests + */ +int	ata_dma_disabled = FALSE; + +/* + * set this to TRUE to enable storing the IDENTIFY DEVICE result in the + * "ata" or "atapi" property. + */ +int	ata_id_debug = FALSE; + +/* + * set this to TRUE to enable logging device-capability data + */ +int	ata_capability_data = FALSE; + +#define	ATAPRT(fmt)	ghd_err fmt + +/* + * DMA selection message pointers + */ +char *ata_cntrl_DMA_sel_msg; +char *ata_dev_DMA_sel_msg; + +/* + * bus nexus operations + */ +static	struct bus_ops	 ata_bus_ops; +static	struct bus_ops	*scsa_bus_ops_p; + +/* ARGSUSED */ +static int +ata_open(dev_t *devp, int flag, int otyp, cred_t *cred_p) +{ +	if (ddi_get_soft_state(ata_state, getminor(*devp)) == NULL) +		return (ENXIO); + +	return (0); +} + +/* + * The purpose of this function is to pass the ioaddress of the controller + * to the caller, specifically used for upgrade from pre-pciide + * to pciide nodes + */ +/* ARGSUSED */ +static int +ata_read(dev_t dev, struct uio *uio_p, cred_t *cred_p) +{ +	ata_ctl_t *ata_ctlp; +	char	buf[18]; +	long len; + +	ata_ctlp = ddi_get_soft_state(ata_state, getminor(dev)); + +	if (ata_ctlp == NULL) +		return (ENXIO); + +	(void) sprintf(buf, "%p\n", (void *) ata_ctlp->ac_ioaddr1); + +	len = strlen(buf) - uio_p->uio_offset; +	len = min(uio_p->uio_resid,  len); +	if (len <= 0) +		return (0); + +	return (uiomove((caddr_t)(buf + uio_p->uio_offset), len, +	    UIO_READ, uio_p)); +} + +int +ata_devo_reset( +	dev_info_t *dip, +	ddi_reset_cmd_t cmd) +{ +	ata_ctl_t *ata_ctlp; +	ata_drv_t *ata_drvp; +	int	   instance; +	int	   i; +	int	   rc; +	int	   flush_okay; + +	if (cmd != DDI_RESET_FORCE) +		return (0); + +	instance = ddi_get_instance(dip); +	ata_ctlp = ddi_get_soft_state(ata_state, instance); + +	if (!ata_ctlp) +		return (0); + +	/* +	 * reset ATA drives and flush the write cache of any drives +	 */ +	flush_okay = TRUE; +	for (i = 0; i < ATA_MAXTARG; i++) { +		if ((ata_drvp = CTL2DRV(ata_ctlp, i, 0)) == 0) +			continue; +		/* Don't revert to defaults for certain IBM drives */ +		if ((ata_drvp->ad_flags & AD_DISK) != 0 && +		    ((ata_drvp->ad_flags & AD_NORVRT) == 0)) { +			/* Enable revert to defaults when reset */ +			(void) ata_set_feature(ata_ctlp, ata_drvp, 0xCC, 0); +		} + +		/* +		 * skip flush cache if device type is cdrom +		 * +		 * notes: the structure definitions for ata_drvp->ad_id are +		 * defined for the ATA IDENTIFY_DEVICE, but if AD_ATAPI is set +		 * the struct holds data for the ATAPI IDENTIFY_PACKET_DEVICE +		 */ +		if (!IS_CDROM(ata_drvp)) { + +			/* +			 * Try the ATA/ATAPI flush write cache command +			 */ +			rc = ata_flush_cache(ata_ctlp, ata_drvp); +			ADBG_WARN(("ata_flush_cache %s\n", +				rc ? "okay" : "failed")); + +			if (!rc) +				flush_okay = FALSE; +		} + + +		/* +		 * do something else if flush cache not supported +		 */ +	} + +	/* +	 * just busy wait if any drive doesn't support FLUSH CACHE +	 */ +	if (!flush_okay) +		drv_usecwait(ata_flush_delay); +	return (0); +} + + +static struct cb_ops ata_cb_ops = { +	ata_open,		/* open */ +	nulldev,		/* close */ +	nodev,			/* strategy */ +	nodev,			/* print */ +	nodev,			/* dump */ +	ata_read,		/* read */ +	nodev,			/* write */ +	nodev,			/* ioctl */ +	nodev,			/* devmap */ +	nodev,			/* mmap */ +	nodev,			/* segmap */ +	nochpoll,		/* chpoll */ +	ddi_prop_op,		/* prop_op */ +	NULL,			/* stream info */ +	D_MP,			/* driver compatibility flag */ +	CB_REV,			/* cb_ops revision */ +	nodev,			/* aread */ +	nodev			/* awrite */ +}; + +static struct dev_ops	ata_ops = { +	DEVO_REV,		/* devo_rev, */ +	0,			/* refcnt  */ +	ddi_getinfo_1to1,	/* info */ +	nulldev,		/* identify */ +	ata_probe,		/* probe */ +	ata_attach,		/* attach */ +	ata_detach,		/* detach */ +	ata_devo_reset,		/* reset */ +	&ata_cb_ops,		/* driver operations */ +	NULL			/* bus operations */ +}; + +/* driver loadable module wrapper */ +static struct modldrv modldrv = { +	&mod_driverops,		/* Type of module. This one is a driver */ +	"ATA AT-bus attachment disk controller Driver",	/* module name */ +	&ata_ops,					/* driver ops */ +}; + +static struct modlinkage modlinkage = { +	MODREV_1, (void *)&modldrv, NULL +}; + +#ifdef ATA_DEBUG +int	ata_debug_init = FALSE; +int	ata_debug_probe = FALSE; +int	ata_debug_attach = FALSE; + +int	ata_debug = ADBG_FLAG_ERROR +		/* | ADBG_FLAG_ARQ */ +		/* | ADBG_FLAG_INIT */ +		/* | ADBG_FLAG_TRACE */ +		/* | ADBG_FLAG_TRANSPORT */ +		/* | ADBG_FLAG_WARN */ +		; +#endif + +int +_init(void) +{ +	int err; + +#ifdef ATA_DEBUG +	if (ata_debug_init) +		debug_enter("\nATA _INIT\n"); +#endif + +	if ((err = ddi_soft_state_init(&ata_state, sizeof (ata_ctl_t), 0)) != 0) +		return (err); + +	if ((err = scsi_hba_init(&modlinkage)) != 0) { +		ddi_soft_state_fini(&ata_state); +		return (err); +	} + +	/* save pointer to SCSA provided bus_ops struct */ +	scsa_bus_ops_p = ata_ops.devo_bus_ops; + +	/* make a copy of SCSA bus_ops */ +	ata_bus_ops = *(ata_ops.devo_bus_ops); + +	/* +	 * Modify our bus_ops to call our routines.  Our implementation +	 * will determine if the device is ATA or ATAPI/SCSA and react +	 * accordingly. +	 */ +	ata_bus_ops.bus_ctl = ata_bus_ctl; + +	/* patch our bus_ops into the dev_ops struct */ +	ata_ops.devo_bus_ops = &ata_bus_ops; + +	if ((err = mod_install(&modlinkage)) != 0) { +		scsi_hba_fini(&modlinkage); +		ddi_soft_state_fini(&ata_state); +	} + +	/* +	 * Initialize the per driver timer info. +	 */ + +	ghd_timer_init(&ata_timer_conf, drv_usectohz(ata_watchdog_usec)); + +	return (err); +} + +int +_fini(void) +{ +	int err; + +	if ((err = mod_remove(&modlinkage)) == 0) { +		ghd_timer_fini(&ata_timer_conf); +		scsi_hba_fini(&modlinkage); +		ddi_soft_state_fini(&ata_state); +	} + +	return (err); +} + +int +_info(struct modinfo *modinfop) +{ +	return (mod_info(&modlinkage, modinfop)); +} + + +/* driver probe entry point */ + +static int +ata_probe( +	dev_info_t *dip) +{ +	ddi_acc_handle_t io_hdl1 = NULL; +	ddi_acc_handle_t io_hdl2 = NULL; +	ddi_acc_handle_t bm_hdl = NULL; +	caddr_t		 ioaddr1; +	caddr_t		 ioaddr2; +	caddr_t		 bm_addr; +	int		 drive; +	struct ata_id	*ata_id_bufp; +	int		 rc = DDI_PROBE_FAILURE; + +	ADBG_TRACE(("ata_probe entered\n")); +#ifdef ATA_DEBUG +	if (ata_debug_probe) +		debug_enter("\nATA_PROBE\n"); +#endif + +	if (!ata_setup_ioaddr(dip, &io_hdl1, &ioaddr1, &io_hdl2, &ioaddr2, +			&bm_hdl, &bm_addr)) +		return (rc); + +	ata_id_bufp = kmem_zalloc(sizeof (*ata_id_bufp), KM_SLEEP); + +	for (drive = 0; drive < ATA_MAXTARG; drive++) { +		uchar_t	drvhd; + +		/* set up drv/hd and feature registers */ + +		drvhd = (drive == 0 ? ATDH_DRIVE0 : ATDH_DRIVE1); + + +		if (ata_drive_type(drvhd, io_hdl1, ioaddr1, io_hdl2, ioaddr2, +		    ata_id_bufp) != ATA_DEV_NONE) { +			rc = (DDI_PROBE_SUCCESS); +			break; +		} +	} + +	/* always leave the controller set to drive 0 */ +	if (drive != 0) { +		ddi_put8(io_hdl1, (uchar_t *)ioaddr1 + AT_DRVHD, ATDH_DRIVE0); +		ATA_DELAY_400NSEC(io_hdl2, ioaddr2); +	} + +out2: +	kmem_free(ata_id_bufp, sizeof (*ata_id_bufp)); + +	if (io_hdl1) +		ddi_regs_map_free(&io_hdl1); +	if (io_hdl2) +		ddi_regs_map_free(&io_hdl2); +	if (bm_hdl) +		ddi_regs_map_free(&bm_hdl); +	return (rc); +} + +/* + * + * driver attach entry point + * + */ + +static int +ata_attach( +	dev_info_t *dip, +	ddi_attach_cmd_t cmd) +{ +	ata_ctl_t	*ata_ctlp; +	ata_drv_t	*ata_drvp; +	ata_drv_t	*first_drvp = NULL; +	uchar_t		 targ; +	uchar_t		 lun; +	uchar_t		 lastlun; +	int		 atapi_count = 0; +	int		 disk_count = 0; + +	ADBG_TRACE(("ata_attach entered\n")); +#ifdef ATA_DEBUG +	if (ata_debug_attach) +		debug_enter("\nATA_ATTACH\n\n"); +#endif + +	if (cmd != DDI_ATTACH) +		return (DDI_FAILURE); + +	/* initialize controller */ +	ata_ctlp = ata_init_controller(dip); + +	if (ata_ctlp == NULL) +		goto errout; + +	mutex_enter(&ata_ctlp->ac_ccc.ccc_hba_mutex); + +	/* initialize drives */ + +	for (targ = 0; targ < ATA_MAXTARG; targ++) { + +		ata_drvp = ata_init_drive(ata_ctlp, targ, 0); +		if (ata_drvp == NULL) +			continue; + +		if (first_drvp == NULL) +			first_drvp = ata_drvp; + +		if (ATAPIDRV(ata_drvp)) { +			atapi_count++; +			lastlun = ata_drvp->ad_id.ai_lastlun; +		} else { +			disk_count++; +			lastlun = 0; +		} + +		/* +		 * LUN support is currently disabled. Check with SFF-8070i +		 * before enabling. +		 */ +		if (!ata_enable_atapi_luns) +			lastlun = 0; + +		/* Initialize higher LUNs, if there are any */ +		for (lun = 1; lun <= lastlun && lun < ATA_MAXLUN; lun++) { +			if ((ata_drvp = +			    ata_init_drive(ata_ctlp, targ, lun)) != NULL) { +				ata_show_transfer_mode(ata_ctlp, ata_drvp); +			} +		} +	} + +	if ((atapi_count == 0) && (disk_count == 0)) { +		ADBG_WARN(("ata_attach: no drives detected\n")); +		goto errout1; +	} + +	/* +	 * Always make certain that a valid drive is selected so +	 * that routines which poll the status register don't get +	 * confused by non-existent drives. +	 */ +	ddi_put8(ata_ctlp->ac_iohandle1, ata_ctlp->ac_drvhd, +		first_drvp->ad_drive_bits); +	ATA_DELAY_400NSEC(ata_ctlp->ac_iohandle2, ata_ctlp->ac_ioaddr2); + +	/* +	 * make certain the drive selected +	 */ +	if (!ata_wait(ata_ctlp->ac_iohandle2, ata_ctlp->ac_ioaddr2, +			0, ATS_BSY, 5000000)) { +		ADBG_ERROR(("ata_attach: select failed\n")); +	} + +	/* +	 * initialize atapi/ata_dsk modules if we have at least +	 * one drive of that type. +	 */ + +	if (atapi_count) { +		if (!atapi_attach(ata_ctlp)) +			goto errout1; +		ata_ctlp->ac_flags |= AC_ATAPI_INIT; +	} + +	if (disk_count) { +		if (!ata_disk_attach(ata_ctlp)) +			goto errout1; +		ata_ctlp->ac_flags |= AC_DISK_INIT; +	} + +	/* +	 * make certain the interrupt and error latches are clear +	 */ +	if (ata_ctlp->ac_pciide) { + +		int instance = ddi_get_instance(dip); +		if (ddi_create_minor_node(dip, "control", S_IFCHR, instance, +		    DDI_PSEUDO, 0) != DDI_SUCCESS) { +			goto errout1; +		} + +		(void) ata_pciide_status_clear(ata_ctlp); + +	} + +	/* +	 * enable the interrupt handler and drop the mutex +	 */ +	ata_ctlp->ac_flags |= AC_ATTACHED; +	mutex_exit(&ata_ctlp->ac_ccc.ccc_hba_mutex); + +	ddi_report_dev(dip); +	return (DDI_SUCCESS); + +errout1: +	mutex_exit(&ata_ctlp->ac_ccc.ccc_hba_mutex); +errout: +	(void) ata_detach(dip, DDI_DETACH); +	return (DDI_FAILURE); +} + +/* driver detach entry point */ + +static int +ata_detach( +	dev_info_t *dip, +	ddi_detach_cmd_t cmd) +{ +	ata_ctl_t *ata_ctlp; +	ata_drv_t *ata_drvp; +	int	   instance; +	int	   i; +	int	   j; + +	ADBG_TRACE(("ata_detach entered\n")); + +	if (cmd != DDI_DETACH) +		return (DDI_FAILURE); + +	instance = ddi_get_instance(dip); +	ata_ctlp = ddi_get_soft_state(ata_state, instance); + +	if (!ata_ctlp) +		return (DDI_SUCCESS); + +	ata_ctlp->ac_flags &= ~AC_ATTACHED; + +	/* destroy ata module */ +	if (ata_ctlp->ac_flags & AC_DISK_INIT) +		ata_disk_detach(ata_ctlp); + +	/* destroy atapi module */ +	if (ata_ctlp->ac_flags & AC_ATAPI_INIT) +		atapi_detach(ata_ctlp); + +	ddi_remove_minor_node(dip, NULL); + +	/* destroy drives */ +	for (i = 0; i < ATA_MAXTARG; i++) { +		for (j = 0; j < ATA_MAXLUN; j++) { +			ata_drvp = CTL2DRV(ata_ctlp, i, j); +			if (ata_drvp != NULL) +				ata_uninit_drive(ata_drvp); +		} +	} + +	if (ata_ctlp->ac_iohandle1) +		ddi_regs_map_free(&ata_ctlp->ac_iohandle1); +	if (ata_ctlp->ac_iohandle2) +		ddi_regs_map_free(&ata_ctlp->ac_iohandle2); +	if (ata_ctlp->ac_bmhandle) +		ddi_regs_map_free(&ata_ctlp->ac_bmhandle); + +	ddi_prop_remove_all(dip); + +	/* destroy controller */ +	ata_destroy_controller(dip); + +	return (DDI_SUCCESS); +} + +/* + * Nexus driver bus_ctl entry point + */ +/*ARGSUSED*/ +static int +ata_bus_ctl( +	dev_info_t *d, +	dev_info_t *r, +	ddi_ctl_enum_t o, +	void *a, +	void *v) +{ +	dev_info_t *tdip; +	int	target_type; +	int	rc; +	char	*bufp; + +	ADBG_TRACE(("ata_bus_ctl entered\n")); + +	switch (o) { + +	case DDI_CTLOPS_SIDDEV: +		return (DDI_FAILURE); + +	case DDI_CTLOPS_IOMIN: + +		/* +		 * Since we use PIO, we return a minimum I/O size of +		 * one byte.  This will need to be updated when we +		 * implement DMA support +		 */ + +		*((int *)v) = 1; +		return (DDI_SUCCESS); + +	case DDI_CTLOPS_DMAPMAPC: +	case DDI_CTLOPS_REPORTINT: +	case DDI_CTLOPS_REGSIZE: +	case DDI_CTLOPS_NREGS: +	case DDI_CTLOPS_SLAVEONLY: +	case DDI_CTLOPS_AFFINITY: +	case DDI_CTLOPS_POKE: +	case DDI_CTLOPS_PEEK: + +		/* These ops shouldn't be called by a target driver */ +		ADBG_ERROR(("ata_bus_ctl: %s%d: invalid op (%d) from %s%d\n", +			ddi_driver_name(d), ddi_get_instance(d), o, +			ddi_driver_name(r), ddi_get_instance(r))); + +		return (DDI_FAILURE); + +	case DDI_CTLOPS_REPORTDEV: +	case DDI_CTLOPS_INITCHILD: +	case DDI_CTLOPS_UNINITCHILD: + +		/* these require special handling below */ +		break; + +	default: +		return (ddi_ctlops(d, r, o, a, v)); +	} + +	/* get targets dip */ + +	if (o == DDI_CTLOPS_INITCHILD || o == DDI_CTLOPS_UNINITCHILD) +		tdip = (dev_info_t *)a; +	else +		tdip = r; + +	/* +	 * XXX - Get class of target +	 *   Before the "class" entry in a conf file becomes +	 *   a real property, we use an additional property +	 *   tentatively called "class_prop".  We will require that +	 *   new classes (ie. direct) export "class_prop". +	 *   SCSA target drivers will not have this property, so +	 *   no property implies SCSA. +	 */ +	if ((ddi_prop_lookup_string(DDI_DEV_T_ANY, tdip, DDI_PROP_DONTPASS, +	    "class", &bufp) == DDI_PROP_SUCCESS) || +	    (ddi_prop_lookup_string(DDI_DEV_T_ANY, tdip, DDI_PROP_DONTPASS, +	    "class_prop", &bufp) == DDI_PROP_SUCCESS)) { +		if (strcmp(bufp, "dada") == 0) +			target_type = ATA_DEV_DISK; +		else if (strcmp(bufp, "scsi") == 0) +			target_type = ATA_DEV_ATAPI; +		else { +			ADBG_WARN(("ata_bus_ctl: invalid target class %s\n", +				bufp)); +			ddi_prop_free(bufp); +			return (DDI_FAILURE); +		} +		ddi_prop_free(bufp); +	} else { +		target_type = ATA_DEV_ATAPI; /* no class prop, assume SCSI */ +	} + +	if (o == DDI_CTLOPS_INITCHILD) { +		int	instance = ddi_get_instance(d); +		ata_ctl_t *ata_ctlp = ddi_get_soft_state(ata_state, instance); +		ata_drv_t *ata_drvp; +		int	targ; +		int	lun; +		int	drive_type; +		char	*disk_prop; +		char	*class_prop; + +		if (ata_ctlp == NULL) { +			ADBG_WARN(("ata_bus_ctl: failed to find ctl struct\n")); +			return (DDI_FAILURE); +		} + +		/* get (target,lun) of child device */ + +		targ = ddi_prop_get_int(DDI_DEV_T_ANY, tdip, DDI_PROP_DONTPASS, +					"target", -1); +		if (targ == -1) { +			ADBG_WARN(("ata_bus_ctl: failed to get targ num\n")); +			return (DDI_FAILURE); +		} + +		lun = ddi_prop_get_int(DDI_DEV_T_ANY, tdip, DDI_PROP_DONTPASS, +			"lun", 0); + +		if ((targ < 0) || (targ >= ATA_MAXTARG) || +				(lun < 0) || (lun >= ATA_MAXLUN)) { +			return (DDI_FAILURE); +		} + +		ata_drvp = CTL2DRV(ata_ctlp, targ, lun); + +		if (ata_drvp == NULL) +			return (DDI_FAILURE);	/* no drive */ + +		/* get type of device */ + +		if (ATAPIDRV(ata_drvp)) +			drive_type = ATA_DEV_ATAPI; +		else +			drive_type = ATA_DEV_DISK; + +		/* +		 * Check for special handling when child driver is +		 * cmdk (which morphs to the correct interface) +		 */ +		if (strcmp(ddi_get_name(tdip), "cmdk") == 0) { + +			if ((target_type == ATA_DEV_DISK) && +				(target_type != drive_type)) +				return (DDI_FAILURE); + +			target_type = drive_type; + +			if (drive_type == ATA_DEV_ATAPI) { +				class_prop = "scsi"; +			} else { +				disk_prop = "dadk"; +				class_prop = "dada"; + +				if (ndi_prop_update_string(DDI_DEV_T_NONE, tdip, +				    "disk", disk_prop) != DDI_PROP_SUCCESS) { +					ADBG_WARN(("ata_bus_ctl: failed to " +						"create disk prop\n")); +					return (DDI_FAILURE); +				    } +			} + +			if (ndi_prop_update_string(DDI_DEV_T_NONE, tdip, +			    "class_prop", class_prop) != DDI_PROP_SUCCESS) { +				ADBG_WARN(("ata_bus_ctl: failed to " +				    "create class prop\n")); +				return (DDI_FAILURE); +			} +		} + +		/* Check that target class matches the device */ + +		if (target_type != drive_type) +			return (DDI_FAILURE); + +		/* save pointer to drive struct for ata_disk_bus_ctl */ +		ddi_set_driver_private(tdip, ata_drvp); + +		/* +		 * Determine whether to enable DMA support for this drive.  This +		 * check is deferred to this point so that the various dma +		 * properties could reside on the devinfo node should finer +		 * grained dma control be required. +		 */ +		ata_drvp->ad_pciide_dma = ata_init_drive_pcidma(ata_ctlp, +		    ata_drvp, tdip); +		ata_show_transfer_mode(ata_ctlp, ata_drvp); +	} + +	if (target_type == ATA_DEV_ATAPI) { +		rc = scsa_bus_ops_p->bus_ctl(d, r, o, a, v); +	} else { +		rc = ata_disk_bus_ctl(d, r, o, a, v); +	} + +	return (rc); +} + +/* + * + * GHD ccc_hba_complete callback + * + */ + +/* ARGSUSED */ +static void +ata_hba_complete( +	void *hba_handle, +	gcmd_t *gcmdp, +	int do_callback) +{ +	ata_drv_t *ata_drvp; +	ata_pkt_t *ata_pktp; + +	ADBG_TRACE(("ata_hba_complete entered\n")); + +	ata_drvp = GCMD2DRV(gcmdp); +	ata_pktp = GCMD2APKT(gcmdp); +	if (ata_pktp->ap_complete) +		(*ata_pktp->ap_complete)(ata_drvp, ata_pktp, +			do_callback); +} + +/* GHD ccc_timeout_func callback */ + +/* ARGSUSED */ +static int +ata_timeout_func( +	void	*hba_handle, +	gcmd_t	*gcmdp, +	gtgt_t	*gtgtp, +	gact_t	 action, +	int	 calltype) +{ +	ata_ctl_t *ata_ctlp; +	ata_pkt_t *ata_pktp; + +	ADBG_TRACE(("ata_timeout_func entered\n")); + +	ata_ctlp = (ata_ctl_t *)hba_handle; + +	if (gcmdp != NULL) +		ata_pktp = GCMD2APKT(gcmdp); +	else +		ata_pktp = NULL; + +	switch (action) { +	case GACTION_EARLY_ABORT: +		/* abort before request was started */ +		if (ata_pktp != NULL) { +			ata_pktp->ap_flags |= AP_ABORT; +		} +		ghd_complete(&ata_ctlp->ac_ccc, gcmdp); +		return (TRUE); + +	case GACTION_EARLY_TIMEOUT: +		/* timeout before request was started */ +		if (ata_pktp != NULL) { +			ata_pktp->ap_flags |= AP_TIMEOUT; +		} +		ghd_complete(&ata_ctlp->ac_ccc, gcmdp); +		return (TRUE); + +	case GACTION_RESET_TARGET: +		/* +		 * Reset a device is not supported. Resetting a specific +		 * device can't be done at all to an ATA device and if +		 * you send a RESET to an ATAPI device you have to +		 * reset the whole bus to make certain both devices +		 * on the bus stay in sync regarding which device is +		 * the currently selected one. +		 */ +		return (FALSE); + +	case GACTION_RESET_BUS: +		/* +		 * Issue bus reset and reinitialize both drives. +		 * But only if this is a timed-out request. Target +		 * driver reset requests are ignored because ATA +		 * and ATAPI devices shouldn't be gratuitously reset. +		 */ +		if (gcmdp == NULL) +			break; +		return (ata_reset_bus(ata_ctlp)); +	default: +		break; +	} +	return (FALSE); +} + +/* + * + * Initialize controller's soft-state structure + * + */ + +static ata_ctl_t * +ata_init_controller( +	dev_info_t *dip) +{ +	ata_ctl_t *ata_ctlp; +	int	   instance; +	caddr_t	   ioaddr1; +	caddr_t	   ioaddr2; + +	ADBG_TRACE(("ata_init_controller entered\n")); + +	instance = ddi_get_instance(dip); + +	/* allocate controller structure */ +	if (ddi_soft_state_zalloc(ata_state, instance) != DDI_SUCCESS) { +		ADBG_WARN(("ata_init_controller: soft_state_zalloc failed\n")); +		return (NULL); +	} + +	ata_ctlp = ddi_get_soft_state(ata_state, instance); + +	if (ata_ctlp == NULL) { +		ADBG_WARN(("ata_init_controller: failed to find " +				"controller struct\n")); +		return (NULL); +	} + +	/* +	 * initialize per-controller data +	 */ +	ata_ctlp->ac_dip = dip; +	ata_ctlp->ac_arq_pktp = kmem_zalloc(sizeof (ata_pkt_t), KM_SLEEP); + +	/* +	 * map the device registers +	 */ +	if (!ata_setup_ioaddr(dip, &ata_ctlp->ac_iohandle1, &ioaddr1, +			&ata_ctlp->ac_iohandle2, &ioaddr2, +			&ata_ctlp->ac_bmhandle, &ata_ctlp->ac_bmaddr)) { +		(void) ata_detach(dip, DDI_DETACH); +		return (NULL); +	} + +	ADBG_INIT(("ata_init_controller: ioaddr1 = 0x%p, ioaddr2 = 0x%p\n", +			ioaddr1, ioaddr2)); + +	/* +	 * Do ARQ setup +	 */ +	atapi_init_arq(ata_ctlp); + +	/* +	 * Do PCI-IDE setup +	 */ +	ata_init_pciide(dip, ata_ctlp); + +	/* +	 * port addresses associated with ioaddr1 +	 */ +	ata_ctlp->ac_ioaddr1	= ioaddr1; +	ata_ctlp->ac_data	= (ushort_t *)ioaddr1 + AT_DATA; +	ata_ctlp->ac_error	= (uchar_t *)ioaddr1 + AT_ERROR; +	ata_ctlp->ac_feature	= (uchar_t *)ioaddr1 + AT_FEATURE; +	ata_ctlp->ac_count	= (uchar_t *)ioaddr1 + AT_COUNT; +	ata_ctlp->ac_sect	= (uchar_t *)ioaddr1 + AT_SECT; +	ata_ctlp->ac_lcyl	= (uchar_t *)ioaddr1 + AT_LCYL; +	ata_ctlp->ac_hcyl	= (uchar_t *)ioaddr1 + AT_HCYL; +	ata_ctlp->ac_drvhd	= (uchar_t *)ioaddr1 + AT_DRVHD; +	ata_ctlp->ac_status	= (uchar_t *)ioaddr1 + AT_STATUS; +	ata_ctlp->ac_cmd	= (uchar_t *)ioaddr1 + AT_CMD; + +	/* +	 * port addresses associated with ioaddr2 +	 */ +	ata_ctlp->ac_ioaddr2	= ioaddr2; +	ata_ctlp->ac_altstatus	= (uchar_t *)ioaddr2 + AT_ALTSTATUS; +	ata_ctlp->ac_devctl	= (uchar_t *)ioaddr2 + AT_DEVCTL; + +	/* +	 * If AC_BSY_WAIT needs to be set  for laptops that do +	 * suspend/resume but do not correctly wait for the busy bit to +	 * drop after a resume. +	 */ +	ata_ctlp->ac_timing_flags = ddi_prop_get_int(DDI_DEV_T_ANY, +			dip, DDI_PROP_DONTPASS, "timing_flags", 0); +	/* +	 * get max transfer size, default to 256 sectors +	 */ +	ata_ctlp->ac_max_transfer = ddi_prop_get_int(DDI_DEV_T_ANY, +			dip, DDI_PROP_DONTPASS, "max_transfer", 0x100); +	if (ata_ctlp->ac_max_transfer < 1) +		ata_ctlp->ac_max_transfer = 1; +	if (ata_ctlp->ac_max_transfer > 0x100) +		ata_ctlp->ac_max_transfer = 0x100; + +	/* +	 * Get the standby timer value +	 */ +	ata_ctlp->ac_standby_time = ddi_prop_get_int(DDI_DEV_T_ANY, +			dip, DDI_PROP_DONTPASS, "standby", -1); + +	/* +	 * If this is a /pci/pci-ide instance check to see if +	 * it's supposed to be attached as an /isa/ata +	 */ +	if (ata_ctlp->ac_pciide) { +		static char prop_buf[] = "SUNW-ata-ffff-isa"; +		int addr1 = (intptr_t)ioaddr1; + + +		if (addr1 < 0 || addr1 > 0xffff) { +			(void) ata_detach(dip, DDI_DETACH); +			return (NULL); +		} +		(void) sprintf(prop_buf, "SUNW-ata-%04x-isa", +			addr1); +		if (ddi_prop_exists(DDI_DEV_T_ANY, ddi_root_node(), +				    DDI_PROP_DONTPASS, prop_buf)) { +			(void) ata_detach(dip, DDI_DETACH); +			return (NULL); +		} +	} + +	/* Init controller specific stuff */ +	(void) ata_spec_init_controller(dip); + +	/* +	 * initialize GHD +	 */ + +	GHD_WAITQ_INIT(&ata_ctlp->ac_ccc.ccc_waitq, NULL, 1); + +	if (!ghd_register("ata", &ata_ctlp->ac_ccc, dip, 0, ata_ctlp, +			atapi_ccballoc, atapi_ccbfree, +			ata_pciide_dma_sg_func, ata_hba_start, +			ata_hba_complete, ata_intr, +			ata_get_status, ata_process_intr, ata_timeout_func, +			&ata_timer_conf, NULL)) { +		(void) ata_detach(dip, DDI_DETACH); +		return (NULL); +	} + +	ata_ctlp->ac_flags |= AC_GHD_INIT; +	return (ata_ctlp); +} + +/* destroy a controller */ + +static void +ata_destroy_controller( +	dev_info_t *dip) +{ +	ata_ctl_t *ata_ctlp; +	int	instance; + +	ADBG_TRACE(("ata_destroy_controller entered\n")); + +	instance = ddi_get_instance(dip); +	ata_ctlp = ddi_get_soft_state(ata_state, instance); + +	if (ata_ctlp == NULL) +		return; + +	/* destroy ghd */ +	if (ata_ctlp->ac_flags & AC_GHD_INIT) +		ghd_unregister(&ata_ctlp->ac_ccc); + +	/* free the pciide buffer (if any) */ +	ata_pciide_free(ata_ctlp); + +	/* destroy controller struct */ +	kmem_free(ata_ctlp->ac_arq_pktp, sizeof (ata_pkt_t)); +	ddi_soft_state_free(ata_state, instance); + +} + + +/* + * + * initialize a drive + * + */ + +static ata_drv_t * +ata_init_drive( +	ata_ctl_t	*ata_ctlp, +	uchar_t		targ, +	uchar_t		lun) +{ +	static	char	 nec_260[]	= "NEC CD-ROM DRIVE"; +	ata_drv_t *ata_drvp; +	struct ata_id	*aidp; +	char	buf[80]; +	int	drive_type; +	int	i; +	int	valid_version = 0; + +	ADBG_TRACE(("ata_init_drive entered, targ = %d, lun = %d\n", +		    targ, lun)); + +	/* check if device already exists */ + +	ata_drvp = CTL2DRV(ata_ctlp, targ, lun); + +	if (ata_drvp != NULL) +		return (ata_drvp); + +	/* allocate new device structure */ + +	ata_drvp = kmem_zalloc(sizeof (ata_drv_t), KM_SLEEP); +	aidp = &ata_drvp->ad_id; + +	/* +	 * set up drive struct +	 */ +	ata_drvp->ad_ctlp = ata_ctlp; +	ata_drvp->ad_targ = targ; +	ata_drvp->ad_drive_bits = +		(ata_drvp->ad_targ == 0 ? ATDH_DRIVE0 : ATDH_DRIVE1); +	/* +	 * Add the LUN for SFF-8070i support +	 */ +	ata_drvp->ad_lun = lun; +	ata_drvp->ad_drive_bits |= ata_drvp->ad_lun; + +	/* +	 * get drive type, side effect is to collect +	 * IDENTIFY DRIVE data +	 */ + +	drive_type = ata_drive_type(ata_drvp->ad_drive_bits, +				    ata_ctlp->ac_iohandle1, +				    ata_ctlp->ac_ioaddr1, +				    ata_ctlp->ac_iohandle2, +				    ata_ctlp->ac_ioaddr2, +				    aidp); + +	switch (drive_type) { +	case ATA_DEV_NONE: +		/* no drive found */ +		goto errout; +	case ATA_DEV_ATAPI: +		ata_drvp->ad_flags |= AD_ATAPI; +		break; +	case ATA_DEV_DISK: +		ata_drvp->ad_flags |= AD_DISK; +		break; +	} + +	/* +	 * swap bytes of all text fields +	 */ +	if (!ata_strncmp(nec_260, aidp->ai_model, sizeof (aidp->ai_model))) { +		swab(aidp->ai_drvser, aidp->ai_drvser, +			sizeof (aidp->ai_drvser)); +		swab(aidp->ai_fw, aidp->ai_fw, +			sizeof (aidp->ai_fw)); +		swab(aidp->ai_model, aidp->ai_model, +			sizeof (aidp->ai_model)); +	} + +	/* +	 * Check if this drive has the Single Sector bug +	 */ + +	if (ata_check_drive_blacklist(&ata_drvp->ad_id, ATA_BL_1SECTOR)) +		ata_drvp->ad_flags |= AD_1SECTOR; +	else +		ata_drvp->ad_flags &= ~AD_1SECTOR; + +	/* Check if this drive has the "revert to defaults" bug */ +	if (!ata_check_revert_to_defaults(ata_drvp)) +		ata_drvp->ad_flags |= AD_NORVRT; + +	/* Dump the drive info */ +	(void) strncpy(buf, aidp->ai_model, sizeof (aidp->ai_model)); +	buf[sizeof (aidp->ai_model)-1] = '\0'; +	for (i = sizeof (aidp->ai_model) - 2; buf[i] == ' '; i--) +		buf[i] = '\0'; + +	ATAPRT(("?\t%s device at targ %d, lun %d lastlun 0x%x\n", +		(ATAPIDRV(ata_drvp) ? "ATAPI":"IDE"), +		ata_drvp->ad_targ, ata_drvp->ad_lun, aidp->ai_lastlun)); + +	ATAPRT(("?\tmodel %s\n", buf)); + +	if (aidp->ai_majorversion != 0 && aidp->ai_majorversion != 0xffff) { +		for (i = 14; i >= 2; i--) { +			if (aidp->ai_majorversion & (1 << i)) { +				valid_version = i; +				break; +			} +		} +		ATAPRT(( +		    "?\tATA/ATAPI-%d supported, majver 0x%x minver 0x%x\n", +			valid_version, +			aidp->ai_majorversion, +			aidp->ai_minorversion)); +	} + +	if (ata_capability_data) { + +		ATAPRT(("?\t\tstat %x, err %x\n", +			ddi_get8(ata_ctlp->ac_iohandle2, +				ata_ctlp->ac_altstatus), +			ddi_get8(ata_ctlp->ac_iohandle1, ata_ctlp->ac_error))); + +		ATAPRT(("?\t\tcfg 0x%x, cap 0x%x\n", +			aidp->ai_config, +			aidp->ai_cap)); + +		/* +		 * Be aware that ATA-6 and later drives may not provide valid +		 * geometry information and other obsoleted info. +		 * Select what is printed based on supported ATA model (skip +		 * anything below ATA/ATAPI-3) +		 */ + +		if (valid_version == 0 || aidp->ai_majorversion < +		    ATAC_MAJVER_6) { +			/* +			 * Supported version less then ATA-6 +			 */ +			ATAPRT(("?\t\tcyl %d, hd %d, sec/trk %d\n", +				aidp->ai_fixcyls, +				aidp->ai_heads, +				aidp->ai_sectors)); +		} +		ATAPRT(("?\t\tmult1 0x%x, mult2 0x%x\n", +			aidp->ai_mult1, +			aidp->ai_mult2)); +		if (valid_version && aidp->ai_majorversion < ATAC_MAJVER_4) { +			ATAPRT(( +			"?\t\tpiomode 0x%x, dmamode 0x%x, advpiomode 0x%x\n", +				aidp->ai_piomode, +				aidp->ai_dmamode, +				aidp->ai_advpiomode)); +		} else { +			ATAPRT(("?\t\tadvpiomode 0x%x\n", +				aidp->ai_advpiomode)); +		} +		ATAPRT(("?\t\tminpio %d, minpioflow %d\n", +			aidp->ai_minpio, +			aidp->ai_minpioflow)); +		if (valid_version && aidp->ai_majorversion >= ATAC_MAJVER_4 && +		    (aidp->ai_validinfo & ATAC_VALIDINFO_83)) { +			ATAPRT(("?\t\tdwdma 0x%x, ultradma 0x%x\n", +				aidp->ai_dworddma, +				aidp->ai_ultradma)); +		} else { +			ATAPRT(("?\t\tdwdma 0x%x\n", +				aidp->ai_dworddma)); +		} +	} + +	if (ATAPIDRV(ata_drvp)) { +		if (!atapi_init_drive(ata_drvp)) +			goto errout; +	} else { +		if (!ata_disk_init_drive(ata_drvp)) +			goto errout; +	} + +	/* +	 * store pointer in controller struct +	 */ +	CTL2DRV(ata_ctlp, targ, lun) = ata_drvp; + +	/* +	 * lock the drive's current settings in case I have to +	 * reset the drive due to some sort of error +	 */ +	(void) ata_set_feature(ata_ctlp, ata_drvp, 0x66, 0); + +	return (ata_drvp); + +errout: +	ata_uninit_drive(ata_drvp); +	return (NULL); +} + +/* destroy a drive */ + +static void +ata_uninit_drive( +	ata_drv_t *ata_drvp) +{ +#if 0 +	ata_ctl_t *ata_ctlp = ata_drvp->ad_ctlp; +#endif + +	ADBG_TRACE(("ata_uninit_drive entered\n")); + +#if 0 +	/* +	 * DON'T DO THIS. disabling interrupts floats the IRQ line +	 * which generates spurious interrupts +	 */ + +	/* +	 * Select the correct drive +	 */ +	ddi_put8(ata_ctlp->ac_iohandle1, ata_ctlp->ac_drvhd, +		ata_drvp->ad_drive_bits); +	ATA_DELAY_400NSEC(ata_ctlp->ac_iohandle2, ata_ctlp->ac_ioaddr2); + +	/* +	 * Disable interrupts from the drive +	 */ +	ddi_put8(ata_ctlp->ac_iohandle2, ata_ctlp->ac_devctl, +		(ATDC_D3 | ATDC_NIEN)); +#endif + +	/* interface specific clean-ups */ + +	if (ata_drvp->ad_flags & AD_ATAPI) +		atapi_uninit_drive(ata_drvp); +	else if (ata_drvp->ad_flags & AD_DISK) +		ata_disk_uninit_drive(ata_drvp); + +	/* free drive struct */ + +	kmem_free(ata_drvp, sizeof (ata_drv_t)); +} + + +/* + * ata_drive_type() + * + * The timeout values and exact sequence of checking is critical + * especially for atapi device detection, and should not be changed lightly. + * + */ +static int +ata_drive_type( +	uchar_t		 drvhd, +	ddi_acc_handle_t io_hdl1, +	caddr_t		 ioaddr1, +	ddi_acc_handle_t io_hdl2, +	caddr_t		 ioaddr2, +	struct ata_id	*ata_id_bufp) +{ +	uchar_t	status; + +	ADBG_TRACE(("ata_drive_type entered\n")); + +	/* +	 * select the appropriate drive and LUN +	 */ +	ddi_put8(io_hdl1, (uchar_t *)ioaddr1 + AT_DRVHD, drvhd); +	ATA_DELAY_400NSEC(io_hdl2, ioaddr2); + +	/* +	 * make certain the drive is selected, and wait for not busy +	 */ +	(void) ata_wait3(io_hdl2, ioaddr2, 0, ATS_BSY, 0x7f, 0, 0x7f, 0, +		5 * 1000000); + +	status = ddi_get8(io_hdl2, (uchar_t *)ioaddr2 + AT_ALTSTATUS); + +	if (status & ATS_BSY) { +		ADBG_TRACE(("ata_drive_type BUSY 0x%p 0x%x\n", +			    ioaddr1, status)); +		return (ATA_DEV_NONE); +	} + +	if (ata_disk_id(io_hdl1, ioaddr1, io_hdl2, ioaddr2, ata_id_bufp)) +		return (ATA_DEV_DISK); + +	/* +	 * No disk, check for atapi unit. +	 */ +	if (!atapi_signature(io_hdl1, ioaddr1)) { +#ifndef ATA_DISABLE_ATAPI_1_7 +		/* +		 * Check for old (but prevalent) atapi 1.7B +		 * spec device, the only known example is the +		 * NEC CDR-260 (not 260R which is (mostly) ATAPI 1.2 +		 * compliant). This device has no signature +		 * and requires conversion from hex to BCD +		 * for some scsi audio commands. +		 */ +		if (atapi_id(io_hdl1, ioaddr1, io_hdl2, ioaddr2, ata_id_bufp)) { +			return (ATA_DEV_ATAPI); +		} +#endif +		return (ATA_DEV_NONE); +	} + +	if (atapi_id(io_hdl1, ioaddr1, io_hdl2, ioaddr2, ata_id_bufp)) { +		return (ATA_DEV_ATAPI); +	} + +	return (ATA_DEV_NONE); + +} + +/* + * Wait for a register of a controller to achieve a specific state. + * To return normally, all the bits in the first sub-mask must be ON, + * all the bits in the second sub-mask must be OFF. + * If timeout_usec microseconds pass without the controller achieving + * the desired bit configuration, we return TRUE, else FALSE. + */ + +int ata_usec_delay = 10; + +int +ata_wait( +	ddi_acc_handle_t io_hdl, +	caddr_t		ioaddr, +	uchar_t		onbits, +	uchar_t		offbits, +	uint_t		timeout_usec) +{ +	ushort_t val; + +	do  { +		val = ddi_get8(io_hdl, (uchar_t *)ioaddr + AT_ALTSTATUS); +		if ((val & onbits) == onbits && (val & offbits) == 0) +			return (TRUE); +		drv_usecwait(ata_usec_delay); +		timeout_usec -= ata_usec_delay; +	} while (timeout_usec > 0); + +	return (FALSE); +} + + + +/* + * + * This is a slightly more complicated version that checks + * for error conditions and bails-out rather than looping + * until the timeout expires + */ +int +ata_wait3( +	ddi_acc_handle_t io_hdl, +	caddr_t		ioaddr, +	uchar_t		onbits1, +	uchar_t		offbits1, +	uchar_t		failure_onbits2, +	uchar_t		failure_offbits2, +	uchar_t		failure_onbits3, +	uchar_t		failure_offbits3, +	uint_t		timeout_usec) +{ +	ushort_t val; + +	do  { +		val = ddi_get8(io_hdl, (uchar_t *)ioaddr + AT_ALTSTATUS); + +		/* +		 * check for expected condition +		 */ +		if ((val & onbits1) == onbits1 && (val & offbits1) == 0) +			return (TRUE); + +		/* +		 * check for error conditions +		 */ +		if ((val & failure_onbits2) == failure_onbits2 && +				(val & failure_offbits2) == 0) { +			return (FALSE); +		} + +		if ((val & failure_onbits3) == failure_onbits3 && +				(val & failure_offbits3) == 0) { +			return (FALSE); +		} + +		drv_usecwait(ata_usec_delay); +		timeout_usec -= ata_usec_delay; +	} while (timeout_usec > 0); + +	return (FALSE); +} + + +/* + * + * low level routine for ata_disk_id() and atapi_id() + * + */ + +int +ata_id_common( +	uchar_t		 id_cmd, +	int		 expect_drdy, +	ddi_acc_handle_t io_hdl1, +	caddr_t		 ioaddr1, +	ddi_acc_handle_t io_hdl2, +	caddr_t		 ioaddr2, +	struct ata_id	*aidp) +{ +	uchar_t	status; + +	ADBG_TRACE(("ata_id_common entered\n")); + +	bzero(aidp, sizeof (struct ata_id)); + +	/* +	 * clear the features register +	 */ +	ddi_put8(io_hdl1, (uchar_t *)ioaddr1 + AT_FEATURE, 0); + +	/* +	 * enable interrupts from the device +	 */ +	ddi_put8(io_hdl2, (uchar_t *)ioaddr2 + AT_DEVCTL, ATDC_D3); + +	/* +	 * issue IDENTIFY DEVICE or IDENTIFY PACKET DEVICE command +	 */ +	ddi_put8(io_hdl1, (uchar_t *)ioaddr1 + AT_CMD, id_cmd); + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ioaddr2); + +	/* +	 * According to the ATA specification, some drives may have +	 * to read the media to complete this command.  We need to +	 * make sure we give them enough time to respond. +	 */ + +	(void) ata_wait3(io_hdl2, ioaddr2, 0, ATS_BSY, +			ATS_ERR, ATS_BSY, 0x7f, 0, 5 * 1000000); + +	/* +	 * read the status byte and clear the pending interrupt +	 */ +	status = ddi_get8(io_hdl2, (uchar_t *)ioaddr1 + AT_STATUS); + +	/* +	 * this happens if there's no drive present +	 */ +	if (status == 0xff || status == 0x7f) { +		/* invalid status, can't be an ATA or ATAPI device */ +		return (FALSE); +	} + +	if (status & ATS_BSY) { +		ADBG_ERROR(("ata_id_common: BUSY status 0x%x error 0x%x\n", +			ddi_get8(io_hdl2, (uchar_t *)ioaddr2 +AT_ALTSTATUS), +			ddi_get8(io_hdl1, (uchar_t *)ioaddr1 + AT_ERROR))); +		return (FALSE); +	} + +	if (!(status & ATS_DRQ)) { +		if (status & (ATS_ERR | ATS_DF)) { +			return (FALSE); +		} +		/* +		 * Give the drive another second to assert DRQ. Some older +		 * drives de-assert BSY before asserting DRQ. +		 */ +		if (!ata_wait(io_hdl2, ioaddr2, ATS_DRQ, ATS_BSY, 1000000)) { +		ADBG_WARN(("ata_id_common: !DRQ status 0x%x error 0x%x\n", +			ddi_get8(io_hdl2, (uchar_t *)ioaddr2 +AT_ALTSTATUS), +			ddi_get8(io_hdl1, (uchar_t *)ioaddr1 + AT_ERROR))); +		return (FALSE); +		} +	} + +	/* +	 * transfer the data +	 */ +	ddi_rep_get16(io_hdl1, (ushort_t *)aidp, (ushort_t *)ioaddr1 + AT_DATA, +		NBPSCTR >> 1, DDI_DEV_NO_AUTOINCR); + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ioaddr2); + + +	/* +	 * Wait for the drive to recognize I've read all the data. +	 * Some drives have been observed to take as much as 3msec to +	 * deassert DRQ after reading the data; allow 10 msec just in case. +	 * +	 * Note: some non-compliant ATAPI drives (e.g., NEC Multispin 6V, +	 * CDR-1350A) don't assert DRDY. If we've made it this far we can +	 * safely ignore the DRDY bit since the ATAPI Packet command +	 * actually doesn't require it to ever be asserted. +	 * +	 */ +	if (!ata_wait(io_hdl2, ioaddr2, (uchar_t)(expect_drdy ? ATS_DRDY : 0), +					(ATS_BSY | ATS_DRQ), 1000000)) { +		ADBG_WARN(("ata_id_common: bad status 0x%x error 0x%x\n", +			ddi_get8(io_hdl2, (uchar_t *)ioaddr2 + AT_ALTSTATUS), +			ddi_get8(io_hdl1, (uchar_t *)ioaddr1 + AT_ERROR))); +		return (FALSE); +	} + +	/* +	 * Check to see if the command aborted. This happens if +	 * an IDENTIFY DEVICE command is issued to an ATAPI PACKET device, +	 * or if an IDENTIFY PACKET DEVICE command is issued to an ATA +	 * (non-PACKET) device. +	 */ +	if (status & (ATS_DF | ATS_ERR)) { +		ADBG_WARN(("ata_id_common: status 0x%x error 0x%x \n", +			ddi_get8(io_hdl2, (uchar_t *)ioaddr2 + AT_ALTSTATUS), +			ddi_get8(io_hdl1, (uchar_t *)ioaddr1 + AT_ERROR))); +		return (FALSE); +	} +	return (TRUE); +} + + +/* + * Low level routine to issue a non-data command and busy wait for + * the completion status. + */ + +int +ata_command( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	int		 expect_drdy, +	int		 silent, +	uint_t		 busy_wait, +	uchar_t		 cmd, +	uchar_t		 feature, +	uchar_t		 count, +	uchar_t		 sector, +	uchar_t		 head, +	uchar_t		 cyl_low, +	uchar_t		 cyl_hi) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	uchar_t		 status; + +	/* select the drive */ +	ddi_put8(io_hdl1, ata_ctlp->ac_drvhd, ata_drvp->ad_drive_bits); +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	/* make certain the drive selected */ +	if (!ata_wait(io_hdl2, ata_ctlp->ac_ioaddr2, +			(uchar_t)(expect_drdy ? ATS_DRDY : 0), +			ATS_BSY, busy_wait)) { +		ADBG_ERROR(("ata_command: select failed " +			    "DRDY 0x%x CMD 0x%x F 0x%x N 0x%x  " +			    "S 0x%x H 0x%x CL 0x%x CH 0x%x\n", +			    expect_drdy, cmd, feature, count, +			    sector, head, cyl_low, cyl_hi)); +		return (FALSE); +	} + +	/* +	 * set all the regs +	 */ +	ddi_put8(io_hdl1, ata_ctlp->ac_drvhd, (head | ata_drvp->ad_drive_bits)); +	ddi_put8(io_hdl1, ata_ctlp->ac_sect, sector); +	ddi_put8(io_hdl1, ata_ctlp->ac_count, count); +	ddi_put8(io_hdl1, ata_ctlp->ac_lcyl, cyl_low); +	ddi_put8(io_hdl1, ata_ctlp->ac_hcyl, cyl_hi); +	ddi_put8(io_hdl1, ata_ctlp->ac_feature, feature); + +	/* send the command */ +	ddi_put8(io_hdl1, ata_ctlp->ac_cmd, cmd); + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	/* wait for not busy */ +	if (!ata_wait(io_hdl2, ata_ctlp->ac_ioaddr2, 0, ATS_BSY, busy_wait)) { +		ADBG_ERROR(("ata_command: BSY too long!" +			    "DRDY 0x%x CMD 0x%x F 0x%x N 0x%x  " +			    "S 0x%x H 0x%x CL 0x%x CH 0x%x\n", +			    expect_drdy, cmd, feature, count, +			    sector, head, cyl_low, cyl_hi)); +		return (FALSE); +	} + +	/* +	 * wait for DRDY before continuing +	 */ +	(void) ata_wait3(io_hdl2, ata_ctlp->ac_ioaddr2, +			ATS_DRDY, ATS_BSY, /* okay */ +			ATS_ERR, ATS_BSY, /* cmd failed */ +			ATS_DF, ATS_BSY, /* drive failed */ +			busy_wait); + +	/* read status to clear IRQ, and check for error */ +	status =  ddi_get8(io_hdl1, ata_ctlp->ac_status); + +	if ((status & (ATS_BSY | ATS_DF | ATS_ERR)) == 0) +		return (TRUE); + +	if (!silent) { +		ADBG_ERROR(("ata_command status 0x%x error 0x%x " +			    "DRDY 0x%x CMD 0x%x F 0x%x N 0x%x  " +			    "S 0x%x H 0x%x CL 0x%x CH 0x%x\n", +			    ddi_get8(io_hdl1, ata_ctlp->ac_status), +			    ddi_get8(io_hdl1, ata_ctlp->ac_error), +			    expect_drdy, cmd, feature, count, +			    sector, head, cyl_low, cyl_hi)); +	} +	return (FALSE); +} + + + +/* + * + * Issue a SET FEATURES command + * + */ + +int +ata_set_feature( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	uchar_t    feature, +	uchar_t    value) +{ +	int		 rc; + +	rc = ata_command(ata_ctlp, ata_drvp, TRUE, TRUE, ata_set_feature_wait, +		ATC_SET_FEAT, feature, value, 0, 0, 0, 0); +		/* feature, count, sector, head, cyl_low, cyl_hi */ + +	if (rc) { +		return (TRUE); +	} + +	ADBG_ERROR(("?ata_set_feature: (0x%x,0x%x) failed\n", feature, value)); +	return (FALSE); +} + + + +/* + * + * Issue a FLUSH CACHE command + * + */ + +static int +ata_flush_cache( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp) +{ +	/* this command is optional so fail silently */ +	return (ata_command(ata_ctlp, ata_drvp, TRUE, TRUE, +			    ata_flush_cache_wait, +			    ATC_FLUSH_CACHE, 0, 0, 0, 0, 0, 0)); +} + +/* + * ata_setup_ioaddr() + * + * Map the device registers and return the handles. + * + * If this is a ISA-ATA controller then only two handles are + * initialized and returned. + * + * If this is a PCI-IDE controller than a third handle (for the + * PCI-IDE Bus Mastering registers) is initialized and returned. + * + */ + +static int +ata_setup_ioaddr( +	dev_info_t	 *dip, +	ddi_acc_handle_t *handle1p, +	caddr_t		 *addr1p, +	ddi_acc_handle_t *handle2p, +	caddr_t		 *addr2p, +	ddi_acc_handle_t *bm_hdlp, +	caddr_t		 *bm_addrp) +{ +	ddi_device_acc_attr_t dev_attr; +	char	*bufp; +	int	 rnumber; +	int	 rc; +	off_t	 regsize; + +	/* +	 * Make certain the controller is enabled and its regs are map-able +	 * +	 */ +	rc = ddi_dev_regsize(dip, 0, ®size); +	if (rc != DDI_SUCCESS || regsize <= AT_CMD) { +		ADBG_INIT(("ata_setup_ioaddr(1): rc %d regsize %lld\n", +			    rc, (long long)regsize)); +		return (FALSE); +	} + +	rc = ddi_dev_regsize(dip, 1, ®size); +	if (rc != DDI_SUCCESS || regsize <= AT_ALTSTATUS) { +		ADBG_INIT(("ata_setup_ioaddr(2): rc %d regsize %lld\n", +			    rc, (long long)regsize)); +		return (FALSE); +	} + +	/* +	 * setup the device attribute structure for little-endian, +	 * strict ordering access. +	 */ +	dev_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; +	dev_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; +	dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; + +	*handle1p = NULL; +	*handle2p = NULL; +	*bm_hdlp = NULL; + +	/* +	 * Determine whether this is a ISA, PNP-ISA, or PCI-IDE device +	 */ +	if (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "pnp-csn")) { +		/* it's PNP-ISA, skip over the extra reg tuple */ +		rnumber = 1; +		goto not_pciide; +	} + +	/* else, it's ISA or PCI-IDE, check further */ +	rnumber = 0; + +	rc = ddi_prop_lookup_string(DDI_DEV_T_ANY, ddi_get_parent(dip), +				    DDI_PROP_DONTPASS, "device_type", &bufp); +	if (rc != DDI_PROP_SUCCESS) { +		ADBG_ERROR(("ata_setup_ioaddr !device_type\n")); +		goto not_pciide; +	} + +	if (strcmp(bufp, "pci-ide") != 0) { +		/* +		 * If it's not a PCI-IDE, there are only two reg tuples +		 * and the first one contains the I/O base (170 or 1f0) +		 * rather than the controller instance number. +		 */ +		ADBG_TRACE(("ata_setup_ioaddr !pci-ide\n")); +		ddi_prop_free(bufp); +		goto not_pciide; +	} +	ddi_prop_free(bufp); + + +	/* +	 * Map the correct half of the PCI-IDE Bus Master registers. +	 * There's a single BAR that maps these registers for both +	 * controller's in a dual-controller chip and it's upto my +	 * parent nexus, pciide, to adjust which (based on my instance +	 * number) half this call maps. +	 */ +	rc = ddi_dev_regsize(dip, 2, ®size); +	if (rc != DDI_SUCCESS || regsize < 8) { +		ADBG_INIT(("ata_setup_ioaddr(3): rc %d regsize %lld\n", +			    rc, (long long)regsize)); +		goto not_pciide; +	} + +	rc = ddi_regs_map_setup(dip, 2, bm_addrp, 0, 0, &dev_attr, bm_hdlp); + +	if (rc != DDI_SUCCESS) { +		/* map failed, try to use in non-pci-ide mode */ +		ADBG_WARN(("ata_setup_ioaddr bus master map failed, rc=0x%x\n", +			rc)); +		*bm_hdlp = NULL; +	} + +not_pciide: +	/* +	 * map the lower command block registers +	 */ + +	rc = ddi_regs_map_setup(dip, rnumber, addr1p, 0, 0, &dev_attr, +				handle1p); + +	if (rc != DDI_SUCCESS) { +		cmn_err(CE_WARN, "ata: reg tuple 0 map failed, rc=0x%x\n", rc); +		goto out1; +	} + +	/* +	 * If the controller is being used in compatibility mode +	 * via /devices/isa/ata@1,{1f0,1f0}/..., the reg property +	 * will specify zeros for the I/O ports for the PCI +	 * instance. +	 */ +	if (*addr1p == 0) { +		ADBG_TRACE(("ata_setup_ioaddr ioaddr1 0\n")); +		goto out2; +	} + +	/* +	 * map the upper control block registers +	 */ +	rc = ddi_regs_map_setup(dip, rnumber + 1, addr2p, 0, 0, &dev_attr, +				handle2p); +	if (rc == DDI_SUCCESS) +		return (TRUE); + +	cmn_err(CE_WARN, "ata: reg tuple 1 map failed, rc=0x%x", rc); + +out2: +	if (*handle1p != NULL) { +		ddi_regs_map_free(handle1p); +		*handle1p = NULL; +	} + +out1: +	if (*bm_hdlp != NULL) { +		ddi_regs_map_free(bm_hdlp); +		*bm_hdlp = NULL; +	} +	return (FALSE); + +} + +/* + * + * Currently, the only supported controllers are ones which + * support the SFF-8038 Bus Mastering spec. + * + * Check the parent node's IEEE 1275 class-code property to + * determine if it's an PCI-IDE instance which supports SFF-8038 + * Bus Mastering. It's perfectly valid to have a PCI-IDE controller + * that doesn't do Bus Mastering. In that case, my interrupt handler + * only uses the interrupt latch bit in PCI-IDE status register. + * The assumption is that the programming interface byte of the + * class-code property reflects the bus master DMA capability of + * the controller. + * + * Whether the drive support supports the DMA option still needs + * to be checked later. Each individual request also has to be + * checked for alignment and size to decide whether to use the + * DMA transfer mode. + */ + +static void +ata_init_pciide( +	dev_info_t	 *dip, +	ata_ctl_t *ata_ctlp) +{ +	uint_t	 class_code; +	uchar_t	 status; + +	ata_cntrl_DMA_sel_msg = NULL; + +	if (ata_ctlp->ac_bmhandle == NULL) { +		ata_ctlp->ac_pciide = FALSE; +		ata_ctlp->ac_pciide_bm = FALSE; +		ata_cntrl_DMA_sel_msg = "cntrl not Bus Master DMA capable"; +		return; +	} + +	/* +	 * check if it's a known bogus PCI-IDE chip +	 */ +	if (ata_check_pciide_blacklist(dip, ATA_BL_BOGUS)) { +		ADBG_WARN(("ata_setup_ioaddr pci-ide blacklist\n")); +		ata_ctlp->ac_pciide = FALSE; +		ata_ctlp->ac_pciide_bm = FALSE; +		ata_cntrl_DMA_sel_msg = "cntrl blacklisted"; +		return; +	} +	ata_ctlp->ac_pciide = TRUE; + +	if (ata_check_pciide_blacklist(dip, ATA_BL_BMSTATREG_PIO_BROKEN)) { +		ata_ctlp->ac_flags |= AC_BMSTATREG_PIO_BROKEN; +	} + +	/* +	 * check for a PCI-IDE chip with a broken DMA engine +	 */ +	if (ata_check_pciide_blacklist(dip, ATA_BL_NODMA)) { +		ata_ctlp->ac_pciide_bm = FALSE; +		ata_cntrl_DMA_sel_msg = +			"cntrl blacklisted/DMA engine broken"; +		return; +	} + +	/* +	 * Check the Programming Interface register to determine +	 * if this device supports PCI-IDE Bus Mastering. Some PCI-IDE +	 * devices don't support Bus Mastering or DMA. +	 * Since we are dealing with pre-qualified pci-ide controller, +	 * check programming interface byte only. +	 */ + +	class_code = ddi_prop_get_int(DDI_DEV_T_ANY, ddi_get_parent(dip), +		DDI_PROP_DONTPASS, "class-code", 0); +	if ((class_code & PCIIDE_BM_CAP_MASK) != PCIIDE_BM_CAP_MASK) { +		ata_ctlp->ac_pciide_bm = FALSE; +		ata_cntrl_DMA_sel_msg = +			"cntrl not Bus Master DMA capable"; +		return; +	} + +	/* +	 * Avoid doing DMA on "simplex" chips which share hardware +	 * between channels +	 */ +	status = ddi_get8(ata_ctlp->ac_bmhandle, +			(uchar_t *)ata_ctlp->ac_bmaddr + PCIIDE_BMISX_REG); +	/* +	 * Some motherboards have CSB5's that are wired "to emulate CSB4 mode". +	 * In such a mode, the simplex bit is asserted,  but in fact testing +	 * on such a motherboard has shown that the devices are not simplex +	 * -- DMA can be used on both channels concurrently with no special +	 * considerations.  For chips like this, we have the ATA_BL_NO_SIMPLEX +	 * flag set to indicate that the value of the simplex bit can be +	 * ignored. +	 */ + +	if (status & PCIIDE_BMISX_SIMPLEX) { +		if (ata_check_pciide_blacklist(dip, ATA_BL_NO_SIMPLEX))  { +			cmn_err(CE_WARN, "Ignoring false simplex bit \n"); +		} else { +			ata_ctlp->ac_pciide_bm = FALSE; +			ata_cntrl_DMA_sel_msg = +				"cntrl sharing DMA engine between channels"; +			return; +		} +	} + +	/* +	 * It's a compatible PCI-IDE Bus Mastering controller, +	 * allocate and map the DMA Scatter/Gather list (PRDE table). +	 */ +	if (ata_pciide_alloc(dip, ata_ctlp)) +		ata_ctlp->ac_pciide_bm = TRUE; +	else { +		ata_ctlp->ac_pciide_bm = FALSE; +		ata_cntrl_DMA_sel_msg = "unable to init DMA S/G list"; +	} +} + +/* + * + * Determine whether to enable DMA support for this drive. + * The controller and the drive both have to support DMA. + * The controller's capabilities were already checked in + * ata_init_pciide(), now just check the drive's capabilities. + * + */ + +static int +ata_init_drive_pcidma( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	dev_info_t *tdip) +{ +	boolean_t dma; +	boolean_t cd_dma; +	boolean_t disk_dma; +	boolean_t atapi_dma; +	int ata_options; + +	ata_dev_DMA_sel_msg = NULL; + +	if (ata_ctlp->ac_pciide_bm != TRUE) { +		ata_dev_DMA_sel_msg = +		    "controller is not Bus Master capable"; + +		return (ATA_DMA_OFF); +	} + +	ata_options = ddi_prop_get_int(DDI_DEV_T_ANY, ata_ctlp->ac_dip, +			0, "ata-options", 0); + +	if (!(ata_options & ATA_OPTIONS_DMA)) { +		/* +		 * Either the ata-options property was not found or +		 * DMA is not enabled by this property +		 */ +		ata_dev_DMA_sel_msg = +			"disabled by \"ata-options\" property"; + +		return (ATA_DMA_OFF); +	} + +	if (ata_check_drive_blacklist(&ata_drvp->ad_id, ATA_BL_NODMA)) { +		ata_dev_DMA_sel_msg = "device not DMA capable; blacklisted"; + +		return (ATA_DMA_OFF); +	} + +	/* +	 * DMA mode is mandatory on ATA-3 (or newer) drives but is +	 * optional on ATA-2 (or older) drives. +	 * +	 * On ATA-2 drives the ai_majorversion word will probably +	 * be 0xffff or 0x0000, check the (now obsolete) DMA bit in +	 * the capabilities word instead. The order of these tests +	 * is important since an ATA-3 drive doesn't have to set +	 * the DMA bit in the capabilities word. +	 * +	 */ + +	if (!((ata_drvp->ad_id.ai_majorversion & 0x8000) == 0 && +	    ata_drvp->ad_id.ai_majorversion >= (1 << 2)) && +	    !(ata_drvp->ad_id.ai_cap & ATAC_DMA_SUPPORT)) { +		ata_dev_DMA_sel_msg = "device not DMA capable"; + +		return (ATA_DMA_OFF); +	} + +	dma = ata_prop_lookup_int(DDI_DEV_T_ANY, tdip, +		0, "ata-dma-enabled", TRUE); +	disk_dma = ata_prop_lookup_int(DDI_DEV_T_ANY, tdip, +		0, "ata-disk-dma-enabled", TRUE); +	cd_dma = ata_prop_lookup_int(DDI_DEV_T_ANY, tdip, +		0, "atapi-cd-dma-enabled", FALSE); +	atapi_dma = ata_prop_lookup_int(DDI_DEV_T_ANY, tdip, +		0, "atapi-other-dma-enabled", TRUE); + +	if (dma == FALSE) { +		cmn_err(CE_CONT, "?ata_init_drive_pcidma: " +		    "DMA disabled by \"ata-dma-enabled\" property"); +		ata_dev_DMA_sel_msg = "disabled by prop ata-dma-enabled"; + +		return (ATA_DMA_OFF); +	} + +	if (IS_CDROM(ata_drvp) == TRUE) { +		if (cd_dma == FALSE) { +			ata_dev_DMA_sel_msg = +			    "disabled.  Control with \"atapi-cd-dma-enabled\"" +			    " property"; + +			return (ATA_DMA_OFF); +		} + +	} else if (ATAPIDRV(ata_drvp) == FALSE) { +		if (disk_dma == FALSE) { +			ata_dev_DMA_sel_msg = +			    "disabled by \"ata-disk-dma-enabled\" property"; + +			return (ATA_DMA_OFF); +		} + +	} else if (atapi_dma == FALSE) { +			ata_dev_DMA_sel_msg = +			    "disabled by \"atapi-other-dma-enabled\" property"; + +			return (ATA_DMA_OFF); +	} + +	return (ATA_DMA_ON); +} + + + +/* + * this compare routine squeezes out extra blanks and + * returns TRUE if p1 matches the leftmost substring of p2 + */ + +static int +ata_strncmp( +	char *p1, +	char *p2, +	int cnt) +{ + +	for (;;) { +		/* +		 * skip over any extra blanks in both strings +		 */ +		while (*p1 != '\0' && *p1 == ' ') +			p1++; + +		while (cnt != 0 && *p2 == ' ') { +			p2++; +			cnt--; +		} + +		/* +		 * compare the two strings +		 */ + +		if (cnt == 0 || *p1 != *p2) +			break; + +		while (cnt > 0 && *p1 == *p2) { +			p1++; +			p2++; +			cnt--; +		} + +	} + +	/* return TRUE if both strings ended at same point */ +	return ((*p1 == '\0') ? TRUE : FALSE); +} + +/* + * Per PSARC/1997/281 create variant="atapi" property (if necessary) + * on the target's dev_info node. Currently, the sd target driver + * is the only driver which refers to this property. + * + * If the flag ata_id_debug is set also create the + * the "ata" or "atapi" property on the target's dev_info node + * + */ + +int +ata_prop_create( +	dev_info_t *tgt_dip, +	ata_drv_t  *ata_drvp, +	char	   *name) +{ +	int	rc; + +	ADBG_TRACE(("ata_prop_create 0x%p 0x%p %s\n", tgt_dip, ata_drvp, name)); + +	if (strcmp("atapi", name) == 0) { +		rc =  ndi_prop_update_string(DDI_DEV_T_NONE, tgt_dip, +			"variant", name); +		if (rc != DDI_PROP_SUCCESS) +			return (FALSE); +	} + +	if (!ata_id_debug) +		return (TRUE); + +	rc =  ndi_prop_update_byte_array(DDI_DEV_T_NONE, tgt_dip, name, +		(uchar_t *)&ata_drvp->ad_id, sizeof (ata_drvp->ad_id)); +	if (rc != DDI_PROP_SUCCESS) { +		ADBG_ERROR(("ata_prop_create failed, rc=%d\n", rc)); +	} +	return (TRUE); +} + + +/* *********************************************************************** */ +/* *********************************************************************** */ +/* *********************************************************************** */ + +/* + * This state machine doesn't implement the ATAPI Optional Overlap + * feature. You need that feature to efficiently support ATAPI + * tape drives. See the 1394-ATA Tailgate spec (D97107), Figure 24, + * for an example of how to add the necessary additional NextActions + * and NextStates to this FSM and the atapi_fsm, in order to support + * the Overlap Feature. + */ + + +uchar_t ata_ctlr_fsm_NextAction[ATA_CTLR_NSTATES][ATA_CTLR_NFUNCS] = { +/* --------------------- next action --------------------- | - current - */ +/* start0 --- start1 ---- intr ------ fini --- reset --- */ +{ AC_START,   AC_START,	  AC_NADA,    AC_NADA, AC_RESET_I }, /* idle	 */ +{ AC_BUSY,    AC_BUSY,	  AC_INTR,    AC_FINI, AC_RESET_A }, /* active0  */ +{ AC_BUSY,    AC_BUSY,	  AC_INTR,    AC_FINI, AC_RESET_A }, /* active1  */ +}; + +uchar_t ata_ctlr_fsm_NextState[ATA_CTLR_NSTATES][ATA_CTLR_NFUNCS] = { + +/* --------------------- next state --------------------- | - current - */ +/* start0 --- start1 ---- intr ------ fini --- reset --- */ +{ AS_ACTIVE0, AS_ACTIVE1, AS_IDLE,    AS_IDLE, AS_IDLE	  }, /* idle    */ +{ AS_ACTIVE0, AS_ACTIVE0, AS_ACTIVE0, AS_IDLE, AS_ACTIVE0 }, /* active0 */ +{ AS_ACTIVE1, AS_ACTIVE1, AS_ACTIVE1, AS_IDLE, AS_ACTIVE1 }, /* active1 */ +}; + + +static int +ata_ctlr_fsm( +	uchar_t		 fsm_func, +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp, +	int		*DoneFlgp) +{ +	uchar_t	   action; +	uchar_t	   current_state; +	uchar_t	   next_state; +	int	   rc; + +	current_state = ata_ctlp->ac_state; +	action = ata_ctlr_fsm_NextAction[current_state][fsm_func]; +	next_state = ata_ctlr_fsm_NextState[current_state][fsm_func]; + +	/* +	 * Set the controller's new state +	 */ +	ata_ctlp->ac_state = next_state; +	switch (action) { + +	case AC_BUSY: +		return (ATA_FSM_RC_BUSY); + +	case AC_NADA: +		return (ATA_FSM_RC_OKAY); + +	case AC_START: +		ASSERT(ata_ctlp->ac_active_pktp == NULL); +		ASSERT(ata_ctlp->ac_active_drvp == NULL); + +		ata_ctlp->ac_active_pktp = ata_pktp; +		ata_ctlp->ac_active_drvp = ata_drvp; + +		rc = (*ata_pktp->ap_start)(ata_ctlp, ata_drvp, ata_pktp); + +		if (rc == ATA_FSM_RC_BUSY) { +			/* the request didn't start, GHD will requeue it */ +			ata_ctlp->ac_state = AS_IDLE; +			ata_ctlp->ac_active_pktp = NULL; +			ata_ctlp->ac_active_drvp = NULL; +		} +		return (rc); + +	case AC_INTR: +		ASSERT(ata_ctlp->ac_active_pktp != NULL); +		ASSERT(ata_ctlp->ac_active_drvp != NULL); + +		ata_drvp = ata_ctlp->ac_active_drvp; +		ata_pktp = ata_ctlp->ac_active_pktp; +		return ((*ata_pktp->ap_intr)(ata_ctlp, ata_drvp, ata_pktp)); + +	case AC_RESET_A: /* Reset, controller active */ +		ASSERT(ata_ctlp->ac_active_pktp != NULL); +		ASSERT(ata_ctlp->ac_active_drvp != NULL); + +		/* clean up the active request */ +		ata_pktp = ata_ctlp->ac_active_pktp; +		ata_pktp->ap_flags |= AP_DEV_RESET | AP_BUS_RESET; + +		/* halt the DMA engine */ +		if (ata_pktp->ap_pciide_dma) { +			ata_pciide_dma_stop(ata_ctlp); +			(void) ata_pciide_status_clear(ata_ctlp); +		} + +		/* Do a Software Reset to unwedge the bus */ +		if (!ata_software_reset(ata_ctlp)) { +			return (ATA_FSM_RC_BUSY); +		} + +		/* Then send a DEVICE RESET cmd to each ATAPI device */ +		atapi_fsm_reset(ata_ctlp); +		return (ATA_FSM_RC_FINI); + +	case AC_RESET_I: /* Reset, controller idle */ +		/* Do a Software Reset to unwedge the bus */ +		if (!ata_software_reset(ata_ctlp)) { +			return (ATA_FSM_RC_BUSY); +		} + +		/* Then send a DEVICE RESET cmd to each ATAPI device */ +		atapi_fsm_reset(ata_ctlp); +		return (ATA_FSM_RC_OKAY); + +	case AC_FINI: +		break; +	} + +	/* +	 * AC_FINI, check ARQ needs to be started or finished +	 */ + +	ASSERT(action == AC_FINI); +	ASSERT(ata_ctlp->ac_active_pktp != NULL); +	ASSERT(ata_ctlp->ac_active_drvp != NULL); + +	/* +	 * The active request is done now. +	 * Disconnect the request from the controller and +	 * add it to the done queue. +	 */ +	ata_drvp = ata_ctlp->ac_active_drvp; +	ata_pktp = ata_ctlp->ac_active_pktp; + +	/* +	 * If ARQ pkt is done, get ptr to original pkt and wrap it up. +	 */ +	if (ata_pktp == ata_ctlp->ac_arq_pktp) { +		ata_pkt_t *arq_pktp; + +		ADBG_ARQ(("ata_ctlr_fsm 0x%p ARQ done\n", ata_ctlp)); + +		arq_pktp = ata_pktp; +		ata_pktp = ata_ctlp->ac_fault_pktp; +		ata_ctlp->ac_fault_pktp = NULL; +		if (arq_pktp->ap_flags & (AP_ERROR | AP_BUS_RESET)) +			ata_pktp->ap_flags |= AP_ARQ_ERROR; +		else +			ata_pktp->ap_flags |= AP_ARQ_OKAY; +		goto all_done; +	} + + +#define	AP_ARQ_NEEDED	(AP_ARQ_ON_ERROR | AP_GOT_STATUS | AP_ERROR) + +	/* +	 * Start ARQ pkt if necessary +	 */ +	if ((ata_pktp->ap_flags & AP_ARQ_NEEDED) == AP_ARQ_NEEDED && +			(ata_pktp->ap_status & ATS_ERR)) { + +		/* set controller state back to active */ +		ata_ctlp->ac_state = current_state; + +		/* try to start the ARQ pkt */ +		rc = ata_start_arq(ata_ctlp, ata_drvp, ata_pktp); + +		if (rc == ATA_FSM_RC_BUSY) { +			ADBG_ARQ(("ata_ctlr_fsm 0x%p ARQ BUSY\n", ata_ctlp)); +			/* let the target driver handle the problem */ +			ata_ctlp->ac_state = AS_IDLE; +			ata_ctlp->ac_active_pktp = NULL; +			ata_ctlp->ac_active_drvp = NULL; +			ata_ctlp->ac_fault_pktp = NULL; +			goto all_done; +		} + +		ADBG_ARQ(("ata_ctlr_fsm 0x%p ARQ started\n", ata_ctlp)); +		return (rc); +	} + +	/* +	 * Normal completion, no error status, and not an ARQ pkt, +	 * just fall through. +	 */ + +all_done: + +	/* +	 * wrap everything up and tie a ribbon around it +	 */ +	ata_ctlp->ac_active_pktp = NULL; +	ata_ctlp->ac_active_drvp = NULL; +	if (APKT2GCMD(ata_pktp) != (gcmd_t *)0) { +		ghd_complete(&ata_ctlp->ac_ccc, APKT2GCMD(ata_pktp)); +		if (DoneFlgp) +			*DoneFlgp = TRUE; +	} + +	return (ATA_FSM_RC_OKAY); +} + + +static int +ata_start_arq( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	ata_pkt_t *ata_pktp) +{ +	ata_pkt_t		*arq_pktp; +	int			 bytes; +	uint_t			 senselen; + +	ADBG_ARQ(("ata_start_arq 0x%p ARQ needed\n", ata_ctlp)); + +	/* +	 * Determine just the size of the Request Sense Data buffer within +	 * the scsi_arq_status structure. +	 */ +#define	SIZEOF_ARQ_HEADER	(sizeof (struct scsi_arq_status)	\ +				- sizeof (struct scsi_extended_sense)) +	senselen = ata_pktp->ap_statuslen - SIZEOF_ARQ_HEADER; +	ASSERT(senselen > 0); + + +	/* save ptr to original pkt */ +	ata_ctlp->ac_fault_pktp = ata_pktp; + +	/* switch the controller's active pkt to the ARQ pkt */ +	arq_pktp = ata_ctlp->ac_arq_pktp; +	ata_ctlp->ac_active_pktp = arq_pktp; + +	/* finish initializing the ARQ CDB */ +	ata_ctlp->ac_arq_cdb[1] = ata_drvp->ad_lun << 4; +	ata_ctlp->ac_arq_cdb[4] = senselen; + +	/* finish initializing the ARQ pkt */ +	arq_pktp->ap_v_addr = (caddr_t)&ata_pktp->ap_scbp->sts_sensedata; + +	arq_pktp->ap_resid = senselen; +	arq_pktp->ap_flags = AP_ATAPI | AP_READ; +	arq_pktp->ap_cdb_pad = +	((unsigned)(ata_drvp->ad_cdb_len - arq_pktp->ap_cdb_len)) >> 1; + +	bytes = min(senselen, ATAPI_MAX_BYTES_PER_DRQ); +	arq_pktp->ap_hicyl = (uchar_t)(bytes >> 8); +	arq_pktp->ap_lwcyl = (uchar_t)bytes; + +	/* +	 * This packet is shared by all drives on this controller +	 * therefore we need to init the drive number on every ARQ. +	 */ +	arq_pktp->ap_hd = ata_drvp->ad_drive_bits; + +	/* start it up */ +	return ((*arq_pktp->ap_start)(ata_ctlp, ata_drvp, arq_pktp)); +} + +/* + * + * reset the bus + * + */ + +static int +ata_reset_bus( +	ata_ctl_t *ata_ctlp) +{ +	int	watchdog; +	uchar_t	drive; +	int	rc = FALSE; +	uchar_t	fsm_func; +	int	DoneFlg = FALSE; + +	/* +	 * Do a Software Reset to unwedge the bus, and send +	 * ATAPI DEVICE RESET to each ATAPI drive. +	 */ +	fsm_func = ATA_FSM_RESET; +	for (watchdog = ata_reset_bus_watchdog; watchdog > 0; watchdog--) { +		switch (ata_ctlr_fsm(fsm_func, ata_ctlp, NULL, NULL, +						&DoneFlg)) { +		case ATA_FSM_RC_OKAY: +			rc = TRUE; +			goto fsm_done; + +		case ATA_FSM_RC_BUSY: +			return (FALSE); + +		case ATA_FSM_RC_INTR: +			fsm_func = ATA_FSM_INTR; +			rc = TRUE; +			continue; + +		case ATA_FSM_RC_FINI: +			fsm_func = ATA_FSM_FINI; +			rc = TRUE; +			continue; +		} +	} +	ADBG_WARN(("ata_reset_bus: watchdog\n")); + +fsm_done: + +	/* +	 * Reinitialize the ATA drives +	 */ +	for (drive = 0; drive < ATA_MAXTARG; drive++) { +		ata_drv_t *ata_drvp; + +		if ((ata_drvp = CTL2DRV(ata_ctlp, drive, 0)) == NULL) +			continue; + +		if (ATAPIDRV(ata_drvp)) +			continue; + +		/* +		 * Reprogram the Read/Write Multiple block factor +		 * and current geometry into the drive. +		 */ +		if (!ata_disk_setup_parms(ata_ctlp, ata_drvp)) +			rc = FALSE; +	} + +	/* If DoneFlg is TRUE, it means that ghd_complete() function */ +	/* has been already called. In this case ignore any errors and */ +	/* return TRUE to the caller, otherwise return the value of rc */ +	/* to the caller */ +	if (DoneFlg) +		return (TRUE); +	else +		return (rc); +} + + +/* + * + * Low level routine to toggle the Software Reset bit + * + */ + +static int +ata_software_reset( +	ata_ctl_t *ata_ctlp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	int		 time_left; + +	ADBG_TRACE(("ata_reset_bus entered\n")); + +	/* disable interrupts and turn the software reset bit on */ +	ddi_put8(io_hdl2, ata_ctlp->ac_devctl, (ATDC_D3 | ATDC_SRST)); + +	/* why 30 milliseconds, the ATA/ATAPI-4 spec says 5 usec. */ +	drv_usecwait(30000); + +	/* turn the software reset bit back off */ +	ddi_put8(io_hdl2, ata_ctlp->ac_devctl, ATDC_D3); + +	/* +	 * Wait for the controller to assert BUSY status. +	 * I don't think 300 msecs is correct. The ATA/ATAPI-4 +	 * spec says 400 nsecs, (and 2 msecs if device +	 * was in sleep mode; but we don't put drives to sleep +	 * so it probably doesn't matter). +	 */ +	drv_usecwait(300000); + +	/* +	 * If drive 0 exists the test for completion is simple +	 */ +	time_left = 31 * 1000000; +	if (CTL2DRV(ata_ctlp, 0, 0)) { +		goto wait_for_not_busy; +	} + +	ASSERT(CTL2DRV(ata_ctlp, 1, 0) != NULL); + +	/* +	 * This must be a single device configuration, with drive 1 +	 * only. This complicates the test for completion because +	 * issuing the software reset just caused drive 1 to +	 * deselect. With drive 1 deselected, if I just read the +	 * status register to test the BSY bit I get garbage, but +	 * I can't re-select drive 1 until I'm certain the BSY bit +	 * is de-asserted. Catch-22. +	 * +	 * In ATA/ATAPI-4, rev 15, section 9.16.2, it says to handle +	 * this situation like this: +	 */ + +	/* give up if the drive doesn't settle within 31 seconds */ +	while (time_left > 0) { +		/* +		 * delay 10msec each time around the loop +		 */ +		drv_usecwait(10000); +		time_left -= 10000; + +		/* +		 * try to select drive 1 +		 */ +		ddi_put8(io_hdl1, ata_ctlp->ac_drvhd, ATDH_DRIVE1); + +		ddi_put8(io_hdl1, ata_ctlp->ac_sect, 0x55); +		ddi_put8(io_hdl1, ata_ctlp->ac_sect, 0xaa); +		if (ddi_get8(io_hdl1, ata_ctlp->ac_sect) != 0xaa) +			continue; + +		ddi_put8(io_hdl1, ata_ctlp->ac_count, 0x55); +		ddi_put8(io_hdl1, ata_ctlp->ac_count, 0xaa); +		if (ddi_get8(io_hdl1, ata_ctlp->ac_count) != 0xaa) +			continue; + +		goto wait_for_not_busy; +	} +	return (FALSE); + +wait_for_not_busy: + +	/* +	 * Now wait upto 31 seconds for BUSY to clear. +	 */ +	(void) ata_wait3(io_hdl2, ata_ctlp->ac_ioaddr2, 0, ATS_BSY, +		ATS_ERR, ATS_BSY, ATS_DF, ATS_BSY, time_left); + +	return (TRUE); +} + +/* + * + * DDI interrupt handler + * + */ + +static uint_t +ata_intr( +	caddr_t arg) +{ +	ata_ctl_t *ata_ctlp; +	int	   one_shot = 1; + +	ata_ctlp = (ata_ctl_t *)arg; + +	return (ghd_intr(&ata_ctlp->ac_ccc, (void *)&one_shot)); +} + + +/* + * + * GHD ccc_get_status callback + * + */ + +static int +ata_get_status( +	void *hba_handle, +	void *intr_status) +{ +	ata_ctl_t *ata_ctlp = (ata_ctl_t *)hba_handle; +	uchar_t	   status; + +	ADBG_TRACE(("ata_get_status entered\n")); + +	/* +	 * ignore interrupts before ata_attach completes +	 */ +	if (!(ata_ctlp->ac_flags & AC_ATTACHED)) +		return (FALSE); + +	/* +	 * can't be interrupt pending if nothing active +	 */ +	switch (ata_ctlp->ac_state) { +	case AS_IDLE: +		return (FALSE); +	case AS_ACTIVE0: +	case AS_ACTIVE1: +		ASSERT(ata_ctlp->ac_active_drvp != NULL); +		ASSERT(ata_ctlp->ac_active_pktp != NULL); +		break; +	} + +	/* +	 * If this is a PCI-IDE controller, check the PCI-IDE controller's +	 * interrupt status latch. But don't clear it yet. +	 * +	 * AC_BMSTATREG_PIO_BROKEN flag is used currently for +	 * CMD chips with device id 0x646. Since the interrupt bit on +	 * Bus master IDE register is not usable when in PIO mode, +	 * this chip is treated as a legacy device for interrupt +	 * indication.  The following code for CMD +	 * chips may need to be revisited when we enable support for dma. +	 * +	 * CHANGE: DMA is not disabled for these devices. BM intr bit is +	 * checked only if there was DMA used or BM intr is useable on PIO, +	 * else treat it as before - as legacy device. +	 */ + +	if ((ata_ctlp->ac_pciide) && +	    ((ata_ctlp->ac_pciide_bm != FALSE) && +	    ((ata_ctlp->ac_active_pktp->ap_pciide_dma == TRUE) || +	    !(ata_ctlp->ac_flags & AC_BMSTATREG_PIO_BROKEN)))) { + +		if (!ata_pciide_status_pending(ata_ctlp)) +			return (FALSE); +	} else { +		/* +		 * Interrupts from legacy ATA/IDE controllers are +		 * edge-triggered but the dumb legacy ATA/IDE controllers +		 * and drives don't have an interrupt status bit. +		 * +		 * Use a one_shot variable to make sure we only return +		 * one status per interrupt. +		 */ +		if (intr_status != NULL) { +			int *one_shot = (int *)intr_status; + +			if (*one_shot == 1) +				*one_shot = 0; +			else +				return (FALSE); +		} +	} + +	/* check if device is still busy */ + +	status = ddi_get8(ata_ctlp->ac_iohandle2, ata_ctlp->ac_altstatus); +	if (status & ATS_BSY) +		return (FALSE); +	return (TRUE); +} + + +/* + * + * get the current status and clear the IRQ + * + */ + +int +ata_get_status_clear_intr( +	ata_ctl_t *ata_ctlp, +	ata_pkt_t *ata_pktp) +{ +	uchar_t	status; + +	/* +	 * Here's where we clear the PCI-IDE interrupt latch. If this +	 * request used DMA mode then we also have to check and clear +	 * the DMA error latch at the same time. +	 */ + +	if (ata_pktp->ap_pciide_dma) { +		if (ata_pciide_status_dmacheck_clear(ata_ctlp)) +			ata_pktp->ap_flags |= AP_ERROR | AP_TRAN_ERROR; +	} else if ((ata_ctlp->ac_pciide) && +	    !(ata_ctlp->ac_flags & AC_BMSTATREG_PIO_BROKEN)) { +		/* +		 * Some requests don't use DMA mode and therefore won't +		 * set the DMA error latch, but we still have to clear +		 * the interrupt latch. +		 * Controllers with broken BM intr in PIO mode do not go +		 * through this path. +		 */ +		(void) ata_pciide_status_clear(ata_ctlp); +	} + +	/* +	 * this clears the drive's interrupt +	 */ +	status = ddi_get8(ata_ctlp->ac_iohandle1, ata_ctlp->ac_status); +	ADBG_TRACE(("ata_get_status_clear_intr: 0x%x\n", status)); +	return (status); +} + + + +/* + * + * GHD interrupt handler + * + */ + +/* ARGSUSED */ +static void +ata_process_intr( +	void *hba_handle, +	void *intr_status) +{ +	ata_ctl_t *ata_ctlp = (ata_ctl_t *)hba_handle; +	int	   watchdog; +	uchar_t	   fsm_func; +	int	   rc; + +	ADBG_TRACE(("ata_process_intr entered\n")); + +	/* +	 * process the ATA or ATAPI interrupt +	 */ + +	fsm_func = ATA_FSM_INTR; +	for (watchdog = ata_process_intr_watchdog; watchdog > 0; watchdog--) { +		rc =  ata_ctlr_fsm(fsm_func, ata_ctlp, NULL, NULL, NULL); + +		switch (rc) { +		case ATA_FSM_RC_OKAY: +			return; + +		case ATA_FSM_RC_BUSY:	/* wait for the next interrupt */ +			return; + +		case ATA_FSM_RC_INTR:	/* re-invoke the FSM */ +			fsm_func = ATA_FSM_INTR; +			break; + +		case ATA_FSM_RC_FINI:	/* move a request to done Q */ +			fsm_func = ATA_FSM_FINI; +			break; +		} +	} +	ADBG_WARN(("ata_process_intr: watchdog\n")); +} + + + +/* + * + * GHD ccc_hba_start callback + * + */ + +static int +ata_hba_start( +	void *hba_handle, +	gcmd_t *gcmdp) +{ +	ata_ctl_t *ata_ctlp; +	ata_drv_t *ata_drvp; +	ata_pkt_t *ata_pktp; +	uchar_t	   fsm_func; +	int	   request_started; +	int	   watchdog; + +	ADBG_TRACE(("ata_hba_start entered\n")); + +	ata_ctlp = (ata_ctl_t *)hba_handle; + +	if (ata_ctlp->ac_active_drvp != NULL) { +		ADBG_WARN(("ata_hba_start drvp not null\n")); +		return (FALSE); +	} +	if (ata_ctlp->ac_active_pktp != NULL) { +		ADBG_WARN(("ata_hba_start pktp not null\n")); +		return (FALSE); +	} + +	ata_pktp = GCMD2APKT(gcmdp); +	ata_drvp = GCMD2DRV(gcmdp); + +	/* +	 * which drive? +	 */ +	if (ata_drvp->ad_targ == 0) +		fsm_func = ATA_FSM_START0; +	else +		fsm_func = ATA_FSM_START1; + +	/* +	 * start the request +	 */ +	request_started = FALSE; +	for (watchdog = ata_hba_start_watchdog; watchdog > 0; watchdog--) { +		switch (ata_ctlr_fsm(fsm_func, ata_ctlp, ata_drvp, ata_pktp, +				NULL)) { +		case ATA_FSM_RC_OKAY: +			request_started = TRUE; +			goto fsm_done; + +		case ATA_FSM_RC_BUSY: +			/* if first time, tell GHD to requeue the request */ +			goto fsm_done; + +		case ATA_FSM_RC_INTR: +			/* +			 * The start function polled for the next +			 * bus phase, now fake an interrupt to process +			 * the next action. +			 */ +			request_started = TRUE; +			fsm_func = ATA_FSM_INTR; +			ata_drvp = NULL; +			ata_pktp = NULL; +			break; + +		case ATA_FSM_RC_FINI: /* move request to the done queue */ +			request_started = TRUE; +			fsm_func = ATA_FSM_FINI; +			ata_drvp = NULL; +			ata_pktp = NULL; +			break; +		} +	} +	ADBG_WARN(("ata_hba_start: watchdog\n")); + +fsm_done: +	return (request_started); + +} + +static int +ata_check_pciide_blacklist( +	dev_info_t *dip, +	uint_t flags) +{ +	ushort_t vendorid; +	ushort_t deviceid; +	pcibl_t	*blp; +	int	*propp; +	uint_t	 count; +	int	 rc; + + +	vendorid = ddi_prop_get_int(DDI_DEV_T_ANY, ddi_get_parent(dip), +				    DDI_PROP_DONTPASS, "vendor-id", 0); +	deviceid = ddi_prop_get_int(DDI_DEV_T_ANY, ddi_get_parent(dip), +				    DDI_PROP_DONTPASS, "device-id", 0); + +	/* +	 * first check for a match in the "pci-ide-blacklist" property +	 */ +	rc = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, 0, +		"pci-ide-blacklist", &propp, &count); + +	if (rc == DDI_PROP_SUCCESS) { +		count = (count * sizeof (uint_t)) / sizeof (pcibl_t); +		blp = (pcibl_t *)propp; +		while (count--) { +			/* check for matching ID */ +			if ((vendorid & blp->b_vmask) +					!= (blp->b_vendorid & blp->b_vmask)) { +				blp++; +				continue; +			} +			if ((deviceid & blp->b_dmask) +					!= (blp->b_deviceid & blp->b_dmask)) { +				blp++; +				continue; +			} + +			/* got a match */ +			if (blp->b_flags & flags) { +				ddi_prop_free(propp); +				return (TRUE); +			} else { +				ddi_prop_free(propp); +				return (FALSE); +			} +		} +		ddi_prop_free(propp); +	} + +	/* +	 * then check the built-in blacklist +	 */ +	for (blp = ata_pciide_blacklist; blp->b_vendorid; blp++) { +		if ((vendorid & blp->b_vmask) != blp->b_vendorid) +			continue; +		if ((deviceid & blp->b_dmask) != blp->b_deviceid) +			continue; +		if (!(blp->b_flags & flags)) +			continue; +		return (TRUE); +	} +	return (FALSE); +} + +int +ata_check_drive_blacklist( +	struct ata_id *aidp, +	uint_t flags) +{ +	atabl_t	*blp; + +	for (blp = ata_drive_blacklist; blp->b_model; blp++) { +		if (!ata_strncmp(blp->b_model, aidp->ai_model, +				sizeof (aidp->ai_model))) +			continue; +		if (blp->b_flags & flags) +			return (TRUE); +		return (FALSE); +	} +	return (FALSE); +} + +/* + * Queue a request to perform some sort of internally + * generated command. When this request packet reaches + * the front of the queue (*func)() is invoked. + * + */ + +int +ata_queue_cmd( +	int	  (*func)(ata_ctl_t *, ata_drv_t *, ata_pkt_t *), +	void	  *arg, +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	gtgt_t	  *gtgtp) +{ +	ata_pkt_t	*ata_pktp; +	gcmd_t		*gcmdp; +	int		 rc; + +	if (!(gcmdp = ghd_gcmd_alloc(gtgtp, sizeof (*ata_pktp), TRUE))) { +		ADBG_ERROR(("atapi_id_update alloc failed\n")); +		return (FALSE); +	} + + +	/* set the back ptr from the ata_pkt to the gcmd_t */ +	ata_pktp = GCMD2APKT(gcmdp); +	ata_pktp->ap_gcmdp = gcmdp; +	ata_pktp->ap_hd = ata_drvp->ad_drive_bits; +	ata_pktp->ap_bytes_per_block = ata_drvp->ad_bytes_per_block; + +	/* +	 * over-ride the default start function +	 */ +	ata_pktp = GCMD2APKT(gcmdp); +	ata_pktp->ap_start = func; +	ata_pktp->ap_complete = NULL; +	ata_pktp->ap_v_addr = (caddr_t)arg; + +	/* +	 * add it to the queue, when it gets to the front the +	 * ap_start function is called. +	 */ +	rc = ghd_transport(&ata_ctlp->ac_ccc, gcmdp, gcmdp->cmd_gtgtp, +		0, TRUE, NULL); + +	if (rc != TRAN_ACCEPT) { +		/* this should never, ever happen */ +		return (FALSE); +	} + +	if (ata_pktp->ap_flags & AP_ERROR) +		return (FALSE); +	return (TRUE); +} + +/* + * Check if this drive has the "revert to defaults" bug + * PSARC 2001/500 and 2001/xxx - check for the properties + * ata-revert-to-defaults and atarvrt-<diskmodel> before + * examining the blacklist. + * <diskmodel> is made from the model number reported by Identify Drive + * with uppercase letters converted to lowercase and all characters + * except letters, digits, ".", "_", and "-" deleted. + * Return value: + *	TRUE:	enable revert to defaults + *	FALSE:	disable revert to defaults + * + * NOTE: revert to power on defaults that includes reverting to MDMA + * mode is allowed by ATA-6 & ATA-7 specs. + * Therefore drives exhibiting this behaviour are not violating the spec. + * Furthermore, the spec explicitly says that after the soft reset + * host should check the current setting of the device features. + * Correctly working BIOS would therefore reprogram either the drive + * and/or the host controller to match transfer modes. + * Devices with ATA_BL_NORVRT flag will be removed from + * the ata_blacklist. + * The default behaviour will be - no revert to power-on defaults + * for all devices. The property is retained in case the user + * explicitly requests revert-to-defaults before reboot. + */ + +#define	ATA_REVERT_PROP_PREFIX "revert-" +#define	ATA_REVERT_PROP_GLOBAL	"ata-revert-to-defaults" +/* room for prefix + model number + terminating NUL character */ +#define	PROP_BUF_SIZE	(sizeof (ATA_REVERT_PROP_PREFIX) + \ +				sizeof (aidp->ai_model) + 1) +#define	PROP_LEN_MAX	(31) + +static int +ata_check_revert_to_defaults( +	ata_drv_t *ata_drvp) +{ +	struct ata_id	*aidp = &ata_drvp->ad_id; +	ata_ctl_t	*ata_ctlp = ata_drvp->ad_ctlp; +	char	 prop_buf[PROP_BUF_SIZE]; +	int	 i, j; +	int	 propval; + +	/* put prefix into the buffer */ +	(void) strcpy(prop_buf, ATA_REVERT_PROP_PREFIX); +	j = strlen(prop_buf); + +	/* append the model number, leaving out invalid characters */ +	for (i = 0;  i < sizeof (aidp->ai_model);  ++i) { +		char c = aidp->ai_model[i]; +		if (c >= 'A' && c <= 'Z')	/* uppercase -> lower */ +			c = c - 'A' + 'a'; +		if (c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || +		    c == '.' || c == '_' || c == '-') +			prop_buf[j++] = c; +		if (c == '\0') +			break; +	} + +	/* make sure there's a terminating NUL character */ +	if (j >= PROP_LEN_MAX) +		j =  PROP_LEN_MAX; +	prop_buf[j] = '\0'; + +	/* look for a disk-specific "revert" property" */ +	propval = ddi_getprop(DDI_DEV_T_ANY, ata_ctlp->ac_dip, +		DDI_PROP_DONTPASS, prop_buf, -1); +	if (propval == 0) +		return (FALSE); +	else if (propval != -1) +		return (TRUE); + +	/* look for a global "revert" property" */ +	propval = ddi_getprop(DDI_DEV_T_ANY, ata_ctlp->ac_dip, +		0, ATA_REVERT_PROP_GLOBAL, -1); +	if (propval == 0) +		return (FALSE); +	else if (propval != -1) +		return (TRUE); + +	return (FALSE); +} + +void +ata_show_transfer_mode(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp) +{ +	int i; + +	if (ata_ctlp->ac_pciide_bm == FALSE || +	    ata_drvp->ad_pciide_dma != ATA_DMA_ON) { +		if (ata_cntrl_DMA_sel_msg) { +			ATAPRT(( +			    "?\tATA DMA off: %s\n", ata_cntrl_DMA_sel_msg)); +		} else if (ata_dev_DMA_sel_msg) { +			ATAPRT(("?\tATA DMA off: %s\n", ata_dev_DMA_sel_msg)); +		} +		ATAPRT(("?\tPIO mode %d selected\n", +		    (ata_drvp->ad_id.ai_advpiomode & ATAC_ADVPIO_4_SUP) == +			ATAC_ADVPIO_4_SUP ? 4 : 3)); +	} else { +		/* Using DMA */ +		if (ata_drvp->ad_id.ai_dworddma & ATAC_MDMA_SEL_MASK) { +			/* +			 * Rely on the fact that either dwdma or udma is +			 * selected, not both. +			 */ +			ATAPRT(("?\tMultiwordDMA mode %d selected\n", +			(ata_drvp->ad_id.ai_dworddma & ATAC_MDMA_2_SEL) == +			    ATAC_MDMA_2_SEL ? 2 : +			    (ata_drvp->ad_id.ai_dworddma & ATAC_MDMA_1_SEL) == +				ATAC_MDMA_1_SEL ? 1 : 0)); +		} else { +			for (i = 0; i <= 6; i++) { +				if (ata_drvp->ad_id.ai_ultradma & +				    (1 << (i + 8))) { +					ATAPRT(( +					    "?\tUltraDMA mode %d selected\n", +					    i)); +					break; +				} +			} +		} +	} +} + +/* + * Controller-specific operation pointers. + * Should be extended as needed - init only for now + */ +struct ata_ctl_spec_ops { +	uint_t	(*cs_init)(dev_info_t *, ushort_t, ushort_t); /* ctlr init */ +}; + + +struct ata_ctl_spec { +	ushort_t		cs_vendor_id; +	ushort_t		cs_device_id; +	struct ata_ctl_spec_ops	*cs_ops; +}; + +/* Sil3XXX-specific functions (init only for now) */ +struct ata_ctl_spec_ops sil3xxx_ops = { +	&sil3xxx_init_controller	/* Sil3XXX cntrl initialization */ +}; + + +struct ata_ctl_spec ata_cntrls_spec[] = { +	{0x1095, 0x3114, &sil3xxx_ops}, +	{0x1095, 0x3512, &sil3xxx_ops}, +	{0x1095, 0x3112, &sil3xxx_ops}, +	{0, 0, NULL}		/* List must end with cs_ops set to NULL */ +}; + +/* + * Do controller specific initialization if necessary. + * Pick-up controller specific functions. + */ + +int +ata_spec_init_controller(dev_info_t *dip) +{ +	ushort_t		vendor_id; +	ushort_t		device_id; +	struct ata_ctl_spec	*ctlsp; + +	vendor_id = ddi_prop_get_int(DDI_DEV_T_ANY, ddi_get_parent(dip), +				    DDI_PROP_DONTPASS, "vendor-id", 0); +	device_id = ddi_prop_get_int(DDI_DEV_T_ANY, ddi_get_parent(dip), +				    DDI_PROP_DONTPASS, "device-id", 0); + +	/* Locate controller specific ops, if they exist */ +	ctlsp = ata_cntrls_spec; +	while (ctlsp->cs_ops != NULL) { +		if (ctlsp->cs_vendor_id == vendor_id && +		    ctlsp->cs_device_id == device_id) +			break; +		ctlsp++; +	} + +	if (ctlsp->cs_ops != NULL) { +		if (ctlsp->cs_ops->cs_init != NULL) { +			/* Initialize controller */ +			if ((*(ctlsp->cs_ops->cs_init)) +			    (dip, vendor_id, device_id) != TRUE) { +				cmn_err(CE_WARN, +				    "pci%4x,%4x cntrl specific " +				    "initialization failed", +				    vendor_id, device_id); +				return (FALSE); +			} +		} +	} +	return (TRUE); +} + +/* + * this routine works like ddi_prop_get_int, except that it works on + * a string property that contains ascii representations + * of an integer. + * If the property is not found, the default value is returned. + */ +static int +ata_prop_lookup_int(dev_t match_dev, dev_info_t *dip, +	uint_t flags, char *name, int defvalue) +{ + +	char *bufp, *cp; +	int rc = defvalue; +	int proprc; + +	proprc = ddi_prop_lookup_string(match_dev, dip, +		flags, name, &bufp); + +	if (proprc == DDI_PROP_SUCCESS) { +		cp = bufp; +		rc = stoi(&cp); +		ddi_prop_free(bufp); +	} else { +		/* +		 * see if property is encoded as an int instead of string. +		 */ +		rc = ddi_prop_get_int(match_dev, dip, flags, name, defvalue); +	} + +	return (rc); +} diff --git a/usr/src/uts/intel/io/dktp/controller/ata/ata_common.h b/usr/src/uts/intel/io/dktp/controller/ata/ata_common.h new file mode 100644 index 0000000000..aca985bae9 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/ata_common.h @@ -0,0 +1,697 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ATA_COMMON_H +#define	_ATA_COMMON_H + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#ifdef	__cplusplus +extern "C" { +#endif + +#include <sys/varargs.h> + +#include <sys/scsi/scsi.h> +#include <sys/dktp/dadkio.h> +#include <sys/dktp/dadev.h> +#include <sys/dkio.h> +#include <sys/dktp/tgdk.h> + +#include <sys/ddi.h> +#include <sys/sunddi.h> + +#include "ghd.h" + +#include "pciide.h" +#include "ata_cmd.h" +#include "ata_fsm.h" +#include "ata_debug.h" + + +/* + * device types + */ +#define	ATA_DEV_NONE	0 +#define	ATA_DEV_DISK	1 +#define	ATA_DEV_ATAPI	2 + +/* + * Largest sector allowed in 28 bit mode + */ +#define	MAX_28BIT_CAPACITY	0xfffffff + + + +/* + * ata-options property configuration bits + */ + +#define	ATA_OPTIONS_DMA		0x01 + + + +/* ad_flags (per-drive) */ + +#define	AD_ATAPI		0x01	/* is an ATAPI drive */ +#define	AD_DISK			0x02 +#define	AD_MUTEX_INIT		0x04 +#define	AD_NO_CDB_INTR		0x20 +#define	AD_1SECTOR		0x40 +#define	AD_INT13LBA		0x80	/* supports LBA at Int13 interface */ +#define	AD_NORVRT		0x100	/* block revert-to-defaults */ +#define	AD_EXT48		0x200	/* 48 bit (extended) LBA */ +#define	ATAPIDRV(X)  ((X)->ad_flags & AD_ATAPI) + + +/* max targets and luns */ + +#define	ATA_MAXTARG	2 +#define	ATA_MAXLUN	16 + +/* + * PCI-IDE Bus Mastering Scatter/Gather list size + */ +#define	ATA_DMA_NSEGS	17	/* enough for at least 64K */ + +/* + * Controller port address defaults + */ +#define	ATA_BASE0	0x1f0 +#define	ATA_BASE1	0x170 + +/* + * port offsets from base address ioaddr1 + */ +#define	AT_DATA		0x00	/* data register 			*/ +#define	AT_ERROR	0x01	/* error register (read)		*/ +#define	AT_FEATURE	0x01	/* features (write)			*/ +#define	AT_COUNT	0x02    /* sector count 			*/ +#define	AT_SECT		0x03	/* sector number 			*/ +#define	AT_LCYL		0x04	/* cylinder low byte 			*/ +#define	AT_HCYL		0x05	/* cylinder high byte 			*/ +#define	AT_DRVHD	0x06    /* drive/head register 			*/ +#define	AT_STATUS	0x07	/* status/command register 		*/ +#define	AT_CMD		0x07	/* status/command register 		*/ + +/* + * port offsets from base address ioaddr2 + */ +#define	AT_ALTSTATUS	0x00	/* alternate status (read)		*/ +#define	AT_DEVCTL	0x00	/* device control (write)		*/ + +/*	Device control register						*/ +#define	ATDC_NIEN    	0x02    /* disable interrupts 			*/ +#define	ATDC_SRST	0x04	/* controller reset			*/ +#define	ATDC_D3		0x08	/* Mysterious bit, must be set  	*/ +/* + * ATA-6 spec + * In 48-bit addressing, reading the LBA location and count + * registers when the high-order bit is set reads the "previous + * content" (LBA bits 47:24, count bits 15:8) instead of the + * "most recent" values (LBA bits 23:0, count bits 7:0). + */ +#define	ATDC_HOB	0x80	/* High order bit			*/ + +/* + * Status bits from AT_STATUS register + */ +#define	ATS_BSY		0x80    /* controller busy 			*/ +#define	ATS_DRDY	0x40    /* drive ready 				*/ +#define	ATS_DF		0x20    /* device fault				*/ +#define	ATS_DSC    	0x10    /* seek operation complete 		*/ +#define	ATS_DRQ		0x08	/* data request 			*/ +#define	ATS_CORR	0x04    /* ECC correction applied 		*/ +#define	ATS_IDX		0x02    /* disk revolution index 		*/ +#define	ATS_ERR		0x01    /* error flag 				*/ + +/* + * Status bits from AT_ERROR register + */ +#define	ATE_BBK_ICRC	0x80	/* bad block detected in ATA-1		*/ +				/* ICRC error in ATA-4 and newer	*/ +#define	ATE_UNC		0x40	/* uncorrectable data error		*/ +#define	ATE_MC		0x20    /* Media change				*/ +#define	ATE_IDNF	0x10    /* ID not found				*/ +#define	ATE_MCR		0x08	/* media change request			*/ +#define	ATE_ABORT	0x04    /* aborted command			*/ +#define	ATE_TKONF	0x02    /* track 0 not found			*/ +#define	ATE_AMNF	0x01    /* address mark not found		*/ + +#define	ATE_NM		0x02	/* no media				*/ + +/* + * Drive selectors for AT_DRVHD register + */ +#define	ATDH_LBA	0x40	/* addressing in LBA mode not chs 	*/ +#define	ATDH_DRIVE0	0xa0    /* or into AT_DRVHD to select drive 0 	*/ +#define	ATDH_DRIVE1	0xb0    /* or into AT_DRVHD to select drive 1 	*/ + +/* + * Feature register bits + */ +#define	ATF_ATAPI_DMA	0x01	/* ATAPI DMA enable bit */ + +/* + * common bits and options for set features (ATC_SET_FEAT) + */ +#define	FC_WRITE_CACHE_ON	0x02 +#define	FC_WRITE_CACHE_OFF	0x82 + +/* Test which version of ATA is supported */ +#define	IS_ATA_VERSION_SUPPORTED(idp, n) \ +	((idp->ai_majorversion != 0xffff) && \ +	(idp->ai_majorversion & (1<<n))) + +/* Test if supported version >= ATA-n */ +#define	IS_ATA_VERSION_GE(idp, n) \ +	((idp->ai_majorversion != 0xffff) && \ +	(idp->ai_majorversion != 0) && \ +	(idp->ai_majorversion >= (1<<n))) + +/* Test whether a device is a CD drive */ +#define	IS_CDROM(dp) \ +		((dp->ad_flags & AD_ATAPI) && \ +		    ((dp->ad_id.ai_config >> 8) & DTYPE_MASK) == \ +		    DTYPE_RODIRECT) + +/*  macros from old common hba code */ + +#define	ATA_INTPROP(devi, pname, pval, plen) \ +	(ddi_prop_op(DDI_DEV_T_ANY, (devi), PROP_LEN_AND_VAL_BUF, \ +		DDI_PROP_DONTPASS, (pname), (caddr_t)(pval), (plen))) + +#define	ATA_LONGPROP(devi, pname, pval, plen) \ +	(ddi_getlongprop(DDI_DEV_T_ANY, (devi), DDI_PROP_DONTPASS, \ +		(pname), (caddr_t)(pval), (plen))) + +/* + * + * per-controller soft-state data structure + * + */ + +#define	CTL2DRV(cp, t, l)	(cp->ac_drvp[t][l]) + +typedef struct ata_ctl { + +	dev_info_t	*ac_dip; +	uint_t		 ac_flags; +	uint_t		 ac_timing_flags; +	struct ata_drv	*ac_drvp[ATA_MAXTARG][ATA_MAXLUN]; +	int		 ac_max_transfer; /* max transfer in sectors */ +	uint_t		 ac_standby_time; /* timer value seconds */ + +	ccc_t		 ac_ccc;	  /* for GHD module */ +	struct ata_drv	*ac_active_drvp;  /* active drive, if any */ +	struct ata_pkt	*ac_active_pktp;  /* active packet, if any */ +	uchar_t		 ac_state; + +	scsi_hba_tran_t *ac_atapi_tran;	  /* for atapi module */ + +	/* +	 * port addresses associated with ioaddr1 +	 */ +	ddi_acc_handle_t ac_iohandle1;	  /* DDI I/O handle */ +	caddr_t		 ac_ioaddr1; +	ushort_t	*ac_data;	  /* data register 		*/ +	uchar_t		*ac_error;	  /* error register (read)	*/ +	uchar_t		*ac_feature;	  /* features (write)		*/ +	uchar_t		*ac_count;	  /* sector count 		*/ +	uchar_t		*ac_sect;	  /* sector number 		*/ +	uchar_t		*ac_lcyl;	  /* cylinder low byte 		*/ +	uchar_t		*ac_hcyl;	  /* cylinder high byte 	*/ +	uchar_t		*ac_drvhd;	  /* drive/head register 	*/ +	uchar_t		*ac_status;	  /* status/command register 	*/ +	uchar_t		*ac_cmd;	  /* status/command register 	*/ + +	/* +	 * port addresses associated with ioaddr2 +	 */ +	ddi_acc_handle_t ac_iohandle2;	  /* DDI I/O handle		*/ +	caddr_t		 ac_ioaddr2; +	uchar_t		*ac_altstatus;	  /* alternate status (read)	*/ +	uchar_t		*ac_devctl;	  /* device control (write)	*/ + +	/* +	 * handle and port addresss for PCI-IDE Bus Master controller +	 */ +	ddi_acc_handle_t ac_bmhandle;	  /* DDI I/O handle		*/ +	caddr_t		 ac_bmaddr;	  /* base addr of Bus Master Regs */ +	uchar_t		 ac_pciide;	  /* PCI-IDE device */ +	uchar_t		 ac_pciide_bm;	  /* Bus Mastering PCI-IDE device */ + +	/* +	 * Scatter/Gather list for PCI-IDE Bus Mastering controllers +	 */ +	caddr_t		 ac_sg_list;	  /* virtual addr of S/G list */ +	paddr_t		 ac_sg_paddr;	  /* phys addr of S/G list */ +	ddi_acc_handle_t ac_sg_acc_handle; +	ddi_dma_handle_t ac_sg_handle; + +	/* +	 * data for managing ARQ on ATAPI devices +	 */ +	struct ata_pkt	*ac_arq_pktp;	  /* pkt for performing ATAPI ARQ */ +	struct ata_pkt	*ac_fault_pktp;	  /* pkt that caused ARQ */ +	uchar_t		 ac_arq_cdb[6]; +} ata_ctl_t; + +/* ac_flags (per-controller) */ + +#define	AC_GHD_INIT			0x02 +#define	AC_ATAPI_INIT			0x04 +#define	AC_DISK_INIT			0x08 +#define	AC_ATTACHED			0x10 +#define	AC_SCSI_HBA_TRAN_ALLOC		0x1000 +#define	AC_SCSI_HBA_ATTACH		0x2000 + +#define	AC_BMSTATREG_PIO_BROKEN		0x80000000 + +/* + * Bug 1256489: + * + * If AC_BSY_WAIT needs to be set  for laptops that do + * suspend/resume but do not correctly wait for the busy bit to + * drop after a resume. + */ + +/* ac_timing_flags (per-controller) */ +#define	AC_BSY_WAIT	0x1	/* tweak timing in ata_start & atapi_start */ + + + +/* Identify drive data */ +struct ata_id { +/*  					WORD				*/ +/* 					OFFSET COMMENT			*/ +	ushort_t  ai_config;	  /*   0  general configuration bits 	*/ +	ushort_t  ai_fixcyls;	  /*   1  # of fixed cylinders		*/ +	ushort_t  ai_resv0;	  /*   2  # reserved			*/ +	ushort_t  ai_heads;	  /*   3  # of heads			*/ +	ushort_t  ai_trksiz;	  /*   4  # of unformatted bytes/track 	*/ +	ushort_t  ai_secsiz;	  /*   5  # of unformatted bytes/sector	*/ +	ushort_t  ai_sectors;	  /*   6  # of sectors/track		*/ +	ushort_t  ai_resv1[3];	  /*   7  "Vendor Unique"		*/ +	char	ai_drvser[20];	  /*  10  Serial number			*/ +	ushort_t ai_buftype;	  /*  20  Buffer type			*/ +	ushort_t ai_bufsz;	  /*  21  Buffer size in 512 byte incr  */ +	ushort_t ai_ecc;	  /*  22  # of ecc bytes avail on rd/wr */ +	char	ai_fw[8];	  /*  23  Firmware revision		*/ +	char	ai_model[40];	  /*  27  Model #			*/ +	ushort_t ai_mult1;	  /*  47  Multiple command flags	*/ +	ushort_t ai_dwcap;	  /*  48  Doubleword capabilities	*/ +	ushort_t ai_cap;	  /*  49  Capabilities			*/ +	ushort_t ai_resv2;	  /*  50  Reserved			*/ +	ushort_t ai_piomode;	  /*  51  PIO timing mode		*/ +	ushort_t ai_dmamode;	  /*  52  DMA timing mode		*/ +	ushort_t ai_validinfo;	  /*  53  bit0: wds 54-58, bit1: 64-70	*/ +	ushort_t ai_curcyls;	  /*  54  # of current cylinders	*/ +	ushort_t ai_curheads;	  /*  55  # of current heads		*/ +	ushort_t ai_cursectrk;	  /*  56  # of current sectors/track	*/ +	ushort_t ai_cursccp[2];	  /*  57  current sectors capacity	*/ +	ushort_t ai_mult2;	  /*  59  multiple sectors info		*/ +	ushort_t ai_addrsec[2];	  /*  60  LBA only: no of addr secs	*/ +	ushort_t ai_sworddma;	  /*  62  single word dma modes		*/ +	ushort_t ai_dworddma;	  /*  63  double word dma modes		*/ +	ushort_t ai_advpiomode;	  /*  64  advanced PIO modes supported	*/ +	ushort_t ai_minmwdma;	  /*  65  min multi-word dma cycle info	*/ +	ushort_t ai_recmwdma;	  /*  66  rec multi-word dma cycle info	*/ +	ushort_t ai_minpio;	  /*  67  min PIO cycle info		*/ +	ushort_t ai_minpioflow;	  /*  68  min PIO cycle info w/flow ctl */ +	ushort_t ai_resv3[2];	  /* 69,70 reserved			*/ +	ushort_t ai_resv4[4];	  /* 71-74 reserved			*/ +	ushort_t ai_qdepth;	  /*  75  queue depth			*/ +	ushort_t ai_resv5[4];	  /* 76-79 reserved			*/ +	ushort_t ai_majorversion; /*  80  major versions supported	*/ +	ushort_t ai_minorversion; /*  81  minor version number supported */ +	ushort_t ai_cmdset82;	  /*  82  command set supported		*/ +	ushort_t ai_cmdset83;	  /*  83  more command sets supported	*/ +	ushort_t ai_cmdset84;	  /*  84  more command sets supported	*/ +	ushort_t ai_features85;	  /*  85 enabled features		*/ +	ushort_t ai_features86;	  /*  86 enabled features		*/ +	ushort_t ai_features87;	  /*  87 enabled features		*/ +	ushort_t ai_ultradma;	  /*  88 Ultra DMA mode			*/ +	ushort_t ai_erasetime;	  /*  89 security erase time		*/ +	ushort_t ai_erasetimex;	  /*  90 enhanced security erase time	*/ +	ushort_t ai_padding1[9];  /* pad through 99			*/ +	ushort_t ai_addrsecxt[4]; /* 100 extended max LBA sector	*/ +	ushort_t ai_padding2[22]; /* pad to 126				*/ +	ushort_t ai_lastlun;	  /* 126 last LUN, as per SFF-8070i	*/ +	ushort_t ai_resv6;	  /* 127 reserved			*/ +	ushort_t ai_securestatus; /* 128 security status		*/ +	ushort_t ai_vendor[31];	  /* 129-159 vendor specific		*/ +	ushort_t ai_padding3[16]; /* 160 pad to 176			*/ +	ushort_t ai_curmedser[30]; /* 176-205 current media serial number */ +	ushort_t ai_padding4[49]; /* 206 pad to 255			*/ +	ushort_t ai_integrity;	  /* 255 integrity word			*/ +}; + +/* Identify Drive: general config bits  - word 0 */ + +#define	ATA_ID_REM_DRV  	0x80 +#define	ATA_ID_COMPACT_FLASH 	0x848a +#define	ATA_ID_CF_TO_ATA 	0x040a + +/* Identify Drive: common capability bits - word 49 */ + +#define	ATAC_DMA_SUPPORT	0x0100 +#define	ATAC_LBA_SUPPORT	0x0200 +#define	ATAC_IORDY_DISABLE	0x0400 +#define	ATAC_IORDY_SUPPORT	0x0800 +#define	ATAC_RESERVED_IDPKT	0x1000	/* rsrvd for identify pkt dev */ +#define	ATAC_STANDBYTIMER	0x2000 +#define	ATAC_ATA_TYPE_MASK	0x8001 +#define	ATAC_ATA_TYPE		0x0000 +#define	ATAC_ATAPI_TYPE_MASK	0xc000 +#define	ATAC_ATAPI_TYPE		0x8000 + +/* Identify Driver ai_validinfo (word 53) */ + +#define	ATAC_VALIDINFO_83	0x0004	/* word 83 supported fields valid */ +#define	ATAC_VALIDINFO_70_64	0x0002	/* word 70:64 sup. fields valid */ + +/* Identify Drive: ai_dworddma (word 63) */ + +#define	ATAC_MDMA_SEL_MASK	0x0700	/* Multiword DMA selected */ +#define	ATAC_MDMA_2_SEL		0x0400	/* Multiword DMA mode 2 selected */ +#define	ATAC_MDMA_1_SEL		0x0200	/* Multiword DMA mode 1 selected */ +#define	ATAC_MDMA_0_SEL		0x0100	/* Multiword DMA mode 0 selected */ +#define	ATAC_MDMA_2_SUP		0x0004	/* Multiword DMA mode 2 supported */ +#define	ATAC_MDMA_1_SUP		0x0002	/* Multiword DMA mode 1 supported */ +#define	ATAC_MDMA_0_SUP		0x0001	/* Multiword DMA mode 0 supported */ + +/* Identify Drive: ai_advpiomode (word 64) */ + +#define	ATAC_ADVPIO_4_SUP	0x0002	/* PIO mode 4 supported */ +#define	ATAC_ADVPIO_3_SUP	0x0001	/* PIO mode 3 supported */ +#define	ATAC_ADVPIO_SERIAL	0x0003	/* Serial interface */ + +/* Identify Drive: ai_majorversion (word 80) */ + +#define	ATAC_MAJVER_6		0x0040	/* ATA/ATAPI-6 version supported */ +#define	ATAC_MAJVER_4		0x0010	/* ATA/ATAPI-4 version supported */ + +/* Identify Drive: command set supported/enabled bits - words 83 and 86 */ + +#define	ATACS_EXT48		0x0400	/* 48 bit address feature */ + +/* Identify Drive: ai_features85 (word 85) */ +#define	ATAC_FEATURES85_WCE	0x0020	/* write cache enabled */ + +/* per-drive data struct */ + +typedef struct ata_drv { +	ata_ctl_t		*ad_ctlp; 	/* pointer back to ctlr */ +	struct ata_id		ad_id;  	/* IDENTIFY DRIVE data */ + +	uint_t			ad_flags; +	uchar_t			ad_pciide_dma;	/* PCIIDE DMA supported */ +	uchar_t			ad_targ;	/* target */ +	uchar_t			ad_lun;		/* lun */ +	uchar_t			ad_drive_bits; + +	/* Used by atapi side only */ + +	uchar_t			ad_state;	/* state of ATAPI FSM */ +	uchar_t			ad_cdb_len;	/* Size of ATAPI CDBs */ + +	uchar_t			ad_bogus_drq; +	uchar_t			ad_nec_bad_status; + +	/* Used by disk side only */ + +	struct scsi_device	ad_device; +	struct scsi_inquiry	ad_inquiry; +	struct ctl_obj		ad_ctl_obj; +	uchar_t			ad_rd_cmd; +	uchar_t			ad_wr_cmd; +	ushort_t		ad_acyl; + +	/* +	 * Geometry note: The following three values are the geometry +	 * that the driver will use.  They may differ from the +	 * geometry reported by the controller and/or BIOS.  See note +	 * on ata_fix_large_disk_geometry in ata_disk.c for more +	 * details. +	 */ +	uint32_t		ad_drvrcyl;	/* number of cyls */ +	uint32_t		ad_drvrhd;	/* number of heads */ +	uint32_t		ad_drvrsec;	/* number of sectors */ +	ushort_t		ad_phhd;	/* number of phys heads */ +	ushort_t		ad_phsec;	/* number of phys sectors */ +	short			ad_block_factor; +	short			ad_bytes_per_block; + +	/* +	 * Support for 48-bit LBA (ATA-6) +	 */ +	uint64_t		ad_capacity;	/* Total sectors on disk */ +} ata_drv_t; + +typedef	struct	ata_tgt { +	ata_drv_t	*at_drvp; +	int		 at_arq; +	ulong_t		 at_total_sectors; +	ddi_dma_attr_t	 at_dma_attr; +} ata_tgt_t; + +/* values for ad_pciide_dma */ +#define	ATA_DMA_OFF	0x0 +#define	ATA_DMA_ON	0x1 + +/* + * (ata_pkt_t *) to (gcmd_t *) + */ +#define	APKT2GCMD(apktp)	(apktp->ap_gcmdp) + +/* + * (gcmd_t *) to (ata_pkt_t *) + */ +#define	GCMD2APKT(gcmdp)	((ata_pkt_t *)gcmdp->cmd_private) + +/* + * (gtgt_t *) to (ata_ctl_t *) + */ +#define	GTGTP2ATAP(gtgtp)	((ata_ctl_t *)GTGTP2HBA(gtgtp)) + +/* + * (gtgt_t *) to (ata_tgt_t *) + */ +#define	GTGTP2ATATGTP(gtgtp)	((ata_tgt_t *)GTGTP2TARGET(gtgtp)) + +/* + * (gtgt_t *) to (ata_drv_t *) + */ +#define	GTGTP2ATADRVP(gtgtp)	(GTGTP2ATATGTP(gtgtp)->at_drvp) + +/* + * (gcmd_t *) to (ata_tgt_t *) + */ +#define	GCMD2TGT(gcmdp)		GTGTP2ATATGTP(GCMDP2GTGTP(gcmdp)) + +/* + * (gcmd_t *) to (ata_drv_t *) + */ +#define	GCMD2DRV(gcmdp)		GTGTP2ATADRVP(GCMDP2GTGTP(gcmdp)) + +/* + * (ata_pkt_t *) to (ata_drv_t *) + */ +#define	APKT2DRV(apktp)		GCMD2DRV(APKT2GCMD(apktp)) + + +/* + * (struct hba_tran *) to (ata_ctl_t *) + */ +#define	TRAN2ATAP(tranp) 	((ata_ctl_t *)TRAN2HBA(tranp)) + + +/* + * ata common packet structure + */ +typedef struct ata_pkt { + +	gcmd_t		*ap_gcmdp;	/* GHD command struct */ + +	uint_t		ap_flags;	/* packet flags */ + +	caddr_t		ap_baddr;	/* I/O buffer base address */ +	size_t		ap_boffset;	/* current offset into I/O buffer */ +	size_t		ap_bcount;	/* # bytes in this request */ + +	caddr_t		ap_v_addr;	/* I/O buffer address */ +	size_t		ap_resid;	/* # bytes left to read/write */ + +	uchar_t		ap_pciide_dma;	/* This pkt uses DMA transfer mode */ +	prde_t		ap_sg_list[ATA_DMA_NSEGS]; /* Scatter/Gather list */ +	int		ap_sg_cnt;	/* number of entries in S/G list */ + +	/* command, starting sector number, sector count */ + +	daddr_t		ap_startsec;	/* starting sector number */ +	ushort_t	ap_count;	/* sector count */ +	uchar_t		ap_sec; +	uchar_t		ap_lwcyl; +	uchar_t		ap_hicyl; +	uchar_t		ap_hd; +	uchar_t		ap_cmd; + +	/* saved status and error registers for error case */ + +	uchar_t		ap_status; +	uchar_t		ap_error; + +	/* disk/atapi callback routines */ + +	int		(*ap_start)(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +				struct ata_pkt *ata_pktp); +	int		(*ap_intr)(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +				struct ata_pkt *ata_pktp); +	void		(*ap_complete)(ata_drv_t *ata_drvp, +				struct ata_pkt *ata_pktp, int do_callback); + +	/* Used by disk side */ + +	char		ap_cdb;		/* disk command */ +	char		ap_scb;		/* status after disk cmd */ +	uint_t		ap_bytes_per_block; /* blk mode factor */ +	uint_t		ap_wrt_count;	/* size of last write */ +	caddr_t		ap_v_addr_sav;	/* Original I/O buffer address. */ +	size_t		ap_resid_sav;	/* Original # of bytes */ +					/* left to read/write. */ + +	/* Used by atapi side */ + +	uchar_t		*ap_cdbp;	/* ptr to SCSI CDB */ +	uchar_t		ap_cdb_len;	/* length of SCSI CDB (in bytes) */ +	uchar_t		ap_cdb_pad;	/* padding after SCSI CDB (in shorts) */ + +	struct scsi_arq_status *ap_scbp; /* ptr to SCSI status block */ +	uchar_t		ap_statuslen;	/* length of SCSI status block */ +} ata_pkt_t; + + +/* + * defines for ap_flags + */ +#define	AP_ATAPI		0x0001	/* device is atapi */ +#define	AP_ERROR		0x0002	/* normal error */ +#define	AP_TRAN_ERROR		0x0004	/* transport error */ +#define	AP_READ			0x0008	/* read data */ +#define	AP_WRITE		0x0010	/* write data */ +#define	AP_ABORT		0x0020	/* packet aborted */ +#define	AP_TIMEOUT		0x0040	/* packet timed out */ +#define	AP_BUS_RESET		0x0080	/* bus reset */ +#define	AP_DEV_RESET		0x0100	/* device reset */ + +#define	AP_SENT_CMD		0x0200	/* atapi: cdb sent */ +#define	AP_XFERRED_DATA		0x0400	/* atapi: data transferred */ +#define	AP_GOT_STATUS		0x0800	/* atapi: status received */ +#define	AP_ARQ_ON_ERROR		0x1000	/* atapi: do ARQ on error */ +#define	AP_ARQ_OKAY		0x2000 +#define	AP_ARQ_ERROR		0x4000 + +#define	AP_FREE		   0x80000000u	/* packet is free! */ + + +/* + * public function prototypes + */ + +int	ata_check_drive_blacklist(struct ata_id *aidp, uint_t flags); +int	ata_command(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, int expect_drdy, +		int silent, uint_t busy_wait, uchar_t cmd, uchar_t feature, +		uchar_t count, uchar_t sector, uchar_t head, uchar_t cyl_low, +		uchar_t cyl_hi); +int	ata_get_status_clear_intr(ata_ctl_t *ata_ctlp, ata_pkt_t *ata_pktp); +int	ata_id_common(uchar_t id_cmd, int drdy_expected, +		ddi_acc_handle_t io_hdl1, caddr_t ioaddr1, +		ddi_acc_handle_t io_hdl2, caddr_t ioaddr2, +		struct ata_id *ata_idp); +int	ata_prop_create(dev_info_t *tgt_dip, ata_drv_t *ata_drvp, char *name); +int	ata_queue_cmd(int (*func)(ata_ctl_t *, ata_drv_t *, ata_pkt_t *), +		void *arg, ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +		gtgt_t *gtgtp); +int	ata_set_feature(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +		uchar_t feature, uchar_t value); +int	ata_wait(ddi_acc_handle_t io_hdl, caddr_t ioaddr, uchar_t onbits, +		uchar_t offbits, uint_t timeout_usec); +int	ata_wait3(ddi_acc_handle_t io_hdl, caddr_t ioaddr, uchar_t onbits1, +		uchar_t offbits1, uchar_t failure_onbits2, +		uchar_t failure_offbits2, uchar_t failure_onbits3, +		uchar_t failure_offbits3, uint_t timeout_usec); +int	ata_test_lba_support(struct ata_id *aidp); + +/* + * It's not clear to which of the two following delay mechanisms is + * better. + * + * We really need something better than drv_usecwait(). The + * granularity for drv_usecwait() currently is 10 usec. This means that + * the ATA_DELAY_400NSEC macro delays 25 timers longer than necessary. + * + * Doing 4 inb()'s from the alternate status register is guaranteed + * to take at least 400 nsecs (it may take as long as 4 usecs. + * The problem with inb() is that on an x86 platform it also causes + * a CPU synchronization, CPU write buffer flush, cache flush, and + * flushes posted writes in any PCI bridge devices between the CPU + * and the ATA controller. + */ +#if 1 +#define	ATA_DELAY_400NSEC(H, A)					\ +	((void) ddi_get8((H), (uint8_t *)(A) + AT_ALTSTATUS), 	\ +	(void) ddi_get8((H), (uint8_t *)(A) + AT_ALTSTATUS),	\ +	(void) ddi_get8((H), (uint8_t *)(A) + AT_ALTSTATUS),	\ +	(void) ddi_get8((H), (uint8_t *)(A) + AT_ALTSTATUS)) +#else +#define	ATA_DELAY_400NSEC(H, A)	((void) drv_usecwait(1)) +#endif + + +/* + * PCIIDE DMA (Bus Mastering) functions and data in ata_dma.c + */ +extern	ddi_dma_attr_t ata_pciide_dma_attr; +extern	int	ata_dma_disabled; + +int	ata_pciide_alloc(dev_info_t *dip, ata_ctl_t *ata_ctlp); +void	ata_pciide_free(ata_ctl_t *ata_ctlp); + +void	ata_pciide_dma_sg_func(gcmd_t *gcmdp, ddi_dma_cookie_t *dmackp, +		int single_segment, int seg_index); +void	ata_pciide_dma_setup(ata_ctl_t *ata_ctlp, prde_t *srcp, int sg_cnt); +void	ata_pciide_dma_start(ata_ctl_t *ata_ctlp, uchar_t direction); +void	ata_pciide_dma_stop(ata_ctl_t *ata_ctlp); +int	ata_pciide_status_clear(ata_ctl_t *ata_ctlp); +int	ata_pciide_status_dmacheck_clear(ata_ctl_t *ata_ctlp); +int	ata_pciide_status_pending(ata_ctl_t *ata_ctlp); + +#ifdef	__cplusplus +} +#endif + +#endif /* _ATA_COMMON_H */ diff --git a/usr/src/uts/intel/io/dktp/controller/ata/ata_debug.c b/usr/src/uts/intel/io/dktp/controller/ata/ata_debug.c new file mode 100644 index 0000000000..930d919c2d --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/ata_debug.c @@ -0,0 +1,120 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include <sys/types.h> +#include <sys/debug.h> + +#include "ata_common.h" +#include "ata_disk.h" +#include "atapi.h" +#include "pciide.h" + + +#ifdef ATA_DEBUG + +void +dump_ata_ctl(ata_ctl_t *P) +{ +	ghd_err("dip 0x%p flags 0x%x timing 0x%x\n", +		P->ac_dip, P->ac_flags, P->ac_timing_flags); +	ghd_err("drvp[0][0..7] 0x%p 0x%p 0x%p 0x%p 0x%p 0x%p 0x%p 0x%p\n", +		P->ac_drvp[0][0], P->ac_drvp[0][1], P->ac_drvp[0][2], +		P->ac_drvp[0][3], P->ac_drvp[0][4], P->ac_drvp[0][5], +		P->ac_drvp[0][6], P->ac_drvp[0][7]); +	ghd_err("drvp[1][0..7] 0x%p 0x%p 0x%p 0x%p 0x%p 0x%p 0x%p 0x%p\n", +		P->ac_drvp[1][0], P->ac_drvp[1][1], P->ac_drvp[1][2], +		P->ac_drvp[1][3], P->ac_drvp[1][4], P->ac_drvp[1][5], +		P->ac_drvp[1][6], P->ac_drvp[1][7]); +	ghd_err("max tran 0x%x &ccc_t 0x%p actv drvp 0x%p actv pktp 0x%p\n", +		P->ac_max_transfer, &P->ac_ccc, +		P->ac_active_drvp, P->ac_active_pktp); +	ghd_err("state %d hba tranp 0x%p\n", P->ac_state, P->ac_atapi_tran); +	ghd_err("iohdl1 0x%p 0x%p D 0x%p E 0x%p F 0x%p C 0x%p S 0x%p LC 0x%p " +		"HC 0x%p HD 0x%p ST 0x%p CMD 0x%p\n", +		P->ac_iohandle1, P->ac_ioaddr1, P->ac_data, P->ac_error, +		P->ac_feature, P->ac_count, P->ac_sect, P->ac_lcyl, +		P->ac_hcyl, P->ac_drvhd, P->ac_status, P->ac_cmd); +	ghd_err("iohdl2 0x%p 0x%p AST 0x%p DC 0x%p\n", +		P->ac_iohandle2, P->ac_ioaddr2, P->ac_altstatus, P->ac_devctl); +	ghd_err("bm hdl 0x%p 0x%p pciide %d BM %d sg_list 0x%p paddr 0x%llx " +		"acc hdl 0x%p sg hdl 0x%p\n", +		P->ac_bmhandle, P->ac_bmaddr, P->ac_pciide, P->ac_pciide_bm, +		P->ac_sg_list, (unsigned long long) P->ac_sg_paddr, +		P->ac_sg_acc_handle, P->ac_sg_handle); +	ghd_err("arq pktp 0x%p flt pktp 0x%p &cdb 0x%p\n", +		P->ac_arq_pktp, P->ac_fault_pktp, &P->ac_arq_cdb); +} + +void +dump_ata_drv(ata_drv_t *P) +{ + + +	ghd_err("ctlp 0x%p &ata_id 0x%p flags 0x%x pciide dma 0x%x\n", +		P->ad_ctlp, &P->ad_id, P->ad_flags, P->ad_pciide_dma); + +	ghd_err("targ %d lun %d driv 0x%x state %d cdb len %d " +		"bogus %d nec %d\n", P->ad_targ, P->ad_lun, P->ad_drive_bits, +		P->ad_state, P->ad_cdb_len, P->ad_bogus_drq, +		P->ad_nec_bad_status); + +	ghd_err("ata &scsi_dev 0x%p &scsi_inquiry 0x%p &ctl_obj 0x%p\n", +		&P->ad_device, &P->ad_inquiry, &P->ad_ctl_obj); + +	ghd_err("ata rd cmd 0x%x wr cmd 0x%x acyl 0x%x\n", +		P->ad_rd_cmd, P->ad_wr_cmd, P->ad_acyl); + +	ghd_err("ata bios cyl %d hd %d sec %d  phs hd %d sec %d\n", +		P->ad_drvrcyl, P->ad_drvrhd, P->ad_drvrsec, P->ad_phhd, +		P->ad_phsec); + +	ghd_err("block factor %d bpb %d\n", +		P->ad_block_factor, P->ad_bytes_per_block); +} + +void +dump_ata_pkt(ata_pkt_t *P) +{ +	ghd_err("gcmdp 0x%p flags 0x%x v_addr 0x%p dma %d\n", +		P->ap_gcmdp, P->ap_flags, P->ap_v_addr, P->ap_pciide_dma); +	ghd_err("&sg_list 0x%p sg cnt 0x%x resid 0x%lx bcnt 0x%lx\n", +		P->ap_sg_list, P->ap_sg_cnt, P->ap_resid, P->ap_bcount); +	ghd_err("sec 0x%x cnt 0x%x lc 0x%x hc 0x%x hd 0x%x cmd 0x%x\n", +		P->ap_sec, P->ap_count, P->ap_lwcyl, P->ap_hicyl, +		P->ap_hd, P->ap_cmd); +	ghd_err("status 0x%x error 0x%x\n", P->ap_status, P->ap_error); +	ghd_err("start 0x%p intr 0x%p complete 0x%p\n", +		P->ap_start, P->ap_intr, P->ap_complete); +	ghd_err("ata cdb 0x%x scb 0x%x bpb 0x%x wrt cnt 0x%x\n", +		P->ap_cdb, P->ap_scb, P->ap_bytes_per_block, P->ap_wrt_count); +	ghd_err("atapi cdbp 0x%p cdb len %d cdb pad %d\n", +		P->ap_cdbp, P->ap_cdb_len, P->ap_cdb_pad); +	ghd_err("scbp 0x%p statuslen 0x%x\n", P->ap_scbp, P->ap_statuslen); +} + +#endif diff --git a/usr/src/uts/intel/io/dktp/controller/ata/ata_debug.h b/usr/src/uts/intel/io/dktp/controller/ata/ata_debug.h new file mode 100644 index 0000000000..3f28a2d2e8 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/ata_debug.h @@ -0,0 +1,88 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 1997 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ATA_DEBUG_H +#define	_ATA_DEBUG_H + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#ifdef	__cplusplus +extern "C" { +#endif + +/* + * debugging options + */ + +/* + * Always print "real" error messages on non-debugging kernels + */ + +#ifdef	ATA_DEBUG +#define	ADBG_ERROR(fmt)		ADBG_FLAG_CHK(ADBG_FLAG_ERROR, fmt) +#else +#define	ADBG_ERROR(fmt)	ghd_err fmt +#endif + +/* + * ... everything else is conditional on the ATA_DEBUG preprocessor symbol + */ + +#define	ADBG_WARN(fmt)		ADBG_FLAG_CHK(ADBG_FLAG_WARN, fmt) +#define	ADBG_TRACE(fmt)		ADBG_FLAG_CHK(ADBG_FLAG_TRACE, fmt) +#define	ADBG_INIT(fmt)		ADBG_FLAG_CHK(ADBG_FLAG_INIT, fmt) +#define	ADBG_TRANSPORT(fmt)	ADBG_FLAG_CHK(ADBG_FLAG_TRANSPORT, fmt) +#define	ADBG_DMA(fmt)		ADBG_FLAG_CHK(ADBG_FLAG_DMA, fmt) +#define	ADBG_ARQ(fmt)		ADBG_FLAG_CHK(ADBG_FLAG_ARQ, fmt) + + + + +extern int ata_debug; + +#define	ADBG_FLAG_ERROR		0x0001 +#define	ADBG_FLAG_WARN		0x0002 +#define	ADBG_FLAG_TRACE		0x0004 +#define	ADBG_FLAG_INIT		0x0008 +#define	ADBG_FLAG_TRANSPORT	0x0010 +#define	ADBG_FLAG_DMA		0x0020 +#define	ADBG_FLAG_ARQ		0x0040 + + + +#ifdef	ATA_DEBUG +#define	ADBG_FLAG_CHK(flag, fmt) if (ata_debug & (flag)) GDBG_PRF(fmt) +#else +#define	ADBG_FLAG_CHK(flag, fmt) +#endif + + + +#ifdef	__cplusplus +} +#endif + +#endif /* _ATA_DEBUG_H */ diff --git a/usr/src/uts/intel/io/dktp/controller/ata/ata_disk.c b/usr/src/uts/intel/io/dktp/controller/ata/ata_disk.c new file mode 100644 index 0000000000..6694ecc191 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/ata_disk.c @@ -0,0 +1,2953 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include <sys/types.h> +#include <sys/dkio.h> +#include <sys/cdio.h> +#include <sys/file.h> + +#include "ata_common.h" +#include "ata_disk.h" + +/* + * this typedef really should be in dktp/cmpkt.h + */ +typedef struct cmpkt cmpkt_t; + + +/* + * DADA entry points + */ + +static int ata_disk_abort(opaque_t ctl_data, cmpkt_t *pktp); +static int ata_disk_reset(opaque_t ctl_data, int level); +static int ata_disk_ioctl(opaque_t ctl_data, int cmd, intptr_t a, int flag); +static cmpkt_t *ata_disk_pktalloc(opaque_t ctl_data, int (*callback)(caddr_t), +    caddr_t arg); +static void ata_disk_pktfree(opaque_t ctl_data, cmpkt_t *pktp); +static cmpkt_t	*ata_disk_memsetup(opaque_t ctl_data, cmpkt_t *pktp, +    struct buf *bp, int (*callback)(caddr_t), caddr_t arg); +static void ata_disk_memfree(opaque_t ctl_data, cmpkt_t *pktp); +static cmpkt_t	*ata_disk_iosetup(opaque_t ctl_data, cmpkt_t *pktp); +static int ata_disk_transport(opaque_t ctl_data, cmpkt_t *pktp); + +/* + * DADA packet callbacks + */ + +static void ata_disk_complete(ata_drv_t *ata_drvp, ata_pkt_t *ata_pktp, +    int do_callback); +static int ata_disk_intr(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_intr_dma(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_intr_pio_in(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_intr_pio_out(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_start(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_start_dma_in(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_start_dma_out(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_start_pio_in(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_start_pio_out(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); + +/* + * Local Function prototypes + */ + +static int ata_disk_eject(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static void ata_disk_fake_inquiry(ata_drv_t *ata_drvp); +static void ata_disk_get_resid(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_initialize_device_parameters(ata_ctl_t *ata_ctlp, +    ata_drv_t *ata_drvp); +static int ata_disk_lock(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_set_multiple(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp); +static void ata_disk_pio_xfer_data_in(ata_ctl_t *ata_ctlp, ata_pkt_t *ata_pktp); +static void ata_disk_pio_xfer_data_out(ata_ctl_t *ata_ctlp, +    ata_pkt_t *ata_pktp); +static void ata_disk_set_standby_timer(ata_ctl_t *ata_ctlp, +    ata_drv_t *ata_drvp); +static int ata_disk_recalibrate(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_standby(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_start_common(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_state(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_disk_unlock(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); +static int ata_get_capacity(ata_drv_t *ata_drvp, uint64_t *capacity); +static void ata_fix_large_disk_geometry(ata_drv_t *ata_drvp); +static uint64_t	ata_calculate_28bits_capacity(ata_drv_t *ata_drvp); +static uint64_t	ata_calculate_48bits_capacity(ata_drv_t *ata_drvp); +static int ata_copy_dk_ioc_string(intptr_t arg, char *source, int length, +    int flag); +static void ata_set_write_cache(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp); + + +/* + * Local static data + */ + +uint_t	ata_disk_init_dev_parm_wait = 4 * 1000000; +uint_t	ata_disk_set_mult_wait = 4 * 1000000; +int	ata_disk_do_standby_timer = TRUE; + +/* + * ata_write_cache == 1  force write cache on. + * ata_write_cache == 0  do not modify write cache.  firmware defaults kept. + * ata_write_cache == -1 force write cache off. + */ +int	ata_write_cache = 1; + + +static struct ctl_objops ata_disk_objops = { +	ata_disk_pktalloc, +	ata_disk_pktfree, +	ata_disk_memsetup, +	ata_disk_memfree, +	ata_disk_iosetup, +	ata_disk_transport, +	ata_disk_reset, +	ata_disk_abort, +	nulldev, +	nulldev, +	ata_disk_ioctl, +	0, 0 +}; + + + +/* + * + * initialize the ata_disk sub-system + * + */ + +/*ARGSUSED*/ +int +ata_disk_attach( +	ata_ctl_t *ata_ctlp) +{ +	ADBG_TRACE(("ata_disk_init entered\n")); +	return (TRUE); +} + + + +/* + * + * destroy the ata_disk sub-system + * + */ + +/*ARGSUSED*/ +void +ata_disk_detach( +	ata_ctl_t *ata_ctlp) +{ +	ADBG_TRACE(("ata_disk_destroy entered\n")); +} + + +/* + * Test whether the disk can support Logical Block Addressing + */ + +int +ata_test_lba_support(struct ata_id *aidp) +{ +#ifdef __old_version__ +	/* +	 * determine if the drive supports LBA mode +	 */ +	if (aidp->ai_cap & ATAC_LBA_SUPPORT) +		return (TRUE); +#else +	/* +	 * Determine if the drive supports LBA mode +	 * LBA mode is mandatory on ATA-3 (or newer) drives but is +	 * optional on ATA-2 (or older) drives. On ATA-2 drives +	 * the ai_majorversion word should be 0xffff or 0x0000 +	 * (version not reported). +	 */ +	if (aidp->ai_majorversion != 0xffff && +	    aidp->ai_majorversion >= (1 << 3)) { +		/* ATA-3 or better */ +		return (TRUE); +	} else if (aidp->ai_cap & ATAC_LBA_SUPPORT) { +		/* ATA-2 LBA capability bit set */ +		return (TRUE); +	} else { +		return (FALSE); +	} +#endif +} + +/* + * ATA-6 drives do not provide geometry information, so words + * ai_heads, ai_sectors and ai_fixcyls may not be valid + */ +static void +ata_fixup_ata6_geometry(struct ata_id *aidp) +{ +	/* check cylinders, heads, and sectors for valid values */ +	if (aidp->ai_heads != 0 && aidp->ai_heads != 0xffff && +	    aidp->ai_sectors != 0 && aidp->ai_sectors != 0xffff && +	    aidp->ai_fixcyls != 0) +		return;		/* assume valid geometry - do nothing */ + +	/* +	 * Pre-set standard geometry values - they are not necessarily +	 * optimal for a given capacity +	 */ +	aidp->ai_heads = 0x10; +	aidp->ai_sectors = 0x3f; +	aidp->ai_fixcyls = 1; +	/* +	 * The fixcyls value will get fixed up later in +	 * ata_fix_large_disk_geometry. +	 */ +} + +/* + * + * initialize the soft-structure for an ATA (non-PACKET) drive and + * then configure the drive with the correct modes and options. + * + */ + +int +ata_disk_init_drive( +	ata_drv_t *ata_drvp) +{ +	ata_ctl_t *ata_ctlp = ata_drvp->ad_ctlp; +	struct ata_id	*aidp = &ata_drvp->ad_id; +	struct ctl_obj	*ctlobjp; +	struct scsi_device	*devp; +	int 		len; +	int		val; +	int		mode; +	short		*chs; +	char 		buf[80]; + +	ADBG_TRACE(("ata_disk_init_drive entered\n")); + +	/* ATA disks don't support LUNs */ + +	if (ata_drvp->ad_lun != 0) +		return (FALSE); + +	/* +	 * set up drive structure +	 * ATA-6 drives do not provide geometry information, so words +	 * ai_heads, ai_sectors and ai_fixcyls may not be valid - they +	 * will be fixed later +	 */ + +	ata_drvp->ad_phhd = aidp->ai_heads; +	ata_drvp->ad_phsec = aidp->ai_sectors; +	ata_drvp->ad_drvrhd   = aidp->ai_heads; +	ata_drvp->ad_drvrsec  = aidp->ai_sectors; +	ata_drvp->ad_drvrcyl  = aidp->ai_fixcyls; +	ata_drvp->ad_acyl = 0; + +	if (ata_test_lba_support(&ata_drvp->ad_id)) +		ata_drvp->ad_drive_bits |= ATDH_LBA; + +	/* Get capacity and check for 48-bit mode */ +	mode = ata_get_capacity(ata_drvp, &ata_drvp->ad_capacity); +	if (mode == AD_EXT48) { +		ata_drvp->ad_flags |= AD_EXT48; +	} + +	/* straighten out the geometry */ +	(void) sprintf(buf, "SUNW-ata-%p-d%d-chs", (void *) ata_ctlp->ac_data, +		ata_drvp->ad_targ+1); +	if (ddi_getlongprop(DDI_DEV_T_ANY, ddi_root_node(), 0, +			buf, (caddr_t)&chs, &len) == DDI_PROP_SUCCESS) { +		/* +		 * if the number of sectors and heads in bios matches the +		 * physical geometry, then so should the number of cylinders +		 * this is to prevent the 1023 limit in the older bios's +		 * causing loss of space. +		 */ +		if (chs[1] == (ata_drvp->ad_drvrhd - 1) && +				chs[2] == ata_drvp->ad_drvrsec) +			/* Set chs[0] to zero-based number of cylinders. */ +			chs[0] = aidp->ai_fixcyls - 1; +		else if (!(ata_drvp->ad_drive_bits & ATDH_LBA)) { +			/* +			 * if the the sector/heads do not match that of the +			 * bios and the drive does not support LBA. We go ahead +			 * and advertise the bios geometry but use the physical +			 * geometry for sector translation. +			 */ +			cmn_err(CE_WARN, "!Disk 0x%p,%d: BIOS geometry " +				"different from physical, and no LBA support.", +				(void *)ata_ctlp->ac_data, ata_drvp->ad_targ); +		} + +		/* +		 * chs[0,1] are zero-based; make them one-based. +		 */ +		ata_drvp->ad_drvrcyl = chs[0] + 1; +		ata_drvp->ad_drvrhd = chs[1] + 1; +		ata_drvp->ad_drvrsec = chs[2]; +		kmem_free(chs, len); +	} else { +		/* +		 * Property not present; this means that boot.bin has +		 * determined that the drive supports Int13 LBA.  Note +		 * this, but just return a geometry with a large +		 * cylinder count; this will be the signal for dadk to +		 * fail DKIOCG_VIRTGEOM. +		 * ad_drvr* are already set; just recalculate ad_drvrcyl +		 * from capacity. +		 */ + +		ata_drvp->ad_flags |= AD_INT13LBA; +		if (ata_drvp->ad_capacity != 0) { +			ata_drvp->ad_drvrcyl = ata_drvp->ad_capacity / +				(ata_drvp->ad_drvrhd * ata_drvp->ad_drvrsec); +		} else { +			/* +			 * Something's wrong; return something sure to +			 * fail the "cyls < 1024" test.  This will +			 * never make it out of the DKIOCG_VIRTGEOM +			 * call, so its total bogosity won't matter. +			 */ +			ata_drvp->ad_drvrcyl = 1025; +			ata_drvp->ad_drvrhd = 1; +			ata_drvp->ad_drvrsec = 1; +		} +	} + +	/* fix geometry for disks > 31GB, if needed */ +	ata_fix_large_disk_geometry(ata_drvp); + +	/* +	 * set up the scsi_device and ctl_obj structures +	 */ +	devp = &ata_drvp->ad_device; +	ctlobjp = &ata_drvp->ad_ctl_obj; + +	devp->sd_inq = &ata_drvp->ad_inquiry; +	devp->sd_address.a_hba_tran = (scsi_hba_tran_t *)ctlobjp; +	devp->sd_address.a_target = (ushort_t)ata_drvp->ad_targ; +	devp->sd_address.a_lun = (uchar_t)ata_drvp->ad_lun; +	mutex_init(&devp->sd_mutex, NULL, MUTEX_DRIVER, NULL); +	ata_drvp->ad_flags |= AD_MUTEX_INIT; + +	/* +	 * DADA ops vectors and cookie +	 */ +	ctlobjp->c_ops  = (struct ctl_objops *)&ata_disk_objops; + +	/* +	 * this is filled in with gtgtp by ata_disk_bus_ctl(INITCHILD) +	 */ +	ctlobjp->c_data = NULL; + +	ctlobjp->c_ext  = &(ctlobjp->c_extblk); +	ctlobjp->c_extblk.c_ctldip = ata_ctlp->ac_dip; +	ctlobjp->c_extblk.c_targ   = ata_drvp->ad_targ; +	ctlobjp->c_extblk.c_blksz  = NBPSCTR; + +	/* +	 * Get highest block factor supported by the drive. +	 * Some drives report 0 if read/write multiple not supported, +	 * adjust their blocking factor to 1. +	 */ +	ata_drvp->ad_block_factor = aidp->ai_mult1 & 0xff; + +	/* +	 * If a block factor property exists, use the smaller of the +	 * property value and the highest value the drive can support. +	 */ +	(void) sprintf(buf, "drive%d_block_factor", ata_drvp->ad_targ); +	val = ddi_prop_get_int(DDI_DEV_T_ANY, ata_ctlp->ac_dip, 0, buf, +		ata_drvp->ad_block_factor); + +	ata_drvp->ad_block_factor = (short)min(val, ata_drvp->ad_block_factor); + +	if (ata_drvp->ad_block_factor == 0) +		ata_drvp->ad_block_factor = 1; + +	if (!ata_disk_setup_parms(ata_ctlp, ata_drvp)) +		return (FALSE); + +	ata_disk_fake_inquiry(ata_drvp); + +	return (TRUE); +} + +/* + * Test if a disk supports 48-bit (extended mode) addressing and + * get disk capacity. + * Return value: + *	AD_EXT48 if 48-bit mode is available, 0 otherwise, + *	capacity in sectors. + * There are several indicators for 48-bit addressing.  If any of + * them is missing, assume 28-bit (non-extended) addressing. + */ + +static int +ata_get_capacity(ata_drv_t *ata_drvp, uint64_t *capacity) +{ +	struct ata_id	*aidp = &ata_drvp->ad_id; +	uint64_t	cap28;	/* capacity in 28-bit mode */ +	uint64_t	cap48;	/* capacity in 48-bit mode */ + +	/* +	 * First compute capacity in 28-bit mode, using 28-bit capacity +	 * words in IDENTIFY DEVICE response words +	 */ +	cap28 = ata_calculate_28bits_capacity(ata_drvp); +	*capacity = cap28; + +	/* No 48-bit mode before ATA 6 */ +	if (!IS_ATA_VERSION_SUPPORTED(aidp, 6)) +		return (0); + +	/* Check that 48 bit addressing is supported & enabled */ +	/* words 83 and 86 */ +	if (!(aidp->ai_cmdset83 & ATACS_EXT48)) +		return (0); +	if (!(aidp->ai_features86 & ATACS_EXT48)) +		return (0); + +	/* +	 * Drive supports ATA-6.  Since ATA-6 drives may not provide +	 * geometry info, pre-set standard geometry values +	 */ +	ata_fixup_ata6_geometry(aidp); + +	/* Compute 48-bit capacity */ +	cap48 = ata_calculate_48bits_capacity(ata_drvp); + +	/* +	 * If capacity is smaller then the maximum capacity addressable +	 * in 28-bit mode, just use 28-bit capacity value. +	 * We will use 28-bit addressing read/write commands. +	 */ +	if (cap48 <= MAX_28BIT_CAPACITY) +		return (0); + +	/* +	 * Capacity is too big for 28-bits addressing. But, to make +	 * sure that the drive implements ATA-6 correctly, the +	 * final check: cap28 should be MAX for 28-bit addressing. +	 * If it's not, we shouldn't use 48-bit mode, so return +	 * the capacity reported in 28-bit capacity words. +	 */ +	if (cap28 != MAX_28BIT_CAPACITY) +		return (0);		/* not max, use 28-bit value */ + +	/* +	 * All is well so return 48-bit capacity indicator +	 */ +	ADBG_INIT(("ATA: using 48-bit mode for capacity %llx blocks\n", +		(unsigned long long)cap48)); + +	*capacity = cap48; +	return (AD_EXT48); +} + +/* + * With the advent of disks that hold more than 31 GB, we run into a + * limitation in the sizes of the fields that describe the geometry. + * The cylinders, heads, and sectors-per-track are each described by a + * 16-bit number -- both in the structure returned from IDENTIFY + * DEVICE and in the structure returned from the DIOCTL_GETGEOM or + * DIOCTL_GETPHYGEOM ioctl. + * + * The typical disk has 32 heads per cylinder and 63 sectors per + * track.  A 16 bit field can contain up to 65535.  So the largest + * disk that can be described in these fields is 65535 * 32 * 63 * 512 + * (bytes/sector), or about 31.5 GB.  The cylinder count gets truncated + * when stored in a narrow field, so a 40GB disk appears to have only + * 8 GB! + * + * The solution (for the time being at least) is to lie about the + * geometry.  If the number of cylinders is too large to fit in 16 + * bits, we will halve the cylinders and double the heads, repeating + * until we can fit the geometry into 3 shorts. + * FUTURE ENHANCEMENT: If this ever isn't enough, we could + * add another step to double sectors/track as well. + */ + +static void +ata_fix_large_disk_geometry( +	ata_drv_t *ata_drvp) +{ +	struct ata_id	*aidp = &ata_drvp->ad_id; + +	/* no hope for large disks if LBA not supported */ +	if (!(ata_drvp->ad_drive_bits & ATDH_LBA)) +		return; + +	/* +	 * Fix up the geometry to be returned by DIOCTL_GETGEOM. +	 * If number of cylinders > USHRT_MAX, double heads and +	 * halve cylinders until everything fits. +	 */ +	while (ata_drvp->ad_drvrcyl > USHRT_MAX) { +		int tempheads; + +		/* is there room in 16 bits to double the heads? */ +		tempheads = 2 * ata_drvp->ad_drvrhd; +		if (tempheads > USHRT_MAX) { +			/* +			 * No room to double the heads. +			 * I give up, there's no way to represent this. +			 * Limit disk size. +			 */ +			cmn_err(CE_WARN, +				"Disk is too large: " +					"Model %s, Serial# %s " +					"Approximating...\n", +				aidp->ai_model, aidp->ai_drvser); +			ata_drvp->ad_drvrcyl = USHRT_MAX; +			break; +		} + +		/* OK, so double the heads and halve the cylinders */ +		ata_drvp->ad_drvrcyl /= 2; +		ata_drvp->ad_drvrhd *= 2; +	} +} + +/* + * Calculate capacity using 28-bit capacity words from IDENTIFY DEVICE + * return words + */ +uint64_t +ata_calculate_28bits_capacity(ata_drv_t *ata_drvp) +{ +	/* +	 * Asked x3t13 for advice; this implements Hale Landis' +	 * response, minus the "use ATA_INIT_DEVPARMS". +	 * See "capacity.notes". +	 */ + +	/* some local shorthand/renaming to clarify the meaning */ + +	ushort_t curcyls_w54, curhds_w55, cursect_w56; +	uint32_t curcap_w57_58; + +	if ((ata_drvp->ad_drive_bits & ATDH_LBA) != 0) { +		return ((uint64_t)(ata_drvp->ad_id.ai_addrsec[0] + +		    ata_drvp->ad_id.ai_addrsec[1] * 0x10000)); +	} + +	/* +	 * If we're not LBA, then first try to validate "current" values. +	 */ + +	curcyls_w54 = ata_drvp->ad_id.ai_curcyls; +	curhds_w55 = ata_drvp->ad_id.ai_curheads; +	cursect_w56 = ata_drvp->ad_id.ai_cursectrk; +	curcap_w57_58 = ata_drvp->ad_id.ai_cursccp[0] + +	    ata_drvp->ad_id.ai_cursccp[1] * 0x10000; + +	if (((ata_drvp->ad_id.ai_validinfo & 1) == 1) && +	    (curhds_w55 >= 1) && (curhds_w55 <= 16) && +	    (cursect_w56 >= 1) && (cursect_w56 <= 63) && +	    (curcap_w57_58 == curcyls_w54 * curhds_w55 * cursect_w56)) { +		return ((uint64_t)curcap_w57_58); +	} + +	/* +	 * At this point, Hale recommends ATA_INIT_DEVPARMS. +	 * I don't want to do that, so simply use 1/3/6 as +	 * a final fallback, and continue to assume the BIOS +	 * has done whatever INIT_DEVPARMS are necessary. +	 */ + +	return ((uint64_t)(ata_drvp->ad_id.ai_fixcyls * +		ata_drvp->ad_id.ai_heads * ata_drvp->ad_id.ai_sectors)); +} + +/* + * Calculate capacity using 48-bits capacity words from IDENTIFY DEVICE + * return words + */ +uint64_t +ata_calculate_48bits_capacity(ata_drv_t *ata_drvp) +{ +	uint64_t cap48 = 0; +	int i; + +	for (i = 3;  i >= 0;  --i) { +		cap48 <<= 16; +		cap48 += ata_drvp->ad_id.ai_addrsecxt[i]; +	} +	return (cap48); +} + + +/* + * + * Setup the drives Read/Write Multiple Blocking factor and the + * current translation geometry. Necessary during attach and after + * Software Resets. + * + */ + +int +ata_disk_setup_parms( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp) +{ + +	/* +	 * program geometry info back to the drive +	 */ +	if (!ata_disk_initialize_device_parameters(ata_ctlp, ata_drvp)) { +		return (FALSE); +	} + +	/* +	 * Determine the blocking factor +	 */ +	if (ata_drvp->ad_block_factor > 1) { +		/* +		 * Program the block factor into the drive. If this +		 * fails, then go back to using a block size of 1. +		 */ +		if (!ata_disk_set_multiple(ata_ctlp, ata_drvp)) +			ata_drvp->ad_block_factor = 1; +	} + + +	if (ata_drvp->ad_block_factor > 1) { +		ata_drvp->ad_rd_cmd = ATC_RDMULT; +		ata_drvp->ad_wr_cmd = ATC_WRMULT; +	} else { +		ata_drvp->ad_rd_cmd = ATC_RDSEC; +		ata_drvp->ad_wr_cmd = ATC_WRSEC; +	} + +	ata_drvp->ad_bytes_per_block = ata_drvp->ad_block_factor << SCTRSHFT; + +	ADBG_INIT(("set block factor for drive %d to %d\n", +			ata_drvp->ad_targ, ata_drvp->ad_block_factor)); + +	if (ata_disk_do_standby_timer) +		ata_disk_set_standby_timer(ata_ctlp, ata_drvp); + +	ata_set_write_cache(ata_ctlp, ata_drvp); + +	return (TRUE); +} + + +/* + * Take the timeout value specified in the "standby" property + * and convert from seconds to the magic parm expected by the + * the drive. Then issue the IDLE command to set the drive's + * internal standby timer. + */ + +static void +ata_disk_set_standby_timer( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp) +{ +	uchar_t	parm; +	int	timeout = ata_ctlp->ac_standby_time; + +	/* +	 * take the timeout value, specificed in seconds, and +	 * encode it into the proper command parm +	 */ + +	/* +	 * don't change it if no property specified or if +	 * the specified value is out of range +	 */ +	if (timeout < 0 || timeout > (12 * 60 * 60)) +		return; + +	/* 1 to 1200 seconds (20 minutes) == N * 5 seconds */ +	if (timeout <= (240 * 5)) +		parm = (timeout + 4) / 5; + +	/* 20 to 21 minutes == 21 minutes */ +	else if (timeout <= (21 * 60)) +		parm = 252; + +	/* 21 minutes to 21 minutes 15 seconds == 21:15 */ +	else if (timeout <= ((21 * 60) + 15)) +		parm = 255; + +	/* 21:15 to 330 minutes == N * 30 minutes */ +	else if (timeout <= (11 * 30 * 60)) +		parm = 240 + ((timeout + (30 * 60) - 1)/ (30 * 60)); + +	/* > 330 minutes == 8 to 12 hours */ +	else +		parm = 253; + +	(void) ata_command(ata_ctlp, ata_drvp, TRUE, FALSE, 5 * 1000000, +		    ATC_IDLE, 0, parm, 0, 0, 0, 0); +} + + + +/* + * + * destroy an ata disk drive + * + */ + +void +ata_disk_uninit_drive( +	ata_drv_t *ata_drvp) +{ +	struct scsi_device *devp = &ata_drvp->ad_device; + +	ADBG_TRACE(("ata_disk_uninit_drive entered\n")); + +	if (ata_drvp->ad_flags & AD_MUTEX_INIT) +		mutex_destroy(&devp->sd_mutex); +} + + + + +/* + * + * DADA compliant bus_ctl entry point + * + */ + +/*ARGSUSED*/ +int +ata_disk_bus_ctl( +	dev_info_t	*d, +	dev_info_t	*r, +	ddi_ctl_enum_t	 o, +	void		*a, +	void		*v) +{ +	ADBG_TRACE(("ata_disk_bus_ctl entered\n")); + +	switch (o) { + +	case DDI_CTLOPS_REPORTDEV: +	{ +		int	targ; + +		targ = ddi_prop_get_int(DDI_DEV_T_ANY, r, DDI_PROP_DONTPASS, +					"target", 0); +		cmn_err(CE_CONT, "?%s%d at %s%d target %d lun %d\n", +			ddi_driver_name(r), ddi_get_instance(r), +			ddi_driver_name(d), ddi_get_instance(d), targ, 0); +		return (DDI_SUCCESS); +	} +	case DDI_CTLOPS_INITCHILD: +	{ +		dev_info_t	*cdip = (dev_info_t *)a; +		ata_drv_t	*ata_drvp; +		ata_ctl_t	*ata_ctlp; +		ata_tgt_t	*ata_tgtp; +		struct scsi_device *devp; +		struct ctl_obj	*ctlobjp; +		gtgt_t		*gtgtp; +		char		 name[MAXNAMELEN]; + +		/* +		 * save time by picking up ptr to drive struct left +		 * by ata_bus_ctl - isn't that convenient. +		 */ +		ata_drvp = ddi_get_driver_private(cdip); +		ata_ctlp = ata_drvp->ad_ctlp; + +		/* set up pointers to child dip */ + +		devp = &ata_drvp->ad_device; +		/* +		 * If sd_dev is set, it means that the target has already +		 * being initialized. The cdip is a duplicate node from +		 * reexpansion of driver.conf. Fail INITCHILD here. +		 */ +		if (devp->sd_dev != NULL) { +			return (DDI_FAILURE); +		} +		devp->sd_dev = cdip; + +		ctlobjp = &ata_drvp->ad_ctl_obj; +		ctlobjp->c_extblk.c_devdip = cdip; + +		/* +		 * Create the "ata" property for use by the target driver +		 */ +		if (!ata_prop_create(cdip, ata_drvp, "ata")) { +			return (DDI_FAILURE); +		} + +		gtgtp = ghd_target_init(d, cdip, &ata_ctlp->ac_ccc, +					sizeof (ata_tgt_t), ata_ctlp, +					ata_drvp->ad_targ, +					ata_drvp->ad_lun); + +		/* gt_tgt_private points to ata_tgt_t */ +		ata_tgtp = GTGTP2ATATGTP(gtgtp); +		ata_tgtp->at_drvp = ata_drvp; +		ata_tgtp->at_dma_attr = ata_pciide_dma_attr; +		ata_tgtp->at_dma_attr.dma_attr_maxxfer = +				ata_ctlp->ac_max_transfer << SCTRSHFT; + +		/* gtgtp is the opaque arg to all my entry points */ +		ctlobjp->c_data = gtgtp; + +		/* create device name */ + +		(void) sprintf(name, "%x,%x", ata_drvp->ad_targ, +			ata_drvp->ad_lun); +		ddi_set_name_addr(cdip, name); +		ddi_set_driver_private(cdip, devp); + +		return (DDI_SUCCESS); +	} + +	case DDI_CTLOPS_UNINITCHILD: +	{ +		dev_info_t *cdip = (dev_info_t *)a; +		struct 	scsi_device *devp; +		struct	ctl_obj *ctlobjp; +		gtgt_t	*gtgtp; + +		devp = ddi_get_driver_private(cdip); +		ctlobjp = (struct ctl_obj *)devp->sd_address.a_hba_tran; +		gtgtp = ctlobjp->c_data; + +		ghd_target_free(d, cdip, >GTP2ATAP(gtgtp)->ac_ccc, gtgtp); + +		ddi_set_driver_private(cdip, NULL); +		ddi_set_name_addr(cdip, NULL); +		return (DDI_SUCCESS); +	} + +	default: +		return (DDI_FAILURE); +	} +} + + +/* + * + * DADA abort entry point - not currently used by dadk + * + */ + +/* ARGSUSED */ +static int +ata_disk_abort(opaque_t ctl_data, cmpkt_t *pktp) +{ +	ADBG_TRACE(("ata_disk_abort entered\n")); + +	/* XXX - Note that this interface is currently not used by dadk */ + +	/* +	 *  GHD abort functions take a pointer to a scsi_address +	 *  and so they're unusable here.  The ata driver used to +	 *  return DDI_SUCCESS here without doing anything.  Its +	 *  seems that DDI_FAILURE is more appropriate. +	 */ + +	return (DDI_FAILURE); +} + + + +/* + * + * DADA reset entry point - not currently used by dadk + * (except in debug versions of driver) + * + */ + +/* ARGSUSED */ +static int +ata_disk_reset(opaque_t ctl_data, int level) +{ +	gtgt_t		*gtgtp = (gtgt_t *)ctl_data; +	ata_drv_t	*ata_drvp = GTGTP2ATADRVP(gtgtp); +	int		rc; + +	ADBG_TRACE(("ata_disk_reset entered\n")); + +	/* XXX - Note that this interface is currently not used by dadk */ + +	if (level == RESET_TARGET) { +		rc = ghd_tran_reset_target(&ata_drvp->ad_ctlp->ac_ccc, gtgtp, +			NULL); +	} else if (level == RESET_ALL) { +		rc = ghd_tran_reset_bus(&ata_drvp->ad_ctlp->ac_ccc, gtgtp, +					NULL); +	} + +	return (rc ? DDI_SUCCESS : DDI_FAILURE); +} + + + +/* + * + * DADA ioctl entry point + * + */ + +/* ARGSUSED */ +static int +ata_disk_ioctl(opaque_t ctl_data, int cmd, intptr_t arg, int flag) +{ +	gtgt_t		*gtgtp = (gtgt_t *)ctl_data; +	ata_ctl_t	*ata_ctlp = GTGTP2ATAP(gtgtp); +	ata_drv_t	*ata_drvp = GTGTP2ATADRVP(gtgtp); +	int		rc; +	struct tgdk_geom *tg; +	struct ata_id	*aidp = &ata_drvp->ad_id; + +	ADBG_TRACE(("ata_disk_ioctl entered, cmd = %d\n", cmd)); + +	switch (cmd) { + +	case DIOCTL_GETGEOM: +	case DIOCTL_GETPHYGEOM: +		tg = (struct tgdk_geom *)arg; +		tg->g_cyl = ata_drvp->ad_drvrcyl; +		tg->g_head = ata_drvp->ad_drvrhd; +		tg->g_sec = ata_drvp->ad_drvrsec; +		tg->g_acyl = ata_drvp->ad_acyl; +		tg->g_secsiz = 512; +		tg->g_cap = tg->g_cyl * tg->g_head * tg->g_sec; +		return (0); + +	case DCMD_UPDATE_GEOM: +/* ??? fix this to issue IDENTIFY DEVICE ??? */ +/* might not be necessary since I don't know of any ATA/IDE that */ +/* can change its geometry. On the other hand, ATAPI devices like the  */ +/* LS-120 or PD/CD can change their geometry when new media is inserted */ +		return (0); + +	/* copy the model number into the caller's buffer */ +	case DIOCTL_GETMODEL: +		rc = ata_copy_dk_ioc_string(arg, aidp->ai_model, +					sizeof (aidp->ai_model), flag); +		return (rc); + +	/* copy the model number into the caller's buffer */ +	case DIOCTL_GETSERIAL: +		rc = ata_copy_dk_ioc_string(arg, aidp->ai_drvser, +					sizeof (aidp->ai_drvser), +					flag); +		return (rc); + +	case DIOCTL_GETWCE: +		/* +		 * WCE is only supported in ATAPI-4 or higher, for +		 * lower rev devices, must assume write cache is +		 * enabled. +		 * NOTE: Since there is currently no Solaris mechanism +		 * to change the state of the Write Cache Enable feature, +		 * this code just checks the value of the WCE bit +		 * obtained at device init time.  If a mechanism +		 * is added to the driver to change WCE, this code +		 * must be updated appropriately. +		 */ +		*(int *)arg = (aidp->ai_majorversion == 0xffff) || +			((aidp->ai_majorversion & ATAC_MAJVER_4) == 0) || +			(aidp->ai_features85 & ATAC_FEATURES85_WCE) != 0; +		return (0); + +	case DCMD_GET_STATE: +		rc = ata_queue_cmd(ata_disk_state, NULL, ata_ctlp, ata_drvp, +			gtgtp); +		break; + +	case DCMD_LOCK: +	case DKIOCLOCK: +		rc = ata_queue_cmd(ata_disk_lock, NULL, ata_ctlp, ata_drvp, +			gtgtp); +		break; + +	case DCMD_UNLOCK: +	case DKIOCUNLOCK: +		rc = ata_queue_cmd(ata_disk_unlock, NULL, ata_ctlp, ata_drvp, +			gtgtp); +		break; + +	case DCMD_START_MOTOR: +	case CDROMSTART: +		rc = ata_queue_cmd(ata_disk_recalibrate, NULL, ata_ctlp, +			ata_drvp, gtgtp); +		break; + +	case DCMD_STOP_MOTOR: +	case CDROMSTOP: +		rc = ata_queue_cmd(ata_disk_standby, NULL, ata_ctlp, ata_drvp, +			gtgtp); +		break; + +	case DKIOCEJECT: +	case CDROMEJECT: +		rc = ata_queue_cmd(ata_disk_eject, NULL, ata_ctlp, ata_drvp, +			gtgtp); +		break; + +	default: +		ADBG_WARN(("ata_disk_ioctl: unsupported cmd 0x%x\n", cmd)); +		return (ENOTTY); +	} + +	if (rc) +		return (0); +	return (ENXIO); + +} + + +#ifdef ___not___used___ +/* + * Issue an ATA command to the drive using the packet already + * allocated by the target driver + */ + +int +ata_disk_do_ioctl( +	int	(*func)(ata_ctl_t *, ata_drv_t *, ata_pkt_t *), +	void	  *arg, +	ata_ctl_t *ata_ctlp, +	gtgt_t	  *gtgtp, +	cmpkt_t   *pktp) +{ +	gcmd_t	  *gcmdp = CPKT2GCMD(pktp); +	ata_pkt_t *ata_pktp = GCMD2APKT(gcmdp); +	int	   rc; + +	ata_pktp->ap_start = func; +	ata_pktp->ap_intr = NULL; +	ata_pktp->ap_complete = NULL; +	ata_pktp->ap_v_addr = (caddr_t)arg; + +	/* +	 * add it to the queue, when it gets to the front the +	 * ap_start function is called. +	 */ +	rc = ghd_transport(&ata_ctlp->ac_ccc, gcmdp, gcmdp->cmd_gtgtp, +		0, TRUE, NULL); + +	if (rc != TRAN_ACCEPT) { +		/* this should never, ever happen */ +		return (ENXIO); +	} + +	if (ata_pktp->ap_flags & AP_ERROR) +		return (ENXIO); +	return (0); +} +#endif + + + +/* + * + * DADA pktalloc entry point + * + */ + +/* ARGSUSED */ +static cmpkt_t * +ata_disk_pktalloc(opaque_t ctl_data, int (*callback)(caddr_t), caddr_t arg) +{ +	gtgt_t		*gtgtp = (gtgt_t *)ctl_data; +	ata_drv_t	*ata_drvp = GTGTP2ATADRVP(gtgtp); +	cmpkt_t		*pktp; +	ata_pkt_t	*ata_pktp; +	gcmd_t		*gcmdp; + +	ADBG_TRACE(("ata_disk_pktalloc entered\n")); + +	/* +	 * Allocate and  init the GHD gcmd_t structure and the +	 * DADA cmpkt and the ata_pkt +	 */ +	if ((gcmdp = ghd_gcmd_alloc(gtgtp, +				    (sizeof (cmpkt_t) + sizeof (ata_pkt_t)), +				    (callback == DDI_DMA_SLEEP))) == NULL) { +		return ((cmpkt_t *)NULL); +	} +	ASSERT(gcmdp != NULL); + +	ata_pktp = GCMD2APKT(gcmdp); +	ASSERT(ata_pktp != NULL); + +	pktp = (cmpkt_t *)(ata_pktp + 1); + +	pktp->cp_ctl_private = (void *)gcmdp; +	ata_pktp->ap_gcmdp = gcmdp; +	gcmdp->cmd_pktp = (void *)pktp; + +	/* +	 * At this point the structures are linked like this: +	 * +	 *	(struct cmpkt) <--> (struct gcmd) <--> (struct ata_pkt) +	 */ + +	/* callback functions */ + +	ata_pktp->ap_start = ata_disk_start; +	ata_pktp->ap_intr = ata_disk_intr; +	ata_pktp->ap_complete = ata_disk_complete; + +	/* other ata_pkt setup */ + +	ata_pktp->ap_bytes_per_block = ata_drvp->ad_bytes_per_block; + +	/* cmpkt setup */ + +	pktp->cp_cdblen = 1; +	pktp->cp_cdbp   = (opaque_t)&ata_pktp->ap_cdb; +	pktp->cp_scbp   = (opaque_t)&ata_pktp->ap_scb; +	pktp->cp_scblen = 1; + +	return (pktp); +} + + + +/* + * + * DADA pktfree entry point + * + */ + +/* ARGSUSED */ +static void +ata_disk_pktfree(opaque_t ctl_data, cmpkt_t *pktp) +{ +	ata_pkt_t *ata_pktp = CPKT2APKT(pktp); + +	ADBG_TRACE(("ata_disk_pktfree entered\n")); + +	/* check not free already */ + +	ASSERT(!(ata_pktp->ap_flags & AP_FREE)); +	ata_pktp->ap_flags = AP_FREE; + +	ghd_gcmd_free(CPKT2GCMD(pktp)); +} + + +/* + * + * DADA memsetup entry point + * + */ + +/* ARGSUSED */ +static cmpkt_t * +ata_disk_memsetup( +	opaque_t ctl_data, +	cmpkt_t *pktp, +	struct buf *bp, +	int (*callback)(caddr_t), +	caddr_t arg) +{ +	gtgt_t		*gtgtp = (gtgt_t *)ctl_data; +	ata_pkt_t	*ata_pktp = CPKT2APKT(pktp); +	gcmd_t		*gcmdp = APKT2GCMD(ata_pktp); +	int		flags; + +	ADBG_TRACE(("ata_disk_memsetup entered\n")); + +	ata_pktp->ap_sg_cnt = 0; + +	if (bp->b_bcount == 0) { +		ata_pktp->ap_v_addr = NULL; +		return (pktp); +	} + +	if (GTGTP2ATADRVP(gtgtp)->ad_pciide_dma != ATA_DMA_ON) +		goto skip_dma_setup; + +	if (ata_dma_disabled) +		goto skip_dma_setup; + +	/* +	 * The PCI-IDE DMA engine is brain-damaged and can't +	 * DMA non-aligned buffers. +	 */ +	if (!(bp->b_flags & B_PAGEIO) && +	    ((uintptr_t)bp->b_un.b_addr) & PCIIDE_PRDE_ADDR_MASK) { +		goto skip_dma_setup; +	} + +	/* +	 * It also insists that the byte count must be even. +	 */ +	if (bp->b_bcount & 1) +		goto skip_dma_setup; + +	/* check direction for data transfer */ +	if (bp->b_flags & B_READ) { +		flags = DDI_DMA_READ | DDI_DMA_PARTIAL; +	} else { +		flags = DDI_DMA_WRITE | DDI_DMA_PARTIAL; +	} + +	/* +	 * Bind the DMA handle to the buf +	 */ +	if (ghd_dma_buf_bind_attr(>GTP2ATAP(gtgtp)->ac_ccc, gcmdp, bp, flags, +			callback, arg, >GTP2ATATGTP(gtgtp)->at_dma_attr)) { +		ata_pktp->ap_v_addr = 0; +		return (pktp); +	} + +skip_dma_setup: +	bp_mapin(bp); +	ata_pktp->ap_v_addr = bp->b_un.b_addr; +	return (pktp); +} + + + +/* + * + * DADA memfree entry point + * + */ + +/* + * 1157317 sez that drivers shouldn't call bp_mapout(), as either + * biodone() or biowait() will end up doing it, but after they + * call bp->b_iodone(), which is a necessary sequence for + * Online Disk Suite.  However, the DDI group wants to rethink + * bp_mapin()/bp_mapout() and how they should behave in the + * presence of layered drivers, etc.  For the moment, fix + * the OLDS problem by removing the bp_mapout() call. + */ + +#define	BUG_1157317 + +/* ARGSUSED */ +static void +ata_disk_memfree(opaque_t ctl_data, cmpkt_t *pktp) +{ +	gcmd_t	*gcmdp = CPKT2GCMD(pktp); + +	ADBG_TRACE(("ata_disk_memfree entered\n")); + +	if (gcmdp->cmd_dma_handle) +		ghd_dmafree_attr(gcmdp); +#if !defined(BUG_1157317) +	else +		bp_mapout(pktp->cp_bp); +#endif +} + + + +/* + * + * DADA iosetup entry point + * + */ + +static cmpkt_t * +ata_disk_iosetup(opaque_t ctl_data, cmpkt_t *pktp) +{ +	gtgt_t		*gtgtp = (gtgt_t *)ctl_data; +	ata_drv_t	*ata_drvp = GTGTP2ATADRVP(gtgtp); +	ata_pkt_t	*ata_pktp = CPKT2APKT(pktp); +	gcmd_t		*gcmdp = APKT2GCMD(ata_pktp); +	uint_t		sec_count; +	daddr_t		start_sec; +	uint_t		byte_count; + +	ADBG_TRACE(("ata_disk_iosetup entered\n")); + +	/* +	 * Check for DCMD_FLUSH_CACHE (which does no I/O) and +	 * just do basic setup. +	 */ +	if (pktp->cp_passthru == NULL && +	    ata_pktp->ap_cdb == DCMD_FLUSH_CACHE) { +		ata_pktp->ap_cmd = ATC_FLUSH_CACHE; +		ata_pktp->ap_flags = 0; +		ata_pktp->ap_count = 0; +		ata_pktp->ap_startsec = 0; +		ata_pktp->ap_sg_cnt = 0; +		ata_pktp->ap_pciide_dma = FALSE; +		return (pktp); +	} + +	/* check for error retry */ +	if (ata_pktp->ap_flags & AP_ERROR) { +		/* +		 * this is a temporary work-around for dadk calling +		 * iosetup for retry. The correct +		 * solution is changing dadk to not to call iosetup +		 * for a retry. +		 * We do not apply the work-around for pio mode since +		 * that does not involve moving dma windows and reducing the +		 * sector count would work for pio mode on a retry +		 * for now. +		 */ +		if (gcmdp->cmd_dma_handle != NULL) { +			ata_pktp->ap_flags = 0; +			return (NULL); +		} + +		ata_pktp->ap_bytes_per_block = NBPSCTR; +		sec_count = 1; + +		/* +		 * Since we are retrying the last read or write operation, +		 * restore the old values of the ap_v_addr and ap_resid. +		 * This assumes CTL_IOSETUP is called again on retry; if not, +		 * this needs to be done in CTL_TRANSPORT. +		 */ +		if (ata_pktp->ap_flags & (AP_READ | AP_WRITE)) { +			ata_pktp->ap_v_addr = ata_pktp->ap_v_addr_sav; +			ata_pktp->ap_resid = ata_pktp->ap_resid_sav; +		} +	} else { +		/* +		 * Limit request to ac_max_transfer sectors. +		 * The value is specified by the user in the +		 * max_transfer property. It must be in the range 1 to 256. +		 * When max_transfer is 0x100 it is bigger than 8 bits. +		 * The spec says 0 represents 256 so it should be OK. +		 */ +		sec_count = min((pktp->cp_bytexfer >> SCTRSHFT), +				ata_drvp->ad_ctlp->ac_max_transfer); +		/* +		 * Save the current values of ap_v_addr and ap_resid +		 * in case a retry operation happens. During a retry +		 * operation we need to restore these values. +		 */ +		ata_pktp->ap_v_addr_sav = ata_pktp->ap_v_addr; +		ata_pktp->ap_resid_sav = ata_pktp->ap_resid; +	} + +	/* reset flags */ +	ata_pktp->ap_flags = 0; + +#ifdef	DADKIO_RWCMD_READ +	start_sec = pktp->cp_passthru ? RWCMDP(pktp)->blkaddr : pktp->cp_srtsec; +#else +	start_sec = pktp->cp_srtsec; +#endif + +	/* +	 * Setup the PCIDE Bus Master Scatter/Gather list +	 */ +	ata_pktp->ap_sg_cnt = 0; +	ata_pktp->ap_pciide_dma = FALSE; +	if (gcmdp->cmd_dma_handle != NULL && sec_count != 0) { +		byte_count = sec_count << SCTRSHFT; +		if ((ghd_dmaget_attr(>GTP2ATAP(gtgtp)->ac_ccc, gcmdp, +			byte_count, ATA_DMA_NSEGS, &byte_count) == FALSE) || +			(byte_count == 0)) { +			ADBG_ERROR(("ata_disk_iosetup: byte count zero\n")); +			return (NULL); +		} +		sec_count = byte_count >> SCTRSHFT; +	} + +	/* +	 * In the non-48-bit mode addressing (CHS and LBA28) the sector +	 * count is a 8-bit value and the sector count 0 represents 256 +	 * sectors. +	 * In the extended addressing (LBA48) the sector count is a 16-bit +	 * value, so max_transfer 0x100 cannot be truncated to 8-bits +	 * because this would represent a zero sector count. +	 */ +	ata_pktp->ap_count = sec_count; +	if (!(ata_drvp->ad_flags & AD_EXT48)) { +		ata_pktp->ap_count &= 0xff; +	} +	ata_pktp->ap_startsec = start_sec; + +#ifdef	DADKIO_RWCMD_READ +	if (pktp->cp_passthru) { +		switch (RWCMDP(pktp)->cmd) { +		case DADKIO_RWCMD_READ: +			if (ata_pktp->ap_sg_cnt) { +				ata_pktp->ap_cmd = ATC_READ_DMA; +				ata_pktp->ap_pciide_dma = TRUE; +				ata_pktp->ap_start = ata_disk_start_dma_in; +				ata_pktp->ap_intr = ata_disk_intr_dma; +			} else { +				ata_pktp->ap_cmd = ATC_RDSEC; +				ata_pktp->ap_start = ata_disk_start_pio_in; +				ata_pktp->ap_intr = ata_disk_intr_pio_in; +			} +			ata_pktp->ap_flags |= AP_READ; +			break; +		case DADKIO_RWCMD_WRITE: +			if (ata_pktp->ap_sg_cnt) { +				ata_pktp->ap_cmd = ATC_WRITE_DMA; +				ata_pktp->ap_pciide_dma = TRUE; +				ata_pktp->ap_start = ata_disk_start_dma_out; +				ata_pktp->ap_intr = ata_disk_intr_dma; +			} else { +				ata_pktp->ap_cmd = ATC_WRSEC; +				ata_pktp->ap_start = ata_disk_start_pio_out; +				ata_pktp->ap_intr = ata_disk_intr_pio_out; +			} +			ata_pktp->ap_flags |= AP_WRITE; +			break; +		} + +		byte_count = RWCMDP(pktp)->buflen; +		pktp->cp_bytexfer = byte_count; +		pktp->cp_resid = byte_count; +		ata_pktp->ap_resid = byte_count; + +		/* +		 * since we're not using READ/WRITE MULTIPLE, we +		 * should set bytes_per_block to one sector +		 * XXX- why wasn't this in the old driver?? +		 */ +		ata_pktp->ap_bytes_per_block = NBPSCTR; +	} else +#endif +	{ +		byte_count = sec_count << SCTRSHFT; +		pktp->cp_bytexfer = byte_count; +		pktp->cp_resid = byte_count; +		ata_pktp->ap_resid = byte_count; + +		/* setup the task file registers */ + +		switch (ata_pktp->ap_cdb) { +		case DCMD_READ: +			if (ata_pktp->ap_sg_cnt) { +				ata_pktp->ap_cmd = ATC_READ_DMA; +				ata_pktp->ap_pciide_dma = TRUE; +				ata_pktp->ap_start = ata_disk_start_dma_in; +				ata_pktp->ap_intr = ata_disk_intr_dma; +			} else { +				ata_pktp->ap_cmd = ata_drvp->ad_rd_cmd; +				ata_pktp->ap_start = ata_disk_start_pio_in; +				ata_pktp->ap_intr = ata_disk_intr_pio_in; +			} +			ata_pktp->ap_flags |= AP_READ; +			break; + +		case DCMD_WRITE: +			if (ata_pktp->ap_sg_cnt) { +				ata_pktp->ap_cmd = ATC_WRITE_DMA; +				ata_pktp->ap_pciide_dma = TRUE; +				ata_pktp->ap_start = ata_disk_start_dma_out; +				ata_pktp->ap_intr = ata_disk_intr_dma; +			} else { +				ata_pktp->ap_cmd = ata_drvp->ad_wr_cmd; +				ata_pktp->ap_start = ata_disk_start_pio_out; +				ata_pktp->ap_intr = ata_disk_intr_pio_out; +			} +			ata_pktp->ap_flags |= AP_WRITE; +			break; + +		default: +			ADBG_WARN(("ata_disk_iosetup: unknown command 0x%x\n", +					ata_pktp->ap_cdb)); +			pktp = NULL; +			break; +		} +	} + +	/* If 48-bit mode is used, convert command to 48-bit mode cmd */ +	if (pktp != NULL && ata_drvp->ad_flags & AD_EXT48) { +		switch (ata_pktp->ap_cmd) { +		case ATC_RDSEC: +			ata_pktp->ap_cmd = ATC_RDSEC_EXT; +			break; +		case ATC_WRSEC: +			ata_pktp->ap_cmd = ATC_WRSEC_EXT; +			break; +		case ATC_RDMULT: +			ata_pktp->ap_cmd = ATC_RDMULT_EXT; +			break; +		case ATC_WRMULT: +			ata_pktp->ap_cmd = ATC_WRMULT_EXT; +			break; +		case ATC_READ_DMA: +			ata_pktp->ap_cmd = ATC_RDDMA_EXT; +			break; +		case ATC_WRITE_DMA: +			ata_pktp->ap_cmd = ATC_WRDMA_EXT; +			break; +		} +	} + +	return (pktp); +} + + + +/* + * + * DADA transport entry point + * + */ + +static int +ata_disk_transport(opaque_t ctl_data, cmpkt_t *pktp) +{ +	gtgt_t		*gtgtp = (gtgt_t *)ctl_data; +	ata_drv_t	*ata_drvp = GTGTP2ATADRVP(gtgtp); +	ata_ctl_t	*ata_ctlp = ata_drvp->ad_ctlp; +	ata_pkt_t	*ata_pktp = CPKT2APKT(pktp); +	int		rc; +	int		polled = FALSE; + +	ADBG_TRACE(("ata_disk_transport entered\n")); + +	/* check for polling pkt */ + +	if (pktp->cp_flags & CPF_NOINTR) { +		polled = TRUE; +	} + +	/* call ghd transport routine */ + +	rc = ghd_transport(&ata_ctlp->ac_ccc, APKT2GCMD(ata_pktp), +		gtgtp, pktp->cp_time, polled, NULL); + +	/* see if pkt was not accepted */ + +	if (rc == TRAN_BUSY) +		return (CTL_SEND_BUSY); + +	if (rc == TRAN_ACCEPT) +		return (CTL_SEND_SUCCESS); + +	return (CTL_SEND_FAILURE); +} + + +/* + * + * routines to load the cylinder/head/sector/count + * task file registers. + * + */ +static void +ata_disk_load_regs_lba28(ata_pkt_t *ata_pktp, ata_drv_t *ata_drvp) +{ +	ata_ctl_t	*ata_ctlp = ata_drvp->ad_ctlp; +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	uint_t		lba;	/* LBA of first sector */ + +	lba = ata_pktp->ap_startsec; + +	ddi_put8(io_hdl1, ata_ctlp->ac_count, +		ata_pktp->ap_count); +	ddi_put8(io_hdl1, ata_ctlp->ac_sect, lba); +	lba >>= 8; +	ddi_put8(io_hdl1, ata_ctlp->ac_lcyl, lba); +	lba >>= 8; +	ddi_put8(io_hdl1, ata_ctlp->ac_hcyl, lba); +	lba >>= 8; +	/* +	 * dev/head register can use only 4 bits +	 * must also include drive selector. +	 */ +	lba = (lba & 0xf) | ata_drvp->ad_drive_bits; +	ddi_put8(io_hdl1,  ata_ctlp->ac_drvhd, lba); +} + +/* + * In 48-bit extended mode, the sector count is 16 bits wide, and the + * LBA is 48 bits wide, as follows: + * register	most recent	previous + * name		value		value + * --------	----------	--------- + * sector cnt	count(7:0)	count(15:8) + * sector num	lba(7:0)	lba(31:24) + * cyl low	lba(15:8)	lba(39:32) + * cyl hi	lba(23:16)	lba(47:40) + * device/head	111D0000	N/A + *               ^ ^ + *               | | + *               | +-- drive number + *               | + *               +-- indicates LBA + *	The other two 1 bits are historical and are not used in 48bit + *	extended mode. + */ +/* + * WARNING: + * dada framework passes starting sector as daddr_t type, thus + * limiting reachable disk space in 32-bit x86 architecture to 1 terabyte. + * Therefore high 16 bits of the 48-bits address can be and + * are currently ignored. + */ +static void +ata_disk_load_regs_lba48(ata_pkt_t *ata_pktp, ata_drv_t *ata_drvp) +{ +	ata_ctl_t	*ata_ctlp = ata_drvp->ad_ctlp; +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	uint16_t	seccnt;		/* 16-bit sector count */ +	uint_t		lbalow;		/* low-order 24 bits of LBA */ +	uint_t		lbahi;		/* high-order 24 bits of LBA */ + +	seccnt = ata_pktp->ap_count; +	/* high-order 8 bits of lbalow never get used */ +	lbalow = ata_pktp->ap_startsec; +	lbahi = ata_pktp->ap_startsec >> 24; + +	ddi_put8(io_hdl1, ata_ctlp->ac_count, seccnt >> 8); +	ddi_put8(io_hdl1, ata_ctlp->ac_count, seccnt); +	/* Send the high-order half first */ +	ddi_put8(io_hdl1, ata_ctlp->ac_sect, lbahi); +	lbahi >>= 8; +	ddi_put8(io_hdl1, ata_ctlp->ac_lcyl, lbahi); +	lbahi >>= 8; +	ddi_put8(io_hdl1, ata_ctlp->ac_hcyl, lbahi); +	/* Send the low-order half */ +	ddi_put8(io_hdl1, ata_ctlp->ac_sect, lbalow); +	lbalow >>= 8; +	ddi_put8(io_hdl1, ata_ctlp->ac_lcyl, lbalow); +	lbalow >>= 8; +	ddi_put8(io_hdl1, ata_ctlp->ac_hcyl, lbalow); +	ddi_put8(io_hdl1,  ata_ctlp->ac_drvhd, +				ata_drvp->ad_drive_bits); +} + +static void +ata_disk_load_regs_chs(ata_pkt_t *ata_pktp, ata_drv_t *ata_drvp) +{ +	ata_ctl_t		*ata_ctlp = ata_drvp->ad_ctlp; +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	uint_t			resid; +	uint_t			cyl; +	uchar_t			head; +	uchar_t			drvheads; +	uchar_t			drvsectors; + +	drvheads = ata_drvp->ad_phhd; +	drvsectors = ata_drvp->ad_phsec; + +	resid = ata_pktp->ap_startsec / drvsectors; +	head = (resid % drvheads) & 0xf; +	cyl = resid / drvheads; +			/* automatically truncate to char */ +	ddi_put8(io_hdl1, ata_ctlp->ac_sect, +			(ata_pktp->ap_startsec % drvsectors) + 1); +	ddi_put8(io_hdl1, ata_ctlp->ac_count, ata_pktp->ap_count); +	ddi_put8(io_hdl1, ata_ctlp->ac_hcyl, (cyl >> 8)); +		/* lcyl gets truncated to 8 bits */ +	ddi_put8(io_hdl1, ata_ctlp->ac_lcyl, cyl); +	ddi_put8(io_hdl1,  ata_ctlp->ac_drvhd, +			ata_drvp->ad_drive_bits | head); +} + + +/* + * + * packet start callback routines + * + */ + +/* ARGSUSED */ +static int +ata_disk_start_common( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; + +	ADBG_TRACE(("ata_disk_start_common entered\n")); + +	ADBG_TRANSPORT(("ata_disk_start:\tpkt = 0x%p, pkt flags = 0x%x\n", +		ata_pktp, ata_pktp->ap_flags)); +	ADBG_TRANSPORT(("\tcommand=0x%x, sect=0x%lx\n", +		ata_pktp->ap_cmd, ata_pktp->ap_startsec)); +	ADBG_TRANSPORT(("\tcount=0x%x, drvhd = 0x%x\n", +		ata_pktp->ap_count, ata_drvp->ad_drive_bits)); + +	/* +	 * If AC_BSY_WAIT is set, wait for controller to not be busy, +	 * before issuing a command.  If AC_BSY_WAIT is not set, +	 * skip the wait.  This is important for laptops that do +	 * suspend/resume but do not correctly wait for the busy bit to +	 * drop after a resume. +	 * +	 * NOTE: this test for ATS_BSY is also needed if/when we +	 * implement the overlapped/queued command protocols. Currently, +	 * the overlap/queued feature is not supported so the test is +	 * conditional. +	 */ +	if (ata_ctlp->ac_timing_flags & AC_BSY_WAIT) { +		if (!ata_wait(io_hdl2,  ata_ctlp->ac_ioaddr2, +				0, ATS_BSY, 5000000)) { +			ADBG_ERROR(("ata_disk_start: BUSY\n")); +			return (FALSE); +		} +	} + +	ddi_put8(io_hdl1, ata_ctlp->ac_drvhd, ata_drvp->ad_drive_bits); +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	/* +	 * make certain the drive selected +	 */ +	if (!ata_wait(io_hdl2,  ata_ctlp->ac_ioaddr2, +			ATS_DRDY, ATS_BSY, 5 * 1000000)) { +		ADBG_ERROR(("ata_disk_start: select failed\n")); +		return (FALSE); +	} + +	/* +	 * We use different methods for loading the task file +	 * registers, depending on whether the disk +	 * uses LBA or CHS addressing and whether 48-bit +	 * extended addressing is to be used. +	 */ +	if (!(ata_drvp->ad_drive_bits & ATDH_LBA)) +		ata_disk_load_regs_chs(ata_pktp, ata_drvp); +	else if (ata_drvp->ad_flags & AD_EXT48) +		ata_disk_load_regs_lba48(ata_pktp, ata_drvp); +	else +		ata_disk_load_regs_lba28(ata_pktp, ata_drvp); +	ddi_put8(io_hdl1, ata_ctlp->ac_feature, 0); + +	/* +	 * Always make certain interrupts are enabled. It's been reported +	 * (but not confirmed) that some notebook computers don't +	 * clear the interrupt disable bit after being resumed. The +	 * easiest way to fix this is to always clear the disable bit +	 * before every command. +	 */ +	ddi_put8(io_hdl2, ata_ctlp->ac_devctl, ATDC_D3); +	return (TRUE); +} + + +/* + * + * Start a non-data ATA command (not DMA and not PIO): + * + */ + +static int +ata_disk_start( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	ata_pkt_t *ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	int		 rc; + +	rc = ata_disk_start_common(ata_ctlp, ata_drvp, ata_pktp); + +	if (!rc) +		return (ATA_FSM_RC_BUSY); + +	/* +	 * This next one sets the controller in motion +	 */ +	ddi_put8(io_hdl1, ata_ctlp->ac_cmd, ata_pktp->ap_cmd); + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	return (ATA_FSM_RC_OKAY); +} + + + +static int +ata_disk_start_dma_in( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	ata_pkt_t *ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	int		 rc; + +	rc = ata_disk_start_common(ata_ctlp, ata_drvp, ata_pktp); + +	if (!rc) +		return (ATA_FSM_RC_BUSY); + +	/* +	 * Copy the Scatter/Gather list to the controller's +	 * Physical Region Descriptor Table +	 */ +	ata_pciide_dma_setup(ata_ctlp, ata_pktp->ap_sg_list, +		ata_pktp->ap_sg_cnt); + +	/* +	 * reset the PCIIDE Controller's interrupt and error status bits +	 */ +	(void) ata_pciide_status_clear(ata_ctlp); + +	/* +	 * This next one sets the drive in motion +	 */ +	ddi_put8(io_hdl1, ata_ctlp->ac_cmd, ata_pktp->ap_cmd); + +	/* wait for the drive's busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	ata_pciide_dma_start(ata_ctlp, PCIIDE_BMICX_RWCON_WRITE_TO_MEMORY); + +	return (ATA_FSM_RC_OKAY); +} + + + +static int +ata_disk_start_dma_out( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	ata_pkt_t *ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	int		 rc; + +	rc = ata_disk_start_common(ata_ctlp, ata_drvp, ata_pktp); + +	if (!rc) +		return (ATA_FSM_RC_BUSY); + +	/* +	 * Copy the Scatter/Gather list to the controller's +	 * Physical Region Descriptor Table +	 */ +	ata_pciide_dma_setup(ata_ctlp, ata_pktp->ap_sg_list, +		ata_pktp->ap_sg_cnt); + +	/* +	 * reset the PCIIDE Controller's interrupt and error status bits +	 */ +	(void) ata_pciide_status_clear(ata_ctlp); + +	/* +	 * This next one sets the drive in motion +	 */ +	ddi_put8(io_hdl1, ata_ctlp->ac_cmd, ata_pktp->ap_cmd); + +	/* wait for the drive's busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	ata_pciide_dma_start(ata_ctlp, PCIIDE_BMICX_RWCON_READ_FROM_MEMORY); + +	return (ATA_FSM_RC_OKAY); +} + + + + + +/* + * + * Start a PIO data-in ATA command: + * + */ + +static int +ata_disk_start_pio_in( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	ata_pkt_t *ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	int		 rc; + +	rc = ata_disk_start_common(ata_ctlp, ata_drvp, ata_pktp); + +	if (!rc) +		return (ATA_FSM_RC_BUSY); +	/* +	 * This next one sets the controller in motion +	 */ +	ddi_put8(io_hdl1, ata_ctlp->ac_cmd, ata_pktp->ap_cmd); + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	return (ATA_FSM_RC_OKAY); +} + + + + +/* + * + * Start a PIO data-out ATA command: + * + */ + +static int +ata_disk_start_pio_out( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	ata_pkt_t *ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	int		 rc; + +	ata_pktp->ap_wrt_count = 0; + +	rc = ata_disk_start_common(ata_ctlp, ata_drvp, ata_pktp); + +	if (!rc) +		return (ATA_FSM_RC_BUSY); +	/* +	 * This next one sets the controller in motion +	 */ +	ddi_put8(io_hdl1, ata_ctlp->ac_cmd, ata_pktp->ap_cmd); + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	/* +	 * Wait for the drive to assert DRQ to send the first chunk +	 * of data. Have to busy wait because there's no interrupt for +	 * the first chunk. This sucks (a lot of cycles) if the +	 * drive responds too slowly or if the wait loop granularity +	 * is too large. It's really bad if the drive is defective and +	 * the loop times out. +	 */ + +	if (!ata_wait3(io_hdl2, ata_ctlp->ac_ioaddr2, +			ATS_DRQ, ATS_BSY, /* okay */ +			ATS_ERR, ATS_BSY, /* cmd failed */ +			ATS_DF, ATS_BSY, /* drive failed */ +			4000000)) { +		ADBG_WARN(("ata_disk_start_pio_out: no DRQ\n")); +		ata_pktp->ap_flags |= AP_ERROR; +		return (ATA_FSM_RC_INTR); +	} + +	/* +	 * Tell the upper layer to fake a hardware interrupt which +	 * actually causes the first segment to be written to the drive. +	 */ +	return (ATA_FSM_RC_INTR); +} + + + +/* + * + * packet complete callback routine + * + */ + +static void +ata_disk_complete( +	ata_drv_t *ata_drvp, +	ata_pkt_t *ata_pktp, +	int do_callback) +{ +	struct ata_id   *aidp = &ata_drvp->ad_id; +	cmpkt_t	*pktp; + +	ADBG_TRACE(("ata_disk_complete entered\n")); +	ADBG_TRANSPORT(("ata_disk_complete: pkt = 0x%p\n", ata_pktp)); + +	pktp = APKT2CPKT(ata_pktp); + +	/* update resid */ + +	pktp->cp_resid = ata_pktp->ap_resid; + +	if (ata_pktp->ap_flags & AP_ERROR) { + +		pktp->cp_reason = CPS_CHKERR; + +		if (ata_pktp->ap_error & ATE_BBK_ICRC) { +			if (IS_ATA_VERSION_GE(aidp, 4)) +				ata_pktp->ap_scb = DERR_ICRC; +			else +				ata_pktp->ap_scb = DERR_BBK; +		} else if (ata_pktp->ap_error & ATE_UNC) +			ata_pktp->ap_scb = DERR_UNC; +		else if (ata_pktp->ap_error & ATE_IDNF) +			ata_pktp->ap_scb = DERR_IDNF; +		else if (ata_pktp->ap_error & ATE_TKONF) +			ata_pktp->ap_scb = DERR_TKONF; +		else if (ata_pktp->ap_error & ATE_AMNF) +			ata_pktp->ap_scb = DERR_AMNF; +		else if (ata_pktp->ap_status & ATS_BSY) +			ata_pktp->ap_scb = DERR_BUSY; +		else if (ata_pktp->ap_status & ATS_DF) +			ata_pktp->ap_scb = DERR_DWF; +		else /* any unknown error	*/ +			ata_pktp->ap_scb = DERR_ABORT; +	} else if (ata_pktp->ap_flags & +			(AP_ABORT|AP_TIMEOUT|AP_BUS_RESET)) { + +		pktp->cp_reason = CPS_CHKERR; +		ata_pktp->ap_scb = DERR_ABORT; +	} else { +		pktp->cp_reason = CPS_SUCCESS; +		ata_pktp->ap_scb = DERR_SUCCESS; +	} + +	/* callback */ +	if (do_callback) +		(*pktp->cp_callback)(pktp); +} + + +/* + * + * Interrupt callbacks + * + */ + + +/* + * + * ATA command, no data + * + */ + +/* ARGSUSED */ +static int +ata_disk_intr( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	ata_pkt_t *ata_pktp) +{ +	uchar_t		 status; + +	ADBG_TRACE(("ata_disk_intr entered\n")); +	ADBG_TRANSPORT(("ata_disk_intr: pkt = 0x%p\n", ata_pktp)); + +	status = ata_get_status_clear_intr(ata_ctlp, ata_pktp); + +	ASSERT((status & (ATS_BSY | ATS_DRQ)) == 0); + +	/* +	 * check for errors +	 */ + +	if (status & (ATS_DF | ATS_ERR)) { +		ADBG_WARN(("ata_disk_intr: status 0x%x error 0x%x\n", status, +			ddi_get8(ata_ctlp->ac_iohandle1, ata_ctlp->ac_error))); +		ata_pktp->ap_flags |= AP_ERROR; +	} + +	if (ata_pktp->ap_flags & AP_ERROR) { +		ata_pktp->ap_status = ddi_get8(ata_ctlp->ac_iohandle2, +			ata_ctlp->ac_altstatus); +		ata_pktp->ap_error = ddi_get8(ata_ctlp->ac_iohandle1, +			ata_ctlp->ac_error); +	} + +	/* tell the upper layer this request is complete */ +	return (ATA_FSM_RC_FINI); +} + + +/* + * + * ATA command, PIO data in + * + */ + +/* ARGSUSED */ +static int +ata_disk_intr_pio_in( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	ata_pkt_t *ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	uchar_t		 status; + +	ADBG_TRACE(("ata_disk_pio_in entered\n")); +	ADBG_TRANSPORT(("ata_disk_pio_in: pkt = 0x%p\n", ata_pktp)); + +	/* +	 * first make certain DRQ is asserted (and no errors) +	 */ +	(void) ata_wait3(io_hdl2, ata_ctlp->ac_ioaddr2, +			ATS_DRQ, ATS_BSY, ATS_ERR, ATS_BSY, ATS_DF, ATS_BSY, +			4000000); + +	status = ata_get_status_clear_intr(ata_ctlp, ata_pktp); + +	if (status & ATS_BSY) { +		ADBG_WARN(("ata_disk_pio_in: BUSY\n")); +		ata_pktp->ap_flags |= AP_ERROR; +		ata_pktp->ap_status = ddi_get8(io_hdl2, ata_ctlp->ac_altstatus); +		ata_pktp->ap_error = ddi_get8(io_hdl1, ata_ctlp->ac_error); +		return (ATA_FSM_RC_BUSY); +	} + +	/* +	 * record any errors +	 */ +	if ((status & (ATS_DRQ | ATS_DF | ATS_ERR)) != ATS_DRQ) { +		ADBG_WARN(("ata_disk_pio_in: status 0x%x error 0x%x\n", +			status, ddi_get8(io_hdl1, ata_ctlp->ac_error))); +		ata_pktp->ap_flags |= AP_ERROR; +		ata_pktp->ap_status = ddi_get8(io_hdl2, ata_ctlp->ac_altstatus); +		ata_pktp->ap_error = ddi_get8(io_hdl1, ata_ctlp->ac_error); +	} + +	/* +	 * read the next chunk of data (if any) +	 */ +	if (status & ATS_DRQ) { +		ata_disk_pio_xfer_data_in(ata_ctlp, ata_pktp); +	} + +	/* +	 * If that was the last chunk, wait for the device to clear DRQ +	 */ +	if (ata_pktp->ap_resid == 0) { +		if (ata_wait(io_hdl2, ata_ctlp->ac_ioaddr2, +			0, (ATS_DRQ | ATS_BSY), 4000000)) { +			/* tell the upper layer this request is complete */ +			return (ATA_FSM_RC_FINI); +		} + +		ADBG_WARN(("ata_disk_pio_in: DRQ stuck\n")); +		ata_pktp->ap_flags |= AP_ERROR; +		ata_pktp->ap_status = ddi_get8(io_hdl2, ata_ctlp->ac_altstatus); +		ata_pktp->ap_error = ddi_get8(io_hdl1, ata_ctlp->ac_error); +	} + +	/* +	 * check for errors +	 */ +	if (ata_pktp->ap_flags & AP_ERROR) { +		return (ATA_FSM_RC_FINI); +	} + +	/* +	 * If the read command isn't done yet, +	 * wait for the next interrupt. +	 */ +	ADBG_TRACE(("ata_disk_pio_in: partial\n")); +	return (ATA_FSM_RC_OKAY); +} + + + +/* + * + * ATA command, PIO data out + * + */ + +/* ARGSUSED */ +static int +ata_disk_intr_pio_out( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	ata_pkt_t *ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	int		 tmp_count = ata_pktp->ap_wrt_count; +	uchar_t		 status; + +	/* +	 * clear the IRQ +	 */ +	status = ata_get_status_clear_intr(ata_ctlp, ata_pktp); + +	ADBG_TRACE(("ata_disk_intr_pio_out entered\n")); +	ADBG_TRANSPORT(("ata_disk_intr_pio_out: pkt = 0x%p\n", ata_pktp)); + +	ASSERT(!(status & ATS_BSY)); + + +	/* +	 * check for errors +	 */ + +	if (status & (ATS_DF | ATS_ERR)) { +		ADBG_WARN(("ata_disk_intr_pio_out: status 0x%x error 0x%x\n", +		status, ddi_get8(io_hdl1, ata_ctlp->ac_error))); +		ata_pktp->ap_flags |= AP_ERROR; +		ata_pktp->ap_status = ddi_get8(io_hdl2, ata_ctlp->ac_altstatus); +		ata_pktp->ap_error = ddi_get8(io_hdl1, ata_ctlp->ac_error); +		/* tell the upper layer this request is complete */ +		return (ATA_FSM_RC_FINI); +	} + + +	/* +	 * last write was okay, bump the ptr and +	 * decr the resid count +	 */ +	ata_pktp->ap_v_addr += tmp_count; +	ata_pktp->ap_resid -= tmp_count; + +	/* +	 * check for final interrupt on write command +	 */ +	if (ata_pktp->ap_resid <= 0) { +		/* tell the upper layer this request is complete */ +		return (ATA_FSM_RC_FINI); +	} + +	/* +	 * Perform the next data transfer +	 * +	 * First make certain DRQ is asserted and no error status. +	 * (I'm not certain but I think some drives might deassert BSY +	 * before asserting DRQ. This extra ata_wait3() will +	 * compensate for such drives). +	 * +	 */ +	(void) ata_wait3(io_hdl2, ata_ctlp->ac_ioaddr2, +		ATS_DRQ, ATS_BSY, ATS_ERR, ATS_BSY, ATS_DF, ATS_BSY, 4000000); + +	status = ddi_get8(io_hdl2, ata_ctlp->ac_altstatus); + +	if (status & ATS_BSY) { +		/* this should never happen */ +		ADBG_WARN(("ata_disk_intr_pio_out: BUSY\n")); +		ata_pktp->ap_flags |= AP_ERROR; +		ata_pktp->ap_status = ddi_get8(io_hdl2, ata_ctlp->ac_altstatus); +		ata_pktp->ap_error = ddi_get8(io_hdl1, ata_ctlp->ac_error); +		return (ATA_FSM_RC_BUSY); +	} + +	/* +	 * bailout if any errors +	 */ +	if ((status & (ATS_DRQ | ATS_DF | ATS_ERR)) != ATS_DRQ) { +		ADBG_WARN(("ata_disk_pio_out: status 0x%x error 0x%x\n", +			status, ddi_get8(io_hdl1, ata_ctlp->ac_error))); +		ata_pktp->ap_flags |= AP_ERROR; +		ata_pktp->ap_status = ddi_get8(io_hdl2, ata_ctlp->ac_altstatus); +		ata_pktp->ap_error = ddi_get8(io_hdl1, ata_ctlp->ac_error); +		return (ATA_FSM_RC_FINI); +	} + +	/* +	 * write  the next chunk of data +	 */ +	ADBG_TRACE(("ata_disk_intr_pio_out: write xfer\n")); +	ata_disk_pio_xfer_data_out(ata_ctlp, ata_pktp); + +	/* +	 * Wait for the next interrupt before checking the transfer +	 * status and adjusting the transfer count. +	 * +	 */ +	return (ATA_FSM_RC_OKAY); +} + + +/* + * + * ATA command, DMA data in/out + * + */ + +static int +ata_disk_intr_dma( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp, +	ata_pkt_t *ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	uchar_t		 status; + +	ADBG_TRACE(("ata_disk_intr_dma entered\n")); +	ADBG_TRANSPORT(("ata_disk_intr_dma: pkt = 0x%p\n", ata_pktp)); + +	/* +	 * halt the DMA engine +	 */ +	ata_pciide_dma_stop(ata_ctlp); + +	/* +	 * wait for the device to clear DRQ +	 */ +	if (!ata_wait(io_hdl2, ata_ctlp->ac_ioaddr2, +			0, (ATS_DRQ | ATS_BSY), 4000000)) { +		ADBG_WARN(("ata_disk_intr_dma: DRQ stuck\n")); +		ata_pktp->ap_flags |= AP_ERROR; +		ata_pktp->ap_status = ddi_get8(io_hdl2, ata_ctlp->ac_altstatus); +		ata_pktp->ap_error = ddi_get8(io_hdl1, ata_ctlp->ac_error); +		return (ATA_FSM_RC_BUSY); +	} + +	/* +	 * get the status and clear the IRQ, and check for DMA error +	 */ +	status = ata_get_status_clear_intr(ata_ctlp, ata_pktp); + +	/* +	 * check for drive errors +	 */ + +	if (status & (ATS_DF | ATS_ERR)) { +		ADBG_WARN(("ata_disk_intr_dma: status 0x%x error 0x%x\n", +			status, ddi_get8(io_hdl1, ata_ctlp->ac_error))); +		ata_pktp->ap_flags |= AP_ERROR; +		ata_pktp->ap_status = ddi_get8(io_hdl2, ata_ctlp->ac_altstatus); +		ata_pktp->ap_error = ddi_get8(io_hdl1, ata_ctlp->ac_error); +	} + +	/* +	 * If there was a drive or DMA error, compute a resid count +	 */ +	if (ata_pktp->ap_flags & AP_ERROR) { +		/* +		 * grab the last sector address from the drive regs +		 * and use that to compute the resid +		 */ +		ata_disk_get_resid(ata_ctlp, ata_drvp, ata_pktp); +	} else { +		ata_pktp->ap_resid = 0; +	} + +	/* tell the upper layer this request is complete */ +	return (ATA_FSM_RC_FINI); +} + + +/* + * + * Low level PIO routine that transfers data from the drive + * + */ + +static void +ata_disk_pio_xfer_data_in( +	ata_ctl_t *ata_ctlp, +	ata_pkt_t *ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	int		 count; + +	count = min(ata_pktp->ap_resid, +			ata_pktp->ap_bytes_per_block); + +	ADBG_TRANSPORT(("ata_disk_pio_xfer_data_in: 0x%x bytes, addr = 0x%p\n", +			count, ata_pktp->ap_v_addr)); + +	/* +	 * read count bytes +	 */ + +	ASSERT(count != 0); + +	ddi_rep_get16(io_hdl1, (ushort_t *)ata_pktp->ap_v_addr, +		ata_ctlp->ac_data, (count >> 1), DDI_DEV_NO_AUTOINCR); + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	/* +	 * this read command completed okay, bump the ptr and +	 * decr the resid count now. +	 */ +	ata_pktp->ap_v_addr += count; +	ata_pktp->ap_resid -= count; +} + + +/* + * + * Low level PIO routine that transfers data to the drive + * + */ + +static void +ata_disk_pio_xfer_data_out( +	ata_ctl_t *ata_ctlp, +	ata_pkt_t *ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	int		 count; + +	count = min(ata_pktp->ap_resid, +			ata_pktp->ap_bytes_per_block); + +	ADBG_TRANSPORT(("ata_disk_pio_xfer_data_out: 0x%x bytes, addr = 0x%p\n", +			count, ata_pktp->ap_v_addr)); + +	/* +	 * read or write count bytes +	 */ + +	ASSERT(count != 0); + +	ddi_rep_put16(io_hdl1, (ushort_t *)ata_pktp->ap_v_addr, +		ata_ctlp->ac_data, (count >> 1), DDI_DEV_NO_AUTOINCR); + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	/* +	 * save the count here so I can correctly adjust +	 * the ap_v_addr and ap_resid values at the next +	 * interrupt. +	 */ +	ata_pktp->ap_wrt_count = count; +} + + +/* + * + * ATA Initialize Device Parameters (aka Set Params) command + * + * If the drive was put in some sort of CHS extended/logical geometry + * mode by the BIOS, this function will reset it to its "native" + * CHS geometry. This ensures that we don't run into any sort of + * 1024 cylinder (or 65535 cylinder) limitation that may have been + * created by a BIOS (or users) that chooses a bogus translated geometry. + */ + +static int +ata_disk_initialize_device_parameters( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp) +{ +	int		 rc; + +#ifdef	_SIMULATOR_SUPPORT +	extern int simulator_run;	/* running under simulator ? */ +#endif	/* _SIMULATOR_SUPPORT */ + +	rc = ata_command(ata_ctlp, ata_drvp, FALSE, FALSE, +			ata_disk_init_dev_parm_wait, +			ATC_SETPARAM, +			0, 			/* feature n/a */ +			ata_drvp->ad_phsec,	/* max sector (1-based) */ +			0,			/* sector n/a */ +			(ata_drvp->ad_phhd -1),	/* max head (0-based) */ +			0,			/* cyl_low n/a */ +			0);			/* cyl_hi n/a */ + +#ifdef	_SIMULATOR_SUPPORT +	if (rc || simulator_run) { +		return (TRUE); +	} +#else +	if (rc) { +		return (TRUE); +	} +#endif	/* _SIMULATOR_SUPPORT */ + +	ADBG_ERROR(("ata_init_dev_parms: failed\n")); +	return (FALSE); +} + + + +/* + * + * create fake inquiry data for DADA interface + * + */ + +static void +ata_disk_fake_inquiry( +	ata_drv_t *ata_drvp) +{ +	struct ata_id *ata_idp = &ata_drvp->ad_id; +	struct scsi_inquiry *inqp = &ata_drvp->ad_inquiry; + +	ADBG_TRACE(("ata_disk_fake_inquiry entered\n")); + +	if (ata_idp->ai_config & ATA_ID_REM_DRV) /* ide removable bit */ +		inqp->inq_rmb = 1;		/* scsi removable bit */ + +	(void) strncpy(inqp->inq_vid, "Gen-ATA ", sizeof (inqp->inq_vid)); +	inqp->inq_dtype = DTYPE_DIRECT; +	inqp->inq_qual = DPQ_POSSIBLE; + +	(void) strncpy(inqp->inq_pid, ata_idp->ai_model, +			sizeof (inqp->inq_pid)); +	(void) strncpy(inqp->inq_revision, ata_idp->ai_fw, +			sizeof (inqp->inq_revision)); +} + +#define	LOOP_COUNT	10000 + + +/* + * + * ATA Set Multiple Mode + * + */ + +static int +ata_disk_set_multiple( +	ata_ctl_t *ata_ctlp, +	ata_drv_t *ata_drvp) +{ +	int		 rc; + +	rc = ata_command(ata_ctlp, ata_drvp, TRUE, FALSE, +			ata_disk_set_mult_wait, +			ATC_SETMULT, +			0, 			/* feature n/a */ +			ata_drvp->ad_block_factor, /* count */ +			0,			/* sector n/a */ +			0, 			/* head n/a */ +			0,			/* cyl_low n/a */ +			0);			/* cyl_hi n/a */ + +	if (rc) { +		return (TRUE); +	} + +	ADBG_ERROR(("ata_disk_set_multiple: failed\n")); +	return (FALSE); +} + + +/* + * + * ATA Identify Device command + * + */ + +int +ata_disk_id( +	ddi_acc_handle_t io_hdl1, +	caddr_t		 ioaddr1, +	ddi_acc_handle_t io_hdl2, +	caddr_t		 ioaddr2, +	struct ata_id	*ata_idp) +{ +	int	rc; + +	ADBG_TRACE(("ata_disk_id entered\n")); + +	rc = ata_id_common(ATC_ID_DEVICE, TRUE, io_hdl1, ioaddr1, io_hdl2, +		ioaddr2, ata_idp); + +	if (!rc) +		return (FALSE); + +	/* +	 * If the disk is a CF/Microdrive that works under ATA mode +	 * through CF<->ATA adapters, identify it as an ATA device +	 * and a non removable media. +	 */ +	if (ata_idp->ai_config == ATA_ID_COMPACT_FLASH) { +		ata_idp->ai_config = ATA_ID_CF_TO_ATA; +	} + +	if ((ata_idp->ai_config & ATAC_ATA_TYPE_MASK) != ATAC_ATA_TYPE) +		return (FALSE); + +	if (ata_idp->ai_heads == 0 || ata_idp->ai_sectors == 0) { +		return (FALSE); +	} + +	return (TRUE); +} + +static daddr_t +ata_last_block_xferred_chs(ata_drv_t *ata_drvp) +{ +	ata_ctl_t	*ata_ctlp = ata_drvp->ad_ctlp; +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	uchar_t		 drvheads = ata_drvp->ad_phhd; +	uchar_t		 drvsectors = ata_drvp->ad_phsec; +	uchar_t		 sector; +	uchar_t		 head; +	uchar_t		 low_cyl; +	uchar_t		 hi_cyl; +	daddr_t		 lbastop; + +	sector = ddi_get8(io_hdl1, ata_ctlp->ac_sect); +	head = ddi_get8(io_hdl1, ata_ctlp->ac_drvhd) & 0xf; +	low_cyl = ddi_get8(io_hdl1, ata_ctlp->ac_lcyl); +	hi_cyl = ddi_get8(io_hdl1, ata_ctlp->ac_hcyl); + +	lbastop = low_cyl; +	lbastop |= (uint_t)hi_cyl << 8; +	lbastop *= (uint_t)drvheads; +	lbastop += (uint_t)head; +	lbastop *= (uint_t)drvsectors; +	lbastop += (uint_t)sector - 1; +	return (lbastop); +} + +static daddr_t +ata_last_block_xferred_lba28(ata_ctl_t *ata_ctlp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	daddr_t		lbastop; + +	lbastop = ddi_get8(io_hdl1, ata_ctlp->ac_drvhd) & 0xf; +	lbastop <<= 8; +	lbastop += ddi_get8(io_hdl1, ata_ctlp->ac_hcyl); +	lbastop <<= 8; +	lbastop += ddi_get8(io_hdl1, ata_ctlp->ac_lcyl); +	lbastop <<= 8; +	lbastop += ddi_get8(io_hdl1, ata_ctlp->ac_sect); +	return (lbastop); +} + +static daddr_t +ata_last_block_xferred_lba48(ata_ctl_t *ata_ctlp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	daddr_t		lbastop; + +	/* turn on HOB and read the high-order 24 bits */ +	ddi_put8(io_hdl2, ata_ctlp->ac_devctl, (ATDC_D3 | ATDC_HOB)); +	lbastop = ddi_get8(io_hdl1, ata_ctlp->ac_hcyl); +	lbastop <<= 8; +	lbastop += ddi_get8(io_hdl1, ata_ctlp->ac_lcyl); +	lbastop <<= 8; +	lbastop += ddi_get8(io_hdl1, ata_ctlp->ac_sect); +	lbastop <<= 8; + +	/* Turn off HOB and read the low-order 24-bits */ +	ddi_put8(io_hdl2, ata_ctlp->ac_devctl, (ATDC_D3)); +	lbastop += ddi_get8(io_hdl1, ata_ctlp->ac_hcyl); +	lbastop <<= 8; +	lbastop += ddi_get8(io_hdl1, ata_ctlp->ac_lcyl); +	lbastop <<= 8; +	lbastop += ddi_get8(io_hdl1, ata_ctlp->ac_sect); +	return (lbastop); +} + + +/* + * + * Need to compute a value for ap_resid so that cp_resid can + * be set by ata_disk_complete(). The cp_resid var is actually + * misnamed. It's actually the offset to the block in which the + * error occurred not the number of bytes transferred to the device. + * At least that's how dadk actually uses the cp_resid when reporting + * an error. In other words the sector that had the error and the + * number of bytes transferred don't always indicate the same offset. + * On top of that, when doing DMA transfers there's actually no + * way to determine how many bytes have been transferred by the DMA + * engine. On the other hand, the drive will report which sector + * it faulted on. Using that address this routine computes the + * number of residual bytes beyond that point which probably weren't + * written to the drive (the drive is allowed to re-order sector + * writes but on an ATA disk there's no way to deal with that + * complication; in other words, the resid value calculated by + * this routine is as good as we can manage). + */ + +static void +ata_disk_get_resid( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	uint_t		 lba_start; +	uint_t		 lba_stop; +	uint_t		 resid_bytes; +	uint_t		 resid_sectors; + +	lba_start = ata_pktp->ap_startsec; + +	if (ata_drvp->ad_flags & AD_EXT48) +		lba_stop = ata_last_block_xferred_lba48(ata_ctlp); +	else if (ata_drvp->ad_drive_bits & ATDH_LBA) +		lba_stop = ata_last_block_xferred_lba28(ata_ctlp); +	else /* CHS mode */ +		lba_stop = ata_last_block_xferred_chs(ata_drvp); + +	resid_sectors = lba_start + ata_pktp->ap_count - lba_stop; +	resid_bytes = resid_sectors << SCTRSHFT; + +	ADBG_TRACE(("ata_disk_get_resid start 0x%x cnt 0x%x stop 0x%x\n", +		    lba_start, ata_pktp->ap_count, lba_stop)); +	ata_pktp->ap_resid = resid_bytes; +} + + + +/* + * Removable media commands * + */ + + + +/* + * get the media status + * + * NOTE: the error handling case probably isn't correct but it + * will have to do until someone gives me a drive to test this on. + */ +static int +ata_disk_state( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	int	*statep = (int *)ata_pktp->ap_v_addr; +	uchar_t	 err; + +	ADBG_TRACE(("ata_disk_state\n")); +	if (ata_command(ata_ctlp, ata_drvp, TRUE, TRUE, 5 * 1000000, +		    ATC_DOOR_LOCK, 0, 0, 0, 0, 0, 0)) { +		*statep = DKIO_INSERTED; +		return (ATA_FSM_RC_FINI); +	} + +	err = ddi_get8(ata_ctlp->ac_iohandle1, ata_ctlp->ac_error); +	if (err & ATE_NM) +		*statep = DKIO_EJECTED; +	else +		*statep = DKIO_NONE; + +	return (ATA_FSM_RC_FINI); +} + +/* + * eject the media + */ + +static int +ata_disk_eject( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	ADBG_TRACE(("ata_disk_eject\n")); +	if (ata_command(ata_ctlp, ata_drvp, TRUE, TRUE, 5 * 1000000, +			ATC_EJECT, 0, 0, 0, 0, 0, 0)) { +		return (ATA_FSM_RC_FINI); +	} +	ata_pktp->ap_flags |= AP_ERROR; +	return (ATA_FSM_RC_FINI); +} + +/* + * lock the drive + * + */ +static int +ata_disk_lock( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	ADBG_TRACE(("ata_disk_lock\n")); +	if (ata_command(ata_ctlp, ata_drvp, TRUE, TRUE, 5 * 1000000, +			ATC_DOOR_LOCK, 0, 0, 0, 0, 0, 0)) { +		return (ATA_FSM_RC_FINI); +	} +	ata_pktp->ap_flags |= AP_ERROR; +	return (ATA_FSM_RC_FINI); +} + + +/* + * unlock the drive + * + */ +static int +ata_disk_unlock( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	ADBG_TRACE(("ata_disk_unlock\n")); +	if (ata_command(ata_ctlp, ata_drvp, TRUE, TRUE, 5 * 1000000, +			ATC_DOOR_UNLOCK, 0, 0, 0, 0, 0, 0)) { +		return (ATA_FSM_RC_FINI); +	} +	ata_pktp->ap_flags |= AP_ERROR; +	return (ATA_FSM_RC_FINI); +} + + +/* + * put the drive into standby mode + */ +static int +ata_disk_standby( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	ADBG_TRACE(("ata_disk_standby\n")); +	if (ata_command(ata_ctlp, ata_drvp, TRUE, TRUE, 5 * 1000000, +			ATC_STANDBY_IM, 0, 0, 0, 0, 0, 0)) { +		return (ATA_FSM_RC_FINI); +	} +	ata_pktp->ap_flags |= AP_ERROR; +	return (ATA_FSM_RC_FINI); +} + + +/* + * Recalibrate + * + * Note the extra long timeout value. This is necessary in case + * the drive was in standby mode and needs to spin up the media. + * + */ +static int +ata_disk_recalibrate( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	ADBG_TRACE(("ata_disk_recalibrate\n")); +	if (ata_command(ata_ctlp, ata_drvp, TRUE, TRUE, 31 * 1000000, +			ATC_RECAL, 0, 0, 0, 0, 0, 0)) { +		return (ATA_FSM_RC_FINI); +	} +	ata_pktp->ap_flags |= AP_ERROR; +	return (ATA_FSM_RC_FINI); +} + +/* + * Copy a string of bytes that were obtained by Identify Device into a + * string buffer provided by the caller. + * + * 1. Determine the amount to copy.  This is the lesser of the + *    length of the source string or the space available in the user's + *    buffer. + * 2. The true length of the source string is always returned to the + *    caller in the size field of the argument. + * 3. Copy the string, add a terminating NUL character at the end. + */ + +static int +ata_copy_dk_ioc_string(intptr_t arg, char *source, int length, int flag) +{ +	STRUCT_DECL(dadk_ioc_string, ds_arg); +	int			destsize; +	char			nulchar; +	caddr_t			outp; + +	/* +	 * The ioctls that use this routine are only available to +	 * the kernel. +	 */ +	if ((flag & FKIOCTL) == 0) +		return (EFAULT); + +	STRUCT_INIT(ds_arg, flag & FMODELS); + +	/* 1. determine size of user's buffer */ +	if (ddi_copyin((caddr_t)arg, STRUCT_BUF(ds_arg), STRUCT_SIZE(ds_arg), +	    flag)) +		return (EFAULT); +	destsize = STRUCT_FGET(ds_arg, is_size); +	if (destsize > length + 1) +		destsize = length + 1; + +	/* +	 * 2. Return the copied length to the caller.  Note: for +	 * convenience, we actually copy the entire structure back out, not +	 * just the length.  We don't change the is_buf field, so this +	 * shouldn't break anything. +	 */ +	STRUCT_FSET(ds_arg, is_size, length); +	if (ddi_copyout(STRUCT_BUF(ds_arg), (caddr_t)arg, STRUCT_SIZE(ds_arg), +	    flag)) +		return (EFAULT); + +	/* 3. copy the string and add a NULL terminator */ +	outp = STRUCT_FGETP(ds_arg, is_buf); +	if (ddi_copyout(source, outp, destsize - 1, flag)) +		return (EFAULT); +	nulchar = '\0'; +	if (ddi_copyout(&nulchar, outp + (destsize - 1), 1, flag)) +		return (EFAULT); +	return (0); +} + +/* + * Sun branded drives are shipped write cache disabled.  The default is to + * force write write caching on. + */ +static void +ata_set_write_cache(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp) +{ +	char *path; + +	if (ata_write_cache == 1) { +		if (ata_set_feature(ata_ctlp, ata_drvp, FC_WRITE_CACHE_ON, 0) +		    == FALSE) { +			path = kmem_alloc(MAXPATHLEN + 1, KM_NOSLEEP); +			if (path != NULL) { +				cmn_err(CE_WARN, +				    "%s unable to enable write cache targ=%d", +				    ddi_pathname(ata_ctlp->ac_dip, path), +				    ata_drvp->ad_targ); +				kmem_free(path, MAXPATHLEN + 1); +			} +		} +	} else if (ata_write_cache == -1) { +		if (ata_set_feature(ata_ctlp, ata_drvp, FC_WRITE_CACHE_OFF, 0) +		    == FALSE) { +			path = kmem_alloc(MAXPATHLEN + 1, KM_NOSLEEP); +			if (path != NULL) { +				cmn_err(CE_WARN, +				    "%s unable to disable write cache targ=%d", +				    ddi_pathname(ata_ctlp->ac_dip, path), +				    ata_drvp->ad_targ); +				kmem_free(path, MAXPATHLEN + 1); +			} +		} +	} +} diff --git a/usr/src/uts/intel/io/dktp/controller/ata/ata_disk.h b/usr/src/uts/intel/io/dktp/controller/ata/ata_disk.h new file mode 100644 index 0000000000..8bdba91cde --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/ata_disk.h @@ -0,0 +1,97 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2002 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ATA_DISK_H +#define	_ATA_DISK_H + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#ifdef	__cplusplus +extern "C" { +#endif + +/* + * ATA disk commands. + */ + +#define	ATC_SEEK	0x70    /* seek cmd, bottom 4 bits step rate 	*/ +#define	ATC_RDVER	0x40	/* read verify cmd			*/ +#define	ATC_RDSEC	0x20    /* read sector cmd			*/ +#define	ATC_RDLONG	0x23    /* read long without retry		*/ +#define	ATC_WRSEC	0x30    /* write sector cmd			*/ +#define	ATC_SETMULT	0xc6	/* set multiple mode			*/ +#define	ATC_RDMULT	0xc4	/* read multiple			*/ +#define	ATC_WRMULT	0xc5	/* write multiple			*/ +#define	ATC_READ_DMA	0xc8	/* read (multiple) w/DMA		*/ +#define	ATC_WRITE_DMA	0xca	/* write (multiple) w/DMA		*/ +#define	ATC_SETPARAM	0x91	/* set parameters command 		*/ +#define	ATC_ID_DEVICE	0xec    /* IDENTIFY DEVICE command 		*/ +#define	ATC_ACK_MC	0xdb	/* acknowledge media change		*/ +	/* ATA extended (48 bit) disk commands */ +#define	ATC_RDSEC_EXT	0x24	/* read sector */ +#define	ATC_RDMULT_EXT	0x29	/* read multiple */ +#define	ATC_RDDMA_EXT	0x25	/* read DMA */ +#define	ATC_WRSEC_EXT	0x34	/* write sector */ +#define	ATC_WRMULT_EXT	0x39	/* write multiple */ +#define	ATC_WRDMA_EXT	0x35	/* write DMA */ + +/* + * Low bits for Read/Write commands... + */ +#define	ATCM_ECCRETRY	0x01    /* Enable ECC and RETRY by controller 	*/ +				/* enabled if bit is CLEARED!!! 	*/ +#define	ATCM_LONGMODE	0x02    /* Use Long Mode (get/send data & ECC) 	*/ + +#ifdef  DADKIO_RWCMD_READ +#define	RWCMDP(pktp)  ((struct dadkio_rwcmd *)((pktp)->cp_bp->b_back)) +#endif + +/* useful macros */ + +#define	CPKT2GCMD(cpkt)	((gcmd_t *)(cpkt)->cp_ctl_private) +#define	CPKT2APKT(cpkt)  (GCMD2APKT(CPKT2GCMD(cpkt))) + +#define	GCMD2CPKT(cmdp)	((struct cmpkt *)((cmdp)->cmd_pktp)) +#define	APKT2CPKT(apkt) (GCMD2CPKT(APKT2GCMD(apkt))) + +/* public function prototypes */ + +int	ata_disk_attach(ata_ctl_t *ata_ctlp); +void	ata_disk_detach(ata_ctl_t *ata_ctlp); +int	ata_disk_init_drive(ata_drv_t *ata_drvp); +void	ata_disk_uninit_drive(ata_drv_t *ata_drvp); +int	ata_disk_id(ddi_acc_handle_t io_hdl1, caddr_t ioaddr1, +		ddi_acc_handle_t io_hdl2, caddr_t ioaddr2, +		struct ata_id *ata_idp); +int	ata_disk_bus_ctl(dev_info_t *d, dev_info_t *r, ddi_ctl_enum_t o, +		void *a, void *v); +int	ata_disk_setup_parms(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp); + +#ifdef	__cplusplus +} +#endif + +#endif /* _ATA_DISK_H */ diff --git a/usr/src/uts/intel/io/dktp/controller/ata/ata_dma.c b/usr/src/uts/intel/io/dktp/controller/ata/ata_dma.c new file mode 100644 index 0000000000..5b7ed19407 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/ata_dma.c @@ -0,0 +1,383 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include <sys/types.h> +#include <sys/debug.h> + +#include "ata_common.h" +#include "ata_disk.h" +#include "atapi.h" +#include "pciide.h" + +/* + * grap the PCI-IDE status byte + */ +#define	PCIIDE_STATUS_GET(hdl, addr)	\ +	ddi_get8((hdl), ((uchar_t *)(addr) + PCIIDE_BMISX_REG)) + +/* + * DMA attributes for device I/O + */ + +ddi_dma_attr_t ata_pciide_dma_attr = { +	DMA_ATTR_V0,		/* dma_attr_version */ +	0,			/* dma_attr_addr_lo */ +	0xffffffffU,		/* dma_attr_addr_hi */ +	0xffff,			/* dma_attr_count_max */ +	sizeof (int),		/* dma_attr_align */ +	1,			/* dma_attr_burstsizes */ +	1,			/* dma_attr_minxfer */ +	0x100 << SCTRSHFT,	/* dma_attr_maxxfer */ +				/* note that this value can change */ +				/* based on max_transfer property */ +	0xffff,			/* dma_attr_seg */ +	ATA_DMA_NSEGS,		/* dma_attr_sgllen */ +	512,			/* dma_attr_granular */ +	0			/* dma_attr_flags */ +}; + +/* + * DMA attributes for the Bus Mastering PRD table + * + * PRD table Must not cross 4k boundary. + * + * NOTE: the SFF-8038i spec says don't cross a 64k boundary but + * some chip specs seem to think the spec says 4k boundary, Intel + * 82371AB, section 5.2.3. I don't know whether the 4k restriction + * is for real or just a typo. I've specified 4k just to be safe. + * The same Intel spec says the buffer must be 64K aligned, I don't + * believe that and have specified 4 byte alignment. + * + */ + +#define	PCIIDE_BOUNDARY	(0x1000) + +ddi_dma_attr_t ata_prd_dma_attr = { +	DMA_ATTR_V0,		/* dma_attr_version */ +	0,			/* dma_attr_addr_lo */ +	0xffffffffU,		/* dma_attr_addr_hi */ +	PCIIDE_BOUNDARY - 1,	/* dma_attr_count_max */ +	sizeof (int),		/* dma_attr_align */ +	1,			/* dma_attr_burstsizes */ +	1,			/* dma_attr_minxfer */ +	PCIIDE_BOUNDARY,	/* dma_attr_maxxfer */ +	PCIIDE_BOUNDARY - 1,	/* dma_attr_seg */ +	1,			/* dma_attr_sgllen */ +	1,			/* dma_attr_granular */ +	0			/* dma_attr_flags */ +}; + + + +size_t	prd_size = sizeof (prde_t) * ATA_DMA_NSEGS; + +int +ata_pciide_alloc( +	dev_info_t *dip, +	ata_ctl_t *ata_ctlp) +{ +	ddi_device_acc_attr_t	dev_attr; +	ddi_dma_cookie_t	cookie; +	size_t			buf_size; +	uint_t			count; +	int			rc; + +	dev_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; +	dev_attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC; +	dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; + + +	rc = ddi_dma_alloc_handle(dip, &ata_prd_dma_attr, DDI_DMA_SLEEP, NULL, +		&ata_ctlp->ac_sg_handle); +	if (rc != DDI_SUCCESS) { +		ADBG_ERROR(("ata_pciide_alloc 0x%p handle %d\n", +		    (void *)ata_ctlp, rc)); +		goto err3; +	} + +	rc = ddi_dma_mem_alloc(ata_ctlp->ac_sg_handle, prd_size, &dev_attr, +	    DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, +	    &ata_ctlp->ac_sg_list, &buf_size, &ata_ctlp->ac_sg_acc_handle); +	if (rc != DDI_SUCCESS) { +		ADBG_ERROR(("ata_pciide_alloc 0x%p mem %d\n", +		    (void *)ata_ctlp, rc)); +		goto err2; +	} + +	rc = ddi_dma_addr_bind_handle(ata_ctlp->ac_sg_handle, NULL, +	    ata_ctlp->ac_sg_list, buf_size, +	    DDI_DMA_WRITE | DDI_DMA_CONSISTENT, +	    DDI_DMA_SLEEP, NULL, &cookie, &count); +	if (rc != DDI_DMA_MAPPED) { +		ADBG_ERROR(("ata_pciide_alloc 0x%p bind %d\n", +		    (void *)ata_ctlp, rc)); +		goto err1; +	} + +	ASSERT(count == 1); +	ASSERT((cookie.dmac_address & (sizeof (int) - 1)) == 0); +#define	Mask4K	0xfffff000 +	ASSERT((cookie.dmac_address & Mask4K) +		== ((cookie.dmac_address + cookie.dmac_size - 1) & Mask4K)); + +	ata_ctlp->ac_sg_paddr = cookie.dmac_address; +	return (TRUE); +err1: +	ddi_dma_mem_free(&ata_ctlp->ac_sg_acc_handle); +	ata_ctlp->ac_sg_acc_handle = NULL; +err2: +	ddi_dma_free_handle(&ata_ctlp->ac_sg_handle); +	ata_ctlp->ac_sg_handle = NULL; +err3: +	return (FALSE); +} + + +void +ata_pciide_free(ata_ctl_t *ata_ctlp) +{ +	if (ata_ctlp->ac_sg_handle == NULL) +		return; + +	(void) ddi_dma_unbind_handle(ata_ctlp->ac_sg_handle); +	ddi_dma_mem_free(&ata_ctlp->ac_sg_acc_handle); +	ddi_dma_free_handle(&ata_ctlp->ac_sg_handle); +	ata_ctlp->ac_sg_handle = NULL; +	ata_ctlp->ac_sg_acc_handle = NULL; +} + + + +void +ata_pciide_dma_setup( +	ata_ctl_t *ata_ctlp, +	prde_t	  *srcp, +	int	   sg_cnt) +{ +	ddi_acc_handle_t bmhandle = ata_ctlp->ac_bmhandle; +	caddr_t		 bmaddr = ata_ctlp->ac_bmaddr; +	ddi_acc_handle_t sg_acc_handle = ata_ctlp->ac_sg_acc_handle; +	uint_t		*dstp = (uint_t *)ata_ctlp->ac_sg_list; +	int		 idx; + +	ASSERT(dstp != 0); +	ASSERT(sg_cnt != 0); + +	ADBG_DMA(("ata dma_setup 0x%p 0x%p %d\n", ata_ctlp, srcp, sg_cnt)); +	/* +	 * Copy the PRD list to controller's phys buffer. +	 * Copying to a fixed location avoids having to check +	 * every ata_pkt for alignment and page boundaries. +	 */ +	for (idx = 0; idx < sg_cnt - 1; idx++, srcp++) { +		ddi_put32(sg_acc_handle, dstp++, srcp->p_address); +		ddi_put32(sg_acc_handle, dstp++, srcp->p_count); +	} + +	/* +	 * set the end of table flag in the last entry +	 */ +	srcp->p_count |= PCIIDE_PRDE_EOT; +	ddi_put32(sg_acc_handle, dstp++, srcp->p_address); +	ddi_put32(sg_acc_handle, dstp++, srcp->p_count); + +	/* +	 * give the pciide chip the physical address of the PRDE table +	 */ +	ddi_put32(bmhandle, (uint_t *)(bmaddr + PCIIDE_BMIDTPX_REG), +		ata_ctlp->ac_sg_paddr); + +	ADBG_DMA(("ata dma_setup 0x%p 0x%llx\n", +		bmaddr, (unsigned long long)ata_ctlp->ac_sg_paddr)); +} + + + +void +ata_pciide_dma_start( +	ata_ctl_t *ata_ctlp, +	uchar_t direction) +{ +	ddi_acc_handle_t bmhandle = ata_ctlp->ac_bmhandle; +	caddr_t		 bmaddr = ata_ctlp->ac_bmaddr; +	uchar_t		 tmp; + +	ASSERT((ata_ctlp->ac_sg_paddr & PCIIDE_BMIDTPX_MASK) == 0); +	ASSERT((direction == PCIIDE_BMICX_RWCON_WRITE_TO_MEMORY) || +		(direction == PCIIDE_BMICX_RWCON_READ_FROM_MEMORY)); + +	/* +	 * Set the direction control and start the PCIIDE DMA controller +	 */ +	tmp = ddi_get8(bmhandle, (uchar_t *)bmaddr + PCIIDE_BMICX_REG); +	tmp &= PCIIDE_BMICX_MASK; +	ddi_put8(bmhandle, (uchar_t *)bmaddr + PCIIDE_BMICX_REG, +		(tmp |  direction)); + +	ddi_put8(bmhandle, (uchar_t *)bmaddr + PCIIDE_BMICX_REG, +		(tmp | PCIIDE_BMICX_SSBM_E | direction)); + +	return; + +} + + +void +ata_pciide_dma_stop( +	ata_ctl_t *ata_ctlp) +{ +	ddi_acc_handle_t bmhandle = ata_ctlp->ac_bmhandle; +	caddr_t		 bmaddr = ata_ctlp->ac_bmaddr; +	uchar_t		 tmp; + +	/* +	 * Stop the PCIIDE DMA controller +	 */ +	tmp = ddi_get8(bmhandle, (uchar_t *)bmaddr + PCIIDE_BMICX_REG); +	tmp &= (PCIIDE_BMICX_MASK & (~PCIIDE_BMICX_SSBM)); + +	ADBG_DMA(("ata_pciide_dma_stop 0x%p 0x%x\n", bmaddr, tmp)); + +	ddi_put8(bmhandle, (uchar_t *)bmaddr + PCIIDE_BMICX_REG, tmp); +} + +/* ARGSUSED */ +void +ata_pciide_dma_sg_func( +	gcmd_t	*gcmdp, +	ddi_dma_cookie_t *dmackp, +	int	 single_segment, +	int	 seg_index) +{ +	ata_pkt_t *ata_pktp = GCMD2APKT(gcmdp); +	prde_t	  *dmap; + +	ASSERT(seg_index < ATA_DMA_NSEGS); +	ASSERT(((uint_t)dmackp->dmac_address & PCIIDE_PRDE_ADDR_MASK) == 0); +	ASSERT((dmackp->dmac_size & PCIIDE_PRDE_CNT_MASK) == 0); +	ASSERT(dmackp->dmac_size <= PCIIDE_PRDE_CNT_MAX); + +	ADBG_TRACE(("adp_dma_sg_func: gcmdp 0x%p dmackp 0x%p s %d idx %d\n", +		    gcmdp, dmackp, single_segment, seg_index)); + +	/* set address of current entry in scatter/gather list */ +	dmap = ata_pktp->ap_sg_list + seg_index; + +	/* store the phys addr and count from the cookie */ +	dmap->p_address = (uint_t)dmackp->dmac_address; +	dmap->p_count = (uint_t)dmackp->dmac_size; + +	/* save the count of scatter/gather segments */ +	ata_pktp->ap_sg_cnt = seg_index + 1; + +	/* compute the total bytes in this request */ +	if (seg_index == 0) +		ata_pktp->ap_bcount = 0; +	ata_pktp->ap_bcount += dmackp->dmac_size; +} + + + +int +ata_pciide_status_clear( +	ata_ctl_t *ata_ctlp) +{ +	ddi_acc_handle_t bmhandle = ata_ctlp->ac_bmhandle; +	caddr_t		 bmaddr = ata_ctlp->ac_bmaddr; +	uchar_t		 status; +	uchar_t		 tmp; + +	/* +	 * Get the current PCIIDE status +	 */ +	status = PCIIDE_STATUS_GET(ata_ctlp->ac_bmhandle, ata_ctlp->ac_bmaddr); +	tmp = status & PCIIDE_BMISX_MASK; +	tmp |= (PCIIDE_BMISX_IDERR | PCIIDE_BMISX_IDEINTS); + +	ADBG_DMA(("ata_pciide_status_clear 0x%p 0x%x\n", +		bmaddr, status)); + +	/* +	 * Clear the latches (and preserve the other bits) +	 */ +	ddi_put8(bmhandle, (uchar_t *)bmaddr + PCIIDE_BMISX_REG, tmp); + +#ifdef NAT_SEMI_PC87415_BUG +	/* ??? chip errata ??? */ +	if (ata_ctlp->ac_nat_semi_bug) { +		tmp = ddi_get8(bmhandle, bmaddr + PCIIDE_BMICX_REG); +		tmp &= PCIIDE_BMICX_MASK; +		ddi_put8(bmhandle, bmaddr + PCIIDE_BMICX_REG, +			(tmp | PCIIDE_BMISX_IDERR | PCIIDE_BMISX_IDEINTS)); +	} +#endif +	return (status); +} + +int +ata_pciide_status_dmacheck_clear( +	ata_ctl_t *ata_ctlp) +{ +	uchar_t		 status; + +	/* +	 * Get the PCIIDE DMA controller's current status +	 */ +	status = ata_pciide_status_clear(ata_ctlp); + +	ADBG_DMA(("ata_pciide_status_dmacheck_clear 0x%p 0x%x\n", +		ata_ctlp->ac_bmaddr, status)); +	/* +	 * check for errors +	 */ +	if (status & PCIIDE_BMISX_IDERR) { +		ADBG_WARN(("ata_pciide_status: 0x%x\n", status)); +		return (TRUE); +	} +	return (FALSE); +} + + + +/* + * Check for a pending PCI-IDE interrupt + */ + +int +ata_pciide_status_pending( +	ata_ctl_t *ata_ctlp) +{ +	uchar_t status; + +	status = PCIIDE_STATUS_GET(ata_ctlp->ac_bmhandle, ata_ctlp->ac_bmaddr); +	ADBG_DMA(("ata_pciide_status_pending 0x%p 0x%x\n", +		ata_ctlp->ac_bmaddr, status)); +	if (status & PCIIDE_BMISX_IDEINTS) +		return (TRUE); +	return (FALSE); +} diff --git a/usr/src/uts/intel/io/dktp/controller/ata/ata_fsm.h b/usr/src/uts/intel/io/dktp/controller/ata/ata_fsm.h new file mode 100644 index 0000000000..4d4bbef7ca --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/ata_fsm.h @@ -0,0 +1,159 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 1997 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ATA_FSM_H +#define	_ATA_FSM_H + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#ifdef	__cplusplus +extern "C" { +#endif + + +/* + * + * The interrupt reason can be interpreted from other bits as follows: + * + *  IO  CoD  DRQ + *  --  ---  --- + *   0    0    1  == 1 Data to device + *   0    1    0  == 2 Idle + *   0    1    1  == 3 Send ATAPI CDB to device + *   1    0    1  == 5 Data from device + *   1    1    0  == 6 Status ready + *   1    1    1  == 7 Future use + * + */ + +/* + * This macro encodes the interrupt reason into a one byte + * event code which is used to index the FSM tables + */ +#define	ATAPI_EVENT(drq, intr)	\ +	(((unsigned char)((drq) & ATS_DRQ) >> 3) \ +	| (((intr) & (ATI_IO | ATI_COD)) << 1)) + +/* + * These are the names for the encoded ATAPI events + */ +#define	ATAPI_EVENT_0		0 +#define	ATAPI_EVENT_IDLE	ATAPI_EVENT(0, ATI_COD) +#define	ATAPI_EVENT_2		2 +#define	ATAPI_EVENT_STATUS	ATAPI_EVENT(0, ATI_IO | ATI_COD) +#define	ATAPI_EVENT_PIO_OUT	ATAPI_EVENT(ATS_DRQ, 0) +#define	ATAPI_EVENT_CDB		ATAPI_EVENT(ATS_DRQ, ATI_COD) +#define	ATAPI_EVENT_PIO_IN	ATAPI_EVENT(ATS_DRQ, ATI_IO) +#define	ATAPI_EVENT_UNKNOWN	ATAPI_EVENT(ATS_DRQ, (ATI_IO | ATI_COD)) + +#define	ATAPI_NEVENTS		8 + +/* + * Actions for the ATAPI PIO FSM + * + */ + +enum { +	A_UNK,		/* invalid event detected */ +	A_NADA,		/* do nothing */ +	A_CDB,		/* send the CDB */ +	A_IN,		/* transfer data out to the device */ +	A_OUT,		/* transfer data in from the device */ +	A_IDLE,		/* unexpected idle phase */ +	A_RE,		/* read the error code register */ +	A_REX		/* alternate read the error code register */ +}; + +/* + * States for the ATAPI PIO FSM + */ + +enum { +	S_IDLE,		/* idle or fatal error state */ +	S_CMD,		/* command byte sent */ +	S_CDB,		/* CDB sent */ +	S_IN,		/* transferring data in from device */ +	S_OUT,		/* transferring data out to device */ +	S_DMA,		/* dma transfer active */ + +	ATAPI_NSTATES +}; + +#define	S_X	S_IDLE	/* alias for idle */ + +/* + * controller and device functions + */ +enum { +	ATA_FSM_START0, +	ATA_FSM_START1, +	ATA_FSM_INTR, +	ATA_FSM_FINI, +	ATA_FSM_RESET, + +	ATA_CTLR_NFUNCS +}; + + +/* + * FSM return codes + */ +enum { +	ATA_FSM_RC_OKAY, +	ATA_FSM_RC_BUSY, +	ATA_FSM_RC_INTR, +	ATA_FSM_RC_FINI +}; + +/* + * states for the controller FSM + */ +enum { +	AS_IDLE, +	AS_ACTIVE0, +	AS_ACTIVE1, + +	ATA_CTLR_NSTATES +}; + +/* + * actions for the controller FSM + */ +enum { +	AC_NADA, +	AC_START, +	AC_INTR, +	AC_FINI, +	AC_BUSY, +	AC_RESET_I, +	AC_RESET_A +}; + +#ifdef	__cplusplus +} +#endif + +#endif /* _ATA_FSM_H */ diff --git a/usr/src/uts/intel/io/dktp/controller/ata/atapi.c b/usr/src/uts/intel/io/dktp/controller/ata/atapi.c new file mode 100644 index 0000000000..0a0f01350a --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/atapi.c @@ -0,0 +1,1174 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2005 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include <sys/types.h> + +#include "ata_common.h" +#include "atapi.h" + +/* SCSA entry points */ + +static int atapi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, +    scsi_hba_tran_t *hba_tran, struct scsi_device *sd); +static int atapi_tran_tgt_probe(struct scsi_device *sd, int (*callback)(void)); +static void atapi_tran_tgt_free(dev_info_t *hba_dip, dev_info_t *tgt_dip, +    scsi_hba_tran_t *hba_tran, struct scsi_device *sd); +static int atapi_tran_abort(struct scsi_address *ap, struct scsi_pkt *spktp); +static int atapi_tran_reset(struct scsi_address *ap, int level); +static int atapi_tran_getcap(struct scsi_address *ap, char *capstr, int whom); +static int atapi_tran_setcap(struct scsi_address *ap, char *capstr, +    int value, int whom); +static struct scsi_pkt	*atapi_tran_init_pkt(struct scsi_address *ap, +    struct scsi_pkt *spktp, struct buf *bp, int cmdlen, int statuslen, +    int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg); +static void atapi_tran_destroy_pkt(struct scsi_address *ap, +    struct scsi_pkt *spktp); +static void atapi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *spktp); +static void atapi_tran_sync_pkt(struct scsi_address *ap, +    struct scsi_pkt *spktp); +static int atapi_tran_start(struct scsi_address *ap, struct scsi_pkt *spktp); + +/* + * packet callbacks + */ +static void atapi_complete(ata_drv_t *ata_drvp, ata_pkt_t *ata_pktp, +    int do_callback); +static int atapi_id_update(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +    ata_pkt_t *ata_pktp); + + +/* external dependencies */ + +char _depends_on[] = "misc/scsi"; + +/* + * Local static data + */ + +#if 0 +static ddi_dma_lim_t atapi_dma_limits = { +	0,		/* address low				*/ +	0xffffffffU,	/* address high				*/ +	0,		/* counter max				*/ +	1,		/* burstsize				*/ +	DMA_UNIT_8,	/* minimum xfer				*/ +	0,		/* dma speed				*/ +	(uint_t)DMALIM_VER0,	/* version			*/ +	0xffffffffU,	/* address register			*/ +	0xffffffffU,	/* counter register			*/ +	1,		/* granular				*/ +	1,		/* scatter/gather list length		*/ +	0xffffffffU	/* request size				*/ +}; +#endif + +static	int	atapi_use_static_geometry = TRUE; +static	int	atapi_arq_enable = TRUE; + + +/* + * + * Call SCSA init to initialize the ATAPI half of the driver + * + */ + +int +atapi_attach(ata_ctl_t *ata_ctlp) +{ +	dev_info_t	*dip = ata_ctlp->ac_dip; +	scsi_hba_tran_t *tran; + +	ADBG_TRACE(("atapi_init entered\n")); + +	/* allocate transport structure */ + +	tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP); + +	if (tran == NULL) { +		ADBG_WARN(("atapi_init: scsi_hba_tran_alloc failed\n")); +		goto errout; +	} + +	ata_ctlp->ac_atapi_tran = tran; +	ata_ctlp->ac_flags |= AC_SCSI_HBA_TRAN_ALLOC; + +	/* initialize transport structure */ + +	tran->tran_hba_private = ata_ctlp; +	tran->tran_tgt_private = NULL; + +	tran->tran_tgt_init = atapi_tran_tgt_init; +	tran->tran_tgt_probe = atapi_tran_tgt_probe; +	tran->tran_tgt_free = atapi_tran_tgt_free; +	tran->tran_start = atapi_tran_start; +	tran->tran_reset = atapi_tran_reset; +	tran->tran_abort = atapi_tran_abort; +	tran->tran_getcap = atapi_tran_getcap; +	tran->tran_setcap = atapi_tran_setcap; +	tran->tran_init_pkt = atapi_tran_init_pkt; +	tran->tran_destroy_pkt = atapi_tran_destroy_pkt; +	tran->tran_dmafree = atapi_tran_dmafree; +	tran->tran_sync_pkt = atapi_tran_sync_pkt; + +	if (scsi_hba_attach_setup(ata_ctlp->ac_dip, &ata_pciide_dma_attr, tran, +		SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) { +		ADBG_WARN(("atapi_init: scsi_hba_attach_setup failed\n")); +		goto errout; +	} + +	ata_ctlp->ac_flags |= AC_SCSI_HBA_ATTACH; + +	return (TRUE); + +errout: +	atapi_detach(ata_ctlp); +	return (FALSE); +} + + +/* + * + * destroy the atapi sub-system + * + */ + +void +atapi_detach( +	ata_ctl_t *ata_ctlp) +{ +	ADBG_TRACE(("atapi_detach entered\n")); + +	if (ata_ctlp->ac_flags & AC_SCSI_HBA_ATTACH) +		scsi_hba_detach(ata_ctlp->ac_dip); + +	if (ata_ctlp->ac_flags & AC_SCSI_HBA_TRAN_ALLOC) +		scsi_hba_tran_free(ata_ctlp->ac_atapi_tran); +} + + + +/* + * + * initialize the ATAPI drive's soft-state based on the + * response to IDENTIFY PACKET DEVICE command + * + */ + +int +atapi_init_drive( +	ata_drv_t *ata_drvp) +{ +	ADBG_TRACE(("atapi_init_drive entered\n")); + +	/* Determine ATAPI CDB size */ + +	switch (ata_drvp->ad_id.ai_config & ATAPI_ID_CFG_PKT_SZ) { + +	case ATAPI_ID_CFG_PKT_12B: +		ata_drvp->ad_cdb_len = 12; +		break; +	case ATAPI_ID_CFG_PKT_16B: +		ata_drvp->ad_cdb_len = 16; +		break; +	default: +		ADBG_WARN(("atapi_init_drive: bad pkt size support\n")); +		return (FALSE); +	} + +	/* determine if drive gives an intr when it wants the CDB */ + +	if ((ata_drvp->ad_id.ai_config & ATAPI_ID_CFG_DRQ_TYPE) != +	    ATAPI_ID_CFG_DRQ_INTR) +		ata_drvp->ad_flags |= AD_NO_CDB_INTR; + +	return (TRUE); +} + + +/* + * + * destroy an atapi drive + * + */ + +/* ARGSUSED */ +void +atapi_uninit_drive( +	ata_drv_t *ata_drvp) +{ +	ADBG_TRACE(("atapi_uninit_drive entered\n")); +} + +/* + * + * Issue an IDENTIFY PACKET (ATAPI) DEVICE command + * + */ + +int +atapi_id( +	ddi_acc_handle_t io_hdl1, +	caddr_t		 ioaddr1, +	ddi_acc_handle_t io_hdl2, +	caddr_t		 ioaddr2, +	struct ata_id	*ata_idp) +{ +	int	rc; + +	ADBG_TRACE(("atapi_id entered\n")); + +	rc = ata_id_common(ATC_ID_PACKET_DEVICE, FALSE, io_hdl1, ioaddr1, +		io_hdl2, ioaddr2, ata_idp); + +	if (!rc) +		return (FALSE); + +	if ((ata_idp->ai_config & ATAC_ATAPI_TYPE_MASK) != ATAC_ATAPI_TYPE) +		return (FALSE); + +	return (TRUE); +} + + +/* + * + * Check the device's register block for the ATAPI signature. + * + * Although the spec says the sector count, sector number and device/head + * registers are also part of the signature, for some unknown reason, this + * routine only checks the cyl hi and cyl low registers. I'm just + * guessing, but it might be because ATA and ATAPI devices return + * identical values in those registers and we actually rely on the + * IDENTIFY DEVICE and IDENTIFY PACKET DEVICE commands to recognize the + * device type. + * + */ + +int +atapi_signature( +	ddi_acc_handle_t io_hdl, +	caddr_t ioaddr) +{ +	int	rc = FALSE; +	ADBG_TRACE(("atapi_signature entered\n")); + +	if (ddi_get8(io_hdl, (uchar_t *)ioaddr + AT_HCYL) == ATAPI_SIG_HI && +		ddi_get8(io_hdl, (uchar_t *)ioaddr + AT_LCYL) != ATAPI_SIG_LO) +		rc = TRUE; + +	/* +	 * The following is a little bit of bullet proofing. +	 * +	 * When some drives are configured on a master-only bus they +	 * "shadow" their registers for the not-present slave drive. +	 * This is bogus and if you're not careful it may cause a +	 * master-only drive to be mistakenly recognized as both +	 * master and slave. By clearing the signature registers here +	 * I can make certain that when ata_drive_type() switches from +	 * the master to slave drive that I'll read back non-signature +	 * values regardless of whether the master-only drive does +	 * the "shadow" register trick. This prevents a bogus +	 * IDENTIFY PACKET DEVICE command from being issued which +	 * a really bogus master-only drive will return "shadow" +	 * data for. +	 */ +	ddi_put8(io_hdl, (uchar_t *)ioaddr + AT_HCYL, 0); +	ddi_put8(io_hdl, (uchar_t *)ioaddr + AT_LCYL, 0); + +	return (rc); +} + + +/* + * + * SCSA tran_tgt_init entry point + * + */ + +/* ARGSUSED */ +static int +atapi_tran_tgt_init( +	dev_info_t	*hba_dip, +	dev_info_t	*tgt_dip, +	scsi_hba_tran_t *hba_tran, +	struct scsi_device *sd) +{ +	gtgt_t	  *gtgtp;	/* GHD's per-target-instance structure */ +	ata_ctl_t *ata_ctlp; +	ata_tgt_t *ata_tgtp; +	ata_drv_t *ata_drvp; +	struct scsi_address *ap; +	int	rc = DDI_SUCCESS; + +	ADBG_TRACE(("atapi_tran_tgt_init entered\n")); + +	/* +	 * Qualification of targ, lun, and ATAPI device presence +	 *  have already been taken care of by ata_bus_ctl +	 */ + +	/* store pointer to drive struct in cloned tran struct */ + +	ata_ctlp = TRAN2CTL(hba_tran); +	ap = &sd->sd_address; + +	ata_drvp = CTL2DRV(ata_ctlp, ap->a_target, ap->a_lun); + +	/* +	 * Create the "atapi" property so the target driver knows +	 * to use the correct set of SCSI commands +	 */ +	if (!ata_prop_create(tgt_dip, ata_drvp, "atapi")) { +		return (DDI_FAILURE); +	} + +	gtgtp = ghd_target_init(hba_dip, tgt_dip, &ata_ctlp->ac_ccc, +	    sizeof (ata_tgt_t), ata_ctlp, +	    ap->a_target, ap->a_lun); + +	/* tran_tgt_private points to gtgt_t */ +	hba_tran->tran_tgt_private = gtgtp; + +	/* gt_tgt_private points to ata_tgt_t */ +	ata_tgtp = GTGTP2ATATGTP(gtgtp); + +	/* initialize the per-target-instance data */ +	ata_tgtp->at_drvp = ata_drvp; +	ata_tgtp->at_dma_attr = ata_pciide_dma_attr; +	ata_tgtp->at_dma_attr.dma_attr_maxxfer = +	    ata_ctlp->ac_max_transfer << SCTRSHFT; + +	return (rc); +} + + +/* + * + * SCSA tran_tgt_probe entry point + * + */ + +static int +atapi_tran_tgt_probe(struct scsi_device *sd, int (*callback)(void)) +{ +	ADBG_TRACE(("atapi_tran_tgt_probe entered\n")); + +	return (scsi_hba_probe(sd, callback)); +} + + +/* + * + * SCSA tran_tgt_free entry point + * + */ + +/* ARGSUSED */ +static void +atapi_tran_tgt_free( +	dev_info_t	*hba_dip, +	dev_info_t	*tgt_dip, +	scsi_hba_tran_t	*hba_tran, +	struct scsi_device *sd) +{ +	ADBG_TRACE(("atapi_tran_tgt_free entered\n")); + +	ghd_target_free(hba_dip, tgt_dip, &TRAN2ATAP(hba_tran)->ac_ccc, +		TRAN2GTGTP(hba_tran)); +	hba_tran->tran_tgt_private = NULL; +} + + + +/* + * + * SCSA tran_abort entry point + * + */ + +/* ARGSUSED */ +static int +atapi_tran_abort( +	struct scsi_address *ap, +	struct scsi_pkt *spktp) +{ +	ADBG_TRACE(("atapi_tran_abort entered\n")); + +	if (spktp) { +		return (ghd_tran_abort(&ADDR2CTL(ap)->ac_ccc, PKTP2GCMDP(spktp), +			ADDR2GTGTP(ap), NULL)); +	} + +	return (ghd_tran_abort_lun(&ADDR2CTL(ap)->ac_ccc, ADDR2GTGTP(ap), +		NULL)); +} + + +/* + * + * SCSA tran_reset entry point + * + */ + +/* ARGSUSED */ +static int +atapi_tran_reset( +	struct scsi_address *ap, +	int level) +{ +	ADBG_TRACE(("atapi_tran_reset entered\n")); + +	if (level == RESET_TARGET) +		return (ghd_tran_reset_target(&ADDR2CTL(ap)->ac_ccc, +		    ADDR2GTGTP(ap), NULL)); +	if (level == RESET_ALL) +		return (ghd_tran_reset_bus(&ADDR2CTL(ap)->ac_ccc, +		    ADDR2GTGTP(ap), NULL)); +	return (FALSE); + +} + + +/* + * + * SCSA tran_setcap entry point + * + */ + +static int +atapi_tran_setcap( +	struct scsi_address *ap, +	char *capstr, +	int value, +	int whom) +{ +	gtgt_t	  *gtgtp = ADDR2GTGTP(ap); +	ata_tgt_t *tgtp = GTGTP2ATATGTP(gtgtp); + +	ADBG_TRACE(("atapi_tran_setcap entered\n")); + +	switch (scsi_hba_lookup_capstr(capstr)) { +		case SCSI_CAP_SECTOR_SIZE: +			tgtp->at_dma_attr.dma_attr_granular = (uint_t)value; +			return (TRUE); + +		case SCSI_CAP_ARQ: +			if (whom) { +				tgtp->at_arq = value; +				return (TRUE); +			} +			break; + +		case SCSI_CAP_TOTAL_SECTORS: +			tgtp->at_total_sectors = value; +			return (TRUE); +	} +	return (FALSE); +} + + +/* + * + * SCSA tran_getcap entry point + * + */ + +static int +atapi_tran_getcap( +	struct scsi_address *ap, +	char *capstr, +	int whom) +{ +	struct ata_id	 ata_id; +	struct ata_id	*ata_idp; +	ata_ctl_t	*ata_ctlp; +	ata_drv_t	*ata_drvp; +	gtgt_t		*gtgtp; +	int		 rval = -1; + +	ADBG_TRACE(("atapi_tran_getcap entered\n")); + +	if (capstr == NULL || whom == 0) +		return (-1); + +	ata_ctlp = ADDR2CTL(ap); + +	switch (scsi_hba_lookup_capstr(capstr)) { +	case SCSI_CAP_ARQ: +		rval = TRUE; +		break; + +	case SCSI_CAP_INITIATOR_ID: +		rval = 7; +		break; + +	case SCSI_CAP_DMA_MAX: +		/* XXX - what should the real limit be?? */ +		/* limit to 64K ??? */ +		rval = 4096 * (ATA_DMA_NSEGS - 1); +		break; + +	case SCSI_CAP_GEOMETRY: +		/* Default geometry */ +		if (atapi_use_static_geometry) { +			rval = ATAPI_HEADS << 16 | ATAPI_SECTORS_PER_TRK; +			break; +		} + +		/* this code is currently not used */ + +		ata_drvp = CTL2DRV(ata_ctlp, ap->a_target, ap->a_lun); +		gtgtp = ADDR2GTGTP(ap); + +		/* +		 * retrieve the current IDENTIFY PACKET DEVICE info +		 */ +		if (!ata_queue_cmd(atapi_id_update, &ata_id, ata_ctlp, +			ata_drvp, gtgtp)) { +			ADBG_TRACE(("atapi_tran_getcap geometry failed")); +			return (0); +		} + +		/* +		 * save the new response data +		 */ +		ata_idp = &ata_drvp->ad_id; +		*ata_idp = ata_id; + +		switch ((ata_idp->ai_config >> 8) & 0xf) { +		case DTYPE_RODIRECT: +			rval = ATAPI_HEADS << 16 | ATAPI_SECTORS_PER_TRK; +			break; +		case DTYPE_DIRECT: +		case DTYPE_OPTICAL: +			rval = (ata_idp->ai_curheads << 16) | +				ata_idp->ai_cursectrk; +			break; +		default: +			rval = 0; +		} +		break; +	} + +	return (rval); +} + + + +/* + * + * SCSA tran_init_pkt entry point + * + */ + +static struct scsi_pkt * +atapi_tran_init_pkt( +	struct scsi_address *ap, +	struct scsi_pkt	*spktp, +	struct buf	*bp, +	int		 cmdlen, +	int		 statuslen, +	int		 tgtlen, +	int		 flags, +	int		(*callback)(caddr_t), +	caddr_t		 arg) +{ +	gtgt_t		*gtgtp = ADDR2GTGTP(ap); +	ata_tgt_t	*ata_tgtp = GTGTP2ATATGTP(gtgtp); +	ata_ctl_t	*ata_ctlp = ADDR2CTL(ap); +	ata_pkt_t	*ata_pktp; +	struct scsi_pkt	*new_spktp; +	ddi_dma_attr_t	*sg_attrp; +	int		 bytes; + +	ADBG_TRACE(("atapi_tran_init_pkt entered\n")); + + +	/* +	 * Determine whether to do PCI-IDE DMA setup, start out by +	 * assuming we're not. +	 */ +	sg_attrp = NULL; + +	if (bp == NULL) { +		/* no data to transfer */ +		goto skip_dma_setup; +	} + +	if (bp->b_bcount == 0) { +		/* no data to transfer */ +		goto skip_dma_setup; +	} + +	if ((GTGTP2ATADRVP(ADDR2GTGTP(ap))->ad_pciide_dma == ATA_DMA_OFF)) { +		goto skip_dma_setup; +	} + +	if (ata_dma_disabled) +		goto skip_dma_setup; + + +	/* +	 * The PCI-IDE DMA engine is brain-damaged and can't +	 * DMA non-aligned buffers. +	 */ +	if (((bp->b_flags & B_PAGEIO) == 0) && +	    ((uintptr_t)bp->b_un.b_addr) & PCIIDE_PRDE_ADDR_MASK) { +		/* +		 * if the virtual address isn't aligned, then the +		 * physical address also isn't aligned. +		 */ +		goto skip_dma_setup; +	} + +	/* +	 * It also insists that the byte count must be even. +	 */ +	if (bp->b_bcount & 1) { +		/* something odd here */ +		goto skip_dma_setup; +	} + +	/* +	 * Huzza! We're really going to do it +	 */ +	sg_attrp = &ata_tgtp->at_dma_attr; + + +skip_dma_setup: + +	/* +	 * Call GHD packet init function +	 */ + +	new_spktp = ghd_tran_init_pkt_attr(&ata_ctlp->ac_ccc, ap, spktp, bp, +		cmdlen, statuslen, tgtlen, flags, +		callback, arg, sizeof (ata_pkt_t), sg_attrp); + +	if (new_spktp == NULL) +		return (NULL); + +	ata_pktp = SPKT2APKT(new_spktp); +	ata_pktp->ap_cdbp = new_spktp->pkt_cdbp; +	ata_pktp->ap_statuslen = (uchar_t)statuslen; + +	/* reset data direction flags */ +	if (spktp) +		ata_pktp->ap_flags &= ~(AP_READ | AP_WRITE); + +	/* +	 * check for ARQ mode +	 */ +	if (atapi_arq_enable == TRUE && +		ata_tgtp->at_arq == TRUE && +		ata_pktp->ap_statuslen >= sizeof (struct scsi_arq_status)) { +		ADBG_TRACE(("atapi_tran_init_pkt ARQ\n")); +		ata_pktp->ap_scbp = +		    (struct scsi_arq_status *)new_spktp->pkt_scbp; +		ata_pktp->ap_flags |= AP_ARQ_ON_ERROR; +	} + +	/* +	 * fill these with zeros for ATA/ATAPI-4 compatibility +	 */ +	ata_pktp->ap_sec = 0; +	ata_pktp->ap_count = 0; + +	if (ata_pktp->ap_sg_cnt) { +		ASSERT(bp != NULL); +		/* determine direction to program the DMA engine later */ +		if (bp->b_flags & B_READ) { +			ata_pktp->ap_flags |= AP_READ; +		} else { +			ata_pktp->ap_flags |= AP_WRITE; +		} +		ata_pktp->ap_pciide_dma = TRUE; +		ata_pktp->ap_hicyl = 0; +		ata_pktp->ap_lwcyl = 0; +		return (new_spktp); +	} + +	/* +	 * Since we're not using DMA, we need to map the buffer into +	 * kernel address space +	 */ + +	ata_pktp->ap_pciide_dma = FALSE; +	if (bp && bp->b_bcount) { +		/* +		 * If this is a fresh request map the buffer and +		 * reset the ap_baddr pointer and the current offset +		 * and byte count. +		 * +		 * The ap_boffset is used to set the ap_v_addr ptr at +		 * the start of each I/O request. +		 * +		 * The ap_bcount is used to update ap_boffset when the +		 * target driver requests the next segment. +		 * +		 */ +		if (cmdlen) { +			bp_mapin(bp); +			ata_pktp->ap_baddr = bp->b_un.b_addr; +			ata_pktp->ap_bcount = 0; +			ata_pktp->ap_boffset = 0; +		} +		ASSERT(ata_pktp->ap_baddr != NULL); + +		/* determine direction for the PIO FSM */ +		if (bp->b_flags & B_READ) { +			ata_pktp->ap_flags |= AP_READ; +		} else { +			ata_pktp->ap_flags |= AP_WRITE; +		} + +		/* +		 * If the drive has the Single Sector bug, limit +		 * the transfer to a single sector. This assumes +		 * ATAPI CD drives always use 2k sectors. +		 */ +		if (GTGTP2ATADRVP(ADDR2GTGTP(ap))->ad_flags & AD_1SECTOR) { +			size_t resid; +			size_t tmp; + +			/* adjust offset based on prior request */ +			ata_pktp->ap_boffset += ata_pktp->ap_bcount; + +			/* compute number of bytes left to transfer */ +			resid = bp->b_bcount - ata_pktp->ap_boffset; + +			/* limit the transfer to 2k */ +			tmp = MIN(2048, resid); +			ata_pktp->ap_bcount = tmp; + +			/* tell target driver how much is left for next time */ +			new_spktp->pkt_resid = resid - tmp; +		} else { +			/* do the whole request in one swell foop */ +			ata_pktp->ap_bcount = bp->b_bcount; +			new_spktp->pkt_resid = 0; +		} + +	} else { +		ata_pktp->ap_baddr = NULL; +		ata_pktp->ap_bcount = 0; +		ata_pktp->ap_boffset = 0; +	} + +	/* +	 * determine the size of each partial data transfer +	 * to/from the drive +	 */ +	bytes = min(ata_pktp->ap_bcount, ATAPI_MAX_BYTES_PER_DRQ); +	ata_pktp->ap_hicyl = (uchar_t)(bytes >> 8); +	ata_pktp->ap_lwcyl = (uchar_t)bytes; +	return (new_spktp); +} + + +/* + * GHD ccballoc callback + * + *	Initializing the ata_pkt, and return the ptr to the gcmd_t to GHD. + * + */ + +/* ARGSUSED */ +int +atapi_ccballoc( +	gtgt_t	*gtgtp, +	gcmd_t	*gcmdp, +	int	 cmdlen, +	int	 statuslen, +	int	 tgtlen, +	int	 ccblen) + +{ +	ata_drv_t *ata_drvp = GTGTP2ATADRVP(gtgtp); +	ata_pkt_t *ata_pktp = GCMD2APKT(gcmdp); + +	ADBG_TRACE(("atapi_ccballoc entered\n")); + +	/* set the back ptr from the ata_pkt to the gcmd_t */ +	ata_pktp->ap_gcmdp = gcmdp; + +	/* check length of SCSI CDB is not larger than drive expects */ + +	if (cmdlen > ata_drvp->ad_cdb_len) { +		ADBG_WARN(("atapi_ccballoc: SCSI CDB too large!\n")); +		return (FALSE); +	} + +	/* +	 * save length of the SCSI CDB, and calculate CDB padding +	 * note that for convenience, padding is expressed in shorts. +	 */ + +	ata_pktp->ap_cdb_len = (uchar_t)cmdlen; +	ata_pktp->ap_cdb_pad = +		((unsigned)(ata_drvp->ad_cdb_len - cmdlen)) >> 1; + +	/* set up callback functions */ + +	ata_pktp->ap_start = atapi_fsm_start; +	ata_pktp->ap_intr = atapi_fsm_intr; +	ata_pktp->ap_complete = atapi_complete; + +	/* set-up for start */ + +	ata_pktp->ap_flags = AP_ATAPI; +	ata_pktp->ap_hd = ata_drvp->ad_drive_bits; +	ata_pktp->ap_cmd = ATC_PACKET; + +	return (TRUE); +} + + + +/* + * + * SCSA tran_destroy_pkt entry point + * + */ + +static void +atapi_tran_destroy_pkt( +	struct scsi_address *ap, +	struct scsi_pkt *spktp) +{ +	gcmd_t	  *gcmdp = PKTP2GCMDP(spktp); + +	ADBG_TRACE(("atapi_tran_destroy_pkt entered\n")); + +	if (gcmdp->cmd_dma_handle != NULL) { +		ghd_dmafree_attr(gcmdp); +	} + +	ghd_pktfree(&ADDR2CTL(ap)->ac_ccc, ap, spktp); +} + + + +/* + * + * GHD ccbfree callback function + * + */ + +/* ARGSUSED */ +void +atapi_ccbfree( +	gcmd_t *gcmdp) +{ +	ADBG_TRACE(("atapi_ccbfree entered\n")); + +	/* nothing to do */ +} + + +/* + * + * SCSA tran_dmafree entry point + * + */ + +/*ARGSUSED*/ +static void +atapi_tran_dmafree( +	struct scsi_address *ap, +	struct scsi_pkt *spktp) +{ +	gcmd_t	  *gcmdp = PKTP2GCMDP(spktp); + +	ADBG_TRACE(("atapi_tran_dmafree entered\n")); + +	if (gcmdp->cmd_dma_handle != NULL) { +		ghd_dmafree_attr(gcmdp); +	} +} + + + +/* + * + * SCSA tran_sync_pkt entry point + * + */ + +/*ARGSUSED*/ +static void +atapi_tran_sync_pkt( +	struct scsi_address *ap, +	struct scsi_pkt *spktp) +{ + +	ADBG_TRACE(("atapi_tran_sync_pkt entered\n")); + +	if (PKTP2GCMDP(spktp)->cmd_dma_handle != NULL) { +		ghd_tran_sync_pkt(ap, spktp); +	} +} + + + +/* + * + * SCSA tran_start entry point + * + */ + +/* ARGSUSED */ +static int +atapi_tran_start( +	struct scsi_address *ap, +	struct scsi_pkt *spktp) +{ +	ata_pkt_t *ata_pktp = SPKT2APKT(spktp); +	ata_drv_t *ata_drvp = APKT2DRV(ata_pktp); +	ata_ctl_t *ata_ctlp = ata_drvp->ad_ctlp; +	gcmd_t	  *gcmdp = APKT2GCMD(ata_pktp); +	int	   polled = FALSE; +	int	   rc; + +	ADBG_TRACE(("atapi_tran_start entered\n")); + +	/* +	 * Basic initialization performed each and every time a +	 * scsi_pkt is submitted. A single scsi_pkt may be submitted +	 * multiple times so this routine has to be idempotent. One +	 * time initializations don't belong here. +	 */ + +	/* +	 * The ap_v_addr pointer is incremented by the PIO data +	 * transfer routine as each word is transferred. Therefore, need +	 * to reset ap_v_addr here (rather than atapi_tran_init_pkt()) +	 * in case the target resubmits the same pkt multiple times +	 * (which is permitted by SCSA). +	 */ +	ata_pktp->ap_v_addr = ata_pktp->ap_baddr + ata_pktp->ap_boffset; + +	/* ap_resid is decremented as the data transfer progresses */ +	ata_pktp->ap_resid = ata_pktp->ap_bcount; + +	/* clear error flags */ +	ata_pktp->ap_flags &= (AP_ATAPI | AP_READ | AP_WRITE | AP_ARQ_ON_ERROR); +	spktp->pkt_reason = 0; +	spktp->pkt_state = 0; +	spktp->pkt_statistics = 0; + +	/* +	 * check for polling pkt +	 */ +	if (spktp->pkt_flags & FLAG_NOINTR) { +		polled = TRUE; +	} + +#ifdef ___just_ignore_unsupported_flags___ +	/* driver cannot accept tagged commands */ + +	if (spktp->pkt_flags & (FLAG_HTAG|FLAG_OTAG|FLAG_STAG)) { +		spktp->pkt_reason = CMD_TRAN_ERR; +		return (TRAN_BADPKT); +	} +#endif + +	/* call common transport routine */ + +	rc = ghd_transport(&ata_ctlp->ac_ccc, gcmdp, gcmdp->cmd_gtgtp, +		spktp->pkt_time, polled, NULL); + +	/* see if pkt was not accepted */ + +	if (rc != TRAN_ACCEPT) +		return (rc); + +	return (rc); +} + + +/* + * + * GHD packet complete callback + * + */ +/* ARGSUSED */ +static void +atapi_complete( +	ata_drv_t *ata_drvp, +	ata_pkt_t *ata_pktp, +	int do_callback) +{ +	struct scsi_pkt *spktp = APKT2SPKT(ata_pktp); +	struct scsi_status *scsi_stat = (struct scsi_status *)spktp->pkt_scbp; + +	ADBG_TRACE(("atapi_complete entered\n")); +	ADBG_TRANSPORT(("atapi_complete: pkt = 0x%p\n", ata_pktp)); + +	/* update resid */ + +	spktp->pkt_resid = ata_pktp->ap_resid; + +	if (ata_pktp->ap_flags & AP_SENT_CMD) { +		spktp->pkt_state |= +			STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; +	} +	if (ata_pktp->ap_flags & AP_XFERRED_DATA) { +		spktp->pkt_state |= STATE_XFERRED_DATA; +	} + +	if (ata_pktp->ap_flags & AP_GOT_STATUS) { +		spktp->pkt_state |= STATE_GOT_STATUS; +	} + +	/* check for fatal errors */ + +	if (ata_pktp->ap_flags & AP_TRAN_ERROR) { +		spktp->pkt_reason = CMD_TRAN_ERR; +	} else if (ata_pktp->ap_flags & AP_BUS_RESET) { +		spktp->pkt_reason = CMD_RESET; +		spktp->pkt_statistics |= STAT_BUS_RESET; +	} else if (ata_pktp->ap_flags & AP_DEV_RESET) { +		spktp->pkt_reason = CMD_RESET; +		spktp->pkt_statistics |= STAT_DEV_RESET; +	} else if (ata_pktp->ap_flags & AP_ABORT) { +		spktp->pkt_reason = CMD_ABORTED; +		spktp->pkt_statistics |= STAT_ABORTED; +	} else if (ata_pktp->ap_flags & AP_TIMEOUT) { +		spktp->pkt_reason = CMD_TIMEOUT; +		spktp->pkt_statistics |= STAT_TIMEOUT; +	} else { +		spktp->pkt_reason = CMD_CMPLT; +	} + +	/* non-fatal errors */ + +	if (ata_pktp->ap_flags & AP_ERROR) +		scsi_stat->sts_chk = 1; +	else +		scsi_stat->sts_chk = 0; + +	if (ata_pktp->ap_flags & AP_ARQ_ERROR) { +		ADBG_ARQ(("atapi_complete ARQ error 0x%p\n", ata_pktp)); +		spktp->pkt_reason = CMD_TRAN_ERR; + +	} else if (ata_pktp->ap_flags & AP_ARQ_OKAY) { +		static struct scsi_status zero_scsi_status = { 0 }; +		struct scsi_arq_status *arqp; + +		ADBG_ARQ(("atapi_complete ARQ okay 0x%p\n", ata_pktp)); +		spktp->pkt_state |= STATE_ARQ_DONE; +		arqp = ata_pktp->ap_scbp; +		arqp->sts_rqpkt_reason = CMD_CMPLT; +		arqp->sts_rqpkt_state = STATE_XFERRED_DATA; +		arqp->sts_rqpkt_status = zero_scsi_status; +		arqp->sts_rqpkt_resid = 0; +		arqp->sts_rqpkt_statistics = 0; + +	} + +	ADBG_TRANSPORT(("atapi_complete: reason = 0x%x stats = 0x%x " +	    "sts_chk = %d\n", spktp->pkt_reason, spktp->pkt_statistics, +	    scsi_stat->sts_chk)); + +	if (do_callback && (spktp->pkt_comp)) +		(*spktp->pkt_comp)(spktp); +} + + + +/* + * Update the IDENTIFY PACKET DEVICE info + */ + +static int +atapi_id_update( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	caddr_t		 ioaddr1 = ata_ctlp->ac_ioaddr1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	caddr_t		 ioaddr2 = ata_ctlp->ac_ioaddr2; +	int	rc; + +	/* +	 * select the appropriate drive and LUN +	 */ +	ddi_put8(io_hdl1, (uchar_t *)ioaddr1 + AT_DRVHD, +		ata_drvp->ad_drive_bits); +	ATA_DELAY_400NSEC(io_hdl2, ioaddr2); + +	/* +	 * make certain the drive is selected, and wait for not busy +	 */ +	if (!ata_wait(io_hdl2, ioaddr2, ATS_DRDY, ATS_BSY, 5 * 1000000)) { +		ADBG_ERROR(("atapi_id_update: select failed\n")); +		ata_pktp->ap_flags |= AP_ERROR; +		return (ATA_FSM_RC_FINI); +	} + +	rc = atapi_id(ata_ctlp->ac_iohandle1, ata_ctlp->ac_ioaddr1, +		ata_ctlp->ac_iohandle2, ata_ctlp->ac_ioaddr2, +		(struct ata_id *)ata_pktp->ap_v_addr); + +	if (!rc) { +		ata_pktp->ap_flags |= AP_ERROR; +	} else { +		ata_pktp->ap_flags |= AP_XFERRED_DATA; +	} +	return (ATA_FSM_RC_FINI); +} + + + +/* + * Both drives on the controller share a common pkt to do + * ARQ processing. Therefore the pkt is only partially + * initialized here. The rest of initialization occurs + * just before starting the ARQ pkt when an error is + * detected. + */ + +void +atapi_init_arq( +	ata_ctl_t *ata_ctlp) +{ +	ata_pkt_t *arq_pktp = ata_ctlp->ac_arq_pktp; + +	arq_pktp->ap_cdbp = ata_ctlp->ac_arq_cdb; +	arq_pktp->ap_cdb_len = sizeof (ata_ctlp->ac_arq_cdb); +	arq_pktp->ap_start = atapi_fsm_start; +	arq_pktp->ap_intr = atapi_fsm_intr; +	arq_pktp->ap_complete = atapi_complete; +	arq_pktp->ap_flags = AP_ATAPI; +	arq_pktp->ap_cmd = ATC_PACKET; + +	ata_ctlp->ac_arq_cdb[0] = SCMD_REQUEST_SENSE; +} diff --git a/usr/src/uts/intel/io/dktp/controller/ata/atapi.h b/usr/src/uts/intel/io/dktp/controller/ata/atapi.h new file mode 100644 index 0000000000..d3be006682 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/atapi.h @@ -0,0 +1,132 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 1997 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ATAPI_H +#define	_ATAPI_H + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#ifdef	__cplusplus +extern "C" { +#endif + + +/* + * Additional atapi status bits (redefinitions) + */ +#define	ATE_ILI		0x01    /* Illegal length indication		*/ +#define	ATE_EOM		0x02	/* End of media detected		*/ +#define	ATE_MCR		0x08	/* Media change requested		*/ +#define	ATS_SERVICE	0x10	/* overlap operation needs service	*/ +#define	ATS_SENSE_KEY	0xf0	/* 4 bit sense key -see ata_sense_table */ + +#define	ATS_SENSE_KEY_SHIFT 4	/* shift to get to ATS_SENSE_KEY	*/ + +/* + * Status bits from ATAPI Interrupt reason register (AT_COUNT) register + */ +#define	ATI_COD		0x01    /* Command or Data			*/ +#define	ATI_IO		0x02    /* IO direction 			*/ +#define	ATI_RELEASE	0x04	/* Release for ATAPI overlap		*/ + +/* ATAPI feature reg definitions */ + +#define	ATF_OVERLAP	0x02 + +/* + * ATAPI IDENTIFY_DRIVE configuration word + */ + +#define	ATAPI_ID_CFG_PKT_SZ   0x3 +#define	ATAPI_ID_CFG_PKT_12B  0x0 +#define	ATAPI_ID_CFG_PKT_16B  0x1 +#define	ATAPI_ID_CFG_DRQ_TYPE 0x60 +#define	ATAPI_ID_CFG_DRQ_INTR 0x20 +#define	ATAPI_ID_CFG_DEV_TYPE 0x0f00 +#define	ATAPI_ID_CFG_DEV_SHFT 8 + +/* + * ATAPI IDENTIFY_DRIVE capabilities word + */ + +#define	ATAPI_ID_CAP_DMA	0x0100 +#define	ATAPI_ID_CAP_OVERLAP	0x2000 + +/* ATAPI SET FEATURE commands */ + +#define	ATAPI_FEAT_RELEASE_INTR		0x5d +#define	ATAPI_FEAT_SERVICE_INTR		0x5e + +/* + * ATAPI bits + */ +#define	ATAPI_SIG_HI	0xeb		/* in high cylinder register	*/ +#define	ATAPI_SIG_LO	0x14		/* in low cylinder register	*/ + + +#define	ATAPI_SECTOR_SIZE	2048 +#define	ATAPI_MAX_BYTES_PER_DRQ	0xf800 /* 16 bits - 2KB  ie 62KB */ +#define	ATAPI_HEADS		64 +#define	ATAPI_SECTORS_PER_TRK   32 + +/* Useful macros */ + +#define	TRAN2CTL(tran)  ((ata_ctl_t *)((tran)->tran_hba_private)) +#define	ADDR2CTL(ap)    (TRAN2CTL(ADDR2TRAN(ap))) + +#define	SPKT2APKT(spkt)	(GCMD2APKT(PKTP2GCMDP(spkt))) +#define	APKT2SPKT(apkt)	(GCMDP2PKTP(APKT2GCMD(apkt))) + +/* public function prototypes */ + +int	atapi_attach(ata_ctl_t *ata_ctlp); +void	atapi_detach(ata_ctl_t *ata_ctlp); +void	atapi_init_arq(ata_ctl_t *ata_ctlp); +int	atapi_init_drive(ata_drv_t *ata_drvp); +void	atapi_uninit_drive(ata_drv_t *ata_drvp); + +int	atapi_id(ddi_acc_handle_t io_hdl1, caddr_t ioaddr1, +		ddi_acc_handle_t io_hdl2, caddr_t ioaddr2, struct ata_id *buf); +int	atapi_signature(ddi_acc_handle_t io_hdl, caddr_t ioaddr); + +int	atapi_ccballoc(gtgt_t *gtgtp, gcmd_t *gcmdp, int cmdlen, +		int statuslen, int tgtlen, int ccblen); +void	atapi_ccbfree(gcmd_t *gcmdp); + + +int	atapi_fsm_intr(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +		ata_pkt_t *ata_pktp); +int	atapi_fsm_start(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +		ata_pkt_t *ata_pktp); +void	atapi_fsm_reset(ata_ctl_t *ata_ctlp); + + + +#ifdef	__cplusplus +} +#endif + +#endif /* _ATAPI_H */ diff --git a/usr/src/uts/intel/io/dktp/controller/ata/atapi_fsm.c b/usr/src/uts/intel/io/dktp/controller/ata/atapi_fsm.c new file mode 100644 index 0000000000..fc4f8355b6 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/atapi_fsm.c @@ -0,0 +1,884 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +/* + * Finite State Machines for ATA controller and ATAPI devices + */ + +#include <sys/types.h> + +#include "ata_common.h" +#include "atapi.h" + +/* + * Local functions + */ +static	int	atapi_start_cmd(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +				ata_pkt_t *ata_pktp); +static	void	atapi_send_cdb(ata_ctl_t *ata_ctlp, ata_pkt_t *ata_pktp); +static	void	atapi_start_dma(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp, +				ata_pkt_t *ata_pktp); +static	void	atapi_pio_data_in(ata_ctl_t *ata_ctlp, ata_pkt_t *ata_pktp); +static	void	atapi_pio_data_out(ata_ctl_t *ata_ctlp, ata_pkt_t *ata_pktp); +static	void	atapi_status(ata_ctl_t *ata_ctlp, ata_pkt_t *ata_pktp, +				uchar_t status, int dma_complete); +static	void	atapi_fsm_error(ata_ctl_t *ata_ctlp, uchar_t state, +				uchar_t event); + + + + +static void +atapi_fsm_error( +	ata_ctl_t *ata_ctlp, +	uchar_t	   state, +	uchar_t	   event) +{ +	ADBG_ERROR(("atapi protocol error: 0x%p 0x%x 0x%x\n", +	    ata_ctlp->ac_data, state, event)); +} + + +/* + * + *  IO  CoD  DRQ + *  --  ---  --- + *   0    0    0  == 0 invalid + *   0    0    1  == 1 Data to device + *   0    1    0  == 2 Idle + *   0    1    1  == 3 Send ATAPI CDB to device + *   1    0    0  == 4 invalid + *   1    0    1  == 5 Data from device + *   1    1    0  == 6 Status ready + *   1    1    1  == 7 Future use + * + */ + +/* + * Given the current state and the current event this + * table determines what action to take. Note, in the actual + * table I've left room for the invalid event codes: 0, 2, and 7. + * + *		+----------------------------------------------------- + *		|		Current Event + *		| + *	State	|	dataout	idle	cdb	datain	status + *		|	1	2	3	5	6 + *		|----------------------------------------------------- + *	idle	|	sendcmd	sendcmd	sendcmd	sendcmd	sendcmd + *	cmd	|	*	 *	sendcdb	*	read-err-code + *	cdb	|	xfer-out nada	nada	xfer-in read-err-code + *	datain	|	*	 *	*	xfer-in	read-err-code + *	dataout	|	xfer-out *	*	*	read-err-code + *	DMA	|	*	 *	*	*	read-err-code + * + */ + +uchar_t	atapi_PioAction[ATAPI_NSTATES][ATAPI_NEVENTS] = { +/* invalid dataout idle	  cdb	  invalid datain  status  future */ +{ A_NADA, A_NADA, A_NADA, A_NADA, A_NADA, A_NADA, A_NADA, A_NADA }, /* Idle */ +{ A_NADA, A_NADA, A_NADA, A_CDB,  A_NADA, A_NADA, A_RE,   A_NADA }, /* Cmd */ +{ A_REX,  A_OUT,  A_NADA, A_NADA, A_IDLE, A_IN,   A_RE,   A_UNK  }, /* Cdb */ +{ A_REX,  A_UNK,  A_IDLE, A_UNK,  A_IDLE, A_IN,   A_RE,   A_UNK  }, /* DtaIn */ +{ A_REX,  A_OUT,  A_IDLE, A_UNK,  A_IDLE, A_UNK,  A_RE,   A_UNK  }, /* DtaOut */ +{ A_REX,  A_UNK,  A_UNK,  A_UNK,  A_UNK,  A_UNK,  A_RE,   A_UNK  }  /* DmaAct */ +}; + +/* + * + * Give the current state and the current event this table + * determines the new state of the device. + * + *		+---------------------------------------------- + *		|		Current Event + *		| + *	State	|	dataout	idle	cdb	datain	status + *		|---------------------------------------------- + *	idle	|	cmd	cmd	cmd	cmd	cmd + *	cmd	|	*	*	cdb	*	* + *	cdb	|	dataout	cdb	cdb	datain	(idle) + *	datain	|	*	*	*	datain	(idle) + *	dataout	|	dataout	*	*	*	(idle) + *	DMA	|	DMA	DMA	DMA	DMA	(idle) + * + * + * Note: the states enclosed in parens "(state)", are the accept states + * for this FSM. A separate table is used to encode the done + * states rather than extra state codes. + * + */ + +uchar_t	atapi_PioNextState[ATAPI_NSTATES][ATAPI_NEVENTS] = { +/* invalid dataout idle	  cdb	  invalid datain  status  future */ +{ S_IDLE, S_IDLE, S_IDLE, S_IDLE, S_IDLE, S_IDLE, S_IDLE, S_IDLE}, /* idle */ +{ S_CDB,  S_CDB,  S_CDB,  S_CDB,  S_CDB,  S_CDB,  S_IDLE, S_X   }, /* cmd */ +{ S_IDLE, S_OUT,  S_CDB,  S_CDB,  S_CDB,  S_IN,   S_IDLE, S_X   }, /* cdb */ +{ S_IDLE, S_X,    S_IN,   S_X,    S_IN,   S_IN,   S_IDLE, S_X   }, /* datain */ +{ S_IDLE, S_OUT,  S_OUT,  S_X,    S_OUT,  S_X,    S_IDLE, S_X   }, /* dataout */ +{ S_IDLE, S_DMA,  S_DMA,  S_DMA,  S_DMA,  S_DMA,  S_IDLE, S_DMA }  /* dmaActv */ +}; + + +static int +atapi_start_cmd( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; + +	/* +	 * Bug 1256489: +	 * +	 * If AC_BSY_WAIT is set, wait for controller to be not busy, +	 * before issuing a command.  If AC_BSY_WAIT is not set, +	 * skip the wait.  This is important for laptops that do +	 * suspend/resume but do not correctly wait for the busy bit to +	 * drop after a resume. +	 */ + +	if (ata_ctlp->ac_timing_flags & AC_BSY_WAIT) { +		if (!ata_wait(io_hdl2, ata_ctlp->ac_ioaddr2, +			0, ATS_BSY, 5000000)) { +			ADBG_WARN(("atapi_start: BSY too long!\n")); +			ata_pktp->ap_flags |= AP_ERROR; +			return (ATA_FSM_RC_BUSY); +		} +	} + +	/* +	 * Select the drive +	 */ +	ddi_put8(io_hdl1, ata_ctlp->ac_drvhd, ata_pktp->ap_hd); +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	/* +	 * make certain the drive selected +	 */ +	if (!ata_wait(io_hdl2,  ata_ctlp->ac_ioaddr2, 0, ATS_BSY, 5000000)) { +		ADBG_ERROR(("atapi_start_cmd: drive select failed\n")); +		return (ATA_FSM_RC_BUSY); +	} + +	/* +	 * Always make certain interrupts are enabled. It's been reported +	 * (but not confirmed) that some notebook computers don't +	 * clear the interrupt disable bit after being resumed. The +	 * easiest way to fix this is to always clear the disable bit +	 * before every command. +	 */ +	ddi_put8(io_hdl2, ata_ctlp->ac_devctl, ATDC_D3); + +	ddi_put8(io_hdl1, ata_ctlp->ac_lcyl, ata_pktp->ap_lwcyl); +	ddi_put8(io_hdl1, ata_ctlp->ac_hcyl, ata_pktp->ap_hicyl); +	ddi_put8(io_hdl1, ata_ctlp->ac_sect, ata_pktp->ap_sec); +	ddi_put8(io_hdl1, ata_ctlp->ac_count, ata_pktp->ap_count); + +	if (ata_pktp->ap_pciide_dma) { + +		ASSERT((ata_pktp->ap_flags & (AP_READ | AP_WRITE)) != 0); + +		/* +		 * DMA but no Overlap +		 */ +		ddi_put8(io_hdl1, ata_ctlp->ac_feature, ATF_ATAPI_DMA); + +		/* +		 * copy the Scatter/Gather list to the controller's +		 * Physical Region Descriptor Table +		 */ +		ata_pciide_dma_setup(ata_ctlp, ata_pktp->ap_sg_list, +			ata_pktp->ap_sg_cnt); +	} else { +		/* +		 * no DMA and no Overlap +		 */ +		ddi_put8(io_hdl1, ata_ctlp->ac_feature, 0); +	} + +	/* +	 * This next one sets the device in motion +	 */ +	ddi_put8(io_hdl1, ata_ctlp->ac_cmd, ata_pktp->ap_cmd); + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	if (!(ata_drvp->ad_flags & AD_NO_CDB_INTR)) { +		/* +		 * the device will send me an interrupt when it's +		 * ready for the packet +		 */ +		return (ATA_FSM_RC_OKAY); +	} + +	/* else */ + +	/* +	 * If we don't receive an interrupt requesting the scsi CDB, +	 * we must poll for DRQ, and then send out the CDB. +	 */ + +	/* +	 * Wait for DRQ before sending the CDB. Bailout early +	 * if an error occurs. +	 * +	 * I'm not certain what the correct timeout should be. +	 */ +	if (ata_wait3(io_hdl2, ata_ctlp->ac_ioaddr2, +		ATS_DRQ, ATS_BSY, /* okay */ +		ATS_ERR, ATS_BSY, /* cmd failed */ +		ATS_DF,  ATS_BSY, /* cmd failed */ +		4000000)) { +		/* got good status */ +		return (ATA_FSM_RC_INTR); +	} + +	ADBG_WARN(("atapi_start_cmd: 0x%x status 0x%x error 0x%x\n", +		ata_pktp->ap_cmd, +		ddi_get8(io_hdl2,  ata_ctlp->ac_altstatus), +		ddi_get8(io_hdl1, ata_ctlp->ac_error))); + +	return (ATA_FSM_RC_INTR); +} + + +/* + * + * Send the SCSI CDB to the ATAPI device + * + */ + +static void +atapi_send_cdb( +	ata_ctl_t	*ata_ctlp, +	ata_pkt_t	*ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	int		 padding; + +	ADBG_TRACE(("atapi_send_cdb entered\n")); + +	/* +	 * send the CDB to the drive +	 */ +	ddi_rep_put16(io_hdl1, (ushort_t *)ata_pktp->ap_cdbp, ata_ctlp->ac_data, +		ata_pktp->ap_cdb_len >> 1, DDI_DEV_NO_AUTOINCR); + +	/* +	 * pad to ad_cdb_len bytes +	 */ + +	padding = ata_pktp->ap_cdb_pad; + +	while (padding) { +		ddi_put16(io_hdl1, ata_ctlp->ac_data, 0); +		padding--; +	} + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +#ifdef ATA_DEBUG_XXX +	{ +		uchar_t	*cp = ata_pktp->ap_cdbp; + +		ADBG_TRANSPORT(("\tatapi scsi cmd (%d bytes):\n ", +				ata_pktp->ap_cdb_len)); +		ADBG_TRANSPORT(("\t\t 0x%x 0x%x 0x%x 0x%x\n", +			cp[0], cp[1], cp[2], cp[3])); +		ADBG_TRANSPORT(("\t\t 0x%x 0x%x 0x%x 0x%x\n", +			cp[4], cp[5], cp[6], cp[7])); +		ADBG_TRANSPORT(("\t\t 0x%x 0x%x 0x%x 0x%x\n", +			cp[8], cp[9], cp[10], cp[11])); +	} +#endif + +	ata_pktp->ap_flags |= AP_SENT_CMD; +} + + + +/* + * Start the DMA engine + */ + +/* ARGSUSED */ +static void +atapi_start_dma( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	uchar_t		 rd_wr; + +	/* +	 * Determine the direction. This may look backwards +	 * but the command bit programmed into the DMA engine +	 * specifies the type of operation the engine performs +	 * on the PCI bus (not the ATA bus). Therefore when +	 * transferring data from the device to system memory, the +	 * DMA engine performs PCI Write operations. +	 */ +	if (ata_pktp->ap_flags & AP_READ) +		rd_wr = PCIIDE_BMICX_RWCON_WRITE_TO_MEMORY; +	else +		rd_wr = PCIIDE_BMICX_RWCON_READ_FROM_MEMORY; + +	/* +	 * Start the DMA engine +	 */ +	ata_pciide_dma_start(ata_ctlp, rd_wr); +} + + + +/* + * Transfer the data from the device + * + * Note: the atapi_pio_data_in() and atapi_pio_data_out() functions + * are complicated a lot by the requirement to handle an odd byte count. + * The only device we've seen which does this is the Hitachi CDR-7730. + * See bug ID 1214595. It's my understanding that Dell stopped shipping + * that drive after discovering all the problems it caused, so it may + * be impossible to find one for any sort of regression test. + * + * In the future, ATAPI tape drives will also probably support odd byte + * counts so this code will be excersized more often. + * + */ + +static void +atapi_pio_data_in( +	ata_ctl_t	*ata_ctlp, +	ata_pkt_t	*ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	int		 drive_bytes; +	int		 xfer_bytes; +	int		 xfer_words; + +	ata_pktp->ap_flags |= AP_XFERRED_DATA; + +	/* +	 * Get the device's byte count for this transfer +	 */ +	drive_bytes = ((int)ddi_get8(io_hdl1, ata_ctlp->ac_hcyl) << 8) +			+ ddi_get8(io_hdl1, ata_ctlp->ac_lcyl); + +	/* +	 * Determine actual number I'm going to transfer. My +	 * buffer might have fewer bytes than what the device +	 * expects or handles on each interrupt. +	 */ +	xfer_bytes = min(ata_pktp->ap_resid, drive_bytes); + +	ASSERT(xfer_bytes >= 0); + +	/* +	 * Round down my transfer count to whole words so that +	 * if the transfer count is odd it's still handled correctly. +	 */ +	xfer_words = xfer_bytes / 2; + +	if (xfer_words) { +		int	byte_count = xfer_words * 2; + +		ddi_rep_get16(io_hdl1, (ushort_t *)ata_pktp->ap_v_addr, +			ata_ctlp->ac_data, xfer_words, DDI_DEV_NO_AUTOINCR); + +		ata_pktp->ap_v_addr += byte_count; +		drive_bytes -= byte_count; +	} + +	/* +	 * Handle possible odd byte at end. Read a 16-bit +	 * word but discard the high-order byte. +	 */ +	if (xfer_bytes & 1) { +		ushort_t tmp_word; + +		tmp_word = ddi_get16(io_hdl1, ata_ctlp->ac_data); +		*ata_pktp->ap_v_addr++ = tmp_word & 0xff; +		drive_bytes -= 2; +	} + +	ata_pktp->ap_resid -= xfer_bytes; + +	ADBG_TRANSPORT(("atapi_pio_data_in: read 0x%x bytes\n", xfer_bytes)); + +	/* +	 * Discard any unwanted data. +	 */ +	if (drive_bytes > 0) { +		ADBG_TRANSPORT(("atapi_pio_data_in: dump 0x%x bytes\n", +				drive_bytes)); + +		/* rounded up if the drive_bytes count is odd */ +		for (; drive_bytes > 0; drive_bytes -= 2) +			(void) ddi_get16(io_hdl1, ata_ctlp->ac_data); +	} + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); +} + + +/* + * Transfer the data to the device + */ + +static void +atapi_pio_data_out( +	ata_ctl_t	*ata_ctlp, +	ata_pkt_t	*ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; +	int		 drive_bytes; +	int		 xfer_bytes; +	int		 xfer_words; + +	ata_pktp->ap_flags |= AP_XFERRED_DATA; + +	/* +	 * Get the device's byte count for this transfer +	 */ +	drive_bytes = ((int)ddi_get8(io_hdl1, ata_ctlp->ac_hcyl) << 8) +			+ ddi_get8(io_hdl1, ata_ctlp->ac_lcyl); + +	/* +	 * Determine actual number I'm going to transfer. My +	 * buffer might have fewer bytes than what the device +	 * expects or handles on each interrupt. +	 */ +	xfer_bytes = min(ata_pktp->ap_resid, drive_bytes); + +	/* +	 * Round down my transfer count to whole words so that +	 * if the transfer count is odd it's handled correctly. +	 */ +	xfer_words = xfer_bytes / 2; + +	if (xfer_words) { +		int	byte_count = xfer_words * 2; + +		ddi_rep_put16(io_hdl1, (ushort_t *)ata_pktp->ap_v_addr, +			ata_ctlp->ac_data, xfer_words, DDI_DEV_NO_AUTOINCR); +		ata_pktp->ap_v_addr += byte_count; +	} + +	/* +	 * If odd byte count, transfer the last +	 * byte. Use a tmp so that I don't run off +	 * the end off the buffer and possibly page +	 * fault. +	 */ +	if (xfer_bytes & 1) { +		ushort_t tmp_word; + +		/* grab the last unsigned byte and widen it to 16-bits */ +		tmp_word = *ata_pktp->ap_v_addr++; +		ddi_put16(io_hdl1, ata_ctlp->ac_data, tmp_word); +	} + +	ata_pktp->ap_resid -= xfer_bytes; + +	ADBG_TRANSPORT(("atapi_pio_data_out: wrote 0x%x bytes\n", xfer_bytes)); + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); +} + + +/* + * + * check status of completed command + * + */ +static void +atapi_status( +	ata_ctl_t	*ata_ctlp, +	ata_pkt_t	*ata_pktp, +	uchar_t		 status, +	int		 dma_completion) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; + +	ata_pktp->ap_flags |= AP_GOT_STATUS; + +	if (status & (ATS_DF | ATS_ERR)) { +		ata_pktp->ap_flags |= AP_ERROR; +	} + +	if (ata_pktp->ap_flags & AP_ERROR) { +		ata_pktp->ap_status = status; +		ata_pktp->ap_error = ddi_get8(io_hdl1, ata_ctlp->ac_error); +	} + + +	/* +	 * If the DMA transfer failed leave the resid set to +	 * the original byte count. The target driver has +	 * to do a REQUEST SENSE to get the true residual +	 * byte count. Otherwise, it all transferred so update +	 * the flags and residual byte count. +	 */ +	if (dma_completion && !(ata_pktp->ap_flags & AP_TRAN_ERROR)) { +		ata_pktp->ap_flags |= AP_XFERRED_DATA; +		ata_pktp->ap_resid = 0; +	} +} + + +static void +atapi_device_reset( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2; + +	/* select the drive */ +	ddi_put8(io_hdl1, ata_ctlp->ac_drvhd, ata_drvp->ad_drive_bits); +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	/* issue atapi DEVICE RESET */ +	ddi_put8(io_hdl1, ata_ctlp->ac_cmd, ATC_DEVICE_RESET); + +	/* wait for the busy bit to settle */ +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	/* +	 * Re-select the drive (this is probably only necessary +	 * when resetting drive 1). +	 */ +	ddi_put8(io_hdl1, ata_ctlp->ac_drvhd, ata_drvp->ad_drive_bits); +	ATA_DELAY_400NSEC(io_hdl2, ata_ctlp->ac_ioaddr2); + +	/* allow the drive the full 6 seconds to respond */ +	/* LINTED */ +	if (!ata_wait(io_hdl2, ata_ctlp->ac_ioaddr2, 0, ATS_BSY, 6 * 1000000)) { +		ADBG_WARN(("atapi_device_reset: still busy\n")); +		/* +		 * It's not clear to me what to do at this point, +		 * the drive might be dead or might eventually +		 * recover. For now just ignore it and continue +		 * to attempt to use the drive. +		 */ +	} +} + + + +void +atapi_fsm_reset(ata_ctl_t *ata_ctlp) +{ +	ata_drv_t *ata_drvp; +	int	   drive; + +	/* +	 * reset drive drive 0 and the drive 1 +	 */ +	for (drive = 0; drive <= 1; drive++) { +		ata_drvp = CTL2DRV(ata_ctlp, drive, 0); +		if (ata_drvp && ATAPIDRV(ata_drvp)) { +			ata_drvp->ad_state = S_IDLE; +			atapi_device_reset(ata_ctlp, ata_drvp); +		} +	} +} + + +int +atapi_fsm_start( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	int		 rc; + +	ADBG_TRACE(("atapi_start entered\n")); +	ADBG_TRANSPORT(("atapi_start: pkt = 0x%p\n", ata_pktp)); + +	/* +	 * check for valid state +	 */ +	if (ata_drvp->ad_state != S_IDLE) { +		ADBG_ERROR(("atapi_fsm_start not idle 0x%x\n", +			    ata_drvp->ad_state)); +		return (ATA_FSM_RC_BUSY); +	} else { +		ata_drvp->ad_state = S_CMD; +	} + +	rc = atapi_start_cmd(ata_ctlp, ata_drvp, ata_pktp); + +	switch (rc) { +	case ATA_FSM_RC_OKAY: +		/* +		 * The command started okay. Just return. +		 */ +		break; +	case ATA_FSM_RC_INTR: +		/* +		 * Got Command Phase. The upper layer will send +		 * the cdb by faking an interrupt. +		 */ +		break; +	case ATA_FSM_RC_FINI: +		/* +		 * command completed immediately, stick on done q +		 */ +		break; +	case ATA_FSM_RC_BUSY: +		/* +		 * The command wouldn't start, tell the upper layer to +		 * stick this request on the done queue. +		 */ +		ata_drvp->ad_state = S_IDLE; +		return (ATA_FSM_RC_BUSY); +	} +	return (rc); +} + +/* + * + * All interrupts on an ATAPI device come through here. + * This function determines what to do next, based on + * the current state of the request and the drive's current + * status bits.  See the FSM tables at the top of this file. + * + */ + +int +atapi_fsm_intr( +	ata_ctl_t	*ata_ctlp, +	ata_drv_t	*ata_drvp, +	ata_pkt_t	*ata_pktp) +{ +	ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1; +	uchar_t		 status; +	uchar_t		 intr_reason; +	uchar_t		 state; +	uchar_t		 event; +	uchar_t		 action; + + +	/* +	 * get the prior state +	 */ +	state = ata_drvp->ad_state; + +	/* +	 * If doing DMA, then: +	 * +	 *	1. halt the DMA engine +	 *	2. reset the interrupt and error latches +	 *	3. reset the drive's IRQ. +	 * +	 * I think the order of these operations must be +	 * exactly as listed. Otherwise we the PCI-IDE +	 * controller can hang or we can miss the next interrupt +	 * edge. +	 * +	 */ +	switch (state) { +	case S_DMA: +		ASSERT(ata_pktp->ap_pciide_dma == TRUE); +		/* +		 * Halt the DMA engine. When we reach this point +		 * we already know for certain that the device has +		 * an interrupt pending since the ata_get_status() +		 * function already checked the PCI-IDE interrupt +		 * status bit. +		 */ +		ata_pciide_dma_stop(ata_ctlp); +		/*FALLTHRU*/ +	case S_IDLE: +	case S_CMD: +	case S_CDB: +	case S_IN: +	case S_OUT: +		break; +	} + + +	/* +	 * Clear the PCI-IDE latches and the drive's IRQ +	 */ +	status = ata_get_status_clear_intr(ata_ctlp, ata_pktp); + +	/* +	 * some non-compliant (i.e., NEC) drives don't +	 * set ATS_BSY within 400 nsec. and/or don't keep +	 * it asserted until they're actually non-busy. +	 * There's a small window between reading the alt_status +	 * and status registers where the drive might "bounce" +	 * the ATS_BSY bit. +	 */ +	if (status & ATS_BSY) +		return (ATA_FSM_RC_BUSY); + +	/* +	 * get the interrupt reason code +	 */ +	intr_reason = ddi_get8(io_hdl1, ata_ctlp->ac_count); + +	/* +	 * encode the status and interrupt reason bits +	 * into an event code which is used to index the +	 * FSM tables +	 */ +	event = ATAPI_EVENT(status, intr_reason); + +	/* +	 * determine the action for this event +	 */ +	action = atapi_PioAction[state][event]; + +	/* +	 * determine the new state +	 */ +	ata_drvp->ad_state = atapi_PioNextState[state][event]; + +	switch (action) { +	default: +	case A_UNK: +		/* +		 * invalid state +		 */ +/* + * ??? this shouldn't happen. ??? + *	if there's an active command on + *	this device, the pkt timer should eventually clear the + *	device. I might try sending a DEVICE-RESET here to speed + *	up the error recovery except that DEVICE-RESET is kind of + *	complicated to implement correctly because if I send a + *	DEVICE-RESET to drive 1 it deselects itself. + */ +		ADBG_WARN(("atapi_fsm_intr: Unsupported intr\n")); +		break; + +	case A_NADA: +		drv_usecwait(100); +		break; + +	case A_CDB: +		/* +		 * send out atapi pkt +		 */ +		atapi_send_cdb(ata_ctlp, ata_pktp); + +		/* +		 * start the DMA engine if necessary and change +		 * the state variable to reflect not doing PIO +		 */ +		if (ata_pktp->ap_pciide_dma) { +			atapi_start_dma(ata_ctlp, ata_drvp, ata_pktp); +			ata_drvp->ad_state = S_DMA; +		} +		break; + +	case A_IN: +		if (!(ata_pktp->ap_flags & AP_READ)) { +			/* +			 * maybe this was a spurious interrupt, just +			 * spin for a bit and see if the drive +			 * recovers +			 */ +			atapi_fsm_error(ata_ctlp, state, event); +			drv_usecwait(100); +			break; +		} +		/* +		 * read in the data +		 */ +		if (!ata_pktp->ap_pciide_dma) { +			atapi_pio_data_in(ata_ctlp, ata_pktp); +		} +		break; + +	case A_OUT: +		if (!(ata_pktp->ap_flags & AP_WRITE)) { +			/* spin for a bit and see if the drive recovers */ +			atapi_fsm_error(ata_ctlp, state, event); +			drv_usecwait(100); +			break; +		} +		/* +		 * send out data +		 */ +		if (!ata_pktp->ap_pciide_dma) { +			atapi_pio_data_out(ata_ctlp, ata_pktp); +		} +		break; + +	case A_IDLE: +		/* +		 * The DRQ bit deasserted before or between the data +		 * transfer phases. +		 */ +		if (!ata_drvp->ad_bogus_drq) { +			ata_drvp->ad_bogus_drq = TRUE; +			atapi_fsm_error(ata_ctlp, state, event); +		} +		drv_usecwait(100); +		break; + +	case A_RE: +		/* +		 * If we get here, a command has completed! +		 * +		 * check status of completed command +		 */ +		atapi_status(ata_ctlp, ata_pktp, status, +			(state == S_DMA) ? TRUE : FALSE); + +		return (ATA_FSM_RC_FINI); + +	case A_REX: +		/* +		 * some NEC drives don't report the right interrupt +		 * reason code for the status phase +		 */ +		if (!ata_drvp->ad_nec_bad_status) { +			ata_drvp->ad_nec_bad_status = TRUE; +			atapi_fsm_error(ata_ctlp, state, event); +			drv_usecwait(100); +		} +		atapi_status(ata_ctlp, ata_pktp, status, +			(state == S_DMA) ? TRUE : FALSE); +		return (ATA_FSM_RC_FINI); + +	} +	return (ATA_FSM_RC_OKAY); +} diff --git a/usr/src/uts/intel/io/dktp/controller/ata/capacity.notes.txt b/usr/src/uts/intel/io/dktp/controller/ata/capacity.notes.txt new file mode 100644 index 0000000000..1b720071d4 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/capacity.notes.txt @@ -0,0 +1,178 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +# +# Copyright 1999 Sun Microsystems, Inc.  All rights reserved. +# Use is subject to license terms. +#  + +# +#ident	"%Z%%M%	%I%	%E% SMI" +# + +Dan Mick, 2/16/1999 + +I had to come up with some sort of synthetic device geometry in the  +case that a drive supports LBA access and therefore the BIOS's geometry +may be wrong or too small. + +In despair at reading the specs, I asked the x3t13 reflector +how one is supposed to calculate capacity: + +== +X-Authentication-Warning: mage.dt.wdc.com: majordom set sender to owner-t13@dt.wdc.com using -f +Date: Thu, 11 Feb 1999 19:16:39 -0800 (PST) +From: Dan Mick <dan.mick@West> +Subject: Capacity? +To: t13@dt.wdc.com + +So, I'm sure I'm being naive in expecting there to be a way to +reliably calculate the capacity of an ATA drive, but I can't make +sense of the IDENTIFY DEVICE results, words + +1,3,6,53,54-58,60-61 + +Is the right algorithm for making sense of all this written down +somewhere?  I *have* searched the specs and Hale's HIW docs and +the "ATA FAQ" from Wehman and den Hahn, and I still don't understand +how this can be so nondeterministic. + +Even assertions in the specs seem to be ignored; I have a drive for +which words 57-58 do *not* represent the product of words 54, 55, and +56, for instance... +== + +Several responses came; one from curtis_stevens@phoenix.com said "just +use LBA", which of course doesn't answer the question about non-LBA +drives.  David_S_Thompson@notes.seagate.com said "read section +6.2.1 of ATA-4, rev 17 or above", which does help a bit.  But +the best pragmatic answer came from Hale Landis.  I've tried to  +implement this algorithm in deriving the capacity and geometry  +for ata, without using "Init Drive Parameters", since the driver +hasn't done that in recent incarnations, and I'm loath to mess +with what the BIOS and the drive have figured out unless it +becomes absolutely necessary. + + +From: "Hale Landis" <hlandis@ibm.net> +To: "T13 Reflector" <t13@dt.wdc.com>, "Dan Mick" <dan.mick@West> +Date: Thu, 11 Feb 1999 23:46:59 -0700 +Subject: Re: Capacity? + +Dan Mick said... +>So, I'm sure I'm being naive in expecting there to be a way to +>reliably calculate the capacity of an ATA drive, but I can't make +>sense of the IDENTIFY DEVICE results, words +> +>1,3,6,53,54-58,60-61 +> +>Is the right algorithm for making sense of all this written down +>somewhere?  I *have* searched the specs and Hale's HIW docs and +>the "ATA FAQ" from Wehman and den Hahn, and I still don't understand +>how this can be so nondeterministic. +> +>Even assertions in the specs seem to be ignored; I have a drive for +>which words 57-58 do *not* represent the product of words 54, 55, and +>56, for instance... + +If the words [54]*[55]*[56] don't match [57:58] then the drive is +"broken".  Warning: some older drives have words 57:58 in big endian +format (that is easy to verify!). + +Of course Read/Set Max do alter the drive's apparent capacity but assuming +this feature is not being used or it is being used and implemented +correctly... + +If you have no need to use CHS mode, then just ignore words 1, 3, 6 and +53:58.  Words 60:61 are the drive capacity.  But even if you must use CHS +mode, words 60:61 are still the true drive capacity but words 57:58 are +the capacity that the current CHS geometry can address and [57:58] must be +<= [60:61].  Oh yea, if you find that 57:58 are big endian then 60:61 are +probably big endian too. + +An algorithm??? (I hope there aren't any typo's here)... + +1) If you are LBA only (don't use CHS) then words 60:61 are all you need, +you are done. + +2) If you must use CHS then I suggest the following: + +2a) Check words 53:58... +    does 53 indicate "valid", +    is 1 <= [55] <= 16,  +    is 1 <= [56] <= 63,  +    and does [54]*[55]*[56] == [57:58]? + +   - Yes, you know that current CHS geometry of the drive, you are done. +     If you don't like this geometry then issue an Init Drv Params with +     a different heads and sectors and repeat this step. + +   - No, then go to 2b). + +2b) Does the drive support LBA and is [1]*[3]*[6] <= [60:61]? + +   - Yes, assume 60:61 are correct, and go to 2c) + +   - No, go to 2d) + +2c) Issue a Init Drv Params and set your favorite heads and sectors. +    Compute the number of cylinders: + +    num-cyl = [60:61] / (favorite heads) * (favorite sectors) + +    The drive capacity is (num-cyl)*(favorite heads)*(favorite sectors). +    And this value should be in 57:58 now.  You are done. + +2d) Now you got a problem... 60:61 are no good, 53:58 are no good. +    You don't have much choice but to assume that [1]*[3]*[6] is the +    drive capacity.  Issue an Init Drv Params to set the default geometry +    from [3] and [6] -or- issue an Init Drv Params with your favorite  +    heads and sectors.  Compute the number of cylinders: + +    num-cyl = ([1]*[3]*[6]) / (num heads) * (num sectors) + +    The drive capacity is (num-cyl)*(num-head)*(num-sectors). + +    You are done. + +And one final thing... If you used Init Drv Params you must now verify +that it worked.  Issue a read command and make sure you can read what you +think is the last sector on the drive.  If this read fails with ABRT or +IDNF, you are in *BIG* trouble. + +All we did here was find a CHS geometry and a drive capacity that should +work.  If the drive has a Master Boot Record then this geometry may not +have a CHS translation that matches the CHS translation that was used in +that Master Boot Record.  But I'll not go into that here (I would probably +have to say bad things about the documents published by some of my friends +a few years ago!). + +I'll say "sorry" now to all you hardware folks that read these reflector +messages but I'm sure this will begin a long series of messages on the +reflector that will just bore you to near death! + + ++---------------+---------------------------+ +| Hale Landis   | hlandis@ibm.net           | +| Niwot, CO USA | hlandis@sugs.talisman.com | ++---------------+---------------------------+ +| !! Coming soon: www.talisman.com/sugs  !! | ++-------------------------------------------+ diff --git a/usr/src/uts/intel/io/dktp/controller/ata/fsm.txt b/usr/src/uts/intel/io/dktp/controller/ata/fsm.txt new file mode 100644 index 0000000000..ec4619c432 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/fsm.txt @@ -0,0 +1,74 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2000 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#ident	"%Z%%M%	%I%	%E% SMI" + + +	/* +	 * The interrupt reason can be interpreted +	 * from other bits as follows: +	 * +	 *  DRQ  IO  CoD +	 *  ---  --  --- +	 *    0   0    1  Idle +	 *    1   0    1  Send ATAPI CDB to device +	 *    1   1    0  Data from device +	 *    1   0    0  Data to device +	 *    1   1    1  Future use +	 *    0   1    1  Status ready +	 * +	 */ + +			ACTION + +			ATAPI Status Bits +Current	|			 +State	|	idle	cdb	datain		dataout		status +        |----------------------------------------------------------------- +idle	|	cmd	cmd	cmd		cmd		cmd +command	|	*	sendcdb	*		*		rd-intr-reason +cdb	|	*	*	start-xfer	start-xfer	rd-intr-reason	 +datain	|	*	*	continue-xfer	*		rd-intr-reason +dataout	|	*	*	*		continue-xfer	rd-intr-reason + + + +			NEXT-STATE + +			ATAPI Status Bits +Current	|			 +State	|	idle	cdb	datain		dataout		status + 	|----------------------------------------------------------------- +idle	|	command	command	command		command		command +command	|	*	cdb	*		*		* +cdb	|	*	*	datain		dataout		(idle) +datain	|	*	*	datain		*		(idle) +dataout	|	*	*	*		dataout		(idle) + + + +States marked '*' should be invalid but some non-complaint drives +don't transition correctly between states.  diff --git a/usr/src/uts/intel/io/dktp/controller/ata/pciide.h b/usr/src/uts/intel/io/dktp/controller/ata/pciide.h new file mode 100644 index 0000000000..047478bdd0 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/pciide.h @@ -0,0 +1,105 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _PCIIDE_H +#define	_PCIIDE_H + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#ifdef	__cplusplus +extern "C" { +#endif + + +/* + * Bus Mastering devices have a PCI class-code of 0x010180 to 0x0101ff + */ +#define	PCIIDE_BM_CAP_MASK	0x80 +#define	PCIIDE_BM_CLASS	((PCI_CLASS_MASS << 16) | (PCI_MASS_IDE << 8) | 0x80) +#define	PCIIDE_BM_CLASS_MASK	0xffffff80 + + +#define	PCIIDE_BMICX_REG	0	/* Bus Master IDE Command Register */ + +#define	PCIIDE_BMICX_SSBM	0x01	/* Start/Stop Bus Master */ +#define	PCIIDE_BMICX_SSBM_E		0x01	/* 1=Start (Enable) */ +						/* 0=Start (Disable) */ + +/* + * NOTE: "read" and "write" are the actions of the DMA + * engine on the PCI bus. Not the DMA engine's action on the ATA + * BUS. Therefore for a ATA READ command, program the DMA engine to + * "write to memory" mode (and vice versa). + */ +#define	PCIIDE_BMICX_RWCON	0x08	/* Read/Write Control */ +#define	PCIIDE_BMICX_RWCON_WRITE_TO_MEMORY	0x08 /* 1=Write (dev to host) */ +#define	PCIIDE_BMICX_RWCON_READ_FROM_MEMORY	0x00 /* 0=Read  (host to dev) */ + +/* preserve these bits during updates */ +#define	PCIIDE_BMICX_MASK	(~(PCIIDE_BMICX_SSBM | PCIIDE_BMICX_RWCON)) + + + +#define	PCIIDE_BMISX_REG	2	/* Bus Master IDE Status Register */ + +#define	PCIIDE_BMISX_BMIDEA	0x01	/* Bus Master IDE Active */ +#define	PCIIDE_BMISX_IDERR	0x02	/* IDE DMA Error */ +#define	PCIIDE_BMISX_IDEINTS	0x04	/* IDE Interrupt Status */ +#define	PCIIDE_BMISX_DMA0CAP	0x20	/* Drive 0 DMA Capable */ +#define	PCIIDE_BMISX_DMA1CAP	0x40	/* Drive 1 DMA Capable */ +#define	PCIIDE_BMISX_SIMPLEX	0x80	/* Simplex only */ + +/* preserve these bits during updates */ +#define	PCIIDE_BMISX_MASK	0xf8 + +#define	PCIIDE_BMIDTPX_REG	4	/* Bus Master IDE Desc. Table Ptr */ +#define	PCIIDE_BMIDTPX_MASK	0x00000003	/* must be zeros */ + + +typedef struct PhysicalRegionDescriptorTableEntry { +	uint_t	p_address;	/* physical address */ +	uint_t	p_count;	/* byte count, EOT in high order bit */ +} prde_t; + +/* + * Some specs say the p_address must 32-bit aligned, and some claim + * 16-bit alignment. Use 32-bit alignment just to be safe. + */ +#ifdef __not_yet__ +#define	PCIIDE_PRDE_ADDR_MASK	((uint_t)(sizeof (short) -1)) +#else +#define	PCIIDE_PRDE_ADDR_MASK	((uint_t)(sizeof (int) -1)) +#endif + +#define	PCIIDE_PRDE_CNT_MASK	((uint_t)0x0001)	/* must be even */ +#define	PCIIDE_PRDE_CNT_MAX	((uint_t)0x10000)	/* 0 == 64k */ +#define	PCIIDE_PRDE_EOT		((uint_t)0x80000000) + +#ifdef	__cplusplus +} +#endif + +#endif /* _PCIIDE_H */ diff --git a/usr/src/uts/intel/io/dktp/controller/ata/sil3xxx.c b/usr/src/uts/intel/io/dktp/controller/ata/sil3xxx.c new file mode 100644 index 0000000000..5a5cd19f8b --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/sil3xxx.c @@ -0,0 +1,152 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ +/* + * Silicon Image 3XXX controller specific processing + * + * This file may be expanded to take advantage of Silicon Image + * additional features (if applicable to specific controller model): + * 1. Virtual DMA operation + * 2. Concurrent all-channel DMA + * 3. Large Block Transfers + * 4. Watchdog Timer + * 5. Power Management + * 6. Hot Plug Support + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include "ata_common.h" +#include "sil3xxx.h" +#include <sys/pci.h> + +int fifocntctl[] = {FIFO_CNTCTL_0, FIFO_CNTCTL_1, FIFO_CNTCTL_2, FIFO_CNTCTL_3}; +int sfiscfg[] = {SFISCFG_0, SFISCFG_1, SFISCFG_2, SFISCFG_3}; + +/* + * Controller specific initialization + */ +uint_t +sil3xxx_init_controller(dev_info_t *dip, +	/* LINTED */ +	ushort_t vendor_id, ushort_t device_id) +{ +	ddi_acc_handle_t  pci_conf_handle; /* pci config space handle */ +	uint8_t cache_lnsz, frrc = 0; +	uint32_t fifo_cnt_ctl; +	int ports, i; + +#ifdef	DEBUG +	/* LINTED */ +	ushort_t sfiscfg_val; +#endif + +	/* +	 * Sil3114, Sil3512, Sil3112 +	 * We want to perform this initialization only once per entire +	 * pciide controller (all channels) +	 */ +	if (ddi_prop_exists(DDI_DEV_T_ANY, ddi_get_parent(dip), +		DDI_PROP_DONTPASS, "sil3xxx-initialized")) { +		return (TRUE); +	} + +	if (pci_config_setup(ddi_get_parent(dip), &pci_conf_handle) != +	    DDI_SUCCESS) { +		cmn_err(CE_WARN, +		    "sil3xxx_init_controller: Can't do pci_config_setup\n"); +		return (FALSE); +	} + +	/* +	 * Sil3114/3512/3112 incorrectly change between MR and back to +	 * MRM for same transaction, which violates the PCI spec and can +	 * lead to incorrect data reads.  The workaround +	 * is to set bits 2:0 in the FIFO count and control register so +	 * that its value, a multiple of 32 bytes starting at 32, not 0, +	 * is greater or equal to the cacheline size, a multiple of 4 +	 * bytes.  This will prevent any reads until the FIFO free space +	 * is greater than a cacheline size, ensuring only MRM is issued. +	 */ + +	cache_lnsz = pci_config_get8(pci_conf_handle, PCI_CONF_CACHE_LINESZ); + +	/* +	 * The cache line is specified in 32-bit words, so multiply by 4 +	 * to get bytes.  Then divide by 32 bytes, the granularity of the +	 * FIFO control bits 2:0.  Add 1 if there is any remainder to +	 * account for a partial 32-byte block, then subtract 1 since for +	 * FIFO controls bits 2:0, 0 corresponds to 32, 1 corresponds to +	 * 64, and so on.  The calculation is expanded for clarity. +	 */ +	if (cache_lnsz != 0) { +		frrc = (cache_lnsz * 4 / 32) + +			(((cache_lnsz * 4) % 32) ? 1 : 0) - 1; +	} + +	if (device_id == SIL3114_DEVICE_ID) { +		ports = 4; +	} else { +		ports = 2; +	} + +	/* +	 * The following BAR5 registers are accessed via an indirect register +	 * in the PCI configuration space rather than mapping BAR5. +	 */ +	for (i = 0; i < ports; i++) { +		GET_BAR5_INDIRECT(pci_conf_handle, fifocntctl[i], +		    fifo_cnt_ctl); +		fifo_cnt_ctl = (fifo_cnt_ctl & ~0x7) | (frrc & 0x7); +		PUT_BAR5_INDIRECT(pci_conf_handle, fifocntctl[i], +		    fifo_cnt_ctl); +		/* +		 * Correct default setting for FIS0cfg +		 */ +#ifdef	DEBUG +		GET_BAR5_INDIRECT(pci_conf_handle, sfiscfg[i], +			sfiscfg_val); +		ADBG_WARN(("sil3xxx_init_controller: old val SFISCfg " +			"ch%d: %x\n", i, sfiscfg_val)); +#endif +		PUT_BAR5_INDIRECT(pci_conf_handle, sfiscfg[i], +			SFISCFG_ERRATA); +#ifdef	DEBUG +		GET_BAR5_INDIRECT(pci_conf_handle, sfiscfg[i], +			sfiscfg_val); +		ADBG_WARN(("sil3xxx_init_controller: new val SFISCfg " +			"ch%d: %x\n", i, sfiscfg_val)); +#endif +	} + +	/* Now tear down the pci config setup */ +	pci_config_teardown(&pci_conf_handle); + +	/* Create property indicating that initialization was done */ +	(void) ddi_prop_update_int(DDI_DEV_T_NONE, ddi_get_parent(dip), +		"sil3xxx-initialized", 1); + +	return (TRUE); +} diff --git a/usr/src/uts/intel/io/dktp/controller/ata/sil3xxx.h b/usr/src/uts/intel/io/dktp/controller/ata/sil3xxx.h new file mode 100644 index 0000000000..f3ea4b0aaf --- /dev/null +++ b/usr/src/uts/intel/io/dktp/controller/ata/sil3xxx.h @@ -0,0 +1,98 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License").   + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#ifndef	_SIL3XXX_H +#define	_SIL3XXX_H + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#ifdef	__cplusplus +extern "C" { +#endif + +/* + * PCI IDs + */ +#define	SILICON_IMAGE_VENDOR_ID	0x1095 +#define	SIL3112_DEVICE_ID	0x3112 +#define	SIL3114_DEVICE_ID	0x3114 +#define	SIL3512_DEVICE_ID	0x3512 + +/* Base Register 5 Indirect Address Offset */ + +#define	PCI_CONF_BA5_IND_ADDRESS	0xc0 +#define	PCI_CONF_BA5_IND_ACCESS		0xc4 + +/* + * FIS Configuration channel offsets + * Sil3114 has 4 channels + * Sil3112 has 2 channels + * Sil3512 has 2 channels + */ +#define	SFISCFG_0	0x14c	/* SFISCfg Channel 0 */ +#define	SFISCFG_1	0x1cc	/* SFISCfg Channel 1 */ +#define	SFISCFG_2	0x34c	/* SFISCfg Channel 2 */ +#define	SFISCFG_3	0x3cc	/* SFISCfg Channel 3 */ + +/* + * FIFO count and contrl offsets for channel 0-4 + */ +#define	FIFO_CNTCTL_0 0x40 +#define	FIFO_CNTCTL_1 0x44 +#define	FIFO_CNTCTL_2 0x240 +#define	FIFO_CNTCTL_3 0x244 + +/* + * Errata Sil-AN-0028-C (Sil3512 Rev 0.3) + * Errata Sil-AN-0109-B2 (Sil3114 Rev 0.3) + * To prevent erroneous ERR set for queued DMA transfers + * greater then 8k, FIS reception for FIS0cfg needs to be set + * to Accept FIS without Interlock + * Default SFISCfg value of 0x10401555 in channel SFISCfg + * register need to be changed to 0x10401554. + */ +#define	SFISCFG_ERRATA	0x10401554 + + +#define	PUT_BAR5_INDIRECT(handle, address, value) \ +{\ +		pci_config_put32(handle, PCI_CONF_BA5_IND_ADDRESS, address); \ +		pci_config_put32(handle, PCI_CONF_BA5_IND_ACCESS, value); \ +} + +#define	GET_BAR5_INDIRECT(handle, address, rval) \ +{\ +		pci_config_put32(handle, PCI_CONF_BA5_IND_ADDRESS, address); \ +		rval = pci_config_get32(handle, PCI_CONF_BA5_IND_ACCESS); \ +} + +uint_t	sil3xxx_init_controller(dev_info_t *, ushort_t, ushort_t); + +#ifdef	__cplusplus +} +#endif + +#endif	/* _SIL3XXX_H */ diff --git a/usr/src/uts/common/io/dktp/dcdev/dadk.c b/usr/src/uts/intel/io/dktp/dcdev/dadk.c index 02b22ef9db..02b22ef9db 100644 --- a/usr/src/uts/common/io/dktp/dcdev/dadk.c +++ b/usr/src/uts/intel/io/dktp/dcdev/dadk.c diff --git a/usr/src/uts/common/io/dktp/dcdev/gda.c b/usr/src/uts/intel/io/dktp/dcdev/gda.c index ca40757c9f..ca40757c9f 100644 --- a/usr/src/uts/common/io/dktp/dcdev/gda.c +++ b/usr/src/uts/intel/io/dktp/dcdev/gda.c diff --git a/usr/src/uts/common/io/dktp/disk/cmdk.c b/usr/src/uts/intel/io/dktp/disk/cmdk.c index 5308314aca..5308314aca 100644 --- a/usr/src/uts/common/io/dktp/disk/cmdk.c +++ b/usr/src/uts/intel/io/dktp/disk/cmdk.c diff --git a/usr/src/uts/common/io/dktp/drvobj/strategy.c b/usr/src/uts/intel/io/dktp/drvobj/strategy.c index fb802e26ca..fb802e26ca 100644 --- a/usr/src/uts/common/io/dktp/drvobj/strategy.c +++ b/usr/src/uts/intel/io/dktp/drvobj/strategy.c diff --git a/usr/src/uts/intel/io/dktp/hba/ghd/ghd.c b/usr/src/uts/intel/io/dktp/hba/ghd/ghd.c new file mode 100644 index 0000000000..80d28d842f --- /dev/null +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd.c @@ -0,0 +1,945 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include <sys/types.h> +#include <sys/kmem.h> +#include <sys/debug.h> +#include <sys/scsi/scsi.h> + +#include "ghd.h" + +/* ghd_poll() function codes: */ +typedef enum { +	GHD_POLL_REQUEST,	/* wait for a specific request */ +	GHD_POLL_DEVICE,	/* wait for a specific device to idle */ +	GHD_POLL_ALL		/* wait for the whole bus to idle */ +} gpoll_t; + +/* + * Local functions: + */ +static	gcmd_t	*ghd_doneq_get(ccc_t *cccp); +static	void	 ghd_doneq_pollmode_enter(ccc_t *cccp); +static	void	 ghd_doneq_pollmode_exit(ccc_t *cccp); +static	uint_t	 ghd_doneq_process(caddr_t arg); +static	void	 ghd_do_reset_notify_callbacks(ccc_t *cccp); + +static	uint_t	 ghd_dummy_intr(caddr_t arg); +static	int	 ghd_poll(ccc_t *cccp, gpoll_t polltype, ulong_t polltime, +			gcmd_t *poll_gcmdp, gtgt_t *gtgtp, void *intr_status); + + +/* + * Local configuration variables + */ + +ulong_t	ghd_tran_abort_timeout = 5; +ulong_t	ghd_tran_abort_lun_timeout = 5; +ulong_t	ghd_tran_reset_target_timeout = 5; +ulong_t	ghd_tran_reset_bus_timeout = 5; + +static int +ghd_doneq_init(ccc_t *cccp) +{ +	ddi_iblock_cookie_t iblock; + +	L2_INIT(&cccp->ccc_doneq); +	cccp->ccc_hba_pollmode = TRUE; + +	if (ddi_add_softintr(cccp->ccc_hba_dip, DDI_SOFTINT_LOW, +	    &cccp->ccc_doneq_softid, &iblock, NULL, +	    ghd_doneq_process, (caddr_t)cccp) != DDI_SUCCESS) { +		GDBG_ERROR(("ghd_doneq_init: add softintr failed cccp 0x%p\n", +		    (void *)cccp)); +		return (FALSE); +	} + +	mutex_init(&cccp->ccc_doneq_mutex, NULL, MUTEX_DRIVER, iblock); +	ghd_doneq_pollmode_exit(cccp); +	return (TRUE); +} + +/* + * ghd_complete(): + * + *	The HBA driver calls this entry point when it's completely + *	done processing a request. + * + *	See the GHD_COMPLETE_INLINE() macro in ghd.h for the actual code. + */ + +void +ghd_complete(ccc_t *cccp, gcmd_t *gcmdp) +{ +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); +	GHD_COMPLETE_INLINE(cccp, gcmdp); +} + + +/* + * ghd_doneq_put_head(): + * + *	Mark the request done and prepend it to the doneq. + *	See the GHD_DONEQ_PUT_HEAD_INLINE() macros in ghd.h for + *	the actual code. + */ +void +ghd_doneq_put_head(ccc_t *cccp, gcmd_t *gcmdp) +{ +	GHD_DONEQ_PUT_HEAD_INLINE(cccp, gcmdp) +} + +/* + * ghd_doneq_put_tail(): + * + *	Mark the request done and append it to the doneq. + *	See the GHD_DONEQ_PUT_TAIL_INLINE() macros in ghd.h for + *	the actual code. + */ +void +ghd_doneq_put_tail(ccc_t *cccp, gcmd_t *gcmdp) +{ +	GHD_DONEQ_PUT_TAIL_INLINE(cccp, gcmdp) +} + +static gcmd_t	* +ghd_doneq_get(ccc_t *cccp) +{ +	kmutex_t *doneq_mutexp = &cccp->ccc_doneq_mutex; +	gcmd_t	 *gcmdp; + +	mutex_enter(doneq_mutexp); +	if ((gcmdp = L2_next(&cccp->ccc_doneq)) != NULL) +		L2_delete(&gcmdp->cmd_q); +	mutex_exit(doneq_mutexp); +	return (gcmdp); +} + + +static void +ghd_doneq_pollmode_enter(ccc_t *cccp) +{ +	kmutex_t *doneq_mutexp = &cccp->ccc_doneq_mutex; + +	mutex_enter(doneq_mutexp); +	cccp->ccc_hba_pollmode = TRUE; +	mutex_exit(doneq_mutexp); +} + + +static void +ghd_doneq_pollmode_exit(ccc_t *cccp) +{ +	kmutex_t *doneq_mutexp = &cccp->ccc_doneq_mutex; + +	mutex_enter(doneq_mutexp); +	cccp->ccc_hba_pollmode = FALSE; +	mutex_exit(doneq_mutexp); + +	/* trigger software interrupt for the completion callbacks */ +	if (!L2_EMPTY(&cccp->ccc_doneq)) +		ddi_trigger_softintr(cccp->ccc_doneq_softid); +} + + +/* ***************************************************************** */ + +/* + * + * ghd_doneq_process() + * + *	This function is called directly from the software interrupt + *	handler. + * + *	The doneq is protected by a separate mutex than the + *	HBA mutex in order to avoid mutex contention on MP systems. + * + */ + +static uint_t +ghd_doneq_process(caddr_t arg) +{ +	ccc_t *cccp =	(ccc_t *)arg; +	kmutex_t	*doneq_mutexp; +	gcmd_t		*gcmdp; +	int			rc = DDI_INTR_UNCLAIMED; + +	doneq_mutexp = &cccp->ccc_doneq_mutex; + +	for (;;) { +		mutex_enter(doneq_mutexp); +		/* skip if FLAG_NOINTR request in progress */ +		if (cccp->ccc_hba_pollmode) +			break; +		/* pop the first one from the done Q */ +		if ((gcmdp = L2_next(&cccp->ccc_doneq)) == NULL) +			break; +		L2_delete(&gcmdp->cmd_q); + +		if (gcmdp->cmd_flags & GCMDFLG_RESET_NOTIFY) { +			/* special request; processed here and discarded */ +			ghd_do_reset_notify_callbacks(cccp); +			ghd_gcmd_free(gcmdp); +			mutex_exit(doneq_mutexp); +			continue; +		} + +		/* +		 * drop the mutex since completion +		 * function can re-enter the top half via +		 * ghd_transport() +		 */ +		mutex_exit(doneq_mutexp); +		gcmdp->cmd_state = GCMD_STATE_IDLE; +		(*cccp->ccc_hba_complete)(cccp->ccc_hba_handle, gcmdp, TRUE); +#ifdef notyet +		/* I don't think this is ever necessary */ +		rc = DDI_INTR_CLAIMED; +#endif +	} +	mutex_exit(doneq_mutexp); +	return (rc); +} + +static void +ghd_do_reset_notify_callbacks(ccc_t *cccp) +{ +	ghd_reset_notify_list_t *rnp; +	L2el_t *rnl = &cccp->ccc_reset_notify_list; + +	ASSERT(mutex_owned(&cccp->ccc_doneq_mutex)); + +	/* lock the reset notify list while we operate on it */ +	mutex_enter(&cccp->ccc_reset_notify_mutex); + +	for (rnp = (ghd_reset_notify_list_t *)L2_next(rnl); +	    rnp != NULL; +	    rnp = (ghd_reset_notify_list_t *)L2_next(&rnp->l2_link)) { + +		/* don't call if HBA driver didn't set it */ +		if (cccp->ccc_hba_reset_notify_callback) { +			(*cccp->ccc_hba_reset_notify_callback)(rnp->gtgtp, +			    rnp->callback, rnp->arg); +		} +	} +	mutex_exit(&cccp->ccc_reset_notify_mutex); +} + + +/* ***************************************************************** */ + + + +/* + * Autovector Interrupt Entry Point + * + *	Dummy return to be used before mutexes has been initialized + *	guard against interrupts from drivers sharing the same irq line + */ + +/*ARGSUSED*/ +static uint_t +ghd_dummy_intr(caddr_t arg) +{ +	return (DDI_INTR_UNCLAIMED); +} + + +/* + * ghd_register() + * + *	Do the usual interrupt handler setup stuff. + * + *	Also, set up three mutexes: the wait queue mutex, the HBA + *	mutex, and the done queue mutex. The permitted locking + *	orders are: + * + *		1. enter(waitq) + *		2. enter(activel) + *		3. enter(doneq) + *		4. enter(HBA) then enter(activel) + *		5. enter(HBA) then enter(doneq) + *		6. enter(HBA) then enter(waitq) + *		7. enter(waitq) then tryenter(HBA) + * + *	Note: cases 6 and 7 won't deadlock because case 7 is always + *	mutex_tryenter() call. + * + */ + + +int +ghd_register(char *labelp, +	ccc_t	*cccp, +	dev_info_t *dip, +	int	inumber, +	void	*hba_handle, +	int	(*ccballoc)(gtgt_t *, gcmd_t *, int, int, int, int), +	void	(*ccbfree)(gcmd_t *), +	void	(*sg_func)(gcmd_t *, ddi_dma_cookie_t *, int, int), +	int	(*hba_start)(void *, gcmd_t *), +	void    (*hba_complete)(void *, gcmd_t *, int), +	uint_t	(*int_handler)(caddr_t), +	int	(*get_status)(void *, void *), +	void	(*process_intr)(void *, void *), +	int	(*timeout_func)(void *, gcmd_t *, gtgt_t *, gact_t, int), +	tmr_t	*tmrp, +	void 	(*hba_reset_notify_callback)(gtgt_t *, +			void (*)(caddr_t), caddr_t)) +{ + +	cccp->ccc_label = labelp; +	cccp->ccc_hba_dip = dip; +	cccp->ccc_ccballoc = ccballoc; +	cccp->ccc_ccbfree = ccbfree; +	cccp->ccc_sg_func = sg_func; +	cccp->ccc_hba_start = hba_start; +	cccp->ccc_hba_complete = hba_complete; +	cccp->ccc_process_intr = process_intr; +	cccp->ccc_get_status = get_status; +	cccp->ccc_hba_handle = hba_handle; +	cccp->ccc_hba_reset_notify_callback = hba_reset_notify_callback; + +	/* initialize the HBA's list headers */ +	CCCP_INIT(cccp); + +	/* +	 *	Establish initial dummy interrupt handler +	 *	get iblock cookie to initialize mutexes used in the +	 *	real interrupt handler +	 */ +	if (ddi_add_intr(dip, inumber, &cccp->ccc_iblock, NULL, +	    ghd_dummy_intr, hba_handle) != DDI_SUCCESS) { +		return (FALSE); +	} +	mutex_init(&cccp->ccc_hba_mutex, NULL, MUTEX_DRIVER, cccp->ccc_iblock); +	ddi_remove_intr(dip, inumber, cccp->ccc_iblock); + +	/* Establish real interrupt handler */ +	if (ddi_add_intr(dip, inumber, &cccp->ccc_iblock, NULL, +	    int_handler, (caddr_t)hba_handle) != DDI_SUCCESS) { +		mutex_destroy(&cccp->ccc_hba_mutex); +		return (FALSE); +	} + +	mutex_init(&cccp->ccc_waitq_mutex, NULL, +	    MUTEX_DRIVER, cccp->ccc_iblock); + +	mutex_init(&cccp->ccc_reset_notify_mutex, NULL, +	    MUTEX_DRIVER, cccp->ccc_iblock); + +	if (ghd_timer_attach(cccp, tmrp, timeout_func) == FALSE) { +		ddi_remove_intr(cccp->ccc_hba_dip, 0, cccp->ccc_iblock); +		mutex_destroy(&cccp->ccc_hba_mutex); +		mutex_destroy(&cccp->ccc_waitq_mutex); +		return (FALSE); +	} + +	if (ghd_doneq_init(cccp)) { +		return (TRUE); +	} + +	ghd_timer_detach(cccp); +	ddi_remove_intr(cccp->ccc_hba_dip, 0, cccp->ccc_iblock); +	mutex_destroy(&cccp->ccc_hba_mutex); +	mutex_destroy(&cccp->ccc_waitq_mutex); +	return (FALSE); + +} + + +void +ghd_unregister(ccc_t *cccp) +{ +	ghd_timer_detach(cccp); +	ddi_remove_intr(cccp->ccc_hba_dip, 0, cccp->ccc_iblock); +	ddi_remove_softintr(cccp->ccc_doneq_softid); +	mutex_destroy(&cccp->ccc_hba_mutex); +	mutex_destroy(&cccp->ccc_waitq_mutex); +	mutex_destroy(&cccp->ccc_doneq_mutex); +} + + + +int +ghd_intr(ccc_t *cccp, void *intr_status) +{ +	int (*statfunc)(void *, void *) = cccp->ccc_get_status; +	void (*processfunc)(void *, void *) = cccp->ccc_process_intr; +	kmutex_t *waitq_mutexp = &cccp->ccc_waitq_mutex; +	kmutex_t *hba_mutexp = &cccp->ccc_hba_mutex; +	void		  *handle = cccp->ccc_hba_handle; +	int		   rc = DDI_INTR_UNCLAIMED; +	int		   more; + + +	mutex_enter(hba_mutexp); + +	GDBG_INTR(("ghd_intr(): cccp=0x%p status=0x%p\n", +		cccp, intr_status)); + +	for (;;) { +		more = FALSE; + +		/* process the interrupt status */ +		while ((*statfunc)(handle, intr_status)) { +			(*processfunc)(handle, intr_status); +			rc = DDI_INTR_CLAIMED; +			more = TRUE; +		} +		mutex_enter(waitq_mutexp); +		if (ghd_waitq_process_and_mutex_hold(cccp)) { +			ASSERT(mutex_owned(hba_mutexp)); +			mutex_exit(waitq_mutexp); +			continue; +		} +		if (more) { +			mutex_exit(waitq_mutexp); +			continue; +		} +		GDBG_INTR(("ghd_intr(): done cccp=0x%p status=0x%p rc %d\n", +			cccp, intr_status, rc)); +		/* +		 * Release the mutexes in the opposite order that they +		 * were acquired to prevent requests queued by +		 * ghd_transport() from getting hung up in the wait queue. +		 */ +		mutex_exit(hba_mutexp); +		mutex_exit(waitq_mutexp); +		return (rc); +	} +} + +static int +ghd_poll(ccc_t	*cccp, +	gpoll_t	 polltype, +	ulong_t	 polltime, +	gcmd_t	*poll_gcmdp, +	gtgt_t	*gtgtp, +	void	*intr_status) +{ +	gcmd_t	*gcmdp; +	L2el_t	 gcmd_hold_queue; +	int	 got_it = FALSE; +	clock_t	 start_lbolt; +	clock_t	 current_lbolt; + + +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); +	L2_INIT(&gcmd_hold_queue); + +	/* Que hora es? */ +	start_lbolt = ddi_get_lbolt(); + +	/* unqueue and save all CMD/CCBs until I find the right one */ +	while (!got_it) { + +		/* Give up yet? */ +		current_lbolt = ddi_get_lbolt(); +		if (polltime && (current_lbolt - start_lbolt >= polltime)) +			break; + +		/* +		 * delay 1 msec each time around the loop (this is an +		 * arbitrary delay value, any value should work) except +		 * zero because some devices don't like being polled too +		 * fast and it saturates the bus on an MP system. +		 */ +		drv_usecwait(1000); + +		/* +		 * check for any new device status +		 */ +		if ((*cccp->ccc_get_status)(cccp->ccc_hba_handle, intr_status)) +			(*cccp->ccc_process_intr)(cccp->ccc_hba_handle, +			    intr_status); + +		/* +		 * If something completed then try to start the +		 * next request from the wait queue. Don't release +		 * the HBA mutex because I don't know whether my +		 * request(s) is/are on the done queue yet. +		 */ +		mutex_enter(&cccp->ccc_waitq_mutex); +		(void) ghd_waitq_process_and_mutex_hold(cccp); +		mutex_exit(&cccp->ccc_waitq_mutex); + +		/* +		 * Process the first of any timed-out requests. +		 */ +		ghd_timer_poll(cccp, GHD_TIMER_POLL_ONE); + +		/* +		 * Unqueue all the completed requests, look for mine +		 */ +		while (gcmdp = ghd_doneq_get(cccp)) { +			/* +			 * If we got one and it's my request, then +			 * we're done. +			 */ +			if (gcmdp == poll_gcmdp) { +				poll_gcmdp->cmd_state = GCMD_STATE_IDLE; +				got_it = TRUE; +				continue; +			} +			/* fifo queue the other cmds on my local list */ +			L2_add(&gcmd_hold_queue, &gcmdp->cmd_q, gcmdp); +		} + + +		/* +		 * Check whether we're done yet. +		 */ +		switch (polltype) { +		case GHD_POLL_DEVICE: +			/* +			 * wait for everything queued on a specific device +			 */ +			if (GDEV_NACTIVE(gtgtp->gt_gdevp) == 0) +				got_it = TRUE; +			break; + +		case GHD_POLL_ALL: +			/* +			 * if waiting for all outstanding requests and +			 * if active list is now empty then exit +			 */ +			if (GHBA_NACTIVE(cccp) == 0) +				got_it = TRUE; +			break; + +		case GHD_POLL_REQUEST: +			break; + +		} +	} + +	if (L2_EMPTY(&gcmd_hold_queue)) { +		ASSERT(!mutex_owned(&cccp->ccc_waitq_mutex)); +		ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); +		return (got_it); +	} + +	/* +	 * copy the local gcmd_hold_queue back to the doneq so +	 * that the order of completion callbacks is preserved +	 */ +	while (gcmdp = L2_next(&gcmd_hold_queue)) { +		L2_delete(&gcmdp->cmd_q); +		GHD_DONEQ_PUT_TAIL(cccp, gcmdp); +	} + +	ASSERT(!mutex_owned(&cccp->ccc_waitq_mutex)); +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); +	return (got_it); +} + + +/* + * ghd_tran_abort() + * + *	Abort specific command on a target. + * + */ + +int +ghd_tran_abort(ccc_t *cccp, gcmd_t *gcmdp, gtgt_t *gtgtp, void *intr_status) +{ +	gact_t	 action; +	int	 rc; + +	/* +	 * call the driver's abort_cmd function +	 */ + +	mutex_enter(&cccp->ccc_hba_mutex); +	ghd_doneq_pollmode_enter(cccp); + +	switch (gcmdp->cmd_state) { +	case GCMD_STATE_WAITQ: +		/* not yet started */ +		action = GACTION_EARLY_ABORT; +		break; + +	case GCMD_STATE_ACTIVE: +		/* in progress */ +		action = GACTION_ABORT_CMD; +		break; + +	default: +		/* everything else, probably already being aborted */ +		rc = FALSE; +		goto exit; +	} + +	/* stop the timer and remove it from the active list */ +	GHD_TIMER_STOP(cccp, gcmdp); + +	/* start a new timer and send out the abort command */ +	ghd_timer_newstate(cccp, gcmdp, gtgtp, action, GHD_TGTREQ); + +	/* wait for the abort to complete */ +	if (rc = ghd_poll(cccp, GHD_POLL_REQUEST, ghd_tran_abort_timeout, +	    gcmdp, gtgtp, intr_status)) { +		gcmdp->cmd_state = GCMD_STATE_DONEQ; +		GHD_DONEQ_PUT_TAIL(cccp, gcmdp); +	} + +exit: +	ghd_doneq_pollmode_exit(cccp); + +	mutex_enter(&cccp->ccc_waitq_mutex); +	ghd_waitq_process_and_mutex_exit(cccp); + +	return (rc); +} + + +/* + * ghd_tran_abort_lun() + * + *	Abort all commands on a specific target. + * + */ + +int +ghd_tran_abort_lun(ccc_t *cccp,	gtgt_t *gtgtp, void *intr_status) +{ +	int	 rc; + +	/* +	 * call the HBA driver's abort_device function +	 */ + +	mutex_enter(&cccp->ccc_hba_mutex); +	ghd_doneq_pollmode_enter(cccp); + +	/* send out the abort device request */ +	ghd_timer_newstate(cccp, NULL, gtgtp, GACTION_ABORT_DEV, GHD_TGTREQ); + +	/* wait for the device to go idle */ +	rc = ghd_poll(cccp, GHD_POLL_DEVICE, ghd_tran_abort_lun_timeout, +		NULL, gtgtp, intr_status); + +	ghd_doneq_pollmode_exit(cccp); + +	mutex_enter(&cccp->ccc_waitq_mutex); +	ghd_waitq_process_and_mutex_exit(cccp); + +	return (rc); +} + + + +/* + * ghd_tran_reset_target() + * + *	reset the target device + * + * + */ + +int +ghd_tran_reset_target(ccc_t *cccp, gtgt_t *gtgtp, void *intr_status) +{ +	int rc = TRUE; + + +	mutex_enter(&cccp->ccc_hba_mutex); +	ghd_doneq_pollmode_enter(cccp); + +	/* send out the device reset request */ +	ghd_timer_newstate(cccp, NULL, gtgtp, GACTION_RESET_TARGET, GHD_TGTREQ); + +	/* wait for the device to reset */ +	rc = ghd_poll(cccp, GHD_POLL_DEVICE, ghd_tran_reset_target_timeout, +		NULL, gtgtp, intr_status); + +	ghd_doneq_pollmode_exit(cccp); + +	mutex_enter(&cccp->ccc_waitq_mutex); +	ghd_waitq_process_and_mutex_exit(cccp); + +	return (rc); +} + + + +/* + * ghd_tran_reset_bus() + * + *	reset the scsi bus + * + */ + +int +ghd_tran_reset_bus(ccc_t *cccp, gtgt_t *gtgtp, void *intr_status) +{ +	int	rc; + +	mutex_enter(&cccp->ccc_hba_mutex); +	ghd_doneq_pollmode_enter(cccp); + +	/* send out the bus reset request */ +	ghd_timer_newstate(cccp, NULL, gtgtp, GACTION_RESET_BUS, GHD_TGTREQ); + +	/* +	 * Wait for all active requests on this HBA to complete +	 */ +	rc = ghd_poll(cccp, GHD_POLL_ALL, ghd_tran_reset_bus_timeout, +		NULL, NULL, intr_status); + + +	ghd_doneq_pollmode_exit(cccp); + +	mutex_enter(&cccp->ccc_waitq_mutex); +	ghd_waitq_process_and_mutex_exit(cccp); + +	return (rc); +} + + +int +ghd_transport(ccc_t	*cccp, +		gcmd_t	*gcmdp, +		gtgt_t	*gtgtp, +		ulong_t	 timeout, +		int	 polled, +		void	*intr_status) +{ +	gdev_t	*gdevp = gtgtp->gt_gdevp; + +	ASSERT(!mutex_owned(&cccp->ccc_hba_mutex)); +	ASSERT(!mutex_owned(&cccp->ccc_waitq_mutex)); + +	if (polled) { +		/* +		 * Grab the HBA mutex so no other requests are started +		 * until after this one completes. +		 */ +		mutex_enter(&cccp->ccc_hba_mutex); + +		GDBG_START(("ghd_transport: polled" +			" cccp 0x%p gdevp 0x%p gtgtp 0x%p gcmdp 0x%p\n", +				cccp, gdevp, gtgtp, gcmdp)); + +		/* +		 * Lock the doneq so no other thread flushes the Q. +		 */ +		ghd_doneq_pollmode_enter(cccp); +	} +#if defined(GHD_DEBUG) || defined(__lint) +	else { +		GDBG_START(("ghd_transport: non-polled" +			" cccp 0x%p gdevp 0x%p gtgtp 0x%p gcmdp 0x%p\n", +				cccp, gdevp, gtgtp, gcmdp)); +	} +#endif +	/* +	 * add this request to the tail of the waitq +	 */ +	gcmdp->cmd_waitq_level = 1; +	mutex_enter(&cccp->ccc_waitq_mutex); +	L2_add(&GDEV_QHEAD(gdevp), &gcmdp->cmd_q, gcmdp); + +	/* +	 * Add this request to the packet timer active list and start its +	 * abort timer. +	 */ +	gcmdp->cmd_state = GCMD_STATE_WAITQ; +	ghd_timer_start(cccp, gcmdp, timeout); + + +	/* +	 * Check the device wait queue throttle and perhaps move +	 * some requests to the end of the HBA wait queue. +	 */ +	ghd_waitq_shuffle_up(cccp, gdevp); + +	if (!polled) { +		/* +		 * See if the HBA mutex is available but use the +		 * tryenter so I don't deadlock. +		 */ +		if (!mutex_tryenter(&cccp->ccc_hba_mutex)) { +			/* The HBA mutex isn't available */ +			GDBG_START(("ghd_transport: !mutex cccp 0x%p\n", cccp)); +			mutex_exit(&cccp->ccc_waitq_mutex); +			return (TRAN_ACCEPT); +		} +		GDBG_START(("ghd_transport: got mutex cccp 0x%p\n", cccp)); + +		/* +		 * start as many requests as possible from the head +		 * of the HBA wait queue +		 */ + +		ghd_waitq_process_and_mutex_exit(cccp); + +		ASSERT(!mutex_owned(&cccp->ccc_hba_mutex)); +		ASSERT(!mutex_owned(&cccp->ccc_waitq_mutex)); + +		return (TRAN_ACCEPT); +	} + + +	/* +	 * If polled mode (FLAG_NOINTR specified in scsi_pkt flags), +	 * then ghd_poll() waits until the request completes or times out +	 * before returning. +	 */ + +	mutex_exit(&cccp->ccc_waitq_mutex); +	(void) ghd_poll(cccp, GHD_POLL_REQUEST, 0, gcmdp, gtgtp, intr_status); +	ghd_doneq_pollmode_exit(cccp); + +	mutex_enter(&cccp->ccc_waitq_mutex); +	ghd_waitq_process_and_mutex_exit(cccp); + +	/* call HBA's completion function but don't do callback to target */ +	(*cccp->ccc_hba_complete)(cccp->ccc_hba_handle, gcmdp, FALSE); + +	GDBG_START(("ghd_transport: polled done cccp 0x%p\n", cccp)); +	return (TRAN_ACCEPT); +} + +int ghd_reset_notify(ccc_t 	*cccp, +			gtgt_t *gtgtp, +			int 	flag, +			void 	(*callback)(caddr_t), +			caddr_t arg) +{ +	ghd_reset_notify_list_t *rnp; +	int rc = FALSE; + +	switch (flag) { + +	case SCSI_RESET_NOTIFY: + +		rnp = (ghd_reset_notify_list_t *)kmem_zalloc(sizeof (*rnp), +		    KM_SLEEP); +		rnp->gtgtp = gtgtp; +		rnp->callback = callback; +		rnp->arg = arg; + +		mutex_enter(&cccp->ccc_reset_notify_mutex); +		L2_add(&cccp->ccc_reset_notify_list, &rnp->l2_link, +		    (void *)rnp); +		mutex_exit(&cccp->ccc_reset_notify_mutex); + +		rc = TRUE; + +		break; + +	case SCSI_RESET_CANCEL: + +		mutex_enter(&cccp->ccc_reset_notify_mutex); +		for (rnp = (ghd_reset_notify_list_t *) +			L2_next(&cccp->ccc_reset_notify_list); +		    rnp != NULL; +		    rnp = (ghd_reset_notify_list_t *)L2_next(&rnp->l2_link)) { +			if (rnp->gtgtp == gtgtp && +			    rnp->callback == callback && +			    rnp->arg == arg) { +				L2_delete(&rnp->l2_link); +				kmem_free(rnp, sizeof (*rnp)); +				rc = TRUE; +			} +		} +		mutex_exit(&cccp->ccc_reset_notify_mutex); +		break; + +	default: +		rc = FALSE; +		break; +	} + +	return (rc); +} + +/* + * freeze the HBA waitq output (see ghd_waitq_process_and_mutex_hold), + * presumably because of a SCSI reset, for delay milliseconds. + */ + +void +ghd_freeze_waitq(ccc_t *cccp, int delay) +{ +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); + +	/* freeze the waitq for delay milliseconds */ + +	mutex_enter(&cccp->ccc_waitq_mutex); +	cccp->ccc_waitq_freezetime = ddi_get_lbolt(); +	cccp->ccc_waitq_freezedelay = delay; +	cccp->ccc_waitq_frozen = 1; +	mutex_exit(&cccp->ccc_waitq_mutex); +} + +void +ghd_queue_hold(ccc_t *cccp) +{ +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); + +	mutex_enter(&cccp->ccc_waitq_mutex); +	cccp->ccc_waitq_held = 1; +	mutex_exit(&cccp->ccc_waitq_mutex); +} + +void +ghd_queue_unhold(ccc_t *cccp) +{ +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); + +	mutex_enter(&cccp->ccc_waitq_mutex); +	cccp->ccc_waitq_held = 0; +	mutex_exit(&cccp->ccc_waitq_mutex); +} + + + +/* + * Trigger previously-registered reset notifications + */ + +void +ghd_trigger_reset_notify(ccc_t *cccp) +{ +	gcmd_t *gcmdp; + +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); + +	/* create magic doneq entry */ + +	gcmdp = ghd_gcmd_alloc((gtgt_t *)NULL, 0, TRUE); +	gcmdp->cmd_flags = GCMDFLG_RESET_NOTIFY; + +	/* put at head of doneq so it's processed ASAP */ + +	GHD_DONEQ_PUT_HEAD(cccp, gcmdp); +} diff --git a/usr/src/uts/common/io/dktp/hba/ghd/ghd.h b/usr/src/uts/intel/io/dktp/hba/ghd/ghd.h index ab7fd4f72b..3c8b45ed3e 100644 --- a/usr/src/uts/common/io/dktp/hba/ghd/ghd.h +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd.h @@ -2,9 +2,8 @@   * CDDL HEADER START   *   * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License").  You may not use this file except in compliance - * with the License. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License.   *   * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE   * or http://www.opensolaris.org/os/licensing. @@ -19,6 +18,7 @@   *   * CDDL HEADER END   */ +  /*   * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.   * Use is subject to license terms. diff --git a/usr/src/uts/intel/io/dktp/hba/ghd/ghd_debug.c b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_debug.c new file mode 100644 index 0000000000..068dbd9f76 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_debug.c @@ -0,0 +1,104 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include "ghd.h" +#include "ghd_debug.h" + +#if !(defined(GHD_DEBUG) || defined(__lint)) +ulong_t	ghd_debug_flags = 0; +#else +ulong_t	ghd_debug_flags = GDBG_FLAG_ERROR +		/*	| GDBG_FLAG_WAITQ	*/ +		/*	| GDBG_FLAG_INTR	*/ +		/*	| GDBG_FLAG_START	*/ +		/*	| GDBG_FLAG_WARN	*/ +		/*	| GDBG_FLAG_DMA		*/ +		/*	| GDBG_FLAG_PEND_INTR	*/ +		/*	| GDBG_FLAG_START	*/ +		/*	| GDBG_FLAG_PKT		*/ +		/*	| GDBG_FLAG_INIT	*/ +			; +#endif + +void +ghd_err(const char *fmt, ...) +{ +	va_list	ap; + +	va_start(ap, fmt); +	vcmn_err(CE_CONT, fmt, ap); +	va_end(ap); +} + +#if defined(GHD_DEBUG) +#define	PRF	prom_printf + +static void +ghd_dump_ccc(ccc_t *P) +{ +	PRF("nextp 0x%p tmrp 0x%p label 0x%p &mutex 0x%p\n", +		P->ccc_nextp, P->ccc_tmrp, P->ccc_label, &P->ccc_activel_mutex); +	PRF("&activel 0x%p dip 0x%p iblock 0x%p\n", +		&P->ccc_activel, P->ccc_hba_dip, P->ccc_iblock); +	PRF("softid 0x%p &hba_mutext 0x%p\n poll 0x%p\n", +		P->ccc_soft_id, &P->ccc_hba_mutex, &P->ccc_hba_pollmode); +	PRF("&devs 0x%p &waitq_mutex 0x%p &waitq 0x%p\n", +		&P->ccc_devs, &P->ccc_waitq_mutex, &P->ccc_waitq); +	PRF("waitq_freezetime 0x%p waitq_freezedelay %p\n", +		&P->ccc_waitq_freezetime, &P->ccc_waitq_freezedelay); +	PRF("dq softid 0x%p &dq_mutex 0x%p &doneq 0x%p\n", +		P->ccc_doneq_softid, &P->ccc_doneq_mutex, &P->ccc_doneq); +	PRF("handle 0x%p &ccballoc 0x%p\n", +		P->ccc_hba_handle, &P->ccc_ccballoc); +	PRF("hba_reset_notify_callback 0x%p notify_list 0x%p mutex 0x%p\n", +		P->ccc_hba_reset_notify_callback, &P->ccc_reset_notify_list, +		&P->ccc_reset_notify_mutex); +} + + +static void +ghd_dump_gcmd(gcmd_t *P) +{ +	PRF("cmd_q nextp 0x%p prevp 0x%p private 0x%p\n", +	    P->cmd_q.l2_nextp, P->cmd_q.l2_prevp, P->cmd_q.l2_private); +	PRF("state %ul wq lev %ld flags 0x%x\n", +	    P->cmd_state, P->cmd_waitq_level, P->cmd_flags); +	PRF("timer Q nextp 0x%p prevp 0x%p private 0x%p\n", +	    P->cmd_timer_link.l2_nextp, P->cmd_timer_link.l2_prevp, +	    P->cmd_timer_link.l2_private); + +	PRF("start time 0x%lx timeout 0x%lx hba private 0x%p pktp 0x%p\n", +	    P->cmd_start_time, P->cmd_timeout, P->cmd_private, P->cmd_pktp); +	PRF("gtgtp 0x%p dma_flags 0x%x dma_handle 0x%p dmawin 0x%p " +	    "dmaseg 0x%p\n", P->cmd_gtgtp, P->cmd_dma_flags, +	    P->cmd_dma_handle, P->cmd_dmawin, P->cmd_dmaseg); +	PRF("wcount %d windex %d ccount %d cindex %d\n", +	    P->cmd_wcount, P->cmd_windex, P->cmd_ccount, P->cmd_cindex); +	PRF("totxfer %ld\n", P->cmd_totxfer); +} +#endif diff --git a/usr/src/uts/common/io/dktp/hba/ghd/ghd_debug.h b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_debug.h index 2c7087419a..cc30fadfb4 100644 --- a/usr/src/uts/common/io/dktp/hba/ghd/ghd_debug.h +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_debug.h @@ -2,9 +2,8 @@   * CDDL HEADER START   *   * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License").  You may not use this file except in compliance - * with the License. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License.   *   * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE   * or http://www.opensolaris.org/os/licensing. @@ -19,6 +18,7 @@   *   * CDDL HEADER END   */ +  /*   * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.   * Use is subject to license terms. diff --git a/usr/src/uts/intel/io/dktp/hba/ghd/ghd_dma.c b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_dma.c new file mode 100644 index 0000000000..45333efb48 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_dma.c @@ -0,0 +1,245 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2005 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include "ghd.h" + +void +ghd_dmafree_attr(gcmd_t *gcmdp) +{ +	GDBG_DMA(("ghd_dma_attr_free: gcmdp 0x%p\n", gcmdp)); + +	if (gcmdp->cmd_dma_handle != NULL) { +		if (ddi_dma_unbind_handle(gcmdp->cmd_dma_handle) != +		    DDI_SUCCESS) +			cmn_err(CE_WARN, "ghd dma free attr: " +			    "unbind handle failed"); +		ddi_dma_free_handle(&gcmdp->cmd_dma_handle); +		GDBG_DMA(("ghd_dma_attr_free: ddi_dma_free 0x%p\n", gcmdp)); +		gcmdp->cmd_dma_handle = NULL; +		gcmdp->cmd_ccount = 0; +		gcmdp->cmd_totxfer = 0; +	} +} + + +int +ghd_dma_buf_bind_attr(ccc_t		*cccp, +			gcmd_t		*gcmdp, +			struct buf	*bp, +			int		 dma_flags, +			int		(*callback)(), +			caddr_t		 arg, +			ddi_dma_attr_t	*sg_attrp) +{ +	int	 status; + +	GDBG_DMA(("ghd_dma_attr_get: start: gcmdp 0x%p sg_attrp 0x%p\n", +		gcmdp, sg_attrp)); + + +	/* +	 * First time, need to establish the handle. +	 */ + +	ASSERT(gcmdp->cmd_dma_handle == NULL); + +	status = ddi_dma_alloc_handle(cccp->ccc_hba_dip, sg_attrp, callback, +		arg, &gcmdp->cmd_dma_handle); + +	if (status != DDI_SUCCESS) { +		bp->b_error = 0; +		return (FALSE); +	} + +	status = ddi_dma_buf_bind_handle(gcmdp->cmd_dma_handle, bp, dma_flags, +		    callback, arg, &gcmdp->cmd_first_cookie, +		    &gcmdp->cmd_ccount); + +	GDBG_DMA(("ghd_dma_attr_get: setup: gcmdp 0x%p status %d h 0x%p " +		"c 0x%d\n", gcmdp, status, gcmdp->cmd_dma_handle, +			gcmdp->cmd_ccount)); + +	switch (status) { +	case DDI_DMA_MAPPED: +		/* enable first (and only) call to ddi_dma_getwin */ +		gcmdp->cmd_wcount = 1; +		break; + +	case DDI_DMA_PARTIAL_MAP: +		/* enable first call to ddi_dma_getwin */ +		if (ddi_dma_numwin(gcmdp->cmd_dma_handle, &gcmdp->cmd_wcount) != +								DDI_SUCCESS) { +			bp->b_error = 0; +			ddi_dma_free_handle(&gcmdp->cmd_dma_handle); +			gcmdp->cmd_dma_handle = NULL; +			return (FALSE); +		} +		break; + +	case DDI_DMA_NORESOURCES: +		bp->b_error = 0; +		ddi_dma_free_handle(&gcmdp->cmd_dma_handle); +		gcmdp->cmd_dma_handle = NULL; +		return (FALSE); + +	case DDI_DMA_TOOBIG: +		bioerror(bp, EINVAL); +		ddi_dma_free_handle(&gcmdp->cmd_dma_handle); +		gcmdp->cmd_dma_handle = NULL; +		return (FALSE); + +	case DDI_DMA_NOMAPPING: +	case DDI_DMA_INUSE: +	default: +		bioerror(bp, EFAULT); +		ddi_dma_free_handle(&gcmdp->cmd_dma_handle); +		gcmdp->cmd_dma_handle = NULL; +		return (FALSE); +	} + +	/* initialize the loop controls for ghd_dmaget_next_attr() */ +	gcmdp->cmd_windex = 0; +	gcmdp->cmd_cindex = 0; +	gcmdp->cmd_totxfer = 0; +	gcmdp->cmd_dma_flags = dma_flags; +	gcmdp->use_first = 1; +	return (TRUE); +} + + +uint_t +ghd_dmaget_next_attr(ccc_t *cccp, gcmd_t *gcmdp, long max_transfer_cnt, +    int sg_size, ddi_dma_cookie_t cookie) +{ +	ulong_t	toxfer = 0; +	int	num_segs = 0; +	int	single_seg; + +	GDBG_DMA(("ghd_dma_attr_get: start: gcmdp 0x%p h 0x%p c 0x%x\n", +			gcmdp, gcmdp->cmd_dma_handle, gcmdp->cmd_ccount)); + +	/* +	 * Disable single-segment Scatter/Gather option +	 * if can't do this transfer in a single segment, +	 */ +	if (gcmdp->cmd_cindex + 1 < gcmdp->cmd_ccount) { +		single_seg = FALSE; +	} else { +		single_seg = TRUE; +	} + + +	for (;;) { +		/* +		 * call the controller specific S/G function +		 */ +		(*cccp->ccc_sg_func)(gcmdp, &cookie, single_seg, num_segs); + +		/* take care of the loop-bookkeeping */ +		toxfer += cookie.dmac_size; +		num_segs++; +		gcmdp->cmd_cindex++; + +		/* +		 * if this was the last cookie in the current window +		 * set the loop controls start the next window and +		 * exit so the HBA can do this partial transfer +		 */ +		if (gcmdp->cmd_cindex >= gcmdp->cmd_ccount) { +			gcmdp->cmd_windex++; +			gcmdp->cmd_cindex = 0; +			break; +		} +		ASSERT(single_seg == FALSE); + +		if (toxfer >= max_transfer_cnt) +			break; + +		if (num_segs >= sg_size) +			break; + +		ddi_dma_nextcookie(gcmdp->cmd_dma_handle, &cookie); +	} + +	gcmdp->cmd_totxfer += toxfer; + +	return (toxfer); +} + + + +int +ghd_dmaget_attr(ccc_t		*cccp, +		gcmd_t		*gcmdp, +		long		count, +		int		sg_size, +		uint_t		*xfer) +{ +	int	status; +	ddi_dma_cookie_t cookie; + +	*xfer = 0; + + +	if (gcmdp->use_first == 1) { +		cookie = gcmdp->cmd_first_cookie; +		gcmdp->use_first = 0; +	} else if (gcmdp->cmd_windex >= gcmdp->cmd_wcount) { +		/* +		 * reached the end of buffer. This should not happen. +		 */ +		ASSERT(gcmdp->cmd_windex < gcmdp->cmd_wcount); +		return (FALSE); + +	} else if (gcmdp->cmd_cindex == 0) { +		off_t	offset; +		size_t	length; + +		/* +		 * start the next window, and get its first cookie +		 */ +		status = ddi_dma_getwin(gcmdp->cmd_dma_handle, +				gcmdp->cmd_windex, &offset, &length, +				&cookie, &gcmdp->cmd_ccount); +		if (status != DDI_SUCCESS) +			return (FALSE); + +	} else { +		/* +		 * get the next cookie in the current window +		 */ +		ddi_dma_nextcookie(gcmdp->cmd_dma_handle, &cookie); +	} + +	/* +	 * start the Scatter/Gather loop passing in the first +	 * cookie obtained above +	 */ +	*xfer = ghd_dmaget_next_attr(cccp, gcmdp, count, sg_size, cookie); +	return (TRUE); +} diff --git a/usr/src/uts/common/io/dktp/hba/ghd/ghd_dma.h b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_dma.h index ad785d1272..ad785d1272 100644 --- a/usr/src/uts/common/io/dktp/hba/ghd/ghd_dma.h +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_dma.h diff --git a/usr/src/uts/intel/io/dktp/hba/ghd/ghd_gcmd.c b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_gcmd.c new file mode 100644 index 0000000000..26c1042505 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_gcmd.c @@ -0,0 +1,102 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 1999 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include "ghd.h" + +/* + * Round up all allocations so that we can guarantee + * long-long alignment.  This is the same alignment + * provided by kmem_alloc(). + */ +#define	ROUNDUP(x)	(((x) + 0x07) & ~0x07) + +/* + * Private wrapper for gcmd_t + */ +typedef struct gw_gcmd_and_length { +	gcmd_t	gcmd;		/* this must be first */ +	int	glen;		/* length includes HBA private area */ +}gw_t; + +/* + * round up the size so the HBA private area is on a 8 byte boundary + */ +#define	GW_PADDED_LENGTH	ROUNDUP(sizeof (gw_t)) + +typedef struct gcmd_padded_wrapper { +	union { +		gw_t	gw; +		char	gw_pad[GW_PADDED_LENGTH]; + +	} gwrap; +} gwrap_t; + +/* + * Allocate a gcmd_t wrapper and HBA private area + */ + +gcmd_t * +ghd_gcmd_alloc(gtgt_t	*gtgtp, +		int	ccblen, +		int	sleep) +{ +	gwrap_t	*gwp; +	gcmd_t	*gcmdp; +	int	 gwrap_len; + +	ccblen = ROUNDUP(ccblen); +	gwrap_len = sizeof (gwrap_t) + ccblen; +	gwp = kmem_zalloc(gwrap_len, (sleep ? KM_SLEEP : KM_NOSLEEP)); +	if (gwp == NULL) { +		ASSERT(sleep == FALSE); +		return (NULL); +	} + +	/* save the total length for the free function */ +	gwp->gwrap.gw.glen = gwrap_len; + +	/* +	 * save the ptr to HBA private area and initialize all +	 * the gcmd_t members and save +	 */ +	gcmdp = &gwp->gwrap.gw.gcmd; +	GHD_GCMD_INIT(gcmdp, (void *)(gwp + 1), gtgtp); +	return (gcmdp); +} + + + +/* + * Free the gcmd_t wrapper and HBA private area + */ + +void +ghd_gcmd_free(gcmd_t *gcmdp) +{ +	kmem_free(gcmdp, ((gw_t *)gcmdp)->glen); +} diff --git a/usr/src/uts/intel/io/dktp/hba/ghd/ghd_queue.c b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_queue.c new file mode 100644 index 0000000000..5529cfa637 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_queue.c @@ -0,0 +1,206 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 1999 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/debug.h> +#include "ghd_queue.h" + + + +void +L1_add(L1_t *lp, L1el_t *lep, void *datap) +{ +	/* init the list element */ +	lep->le_nextp = NULL; +	lep->le_datap = datap; + +	if (!lp->l1_tailp) { +		/* list is empty */ +		lp->l1_headp = lep; +	} else { +		/* add it to the tailend */ +		lp->l1_tailp->le_nextp = lep; +	} + +	lp->l1_tailp = lep; +} + + +/* + * L1Delete() + * + *	Remove a specific entry from a singly-linked list. + * + */ + +void +L1_delete(L1_t *lp, L1el_t *lep) +{ +	L1el_t	*prevp; + +	if (lp->l1_headp == lep) { +		/* it's the first entry in the list */ +		if ((lp->l1_headp = lep->le_nextp) == NULL) { +			/* the list is now empty */ +			lp->l1_tailp = NULL; +		} +		return; +	} + +	for (prevp = lp->l1_headp; prevp != NULL; prevp = prevp->le_nextp) { +		if (prevp->le_nextp == lep) { +			if ((prevp->le_nextp = lep->le_nextp) == NULL) +				lp->l1_tailp = prevp; +			return; +		} +	} +	/* its not on this list */ +} + + +/* + * L1_remove() + * + *	Remove the entry at the head of the list (if any). + * + */ + +void * +L1_remove(L1_t *lp) +{ +	L1el_t	*lep; + +	/* pop the first one off the list head */ +	if ((lep = lp->l1_headp) == NULL) { +		return (NULL); +	} + +	/* if the list is now empty fix the tail pointer */ +	if ((lp->l1_headp = lep->le_nextp) == NULL) +		lp->l1_tailp = NULL; + +	lep->le_nextp = NULL; + +	return (lep->le_datap); +} + + +void +L2_add(L2el_t *headp, L2el_t *elementp, void *private) +{ + +	ASSERT(headp != NULL && elementp != NULL); +	ASSERT(headp->l2_nextp != NULL); +	ASSERT(headp->l2_prevp != NULL); + +	elementp->l2_private = private; + +	elementp->l2_nextp = headp; +	elementp->l2_prevp = headp->l2_prevp; +	headp->l2_prevp->l2_nextp = elementp; +	headp->l2_prevp = elementp; +} + +void +L2_delete(L2el_t *elementp) +{ + +	ASSERT(elementp != NULL); +	ASSERT(elementp->l2_nextp != NULL); +	ASSERT(elementp->l2_prevp != NULL); +	ASSERT(elementp->l2_nextp->l2_prevp == elementp); +	ASSERT(elementp->l2_prevp->l2_nextp == elementp); + +	elementp->l2_prevp->l2_nextp = elementp->l2_nextp; +	elementp->l2_nextp->l2_prevp = elementp->l2_prevp; + +	/* link it to itself in case someone does a double delete */ +	elementp->l2_nextp = elementp; +	elementp->l2_prevp = elementp; +} + + +void +L2_add_head(L2el_t *headp, L2el_t *elementp, void *private) +{ + +	ASSERT(headp != NULL && elementp != NULL); +	ASSERT(headp->l2_nextp != NULL); +	ASSERT(headp->l2_prevp != NULL); + +	elementp->l2_private = private; + +	elementp->l2_prevp = headp; +	elementp->l2_nextp = headp->l2_nextp; +	headp->l2_nextp->l2_prevp = elementp; +	headp->l2_nextp = elementp; +} + + + +/* + * L2_remove() + * + *	Remove the entry from the head of the list (if any). + * + */ + +void * +L2_remove_head(L2el_t *headp) +{ +	L2el_t *elementp; + +	ASSERT(headp != NULL); + +	if (L2_EMPTY(headp)) +		return (NULL); + +	elementp = headp->l2_nextp; + +	headp->l2_nextp = elementp->l2_nextp; +	elementp->l2_nextp->l2_prevp = headp; + +	/* link it to itself in case someone does a double delete */ +	elementp->l2_nextp = elementp; +	elementp->l2_prevp = elementp; + +	return (elementp->l2_private); +} + +void * +L2_next(L2el_t *elementp) +{ + +	ASSERT(elementp != NULL); + +	if (L2_EMPTY(elementp)) +		return (NULL); +	return (elementp->l2_nextp->l2_private); +} diff --git a/usr/src/uts/common/io/dktp/hba/ghd/ghd_queue.h b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_queue.h index b76f6c5240..472ba1eb44 100644 --- a/usr/src/uts/common/io/dktp/hba/ghd/ghd_queue.h +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_queue.h @@ -2,9 +2,8 @@   * CDDL HEADER START   *   * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License").  You may not use this file except in compliance - * with the License. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License.   *   * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE   * or http://www.opensolaris.org/os/licensing. @@ -19,15 +18,16 @@   *   * CDDL HEADER END   */ +  /* - * Copyright (c) 1999, by Sun Microsystems, Inc. - * All rights reserved. + * Copyright 1999 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms.   */  #ifndef _GHD_QUEUE_H  #define	_GHD_QUEUE_H -#pragma	ident	"%Z%%M%	%I%	%E% SMI" +#pragma ident	"%Z%%M%	%I%	%E% SMI"  #ifdef	__cplusplus  extern "C" { diff --git a/usr/src/uts/intel/io/dktp/hba/ghd/ghd_scsa.c b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_scsa.c new file mode 100644 index 0000000000..ff479c2eab --- /dev/null +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_scsa.c @@ -0,0 +1,261 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2005 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include "ghd.h" + + +/* + * Local Function Prototypes + */ + +static	struct scsi_pkt	*ghd_pktalloc(ccc_t *cccp, struct scsi_address *ap, +				int cmdlen, int statuslen, int tgtlen, +				int (*callback)(), caddr_t arg, int ccblen); + +/* + * Round up all allocations so that we can guarantee + * long-long alignment.  This is the same alignment + * provided by kmem_alloc(). + */ +#define	ROUNDUP(x)	(((x) + 0x07) & ~0x07) + +/* + * Private wrapper for gcmd_t + */ + +/* + * round up the size so the HBA private area is on a 8 byte boundary + */ +#define	GW_PADDED_LENGTH	ROUNDUP(sizeof (gcmd_t)) + +typedef struct gcmd_padded_wrapper { +	union { +		gcmd_t	gw_gcmd; +		char	gw_pad[GW_PADDED_LENGTH]; + +	} gwrap; +} gwrap_t; + + + + +/*ARGSUSED*/ +void +ghd_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pktp) +{ +	gcmd_t *gcmdp = PKTP2GCMDP(pktp); +	int	status; + +	if (gcmdp->cmd_dma_handle) { +		status = ddi_dma_sync(gcmdp->cmd_dma_handle, 0, 0, +			(gcmdp->cmd_dma_flags & DDI_DMA_READ) ? +			DDI_DMA_SYNC_FORCPU : DDI_DMA_SYNC_FORDEV); +		if (status != DDI_SUCCESS) { +			cmn_err(CE_WARN, "ghd_tran_sync_pkt() fail\n"); +		} +	} +} + + +static struct scsi_pkt * +ghd_pktalloc(ccc_t	*cccp, +	struct scsi_address *ap, +	int	 cmdlen, +	int	 statuslen, +	int	 tgtlen, +	int	(*callback)(), +	caddr_t	 arg, +	int	ccblen) +{ +	gtgt_t		*gtgtp =  ADDR2GTGTP(ap); +	struct scsi_pkt	*pktp; +	gcmd_t		*gcmdp; +	gwrap_t		*gwp; +	int		 gwrap_len; + +	gwrap_len = sizeof (gwrap_t) + ROUNDUP(ccblen); + +	/* allocate everything from kmem pool */ +	pktp = scsi_hba_pkt_alloc(cccp->ccc_hba_dip, ap, cmdlen, statuslen, +	    tgtlen, gwrap_len, callback, arg); +	if (pktp == NULL) { +		return (NULL); +	} + +	/* get the ptr to the HBA specific buffer */ +	gwp = (gwrap_t *)(pktp->pkt_ha_private); + +	/* get the ptr to the GHD specific buffer */ +	gcmdp = &gwp->gwrap.gw_gcmd; + +	ASSERT((caddr_t)gwp == (caddr_t)gcmdp); + +	/* +	 * save the ptr to HBA private area and initialize the rest +	 * of the gcmd_t members +	 */ +	GHD_GCMD_INIT(gcmdp, (void *)(gwp + 1), gtgtp); + +	/* +	 * save the the scsi_pkt ptr in gcmd_t. +	 */ +	gcmdp->cmd_pktp = pktp; + +	/* +	 * callback to the HBA driver so it can initalize its +	 * buffer and return the ptr to my cmd_t structure which is +	 * probably embedded in its buffer. +	 */ + +	if (!(*cccp->ccc_ccballoc)(gtgtp, gcmdp, cmdlen, statuslen, tgtlen, +	    ccblen)) { +		scsi_hba_pkt_free(ap, pktp); +		return (NULL); +	} + +	return (pktp); +} + + + +/* + * packet free + */ +/*ARGSUSED*/ +void +ghd_pktfree(ccc_t		*cccp, +	struct scsi_address	*ap, +	struct scsi_pkt		*pktp) +{ +	GDBG_PKT(("ghd_pktfree: cccp 0x%p ap 0x%p pktp 0x%p\n", +	    cccp, ap, pktp)); + +	/* free any extra resources allocated by the HBA */ +	(*cccp->ccc_ccbfree)(PKTP2GCMDP(pktp)); + +	/* free the scsi_pkt and the GHD and HBA private areas */ +	scsi_hba_pkt_free(ap, pktp); +} + + +struct scsi_pkt * +ghd_tran_init_pkt_attr(ccc_t	*cccp, +			struct scsi_address *ap, +			struct scsi_pkt	*pktp, +			struct buf	*bp, +			int	 cmdlen, +			int	 statuslen, +			int	 tgtlen, +			int	 flags, +			int	(*callback)(), +			caddr_t	 arg, +			int	 ccblen, +			ddi_dma_attr_t	*sg_attrp) +{ +	gcmd_t	*gcmdp; +	int	 new_pkt; +	uint_t	xfercount; + +	ASSERT(callback == NULL_FUNC || callback == SLEEP_FUNC); + +	/* +	 * Allocate a pkt +	 */ +	if (pktp == NULL) { +		pktp = ghd_pktalloc(cccp, ap, cmdlen, statuslen, tgtlen, +		    callback, arg, ccblen); +		if (pktp == NULL) +			return (NULL); +		new_pkt = TRUE; + +	} else { +		new_pkt = FALSE; + +	} + +	gcmdp = PKTP2GCMDP(pktp); + +	GDBG_PKT(("ghd_tran_init_pkt_attr: gcmdp 0x%p dma_handle 0x%p\n", +	    gcmdp, gcmdp->cmd_dma_handle)); + +	/* +	 * free stale DMA window if necessary. +	 */ + +	if (cmdlen && gcmdp->cmd_dma_handle) { +		/* release the old DMA resources */ +		ghd_dmafree_attr(gcmdp); +	} + +	/* +	 * Set up dma info if there's any data and +	 * if the device supports DMA. +	 */ + +	GDBG_PKT(("ghd_tran_init_pkt: gcmdp 0x%p bp 0x%p limp 0x%p\n", +	    gcmdp, bp, sg_attrp)); + +	if (bp && bp->b_bcount && sg_attrp) { +		int	dma_flags; + +		/* check direction for data transfer */ +		if (bp->b_flags & B_READ) +			dma_flags = DDI_DMA_READ; +		else +			dma_flags = DDI_DMA_WRITE; + +		/* check dma option flags */ +		if (flags & PKT_CONSISTENT) +			dma_flags |= DDI_DMA_CONSISTENT; +		if (flags & PKT_DMA_PARTIAL) +			dma_flags |= DDI_DMA_PARTIAL; + +		if (gcmdp->cmd_dma_handle == NULL) { +			if (!ghd_dma_buf_bind_attr(cccp, gcmdp, bp, dma_flags, +				callback, arg, sg_attrp)) { +				if (new_pkt) +					ghd_pktfree(cccp, ap, pktp); +				return (NULL); +			} +		} + +		/* map the buffer and/or create the scatter/gather list */ +		if (!ghd_dmaget_attr(cccp, gcmdp, +			bp->b_bcount - gcmdp->cmd_totxfer, +			sg_attrp->dma_attr_sgllen, &xfercount)) { +			if (new_pkt) +				ghd_pktfree(cccp, ap, pktp); +			return (NULL); +		} +		pktp->pkt_resid = bp->b_bcount - gcmdp->cmd_totxfer; +	} else { +		pktp->pkt_resid = 0; +	} + +	return (pktp); +} diff --git a/usr/src/uts/common/io/dktp/hba/ghd/ghd_scsa.h b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_scsa.h index 2e622df39f..2e622df39f 100644 --- a/usr/src/uts/common/io/dktp/hba/ghd/ghd_scsa.h +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_scsa.h diff --git a/usr/src/uts/intel/io/dktp/hba/ghd/ghd_scsi.c b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_scsi.c new file mode 100644 index 0000000000..2c40084f21 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_scsi.c @@ -0,0 +1,73 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 1999 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include <sys/types.h> +#include <sys/byteorder.h> + + +/* + * functions to convert between host format and scsi format + */ +void +scsi_htos_3byte(unchar *ap, ulong nav) +{ +	*(ushort *)ap = (ushort)(((nav & 0xff0000) >> 16) | (nav & 0xff00)); +	ap[2] = (unchar)nav; +} + +void +scsi_htos_long(unchar *ap, ulong niv) +{ +	*(ulong *)ap = htonl(niv); +} + +void +scsi_htos_short(unchar *ap, ushort nsv) +{ +	*(ushort *)ap = htons(nsv); +} + +ulong +scsi_stoh_3byte(unchar *ap) +{ +	register ulong av = *(ulong *)ap; + +	return (((av & 0xff) << 16) | (av & 0xff00) | ((av & 0xff0000) >> 16)); +} + +ulong +scsi_stoh_long(ulong ai) +{ +	return (ntohl(ai)); +} + +ushort +scsi_stoh_short(ushort as) +{ +	return (ntohs(as)); +} diff --git a/usr/src/uts/common/io/dktp/hba/ghd/ghd_scsi.h b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_scsi.h index 5c41ed6215..40e6aa42f2 100644 --- a/usr/src/uts/common/io/dktp/hba/ghd/ghd_scsi.h +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_scsi.h @@ -2,9 +2,8 @@   * CDDL HEADER START   *   * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License").  You may not use this file except in compliance - * with the License. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License.   *   * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE   * or http://www.opensolaris.org/os/licensing. @@ -21,14 +20,15 @@   */  /* - * Copyright (c) 1996, Sun Microsystems, Inc. - * All Rights Reserved. + * Copyright 1996 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms.   */ +  #ifndef _GHD_SCSI_H  #define	_GHD_SCSI_H -#pragma	ident	"%Z%%M%	%I%	%E% SMI" +#pragma ident	"%Z%%M%	%I%	%E% SMI"  #ifdef	__cplusplus  extern "C" { diff --git a/usr/src/uts/intel/io/dktp/hba/ghd/ghd_timer.c b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_timer.c new file mode 100644 index 0000000000..5ab711f6d0 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_timer.c @@ -0,0 +1,898 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/ksynch.h> +#include <sys/scsi/conf/autoconf.h> +#include <sys/reboot.h> + +#include "ghd.h" + +/* + * Local functions + */ + +static	gcmd_t	*ghd_timeout_get(ccc_t *cccp); +static	int	 ghd_timeout_loop(ccc_t *cccp); +static	uint_t	 ghd_timeout_softintr(caddr_t arg); +static	void	 ghd_timeout(void *arg); +static	void	 ghd_timeout_disable(tmr_t *tmrp); +static	void	 ghd_timeout_enable(tmr_t *tmrp); + +/* + * Local data + */ +long	ghd_HZ; +static	kmutex_t tglobal_mutex; + +/* table of timeouts for abort processing steps */ +cmdstate_t ghd_timeout_table[GCMD_NSTATES]; + +/* This table indirectly initializes the ghd_timeout_table */ +struct { +	int		valid; +	cmdstate_t	state; +	long		value; +} ghd_time_inits[] = { +	{ TRUE, GCMD_STATE_ABORTING_CMD, 3 }, +	{ TRUE, GCMD_STATE_ABORTING_DEV, 3 }, +	{ TRUE, GCMD_STATE_RESETTING_DEV, 5 }, +	{ TRUE, GCMD_STATE_RESETTING_BUS, 10 }, +	{ TRUE, GCMD_STATE_HUNG, 60}, +	{ FALSE, 0, 0 },	/* spare entry */ +	{ FALSE, 0, 0 },	/* spare entry */ +	{ FALSE, 0, 0 },	/* spare entry */ +	{ FALSE, 0, 0 },	/* spare entry */ +	{ FALSE, 0, 0 }		/* spare entry */ +}; +int	ghd_ntime_inits = sizeof (ghd_time_inits) +				/ sizeof (ghd_time_inits[0]); + +/* + * Locally-used macros + */ + +/* + * Compare two gcmd_t's to see if they're for the same device (same gdev_t) + */ +#define	GCMD_SAME_DEV(gcmdp1, gcmdp2)		\ +	(GCMDP2GDEVP(gcmdp1) == GCMDP2GDEVP(gcmdp2)) + +/* + * Compare two gcmd_t's to see if they're for the same bus (same HBA inst) + */ +#define	GCMD_SAME_BUS(gcmdp1, gcmdp2)		\ +	(GCMDP2CCCP(gcmdp1) == GCMDP2CCCP(gcmdp2)) + + +/* + * Update state of gcmdp (in one direction, increasing state number, only) + */ +#define	GCMD_UPDATE_STATE(gcmdp, newstate) 		\ +{							\ +	if ((gcmdp)->cmd_state < (newstate)) {		\ +		((gcmdp)->cmd_state = (newstate));	\ +	}						\ +} + +#ifdef ___notyet___ + +#include <sys/modctl.h> +extern struct mod_ops mod_miscops; +static struct modlmisc modlmisc = { +	&mod_miscops,	/* Type of module */ +	"CCB Timeout Utility Routines" +}; +static struct modlinkage modlinkage = { +	MODREV_1, (void *)&modlmisc, NULL +}; + +/* + * If this is a loadable module then there's a single CCB timer configure + * structure for all HBA drivers (rather than one per HBA driver). + */ +static	tmr_t	tmr_conf; + +int +_init() +{ +	int	err; + +	ghd_timer_init(&tmr_conf, 0); +	return ((err = mod_install(&modlinkage)) != 0) +		ghd_timer_fini(&tmr_conf); +	return (err); +} + +int +_fini() +{ +	int	err; + +	if ((err = mod_remove(&modlinkage)) == 0) +		ghd_timer_fini(&tmr_conf); +	return (err); +} + +int +_info(struct modinfo *modinfop) +{ +	return (mod_info(&modlinkage, modinfop)); +} + +#endif /* ___notyet___ */ + + + +/* + * + * ghd_timeout_loop() + * + *	Check the CCB timer value for every active CCB for this + * HBA driver instance. + * + *	This function is called both by the ghd_timeout() interrupt + * handler when called via the timer callout, and by ghd_timer_poll() + * while procesing "polled" (FLAG_NOINTR) requests. + * + * 	The ccc_activel_mutex is held while a CCB list is being scanned. + * This prevents the HBA driver's transport or interrupt functions + * from changing the active CCB list. But we wake up very infrequently + * and do as little as possible so it shouldn't affect performance. + * + */ + +static int +ghd_timeout_loop(ccc_t *cccp) +{ +	int	 got_any = FALSE; +	gcmd_t	*gcmdp; +	ulong_t	 lbolt; + +	mutex_enter(&cccp->ccc_activel_mutex); +	lbolt = ddi_get_lbolt(); +	gcmdp = (gcmd_t *)L2_next(&cccp->ccc_activel); +	while (gcmdp) { +		/* +		 * check to see if this one has timed out +		 */ +		if ((gcmdp->cmd_timeout > 0) && +		    (lbolt - gcmdp->cmd_start_time >= gcmdp->cmd_timeout)) { +			got_any = TRUE; +		} +		gcmdp = (gcmd_t *)L2_next(&gcmdp->cmd_timer_link); +	} +	mutex_exit(&cccp->ccc_activel_mutex); +	return (got_any); +} + +/* + * + * ghd_timeout() + * + *	Called every t_ticks ticks to scan the CCB timer lists + * + *	The t_mutex mutex is held the entire time this routine is active. + *	It protects the list of ccc_t's. + * + *	The list of cmd_t's is protected by the ccc_activel_mutex mutex + *	in the ghd_timeout_loop() routine. + * + * 	We also check to see if the waitq is frozen, and if so, + * 	adjust our timeout to call back sooner if necessary (to + * 	unfreeze the waitq as soon as possible). + * + * + *	+------------+ + *	|   tmr_t    |----+ + *	+------------+    | + *			  | + *			  V + *			  +---------+ + *			  |  ccc_t  |----+ + *			  +---------+    | + *			  |		 V + *			  |		 +--------+   +--------+ + *			  |		 | gcmd_t |-->| gcmd_t |--> ... + *			  |		 +--------+   +--------+ + *			  V + *			  +---------+ + *			  |  ccc_t  |----+ + *			  +---------+    | + *			  |		 V + *			  |		 +--------+ + *			  |		 | gcmd_t | + *			  V		 +--------+ + *			  ... + * + * + * + */ + +static void +ghd_timeout(void *arg) +{ +	tmr_t	*tmrp = (tmr_t *)arg; +	ccc_t	*cccp; +	clock_t	ufdelay_curr; +	clock_t	lbolt, delay_in_hz; +	clock_t	resched = (clock_t)0x7FFFFFFF; + +	/* +	 * Each HBA driver instance has a separate CCB timer list.  Skip +	 * timeout processing if there are no more active timeout lists +	 * to process.  (There are no lists only if there are no attached +	 * HBA instances; the list still exists if there are no outstanding +	 * active commands.) +	 */ +	mutex_enter(&tmrp->t_mutex); +	if ((cccp = tmrp->t_ccc_listp) == NULL) { +		mutex_exit(&tmrp->t_mutex); +		return; +	} + +	lbolt = ddi_get_lbolt(); + +	do { +		/* +		 * If any active CCBs on this HBA have timed out +		 * then kick off the HBA driver's softintr +		 * handler to do the timeout processing +		 */ +		if (ghd_timeout_loop(cccp)) { +			cccp->ccc_timeout_pending = 1; +			ddi_trigger_softintr(cccp->ccc_soft_id); +		} + +		/* Record closest unfreeze time for use in next timeout */ + +		mutex_enter(&cccp->ccc_waitq_mutex); +		if (cccp->ccc_waitq_frozen) { + +			delay_in_hz = +			    drv_usectohz(cccp->ccc_waitq_freezedelay * 1000); +			ufdelay_curr = delay_in_hz - +			    (lbolt - cccp->ccc_waitq_freezetime); + +			if (ufdelay_curr < resched) +				resched = ufdelay_curr; + +			/* frozen; trigger softintr to maybe unfreeze */ +			ddi_trigger_softintr(cccp->ccc_soft_id); +		} +		mutex_exit(&cccp->ccc_waitq_mutex); + +	} while ((cccp = cccp->ccc_nextp) != NULL); + +	/* don't allow any unfreeze delays to increase the timeout delay */ +	if (resched > tmrp->t_ticks) +		resched = tmrp->t_ticks; + +	/* re-establish the timeout callback */ +	tmrp->t_timeout_id = timeout(ghd_timeout, (void *)tmrp, resched); + +	mutex_exit(&tmrp->t_mutex); +} + + +/* + * + * ghd_timer_newstate() + * + *	The HBA mutex is held by my caller. + * + */ + +void +ghd_timer_newstate(ccc_t *cccp, gcmd_t *gcmdp, gtgt_t *gtgtp, +    gact_t action, int calltype) +{ +	gact_t	next_action; +	cmdstate_t next_state; +	char	*msgp; +	long	new_timeout; +	int	(*func)(void *, gcmd_t *, gtgt_t *, gact_t, int); +	void	*hba_handle; +	gcmd_t	gsav; +	int	gsav_used = 0; +	gcmd_t	*gcmdp_scan; + +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); + +#ifdef	DEBUG +	/* it shouldn't be on the timer active list */ +	if (gcmdp != NULL) { +		L2el_t	*lp = &gcmdp->cmd_timer_link; +		ASSERT(lp->l2_nextp == lp); +		ASSERT(lp->l2_prevp == lp); +	} +#endif + +	func = cccp->ccc_timeout_func; +	hba_handle = cccp->ccc_hba_handle; + +	for (;;) { +		switch (action) { +		case GACTION_EARLY_ABORT: +			/* done before it started */ +			ASSERT(gcmdp != NULL); +			msgp = "early abort"; +			next_state = GCMD_STATE_DONEQ; +			next_action = GACTION_ABORT_CMD; +			break; + +		case GACTION_EARLY_TIMEOUT: +			/* done before it started */ +			ASSERT(gcmdp != NULL); +			msgp = "early timeout"; +			next_state = GCMD_STATE_DONEQ; +			next_action = GACTION_ABORT_CMD; +			break; + +		case GACTION_ABORT_CMD: +			msgp = "abort request"; +			ASSERT(gcmdp != NULL); +			next_state = GCMD_STATE_ABORTING_CMD; +			next_action = GACTION_ABORT_DEV; +			break; + +		case GACTION_ABORT_DEV: +			msgp = "abort device"; +			next_state = GCMD_STATE_ABORTING_DEV; +			next_action = GACTION_RESET_TARGET; +			break; + +		case GACTION_RESET_TARGET: +			msgp = "reset target"; +			next_state = GCMD_STATE_RESETTING_DEV; +			next_action = GACTION_RESET_BUS; +			break; + +		case GACTION_RESET_BUS: +			msgp = "reset bus"; +			next_state = GCMD_STATE_RESETTING_BUS; +			next_action = GACTION_INCOMPLETE; +			break; + +		case GACTION_INCOMPLETE: +		default: +			/* be verbose about HBA resets */ +			GDBG_ERROR(("?ghd_timer_newstate: HBA reset failed " +			    "hba 0x%p gcmdp 0x%p gtgtp 0x%p\n", +			    (void *)hba_handle, (void *)gcmdp, (void *)gtgtp)); +			/* +			 * When all else fails, punt. +			 * +			 * We're in big trouble if we get to this point. +			 * Maybe we should try to re-initialize the HBA. +			 */ +			msgp = "HBA reset"; +			next_state = GCMD_STATE_HUNG; +			next_action = GACTION_INCOMPLETE; +			break; +		} + +		/* +		 * I want to see target requests only if verbose, but +		 * scsi_log() only prints the device pathname if level +		 * is CE_WARN or CE_PANIC...so I guess we can't use +		 * scsi_log for TGTREQ messages, or they must come to +		 * the console.  How silly.  Looking for "verbose boot" +		 * is non-DDI-compliant, but let's do it anyway. +		 */ + +		if (calltype == GHD_TGTREQ) { +			if ((boothowto & RB_VERBOSE)) { +				scsi_log(cccp->ccc_hba_dip, cccp->ccc_label, +				    CE_WARN, +				    "target request: %s, target=%d lun=%d", +				    msgp, gtgtp->gt_target, gtgtp->gt_lun); +			} +		} else { +			scsi_log(cccp->ccc_hba_dip, cccp->ccc_label, CE_WARN, +			    "timeout: %s, target=%d lun=%d", msgp, +				gtgtp->gt_target, gtgtp->gt_lun); +		} + +		/* +		 * Before firing off the HBA action, restart the timer +		 * using the timeout value from ghd_timeout_table[]. +		 * +		 * The table entries should never restart the timer +		 * for the GHD_STATE_IDLE and GHD_STATE_DONEQ states. +		 * +		 */ +		if (gcmdp) { +			gcmdp->cmd_state = next_state; +			new_timeout = ghd_timeout_table[gcmdp->cmd_state]; +			if (new_timeout != 0) +				ghd_timer_start(cccp, gcmdp, new_timeout); + +			/* save a copy in case action function frees it */ +			gsav = *gcmdp; +			gsav_used = 1; +		} + +		if (action == GACTION_RESET_BUS && cccp->ccc_waitq_frozen) { +			GDBG_WARN(("avoiding bus reset while waitq frozen\n")); +			break; +		} + +		/* invoke the HBA's action function */ +		if ((*func)(hba_handle, gcmdp, gtgtp, action, calltype)) { +			/* if it took wait for an interrupt or timeout */ +			break; +		} +		/* +		 * if the HBA reset fails leave the retry +		 * timer running and just exit. +		 */ +		if (action == GACTION_INCOMPLETE) +			return; + +		/* all other failures cause transition to next action */ +		if (gcmdp != NULL && new_timeout != 0) { +			/* +			 * But stop the old timer prior to +			 * restarting a new timer because each step may +			 * have a different timeout value. +			 */ +			GHD_TIMER_STOP(cccp, gcmdp); +		} +		action = next_action; +	} + +	/* +	 * HBA action function is done with gsav (if used) +	 * or gtgtp/cccp (if gsav not used).  We need to mark other +	 * outstanding requests if they were affected by this action +	 * (say, a device reset which also cancels all outstanding +	 * requests on this device) to prevent multiple timeouts/HBA +	 * actions for the same device or bus condition.  Scan the timer +	 * list (all active requests) and update states as necessary. +	 * Hold the activel_mutex while scanning the active list.  Check +	 * for either same dev/bus as gsav (if used) or for same +	 * dev/bus as gtgtp or cccp (if gsav is not used). +	 */ + +	mutex_enter(&cccp->ccc_activel_mutex); + +	for (gcmdp_scan = (gcmd_t *)L2_next(&cccp->ccc_activel); +	    gcmdp_scan != NULL; +	    gcmdp_scan = (gcmd_t *)L2_next(&gcmdp_scan->cmd_timer_link)) { + +		/* skip idle or waitq commands */ +		if (gcmdp_scan->cmd_state <= GCMD_STATE_WAITQ) +			continue; + +		switch (action) { + +		case GACTION_ABORT_DEV: +			if ((gsav_used && GCMD_SAME_DEV(&gsav, gcmdp_scan)) || +			    (GCMDP2GDEVP(gcmdp_scan) == GTGTP2GDEVP(gtgtp))) { +				GCMD_UPDATE_STATE(gcmdp_scan, +				    GCMD_STATE_ABORTING_DEV); +			} +			break; + +		case GACTION_RESET_TARGET: +			if ((gsav_used && GCMD_SAME_DEV(&gsav, gcmdp_scan)) || +			    (GCMDP2GDEVP(gcmdp_scan) == GTGTP2GDEVP(gtgtp))) { +				GCMD_UPDATE_STATE(gcmdp_scan, +				    GCMD_STATE_RESETTING_DEV); +			} +			break; + +		case GACTION_RESET_BUS: +			if ((gsav_used && GCMD_SAME_BUS(&gsav, gcmdp_scan)) || +			    (GCMDP2CCCP(gcmdp_scan) == cccp)) { +				GCMD_UPDATE_STATE(gcmdp_scan, +				    GCMD_STATE_RESETTING_BUS); +			} +			break; +		default: +			break; +		} +	} + +	mutex_exit(&cccp->ccc_activel_mutex); +} + + +/* + * + * ghd_timeout_softintr() + * + *	This interrupt is scheduled if a particular HBA instance's + *	CCB timer list has a timed out CCB, or if the waitq is in a + * 	frozen state. + * + *	Find the timed out CCB and then call the HBA driver's timeout + *	function. + * + *	In order to avoid race conditions all processing must be done + *	while holding the HBA instance's mutex. If the mutex wasn't + *	held the HBA driver's hardware interrupt routine could be + *	triggered and it might try to remove a CCB from the list at + *	same time as were trying to abort it. + * + *	For frozen-waitq processing, just call ghd_waitq_process... + *	it takes care of the time calculations. + * + */ + +static uint_t +ghd_timeout_softintr(caddr_t arg) +{ +	ccc_t	*cccp = (ccc_t *)arg; + +	if (cccp->ccc_timeout_pending) { + +		/* grab this HBA instance's mutex */ +		mutex_enter(&cccp->ccc_hba_mutex); + +		/* +		 * The claim is we could reset "pending" outside the mutex, but +		 * since we have to acquire the mutex anyway, it doesn't hurt +		 */ +		cccp->ccc_timeout_pending = 0; + +		/* timeout each expired CCB */ +		ghd_timer_poll(cccp, GHD_TIMER_POLL_ALL); + +		mutex_enter(&cccp->ccc_waitq_mutex); +		ghd_waitq_process_and_mutex_exit(cccp); + +	} else if (cccp->ccc_waitq_frozen) { +		mutex_enter(&cccp->ccc_hba_mutex); +		mutex_enter(&cccp->ccc_waitq_mutex); +		ghd_waitq_process_and_mutex_exit(cccp); +	} + +	return (DDI_INTR_UNCLAIMED); +} + + +/* + * ghd_timer_poll() + * + * This function steps a packet to the next action in the recovery + * procedure. + * + * The caller must be  already holding the HBA mutex and take care of + * running the pkt completion functions. + * + */ + +void +ghd_timer_poll(ccc_t *cccp, gtimer_poll_t calltype) +{ +	gcmd_t	*gcmdp; +	gact_t	 action; + +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); + +	/* abort each expired CCB */ +	while (gcmdp = ghd_timeout_get(cccp)) { + +		GDBG_INTR(("?ghd_timer_poll: cccp=0x%p gcmdp=0x%p\n", +		    cccp, gcmdp)); + +		switch (gcmdp->cmd_state) { +		case GCMD_STATE_IDLE: +		case GCMD_STATE_DONEQ: +		default: +			/* not supposed to happen */ +			GDBG_ERROR(("ghd_timer_poll: invalid state %d\n", +			    gcmdp->cmd_state)); +			return; + +		case GCMD_STATE_WAITQ: +			action = GACTION_EARLY_TIMEOUT; +			break; + +		case GCMD_STATE_ACTIVE: +			action = GACTION_ABORT_CMD; +			break; + +		case GCMD_STATE_ABORTING_CMD: +			action = GACTION_ABORT_DEV; +			break; + +		case GCMD_STATE_ABORTING_DEV: +			action = GACTION_RESET_TARGET; +			break; + +		case GCMD_STATE_RESETTING_DEV: +			action = GACTION_RESET_BUS; +			break; + +		case GCMD_STATE_RESETTING_BUS: +			action = GACTION_INCOMPLETE; +			break; + +		case GCMD_STATE_HUNG: +			action = GACTION_INCOMPLETE; +			break; +		} + +		ghd_timer_newstate(cccp, gcmdp, gcmdp->cmd_gtgtp, action, +		    GHD_TIMEOUT); + +		/* return after processing first cmd if requested */ + +		if (calltype == GHD_TIMER_POLL_ONE) +			return; +	} +} + + + + +/* + * + * ghd_timeout_get() + * + *	Remove the first expired CCB from a particular timer list. + * + */ + +static gcmd_t * +ghd_timeout_get(ccc_t *cccp) +{ +	gcmd_t	*gcmdp; +	ulong_t	lbolt; + +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); + +	mutex_enter(&cccp->ccc_activel_mutex); +	lbolt = ddi_get_lbolt(); +	gcmdp = (gcmd_t *)L2_next(&cccp->ccc_activel); +	while (gcmdp != NULL) { +		if ((gcmdp->cmd_timeout > 0) && +		    (lbolt - gcmdp->cmd_start_time >= gcmdp->cmd_timeout)) +			goto expired; +		gcmdp = (gcmd_t *)L2_next(&gcmdp->cmd_timer_link); +	} +	mutex_exit(&cccp->ccc_activel_mutex); +	return (NULL); + +expired: +	/* unlink if from the CCB timer list */ +	L2_delete(&gcmdp->cmd_timer_link); +	mutex_exit(&cccp->ccc_activel_mutex); +	return (gcmdp); +} + + +/* + * + * ghd_timeout_enable() + * + *	Only start a single timeout callback for each HBA driver + *	regardless of the number of boards it supports. + * + */ + +static void +ghd_timeout_enable(tmr_t *tmrp) +{ +	mutex_enter(&tglobal_mutex); +	if (tmrp->t_refs++ == 0)  { +		/* establish the timeout callback */ +		tmrp->t_timeout_id = timeout(ghd_timeout, (void *)tmrp, +			tmrp->t_ticks); +	} +	mutex_exit(&tglobal_mutex); +} + +static void +ghd_timeout_disable(tmr_t *tmrp) +{ +	ASSERT(tmrp != NULL); +	ASSERT(tmrp->t_ccc_listp == NULL); + +	mutex_enter(&tglobal_mutex); +	if (tmrp->t_refs-- <= 1) +		(void) untimeout(tmrp->t_timeout_id); +	mutex_exit(&tglobal_mutex); +} + +/* ************************************************************************ */ + +	/* these are the externally callable routines */ + + +void +ghd_timer_init(tmr_t *tmrp, long ticks) +{ +	int	indx; + +	mutex_init(&tglobal_mutex, NULL, MUTEX_DRIVER, NULL); +	mutex_init(&tmrp->t_mutex, NULL, MUTEX_DRIVER, NULL); + +	/* +	 * determine default timeout value +	 */ +	ghd_HZ = drv_usectohz(1000000); +	if (ticks == 0) +		ticks = scsi_watchdog_tick * ghd_HZ; +	tmrp->t_ticks = ticks; + + +	/* +	 * Initialize the table of abort timer values using an +	 * indirect lookup table so that this code isn't dependant +	 * on the cmdstate_t enum values or order. +	 */ +	for (indx = 0; indx < ghd_ntime_inits; indx++) { +		int	state; +		ulong_t	value; + +		if (!ghd_time_inits[indx].valid) +			continue; +		state = ghd_time_inits[indx].state; +		value = ghd_time_inits[indx].value; +		ghd_timeout_table[state] = value; +	} +} + +void +ghd_timer_fini(tmr_t *tmrp) +{ +	mutex_destroy(&tmrp->t_mutex); +	mutex_destroy(&tglobal_mutex); +} + +int +ghd_timer_attach(ccc_t *cccp, tmr_t *tmrp, +    int (*timeout_func)(void *, gcmd_t *, gtgt_t *, gact_t, int)) +{ +	ddi_iblock_cookie_t iblock; + +	if (ddi_add_softintr(cccp->ccc_hba_dip, DDI_SOFTINT_LOW, +	    &cccp->ccc_soft_id, &iblock, NULL, +	    ghd_timeout_softintr, (caddr_t)cccp) != DDI_SUCCESS) { +		GDBG_ERROR(( +		    "ghd_timer_attach: add softintr failed cccp 0x%p\n", +		    (void *)cccp)); +		return (FALSE); +	} + +	/* init the per HBA-instance control fields */ +	mutex_init(&cccp->ccc_activel_mutex, NULL, MUTEX_DRIVER, iblock); +	L2_INIT(&cccp->ccc_activel); +	cccp->ccc_timeout_func = timeout_func; + +	/* stick this HBA's control structure on the master list */ +	mutex_enter(&tmrp->t_mutex); + +	cccp->ccc_nextp = tmrp->t_ccc_listp; +	tmrp->t_ccc_listp = cccp; +	cccp->ccc_tmrp = tmrp; +	mutex_exit(&tmrp->t_mutex); + +	/* +	 * The enable and disable routines use a separate mutex than +	 * t_mutex which is used by the timeout callback function. +	 * This is to avoid a deadlock when calling untimeout() from +	 * the disable routine. +	 */ +	ghd_timeout_enable(tmrp); + +	return (TRUE); +} + + +/* + * + * ghd_timer_detach() + * + *	clean up for a detaching HBA instance + * + */ + +void +ghd_timer_detach(ccc_t *cccp) +{ +	tmr_t	*tmrp = cccp->ccc_tmrp; +	ccc_t	**prevpp; + +	/* make certain the CCB list is empty */ +	ASSERT(cccp->ccc_activel.l2_nextp == &cccp->ccc_activel); +	ASSERT(cccp->ccc_activel.l2_nextp == cccp->ccc_activel.l2_prevp); + +	mutex_enter(&tmrp->t_mutex); + +	prevpp = &tmrp->t_ccc_listp; +	ASSERT(*prevpp != NULL); + +	/* run down the linked list to find the entry that preceeds this one */ +	do { +		if (*prevpp == cccp) +			goto remove_it; +		prevpp = &(*prevpp)->ccc_nextp; +	} while (*prevpp != NULL); + +	/* fell off the end of the list */ +	GDBG_ERROR(("ghd_timer_detach: corrupt list, cccp=0x%p\n", +	    (void *)cccp)); + +remove_it: +	*prevpp = cccp->ccc_nextp; +	mutex_exit(&tmrp->t_mutex); +	mutex_destroy(&cccp->ccc_activel_mutex); + +	ddi_remove_softintr(cccp->ccc_soft_id); + +	ghd_timeout_disable(tmrp); +} + +/* + * + * ghd_timer_start() + * + *	Add a CCB to the CCB timer list. + */ + +void +ghd_timer_start(ccc_t *cccp, gcmd_t *gcmdp, long cmd_timeout) +{ +	ulong_t	lbolt; + +	mutex_enter(&cccp->ccc_activel_mutex); +	lbolt = ddi_get_lbolt(); + +	/* initialize this CCB's timer */ +	gcmdp->cmd_start_time = lbolt; +	gcmdp->cmd_timeout = (cmd_timeout * ghd_HZ); + +	/* add it to the list */ +	L2_add(&cccp->ccc_activel, &gcmdp->cmd_timer_link, gcmdp); +	mutex_exit(&cccp->ccc_activel_mutex); +} + + +/* + * + * ghd_timer_stop() + * + *	Remove a completed CCB from the CCB timer list. + * + *	See the GHD_TIMER_STOP_INLINE() macro in ghd.h for + *	the actual code. + */ + +void +ghd_timer_stop(ccc_t *cccp, gcmd_t *gcmdp) +{ +	GHD_TIMER_STOP_INLINE(cccp, gcmdp); +} diff --git a/usr/src/uts/intel/io/dktp/hba/ghd/ghd_waitq.c b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_waitq.c new file mode 100644 index 0000000000..af06ea69a2 --- /dev/null +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_waitq.c @@ -0,0 +1,429 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include <sys/types.h> +#include <sys/kmem.h> +#include <sys/note.h> + +#include "ghd.h" + + + +/*ARGSUSED*/ +gtgt_t * +ghd_target_init(dev_info_t	*hba_dip, +		dev_info_t	*tgt_dip, +		ccc_t		*cccp, +		size_t		 tgt_private_size, +		void		*hba_private, +		ushort_t	 target, +		uchar_t		 lun) +{ +	_NOTE(ARGUNUSED(hba_dip)) +	gtgt_t	*gtgtp; +	size_t	 size = sizeof (*gtgtp) + tgt_private_size; +	gdev_t	*gdevp; +	ulong_t	 maxactive; + +	gtgtp = kmem_zalloc(size, KM_SLEEP); + +	/* +	 * initialize the per instance structure +	 */ + +	gtgtp->gt_tgt_private = (void *)(gtgtp + 1); +	gtgtp->gt_size = size; +	gtgtp->gt_hba_private = hba_private; +	gtgtp->gt_target = target; +	gtgtp->gt_lun = lun; +	gtgtp->gt_ccc = cccp; + +	/* +	 * set the queue's maxactive to 1 if +	 * property not specified on target or hba devinfo node +	 */ +	maxactive = ddi_getprop(DDI_DEV_T_ANY, tgt_dip, 0, "ghd-maxactive", 1); +	gtgtp->gt_maxactive = maxactive; + +	/* initialize the linked list pointers */ +	GTGT_INIT(gtgtp); + +	/* +	 * grab both mutexes so the queue structures +	 * stay stable while adding this instance to the linked lists +	 */ +	mutex_enter(&cccp->ccc_hba_mutex); +	mutex_enter(&cccp->ccc_waitq_mutex); + +	/* +	 * Search the HBA's linked list of device structures. +	 * +	 * If this device is already attached then link this instance +	 * to the existing per-device-structure on the ccc_devs list. +	 * +	 */ +	gdevp = CCCP2GDEVP(cccp); +	while (gdevp != NULL) { +		if (gdevp->gd_target == target && gdevp->gd_lun == lun) { +			GDBG_WAITQ(("ghd_target_init(%d,%d) found gdevp 0x%p" +				" gtgtp 0x%p max %lu\n", +					target, lun, gdevp, gtgtp, maxactive)); + +			goto foundit; +		} +		gdevp = GDEV_NEXTP(gdevp); +	} + +	/* +	 * Not found. This is the first instance for this device. +	 */ + + +	/* allocate the per-device-structure */ + +	gdevp = kmem_zalloc(sizeof (*gdevp), KM_SLEEP); +	gdevp->gd_target = target; +	gdevp->gd_lun = lun; + +	/* +	 * link this second level queue to the HBA's first +	 * level queue +	 */ +	GDEV_QATTACH(gdevp, cccp, maxactive); + +	GDBG_WAITQ(("ghd_target_init(%d,%d) new gdevp 0x%p gtgtp 0x%p" +		    " max %lu\n", target, lun, gdevp, gtgtp, maxactive)); + +foundit: + +	/* save the ptr to the per device structure */ +	gtgtp->gt_gdevp = gdevp; + +	/* Add the per instance structure to the per device list  */ +	GTGT_ATTACH(gtgtp, gdevp); + +	ghd_waitq_process_and_mutex_exit(cccp); + +	return (gtgtp); +} + +/*ARGSUSED*/ +void +ghd_target_free(dev_info_t	*hba_dip, +		dev_info_t	*tgt_dip, +		ccc_t		*cccp, +		gtgt_t		*gtgtp) +{ +	_NOTE(ARGUNUSED(hba_dip,tgt_dip)) + +	gdev_t	*gdevp = gtgtp->gt_gdevp; + +	GDBG_WAITQ(("ghd_target_free(%d,%d) gdevp-0x%p gtgtp 0x%p\n", +		gtgtp->gt_target, gtgtp->gt_lun, gdevp, gtgtp)); + +	/* +	 * grab both mutexes so the queue structures +	 * stay stable while deleting this instance +	 */ +	mutex_enter(&cccp->ccc_hba_mutex); +	mutex_enter(&cccp->ccc_waitq_mutex); + +	ASSERT(gdevp->gd_ninstances > 0); + +	/* +	 * remove this per-instance structure from the device list and +	 * free the memory +	 */ +	GTGT_DEATTACH(gtgtp, gdevp); +	kmem_free((caddr_t)gtgtp, gtgtp->gt_size); + +	if (gdevp->gd_ninstances == 1) { +		GDBG_WAITQ(("ghd_target_free: N=1 gdevp 0x%p\n", gdevp)); +		/* +		 * If there's now just one instance left attached to this +		 * device then reset the queue's max active value +		 * from that instance's saved value. +		 */ +		gtgtp = GDEVP2GTGTP(gdevp); +		GDEV_MAXACTIVE(gdevp) = gtgtp->gt_maxactive; + +	} else if (gdevp->gd_ninstances == 0) { +		/* else no instances left */ +		GDBG_WAITQ(("ghd_target_free: N=0 gdevp 0x%p\n", gdevp)); + +		/* detach this per-dev-structure from the HBA's dev list */ +		GDEV_QDETACH(gdevp, cccp); +		kmem_free(gdevp, sizeof (*gdevp)); + +	} +#if defined(GHD_DEBUG) || defined(__lint) +	else { +		/* leave maxactive set to 1 */ +		GDBG_WAITQ(("ghd_target_free: N>1 gdevp 0x%p\n", gdevp)); +	} +#endif + +	ghd_waitq_process_and_mutex_exit(cccp); +} + +void +ghd_waitq_shuffle_up(ccc_t *cccp, gdev_t *gdevp) +{ +	gcmd_t	*gcmdp; + +	ASSERT(mutex_owned(&cccp->ccc_waitq_mutex)); + +	GDBG_WAITQ(("ghd_waitq_shuffle_up: cccp 0x%p gdevp 0x%p N %ld " +	    "max %ld\n", cccp, gdevp, GDEV_NACTIVE(gdevp), +	    GDEV_MAXACTIVE(gdevp))); +	for (;;) { +		/* +		 * Now check the device wait queue throttle to see if I can +		 * shuffle up a request to the HBA wait queue. +		 */ +		if (GDEV_NACTIVE(gdevp) >= GDEV_MAXACTIVE(gdevp)) { +			GDBG_WAITQ(("ghd_waitq_shuffle_up: N>MAX gdevp 0x%p\n", +				gdevp)); +			return; +		} + +		/* +		 * single thread requests while multiple instances +		 * because the different target drives might have +		 * conflicting maxactive throttles. +		 */ +		if (gdevp->gd_ninstances > 1 && GDEV_NACTIVE(gdevp) > 0) { +			GDBG_WAITQ(("ghd_waitq_shuffle_up: multi gdevp 0x%p\n", +				gdevp)); +			return; +		} + +		/* +		 * promote the topmost request from the device queue to +		 * the HBA queue. +		 */ +		if ((gcmdp = L2_remove_head(&GDEV_QHEAD(gdevp))) == NULL) { +			/* the device is empty so we're done */ +			GDBG_WAITQ(("ghd_waitq_shuffle_up: MT gdevp 0x%p\n", +				gdevp)); +			return; +		} +		L2_add(&GHBA_QHEAD(cccp), &gcmdp->cmd_q, gcmdp); +		GDEV_NACTIVE(gdevp)++; +		gcmdp->cmd_waitq_level++; +		GDBG_WAITQ(("ghd_waitq_shuffle_up: gdevp 0x%p gcmdp 0x%p\n", +			gdevp, gcmdp)); +	} +} + + +void +ghd_waitq_delete(ccc_t *cccp, gcmd_t *gcmdp) +{ +	gtgt_t	*gtgtp = GCMDP2GTGTP(gcmdp); +	gdev_t	*gdevp = gtgtp->gt_gdevp; +#if defined(GHD_DEBUG) || defined(__lint) +	Q_t	*qp = &gdevp->gd_waitq; +#endif + +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); +	mutex_enter(&cccp->ccc_waitq_mutex); + +	/* +	 * Adjust all queue counters. If this request is being aborted +	 * it might only have made it to the target queue. Otherwise, +	 * both the target and hba queue have to be adjusted when a +	 * request is completed normally. The cmd_waitq_level value +	 * indicates which queue counters need to be adjusted. It's +	 * incremented as the request progresses up the queues. +	 */ +	switch (gcmdp->cmd_waitq_level) { +	case 0: +		break; +	case 1: +		/* +		 * If this is an early-timeout, or early-abort, the request +		 * is still linked onto a waitq. Remove it now. If it's +		 * an active request and no longer on the waitq then calling +		 * L2_delete a second time does no harm. +		 */ +		L2_delete(&gcmdp->cmd_q); +		break; + +	case 2: +		L2_delete(&gcmdp->cmd_q); +#if defined(GHD_DEBUG) || defined(__lint) +		if (GDEV_NACTIVE(gdevp) == 0) +			debug_enter("\n\nGHD WAITQ DELETE\n\n"); +#endif +		GDEV_NACTIVE(gdevp)--; +		break; + +	case 3: +		/* it's an active or completed command */ +#if defined(GHD_DEBUG) || defined(__lint) +		if (GDEV_NACTIVE(gdevp) == 0 || GHBA_NACTIVE(cccp) == 0) +			debug_enter("\n\nGHD WAITQ DELETE\n\n"); +#endif +		GDEV_NACTIVE(gdevp)--; +		GHBA_NACTIVE(cccp)--; +		break; + +	default: +		/* this shouldn't happen */ +#if defined(GHD_DEBUG) || defined(__lint) +		debug_enter("\n\nGHD WAITQ LEVEL > 3\n\n"); +#endif +		break; +	} + +	GDBG_WAITQ(("ghd_waitq_delete: gcmdp 0x%p qp 0x%p level %ld\n", +		gcmdp, qp, gcmdp->cmd_waitq_level)); + + +	/* +	 * There's probably now more room in the HBA queue. Move +	 * up as many requests as possible. +	 */ +	ghd_waitq_shuffle_up(cccp, gdevp); + +	mutex_exit(&cccp->ccc_waitq_mutex); +} + + +int +ghd_waitq_process_and_mutex_hold(ccc_t *cccp) +{ +	gcmd_t	*gcmdp; +	int	 rc = FALSE; + +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); +	ASSERT(mutex_owned(&cccp->ccc_waitq_mutex)); + +	for (;;) { +		if (L2_EMPTY(&GHBA_QHEAD(cccp))) { +			/* return if the list is empty */ +			GDBG_WAITQ(("ghd_waitq_proc: MT cccp 0x%p qp 0x%p\n", +				cccp, &cccp->ccc_waitq)); +			break; +		} +		if (GHBA_NACTIVE(cccp) >= GHBA_MAXACTIVE(cccp)) { +			/* return if the HBA is too active */ +			GDBG_WAITQ(("ghd_waitq_proc: N>M cccp 0x%p qp 0x%p" +				" N %ld max %ld\n", cccp, &cccp->ccc_waitq, +					GHBA_NACTIVE(cccp), +					GHBA_MAXACTIVE(cccp))); +			break; +		} + +		/* +		 * bail out if the wait queue has been +		 * "held" by the HBA driver +		 */ +		if (cccp->ccc_waitq_held) { +			GDBG_WAITQ(("ghd_waitq_proc: held")); +			return (rc); +		} + +		if (cccp->ccc_waitq_frozen) { + +			clock_t lbolt, delay_in_hz, time_to_wait; + +			delay_in_hz = +			    drv_usectohz(cccp->ccc_waitq_freezedelay * 1000); + +			lbolt = ddi_get_lbolt(); +			time_to_wait = delay_in_hz - +			    (lbolt - cccp->ccc_waitq_freezetime); + +			if (time_to_wait > 0) { +				/* +				 * stay frozen; we'll be called again +				 * by ghd_timeout_softintr() +				 */ +				GDBG_WAITQ(("ghd_waitq_proc: frozen")); +				return (rc); +			} else { +				/* unfreeze and continue */ +				GDBG_WAITQ(("ghd_waitq_proc: unfreezing")); +				cccp->ccc_waitq_freezetime = 0; +				cccp->ccc_waitq_freezedelay = 0; +				cccp->ccc_waitq_frozen = 0; +			} +		} + +		gcmdp = (gcmd_t *)L2_remove_head(&GHBA_QHEAD(cccp)); +		GHBA_NACTIVE(cccp)++; +		gcmdp->cmd_waitq_level++; +		mutex_exit(&cccp->ccc_waitq_mutex); + +		/* +		 * Start up the next I/O request +		 */ +		ASSERT(gcmdp != NULL); +		gcmdp->cmd_state = GCMD_STATE_ACTIVE; +		if (!(*cccp->ccc_hba_start)(cccp->ccc_hba_handle, gcmdp)) { +			/* if the HBA rejected the request, requeue it */ +			gcmdp->cmd_state = GCMD_STATE_WAITQ; +			mutex_enter(&cccp->ccc_waitq_mutex); +			GHBA_NACTIVE(cccp)--; +			gcmdp->cmd_waitq_level--; +			L2_add_head(&GHBA_QHEAD(cccp), &gcmdp->cmd_q, gcmdp); +			GDBG_WAITQ(("ghd_waitq_proc: busy cccp 0x%p gcmdp 0x%p" +				" handle 0x%p\n", cccp, gcmdp, +					cccp->ccc_hba_handle)); +			break; +		} +		rc = TRUE; +		mutex_enter(&cccp->ccc_waitq_mutex); +		GDBG_WAITQ(("ghd_waitq_proc: ++ cccp 0x%p gcmdp 0x%p N %ld\n", +			cccp, gcmdp, GHBA_NACTIVE(cccp))); +	} +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); +	ASSERT(mutex_owned(&cccp->ccc_waitq_mutex)); +	return (rc); +} + +void +ghd_waitq_process_and_mutex_exit(ccc_t *cccp) +{ +	ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); +	ASSERT(mutex_owned(&cccp->ccc_waitq_mutex)); + +	GDBG_WAITQ(("ghd_waitq_process_and_mutex_exit: cccp 0x%p\n", cccp)); + +	(void) ghd_waitq_process_and_mutex_hold(cccp); + +	/* +	 * Release the mutexes in the opposite order that they +	 * were acquired to prevent requests queued by +	 * ghd_transport() from getting hung up in the wait queue. +	 */ +	mutex_exit(&cccp->ccc_hba_mutex); +	mutex_exit(&cccp->ccc_waitq_mutex); +} diff --git a/usr/src/uts/common/io/dktp/hba/ghd/ghd_waitq.h b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_waitq.h index 143d2472e9..67a23fa37a 100644 --- a/usr/src/uts/common/io/dktp/hba/ghd/ghd_waitq.h +++ b/usr/src/uts/intel/io/dktp/hba/ghd/ghd_waitq.h @@ -2,9 +2,8 @@   * CDDL HEADER START   *   * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License").  You may not use this file except in compliance - * with the License. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License.   *   * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE   * or http://www.opensolaris.org/os/licensing. @@ -19,15 +18,16 @@   *   * CDDL HEADER END   */ +  /* - * Copyright (c) 1999, by Sun Microsystems, Inc. - * All rights reserved. + * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms.   */  #ifndef _GHD_WAITQ_H  #define	_GHD_WAITQ_H -#pragma	ident	"%Z%%M%	%I%	%E% SMI" +#pragma ident	"%Z%%M%	%I%	%E% SMI"  #ifdef	__cplusplus  extern "C" { | 
