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);
}
Example #2
0
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;
		sda_slot_exit(slot);

		/*
		 * 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_hp_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);

		slot->s_intransit = 0;
		sda_slot_exit(slot);

		bd_state_change(slot->s_bdh);
	}

	sda_slot_wakeup(slot);
}
Example #3
0
int
sda_mem_bd_mediainfo(void *arg, bd_media_t *media)
{
	sda_slot_t	*slot = arg;

	sda_slot_enter(slot);
	if (!slot->s_ready) {
		sda_slot_exit(slot);
		return (ENXIO);
	}
	media->m_nblks = slot->s_nblks;
	media->m_blksize = slot->s_blksz;
	media->m_readonly = slot->s_flags & SLOTF_WRITABLE ? B_FALSE : B_TRUE;
	media->m_solidstate = B_TRUE;
	sda_slot_exit(slot);
	return (0);
}
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);
}
void
sda_slot_halt(sda_slot_t *slot)
{
	sda_slot_enter(slot);
	slot->s_ops.so_halt(slot->s_prv);
	/* We need to wait 1 msec for power down. */
	drv_usecwait(1000);
	sda_slot_exit(slot);
}
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_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);
}
Example #8
0
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);

	} else if ((slot->s_flags & SLOTF_MEMORY) == 0) {
		/*
		 * 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");
	} else {

		sda_slot_enter(slot);
		if (sda_mem_parse_cid_csd(slot) != DDI_SUCCESS) {
			sda_slot_err(slot,
			    "Unable to parse card identification");
		} else {
			slot->s_warn = B_FALSE;
			slot->s_ready = B_TRUE;
		}
		sda_slot_exit(slot);
	}

	slot->s_stamp = ddi_get_time();
	slot->s_intransit = 0;
	bd_state_change(slot->s_bdh);
}
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);
	}
}
Example #10
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);
}
Example #11
0
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;
}
Example #12
0
void
sda_slot_thread(void *arg)
{
	sda_slot_t	*slot = arg;

	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_suspend) && (slot->s_xfrp == NULL)) {
			/*
			 * Host wants to suspend, but don't do it if
			 * we have a transfer outstanding.
			 */
			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_hp_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 the slot has suspended, then we can't process
		 * any new commands yet.
		 */
		if ((slot->s_suspend) || (!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.)
			 */

			if ((slot->s_xfrp != NULL) || (slot->s_reap)) {
				/* Wait 3 sec (reap attempts). */
				(void) cv_reltimedwait(&slot->s_evcv,
				    &slot->s_evlock, drv_usectohz(3000000),
				    TR_CLOCK_TICK);
			} else {
				(void) cv_wait(&slot->s_evcv, &slot->s_evlock);
			}

			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;
		}

		/*
		 * 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
		 * suspend/resume cycle, where the card has not been
		 * removed, but a reset is in progress.)
		 */
		if (slot->s_init && !(cmdp->sc_flags & SDA_CMDF_INIT)) {
			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.
		 *
		 * 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)) {
			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);
	}

	mutex_exit(&slot->s_evlock);
}
Example #13
0
void
sda_slot_attach(sda_slot_t *slot)
{
	sda_host_t	*h = slot->s_hostp;
	char		name[16];
	uint32_t	cap;

	/*
	 * 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.
	 */

	sda_slot_enter(slot);

	(void) snprintf(name, sizeof (name), "slot_%d_hp_tq",
	    slot->s_slot_num);
	slot->s_hp_tq = ddi_taskq_create(h->h_dip, name, 1,
	    TASKQ_DEFAULTPRI, 0);
	if (slot->s_hp_tq == NULL) {
		/* Generally, this failure should never occur */
		sda_slot_err(slot, "Unable to create hotplug slot taskq");
		sda_slot_exit(slot);
		return;
	}

	/* create the main processing thread */
	(void) snprintf(name, sizeof (name), "slot_%d_main_tq",
	    slot->s_slot_num);
	slot->s_main_tq = ddi_taskq_create(h->h_dip, name, 1,
	    TASKQ_DEFAULTPRI, 0);
	if (slot->s_main_tq == NULL) {
		/* Generally, this failure should never occur */
		sda_slot_err(slot, "Unable to create main slot taskq");
		sda_slot_exit(slot);
		return;
	}
	(void) ddi_taskq_dispatch(slot->s_main_tq, sda_slot_thread, slot,
	    DDI_SLEEP);

	/*
	 * 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);
}