From: Dmitry Torokhov =================================================================== ChangeSet@1.1796, 2004-06-18 00:09:46-05:00, dtor_core@ameritech.net Input: allow serio drivers to create children ports and register these ports for them in serio core to avoid having recursion in connect methods. Signed-off-by: Dmitry Torokhov include/linux/serio.h | 4 =================================================================== Signed-off-by: Andrew Morton --- 25-akpm/drivers/input/mouse/psmouse-base.c | 75 ++++++---- 25-akpm/drivers/input/mouse/psmouse.h | 16 -- 25-akpm/drivers/input/mouse/synaptics.c | 27 +-- 25-akpm/drivers/input/serio/serio.c | 215 ++++++++++++++++++++++------- 25-akpm/include/linux/serio.h | 4 5 files changed, 231 insertions(+), 106 deletions(-) diff -puN drivers/input/mouse/psmouse-base.c~input-serio-no-recursion drivers/input/mouse/psmouse-base.c --- 25/drivers/input/mouse/psmouse-base.c~input-serio-no-recursion 2004-06-19 13:52:28.789169576 -0700 +++ 25-akpm/drivers/input/mouse/psmouse-base.c 2004-06-19 13:52:28.800167904 -0700 @@ -670,16 +670,15 @@ static void psmouse_cleanup(struct serio static void psmouse_disconnect(struct serio *serio) { - struct psmouse *psmouse = serio->private; + struct psmouse *psmouse, *parent; + psmouse = serio->private; psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); - if (psmouse->ptport) { - if (psmouse->ptport->deactivate) - psmouse->ptport->deactivate(psmouse); - __serio_unregister_port(psmouse->ptport->serio); /* we have serio_sem */ - kfree(psmouse->ptport); - psmouse->ptport = NULL; + if (serio->parent && (serio->type & SERIO_TYPE) == SERIO_PS_PSTHRU) { + parent = serio->parent->private; + if (parent->pt_deactivate) + parent->pt_deactivate(parent); } if (psmouse->disconnect) @@ -698,14 +697,17 @@ static void psmouse_disconnect(struct se */ static void psmouse_connect(struct serio *serio, struct serio_driver *drv) { - struct psmouse *psmouse; + struct psmouse *psmouse, *parent = NULL; if ((serio->type & SERIO_TYPE) != SERIO_8042 && (serio->type & SERIO_TYPE) != SERIO_PS_PSTHRU) return; + if (serio->parent && (serio->type & SERIO_TYPE) == SERIO_PS_PSTHRU) + parent = serio->parent->private; + if (!(psmouse = kmalloc(sizeof(struct psmouse), GFP_KERNEL))) - return; + goto out; memset(psmouse, 0, sizeof(struct psmouse)); @@ -721,14 +723,14 @@ static void psmouse_connect(struct serio if (serio_open(serio, drv)) { kfree(psmouse); serio->private = NULL; - return; + goto out; } if (psmouse_probe(psmouse) < 0) { serio_close(serio); kfree(psmouse); serio->private = NULL; - return; + goto out; } psmouse->type = psmouse_extensions(psmouse, psmouse_max_proto, 1); @@ -757,20 +759,36 @@ static void psmouse_connect(struct serio psmouse_initialize(psmouse); - if (psmouse->ptport) { - printk(KERN_INFO "serio: %s port at %s\n", psmouse->ptport->serio->name, psmouse->phys); - __serio_register_port(psmouse->ptport->serio); /* we have serio_sem */ - if (psmouse->ptport->activate) - psmouse->ptport->activate(psmouse); - } + if (parent && parent->pt_activate) + parent->pt_activate(parent); - psmouse_activate(psmouse); + /* + * OK, the device is ready, we just need to activate it (turn the + * stream mode on). But if mouse has a pass-through port we don't + * want to do it yet to not disturb child detection. + * The child will activate this port when it's ready. + */ + + if (serio->child) { + /* + * Nothing to be done here, serio core will detect that + * the driver set serio->child and will register it for us. + */ + printk(KERN_INFO "serio: %s port at %s\n", serio->child->name, psmouse->phys); + } else + psmouse_activate(psmouse); + +out: + /* If this is a pass-through port the parent awaits to be activated */ + if (parent) + psmouse_activate(parent); } static int psmouse_reconnect(struct serio *serio) { struct psmouse *psmouse = serio->private; + struct psmouse *parent = NULL; struct serio_driver *drv = serio->drv; if (!drv || !psmouse) { @@ -792,16 +810,19 @@ static int psmouse_reconnect(struct seri */ psmouse_initialize(psmouse); - if (psmouse->ptport) { - if (psmouse_reconnect(psmouse->ptport->serio)) { - __serio_unregister_port(psmouse->ptport->serio); - __serio_register_port(psmouse->ptport->serio); - if (psmouse->ptport->activate) - psmouse->ptport->activate(psmouse); - } - } + if (serio->parent && (serio->type & SERIO_TYPE) == SERIO_PS_PSTHRU) + parent = serio->parent->private; + + if (parent && parent->pt_activate) + parent->pt_activate(parent); + + if (!serio->child) + psmouse_activate(psmouse); + + /* If this is a pass-through port the parent waits to be activated */ + if (parent) + psmouse_activate(parent); - psmouse_activate(psmouse); return 0; } diff -puN drivers/input/mouse/psmouse.h~input-serio-no-recursion drivers/input/mouse/psmouse.h --- 25/drivers/input/mouse/psmouse.h~input-serio-no-recursion 2004-06-19 13:52:28.790169424 -0700 +++ 25-akpm/drivers/input/mouse/psmouse.h 2004-06-19 13:52:28.800167904 -0700 @@ -34,21 +34,10 @@ typedef enum { PSMOUSE_FULL_PACKET } psmouse_ret_t; -struct psmouse; - -struct psmouse_ptport { - struct serio *serio; - struct psmouse *parent; - - void (*activate)(struct psmouse *parent); - void (*deactivate)(struct psmouse *parent); -}; - struct psmouse { void *private; struct input_dev dev; struct serio *serio; - struct psmouse_ptport *ptport; char *vendor; char *name; unsigned char cmdbuf[8]; @@ -66,9 +55,12 @@ struct psmouse { char phys[32]; unsigned long flags; - psmouse_ret_t (*protocol_handler)(struct psmouse *psmouse, struct pt_regs *regs); + psmouse_ret_t (*protocol_handler)(struct psmouse *psmouse, struct pt_regs *regs); int (*reconnect)(struct psmouse *psmouse); void (*disconnect)(struct psmouse *psmouse); + + void (*pt_activate)(struct psmouse *psmouse); + void (*pt_deactivate)(struct psmouse *psmouse); }; #define PSMOUSE_PS2 1 diff -puN drivers/input/mouse/synaptics.c~input-serio-no-recursion drivers/input/mouse/synaptics.c --- 25/drivers/input/mouse/synaptics.c~input-serio-no-recursion 2004-06-19 13:52:28.792169120 -0700 +++ 25-akpm/drivers/input/mouse/synaptics.c 2004-06-19 13:52:28.801167752 -0700 @@ -212,14 +212,14 @@ static int synaptics_set_mode(struct psm /***************************************************************************** * Synaptics pass-through PS/2 port support ****************************************************************************/ -static int synaptics_pt_write(struct serio *port, unsigned char c) +static int synaptics_pt_write(struct serio *serio, unsigned char c) { - struct psmouse_ptport *ptport = port->port_data; + struct psmouse *parent = serio->parent->private; char rate_param = SYN_PS_CLIENT_CMD; /* indicates that we want pass-through port */ - if (psmouse_sliced_command(ptport->parent, c)) + if (psmouse_sliced_command(parent, c)) return -1; - if (psmouse_command(ptport->parent, &rate_param, PSMOUSE_CMD_SETRATE)) + if (psmouse_command(parent, &rate_param, PSMOUSE_CMD_SETRATE)) return -1; return 0; } @@ -248,7 +248,7 @@ static void synaptics_pass_pt_packet(str static void synaptics_pt_activate(struct psmouse *psmouse) { - struct psmouse *child = psmouse->ptport->serio->private; + struct psmouse *child = psmouse->serio->child->private; /* adjust the touchpad to child's choice of protocol */ if (child && child->type >= PSMOUSE_GENPS) { @@ -259,30 +259,25 @@ static void synaptics_pt_activate(struct static void synaptics_pt_create(struct psmouse *psmouse) { - struct psmouse_ptport *port; struct serio *serio; - port = kmalloc(sizeof(struct psmouse_ptport), GFP_KERNEL); serio = kmalloc(sizeof(struct serio), GFP_KERNEL); - if (!port || !serio) { + if (!serio) { printk(KERN_ERR "synaptics: not enough memory to allocate pass-through port\n"); return; } - memset(port, 0, sizeof(struct psmouse_ptport)); memset(serio, 0, sizeof(struct serio)); serio->type = SERIO_PS_PSTHRU; strlcpy(serio->name, "Synaptics pass-through", sizeof(serio->name)); strlcpy(serio->phys, "synaptics-pt/serio0", sizeof(serio->name)); serio->write = synaptics_pt_write; - serio->port_data = port; + serio->parent = psmouse->serio; - port->serio = serio; - port->parent = psmouse; - port->activate = synaptics_pt_activate; + psmouse->pt_activate = synaptics_pt_activate; - psmouse->ptport = port; + psmouse->serio->child = serio; } /***************************************************************************** @@ -477,8 +472,8 @@ static psmouse_ret_t synaptics_process_b if (unlikely(priv->pkt_type == SYN_NEWABS)) priv->pkt_type = synaptics_detect_pkt_type(psmouse); - if (psmouse->ptport && psmouse->ptport->serio->drv && synaptics_is_pt_packet(psmouse->packet)) - synaptics_pass_pt_packet(psmouse->ptport->serio, psmouse->packet); + if (psmouse->serio->child && psmouse->serio->child->drv && synaptics_is_pt_packet(psmouse->packet)) + synaptics_pass_pt_packet(psmouse->serio->child, psmouse->packet); else synaptics_process_packet(psmouse); diff -puN drivers/input/serio/serio.c~input-serio-no-recursion drivers/input/serio/serio.c --- 25/drivers/input/serio/serio.c~input-serio-no-recursion 2004-06-19 13:52:28.794168816 -0700 +++ 25-akpm/drivers/input/serio/serio.c 2004-06-19 13:52:28.803167448 -0700 @@ -44,10 +44,8 @@ MODULE_LICENSE("GPL"); EXPORT_SYMBOL(serio_interrupt); EXPORT_SYMBOL(serio_register_port); EXPORT_SYMBOL(serio_register_port_delayed); -EXPORT_SYMBOL(__serio_register_port); EXPORT_SYMBOL(serio_unregister_port); EXPORT_SYMBOL(serio_unregister_port_delayed); -EXPORT_SYMBOL(__serio_unregister_port); EXPORT_SYMBOL(serio_register_driver); EXPORT_SYMBOL(serio_unregister_driver); EXPORT_SYMBOL(serio_open); @@ -59,17 +57,28 @@ static DECLARE_MUTEX(serio_sem); /* prot static LIST_HEAD(serio_list); static LIST_HEAD(serio_driver_list); -/* serio_find_driver() must be called with serio_sem down. */ +static void serio_find_driver(struct serio *serio); +static void serio_create_port(struct serio *serio); +static void serio_destroy_port(struct serio *serio); +static void serio_connect_port(struct serio *serio, struct serio_driver *drv); +static void serio_reconnect_port(struct serio *serio); +static void serio_disconnect_port(struct serio *serio); + +static int serio_bind_driver(struct serio *serio, struct serio_driver *drv) +{ + drv->connect(serio, drv); + + return serio->drv != NULL; +} +/* serio_find_driver() must be called with serio_sem down. */ static void serio_find_driver(struct serio *serio) { struct serio_driver *drv; - list_for_each_entry(drv, &serio_driver_list, node) { - if (serio->drv) + list_for_each_entry(drv, &serio_driver_list, node) + if (serio_bind_driver(serio, drv)) break; - drv->connect(serio, drv); - } } /* @@ -145,23 +154,22 @@ static void serio_handle_events(void) switch (event->type) { case SERIO_REGISTER_PORT : - __serio_register_port(event->serio); + serio_create_port(event->serio); + serio_connect_port(event->serio, NULL); break; case SERIO_UNREGISTER_PORT : - __serio_unregister_port(event->serio); + serio_disconnect_port(event->serio); + serio_destroy_port(event->serio); break; case SERIO_RECONNECT : - if (event->serio->drv && event->serio->drv->reconnect) - if (event->serio->drv->reconnect(event->serio) == 0) - break; - /* reconnect failed - fall through to rescan */ + serio_reconnect_port(event->serio); + break; case SERIO_RESCAN : - if (event->serio->drv) - event->serio->drv->disconnect(event->serio); - serio_find_driver(event->serio); + serio_disconnect_port(event->serio); + serio_connect_port(event->serio, NULL); break; default: break; @@ -216,6 +224,118 @@ static int serio_thread(void *nothing) * Serio port operations */ +static void serio_create_port(struct serio *serio) +{ + spin_lock_init(&serio->lock); + list_add_tail(&serio->node, &serio_list); +} + +/* + * serio_destroy_port() completes deregistration process and removes + * port from the system + */ +static void serio_destroy_port(struct serio *serio) +{ + struct serio_driver *drv = serio->drv; + unsigned long flags; + + serio_remove_pending_events(serio); + list_del_init(&serio->node); + + if (drv) + drv->disconnect(serio); + + if (serio->parent) { + spin_lock_irqsave(&serio->parent->lock, flags); + serio->parent->child = NULL; + spin_unlock_irqrestore(&serio->parent->lock, flags); + } + + kfree(serio); +} + +/* + * serio_connect_port() tries to bind the port and possible all its + * children to appropriate drivers. If driver passed in the function will not + * try otehr drivers when binding parent port. + */ +static void serio_connect_port(struct serio *serio, struct serio_driver *drv) +{ + WARN_ON(serio->drv); + WARN_ON(serio->child); + + if (drv) + serio_bind_driver(serio, drv); + else + serio_find_driver(serio); + + /* Ok, now bind children, if any */ + while (serio->child) { + serio = serio->child; + + WARN_ON(serio->drv); + WARN_ON(serio->child); + + serio_create_port(serio); + + /* + * With children we just _prefer_ passed in driver, + * but we will try other options in case preferred + * is not the one + */ + if (!drv || !serio_bind_driver(serio, drv)) + serio_find_driver(serio); + } +} + +/* + * + */ +static void serio_reconnect_port(struct serio *serio) +{ + do { + if (!serio->drv || !serio->drv->reconnect || serio->drv->reconnect(serio)) { + serio_disconnect_port(serio); + serio_connect_port(serio, NULL); + /* Ok, old children are now gone, we are done */ + break; + } + serio = serio->child; + } while (serio); +} + +/* + * serio_disconnect_port() unbinds a port from its driver. As a side effect + * all child ports are unbound and destroyed. + */ +static void serio_disconnect_port(struct serio *serio) +{ + struct serio_driver *drv = serio->drv; + struct serio *s; + + if (serio->child) { + /* + * Children ports should be disconnected and destroyed + * first, staring with the leaf one, since we don't want + * to do recursion + */ + do { + s = serio->child; + } while (s->child); + + while (s != serio) { + s = s->parent; + serio_destroy_port(s->child); + } + } + + /* + * Ok, no children left, now disconnect this port + */ + if (drv) + drv->disconnect(serio); +} + void serio_rescan(struct serio *serio) { serio_queue_event(serio, SERIO_RESCAN); @@ -229,7 +349,8 @@ void serio_reconnect(struct serio *serio void serio_register_port(struct serio *serio) { down(&serio_sem); - __serio_register_port(serio); + serio_create_port(serio); + serio_connect_port(serio, NULL); up(&serio_sem); } @@ -243,22 +364,11 @@ void serio_register_port_delayed(struct serio_queue_event(serio, SERIO_REGISTER_PORT); } -/* - * Should only be called directly if serio_sem has already been taken, - * for example when unregistering a serio from other input device's - * connect() function. - */ -void __serio_register_port(struct serio *serio) -{ - spin_lock_init(&serio->lock); - list_add_tail(&serio->node, &serio_list); - serio_find_driver(serio); -} - void serio_unregister_port(struct serio *serio) { down(&serio_sem); - __serio_unregister_port(serio); + serio_disconnect_port(serio); + serio_destroy_port(serio); up(&serio_sem); } @@ -272,32 +382,33 @@ void serio_unregister_port_delayed(struc serio_queue_event(serio, SERIO_UNREGISTER_PORT); } -/* - * Should only be called directly if serio_sem has already been taken, - * for example when unregistering a serio from other input device's - * disconnect() function. - */ -void __serio_unregister_port(struct serio *serio) -{ - serio_remove_pending_events(serio); - list_del_init(&serio->node); - if (serio->drv) - serio->drv->disconnect(serio); - kfree(serio); -} /* * Serio driver operations */ + void serio_register_driver(struct serio_driver *drv) { struct serio *serio; + down(&serio_sem); + list_add_tail(&drv->node, &serio_driver_list); - list_for_each_entry(serio, &serio_list, node) - if (!serio->drv) - drv->connect(serio, drv); + +start_over: + list_for_each_entry(serio, &serio_list, node) { + if (!serio->drv) { + serio_connect_port(serio, drv); + /* + * if new child appeared then the list is changed, + * we need to start over + */ + if (serio->child) + goto start_over; + } + } + up(&serio_sem); } @@ -306,13 +417,19 @@ void serio_unregister_driver(struct seri struct serio *serio; down(&serio_sem); + list_del_init(&drv->node); +start_over: list_for_each_entry(serio, &serio_list, node) { - if (serio->drv == drv) - drv->disconnect(serio); - serio_find_driver(serio); + if (serio->drv == drv) { + serio_disconnect_port(serio); + serio_connect_port(serio, NULL); + /* we could've deleted some ports, restart */ + goto start_over; + } } + up(&serio_sem); } diff -puN include/linux/serio.h~input-serio-no-recursion include/linux/serio.h --- 25/include/linux/serio.h~input-serio-no-recursion 2004-06-19 13:52:28.795168664 -0700 +++ 25-akpm/include/linux/serio.h 2004-06-19 13:52:28.803167448 -0700 @@ -40,6 +40,8 @@ struct serio { int (*open)(struct serio *); void (*close)(struct serio *); + struct serio *parent, *child; + struct serio_driver *drv; /* Accessed from interrupt, writes must be protected by serio_lock */ struct list_head node; @@ -68,10 +70,8 @@ irqreturn_t serio_interrupt(struct serio void serio_register_port(struct serio *serio); void serio_register_port_delayed(struct serio *serio); -void __serio_register_port(struct serio *serio); void serio_unregister_port(struct serio *serio); void serio_unregister_port_delayed(struct serio *serio); -void __serio_unregister_port(struct serio *serio); void serio_register_driver(struct serio_driver *drv); void serio_unregister_driver(struct serio_driver *drv); _