Implement suspend/resume for mmc and mmcsd drivers.

Now it is possible to suspend/resume with inserted and active card.

To reinitialize card on resume and to detect card change while suspended,
implement bus rescan routines. It can also be used by controllers without
card presence detection signals or with multiple cards per slot support.

While there, cleanup msleep() usage. We have no any rights to exit without
"request done" signal from driver as it could lead to modify after free.
This commit is contained in:
Alexander Motin 2008-12-06 21:41:27 +00:00
parent 422dcc2416
commit eb67f31a1b
2 changed files with 164 additions and 66 deletions

View File

@ -108,6 +108,8 @@ struct mmc_ivars {
static int mmc_probe(device_t dev);
static int mmc_attach(device_t dev);
static int mmc_detach(device_t dev);
static int mmc_suspend(device_t dev);
static int mmc_resume(device_t dev);
#define MMC_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
#define MMC_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
@ -130,6 +132,8 @@ static int mmc_set_card_bus_width(struct mmc_softc *sc, uint16_t rca, int width)
static int mmc_app_send_scr(struct mmc_softc *sc, uint16_t rca, uint32_t *rawscr);
static void mmc_app_decode_scr(uint32_t *raw_scr, struct mmc_scr *scr);
static int mmc_send_ext_csd(struct mmc_softc *sc, uint8_t *rawextcsd);
static void mmc_scan(struct mmc_softc *sc);
static int mmc_delete_cards(struct mmc_softc *sc);
static void
mmc_ms_delay(int ms)
@ -166,28 +170,38 @@ static int
mmc_detach(device_t dev)
{
struct mmc_softc *sc = device_get_softc(dev);
device_t *kids;
int i, nkid;
int err;
/* kill children [ph33r]. -sorbo */
if (device_get_children(sc->dev, &kids, &nkid) != 0)
return (0);
for (i = 0; i < nkid; i++) {
device_t kid = kids[i];
void *ivar = device_get_ivars(kid);
device_detach(kid);
device_delete_child(sc->dev, kid);
free(ivar, M_DEVBUF);
}
free(kids, M_TEMP);
if ((err = mmc_delete_cards(sc)) != 0)
return (err);
mmc_power_down(sc);
MMC_LOCK_DESTROY(sc);
return (0);
}
static int
mmc_suspend(device_t dev)
{
struct mmc_softc *sc = device_get_softc(dev);
int err;
err = bus_generic_suspend(dev);
if (err)
return (err);
mmc_power_down(sc);
return (0);
}
static int
mmc_resume(device_t dev)
{
struct mmc_softc *sc = device_get_softc(dev);
mmc_scan(sc);
return (bus_generic_resume(dev));
}
static int
mmc_acquire_bus(device_t busdev, device_t dev)
{
@ -265,12 +279,6 @@ mmc_release_bus(device_t busdev, device_t dev)
return (0);
}
static void
mmc_rescan_cards(struct mmc_softc *sc)
{
/* XXX: Look at the children and see if they respond to status */
}
static uint32_t
mmc_select_vdd(struct mmc_softc *sc, uint32_t ocr)
{
@ -294,31 +302,25 @@ mmc_wakeup(struct mmc_request *req)
{
struct mmc_softc *sc;
/* printf("Wakeup for req %p done_data %p\n", req, req->done_data); */
sc = (struct mmc_softc *)req->done_data;
MMC_LOCK(sc);
req->flags |= MMC_REQ_DONE;
wakeup(req);
MMC_UNLOCK(sc);
wakeup(req);
}
static int
mmc_wait_for_req(struct mmc_softc *sc, struct mmc_request *req)
{
int err;
req->done = mmc_wakeup;
req->done_data = sc;
/* printf("Submitting request %p sc %p\n", req, sc); */
MMCBR_REQUEST(device_get_parent(sc->dev), sc->dev, req);
MMC_LOCK(sc);
do {
err = msleep(req, &sc->sc_mtx, PZERO | PCATCH, "mmcreq",
hz / 10);
} while (!(req->flags & MMC_REQ_DONE) && err == EAGAIN);
/* printf("Request %p done with error %d\n", req, err); */
while ((req->flags & MMC_REQ_DONE) == 0)
msleep(req, &sc->sc_mtx, 0, "mmcreq", 0);
MMC_UNLOCK(sc);
return (err);
return (0);
}
static int
@ -1060,25 +1062,41 @@ mmc_send_relative_addr(struct mmc_softc *sc, uint32_t *resp)
static void
mmc_discover_cards(struct mmc_softc *sc)
{
struct mmc_ivars *ivar;
int err;
struct mmc_ivars *ivar = NULL;
device_t *devlist;
int err, i, devcount, newcard;
uint32_t raw_cid[4];
uint32_t resp, sec_count;
device_t child;
uint16_t rca = 2;
u_char switch_res[64];
while (1) {
ivar = malloc(sizeof(struct mmc_ivars), M_DEVBUF,
M_WAITOK | M_ZERO);
if (!ivar)
return;
err = mmc_all_send_cid(sc, ivar->raw_cid);
err = mmc_all_send_cid(sc, raw_cid);
if (err == MMC_ERR_TIMEOUT)
break;
if (err != MMC_ERR_NONE) {
device_printf(sc->dev, "Error reading CID %d\n", err);
break;
}
newcard = 1;
if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0)
return;
for (i = 0; i < devcount; i++) {
ivar = device_get_ivars(devlist[i]);
if (memcmp(ivar->raw_cid, raw_cid, sizeof(raw_cid)) == 0) {
newcard = 0;
break;
}
}
free(devlist, M_TEMP);
if (newcard) {
ivar = malloc(sizeof(struct mmc_ivars), M_DEVBUF,
M_WAITOK | M_ZERO);
if (!ivar)
return;
memcpy(ivar->raw_cid, raw_cid, sizeof(raw_cid));
}
if (mmcbr_get_ro(sc->dev))
ivar->read_only = 1;
ivar->bus_width = bus_width_1;
@ -1121,9 +1139,11 @@ mmc_discover_cards(struct mmc_softc *sc)
if ((mmcbr_get_caps(sc->dev) & MMC_CAP_4_BIT_DATA) &&
(ivar->scr.bus_widths & SD_SCR_BUS_WIDTH_4))
ivar->bus_width = bus_width_4;
/* Add device. */
child = device_add_child(sc->dev, NULL, -1);
device_set_ivars(child, ivar);
if (newcard) {
/* Add device. */
child = device_add_child(sc->dev, NULL, -1);
device_set_ivars(child, ivar);
}
return;
}
mmc_decode_cid_mmc(ivar->raw_cid, &ivar->cid);
@ -1174,11 +1194,50 @@ mmc_discover_cards(struct mmc_softc *sc)
ivar->bus_width = bus_width_1;
ivar->timing = bus_timing_normal;
}
/* Add device. */
child = device_add_child(sc->dev, NULL, -1);
device_set_ivars(child, ivar);
if (newcard) {
/* Add device. */
child = device_add_child(sc->dev, NULL, -1);
device_set_ivars(child, ivar);
}
}
free(ivar, M_DEVBUF);
}
static void
mmc_rescan_cards(struct mmc_softc *sc)
{
struct mmc_ivars *ivar = NULL;
device_t *devlist;
int err, i, devcount;
if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0)
return;
for (i = 0; i < devcount; i++) {
ivar = device_get_ivars(devlist[i]);
if (mmc_select_card(sc, ivar->rca)) {
device_delete_child(sc->dev, devlist[i]);
free(ivar, M_DEVBUF);
}
}
free(devlist, M_TEMP);
mmc_select_card(sc, 0);
}
static int
mmc_delete_cards(struct mmc_softc *sc)
{
struct mmc_ivars *ivar;
device_t *devlist;
int err, i, devcount;
if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0)
return (err);
for (i = 0; i < devcount; i++) {
ivar = device_get_ivars(devlist[i]);
device_delete_child(sc->dev, devlist[i]);
free(ivar, M_DEVBUF);
}
free(devlist, M_TEMP);
return (0);
}
static void
@ -1205,7 +1264,7 @@ mmc_go_discovery(struct mmc_softc *sc)
*/
mmcbr_set_mode(dev, mode_mmc);
if (mmc_send_op_cond(sc, 0, &ocr) != MMC_ERR_NONE)
return; /* Failed both, punt! XXX powerdown? */
ocr = 0; /* Failed both, powerdown. */
}
mmcbr_set_ocr(dev, mmc_select_vdd(sc, ocr));
if (mmcbr_get_ocr(dev) != 0)
@ -1220,8 +1279,11 @@ mmc_go_discovery(struct mmc_softc *sc)
* Make sure that we have a mutually agreeable voltage to at least
* one card on the bus.
*/
if (mmcbr_get_ocr(dev) == 0)
if (mmcbr_get_ocr(dev) == 0) {
mmc_delete_cards(sc);
mmc_power_down(sc);
return;
}
/*
* Reselect the cards after we've idled them above.
*/
@ -1232,6 +1294,7 @@ mmc_go_discovery(struct mmc_softc *sc)
} else
mmc_send_op_cond(sc, mmcbr_get_ocr(dev), NULL);
mmc_discover_cards(sc);
mmc_rescan_cards(sc);
mmcbr_set_bus_mode(dev, pushpull);
mmcbr_update_ios(dev);
@ -1292,17 +1355,11 @@ mmc_calculate_clock(struct mmc_softc *sc)
static void
mmc_scan(struct mmc_softc *sc)
{
device_t dev;
device_t dev = sc->dev;
dev = sc->dev;
mmc_acquire_bus(dev, dev);
if (mmcbr_get_power_mode(dev) == power_on)
mmc_rescan_cards(sc);
mmc_go_discovery(sc);
mmc_release_bus(dev, dev);
/* XXX probe/attach/detach children? */
}
static int
@ -1374,6 +1431,8 @@ static device_method_t mmc_methods[] = {
DEVMETHOD(device_probe, mmc_probe),
DEVMETHOD(device_attach, mmc_attach),
DEVMETHOD(device_detach, mmc_detach),
DEVMETHOD(device_suspend, mmc_suspend),
DEVMETHOD(device_resume, mmc_resume),
/* Bus interface */
DEVMETHOD(bus_read_ivar, mmc_read_ivar),

View File

@ -79,6 +79,7 @@ struct mmcsd_softc {
struct bio_queue_head bio_queue;
daddr_t eblock, eend; /* Range remaining after the last erase. */
int running;
int suspend;
};
/* bus entry points */
@ -163,6 +164,7 @@ mmcsd_attach(device_t dev)
bioq_init(&sc->bio_queue);
sc->running = 1;
sc->suspend = 0;
sc->eblock = sc->eend = 0;
kproc_create(&mmcsd_task, sc, &sc->p, 0, 0, "task: mmc/sd card");
@ -174,16 +176,16 @@ mmcsd_detach(device_t dev)
{
struct mmcsd_softc *sc = device_get_softc(dev);
/* kill thread */
MMCSD_LOCK(sc);
sc->running = 0;
wakeup(sc);
MMCSD_UNLOCK(sc);
/* wait for thread to finish. XXX probably want timeout. -sorbo */
MMCSD_LOCK(sc);
while (sc->running != -1)
msleep(sc, &sc->sc_mtx, PRIBIO, "detach", 0);
sc->suspend = 0;
if (sc->running > 0) {
/* kill thread */
sc->running = 0;
wakeup(sc);
/* wait for thread to finish. */
while (sc->running != -1)
msleep(sc, &sc->sc_mtx, 0, "detach", 0);
}
MMCSD_UNLOCK(sc);
/* Flush the request queue. */
@ -196,6 +198,41 @@ mmcsd_detach(device_t dev)
return (0);
}
static int
mmcsd_suspend(device_t dev)
{
struct mmcsd_softc *sc = device_get_softc(dev);
MMCSD_LOCK(sc);
sc->suspend = 1;
if (sc->running > 0) {
/* kill thread */
sc->running = 0;
wakeup(sc);
/* wait for thread to finish. */
while (sc->running != -1)
msleep(sc, &sc->sc_mtx, 0, "detach", 0);
}
MMCSD_UNLOCK(sc);
return (0);
}
static int
mmcsd_resume(device_t dev)
{
struct mmcsd_softc *sc = device_get_softc(dev);
MMCSD_LOCK(sc);
sc->suspend = 0;
if (sc->running <= 0) {
sc->running = 1;
MMCSD_UNLOCK(sc);
kproc_create(&mmcsd_task, sc, &sc->p, 0, 0, "task: mmc/sd card");
} else
MMCSD_UNLOCK(sc);
return (0);
}
static int
mmcsd_open(struct disk *dp)
{
@ -215,10 +252,10 @@ mmcsd_strategy(struct bio *bp)
sc = (struct mmcsd_softc *)bp->bio_disk->d_drv1;
MMCSD_LOCK(sc);
if (sc->running > 0) {
if (sc->running > 0 || sc->suspend > 0) {
bioq_disksort(&sc->bio_queue, bp);
wakeup(sc);
MMCSD_UNLOCK(sc);
wakeup(sc);
} else {
MMCSD_UNLOCK(sc);
biofinish(bp, NULL, ENXIO);
@ -428,8 +465,8 @@ mmcsd_task(void *arg)
out:
/* tell parent we're done */
sc->running = -1;
wakeup(sc);
MMCSD_UNLOCK(sc);
wakeup(sc);
kproc_exit(0);
}
@ -458,6 +495,8 @@ static device_method_t mmcsd_methods[] = {
DEVMETHOD(device_probe, mmcsd_probe),
DEVMETHOD(device_attach, mmcsd_attach),
DEVMETHOD(device_detach, mmcsd_detach),
DEVMETHOD(device_suspend, mmcsd_suspend),
DEVMETHOD(device_resume, mmcsd_resume),
{0, 0},
};