What is the purpose of fix-EVIOCGRAB-semantics.patch ??

Neil Brown neilb at suse.de
Wed Aug 13 12:21:45 CEST 2008

On Wednesday August 13, mickey at openmoko.org wrote:
> Am Mittwoch 13 August 2008 09:13:43 schrieb Neil Brown:
> > Hi I just spent some time trying to figure out why the EVIOCGRAB ioctl
> > wasn't behaving was I expected and I finally stumbled on to this
> > patch, which substantially changes the behaviour of EVIOCGRAB.
> >
> > Would it be possible to find out what the purpose of this patch is (it
> > unfortunately contains no documentation)?
> See http://lkml.org/lkml/2006/8/12/64

Thanks for the pointer!  Interesting reading.

So this is a 2 year old issue (almost to the day!) which does not
appear to have been properly resolved yet.  Sad.

An input device (e.g. touch pad, key board) can be attached to
multiple handlers (/dev/mouseX, console, etc) one of which can be
evdev which provides the /dev/input/event* files.
evdev can support multiple clients on a single device, as multiple
processes can open /dev/input/eventX.  The each get to see all the

EVIOCGRAB is an ioctl on an evdev device which causes all events to go
to that handler, and that client, exclusively.

It is being used for two distinct purposes.

1/ Stop events going to other handlers, so that e.g. absolute touchpad 
   events don't get mapped to relative mouse events.  Keyboard events
   processed by X server don't get processed by console and control-C
   kills the X server.
2/ Stop events going to any other client, so they can e.g. be mapped
   and fed back into a uinput device,

So there are three levels of grab that are required:

 0/ no grab : all events go to all handlers and all clients
 1/ evdev grab: events only go to evdev, but can then go to any
       client of evdev
 2/ total grab:  events only go to the specific evdev client that
       requested the grab.

0 is the default
mainline allows you to select 2 but not 1.
this patch allows you to select 1, but not 2.

It seems that the "obvious" thing to do is to interpret the argument
to EVIOCGRAB to select between these options.
We cannot use 0, 1, 2 as above - despite the fact that it makes sense
- because that would be a regression: working programs would break.
Maybe it's best to use 0, 2, 1 and document it clearly.  The chance of
anyone using 2 already has got to be very close to zero.

We would then need to change the drivers in the X server to use '2'
rather than '1', and (I think) everyone would be happy.

Below is my proposed patch which provides the functionality of 
the patch in question without causing any regressions.
It also fixes two minor buglets in the original.

If anyone would like to comment, I'd appreciate it.  If I hear nothing
bad I'll propose it to Dimitry Torokhov on some appropriate list.


diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index 2d65411..94e507f 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -29,6 +29,7 @@ struct evdev {
 	struct input_handle handle;
 	wait_queue_head_t wait;
 	struct evdev_client *grab;
+	int grabcnt;
 	struct list_head client_list;
 	spinlock_t client_lock; /* protects client_list */
 	struct mutex mutex;
@@ -39,6 +40,7 @@ struct evdev_client {
 	struct input_event buffer[EVDEV_BUFFER_SIZE];
 	int head;
 	int tail;
+	int grab;
 	spinlock_t buffer_lock; /* protects access to buffer, head and tail */
 	struct fasync_struct *fasync;
 	struct evdev *evdev;
@@ -129,17 +131,38 @@ static void evdev_free(struct device *dev)
+ * Grabs an underlying input device for use by evdev only.
+ */
+static int evdev_soft_grab(struct evdev *evdev, struct evdev_client *client)
+	int error;
+	if (client->grab)
+		return -EBUSY;
+	if (!evdev->grabcnt) {
+	    error = input_grab_device(&evdev->handle);
+	    if (error)
+		    return error;
+	}
+	evdev->grabcnt++;
+	client->grab = 1;
+	return 0;
  * 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)
+static int evdev_hard_grab(struct evdev *evdev, struct evdev_client *client)
 	int error;
 	if (evdev->grab)
 		return -EBUSY;
-	error = input_grab_device(&evdev->handle);
+	error = evdev_soft_grab(evdev, client);
 	if (error)
 		return error;
@@ -151,12 +174,17 @@ static int evdev_grab(struct evdev *evdev, struct evdev_client *client)
 static int evdev_ungrab(struct evdev *evdev, struct evdev_client *client)
-	if (evdev->grab != client)
+	if (!client->grab)
 		return  -EINVAL;
-	rcu_assign_pointer(evdev->grab, NULL);
-	synchronize_rcu();
-	input_release_device(&evdev->handle);
+	if (evdev->grab == client) {
+		rcu_assign_pointer(evdev->grab, NULL);
+		synchronize_rcu();
+	}
+	if (!--evdev->grabcnt && evdev->exist)
+		input_release_device(&evdev->handle);
+	client->grab = 0;
 	return 0;
@@ -231,7 +259,7 @@ static int evdev_release(struct inode *inode, struct file *file)
 	struct evdev *evdev = client->evdev;
-	if (evdev->grab == client)
+	if (client->grab)
 		evdev_ungrab(evdev, client);
@@ -721,10 +749,12 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
 		return 0;
-		if (p)
-			return evdev_grab(evdev, client);
-		else
-			return evdev_ungrab(evdev, client);
+		switch (p) {
+		case 0: return evdev_ungrab(evdev, client);
+		case 1: return evdev_hard_grab(evdev, client);
+		case 2: return evdev_soft_grab(evdev, client);
+		default: return -EINVAL;
+		}

More information about the openmoko-kernel mailing list