Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+2 −2
Original line number Original line Diff line number Diff line
@@ -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;
+81 −43
Original line number Original line Diff line number Diff line
@@ -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)
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)
			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)
}
}


/*
/*
 * 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 *
	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)
	__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)
 */
 */
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)
}
}


/*
/*
 * 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)


	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)
}
}


/*
/*
 * 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);


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)
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);
}
}
+3 −1
Original line number Original line Diff line number Diff line
@@ -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 */