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:
parent
cfe5d80951
commit
f971dbd5da
5 changed files with 83 additions and 141 deletions
|
@ -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 & 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())
|
||||
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 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 */
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -124,11 +124,11 @@ 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);
|
||||
#define PCMCIA_UEVENT_EJECT 0x0001
|
||||
#define PCMCIA_UEVENT_INSERT 0x0002
|
||||
#define PCMCIA_UEVENT_SUSPEND 0x0004
|
||||
#define PCMCIA_UEVENT_RESUME 0x0008
|
||||
|
||||
struct pcmcia_socket *pcmcia_get_socket(struct pcmcia_socket *skt);
|
||||
void pcmcia_put_socket(struct pcmcia_socket *skt);
|
||||
|
|
|
@ -925,16 +925,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, PCMCIA_UEVENT_SUSPEND);
|
||||
break;
|
||||
case DS_RESUME_CARD:
|
||||
ret = pcmcia_resume_card(s);
|
||||
pcmcia_parse_uevents(s, PCMCIA_UEVENT_RESUME);
|
||||
break;
|
||||
case DS_EJECT_CARD:
|
||||
err = pcmcia_eject_card(s);
|
||||
pcmcia_parse_uevents(s, PCMCIA_UEVENT_EJECT);
|
||||
break;
|
||||
case DS_INSERT_CARD:
|
||||
err = pcmcia_insert_card(s);
|
||||
pcmcia_parse_uevents(s, PCMCIA_UEVENT_INSERT);
|
||||
break;
|
||||
case DS_ACCESS_CONFIGURATION_REGISTER:
|
||||
if ((buf->conf_reg.Action == CS_WRITE) && !capable(CAP_SYS_ADMIN)) {
|
||||
|
|
|
@ -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, PCMCIA_UEVENT_INSERT);
|
||||
|
||||
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, PCMCIA_UEVENT_SUSPEND);
|
||||
else {
|
||||
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);
|
||||
|
||||
|
@ -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, PCMCIA_UEVENT_EJECT);
|
||||
|
||||
return ret ? ret : count;
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR(card_eject, 0200, NULL, pccard_store_eject);
|
||||
|
||||
|
|
|
@ -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) */
|
||||
|
|
Loading…
Reference in a new issue