blob: 919618a71b00e60c3d7314283f4db5b69443394a [file]
// 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;
}