X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=net%2Fbluetooth%2Fhidp%2Fcore.c;h=bfe641b7dfaf0e0ba70fa604ebffec7155cbaec8;hb=ff12fd643334071084b6145cad3793bb6c956638;hp=4b99c5e4478da77c099190daea614244ada030d3;hpb=8e87d14255acffeee36873de226dc25c11b5f46d;p=safe%2Fjmp%2Flinux-2.6 diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index 4b99c5e..bfe641b 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,8 @@ #include #include +#include +#include #include #include @@ -45,12 +48,7 @@ #include "hidp.h" -#ifndef CONFIG_BT_HIDP_DEBUG -#undef BT_DBG -#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); @@ -95,10 +93,14 @@ static void __hidp_link_session(struct hidp_session *session) { __module_get(THIS_MODULE); list_add(&session->list, &hidp_session_list); + + hci_conn_hold_device(session->conn); } static void __hidp_unlink_session(struct hidp_session *session) { + hci_conn_put_device(session->conn); + list_del(&session->list); module_put(THIS_MODULE); } @@ -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 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,26 +243,6 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb) input_sync(dev); } -static void hidp_idle_timeout(unsigned long arg) -{ - struct hidp_session *session = (struct hidp_session *) arg; - - atomic_inc(&session->terminate); - hidp_schedule(session); -} - -static inline void hidp_set_timer(struct hidp_session *session) -{ - if (session->idle_to > 0) - mod_timer(&session->timer, jiffies + HZ * session->idle_to); -} - -static inline void hidp_del_timer(struct hidp_session *session) -{ - if (session->idle_to > 0) - del_timer(&session->timer); -} - static int __hidp_send_ctrl_message(struct hidp_session *session, unsigned char hdr, unsigned char *data, int size) { @@ -260,7 +264,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; @@ -272,7 +276,85 @@ static int inline hidp_send_ctrl_message(struct hidp_session *session, return err; } -static inline void hidp_process_handshake(struct hidp_session *session, unsigned char param) +static 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, session->hid, 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 int hidp_output_raw_report(struct hid_device *hid, unsigned char *data, size_t count, + unsigned char report_type) +{ + switch (report_type) { + case HID_FEATURE_REPORT: + report_type = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE; + break; + case HID_OUTPUT_REPORT: + report_type = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUPUT; + break; + default: + return -EINVAL; + } + + if (hidp_send_ctrl_message(hid->driver_data, report_type, + data, count)) + return -ENOMEM; + return count; +} + +static void hidp_idle_timeout(unsigned long arg) +{ + struct hidp_session *session = (struct hidp_session *) arg; + + atomic_inc(&session->terminate); + hidp_schedule(session); +} + +static void hidp_set_timer(struct hidp_session *session) +{ + if (session->idle_to > 0) + mod_timer(&session->timer, jiffies + HZ * session->idle_to); +} + +static inline void hidp_del_timer(struct hidp_session *session) +{ + if (session->idle_to > 0) + del_timer(&session->timer); +} + +static void hidp_process_handshake(struct hidp_session *session, + unsigned char param) { BT_DBG("session %p param 0x%02x", session, param); @@ -305,38 +387,24 @@ static inline void hidp_process_handshake(struct hidp_session *session, unsigned } } -static inline void hidp_process_hid_control(struct hidp_session *session, unsigned char param) +static void hidp_process_hid_control(struct hidp_session *session, + unsigned char param) { BT_DBG("session %p param 0x%02x", session, param); - switch (param) { - case HIDP_CTRL_NOP: - break; - - case HIDP_CTRL_VIRTUAL_CABLE_UNPLUG: + if (param == HIDP_CTRL_VIRTUAL_CABLE_UNPLUG) { /* Flush the transmit queues */ skb_queue_purge(&session->ctrl_transmit); skb_queue_purge(&session->intr_transmit); /* Kill session thread */ atomic_inc(&session->terminate); - break; - - case HIDP_CTRL_HARD_RESET: - case HIDP_CTRL_SOFT_RESET: - case HIDP_CTRL_SUSPEND: - case HIDP_CTRL_EXIT_SUSPEND: - /* FIXME: We have to parse these and return no error */ - break; - - default: - __hidp_send_ctrl_message(session, - HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0); - break; + hidp_schedule(session); } } -static inline void hidp_process_data(struct hidp_session *session, struct sk_buff *skb, unsigned char param) +static void hidp_process_data(struct hidp_session *session, struct sk_buff *skb, + unsigned char param) { BT_DBG("session %p skb %p len %d param 0x%02x", session, skb, skb->len, param); @@ -346,6 +414,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: @@ -359,7 +431,8 @@ static inline void hidp_process_data(struct hidp_session *session, struct sk_buf } } -static inline void hidp_recv_ctrl_frame(struct hidp_session *session, struct sk_buff *skb) +static void hidp_recv_ctrl_frame(struct hidp_session *session, + struct sk_buff *skb) { unsigned char hdr, type, param; @@ -393,7 +466,8 @@ static inline void hidp_recv_ctrl_frame(struct hidp_session *session, struct sk_ kfree_skb(skb); } -static inline void hidp_recv_intr_frame(struct hidp_session *session, struct sk_buff *skb) +static void hidp_recv_intr_frame(struct hidp_session *session, + struct sk_buff *skb) { unsigned char hdr; @@ -404,8 +478,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,14 +551,18 @@ 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); - add_wait_queue(ctrl_sk->sk_sleep, &ctrl_wait); - add_wait_queue(intr_sk->sk_sleep, &intr_wait); + add_wait_queue(sk_sleep(ctrl_sk), &ctrl_wait); + add_wait_queue(sk_sleep(intr_sk), &intr_wait); while (!atomic_read(&session->terminate)) { set_current_state(TASK_INTERRUPTIBLE); @@ -500,27 +584,38 @@ static int hidp_session(void *arg) schedule(); } set_current_state(TASK_RUNNING); - remove_wait_queue(intr_sk->sk_sleep, &intr_wait); - remove_wait_queue(ctrl_sk->sk_sleep, &ctrl_wait); + remove_wait_queue(sk_sleep(intr_sk), &intr_wait); + remove_wait_queue(sk_sleep(ctrl_sk), &ctrl_wait); down_write(&hidp_session_sem); hidp_del_timer(session); + if (session->input) { + input_unregister_device(session->input); + session->input = NULL; + } + + if (session->hid) { + hid_destroy_device(session->hid); + session->hid = NULL; + } + + /* Wakeup user-space polling for socket errors */ + session->intr_sock->sk->sk_err = EUNATCH; + session->ctrl_sock->sk->sk_err = EUNATCH; + + hidp_schedule(session); + fput(session->intr_sock->file); - wait_event_timeout(*(ctrl_sk->sk_sleep), + wait_event_timeout(*(sk_sleep(ctrl_sk)), (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); @@ -531,26 +626,35 @@ static struct device *hidp_get_device(struct hidp_session *session) { bdaddr_t *src = &bt_sk(session->ctrl_sock->sk)->src; bdaddr_t *dst = &bt_sk(session->ctrl_sock->sk)->dst; + struct device *device = NULL; struct hci_dev *hdev; - struct hci_conn *conn; hdev = hci_get_route(dst, src); if (!hdev) return NULL; - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst); + session->conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst); + if (session->conn) + device = &session->conn->dev; hci_dev_put(hdev); - return conn ? &conn->dev : NULL; + return device; } -static inline void hidp_setup_input(struct hidp_session *session, struct hidp_connadd_req *req) +static int hidp_setup_input(struct hidp_session *session, + struct hidp_connadd_req *req) { - struct input_dev *input = session->input; - int i; + struct input_dev *input; + int err, i; + + input = input_allocate_device(); + if (!input) + return -ENOMEM; + + session->input = input; - input->private = session; + input_set_drvdata(input, session); input->name = "Bluetooth HID Boot Protocol Device"; @@ -576,18 +680,140 @@ static inline void hidp_setup_input(struct hidp_session *session, struct hidp_co } if (req->subclass & 0x80) { - input->evbit[0] = BIT(EV_KEY) | BIT(EV_REL); - input->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE); - input->relbit[0] = BIT(REL_X) | BIT(REL_Y); - input->keybit[LONG(BTN_MOUSE)] |= BIT(BTN_SIDE) | BIT(BTN_EXTRA); - input->relbit[0] |= BIT(REL_WHEEL); + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + input->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE); + input->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + input->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) | + BIT_MASK(BTN_EXTRA); + input->relbit[0] |= BIT_MASK(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); + err = input_register_device(input); + if (err < 0) { + hci_conn_put_device(session->conn); + return err; + } + + return 0; +} + +static int hidp_open(struct hid_device *hid) +{ + return 0; +} + +static void hidp_close(struct hid_device *hid) +{ +} + +static int hidp_parse(struct hid_device *hid) +{ + struct hidp_session *session = hid->driver_data; + + return hid_parse_report(session->hid, session->rd_data, + session->rd_size); +} + +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); + + 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); + + 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, + struct hidp_connadd_req *req) +{ + struct hid_device *hid; + bdaddr_t src, dst; + int err; + + session->rd_data = kzalloc(req->rd_size, GFP_KERNEL); + if (!session->rd_data) + return -ENOMEM; + + if (copy_from_user(session->rd_data, req->rd_data, req->rd_size)) { + err = -EFAULT; + goto fault; + } + session->rd_size = req->rd_size; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) { + err = PTR_ERR(hid); + goto fault; + } + + session->hid = hid; + + hid->driver_data = session; + + baswap(&src, &bt_sk(session->ctrl_sock->sk)->src); + baswap(&dst, &bt_sk(session->ctrl_sock->sk)->dst); + + hid->bus = BUS_BLUETOOTH; + hid->vendor = req->vendor; + hid->product = req->product; + hid->version = req->version; + hid->country = req->country; + + strncpy(hid->name, req->name, 128); + strncpy(hid->phys, batostr(&src), 64); + strncpy(hid->uniq, batostr(&dst), 64); + + hid->dev.parent = hidp_get_device(session); + hid->ll_driver = &hidp_hid_driver; + + hid->hid_output_raw_report = hidp_output_raw_report; + + err = hid_add_device(hid); + if (err < 0) + goto failed; + + return 0; + +failed: + hid_destroy_device(hid); + session->hid = NULL; + +fault: + kfree(session->rd_data); + session->rd_data = NULL; + + return err; } int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock) @@ -605,11 +831,7 @@ 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); down_write(&hidp_session_sem); @@ -630,10 +852,7 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, session->intr_sock = intr_sock; session->state = BT_CONNECTED; - init_timer(&session->timer); - - session->timer.function = hidp_idle_timeout; - session->timer.data = (unsigned long) session; + setup_timer(&session->timer, hidp_idle_timeout, (unsigned long)session); skb_queue_head_init(&session->ctrl_transmit); skb_queue_head_init(&session->intr_transmit); @@ -641,8 +860,17 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, session->flags = req->flags & (1 << HIDP_BLUETOOTH_VENDOR_ID); session->idle_to = req->idle_to; - if (session->input) - hidp_setup_input(session, req); + if (req->rd_size > 0) { + err = hidp_setup_hid(session, req); + if (err && err != -ENODEV) + goto purge; + } + + if (!session->hid) { + err = hidp_setup_input(session, req); + if (err < 0) + goto purge; + } __hidp_link_session(session); @@ -671,13 +899,25 @@ unlink: if (session->input) { input_unregister_device(session->input); - session->input = NULL; /* don't try to free it here */ + session->input = NULL; + } + + if (session->hid) { + hid_destroy_device(session->hid); + session->hid = NULL; } + kfree(session->rd_data); + session->rd_data = NULL; + +purge: + skb_queue_purge(&session->ctrl_transmit); + skb_queue_purge(&session->intr_transmit); + failed: up_write(&hidp_session_sem); - kfree(session->input); + input_free_device(session->input); kfree(session); return err; } @@ -701,6 +941,10 @@ int hidp_del_connection(struct hidp_conndel_req *req) skb_queue_purge(&session->ctrl_transmit); skb_queue_purge(&session->intr_transmit); + /* Wakeup user-space polling for socket errors */ + session->intr_sock->sk->sk_err = EUNATCH; + session->ctrl_sock->sk->sk_err = EUNATCH; + /* Kill session thread */ atomic_inc(&session->terminate); hidp_schedule(session); @@ -762,18 +1006,43 @@ int hidp_get_conninfo(struct hidp_conninfo *ci) return err; } +static const struct hid_device_id hidp_table[] = { + { HID_BLUETOOTH_DEVICE(HID_ANY_ID, HID_ANY_ID) }, + { } +}; + +static struct hid_driver hidp_driver = { + .name = "generic-bluetooth", + .id_table = hidp_table, +}; + static int __init hidp_init(void) { + int ret; + l2cap_load(); BT_INFO("HIDP (Human Interface Emulation) ver %s", VERSION); - return hidp_init_sockets(); + ret = hid_register_driver(&hidp_driver); + if (ret) + goto err; + + ret = hidp_init_sockets(); + if (ret) + goto err_drv; + + return 0; +err_drv: + hid_unregister_driver(&hidp_driver); +err: + return ret; } static void __exit hidp_exit(void) { hidp_cleanup_sockets(); + hid_unregister_driver(&hidp_driver); } module_init(hidp_init);