vserver 1.9.3
[linux-2.6.git] / drivers / input / serio / serio.c
index 6f00319..268ca1d 100644 (file)
@@ -1,11 +1,9 @@
-/*
- * $Id: serio.c,v 1.15 2002/01/22 21:12:03 vojtech Exp $
- *
- *  Copyright (c) 1999-2001 Vojtech Pavlik
- */
-
 /*
  *  The Serio abstraction module
+ *
+ *  Copyright (c) 1999-2004 Vojtech Pavlik
+ *  Copyright (c) 2004 Dmitry Torokhov
+ *  Copyright (c) 2003 Daniele Bellucci
  */
 
 /*
  * Should you need to contact me, the author, you can do so either by
  * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
  * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
- *
- * Changes:
- * 20 Jul. 2003    Daniele Bellucci <bellucda@tiscali.it>
- *                 Minor cleanups.
  */
 
 #include <linux/stddef.h>
@@ -50,100 +44,178 @@ MODULE_LICENSE("GPL");
 EXPORT_SYMBOL(serio_interrupt);
 EXPORT_SYMBOL(serio_register_port);
 EXPORT_SYMBOL(serio_register_port_delayed);
-EXPORT_SYMBOL(__serio_register_port);
 EXPORT_SYMBOL(serio_unregister_port);
 EXPORT_SYMBOL(serio_unregister_port_delayed);
-EXPORT_SYMBOL(__serio_unregister_port);
-EXPORT_SYMBOL(serio_register_device);
-EXPORT_SYMBOL(serio_unregister_device);
+EXPORT_SYMBOL(serio_register_driver);
+EXPORT_SYMBOL(serio_unregister_driver);
 EXPORT_SYMBOL(serio_open);
 EXPORT_SYMBOL(serio_close);
 EXPORT_SYMBOL(serio_rescan);
 EXPORT_SYMBOL(serio_reconnect);
 
+static DECLARE_MUTEX(serio_sem);       /* protects serio_list and serio_diriver_list */
+static LIST_HEAD(serio_list);
+static LIST_HEAD(serio_driver_list);
+static unsigned int serio_no;
+
+struct bus_type serio_bus = {
+       .name = "serio",
+};
+
+static void serio_find_driver(struct serio *serio);
+static void serio_create_port(struct serio *serio);
+static void serio_destroy_port(struct serio *serio);
+static void serio_connect_port(struct serio *serio, struct serio_driver *drv);
+static void serio_reconnect_port(struct serio *serio);
+static void serio_disconnect_port(struct serio *serio);
+
+static int serio_bind_driver(struct serio *serio, struct serio_driver *drv)
+{
+       get_driver(&drv->driver);
+
+       drv->connect(serio, drv);
+       if (serio->drv) {
+               down_write(&serio_bus.subsys.rwsem);
+               serio->dev.driver = &drv->driver;
+               device_bind_driver(&serio->dev);
+               up_write(&serio_bus.subsys.rwsem);
+               return 1;
+       }
+
+       put_driver(&drv->driver);
+       return 0;
+}
+
+/* serio_find_driver() must be called with serio_sem down.  */
+static void serio_find_driver(struct serio *serio)
+{
+       struct serio_driver *drv;
+
+       list_for_each_entry(drv, &serio_driver_list, node)
+               if (!drv->manual_bind)
+                       if (serio_bind_driver(serio, drv))
+                               break;
+}
+
+/*
+ * Serio event processing.
+ */
+
 struct serio_event {
        int type;
        struct serio *serio;
        struct list_head node;
 };
 
-static DECLARE_MUTEX(serio_sem);
-static LIST_HEAD(serio_list);
-static LIST_HEAD(serio_dev_list);
+enum serio_event_type {
+       SERIO_RESCAN,
+       SERIO_RECONNECT,
+       SERIO_REGISTER_PORT,
+       SERIO_UNREGISTER_PORT,
+};
+
+static spinlock_t serio_event_lock = SPIN_LOCK_UNLOCKED;       /* protects serio_event_list */
 static LIST_HEAD(serio_event_list);
+static DECLARE_WAIT_QUEUE_HEAD(serio_wait);
+static DECLARE_COMPLETION(serio_exited);
 static int serio_pid;
 
-static void serio_find_dev(struct serio *serio)
+static void serio_queue_event(struct serio *serio, int event_type)
 {
-       struct serio_dev *dev;
+       unsigned long flags;
+       struct serio_event *event;
 
-       list_for_each_entry(dev, &serio_dev_list, node) {
-               if (serio->dev)
-                       break;
-               if (dev->connect)
-                       dev->connect(serio, dev);
-       }
-}
+       spin_lock_irqsave(&serio_event_lock, flags);
 
-#define SERIO_RESCAN           1
-#define SERIO_RECONNECT                2
-#define SERIO_REGISTER_PORT    3
-#define SERIO_UNREGISTER_PORT  4
+       if ((event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC))) {
+               event->type = event_type;
+               event->serio = serio;
 
-static DECLARE_WAIT_QUEUE_HEAD(serio_wait);
-static DECLARE_COMPLETION(serio_exited);
+               list_add_tail(&event->node, &serio_event_list);
+               wake_up(&serio_wait);
+       }
+
+       spin_unlock_irqrestore(&serio_event_lock, flags);
+}
 
-static void serio_invalidate_pending_events(struct serio *serio)
+static struct serio_event *serio_get_event(void)
 {
        struct serio_event *event;
+       struct list_head *node;
+       unsigned long flags;
+
+       spin_lock_irqsave(&serio_event_lock, flags);
+
+       if (list_empty(&serio_event_list)) {
+               spin_unlock_irqrestore(&serio_event_lock, flags);
+               return NULL;
+       }
+
+       node = serio_event_list.next;
+       event = container_of(node, struct serio_event, node);
+       list_del_init(node);
+
+       spin_unlock_irqrestore(&serio_event_lock, flags);
 
-       list_for_each_entry(event, &serio_event_list, node)
-               if (event->serio == serio)
-                       event->serio = NULL;
+       return event;
 }
 
-void serio_handle_events(void)
+static void serio_handle_events(void)
 {
-       struct list_head *node, *next;
        struct serio_event *event;
 
-       list_for_each_safe(node, next, &serio_event_list) {
-               event = container_of(node, struct serio_event, node);
+       while ((event = serio_get_event())) {
 
                down(&serio_sem);
-               if (event->serio == NULL)
-                       goto event_done;
 
                switch (event->type) {
                        case SERIO_REGISTER_PORT :
-                               __serio_register_port(event->serio);
+                               serio_create_port(event->serio);
+                               serio_connect_port(event->serio, NULL);
                                break;
 
                        case SERIO_UNREGISTER_PORT :
-                               __serio_unregister_port(event->serio);
+                               serio_disconnect_port(event->serio);
+                               serio_destroy_port(event->serio);
                                break;
 
                        case SERIO_RECONNECT :
-                               if (event->serio->dev && event->serio->dev->reconnect)
-                                       if (event->serio->dev->reconnect(event->serio) == 0)
-                                               break;
-                               /* reconnect failed - fall through to rescan */
+                               serio_reconnect_port(event->serio);
+                               break;
 
                        case SERIO_RESCAN :
-                               if (event->serio->dev && event->serio->dev->disconnect)
-                                       event->serio->dev->disconnect(event->serio);
-                               serio_find_dev(event->serio);
+                               serio_disconnect_port(event->serio);
+                               serio_connect_port(event->serio, NULL);
                                break;
                        default:
                                break;
                }
-event_done:
+
                up(&serio_sem);
-               list_del_init(node);
                kfree(event);
        }
 }
 
+static void serio_remove_pending_events(struct serio *serio)
+{
+       struct list_head *node, *next;
+       struct serio_event *event;
+       unsigned long flags;
+
+       spin_lock_irqsave(&serio_event_lock, flags);
+
+       list_for_each_safe(node, next, &serio_event_list) {
+               event = container_of(node, struct serio_event, node);
+               if (event->serio == serio) {
+                       list_del_init(node);
+                       kfree(event);
+               }
+       }
+
+       spin_unlock_irqrestore(&serio_event_lock, flags);
+}
+
+
 static int serio_thread(void *nothing)
 {
        lock_kernel();
@@ -163,52 +235,239 @@ static int serio_thread(void *nothing)
        complete_and_exit(&serio_exited, 0);
 }
 
-static void serio_queue_event(struct serio *serio, int event_type)
+
+/*
+ * Serio port operations
+ */
+
+static ssize_t serio_show_description(struct device *dev, char *buf)
 {
-       struct serio_event *event;
+       struct serio *serio = to_serio_port(dev);
+       return sprintf(buf, "%s\n", serio->name);
+}
 
-       if ((event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC))) {
-               event->type = event_type;
-               event->serio = serio;
+static ssize_t serio_show_driver(struct device *dev, char *buf)
+{
+       return sprintf(buf, "%s\n", dev->driver ? dev->driver->name : "(none)");
+}
 
-               list_add_tail(&event->node, &serio_event_list);
-               wake_up(&serio_wait);
+static ssize_t serio_rebind_driver(struct device *dev, const char *buf, size_t count)
+{
+       struct serio *serio = to_serio_port(dev);
+       struct device_driver *drv;
+       int retval;
+
+       retval = down_interruptible(&serio_sem);
+       if (retval)
+               return retval;
+
+       retval = count;
+       if (!strncmp(buf, "none", count)) {
+               serio_disconnect_port(serio);
+       } else if (!strncmp(buf, "reconnect", count)) {
+               serio_reconnect_port(serio);
+       } else if (!strncmp(buf, "rescan", count)) {
+               serio_disconnect_port(serio);
+               serio_connect_port(serio, NULL);
+       } else if ((drv = driver_find(buf, &serio_bus)) != NULL) {
+               serio_disconnect_port(serio);
+               serio_connect_port(serio, to_serio_driver(drv));
+               put_driver(drv);
+       } else {
+               retval = -EINVAL;
        }
+
+       up(&serio_sem);
+
+       return retval;
 }
 
-void serio_rescan(struct serio *serio)
+static ssize_t serio_show_bind_mode(struct device *dev, char *buf)
 {
-       serio_queue_event(serio, SERIO_RESCAN);
+       struct serio *serio = to_serio_port(dev);
+       return sprintf(buf, "%s\n", serio->manual_bind ? "manual" : "auto");
 }
 
-void serio_reconnect(struct serio *serio)
+static ssize_t serio_set_bind_mode(struct device *dev, const char *buf, size_t count)
 {
-       serio_queue_event(serio, SERIO_RECONNECT);
+       struct serio *serio = to_serio_port(dev);
+       int retval;
+
+       retval = count;
+       if (!strncmp(buf, "manual", count)) {
+               serio->manual_bind = 1;
+       } else if (!strncmp(buf, "auto", count)) {
+               serio->manual_bind = 0;
+       } else {
+               retval = -EINVAL;
+       }
+
+       return retval;
 }
 
-irqreturn_t serio_interrupt(struct serio *serio,
-               unsigned char data, unsigned int flags, struct pt_regs *regs)
+static struct device_attribute serio_device_attrs[] = {
+       __ATTR(description, S_IRUGO, serio_show_description, NULL),
+       __ATTR(driver, S_IWUSR | S_IRUGO, serio_show_driver, serio_rebind_driver),
+       __ATTR(bind_mode, S_IWUSR | S_IRUGO, serio_show_bind_mode, serio_set_bind_mode),
+       __ATTR_NULL
+};
+
+
+static void serio_release_port(struct device *dev)
 {
-       irqreturn_t ret = IRQ_NONE;
+       struct serio *serio = to_serio_port(dev);
 
-        if (serio->dev && serio->dev->interrupt) {
-                ret = serio->dev->interrupt(serio, data, flags, regs);
-       } else {
-               if (!flags) {
-                       if ((serio->type == SERIO_8042 ||
-                               serio->type == SERIO_8042_XL) && (data != 0xaa))
-                                       return ret;
-                       serio_rescan(serio);
-                       ret = IRQ_HANDLED;
+       kfree(serio);
+       module_put(THIS_MODULE);
+}
+
+static void serio_create_port(struct serio *serio)
+{
+       try_module_get(THIS_MODULE);
+
+       spin_lock_init(&serio->lock);
+       list_add_tail(&serio->node, &serio_list);
+       snprintf(serio->dev.bus_id, sizeof(serio->dev.bus_id), "serio%d", serio_no++);
+       serio->dev.bus = &serio_bus;
+       serio->dev.release = serio_release_port;
+       if (serio->parent)
+               serio->dev.parent = &serio->parent->dev;
+       device_register(&serio->dev);
+}
+
+/*
+ * serio_destroy_port() completes deregistration process and removes
+ * port from the system
+ */
+static void serio_destroy_port(struct serio *serio)
+{
+       struct serio_driver *drv = serio->drv;
+       unsigned long flags;
+
+       serio_remove_pending_events(serio);
+       list_del_init(&serio->node);
+
+       if (drv) {
+               drv->disconnect(serio);
+               down_write(&serio_bus.subsys.rwsem);
+               device_release_driver(&serio->dev);
+               up_write(&serio_bus.subsys.rwsem);
+               put_driver(&drv->driver);
+       }
+
+       if (serio->parent) {
+               spin_lock_irqsave(&serio->parent->lock, flags);
+               serio->parent->child = NULL;
+               spin_unlock_irqrestore(&serio->parent->lock, flags);
+       }
+
+       device_unregister(&serio->dev);
+}
+
+/*
+ * serio_connect_port() tries to bind the port and possible all its
+ * children to appropriate drivers. If driver passed in the function will not
+ * try otehr drivers when binding parent port.
+ */
+static void serio_connect_port(struct serio *serio, struct serio_driver *drv)
+{
+       WARN_ON(serio->drv);
+       WARN_ON(serio->child);
+
+       if (drv)
+               serio_bind_driver(serio, drv);
+       else if (!serio->manual_bind)
+               serio_find_driver(serio);
+
+       /* Ok, now bind children, if any */
+       while (serio->child) {
+               serio = serio->child;
+
+               WARN_ON(serio->drv);
+               WARN_ON(serio->child);
+
+               serio_create_port(serio);
+
+               if (!serio->manual_bind) {
+                       /*
+                        * With children we just _prefer_ passed in driver,
+                        * but we will try other options in case preferred
+                        * is not the one
+                        */
+                       if (!drv || !serio_bind_driver(serio, drv))
+                               serio_find_driver(serio);
                }
        }
-       return ret;
+}
+
+/*
+ *
+ */
+static void serio_reconnect_port(struct serio *serio)
+{
+       do {
+               if (!serio->drv || !serio->drv->reconnect || serio->drv->reconnect(serio)) {
+                       serio_disconnect_port(serio);
+                       serio_connect_port(serio, NULL);
+                       /* Ok, old children are now gone, we are done */
+                       break;
+               }
+               serio = serio->child;
+       } while (serio);
+}
+
+/*
+ * serio_disconnect_port() unbinds a port from its driver. As a side effect
+ * all child ports are unbound and destroyed.
+ */
+static void serio_disconnect_port(struct serio *serio)
+{
+       struct serio_driver *drv = serio->drv;
+       struct serio *s;
+
+       if (serio->child) {
+               /*
+                * Children ports should be disconnected and destroyed
+                * first, staring with the leaf one, since we don't want
+                * to do recursion
+                */
+               do {
+                       s = serio->child;
+               } while (s->child);
+
+               while (s != serio) {
+                       s = s->parent;
+                       serio_destroy_port(s->child);
+               }
+       }
+
+       /*
+        * Ok, no children left, now disconnect this port
+        */
+       if (drv) {
+               drv->disconnect(serio);
+               down_write(&serio_bus.subsys.rwsem);
+               device_release_driver(&serio->dev);
+               up_write(&serio_bus.subsys.rwsem);
+               put_driver(&drv->driver);
+       }
+}
+
+void serio_rescan(struct serio *serio)
+{
+       serio_queue_event(serio, SERIO_RESCAN);
+}
+
+void serio_reconnect(struct serio *serio)
+{
+       serio_queue_event(serio, SERIO_RECONNECT);
 }
 
 void serio_register_port(struct serio *serio)
 {
        down(&serio_sem);
-       __serio_register_port(serio);
+       serio_create_port(serio);
+       serio_connect_port(serio, NULL);
        up(&serio_sem);
 }
 
@@ -222,21 +481,11 @@ void serio_register_port_delayed(struct serio *serio)
        serio_queue_event(serio, SERIO_REGISTER_PORT);
 }
 
-/*
- * Should only be called directly if serio_sem has already been taken,
- * for example when unregistering a serio from other input device's
- * connect() function.
- */
-void __serio_register_port(struct serio *serio)
-{
-       list_add_tail(&serio->node, &serio_list);
-       serio_find_dev(serio);
-}
-
 void serio_unregister_port(struct serio *serio)
 {
        down(&serio_sem);
-       __serio_unregister_port(serio);
+       serio_disconnect_port(serio);
+       serio_destroy_port(serio);
        up(&serio_sem);
 }
 
@@ -250,82 +499,171 @@ void serio_unregister_port_delayed(struct serio *serio)
        serio_queue_event(serio, SERIO_UNREGISTER_PORT);
 }
 
+
 /*
- * Should only be called directly if serio_sem has already been taken,
- * for example when unregistering a serio from other input device's
- * disconnect() function.
+ * Serio driver operations
  */
-void __serio_unregister_port(struct serio *serio)
+
+static ssize_t serio_driver_show_description(struct device_driver *drv, char *buf)
 {
-       serio_invalidate_pending_events(serio);
-       list_del_init(&serio->node);
-       if (serio->dev && serio->dev->disconnect)
-               serio->dev->disconnect(serio);
+       struct serio_driver *driver = to_serio_driver(drv);
+       return sprintf(buf, "%s\n", driver->description ? driver->description : "(none)");
+}
+
+static ssize_t serio_driver_show_bind_mode(struct device_driver *drv, char *buf)
+{
+       struct serio_driver *serio_drv = to_serio_driver(drv);
+       return sprintf(buf, "%s\n", serio_drv->manual_bind ? "manual" : "auto");
 }
 
-void serio_register_device(struct serio_dev *dev)
+static ssize_t serio_driver_set_bind_mode(struct device_driver *drv, const char *buf, size_t count)
+{
+       struct serio_driver *serio_drv = to_serio_driver(drv);
+       int retval;
+
+       retval = count;
+       if (!strncmp(buf, "manual", count)) {
+               serio_drv->manual_bind = 1;
+       } else if (!strncmp(buf, "auto", count)) {
+               serio_drv->manual_bind = 0;
+       } else {
+               retval = -EINVAL;
+       }
+
+       return retval;
+}
+
+
+static struct driver_attribute serio_driver_attrs[] = {
+       __ATTR(description, S_IRUGO, serio_driver_show_description, NULL),
+       __ATTR(bind_mode, S_IWUSR | S_IRUGO,
+               serio_driver_show_bind_mode, serio_driver_set_bind_mode),
+       __ATTR_NULL
+};
+
+void serio_register_driver(struct serio_driver *drv)
 {
        struct serio *serio;
+
        down(&serio_sem);
-       list_add_tail(&dev->node, &serio_dev_list);
-       list_for_each_entry(serio, &serio_list, node)
-               if (!serio->dev && dev->connect)
-                       dev->connect(serio, dev);
+
+       list_add_tail(&drv->node, &serio_driver_list);
+
+       drv->driver.bus = &serio_bus;
+       driver_register(&drv->driver);
+
+       if (drv->manual_bind)
+               goto out;
+
+start_over:
+       list_for_each_entry(serio, &serio_list, node) {
+               if (!serio->drv) {
+                       serio_connect_port(serio, drv);
+                       /*
+                        * if new child appeared then the list is changed,
+                        * we need to start over
+                        */
+                       if (serio->child)
+                               goto start_over;
+               }
+       }
+
+out:
        up(&serio_sem);
 }
 
-void serio_unregister_device(struct serio_dev *dev)
+void serio_unregister_driver(struct serio_driver *drv)
 {
        struct serio *serio;
 
        down(&serio_sem);
-       list_del_init(&dev->node);
 
+       list_del_init(&drv->node);
+
+start_over:
        list_for_each_entry(serio, &serio_list, node) {
-               if (serio->dev == dev && dev->disconnect)
-                       dev->disconnect(serio);
-               serio_find_dev(serio);
+               if (serio->drv == drv) {
+                       serio_disconnect_port(serio);
+                       serio_connect_port(serio, NULL);
+                       /* we could've deleted some ports, restart */
+                       goto start_over;
+               }
        }
+
+       driver_unregister(&drv->driver);
+
        up(&serio_sem);
 }
 
-/* called from serio_dev->connect/disconnect methods under serio_sem */
-int serio_open(struct serio *serio, struct serio_dev *dev)
+/* called from serio_driver->connect/disconnect methods under serio_sem */
+int serio_open(struct serio *serio, struct serio_driver *drv)
 {
-       serio->dev = dev;
+       serio_pause_rx(serio);
+       serio->drv = drv;
+       serio_continue_rx(serio);
+
        if (serio->open && serio->open(serio)) {
-               serio->dev = NULL;
+               serio_pause_rx(serio);
+               serio->drv = NULL;
+               serio_continue_rx(serio);
                return -1;
        }
        return 0;
 }
 
-/* called from serio_dev->connect/disconnect methods under serio_sem */
+/* called from serio_driver->connect/disconnect methods under serio_sem */
 void serio_close(struct serio *serio)
 {
        if (serio->close)
                serio->close(serio);
-       serio->dev = NULL;
+
+       serio_pause_rx(serio);
+       serio->drv = NULL;
+       serio_continue_rx(serio);
 }
 
-static int __init serio_init(void)
+irqreturn_t serio_interrupt(struct serio *serio,
+               unsigned char data, unsigned int dfl, struct pt_regs *regs)
 {
-       int pid;
+       unsigned long flags;
+       irqreturn_t ret = IRQ_NONE;
+
+       spin_lock_irqsave(&serio->lock, flags);
+
+        if (likely(serio->drv)) {
+                ret = serio->drv->interrupt(serio, data, dfl, regs);
+       } else {
+               if (!dfl) {
+                       if ((serio->type != SERIO_8042 &&
+                            serio->type != SERIO_8042_XL) || (data == 0xaa)) {
+                               serio_rescan(serio);
+                               ret = IRQ_HANDLED;
+                       }
+               }
+       }
+
+       spin_unlock_irqrestore(&serio->lock, flags);
 
-       pid = kernel_thread(serio_thread, NULL, CLONE_KERNEL);
+       return ret;
+}
 
-       if (!pid) {
+static int __init serio_init(void)
+{
+       if (!(serio_pid = kernel_thread(serio_thread, NULL, CLONE_KERNEL))) {
                printk(KERN_WARNING "serio: Failed to start kseriod\n");
                return -1;
        }
 
-       serio_pid = pid;
+       serio_bus.dev_attrs = serio_device_attrs;
+       serio_bus.drv_attrs = serio_driver_attrs;
+       bus_register(&serio_bus);
 
        return 0;
 }
 
 static void __exit serio_exit(void)
 {
+       bus_unregister(&serio_bus);
        kill_proc(serio_pid, SIGTERM, 1);
        wait_for_completion(&serio_exited);
 }