From patchwork Mon Jul 15 17:10:12 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Herrmann X-Patchwork-Id: 2827672 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: X-Original-To: patchwork-linux-input@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 00CD5C0AB2 for ; Mon, 15 Jul 2013 17:10:44 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id EC0842027F for ; Mon, 15 Jul 2013 17:10:42 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 96BA62028A for ; Mon, 15 Jul 2013 17:10:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751505Ab3GORKl (ORCPT ); Mon, 15 Jul 2013 13:10:41 -0400 Received: from mail-ee0-f47.google.com ([74.125.83.47]:61571 "EHLO mail-ee0-f47.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751492Ab3GORKk (ORCPT ); Mon, 15 Jul 2013 13:10:40 -0400 Received: by mail-ee0-f47.google.com with SMTP id e49so7624711eek.20 for ; Mon, 15 Jul 2013 10:10:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; bh=SXggnvqjPItqcE679dRaUyz267OfMTNOVV/gFxkauFk=; b=Tpfh4i1gmpAH7jAuwZkctNuZIkMq/FQJ4/MpqEzxQL7dWNMNpO0bZWiDRO7I4t77HZ dDCTx9/CNGU9hQwngqi3u3/T3fH/XaLFL1JhYN0WJzX3I6mxZ1ZYcT5gTHoqbA6CzxwM PWc/QjzOHsbcyd5T7vyrx+ziAkdWG0AVy6+4CBLoKdcLojFwOxBPB5gtGStmKRfxK7vH cFDt92cWml8Kzxtnju1jfhXAR51X7Jvk0hNrbc1jvepqpCQnY6/gyC56jfOFPhn9eQPV KwWOduCZMUjaSn+y1JKoLbCEnMLtcK7kSRGxqWvIIjv9kewbfLwsswE9l0okRDtJ0QgW vAQw== X-Received: by 10.15.94.11 with SMTP id ba11mr60144720eeb.101.1373908239108; Mon, 15 Jul 2013 10:10:39 -0700 (PDT) Received: from localhost.localdomain (stgt-5f71bdec.pool.mediaWays.net. [95.113.189.236]) by mx.google.com with ESMTPSA id cg12sm104118163eeb.7.2013.07.15.10.10.36 for (version=TLSv1.2 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Mon, 15 Jul 2013 10:10:38 -0700 (PDT) From: David Herrmann To: linux-input@vger.kernel.org Cc: Jiri Kosina , Benjamin Tissoires , Henrik Rydberg , Oliver Neukum , David Herrmann Subject: [RFC 3/8] HID: input: generic hidinput_input_event handler Date: Mon, 15 Jul 2013 19:10:12 +0200 Message-Id: <1373908217-16748-4-git-send-email-dh.herrmann@gmail.com> X-Mailer: git-send-email 1.8.3.2 In-Reply-To: <1373908217-16748-1-git-send-email-dh.herrmann@gmail.com> References: <1373908217-16748-1-git-send-email-dh.herrmann@gmail.com> Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Spam-Status: No, score=-4.5 required=5.0 tests=BANG_GUAR,BAYES_00, DKIM_ADSP_CUSTOM_MED,DKIM_SIGNED,FREEMAIL_FROM,RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP The hidinput_input_event() callback converts input events written from userspace into HID reports and sends them to the device. We currently implement this in every HID transport driver, even though most of them do the same. This provides a generic hidinput_input_event() implementation which is mostly copied from usbhid. It uses a delayed worker to allow multiple LED events to be collected into a single output event. We use the custom ->request() transport driver callback to allow drivers to adjust the outgoing report and handle the request asynchronously. If no custom ->request() callback is available, we fall back to the generic raw output report handler (which is synchronous). Drivers can still provide custom hidinput_input_event() handlers (see logitech-dj) if the generic implementation doesn't fit their needs. Signed-off-by: David Herrmann --- drivers/hid/hid-input.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++++- include/linux/hid.h | 1 + 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 7480799..308eee8 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -1137,6 +1137,74 @@ unsigned int hidinput_count_leds(struct hid_device *hid) } EXPORT_SYMBOL_GPL(hidinput_count_leds); +static void hidinput_led_worker(struct work_struct *work) +{ + struct hid_device *hid = container_of(work, struct hid_device, + led_work); + struct hid_field *field; + struct hid_report *report; + int len; + __u8 *buf; + + field = hidinput_get_led_field(hid); + if (!field) + return; + + /* + * field->report is accessed unlocked regarding HID core. So there might + * be another incoming SET-LED request from user-space, which changes + * the LED state while we assemble our outgoing buffer. However, this + * doesn't matter as hid_output_report() correctly converts it into a + * boolean value no matter what information is currently set on the LED + * field (even garbage). So the remote device will always get a valid + * request. + * And in case we send a wrong value, a next led worker is spawned + * for every SET-LED request so the following worker will send the + * correct value, guaranteed! + */ + + report = field->report; + + /* use custom SET_REPORT request if possible (asynchronous) */ + if (hid->ll_driver->request) + return hid->ll_driver->request(hid, report, HID_REQ_SET_REPORT); + + /* fall back to generic raw-output-report */ + len = ((report->size - 1) >> 3) + 1 + (report->id > 0); + buf = kmalloc(len, GFP_KERNEL); + if (!buf) + return; + + hid_output_report(report, buf); + /* synchronous output report */ + hid->hid_output_raw_report(hid, buf, len, HID_OUTPUT_REPORT); + kfree(buf); +} + +static int hidinput_input_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct hid_field *field; + int offset; + + if (type == EV_FF) + return input_ff_event(dev, type, code, value); + + if (type != EV_LED) + return -1; + + if ((offset = hidinput_find_field(hid, type, code, &field)) == -1) { + hid_warn(dev, "event field not found\n"); + return -1; + } + + hid_set_field(field, offset, value); + + schedule_work(&hid->led_work); + return 0; +} + static int hidinput_open(struct input_dev *dev) { struct hid_device *hid = input_get_drvdata(dev); @@ -1183,7 +1251,10 @@ static struct hid_input *hidinput_allocate(struct hid_device *hid) } input_set_drvdata(input_dev, hid); - input_dev->event = hid->ll_driver->hidinput_input_event; + if (hid->ll_driver->hidinput_input_event) + input_dev->event = hid->ll_driver->hidinput_input_event; + else if (hid->ll_driver->request || hid->hid_output_raw_report) + input_dev->event = hidinput_input_event; input_dev->open = hidinput_open; input_dev->close = hidinput_close; input_dev->setkeycode = hidinput_setkeycode; @@ -1278,6 +1349,7 @@ int hidinput_connect(struct hid_device *hid, unsigned int force) int i, j, k; INIT_LIST_HEAD(&hid->inputs); + INIT_WORK(&hid->led_work, hidinput_led_worker); if (!force) { for (i = 0; i < hid->maxcollection; i++) { @@ -1379,6 +1451,12 @@ void hidinput_disconnect(struct hid_device *hid) input_unregister_device(hidinput->input); kfree(hidinput); } + + /* led_work is spawned by input_dev callbacks, but doesn't access the + * parent input_dev at all. Once all input devices are removed, we + * know that led_work will never get restarted, so we can cancel it + * synchronously and are safe. */ + cancel_work_sync(&hid->led_work); } EXPORT_SYMBOL_GPL(hidinput_disconnect); diff --git a/include/linux/hid.h b/include/linux/hid.h index b8058c5..ea4b828 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -456,6 +456,7 @@ struct hid_device { /* device report descriptor */ enum hid_type type; /* device type (mouse, kbd, ...) */ unsigned country; /* HID country */ struct hid_report_enum report_enum[HID_REPORT_TYPES]; + struct work_struct led_work; /* delayed LED worker */ struct semaphore driver_lock; /* protects the current driver, except during input */ struct semaphore driver_input_lock; /* protects the current driver */