mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-11 12:28:41 +08:00
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: 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>
This commit is contained in:
parent
2a039bf5a6
commit
a7197c2e41
@ -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)
|
|||||||
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)
|
|||||||
* @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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
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,13 +1781,15 @@ 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);
|
||||||
return ret;
|
return ret;
|
||||||
@ -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)
|
|||||||
&& (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);
|
|||||||
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)
|
|||||||
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)
|
|||||||
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;
|
|||||||
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);
|
|||||||
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,
|
|||||||
*/
|
*/
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user