Commit d537c384 authored by Dmitry Torokhov's avatar Dmitry Torokhov

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: default avatarDmitry Torokhov <dtor@mail.ru>
parent 7d9f1d2f
...@@ -652,16 +652,15 @@ static void psmouse_cleanup(struct serio *serio) ...@@ -652,16 +652,15 @@ static void psmouse_cleanup(struct serio *serio)
static void psmouse_disconnect(struct serio *serio) static void psmouse_disconnect(struct serio *serio)
{ {
struct psmouse *psmouse = serio->private; struct psmouse *psmouse, *parent;
psmouse = serio->private;
psmouse->state = PSMOUSE_CMD_MODE; psmouse->state = PSMOUSE_CMD_MODE;
if (psmouse->ptport) { if (serio->parent && (serio->type & SERIO_TYPE) == SERIO_PS_PSTHRU) {
if (psmouse->ptport->deactivate) parent = serio->parent->private;
psmouse->ptport->deactivate(psmouse); if (parent->pt_deactivate)
__serio_unregister_port(psmouse->ptport->serio); /* we have serio_sem */ parent->pt_deactivate(parent);
kfree(psmouse->ptport);
psmouse->ptport = NULL;
} }
if (psmouse->disconnect) if (psmouse->disconnect)
...@@ -680,14 +679,17 @@ static void psmouse_disconnect(struct serio *serio) ...@@ -680,14 +679,17 @@ static void psmouse_disconnect(struct serio *serio)
*/ */
static void psmouse_connect(struct serio *serio, struct serio_driver *drv) 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 && if ((serio->type & SERIO_TYPE) != SERIO_8042 &&
(serio->type & SERIO_TYPE) != SERIO_PS_PSTHRU) (serio->type & SERIO_TYPE) != SERIO_PS_PSTHRU)
return; return;
if (serio->parent && (serio->type & SERIO_TYPE) == SERIO_PS_PSTHRU)
parent = serio->parent->private;
if (!(psmouse = kmalloc(sizeof(struct psmouse), GFP_KERNEL))) if (!(psmouse = kmalloc(sizeof(struct psmouse), GFP_KERNEL)))
return; goto out;
memset(psmouse, 0, sizeof(struct psmouse)); memset(psmouse, 0, sizeof(struct psmouse));
...@@ -703,14 +705,14 @@ static void psmouse_connect(struct serio *serio, struct serio_driver *drv) ...@@ -703,14 +705,14 @@ static void psmouse_connect(struct serio *serio, struct serio_driver *drv)
if (serio_open(serio, drv)) { if (serio_open(serio, drv)) {
kfree(psmouse); kfree(psmouse);
serio->private = NULL; serio->private = NULL;
return; goto out;
} }
if (psmouse_probe(psmouse) < 0) { if (psmouse_probe(psmouse) < 0) {
serio_close(serio); serio_close(serio);
kfree(psmouse); kfree(psmouse);
serio->private = NULL; serio->private = NULL;
return; goto out;
} }
psmouse->type = psmouse_extensions(psmouse, psmouse_max_proto, 1); psmouse->type = psmouse_extensions(psmouse, psmouse_max_proto, 1);
...@@ -739,20 +741,36 @@ static void psmouse_connect(struct serio *serio, struct serio_driver *drv) ...@@ -739,20 +741,36 @@ static void psmouse_connect(struct serio *serio, struct serio_driver *drv)
psmouse_initialize(psmouse); psmouse_initialize(psmouse);
if (psmouse->ptport) { if (parent && parent->pt_activate)
printk(KERN_INFO "serio: %s port at %s\n", psmouse->ptport->serio->name, psmouse->phys); parent->pt_activate(parent);
__serio_register_port(psmouse->ptport->serio); /* we have serio_sem */
if (psmouse->ptport->activate)
psmouse->ptport->activate(psmouse);
}
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) static int psmouse_reconnect(struct serio *serio)
{ {
struct psmouse *psmouse = serio->private; struct psmouse *psmouse = serio->private;
struct psmouse *parent = NULL;
struct serio_driver *drv = serio->drv; struct serio_driver *drv = serio->drv;
if (!drv || !psmouse) { if (!drv || !psmouse) {
...@@ -779,16 +797,19 @@ static int psmouse_reconnect(struct serio *serio) ...@@ -779,16 +797,19 @@ static int psmouse_reconnect(struct serio *serio)
*/ */
psmouse_initialize(psmouse); psmouse_initialize(psmouse);
if (psmouse->ptport) { if (serio->parent && (serio->type & SERIO_TYPE) == SERIO_PS_PSTHRU)
if (psmouse_reconnect(psmouse->ptport->serio)) { parent = serio->parent->private;
__serio_unregister_port(psmouse->ptport->serio);
__serio_register_port(psmouse->ptport->serio); if (parent && parent->pt_activate)
if (psmouse->ptport->activate) parent->pt_activate(parent);
psmouse->ptport->activate(psmouse);
} 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; return 0;
} }
......
...@@ -34,21 +34,10 @@ typedef enum { ...@@ -34,21 +34,10 @@ typedef enum {
PSMOUSE_FULL_PACKET PSMOUSE_FULL_PACKET
} psmouse_ret_t; } 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 { struct psmouse {
void *private; void *private;
struct input_dev dev; struct input_dev dev;
struct serio *serio; struct serio *serio;
struct psmouse_ptport *ptport;
char *vendor; char *vendor;
char *name; char *name;
unsigned char cmdbuf[8]; unsigned char cmdbuf[8];
...@@ -66,9 +55,12 @@ struct psmouse { ...@@ -66,9 +55,12 @@ struct psmouse {
char phys[32]; char phys[32];
unsigned long flags; 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); int (*reconnect)(struct psmouse *psmouse);
void (*disconnect)(struct psmouse *psmouse); void (*disconnect)(struct psmouse *psmouse);
void (*pt_activate)(struct psmouse *psmouse);
void (*pt_deactivate)(struct psmouse *psmouse);
}; };
#define PSMOUSE_PS2 1 #define PSMOUSE_PS2 1
......
...@@ -212,14 +212,14 @@ static int synaptics_set_mode(struct psmouse *psmouse, int mode) ...@@ -212,14 +212,14 @@ static int synaptics_set_mode(struct psmouse *psmouse, int mode)
/***************************************************************************** /*****************************************************************************
* Synaptics pass-through PS/2 port support * 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 */ 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; return -1;
if (psmouse_command(ptport->parent, &rate_param, PSMOUSE_CMD_SETRATE)) if (psmouse_command(parent, &rate_param, PSMOUSE_CMD_SETRATE))
return -1; return -1;
return 0; return 0;
} }
...@@ -248,7 +248,7 @@ static void synaptics_pass_pt_packet(struct serio *ptport, unsigned char *packet ...@@ -248,7 +248,7 @@ static void synaptics_pass_pt_packet(struct serio *ptport, unsigned char *packet
static void synaptics_pt_activate(struct psmouse *psmouse) 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 */ /* adjust the touchpad to child's choice of protocol */
if (child && child->type >= PSMOUSE_GENPS) { if (child && child->type >= PSMOUSE_GENPS) {
...@@ -259,30 +259,25 @@ static void synaptics_pt_activate(struct psmouse *psmouse) ...@@ -259,30 +259,25 @@ static void synaptics_pt_activate(struct psmouse *psmouse)
static void synaptics_pt_create(struct psmouse *psmouse) static void synaptics_pt_create(struct psmouse *psmouse)
{ {
struct psmouse_ptport *port;
struct serio *serio; struct serio *serio;
port = kmalloc(sizeof(struct psmouse_ptport), GFP_KERNEL);
serio = kmalloc(sizeof(struct serio), 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"); printk(KERN_ERR "synaptics: not enough memory to allocate pass-through port\n");
return; return;
} }
memset(port, 0, sizeof(struct psmouse_ptport));
memset(serio, 0, sizeof(struct serio)); memset(serio, 0, sizeof(struct serio));
serio->type = SERIO_PS_PSTHRU; serio->type = SERIO_PS_PSTHRU;
strlcpy(serio->name, "Synaptics pass-through", sizeof(serio->name)); strlcpy(serio->name, "Synaptics pass-through", sizeof(serio->name));
strlcpy(serio->phys, "synaptics-pt/serio0", sizeof(serio->name)); strlcpy(serio->phys, "synaptics-pt/serio0", sizeof(serio->name));
serio->write = synaptics_pt_write; serio->write = synaptics_pt_write;
serio->port_data = port; serio->parent = psmouse->serio;
port->serio = serio; psmouse->pt_activate = synaptics_pt_activate;
port->parent = psmouse;
port->activate = synaptics_pt_activate;
psmouse->ptport = port; psmouse->serio->child = serio;
} }
/***************************************************************************** /*****************************************************************************
...@@ -477,8 +472,8 @@ static psmouse_ret_t synaptics_process_byte(struct psmouse *psmouse, struct pt_r ...@@ -477,8 +472,8 @@ static psmouse_ret_t synaptics_process_byte(struct psmouse *psmouse, struct pt_r
if (unlikely(priv->pkt_type == SYN_NEWABS)) if (unlikely(priv->pkt_type == SYN_NEWABS))
priv->pkt_type = synaptics_detect_pkt_type(psmouse); priv->pkt_type = synaptics_detect_pkt_type(psmouse);
if (psmouse->ptport && psmouse->ptport->serio->drv && synaptics_is_pt_packet(psmouse->packet)) if (psmouse->serio->child && psmouse->serio->child->drv && synaptics_is_pt_packet(psmouse->packet))
synaptics_pass_pt_packet(psmouse->ptport->serio, psmouse->packet); synaptics_pass_pt_packet(psmouse->serio->child, psmouse->packet);
else else
synaptics_process_packet(psmouse); synaptics_process_packet(psmouse);
......
...@@ -44,10 +44,8 @@ MODULE_LICENSE("GPL"); ...@@ -44,10 +44,8 @@ MODULE_LICENSE("GPL");
EXPORT_SYMBOL(serio_interrupt); EXPORT_SYMBOL(serio_interrupt);
EXPORT_SYMBOL(serio_register_port); EXPORT_SYMBOL(serio_register_port);
EXPORT_SYMBOL(serio_register_port_delayed); EXPORT_SYMBOL(serio_register_port_delayed);
EXPORT_SYMBOL(__serio_register_port);
EXPORT_SYMBOL(serio_unregister_port); EXPORT_SYMBOL(serio_unregister_port);
EXPORT_SYMBOL(serio_unregister_port_delayed); EXPORT_SYMBOL(serio_unregister_port_delayed);
EXPORT_SYMBOL(__serio_unregister_port);
EXPORT_SYMBOL(serio_register_driver); EXPORT_SYMBOL(serio_register_driver);
EXPORT_SYMBOL(serio_unregister_driver); EXPORT_SYMBOL(serio_unregister_driver);
EXPORT_SYMBOL(serio_open); EXPORT_SYMBOL(serio_open);
...@@ -59,17 +57,28 @@ static DECLARE_MUTEX(serio_sem); /* protects serio_list and serio_diriver_list * ...@@ -59,17 +57,28 @@ static DECLARE_MUTEX(serio_sem); /* protects serio_list and serio_diriver_list *
static LIST_HEAD(serio_list); static LIST_HEAD(serio_list);
static LIST_HEAD(serio_driver_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) static void serio_find_driver(struct serio *serio)
{ {
struct serio_driver *drv; struct serio_driver *drv;
list_for_each_entry(drv, &serio_driver_list, node) { list_for_each_entry(drv, &serio_driver_list, node)
if (serio->drv) if (serio_bind_driver(serio, drv))
break; break;
drv->connect(serio, drv);
}
} }
/* /*
...@@ -145,23 +154,22 @@ static void serio_handle_events(void) ...@@ -145,23 +154,22 @@ static void serio_handle_events(void)
switch (event->type) { switch (event->type) {
case SERIO_REGISTER_PORT : case SERIO_REGISTER_PORT :
__serio_register_port(event->serio); serio_create_port(event->serio);
serio_connect_port(event->serio, NULL);
break; break;
case SERIO_UNREGISTER_PORT : case SERIO_UNREGISTER_PORT :
__serio_unregister_port(event->serio); serio_disconnect_port(event->serio);
serio_destroy_port(event->serio);
break; break;
case SERIO_RECONNECT : case SERIO_RECONNECT :
if (event->serio->drv && event->serio->drv->reconnect) serio_reconnect_port(event->serio);
if (event->serio->drv->reconnect(event->serio) == 0) break;
break;
/* reconnect failed - fall through to rescan */
case SERIO_RESCAN : case SERIO_RESCAN :
if (event->serio->drv) serio_disconnect_port(event->serio);
event->serio->drv->disconnect(event->serio); serio_connect_port(event->serio, NULL);
serio_find_driver(event->serio);
break; break;
default: default:
break; break;
...@@ -216,6 +224,118 @@ static int serio_thread(void *nothing) ...@@ -216,6 +224,118 @@ static int serio_thread(void *nothing)
* Serio port operations * 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) void serio_rescan(struct serio *serio)
{ {
serio_queue_event(serio, SERIO_RESCAN); serio_queue_event(serio, SERIO_RESCAN);
...@@ -229,7 +349,8 @@ void serio_reconnect(struct serio *serio) ...@@ -229,7 +349,8 @@ void serio_reconnect(struct serio *serio)
void serio_register_port(struct serio *serio) void serio_register_port(struct serio *serio)
{ {
down(&serio_sem); down(&serio_sem);
__serio_register_port(serio); serio_create_port(serio);
serio_connect_port(serio, NULL);
up(&serio_sem); up(&serio_sem);
} }
...@@ -243,22 +364,11 @@ void serio_register_port_delayed(struct serio *serio) ...@@ -243,22 +364,11 @@ void serio_register_port_delayed(struct serio *serio)
serio_queue_event(serio, SERIO_REGISTER_PORT); 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) void serio_unregister_port(struct serio *serio)
{ {
down(&serio_sem); down(&serio_sem);
__serio_unregister_port(serio); serio_disconnect_port(serio);
serio_destroy_port(serio);
up(&serio_sem); up(&serio_sem);
} }
...@@ -272,32 +382,33 @@ void serio_unregister_port_delayed(struct serio *serio) ...@@ -272,32 +382,33 @@ void serio_unregister_port_delayed(struct serio *serio)
serio_queue_event(serio, SERIO_UNREGISTER_PORT); 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 * Serio driver operations
*/ */
void serio_register_driver(struct serio_driver *drv) void serio_register_driver(struct serio_driver *drv)
{ {
struct serio *serio; struct serio *serio;
down(&serio_sem); down(&serio_sem);
list_add_tail(&drv->node, &serio_driver_list); list_add_tail(&drv->node, &serio_driver_list);
list_for_each_entry(serio, &serio_list, node)
if (!serio->drv) start_over:
drv->connect(serio, drv); 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); up(&serio_sem);
} }
...@@ -306,13 +417,19 @@ void serio_unregister_driver(struct serio_driver *drv) ...@@ -306,13 +417,19 @@ void serio_unregister_driver(struct serio_driver *drv)
struct serio *serio; struct serio *serio;
down(&serio_sem); down(&serio_sem);
list_del_init(&drv->node); list_del_init(&drv->node);
start_over:
list_for_each_entry(serio, &serio_list, node) { list_for_each_entry(serio, &serio_list, node) {
if (serio->drv == drv) if (serio->drv == drv) {
drv->disconnect(serio); serio_disconnect_port(serio);
serio_find_driver(serio); serio_connect_port(serio, NULL);
/* we could've deleted some ports, restart */
goto start_over;
}
} }
up(&serio_sem); up(&serio_sem);
} }
......
...@@ -40,6 +40,8 @@ struct serio { ...@@ -40,6 +40,8 @@ struct serio {
int (*open)(struct serio *); int (*open)(struct serio *);
void (*close)(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 serio_driver *drv; /* Accessed from interrupt, writes must be protected by serio_lock */
struct list_head node; struct list_head node;
...@@ -68,10 +70,8 @@ irqreturn_t serio_interrupt(struct serio *serio, unsigned char data, unsigned in ...@@ -68,10 +70,8 @@ irqreturn_t serio_interrupt(struct serio *serio, unsigned char data, unsigned in
void serio_register_port(struct serio *serio); void serio_register_port(struct serio *serio);
void serio_register_port_delayed(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(struct serio *serio);
void serio_unregister_port_delayed(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_register_driver(struct serio_driver *drv);
void serio_unregister_driver(struct serio_driver *drv); void serio_unregister_driver(struct serio_driver *drv);
......
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