Input: uinput - flush all pending ff effects before destroying device

The destruction of a input device in uinput is triggered by an ioctl().
If a process tries to destroy an input device while other is uploading a
force feedback effect by evdev to the same device, they'll deadlock.
This patch fixes the problem by flushing all pending FF uploads before
destroying the device and preventing new uploads during this operation.

[dtor@mail.ru: fix logic that ensures we don't submit new requests
 to the device that is being destroyed.]
Signed-off-by: Aristeu Rozanski <aris@redhat.com>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
This commit is contained in:
Aristeu Sergio Rozanski Filho 2009-05-14 22:01:57 -07:00 committed by Dmitry Torokhov
parent 3d5cb60ef3
commit 05cebd3816

View File

@ -54,27 +54,28 @@ static int uinput_dev_event(struct input_dev *dev, unsigned int type, unsigned i
return 0; return 0;
} }
/* Atomically allocate an ID for the given request. Returns 0 on success. */
static int uinput_request_alloc_id(struct uinput_device *udev, struct uinput_request *request) static int uinput_request_alloc_id(struct uinput_device *udev, struct uinput_request *request)
{ {
/* Atomically allocate an ID for the given request. Returns 0 on success. */
int id; int id;
int err = -1; int err = -1;
spin_lock(&udev->requests_lock); spin_lock(&udev->requests_lock);
for (id = 0; id < UINPUT_NUM_REQUESTS; id++) for (id = 0; id < UINPUT_NUM_REQUESTS; id++) {
if (!udev->requests[id]) { if (!udev->requests[id]) {
request->id = id; request->id = id;
udev->requests[id] = request; udev->requests[id] = request;
err = 0; err = 0;
break; break;
} }
}
spin_unlock(&udev->requests_lock); spin_unlock(&udev->requests_lock);
return err; return err;
} }
static struct uinput_request* uinput_request_find(struct uinput_device *udev, int id) static struct uinput_request *uinput_request_find(struct uinput_device *udev, int id)
{ {
/* Find an input request, by ID. Returns NULL if the ID isn't valid. */ /* Find an input request, by ID. Returns NULL if the ID isn't valid. */
if (id >= UINPUT_NUM_REQUESTS || id < 0) if (id >= UINPUT_NUM_REQUESTS || id < 0)
@ -99,14 +100,51 @@ static void uinput_request_done(struct uinput_device *udev, struct uinput_reques
complete(&request->done); complete(&request->done);
} }
static int uinput_request_submit(struct input_dev *dev, struct uinput_request *request) static int uinput_request_submit(struct uinput_device *udev, struct uinput_request *request)
{ {
/* Tell our userspace app about this new request by queueing an input event */ int retval;
uinput_dev_event(dev, EV_UINPUT, request->code, request->id);
/* Wait for the request to complete */ retval = uinput_request_reserve_slot(udev, request);
wait_for_completion(&request->done); if (retval)
return request->retval; return retval;
retval = mutex_lock_interruptible(&udev->mutex);
if (retval)
return retval;
if (udev->state != UIST_CREATED) {
retval = -ENODEV;
goto out;
}
/* Tell our userspace app about this new request by queueing an input event */
uinput_dev_event(udev->dev, EV_UINPUT, request->code, request->id);
out:
mutex_unlock(&udev->mutex);
return retval;
}
/*
* Fail all ouitstanding requests so handlers don't wait for the userspace
* to finish processing them.
*/
static void uinput_flush_requests(struct uinput_device *udev)
{
struct uinput_request *request;
int i;
spin_lock(&udev->requests_lock);
for (i = 0; i < UINPUT_NUM_REQUESTS; i++) {
request = udev->requests[i];
if (request) {
request->retval = -ENODEV;
uinput_request_done(udev, request);
}
}
spin_unlock(&udev->requests_lock);
} }
static void uinput_dev_set_gain(struct input_dev *dev, u16 gain) static void uinput_dev_set_gain(struct input_dev *dev, u16 gain)
@ -126,6 +164,7 @@ static int uinput_dev_playback(struct input_dev *dev, int effect_id, int value)
static int uinput_dev_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old) static int uinput_dev_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old)
{ {
struct uinput_device *udev = input_get_drvdata(dev);
struct uinput_request request; struct uinput_request request;
int retval; int retval;
@ -146,15 +185,18 @@ static int uinput_dev_upload_effect(struct input_dev *dev, struct ff_effect *eff
request.u.upload.effect = effect; request.u.upload.effect = effect;
request.u.upload.old = old; request.u.upload.old = old;
retval = uinput_request_reserve_slot(input_get_drvdata(dev), &request); retval = uinput_request_submit(udev, &request);
if (!retval) if (!retval) {
retval = uinput_request_submit(dev, &request); wait_for_completion(&request.done);
retval = request.retval;
}
return retval; return retval;
} }
static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id) static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id)
{ {
struct uinput_device *udev = input_get_drvdata(dev);
struct uinput_request request; struct uinput_request request;
int retval; int retval;
@ -166,9 +208,11 @@ static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id)
request.code = UI_FF_ERASE; request.code = UI_FF_ERASE;
request.u.effect_id = effect_id; request.u.effect_id = effect_id;
retval = uinput_request_reserve_slot(input_get_drvdata(dev), &request); retval = uinput_request_submit(udev, &request);
if (!retval) if (!retval) {
retval = uinput_request_submit(dev, &request); wait_for_completion(&request.done);
retval = request.retval;
}
return retval; return retval;
} }
@ -176,20 +220,24 @@ static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id)
static void uinput_destroy_device(struct uinput_device *udev) static void uinput_destroy_device(struct uinput_device *udev)
{ {
const char *name, *phys; const char *name, *phys;
struct input_dev *dev = udev->dev;
enum uinput_state old_state = udev->state;
if (udev->dev) { udev->state = UIST_NEW_DEVICE;
name = udev->dev->name;
phys = udev->dev->phys; if (dev) {
if (udev->state == UIST_CREATED) name = dev->name;
input_unregister_device(udev->dev); phys = dev->phys;
else if (old_state == UIST_CREATED) {
input_free_device(udev->dev); uinput_flush_requests(udev);
input_unregister_device(dev);
} else {
input_free_device(dev);
}
kfree(name); kfree(name);
kfree(phys); kfree(phys);
udev->dev = NULL; udev->dev = NULL;
} }
udev->state = UIST_NEW_DEVICE;
} }
static int uinput_create_device(struct uinput_device *udev) static int uinput_create_device(struct uinput_device *udev)