Re: [PATCH] Force feedback support for uinput

From: Micah Dowty
Date: Sun Nov 21 2004 - 03:47:14 EST


On Fri, Nov 12, 2004 at 10:09:12AM -0200, aris@xxxxxxxxxx wrote:
> > +#define EV_UINPUT 0x0101
> I guess it should be moved to input.h with other types of events.

Done. I didn't do this before since it isn't used anywhere other than
between uinput and its applications. I added a short note to this effect
in input.h

> > +/* To write a force-feedback-capable driver, the upload_effect
> > + * and erase_effect callbacks in input_dev must be implemented.
> > + * The uinput driver will generate a fake input event when one of
> > + * these callbacks are invoked. The userspace code then uses
> > + * ioctls to retrieve additional parameters and send the return code.
> > + * The callback blocks until this return code is sent.
> (snip)
> what about moving this long comment to Documentation/input/uinput.txt?

Also done. Since uinput.txt didn't have any documentation at all,
I added a short section on basic usage. It should be expanded, but it's
better than nothing.

> (p.s.: sorry for the delay and the dup that will follow. my server is
> offline due adsl problems and the first answer is stuck there :)

No problem at all, I've been really busy lately.

Here's the revised patch, also including Andrew Morton's cleanups:

Signed-off-by: Andrew Morton <akpm@xxxxxxxx>
Signed-off-by: Micah Dowty <micah@xxxxxxx>

diff -puNr linux-2.6.10-rc2-bk5/Documentation/input/uinput.txt linux-uinput-ff/Documentation/input/uinput.txt
--- linux-2.6.10-rc2-bk5/Documentation/input/uinput.txt 1969-12-31 17:00:00.000000000 -0700
+++ linux-uinput-ff/Documentation/input/uinput.txt 2004-11-20 20:41:08.794414600 -0700
@@ -0,0 +1,66 @@
+uinput: user level driver support for input subsystem
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+1. Basic use
+~~~~~~~~~~~~
+
+The uinput driver creates a character device, usually at /dev/uinput, that can
+be used to create Linux input devices from userspace. The rest of this text
+assumes you are already familiar with writing input drivers in kernelspace.
+
+The device's capabilities and identity are established primarily by write()'ing
+a struct uinput_device. Information not contained in this structure is provided
+with the UI_SET_* family of ioctl()s. After all this, UI_DEV_CREATE actually
+registers a new device with the input subsystem. Between UI_DEV_CREATE and
+UI_DEV_DESTROY, the UI_SET_* ioctl()s can't be used.
+
+Once the device has been created, /dev/uinput acts much like an event device.
+write() sends a struct input_event out of the device, read() fetches input
+events sent back to the driver by applications. Note that events written will
+also be echoed back on read.
+
+2. Force Feedback
+~~~~~~~~~~~~~~~~~
+
+To write a force-feedback-capable driver, the upload_effect and erase_effect
+callbacks in input_dev must be implemented. The uinput driver will generate a
+fake input event when one of these callbacks are invoked. The userspace code
+then uses ioctls to retrieve additional parameters and send the return code.
+The callback blocks until this return code is sent.
+
+The described callback mechanism is only used if EV_FF is set. Otherwise,
+default implementations of upload_effect and erase_effect are used.
+
+To implement upload_effect():
+
+ 1. Wait for an event with type==EV_UINPUT and code==UI_FF_UPLOAD. A request
+ ID will be given in 'value'.
+
+ 2. Allocate a uinput_ff_upload struct, fill in request_id with the 'value'
+ from the EV_UINPUT event.
+
+ 3. Issue a UI_BEGIN_FF_UPLOAD ioctl, giving it the uinput_ff_upload struct.
+ It will be filled in with the ff_effect passed to upload_effect().
+
+ 4. Perform the effect upload. Place the modified ff_effect and a return
+ code back into the uinput_ff_upload struct.
+
+ 5. Issue a UI_END_FF_UPLOAD ioctl, also giving it the uinput_ff_upload_effect
+ struct. This will complete execution of our upload_effect() handler.
+
+To implement erase_effect():
+
+ 1. Wait for an event with type==EV_UINPUT and code==UI_FF_ERASE. A request
+ ID will be given in 'value'.
+
+ 2. Allocate a uinput_ff_erase struct, fill in request_id with the 'value'
+ from the EV_UINPUT event.
+
+ 3. Issue a UI_BEGIN_FF_ERASE ioctl, giving it the uinput_ff_erase struct.
+ It will be filled in with the effect ID passed to erase_effect().
+
+ 4. Perform the effect erasure, and place a return code back into the
+ uinput_ff_erase struct.
+
+ 5. Issue a UI_END_FF_ERASE ioctl, also giving it the uinput_ff_erase_effect
+ struct. This will complete execution of our erase_effect() handler.
diff -puNr linux-2.6.10-rc2-bk5/drivers/input/misc/uinput.c linux-uinput-ff/drivers/input/misc/uinput.c
--- linux-2.6.10-rc2-bk5/drivers/input/misc/uinput.c 2004-10-18 15:54:55.000000000 -0600
+++ linux-uinput-ff/drivers/input/misc/uinput.c 2004-11-20 20:40:04.583176192 -0700
@@ -20,6 +20,9 @@
* Author: Aristeu Sergio Rozanski Filho <aris@xxxxxxxxxxxxxxxxx>
*
* Changes/Revisions:
+ * 0.2 16/10/2004 (Micah Dowty <micah@xxxxxxx>)
+ * - added force feedback support
+ * - added UI_SET_PHYS
* 0.1 20/06/2002
* - first public version
*/
@@ -47,7 +56,7 @@ static int uinput_dev_event(struct input
{
struct uinput_device *udev;

- udev = (struct uinput_device *)dev->private;
+ udev = dev->private;

udev->buff[udev->head].type = type;
udev->buff[udev->head].code = code;
@@ -60,14 +69,93 @@ static int uinput_dev_event(struct input
return 0;
}

+static int uinput_request_alloc_id(struct input_dev *dev, struct uinput_request *request)
+{
+ /* Atomically allocate an ID for the given request. Returns 0 on success. */
+ struct uinput_device *udev = dev->private;
+ int id;
+
+ down(&udev->requests_sem);
+ for (id=0; id<UINPUT_NUM_REQUESTS; id++)
+ if (!udev->requests[id]) {
+ udev->requests[id] = request;
+ request->id = id;
+ up(&udev->requests_sem);
+ return 0;
+ }
+ up(&udev->requests_sem);
+ return -1;
+}
+
+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. */
+ if (id >= UINPUT_NUM_REQUESTS || id < 0)
+ return NULL;
+ if (udev->requests[id]->completed)
+ return NULL;
+ return udev->requests[id];
+}
+
+static void uinput_request_init(struct input_dev *dev, struct uinput_request *request, int code)
+{
+ struct uinput_device *udev = dev->private;
+
+ memset(request, 0, sizeof(struct uinput_request));
+ request->code = code;
+ init_waitqueue_head(&request->waitq);
+
+ /* Allocate an ID. If none are available right away, wait. */
+ request->retval = wait_event_interruptible(udev->requests_waitq,
+ !uinput_request_alloc_id(dev, request));
+}
+
+static void uinput_request_submit(struct input_dev *dev, struct uinput_request *request)
+{
+ struct uinput_device *udev = dev->private;
+ int retval;
+
+ /* Tell our userspace app about this new request by queueing an input event */
+ uinput_dev_event(dev, EV_UINPUT, request->code, request->id);
+
+ /* Wait for the request to complete */
+ retval = wait_event_interruptible(request->waitq, request->completed);
+ if (retval)
+ request->retval = retval;
+
+ /* Release this request's ID, let others know it's available */
+ udev->requests[request->id] = NULL;
+ wake_up_interruptible(&udev->requests_waitq);
+}
+
static int uinput_dev_upload_effect(struct input_dev *dev, struct ff_effect *effect)
{
- return 0;
+ struct uinput_request request;
+
+ if (!test_bit(EV_FF, dev->evbit))
+ return -ENOSYS;
+
+ uinput_request_init(dev, &request, UI_FF_UPLOAD);
+ if (request.retval)
+ return request.retval;
+ request.u.effect = effect;
+ uinput_request_submit(dev, &request);
+ return request.retval;
}

static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id)
{
- return 0;
+ struct uinput_request request;
+
+ if (!test_bit(EV_FF, dev->evbit))
+ return -ENOSYS;
+
+ uinput_request_init(dev, &request, UI_FF_ERASE);
+ if (request.retval)
+ return request.retval;
+ request.u.effect_id = effect_id;
+ uinput_request_submit(dev, &request);
+ return request.retval;
}

static int uinput_create_device(struct uinput_device *udev)
@@ -116,6 +204,8 @@ static int uinput_open(struct inode *ino
if (!newdev)
goto error;
memset(newdev, 0, sizeof(struct uinput_device));
+ init_MUTEX(&newdev->requests_sem);
+ init_waitqueue_head(&newdev->requests_waitq);

newinput = kmalloc(sizeof(struct input_dev), GFP_KERNEL);
if (!newinput)
@@ -176,7 +266,7 @@ static int uinput_alloc_device(struct fi

retval = count;

- udev = (struct uinput_device *)file->private_data;
+ udev = file->private_data;
dev = udev->dev;

user_dev = kmalloc(sizeof(*user_dev), GFP_KERNEL);
@@ -228,7 +318,7 @@ exit:

static ssize_t uinput_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
{
- struct uinput_device *udev = file->private_data;
+ struct uinput_device *udev = file->private_data;

if (test_bit(UIST_CREATED, &(udev->state))) {
struct input_event ev;
@@ -295,6 +382,11 @@ static int uinput_burn_device(struct uin
if (test_bit(UIST_CREATED, &(udev->state)))
uinput_destroy_device(udev);

+ if (NULL != udev->dev->name)
+ kfree(udev->dev->name);
+ if (NULL != udev->dev->phys)
+ kfree(udev->dev->phys);
+
kfree(udev->dev);
kfree(udev);

@@ -303,19 +395,35 @@ static int uinput_burn_device(struct uin

static int uinput_close(struct inode *inode, struct file *file)
{
- return uinput_burn_device((struct uinput_device *)file->private_data);
+ return uinput_burn_device(file->private_data);
}

static int uinput_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int retval = 0;
struct uinput_device *udev;
+ void __user *p = (void __user *)arg;
+ struct uinput_ff_upload ff_up;
+ struct uinput_ff_erase ff_erase;
+ struct uinput_request *req;
+ int length;

- udev = (struct uinput_device *)file->private_data;
+ udev = file->private_data;

/* device attributes can not be changed after the device is created */
- if (cmd >= UI_SET_EVBIT && test_bit(UIST_CREATED, &(udev->state)))
- return -EINVAL;
+ switch (cmd) {
+ case UI_SET_EVBIT:
+ case UI_SET_KEYBIT:
+ case UI_SET_RELBIT:
+ case UI_SET_ABSBIT:
+ case UI_SET_MSCBIT:
+ case UI_SET_LEDBIT:
+ case UI_SET_SNDBIT:
+ case UI_SET_FFBIT:
+ case UI_SET_PHYS:
+ if (test_bit(UIST_CREATED, &(udev->state)))
+ return -EINVAL;
+ }

switch (cmd) {
case UI_DEV_CREATE:
@@ -390,8 +498,97 @@ static int uinput_ioctl(struct inode *in
set_bit(arg, udev->dev->ffbit);
break;

+ case UI_SET_PHYS:
+ length = strnlen_user(p, 1024);
+ if (length <= 0) {
+ retval = -EFAULT;
+ break;
+ }
+ if (NULL != udev->dev->phys)
+ kfree(udev->dev->phys);
+ udev->dev->phys = kmalloc(length, GFP_KERNEL);
+ if (!udev->dev->phys) {
+ retval = -ENOMEM;
+ break;
+ }
+ if (copy_from_user(udev->dev->phys, p, length)) {
+ retval = -EFAULT;
+ kfree(udev->dev->phys);
+ udev->dev->phys = NULL;
+ break;
+ }
+ udev->dev->phys[length-1] = '\0';
+ break;
+
+ case UI_BEGIN_FF_UPLOAD:
+ if (copy_from_user(&ff_up, p, sizeof(ff_up))) {
+ retval = -EFAULT;
+ break;
+ }
+ req = uinput_request_find(udev, ff_up.request_id);
+ if (!(req && req->code==UI_FF_UPLOAD && req->u.effect)) {
+ retval = -EINVAL;
+ break;
+ }
+ ff_up.retval = 0;
+ memcpy(&ff_up.effect, req->u.effect, sizeof(struct ff_effect));
+ if (copy_to_user(p, &ff_up, sizeof(ff_up))) {
+ retval = -EFAULT;
+ break;
+ }
+ break;
+
+ case UI_BEGIN_FF_ERASE:
+ if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) {
+ retval = -EFAULT;
+ break;
+ }
+ req = uinput_request_find(udev, ff_erase.request_id);
+ if (!(req && req->code==UI_FF_ERASE)) {
+ retval = -EINVAL;
+ break;
+ }
+ ff_erase.retval = 0;
+ ff_erase.effect_id = req->u.effect_id;
+ if (copy_to_user(p, &ff_erase, sizeof(ff_erase))) {
+ retval = -EFAULT;
+ break;
+ }
+ break;
+
+ case UI_END_FF_UPLOAD:
+ if (copy_from_user(&ff_up, p, sizeof(ff_up))) {
+ retval = -EFAULT;
+ break;
+ }
+ req = uinput_request_find(udev, ff_up.request_id);
+ if (!(req && req->code==UI_FF_UPLOAD && req->u.effect)) {
+ retval = -EINVAL;
+ break;
+ }
+ req->retval = ff_up.retval;
+ memcpy(req->u.effect, &ff_up.effect, sizeof(struct ff_effect));
+ req->completed = 1;
+ wake_up_interruptible(&req->waitq);
+ break;
+
+ case UI_END_FF_ERASE:
+ if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) {
+ retval = -EFAULT;
+ break;
+ }
+ req = uinput_request_find(udev, ff_erase.request_id);
+ if (!(req && req->code==UI_FF_ERASE)) {
+ retval = -EINVAL;
+ break;
+ }
+ req->retval = ff_erase.retval;
+ req->completed = 1;
+ wake_up_interruptible(&req->waitq);
+ break;
+
default:
- retval = -EFAULT;
+ retval = -EINVAL;
}
return retval;
}
diff -puNr linux-2.6.10-rc2-bk5/include/linux/input.h linux-uinput-ff/include/linux/input.h
--- linux-2.6.10-rc2-bk5/include/linux/input.h 2004-11-20 20:33:25.000000000 -0700
+++ linux-uinput-ff/include/linux/input.h 2004-11-20 20:40:49.000000000 -0700
@@ -94,6 +94,8 @@ struct input_absinfo {
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f

+#define EV_UINPUT 0x0101 /* Used only between /dev/uinput and applications */
+
/*
* Synchronization events.
*/
diff -puNr linux-2.6.10-rc2-bk5/include/linux/uinput.h linux-uinput-ff/include/linux/uinput.h
--- linux-2.6.10-rc2-bk5/include/linux/uinput.h 2004-10-18 15:54:40.000000000 -0600
+++ linux-uinput-ff/include/linux/uinput.h 2004-11-20 20:40:55.000000000 -0700
@@ -22,6 +22,9 @@
* Author: Aristeu Sergio Rozanski Filho <aris@xxxxxxxxxxxxxxxxx>
*
* Changes/Revisions:
+ * 0.2 16/10/2004 (Micah Dowty <micah@xxxxxxx>)
+ * - added force feedback support
+ * - added UI_SET_PHYS
* 0.1 20/06/2002
* - first public version
*/
@@ -29,10 +32,25 @@
#define UINPUT_MINOR 223
#define UINPUT_NAME "uinput"
#define UINPUT_BUFFER_SIZE 16
+#define UINPUT_NUM_REQUESTS 16

/* state flags => bit index for {set|clear|test}_bit ops */
#define UIST_CREATED 0

+struct uinput_request {
+ int id;
+ int code; /* UI_FF_UPLOAD, UI_FF_ERASE */
+
+ int retval;
+ wait_queue_head_t waitq;
+ int completed;
+
+ union {
+ int effect_id;
+ struct ff_effect* effect;
+ } u;
+};
+
struct uinput_device {
struct input_dev *dev;
unsigned long state;
@@ -41,13 +59,30 @@ struct uinput_device {
head,
tail;
struct input_event buff[UINPUT_BUFFER_SIZE];
+
+ struct uinput_request *requests[UINPUT_NUM_REQUESTS];
+ wait_queue_head_t requests_waitq;
+ struct semaphore requests_sem;
};
#endif /* __KERNEL__ */

+struct uinput_ff_upload {
+ int request_id;
+ int retval;
+ struct ff_effect effect;
+};
+
+struct uinput_ff_erase {
+ int request_id;
+ int retval;
+ int effect_id;
+};
+
/* ioctl */
#define UINPUT_IOCTL_BASE 'U'
#define UI_DEV_CREATE _IO(UINPUT_IOCTL_BASE, 1)
#define UI_DEV_DESTROY _IO(UINPUT_IOCTL_BASE, 2)
+
#define UI_SET_EVBIT _IOW(UINPUT_IOCTL_BASE, 100, int)
#define UI_SET_KEYBIT _IOW(UINPUT_IOCTL_BASE, 101, int)
#define UI_SET_RELBIT _IOW(UINPUT_IOCTL_BASE, 102, int)
@@ -56,6 +91,16 @@ struct uinput_device {
#define UI_SET_LEDBIT _IOW(UINPUT_IOCTL_BASE, 105, int)
#define UI_SET_SNDBIT _IOW(UINPUT_IOCTL_BASE, 106, int)
#define UI_SET_FFBIT _IOW(UINPUT_IOCTL_BASE, 107, int)
+#define UI_SET_PHYS _IOW(UINPUT_IOCTL_BASE, 108, char*)
+
+#define UI_BEGIN_FF_UPLOAD _IOWR(UINPUT_IOCTL_BASE, 200, struct uinput_ff_upload)
+#define UI_END_FF_UPLOAD _IOW(UINPUT_IOCTL_BASE, 201, struct uinput_ff_upload)
+#define UI_BEGIN_FF_ERASE _IOWR(UINPUT_IOCTL_BASE, 202, struct uinput_ff_erase)
+#define UI_END_FF_ERASE _IOW(UINPUT_IOCTL_BASE, 203, struct uinput_ff_erase)
+
+/* 'code' values for EV_UINPUT */
+#define UI_FF_UPLOAD 1
+#define UI_FF_ERASE 2

#ifndef NBITS
#define NBITS(x) ((((x)-1)/(sizeof(long)*8))+1)


--
Only you can prevent creeping featurism!
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/