lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:	Mon, 15 Jun 2009 20:05:17 +0800
From:	"Li, Jiebing" <jiebing.li@...el.com>
To:	Pierre Ossman <pierre@...man.eu>
CC:	"linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>,
	"Johnson, Charles F" <charles.f.johnson@...el.com>,
	"Zhu, Daniel" <daniel.zhu@...el.com>,
	"Yuan, Hang" <hang.yuan@...el.com>,
	"Li, Jiebing" <jiebing.li@...el.com>
Subject: [PATCH 1/1] MMC: SDIO driver for Intel Moorestown  platform


This patch enables SDIO bus driver suspend/resume operation support
and supplies sysfs interface for user to call suspend/resume selectively.

Signed-off-by: JiebingLi <jiebing.li@...el.com>
---
 drivers/mmc/core/Kconfig      |    9 +
 drivers/mmc/core/sdio.c       |  497 +++++++++++++++++++++++++++++++++++++++++
 drivers/mmc/core/sdio_bus.c   |   26 +++
 include/linux/mmc/card.h      |   11 +
 include/linux/mmc/sdio_func.h |   14 ++
 5 files changed, 557 insertions(+), 0 deletions(-)

diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig
index ab37a6d..eaa5fcf 100644
--- a/drivers/mmc/core/Kconfig
+++ b/drivers/mmc/core/Kconfig
@@ -14,3 +14,12 @@ config MMC_UNSAFE_RESUME
          This option is usually just for embedded systems which use
          a MMC/SD card for rootfs. Most people should say N here.

+config SDIO_SUSPEND
+       bool "SDIO selective suspend/resume"
+       depends on MMC && PM
+       help
+         If you say Y here, you can use driver calls or the sysfs
+         "power/level" file to suspend or resume the SDIO
+         peripherals.
+
+         If you are unsure about this, say N here.
diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c
index fb99ccf..a44741d 100644
--- a/drivers/mmc/core/sdio.c
+++ b/drivers/mmc/core/sdio.c
@@ -24,6 +24,262 @@
 #include "sdio_ops.h"
 #include "sdio_cis.h"

+#define to_sdio_driver(d) container_of(d, struct sdio_driver, drv)
+
+#ifdef CONFIG_SDIO_SUSPEND
+
+static int sdio_suspend_func(struct mmc_card *card,
+        struct sdio_func *func, pm_message_t msg)
+{
+       struct device *dev;
+       int error = 0;
+
+       dev = &func->dev;
+       BUG_ON(!dev);
+
+       if (dev->bus)
+               if (dev->bus->suspend)
+                       error = dev->bus->suspend(dev, msg);
+
+       return error;
+}
+
+static int sdio_resume_func(struct mmc_card *card, struct sdio_func *func)
+{
+       struct device *dev;
+       int error = 0;
+
+       dev = &func->dev;
+       BUG_ON(!dev);
+
+       if (dev->bus)
+               if (dev->bus->resume)
+                       error = dev->bus->resume(dev);
+
+       return error;
+}
+
+int sdio_suspend_host(struct mmc_card *card, pm_message_t msg)
+{
+       int ret = 0;
+       int i = 0;
+
+       mutex_lock(&card->pm_mutex);
+
+       if (!mmc_card_present(card) ||
+               mmc_card_suspended(card))
+               goto done;
+
+       for (i = 0; i < card->sdio_funcs; i++)
+               if (!sdio_func_suspended(card->sdio_func[i])) {
+                       struct device *dev = &(card->sdio_func[i])->dev;
+                       BUG_ON(!dev);
+
+                       struct sdio_driver *drv = to_sdio_driver(dev->driver);
+
+                       if (dev->driver && drv->suspend)
+                               goto done;
+               }
+
+       ret = mmc_suspend_host(card->host, msg);
+
+       if (ret == 0)
+               mmc_card_set_suspended(card);
+
+done:
+       mutex_unlock(&card->pm_mutex);
+
+       return ret;
+}
+
+int sdio_resume_host(struct mmc_card *card)
+{
+       int ret = 0;
+
+       mutex_lock(&card->pm_mutex);
+
+       if (!mmc_card_present(card)) {
+               ret = -ENODEV;
+               goto done;
+       }
+
+       if (mmc_card_suspended(card)) {
+               ret = mmc_resume_host(card->host);
+
+               if (ret == 0)
+                       mmc_card_clear_suspended(card);
+               else
+                       goto done;
+       }
+
+done:
+       mutex_unlock(&card->pm_mutex);
+
+       return ret;
+}
+
+/*
+ * This routine handles external suspend request coming from sysfs:
+ * Selectively suspend a particular device(function) using sysfs interface
+ * for power management scheme (non-ACPI) if the device is not in use.
+ */
+int sdio_external_suspend_device(struct sdio_func *func, pm_message_t msg)
+{
+       int ret = 0;
+       struct mmc_card *card = func->card;
+
+       BUG_ON(!card);
+       BUG_ON(!card->host);
+
+       mutex_lock(&card->pm_mutex);
+
+       if (!sdio_func_present(func) ||
+               sdio_func_suspended(func)) {
+               mutex_unlock(&card->pm_mutex);
+               goto done;
+       }
+
+       /* suspend the function of the SDIO device */
+       ret = sdio_suspend_func(card, func, msg);
+
+       if (ret != 0) {
+               mutex_unlock(&card->pm_mutex);
+               goto done;
+       }
+
+       sdio_func_set_suspended(func);
+
+       mutex_unlock(&card->pm_mutex);
+
+       ret = sdio_suspend_host(card, msg);
+
+done:
+       return ret;
+}
+
+/*
+ * This routine handles external resume request coming from sysfs
+ */
+int sdio_external_resume_device(struct sdio_func *func)
+{
+       int ret = 0;
+       struct mmc_card *card = func->card;
+
+       BUG_ON(!card);
+       BUG_ON(!card->host);
+
+       ret = sdio_resume_host(card);
+       if (ret)
+               goto done;
+
+       mutex_lock(&card->pm_mutex);
+
+       if (sdio_func_suspended(func)) {
+               ret = sdio_resume_func(card, func);
+
+                       if (ret != 0) {
+                               mutex_unlock(&card->pm_mutex);
+                               goto done;
+                       } else
+                               sdio_func_clear_suspended(func);
+       }
+
+       mutex_unlock(&card->pm_mutex);
+done:
+
+       return ret;
+}
+
+static const char power_group[] = "power";
+
+static const char resume_string[] = "resume";
+static const char suspend_string[] = "suspend";
+
+static ssize_t
+show_level(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct sdio_func *func = container_of(dev, struct sdio_func, dev);
+       const char *p = suspend_string;
+
+       BUG_ON(!func);
+
+       if (sdio_func_suspended(func))
+               p = suspend_string;
+       else
+               p = resume_string;
+
+       return sprintf(buf, "%s\n", p);
+}
+
+static ssize_t
+set_level(struct device *dev, struct device_attribute *attr,
+       const char *buf, size_t count)
+{
+       struct sdio_func *func = container_of(dev, struct sdio_func, dev);
+       int len = count;
+       char *cp;
+       int ret = 0;
+
+       BUG_ON(!func);
+
+       cp = memchr(buf, '\n', count);
+       if (cp)
+               len = cp - buf;
+
+       down(&dev->sem);
+
+       if (len == sizeof resume_string - 1 &&
+               strncmp(buf, resume_string, len) == 0) {
+               ret = sdio_external_resume_device(func);
+       } else if (len == sizeof suspend_string - 1 &&
+               strncmp(buf, suspend_string, len) == 0) {
+               ret = sdio_external_suspend_device(func, PMSG_SUSPEND);
+       } else {
+               ret = -EINVAL;
+       }
+
+       up(&dev->sem);
+
+       return (ret < 0 ? ret : count);
+}
+
+static DEVICE_ATTR(level, S_IRUGO | S_IWUSR, show_level, set_level);
+
+void sdio_remove_sysfs_file(struct sdio_func *func)
+{
+       struct device *dev = &func->dev;
+       struct sdio_driver *drv = to_sdio_driver(dev->driver);
+
+       if (dev->driver && drv->suspend)
+               sysfs_remove_file_from_group(&dev->kobj,
+                       &dev_attr_level.attr,
+                       power_group);
+}
+
+int sdio_create_sysfs_file(struct sdio_func *func)
+{
+       int ret;
+       struct device *dev = &func->dev;
+       struct sdio_driver *drv = to_sdio_driver(dev->driver);
+
+       if (dev->driver && drv->suspend) {
+               ret = sysfs_add_file_to_group(&dev->kobj,
+                       &dev_attr_level.attr,
+                       power_group);
+
+               if (ret)
+                       goto error;
+       }
+
+       return 0;
+
+error:
+       sdio_remove_sysfs_file(func);
+       return ret;
+}
+
+#endif /* CONFIG_SDIO_SUSPEND */
+
 static int sdio_read_fbr(struct sdio_func *func)
 {
        int ret;
@@ -195,6 +451,97 @@ static int sdio_enable_hs(struct mmc_card *card)
 }

 /*
+ * Handle the re-initialization of a SDIO card.
+ */
+static int mmc_sdio_reinit_card(struct mmc_host *host,
+       struct mmc_card *oldcard)
+{
+       int err = 0;
+       u16 funcs;
+       u32 ocr;
+       struct mmc_card *card;
+
+       BUG_ON(!host);
+       WARN_ON(!host->claimed);
+
+       if (!oldcard)
+               goto err;
+
+       card = oldcard;
+
+       err = mmc_send_io_op_cond(host, 0, &ocr);
+       if (err)
+               goto remove;
+
+       /*
+        * Inform the card of the voltage
+        */
+       err = mmc_send_io_op_cond(host, host->ocr, &ocr);
+       if (err)
+               goto remove;
+
+       /*
+        * For SPI, enable CRC as appropriate.
+        */
+       if (mmc_host_is_spi(host)) {
+               err = mmc_spi_set_crc(host, use_spi_crc);
+               if (err)
+                       goto remove;
+       }
+
+       funcs = (ocr & 0x70000000) >> 28;
+
+       if (funcs != card->sdio_funcs)
+               printk(KERN_INFO "funcs number is changed from OCR register after suspend!\n");
+
+       if (!mmc_host_is_spi(host)) {
+               err = mmc_send_relative_addr(host, &card->rca);
+               if (err)
+                       goto remove;
+
+               mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
+       }
+
+       /*
+        * Select card, as all following commands rely on that.
+        */
+       if (!mmc_host_is_spi(host)) {
+               err = mmc_select_card(card);
+               if (err)
+                       goto remove;
+       }
+
+       /*
+        * Read the common CIS tuples.
+        */
+       err = sdio_read_cccr(card);
+       if (err)
+               goto remove;
+
+#ifdef CONFIG_MRST_MMC_WR
+       /* restricting to 24MHz for Langwell A0 */
+       if (card->cis.max_dtr > 24000000)
+               card->cis.max_dtr = 24000000;
+#endif
+       mmc_set_clock(host, card->cis.max_dtr);
+
+       /*
+        * Switch to wider bus (if supported).
+        */
+       err = sdio_enable_wide(card);
+       if (err)
+               goto remove;
+
+       host->card = card;
+
+       return 0;
+
+remove:
+err:
+       return err;
+}
+
+/*
  * Host is being removed. Free up the current card.
  */
 static void mmc_sdio_remove(struct mmc_host *host)
@@ -206,6 +553,9 @@ static void mmc_sdio_remove(struct mmc_host *host)

        for (i = 0;i < host->card->sdio_funcs;i++) {
                if (host->card->sdio_func[i]) {
+#ifdef CONFIG_SDIO_SUSPEND
+                       sdio_remove_sysfs_file(host->card->sdio_func[i]);
+#endif
                        sdio_remove_func(host->card->sdio_func[i]);
                        host->card->sdio_func[i] = NULL;
                }
@@ -244,9 +594,58 @@ static void mmc_sdio_detect(struct mmc_host *host)
 }


+/*
+ * Suspend callback from host.
+ */
+static void mmc_sdio_suspend(struct mmc_host *host)
+{
+       BUG_ON(!host);
+       BUG_ON(!host->card);
+
+       mmc_claim_host(host);
+
+       if (!mmc_host_is_spi(host))
+               mmc_deselect_cards(host);
+
+       mmc_release_host(host);
+
+       printk(KERN_INFO "%s: SDIO device is suspended\n",
+               mmc_hostname(host));
+}
+
+/*
+ * Resume callback from host.
+ */
+static void mmc_sdio_resume(struct mmc_host *host)
+{
+       int err;
+
+       BUG_ON(!host);
+       BUG_ON(!host->card);
+
+       mmc_claim_host(host);
+
+       err = mmc_select_card(host->card);
+
+       mmc_release_host(host);
+
+       if (err) {
+               mmc_sdio_remove(host);
+
+               mmc_claim_host(host);
+               mmc_detach_bus(host);
+               mmc_release_host(host);
+       } else {
+               printk(KERN_INFO "%s: SDIO device is resumed\n",
+                       mmc_hostname(host));
+       }
+}
+
 static const struct mmc_bus_ops mmc_sdio_ops = {
        .remove = mmc_sdio_remove,
        .detect = mmc_sdio_detect,
+       .suspend = mmc_sdio_suspend,
+       .resume = mmc_sdio_resume,
 };


@@ -323,6 +722,10 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr)
                goto err;
        }

+#ifdef CONFIG_SDIO_SUSPEND
+       mutex_init(&card->pm_mutex);
+#endif
+
        card->type = MMC_TYPE_SDIO;
        card->sdio_funcs = funcs;

@@ -416,6 +819,13 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr)
                err = sdio_add_func(host->card->sdio_func[i]);
                if (err)
                        goto remove_added;
+               /*
+                * create the user interface to call suspend/resume
+                * from susfs
+                */
+#ifdef CONFIG_SDIO_SUSPEND
+               sdio_create_sysfs_file(host->card->sdio_func[i]);
+#endif
        }

        return 0;
@@ -439,3 +849,90 @@ err:
        return err;
 }

+/*
+ * Sometimes there is a hang in the host interface hardware,
+ * eg: the device driver can not access the device due to some error.
+ * This API is used by device driver to perform a SDIO device
+ * reset in order to solve such issues.
+ *
+ * Warn all device drivers using their pre_reset method,
+ * performs the whole SDIO card and then lets the drivers
+ * know that the reset is over using the post_reset method.
+ */
+int sdio_reset_device(struct mmc_card *card)
+{
+       int ret = 0;
+       int i = 0;
+       u8 reg = 0;
+
+       BUG_ON(!card);
+       BUG_ON(!card->host);
+       BUG_ON(!card->sdio_func);
+
+       if (!mmc_card_present(card) ||
+               mmc_card_suspended(card)) {
+               dev_dbg(&card->dev, "device reset not allowed\n");
+               return -EINVAL;
+       }
+
+       for (; i < card->sdio_funcs; i++) {
+               struct sdio_func *func = card->sdio_func[i];
+               struct sdio_driver *drv;
+
+               if (func && func->dev.driver) {
+                       drv = to_sdio_driver(func->dev.driver);
+                       if (drv->pre_reset) {
+                               ret = (drv->pre_reset)(func);
+                               if (ret)
+                                       break;
+                       }
+               }
+       }
+
+       if (ret)
+               goto err;
+
+       /* reset SDIO card via CMD52 */
+       mmc_claim_host(card->host);
+
+       ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_ABORT, 0, &reg);
+
+       if (ret)
+               reg = 0x08;
+       else
+               reg |= 0x08;
+
+       mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_ABORT, reg, NULL);
+
+       /* re-enumerate the device */
+       ret = mmc_sdio_reinit_card(card->host, card);
+
+       mmc_release_host(card->host);
+
+       if (ret)
+               goto err;
+
+       for (i = card->sdio_funcs - 1; i >= 0; i--) {
+               struct sdio_func *func = card->sdio_func[i];
+               struct sdio_driver *drv;
+
+               if (func && func->dev.driver) {
+                       drv = to_sdio_driver(func->dev.driver);
+                       if (drv->post_reset) {
+                               ret = (drv->post_reset)(func);
+                               if (ret)
+                                       break;
+                       }
+               }
+       }
+
+       if (ret)
+               goto err;
+
+       return 0;
+
+err:
+       return -EINVAL;
+
+}
+EXPORT_SYMBOL_GPL(sdio_reset_device);
diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c
index 46284b5..3f53bae 100644
--- a/drivers/mmc/core/sdio_bus.c
+++ b/drivers/mmc/core/sdio_bus.c
@@ -156,6 +156,30 @@ static int sdio_bus_remove(struct device *dev)
        return 0;
 }

+static int sdio_bus_suspend(struct device *dev, pm_message_t state)
+{
+       struct sdio_driver *drv = to_sdio_driver(dev->driver);
+       struct sdio_func *func = dev_to_sdio_func(dev);
+       int ret = 0;
+
+       if (dev->driver && drv->suspend)
+               ret = drv->suspend(func, state);
+
+       return ret;
+}
+
+static int sdio_bus_resume(struct device *dev)
+{
+       struct sdio_driver *drv = to_sdio_driver(dev->driver);
+       struct sdio_func *func = dev_to_sdio_func(dev);
+       int ret = 0;
+
+       if (dev->driver && drv->resume)
+               ret = drv->resume(func);
+
+       return ret;
+}
+
 static struct bus_type sdio_bus_type = {
        .name           = "sdio",
        .dev_attrs      = sdio_dev_attrs,
@@ -163,6 +187,8 @@ static struct bus_type sdio_bus_type = {
        .uevent         = sdio_bus_uevent,
        .probe          = sdio_bus_probe,
        .remove         = sdio_bus_remove,
+       .suspend                = sdio_bus_suspend,
+       .resume         = sdio_bus_resume,
 };

 int sdio_register_bus(void)
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 403aa50..c6b77b8 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -94,6 +94,7 @@ struct mmc_card {
 #define MMC_STATE_READONLY     (1<<1)          /* card is read-only */
 #define MMC_STATE_HIGHSPEED    (1<<2)          /* card is in high speed mode */
 #define MMC_STATE_BLOCKADDR    (1<<3)          /* card uses block-addressing */
+#define MMC_STATE_SUSPENDED    (1<<4)          /* card suspended */

        u32                     raw_cid[4];     /* raw card CID */
        u32                     raw_csd[4];     /* raw card CSD */
@@ -113,6 +114,10 @@ struct mmc_card {
        struct sdio_func_tuple  *tuples;        /* unknown common tuples */

        struct dentry           *debugfs_root;
+
+#ifdef CONFIG_SDIO_SUSPEND
+       struct mutex            pm_mutex;
+#endif
 };

 #define mmc_card_mmc(c)                ((c)->type == MMC_TYPE_MMC)
@@ -123,12 +128,18 @@ struct mmc_card {
 #define mmc_card_readonly(c)   ((c)->state & MMC_STATE_READONLY)
 #define mmc_card_highspeed(c)  ((c)->state & MMC_STATE_HIGHSPEED)
 #define mmc_card_blockaddr(c)  ((c)->state & MMC_STATE_BLOCKADDR)
+#define mmc_card_suspended(c)  ((c)->state & MMC_STATE_SUSPENDED)

 #define mmc_card_set_present(c)        ((c)->state |= MMC_STATE_PRESENT)
 #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY)
 #define mmc_card_set_highspeed(c) ((c)->state |= MMC_STATE_HIGHSPEED)
 #define mmc_card_set_blockaddr(c) ((c)->state |= MMC_STATE_BLOCKADDR)

+#ifdef CONFIG_SDIO_SUSPEND
+#define mmc_card_set_suspended(c) ((c)->state |= MMC_STATE_SUSPENDED)
+#define mmc_card_clear_suspended(c) ((c)->state &= ~MMC_STATE_SUSPENDED)
+#endif
+
 #define mmc_card_name(c)       ((c)->cid.prod_name)
 #define mmc_card_id(c)         (dev_name(&(c)->dev))

diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h
index 451bdfc..5c94b6b 100644
--- a/include/linux/mmc/sdio_func.h
+++ b/include/linux/mmc/sdio_func.h
@@ -50,6 +50,7 @@ struct sdio_func {

        unsigned int            state;          /* function state */
 #define SDIO_STATE_PRESENT     (1<<0)          /* present in sysfs */
+#define SDIO_STATE_SUSPENDED   (1<<1)          /* present in sysfs */

        u8                      tmpbuf[4];      /* DMA:able scratch buffer */

@@ -60,9 +61,13 @@ struct sdio_func {
 };

 #define sdio_func_present(f)   ((f)->state & SDIO_STATE_PRESENT)
+#define sdio_func_suspended(f)  ((f)->state & SDIO_STATE_SUSPENDED)

 #define sdio_func_set_present(f) ((f)->state |= SDIO_STATE_PRESENT)

+#define sdio_func_set_suspended(f)      ((f)->state |= SDIO_STATE_SUSPENDED)
+#define sdio_func_clear_suspended(f)    ((f)->state &= ~SDIO_STATE_SUSPENDED)
+
 #define sdio_func_id(f)                (dev_name(&(f)->dev))

 #define sdio_get_drvdata(f)    dev_get_drvdata(&(f)->dev)
@@ -77,6 +82,11 @@ struct sdio_driver {

        int (*probe)(struct sdio_func *, const struct sdio_device_id *);
        void (*remove)(struct sdio_func *);
+       int (*suspend)(struct sdio_func *, pm_message_t);
+       int (*resume)(struct sdio_func *);
+
+       int (*pre_reset)(struct sdio_func *);
+       int (*post_reset)(struct sdio_func *);

        struct device_driver drv;
 };
@@ -150,5 +160,9 @@ extern unsigned char sdio_f0_readb(struct sdio_func *func,
 extern void sdio_f0_writeb(struct sdio_func *func, unsigned char b,
        unsigned int addr, int *err_ret);

+extern int sdio_reset_device(struct mmc_card *card);
+
+extern int sdio_suspend_host(struct mmc_card *card, pm_message_t msg);
+extern int sdio_resume_host(struct mmc_card *card);
 #endif

--
1.6.0
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ