Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f971dbd5 authored by Dominik Brodowski's avatar Dominik Brodowski
Browse files

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: default avatarMing Lei <tom.leiming@gmail.com>
Tested-by: default avatarWolfram Sang <w.sang@pengutronix.de>
Signed-off-by: default avatarDominik Brodowski <linux@dominikbrodowski.net>
parent cfe5d809
Loading
Loading
Loading
Loading
+58 −119
Original line number Diff line number Diff line
@@ -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);

		if (events) {
		mutex_lock(&skt->skt_mutex);
		if (events) {
			if (events & SS_DETECT)
				socket_detect_change(skt);
			if (events & SS_BATDEAD)
@@ -700,9 +706,33 @@ 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);
		}

		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)
{
+5 −5
Original line number Diff line number Diff line
@@ -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);
+4 −4
Original line number Diff line number Diff line
@@ -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)) {
+14 −12
Original line number Diff line number Diff line
@@ -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);

+2 −1
Original line number Diff line number Diff line
@@ -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) */