/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-io-channel.h" #include "fu-archive.h" #include "fu-mm-device.h" #include "fu-device-private.h" #include "fu-mm-utils.h" #include "fu-qmi-pdc-updater.h" /* Amount of time for the modem to boot in fastboot mode. */ #define FU_MM_DEVICE_REMOVE_DELAY_RE_ENUMERATE 20000 /* ms */ /* Amount of time for the modem to be re-probed and exposed in MM after being * uninhibited. The timeout is long enough to cover the worst case, where the * modem boots without SIM card inserted (and therefore the initialization * may be very slow) and also where carrier config switching is explicitly * required (e.g. if switching from the default (DF) to generic (GC).*/ #define FU_MM_DEVICE_REMOVE_DELAY_REPROBE 120000 /* ms */ struct _FuMmDevice { FuDevice parent_instance; MMManager *manager; /* ModemManager-based devices will have MMObject and inhibition_uid set, * udev-based ones won't (as device is already inhibited) */ MMObject *omodem; gchar *inhibition_uid; /* Properties read from the ModemManager-exposed modem, and to be * propagated to plain udev-exposed modem objects. We assume that * the firmware upgrade operation doesn't change the USB layout, and * therefore the USB interface of the modem device that was an * AT-capable TTY is assumed to be the same one after the upgrade. */ MMModemFirmwareUpdateMethod update_methods; gchar *detach_fastboot_at; gint port_at_ifnum; /* fastboot detach handling */ gchar *port_at; FuIOChannel *io_channel; /* qmi-pdc update logic */ gchar *port_qmi; FuQmiPdcUpdater *qmi_pdc_updater; GArray *qmi_pdc_active_id; guint attach_idle; }; enum { SIGNAL_ATTACH_FINISHED, SIGNAL_LAST }; static guint signals [SIGNAL_LAST] = { 0 }; G_DEFINE_TYPE (FuMmDevice, fu_mm_device, FU_TYPE_DEVICE) static void fu_mm_device_to_string (FuDevice *device, guint idt, GString *str) { FuMmDevice *self = FU_MM_DEVICE (device); if (self->port_at != NULL) fu_common_string_append_kv (str, idt, "AtPort", self->port_at); if (self->port_qmi != NULL) fu_common_string_append_kv (str, idt, "QmiPort", self->port_qmi); } const gchar * fu_mm_device_get_inhibition_uid (FuMmDevice *device) { g_return_val_if_fail (FU_IS_MM_DEVICE (device), NULL); return device->inhibition_uid; } MMModemFirmwareUpdateMethod fu_mm_device_get_update_methods (FuMmDevice *device) { g_return_val_if_fail (FU_IS_MM_DEVICE (device), MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE); return device->update_methods; } const gchar * fu_mm_device_get_detach_fastboot_at (FuMmDevice *device) { g_return_val_if_fail (FU_IS_MM_DEVICE (device), NULL); return device->detach_fastboot_at; } gint fu_mm_device_get_port_at_ifnum (FuMmDevice *device) { g_return_val_if_fail (FU_IS_MM_DEVICE (device), -1); return device->port_at_ifnum; } static gboolean fu_mm_device_probe_default (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); MMModemFirmware *modem_fw; MMModem *modem = mm_object_peek_modem (self->omodem); MMModemPortInfo *ports = NULL; const gchar **device_ids; const gchar *version; guint n_ports = 0; g_autoptr(MMFirmwareUpdateSettings) update_settings = NULL; g_autofree gchar *device_sysfs_path = NULL; /* inhibition uid is the modem interface 'Device' property, which may * be the device sysfs path or a different user-provided id */ self->inhibition_uid = mm_modem_dup_device (modem); /* find out what update methods we should use */ modem_fw = mm_object_peek_modem_firmware (self->omodem); update_settings = mm_modem_firmware_get_update_settings (modem_fw); self->update_methods = mm_firmware_update_settings_get_method (update_settings); if (self->update_methods == MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "modem cannot be put in programming mode"); return FALSE; } /* various fastboot commands */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) { const gchar *tmp; tmp = mm_firmware_update_settings_get_fastboot_at (update_settings); if (tmp == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "modem does not set fastboot command"); return FALSE; } self->detach_fastboot_at = g_strdup (tmp); } /* get GUIDs */ device_ids = mm_firmware_update_settings_get_device_ids (update_settings); if (device_ids == NULL || device_ids[0] == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "modem did not specify any device IDs"); return FALSE; } /* get version string, which is fw_ver+config_ver */ version = mm_firmware_update_settings_get_version (update_settings); if (version == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "modem did not specify a firmware version"); return FALSE; } /* look for the AT and QMI/MBIM ports */ if (!mm_modem_get_ports (modem, &ports, &n_ports)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to get port information"); return FALSE; } if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) { for (guint i = 0; i < n_ports; i++) { if (ports[i].type == MM_MODEM_PORT_TYPE_AT) { self->port_at = g_strdup_printf ("/dev/%s", ports[i].name); break; } } } if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) { for (guint i = 0; i < n_ports; i++) { if ((ports[i].type == MM_MODEM_PORT_TYPE_QMI) || (ports[i].type == MM_MODEM_PORT_TYPE_MBIM)) { self->port_qmi = g_strdup_printf ("/dev/%s", ports[i].name); break; } } } mm_modem_port_info_array_free (ports, n_ports); /* an at port is required for fastboot */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) && (self->port_at == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find AT port"); return FALSE; } /* a qmi port is required for qmi-pdc */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) && (self->port_qmi == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find QMI port"); return FALSE; } /* if we have the at port reported, get sysfs path and interface number */ if (self->port_at != NULL) { fu_mm_utils_get_port_info (self->port_at, &device_sysfs_path, &self->port_at_ifnum, NULL); } else if (self->port_qmi != NULL) { fu_mm_utils_get_port_info (self->port_qmi, &device_sysfs_path, NULL, NULL); } else { g_warn_if_reached (); } /* if no device sysfs file, error out */ if (device_sysfs_path == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find device sysfs path"); return FALSE; } /* add properties to fwupd device */ fu_device_set_physical_id (device, device_sysfs_path); if (mm_modem_get_manufacturer (modem) != NULL) fu_device_set_vendor (device, mm_modem_get_manufacturer (modem)); if (mm_modem_get_model (modem) != NULL) fu_device_set_name (device, mm_modem_get_model (modem)); fu_device_set_version (device, version, FWUPD_VERSION_FORMAT_PLAIN); for (guint i = 0; device_ids[i] != NULL; i++) fu_device_add_instance_id (device, device_ids[i]); /* convert the instance IDs to GUIDs */ fu_device_convert_instance_ids (device); return TRUE; } static gboolean fu_mm_device_probe_udev (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); /* an at port is required for fastboot */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) && (self->port_at == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find AT port"); return FALSE; } /* a qmi port is required for qmi-pdc */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) && (self->port_qmi == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find QMI port"); return FALSE; } return TRUE; } static gboolean fu_mm_device_probe (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); if (self->omodem) { return fu_mm_device_probe_default (device, error); } else { return fu_mm_device_probe_udev (device, error); } } static gboolean fu_mm_device_at_cmd (FuMmDevice *self, const gchar *cmd, GError **error) { const gchar *buf; gsize bufsz = 0; g_autoptr(GBytes) at_req = NULL; g_autoptr(GBytes) at_res = NULL; g_autofree gchar *cmd_cr = g_strdup_printf ("%s\r\n", cmd); /* command */ at_req = g_bytes_new (cmd_cr, strlen (cmd_cr)); if (g_getenv ("FWUPD_MODEM_MANAGER_VERBOSE") != NULL) fu_common_dump_bytes (G_LOG_DOMAIN, "writing", at_req); if (!fu_io_channel_write_bytes (self->io_channel, at_req, 1500, FU_IO_CHANNEL_FLAG_FLUSH_INPUT, error)) { g_prefix_error (error, "failed to write %s: ", cmd); return FALSE; } /* response */ at_res = fu_io_channel_read_bytes (self->io_channel, -1, 1500, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error); if (at_res == NULL) { g_prefix_error (error, "failed to read response for %s: ", cmd); return FALSE; } if (g_getenv ("FWUPD_MODEM_MANAGER_VERBOSE") != NULL) fu_common_dump_bytes (G_LOG_DOMAIN, "read", at_res); buf = g_bytes_get_data (at_res, &bufsz); if (bufsz < 6) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to read valid response for %s", cmd); return FALSE; } if (memcmp (buf, "\r\nOK\r\n", 6) != 0) { g_autofree gchar *tmp = g_strndup (buf + 2, bufsz - 4); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to read valid response for %s: %s", cmd, tmp); return FALSE; } return TRUE; } static gboolean fu_mm_device_io_open (FuMmDevice *self, GError **error) { /* open device */ self->io_channel = fu_io_channel_new_file (self->port_at, error); if (self->io_channel == NULL) return FALSE; /* success */ return TRUE; } static gboolean fu_mm_device_io_close (FuMmDevice *self, GError **error) { if (!fu_io_channel_shutdown (self->io_channel, error)) return FALSE; g_clear_object (&self->io_channel); return TRUE; } static gboolean fu_mm_device_detach_fastboot (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); g_autoptr(FuDeviceLocker) locker = NULL; /* boot to fastboot mode */ locker = fu_device_locker_new_full (device, (FuDeviceLockerFunc) fu_mm_device_io_open, (FuDeviceLockerFunc) fu_mm_device_io_close, error); if (locker == NULL) return FALSE; if (!fu_mm_device_at_cmd (self, "AT", error)) return FALSE; if (!fu_mm_device_at_cmd (self, self->detach_fastboot_at, error)) { g_prefix_error (error, "rebooting into fastboot not supported: "); return FALSE; } /* success */ fu_device_set_remove_delay (device, FU_MM_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_mm_device_detach (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; /* This plugin supports currently two methods to download firmware: * fastboot and qmi-pdc. A modem may require one of those, or both, * depending on the update type or the modem type. * * The first time this detach() method is executed is always for a * FuMmDevice that was created from a MM-exposed modem, which is the * moment when we're going to decide the amount of retries we need to * flash all firmware. * * If the FuMmModem is created from a MM-exposed modem and... * a) we only support fastboot, we just trigger the fastboot detach. * b) we only support qmi-pdc, we just exit without any detach. * c) we support both fastboot and qmi-pdc, we will set the * ANOTHER_WRITE_REQUIRED flag in the device and we'll trigger * the fastboot detach. * * If the FuMmModem is created from udev events... * d) it means we're in the extra required write that was flagged * in an earlier detach(), and we need to perform the qmi-pdc * update procedure at this time, so we just exit without any * detach. */ /* FuMmDevice created from MM... */ if (self->omodem != NULL) { /* both fastboot and qmi-pdc supported? another write required */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) && (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC)) { g_debug ("both fastboot and qmi-pdc supported, so the upgrade requires another write"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); } /* fastboot */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) return fu_mm_device_detach_fastboot (device, error); /* otherwise, assume we don't need any detach */ return TRUE; } /* FuMmDevice created from udev... * assume we don't need any detach */ return TRUE; } typedef struct { gchar *filename; GBytes *bytes; GArray *digest; gboolean active; } FuMmFileInfo; static void fu_mm_file_info_free (FuMmFileInfo *file_info) { g_clear_pointer (&file_info->digest, g_array_unref); g_free (file_info->filename); g_bytes_unref (file_info->bytes); g_free (file_info); } typedef struct { FuMmDevice *device; GError *error; GPtrArray *file_infos; gsize total_written; gsize total_bytes; } FuMmArchiveIterateCtx; static gboolean fu_mm_should_be_active (const gchar *version, const gchar *filename) { g_auto(GStrv) split = NULL; g_autofree gchar *carrier_id = NULL; /* The filename of the mcfg file is composed of a "mcfg." prefix, then the * carrier code, followed by the carrier version, and finally a ".mbn" * prefix. Here we try to guess, based on the carrier code, whether the * specific mcfg file should be activated after the firmware upgrade * operation. * * This logic requires that the previous device version includes the carrier * code also embedded in the version string. E.g. "xxxx.VF.xxxx". If we find * this match, we assume this is the active config to use. */ split = g_strsplit (filename, ".", -1); if (g_strv_length (split) < 4) return FALSE; if (g_strcmp0 (split[0], "mcfg") != 0) return FALSE; carrier_id = g_strdup_printf (".%s.", split[1]); return (g_strstr_len (version, -1, carrier_id) != NULL); } static gboolean fu_mm_qmi_pdc_archive_iterate_mcfg (FuArchive *archive, const gchar *filename, GBytes *bytes, gpointer user_data, GError **error) { FuMmArchiveIterateCtx *ctx = user_data; FuMmFileInfo *file_info; /* filenames should be named as 'mcfg.*.mbn', e.g.: mcfg.A2.018.mbn */ if (!g_str_has_prefix (filename, "mcfg.") || !g_str_has_suffix (filename, ".mbn")) return TRUE; file_info = g_new0 (FuMmFileInfo, 1); file_info->filename = g_strdup (filename); file_info->bytes = g_bytes_ref (bytes); file_info->active = fu_mm_should_be_active (fu_device_get_version (FU_DEVICE (ctx->device)), filename); g_ptr_array_add (ctx->file_infos, file_info); ctx->total_bytes += g_bytes_get_size (file_info->bytes); return TRUE; } static gboolean fu_mm_device_qmi_open (FuMmDevice *self, GError **error) { self->qmi_pdc_updater = fu_qmi_pdc_updater_new (self->port_qmi); return fu_qmi_pdc_updater_open (self->qmi_pdc_updater, error); } static gboolean fu_mm_device_qmi_close (FuMmDevice *self, GError **error) { g_autoptr(FuQmiPdcUpdater) updater = NULL; updater = g_steal_pointer (&self->qmi_pdc_updater); return fu_qmi_pdc_updater_close (updater, error); } static gboolean fu_mm_device_qmi_close_no_error (FuMmDevice *self, GError **error) { g_autoptr(FuQmiPdcUpdater) updater = NULL; updater = g_steal_pointer (&self->qmi_pdc_updater); fu_qmi_pdc_updater_close (updater, NULL); return TRUE; } static gboolean fu_mm_device_write_firmware_qmi_pdc (FuDevice *device, GBytes *fw, GArray **active_id, GError **error) { g_autoptr(FuArchive) archive = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GPtrArray) file_infos = g_ptr_array_new_with_free_func ((GDestroyNotify)fu_mm_file_info_free); gint active_i = -1; FuMmArchiveIterateCtx archive_context = { .device = FU_MM_DEVICE (device), .error = NULL, .file_infos = file_infos, .total_written = 0, .total_bytes = 0, }; /* decompress entire archive ahead of time */ archive = fu_archive_new (fw, FU_ARCHIVE_FLAG_IGNORE_PATH, error); if (archive == NULL) return FALSE; /* boot to fastboot mode */ locker = fu_device_locker_new_full (device, (FuDeviceLockerFunc) fu_mm_device_qmi_open, (FuDeviceLockerFunc) fu_mm_device_qmi_close, error); if (locker == NULL) return FALSE; /* process the list of MCFG files to write */ if (!fu_archive_iterate (archive, fu_mm_qmi_pdc_archive_iterate_mcfg, &archive_context, error)) return FALSE; for (guint i = 0; i < file_infos->len; i++) { FuMmFileInfo *file_info = g_ptr_array_index (file_infos, i); file_info->digest = fu_qmi_pdc_updater_write (archive_context.device->qmi_pdc_updater, file_info->filename, file_info->bytes, &archive_context.error); if (file_info->digest == NULL) { g_prefix_error (&archive_context.error, "Failed to write file '%s':", file_info->filename); break; } /* if we wrongly detect more than one, just assume the latest one; this * is not critical, it may just take a bit more time to perform the * automatic carrier config switching in ModemManager */ if (file_info->active) active_i = i; } /* set expected active configuration */ if (active_i >= 0 && active_id != NULL) { FuMmFileInfo *file_info = g_ptr_array_index (file_infos, active_i); *active_id = g_array_ref (file_info->digest); } if (archive_context.error != NULL) { g_propagate_error (error, archive_context.error); return FALSE; } return TRUE; } static gboolean fu_mm_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) array = NULL; /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; /* lock device */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; /* qmi pdc write operation */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) return fu_mm_device_write_firmware_qmi_pdc (device, fw, &self->qmi_pdc_active_id, error); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported update method"); return FALSE; } static gboolean fu_mm_device_attach_qmi_pdc (FuMmDevice *self, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* ignore action if there is no active id specified */ if (self->qmi_pdc_active_id == NULL) return TRUE; /* errors closing may be expected if the device really reboots itself */ locker = fu_device_locker_new_full (self, (FuDeviceLockerFunc) fu_mm_device_qmi_open, (FuDeviceLockerFunc) fu_mm_device_qmi_close_no_error, error); if (locker == NULL) return FALSE; if (!fu_qmi_pdc_updater_activate (self->qmi_pdc_updater, self->qmi_pdc_active_id, error)) return FALSE; return TRUE; } static gboolean fu_mm_device_attach_noop_idle (gpointer user_data) { FuMmDevice *self = FU_MM_DEVICE (user_data); self->attach_idle = 0; g_signal_emit (self, signals [SIGNAL_ATTACH_FINISHED], 0); return G_SOURCE_REMOVE; } static gboolean fu_mm_device_attach_qmi_pdc_idle (gpointer user_data) { FuMmDevice *self = FU_MM_DEVICE (user_data); g_autoptr(GError) error = NULL; if (!fu_mm_device_attach_qmi_pdc (self, &error)) g_warning ("qmi-pdc attach operation failed: %s", error->message); else g_debug ("qmi-pdc attach operation successful"); self->attach_idle = 0; g_signal_emit (self, signals [SIGNAL_ATTACH_FINISHED], 0); return G_SOURCE_REMOVE; } static gboolean fu_mm_device_attach (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); g_autoptr(FuDeviceLocker) locker = NULL; /* lock device */ locker = fu_device_locker_new (device, error); if (locker == NULL) return FALSE; /* we want this attach operation to be triggered asynchronously, because the engine * must learn that it has to wait for replug before we actually trigger the reset. */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) self->attach_idle = g_idle_add ((GSourceFunc) fu_mm_device_attach_qmi_pdc_idle, self); else self->attach_idle = g_idle_add ((GSourceFunc) fu_mm_device_attach_noop_idle, self); /* wait for re-probing after uninhibiting */ fu_device_set_remove_delay (device, FU_MM_DEVICE_REMOVE_DELAY_REPROBE); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_mm_device_init (FuMmDevice *self) { fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION); fu_device_set_summary (FU_DEVICE (self), "Mobile broadband device"); fu_device_add_icon (FU_DEVICE (self), "network-modem"); } static void fu_mm_device_finalize (GObject *object) { FuMmDevice *self = FU_MM_DEVICE (object); if (self->attach_idle) g_source_remove (self->attach_idle); if (self->qmi_pdc_active_id) g_array_unref (self->qmi_pdc_active_id); g_object_unref (self->manager); if (self->omodem != NULL) g_object_unref (self->omodem); g_free (self->detach_fastboot_at); g_free (self->port_at); g_free (self->port_qmi); g_free (self->inhibition_uid); G_OBJECT_CLASS (fu_mm_device_parent_class)->finalize (object); } static void fu_mm_device_class_init (FuMmDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); object_class->finalize = fu_mm_device_finalize; klass_device->to_string = fu_mm_device_to_string; klass_device->probe = fu_mm_device_probe; klass_device->detach = fu_mm_device_detach; klass_device->write_firmware = fu_mm_device_write_firmware; klass_device->attach = fu_mm_device_attach; signals [SIGNAL_ATTACH_FINISHED] = g_signal_new ("attach-finished", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } FuMmDevice * fu_mm_device_new (MMManager *manager, MMObject *omodem) { FuMmDevice *self = g_object_new (FU_TYPE_MM_DEVICE, NULL); self->manager = g_object_ref (manager); self->omodem = g_object_ref (omodem); self->port_at_ifnum = -1; return self; } FuPluginMmInhibitedDeviceInfo * fu_plugin_mm_inhibited_device_info_new (FuMmDevice *device) { FuPluginMmInhibitedDeviceInfo *info; info = g_new0 (FuPluginMmInhibitedDeviceInfo, 1); info->physical_id = g_strdup (fu_device_get_physical_id (FU_DEVICE (device))); info->vendor = g_strdup (fu_device_get_vendor (FU_DEVICE (device))); info->name = g_strdup (fu_device_get_name (FU_DEVICE (device))); info->version = g_strdup (fu_device_get_version (FU_DEVICE (device))); info->guids = fu_device_get_guids (FU_DEVICE (device)); info->update_methods = fu_mm_device_get_update_methods (device); info->detach_fastboot_at = g_strdup (fu_mm_device_get_detach_fastboot_at (device)); info->port_at_ifnum = fu_mm_device_get_port_at_ifnum (device); info->inhibited_uid = g_strdup (fu_mm_device_get_inhibition_uid (device)); return info; } void fu_plugin_mm_inhibited_device_info_free (FuPluginMmInhibitedDeviceInfo *info) { g_free (info->inhibited_uid); g_free (info->physical_id); g_free (info->vendor); g_free (info->name); g_free (info->version); if (info->guids) g_ptr_array_unref (info->guids); g_free (info->detach_fastboot_at); g_free (info); } FuMmDevice * fu_mm_device_udev_new (MMManager *manager, FuPluginMmInhibitedDeviceInfo *info) { FuMmDevice *self = g_object_new (FU_TYPE_MM_DEVICE, NULL); g_debug ("creating udev-based mm device at %s", info->physical_id); self->manager = g_object_ref (manager); fu_device_set_physical_id (FU_DEVICE (self), info->physical_id); fu_device_set_vendor (FU_DEVICE (self), info->vendor); fu_device_set_name (FU_DEVICE (self), info->name); fu_device_set_version (FU_DEVICE (self), info->version, FWUPD_VERSION_FORMAT_PLAIN); self->update_methods = info->update_methods; self->detach_fastboot_at = g_strdup (info->detach_fastboot_at); self->port_at_ifnum = info->port_at_ifnum; for (guint i = 0; i < info->guids->len; i++) fu_device_add_guid (FU_DEVICE (self), g_ptr_array_index (info->guids, i)); return self; } void fu_mm_device_udev_add_port (FuMmDevice *self, const gchar *subsystem, const gchar *path, gint ifnum) { g_return_if_fail (FU_IS_MM_DEVICE (self)); /* cdc-wdm ports always added unless one already set */ if (g_str_equal (subsystem, "usbmisc") && (self->port_qmi == NULL)) { g_debug ("added QMI port %s (%s)", path, subsystem); self->port_qmi = g_strdup (path); return; } if (g_str_equal (subsystem, "tty") && (self->port_at == NULL) && (ifnum >= 0) && (ifnum == self->port_at_ifnum)) { g_debug ("added AT port %s (%s)", path, subsystem); self->port_at = g_strdup (path); return; } /* otherwise, ignore all other ports */ g_debug ("ignoring port %s (%s)", path, subsystem); }