sda_slot.c revision 0a96f64e3185cf2fb9959e528ba7f454ca681484
/*
* 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
* 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 2010 Nexenta Systems, Inc. All rights reserved.
*/
/*
* SD card slot support.
*/
/*
* Prototypes.
*/
static void sda_slot_insert(void *);
static void sda_slot_handle_detect(sda_slot_t *);
static void sda_slot_halt(sda_slot_t *);
static void sda_slot_thread(void *);
/*
* Static Variables.
*/
static struct {
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" },
};
/*
* 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
{
slot->s_circular++;
} else {
}
slot->s_circular++;
}
}
void
{
slot->s_circular--;
if (slot->s_circular == 0) {
}
}
{
}
{
case R1:
return (SDA_EWPROTECT);
}
return (SDA_EINVAL);
}
return (SDA_EIO);
}
break;
case R5:
return (SDA_EIO);
}
break;
}
return (SDA_EOK);
}
void
{
/* We need to wait 1 msec for power down. */
drv_usecwait(1000);
}
void
{
}
}
int
{
int rv;
/*
* 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.
*/
goto done;
}
if ((ocr & OCR_HI_MASK) == 0) {
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) {
} else if (ocr & OCR_29_30V) {
} else {
}
/*
* Turn on the power.
*/
goto done;
}
/*
* Wait 250 msec (per spec) for power ramp to complete.
*/
return (0);
done:
return (rv);
}
void
{
/* 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);
}
void
sda_slot_insert(void *arg)
{
/*
* Remove power from the slot. If a more severe fault
* occurred, then a manual reset with cfgadm will be needed.
*/
/*
* 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.
*/
} else {
"Unable to parse card identification");
} else {
}
}
slot->s_intransit = 0;
}
void
{
}
}
}
void
{
} else {
}
}
void
{
const char *msg;
int i;
/*
* Timeouts during initialization are quite normal.
*/
return;
}
msg = "Unknown fault (%d)";
break;
}
}
/*
* FMA would be a better choice here.
*/
/*
* Shut down the slot. Interaction from userland via cfgadm
* can revive it.
*
* FMA can help here.
*/
}
void
{
/*
* We need to initialize the card, so we only support
* hipri commands for now.
*/
/*
* Card insertion occurred. We have to run this on
* another task, to avoid deadlock as the task may
* need to dispatch commands.
*/
} else {
/*
* Nuke in-flight commands.
*/
/*
* Restart the slot (incl. power cycle). This gets the
* slot to a known good state.
*/
slot->s_intransit = 0;
}
}
void
{
}
void
{
}
void
{
}
void
{
}
void
{
}
void
{
}
static bd_ops_t sda_bd_ops = {
NULL, /* devid_init */
NULL, /* sync_cache */
NULL /* dump */
};
void
{
char name[16];
/*
* We have two taskqs. The first taskq is used for
* card initialization.
*
* The second 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.
*/
slot->s_slot_num);
TASKQ_DEFAULTPRI, 0);
/* Generally, this failure should never occur */
return;
}
/* create the main processing thread */
slot->s_slot_num);
TASKQ_DEFAULTPRI, 0);
/* Generally, this failure should never occur */
return;
}
/*
* Determine slot capabilities.
*/
}
}
(cap != 0)) {
}
/* make sure that the host is started up */
}
}
void
{
/*
* Shut down the thread.
*/
/*
* Nuke the taskqs. We do this after stopping the background
* thread to avoid deadlock.
*/
}
void
{
}
void
{
/*
* A card change event may have occurred, and in any case we need
* to reinitialize the card.
*/
/* Start up a new instance of the main processing task. */
}
void
sda_slot_thread(void *arg)
{
for (;;) {
/*
* Process any abort list first.
*/
/*
* EOK used here, to avoid clobbering previous
* error code.
*/
SDA_EOK);
continue;
}
/* Parent is detaching the slot, bail out. */
break;
}
/*
* Host wants to suspend, but don't do it if
* we have a transfer outstanding.
*/
break;
}
continue;
}
continue;
}
continue;
}
/*
* The device stalled processing the data request.
* At this point, we really have no choice but to
* nuke the request, and flag a fault.
*/
continue;
}
/*
* If the slot has suspended, then we can't process
* any new commands yet.
*/
/*
* We use a timed wait if we are waiting for a
* data transfer to complete. Otherwise we
* avoid the timed wait to avoid waking CPU
* (power savings.)
*/
/* Wait 3 sec (reap attempts). */
} else {
}
continue;
}
/*
* We're awake now, so look for work to do. First
* acquire access to the slot.
*/
/*
* If no more commands to process, go back to sleep.
*/
continue;
}
/*
* If the current command is not an initialization
* command, but we are initializing, go back to sleep.
* (This happens potentially during a card reset or
* removed, but a reset is in progress.)
*/
continue;
}
if (datline) {
/*
* If the current command has a data phase
* while a transfer is in progress, then go
* back to sleep.
*/
continue;
}
/*
* Note that APP_CMD doesn't have a data phase,
* although the associated ACMD might.
*/
/*
* 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.
*/
5000000000ULL;
}
}
/*
* We're committed to dispatching this command now,
* so remove it from the list.
*/
/*
* There could be more commands after this one, so we
* mark ourself so we stay awake for another cycle.
*/
/*
* 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.
*
* 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!)
*/
rv = SDA_ENODEV;
} else {
}
/*
* If APP_CMD completed properly, then
* resubmit with ACMD index. Note wake was
* already set above.
*/
}
continue;
}
} else if (datline) {
/*
* If an error occurred and we were expecting
* a transfer phase, we have to clean up.
*/
/*
* And notify any waiter.
*/
continue;
}
/*
* Wake any waiter.
*/
}
}
void
{
char msgbuf[256];
pfx = "!";
sfx = "\n";
} else {
}
if (s != NULL) {
"%s%s%d: slot %d: %s%s", pfx,
} else {
}
}
void
{
}
void
{
}