diff options
Diffstat (limited to 'drivers/input/serio/serio_raw.c')
| -rw-r--r-- | drivers/input/serio/serio_raw.c | 289 |
1 files changed, 162 insertions, 127 deletions
diff --git a/drivers/input/serio/serio_raw.c b/drivers/input/serio/serio_raw.c index 27fdaaffbb4..c9a02fe5757 100644 --- a/drivers/input/serio/serio_raw.c +++ b/drivers/input/serio/serio_raw.c @@ -9,13 +9,12 @@ * the Free Software Foundation. */ +#include <linux/kref.h> #include <linux/sched.h> #include <linux/slab.h> -#include <linux/smp_lock.h> #include <linux/poll.h> #include <linux/module.h> #include <linux/serio.h> -#include <linux/init.h> #include <linux/major.h> #include <linux/device.h> #include <linux/miscdevice.h> @@ -34,15 +33,16 @@ struct serio_raw { unsigned int tail, head; char name[16]; - unsigned int refcnt; + struct kref kref; struct serio *serio; struct miscdevice dev; wait_queue_head_t wait; - struct list_head list; + struct list_head client_list; struct list_head node; + bool dead; }; -struct serio_raw_list { +struct serio_raw_client { struct fasync_struct *fasync; struct serio_raw *serio_raw; struct list_head node; @@ -50,7 +50,6 @@ struct serio_raw_list { static DEFINE_MUTEX(serio_raw_mutex); static LIST_HEAD(serio_raw_list); -static unsigned int serio_raw_no; /********************************************************************* * Interface with userspace (file operations) * @@ -58,9 +57,9 @@ static unsigned int serio_raw_no; static int serio_raw_fasync(int fd, struct file *file, int on) { - struct serio_raw_list *list = file->private_data; + struct serio_raw_client *client = file->private_data; - return fasync_helper(fd, file, on, &list->fasync); + return fasync_helper(fd, file, on, &client->fasync); } static struct serio_raw *serio_raw_locate(int minor) @@ -78,74 +77,74 @@ static struct serio_raw *serio_raw_locate(int minor) static int serio_raw_open(struct inode *inode, struct file *file) { struct serio_raw *serio_raw; - struct serio_raw_list *list; - int retval = 0; + struct serio_raw_client *client; + int retval; - lock_kernel(); retval = mutex_lock_interruptible(&serio_raw_mutex); if (retval) - goto out_bkl; + return retval; - if (!(serio_raw = serio_raw_locate(iminor(inode)))) { + serio_raw = serio_raw_locate(iminor(inode)); + if (!serio_raw) { retval = -ENODEV; goto out; } - if (!serio_raw->serio) { + if (serio_raw->dead) { retval = -ENODEV; goto out; } - if (!(list = kzalloc(sizeof(struct serio_raw_list), GFP_KERNEL))) { + client = kzalloc(sizeof(struct serio_raw_client), GFP_KERNEL); + if (!client) { retval = -ENOMEM; goto out; } - list->serio_raw = serio_raw; - file->private_data = list; + client->serio_raw = serio_raw; + file->private_data = client; + + kref_get(&serio_raw->kref); - serio_raw->refcnt++; - list_add_tail(&list->node, &serio_raw->list); + serio_pause_rx(serio_raw->serio); + list_add_tail(&client->node, &serio_raw->client_list); + serio_continue_rx(serio_raw->serio); out: mutex_unlock(&serio_raw_mutex); -out_bkl: - unlock_kernel(); return retval; } -static int serio_raw_cleanup(struct serio_raw *serio_raw) +static void serio_raw_free(struct kref *kref) { - if (--serio_raw->refcnt == 0) { - misc_deregister(&serio_raw->dev); - list_del_init(&serio_raw->node); - kfree(serio_raw); + struct serio_raw *serio_raw = + container_of(kref, struct serio_raw, kref); - return 1; - } - - return 0; + put_device(&serio_raw->serio->dev); + kfree(serio_raw); } static int serio_raw_release(struct inode *inode, struct file *file) { - struct serio_raw_list *list = file->private_data; - struct serio_raw *serio_raw = list->serio_raw; + struct serio_raw_client *client = file->private_data; + struct serio_raw *serio_raw = client->serio_raw; - mutex_lock(&serio_raw_mutex); + serio_pause_rx(serio_raw->serio); + list_del(&client->node); + serio_continue_rx(serio_raw->serio); - serio_raw_cleanup(serio_raw); + kfree(client); + + kref_put(&serio_raw->kref, serio_raw_free); - mutex_unlock(&serio_raw_mutex); return 0; } -static int serio_raw_fetch_byte(struct serio_raw *serio_raw, char *c) +static bool serio_raw_fetch_byte(struct serio_raw *serio_raw, char *c) { - unsigned long flags; - int empty; + bool empty; - spin_lock_irqsave(&serio_raw->serio->lock, flags); + serio_pause_rx(serio_raw->serio); empty = serio_raw->head == serio_raw->tail; if (!empty) { @@ -153,53 +152,65 @@ static int serio_raw_fetch_byte(struct serio_raw *serio_raw, char *c) serio_raw->tail = (serio_raw->tail + 1) % SERIO_RAW_QUEUE_LEN; } - spin_unlock_irqrestore(&serio_raw->serio->lock, flags); + serio_continue_rx(serio_raw->serio); return !empty; } -static ssize_t serio_raw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +static ssize_t serio_raw_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) { - struct serio_raw_list *list = file->private_data; - struct serio_raw *serio_raw = list->serio_raw; + struct serio_raw_client *client = file->private_data; + struct serio_raw *serio_raw = client->serio_raw; char uninitialized_var(c); - ssize_t retval = 0; + ssize_t read = 0; + int error; - if (!serio_raw->serio) - return -ENODEV; + for (;;) { + if (serio_raw->dead) + return -ENODEV; - if (serio_raw->head == serio_raw->tail && (file->f_flags & O_NONBLOCK)) - return -EAGAIN; + if (serio_raw->head == serio_raw->tail && + (file->f_flags & O_NONBLOCK)) + return -EAGAIN; - retval = wait_event_interruptible(list->serio_raw->wait, - serio_raw->head != serio_raw->tail || !serio_raw->serio); - if (retval) - return retval; + if (count == 0) + break; + + while (read < count && serio_raw_fetch_byte(serio_raw, &c)) { + if (put_user(c, buffer++)) + return -EFAULT; + read++; + } - if (!serio_raw->serio) - return -ENODEV; + if (read) + break; - while (retval < count && serio_raw_fetch_byte(serio_raw, &c)) { - if (put_user(c, buffer++)) - return -EFAULT; - retval++; + if (!(file->f_flags & O_NONBLOCK)) { + error = wait_event_interruptible(serio_raw->wait, + serio_raw->head != serio_raw->tail || + serio_raw->dead); + if (error) + return error; + } } - return retval; + return read; } -static ssize_t serio_raw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +static ssize_t serio_raw_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) { - struct serio_raw_list *list = file->private_data; - ssize_t written = 0; - int retval; + struct serio_raw_client *client = file->private_data; + struct serio_raw *serio_raw = client->serio_raw; + int retval = 0; unsigned char c; retval = mutex_lock_interruptible(&serio_raw_mutex); if (retval) return retval; - if (!list->serio_raw->serio) { + if (serio_raw->dead) { retval = -ENODEV; goto out; } @@ -212,59 +223,67 @@ static ssize_t serio_raw_write(struct file *file, const char __user *buffer, siz retval = -EFAULT; goto out; } - if (serio_write(list->serio_raw->serio, c)) { - retval = -EIO; + + if (serio_write(serio_raw->serio, c)) { + /* Either signal error or partial write */ + if (retval == 0) + retval = -EIO; goto out; } - written++; - }; + + retval++; + } out: mutex_unlock(&serio_raw_mutex); - return written; + return retval; } static unsigned int serio_raw_poll(struct file *file, poll_table *wait) { - struct serio_raw_list *list = file->private_data; + struct serio_raw_client *client = file->private_data; + struct serio_raw *serio_raw = client->serio_raw; + unsigned int mask; - poll_wait(file, &list->serio_raw->wait, wait); + poll_wait(file, &serio_raw->wait, wait); - if (list->serio_raw->head != list->serio_raw->tail) - return POLLIN | POLLRDNORM; + mask = serio_raw->dead ? POLLHUP | POLLERR : POLLOUT | POLLWRNORM; + if (serio_raw->head != serio_raw->tail) + mask |= POLLIN | POLLRDNORM; - return 0; + return mask; } static const struct file_operations serio_raw_fops = { - .owner = THIS_MODULE, - .open = serio_raw_open, - .release = serio_raw_release, - .read = serio_raw_read, - .write = serio_raw_write, - .poll = serio_raw_poll, - .fasync = serio_raw_fasync, + .owner = THIS_MODULE, + .open = serio_raw_open, + .release = serio_raw_release, + .read = serio_raw_read, + .write = serio_raw_write, + .poll = serio_raw_poll, + .fasync = serio_raw_fasync, + .llseek = noop_llseek, }; /********************************************************************* - * Interface with serio port * + * Interface with serio port * *********************************************************************/ static irqreturn_t serio_raw_interrupt(struct serio *serio, unsigned char data, unsigned int dfl) { struct serio_raw *serio_raw = serio_get_drvdata(serio); - struct serio_raw_list *list; + struct serio_raw_client *client; unsigned int head = serio_raw->head; - /* we are holding serio->lock here so we are prootected */ + /* we are holding serio->lock here so we are protected */ serio_raw->queue[head] = data; head = (head + 1) % SERIO_RAW_QUEUE_LEN; if (likely(head != serio_raw->tail)) { serio_raw->head = head; - list_for_each_entry(list, &serio_raw->list, node) - kill_fasync(&list->fasync, SIGIO, POLL_IN); + list_for_each_entry(client, &serio_raw->client_list, node) + kill_fasync(&client->fasync, SIGIO, POLL_IN); wake_up_interruptible(&serio_raw->wait); } @@ -273,29 +292,37 @@ static irqreturn_t serio_raw_interrupt(struct serio *serio, unsigned char data, static int serio_raw_connect(struct serio *serio, struct serio_driver *drv) { + static atomic_t serio_raw_no = ATOMIC_INIT(0); struct serio_raw *serio_raw; int err; - if (!(serio_raw = kzalloc(sizeof(struct serio_raw), GFP_KERNEL))) { - printk(KERN_ERR "serio_raw.c: can't allocate memory for a device\n"); + serio_raw = kzalloc(sizeof(struct serio_raw), GFP_KERNEL); + if (!serio_raw) { + dev_dbg(&serio->dev, "can't allocate memory for a device\n"); return -ENOMEM; } - mutex_lock(&serio_raw_mutex); + snprintf(serio_raw->name, sizeof(serio_raw->name), + "serio_raw%ld", (long)atomic_inc_return(&serio_raw_no) - 1); + kref_init(&serio_raw->kref); + INIT_LIST_HEAD(&serio_raw->client_list); + init_waitqueue_head(&serio_raw->wait); - snprintf(serio_raw->name, sizeof(serio_raw->name), "serio_raw%d", serio_raw_no++); - serio_raw->refcnt = 1; serio_raw->serio = serio; - INIT_LIST_HEAD(&serio_raw->list); - init_waitqueue_head(&serio_raw->wait); + get_device(&serio->dev); serio_set_drvdata(serio, serio_raw); err = serio_open(serio, drv); if (err) - goto out_free; + goto err_free; + + err = mutex_lock_killable(&serio_raw_mutex); + if (err) + goto err_close; list_add_tail(&serio_raw->node, &serio_raw_list); + mutex_unlock(&serio_raw_mutex); serio_raw->dev.minor = PSMOUSE_MINOR; serio_raw->dev.name = serio_raw->name; @@ -309,23 +336,23 @@ static int serio_raw_connect(struct serio *serio, struct serio_driver *drv) } if (err) { - printk(KERN_INFO "serio_raw: failed to register raw access device for %s\n", + dev_err(&serio->dev, + "failed to register raw access device for %s\n", serio->phys); - goto out_close; + goto err_unlink; } - printk(KERN_INFO "serio_raw: raw access enabled on %s (%s, minor %d)\n", - serio->phys, serio_raw->name, serio_raw->dev.minor); - goto out; + dev_info(&serio->dev, "raw access enabled on %s (%s, minor %d)\n", + serio->phys, serio_raw->name, serio_raw->dev.minor); + return 0; -out_close: - serio_close(serio); +err_unlink: list_del_init(&serio_raw->node); -out_free: +err_close: + serio_close(serio); +err_free: serio_set_drvdata(serio, NULL); - kfree(serio_raw); -out: - mutex_unlock(&serio_raw_mutex); + kref_put(&serio_raw->kref, serio_raw_free); return err; } @@ -335,7 +362,8 @@ static int serio_raw_reconnect(struct serio *serio) struct serio_driver *drv = serio->drv; if (!drv || !serio_raw) { - printk(KERN_DEBUG "serio_raw: reconnect request, but serio is disconnected, ignoring...\n"); + dev_dbg(&serio->dev, + "reconnect request, but serio is disconnected, ignoring...\n"); return -1; } @@ -346,22 +374,40 @@ static int serio_raw_reconnect(struct serio *serio) return 0; } +/* + * Wake up users waiting for IO so they can disconnect from + * dead device. + */ +static void serio_raw_hangup(struct serio_raw *serio_raw) +{ + struct serio_raw_client *client; + + serio_pause_rx(serio_raw->serio); + list_for_each_entry(client, &serio_raw->client_list, node) + kill_fasync(&client->fasync, SIGIO, POLL_HUP); + serio_continue_rx(serio_raw->serio); + + wake_up_interruptible(&serio_raw->wait); +} + + static void serio_raw_disconnect(struct serio *serio) { - struct serio_raw *serio_raw; + struct serio_raw *serio_raw = serio_get_drvdata(serio); + + misc_deregister(&serio_raw->dev); mutex_lock(&serio_raw_mutex); + serio_raw->dead = true; + list_del_init(&serio_raw->node); + mutex_unlock(&serio_raw_mutex); - serio_raw = serio_get_drvdata(serio); + serio_raw_hangup(serio_raw); serio_close(serio); - serio_set_drvdata(serio, NULL); - - serio_raw->serio = NULL; - if (!serio_raw_cleanup(serio_raw)) - wake_up_interruptible(&serio_raw->wait); + kref_put(&serio_raw->kref, serio_raw_free); - mutex_unlock(&serio_raw_mutex); + serio_set_drvdata(serio, NULL); } static struct serio_device_id serio_raw_serio_ids[] = { @@ -392,18 +438,7 @@ static struct serio_driver serio_raw_drv = { .connect = serio_raw_connect, .reconnect = serio_raw_reconnect, .disconnect = serio_raw_disconnect, - .manual_bind = 1, + .manual_bind = true, }; -static int __init serio_raw_init(void) -{ - return serio_register_driver(&serio_raw_drv); -} - -static void __exit serio_raw_exit(void) -{ - serio_unregister_driver(&serio_raw_drv); -} - -module_init(serio_raw_init); -module_exit(serio_raw_exit); +module_serio_driver(serio_raw_drv); |
