Merge branch 'topic/core-cleanup' into for-linus
[safe/jmp/linux-2.6] / drivers / pcmcia / cs.c
index 8c51493..c338375 100644 (file)
@@ -76,83 +76,18 @@ 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);
        if (!dev)
                return NULL;
-       skt = dev_get_drvdata(dev);
-       if (!try_module_get(skt->owner)) {
-               put_device(&skt->dev);
-               return NULL;
-       }
-       return skt;
+       return dev_get_drvdata(dev);
 }
 EXPORT_SYMBOL(pcmcia_get_socket);
 
 
 void pcmcia_put_socket(struct pcmcia_socket *skt)
 {
-       module_put(skt->owner);
        put_device(&skt->dev);
 }
 EXPORT_SYMBOL(pcmcia_put_socket);
@@ -181,8 +116,6 @@ int pcmcia_register_socket(struct pcmcia_socket *socket)
 
        dev_dbg(&socket->dev, "pcmcia_register_socket(0x%p)\n", socket->ops);
 
-       spin_lock_init(&socket->lock);
-
        /* try to obtain a socket number [yes, it gets ugly if we
         * register more than 2^sizeof(unsigned int) pcmcia
         * sockets... but the socket number is deprecated
@@ -228,10 +161,13 @@ int pcmcia_register_socket(struct pcmcia_socket *socket)
        init_completion(&socket->socket_released);
        init_completion(&socket->thread_done);
        mutex_init(&socket->skt_mutex);
+       mutex_init(&socket->ops_mutex);
        spin_lock_init(&socket->thread_lock);
 
        if (socket->resource_ops->init) {
+               mutex_lock(&socket->ops_mutex);
                ret = socket->resource_ops->init(socket);
+               mutex_unlock(&socket->ops_mutex);
                if (ret)
                        goto err;
        }
@@ -283,15 +219,17 @@ void pcmcia_unregister_socket(struct pcmcia_socket *socket)
        if (socket->thread)
                kthread_stop(socket->thread);
 
-       release_cis_mem(socket);
-
        /* remove from our own list */
        down_write(&pcmcia_socket_list_rwsem);
        list_del(&socket->socket_list);
        up_write(&pcmcia_socket_list_rwsem);
 
        /* wait for sysfs to drop all references */
-       release_resource_db(socket);
+       if (socket->resource_ops->exit) {
+               mutex_lock(&socket->ops_mutex);
+               socket->resource_ops->exit(socket);
+               mutex_unlock(&socket->ops_mutex);
+       }
        wait_for_completion(&socket->socket_released);
 } /* pcmcia_unregister_socket */
 EXPORT_SYMBOL(pcmcia_unregister_socket);
@@ -389,6 +327,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);
+
+       mutex_lock(&s->ops_mutex);
        s->state &= SOCKET_INUSE | SOCKET_PRESENT;
        msleep(shutdown_delay * 10);
        s->state &= SOCKET_INUSE;
@@ -399,13 +339,21 @@ static void socket_shutdown(struct pcmcia_socket *s)
        s->ops->set_socket(s, &s->socket);
        s->irq.AssignedIRQ = s->irq.Config = 0;
        s->lock_count = 0;
-       destroy_cis_cache(s);
        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);
@@ -416,7 +364,7 @@ static void socket_shutdown(struct pcmcia_socket *s)
                           "*** DANGER *** unable to remove socket power\n");
        }
 
-       cs_socket_put(s);
+       s->state &= ~SOCKET_INUSE;
 }
 
 static int socket_setup(struct pcmcia_socket *skt, int initial_delay)
@@ -505,8 +453,12 @@ static int socket_insert(struct pcmcia_socket *skt)
 
        dev_dbg(&skt->dev, "insert\n");
 
-       if (!cs_socket_get(skt))
-               return -ENODEV;
+       mutex_lock(&skt->ops_mutex);
+       if (skt->state & SOCKET_INUSE) {
+               mutex_unlock(&skt->ops_mutex);
+               return -EINVAL;
+       }
+       skt->state |= SOCKET_INUSE;
 
        ret = socket_setup(skt, setup_delay);
        if (ret == 0) {
@@ -524,9 +476,11 @@ static int socket_insert(struct pcmcia_socket *skt)
                }
 #endif
                dev_dbg(&skt->dev, "insert done\n");
+               mutex_unlock(&skt->ops_mutex);
 
                send_event(skt, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW);
        } else {
+               mutex_unlock(&skt->ops_mutex);
                socket_shutdown(skt);
        }
 
@@ -538,6 +492,7 @@ static int socket_suspend(struct pcmcia_socket *skt)
        if (skt->state & SOCKET_SUSPEND)
                return -EBUSY;
 
+       mutex_lock(&skt->ops_mutex);
        skt->suspended_state = skt->state;
 
        send_event(skt, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW);
@@ -546,26 +501,36 @@ static int socket_suspend(struct pcmcia_socket *skt)
        if (skt->ops->suspend)
                skt->ops->suspend(skt);
        skt->state |= SOCKET_SUSPEND;
-
+       mutex_unlock(&skt->ops_mutex);
        return 0;
 }
 
 static int socket_early_resume(struct pcmcia_socket *skt)
 {
+       mutex_lock(&skt->ops_mutex);
        skt->socket = dead_socket;
        skt->ops->init(skt);
        skt->ops->set_socket(skt, &skt->socket);
        if (skt->state & SOCKET_PRESENT)
                skt->resume_status = socket_setup(skt, resume_delay);
+       mutex_unlock(&skt->ops_mutex);
        return 0;
 }
 
 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);
@@ -671,20 +636,26 @@ 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);
 
+               mutex_lock(&skt->skt_mutex);
                if (events) {
-                       mutex_lock(&skt->skt_mutex);
                        if (events & SS_DETECT)
                                socket_detect_change(skt);
                        if (events & SS_BATDEAD)
@@ -693,10 +664,41 @@ 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_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;
 
@@ -706,6 +708,13 @@ static int pccardd(void *__skt)
        /* make sure we are running before we exit */
        set_current_state(TASK_RUNNING);
 
+       /* shut down socket, if a device is still present */
+       if (skt->state & SOCKET_PRESENT) {
+               mutex_lock(&skt->skt_mutex);
+               socket_remove(skt);
+               mutex_unlock(&skt->skt_mutex);
+       }
+
        /* remove from the device core */
        pccard_sysfs_remove_socket(&skt->dev);
        device_unregister(&skt->dev);
@@ -731,6 +740,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)
@@ -795,7 +829,10 @@ int pcmcia_reset_card(struct pcmcia_socket *skt)
                        send_event(skt, CS_EVENT_RESET_PHYSICAL, CS_EVENT_PRI_LOW);
                        if (skt->callback)
                                skt->callback->suspend(skt);
-                       if (socket_reset(skt) == 0) {
+                       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);
@@ -811,146 +848,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);