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 <thevancedgamer@mentallysanemainliners.org>
This commit is contained in:
Muhammad Asif 2025-12-22 02:17:25 +05:00 committed by Muhammad
parent 9c6cf71f4f
commit 3b264e830e
2 changed files with 213 additions and 3 deletions

View file

@ -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, &current_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);

View file

@ -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"