mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-18 06:50:08 +00:00
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: Dmitry Eremin-Solenikov <dbaryshkov@gmail.com> Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
This commit is contained in:
parent
a8b3c0f57b
commit
0982258264
@ -1584,10 +1584,10 @@ static ssize_t psmouse_attr_set_protocol(struct psmouse *psmouse, void *data, co
|
||||
if (!new_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
while (serio->child) {
|
||||
while (!list_empty(&serio->children)) {
|
||||
if (++retry > 3) {
|
||||
printk(KERN_WARNING
|
||||
"psmouse: failed to destroy child port, "
|
||||
"psmouse: failed to destroy children ports, "
|
||||
"protocol change aborted.\n");
|
||||
input_free_device(new_dev);
|
||||
return -EIO;
|
||||
|
@ -55,7 +55,7 @@ static struct bus_type serio_bus;
|
||||
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)
|
||||
@ -151,7 +151,7 @@ static void serio_find_driver(struct serio *serio)
|
||||
enum serio_event_type {
|
||||
SERIO_RESCAN_PORT,
|
||||
SERIO_RECONNECT_PORT,
|
||||
SERIO_RECONNECT_CHAIN,
|
||||
SERIO_RECONNECT_SUBTREE,
|
||||
SERIO_REGISTER_PORT,
|
||||
SERIO_ATTACH_DRIVER,
|
||||
};
|
||||
@ -291,8 +291,8 @@ static void serio_handle_event(void)
|
||||
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:
|
||||
@ -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
|
||||
* 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)
|
||||
{
|
||||
@ -448,7 +446,7 @@ static ssize_t serio_rebind_driver(struct device *dev, struct device_attribute *
|
||||
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);
|
||||
@ -515,6 +513,8 @@ static void serio_init_port(struct serio *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);
|
||||
@ -537,12 +537,13 @@ static void serio_init_port(struct serio *serio)
|
||||
*/
|
||||
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);
|
||||
@ -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
|
||||
*/
|
||||
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);
|
||||
}
|
||||
@ -576,7 +576,7 @@ static void serio_destroy_port(struct serio *serio)
|
||||
|
||||
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;
|
||||
}
|
||||
@ -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 {
|
||||
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);
|
||||
}
|
||||
@ -660,7 +696,7 @@ EXPORT_SYMBOL(serio_rescan);
|
||||
|
||||
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);
|
||||
|
||||
@ -688,14 +724,16 @@ void serio_unregister_port(struct serio *serio)
|
||||
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);
|
||||
}
|
||||
|
@ -41,7 +41,9 @@ struct serio {
|
||||
int (*start)(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 */
|
||||
|
||||
struct serio_driver *drv; /* accessed from interrupt, must be protected by serio->lock and serio->sem */
|
||||
|
Loading…
Reference in New Issue
Block a user