static void serio_add_port(struct serio *serio);
static int serio_reconnect_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 int serio_connect_driver(struct serio *serio, struct serio_driver *drv)
enum serio_event_type {
SERIO_RESCAN_PORT,
SERIO_RECONNECT_PORT,
- SERIO_RECONNECT_CHAIN,
+ SERIO_RECONNECT_SUBTREE,
SERIO_REGISTER_PORT,
SERIO_ATTACH_DRIVER,
};
serio_find_driver(event->object);
break;
- case SERIO_RECONNECT_CHAIN:
- serio_reconnect_chain(event->object);
+ case SERIO_RECONNECT_SUBTREE:
+ serio_reconnect_subtree(event->object);
break;
case SERIO_ATTACH_DRIVER:
}
/*
- * 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
- * only one child registration request can be pending. Additionally, children
- * are registered by driver's connect() handler so there can't be a grandchild
- * pending registration together with a child.
+ * Children 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)
{
if (!strncmp(buf, "none", count)) {
serio_disconnect_port(serio);
} else if (!strncmp(buf, "reconnect", count)) {
- serio_reconnect_chain(serio);
+ serio_reconnect_subtree(serio);
} else if (!strncmp(buf, "rescan", count)) {
serio_disconnect_port(serio);
serio_find_driver(serio);
__module_get(THIS_MODULE);
INIT_LIST_HEAD(&serio->node);
+ INIT_LIST_HEAD(&serio->child_node);
+ INIT_LIST_HEAD(&serio->children);
spin_lock_init(&serio->lock);
mutex_init(&serio->drv_mutex);
device_initialize(&serio->dev);
*/
static void serio_add_port(struct serio *serio)
{
+ struct serio *parent = serio->parent;
int error;
- if (serio->parent) {
- serio_pause_rx(serio->parent);
- serio->parent->child = serio;
- serio_continue_rx(serio->parent);
+ if (parent) {
+ serio_pause_rx(parent);
+ list_add_tail(&serio->child_node, &parent->children);
+ serio_continue_rx(parent);
}
list_add_tail(&serio->node, &serio_list);
}
/*
- * serio_destroy_port() completes deregistration process and removes
+ * serio_destroy_port() completes unregistration process and removes
* port from the system
*/
static void serio_destroy_port(struct serio *serio)
{
struct serio *child;
- child = serio_get_pending_child(serio);
- if (child) {
+ while ((child = serio_get_pending_child(serio)) != NULL) {
serio_remove_pending_events(child);
put_device(&child->dev);
}
if (serio->parent) {
serio_pause_rx(serio->parent);
- serio->parent->child = NULL;
+ list_del_init(&serio->child_node);
serio_continue_rx(serio->parent);
serio->parent = NULL;
}
}
/*
- * 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 {
- if (serio_reconnect_port(serio)) {
- /* Ok, old children are now gone, we are done */
- break;
+ error = serio_reconnect_port(s);
+ 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;
+ }
}
- serio = serio->child;
- } while (serio);
+
+ /*
+ * 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;
+ }
+
+ s = parent;
+ }
+ } while (s != root);
}
/*
* 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)
{
- struct serio *s, *parent;
+ struct serio *s = serio;
+
+ /*
+ * Children ports should be disconnected and destroyed
+ * first; we travel the tree in depth-first order.
+ */
+ while (!list_empty(&serio->children)) {
+
+ /* Locate a leaf */
+ while (!list_empty(&s->children))
+ s = list_first_entry(&s->children,
+ struct serio, child_node);
- if (serio->child) {
/*
- * Children ports should be disconnected and destroyed
- * first, staring with the leaf one, since we don't want
- * to do recursion
+ * Prune this leaf node unless it is the one we
+ * started with.
*/
- for (s = serio; s->child; s = s->child)
- /* empty */;
-
- do {
- parent = s->parent;
+ if (s != serio) {
+ struct serio *parent = s->parent;
device_release_driver(&s->dev);
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);
}
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_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)
{
+ struct serio *s, *next;
+
mutex_lock(&serio_mutex);
- if (serio->child) {
- serio_disconnect_port(serio->child);
- serio_destroy_port(serio->child);
+ list_for_each_entry_safe(s, next, &serio->children, child_node) {
+ serio_disconnect_port(s);
+ serio_destroy_port(s);
}
mutex_unlock(&serio_mutex);
}