[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