Commit c500c971 authored by Jiri Slaby's avatar Jiri Slaby Committed by Jiri Kosina

HID: hid, make parsing event driven

Next step for complete hid bus, this patch includes:
- call parser either from probe or from hid-core if there is no probe.
- add ll_driver structure and centralize some stuff there (open, close...)
- split and merge usb_hid_configure and hid_probe into several functions
  to allow hooks/fixes between them
Signed-off-by: default avatarJiri Slaby <jslaby@suse.cz>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 85cdaf52
...@@ -648,6 +648,9 @@ int hid_parse_report(struct hid_device *device, __u8 *start, ...@@ -648,6 +648,9 @@ int hid_parse_report(struct hid_device *device, __u8 *start,
hid_parser_reserved hid_parser_reserved
}; };
if (device->driver->report_fixup)
device->driver->report_fixup(device, start, size);
device->rdesc = kmalloc(size, GFP_KERNEL); device->rdesc = kmalloc(size, GFP_KERNEL);
if (device->rdesc == NULL) if (device->rdesc == NULL)
return -ENOMEM; return -ENOMEM;
...@@ -1152,15 +1155,20 @@ static int hid_device_probe(struct device *dev) ...@@ -1152,15 +1155,20 @@ static int hid_device_probe(struct device *dev)
int ret = 0; int ret = 0;
if (!hdev->driver) { if (!hdev->driver) {
if (hdrv->probe) { id = hid_match_id(hdev, hdrv->id_table);
ret = -ENODEV; if (id == NULL)
return -ENODEV;
id = hid_match_id(hdev, hdrv->id_table); hdev->driver = hdrv;
if (id) if (hdrv->probe) {
ret = hdrv->probe(hdev, id); ret = hdrv->probe(hdev, id);
} else { /* default probe */
ret = hid_parse(hdev);
if (!ret)
ret = hid_hw_start(hdev);
} }
if (!ret) if (ret)
hdev->driver = hdrv; hdev->driver = NULL;
} }
return ret; return ret;
} }
...@@ -1173,6 +1181,8 @@ static int hid_device_remove(struct device *dev) ...@@ -1173,6 +1181,8 @@ static int hid_device_remove(struct device *dev)
if (hdrv) { if (hdrv) {
if (hdrv->remove) if (hdrv->remove)
hdrv->remove(hdev); hdrv->remove(hdev);
else /* default remove */
hid_hw_stop(hdev);
hdev->driver = NULL; hdev->driver = NULL;
} }
......
...@@ -390,6 +390,15 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel ...@@ -390,6 +390,15 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
if (ret) if (ret)
goto mapped; goto mapped;
if (device->driver->input_mapping) {
int ret = device->driver->input_mapping(device, hidinput, field,
usage, &bit, &max);
if (ret > 0)
goto mapped;
if (ret < 0)
goto ignore;
}
switch (usage->hid & HID_USAGE_PAGE) { switch (usage->hid & HID_USAGE_PAGE) {
case HID_UP_UNDEFINED: case HID_UP_UNDEFINED:
...@@ -755,6 +764,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel ...@@ -755,6 +764,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
} }
mapped: mapped:
if (device->driver->input_mapped && device->driver->input_mapped(device,
hidinput, field, usage, &bit, &max) < 0)
goto ignore;
if (device->quirks & HID_QUIRK_MIGHTYMOUSE) { if (device->quirks & HID_QUIRK_MIGHTYMOUSE) {
if (usage->hid == HID_GD_Z) if (usage->hid == HID_GD_Z)
map_rel(REL_HWHEEL); map_rel(REL_HWHEEL);
...@@ -961,14 +974,14 @@ static int hidinput_open(struct input_dev *dev) ...@@ -961,14 +974,14 @@ static int hidinput_open(struct input_dev *dev)
{ {
struct hid_device *hid = input_get_drvdata(dev); struct hid_device *hid = input_get_drvdata(dev);
return hid->hid_open(hid); return hid->ll_driver->open(hid);
} }
static void hidinput_close(struct input_dev *dev) static void hidinput_close(struct input_dev *dev)
{ {
struct hid_device *hid = input_get_drvdata(dev); struct hid_device *hid = input_get_drvdata(dev);
hid->hid_close(hid); hid->ll_driver->close(hid);
} }
/* /*
...@@ -1019,7 +1032,8 @@ int hidinput_connect(struct hid_device *hid) ...@@ -1019,7 +1032,8 @@ int hidinput_connect(struct hid_device *hid)
} }
input_set_drvdata(input_dev, hid); input_set_drvdata(input_dev, hid);
input_dev->event = hid->hidinput_input_event; input_dev->event =
hid->ll_driver->hidinput_input_event;
input_dev->open = hidinput_open; input_dev->open = hidinput_open;
input_dev->close = hidinput_close; input_dev->close = hidinput_close;
input_dev->setkeycode = hidinput_setkeycode; input_dev->setkeycode = hidinput_setkeycode;
......
...@@ -181,7 +181,7 @@ static int hidraw_open(struct inode *inode, struct file *file) ...@@ -181,7 +181,7 @@ static int hidraw_open(struct inode *inode, struct file *file)
dev = hidraw_table[minor]; dev = hidraw_table[minor];
if (!dev->open++) if (!dev->open++)
dev->hid->hid_open(dev->hid); dev->hid->ll_driver->open(dev->hid);
out_unlock: out_unlock:
spin_unlock(&minors_lock); spin_unlock(&minors_lock);
...@@ -207,7 +207,7 @@ static int hidraw_release(struct inode * inode, struct file * file) ...@@ -207,7 +207,7 @@ static int hidraw_release(struct inode * inode, struct file * file)
dev = hidraw_table[minor]; dev = hidraw_table[minor];
if (!dev->open--) { if (!dev->open--) {
if (list->hidraw->exist) if (list->hidraw->exist)
dev->hid->hid_close(dev->hid); dev->hid->ll_driver->close(dev->hid);
else else
kfree(list->hidraw); kfree(list->hidraw);
} }
...@@ -367,7 +367,7 @@ void hidraw_disconnect(struct hid_device *hid) ...@@ -367,7 +367,7 @@ void hidraw_disconnect(struct hid_device *hid)
device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor)); device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor));
if (hidraw->open) { if (hidraw->open) {
hid->hid_close(hid); hid->ll_driver->close(hid);
wake_up_interruptible(&hidraw->wait); wake_up_interruptible(&hidraw->wait);
} else { } else {
kfree(hidraw); kfree(hidraw);
......
This diff is collapsed.
...@@ -419,6 +419,7 @@ struct hid_control_fifo { ...@@ -419,6 +419,7 @@ struct hid_control_fifo {
#define HID_CLAIMED_HIDRAW 4 #define HID_CLAIMED_HIDRAW 4
#define HID_STAT_ADDED 1 #define HID_STAT_ADDED 1
#define HID_STAT_PARSED 2
#define HID_CTRL_RUNNING 1 #define HID_CTRL_RUNNING 1
#define HID_OUT_RUNNING 2 #define HID_OUT_RUNNING 2
...@@ -435,6 +436,7 @@ struct hid_input { ...@@ -435,6 +436,7 @@ struct hid_input {
}; };
struct hid_driver; struct hid_driver;
struct hid_ll_driver;
struct hid_device { /* device report descriptor */ struct hid_device { /* device report descriptor */
__u8 *rdesc; __u8 *rdesc;
...@@ -452,6 +454,7 @@ struct hid_device { /* device report descriptor */ ...@@ -452,6 +454,7 @@ struct hid_device { /* device report descriptor */
struct device dev; /* device */ struct device dev; /* device */
struct hid_driver *driver; struct hid_driver *driver;
struct hid_ll_driver *ll_driver;
unsigned int status; /* see STAT flags above */ unsigned int status; /* see STAT flags above */
unsigned claimed; /* Claimed by hidinput, hiddev? */ unsigned claimed; /* Claimed by hidinput, hiddev? */
...@@ -471,11 +474,6 @@ struct hid_device { /* device report descriptor */ ...@@ -471,11 +474,6 @@ struct hid_device { /* device report descriptor */
__s32 delayed_value; /* For A4 Tech mice hwheel quirk */ __s32 delayed_value; /* For A4 Tech mice hwheel quirk */
/* device-specific function pointers */
int (*hidinput_input_event) (struct input_dev *, unsigned int, unsigned int, int);
int (*hid_open) (struct hid_device *);
void (*hid_close) (struct hid_device *);
/* hiddev event handler */ /* hiddev event handler */
void (*hiddev_hid_event) (struct hid_device *, struct hid_field *field, void (*hiddev_hid_event) (struct hid_device *, struct hid_field *field,
struct hid_usage *, __s32); struct hid_usage *, __s32);
...@@ -561,9 +559,22 @@ struct hid_usage_id { ...@@ -561,9 +559,22 @@ struct hid_usage_id {
* @raw_event: if report in report_table, this hook is called (NULL means nop) * @raw_event: if report in report_table, this hook is called (NULL means nop)
* @usage_table: on which events to call event (NULL means all) * @usage_table: on which events to call event (NULL means all)
* @event: if usage in usage_table, this hook is called (NULL means nop) * @event: if usage in usage_table, this hook is called (NULL means nop)
* @report_fixup: called before report descriptor parsing (NULL means nop)
* @input_mapping: invoked on input registering before mapping an usage
* @input_mapped: invoked on input registering after mapping an usage
* *
* raw_event and event should return 0 on no action performed, 1 when no * raw_event and event should return 0 on no action performed, 1 when no
* further processing should be done and negative on error * further processing should be done and negative on error
*
* input_mapping shall return a negative value to completely ignore this usage
* (e.g. doubled or invalid usage), zero to continue with parsing of this
* usage by generic code (no special handling needed) or positive to skip
* generic parsing (needed special handling which was done in the hook already)
* input_mapped shall return negative to inform the layer that this usage
* should not be considered for further processing or zero to notify that
* no processing was performed and should be done in a generic manner
* Both these functions may be NULL which means the same behavior as returning
* zero from them.
*/ */
struct hid_driver { struct hid_driver {
char *name; char *name;
...@@ -578,10 +589,43 @@ struct hid_driver { ...@@ -578,10 +589,43 @@ struct hid_driver {
const struct hid_usage_id *usage_table; const struct hid_usage_id *usage_table;
int (*event)(struct hid_device *hdev, struct hid_field *field, int (*event)(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value); struct hid_usage *usage, __s32 value);
void (*report_fixup)(struct hid_device *hdev, __u8 *buf,
unsigned int size);
int (*input_mapping)(struct hid_device *hdev,
struct hid_input *hidinput, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max);
int (*input_mapped)(struct hid_device *hdev,
struct hid_input *hidinput, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max);
/* private: */ /* private: */
struct device_driver driver; struct device_driver driver;
}; };
/**
* hid_ll_driver - low level driver callbacks
* @start: called on probe to start the device
* @stop: called on remove
* @open: called by input layer on open
* @close: called by input layer on close
* @hidinput_input_event: event input event (e.g. ff or leds)
* @parse: this method is called only once to parse the device data,
* shouldn't allocate anything to not leak memory
*/
struct hid_ll_driver {
int (*start)(struct hid_device *hdev);
void (*stop)(struct hid_device *hdev);
int (*open)(struct hid_device *hdev);
void (*close)(struct hid_device *hdev);
int (*hidinput_input_event) (struct input_dev *idev, unsigned int type,
unsigned int code, int value);
int (*parse)(struct hid_device *hdev);
};
/* Applications from HID Usage Tables 4/8/99 Version 1.1 */ /* Applications from HID Usage Tables 4/8/99 Version 1.1 */
/* We ignore a few input applications that are not widely used */ /* We ignore a few input applications that are not widely used */
#define IS_INPUT_APPLICATION(a) (((a >= 0x00010000) && (a <= 0x00010008)) || (a == 0x00010080) || (a == 0x000c0001) || (a == 0x000d0002)) #define IS_INPUT_APPLICATION(a) (((a >= 0x00010000) && (a <= 0x00010008)) || (a == 0x00010080) || (a == 0x000c0001) || (a == 0x000d0002))
...@@ -618,6 +662,56 @@ void hid_output_report(struct hid_report *report, __u8 *data); ...@@ -618,6 +662,56 @@ void hid_output_report(struct hid_report *report, __u8 *data);
struct hid_device *hid_allocate_device(void); struct hid_device *hid_allocate_device(void);
int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size); int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size);
/**
* hid_parse - parse HW reports
*
* @hdev: hid device
*
* Call this from probe after you set up the device (if needed). Your
* report_fixup will be called (if non-NULL) after reading raw report from
* device before passing it to hid layer for real parsing.
*/
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;
}
/**
* hid_hw_start - start underlaying HW
*
* @hdev: hid device
*
* Call this in probe function *after* hid_parse. This will setup HW buffers
* and start the device (if not deffered to device open). hid_hw_stop must be
* called if this was successfull.
*/
static inline int __must_check hid_hw_start(struct hid_device *hdev)
{
return hdev->ll_driver->start(hdev);
}
/**
* hid_hw_stop - stop underlaying HW
*
* @hdev: hid device
*
* This is usually called from remove function or from probe when something
* failed and hid_hw_start was called already.
*/
static inline void hid_hw_stop(struct hid_device *hdev)
{
hdev->ll_driver->stop(hdev);
}
void hid_report_raw_event(struct hid_device *hid, int type, u8 *data, int size, void hid_report_raw_event(struct hid_device *hid, int type, u8 *data, int size,
int interrupt); int interrupt);
......
...@@ -623,9 +623,15 @@ static struct device *hidp_get_device(struct hidp_session *session) ...@@ -623,9 +623,15 @@ static struct device *hidp_get_device(struct hidp_session *session)
static int hidp_setup_input(struct hidp_session *session, static int hidp_setup_input(struct hidp_session *session,
struct hidp_connadd_req *req) struct hidp_connadd_req *req)
{ {
struct input_dev *input = session->input; struct input_dev *input;
int i; int i;
input = input_allocate_device();
if (!input)
return -ENOMEM;
session->input = input;
input_set_drvdata(input, session); input_set_drvdata(input, session);
input->name = "Bluetooth HID Boot Protocol Device"; input->name = "Bluetooth HID Boot Protocol Device";
...@@ -698,55 +704,117 @@ static void hidp_setup_quirks(struct hid_device *hid) ...@@ -698,55 +704,117 @@ static void hidp_setup_quirks(struct hid_device *hid)
hid->quirks = hidp_blacklist[n].quirks; hid->quirks = hidp_blacklist[n].quirks;
} }
static int hidp_parse(struct hid_device *hid)
{
struct hidp_session *session = hid->driver_data;
struct hidp_connadd_req *req = session->req;
unsigned char *buf;
int ret;
buf = kmalloc(req->rd_size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (copy_from_user(buf, req->rd_data, req->rd_size)) {
kfree(buf);
return -EFAULT;
}
ret = hid_parse_report(session->hid, buf, req->rd_size);
kfree(buf);
if (ret)
return ret;
session->req = NULL;
hidp_setup_quirks(hid);
return 0;
}
static int hidp_start(struct hid_device *hid)
{
struct hidp_session *session = hid->driver_data;
struct hid_report *report;
list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].
report_list, list)
hidp_send_report(session, report);
list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].
report_list, list)
hidp_send_report(session, report);
if (hidinput_connect(hid) == 0)
hid->claimed |= HID_CLAIMED_INPUT;
return 0;
}
static void hidp_stop(struct hid_device *hid)
{
struct hidp_session *session = hid->driver_data;
skb_queue_purge(&session->ctrl_transmit);
skb_queue_purge(&session->intr_transmit);
if (hid->claimed & HID_CLAIMED_INPUT)
hidinput_disconnect(hid);
hid->claimed = 0;
}
static struct hid_ll_driver hidp_hid_driver = {
.parse = hidp_parse,
.start = hidp_start,
.stop = hidp_stop,
.open = hidp_open,
.close = hidp_close,
.hidinput_input_event = hidp_hidinput_event,
};
static int hidp_setup_hid(struct hidp_session *session, static int hidp_setup_hid(struct hidp_session *session,
struct hidp_connadd_req *req) struct hidp_connadd_req *req)
{ {
struct hid_device *hid = session->hid; struct hid_device *hid;
struct hid_report *report;
bdaddr_t src, dst; bdaddr_t src, dst;
int ret; int ret;
baswap(&src, &bt_sk(session->ctrl_sock->sk)->src); hid = hid_allocate_device();
baswap(&dst, &bt_sk(session->ctrl_sock->sk)->dst); if (IS_ERR(hid)) {
ret = PTR_ERR(session->hid);
goto err;
}
session->hid = hid;
session->req = req;
hid->driver_data = session; hid->driver_data = session;
hid->country = req->country; baswap(&src, &bt_sk(session->ctrl_sock->sk)->src);
baswap(&dst, &bt_sk(session->ctrl_sock->sk)->dst);
hid->bus = BUS_BLUETOOTH; hid->bus = BUS_BLUETOOTH;
hid->vendor = req->vendor; hid->vendor = req->vendor;
hid->product = req->product; hid->product = req->product;
hid->version = req->version; hid->version = req->version;
hid->country = req->country;
strncpy(hid->name, req->name, 128); strncpy(hid->name, req->name, 128);
strncpy(hid->phys, batostr(&src), 64); strncpy(hid->phys, batostr(&src), 64);
strncpy(hid->uniq, batostr(&dst), 64); strncpy(hid->uniq, batostr(&dst), 64);
hid->dev.parent = hidp_get_device(session); hid->dev.parent = hidp_get_device(session);
hid->ll_driver = &hidp_hid_driver;
hid->hid_open = hidp_open;
hid->hid_close = hidp_close;
hid->hidinput_input_event = hidp_hidinput_event;
hidp_setup_quirks(hid);
list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list)
hidp_send_report(session, report);
list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list)
hidp_send_report(session, report);
if (hidinput_connect(hid) == 0)
hid->claimed |= HID_CLAIMED_INPUT;
ret = hid_add_device(hid); ret = hid_add_device(hid);
if (ret) { if (ret)
if (hid->claimed & HID_CLAIMED_INPUT) goto err_hid;
hidinput_disconnect(hid);
skb_queue_purge(&session->intr_transmit);
}
return 0;
err_hid:
hid_destroy_device(hid);
session->hid = NULL;
err:
return ret; return ret;
} }
...@@ -767,46 +835,6 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, ...@@ -767,46 +835,6 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
BT_DBG("rd_data %p rd_size %d", req->rd_data, req->rd_size); BT_DBG("rd_data %p rd_size %d", req->rd_data, req->rd_size);
if (req->rd_size > 0) {
unsigned char *buf = kmalloc(req->rd_size, GFP_KERNEL);
if (!buf) {
kfree(session);
return -ENOMEM;
}
if (copy_from_user(buf, req->rd_data, req->rd_size)) {
kfree(buf);
kfree(session);
return -EFAULT;
}
session->hid = hid_allocate_device();
if (IS_ERR(session->hid)) {
kfree(buf);
kfree(session);
return PTR_ERR(session->hid);
}
err = hid_parse_report(session->hid, buf, req->rd_size);
kfree(buf);
if (err) {
hid_destroy_device(session->hid);
kfree(session);
return -EINVAL;
}
}
if (!session->hid) {
session->input = input_allocate_device();
if (!session->input) {
kfree(session);
return -ENOMEM;
}
}
down_write(&hidp_session_sem); down_write(&hidp_session_sem);
s = __hidp_get_session(&bt_sk(ctrl_sock->sk)->dst); s = __hidp_get_session(&bt_sk(ctrl_sock->sk)->dst);
...@@ -834,16 +862,16 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, ...@@ -834,16 +862,16 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock,
session->flags = req->flags & (1 << HIDP_BLUETOOTH_VENDOR_ID); session->flags = req->flags & (1 << HIDP_BLUETOOTH_VENDOR_ID);
session->idle_to = req->idle_to; session->idle_to = req->idle_to;
if (session->input) { if (req->rd_size > 0) {
err = hidp_setup_input(session, req);
if (err < 0)
goto failed;
}
if (session->hid) {
err = hidp_setup_hid(session, req); err = hidp_setup_hid(session, req);
if (err) if (err)
goto failed; goto err_skb;
}
if (!session->hid) {
err = hidp_setup_input(session, req);
if (err < 0)
goto err_skb;
} }
__hidp_link_session(session); __hidp_link_session(session);
...@@ -871,16 +899,15 @@ unlink: ...@@ -871,16 +899,15 @@ unlink:
__hidp_unlink_session(session); __hidp_unlink_session(session);
if (session->input) { if (session->input)
input_unregister_device(session->input); input_unregister_device(session->input);
session->input = NULL; /* don't try to free it here */
}
failed:
up_write(&hidp_session_sem);
if (session->hid) if (session->hid)
hid_destroy_device(session->hid); hid_destroy_device(session->hid);
err_skb:
skb_queue_purge(&session->ctrl_transmit);
skb_queue_purge(&session->intr_transmit);
failed:
up_write(&hidp_session_sem);
input_free_device(session->input); input_free_device(session->input);
kfree(session); kfree(session);
......
...@@ -151,6 +151,8 @@ struct hidp_session { ...@@ -151,6 +151,8 @@ struct hidp_session {
struct sk_buff_head ctrl_transmit; struct sk_buff_head ctrl_transmit;
struct sk_buff_head intr_transmit; struct sk_buff_head intr_transmit;
struct hidp_connadd_req *req;
}; };
static inline void hidp_schedule(struct hidp_session *session) static inline void hidp_schedule(struct hidp_session *session)
......
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