summaryrefslogtreecommitdiff
path: root/profiles/input/hog.c
diff options
context:
space:
mode:
Diffstat (limited to 'profiles/input/hog.c')
-rw-r--r--profiles/input/hog.c275
1 files changed, 90 insertions, 185 deletions
diff --git a/profiles/input/hog.c b/profiles/input/hog.c
index e82e827e1..47a4fb1f9 100644
--- a/profiles/input/hog.c
+++ b/profiles/input/hog.c
@@ -34,7 +34,6 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
-#include "uhid_copy.h"
#include <bluetooth/bluetooth.h>
@@ -48,6 +47,7 @@
#include "src/profile.h"
#include "src/service.h"
#include "src/shared/util.h"
+#include "src/shared/uhid.h"
#include "src/plugin.h"
@@ -72,8 +72,6 @@
#define HOG_PROTO_MODE_BOOT 0
#define HOG_PROTO_MODE_REPORT 1
-#define UHID_DEVICE_FILE "/dev/uhid"
-
#define HOG_REPORT_MAP_MAX_SIZE 512
#define HID_INFO_SIZE 4
#define ATT_NOTIFICATION_HEADER_SIZE 3
@@ -85,9 +83,8 @@ struct hog_device {
guint attioid;
struct gatt_primary *hog_primary;
GSList *reports;
- int uhid_fd;
+ struct bt_uhid *uhid;
gboolean has_report_id;
- guint uhid_watch_id;
uint16_t bcdhid;
uint8_t bcountrycode;
uint16_t proto_mode_handle;
@@ -112,7 +109,7 @@ static void report_value_cb(const guint8 *pdu, guint16 len, gpointer user_data)
struct hog_device *hogdev = report->hogdev;
struct uhid_event ev;
uint8_t *buf;
- ssize_t status;
+ int err;
if (len < ATT_NOTIFICATION_HEADER_SIZE) {
error("Malformed ATT notification");
@@ -137,21 +134,13 @@ static void report_value_cb(const guint8 *pdu, guint16 len, gpointer user_data)
ev.u.input.size = len;
}
- status = write(hogdev->uhid_fd, &ev, sizeof(ev));
- if (status < 0) {
- error("uHID dev write error: %s (%d)", strerror(errno), errno);
- return;
- }
-
- /* uHID kernel driver does not handle partial writes */
- if ((size_t) status < sizeof(ev)) {
- error("uHID dev write error: partial write (%zd of %zu bytes)",
- status, sizeof(ev));
+ err = bt_uhid_send(hogdev->uhid, &ev);
+ if (err < 0) {
+ error("bt_uhid_send: %s (%d)", strerror(-err), -err);
return;
}
- DBG("HoG report (%u bytes) -> uHID fd %d", ev.u.input.size,
- hogdev->uhid_fd);
+ DBG("HoG report (%u bytes)", ev.u.input.size);
}
static void report_ccc_written_cb(guint8 status, const guint8 *pdu,
@@ -317,6 +306,72 @@ static void external_report_reference_cb(guint8 status, const guint8 *pdu,
external_service_char_cb, hogdev);
}
+static int report_type_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct report *report = a;
+ uint8_t type = GPOINTER_TO_UINT(b);
+
+ return report->type - type;
+}
+
+static void output_written_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data)
+{
+ if (status != 0) {
+ error("Write output report failed: %s", att_ecode2str(status));
+ return;
+ }
+}
+
+static void forward_report(struct uhid_event *ev, void *user_data)
+{
+ struct hog_device *hogdev = user_data;
+ struct report *report;
+ GSList *l;
+ void *data;
+ int size;
+ guint type;
+
+ if (hogdev->has_report_id) {
+ data = ev->u.output.data + 1;
+ size = ev->u.output.size - 1;
+ } else {
+ data = ev->u.output.data;
+ size = ev->u.output.size;
+ }
+
+ switch (ev->type) {
+ case UHID_OUTPUT:
+ type = HOG_REPORT_TYPE_OUTPUT;
+ break;
+ case UHID_FEATURE:
+ type = HOG_REPORT_TYPE_FEATURE;
+ break;
+ default:
+ return;
+ }
+
+ l = g_slist_find_custom(hogdev->reports, GUINT_TO_POINTER(type),
+ report_type_cmp);
+ if (!l)
+ return;
+
+ report = l->data;
+
+ DBG("Sending report type %d to device 0x%04X handle 0x%X", type,
+ hogdev->id, report->decl->value_handle);
+
+ if (hogdev->attrib == NULL)
+ return;
+
+ if (report->decl->properties & GATT_CHR_PROP_WRITE)
+ gatt_write_char(hogdev->attrib, report->decl->value_handle,
+ data, size, output_written_cb, hogdev);
+ else if (report->decl->properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP)
+ gatt_write_cmd(hogdev->attrib, report->decl->value_handle,
+ data, size, NULL, NULL);
+}
+
static bool get_descriptor_item_info(uint8_t *buf, ssize_t blen, ssize_t *len,
bool *is_long)
{
@@ -389,7 +444,7 @@ static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
uint16_t vendor_src, vendor, product, version;
ssize_t vlen;
char itemstr[20]; /* 5x3 (data) + 4 (continuation) + 1 (null) */
- int i;
+ int i, err;
if (status != 0) {
error("Report Map read failed: %s", att_ecode2str(status));
@@ -450,8 +505,14 @@ static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
ev.u.create.rd_data = value;
ev.u.create.rd_size = vlen;
- if (write(hogdev->uhid_fd, &ev, sizeof(ev)) < 0)
- error("Failed to create uHID device: %s", strerror(errno));
+ err = bt_uhid_send(hogdev->uhid, &ev);
+ if (err < 0) {
+ error("bt_uhid_send: %s", strerror(-err));
+ return;
+ }
+
+ bt_uhid_register(hogdev->uhid, UHID_OUTPUT, forward_report, hogdev);
+ bt_uhid_register(hogdev->uhid, UHID_FEATURE, forward_report, hogdev);
}
static void info_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
@@ -581,148 +642,6 @@ static void char_discovered_cb(uint8_t status, GSList *chars, void *user_data)
hogdev);
}
-static void output_written_cb(guint8 status, const guint8 *pdu,
- guint16 plen, gpointer user_data)
-{
- if (status != 0) {
- error("Write output report failed: %s", att_ecode2str(status));
- return;
- }
-}
-
-static int report_type_cmp(gconstpointer a, gconstpointer b)
-{
- const struct report *report = a;
- uint8_t type = GPOINTER_TO_UINT(b);
-
- return report->type - type;
-}
-
-static void forward_report(struct hog_device *hogdev,
- struct uhid_event *ev)
-{
- struct report *report;
- GSList *l;
- void *data;
- int size;
- guint type;
-
- if (hogdev->has_report_id) {
- data = ev->u.output.data + 1;
- size = ev->u.output.size - 1;
- } else {
- data = ev->u.output.data;
- size = ev->u.output.size;
- }
-
- switch (ev->type) {
- case UHID_OUTPUT:
- type = HOG_REPORT_TYPE_OUTPUT;
- break;
- case UHID_FEATURE:
- type = HOG_REPORT_TYPE_FEATURE;
- break;
- default:
- return;
- }
-
- l = g_slist_find_custom(hogdev->reports, GUINT_TO_POINTER(type),
- report_type_cmp);
- if (!l)
- return;
-
- report = l->data;
-
- DBG("Sending report type %d to device 0x%04X handle 0x%X", type,
- hogdev->id, report->decl->value_handle);
-
- if (hogdev->attrib == NULL)
- return;
-
- if (report->decl->properties & GATT_CHR_PROP_WRITE)
- gatt_write_char(hogdev->attrib, report->decl->value_handle,
- data, size, output_written_cb, hogdev);
- else if (report->decl->properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP)
- gatt_write_cmd(hogdev->attrib, report->decl->value_handle,
- data, size, NULL, NULL);
-}
-
-static gboolean uhid_event_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
-{
- struct hog_device *hogdev = user_data;
- struct uhid_event ev;
- ssize_t bread;
- int fd;
-
- if (cond & (G_IO_ERR | G_IO_NVAL))
- goto failed;
-
- fd = g_io_channel_unix_get_fd(io);
- memset(&ev, 0, sizeof(ev));
-
- bread = read(fd, &ev, sizeof(ev));
- if (bread < 0) {
- int err = -errno;
- DBG("uhid-dev read: %s(%d)", strerror(-err), -err);
- goto failed;
- }
-
- DBG("uHID event type %d received", ev.type);
-
- switch (ev.type) {
- case UHID_START:
- case UHID_STOP:
- /* These are called to start and stop the underlying hardware.
- * For HoG 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:
- case UHID_FEATURE:
- forward_report(hogdev, &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 %d code %d 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:
- hogdev->uhid_watch_id = 0;
- return FALSE;
-}
-
static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
{
struct hog_device *hogdev = user_data;
@@ -807,27 +726,18 @@ static struct hog_device *hog_register_device(struct btd_device *device,
struct gatt_primary *prim)
{
struct hog_device *hogdev;
- GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL;
- GIOChannel *io;
hogdev = hog_new_device(device, prim->range.start);
if (!hogdev)
return NULL;
- hogdev->uhid_fd = open(UHID_DEVICE_FILE, O_RDWR | O_CLOEXEC);
- if (hogdev->uhid_fd < 0) {
- error("Failed to open uHID device: %s(%d)", strerror(errno),
- errno);
+ hogdev->uhid = bt_uhid_new_default();
+ if (!hogdev->uhid) {
+ error("bt_uhid_new_default: failed");
hog_free_device(hogdev);
return NULL;
}
- io = g_io_channel_unix_new(hogdev->uhid_fd);
- g_io_channel_set_encoding(io, NULL, NULL);
- hogdev->uhid_watch_id = g_io_add_watch(io, cond, uhid_event_cb,
- hogdev);
- g_io_channel_unref(io);
-
hogdev->hog_primary = g_memdup(prim, sizeof(*prim));
hogdev->attioid = btd_device_add_attio_callback(device,
@@ -841,22 +751,17 @@ static struct hog_device *hog_register_device(struct btd_device *device,
static int hog_unregister_device(struct hog_device *hogdev)
{
struct uhid_event ev;
+ int err;
btd_device_remove_attio_callback(hogdev->device, hogdev->attioid);
- if (hogdev->uhid_watch_id) {
- g_source_remove(hogdev->uhid_watch_id);
- hogdev->uhid_watch_id = 0;
- }
-
memset(&ev, 0, sizeof(ev));
ev.type = UHID_DESTROY;
- if (write(hogdev->uhid_fd, &ev, sizeof(ev)) < 0)
- error("Failed to destroy uHID device: %s", strerror(errno));
-
- close(hogdev->uhid_fd);
- hogdev->uhid_fd = -1;
+ err = bt_uhid_send(hogdev->uhid, &ev);
+ if (err < 0)
+ error("bt_uhid_send: %s (%d)", strerror(-err), -err);
+ bt_uhid_unref(hogdev->uhid);
hog_free_device(hogdev);
return 0;