aboutsummaryrefslogtreecommitdiff
path: root/drivers/misc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc')
-rw-r--r--drivers/misc/mic/host/mic_boot.c120
-rw-r--r--drivers/misc/mic/host/mic_device.h8
-rw-r--r--drivers/misc/mic/host/mic_main.c63
-rw-r--r--drivers/misc/mic/host/mic_sysfs.c7
4 files changed, 193 insertions, 5 deletions
diff --git a/drivers/misc/mic/host/mic_boot.c b/drivers/misc/mic/host/mic_boot.c
index 60c54d5c43c2..d56b8bf4eaf6 100644
--- a/drivers/misc/mic/host/mic_boot.c
+++ b/drivers/misc/mic/host/mic_boot.c
@@ -37,12 +37,13 @@ static void mic_reset(struct mic_device *mdev)
#define MIC_RESET_TO (45)
+ INIT_COMPLETION(mdev->reset_wait);
mdev->ops->reset_fw_ready(mdev);
mdev->ops->reset(mdev);
for (i = 0; i < MIC_RESET_TO; i++) {
if (mdev->ops->is_fw_ready(mdev))
- return;
+ goto done;
/*
* Resets typically take 10s of seconds to complete.
* Since an MMIO read is required to check if the
@@ -51,6 +52,8 @@ static void mic_reset(struct mic_device *mdev)
msleep(1000);
}
mic_set_state(mdev, MIC_RESET_FAILED);
+done:
+ complete_all(&mdev->reset_wait);
}
/* Initialize the MIC bootparams */
@@ -123,7 +126,8 @@ void mic_stop(struct mic_device *mdev, bool force)
if (MIC_RESET_FAILED == mdev->state)
goto unlock;
mic_set_shutdown_status(mdev, MIC_NOP);
- mic_set_state(mdev, MIC_OFFLINE);
+ if (MIC_SUSPENDED != mdev->state)
+ mic_set_state(mdev, MIC_OFFLINE);
}
unlock:
mutex_unlock(&mdev->mic_mutex);
@@ -165,7 +169,14 @@ void mic_shutdown_work(struct work_struct *work)
mutex_lock(&mdev->mic_mutex);
mic_set_shutdown_status(mdev, bootparam->shutdown_status);
bootparam->shutdown_status = 0;
- if (MIC_SHUTTING_DOWN != mdev->state)
+
+ /*
+ * if state is MIC_SUSPENDED, OSPM suspend is in progress. We do not
+ * change the state here so as to prevent users from booting the card
+ * during and after the suspend operation.
+ */
+ if (MIC_SHUTTING_DOWN != mdev->state &&
+ MIC_SUSPENDED != mdev->state)
mic_set_state(mdev, MIC_SHUTTING_DOWN);
mutex_unlock(&mdev->mic_mutex);
}
@@ -183,3 +194,106 @@ void mic_reset_trigger_work(struct work_struct *work)
mic_stop(mdev, false);
}
+
+/**
+ * mic_complete_resume - Complete MIC Resume after an OSPM suspend/hibernate
+ * event.
+ * @mdev: pointer to mic_device instance
+ *
+ * RETURNS: None.
+ */
+void mic_complete_resume(struct mic_device *mdev)
+{
+ if (mdev->state != MIC_SUSPENDED) {
+ dev_warn(mdev->sdev->parent, "state %d should be %d\n",
+ mdev->state, MIC_SUSPENDED);
+ return;
+ }
+
+ /* Make sure firmware is ready */
+ if (!mdev->ops->is_fw_ready(mdev))
+ mic_stop(mdev, true);
+
+ mutex_lock(&mdev->mic_mutex);
+ mic_set_state(mdev, MIC_OFFLINE);
+ mutex_unlock(&mdev->mic_mutex);
+}
+
+/**
+ * mic_prepare_suspend - Handle suspend notification for the MIC device.
+ * @mdev: pointer to mic_device instance
+ *
+ * RETURNS: None.
+ */
+void mic_prepare_suspend(struct mic_device *mdev)
+{
+ int rc;
+
+#define MIC_SUSPEND_TIMEOUT (60 * HZ)
+
+ mutex_lock(&mdev->mic_mutex);
+ switch (mdev->state) {
+ case MIC_OFFLINE:
+ /*
+ * Card is already offline. Set state to MIC_SUSPENDED
+ * to prevent users from booting the card.
+ */
+ mic_set_state(mdev, MIC_SUSPENDED);
+ mutex_unlock(&mdev->mic_mutex);
+ break;
+ case MIC_ONLINE:
+ /*
+ * Card is online. Set state to MIC_SUSPENDING and notify
+ * MIC user space daemon which will issue card
+ * shutdown and reset.
+ */
+ mic_set_state(mdev, MIC_SUSPENDING);
+ mutex_unlock(&mdev->mic_mutex);
+ rc = wait_for_completion_timeout(&mdev->reset_wait,
+ MIC_SUSPEND_TIMEOUT);
+ /* Force reset the card if the shutdown completion timed out */
+ if (!rc) {
+ mutex_lock(&mdev->mic_mutex);
+ mic_set_state(mdev, MIC_SUSPENDED);
+ mutex_unlock(&mdev->mic_mutex);
+ mic_stop(mdev, true);
+ }
+ break;
+ case MIC_SHUTTING_DOWN:
+ /*
+ * Card is shutting down. Set state to MIC_SUSPENDED
+ * to prevent further boot of the card.
+ */
+ mic_set_state(mdev, MIC_SUSPENDED);
+ mutex_unlock(&mdev->mic_mutex);
+ rc = wait_for_completion_timeout(&mdev->reset_wait,
+ MIC_SUSPEND_TIMEOUT);
+ /* Force reset the card if the shutdown completion timed out */
+ if (!rc)
+ mic_stop(mdev, true);
+ break;
+ default:
+ mutex_unlock(&mdev->mic_mutex);
+ break;
+ }
+}
+
+/**
+ * mic_suspend - Initiate MIC suspend. Suspend merely issues card shutdown.
+ * @mdev: pointer to mic_device instance
+ *
+ * RETURNS: None.
+ */
+void mic_suspend(struct mic_device *mdev)
+{
+ struct mic_bootparam *bootparam = mdev->dp;
+ s8 db = bootparam->h2c_shutdown_db;
+
+ mutex_lock(&mdev->mic_mutex);
+ if (MIC_SUSPENDING == mdev->state && db != -1) {
+ bootparam->shutdown_card = 1;
+ mdev->ops->send_intr(mdev, db);
+ mic_set_state(mdev, MIC_SUSPENDED);
+ }
+ mutex_unlock(&mdev->mic_mutex);
+}
diff --git a/drivers/misc/mic/host/mic_device.h b/drivers/misc/mic/host/mic_device.h
index dcba2a59e77f..3574cc375bb9 100644
--- a/drivers/misc/mic/host/mic_device.h
+++ b/drivers/misc/mic/host/mic_device.h
@@ -23,6 +23,7 @@
#include <linux/cdev.h>
#include <linux/idr.h>
+#include <linux/notifier.h>
#include "mic_intr.h"
@@ -75,6 +76,7 @@ enum mic_stepping {
* @state: MIC state.
* @shutdown_status: MIC status reported by card for shutdown/crashes.
* @state_sysfs: Sysfs dirent for notifying ring 3 about MIC state changes.
+ * @reset_wait: Waitqueue for sleeping while reset completes.
* @log_buf_addr: Log buffer address for MIC.
* @log_buf_len: Log buffer length address for MIC.
* @dp: virtio device page
@@ -83,6 +85,7 @@ enum mic_stepping {
* @shutdown_cookie: shutdown cookie.
* @cdev: Character device for MIC.
* @vdev_list: list of virtio devices.
+ * @pm_notifier: Handles PM notifications from the OS.
*/
struct mic_device {
struct mic_mw mmio;
@@ -110,6 +113,7 @@ struct mic_device {
u8 state;
u8 shutdown_status;
struct sysfs_dirent *state_sysfs;
+ struct completion reset_wait;
void *log_buf_addr;
int *log_buf_len;
void *dp;
@@ -118,6 +122,7 @@ struct mic_device {
struct mic_irq *shutdown_cookie;
struct cdev cdev;
struct list_head vdev_list;
+ struct notifier_block pm_notifier;
};
/**
@@ -192,4 +197,7 @@ void mic_create_debug_dir(struct mic_device *dev);
void mic_delete_debug_dir(struct mic_device *dev);
void __init mic_init_debugfs(void);
void mic_exit_debugfs(void);
+void mic_prepare_suspend(struct mic_device *mdev);
+void mic_complete_resume(struct mic_device *mdev);
+void mic_suspend(struct mic_device *mdev);
#endif
diff --git a/drivers/misc/mic/host/mic_main.c b/drivers/misc/mic/host/mic_main.c
index ca06aa9b7114..b3520859abd3 100644
--- a/drivers/misc/mic/host/mic_main.c
+++ b/drivers/misc/mic/host/mic_main.c
@@ -26,6 +26,7 @@
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/poll.h>
+#include <linux/suspend.h>
#include <linux/mic_common.h>
#include "../common/mic_dev.h"
@@ -187,6 +188,43 @@ static enum mic_hw_family mic_get_family(struct pci_dev *pdev)
}
/**
+* mic_pm_notifier: Notifier callback function that handles
+* PM notifications.
+*
+* @notifier_block: The notifier structure.
+* @pm_event: The event for which the driver was notified.
+* @unused: Meaningless. Always NULL.
+*
+* returns NOTIFY_DONE
+*/
+static int mic_pm_notifier(struct notifier_block *notifier,
+ unsigned long pm_event, void *unused)
+{
+ struct mic_device *mdev = container_of(notifier,
+ struct mic_device, pm_notifier);
+
+ switch (pm_event) {
+ case PM_HIBERNATION_PREPARE:
+ /* Fall through */
+ case PM_SUSPEND_PREPARE:
+ mic_prepare_suspend(mdev);
+ break;
+ case PM_POST_HIBERNATION:
+ /* Fall through */
+ case PM_POST_SUSPEND:
+ /* Fall through */
+ case PM_POST_RESTORE:
+ mic_complete_resume(mdev);
+ break;
+ case PM_RESTORE_PREPARE:
+ break;
+ default:
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+/**
* mic_device_init - Allocates and initializes the MIC device structure
*
* @mdev: pointer to mic_device instance
@@ -194,9 +232,11 @@ static enum mic_hw_family mic_get_family(struct pci_dev *pdev)
*
* returns none.
*/
-static void
+static int
mic_device_init(struct mic_device *mdev, struct pci_dev *pdev)
{
+ int rc;
+
mdev->family = mic_get_family(pdev);
mdev->stepping = pdev->revision;
mic_ops_init(mdev);
@@ -205,7 +245,20 @@ mic_device_init(struct mic_device *mdev, struct pci_dev *pdev)
mdev->irq_info.next_avail_src = 0;
INIT_WORK(&mdev->reset_trigger_work, mic_reset_trigger_work);
INIT_WORK(&mdev->shutdown_work, mic_shutdown_work);
+ init_completion(&mdev->reset_wait);
INIT_LIST_HEAD(&mdev->vdev_list);
+ mdev->pm_notifier.notifier_call = mic_pm_notifier;
+ rc = register_pm_notifier(&mdev->pm_notifier);
+ if (rc) {
+ dev_err(&pdev->dev, "register_pm_notifier failed rc %d\n",
+ rc);
+ goto register_pm_notifier_fail;
+ }
+ return 0;
+register_pm_notifier_fail:
+ flush_work(&mdev->shutdown_work);
+ flush_work(&mdev->reset_trigger_work);
+ return rc;
}
/**
@@ -224,6 +277,7 @@ static void mic_device_uninit(struct mic_device *mdev)
kfree(mdev->bootmode);
flush_work(&mdev->reset_trigger_work);
flush_work(&mdev->shutdown_work);
+ unregister_pm_notifier(&mdev->pm_notifier);
}
/**
@@ -253,7 +307,11 @@ static int mic_probe(struct pci_dev *pdev,
goto ida_fail;
}
- mic_device_init(mdev, pdev);
+ rc = mic_device_init(mdev, pdev);
+ if (rc) {
+ dev_err(&pdev->dev, "mic_device_init failed rc %d\n", rc);
+ goto device_init_fail;
+ }
rc = pci_enable_device(pdev);
if (rc) {
@@ -376,6 +434,7 @@ disable_device:
pci_disable_device(pdev);
uninit_device:
mic_device_uninit(mdev);
+device_init_fail:
ida_simple_remove(&g_mic_ida, mdev->id);
ida_fail:
kfree(mdev);
diff --git a/drivers/misc/mic/host/mic_sysfs.c b/drivers/misc/mic/host/mic_sysfs.c
index 75746adfb155..6dd864e4a617 100644
--- a/drivers/misc/mic/host/mic_sysfs.c
+++ b/drivers/misc/mic/host/mic_sysfs.c
@@ -33,6 +33,8 @@ static const char * const mic_state_string[] = {
[MIC_ONLINE] = "online",
[MIC_SHUTTING_DOWN] = "shutting_down",
[MIC_RESET_FAILED] = "reset_failed",
+ [MIC_SUSPENDING] = "suspending",
+ [MIC_SUSPENDED] = "suspended",
};
/*
@@ -156,6 +158,11 @@ state_store(struct device *dev, struct device_attribute *attr,
goto done;
}
+ if (sysfs_streq(buf, "suspend")) {
+ mic_suspend(mdev);
+ goto done;
+ }
+
count = -EINVAL;
done:
return count;