modem-manager: add generic support for PCI based modems

No longer rely on the modems being USB based, we can also support PCI
based devices with the same protocols.
This commit is contained in:
Aleksander Morgado 2020-11-24 10:30:36 +01:00 committed by Richard Hughes
parent 99b4475777
commit 3417128704
4 changed files with 150 additions and 48 deletions

View File

@ -144,6 +144,7 @@ fu_mm_device_probe_default (FuDevice *device, GError **error)
guint n_ports = 0; guint n_ports = 0;
g_autoptr(MMFirmwareUpdateSettings) update_settings = NULL; g_autoptr(MMFirmwareUpdateSettings) update_settings = NULL;
g_autofree gchar *device_sysfs_path = NULL; g_autofree gchar *device_sysfs_path = NULL;
g_autofree gchar *device_bus = NULL;
/* inhibition uid is the modem interface 'Device' property, which may /* inhibition uid is the modem interface 'Device' property, which may
* be the device sysfs path or a different user-provided id */ * be the device sysfs path or a different user-provided id */
@ -251,11 +252,12 @@ fu_mm_device_probe_default (FuDevice *device, GError **error)
} }
if (self->port_at != NULL) { if (self->port_at != NULL) {
fu_mm_utils_get_port_info (self->port_at, &device_sysfs_path, &self->port_at_ifnum, 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) { if (self->port_qmi != NULL) {
g_autofree gchar *qmi_device_sysfs_path = NULL; g_autofree gchar *qmi_device_sysfs_path = NULL;
fu_mm_utils_get_port_info (self->port_qmi, &qmi_device_sysfs_path, &self->port_qmi_ifnum, 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) { if (device_sysfs_path == NULL && qmi_device_sysfs_path != NULL) {
device_sysfs_path = g_steal_pointer (&qmi_device_sysfs_path); device_sysfs_path = g_steal_pointer (&qmi_device_sysfs_path);
} else if (g_strcmp0 (device_sysfs_path, qmi_device_sysfs_path) != 0) { } else if (g_strcmp0 (device_sysfs_path, qmi_device_sysfs_path) != 0) {
@ -264,14 +266,24 @@ fu_mm_device_probe_default (FuDevice *device, GError **error)
device_sysfs_path, qmi_device_sysfs_path); device_sysfs_path, qmi_device_sysfs_path);
return FALSE; 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 no device sysfs file, error out */ /* if no device sysfs file, error out */
if (device_sysfs_path == NULL) { if (device_sysfs_path == NULL || device_bus == NULL) {
g_set_error_literal (error, g_set_error_literal (error,
FWUPD_ERROR, FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED, FWUPD_ERROR_NOT_SUPPORTED,
"failed to find device sysfs path"); "failed to find device details");
return FALSE; return FALSE;
} }
@ -285,15 +297,32 @@ fu_mm_device_probe_default (FuDevice *device, GError **error)
for (guint i = 0; device_ids[i] != NULL; i++) for (guint i = 0; device_ids[i] != NULL; i++)
fu_device_add_instance_id (device, device_ids[i]); fu_device_add_instance_id (device, device_ids[i]);
if (fu_device_get_vendor_ids (device) == NULL) { if (fu_device_get_vendor_ids (device) == NULL) {
g_autofree gchar *path = g_build_filename (device_sysfs_path, "idVendor", NULL); g_autofree gchar *path = NULL;
g_autofree gchar *value = NULL; g_autofree gchar *value_str = NULL;
g_autoptr(GError) error_local = NULL; g_autoptr(GError) error_local = NULL;
if (!g_file_get_contents (path, &value, NULL, &error_local)) { 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); g_warning ("failed to set vendor ID: %s", error_local->message);
} else { } else {
g_autofree gchar *vendor_id = g_strdup_printf ("USB:0x%s", g_strchomp (value)); guint64 value_int;
fu_device_add_vendor_id (device, vendor_id);
/* 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);
}
} }
} }

View File

@ -12,59 +12,129 @@
#include "fu-mm-utils.h" #include "fu-mm-utils.h"
gboolean static gchar *
fu_mm_utils_get_udev_port_info (GUdevDevice *device, find_device_bus_subsystem (GUdevDevice *device)
gchar **out_device_sysfs_path,
gint *out_port_ifnum,
GError **error)
{ {
gint port_ifnum = -1; g_autoptr(GUdevDevice) iter = NULL;
const gchar *aux;
g_autoptr(GUdevDevice) parent = NULL;
g_autofree gchar *device_sysfs_path = NULL;
/* ID_USB_INTERFACE_NUM is set on the port device itself */ iter = g_object_ref (device);
aux = g_udev_device_get_property (device, "ID_USB_INTERFACE_NUM"); while (iter != NULL) {
if (aux != NULL)
port_ifnum = (guint16) g_ascii_strtoull (aux, NULL, 16);
/* we need to traverse all parents of the give udev device until we find
* the first 'usb_device' reported, which is the GUdevDevice associated with
* the full USB device (i.e. all ports of the same device).
*/
parent = g_udev_device_get_parent (device);
while (parent != NULL) {
g_autoptr(GUdevDevice) next = NULL; g_autoptr(GUdevDevice) next = NULL;
const gchar *subsys;
if (g_strcmp0 (g_udev_device_get_devtype (parent), "usb_device") == 0) { /* stop search as soon as we find a parent object
device_sysfs_path = g_strdup (g_udev_device_get_sysfs_path (parent)); * of one of the bus subsystems supported in
break; * ModemManager */
subsys = g_udev_device_get_subsystem (iter);
if ((g_strcmp0 (subsys, "usb") == 0) ||
(g_strcmp0 (subsys, "pcmcia") == 0) ||
(g_strcmp0 (subsys, "pci") == 0) ||
(g_strcmp0 (subsys, "platform") == 0) ||
(g_strcmp0 (subsys, "pnp") == 0) ||
(g_strcmp0 (subsys, "sdio") == 0)) {
return g_ascii_strup (subsys, -1);
} }
/* check next parent */ next = g_udev_device_get_parent (iter);
next = g_udev_device_get_parent (parent); g_set_object (&iter, next);
g_set_object (&parent, next);
} }
if (parent == NULL) { /* no more parents to check */
return NULL;
}
gboolean
fu_mm_utils_get_udev_port_info (GUdevDevice *device,
gchar **out_device_bus,
gchar **out_device_sysfs_path,
gint *out_port_usb_ifnum,
GError **error)
{
gint port_usb_ifnum = -1;
g_autoptr(GUdevDevice) parent = NULL;
g_autofree gchar *device_sysfs_path = NULL;
g_autofree gchar *device_bus = NULL;
/* lookup the main bus the device is in; for supported devices it will
* usually be either 'PCI' or 'USB' */
device_bus = find_device_bus_subsystem (device);
if (device_bus == NULL) {
g_set_error (error, g_set_error (error,
G_IO_ERROR, G_IO_ERROR,
G_IO_ERROR_NOT_FOUND, G_IO_ERROR_NOT_FOUND,
"failed to lookup device info: parent usb_device not found"); "failed to lookup device info: bus not found");
return FALSE; return FALSE;
} }
if (out_port_ifnum != NULL) if (g_strcmp0 (device_bus, "USB") == 0) {
*out_port_ifnum = port_ifnum; /* ID_USB_INTERFACE_NUM is set on the port device itself */
const gchar *aux = g_udev_device_get_property (device, "ID_USB_INTERFACE_NUM");
if (aux != NULL)
port_usb_ifnum = (guint16) g_ascii_strtoull (aux, NULL, 16);
/* we need to traverse all parents of the give udev device until we find
* the first 'usb_device' reported, which is the GUdevDevice associated with
* the full USB device (i.e. all ports of the same device).
*/
parent = g_udev_device_get_parent (device);
while (parent != NULL) {
g_autoptr(GUdevDevice) next = NULL;
if (g_strcmp0 (g_udev_device_get_devtype (parent), "usb_device") == 0) {
device_sysfs_path = g_strdup (g_udev_device_get_sysfs_path (parent));
break;
}
/* check next parent */
next = g_udev_device_get_parent (parent);
g_set_object (&parent, next);
}
} else if (g_strcmp0 (device_bus, "PCI") == 0) {
/* the first parent in the 'pci' subsystem is our physical device */
parent = g_udev_device_get_parent (device);
while (parent != NULL) {
g_autoptr(GUdevDevice) next = NULL;
if (g_strcmp0 (g_udev_device_get_subsystem (parent), "pci") == 0) {
device_sysfs_path = g_strdup (g_udev_device_get_sysfs_path (parent));
break;
}
/* check next parent */
next = g_udev_device_get_parent (parent);
g_set_object (&parent, next);
}
} else {
/* other subsystems, we don't support firmware upgrade for those */
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"device bus unsupported: %s", device_bus);
return FALSE;
}
if (device_sysfs_path == NULL) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"failed to lookup device info: physical device not found");
return FALSE;
}
if (out_port_usb_ifnum != NULL)
*out_port_usb_ifnum = port_usb_ifnum;
if (out_device_sysfs_path != NULL) if (out_device_sysfs_path != NULL)
*out_device_sysfs_path = g_steal_pointer (&device_sysfs_path); *out_device_sysfs_path = g_steal_pointer (&device_sysfs_path);
if (out_device_bus != NULL)
*out_device_bus = g_steal_pointer (&device_bus);
return TRUE; return TRUE;
} }
gboolean gboolean
fu_mm_utils_get_port_info (const gchar *path, fu_mm_utils_get_port_info (const gchar *path,
gchar **out_device_bus,
gchar **out_device_sysfs_path, gchar **out_device_sysfs_path,
gint *out_port_ifnum, gint *out_port_usb_ifnum,
GError **error) GError **error)
{ {
g_autoptr(GUdevClient) client = NULL; g_autoptr(GUdevClient) client = NULL;
@ -80,5 +150,5 @@ fu_mm_utils_get_port_info (const gchar *path,
return FALSE; return FALSE;
} }
return fu_mm_utils_get_udev_port_info (dev, out_device_sysfs_path, out_port_ifnum, error); return fu_mm_utils_get_udev_port_info (dev, out_device_bus, out_device_sysfs_path, out_port_usb_ifnum, error);
} }

View File

@ -11,12 +11,14 @@
#include <gudev/gudev.h> #include <gudev/gudev.h>
gboolean fu_mm_utils_get_udev_port_info (GUdevDevice *dev, gboolean fu_mm_utils_get_udev_port_info (GUdevDevice *dev,
gchar **device_bus,
gchar **device_sysfs_path, gchar **device_sysfs_path,
gint *port_ifnum, gint *port_usb_ifnum,
GError **error); GError **error);
gboolean fu_mm_utils_get_port_info (const gchar *path, gboolean fu_mm_utils_get_port_info (const gchar *path,
gchar **device_bus,
gchar **device_sysfs_path, gchar **device_sysfs_path,
gint *port_ifnum, gint *port_usb_ifnum,
GError **error); GError **error);
#endif /* __FU_MM_UTILS_H */ #endif /* __FU_MM_UTILS_H */

View File

@ -149,23 +149,24 @@ fu_plugin_mm_udev_uevent_cb (GUdevClient *udev,
const gchar *name = g_udev_device_get_name (device); const gchar *name = g_udev_device_get_name (device);
g_autofree gchar *path = NULL; g_autofree gchar *path = NULL;
g_autofree gchar *device_sysfs_path = NULL; g_autofree gchar *device_sysfs_path = NULL;
g_autofree gchar *device_bus = NULL;
gint ifnum = -1; gint ifnum = -1;
if (action == NULL || subsystem == NULL || priv->inhibited == NULL || name == NULL) if (action == NULL || subsystem == NULL || priv->inhibited == NULL || name == NULL)
return TRUE; return TRUE;
/* ignore if loading port info fails */ /* ignore if loading port info fails */
if (!fu_mm_utils_get_udev_port_info (device, &device_sysfs_path, &ifnum, NULL)) if (!fu_mm_utils_get_udev_port_info (device, &device_bus, &device_sysfs_path, &ifnum, NULL))
return TRUE;
/* ignore non-USB and non-PCI events */
if (g_strcmp0 (device_bus, "USB") != 0 && g_strcmp0 (device_bus, "PCI") != 0)
return TRUE; return TRUE;
/* ignore all events for ports not owned by our device */ /* ignore all events for ports not owned by our device */
if (g_strcmp0 (device_sysfs_path, priv->inhibited->physical_id) != 0) if (g_strcmp0 (device_sysfs_path, priv->inhibited->physical_id) != 0)
return TRUE; return TRUE;
/* ignore non-cdc-wdm usbmisc ports */
if (g_str_equal (subsystem, "usbmisc") && !g_str_has_prefix (name, "cdc-wdm"))
return TRUE;
path = g_strdup_printf ("/dev/%s", name); path = g_strdup_printf ("/dev/%s", name);
if ((g_str_equal (action, "add")) || (g_str_equal (action, "change"))) { if ((g_str_equal (action, "add")) || (g_str_equal (action, "change"))) {