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");
|
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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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) */
|
||||||
|
|
Loading…
Reference in a new issue