pcmcia: use pccardd to handle eject, insert, suspend and resume requests

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@gmail.com>
Tested-by: Wolfram Sang <w.sang@pengutronix.de>
Signed-off-by: Dominik Brodowski <linux@dominikbrodowski.net>
This commit is contained in:
Dominik Brodowski 2010-01-17 18:13:31 +01:00
parent cfe5d80951
commit f971dbd5da
5 changed files with 83 additions and 141 deletions

View file

@ -505,7 +505,10 @@ static int socket_insert(struct pcmcia_socket *skt)
dev_dbg(&skt->dev, "insert\n"); dev_dbg(&skt->dev, "insert\n");
mutex_lock(&skt->ops_mutex); 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; skt->state |= SOCKET_INUSE;
ret = socket_setup(skt, setup_delay); ret = socket_setup(skt, setup_delay);
@ -682,16 +685,19 @@ static int pccardd(void *__skt)
for (;;) { for (;;) {
unsigned long flags; unsigned long flags;
unsigned int events; unsigned int events;
unsigned int sysfs_events;
set_current_state(TASK_INTERRUPTIBLE); set_current_state(TASK_INTERRUPTIBLE);
spin_lock_irqsave(&skt->thread_lock, flags); spin_lock_irqsave(&skt->thread_lock, flags);
events = skt->thread_events; events = skt->thread_events;
skt->thread_events = 0; skt->thread_events = 0;
sysfs_events = skt->sysfs_events;
skt->sysfs_events = 0;
spin_unlock_irqrestore(&skt->thread_lock, flags); spin_unlock_irqrestore(&skt->thread_lock, flags);
mutex_lock(&skt->skt_mutex);
if (events) { if (events) {
mutex_lock(&skt->skt_mutex);
if (events & SS_DETECT) if (events & SS_DETECT)
socket_detect_change(skt); socket_detect_change(skt);
if (events & SS_BATDEAD) if (events & SS_BATDEAD)
@ -700,10 +706,34 @@ static int pccardd(void *__skt)
send_event(skt, CS_EVENT_BATTERY_LOW, CS_EVENT_PRI_LOW); send_event(skt, CS_EVENT_BATTERY_LOW, CS_EVENT_PRI_LOW);
if (events & SS_READY) if (events & SS_READY)
send_event(skt, CS_EVENT_READY_CHANGE, CS_EVENT_PRI_LOW); send_event(skt, CS_EVENT_READY_CHANGE, CS_EVENT_PRI_LOW);
mutex_unlock(&skt->skt_mutex);
continue;
} }
if (sysfs_events) {
if (sysfs_events & PCMCIA_UEVENT_EJECT)
socket_remove(skt);
if (sysfs_events & PCMCIA_UEVENT_INSERT)
socket_insert(skt);
if ((sysfs_events & PCMCIA_UEVENT_RESUME) &&
!(skt->state & SOCKET_CARDBUS)) {
ret = socket_resume(skt);
if (!ret && skt->callback)
skt->callback->resume(skt);
}
if ((sysfs_events & PCMCIA_UEVENT_SUSPEND) &&
!(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()) if (kthread_should_stop())
break; break;
@ -745,6 +775,30 @@ void pcmcia_parse_events(struct pcmcia_socket *s, u_int events)
} /* pcmcia_parse_events */ } /* pcmcia_parse_events */
EXPORT_SYMBOL(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 PCMCIA_UEVENT_EJECT (for eject), PCMCIA_UEVENT__INSERT (for insert),
* PCMCIA_UEVENT_RESUME (for resume) and PCMCIA_UEVENT_SUSPEND (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 */ /* register pcmcia_callback */
int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c) 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); 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, static int pcmcia_socket_uevent(struct device *dev,
struct kobj_uevent_env *env) struct kobj_uevent_env *env)
{ {

View file

@ -124,11 +124,11 @@ extern struct class pcmcia_socket_class;
int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c); int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c);
struct pcmcia_socket *pcmcia_get_socket_by_nr(unsigned int nr); struct pcmcia_socket *pcmcia_get_socket_by_nr(unsigned int nr);
int pcmcia_suspend_card(struct pcmcia_socket *skt); void pcmcia_parse_uevents(struct pcmcia_socket *socket, unsigned int events);
int pcmcia_resume_card(struct pcmcia_socket *skt); #define PCMCIA_UEVENT_EJECT 0x0001
#define PCMCIA_UEVENT_INSERT 0x0002
int pcmcia_eject_card(struct pcmcia_socket *skt); #define PCMCIA_UEVENT_SUSPEND 0x0004
int pcmcia_insert_card(struct pcmcia_socket *skt); #define PCMCIA_UEVENT_RESUME 0x0008
struct pcmcia_socket *pcmcia_get_socket(struct pcmcia_socket *skt); struct pcmcia_socket *pcmcia_get_socket(struct pcmcia_socket *skt);
void pcmcia_put_socket(struct pcmcia_socket *skt); void pcmcia_put_socket(struct pcmcia_socket *skt);

View file

@ -925,16 +925,16 @@ static int ds_ioctl(struct inode *inode, struct file *file,
ret = pccard_validate_cis(s, &buf->cisinfo.Chains); ret = pccard_validate_cis(s, &buf->cisinfo.Chains);
break; break;
case DS_SUSPEND_CARD: case DS_SUSPEND_CARD:
ret = pcmcia_suspend_card(s); pcmcia_parse_uevents(s, PCMCIA_UEVENT_SUSPEND);
break; break;
case DS_RESUME_CARD: case DS_RESUME_CARD:
ret = pcmcia_resume_card(s); pcmcia_parse_uevents(s, PCMCIA_UEVENT_RESUME);
break; break;
case DS_EJECT_CARD: case DS_EJECT_CARD:
err = pcmcia_eject_card(s); pcmcia_parse_uevents(s, PCMCIA_UEVENT_EJECT);
break; break;
case DS_INSERT_CARD: case DS_INSERT_CARD:
err = pcmcia_insert_card(s); pcmcia_parse_uevents(s, PCMCIA_UEVENT_INSERT);
break; break;
case DS_ACCESS_CONFIGURATION_REGISTER: case DS_ACCESS_CONFIGURATION_REGISTER:
if ((buf->conf_reg.Action == CS_WRITE) && !capable(CAP_SYS_ADMIN)) { if ((buf->conf_reg.Action == CS_WRITE) && !capable(CAP_SYS_ADMIN)) {

View file

@ -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, static ssize_t pccard_store_insert(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count)
{ {
ssize_t ret;
struct pcmcia_socket *s = to_socket(dev); struct pcmcia_socket *s = to_socket(dev);
if (!count) if (!count)
return -EINVAL; return -EINVAL;
ret = pcmcia_insert_card(s); pcmcia_parse_uevents(s, PCMCIA_UEVENT_INSERT);
return ret ? ret : count; return count;
} }
static DEVICE_ATTR(card_insert, 0200, NULL, pccard_store_insert); 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, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count)
{ {
ssize_t ret = -EINVAL;
struct pcmcia_socket *s = to_socket(dev); struct pcmcia_socket *s = to_socket(dev);
ssize_t ret = count;
if (!count) if (!count)
return -EINVAL; return -EINVAL;
if (!(s->state & SOCKET_SUSPEND) && !strncmp(buf, "off", 3)) if (!strncmp(buf, "off", 3))
ret = pcmcia_suspend_card(s); pcmcia_parse_uevents(s, PCMCIA_UEVENT_SUSPEND);
else if ((s->state & SOCKET_SUSPEND) && !strncmp(buf, "on", 2)) else {
ret = pcmcia_resume_card(s); if (!strncmp(buf, "on", 2))
pcmcia_parse_uevents(s, PCMCIA_UEVENT_RESUME);
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); 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, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count)
{ {
ssize_t ret;
struct pcmcia_socket *s = to_socket(dev); struct pcmcia_socket *s = to_socket(dev);
if (!count) if (!count)
return -EINVAL; return -EINVAL;
ret = pcmcia_eject_card(s); pcmcia_parse_uevents(s, PCMCIA_UEVENT_EJECT);
return ret ? ret : count; return count;
} }
static DEVICE_ATTR(card_eject, 0200, NULL, pccard_store_eject); static DEVICE_ATTR(card_eject, 0200, NULL, pccard_store_eject);

View file

@ -200,13 +200,14 @@ struct pcmcia_socket {
struct task_struct *thread; struct task_struct *thread;
struct completion thread_done; struct completion thread_done;
unsigned int thread_events; unsigned int thread_events;
unsigned int sysfs_events;
/* For the non-trivial interaction between these locks, /* For the non-trivial interaction between these locks,
* see Documentation/pcmcia/locking.txt */ * see Documentation/pcmcia/locking.txt */
struct mutex skt_mutex; struct mutex skt_mutex;
struct mutex ops_mutex; struct mutex ops_mutex;
/* protects thread_events */ /* protects thread_events and sysfs_events */
spinlock_t thread_lock; spinlock_t thread_lock;
/* pcmcia (16-bit) */ /* pcmcia (16-bit) */