diff options
Diffstat (limited to 'drivers/input/serio/serio_raw.c')
| -rw-r--r-- | drivers/input/serio/serio_raw.c | 283 | 
1 files changed, 159 insertions, 124 deletions
diff --git a/drivers/input/serio/serio_raw.c b/drivers/input/serio/serio_raw.c index cd82bb12591..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,8 +77,8 @@ 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;  	retval = mutex_lock_interruptible(&serio_raw_mutex);  	if (retval) @@ -91,60 +90,61 @@ static int serio_raw_open(struct inode *inode, struct file *file)  		goto out;  	} -	if (!serio_raw->serio) { +	if (serio_raw->dead) {  		retval = -ENODEV;  		goto out;  	} -	list = kzalloc(sizeof(struct serio_raw_list), GFP_KERNEL); -	if (!list) { +	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; -	serio_raw->refcnt++; -	list_add_tail(&list->node, &serio_raw->list); +	kref_get(&serio_raw->kref); + +	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);  	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); - -		return 1; -	} +	struct serio_raw *serio_raw = +			container_of(kref, struct serio_raw, kref); -	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) { @@ -152,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; -	if (!serio_raw->serio) -		return -ENODEV; +		while (read < count && serio_raw_fetch_byte(serio_raw, &c)) { +			if (put_user(c, buffer++)) +				return -EFAULT; +			read++; +		} -	while (retval < count && serio_raw_fetch_byte(serio_raw, &c)) { -		if (put_user(c, buffer++)) -			return -EFAULT; -		retval++; +		if (read) +			break; + +		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;  	} @@ -211,60 +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, -	.llseek = noop_llseek, +	.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);  | 
