HID: Handle driver-specific device descriptor in core
authorHenrik Rydberg <rydberg@euromail.se>
Sun, 22 Apr 2012 12:21:40 +0000 (14:21 +0200)
committerJiri Kosina <jkosina@suse.cz>
Tue, 1 May 2012 10:54:53 +0000 (12:54 +0200)
The low-level driver can read the report descriptor, but it cannot
determine driver-specific changes to it. The hid core can fixup
and parse the report descriptor during driver attach, but does
not have direct access to the descriptor when doing so.

To be able to handle attach/detach of hid drivers properly,
a semantic change to hid_parse_report() is needed. This function has
been used in two ways, both as descriptor reader in the ll drivers and
as a parsor in the probe of the drivers. This patch splits the usage
by introducing hid_open_report(), and modifies the hid_parse() macro
to call hid_open_report() instead. The only usage of hid_parse_report()
is then to read and store the device descriptor. As a consequence, we
can handle the report fixups automatically inside the hid core.

Signed-off-by: Henrik Rydberg <rydberg@euromail.se>
Tested-by: Nikolai Kondrashov <spbnick@gmail.com>
Tested-by: Benjamin Tissoires <benjamin.tissoires@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
drivers/hid/hid-core.c
include/linux/hid.h

index 4da66b4b977c334df920200d1ee08d5ec6a1b164..9ed4ff37dc2bf3797f8abfd6d33a90a428479cab 100644 (file)
@@ -546,12 +546,11 @@ static void hid_free_report(struct hid_report *report)
 }
 
 /*
- * Free a device structure, all reports, and all fields.
+ * Close report. This function returns the device
+ * state to the point prior to hid_open_report().
  */
-
-static void hid_device_release(struct device *dev)
+static void hid_close_report(struct hid_device *device)
 {
-       struct hid_device *device = container_of(dev, struct hid_device, dev);
        unsigned i, j;
 
        for (i = 0; i < HID_REPORT_TYPES; i++) {
@@ -562,11 +561,34 @@ static void hid_device_release(struct device *dev)
                        if (report)
                                hid_free_report(report);
                }
+               memset(report_enum, 0, sizeof(*report_enum));
+               INIT_LIST_HEAD(&report_enum->report_list);
        }
 
        kfree(device->rdesc);
+       device->rdesc = NULL;
+       device->rsize = 0;
+
        kfree(device->collection);
-       kfree(device);
+       device->collection = NULL;
+       device->collection_size = 0;
+       device->maxcollection = 0;
+       device->maxapplication = 0;
+
+       device->status &= ~HID_STAT_PARSED;
+}
+
+/*
+ * Free a device structure, all reports, and all fields.
+ */
+
+static void hid_device_release(struct device *dev)
+{
+       struct hid_device *hid = container_of(dev, struct hid_device, dev);
+
+       hid_close_report(hid);
+       kfree(hid->dev_rdesc);
+       kfree(hid);
 }
 
 /*
@@ -643,15 +665,37 @@ static u8 *fetch_item(__u8 *start, __u8 *end, struct hid_item *item)
  * @start: report start
  * @size: report size
  *
+ * Allocate the device report as read by the bus driver. This function should
+ * only be called from parse() in ll drivers.
+ */
+int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size)
+{
+       hid->dev_rdesc = kmemdup(start, size, GFP_KERNEL);
+       if (!hid->dev_rdesc)
+               return -ENOMEM;
+       hid->dev_rsize = size;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(hid_parse_report);
+
+/**
+ * hid_open_report - open a driver-specific device report
+ *
+ * @device: hid device
+ *
  * Parse a report description into a hid_device structure. Reports are
  * enumerated, fields are attached to these reports.
  * 0 returned on success, otherwise nonzero error value.
+ *
+ * This function (or the equivalent hid_parse() macro) should only be
+ * called from probe() in drivers, before starting the device.
  */
-int hid_parse_report(struct hid_device *device, __u8 *start,
-               unsigned size)
+int hid_open_report(struct hid_device *device)
 {
        struct hid_parser *parser;
        struct hid_item item;
+       unsigned int size;
+       __u8 *start;
        __u8 *end;
        int ret;
        static int (*dispatch_type[])(struct hid_parser *parser,
@@ -662,6 +706,14 @@ int hid_parse_report(struct hid_device *device, __u8 *start,
                hid_parser_reserved
        };
 
+       if (WARN_ON(device->status & HID_STAT_PARSED))
+               return -EBUSY;
+
+       start = device->dev_rdesc;
+       if (WARN_ON(!start))
+               return -ENODEV;
+       size = device->dev_rsize;
+
        if (device->driver->report_fixup)
                start = device->driver->report_fixup(device, start, &size);
 
@@ -679,6 +731,15 @@ int hid_parse_report(struct hid_device *device, __u8 *start,
        parser->device = device;
 
        end = start + size;
+
+       device->collection = kcalloc(HID_DEFAULT_NUM_COLLECTIONS,
+                                    sizeof(struct hid_collection), GFP_KERNEL);
+       if (!device->collection) {
+               ret = -ENOMEM;
+               goto err;
+       }
+       device->collection_size = HID_DEFAULT_NUM_COLLECTIONS;
+
        ret = -EINVAL;
        while ((start = fetch_item(start, end, &item)) != NULL) {
 
@@ -704,6 +765,7 @@ int hid_parse_report(struct hid_device *device, __u8 *start,
                                goto err;
                        }
                        vfree(parser);
+                       device->status |= HID_STAT_PARSED;
                        return 0;
                }
        }
@@ -711,9 +773,10 @@ int hid_parse_report(struct hid_device *device, __u8 *start,
        hid_err(device, "item fetching failed at offset %d\n", (int)(end - start));
 err:
        vfree(parser);
+       hid_close_report(device);
        return ret;
 }
-EXPORT_SYMBOL_GPL(hid_parse_report);
+EXPORT_SYMBOL_GPL(hid_open_report);
 
 /*
  * Convert a signed n-bit integer to signed 32-bit integer. Common
@@ -1718,12 +1781,14 @@ static int hid_device_probe(struct device *dev)
                if (hdrv->probe) {
                        ret = hdrv->probe(hdev, id);
                } else { /* default probe */
-                       ret = hid_parse(hdev);
+                       ret = hid_open_report(hdev);
                        if (!ret)
                                ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
                }
-               if (ret)
+               if (ret) {
+                       hid_close_report(hdev);
                        hdev->driver = NULL;
+               }
        }
 unlock:
        up(&hdev->driver_lock);
@@ -1744,6 +1809,7 @@ static int hid_device_remove(struct device *dev)
                        hdrv->remove(hdev);
                else /* default remove */
                        hid_hw_stop(hdev);
+               hid_close_report(hdev);
                hdev->driver = NULL;
        }
 
@@ -2075,6 +2141,16 @@ int hid_add_device(struct hid_device *hdev)
             && (hid_ignore(hdev) || (hdev->quirks & HID_QUIRK_IGNORE)))
                return -ENODEV;
 
+       /*
+        * Read the device report descriptor once and use as template
+        * for the driver-specific modifications.
+        */
+       ret = hdev->ll_driver->parse(hdev);
+       if (ret)
+               return ret;
+       if (!hdev->dev_rdesc)
+               return -ENODEV;
+
        /* XXX hack, any other cleaner solution after the driver core
         * is converted to allow more than 20 bytes as the device name? */
        dev_set_name(&hdev->dev, "%04X:%04X:%04X.%04X", hdev->bus,
@@ -2103,7 +2179,6 @@ EXPORT_SYMBOL_GPL(hid_add_device);
 struct hid_device *hid_allocate_device(void)
 {
        struct hid_device *hdev;
-       unsigned int i;
        int ret = -ENOMEM;
 
        hdev = kzalloc(sizeof(*hdev), GFP_KERNEL);
@@ -2114,23 +2189,13 @@ struct hid_device *hid_allocate_device(void)
        hdev->dev.release = hid_device_release;
        hdev->dev.bus = &hid_bus_type;
 
-       hdev->collection = kcalloc(HID_DEFAULT_NUM_COLLECTIONS,
-                       sizeof(struct hid_collection), GFP_KERNEL);
-       if (hdev->collection == NULL)
-               goto err;
-       hdev->collection_size = HID_DEFAULT_NUM_COLLECTIONS;
-
-       for (i = 0; i < HID_REPORT_TYPES; i++)
-               INIT_LIST_HEAD(&hdev->report_enum[i].report_list);
+       hid_close_report(hdev);
 
        init_waitqueue_head(&hdev->debug_wait);
        INIT_LIST_HEAD(&hdev->debug_list);
        sema_init(&hdev->driver_lock, 1);
 
        return hdev;
-err:
-       put_device(&hdev->dev);
-       return ERR_PTR(ret);
 }
 EXPORT_SYMBOL_GPL(hid_allocate_device);
 
@@ -2141,6 +2206,9 @@ static void hid_remove_device(struct hid_device *hdev)
                hid_debug_unregister(hdev);
                hdev->status &= ~HID_STAT_ADDED;
        }
+       kfree(hdev->dev_rdesc);
+       hdev->dev_rdesc = NULL;
+       hdev->dev_rsize = 0;
 }
 
 /**
index 3a95da60fd3e5cda95617fffc824168437373d95..d8e7cc7f894fa69e9a1d43349ebfb955a6a9719b 100644 (file)
@@ -467,6 +467,8 @@ struct hid_driver;
 struct hid_ll_driver;
 
 struct hid_device {                                                    /* device report descriptor */
+       __u8 *dev_rdesc;
+       unsigned dev_rsize;
        __u8 *rdesc;
        unsigned rsize;
        struct hid_collection *collection;                              /* List of HID collections */
@@ -735,6 +737,7 @@ void hid_output_report(struct hid_report *report, __u8 *data);
 struct hid_device *hid_allocate_device(void);
 struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id);
 int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size);
+int hid_open_report(struct hid_device *device);
 int hid_check_keys_pressed(struct hid_device *hid);
 int hid_connect(struct hid_device *hid, unsigned int connect_mask);
 void hid_disconnect(struct hid_device *hid);
@@ -805,16 +808,7 @@ static inline void hid_map_usage_clear(struct hid_input *hidinput,
  */
 static inline int __must_check hid_parse(struct hid_device *hdev)
 {
-       int ret;
-
-       if (hdev->status & HID_STAT_PARSED)
-               return 0;
-
-       ret = hdev->ll_driver->parse(hdev);
-       if (!ret)
-               hdev->status |= HID_STAT_PARSED;
-
-       return ret;
+       return hid_open_report(hdev);
 }
 
 /**