Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fcfcf0de authored by David Herrmann's avatar David Herrmann Committed by Jiri Kosina
Browse files

HID: uhid: implement feature requests



HID standard allows sending a feature request to the device which is
answered by an HID report. uhid implements this by sending a UHID_FEATURE
event to user-space which then must answer with UHID_FEATURE_ANSWER. If it
doesn't do this in a timely manner, the request is discarded silently.

We serialize the feature requests, that is, there is always only a single
active feature-request sent to user-space, other requests have to wait.
HIDP and USB-HID do it the same way.

Because we discard feature-requests silently, we must make sure to match
a response to the corresponding request. We use sequence-IDs for this so
user-space must copy the ID from the request into the answer.
Feature-answers are ignored if they do not contain the same ID as the
currently pending feature request.

Internally, we must make sure that feature-requests are synchronized with
UHID_DESTROY and close() events. We must not dead-lock when closing the
HID device, either, so we have to use separate locks.

Signed-off-by: default avatarDavid Herrmann <dh.herrmann@googlemail.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 3b3baa82
Loading
Loading
Loading
Loading
+119 −1
Original line number Diff line number Diff line
@@ -42,6 +42,12 @@ struct uhid_device {
	__u8 head;
	__u8 tail;
	struct uhid_event *outq[UHID_BUFSIZE];

	struct mutex report_lock;
	wait_queue_head_t report_wait;
	atomic_t report_done;
	atomic_t report_id;
	struct uhid_event report_buf;
};

static struct miscdevice uhid_misc;
@@ -143,7 +149,84 @@ static int uhid_hid_parse(struct hid_device *hid)
static int uhid_hid_get_raw(struct hid_device *hid, unsigned char rnum,
			    __u8 *buf, size_t count, unsigned char rtype)
{
	return 0;
	struct uhid_device *uhid = hid->driver_data;
	__u8 report_type;
	struct uhid_event *ev;
	unsigned long flags;
	int ret;
	size_t len;
	struct uhid_feature_answer_req *req;

	if (!uhid->running)
		return -EIO;

	switch (rtype) {
	case HID_FEATURE_REPORT:
		report_type = UHID_FEATURE_REPORT;
		break;
	case HID_OUTPUT_REPORT:
		report_type = UHID_OUTPUT_REPORT;
		break;
	case HID_INPUT_REPORT:
		report_type = UHID_INPUT_REPORT;
		break;
	default:
		return -EINVAL;
	}

	ret = mutex_lock_interruptible(&uhid->report_lock);
	if (ret)
		return ret;

	ev = kzalloc(sizeof(*ev), GFP_KERNEL);
	if (!ev) {
		ret = -ENOMEM;
		goto unlock;
	}

	spin_lock_irqsave(&uhid->qlock, flags);
	ev->type = UHID_FEATURE;
	ev->u.feature.id = atomic_inc_return(&uhid->report_id);
	ev->u.feature.rnum = rnum;
	ev->u.feature.rtype = report_type;

	atomic_set(&uhid->report_done, 0);
	uhid_queue(uhid, ev);
	spin_unlock_irqrestore(&uhid->qlock, flags);

	ret = wait_event_interruptible_timeout(uhid->report_wait,
				atomic_read(&uhid->report_done), 5 * HZ);

	/*
	 * Make sure "uhid->running" is cleared on shutdown before
	 * "uhid->report_done" is set.
	 */
	smp_rmb();
	if (!ret || !uhid->running) {
		ret = -EIO;
	} else if (ret < 0) {
		ret = -ERESTARTSYS;
	} else {
		spin_lock_irqsave(&uhid->qlock, flags);
		req = &uhid->report_buf.u.feature_answer;

		if (req->err) {
			ret = -EIO;
		} else {
			ret = 0;
			len = min(count,
				min_t(size_t, req->size, UHID_DATA_MAX));
			memcpy(buf, req->data, len);
		}

		spin_unlock_irqrestore(&uhid->qlock, flags);
	}

	atomic_set(&uhid->report_done, 1);

unlock:
	mutex_unlock(&uhid->report_lock);
	return ret ? ret : len;
}

static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count,
@@ -265,7 +348,11 @@ static int uhid_dev_destroy(struct uhid_device *uhid)
	if (!uhid->running)
		return -EINVAL;

	/* clear "running" before setting "report_done" */
	uhid->running = false;
	smp_wmb();
	atomic_set(&uhid->report_done, 1);
	wake_up_interruptible(&uhid->report_wait);

	hid_destroy_device(uhid->hid);
	kfree(uhid->rd_data);
@@ -284,6 +371,31 @@ static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev)
	return 0;
}

static int uhid_dev_feature_answer(struct uhid_device *uhid,
				   struct uhid_event *ev)
{
	unsigned long flags;

	if (!uhid->running)
		return -EINVAL;

	spin_lock_irqsave(&uhid->qlock, flags);

	/* id for old report; drop it silently */
	if (atomic_read(&uhid->report_id) != ev->u.feature_answer.id)
		goto unlock;
	if (atomic_read(&uhid->report_done))
		goto unlock;

	memcpy(&uhid->report_buf, ev, sizeof(*ev));
	atomic_set(&uhid->report_done, 1);
	wake_up_interruptible(&uhid->report_wait);

unlock:
	spin_unlock_irqrestore(&uhid->qlock, flags);
	return 0;
}

static int uhid_char_open(struct inode *inode, struct file *file)
{
	struct uhid_device *uhid;
@@ -293,9 +405,12 @@ static int uhid_char_open(struct inode *inode, struct file *file)
		return -ENOMEM;

	mutex_init(&uhid->devlock);
	mutex_init(&uhid->report_lock);
	spin_lock_init(&uhid->qlock);
	init_waitqueue_head(&uhid->waitq);
	init_waitqueue_head(&uhid->report_wait);
	uhid->running = false;
	atomic_set(&uhid->report_done, 1);

	file->private_data = uhid;
	nonseekable_open(inode, file);
@@ -398,6 +513,9 @@ static ssize_t uhid_char_write(struct file *file, const char __user *buffer,
	case UHID_INPUT:
		ret = uhid_dev_input(uhid, &uhid->input_buf);
		break;
	case UHID_FEATURE_ANSWER:
		ret = uhid_dev_feature_answer(uhid, &uhid->input_buf);
		break;
	default:
		ret = -EOPNOTSUPP;
	}
+17 −0
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ enum uhid_event_type {
	UHID_OUTPUT,
	UHID_OUTPUT_EV,
	UHID_INPUT,
	UHID_FEATURE,
	UHID_FEATURE_ANSWER,
};

struct uhid_create_req {
@@ -73,6 +75,19 @@ struct uhid_output_ev_req {
	__s32 value;
} __attribute__((__packed__));

struct uhid_feature_req {
	__u32 id;
	__u8 rnum;
	__u8 rtype;
} __attribute__((__packed__));

struct uhid_feature_answer_req {
	__u32 id;
	__u16 err;
	__u16 size;
	__u8 data[UHID_DATA_MAX];
};

struct uhid_event {
	__u32 type;

@@ -81,6 +96,8 @@ struct uhid_event {
		struct uhid_input_req input;
		struct uhid_output_req output;
		struct uhid_output_ev_req output_ev;
		struct uhid_feature_req feature;
		struct uhid_feature_answer_req feature_answer;
	} u;
} __attribute__((__packed__));