From 3b264e830eb84bb6b69dc664b2cbf76461e64937 Mon Sep 17 00:00:00 2001 From: Muhammad Asif Date: Mon, 22 Dec 2025 02:17:25 +0500 Subject: [PATCH] wwan: ofono: set modem capabilities when we receive them Implement the `org.ofono.RadioSettings` interface, which sends modem capabilities after modem power on. As oFono only updates capabilities after power on, set the capabilities to ensure NetworkManager receives the update. On devices that don't support the RadioSettings interface, NM will default to no specific capabilities set. Signed-off-by: Muhammad Asif --- src/core/devices/wwan/nm-modem-ofono.c | 215 ++++++++++++++++++++++++- src/core/devices/wwan/nm-modem-ofono.h | 1 + 2 files changed, 213 insertions(+), 3 deletions(-) diff --git a/src/core/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c index 472f5b787f..9700e9800c 100644 --- a/src/core/devices/wwan/nm-modem-ofono.c +++ b/src/core/devices/wwan/nm-modem-ofono.c @@ -16,6 +16,7 @@ #include "nm-modem.h" #include "libnm-platform/nm-platform.h" #include "nm-l3-config-data.h" +#include "nm-device-modem.h" #define VARIANT_IS_OF_TYPE_BOOLEAN(v) \ ((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_BOOLEAN))) @@ -46,11 +47,13 @@ typedef struct { GDBusProxy *modem_proxy; GDBusProxy *connman_proxy; GDBusProxy *sim_proxy; + GDBusProxy *rat_proxy; GCancellable *modem_proxy_cancellable; GCancellable *connman_proxy_cancellable; GCancellable *connect_cancellable; GCancellable *sim_proxy_cancellable; + GCancellable *rat_proxy_cancellable; GError *property_error; @@ -69,6 +72,9 @@ typedef struct { OfonoContextData *current_octx; GSource *deferred_connection_timeout_source; + + gchar **available_technologies; + char *preferred_technology; } NMModemOfonoPrivate; struct _NMModemOfono { @@ -137,9 +143,48 @@ get_capabilities(NMModem *_self, NMDeviceModemCapabilities *modem_caps, NMDeviceModemCapabilities *current_caps) { - /* FIXME: auto-detect capabilities to allow LTE */ - *modem_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS; - *current_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS; + NMModemOfono *self = NM_MODEM_OFONO(_self); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + *modem_caps = NM_DEVICE_MODEM_CAPABILITY_NONE; + *current_caps = NM_DEVICE_MODEM_CAPABILITY_NONE; + + /* Example values: + * AvailableTechnologies: "gsm umts lte" + * TechnologyPreference: "lte" + */ + + if (!priv->available_technologies || !priv->preferred_technology) { + _LOGD("no technology information available"); + return; + } + + for (gint i = 0; priv->available_technologies[i]; i++) { + const char *tech = priv->available_technologies[i]; + + _LOGD("available technology: %s", tech); + + if (nm_streq(tech, "gsm") || nm_streq(tech, "umts")) + *modem_caps |= NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS; + else if (nm_streq(tech, "lte")) + *modem_caps |= NM_DEVICE_MODEM_CAPABILITY_LTE; + } + + if (nm_streq(priv->preferred_technology, "gsm") || nm_streq(priv->preferred_technology, "umts")) + *current_caps |= NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS; + else if (nm_streq(priv->preferred_technology, "lte")) + *current_caps |= NM_DEVICE_MODEM_CAPABILITY_LTE; +} + +static void +set_capabilities_changed(NMModemOfono *self) +{ + NMDeviceModemCapabilities modem_caps = NM_DEVICE_MODEM_CAPABILITY_NONE; + NMDeviceModemCapabilities current_caps = NM_DEVICE_MODEM_CAPABILITY_NONE; + + get_capabilities(NM_MODEM(self), &modem_caps, ¤t_caps); + + nm_modem_set_capabilities(NM_MODEM(self), modem_caps, current_caps); } static void do_context_activate(NMModemOfono *self); @@ -1082,6 +1127,154 @@ handle_connman_iface(NMModemOfono *self, gboolean found) } } +static void +handle_rat_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + NMModemOfono *self = NM_MODEM_OFONO(user_data); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (nm_streq(property, "AvailableTechnologies") && VARIANT_IS_OF_TYPE_STRING_ARRAY(v)) { + gchar **available_technologies = g_variant_dup_strv(v, NULL); + + if (!priv->available_technologies) { + priv->available_technologies = available_technologies; + set_capabilities_changed(self); + } + } else if (nm_streq(property, "TechnologyPreference") && VARIANT_IS_OF_TYPE_STRING(v)) { + const char *technology_preference = g_variant_get_string(v, NULL); + + if (!priv->preferred_technology + || !nm_streq(priv->preferred_technology, technology_preference)) { + g_free(priv->preferred_technology); + priv->preferred_technology = g_strdup(technology_preference); + set_capabilities_changed(self); + } + } +} + +static void +rat_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + GVariant *v_child = g_variant_get_child_value(v, 0); + + handle_rat_property(proxy, property, v_child, user_data); + g_variant_unref(v_child); +} + +static void +rat_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono *self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *v_properties = NULL; + gs_unref_variant GVariant *v_dict = NULL; + gs_unref_variant GVariant *v = NULL; + GVariantIter i; + const char *property; + + v_properties = + _nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error); + if (!v_properties && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + g_clear_object(&priv->rat_proxy_cancellable); + + if (!v_properties) { + g_dbus_error_strip_remote_error(error); + _LOGW("error getting RadioSettings properties: %s", error->message); + return; + } + + _LOGD("rat v_properties is type: %s", g_variant_get_type_string(v_properties)); + + v_dict = g_variant_get_child_value(v_properties, 0); + if (!v_dict) { + _LOGW("error getting RadioSettings properties: no v_dict"); + return; + } + + _LOGD("rat v_dict is type: %s", g_variant_get_type_string(v_dict)); + + g_variant_iter_init(&i, v_dict); + while (g_variant_iter_loop(&i, "{&sv}", &property, &v)) { + handle_rat_property(NULL, property, v, self); + } +} + +static void +_rat_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono *self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy *proxy; + + proxy = g_dbus_proxy_new_for_bus_finish(result, &error); + if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (!proxy) { + _LOGW("failed to create RadioSettings proxy: %s", error->message); + g_clear_object(&priv->rat_proxy_cancellable); + return; + } + + priv->rat_proxy = proxy; + + /* Watch for custom ofono PropertyChanged signals */ + _nm_dbus_proxy_signal_connect(priv->rat_proxy, + "PropertyChanged", + G_VARIANT_TYPE("(sv)"), + G_CALLBACK(rat_property_changed), + self); + + g_dbus_proxy_call(priv->rat_proxy, + "GetProperties", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 20000, + priv->rat_proxy_cancellable, + rat_get_properties_done, + self); +} + +static void +handle_rat_iface(NMModemOfono *self, gboolean found) +{ + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (!found && (priv->rat_proxy || priv->rat_proxy_cancellable)) { + _LOGI("RadioSettings interface disappeared"); + nm_clear_g_cancellable(&priv->rat_proxy_cancellable); + if (priv->rat_proxy) { + g_signal_handlers_disconnect_by_data(priv->rat_proxy, self); + g_clear_object(&priv->rat_proxy); + } + } else if (found && (!priv->rat_proxy && !priv->rat_proxy_cancellable)) { + _LOGI("found new RadioSettings interface"); + + priv->rat_proxy_cancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES + | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, /* GDBusInterfaceInfo */ + OFONO_DBUS_SERVICE, + nm_modem_get_path(NM_MODEM(self)), + OFONO_DBUS_INTERFACE_RADIO_SETTINGS, + priv->rat_proxy_cancellable, + _rat_proxy_new_cb, + self); + } +} + static void handle_modem_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) { @@ -1119,6 +1312,7 @@ handle_modem_property(GDBusProxy *proxy, const char *property, GVariant *v, gpoi const char **array, **iter; gboolean found_connman = FALSE; gboolean found_sim = FALSE; + gboolean found_rat = FALSE; _LOGD("Interfaces found"); @@ -1129,12 +1323,15 @@ handle_modem_property(GDBusProxy *proxy, const char *property, GVariant *v, gpoi found_sim = TRUE; else if (g_strcmp0(OFONO_DBUS_INTERFACE_CONNECTION_MANAGER, *iter) == 0) found_connman = TRUE; + else if (g_strcmp0(OFONO_DBUS_INTERFACE_RADIO_SETTINGS, *iter) == 0) + found_rat = TRUE; } g_free(array); } handle_sim_iface(self, found_sim); handle_connman_iface(self, found_connman); + handle_rat_iface(self, found_rat); } } @@ -1713,6 +1910,7 @@ dispose(GObject *object) nm_clear_g_cancellable(&priv->connman_proxy_cancellable); nm_clear_g_cancellable(&priv->connect_cancellable); nm_clear_g_cancellable(&priv->sim_proxy_cancellable); + nm_clear_g_cancellable(&priv->rat_proxy_cancellable); if (priv->connect_properties) { g_hash_table_destroy(priv->connect_properties); @@ -1746,6 +1944,11 @@ dispose(GObject *object) g_clear_object(&priv->sim_proxy); } + if (priv->rat_proxy) { + g_signal_handlers_disconnect_by_data(priv->rat_proxy, self); + g_clear_object(&priv->rat_proxy); + } + if (priv->settings) { g_signal_handlers_disconnect_by_data(priv->settings, self); g_clear_object(&priv->settings); @@ -1754,6 +1957,12 @@ dispose(GObject *object) g_free(priv->imsi); priv->imsi = NULL; + g_strfreev(priv->available_technologies); + priv->available_technologies = NULL; + + g_free(priv->preferred_technology); + priv->preferred_technology = NULL; + nm_clear_g_source_inst(&priv->deferred_connection_timeout_source); G_OBJECT_CLASS(nm_modem_ofono_parent_class)->dispose(object); diff --git a/src/core/devices/wwan/nm-modem-ofono.h b/src/core/devices/wwan/nm-modem-ofono.h index 02ba49ad81..a40b8d8143 100644 --- a/src/core/devices/wwan/nm-modem-ofono.h +++ b/src/core/devices/wwan/nm-modem-ofono.h @@ -25,6 +25,7 @@ #define OFONO_DBUS_INTERFACE_CONNECTION_MANAGER "org.ofono.ConnectionManager" #define OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT "org.ofono.ConnectionContext" #define OFONO_DBUS_INTERFACE_SIM_MANAGER "org.ofono.SimManager" +#define OFONO_DBUS_INTERFACE_RADIO_SETTINGS "org.ofono.RadioSettings" #define OFONO_ERROR_IN_PROGRESS "org.ofono.Error.InProgress"