From 62d03cf33e76d18362b7c15ccde7bc1b21beafac Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 1 Jul 2010 10:32:11 -0700 Subject: [PATCH] core: better missing firmware handling when firmware appears (rh #609587) Monitor the kernel firmware directory (set at configure-time with --with-kernel-firmware-dir=) for changes, and if there are any, try bringing up devices that are missing firmware. --- configure.ac | 10 +++++ src/nm-device-private.h | 4 ++ src/nm-device-wifi.c | 13 ++++-- src/nm-device.c | 34 ++++++++++----- src/nm-manager.c | 94 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 14 deletions(-) diff --git a/configure.ac b/configure.ac index 1c54f6088f..b5147c5991 100644 --- a/configure.ac +++ b/configure.ac @@ -423,6 +423,16 @@ fi AC_DEFINE_UNQUOTED(SYSTEM_CA_PATH, "$SYSTEM_CA_PATH", [Define to path to system CA certificates]) AC_SUBST(SYSTEM_CA_PATH) +AC_ARG_WITH(kernel-firmware-dir, AS_HELP_STRING([--with-kernel-firmware-dir=DIR], [where kernel firmware directory is (default is /lib/firmware)])) + +if test -n "$with_kernel_firmware_dir" ; then + KERNEL_FIRMWARE_DIR="$with_kernel_firmware_dir" +else + KERNEL_FIRMWARE_DIR="/lib/firmware" +fi +AC_DEFINE_UNQUOTED(KERNEL_FIRMWARE_DIR, "$KERNEL_FIRMWARE_DIR", [Define to path of the kernel firmware directory]) +AC_SUBST(KERNEL_FIRMWARE_DIR) + NM_COMPILER_WARNINGS GTK_DOC_CHECK(1.0) diff --git a/src/nm-device-private.h b/src/nm-device-private.h index 371f17f10d..f4f968a949 100644 --- a/src/nm-device-private.h +++ b/src/nm-device-private.h @@ -42,4 +42,8 @@ void nm_device_handle_autoip4_event (NMDevice *self, gboolean nm_device_ip_config_should_fail (NMDevice *self, gboolean ip6); +gboolean nm_device_get_firmware_missing (NMDevice *self); + +void nm_device_set_firmware_missing (NMDevice *self, gboolean missing); + #endif /* NM_DEVICE_PRIVATE_H */ diff --git a/src/nm-device-wifi.c b/src/nm-device-wifi.c index aecb035255..dc76dc29db 100644 --- a/src/nm-device-wifi.c +++ b/src/nm-device-wifi.c @@ -2609,7 +2609,8 @@ supplicant_mgr_state_cb_handler (gpointer user_data) dev_state = nm_device_get_state (dev); if ( priv->enabled && !priv->supplicant.iface - && (dev_state >= NM_DEVICE_STATE_UNAVAILABLE)) { + && (dev_state >= NM_DEVICE_STATE_UNAVAILABLE) + && (nm_device_get_firmware_missing (NM_DEVICE (self)) == FALSE)) { /* request a supplicant interface from the supplicant manager */ supplicant_interface_acquire (self); @@ -3599,7 +3600,7 @@ device_state_changed (NMDevice *device, * acquire a supplicant interface and transition to DISCONNECTED because * the device is now ready to use. */ - if (priv->enabled) { + if (priv->enabled && (nm_device_get_firmware_missing (device) == FALSE)) { gboolean success; struct iw_range range; @@ -3692,8 +3693,12 @@ real_set_enabled (NMDeviceInterface *device, gboolean enabled) nm_log_dbg (LOGD_WIFI, "(%s): enable blocked by failure to bring device up", nm_device_get_iface (NM_DEVICE (device))); - /* The device sucks, or HAL was lying to us about the killswitch state */ - priv->enabled = FALSE; + if (no_firmware) + nm_device_set_firmware_missing (NM_DEVICE (device), TRUE); + else { + /* The device sucks, or the kernel was lying to us about the killswitch state */ + priv->enabled = FALSE; + } return; } diff --git a/src/nm-device.c b/src/nm-device.c index df14a47a4b..84eb97730a 100644 --- a/src/nm-device.c +++ b/src/nm-device.c @@ -3655,17 +3655,27 @@ unavailable_to_disconnected (gpointer user_data) return FALSE; } -static void -set_firmware_missing (NMDevice *self, gboolean new_missing) +void +nm_device_set_firmware_missing (NMDevice *self, gboolean new_missing) { - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMDevicePrivate *priv; + g_return_if_fail (self != NULL); + g_return_if_fail (NM_IS_DEVICE (self)); + + priv = NM_DEVICE_GET_PRIVATE (self); if (priv->firmware_missing != new_missing) { priv->firmware_missing = new_missing; g_object_notify (G_OBJECT (self), NM_DEVICE_INTERFACE_FIRMWARE_MISSING); } } +gboolean +nm_device_get_firmware_missing (NMDevice *self) +{ + return NM_DEVICE_GET_PRIVATE (self)->firmware_missing; +} + void nm_device_state_changed (NMDevice *device, NMDeviceState state, @@ -3678,7 +3688,12 @@ nm_device_state_changed (NMDevice *device, g_return_if_fail (NM_IS_DEVICE (device)); - if (priv->state == state) + /* Do nothing if state isn't changing, but as a special case allow + * re-setting UNAVAILABLE if the device is missing firmware so that we + * can retry device initialization. + */ + if ( (priv->state == state) + && !(state == NM_DEVICE_STATE_UNAVAILABLE && priv->firmware_missing)) return; old_state = priv->state; @@ -3698,16 +3713,15 @@ nm_device_state_changed (NMDevice *device, */ switch (state) { case NM_DEVICE_STATE_UNMANAGED: - set_firmware_missing (device, FALSE); + nm_device_set_firmware_missing (device, FALSE); if (old_state > NM_DEVICE_STATE_UNMANAGED) nm_device_take_down (device, TRUE, reason); break; case NM_DEVICE_STATE_UNAVAILABLE: - if (old_state == NM_DEVICE_STATE_UNMANAGED) { - if (!nm_device_bring_up (device, TRUE, &no_firmware) && no_firmware) { - nm_log_warn (LOGD_HW, "%s: firmware may be missing.", nm_device_get_iface (device)); - set_firmware_missing (device, TRUE); - } + if (old_state == NM_DEVICE_STATE_UNMANAGED || priv->firmware_missing) { + if (!nm_device_bring_up (device, TRUE, &no_firmware) && no_firmware) + nm_log_warn (LOGD_HW, "(%s): firmware may be missing.", nm_device_get_iface (device)); + nm_device_set_firmware_missing (device, no_firmware ? TRUE : FALSE); } /* Ensure the device gets deactivated in response to stuff like * carrier changes or rfkill. But don't deactivate devices that are diff --git a/src/nm-manager.c b/src/nm-manager.c index 1e94f01285..b52c6ce17e 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -221,6 +221,11 @@ typedef struct { guint auth_changed_id; GSList *auth_chains; + /* Firmware dir monitor */ + GFileMonitor *fw_monitor; + guint fw_monitor_id; + guint fw_changed_id; + gboolean disposed; } NMManagerPrivate; @@ -3884,6 +3889,65 @@ nm_manager_start (NMManager *self) bluez_manager_resync_devices (self); } +static gboolean +handle_firmware_changed (gpointer user_data) +{ + NMManager *self = NM_MANAGER (user_data); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); + GSList *iter; + + priv->fw_changed_id = 0; + + if (manager_sleeping (self)) + return FALSE; + + /* Try to re-enable devices with missing firmware */ + for (iter = priv->devices; iter; iter = iter->next) { + NMDevice *candidate = NM_DEVICE (iter->data); + NMDeviceState state = nm_device_get_state (candidate); + + if ( nm_device_get_firmware_missing (candidate) + && (state == NM_DEVICE_STATE_UNAVAILABLE)) { + nm_log_info (LOGD_CORE, "(%s): firmware may now be available", + nm_device_get_iface (candidate)); + + /* Re-set unavailable state to try bringing the device up again */ + nm_device_state_changed (candidate, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NONE); + } + } + + return FALSE; +} + +static void +firmware_dir_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + NMManager *self = NM_MANAGER (user_data); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); + + switch (event_type) { + case G_FILE_MONITOR_EVENT_CREATED: + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_MOVED: + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + if (!priv->fw_changed_id) { + priv->fw_changed_id = g_timeout_add_seconds (4, handle_firmware_changed, self); + nm_log_info (LOGD_CORE, "kernel firmware directory '%s' changed", + KERNEL_FIRMWARE_DIR); + } + break; + default: + break; + } +} + NMManager * nm_manager_get (const char *config_file, const char *plugins, @@ -4039,6 +4103,17 @@ dispose (GObject *object) if (priv->bluez_mgr) g_object_unref (priv->bluez_mgr); + if (priv->fw_monitor) { + if (priv->fw_monitor_id) + g_signal_handler_disconnect (priv->fw_monitor, priv->fw_monitor_id); + + if (priv->fw_changed_id) + g_source_remove (priv->fw_changed_id); + + g_file_monitor_cancel (priv->fw_monitor); + g_object_unref (priv->fw_monitor); + } + G_OBJECT_CLASS (nm_manager_parent_class)->dispose (object); } @@ -4118,6 +4193,7 @@ nm_manager_init (NMManager *manager) NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager); DBusGConnection *g_connection; guint id, i; + GFile *file; /* Initialize rfkill structures and states */ memset (priv->radio_states, 0, sizeof (priv->radio_states)); @@ -4208,6 +4284,24 @@ nm_manager_init (NMManager *manager) manager); } else nm_log_warn (LOGD_CORE, "failed to create PolicyKit authority."); + + /* Monitor the firmware directory */ + if (strlen (KERNEL_FIRMWARE_DIR)) { + file = g_file_new_for_path (KERNEL_FIRMWARE_DIR "/"); + priv->fw_monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref (file); + } + + if (priv->fw_monitor) { + priv->fw_monitor_id = g_signal_connect (priv->fw_monitor, "changed", + G_CALLBACK (firmware_dir_changed), + manager); + nm_log_info (LOGD_CORE, "monitoring kernel firmware directory '%s'.", + KERNEL_FIRMWARE_DIR); + } else { + nm_log_warn (LOGD_CORE, "failed to monitor kernel firmware directory '%s'.", + KERNEL_FIRMWARE_DIR); + } } static void