pcmcia: split up central event handler
[linux-2.6.git] / drivers / pcmcia / cs.c
index 7ba45b0..efa30b8 100644 (file)
@@ -76,65 +76,6 @@ DECLARE_RWSEM(pcmcia_socket_list_rwsem);
 EXPORT_SYMBOL(pcmcia_socket_list_rwsem);
 
 
-/*
- * Low-level PCMCIA socket drivers need to register with the PCCard
- * core using pcmcia_register_socket.
- *
- * socket drivers are expected to use the following callbacks in their
- * .drv struct:
- *  - pcmcia_socket_dev_suspend
- *  - pcmcia_socket_dev_resume
- * These functions check for the appropriate struct pcmcia_soket arrays,
- * and pass them to the low-level functions pcmcia_{suspend,resume}_socket
- */
-static int socket_early_resume(struct pcmcia_socket *skt);
-static int socket_late_resume(struct pcmcia_socket *skt);
-static int socket_resume(struct pcmcia_socket *skt);
-static int socket_suspend(struct pcmcia_socket *skt);
-
-static void pcmcia_socket_dev_run(struct device *dev,
-                                 int (*cb)(struct pcmcia_socket *))
-{
-       struct pcmcia_socket *socket;
-
-       down_read(&pcmcia_socket_list_rwsem);
-       list_for_each_entry(socket, &pcmcia_socket_list, socket_list) {
-               if (socket->dev.parent != dev)
-                       continue;
-               mutex_lock(&socket->skt_mutex);
-               cb(socket);
-               mutex_unlock(&socket->skt_mutex);
-       }
-       up_read(&pcmcia_socket_list_rwsem);
-}
-
-int pcmcia_socket_dev_suspend(struct device *dev)
-{
-       pcmcia_socket_dev_run(dev, socket_suspend);
-       return 0;
-}
-EXPORT_SYMBOL(pcmcia_socket_dev_suspend);
-
-void pcmcia_socket_dev_early_resume(struct device *dev)
-{
-       pcmcia_socket_dev_run(dev, socket_early_resume);
-}
-EXPORT_SYMBOL(pcmcia_socket_dev_early_resume);
-
-void pcmcia_socket_dev_late_resume(struct device *dev)
-{
-       pcmcia_socket_dev_run(dev, socket_late_resume);
-}
-EXPORT_SYMBOL(pcmcia_socket_dev_late_resume);
-
-int pcmcia_socket_dev_resume(struct device *dev)
-{
-       pcmcia_socket_dev_run(dev, socket_resume);
-       return 0;
-}
-EXPORT_SYMBOL(pcmcia_socket_dev_resume);
-
-
 struct pcmcia_socket *pcmcia_get_socket(struct pcmcia_socket *skt)
 {
        struct device *dev = get_device(&skt->dev);
@@ -311,38 +252,6 @@ struct pcmcia_socket *pcmcia_get_socket_by_nr(unsigned int nr)
 }
 EXPORT_SYMBOL(pcmcia_get_socket_by_nr);
 
-/*
- * The central event handler.  Send_event() sends an event to the
- * 16-bit subsystem, which then calls the relevant device drivers.
- * Parse_events() interprets the event bits from
- * a card status change report.  Do_shutdown() handles the high
- * priority stuff associated with a card removal.
- */
-
-/* NOTE: send_event needs to be called with skt->sem held. */
-
-static int send_event(struct pcmcia_socket *s, event_t event, int priority)
-{
-       int ret;
-
-       if ((s->state & SOCKET_CARDBUS) && (event != CS_EVENT_CARD_REMOVAL))
-               return 0;
-
-       dev_dbg(&s->dev, "send_event(event %d, pri %d, callback 0x%p)\n",
-          event, priority, s->callback);
-
-       if (!s->callback)
-               return 0;
-       if (!try_module_get(s->callback->owner))
-               return 0;
-
-       ret = s->callback->event(s, event, priority);
-
-       module_put(s->callback->owner);
-
-       return ret;
-}
-
 static int socket_reset(struct pcmcia_socket *skt)
 {
        int status, i;
@@ -385,7 +294,8 @@ static void socket_shutdown(struct pcmcia_socket *s)
 
        dev_dbg(&s->dev, "shutdown\n");
 
-       send_event(s, CS_EVENT_CARD_REMOVAL, CS_EVENT_PRI_HIGH);
+       if (s->callback)
+               s->callback->remove(s);
 
        mutex_lock(&s->ops_mutex);
        s->state &= SOCKET_INUSE | SOCKET_PRESENT;
@@ -396,14 +306,22 @@ static void socket_shutdown(struct pcmcia_socket *s)
        s->socket = dead_socket;
        s->ops->init(s);
        s->ops->set_socket(s, &s->socket);
-       s->irq.AssignedIRQ = s->irq.Config = 0;
        s->lock_count = 0;
        kfree(s->fake_cis);
        s->fake_cis = NULL;
+       s->functions = 0;
+
+       /* From here on we can be sure that only we (that is, the
+        * pccardd thread) accesses this socket, and all (16-bit)
+        * PCMCIA interactions are gone. Therefore, release
+        * ops_mutex so that we don't get a sysfs-related lockdep
+        * warning.
+        */
+       mutex_unlock(&s->ops_mutex);
+
 #ifdef CONFIG_CARDBUS
        cb_free(s);
 #endif
-       s->functions = 0;
 
        /* give socket some time to power down */
        msleep(100);
@@ -415,7 +333,6 @@ static void socket_shutdown(struct pcmcia_socket *s)
        }
 
        s->state &= ~SOCKET_INUSE;
-       mutex_unlock(&s->ops_mutex);
 }
 
 static int socket_setup(struct pcmcia_socket *skt, int initial_delay)
@@ -505,7 +422,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);
@@ -526,7 +446,8 @@ static int socket_insert(struct pcmcia_socket *skt)
                dev_dbg(&skt->dev, "insert done\n");
                mutex_unlock(&skt->ops_mutex);
 
-               send_event(skt, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW);
+               if (!(skt->state & SOCKET_CARDBUS) && (skt->callback))
+                       skt->callback->add(skt);
        } else {
                mutex_unlock(&skt->ops_mutex);
                socket_shutdown(skt);
@@ -543,7 +464,6 @@ static int socket_suspend(struct pcmcia_socket *skt)
        mutex_lock(&skt->ops_mutex);
        skt->suspended_state = skt->state;
 
-       send_event(skt, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW);
        skt->socket = dead_socket;
        skt->ops->set_socket(skt, &skt->socket);
        if (skt->ops->suspend)
@@ -567,12 +487,18 @@ static int socket_early_resume(struct pcmcia_socket *skt)
 
 static int socket_late_resume(struct pcmcia_socket *skt)
 {
+       int ret;
+
        mutex_lock(&skt->ops_mutex);
        skt->state &= ~SOCKET_SUSPEND;
        mutex_unlock(&skt->ops_mutex);
 
-       if (!(skt->state & SOCKET_PRESENT))
-               return socket_insert(skt);
+       if (!(skt->state & SOCKET_PRESENT)) {
+               ret = socket_insert(skt);
+               if (ret == -ENODEV)
+                       ret = 0;
+               return ret;
+       }
 
        if (skt->resume_status) {
                socket_shutdown(skt);
@@ -598,8 +524,8 @@ static int socket_late_resume(struct pcmcia_socket *skt)
                return 0;
        }
 #endif
-
-       send_event(skt, CS_EVENT_PM_RESUME, CS_EVENT_PRI_LOW);
+       if (!(skt->state & SOCKET_CARDBUS) && (skt->callback))
+               skt->callback->early_resume(skt);
        return 0;
 }
 
@@ -678,31 +604,60 @@ static int pccardd(void *__skt)
 
        complete(&skt->thread_done);
 
+       /* wait for userspace to catch up */
+       msleep(250);
+
        set_freezable();
        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 & SS_DETECT)
-                               socket_detect_change(skt);
-                       if (events & SS_BATDEAD)
-                               send_event(skt, CS_EVENT_BATTERY_DEAD, CS_EVENT_PRI_LOW);
-                       if (events & SS_BATWARN)
-                               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;
+               mutex_lock(&skt->skt_mutex);
+               if (events & SS_DETECT)
+                       socket_detect_change(skt);
+
+               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_SUSPEND) &&
+                               !(skt->state & SOCKET_CARDBUS)) {
+                               if (skt->callback)
+                                       ret = skt->callback->suspend(skt);
+                               else
+                                       ret = 0;
+                               if (!ret) {
+                                       socket_suspend(skt);
+                                       msleep(100);
+                               }
+                       }
+                       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_REQUERY) &&
+                               !(skt->state & SOCKET_CARDBUS)) {
+                               if (!ret && skt->callback)
+                                       skt->callback->requery(skt);
+                       }
                }
+               mutex_unlock(&skt->skt_mutex);
+
+               if (events || sysfs_events)
+                       continue;
 
                if (kthread_should_stop())
                        break;
@@ -745,6 +700,31 @@ 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), PCMCIA_UEVENT_SUSPEND (for suspend)
+ * and PCMCIA_UEVENT_REQUERY (for re-querying the PCMCIA card).
+ */
+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)
@@ -764,7 +744,7 @@ int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c)
                s->callback = c;
 
                if ((s->state & (SOCKET_PRESENT|SOCKET_CARDBUS)) == SOCKET_PRESENT)
-                       send_event(s, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW);
+                       s->callback->add(s);
        } else
                s->callback = NULL;
  err:
@@ -804,20 +784,13 @@ int pcmcia_reset_card(struct pcmcia_socket *skt)
                        break;
                }
 
-               ret = send_event(skt, CS_EVENT_RESET_REQUEST, CS_EVENT_PRI_LOW);
-               if (ret == 0) {
-                       send_event(skt, CS_EVENT_RESET_PHYSICAL, CS_EVENT_PRI_LOW);
-                       if (skt->callback)
-                               skt->callback->suspend(skt);
-                       mutex_lock(&skt->ops_mutex);
-                       ret = socket_reset(skt);
-                       mutex_unlock(&skt->ops_mutex);
-                       if (ret == 0) {
-                               send_event(skt, CS_EVENT_CARD_RESET, CS_EVENT_PRI_LOW);
-                               if (skt->callback)
-                                       skt->callback->resume(skt);
-                       }
-               }
+               if (skt->callback)
+                       skt->callback->suspend(skt);
+               mutex_lock(&skt->ops_mutex);
+               ret = socket_reset(skt);
+               mutex_unlock(&skt->ops_mutex);
+               if ((ret == 0) && (skt->callback))
+                       skt->callback->resume(skt);
 
                ret = 0;
        } while (0);
@@ -828,146 +801,86 @@ 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)
+static int pcmcia_socket_uevent(struct device *dev,
+                               struct kobj_uevent_env *env)
 {
-       int ret;
+       struct pcmcia_socket *s = container_of(dev, struct pcmcia_socket, dev);
 
-       dev_dbg(&skt->dev, "suspending socket\n");
+       if (add_uevent_var(env, "SOCKET_NO=%u", s->sock))
+               return -ENOMEM;
 
-       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 0;
+}
 
-       return ret;
-} /* suspend_card */
-EXPORT_SYMBOL(pcmcia_suspend_card);
 
+static struct completion pcmcia_unload;
 
-int pcmcia_resume_card(struct pcmcia_socket *skt)
+static void pcmcia_release_socket_class(struct class *data)
 {
-       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);
+       complete(&pcmcia_unload);
+}
 
-       return ret;
-} /* resume_card */
-EXPORT_SYMBOL(pcmcia_resume_card);
 
+#ifdef CONFIG_PM
 
-/* These handle user requests to eject or insert a card. */
-int pcmcia_eject_card(struct pcmcia_socket *skt)
+static int __pcmcia_pm_op(struct device *dev,
+                         int (*callback) (struct pcmcia_socket *skt))
 {
+       struct pcmcia_socket *s = container_of(dev, struct pcmcia_socket, dev);
        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);
+       mutex_lock(&s->skt_mutex);
+       ret = callback(s);
+       mutex_unlock(&s->skt_mutex);
 
        return ret;
-} /* eject_card */
-EXPORT_SYMBOL(pcmcia_eject_card);
-
+}
 
-int pcmcia_insert_card(struct pcmcia_socket *skt)
+static int pcmcia_socket_dev_suspend_noirq(struct device *dev)
 {
-       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);
+       return __pcmcia_pm_op(dev, socket_suspend);
+}
 
+static int pcmcia_socket_dev_resume_noirq(struct device *dev)
+{
+       return __pcmcia_pm_op(dev, socket_early_resume);
+}
 
-static int pcmcia_socket_uevent(struct device *dev,
-                               struct kobj_uevent_env *env)
+static int pcmcia_socket_dev_resume(struct device *dev)
 {
-       struct pcmcia_socket *s = container_of(dev, struct pcmcia_socket, dev);
+       return __pcmcia_pm_op(dev, socket_late_resume);
+}
 
-       if (add_uevent_var(env, "SOCKET_NO=%u", s->sock))
-               return -ENOMEM;
+static const struct dev_pm_ops pcmcia_socket_pm_ops = {
+       /* dev_resume may be called with IRQs enabled */
+       SET_SYSTEM_SLEEP_PM_OPS(NULL,
+                               pcmcia_socket_dev_resume)
 
-       return 0;
-}
+       /* late suspend must be called with IRQs disabled */
+       .suspend_noirq = pcmcia_socket_dev_suspend_noirq,
+       .freeze_noirq = pcmcia_socket_dev_suspend_noirq,
+       .poweroff_noirq = pcmcia_socket_dev_suspend_noirq,
 
+       /* early resume must be called with IRQs disabled */
+       .resume_noirq = pcmcia_socket_dev_resume_noirq,
+       .thaw_noirq = pcmcia_socket_dev_resume_noirq,
+       .restore_noirq = pcmcia_socket_dev_resume_noirq,
+};
 
-static struct completion pcmcia_unload;
+#define PCMCIA_SOCKET_CLASS_PM_OPS (&pcmcia_socket_pm_ops)
 
-static void pcmcia_release_socket_class(struct class *data)
-{
-       complete(&pcmcia_unload);
-}
+#else /* CONFIG_PM */
+
+#define PCMCIA_SOCKET_CLASS_PM_OPS NULL
 
+#endif /* CONFIG_PM */
 
 struct class pcmcia_socket_class = {
        .name = "pcmcia_socket",
        .dev_uevent = pcmcia_socket_uevent,
        .dev_release = pcmcia_release_socket,
        .class_release = pcmcia_release_socket_class,
+       .pm = PCMCIA_SOCKET_CLASS_PM_OPS,
 };
 EXPORT_SYMBOL(pcmcia_socket_class);