[PATCH 14/16] pcmcia: use pccardd to handle eject, insert, suspend and resume requests
Dominik Brodowski
linux at dominikbrodowski.net
Mon Jan 18 03:36:34 EST 2010
This avoids any sysfs-related deadlock (or lockdep warning), such
as reported at http://lkml.org/lkml/2010/1/17/88 .
Reported-by: Ming Lei <tom.leiming at gmail.com>
Signed-off-by: Dominik Brodowski <linux at dominikbrodowski.net>
---
drivers/pcmcia/cs.c | 177 +++++++++++++---------------------------
drivers/pcmcia/cs_internal.h | 6 +-
drivers/pcmcia/pcmcia_ioctl.c | 8 +-
drivers/pcmcia/socket_sysfs.c | 26 +++---
include/pcmcia/ss.h | 3 +-
5 files changed, 79 insertions(+), 141 deletions(-)
diff --git a/drivers/pcmcia/cs.c b/drivers/pcmcia/cs.c
index 7ba45b0..ca6975a 100644
--- a/drivers/pcmcia/cs.c
+++ b/drivers/pcmcia/cs.c
@@ -505,7 +505,10 @@ static int socket_insert(struct pcmcia_socket *skt)
dev_dbg(&skt->dev, "insert\n");
mutex_lock(&skt->ops_mutex);
- WARN_ON(skt->state & SOCKET_INUSE);
+ if (skt->state & SOCKET_INUSE) {
+ mutex_unlock(&skt->ops_mutex);
+ return -EINVAL;
+ }
skt->state |= SOCKET_INUSE;
ret = socket_setup(skt, setup_delay);
@@ -682,16 +685,19 @@ static int pccardd(void *__skt)
for (;;) {
unsigned long flags;
unsigned int events;
+ unsigned int sysfs_events;
set_current_state(TASK_INTERRUPTIBLE);
spin_lock_irqsave(&skt->thread_lock, flags);
events = skt->thread_events;
skt->thread_events = 0;
+ sysfs_events = skt->sysfs_events;
+ skt->sysfs_events = 0;
spin_unlock_irqrestore(&skt->thread_lock, flags);
+ mutex_lock(&skt->skt_mutex);
if (events) {
- mutex_lock(&skt->skt_mutex);
if (events & SS_DETECT)
socket_detect_change(skt);
if (events & SS_BATDEAD)
@@ -700,10 +706,34 @@ static int pccardd(void *__skt)
send_event(skt, CS_EVENT_BATTERY_LOW, CS_EVENT_PRI_LOW);
if (events & SS_READY)
send_event(skt, CS_EVENT_READY_CHANGE, CS_EVENT_PRI_LOW);
- mutex_unlock(&skt->skt_mutex);
- continue;
}
+ if (sysfs_events) {
+ if (sysfs_events & SS_EJECTION)
+ socket_remove(skt);
+ if (sysfs_events & SS_INSERTION)
+ socket_insert(skt);
+ if ((sysfs_events & SS_POWERON) &&
+ !(skt->state & SOCKET_CARDBUS)) {
+ ret = socket_resume(skt);
+ if (!ret && skt->callback)
+ skt->callback->resume(skt);
+ }
+ if ((sysfs_events & SS_BATDEAD) &&
+ !(skt->state & SOCKET_CARDBUS)) {
+ if (skt->callback)
+ ret = skt->callback->suspend(skt);
+ else
+ ret = 0;
+ if (!ret)
+ socket_suspend(skt);
+ }
+ }
+ mutex_unlock(&skt->skt_mutex);
+
+ if (events || sysfs_events)
+ continue;
+
if (kthread_should_stop())
break;
@@ -745,6 +775,30 @@ void pcmcia_parse_events(struct pcmcia_socket *s, u_int events)
} /* pcmcia_parse_events */
EXPORT_SYMBOL(pcmcia_parse_events);
+/**
+ * pcmcia_parse_uevents() - tell pccardd to issue manual commands
+ * @s: the PCMCIA socket we wan't to command
+ * @events: events to pass to pccardd
+ *
+ * userspace-issued insert, eject, suspend and resume commands must be
+ * handled by pccardd to avoid any sysfs-related deadlocks. Valid events
+ * are SS_EJECTION (for eject), SS_INSERTION (for insert),
+ * SS_POWERON (for resume) and SS_BATDEAD (for suspend).
+ */
+void pcmcia_parse_uevents(struct pcmcia_socket *s, u_int events)
+{
+ unsigned long flags;
+ dev_dbg(&s->dev, "parse_uevents: events %08x\n", events);
+ if (s->thread) {
+ spin_lock_irqsave(&s->thread_lock, flags);
+ s->sysfs_events |= events;
+ spin_unlock_irqrestore(&s->thread_lock, flags);
+
+ wake_up_process(s->thread);
+ }
+}
+EXPORT_SYMBOL(pcmcia_parse_uevents);
+
/* register pcmcia_callback */
int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c)
@@ -828,121 +882,6 @@ int pcmcia_reset_card(struct pcmcia_socket *skt)
EXPORT_SYMBOL(pcmcia_reset_card);
-/* These shut down or wake up a socket. They are sort of user
- * initiated versions of the APM suspend and resume actions.
- */
-int pcmcia_suspend_card(struct pcmcia_socket *skt)
-{
- int ret;
-
- dev_dbg(&skt->dev, "suspending socket\n");
-
- mutex_lock(&skt->skt_mutex);
- do {
- if (!(skt->state & SOCKET_PRESENT)) {
- ret = -ENODEV;
- break;
- }
- if (skt->state & SOCKET_CARDBUS) {
- ret = -EPERM;
- break;
- }
- if (skt->callback) {
- ret = skt->callback->suspend(skt);
- if (ret)
- break;
- }
- ret = socket_suspend(skt);
- } while (0);
- mutex_unlock(&skt->skt_mutex);
-
- return ret;
-} /* suspend_card */
-EXPORT_SYMBOL(pcmcia_suspend_card);
-
-
-int pcmcia_resume_card(struct pcmcia_socket *skt)
-{
- int ret;
-
- dev_dbg(&skt->dev, "waking up socket\n");
-
- mutex_lock(&skt->skt_mutex);
- do {
- if (!(skt->state & SOCKET_PRESENT)) {
- ret = -ENODEV;
- break;
- }
- if (skt->state & SOCKET_CARDBUS) {
- ret = -EPERM;
- break;
- }
- ret = socket_resume(skt);
- if (!ret && skt->callback)
- skt->callback->resume(skt);
- } while (0);
- mutex_unlock(&skt->skt_mutex);
-
- return ret;
-} /* resume_card */
-EXPORT_SYMBOL(pcmcia_resume_card);
-
-
-/* These handle user requests to eject or insert a card. */
-int pcmcia_eject_card(struct pcmcia_socket *skt)
-{
- int ret;
-
- dev_dbg(&skt->dev, "user eject request\n");
-
- mutex_lock(&skt->skt_mutex);
- do {
- if (!(skt->state & SOCKET_PRESENT)) {
- ret = -ENODEV;
- break;
- }
-
- ret = send_event(skt, CS_EVENT_EJECTION_REQUEST, CS_EVENT_PRI_LOW);
- if (ret != 0) {
- ret = -EINVAL;
- break;
- }
-
- socket_remove(skt);
- ret = 0;
- } while (0);
- mutex_unlock(&skt->skt_mutex);
-
- return ret;
-} /* eject_card */
-EXPORT_SYMBOL(pcmcia_eject_card);
-
-
-int pcmcia_insert_card(struct pcmcia_socket *skt)
-{
- int ret;
-
- dev_dbg(&skt->dev, "user insert request\n");
-
- mutex_lock(&skt->skt_mutex);
- do {
- if (skt->state & SOCKET_PRESENT) {
- ret = -EBUSY;
- break;
- }
- if (socket_insert(skt) == -ENODEV) {
- ret = -ENODEV;
- break;
- }
- ret = 0;
- } while (0);
- mutex_unlock(&skt->skt_mutex);
-
- return ret;
-} /* insert_card */
-EXPORT_SYMBOL(pcmcia_insert_card);
-
-
static int pcmcia_socket_uevent(struct device *dev,
struct kobj_uevent_env *env)
{
diff --git a/drivers/pcmcia/cs_internal.h b/drivers/pcmcia/cs_internal.h
index c07041e..1c793a1 100644
--- a/drivers/pcmcia/cs_internal.h
+++ b/drivers/pcmcia/cs_internal.h
@@ -129,11 +129,7 @@ extern struct class pcmcia_socket_class;
int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c);
struct pcmcia_socket *pcmcia_get_socket_by_nr(unsigned int nr);
-int pcmcia_suspend_card(struct pcmcia_socket *skt);
-int pcmcia_resume_card(struct pcmcia_socket *skt);
-
-int pcmcia_eject_card(struct pcmcia_socket *skt);
-int pcmcia_insert_card(struct pcmcia_socket *skt);
+void pcmcia_parse_uevents(struct pcmcia_socket *socket, unsigned int events);
struct pcmcia_socket *pcmcia_get_socket(struct pcmcia_socket *skt);
void pcmcia_put_socket(struct pcmcia_socket *skt);
diff --git a/drivers/pcmcia/pcmcia_ioctl.c b/drivers/pcmcia/pcmcia_ioctl.c
index 61e49fe..92c9cb4 100644
--- a/drivers/pcmcia/pcmcia_ioctl.c
+++ b/drivers/pcmcia/pcmcia_ioctl.c
@@ -928,16 +928,16 @@ static int ds_ioctl(struct inode *inode, struct file *file,
ret = pccard_validate_cis(s, &buf->cisinfo.Chains);
break;
case DS_SUSPEND_CARD:
- ret = pcmcia_suspend_card(s);
+ pcmcia_parse_uevents(s, SS_BATDEAD);
break;
case DS_RESUME_CARD:
- ret = pcmcia_resume_card(s);
+ pcmcia_parse_uevents(s, SS_POWERON);
break;
case DS_EJECT_CARD:
- err = pcmcia_eject_card(s);
+ pcmcia_parse_uevents(s, SS_EJECTION);
break;
case DS_INSERT_CARD:
- err = pcmcia_insert_card(s);
+ pcmcia_parse_uevents(s, SS_INSERTION);
break;
case DS_ACCESS_CONFIGURATION_REGISTER:
if ((buf->conf_reg.Action == CS_WRITE) && !capable(CAP_SYS_ADMIN)) {
diff --git a/drivers/pcmcia/socket_sysfs.c b/drivers/pcmcia/socket_sysfs.c
index aadf83d..8ec426e 100644
--- a/drivers/pcmcia/socket_sysfs.c
+++ b/drivers/pcmcia/socket_sysfs.c
@@ -88,15 +88,14 @@ static DEVICE_ATTR(card_vcc, 0444, pccard_show_vcc, NULL);
static ssize_t pccard_store_insert(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
- ssize_t ret;
struct pcmcia_socket *s = to_socket(dev);
if (!count)
return -EINVAL;
- ret = pcmcia_insert_card(s);
+ pcmcia_parse_uevents(s, SS_INSERTION);
- return ret ? ret : count;
+ return count;
}
static DEVICE_ATTR(card_insert, 0200, NULL, pccard_store_insert);
@@ -113,18 +112,22 @@ static ssize_t pccard_store_card_pm_state(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
- ssize_t ret = -EINVAL;
struct pcmcia_socket *s = to_socket(dev);
+ ssize_t ret = count;
if (!count)
return -EINVAL;
- if (!(s->state & SOCKET_SUSPEND) && !strncmp(buf, "off", 3))
- ret = pcmcia_suspend_card(s);
- else if ((s->state & SOCKET_SUSPEND) && !strncmp(buf, "on", 2))
- ret = pcmcia_resume_card(s);
+ if (!strncmp(buf, "off", 3))
+ pcmcia_parse_uevents(s, SS_BATDEAD);
+ else {
+ if (!strncmp(buf, "on", 2))
+ pcmcia_parse_uevents(s, SS_POWERON);
+ else
+ ret = -EINVAL;
+ }
- return ret ? -ENODEV : count;
+ return ret;
}
static DEVICE_ATTR(card_pm_state, 0644, pccard_show_card_pm_state, pccard_store_card_pm_state);
@@ -132,15 +135,14 @@ static ssize_t pccard_store_eject(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
- ssize_t ret;
struct pcmcia_socket *s = to_socket(dev);
if (!count)
return -EINVAL;
- ret = pcmcia_eject_card(s);
+ pcmcia_parse_uevents(s, SS_EJECTION);
- return ret ? ret : count;
+ return count;
}
static DEVICE_ATTR(card_eject, 0200, NULL, pccard_store_eject);
diff --git a/include/pcmcia/ss.h b/include/pcmcia/ss.h
index e197adb..b8f030c 100644
--- a/include/pcmcia/ss.h
+++ b/include/pcmcia/ss.h
@@ -200,13 +200,14 @@ struct pcmcia_socket {
struct task_struct *thread;
struct completion thread_done;
unsigned int thread_events;
+ unsigned int sysfs_events;
/* For the non-trivial interaction between these locks,
* see Documentation/pcmcia/locking.txt */
struct mutex skt_mutex;
struct mutex ops_mutex;
- /* protects thread_events */
+ /* protects thread_events and sysfs_events */
spinlock_t thread_lock;
/* pcmcia (16-bit) */
--
1.6.3.3
More information about the linux-pcmcia
mailing list