Commit 09822582 authored by Dmitry Eremin-Solenikov's avatar Dmitry Eremin-Solenikov Committed by Dmitry Torokhov

Input: serio - support multiple child devices per single parent

Some (rare) serio devices need to have multiple serio children. One of
the examples is PS/2 multiplexer present on several TQC STKxxx boards,
which connect PS/2 keyboard and mouse to single tty port.
Signed-off-by: default avatarDmitry Eremin-Solenikov <dbaryshkov@gmail.com>
Signed-off-by: default avatarDmitry Torokhov <dtor@mail.ru>
parent a8b3c0f5
...@@ -1584,10 +1584,10 @@ static ssize_t psmouse_attr_set_protocol(struct psmouse *psmouse, void *data, co ...@@ -1584,10 +1584,10 @@ static ssize_t psmouse_attr_set_protocol(struct psmouse *psmouse, void *data, co
if (!new_dev) if (!new_dev)
return -ENOMEM; return -ENOMEM;
while (serio->child) { while (!list_empty(&serio->children)) {
if (++retry > 3) { if (++retry > 3) {
printk(KERN_WARNING printk(KERN_WARNING
"psmouse: failed to destroy child port, " "psmouse: failed to destroy children ports, "
"protocol change aborted.\n"); "protocol change aborted.\n");
input_free_device(new_dev); input_free_device(new_dev);
return -EIO; return -EIO;
......
...@@ -55,7 +55,7 @@ static struct bus_type serio_bus; ...@@ -55,7 +55,7 @@ static struct bus_type serio_bus;
static void serio_add_port(struct serio *serio); static void serio_add_port(struct serio *serio);
static int serio_reconnect_port(struct serio *serio); static int serio_reconnect_port(struct serio *serio);
static void serio_disconnect_port(struct serio *serio); static void serio_disconnect_port(struct serio *serio);
static void serio_reconnect_chain(struct serio *serio); static void serio_reconnect_subtree(struct serio *serio);
static void serio_attach_driver(struct serio_driver *drv); static void serio_attach_driver(struct serio_driver *drv);
static int serio_connect_driver(struct serio *serio, struct serio_driver *drv) static int serio_connect_driver(struct serio *serio, struct serio_driver *drv)
...@@ -151,7 +151,7 @@ static void serio_find_driver(struct serio *serio) ...@@ -151,7 +151,7 @@ static void serio_find_driver(struct serio *serio)
enum serio_event_type { enum serio_event_type {
SERIO_RESCAN_PORT, SERIO_RESCAN_PORT,
SERIO_RECONNECT_PORT, SERIO_RECONNECT_PORT,
SERIO_RECONNECT_CHAIN, SERIO_RECONNECT_SUBTREE,
SERIO_REGISTER_PORT, SERIO_REGISTER_PORT,
SERIO_ATTACH_DRIVER, SERIO_ATTACH_DRIVER,
}; };
...@@ -291,8 +291,8 @@ static void serio_handle_event(void) ...@@ -291,8 +291,8 @@ static void serio_handle_event(void)
serio_find_driver(event->object); serio_find_driver(event->object);
break; break;
case SERIO_RECONNECT_CHAIN: case SERIO_RECONNECT_SUBTREE:
serio_reconnect_chain(event->object); serio_reconnect_subtree(event->object);
break; break;
case SERIO_ATTACH_DRIVER: case SERIO_ATTACH_DRIVER:
...@@ -329,12 +329,10 @@ static void serio_remove_pending_events(void *object) ...@@ -329,12 +329,10 @@ static void serio_remove_pending_events(void *object)
} }
/* /*
* Destroy child serio port (if any) that has not been fully registered yet. * Locate child serio port (if any) that has not been fully registered yet.
* *
* Note that we rely on the fact that port can have only one child and therefore * Children are registered by driver's connect() handler so there can't be a
* only one child registration request can be pending. Additionally, children * grandchild pending registration together with a child.
* are registered by driver's connect() handler so there can't be a grandchild
* pending registration together with a child.
*/ */
static struct serio *serio_get_pending_child(struct serio *parent) static struct serio *serio_get_pending_child(struct serio *parent)
{ {
...@@ -448,7 +446,7 @@ static ssize_t serio_rebind_driver(struct device *dev, struct device_attribute * ...@@ -448,7 +446,7 @@ static ssize_t serio_rebind_driver(struct device *dev, struct device_attribute *
if (!strncmp(buf, "none", count)) { if (!strncmp(buf, "none", count)) {
serio_disconnect_port(serio); serio_disconnect_port(serio);
} else if (!strncmp(buf, "reconnect", count)) { } else if (!strncmp(buf, "reconnect", count)) {
serio_reconnect_chain(serio); serio_reconnect_subtree(serio);
} else if (!strncmp(buf, "rescan", count)) { } else if (!strncmp(buf, "rescan", count)) {
serio_disconnect_port(serio); serio_disconnect_port(serio);
serio_find_driver(serio); serio_find_driver(serio);
...@@ -515,6 +513,8 @@ static void serio_init_port(struct serio *serio) ...@@ -515,6 +513,8 @@ static void serio_init_port(struct serio *serio)
__module_get(THIS_MODULE); __module_get(THIS_MODULE);
INIT_LIST_HEAD(&serio->node); INIT_LIST_HEAD(&serio->node);
INIT_LIST_HEAD(&serio->child_node);
INIT_LIST_HEAD(&serio->children);
spin_lock_init(&serio->lock); spin_lock_init(&serio->lock);
mutex_init(&serio->drv_mutex); mutex_init(&serio->drv_mutex);
device_initialize(&serio->dev); device_initialize(&serio->dev);
...@@ -537,12 +537,13 @@ static void serio_init_port(struct serio *serio) ...@@ -537,12 +537,13 @@ static void serio_init_port(struct serio *serio)
*/ */
static void serio_add_port(struct serio *serio) static void serio_add_port(struct serio *serio)
{ {
struct serio *parent = serio->parent;
int error; int error;
if (serio->parent) { if (parent) {
serio_pause_rx(serio->parent); serio_pause_rx(parent);
serio->parent->child = serio; list_add_tail(&serio->child_node, &parent->children);
serio_continue_rx(serio->parent); serio_continue_rx(parent);
} }
list_add_tail(&serio->node, &serio_list); list_add_tail(&serio->node, &serio_list);
...@@ -558,15 +559,14 @@ static void serio_add_port(struct serio *serio) ...@@ -558,15 +559,14 @@ static void serio_add_port(struct serio *serio)
} }
/* /*
* serio_destroy_port() completes deregistration process and removes * serio_destroy_port() completes unregistration process and removes
* port from the system * port from the system
*/ */
static void serio_destroy_port(struct serio *serio) static void serio_destroy_port(struct serio *serio)
{ {
struct serio *child; struct serio *child;
child = serio_get_pending_child(serio); while ((child = serio_get_pending_child(serio)) != NULL) {
if (child) {
serio_remove_pending_events(child); serio_remove_pending_events(child);
put_device(&child->dev); put_device(&child->dev);
} }
...@@ -576,7 +576,7 @@ static void serio_destroy_port(struct serio *serio) ...@@ -576,7 +576,7 @@ static void serio_destroy_port(struct serio *serio)
if (serio->parent) { if (serio->parent) {
serio_pause_rx(serio->parent); serio_pause_rx(serio->parent);
serio->parent->child = NULL; list_del_init(&serio->child_node);
serio_continue_rx(serio->parent); serio_continue_rx(serio->parent);
serio->parent = NULL; serio->parent = NULL;
} }
...@@ -608,46 +608,82 @@ static int serio_reconnect_port(struct serio *serio) ...@@ -608,46 +608,82 @@ static int serio_reconnect_port(struct serio *serio)
} }
/* /*
* Reconnect serio port and all its children (re-initialize attached devices) * Reconnect serio port and all its children (re-initialize attached
* devices).
*/ */
static void serio_reconnect_chain(struct serio *serio) static void serio_reconnect_subtree(struct serio *root)
{ {
struct serio *s = root;
int error;
do { do {
if (serio_reconnect_port(serio)) { error = serio_reconnect_port(s);
/* Ok, old children are now gone, we are done */ if (!error) {
/*
* Reconnect was successful, move on to do the
* first child.
*/
if (!list_empty(&s->children)) {
s = list_first_entry(&s->children,
struct serio, child_node);
continue;
}
}
/*
* Either it was a leaf node or reconnect failed and it
* became a leaf node. Continue reconnecting starting with
* the next sibling of the parent node.
*/
while (s != root) {
struct serio *parent = s->parent;
if (!list_is_last(&s->child_node, &parent->children)) {
s = list_entry(s->child_node.next,
struct serio, child_node);
break; break;
} }
serio = serio->child;
} while (serio); s = parent;
}
} while (s != root);
} }
/* /*
* serio_disconnect_port() unbinds a port from its driver. As a side effect * serio_disconnect_port() unbinds a port from its driver. As a side effect
* all child ports are unbound and destroyed. * all children ports are unbound and destroyed.
*/ */
static void serio_disconnect_port(struct serio *serio) static void serio_disconnect_port(struct serio *serio)
{ {
struct serio *s, *parent; struct serio *s = serio;
if (serio->child) {
/* /*
* Children ports should be disconnected and destroyed * Children ports should be disconnected and destroyed
* first, staring with the leaf one, since we don't want * first; we travel the tree in depth-first order.
* to do recursion
*/ */
for (s = serio; s->child; s = s->child) while (!list_empty(&serio->children)) {
/* empty */;
do { /* Locate a leaf */
parent = s->parent; while (!list_empty(&s->children))
s = list_first_entry(&s->children,
struct serio, child_node);
/*
* Prune this leaf node unless it is the one we
* started with.
*/
if (s != serio) {
struct serio *parent = s->parent;
device_release_driver(&s->dev); device_release_driver(&s->dev);
serio_destroy_port(s); serio_destroy_port(s);
} while ((s = parent) != serio);
s = parent;
}
} }
/* /*
* Ok, no children left, now disconnect this port * OK, no children left, now disconnect this port.
*/ */
device_release_driver(&serio->dev); device_release_driver(&serio->dev);
} }
...@@ -660,7 +696,7 @@ EXPORT_SYMBOL(serio_rescan); ...@@ -660,7 +696,7 @@ EXPORT_SYMBOL(serio_rescan);
void serio_reconnect(struct serio *serio) void serio_reconnect(struct serio *serio)
{ {
serio_queue_event(serio, NULL, SERIO_RECONNECT_CHAIN); serio_queue_event(serio, NULL, SERIO_RECONNECT_SUBTREE);
} }
EXPORT_SYMBOL(serio_reconnect); EXPORT_SYMBOL(serio_reconnect);
...@@ -688,14 +724,16 @@ void serio_unregister_port(struct serio *serio) ...@@ -688,14 +724,16 @@ void serio_unregister_port(struct serio *serio)
EXPORT_SYMBOL(serio_unregister_port); EXPORT_SYMBOL(serio_unregister_port);
/* /*
* Safely unregisters child port if one is present. * Safely unregisters children ports if they are present.
*/ */
void serio_unregister_child_port(struct serio *serio) void serio_unregister_child_port(struct serio *serio)
{ {
struct serio *s, *next;
mutex_lock(&serio_mutex); mutex_lock(&serio_mutex);
if (serio->child) { list_for_each_entry_safe(s, next, &serio->children, child_node) {
serio_disconnect_port(serio->child); serio_disconnect_port(s);
serio_destroy_port(serio->child); serio_destroy_port(s);
} }
mutex_unlock(&serio_mutex); mutex_unlock(&serio_mutex);
} }
......
...@@ -41,7 +41,9 @@ struct serio { ...@@ -41,7 +41,9 @@ struct serio {
int (*start)(struct serio *); int (*start)(struct serio *);
void (*stop)(struct serio *); void (*stop)(struct serio *);
struct serio *parent, *child; struct serio *parent;
struct list_head child_node; /* Entry in parent->children list */
struct list_head children;
unsigned int depth; /* level of nesting in serio hierarchy */ unsigned int depth; /* level of nesting in serio hierarchy */
struct serio_driver *drv; /* accessed from interrupt, must be protected by serio->lock and serio->sem */ struct serio_driver *drv; /* accessed from interrupt, must be protected by serio->lock and serio->sem */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment