udev: new ID_MM_REQUIRED tag

Users with QMI or MBIM capable modems may want to ensure that these
are never managed using plain AT commands, as that also involves using
PPP. This fallback to AT could happen if the QMI or MBIM port probing
fails for whatever reason.

The new `ID_MM_REQUIRED` udev tag allows specifying that a given port
MUST be successfully grabbed when creating a new modem object, or
otherwise the modem object will not be created at all (even if there
are other fallback control ports like AT that could have been used).

Use this tag with caution.

It is assumed that when this tag is used some other external process
may be monitoring the existence of the modem object in DBus as exposed
by ModemManager, and if it does not appear for any reason then the
modem would be reseted with some other mechanism (e.g. GPIOs, if
available). If no such mechanism to autorecover the modem is in place,
using this tag may leave the modem exposed in the kernel but ignored
by ModemManager.

This tag must be applied on the specific port for which the existence
and usability must be ensured.

E.g. flagging the MBIM port of the Fibocom L850 module as required:

   $ vim /lib/udev/rules.d/78-mm-test.rules
   ACTION!="add|change|move|bind", GOTO="mm_test_rules_end"
   SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
   ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0007", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_REQUIRED}="1"
   LABEL="mm_test_rules_end"

   $ sudo udevadm control --reload
   $ sudo udevadm trigger

   $ sudo udevadm info -p /sys/class/usbmisc/cdc-wdm0
   ...
   E: ID_MM_REQUIRED=1
   E: ID_MM_CANDIDATE=1
This commit is contained in:
Aleksander Morgado
2023-03-27 13:42:33 +00:00
parent 79b15cf87c
commit 7e0663f922
3 changed files with 41 additions and 1 deletions

View File

@@ -252,6 +252,24 @@
*/
#define ID_MM_TTY_FLOW_CONTROL "ID_MM_TTY_FLOW_CONTROL"
/**
* ID_MM_REQUIRED:
*
* This is a port-specific tag that allows users to specify that the modem
* must be able to successfully probe and use the given control port.
*
* If this tag is set and the port probing procedure fails, the modem object
* will not be created, which is the same as if the port didn't exist in the
* first place.
*
* E.g. this tag may be set on a QMI control port if we want to make sure the
* modem object exposed by ModemManager is QMI-capable and never an AT-based
* modem created due to falling back on a failed QMI port probing procedure.
*
* Since: 1.22
*/
#define ID_MM_REQUIRED "ID_MM_REQUIRED"
/*
* The following symbols are deprecated. We don't add them to -compat
* because this -tags file is not really part of the installed API.

View File

@@ -470,8 +470,21 @@ mm_base_modem_grab_port (MMBaseModem *self,
MMPortSerialAtFlag at_pflags,
GError **error)
{
if (!base_modem_internal_grab_port (self, kernel_device, FALSE, ptype, at_pflags, error))
g_autoptr(GError) inner_error = NULL;
if (!base_modem_internal_grab_port (self, kernel_device, FALSE, ptype, at_pflags, &inner_error)) {
/* If the port was REQUIRED via udev tags and we failed to grab it, we will report
* a fatal error. */
if (mm_kernel_device_get_property_as_boolean (kernel_device, ID_MM_REQUIRED)) {
mm_obj_err (self, "required port '%s/%s' failed to be grabbed",
mm_kernel_device_get_subsystem (kernel_device),
mm_kernel_device_get_name (kernel_device));
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
"Required port failed to be grabbed");
} else
g_propagate_error (error, g_steal_pointer (&inner_error));
return FALSE;
}
mm_obj_dbg (self, "port '%s/%s' grabbed",
mm_kernel_device_get_subsystem (kernel_device),

View File

@@ -1084,6 +1084,15 @@ mm_plugin_create_modem (MMPlugin *self,
next:
if (!grabbed) {
mm_obj_warn (self, "could not grab port %s: %s", name, inner_error ? inner_error->message : "unknown error");
/* An ABORTED error is emitted exclusively when the port grabbing operation
* detects that a REQUIRED port is unusable. */
if (g_error_matches (inner_error, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED)) {
g_propagate_error (error, inner_error);
g_clear_object (&modem);
return NULL;
}
g_clear_error (&inner_error);
}
}