api,firmware: expose device ids
diff --git a/cli/mmcli-modem-firmware.c b/cli/mmcli-modem-firmware.c
index ffcceb6..21ba5bb 100644
--- a/cli/mmcli-modem-firmware.c
+++ b/cli/mmcli-modem-firmware.c
@@ -142,17 +142,20 @@
 static void
 print_firmware_status (void)
 {
-    MMFirmwareUpdateSettings *update_settings;
-    const gchar              *method = NULL;
-    const gchar              *fastboot_at = NULL;
+    MMFirmwareUpdateSettings  *update_settings;
+    const gchar               *method = NULL;
+    const gchar              **device_ids = NULL;
+    const gchar               *fastboot_at = NULL;
 
     update_settings = mm_modem_firmware_peek_update_settings (ctx->modem_firmware);
     if (update_settings) {
         MMModemFirmwareUpdateMethod m;
 
         m = mm_firmware_update_settings_get_method (update_settings);
-        if (m != MM_MODEM_FIRMWARE_UPDATE_METHOD_UNKNOWN)
+        if (m != MM_MODEM_FIRMWARE_UPDATE_METHOD_UNKNOWN) {
             method = mm_modem_firmware_update_method_get_string (m);
+            device_ids = mm_firmware_update_settings_get_device_ids (update_settings);
+        }
 
         switch (m) {
         case MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT:
@@ -175,8 +178,9 @@
         exit (EXIT_FAILURE);
     }
 
-    mmcli_output_string (MMC_F_FIRMWARE_METHOD,      method);
-    mmcli_output_string (MMC_F_FIRMWARE_FASTBOOT_AT, fastboot_at);
+    mmcli_output_string       (MMC_F_FIRMWARE_METHOD,      method);
+    mmcli_output_string_array (MMC_F_FIRMWARE_DEVICE_IDS,  device_ids, FALSE);
+    mmcli_output_string       (MMC_F_FIRMWARE_FASTBOOT_AT, fastboot_at);
     mmcli_output_dump ();
 }
 
diff --git a/cli/mmcli-output.c b/cli/mmcli-output.c
index 628a4e1..0b19128 100644
--- a/cli/mmcli-output.c
+++ b/cli/mmcli-output.c
@@ -191,6 +191,7 @@
     [MMC_F_LOCATION_CDMABS_LAT]               = { "modem.location.cdma-bs.latitude",                 "latitude",                 MMC_S_MODEM_LOCATION_CDMABS,   },
     [MMC_F_FIRMWARE_LIST]                     = { "modem.firmware.list",                             "list",                     MMC_S_MODEM_FIRMWARE,          },
     [MMC_F_FIRMWARE_METHOD]                   = { "modem.firmware.method",                           "method",                   MMC_S_MODEM_FIRMWARE,          },
+    [MMC_F_FIRMWARE_DEVICE_IDS]               = { "modem.firmware.device-ids",                       "device ids",               MMC_S_MODEM_FIRMWARE,          },
     [MMC_F_FIRMWARE_FASTBOOT_AT]              = { "modem.firmware.fastboot.at",                      "at command",               MMC_S_MODEM_FIRMWARE_FASTBOOT, },
     [MMC_F_BEARER_GENERAL_DBUS_PATH]          = { "bearer.dbus-path",                                "dbus path",                MMC_S_BEARER_GENERAL,          },
     [MMC_F_BEARER_GENERAL_TYPE]               = { "bearer.type",                                     "type",                     MMC_S_BEARER_GENERAL,          },
diff --git a/cli/mmcli-output.h b/cli/mmcli-output.h
index a95c4a5..8cf5000 100644
--- a/cli/mmcli-output.h
+++ b/cli/mmcli-output.h
@@ -204,6 +204,7 @@
     /* Firmware status section */
     MMC_F_FIRMWARE_LIST,
     MMC_F_FIRMWARE_METHOD,
+    MMC_F_FIRMWARE_DEVICE_IDS,
     MMC_F_FIRMWARE_FASTBOOT_AT,
     /* Bearer general section */
     MMC_F_BEARER_GENERAL_DBUS_PATH,
diff --git a/docs/reference/libmm-glib/libmm-glib-sections.txt b/docs/reference/libmm-glib/libmm-glib-sections.txt
index 0931f3d..40b9fe3 100644
--- a/docs/reference/libmm-glib/libmm-glib-sections.txt
+++ b/docs/reference/libmm-glib/libmm-glib-sections.txt
@@ -738,11 +738,13 @@
 <SUBSECTION Getters>
 mm_firmware_update_settings_get_fastboot_at
 mm_firmware_update_settings_get_method
+mm_firmware_update_settings_get_device_ids
 <SUBSECTION Private>
 mm_firmware_update_settings_get_variant
 mm_firmware_update_settings_new
 mm_firmware_update_settings_new_from_variant
 mm_firmware_update_settings_set_fastboot_at
+mm_firmware_update_settings_set_device_ids
 <SUBSECTION Standard>
 MMFirmwareUpdateSettingsClass
 MMFirmwareUpdateSettingsPrivate
diff --git a/introspection/org.freedesktop.ModemManager1.Modem.Firmware.xml b/introspection/org.freedesktop.ModemManager1.Modem.Firmware.xml
index 3d00759..8e4a4f7 100644
--- a/introspection/org.freedesktop.ModemManager1.Modem.Firmware.xml
+++ b/introspection/org.freedesktop.ModemManager1.Modem.Firmware.xml
@@ -134,6 +134,14 @@
                 following additional settings:
               </para>
               <variablelist>
+                <varlistentry><term><literal>"device-ids"</literal></term>
+                  <listitem>
+                    (Required) This property exposes the list of device IDs associated to a given
+                    device, from most specific to least specific. (signature <literal>'as'</literal>).
+                    E.g. a list containing: <literal>"USB\VID_413C&amp;PID_81D7&amp;REV_0001"</literal>,
+                    <literal>"USB\VID_413C&amp;PID_81D7"</literal> and <literal>"USB\VID_413C"</literal>.
+                  </listitem>
+                </varlistentry>
                 <varlistentry><term><literal>"fastboot-at"</literal></term>
                   <listitem>
                     (Required) This property exposes the AT command that should be sent to the
diff --git a/libmm-glib/mm-firmware-update-settings.c b/libmm-glib/mm-firmware-update-settings.c
index 7ac081e..a8886c4 100644
--- a/libmm-glib/mm-firmware-update-settings.c
+++ b/libmm-glib/mm-firmware-update-settings.c
@@ -30,10 +30,13 @@
 
 G_DEFINE_TYPE (MMFirmwareUpdateSettings, mm_firmware_update_settings, G_TYPE_OBJECT)
 
+#define PROPERTY_DEVICE_IDS  "device-ids"
 #define PROPERTY_FASTBOOT_AT "fastboot-at"
 
 struct _MMFirmwareUpdateSettingsPrivate {
-    MMModemFirmwareUpdateMethod method;
+    /* Generic */
+    MMModemFirmwareUpdateMethod   method;
+    gchar                       **device_ids;
     /* Fasboot specific */
     gchar *fastboot_at;
 };
@@ -59,6 +62,35 @@
 /*****************************************************************************/
 
 /**
+ * mm_firmware_update_settings_get_device_ids:
+ * @self: a #MMFirmwareUpdateSettings.
+ *
+ * Gets the list of device ids used to identify the device during a firmware update
+ * operation.
+ *
+ * Returns: (transfer none): The list of device ids, or %NULL if unknown. Do not free the returned value, it is owned by @self.
+ */
+const gchar **
+mm_firmware_update_settings_get_device_ids (MMFirmwareUpdateSettings *self)
+{
+    g_return_val_if_fail (MM_IS_FIRMWARE_UPDATE_SETTINGS (self), NULL);
+
+    return (const gchar **) self->priv->device_ids;
+}
+
+void
+mm_firmware_update_settings_set_device_ids (MMFirmwareUpdateSettings  *self,
+                                            const gchar              **device_ids)
+{
+    g_return_if_fail (MM_IS_FIRMWARE_UPDATE_SETTINGS (self));
+
+    g_strfreev (self->priv->device_ids);
+    self->priv->device_ids = g_strdupv ((gchar **)device_ids);
+}
+
+/*****************************************************************************/
+
+/**
  * mm_firmware_update_settings_get_fastboot_at:
  * @self: a #MMFirmwareUpdateSettings.
  *
@@ -110,18 +142,25 @@
     g_variant_builder_init (&builder, G_VARIANT_TYPE ("(ua{sv})"));
     g_variant_builder_add (&builder, "u", method);
 
-    switch (method) {
-    case MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT:
-        g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
+    g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
+    {
         g_variant_builder_add (&builder,
                                "{sv}",
-                               PROPERTY_FASTBOOT_AT,
-                               g_variant_new_string (self->priv->fastboot_at));
-        g_variant_builder_close (&builder);
-        break;
-    default:
-        break;
+                               PROPERTY_DEVICE_IDS,
+                               g_variant_new_strv ((const gchar * const *)self->priv->device_ids, -1));
+
+        switch (method) {
+        case MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT:
+            g_variant_builder_add (&builder,
+                                   "{sv}",
+                                   PROPERTY_FASTBOOT_AT,
+                                   g_variant_new_string (self->priv->fastboot_at));
+            break;
+        default:
+            break;
+        }
     }
+    g_variant_builder_close (&builder);
 
     return g_variant_ref_sink (g_variant_builder_end (&builder));
 }
@@ -137,6 +176,9 @@
     if (g_str_equal (key, PROPERTY_FASTBOOT_AT)) {
         g_free (self->priv->fastboot_at);
         self->priv->fastboot_at = g_variant_dup_string (value, NULL);
+    } else if (g_str_equal (key, PROPERTY_DEVICE_IDS)) {
+        g_strfreev (self->priv->device_ids);
+        self->priv->device_ids = g_variant_dup_strv (value, NULL);
     } else {
         g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
                      "Invalid settings dictionary, unexpected key '%s'", key);
@@ -190,6 +232,9 @@
             g_variant_unref (value);
         }
 
+        if (!inner_error && !self->priv->device_ids)
+            inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+                                       "Missing required'" PROPERTY_DEVICE_IDS "' setting");
         if (!inner_error) {
             switch (method) {
             case MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT:
@@ -213,14 +258,6 @@
 
 /*****************************************************************************/
 
-/**
- * mm_firmware_update_settings_new:
- * @method: A #MMModemFirmwareUpdateMethod specifying the update method.
- *
- * Creates a new #MMFirmwareUpdateSettings object.
- *
- * Returns: (transfer full): A #MMFirmwareUpdateSettings. The returned value should be freed with g_object_unref().
- */
 MMFirmwareUpdateSettings *
 mm_firmware_update_settings_new (MMModemFirmwareUpdateMethod method)
 {
@@ -243,6 +280,7 @@
 {
     MMFirmwareUpdateSettings *self = MM_FIRMWARE_UPDATE_SETTINGS (object);
 
+    g_strfreev (self->priv->device_ids);
     g_free (self->priv->fastboot_at);
 
     G_OBJECT_CLASS (mm_firmware_update_settings_parent_class)->finalize (object);
diff --git a/libmm-glib/mm-firmware-update-settings.h b/libmm-glib/mm-firmware-update-settings.h
index 37acc42..2a4cb80 100644
--- a/libmm-glib/mm-firmware-update-settings.h
+++ b/libmm-glib/mm-firmware-update-settings.h
@@ -61,6 +61,9 @@
 
 MMModemFirmwareUpdateMethod mm_firmware_update_settings_get_method (MMFirmwareUpdateSettings *self);
 
+/* Generic */
+const gchar **mm_firmware_update_settings_get_device_ids (MMFirmwareUpdateSettings *self);
+
 /* Fastboot specific */
 const gchar *mm_firmware_update_settings_get_fastboot_at (MMFirmwareUpdateSettings *self);
 
@@ -78,6 +81,10 @@
 
 GVariant *mm_firmware_update_settings_get_variant (MMFirmwareUpdateSettings *self);
 
+/* Generic */
+void mm_firmware_update_settings_set_device_ids (MMFirmwareUpdateSettings  *self,
+                                                 const gchar              **device_ids);
+
 /* Fastboot specific */
 void mm_firmware_update_settings_set_fastboot_at (MMFirmwareUpdateSettings *self,
                                                   const gchar              *fastboot_at);
diff --git a/src/mm-iface-modem-firmware.c b/src/mm-iface-modem-firmware.c
index 571758a..11eff7c 100644
--- a/src/mm-iface-modem-firmware.c
+++ b/src/mm-iface-modem-firmware.c
@@ -282,6 +282,51 @@
     return g_task_propagate_boolean (G_TASK (res), error);
 }
 
+static gboolean
+add_generic_device_ids (MMBaseModem               *self,
+                        MMFirmwareUpdateSettings  *update_settings,
+                        GError                   **error)
+{
+    guint16      vid;
+    guint16      pid;
+    guint16      rid;
+    GPtrArray   *ids;
+    MMPort      *primary = NULL;
+    const gchar *subsystem;
+
+    vid = mm_base_modem_get_vendor_id (self);
+    pid = mm_base_modem_get_product_id (self);
+
+#if defined WITH_QMI
+    primary = MM_PORT (mm_base_modem_peek_port_qmi (self));
+#endif
+#if defined WITH_MBIM
+    if (!primary)
+        primary = MM_PORT (mm_base_modem_peek_port_mbim (self));
+#endif
+    if (!primary)
+        primary = MM_PORT (mm_base_modem_peek_port_primary (self));
+    g_assert (primary != NULL);
+    rid = mm_kernel_device_get_physdev_revision (mm_port_peek_kernel_device (primary));
+
+    subsystem = mm_kernel_device_get_physdev_subsystem (mm_port_peek_kernel_device (primary));
+    if (g_strcmp0 (subsystem, "usb")) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                     "Unsupported subsystem: %s", subsystem);
+        return FALSE;
+    }
+
+    ids = g_ptr_array_new_with_free_func ((GDestroyNotify)g_free);
+    g_ptr_array_add (ids, g_strdup_printf ("USB\\VID_%04X&PID_%04X&REV_%04X", vid, pid, rid));
+    g_ptr_array_add (ids, g_strdup_printf ("USB\\VID_%04X&PID_%04X", vid, pid));
+    g_ptr_array_add (ids, g_strdup_printf ("USB\\VID_%04X", vid));
+    g_ptr_array_add (ids, NULL);
+
+    mm_firmware_update_settings_set_device_ids (update_settings, (const gchar **)ids->pdata);
+    g_ptr_array_unref (ids);
+    return TRUE;
+}
+
 static void
 load_update_settings_ready (MMIfaceModemFirmware *self,
                             GAsyncResult         *res,
@@ -299,8 +344,15 @@
         mm_dbg ("Couldn't load update settings: '%s'", error->message);
         g_error_free (error);
     } else {
-        variant = mm_firmware_update_settings_get_variant (update_settings);
-        g_object_unref (update_settings);
+        /* If the plugin didn't specify custom device ids, add the default ones ourselves */
+        if (!mm_firmware_update_settings_get_device_ids (update_settings) &&
+            !add_generic_device_ids (MM_BASE_MODEM (self), update_settings, &error)) {
+            mm_warn ("Couldn't build device ids: '%s'", error->message);
+            g_error_free (error);
+        } else {
+            variant = mm_firmware_update_settings_get_variant (update_settings);
+            g_object_unref (update_settings);
+        }
     }
 
     mm_gdbus_modem_firmware_set_update_settings (ctx->skeleton, variant);