NetworkManager/src/nm-hal-manager.c
Dan Williams 73e39bc499 modem: add support for Sony Ericsson F3507g / Dell 5530 (bgo #574014)
These devices also need AT+CGREG, they have problems reporting PIN status
correctly the first time, and are schizophrenic about which init strings
are acceptable.  Since they provide so many serial ports, filter out
ones we don't care about, and ignore the cdc-ether device that we dont
use yet.
2009-03-12 12:16:16 -04:00

1280 lines
34 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager -- Network link manager
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2007 - 2008 Novell, Inc.
* Copyright (C) 2007 - 2008 Red Hat, Inc.
*/
#include "config.h"
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <libhal.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
#if HAVE_LIBUDEV
#define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE
#include <libudev.h>
#endif /* HAVE_LIBUDEV */
#include "nm-glib-compat.h"
#include "nm-hal-manager.h"
#include "nm-marshal.h"
#include "nm-dbus-manager.h"
#include "nm-utils.h"
#include "nm-device-wifi.h"
#include "nm-device-ethernet.h"
#include "nm-gsm-device.h"
#include "nm-hso-gsm-device.h"
#include "nm-cdma-device.h"
/* Killswitch poll frequency in seconds */
#define RFKILL_POLL_FREQUENCY 6
#define HAL_DBUS_SERVICE "org.freedesktop.Hal"
typedef struct {
LibHalContext *hal_ctx;
NMDBusManager *dbus_mgr;
GSList *device_creators;
gboolean rfkilled; /* Authoritative rfkill state */
/* Killswitch handling */
GSList *killswitch_list;
guint32 killswitch_poll_id;
char *kswitch_err;
gboolean poll_rfkilled;
guint32 pending_polls;
GSList *poll_proxies;
gboolean disposed;
} NMHalManagerPrivate;
#define NM_HAL_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_HAL_MANAGER, NMHalManagerPrivate))
G_DEFINE_TYPE (NMHalManager, nm_hal_manager, G_TYPE_OBJECT)
enum {
UDI_ADDED,
UDI_REMOVED,
RFKILL_CHANGED,
HAL_REAPPEARED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static gboolean poll_killswitches (gpointer user_data);
/* Device creators */
typedef struct {
GType device_type;
char *capability_str;
char *category;
gboolean (*is_device_fn) (NMHalManager *self, const char *udi);
NMDeviceCreatorFn creator_fn;
} DeviceCreator;
static DeviceCreator *
get_creator (NMHalManager *self, const char *udi)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
DeviceCreator *creator;
GSList *iter;
for (iter = priv->device_creators; iter; iter = g_slist_next (iter)) {
creator = (DeviceCreator *) iter->data;
if (libhal_device_query_capability (priv->hal_ctx, udi, creator->capability_str, NULL) &&
creator->is_device_fn (self, udi))
return creator;
}
return NULL;
}
/* end of device creators */
/* Common helpers for built-in device creators */
static char *
nm_get_device_driver_name (LibHalContext *ctx, const char *origdev_udi)
{
char *driver_name = NULL;
if (origdev_udi && libhal_device_property_exists (ctx, origdev_udi, "info.linux.driver", NULL)) {
char *drv = libhal_device_get_property_string (ctx, origdev_udi, "info.linux.driver", NULL);
driver_name = g_strdup (drv);
libhal_free_string (drv);
}
return driver_name;
}
/* Returns the parent if the device is a Sony Ericsson 'mbm'-style device */
static char *
is_mbm (LibHalContext *ctx, const char *udi)
{
guint32 vendor_id = 0, product_id = 0;
char *parent;
parent = libhal_device_get_property_string (ctx, udi, "info.parent", NULL);
if (!parent)
return NULL;
vendor_id = libhal_device_get_property_int (ctx, parent, "usb.vendor_id", NULL);
product_id = libhal_device_get_property_int (ctx, parent, "usb.product_id", NULL);
if ( (vendor_id == 0x0bdb && product_id == 0x1900) /* SE F3507g */
|| (vendor_id == 0x0bdb && product_id == 0x1902) /* SE F3507g */
|| (vendor_id == 0x0fce && product_id == 0xd0cf) /* SE MD300 */
|| (vendor_id == 0x413c && product_id == 0x8147)) /* Dell 5530 HSDPA */
return parent;
libhal_free_string (parent);
return NULL;
}
/* Wired device creator */
static gboolean
is_wired_device (NMHalManager *self, const char *udi)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
char *category;
gboolean is_wired = FALSE;
if (libhal_device_property_exists (priv->hal_ctx, udi, "net.linux.ifindex", NULL) &&
libhal_device_property_exists (priv->hal_ctx, udi, "info.category", NULL)) {
category = libhal_device_get_property_string (priv->hal_ctx, udi, "info.category", NULL);
if (category) {
is_wired = strcmp (category, "net.80203") == 0;
libhal_free_string (category);
}
}
return is_wired;
}
static GObject *
wired_device_creator (NMHalManager *self,
const char *udi,
const char *origdev_udi,
gboolean managed)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
GObject *device = NULL;
char *iface, *driver, *parent;
gboolean mbm = FALSE;
iface = libhal_device_get_property_string (priv->hal_ctx, udi, "net.interface", NULL);
if (!iface) {
nm_warning ("Couldn't get interface for %s, ignoring.", udi);
return NULL;
}
driver = nm_get_device_driver_name (priv->hal_ctx, origdev_udi);
/* Special handling of Ericsson F3507g 'mbm' devices; ignore the
* cdc-ether device that it provides since we don't use it yet.
*/
if (!strcmp (driver, "cdc_ether")) {
parent = is_mbm (priv->hal_ctx, udi);
mbm = !!parent;
libhal_free_string (parent);
}
if (!mbm)
device = (GObject *) nm_device_ethernet_new (udi, iface, driver, managed);
libhal_free_string (iface);
g_free (driver);
return device;
}
/* Wireless device creator */
static gboolean
is_wireless_device (NMHalManager *self, const char *udi)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
char *category;
gboolean is_wireless = FALSE;
if (libhal_device_property_exists (priv->hal_ctx, udi, "net.linux.ifindex", NULL) &&
libhal_device_property_exists (priv->hal_ctx, udi, "info.category", NULL)) {
category = libhal_device_get_property_string (priv->hal_ctx, udi, "info.category", NULL);
if (category) {
is_wireless = strcmp (category, "net.80211") == 0;
libhal_free_string (category);
}
}
return is_wireless;
}
static GObject *
wireless_device_creator (NMHalManager *self,
const char *udi,
const char *origdev_udi,
gboolean managed)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
GObject *device;
char *iface;
char *driver;
iface = libhal_device_get_property_string (priv->hal_ctx, udi, "net.interface", NULL);
if (!iface) {
nm_warning ("Couldn't get interface for %s, ignoring.", udi);
return NULL;
}
driver = nm_get_device_driver_name (priv->hal_ctx, origdev_udi);
device = (GObject *) nm_device_wifi_new (udi, iface, driver, managed);
libhal_free_string (iface);
g_free (driver);
return device;
}
/* Modem device creator */
static gboolean
is_modem_device (NMHalManager *self, const char *udi)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
gboolean is_modem = FALSE;
if (libhal_device_property_exists (priv->hal_ctx, udi, "info.category", NULL)) {
char *category;
category = libhal_device_get_property_string (priv->hal_ctx, udi, "info.category", NULL);
if (category) {
is_modem = strcmp (category, "serial") == 0;
libhal_free_string (category);
}
}
return is_modem;
}
static char *
get_hso_netdev (LibHalContext *ctx, const char *udi)
{
char *serial_parent, *netdev = NULL;
char **netdevs;
int num, i;
/* Get the serial interface's originating device UDI, used to find the
* originating device's netdev.
*/
serial_parent = libhal_device_get_property_string (ctx, udi, "serial.originating_device", NULL);
if (!serial_parent)
serial_parent = libhal_device_get_property_string (ctx, udi, "info.parent", NULL);
if (!serial_parent)
return NULL;
/* Look for the originating device's netdev */
netdevs = libhal_find_device_by_capability (ctx, "net", &num, NULL);
for (i = 0; netdevs && !netdev && (i < num); i++) {
char *netdev_parent, *tmp;
netdev_parent = libhal_device_get_property_string (ctx, netdevs[i], "net.originating_device", NULL);
if (!netdev_parent)
netdev_parent = libhal_device_get_property_string (ctx, netdevs[i], "net.physical_device", NULL);
if (!netdev_parent)
continue;
if (!strcmp (netdev_parent, serial_parent)) {
/* We found it */
tmp = libhal_device_get_property_string (ctx, netdevs[i], "net.interface", NULL);
if (tmp) {
netdev = g_strdup (tmp);
libhal_free_string (tmp);
}
}
libhal_free_string (netdev_parent);
}
libhal_free_string_array (netdevs);
libhal_free_string (serial_parent);
return netdev;
}
#define PROP_GSM "ID_NM_MODEM_GSM"
#define PROP_CDMA "ID_NM_MODEM_IS707_A"
#define PROP_EVDO1 "ID_NM_MODEM_IS856"
#define PROP_EVDOA "ID_NM_MODEM_IS856_A"
#if HAVE_LIBUDEV
typedef struct {
gboolean gsm;
gboolean cdma;
} UdevIterData;
#if UDEV_VERSION >= 129
static const char *
get_udev_property (struct udev_device *device, const char *name)
{
struct udev_list_entry *entry;
udev_list_entry_foreach (entry, udev_device_get_properties_list_entry (device)) {
if (strcmp (udev_list_entry_get_name (entry), name) == 0)
return udev_list_entry_get_value (entry);
}
return NULL;
}
#else
static int udev_device_prop_iter(struct udev_device *udev_device,
const char *key,
const char *value,
void *data)
{
UdevIterData *types = data;
if (!strcmp (key, PROP_GSM) && !strcmp (value, "1"))
types->gsm = TRUE;
if (!strcmp (key, PROP_CDMA) && !strcmp (value, "1"))
types->cdma = TRUE;
if (!strcmp (key, PROP_EVDO1) && !strcmp (value, "1"))
types->cdma = TRUE;
if (!strcmp (key, PROP_EVDOA) && !strcmp (value, "1"))
types->cdma = TRUE;
/* Return 0 to continue looking */
return types->gsm && types->cdma;
}
#endif
static gboolean
libudev_get_modem_capabilities (const char *sysfs_path,
gboolean *gsm,
gboolean *cdma)
{
struct udev *udev;
struct udev_device *device;
g_return_val_if_fail (sysfs_path != NULL, FALSE);
g_return_val_if_fail (gsm != NULL, FALSE);
g_return_val_if_fail (*gsm == FALSE, FALSE);
g_return_val_if_fail (cdma != NULL, FALSE);
g_return_val_if_fail (*cdma == FALSE, FALSE);
udev = udev_new ();
if (!udev)
return FALSE;
#if UDEV_VERSION >= 129
device = udev_device_new_from_syspath (udev, sysfs_path);
#else
/* udev_device_new_from_devpath() requires the sysfs mount point to be
* stripped off the path.
*/
if (!strncmp (sysfs_path, "/sys", 4))
sysfs_path += 4;
device = udev_device_new_from_devpath (udev, sysfs_path);
#endif
if (!device) {
udev_unref (udev);
nm_warning ("couldn't inspect device '%s' with libudev", sysfs_path);
return FALSE;
}
#if UDEV_VERSION >= 129
{
const char *gsm_val = get_udev_property (device, PROP_GSM);
const char *cdma_val = get_udev_property (device, PROP_CDMA);
const char *evdo1_val = get_udev_property (device, PROP_EVDO1);
const char *evdoa_val = get_udev_property (device, PROP_EVDOA);
if (gsm_val && !strcmp (gsm_val, "1"))
*gsm = TRUE;
if (cdma_val && !strcmp (cdma_val, "1"))
*cdma = TRUE;
if (evdo1_val && !strcmp (evdo1_val, "1"))
*cdma = TRUE;
if (evdoa_val && !strcmp (evdoa_val, "1"))
*cdma = TRUE;
}
#else
{
UdevIterData iterdata = { FALSE, FALSE };
udev_device_get_properties (device, udev_device_prop_iter, &iterdata);
*gsm = iterdata.gsm;
*cdma = iterdata.cdma;
}
#endif
udev_device_unref (device);
udev_unref (udev);
return TRUE;
}
#else
static gboolean
udevadm_get_modem_capabilities (const char *sysfs_path,
gboolean *gsm,
gboolean *cdma)
{
char *udevadm_argv[] = { "/sbin/udevadm", "info", "--query=env", NULL, NULL };
char *syspath_arg = NULL;
char *udevadm_stdout = NULL;
int exitcode;
GError *error = NULL;
char **lines = NULL, **iter;
gboolean success = FALSE;
g_return_val_if_fail (sysfs_path != NULL, FALSE);
g_return_val_if_fail (gsm != NULL, FALSE);
g_return_val_if_fail (*gsm == FALSE, FALSE);
g_return_val_if_fail (cdma != NULL, FALSE);
g_return_val_if_fail (*cdma == FALSE, FALSE);
udevadm_argv[3] = syspath_arg = g_strdup_printf ("--path=%s", sysfs_path);
if (g_spawn_sync ("/", udevadm_argv, NULL, 0, NULL, NULL,
&udevadm_stdout,
NULL,
&exitcode,
&error) != TRUE) {
nm_warning ("could not run udevadm to get modem capabilities for '%s': %s",
sysfs_path,
(error && error->message) ? error->message : "(unknown)");
g_clear_error (&error);
goto error;
}
if (exitcode != 0) {
nm_warning ("udevadm error while getting modem capabilities for '%s': %d",
sysfs_path, WEXITSTATUS (exitcode));
goto error;
}
lines = g_strsplit_set (udevadm_stdout, "\n\r", -1);
for (iter = lines; *iter; iter++) {
if (!strcmp (*iter, PROP_GSM "=1")) {
*gsm = TRUE;
break;
} else if ( !strcmp (*iter, PROP_CDMA "=1")
|| !strcmp (*iter, PROP_EVDO1 "=1")
|| !strcmp (*iter, PROP_EVDOA "=1")) {
*cdma = TRUE;
break;
}
}
success = TRUE;
error:
if (lines)
g_strfreev (lines);
g_free (udevadm_stdout);
g_free (syspath_arg);
return success;
}
#endif
static gboolean
hal_get_modem_capabilities (LibHalContext *ctx,
const char *udi,
gboolean *gsm,
gboolean *cdma)
{
char **capabilities, **iter;
g_return_val_if_fail (ctx != NULL, FALSE);
g_return_val_if_fail (udi != NULL, FALSE);
g_return_val_if_fail (gsm != NULL, FALSE);
g_return_val_if_fail (*gsm == FALSE, FALSE);
g_return_val_if_fail (cdma != NULL, FALSE);
g_return_val_if_fail (*cdma == FALSE, FALSE);
/* Make sure it has the 'modem' capability first */
if (!libhal_device_query_capability (ctx, udi, "modem", NULL))
return TRUE;
capabilities = libhal_device_get_property_strlist (ctx, udi, "modem.command_sets", NULL);
/* 'capabilites' may be NULL */
for (iter = capabilities; iter && *iter; iter++) {
if (!strcmp (*iter, "GSM-07.07")) {
*gsm = TRUE;
break;
}
if (!strcmp (*iter, "IS-707-A")) {
*cdma = TRUE;
break;
}
}
if (capabilities)
g_strfreev (capabilities);
/* Compatiblity with the pre-specification bits */
if (!*gsm && !*cdma) {
capabilities = libhal_device_get_property_strlist (ctx, udi, "info.capabilities", NULL);
for (iter = capabilities; iter && *iter; iter++) {
if (!strcmp (*iter, "gsm")) {
*gsm = TRUE;
break;
}
if (!strcmp (*iter, "cdma")) {
*cdma = TRUE;
break;
}
}
if (capabilities)
g_strfreev (capabilities);
}
return TRUE;
}
static GObject *
modem_device_creator (NMHalManager *self,
const char *udi,
const char *origdev_udi,
gboolean managed)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
char *serial_device;
const char *ttyname;
char *sysfs_path;
char *driver = NULL;
GObject *device = NULL;
gboolean type_gsm = FALSE;
gboolean type_cdma = FALSE;
char *netdev = NULL;
gboolean udev = TRUE;
serial_device = libhal_device_get_property_string (priv->hal_ctx, udi, "serial.device", NULL);
driver = nm_get_device_driver_name (priv->hal_ctx, origdev_udi);
if (!serial_device || !driver)
goto out;
/* Try udev first */
sysfs_path = libhal_device_get_property_string (priv->hal_ctx, udi, "linux.sysfs_path", NULL);
if (!sysfs_path) {
nm_warning ("could not determine sysfs path for '%s'", serial_device);
goto out;
}
ttyname = serial_device + strlen ("/dev/");
#if HAVE_LIBUDEV
libudev_get_modem_capabilities (sysfs_path, &type_gsm, &type_cdma);
#else
udevadm_get_modem_capabilities (sysfs_path, &type_gsm, &type_cdma);
#endif
libhal_free_string (sysfs_path);
/* If udev didn't know anything, try deprecated HAL modem capabilities */
if (!type_gsm && !type_cdma) {
hal_get_modem_capabilities (priv->hal_ctx, udi, &type_gsm, &type_cdma);
udev = FALSE;
}
if (!type_gsm && !type_cdma)
goto out;
nm_info ("(%s): detected %s modem via %s capabilities",
ttyname,
type_gsm ? "GSM" : "CDMA",
udev ? "udev" : "HAL");
/* Special handling of 'hso' cards (until punted to ModemManager) */
if (type_gsm && !strcmp (driver, "hso")) {
char *hsotype_path;
char *contents = NULL;
gboolean success;
/* We only want the "Control" interface, since that's the only
* one that gives us the unsolicited OWANCALL responses. Ignore
* errors since we didn't care about them before.
*/
hsotype_path = g_strdup_printf ("/sys/class/tty/%s/hsotype", ttyname);
success = g_file_get_contents (hsotype_path, &contents, NULL, NULL);
g_free (hsotype_path);
if (success && contents) {
if ( !strstr (contents, "Control")
&& !strstr (contents, "control")) {
g_free (contents);
goto out;
}
g_free (contents);
}
netdev = get_hso_netdev (priv->hal_ctx, udi);
}
/* Special handling of Option cards (until punted to ModemManager). Only
* the first USB interface can be used for control and PPP.
*/
if (type_gsm && !strcmp (driver, "option")) {
char *parent;
guint32 vendor_id = 0, usb_interface = 0;
parent = libhal_device_get_property_string (priv->hal_ctx, udi, "info.parent", NULL);
if (!parent)
goto out;
vendor_id = libhal_device_get_property_int (priv->hal_ctx, parent, "usb.vendor_id", NULL);
if (vendor_id == 0x0AF0) {
usb_interface = libhal_device_get_property_int (priv->hal_ctx, parent, "usb.interface.number", NULL);
if (usb_interface > 0) {
g_free (parent);
goto out;
}
}
libhal_free_string (parent);
}
/* Special handling of Ericsson F3507g 'mbm' devices; already handled by
* ModemManager more correctly in HEAD.
*/
if (type_gsm && !strcmp (driver, "cdc_acm")) {
char *parent;
guint32 usb_interface;
parent = is_mbm (priv->hal_ctx, udi);
if (parent) {
usb_interface = libhal_device_get_property_int (priv->hal_ctx, parent, "usb.interface.number", NULL);
if (usb_interface != 1) {
libhal_free_string (parent);
goto out;
}
libhal_free_string (parent);
}
}
if (type_gsm) {
if (netdev)
device = (GObject *) nm_hso_gsm_device_new (udi, ttyname, NULL, netdev, driver, managed);
else
device = (GObject *) nm_gsm_device_new (udi, ttyname, NULL, driver, managed);
} else if (type_cdma)
device = (GObject *) nm_cdma_device_new (udi, ttyname, NULL, driver, managed);
out:
libhal_free_string (serial_device);
g_free (driver);
return device;
}
static void
register_built_in_creators (NMHalManager *self)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
DeviceCreator *creator;
/* Wired device */
creator = g_slice_new0 (DeviceCreator);
creator->device_type = NM_TYPE_DEVICE_ETHERNET;
creator->capability_str = g_strdup ("net.80203");
creator->category = g_strdup ("net");
creator->is_device_fn = is_wired_device;
creator->creator_fn = wired_device_creator;
priv->device_creators = g_slist_append (priv->device_creators, creator);
/* Wireless device */
creator = g_slice_new0 (DeviceCreator);
creator->device_type = NM_TYPE_DEVICE_WIFI;
creator->capability_str = g_strdup ("net.80211");
creator->category = g_strdup ("net");
creator->is_device_fn = is_wireless_device;
creator->creator_fn = wireless_device_creator;
priv->device_creators = g_slist_append (priv->device_creators, creator);
/* Modem */
creator = g_slice_new0 (DeviceCreator);
creator->device_type = NM_TYPE_SERIAL_DEVICE;
creator->capability_str = g_strdup ("serial");
creator->category = g_strdup ("serial");
creator->is_device_fn = is_modem_device;
creator->creator_fn = modem_device_creator;
priv->device_creators = g_slist_append (priv->device_creators, creator);
}
static void
emit_udi_added (NMHalManager *self, const char *udi, DeviceCreator *creator)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
char *od, *tmp;
g_return_if_fail (self != NULL);
g_return_if_fail (udi != NULL);
g_return_if_fail (creator != NULL);
tmp = g_strdup_printf ("%s.originating_device", creator->category);
od = libhal_device_get_property_string (priv->hal_ctx, udi, tmp, NULL);
g_free (tmp);
if (!od) {
/* Older HAL uses 'physical_device' */
tmp = g_strdup_printf ("%s.physical_device", creator->category);
od = libhal_device_get_property_string (priv->hal_ctx, udi, tmp, NULL);
g_free (tmp);
}
g_signal_emit (self, signals[UDI_ADDED], 0,
udi,
od,
GSIZE_TO_POINTER (creator->device_type),
creator->creator_fn);
libhal_free_string (od);
}
static void
device_added (LibHalContext *ctx, const char *udi)
{
NMHalManager *self = NM_HAL_MANAGER (libhal_ctx_get_user_data (ctx));
DeviceCreator *creator;
/* If not all the device's properties are set up yet (like net.interface),
* the device will actually get added later when HAL signals new device
* capabilties.
*/
creator = get_creator (self, udi);
if (creator)
emit_udi_added (self, udi, creator);
}
static void
device_removed (LibHalContext *ctx, const char *udi)
{
NMHalManager *self = NM_HAL_MANAGER (libhal_ctx_get_user_data (ctx));
g_signal_emit (self, signals[UDI_REMOVED], 0, udi);
}
static void
device_new_capability (LibHalContext *ctx, const char *udi, const char *capability)
{
NMHalManager *self = NM_HAL_MANAGER (libhal_ctx_get_user_data (ctx));
DeviceCreator *creator;
creator = get_creator (self, udi);
if (creator)
emit_udi_added (self, udi, creator);
}
static void
add_initial_devices (NMHalManager *self)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
DeviceCreator *creator;
GSList *iter;
char **devices;
int num_devices;
int i;
DBusError err;
for (iter = priv->device_creators; iter; iter = g_slist_next (iter)) {
creator = (DeviceCreator *) iter->data;
dbus_error_init (&err);
devices = libhal_find_device_by_capability (priv->hal_ctx,
creator->capability_str,
&num_devices,
&err);
if (dbus_error_is_set (&err)) {
nm_warning ("could not find existing devices: %s", err.message);
dbus_error_free (&err);
continue;
}
if (!devices)
continue;
for (i = 0; i < num_devices; i++) {
if (creator->is_device_fn (self, devices[i]))
emit_udi_added (self, devices[i], creator);
}
libhal_free_string_array (devices);
}
}
static void
killswitch_poll_cleanup (NMHalManager *self)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
if (priv->poll_proxies) {
g_slist_foreach (priv->poll_proxies, (GFunc) g_object_unref, NULL);
g_slist_free (priv->poll_proxies);
priv->poll_proxies = NULL;
}
priv->pending_polls = 0;
priv->poll_rfkilled = FALSE;
}
static void
killswitch_getpower_done (gpointer user_data)
{
NMHalManager *self = NM_HAL_MANAGER (user_data);
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
priv->pending_polls--;
if (priv->pending_polls > 0)
return;
if (priv->poll_rfkilled != priv->rfkilled) {
priv->rfkilled = priv->poll_rfkilled;
g_signal_emit (self, signals[RFKILL_CHANGED], 0, priv->rfkilled);
}
killswitch_poll_cleanup (self);
/* Schedule next poll */
priv->killswitch_poll_id = g_timeout_add_seconds (RFKILL_POLL_FREQUENCY,
poll_killswitches,
self);
}
static void
killswitch_getpower_reply (DBusGProxy *proxy,
DBusGProxyCall *call_id,
gpointer user_data)
{
NMHalManager *self = NM_HAL_MANAGER (user_data);
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
int power = 1;
GError *err = NULL;
if (dbus_g_proxy_end_call (proxy, call_id, &err,
G_TYPE_INT, &power,
G_TYPE_INVALID)) {
if (power == 0)
priv->poll_rfkilled = TRUE;
} else {
if (err->message) {
/* Only print the error if we haven't seen it before */
if (!priv->kswitch_err || strcmp (priv->kswitch_err, err->message) != 0) {
nm_warning ("Error getting killswitch power: %s.", err->message);
g_free (priv->kswitch_err);
priv->kswitch_err = g_strdup (err->message);
/* If there was an error talking to HAL, treat that as rfkilled.
* See rh #448889. On some Dell laptops, dellWirelessCtl
* may not be present, but HAL still advertises a killswitch,
* and calls to GetPower() will fail. Thus we cannot assume
* that a failure of GetPower() automatically means the wireless
* is rfkilled, because in this situation NM would never bring
* the radio up. Only assume failures between NM and HAL should
* block the radio, not failures of the HAL killswitch callout
* itself.
*/
if (strstr (err->message, "Did not receive a reply")) {
nm_warning ("HAL did not reply to killswitch power request;"
" assuming radio is blocked.");
priv->poll_rfkilled = TRUE;
}
}
}
g_error_free (err);
}
}
static void
poll_one_killswitch (gpointer data, gpointer user_data)
{
NMHalManager *self = NM_HAL_MANAGER (user_data);
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
DBusGProxy *proxy;
proxy = dbus_g_proxy_new_for_name (nm_dbus_manager_get_connection (priv->dbus_mgr),
"org.freedesktop.Hal",
(char *) data,
"org.freedesktop.Hal.Device.KillSwitch");
dbus_g_proxy_begin_call (proxy, "GetPower",
killswitch_getpower_reply,
self,
killswitch_getpower_done,
G_TYPE_INVALID);
priv->pending_polls++;
priv->poll_proxies = g_slist_prepend (priv->poll_proxies, proxy);
}
static gboolean
poll_killswitches (gpointer user_data)
{
NMHalManager *self = NM_HAL_MANAGER (user_data);
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
killswitch_poll_cleanup (self);
g_slist_foreach (priv->killswitch_list, poll_one_killswitch, self);
return FALSE;
}
static void
add_killswitch_device (NMHalManager *self, const char *udi)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
char *type;
GSList *iter;
type = libhal_device_get_property_string (priv->hal_ctx, udi, "killswitch.type", NULL);
if (!type)
return;
if (strcmp (type, "wlan"))
goto out;
/* see if it's already in the list */
for (iter = priv->killswitch_list; iter; iter = iter->next) {
const char *list_udi = (const char *) iter->data;
if (!strcmp (list_udi, udi))
goto out;
}
/* Poll switches if this is the first switch we've found */
if (!priv->killswitch_list)
priv->killswitch_poll_id = g_idle_add (poll_killswitches, self);
priv->killswitch_list = g_slist_append (priv->killswitch_list, g_strdup (udi));
nm_info ("Found radio killswitch %s", udi);
out:
libhal_free_string (type);
}
static void
add_killswitch_devices (NMHalManager *self)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
char **udis;
int num_udis;
int i;
DBusError err;
dbus_error_init (&err);
udis = libhal_find_device_by_capability (priv->hal_ctx, "killswitch", &num_udis, &err);
if (!udis)
return;
if (dbus_error_is_set (&err)) {
nm_warning ("Could not find killswitch devices: %s", err.message);
dbus_error_free (&err);
return;
}
for (i = 0; i < num_udis; i++)
add_killswitch_device (self, udis[i]);
libhal_free_string_array (udis);
}
static gboolean
hal_init (NMHalManager *self)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
DBusError error;
DBusGConnection *connection;
priv->hal_ctx = libhal_ctx_new ();
if (!priv->hal_ctx) {
nm_warning ("Could not get connection to the HAL service.");
return FALSE;
}
connection = nm_dbus_manager_get_connection (priv->dbus_mgr);
libhal_ctx_set_dbus_connection (priv->hal_ctx,
dbus_g_connection_get_connection (connection));
dbus_error_init (&error);
if (!libhal_ctx_init (priv->hal_ctx, &error)) {
nm_warning ("libhal_ctx_init() failed: %s\n"
"Make sure the hal daemon is running?",
error.message);
goto error;
}
libhal_ctx_set_user_data (priv->hal_ctx, self);
libhal_ctx_set_device_added (priv->hal_ctx, device_added);
libhal_ctx_set_device_removed (priv->hal_ctx, device_removed);
libhal_ctx_set_device_new_capability (priv->hal_ctx, device_new_capability);
libhal_device_property_watch_all (priv->hal_ctx, &error);
if (dbus_error_is_set (&error)) {
nm_error ("libhal_device_property_watch_all(): %s", error.message);
libhal_ctx_shutdown (priv->hal_ctx, NULL);
goto error;
}
return TRUE;
error:
if (dbus_error_is_set (&error))
dbus_error_free (&error);
if (priv->hal_ctx) {
libhal_ctx_free (priv->hal_ctx);
priv->hal_ctx = NULL;
}
return FALSE;
}
static void
hal_deinit (NMHalManager *self)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
DBusError error;
if (priv->killswitch_poll_id) {
g_source_remove (priv->killswitch_poll_id);
priv->killswitch_poll_id = 0;
}
killswitch_poll_cleanup (self);
if (priv->killswitch_list) {
g_slist_foreach (priv->killswitch_list, (GFunc) g_free, NULL);
g_slist_free (priv->killswitch_list);
priv->killswitch_list = NULL;
}
if (!priv->hal_ctx)
return;
dbus_error_init (&error);
libhal_ctx_shutdown (priv->hal_ctx, &error);
if (dbus_error_is_set (&error)) {
nm_warning ("libhal shutdown failed - %s", error.message);
dbus_error_free (&error);
}
libhal_ctx_free (priv->hal_ctx);
priv->hal_ctx = NULL;
}
static void
name_owner_changed (NMDBusManager *dbus_mgr,
const char *name,
const char *old,
const char *new,
gpointer user_data)
{
NMHalManager *self = NM_HAL_MANAGER (user_data);
gboolean old_owner_good = (old && (strlen (old) > 0));
gboolean new_owner_good = (new && (strlen (new) > 0));
/* Only care about signals from HAL */
if (strcmp (name, HAL_DBUS_SERVICE))
return;
if (!old_owner_good && new_owner_good) {
nm_info ("HAL re-appeared");
/* HAL just appeared */
if (!hal_init (self))
nm_warning ("Could not re-connect to HAL!!");
else
g_signal_emit (self, signals[HAL_REAPPEARED], 0);
} else if (old_owner_good && !new_owner_good) {
/* HAL went away. Bad HAL. */
nm_info ("HAL disappeared");
hal_deinit (self);
}
}
static void
connection_changed (NMDBusManager *dbus_mgr,
DBusGConnection *connection,
gpointer user_data)
{
NMHalManager *self = NM_HAL_MANAGER (user_data);
if (!connection) {
hal_deinit (self);
return;
}
if (nm_dbus_manager_name_has_owner (dbus_mgr, HAL_DBUS_SERVICE)) {
if (!hal_init (self))
nm_warning ("Could not re-connect to HAL!!");
}
}
void
nm_hal_manager_query_devices (NMHalManager *self)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
/* Find hardware we care about */
if (priv->hal_ctx) {
add_killswitch_devices (self);
add_initial_devices (self);
}
}
gboolean
nm_hal_manager_udi_exists (NMHalManager *self, const char *udi)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
return libhal_device_property_exists (priv->hal_ctx, udi, "info.udi", NULL);
}
NMHalManager *
nm_hal_manager_new (void)
{
NMHalManager *self;
NMHalManagerPrivate *priv;
self = NM_HAL_MANAGER (g_object_new (NM_TYPE_HAL_MANAGER, NULL));
priv = NM_HAL_MANAGER_GET_PRIVATE (self);
if (!nm_dbus_manager_name_has_owner (priv->dbus_mgr, HAL_DBUS_SERVICE)) {
nm_info ("Waiting for HAL to start...");
return self;
}
if (!hal_init (self)) {
g_object_unref (self);
self = NULL;
}
return self;
}
static void
nm_hal_manager_init (NMHalManager *self)
{
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
priv->rfkilled = FALSE;
priv->dbus_mgr = nm_dbus_manager_get ();
register_built_in_creators (self);
g_signal_connect (priv->dbus_mgr,
"name-owner-changed",
G_CALLBACK (name_owner_changed),
self);
g_signal_connect (priv->dbus_mgr,
"dbus-connection-changed",
G_CALLBACK (connection_changed),
self);
}
static void
destroy_creator (gpointer data, gpointer user_data)
{
DeviceCreator *creator = (DeviceCreator *) data;
g_free (creator->capability_str);
g_free (creator->category);
g_slice_free (DeviceCreator, data);
}
static void
dispose (GObject *object)
{
NMHalManager *self = NM_HAL_MANAGER (object);
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
if (priv->disposed) {
G_OBJECT_CLASS (nm_hal_manager_parent_class)->dispose (object);
return;
}
priv->disposed = TRUE;
g_object_unref (priv->dbus_mgr);
g_slist_foreach (priv->device_creators, destroy_creator, NULL);
g_slist_free (priv->device_creators);
hal_deinit (self);
G_OBJECT_CLASS (nm_hal_manager_parent_class)->dispose (object);
}
static void
finalize (GObject *object)
{
NMHalManager *self = NM_HAL_MANAGER (object);
NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
g_free (priv->kswitch_err);
G_OBJECT_CLASS (nm_hal_manager_parent_class)->finalize (object);
}
static void
nm_hal_manager_class_init (NMHalManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (NMHalManagerPrivate));
/* virtual methods */
object_class->dispose = dispose;
object_class->finalize = finalize;
/* Signals */
signals[UDI_ADDED] =
g_signal_new ("udi-added",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMHalManagerClass, udi_added),
NULL, NULL,
_nm_marshal_VOID__STRING_STRING_POINTER_POINTER,
G_TYPE_NONE, 4,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER);
signals[UDI_REMOVED] =
g_signal_new ("udi-removed",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMHalManagerClass, udi_removed),
NULL, NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
signals[RFKILL_CHANGED] =
g_signal_new ("rfkill-changed",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMHalManagerClass, rfkill_changed),
NULL, NULL,
g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE, 1,
G_TYPE_BOOLEAN);
signals[HAL_REAPPEARED] =
g_signal_new ("hal-reappeared",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMHalManagerClass, hal_reappeared),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}