summaryrefslogtreecommitdiff
path: root/usr/src/uts/common
diff options
context:
space:
mode:
authorGarrett D'Amore <gdamore@opensolaris.org>2008-08-08 11:50:24 -0700
committerGarrett D'Amore <gdamore@opensolaris.org>2008-08-08 11:50:24 -0700
commit4bb7efa72ed531c10f097919636e67724ec4c25a (patch)
treec05a2e2b27d9713b50f77846c7b425378a149fb5 /usr/src/uts/common
parentd1e7ea071590dc37f56ac00d625f7d9e19d6064b (diff)
downloadillumos-joyent-4bb7efa72ed531c10f097919636e67724ec4c25a.tar.gz
PSARC 2007/654 blk2scsa
PSARC 2007/659 SDcard Stack Phase I PSARC 2008/001 SDcard Simplified Synchronous Command Processing 6632236 blk2scsa generic block layer 6632237 SDcard stack phase I 6649849 desire winbond sdcard reader support
Diffstat (limited to 'usr/src/uts/common')
-rw-r--r--usr/src/uts/common/Makefile.files17
-rw-r--r--usr/src/uts/common/Makefile.rules41
-rw-r--r--usr/src/uts/common/io/scsi/adapters/blk2scsa/blk2scsa.c1971
-rw-r--r--usr/src/uts/common/io/sdcard/adapters/sdhost/sdhost.c1467
-rw-r--r--usr/src/uts/common/io/sdcard/adapters/sdhost/sdhost.h299
-rw-r--r--usr/src/uts/common/io/sdcard/adapters/wbsd/wbsd.c1058
-rw-r--r--usr/src/uts/common/io/sdcard/adapters/wbsd/wbsd.h154
-rw-r--r--usr/src/uts/common/io/sdcard/impl/mapfile48
-rw-r--r--usr/src/uts/common/io/sdcard/impl/sda_cmd.c355
-rw-r--r--usr/src/uts/common/io/sdcard/impl/sda_host.c237
-rw-r--r--usr/src/uts/common/io/sdcard/impl/sda_init.c654
-rw-r--r--usr/src/uts/common/io/sdcard/impl/sda_mem.c653
-rw-r--r--usr/src/uts/common/io/sdcard/impl/sda_mod.c82
-rw-r--r--usr/src/uts/common/io/sdcard/impl/sda_nexus.c930
-rw-r--r--usr/src/uts/common/io/sdcard/impl/sda_slot.c896
-rw-r--r--usr/src/uts/common/io/sdcard/targets/sdcard/sdcard.c105
-rw-r--r--usr/src/uts/common/io/warlock/blk2scsa.wlcmd63
-rw-r--r--usr/src/uts/common/io/warlock/sdhost.wlcmd34
-rw-r--r--usr/src/uts/common/sys/Makefile18
-rw-r--r--usr/src/uts/common/sys/Makefile.syshdrs14
-rw-r--r--usr/src/uts/common/sys/scsi/adapters/blk2scsa.h182
-rw-r--r--usr/src/uts/common/sys/sdcard/sda.h327
-rw-r--r--usr/src/uts/common/sys/sdcard/sda_impl.h283
-rw-r--r--usr/src/uts/common/sys/sdcard/sda_ioctl.h87
-rw-r--r--usr/src/uts/common/sys/sunddi.h5
25 files changed, 9966 insertions, 14 deletions
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index e4d97623f1..65826a2483 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -23,8 +23,6 @@
# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
-# ident "%Z%%M% %I% %E% SMI"
-#
# This Makefile defines all file modules for the directory uts/common
# and its children. These are the source files which may be considered
# common to all SunOS systems.
@@ -1617,6 +1615,11 @@ NXGE_HCALL_OBJS = \
#
KICONV_EMEA_OBJS += kiconv_emea.o
+#
+# blk2scsa
+#
+BLK2SCSA_OBJS = blk2scsa.o
+
KICONV_JA_OBJS += kiconv_ja.o
KICONV_KO_OBJS += kiconv_cck_common.o kiconv_ko.o
@@ -1629,6 +1632,16 @@ KICONV_TC_OBJS += kiconv_cck_common.o kiconv_tc.o
# AAC module
#
AAC_OBJS = aac.o aac_ioctl.o
+
+#
+# sdcard modules
+#
+SDA_OBJS = sda_cmd.o sda_host.o sda_init.o sda_mem.o sda_mod.o \
+ sda_nexus.o sda_slot.o
+SDCARD_OBJS = sdcard.o
+SDHOST_OBJS = sdhost.o
+WBSD_OBJS = wbsd.o
+
#
# hxge 10G driver module
#
diff --git a/usr/src/uts/common/Makefile.rules b/usr/src/uts/common/Makefile.rules
index 4e5501a52f..c0f22a9175 100644
--- a/usr/src/uts/common/Makefile.rules
+++ b/usr/src/uts/common/Makefile.rules
@@ -23,8 +23,6 @@
# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
-# ident "%Z%%M% %I% %E% SMI"
-#
# uts/common/Makefile.rules
#
# This Makefile defines all the file build rules for the directory
@@ -763,7 +761,7 @@ $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/scsi/adapters/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
-$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/softmac/%.c
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/scsi/adapters/blk2scsa/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
@@ -775,10 +773,30 @@ $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/scsi/adapters/scsi_vhci/fops/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/sdcard/adapters/sdhost/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/sdcard/adapters/wbsd/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/sdcard/impl/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/sdcard/targets/sdcard/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/sfe/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/softmac/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/ural/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
@@ -1642,6 +1660,9 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/sata/impl/%.c
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/scsi/adapters/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
+$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/scsi/adapters/blk2scsa/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/scsi/adapters/scsi_vhci/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
@@ -1657,12 +1678,24 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/scsi/impl/%.c
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/scsi/targets/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
-$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/softmac/%.c
+$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/sdcard/adapters/sdhost/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
+$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/sdcard/adapters/wbsd/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
+$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/sdcard/impl/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
+$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/sdcard/targets/sdcard/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/sfe/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
+$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/softmac/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/ural/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
diff --git a/usr/src/uts/common/io/scsi/adapters/blk2scsa/blk2scsa.c b/usr/src/uts/common/io/scsi/adapters/blk2scsa/blk2scsa.c
new file mode 100644
index 0000000000..19eb56b808
--- /dev/null
+++ b/usr/src/uts/common/io/scsi/adapters/blk2scsa/blk2scsa.c
@@ -0,0 +1,1971 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/types.h>
+#include <sys/ksynch.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/scsi/scsi.h>
+#include <sys/scsi/adapters/blk2scsa.h>
+
+/*
+ * We implement the following SCSI-2 commands on behalf of targets:
+ *
+ * SCMD_DOORLOCK
+ * SCMD_FORMAT
+ * SCMD_INQUIRY
+ * SCMD_MODE_SENSE
+ * SCMD_READ
+ * SCMD_READ_G1
+ * SCMD_READ_CAPACITY
+ * SCMD_RELEASE
+ * SCMD_REQUEST_SENSE
+ * SCMD_RESERVE
+ * SCMD_SDIAG
+ * SCMD_START_STOP
+ * SCMD_TEST_UNIT_READY
+ * SCMD_WRITE
+ * SCMD_WRITE_G1
+ *
+ * We really should, at some point in the future, investigate offering
+ * more complete SCSI-3 commands, including the G4 and G5 variants of
+ * READ and WRITE, MODE_SELECT, PERSISTENT_RESERVE_IN,
+ * PERSISTENT_RESERVE_OUT, SYNCHRONIZE_CACHE, READ_MEDIAL_SERIAL,
+ * REPORT_LUNS, etc.
+ */
+
+typedef struct b2s_request_impl b2s_request_impl_t;
+
+struct b2s_request_impl {
+ b2s_request_t ri_public;
+ struct scsi_pkt *ri_pkt;
+ struct scsi_arq_status *ri_sts;
+ buf_t *ri_bp;
+
+ size_t ri_resid;
+ b2s_nexus_t *ri_nexus;
+ b2s_leaf_t *ri_leaf;
+ void (*ri_done)(struct b2s_request_impl *);
+};
+
+#define ri_lun ri_public.br_lun
+#define ri_target ri_public.br_target
+#define ri_cmd ri_public.br_cmd
+#define ri_errno ri_public.br_errno
+#define ri_count ri_public.br_count
+#define ri_xfered ri_public.br_xfered
+
+#define ri_flags ri_public.br_flags
+#define ri_media ri_public.br_media
+#define ri_inquiry ri_public.br_inquiry
+#define ri_lba ri_public.br_lba
+#define ri_nblks ri_public.br_nblks
+
+struct b2s_nexus {
+ dev_info_t *n_dip;
+ struct scsi_hba_tran *n_tran;
+ void *n_private;
+ ddi_dma_attr_t *n_dma;
+ boolean_t (*n_request)(void *, b2s_request_t *);
+
+ kmutex_t n_lock;
+ kcondvar_t n_cv;
+ boolean_t n_attached;
+ list_t n_leaves;
+};
+#define B2S_NEXUS_ATTACHED (1U << 0)
+
+_NOTE(MUTEX_PROTECTS_DATA(b2s_nexus::n_lock, b2s_nexus::n_leaves))
+_NOTE(SCHEME_PROTECTS_DATA("stable data", b2s_nexus::n_dip))
+_NOTE(SCHEME_PROTECTS_DATA("stable data", b2s_nexus::n_private))
+_NOTE(SCHEME_PROTECTS_DATA("stable data", b2s_nexus::n_request))
+_NOTE(SCHEME_PROTECTS_DATA("stable data", b2s_nexus::n_dma))
+_NOTE(SCHEME_PROTECTS_DATA("stable data", b2s_nexus::n_tran))
+_NOTE(SCHEME_PROTECTS_DATA("client synchronized", b2s_nexus::n_attached))
+
+struct b2s_leaf {
+ b2s_nexus_t *l_nexus;
+ uint_t l_target;
+ uint_t l_lun;
+ uint32_t l_flags;
+ char *l_uuid;
+ uint32_t l_refcnt;
+ list_node_t l_node;
+ struct scsi_inquiry l_inq;
+};
+
+_NOTE(MUTEX_PROTECTS_DATA(b2s_nexus::n_lock, b2s_leaf::l_node))
+_NOTE(MUTEX_PROTECTS_DATA(b2s_nexus::n_lock, b2s_leaf::l_refcnt))
+_NOTE(MUTEX_PROTECTS_DATA(b2s_nexus::n_lock, b2s_leaf::l_uuid))
+_NOTE(MUTEX_PROTECTS_DATA(b2s_nexus::n_lock, b2s_leaf::l_lun))
+_NOTE(MUTEX_PROTECTS_DATA(b2s_nexus::n_lock, b2s_leaf::l_target))
+_NOTE(MUTEX_PROTECTS_DATA(b2s_nexus::n_lock, b2s_leaf::l_nexus))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(b2s_leaf::l_uuid))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(b2s_leaf::l_lun))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(b2s_leaf::l_target))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(b2s_leaf::l_nexus))
+
+_NOTE(SCHEME_PROTECTS_DATA("stable data", scsi_hba_tran))
+_NOTE(SCHEME_PROTECTS_DATA("unshared data", b2s_request_impl))
+_NOTE(SCHEME_PROTECTS_DATA("unique per packet", scsi_arq_status))
+_NOTE(SCHEME_PROTECTS_DATA("unique per packet", scsi_pkt))
+_NOTE(SCHEME_PROTECTS_DATA("unique per packet", scsi_inquiry))
+_NOTE(SCHEME_PROTECTS_DATA("client synchronized", b2s_leaf::l_flags))
+
+/*
+ * This copies a string into a target buf, obeying the size limits
+ * of the target. It does not null terminate, ever.
+ */
+#define COPYSTR(src, dst) bcopy(src, dst, min(strlen(src), sizeof (dst)))
+
+/*
+ * Thank you SCSA, for making it a PITA to deal with a single byte
+ * value by turning it into a bitfield!
+ */
+#define PUTSTAT(dst, val) (*((uint8_t *)(void *)&dst) = val)
+
+struct b2s_error {
+ uint8_t e_reason; /* scsi CMD_xxx reason */
+ uint8_t e_status; /* scsi STATUS_xxx code */
+ uint8_t e_skey; /* sense key */
+ uint8_t e_asc; /* additional sense code */
+ uint8_t e_ascq; /* sense code qualifier */
+ uint8_t e_sksv[3]; /* sense key specific-value */
+};
+
+static struct b2s_error b2s_errs[B2S_NERRS];
+
+static struct modlmisc modlmisc = {
+ &mod_miscops,
+ "SCSA Block Device Emulation",
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1, { &modlmisc, NULL }
+};
+
+/*
+ * For layers that don't provide a DMA attribute, we offer a default
+ * one. Such devices probably just want to do mapin, all of the time,
+ * but since SCSI doesn't give us a way to indicate that, we have to
+ * provide a fake attribute. Slightly wasteful, but PIO-only disk
+ * devices are going to have some performance issues anyway.
+ *
+ * For such devices, we only want to commit to transferring 64K at a time,
+ * and let the SCSA layer break it up for us.
+ */
+static struct ddi_dma_attr b2s_default_dma_attr = {
+ DMA_ATTR_V0,
+ 0, /* lo address */
+ 0xffffffffffffffffULL, /* high address */
+ 0xffffU, /* DMA counter max */
+ 1, /* alignment */
+ 0x0c, /* burst sizes */
+ 1, /* minimum transfer size */
+ 0xffffU, /* maximum transfer size */
+ 0xffffU, /* maximum segment size */
+ 1, /* scatter/gather list length */
+ 1, /* granularity */
+ 0 /* DMA flags */
+};
+
+
+/*
+ * Private prototypes.
+ */
+
+static int b2s_tran_tgt_init(dev_info_t *, dev_info_t *,
+ scsi_hba_tran_t *, struct scsi_device *);
+static void b2s_tran_tgt_free(dev_info_t *, dev_info_t *,
+ scsi_hba_tran_t *, struct scsi_device *);
+static int b2s_tran_getcap(struct scsi_address *, char *, int);
+static int b2s_tran_setcap(struct scsi_address *, char *, int, int);
+static void b2s_tran_teardown_pkt(struct scsi_pkt *);
+static int b2s_tran_setup_pkt(struct scsi_pkt *, int (*)(caddr_t), caddr_t);
+static int b2s_tran_start(struct scsi_address *, struct scsi_pkt *);
+static int b2s_tran_abort(struct scsi_address *, struct scsi_pkt *);
+static int b2s_tran_reset(struct scsi_address *, int);
+static int b2s_bus_config(dev_info_t *, uint_t, ddi_bus_config_op_t, void *,
+ dev_info_t **);
+static b2s_leaf_t *b2s_hold_leaf(b2s_nexus_t *, uint_t, uint_t);
+static dev_info_t *b2s_find_node(b2s_nexus_t *, b2s_leaf_t *);
+static int b2s_create_node(b2s_nexus_t *, b2s_leaf_t *, dev_info_t **);
+static int b2s_update_props(dev_info_t *, b2s_leaf_t *, char **, int);
+static void b2s_inquiry_done(b2s_request_impl_t *);
+static int b2s_inquiry(b2s_leaf_t *);
+static void b2s_init_err_table(void);
+static int b2s_scmd_inq(b2s_request_impl_t *);
+static int b2s_scmd_tur(b2s_request_impl_t *);
+static int b2s_scmd_doorlock(b2s_request_impl_t *);
+static int b2s_scmd_format(b2s_request_impl_t *);
+static int b2s_scmd_readcap(b2s_request_impl_t *);
+static int b2s_scmd_rw(b2s_request_impl_t *);
+static int b2s_scmd_rqs(b2s_request_impl_t *);
+static int b2s_scmd_sdiag(b2s_request_impl_t *);
+static int b2s_scmd_start_stop(b2s_request_impl_t *);
+static int b2s_scmd_mode_sense(b2s_request_impl_t *);
+static int b2s_scmd_reserve_release(b2s_request_impl_t *);
+static void b2s_scmd_readcap_done(b2s_request_impl_t *);
+static void b2s_scmd_mode_sense_done(b2s_request_impl_t *);
+static void b2s_warn(b2s_leaf_t *, const char *, ...);
+
+int
+_init(void)
+{
+ int rv;
+
+ b2s_init_err_table();
+ rv = mod_install(&modlinkage);
+ return (rv);
+}
+
+int
+_fini(void)
+{
+ int rv;
+
+ rv = mod_remove(&modlinkage);
+ return (rv);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+int
+b2s_mod_init(struct modlinkage *modlp)
+{
+ return (scsi_hba_init(modlp));
+}
+
+void
+b2s_mod_fini(struct modlinkage *modlp)
+{
+ scsi_hba_fini(modlp);
+}
+
+void
+b2s_init_err_table(void)
+{
+ int i;
+
+ /* fill up most of them with defaults */
+ for (i = 0; i < B2S_NERRS; i++) {
+ b2s_errs[i].e_reason = CMD_CMPLT;
+ b2s_errs[i].e_status = STATUS_CHECK;
+ b2s_errs[i].e_skey = KEY_NO_SENSE;
+ b2s_errs[i].e_asc = 0;
+ b2s_errs[i].e_ascq = 0;
+ b2s_errs[i].e_sksv[0] = 0;
+ b2s_errs[i].e_sksv[1] = 0;
+ b2s_errs[i].e_sksv[2] = 0;
+ }
+
+ /* now flesh out real values */
+ b2s_errs[B2S_EOK].e_status = STATUS_GOOD;
+
+ b2s_errs[B2S_ENOTSUP].e_skey = KEY_ILLEGAL_REQUEST;
+ b2s_errs[B2S_ENOTSUP].e_asc = 0x20;
+
+ b2s_errs[B2S_EFORMATTING].e_skey = KEY_NOT_READY;
+ b2s_errs[B2S_EFORMATTING].e_asc = 0x04;
+ b2s_errs[B2S_EFORMATTING].e_ascq = 0x04;
+ b2s_errs[B2S_EFORMATTING].e_sksv[0] = 0x80;
+
+ b2s_errs[B2S_ENOMEDIA].e_skey = KEY_NOT_READY;
+ b2s_errs[B2S_ENOMEDIA].e_asc = 0x3A;
+
+ b2s_errs[B2S_EMEDIACHG].e_skey = KEY_UNIT_ATTENTION;
+ b2s_errs[B2S_EMEDIACHG].e_asc = 0x28;
+
+ b2s_errs[B2S_ESTOPPED].e_skey = KEY_NOT_READY;
+ b2s_errs[B2S_ESTOPPED].e_asc = 0x04;
+ b2s_errs[B2S_ESTOPPED].e_ascq = 0x02;
+
+ b2s_errs[B2S_EBLKADDR].e_skey = KEY_ILLEGAL_REQUEST;
+ b2s_errs[B2S_EBLKADDR].e_asc = 0x21;
+
+ b2s_errs[B2S_EIO].e_skey = KEY_HARDWARE_ERROR;
+ b2s_errs[B2S_EIO].e_asc = 0x08;
+ b2s_errs[B2S_EIO].e_ascq = 0x00;
+
+ b2s_errs[B2S_EHARDWARE].e_skey = KEY_HARDWARE_ERROR;
+ b2s_errs[B2S_EHARDWARE].e_asc = 0x44;
+
+ b2s_errs[B2S_ENODEV].e_reason = CMD_DEV_GONE;
+
+ b2s_errs[B2S_EMEDIA].e_skey = KEY_MEDIUM_ERROR;
+
+ b2s_errs[B2S_EDOORLOCK].e_skey = KEY_NOT_READY;
+ b2s_errs[B2S_EDOORLOCK].e_asc = 0x53;
+ b2s_errs[B2S_EDOORLOCK].e_ascq = 0x02;
+
+ b2s_errs[B2S_EWPROTECT].e_skey = KEY_DATA_PROTECT;
+ b2s_errs[B2S_EWPROTECT].e_asc = 0x27;
+
+ b2s_errs[B2S_ESTARTING].e_skey = KEY_NOT_READY;
+ b2s_errs[B2S_ESTARTING].e_asc = 0x04;
+ b2s_errs[B2S_ESTARTING].e_ascq = 0x01;
+
+ b2s_errs[B2S_ETIMEDOUT].e_skey = KEY_ABORTED_COMMAND;
+ b2s_errs[B2S_ETIMEDOUT].e_asc = 0x08;
+ b2s_errs[B2S_ETIMEDOUT].e_ascq = 0x01;
+
+ /*
+ * This one, SYSTEM_RESOURCE_FAILURE, is not really legal for
+ * DTYPE_DIRECT in SCSI-2, but sd doesn't care, and reporting
+ * it this way may help diagnosis. sd will retry it in any
+ * case.
+ */
+ b2s_errs[B2S_ENOMEM].e_skey = KEY_ABORTED_COMMAND;
+ b2s_errs[B2S_ENOMEM].e_asc = 0x55;
+
+ b2s_errs[B2S_ERESET].e_reason = CMD_RESET;
+
+ b2s_errs[B2S_EABORT].e_reason = CMD_ABORTED;
+
+ b2s_errs[B2S_ERSVD].e_status = STATUS_RESERVATION_CONFLICT;
+
+ b2s_errs[B2S_EINVAL].e_skey = KEY_ILLEGAL_REQUEST;
+ b2s_errs[B2S_EINVAL].e_asc = 0x24;
+
+ b2s_errs[B2S_EPARAM].e_skey = KEY_ILLEGAL_REQUEST;
+ b2s_errs[B2S_EPARAM].e_asc = 0x26;
+
+ b2s_errs[B2S_EBADMSG].e_reason = CMD_BADMSG;
+}
+
+/*
+ * Locate the the leaf node for the given target/lun. This must be
+ * called with the nexus lock held.
+ */
+b2s_leaf_t *
+b2s_get_leaf(b2s_nexus_t *n, uint_t target, uint_t lun)
+{
+ b2s_leaf_t *l;
+
+ ASSERT(mutex_owned(&n->n_lock));
+
+ l = list_head(&n->n_leaves);
+ while (l != NULL) {
+ ASSERT(l->l_nexus == n);
+ if ((l->l_target == target) && (l->l_lun == lun)) {
+ break;
+ }
+ l = list_next(&n->n_leaves, l);
+ }
+
+ return (l);
+}
+
+/*
+ * Locate the the leaf node for the given target/lun, and hold it. The
+ * nexus lock must *NOT* be held.
+ */
+b2s_leaf_t *
+b2s_hold_leaf(b2s_nexus_t *n, uint_t target, uint_t lun)
+{
+ b2s_leaf_t *l;
+
+ mutex_enter(&n->n_lock);
+ l = b2s_get_leaf(n, target, lun);
+ if (l != NULL) {
+ l->l_refcnt++;
+ }
+ mutex_exit(&n->n_lock);
+ return (l);
+}
+
+/*
+ * Drop the hold on the leaf.
+ */
+void
+b2s_rele_leaf(b2s_leaf_t *l)
+{
+ b2s_nexus_t *n = l->l_nexus;
+ mutex_enter(&n->n_lock);
+ l->l_refcnt--;
+ if (l->l_refcnt == 0) {
+ list_remove(&n->n_leaves, l);
+ kmem_free(l->l_uuid, strlen(l->l_uuid) + 1);
+ kmem_free(l, sizeof (*l));
+ }
+ mutex_exit(&n->n_lock);
+}
+
+/*
+ * This is used to walk the list of leaves safely, without requiring the
+ * nexus lock to be held. The returned leaf is held. (If the passed in
+ * lastl is not NULL, then it is released as well.)
+ *
+ * Pass NULL for lastl to start the walk.
+ */
+b2s_leaf_t *
+b2s_next_leaf(b2s_nexus_t *n, b2s_leaf_t *lastl)
+{
+ b2s_leaf_t *l;
+
+ mutex_enter(&n->n_lock);
+ if (lastl == NULL) {
+ l = list_head(&n->n_leaves);
+ } else {
+ l = list_next(&n->n_leaves, lastl);
+ }
+ if (l != NULL) {
+ l->l_refcnt++;
+ }
+ mutex_exit(&n->n_lock);
+
+ if (lastl != NULL) {
+ b2s_rele_leaf(lastl);
+ }
+
+ return (l);
+}
+
+void
+b2s_request_mapin(b2s_request_t *req, caddr_t *addrp, size_t *lenp)
+{
+ b2s_request_impl_t *ri = (void *)req;
+ struct scsi_pkt *pkt = ri->ri_pkt;
+ buf_t *bp;
+
+ if ((pkt != NULL) && ((bp = scsi_pkt2bp(pkt)) != NULL) &&
+ (bp->b_bcount != 0)) {
+
+ /*
+ * This uses some undocumented fields of the SCSI
+ * packet, but it is what is required to get the
+ * kernel virtual addresses.
+ */
+
+ ri->ri_bp = bioclone(bp, pkt->pkt_dma_offset,
+ pkt->pkt_dma_len, bp->b_edev, bp->b_blkno, NULL,
+ ri->ri_bp, KM_SLEEP);
+ if ((bp->b_flags & (B_PAGEIO | B_PHYS)) != 0) {
+ ri->ri_flags |= B2S_REQUEST_FLAG_MAPIN;
+ bp_mapin(ri->ri_bp);
+ }
+
+ *addrp = ri->ri_bp->b_un.b_addr;
+ *lenp = pkt->pkt_dma_len;
+ } else {
+ *addrp = 0;
+ *lenp = 0;
+ }
+}
+
+void
+b2s_request_dma(b2s_request_t *req, uint_t *ndmacp, ddi_dma_cookie_t **dmacsp)
+{
+ b2s_request_impl_t *ri = (void *)req;
+ struct scsi_pkt *pkt = ri->ri_pkt;
+
+ *ndmacp = pkt->pkt_numcookies;
+ *dmacsp = pkt->pkt_cookies;
+}
+
+void
+b2s_request_done_pkt(b2s_request_impl_t *ri)
+{
+ struct scsi_pkt *pkt;
+ uint8_t status;
+ struct scsi_arq_status *sts = ri->ri_sts;
+ b2s_err_t err;
+
+ err = ri->ri_errno;
+
+ pkt = ri->ri_pkt;
+ pkt->pkt_resid = ri->ri_resid;
+
+ bzero(sts, sizeof (*sts));
+
+ /*
+ * Make sure that the status is in range of our known errs. If we
+ * don't know it, then just cobble up a bogus one.
+ */
+ if ((err < 0) || (err >= B2S_NERRS)) {
+ pkt->pkt_reason = CMD_TRAN_ERR;
+ } else {
+ pkt->pkt_reason = b2s_errs[err].e_reason;
+ status = b2s_errs[err].e_status;
+ }
+
+ if (pkt->pkt_reason == CMD_CMPLT) {
+
+ pkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS;
+
+ PUTSTAT(sts->sts_status, status);
+
+ if (status == STATUS_CHECK) {
+ /*
+ * Contingent allegiance. We need to do the
+ * ARQ thing.
+ */
+ PUTSTAT(sts->sts_rqpkt_status, STATUS_GOOD);
+
+ sts->sts_rqpkt_reason = CMD_CMPLT;
+ sts->sts_rqpkt_resid = 0;
+ sts->sts_rqpkt_state = STATE_XFERRED_DATA |
+ STATE_GOT_BUS | STATE_GOT_STATUS;
+
+ sts->sts_sensedata.es_valid = 1;
+ sts->sts_sensedata.es_class = CLASS_EXTENDED_SENSE;
+ sts->sts_sensedata.es_key = b2s_errs[err].e_skey;
+ sts->sts_sensedata.es_add_code = b2s_errs[err].e_asc;
+ sts->sts_sensedata.es_qual_code = b2s_errs[err].e_ascq;
+ bcopy(sts->sts_sensedata.es_skey_specific,
+ b2s_errs[err].e_sksv, 3);
+ /*
+ * Stash any residue information.
+ */
+ sts->sts_sensedata.es_info_1 =
+ (ri->ri_resid >> 24) & 0xff;
+ sts->sts_sensedata.es_info_2 =
+ (ri->ri_resid >> 16) & 0xff;
+ sts->sts_sensedata.es_info_3 =
+ (ri->ri_resid >> 8) & 0xff;
+ sts->sts_sensedata.es_info_4 =
+ (ri->ri_resid) & 0xff;
+
+ pkt->pkt_state |= STATE_ARQ_DONE;
+ }
+
+ } else if (pkt->pkt_reason == CMD_ABORTED) {
+ pkt->pkt_statistics |= STAT_ABORTED;
+ } else if (pkt->pkt_reason == CMD_RESET) {
+ pkt->pkt_statistics |= STAT_DEV_RESET;
+ } else {
+ pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD;
+ }
+
+ /*
+ * N.B.: Obviously not all commands actually have a SCSI
+ * DATA-IN or DATA-OUT phase. But it doesn't matter, since
+ * sd.c only bothers to look at this flag for request sense
+ * traffic, which is always correct within our emulation.
+ *
+ * We go ahead and set it on all good packets however, since
+ * there may in the future be some additional checks to make
+ * sure a data transfer occurred. This seems safer (since
+ * then sd should examine pkt_resid) rather than leaving it
+ * off by default.
+ */
+ if (ri->ri_errno == 0) {
+ pkt->pkt_state |= STATE_XFERRED_DATA;
+ }
+
+ /*
+ * Finally, execute the callback (unless running POLLED)
+ */
+ if (((pkt->pkt_flags & FLAG_NOINTR) == 0) && (pkt->pkt_comp != NULL)) {
+ pkt->pkt_comp(pkt);
+ }
+
+}
+
+void
+b2s_request_done(b2s_request_t *req, b2s_err_t err, size_t resid)
+{
+ b2s_request_impl_t *ri = (void *)req;
+
+ ri->ri_errno = err;
+ ri->ri_resid = (ssize_t)resid;
+
+ /*
+ * Post process... this is used for massaging results into
+ * what SCSI wants.
+ */
+ if (ri->ri_done != NULL)
+ ri->ri_done(ri);
+
+ /*
+ * Undo the effect of any specific mapin that may have been done to
+ * process the request.
+ */
+ if (ri->ri_flags & B2S_REQUEST_FLAG_MAPIN) {
+ bp_mapout(ri->ri_bp);
+ ri->ri_flags &= ~B2S_REQUEST_FLAG_MAPIN;
+ }
+
+ /*
+ * For SCSI packets, we have special completion handling. For
+ * internal requests, we just mark the request done so the caller
+ * can free it.
+ */
+ if (ri->ri_pkt == NULL) {
+ b2s_nexus_t *n = ri->ri_nexus;
+
+ mutex_enter(&n->n_lock);
+ ri->ri_flags |= B2S_REQUEST_FLAG_DONE;
+ cv_broadcast(&n->n_cv);
+ mutex_exit(&n->n_lock);
+ } else {
+ b2s_request_done_pkt(ri);
+ }
+}
+
+/*ARGSUSED*/
+int
+b2s_tran_tgt_init(dev_info_t *hbadip, dev_info_t *tgtdip,
+ scsi_hba_tran_t *tran, struct scsi_device *sd)
+{
+ uint_t tgt, lun;
+ b2s_nexus_t *n;
+ b2s_leaf_t *l;
+
+ /*
+ * Lookup the target and lun.
+ */
+ tgt = (uint_t)ddi_prop_get_int(DDI_DEV_T_ANY, tgtdip,
+ DDI_PROP_DONTPASS, "target", -1);
+
+ lun = (uint_t)ddi_prop_get_int(DDI_DEV_T_ANY, tgtdip,
+ DDI_PROP_DONTPASS, "lun", -1);
+
+ n = tran->tran_hba_private;
+
+ /*
+ * Hold the leaf node as long as the devinfo node is using it.
+ */
+ l = b2s_hold_leaf(n, tgt, lun);
+ if (l == NULL) {
+ /*
+ * Target node not found on bus.
+ */
+ return (DDI_FAILURE);
+ }
+ tran->tran_tgt_private = l;
+
+ return (DDI_SUCCESS);
+}
+
+/*ARGSUSED*/
+void
+b2s_tran_tgt_free(dev_info_t *hbadip, dev_info_t *tgtdip,
+ scsi_hba_tran_t *tran, struct scsi_device *sd)
+{
+ b2s_leaf_t *l;
+
+ l = tran->tran_tgt_private;
+ ASSERT(l != NULL);
+ b2s_rele_leaf(l);
+}
+
+/*ARGSUSED*/
+int
+b2s_tran_setup_pkt(struct scsi_pkt *pkt, int (*cb)(caddr_t), caddr_t arg)
+{
+ b2s_request_impl_t *ri = pkt->pkt_ha_private;
+
+ ri->ri_pkt = pkt;
+ ri->ri_sts = (struct scsi_arq_status *)(void *)pkt->pkt_scbp;
+ ri->ri_bp = getrbuf(KM_SLEEP);
+
+ /*
+ * NB: The other fields are initialized properly at tran_start(9e).
+ * We don't care about their values the rest of the time.
+ */
+ return (0);
+}
+
+/*ARGSUSED*/
+void
+b2s_tran_teardown_pkt(struct scsi_pkt *pkt)
+{
+ b2s_request_impl_t *ri = pkt->pkt_ha_private;
+
+ /*
+ * Free the cloned buf header.
+ */
+ freerbuf(ri->ri_bp);
+}
+
+/*ARGSUSED*/
+int
+b2s_tran_getcap(struct scsi_address *ap, char *cap, int whom)
+{
+ int capid;
+
+ capid = scsi_hba_lookup_capstr(cap);
+
+ switch (capid) {
+ case SCSI_CAP_ARQ:
+ case SCSI_CAP_UNTAGGED_QING:
+ return (1);
+
+ default:
+ return (-1);
+ }
+}
+
+int
+b2s_tran_abort(struct scsi_address *ap, struct scsi_pkt *pkt)
+{
+ b2s_request_impl_t *ri;
+ b2s_nexus_t *n = ap->a_hba_tran->tran_hba_private;
+ b2s_leaf_t *l = ap->a_hba_tran->tran_tgt_private;
+ int err;
+
+ /*
+ * We can only do the blind abort of all packets. We have
+ * no way to request an individual packet be aborted.
+ */
+ if (pkt != NULL) {
+ return (B_FALSE);
+ }
+
+ ri = kmem_zalloc(sizeof (*ri), KM_NOSLEEP);
+ if (ri == NULL) {
+ return (B_FALSE);
+ }
+ ri->ri_cmd = B2S_CMD_ABORT;
+ ri->ri_target = l->l_target;
+ ri->ri_lun = l->l_lun;
+ ri->ri_flags = B2S_REQUEST_FLAG_HEAD;
+ ri->ri_leaf = l;
+ ri->ri_nexus = n;
+ /* leave all else null */
+
+ /*
+ * Submit request to device driver.
+ */
+ if (!n->n_request(n->n_private, &ri->ri_public)) {
+ /* this shouldn't happen, since we are just starting out */
+ b2s_warn(l, "Busy trying to abort");
+ kmem_free(ri, sizeof (*ri));
+ return (B_FALSE);
+ }
+
+ /*
+ * Wait for command completion.
+ */
+ mutex_enter(&n->n_lock);
+ while ((ri->ri_flags & B2S_REQUEST_FLAG_DONE) == 0)
+ cv_wait(&n->n_cv, &n->n_lock);
+ mutex_exit(&n->n_lock);
+
+ err = ri->ri_errno;
+ kmem_free(ri, sizeof (*ri));
+
+ if (err != 0) {
+ b2s_warn(l, "Failed during abort (error %d)", err);
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+int
+b2s_tran_reset(struct scsi_address *ap, int level)
+{
+ b2s_request_impl_t *ri;
+ b2s_nexus_t *n = ap->a_hba_tran->tran_hba_private;
+ b2s_leaf_t *l = ap->a_hba_tran->tran_tgt_private;
+ int err;
+
+ if (level == RESET_LUN) {
+ return (B_FALSE);
+ }
+
+ ri = kmem_zalloc(sizeof (*ri), KM_NOSLEEP);
+ if (ri == NULL) {
+ return (B_FALSE);
+ }
+ ri->ri_cmd = B2S_CMD_RESET;
+ ri->ri_target = l->l_target;
+ ri->ri_lun = l->l_lun;
+ ri->ri_flags = B2S_REQUEST_FLAG_HEAD;
+ ri->ri_leaf = l;
+ ri->ri_nexus = n;
+ /* leave all else null */
+
+ /*
+ * Submit request to device driver.
+ */
+ if (!n->n_request(n->n_private, &ri->ri_public)) {
+ /* this shouldn't happen, since we are just starting out */
+ b2s_warn(l, "Busy trying to reset");
+ kmem_free(ri, sizeof (*ri));
+ return (B_FALSE);
+ }
+
+ /*
+ * Wait for command completion.
+ */
+ mutex_enter(&n->n_lock);
+ while ((ri->ri_flags & B2S_REQUEST_FLAG_DONE) == 0)
+ cv_wait(&n->n_cv, &n->n_lock);
+ mutex_exit(&n->n_lock);
+
+ err = ri->ri_errno;
+ kmem_free(ri, sizeof (*ri));
+
+ if (err != 0) {
+ b2s_warn(l, "Failed during reset (error %d)", err);
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+/*ARGSUSED*/
+int
+b2s_tran_setcap(struct scsi_address *ap, char *cap, int val, int whom)
+{
+ int capid;
+
+ capid = scsi_hba_lookup_capstr(cap);
+
+ switch (capid) {
+ case SCSI_CAP_ARQ:
+ if (val == 0) {
+ return (0);
+ } else {
+ return (1);
+ }
+
+ default:
+ return (-1);
+ }
+}
+
+int
+b2s_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt)
+{
+ b2s_request_impl_t *ri = pkt->pkt_ha_private;
+ b2s_nexus_t *n = ap->a_hba_tran->tran_hba_private;
+ b2s_leaf_t *l = ap->a_hba_tran->tran_tgt_private;
+
+ ri->ri_errno = B2S_EOK;
+ ri->ri_resid = 0;
+ ri->ri_public.br_args.a_ints[0] = 0;
+ ri->ri_public.br_args.a_ints[1] = 0;
+ ri->ri_flags = 0;
+ ri->ri_done = NULL;
+
+ if ((n == NULL) || (l == NULL) ||
+ ((l->l_flags & B2S_LEAF_DETACHED) != 0)) {
+ /*
+ * Leaf is not on the bus!
+ *
+ * We should add support for inquiry when lun != 0,
+ * even if when the lun does not exist, but lun 0 is
+ * present. But, it turns out this is not strictly
+ * required by sd(7d).
+ */
+ b2s_request_done(&ri->ri_public, B2S_ENODEV, 0);
+ return (TRAN_ACCEPT);
+ }
+
+ ri->ri_nexus = n;
+ ri->ri_leaf = l;
+ ri->ri_target = l->l_target;
+ ri->ri_lun = l->l_lun;
+
+ if (pkt->pkt_flags & FLAG_NOINTR)
+ ri->ri_flags |= B2S_REQUEST_FLAG_POLL;
+ if (pkt->pkt_flags & FLAG_HEAD)
+ ri->ri_flags |= B2S_REQUEST_FLAG_HEAD;
+
+ switch (pkt->pkt_cdbp[0]) {
+ case SCMD_DOORLOCK:
+ return (b2s_scmd_doorlock(ri));
+
+ case SCMD_FORMAT:
+ return (b2s_scmd_format(ri));
+
+ case SCMD_INQUIRY:
+ return (b2s_scmd_inq(ri));
+
+ case SCMD_REQUEST_SENSE:
+ return (b2s_scmd_rqs(ri));
+
+ case SCMD_SDIAG:
+ return (b2s_scmd_sdiag(ri));
+
+ case SCMD_TEST_UNIT_READY:
+ return (b2s_scmd_tur(ri));
+
+ case SCMD_READ_CAPACITY:
+ return (b2s_scmd_readcap(ri));
+
+ case SCMD_RELEASE:
+ case SCMD_RESERVE:
+ return (b2s_scmd_reserve_release(ri));
+
+ case SCMD_START_STOP:
+ return (b2s_scmd_start_stop(ri));
+
+ case SCMD_MODE_SENSE:
+ return (b2s_scmd_mode_sense(ri));
+
+ case SCMD_READ:
+ case SCMD_READ_G1:
+ case SCMD_WRITE:
+ case SCMD_WRITE_G1:
+ return (b2s_scmd_rw(ri));
+
+ default:
+ b2s_request_done(&ri->ri_public, B2S_ENOTSUP, 0);
+ return (TRAN_ACCEPT);
+ }
+}
+
+/*
+ * Publish standard properties on a newly created devinfo node.
+ */
+int
+b2s_update_props(dev_info_t *dip, b2s_leaf_t *l, char **compat, int ncompat)
+{
+ if (ndi_prop_update_int(DDI_DEV_T_NONE, dip, "target", l->l_target) !=
+ DDI_PROP_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+ if (ndi_prop_update_int(DDI_DEV_T_NONE, dip, "lun", l->l_lun) !=
+ DDI_PROP_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+ if (ndi_prop_update_int(DDI_DEV_T_NONE, dip, "pm-capable", 1) !=
+ DDI_PROP_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+ if (ndi_prop_update_string_array(DDI_DEV_T_NONE, dip, "compatible",
+ compat, ncompat) != DDI_PROP_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+ if (ndi_prop_update_string(DDI_DEV_T_NONE, dip, "unique-id",
+ l->l_uuid) != DDI_PROP_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+
+ if (l->l_flags & B2S_LEAF_HOTPLUGGABLE) {
+ if (ndi_prop_create_boolean(DDI_DEV_T_NONE, dip,
+ "hotpluggable") != DDI_PROP_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+ }
+
+ return (DDI_SUCCESS);
+}
+
+/*
+ * Find the devinfo node associated with the leaf, looking up by target and
+ * lun. (Alternatively in the future we could use a full address)
+ *
+ * This must be called with the tree lock held.
+ */
+dev_info_t *
+b2s_find_node(b2s_nexus_t *n, b2s_leaf_t *l)
+{
+ dev_info_t *dip;
+ int tgt, lun;
+
+ dip = ddi_get_child(n->n_dip);
+ while (dip != NULL) {
+
+ tgt = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
+ DDI_PROP_DONTPASS, "target", -1);
+
+ lun = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
+ DDI_PROP_DONTPASS, "lun", -1);
+
+ /* is this the right target */
+ if ((lun == l->l_lun) && (tgt == l->l_target)) {
+ return (dip);
+ }
+
+ dip = ddi_get_next_sibling(dip);
+ }
+
+ return (NULL);
+
+}
+
+/*
+ * Create and attach a devinfo node for the supplied nexus/leaf
+ * combination.
+ */
+int
+b2s_create_node(b2s_nexus_t *n, b2s_leaf_t *l, dev_info_t **dipp)
+{
+ dev_info_t *dip;
+ char *name;
+ char **compat;
+ int ncompat;
+ int rv;
+
+ /*
+ * If the node was already created, then we're done.
+ */
+ if ((dip = b2s_find_node(n, l)) != NULL) {
+ if (dipp)
+ *dipp = dip;
+ return (DDI_SUCCESS);
+ }
+
+ ASSERT(l != NULL);
+
+ /*
+ * Perform an inquiry to collect key information.
+ */
+ if (b2s_inquiry(l) != DDI_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+
+ scsi_hba_nodename_compatible_get(&l->l_inq, NULL, l->l_inq.inq_dtype,
+ NULL, &name, &compat, &ncompat);
+
+ if (ndi_devi_alloc(n->n_dip, name, DEVI_SID_NODEID, &dip) !=
+ NDI_SUCCESS) {
+ scsi_hba_nodename_compatible_free(name, compat);
+ b2s_warn(l, "Unable to create devinfo node");
+ return (DDI_FAILURE);
+ }
+
+ if (b2s_update_props(dip, l, compat, ncompat) != DDI_SUCCESS) {
+ scsi_hba_nodename_compatible_free(name, compat);
+ ndi_prop_remove_all(dip);
+ (void) ndi_devi_free(dip);
+ b2s_warn(l, "Unable to create properties");
+ return (DDI_FAILURE);
+ }
+ scsi_hba_nodename_compatible_free(name, compat);
+
+ if (dipp) {
+ /*
+ * We were called by bus_config BUS_CONFIG_ONE,
+ * and therefore must be done synchronously.
+ */
+ rv = ndi_devi_online(dip, NDI_ONLINE_ATTACH);
+ if (rv == NDI_SUCCESS)
+ *dipp = dip;
+ } else {
+ /*
+ * The rest of the time, asynchronous is easier and
+ * safer (nexus could call us from interrupt context).
+ */
+ rv = ndi_devi_online_async(dip,
+ NDI_ONLINE_ATTACH | NDI_NOSLEEP);
+ }
+ if (rv != NDI_SUCCESS) {
+ b2s_warn(l, "Failed to online device");
+ ndi_prop_remove_all(dip);
+ (void) ndi_devi_free(dip);
+ return (DDI_FAILURE);
+ }
+
+ return (DDI_SUCCESS);
+}
+
+int
+b2s_bus_config(dev_info_t *ndip, uint_t flag, ddi_bus_config_op_t op,
+ void *arg, dev_info_t **ldip)
+{
+ long val;
+ char *ptr;
+ int rv;
+ scsi_hba_tran_t *tran;
+ b2s_leaf_t *l;
+ b2s_nexus_t *n;
+ int circ;
+ uint_t target, lun;
+
+ tran = ddi_get_driver_private(ndip);
+ n = tran->tran_hba_private;
+
+ ndi_devi_enter(ndip, &circ);
+
+ switch (op) {
+ case BUS_CONFIG_ONE:
+
+ /*
+ * First parse out the target and lun from the
+ * address.
+ */
+ if ((ptr = strchr((char *)arg, '@')) == NULL) {
+ rv = NDI_FAILURE;
+ break;
+ }
+ ptr++;
+ if ((ddi_strtol(ptr, &ptr, 16, &val) != 0) ||
+ (val < 0) || (*ptr != ',')) {
+ rv = NDI_FAILURE;
+ break;
+ }
+ ptr++;
+ target = (uint_t)val;
+ if ((ddi_strtol(ptr, &ptr, 16, &val) != 0) ||
+ (val < 0) || (*ptr != 0)) {
+ rv = NDI_FAILURE;
+ break;
+ }
+ lun = (uint_t)val;
+
+ /*
+ * Now lookup the leaf, and if we have it, attempt to create
+ * the devinfo node for it.
+ */
+ rv = NDI_SUCCESS;
+ if ((l = b2s_hold_leaf(n, target, lun)) != NULL) {
+ if (b2s_create_node(n, l, ldip) != DDI_SUCCESS) {
+ rv = NDI_FAILURE;
+ }
+ b2s_rele_leaf(l);
+ break;
+ }
+ break;
+
+ case BUS_CONFIG_DRIVER:
+ case BUS_CONFIG_ALL:
+
+ l = b2s_next_leaf(n, NULL);
+ while (l != NULL) {
+ (void) b2s_create_node(n, l, NULL);
+ l = b2s_next_leaf(n, l);
+ }
+
+ rv = NDI_SUCCESS;
+ break;
+
+ default:
+ rv = NDI_FAILURE;
+ break;
+ }
+
+ if (rv == NDI_SUCCESS) {
+ rv = ndi_busop_bus_config(ndip, flag, op, arg, ldip, 0);
+ }
+
+ ndi_devi_exit(ndip, circ);
+ return (rv);
+}
+
+void
+b2s_inquiry_done(b2s_request_impl_t *ri)
+{
+ struct scsi_inquiry *inqp = &ri->ri_leaf->l_inq;
+
+ /*
+ * The only post processing we have to do is to massage the
+ * strings into the inquiry structure.
+ */
+ COPYSTR(ri->ri_inquiry.inq_vendor, inqp->inq_vid);
+ COPYSTR(ri->ri_inquiry.inq_product, inqp->inq_pid);
+ COPYSTR(ri->ri_inquiry.inq_revision, inqp->inq_revision);
+ COPYSTR(ri->ri_inquiry.inq_serial, inqp->inq_serial);
+}
+
+int
+b2s_inquiry(b2s_leaf_t *l)
+{
+ b2s_nexus_t *n;
+ b2s_request_impl_t *ri;
+ struct scsi_inquiry *inqp;
+ int err;
+
+ inqp = &l->l_inq;
+ n = l->l_nexus;
+
+ /*
+ * Set up basic structure, including space padding for ASCII strings.
+ */
+ bzero(inqp, sizeof (*inqp));
+ (void) memset(inqp->inq_vid, ' ', sizeof (inqp->inq_vid));
+ (void) memset(inqp->inq_pid, ' ', sizeof (inqp->inq_pid));
+ (void) memset(inqp->inq_revision, ' ', sizeof (inqp->inq_revision));
+ (void) memset(inqp->inq_serial, ' ', sizeof (inqp->inq_serial));
+ inqp->inq_len = sizeof (*inqp) - 4;
+ inqp->inq_ansi = 2;
+ inqp->inq_rdf = RDF_SCSI2;
+ inqp->inq_dtype = DTYPE_DIRECT;
+ if (l->l_flags & B2S_LEAF_REMOVABLE)
+ inqp->inq_rmb = 1;
+
+ /*
+ * To get product strings, we have to issue a query to the driver.
+ */
+ ri = kmem_zalloc(sizeof (*ri), KM_NOSLEEP);
+ if (ri == NULL) {
+ return (DDI_FAILURE);
+ }
+ ri->ri_cmd = B2S_CMD_INQUIRY;
+ ri->ri_target = l->l_target;
+ ri->ri_lun = l->l_lun;
+ ri->ri_flags = B2S_REQUEST_FLAG_HEAD;
+ ri->ri_leaf = l;
+ ri->ri_nexus = n;
+ ri->ri_done = b2s_inquiry_done;
+ /* leave all else null */
+
+ /*
+ * Submit inquiry request to device driver.
+ */
+ if (!n->n_request(n->n_private, &ri->ri_public)) {
+ /* this shouldn't happen, since we are just starting out */
+ b2s_warn(l, "Busy trying to collect inquiry data");
+ kmem_free(ri, sizeof (*ri));
+ return (DDI_FAILURE);
+ }
+
+ /*
+ * Wait for inquiry completion.
+ */
+ mutex_enter(&n->n_lock);
+ while ((ri->ri_flags & B2S_REQUEST_FLAG_DONE) == 0)
+ cv_wait(&n->n_cv, &n->n_lock);
+ mutex_exit(&n->n_lock);
+
+ err = ri->ri_errno;
+ kmem_free(ri, sizeof (*ri));
+
+ if (err != 0) {
+ b2s_warn(l, "Failed during inquiry (error %d)", err);
+ return (DDI_FAILURE);
+ }
+
+ return (DDI_SUCCESS);
+}
+
+int
+b2s_scmd_inq(b2s_request_impl_t *ri)
+{
+ b2s_leaf_t *l = ri->ri_leaf;
+ union scsi_cdb *cdb = (void *)ri->ri_pkt->pkt_cdbp;
+ caddr_t ptr;
+ size_t resid, len;
+ uint8_t hdr[4];
+ const uint8_t *data;
+ /*
+ * Suppport inquiry pages: 0 is the list itself, and 80 is the
+ * unit serial number (in ASCII).
+ */
+ const uint8_t supp[2] = { 0, 0x80 };
+
+ b2s_request_mapin(&ri->ri_public, &ptr, &len);
+
+ hdr[2] = 0;
+
+ /*
+ * We don't support the EVP data bit, and hence neither a page code.
+ * This corresponds to the entire G0 address field (which includes
+ * a few reserved bits).
+ */
+ switch (GETG0ADDR(cdb)) {
+ case 0x00000: /* standard SCSI inquiry */
+ resid = min(sizeof (l->l_inq), GETG0COUNT(cdb));
+ len = min(resid, len);
+ bcopy(&l->l_inq, ptr, len);
+ ri->ri_resid = resid - len;
+ bcopy(&l->l_inq, ptr, len);
+ ri->ri_resid = resid - len;
+ b2s_request_done(&ri->ri_public, B2S_EOK, 0);
+ return (TRAN_ACCEPT);
+
+ case 0x10000: /* page 0 supported VPD pages */
+ data = supp;
+ hdr[0] = DTYPE_DIRECT;
+ hdr[1] = 0; /* page code */
+ hdr[2] = 0;
+ hdr[3] = 2; /* page length */
+ break;
+
+ case 0x18000: /* page 80 unit serial number */
+ data = (uint8_t *)l->l_uuid;
+ hdr[0] = DTYPE_DIRECT;
+ hdr[1] = 0x80; /* page code */
+ hdr[2] = 0;
+ hdr[3] = l->l_uuid ? strlen(l->l_uuid) : 0; /* page len */
+ break;
+
+ default:
+ b2s_request_done(&ri->ri_public, B2S_EINVAL, 0);
+ return (TRAN_ACCEPT);
+ }
+
+ resid = min(hdr[3] + 4, GETG0COUNT(cdb));
+ len = min(resid, len);
+ ri->ri_resid = resid - len;
+
+ /* now copy the header */
+ len = min(resid, 4);
+ bcopy(hdr, ptr, len);
+ resid -= len;
+
+ /* now copy the actual page data */
+ bcopy(data, ptr + len, resid);
+
+ b2s_request_done(&ri->ri_public, B2S_EOK, 0);
+ return (TRAN_ACCEPT);
+}
+
+int
+b2s_scmd_rqs(b2s_request_impl_t *ri)
+{
+ union scsi_cdb *cdb = (void *)ri->ri_pkt->pkt_cdbp;
+ size_t len, resid;
+ caddr_t ptr;
+ int rv;
+
+ /* Like inquiry, the entire G0 address field must be zero. */
+ if (GETG0ADDR(cdb) != 0) {
+ rv = B2S_EINVAL;
+ len = 0;
+ resid = 0;
+ } else {
+ struct scsi_extended_sense es;
+
+ /*
+ * We always use ARQ, unconditionally, so this command
+ * can always return success.
+ */
+ bzero(&es, sizeof (es));
+ es.es_valid = 1;
+ es.es_class = CLASS_EXTENDED_SENSE;
+ es.es_key = KEY_NO_SENSE;
+
+ resid = sizeof (es);
+
+ b2s_request_mapin(&ri->ri_public, &ptr, &len);
+
+ len = min(resid, len);
+ bcopy(&es, ptr, len);
+ resid -= len;
+
+ rv = B2S_EOK;
+ }
+ b2s_request_done(&ri->ri_public, rv, resid);
+ return (TRAN_ACCEPT);
+}
+
+int
+b2s_scmd_sdiag(b2s_request_impl_t *ri)
+{
+ union scsi_cdb *cdb = (void *)ri->ri_pkt->pkt_cdbp;
+ int rv;
+
+ /* we only support the SELFTEST bit */
+ if ((GETG0TAG(cdb) & 0x4) == 0) {
+ rv = B2S_EINVAL;
+ } else {
+ rv = B2S_EOK;
+ }
+ b2s_request_done(&ri->ri_public, rv, 0);
+ return (TRAN_ACCEPT);
+}
+
+int
+b2s_scmd_tur(b2s_request_impl_t *ri)
+{
+ b2s_nexus_t *n = ri->ri_nexus;
+
+ ri->ri_cmd = B2S_CMD_GETMEDIA;
+ if (!n->n_request(n->n_private, &ri->ri_public)) {
+ return (TRAN_BUSY);
+ }
+ return (TRAN_ACCEPT);
+}
+
+int
+b2s_scmd_doorlock(b2s_request_impl_t *ri)
+{
+ b2s_nexus_t *n = ri->ri_nexus;
+ union scsi_cdb *cdb = (void *)ri->ri_pkt->pkt_cdbp;
+
+ /*
+ * Bit 0 of the count indicates the "Prevent" mode. All other address
+ * and count bits are reserved.
+ */
+ if ((GETG0ADDR(cdb) != 0) || ((GETG0COUNT(cdb) & 0xFE) != 0)) {
+ b2s_request_done(&ri->ri_public, B2S_EINVAL, 0);
+ return (TRAN_ACCEPT);
+ }
+
+ ri->ri_cmd = (GETG0COUNT(cdb) != 0) ? B2S_CMD_LOCK : B2S_CMD_UNLOCK;
+ if (!n->n_request(n->n_private, &ri->ri_public)) {
+ return (TRAN_BUSY);
+ }
+ return (TRAN_ACCEPT);
+}
+
+int
+b2s_scmd_format(b2s_request_impl_t *ri)
+{
+ b2s_nexus_t *n = ri->ri_nexus;
+ union scsi_cdb *cdb = (void *)ri->ri_pkt->pkt_cdbp;
+ size_t len;
+ caddr_t ptr;
+
+ if (GETG0TAG(cdb) & 0x7) {
+ b2s_request_done(&ri->ri_public, B2S_EINVAL, 0);
+ return (TRAN_ACCEPT);
+ }
+
+ if (GETG0TAG(cdb) & FPB_DATA) {
+ /*
+ * FmtData set. A defect list is attached.
+ *
+ * This is an awful lot of work just to support a command
+ * option we don't ever care about. SCSI-2 says we have
+ * to do it.
+ *
+ * The alternative would just be to ignore the defect list
+ * and format options altogether. That would be a lot easier.
+ */
+
+ b2s_request_mapin(&ri->ri_public, &ptr, &len);
+
+ if (len < 4) {
+ b2s_request_done(&ri->ri_public, B2S_EBADMSG, 0);
+ return (TRAN_ACCEPT);
+ }
+
+ if ((ptr[0] != 0) || (ptr[2] != 0) || (ptr[3] != 0) ||
+ ((ptr[1] & 0xF9) != 0)) {
+ b2s_request_done(&ri->ri_public, B2S_EPARAM, 0);
+ return (TRAN_ACCEPT);
+ }
+
+ if (ptr[1] & 0x2) {
+ ri->ri_flags |= B2S_REQUEST_FLAG_IMMED;
+ }
+
+ } else if (GETG0TAG(cdb) & FPB_CMPLT) {
+ /*
+ * No defect list, so this bit (CmpLst) should have been zero!
+ */
+ b2s_request_done(&ri->ri_public, B2S_EINVAL, 0);
+ return (TRAN_ACCEPT);
+ }
+
+ ri->ri_cmd = B2S_CMD_FORMAT;
+ if (!n->n_request(n->n_private, &ri->ri_public)) {
+ return (TRAN_BUSY);
+ }
+
+ return (TRAN_ACCEPT);
+}
+
+void
+b2s_scmd_readcap_done(b2s_request_impl_t *ri)
+{
+ uint32_t lba;
+ union scsi_cdb *cdb = (void *)ri->ri_pkt->pkt_cdbp;
+ struct scsi_capacity cap;
+ caddr_t ptr;
+ size_t resid, len;
+
+ /*
+ * Lower layer resid is meaningless here.
+ */
+ if (ri->ri_errno != B2S_EOK) {
+ return;
+ }
+
+ lba = GETG1ADDR(cdb);
+
+ switch (GETG1COUNT(cdb)) {
+ case 0: /* PMI == 0 */
+ if (lba != 0) {
+ ri->ri_errno = B2S_EINVAL;
+ return;
+ }
+ break;
+ case 1: /* PMI == 1 */
+ if (lba >= ri->ri_media.media_nblks) {
+ ri->ri_errno = B2S_EBLKADDR;
+ return;
+ }
+ break;
+ default:
+ ri->ri_errno = B2S_EINVAL;
+ return;
+ }
+
+ /*
+ * Note that the capacity is the LBA of the last block, not the
+ * number of blocks. A little surprising if you don't pay close
+ * enough attention to the spec.
+ */
+ SCSI_WRITE32(&cap.capacity, ri->ri_media.media_nblks - 1);
+ SCSI_WRITE32(&cap.lbasize, ri->ri_media.media_blksz);
+
+ b2s_request_mapin(&ri->ri_public, &ptr, &len);
+
+ if (len != 0) {
+ resid = sizeof (cap);
+ len = min(resid, len);
+ bcopy(&cap, ptr, len);
+ ri->ri_resid = resid - len;
+ }
+}
+
+int
+b2s_scmd_readcap(b2s_request_impl_t *ri)
+{
+ b2s_nexus_t *n = ri->ri_nexus;
+ union scsi_cdb *cdb = (void *)ri->ri_pkt->pkt_cdbp;
+
+ /*
+ * No transfer by real target.
+ */
+ ri->ri_done = b2s_scmd_readcap_done;
+
+ if ((GETG1TAG(cdb)) != 0) {
+ b2s_request_done(&ri->ri_public, B2S_EINVAL, 0);
+ return (TRAN_ACCEPT);
+ }
+
+ ri->ri_cmd = B2S_CMD_GETMEDIA;
+ if (!n->n_request(n->n_private, &ri->ri_public)) {
+ return (TRAN_BUSY);
+ }
+
+ return (TRAN_ACCEPT);
+}
+
+int
+b2s_scmd_reserve_release(b2s_request_impl_t *ri)
+{
+ union scsi_cdb *cdb = (void *)ri->ri_pkt->pkt_cdbp;
+
+ /* we aren't checking fields we don't care about */
+ if ((GETG0TAG(cdb) & 0x1) != 0) {
+ /* extent reservations not supported */
+ b2s_request_done(&ri->ri_public, B2S_EINVAL, 0);
+ return (TRAN_ACCEPT);
+ }
+
+ /*
+ * We don't support multi-initiator access, so we always
+ * return success.
+ */
+
+ b2s_request_done(&ri->ri_public, B2S_EOK, 0);
+ return (TRAN_ACCEPT);
+}
+
+int
+b2s_scmd_start_stop(b2s_request_impl_t *ri)
+{
+ b2s_nexus_t *n = ri->ri_nexus;
+ union scsi_cdb *cdb = (void *)ri->ri_pkt->pkt_cdbp;
+ uint8_t count;
+
+ switch (GETG0ADDR(cdb)) {
+ case 0:
+ break;
+ case 0x10000: /* immed set */
+ ri->ri_flags |= B2S_REQUEST_FLAG_IMMED;
+ break;
+ default:
+ b2s_request_done(&ri->ri_public, B2S_EINVAL, 0);
+ return (TRAN_ACCEPT);
+ }
+ count = GETG0COUNT(cdb);
+ if (count > 3) {
+ b2s_request_done(&ri->ri_public, B2S_EINVAL, 0);
+ return (TRAN_ACCEPT);
+ }
+ if (count & 0x2)
+ ri->ri_flags |= B2S_REQUEST_FLAG_LOAD_EJECT;
+ if (count & 0x1) {
+ ri->ri_cmd = B2S_CMD_START;
+ } else {
+ ri->ri_cmd = B2S_CMD_STOP;
+ }
+
+ if (!n->n_request(n->n_private, &ri->ri_public)) {
+ return (TRAN_BUSY);
+ }
+ return (TRAN_ACCEPT);
+}
+
+void
+b2s_scmd_mode_sense_done(b2s_request_impl_t *ri)
+{
+ uchar_t *cdb = ri->ri_pkt->pkt_cdbp;
+ uint8_t pc, page, devspec;
+ caddr_t ptr;
+ size_t len, resid;
+ uint8_t data[16];
+
+ if ((ri->ri_errno == 0) &&
+ ((ri->ri_media.media_flags & B2S_MEDIA_FLAG_READ_ONLY) == 0)) {
+ devspec = 0;
+ } else {
+ /* this marks the media read-only */
+ devspec = 0x80;
+ }
+
+ pc = page = cdb[2];
+ pc &= 0xc0;
+ page &= 0x3f;
+
+ /* we do not support savable parameters, at all */
+ if ((pc & 0xc0) == 0x3) {
+ ri->ri_errno = B2S_ENOSAV;
+ ri->ri_resid = 0;
+ return;
+ }
+
+ b2s_request_mapin(&ri->ri_public, &ptr, &resid);
+
+ if ((page == 0x9) || (page == 0x3f)) {
+ /* Peripheral device page */
+
+ /* header */
+ data[0] = 9 + 3; /* length following */
+ data[1] = 0; /* medium type */
+ data[2] = devspec; /* mostly r/w flag */
+ data[3] = 0; /* block descriptor len */
+ len = min(4, resid);
+
+ bcopy(data, ptr, len);
+ resid -= len;
+ ptr += len;
+
+ /* page data - 9 bytes long */
+ bzero(data, 9);
+ data[0] = 0x9; /* page code */
+ data[1] = 0x8; /* following data */
+ len = min(resid, 9);
+ bcopy(data, ptr, len);
+ resid -= len;
+ ptr += len;
+ }
+
+ if ((page == 0xa) || (page == 0x3f)) {
+ /* Control mode page */
+
+ /* header */
+ data[0] = 8 + 3; /* length following */
+ data[1] = 0; /* medium type */
+ data[2] = devspec; /* mostly r/w flag */
+ data[3] = 0; /* block descriptor len */
+ len = min(4, resid);
+
+ bcopy(data, ptr, len);
+ resid -= len;
+ ptr += len;
+
+ /* page data - 9 bytes long */
+ bzero(data, 8);
+ data[0] = 0xa; /* page code */
+ data[1] = 0x7; /* following data */
+ len = min(resid, 9);
+ bcopy(data, ptr, len);
+ resid -= len;
+ ptr += len;
+ }
+ ri->ri_resid = 0;
+ ri->ri_errno = B2S_EOK;
+}
+
+int
+b2s_scmd_mode_sense(b2s_request_impl_t *ri)
+{
+ b2s_nexus_t *n = ri->ri_nexus;
+
+ ri->ri_done = b2s_scmd_mode_sense_done;
+ ri->ri_cmd = B2S_CMD_GETMEDIA;
+ if (!n->n_request(n->n_private, &ri->ri_public)) {
+ return (TRAN_BUSY);
+ }
+ return (TRAN_ACCEPT);
+}
+
+int
+b2s_scmd_rw(b2s_request_impl_t *ri)
+{
+ b2s_nexus_t *n = ri->ri_nexus;
+ uint32_t lba;
+ uint32_t nblks;
+ union scsi_cdb *cdb = (void *)ri->ri_pkt->pkt_cdbp;
+
+ switch (GETGROUP(cdb)) {
+ case CDB_GROUPID_0:
+ nblks = GETG0COUNT(cdb);
+ nblks = nblks ? nblks : 256;
+ lba = GETG0ADDR(cdb);
+ break;
+ case CDB_GROUPID_1:
+ if (GETG1TAG(cdb)) {
+ /* we don't support relative addresses */
+ b2s_request_done(&ri->ri_public, B2S_EINVAL, 0);
+ return (TRAN_ACCEPT);
+ }
+ lba = GETG1ADDR(cdb);
+ nblks = GETG1COUNT(cdb);
+ break;
+ default:
+ b2s_request_done(&ri->ri_public, B2S_ENOTSUP, 0);
+ return (TRAN_ACCEPT);
+ }
+
+ if (nblks == 0) {
+ b2s_request_done(&ri->ri_public, 0, 0);
+ return (TRAN_ACCEPT);
+ }
+
+ ri->ri_nblks = nblks;
+ ri->ri_lba = lba;
+ ri->ri_flags |= B2S_REQUEST_FLAG_BLKS;
+ ri->ri_cmd = (GETCMD(cdb)) == SCMD_READ ?
+ B2S_CMD_READ : B2S_CMD_WRITE;
+
+ if (!n->n_request(n->n_private, &ri->ri_public)) {
+ return (TRAN_BUSY);
+ }
+ return (TRAN_ACCEPT);
+}
+
+void
+b2s_warn(b2s_leaf_t *l, const char *fmt, ...)
+{
+ va_list ap;
+ b2s_nexus_t *n;
+ char msg[256];
+
+ n = l->l_nexus;
+
+ (void) snprintf(msg, sizeof (msg), "%s%d target %d lun %d: %s",
+ ddi_driver_name(n->n_dip), ddi_get_instance(n->n_dip),
+ l->l_target, l->l_lun, fmt);
+
+ va_start(ap, fmt);
+ vcmn_err(CE_WARN, msg, ap);
+ va_end(ap);
+}
+
+b2s_nexus_t *
+b2s_alloc_nexus(b2s_nexus_info_t *info)
+{
+ b2s_nexus_t *n;
+ struct scsi_hba_tran *tran;
+
+ if (info->nexus_version != B2S_VERSION_0)
+ return (NULL);
+
+ n = kmem_zalloc(sizeof (*n), KM_SLEEP);
+ mutex_init(&n->n_lock, NULL, MUTEX_DRIVER, NULL);
+ cv_init(&n->n_cv, NULL, CV_DRIVER, NULL);
+ list_create(&n->n_leaves, sizeof (struct b2s_leaf),
+ offsetof(struct b2s_leaf, l_node));
+
+ n->n_dip = info->nexus_dip;
+ n->n_private = info->nexus_private;
+ n->n_request = info->nexus_request;
+ if (info->nexus_dma_attr != NULL) {
+ n->n_dma = info->nexus_dma_attr;
+ } else {
+ n->n_dma = &b2s_default_dma_attr;
+ }
+
+ tran = scsi_hba_tran_alloc(n->n_dip, SCSI_HBA_CANSLEEP);
+ if (tran == NULL) {
+ list_destroy(&n->n_leaves);
+ mutex_destroy(&n->n_lock);
+ cv_destroy(&n->n_cv);
+ kmem_free(n, sizeof (*n));
+ return (NULL);
+ }
+ n->n_tran = tran;
+
+ tran->tran_hba_dip = n->n_dip;
+ tran->tran_hba_private = n;
+ tran->tran_tgt_private = NULL;
+ tran->tran_tgt_init = b2s_tran_tgt_init;
+ tran->tran_tgt_free = b2s_tran_tgt_free;
+ tran->tran_tgt_probe = scsi_hba_probe;
+ tran->tran_tgt_free = NULL;
+ tran->tran_start = b2s_tran_start;
+ tran->tran_reset = b2s_tran_reset;
+ tran->tran_abort = b2s_tran_abort;
+ tran->tran_getcap = b2s_tran_getcap;
+ tran->tran_setcap = b2s_tran_setcap;
+ tran->tran_setup_pkt = b2s_tran_setup_pkt;
+ tran->tran_teardown_pkt = b2s_tran_teardown_pkt;
+ tran->tran_hba_len = sizeof (b2s_request_impl_t);
+ tran->tran_bus_config = b2s_bus_config;
+
+ return (n);
+}
+
+void
+b2s_free_nexus(b2s_nexus_t *n)
+{
+ b2s_leaf_t *l;
+
+ /*
+ * Toss any registered leaves, if we haven't already done so.
+ * At this point we don't care about upper layers, because the
+ * DDI should not have allowed us to detach if there were busy
+ * targets.
+ */
+ while ((l = list_head(&n->n_leaves)) != NULL) {
+ list_remove(&n->n_leaves, l);
+ kmem_free(l, sizeof (struct b2s_leaf));
+ }
+ list_destroy(&n->n_leaves);
+ mutex_destroy(&n->n_lock);
+ cv_destroy(&n->n_cv);
+ kmem_free(n, sizeof (struct b2s_nexus));
+}
+
+int
+b2s_attach_nexus(b2s_nexus_t *n)
+{
+ int rv;
+
+ rv = scsi_hba_attach_setup(n->n_dip, n->n_dma, n->n_tran,
+ SCSI_HBA_TRAN_SCB | SCSI_HBA_TRAN_CDB | SCSI_HBA_TRAN_CLONE);
+ if (rv == 0) {
+ n->n_attached = B_TRUE;
+ }
+ return (rv);
+}
+
+int
+b2s_detach_nexus(b2s_nexus_t *n)
+{
+ int rv;
+
+ if (n->n_attached) {
+ rv = scsi_hba_detach(n->n_dip);
+ if (rv == 0) {
+ n->n_attached = B_FALSE;
+ }
+ } else {
+ rv = 0;
+ }
+ return ((rv == 0) ? DDI_SUCCESS : DDI_FAILURE);
+}
+
+b2s_leaf_t *
+b2s_attach_leaf(b2s_nexus_t *n, b2s_leaf_info_t *info)
+{
+ b2s_leaf_t *l;
+ uint_t target = info->leaf_target;
+ uint_t lun = info->leaf_lun;
+ const char *uuid = info->leaf_unique_id;
+ uint32_t flags = info->leaf_flags;
+
+ if (uuid == NULL) {
+ uuid = "";
+ }
+
+ mutex_enter(&n->n_lock);
+
+ /*
+ * If the leaf already exists, it is a sign that the device
+ * was kept around because it was still in use. In that case,
+ * we attempt to detect the situation where the node is the same
+ * as the previous one, and reconnect it.
+ */
+ if ((l = b2s_get_leaf(n, target, lun)) != NULL) {
+ if (strcmp(l->l_uuid, uuid) != 0) {
+ /*
+ * Leaf already exists, but is not the same! This
+ * would be a good time to issue a warning.
+ */
+ mutex_exit(&n->n_lock);
+ b2s_warn(l, "Target disconnected while still in use.");
+ b2s_warn(l, "Reconnect the previous target device.");
+ return (NULL);
+ }
+ l->l_flags &= ~B2S_LEAF_DETACHED;
+ } else {
+ if ((l = kmem_zalloc(sizeof (*l), KM_NOSLEEP)) == NULL) {
+ mutex_exit(&n->n_lock);
+ b2s_warn(l, "Unable to allocate target state.");
+ return (NULL);
+ }
+ l->l_nexus = n;
+ l->l_target = target;
+ l->l_lun = lun;
+ l->l_flags = flags;
+
+ /* strdup would be nice here */
+ l->l_uuid = kmem_alloc(strlen(uuid) + 1, KM_NOSLEEP);
+ if (l->l_uuid == NULL) {
+ mutex_exit(&n->n_lock);
+ kmem_free(l, sizeof (*l));
+ b2s_warn(l, "Unable to allocate target UUID storage.");
+ return (NULL);
+ }
+ (void) strcpy(l->l_uuid, uuid);
+
+ list_insert_tail(&n->n_leaves, l);
+ }
+
+ /*
+ * Make sure we hold it, so that it won't be freed out from
+ * underneath us.
+ */
+ l->l_refcnt++;
+ mutex_exit(&n->n_lock);
+
+ /*
+ * If the HBA is currently attached, then we need to attach
+ * the node right now. This supports "hotplug". Note that
+ * if the node is a reinsert, then this should degenerate into
+ * a NOP.
+ */
+ if (n->n_attached) {
+ int circ;
+ ndi_devi_enter(n->n_dip, &circ);
+ (void) b2s_create_node(n, l, NULL);
+ ndi_devi_exit(n->n_dip, circ);
+ }
+
+ return (l);
+}
+
+void
+b2s_detach_leaf(b2s_leaf_t *l)
+{
+ b2s_nexus_t *n = l->l_nexus;
+ dev_info_t *dip;
+ int circ;
+
+ l->l_flags |= B2S_LEAF_DETACHED;
+
+ /*
+ * Search for an appropriate child devinfo.
+ */
+ ndi_devi_enter(n->n_dip, &circ);
+ dip = b2s_find_node(n, l);
+ if (dip != NULL) {
+ (void) ndi_devi_offline(dip, NDI_DEVI_REMOVE);
+ }
+ ndi_devi_exit(n->n_dip, circ);
+
+ b2s_rele_leaf(l);
+}
diff --git a/usr/src/uts/common/io/sdcard/adapters/sdhost/sdhost.c b/usr/src/uts/common/io/sdcard/adapters/sdhost/sdhost.c
new file mode 100644
index 0000000000..6587c22187
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/adapters/sdhost/sdhost.c
@@ -0,0 +1,1467 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include "sdhost.h"
+
+typedef struct sdslot sdslot_t;
+typedef struct sdhost sdhost_t;
+
+/*
+ * Per slot state.
+ */
+struct sdslot {
+ sda_host_t *ss_host;
+ int ss_num;
+ ddi_acc_handle_t ss_acch;
+ caddr_t ss_regva;
+ kmutex_t ss_lock;
+ uint32_t ss_capab;
+ uint32_t ss_baseclk; /* Hz */
+ uint32_t ss_cardclk; /* Hz */
+ uint8_t ss_tmoutclk;
+ uint32_t ss_tmusecs; /* timeout units in usecs */
+ uint32_t ss_ocr; /* OCR formatted voltages */
+ uint16_t ss_mode;
+ boolean_t ss_suspended;
+
+ /*
+ * Command in progress
+ */
+ uint8_t *ss_kvaddr;
+ ddi_dma_cookie_t *ss_dmacs;
+ uint_t ss_ndmac;
+ int ss_blksz;
+ uint16_t ss_resid; /* in blocks */
+
+ /* scratch buffer, to receive extra PIO data */
+ uint32_t ss_bounce[2048 / 4];
+};
+
+/*
+ * Per controller state.
+ */
+struct sdhost {
+ int sh_numslots;
+ ddi_dma_attr_t sh_dmaattr;
+ sdslot_t sh_slots[SDHOST_MAXSLOTS];
+ sda_host_t *sh_host;
+
+ /*
+ * Interrupt related information.
+ */
+ ddi_intr_handle_t sh_ihandle;
+ int sh_icap;
+ uint_t sh_ipri;
+};
+
+
+static int sdhost_attach(dev_info_t *, ddi_attach_cmd_t);
+static int sdhost_detach(dev_info_t *, ddi_detach_cmd_t);
+static int sdhost_suspend(dev_info_t *);
+static int sdhost_resume(dev_info_t *);
+
+static void sdhost_enable_interrupts(sdslot_t *);
+static void sdhost_disable_interrupts(sdslot_t *);
+static int sdhost_setup_intr(dev_info_t *, sdhost_t *);
+static uint_t sdhost_intr(caddr_t, caddr_t);
+static int sdhost_init_slot(dev_info_t *, sdhost_t *, int, int);
+static void sdhost_uninit_slot(sdhost_t *, int);
+static sda_err_t sdhost_soft_reset(sdslot_t *, uint8_t);
+static sda_err_t sdhost_set_clock(sdslot_t *, uint32_t);
+static void sdhost_xfer_done(sdslot_t *, sda_err_t);
+static sda_err_t sdhost_wait_cmd(sdslot_t *, sda_cmd_t *);
+static uint_t sdhost_slot_intr(sdslot_t *);
+
+static sda_err_t sdhost_cmd(void *, sda_cmd_t *);
+static sda_err_t sdhost_getprop(void *, sda_prop_t, uint32_t *);
+static sda_err_t sdhost_setprop(void *, sda_prop_t, uint32_t);
+static sda_err_t sdhost_poll(void *);
+static sda_err_t sdhost_reset(void *);
+static sda_err_t sdhost_halt(void *);
+
+static struct dev_ops sdhost_dev_ops = {
+ DEVO_REV, /* devo_rev */
+ 0, /* devo_refcnt */
+ ddi_no_info, /* devo_getinfo */
+ nulldev, /* devo_identify */
+ nulldev, /* devo_probe */
+ sdhost_attach, /* devo_attach */
+ sdhost_detach, /* devo_detach */
+ nodev, /* devo_reset */
+ NULL, /* devo_cb_ops */
+ NULL, /* devo_bus_ops */
+ NULL /* devo_power */
+};
+
+static struct modldrv sdhost_modldrv = {
+ &mod_driverops, /* drv_modops */
+ "Standard SD Host Controller", /* drv_linkinfo */
+ &sdhost_dev_ops /* drv_dev_ops */
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1, /* ml_rev */
+ { &sdhost_modldrv, NULL } /* ml_linkage */
+};
+
+static struct sda_ops sdhost_ops = {
+ SDA_OPS_VERSION,
+ sdhost_cmd, /* so_cmd */
+ sdhost_getprop, /* so_getprop */
+ sdhost_setprop, /* so_setprop */
+ sdhost_poll, /* so_poll */
+ sdhost_reset, /* so_reset */
+ sdhost_halt, /* so_halt */
+};
+
+static ddi_device_acc_attr_t sdhost_regattr = {
+ DDI_DEVICE_ATTR_V0, /* devacc_attr_version */
+ DDI_STRUCTURE_LE_ACC, /* devacc_attr_endian_flags */
+ DDI_STRICTORDER_ACC, /* devacc_attr_dataorder */
+ DDI_DEFAULT_ACC, /* devacc_attr_access */
+};
+
+#define GET16(ss, reg) \
+ ddi_get16(ss->ss_acch, (void *)(ss->ss_regva + reg))
+#define PUT16(ss, reg, val) \
+ ddi_put16(ss->ss_acch, (void *)(ss->ss_regva + reg), val)
+#define GET32(ss, reg) \
+ ddi_get32(ss->ss_acch, (void *)(ss->ss_regva + reg))
+#define PUT32(ss, reg, val) \
+ ddi_put32(ss->ss_acch, (void *)(ss->ss_regva + reg), val)
+#define GET64(ss, reg) \
+ ddi_get64(ss->ss_acch, (void *)(ss->ss_regva + reg))
+
+#define GET8(ss, reg) \
+ ddi_get8(ss->ss_acch, (void *)(ss->ss_regva + reg))
+#define PUT8(ss, reg, val) \
+ ddi_put8(ss->ss_acch, (void *)(ss->ss_regva + reg), val)
+
+#define CLR8(ss, reg, mask) PUT8(ss, reg, GET8(ss, reg) & ~(mask))
+#define SET8(ss, reg, mask) PUT8(ss, reg, GET8(ss, reg) | (mask))
+
+/*
+ * If ever anyone uses PIO on SPARC, we have to endian-swap. But we
+ * think that SD Host Controllers are likely to be uncommon on SPARC,
+ * and hopefully when they exist at all they will be able to use DMA.
+ */
+#ifdef _BIG_ENDIAN
+#define sw32(x) ddi_swap32(x)
+#define sw16(x) ddi_swap16(x)
+#else
+#define sw32(x) (x)
+#define sw16(x) (x)
+#endif
+
+#define GETDATA32(ss) sw32(GET32(ss, REG_DATA))
+#define GETDATA16(ss) sw16(GET16(ss, REG_DATA))
+#define GETDATA8(ss) GET8(ss, REG_DATA)
+
+#define PUTDATA32(ss, val) PUT32(ss, REG_DATA, sw32(val))
+#define PUTDATA16(ss, val) PUT16(ss, REG_DATA, sw16(val))
+#define PUTDATA8(ss, val) PUT8(ss, REG_DATA, val)
+
+#define CHECK_STATE(ss, nm) \
+ ((GET32(ss, REG_PRS) & PRS_ ## nm) != 0)
+
+int
+_init(void)
+{
+ int rv;
+
+ sda_host_init_ops(&sdhost_dev_ops);
+
+ if ((rv = mod_install(&modlinkage)) != 0) {
+ sda_host_fini_ops(&sdhost_dev_ops);
+ }
+
+ return (rv);
+}
+
+int
+_fini(void)
+{
+ int rv;
+
+ if ((rv = mod_remove(&modlinkage)) == 0) {
+ sda_host_fini_ops(&sdhost_dev_ops);
+ }
+ return (rv);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+int
+sdhost_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ sdhost_t *shp;
+ ddi_acc_handle_t pcih;
+ uint8_t slotinfo;
+ uint8_t bar;
+ int i;
+
+ switch (cmd) {
+ case DDI_ATTACH:
+ break;
+
+ case DDI_RESUME:
+ return (sdhost_resume(dip));
+
+ default:
+ return (DDI_FAILURE);
+ }
+
+ /*
+ * Soft state allocation.
+ */
+ shp = kmem_zalloc(sizeof (*shp), KM_SLEEP);
+ ddi_set_driver_private(dip, shp);
+
+ /*
+ * Initialize DMA attributes. For now we initialize as for
+ * SDMA. If we add ADMA support we can improve this.
+ */
+ shp->sh_dmaattr.dma_attr_version = DMA_ATTR_V0;
+ shp->sh_dmaattr.dma_attr_addr_lo = 0;
+ shp->sh_dmaattr.dma_attr_addr_hi = 0xffffffffU;
+ shp->sh_dmaattr.dma_attr_count_max = 0xffffffffU;
+ shp->sh_dmaattr.dma_attr_align = 1;
+ shp->sh_dmaattr.dma_attr_burstsizes = 0; /* for now! */
+ shp->sh_dmaattr.dma_attr_minxfer = 1;
+ shp->sh_dmaattr.dma_attr_maxxfer = 0xffffffffU;
+ shp->sh_dmaattr.dma_attr_sgllen = -1; /* unlimited! */
+ shp->sh_dmaattr.dma_attr_seg = 0xfff; /* 4K segments */
+ shp->sh_dmaattr.dma_attr_granular = 1;
+ shp->sh_dmaattr.dma_attr_flags = 0;
+
+ /*
+ * PCI configuration access to figure out number of slots present.
+ */
+ if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "pci_config_setup failed");
+ goto failed;
+ }
+
+ slotinfo = pci_config_get8(pcih, SLOTINFO);
+ shp->sh_numslots = SLOTINFO_NSLOT(slotinfo);
+
+ if (shp->sh_numslots > SDHOST_MAXSLOTS) {
+ cmn_err(CE_WARN, "Host reports to have too many slots: %d",
+ shp->sh_numslots);
+ goto failed;
+ }
+
+ /*
+ * Enable master accesses and DMA.
+ */
+ pci_config_put16(pcih, PCI_CONF_COMM,
+ pci_config_get16(pcih, PCI_CONF_COMM) |
+ PCI_COMM_MAE | PCI_COMM_ME);
+
+ /*
+ * Figure out which BAR to use. Note that we number BARs from
+ * 1, although PCI and SD Host numbers from 0. (We number
+ * from 1, because register number 0 means PCI configuration
+ * space in Solaris.)
+ */
+ bar = SLOTINFO_BAR(slotinfo) + 1;
+
+ pci_config_teardown(&pcih);
+
+ /*
+ * Setup interrupts ... supports the new DDI interrupt API. This
+ * will support MSI or MSI-X interrupts if a device is found to
+ * support it.
+ */
+ if (sdhost_setup_intr(dip, shp) != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "Failed to setup interrupts");
+ goto failed;
+ }
+
+ shp->sh_host = sda_host_alloc(dip, shp->sh_numslots, &sdhost_ops,
+ &shp->sh_dmaattr);
+ if (shp->sh_host == NULL) {
+ cmn_err(CE_WARN, "Failed allocating SD host structure");
+ goto failed;
+ }
+
+ /*
+ * Configure slots, this also maps registers, enables
+ * interrupts, etc. Most of the hardware setup is done here.
+ */
+ for (i = 0; i < shp->sh_numslots; i++) {
+ if (sdhost_init_slot(dip, shp, i, bar + i) != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "Failed initializing slot %d", i);
+ goto failed;
+ }
+ }
+
+ ddi_report_dev(dip);
+
+ /*
+ * Enable device interrupts at the DDI layer.
+ */
+ (void) ddi_intr_enable(shp->sh_ihandle);
+
+ /*
+ * Mark the slots online with the framework. This will cause
+ * the framework to probe them for the presence of cards.
+ */
+ if (sda_host_attach(shp->sh_host) != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "Failed attaching to SDA framework");
+ (void) ddi_intr_disable(shp->sh_ihandle);
+ goto failed;
+ }
+
+ return (DDI_SUCCESS);
+
+failed:
+ if (shp->sh_ihandle != NULL) {
+ (void) ddi_intr_remove_handler(shp->sh_ihandle);
+ (void) ddi_intr_free(shp->sh_ihandle);
+ }
+ for (i = 0; i < shp->sh_numslots; i++)
+ sdhost_uninit_slot(shp, i);
+ kmem_free(shp, sizeof (*shp));
+
+ return (DDI_FAILURE);
+}
+
+int
+sdhost_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ sdhost_t *shp;
+ int i;
+
+ switch (cmd) {
+ case DDI_DETACH:
+ break;
+
+ case DDI_SUSPEND:
+ return (sdhost_suspend(dip));
+
+ default:
+ return (DDI_FAILURE);
+ }
+
+ shp = ddi_get_driver_private(dip);
+ if (shp == NULL)
+ return (DDI_FAILURE);
+
+ /*
+ * Take host offline with the framework.
+ */
+ sda_host_detach(shp->sh_host);
+
+ /*
+ * Tear down interrupts.
+ */
+ if (shp->sh_ihandle != NULL) {
+ (void) ddi_intr_disable(shp->sh_ihandle);
+ (void) ddi_intr_remove_handler(shp->sh_ihandle);
+ (void) ddi_intr_free(shp->sh_ihandle);
+ }
+
+ /*
+ * Tear down register mappings, etc.
+ */
+ for (i = 0; i < shp->sh_numslots; i++)
+ sdhost_uninit_slot(shp, i);
+ kmem_free(shp, sizeof (*shp));
+
+ return (DDI_SUCCESS);
+}
+
+int
+sdhost_suspend(dev_info_t *dip)
+{
+ sdhost_t *shp;
+ sdslot_t *ss;
+ int i;
+
+ shp = ddi_get_driver_private(dip);
+ if (shp == NULL)
+ return (DDI_FAILURE);
+
+ /* disable the interrupts */
+ (void) ddi_intr_disable(shp->sh_ihandle);
+
+ for (i = 0; i < shp->sh_numslots; i++) {
+ ss = &shp->sh_slots[i];
+ mutex_enter(&ss->ss_lock);
+ ss->ss_suspended = B_TRUE;
+ sdhost_disable_interrupts(ss);
+ (void) sdhost_soft_reset(ss, SOFT_RESET_ALL);
+ mutex_exit(&ss->ss_lock);
+ }
+ return (DDI_SUCCESS);
+}
+
+int
+sdhost_resume(dev_info_t *dip)
+{
+ sdhost_t *shp;
+ sdslot_t *ss;
+ int i;
+
+ shp = ddi_get_driver_private(dip);
+ if (shp == NULL)
+ return (DDI_FAILURE);
+
+ for (i = 0; i < shp->sh_numslots; i++) {
+ ss = &shp->sh_slots[i];
+ mutex_enter(&ss->ss_lock);
+ ss->ss_suspended = B_FALSE;
+ (void) sdhost_soft_reset(ss, SOFT_RESET_ALL);
+ sdhost_enable_interrupts(ss);
+ mutex_exit(&ss->ss_lock);
+ }
+
+ /* re-enable the interrupts */
+ (void) ddi_intr_enable(shp->sh_ihandle);
+
+ /* kick off a new card detect task */
+ for (i = 0; i < shp->sh_numslots; i++) {
+ ss = &shp->sh_slots[i];
+ sda_host_detect(ss->ss_host, ss->ss_num);
+ }
+ return (DDI_SUCCESS);
+}
+
+sda_err_t
+sdhost_set_clock(sdslot_t *ss, uint32_t hz)
+{
+ uint16_t div;
+ uint32_t val;
+ uint32_t clk;
+ int count;
+
+ /*
+ * Shut off the clock to begin.
+ */
+ ss->ss_cardclk = 0;
+ PUT16(ss, REG_CLOCK_CONTROL, 0);
+ if (hz == 0) {
+ return (SDA_EOK);
+ }
+
+ if (ss->ss_baseclk == 0) {
+ sda_host_log(ss->ss_host, ss->ss_num,
+ "Base clock frequency not established.");
+ return (SDA_EINVAL);
+ }
+
+ if ((hz > 25000000) && ((ss->ss_capab & CAPAB_HIGH_SPEED) != 0)) {
+ /* this clock requires high speed timings! */
+ SET8(ss, REG_HOST_CONTROL, HOST_CONTROL_HIGH_SPEED_EN);
+ } else {
+ /* don't allow clock to run faster than 25MHz */
+ hz = min(hz, 25000000);
+ CLR8(ss, REG_HOST_CONTROL, HOST_CONTROL_HIGH_SPEED_EN);
+ }
+
+ /* figure out the divider */
+ clk = ss->ss_baseclk;
+ div = 1;
+ while (clk > hz) {
+ if (div > 0x80)
+ break;
+ clk >>= 1; /* divide clock by two */
+ div <<= 1; /* divider goes up by one */
+ }
+ div >>= 1; /* 0 == divide by 1, 1 = divide by 2 */
+
+ /*
+ * Set the internal clock divider first, without enabling the
+ * card clock yet.
+ */
+ PUT16(ss, REG_CLOCK_CONTROL,
+ (div << CLOCK_CONTROL_FREQ_SHIFT) | CLOCK_CONTROL_INT_CLOCK_EN);
+
+ /*
+ * Wait up to 100 msec for the internal clock to stabilize.
+ * (The spec does not seem to indicate a maximum timeout, but
+ * it also suggests that an infinite loop be used, which is
+ * not appropriate for hardened Solaris drivers.)
+ */
+ for (count = 100000; count; count -= 10) {
+
+ val = GET16(ss, REG_CLOCK_CONTROL);
+
+ if (val & CLOCK_CONTROL_INT_CLOCK_STABLE) {
+ /* if clock is stable, enable the SD clock pin */
+ PUT16(ss, REG_CLOCK_CONTROL, val |
+ CLOCK_CONTROL_SD_CLOCK_EN);
+
+ ss->ss_cardclk = clk;
+ return (SDA_EOK);
+ }
+
+ drv_usecwait(10);
+ }
+
+ return (SDA_ETIME);
+}
+
+sda_err_t
+sdhost_soft_reset(sdslot_t *ss, uint8_t bits)
+{
+ int count;
+
+ /*
+ * There appears to be a bug where Ricoh hosts might have a
+ * problem if the host frequency is not set. If the card
+ * isn't present, or we are doing a master reset, just enable
+ * the internal clock at its native speed. (No dividers, and
+ * not exposed to card.).
+ */
+ if ((bits == SOFT_RESET_ALL) || !(CHECK_STATE(ss, CARD_INSERTED))) {
+ PUT16(ss, REG_CLOCK_CONTROL, CLOCK_CONTROL_INT_CLOCK_EN);
+ /* simple 1msec wait, don't wait for clock to stabilize */
+ drv_usecwait(1000);
+ }
+
+ PUT8(ss, REG_SOFT_RESET, bits);
+ for (count = 100000; count != 0; count -= 10) {
+ if ((GET8(ss, REG_SOFT_RESET) & bits) == 0) {
+ return (SDA_EOK);
+ }
+ drv_usecwait(10);
+ }
+
+ return (SDA_ETIME);
+}
+
+void
+sdhost_disable_interrupts(sdslot_t *ss)
+{
+ /* disable slot interrupts for card insert and remove */
+ PUT16(ss, REG_INT_MASK, 0);
+ PUT16(ss, REG_INT_EN, 0);
+
+ /* disable error interrupts */
+ PUT16(ss, REG_ERR_MASK, 0);
+ PUT16(ss, REG_ERR_EN, 0);
+}
+
+void
+sdhost_enable_interrupts(sdslot_t *ss)
+{
+ /*
+ * Note that we want to enable reading of the CMD related
+ * bits, but we do not want them to generate an interrupt.
+ * (The busy wait for typical CMD stuff will normally be less
+ * than 10usec, so its simpler/easier to just poll. Even in
+ * the worst case of 100 kHz, the poll is at worst 2 msec.)
+ */
+
+ /* enable slot interrupts for card insert and remove */
+ PUT16(ss, REG_INT_MASK, INT_MASK);
+ PUT16(ss, REG_INT_EN, INT_ENAB);
+
+ /* enable error interrupts */
+ PUT16(ss, REG_ERR_MASK, ERR_MASK);
+ PUT16(ss, REG_ERR_EN, ERR_ENAB);
+}
+
+int
+sdhost_setup_intr(dev_info_t *dip, sdhost_t *shp)
+{
+ int itypes;
+ int itype;
+
+ /*
+ * Set up interrupt handler.
+ */
+ if (ddi_intr_get_supported_types(dip, &itypes) != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "ddi_intr_get_supported_types failed");
+ return (DDI_FAILURE);
+ }
+
+ /*
+ * Interrupt types are bits in a mask. We know about these ones:
+ * FIXED = 1
+ * MSI = 2
+ * MSIX = 4
+ */
+ for (itype = DDI_INTR_TYPE_MSIX; itype != 0; itype >>= 1) {
+
+ int count;
+
+ if ((itypes & itype) == 0) {
+ /* this type is not supported on this device! */
+ continue;
+ }
+
+ if ((ddi_intr_get_nintrs(dip, itype, &count) != DDI_SUCCESS) ||
+ (count == 0)) {
+ cmn_err(CE_WARN, "ddi_intr_get_nintrs failed");
+ continue;
+ }
+
+ /*
+ * We have not seen a host device with multiple
+ * interrupts (one per slot?), and the spec does not
+ * indicate that they exist. But if one ever occurs,
+ * we spew a warning to help future debugging/support
+ * efforts.
+ */
+ if (count > 1) {
+ cmn_err(CE_WARN, "Controller offers %d interrupts, "
+ "but driver only supports one", count);
+ continue;
+ }
+
+ if ((ddi_intr_alloc(dip, &shp->sh_ihandle, itype, 0, 1,
+ &count, DDI_INTR_ALLOC_NORMAL) != DDI_SUCCESS) ||
+ (count != 1)) {
+ cmn_err(CE_WARN, "ddi_intr_alloc failed");
+ continue;
+ }
+
+ if (ddi_intr_get_pri(shp->sh_ihandle, &shp->sh_ipri) !=
+ DDI_SUCCESS) {
+ cmn_err(CE_WARN, "ddi_intr_get_pri failed");
+ (void) ddi_intr_free(shp->sh_ihandle);
+ shp->sh_ihandle = NULL;
+ continue;
+ }
+
+ if (shp->sh_ipri >= ddi_intr_get_hilevel_pri()) {
+ cmn_err(CE_WARN, "Hi level interrupt not supported");
+ (void) ddi_intr_free(shp->sh_ihandle);
+ shp->sh_ihandle = NULL;
+ continue;
+ }
+
+ if (ddi_intr_get_cap(shp->sh_ihandle, &shp->sh_icap) !=
+ DDI_SUCCESS) {
+ cmn_err(CE_WARN, "ddi_intr_get_cap failed");
+ (void) ddi_intr_free(shp->sh_ihandle);
+ shp->sh_ihandle = NULL;
+ continue;
+ }
+
+ if (ddi_intr_add_handler(shp->sh_ihandle, sdhost_intr,
+ shp, NULL) != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "ddi_intr_add_handler failed");
+ (void) ddi_intr_free(shp->sh_ihandle);
+ shp->sh_ihandle = NULL;
+ continue;
+ }
+
+ return (DDI_SUCCESS);
+ }
+
+ return (DDI_FAILURE);
+}
+
+void
+sdhost_xfer_done(sdslot_t *ss, sda_err_t errno)
+{
+ if ((errno == SDA_EOK) && (ss->ss_resid != 0)) {
+ /* an unexpected partial transfer was found */
+ errno = SDA_ERESID;
+ }
+ ss->ss_blksz = 0;
+ ss->ss_resid = 0;
+
+ if (errno != SDA_EOK) {
+ (void) sdhost_soft_reset(ss, SOFT_RESET_CMD);
+ (void) sdhost_soft_reset(ss, SOFT_RESET_DAT);
+
+ /* send a STOP command if necessary */
+ if (ss->ss_mode & XFR_MODE_AUTO_CMD12) {
+ PUT32(ss, REG_ARGUMENT, 0);
+ PUT16(ss, REG_COMMAND,
+ (CMD_STOP_TRANSMIT << 8) |
+ COMMAND_TYPE_NORM | COMMAND_INDEX_CHECK_EN |
+ COMMAND_CRC_CHECK_EN | COMMAND_RESP_48_BUSY);
+ }
+ }
+
+ sda_host_transfer(ss->ss_host, ss->ss_num, errno);
+}
+
+uint_t
+sdhost_slot_intr(sdslot_t *ss)
+{
+ uint16_t intr;
+ uint16_t errs;
+ uint8_t *data;
+ int count;
+
+ mutex_enter(&ss->ss_lock);
+
+ if (ss->ss_suspended) {
+ mutex_exit(&ss->ss_lock);
+ return (DDI_INTR_UNCLAIMED);
+ }
+
+ intr = GET16(ss, REG_INT_STAT);
+ if (intr == 0) {
+ mutex_exit(&ss->ss_lock);
+ return (DDI_INTR_UNCLAIMED);
+ }
+ errs = GET16(ss, REG_ERR_STAT);
+
+ if (intr & (INT_REM | INT_INS)) {
+
+ PUT16(ss, REG_INT_STAT, intr);
+ mutex_exit(&ss->ss_lock);
+
+ sda_host_detect(ss->ss_host, ss->ss_num);
+ /* no further interrupt processing this cycle */
+ return (DDI_INTR_CLAIMED);
+ }
+
+ if (intr & INT_DMA) {
+ /*
+ * We have crossed a DMA/page boundary. Cope with it.
+ */
+ if (ss->ss_ndmac) {
+ ss->ss_ndmac--;
+ ss->ss_dmacs++;
+ PUT16(ss, REG_INT_STAT, INT_DMA);
+ PUT32(ss, REG_SDMA_ADDR, ss->ss_dmacs->dmac_address);
+
+ } else {
+ /*
+ * Apparently some sdhost controllers issue a
+ * final DMA interrupt if the DMA completes on
+ * a boundary, even though there is no further
+ * data to transfer.
+ *
+ * There might be a risk here of the
+ * controller continuing to access the same
+ * data over and over again, but we accept the
+ * risk.
+ */
+ PUT16(ss, REG_INT_STAT, INT_DMA);
+ }
+ }
+
+ if (intr & INT_RD) {
+ /*
+ * PIO read! PIO is quite suboptimal, but we expect
+ * performance critical applications to use DMA
+ * whenever possible. We have to stage this through
+ * the bounce buffer to meet alignment considerations.
+ */
+
+ PUT16(ss, REG_INT_STAT, INT_RD);
+
+ while ((ss->ss_resid > 0) && CHECK_STATE(ss, BUF_RD_EN)) {
+
+ data = (void *)ss->ss_bounce;
+ count = ss->ss_blksz;
+
+ ASSERT(count > 0);
+ ASSERT(ss->ss_kvaddr != NULL);
+
+ while (count >= sizeof (uint32_t)) {
+ *(uint32_t *)(void *)data = GETDATA32(ss);
+ data += sizeof (uint32_t);
+ count -= sizeof (uint32_t);
+ }
+ while (count >= sizeof (uint16_t)) {
+ *(uint16_t *)(void *)data = GETDATA16(ss);
+ data += sizeof (uint16_t);
+ count -= sizeof (uint16_t);
+ }
+ while (count >= sizeof (uint8_t)) {
+ *(uint8_t *)data = GETDATA8(ss);
+ data += sizeof (uint8_t);
+ count -= sizeof (uint8_t);
+ }
+
+ bcopy(ss->ss_bounce, ss->ss_kvaddr, ss->ss_blksz);
+ ss->ss_kvaddr += ss->ss_blksz;
+ ss->ss_resid--;
+ }
+ }
+
+ if (intr & INT_WR) {
+ /*
+ * PIO write! PIO is quite suboptimal, but we expect
+ * performance critical applications to use DMA
+ * whenever possible. We have to stage this trhough
+ * the bounce buffer to meet alignment considerations.
+ */
+
+ PUT16(ss, REG_INT_STAT, INT_WR);
+
+ while ((ss->ss_resid > 0) && CHECK_STATE(ss, BUF_WR_EN)) {
+
+ data = (void *)ss->ss_bounce;
+ count = ss->ss_blksz;
+
+ ASSERT(count > 0);
+ ASSERT(ss->ss_kvaddr != NULL);
+
+ bcopy(ss->ss_kvaddr, data, count);
+ while (count >= sizeof (uint32_t)) {
+ PUTDATA32(ss, *(uint32_t *)(void *)data);
+ data += sizeof (uint32_t);
+ count -= sizeof (uint32_t);
+ }
+ while (count >= sizeof (uint16_t)) {
+ PUTDATA16(ss, *(uint16_t *)(void *)data);
+ data += sizeof (uint16_t);
+ count -= sizeof (uint16_t);
+ }
+ while (count >= sizeof (uint8_t)) {
+ PUTDATA8(ss, *(uint8_t *)data);
+ data += sizeof (uint8_t);
+ count -= sizeof (uint8_t);
+ }
+
+ ss->ss_kvaddr += ss->ss_blksz;
+ ss->ss_resid--;
+ }
+ }
+
+ if (intr & INT_XFR) {
+ PUT16(ss, REG_INT_STAT, INT_XFR);
+
+ sdhost_xfer_done(ss, SDA_EOK);
+ }
+
+ if (intr & INT_ERR) {
+ PUT16(ss, REG_ERR_STAT, errs);
+ PUT16(ss, REG_INT_STAT, INT_ERR);
+
+ if (errs & ERR_DAT) {
+ if ((errs & ERR_DAT_END) == ERR_DAT_END) {
+ sdhost_xfer_done(ss, SDA_EPROTO);
+ } else if ((errs & ERR_DAT_CRC) == ERR_DAT_CRC) {
+ sdhost_xfer_done(ss, SDA_ECRC7);
+ } else {
+ sdhost_xfer_done(ss, SDA_ETIME);
+ }
+
+ } else if (errs & ERR_ACMD12) {
+ /*
+ * Generally, this is bad news. we need a full
+ * reset to recover properly.
+ */
+ sdhost_xfer_done(ss, SDA_ECMD12);
+ }
+
+ /*
+ * This asynchronous error leaves the slot more or less
+ * useless. Report it to the framework.
+ */
+ if (errs & ERR_CURRENT) {
+ sda_host_fault(ss->ss_host, ss->ss_num,
+ SDA_FAULT_CURRENT);
+ }
+ }
+
+ mutex_exit(&ss->ss_lock);
+
+ return (DDI_INTR_CLAIMED);
+}
+
+/*ARGSUSED1*/
+uint_t
+sdhost_intr(caddr_t arg1, caddr_t arg2)
+{
+ sdhost_t *shp = (void *)arg1;
+ int rv = DDI_INTR_UNCLAIMED;
+ int num;
+
+ /* interrupt for each of the slots present in the system */
+ for (num = 0; num < shp->sh_numslots; num++) {
+ if (sdhost_slot_intr(&shp->sh_slots[num]) ==
+ DDI_INTR_CLAIMED) {
+ rv = DDI_INTR_CLAIMED;
+ }
+ }
+ return (rv);
+}
+
+int
+sdhost_init_slot(dev_info_t *dip, sdhost_t *shp, int num, int bar)
+{
+ sdslot_t *ss;
+ uint32_t capab;
+ uint32_t clk;
+
+ /*
+ * Register the private state.
+ */
+ ss = &shp->sh_slots[num];
+ ss->ss_host = shp->sh_host;
+ ss->ss_num = num;
+ sda_host_set_private(shp->sh_host, num, ss);
+
+ /*
+ * Initialize core data structure, locks, etc.
+ */
+ mutex_init(&ss->ss_lock, NULL, MUTEX_DRIVER,
+ DDI_INTR_PRI(shp->sh_ipri));
+
+ if (ddi_regs_map_setup(dip, bar, &ss->ss_regva, 0, 0, &sdhost_regattr,
+ &ss->ss_acch) != DDI_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+
+ /* reset before reading capabilities */
+ if (sdhost_soft_reset(ss, SOFT_RESET_ALL) != SDA_EOK)
+ return (DDI_FAILURE);
+
+ capab = GET64(ss, REG_CAPAB) & 0xffffffffU; /* upper bits reserved */
+ ss->ss_capab = capab;
+
+ /* host voltages in OCR format */
+ ss->ss_ocr = 0;
+ if (capab & CAPAB_18V)
+ ss->ss_ocr |= OCR_18_19V; /* 1.8V */
+ if (capab & CAPAB_30V)
+ ss->ss_ocr |= OCR_30_31V;
+ if (capab & CAPAB_33V)
+ ss->ss_ocr |= OCR_32_33V;
+
+ /* base clock */
+ ss->ss_baseclk =
+ ((capab & CAPAB_BASE_FREQ_MASK) >> CAPAB_BASE_FREQ_SHIFT);
+ ss->ss_baseclk *= 1000000;
+
+ /*
+ * Timeout clock. We can calculate this using the following
+ * formula:
+ *
+ * (1000000 usec/1sec) * (1sec/tmoutclk) * base factor = clock time
+ *
+ * Clock time is the length of the base clock in usecs.
+ *
+ * Our base factor is 2^13, which is the shortest clock we
+ * can count.
+ *
+ * To simplify the math and avoid overflow, we cancel out the
+ * zeros for kHz or MHz. Since we want to wait more clocks, not
+ * less, on error, we truncate the result rather than rounding
+ * up.
+ */
+ clk = ((capab & CAPAB_TIMEOUT_FREQ_MASK) >> CAPAB_TIMEOUT_FREQ_SHIFT);
+ if ((ss->ss_baseclk == 0) || (clk == 0)) {
+ cmn_err(CE_WARN, "Unable to determine clock frequencies");
+ return (DDI_FAILURE);
+ }
+
+ if (capab & CAPAB_TIMEOUT_UNITS) {
+ /* MHz */
+ ss->ss_tmusecs = (1 << 13) / clk;
+ clk *= 1000000;
+ } else {
+ /* kHz */
+ ss->ss_tmusecs = (1000 * (1 << 13)) / clk;
+ clk *= 1000;
+ }
+
+ /*
+ * Calculation of the timeout.
+ *
+ * SDIO cards use a 1sec timeout, and SDHC cards use fixed
+ * 100msec for read and 250 msec for write.
+ *
+ * Legacy cards running at 375kHz have a worst case of about
+ * 15 seconds. Running at 25MHz (the standard speed) it is
+ * about 100msec for read, and about 3.2 sec for write.
+ * Typical values are 1/100th that, or about 1msec for read,
+ * and 32 msec for write.
+ *
+ * No transaction at full speed should ever take more than 4
+ * seconds. (Some slow legacy cards might have trouble, but
+ * we'll worry about them if they ever are seen. Nobody wants
+ * to wait 4 seconds to access a single block anyway!)
+ *
+ * To get to 4 seconds, we continuously double usec until we
+ * get to the maximum value, or a timeout greater than 4
+ * seconds.
+ *
+ * Note that for high-speed timeout clocks, we might not be
+ * able to get to the full 4 seconds. E.g. with a 48MHz
+ * timeout clock, we can only get to about 2.8 seconds. Its
+ * possible that there could be some slow MMC cards that will
+ * timeout at this clock rate, but it seems unlikely. (The
+ * device would have to be pressing the very worst times,
+ * against the 100-fold "permissive" window allowed, and
+ * running at only 12.5MHz.)
+ *
+ * XXX: this could easily be a tunable. Someone dealing with only
+ * reasonable cards could set this to just 1 second.
+ */
+ for (ss->ss_tmoutclk = 0; ss->ss_tmoutclk < 14; ss->ss_tmoutclk++) {
+ if ((ss->ss_tmusecs * (1 << ss->ss_tmoutclk)) >= 4000000) {
+ break;
+ }
+ }
+
+ /*
+ * Enable slot interrupts.
+ */
+ sdhost_enable_interrupts(ss);
+
+ return (DDI_SUCCESS);
+}
+
+void
+sdhost_uninit_slot(sdhost_t *shp, int num)
+{
+ sdslot_t *ss;
+
+ ss = &shp->sh_slots[num];
+ if (ss->ss_acch == NULL)
+ return;
+
+ (void) sdhost_soft_reset(ss, SOFT_RESET_ALL);
+
+ ddi_regs_map_free(&ss->ss_acch);
+ mutex_destroy(&ss->ss_lock);
+}
+
+void
+sdhost_get_response(sdslot_t *ss, sda_cmd_t *cmdp)
+{
+ uint32_t *resp = cmdp->sc_response;
+ int i;
+
+ resp[0] = GET32(ss, REG_RESP1);
+ resp[1] = GET32(ss, REG_RESP2);
+ resp[2] = GET32(ss, REG_RESP3);
+ resp[3] = GET32(ss, REG_RESP4);
+
+ /*
+ * Response 2 is goofy because the host drops the low
+ * order CRC bits. This makes it a bit awkward, so we
+ * have to shift the bits to make it work out right.
+ *
+ * Note that the framework expects the 32 bit
+ * words to be ordered in LE fashion. (The
+ * bits within the words are in native order).
+ */
+ if (cmdp->sc_rtype == R2) {
+ for (i = 3; i > 0; i--) {
+ resp[i] <<= 8;
+ resp[i] |= (resp[i - 1] >> 24);
+ }
+ resp[0] <<= 8;
+ }
+}
+
+sda_err_t
+sdhost_wait_cmd(sdslot_t *ss, sda_cmd_t *cmdp)
+{
+ int i;
+ uint16_t errs;
+
+ /*
+ * Worst case for 100kHz timeout is 2msec (200 clocks), we add
+ * a tiny bit for safety. (Generally timeout will be far, far
+ * less than that.)
+ *
+ * Note that at more typical 12MHz (and normally it will be
+ * even faster than that!) that the device timeout is only
+ * 16.67 usec. We could be smarter and reduce the delay time,
+ * but that would require putting more intelligence into the
+ * code, and we don't expect CMD timeout to normally occur
+ * except during initialization. (At which time we need the
+ * full timeout anyway.)
+ *
+ * Checking the ERR_STAT will normally cause the timeout to
+ * terminate to finish early if the device is healthy, anyway.
+ */
+
+ for (i = 3000; i > 0; i -= 5) {
+ if (GET16(ss, REG_INT_STAT) & INT_CMD) {
+
+ PUT16(ss, REG_INT_STAT, INT_CMD);
+
+ /* command completed */
+ sdhost_get_response(ss, cmdp);
+ return (SDA_EOK);
+ }
+
+ if ((errs = (GET16(ss, REG_ERR_STAT) & ERR_CMD)) != 0) {
+ PUT16(ss, REG_ERR_STAT, errs);
+
+ /* command timeout isn't a host failure */
+ if ((errs & ERR_CMD_TMO) == ERR_CMD_TMO) {
+ return (SDA_ETIME);
+ }
+
+ if ((errs & ERR_CMD_CRC) == ERR_CMD_CRC) {
+ return (SDA_ECRC7);
+ } else {
+ return (SDA_EPROTO);
+ }
+ }
+
+ drv_usecwait(5);
+ }
+
+ return (SDA_ETIME);
+}
+
+sda_err_t
+sdhost_poll(void *arg)
+{
+ sdslot_t *ss = arg;
+
+ (void) sdhost_slot_intr(ss);
+ return (SDA_EOK);
+}
+
+sda_err_t
+sdhost_cmd(void *arg, sda_cmd_t *cmdp)
+{
+ sdslot_t *ss = arg;
+ uint16_t command;
+ uint16_t mode;
+ sda_err_t rv;
+
+ /*
+ * Command register:
+ * bit 13-8 = command index
+ * bit 7-6 = command type (always zero for us!)
+ * bit 5 = data present select
+ * bit 4 = command index check (always on!)
+ * bit 3 = command CRC check enable
+ * bit 2 = reserved
+ * bit 1-0 = response type
+ */
+
+ command = ((uint16_t)cmdp->sc_index << 8);
+ command |= COMMAND_TYPE_NORM |
+ COMMAND_INDEX_CHECK_EN | COMMAND_CRC_CHECK_EN;
+
+ switch (cmdp->sc_rtype) {
+ case R0:
+ command |= COMMAND_RESP_NONE;
+ break;
+ case R1:
+ case R5:
+ case R6:
+ case R7:
+ command |= COMMAND_RESP_48;
+ break;
+ case R1b:
+ case R5b:
+ command |= COMMAND_RESP_48_BUSY;
+ break;
+ case R2:
+ command |= COMMAND_RESP_136;
+ command &= ~(COMMAND_INDEX_CHECK_EN | COMMAND_CRC_CHECK_EN);
+ break;
+ case R3:
+ case R4:
+ command |= COMMAND_RESP_48;
+ command &= ~COMMAND_CRC_CHECK_EN;
+ command &= ~COMMAND_INDEX_CHECK_EN;
+ break;
+ default:
+ return (SDA_EINVAL);
+ }
+
+ mutex_enter(&ss->ss_lock);
+ if (ss->ss_suspended) {
+ mutex_exit(&ss->ss_lock);
+ return (SDA_ESUSPENDED);
+ }
+
+ if (cmdp->sc_nblks != 0) {
+ uint16_t blksz;
+ uint16_t nblks;
+
+ blksz = cmdp->sc_blksz;
+ nblks = cmdp->sc_nblks;
+
+ /*
+ * Ensure that we have good data.
+ */
+ if ((blksz < 1) || (blksz > 2048)) {
+ mutex_exit(&ss->ss_lock);
+ return (SDA_EINVAL);
+ }
+ command |= COMMAND_DATA_PRESENT;
+
+ ss->ss_blksz = blksz;
+
+ /*
+ * Only SDMA for now. We can investigate ADMA2 later.
+ * (Right now we don't have ADMA2 capable hardware.)
+ */
+ if (((ss->ss_capab & CAPAB_SDMA) != 0) &&
+ (cmdp->sc_ndmac != 0)) {
+ ddi_dma_cookie_t *dmacs = cmdp->sc_dmacs;
+
+ ASSERT(dmacs != NULL);
+
+ ss->ss_kvaddr = NULL;
+ ss->ss_resid = 0;
+ ss->ss_dmacs = dmacs;
+ ss->ss_ndmac = cmdp->sc_ndmac - 1;
+
+ PUT32(ss, REG_SDMA_ADDR, dmacs->dmac_address);
+ mode = XFR_MODE_DMA_EN;
+ PUT16(ss, REG_BLKSZ, blksz);
+
+ } else {
+ ss->ss_kvaddr = (void *)cmdp->sc_kvaddr;
+ ss->ss_resid = nblks;
+ ss->ss_dmacs = NULL;
+ ss->ss_ndmac = 0;
+ mode = 0;
+ PUT16(ss, REG_BLKSZ, blksz);
+ }
+
+ if (nblks > 1) {
+ mode |= XFR_MODE_MULTI | XFR_MODE_COUNT;
+ if (cmdp->sc_flags & SDA_CMDF_AUTO_CMD12)
+ mode |= XFR_MODE_AUTO_CMD12;
+ }
+ if ((cmdp->sc_flags & SDA_CMDF_READ) != 0) {
+ mode |= XFR_MODE_READ;
+ }
+
+ ss->ss_mode = mode;
+
+ PUT8(ss, REG_TIMEOUT_CONTROL, ss->ss_tmoutclk);
+ PUT16(ss, REG_BLOCK_COUNT, nblks);
+ PUT16(ss, REG_XFR_MODE, mode);
+ }
+
+ PUT32(ss, REG_ARGUMENT, cmdp->sc_argument);
+ PUT16(ss, REG_COMMAND, command);
+
+ rv = sdhost_wait_cmd(ss, cmdp);
+
+ mutex_exit(&ss->ss_lock);
+
+ return (rv);
+}
+
+sda_err_t
+sdhost_getprop(void *arg, sda_prop_t prop, uint32_t *val)
+{
+ sdslot_t *ss = arg;
+ sda_err_t rv = 0;
+
+ mutex_enter(&ss->ss_lock);
+
+ if (ss->ss_suspended) {
+ mutex_exit(&ss->ss_lock);
+ return (SDA_ESUSPENDED);
+ }
+
+ switch (prop) {
+ case SDA_PROP_INSERTED:
+ if (CHECK_STATE(ss, CARD_INSERTED)) {
+ *val = B_TRUE;
+ } else {
+ *val = B_FALSE;
+ }
+ break;
+
+ case SDA_PROP_WPROTECT:
+ if (CHECK_STATE(ss, WRITE_ENABLE)) {
+ *val = B_FALSE;
+ } else {
+ *val = B_TRUE;
+ }
+ break;
+
+ case SDA_PROP_OCR:
+ *val = ss->ss_ocr;
+ break;
+
+ case SDA_PROP_CLOCK:
+ *val = ss->ss_cardclk;
+ break;
+
+ case SDA_PROP_CAP_HISPEED:
+ if ((ss->ss_capab & CAPAB_HIGH_SPEED) != 0) {
+ *val = B_TRUE;
+ } else {
+ *val = B_FALSE;
+ }
+ break;
+
+ case SDA_PROP_CAP_4BITS:
+ *val = B_TRUE;
+ break;
+
+ case SDA_PROP_CAP_NOPIO:
+ if ((ss->ss_capab & CAPAB_SDMA) != 0) {
+ *val = B_TRUE;
+ } else {
+ *val = B_FALSE;
+ }
+ break;
+
+ case SDA_PROP_CAP_INTR:
+ case SDA_PROP_CAP_8BITS:
+ *val = B_FALSE;
+ break;
+
+ default:
+ rv = SDA_ENOTSUP;
+ break;
+ }
+ mutex_exit(&ss->ss_lock);
+
+ return (rv);
+}
+
+sda_err_t
+sdhost_setprop(void *arg, sda_prop_t prop, uint32_t val)
+{
+ sdslot_t *ss = arg;
+ sda_err_t rv = SDA_EOK;
+
+ mutex_enter(&ss->ss_lock);
+
+ if (ss->ss_suspended) {
+ mutex_exit(&ss->ss_lock);
+ return (SDA_ESUSPENDED);
+ }
+
+ switch (prop) {
+ case SDA_PROP_LED:
+ if (val) {
+ SET8(ss, REG_HOST_CONTROL, HOST_CONTROL_LED_ON);
+ } else {
+ CLR8(ss, REG_HOST_CONTROL, HOST_CONTROL_LED_ON);
+ }
+ break;
+
+ case SDA_PROP_CLOCK:
+ rv = sdhost_set_clock(arg, val);
+ break;
+
+ case SDA_PROP_BUSWIDTH:
+ switch (val) {
+ case 1:
+ CLR8(ss, REG_HOST_CONTROL, HOST_CONTROL_DATA_WIDTH);
+ break;
+ case 4:
+ SET8(ss, REG_HOST_CONTROL, HOST_CONTROL_DATA_WIDTH);
+ break;
+ default:
+ rv = SDA_EINVAL;
+ }
+ break;
+
+ case SDA_PROP_OCR:
+ val &= ss->ss_ocr;
+
+ if (val & OCR_17_18V) {
+ PUT8(ss, REG_POWER_CONTROL, POWER_CONTROL_18V);
+ PUT8(ss, REG_POWER_CONTROL, POWER_CONTROL_18V |
+ POWER_CONTROL_BUS_POWER);
+ } else if (val & OCR_29_30V) {
+ PUT8(ss, REG_POWER_CONTROL, POWER_CONTROL_30V);
+ PUT8(ss, REG_POWER_CONTROL, POWER_CONTROL_30V |
+ POWER_CONTROL_BUS_POWER);
+ } else if (val & OCR_32_33V) {
+ PUT8(ss, REG_POWER_CONTROL, POWER_CONTROL_33V);
+ PUT8(ss, REG_POWER_CONTROL, POWER_CONTROL_33V |
+ POWER_CONTROL_BUS_POWER);
+ } else if (val == 0) {
+ /* turn off power */
+ PUT8(ss, REG_POWER_CONTROL, 0);
+ } else {
+ rv = SDA_EINVAL;
+ }
+ break;
+
+ case SDA_PROP_HISPEED:
+ if (val) {
+ SET8(ss, REG_HOST_CONTROL, HOST_CONTROL_HIGH_SPEED_EN);
+ } else {
+ CLR8(ss, REG_HOST_CONTROL, HOST_CONTROL_HIGH_SPEED_EN);
+ }
+ /* give clocks time to settle */
+ drv_usecwait(10);
+ break;
+
+ default:
+ rv = SDA_ENOTSUP;
+ break;
+ }
+
+ /*
+ * Apparently some controllers (ENE) have issues with changing
+ * certain parameters (bus width seems to be one), requiring
+ * a reset of the DAT and CMD lines.
+ */
+ if (rv == SDA_EOK) {
+ (void) sdhost_soft_reset(ss, SOFT_RESET_CMD);
+ (void) sdhost_soft_reset(ss, SOFT_RESET_DAT);
+ }
+ mutex_exit(&ss->ss_lock);
+ return (rv);
+}
+
+sda_err_t
+sdhost_reset(void *arg)
+{
+ sdslot_t *ss = arg;
+
+ mutex_enter(&ss->ss_lock);
+ if (!ss->ss_suspended) {
+ if (sdhost_soft_reset(ss, SOFT_RESET_ALL) != SDA_EOK) {
+ mutex_exit(&ss->ss_lock);
+ return (SDA_ETIME);
+ }
+ sdhost_enable_interrupts(ss);
+ }
+ mutex_exit(&ss->ss_lock);
+ return (SDA_EOK);
+}
+
+sda_err_t
+sdhost_halt(void *arg)
+{
+ sdslot_t *ss = arg;
+
+ mutex_enter(&ss->ss_lock);
+ if (!ss->ss_suspended) {
+ sdhost_disable_interrupts(ss);
+ /* this has the side effect of removing power from the card */
+ if (sdhost_soft_reset(ss, SOFT_RESET_ALL) != SDA_EOK) {
+ mutex_exit(&ss->ss_lock);
+ return (SDA_ETIME);
+ }
+ }
+ mutex_exit(&ss->ss_lock);
+ return (SDA_EOK);
+}
diff --git a/usr/src/uts/common/io/sdcard/adapters/sdhost/sdhost.h b/usr/src/uts/common/io/sdcard/adapters/sdhost/sdhost.h
new file mode 100644
index 0000000000..a78c248ea5
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/adapters/sdhost/sdhost.h
@@ -0,0 +1,299 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_SDCARD_SDHOST_H
+#define _SYS_SDCARD_SDHOST_H
+
+/*
+ * The entire contents of this file are private the SD Host driver
+ * implementation.
+ */
+
+#include <sys/types.h>
+#include <sys/ksynch.h>
+#include <sys/param.h>
+#include <sys/kmem.h>
+#include <sys/inttypes.h>
+#include <sys/cmn_err.h>
+#include <sys/conf.h>
+#include <sys/modctl.h>
+#include <sys/sdcard/sda.h>
+#include <sys/pci.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+
+#define BIT(x) (1 << (x))
+
+/*
+ * SD Host Spec says that a controller can support up to 6 different
+ * slots, each with its own register set.
+ */
+#define SDHOST_MAXSLOTS 6
+
+/*
+ * SD Host specific PCI configuration register.
+ */
+#define SLOTINFO 0x40
+#define SLOTINFO_NSLOT_SHFT 4
+#define SLOTINFO_NSLOT_MSK (0x3 << SLOTINFO_NSLOT_SHFT)
+#define SLOTINFO_BAR_SHFT 0
+#define SLOTINFO_BAR_MSK (0x3 << SLOTINFO_BAR_SHFT)
+
+#define SLOTINFO_NSLOT(x) \
+ ((((x) & SLOTINFO_NSLOT_MSK) >> SLOTINFO_NSLOT_SHFT) + 1)
+
+#define SLOTINFO_BAR(x) \
+ (((x) & SLOTINFO_BAR_MSK) >> SLOTINFO_BAR_SHFT)
+
+/*
+ * Slot-specific CSRs
+ */
+#define REG_SDMA_ADDR 0x0000 /* 32 bits */
+#define REG_BLKSZ 0x0004 /* 16 bits */
+#define REG_BLOCK_COUNT 0x0006 /* 16 bits */
+#define REG_ARGUMENT 0x0008 /* 32 bits */
+#define REG_XFR_MODE 0x000C /* 16 bits */
+#define REG_COMMAND 0x000E /* 16 bits */
+#define REG_RESP1 0x0010 /* 32 bits */
+#define REG_RESP2 0x0014 /* 32 bits */
+#define REG_RESP3 0x0018 /* 32 bits */
+#define REG_RESP4 0x001C /* 32 bits */
+#define REG_DATA 0x0020 /* 32 bits */
+#define REG_PRS 0x0024 /* 32 bits */
+#define REG_HOST_CONTROL 0x0028 /* 8 bits */
+#define REG_POWER_CONTROL 0x0029 /* 8 bits */
+#define REG_BLOCK_GAP_CONTROL 0x002A /* 8 bits */
+#define REG_WAKEUP_CONTROL 0x002B /* 8 bits */
+#define REG_CLOCK_CONTROL 0x002C /* 16 bits */
+#define REG_TIMEOUT_CONTROL 0x002E /* 8 bits */
+#define REG_SOFT_RESET 0x002F /* 8 bits */
+#define REG_INT_STAT 0x0030 /* 16 bits */
+#define REG_ERR_STAT 0x0032 /* 16 bits */
+#define REG_INT_EN 0x0034 /* 16 bits */
+#define REG_ERR_EN 0x0036 /* 16 bits */
+#define REG_INT_MASK 0x0038 /* 16 bits */
+#define REG_ERR_MASK 0x003A /* 16 bits */
+#define REG_ACMD12_ERROR 0x003C /* 16 bits */
+#define REG_CAPAB 0x0040 /* 64 bits */
+#define REG_MAX_CURRENT 0x0048 /* 64 bits */
+#define REG_SLOT_INT_STAT 0x00FC /* 16 bits */
+#define REG_VERSION 0x00FE /* 16 bits */
+#define REG_ERR_FORCE 0x0052 /* 16 bits */
+#define REG_ACMD12_ERROR_FORCE 0x0050 /* 16 bits */
+#define REG_ADMA_ERROR 0x0054 /* 8 bits */
+#define REG_ADMA_ADDR 0x0058 /* 64 bits */
+
+/* REG_BLKSZ bits */
+#define BLKSZ_XFR_BLK_SIZE_MASK (0x0fff)
+#define BLKSZ_BOUNDARY_4K (0 << 12)
+#define BLKSZ_BOUNDARY_8K (1 << 12)
+#define BLKSZ_BOUNDARY_16K (2 << 12)
+#define BLKSZ_BOUNDARY_32K (3 << 12)
+#define BLKSZ_BOUNDARY_64K (4 << 12)
+#define BLKSZ_BOUNDARY_128K (5 << 12)
+#define BLKSZ_BOUNDARY_256K (6 << 12)
+#define BLKSZ_BOUNDARY_512K (7 << 12)
+#define BLKSZ_BOUNDARY_MASK (0x7 << 12)
+
+/* REG_XFR_MODE bits */
+#define XFR_MODE_DMA_EN BIT(0)
+#define XFR_MODE_COUNT BIT(1)
+#define XFR_MODE_AUTO_CMD12 BIT(2)
+#define XFR_MODE_READ BIT(4) /* 1 = read, 0 = write */
+#define XFR_MODE_MULTI BIT(5) /* 1 = multi, 0 = single */
+
+/* REG_COMMAND bits */
+#define COMMAND_CRC_CHECK_EN BIT(3)
+#define COMMAND_INDEX_CHECK_EN BIT(4)
+#define COMMAND_DATA_PRESENT BIT(5)
+#define COMMAND_TYPE
+#define COMMAND_TYPE_NORM (0 << 6)
+#define COMMAND_TYPE_SUSPEND (1 << 6)
+#define COMMAND_TYPE_RESUME (2 << 6)
+#define COMMAND_TYPE_ABORT (3 << 6)
+#define COMMAND_TYPE_MASK (0x3 << 6)
+#define COMMAND_RESP_NONE 0
+#define COMMAND_RESP_136 1 /* R2 */
+#define COMMAND_RESP_48 2 /* R1, R3, R6, R7 */
+#define COMMAND_RESP_48_BUSY 3 /* R1b */
+
+/* REG_PRS bits */
+#define PRS_CMD_INHIBIT BIT(0)
+#define PRS_DAT_INHIBIT BIT(1)
+#define PRS_DAT_ACTIVE BIT(2)
+#define PRS_WRITE_ACTIVE BIT(8)
+#define PRS_READ_ACTIVE BIT(9)
+#define PRS_BUF_WR_EN BIT(10)
+#define PRS_BUF_RD_EN BIT(11)
+#define PRS_CARD_INSERTED BIT(16)
+#define PRS_CARD_STABLE BIT(17)
+#define PRS_CARD_DETECT BIT(18)
+#define PRS_WRITE_ENABLE BIT(19)
+#define PRS_DAT0_SIG BIT(20)
+#define PRS_DAT1_SIG BIT(21)
+#define PRS_DAT2_SIG BIT(22)
+#define PRS_DAT3_SIG BIT(23)
+
+#define PRS_INHIBIT \
+ (PRS_CMD_INHIBIT | PRS_DAT_INHIBIT)
+#define PRS_DAT_SIG \
+ (PRS_DAT0_SIG | PRS_DAT1_SIG | PRS_DAT2_SIG | PRS_DAT3_SIG)
+
+/* REG_HOST_CONTROL bits */
+#define HOST_CONTROL_LED_ON BIT(0)
+#define HOST_CONTROL_DATA_WIDTH BIT(1)
+#define HOST_CONTROL_HIGH_SPEED_EN BIT(2)
+#define HOST_CONTROL_DMA_SDMA (0 << 3)
+#define HOST_CONTROL_DMA_ADMA32 (2 << 3)
+#define HOST_CONTROL_DMA_ADMA64 (3 << 3)
+#define HOST_CONTROL_DMA_MASK (0x3 << 3)
+#define HOST_CONTROL_CARD_DETECT_TEST BIT(6)
+#define HOST_CONTROL_CARD_DETECT_SEL BIT(7)
+
+/* REG_POWER_CONTROL bits */
+#define POWER_CONTROL_BUS_POWER BIT(0)
+#define POWER_CONTROL_33V (7 << 1)
+#define POWER_CONTROL_30V (6 << 1)
+#define POWER_CONTROL_18V (5 << 1)
+
+/* REG_BLOCK_GAP_CONTROL bits */
+#define BLOCK_GAP_CONTROL_STOP BIT(0)
+#define BLOCK_GAP_CONTROL_CONTINUE BIT(1)
+#define BLOCK_GAP_CONTROL_READ_WAIT BIT(2)
+#define BLOCK_GAP_CONTROL_INTERRUPT BIT(3)
+
+/* REG_WAKEUP_CONTROL bits */
+#define WAKEUP_CONTROL_INTERRUPT BIT(0)
+#define WAKEUP_CONTROL_INSERT BIT(1)
+#define WAKEUP_CONTROL_REMOVE BIT(2)
+
+/* REG_CLOCK_CONTROL bits */
+#define CLOCK_CONTROL_INT_CLOCK_EN BIT(0)
+#define CLOCK_CONTROL_INT_CLOCK_STABLE BIT(1)
+#define CLOCK_CONTROL_SD_CLOCK_EN BIT(2)
+#define CLOCK_CONTROL_FREQ_MASK (0xff << 8)
+#define CLOCK_CONTROL_FREQ_SHIFT 8
+
+/* REG_TIMEOUT_CONTROL bits */
+#define TIMEOUT_TIMECLK_2_27 (0xe)
+/* not listing them all here... but it goes on */
+#define TIMEOUT_TIMECLK_2_13 (0x0)
+
+/* REG_SOFT_RESET bits */
+#define SOFT_RESET_ALL BIT(0)
+#define SOFT_RESET_CMD BIT(1)
+#define SOFT_RESET_DAT BIT(2)
+
+/* REG_INT_{STAT,EN,MASK} bits */
+#define INT_CMD BIT(0)
+#define INT_XFR BIT(1)
+#define INT_BG BIT(2)
+#define INT_DMA BIT(3)
+#define INT_WR BIT(4)
+#define INT_RD BIT(5)
+#define INT_INS BIT(6)
+#define INT_REM BIT(7)
+#define INT_CARD BIT(8)
+#define INT_ERR BIT(15)
+
+#define INT_PIO (INT_RD | INT_WR)
+#define INT_HOTPLUG (INT_INS | INT_REM)
+
+#define INT_MASK (INT_XFR | INT_DMA | INT_PIO | INT_HOTPLUG)
+#define INT_ENAB (INT_MASK | INT_CMD)
+
+/* REG_ERR_{STAT,EN,MASK} bits */
+#define ERR_VENDOR (0xf << 12)
+#define ERR_ADMA BIT(9)
+#define ERR_ACMD12 BIT(8)
+#define ERR_CURRENT BIT(7)
+#define ERR_DAT_END BIT(6)
+#define ERR_DAT_CRC BIT(5)
+#define ERR_DAT_TMO BIT(4)
+#define ERR_CMD_IDX BIT(3)
+#define ERR_CMD_END BIT(2)
+#define ERR_CMD_CRC BIT(1)
+#define ERR_CMD_TMO BIT(0)
+
+#define ERR_CMD (ERR_CMD_IDX | ERR_CMD_END | ERR_CMD_CRC | ERR_CMD_TMO)
+#define ERR_CMD_CFL (ERR_CMD_CRC | ERR_CMD_TMO)
+
+#define ERR_DAT (ERR_DAT_END | ERR_DAT_CRC | ERR_DAT_TMO)
+
+#define ERR_MASK (ERR_ACMD12 | ERR_DAT)
+#define ERR_ENAB (ERR_MASK | ERR_CMD)
+
+/* REG_ACMD12_ERROR bits */
+#define ACMD12_ERROR_NOT_EXECUTED BIT(0)
+#define ACMD12_ERROR_TIMEOUT BIT(1)
+#define ACMD12_ERROR_CRC BIT(2)
+#define ACMD12_ERROR_END_BIT BIT(3)
+#define ACMD12_ERROR_INDEX BIT(4)
+#define ACMD12_ERROR_NOT_ISSUED BIT(7)
+
+/* REG_CAPAB bits */
+#define CAPAB_TIMEOUT_FREQ_SHIFT 0
+#define CAPAB_TIMEOUT_FREQ_MASK (0x3f << 0)
+#define CAPAB_TIMEOUT_UNITS BIT(7) /* 1 == MHz, 0 = kHz */
+#define CAPAB_BASE_FREQ_SHIFT 8
+#define CAPAB_BASE_FREQ_MASK (0x3f << 8)
+#define CAPAB_MAXBLK_512 (0 << 16)
+#define CAPAB_MAXBLK_1K (1 << 16)
+#define CAPAB_MAXBLK_2K (2 << 16)
+#define CAPAB_MAXBLK_MASK (0x3 << 16)
+#define CAPAB_ADMA2 BIT(19)
+#define CAPAB_ADMA1 BIT(20)
+#define CAPAB_HIGH_SPEED BIT(21)
+#define CAPAB_SDMA BIT(22)
+#define CAPAB_SUSPEND BIT(23)
+#define CAPAB_33V BIT(24)
+#define CAPAB_30V BIT(25)
+#define CAPAB_18V BIT(26)
+#define CAPAB_VOLTS (CAPAB_33V | CAPAB_30V | CAPAB_18V)
+#define CAPAB_64BIT BIT(28)
+
+/* REG_MAX_CURRENT bits */
+#define MAX_CURRENT_33V_SHIFT 0
+#define MAX_CURRENT_33V_MASK (0xff << 0)
+#define MAX_CURRENT_30V_SHIFT 8
+#define MAX_CURRENT_30V_MASK (0xff << 8)
+#define MAX_CURRENT_18V_SHIFT 16
+#define MAX_CURRENT_18V_MASK (0xff << 16)
+
+/* REG_VERSION bits */
+#define VERSION_VENDOR_SHIFT 8
+#define VERSION_VENDOR_MASK (0xff << 8)
+#define VERSION_SDHOST_MASK 0xff
+#define VERSION_SDHOST_1 0
+#define VERSION_SDHOST_2 1
+
+/* REG_ADMA_ERROR bits */
+#define ADMA_ERROR_STATE_ST_STOP 0
+#define ADMA_ERROR_STATE_ST_FDS 1
+#define ADMA_ERROR_STATE_ST_TFR 3
+#define ADMA_ERROR_STATE_MASK 0x3
+#define ADMA_ERROR_LEN_MISMATCH BIT(2)
+
+#endif /* _SYS_SDCARD_SDHOST_H */
diff --git a/usr/src/uts/common/io/sdcard/adapters/wbsd/wbsd.c b/usr/src/uts/common/io/sdcard/adapters/wbsd/wbsd.c
new file mode 100644
index 0000000000..9ad7ad2428
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/adapters/wbsd/wbsd.c
@@ -0,0 +1,1058 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/sdcard/sda.h>
+#include <sys/note.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include "wbsd.h"
+
+typedef enum wbsd_direction { READ, WRITE } wbsd_direction_t;
+
+/*
+ * Soft state.
+ */
+typedef struct wbsd {
+ dev_info_t *w_dip;
+ sda_host_t *w_host;
+ ddi_intr_handle_t w_ihandle;
+ ddi_softint_handle_t w_shandle;
+ ddi_acc_handle_t w_acch;
+ uint8_t *w_regs;
+ kmutex_t w_lock;
+ boolean_t w_suspended;
+ uint8_t w_width; /* data bus width */
+ uint32_t w_resid; /* bytes remaining to xfer */
+ uint32_t w_nblks; /* blocks remaining to xfer */
+ uint32_t w_blksz; /* block size */
+ uint8_t *w_data; /* data pointer */
+ wbsd_direction_t w_direction;
+ sda_err_t w_cmd_err;
+ sda_err_t w_dat_err;
+ boolean_t w_done;
+ boolean_t w_detect;
+ boolean_t w_acmd12;
+ boolean_t w_do_soft;
+ uint16_t w_ctime; /* command timeout (us) */
+} wbsd_t;
+
+_NOTE(DATA_READABLE_WITHOUT_LOCK(wbsd::w_ctime))
+
+#define GETREG(wp, r) ddi_get8(wp->w_acch, wp->w_regs + r)
+#define PUTREG(wp, r, v) ddi_put8(wp->w_acch, wp->w_regs + r, v)
+#define SETREG(wp, r, b) PUTREG(wp, r, GETREG(wp, r) | b)
+#define CLRREG(wp, r, b) PUTREG(wp, r, GETREG(wp, r) & ~b)
+#define GETIDX(wp, i, v) \
+ { PUTREG(wp, REG_IDXR, i); v = GETREG(wp, REG_DATAR); }
+#define PUTIDX(wp, i, v) \
+ { PUTREG(wp, REG_IDXR, i); PUTREG(wp, REG_DATAR, v); }
+
+static int wbsd_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
+static int wbsd_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
+
+static int wbsd_attach(dev_info_t *);
+static int wbsd_detach(dev_info_t *);
+static int wbsd_resume(dev_info_t *);
+static int wbsd_suspend(dev_info_t *);
+
+static sda_err_t wbsd_cmd(void *, sda_cmd_t *);
+static sda_err_t wbsd_getprop(void *, sda_prop_t, uint32_t *);
+static sda_err_t wbsd_setprop(void *, sda_prop_t, uint32_t);
+static sda_err_t wbsd_reset(void *);
+static sda_err_t wbsd_halt(void *);
+static sda_err_t wbsd_poll(void *);
+
+static uint_t wbsd_hard_intr(caddr_t, caddr_t);
+static uint_t wbsd_soft_intr(caddr_t, caddr_t);
+static int wbsd_setup_interrupts(wbsd_t *);
+static void wbsd_teardown_interrupts(wbsd_t *);
+static void wbsd_fifo_read(wbsd_t *);
+static void wbsd_fifo_write(wbsd_t *);
+static void wbsd_reset_hw(wbsd_t *);
+static void wbsd_halt_hw(wbsd_t *);
+static void wbsd_send_stop(wbsd_t *);
+static void wbsd_busy_end(wbsd_t *);
+static void wbsd_prog_end(wbsd_t *);
+static void wbsd_detect(wbsd_t *);
+static void wbsd_error(wbsd_t *, sda_err_t);
+
+static struct dev_ops wbsd_dev_ops = {
+ DEVO_REV, /* devo_rev */
+ 0, /* devo_refcnt */
+ ddi_no_info, /* devo_getinfo */
+ nulldev, /* devo_identify */
+ nulldev, /* devo_probe */
+ wbsd_ddi_attach, /* devo_attach */
+ wbsd_ddi_detach, /* devo_detach */
+ nodev, /* devo_reset */
+ NULL, /* devo_cb_ops */
+ NULL, /* devo_bus_ops */
+ NULL /* devo_power */
+};
+
+static struct modldrv wbsd_modldrv = {
+ &mod_driverops, /* drv_modops */
+ "Winbond W83L519D SD Host", /* drv_linkinfo */
+ &wbsd_dev_ops /* drv_dev_ops */
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1, /* ml_rev */
+ { &wbsd_modldrv, NULL } /* ml_linkage */
+};
+
+static struct sda_ops wbsd_sda_ops = {
+ SDA_OPS_VERSION,
+ wbsd_cmd, /* so_cmd */
+ wbsd_getprop, /* so_getprop */
+ wbsd_setprop, /* so_setprop */
+ wbsd_poll, /* so_poll */
+ wbsd_reset, /* so_reset */
+ wbsd_halt, /* so_halt */
+};
+
+static ddi_device_acc_attr_t wbsd_regattr = {
+ DDI_DEVICE_ATTR_V0, /* devacc_attr_version */
+ DDI_NEVERSWAP_ACC, /* devacc_attr_endian_flags */
+ DDI_STRICTORDER_ACC, /* devacc_attr_dataorder */
+ DDI_DEFAULT_ACC, /* devacc_attr_access */
+};
+
+int
+_init(void)
+{
+ int rv;
+
+ sda_host_init_ops(&wbsd_dev_ops);
+
+ if ((rv = mod_install(&modlinkage)) != 0) {
+ sda_host_fini_ops(&wbsd_dev_ops);
+ }
+
+ return (rv);
+}
+
+int
+_fini(void)
+{
+ int rv;
+
+ if ((rv = mod_remove(&modlinkage)) == 0) {
+ sda_host_fini_ops(&wbsd_dev_ops);
+ }
+
+ return (rv);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+int
+wbsd_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ switch (cmd) {
+ case DDI_ATTACH:
+ return (wbsd_attach(dip));
+ case DDI_RESUME:
+ return (wbsd_resume(dip));
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+int
+wbsd_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ switch (cmd) {
+ case DDI_DETACH:
+ return (wbsd_detach(dip));
+ case DDI_SUSPEND:
+ return (wbsd_suspend(dip));
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+int
+wbsd_attach(dev_info_t *dip)
+{
+ wbsd_t *wp;
+
+ wp = kmem_zalloc(sizeof (*wp), KM_SLEEP);
+
+ wp->w_host = sda_host_alloc(dip, 1, &wbsd_sda_ops, NULL);
+ if (wp->w_host == NULL) {
+ cmn_err(CE_WARN, "Unable to allocate SDA host structure");
+ goto failed;
+ }
+ ddi_set_driver_private(dip, wp);
+ sda_host_set_private(wp->w_host, 0, wp);
+
+ wp->w_dip = dip;
+
+ if (ddi_regs_map_setup(dip, 0, (caddr_t *)&wp->w_regs, 0, 0,
+ &wbsd_regattr, &wp->w_acch) != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "Unable to map registers");
+ goto failed;
+ }
+
+ /* make sure interrupts are disabled */
+ PUTREG(wp, REG_EIR, 0);
+
+ /* setup interrupts, also initializes locks */
+ if (wbsd_setup_interrupts(wp) != DDI_SUCCESS)
+ goto failed;
+
+ ddi_report_dev(dip);
+
+ /* enable device interrupts in DDI */
+ (void) ddi_intr_enable(wp->w_ihandle);
+
+ /* attach to the framework */
+ if (sda_host_attach(wp->w_host) != DDI_SUCCESS) {
+ goto failed;
+ }
+
+ return (DDI_SUCCESS);
+
+failed:
+ /* tear down interrupts */
+ if (wp->w_ihandle != NULL) {
+ PUTREG(wp, REG_EIR, 0);
+ (void) GETREG(wp, REG_ISR);
+
+ wbsd_teardown_interrupts(wp);
+ }
+
+ /* toss register map */
+ if (wp->w_regs != NULL) {
+ ddi_regs_map_free(&wp->w_acch);
+ }
+
+ /* free host resources */
+ if (wp->w_host != NULL) {
+ sda_host_free(wp->w_host);
+ }
+
+ kmem_free(wp, sizeof (*wp));
+ return (DDI_FAILURE);
+}
+
+int
+wbsd_detach(dev_info_t *dip)
+{
+ wbsd_t *wp;
+
+ if ((wp = ddi_get_driver_private(dip)) == NULL) {
+ cmn_err(CE_WARN, "Unable to get soft state");
+ return (DDI_FAILURE);
+ }
+
+
+ sda_host_detach(wp->w_host);
+
+ /* disable interrupts */
+ PUTREG(wp, REG_EIR, 0);
+ (void) GETREG(wp, REG_ISR);
+
+ /* remove power from the socket */
+ SETREG(wp, REG_CSR, CSR_POWER_N);
+
+ wbsd_teardown_interrupts(wp);
+ ddi_regs_map_free(&wp->w_acch);
+ kmem_free(wp, sizeof (*wp));
+ return (DDI_SUCCESS);
+}
+
+int
+wbsd_suspend(dev_info_t *dip)
+{
+ wbsd_t *wp;
+
+ if ((wp = ddi_get_driver_private(dip)) == NULL) {
+ cmn_err(CE_WARN, "Unable to get soft state");
+ return (DDI_FAILURE);
+ }
+ mutex_enter(&wp->w_lock);
+ wp->w_suspended = B_TRUE;
+ wbsd_halt_hw(wp);
+ mutex_exit(&wp->w_lock);
+
+ return (DDI_SUCCESS);
+}
+
+int
+wbsd_resume(dev_info_t *dip)
+{
+ wbsd_t *wp;
+
+ if ((wp = ddi_get_driver_private(dip)) == NULL) {
+ cmn_err(CE_WARN, "Unable to get soft state");
+ return (DDI_FAILURE);
+ }
+
+ mutex_enter(&wp->w_lock);
+ wp->w_suspended = B_FALSE;
+ wbsd_reset_hw(wp);
+ mutex_exit(&wp->w_lock);
+
+ sda_host_detect(wp->w_host, 0);
+
+ return (DDI_SUCCESS);
+}
+
+int
+wbsd_setup_interrupts(wbsd_t *wp)
+{
+ uint_t ipri;
+ int actual;
+ ddi_intr_handle_t ih;
+ ddi_softint_handle_t sh;
+
+ /*
+ * Setup interrupt. Note that these are ISA devices, and only have
+ * a single fixed interrupt.
+ */
+ if (ddi_intr_alloc(wp->w_dip, &ih, DDI_INTR_TYPE_FIXED, 0,
+ 1, &actual, DDI_INTR_ALLOC_STRICT) != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "Unable to allocate interrupt");
+ return (DDI_FAILURE);
+ }
+ if (ddi_intr_get_pri(ih, &ipri) != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "Unable to get interrupt priority");
+ (void) ddi_intr_free(ih);
+ return (DDI_FAILURE);
+ }
+ if (ddi_intr_add_handler(ih, wbsd_hard_intr, wp, NULL) !=
+ DDI_SUCCESS) {
+ cmn_err(CE_WARN, "Unable to add interrupt handler");
+ (void) ddi_intr_free(ih);
+ return (DDI_FAILURE);
+ }
+ mutex_init(&wp->w_lock, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ipri));
+
+ /*
+ * Soft interrupt is next.
+ */
+ if (ddi_intr_add_softint(wp->w_dip, &sh, DDI_INTR_SOFTPRI_MIN,
+ wbsd_soft_intr, wp) != DDI_SUCCESS) {
+ (void) ddi_intr_remove_handler(ih);
+ (void) ddi_intr_free(ih);
+ mutex_destroy(&wp->w_lock);
+ return (DDI_FAILURE);
+ }
+
+ wp->w_ihandle = ih;
+ wp->w_shandle = sh;
+
+ return (DDI_SUCCESS);
+}
+
+void
+wbsd_teardown_interrupts(wbsd_t *wp)
+{
+ (void) ddi_intr_disable(wp->w_ihandle);
+
+ /*
+ * These are here to ensure that any previously
+ * running interrupts (hard or soft) have completed.
+ */
+ mutex_enter(&wp->w_lock);
+ mutex_exit(&wp->w_lock);
+
+ (void) ddi_intr_remove_handler(wp->w_ihandle);
+ (void) ddi_intr_free(wp->w_ihandle);
+ (void) ddi_intr_remove_softint(wp->w_shandle);
+
+ mutex_destroy(&wp->w_lock);
+}
+
+
+void
+wbsd_detect(wbsd_t *wp)
+{
+ wp->w_detect = B_TRUE;
+ wp->w_done = B_TRUE;
+ wp->w_cmd_err = wp->w_dat_err = SDA_ENODEV;
+ wp->w_do_soft = B_TRUE;
+}
+
+void
+wbsd_error(wbsd_t *wp, sda_err_t err)
+{
+ wp->w_done = B_TRUE;
+ wp->w_cmd_err = wp->w_dat_err = err;
+ wp->w_do_soft = B_TRUE;
+}
+
+void
+wbsd_busy_end(wbsd_t *wp)
+{
+ wp->w_done = B_TRUE;
+ wp->w_do_soft = B_TRUE;
+}
+
+void
+wbsd_prog_end(wbsd_t *wp)
+{
+ ASSERT(wp->w_direction == WRITE);
+ if (wp->w_nblks > 0) {
+ wp->w_nblks--;
+ if (wp->w_nblks > 0) {
+
+ /*
+ * Start transferring the next block.
+ */
+ wp->w_resid = wp->w_blksz;
+ wbsd_fifo_write(wp);
+
+ } else {
+ /*
+ * If we needed auto terminate, then do it now.
+ */
+ if (wp->w_acmd12) {
+ wbsd_send_stop(wp);
+
+ /*
+ * Otherwise its a single block write completion, so
+ * just complete the transfer.
+ */
+ } else {
+ wp->w_done = B_TRUE;
+ wp->w_do_soft = B_TRUE;
+ }
+ }
+ }
+}
+
+uint_t
+wbsd_hard_intr(caddr_t arg1, caddr_t arg2)
+{
+ wbsd_t *wp = (void *)arg1;
+ uint8_t isr;
+ uint_t rv = DDI_INTR_UNCLAIMED;
+ boolean_t do_soft = B_FALSE;
+ int i;
+
+ mutex_enter(&wp->w_lock);
+ if (wp->w_suspended) {
+ mutex_exit(&wp->w_lock);
+ return (rv);
+ }
+
+ for (i = 0; i < 100000; i++) {
+ isr = GETREG(wp, REG_ISR);
+
+ if ((isr == 0xff) || ((isr & ISR_WANTED) == 0))
+ break;
+
+ rv = DDI_INTR_CLAIMED;
+
+ if (isr & ISR_CARD) {
+ /*
+ * Make sure that the chip is fully reset after
+ * a card interrupt occurs.
+ */
+ wbsd_reset_hw(wp);
+ wbsd_detect(wp);
+ break;
+ }
+
+ if (isr & ISR_FIFO) {
+ /*
+ * FIFO data ready. Process this as quickly as
+ * possible.
+ */
+ if (wp->w_direction == WRITE) {
+ wbsd_fifo_write(wp);
+ } else {
+ wbsd_fifo_read(wp);
+ }
+ }
+
+ if (isr & ISR_BUSY_END) {
+ wbsd_busy_end(wp);
+ }
+
+ if (isr & ISR_PROG_END) {
+ wbsd_prog_end(wp);
+ }
+
+ if (isr & ISR_TIMEOUT) {
+ wbsd_error(wp, SDA_ETIME);
+ }
+
+ if (isr & ISR_CRC_ERR) {
+ wbsd_error(wp, SDA_ECRC7);
+ }
+ }
+
+ if (i >= 100000) {
+ PUTREG(wp, REG_EIR, 0);
+ sda_host_log(wp->w_host, 0,
+ "Stuck interrupt detected (isr %x)", isr);
+ sda_host_fault(wp->w_host, 0, SDA_FAULT_HOST);
+ }
+
+ /*
+ * If arg2 is NULL, then we are running as an ordinary interrupt.
+ * Otherwise we are running from polled context, and cannot trigger
+ * soft interrupts.
+ */
+ if (wp->w_do_soft && (arg2 == NULL)) {
+ wp->w_do_soft = B_FALSE;
+ do_soft = B_TRUE;
+ }
+ mutex_exit(&wp->w_lock);
+
+ if (do_soft)
+ (void) ddi_intr_trigger_softint(wp->w_shandle, NULL);
+
+ return (rv);
+}
+
+/*ARGSUSED1*/
+uint_t
+wbsd_soft_intr(caddr_t arg1, caddr_t arg2)
+{
+ wbsd_t *wp = (void *)arg1;
+ boolean_t detect = B_FALSE;
+ boolean_t done = B_FALSE;
+ sda_err_t err = SDA_EOK;
+
+ mutex_enter(&wp->w_lock);
+
+ detect = wp->w_detect;
+ done = wp->w_done;
+ err = wp->w_dat_err;
+
+ wp->w_done = B_FALSE;
+ wp->w_detect = B_FALSE;
+ wp->w_dat_err = SDA_EOK;
+
+ mutex_exit(&wp->w_lock);
+
+ if (detect) {
+ sda_host_detect(wp->w_host, 0);
+ }
+
+ if (done) {
+ sda_host_transfer(wp->w_host, 0, err);
+ }
+
+ return (DDI_INTR_CLAIMED);
+}
+
+sda_err_t
+wbsd_poll(void *arg)
+{
+ /* 2nd argument indicates running from poll */
+ (void) wbsd_hard_intr(arg, (void *)wbsd_poll);
+ (void) wbsd_soft_intr(arg, (void *)wbsd_poll);
+ return (SDA_EOK);
+}
+
+sda_err_t
+wbsd_getprop(void *arg, sda_prop_t prop, uint32_t *val)
+{
+ wbsd_t *wp = arg;
+ sda_err_t rv = SDA_EOK;
+ uint8_t clock;
+
+ mutex_enter(&wp->w_lock);
+ if (wp->w_suspended) {
+ mutex_exit(&wp->w_lock);
+ return (SDA_ESUSPENDED);
+ }
+
+ switch (prop) {
+ case SDA_PROP_INSERTED:
+ *val = (GETREG(wp, REG_CSR) & CSR_PRESENT) ? B_TRUE : B_FALSE;
+ break;
+
+ case SDA_PROP_WPROTECT:
+ /* switch signal select */
+ SETREG(wp, REG_CSR, CSR_MSLED);
+
+ drv_usecwait(1000);
+ *val = (GETREG(wp, REG_CSR) & CSR_WPROTECT) ? B_TRUE : B_FALSE;
+ CLRREG(wp, REG_CSR, CSR_MSLED);
+ break;
+
+ case SDA_PROP_OCR:
+ *val = OCR_32_33V;
+ break;
+
+ case SDA_PROP_CLOCK:
+ GETIDX(wp, IDX_CLOCK, clock)
+ switch (clock) {
+ case IDX_CLOCK_24M:
+ *val = 24000000;
+ break;
+ case IDX_CLOCK_16M:
+ *val = 16000000;
+ break;
+ case IDX_CLOCK_12M:
+ *val = 12000000;
+ break;
+ case IDX_CLOCK_375K:
+ *val = 375000;
+ break;
+ default:
+ *val = 0;
+ break;
+ }
+ break;
+
+ case SDA_PROP_CAP_4BITS:
+ /*
+ * On Tadpole SPARCLE hardware, the card detect uses
+ * DAT3, which causes all kinds of problems. It is quite
+ * troublesome to support card detection events properly with
+ * this configuration, so we fall back to supporting only
+ * single bit mode. It is possible to correct this, but
+ * it requires changes in the framework, particularly to
+ * note that the DAT3 pin is used this way.
+ *
+ * In particular, this would require separate commands to
+ * the card to connect/disconnect the card internal pullup
+ * resistor, as well as to manipulate the interrupt register,
+ * and then poll card status on command completion.
+ *
+ * The Winbond part is so slow, that it is doubtful that the
+ * trouble would be worth it. On x86 hardware where we can
+ * make use of GPIO pin detection, the situation might be
+ * quite different.
+ */
+ *val = B_FALSE;
+ break;
+
+ case SDA_PROP_CAP_NOPIO:
+ case SDA_PROP_CAP_INTR:
+ case SDA_PROP_CAP_8BITS:
+ *val = B_FALSE;
+ break;
+
+ default:
+ rv = SDA_ENOTSUP;
+ break;
+ }
+
+ mutex_exit(&wp->w_lock);
+
+ return (rv);
+}
+
+sda_err_t
+wbsd_setprop(void *arg, sda_prop_t prop, uint32_t val)
+{
+ wbsd_t *wp = arg;
+ sda_err_t rv = SDA_EOK;
+ uint8_t clock;
+
+ mutex_enter(&wp->w_lock);
+ if (wp->w_suspended) {
+ mutex_exit(&wp->w_lock);
+ return (SDA_ESUSPENDED);
+ }
+
+ switch (prop) {
+
+ case SDA_PROP_LED:
+ break;
+ case SDA_PROP_CLOCK:
+ /*
+ * Note that the "worst case" command timeouts are 16.7us for
+ * the "slow" 12MHz clock. So a 20us timeout is enough for
+ * everything faster.
+ */
+ wp->w_ctime = 20;
+ if (val >= 24000000) {
+ clock = IDX_CLOCK_24M;
+ } else if (val >= 16000000) {
+ clock = IDX_CLOCK_16M;
+ } else if (val >= 12000000) {
+ clock = IDX_CLOCK_12M;
+ } else {
+ /*
+ * Worst case command timeout is 533.3 usec. Just
+ * pick a big enough value to force it. If we choose
+ * a value of 2 msec, it is enough even if the clock
+ * runs as low as 100KHz.
+ */
+ clock = IDX_CLOCK_375K;
+ wp->w_ctime = 2000;
+ }
+ PUTIDX(wp, IDX_CLOCK, clock);
+ break;
+
+ case SDA_PROP_BUSWIDTH:
+ /*
+ * See the comment in SDA_PROP_CAP_4BITS, though.
+ */
+ if ((val == 4) || (val == 1)) {
+ wp->w_width = (uint8_t)val;
+ } else {
+ rv = SDA_EINVAL;
+ }
+ break;
+
+ case SDA_PROP_OCR:
+ if ((val == OCR_32_33V) &&
+ ((GETREG(wp, REG_CSR) & CSR_PRESENT) != 0)) {
+ /* apply power */
+ CLRREG(wp, REG_CSR, CSR_POWER_N);
+ /* activate the various other interrupts on the chip */
+ PUTREG(wp, REG_EIR, EIR_CARD | EIR_FIFO | EIR_CRC_ERR |
+ EIR_TIMEOUT | EIR_PROG_END | EIR_BUSY_END);
+ } else {
+ /* power down and reset */
+ wbsd_reset_hw(wp);
+ if (val != 0) {
+ rv = SDA_EINVAL;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ mutex_exit(&wp->w_lock);
+
+ return (rv);
+}
+
+void
+wbsd_fifo_read(wbsd_t *wp)
+{
+ uint8_t fsr;
+ uint8_t cnt;
+
+ ASSERT(mutex_owned(&wp->w_lock));
+
+ while ((((fsr = GETREG(wp, REG_FSR)) & FSR_EMPTY) == 0) &&
+ (wp->w_resid != 0)) {
+ /*
+ * The point of this logic is to avoid extra reads of
+ * the fifo status register. We are throughput
+ * limited by the number of PIOs.
+ */
+ if ((fsr & FSR_FULL) != 0) {
+ cnt = 16;
+ } else if ((fsr & FSR_FULL_THRE) != 0) {
+ cnt = 8;
+ } else {
+ cnt = 1;
+ }
+ while ((cnt != 0) && (wp->w_resid != 0)) {
+ cnt--;
+ wp->w_resid--;
+ *wp->w_data++ = GETREG(wp, REG_DFR);
+ }
+ }
+
+ if (wp->w_resid != 0) {
+ PUTIDX(wp, IDX_THRESH, IDX_THRESH_FULL | min(wp->w_resid, 8));
+ } else {
+ PUTIDX(wp, IDX_THRESH, 0);
+
+ if (wp->w_acmd12) {
+ wbsd_send_stop(wp);
+ } else {
+ wbsd_busy_end(wp);
+ }
+ }
+}
+
+void
+wbsd_fifo_write(wbsd_t *wp)
+{
+ uint8_t fsr;
+ uint8_t cnt;
+
+ ASSERT(mutex_owned(&wp->w_lock));
+
+ while ((((fsr = GETREG(wp, REG_FSR)) & FSR_FULL) == 0) &&
+ (wp->w_resid != 0)) {
+ if ((fsr & FSR_EMPTY) != 0) {
+ cnt = 16;
+ } else if ((fsr & FSR_EMPTY_THRE) != 0) {
+ cnt = 8;
+ } else {
+ cnt = 1;
+ }
+ while ((cnt != 0) && (wp->w_resid != 0)) {
+ cnt--;
+ wp->w_resid--;
+ PUTREG(wp, REG_DFR, *wp->w_data++);
+ }
+ }
+ if (wp->w_resid != 0) {
+ PUTIDX(wp, IDX_THRESH, IDX_THRESH_EMPTY | min(wp->w_resid, 8));
+ } else {
+ PUTIDX(wp, IDX_THRESH, 0);
+ /* wait for PROG interrupt */
+ }
+}
+
+void
+wbsd_send_stop(wbsd_t *wp)
+{
+ wp->w_acmd12 = B_FALSE;
+
+ PUTREG(wp, REG_CMDR, CMD_STOP_TRANSMIT);
+ PUTREG(wp, REG_CMDR, 0);
+ PUTREG(wp, REG_CMDR, 0);
+ PUTREG(wp, REG_CMDR, 0);
+ PUTREG(wp, REG_CMDR, 0);
+}
+
+sda_err_t
+wbsd_cmd(void *arg, sda_cmd_t *cmdp)
+{
+ wbsd_t *wp = arg;
+ boolean_t checkcrc;
+ uint8_t rstart;
+ uint8_t rwords;
+ sda_err_t rv = SDA_EOK;
+
+ checkcrc = B_TRUE;
+ rstart = IDX_RESP_12;
+ rwords = 1;
+
+ switch (cmdp->sc_rtype) {
+ case R0:
+ rwords = 0;
+ break;
+ case R1:
+ case R5:
+ case R6:
+ case R7:
+ case R1b:
+ case R5b:
+ break;
+ case R2:
+ rstart = IDX_RESP_1;
+ rwords = 4;
+ checkcrc = B_FALSE;
+ break;
+ case R3:
+ case R4:
+ checkcrc = B_FALSE;
+ break;
+ }
+
+ mutex_enter(&wp->w_lock);
+ if (wp->w_suspended) {
+ mutex_exit(&wp->w_lock);
+ return (SDA_ESUSPENDED);
+ }
+
+ if (cmdp->sc_nblks != 0) {
+ uint16_t sz;
+ uint8_t v;
+
+ wp->w_blksz = cmdp->sc_blksz;
+ wp->w_nblks = cmdp->sc_nblks;
+
+ /* save a few things for completion */
+ wp->w_data = (uint8_t *)cmdp->sc_kvaddr;
+
+ wp->w_acmd12 = (cmdp->sc_flags & SDA_CMDF_AUTO_CMD12) ?
+ B_TRUE : B_FALSE;
+
+ /* maximum timeouts, 127 msec and 25500 cycles */
+ PUTIDX(wp, IDX_TAAC, 127);
+ PUTIDX(wp, IDX_NSAC, 255);
+
+ /* set data width */
+ sz = cmdp->sc_blksz + ((wp->w_width == 4) ? 8 : 2);
+ PUTIDX(wp, IDX_BLKSZMSB, ((sz >> 4) & 0xf0) |
+ ((wp->w_width == 4) ? 1 : 0));
+ PUTIDX(wp, IDX_BLKSZLSB, sz & 0xff);
+
+ /* make sure start the fifo with a clean slate */
+ GETIDX(wp, IDX_RESET, v);
+ v |= IDX_RESET_FIFO;
+ PUTIDX(wp, IDX_RESET, v);
+
+ /* we don't use DMA, period */
+ PUTIDX(wp, IDX_DMA, 0);
+
+ if ((cmdp->sc_flags & SDA_CMDF_READ) != 0) {
+ /*
+ * Reading... we arrange to wait for the full
+ * transfer, than doing a block at a time.
+ * Simpler that way.
+ */
+
+ wp->w_direction = READ;
+ wp->w_resid = wp->w_blksz * wp->w_nblks;
+ PUTIDX(wp, IDX_THRESH, IDX_THRESH_FULL |
+ min(wp->w_resid, 8));
+ } else {
+ /*
+ * Writing... go ahead and prefill the fifo.
+ * We write a block at a time, because we need
+ * the PROG interrupts in the block gaps.
+ */
+
+ wp->w_direction = WRITE;
+ wp->w_resid = wp->w_blksz;
+ PUTIDX(wp, IDX_THRESH, IDX_THRESH_EMPTY |
+ min(wp->w_blksz, 8));
+ wbsd_fifo_write(wp);
+ }
+ }
+
+ /*
+ * This chip is a bit simple minded. It cannot distinguish
+ * between errors that occur on the data line, and those that
+ * occur on the CMD line.
+ */
+
+ /* make sure we clear any preexisting error condition */
+ wp->w_cmd_err = SDA_EOK;
+
+ PUTREG(wp, REG_CMDR, cmdp->sc_index);
+ PUTREG(wp, REG_CMDR, (cmdp->sc_argument >> 24) & 0xff);
+ PUTREG(wp, REG_CMDR, (cmdp->sc_argument >> 16) & 0xff);
+ PUTREG(wp, REG_CMDR, (cmdp->sc_argument >> 8) & 0xff);
+ PUTREG(wp, REG_CMDR, (cmdp->sc_argument) & 0xff);
+
+ /*
+ * Note that while we are waiting for the timer to run out (which
+ * is really short), a timeout or other error interrupt can occur.
+ * We want to know about such error indications, so we have to drop
+ * to the lock so that the interrupt service routine can post the
+ * appropriate error in the w_cmd_err variable.
+ */
+ mutex_exit(&wp->w_lock);
+ drv_usecwait(wp->w_ctime);
+ mutex_enter(&wp->w_lock);
+
+ if ((rv = wp->w_cmd_err) == SDA_EOK) {
+ uint8_t stat;
+ GETIDX(wp, IDX_STATUS, stat);
+ if ((stat & IDX_STATUS_TRAFFIC) != 0) {
+ rv = SDA_ETIME;
+ }
+ }
+
+ /* some commands don't use valid CRC */
+ if ((rv == SDA_ECRC7) && !checkcrc) {
+ rv = SDA_EOK;
+ }
+
+ PUTIDX(wp, IDX_RESET, IDX_RESET_AUTO_INC);
+ PUTREG(wp, REG_IDXR, rstart);
+ while (rwords != 0) {
+ uint32_t v;
+ v = GETREG(wp, REG_DATAR);
+ v <<= 8;
+ v |= GETREG(wp, REG_DATAR);
+ v <<= 8;
+ v |= GETREG(wp, REG_DATAR);
+ v <<= 8;
+ v |= GETREG(wp, REG_DATAR);
+ rwords--;
+ cmdp->sc_response[rwords] = v;
+ }
+ PUTIDX(wp, IDX_RESET, IDX_RESET_AUTO_INC);
+
+ mutex_exit(&wp->w_lock);
+
+
+ return (rv);
+}
+
+void
+wbsd_halt_hw(wbsd_t *wp)
+{
+ /* reset chip and fifo */
+ PUTIDX(wp, IDX_RESET, IDX_RESET_SOFT | IDX_RESET_FIFO);
+
+ /* disable interrupts */
+ PUTREG(wp, REG_EIR, 0);
+
+ /* remove power */
+ SETREG(wp, REG_CSR, CSR_POWER_N);
+}
+
+void
+wbsd_reset_hw(wbsd_t *wp)
+{
+ /* remove power from slot, set LED enable */
+ PUTREG(wp, REG_CSR, CSR_POWER_N);
+
+ /* reset chip and fifo */
+ PUTIDX(wp, IDX_RESET, IDX_RESET_SOFT | IDX_RESET_FIFO);
+
+ /* clear any pending interrupts */
+ (void) GETREG(wp, REG_ISR);
+
+ /* enable card interrupt */
+ PUTREG(wp, REG_EIR, EIR_CARD);
+}
+
+sda_err_t
+wbsd_reset(void *arg)
+{
+ wbsd_t *wp = arg;
+
+ mutex_enter(&wp->w_lock);
+ wp->w_acmd12 = B_FALSE;
+ wp->w_resid = 0;
+ wp->w_data = NULL;
+ wp->w_width = 1;
+
+ if (!wp->w_suspended) {
+ /* reset occurred when we suspended */
+ wbsd_reset_hw(wp);
+ }
+ mutex_exit(&wp->w_lock);
+
+ return (SDA_EOK);
+}
+
+sda_err_t
+wbsd_halt(void *arg)
+{
+ wbsd_t *wp = arg;
+
+ mutex_enter(&wp->w_lock);
+ if (!wp->w_suspended) {
+ wbsd_halt_hw(wp);
+ }
+ mutex_exit(&wp->w_lock);
+
+ return (SDA_EOK);
+}
diff --git a/usr/src/uts/common/io/sdcard/adapters/wbsd/wbsd.h b/usr/src/uts/common/io/sdcard/adapters/wbsd/wbsd.h
new file mode 100644
index 0000000000..b77ef1bd94
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/adapters/wbsd/wbsd.h
@@ -0,0 +1,154 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_SDCARD_WBSD_H
+#define _SYS_SDCARD_WBSD_H
+
+/*
+ * Private header for the Winbond W83L519D series SD controller.
+ */
+
+/*
+ * Direct access registers.
+ */
+#define REG_CMDR 0x00 /* command register */
+#define REG_DFR 0x01 /* data fifo register */
+#define REG_EIR 0x02 /* enable interrupt register */
+#define REG_ISR 0x03 /* interrupt status register */
+#define REG_FSR 0x04 /* fifo status register */
+#define REG_IDXR 0x05 /* index register */
+#define REG_DATAR 0x06 /* data register */
+#define REG_CSR 0x07 /* card status register */
+
+/*
+ * Direct access register values.
+ */
+
+/*
+ * Note that some sources appear to have mixed up the busy and prog
+ * bits. At least on a Tadpole SPARCLE the bits seem to work as
+ * defined here, although note that on SPARC hardware there does not
+ * appear to be any kind of DMA support for ebus (ISA).
+ */
+#define EIR_CARD 0x40 /* card interrupt */
+#define EIR_FIFO 0x20 /* FIFO threshold reached */
+#define EIR_CRC_ERR 0x10 /* CRC error? */
+#define EIR_TIMEOUT 0x08 /* timeout on CMD or DAT */
+#define EIR_BUSY_END 0x04 /* programming complete */
+#define EIR_PROG_END 0x02 /* busy bit has cleared */
+#define EIR_TC 0x01 /* DMA transfer complete */
+#define EIR_TYPICAL (EIR_CARD | EIR_CRC_ERR | EIR_TIMEOUT)
+#define EIR_WRITE (EIR_TYPICAL | EIR_FIFO | EIR_PROG_END)
+#define EIR_READ (EIR_TYPICAL | EIR_FIFO)
+#define EIR_STOP (EIR_TYPICAL | EIR_BUSY_END)
+
+#define ISR_CARD 0x40 /* card interrupt */
+#define ISR_FIFO 0x20 /* FIFO threshold reached */
+#define ISR_CRC_ERR 0x10 /* CRC7 error */
+#define ISR_TIMEOUT 0x08 /* timeout on CMD or DAT */
+#define ISR_BUSY_END 0x04 /* programming complete */
+#define ISR_PROG_END 0x02 /* busy bit has cleared */
+#define ISR_TC 0x01 /* DMA transfer complete */
+#define ISR_WANTED (ISR_CARD | ISR_FIFO | ISR_CRC_ERR | ISR_TIMEOUT | \
+ ISR_BUSY_END | ISR_PROG_END)
+
+#define FSR_FULL_THRE 0x10
+#define FSR_EMPTY_THRE 0x20
+#define FSR_FULL 0x40
+#define FSR_EMPTY 0x80
+#define FSR_PTR_MASK 0x0F
+
+#define CSR_PRESENT 0x01
+#define CSR_WPROTECT 0x04
+#define CSR_POWER_N 0x10
+#define CSR_MSLED 0x20
+
+/*
+ * Index offsets for indirect registers.
+ */
+#define IDX_CLOCK 0x01 /* clock select */
+#define IDX_BLKSZMSB 0x02 /* data width, block size MSB */
+#define IDX_TAAC 0x03 /* TAAC timing spec */
+#define IDX_NSAC 0x04 /* NSAC timing spec */
+#define IDX_BLKSZLSB 0x05 /* block size LSB */
+#define IDX_RESET 0x06 /* reset */
+#define IDX_DMA 0x07 /* DMA setting */
+#define IDX_THRESH 0x08 /* FIFO threshold control */
+#define IDX_PID_1 0x0E /* product id */
+#define IDX_PID_2 0x0F /* product id */
+#define IDX_STATUS 0x10 /* chip status */
+#define IDX_CMD 0x11 /* first command index */
+#define IDX_RESP_TYPE 0x1E
+#define IDX_RESP_0 0x1F
+#define IDX_RESP_1 0x20
+#define IDX_RESP_2 0x21
+#define IDX_RESP_3 0x22
+#define IDX_RESP_4 0x13
+#define IDX_RESP_5 0x24
+#define IDX_RESP_6 0x25
+#define IDX_RESP_7 0x26
+#define IDX_RESP_8 0x27
+#define IDX_RESP_9 0x28
+#define IDX_RESP_10 0x29
+#define IDX_RESP_11 0x2A
+#define IDX_RESP_12 0x2B
+#define IDX_RESP_13 0x2C
+#define IDX_RESP_14 0x2D
+#define IDX_RESP_15 0x2E
+#define IDX_RESP_16 0x2F
+#define IDX_CRCSTAT 0x30
+
+#define IDX_CLOCK_375K 0 /* clock/128 */
+#define IDX_CLOCK_12M 1 /* clock/4 */
+#define IDX_CLOCK_16M 2 /* clock/3 */
+#define IDX_CLOCK_24M 3 /* clock/2 */
+
+#define IDX_RESET_DAT3_H 0x08
+#define IDX_RESET_FIFO 0x04
+#define IDX_RESET_SOFT 0x02
+#define IDX_RESET_AUTO_INC 0x01 /* not really a reset bit */
+
+#define IDX_DMA_EN 0x02
+#define IDX_DMA_SINGLE 0x01
+
+#define IDX_STATUS_READ 0x80 /* block write in progress */
+#define IDX_STATUS_WRITE 0x40 /* block read in progress */
+#define IDX_STATUS_BUSY 0x20 /* e.g. R1b or R5b */
+#define IDX_STATUS_DAT 0xE0 /* stats using DAT line */
+#define IDX_STATUS_TRAFFIC 0x04 /* cmd line busy */
+#define IDX_STATUS_CMD 0x02
+#define IDX_STATUS_RESP 0x01
+
+#define IDX_RESP_TYPE_LONG 0x01 /* the chip figures these out, btw */
+#define IDX_RESP_TYPE_SHORT 0x00
+
+#define IDX_CRC_MASK 0x1F
+#define IDX_CRC_OK 0x05
+
+#define IDX_THRESH_MASK 0x0F /* threshold value (may not work) */
+#define IDX_THRESH_FULL 0x10 /* enable threshold full */
+#define IDX_THRESH_EMPTY 0x20 /* enable threshold empty */
+
+#endif /* _SYS_SDCARD_WBSD_H */
diff --git a/usr/src/uts/common/io/sdcard/impl/mapfile b/usr/src/uts/common/io/sdcard/impl/mapfile
new file mode 100644
index 0000000000..62bc1454e9
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/impl/mapfile
@@ -0,0 +1,48 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+{
+ global:
+ # consolidation private nexus interfaces
+ sda_host_init_ops;
+ sda_host_fini_ops;
+ sda_host_alloc;
+ sda_host_free;
+ sda_host_attach;
+ sda_host_detach;
+ sda_host_detect;
+ sda_host_set_private;
+ sda_host_transfer;
+ sda_host_fault;
+ sda_host_log;
+
+ # project private interfaces used by sdcard
+ sda_mem_init;
+ sda_mem_fini;
+
+ local:
+ *;
+};
diff --git a/usr/src/uts/common/io/sdcard/impl/sda_cmd.c b/usr/src/uts/common/io/sdcard/impl/sda_cmd.c
new file mode 100644
index 0000000000..4524e3f03c
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/impl/sda_cmd.c
@@ -0,0 +1,355 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * SD card common framework. This module provides most of the common
+ * functionality so that SecureDigital host adapters and client devices
+ * (such as the sdcard driver) can share common code.
+ *
+ * NB that this file contains a fair bit of non-DDI compliant code.
+ * But writing a nexus driver would be impossible to do with only DDI
+ * compliant interfaces.
+ */
+
+#include <sys/types.h>
+#include <sys/kmem.h>
+#include <sys/sysmacros.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/sunndi.h>
+#include <sys/sdcard/sda_impl.h>
+
+/*
+ * Types and Structures.
+ */
+
+typedef struct sda_cmd_impl {
+ struct sda_cmd c_public;
+
+ /*
+ * Implementation private stuff.
+ */
+ sda_slot_t *c_slot;
+ kmutex_t c_lock;
+ kcondvar_t c_cv;
+ list_node_t c_list;
+ sda_err_t c_errno;
+
+ sda_index_t c_acmd; /* saved acmd */
+ sda_rtype_t c_artype; /* saved rtype */
+ uint32_t c_aarg; /* saved argument */
+
+ void (*c_done)(struct sda_cmd *);
+ void *c_private;
+} sda_cmd_impl_t;
+
+#define c_index c_public.sc_index
+#define c_argument c_public.sc_argument
+#define c_rtype c_public.sc_rtype
+#define c_response c_public.sc_response
+#define c_blksz c_public.sc_blksz
+#define c_nblks c_public.sc_nblks
+#define c_resid c_public.sc_resid
+#define c_flags c_public.sc_flags
+#define c_ndmac c_public.sc_ndmac
+#define c_dmacs c_public.sc_dmacs
+#define c_kvaddr c_public.sc_kvaddr
+
+/*
+ * Local Prototypes.
+ */
+
+static void sda_cmd_wait(sda_cmd_t *);
+static int sda_cmd_ctor(void *, void *, int);
+static void sda_cmd_dtor(void *, void *);
+
+/*
+ * Static Variables.
+ */
+
+static kmem_cache_t *sda_cmd_cache;
+
+/*
+ * Macros.
+ */
+
+#define CIP(cmdp) ((sda_cmd_impl_t *)(void *)cmdp)
+
+/*
+ * Implementation.
+ */
+
+void
+sda_cmd_init(void)
+{
+ sda_cmd_cache = kmem_cache_create("sda_cmd_cache",
+ sizeof (struct sda_cmd_impl), 0, sda_cmd_ctor, sda_cmd_dtor,
+ NULL, NULL, NULL, 0);
+}
+
+void
+sda_cmd_fini(void)
+{
+ kmem_cache_destroy(sda_cmd_cache);
+}
+
+void
+sda_cmd_list_init(list_t *list)
+{
+ list_create(list, sizeof (struct sda_cmd_impl),
+ offsetof(struct sda_cmd_impl, c_list));
+}
+
+void
+sda_cmd_list_fini(list_t *list)
+{
+ list_destroy(list);
+}
+
+/*ARGSUSED1*/
+int
+sda_cmd_ctor(void *cbuf, void *arg, int kmflags)
+{
+ sda_cmd_impl_t *c = cbuf;
+
+ mutex_init(&c->c_lock, NULL, MUTEX_DRIVER, NULL);
+ cv_init(&c->c_cv, NULL, CV_DRIVER, NULL);
+ return (0);
+}
+
+/*ARGSUSED1*/
+void
+sda_cmd_dtor(void *cbuf, void *arg)
+{
+ sda_cmd_impl_t *c = cbuf;
+
+ cv_destroy(&c->c_cv);
+ mutex_destroy(&c->c_lock);
+}
+
+void *
+sda_cmd_data(sda_cmd_t *cmdp)
+{
+ return (CIP(cmdp)->c_private);
+}
+
+sda_err_t
+sda_cmd_errno(sda_cmd_t *cmdp)
+{
+ return (CIP(cmdp)->c_errno);
+}
+
+void
+sda_cmd_notify(sda_cmd_t *cmdp, uint16_t flags, sda_err_t errno)
+{
+ sda_cmd_impl_t *c = CIP(cmdp);
+
+ /*
+ * Now we need to make sure that we wake anyone waiting on this
+ * command to complete, if it is complete.
+ */
+ mutex_enter(&c->c_lock);
+ c->c_flags &= ~(flags);
+ /*
+ * Don't overwrite an earlier error.
+ */
+ if (c->c_errno == SDA_EOK) {
+ c->c_errno = errno;
+ }
+ if ((c->c_flags & (SDA_CMDF_BUSY | SDA_CMDF_DAT)) == 0) {
+
+ if (c->c_done != NULL) {
+ mutex_exit(&c->c_lock);
+ c->c_done(cmdp);
+ } else {
+ cv_broadcast(&c->c_cv);
+ mutex_exit(&c->c_lock);
+ }
+ } else {
+ mutex_exit(&c->c_lock);
+ }
+}
+
+void
+sda_cmd_wait(sda_cmd_t *cmdp)
+{
+ sda_cmd_impl_t *c = CIP(cmdp);
+
+ mutex_enter(&c->c_lock);
+ while ((c->c_flags & (SDA_CMDF_BUSY | SDA_CMDF_DAT)) != 0)
+ cv_wait(&c->c_cv, &c->c_lock);
+ mutex_exit(&c->c_lock);
+}
+
+void
+sda_cmd_submit(sda_slot_t *slot, sda_cmd_t *cmdp, void (*done)(sda_cmd_t *))
+{
+ sda_cmd_impl_t *c = CIP(cmdp);
+ sda_err_t errno = 0;
+
+ mutex_enter(&c->c_lock);
+ c->c_done = done;
+ c->c_flags |= SDA_CMDF_BUSY;
+ mutex_exit(&c->c_lock);
+
+ sda_slot_enter(slot);
+
+ /* checks for cases where the slot can't accept the command */
+ if (slot->s_failed) {
+ errno = SDA_EFAULT;
+ }
+ if (!slot->s_inserted) {
+ errno = SDA_ENODEV;
+ }
+ if (errno != SDA_EOK) {
+ sda_slot_exit(slot);
+ /* fail it synchronously */
+ sda_cmd_notify(cmdp, SDA_CMDF_DAT | SDA_CMDF_BUSY, errno);
+ return;
+ }
+
+ list_insert_tail(&slot->s_cmdlist, c);
+ sda_slot_exit(slot);
+
+ sda_slot_wakeup(slot);
+}
+
+void
+sda_cmd_resubmit_acmd(sda_slot_t *slot, sda_cmd_t *cmdp)
+{
+ sda_cmd_impl_t *c = CIP(cmdp);
+
+ ASSERT(sda_slot_owned(slot));
+
+ c->c_index = c->c_acmd;
+ c->c_argument = c->c_aarg;
+ c->c_rtype = c->c_artype;
+ c->c_acmd = 0;
+
+ list_insert_head(&slot->s_cmdlist, c);
+}
+
+sda_cmd_t *
+sda_cmd_alloc(sda_slot_t *slot, sda_index_t index, uint32_t argument,
+ sda_rtype_t rtype, void *data, int kmflag)
+{
+ sda_cmd_impl_t *c;
+
+ c = kmem_cache_alloc(sda_cmd_cache, kmflag);
+ if (c == NULL) {
+ return (NULL);
+ }
+ c->c_index = index;
+ c->c_rtype = rtype;
+ c->c_argument = argument;
+ c->c_resid = 0;
+ c->c_nblks = 0;
+ c->c_blksz = 0;
+
+ c->c_kvaddr = 0;
+ c->c_ndmac = 0;
+ c->c_dmacs = NULL;
+ c->c_flags = 0;
+
+ c->c_slot = slot;
+ c->c_errno = SDA_EOK;
+ c->c_done = NULL;
+ c->c_private = data;
+ c->c_acmd = 0;
+
+ return (&(c->c_public));
+}
+
+sda_cmd_t *
+sda_cmd_alloc_acmd(sda_slot_t *slot, sda_index_t index, uint32_t argument,
+ sda_rtype_t rtype, void *data, int kmflag)
+{
+ sda_cmd_impl_t *c;
+
+ c = kmem_cache_alloc(sda_cmd_cache, kmflag);
+ if (c == NULL) {
+ return (NULL);
+ }
+ c->c_index = CMD_APP_CMD;
+ c->c_argument = index == ACMD_SD_SEND_OCR ? 0 : slot->s_rca << 16;
+ c->c_rtype = R1;
+ c->c_acmd = index;
+ c->c_artype = rtype;
+ c->c_aarg = argument;
+ c->c_resid = 0;
+ c->c_nblks = 0;
+ c->c_blksz = 0;
+
+ c->c_kvaddr = 0;
+ c->c_ndmac = 0;
+ c->c_dmacs = NULL;
+ c->c_flags = 0;
+
+ c->c_slot = slot;
+ c->c_errno = SDA_EOK;
+ c->c_done = NULL;
+ c->c_private = data;
+
+ return (&(c->c_public));
+}
+
+void
+sda_cmd_free(sda_cmd_t *cmdp)
+{
+ kmem_cache_free(sda_cmd_cache, cmdp);
+}
+
+sda_err_t
+sda_cmd_exec(sda_slot_t *slot, sda_cmd_t *cmdp, uint32_t *resp)
+{
+ int errno;
+
+ if ((cmdp->sc_rtype & Rb) || (cmdp->sc_nblks != 0)) {
+ cmdp->sc_flags |= SDA_CMDF_DAT;
+ }
+ sda_cmd_submit(slot, cmdp, NULL);
+
+ sda_cmd_wait(cmdp);
+
+ if (resp != NULL) {
+ switch (cmdp->sc_rtype) {
+ case R0:
+ break;
+ case R2:
+ resp[0] = cmdp->sc_response[0];
+ resp[1] = cmdp->sc_response[1];
+ resp[2] = cmdp->sc_response[2];
+ resp[3] = cmdp->sc_response[3];
+ break;
+ default:
+ resp[0] = cmdp->sc_response[0];
+ break;
+ }
+ }
+
+ errno = CIP(cmdp)->c_errno;
+
+ return (errno);
+}
diff --git a/usr/src/uts/common/io/sdcard/impl/sda_host.c b/usr/src/uts/common/io/sdcard/impl/sda_host.c
new file mode 100644
index 0000000000..5ffb309fa0
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/impl/sda_host.c
@@ -0,0 +1,237 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * SD card host support. This is the API that host drivers access.
+ */
+
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/cmn_err.h>
+#include <sys/varargs.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/sdcard/sda.h>
+#include <sys/sdcard/sda_impl.h>
+
+/*
+ * Static Variables.
+ */
+
+static struct bus_ops sda_host_bus_ops = {
+ BUSO_REV, /* busops_rev */
+ nullbusmap, /* bus_map */
+ NULL, /* bus_get_intrspec (OBSOLETE) */
+ NULL, /* bus_add_intrspec (OBSOLETE) */
+ NULL, /* bus_remove_intrspec (OBSOLETE) */
+ i_ddi_map_fault, /* bus_map_fault */
+ ddi_dma_map, /* bus_dma_map */
+ ddi_dma_allochdl, /* bus_dma_allochdl */
+ ddi_dma_freehdl, /* bus_dma_freehdl */
+ ddi_dma_bindhdl, /* bus_dma_bindhdl */
+ ddi_dma_unbindhdl, /* bus_dma_unbindhdl */
+ ddi_dma_flush, /* bus_dma_flush */
+ ddi_dma_win, /* bus_dma_win */
+ ddi_dma_mctl, /* bus_dma_ctl */
+ sda_nexus_bus_ctl, /* bus_ctl */
+ ddi_bus_prop_op, /* bus_prop_op */
+ NULL, /* bus_get_eventcookie */
+ NULL, /* bus_add_eventcall */
+ NULL, /* bus_remove_eventcall */
+ NULL, /* bus_post_event */
+ NULL, /* bus_intr_ctl (OBSOLETE) */
+ NULL, /* sda_nexus_bus_config, */ /* bus_config */
+ NULL, /* sda_nexus_bus_unconfig, */ /* bus_unconfig */
+ NULL, /* bus_fm_init */
+ NULL, /* bus_fm_fini */
+ NULL, /* bus_fm_access_enter */
+ NULL, /* bus_fm_access_exit */
+ NULL, /* bus_power */
+ NULL, /* bus_intr_op */
+};
+
+static struct cb_ops sda_host_cb_ops = {
+ sda_nexus_open, /* cb_open */
+ sda_nexus_close, /* cb_close */
+ nodev, /* cb_strategy */
+ nodev, /* cb_print */
+ nodev, /* cb_dump */
+ nodev, /* cb_read */
+ nodev, /* cb_write */
+ sda_nexus_ioctl, /* cb_ioctl */
+ nodev, /* cb_devmap */
+ nodev, /* cb_mmap */
+ nodev, /* cb_segmap */
+ nochpoll, /* cb_poll */
+ ddi_prop_op, /* cb_prop_op */
+ NULL, /* cb_str */
+ D_MP, /* cb_flag */
+ CB_REV, /* cb_rev */
+ nodev, /* cb_aread */
+ nodev, /* cb_awrite */
+};
+
+/*
+ * Implementation.
+ */
+
+void
+sda_host_init_ops(struct dev_ops *devops)
+{
+ devops->devo_getinfo = sda_nexus_getinfo;
+ devops->devo_cb_ops = &sda_host_cb_ops;
+ devops->devo_bus_ops = &sda_host_bus_ops;
+}
+
+void
+sda_host_fini_ops(struct dev_ops *devops)
+{
+ devops->devo_bus_ops = NULL;
+}
+
+sda_host_t *
+sda_host_alloc(dev_info_t *dip, int nslot, sda_ops_t *ops, ddi_dma_attr_t *dma)
+{
+ sda_host_t *h;
+ int i;
+
+ if (ops->so_version != SDA_OPS_VERSION) {
+ return (NULL);
+ }
+
+ h = kmem_zalloc(sizeof (*h), KM_SLEEP);
+ h->h_nslot = nslot;
+ h->h_slots = kmem_zalloc(sizeof (sda_slot_t) * nslot, KM_SLEEP);
+ h->h_dma = dma;
+ h->h_dip = dip;
+
+ /* initialize each slot */
+ for (i = 0; i < nslot; i++) {
+ sda_slot_t *slot = &h->h_slots[i];
+
+ slot->s_host = h;
+ slot->s_slot_num = i;
+ slot->s_ops = *ops;
+
+ sda_slot_init(slot);
+ }
+
+ return (h);
+}
+
+void
+sda_host_free(sda_host_t *h)
+{
+ int i;
+
+ for (i = 0; i < h->h_nslot; i++) {
+ sda_slot_fini(&h->h_slots[i]);
+ }
+
+ kmem_free(h->h_slots, sizeof (sda_slot_t) * h->h_nslot);
+ kmem_free(h, sizeof (*h));
+}
+
+void
+sda_host_set_private(sda_host_t *h, int num, void *private)
+{
+ h->h_slots[num].s_prv = private;
+}
+
+int
+sda_host_attach(sda_host_t *h)
+{
+ int i;
+
+ /*
+ * Attach slots.
+ */
+ for (i = 0; i < h->h_nslot; i++) {
+
+ sda_slot_attach(&h->h_slots[i]);
+
+ /*
+ * Initiate card detection.
+ */
+ sda_host_detect(h, i);
+ }
+
+ /*
+ * Register (create) nexus minor nodes.
+ */
+ sda_nexus_register(h);
+
+ return (DDI_SUCCESS);
+}
+
+void
+sda_host_detach(sda_host_t *h)
+{
+ int i;
+
+ /*
+ * Unregister nexus minor nodes.
+ */
+ sda_nexus_unregister(h);
+
+ /*
+ * Detach slots.
+ */
+ for (i = 0; i < h->h_nslot; i++) {
+ sda_slot_detach(&h->h_slots[i]);
+ }
+}
+
+void
+sda_host_transfer(sda_host_t *h, int num, sda_err_t errno)
+{
+ sda_slot_transfer(&h->h_slots[num], errno);
+}
+
+void
+sda_host_detect(sda_host_t *h, int num)
+{
+ sda_slot_detect(&h->h_slots[num]);
+}
+
+void
+sda_host_fault(sda_host_t *h, int num, sda_fault_t fail)
+{
+ sda_slot_fault(&h->h_slots[num], fail);
+}
+
+void
+sda_host_log(sda_host_t *h, int snum, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (h != NULL) {
+ sda_slot_log(&h->h_slots[snum], fmt, ap);
+ } else {
+ sda_slot_log(NULL, fmt, ap);
+ }
+ va_end(ap);
+}
diff --git a/usr/src/uts/common/io/sdcard/impl/sda_init.c b/usr/src/uts/common/io/sdcard/impl/sda_init.c
new file mode 100644
index 0000000000..e6cfce429d
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/impl/sda_init.c
@@ -0,0 +1,654 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * SD card initialization support.
+ */
+
+#include <sys/types.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/sdcard/sda.h>
+#include <sys/sdcard/sda_impl.h>
+
+
+/*
+ * Local Prototypes.
+ */
+
+static sda_err_t sda_init_mmc(sda_slot_t *);
+static sda_err_t sda_init_sdio(sda_slot_t *);
+static sda_err_t sda_init_sdmem(sda_slot_t *);
+static sda_err_t sda_init_cmd(sda_slot_t *, sda_index_t, uint32_t,
+ sda_rtype_t, uint32_t *);
+static sda_err_t sda_init_acmd(sda_slot_t *, sda_index_t, uint32_t,
+ sda_rtype_t, uint32_t *);
+static sda_err_t sda_init_blocklen(sda_slot_t *);
+static sda_err_t sda_init_width(sda_slot_t *);
+static sda_err_t sda_init_rca(sda_slot_t *);
+static sda_err_t sda_init_ifcond(sda_slot_t *);
+static sda_err_t sda_init_highspeed(sda_slot_t *);
+static sda_err_t sda_init_switch(sda_slot_t *, uint8_t, uint8_t, uint8_t,
+ uint8_t *);
+static void sda_init_clock(sda_slot_t *, uint32_t);
+
+/*
+ * Implementation.
+ */
+sda_err_t
+sda_init_cmd(sda_slot_t *slot, sda_index_t cmd, uint32_t arg,
+ sda_rtype_t rtype, uint32_t *resp)
+{
+ sda_cmd_t *cmdp;
+ sda_err_t errno;
+
+ cmdp = sda_cmd_alloc(slot, cmd, arg, rtype, NULL, KM_SLEEP);
+
+ cmdp->sc_flags |= SDA_CMDF_INIT;
+
+ errno = sda_cmd_exec(slot, cmdp, resp);
+
+ sda_cmd_free(cmdp);
+
+ return (errno);
+}
+
+sda_err_t
+sda_init_acmd(sda_slot_t *slot, sda_index_t cmd, uint32_t arg,
+ sda_rtype_t rtype, uint32_t *resp)
+{
+ sda_cmd_t *cmdp;
+ sda_err_t errno;
+
+ cmdp = sda_cmd_alloc_acmd(slot, cmd, arg, rtype, NULL, KM_SLEEP);
+
+ cmdp->sc_flags |= SDA_CMDF_INIT;
+
+ errno = sda_cmd_exec(slot, cmdp, resp);
+
+ sda_cmd_free(cmdp);
+
+ return (errno);
+}
+
+sda_err_t
+sda_init_sdio(sda_slot_t *slot)
+{
+ slot->s_num_io = 0;
+
+ /*
+ * TODO: SDIO: We need to initialize the SDIO OCR register using
+ * the special CMD_IO_SEND_OCR (CMD5) command.
+ */
+ return (SDA_EOK);
+}
+
+sda_err_t
+sda_init_sdmem(sda_slot_t *slot)
+{
+ uint32_t ocr;
+ int count;
+
+ slot->s_flags &= ~SLOTF_SDMEM;
+
+ /*
+ * Try sending the ACMD41 to query the OCR (Op Cond Register).
+ */
+ if (sda_init_acmd(slot, ACMD_SD_SEND_OCR, 0, R3, &ocr) != SDA_EOK) {
+ /*
+ * Card failed to respond to query, not an SD card?
+ * We send GO_IDLE to clear any error status on the
+ * card.
+ */
+ (void) sda_init_cmd(slot, CMD_GO_IDLE, 0, R0, NULL);
+ return (SDA_EOK);
+ }
+
+ /*
+ * Now we have to send our OCR value, along with the HCS (High
+ * Capacity Support) bit. The HCS bit is required, to
+ * activate high capacity cards. We only set the HCS bit if
+ * the card responded to CMD8 (SEND_IFCOND), indicating that
+ * it supports the new protocol.
+ *
+ * Note that the HCS bit occupies the same location as the CCS bit
+ * in the response.
+ */
+ if ((ocr & slot->s_cur_ocr) == 0) {
+ sda_slot_err(slot, "SD card not compatible with host");
+ return (SDA_ENOTSUP);
+ }
+ /* set the HCS bit if its a ver 2.00 card */
+ if (slot->s_flags & SLOTF_IFCOND) {
+ ocr |= OCR_CCS;
+ }
+
+ /* make sure card is powered up */
+ for (count = 1000000; count != 0; count -= 10000) {
+ uint32_t r3;
+
+ if (sda_init_acmd(slot, ACMD_SD_SEND_OCR, ocr, R3, &r3) != 0) {
+ sda_slot_err(slot, "SD card failed to power up");
+ return (SDA_ENOTSUP);
+ }
+
+ /* Now check the busy bit */
+ if (r3 & OCR_POWER_UP) {
+ slot->s_flags |= SLOTF_SDMEM;
+ if ((slot->s_flags & SLOTF_IFCOND) &&
+ (r3 & OCR_CCS)) {
+ slot->s_flags |= SLOTF_SDHC;
+ } else {
+ slot->s_flags &= ~SLOTF_SDHC;
+ }
+ return (0);
+ }
+
+ drv_usecwait(10000);
+ }
+
+ sda_slot_err(slot, "SD card timed out during power up");
+ return (SDA_ETIME);
+}
+
+sda_err_t
+sda_init_mmc(sda_slot_t *slot)
+{
+ uint32_t ocr;
+ int count;
+
+ slot->s_flags &= ~SLOTF_MMC;
+
+ /*
+ * If the card has already been identified as an SD card, then
+ * cannot also be an MMC card, so don't probe it as such.
+ */
+ if (slot->s_flags & SLOTF_SD) {
+ return (SDA_EOK);
+ }
+
+ /*
+ * Try sending the CMD1 to query the OCR.
+ */
+ if (sda_init_cmd(slot, CMD_SEND_OCR, 0, R3, &ocr) != 0) {
+ /*
+ * Card failed to respond to query, not an MMC card?
+ * We send GO_IDLE to clear any error status on the
+ * card.
+ */
+ (void) sda_init_cmd(slot, CMD_GO_IDLE, 0, R0, NULL);
+ return (SDA_EOK);
+ }
+
+ if ((ocr & slot->s_cur_ocr) == 0) {
+ sda_slot_err(slot, "MMC card not compatible with host");
+ return (SDA_ENOTSUP);
+ }
+
+ /* make sure card is powered up */
+ for (count = 1000000; count != 0; count -= 10000) {
+ uint32_t r3;
+
+ if (sda_init_cmd(slot, CMD_SEND_OCR, ocr, R3, &r3) != 0) {
+ sda_slot_err(slot, "MMC card failed to power up");
+ return (SDA_ENOTSUP);
+ }
+
+ /* Now check the busy bit */
+ if (r3 & OCR_POWER_UP) {
+ slot->s_flags |= SLOTF_MMC;
+ return (SDA_EOK);
+ }
+
+ drv_usecwait(10000);
+ }
+
+ sda_slot_err(slot, "MMC card timed out during power up");
+ return (SDA_ETIME);
+}
+
+sda_err_t
+sda_init_card(sda_slot_t *slot)
+{
+ int rv;
+ uint32_t resp;
+ uint32_t val;
+
+ /*
+ * Power off slot/card initially.
+ */
+ sda_slot_power_off(slot);
+
+ /*
+ * Apply initial power to the slot.
+ */
+ if ((rv = sda_slot_power_on(slot)) != 0) {
+ return (rv);
+ }
+
+ /*
+ * First enable the clock, but only at 400 kHz. All cards are
+ * supposed to be able to operate between this speed and 100
+ * kHz, and all hosts must be able to pick a speed between 100
+ * kHz and 400 kHz.
+ *
+ * Once we know what the device can support, then we speed up.
+ */
+ sda_init_clock(slot, 400000);
+
+ if ((rv = sda_init_ifcond(slot)) != SDA_EOK) {
+ goto done;
+ }
+
+ if (((rv = sda_init_sdio(slot)) != SDA_EOK) ||
+ ((rv = sda_init_sdmem(slot)) != SDA_EOK) ||
+ ((rv = sda_init_mmc(slot)) != SDA_EOK)) {
+
+ /* message will already have been logged */
+ goto done;
+ }
+
+ if ((slot->s_flags & (SLOTF_MEMORY | SLOTF_SDIO)) == 0) {
+ sda_slot_err(slot, "Unidentified card type");
+ rv = SDA_ENOTSUP;
+ goto done;
+ }
+
+ /*
+ * Memory cards need to obtain their CID before getting their RCA.
+ * This is a requirement for the state transitions... they go thru
+ * the ident state, unlike SDIO cards.
+ */
+ if (slot->s_flags & SLOTF_MEMORY) {
+ rv = sda_init_cmd(slot, CMD_BCAST_CID, 0, R2, slot->s_rcid);
+ if (rv != SDA_EOK) {
+ sda_slot_err(slot, "Failed getting card CID (%d)", rv);
+ goto done;
+ }
+ }
+
+ if ((rv = sda_init_rca(slot)) != SDA_EOK) {
+ goto done;
+ }
+
+ slot->s_maxclk = 0xffffffffU; /* special sentinel */
+
+ /*
+ * Figure out card supported bus width and speed.
+ *
+ * TODO: SDIO: For IO cards, we have to check what speed the card
+ * supports by looking in the CCCR_CAPAB register. (SDIO cards
+ * can go low-speed only, full-speed, or high-speed.)
+ */
+ if (slot->s_flags & SLOTF_MEMORY) {
+
+ /*
+ * We need to obtain the CSD.
+ */
+ rv = sda_init_cmd(slot, CMD_SEND_CSD, slot->s_rca << 16, R2,
+ slot->s_rcsd);
+ if (rv != 0) {
+ sda_slot_err(slot, "Failed getting card CSD (%d)", rv);
+ goto done;
+ }
+
+ /*
+ * Calculate the maxclock.
+ */
+ slot->s_maxclk = sda_mem_maxclk(slot);
+ }
+ if (((slot->s_flags & SLOTF_SDMEM) != 0) &&
+ ((slot->s_caps & SLOT_CAP_4BITS) != 0)) {
+ slot->s_flags |= SLOTF_4BITS;
+ }
+ if (slot->s_flags & SLOTF_SDIO) {
+ sda_slot_debug(slot, "Wide SDIO bus not yet supported");
+ slot->s_flags &= ~SLOTF_4BITS;
+ }
+
+ /*
+ * Now select the card.
+ */
+ if ((rv = sda_init_cmd(slot, CMD_SELECT_CARD, slot->s_rca << 16,
+ R1b, &resp)) != SDA_EOK) {
+ sda_slot_err(slot, "Failed selecting card (%d, %x)", rv, resp);
+ goto done;
+ }
+
+ if ((rv = sda_init_highspeed(slot)) != SDA_EOK) {
+ goto done;
+ }
+
+ sda_init_clock(slot, slot->s_maxclk);
+
+ /*
+ * Lets go to 4-bit bus mode, if possible.
+ */
+ if ((rv = sda_init_width(slot)) != SDA_EOK) {
+ goto done;
+ }
+
+ if ((rv = sda_init_blocklen(slot)) != SDA_EOK) {
+ goto done;
+ }
+
+ /* note if a card is writable */
+ if ((sda_getprop(slot, SDA_PROP_WPROTECT, &val) == SDA_EOK) &&
+ (val == 0)) {
+ slot->s_flags |= SLOTF_WRITABLE;
+ }
+
+ rv = SDA_EOK;
+
+done:
+
+ sda_slot_enter(slot);
+ slot->s_init = B_FALSE;
+ sda_slot_exit(slot);
+
+ sda_slot_wakeup(slot);
+
+ return (rv);
+}
+
+sda_err_t
+sda_init_blocklen(sda_slot_t *slot)
+{
+ int rv;
+ uint32_t resp;
+
+ if ((slot->s_flags & SLOTF_MEMORY) == 0) {
+ return (SDA_EOK);
+ }
+
+ /*
+ * All memory cards support block sizes of 512. Full stop.
+ */
+ rv = sda_init_cmd(slot, CMD_SET_BLOCKLEN, 512, R1, &resp);
+ if (rv != SDA_EOK) {
+ sda_slot_err(slot, "Unable to set block length (%d, %x)",
+ rv, resp);
+ }
+ return (rv);
+}
+
+void
+sda_init_clock(sda_slot_t *slot, uint32_t hz)
+{
+ int rv;
+ uint32_t act;
+
+ /*
+ * Note that at no time is a failure programming the clock
+ * itself necessarily a fatal error. Although if the clock
+ * wasn't programmed, other things will probably not work during
+ * initialization.
+ */
+
+ if ((rv = sda_setprop(slot, SDA_PROP_CLOCK, hz)) != SDA_EOK) {
+ sda_slot_err(slot, "Failed setting clock to %u Hz (%d)",
+ hz, rv);
+ /* XXX: FMA fail the slot */
+ return;
+ }
+
+ if ((rv = sda_getprop(slot, SDA_PROP_CLOCK, &act)) == SDA_EOK) {
+ sda_slot_debug(slot, "Clock set to %u Hz (requested %u Hz)",
+ act, hz);
+ } else {
+ sda_slot_debug(slot, "Clock frequency unknown (good luck).");
+ }
+
+ /*
+ * For now, just wait 10msec for clocks to stabilize to the
+ * card. (Is this really necessary?)
+ */
+ delay(drv_usectohz(10000));
+}
+
+sda_err_t
+sda_init_width(sda_slot_t *slot)
+{
+ int rv;
+ uint32_t resp;
+
+ /*
+ * Spec says we should command the card first.
+ */
+
+ rv = sda_setprop(slot, SDA_PROP_BUSWIDTH, 1);
+ if (rv != SDA_EOK) {
+ sda_slot_err(slot, "Unable to set slot 1-bit mode (%d)", rv);
+ return (rv);
+ }
+
+ if ((slot->s_flags & SLOTF_4BITS) == 0) {
+ return (SDA_EOK);
+ }
+
+ /*
+ * TODO: SDIO: SDIO cards set the CCCR_BUS_WIDTH
+ * and CCCR_CD_DISABLE bits here.
+ */
+
+ /*
+ * If we're going to use all 4 pins, we really need to disconnect
+ * the card pullup resistor. A consquence of this, is that hosts
+ * which use that resistor for detection must not claim to support
+ * 4-bit bus mode. This is a limitation of our implementation.
+ */
+ rv = sda_init_acmd(slot, ACMD_SET_CLR_CARD_DETECT, 1, R1, &resp);
+ if (rv != SDA_EOK) {
+ sda_slot_err(slot,
+ "Unable disconnect DAT3 resistor on card (%d, %x)",
+ rv, resp);
+ /* non-fatal error, muddle on */
+ return (SDA_EOK);
+ }
+
+ rv = sda_init_acmd(slot, ACMD_SET_BUS_WIDTH, 2, R1, &resp);
+ if (rv != SDA_EOK) {
+ sda_slot_err(slot, "Unable to set card 4-bit mode (%d, %x)",
+ rv, resp);
+ /* non-fatal error, muddle on */
+ return (SDA_EOK);
+ }
+
+ rv = sda_setprop(slot, SDA_PROP_BUSWIDTH, 4);
+ if (rv != SDA_EOK) {
+ /*
+ * This is bad news. We've already asked for the card to
+ * to use 4-bit mode, but the host is not complying. It
+ * shouldn't ever happen, so we just error out.
+ */
+ sda_slot_err(slot, "Unable to set slot 4-bit mode (%d)", rv);
+ }
+
+ return (rv);
+}
+
+sda_err_t
+sda_init_ifcond(sda_slot_t *slot)
+{
+ int rv;
+ int tries;
+ uint32_t vchk;
+ uint32_t resp;
+
+ /*
+ * Try SEND_IF_COND. Note that this assumes that the host is
+ * supplying 2.7 - 3.6 voltage range. The standard is not
+ * defined for any other ranges.
+ */
+ vchk = R7_VHS_27_36V | R7_PATTERN;
+
+ /* we try this a few times, just to be sure */
+ for (tries = 0; tries < 5; tries++) {
+ rv = sda_init_cmd(slot, CMD_GO_IDLE, 0, R0, NULL);
+ if (rv != SDA_EOK) {
+ sda_slot_err(slot, "Failed to IDLE card");
+ return (rv);
+ }
+
+ rv = sda_init_cmd(slot, CMD_SEND_IF_COND, vchk, R7, &resp);
+ if (rv == SDA_EOK) {
+ break;
+ }
+ delay(drv_usectohz(10000));
+ }
+
+ if (rv != SDA_EOK) {
+ (void) sda_init_cmd(slot, CMD_GO_IDLE, 0, R0, NULL);
+ slot->s_flags &= ~SLOTF_IFCOND;
+
+ } else if (resp != vchk) {
+ sda_slot_err(slot, "Card voltages incompatible! (%x)", resp);
+ return (SDA_ENOTSUP);
+
+ } else {
+ /* SDHC compliant */
+ slot->s_flags |= SLOTF_IFCOND;
+ }
+
+ return (SDA_EOK);
+}
+
+sda_err_t
+sda_init_rca(sda_slot_t *slot)
+{
+ int rv;
+ int tries;
+ uint32_t resp;
+
+ /*
+ * Program the RCA. Note that MMC has a different mechanism
+ * for this.
+ */
+ for (tries = 0; tries < 10; tries++) {
+
+ if (slot->s_flags & SLOTF_MMC) {
+ /*
+ * For MMC, we push the RCA to the MMC. We
+ * arbitrarily start at 0x100, and add from
+ * there.
+ */
+ rv = sda_init_cmd(slot, CMD_SEND_RCA,
+ (0x100 + tries) << 16, R1, NULL);
+ if (rv == SDA_EOK)
+ slot->s_rca = 0x100 + tries;
+ } else {
+ /*
+ * For SDcard, we are basically asking the
+ * card to propose a value. It *may* propose
+ * a value of zero, in which case we will have
+ * to ask again.
+ */
+ rv = sda_init_cmd(slot, CMD_SEND_RCA, 0, R6, &resp);
+ if (rv == SDA_EOK)
+ slot->s_rca = resp >> 16;
+ }
+ if ((rv == SDA_EOK) && (slot->s_rca != 0)) {
+ sda_slot_debug(slot, "Relative address (RCA) = %d",
+ slot->s_rca);
+ return (SDA_EOK);
+ }
+ }
+
+ sda_slot_err(slot, "Unable to negotiate a suitable RCA (%d)", rv);
+ return ((rv != SDA_EOK) ? rv : SDA_EINVAL);
+}
+
+sda_err_t
+sda_init_switch(sda_slot_t *slot, uint8_t mode, uint8_t grp, uint8_t val,
+ uint8_t *data)
+{
+ sda_cmd_t *cmdp;
+ sda_err_t errno;
+ uint32_t arg;
+
+ /*
+ * The spec says we should leave unselected groups set to 0xf,
+ * to prevent inadvertent changes.
+ */
+ arg = (mode << 31) | 0xffffff;
+ arg &= ~(0xf << (grp << 2));
+ arg |= (val << (grp << 2));
+
+ cmdp = sda_cmd_alloc(slot, CMD_SWITCH_FUNC, arg, R1, NULL, KM_SLEEP);
+
+ cmdp->sc_flags |= SDA_CMDF_INIT | SDA_CMDF_DAT | SDA_CMDF_READ;
+ cmdp->sc_blksz = 64;
+ cmdp->sc_nblks = 1;
+ cmdp->sc_kvaddr = (void *)data;
+
+ errno = sda_cmd_exec(slot, cmdp, NULL);
+
+ sda_cmd_free(cmdp);
+
+ return (errno);
+
+}
+
+sda_err_t
+sda_init_highspeed(sda_slot_t *slot)
+{
+ uint32_t ccc;
+ uint8_t data[64];
+ sda_err_t rv;
+
+ if ((slot->s_caps & SLOT_CAP_HISPEED) == 0) {
+ return (SDA_EOK);
+ }
+ if ((slot->s_flags & SLOTF_SDMEM) == 0) {
+ return (SDA_EOK);
+ }
+ ccc = sda_mem_getbits(slot->s_rcsd, 95, 12);
+ if ((ccc & (1 << 10)) == 0) {
+ return (SDA_EOK);
+ }
+
+ rv = sda_init_switch(slot, 0, 0, 1, data);
+
+ /* these are big-endian bits, bit 401 */
+ if ((rv != SDA_EOK) || ((data[13] & (1 << 1)) == 0)) {
+ return (SDA_EOK);
+ }
+
+ rv = sda_init_switch(slot, 1, 0, 1, data);
+ if (rv != SDA_EOK) {
+ return (SDA_EOK);
+ }
+
+ /* now program the card */
+ rv = sda_setprop(slot, SDA_PROP_HISPEED, 1);
+ if (rv != SDA_EOK) {
+ sda_slot_err(slot, "Failed setting slot to high speed mode");
+ } else {
+ /* the card should now support 50 MHz */
+ slot->s_maxclk = 50000000;
+ }
+
+ return (rv);
+}
diff --git a/usr/src/uts/common/io/sdcard/impl/sda_mem.c b/usr/src/uts/common/io/sdcard/impl/sda_mem.c
new file mode 100644
index 0000000000..336fe291ec
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/impl/sda_mem.c
@@ -0,0 +1,653 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * Memory target support for SDcard.
+ */
+
+#include <sys/types.h>
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/scsi/adapters/blk2scsa.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/sdcard/sda.h>
+#include <sys/sdcard/sda_impl.h>
+
+static int sda_mem_attach(dev_info_t *, ddi_attach_cmd_t);
+static int sda_mem_detach(dev_info_t *, ddi_detach_cmd_t);
+static b2s_err_t sda_mem_b2s_errno(sda_err_t);
+static boolean_t sda_mem_b2s_request(void *, b2s_request_t *);
+static boolean_t sda_mem_b2s_rw(sda_slot_t *, b2s_request_t *);
+static void sda_mem_b2s_done(sda_cmd_t *);
+static void sda_mem_getstring(uint32_t *, char *, int, int);
+static int sda_mem_parse_cid_csd(sda_slot_t *, dev_info_t *);
+static int sda_mem_cmd(sda_slot_t *, uint8_t, uint32_t, uint8_t, uint32_t *);
+
+
+/*
+ * To minimize complexity and reduce layering, we implement almost the
+ * entire memory card driver (sdcard) here. The memory card still
+ * needs to be a separate driver though, due to the requirement to
+ * have both SCSI HBA bus ops and SD bus ops.
+ */
+
+/*
+ * SCSA layer supplies a cb_ops, but we don't want it, because we
+ * don't want to expose a SCSI attachment point. (Our parent handles
+ * the attachment point, the SCSI one would be confusing.) We have to
+ * supply a stubbed out one, to prevent SCSA from trying to create minor
+ * nodes on our behalf.
+ *
+ * Perhaps at some future point we might want to expose a separate set
+ * of ioctls for these nodes, but for now we rely on our parent to do
+ * all that work.
+ */
+static struct cb_ops sda_mem_ops = {
+ nodev, /* cb_open */
+ nodev, /* cb_close */
+ nodev, /* cb_strategy */
+ nodev, /* cb_print */
+ nodev, /* cb_dump */
+ nodev, /* cb_read */
+ nodev, /* cb_write */
+ nodev, /* cb_ioctl */
+ nodev, /* cb_devmap */
+ nodev, /* cb_mmap */
+ nodev, /* cb_segmap */
+ nochpoll, /* cb_chpoll */
+ ddi_prop_op, /* cb_prop_op */
+ NULL, /* cb_stream */
+ D_MP /* cb_flag */
+};
+
+/*
+ * Here are the public functions.
+ */
+void
+sda_mem_init(struct modlinkage *modlp)
+{
+ struct dev_ops *devo;
+
+ devo = ((struct modldrv *)(modlp->ml_linkage[0]))->drv_dev_ops;
+ devo->devo_attach = sda_mem_attach;
+ devo->devo_detach = sda_mem_detach;
+
+ devo->devo_cb_ops = &sda_mem_ops;
+
+ /* it turns out that this can't ever really fail */
+ (void) b2s_mod_init(modlp);
+}
+
+void
+sda_mem_fini(struct modlinkage *modlp)
+{
+ b2s_mod_fini(modlp);
+}
+
+/*
+ * Everything beyond this is private.
+ */
+
+int
+sda_mem_cmd(sda_slot_t *slot, uint8_t cmd, uint32_t arg, uint8_t rtype,
+ uint32_t *resp)
+{
+ sda_cmd_t *cmdp;
+ int errno;
+
+ cmdp = sda_cmd_alloc(slot, cmd, arg, rtype, NULL, KM_SLEEP);
+ if (cmdp == NULL) {
+ return (ENOMEM);
+ }
+ errno = sda_cmd_exec(slot, cmdp, resp);
+ sda_cmd_free(cmdp);
+
+ return (errno);
+}
+
+boolean_t
+sda_mem_b2s_rw(sda_slot_t *slot, b2s_request_t *reqp)
+{
+ sda_cmd_t *cmdp;
+ uint64_t nblks;
+ uint64_t blkno;
+ uint16_t rblen;
+ int rv;
+ uint8_t index;
+ uint16_t flags;
+
+ blkno = reqp->br_lba;
+ nblks = reqp->br_nblks;
+
+ switch (reqp->br_cmd) {
+ case B2S_CMD_READ:
+ if (nblks > 1) {
+ index = CMD_READ_MULTI;
+ flags = SDA_CMDF_DAT | SDA_CMDF_MEM | SDA_CMDF_READ |
+ SDA_CMDF_AUTO_CMD12;
+ } else {
+ index = CMD_READ_SINGLE;
+ flags = SDA_CMDF_DAT | SDA_CMDF_MEM | SDA_CMDF_READ;
+ }
+ break;
+ case B2S_CMD_WRITE:
+ if (nblks > 1) {
+ index = CMD_WRITE_MULTI;
+ flags = SDA_CMDF_DAT | SDA_CMDF_MEM | SDA_CMDF_WRITE |
+ SDA_CMDF_AUTO_CMD12;
+ } else {
+ index = CMD_WRITE_SINGLE;
+ flags = SDA_CMDF_DAT | SDA_CMDF_MEM | SDA_CMDF_WRITE;
+ }
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+
+ cmdp = sda_cmd_alloc(slot, index, blkno << slot->s_bshift,
+ R1, reqp, KM_NOSLEEP);
+ if (cmdp == NULL) {
+ b2s_request_done(reqp, B2S_ENOMEM, 0);
+ return (B_TRUE);
+ }
+
+ if (slot->s_host->h_dma != NULL) {
+ b2s_request_dma(reqp, &cmdp->sc_ndmac, &cmdp->sc_dmacs);
+ cmdp->sc_kvaddr = 0;
+ }
+ if ((slot->s_caps & SLOT_CAP_NOPIO) == 0) {
+ size_t maplen;
+ b2s_request_mapin(reqp, &cmdp->sc_kvaddr, &maplen);
+ cmdp->sc_ndmac = 0;
+ }
+
+ if (nblks == 0) {
+ /*
+ * This is not strictly a failure, but no work to do.
+ * We have to do it late here because we don't want to
+ * by pass the above media readiness checks.
+ */
+ rv = B2S_EOK;
+ goto failed;
+ }
+ if (nblks > 0xffff) {
+ rv = B2S_EINVAL;
+ goto failed;
+ }
+
+ rblen = slot->s_blksz;
+
+ if ((blkno + nblks) > slot->s_nblks) {
+ rv = B2S_EBLKADDR;
+ goto failed;
+ }
+
+ cmdp->sc_rtype = R1;
+ cmdp->sc_blksz = rblen;
+ cmdp->sc_nblks = (uint16_t)nblks;
+ cmdp->sc_index = index;
+ cmdp->sc_flags = flags;
+
+ sda_cmd_submit(slot, cmdp, sda_mem_b2s_done);
+ return (B_TRUE);
+
+failed:
+ sda_cmd_free(cmdp);
+ b2s_request_done(reqp, rv, 0);
+ return (B_TRUE);
+}
+
+boolean_t
+sda_mem_b2s_format(sda_slot_t *slot, b2s_request_t *reqp)
+{
+ sda_cmd_t *cmdp;
+ int rv;
+
+
+ rv = sda_mem_cmd(slot, CMD_ERASE_START, 0, R1, NULL);
+ if (rv != 0) {
+ b2s_request_done(reqp, sda_mem_b2s_errno(rv), 0);
+ return (B_TRUE);
+ }
+ rv = sda_mem_cmd(slot, CMD_ERASE_END, slot->s_nblks - 1, R1, NULL);
+ if (rv != 0) {
+ b2s_request_done(reqp, sda_mem_b2s_errno(rv), 0);
+ return (B_TRUE);
+ }
+
+ cmdp = sda_cmd_alloc(slot, CMD_ERASE, 0, R1b, reqp, KM_NOSLEEP);
+ if (cmdp == NULL) {
+ b2s_request_done(reqp, B2S_ENOMEM, 0);
+ return (B_TRUE);
+ }
+ cmdp->sc_flags = SDA_CMDF_DAT | SDA_CMDF_MEM;
+
+ sda_cmd_submit(slot, cmdp, sda_mem_b2s_done);
+ return (B_TRUE);
+}
+
+b2s_err_t
+sda_mem_b2s_errno(sda_err_t errno)
+{
+ /* the hot path */
+ if (errno == SDA_EOK) {
+ return (B2S_EOK);
+ }
+
+ switch (errno) {
+ case SDA_ENOMEM:
+ return (B2S_ENOMEM);
+ case SDA_ETIME:
+ return (B2S_ETIMEDOUT);
+ case SDA_EWPROTECT:
+ return (B2S_EWPROTECT);
+ case SDA_ESUSPENDED:
+ case SDA_ENODEV:
+ return (B2S_ENOMEDIA);
+ case SDA_EFAULT:
+ case SDA_ECRC7:
+ case SDA_EPROTO:
+ return (B2S_EHARDWARE);
+ case SDA_ERESET:
+ return (B2S_ERESET);
+ case SDA_EIO:
+ case SDA_ERESID:
+ default:
+ return (B2S_EIO);
+ }
+}
+
+void
+sda_mem_b2s_done(sda_cmd_t *cmdp)
+{
+ b2s_request_t *reqp = sda_cmd_data(cmdp);
+ int errno = sda_cmd_errno(cmdp);
+
+ b2s_request_done(reqp, sda_mem_b2s_errno(errno), cmdp->sc_resid);
+ sda_cmd_free(cmdp);
+}
+
+boolean_t
+sda_mem_b2s_request(void *arg, b2s_request_t *reqp)
+{
+ sda_slot_t *slot = arg;
+ int rv;
+
+ switch (reqp->br_cmd) {
+ case B2S_CMD_WRITE:
+ if ((slot->s_flags & SLOTF_WRITABLE) == 0) {
+ rv = B2S_EWPROTECT;
+ } else {
+ return (sda_mem_b2s_rw(slot, reqp));
+ }
+ break;
+
+ case B2S_CMD_READ:
+ return (sda_mem_b2s_rw(slot, reqp));
+
+ case B2S_CMD_INQUIRY:
+ reqp->br_inquiry.inq_vendor = "OSOL";
+ reqp->br_inquiry.inq_product =
+ slot->s_flags & SLOTF_MMC ? "MultiMediaCard" :
+ slot->s_flags & SLOTF_SDHC ? "SDHC Memory Card" :
+ "SD Memory Card";
+ reqp->br_inquiry.inq_revision = "";
+ reqp->br_inquiry.inq_serial = "";
+ rv = B2S_EOK;
+ break;
+
+ case B2S_CMD_GETMEDIA:
+ if (!slot->s_ready) {
+ rv = B2S_ENODEV;
+ } else {
+ reqp->br_media.media_blksz = slot->s_blksz;
+ reqp->br_media.media_nblks = slot->s_nblks;
+ /* detect read-only cards */
+ if (slot->s_flags & SLOTF_WRITABLE) {
+ reqp->br_media.media_flags = 0;
+ } else {
+ reqp->br_media.media_flags =
+ B2S_MEDIA_FLAG_READ_ONLY;
+ }
+ rv = B2S_EOK;
+ }
+ break;
+
+ case B2S_CMD_FORMAT:
+ return (sda_mem_b2s_format(slot, reqp));
+
+ case B2S_CMD_ABORT:
+ sda_slot_mem_reset(slot, SDA_EABORT);
+ rv = B2S_EOK;
+ break;
+
+ case B2S_CMD_RESET:
+ sda_slot_mem_reset(slot, SDA_ERESET);
+ rv = B2S_EOK;
+ break;
+
+ case B2S_CMD_START:
+ case B2S_CMD_STOP:
+ case B2S_CMD_SYNC:
+ rv = B2S_EOK;
+ break;
+
+ case B2S_CMD_LOCK:
+ case B2S_CMD_UNLOCK:
+ default:
+ rv = B2S_ENOTSUP;
+ break;
+ }
+
+ b2s_request_done(reqp, rv, 0);
+ return (B_TRUE);
+}
+
+int
+sda_mem_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ sda_slot_t *slot;
+ b2s_nexus_t *nexus;
+ b2s_nexus_info_t nexinfo;
+ b2s_leaf_info_t leafinfo;
+
+ switch (cmd) {
+ case DDI_ATTACH:
+ if ((slot = ddi_get_parent_data(dip)) == NULL) {
+ return (DDI_FAILURE);
+ }
+
+ if (sda_mem_parse_cid_csd(slot, dip) != DDI_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+
+ nexinfo.nexus_version = B2S_VERSION_0;
+ nexinfo.nexus_private = slot;
+ nexinfo.nexus_dip = dip;
+ nexinfo.nexus_dma_attr = slot->s_host->h_dma;
+ nexinfo.nexus_request = sda_mem_b2s_request;
+
+ nexus = b2s_alloc_nexus(&nexinfo);
+ if (nexus == NULL) {
+ return (DDI_FAILURE);
+ }
+
+ leafinfo.leaf_target = 0;
+ leafinfo.leaf_lun = 0;
+ leafinfo.leaf_flags =
+ B2S_LEAF_REMOVABLE | B2S_LEAF_HOTPLUGGABLE;
+ leafinfo.leaf_unique_id = slot->s_uuid;
+
+ slot->s_leaf = b2s_attach_leaf(nexus, &leafinfo);
+ if (slot->s_leaf == NULL) {
+ b2s_free_nexus(nexus);
+ return (DDI_FAILURE);
+ }
+
+ slot->s_nexus = nexus;
+ if (b2s_attach_nexus(nexus) != DDI_SUCCESS) {
+ slot->s_nexus = NULL;
+ b2s_free_nexus(nexus);
+ return (DDI_FAILURE);
+ }
+ slot->s_nexus = nexus;
+
+ return (DDI_SUCCESS);
+
+
+ case DDI_RESUME:
+ return (DDI_SUCCESS);
+
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+int
+sda_mem_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ sda_slot_t *slot;
+ b2s_nexus_t *nexus;
+
+ switch (cmd) {
+ case DDI_DETACH:
+ if ((slot = ddi_get_parent_data(dip)) == NULL) {
+ return (DDI_FAILURE);
+ }
+ if ((nexus = slot->s_nexus) == NULL) {
+ /* nothing to do */
+ return (DDI_SUCCESS);
+ }
+ if (b2s_detach_nexus(nexus) != DDI_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+ slot->s_nexus = NULL;
+ b2s_free_nexus(nexus);
+ return (DDI_SUCCESS);
+
+ case DDI_SUSPEND:
+ return (DDI_SUCCESS);
+
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+uint32_t
+sda_mem_getbits(uint32_t *resp, int hibit, int len)
+{
+ uint32_t val = 0;
+ uint32_t bit;
+
+ for (bit = hibit; len--; bit--) {
+ val <<= 1;
+ val |= ((resp[bit / 32]) >> (bit % 32)) & 1;
+ }
+ return (val);
+}
+
+void
+sda_mem_getstring(uint32_t *resp, char *s, int hibit, int len)
+{
+ while (len--) {
+ *s++ = sda_mem_getbits(resp, hibit, 8);
+ hibit -= 8;
+ }
+ *s = 0;
+}
+
+uint32_t
+sda_mem_maxclk(sda_slot_t *slot)
+{
+ static const uint32_t mult[16] = {
+ 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80
+ };
+
+ static const uint32_t units[8] = {
+ 10000, 100000, 1000000, 10000000, 0, 0, 0, 0,
+ };
+ uint8_t ts;
+
+ ts = sda_mem_getbits(slot->s_rcsd, 103, 8);
+
+ return ((units[ts & 0x7]) * (mult[(ts >> 3) & 0xf]));
+}
+
+int
+sda_mem_parse_cid_csd(sda_slot_t *slot, dev_info_t *dip)
+{
+ uint32_t *rcid;
+ uint32_t *rcsd;
+ int csdver;
+ uint16_t rblen;
+ uint16_t bshift;
+ uint32_t cmult;
+ uint32_t csize;
+ char date[16];
+ char *dtype;
+
+ rcid = slot->s_rcid;
+ rcsd = slot->s_rcsd;
+
+ csdver = sda_mem_getbits(rcsd, 127, 2);
+
+ if (slot->s_flags & SLOTF_SDMEM) {
+ switch (csdver) {
+ case 0:
+ csize = sda_mem_getbits(rcsd, 73, 12);
+ /* see comment above */
+ rblen = (1 << sda_mem_getbits(rcsd, 83, 4));
+ cmult = (4 << sda_mem_getbits(rcsd, 49, 3));
+ bshift = 9;
+ break;
+ case 1:
+ rblen = 512;
+ csize = sda_mem_getbits(rcsd, 69, 22);
+ cmult = 1024;
+ bshift = 0;
+ break;
+ default:
+ sda_slot_err(slot, "Unknown SD CSD version (%d)",
+ csdver);
+ return (DDI_FAILURE);
+ }
+
+ dtype = slot->s_flags & SLOTF_SDHC ? "sdhc" : "sdcard";
+ slot->s_mfg = sda_mem_getbits(rcid, 127, 8);
+ sda_mem_getstring(rcid, slot->s_oem, 119, 2);
+ sda_mem_getstring(rcid, slot->s_prod, 103, 5);
+ slot->s_majver = sda_mem_getbits(rcid, 63, 4);
+ slot->s_minver = sda_mem_getbits(rcid, 59, 4);
+ slot->s_serial = sda_mem_getbits(rcid, 55, 32);
+ slot->s_year = sda_mem_getbits(rcid, 19, 8) + 2000;
+ slot->s_month = sda_mem_getbits(rcid, 11, 4);
+
+ } else if (slot->s_flags & SLOTF_MMC) {
+ if ((csdver < 1) || (csdver > 2)) {
+ sda_slot_err(slot, "Unknown MMC CSD version (%d)",
+ csdver);
+ return (DDI_FAILURE);
+ }
+
+ dtype = "mmc";
+
+ switch (sda_mem_getbits(rcsd, 125, 4)) {
+ case 0: /* MMC 1.0 - 1.2 */
+ case 1: /* MMC 1.4 */
+ slot->s_mfg = sda_mem_getbits(rcid, 127, 24);
+ slot->s_oem[0] = 0;
+ sda_mem_getstring(rcid, slot->s_prod, 103, 7);
+ slot->s_majver = sda_mem_getbits(rcid, 47, 4);
+ slot->s_minver = sda_mem_getbits(rcid, 43, 4);
+ slot->s_serial = sda_mem_getbits(rcid, 39, 24);
+ break;
+
+ case 2: /* MMC 2.0 - 2.2 */
+ case 3: /* MMC 3.1 - 3.3 */
+ case 4: /* MMC 4.x */
+ slot->s_mfg = sda_mem_getbits(rcid, 127, 8);
+ sda_mem_getstring(rcid, slot->s_oem, 119, 2);
+ sda_mem_getstring(rcid, slot->s_prod, 103, 6);
+ slot->s_majver = sda_mem_getbits(rcid, 55, 4);
+ slot->s_minver = sda_mem_getbits(rcid, 51, 4);
+ slot->s_serial = sda_mem_getbits(rcid, 47, 32);
+ break;
+
+ default:
+ /* this error isn't fatal to us */
+ sda_slot_err(slot, "Unknown MMCA version (%d)",
+ sda_mem_getbits(rcsd, 125, 4));
+ break;
+ }
+
+ slot->s_year = sda_mem_getbits(rcid, 11, 4) + 1997;
+ slot->s_month = sda_mem_getbits(rcid, 15, 4);
+
+ csize = sda_mem_getbits(rcsd, 73, 12);
+ rblen = (1 << sda_mem_getbits(rcsd, 83, 4));
+ cmult = (4 << sda_mem_getbits(rcsd, 49, 3));
+ bshift = 9;
+
+ } else {
+
+ sda_slot_err(slot, "Card type unknown");
+ return (DDI_FAILURE);
+ }
+
+ /*
+ * These fields are common to all known MMC/SDcard memory cards.
+ *
+ * The spec requires that block size 512 be supported.
+ * The media may have a different native size, but 512
+ * byte blocks will always work. This is true for SDcard,
+ * and apparently for MMC as well.
+ */
+ rblen = max(rblen, 512); /* paranoia */
+ slot->s_nblks = (csize + 1) * cmult * (rblen / 512);
+ slot->s_bshift = bshift;
+ slot->s_blksz = 512;
+
+ slot->s_r2w = (1 << sda_mem_getbits(rcsd, 28, 3));
+ slot->s_ccc = sda_mem_getbits(rcsd, 95, 12);
+ slot->s_perm_wp = sda_mem_getbits(rcsd, 13, 1);
+ slot->s_temp_wp = sda_mem_getbits(rcsd, 12, 1);
+ slot->s_dsr = sda_mem_getbits(rcsd, 76, 1);
+
+ if (((slot->s_ccc & (1 << 4)) == 0) ||
+ (slot->s_perm_wp != 0) || (slot->s_temp_wp != 0)) {
+ slot->s_flags &= ~SLOTF_WRITABLE;
+ }
+ (void) snprintf(date, sizeof (date), "%02d-%04d",
+ slot->s_month, slot->s_year);
+
+#define prop_set_int(name, val) \
+ (void) ddi_prop_update_int(DDI_DEV_T_NONE, dip, name, val)
+#define prop_set_str(name, val) \
+ (void) ddi_prop_update_string(DDI_DEV_T_NONE, dip, name, val)
+#define prop_set_bool(name, val) \
+ if (val) (void) ddi_prop_create(DDI_DEV_T_NONE, dip, 0, name, NULL, 0)
+
+ prop_set_str("device-type", dtype);
+ prop_set_int("mfg-id", slot->s_mfg);
+ prop_set_str("product-id", slot->s_prod);
+ prop_set_str("oem-id", slot->s_oem);
+ prop_set_str("mfg-date", date);
+
+ prop_set_int("block-size", slot->s_blksz);
+ prop_set_int("num-blocks", slot->s_nblks);
+ prop_set_int("max-freq", slot->s_maxclk);
+ prop_set_bool("dsr-implemented", slot->s_dsr);
+ prop_set_int("ccc", slot->s_ccc);
+ prop_set_bool("perm-wp", slot->s_perm_wp);
+ prop_set_bool("temp-wp", slot->s_temp_wp);
+
+#undef prop_set_int
+#undef prop_set_str
+#undef prop_set_bool
+
+ return (DDI_SUCCESS);
+}
diff --git a/usr/src/uts/common/io/sdcard/impl/sda_mod.c b/usr/src/uts/common/io/sdcard/impl/sda_mod.c
new file mode 100644
index 0000000000..34da209941
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/impl/sda_mod.c
@@ -0,0 +1,82 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * SD card module support.
+ */
+
+#include <sys/modctl.h>
+#include <sys/sdcard/sda_impl.h>
+
+/*
+ * Static Variables.
+ */
+
+static struct modlmisc modlmisc = {
+ &mod_miscops,
+ "SD Card Architecture",
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1, { &modlmisc, NULL }
+};
+
+/*
+ * DDI entry points.
+ */
+
+int
+_init(void)
+{
+ int rv;
+
+ sda_cmd_init();
+ sda_nexus_init();
+
+ if ((rv = mod_install(&modlinkage)) != 0) {
+ sda_cmd_fini();
+ sda_nexus_fini();
+ }
+
+ return (rv);
+}
+
+int
+_fini(void)
+{
+ int rv;
+
+ if ((rv = mod_remove(&modlinkage)) == 0) {
+ sda_cmd_fini();
+ sda_nexus_fini();
+ }
+ return (rv);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
diff --git a/usr/src/uts/common/io/sdcard/impl/sda_nexus.c b/usr/src/uts/common/io/sdcard/impl/sda_nexus.c
new file mode 100644
index 0000000000..764280c4f2
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/impl/sda_nexus.c
@@ -0,0 +1,930 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * SD card nexus support.
+ *
+ * NB that this file contains a fair bit of non-DDI compliant code.
+ * But writing a nexus driver would be impossible to do with only DDI
+ * compliant interfaces.
+ */
+
+#include <sys/types.h>
+#include <sys/modctl.h>
+#include <sys/list.h>
+#include <sys/mkdev.h>
+#include <sys/file.h>
+#include <sys/errno.h>
+#include <sys/open.h>
+#include <sys/cred.h>
+#include <sys/stat.h>
+#include <sys/conf.h>
+#include <sys/sysmacros.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/sunndi.h>
+#include <sys/sdcard/sda.h>
+#include <sys/sdcard/sda_ioctl.h>
+#include <sys/sdcard/sda_impl.h>
+
+
+/*
+ * Local prototypes.
+ */
+
+static sda_host_t *sda_nexus_lookup_dev(dev_t);
+static dev_info_t *sda_nexus_get_child(sda_slot_t *);
+static int sda_nexus_ap_ioctl(sda_host_t *, int, int, intptr_t);
+static int sda_nexus_ap_control(sda_host_t *, int, intptr_t, int);
+static int sda_nexus_ap_disconnect(sda_slot_t *);
+static int sda_nexus_ap_configure(sda_slot_t *);
+static int sda_nexus_ap_unconfigure(sda_slot_t *);
+static void sda_nexus_ap_getstate(sda_slot_t *, devctl_ap_state_t *);
+static void sda_nexus_reinsert(sda_slot_t *);
+static void sda_nexus_create(sda_slot_t *);
+
+/*
+ * Static Variables.
+ */
+
+static kmutex_t sda_nexus_lock;
+static list_t sda_nexus_list;
+
+/*
+ * Minor number allocation.
+ *
+ * We have up to NBITSMINOR32 (18) bits available.
+ *
+ * For each instance, we need one minor number for each slot, and one
+ * minor number for the devctl node.
+ *
+ * For simplicity's sake, we use the lower 8 bits for AP and DEVCTL nodes,
+ * and the remaining 10 bits for the instance number.
+ */
+#define MINOR_DC 0xff
+#define DEV_SLOT(dev) (getminor(dev) & 0xff)
+#define DEV_INST(dev) (getminor(dev) >> 8)
+#define MKMINOR_AP(inst, slot) (((slot) & 0xff) | ((inst) << 8))
+#define MKMINOR_DC(inst) (((inst) << 8) | MINOR_DC)
+
+/*
+ * Implementation.
+ */
+
+void
+sda_nexus_init(void)
+{
+ list_create(&sda_nexus_list, sizeof (sda_host_t),
+ offsetof(struct sda_host, h_node));
+ mutex_init(&sda_nexus_lock, NULL, MUTEX_DRIVER, NULL);
+}
+
+void
+sda_nexus_fini(void)
+{
+ list_destroy(&sda_nexus_list);
+ mutex_destroy(&sda_nexus_lock);
+}
+
+int
+sda_nexus_bus_ctl(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop,
+ void *arg, void *result)
+{
+ switch (ctlop) {
+ case DDI_CTLOPS_REPORTDEV:
+ {
+ cmn_err(CE_CONT, "?SD-device: %s@%s, %s#%d\n",
+ ddi_node_name(rdip), ddi_get_name_addr(rdip),
+ ddi_driver_name(rdip), ddi_get_instance(rdip));
+
+ return (DDI_SUCCESS);
+ }
+
+ case DDI_CTLOPS_INITCHILD:
+ {
+ dev_info_t *child_dip = (dev_info_t *)arg;
+ dev_info_t *ndip;
+ sda_slot_t *slot;
+ char addr[16];
+
+ if ((slot = ddi_get_parent_data(child_dip)) == NULL) {
+ sda_slot_err(NULL, "Parent data struct missing!");
+ return (DDI_FAILURE);
+ }
+
+ /*
+ * TODO: SDIO: We will need to use x,y addresses for
+ * SDIO function numbers. Memory cards will always
+ * resid at address 0. Probably this can be passed in
+ * to this function using properties.
+ */
+ (void) snprintf(addr, sizeof (addr), "%x", slot->s_slot_num);
+
+ /*
+ * Prevent duplicate nodes.
+ */
+ ndip = ndi_devi_find(dip, ddi_node_name(child_dip), addr);
+ if (ndip && (ndip != child_dip)) {
+ return (DDI_NOT_WELL_FORMED);
+ }
+
+ /*
+ * Stash the address in the devinfo node.
+ */
+ ddi_set_name_addr(child_dip, addr);
+
+ return (DDI_SUCCESS);
+ }
+
+ case DDI_CTLOPS_UNINITCHILD:
+ {
+ dev_info_t *child_dip = (dev_info_t *)arg;
+
+ ddi_set_name_addr(child_dip, NULL);
+ ndi_prop_remove_all(child_dip);
+ return (DDI_SUCCESS);
+ }
+
+ case DDI_CTLOPS_SIDDEV:
+ /*
+ * All SDA target devices are self-identifying.
+ */
+ return (DDI_SUCCESS);
+
+ case DDI_CTLOPS_SLAVEONLY:
+ /*
+ * We don't support DMA master for SDA targets.
+ */
+ return (DDI_SUCCESS);
+
+ case DDI_CTLOPS_AFFINITY:
+ /*
+ * NB: We may want to revisit this later, so that functions
+ * on one card can see other functions on the same card.
+ * Right now there is no need.
+ */
+ return (DDI_FAILURE);
+
+ case DDI_CTLOPS_DMAPMAPC:
+ case DDI_CTLOPS_REPORTINT:
+ case DDI_CTLOPS_POKE:
+ case DDI_CTLOPS_PEEK:
+ case DDI_CTLOPS_NREGS:
+ case DDI_CTLOPS_REGSIZE:
+ /*
+ * We don't support any of these (yet?).
+ */
+ return (DDI_FAILURE);
+
+ default:
+ /*
+ * Everything else goes to the parent nexus.
+ */
+ return (ddi_ctlops(dip, rdip, ctlop, arg, result));
+ }
+}
+
+void
+sda_nexus_register(sda_host_t *h)
+{
+ int i;
+ int inst;
+ char name[16];
+
+ mutex_enter(&sda_nexus_lock);
+ list_insert_tail(&sda_nexus_list, h);
+ mutex_exit(&sda_nexus_lock);
+
+ /*
+ * Now create minor nodes. Note that failures to create these nodes
+ * are mostly harmless, so we don't do much besides warn about it.
+ * (It means cfgadm will be useless, but most folks aren't likely
+ * to use cfgadm anyway.)
+ */
+
+ inst = ddi_get_instance(h->h_dip);
+
+ /*
+ * Create the devctl minor node.
+ */
+ if (ddi_create_minor_node(h->h_dip, "devctl", S_IFCHR,
+ MKMINOR_DC(inst), DDI_NT_NEXUS, 0) != DDI_SUCCESS) {
+ sda_slot_err(NULL, "Unable to create devctl node");
+ }
+
+ for (i = 0; i < h->h_nslot; i++) {
+
+ sda_slot_t *slot;
+
+ slot = &h->h_slots[i];
+ /*
+ * Create the attachment point minor nodes.
+ */
+ (void) snprintf(name, sizeof (name), "%d", i);
+ if (ddi_create_minor_node(h->h_dip, name, S_IFCHR,
+ MKMINOR_AP(inst, i), DDI_NT_SDCARD_ATTACHMENT_POINT,
+ 0) != DDI_SUCCESS) {
+ sda_slot_err(slot,
+ "Unable to create attachment point node");
+ }
+ }
+}
+
+void
+sda_nexus_unregister(sda_host_t *h)
+{
+ /*
+ * Remove all minor nodes.
+ */
+ ddi_remove_minor_node(h->h_dip, NULL);
+
+ mutex_enter(&sda_nexus_lock);
+ list_remove(&sda_nexus_list, h);
+ mutex_exit(&sda_nexus_lock);
+}
+
+sda_host_t *
+sda_nexus_lookup_dev(dev_t dev)
+{
+ major_t maj;
+ int inst;
+ sda_host_t *h;
+
+ ASSERT(mutex_owned(&sda_nexus_lock));
+
+ maj = getmajor(dev);
+ inst = DEV_INST(dev);
+
+ h = list_head(&sda_nexus_list);
+ while (h != NULL) {
+ if ((ddi_driver_major(h->h_dip) == maj) &&
+ (ddi_get_instance(h->h_dip) == inst)) {
+ break;
+ }
+ h = list_next(&sda_nexus_list, h);
+ }
+ return (h);
+}
+
+void
+sda_nexus_create(sda_slot_t *slot)
+{
+ dev_info_t *pdip, *cdip;
+ int rv;
+
+ pdip = slot->s_host->h_dip;
+
+ /*
+ * SDIO: This whole function will need to be recrafted to
+ * support non-memory children. For SDIO, there could be
+ * multiple functions, which get inserted or removed together.
+ */
+
+ if (ndi_devi_alloc(pdip, "sdcard", DEVI_SID_NODEID, &cdip) !=
+ NDI_SUCCESS) {
+ sda_slot_err(slot, "Failed allocating devinfo node");
+ return;
+ }
+
+ ddi_set_parent_data(cdip, slot);
+
+ /*
+ * Make sure the child node gets suspend/resume events.
+ */
+ rv = ndi_prop_update_int(DDI_DEV_T_NONE, cdip, "pm-capable", 1);
+ if (rv != 0) {
+ sda_slot_err(slot, "Failed creating pm-capable property");
+ (void) ndi_devi_free(cdip);
+ return;
+ }
+
+ sda_slot_enter(slot);
+ slot->s_ready = B_TRUE;
+ sda_slot_exit(slot);
+
+ if (ndi_devi_online(cdip, NDI_ONLINE_ATTACH) != NDI_SUCCESS) {
+ sda_slot_err(slot, "Failed bringing node online");
+ (void) ndi_devi_free(cdip);
+ }
+}
+
+void
+sda_nexus_reinsert(sda_slot_t *slot)
+{
+ dev_info_t *cdip, *ndip, *pdip;
+ int circ;
+
+ pdip = slot->s_host->h_dip;
+
+ ndi_devi_enter(pdip, &circ);
+ ndip = ddi_get_child(pdip);
+ while ((cdip = ndip) != NULL) {
+ ndip = ddi_get_next_sibling(cdip);
+ if (ddi_get_parent_data(cdip) == slot) {
+ mutex_enter(&DEVI(cdip)->devi_lock);
+ DEVI_SET_DEVICE_REINSERTED(cdip);
+ mutex_exit(&DEVI(cdip)->devi_lock);
+ }
+ }
+ ndi_devi_exit(pdip, circ);
+
+ sda_slot_enter(slot);
+ slot->s_warn = B_FALSE;
+ slot->s_ready = B_TRUE;
+ sda_slot_exit(slot);
+}
+
+void
+sda_nexus_insert(sda_slot_t *slot)
+{
+ char uuid[40];
+ boolean_t match;
+
+ if (slot->s_flags & SLOTF_MEMORY) {
+ (void) snprintf(uuid, sizeof (uuid), "%c%08X%08X%08X%08X",
+ slot->s_flags & SLOTF_MMC ? 'M' : 'S',
+ slot->s_rcid[0], slot->s_rcid[1],
+ slot->s_rcid[2], slot->s_rcid[3]);
+ } else {
+ /*
+ * SDIO: For SDIO, we can write the card's MANFID
+ * tuple in CIS to the UUID. Until we support SDIO,
+ * we just suppress creating devinfo nodes.
+ */
+ sda_slot_err(slot, "Non-memory target not supported");
+ uuid[0] = 0;
+ }
+
+ match = ((uuid[0] != 0) && (strcmp(slot->s_uuid, uuid) == 0));
+
+ if (sda_nexus_get_child(slot) != NULL) {
+ if (!match) {
+ sda_slot_err(slot, "Card removed while still in use.");
+ sda_slot_err(slot, "Please reinsert previous card.");
+
+ sda_nexus_remove(slot);
+ } else {
+ sda_nexus_reinsert(slot);
+ }
+ } else {
+ /*
+ * Remember the UUID.
+ */
+ (void) strlcpy(slot->s_uuid, uuid, sizeof (slot->s_uuid));
+ /*
+ * Create the children.
+ */
+ if (uuid[0] != 0)
+ sda_nexus_create(slot);
+ }
+}
+
+void
+sda_nexus_remove(sda_slot_t *slot)
+{
+ sda_host_t *h = slot->s_host;
+ dev_info_t *pdip = h->h_dip;
+ dev_info_t *cdip;
+ int circ;
+ char addr[16];
+ int addrl;
+ char *ap;
+ boolean_t reap = B_FALSE;
+
+ ndi_devi_enter(pdip, &circ);
+ cdip = ddi_get_child(pdip);
+
+ /* calculate the prefix address that slot's children should have */
+ (void) snprintf(addr, sizeof (addr), "%x", slot->s_slot_num);
+ addrl = strlen(addr);
+
+ while (cdip != NULL) {
+ ap = ddi_get_name_addr(cdip);
+ if (ap == NULL)
+ continue;
+
+ if ((strncmp(addr, ap, addrl) != 0) ||
+ ((ap[addrl] != '\0') && (ap[addrl] != ','))) {
+ /* address isn't for this slot */
+ continue;
+ }
+
+ reap = B_TRUE;
+ mutex_enter(&(DEVI(cdip))->devi_lock);
+ DEVI_SET_DEVICE_REMOVED(cdip);
+ mutex_exit(&(DEVI(cdip))->devi_lock);
+
+ cdip = ddi_get_next_sibling(cdip);
+ }
+ ndi_devi_exit(pdip, circ);
+
+ if (reap) {
+ sda_slot_enter(slot);
+ slot->s_reap = B_TRUE;
+ sda_slot_exit(slot);
+ sda_slot_wakeup(slot);
+ }
+}
+
+void
+sda_nexus_reap(void *arg)
+{
+ sda_slot_t *slot = arg;
+ dev_info_t *pdip = slot->s_host->h_dip;
+ dev_info_t *cdip, *ndip;
+ int circ;
+
+ ndi_devi_enter(pdip, &circ);
+ ndip = ddi_get_child(pdip);
+
+ /*
+ * NB: The goofy locking order here is required because
+ * ndi_devi_offline won't clean the devfs cache if the parent
+ * lock is held. There really needs to be a better way, such
+ * as a recurse flag.
+ */
+ while ((cdip = ndip) != NULL) {
+
+ /* get the next node before we delete this one! */
+ ndip = ddi_get_next_sibling(cdip);
+
+ if ((ddi_get_parent_data(cdip) == slot) &&
+ (DEVI_IS_DEVICE_REMOVED(cdip))) {
+
+
+ ndi_devi_exit(pdip, circ);
+ if (ndi_devi_offline(cdip, NDI_DEVI_REMOVE) !=
+ NDI_SUCCESS) {
+
+ mutex_enter(&slot->s_evlock);
+ slot->s_reap = B_TRUE;
+ mutex_exit(&slot->s_evlock);
+ return;
+ }
+
+ ndi_devi_enter(pdip, &circ);
+ /* we removed it, so restart from the beginning */
+ ndip = ddi_get_child(pdip);
+ }
+ }
+ mutex_enter(&slot->s_evlock);
+ /* woohoo, done reaping nodes */
+ slot->s_reap = B_FALSE;
+ mutex_exit(&slot->s_evlock);
+
+ ndi_devi_exit(pdip, circ);
+}
+
+dev_info_t *
+sda_nexus_get_child(sda_slot_t *slot)
+{
+ int circ;
+ dev_info_t *cdip, *pdip;
+
+ pdip = slot->s_host->h_dip;
+
+ ndi_devi_enter(pdip, &circ);
+ cdip = ddi_get_child(pdip);
+ while (cdip != NULL) {
+ if (ddi_get_parent_data(cdip) == slot) {
+ break;
+ }
+ cdip = ddi_get_next_sibling(cdip);
+ }
+ ndi_devi_exit(pdip, circ);
+ return (cdip);
+}
+
+
+/*ARGSUSED3*/
+int
+sda_nexus_open(dev_t *devp, int flags, int otyp, cred_t *credp)
+{
+ int rv = 0;
+ sda_host_t *h;
+
+ if (otyp != OTYP_CHR)
+ return (EINVAL);
+
+ mutex_enter(&sda_nexus_lock);
+ if ((h = sda_nexus_lookup_dev(*devp)) == NULL) {
+ mutex_exit(&sda_nexus_lock);
+ return (ENXIO);
+ }
+
+ if (flags & FEXCL) {
+ if ((h->h_flags & (HOST_SOPEN|HOST_XOPEN)) != 0) {
+ rv = EBUSY;
+ } else {
+ h->h_flags |= HOST_XOPEN;
+ }
+ } else {
+ if ((h->h_flags & HOST_XOPEN) != 0) {
+ rv = EBUSY;
+ } else {
+ h->h_flags |= HOST_SOPEN;
+ }
+ }
+ mutex_exit(&sda_nexus_lock);
+ return (rv);
+}
+
+/*ARGSUSED1*/
+int
+sda_nexus_close(dev_t dev, int flag, int otyp, cred_t *credp)
+{
+ sda_host_t *h;
+
+ if (otyp != OTYP_CHR)
+ return (EINVAL);
+
+ mutex_enter(&sda_nexus_lock);
+ if ((h = sda_nexus_lookup_dev(dev)) == NULL) {
+ mutex_exit(&sda_nexus_lock);
+ return (ENXIO);
+ }
+ h->h_flags &= ~(HOST_XOPEN | HOST_SOPEN);
+ mutex_exit(&sda_nexus_lock);
+ return (0);
+}
+
+void
+sda_nexus_ap_getstate(sda_slot_t *slot, devctl_ap_state_t *ap_state)
+{
+ dev_info_t *cdip;
+ int circ;
+
+ ndi_devi_enter(slot->s_host->h_dip, &circ);
+
+ /*
+ * Default state.
+ */
+ ap_state->ap_rstate = AP_RSTATE_EMPTY;
+ ap_state->ap_condition = AP_COND_OK;
+ ap_state->ap_ostate = AP_OSTATE_UNCONFIGURED;
+
+ if (slot->s_inserted) {
+ ap_state->ap_rstate = AP_RSTATE_CONNECTED;
+ }
+
+ if ((cdip = sda_nexus_get_child(slot)) != NULL) {
+ mutex_enter(&DEVI(cdip)->devi_lock);
+ if (DEVI_IS_DEVICE_REMOVED(cdip)) {
+ ap_state->ap_condition = AP_COND_UNUSABLE;
+ }
+ if (DEVI_IS_DEVICE_OFFLINE(cdip) ||
+ DEVI_IS_DEVICE_DOWN(cdip)) {
+ ap_state->ap_ostate = AP_OSTATE_UNCONFIGURED;
+ } else {
+ ap_state->ap_ostate = AP_OSTATE_CONFIGURED;
+ }
+ mutex_exit(&DEVI(cdip)->devi_lock);
+ }
+
+ if (slot->s_failed) {
+ ap_state->ap_condition = AP_COND_FAILED;
+ }
+
+ ap_state->ap_last_change = slot->s_stamp;
+ ap_state->ap_in_transition = slot->s_intransit;
+
+ ndi_devi_exit(slot->s_host->h_dip, circ);
+}
+
+int
+sda_nexus_ap_disconnect(sda_slot_t *slot)
+{
+ dev_info_t *cdip;
+
+ /* if a child node exists, try to delete it */
+ if ((cdip = sda_nexus_get_child(slot)) != NULL) {
+ if (ndi_devi_offline(cdip, NDI_DEVI_REMOVE) != NDI_SUCCESS) {
+ /* couldn't disconnect, why not? */
+ return (EBUSY);
+ }
+ slot->s_stamp = ddi_get_time();
+ }
+ return (0);
+}
+
+int
+sda_nexus_ap_unconfigure(sda_slot_t *slot)
+{
+ dev_info_t *cdip;
+
+ /* attempt to unconfigure the node */
+ if ((cdip = sda_nexus_get_child(slot)) == NULL) {
+ /* node not there! */
+ return (ENXIO);
+ }
+
+ if (ndi_devi_offline(cdip, NDI_UNCONFIG) != NDI_SUCCESS) {
+ /* failed to unconfigure the node (EBUSY?) */
+ return (EIO);
+ }
+ slot->s_stamp = ddi_get_time();
+ return (0);
+}
+
+int
+sda_nexus_ap_configure(sda_slot_t *slot)
+{
+ dev_info_t *cdip;
+
+ sda_slot_enter(slot);
+ if (slot->s_inserted == B_FALSE) {
+ /* device not present */
+ sda_slot_exit(slot);
+ return (ENXIO);
+ }
+
+ /* attempt to configure the node */
+ if ((cdip = sda_nexus_get_child(slot)) == NULL) {
+ sda_slot_exit(slot);
+ /* node not there! */
+ return (ENXIO);
+ }
+ sda_slot_exit(slot);
+
+ slot->s_intransit = 1;
+ if (ndi_devi_online(cdip, NDI_CONFIG) != NDI_SUCCESS) {
+ /* failed to configure the node */
+ slot->s_intransit = 0;
+ return (EIO);
+ }
+ slot->s_intransit = 0;
+ slot->s_stamp = ddi_get_time();
+ return (0);
+}
+
+int
+sda_nexus_ap_ioctl(sda_host_t *h, int snum, int cmd, intptr_t arg)
+{
+ struct devctl_iocdata *dcp = NULL;
+ devctl_ap_state_t ap_state;
+ sda_slot_t *slot;
+ int rv = 0;
+
+ /*
+ * In theory we could try to support this operation on the
+ * DEVCTL minor, but then we would need a slot member in the
+ * user nvlist. For now its easiest to assume a 1:1 relation
+ * between the AP minor node, and the slot number.
+ */
+ if (snum >= h->h_nslot) {
+ return (ENXIO);
+ }
+ slot = &h->h_slots[snum];
+
+ if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS)
+ return (EFAULT);
+
+ switch (cmd) {
+ case DEVCTL_AP_DISCONNECT:
+ rv = sda_nexus_ap_disconnect(slot);
+ break;
+
+ case DEVCTL_AP_UNCONFIGURE:
+ rv = sda_nexus_ap_unconfigure(slot);
+ break;
+
+ case DEVCTL_AP_CONFIGURE:
+ rv = sda_nexus_ap_configure(slot);
+ break;
+
+ case DEVCTL_AP_GETSTATE:
+ sda_nexus_ap_getstate(slot, &ap_state);
+ if (ndi_dc_return_ap_state(&ap_state, dcp) != NDI_SUCCESS) {
+ rv = EFAULT;
+ }
+ break;
+ }
+
+ ndi_dc_freehdl(dcp);
+
+ return (rv);
+}
+
+int
+sda_nexus_ap_control(sda_host_t *h, int snum, intptr_t arg, int mode)
+{
+ struct sda_ap_control apc;
+ struct sda_ap_control32 apc32;
+ sda_slot_t *slot;
+ int rv = 0;
+
+ if (snum >= h->h_nslot) {
+ return (ENXIO);
+ }
+ slot = &h->h_slots[snum];
+
+ switch (ddi_model_convert_from(mode & FMODELS)) {
+ case DDI_MODEL_ILP32:
+ if (ddi_copyin((void *)arg, &apc32, sizeof (apc32), mode) !=
+ 0) {
+ return (EFAULT);
+ }
+ apc.cmd = apc32.cmd;
+ apc.size = apc32.size;
+ apc.data = (caddr_t *)(intptr_t)apc32.data;
+ break;
+ case DDI_MODEL_NONE:
+ if (ddi_copyin((void *)arg, &apc, sizeof (apc), mode) != 0) {
+ return (EFAULT);
+ }
+ break;
+ }
+
+ switch (apc.cmd) {
+ case SDA_CFGA_GET_CARD_INFO: {
+ sda_card_info_t ci;
+
+ if (apc.size < sizeof (sda_card_info_t)) {
+ apc.size = sizeof (sda_card_info_t);
+ break;
+ }
+ sda_slot_enter(slot);
+ if (!slot->s_inserted) {
+ ci.ci_type = SDA_CT_UNKNOWN;
+ } else if (slot->s_flags & SLOTF_MMC) {
+ ci.ci_type = SDA_CT_MMC;
+ } else if (slot->s_flags & SLOTF_SDIO) {
+ if (slot->s_flags & SLOTF_MEMORY) {
+ ci.ci_type = SDA_CT_SDCOMBO;
+ } else {
+ ci.ci_type = SDA_CT_SDIO;
+ }
+ } else if (slot->s_flags & SLOTF_SDMEM) {
+ if (slot->s_flags & SLOTF_SDHC) {
+ ci.ci_type = SDA_CT_SDHC;
+ } else {
+ ci.ci_type = SDA_CT_SDMEM;
+ }
+ } else {
+ ci.ci_type = SDA_CT_UNKNOWN;
+ }
+
+ if (slot->s_flags & SLOTF_MEMORY) {
+ ci.ci_mfg = slot->s_mfg;
+ (void) strlcpy(ci.ci_oem,
+ slot->s_oem, sizeof (ci.ci_oem));
+ (void) strlcpy(ci.ci_pid,
+ slot->s_prod, sizeof (ci.ci_pid));
+ ci.ci_serial = slot->s_serial;
+ ci.ci_month = slot->s_month;
+ ci.ci_year = (slot->s_year - 1900) & 0xff;
+ ci.ci_major = slot->s_majver;
+ ci.ci_minor = slot->s_minver;
+ }
+
+ sda_slot_exit(slot);
+
+ if (ddi_copyout(&ci, apc.data, sizeof (ci), mode) != 0) {
+ return (EFAULT);
+ }
+
+ break;
+ }
+
+ case SDA_CFGA_GET_DEVICE_PATH:
+ {
+ char path[MAXPATHLEN];
+ dev_info_t *cdip;
+ int slen;
+
+ if ((cdip = sda_nexus_get_child(slot)) == NULL) {
+ return (ENOENT);
+ }
+ (void) strcpy(path, "/devices");
+ (void) ddi_pathname(cdip, path + strlen(path));
+ slen = strlen(path) + 1;
+ if (apc.size < slen) {
+ apc.size = slen;
+ rv = ENOSPC;
+ break;
+ }
+ apc.size = slen;
+ if (ddi_copyout(path, apc.data, slen, mode) != 0) {
+ return (EFAULT);
+ }
+ break;
+ }
+
+ case SDA_CFGA_RESET_SLOT:
+ {
+ sda_slot_enter(slot);
+ slot->s_failed = B_FALSE;
+ sda_slot_exit(slot);
+ sda_slot_reset(slot);
+ sda_slot_detect(slot);
+ break;
+ }
+
+ default:
+ return (EINVAL);
+ }
+
+ switch (ddi_model_convert_from(mode & FMODELS)) {
+ case DDI_MODEL_ILP32:
+ apc32.cmd = apc.cmd;
+ apc32.size = (size32_t)apc.size;
+ apc32.data = (caddr32_t)(intptr_t)apc.data;
+ if (ddi_copyout(&apc32, (void *)arg, sizeof (apc32), mode) !=
+ 0) {
+ return (EFAULT);
+ }
+ break;
+ case DDI_MODEL_NONE:
+ if (ddi_copyout(&apc, (void *)arg, sizeof (apc), mode) != 0) {
+ return (EFAULT);
+ }
+ break;
+ }
+ return (rv);
+}
+
+/*ARGSUSED4*/
+int
+sda_nexus_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
+ int *rvp)
+{
+ sda_host_t *h;
+
+ mutex_enter(&sda_nexus_lock);
+ h = sda_nexus_lookup_dev(dev);
+ mutex_exit(&sda_nexus_lock);
+
+ if (h == NULL)
+ return (ENXIO);
+
+ switch (cmd) {
+ case DEVCTL_DEVICE_GETSTATE:
+ case DEVCTL_DEVICE_ONLINE:
+ case DEVCTL_DEVICE_OFFLINE:
+ case DEVCTL_DEVICE_REMOVE:
+ case DEVCTL_BUS_GETSTATE:
+ return (ndi_devctl_ioctl(h->h_dip, cmd, arg, mode, 0));
+
+ case DEVCTL_AP_DISCONNECT:
+ case DEVCTL_AP_CONFIGURE:
+ case DEVCTL_AP_UNCONFIGURE:
+ case DEVCTL_AP_GETSTATE:
+ return (sda_nexus_ap_ioctl(h, DEV_SLOT(dev), cmd, arg));
+
+ case DEVCTL_AP_CONTROL:
+ return (sda_nexus_ap_control(h, DEV_SLOT(dev), arg, mode));
+
+ default:
+ return (ENOTSUP);
+ }
+}
+
+/*ARGSUSED*/
+int
+sda_nexus_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resp)
+{
+ sda_host_t *h;
+ int rv;
+
+ rv = DDI_FAILURE;
+
+ switch (cmd) {
+ case DDI_INFO_DEVT2DEVINFO:
+ mutex_enter(&sda_nexus_lock);
+ h = sda_nexus_lookup_dev((dev_t)arg);
+ if (h != NULL) {
+ *resp = h->h_dip;
+ rv = DDI_SUCCESS;
+ }
+ mutex_exit(&sda_nexus_lock);
+ break;
+
+ case DDI_INFO_DEVT2INSTANCE:
+ *resp = (void *)(intptr_t)DEV_INST((dev_t)arg);
+ rv = DDI_SUCCESS;
+ break;
+ }
+ return (rv);
+}
diff --git a/usr/src/uts/common/io/sdcard/impl/sda_slot.c b/usr/src/uts/common/io/sdcard/impl/sda_slot.c
new file mode 100644
index 0000000000..8aacb2806b
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/impl/sda_slot.c
@@ -0,0 +1,896 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * SD card slot support.
+ *
+ * NB that this file contains a fair bit of non-DDI compliant code.
+ * But writing a nexus driver would be impossible to do with only DDI
+ * compliant interfaces.
+ */
+
+#include <sys/types.h>
+#include <sys/thread.h>
+#include <sys/proc.h>
+#include <sys/callb.h>
+#include <sys/sysmacros.h>
+#include <sys/cpuvar.h>
+#include <sys/cmn_err.h>
+#include <sys/varargs.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/sunndi.h>
+#include <sys/sdcard/sda_impl.h>
+
+
+/*
+ * Prototypes.
+ */
+
+static void sda_slot_insert(void *);
+static sda_err_t sda_slot_check_response(sda_cmd_t *);
+static void sda_slot_handle_detect(sda_slot_t *);
+static void sda_slot_handle_transfer(sda_slot_t *, sda_err_t);
+static void sda_slot_handle_fault(sda_slot_t *, sda_fault_t);
+static void sda_slot_abort(sda_slot_t *, sda_err_t);
+static void sda_slot_halt(sda_slot_t *);
+static void sda_slot_thread(void *);
+static void sda_slot_vprintf(sda_slot_t *, int, const char *, va_list);
+
+/*
+ * Static Variables.
+ */
+
+static struct {
+ sda_fault_t fault;
+ const char *msg;
+} sda_slot_faults[] = {
+ { SDA_FAULT_TIMEOUT, "Data transfer timed out" },
+ { SDA_FAULT_ACMD12, "Auto CMD12 failure" },
+ { SDA_FAULT_CRC7, "CRC7 failure on CMD/DAT line" },
+ { SDA_FAULT_PROTO, "SD/MMC protocol signaling error" },
+ { SDA_FAULT_INIT, "Card initialization failure" },
+ { SDA_FAULT_HOST, "Internal host or slot failure" },
+ { SDA_FAULT_CURRENT, "Current overlimit detected" },
+ { SDA_FAULT_RESET, "Failed to reset slot" },
+ { SDA_FAULT_NONE, NULL }, /* sentinel, must be last! */
+};
+
+/*
+ * Internal implementation.
+ */
+
+/*
+ * These allow for recursive entry. This is necessary to facilitate
+ * simpler locking with things like the fault handler, where a caller
+ * might already be "holding" the slot.
+ *
+ * This is modeled in part after ndi_devi_enter and ndi_devi_exit.
+ */
+void
+sda_slot_enter(sda_slot_t *slot)
+{
+ kt_did_t self = ddi_get_kt_did();
+ mutex_enter(&slot->s_lock);
+ if (slot->s_owner == self) {
+ slot->s_circular++;
+ } else {
+ while ((slot->s_owner != 0) && (slot->s_owner != self)) {
+ cv_wait(&slot->s_cv, &slot->s_lock);
+ }
+ slot->s_owner = self;
+ slot->s_circular++;
+ }
+ mutex_exit(&slot->s_lock);
+}
+
+void
+sda_slot_exit(sda_slot_t *slot)
+{
+ ASSERT(sda_slot_owned(slot));
+
+ mutex_enter(&slot->s_lock);
+ slot->s_circular--;
+ if (slot->s_circular == 0) {
+ slot->s_owner = 0;
+ cv_broadcast(&slot->s_cv);
+ }
+ mutex_exit(&slot->s_lock);
+}
+
+boolean_t
+sda_slot_owned(sda_slot_t *slot)
+{
+ return (slot->s_owner == ddi_get_kt_did());
+}
+
+sda_err_t
+sda_slot_check_response(sda_cmd_t *cmdp)
+{
+ uint32_t errs;
+ switch (cmdp->sc_rtype & 0xf) {
+ case R1:
+ if ((errs = (cmdp->sc_response[0] & R1_ERRS)) != 0) {
+ if (errs & (R1_WP_VIOLATION | R1_CSD_OVERWRITE)) {
+ return (SDA_EWPROTECT);
+ }
+ if (errs & (R1_ADDRESS_ERROR | R1_BLOCK_LEN_ERROR |
+ R1_OUT_OF_RANGE | R1_ERASE_PARAM)) {
+ return (SDA_EINVAL);
+ }
+ return (SDA_EIO);
+ }
+ break;
+ case R5:
+ if ((errs = (cmdp->sc_response[0] & R5_ERRS)) != 0) {
+ return (SDA_EIO);
+ }
+ break;
+ }
+ return (SDA_EOK);
+}
+
+void
+sda_slot_halt(sda_slot_t *slot)
+{
+ sda_slot_enter(slot);
+ slot->s_ops.so_halt(slot->s_prv);
+ drv_usecwait(1000); /* we need to wait 1 msec for power down */
+ sda_slot_exit(slot);
+}
+
+void
+sda_slot_reset(sda_slot_t *slot)
+{
+ sda_slot_enter(slot);
+ if (slot->s_ops.so_reset(slot->s_prv) != 0) {
+ sda_slot_fault(slot, SDA_FAULT_RESET);
+ }
+ sda_slot_exit(slot);
+}
+
+int
+sda_slot_power_on(sda_slot_t *slot)
+{
+ int rv;
+ uint32_t ocr;
+
+ sda_slot_enter(slot);
+
+ /*
+ * Get the voltage supplied by the host. Note that we expect
+ * hosts will include a range of 2.7-3.7 in their supported
+ * voltage ranges. The spec does not allow for hosts that
+ * cannot supply a voltage in this range, yet.
+ */
+ if ((rv = sda_getprop(slot, SDA_PROP_OCR, &ocr)) != 0) {
+ sda_slot_err(slot, "Failed to get host OCR (%d)", rv);
+ goto done;
+ }
+ if ((ocr & OCR_HI_MASK) == 0) {
+ sda_slot_err(slot, "Host does not support standard voltages.");
+ rv = ENOTSUP;
+ goto done;
+ }
+
+ /*
+ * We prefer 3.3V, 3.0V, and failing that, just use the
+ * maximum that the host supports. 3.3V is preferable,
+ * because it is the typical common voltage that just about
+ * everything supports. Otherwise we just pick the highest
+ * supported voltage. This facilitates initial power up.
+ */
+ if (ocr & OCR_32_33V) {
+ slot->s_cur_ocr = OCR_32_33V;
+ } else if (ocr & OCR_29_30V) {
+ slot->s_cur_ocr = OCR_29_30V;
+ } else {
+ slot->s_cur_ocr = (1U << (ddi_fls(ocr) - 1));
+ }
+
+ /*
+ * Turn on the power.
+ */
+ if ((rv = sda_setprop(slot, SDA_PROP_OCR, slot->s_cur_ocr)) != 0) {
+ sda_slot_err(slot, "Failed to set OCR %x (%d)",
+ slot->s_cur_ocr, rv);
+ goto done;
+ }
+
+ sda_slot_exit(slot);
+
+ /*
+ * Wait 250 msec (per spec) for power ramp to complete.
+ */
+ delay(drv_usectohz(250000));
+ return (0);
+
+done:
+ sda_slot_exit(slot);
+ return (rv);
+}
+
+void
+sda_slot_power_off(sda_slot_t *slot)
+{
+ sda_slot_enter(slot);
+ (void) sda_setprop(slot, SDA_PROP_OCR, 0);
+ /* XXX: FMA: on failure this should cause a fault to be generated */
+ /* spec requires voltage to stay low for at least 1 msec */
+ drv_usecwait(1000);
+ sda_slot_exit(slot);
+}
+
+void
+sda_slot_insert(void *arg)
+{
+ sda_slot_t *slot = arg;
+
+ if (sda_init_card(slot) != SDA_EOK) {
+ /*
+ * Remove power from the slot. If a more severe fault
+ * occurred, then a manual reset with cfgadm will be needed.
+ */
+ sda_slot_err(slot, "Unable to initialize card!");
+ sda_slot_enter(slot);
+ sda_slot_power_off(slot);
+ sda_slot_abort(slot, SDA_ENODEV);
+ sda_slot_exit(slot);
+ sda_nexus_remove(slot);
+
+ } else {
+ sda_nexus_insert(slot);
+ }
+
+ slot->s_stamp = ddi_get_time();
+ slot->s_intransit = 0;
+}
+
+void
+sda_slot_mem_reset(sda_slot_t *slot, sda_err_t errno)
+{
+ sda_cmd_t *cmdp;
+
+ sda_slot_enter(slot);
+ cmdp = list_head(&slot->s_cmdlist);
+ while (cmdp != NULL) {
+ sda_cmd_t *next;
+ next = list_next(&slot->s_cmdlist, cmdp);
+ if (cmdp->sc_flags & SDA_CMDF_MEM) {
+ list_remove(&slot->s_cmdlist, cmdp);
+ sda_cmd_notify(cmdp, 0, errno);
+ mutex_enter(&slot->s_evlock);
+ list_insert_tail(&slot->s_abortlist, cmdp);
+ mutex_exit(&slot->s_evlock);
+ }
+ cmdp = next;
+ }
+ sda_slot_exit(slot);
+
+ /* wake up to process the abort list */
+ sda_slot_wakeup(slot);
+}
+
+void
+sda_slot_abort(sda_slot_t *slot, sda_err_t errno)
+{
+ sda_cmd_t *cmdp;
+
+ ASSERT(sda_slot_owned(slot));
+
+ if ((cmdp = slot->s_xfrp) != NULL) {
+ slot->s_xfrp = NULL;
+ sda_cmd_notify(cmdp, SDA_CMDF_BUSY | SDA_CMDF_DAT, errno);
+ }
+ while ((cmdp = list_head(&slot->s_cmdlist)) != NULL) {
+ list_remove(&slot->s_cmdlist, cmdp);
+ sda_cmd_notify(cmdp, 0, errno);
+ mutex_enter(&slot->s_evlock);
+ list_insert_tail(&slot->s_abortlist, cmdp);
+ mutex_exit(&slot->s_evlock);
+ }
+
+ sda_slot_wakeup(slot);
+}
+
+void
+sda_slot_handle_transfer(sda_slot_t *slot, sda_err_t errno)
+{
+ sda_cmd_t *cmdp;
+
+ sda_slot_enter(slot);
+
+ if ((cmdp = slot->s_xfrp) != NULL) {
+
+ slot->s_xfrp = NULL;
+ slot->s_xfrtmo = 0;
+ (void) sda_setprop(slot, SDA_PROP_LED, 0);
+ sda_slot_exit(slot);
+
+ sda_slot_wakeup(slot);
+
+ sda_cmd_notify(cmdp, SDA_CMDF_DAT, errno);
+ } else {
+ sda_slot_exit(slot);
+ }
+}
+
+void
+sda_slot_handle_fault(sda_slot_t *slot, sda_fault_t fault)
+{
+ const char *msg;
+ int i;
+
+ sda_slot_enter(slot);
+
+ if ((fault == SDA_FAULT_TIMEOUT) && (slot->s_init)) {
+ /*
+ * Timeouts during initialization are quite normal.
+ */
+ sda_slot_exit(slot);
+ return;
+ }
+
+ slot->s_failed = B_TRUE;
+ sda_slot_abort(slot, SDA_EFAULT);
+
+ msg = "Unknown fault (%d)";
+ for (i = 0; sda_slot_faults[i].msg != NULL; i++) {
+ if (sda_slot_faults[i].fault == fault) {
+ msg = sda_slot_faults[i].msg;
+ break;
+ }
+ }
+
+ /*
+ * FMA would be a better choice here.
+ */
+ sda_slot_err(slot, msg, fault);
+
+ /*
+ * Shut down the slot. Interaction from userland via cfgadm
+ * can revive it.
+ *
+ * FMA can help here.
+ */
+ sda_slot_halt(slot);
+
+ sda_slot_exit(slot);
+}
+
+void
+sda_slot_handle_detect(sda_slot_t *slot)
+{
+ uint32_t inserted;
+
+ sda_slot_enter(slot);
+
+ slot->s_stamp = ddi_get_time();
+ slot->s_intransit = 1;
+ slot->s_flags = 0;
+ slot->s_rca = 0;
+ slot->s_ready = B_FALSE;
+
+ sda_getprop(slot, SDA_PROP_INSERTED, &inserted);
+ slot->s_inserted = (inserted != 0);
+
+ if (slot->s_inserted && !slot->s_failed) {
+ /*
+ * We need to initialize the card, so we only support
+ * hipri commands for now.
+ */
+ slot->s_init = B_TRUE;
+
+ /*
+ * Card insertion occurred. We have to run this on
+ * another task, to avoid deadlock as the task may
+ * need to dispatch commands.
+ */
+ (void) ddi_taskq_dispatch(slot->s_tq, sda_slot_insert, slot,
+ DDI_SLEEP);
+ } else {
+
+ /*
+ * Nuke in-flight commands.
+ */
+ sda_slot_abort(slot, SDA_ENODEV);
+
+ /*
+ * Restart the slot (incl. power cycle). This gets the
+ * slot to a known good state.
+ */
+ sda_slot_reset(slot);
+
+ sda_nexus_remove(slot);
+
+ slot->s_intransit = 0;
+ }
+ sda_slot_exit(slot);
+
+ sda_slot_wakeup(slot);
+}
+
+void
+sda_slot_transfer(sda_slot_t *slot, sda_err_t errno)
+{
+ mutex_enter(&slot->s_evlock);
+ slot->s_errno = errno;
+ slot->s_xfrdone = B_TRUE;
+ cv_broadcast(&slot->s_evcv);
+ mutex_exit(&slot->s_evlock);
+}
+
+void
+sda_slot_detect(sda_slot_t *slot)
+{
+ mutex_enter(&slot->s_evlock);
+ slot->s_detect = B_TRUE;
+ cv_broadcast(&slot->s_evcv);
+ mutex_exit(&slot->s_evlock);
+}
+
+void
+sda_slot_fault(sda_slot_t *slot, sda_fault_t fault)
+{
+ mutex_enter(&slot->s_evlock);
+ slot->s_fault = fault;
+ cv_broadcast(&slot->s_evcv);
+ mutex_exit(&slot->s_evlock);
+}
+
+void
+sda_slot_wakeup(sda_slot_t *slot)
+{
+ mutex_enter(&slot->s_evlock);
+ slot->s_wake = B_TRUE;
+ cv_broadcast(&slot->s_evcv);
+ mutex_exit(&slot->s_evlock);
+}
+
+void
+sda_slot_init(sda_slot_t *slot)
+{
+ mutex_init(&slot->s_lock, NULL, MUTEX_DRIVER, NULL);
+ cv_init(&slot->s_cv, NULL, CV_DRIVER, NULL);
+ mutex_init(&slot->s_evlock, NULL, MUTEX_DRIVER, NULL);
+ cv_init(&slot->s_evcv, NULL, CV_DRIVER, NULL);
+
+ sda_cmd_list_init(&slot->s_cmdlist);
+ sda_cmd_list_init(&slot->s_abortlist);
+}
+
+void
+sda_slot_fini(sda_slot_t *slot)
+{
+ sda_cmd_list_fini(&slot->s_cmdlist);
+ sda_cmd_list_fini(&slot->s_abortlist);
+ mutex_destroy(&slot->s_lock);
+ mutex_destroy(&slot->s_evlock);
+ cv_destroy(&slot->s_cv);
+ cv_destroy(&slot->s_evcv);
+}
+
+void
+sda_slot_attach(sda_slot_t *slot)
+{
+ sda_host_t *h = slot->s_host;
+ char name[16];
+ kthread_t *thr;
+ uint32_t cap;
+
+ /*
+ * We have both a thread and a taskq. The taskq is used for
+ * card initialization.
+ *
+ * The thread is used for the main processing loop.
+ *
+ * The reason for a separate taskq is that initialization
+ * needs to acquire locks which may be held by the slot
+ * thread, or by device driver context... use of the separate
+ * taskq breaks the deadlock. Additionally, the
+ * initialization task may need to sleep quite a while during
+ * card initialization.
+ */
+
+ sda_slot_enter(slot);
+
+ (void) snprintf(name, sizeof (name), "slot_%d_tq", slot->s_slot_num);
+ slot->s_tq = ddi_taskq_create(h->h_dip, name, 1, TASKQ_DEFAULTPRI, 0);
+ if (slot->s_tq == NULL) {
+ /* Generally, this failure should never occur */
+ sda_slot_err(slot, "Unable to create slot taskq");
+ sda_slot_exit(slot);
+ return;
+ }
+
+ /* create the main processing thread */
+ thr = thread_create(NULL, 0, sda_slot_thread, slot, 0, &p0, TS_RUN,
+ minclsyspri);
+ slot->s_thrid = thr->t_did;
+
+ /*
+ * Determine slot capabilities.
+ */
+ slot->s_caps = 0;
+
+ if ((sda_getprop(slot, SDA_PROP_CAP_NOPIO, &cap) == 0) && (cap != 0)) {
+ slot->s_caps |= SLOT_CAP_NOPIO;
+ }
+ if ((sda_getprop(slot, SDA_PROP_CAP_4BITS, &cap) == 0) && (cap != 0)) {
+ slot->s_caps |= SLOT_CAP_4BITS;
+ }
+ if ((sda_getprop(slot, SDA_PROP_CAP_HISPEED, &cap) == 0) &&
+ (cap != 0)) {
+ slot->s_caps |= SLOT_CAP_HISPEED;
+ }
+
+ /* make sure that the host is started up */
+ if (slot->s_ops.so_reset(slot->s_prv) != 0) {
+ sda_slot_fault(slot, SDA_FAULT_RESET);
+ }
+
+ sda_slot_exit(slot);
+}
+
+void
+sda_slot_detach(sda_slot_t *slot)
+{
+ /*
+ * Shut down the thread.
+ */
+ if (slot->s_thrid) {
+ mutex_enter(&slot->s_evlock);
+ slot->s_detach = B_TRUE;
+ cv_broadcast(&slot->s_evcv);
+ mutex_exit(&slot->s_evlock);
+ }
+ thread_join(slot->s_thrid);
+
+ /*
+ * Nuke the taskq. We do this after killing the
+ * thread, to ensure that the thread doesn't try to
+ * dispatch to it.
+ */
+ if (slot->s_tq)
+ ddi_taskq_destroy(slot->s_tq);
+}
+
+void
+sda_slot_thread(void *arg)
+{
+ sda_slot_t *slot = arg;
+#ifndef __lock_lint
+ callb_cpr_t cprinfo;
+
+ CALLB_CPR_INIT(&cprinfo, &slot->s_evlock, callb_generic_cpr,
+ "sda_slot_thread");
+#endif
+
+ for (;;) {
+ sda_cmd_t *cmdp;
+ boolean_t datline;
+ sda_err_t rv;
+
+ mutex_enter(&slot->s_evlock);
+
+ /*
+ * Process any abort list first.
+ */
+ if ((cmdp = list_head(&slot->s_abortlist)) != NULL) {
+ list_remove(&slot->s_abortlist, cmdp);
+ mutex_exit(&slot->s_evlock);
+ /*
+ * EOK used here, to avoid clobbering previous
+ * error code.
+ */
+ sda_cmd_notify(cmdp, SDA_CMDF_BUSY | SDA_CMDF_DAT,
+ SDA_EOK);
+ continue;
+ }
+
+ if (slot->s_detach) {
+ /* parent is detaching the slot, bail out */
+ break;
+ }
+
+ if (slot->s_detect) {
+ slot->s_detect = B_FALSE;
+ mutex_exit(&slot->s_evlock);
+
+ sda_slot_handle_detect(slot);
+ continue;
+ }
+
+ if (slot->s_xfrdone) {
+ sda_err_t errno;
+
+ errno = slot->s_errno;
+ slot->s_errno = SDA_EOK;
+ slot->s_xfrdone = B_FALSE;
+ mutex_exit(&slot->s_evlock);
+
+ sda_slot_handle_transfer(slot, errno);
+ continue;
+ }
+
+ if (slot->s_fault != SDA_FAULT_NONE) {
+ sda_fault_t fault;
+
+ fault = slot->s_fault;
+ slot->s_fault = SDA_FAULT_NONE;
+ mutex_exit(&slot->s_evlock);
+
+ sda_slot_handle_fault(slot, fault);
+ continue;
+ }
+
+ if (slot->s_reap) {
+ /*
+ * Do not sleep while holding the evlock. If this
+ * fails, we'll just try again the next cycle.
+ */
+ (void) ddi_taskq_dispatch(slot->s_tq, sda_nexus_reap,
+ slot, DDI_NOSLEEP);
+ }
+
+ if ((slot->s_xfrp != NULL) && (gethrtime() > slot->s_xfrtmo)) {
+ /*
+ * The device stalled processing the data request.
+ * At this point, we really have no choice but to
+ * nuke the request, and flag a fault.
+ */
+ mutex_exit(&slot->s_evlock);
+ sda_slot_handle_transfer(slot, SDA_ETIME);
+ sda_slot_fault(slot, SDA_FAULT_TIMEOUT);
+ continue;
+ }
+
+ if (!slot->s_wake) {
+
+ /*
+ * We use a timed wait if we are waiting for a
+ * data transfer to complete, or if we might
+ * need to reap child nodes. Otherwise we
+ * avoid the timed wait to avoid waking CPU
+ * (power savings.)
+ */
+#ifndef __lock_lint
+ CALLB_CPR_SAFE_BEGIN(&cprinfo);
+#endif
+
+ if ((slot->s_xfrp != NULL) || (slot->s_reap)) {
+ /* wait 3 sec (reap attempts) */
+
+ (void) cv_timedwait(&slot->s_evcv,
+ &slot->s_evlock,
+ ddi_get_lbolt() + drv_usectohz(3000000));
+ } else {
+ (void) cv_wait(&slot->s_evcv, &slot->s_evlock);
+ }
+#ifndef __lock_lint
+ CALLB_CPR_SAFE_END(&cprinfo, &slot->s_evlock);
+#endif
+
+ mutex_exit(&slot->s_evlock);
+ continue;
+ }
+
+ slot->s_wake = B_FALSE;
+
+ /*
+ * Possibly reap child nodes.
+ */
+ if (slot->s_reap) {
+ slot->s_reap = B_FALSE;
+ mutex_exit(&slot->s_evlock);
+ sda_nexus_reap(slot);
+ } else {
+ mutex_exit(&slot->s_evlock);
+ }
+
+ /*
+ * We're awake now, so look for work to do. First
+ * acquire access to the slot.
+ */
+
+ sda_slot_enter(slot);
+
+ /*
+ * If no more commands to process, go back to sleep.
+ */
+ if ((cmdp = list_head(&slot->s_cmdlist)) == NULL) {
+ sda_slot_exit(slot);
+ continue;
+ }
+
+ datline = ((cmdp->sc_flags & SDA_CMDF_DAT) != 0);
+
+ if (datline) {
+ /*
+ * If the current command has a data phase
+ * while a transfer is in progress, then go
+ * back to sleep.
+ */
+ if (slot->s_xfrp != NULL) {
+ sda_slot_exit(slot);
+ continue;
+ }
+
+ /*
+ * Note that APP_CMD doesn't have a data phase,
+ * although the associated ACMD might.
+ */
+ if (cmdp->sc_index != CMD_APP_CMD) {
+ slot->s_xfrp = cmdp;
+ /*
+ * All commands should complete in
+ * less than 5 seconds. The worst
+ * case is actually somewhere around 4
+ * seconds, but that is when the clock
+ * is only 100 kHz.
+ */
+ slot->s_xfrtmo = gethrtime() +
+ 5000000000ULL;
+ (void) sda_setprop(slot, SDA_PROP_LED, 1);
+ }
+ }
+
+ /*
+ * We're committed to dispatching this command now,
+ * so remove it from the list.
+ */
+ list_remove(&slot->s_cmdlist, cmdp);
+
+ /*
+ * There could be more commands after this one, so we
+ * mark ourself so we stay awake for another cycle.
+ */
+ sda_slot_wakeup(slot);
+
+ /*
+ * Submit the command. Note that we are holding the
+ * slot lock here, so it is critical that the caller
+ * *not* call back up into the framework. The caller
+ * must break context. But doing it this way prevents
+ * a critical race on card removal.
+ *
+ * During initialization, we reject any commands that
+ * are not from the initialization code. This does
+ * have the side effect of removing them.
+ *
+ * Note that we don't resubmit memory to the device if
+ * it isn't flagged as ready (e.g. if the wrong device
+ * was inserted!)
+ */
+ if (((!slot->s_ready) && (cmdp->sc_flags & SDA_CMDF_MEM)) ||
+ (slot->s_init && !(cmdp->sc_flags & SDA_CMDF_INIT))) {
+ rv = SDA_ENODEV;
+ if (!slot->s_warn) {
+ sda_slot_err(slot,
+ "Device removed while in use. "
+ "Please reinsert!");
+ slot->s_warn = B_TRUE;
+ }
+ } else {
+ rv = slot->s_ops.so_cmd(slot->s_prv, cmdp);
+ }
+ if (rv == SDA_EOK)
+ rv = sda_slot_check_response(cmdp);
+
+ if (rv == SDA_EOK) {
+ /*
+ * If APP_CMD completed properly, then
+ * resubmit with ACMD index. Note wake was
+ * already set above.
+ */
+ if (cmdp->sc_index == CMD_APP_CMD) {
+ if ((cmdp->sc_response[0] & R1_APP_CMD) == 0) {
+ sda_slot_log(slot, "APP_CMD not set!");
+ }
+ sda_cmd_resubmit_acmd(slot, cmdp);
+ sda_slot_exit(slot);
+
+ continue;
+ }
+
+ } else if (datline) {
+ /*
+ * If an error occurred and we were expecting
+ * a transfer phase, we have to clean up.
+ */
+ (void) sda_setprop(slot, SDA_PROP_LED, 0);
+ slot->s_xfrp = NULL;
+ slot->s_xfrtmo = 0;
+
+ /*
+ * And notify any waiter.
+ */
+ sda_slot_exit(slot);
+ sda_cmd_notify(cmdp, SDA_CMDF_BUSY | SDA_CMDF_DAT, rv);
+ continue;
+ }
+
+ /*
+ * Wake any waiter.
+ */
+ sda_slot_exit(slot);
+ sda_cmd_notify(cmdp, SDA_CMDF_BUSY, rv);
+ }
+
+#ifdef __lock_lint
+ mutex_exit(&slot->s_evlock);
+#else
+ CALLB_CPR_EXIT(&cprinfo);
+#endif
+
+ thread_exit();
+}
+
+void
+sda_slot_vprintf(sda_slot_t *s, int level, const char *fmt, va_list ap)
+{
+ char msgbuf[256];
+ const char *pfx, *sfx;
+
+ if (level == CE_CONT) {
+ pfx = "!";
+ sfx = "\n";
+ } else {
+ pfx = sfx = "";
+ }
+
+ if (s != NULL) {
+ dev_info_t *dip = s->s_host->h_dip;
+
+ (void) snprintf(msgbuf, sizeof (msgbuf),
+ "%s%s%d: slot %d: %s%s", pfx,
+ ddi_driver_name(dip), ddi_get_instance(dip),
+ s->s_slot_num, fmt, sfx);
+ } else {
+ (void) snprintf(msgbuf, sizeof (msgbuf), "%ssda: %s%s",
+ pfx, fmt, sfx);
+ }
+ vcmn_err(level, msgbuf, ap);
+}
+
+void
+sda_slot_err(sda_slot_t *s, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ sda_slot_vprintf(s, CE_WARN, fmt, ap);
+ va_end(ap);
+}
+
+void
+sda_slot_log(sda_slot_t *s, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ sda_slot_vprintf(s, CE_CONT, fmt, ap);
+ va_end(ap);
+}
diff --git a/usr/src/uts/common/io/sdcard/targets/sdcard/sdcard.c b/usr/src/uts/common/io/sdcard/targets/sdcard/sdcard.c
new file mode 100644
index 0000000000..4b5d7356c0
--- /dev/null
+++ b/usr/src/uts/common/io/sdcard/targets/sdcard/sdcard.c
@@ -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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * SD memory card target driver. It relies on the SDA common
+ * framework, and translates to SCSA. That is to say, it emulates a
+ * simple SCSI block device.
+ *
+ * The entire driver is a tiny shim for the SDA framework, because to
+ * make life simplify and reduce layering overhead, we just use implementation
+ * in the SDA framework.
+ *
+ * (We have to be a separate driver, unfortunately, because SDA nexus drivers
+ * need to support SDIO and memory targets, and there can only be one bus_ops
+ * per driver.)
+ */
+
+#include <sys/types.h>
+#include <sys/modctl.h>
+#include <sys/conf.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+
+
+/* our entire API with SDA is miniscule */
+extern void sda_mem_init(struct modlinkage *);
+extern void sda_mem_fini(struct modlinkage *);
+
+static struct dev_ops sdcard_devops = {
+ DEVO_REV,
+ 0,
+ NULL,
+ nulldev,
+ nulldev,
+ NULL,
+ NULL,
+ nodev,
+ NULL, /* cb_ops */
+ NULL, /* bus_ops */
+ NULL, /* power */
+};
+
+static struct modldrv modldrv = {
+ &mod_driverops,
+ "SD Memory Slot",
+ &sdcard_devops,
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1, { &modldrv, NULL }
+};
+
+int
+_init(void)
+{
+ int rv;
+
+ sda_mem_init(&modlinkage);
+
+ if ((rv = mod_install(&modlinkage)) != 0) {
+ sda_mem_fini(&modlinkage);
+ return (rv);
+ }
+ return (rv);
+}
+
+int
+_fini(void)
+{
+ int rv;
+
+ if ((rv = mod_remove(&modlinkage)) == 0) {
+ sda_mem_fini(&modlinkage);
+ return (rv);
+ }
+ return (rv);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
diff --git a/usr/src/uts/common/io/warlock/blk2scsa.wlcmd b/usr/src/uts/common/io/warlock/blk2scsa.wlcmd
new file mode 100644
index 0000000000..b72bc7c2ed
--- /dev/null
+++ b/usr/src/uts/common/io/warlock/blk2scsa.wlcmd
@@ -0,0 +1,63 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+root b2s_mod_fini
+root b2s_mod_init
+root b2s_alloc_nexus
+root b2s_attach_leaf
+root b2s_attach_nexus
+root b2s_detach_leaf
+root b2s_detach_nexus
+root b2s_free_nexus
+root b2s_request_dma
+root b2s_tran_tgt_free
+root b2s_tran_tgt_init
+
+root b2s_tran_setup_pkt
+root b2s_tran_start
+root b2s_tran_teardown_pkt
+root b2s_tran_tgt_free
+root b2s_tran_tgt_init
+
+add bus_ops::bus_config targets b2s_bus_config
+
+add scsi_pkt::pkt_comp targets \
+ b2s_tran_teardown_pkt \
+ b2s_tran_setup_pkt \
+ b2s_tran_abort \
+ b2s_tran_reset \
+ b2s_tran_getcap \
+ b2s_tran_setcap \
+ b2s_tran_start
+
+add b2s_nexus::n_request targets b2s_request_dma
+
+add bus_ops::bus_add_eventcall targets warlock_dummy
+add bus_ops::bus_unconfig targets warlock_dummy
+add bus_ops::bus_get_eventcookie targets warlock_dummy
+add bus_ops::bus_intr_ctl targets warlock_dummy
+add bus_ops::bus_post_event targets warlock_dummy
+add bus_ops::bus_remove_eventcall targets warlock_dummy
+
diff --git a/usr/src/uts/common/io/warlock/sdhost.wlcmd b/usr/src/uts/common/io/warlock/sdhost.wlcmd
new file mode 100644
index 0000000000..1414325c61
--- /dev/null
+++ b/usr/src/uts/common/io/warlock/sdhost.wlcmd
@@ -0,0 +1,34 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+root sda_host_log
+root sdhost_poll
+
+add sda_slot::s_ops.so_cmd targets sdhost_cmd \
+ sdhost_halt
+add sda_slot::s_ops.so_getprop targets sdhost_getprop
+add sda_slot::s_ops.so_reset targets sdhost_reset
+add sda_slot::s_ops.so_setprop targets sdhost_setprop
+
diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile
index 728860594a..0095caf0a2 100644
--- a/usr/src/uts/common/sys/Makefile
+++ b/usr/src/uts/common/sys/Makefile
@@ -22,8 +22,6 @@
# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
-# ident "%Z%%M% %I% %E% SMI"
-#
include $(SRC)/uts/Makefile.uts
@@ -859,6 +857,9 @@ SCSITARGETSHDRS= \
sddef.h \
smp.h
+SCSIADHDRS= \
+ blk2scsa.h
+
SCSICADHDRS=
SCSIVHCIHDRS= \
@@ -866,6 +867,11 @@ SCSIVHCIHDRS= \
mpapi_impl.h \
mpapi_scsi_vhci.h
+SDCARDHDRS= \
+ sda.h \
+ sda_impl.h \
+ sda_ioctl.h
+
FCHDRS= \
fc_transport.h \
linkapp.h \
@@ -1049,12 +1055,14 @@ CHECKHDRS= \
$(LVMHDRS:%.h=lvm/%.check) \
$(PCMCIAHDRS:%.h=pcmcia/%.check) \
$(SCSIHDRS:%.h=scsi/%.check) \
- $(SCSICONHDRS:%.h=scsi/conf/%.check) \
+ $(SCSIADHDRS:%.h=scsi/adapters/%.check) \
+ $(SCSICONFHDRS:%.h=scsi/conf/%.check) \
$(SCSIIMPLHDRS:%.h=scsi/impl/%.check) \
$(SCSITARGETSHDRS:%.h=scsi/targets/%.check) \
$(SCSIVHCIHDRS:%.h=scsi/adapters/%.check) \
$(FCHDRS:%.h=fc4/%.check) \
$(SATAGENHDRS:%.h=sata/%.check) \
+ $(SDCARDHDRS:%.h=sdcard/%.check) \
$(SYSEVENTHDRS:%.h=sysevent/%.check) \
$(CONTRACTHDRS:%.h=contract/%.check) \
$(USBAUDHDRS:%.h=usb/clients/audio/%.check) \
@@ -1096,11 +1104,13 @@ CHECKHDRS= \
$(ROOTLVMHDRS) \
$(ROOTPCMCIAHDRS) \
$(ROOTSCSIHDRS) \
+ $(ROOTSCSIADHDRS) \
$(ROOTSCSICONFHDRS) \
$(ROOTSCSIGENHDRS) \
$(ROOTSCSIIMPLHDRS) \
$(ROOTSCSIVHCIHDRS) \
$(ROOTFCHDRS) \
+ $(ROOTSDCARDHDRS) \
$(ROOTSYSEVENTHDRS) \
$(ROOTCONTRACTHDRS) \
$(ROOTUSBHDRS) \
@@ -1144,11 +1154,13 @@ install_h: \
$(ROOTLVMHDRS) \
$(ROOTPCMCIAHDRS) \
$(ROOTSCSIHDRS) \
+ $(ROOTSCSIADHDRS) \
$(ROOTSCSICONFHDRS) \
$(ROOTSCSIGENHDRS) \
$(ROOTSCSIIMPLHDRS) \
$(ROOTSCSIVHCIHDRS) \
$(ROOTFCHDRS) \
+ $(ROOTSDCARDHDRS) \
$(ROOTSYSEVENTHDRS) \
$(ROOTCONTRACTHDRS) \
$(ROOTUSBHDRS) \
diff --git a/usr/src/uts/common/sys/Makefile.syshdrs b/usr/src/uts/common/sys/Makefile.syshdrs
index 26970f5219..66fdf546f2 100644
--- a/usr/src/uts/common/sys/Makefile.syshdrs
+++ b/usr/src/uts/common/sys/Makefile.syshdrs
@@ -18,11 +18,9 @@
#
# CDDL HEADER END
#
-# Copyright 2007 Sun Microsystems, Inc. All rights reserved.
+# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
-# ident "%Z%%M% %I% %E% SMI"
-#
# Common definitions for open and closed headers.
@@ -97,6 +95,9 @@ scsi/targets/%.check: scsi/targets/%.h
scsi/adapters/%.check: scsi/adapters/%.h
$(DOT_H_CHECK)
+sdcard/%.check: sdcard/%.h
+ $(DOT_H_CHECK)
+
sysevent/%.check: sysevent/%.h
$(DOT_H_CHECK)
@@ -178,6 +179,7 @@ ROOTDIRS= \
$(ROOTDIR)/scsi/generic \
$(ROOTDIR)/scsi/impl \
$(ROOTDIR)/fc4 \
+ $(ROOTDIR)/sdcard \
$(ROOTDIR)/sysevent \
$(ROOTDIR)/contract \
$(ROOTDIR)/usb \
@@ -237,10 +239,13 @@ ROOTSCSIGENHDRS= $(SCSIGENHDRS:%=$(ROOTDIR)/scsi/generic/%)
ROOTSCSIIMPLHDRS= $(SCSIIMPLHDRS:%=$(ROOTDIR)/scsi/impl/%)
ROOTSCSITARGETSHDRS= $(SCSITARGETSHDRS:%=$(ROOTDIR)/scsi/targets/%)
ROOTSCSICADHDRS= $(SCSICADHDRS:%=$(ROOTDIR)/scsi/adapters/%)
+ROOTSCSIADHDRS= $(SCSICADHDRS:%=$(ROOTDIR)/scsi/adapters/%)
ROOTSCSIVHCIHDRS= $(SCSIVHCIHDRS:%=$(ROOTDIR)/scsi/adapters/%)
ROOTFCHDRS= $(FCHDRS:%=$(ROOTDIR)/fc4/%)
+ROOTSDCARDHDRS= $(SDCARDHDRS:%=$(ROOTDIR)/sdcard/%)
+
ROOTSYSEVENTHDRS= $(SYSEVENTHDRS:%=$(ROOTDIR)/sysevent/%)
ROOTCONTRACTHDRS= $(CONTRACTHDRS:%=$(ROOTDIR)/contract/%)
@@ -346,6 +351,9 @@ $(ROOTDIR)/scsi/targets/%: scsi/targets/%
$(ROOTDIR)/fc4/%: fc4/%
$(INS.file)
+$(ROOTDIR)/sdcard/%: sdcard/%
+ $(INS.file)
+
$(ROOTDIR)/sysevent/%: sysevent/%
$(INS.file)
diff --git a/usr/src/uts/common/sys/scsi/adapters/blk2scsa.h b/usr/src/uts/common/sys/scsi/adapters/blk2scsa.h
new file mode 100644
index 0000000000..7b825dd6e4
--- /dev/null
+++ b/usr/src/uts/common/sys/scsi/adapters/blk2scsa.h
@@ -0,0 +1,182 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_SCSI_ADAPTERS_BLK2SCSA_H
+#define _SYS_SCSI_ADAPTERS_BLK2SCSA_H
+
+#include <sys/types.h>
+#include <sys/ksynch.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct b2s_nexus_info b2s_nexus_info_t;
+typedef struct b2s_leaf_info b2s_leaf_info_t;
+typedef struct b2s_media b2s_media_t;
+typedef struct b2s_inquiry b2s_inquiry_t;
+typedef struct b2s_request b2s_request_t;
+typedef struct b2s_nexus b2s_nexus_t;
+typedef struct b2s_leaf b2s_leaf_t;
+
+struct b2s_media {
+ uint64_t media_blksz;
+ uint64_t media_nblks;
+ uint64_t media_flags;
+};
+#define B2S_MEDIA_FLAG_READ_ONLY (1U << 1)
+#define B2S_MEDIA_FLAG_LOCKED (1U << 2)
+
+
+struct b2s_inquiry {
+ const char *inq_vendor;
+ const char *inq_product;
+ const char *inq_revision;
+ const char *inq_serial;
+};
+
+struct b2s_nexus_info {
+ int nexus_version;
+ dev_info_t *nexus_dip;
+ void *nexus_private;
+ ddi_dma_attr_t *nexus_dma_attr;
+ boolean_t (*nexus_request)(void *, b2s_request_t *);
+};
+
+struct b2s_leaf_info {
+ uint_t leaf_target;
+ uint_t leaf_lun;
+ uint32_t leaf_flags;
+ const char *leaf_unique_id;
+};
+
+#define B2S_LEAF_REMOVABLE (1U << 0)
+#define B2S_LEAF_HOTPLUGGABLE (1U << 1)
+/* these values reserved! */
+#define B2S_LEAF_DETACHED (1U << 16)
+
+typedef enum {
+ B2S_CMD_GETMEDIA = 0, /* get content */
+ B2S_CMD_FORMAT = 1, /* format media */
+ B2S_CMD_START = 2, /* spin up */
+ B2S_CMD_STOP = 3, /* spin down */
+ B2S_CMD_LOCK = 4, /* lock media door */
+ B2S_CMD_UNLOCK = 5, /* unlock media door */
+ B2S_CMD_READ = 6, /* read blocks */
+ B2S_CMD_WRITE = 7, /* write blocks */
+ B2S_CMD_SYNC = 8, /* flush write cache */
+ B2S_CMD_INQUIRY = 9, /* inquiry data */
+ B2S_CMD_RESET = 10, /* reset of bus */
+ B2S_CMD_ABORT = 11, /* abort inflight commands */
+} b2s_cmd_t;
+
+typedef enum {
+ B2S_EOK = 0, /* success */
+ B2S_ENOTSUP = 1, /* operation not sup */
+ B2S_EFORMATTING = 2, /* busy formatting */
+ B2S_ENOMEDIA = 3, /* media not mounted */
+ B2S_EMEDIACHG = 4, /* media changed */
+ B2S_ESTOPPED = 5, /* unit not started */
+ B2S_EBLKADDR = 6, /* blkno invalid */
+ B2S_EIO = 7, /* general failure */
+ B2S_EHARDWARE = 8, /* hardware error */
+ B2S_ENODEV = 9, /* hardware removed */
+ B2S_EMEDIA = 10, /* media problem */
+ B2S_EDOORLOCK = 11, /* door lock engaged */
+ B2S_EWPROTECT = 12, /* write protected */
+ B2S_ESTARTING = 13, /* unit spinning up */
+ B2S_ETIMEDOUT = 14, /* request timed out */
+ B2S_ENOMEM = 15, /* out of memory */
+ B2S_ERESET = 16, /* reset aborted command */
+ B2S_EABORT = 17, /* aborted command */
+
+ /* these are framework internal use only */
+ B2S_ERSVD = 18, /* unit reserved */
+ B2S_EINVAL = 19, /* invalid parameter */
+ B2S_EPARAM = 20, /* bad parameter */
+ B2S_EBADMSG = 21, /* malformed message */
+ B2S_ENOSAV = 22, /* no saveable parms */
+
+ /* used internally for array sizing, must be last */
+ B2S_NERRS = 23
+} b2s_err_t;
+
+#define B2S_REQUEST_FLAG_POLL (1U << 0) /* use polled io */
+#define B2S_REQUEST_FLAG_HEAD (1U << 1)
+#define B2S_REQUEST_FLAG_DONE (1U << 2)
+#define B2S_REQUEST_FLAG_LOAD_EJECT (1U << 3) /* for start/stop */
+#define B2S_REQUEST_FLAG_IMMED (1U << 4) /* get status immed */
+/* framework internal flags */
+#define B2S_REQUEST_FLAG_BLKS (1U << 16) /* block-oriented */
+#define B2S_REQUEST_FLAG_MAPIN (1U << 17) /* bp_mapin done */
+
+struct b2s_request {
+ b2s_cmd_t br_cmd;
+ b2s_err_t br_errno;
+ uint_t br_target;
+ uint_t br_lun;
+ uint32_t br_flags;
+
+ /* note that this member should come last for future expansion */
+ union {
+ uint64_t a_ints[3];
+ b2s_media_t a_media;
+ b2s_inquiry_t a_inquiry;
+ } br_args;
+};
+#define br_lba br_args.a_ints[0]
+#define br_nblks br_args.a_ints[1]
+#define br_media br_args.a_media
+#define br_inquiry br_args.a_inquiry
+
+
+int b2s_mod_init(struct modlinkage *);
+void b2s_mod_fini(struct modlinkage *);
+
+/* used as version to alloc_hba */
+#define B2S_VERSION_0 0
+
+b2s_nexus_t *b2s_alloc_nexus(b2s_nexus_info_t *);
+void b2s_free_nexus(b2s_nexus_t *);
+int b2s_attach_nexus(b2s_nexus_t *);
+int b2s_detach_nexus(b2s_nexus_t *);
+
+b2s_leaf_t *b2s_attach_leaf(b2s_nexus_t *, b2s_leaf_info_t *);
+void b2s_detach_leaf(b2s_leaf_t *);
+
+/*
+ * Address information.
+ */
+void b2s_request_mapin(b2s_request_t *, caddr_t *, size_t *);
+void b2s_request_dma(b2s_request_t *, uint_t *, ddi_dma_cookie_t **);
+void b2s_request_done(b2s_request_t *, b2s_err_t, size_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_SCSI_ADAPTERS_BLK2SCSA_H */
diff --git a/usr/src/uts/common/sys/sdcard/sda.h b/usr/src/uts/common/sys/sdcard/sda.h
new file mode 100644
index 0000000000..5f48cf8403
--- /dev/null
+++ b/usr/src/uts/common/sys/sdcard/sda.h
@@ -0,0 +1,327 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_SDCARD_SDA_H
+#define _SYS_SDCARD_SDA_H
+
+#include <sys/types.h>
+#include <sys/note.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * SD card common framework. This module provides most of the common
+ * functionality so that SecureDigital host adapters and client devices
+ * (such as the sdmem driver) can share common code.
+ */
+
+/*
+ * SD Commands. Commmand format is 48-bits as follows:
+ *
+ * bits value desc
+ * -------------------------------------------
+ * 47 0 start bit
+ * 46 1 transmission bit
+ * 45:40 cmd command index (see values listed below)
+ * 39:8 arg 32-bit command argument
+ * 7:1 crc7 crc7 check value
+ * 0 1 end bit
+ * -------------------------------------------
+ */
+typedef enum {
+ CMD_GO_IDLE = 0,
+ CMD_SEND_OCR = 1, /* MMC only */
+ CMD_BCAST_CID = 2,
+ CMD_SEND_RCA = 3,
+ CMD_SET_DSR = 4,
+ CMD_IO_SEND_OCR = 5, /* SDIO only */
+ CMD_SWITCH_FUNC = 6,
+ CMD_SELECT_CARD = 7,
+ CMD_SEND_IF_COND = 8,
+ CMD_SEND_CSD = 9,
+ CMD_SEND_CID = 10,
+ CMD_STOP_TRANSMIT = 12,
+ CMD_SEND_STATUS = 13,
+ CMD_GO_INACTIVE = 15,
+ CMD_SET_BLOCKLEN = 16,
+ CMD_READ_SINGLE = 17,
+ CMD_READ_MULTI = 18,
+ CMD_WRITE_SINGLE = 24,
+ CMD_WRITE_MULTI = 25,
+ CMD_PROGRAM_CSD = 27,
+ CMD_SET_WRITE_PROT = 28,
+ CMD_CLR_WRITE_PROT = 29,
+ CMD_SEND_WRITE_PROT = 30,
+ CMD_ERASE_START = 32,
+ CMD_ERASE_END = 33,
+ CMD_ERASE = 38,
+ CMD_LOCK = 42,
+ CMD_IO_RW_DIRECT = 52,
+ CMD_IO_RW_EXTENDED = 53,
+ CMD_APP_CMD = 55,
+ CMD_GEN_CMD = 56,
+ /* APP CMD values, send ACMD first */
+ ACMD_SET_BUS_WIDTH = 6,
+ ACMD_SD_STATUS = 13,
+ ACMD_SEND_NUM_WR_BLKS = 22,
+ ACMD_SET_WR_BLK_ERASE_COUNT = 23,
+ ACMD_SD_SEND_OCR = 41,
+ ACMD_SET_CLR_CARD_DETECT = 42,
+ ACMD_SEND_SCR = 51
+} sda_index_t;
+
+/*
+ * Classes of response type. Note that we encode the "busy bit" as
+ * value 0x10.
+ */
+typedef enum {
+ R0 = 0,
+ R1 = 1,
+ R2 = 2,
+ R3 = 3,
+ R4 = 4,
+ R5 = 5,
+ R6 = 6,
+ R7 = 7,
+ Rb = 0x10,
+ R1b = 0x11,
+ R5b = 0x15
+} sda_rtype_t;
+
+/*
+ * R1 status bits.
+ */
+#define R1_OUT_OF_RANGE (1U << 31)
+#define R1_ADDRESS_ERROR (1U << 30)
+#define R1_BLOCK_LEN_ERROR (1U << 29)
+#define R1_ERASE_SEQ_ERROR (1U << 28)
+#define R1_ERASE_PARAM (1U << 27)
+#define R1_WP_VIOLATION (1U << 26)
+#define R1_CARD_IS_LOCKED (1U << 25)
+#define R1_LOCK_FAILED (1U << 24)
+#define R1_COM_CRC_ERROR (1U << 23)
+#define R1_ILLEGAL_COMMAND (1U << 22)
+#define R1_CARD_ECC_FAILED (1U << 21)
+#define R1_CC_ERROR (1U << 20)
+#define R1_ERROR (1U << 19)
+#define R1_CSD_OVERWRITE (1U << 16)
+#define R1_WP_ERASE_SKIP (1U << 15)
+#define R1_CARD_ECC_DIS (1U << 14)
+#define R1_ERASE_RESET (1U << 13)
+#define R1_READY_FOR_DATA (1U << 8)
+#define R1_APP_CMD (1U << 5)
+#define R1_AKE_SEQ_ERROR (1U << 3)
+
+/*
+ * Note that R1_COM_CRC_ERR, R1_ILLEGAL_COMMAND, R1_ERASE_SEQ_ERROR, and
+ * R1_AKE_SEQ_ERROR errors are delayed error bits reported on the next
+ * command. So we don't list them here.
+ */
+#define R1_ERRS (\
+ R1_ERROR | R1_OUT_OF_RANGE | R1_ADDRESS_ERROR | R1_BLOCK_LEN_ERROR | \
+ R1_ERASE_PARAM | R1_WP_VIOLATION | R1_LOCK_FAILED | \
+ R1_CARD_ECC_FAILED | R1_CC_ERROR | R1_CSD_OVERWRITE | \
+ R1_WP_ERASE_SKIP)
+
+#define R1_STATE(x) (((x) & 0xf) >> 9)
+
+/*
+ * R5 status bits.
+ */
+#define R5_COM_CRC_ERROR (1U << 7)
+#define R5_ILLEGAL_COMMAND (1U << 6)
+#define R5_ERROR (1U << 3)
+#define R5_RFU (1U << 2)
+#define R5_FUNCTION_NUMBER (1U << 1)
+#define R5_OUT_OF_RANGE (1U << 0)
+
+#define R5_ERRS (R5_ERROR | R5_FUNCTION_NUMBER | R5_OUT_OF_RANGE)
+
+#define R5_IO_STATE(x) (((x) & 0x3) >> 4)
+
+/*
+ * R7 bits (CMD8).
+ */
+#define R7_VHS_27_36V (1U << 8)
+#define R7_PATTERN (0xAA)
+
+/*
+ * OCR bits.
+ */
+#define OCR_POWER_UP (1U << 31)
+#define OCR_CCS (1U << 30)
+#define OCR_FUNCS(x) (((x) & 7) >> 28) /* SDIO only */
+#define OCR_MEM_PRESENT (1U << 27) /* SDIO only */
+#define OCR_VOLTAGE_MASK (0xffffffU) /* (bits 0-23 */
+#define OCR_HI_MASK (0xff8000U) /* 2.7-3.6V */
+#define OCR_35_36V (1U << 23)
+#define OCR_34_35V (1U << 22)
+#define OCR_33_34V (1U << 21)
+#define OCR_32_33V (1U << 20)
+#define OCR_31_32V (1U << 19)
+#define OCR_30_31V (1U << 18)
+#define OCR_29_30V (1U << 17)
+#define OCR_28_29V (1U << 16)
+#define OCR_27_28V (1U << 15)
+#define OCR_26_27V (1U << 14)
+#define OCR_25_26V (1U << 14)
+#define OCR_24_25V (1U << 13)
+#define OCR_23_24V (1U << 12)
+#define OCR_22_23V (1U << 11)
+#define OCR_21_22V (1U << 10)
+#define OCR_20_21V (1U << 9)
+#define OCR_19_20V (1U << 8)
+#define OCR_18_19V (1U << 7)
+#define OCR_17_18V (1U << 6)
+
+
+/*
+ * Command structure. Used internally by the framework, and by host
+ * drivers. Note that it is forbidden to depend on the size of this
+ * structure.
+ */
+typedef struct sda_cmd sda_cmd_t;
+
+struct sda_cmd {
+ /*
+ * The ordering of these is done to maximize packing.
+ */
+ sda_index_t sc_index; /* command name */
+ sda_rtype_t sc_rtype; /* response type expected */
+ uint16_t sc_flags;
+ uint32_t sc_argument; /* command argument */
+
+ uint32_t sc_response[4];
+
+ uint16_t sc_nblks;
+ uint16_t sc_blksz;
+
+ uint32_t sc_resid;
+
+ uint_t sc_ndmac; /* # DMA cookies */
+ ddi_dma_cookie_t *sc_dmacs; /* actual DMA cookies */
+ caddr_t sc_kvaddr; /* kernel virtual address */
+
+#define SDA_CMDF_READ 0x0001 /* transfer direction */
+#define SDA_CMDF_WRITE 0x0002 /* transfer direction */
+#define SDA_CMDF_AUTO_CMD12 0x0004 /* cmd12 requested */
+/* private flags .. not for driver consumption */
+#define SDA_CMDF_DAT 0x0100 /* data phase pending */
+#define SDA_CMDF_BUSY 0x0200 /* cmd in-flight or queued */
+#define SDA_CMDF_INIT 0x0400 /* initialization command */
+#define SDA_CMDF_MEM 0x0800 /* memory target command */
+};
+
+_NOTE(SCHEME_PROTECTS_DATA("unshared request", sda_cmd))
+
+/*
+ * The framework has two APIs. The first API is for host controllers,
+ * and is referred to as SDHOST. The second API is for target devices,
+ * and is referred to as SDCLIENT. Please don't mix and match usage outside
+ * of the framework implementation itself!
+ */
+
+typedef struct sda_host sda_host_t;
+
+typedef enum {
+ SDA_PROP_INSERTED = 1, /* R: is card inserted? */
+ SDA_PROP_WPROTECT = 2, /* R: is card write protected */
+ SDA_PROP_LED = 3, /* W: LED */
+ SDA_PROP_CLOCK = 4, /* R: frequency, Hz */
+ SDA_PROP_BUSWIDTH = 5, /* W: bus width */
+ SDA_PROP_OCR = 6, /* RW: ocr R: supported, W: set curr */
+ SDA_PROP_CAP_4BITS = 7, /* R: 4 bit data bus? */
+ SDA_PROP_CAP_8BITS = 8, /* R: MMC future expansion */
+ SDA_PROP_CAP_HISPEED = 9, /* R: fast bus rates (> 25MHz) */
+ SDA_PROP_CAP_INTR = 10, /* R: SDIO interrupt support */
+ SDA_PROP_CAP_NOPIO = 11, /* R: Never needs bp_mapin */
+ SDA_PROP_HISPEED = 12 /* W: high speed (>25MHz) */
+} sda_prop_t;
+
+typedef enum {
+ SDA_FAULT_NONE = 0, /* No failure */
+ SDA_FAULT_ACMD12 = 1, /* Auto CMD12 failure */
+ SDA_FAULT_CRC7 = 2, /* CRC7 failure on CMD/DAT line */
+ SDA_FAULT_PROTO = 3, /* SD/MMC protocol error */
+ SDA_FAULT_CURRENT = 4, /* Current overlimit detected */
+ SDA_FAULT_INIT = 5, /* Card initialization failure */
+ SDA_FAULT_TIMEOUT = 6, /* Unexpected timeout failure */
+ SDA_FAULT_HOST = 7, /* Internal host or slot failure */
+ SDA_FAULT_RESET = 8, /* Slot failed to reset */
+} sda_fault_t;
+
+typedef enum {
+ SDA_EOK = 0, /* Success */
+ SDA_ECRC7 = 1, /* CRC7 failure */
+ SDA_EPROTO = 2, /* SD/MMC protocol error */
+ SDA_EINVAL = 3, /* Invalid argument */
+ SDA_ETIME = 4, /* Timeout */
+ SDA_ECMD12 = 5, /* Failed during stop cmd */
+ SDA_ENOTSUP = 6, /* Setting/property not supported */
+ SDA_ERESID = 7, /* Incomplete transfer */
+ SDA_EFAULT = 8, /* Previous fault condition present */
+ SDA_ENOMEM = 9, /* Memory exhausted */
+ SDA_EWPROTECT = 10, /* Media is write protected */
+ SDA_ENODEV = 11, /* Card removed */
+ SDA_ERESET = 12, /* Memory card reset */
+ SDA_EABORT = 13, /* Memory command aborted */
+ SDA_EIO = 14, /* Other generic error */
+ SDA_ESUSPENDED = 15, /* Slot has been suspended */
+} sda_err_t;
+
+typedef struct sda_ops {
+ int so_version;
+#define SDA_OPS_VERSION 1
+ sda_err_t (*so_cmd)(void *, sda_cmd_t *);
+ sda_err_t (*so_getprop)(void *, sda_prop_t, uint32_t *);
+ sda_err_t (*so_setprop)(void *, sda_prop_t, uint32_t);
+ sda_err_t (*so_poll)(void *);
+ sda_err_t (*so_reset)(void *);
+ sda_err_t (*so_halt)(void *);
+} sda_ops_t;
+
+/*
+ * Host operations.
+ */
+void sda_host_init_ops(struct dev_ops *);
+void sda_host_fini_ops(struct dev_ops *);
+sda_host_t *sda_host_alloc(dev_info_t *, int, sda_ops_t *, ddi_dma_attr_t *);
+void sda_host_free(sda_host_t *);
+void sda_host_set_private(sda_host_t *, int, void *);
+int sda_host_attach(sda_host_t *);
+void sda_host_detach(sda_host_t *);
+void sda_host_detect(sda_host_t *, int);
+void sda_host_fault(sda_host_t *, int, sda_fault_t);
+void sda_host_transfer(sda_host_t *, int, sda_err_t);
+/*PRINTFLIKE3*/
+void sda_host_log(sda_host_t *, int, const char *, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_SDCARD_SDA_H */
diff --git a/usr/src/uts/common/sys/sdcard/sda_impl.h b/usr/src/uts/common/sys/sdcard/sda_impl.h
new file mode 100644
index 0000000000..c71c897709
--- /dev/null
+++ b/usr/src/uts/common/sys/sdcard/sda_impl.h
@@ -0,0 +1,283 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_SDCARD_SDA_IMPL_H
+#define _SYS_SDCARD_SDA_IMPL_H
+
+#include <sys/list.h>
+#include <sys/ksynch.h>
+#include <sys/note.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/sdcard/sda.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Type and structure definitions.
+ */
+typedef struct sda_slot sda_slot_t;
+
+/*
+ * Per slot state.
+ */
+struct sda_slot {
+ sda_host_t *s_host;
+ void *s_prv; /* bus private data */
+
+ int s_slot_num;
+ boolean_t s_inserted;
+ boolean_t s_failed;
+ uint8_t s_num_io;
+ uint32_t s_cur_ocr; /* current ocr */
+
+ uint16_t s_rca;
+ uint32_t s_maxclk; /* maximum freq for card */
+
+ sda_cmd_t *s_xfrp; /* pending transfer cmd */
+ hrtime_t s_xfrtmo; /* transfer timeout */
+
+ boolean_t s_reap;
+ boolean_t s_warn;
+ boolean_t s_ready; /* target node ready */
+ boolean_t s_init; /* slot initializing */
+
+ /* these are protected by the evlock */
+ boolean_t s_wake; /* wake up thread */
+ boolean_t s_detach; /* detach in progress */
+ boolean_t s_detect; /* detect event occurred */
+ sda_fault_t s_fault;
+ boolean_t s_xfrdone; /* transfer event occurred */
+ sda_err_t s_errno;
+
+ uint16_t s_flags;
+#define SLOTF_WRITABLE 0x0004
+#define SLOTF_4BITS 0x0008
+#define SLOTF_IFCOND 0x0010
+#define SLOTF_MMC 0x0020
+#define SLOTF_SDMEM 0x0040
+#define SLOTF_SDIO 0x0080
+#define SLOTF_SDHC 0x0100
+#define SLOTF_MEMORY (SLOTF_MMC | SLOTF_SDMEM)
+#define SLOTF_SD (SLOTF_SDMEM | SLOTF_SDIO)
+
+ uint16_t s_caps;
+#define SLOT_CAP_NOPIO 0x0002
+#define SLOT_CAP_HISPEED 0x0004
+#define SLOT_CAP_4BITS 0x0008
+
+ list_t s_cmdlist;
+ list_t s_abortlist;
+
+ /*
+ * Slot operations. Slot local copy for performance.
+ */
+ sda_ops_t s_ops;
+
+ /*
+ * Recursive locking of slot.
+ */
+ kmutex_t s_lock;
+ kcondvar_t s_cv;
+ kt_did_t s_owner; /* owner holding the slot */
+ uint32_t s_circular; /* circular sda_slot_enter() calls */
+
+ /*
+ * Event notification/thread wakeup.
+ */
+ kmutex_t s_evlock;
+ kcondvar_t s_evcv;
+
+ /*
+ * Asynch. threads.
+ */
+ kt_did_t s_thrid; /* processing thread id */
+ ddi_taskq_t *s_tq; /* insert taskq */
+
+ /*
+ * Timestamping for cfgadm benefit.
+ */
+ uint8_t s_intransit;
+ time_t s_stamp;
+
+ /*
+ * Memory card-specific.
+ */
+ uint32_t s_rcsd[4]; /* raw csd */
+ uint32_t s_rcid[4]; /* raw cid */
+ uint32_t s_nblks; /* total blocks on device */
+ uint16_t s_blksz; /* device block size (typ. 512) */
+ uint16_t s_bshift; /* block address shift factor */
+ uint32_t s_speed; /* max memory clock in hz */
+
+ /* Other CID and CSD values */
+ uint32_t s_mfg; /* mfg id */
+ char s_prod[8]; /* product id */
+ char s_oem[2]; /* oem id */
+ uint32_t s_serial;
+ uint8_t s_majver;
+ uint8_t s_minver;
+ uint16_t s_year;
+ uint8_t s_month;
+
+ uint16_t s_ccc; /* card command classes */
+ uint8_t s_r2w; /* read/write factor */
+ uint8_t s_dsr; /* DSR implemented? */
+ uint8_t s_perm_wp; /* permanent write protect set? */
+ uint8_t s_temp_wp; /* temporary write protect set? */
+
+ char s_uuid[40]; /* fabricated universal unique id */
+
+ struct b2s_nexus *s_nexus;
+ struct b2s_leaf *s_leaf;
+};
+
+_NOTE(MUTEX_PROTECTS_DATA(sda_slot::s_lock, sda_slot::s_circular))
+_NOTE(MUTEX_PROTECTS_DATA(sda_slot::s_evlock, sda_slot::s_wake))
+_NOTE(MUTEX_PROTECTS_DATA(sda_slot::s_evlock, sda_slot::s_detach))
+_NOTE(MUTEX_PROTECTS_DATA(sda_slot::s_evlock, sda_slot::s_detect))
+_NOTE(MUTEX_PROTECTS_DATA(sda_slot::s_evlock, sda_slot::s_fault))
+_NOTE(MUTEX_PROTECTS_DATA(sda_slot::s_evlock, sda_slot::s_xfrdone))
+_NOTE(MUTEX_PROTECTS_DATA(sda_slot::s_evlock, sda_slot::s_errno))
+_NOTE(SCHEME_PROTECTS_DATA("slot_enter", sda_slot::s_warn))
+_NOTE(SCHEME_PROTECTS_DATA("slot_enter", sda_slot::s_xfrtmo))
+_NOTE(SCHEME_PROTECTS_DATA("slot_enter", sda_slot::s_xfrp))
+
+/*
+ * Per host state. One per devinfo node. There could be multiple
+ * slots per devinfo node.
+ */
+struct sda_host {
+ dev_info_t *h_dip;
+ int h_nslot;
+ sda_slot_t *h_slots;
+ ddi_dma_attr_t *h_dma; /* dma attr, needed for mem */
+
+ list_node_t h_node; /* nexus node linkage */
+
+ uint32_t h_flags;
+#define HOST_ATTACH (1U << 0) /* host attach completed */
+#define HOST_XOPEN (1U << 2) /* exclusive open */
+#define HOST_SOPEN (1U << 3) /* shared open */
+};
+
+_NOTE(SCHEME_PROTECTS_DATA("stable data", sda_host::h_dip))
+_NOTE(SCHEME_PROTECTS_DATA("stable data", sda_host::h_nslot))
+_NOTE(SCHEME_PROTECTS_DATA("stable data", sda_host::h_dma))
+
+/*
+ * Useful function-like macros.
+ */
+#define sda_setprop(s, p, v) s->s_ops.so_setprop(s->s_prv, p, v)
+#define sda_getprop(s, p, v) s->s_ops.so_getprop(s->s_prv, p, v)
+
+/*
+ * sda_cmd.c
+ */
+void sda_cmd_init(void);
+void sda_cmd_fini(void);
+void sda_cmd_list_init(list_t *);
+void sda_cmd_list_fini(list_t *);
+sda_cmd_t *sda_cmd_alloc(sda_slot_t *, sda_index_t, uint32_t, sda_rtype_t,
+ void *, int);
+sda_cmd_t *sda_cmd_alloc_acmd(sda_slot_t *, sda_index_t, uint32_t, sda_rtype_t,
+ void *, int);
+void sda_cmd_free(sda_cmd_t *);
+sda_err_t sda_cmd_errno(sda_cmd_t *);
+void *sda_cmd_data(sda_cmd_t *);
+void sda_cmd_submit(sda_slot_t *, sda_cmd_t *, void (*)(sda_cmd_t *));
+void sda_cmd_resubmit_acmd(sda_slot_t *, sda_cmd_t *);
+void sda_cmd_notify(sda_cmd_t *, uint16_t, sda_err_t);
+sda_err_t sda_cmd_exec(sda_slot_t *, sda_cmd_t *, uint32_t *);
+
+/*
+ * sda_init.c
+ */
+sda_err_t sda_init_card(sda_slot_t *);
+
+/*
+ * sda_mem.c
+ */
+void sda_mem_init(struct modlinkage *);
+void sda_mem_fini(struct modlinkage *);
+uint32_t sda_mem_maxclk(sda_slot_t *);
+uint32_t sda_mem_getbits(uint32_t *, int, int);
+
+
+/*
+ * sda_nexus.c
+ */
+void sda_nexus_init(void);
+void sda_nexus_fini(void);
+void sda_nexus_register(sda_host_t *);
+void sda_nexus_unregister(sda_host_t *);
+int sda_nexus_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
+int sda_nexus_open(dev_t *, int, int, cred_t *);
+int sda_nexus_close(dev_t, int, int, cred_t *);
+int sda_nexus_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
+int sda_nexus_bus_ctl(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *,
+ void *);
+void sda_nexus_remove(sda_slot_t *);
+void sda_nexus_insert(sda_slot_t *);
+void sda_nexus_reap(void *);
+
+/*
+ * sda_slot.c
+ */
+void sda_slot_init(sda_slot_t *);
+void sda_slot_fini(sda_slot_t *);
+void sda_slot_enter(sda_slot_t *);
+void sda_slot_exit(sda_slot_t *);
+boolean_t sda_slot_owned(sda_slot_t *);
+void sda_slot_attach(sda_slot_t *);
+void sda_slot_detach(sda_slot_t *);
+void sda_slot_reset(sda_slot_t *);
+void sda_slot_wakeup(sda_slot_t *);
+void sda_slot_detect(sda_slot_t *);
+int sda_slot_power_on(sda_slot_t *);
+void sda_slot_power_off(sda_slot_t *);
+void sda_slot_reset(sda_slot_t *);
+void sda_slot_shutdown(sda_slot_t *);
+void sda_slot_transfer(sda_slot_t *, sda_err_t);
+void sda_slot_mem_reset(sda_slot_t *, sda_err_t);
+void sda_slot_fault(sda_slot_t *, sda_fault_t);
+/*PRINTFLIKE2*/
+void sda_slot_err(sda_slot_t *, const char *, ...);
+/*PRINTFLIKE2*/
+void sda_slot_log(sda_slot_t *, const char *, ...);
+
+#ifdef DEBUG
+#define sda_slot_debug(...) sda_slot_log(__VA_ARGS__)
+#else
+#define sda_slot_debug(...)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_SDCARD_SDA_IMPL_H */
diff --git a/usr/src/uts/common/sys/sdcard/sda_ioctl.h b/usr/src/uts/common/sys/sdcard/sda_ioctl.h
new file mode 100644
index 0000000000..616a571348
--- /dev/null
+++ b/usr/src/uts/common/sys/sdcard/sda_ioctl.h
@@ -0,0 +1,87 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_SDCARD_SDA_IOCTL_H
+#define _SYS_SDCARD_SDA_IOCTL_H
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * These IOCTLs are private between the sdcard cfgadm plugin, and the sda
+ * framework.
+ */
+
+typedef enum {
+ SDA_CT_UNKNOWN,
+ SDA_CT_MMC,
+ SDA_CT_SDMEM,
+ SDA_CT_SDHC,
+ SDA_CT_SDCOMBO,
+ SDA_CT_SDIO /* expand on this later */
+} sda_card_type_t;
+
+typedef struct {
+
+ sda_card_type_t ci_type;
+
+ /* these are only valid for memory cards */
+ uint32_t ci_mfg;
+ char ci_oem[16]; /* mfg id */
+ char ci_pid[16]; /* ASCIIZ product */
+ uint32_t ci_serial;
+ uint8_t ci_month;
+ uint8_t ci_year;
+ uint8_t ci_major;
+ uint8_t ci_minor;
+} sda_card_info_t;
+
+struct sda_ap_control {
+ unsigned cmd;
+ size_t size;
+ void *data;
+};
+
+#ifdef _KERNEL
+struct sda_ap_control32 {
+ unsigned cmd;
+ size32_t size;
+ caddr32_t data;
+};
+#endif
+
+/* AP_CONTROL commands */
+#define SDA_CFGA_GET_CARD_INFO 1
+#define SDA_CFGA_GET_DEVICE_PATH 2
+#define SDA_CFGA_RESET_SLOT 3
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_SDCARD_SDA_IOCTL_H */
diff --git a/usr/src/uts/common/sys/sunddi.h b/usr/src/uts/common/sys/sunddi.h
index af6acd290a..316d38682b 100644
--- a/usr/src/uts/common/sys/sunddi.h
+++ b/usr/src/uts/common/sys/sunddi.h
@@ -27,8 +27,6 @@
#ifndef _SYS_SUNDDI_H
#define _SYS_SUNDDI_H
-#pragma ident "%Z%%M% %I% %E% SMI"
-
/*
* Sun Specific DDI definitions
*/
@@ -213,6 +211,9 @@ extern "C" {
#define DDI_NT_SATA_ATTACHMENT_POINT "ddi_ctl:attachment_point:sata"
/* sata attachment pt */
+#define DDI_NT_SDCARD_ATTACHMENT_POINT "ddi_ctl:attachment_point:sdcard"
+ /* sdcard attachment pt */
+
#define DDI_NT_PCI_ATTACHMENT_POINT "ddi_ctl:attachment_point:pci"
/* PCI attachment pt */
#define DDI_NT_SBD_ATTACHMENT_POINT "ddi_ctl:attachment_point:sbd"