Files
ModemManager/src/mm-iface-modem-firmware.c
T
Dan Williams ef5cee3ab5 auth-provider: move auth provider logic into each class
Rather than make all of them rely on MMBaseModem for it. This
lets us disentangle dependencies for easier unit testing.

For interfaces, rather than casting directly to MMBaseModem
use intermediate interfaces (MMIfaceAuth and MMIfaceOpLock)
that tests can fake out.

Signed-off-by: Dan Williams <dan@ioncontrol.co>
2025-05-08 20:08:06 -05:00

663 lines
23 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2012 Google, Inc.
*/
#include <ModemManager.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-iface-modem.h"
#include "mm-iface-modem-firmware.h"
#include "mm-error-helpers.h"
#include "mm-log-object.h"
#if defined WITH_QMI
# include "mm-broadband-modem-qmi.h"
#endif
#if defined WITH_MBIM
# include "mm-broadband-modem-mbim.h"
#endif
G_DEFINE_INTERFACE (MMIfaceModemFirmware, mm_iface_modem_firmware, MM_TYPE_IFACE_MODEM)
/*****************************************************************************/
void
mm_iface_modem_firmware_bind_simple_status (MMIfaceModemFirmware *self,
MMSimpleStatus *status)
{
}
/*****************************************************************************/
/* Load update settings in port (Firmware interface) */
MMFirmwareUpdateSettings *
mm_iface_modem_firmware_load_update_settings_in_port_finish (MMIfaceModemFirmware *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static gboolean
modem_is_sahara_supported (MMKernelDevice *kernel_device)
{
return mm_kernel_device_get_global_property_as_boolean (kernel_device, "ID_MM_QUALCOMM_SAHARA");
}
static gboolean
modem_is_firehose_supported (MMKernelDevice *kernel_device)
{
return mm_kernel_device_get_global_property_as_boolean (kernel_device, "ID_MM_QUALCOMM_FIREHOSE");
}
void
mm_iface_modem_firmware_load_update_settings_in_port (MMIfaceModemFirmware *self,
MMPort *port,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
MMModemFirmwareUpdateMethod update_methods;
g_autoptr(MMFirmwareUpdateSettings) update_settings = NULL;
task = g_task_new (self, NULL, callback, user_data);
update_methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE;
if (MM_IS_PORT (port)) {
MMKernelDevice *kernel_device;
kernel_device = mm_port_peek_kernel_device (MM_PORT (port));
if (modem_is_firehose_supported (kernel_device))
update_methods |= MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE;
if (modem_is_sahara_supported (kernel_device))
update_methods |= MM_MODEM_FIRMWARE_UPDATE_METHOD_SAHARA;
}
update_settings = mm_firmware_update_settings_new (update_methods);
g_task_return_pointer (task, g_object_ref (update_settings), (GDestroyNotify)g_object_unref);
g_object_unref (task);
}
/*****************************************************************************/
/* Handle the 'List' method from DBus */
typedef struct {
MMIfaceModemFirmware *self;
MmGdbusModemFirmware *skeleton;
GDBusMethodInvocation *invocation;
GList *list;
MMFirmwareProperties *current;
} HandleListContext;
static void
handle_list_context_free (HandleListContext *ctx)
{
if (ctx->list)
g_list_free_full (ctx->list, g_object_unref);
if (ctx->current)
g_object_unref (ctx->current);
g_object_unref (ctx->skeleton);
g_object_unref (ctx->invocation);
g_object_unref (ctx->self);
g_slice_free (HandleListContext, ctx);
}
static void
load_current_ready (MMIfaceModemFirmware *self,
GAsyncResult *res,
HandleListContext *ctx)
{
GVariantBuilder builder;
GList *l;
GError *error = NULL;
ctx->current = MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->load_current_finish (self, res, &error);
if (!ctx->current) {
/* Not found isn't fatal */
if (!g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND)) {
mm_dbus_method_invocation_take_error (ctx->invocation, error);
handle_list_context_free (ctx);
return;
}
mm_obj_dbg (self, "couldn't load current firmware image: %s", error->message);
g_clear_error (&error);
}
/* Build array of dicts */
g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
for (l = ctx->list; l; l = g_list_next (l)) {
GVariant *dict;
dict = mm_firmware_properties_get_dictionary (MM_FIRMWARE_PROPERTIES (l->data));
g_variant_builder_add_value (&builder, dict);
g_variant_unref (dict);
}
mm_gdbus_modem_firmware_complete_list (
ctx->skeleton,
ctx->invocation,
(ctx->current ? mm_firmware_properties_get_unique_id (ctx->current) : ""),
g_variant_builder_end (&builder));
handle_list_context_free (ctx);
}
static void
load_list_ready (MMIfaceModemFirmware *self,
GAsyncResult *res,
HandleListContext *ctx)
{
GError *error = NULL;
ctx->list = MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->load_list_finish (self, res, &error);
if (!ctx->list) {
/* Not found isn't fatal */
if (!g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND)) {
mm_dbus_method_invocation_take_error (ctx->invocation, error);
handle_list_context_free (ctx);
return;
}
mm_obj_dbg (self, "couldn't load firmware image list: %s", error->message);
g_clear_error (&error);
}
MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->load_current (
MM_IFACE_MODEM_FIRMWARE (self),
(GAsyncReadyCallback)load_current_ready,
ctx);
}
static void
list_auth_ready (MMIfaceAuth *_self,
GAsyncResult *res,
HandleListContext *ctx)
{
MMIfaceModemFirmware *self = MM_IFACE_MODEM_FIRMWARE (_self);
GError *error = NULL;
if (!mm_iface_auth_authorize_finish (_self, res, &error)) {
mm_dbus_method_invocation_take_error (ctx->invocation, error);
handle_list_context_free (ctx);
return;
}
if (!MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->load_list ||
!MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->load_list_finish ||
!MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->load_current ||
!MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->load_current_finish) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
"Cannot list firmware: operation not supported");
handle_list_context_free (ctx);
return;
}
mm_obj_info (self, "processing user request to load firmware list...");
MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->load_list (
MM_IFACE_MODEM_FIRMWARE (self),
(GAsyncReadyCallback)load_list_ready,
ctx);
}
static gboolean
handle_list (MmGdbusModemFirmware *skeleton,
GDBusMethodInvocation *invocation,
MMIfaceModemFirmware *self)
{
HandleListContext *ctx;
ctx = g_slice_new0 (HandleListContext);
ctx->skeleton = g_object_ref (skeleton);
ctx->invocation = g_object_ref (invocation);
ctx->self = g_object_ref (self);
mm_iface_auth_authorize (MM_IFACE_AUTH (self),
invocation,
MM_AUTHORIZATION_FIRMWARE,
(GAsyncReadyCallback)list_auth_ready,
ctx);
return TRUE;
}
/*****************************************************************************/
/* Handle the 'Select' method from DBus */
typedef struct {
MMIfaceModemFirmware *self;
MmGdbusModemFirmware *skeleton;
GDBusMethodInvocation *invocation;
gchar *name;
} HandleSelectContext;
static void
handle_select_context_free (HandleSelectContext *ctx)
{
g_free (ctx->name);
g_object_unref (ctx->skeleton);
g_object_unref (ctx->invocation);
g_object_unref (ctx->self);
g_slice_free (HandleSelectContext, ctx);
}
static void
change_current_ready (MMIfaceModemFirmware *self,
GAsyncResult *res,
HandleSelectContext *ctx)
{
GError *error = NULL;
if (!MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->change_current_finish (self, res, &error))
mm_dbus_method_invocation_take_error (ctx->invocation, error);
else
mm_gdbus_modem_firmware_complete_select (ctx->skeleton, ctx->invocation);
handle_select_context_free (ctx);
}
static void
select_auth_ready (MMIfaceAuth *_self,
GAsyncResult *res,
HandleSelectContext *ctx)
{
MMIfaceModemFirmware *self = MM_IFACE_MODEM_FIRMWARE (_self);
GError *error = NULL;
if (!mm_iface_auth_authorize_finish (_self, res, &error)) {
mm_dbus_method_invocation_take_error (ctx->invocation, error);
handle_select_context_free (ctx);
return;
}
if (!MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->change_current ||
!MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->change_current_finish) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
"Cannot select firmware: operation not supported");
handle_select_context_free (ctx);
return;
}
mm_obj_info (self, "processing user request to select firmware...");
MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->change_current (
MM_IFACE_MODEM_FIRMWARE (self),
ctx->name,
(GAsyncReadyCallback)change_current_ready,
ctx);
}
static gboolean
handle_select (MmGdbusModemFirmware *skeleton,
GDBusMethodInvocation *invocation,
const gchar *name,
MMIfaceModemFirmware *self)
{
HandleSelectContext *ctx;
ctx = g_slice_new (HandleSelectContext);
ctx->skeleton = g_object_ref (skeleton);
ctx->invocation = g_object_ref (invocation);
ctx->self = g_object_ref (self);
ctx->name = g_strdup (name);
mm_iface_auth_authorize (MM_IFACE_AUTH (self),
invocation,
MM_AUTHORIZATION_FIRMWARE,
(GAsyncReadyCallback)select_auth_ready,
ctx);
return TRUE;
}
/*****************************************************************************/
typedef struct _InitializationContext InitializationContext;
static void interface_initialization_step (GTask *task);
typedef enum {
INITIALIZATION_STEP_FIRST,
INITIALIZATION_STEP_UPDATE_SETTINGS,
INITIALIZATION_STEP_LAST
} InitializationStep;
struct _InitializationContext {
MmGdbusModemFirmware *skeleton;
InitializationStep step;
};
static void
initialization_context_free (InitializationContext *ctx)
{
g_object_unref (ctx->skeleton);
g_free (ctx);
}
gboolean
mm_iface_modem_firmware_initialize_finish (MMIfaceModemFirmware *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static gboolean
add_generic_version (MMBaseModem *self,
MMFirmwareUpdateSettings *update_settings,
GError **error)
{
const gchar *firmware_revision;
const gchar *carrier_revision = NULL;
g_autofree gchar *combined = NULL;
gboolean ignore_carrier = FALSE;
firmware_revision = mm_iface_modem_get_revision (MM_IFACE_MODEM (self));
if (!firmware_revision) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unknown revision");
return FALSE;
}
g_object_get (self,
MM_IFACE_MODEM_FIRMWARE_IGNORE_CARRIER, &ignore_carrier,
NULL);
if (!ignore_carrier)
mm_iface_modem_get_carrier_config (MM_IFACE_MODEM (self), NULL, &carrier_revision);
if (!carrier_revision) {
mm_firmware_update_settings_set_version (update_settings, firmware_revision);
return TRUE;
}
combined = g_strdup_printf ("%s - %s", firmware_revision, carrier_revision);
mm_firmware_update_settings_set_version (update_settings, combined);
return TRUE;
}
GPtrArray *
mm_iface_firmware_build_generic_device_ids (MMIfaceModemFirmware *self,
GError **error)
{
static const gchar *supported_subsystems[] = { "USB", "PCI" };
guint16 vid;
guint16 pid;
guint16 subsystem_vid;
guint16 subsystem_pid;
guint16 rid;
MMPort *primary = NULL;
const gchar *subsystem;
const gchar *carrier_config = NULL;
g_autoptr(GPtrArray) ids = NULL;
guint i;
gboolean ignore_carrier = FALSE;
vid = mm_base_modem_get_vendor_id (MM_BASE_MODEM (self));
pid = mm_base_modem_get_product_id (MM_BASE_MODEM (self));
subsystem_vid = mm_base_modem_get_subsystem_vendor_id (MM_BASE_MODEM (self));
subsystem_pid = mm_base_modem_get_subsystem_device_id (MM_BASE_MODEM (self));
#if defined WITH_QMI
if (MM_IS_BROADBAND_MODEM_QMI (self))
primary = MM_PORT (mm_broadband_modem_qmi_peek_port_qmi (MM_BROADBAND_MODEM_QMI (self)));
#endif
#if defined WITH_MBIM
if (!primary && MM_IS_BROADBAND_MODEM_MBIM (self))
primary = MM_PORT (mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self)));
#endif
if (!primary)
primary = MM_PORT (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)));
if (!primary) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"No valid primary port");
return NULL;
}
rid = mm_kernel_device_get_physdev_revision (mm_port_peek_kernel_device (primary));
subsystem = mm_kernel_device_get_physdev_subsystem (mm_port_peek_kernel_device (primary));
if (!subsystem) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Unknown device subsystem");
return NULL;
}
for (i = 0; i < G_N_ELEMENTS (supported_subsystems); i++) {
if (g_ascii_strcasecmp (supported_subsystems[i], subsystem) == 0)
break;
}
if (i == G_N_ELEMENTS (supported_subsystems)) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Unsupported subsystem: %s", subsystem);
return NULL;
}
g_object_get (self,
MM_IFACE_MODEM_FIRMWARE_IGNORE_CARRIER, &ignore_carrier,
NULL);
if (!ignore_carrier)
mm_iface_modem_get_carrier_config (MM_IFACE_MODEM (self), &carrier_config, NULL);
ids = g_ptr_array_new_with_free_func (g_free);
if (carrier_config) {
g_autofree gchar *carrier = NULL;
carrier = g_ascii_strup (carrier_config, -1);
/* Expect non-zero values for PCI devices only */
if (subsystem_vid && subsystem_pid)
g_ptr_array_add (ids, g_strdup_printf ("%s\\SSVID_%04X&SSPID_%04X&REV_%04X&CARRIER_%s",
supported_subsystems[i], subsystem_vid,
subsystem_pid, rid, carrier));
g_ptr_array_add (ids, g_strdup_printf ("%s\\VID_%04X&PID_%04X&REV_%04X&CARRIER_%s",
supported_subsystems[i], vid, pid, rid, carrier));
}
g_ptr_array_add (ids, g_strdup_printf ("%s\\VID_%04X&PID_%04X&REV_%04X",
supported_subsystems[i], vid, pid, rid));
g_ptr_array_add (ids, g_strdup_printf ("%s\\VID_%04X&PID_%04X",
supported_subsystems[i], vid, pid));
g_ptr_array_add (ids, g_strdup_printf ("%s\\VID_%04X",
supported_subsystems[i], vid));
g_ptr_array_add (ids, NULL);
return g_steal_pointer (&ids);
}
static void
load_update_settings_ready (MMIfaceModemFirmware *self,
GAsyncResult *res,
GTask *task)
{
InitializationContext *ctx;
MMFirmwareUpdateSettings *update_settings;
GError *error = NULL;
GVariant *variant = NULL;
g_autoptr(GPtrArray) ids = NULL;
ctx = g_task_get_task_data (task);
update_settings = MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->load_update_settings_finish (self, res, &error);
if (!update_settings) {
mm_obj_dbg (self, "couldn't load update settings: %s", error->message);
g_error_free (error);
goto out;
}
/* If the plugin didn't specify custom device ids, add the default ones ourselves */
if (!mm_firmware_update_settings_get_device_ids (update_settings)) {
mm_obj_dbg (self, "No device ids set by plugin, adding generic ids");
ids = mm_iface_firmware_build_generic_device_ids (self, &error);
if (error) {
mm_obj_warn (self, "couldn't build device ids: %s", error->message);
g_error_free (error);
g_clear_object (&update_settings);
goto out;
}
mm_firmware_update_settings_set_device_ids (update_settings, (const gchar **)ids->pdata);
}
/* If the plugin didn't specify custom version, add the default one ourselves */
if (!mm_firmware_update_settings_get_version (update_settings) &&
!add_generic_version (MM_BASE_MODEM (self), update_settings, &error)) {
mm_obj_warn (self, "couldn't set version: %s", error->message);
g_error_free (error);
g_clear_object (&update_settings);
goto out;
}
out:
if (update_settings) {
variant = mm_firmware_update_settings_get_variant (update_settings);
g_object_unref (update_settings);
}
mm_gdbus_modem_firmware_set_update_settings (ctx->skeleton, variant);
if (variant)
g_variant_unref (variant);
/* Go on to next step */
ctx->step++;
interface_initialization_step (task);
}
static void
interface_initialization_step (GTask *task)
{
MMIfaceModemFirmware *self;
InitializationContext *ctx;
/* Don't run new steps if we're cancelled */
if (g_task_return_error_if_cancelled (task)) {
g_object_unref (task);
return;
}
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case INITIALIZATION_STEP_FIRST:
ctx->step++;
/* fall through */
case INITIALIZATION_STEP_UPDATE_SETTINGS:
if (MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->load_update_settings &&
MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->load_update_settings_finish) {
MM_IFACE_MODEM_FIRMWARE_GET_IFACE (self)->load_update_settings (
self,
(GAsyncReadyCallback)load_update_settings_ready,
task);
return;
}
ctx->step++;
/* fall through */
case INITIALIZATION_STEP_LAST:
/* We are done without errors! */
/* Handle method invocations */
g_object_connect (ctx->skeleton,
"signal::handle-list", G_CALLBACK (handle_list), self,
"signal::handle-select", G_CALLBACK (handle_select), self,
NULL);
/* Finally, export the new interface */
mm_gdbus_object_skeleton_set_modem_firmware (MM_GDBUS_OBJECT_SKELETON (self),
MM_GDBUS_MODEM_FIRMWARE (ctx->skeleton));
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
default:
break;
}
g_assert_not_reached ();
}
void
mm_iface_modem_firmware_initialize (MMIfaceModemFirmware *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
InitializationContext *ctx;
MmGdbusModemFirmware *skeleton = NULL;
GTask *task;
/* Did we already create it? */
g_object_get (self,
MM_IFACE_MODEM_FIRMWARE_DBUS_SKELETON, &skeleton,
NULL);
if (!skeleton) {
skeleton = mm_gdbus_modem_firmware_skeleton_new ();
g_object_set (self,
MM_IFACE_MODEM_FIRMWARE_DBUS_SKELETON, skeleton,
NULL);
}
/* Perform async initialization here */
ctx = g_new0 (InitializationContext, 1);
ctx->step = INITIALIZATION_STEP_FIRST;
ctx->skeleton = skeleton;
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify)initialization_context_free);
interface_initialization_step (task);
}
void
mm_iface_modem_firmware_shutdown (MMIfaceModemFirmware *self)
{
g_return_if_fail (MM_IS_IFACE_MODEM_FIRMWARE (self));
/* Unexport DBus interface and remove the skeleton */
mm_gdbus_object_skeleton_set_modem_firmware (MM_GDBUS_OBJECT_SKELETON (self), NULL);
g_object_set (self,
MM_IFACE_MODEM_FIRMWARE_DBUS_SKELETON, NULL,
NULL);
}
/*****************************************************************************/
static void
mm_iface_modem_firmware_default_init (MMIfaceModemFirmwareInterface *iface)
{
static gsize initialized = 0;
if (!g_once_init_enter (&initialized))
return;
/* Properties */
g_object_interface_install_property (
iface,
g_param_spec_object (MM_IFACE_MODEM_FIRMWARE_DBUS_SKELETON,
"Firmware DBus skeleton",
"DBus skeleton for the Firmware interface",
MM_GDBUS_TYPE_MODEM_FIRMWARE_SKELETON,
G_PARAM_READWRITE));
g_object_interface_install_property (
iface,
g_param_spec_boolean (MM_IFACE_MODEM_FIRMWARE_IGNORE_CARRIER,
"Ignore carrier info in firmware details",
"Whether carrier info (version, name) should be ignored when showing the firmware details",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_once_init_leave (&initialized, 1);
}