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:
parent
422dcc2416
commit
eb67f31a1b
@ -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),
|
||||
|
@ -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},
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user