2007-10-19 15:59:34 +04:00
|
|
|
/* $NetBSD: dpt.c,v 1.58 2007/10/19 11:59:51 ad Exp $ */
|
1999-09-28 03:41:47 +04:00
|
|
|
|
|
|
|
/*-
|
2001-04-25 21:53:04 +04:00
|
|
|
* Copyright (c) 1997, 1998, 1999, 2000, 2001 The NetBSD Foundation, Inc.
|
1999-09-28 03:41:47 +04:00
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
2000-06-13 17:36:42 +04:00
|
|
|
* by Andrew Doran, Charles M. Hannum and by Jason R. Thorpe of the Numerical
|
1999-09-28 03:41:47 +04:00
|
|
|
* Aerospace Simulation Facility, NASA Ames Research Center.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
|
|
* must display the following acknowledgement:
|
|
|
|
* This product includes software developed by the NetBSD
|
|
|
|
* Foundation, Inc. and its contributors.
|
|
|
|
* 4. Neither the name of The NetBSD Foundation nor the names of its
|
|
|
|
* contributors may be used to endorse or promote products derived
|
|
|
|
* from this software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
2002-12-07 22:48:30 +03:00
|
|
|
/*
|
|
|
|
* Copyright (c) 1996-2000 Distributed Processing Technology Corporation
|
|
|
|
* Copyright (c) 2000 Adaptec Corporation
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* TERMS AND CONDITIONS OF USE
|
|
|
|
*
|
|
|
|
* Redistribution and use in source form, with or without modification, are
|
|
|
|
* permitted provided that redistributions of source code must retain the
|
|
|
|
* above copyright notice, this list of conditions and the following disclaimer.
|
|
|
|
*
|
|
|
|
* This software is provided `as is' by Adaptec and any express or implied
|
|
|
|
* warranties, including, but not limited to, the implied warranties of
|
|
|
|
* merchantability and fitness for a particular purpose, are disclaimed. In no
|
|
|
|
* event shall Adaptec be liable for any direct, indirect, incidental, special,
|
|
|
|
* exemplary or consequential damages (including, but not limited to,
|
|
|
|
* procurement of substitute goods or services; loss of use, data, or profits;
|
|
|
|
* or business interruptions) however caused and on any theory of liability,
|
|
|
|
* whether in contract, strict liability, or tort (including negligence or
|
|
|
|
* otherwise) arising in any way out of the use of this driver software, even
|
|
|
|
* if advised of the possibility of such damage.
|
|
|
|
*/
|
|
|
|
|
1999-09-28 03:41:47 +04:00
|
|
|
/*
|
|
|
|
* Portions of this code fall under the following copyright:
|
|
|
|
*
|
|
|
|
* Originally written by Julian Elischer (julian@tfs.com)
|
|
|
|
* for TRW Financial Systems for use under the MACH(2.5) operating system.
|
|
|
|
*
|
|
|
|
* TRW Financial Systems, in accordance with their agreement with Carnegie
|
|
|
|
* Mellon University, makes this software available to CMU to distribute
|
|
|
|
* or use in any manner that they see fit as long as this message is kept with
|
|
|
|
* the software. For this reason TFS also grants any other persons or
|
|
|
|
* organisations permission to use or modify this software.
|
|
|
|
*
|
|
|
|
* TFS supplies this software to be publicly redistributed
|
|
|
|
* on the understanding that TFS is not responsible for the correct
|
|
|
|
* functioning of this software in any circumstances.
|
|
|
|
*/
|
|
|
|
|
2001-11-13 16:14:31 +03:00
|
|
|
#include <sys/cdefs.h>
|
2007-10-19 15:59:34 +04:00
|
|
|
__KERNEL_RCSID(0, "$NetBSD: dpt.c,v 1.58 2007/10/19 11:59:51 ad Exp $");
|
2001-11-13 16:14:31 +03:00
|
|
|
|
1999-09-28 03:41:47 +04:00
|
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/systm.h>
|
|
|
|
#include <sys/device.h>
|
|
|
|
#include <sys/queue.h>
|
|
|
|
#include <sys/buf.h>
|
1999-11-29 18:04:23 +03:00
|
|
|
#include <sys/endian.h>
|
2002-12-07 22:48:30 +03:00
|
|
|
#include <sys/conf.h>
|
2006-11-08 03:17:09 +03:00
|
|
|
#include <sys/kauth.h>
|
2007-07-10 00:51:58 +04:00
|
|
|
#include <sys/proc.h>
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2000-11-14 21:21:00 +03:00
|
|
|
#include <uvm/uvm_extern.h>
|
|
|
|
|
2007-10-19 15:59:34 +04:00
|
|
|
#include <sys/bus.h>
|
2002-12-07 22:48:30 +03:00
|
|
|
#ifdef i386
|
|
|
|
#include <machine/pio.h>
|
|
|
|
#endif
|
1999-09-28 03:41:47 +04:00
|
|
|
|
|
|
|
#include <dev/scsipi/scsi_all.h>
|
|
|
|
#include <dev/scsipi/scsipi_all.h>
|
|
|
|
#include <dev/scsipi/scsiconf.h>
|
|
|
|
|
|
|
|
#include <dev/ic/dptreg.h>
|
|
|
|
#include <dev/ic/dptvar.h>
|
|
|
|
|
2002-12-07 22:48:30 +03:00
|
|
|
#include <dev/i2o/dptivar.h>
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
#define DPRINTF(x) printf x
|
|
|
|
#else
|
|
|
|
#define DPRINTF(x)
|
|
|
|
#endif
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
#define dpt_inb(x, o) \
|
|
|
|
bus_space_read_1((x)->sc_iot, (x)->sc_ioh, (o))
|
|
|
|
#define dpt_outb(x, o, d) \
|
|
|
|
bus_space_write_1((x)->sc_iot, (x)->sc_ioh, (o), (d))
|
|
|
|
|
|
|
|
static const char * const dpt_cname[] = {
|
|
|
|
"3334", "SmartRAID IV",
|
|
|
|
"3332", "SmartRAID IV",
|
|
|
|
"2144", "SmartCache IV",
|
|
|
|
"2044", "SmartCache IV",
|
|
|
|
"2142", "SmartCache IV",
|
|
|
|
"2042", "SmartCache IV",
|
|
|
|
"2041", "SmartCache IV",
|
|
|
|
"3224", "SmartRAID III",
|
2005-02-27 03:26:58 +03:00
|
|
|
"3222", "SmartRAID III",
|
2001-04-25 21:53:04 +04:00
|
|
|
"3021", "SmartRAID III",
|
|
|
|
"2124", "SmartCache III",
|
|
|
|
"2024", "SmartCache III",
|
|
|
|
"2122", "SmartCache III",
|
|
|
|
"2022", "SmartCache III",
|
|
|
|
"2021", "SmartCache III",
|
2005-02-27 03:26:58 +03:00
|
|
|
"2012", "SmartCache Plus",
|
2001-04-25 21:53:04 +04:00
|
|
|
"2011", "SmartCache Plus",
|
|
|
|
NULL, "<unknown>",
|
1999-09-28 03:41:47 +04:00
|
|
|
};
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
static void *dpt_sdh;
|
|
|
|
|
2002-12-07 22:48:30 +03:00
|
|
|
dev_type_open(dptopen);
|
|
|
|
dev_type_ioctl(dptioctl);
|
|
|
|
|
|
|
|
const struct cdevsw dpt_cdevsw = {
|
|
|
|
dptopen, nullclose, noread, nowrite, dptioctl,
|
2006-08-30 04:40:56 +04:00
|
|
|
nostop, notty, nopoll, nommap, nokqfilter, D_OTHER,
|
2002-12-07 22:48:30 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
extern struct cfdriver dpt_cd;
|
|
|
|
|
|
|
|
static struct dpt_sig dpt_sig = {
|
|
|
|
{ 'd', 'P', 't', 'S', 'i', 'G'},
|
|
|
|
SIG_VERSION,
|
|
|
|
#if defined(i386)
|
|
|
|
PROC_INTEL,
|
|
|
|
#elif defined(powerpc)
|
|
|
|
PROC_POWERPC,
|
|
|
|
#elif defined(alpha)
|
|
|
|
PROC_ALPHA,
|
|
|
|
#elif defined(__mips__)
|
|
|
|
PROC_MIPS,
|
|
|
|
#elif defined(sparc64)
|
|
|
|
PROC_ULTRASPARC,
|
2002-12-10 23:06:43 +03:00
|
|
|
#else
|
|
|
|
0xff,
|
2002-12-07 22:48:30 +03:00
|
|
|
#endif
|
|
|
|
#if defined(i386)
|
|
|
|
PROC_386 | PROC_486 | PROC_PENTIUM | PROC_SEXIUM,
|
|
|
|
#else
|
|
|
|
0,
|
|
|
|
#endif
|
|
|
|
FT_HBADRVR,
|
|
|
|
0,
|
|
|
|
OEM_DPT,
|
|
|
|
OS_FREE_BSD, /* XXX */
|
|
|
|
CAP_ABOVE16MB,
|
|
|
|
DEV_ALL,
|
|
|
|
ADF_ALL_EATA,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
DPT_VERSION,
|
|
|
|
DPT_REVISION,
|
|
|
|
DPT_SUBREVISION,
|
|
|
|
DPT_MONTH,
|
|
|
|
DPT_DAY,
|
|
|
|
DPT_YEAR,
|
|
|
|
"" /* Will be filled later */
|
|
|
|
};
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
static void dpt_ccb_abort(struct dpt_softc *, struct dpt_ccb *);
|
|
|
|
static void dpt_ccb_done(struct dpt_softc *, struct dpt_ccb *);
|
|
|
|
static int dpt_ccb_map(struct dpt_softc *, struct dpt_ccb *);
|
|
|
|
static int dpt_ccb_poll(struct dpt_softc *, struct dpt_ccb *);
|
|
|
|
static void dpt_ccb_unmap(struct dpt_softc *, struct dpt_ccb *);
|
|
|
|
static int dpt_cmd(struct dpt_softc *, struct dpt_ccb *, int, int);
|
2002-12-07 22:48:30 +03:00
|
|
|
static void dpt_ctlrinfo(struct dpt_softc *, struct dpt_eata_ctlrinfo *);
|
2001-04-25 21:53:04 +04:00
|
|
|
static void dpt_hba_inquire(struct dpt_softc *, struct eata_inquiry_data **);
|
|
|
|
static void dpt_minphys(struct buf *);
|
2002-12-09 18:24:28 +03:00
|
|
|
static int dpt_passthrough(struct dpt_softc *, struct eata_ucp *,
|
2005-12-11 15:16:03 +03:00
|
|
|
struct lwp *);
|
2001-04-25 21:53:04 +04:00
|
|
|
static void dpt_scsipi_request(struct scsipi_channel *,
|
|
|
|
scsipi_adapter_req_t, void *);
|
|
|
|
static void dpt_shutdown(void *);
|
2002-12-07 22:48:30 +03:00
|
|
|
static void dpt_sysinfo(struct dpt_softc *, struct dpt_sysinfo *);
|
2001-04-25 21:53:04 +04:00
|
|
|
static int dpt_wait(struct dpt_softc *, u_int8_t, u_int8_t, int);
|
|
|
|
|
2005-12-25 02:41:33 +03:00
|
|
|
static inline struct dpt_ccb *dpt_ccb_alloc(struct dpt_softc *);
|
|
|
|
static inline void dpt_ccb_free(struct dpt_softc *, struct dpt_ccb *);
|
2001-04-25 21:53:04 +04:00
|
|
|
|
2005-12-25 02:41:33 +03:00
|
|
|
static inline struct dpt_ccb *
|
2001-04-25 21:53:04 +04:00
|
|
|
dpt_ccb_alloc(struct dpt_softc *sc)
|
|
|
|
{
|
|
|
|
struct dpt_ccb *ccb;
|
|
|
|
int s;
|
|
|
|
|
|
|
|
s = splbio();
|
|
|
|
ccb = SLIST_FIRST(&sc->sc_ccb_free);
|
|
|
|
SLIST_REMOVE_HEAD(&sc->sc_ccb_free, ccb_chain);
|
|
|
|
splx(s);
|
|
|
|
|
|
|
|
return (ccb);
|
|
|
|
}
|
|
|
|
|
2005-12-25 02:41:33 +03:00
|
|
|
static inline void
|
2001-04-25 21:53:04 +04:00
|
|
|
dpt_ccb_free(struct dpt_softc *sc, struct dpt_ccb *ccb)
|
|
|
|
{
|
|
|
|
int s;
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
ccb->ccb_flg = 0;
|
2002-12-07 22:48:30 +03:00
|
|
|
ccb->ccb_savesp = NULL;
|
2001-04-25 21:53:04 +04:00
|
|
|
s = splbio();
|
|
|
|
SLIST_INSERT_HEAD(&sc->sc_ccb_free, ccb, ccb_chain);
|
|
|
|
splx(s);
|
|
|
|
}
|
2000-02-24 21:47:55 +03:00
|
|
|
|
1999-09-28 03:41:47 +04:00
|
|
|
/*
|
|
|
|
* Handle an interrupt from the HBA.
|
|
|
|
*/
|
|
|
|
int
|
2001-04-25 21:53:04 +04:00
|
|
|
dpt_intr(void *cookie)
|
1999-09-28 03:41:47 +04:00
|
|
|
{
|
|
|
|
struct dpt_softc *sc;
|
|
|
|
struct dpt_ccb *ccb;
|
|
|
|
struct eata_sp *sp;
|
2000-02-24 21:47:55 +03:00
|
|
|
volatile int junk;
|
2001-04-25 21:53:04 +04:00
|
|
|
int forus;
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
sc = cookie;
|
2000-02-24 21:47:55 +03:00
|
|
|
sp = sc->sc_stp;
|
2001-04-25 21:53:04 +04:00
|
|
|
forus = 0;
|
2000-02-24 21:47:55 +03:00
|
|
|
|
1999-10-01 16:20:12 +04:00
|
|
|
for (;;) {
|
|
|
|
/*
|
|
|
|
* HBA might have interrupted while we were dealing with the
|
2005-02-27 03:26:58 +03:00
|
|
|
* last completed command, since we ACK before we deal; keep
|
2000-02-24 21:47:55 +03:00
|
|
|
* polling.
|
2001-04-25 21:53:04 +04:00
|
|
|
*/
|
2000-02-24 21:47:55 +03:00
|
|
|
if ((dpt_inb(sc, HA_AUX_STATUS) & HA_AUX_INTR) == 0)
|
1999-10-01 16:20:12 +04:00
|
|
|
break;
|
2001-04-25 21:53:04 +04:00
|
|
|
forus = 1;
|
|
|
|
|
2000-02-24 21:47:55 +03:00
|
|
|
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, sc->sc_stpoff,
|
1999-09-28 03:41:47 +04:00
|
|
|
sizeof(struct eata_sp), BUS_DMASYNC_POSTREAD);
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Might have looped before HBA can reset HBA_AUX_INTR. */
|
1999-09-28 03:41:47 +04:00
|
|
|
if (sp->sp_ccbid == -1) {
|
|
|
|
DELAY(50);
|
2000-02-24 21:47:55 +03:00
|
|
|
|
1999-09-28 03:41:47 +04:00
|
|
|
if ((dpt_inb(sc, HA_AUX_STATUS) & HA_AUX_INTR) == 0)
|
|
|
|
return (0);
|
2000-02-24 21:47:55 +03:00
|
|
|
|
|
|
|
printf("%s: no status\n", sc->sc_dv.dv_xname);
|
|
|
|
|
1999-09-28 03:41:47 +04:00
|
|
|
/* Re-sync DMA map */
|
2001-04-25 21:53:04 +04:00
|
|
|
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap,
|
2000-02-24 21:47:55 +03:00
|
|
|
sc->sc_stpoff, sizeof(struct eata_sp),
|
1999-09-28 03:41:47 +04:00
|
|
|
BUS_DMASYNC_POSTREAD);
|
|
|
|
}
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Make sure CCB ID from status packet is realistic. */
|
|
|
|
if ((u_int)sp->sp_ccbid >= sc->sc_nccbs) {
|
2005-02-27 03:26:58 +03:00
|
|
|
printf("%s: bogus status (returned CCB id %d)\n",
|
1999-09-28 03:41:47 +04:00
|
|
|
sc->sc_dv.dv_xname, sp->sp_ccbid);
|
|
|
|
|
|
|
|
/* Ack the interrupt */
|
|
|
|
sp->sp_ccbid = -1;
|
2000-02-29 14:14:46 +03:00
|
|
|
junk = dpt_inb(sc, HA_STATUS);
|
2001-04-25 21:53:04 +04:00
|
|
|
continue;
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
2001-04-25 21:53:04 +04:00
|
|
|
|
|
|
|
/* Sync up DMA map and cache cmd status. */
|
|
|
|
ccb = sc->sc_ccbs + sp->sp_ccbid;
|
|
|
|
|
|
|
|
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, CCB_OFF(sc, ccb),
|
|
|
|
sizeof(struct dpt_ccb), BUS_DMASYNC_POSTWRITE);
|
|
|
|
|
|
|
|
ccb->ccb_hba_status = sp->sp_hba_status & 0x7f;
|
|
|
|
ccb->ccb_scsi_status = sp->sp_scsi_status;
|
2002-12-07 22:48:30 +03:00
|
|
|
if (ccb->ccb_savesp != NULL)
|
|
|
|
memcpy(ccb->ccb_savesp, sp, sizeof(*sp));
|
2001-04-25 21:53:04 +04:00
|
|
|
|
2005-02-27 03:26:58 +03:00
|
|
|
/*
|
2001-04-25 21:53:04 +04:00
|
|
|
* Ack the interrupt and process the CCB. If this
|
|
|
|
* is a private CCB it's up to dpt_ccb_poll() to
|
|
|
|
* notice.
|
|
|
|
*/
|
|
|
|
sp->sp_ccbid = -1;
|
|
|
|
ccb->ccb_flg |= CCB_INTR;
|
|
|
|
junk = dpt_inb(sc, HA_STATUS);
|
|
|
|
if ((ccb->ccb_flg & CCB_PRIVATE) == 0)
|
|
|
|
dpt_ccb_done(sc, ccb);
|
2002-12-07 22:48:30 +03:00
|
|
|
else if ((ccb->ccb_flg & CCB_WAIT) != 0)
|
|
|
|
wakeup(ccb);
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
return (forus);
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2001-04-25 21:53:04 +04:00
|
|
|
* Initialize and attach the HBA. This is the entry point from bus
|
1999-09-28 03:41:47 +04:00
|
|
|
* specific probe-and-attach code.
|
|
|
|
*/
|
|
|
|
void
|
2001-04-25 21:53:04 +04:00
|
|
|
dpt_init(struct dpt_softc *sc, const char *intrstr)
|
1999-09-28 03:41:47 +04:00
|
|
|
{
|
2001-04-25 21:53:04 +04:00
|
|
|
struct scsipi_adapter *adapt;
|
|
|
|
struct scsipi_channel *chan;
|
1999-09-28 03:41:47 +04:00
|
|
|
struct eata_inquiry_data *ei;
|
2001-04-25 21:53:04 +04:00
|
|
|
int i, j, rv, rseg, maxchannel, maxtarget, mapsize;
|
1999-09-28 03:41:47 +04:00
|
|
|
bus_dma_segment_t seg;
|
1999-09-29 21:33:02 +04:00
|
|
|
struct eata_cfg *ec;
|
2001-04-25 21:53:04 +04:00
|
|
|
struct dpt_ccb *ccb;
|
2006-04-15 00:43:11 +04:00
|
|
|
char model[__arraycount(ei->ei_model) + __arraycount(ei->ei_suffix) + 1];
|
|
|
|
char vendor[__arraycount(ei->ei_vendor) + 1];
|
2001-04-25 21:53:04 +04:00
|
|
|
|
1999-09-29 21:33:02 +04:00
|
|
|
ec = &sc->sc_ec;
|
2004-04-22 04:17:10 +04:00
|
|
|
snprintf(dpt_sig.dsDescription, sizeof(dpt_sig.dsDescription),
|
|
|
|
"NetBSD %s DPT driver", osrelease);
|
2002-12-07 22:48:30 +03:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/*
|
|
|
|
* Allocate the CCB/status packet/scratch DMA map and load.
|
|
|
|
*/
|
2005-02-27 03:26:58 +03:00
|
|
|
sc->sc_nccbs =
|
2000-02-24 21:47:55 +03:00
|
|
|
min(be16toh(*(int16_t *)ec->ec_queuedepth), DPT_MAX_CCBS);
|
|
|
|
sc->sc_stpoff = sc->sc_nccbs * sizeof(struct dpt_ccb);
|
|
|
|
sc->sc_scroff = sc->sc_stpoff + sizeof(struct eata_sp);
|
2005-02-27 03:26:58 +03:00
|
|
|
mapsize = sc->sc_nccbs * sizeof(struct dpt_ccb) +
|
2001-04-25 21:53:04 +04:00
|
|
|
DPT_SCRATCH_SIZE + sizeof(struct eata_sp);
|
|
|
|
|
|
|
|
if ((rv = bus_dmamem_alloc(sc->sc_dmat, mapsize,
|
2000-11-14 21:21:00 +03:00
|
|
|
PAGE_SIZE, 0, &seg, 1, &rseg, BUS_DMA_NOWAIT)) != 0) {
|
2003-01-31 03:26:25 +03:00
|
|
|
aprint_error("%s: unable to allocate CCBs, rv = %d\n",
|
2001-04-25 21:53:04 +04:00
|
|
|
sc->sc_dv.dv_xname, rv);
|
1999-09-28 03:41:47 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
if ((rv = bus_dmamem_map(sc->sc_dmat, &seg, rseg, mapsize,
|
2007-03-04 08:59:00 +03:00
|
|
|
(void **)&sc->sc_ccbs, BUS_DMA_NOWAIT|BUS_DMA_COHERENT)) != 0) {
|
2003-01-31 03:26:25 +03:00
|
|
|
aprint_error("%s: unable to map CCBs, rv = %d\n",
|
2001-04-25 21:53:04 +04:00
|
|
|
sc->sc_dv.dv_xname, rv);
|
1999-09-28 03:41:47 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
if ((rv = bus_dmamap_create(sc->sc_dmat, mapsize,
|
|
|
|
mapsize, 1, 0, BUS_DMA_NOWAIT, &sc->sc_dmamap)) != 0) {
|
2003-01-31 03:26:25 +03:00
|
|
|
aprint_error("%s: unable to create CCB DMA map, rv = %d\n",
|
2001-04-25 21:53:04 +04:00
|
|
|
sc->sc_dv.dv_xname, rv);
|
1999-09-28 03:41:47 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
if ((rv = bus_dmamap_load(sc->sc_dmat, sc->sc_dmamap,
|
|
|
|
sc->sc_ccbs, mapsize, NULL, BUS_DMA_NOWAIT)) != 0) {
|
2003-01-31 03:26:25 +03:00
|
|
|
aprint_error("%s: unable to load CCB DMA map, rv = %d\n",
|
2001-04-25 21:53:04 +04:00
|
|
|
sc->sc_dv.dv_xname, rv);
|
1999-09-28 03:41:47 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-03-04 08:59:00 +03:00
|
|
|
sc->sc_stp = (struct eata_sp *)((char *)sc->sc_ccbs + sc->sc_stpoff);
|
2000-02-24 21:47:55 +03:00
|
|
|
sc->sc_stppa = sc->sc_dmamap->dm_segs[0].ds_addr + sc->sc_stpoff;
|
2007-03-04 08:59:00 +03:00
|
|
|
sc->sc_scr = (char *)sc->sc_ccbs + sc->sc_scroff;
|
2000-02-24 21:47:55 +03:00
|
|
|
sc->sc_scrpa = sc->sc_dmamap->dm_segs[0].ds_addr + sc->sc_scroff;
|
|
|
|
sc->sc_stp->sp_ccbid = -1;
|
1999-09-29 21:33:02 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/*
|
|
|
|
* Create the CCBs.
|
|
|
|
*/
|
|
|
|
SLIST_INIT(&sc->sc_ccb_free);
|
|
|
|
memset(sc->sc_ccbs, 0, sizeof(struct dpt_ccb) * sc->sc_nccbs);
|
|
|
|
|
|
|
|
for (i = 0, ccb = sc->sc_ccbs; i < sc->sc_nccbs; i++, ccb++) {
|
|
|
|
rv = bus_dmamap_create(sc->sc_dmat, DPT_MAX_XFER,
|
|
|
|
DPT_SG_SIZE, DPT_MAX_XFER, 0,
|
|
|
|
BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW,
|
|
|
|
&ccb->ccb_dmamap_xfer);
|
|
|
|
if (rv) {
|
2005-02-27 03:26:58 +03:00
|
|
|
aprint_error("%s: can't create ccb dmamap (%d)\n",
|
2001-04-25 21:53:04 +04:00
|
|
|
sc->sc_dv.dv_xname, rv);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ccb->ccb_id = i;
|
|
|
|
ccb->ccb_ccbpa = sc->sc_dmamap->dm_segs[0].ds_addr +
|
|
|
|
CCB_OFF(sc, ccb);
|
|
|
|
SLIST_INSERT_HEAD(&sc->sc_ccb_free, ccb, ccb_chain);
|
|
|
|
}
|
1999-09-28 03:41:47 +04:00
|
|
|
|
|
|
|
if (i == 0) {
|
2003-01-31 03:26:25 +03:00
|
|
|
aprint_error("%s: unable to create CCBs\n", sc->sc_dv.dv_xname);
|
1999-09-28 03:41:47 +04:00
|
|
|
return;
|
|
|
|
} else if (i != sc->sc_nccbs) {
|
2003-01-31 03:26:25 +03:00
|
|
|
aprint_error("%s: %d/%d CCBs created!\n", sc->sc_dv.dv_xname,
|
|
|
|
i, sc->sc_nccbs);
|
1999-09-28 03:41:47 +04:00
|
|
|
sc->sc_nccbs = i;
|
|
|
|
}
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Set shutdownhook before we start any device activity. */
|
2000-07-18 19:27:44 +04:00
|
|
|
if (dpt_sdh == NULL)
|
2000-02-24 21:47:55 +03:00
|
|
|
dpt_sdh = shutdownhook_establish(dpt_shutdown, NULL);
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Get the inquiry data from the HBA. */
|
1999-09-28 03:41:47 +04:00
|
|
|
dpt_hba_inquire(sc, &ei);
|
|
|
|
|
2005-02-27 03:26:58 +03:00
|
|
|
/*
|
1999-09-28 03:41:47 +04:00
|
|
|
* dpt0 at pci0 dev 12 function 0: DPT SmartRAID III (PM3224A/9X-R)
|
|
|
|
* dpt0: interrupting at irq 10
|
|
|
|
* dpt0: 64 queued commands, 1 channel(s), adapter on ID(s) 7
|
|
|
|
*/
|
2006-04-15 00:43:11 +04:00
|
|
|
for (i = 0; ei->ei_vendor[i] != ' ' && i < __arraycount(ei->ei_vendor);
|
|
|
|
i++)
|
|
|
|
vendor[i] = ei->ei_vendor[i];
|
|
|
|
vendor[i] = '\0';
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2006-04-15 00:43:11 +04:00
|
|
|
for (i = 0; ei->ei_model[i] != ' ' && i < __arraycount(ei->ei_model);
|
|
|
|
i++)
|
2001-02-24 03:03:12 +03:00
|
|
|
model[i] = ei->ei_model[i];
|
2006-04-15 00:43:11 +04:00
|
|
|
for (j = 0; ei->ei_suffix[j] != ' ' && j < __arraycount(ei->ei_suffix);
|
|
|
|
i++, j++)
|
|
|
|
model[i] = ei->ei_suffix[j];
|
1999-09-28 03:41:47 +04:00
|
|
|
model[i] = '\0';
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Find the marketing name for the board. */
|
1999-10-20 00:16:48 +04:00
|
|
|
for (i = 0; dpt_cname[i] != NULL; i += 2)
|
2001-04-25 21:53:04 +04:00
|
|
|
if (memcmp(ei->ei_model + 2, dpt_cname[i], 4) == 0)
|
1999-09-28 03:41:47 +04:00
|
|
|
break;
|
2001-04-25 21:53:04 +04:00
|
|
|
|
2006-04-15 00:43:11 +04:00
|
|
|
aprint_normal("%s %s (%s)\n", vendor, dpt_cname[i + 1], model);
|
1999-09-28 03:41:47 +04:00
|
|
|
|
|
|
|
if (intrstr != NULL)
|
2003-01-31 03:26:25 +03:00
|
|
|
aprint_normal("%s: interrupting at %s\n", sc->sc_dv.dv_xname,
|
2001-04-25 21:53:04 +04:00
|
|
|
intrstr);
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2000-02-24 21:47:55 +03:00
|
|
|
maxchannel = (ec->ec_feat3 & EC_F3_MAX_CHANNEL_MASK) >>
|
|
|
|
EC_F3_MAX_CHANNEL_SHIFT;
|
|
|
|
maxtarget = (ec->ec_feat3 & EC_F3_MAX_TARGET_MASK) >>
|
|
|
|
EC_F3_MAX_TARGET_SHIFT;
|
|
|
|
|
2003-01-31 03:26:25 +03:00
|
|
|
aprint_normal("%s: %d queued commands, %d channel(s), adapter on ID(s)",
|
2000-02-24 21:47:55 +03:00
|
|
|
sc->sc_dv.dv_xname, sc->sc_nccbs, maxchannel + 1);
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2000-02-24 21:47:55 +03:00
|
|
|
for (i = 0; i <= maxchannel; i++) {
|
|
|
|
sc->sc_hbaid[i] = ec->ec_hba[3 - i];
|
2003-01-31 03:26:25 +03:00
|
|
|
aprint_normal(" %d", sc->sc_hbaid[i]);
|
2000-02-24 21:47:55 +03:00
|
|
|
}
|
2003-01-31 03:26:25 +03:00
|
|
|
aprint_normal("\n");
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/*
|
|
|
|
* Reset the SCSI controller chip(s) and bus. XXX Do we need to do
|
|
|
|
* this for each bus?
|
|
|
|
*/
|
|
|
|
if (dpt_cmd(sc, NULL, CP_IMMEDIATE, CPI_BUS_RESET))
|
1999-09-29 21:33:02 +04:00
|
|
|
panic("%s: dpt_cmd failed", sc->sc_dv.dv_xname);
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Fill in the scsipi_adapter. */
|
|
|
|
adapt = &sc->sc_adapt;
|
|
|
|
memset(adapt, 0, sizeof(*adapt));
|
|
|
|
adapt->adapt_dev = &sc->sc_dv;
|
|
|
|
adapt->adapt_nchannels = maxchannel + 1;
|
2002-12-07 22:48:30 +03:00
|
|
|
adapt->adapt_openings = sc->sc_nccbs - 1;
|
|
|
|
adapt->adapt_max_periph = sc->sc_nccbs - 1;
|
2001-04-25 21:53:04 +04:00
|
|
|
adapt->adapt_request = dpt_scsipi_request;
|
|
|
|
adapt->adapt_minphys = dpt_minphys;
|
1999-09-30 21:15:54 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
for (i = 0; i <= maxchannel; i++) {
|
|
|
|
/* Fill in the scsipi_channel. */
|
|
|
|
chan = &sc->sc_chans[i];
|
|
|
|
memset(chan, 0, sizeof(*chan));
|
|
|
|
chan->chan_adapter = adapt;
|
|
|
|
chan->chan_bustype = &scsi_bustype;
|
|
|
|
chan->chan_channel = i;
|
|
|
|
chan->chan_ntargets = maxtarget + 1;
|
|
|
|
chan->chan_nluns = ec->ec_maxlun + 1;
|
|
|
|
chan->chan_id = sc->sc_hbaid[i];
|
|
|
|
config_found(&sc->sc_dv, chan, scsiprint);
|
2000-02-24 21:47:55 +03:00
|
|
|
}
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read the EATA configuration from the HBA and perform some sanity checks.
|
|
|
|
*/
|
|
|
|
int
|
2001-04-25 21:53:04 +04:00
|
|
|
dpt_readcfg(struct dpt_softc *sc)
|
1999-09-28 03:41:47 +04:00
|
|
|
{
|
1999-09-29 21:33:02 +04:00
|
|
|
struct eata_cfg *ec;
|
1999-09-28 03:41:47 +04:00
|
|
|
int i, j, stat;
|
|
|
|
u_int16_t *p;
|
|
|
|
|
1999-09-29 21:33:02 +04:00
|
|
|
ec = &sc->sc_ec;
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Older firmware may puke if we talk to it too soon after reset. */
|
1999-09-29 21:33:02 +04:00
|
|
|
dpt_outb(sc, HA_COMMAND, CP_RESET);
|
2000-02-24 21:47:55 +03:00
|
|
|
DELAY(750000);
|
1999-09-29 21:33:02 +04:00
|
|
|
|
|
|
|
for (i = 1000; i; i--) {
|
|
|
|
if ((dpt_inb(sc, HA_STATUS) & HA_ST_READY) != 0)
|
|
|
|
break;
|
|
|
|
DELAY(2000);
|
|
|
|
}
|
2001-04-25 21:53:04 +04:00
|
|
|
|
1999-09-29 21:33:02 +04:00
|
|
|
if (i == 0) {
|
2000-01-16 17:08:42 +03:00
|
|
|
printf("%s: HBA not ready after reset (hba status:%02x)\n",
|
1999-09-29 21:33:02 +04:00
|
|
|
sc->sc_dv.dv_xname, dpt_inb(sc, HA_STATUS));
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
1999-09-28 03:41:47 +04:00
|
|
|
while((((stat = dpt_inb(sc, HA_STATUS))
|
2000-02-24 21:47:55 +03:00
|
|
|
!= (HA_ST_READY|HA_ST_SEEK_COMPLETE))
|
|
|
|
&& (stat != (HA_ST_READY|HA_ST_SEEK_COMPLETE|HA_ST_ERROR))
|
|
|
|
&& (stat != (HA_ST_READY|HA_ST_SEEK_COMPLETE|HA_ST_ERROR|HA_ST_DRQ)))
|
2001-04-25 21:53:04 +04:00
|
|
|
|| (dpt_wait(sc, HA_ST_BUSY, 0, 2000))) {
|
2000-02-24 21:47:55 +03:00
|
|
|
/* RAID drives still spinning up? */
|
2001-04-25 21:53:04 +04:00
|
|
|
if(dpt_inb(sc, HA_ERROR) != 'D' ||
|
|
|
|
dpt_inb(sc, HA_ERROR + 1) != 'P' ||
|
|
|
|
dpt_inb(sc, HA_ERROR + 2) != 'T') {
|
|
|
|
printf("%s: HBA not ready\n", sc->sc_dv.dv_xname);
|
2000-02-24 21:47:55 +03:00
|
|
|
return (-1);
|
1999-09-28 13:18:00 +04:00
|
|
|
}
|
2000-02-24 21:47:55 +03:00
|
|
|
}
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2005-02-27 03:26:58 +03:00
|
|
|
/*
|
|
|
|
* Issue the read-config command and wait for the data to appear.
|
2001-04-25 21:53:04 +04:00
|
|
|
*
|
|
|
|
* Apparently certian firmware revisions won't DMA later on if we
|
|
|
|
* request the config data using PIO, but it makes it a lot easier
|
|
|
|
* as no DMA setup is required.
|
1999-09-28 03:41:47 +04:00
|
|
|
*/
|
|
|
|
dpt_outb(sc, HA_COMMAND, CP_PIO_GETCFG);
|
1999-09-29 21:33:02 +04:00
|
|
|
memset(ec, 0, sizeof(*ec));
|
2005-02-27 03:26:58 +03:00
|
|
|
i = ((int)&((struct eata_cfg *)0)->ec_cfglen +
|
1999-09-29 21:33:02 +04:00
|
|
|
sizeof(ec->ec_cfglen)) >> 1;
|
|
|
|
p = (u_int16_t *)ec;
|
2005-02-27 03:26:58 +03:00
|
|
|
|
1999-09-29 21:33:02 +04:00
|
|
|
if (dpt_wait(sc, 0xFF, HA_ST_DATA_RDY, 2000)) {
|
2001-04-25 21:53:04 +04:00
|
|
|
printf("%s: cfg data didn't appear (hba status:%02x)\n",
|
1999-10-20 00:16:48 +04:00
|
|
|
sc->sc_dv.dv_xname, dpt_inb(sc, HA_STATUS));
|
2001-04-25 21:53:04 +04:00
|
|
|
return (-1);
|
|
|
|
}
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Begin reading. */
|
|
|
|
while (i--)
|
2001-03-10 16:30:55 +03:00
|
|
|
*p++ = bus_space_read_stream_2(sc->sc_iot, sc->sc_ioh, HA_DATA);
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2000-02-24 21:47:55 +03:00
|
|
|
if ((i = ec->ec_cfglen) > (sizeof(struct eata_cfg)
|
|
|
|
- (int)(&(((struct eata_cfg *)0L)->ec_cfglen))
|
|
|
|
- sizeof(ec->ec_cfglen)))
|
|
|
|
i = sizeof(struct eata_cfg)
|
|
|
|
- (int)(&(((struct eata_cfg *)0L)->ec_cfglen))
|
|
|
|
- sizeof(ec->ec_cfglen);
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2005-02-27 03:26:58 +03:00
|
|
|
j = i + (int)(&(((struct eata_cfg *)0L)->ec_cfglen)) +
|
2000-02-24 21:47:55 +03:00
|
|
|
sizeof(ec->ec_cfglen);
|
|
|
|
i >>= 1;
|
1999-09-28 03:41:47 +04:00
|
|
|
|
|
|
|
while (i--)
|
2001-03-10 16:30:55 +03:00
|
|
|
*p++ = bus_space_read_stream_2(sc->sc_iot, sc->sc_ioh, HA_DATA);
|
2001-04-25 21:53:04 +04:00
|
|
|
|
2000-02-24 21:47:55 +03:00
|
|
|
/* Flush until we have read 512 bytes. */
|
|
|
|
i = (512 - j + 1) >> 1;
|
1999-09-28 03:41:47 +04:00
|
|
|
while (i--)
|
2006-05-22 03:56:09 +04:00
|
|
|
(void)bus_space_read_stream_2(sc->sc_iot, sc->sc_ioh, HA_DATA);
|
2005-02-27 03:26:58 +03:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Defaults for older firmware... */
|
1999-09-29 21:33:02 +04:00
|
|
|
if (p <= (u_short *)&ec->ec_hba[DPT_MAX_CHANNELS - 1])
|
|
|
|
ec->ec_hba[DPT_MAX_CHANNELS - 1] = 7;
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2000-02-24 21:47:55 +03:00
|
|
|
if ((dpt_inb(sc, HA_STATUS) & HA_ST_ERROR) != 0) {
|
|
|
|
printf("%s: HBA error\n", sc->sc_dv.dv_xname);
|
|
|
|
return (-1);
|
|
|
|
}
|
2001-04-25 21:53:04 +04:00
|
|
|
|
2000-01-15 21:10:32 +03:00
|
|
|
if (memcmp(ec->ec_eatasig, "EATA", 4) != 0) {
|
2001-04-25 21:53:04 +04:00
|
|
|
printf("%s: EATA signature mismatch\n", sc->sc_dv.dv_xname);
|
1999-09-28 03:41:47 +04:00
|
|
|
return (-1);
|
|
|
|
}
|
2001-04-25 21:53:04 +04:00
|
|
|
|
2000-02-24 21:47:55 +03:00
|
|
|
if ((ec->ec_feat0 & EC_F0_HBA_VALID) == 0) {
|
|
|
|
printf("%s: ec_hba field invalid\n", sc->sc_dv.dv_xname);
|
1999-09-28 03:41:47 +04:00
|
|
|
return (-1);
|
|
|
|
}
|
2001-04-25 21:53:04 +04:00
|
|
|
|
2000-02-24 21:47:55 +03:00
|
|
|
if ((ec->ec_feat0 & EC_F0_DMA_SUPPORTED) == 0) {
|
2001-04-25 21:53:04 +04:00
|
|
|
printf("%s: DMA not supported\n", sc->sc_dv.dv_xname);
|
1999-09-28 03:41:47 +04:00
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2001-04-25 21:53:04 +04:00
|
|
|
* Our `shutdownhook' to cleanly shut down the HBA. The HBA must flush all
|
|
|
|
* data from it's cache and mark array groups as clean.
|
|
|
|
*
|
|
|
|
* XXX This doesn't always work (i.e., the HBA may still be flushing after
|
|
|
|
* we tell root that it's safe to power off).
|
1999-09-28 03:41:47 +04:00
|
|
|
*/
|
2001-04-25 21:53:04 +04:00
|
|
|
static void
|
2006-11-16 04:32:37 +03:00
|
|
|
dpt_shutdown(void *cookie)
|
1999-09-28 03:41:47 +04:00
|
|
|
{
|
2001-04-25 21:53:04 +04:00
|
|
|
extern struct cfdriver dpt_cd;
|
|
|
|
struct dpt_softc *sc;
|
|
|
|
int i;
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
printf("shutting down dpt devices...");
|
|
|
|
|
|
|
|
for (i = 0; i < dpt_cd.cd_ndevs; i++) {
|
|
|
|
if ((sc = device_lookup(&dpt_cd, i)) == NULL)
|
|
|
|
continue;
|
|
|
|
dpt_cmd(sc, NULL, CP_IMMEDIATE, CPI_POWEROFF_WARN);
|
|
|
|
}
|
|
|
|
|
|
|
|
delay(10000*1000);
|
|
|
|
printf(" done\n");
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2001-04-25 21:53:04 +04:00
|
|
|
* Send an EATA command to the HBA.
|
1999-09-28 03:41:47 +04:00
|
|
|
*/
|
2001-04-25 21:53:04 +04:00
|
|
|
static int
|
|
|
|
dpt_cmd(struct dpt_softc *sc, struct dpt_ccb *ccb, int eatacmd, int icmd)
|
1999-09-28 03:41:47 +04:00
|
|
|
{
|
2001-04-25 21:53:04 +04:00
|
|
|
u_int32_t pa;
|
|
|
|
int i, s;
|
1999-09-28 03:41:47 +04:00
|
|
|
|
|
|
|
s = splbio();
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
for (i = 20000; i != 0; i--) {
|
|
|
|
if ((dpt_inb(sc, HA_AUX_STATUS) & HA_AUX_BUSY) == 0)
|
|
|
|
break;
|
|
|
|
DELAY(50);
|
|
|
|
}
|
|
|
|
if (i == 0) {
|
|
|
|
splx(s);
|
|
|
|
return (-1);
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
pa = (ccb != NULL ? ccb->ccb_ccbpa : 0);
|
|
|
|
dpt_outb(sc, HA_DMA_BASE + 0, (pa ) & 0xff);
|
|
|
|
dpt_outb(sc, HA_DMA_BASE + 1, (pa >> 8) & 0xff);
|
|
|
|
dpt_outb(sc, HA_DMA_BASE + 2, (pa >> 16) & 0xff);
|
|
|
|
dpt_outb(sc, HA_DMA_BASE + 3, (pa >> 24) & 0xff);
|
|
|
|
|
|
|
|
if (eatacmd == CP_IMMEDIATE)
|
|
|
|
dpt_outb(sc, HA_ICMD, icmd);
|
|
|
|
|
|
|
|
dpt_outb(sc, HA_COMMAND, eatacmd);
|
|
|
|
|
|
|
|
splx(s);
|
1999-09-28 03:41:47 +04:00
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2001-04-25 21:53:04 +04:00
|
|
|
* Wait for the HBA status register to reach a specific state.
|
1999-09-28 03:41:47 +04:00
|
|
|
*/
|
2001-04-25 21:53:04 +04:00
|
|
|
static int
|
|
|
|
dpt_wait(struct dpt_softc *sc, u_int8_t mask, u_int8_t state, int ms)
|
1999-09-28 03:41:47 +04:00
|
|
|
{
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
for (ms *= 10; ms != 0; ms--) {
|
|
|
|
if ((dpt_inb(sc, HA_STATUS) & mask) == state)
|
|
|
|
return (0);
|
|
|
|
DELAY(100);
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
return (-1);
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2001-04-25 21:53:04 +04:00
|
|
|
* Spin waiting for a command to finish. The timeout value from the CCB is
|
|
|
|
* used. The CCB must be marked with CCB_PRIVATE, otherwise it'll will get
|
|
|
|
* recycled before we get a look at it.
|
1999-09-28 03:41:47 +04:00
|
|
|
*/
|
2001-04-25 21:53:04 +04:00
|
|
|
static int
|
|
|
|
dpt_ccb_poll(struct dpt_softc *sc, struct dpt_ccb *ccb)
|
1999-09-28 03:41:47 +04:00
|
|
|
{
|
2001-04-25 21:53:04 +04:00
|
|
|
int i, s;
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
if ((ccb->ccb_flg & CCB_PRIVATE) == 0)
|
2002-09-27 19:35:29 +04:00
|
|
|
panic("dpt_ccb_poll: called for non-CCB_PRIVATE request");
|
2001-04-25 21:53:04 +04:00
|
|
|
#endif
|
1999-09-28 03:41:47 +04:00
|
|
|
|
|
|
|
s = splbio();
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
if ((ccb->ccb_flg & CCB_INTR) != 0) {
|
|
|
|
splx(s);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = ccb->ccb_timeout * 20; i != 0; i--) {
|
|
|
|
if ((dpt_inb(sc, HA_AUX_STATUS) & HA_AUX_INTR) != 0)
|
|
|
|
dpt_intr(sc);
|
|
|
|
if ((ccb->ccb_flg & CCB_INTR) != 0)
|
2000-02-24 21:47:55 +03:00
|
|
|
break;
|
2001-04-25 21:53:04 +04:00
|
|
|
DELAY(50);
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
splx(s);
|
2001-04-25 21:53:04 +04:00
|
|
|
return (i == 0);
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2001-04-25 21:53:04 +04:00
|
|
|
* We have a command which has been processed by the HBA, so now we look to
|
|
|
|
* see how the operation went. CCBs marked CCB_PRIVATE are not passed here
|
|
|
|
* by dpt_intr().
|
1999-09-28 03:41:47 +04:00
|
|
|
*/
|
2001-04-25 21:53:04 +04:00
|
|
|
static void
|
|
|
|
dpt_ccb_done(struct dpt_softc *sc, struct dpt_ccb *ccb)
|
1999-09-28 03:41:47 +04:00
|
|
|
{
|
|
|
|
struct scsipi_xfer *xs;
|
2001-04-25 21:53:04 +04:00
|
|
|
|
1999-09-28 03:41:47 +04:00
|
|
|
xs = ccb->ccb_xs;
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
SC_DEBUG(xs->xs_periph, SCSIPI_DB2, ("dpt_ccb_done\n"));
|
1999-09-28 03:41:47 +04:00
|
|
|
|
|
|
|
/*
|
2005-02-27 03:26:58 +03:00
|
|
|
* If we were a data transfer, unload the map that described the
|
1999-09-28 03:41:47 +04:00
|
|
|
* data buffer.
|
|
|
|
*/
|
2001-04-25 21:53:04 +04:00
|
|
|
if (xs->datalen != 0)
|
|
|
|
dpt_ccb_unmap(sc, ccb);
|
1999-09-28 03:41:47 +04:00
|
|
|
|
|
|
|
if (xs->error == XS_NOERROR) {
|
2000-02-24 21:47:55 +03:00
|
|
|
if (ccb->ccb_hba_status != SP_HBA_NO_ERROR) {
|
1999-09-28 03:41:47 +04:00
|
|
|
switch (ccb->ccb_hba_status) {
|
2000-02-24 21:47:55 +03:00
|
|
|
case SP_HBA_ERROR_SEL_TO:
|
1999-09-28 03:41:47 +04:00
|
|
|
xs->error = XS_SELTIMEOUT;
|
|
|
|
break;
|
2000-02-24 21:47:55 +03:00
|
|
|
case SP_HBA_ERROR_RESET:
|
1999-09-28 03:41:47 +04:00
|
|
|
xs->error = XS_RESET;
|
|
|
|
break;
|
2001-04-25 21:53:04 +04:00
|
|
|
default:
|
1999-09-28 03:41:47 +04:00
|
|
|
printf("%s: HBA status %x\n",
|
|
|
|
sc->sc_dv.dv_xname, ccb->ccb_hba_status);
|
|
|
|
xs->error = XS_DRIVER_STUFFUP;
|
2001-04-25 21:53:04 +04:00
|
|
|
break;
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
} else if (ccb->ccb_scsi_status != SCSI_OK) {
|
|
|
|
switch (ccb->ccb_scsi_status) {
|
|
|
|
case SCSI_CHECK:
|
2001-04-25 21:53:04 +04:00
|
|
|
memcpy(&xs->sense.scsi_sense, &ccb->ccb_sense,
|
|
|
|
sizeof(xs->sense.scsi_sense));
|
1999-09-28 03:41:47 +04:00
|
|
|
xs->error = XS_SENSE;
|
|
|
|
break;
|
|
|
|
case SCSI_BUSY:
|
2001-04-25 21:53:04 +04:00
|
|
|
case SCSI_QUEUE_FULL:
|
1999-09-28 03:41:47 +04:00
|
|
|
xs->error = XS_BUSY;
|
|
|
|
break;
|
|
|
|
default:
|
2001-04-25 21:53:04 +04:00
|
|
|
scsipi_printaddr(xs->xs_periph);
|
|
|
|
printf("SCSI status %x\n",
|
|
|
|
ccb->ccb_scsi_status);
|
1999-09-28 03:41:47 +04:00
|
|
|
xs->error = XS_DRIVER_STUFFUP;
|
2001-04-25 21:53:04 +04:00
|
|
|
break;
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
} else
|
|
|
|
xs->resid = 0;
|
2001-04-25 21:53:04 +04:00
|
|
|
|
1999-10-01 16:20:12 +04:00
|
|
|
xs->status = ccb->ccb_scsi_status;
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Free up the CCB and mark the command as done. */
|
|
|
|
dpt_ccb_free(sc, ccb);
|
1999-09-28 03:41:47 +04:00
|
|
|
scsipi_done(xs);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2001-04-25 21:53:04 +04:00
|
|
|
* Specified CCB has timed out, abort it.
|
1999-09-28 03:41:47 +04:00
|
|
|
*/
|
2001-04-25 21:53:04 +04:00
|
|
|
static void
|
|
|
|
dpt_ccb_abort(struct dpt_softc *sc, struct dpt_ccb *ccb)
|
1999-09-28 03:41:47 +04:00
|
|
|
{
|
2001-04-25 21:53:04 +04:00
|
|
|
struct scsipi_periph *periph;
|
|
|
|
struct scsipi_xfer *xs;
|
|
|
|
int s;
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
xs = ccb->ccb_xs;
|
|
|
|
periph = xs->xs_periph;
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
scsipi_printaddr(periph);
|
2005-02-27 03:26:58 +03:00
|
|
|
printf("timed out (status:%02x aux status:%02x)",
|
2001-04-25 21:53:04 +04:00
|
|
|
dpt_inb(sc, HA_STATUS), dpt_inb(sc, HA_AUX_STATUS));
|
1999-09-28 03:41:47 +04:00
|
|
|
|
|
|
|
s = splbio();
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
if ((ccb->ccb_flg & CCB_ABORT) != 0) {
|
|
|
|
/* Abort timed out, reset the HBA */
|
|
|
|
printf(" AGAIN, resetting HBA\n");
|
|
|
|
dpt_outb(sc, HA_COMMAND, CP_RESET);
|
|
|
|
DELAY(750000);
|
1999-09-28 03:41:47 +04:00
|
|
|
} else {
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Abort the operation that has timed out */
|
|
|
|
printf("\n");
|
|
|
|
xs->error = XS_TIMEOUT;
|
|
|
|
ccb->ccb_timeout = DPT_ABORT_TIMEOUT;
|
|
|
|
ccb->ccb_flg |= CCB_ABORT;
|
|
|
|
/* Start the abort */
|
|
|
|
if (dpt_cmd(sc, ccb, CP_IMMEDIATE, CPI_SPEC_ABORT))
|
|
|
|
printf("%s: dpt_cmd failed\n", sc->sc_dv.dv_xname);
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
splx(s);
|
2001-04-25 21:53:04 +04:00
|
|
|
}
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/*
|
|
|
|
* Map a data transfer.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
dpt_ccb_map(struct dpt_softc *sc, struct dpt_ccb *ccb)
|
|
|
|
{
|
|
|
|
struct scsipi_xfer *xs;
|
|
|
|
bus_dmamap_t xfer;
|
|
|
|
bus_dma_segment_t *ds;
|
|
|
|
struct eata_sg *sg;
|
|
|
|
struct eata_cp *cp;
|
|
|
|
int rv, i;
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
xs = ccb->ccb_xs;
|
|
|
|
xfer = ccb->ccb_dmamap_xfer;
|
1999-09-28 03:41:47 +04:00
|
|
|
cp = &ccb->ccb_eata_cp;
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
rv = bus_dmamap_load(sc->sc_dmat, xfer, xs->data, xs->datalen, NULL,
|
2005-02-27 03:26:58 +03:00
|
|
|
((xs->xs_control & XS_CTL_NOSLEEP) != 0 ?
|
2001-07-19 20:25:23 +04:00
|
|
|
BUS_DMA_NOWAIT : BUS_DMA_WAITOK) | BUS_DMA_STREAMING |
|
|
|
|
((xs->xs_control & XS_CTL_DATA_IN) ? BUS_DMA_READ : BUS_DMA_WRITE));
|
2001-04-25 21:53:04 +04:00
|
|
|
|
|
|
|
switch (rv) {
|
|
|
|
case 0:
|
|
|
|
break;
|
|
|
|
case ENOMEM:
|
|
|
|
case EAGAIN:
|
|
|
|
xs->error = XS_RESOURCE_SHORTAGE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
xs->error = XS_DRIVER_STUFFUP;
|
|
|
|
printf("%s: error %d loading map\n", sc->sc_dv.dv_xname, rv);
|
|
|
|
break;
|
|
|
|
}
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
if (xs->error != XS_NOERROR) {
|
|
|
|
dpt_ccb_free(sc, ccb);
|
|
|
|
scsipi_done(xs);
|
|
|
|
return (-1);
|
|
|
|
}
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
bus_dmamap_sync(sc->sc_dmat, xfer, 0, xfer->dm_mapsize,
|
|
|
|
(xs->xs_control & XS_CTL_DATA_IN) != 0 ? BUS_DMASYNC_PREREAD :
|
|
|
|
BUS_DMASYNC_PREWRITE);
|
|
|
|
|
|
|
|
/* Don't bother using scatter/gather for just 1 seg */
|
|
|
|
if (xfer->dm_nsegs == 1) {
|
|
|
|
cp->cp_dataaddr = htobe32(xfer->dm_segs[0].ds_addr);
|
|
|
|
cp->cp_datalen = htobe32(xfer->dm_segs[0].ds_len);
|
1999-09-28 03:41:47 +04:00
|
|
|
} else {
|
2001-04-25 21:53:04 +04:00
|
|
|
/*
|
2005-02-27 03:26:58 +03:00
|
|
|
* Load the hardware scatter/gather map with
|
2001-04-25 21:53:04 +04:00
|
|
|
* the contents of the DMA map.
|
|
|
|
*/
|
|
|
|
sg = ccb->ccb_sg;
|
|
|
|
ds = xfer->dm_segs;
|
|
|
|
for (i = 0; i < xfer->dm_nsegs; i++, sg++, ds++) {
|
|
|
|
sg->sg_addr = htobe32(ds->ds_addr);
|
|
|
|
sg->sg_len = htobe32(ds->ds_len);
|
|
|
|
}
|
2005-02-27 03:26:58 +03:00
|
|
|
cp->cp_dataaddr = htobe32(CCB_OFF(sc, ccb) +
|
2001-04-25 21:53:04 +04:00
|
|
|
sc->sc_dmamap->dm_segs[0].ds_addr +
|
|
|
|
offsetof(struct dpt_ccb, ccb_sg));
|
|
|
|
cp->cp_datalen = htobe32(i * sizeof(struct eata_sg));
|
|
|
|
cp->cp_ctl0 |= CP_C0_SCATTER;
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
return (0);
|
|
|
|
}
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/*
|
|
|
|
* Unmap a transfer.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
dpt_ccb_unmap(struct dpt_softc *sc, struct dpt_ccb *ccb)
|
|
|
|
{
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
bus_dmamap_sync(sc->sc_dmat, ccb->ccb_dmamap_xfer, 0,
|
|
|
|
ccb->ccb_dmamap_xfer->dm_mapsize,
|
|
|
|
(ccb->ccb_eata_cp.cp_ctl0 & CP_C0_DATA_IN) != 0 ?
|
|
|
|
BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);
|
|
|
|
bus_dmamap_unload(sc->sc_dmat, ccb->ccb_dmamap_xfer);
|
|
|
|
}
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/*
|
|
|
|
* Adjust the size of each I/O before it passes to the SCSI layer.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
dpt_minphys(struct buf *bp)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (bp->b_bcount > DPT_MAX_XFER)
|
|
|
|
bp->b_bcount = DPT_MAX_XFER;
|
|
|
|
minphys(bp);
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2001-04-25 21:53:04 +04:00
|
|
|
* Start a SCSI command.
|
1999-09-28 03:41:47 +04:00
|
|
|
*/
|
2001-04-25 21:53:04 +04:00
|
|
|
static void
|
|
|
|
dpt_scsipi_request(struct scsipi_channel *chan, scsipi_adapter_req_t req,
|
|
|
|
void *arg)
|
1999-09-28 03:41:47 +04:00
|
|
|
{
|
|
|
|
struct dpt_softc *sc;
|
2001-04-25 21:53:04 +04:00
|
|
|
struct scsipi_xfer *xs;
|
|
|
|
int flags;
|
|
|
|
struct scsipi_periph *periph;
|
|
|
|
struct dpt_ccb *ccb;
|
|
|
|
struct eata_cp *cp;
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
sc = (struct dpt_softc *)chan->chan_adapter->adapt_dev;
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
switch (req) {
|
|
|
|
case ADAPTER_REQ_RUN_XFER:
|
|
|
|
xs = arg;
|
|
|
|
periph = xs->xs_periph;
|
|
|
|
flags = xs->xs_control;
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
#ifdef DIAGNOSTIC
|
|
|
|
/* Cmds must be no more than 12 bytes for us. */
|
|
|
|
if (xs->cmdlen > 12) {
|
|
|
|
xs->error = XS_DRIVER_STUFFUP;
|
|
|
|
scsipi_done(xs);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
|
|
* XXX We can't reset devices just yet. Apparently some
|
|
|
|
* older firmware revisions don't even support it.
|
|
|
|
*/
|
|
|
|
if ((flags & XS_CTL_RESET) != 0) {
|
|
|
|
xs->error = XS_DRIVER_STUFFUP;
|
|
|
|
scsipi_done(xs);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get a CCB and fill it.
|
|
|
|
*/
|
|
|
|
ccb = dpt_ccb_alloc(sc);
|
|
|
|
ccb->ccb_xs = xs;
|
|
|
|
ccb->ccb_timeout = xs->timeout;
|
|
|
|
|
|
|
|
cp = &ccb->ccb_eata_cp;
|
|
|
|
memcpy(&cp->cp_cdb_cmd, xs->cmd, xs->cmdlen);
|
|
|
|
cp->cp_ccbid = ccb->ccb_id;
|
|
|
|
cp->cp_senselen = sizeof(ccb->ccb_sense);
|
|
|
|
cp->cp_stataddr = htobe32(sc->sc_stppa);
|
|
|
|
cp->cp_ctl0 = CP_C0_AUTO_SENSE;
|
2005-02-27 03:26:58 +03:00
|
|
|
cp->cp_ctl1 = 0;
|
2001-04-25 21:53:04 +04:00
|
|
|
cp->cp_ctl2 = 0;
|
|
|
|
cp->cp_ctl3 = periph->periph_target << CP_C3_ID_SHIFT;
|
|
|
|
cp->cp_ctl3 |= chan->chan_channel << CP_C3_CHANNEL_SHIFT;
|
|
|
|
cp->cp_ctl4 = periph->periph_lun << CP_C4_LUN_SHIFT;
|
|
|
|
cp->cp_ctl4 |= CP_C4_DIS_PRI | CP_C4_IDENTIFY;
|
|
|
|
|
|
|
|
if ((flags & XS_CTL_DATA_IN) != 0)
|
|
|
|
cp->cp_ctl0 |= CP_C0_DATA_IN;
|
|
|
|
if ((flags & XS_CTL_DATA_OUT) != 0)
|
|
|
|
cp->cp_ctl0 |= CP_C0_DATA_OUT;
|
|
|
|
if (sc->sc_hbaid[chan->chan_channel] == periph->periph_target)
|
|
|
|
cp->cp_ctl0 |= CP_C0_INTERPRET;
|
|
|
|
|
|
|
|
/* Synchronous xfers musn't write-back through the cache. */
|
|
|
|
if (xs->bp != NULL)
|
|
|
|
if ((xs->bp->b_flags & (B_ASYNC | B_READ)) == 0)
|
|
|
|
cp->cp_ctl2 |= CP_C2_NO_CACHE;
|
|
|
|
|
|
|
|
cp->cp_senseaddr =
|
|
|
|
htobe32(sc->sc_dmamap->dm_segs[0].ds_addr +
|
|
|
|
CCB_OFF(sc, ccb) + offsetof(struct dpt_ccb, ccb_sense));
|
|
|
|
|
|
|
|
if (xs->datalen != 0) {
|
|
|
|
if (dpt_ccb_map(sc, ccb))
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
cp->cp_dataaddr = 0;
|
|
|
|
cp->cp_datalen = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sync up CCB and status packet. */
|
|
|
|
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap,
|
|
|
|
CCB_OFF(sc, ccb), sizeof(struct dpt_ccb),
|
|
|
|
BUS_DMASYNC_PREWRITE);
|
|
|
|
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, sc->sc_stpoff,
|
|
|
|
sizeof(struct eata_sp), BUS_DMASYNC_PREREAD);
|
|
|
|
|
2005-02-27 03:26:58 +03:00
|
|
|
/*
|
2001-04-25 21:53:04 +04:00
|
|
|
* Start the command.
|
|
|
|
*/
|
|
|
|
if ((xs->xs_control & XS_CTL_POLL) != 0)
|
2005-02-27 03:26:58 +03:00
|
|
|
ccb->ccb_flg |= CCB_PRIVATE;
|
2001-04-25 21:53:04 +04:00
|
|
|
|
|
|
|
if (dpt_cmd(sc, ccb, CP_DMA_CMD, 0)) {
|
2000-02-24 21:47:55 +03:00
|
|
|
printf("%s: dpt_cmd failed\n", sc->sc_dv.dv_xname);
|
2001-04-25 21:53:04 +04:00
|
|
|
xs->error = XS_DRIVER_STUFFUP;
|
|
|
|
if (xs->datalen != 0)
|
|
|
|
dpt_ccb_unmap(sc, ccb);
|
|
|
|
dpt_ccb_free(sc, ccb);
|
|
|
|
break;
|
|
|
|
}
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
if ((xs->xs_control & XS_CTL_POLL) == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (dpt_ccb_poll(sc, ccb)) {
|
|
|
|
dpt_ccb_abort(sc, ccb);
|
|
|
|
/* Wait for abort to complete... */
|
|
|
|
if (dpt_ccb_poll(sc, ccb))
|
|
|
|
dpt_ccb_abort(sc, ccb);
|
2005-02-27 03:26:58 +03:00
|
|
|
}
|
2001-04-25 21:53:04 +04:00
|
|
|
|
|
|
|
dpt_ccb_done(sc, ccb);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ADAPTER_REQ_GROW_RESOURCES:
|
|
|
|
/*
|
|
|
|
* Not supported, since we allocate the maximum number of
|
|
|
|
* CCBs up front.
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ADAPTER_REQ_SET_XFER_MODE:
|
|
|
|
/*
|
|
|
|
* This will be handled by the HBA itself, and we can't
|
|
|
|
* modify that (ditto for tagged queueing).
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get inquiry data from the adapter.
|
|
|
|
*/
|
2001-04-25 21:53:04 +04:00
|
|
|
static void
|
|
|
|
dpt_hba_inquire(struct dpt_softc *sc, struct eata_inquiry_data **ei)
|
1999-09-28 03:41:47 +04:00
|
|
|
{
|
|
|
|
struct dpt_ccb *ccb;
|
|
|
|
struct eata_cp *cp;
|
2001-04-25 21:53:04 +04:00
|
|
|
|
1999-09-28 03:41:47 +04:00
|
|
|
*ei = (struct eata_inquiry_data *)sc->sc_scr;
|
|
|
|
|
|
|
|
/* Get a CCB and mark as private */
|
2001-04-25 21:53:04 +04:00
|
|
|
ccb = dpt_ccb_alloc(sc);
|
1999-09-28 03:41:47 +04:00
|
|
|
ccb->ccb_flg |= CCB_PRIVATE;
|
|
|
|
ccb->ccb_timeout = 200;
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Put all the arguments into the CCB. */
|
1999-09-28 03:41:47 +04:00
|
|
|
cp = &ccb->ccb_eata_cp;
|
|
|
|
cp->cp_ccbid = ccb->ccb_id;
|
|
|
|
cp->cp_senselen = sizeof(ccb->ccb_sense);
|
2001-04-25 21:53:04 +04:00
|
|
|
cp->cp_senseaddr = 0;
|
2000-02-24 21:47:55 +03:00
|
|
|
cp->cp_stataddr = htobe32(sc->sc_stppa);
|
1999-11-29 18:04:23 +03:00
|
|
|
cp->cp_dataaddr = htobe32(sc->sc_scrpa);
|
|
|
|
cp->cp_datalen = htobe32(sizeof(struct eata_inquiry_data));
|
2000-02-24 21:47:55 +03:00
|
|
|
cp->cp_ctl0 = CP_C0_DATA_IN | CP_C0_INTERPRET;
|
|
|
|
cp->cp_ctl1 = 0;
|
|
|
|
cp->cp_ctl2 = 0;
|
|
|
|
cp->cp_ctl3 = sc->sc_hbaid[0] << CP_C3_ID_SHIFT;
|
|
|
|
cp->cp_ctl4 = CP_C4_DIS_PRI | CP_C4_IDENTIFY;
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Put together the SCSI inquiry command. */
|
|
|
|
memset(&cp->cp_cdb_cmd, 0, 12);
|
2000-02-24 21:47:55 +03:00
|
|
|
cp->cp_cdb_cmd = INQUIRY;
|
|
|
|
cp->cp_cdb_len = sizeof(struct eata_inquiry_data);
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Sync up CCB, status packet and scratch area. */
|
2005-02-27 03:26:58 +03:00
|
|
|
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, CCB_OFF(sc, ccb),
|
1999-09-28 03:41:47 +04:00
|
|
|
sizeof(struct dpt_ccb), BUS_DMASYNC_PREWRITE);
|
2005-02-27 03:26:58 +03:00
|
|
|
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, sc->sc_stpoff,
|
1999-09-28 03:41:47 +04:00
|
|
|
sizeof(struct eata_sp), BUS_DMASYNC_PREREAD);
|
2005-02-27 03:26:58 +03:00
|
|
|
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, sc->sc_scroff,
|
1999-09-28 03:41:47 +04:00
|
|
|
sizeof(struct eata_inquiry_data), BUS_DMASYNC_PREREAD);
|
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
/* Start the command and poll on completion. */
|
|
|
|
if (dpt_cmd(sc, ccb, CP_DMA_CMD, 0))
|
1999-09-29 21:33:02 +04:00
|
|
|
panic("%s: dpt_cmd failed", sc->sc_dv.dv_xname);
|
1999-09-28 03:41:47 +04:00
|
|
|
|
2001-04-25 21:53:04 +04:00
|
|
|
if (dpt_ccb_poll(sc, ccb))
|
1999-09-28 03:41:47 +04:00
|
|
|
panic("%s: inquiry timed out", sc->sc_dv.dv_xname);
|
|
|
|
|
2000-02-24 21:47:55 +03:00
|
|
|
if (ccb->ccb_hba_status != SP_HBA_NO_ERROR ||
|
1999-09-28 03:41:47 +04:00
|
|
|
ccb->ccb_scsi_status != SCSI_OK)
|
2001-04-25 21:53:04 +04:00
|
|
|
panic("%s: inquiry failed (hba:%02x scsi:%02x)",
|
|
|
|
sc->sc_dv.dv_xname, ccb->ccb_hba_status,
|
|
|
|
ccb->ccb_scsi_status);
|
|
|
|
|
|
|
|
/* Sync up the DMA map and free CCB, returning. */
|
2005-02-27 03:26:58 +03:00
|
|
|
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, sc->sc_scroff,
|
1999-09-28 03:41:47 +04:00
|
|
|
sizeof(struct eata_inquiry_data), BUS_DMASYNC_POSTREAD);
|
2001-04-25 21:53:04 +04:00
|
|
|
dpt_ccb_free(sc, ccb);
|
1999-09-28 03:41:47 +04:00
|
|
|
}
|
2002-12-07 22:48:30 +03:00
|
|
|
|
|
|
|
int
|
2006-11-16 04:32:37 +03:00
|
|
|
dptopen(dev_t dev, int flag, int mode, struct lwp *l)
|
2002-12-07 22:48:30 +03:00
|
|
|
{
|
|
|
|
|
|
|
|
if (device_lookup(&dpt_cd, minor(dev)) == NULL)
|
|
|
|
return (ENXIO);
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2007-03-04 08:59:00 +03:00
|
|
|
dptioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
|
2002-12-07 22:48:30 +03:00
|
|
|
{
|
|
|
|
struct dpt_softc *sc;
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
sc = device_lookup(&dpt_cd, minor(dev));
|
|
|
|
|
|
|
|
switch (cmd & 0xffff) {
|
|
|
|
case DPT_SIGNATURE:
|
|
|
|
memcpy(data, &dpt_sig, min(IOCPARM_LEN(cmd), sizeof(dpt_sig)));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DPT_CTRLINFO:
|
|
|
|
dpt_ctlrinfo(sc, (struct dpt_eata_ctlrinfo *)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DPT_SYSINFO:
|
|
|
|
dpt_sysinfo(sc, (struct dpt_sysinfo *)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DPT_BLINKLED:
|
|
|
|
/*
|
|
|
|
* XXX Don't know how to get this from EATA boards. I think
|
|
|
|
* it involves waiting for a "DPT" sequence from HA_ERROR
|
|
|
|
* and then reading one of the HA_ICMD registers.
|
|
|
|
*/
|
|
|
|
*(int *)data = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DPT_EATAUSRCMD:
|
2006-12-02 06:10:42 +03:00
|
|
|
rv = kauth_authorize_device_passthru(l->l_cred, dev,
|
|
|
|
KAUTH_REQ_DEVICE_RAWIO_PASSTHRU_ALL, data);
|
2006-11-08 03:17:09 +03:00
|
|
|
if (rv)
|
|
|
|
return (rv);
|
2006-08-23 19:44:29 +04:00
|
|
|
|
2003-01-28 02:31:19 +03:00
|
|
|
if (IOCPARM_LEN(cmd) < sizeof(struct eata_ucp)) {
|
2003-01-28 21:16:18 +03:00
|
|
|
DPRINTF(("%s: ucp %lu vs %lu bytes\n",
|
2003-01-28 02:31:19 +03:00
|
|
|
sc->sc_dv.dv_xname, IOCPARM_LEN(cmd),
|
2003-01-28 21:16:18 +03:00
|
|
|
(unsigned long int)sizeof(struct eata_ucp)));
|
2003-01-28 02:31:19 +03:00
|
|
|
return (EINVAL);
|
|
|
|
}
|
|
|
|
|
2002-12-07 22:48:30 +03:00
|
|
|
if (sc->sc_uactive++)
|
|
|
|
tsleep(&sc->sc_uactive, PRIBIO, "dptslp", 0);
|
|
|
|
|
2005-12-11 15:16:03 +03:00
|
|
|
rv = dpt_passthrough(sc, (struct eata_ucp *)data, l);
|
2002-12-07 22:48:30 +03:00
|
|
|
|
|
|
|
sc->sc_uactive--;
|
|
|
|
wakeup_one(&sc->sc_uactive);
|
|
|
|
return (rv);
|
|
|
|
|
|
|
|
default:
|
|
|
|
DPRINTF(("%s: unknown ioctl %lx\n", sc->sc_dv.dv_xname, cmd));
|
|
|
|
return (ENOTTY);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
dpt_ctlrinfo(struct dpt_softc *sc, struct dpt_eata_ctlrinfo *info)
|
|
|
|
{
|
|
|
|
|
|
|
|
memset(info, 0, sizeof(*info));
|
|
|
|
info->id = sc->sc_hbaid[0];
|
|
|
|
info->vect = sc->sc_isairq;
|
|
|
|
info->base = sc->sc_isaport;
|
|
|
|
info->qdepth = sc->sc_nccbs;
|
|
|
|
info->sgsize = DPT_SG_SIZE * sizeof(struct eata_sg);
|
|
|
|
info->heads = 16;
|
|
|
|
info->sectors = 63;
|
|
|
|
info->do_drive32 = 1;
|
|
|
|
info->primary = 1;
|
|
|
|
info->cpLength = sizeof(struct eata_cp);
|
|
|
|
info->spLength = sizeof(struct eata_sp);
|
|
|
|
info->drqNum = sc->sc_isadrq;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
dpt_sysinfo(struct dpt_softc *sc, struct dpt_sysinfo *info)
|
|
|
|
{
|
|
|
|
#ifdef i386
|
|
|
|
int i, j;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
memset(info, 0, sizeof(*info));
|
|
|
|
|
|
|
|
#ifdef i386
|
|
|
|
outb (0x70, 0x12);
|
|
|
|
i = inb(0x71);
|
|
|
|
j = i >> 4;
|
|
|
|
if (i == 0x0f) {
|
|
|
|
outb (0x70, 0x19);
|
|
|
|
j = inb (0x71);
|
|
|
|
}
|
|
|
|
info->drive0CMOS = j;
|
|
|
|
|
|
|
|
j = i & 0x0f;
|
|
|
|
if (i == 0x0f) {
|
|
|
|
outb (0x70, 0x1a);
|
|
|
|
j = inb (0x71);
|
|
|
|
}
|
|
|
|
info->drive1CMOS = j;
|
|
|
|
info->processorFamily = dpt_sig.dsProcessorFamily;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the conventional memory size from CMOS.
|
|
|
|
*/
|
|
|
|
outb(0x70, 0x16);
|
|
|
|
j = inb(0x71);
|
|
|
|
j <<= 8;
|
|
|
|
outb(0x70, 0x15);
|
|
|
|
j |= inb(0x71);
|
|
|
|
info->conventionalMemSize = j;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the extended memory size from CMOS.
|
|
|
|
*/
|
|
|
|
outb(0x70, 0x31);
|
|
|
|
j = inb(0x71);
|
|
|
|
j <<= 8;
|
|
|
|
outb(0x70, 0x30);
|
|
|
|
j |= inb(0x71);
|
|
|
|
info->extendedMemSize = j;
|
|
|
|
|
|
|
|
switch (cpu_class) {
|
|
|
|
case CPUCLASS_386:
|
|
|
|
info->processorType = PROC_386;
|
|
|
|
break;
|
|
|
|
case CPUCLASS_486:
|
|
|
|
info->processorType = PROC_486;
|
|
|
|
break;
|
|
|
|
case CPUCLASS_586:
|
|
|
|
info->processorType = PROC_PENTIUM;
|
|
|
|
break;
|
|
|
|
case CPUCLASS_686:
|
|
|
|
default:
|
|
|
|
info->processorType = PROC_SEXIUM;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
info->flags = SI_CMOS_Valid | SI_BusTypeValid |
|
|
|
|
SI_MemorySizeValid | SI_NO_SmartROM;
|
|
|
|
#else
|
|
|
|
info->flags = SI_BusTypeValid | SI_NO_SmartROM;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
info->busType = sc->sc_bustype;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2005-12-11 15:16:03 +03:00
|
|
|
dpt_passthrough(struct dpt_softc *sc, struct eata_ucp *ucp, struct lwp *l)
|
2002-12-07 22:48:30 +03:00
|
|
|
{
|
|
|
|
struct dpt_ccb *ccb;
|
|
|
|
struct eata_sp sp;
|
|
|
|
struct eata_cp *cp;
|
|
|
|
struct eata_sg *sg;
|
2003-10-25 22:34:14 +04:00
|
|
|
bus_dmamap_t xfer = 0; /* XXX: gcc */
|
2002-12-07 22:48:30 +03:00
|
|
|
bus_dma_segment_t *ds;
|
2003-10-25 22:34:14 +04:00
|
|
|
int datain = 0, s, rv = 0, i, uslen; /* XXX: gcc */
|
2002-12-07 22:48:30 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Get a CCB and fill.
|
|
|
|
*/
|
|
|
|
ccb = dpt_ccb_alloc(sc);
|
|
|
|
ccb->ccb_flg |= CCB_PRIVATE | CCB_WAIT;
|
|
|
|
ccb->ccb_timeout = 0;
|
|
|
|
ccb->ccb_savesp = &sp;
|
|
|
|
|
|
|
|
cp = &ccb->ccb_eata_cp;
|
2002-12-09 18:24:28 +03:00
|
|
|
memcpy(cp, ucp->ucp_cp, sizeof(ucp->ucp_cp));
|
|
|
|
uslen = cp->cp_senselen;
|
2002-12-07 22:48:30 +03:00
|
|
|
cp->cp_ccbid = ccb->ccb_id;
|
|
|
|
cp->cp_senselen = sizeof(ccb->ccb_sense);
|
|
|
|
cp->cp_senseaddr = htobe32(sc->sc_dmamap->dm_segs[0].ds_addr +
|
|
|
|
CCB_OFF(sc, ccb) + offsetof(struct dpt_ccb, ccb_sense));
|
|
|
|
cp->cp_stataddr = htobe32(sc->sc_stppa);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Map data transfers.
|
|
|
|
*/
|
2002-12-09 18:24:28 +03:00
|
|
|
if (ucp->ucp_dataaddr && ucp->ucp_datalen) {
|
2002-12-07 22:48:30 +03:00
|
|
|
xfer = ccb->ccb_dmamap_xfer;
|
2002-12-09 18:24:28 +03:00
|
|
|
datain = ((cp->cp_ctl0 & CP_C0_DATA_IN) != 0);
|
2002-12-07 22:48:30 +03:00
|
|
|
|
2002-12-09 18:24:28 +03:00
|
|
|
if (ucp->ucp_datalen > DPT_MAX_XFER) {
|
2002-12-07 22:48:30 +03:00
|
|
|
DPRINTF(("%s: xfer too big\n", sc->sc_dv.dv_xname));
|
|
|
|
dpt_ccb_free(sc, ccb);
|
|
|
|
return (EFBIG);
|
|
|
|
}
|
|
|
|
rv = bus_dmamap_load(sc->sc_dmat, xfer,
|
2005-12-11 15:16:03 +03:00
|
|
|
ucp->ucp_dataaddr, ucp->ucp_datalen, l->l_proc,
|
2002-12-07 22:48:30 +03:00
|
|
|
BUS_DMA_WAITOK | BUS_DMA_STREAMING |
|
|
|
|
(datain ? BUS_DMA_READ : BUS_DMA_WRITE));
|
|
|
|
if (rv != 0) {
|
|
|
|
DPRINTF(("%s: map failed; %d\n", sc->sc_dv.dv_xname,
|
|
|
|
rv));
|
|
|
|
dpt_ccb_free(sc, ccb);
|
|
|
|
return (rv);
|
|
|
|
}
|
|
|
|
|
|
|
|
bus_dmamap_sync(sc->sc_dmat, xfer, 0, xfer->dm_mapsize,
|
|
|
|
(datain ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE));
|
|
|
|
|
|
|
|
sg = ccb->ccb_sg;
|
|
|
|
ds = xfer->dm_segs;
|
|
|
|
for (i = 0; i < xfer->dm_nsegs; i++, sg++, ds++) {
|
|
|
|
sg->sg_addr = htobe32(ds->ds_addr);
|
2002-12-09 18:24:28 +03:00
|
|
|
sg->sg_len = htobe32(ds->ds_len);
|
2002-12-07 22:48:30 +03:00
|
|
|
}
|
2005-02-27 03:26:58 +03:00
|
|
|
cp->cp_dataaddr = htobe32(CCB_OFF(sc, ccb) +
|
2002-12-07 22:48:30 +03:00
|
|
|
sc->sc_dmamap->dm_segs[0].ds_addr +
|
|
|
|
offsetof(struct dpt_ccb, ccb_sg));
|
|
|
|
cp->cp_datalen = htobe32(i * sizeof(struct eata_sg));
|
|
|
|
cp->cp_ctl0 |= CP_C0_SCATTER;
|
|
|
|
} else {
|
|
|
|
cp->cp_dataaddr = 0;
|
|
|
|
cp->cp_datalen = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Start the command and sleep on completion.
|
|
|
|
*/
|
2007-07-10 00:51:58 +04:00
|
|
|
uvm_lwp_hold(curlwp);
|
2005-02-27 03:26:58 +03:00
|
|
|
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, CCB_OFF(sc, ccb),
|
2002-12-07 22:48:30 +03:00
|
|
|
sizeof(struct dpt_ccb), BUS_DMASYNC_PREWRITE);
|
|
|
|
s = splbio();
|
2005-02-27 03:26:58 +03:00
|
|
|
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, sc->sc_stpoff,
|
2002-12-07 22:48:30 +03:00
|
|
|
sizeof(struct eata_sp), BUS_DMASYNC_PREREAD);
|
|
|
|
if (dpt_cmd(sc, ccb, CP_DMA_CMD, 0))
|
|
|
|
panic("%s: dpt_cmd failed", sc->sc_dv.dv_xname);
|
|
|
|
tsleep(ccb, PWAIT, "dptucmd", 0);
|
|
|
|
splx(s);
|
2007-07-10 00:51:58 +04:00
|
|
|
uvm_lwp_rele(curlwp);
|
2002-12-07 22:48:30 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Sync up the DMA map and copy out results.
|
|
|
|
*/
|
2005-02-27 03:26:58 +03:00
|
|
|
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, CCB_OFF(sc, ccb),
|
2002-12-07 22:48:30 +03:00
|
|
|
sizeof(struct dpt_ccb), BUS_DMASYNC_POSTWRITE);
|
|
|
|
|
|
|
|
if (cp->cp_datalen != 0) {
|
|
|
|
bus_dmamap_sync(sc->sc_dmat, xfer, 0, xfer->dm_mapsize,
|
|
|
|
(datain ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE));
|
|
|
|
bus_dmamap_unload(sc->sc_dmat, xfer);
|
|
|
|
}
|
|
|
|
|
2002-12-09 18:24:28 +03:00
|
|
|
if (ucp->ucp_stataddr != NULL) {
|
|
|
|
rv = copyout(&sp, ucp->ucp_stataddr, sizeof(sp));
|
2006-10-04 19:41:25 +04:00
|
|
|
if (rv != 0) {
|
2002-12-07 22:48:30 +03:00
|
|
|
DPRINTF(("%s: sp copyout() failed\n",
|
|
|
|
sc->sc_dv.dv_xname));
|
2006-10-04 19:41:25 +04:00
|
|
|
}
|
2002-12-07 22:48:30 +03:00
|
|
|
}
|
2002-12-09 18:24:28 +03:00
|
|
|
if (rv == 0 && ucp->ucp_senseaddr != NULL) {
|
|
|
|
i = min(uslen, sizeof(ccb->ccb_sense));
|
|
|
|
rv = copyout(&ccb->ccb_sense, ucp->ucp_senseaddr, i);
|
2006-10-04 19:41:25 +04:00
|
|
|
if (rv != 0) {
|
2002-12-07 22:48:30 +03:00
|
|
|
DPRINTF(("%s: sense copyout() failed\n",
|
|
|
|
sc->sc_dv.dv_xname));
|
2006-10-04 19:41:25 +04:00
|
|
|
}
|
2002-12-07 22:48:30 +03:00
|
|
|
}
|
|
|
|
|
2003-01-28 02:31:19 +03:00
|
|
|
ucp->ucp_hstatus = (u_int8_t)ccb->ccb_hba_status;
|
|
|
|
ucp->ucp_tstatus = (u_int8_t)ccb->ccb_scsi_status;
|
2002-12-07 22:48:30 +03:00
|
|
|
dpt_ccb_free(sc, ccb);
|
|
|
|
return (rv);
|
|
|
|
}
|