diff mbox series

udevng: Detect embedded qmi qrtr modems

Message ID 20240402212013.39038-1-steve.schrock@getcruise.com (mailing list archive)
State Superseded
Headers show
Series udevng: Detect embedded qmi qrtr modems | expand

Commit Message

Steve Schrock April 2, 2024, 9:20 p.m. UTC
Embedded qmi qrtr modems are identified by the existence of
rmnet_ipaX and rmnet_dataX devices. Add a new "embedded" modem type
so that these devices can be collected during enumeration and then
configured for use by the gobi plugin. Modems of this type will be
exposed as /gobiqrtr_X.

Retrieving the endpoint ID is the main complication. It requires
using an interface IOCTL and extended structure that is declared
in msm_rmnet.h. This header is available many different places on
the internet including in Android sources, but it is not available
in popular Linux distributions. The minimum set of declarations is
provided here so that the IOCTL may be used.
---
 plugins/gobi.c   |  19 ++++--
 plugins/udevng.c | 163 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 178 insertions(+), 4 deletions(-)

Comments

Marcel Holtmann April 2, 2024, 9:59 p.m. UTC | #1
Hi Steve,

> Embedded qmi qrtr modems are identified by the existence of
> rmnet_ipaX and rmnet_dataX devices. Add a new "embedded" modem type
> so that these devices can be collected during enumeration and then
> configured for use by the gobi plugin. Modems of this type will be
> exposed as /gobiqrtr_X.
> 
> Retrieving the endpoint ID is the main complication. It requires
> using an interface IOCTL and extended structure that is declared
> in msm_rmnet.h. This header is available many different places on
> the internet including in Android sources, but it is not available
> in popular Linux distributions. The minimum set of declarations is
> provided here so that the IOCTL may be used.

can’t this be integrated into the WWAN subsystem? And then we just
add support for WWAN subsystem to oFono.

Regards

Marcel
Denis Kenzior April 2, 2024, 10:21 p.m. UTC | #2
Hi Marcel,

On 4/2/24 16:59, Marcel Holtmann wrote:
> Hi Steve,
> 
>> Embedded qmi qrtr modems are identified by the existence of
>> rmnet_ipaX and rmnet_dataX devices. Add a new "embedded" modem type
>> so that these devices can be collected during enumeration and then
>> configured for use by the gobi plugin. Modems of this type will be
>> exposed as /gobiqrtr_X.
>>
>> Retrieving the endpoint ID is the main complication. It requires
>> using an interface IOCTL and extended structure that is declared
>> in msm_rmnet.h. This header is available many different places on
>> the internet including in Android sources, but it is not available
>> in popular Linux distributions. The minimum set of declarations is
>> provided here so that the IOCTL may be used.
> 
> can’t this be integrated into the WWAN subsystem? And then we just
> add support for WWAN subsystem to oFono.

We'll need to support WWAN subsystem eventually, yes.  Unfortunately this is 
coming from Qualcomm proprietary drivers.  Upstream WWAN subsystem does 
something similar, but in a completely different way.  Suggestions are welcome.

Regards,
-Denis
diff mbox series

Patch

diff --git a/plugins/gobi.c b/plugins/gobi.c
index 8d3b7ef4cdfc..d58fb8aed1a8 100644
--- a/plugins/gobi.c
+++ b/plugins/gobi.c
@@ -419,16 +419,27 @@  static void discover_cb(void *user_data)
 static int gobi_enable(struct ofono_modem *modem)
 {
 	struct gobi_data *data = ofono_modem_get_data(modem);
-	const char *device;
+	const char *kernel_driver;
 	int r;
 
 	DBG("%p", modem);
 
-	device = ofono_modem_get_string(modem, "Device");
-	if (!device)
+	kernel_driver = ofono_modem_get_string(modem, "KernelDriver");
+	if (!kernel_driver)
 		return -EINVAL;
 
-	data->device = qmi_device_new_qmux(device);
+	if (!strcmp(kernel_driver, "qrtr"))
+		data->device = qmi_device_new_qrtr();
+	else {
+		const char *device;
+
+		device = ofono_modem_get_string(modem, "Device");
+		if (!device)
+			return -EINVAL;
+
+		data->device = qmi_device_new_qmux(device);
+	}
+
 	if (!data->device)
 		return -EIO;
 
diff --git a/plugins/udevng.c b/plugins/udevng.c
index 8bdcf0dbbccd..53cc0836a928 100644
--- a/plugins/udevng.c
+++ b/plugins/udevng.c
@@ -33,6 +33,9 @@ 
 
 #include <glib.h>
 #include <ell/ell.h>
+#include <linux/if.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
 
 #define OFONO_API_SUBJECT_TO_CHANGE
 #include <ofono/plugin.h>
@@ -43,6 +46,7 @@  enum modem_type {
 	MODEM_TYPE_USB,
 	MODEM_TYPE_SERIAL,
 	MODEM_TYPE_PCIE,
+	MODEM_TYPE_EMBEDDED,
 };
 
 struct modem_info {
@@ -234,6 +238,9 @@  static int setup_qmi(struct modem_info *modem, const struct device_info *qmi,
 	case MODEM_TYPE_PCIE:
 		ofono_modem_set_string(modem->modem, "Bus", "pcie");
 		break;
+	case MODEM_TYPE_EMBEDDED:
+		ofono_modem_set_string(modem->modem, "Bus", "embedded");
+		break;
 	case MODEM_TYPE_SERIAL:
 		break;
 	}
@@ -241,6 +248,139 @@  static int setup_qmi(struct modem_info *modem, const struct device_info *qmi,
 	return 0;
 }
 
+static gboolean setup_gobi_qrtr_premux(struct modem_info *modem,
+					const char *name, int premux_index)
+{
+	const char *rmnet_data_prefix = "rmnet_data";
+	int rmnet_data_prefix_length = strlen(rmnet_data_prefix);
+	char buf[256];
+	int r;
+	uint32_t data_id;
+	uint32_t mux_id;
+
+	r = l_safe_atou32(name + rmnet_data_prefix_length, &data_id);
+	if (r < 0)
+		return FALSE;
+
+	mux_id = data_id + 1;
+
+	DBG("Adding premux interface %s, mux id: %d", name, mux_id);
+	sprintf(buf, "PremuxInterface%d", premux_index);
+	ofono_modem_set_string(modem->modem, buf, name);
+	sprintf(buf, "PremuxInterface%dMuxId", premux_index);
+	ofono_modem_set_integer(modem->modem, buf, mux_id);
+
+	return TRUE;
+}
+
+/*
+ * The following rmnet declarations are contained in msm_rmnet.h which is not
+ * commonly provided in Linux distributions.
+ */
+
+#define RMNET_IOCTL_EXTENDED 0x000089FD
+#define RMNET_IOCTL_GET_EPID 0x0003
+
+/*
+ * This is the minimal set of fields needed to get the endpoint ID. The actual
+ * structure has many more union members.
+ */
+struct rmnet_ioctl_extended_s {
+	uint32_t   extended_ioctl;
+
+	union {
+		uint32_t data;
+
+		/* Ensure the struct is at least as large as the real one. */
+		uint8_t buffer[32];
+	};
+};
+
+static int get_rmnet_endpoint_id(const char *if_name, uint32_t *id)
+{
+	_auto_(close) int fd = -1;
+	int r;
+	struct rmnet_ioctl_extended_s ext_ioctl_arg;
+	struct ifreq ifr;
+
+	fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		ofono_warn("Failed to open socket. %s", strerror(errno));
+		return -errno;
+	}
+
+	memset(&ext_ioctl_arg, 0, sizeof(ext_ioctl_arg));
+	ext_ioctl_arg.extended_ioctl = RMNET_IOCTL_GET_EPID;
+
+	memset(&ifr, 0, sizeof(ifr));
+	l_strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
+	ifr.ifr_ifru.ifru_data = &ext_ioctl_arg;
+
+	r = ioctl(fd, RMNET_IOCTL_EXTENDED, &ifr);
+	if (r == -1) {
+		ofono_warn("Failed to execute RMNET_IOCTL_EXTENDED ioctl. %s",
+						strerror(errno));
+		return -errno;
+	}
+
+	*id = ext_ioctl_arg.data;
+
+	return 0;
+}
+
+static gboolean setup_gobi_qrtr(struct modem_info *modem)
+{
+	const struct device_info *ipa_info = NULL;
+	int premux_count = 0;
+	int r;
+	GSList *list;
+
+	DBG("%s", modem->syspath);
+
+	for (list = modem->devices; list; list = list->next) {
+		struct device_info *info = list->data;
+		const char *name;
+
+		name = udev_device_get_sysname(info->udev_device);
+		if (l_str_has_prefix(name, "rmnet_ipa")) {
+			int endpoint_id;
+
+			if (!get_rmnet_endpoint_id(name, &endpoint_id)) {
+				DBG("%s: endpoint_id: %d", name, endpoint_id);
+				info->number = g_strdup_printf("%d",
+								endpoint_id);
+			}
+
+			ipa_info = info;
+
+		} else if (l_str_has_prefix(name, "rmnet_data")) {
+			int premux_index = premux_count + 1;
+
+			if (setup_gobi_qrtr_premux(modem, name, premux_index))
+				premux_count++;
+		}
+	}
+
+	if (premux_count < 3) {
+		DBG("Not enough rmnet_data interfaces found");
+		return FALSE;
+	}
+
+	ofono_modem_set_integer(modem->modem, "NumPremuxInterfaces",
+							premux_count);
+
+	if (!ipa_info) {
+		DBG("No rmnet_ipa interface found");
+		return FALSE;
+	}
+
+	r = setup_qmi(modem, ipa_info, ipa_info);
+	if (r < 0)
+		return FALSE;
+
+	return TRUE;
+}
+
 static gboolean setup_gobi(struct modem_info *modem)
 {
 	const struct device_info *qmi = NULL;
@@ -1594,6 +1734,7 @@  static struct {
 	{ "wavecom",	setup_wavecom		},
 	{ "tc65",	setup_tc65		},
 	{ "ehs6",	setup_ehs6		},
+	{ "gobiqrtr",	setup_gobi_qrtr		},
 	{ }
 };
 
@@ -2133,6 +2274,26 @@  static void check_pci_device(struct udev_device *device)
 			device, kernel_driver);
 }
 
+static void check_net_device(struct udev_device *device)
+{
+	char path[32];
+	const char *name;
+	const char *iflink;
+
+	name = udev_device_get_sysname(device);
+	if (!l_str_has_prefix(name, "rmnet_"))
+		return;
+
+	iflink = udev_device_get_sysattr_value(device, "iflink");
+	if (!iflink)
+		return;
+
+	/* Collect all rmnet devices with this iflink under a common path. */
+	sprintf(path, "/embedded/qrtr/%s", iflink);
+	add_device(path, NULL, "gobiqrtr", NULL, NULL, MODEM_TYPE_EMBEDDED,
+							device, "qrtr");
+}
+
 static void check_device(struct udev_device *device)
 {
 	const char *bus;
@@ -2149,6 +2310,8 @@  static void check_device(struct udev_device *device)
 		check_usb_device(device);
 	else if (g_str_equal(bus, "pci") == TRUE)
 		check_pci_device(device);
+	else if (g_str_equal(bus, "net") == TRUE)
+		check_net_device(device);
 	else
 		add_serial_device(device);