Commit a7197c2e authored by Henrik Rydberg's avatar Henrik Rydberg Committed by Jiri Kosina

HID: Handle driver-specific device descriptor in core

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: default avatarHenrik Rydberg <rydberg@euromail.se>
Tested-by: default avatarNikolai Kondrashov <spbnick@gmail.com>
Tested-by: default avatarBenjamin Tissoires <benjamin.tissoires@gmail.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 2a039bf5
...@@ -546,12 +546,11 @@ static void hid_free_report(struct hid_report *report) ...@@ -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_close_report(struct hid_device *device)
static void hid_device_release(struct device *dev)
{ {
struct hid_device *device = container_of(dev, struct hid_device, dev);
unsigned i, j; unsigned i, j;
for (i = 0; i < HID_REPORT_TYPES; i++) { for (i = 0; i < HID_REPORT_TYPES; i++) {
...@@ -562,11 +561,34 @@ static void hid_device_release(struct device *dev) ...@@ -562,11 +561,34 @@ static void hid_device_release(struct device *dev)
if (report) if (report)
hid_free_report(report); hid_free_report(report);
} }
memset(report_enum, 0, sizeof(*report_enum));
INIT_LIST_HEAD(&report_enum->report_list);
} }
kfree(device->rdesc); kfree(device->rdesc);
device->rdesc = NULL;
device->rsize = 0;
kfree(device->collection); 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) ...@@ -643,15 +665,37 @@ static u8 *fetch_item(__u8 *start, __u8 *end, struct hid_item *item)
* @start: report start * @start: report start
* @size: report size * @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 * Parse a report description into a hid_device structure. Reports are
* enumerated, fields are attached to these reports. * enumerated, fields are attached to these reports.
* 0 returned on success, otherwise nonzero error value. * 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, int hid_open_report(struct hid_device *device)
unsigned size)
{ {
struct hid_parser *parser; struct hid_parser *parser;
struct hid_item item; struct hid_item item;
unsigned int size;
__u8 *start;
__u8 *end; __u8 *end;
int ret; int ret;
static int (*dispatch_type[])(struct hid_parser *parser, static int (*dispatch_type[])(struct hid_parser *parser,
...@@ -662,6 +706,14 @@ int hid_parse_report(struct hid_device *device, __u8 *start, ...@@ -662,6 +706,14 @@ int hid_parse_report(struct hid_device *device, __u8 *start,
hid_parser_reserved 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) if (device->driver->report_fixup)
start = device->driver->report_fixup(device, start, &size); start = device->driver->report_fixup(device, start, &size);
...@@ -679,6 +731,15 @@ int hid_parse_report(struct hid_device *device, __u8 *start, ...@@ -679,6 +731,15 @@ int hid_parse_report(struct hid_device *device, __u8 *start,
parser->device = device; parser->device = device;
end = start + size; 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; ret = -EINVAL;
while ((start = fetch_item(start, end, &item)) != NULL) { while ((start = fetch_item(start, end, &item)) != NULL) {
...@@ -704,6 +765,7 @@ int hid_parse_report(struct hid_device *device, __u8 *start, ...@@ -704,6 +765,7 @@ int hid_parse_report(struct hid_device *device, __u8 *start,
goto err; goto err;
} }
vfree(parser); vfree(parser);
device->status |= HID_STAT_PARSED;
return 0; return 0;
} }
} }
...@@ -711,9 +773,10 @@ int hid_parse_report(struct hid_device *device, __u8 *start, ...@@ -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)); hid_err(device, "item fetching failed at offset %d\n", (int)(end - start));
err: err:
vfree(parser); vfree(parser);
hid_close_report(device);
return ret; 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 * Convert a signed n-bit integer to signed 32-bit integer. Common
...@@ -1718,12 +1781,14 @@ static int hid_device_probe(struct device *dev) ...@@ -1718,12 +1781,14 @@ static int hid_device_probe(struct device *dev)
if (hdrv->probe) { if (hdrv->probe) {
ret = hdrv->probe(hdev, id); ret = hdrv->probe(hdev, id);
} else { /* default probe */ } else { /* default probe */
ret = hid_parse(hdev); ret = hid_open_report(hdev);
if (!ret) if (!ret)
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
} }
if (ret) if (ret) {
hid_close_report(hdev);
hdev->driver = NULL; hdev->driver = NULL;
}
} }
unlock: unlock:
up(&hdev->driver_lock); up(&hdev->driver_lock);
...@@ -1744,6 +1809,7 @@ static int hid_device_remove(struct device *dev) ...@@ -1744,6 +1809,7 @@ static int hid_device_remove(struct device *dev)
hdrv->remove(hdev); hdrv->remove(hdev);
else /* default remove */ else /* default remove */
hid_hw_stop(hdev); hid_hw_stop(hdev);
hid_close_report(hdev);
hdev->driver = NULL; hdev->driver = NULL;
} }
...@@ -2075,6 +2141,16 @@ int hid_add_device(struct hid_device *hdev) ...@@ -2075,6 +2141,16 @@ int hid_add_device(struct hid_device *hdev)
&& (hid_ignore(hdev) || (hdev->quirks & HID_QUIRK_IGNORE))) && (hid_ignore(hdev) || (hdev->quirks & HID_QUIRK_IGNORE)))
return -ENODEV; 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 /* XXX hack, any other cleaner solution after the driver core
* is converted to allow more than 20 bytes as the device name? */ * is converted to allow more than 20 bytes as the device name? */
dev_set_name(&hdev->dev, "%04X:%04X:%04X.%04X", hdev->bus, dev_set_name(&hdev->dev, "%04X:%04X:%04X.%04X", hdev->bus,
...@@ -2103,7 +2179,6 @@ EXPORT_SYMBOL_GPL(hid_add_device); ...@@ -2103,7 +2179,6 @@ EXPORT_SYMBOL_GPL(hid_add_device);
struct hid_device *hid_allocate_device(void) struct hid_device *hid_allocate_device(void)
{ {
struct hid_device *hdev; struct hid_device *hdev;
unsigned int i;
int ret = -ENOMEM; int ret = -ENOMEM;
hdev = kzalloc(sizeof(*hdev), GFP_KERNEL); hdev = kzalloc(sizeof(*hdev), GFP_KERNEL);
...@@ -2114,23 +2189,13 @@ struct hid_device *hid_allocate_device(void) ...@@ -2114,23 +2189,13 @@ struct hid_device *hid_allocate_device(void)
hdev->dev.release = hid_device_release; hdev->dev.release = hid_device_release;
hdev->dev.bus = &hid_bus_type; hdev->dev.bus = &hid_bus_type;
hdev->collection = kcalloc(HID_DEFAULT_NUM_COLLECTIONS, hid_close_report(hdev);
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);
init_waitqueue_head(&hdev->debug_wait); init_waitqueue_head(&hdev->debug_wait);
INIT_LIST_HEAD(&hdev->debug_list); INIT_LIST_HEAD(&hdev->debug_list);
sema_init(&hdev->driver_lock, 1); sema_init(&hdev->driver_lock, 1);
return hdev; return hdev;
err:
put_device(&hdev->dev);
return ERR_PTR(ret);
} }
EXPORT_SYMBOL_GPL(hid_allocate_device); EXPORT_SYMBOL_GPL(hid_allocate_device);
...@@ -2141,6 +2206,9 @@ static void hid_remove_device(struct hid_device *hdev) ...@@ -2141,6 +2206,9 @@ static void hid_remove_device(struct hid_device *hdev)
hid_debug_unregister(hdev); hid_debug_unregister(hdev);
hdev->status &= ~HID_STAT_ADDED; hdev->status &= ~HID_STAT_ADDED;
} }
kfree(hdev->dev_rdesc);
hdev->dev_rdesc = NULL;
hdev->dev_rsize = 0;
} }
/** /**
......
...@@ -467,6 +467,8 @@ struct hid_driver; ...@@ -467,6 +467,8 @@ struct hid_driver;
struct hid_ll_driver; struct hid_ll_driver;
struct hid_device { /* device report descriptor */ struct hid_device { /* device report descriptor */
__u8 *dev_rdesc;
unsigned dev_rsize;
__u8 *rdesc; __u8 *rdesc;
unsigned rsize; unsigned rsize;
struct hid_collection *collection; /* List of HID collections */ struct hid_collection *collection; /* List of HID collections */
...@@ -735,6 +737,7 @@ void hid_output_report(struct hid_report *report, __u8 *data); ...@@ -735,6 +737,7 @@ void hid_output_report(struct hid_report *report, __u8 *data);
struct hid_device *hid_allocate_device(void); struct hid_device *hid_allocate_device(void);
struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id); 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_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_check_keys_pressed(struct hid_device *hid);
int hid_connect(struct hid_device *hid, unsigned int connect_mask); int hid_connect(struct hid_device *hid, unsigned int connect_mask);
void hid_disconnect(struct hid_device *hid); void hid_disconnect(struct hid_device *hid);
...@@ -805,16 +808,7 @@ static inline void hid_map_usage_clear(struct hid_input *hidinput, ...@@ -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) static inline int __must_check hid_parse(struct hid_device *hdev)
{ {
int ret; return hid_open_report(hdev);
if (hdev->status & HID_STAT_PARSED)
return 0;
ret = hdev->ll_driver->parse(hdev);
if (!ret)
hdev->status |= HID_STAT_PARSED;
return ret;
} }
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment