This adds hibernate support for sdmmc devices, only for sdhc controllers for
now, but other controllers get most of the way there for free.

This uses the normal io path as much as possible.  The main thing I've had to
duplicate is the sdhc interrupt handler, which calls scary things like wakeup()
and task_add() in a few different spots.  These would cause side effects
elsewhere in the kernel so they must be avoided.  We also need to avoid
wakeup() in sdmmc_mmc_command().  I've used 'if (cold)' to decide what to do
in both cases.

One interesting bit is that we have to give the chipset driver (sdhc) some
memory to create a new sdmmc_chipset_handle to use for hibernate writes.
The sdmmc layer doesn't know how big this is, so it gives the chipset driver
the rest of the hibernate page and calls a new function in sdmmc_chip_functions
to fill it in.

I've tested this on qemu, where it hibernates and in some configurations
resumes properly, and on an Ematic tablet (only sdmmc storage and no S3
suspend) where some additional ACPI hacks were required to get it into the
hibernate path.  I'll leave those for later though.

ok?


Index: arch/amd64/amd64/hibernate_machdep.c
===================================================================
RCS file: /cvs/src/sys/arch/amd64/amd64/hibernate_machdep.c,v
retrieving revision 1.40
diff -u -p -u -p -r1.40 hibernate_machdep.c
--- arch/amd64/amd64/hibernate_machdep.c        10 Feb 2018 05:11:06 -0000      
1.40
+++ arch/amd64/amd64/hibernate_machdep.c        7 Mar 2018 01:39:08 -0000
@@ -48,6 +48,7 @@
 #include "softraid.h"
 #include "sd.h"
 #include "nvme.h"
+#include "sdmmc.h"
 
 /* Hibernate support */
 void    hibernate_enter_resume_4k_pte(vaddr_t, paddr_t);
@@ -94,6 +95,8 @@ get_hibernate_io_function(dev_t dev)
                    vaddr_t addr, size_t size, int op, void *page);
                extern int sr_hibernate_io(dev_t dev, daddr_t blkno,
                    vaddr_t addr, size_t size, int op, void *page);
+               extern int sdmmc_scsi_hibernate_io(dev_t dev, daddr_t blkno,
+                   vaddr_t addr, size_t size, int op, void *page);
                struct device *dv = disk_lookup(&sd_cd, DISKUNIT(dev));
                struct {
                        const char *driver;
@@ -107,6 +110,9 @@ get_hibernate_io_function(dev_t dev)
 #endif
 #if NSOFTRAID > 0
                        { "softraid", sr_hibernate_io },
+#endif
+#if NSDMMC > 0
+                       { "sdmmc", sdmmc_scsi_hibernate_io },
 #endif
                };
 
Index: arch/i386/i386/hibernate_machdep.c
===================================================================
RCS file: /cvs/src/sys/arch/i386/i386/hibernate_machdep.c,v
retrieving revision 1.50
diff -u -p -u -p -r1.50 hibernate_machdep.c
--- arch/i386/i386/hibernate_machdep.c  10 Feb 2018 05:11:06 -0000      1.50
+++ arch/i386/i386/hibernate_machdep.c  7 Mar 2018 01:39:08 -0000
@@ -45,6 +45,7 @@
 #include "ahci.h"
 #include "softraid.h"
 #include "sd.h"
+#include "sdmmc.h"
 
 /* Hibernate support */
 void    hibernate_enter_resume_4k_pte(vaddr_t, paddr_t);
@@ -97,6 +98,8 @@ get_hibernate_io_function(dev_t dev)
                    vaddr_t addr, size_t size, int op, void *page);
                extern int sr_hibernate_io(dev_t dev, daddr_t blkno,
                    vaddr_t addr, size_t size, int op, void *page);
+               extern int sdmmc_scsi_hibernate_io(dev_t dev, daddr_t blkno,
+                   vaddr_t addr, size_t size, int op, void *page);
                struct device *dv = disk_lookup(&sd_cd, DISKUNIT(dev));
                struct {
                        const char *driver;
@@ -107,6 +110,9 @@ get_hibernate_io_function(dev_t dev)
 #endif
 #if NSOFTRAID > 0
                        { "softraid", sr_hibernate_io },
+#endif
+#if SDMMC > 0
+                       { "sdmmc", sdmmc_scsi_hibernate_io },
 #endif
                };
 
Index: dev/sdmmc/files.sdmmc
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/files.sdmmc,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 files.sdmmc
--- dev/sdmmc/files.sdmmc       11 Oct 2017 17:19:50 -0000      1.5
+++ dev/sdmmc/files.sdmmc       7 Mar 2018 01:39:08 -0000
@@ -6,7 +6,7 @@
 define sdmmc {}
 device sdmmc: scsi
 attach sdmmc at sdmmcbus
-file   dev/sdmmc/sdmmc.c               sdmmc
+file   dev/sdmmc/sdmmc.c               sdmmc   needs-flag
 file   dev/sdmmc/sdmmc_cis.c           sdmmc
 file   dev/sdmmc/sdmmc_io.c            sdmmc
 file   dev/sdmmc/sdmmc_mem.c           sdmmc
Index: dev/sdmmc/sdhc.c
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdhc.c,v
retrieving revision 1.56
diff -u -p -u -p -r1.56 sdhc.c
--- dev/sdmmc/sdhc.c    10 Feb 2018 05:21:13 -0000      1.56
+++ dev/sdmmc/sdhc.c    7 Mar 2018 01:39:08 -0000
@@ -96,10 +96,12 @@ void        sdhc_exec_command(sdmmc_chipset_han
 int    sdhc_start_command(struct sdhc_host *, struct sdmmc_command *);
 int    sdhc_wait_state(struct sdhc_host *, u_int32_t, u_int32_t);
 int    sdhc_soft_reset(struct sdhc_host *, int);
+int    sdhc_wait_intr_cold(struct sdhc_host *, int, int);
 int    sdhc_wait_intr(struct sdhc_host *, int, int);
 void   sdhc_transfer_data(struct sdhc_host *, struct sdmmc_command *);
 void   sdhc_read_data(struct sdhc_host *, u_char *, int);
 void   sdhc_write_data(struct sdhc_host *, u_char *, int);
+int    sdhc_hibernate_init(sdmmc_chipset_handle_t, void *);
 
 #ifdef SDHC_DEBUG
 int sdhcdebug = 0;
@@ -127,7 +129,9 @@ struct sdmmc_chip_functions sdhc_functio
        sdhc_card_intr_mask,
        sdhc_card_intr_ack,
        /* UHS functions */
-       sdhc_signal_voltage
+       sdhc_signal_voltage,
+       /* hibernate */
+       sdhc_hibernate_init,
 };
 
 struct cfdriver sdhc_cd = {
@@ -1069,11 +1073,64 @@ sdhc_soft_reset(struct sdhc_host *hp, in
 }
 
 int
+sdhc_wait_intr_cold(struct sdhc_host *hp, int mask, int timo)
+{
+       int status;
+
+       mask |= SDHC_ERROR_INTERRUPT;
+       timo = timo * tick;
+       status = hp->intr_status;
+       while ((status & mask) == 0) {
+
+               status = HREAD2(hp, SDHC_NINTR_STATUS);
+               if (ISSET(status, SDHC_NINTR_STATUS_MASK)) {
+                       HWRITE2(hp, SDHC_NINTR_STATUS, status);
+                       if (ISSET(status, SDHC_ERROR_INTERRUPT)) {
+                               uint16_t error;
+                               error = HREAD2(hp, SDHC_EINTR_STATUS);
+                               HWRITE2(hp, SDHC_EINTR_STATUS, error);
+                               hp->intr_status |= status;
+
+                               if (ISSET(error, SDHC_CMD_TIMEOUT_ERROR|
+                                   SDHC_DATA_TIMEOUT_ERROR))
+                                       break;
+                       }
+
+                       if (ISSET(status, SDHC_BUFFER_READ_READY |
+                           SDHC_BUFFER_WRITE_READY | SDHC_COMMAND_COMPLETE |
+                           SDHC_TRANSFER_COMPLETE)) {
+                               hp->intr_status |= status;
+                               break;
+                       }
+
+                       if (ISSET(status, SDHC_CARD_INTERRUPT)) {
+                               HSET2(hp, SDHC_NINTR_STATUS_EN,
+                                   SDHC_CARD_INTERRUPT);
+                       }
+
+                       continue;
+               }
+
+               delay(1);
+               if (timo-- == 0) {
+                       status |= SDHC_ERROR_INTERRUPT;
+                       break;
+               }
+       }
+
+       hp->intr_status &= ~(status & mask);
+       return (status & mask);
+}
+
+int
 sdhc_wait_intr(struct sdhc_host *hp, int mask, int timo)
 {
        int status;
        int s;
 
+       if (cold)
+               return (sdhc_wait_intr_cold(hp, mask, timo));
+
        mask |= SDHC_ERROR_INTERRUPT;
 
        s = splsdmmc();
@@ -1217,3 +1274,14 @@ sdhc_dump_regs(struct sdhc_host *hp)
            HREAD4(hp, SDHC_MAX_CAPABILITIES));
 }
 #endif
+
+int
+sdhc_hibernate_init(sdmmc_chipset_handle_t sch, void *fake_softc)
+{
+       struct sdhc_host *hp, *fhp;
+       fhp = fake_softc;
+       hp = sch;
+       *fhp = *hp;
+
+       return (0);
+}
Index: dev/sdmmc/sdmmc.c
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdmmc.c,v
retrieving revision 1.49
diff -u -p -u -p -r1.49 sdmmc.c
--- dev/sdmmc/sdmmc.c   11 Feb 2018 20:58:40 -0000      1.49
+++ dev/sdmmc/sdmmc.c   7 Mar 2018 01:39:08 -0000
@@ -630,7 +630,8 @@ sdmmc_mmc_command(struct sdmmc_softc *sc
 #endif
 
        error = cmd->c_error;
-       wakeup(cmd);
+       if (!cold)
+               wakeup(cmd);
 
        return error;
 }
Index: dev/sdmmc/sdmmc_mem.c
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdmmc_mem.c,v
retrieving revision 1.30
diff -u -p -u -p -r1.30 sdmmc_mem.c
--- dev/sdmmc/sdmmc_mem.c       6 Apr 2017 03:15:29 -0000       1.30
+++ dev/sdmmc/sdmmc_mem.c       7 Mar 2018 01:39:08 -0000
@@ -28,6 +28,10 @@
 #include <dev/sdmmc/sdmmcreg.h>
 #include <dev/sdmmc/sdmmcvar.h>
 
+#ifdef HIBERNATE
+#include <uvm/uvm_extern.h>
+#endif
+
 typedef struct { uint32_t _bits[512/32]; } __packed __aligned(4) 
sdmmc_bitfield512_t;
 
 void   sdmmc_be512_to_bitfield512(sdmmc_bitfield512_t *);
@@ -1104,3 +1108,37 @@ out:
        rw_exit(&sc->sc_lock);
        return (error);
 }
+
+#ifdef HIBERNATE
+int
+sdmmc_mem_hibernate_write(struct sdmmc_function *sf, daddr_t blkno,
+    u_char *data, size_t datalen)
+{
+       struct sdmmc_softc *sc = sf->sc;
+       int i, error;
+       struct bus_dmamap dmamap;
+       paddr_t phys_addr;
+
+       if (ISSET(sc->sc_caps, SMC_CAPS_SINGLE_ONLY)) {
+               for (i = 0; i < datalen / sf->csd.sector_size; i++) {
+                       error = sdmmc_mem_write_block_subr(sf, NULL, blkno + i,
+                           data + i * sf->csd.sector_size,
+                           sf->csd.sector_size);
+                       if (error)
+                               return (error);
+               }
+       } else if (!ISSET(sc->sc_caps, SMC_CAPS_DMA)) {
+               return (sdmmc_mem_write_block_subr(sf, NULL, blkno, data,
+                   datalen));
+       }
+
+       /* pretend we're bus_dmamap_load */
+       bzero(&dmamap, sizeof(dmamap));
+       pmap_extract(pmap_kernel(), (vaddr_t)data, &phys_addr);
+       dmamap.dm_mapsize = datalen;
+       dmamap.dm_nsegs = 1;
+       dmamap.dm_segs[0].ds_addr = phys_addr;
+       dmamap.dm_segs[0].ds_len = datalen;
+       return (sdmmc_mem_write_block_subr(sf, &dmamap, blkno, data, datalen));
+}
+#endif
Index: dev/sdmmc/sdmmc_scsi.c
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdmmc_scsi.c,v
retrieving revision 1.40
diff -u -p -u -p -r1.40 sdmmc_scsi.c
--- dev/sdmmc/sdmmc_scsi.c      6 Apr 2017 17:00:53 -0000       1.40
+++ dev/sdmmc/sdmmc_scsi.c      7 Mar 2018 01:39:08 -0000
@@ -32,6 +32,13 @@
 #include <dev/sdmmc/sdmmc_scsi.h>
 #include <dev/sdmmc/sdmmcvar.h>
 
+#ifdef HIBERNATE
+#include <sys/hibernate.h>
+#include <sys/disk.h>
+#include <sys/disklabel.h>
+#include <sys/rwlock.h>
+#endif
+
 #define SDMMC_SCSIID_HOST      0x00
 #define SDMMC_SCSIID_MAX       0x0f
 
@@ -552,3 +559,93 @@ sdmmc_scsi_minphys(struct buf *bp, struc
 
        minphys(bp);
 }
+
+#ifdef HIBERNATE
+int
+sdmmc_scsi_hibernate_io(dev_t dev, daddr_t blkno, vaddr_t addr, size_t size,
+    int op, void *page)
+{
+       struct {
+               struct sdmmc_softc sdmmc_sc;
+               struct sdmmc_function sdmmc_sf;
+               daddr_t poffset;
+               size_t psize;
+               struct sdmmc_function *orig_sf;
+               char chipset_softc[0];  /* size depends on the chipset layer */
+       } *state = page;
+       extern struct cfdriver sd_cd;
+       struct device *disk, *scsibus, *chip, *sdmmc;
+       struct scsibus_softc *bus_sc;
+       struct sdmmc_scsi_softc *scsi_sc;
+       struct scsi_link *link;
+       struct sdmmc_function *sf;
+       struct sdmmc_softc *sc;
+       int error;
+
+       switch (op) {
+       case HIB_INIT:
+               /* find device (sdmmc_softc, sdmmc_function) */
+               disk = disk_lookup(&sd_cd, DISKUNIT(dev));
+               scsibus = disk->dv_parent;
+               sdmmc = scsibus->dv_parent;
+               chip = sdmmc->dv_parent;
+
+               bus_sc = (struct scsibus_softc *)scsibus;
+               scsi_sc = (struct sdmmc_scsi_softc *)scsibus;
+               SLIST_FOREACH(link, &bus_sc->sc_link_list, bus_list) {
+                       if (link->device_softc == disk) {
+                               sc = (struct sdmmc_softc *)link->adapter_softc;
+                               scsi_sc = sc->sc_scsibus;
+                               sf = scsi_sc->sc_tgt[link->target].card;
+                       }
+               }
+
+               /* if the chipset doesn't do hibernate, bail out now */
+               sc = (struct sdmmc_softc *)sdmmc;
+               if (sc->sct->hibernate_init == NULL)
+                       return (ENOTTY);
+
+               state->sdmmc_sc = *sc;
+               state->sdmmc_sf = *sf;
+               state->sdmmc_sf.sc = &state->sdmmc_sc;
+
+               /* pretend we own the lock */
+               state->sdmmc_sc.sc_lock.rwl_owner =
+                   (((long)curproc) & ~RWLOCK_MASK) | RWLOCK_WRLOCK;
+
+               /* build chip layer fake softc */
+               error = state->sdmmc_sc.sct->hibernate_init(state->sdmmc_sc.sch,
+                   &state->chipset_softc);
+               if (error)
+                       return (error);
+               state->sdmmc_sc.sch = state->chipset_softc;
+
+               /* make sure we're talking to the right target */
+               state->orig_sf = sc->sc_card;
+               error = sdmmc_select_card(&state->sdmmc_sc, &state->sdmmc_sf);
+               if (error)
+                       return (error);
+
+               state->poffset = blkno;
+               state->psize = size;
+               return (0);
+
+       case HIB_W:
+               if (blkno > state->psize)
+                       return (E2BIG);
+               return (sdmmc_mem_hibernate_write(&state->sdmmc_sf,
+                   blkno + state->poffset, (u_char *)addr, size));
+
+       case HIB_DONE:
+               /*
+                * bring the hardware state back into line with the real
+                * softc by operating on the fake one
+                */
+               sdmmc_select_card(&state->sdmmc_sc, state->orig_sf);
+               return (0);
+       }
+
+       return (EINVAL);
+}
+
+#endif
Index: dev/sdmmc/sdmmcchip.h
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdmmcchip.h,v
retrieving revision 1.10
diff -u -p -u -p -r1.10 sdmmcchip.h
--- dev/sdmmc/sdmmcchip.h       24 Dec 2017 12:55:52 -0000      1.10
+++ dev/sdmmc/sdmmcchip.h       7 Mar 2018 01:39:08 -0000
@@ -46,6 +46,8 @@ struct sdmmc_chip_functions {
        void    (*card_intr_ack)(sdmmc_chipset_handle_t);
        /* UHS functions */
        int             (*signal_voltage)(sdmmc_chipset_handle_t, int);
+       /* hibernate */
+       int     (*hibernate_init)(sdmmc_chipset_handle_t, void *);
 };
 
 /* host controller reset */
Index: dev/sdmmc/sdmmcvar.h
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdmmcvar.h,v
retrieving revision 1.28
diff -u -p -u -p -r1.28 sdmmcvar.h
--- dev/sdmmc/sdmmcvar.h        11 Feb 2018 20:58:40 -0000      1.28
+++ dev/sdmmc/sdmmcvar.h        7 Mar 2018 01:39:08 -0000
@@ -280,6 +280,11 @@ int        sdmmc_mem_init(struct sdmmc_softc *,
 int    sdmmc_mem_read_block(struct sdmmc_function *, int, u_char *, size_t);
 int    sdmmc_mem_write_block(struct sdmmc_function *, int, u_char *, size_t);
 
+#ifdef HIBERNATE
+int    sdmmc_mem_hibernate_write(struct sdmmc_function *, daddr_t, u_char *,
+           size_t);
+#endif
+
 /* ioctls */
 
 #include <sys/ioccom.h>

Reply via email to