#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/kthread.h>
+#include <linux/mutex.h>
+#include <linux/freezer.h>
MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
MODULE_DESCRIPTION("Serio abstraction core");
EXPORT_SYMBOL(__serio_register_port);
EXPORT_SYMBOL(serio_unregister_port);
EXPORT_SYMBOL(serio_unregister_child_port);
-EXPORT_SYMBOL(__serio_unregister_port_delayed);
-EXPORT_SYMBOL(__serio_register_driver);
+EXPORT_SYMBOL(serio_register_driver);
EXPORT_SYMBOL(serio_unregister_driver);
EXPORT_SYMBOL(serio_open);
EXPORT_SYMBOL(serio_close);
EXPORT_SYMBOL(serio_reconnect);
/*
- * serio_sem protects entire serio subsystem and is taken every time
+ * serio_mutex protects entire serio subsystem and is taken every time
* serio port or driver registrered or unregistered.
*/
-static DECLARE_MUTEX(serio_sem);
+static DEFINE_MUTEX(serio_mutex);
static LIST_HEAD(serio_list);
static struct bus_type serio_bus;
static void serio_add_port(struct serio *serio);
-static void serio_destroy_port(struct serio *serio);
static void serio_reconnect_port(struct serio *serio);
static void serio_disconnect_port(struct serio *serio);
+static void serio_attach_driver(struct serio_driver *drv);
static int serio_connect_driver(struct serio *serio, struct serio_driver *drv)
{
int retval;
- down(&serio->drv_sem);
+ mutex_lock(&serio->drv_mutex);
retval = drv->connect(serio, drv);
- up(&serio->drv_sem);
+ mutex_unlock(&serio->drv_mutex);
return retval;
}
{
int retval = -1;
- down(&serio->drv_sem);
+ mutex_lock(&serio->drv_mutex);
if (serio->drv && serio->drv->reconnect)
retval = serio->drv->reconnect(serio);
- up(&serio->drv_sem);
+ mutex_unlock(&serio->drv_mutex);
return retval;
}
static void serio_disconnect_driver(struct serio *serio)
{
- down(&serio->drv_sem);
+ mutex_lock(&serio->drv_mutex);
if (serio->drv)
serio->drv->disconnect(serio);
- up(&serio->drv_sem);
+ mutex_unlock(&serio->drv_mutex);
}
static int serio_match_port(const struct serio_device_id *ids, struct serio *serio)
static void serio_bind_driver(struct serio *serio, struct serio_driver *drv)
{
+ int error;
+
down_write(&serio_bus.subsys.rwsem);
if (serio_match_port(drv->id_table, serio)) {
serio->dev.driver = NULL;
goto out;
}
- device_bind_driver(&serio->dev);
+ error = device_bind_driver(&serio->dev);
+ if (error) {
+ printk(KERN_WARNING
+ "serio: device_bind_driver() failed "
+ "for %s (%s) and %s, error: %d\n",
+ serio->phys, serio->name,
+ drv->description, error);
+ serio_disconnect_driver(serio);
+ serio->dev.driver = NULL;
+ goto out;
+ }
}
-out:
+ out:
up_write(&serio_bus.subsys.rwsem);
}
static void serio_find_driver(struct serio *serio)
{
+ int error;
+
down_write(&serio_bus.subsys.rwsem);
- device_attach(&serio->dev);
+ error = device_attach(&serio->dev);
+ if (error < 0)
+ printk(KERN_WARNING
+ "serio: device_attach() failed for %s (%s), error: %d\n",
+ serio->phys, serio->name, error);
up_write(&serio_bus.subsys.rwsem);
}
*/
enum serio_event_type {
- SERIO_RESCAN,
- SERIO_RECONNECT,
+ SERIO_RESCAN_PORT,
+ SERIO_RECONNECT_PORT,
SERIO_REGISTER_PORT,
- SERIO_UNREGISTER_PORT,
- SERIO_REGISTER_DRIVER,
+ SERIO_ATTACH_DRIVER,
};
struct serio_event {
static DECLARE_WAIT_QUEUE_HEAD(serio_wait);
static struct task_struct *serio_task;
-static void serio_queue_event(void *object, struct module *owner,
- enum serio_event_type event_type)
+static int serio_queue_event(void *object, struct module *owner,
+ enum serio_event_type event_type)
{
unsigned long flags;
struct serio_event *event;
+ int retval = 0;
spin_lock_irqsave(&serio_event_lock, flags);
}
}
- if ((event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC))) {
- if (!try_module_get(owner)) {
- printk(KERN_WARNING "serio: Can't get module reference, dropping event %d\n", event_type);
- goto out;
- }
-
- event->type = event_type;
- event->object = object;
- event->owner = owner;
+ event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC);
+ if (!event) {
+ printk(KERN_ERR
+ "serio: Not enough memory to queue event %d\n",
+ event_type);
+ retval = -ENOMEM;
+ goto out;
+ }
- list_add_tail(&event->node, &serio_event_list);
- wake_up(&serio_wait);
- } else {
- printk(KERN_ERR "serio: Not enough memory to queue event %d\n", event_type);
+ if (!try_module_get(owner)) {
+ printk(KERN_WARNING
+ "serio: Can't get module reference, dropping event %d\n",
+ event_type);
+ kfree(event);
+ retval = -EINVAL;
+ goto out;
}
+
+ event->type = event_type;
+ event->object = object;
+ event->owner = owner;
+
+ list_add_tail(&event->node, &serio_event_list);
+ wake_up(&serio_wait);
+
out:
spin_unlock_irqrestore(&serio_event_lock, flags);
+ return retval;
}
static void serio_free_event(struct serio_event *event)
static void serio_handle_event(void)
{
struct serio_event *event;
- struct serio_driver *serio_drv;
- down(&serio_sem);
+ mutex_lock(&serio_mutex);
/*
* Note that we handle only one event here to give swsusp
serio_add_port(event->object);
break;
- case SERIO_UNREGISTER_PORT:
- serio_disconnect_port(event->object);
- serio_destroy_port(event->object);
- break;
-
- case SERIO_RECONNECT:
+ case SERIO_RECONNECT_PORT:
serio_reconnect_port(event->object);
break;
- case SERIO_RESCAN:
+ case SERIO_RESCAN_PORT:
serio_disconnect_port(event->object);
serio_find_driver(event->object);
break;
- case SERIO_REGISTER_DRIVER:
- serio_drv = event->object;
- driver_register(&serio_drv->driver);
+ case SERIO_ATTACH_DRIVER:
+ serio_attach_driver(event->object);
break;
default:
serio_free_event(event);
}
- up(&serio_sem);
+ mutex_unlock(&serio_mutex);
}
/*
struct device_driver *drv;
int retval;
- retval = down_interruptible(&serio_sem);
+ retval = mutex_lock_interruptible(&serio_mutex);
if (retval)
return retval;
retval = -EINVAL;
}
- up(&serio_sem);
+ mutex_unlock(&serio_mutex);
return retval;
}
__module_get(THIS_MODULE);
+ INIT_LIST_HEAD(&serio->node);
spin_lock_init(&serio->lock);
- init_MUTEX(&serio->drv_sem);
+ mutex_init(&serio->drv_mutex);
device_initialize(&serio->dev);
snprintf(serio->dev.bus_id, sizeof(serio->dev.bus_id),
"serio%ld", (long)atomic_inc_return(&serio_no) - 1);
serio->dev.bus = &serio_bus;
serio->dev.release = serio_release_port;
- if (serio->parent)
+ if (serio->parent) {
serio->dev.parent = &serio->parent->dev;
+ serio->depth = serio->parent->depth + 1;
+ } else
+ serio->depth = 0;
+ lockdep_set_subclass(&serio->lock, serio->depth);
}
/*
*/
static void serio_add_port(struct serio *serio)
{
+ int error;
+
if (serio->parent) {
serio_pause_rx(serio->parent);
serio->parent->child = serio;
list_add_tail(&serio->node, &serio_list);
if (serio->start)
serio->start(serio);
- device_add(&serio->dev);
- sysfs_create_group(&serio->dev.kobj, &serio_id_attr_group);
- serio->registered = 1;
+ error = device_add(&serio->dev);
+ if (error)
+ printk(KERN_ERR
+ "serio: device_add() failed for %s (%s), error: %d\n",
+ serio->phys, serio->name, error);
+ else {
+ serio->registered = 1;
+ error = sysfs_create_group(&serio->dev.kobj, &serio_id_attr_group);
+ if (error)
+ printk(KERN_ERR
+ "serio: sysfs_create_group() failed for %s (%s), error: %d\n",
+ serio->phys, serio->name, error);
+ }
}
/*
if (serio->registered) {
sysfs_remove_group(&serio->dev.kobj, &serio_id_attr_group);
device_del(&serio->dev);
- list_del_init(&serio->node);
serio->registered = 0;
}
+ list_del_init(&serio->node);
serio_remove_pending_events(serio);
put_device(&serio->dev);
}
void serio_rescan(struct serio *serio)
{
- serio_queue_event(serio, NULL, SERIO_RESCAN);
+ serio_queue_event(serio, NULL, SERIO_RESCAN_PORT);
}
void serio_reconnect(struct serio *serio)
{
- serio_queue_event(serio, NULL, SERIO_RECONNECT);
+ serio_queue_event(serio, NULL, SERIO_RECONNECT_PORT);
}
/*
*/
void serio_unregister_port(struct serio *serio)
{
- down(&serio_sem);
+ mutex_lock(&serio_mutex);
serio_disconnect_port(serio);
serio_destroy_port(serio);
- up(&serio_sem);
+ mutex_unlock(&serio_mutex);
}
/*
*/
void serio_unregister_child_port(struct serio *serio)
{
- down(&serio_sem);
+ mutex_lock(&serio_mutex);
if (serio->child) {
serio_disconnect_port(serio->child);
serio_destroy_port(serio->child);
}
- up(&serio_sem);
-}
-
-/*
- * Submits register request to kseriod for subsequent execution.
- * Can be used when it is not obvious whether the serio_sem is
- * taken or not and when delayed execution is feasible.
- */
-void __serio_unregister_port_delayed(struct serio *serio, struct module *owner)
-{
- serio_queue_event(serio, owner, SERIO_UNREGISTER_PORT);
+ mutex_unlock(&serio_mutex);
}
return 0;
}
-static struct bus_type serio_bus = {
- .name = "serio",
- .probe = serio_driver_probe,
- .remove = serio_driver_remove,
-};
+static void serio_attach_driver(struct serio_driver *drv)
+{
+ int error;
+
+ error = driver_attach(&drv->driver);
+ if (error)
+ printk(KERN_WARNING
+ "serio: driver_attach() failed for %s with error %d\n",
+ drv->driver.name, error);
+}
-void __serio_register_driver(struct serio_driver *drv, struct module *owner)
+int serio_register_driver(struct serio_driver *drv)
{
+ int manual_bind = drv->manual_bind;
+ int error;
+
drv->driver.bus = &serio_bus;
- serio_queue_event(drv, owner, SERIO_REGISTER_DRIVER);
+ /*
+ * Temporarily disable automatic binding because probing
+ * takes long time and we are better off doing it in kseriod
+ */
+ drv->manual_bind = 1;
+
+ error = driver_register(&drv->driver);
+ if (error) {
+ printk(KERN_ERR
+ "serio: driver_register() failed for %s, error: %d\n",
+ drv->driver.name, error);
+ return error;
+ }
+
+ /*
+ * Restore original bind mode and let kseriod bind the
+ * driver to free ports
+ */
+ if (!manual_bind) {
+ drv->manual_bind = 0;
+ error = serio_queue_event(drv, NULL, SERIO_ATTACH_DRIVER);
+ if (error) {
+ driver_unregister(&drv->driver);
+ return error;
+ }
+ }
+
+ return 0;
}
void serio_unregister_driver(struct serio_driver *drv)
{
struct serio *serio;
- down(&serio_sem);
+ mutex_lock(&serio_mutex);
drv->manual_bind = 1; /* so serio_find_driver ignores it */
start_over:
}
driver_unregister(&drv->driver);
- up(&serio_sem);
+ mutex_unlock(&serio_mutex);
}
static void serio_set_drv(struct serio *serio, struct serio_driver *drv)
return 0;
}
-/* called from serio_driver->connect/disconnect methods under serio_sem */
+/* called from serio_driver->connect/disconnect methods under serio_mutex */
int serio_open(struct serio *serio, struct serio_driver *drv)
{
serio_set_drv(serio, drv);
return 0;
}
-/* called from serio_driver->connect/disconnect methods under serio_sem */
+/* called from serio_driver->connect/disconnect methods under serio_mutex */
void serio_close(struct serio *serio)
{
if (serio->close)
}
irqreturn_t serio_interrupt(struct serio *serio,
- unsigned char data, unsigned int dfl, struct pt_regs *regs)
+ unsigned char data, unsigned int dfl)
{
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);
+ ret = serio->drv->interrupt(serio, data, dfl);
} else if (!dfl && serio->registered) {
serio_rescan(serio);
ret = IRQ_HANDLED;
return ret;
}
+static struct bus_type serio_bus = {
+ .name = "serio",
+ .dev_attrs = serio_device_attrs,
+ .drv_attrs = serio_driver_attrs,
+ .match = serio_bus_match,
+ .uevent = serio_uevent,
+ .probe = serio_driver_probe,
+ .remove = serio_driver_remove,
+ .resume = serio_resume,
+};
+
static int __init serio_init(void)
{
+ int error;
+
+ error = bus_register(&serio_bus);
+ if (error) {
+ printk(KERN_ERR "serio: failed to register serio bus, error: %d\n", error);
+ return error;
+ }
+
serio_task = kthread_run(serio_thread, NULL, "kseriod");
if (IS_ERR(serio_task)) {
- printk(KERN_ERR "serio: Failed to start kseriod\n");
- return PTR_ERR(serio_task);
+ bus_unregister(&serio_bus);
+ error = PTR_ERR(serio_task);
+ printk(KERN_ERR "serio: Failed to start kseriod, error: %d\n", error);
+ return error;
}
- serio_bus.dev_attrs = serio_device_attrs;
- serio_bus.drv_attrs = serio_driver_attrs;
- serio_bus.match = serio_bus_match;
- serio_bus.uevent = serio_uevent;
- serio_bus.resume = serio_resume;
- bus_register(&serio_bus);
-
return 0;
}
kthread_stop(serio_task);
}
-module_init(serio_init);
+subsys_initcall(serio_init);
module_exit(serio_exit);