From 1f87f7d3a3b42b20f34cb03f0fd1a41c3d0e27f3 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 2 Jun 2009 13:01:41 +0200 Subject: cfg80211: add rfkill support To be easier on drivers and users, have cfg80211 register an rfkill structure that drivers can access. When soft-killed, simply take down all interfaces; when hard-killed the driver needs to notify us and we will take down the interfaces after the fact. While rfkilled, interfaces cannot be set UP. Signed-off-by: Johannes Berg Signed-off-by: John W. Linville --- net/mac80211/cfg.c | 20 ++++------ net/mac80211/driver-ops.h | 7 ++++ net/mac80211/iface.c | 4 +- net/mac80211/util.c | 2 +- net/wireless/Kconfig | 3 +- net/wireless/core.c | 97 ++++++++++++++++++++++++++++++++++++++++++++-- net/wireless/core.h | 7 ++++ net/wireless/wext-compat.c | 11 ++++-- 8 files changed, 128 insertions(+), 23 deletions(-) (limited to 'net') diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 81258acf48b..a9211cc183c 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1340,7 +1340,6 @@ static int ieee80211_set_tx_power(struct wiphy *wiphy, struct ieee80211_local *local = wiphy_priv(wiphy); struct ieee80211_channel *chan = local->hw.conf.channel; u32 changes = 0; - bool radio_enabled = true; switch (type) { case TX_POWER_AUTOMATIC: @@ -1359,14 +1358,6 @@ static int ieee80211_set_tx_power(struct wiphy *wiphy, return -EINVAL; local->user_power_level = dbm; break; - case TX_POWER_OFF: - radio_enabled = false; - break; - } - - if (radio_enabled != local->hw.conf.radio_enabled) { - changes |= IEEE80211_CONF_CHANGE_RADIO_ENABLED; - local->hw.conf.radio_enabled = radio_enabled; } ieee80211_hw_config(local, changes); @@ -1380,12 +1371,16 @@ static int ieee80211_get_tx_power(struct wiphy *wiphy, int *dbm) *dbm = local->hw.conf.power_level; - if (!local->hw.conf.radio_enabled) - return -ENETDOWN; - return 0; } +static void ieee80211_rfkill_poll(struct wiphy *wiphy) +{ + struct ieee80211_local *local = wiphy_priv(wiphy); + + drv_rfkill_poll(local); +} + struct cfg80211_ops mac80211_config_ops = { .add_virtual_intf = ieee80211_add_iface, .del_virtual_intf = ieee80211_del_iface, @@ -1427,4 +1422,5 @@ struct cfg80211_ops mac80211_config_ops = { .set_wiphy_params = ieee80211_set_wiphy_params, .set_tx_power = ieee80211_set_tx_power, .get_tx_power = ieee80211_get_tx_power, + .rfkill_poll = ieee80211_rfkill_poll, }; diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h index 3912b5334b9..b13446afd48 100644 --- a/net/mac80211/driver-ops.h +++ b/net/mac80211/driver-ops.h @@ -181,4 +181,11 @@ static inline int drv_ampdu_action(struct ieee80211_local *local, sta, tid, ssn); return -EOPNOTSUPP; } + + +static inline void drv_rfkill_poll(struct ieee80211_local *local) +{ + if (local->ops->rfkill_poll) + local->ops->rfkill_poll(&local->hw); +} #endif /* __MAC80211_DRIVER_OPS */ diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 8c9f1c722cd..b7c8a448429 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -170,7 +170,7 @@ static int ieee80211_open(struct net_device *dev) goto err_del_bss; /* we're brought up, everything changes */ hw_reconf_flags = ~0; - ieee80211_led_radio(local, local->hw.conf.radio_enabled); + ieee80211_led_radio(local, true); } /* @@ -560,7 +560,7 @@ static int ieee80211_stop(struct net_device *dev) drv_stop(local); - ieee80211_led_radio(local, 0); + ieee80211_led_radio(local, false); flush_workqueue(local->hw.workqueue); diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 31284c984e3..22f63815fb3 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -973,7 +973,7 @@ int ieee80211_reconfig(struct ieee80211_local *local) if (local->open_count) { res = drv_start(local); - ieee80211_led_radio(local, hw->conf.radio_enabled); + ieee80211_led_radio(local, true); } /* add interfaces */ diff --git a/net/wireless/Kconfig b/net/wireless/Kconfig index 45005497c63..4428dd5e911 100644 --- a/net/wireless/Kconfig +++ b/net/wireless/Kconfig @@ -1,5 +1,6 @@ config CFG80211 - tristate "Improved wireless configuration API" + tristate "Improved wireless configuration API" + depends on RFKILL || !RFKILL config CFG80211_REG_DEBUG bool "cfg80211 regulatory debugging" diff --git a/net/wireless/core.c b/net/wireless/core.c index a5dbea1da47..3b74b88e10a 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include "nl80211.h" @@ -227,6 +228,41 @@ int cfg80211_dev_rename(struct cfg80211_registered_device *rdev, return 0; } +static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data) +{ + struct cfg80211_registered_device *drv = data; + + drv->ops->rfkill_poll(&drv->wiphy); +} + +static int cfg80211_rfkill_set_block(void *data, bool blocked) +{ + struct cfg80211_registered_device *drv = data; + struct wireless_dev *wdev; + + if (!blocked) + return 0; + + rtnl_lock(); + mutex_lock(&drv->devlist_mtx); + + list_for_each_entry(wdev, &drv->netdev_list, list) + dev_close(wdev->netdev); + + mutex_unlock(&drv->devlist_mtx); + rtnl_unlock(); + + return 0; +} + +static void cfg80211_rfkill_sync_work(struct work_struct *work) +{ + struct cfg80211_registered_device *drv; + + drv = container_of(work, struct cfg80211_registered_device, rfkill_sync); + cfg80211_rfkill_set_block(drv, rfkill_blocked(drv->rfkill)); +} + /* exported functions */ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) @@ -274,6 +310,18 @@ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) drv->wiphy.dev.class = &ieee80211_class; drv->wiphy.dev.platform_data = drv; + drv->rfkill_ops.set_block = cfg80211_rfkill_set_block; + drv->rfkill = rfkill_alloc(dev_name(&drv->wiphy.dev), + &drv->wiphy.dev, RFKILL_TYPE_WLAN, + &drv->rfkill_ops, drv); + + if (!drv->rfkill) { + kfree(drv); + return NULL; + } + + INIT_WORK(&drv->rfkill_sync, cfg80211_rfkill_sync_work); + /* * Initialize wiphy parameters to IEEE 802.11 MIB default values. * Fragmentation and RTS threshold are disabled by default with the @@ -356,6 +404,10 @@ int wiphy_register(struct wiphy *wiphy) if (res) goto out_unlock; + res = rfkill_register(drv->rfkill); + if (res) + goto out_rm_dev; + list_add(&drv->list, &cfg80211_drv_list); /* add to debugfs */ @@ -379,16 +431,41 @@ int wiphy_register(struct wiphy *wiphy) cfg80211_debugfs_drv_add(drv); res = 0; -out_unlock: + goto out_unlock; + + out_rm_dev: + device_del(&drv->wiphy.dev); + out_unlock: mutex_unlock(&cfg80211_mutex); return res; } EXPORT_SYMBOL(wiphy_register); +void wiphy_rfkill_start_polling(struct wiphy *wiphy) +{ + struct cfg80211_registered_device *drv = wiphy_to_dev(wiphy); + + if (!drv->ops->rfkill_poll) + return; + drv->rfkill_ops.poll = cfg80211_rfkill_poll; + rfkill_resume_polling(drv->rfkill); +} +EXPORT_SYMBOL(wiphy_rfkill_start_polling); + +void wiphy_rfkill_stop_polling(struct wiphy *wiphy) +{ + struct cfg80211_registered_device *drv = wiphy_to_dev(wiphy); + + rfkill_pause_polling(drv->rfkill); +} +EXPORT_SYMBOL(wiphy_rfkill_stop_polling); + void wiphy_unregister(struct wiphy *wiphy) { struct cfg80211_registered_device *drv = wiphy_to_dev(wiphy); + rfkill_unregister(drv->rfkill); + /* protect the device list */ mutex_lock(&cfg80211_mutex); @@ -425,6 +502,7 @@ EXPORT_SYMBOL(wiphy_unregister); void cfg80211_dev_free(struct cfg80211_registered_device *drv) { struct cfg80211_internal_bss *scan, *tmp; + rfkill_destroy(drv->rfkill); mutex_destroy(&drv->mtx); mutex_destroy(&drv->devlist_mtx); list_for_each_entry_safe(scan, tmp, &drv->bss_list, list) @@ -438,6 +516,15 @@ void wiphy_free(struct wiphy *wiphy) } EXPORT_SYMBOL(wiphy_free); +void wiphy_rfkill_set_hw_state(struct wiphy *wiphy, bool blocked) +{ + struct cfg80211_registered_device *drv = wiphy_to_dev(wiphy); + + if (rfkill_set_hw_state(drv->rfkill, blocked)) + schedule_work(&drv->rfkill_sync); +} +EXPORT_SYMBOL(wiphy_rfkill_set_hw_state); + static int cfg80211_netdev_notifier_call(struct notifier_block * nb, unsigned long state, void *ndev) @@ -446,7 +533,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, struct cfg80211_registered_device *rdev; if (!dev->ieee80211_ptr) - return 0; + return NOTIFY_DONE; rdev = wiphy_to_dev(dev->ieee80211_ptr->wiphy); @@ -492,9 +579,13 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, } mutex_unlock(&rdev->devlist_mtx); break; + case NETDEV_PRE_UP: + if (rfkill_blocked(rdev->rfkill)) + return notifier_from_errno(-ERFKILL); + break; } - return 0; + return NOTIFY_DONE; } static struct notifier_block cfg80211_netdev_notifier = { diff --git a/net/wireless/core.h b/net/wireless/core.h index ab512bcd815..bfa340c7abb 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include #include "reg.h" @@ -24,6 +26,11 @@ struct cfg80211_registered_device { * any call is in progress */ struct mutex mtx; + /* rfkill support */ + struct rfkill_ops rfkill_ops; + struct rfkill *rfkill; + struct work_struct rfkill_sync; + /* ISO / IEC 3166 alpha2 for which this device is receiving * country IEs on, this can help disregard country IEs from APs * on the same alpha2 quickly. The alpha2 may differ from diff --git a/net/wireless/wext-compat.c b/net/wireless/wext-compat.c index 9fbfb8536e7..d030c531567 100644 --- a/net/wireless/wext-compat.c +++ b/net/wireless/wext-compat.c @@ -764,6 +764,8 @@ int cfg80211_wext_siwtxpower(struct net_device *dev, /* only change when not disabling */ if (!data->txpower.disabled) { + rfkill_set_sw_state(rdev->rfkill, false); + if (data->txpower.fixed) { /* * wext doesn't support negative values, see @@ -787,7 +789,9 @@ int cfg80211_wext_siwtxpower(struct net_device *dev, } } } else { - type = TX_POWER_OFF; + rfkill_set_sw_state(rdev->rfkill, true); + schedule_work(&rdev->rfkill_sync); + return 0; } return rdev->ops->set_tx_power(wdev->wiphy, type, dbm);; @@ -811,13 +815,12 @@ int cfg80211_wext_giwtxpower(struct net_device *dev, return -EOPNOTSUPP; err = rdev->ops->get_tx_power(wdev->wiphy, &val); - /* HACK!!! */ - if (err && err != -ENETDOWN) + if (err) return err; /* well... oh well */ data->txpower.fixed = 1; - data->txpower.disabled = err == -ENETDOWN; + data->txpower.disabled = rfkill_blocked(rdev->rfkill); data->txpower.value = val; data->txpower.flags = IW_TXPOW_DBM; -- cgit v1.2.3