Skip to content

Commit

Permalink
rfkill: rewrite
Browse files Browse the repository at this point in the history
This patch completely rewrites the rfkill core to address
the following deficiencies:

 * all rfkill drivers need to implement polling where necessary
   rather than having one central implementation

 * updating the rfkill state cannot be done from arbitrary
   contexts, forcing drivers to use schedule_work and requiring
   lots of code

 * rfkill drivers need to keep track of soft/hard blocked
   internally -- the core should do this

 * the rfkill API has many unexpected quirks, for example being
   asymmetric wrt. alloc/free and register/unregister

 * rfkill can call back into a driver from within a function the
   driver called -- this is prone to deadlocks and generally
   should be avoided

 * rfkill-input pointlessly is a separate module

 * drivers need to #ifdef rfkill functions (unless they want to
   depend on or select RFKILL) -- rfkill should provide inlines
   that do nothing if it isn't compiled in

 * the rfkill structure is not opaque -- drivers need to initialise
   it correctly (lots of sanity checking code required) -- instead
   force drivers to pass the right variables to rfkill_alloc()

 * the documentation is hard to read because it always assumes the
   reader is completely clueless and contains way TOO MANY CAPS

 * the rfkill code needlessly uses a lot of locks and atomic
   operations in locked sections

 * fix LED trigger to actually change the LED when the radio state
   changes -- this wasn't done before

Tested-by: Alan Jenkins <alan-jenkins@tuffmail.co.uk>
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br> [thinkpad]
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
  • Loading branch information
jmberg authored and linvjw committed Jun 3, 2009
1 parent 0f6399c commit 19d337d
Show file tree
Hide file tree
Showing 46 changed files with 2,536 additions and 3,273 deletions.
597 changes: 78 additions & 519 deletions Documentation/rfkill.txt

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -4753,9 +4753,9 @@ S: Supported
F: fs/reiserfs/

RFKILL
P: Ivo van Doorn
M: IvDoorn@gmail.com
L: netdev@vger.kernel.org
P: Johannes Berg
M: johannes@sipsolutions.net
L: linux-wireless@vger.kernel.org
S: Maintained
F Documentation/rfkill.txt
F: net/rfkill/
Expand Down
30 changes: 15 additions & 15 deletions arch/arm/mach-pxa/tosa-bt.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,25 @@ static void tosa_bt_off(struct tosa_bt_data *data)
gpio_set_value(data->gpio_reset, 0);
}

static int tosa_bt_toggle_radio(void *data, enum rfkill_state state)
static int tosa_bt_set_block(void *data, bool blocked)
{
pr_info("BT_RADIO going: %s\n",
state == RFKILL_STATE_UNBLOCKED ? "on" : "off");
pr_info("BT_RADIO going: %s\n", blocked ? "off" : "on");

if (state == RFKILL_STATE_UNBLOCKED) {
if (!blocked) {
pr_info("TOSA_BT: going ON\n");
tosa_bt_on(data);
} else {
pr_info("TOSA_BT: going OFF\n");
tosa_bt_off(data);
}

return 0;
}

static const struct rfkill_ops tosa_bt_rfkill_ops = {
.set_block = tosa_bt_set_block,
};

static int tosa_bt_probe(struct platform_device *dev)
{
int rc;
Expand All @@ -70,18 +74,14 @@ static int tosa_bt_probe(struct platform_device *dev)
if (rc)
goto err_pwr_dir;

rfk = rfkill_allocate(&dev->dev, RFKILL_TYPE_BLUETOOTH);
rfk = rfkill_alloc("tosa-bt", &dev->dev, RFKILL_TYPE_BLUETOOTH,
&tosa_bt_rfkill_ops, data);
if (!rfk) {
rc = -ENOMEM;
goto err_rfk_alloc;
}

rfk->name = "tosa-bt";
rfk->toggle_radio = tosa_bt_toggle_radio;
rfk->data = data;
#ifdef CONFIG_RFKILL_LEDS
rfk->led_trigger.name = "tosa-bt";
#endif
rfkill_set_led_trigger_name(rfk, "tosa-bt");

rc = rfkill_register(rfk);
if (rc)
Expand All @@ -92,9 +92,7 @@ static int tosa_bt_probe(struct platform_device *dev)
return 0;

err_rfkill:
if (rfk)
rfkill_free(rfk);
rfk = NULL;
rfkill_destroy(rfk);
err_rfk_alloc:
tosa_bt_off(data);
err_pwr_dir:
Expand All @@ -113,8 +111,10 @@ static int __devexit tosa_bt_remove(struct platform_device *dev)

platform_set_drvdata(dev, NULL);

if (rfk)
if (rfk) {
rfkill_unregister(rfk);
rfkill_destroy(rfk);
}
rfk = NULL;

tosa_bt_off(data);
Expand Down
1 change: 0 additions & 1 deletion arch/arm/mach-pxa/tosa.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
#include <linux/input.h>
#include <linux/gpio.h>
#include <linux/pda_power.h>
#include <linux/rfkill.h>
#include <linux/spi/spi.h>

#include <asm/setup.h>
Expand Down
42 changes: 22 additions & 20 deletions drivers/net/usb/hso.c
Original file line number Diff line number Diff line change
Expand Up @@ -2481,10 +2481,10 @@ static int add_net_device(struct hso_device *hso_dev)
return 0;
}

static int hso_radio_toggle(void *data, enum rfkill_state state)
static int hso_rfkill_set_block(void *data, bool blocked)
{
struct hso_device *hso_dev = data;
int enabled = (state == RFKILL_STATE_UNBLOCKED);
int enabled = !blocked;
int rv;

mutex_lock(&hso_dev->mutex);
Expand All @@ -2498,6 +2498,10 @@ static int hso_radio_toggle(void *data, enum rfkill_state state)
return rv;
}

static const struct rfkill_ops hso_rfkill_ops = {
.set_block = hso_rfkill_set_block,
};

/* Creates and sets up everything for rfkill */
static void hso_create_rfkill(struct hso_device *hso_dev,
struct usb_interface *interface)
Expand All @@ -2506,29 +2510,25 @@ static void hso_create_rfkill(struct hso_device *hso_dev,
struct device *dev = &hso_net->net->dev;
char *rfkn;

hso_net->rfkill = rfkill_allocate(&interface_to_usbdev(interface)->dev,
RFKILL_TYPE_WWAN);
if (!hso_net->rfkill) {
dev_err(dev, "%s - Out of memory\n", __func__);
return;
}
rfkn = kzalloc(20, GFP_KERNEL);
if (!rfkn) {
rfkill_free(hso_net->rfkill);
hso_net->rfkill = NULL;
if (!rfkn)
dev_err(dev, "%s - Out of memory\n", __func__);
return;
}

snprintf(rfkn, 20, "hso-%d",
interface->altsetting->desc.bInterfaceNumber);
hso_net->rfkill->name = rfkn;
hso_net->rfkill->state = RFKILL_STATE_UNBLOCKED;
hso_net->rfkill->data = hso_dev;
hso_net->rfkill->toggle_radio = hso_radio_toggle;

hso_net->rfkill = rfkill_alloc(rfkn,
&interface_to_usbdev(interface)->dev,
RFKILL_TYPE_WWAN,
&hso_rfkill_ops, hso_dev);
if (!hso_net->rfkill) {
dev_err(dev, "%s - Out of memory\n", __func__);
kfree(rfkn);
return;
}
if (rfkill_register(hso_net->rfkill) < 0) {
rfkill_destroy(hso_net->rfkill);
kfree(rfkn);
hso_net->rfkill->name = NULL;
rfkill_free(hso_net->rfkill);
hso_net->rfkill = NULL;
dev_err(dev, "%s - Failed to register rfkill\n", __func__);
return;
Expand Down Expand Up @@ -3165,8 +3165,10 @@ static void hso_free_interface(struct usb_interface *interface)
hso_stop_net_device(network_table[i]);
cancel_work_sync(&network_table[i]->async_put_intf);
cancel_work_sync(&network_table[i]->async_get_intf);
if (rfk)
if (rfk) {
rfkill_unregister(rfk);
rfkill_destroy(rfk);
}
hso_free_net_device(network_table[i]);
}
}
Expand Down
7 changes: 1 addition & 6 deletions drivers/net/wireless/ath/ath9k/ath9k.h
Original file line number Diff line number Diff line change
Expand Up @@ -460,12 +460,9 @@ struct ath_led {
bool registered;
};

/* Rfkill */
#define ATH_RFKILL_POLL_INTERVAL 2000 /* msecs */

struct ath_rfkill {
struct rfkill *rfkill;
struct delayed_work rfkill_poll;
struct rfkill_ops ops;
char rfkill_name[32];
};

Expand Down Expand Up @@ -509,8 +506,6 @@ struct ath_rfkill {
#define SC_OP_RXFLUSH BIT(7)
#define SC_OP_LED_ASSOCIATED BIT(8)
#define SC_OP_RFKILL_REGISTERED BIT(9)
#define SC_OP_RFKILL_SW_BLOCKED BIT(10)
#define SC_OP_RFKILL_HW_BLOCKED BIT(11)
#define SC_OP_WAIT_FOR_BEACON BIT(12)
#define SC_OP_LED_ON BIT(13)
#define SC_OP_SCANNING BIT(14)
Expand Down
115 changes: 29 additions & 86 deletions drivers/net/wireless/ath/ath9k/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1192,120 +1192,69 @@ static bool ath_is_rfkill_set(struct ath_softc *sc)
ah->rfkill_polarity;
}

/* h/w rfkill poll function */
static void ath_rfkill_poll(struct work_struct *work)
/* s/w rfkill handlers */
static int ath_rfkill_set_block(void *data, bool blocked)
{
struct ath_softc *sc = container_of(work, struct ath_softc,
rf_kill.rfkill_poll.work);
bool radio_on;

if (sc->sc_flags & SC_OP_INVALID)
return;

radio_on = !ath_is_rfkill_set(sc);

/*
* enable/disable radio only when there is a
* state change in RF switch
*/
if (radio_on == !!(sc->sc_flags & SC_OP_RFKILL_HW_BLOCKED)) {
enum rfkill_state state;

if (sc->sc_flags & SC_OP_RFKILL_SW_BLOCKED) {
state = radio_on ? RFKILL_STATE_SOFT_BLOCKED
: RFKILL_STATE_HARD_BLOCKED;
} else if (radio_on) {
ath_radio_enable(sc);
state = RFKILL_STATE_UNBLOCKED;
} else {
ath_radio_disable(sc);
state = RFKILL_STATE_HARD_BLOCKED;
}

if (state == RFKILL_STATE_HARD_BLOCKED)
sc->sc_flags |= SC_OP_RFKILL_HW_BLOCKED;
else
sc->sc_flags &= ~SC_OP_RFKILL_HW_BLOCKED;
struct ath_softc *sc = data;

rfkill_force_state(sc->rf_kill.rfkill, state);
}
if (blocked)
ath_radio_disable(sc);
else
ath_radio_enable(sc);

queue_delayed_work(sc->hw->workqueue, &sc->rf_kill.rfkill_poll,
msecs_to_jiffies(ATH_RFKILL_POLL_INTERVAL));
return 0;
}

/* s/w rfkill handler */
static int ath_sw_toggle_radio(void *data, enum rfkill_state state)
static void ath_rfkill_poll_state(struct rfkill *rfkill, void *data)
{
struct ath_softc *sc = data;
bool blocked = !!ath_is_rfkill_set(sc);

switch (state) {
case RFKILL_STATE_SOFT_BLOCKED:
if (!(sc->sc_flags & (SC_OP_RFKILL_HW_BLOCKED |
SC_OP_RFKILL_SW_BLOCKED)))
ath_radio_disable(sc);
sc->sc_flags |= SC_OP_RFKILL_SW_BLOCKED;
return 0;
case RFKILL_STATE_UNBLOCKED:
if ((sc->sc_flags & SC_OP_RFKILL_SW_BLOCKED)) {
sc->sc_flags &= ~SC_OP_RFKILL_SW_BLOCKED;
if (sc->sc_flags & SC_OP_RFKILL_HW_BLOCKED) {
DPRINTF(sc, ATH_DBG_FATAL, "Can't turn on the"
"radio as it is disabled by h/w\n");
return -EPERM;
}
ath_radio_enable(sc);
}
return 0;
default:
return -EINVAL;
}
if (rfkill_set_hw_state(rfkill, blocked))
ath_radio_disable(sc);
else
ath_radio_enable(sc);
}

/* Init s/w rfkill */
static int ath_init_sw_rfkill(struct ath_softc *sc)
{
sc->rf_kill.rfkill = rfkill_allocate(wiphy_dev(sc->hw->wiphy),
RFKILL_TYPE_WLAN);
sc->rf_kill.ops.set_block = ath_rfkill_set_block;
if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT)
sc->rf_kill.ops.poll = ath_rfkill_poll_state;

snprintf(sc->rf_kill.rfkill_name, sizeof(sc->rf_kill.rfkill_name),
"ath9k-%s::rfkill", wiphy_name(sc->hw->wiphy));

sc->rf_kill.rfkill = rfkill_alloc(sc->rf_kill.rfkill_name,
wiphy_dev(sc->hw->wiphy),
RFKILL_TYPE_WLAN,
&sc->rf_kill.ops, sc);
if (!sc->rf_kill.rfkill) {
DPRINTF(sc, ATH_DBG_FATAL, "Failed to allocate rfkill\n");
return -ENOMEM;
}

snprintf(sc->rf_kill.rfkill_name, sizeof(sc->rf_kill.rfkill_name),
"ath9k-%s::rfkill", wiphy_name(sc->hw->wiphy));
sc->rf_kill.rfkill->name = sc->rf_kill.rfkill_name;
sc->rf_kill.rfkill->data = sc;
sc->rf_kill.rfkill->toggle_radio = ath_sw_toggle_radio;
sc->rf_kill.rfkill->state = RFKILL_STATE_UNBLOCKED;

return 0;
}

/* Deinitialize rfkill */
static void ath_deinit_rfkill(struct ath_softc *sc)
{
if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT)
cancel_delayed_work_sync(&sc->rf_kill.rfkill_poll);

if (sc->sc_flags & SC_OP_RFKILL_REGISTERED) {
rfkill_unregister(sc->rf_kill.rfkill);
rfkill_destroy(sc->rf_kill.rfkill);
sc->sc_flags &= ~SC_OP_RFKILL_REGISTERED;
sc->rf_kill.rfkill = NULL;
}
}

static int ath_start_rfkill_poll(struct ath_softc *sc)
{
if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT)
queue_delayed_work(sc->hw->workqueue,
&sc->rf_kill.rfkill_poll, 0);

if (!(sc->sc_flags & SC_OP_RFKILL_REGISTERED)) {
if (rfkill_register(sc->rf_kill.rfkill)) {
DPRINTF(sc, ATH_DBG_FATAL,
"Unable to register rfkill\n");
rfkill_free(sc->rf_kill.rfkill);
rfkill_destroy(sc->rf_kill.rfkill);

/* Deinitialize the device */
ath_cleanup(sc);
Expand Down Expand Up @@ -1678,10 +1627,6 @@ int ath_attach(u16 devid, struct ath_softc *sc)
goto error_attach;

#if defined(CONFIG_RFKILL) || defined(CONFIG_RFKILL_MODULE)
/* Initialze h/w Rfkill */
if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT)
INIT_DELAYED_WORK(&sc->rf_kill.rfkill_poll, ath_rfkill_poll);

/* Initialize s/w rfkill */
error = ath_init_sw_rfkill(sc);
if (error)
Expand Down Expand Up @@ -2214,10 +2159,8 @@ static void ath9k_stop(struct ieee80211_hw *hw)
} else
sc->rx.rxlink = NULL;

#if defined(CONFIG_RFKILL) || defined(CONFIG_RFKILL_MODULE)
if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT)
cancel_delayed_work_sync(&sc->rf_kill.rfkill_poll);
#endif
rfkill_pause_polling(sc->rf_kill.rfkill);

/* disable HAL and put h/w to sleep */
ath9k_hw_disable(sc->sc_ah);
ath9k_hw_configpcipowersave(sc->sc_ah, 1);
Expand Down
15 changes: 0 additions & 15 deletions drivers/net/wireless/ath/ath9k/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,6 @@ static int ath_pci_suspend(struct pci_dev *pdev, pm_message_t state)

ath9k_hw_set_gpio(sc->sc_ah, ATH_LED_PIN, 1);

#if defined(CONFIG_RFKILL) || defined(CONFIG_RFKILL_MODULE)
if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT)
cancel_delayed_work_sync(&sc->rf_kill.rfkill_poll);
#endif

pci_save_state(pdev);
pci_disable_device(pdev);
pci_set_power_state(pdev, PCI_D3hot);
Expand All @@ -256,16 +251,6 @@ static int ath_pci_resume(struct pci_dev *pdev)
AR_GPIO_OUTPUT_MUX_AS_OUTPUT);
ath9k_hw_set_gpio(sc->sc_ah, ATH_LED_PIN, 1);

#if defined(CONFIG_RFKILL) || defined(CONFIG_RFKILL_MODULE)
/*
* check the h/w rfkill state on resume
* and start the rfkill poll timer
*/
if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT)
queue_delayed_work(sc->hw->workqueue,
&sc->rf_kill.rfkill_poll, 0);
#endif

return 0;
}

Expand Down
Loading

0 comments on commit 19d337d

Please sign in to comment.