input/device: Rework uHID code to use bt_uhid

This commit is contained in:
Luiz Augusto von Dentz 2014-05-22 17:07:05 +03:00
parent 00d4b9d89f
commit e93eeb6cff
2 changed files with 73 additions and 185 deletions

View File

@ -56,7 +56,8 @@ builtin_modules += input
builtin_sources += profiles/input/manager.c \
profiles/input/server.h profiles/input/server.c \
profiles/input/device.h profiles/input/device.c \
profiles/input/uhid_copy.h profiles/input/hidp_defs.h
src/shared/uhid.h src/shared/uhid.c \
profiles/input/hidp_defs.h
builtin_modules += hog
builtin_sources += profiles/input/hog.c profiles/input/uhid_copy.h \

View File

@ -52,15 +52,13 @@
#include "src/dbus-common.h"
#include "src/error.h"
#include "src/sdp-client.h"
#include "src/shared/uhid.h"
#include "device.h"
#include "hidp_defs.h"
#include "uhid_copy.h"
#define INPUT_INTERFACE "org.bluez.Input1"
#define UHID_DEVICE_FILE "/dev/uhid"
enum reconnect_mode_t {
RECONNECT_NONE = 0,
RECONNECT_DEVICE,
@ -86,9 +84,7 @@ struct input_device {
enum reconnect_mode_t reconnect_mode;
guint reconnect_timer;
uint32_t reconnect_attempt;
bool uhid_enabled;
int uhid_fd;
guint uhid_watch;
struct bt_uhid *uhid;
bool uhid_created;
uint8_t report_req_pending;
guint report_req_timer;
@ -116,6 +112,22 @@ static void input_device_free(struct input_device *idev)
if (idev->dc_id)
device_remove_disconnect_watch(idev->device, idev->dc_id);
if (idev->uhid) {
if (idev->uhid_created) {
int err;
struct uhid_event ev;
memset(&ev, 0, sizeof(ev));
ev.type = UHID_DESTROY;
err = bt_uhid_send(idev->uhid, &ev);
if (err < 0)
error("bt_uhid_send: %s (%d)", strerror(-err),
-err);
}
bt_uhid_unref(idev->uhid);
}
btd_service_unref(idev->service);
btd_device_unref(idev->device);
g_free(idev->path);
@ -198,7 +210,7 @@ static bool uhid_send_feature_answer(struct input_device *idev,
uint32_t id, uint16_t err)
{
struct uhid_event ev;
ssize_t len;
int ret;
if (data == NULL)
size = 0;
@ -220,20 +232,13 @@ static bool uhid_send_feature_answer(struct input_device *idev,
if (size > 0)
memcpy(ev.u.feature_answer.data, data, size);
len = write(idev->uhid_fd, &ev, sizeof(ev));
if (len < 0) {
error("uHID dev write error: %s (%d)", strerror(errno), errno);
ret = bt_uhid_send(idev->uhid, &ev);
if (ret < 0) {
error("bt_uhid_send: %s (%d)", strerror(-ret), -ret);
return false;
}
/* uHID kernel driver does not handle partial writes */
if ((size_t) len < sizeof(ev)) {
error("uHID dev write error: partial write (%zd of %zu bytes)",
len, sizeof(ev));
return false;
}
DBG("HID report (%zu bytes) -> uHID fd %d", size, idev->uhid_fd);
DBG("HID report (%zu bytes)", size);
return true;
}
@ -242,7 +247,7 @@ static bool uhid_send_input_report(struct input_device *idev,
const uint8_t *data, size_t size)
{
struct uhid_event ev;
ssize_t len;
int err;
if (data == NULL)
size = 0;
@ -262,20 +267,13 @@ static bool uhid_send_input_report(struct input_device *idev,
if (size > 0)
memcpy(ev.u.input.data, data, size);
len = write(idev->uhid_fd, &ev, sizeof(ev));
if (len < 0) {
error("uHID dev write error: %s (%d)", strerror(errno), errno);
err = bt_uhid_send(idev->uhid, &ev);
if (err < 0) {
error("bt_uhid_send: %s (%d)", strerror(-err), -err);
return false;
}
/* uHID kernel driver does not handle partial writes */
if ((size_t) len < sizeof(ev)) {
error("uHID dev write error: partial write (%zd of %zu bytes)",
len, sizeof(ev));
return false;
}
DBG("HID report (%zu bytes) -> uHID fd %d", size, idev->uhid_fd);
DBG("HID report (%zu bytes)", size);
return true;
}
@ -580,9 +578,9 @@ static gboolean hidp_report_req_timeout(gpointer data)
return FALSE;
}
static void hidp_send_set_report(struct input_device *idev,
struct uhid_event *ev)
static void hidp_send_set_report(struct uhid_event *ev, void *user_data)
{
struct input_device *idev = user_data;
uint8_t hdr;
bool sent;
@ -618,9 +616,9 @@ static void hidp_send_set_report(struct input_device *idev,
}
}
static void hidp_send_get_report(struct input_device *idev,
struct uhid_event *ev)
static void hidp_send_get_report(struct uhid_event *ev, void *user_data)
{
struct input_device *idev = user_data;
uint8_t hdr;
bool sent;
@ -660,91 +658,6 @@ static void hidp_send_get_report(struct input_device *idev,
}
}
static gboolean uhid_watch_cb(GIOChannel *chan, GIOCondition cond,
gpointer user_data)
{
struct input_device *idev = user_data;
int fd;
ssize_t len;
struct uhid_event ev;
if (cond & (G_IO_ERR | G_IO_NVAL))
goto failed;
fd = g_io_channel_unix_get_fd(chan);
memset(&ev, 0, sizeof(ev));
len = read(fd, &ev, sizeof(ev));
if (len < 0) {
error("uHID dev read error: %s (%d)", strerror(errno), errno);
goto failed;
}
if ((size_t) len < sizeof(ev.type)) {
error("uHID dev read returned too few bytes");
goto failed;
}
DBG("uHID event type %u received (%zd bytes)", ev.type, len);
switch (ev.type) {
case UHID_START:
case UHID_STOP:
/* These are called to start and stop the underlying hardware.
* For HID we open the channels before creating the device so
* the hardware is always ready. No need to handle these.
* Note that these are also called when the kernel switches
* between device-drivers loaded on the HID device. But we can
* simply keep the hardware alive during transitions and it
* works just fine.
* The kernel never destroys a device itself! Only an explicit
* UHID_DESTROY request can remove a device.
*/
break;
case UHID_OPEN:
case UHID_CLOSE:
/* OPEN/CLOSE are sent whenever user-space opens any interface
* provided by the kernel HID device. Whenever the open-count
* is non-zero we must be ready for I/O. As long as it is zero,
* we can decide to drop all I/O and put the device
* asleep This is optional, though. Moreover, some
* special device drivers are buggy in that regard, so
* maybe we just keep I/O always awake like HIDP in the
* kernel does.
*/
break;
case UHID_OUTPUT:
hidp_send_set_report(idev, &ev);
break;
case UHID_FEATURE:
hidp_send_get_report(idev, &ev);
break;
case UHID_OUTPUT_EV:
/* This is only sent by kernels prior to linux-3.11. It
* requires us to parse HID-descriptors in user-space to
* properly handle it. This is redundant as the kernel
* does it already. That's why newer kernels assemble
* the output-reports and send it to us via UHID_OUTPUT.
* We never implemented this, so we rely on users to use
* recent-enough kernels if they want this feature. No reason
* to implement this for older kernels.
*/
DBG("Unsupported uHID output event: type %u code %u value %d",
ev.u.output_ev.type, ev.u.output_ev.code,
ev.u.output_ev.value);
break;
default:
warn("unexpected uHID event");
break;
}
return TRUE;
failed:
idev->uhid_watch = 0;
return FALSE;
}
static void epox_endian_quirk(unsigned char *data, int size)
{
/* USAGE_PAGE (Keyboard) 05 07
@ -949,33 +862,38 @@ static int ioctl_disconnect(struct input_device *idev, uint32_t flags)
static int uhid_connadd(struct input_device *idev, struct hidp_connadd_req *req)
{
int err = 0;
int err;
struct uhid_event ev;
if (!idev->uhid_created) {
/* create uHID device */
memset(&ev, 0, sizeof(ev));
ev.type = UHID_CREATE;
strncpy((char *) ev.u.create.name, req->name,
sizeof(ev.u.create.name) - 1);
ba2str(&idev->src, (char *) ev.u.create.phys);
ba2str(&idev->dst, (char *) ev.u.create.uniq);
ev.u.create.vendor = req->vendor;
ev.u.create.product = req->product;
ev.u.create.version = req->version;
ev.u.create.country = req->country;
ev.u.create.bus = BUS_BLUETOOTH;
ev.u.create.rd_data = req->rd_data;
ev.u.create.rd_size = req->rd_size;
if (idev->uhid_created)
return 0;
if (write(idev->uhid_fd, &ev, sizeof(ev)) < 0) {
err = -errno;
error("Failed to create uHID device: %s (%d)",
strerror(-err), -err);
} else
idev->uhid_created = true;
/* create uHID device */
memset(&ev, 0, sizeof(ev));
ev.type = UHID_CREATE;
strncpy((char *) ev.u.create.name, req->name,
sizeof(ev.u.create.name) - 1);
ba2str(&idev->src, (char *) ev.u.create.phys);
ba2str(&idev->dst, (char *) ev.u.create.uniq);
ev.u.create.vendor = req->vendor;
ev.u.create.product = req->product;
ev.u.create.version = req->version;
ev.u.create.country = req->country;
ev.u.create.bus = BUS_BLUETOOTH;
ev.u.create.rd_data = req->rd_data;
ev.u.create.rd_size = req->rd_size;
err = bt_uhid_send(idev->uhid, &ev);
if (err < 0) {
error("bt_uhid_send: %s", strerror(-err));
return err;
}
bt_uhid_register(idev->uhid, UHID_OUTPUT, hidp_send_set_report, idev);
bt_uhid_register(idev->uhid, UHID_FEATURE, hidp_send_get_report, idev);
idev->uhid_created = true;
return err;
}
@ -987,7 +905,7 @@ static gboolean encrypt_notify(GIOChannel *io, GIOCondition condition,
DBG("");
if (idev->uhid_enabled)
if (idev->uhid)
err = uhid_connadd(idev, idev->req);
else
err = ioctl_connadd(idev->req);
@ -1089,7 +1007,7 @@ static int hidp_add_connection(struct input_device *idev)
return 0;
}
if (idev->uhid_enabled)
if (idev->uhid)
err = uhid_connadd(idev, req);
else
err = ioctl_connadd(req);
@ -1103,7 +1021,7 @@ cleanup:
static bool is_connected(struct input_device *idev)
{
if (idev->uhid_enabled)
if (idev->uhid)
return (idev->intr_io != NULL && idev->ctrl_io != NULL);
else
return ioctl_is_connected(idev);
@ -1120,7 +1038,7 @@ static int connection_disconnect(struct input_device *idev, uint32_t flags)
if (idev->ctrl_io)
g_io_channel_shutdown(idev->ctrl_io, TRUE, NULL);
if (idev->uhid_enabled)
if (idev->uhid)
return 0;
else
return ioctl_disconnect(idev, flags);
@ -1174,7 +1092,7 @@ static void interrupt_connect_cb(GIOChannel *chan, GError *conn_err,
if (err < 0)
goto failed;
if (idev->uhid_enabled)
if (idev->uhid)
cond |= G_IO_IN;
idev->intr_watch = g_io_add_watch(idev->intr_io, cond, intr_watch_cb,
@ -1229,7 +1147,7 @@ static void control_connect_cb(GIOChannel *chan, GError *conn_err,
idev->intr_io = io;
if (idev->uhid_enabled)
if (idev->uhid)
cond |= G_IO_IN;
idev->ctrl_watch = g_io_add_watch(idev->ctrl_io, cond, ctrl_watch_cb,
@ -1435,7 +1353,6 @@ static struct input_device *input_device_new(struct btd_service *service)
idev->path = g_strdup(path);
idev->handle = rec->handle;
idev->disable_sdp = is_device_sdp_disable(rec);
idev->uhid_enabled = uhid_enabled;
/* Initialize device properties */
extract_hid_props(idev, rec);
@ -1465,8 +1382,6 @@ int input_device_register(struct btd_service *service)
struct btd_device *device = btd_service_get_device(service);
const char *path = device_get_path(device);
struct input_device *idev;
int err;
GIOChannel *io;
DBG("%s", path);
@ -1474,22 +1389,13 @@ int input_device_register(struct btd_service *service)
if (!idev)
return -EINVAL;
if (idev->uhid_enabled) {
idev->uhid_fd = open(UHID_DEVICE_FILE, O_RDWR | O_CLOEXEC);
if (idev->uhid_fd < 0) {
err = errno;
error("Failed to open uHID device: %s (%d)",
strerror(err), err);
if (uhid_enabled) {
idev->uhid = bt_uhid_new_default();
if (!idev->uhid) {
error("bt_uhid_new_default: failed");
input_device_free(idev);
return -err;
return -EIO;
}
io = g_io_channel_unix_new(idev->uhid_fd);
g_io_channel_set_encoding(io, NULL, NULL);
idev->uhid_watch = g_io_add_watch(io,
G_IO_IN | G_IO_ERR | G_IO_NVAL,
uhid_watch_cb, idev);
g_io_channel_unref(io);
}
if (g_dbus_register_interface(btd_get_dbus_connection(),
@ -1529,31 +1435,12 @@ void input_device_unregister(struct btd_service *service)
struct btd_device *device = btd_service_get_device(service);
const char *path = device_get_path(device);
struct input_device *idev = btd_service_get_user_data(service);
struct uhid_event ev;
DBG("%s", path);
g_dbus_unregister_interface(btd_get_dbus_connection(),
idev->path, INPUT_INTERFACE);
if (idev->uhid_enabled) {
if (idev->uhid_watch) {
g_source_remove(idev->uhid_watch);
idev->uhid_watch = 0;
}
if (idev->uhid_created) {
memset(&ev, 0, sizeof(ev));
ev.type = UHID_DESTROY;
if (write(idev->uhid_fd, &ev, sizeof(ev)) < 0)
error("Failed to destroy uHID device: %s (%d)",
strerror(errno), errno);
}
close(idev->uhid_fd);
idev->uhid_fd = -1;
}
input_device_free(idev);
}
@ -1599,7 +1486,7 @@ int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm,
if (!idev)
return -ENOENT;
if (idev->uhid_enabled)
if (uhid_enabled)
cond |= G_IO_IN;
switch (psm) {