| // SPDX-License-Identifier: LGPL-2.1-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2014 Intel Corporation. All rights reserved. |
| * |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| |
| #include "src/shared/io.h" |
| #include "src/shared/util.h" |
| #include "src/shared/queue.h" |
| #include "src/shared/uhid.h" |
| |
| #define UHID_DEVICE_FILE "/dev/uhid" |
| |
| struct uhid_replay { |
| bool active; |
| struct queue *out; |
| struct queue *in; |
| struct queue *rout; |
| struct queue *rin; |
| }; |
| |
| struct bt_uhid { |
| int ref_count; |
| struct io *io; |
| unsigned int notify_id; |
| bool notifying; |
| struct queue *notify_list; |
| struct queue *input; |
| uint8_t type; |
| bool created; |
| unsigned int start_id; |
| bool started; |
| struct uhid_replay *replay; |
| }; |
| |
| struct uhid_notify { |
| unsigned int id; |
| uint32_t event; |
| bt_uhid_callback_t func; |
| void *user_data; |
| bool removed; |
| }; |
| |
| static void uhid_replay_free(struct uhid_replay *replay) |
| { |
| if (!replay) |
| return; |
| |
| queue_destroy(replay->rin, NULL); |
| queue_destroy(replay->in, free); |
| queue_destroy(replay->rout, NULL); |
| queue_destroy(replay->out, free); |
| free(replay); |
| } |
| |
| static void uhid_free(struct bt_uhid *uhid) |
| { |
| if (uhid->io) |
| io_destroy(uhid->io); |
| |
| if (uhid->notify_list) |
| queue_destroy(uhid->notify_list, free); |
| |
| if (uhid->input) |
| queue_destroy(uhid->input, free); |
| |
| uhid_replay_free(uhid->replay); |
| |
| free(uhid); |
| } |
| |
| static void notify_handler(void *data, void *user_data) |
| { |
| struct uhid_notify *notify = data; |
| struct uhid_event *ev = user_data; |
| |
| if (notify->event != ev->type) |
| return; |
| |
| if (notify->func) |
| notify->func(ev, notify->user_data); |
| } |
| |
| static struct uhid_replay *uhid_replay_new(void) |
| { |
| struct uhid_replay *replay = new0(struct uhid_replay, 1); |
| |
| replay->out = queue_new(); |
| replay->in = queue_new(); |
| |
| return replay; |
| } |
| |
| static int bt_uhid_record(struct bt_uhid *uhid, bool input, |
| struct uhid_event *ev) |
| { |
| if (!uhid) |
| return -EINVAL; |
| |
| /* Capture input events in replay mode and send the next replay event */ |
| if (uhid->replay && uhid->replay->active && input) { |
| queue_pop_head(uhid->replay->rin); |
| bt_uhid_replay(uhid); |
| return -EALREADY; |
| } |
| |
| if (!uhid->replay) |
| uhid->replay = uhid_replay_new(); |
| |
| if (input) |
| queue_push_tail(uhid->replay->in, |
| util_memdup(ev, sizeof(*ev))); |
| else |
| queue_push_tail(uhid->replay->out, |
| util_memdup(ev, sizeof(*ev))); |
| |
| return 0; |
| } |
| |
| static bool match_removed(const void *a, const void *b) |
| { |
| const struct uhid_notify *notify = a; |
| |
| return notify->removed; |
| } |
| |
| static void uhid_notify(struct bt_uhid *uhid, struct uhid_event *ev) |
| { |
| /* Add a reference to the uhid to ensure it doesn't get freed while at |
| * notify_handler. |
| */ |
| bt_uhid_ref(uhid); |
| |
| uhid->notifying = true; |
| queue_foreach(uhid->notify_list, notify_handler, ev); |
| uhid->notifying = false; |
| queue_remove_all(uhid->notify_list, match_removed, NULL, free); |
| |
| bt_uhid_unref(uhid); |
| } |
| |
| static bool uhid_read_handler(struct io *io, void *user_data) |
| { |
| struct bt_uhid *uhid = user_data; |
| int fd; |
| ssize_t len; |
| struct uhid_event ev; |
| |
| fd = io_get_fd(io); |
| if (fd < 0) |
| return false; |
| |
| memset(&ev, 0, sizeof(ev)); |
| |
| len = read(fd, &ev, sizeof(ev)); |
| if (len < 0) |
| return false; |
| |
| if ((size_t) len < sizeof(ev.type)) |
| return false; |
| |
| switch (ev.type) { |
| case UHID_GET_REPORT: |
| case UHID_SET_REPORT: |
| bt_uhid_record(uhid, false, &ev); |
| break; |
| } |
| |
| uhid_notify(uhid, &ev); |
| |
| return true; |
| } |
| |
| struct bt_uhid *bt_uhid_new_default(void) |
| { |
| struct bt_uhid *uhid; |
| int fd; |
| |
| fd = open(UHID_DEVICE_FILE, O_RDWR | O_CLOEXEC); |
| if (fd < 0) |
| return NULL; |
| |
| uhid = bt_uhid_new(fd); |
| if (!uhid) { |
| close(fd); |
| return NULL; |
| } |
| |
| io_set_close_on_destroy(uhid->io, true); |
| |
| return uhid; |
| } |
| |
| struct bt_uhid *bt_uhid_new(int fd) |
| { |
| struct bt_uhid *uhid; |
| |
| uhid = new0(struct bt_uhid, 1); |
| uhid->io = io_new(fd); |
| if (!uhid->io) |
| goto failed; |
| |
| uhid->notify_list = queue_new(); |
| |
| if (!io_set_read_handler(uhid->io, uhid_read_handler, uhid, NULL)) |
| goto failed; |
| |
| return bt_uhid_ref(uhid); |
| |
| failed: |
| uhid_free(uhid); |
| return NULL; |
| } |
| |
| struct bt_uhid *bt_uhid_ref(struct bt_uhid *uhid) |
| { |
| if (!uhid) |
| return NULL; |
| |
| __sync_fetch_and_add(&uhid->ref_count, 1); |
| |
| return uhid; |
| } |
| |
| void bt_uhid_unref(struct bt_uhid *uhid) |
| { |
| if (!uhid) |
| return; |
| |
| if (__sync_sub_and_fetch(&uhid->ref_count, 1)) |
| return; |
| |
| uhid_free(uhid); |
| } |
| |
| bool bt_uhid_set_close_on_unref(struct bt_uhid *uhid, bool do_close) |
| { |
| if (!uhid || !uhid->io) |
| return false; |
| |
| io_set_close_on_destroy(uhid->io, do_close); |
| |
| return true; |
| } |
| |
| unsigned int bt_uhid_register(struct bt_uhid *uhid, uint32_t event, |
| bt_uhid_callback_t func, void *user_data) |
| { |
| struct uhid_notify *notify; |
| |
| if (!uhid) |
| return 0; |
| |
| notify = new0(struct uhid_notify, 1); |
| notify->id = ++uhid->notify_id ? uhid->notify_id : ++uhid->notify_id; |
| notify->event = event; |
| notify->func = func; |
| notify->user_data = user_data; |
| |
| if (!queue_push_tail(uhid->notify_list, notify)) { |
| free(notify); |
| return 0; |
| } |
| |
| return notify->id; |
| } |
| |
| static bool match_notify_id(const void *a, const void *b) |
| { |
| const struct uhid_notify *notify = a; |
| unsigned int id = PTR_TO_UINT(b); |
| |
| return notify->id == id; |
| } |
| |
| bool bt_uhid_unregister(struct bt_uhid *uhid, unsigned int id) |
| { |
| struct uhid_notify *notify; |
| |
| if (!uhid || !id) |
| return false; |
| |
| notify = queue_remove_if(uhid->notify_list, match_notify_id, |
| UINT_TO_PTR(id)); |
| if (!notify) |
| return false; |
| |
| free(notify); |
| return true; |
| } |
| |
| static bool match_not_id(const void *a, const void *b) |
| { |
| const struct uhid_notify *notify = a; |
| unsigned int id = PTR_TO_UINT(b); |
| |
| return notify->id != id; |
| } |
| |
| static void uhid_notify_removed(void *data, void *user_data) |
| { |
| struct uhid_notify *notify = data; |
| struct bt_uhid *uhid = user_data; |
| |
| /* Skip marking start_id as removed since that is not removed with |
| * unregister all. |
| */ |
| if (notify->id == uhid->start_id) |
| return; |
| |
| notify->removed = true; |
| } |
| |
| bool bt_uhid_unregister_all(struct bt_uhid *uhid) |
| { |
| if (!uhid) |
| return false; |
| |
| if (!uhid->notifying) |
| queue_remove_all(uhid->notify_list, match_not_id, |
| UINT_TO_PTR(uhid->start_id), free); |
| else |
| queue_foreach(uhid->notify_list, uhid_notify_removed, uhid); |
| |
| return true; |
| } |
| |
| static int uhid_send(struct bt_uhid *uhid, const struct uhid_event *ev) |
| { |
| ssize_t len; |
| struct iovec iov; |
| |
| iov.iov_base = (void *) ev; |
| iov.iov_len = sizeof(*ev); |
| |
| len = io_send(uhid->io, &iov, 1); |
| if (len < 0) |
| return -errno; |
| |
| /* uHID kernel driver does not handle partial writes */ |
| return len != sizeof(*ev) ? -EIO : 0; |
| } |
| |
| int bt_uhid_send(struct bt_uhid *uhid, const struct uhid_event *ev) |
| { |
| if (!uhid || !ev) |
| return -EINVAL; |
| |
| if (!uhid->io) |
| return -ENOTCONN; |
| |
| return uhid_send(uhid, ev); |
| } |
| |
| static bool input_dequeue(const void *data, const void *match_data) |
| { |
| struct uhid_event *ev = (void *)data; |
| struct bt_uhid *uhid = (void *)match_data; |
| |
| return bt_uhid_send(uhid, ev) == 0; |
| } |
| |
| static void uhid_start(struct uhid_event *ev, void *user_data) |
| { |
| struct bt_uhid *uhid = user_data; |
| |
| uhid->started = true; |
| |
| /* dequeue input events send while UHID_CREATE2 was in progress */ |
| queue_remove_all(uhid->input, input_dequeue, uhid, free); |
| } |
| |
| int bt_uhid_create(struct bt_uhid *uhid, const char *name, bdaddr_t *src, |
| bdaddr_t *dst, uint32_t vendor, uint32_t product, |
| uint32_t version, uint32_t country, uint8_t type, |
| void *rd_data, size_t rd_size) |
| { |
| struct uhid_event ev; |
| int err; |
| |
| if (!uhid || !name || rd_size > sizeof(ev.u.create2.rd_data)) |
| return -EINVAL; |
| |
| if (uhid->created) |
| return 0; |
| |
| /* Register callback for UHID_START if not registered yet */ |
| if (!uhid->start_id) { |
| uhid->start_id = bt_uhid_register(uhid, UHID_START, uhid_start, |
| uhid); |
| if (!uhid->start_id) |
| return -ENOMEM; |
| } |
| |
| memset(&ev, 0, sizeof(ev)); |
| ev.type = UHID_CREATE2; |
| strncpy((char *) ev.u.create2.name, name, |
| sizeof(ev.u.create2.name) - 1); |
| if (src) |
| sprintf((char *)ev.u.create2.phys, |
| "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", |
| src->b[5], src->b[4], src->b[3], src->b[2], src->b[1], |
| src->b[0]); |
| if (dst) |
| sprintf((char *)ev.u.create2.uniq, |
| "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", |
| dst->b[5], dst->b[4], dst->b[3], dst->b[2], dst->b[1], |
| dst->b[0]); |
| ev.u.create2.vendor = vendor; |
| ev.u.create2.product = product; |
| ev.u.create2.version = version; |
| ev.u.create2.country = country; |
| ev.u.create2.bus = BUS_BLUETOOTH; |
| if (rd_size) |
| memcpy(ev.u.create2.rd_data, rd_data, rd_size); |
| ev.u.create2.rd_size = rd_size; |
| |
| err = bt_uhid_send(uhid, &ev); |
| if (err) |
| return err; |
| |
| uhid->created = true; |
| uhid->started = false; |
| uhid->type = type; |
| |
| return 0; |
| } |
| |
| bool bt_uhid_created(struct bt_uhid *uhid) |
| { |
| if (!uhid) |
| return false; |
| |
| return uhid->created; |
| } |
| |
| bool bt_uhid_started(struct bt_uhid *uhid) |
| { |
| if (!uhid) |
| return false; |
| |
| return uhid->started; |
| } |
| |
| int bt_uhid_input(struct bt_uhid *uhid, uint8_t number, const void *data, |
| size_t size) |
| { |
| struct uhid_event ev; |
| struct uhid_input2_req *req = &ev.u.input2; |
| size_t len = 0; |
| |
| if (!uhid) |
| return -EINVAL; |
| |
| memset(&ev, 0, sizeof(ev)); |
| ev.type = UHID_INPUT2; |
| |
| if (number) { |
| req->data[len++] = number; |
| req->size = 1 + MIN(size, sizeof(req->data) - 1); |
| } else |
| req->size = MIN(size, sizeof(req->data)); |
| |
| if (data && size) |
| memcpy(&req->data[len], data, req->size - len); |
| |
| /* Queue events if UHID_START has not been received yet */ |
| if (!uhid->started) { |
| if (!uhid->input) |
| uhid->input = queue_new(); |
| |
| queue_push_tail(uhid->input, util_memdup(&ev, sizeof(ev))); |
| return 0; |
| } |
| |
| return bt_uhid_send(uhid, &ev); |
| } |
| |
| int bt_uhid_set_report_reply(struct bt_uhid *uhid, uint32_t id, uint8_t status) |
| { |
| struct uhid_event ev; |
| struct uhid_set_report_reply_req *rsp = &ev.u.set_report_reply; |
| |
| if (!uhid) |
| return false; |
| |
| memset(&ev, 0, sizeof(ev)); |
| ev.type = UHID_SET_REPORT_REPLY; |
| rsp->id = id; |
| rsp->err = status; |
| |
| if (bt_uhid_record(uhid, true, &ev) == -EALREADY) |
| return 0; |
| |
| return bt_uhid_send(uhid, &ev); |
| } |
| |
| int bt_uhid_get_report_reply(struct bt_uhid *uhid, uint32_t id, uint8_t number, |
| uint8_t status, const void *data, size_t size) |
| { |
| struct uhid_event ev; |
| struct uhid_get_report_reply_req *rsp = &ev.u.get_report_reply; |
| size_t len = 0; |
| |
| if (!uhid) |
| return false; |
| |
| memset(&ev, 0, sizeof(ev)); |
| ev.type = UHID_GET_REPORT_REPLY; |
| rsp->id = id; |
| rsp->err = status; |
| |
| if (!data || !size) |
| goto done; |
| |
| if (number) { |
| rsp->data[len++] = number; |
| rsp->size += MIN(size, sizeof(rsp->data) - 1); |
| } else |
| rsp->size = MIN(size, sizeof(ev.u.input.data)); |
| |
| memcpy(&rsp->data[len], data, rsp->size - len); |
| |
| done: |
| if (bt_uhid_record(uhid, true, &ev) == -EALREADY) |
| return 0; |
| |
| return bt_uhid_send(uhid, &ev); |
| } |
| |
| int bt_uhid_destroy(struct bt_uhid *uhid, bool force) |
| { |
| struct uhid_event ev; |
| int err; |
| |
| if (!uhid) |
| return -EINVAL; |
| |
| /* Cleanup input queue */ |
| queue_destroy(uhid->input, free); |
| uhid->input = NULL; |
| |
| /* Force destroy for non-keyboard devices - keyboards are not destroyed |
| * on disconnect since they can glitch on reconnection losing |
| * keypresses. |
| */ |
| if (!force && uhid->type != BT_UHID_KEYBOARD) |
| force = true; |
| |
| if (!uhid->created || !force) |
| return 0; |
| |
| memset(&ev, 0, sizeof(ev)); |
| ev.type = UHID_DESTROY; |
| |
| err = bt_uhid_send(uhid, &ev); |
| if (err < 0) |
| return err; |
| |
| uhid->created = false; |
| uhid_replay_free(uhid->replay); |
| uhid->replay = NULL; |
| |
| return err; |
| } |
| |
| static void queue_append(void *data, void *user_data) |
| { |
| queue_push_tail(user_data, data); |
| } |
| |
| static struct queue *queue_dup(struct queue *q) |
| { |
| struct queue *dup; |
| |
| if (!q || queue_isempty(q)) |
| return NULL; |
| |
| dup = queue_new(); |
| |
| queue_foreach(q, queue_append, dup); |
| |
| return dup; |
| } |
| |
| int bt_uhid_replay(struct bt_uhid *uhid) |
| { |
| struct uhid_event *ev; |
| |
| if (!uhid || !uhid->started) |
| return -EINVAL; |
| |
| if (!uhid->replay) |
| return 0; |
| |
| if (uhid->replay->active) |
| goto resend; |
| |
| uhid->replay->active = true; |
| queue_destroy(uhid->replay->rin, NULL); |
| uhid->replay->rin = queue_dup(uhid->replay->in); |
| |
| queue_destroy(uhid->replay->rout, NULL); |
| uhid->replay->rout = queue_dup(uhid->replay->out); |
| |
| resend: |
| ev = queue_pop_head(uhid->replay->rout); |
| if (!ev) { |
| uhid->replay->active = false; |
| return 0; |
| } |
| |
| uhid_notify(uhid, ev); |
| |
| return 0; |
| } |