+ struct serio *serio = to_serio_port(dev);
+ struct psmouse_attribute *attr = to_psmouse_attr(devattr);
+ struct psmouse *psmouse, *parent = NULL;
+ int retval;
+
+ retval = serio_pin_driver(serio);
+ if (retval)
+ return retval;
+
+ if (serio->drv != &psmouse_drv) {
+ retval = -ENODEV;
+ goto out_unpin;
+ }
+
+ retval = mutex_lock_interruptible(&psmouse_mutex);
+ if (retval)
+ goto out_unpin;
+
+ psmouse = serio_get_drvdata(serio);
+
+ if (psmouse->state == PSMOUSE_IGNORE) {
+ retval = -ENODEV;
+ goto out_unlock;
+ }
+
+ if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) {
+ parent = serio_get_drvdata(serio->parent);
+ psmouse_deactivate(parent);
+ }
+
+ psmouse_deactivate(psmouse);
+
+ retval = attr->set(psmouse, attr->data, buf, count);
+
+ if (retval != -ENODEV)
+ psmouse_activate(psmouse);
+
+ if (parent)
+ psmouse_activate(parent);
+
+ out_unlock:
+ mutex_unlock(&psmouse_mutex);
+ out_unpin:
+ serio_unpin_driver(serio);
+ return retval;
+}
+
+static ssize_t psmouse_show_int_attr(struct psmouse *psmouse, void *offset, char *buf)
+{
+ unsigned long *field = (unsigned long *)((char *)psmouse + (size_t)offset);
+
+ return sprintf(buf, "%lu\n", *field);
+}
+
+static ssize_t psmouse_set_int_attr(struct psmouse *psmouse, void *offset, const char *buf, size_t count)
+{
+ unsigned long *field = (unsigned long *)((char *)psmouse + (size_t)offset);
+ unsigned long value;
+ char *rest;
+
+ value = simple_strtoul(buf, &rest, 10);
+ if (*rest)
+ return -EINVAL;
+
+ *field = value;
+
+ return count;
+}
+
+static ssize_t psmouse_attr_show_protocol(struct psmouse *psmouse, void *data, char *buf)
+{
+ return sprintf(buf, "%s\n", psmouse_protocol_by_type(psmouse->type)->name);
+}
+
+static ssize_t psmouse_attr_set_protocol(struct psmouse *psmouse, void *data, const char *buf, size_t count)
+{
+ struct serio *serio = psmouse->ps2dev.serio;
+ struct psmouse *parent = NULL;
+ struct input_dev *new_dev;
+ struct psmouse_protocol *proto;
+ int retry = 0;
+
+ if (!(proto = psmouse_protocol_by_name(buf, count)))
+ return -EINVAL;
+
+ if (psmouse->type == proto->type)
+ return count;
+
+ if (!(new_dev = input_allocate_device()))
+ return -ENOMEM;
+
+ while (serio->child) {
+ if (++retry > 3) {
+ printk(KERN_WARNING "psmouse: failed to destroy child port, protocol change aborted.\n");
+ input_free_device(new_dev);
+ return -EIO;
+ }
+
+ mutex_unlock(&psmouse_mutex);
+ serio_unpin_driver(serio);
+ serio_unregister_child_port(serio);
+ serio_pin_driver_uninterruptible(serio);
+ mutex_lock(&psmouse_mutex);
+
+ if (serio->drv != &psmouse_drv) {
+ input_free_device(new_dev);
+ return -ENODEV;
+ }
+
+ if (psmouse->type == proto->type) {
+ input_free_device(new_dev);
+ return count; /* switched by other thread */
+ }
+ }
+
+ if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) {
+ parent = serio_get_drvdata(serio->parent);
+ if (parent->pt_deactivate)
+ parent->pt_deactivate(parent);
+ }
+
+ if (psmouse->disconnect)
+ psmouse->disconnect(psmouse);
+
+ psmouse_set_state(psmouse, PSMOUSE_IGNORE);
+ input_unregister_device(psmouse->dev);
+
+ psmouse->dev = new_dev;
+ psmouse_set_state(psmouse, PSMOUSE_INITIALIZING);
+
+ if (psmouse_switch_protocol(psmouse, proto) < 0) {
+ psmouse_reset(psmouse);
+ /* default to PSMOUSE_PS2 */
+ psmouse_switch_protocol(psmouse, &psmouse_protocols[0]);
+ }
+
+ psmouse_initialize(psmouse);
+ psmouse_set_state(psmouse, PSMOUSE_CMD_MODE);
+
+ input_register_device(psmouse->dev);
+
+ if (parent && parent->pt_activate)
+ parent->pt_activate(parent);
+
+ return count;
+}
+
+static ssize_t psmouse_attr_set_rate(struct psmouse *psmouse, void *data, const char *buf, size_t count)
+{
+ unsigned long value;
+ char *rest;
+
+ value = simple_strtoul(buf, &rest, 10);
+ if (*rest)
+ return -EINVAL;
+
+ psmouse->set_rate(psmouse, value);
+ return count;
+}
+
+static ssize_t psmouse_attr_set_resolution(struct psmouse *psmouse, void *data, const char *buf, size_t count)
+{
+ unsigned long value;
+ char *rest;
+
+ value = simple_strtoul(buf, &rest, 10);
+ if (*rest)
+ return -EINVAL;
+
+ psmouse->set_resolution(psmouse, value);
+ return count;
+}
+
+
+static int psmouse_set_maxproto(const char *val, struct kernel_param *kp)
+{
+ struct psmouse_protocol *proto;
+
+ if (!val)
+ return -EINVAL;
+
+ proto = psmouse_protocol_by_name(val, strlen(val));
+
+ if (!proto || !proto->maxproto)
+ return -EINVAL;
+
+ *((unsigned int *)kp->arg) = proto->type;
+
+ return 0;
+}
+
+static int psmouse_get_maxproto(char *buffer, struct kernel_param *kp)
+{
+ int type = *((unsigned int *)kp->arg);
+
+ return sprintf(buffer, "%s\n", psmouse_protocol_by_type(type)->name);
+}
+
+static int __init psmouse_init(void)
+{
+ kpsmoused_wq = create_singlethread_workqueue("kpsmoused");
+ if (!kpsmoused_wq) {
+ printk(KERN_ERR "psmouse: failed to create kpsmoused workqueue\n");
+ return -ENOMEM;
+ }
+
+ serio_register_driver(&psmouse_drv);
+