/
githubmirror
/
fwupd
Обзор
Документация
Войти
/
githubmirror
/
fwupd
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
plugins/logitech-bulkcontroller/fu-logitech-bulkcontroller-device.c
1 680 строк
54 KB
Peter Hutterer
treewide: Use FuInputStream throughout
13 июл 2026, 22:08
13 июл 2026, 22:08
54f29d2
Код
Авторство
О чём код?
/* * Copyright 1999-2021 Logitech, Inc. * Copyright 2021 Richard Hughes <richard@hughsie.com> * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include <string.h> #include "fu-logitech-bulkcontroller-child.h" #include "fu-logitech-bulkcontroller-device.h" #include "fu-logitech-bulkcontroller-struct.h" #define HASH_TIMEOUT 30000 #define UPD_INTERFACE_SUBPROTOCOL_ID 117 #define SYNC_INTERFACE_SUBPROTOCOL_ID 118 #define BULK_TRANSFER_TIMEOUT 2500 #define BULK_TRANSFER_FLUSH_TIMEOUT 5 #define HASH_VALUE_SIZE 16 #define MAX_RETRIES 5 #define MAX_SETUP_RETRIES 50 #define MAX_WAIT_COUNT 150 #define POST_INSTALL_SLEEP_DURATION (80 * 1000) /* ms */ enum { EP_OUT, EP_IN, EP_LAST }; struct _FuLogitechBulkcontrollerDevice { FuUsbDevice parent_instance; guint sync_ep[EP_LAST]; guint update_ep[EP_LAST]; guint sync_iface; guint update_iface; FuLogitechBulkcontrollerDeviceState status; FuLogitechBulkcontrollerUpdateState update_status; guint update_progress; /* percentage value */ gboolean is_sync_flush_events_in_progress; GString *device_info_response_json; gsize transfer_bufsz; guint32 sequence_id; }; G_DEFINE_TYPE(FuLogitechBulkcontrollerDevice, fu_logitech_bulkcontroller_device, FU_TYPE_USB_DEVICE) static void fu_logitech_bulkcontroller_device_to_string(FuDevice *device, guint idt, GString *str) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); fwupd_codec_string_append_hex(str, idt, "BufferSize", self->transfer_bufsz); fwupd_codec_string_append_hex(str, idt, "SyncIface", self->sync_iface); fwupd_codec_string_append_hex(str, idt, "UpdateIface", self->update_iface); fwupd_codec_string_append(str, idt, "State", fu_logitech_bulkcontroller_device_state_to_string(self->status)); fwupd_codec_string_append( str, idt, "UpdateState", fu_logitech_bulkcontroller_update_state_to_string(self->update_status)); if (self->device_info_response_json->len > 0) { fwupd_codec_string_append(str, idt, "DeviceInfoResponse", self->device_info_response_json->str); } fwupd_codec_string_append_hex(str, idt, "SequenceId", self->sequence_id); } static gboolean fu_logitech_bulkcontroller_device_probe(FuDevice *device, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); g_autoptr(GPtrArray) intfs = NULL; intfs = fu_usb_device_get_interfaces(FU_USB_DEVICE(self), error); if (intfs == NULL) return FALSE; for (guint i = 0; i < intfs->len; i++) { FuUsbInterface *intf = g_ptr_array_index(intfs, i); if (fu_usb_interface_get_class(intf) == FU_USB_CLASS_VENDOR_SPECIFIC && fu_usb_interface_get_protocol(intf) == 0x1) { if (fu_usb_interface_get_subclass(intf) == SYNC_INTERFACE_SUBPROTOCOL_ID) { g_autoptr(GPtrArray) endpoints = fu_usb_interface_get_endpoints(intf); self->sync_iface = fu_usb_interface_get_number(intf); if (endpoints == NULL) continue; for (guint j = 0; j < endpoints->len; j++) { FuUsbEndpoint *ep = g_ptr_array_index(endpoints, j); if (j == EP_OUT) self->sync_ep[EP_OUT] = fu_usb_endpoint_get_address(ep); else self->sync_ep[EP_IN] = fu_usb_endpoint_get_address(ep); } } else if (fu_usb_interface_get_subclass(intf) == UPD_INTERFACE_SUBPROTOCOL_ID) { g_autoptr(GPtrArray) endpoints = fu_usb_interface_get_endpoints(intf); self->update_iface = fu_usb_interface_get_number(intf); if (endpoints == NULL) continue; for (guint j = 0; j < endpoints->len; j++) { FuUsbEndpoint *ep = g_ptr_array_index(endpoints, j); if (j == EP_OUT) self->update_ep[EP_OUT] = fu_usb_endpoint_get_address(ep); else self->update_ep[EP_IN] = fu_usb_endpoint_get_address(ep); } } } } fu_usb_device_add_interface(FU_USB_DEVICE(self), self->update_iface); fu_usb_device_add_interface(FU_USB_DEVICE(self), self->sync_iface); return TRUE; } typedef struct { FuLogitechBulkcontrollerCmd cmd; guint32 sequence_id; GByteArray *data; guint timeout; } FuLogitechBulkcontrollerResponse; static FuLogitechBulkcontrollerResponse * fu_logitech_bulkcontroller_device_response_new(void) { FuLogitechBulkcontrollerResponse *response = g_new0(FuLogitechBulkcontrollerResponse, 1); response->data = g_byte_array_new(); return response; } static void fu_logitech_bulkcontroller_device_response_free(FuLogitechBulkcontrollerResponse *response) { if (response->data != NULL) g_byte_array_unref(response->data); g_free(response); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuLogitechBulkcontrollerResponse, fu_logitech_bulkcontroller_device_response_free) static gboolean fu_logitech_bulkcontroller_device_sync_send_cmd(FuLogitechBulkcontrollerDevice *self, FuLogitechBulkcontrollerCmd cmd, GByteArray *buf, guint timeout, GError **error) { g_autoptr(FuStructLogitechBulkcontrollerSendSyncReq) st_req = fu_struct_logitech_bulkcontroller_send_sync_req_new(); g_autofree gchar *str = NULL; /* increment */ self->sequence_id++; /* send */ fu_struct_logitech_bulkcontroller_send_sync_req_set_cmd(st_req, cmd); fu_struct_logitech_bulkcontroller_send_sync_req_set_sequence_id(st_req, self->sequence_id); if (buf != NULL) { fu_struct_logitech_bulkcontroller_send_sync_req_set_payload_length(st_req, buf->len); g_byte_array_append(st_req->buf, buf->data, buf->len); } str = fu_struct_logitech_bulkcontroller_send_sync_req_to_string(st_req); g_debug("sending: %s", str); if (!fu_usb_device_bulk_transfer(FU_USB_DEVICE(self), self->sync_ep[EP_OUT], st_req->buf->data, st_req->buf->len, NULL, /* transferred */ timeout, NULL, error)) { g_prefix_error_literal(error, "failed to send sync bulk transfer: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_sync_send_ack(FuLogitechBulkcontrollerDevice *self, FuLogitechBulkcontrollerCmd cmd, guint timeout, GError **error) { g_autoptr(GByteArray) buf_ack = g_byte_array_new(); fu_byte_array_append_uint32(buf_ack, cmd, G_LITTLE_ENDIAN); if (!fu_logitech_bulkcontroller_device_sync_send_cmd(self, FU_LOGITECH_BULKCONTROLLER_CMD_ACK, buf_ack, timeout, error)) { g_prefix_error(error, "failed to send ack for %s: ", fu_logitech_bulkcontroller_cmd_to_string(cmd)); return FALSE; } return TRUE; } static FuLogitechBulkcontrollerResponse * fu_logitech_bulkcontroller_device_sync_wait_any(FuLogitechBulkcontrollerDevice *self, guint timeout, GError **error) { gsize actual_length = 0; g_autofree guint8 *buf = g_malloc0(self->transfer_bufsz); g_autoptr(FuStructLogitechBulkcontrollerSendSyncRes) st = NULL; g_autoptr(FuLogitechBulkcontrollerResponse) response = fu_logitech_bulkcontroller_device_response_new(); if (!fu_usb_device_bulk_transfer(FU_USB_DEVICE(self), self->sync_ep[EP_IN], buf, self->transfer_bufsz, &actual_length, timeout, NULL, error)) { g_prefix_error_literal(error, "failed to receive: "); return NULL; } fu_dump_raw(G_LOG_DOMAIN, "response", buf, MIN(actual_length, 12)); st = fu_struct_logitech_bulkcontroller_send_sync_res_parse(buf, self->transfer_bufsz, 0x0, error); if (st == NULL) return NULL; response->cmd = fu_struct_logitech_bulkcontroller_send_sync_res_get_cmd(st); response->sequence_id = fu_struct_logitech_bulkcontroller_send_sync_res_get_sequence_id(st); if (!fu_byte_array_append_safe( response->data, buf, self->transfer_bufsz, st->buf->len, /* offset */ fu_struct_logitech_bulkcontroller_send_sync_res_get_payload_length(st), error)) return NULL; /* no payload for UninitBuffer, skip check */ if ((response->cmd != FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT_BUFFER) && (response->data->len == 0)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to receive packet"); return NULL; } return g_steal_pointer(&response); } static GByteArray * fu_logitech_bulkcontroller_device_sync_wait_cmd(FuLogitechBulkcontrollerDevice *self, FuLogitechBulkcontrollerCmd cmd, guint32 sequence_id, guint timeout, GError **error) { g_autoptr(FuLogitechBulkcontrollerResponse) response = NULL; response = fu_logitech_bulkcontroller_device_sync_wait_any(self, timeout, error); if (response == NULL) return NULL; if (response->cmd != cmd) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "command invalid, expected %s and got %s", fu_logitech_bulkcontroller_cmd_to_string(cmd), fu_logitech_bulkcontroller_cmd_to_string(response->cmd)); return NULL; } /* verify the sequence ID */ if (response->sequence_id != sequence_id) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "sequence ID invalid, expected 0x%04x and got 0x%04x", sequence_id, response->sequence_id); return NULL; } /* success */ return g_steal_pointer(&response->data); } static gboolean fu_logitech_bulkcontroller_device_sync_wait_cmd_retry_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); FuLogitechBulkcontrollerResponse *helper = (FuLogitechBulkcontrollerResponse *)user_data; helper->data = fu_logitech_bulkcontroller_device_sync_wait_cmd(self, helper->cmd, helper->sequence_id, helper->timeout, error); if (helper->data == NULL) return FALSE; /* success */ return TRUE; } static GByteArray * fu_logitech_bulkcontroller_device_sync_wait_cmd_retry(FuLogitechBulkcontrollerDevice *self, FuLogitechBulkcontrollerCmd cmd, guint32 sequence_id, guint timeout, GError **error) { FuLogitechBulkcontrollerResponse helper = {.cmd = cmd, .sequence_id = sequence_id, .timeout = timeout}; if (!fu_device_retry(FU_DEVICE(self), fu_logitech_bulkcontroller_device_sync_wait_cmd_retry_cb, MAX_RETRIES, &helper, error)) return NULL; return helper.data; } static gboolean fu_logitech_bulkcontroller_device_sync_check_ack_cmd(GByteArray *buf, FuLogitechBulkcontrollerCmd cmd, GError **error) { gchar ack_payload[6] = {0x0}; guint64 ack_cmd = 0; /* this is weird; base 10 number as ASCII as the ack payload... */ if (!fu_memcpy_safe((guint8 *)ack_payload, sizeof(ack_payload), 0x0, buf->data, buf->len, 0x0, sizeof(ack_payload) - 1, error)) { g_prefix_error_literal(error, "failed to copy ack payload: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "ack_payload", (guint8 *)ack_payload, sizeof(ack_payload)); if (!fu_strtoull((const gchar *)ack_payload, &ack_cmd, 0, G_MAXUINT32, FU_INTEGER_BASE_AUTO, error)) { g_prefix_error_literal(error, "failed to parse ack payload cmd: "); return FALSE; } g_debug("ack_cmd: %s [0x%x]", fu_logitech_bulkcontroller_cmd_to_string(ack_cmd), (guint)ack_cmd); if (ack_cmd != cmd) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "command invalid, expected %s and got %s", fu_logitech_bulkcontroller_cmd_to_string(cmd), fu_logitech_bulkcontroller_cmd_to_string(ack_cmd)); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_sync_wait_ack_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); FuLogitechBulkcontrollerResponse *helper = (FuLogitechBulkcontrollerResponse *)user_data; g_autoptr(GByteArray) buf = NULL; buf = fu_logitech_bulkcontroller_device_sync_wait_cmd(self, FU_LOGITECH_BULKCONTROLLER_CMD_ACK, self->sequence_id, helper->timeout, error); if (buf == NULL) return FALSE; if (!fu_logitech_bulkcontroller_device_sync_check_ack_cmd(buf, helper->cmd, error)) return FALSE; /* success */ return TRUE; } /* send command and wait for ACK */ static gboolean fu_logitech_bulkcontroller_device_sync_wait_ack(FuLogitechBulkcontrollerDevice *self, FuLogitechBulkcontrollerCmd cmd, guint timeout, GError **error) { FuLogitechBulkcontrollerResponse helper = {.cmd = cmd, .timeout = timeout}; return fu_device_retry_full(FU_DEVICE(self), fu_logitech_bulkcontroller_device_sync_wait_ack_cb, 10, 200, &helper, error); } static gboolean fu_logitech_bulkcontroller_device_sync_check_ack(FuLogitechBulkcontrollerDevice *self, FuLogitechBulkcontrollerResponse *response, FuLogitechBulkcontrollerCmd cmd, GError **error) { /* verify the sequence ID */ if (response->sequence_id != self->sequence_id) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "sequence ID invalid, expected 0x%04x and got 0x%04x", self->sequence_id, response->sequence_id); return FALSE; } return fu_logitech_bulkcontroller_device_sync_check_ack_cmd(response->data, cmd, error); } static GByteArray * fu_logitech_bulkcontroller_device_sync_write(FuLogitechBulkcontrollerDevice *self, GByteArray *req, GError **error) { g_autoptr(GByteArray) res_ack = NULL; g_autoptr(GByteArray) res_read = NULL; g_autoptr(GByteArray) buf = NULL; /* send host->device buffer-write */ if (!fu_logitech_bulkcontroller_device_sync_send_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_WRITE, req, BULK_TRANSFER_TIMEOUT, error)) { g_prefix_error_literal(error, "failed to send request: "); return NULL; } /* wait device->host ack */ if (!fu_logitech_bulkcontroller_device_sync_wait_ack( self, FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_WRITE, BULK_TRANSFER_TIMEOUT, error)) { g_prefix_error_literal(error, "failed to wait for ack: "); return NULL; } /* send host->device buffer-uninit */ if (!fu_logitech_bulkcontroller_device_sync_send_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT_BUFFER, NULL, BULK_TRANSFER_TIMEOUT, error)) { g_prefix_error_literal(error, "failed to uninit buffer: "); return NULL; } /* wait device->host buffer-read|ack */ do { g_autoptr(FuLogitechBulkcontrollerResponse) response_tmp = NULL; response_tmp = fu_logitech_bulkcontroller_device_sync_wait_any(self, BULK_TRANSFER_TIMEOUT, error); if (response_tmp == NULL) { g_prefix_error_literal(error, "failed to wait for any: "); return NULL; } if (response_tmp->cmd == FU_LOGITECH_BULKCONTROLLER_CMD_ACK) { if (res_ack != NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "already received ack"); return NULL; } if (!fu_logitech_bulkcontroller_device_sync_check_ack( self, response_tmp, FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT_BUFFER, error)) { g_prefix_error_literal(error, "failed to check uninit buffer: "); return NULL; } res_ack = g_steal_pointer(&response_tmp->data); } else if (response_tmp->cmd == FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_READ) { if (res_read != NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "already received read-buffer"); return NULL; } res_read = g_steal_pointer(&response_tmp->data); } } while (res_ack == NULL || res_read == NULL); /* send host->device ack */ if (!fu_logitech_bulkcontroller_device_sync_send_ack( self, FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_READ, BULK_TRANSFER_TIMEOUT, error)) { g_prefix_error_literal(error, "failed to ack read buffer: "); return NULL; } /* wait device->host uninit */ buf = fu_logitech_bulkcontroller_device_sync_wait_cmd_retry( self, FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT_BUFFER, 0x0, /* why? */ BULK_TRANSFER_TIMEOUT, error); if (buf == NULL) { g_prefix_error_literal(error, "failed to wait for uninit buffer: "); return NULL; } /* send host->device ack */ if (!fu_logitech_bulkcontroller_device_sync_send_ack( self, FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT_BUFFER, BULK_TRANSFER_TIMEOUT, error)) { g_prefix_error_literal(error, "failed to ack uninit buffer: "); return NULL; } /* success */ return g_steal_pointer(&res_read); } static gboolean fu_logitech_bulkcontroller_device_upd_send_cmd(FuLogitechBulkcontrollerDevice *self, guint32 cmd, GBytes *buf, guint timeout, GError **error) { gsize actual_length = 0; g_autofree guint8 *buf_tmp = g_malloc0(self->transfer_bufsz); g_autoptr(FuStructLogitechBulkcontrollerUpdateRes) st_res = NULL; g_autoptr(FuStructLogitechBulkcontrollerUpdateReq) st_pkt = fu_struct_logitech_bulkcontroller_update_req_new(); fu_struct_logitech_bulkcontroller_update_req_set_cmd(st_pkt, cmd); if (buf != NULL) { fu_struct_logitech_bulkcontroller_update_req_set_payload_length( st_pkt, g_bytes_get_size(buf)); fu_byte_array_append_bytes(st_pkt->buf, buf); } fu_dump_raw(G_LOG_DOMAIN, "request", st_pkt->buf->data, MIN(st_pkt->buf->len, 12)); if (!fu_usb_device_bulk_transfer(FU_USB_DEVICE(self), self->update_ep[EP_OUT], st_pkt->buf->data, st_pkt->buf->len, NULL, /* transferred */ BULK_TRANSFER_TIMEOUT, NULL, error)) { g_prefix_error_literal(error, "failed to send upd bulk transfer: "); fwupd_error_convert(error); return FALSE; } /* receiving ACK */ if (!fu_usb_device_bulk_transfer(FU_USB_DEVICE(self), self->update_ep[EP_IN], buf_tmp, self->transfer_bufsz, &actual_length, timeout, NULL, error)) { g_prefix_error_literal(error, "failed to receive: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "response", buf_tmp, MIN(actual_length, 12)); st_res = fu_struct_logitech_bulkcontroller_update_res_parse(buf_tmp, self->transfer_bufsz, 0x0, error); if (st_res == NULL) return FALSE; if (fu_struct_logitech_bulkcontroller_update_res_get_cmd(st_res) != FU_LOGITECH_BULKCONTROLLER_CMD_ACK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "not CMD_ACK, got %s", fu_logitech_bulkcontroller_cmd_to_string( fu_struct_logitech_bulkcontroller_update_res_get_cmd(st_res))); return FALSE; } if (fu_struct_logitech_bulkcontroller_update_res_get_cmd_req(st_res) != cmd) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "invalid upd message received, expected %s, got %s", fu_logitech_bulkcontroller_cmd_to_string(cmd), fu_logitech_bulkcontroller_cmd_to_string( fu_struct_logitech_bulkcontroller_update_res_get_cmd_req(st_res))); return FALSE; } return TRUE; } static FwupdStatus fu_logitech_bulkcontroller_device_update_state_to_status( FuLogitechBulkcontrollerUpdateState update_state) { if (update_state == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_DOWNLOADING) return FWUPD_STATUS_DEVICE_WRITE; if (update_state == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_STARTING) return FWUPD_STATUS_DEVICE_VERIFY; if (update_state == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_UPDATING) return FWUPD_STATUS_DEVICE_WRITE; if (update_state == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_CURRENT) return FWUPD_STATUS_IDLE; return FWUPD_STATUS_UNKNOWN; } static gboolean fu_logitech_bulkcontroller_device_ensure_child(FuLogitechBulkcontrollerDevice *self, FwupdJsonObject *json_device, GError **error) { GPtrArray *children = fu_device_get_children(FU_DEVICE(self)); g_autoptr(FuDevice) child = NULL; const gchar *name; const gchar *tmp; const gchar *version; gint64 status = 0; /* sanity check */ tmp = fwupd_json_object_get_string(json_device, "type", error); if (tmp == NULL) return FALSE; if (g_strcmp0(tmp, "Sentinel") != 0) return TRUE; /* check status */ if (!fwupd_json_object_get_integer(json_device, "status", &status, error)) return FALSE; if (status != FU_LOGITECH_BULKCONTROLLER_DEVICE_STATE_ONLINE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "status is %s [0x%x]", fu_logitech_bulkcontroller_device_state_to_string(status), (guint)status); return FALSE; } /* version must exist */ version = fwupd_json_object_get_string(json_device, "sw", error); if (version == NULL) return FALSE; /* child already exists */ name = fwupd_json_object_get_string(json_device, "name", error); if (name == NULL) return FALSE; for (guint i = 0; i < children->len; i++) { FuDevice *child_tmp = g_ptr_array_index(children, i); if (g_strcmp0(fu_device_get_logical_id(child_tmp), name) == 0) { g_debug("found existing %s device, just updating version", name); fu_device_set_version(child_tmp, version); return TRUE; } } /* create new child */ child = g_object_new(FU_TYPE_LOGITECH_BULKCONTROLLER_CHILD, "proxy", self, NULL); fu_device_add_private_flag(child, FU_DEVICE_PRIVATE_FLAG_REFCOUNTED_PROXY); fu_device_set_name(child, name); tmp = fwupd_json_object_get_string(json_device, "make", error); if (tmp == NULL) return FALSE; fu_device_set_vendor(child, tmp); fu_device_set_logical_id(child, name); fu_device_set_version(child, version); tmp = fwupd_json_object_get_string(json_device, "serial", NULL); if (tmp != NULL) fu_device_set_serial(child, tmp); tmp = fwupd_json_object_get_string(json_device, "model", error); if (tmp == NULL) return FALSE; fu_device_add_instance_strup(child, "MODEL", tmp); if (!fu_device_build_instance_id(child, error, "LOGI", "MODEL", NULL)) return FALSE; fu_device_add_child(FU_DEVICE(self), child); /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_json_parser(FuLogitechBulkcontrollerDevice *self, const gchar *json, GError **error) { const gchar *tmp; gint64 tmpi = 0; g_autoptr(FwupdJsonArray) json_devices = NULL; g_autoptr(FwupdJsonNode) json_node = NULL; g_autoptr(FwupdJsonObject) json_device = NULL; g_autoptr(FwupdJsonObject) json_obj = NULL; g_autoptr(FwupdJsonObject) json_payload = NULL; g_autoptr(FwupdJsonParser) json_parser = fwupd_json_parser_new(); /* set appropriate limits */ fwupd_json_parser_set_max_depth(json_parser, 10); fwupd_json_parser_set_max_items(json_parser, 100); fwupd_json_parser_set_max_quoted(json_parser, 10000); /* parse JSON reply */ json_node = fwupd_json_parser_load_from_data(json_parser, json, FWUPD_JSON_LOAD_FLAG_NONE, error); if (json_node == NULL) return FALSE; json_obj = fwupd_json_node_get_object(json_node, error); if (json_obj == NULL) return FALSE; json_payload = fwupd_json_object_get_object(json_obj, "payload", error); if (json_payload == NULL) return FALSE; json_devices = fwupd_json_object_get_array(json_payload, "devices", error); if (json_devices == NULL) return FALSE; json_device = fwupd_json_array_get_object(json_devices, 0, error); if (json_device == NULL) return FALSE; tmp = fwupd_json_object_get_string(json_device, "name", NULL); if (tmp != NULL) fu_device_set_name(FU_DEVICE(self), tmp); tmp = fwupd_json_object_get_string(json_device, "sw", NULL); if (tmp != NULL) fu_device_set_version(FU_DEVICE(self), tmp); tmp = fwupd_json_object_get_string(json_device, "type", NULL); if (tmp != NULL) { fu_device_add_instance_id_full(FU_DEVICE(self), tmp, FU_DEVICE_INSTANCE_FLAG_QUIRKS); } if (!fwupd_json_object_get_integer_with_default( json_device, "status", &tmpi, FU_LOGITECH_BULKCONTROLLER_DEVICE_STATE_UNKNOWN, error)) return FALSE; if (tmpi != FU_LOGITECH_BULKCONTROLLER_DEVICE_STATE_UNKNOWN) self->status = tmpi; if (!fwupd_json_object_get_integer_with_default( json_device, "updateStatus", &tmpi, FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_UNKNOWN, error)) return FALSE; if (tmpi != FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_UNKNOWN) self->update_status = tmpi; /* updateProgress only available while firmware upgrade is going on */ if (!fwupd_json_object_get_integer_with_default(json_device, "updateProgress", &tmpi, -1, error)) return FALSE; if (tmpi != -1) self->update_progress = tmpi; /* ensure child pheripheral devices exist */ for (guint i = 1; i < fwupd_json_array_get_size(json_devices); i++) { g_autoptr(FwupdJsonObject) json_child = NULL; g_autoptr(GError) error_local = NULL; json_child = fwupd_json_array_get_object(json_devices, i, error); if (json_child == NULL) return FALSE; if (!fu_logitech_bulkcontroller_device_ensure_child(self, json_child, &error_local)) g_warning("failed to add child: %s", error_local->message); } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_parse_info(FuLogitechBulkcontrollerDevice *self, GByteArray *buf, GError **error) { g_autofree gchar *payload = NULL; g_autoptr(FuProtobuf) pbuf = fu_protobuf_new_from_data(buf->data, buf->len); g_autoptr(FuProtobuf) pbuf_rsp = NULL; /* as a response */ pbuf_rsp = fu_protobuf_get_embedded(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_USB_MSG_RESPONSE, NULL); if (pbuf_rsp != NULL) { g_autoptr(FuProtobuf) pbuf_rsp2 = fu_protobuf_get_embedded( pbuf_rsp, FU_LOGITECH_BULKCONTROLLER_FNUM_RESPONSE_GET_DEVICE_INFO, error); if (pbuf_rsp2 == NULL) { g_prefix_error_literal( error, "invalid response, expected UsbMsg.Response.GetDeviceInfo: "); return FALSE; } payload = fu_protobuf_get_string( pbuf_rsp2, FU_LOGITECH_BULKCONTROLLER_FNUM_GET_DEVICE_INFO_RESPONSE_PAYLOAD, error); if (payload == NULL) { g_prefix_error_literal( error, "incorrect GetDeviceInfo response -- no MQTT payload: "); return FALSE; } } /* async from device */ pbuf_rsp = fu_protobuf_get_embedded(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_USB_MSG_EVENT, NULL); if (pbuf_rsp != NULL) { g_autoptr(FuProtobuf) pbuf_rsp2 = fu_protobuf_get_embedded(pbuf_rsp, FU_LOGITECH_BULKCONTROLLER_FNUM_EVENT_KONG, error); if (pbuf_rsp2 == NULL) { g_prefix_error_literal(error, "invalid response, expected UsbMsg.Event.Kong: "); return FALSE; } payload = fu_protobuf_get_string(pbuf_rsp2, FU_LOGITECH_BULKCONTROLLER_FNUM_KONG_EVENT_PAYLOAD, error); if (payload == NULL) { g_prefix_error_literal( error, "incorrect UsbMsg.Event.Kong response -- no MQTT payload: "); return FALSE; } } if (payload == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "incorrect response for device info request, expected " "UsbMsg.Response or UsbMsg.Event"); return FALSE; } if (!fu_logitech_bulkcontroller_device_json_parser(self, payload, error)) return FALSE; /* success */ g_string_assign(self->device_info_response_json, payload); return TRUE; } static FuProtobuf * fu_logitech_bulkcontroller_device_build_header(FuLogitechBulkcontrollerDevice *self) { g_autoptr(FuProtobuf) pbuf = fu_protobuf_new(); /* make predictable */ if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) || fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATION_TAG)) { fu_protobuf_add_string(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_HEADER_TIMESTAMP, "0"); } else { g_autofree gchar *timestamp = g_strdup_printf("%" G_GINT64_FORMAT, g_get_real_time() / 1000); g_autofree gchar *id = g_uuid_string_random(); fu_protobuf_add_string(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_HEADER_ID, id); fu_protobuf_add_string(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_HEADER_TIMESTAMP, timestamp); } return g_steal_pointer(&pbuf); } static GByteArray * fu_logitech_bulkcontroller_device_build_get_device_info_request( FuLogitechBulkcontrollerDevice *self) { g_autoptr(FuProtobuf) pbuf = fu_protobuf_new(); g_autoptr(FuProtobuf) pbuf_hdr = fu_logitech_bulkcontroller_device_build_header(self); g_autoptr(FuProtobuf) pbuf_req = fu_protobuf_new(); fu_protobuf_add_empty(pbuf_req, FU_LOGITECH_BULKCONTROLLER_FNUM_REQUEST_GET_DEVICE_INFO); fu_protobuf_add_embedded(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_USB_MSG_HEADER, pbuf_hdr); fu_protobuf_add_embedded(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_USB_MSG_REQUEST, pbuf_req); return fu_protobuf_write(pbuf); } static gboolean fu_logitech_bulkcontroller_device_ensure_info_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); g_autoptr(GByteArray) buf = NULL; gboolean send_req = *(gboolean *)user_data; /* sending GetDeviceInfoRequest. Device reports quite a few matrix, including status, * progress etc * Two ways to get data from device: * 1. Listen for the data broadcasted by device, while firmware upgrade is going on * 2. Make explicit request to the device. Used when data is needed before/after firmware * upgrade */ if (send_req) { g_autoptr(GByteArray) buf_req = fu_logitech_bulkcontroller_device_build_get_device_info_request(self); buf = fu_logitech_bulkcontroller_device_sync_write(self, buf_req, error); if (buf == NULL) return FALSE; } else { /* poll the out interface */ buf = fu_logitech_bulkcontroller_device_sync_wait_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_READ, 0x0, /* sequence_id */ BULK_TRANSFER_TIMEOUT, error); if (buf == NULL) return FALSE; } return fu_logitech_bulkcontroller_device_parse_info(self, buf, error); } static gboolean fu_logitech_bulkcontroller_device_ensure_info(FuLogitechBulkcontrollerDevice *self, gboolean send_req, GError **error) { return fu_device_retry(FU_DEVICE(self), fu_logitech_bulkcontroller_device_ensure_info_cb, MAX_SETUP_RETRIES, &send_req, error); } static gboolean fu_logitech_bulkcontroller_device_upd_send_init_cmd_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); return fu_logitech_bulkcontroller_device_upd_send_cmd(self, FU_LOGITECH_BULKCONTROLLER_CMD_INIT, NULL, BULK_TRANSFER_TIMEOUT, error); } static gboolean fu_logitech_bulkcontroller_device_write_fw(FuLogitechBulkcontrollerDevice *self, FuInputStream *stream, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = NULL; chunks = fu_chunk_array_new_from_stream( stream, FU_CHUNK_ADDR_OFFSET_NONE, FU_CHUNK_PAGESZ_NONE, self->transfer_bufsz - FU_STRUCT_LOGITECH_BULKCONTROLLER_UPDATE_REQ_SIZE, error); if (chunks == NULL) return FALSE; fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = NULL; g_autoptr(GBytes) chk_blob = NULL; /* prepare chunk */ chk = fu_chunk_array_index(chunks, i, error); if (chk == NULL) return FALSE; chk_blob = fu_chunk_get_bytes(chk); if (!fu_logitech_bulkcontroller_device_upd_send_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_DATA_TRANSFER, chk_blob, BULK_TRANSFER_TIMEOUT, error)) { g_prefix_error(error, "failed to send data packet 0x%x: ", i); return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gboolean fu_logitech_bulkcontroller_device_verify_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); FuProgress *progress = FU_PROGRESS(user_data); g_autoptr(GError) error_local = NULL; g_autoptr(GByteArray) buf = NULL; g_autoptr(GByteArray) uninit_buf = NULL; /* * poll the out interface. Device mandates following read flow on SYNC interface * Device->Host FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_READ * Host->Device FU_LOGITECH_BULKCONTROLLER_CMD_ACK * Device->Host FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT_BUFFER * Host->Device FU_LOGITECH_BULKCONTROLLER_CMD_ACK * * use lower timeout to quickly flush any kUpdateStateDownloading progress events, * accumulated while host busy sending firmware image. Device queue stores upto 100 events. * These events are not removed from the queue, unless properly acknowledged */ buf = fu_logitech_bulkcontroller_device_sync_wait_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_READ, 0x0, /* sequence_id */ (self->is_sync_flush_events_in_progress) ? BULK_TRANSFER_FLUSH_TIMEOUT : BULK_TRANSFER_TIMEOUT, error); if (buf == NULL) return FALSE; /* send host->device ack */ if (!fu_logitech_bulkcontroller_device_sync_send_ack( self, FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_READ, (self->is_sync_flush_events_in_progress) ? BULK_TRANSFER_FLUSH_TIMEOUT : BULK_TRANSFER_TIMEOUT, error)) return FALSE; /* wait device->host uninit */ uninit_buf = fu_logitech_bulkcontroller_device_sync_wait_cmd_retry( self, FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT_BUFFER, 0x0, /* why? */ (self->is_sync_flush_events_in_progress) ? BULK_TRANSFER_FLUSH_TIMEOUT : BULK_TRANSFER_TIMEOUT, error); if (uninit_buf == NULL) return FALSE; /* send host->device ack */ if (!fu_logitech_bulkcontroller_device_sync_send_ack( self, FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT_BUFFER, (self->is_sync_flush_events_in_progress) ? BULK_TRANSFER_FLUSH_TIMEOUT : BULK_TRANSFER_TIMEOUT, error)) return FALSE; if (buf == NULL) { g_autoptr(GByteArray) buf_req = fu_logitech_bulkcontroller_device_build_get_device_info_request(self); g_debug("manually requesting as no pending request: %s", error_local->message); buf = fu_logitech_bulkcontroller_device_sync_write(self, buf_req, error); if (buf == NULL) return FALSE; } if (!fu_logitech_bulkcontroller_device_parse_info(self, buf, error)) return FALSE; g_debug("firmware update status: %s, progress: %u", fu_logitech_bulkcontroller_update_state_to_string(self->update_status), self->update_progress); /* events are not sorted, so stale downloading events can appear anytime */ if (self->update_status == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_DOWNLOADING) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_BUSY, "waiting for download to finish"); self->is_sync_flush_events_in_progress = TRUE; return FALSE; } self->is_sync_flush_events_in_progress = FALSE; fu_progress_set_status( progress, fu_logitech_bulkcontroller_device_update_state_to_status(self->update_status)); /* existing device image version is same as newly pushed image? */ if (self->update_status == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_ERROR || self->update_status == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_CURRENT) return TRUE; /* only update the child if the percentage is bigger -- which means the progressbar * may stall, but will never go backwards */ if (self->update_progress > fu_progress_get_percentage(progress)) fu_progress_set_percentage(progress, self->update_progress); /* keep waiting */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_BUSY, "waiting for verify to finish"); return FALSE; } static gboolean fu_logitech_bulkcontroller_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); gsize streamsz = 0; g_autofree gchar *base64hash = NULL; g_autofree gchar *md5_str = NULL; g_autoptr(GByteArray) md5_buf = NULL; g_autoptr(GByteArray) end_pkt = g_byte_array_new(); g_autoptr(GByteArray) start_pkt = g_byte_array_new(); g_autoptr(FuInputStream) stream = NULL; g_autoptr(GBytes) end_pkt_blob = NULL; g_autoptr(GBytes) start_pkt_blob = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 4, "checksum-stream"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "init"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 51, "device-write-blocks"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "end-transfer"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "uninit"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 40, NULL); /* get default image */ stream = fu_firmware_get_stream(firmware, error); if (stream == NULL) return FALSE; /* calculate firmware checksum, weirdly as a base64 string */ md5_str = fu_firmware_get_checksum(firmware, G_CHECKSUM_MD5, error); md5_buf = fu_byte_array_from_string(md5_str, error); if (md5_buf == NULL) return FALSE; base64hash = fu_base64_encode(md5_buf->data, md5_buf->len); fu_progress_step_done(progress); /* sending INIT. Retry if device is not in IDLE state to receive the file */ if (!fu_device_retry(device, fu_logitech_bulkcontroller_device_upd_send_init_cmd_cb, MAX_RETRIES, NULL, error)) { g_prefix_error_literal( error, "failed to write init transfer packet: please reboot the device: "); return FALSE; } /* transfer sent */ if (!fu_input_stream_size(stream, &streamsz, error)) return FALSE; fu_byte_array_append_uint64(start_pkt, streamsz, G_LITTLE_ENDIAN); start_pkt_blob = g_bytes_new(start_pkt->data, start_pkt->len); if (!fu_logitech_bulkcontroller_device_upd_send_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_START_TRANSFER, start_pkt_blob, BULK_TRANSFER_TIMEOUT, error)) { g_prefix_error_literal(error, "failed to write start transfer packet: "); return FALSE; } fu_progress_step_done(progress); /* push each block to device */ if (!fu_logitech_bulkcontroller_device_write_fw(self, stream, fu_progress_get_child(progress), error)) { g_prefix_error_literal(error, "failed to write firmware: "); return FALSE; } fu_progress_step_done(progress); /* sending end transfer -- extend the bulk transfer timeout value, as android device takes * some time to calculate the hash and respond */ fu_byte_array_append_uint32(end_pkt, 1, G_LITTLE_ENDIAN); /* update */ fu_byte_array_append_uint32(end_pkt, 0, G_LITTLE_ENDIAN); /* force */ fu_byte_array_append_uint32(end_pkt, FU_LOGITECH_BULKCONTROLLER_CHECKSUM_TYPE_MD5, G_LITTLE_ENDIAN); g_byte_array_append(end_pkt, (const guint8 *)base64hash, strlen(base64hash)); end_pkt_blob = g_bytes_new(end_pkt->data, end_pkt->len); if (!fu_logitech_bulkcontroller_device_upd_send_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_END_TRANSFER, end_pkt_blob, HASH_TIMEOUT, error)) { g_prefix_error_literal(error, "failed to write end transfer packet: "); return FALSE; } fu_progress_step_done(progress); /* send uninit */ if (!fu_logitech_bulkcontroller_device_upd_send_cmd(self, FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT, NULL, BULK_TRANSFER_TIMEOUT, error)) { g_prefix_error_literal(error, "failed to write finish transfer packet: "); return FALSE; } fu_progress_step_done(progress); /* * image file pushed. Device validates and uploads new image on inactive partition. * Restart sync cb, to get the update progress * Flush any kUpdateStateDownloading progress events, accumulated while busy sending * firmware image. Peripheral device needs higher delay * Normally status changes as follows: * While image being pushed: kUpdateStateCurrent->kUpdateStateDownloading (~5minutes) * After image push is complete: kUpdateStateDownloading->kUpdateStateReady * Validating image: kUpdateStateReady->kUpdateStateStarting * Uploading image: kUpdateStateStarting->kUpdateStateUpdating * Upload finished: kUpdateStateUpdating->kUpdateStateCurrent (~5minutes) * After upload is finished, device reboots itself */ if (fu_device_has_private_flag(device, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_PHERIPHERAL_UPDATE)) self->is_sync_flush_events_in_progress = FALSE; else self->is_sync_flush_events_in_progress = TRUE; if (!fu_device_retry_full(device, fu_logitech_bulkcontroller_device_verify_cb, 5000, /* over 10 minutes */ (self->is_sync_flush_events_in_progress) ? 5 : 250, /* ms */ fu_progress_get_child(progress), error)) return FALSE; if (self->update_status == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_ERROR) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "firmware upgrade failed"); return FALSE; } fu_progress_step_done(progress); /* success! */ if (fu_device_has_private_flag(device, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_PHERIPHERAL_UPDATE)) { /* skip replug if updating child. parent waits till child reboot with new firmware */ g_debug("child firmware updated"); fu_device_remove_private_flag( device, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_PHERIPHERAL_UPDATE); } else { fu_device_set_remove_delay(FU_DEVICE(self), 10 * 60 * 1000); /* >1 min to finish init */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } return TRUE; } static GByteArray * fu_logitech_bulkcontroller_device_build_set_device_time_request( FuLogitechBulkcontrollerDevice *self, GError **error) { FuContext *ctx = fu_device_get_context(FU_DEVICE(self)); FuPathStore *pstore = fu_context_get_path_store(ctx); g_autofree gchar *olson_location = NULL; g_autoptr(FuProtobuf) pbuf = fu_protobuf_new(); g_autoptr(FuProtobuf) pbuf_hdr = fu_logitech_bulkcontroller_device_build_header(self); g_autoptr(FuProtobuf) pbuf_req = fu_protobuf_new(); g_autoptr(FuProtobuf) pbuf_time = fu_protobuf_new(); /* the device expects an olson_location, not a timezone */ if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) || fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATION_TAG)) { olson_location = g_strdup("Europe/London"); } else { olson_location = fu_common_get_olson_timezone_id(pstore, error); if (olson_location == NULL) return NULL; } /* send future time to keep PC & device time as close as possible, unless emulated */ if (!fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) && !fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATION_TAG)) { fu_protobuf_add_uint64(pbuf_time, FU_LOGITECH_BULKCONTROLLER_FNUM_SET_DEVICE_TIME_REQUEST_TS, (g_get_real_time() / 1000) + 500); } fu_protobuf_add_string(pbuf_time, FU_LOGITECH_BULKCONTROLLER_FNUM_SET_DEVICE_TIME_REQUEST_TIMEZONE, olson_location); fu_protobuf_add_embedded(pbuf_req, FU_LOGITECH_BULKCONTROLLER_FNUM_REQUEST_SET_DEVICE_TIME, pbuf_time); fu_protobuf_add_embedded(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_USB_MSG_HEADER, pbuf_hdr); fu_protobuf_add_embedded(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_USB_MSG_REQUEST, pbuf_req); return fu_protobuf_write(pbuf); } static gboolean fu_logitech_bulkcontroller_device_send_time_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); gboolean success = FALSE; g_autoptr(FuProtobuf) pbuf = NULL; g_autoptr(FuProtobuf) pbuf_ack = NULL; g_autoptr(GByteArray) buf_req = NULL; g_autoptr(GByteArray) buf_rsp = NULL; /* send SetDeviceTimeRequest to sync device clock with host */ buf_req = fu_logitech_bulkcontroller_device_build_set_device_time_request(self, error); if (buf_req == NULL) return FALSE; buf_rsp = fu_logitech_bulkcontroller_device_sync_write(self, buf_req, error); if (buf_rsp == NULL) return FALSE; pbuf = fu_protobuf_new_from_data(buf_rsp->data, buf_rsp->len); pbuf_ack = fu_protobuf_get_embedded(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_USB_MSG_ACKNOWLEDGE, error); if (pbuf_ack == NULL) { g_prefix_error_literal(error, "invalid response, expected UsbMsg.Acknowledge: "); return FALSE; } if (!fu_protobuf_get_boolean(pbuf_ack, FU_LOGITECH_BULKCONTROLLER_FNUM_ACKNOWLEDGE_SUCCESS, &success, error)) { g_prefix_error_literal(error, "incorrect UsbMsg.Acknowledge response: "); return FALSE; } if (!success) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "incorrect UsbMsg.Acknowledge, expected Success"); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_send_time(FuLogitechBulkcontrollerDevice *self, GError **error) { return fu_device_retry(FU_DEVICE(self), fu_logitech_bulkcontroller_device_send_time_cb, MAX_SETUP_RETRIES, NULL, error); } static GByteArray * fu_logitech_bulkcontroller_device_build_transition_to_device_mode_request( FuLogitechBulkcontrollerDevice *self) { g_autoptr(FuProtobuf) pbuf = fu_protobuf_new(); g_autoptr(FuProtobuf) pbuf_hdr = fu_logitech_bulkcontroller_device_build_header(self); g_autoptr(FuProtobuf) pbuf_req = fu_protobuf_new(); fu_protobuf_add_empty(pbuf_req, FU_LOGITECH_BULKCONTROLLER_FNUM_REQUEST_TRANSITION_TO_DEVICE_MODE); fu_protobuf_add_embedded(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_USB_MSG_HEADER, pbuf_hdr); fu_protobuf_add_embedded(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_USB_MSG_REQUEST, pbuf_req); return fu_protobuf_write(pbuf); } static gboolean fu_logitech_bulkcontroller_device_transition_to_device_mode_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); gboolean success = FALSE; g_autoptr(FuProtobuf) pbuf = NULL; g_autoptr(FuProtobuf) pbuf_rsp = NULL; g_autoptr(FuProtobuf) pbuf_rsp2 = NULL; g_autoptr(GByteArray) buf_req = NULL; g_autoptr(GByteArray) buf_rsp = NULL; buf_req = fu_logitech_bulkcontroller_device_build_transition_to_device_mode_request(self); buf_rsp = fu_logitech_bulkcontroller_device_sync_write(self, buf_req, error); if (buf_rsp == NULL) return FALSE; pbuf = fu_protobuf_new_from_data(buf_rsp->data, buf_rsp->len); pbuf_rsp = fu_protobuf_get_embedded(pbuf, FU_LOGITECH_BULKCONTROLLER_FNUM_USB_MSG_RESPONSE, error); if (pbuf_rsp == NULL) { g_prefix_error_literal(error, "invalid response, expected UsbMsg.Response: "); return FALSE; } pbuf_rsp2 = fu_protobuf_get_embedded( pbuf_rsp, FU_LOGITECH_BULKCONTROLLER_FNUM_RESPONSE_TRANSITION_TO_DEVICE_MODE, error); if (pbuf_rsp2 == NULL) { g_prefix_error_literal( error, "invalid response, expected UsbMsg.Response.TransitionToDeviceMode: "); return FALSE; } if (!fu_protobuf_get_boolean( pbuf_rsp2, FU_LOGITECH_BULKCONTROLLER_FNUM_TRANSITION_TO_DEVICE_MODE_RESPONSE_SUCCESS, &success, error)) { g_prefix_error_literal( error, "incorrect UsbMsg.Response.TransitionToDeviceMode response: "); return FALSE; } if (!success) { g_autofree gchar *error_msg = fu_protobuf_get_string( pbuf_rsp2, FU_LOGITECH_BULKCONTROLLER_FNUM_TRANSITION_TO_DEVICE_MODE_RESPONSE_ERROR_DESCRIPTION, NULL); if (error_msg != NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "incorrect UsbMsg.Response.TransitionToDeviceMode: %s", error_msg); return FALSE; } g_set_error_literal( error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "incorrect UsbMsg.Response.TransitionToDeviceMode, expected Success"); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_transition_to_device_mode(FuLogitechBulkcontrollerDevice *self, GError **error) { return fu_device_retry(FU_DEVICE(self), fu_logitech_bulkcontroller_device_transition_to_device_mode_cb, MAX_SETUP_RETRIES, NULL, error); } static gboolean fu_logitech_bulkcontroller_device_clear_queue_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); gsize actual_length = 0; g_autofree guint8 *buf = g_malloc0(self->transfer_bufsz); g_autoptr(GError) error_local = NULL; if (!fu_usb_device_bulk_transfer(FU_USB_DEVICE(self), self->sync_ep[EP_IN], buf, self->transfer_bufsz, &actual_length, 250, /* ms */ NULL, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_TIMED_OUT)) { g_debug("timed out successfully"); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* failed */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "got valid data, so keep going"); return FALSE; } static gboolean fu_logitech_bulkcontroller_device_clear_queue(FuLogitechBulkcontrollerDevice *self, GError **error) { g_debug("clearing any bulk data"); return fu_device_retry(FU_DEVICE(self), fu_logitech_bulkcontroller_device_clear_queue_cb, 3, NULL, error); } static gboolean fu_logitech_bulkcontroller_device_check_buffer_size(FuLogitechBulkcontrollerDevice *self, GError **error) { g_autoptr(GByteArray) buf = NULL; g_autoptr(GError) error_local = NULL; if (!fu_logitech_bulkcontroller_device_sync_send_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_CHECK_BUFFERSIZE, NULL, /* data */ BULK_TRANSFER_TIMEOUT, error)) { g_prefix_error_literal(error, "failed to send request: "); return FALSE; } buf = fu_logitech_bulkcontroller_device_sync_wait_cmd_retry( self, FU_LOGITECH_BULKCONTROLLER_CMD_CHECK_BUFFERSIZE, 0x0, /* always zero */ BULK_TRANSFER_TIMEOUT, &error_local); if (buf != NULL) { self->transfer_bufsz = 16 * FU_KB; } else { g_debug("sticking to 8k buffersize: %s", error_local->message); } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_setup(FuDevice *device, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); /* the hardware is unable to handle requests -- firmware issue */ if (fu_device_has_private_flag(device, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_POST_INSTALL)) { fu_device_sleep(device, POST_INSTALL_SLEEP_DURATION); fu_device_remove_private_flag(device, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_POST_INSTALL); } /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_logitech_bulkcontroller_device_parent_class) ->setup(device, error)) { g_prefix_error_literal(error, "failed to FuUsbDevice->setup: "); return FALSE; } /* empty the queue */ if (!fu_logitech_bulkcontroller_device_clear_queue(self, error)) { g_prefix_error_literal(error, "failed to clear queue: "); return FALSE; } /* check if the device supports a 16kb transfer buffer */ if (fu_device_has_private_flag(device, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_CHECK_BUFFER_SIZE)) { if (!fu_logitech_bulkcontroller_device_check_buffer_size(self, error)) { g_prefix_error_literal(error, "failed to check buffer size: "); return FALSE; } } /* device supports modes of Device (supported), Appliance and BYOD (both unsupported) */ if (!fu_logitech_bulkcontroller_device_transition_to_device_mode(self, error)) { g_prefix_error_literal(error, "failed to transition to device_mode: "); return FALSE; } /* set device time */ if (!fu_logitech_bulkcontroller_device_send_time(self, error)) { g_prefix_error_literal(error, "failed to set time: "); return FALSE; } /* load current device data */ if (!fu_logitech_bulkcontroller_device_ensure_info(self, TRUE, error)) { g_prefix_error_literal(error, "failed to ensure info: "); return FALSE; } /* success */ return TRUE; } static void fu_logitech_bulkcontroller_device_set_progress(FuDevice *device, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DECOMPRESSING, 0, "prepare-fw"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, "reload"); } static void fu_logitech_bulkcontroller_device_init(FuLogitechBulkcontrollerDevice *self) { self->transfer_bufsz = 8 * FU_KB; self->device_info_response_json = g_string_new(NULL); fu_device_add_protocol(FU_DEVICE(self), "com.logitech.vc.proto"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_private_flag(FU_DEVICE(self), FU_DEVICE_PRIVATE_FLAG_RETRY_OPEN); fu_usb_device_set_claim_retry_count(FU_USB_DEVICE(self), 100); fu_device_retry_set_delay(FU_DEVICE(self), 1000); /* these are unrecoverable */ fu_device_retry_add_recovery(FU_DEVICE(self), FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, NULL); fu_device_retry_add_recovery(FU_DEVICE(self), FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, NULL); } static void fu_logitech_bulkcontroller_device_finalize(GObject *object) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(object); g_string_free(self->device_info_response_json, TRUE); G_OBJECT_CLASS(fu_logitech_bulkcontroller_device_parent_class)->finalize(object); } static void fu_logitech_bulkcontroller_device_class_init(FuLogitechBulkcontrollerDeviceClass *klass) { FuDeviceClass *device_class = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_logitech_bulkcontroller_device_finalize; device_class->to_string = fu_logitech_bulkcontroller_device_to_string; device_class->write_firmware = fu_logitech_bulkcontroller_device_write_firmware; device_class->probe = fu_logitech_bulkcontroller_device_probe; device_class->setup = fu_logitech_bulkcontroller_device_setup; device_class->set_progress = fu_logitech_bulkcontroller_device_set_progress; fu_device_register_private_flag(device_class, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_CHECK_BUFFER_SIZE); fu_device_register_private_flag(device_class, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_POST_INSTALL); fu_device_register_private_flag(device_class, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_PHERIPHERAL_UPDATE); }