diff options
Diffstat (limited to 'drivers/input/gameport/gameport.c')
| -rw-r--r-- | drivers/input/gameport/gameport.c | 456 |
1 files changed, 244 insertions, 212 deletions
diff --git a/drivers/input/gameport/gameport.c b/drivers/input/gameport/gameport.c index ab09cf4093e..24c41ba7d4e 100644 --- a/drivers/input/gameport/gameport.c +++ b/drivers/input/gameport/gameport.c @@ -11,16 +11,18 @@ * the Free Software Foundation. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/stddef.h> #include <linux/module.h> #include <linux/ioport.h> #include <linux/init.h> #include <linux/gameport.h> -#include <linux/wait.h> -#include <linux/sched.h> #include <linux/slab.h> #include <linux/delay.h> -#include <linux/kthread.h> +#include <linux/workqueue.h> +#include <linux/sched.h> /* HZ */ +#include <linux/mutex.h> /*#include <asm/io.h>*/ @@ -28,39 +30,24 @@ MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); MODULE_DESCRIPTION("Generic gameport layer"); MODULE_LICENSE("GPL"); -EXPORT_SYMBOL(__gameport_register_port); -EXPORT_SYMBOL(gameport_unregister_port); -EXPORT_SYMBOL(__gameport_register_driver); -EXPORT_SYMBOL(gameport_unregister_driver); -EXPORT_SYMBOL(gameport_open); -EXPORT_SYMBOL(gameport_close); -EXPORT_SYMBOL(gameport_rescan); -EXPORT_SYMBOL(gameport_cooked_read); -EXPORT_SYMBOL(gameport_set_name); -EXPORT_SYMBOL(gameport_set_phys); -EXPORT_SYMBOL(gameport_start_polling); -EXPORT_SYMBOL(gameport_stop_polling); - /* - * gameport_sem protects entire gameport subsystem and is taken + * gameport_mutex protects entire gameport subsystem and is taken * every time gameport port or driver registrered or unregistered. */ -static DECLARE_MUTEX(gameport_sem); +static DEFINE_MUTEX(gameport_mutex); static LIST_HEAD(gameport_list); -static struct bus_type gameport_bus = { - .name = "gameport", -}; +static struct bus_type gameport_bus; static void gameport_add_port(struct gameport *gameport); -static void gameport_destroy_port(struct gameport *gameport); +static void gameport_attach_driver(struct gameport_driver *drv); static void gameport_reconnect_port(struct gameport *gameport); static void gameport_disconnect_port(struct gameport *gameport); #if defined(__i386__) -#include <asm/i8253.h> +#include <linux/i8253.h> #define DELTA(x,y) ((y)-(x)+((y)<(x)?1193182/HZ:0)) #define GET_TIME(x) do { x = get_time_pit(); } while (0) @@ -70,11 +57,11 @@ static unsigned int get_time_pit(void) unsigned long flags; unsigned int count; - spin_lock_irqsave(&i8253_lock, flags); + raw_spin_lock_irqsave(&i8253_lock, flags); outb_p(0x00, 0x43); count = inb_p(0x40); count |= inb_p(0x40) << 8; - spin_unlock_irqrestore(&i8253_lock, flags); + raw_spin_unlock_irqrestore(&i8253_lock, flags); return count; } @@ -134,7 +121,8 @@ static int gameport_measure_speed(struct gameport *gameport) } gameport_close(gameport); - return (cpu_data[raw_smp_processor_id()].loops_per_jiffy * (unsigned long)HZ / (1000 / 50)) / (tx < 1 ? 1 : tx); + return (this_cpu_read(cpu_info.loops_per_jiffy) * + (unsigned long)HZ / (1000 / 50)) / (tx < 1 ? 1 : tx); #else @@ -164,6 +152,7 @@ void gameport_start_polling(struct gameport *gameport) spin_unlock(&gameport->timer_lock); } +EXPORT_SYMBOL(gameport_start_polling); void gameport_stop_polling(struct gameport *gameport) { @@ -174,6 +163,7 @@ void gameport_stop_polling(struct gameport *gameport) spin_unlock(&gameport->timer_lock); } +EXPORT_SYMBOL(gameport_stop_polling); static void gameport_run_poll_handler(unsigned long d) { @@ -188,32 +178,39 @@ static void gameport_run_poll_handler(unsigned long d) * Basic gameport -> driver core mappings */ -static void gameport_bind_driver(struct gameport *gameport, struct gameport_driver *drv) +static int gameport_bind_driver(struct gameport *gameport, struct gameport_driver *drv) { - down_write(&gameport_bus.subsys.rwsem); + int error; gameport->dev.driver = &drv->driver; if (drv->connect(gameport, drv)) { gameport->dev.driver = NULL; - goto out; + return -ENODEV; } - device_bind_driver(&gameport->dev); -out: - up_write(&gameport_bus.subsys.rwsem); -} -static void gameport_release_driver(struct gameport *gameport) -{ - down_write(&gameport_bus.subsys.rwsem); - device_release_driver(&gameport->dev); - up_write(&gameport_bus.subsys.rwsem); + error = device_bind_driver(&gameport->dev); + if (error) { + dev_warn(&gameport->dev, + "device_bind_driver() failed for %s (%s) and %s, error: %d\n", + gameport->phys, gameport->name, + drv->description, error); + drv->disconnect(gameport); + gameport->dev.driver = NULL; + return error; + } + + return 0; } static void gameport_find_driver(struct gameport *gameport) { - down_write(&gameport_bus.subsys.rwsem); - device_attach(&gameport->dev); - up_write(&gameport_bus.subsys.rwsem); + int error; + + error = device_attach(&gameport->dev); + if (error < 0) + dev_warn(&gameport->dev, + "device_attach() failed for %s (%s), error: %d\n", + gameport->phys, gameport->name, error); } @@ -222,10 +219,8 @@ static void gameport_find_driver(struct gameport *gameport) */ enum gameport_event_type { - GAMEPORT_RESCAN, - GAMEPORT_RECONNECT, GAMEPORT_REGISTER_PORT, - GAMEPORT_REGISTER_DRIVER, + GAMEPORT_ATTACH_DRIVER, }; struct gameport_event { @@ -237,49 +232,22 @@ struct gameport_event { static DEFINE_SPINLOCK(gameport_event_lock); /* protects gameport_event_list */ static LIST_HEAD(gameport_event_list); -static DECLARE_WAIT_QUEUE_HEAD(gameport_wait); -static struct task_struct *gameport_task; -static void gameport_queue_event(void *object, struct module *owner, - enum gameport_event_type event_type) +static struct gameport_event *gameport_get_event(void) { + struct gameport_event *event = NULL; unsigned long flags; - struct gameport_event *event; spin_lock_irqsave(&gameport_event_lock, flags); - /* - * Scan event list for the other events for the same gameport port, - * starting with the most recent one. If event is the same we - * do not need add new one. If event is of different type we - * need to add this event and should not look further because - * we need to preseve sequence of distinct events. - */ - list_for_each_entry_reverse(event, &gameport_event_list, node) { - if (event->object == object) { - if (event->type == event_type) - goto out; - break; - } + if (!list_empty(&gameport_event_list)) { + event = list_first_entry(&gameport_event_list, + struct gameport_event, node); + list_del_init(&event->node); } - if ((event = kmalloc(sizeof(struct gameport_event), GFP_ATOMIC))) { - if (!try_module_get(owner)) { - printk(KERN_WARNING "gameport: Can't get module reference, dropping event %d\n", event_type); - goto out; - } - - event->type = event_type; - event->object = object; - event->owner = owner; - - list_add_tail(&event->node, &gameport_event_list); - wake_up(&gameport_wait); - } else { - printk(KERN_ERR "gameport: Not enough memory to queue event %d\n", event_type); - } -out: spin_unlock_irqrestore(&gameport_event_lock, flags); + return event; } static void gameport_free_event(struct gameport_event *event) @@ -290,14 +258,12 @@ static void gameport_free_event(struct gameport_event *event) static void gameport_remove_duplicate_events(struct gameport_event *event) { - struct list_head *node, *next; - struct gameport_event *e; + struct gameport_event *e, *next; unsigned long flags; spin_lock_irqsave(&gameport_event_lock, flags); - list_for_each_safe(node, next, &gameport_event_list) { - e = list_entry(node, struct gameport_event, node); + list_for_each_entry_safe(e, next, &gameport_event_list, node) { if (event->object == e->object) { /* * If this event is of different type we should not @@ -307,7 +273,7 @@ static void gameport_remove_duplicate_events(struct gameport_event *event) if (event->type != e->type) break; - list_del_init(node); + list_del_init(&e->node); gameport_free_event(e); } } @@ -316,82 +282,105 @@ static void gameport_remove_duplicate_events(struct gameport_event *event) } -static struct gameport_event *gameport_get_event(void) +static void gameport_handle_events(struct work_struct *work) { struct gameport_event *event; - struct list_head *node; - unsigned long flags; - spin_lock_irqsave(&gameport_event_lock, flags); + mutex_lock(&gameport_mutex); - if (list_empty(&gameport_event_list)) { - spin_unlock_irqrestore(&gameport_event_lock, flags); - return NULL; - } + /* + * Note that we handle only one event here to give swsusp + * a chance to freeze kgameportd thread. Gameport events + * should be pretty rare so we are not concerned about + * taking performance hit. + */ + if ((event = gameport_get_event())) { - node = gameport_event_list.next; - event = list_entry(node, struct gameport_event, node); - list_del_init(node); + switch (event->type) { - spin_unlock_irqrestore(&gameport_event_lock, flags); + case GAMEPORT_REGISTER_PORT: + gameport_add_port(event->object); + break; - return event; + case GAMEPORT_ATTACH_DRIVER: + gameport_attach_driver(event->object); + break; + } + + gameport_remove_duplicate_events(event); + gameport_free_event(event); + } + + mutex_unlock(&gameport_mutex); } -static void gameport_handle_events(void) +static DECLARE_WORK(gameport_event_work, gameport_handle_events); + +static int gameport_queue_event(void *object, struct module *owner, + enum gameport_event_type event_type) { + unsigned long flags; struct gameport_event *event; - struct gameport_driver *gameport_drv; - - down(&gameport_sem); + int retval = 0; - while ((event = gameport_get_event())) { - - switch (event->type) { - case GAMEPORT_REGISTER_PORT: - gameport_add_port(event->object); - break; + spin_lock_irqsave(&gameport_event_lock, flags); - case GAMEPORT_RECONNECT: - gameport_reconnect_port(event->object); - break; + /* + * Scan event list for the other events for the same gameport port, + * starting with the most recent one. If event is the same we + * do not need add new one. If event is of different type we + * need to add this event and should not look further because + * we need to preserve sequence of distinct events. + */ + list_for_each_entry_reverse(event, &gameport_event_list, node) { + if (event->object == object) { + if (event->type == event_type) + goto out; + break; + } + } - case GAMEPORT_RESCAN: - gameport_disconnect_port(event->object); - gameport_find_driver(event->object); - break; + event = kmalloc(sizeof(struct gameport_event), GFP_ATOMIC); + if (!event) { + pr_err("Not enough memory to queue event %d\n", event_type); + retval = -ENOMEM; + goto out; + } - case GAMEPORT_REGISTER_DRIVER: - gameport_drv = event->object; - driver_register(&gameport_drv->driver); - break; + if (!try_module_get(owner)) { + pr_warning("Can't get module reference, dropping event %d\n", + event_type); + kfree(event); + retval = -EINVAL; + goto out; + } - default: - break; - } + event->type = event_type; + event->object = object; + event->owner = owner; - gameport_remove_duplicate_events(event); - gameport_free_event(event); - } + list_add_tail(&event->node, &gameport_event_list); + queue_work(system_long_wq, &gameport_event_work); - up(&gameport_sem); +out: + spin_unlock_irqrestore(&gameport_event_lock, flags); + return retval; } /* - * Remove all events that have been submitted for a given gameport port. + * Remove all events that have been submitted for a given object, + * be it a gameport port or a driver. */ -static void gameport_remove_pending_events(struct gameport *gameport) +static void gameport_remove_pending_events(void *object) { - struct list_head *node, *next; - struct gameport_event *event; + struct gameport_event *event, *next; unsigned long flags; spin_lock_irqsave(&gameport_event_lock, flags); - list_for_each_safe(node, next, &gameport_event_list) { - event = list_entry(node, struct gameport_event, node); - if (event->object == gameport) { - list_del_init(node); + list_for_each_entry_safe(event, next, &gameport_event_list, node) { + if (event->object == object) { + list_del_init(&event->node); gameport_free_event(event); } } @@ -429,41 +418,28 @@ static struct gameport *gameport_get_pending_child(struct gameport *parent) return child; } -static int gameport_thread(void *nothing) -{ - do { - gameport_handle_events(); - wait_event_interruptible(gameport_wait, - kthread_should_stop() || !list_empty(&gameport_event_list)); - try_to_freeze(); - } while (!kthread_should_stop()); - - printk(KERN_DEBUG "gameport: kgameportd exiting\n"); - return 0; -} - - /* * Gameport port operations */ -static ssize_t gameport_show_description(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t gameport_description_show(struct device *dev, struct device_attribute *attr, char *buf) { struct gameport *gameport = to_gameport_port(dev); + return sprintf(buf, "%s\n", gameport->name); } +static DEVICE_ATTR(description, S_IRUGO, gameport_description_show, NULL); -static ssize_t gameport_rebind_driver(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +static ssize_t drvctl_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct gameport *gameport = to_gameport_port(dev); struct device_driver *drv; - int retval; + int error; - retval = down_interruptible(&gameport_sem); - if (retval) - return retval; + error = mutex_lock_interruptible(&gameport_mutex); + if (error) + return error; - retval = count; if (!strncmp(buf, "none", count)) { gameport_disconnect_port(gameport); } else if (!strncmp(buf, "reconnect", count)) { @@ -473,22 +449,23 @@ static ssize_t gameport_rebind_driver(struct device *dev, struct device_attribut gameport_find_driver(gameport); } else if ((drv = driver_find(buf, &gameport_bus)) != NULL) { gameport_disconnect_port(gameport); - gameport_bind_driver(gameport, to_gameport_driver(drv)); - put_driver(drv); + error = gameport_bind_driver(gameport, to_gameport_driver(drv)); } else { - retval = -EINVAL; + error = -EINVAL; } - up(&gameport_sem); + mutex_unlock(&gameport_mutex); - return retval; + return error ? error : count; } +static DEVICE_ATTR_WO(drvctl); -static struct device_attribute gameport_device_attrs[] = { - __ATTR(description, S_IRUGO, gameport_show_description, NULL), - __ATTR(drvctl, S_IWUSR, NULL, gameport_rebind_driver), - __ATTR_NULL +static struct attribute *gameport_device_attrs[] = { + &dev_attr_description.attr, + &dev_attr_drvctl.attr, + NULL, }; +ATTRIBUTE_GROUPS(gameport_device); static void gameport_release_port(struct device *dev) { @@ -506,6 +483,7 @@ void gameport_set_phys(struct gameport *gameport, const char *fmt, ...) vsnprintf(gameport->phys, sizeof(gameport->phys), fmt, args); va_end(args); } +EXPORT_SYMBOL(gameport_set_phys); /* * Prepare gameport port for registration. @@ -516,15 +494,16 @@ static void gameport_init_port(struct gameport *gameport) __module_get(THIS_MODULE); - init_MUTEX(&gameport->drv_sem); + mutex_init(&gameport->drv_mutex); device_initialize(&gameport->dev); - snprintf(gameport->dev.bus_id, sizeof(gameport->dev.bus_id), - "gameport%lu", (unsigned long)atomic_inc_return(&gameport_no) - 1); + dev_set_name(&gameport->dev, "gameport%lu", + (unsigned long)atomic_inc_return(&gameport_no) - 1); gameport->dev.bus = &gameport_bus; gameport->dev.release = gameport_release_port; if (gameport->parent) gameport->dev.parent = &gameport->parent->dev; + INIT_LIST_HEAD(&gameport->node); spin_lock_init(&gameport->timer_lock); init_timer(&gameport->poll_timer); gameport->poll_timer.function = gameport_run_poll_handler; @@ -537,6 +516,8 @@ static void gameport_init_port(struct gameport *gameport) */ static void gameport_add_port(struct gameport *gameport) { + int error; + if (gameport->parent) gameport->parent->child = gameport; @@ -545,14 +526,17 @@ static void gameport_add_port(struct gameport *gameport) list_add_tail(&gameport->node, &gameport_list); if (gameport->io) - printk(KERN_INFO "gameport: %s is %s, io %#x, speed %dkHz\n", - gameport->name, gameport->phys, gameport->io, gameport->speed); + dev_info(&gameport->dev, "%s is %s, io %#x, speed %dkHz\n", + gameport->name, gameport->phys, gameport->io, gameport->speed); else - printk(KERN_INFO "gameport: %s is %s, speed %dkHz\n", + dev_info(&gameport->dev, "%s is %s, speed %dkHz\n", gameport->name, gameport->phys, gameport->speed); - device_add(&gameport->dev); - gameport->registered = 1; + error = device_add(&gameport->dev); + if (error) + dev_err(&gameport->dev, + "device_add() failed for %s (%s), error: %d\n", + gameport->phys, gameport->name, error); } /* @@ -574,11 +558,10 @@ static void gameport_destroy_port(struct gameport *gameport) gameport->parent = NULL; } - if (gameport->registered) { + if (device_is_registered(&gameport->dev)) device_del(&gameport->dev); - list_del_init(&gameport->node); - gameport->registered = 0; - } + + list_del_init(&gameport->node); gameport_remove_pending_events(gameport); put_device(&gameport->dev); @@ -620,7 +603,7 @@ static void gameport_disconnect_port(struct gameport *gameport) do { parent = s->parent; - gameport_release_driver(s); + device_release_driver(&s->dev); gameport_destroy_port(s); } while ((s = parent) != gameport); } @@ -628,17 +611,7 @@ static void gameport_disconnect_port(struct gameport *gameport) /* * Ok, no children left, now disconnect this port */ - gameport_release_driver(gameport); -} - -void gameport_rescan(struct gameport *gameport) -{ - gameport_queue_event(gameport, NULL, GAMEPORT_RESCAN); -} - -void gameport_reconnect(struct gameport *gameport) -{ - gameport_queue_event(gameport, NULL, GAMEPORT_RECONNECT); + device_release_driver(&gameport->dev); } /* @@ -650,33 +623,37 @@ void __gameport_register_port(struct gameport *gameport, struct module *owner) gameport_init_port(gameport); gameport_queue_event(gameport, owner, GAMEPORT_REGISTER_PORT); } +EXPORT_SYMBOL(__gameport_register_port); /* * Synchronously unregisters gameport port. */ void gameport_unregister_port(struct gameport *gameport) { - down(&gameport_sem); + mutex_lock(&gameport_mutex); gameport_disconnect_port(gameport); gameport_destroy_port(gameport); - up(&gameport_sem); + mutex_unlock(&gameport_mutex); } +EXPORT_SYMBOL(gameport_unregister_port); /* * Gameport driver operations */ -static ssize_t gameport_driver_show_description(struct device_driver *drv, char *buf) +static ssize_t description_show(struct device_driver *drv, char *buf) { struct gameport_driver *driver = to_gameport_driver(drv); return sprintf(buf, "%s\n", driver->description ? driver->description : "(none)"); } +static DRIVER_ATTR_RO(description); -static struct driver_attribute gameport_driver_attrs[] = { - __ATTR(description, S_IRUGO, gameport_driver_show_description, NULL), - __ATTR_NULL +static struct attribute *gameport_driver_attrs[] = { + &driver_attr_description.attr, + NULL }; +ATTRIBUTE_GROUPS(gameport_driver); static int gameport_driver_probe(struct device *dev) { @@ -696,20 +673,60 @@ static int gameport_driver_remove(struct device *dev) return 0; } -void __gameport_register_driver(struct gameport_driver *drv, struct module *owner) +static void gameport_attach_driver(struct gameport_driver *drv) { + int error; + + error = driver_attach(&drv->driver); + if (error) + pr_err("driver_attach() failed for %s, error: %d\n", + drv->driver.name, error); +} + +int __gameport_register_driver(struct gameport_driver *drv, struct module *owner, + const char *mod_name) +{ + int error; + drv->driver.bus = &gameport_bus; - drv->driver.probe = gameport_driver_probe; - drv->driver.remove = gameport_driver_remove; - gameport_queue_event(drv, owner, GAMEPORT_REGISTER_DRIVER); + drv->driver.owner = owner; + drv->driver.mod_name = mod_name; + + /* + * Temporarily disable automatic binding because probing + * takes long time and we are better off doing it in kgameportd + */ + drv->ignore = true; + + error = driver_register(&drv->driver); + if (error) { + pr_err("driver_register() failed for %s, error: %d\n", + drv->driver.name, error); + return error; + } + + /* + * Reset ignore flag and let kgameportd bind the driver to free ports + */ + drv->ignore = false; + error = gameport_queue_event(drv, NULL, GAMEPORT_ATTACH_DRIVER); + if (error) { + driver_unregister(&drv->driver); + return error; + } + + return 0; } +EXPORT_SYMBOL(__gameport_register_driver); void gameport_unregister_driver(struct gameport_driver *drv) { struct gameport *gameport; - down(&gameport_sem); - drv->ignore = 1; /* so gameport_find_driver ignores it */ + mutex_lock(&gameport_mutex); + + drv->ignore = true; /* so gameport_find_driver ignores it */ + gameport_remove_pending_events(drv); start_over: list_for_each_entry(gameport, &gameport_list, node) { @@ -722,8 +739,10 @@ start_over: } driver_unregister(&drv->driver); - up(&gameport_sem); + + mutex_unlock(&gameport_mutex); } +EXPORT_SYMBOL(gameport_unregister_driver); static int gameport_bus_match(struct device *dev, struct device_driver *drv) { @@ -732,16 +751,24 @@ static int gameport_bus_match(struct device *dev, struct device_driver *drv) return !gameport_drv->ignore; } +static struct bus_type gameport_bus = { + .name = "gameport", + .dev_groups = gameport_device_groups, + .drv_groups = gameport_driver_groups, + .match = gameport_bus_match, + .probe = gameport_driver_probe, + .remove = gameport_driver_remove, +}; + static void gameport_set_drv(struct gameport *gameport, struct gameport_driver *drv) { - down(&gameport->drv_sem); + mutex_lock(&gameport->drv_mutex); gameport->drv = drv; - up(&gameport->drv_sem); + mutex_unlock(&gameport->drv_mutex); } int gameport_open(struct gameport *gameport, struct gameport_driver *drv, int mode) { - if (gameport->open) { if (gameport->open(gameport, mode)) { return -1; @@ -754,6 +781,7 @@ int gameport_open(struct gameport *gameport, struct gameport_driver *drv, int mo gameport_set_drv(gameport, drv); return 0; } +EXPORT_SYMBOL(gameport_open); void gameport_close(struct gameport *gameport) { @@ -764,19 +792,18 @@ void gameport_close(struct gameport *gameport) if (gameport->close) gameport->close(gameport); } +EXPORT_SYMBOL(gameport_close); static int __init gameport_init(void) { - gameport_task = kthread_run(gameport_thread, NULL, "kgameportd"); - if (IS_ERR(gameport_task)) { - printk(KERN_ERR "gameport: Failed to start kgameportd\n"); - return PTR_ERR(gameport_task); + int error; + + error = bus_register(&gameport_bus); + if (error) { + pr_err("failed to register gameport bus, error: %d\n", error); + return error; } - gameport_bus.dev_attrs = gameport_device_attrs; - gameport_bus.drv_attrs = gameport_driver_attrs; - gameport_bus.match = gameport_bus_match; - bus_register(&gameport_bus); return 0; } @@ -784,8 +811,13 @@ static int __init gameport_init(void) static void __exit gameport_exit(void) { bus_unregister(&gameport_bus); - kthread_stop(gameport_task); + + /* + * There should not be any outstanding events but work may + * still be scheduled so simply cancel it. + */ + cancel_work_sync(&gameport_event_work); } -module_init(gameport_init); +subsys_initcall(gameport_init); module_exit(gameport_exit); |
