[RFC/RFT 2/5] evdev - implement proper locking

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Input: evdev - implement proper locking

Signed-off-by: Dmitry Torokhov <[email protected]>
---

 drivers/input/evdev.c |  719 +++++++++++++++++++++++++++++++++-----------------
 1 files changed, 476 insertions(+), 243 deletions(-)

Index: work/drivers/input/evdev.c
===================================================================
--- work.orig/drivers/input/evdev.c
+++ work/drivers/input/evdev.c
@@ -30,6 +30,8 @@ struct evdev {
 	wait_queue_head_t wait;
 	struct evdev_client *grab;
 	struct list_head client_list;
+	spinlock_t client_lock; /* protects client_list */
+	struct mutex mutex;
 	struct device dev;
 };
 
@@ -37,39 +39,53 @@ struct evdev_client {
 	struct input_event buffer[EVDEV_BUFFER_SIZE];
 	int head;
 	int tail;
+	spinlock_t buffer_lock; /* protects access to buffer, head and tail */
 	struct fasync_struct *fasync;
 	struct evdev *evdev;
 	struct list_head node;
 };
 
 static struct evdev *evdev_table[EVDEV_MINORS];
+static DEFINE_MUTEX(evdev_table_mutex);
 
-static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
+static void evdev_pass_event(struct evdev_client *client,
+			     struct input_event *event)
+{
+	/*
+	 * Interrupts are disabled, just acquire the lock
+	 */
+	spin_lock(&client->buffer_lock);
+	client->buffer[client->head++] = *event;
+	client->head &= EVDEV_BUFFER_SIZE - 1;
+	spin_unlock(&client->buffer_lock);
+
+	kill_fasync(&client->fasync, SIGIO, POLL_IN);
+}
+
+/*
+ * Pass incoming event to all connected clients. Note that we are
+ * caleld under a spinlock with interrupts off so we don't need
+ * to use rcu_read_lock() here. Writers will be using syncronize_sched()
+ * instead of synchrnoize_rcu().
+ */
+static void evdev_event(struct input_handle *handle,
+			unsigned int type, unsigned int code, int value)
 {
 	struct evdev *evdev = handle->private;
 	struct evdev_client *client;
+	struct input_event event;
 
-	if (evdev->grab) {
-		client = evdev->grab;
-
-		do_gettimeofday(&client->buffer[client->head].time);
-		client->buffer[client->head].type = type;
-		client->buffer[client->head].code = code;
-		client->buffer[client->head].value = value;
-		client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);
-
-		kill_fasync(&client->fasync, SIGIO, POLL_IN);
-	} else
-		list_for_each_entry(client, &evdev->client_list, node) {
-
-			do_gettimeofday(&client->buffer[client->head].time);
-			client->buffer[client->head].type = type;
-			client->buffer[client->head].code = code;
-			client->buffer[client->head].value = value;
-			client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);
-
-			kill_fasync(&client->fasync, SIGIO, POLL_IN);
-		}
+	do_gettimeofday(&event.time);
+	event.type = type;
+	event.code = code;
+	event.value = value;
+
+	client = rcu_dereference(evdev->grab);
+	if (client)
+		evdev_pass_event(client, &event);
+	else
+		list_for_each_entry_rcu(client, &evdev->client_list, node)
+			evdev_pass_event(client, &event);
 
 	wake_up_interruptible(&evdev->wait);
 }
@@ -88,38 +104,142 @@ static int evdev_flush(struct file *file
 {
 	struct evdev_client *client = file->private_data;
 	struct evdev *evdev = client->evdev;
+	int retval;
+
+	retval = mutex_lock_interruptible(&evdev->mutex);
+	if (retval)
+		return retval;
 
 	if (!evdev->exist)
-		return -ENODEV;
+		retval = -ENODEV;
+	else
+		retval = input_flush_device(&evdev->handle, file);
 
-	return input_flush_device(&evdev->handle, file);
+	mutex_unlock(&evdev->mutex);
+	return retval;
 }
 
 static void evdev_free(struct device *dev)
 {
 	struct evdev *evdev = container_of(dev, struct evdev, dev);
 
-	evdev_table[evdev->minor] = NULL;
 	kfree(evdev);
 }
 
+/*
+ * Grabs an event device (along with underlying input device).
+ * This function is called with evdev->mutex taken.
+ */
+static int evdev_grab(struct evdev *evdev, struct evdev_client *client)
+{
+	int error;
+
+	if (evdev->grab)
+		return -EBUSY;
+
+	error = input_grab_device(&evdev->handle);
+	if (error)
+		return error;
+
+	rcu_assign_pointer(evdev->grab, client);
+	/*
+	 * We don't use synchronize_rcu() here because read-size
+	 * critical section is protected by a spinlock instead
+	 * of rcu_read_lock().
+	 */
+	synchronize_sched();
+
+	return 0;
+}
+
+static int evdev_ungrab(struct evdev *evdev, struct evdev_client *client)
+{
+	if (evdev->grab != client)
+		return  -EINVAL;
+
+	rcu_assign_pointer(evdev->grab, NULL);
+	synchronize_sched();
+	input_release_device(&evdev->handle);
+
+	return 0;
+}
+
+static void evdev_attach_client(struct evdev *evdev,
+				struct evdev_client *client)
+{
+	spin_lock(&evdev->client_lock);
+	list_add_tail_rcu(&client->node, &evdev->client_list);
+	spin_unlock(&evdev->client_lock);
+	synchronize_sched();
+}
+
+static void evdev_detach_client(struct evdev *evdev,
+				struct evdev_client *client)
+{
+	spin_lock(&evdev->client_lock);
+	list_del_rcu(&client->node);
+	spin_unlock(&evdev->client_lock);
+	synchronize_sched();
+}
+
+static int evdev_open_device(struct evdev *evdev)
+{
+	int retval;
+
+	retval = mutex_lock_interruptible(&evdev->mutex);
+	if (retval)
+		return retval;
+
+	if (!evdev->exist)
+		retval = -ENODEV;
+	else if (!evdev->open++)
+		retval = input_open_device(&evdev->handle);
+
+	mutex_unlock(&evdev->mutex);
+	return retval;
+}
+
+static void evdev_close_device(struct evdev *evdev)
+{
+	mutex_lock(&evdev->mutex);
+
+	if (evdev->exist && !--evdev->open)
+		input_close_device(&evdev->handle);
+
+	mutex_unlock(&evdev->mutex);
+}
+
+/*
+ * Wake up users waiting for IO so they can disconnect from
+ * dead device.
+ */
+static void evdev_hangup(struct evdev *evdev)
+{
+	struct evdev_client *client;
+
+	spin_lock(&evdev->client_lock);
+	list_for_each_entry(client, &evdev->client_list, node)
+		kill_fasync(&client->fasync, SIGIO, POLL_HUP);
+	spin_unlock(&evdev->client_lock);
+
+	wake_up_interruptible(&evdev->wait);
+}
+
 static int evdev_release(struct inode *inode, struct file *file)
 {
 	struct evdev_client *client = file->private_data;
 	struct evdev *evdev = client->evdev;
 
-	if (evdev->grab == client) {
-		input_release_device(&evdev->handle);
-		evdev->grab = NULL;
-	}
+	mutex_lock(&evdev->mutex);
+	if (evdev->grab == client)
+		evdev_ungrab(evdev, client);
+	mutex_unlock(&evdev->mutex);
 
 	evdev_fasync(-1, file, 0);
-	list_del(&client->node);
+	evdev_detach_client(evdev, client);
 	kfree(client);
 
-	if (!--evdev->open && evdev->exist)
-		input_close_device(&evdev->handle);
-
+	evdev_close_device(evdev);
 	put_device(&evdev->dev);
 
 	return 0;
@@ -127,41 +247,44 @@ static int evdev_release(struct inode *i
 
 static int evdev_open(struct inode *inode, struct file *file)
 {
-	struct evdev_client *client;
 	struct evdev *evdev;
+	struct evdev_client *client;
 	int i = iminor(inode) - EVDEV_MINOR_BASE;
 	int error;
 
 	if (i >= EVDEV_MINORS)
 		return -ENODEV;
 
+	error = mutex_lock_interruptible(&evdev_table_mutex);
+	if (error)
+		return error;
 	evdev = evdev_table[i];
+	if (evdev)
+		get_device(&evdev->dev);
+	mutex_unlock(&evdev_table_mutex);
 
-	if (!evdev || !evdev->exist)
+	if (!evdev)
 		return -ENODEV;
 
-	get_device(&evdev->dev);
-
 	client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
 	if (!client) {
 		error = -ENOMEM;
 		goto err_put_evdev;
 	}
 
+	spin_lock_init(&client->buffer_lock);
 	client->evdev = evdev;
-	list_add_tail(&client->node, &evdev->client_list);
+	evdev_attach_client(evdev, client);
 
-	if (!evdev->open++ && evdev->exist) {
-		error = input_open_device(&evdev->handle);
-		if (error)
-			goto err_free_client;
-	}
+	error = evdev_open_device(evdev);
+	if (error)
+		goto err_free_client;
 
 	file->private_data = client;
 	return 0;
 
  err_free_client:
-	list_del(&client->node);
+	evdev_detach_client(evdev, client);
 	kfree(client);
  err_put_evdev:
 	put_device(&evdev->dev);
@@ -197,12 +320,14 @@ static inline size_t evdev_event_size(vo
 		sizeof(struct input_event_compat) : sizeof(struct input_event);
 }
 
-static int evdev_event_from_user(const char __user *buffer, struct input_event *event)
+static int evdev_event_from_user(const char __user *buffer,
+				 struct input_event *event)
 {
 	if (COMPAT_TEST) {
 		struct input_event_compat compat_event;
 
-		if (copy_from_user(&compat_event, buffer, sizeof(struct input_event_compat)))
+		if (copy_from_user(&compat_event, buffer,
+				   sizeof(struct input_event_compat)))
 			return -EFAULT;
 
 		event->time.tv_sec = compat_event.time.tv_sec;
@@ -219,7 +344,8 @@ static int evdev_event_from_user(const c
 	return 0;
 }
 
-static int evdev_event_to_user(char __user *buffer, const struct input_event *event)
+static int evdev_event_to_user(char __user *buffer,
+				const struct input_event *event)
 {
 	if (COMPAT_TEST) {
 		struct input_event_compat compat_event;
@@ -230,7 +356,8 @@ static int evdev_event_to_user(char __us
 		compat_event.code = event->code;
 		compat_event.value = event->value;
 
-		if (copy_to_user(buffer, &compat_event, sizeof(struct input_event_compat)))
+		if (copy_to_user(buffer, &compat_event,
+				 sizeof(struct input_event_compat)))
 			return -EFAULT;
 
 	} else {
@@ -248,7 +375,8 @@ static inline size_t evdev_event_size(vo
 	return sizeof(struct input_event);
 }
 
-static int evdev_event_from_user(const char __user *buffer, struct input_event *event)
+static int evdev_event_from_user(const char __user *buffer,
+				 struct input_event *event)
 {
 	if (copy_from_user(event, buffer, sizeof(struct input_event)))
 		return -EFAULT;
@@ -256,7 +384,8 @@ static int evdev_event_from_user(const c
 	return 0;
 }
 
-static int evdev_event_to_user(char __user *buffer, const struct input_event *event)
+static int evdev_event_to_user(char __user *buffer,
+				const struct input_event *event)
 {
 	if (copy_to_user(buffer, event, sizeof(struct input_event)))
 		return -EFAULT;
@@ -266,37 +395,71 @@ static int evdev_event_to_user(char __us
 
 #endif /* CONFIG_COMPAT */
 
-static ssize_t evdev_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
+static ssize_t evdev_write(struct file *file, const char __user *buffer,
+			   size_t count, loff_t *ppos)
 {
 	struct evdev_client *client = file->private_data;
 	struct evdev *evdev = client->evdev;
 	struct input_event event;
-	int retval = 0;
+	int retval;
 
-	if (!evdev->exist)
-		return -ENODEV;
+	retval = mutex_lock_interruptible(&evdev->mutex);
+	if (retval)
+		return retval;
+
+	if (!evdev->exist) {
+		retval = -ENODEV;
+		goto out;
+	}
 
 	while (retval < count) {
 
-		if (evdev_event_from_user(buffer + retval, &event))
-			return -EFAULT;
-		input_inject_event(&evdev->handle, event.type, event.code, event.value);
+		if (evdev_event_from_user(buffer + retval, &event)) {
+			retval = -EFAULT;
+			goto out;
+		}
+
+		input_inject_event(&evdev->handle,
+				   event.type, event.code, event.value);
 		retval += evdev_event_size();
 	}
 
+ out:
+	mutex_unlock(&evdev->mutex);
 	return retval;
 }
 
-static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
+static int evdev_fetch_next_event(struct evdev_client *client,
+				  struct input_event *event)
+{
+	int have_event;
+
+	spin_lock_irq(&client->buffer_lock);
+
+	have_event = client->head != client->tail;
+	if (have_event) {
+		*event = client->buffer[client->tail++];
+		client->tail &= EVDEV_BUFFER_SIZE - 1;
+	}
+
+	spin_unlock_irq(&client->buffer_lock);
+
+	return have_event;
+}
+
+static ssize_t evdev_read(struct file *file, char __user *buffer,
+			  size_t count, loff_t *ppos)
 {
 	struct evdev_client *client = file->private_data;
 	struct evdev *evdev = client->evdev;
+	struct input_event event;
 	int retval;
 
 	if (count < evdev_event_size())
 		return -EINVAL;
 
-	if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
+	if (client->head == client->tail && evdev->exist &&
+	    (file->f_flags & O_NONBLOCK))
 		return -EAGAIN;
 
 	retval = wait_event_interruptible(evdev->wait,
@@ -307,14 +470,12 @@ static ssize_t evdev_read(struct file *f
 	if (!evdev->exist)
 		return -ENODEV;
 
-	while (client->head != client->tail && retval + evdev_event_size() <= count) {
-
-		struct input_event *event = (struct input_event *) client->buffer + client->tail;
+	while (retval + evdev_event_size() <= count &&
+	       evdev_fetch_next_event(client, &event)) {
 
-		if (evdev_event_to_user(buffer + retval, event))
+		if (evdev_event_to_user(buffer + retval, &event))
 			return -EFAULT;
 
-		client->tail = (client->tail + 1) & (EVDEV_BUFFER_SIZE - 1);
 		retval += evdev_event_size();
 	}
 
@@ -409,8 +570,8 @@ static int str_to_user(const char *str, 
 	return copy_to_user(p, str, len) ? -EFAULT : len;
 }
 
-static long evdev_ioctl_handler(struct file *file, unsigned int cmd,
-				void __user *p, int compat_mode)
+static long evdev_do_ioctl(struct file *file, unsigned int cmd,
+			   void __user *p, int compat_mode)
 {
 	struct evdev_client *client = file->private_data;
 	struct evdev *evdev = client->evdev;
@@ -421,186 +582,208 @@ static long evdev_ioctl_handler(struct f
 	int i, t, u, v;
 	int error;
 
-	if (!evdev->exist)
-		return -ENODEV;
-
 	switch (cmd) {
 
-		case EVIOCGVERSION:
-			return put_user(EV_VERSION, ip);
+	case EVIOCGVERSION:
+		return put_user(EV_VERSION, ip);
 
-		case EVIOCGID:
-			if (copy_to_user(p, &dev->id, sizeof(struct input_id)))
-				return -EFAULT;
-			return 0;
+	case EVIOCGID:
+		if (copy_to_user(p, &dev->id, sizeof(struct input_id)))
+			return -EFAULT;
+		return 0;
 
-		case EVIOCGREP:
-			if (!test_bit(EV_REP, dev->evbit))
-				return -ENOSYS;
-			if (put_user(dev->rep[REP_DELAY], ip))
-				return -EFAULT;
-			if (put_user(dev->rep[REP_PERIOD], ip + 1))
-				return -EFAULT;
-			return 0;
+	case EVIOCGREP:
+		if (!test_bit(EV_REP, dev->evbit))
+			return -ENOSYS;
+		if (put_user(dev->rep[REP_DELAY], ip))
+			return -EFAULT;
+		if (put_user(dev->rep[REP_PERIOD], ip + 1))
+			return -EFAULT;
+		return 0;
 
-		case EVIOCSREP:
-			if (!test_bit(EV_REP, dev->evbit))
-				return -ENOSYS;
-			if (get_user(u, ip))
-				return -EFAULT;
-			if (get_user(v, ip + 1))
-				return -EFAULT;
+	case EVIOCSREP:
+		if (!test_bit(EV_REP, dev->evbit))
+			return -ENOSYS;
+		if (get_user(u, ip))
+			return -EFAULT;
+		if (get_user(v, ip + 1))
+			return -EFAULT;
 
-			input_inject_event(&evdev->handle, EV_REP, REP_DELAY, u);
-			input_inject_event(&evdev->handle, EV_REP, REP_PERIOD, v);
+		input_inject_event(&evdev->handle, EV_REP, REP_DELAY, u);
+		input_inject_event(&evdev->handle, EV_REP, REP_PERIOD, v);
 
-			return 0;
+		return 0;
 
-		case EVIOCGKEYCODE:
-			if (get_user(t, ip))
-				return -EFAULT;
+	case EVIOCGKEYCODE:
+		if (get_user(t, ip))
+			return -EFAULT;
 
-			error = dev->getkeycode(dev, t, &v);
-			if (error)
-				return error;
+		error = dev->getkeycode(dev, t, &v);
+		if (error)
+			return error;
 
-			if (put_user(v, ip + 1))
-				return -EFAULT;
+		if (put_user(v, ip + 1))
+			return -EFAULT;
 
-			return 0;
+		return 0;
 
-		case EVIOCSKEYCODE:
-			if (get_user(t, ip) || get_user(v, ip + 1))
-				return -EFAULT;
+	case EVIOCSKEYCODE:
+		if (get_user(t, ip) || get_user(v, ip + 1))
+			return -EFAULT;
 
-			return dev->setkeycode(dev, t, v);
+		return dev->setkeycode(dev, t, v);
 
-		case EVIOCSFF:
-			if (copy_from_user(&effect, p, sizeof(effect)))
-				return -EFAULT;
+	case EVIOCSFF:
+		if (copy_from_user(&effect, p, sizeof(effect)))
+			return -EFAULT;
 
-			error = input_ff_upload(dev, &effect, file);
+		error = input_ff_upload(dev, &effect, file);
 
-			if (put_user(effect.id, &(((struct ff_effect __user *)p)->id)))
-				return -EFAULT;
+		if (put_user(effect.id, &(((struct ff_effect __user *)p)->id)))
+			return -EFAULT;
 
-			return error;
+		return error;
 
-		case EVIOCRMFF:
-			return input_ff_erase(dev, (int)(unsigned long) p, file);
+	case EVIOCRMFF:
+		return input_ff_erase(dev, (int)(unsigned long) p, file);
 
-		case EVIOCGEFFECTS:
-			i = test_bit(EV_FF, dev->evbit) ? dev->ff->max_effects : 0;
-			if (put_user(i, ip))
-				return -EFAULT;
-			return 0;
+	case EVIOCGEFFECTS:
+		i = test_bit(EV_FF, dev->evbit) ?
+				dev->ff->max_effects : 0;
+		if (put_user(i, ip))
+			return -EFAULT;
+		return 0;
 
-		case EVIOCGRAB:
-			if (p) {
-				if (evdev->grab)
-					return -EBUSY;
-				if (input_grab_device(&evdev->handle))
-					return -EBUSY;
-				evdev->grab = client;
-				return 0;
-			} else {
-				if (evdev->grab != client)
-					return -EINVAL;
-				input_release_device(&evdev->handle);
-				evdev->grab = NULL;
-				return 0;
+	case EVIOCGRAB:
+		if (p)
+			return evdev_grab(evdev, client);
+		else
+			return evdev_ungrab(evdev, client);
+
+	default:
+
+		if (_IOC_TYPE(cmd) != 'E')
+			return -EINVAL;
+
+		if (_IOC_DIR(cmd) == _IOC_READ) {
+
+			if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0, 0))) {
+
+				unsigned long *bits;
+				int len;
+
+				switch (_IOC_NR(cmd) & EV_MAX) {
+
+				case      0: bits = dev->evbit;  len = EV_MAX;  break;
+				case EV_KEY: bits = dev->keybit; len = KEY_MAX; break;
+				case EV_REL: bits = dev->relbit; len = REL_MAX; break;
+				case EV_ABS: bits = dev->absbit; len = ABS_MAX; break;
+				case EV_MSC: bits = dev->mscbit; len = MSC_MAX; break;
+				case EV_LED: bits = dev->ledbit; len = LED_MAX; break;
+				case EV_SND: bits = dev->sndbit; len = SND_MAX; break;
+				case EV_FF:  bits = dev->ffbit;  len = FF_MAX;  break;
+				case EV_SW:  bits = dev->swbit;  len = SW_MAX;  break;
+				default: return -EINVAL;
+			}
+				return bits_to_user(bits, len, _IOC_SIZE(cmd), p, compat_mode);
 			}
 
-		default:
+			if (_IOC_NR(cmd) == _IOC_NR(EVIOCGKEY(0)))
+				return bits_to_user(dev->key, KEY_MAX, _IOC_SIZE(cmd),
+						    p, compat_mode);
 
-			if (_IOC_TYPE(cmd) != 'E')
-				return -EINVAL;
+			if (_IOC_NR(cmd) == _IOC_NR(EVIOCGLED(0)))
+				return bits_to_user(dev->led, LED_MAX, _IOC_SIZE(cmd),
+						    p, compat_mode);
 
-			if (_IOC_DIR(cmd) == _IOC_READ) {
+			if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSND(0)))
+				return bits_to_user(dev->snd, SND_MAX, _IOC_SIZE(cmd),
+						    p, compat_mode);
 
-				if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0,0))) {
+			if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSW(0)))
+				return bits_to_user(dev->sw, SW_MAX, _IOC_SIZE(cmd),
+						    p, compat_mode);
 
-					unsigned long *bits;
-					int len;
+			if (_IOC_NR(cmd) == _IOC_NR(EVIOCGNAME(0)))
+				return str_to_user(dev->name, _IOC_SIZE(cmd), p);
 
-					switch (_IOC_NR(cmd) & EV_MAX) {
-						case      0: bits = dev->evbit;  len = EV_MAX;  break;
-						case EV_KEY: bits = dev->keybit; len = KEY_MAX; break;
-						case EV_REL: bits = dev->relbit; len = REL_MAX; break;
-						case EV_ABS: bits = dev->absbit; len = ABS_MAX; break;
-						case EV_MSC: bits = dev->mscbit; len = MSC_MAX; break;
-						case EV_LED: bits = dev->ledbit; len = LED_MAX; break;
-						case EV_SND: bits = dev->sndbit; len = SND_MAX; break;
-						case EV_FF:  bits = dev->ffbit;  len = FF_MAX;  break;
-						case EV_SW:  bits = dev->swbit;  len = SW_MAX;  break;
-						default: return -EINVAL;
-					}
-					return bits_to_user(bits, len, _IOC_SIZE(cmd), p, compat_mode);
-				}
+			if (_IOC_NR(cmd) == _IOC_NR(EVIOCGPHYS(0)))
+				return str_to_user(dev->phys, _IOC_SIZE(cmd), p);
 
-				if (_IOC_NR(cmd) == _IOC_NR(EVIOCGKEY(0)))
-					return bits_to_user(dev->key, KEY_MAX, _IOC_SIZE(cmd),
-							    p, compat_mode);
+			if (_IOC_NR(cmd) == _IOC_NR(EVIOCGUNIQ(0)))
+				return str_to_user(dev->uniq, _IOC_SIZE(cmd), p);
 
-				if (_IOC_NR(cmd) == _IOC_NR(EVIOCGLED(0)))
-					return bits_to_user(dev->led, LED_MAX, _IOC_SIZE(cmd),
-							    p, compat_mode);
+			if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {
 
-				if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSND(0)))
-					return bits_to_user(dev->snd, SND_MAX, _IOC_SIZE(cmd),
-							    p, compat_mode);
+				t = _IOC_NR(cmd) & ABS_MAX;
 
-				if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSW(0)))
-					return bits_to_user(dev->sw, SW_MAX, _IOC_SIZE(cmd),
-							    p, compat_mode);
+				abs.value = dev->abs[t];
+				abs.minimum = dev->absmin[t];
+				abs.maximum = dev->absmax[t];
+				abs.fuzz = dev->absfuzz[t];
+				abs.flat = dev->absflat[t];
 
-				if (_IOC_NR(cmd) == _IOC_NR(EVIOCGNAME(0)))
-					return str_to_user(dev->name, _IOC_SIZE(cmd), p);
+				if (copy_to_user(p, &abs, sizeof(struct input_absinfo)))
+					return -EFAULT;
 
-				if (_IOC_NR(cmd) == _IOC_NR(EVIOCGPHYS(0)))
-					return str_to_user(dev->phys, _IOC_SIZE(cmd), p);
+				return 0;
+			}
 
-				if (_IOC_NR(cmd) == _IOC_NR(EVIOCGUNIQ(0)))
-					return str_to_user(dev->uniq, _IOC_SIZE(cmd), p);
+		}
 
-				if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {
+		if (_IOC_DIR(cmd) == _IOC_WRITE) {
 
-					t = _IOC_NR(cmd) & ABS_MAX;
+			if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) {
 
-					abs.value = dev->abs[t];
-					abs.minimum = dev->absmin[t];
-					abs.maximum = dev->absmax[t];
-					abs.fuzz = dev->absfuzz[t];
-					abs.flat = dev->absflat[t];
+				t = _IOC_NR(cmd) & ABS_MAX;
 
-					if (copy_to_user(p, &abs, sizeof(struct input_absinfo)))
-						return -EFAULT;
+				if (copy_from_user(&abs, p,
+						sizeof(struct input_absinfo)))
+					return -EFAULT;
+
+				/*
+				 * Take event lock to ensure that we are not
+				 * changing device parameters in the middle
+				 * of event.
+				 */
+				spin_lock_irq(&dev->event_lock);
+
+				dev->abs[t] = abs.value;
+				dev->absmin[t] = abs.minimum;
+				dev->absmax[t] = abs.maximum;
+				dev->absfuzz[t] = abs.fuzz;
+				dev->absflat[t] = abs.flat;
 
-					return 0;
-				}
+				spin_unlock_irq(&dev->event_lock);
 
+				return 0;
 			}
+		}
+	}
+	return -EINVAL;
+}
 
-			if (_IOC_DIR(cmd) == _IOC_WRITE) {
-
-				if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) {
+static long evdev_ioctl_handler(struct file *file, unsigned int cmd,
+				void __user *p, int compat_mode)
+{
+	struct evdev_client *client = file->private_data;
+	struct evdev *evdev = client->evdev;
+	int retval;
 
-					t = _IOC_NR(cmd) & ABS_MAX;
+	retval = mutex_lock_interruptible(&evdev->mutex);
+	if (retval)
+		return retval;
 
-					if (copy_from_user(&abs, p, sizeof(struct input_absinfo)))
-						return -EFAULT;
+	if (!evdev->exist) {
+		retval = -ENODEV;
+		goto out;
+	}
 
-					dev->abs[t] = abs.value;
-					dev->absmin[t] = abs.minimum;
-					dev->absmax[t] = abs.maximum;
-					dev->absfuzz[t] = abs.fuzz;
-					dev->absflat[t] = abs.flat;
+	retval = evdev_do_ioctl(file, cmd, p, compat_mode);
 
-					return 0;
-				}
-			}
-	}
-	return -EINVAL;
+ out:
+	mutex_unlock(&evdev->mutex);
+	return retval;
 }
 
 static long evdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
@@ -609,27 +792,79 @@ static long evdev_ioctl(struct file *fil
 }
 
 #ifdef CONFIG_COMPAT
-static long evdev_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+static long evdev_ioctl_compat(struct file *file,
+				unsigned int cmd, unsigned long arg)
 {
 	return evdev_ioctl_handler(file, cmd, compat_ptr(arg), 1);
 }
 #endif
 
 static const struct file_operations evdev_fops = {
-	.owner =	THIS_MODULE,
-	.read =		evdev_read,
-	.write =	evdev_write,
-	.poll =		evdev_poll,
-	.open =		evdev_open,
-	.release =	evdev_release,
-	.unlocked_ioctl = evdev_ioctl,
+	.owner		= THIS_MODULE,
+	.read		= evdev_read,
+	.write		= evdev_write,
+	.poll		= evdev_poll,
+	.open		= evdev_open,
+	.release	= evdev_release,
+	.unlocked_ioctl	= evdev_ioctl,
 #ifdef CONFIG_COMPAT
-	.compat_ioctl =	evdev_ioctl_compat,
+	.compat_ioctl	= evdev_ioctl_compat,
 #endif
-	.fasync =	evdev_fasync,
-	.flush =	evdev_flush
+	.fasync		= evdev_fasync,
+	.flush		= evdev_flush
 };
 
+static int evdev_install_chrdev(struct evdev *evdev)
+{
+	/*
+	 * No need to do any locking here as calls to connect and
+	 * disconnect are serialized by the input core
+	 */
+	evdev_table[evdev->minor] = evdev;
+	return 0;
+}
+
+static void evdev_remove_chrdev(struct evdev *evdev)
+{
+	/*
+	 * Lock evdev table to prevent race with evdev_open()
+	 */
+	mutex_lock(&evdev_table_mutex);
+	evdev_table[evdev->minor] = NULL;
+	mutex_unlock(&evdev_table_mutex);
+}
+
+/*
+ * Mark device non-existent. This disables writes, ioctls and
+ * prevents new users from opening the device. Already posted
+ * blocking reads will stay, however new ones will fail.
+ */
+static void evdev_mark_dead(struct evdev *evdev)
+{
+	mutex_lock(&evdev->mutex);
+	evdev->exist = 0;
+	mutex_unlock(&evdev->mutex);
+}
+
+static void evdev_cleanup(struct evdev *evdev)
+{
+	struct input_handle *handle = &evdev->handle;
+
+	evdev_mark_dead(evdev);
+	evdev_hangup(evdev);
+	evdev_remove_chrdev(evdev);
+
+	/* evdev is marked dead so no one else accesses evdev->open */
+	if (evdev->open) {
+		input_flush_device(handle, NULL);
+		input_close_device(handle);
+	}
+}
+
+/*
+ * Create new evdev device. Note that input core serializes calls
+ * to connect and disconnect so we don't need to lock evdev_table here.
+ */
 static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
 			 const struct input_device_id *id)
 {
@@ -637,7 +872,10 @@ static int evdev_connect(struct input_ha
 	int minor;
 	int error;
 
-	for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);
+	for (minor = 0; minor < EVDEV_MINORS; minor++)
+		if (!evdev_table[minor])
+			break;
+
 	if (minor == EVDEV_MINORS) {
 		printk(KERN_ERR "evdev: no more free evdev devices\n");
 		return -ENFILE;
@@ -648,38 +886,44 @@ static int evdev_connect(struct input_ha
 		return -ENOMEM;
 
 	INIT_LIST_HEAD(&evdev->client_list);
+	spin_lock_init(&evdev->client_lock);
+	mutex_init(&evdev->mutex);
 	init_waitqueue_head(&evdev->wait);
 
+	snprintf(evdev->name, sizeof(evdev->name), "event%d", minor);
 	evdev->exist = 1;
 	evdev->minor = minor;
+
 	evdev->handle.dev = dev;
 	evdev->handle.name = evdev->name;
 	evdev->handle.handler = handler;
 	evdev->handle.private = evdev;
-	snprintf(evdev->name, sizeof(evdev->name), "event%d", minor);
 
-	snprintf(evdev->dev.bus_id, sizeof(evdev->dev.bus_id),
-		 "event%d", minor);
+	strlcpy(evdev->dev.bus_id, evdev->name, sizeof(evdev->dev.bus_id));
+	evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
 	evdev->dev.class = &input_class;
 	evdev->dev.parent = &dev->dev;
-	evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
 	evdev->dev.release = evdev_free;
 	device_initialize(&evdev->dev);
 
-	evdev_table[minor] = evdev;
-
-	error = device_add(&evdev->dev);
+	error = input_register_handle(&evdev->handle);
 	if (error)
 		goto err_free_evdev;
 
-	error = input_register_handle(&evdev->handle);
+	error = evdev_install_chrdev(evdev);
+	if (error)
+		goto err_unregister_handle;
+
+	error = device_add(&evdev->dev);
 	if (error)
-		goto err_delete_evdev;
+		goto err_cleanup_evdev;
 
 	return 0;
 
- err_delete_evdev:
-	device_del(&evdev->dev);
+ err_cleanup_evdev:
+	evdev_cleanup(evdev);
+ err_unregister_handle:
+	input_unregister_handle(&evdev->handle);
  err_free_evdev:
 	put_device(&evdev->dev);
 	return error;
@@ -688,21 +932,10 @@ static int evdev_connect(struct input_ha
 static void evdev_disconnect(struct input_handle *handle)
 {
 	struct evdev *evdev = handle->private;
-	struct evdev_client *client;
 
-	input_unregister_handle(handle);
 	device_del(&evdev->dev);
-
-	evdev->exist = 0;
-
-	if (evdev->open) {
-		input_flush_device(handle, NULL);
-		input_close_device(handle);
-		list_for_each_entry(client, &evdev->client_list, node)
-			kill_fasync(&client->fasync, SIGIO, POLL_HUP);
-		wake_up_interruptible(&evdev->wait);
-	}
-
+	evdev_cleanup(evdev);
+	input_unregister_handle(handle);
 	put_device(&evdev->dev);
 }
 
@@ -714,13 +947,13 @@ static const struct input_device_id evde
 MODULE_DEVICE_TABLE(input, evdev_ids);
 
 static struct input_handler evdev_handler = {
-	.event =	evdev_event,
-	.connect =	evdev_connect,
-	.disconnect =	evdev_disconnect,
-	.fops =		&evdev_fops,
-	.minor =	EVDEV_MINOR_BASE,
-	.name =		"evdev",
-	.id_table =	evdev_ids,
+	.event		= evdev_event,
+	.connect	= evdev_connect,
+	.disconnect	= evdev_disconnect,
+	.fops		= &evdev_fops,
+	.minor		= EVDEV_MINOR_BASE,
+	.name		= "evdev",
+	.id_table	= evdev_ids,
 };
 
 static int __init evdev_init(void)
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux