From: Luiz Augusto von Dentz <luiz.von.dentz@xxxxxxxxx> If we cannot sent a message back to the device destroy the UHID device since it is likely that the driver is trying to reach the remote device which is no longer connected. Fixes: https://github.com/bluez/bluez/issues/777 --- profiles/input/device.c | 65 +++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/profiles/input/device.c b/profiles/input/device.c index c4f75c7442e8..1b28cdc174b1 100644 --- a/profiles/input/device.c +++ b/profiles/input/device.c @@ -105,7 +105,6 @@ bool input_get_classic_bonded_only(void) static void input_device_enter_reconnect_mode(struct input_device *idev); static int connection_disconnect(struct input_device *idev, uint32_t flags); -static int uhid_disconnect(struct input_device *idev); static bool input_device_bonded(struct input_device *idev) { @@ -314,6 +313,28 @@ static bool hidp_recv_intr_data(GIOChannel *chan, struct input_device *idev) return true; } +static int uhid_disconnect(struct input_device *idev, bool force) +{ + int err; + + if (!bt_uhid_created(idev->uhid)) + return 0; + + /* Only destroy the node if virtual cable unplug flag has been set */ + if (!idev->virtual_cable_unplug && !force) + return 0; + + bt_uhid_unregister_all(idev->uhid); + + err = bt_uhid_destroy(idev->uhid); + if (err < 0) { + error("bt_uhid_destroy: %s", strerror(-err)); + return err; + } + + return err; +} + static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data) { struct input_device *idev = data; @@ -356,8 +377,7 @@ static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data virtual_cable_unplug(idev); /* If connection abruptly ended, uhid might be not yet disconnected */ - if (bt_uhid_created(idev->uhid)) - uhid_disconnect(idev); + uhid_disconnect(idev, false); return FALSE; } @@ -610,11 +630,14 @@ static void hidp_send_output(struct uhid_event *ev, void *user_data) { struct input_device *idev = user_data; uint8_t hdr = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUTPUT; + bool sent; DBG(""); - hidp_send_intr_message(idev, hdr, ev->u.output.data, + sent = hidp_send_intr_message(idev, hdr, ev->u.output.data, ev->u.output.size); + if (!sent) + uhid_disconnect(idev, true); } static void hidp_send_set_report(struct uhid_event *ev, void *user_data) @@ -654,8 +677,11 @@ static void hidp_send_set_report(struct uhid_event *ev, void *user_data) timeout_add_seconds(REPORT_REQ_TIMEOUT, hidp_report_req_timeout, idev, NULL); idev->report_rsp_id = ev->u.set_report.id; - } else + } else { uhid_send_set_report_reply(idev, ev->u.set_report.id, EIO); + /* Force UHID_DESTROY on error */ + uhid_disconnect(idev, true); + } } static void hidp_send_get_report(struct uhid_event *ev, void *user_data) @@ -698,9 +724,12 @@ static void hidp_send_get_report(struct uhid_event *ev, void *user_data) hidp_report_req_timeout, idev, NULL); idev->report_rsp_id = ev->u.get_report.id; - } else + } else { uhid_send_get_report_reply(idev, NULL, 0, ev->u.get_report.id, EIO); + /* Force UHID_DESTROY on error */ + uhid_disconnect(idev, true); + } } static void epox_endian_quirk(unsigned char *data, int size) @@ -934,28 +963,6 @@ static int uhid_connadd(struct input_device *idev, struct hidp_connadd_req *req) return err; } -static int uhid_disconnect(struct input_device *idev) -{ - int err; - - if (!bt_uhid_created(idev->uhid)) - return 0; - - /* Only destroy the node if virtual cable unplug flag has been set */ - if (!idev->virtual_cable_unplug) - return 0; - - bt_uhid_unregister_all(idev->uhid); - - err = bt_uhid_destroy(idev->uhid); - if (err < 0) { - error("bt_uhid_destroy: %s", strerror(-err)); - return err; - } - - return err; -} - static gboolean encrypt_notify(GIOChannel *io, GIOCondition condition, gpointer data) { @@ -1087,7 +1094,7 @@ static int connection_disconnect(struct input_device *idev, uint32_t flags) idev->virtual_cable_unplug = true; if (idev->uhid) - return uhid_disconnect(idev); + return uhid_disconnect(idev, false); else return ioctl_disconnect(idev, flags); } -- 2.44.0