X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=net%2Fbluetooth%2Fhidp%2Fcore.c;h=64d89ca2884796ab29365f1ad2b40cebf35a136c;hb=831441862956fffa17b9801db37e6ea1650b0f69;hp=9a562cf7406bc89ba0ea125eec4979a2783744ce;hpb=b2cfcd75df77b80d9cc3fa84190a350dfa79eb93;p=safe%2Fjmp%2Flinux-2.6 diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index 9a562cf..64d89ca 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c @@ -1,4 +1,4 @@ -/* +/* HIDP implementation for Linux Bluetooth stack (BlueZ). Copyright (C) 2003-2004 Marcel Holtmann @@ -10,13 +10,13 @@ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY - CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, - COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS + ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, + COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED. */ @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ #include #include +#include #include #include @@ -50,7 +52,7 @@ #define BT_DBG(D...) #endif -#define VERSION "1.1" +#define VERSION "1.2" static DECLARE_RWSEM(hidp_session_sem); static LIST_HEAD(hidp_session_list); @@ -124,15 +126,22 @@ static void __hidp_copy_session(struct hidp_session *session, struct hidp_connin else strncpy(ci->name, "HID Boot Device", 128); } + + if (session->hid) { + ci->vendor = session->hid->vendor; + ci->product = session->hid->product; + ci->version = session->hid->version; + strncpy(ci->name, session->hid->name, 128); + } } -static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +static inline int hidp_queue_event(struct hidp_session *session, struct input_dev *dev, + unsigned int type, unsigned int code, int value) { - struct hidp_session *session = dev->private; - struct sk_buff *skb; unsigned char newleds; + struct sk_buff *skb; - BT_DBG("input %p type %d code %d value %d", dev, type, code, value); + BT_DBG("session %p type %d code %d value %d", session, type, code, value); if (type != EV_LED) return -1; @@ -164,6 +173,21 @@ static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned i return 0; } +static int hidp_hidinput_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct hidp_session *session = hid->driver_data; + + return hidp_queue_event(session, dev, type, code, value); +} + +static int hidp_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct hidp_session *session = input_get_drvdata(dev); + + return hidp_queue_event(session, dev, type, code, value); +} + static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb) { struct input_dev *dev = session->input; @@ -219,6 +243,42 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb) input_sync(dev); } +static inline int hidp_queue_report(struct hidp_session *session, unsigned char *data, int size) +{ + struct sk_buff *skb; + + BT_DBG("session %p hid %p data %p size %d", session, device, data, size); + + if (!(skb = alloc_skb(size + 1, GFP_ATOMIC))) { + BT_ERR("Can't allocate memory for new frame"); + return -ENOMEM; + } + + *skb_put(skb, 1) = 0xa2; + if (size > 0) + memcpy(skb_put(skb, size), data, size); + + skb_queue_tail(&session->intr_transmit, skb); + + hidp_schedule(session); + + return 0; +} + +static int hidp_send_report(struct hidp_session *session, struct hid_report *report) +{ + unsigned char buf[32]; + int rsize; + + rsize = ((report->size - 1) >> 3) + 1 + (report->id > 0); + if (rsize > sizeof(buf)) + return -EIO; + + hid_output_report(report, buf); + + return hidp_queue_report(session, buf, rsize); +} + static void hidp_idle_timeout(unsigned long arg) { struct hidp_session *session = (struct hidp_session *) arg; @@ -260,7 +320,7 @@ static int __hidp_send_ctrl_message(struct hidp_session *session, return 0; } -static int inline hidp_send_ctrl_message(struct hidp_session *session, +static inline int hidp_send_ctrl_message(struct hidp_session *session, unsigned char hdr, unsigned char *data, int size) { int err; @@ -293,7 +353,7 @@ static inline void hidp_process_handshake(struct hidp_session *session, unsigned case HIDP_HSHK_ERR_FATAL: /* Device requests a reboot, as this is the only way this error - * can be recovered. */ + * can be recovered. */ __hidp_send_ctrl_message(session, HIDP_TRANS_HID_CONTROL | HIDP_CTRL_SOFT_RESET, NULL, 0); break; @@ -346,6 +406,10 @@ static inline void hidp_process_data(struct hidp_session *session, struct sk_buf if (session->input) hidp_input_report(session, skb); + + if (session->hid) + hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 0); + break; case HIDP_DATA_RTYPE_OTHER: @@ -404,8 +468,14 @@ static inline void hidp_recv_intr_frame(struct hidp_session *session, struct sk_ if (hdr == (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) { hidp_set_timer(session); + if (session->input) hidp_input_report(session, skb); + + if (session->hid) { + hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 1); + BT_DBG("report len %d", skb->len); + } } else { BT_DBG("Unsupported protocol header 0x%02x", hdr); } @@ -471,9 +541,13 @@ static int hidp_session(void *arg) product = session->input->id.product; } + if (session->hid) { + vendor = session->hid->vendor; + product = session->hid->product; + } + daemonize("khidpd_%04x%04x", vendor, product); set_user_nice(current, -15); - current->flags |= PF_NOFREEZE; init_waitqueue_entry(&ctrl_wait, current); init_waitqueue_entry(&intr_wait, current); @@ -507,22 +581,26 @@ static int hidp_session(void *arg) hidp_del_timer(session); - if (intr_sk->sk_state != BT_CONNECTED) - wait_event_timeout(*(ctrl_sk->sk_sleep), (ctrl_sk->sk_state == BT_CLOSED), HZ); + if (session->input) { + input_unregister_device(session->input); + session->input = NULL; + } + + if (session->hid) { + if (session->hid->claimed & HID_CLAIMED_INPUT) + hidinput_disconnect(session->hid); + hid_free_device(session->hid); + } fput(session->intr_sock->file); - wait_event_timeout(*(intr_sk->sk_sleep), (intr_sk->sk_state == BT_CLOSED), HZ); + wait_event_timeout(*(ctrl_sk->sk_sleep), + (ctrl_sk->sk_state == BT_CLOSED), msecs_to_jiffies(500)); fput(session->ctrl_sock->file); __hidp_unlink_session(session); - if (session->input) { - input_unregister_device(session->input); - session->input = NULL; - } - up_write(&hidp_session_sem); kfree(session); @@ -552,7 +630,7 @@ static inline void hidp_setup_input(struct hidp_session *session, struct hidp_co struct input_dev *input = session->input; int i; - input->private = session; + input_set_drvdata(input, session); input->name = "Bluetooth HID Boot Protocol Device"; @@ -585,13 +663,84 @@ static inline void hidp_setup_input(struct hidp_session *session, struct hidp_co input->relbit[0] |= BIT(REL_WHEEL); } - input->cdev.dev = hidp_get_device(session); + input->dev.parent = hidp_get_device(session); input->event = hidp_input_event; input_register_device(input); } +static int hidp_open(struct hid_device *hid) +{ + return 0; +} + +static void hidp_close(struct hid_device *hid) +{ +} + +static const struct { + __u16 idVendor; + __u16 idProduct; + unsigned quirks; +} hidp_blacklist[] = { + /* Apple wireless Mighty Mouse */ + { 0x05ac, 0x030c, HID_QUIRK_MIGHTYMOUSE | HID_QUIRK_INVERT_HWHEEL }, + + { } /* Terminating entry */ +}; + +static void hidp_setup_quirks(struct hid_device *hid) +{ + unsigned int n; + + for (n = 0; hidp_blacklist[n].idVendor; n++) + if (hidp_blacklist[n].idVendor == le16_to_cpu(hid->vendor) && + hidp_blacklist[n].idProduct == le16_to_cpu(hid->product)) + hid->quirks = hidp_blacklist[n].quirks; +} + +static inline void hidp_setup_hid(struct hidp_session *session, struct hidp_connadd_req *req) +{ + struct hid_device *hid = session->hid; + struct hid_report *report; + bdaddr_t src, dst; + + baswap(&src, &bt_sk(session->ctrl_sock->sk)->src); + baswap(&dst, &bt_sk(session->ctrl_sock->sk)->dst); + + hid->driver_data = session; + + hid->country = req->country; + + hid->bus = BUS_BLUETOOTH; + hid->vendor = req->vendor; + hid->product = req->product; + hid->version = req->version; + + strncpy(hid->name, req->name, 128); + strncpy(hid->phys, batostr(&src), 64); + strncpy(hid->uniq, batostr(&dst), 64); + + hid->dev = hidp_get_device(session); + + 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; +} + int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock) { struct hidp_session *session, *s; @@ -607,10 +756,38 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, if (!session) return -ENOMEM; - session->input = input_allocate_device(); - if (!session->input) { - kfree(session); - return -ENOMEM; + 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_parse_report(buf, req->rd_size); + + kfree(buf); + + if (!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); @@ -646,6 +823,9 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, if (session->input) hidp_setup_input(session, req); + if (session->hid) + hidp_setup_hid(session, req); + __hidp_link_session(session); hidp_set_timer(session); @@ -679,7 +859,10 @@ unlink: failed: up_write(&hidp_session_sem); - kfree(session->input); + if (session->hid) + hid_free_device(session->hid); + + input_free_device(session->input); kfree(session); return err; }