blob: 1d3d65b28f279f0523ed51b90398681de547b13d [file] [log] [blame]
/*
*
* Connection Manager
*
* Copyright (C) 2007-2009 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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 St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE
#include <libudev.h>
#include <glib.h>
#include "connman.h"
#define _DBG_UDEV(fmt, arg...) DBG(DBG_UDEV, fmt, ## arg)
#ifdef NEED_UDEV_MONITOR_FILTER
static int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor,
const char *subsystem, const char *devtype)
{
return -EINVAL;
}
static int udev_monitor_filter_update(struct udev_monitor *udev_monitor)
{
return -EINVAL;
}
static int udev_monitor_filter_remove(struct udev_monitor *udev_monitor)
{
return -EINVAL;
}
#endif
static GSList *device_list = NULL;
static struct connman_device *find_device(int index)
{
GSList *list;
if (index < 0)
return NULL;
for (list = device_list; list; list = list->next) {
struct connman_device *device = list->data;
if (connman_device_get_index(device) == index)
return device;
}
return NULL;
}
/*
* Hack work around for netdevice registration race. On some systems
* we receive a udev event notifiying us of a new network device, but
* when we request the interface name it's malformed (e.g. "wlan%d")
* because registration is incomplete. Work around this by checking
* the interface name and re-trying the add operation when it's
* recognized as incomplete.
*
* This is reliably seen with ath6kl and bcmfmac on 2.6.38 ARM systems.
*/
struct add_net_device_ctx {
struct udev_device *device;
int tries;
};
static void *new_add_net_device_ctx(struct udev_device *device, const int tries)
{
struct add_net_device_ctx *ctx =
g_try_new(struct add_net_device_ctx, 1);
if (ctx == NULL) {
connman_error("%s: no memory for add net device ctx", __func__);
return NULL;
}
ctx->device = udev_device_ref(device);
ctx->tries = tries;
return ctx;
}
static void free_add_net_device_ctx(struct add_net_device_ctx *ctx)
{
udev_device_unref(ctx->device);
g_free(ctx);
}
static void add_net_device(struct add_net_device_ctx *ctx);
static gboolean __add_net_device(gpointer data)
{
struct add_net_device_ctx *ctx = data;
add_net_device(ctx);
return FALSE;
}
static gboolean check_add_net_device(struct add_net_device_ctx *ctx, int index)
{
char *ifname = connman_inet_ifname(index);
if (ifname == NULL || g_str_has_suffix(ifname, "%d") == TRUE) {
if (--(ctx->tries) == 0) {
connman_error("%s: DEVICE NOT READY, too many tries",
ifname);
free_add_net_device_ctx(ctx);
} else {
connman_info("%s: DEVICE NOT READY, try again (%d)",
ifname, ctx->tries);
(void) g_timeout_add_seconds(1, __add_net_device, ctx);
}
g_free(ifname);
return FALSE;
}
g_free(ifname);
free_add_net_device_ctx(ctx);
return TRUE;
}
static void add_net_device(struct add_net_device_ctx *ctx)
{
struct udev_device *udev_device;
struct udev_list_entry *entry;
struct connman_device *device;
enum connman_device_type devtype;
const char *value, *systype;
int index = -1;
_DBG_UDEV("ctx %p", ctx);
if (ctx == NULL)
return;
udev_device = ctx->device;
systype = udev_device_get_sysattr_value(udev_device, "type");
if (systype == NULL || atoi(systype) != 1) {
free_add_net_device_ctx(ctx);
return;
}
entry = udev_device_get_properties_list_entry(udev_device);
while (entry) {
const char *name = udev_list_entry_get_name(entry);
if (g_str_has_prefix(name, "IFINDEX") == TRUE) {
const char *value = udev_list_entry_get_value(entry);
if (value != NULL)
index = atoi(value);
}
entry = udev_list_entry_get_next(entry);
}
if (index < 0) {
free_add_net_device_ctx(ctx);
return;
}
if (check_add_net_device(ctx, index) == FALSE) {
/* device not ready (defer), or out of tries (give up) */
return;
}
ctx = NULL; /* NB: ctx reclaimed on successful check */
devtype = __connman_inet_get_device_type(index);
switch (devtype) {
case CONNMAN_DEVICE_TYPE_UNKNOWN:
case CONNMAN_DEVICE_TYPE_VENDOR:
case CONNMAN_DEVICE_TYPE_WIMAX:
case CONNMAN_DEVICE_TYPE_BLUETOOTH:
case CONNMAN_DEVICE_TYPE_GPS:
case CONNMAN_DEVICE_TYPE_CELLULAR:
return;
case CONNMAN_DEVICE_TYPE_ETHERNET:
case CONNMAN_DEVICE_TYPE_WIFI:
break;
}
device = find_device(index);
if (device != NULL)
return;
device = connman_inet_create_device(index);
if (device == NULL)
return;
value = udev_device_get_sysattr_value(udev_device, "phy80211/index");
if (value != NULL)
__connman_device_set_phyindex(device, atoi(value));
if (connman_device_register(device) < 0) {
connman_device_unref(device);
return;
}
device_list = g_slist_append(device_list, device);
}
static void remove_net_device(struct udev_device *udev_device)
{
struct udev_list_entry *entry;
struct connman_device *device;
int index = -1;
_DBG_UDEV("");
entry = udev_device_get_properties_list_entry(udev_device);
while (entry) {
const char *name = udev_list_entry_get_name(entry);
if (g_str_has_prefix(name, "IFINDEX") == TRUE) {
const char *value = udev_list_entry_get_value(entry);
if (value != NULL)
index = atoi(value);
}
entry = udev_list_entry_get_next(entry);
}
if (index < 0)
return;
device = find_device(index);
if (device == NULL)
return;
device_list = g_slist_remove(device_list, device);
connman_device_unregister(device);
connman_device_unref(device);
}
static void phyindex_rfkill(int phyindex, connman_bool_t blocked)
{
GSList *list;
if (phyindex < 0)
return;
for (list = device_list; list; list = list->next) {
struct connman_device *device = list->data;
if (__connman_device_get_phyindex(device) == phyindex)
__connman_device_set_blocked(device, blocked);
}
}
static void change_rfkill_device(struct udev_device *device)
{
struct udev_device *parent;
struct udev_list_entry *entry;
connman_bool_t blocked;
const char *value, *type = NULL;
int state = -1;
entry = udev_device_get_properties_list_entry(device);
while (entry) {
const char *name = udev_list_entry_get_name(entry);
if (g_str_has_prefix(name, "RFKILL_STATE") == TRUE) {
value = udev_list_entry_get_value(entry);
if (value != NULL)
state = atoi(value);
} else if (g_str_has_prefix(name, "RFKILL_TYPE") == TRUE)
type = udev_list_entry_get_value(entry);
entry = udev_list_entry_get_next(entry);
}
if (type == NULL || state < 0)
return;
if (g_str_equal(type, "wlan") == FALSE)
return;
parent = udev_device_get_parent(device);
if (parent == NULL)
return;
value = udev_device_get_sysattr_value(parent, "index");
if (value == NULL)
return;
blocked = (state != 1) ? TRUE : FALSE;
phyindex_rfkill(atoi(value), blocked);
}
static void add_rfkill_device(struct udev_device *device)
{
change_rfkill_device(device);
}
static void print_properties(struct udev_device *device, const char *prefix)
{
struct udev_list_entry *entry;
entry = udev_device_get_properties_list_entry(device);
while (entry) {
const char *name = udev_list_entry_get_name(entry);
const char *value = udev_list_entry_get_value(entry);
if (g_str_has_prefix(name, "CONNMAN") == TRUE ||
g_str_has_prefix(name, "RFKILL") == TRUE ||
g_str_has_prefix(name, "ID_MODEM") == TRUE ||
g_str_equal(name, "ID_VENDOR") == TRUE ||
g_str_equal(name, "ID_MODEL") == TRUE ||
g_str_equal(name, "INTERFACE") == TRUE ||
g_str_equal(name, "IFINDEX") == TRUE ||
g_str_equal(name, "DEVNAME") == TRUE ||
g_str_equal(name, "DEVPATH") == TRUE)
connman_debug(DBG_UDEV, "%s%s = %s", prefix, name, value);
entry = udev_list_entry_get_next(entry);
}
}
static void print_device(struct udev_device *device, const char *action)
{
const char *subsystem, *devtype = NULL;
struct udev_device *parent;
connman_debug(DBG_UDEV, "=== %s ===", action);
print_properties(device, "");
parent = udev_device_get_parent(device);
if (parent == NULL)
return;
subsystem = udev_device_get_subsystem(parent);
if (subsystem != NULL &&
g_str_equal(subsystem, "usb-serial") == TRUE) {
subsystem = "usb";
devtype = "usb_device";
}
parent = udev_device_get_parent_with_subsystem_devtype(device,
subsystem, devtype);
print_properties(parent, " ");
}
static void enumerate_devices(struct udev *context)
{
struct udev_enumerate *enumerate;
struct udev_list_entry *entry;
enumerate = udev_enumerate_new(context);
if (enumerate == NULL)
return;
udev_enumerate_add_match_subsystem(enumerate, "net");
udev_enumerate_add_match_subsystem(enumerate, "rfkill");
udev_enumerate_scan_devices(enumerate);
entry = udev_enumerate_get_list_entry(enumerate);
while (entry) {
const char *syspath = udev_list_entry_get_name(entry);
struct udev_device *device;
device = udev_device_new_from_syspath(context, syspath);
if (device != NULL) {
const char *subsystem;
if (connman_debug_enabled(DBG_UDEV) == TRUE)
print_device(device, "coldplug");
subsystem = udev_device_get_subsystem(device);
if (g_strcmp0(subsystem, "net") == 0)
add_net_device(
new_add_net_device_ctx(device, 3));
else if (g_strcmp0(subsystem, "rfkill") == 0)
add_rfkill_device(device);
udev_device_unref(device);
}
entry = udev_list_entry_get_next(entry);
}
udev_enumerate_unref(enumerate);
}
static gboolean udev_event(GIOChannel *channel,
GIOCondition condition, gpointer user_data)
{
struct udev_monitor *monitor = user_data;
struct udev_device *device;
const char *subsystem, *action;
device = udev_monitor_receive_device(monitor);
if (device == NULL)
return TRUE;
subsystem = udev_device_get_subsystem(device);
if (subsystem == NULL)
goto done;
action = udev_device_get_action(device);
if (action == NULL)
goto done;
if (connman_debug_enabled(DBG_UDEV) == TRUE)
print_device(device, action);
if (g_str_equal(action, "add") == TRUE) {
if (g_str_equal(subsystem, "net") == TRUE)
add_net_device(new_add_net_device_ctx(device, 3));
else if (g_str_equal(subsystem, "rfkill") == TRUE)
add_rfkill_device(device);
} else if (g_str_equal(action, "remove") == TRUE) {
if (g_str_equal(subsystem, "net") == TRUE)
remove_net_device(device);
} else if (g_str_equal(action, "change") == TRUE) {
if (g_str_equal(subsystem, "rfkill") == TRUE)
change_rfkill_device(device);
}
done:
udev_device_unref(device);
return TRUE;
}
static struct udev *udev_ctx;
static struct udev_monitor *udev_mon;
static guint udev_watch = 0;
char *__connman_udev_get_devtype(const char *ifname)
{
struct udev_device *device;
const char *devtype;
device = udev_device_new_from_subsystem_sysname(udev_ctx,
"net", ifname);
if (device == NULL)
return NULL;
devtype = udev_device_get_devtype(device);
if (devtype == NULL)
goto done;
done:
udev_device_unref(device);
return NULL;
}
/**
* Return true if device is associated with an modem.
*
* Current implementation works for:
* - Modems that have an associated tty device (such as /dev/ttyACM%d or
* /dev/ttyUSB%d).
* - GOBI modems that use the QCUSBNet2k driver
* - Interfaces named "pseudo-modem*" that are used for simulations
*/
connman_bool_t __connman_udev_has_associated_modem(const char *ifname)
{
struct udev_device *device, *parent, *control;
struct udev_enumerate *ue;
struct udev_list_entry *dev_list_entry, *devices;
const char *driver, *devpath1, *devpath2;
connman_bool_t has_modem = FALSE;
if (g_str_has_prefix(ifname, "pseudo-modem"))
return TRUE;
device = udev_device_new_from_subsystem_sysname(udev_ctx,
"net", ifname);
if (device == NULL)
return FALSE;
if (g_str_has_prefix(ifname, "rmnet")) {
/*
* rmnet is always associated with modems.
* It is a virtual device and it does not have any
* parent/driver associated with it
*/
has_modem = TRUE;
goto done;
}
parent = udev_device_get_parent(device);
if (parent == NULL)
goto done;
driver = udev_device_get_driver(parent);
if (g_strcmp0(driver, "QCUSBNet2k") == 0 ||
g_strcmp0(driver, "GobiNet") == 0 ||
g_strcmp0(driver, "gobi") == 0) {
/*
* This family of drivers is always associated with Gobi 3G
* modems.
*/
has_modem = TRUE;
goto done;
}
if (g_strcmp0(driver, "cdc_ether") != 0 &&
g_strcmp0(driver, "cdc_ncm") != 0)
goto done;
/*
* cdc_ether could be a USB ethernet controller or the packet
* interface for a modem. Try to figure out which.
*/
parent = udev_device_get_parent_with_subsystem_devtype(device,
"usb", "usb_device");
if (parent == NULL)
goto done;
devpath1 = udev_device_get_devpath(parent);
ue = udev_enumerate_new(udev_ctx);
udev_enumerate_add_match_subsystem(ue, "tty");
udev_enumerate_scan_devices(ue);
devices = udev_enumerate_get_list_entry(ue);
udev_list_entry_foreach(dev_list_entry, devices) {
const char *path;
path = udev_list_entry_get_name(dev_list_entry);
control = udev_device_new_from_syspath(udev_ctx, path);
if (control == NULL)
continue;
parent = udev_device_get_parent_with_subsystem_devtype(control,
"usb", "usb_device");
if (parent == NULL) {
udev_device_unref(control);
continue;
}
devpath2 = udev_device_get_devpath(parent);
if (g_strcmp0(devpath1, devpath2) == 0) {
has_modem = TRUE;
udev_device_unref(control);
break;
}
udev_device_unref(control);
}
udev_enumerate_unref(ue);
done:
udev_device_unref(device);
return has_modem;
}
void __connman_udev_rfkill(const char *sysname, connman_bool_t blocked)
{
struct udev_device *device, *parent;
const char *value;
device = udev_device_new_from_subsystem_sysname(udev_ctx,
"rfkill", sysname);
if (device == NULL)
return;
parent = udev_device_get_parent(device);
if (parent == NULL) {
udev_device_unref(device);
return;
}
value = udev_device_get_sysattr_value(parent, "index");
if (value == NULL) {
udev_device_unref(device);
return;
}
phyindex_rfkill(atoi(value), blocked);
udev_device_unref(device);
}
int __connman_udev_init(void)
{
_DBG_UDEV("");
udev_ctx = udev_new();
if (udev_ctx == NULL) {
connman_error("Failed to create udev context");
return -1;
}
udev_mon = udev_monitor_new_from_netlink(udev_ctx, "udev");
if (udev_mon == NULL) {
connman_error("Failed to create udev monitor");
udev_unref(udev_ctx);
udev_ctx = NULL;
return -1;
}
udev_monitor_filter_add_match_subsystem_devtype(udev_mon,
"net", NULL);
udev_monitor_filter_add_match_subsystem_devtype(udev_mon,
"rfkill", NULL);
udev_monitor_filter_update(udev_mon);
return 0;
}
void __connman_udev_start(void)
{
GIOChannel *channel;
int fd;
_DBG_UDEV("");
if (udev_monitor_enable_receiving(udev_mon) < 0) {
connman_error("Failed to enable udev monitor");
return;
}
enumerate_devices(udev_ctx);
fd = udev_monitor_get_fd(udev_mon);
channel = g_io_channel_unix_new(fd);
if (channel == NULL)
return;
udev_watch = g_io_add_watch(channel, G_IO_IN, udev_event, udev_mon);
g_io_channel_unref(channel);
}
void __connman_udev_cleanup(void)
{
GSList *list;
_DBG_UDEV("");
if (udev_watch > 0)
g_source_remove(udev_watch);
for (list = device_list; list; list = list->next) {
struct connman_device *device = list->data;
connman_device_unregister(device);
connman_device_unref(device);
}
g_slist_free(device_list);
device_list = NULL;
if (udev_ctx == NULL)
return;
udev_monitor_filter_remove(udev_mon);
udev_monitor_unref(udev_mon);
udev_unref(udev_ctx);
}