/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include "fu-mm-device.h" #include "fu-mm-utils.h" #include "fu-qmi-pdc-updater.h" #include "fu-mbim-qdu-updater.h" #if MM_CHECK_VERSION(1,17,2) #include "fu-firehose-updater.h" #endif /* 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 */ /* Amount of time for the modem to get firmware version */ #define MAX_WAIT_TIME_SECS 150 /* s */ 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; gint port_qmi_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; /* mbim-qdu update logic */ gchar *port_mbim; #if MBIM_CHECK_VERSION(1,25,3) FuMbimQduUpdater *mbim_qdu_updater; #endif /* MBIM_CHECK_VERSION(1,25,3) */ /* firehose update handling */ gchar *port_qcdm; gchar *port_edl; #if MM_CHECK_VERSION(1,17,2) FuFirehoseUpdater *firehose_updater; #endif /* firmware path */ gchar *firmware_path; gchar *restore_firmware_path; }; 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); if (self->port_mbim != NULL) fu_common_string_append_kv (str, idt, "MbimPort", self->port_mbim); if (self->port_qcdm != NULL) fu_common_string_append_kv (str, idt, "QcdmPort", self->port_qcdm); } 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; } gint fu_mm_device_get_port_qmi_ifnum (FuMmDevice *device) { g_return_val_if_fail (FU_IS_MM_DEVICE (device), -1); return device->port_qmi_ifnum; } static gboolean validate_firmware_update_method (MMModemFirmwareUpdateMethod methods, GError **error) { static const MMModemFirmwareUpdateMethod supported_combinations[] = { MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT, MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC | MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT, #if MM_CHECK_VERSION(1,17,1) MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU, #endif /* MM_CHECK_VERSION(1,17,1) */ #if MM_CHECK_VERSION(1,17,2) MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE, #endif }; g_autofree gchar *methods_str = NULL; methods_str = mm_modem_firmware_update_method_build_string_from_mask (methods); for (guint i = 0; i < G_N_ELEMENTS (supported_combinations); i++) { if (supported_combinations[i] == methods) { g_debug ("valid firmware update combination: %s", methods_str); return TRUE; } } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid firmware update combination: %s", methods_str); return FALSE; } 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; GPtrArray *vendors; g_autoptr(MMFirmwareUpdateSettings) update_settings = NULL; g_autofree gchar *device_sysfs_path = NULL; g_autofree gchar *device_bus = 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; } /* make sure the combination is supported */ if (!validate_firmware_update_method (self->update_methods, error)) 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; } } fu_device_add_protocol (device, "com.google.fastboot"); } 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; } } /* only set if fastboot wasn't already set */ if (fu_device_get_protocols (device)->len == 0) fu_device_add_protocol (device, "com.qualcomm.qmi_pdc"); } #if MM_CHECK_VERSION(1,17,1) if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU) { for (guint i = 0; i < n_ports; i++) { if (ports[i].type == MM_MODEM_PORT_TYPE_MBIM) { self->port_mbim = g_strdup_printf ("/dev/%s", ports[i].name); break; } } fu_device_add_protocol (device, "com.qualcomm.mbim_qdu"); } #endif /* MM_CHECK_VERSION(1,17,1) */ #if MM_CHECK_VERSION(1,17,2) if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE) { for (guint i = 0; i < n_ports; i++) { if (ports[i].type == MM_MODEM_PORT_TYPE_QCDM) { self->port_qcdm = g_strdup_printf ("/dev/%s", ports[i].name); break; } } fu_device_add_protocol (device, "com.qualcomm.firehose"); } #endif 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 MM_CHECK_VERSION(1,17,1) /* a mbim port is required for mbim-qdu */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU) && (self->port_mbim == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find MBIM port"); return FALSE; } #endif /* MM_CHECK_VERSION(1,17,1) */ #if MM_CHECK_VERSION(1,17,2) /* a qcdm port is required for firehose */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE) && (self->port_qcdm == NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find QCDM port"); return FALSE; } #endif if (self->port_at != NULL) { fu_mm_utils_get_port_info (self->port_at, &device_bus, &device_sysfs_path, &self->port_at_ifnum, NULL); } if (self->port_qmi != NULL) { g_autofree gchar *qmi_device_sysfs_path = NULL; g_autofree gchar *qmi_device_bus = NULL; fu_mm_utils_get_port_info (self->port_qmi, &qmi_device_bus, &qmi_device_sysfs_path, &self->port_qmi_ifnum, NULL); if (device_sysfs_path == NULL && qmi_device_sysfs_path != NULL) { device_sysfs_path = g_steal_pointer (&qmi_device_sysfs_path); } else if (g_strcmp0 (device_sysfs_path, qmi_device_sysfs_path) != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "mismatched device sysfs path: %s != %s", device_sysfs_path, qmi_device_sysfs_path); return FALSE; } if (device_bus == NULL && qmi_device_bus != NULL) { device_bus = g_steal_pointer (&qmi_device_bus); } else if (g_strcmp0 (device_bus, qmi_device_bus) != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "mismatched device bus: %s != %s", device_bus, qmi_device_bus); return FALSE; } } if (self->port_mbim != NULL) { g_autofree gchar *mbim_device_sysfs_path = NULL; g_autofree gchar *mbim_device_bus = NULL; fu_mm_utils_get_port_info (self->port_mbim, &mbim_device_bus, &mbim_device_sysfs_path, NULL, NULL); if (device_sysfs_path == NULL && mbim_device_sysfs_path != NULL) { device_sysfs_path = g_steal_pointer (&mbim_device_sysfs_path); } else if (g_strcmp0 (device_sysfs_path, mbim_device_sysfs_path) != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "mismatched device sysfs path: %s != %s", device_sysfs_path, mbim_device_sysfs_path); return FALSE; } if (device_bus == NULL && mbim_device_bus != NULL) { device_bus = g_steal_pointer (&mbim_device_bus); } else if (g_strcmp0 (device_bus, mbim_device_bus) != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "mismatched device bus: %s != %s", device_bus, mbim_device_bus); return FALSE; } } if (self->port_qcdm != NULL) { g_autofree gchar *qcdm_device_sysfs_path = NULL; g_autofree gchar *qcdm_device_bus = NULL; fu_mm_utils_get_port_info (self->port_qcdm, &qcdm_device_bus, &qcdm_device_sysfs_path, NULL, NULL); if (device_sysfs_path == NULL && qcdm_device_sysfs_path != NULL) { device_sysfs_path = g_steal_pointer (&qcdm_device_sysfs_path); } else if (g_strcmp0 (device_sysfs_path, qcdm_device_sysfs_path) != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "mismatched device sysfs path: %s != %s", device_sysfs_path, qcdm_device_sysfs_path); return FALSE; } if (device_bus == NULL && qcdm_device_bus != NULL) { device_bus = g_steal_pointer (&qcdm_device_bus); } else if (g_strcmp0 (device_bus, qcdm_device_bus) != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "mismatched device bus: %s != %s", device_bus, qcdm_device_bus); return FALSE; } } /* if no device sysfs file, error out */ if (device_sysfs_path == NULL || device_bus == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find device details"); 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); for (guint i = 0; device_ids[i] != NULL; i++) fu_device_add_instance_id (device, device_ids[i]); vendors = fu_device_get_vendor_ids (device); if (vendors == NULL || vendors->len == 0) { g_autofree gchar *path = NULL; g_autofree gchar *value_str = NULL; g_autoptr(GError) error_local = NULL; if (g_strcmp0 (device_bus, "USB") == 0) path = g_build_filename (device_sysfs_path, "idVendor", NULL); else if (g_strcmp0 (device_bus, "PCI") == 0) path = g_build_filename (device_sysfs_path, "vendor", NULL); if (path == NULL) { g_warning ("failed to set vendor ID: unsupported bus: %s", device_bus); } else if (!g_file_get_contents (path, &value_str, NULL, &error_local)) { g_warning ("failed to set vendor ID: %s", error_local->message); } else { guint64 value_int; /* note: the string value may be prefixed with '0x' (e.g. when reading * the PCI 'vendor' attribute, or not prefixed with anything, as in the * USB 'idVendor' attribute. */ value_int = g_ascii_strtoull (value_str, NULL, 16); if (value_int > G_MAXUINT16) { g_warning ("failed to set vendor ID: invalid value: %s", value_str); } else { g_autofree gchar *vendor_id = g_strdup_printf ("%s:0x%04X", device_bus, (guint) value_int); fu_device_add_vendor_id (device, vendor_id); } } } 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); } } #if MM_CHECK_VERSION(1,17,2) static gboolean fu_mm_device_io_open_qcdm (FuMmDevice *self, GError **error) { /* open device */ self->io_channel = fu_io_channel_new_file (self->port_qcdm, error); if (self->io_channel == NULL) return FALSE; /* success */ return TRUE; } static gboolean fu_mm_device_qcdm_cmd (FuMmDevice *self, const guint8 *cmd, gsize cmd_len, GError **error) { g_autoptr(GBytes) qcdm_req = NULL; g_autoptr(GBytes) qcdm_res = NULL; /* command */ qcdm_req = g_bytes_new (cmd, cmd_len); if (g_getenv ("FWUPD_MODEM_MANAGER_VERBOSE") != NULL) fu_common_dump_bytes (G_LOG_DOMAIN, "writing", qcdm_req); if (!fu_io_channel_write_bytes (self->io_channel, qcdm_req, 1500, FU_IO_CHANNEL_FLAG_FLUSH_INPUT, error)) { g_prefix_error (error, "failed to write qcdm command: "); return FALSE; } /* response */ qcdm_res = fu_io_channel_read_bytes (self->io_channel, -1, 1500, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error); if (qcdm_res == NULL) { g_prefix_error (error, "failed to read qcdm response: "); return FALSE; } if (g_getenv ("FWUPD_MODEM_MANAGER_VERBOSE") != NULL) fu_common_dump_bytes (G_LOG_DOMAIN, "read", qcdm_res); /* command == response */ if (g_bytes_compare (qcdm_res, qcdm_req) != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to read valid qcdm response"); return FALSE; } return TRUE; } #endif /* MM_CHECK_VERSION(1,17,2) */ 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 (self->io_channel != NULL) { 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; } #if MM_CHECK_VERSION(1,17,2) static gboolean fu_mm_device_find_edl_port (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); for (guint i = 0; i < 30; i++) { if (fu_mm_utils_find_device_file (fu_device_get_physical_id ( FU_DEVICE (self)), "wwan", &self->port_edl, NULL)) return TRUE; g_usleep (250 * 1000); } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Couldn't find EDL port"); return FALSE; } static gboolean fu_mm_device_qcdm_switch_to_edl (FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); static const guint8 emergency_download[] = { 0x4b, 0x65, 0x01, 0x00, 0x54, 0x0f, 0x7e }; /* trigger emergency download mode, up to 30s retrying until the QCDM * port goes away; this takes us to the EDL (embedded downloader) execution * environment */ for (guint i = 0; i < 30; i++) { g_autoptr(GError) error_local = NULL; g_autoptr(FuDeviceLocker) locker = NULL; if (i > 0) g_usleep (G_USEC_PER_SEC); locker = fu_device_locker_new_full (self, (FuDeviceLockerFunc) fu_mm_device_io_open_qcdm, (FuDeviceLockerFunc) fu_mm_device_io_close, &error_local); if (locker == NULL) { if (i > 0 && g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) return fu_mm_device_find_edl_port (device, error); g_debug ("couldn't open QCDM port to switch to EDL mode: %s", error_local->message); break; } if (!fu_mm_device_qcdm_cmd (self, emergency_download, G_N_ELEMENTS (emergency_download), &error_local)) { g_debug ("couldn't send QCDM command to switch to EDL mode: %s", error_local->message); break; } } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Couldn't switch device to embedded downloader execution environment"); return FALSE; } #endif /* MM_CHECK_VERSION(1,17,2) */ 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 several methods to download firmware: * fastboot, qmi-pdc, firehose. A modem may require one of those, * or several, 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 support both fastboot and qmi-pdc, we will set the * ANOTHER_WRITE_REQUIRED flag in the device and we'll trigger * the fastboot detach. * c) we only support firehose, skip detach and switch to embedded * downloader mode (EDL) during write_firmware. * * If the FuMmModem is created from udev events... * c) 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; } 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); 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, }; /* 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; } #if MM_CHECK_VERSION(1,17,1) && MBIM_CHECK_VERSION(1,25,3) typedef struct { FuDevice *device; GMainLoop *mainloop; gchar *version; GError *error; } FuMmGetFirmwareVersionCtx; static gboolean fu_mm_device_mbim_open (FuMmDevice *self, GError **error) { self->mbim_qdu_updater = fu_mbim_qdu_updater_new (self->port_mbim); return fu_mbim_qdu_updater_open (self->mbim_qdu_updater, error); } static gboolean fu_mm_device_mbim_close (FuMmDevice *self, GError **error) { g_autoptr(FuMbimQduUpdater) updater = NULL; updater = g_steal_pointer (&self->mbim_qdu_updater); return fu_mbim_qdu_updater_close (updater, error); } static gboolean fu_mm_device_locker_new_timeout (gpointer user_data) { FuMmGetFirmwareVersionCtx *ctx = user_data; g_main_loop_quit (ctx->mainloop); return G_SOURCE_REMOVE; } static gboolean fu_mm_device_get_firmware_version_mbim_timeout (gpointer user_data) { FuMmGetFirmwareVersionCtx *ctx = user_data; FuMmDevice *self = FU_MM_DEVICE (ctx->device); g_clear_error (&ctx->error); ctx->version = fu_mbim_qdu_updater_check_ready (self->mbim_qdu_updater, &ctx->error); g_main_loop_quit (ctx->mainloop); return G_SOURCE_REMOVE; } static gchar * fu_mm_device_get_firmware_version_mbim (FuDevice *device, GError **error) { GTimer *timer = g_timer_new (); g_autoptr(GMainLoop) mainloop = g_main_loop_new (NULL, FALSE); FuMmGetFirmwareVersionCtx ctx = { .device = device, .mainloop = mainloop, .version = NULL, .error = NULL, }; while (ctx.version == NULL && g_timer_elapsed (timer, NULL) < MAX_WAIT_TIME_SECS) { g_autoptr(FuDeviceLocker) locker = NULL; g_clear_error (&ctx.error); locker = fu_device_locker_new_full (device, (FuDeviceLockerFunc) fu_mm_device_mbim_open, (FuDeviceLockerFunc) fu_mm_device_mbim_close, &ctx.error); if (locker == NULL) { g_timeout_add_seconds (20, fu_mm_device_locker_new_timeout, &ctx); g_main_loop_run (mainloop); continue; } g_timeout_add_seconds (10, fu_mm_device_get_firmware_version_mbim_timeout, &ctx); g_main_loop_run (mainloop); } g_timer_destroy (timer); if (ctx.version == NULL && ctx.error != NULL) { g_propagate_error (error, ctx.error); return NULL; } return ctx.version; } static gboolean fu_mm_device_writeln (const gchar *fn, const gchar *buf, GError **error) { int fd; g_autoptr(FuIOChannel) io = NULL; fd = open (fn, O_WRONLY); if (fd < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "could not open %s", fn); return FALSE; } io = fu_io_channel_unix_new (fd); return fu_io_channel_write_raw (io, (const guint8 *) buf, strlen (buf), 1000, FU_IO_CHANNEL_FLAG_NONE, error); } static gboolean fu_mm_device_write_firmware_mbim_qdu (FuDevice *device, GBytes *fw, GError **error) { GBytes *data; XbNode *part = NULL; const gchar *filename = NULL; const gchar *csum; FuMmDevice *self = FU_MM_DEVICE (device); g_autofree gchar *device_sysfs_path = NULL; g_autofree gchar *autosuspend_delay_filename = NULL; g_autofree gchar *csum_actual = NULL; g_autofree gchar *version = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(XbBuilder) builder = xb_builder_new (); g_autoptr(XbBuilderSource) source = xb_builder_source_new (); g_autoptr(XbSilo) silo = NULL; /* decompress entire archive ahead of time */ archive = fu_archive_new (fw, FU_ARCHIVE_FLAG_IGNORE_PATH, error); if (archive == NULL) return FALSE; locker = fu_device_locker_new_full (device, (FuDeviceLockerFunc) fu_mm_device_mbim_open, (FuDeviceLockerFunc) fu_mm_device_mbim_close, error); if (locker == NULL) return FALSE; /* load the manifest of operations */ data = fu_archive_lookup_by_fn (archive, "flashfile.xml", error); if (data == NULL) return FALSE; if (!xb_builder_source_load_bytes (source, data, XB_BUILDER_SOURCE_FLAG_NONE, error)) return FALSE; xb_builder_import_source (builder, source); silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; part = xb_silo_query_first (silo, "parts/part", error); if (part == NULL) return FALSE; filename = xb_node_get_attr (part, "filename"); csum = xb_node_get_attr (part, "MD5"); data = fu_archive_lookup_by_fn (archive, filename, error); if (data == NULL) return FALSE; csum_actual = g_compute_checksum_for_bytes (G_CHECKSUM_MD5, data); if (g_strcmp0 (csum, csum_actual) != 0) { g_debug ("[%s] MD5 not matched", filename); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "[%s] MD5 not matched", filename); return FALSE; } else { g_debug ("[%s] MD5 matched", filename); } /* autosuspend delay updated for a proper firmware update */ fu_mm_utils_get_port_info (self->port_mbim, NULL, &device_sysfs_path, NULL, NULL); autosuspend_delay_filename = g_build_filename (device_sysfs_path, "/power/autosuspend_delay_ms", NULL); if(!fu_mm_device_writeln (autosuspend_delay_filename, "10000", error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); fu_mbim_qdu_updater_write (self->mbim_qdu_updater, filename, data, device, error); if (!fu_device_locker_close (locker, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_READ); version = fu_mm_device_get_firmware_version_mbim (device, error); if (version == NULL) return FALSE; return TRUE; } #endif /* MM_CHECK_VERSION(1,17,1) && MBIM_CHECK_VERSION(1,25,3) */ #if MM_CHECK_VERSION(1,17,2) static gboolean fu_mm_device_firehose_open (FuMmDevice *self, GError **error) { self->firehose_updater = fu_firehose_updater_new (self->port_edl); return fu_firehose_updater_open (self->firehose_updater, error); } static gboolean fu_mm_device_firehose_close (FuMmDevice *self, GError **error) { g_autoptr(FuFirehoseUpdater) updater = NULL; updater = g_steal_pointer (&self->firehose_updater); return fu_firehose_updater_close (updater, error); } static gboolean fu_mm_device_firehose_write (FuMmDevice *self, XbSilo *rawprogram_silo, GPtrArray *rawprogram_actions, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; guint progress_signal_id; gboolean write_result; locker = fu_device_locker_new_full (self, (FuDeviceLockerFunc) fu_mm_device_firehose_open, (FuDeviceLockerFunc) fu_mm_device_firehose_close, error); if (locker == NULL) return FALSE; progress_signal_id = g_signal_connect_swapped (self->firehose_updater, "write-percentage", G_CALLBACK (fu_device_set_progress), self); write_result = fu_firehose_updater_write (self->firehose_updater, rawprogram_silo, rawprogram_actions, error); g_signal_handler_disconnect (self->firehose_updater, progress_signal_id); return write_result; } static gboolean fu_mm_setup_firmware_dir (FuMmDevice *self, GError **error) { g_autofree gchar *cachedir = NULL; g_autofree gchar *mm_fw_dir = NULL; /* create a directory to store firmware files for modem-manager plugin */ cachedir = fu_common_get_path (FU_PATH_KIND_CACHEDIR_PKG); mm_fw_dir = g_build_filename (cachedir, "modem-manager", "firmware", NULL); if (g_mkdir_with_parents (mm_fw_dir, 0700) == -1) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to create '%s': %s", mm_fw_dir, g_strerror (errno)); return FALSE; } if (!fu_common_set_firmware_search_path (mm_fw_dir, error)) return FALSE; self->firmware_path = g_steal_pointer (&mm_fw_dir); return TRUE; } static gboolean fu_mm_copy_firehose_prog (FuMmDevice *self, FuArchive *archive, GError **error) { g_autofree gchar *qcom_fw_dir = NULL; g_autofree gchar *firehose_file_path = NULL; g_autoptr(GBytes) firehose_prog = NULL; /* lookup firehose-prog bootloader */ firehose_prog = fu_archive_lookup_by_fn (archive, "firehose-prog.mbn", error); if (firehose_prog == NULL) return FALSE; qcom_fw_dir = g_build_filename (self->firmware_path, "qcom", NULL); if (!fu_common_mkdir_parent (qcom_fw_dir, error)) return FALSE; firehose_file_path = g_build_filename (qcom_fw_dir, "prog_firehose_sdx24.mbn", NULL); if (!fu_common_set_contents_bytes (firehose_file_path, firehose_prog, error)) return FALSE; return TRUE; } static gboolean fu_mm_prepare_firmware_search_path (FuMmDevice *self, GError **error) { self->restore_firmware_path = fu_common_get_firmware_search_path (NULL); return fu_mm_setup_firmware_dir (self, error); } static gboolean fu_mm_restore_firmware_search_path (FuMmDevice *self, GError **error) { if (self->restore_firmware_path != NULL && strlen (self->restore_firmware_path) > 0) return fu_common_set_firmware_search_path (self->restore_firmware_path, error); return fu_common_reset_firmware_search_path (error); } static gboolean fu_mm_device_write_firmware_firehose (FuDevice *device, GBytes *fw, GError **error) { FuMmDevice *self = FU_MM_DEVICE (device); GBytes *firehose_rawprogram; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(XbSilo) firehose_rawprogram_silo = NULL; g_autoptr(GPtrArray) firehose_rawprogram_actions = NULL; /* decompress entire archive ahead of time */ archive = fu_archive_new (fw, FU_ARCHIVE_FLAG_IGNORE_PATH, error); if (archive == NULL) return FALSE; /* modify firmware search path and restore it before function returns */ locker = fu_device_locker_new_full (self, (FuDeviceLockerFunc) fu_mm_prepare_firmware_search_path, (FuDeviceLockerFunc) fu_mm_restore_firmware_search_path, error); if (locker == NULL) return FALSE; /* firehose modems that switch to the EDL mode over QCDM port require * firehose binary to be present in the firmware-loader search path. */ if (!fu_mm_copy_firehose_prog (self, archive, error)) return FALSE; /* flag as restart because the QCDM and MHI/BHI operations switch the * device into FP execution environment */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_RESTART); /* switch to embedded downloader (EDL) execution environment */ if (!fu_mm_device_qcdm_switch_to_edl (FU_DEVICE(self), error)) return FALSE; /* lookup and validate firehose-rawprogram actions */ firehose_rawprogram = fu_archive_lookup_by_fn (archive, "firehose-rawprogram.xml", error); if (firehose_rawprogram == NULL) return FALSE; if (!fu_firehose_validate_rawprogram (firehose_rawprogram, archive, &firehose_rawprogram_silo, &firehose_rawprogram_actions, error)) { g_prefix_error (error, "Invalid firehose rawprogram manifest: "); return FALSE; } /* flag as write */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); /* download all files in the firehose-rawprogram manifest via Firehose */ if (!fu_mm_device_firehose_write (self, firehose_rawprogram_silo, firehose_rawprogram_actions, error)) return FALSE; /* flag as restart again, the module is switching to modem mode */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_RESTART); return TRUE; } #endif /* MM_CHECK_VERSION(1,17,2) */ 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(GBytes) fw = NULL; /* get default image */ fw = fu_firmware_get_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); #if MM_CHECK_VERSION(1,17,1) && MBIM_CHECK_VERSION(1,25,3) /* mbim qdu write operation */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU) return fu_mm_device_write_firmware_mbim_qdu (device, fw, error); #endif /* MM_CHECK_VERSION(1,17,1) && MBIM_CHECK_VERSION(1,25,3) */ #if MM_CHECK_VERSION(1,17,2) /* firehose operation */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE) return fu_mm_device_write_firmware_firehose (device, fw, error); #endif 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_add_internal_flag (FU_DEVICE (self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_PLAIN); 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->port_mbim); g_free (self->port_qcdm); g_free (self->inhibition_uid); g_free (self->firmware_path); g_free (self->restore_firmware_path); 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; self->port_qmi_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->port_qmi_ifnum = fu_mm_device_get_port_qmi_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); self->update_methods = info->update_methods; self->detach_fastboot_at = g_strdup (info->detach_fastboot_at); self->port_at_ifnum = info->port_at_ifnum; self->port_qmi_ifnum = info->port_qmi_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)); if (g_str_equal (subsystem, "usbmisc") && self->port_qmi == NULL && ifnum >= 0 && ifnum == self->port_qmi_ifnum) { 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); }