/*
* Linux rfkill helper functions for driver wrappers
* Copyright (c) 2010, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include <fcntl.h>
#include <limits.h>
#include "utils/common.h"
#include "utils/eloop.h"
#include "rfkill.h"
#define RFKILL_EVENT_SIZE_V1 8
struct rfkill_event {
u32 idx;
u8 type;
u8 op;
u8 soft;
u8 hard;
} STRUCT_PACKED;
enum rfkill_operation {
RFKILL_OP_ADD = 0,
RFKILL_OP_DEL,
RFKILL_OP_CHANGE,
RFKILL_OP_CHANGE_ALL,
};
enum rfkill_type {
RFKILL_TYPE_ALL = 0,
RFKILL_TYPE_WLAN,
RFKILL_TYPE_BLUETOOTH,
RFKILL_TYPE_UWB,
RFKILL_TYPE_WIMAX,
RFKILL_TYPE_WWAN,
RFKILL_TYPE_GPS,
RFKILL_TYPE_FM,
NUM_RFKILL_TYPES,
};
struct rfkill_data {
struct rfkill_config *cfg;
int fd;
int blocked;
uint32_t idx;
};
static void rfkill_receive(int sock, void *eloop_ctx, void *sock_ctx)
{
struct rfkill_data *rfkill = eloop_ctx;
struct rfkill_event event;
ssize_t len;
int new_blocked;
len = read(rfkill->fd, &event, sizeof(event));
if (len < 0) {
wpa_printf(MSG_ERROR, "rfkill: Event read failed: %s",
strerror(errno));
return;
}
if (len != RFKILL_EVENT_SIZE_V1) {
wpa_printf(MSG_DEBUG, "rfkill: Unexpected event size "
"%d (expected %d)",
(int) len, RFKILL_EVENT_SIZE_V1);
return;
}
if (event.op != RFKILL_OP_CHANGE || event.idx != rfkill->idx)
return;
wpa_printf(MSG_DEBUG, "rfkill: event: idx=%u type=%d "
"op=%u soft=%u hard=%u",
event.idx, event.type, event.op, event.soft,
event.hard);
if (event.hard) {
wpa_printf(MSG_INFO, "rfkill: WLAN hard blocked");
new_blocked = 1;
} else if (event.soft) {
wpa_printf(MSG_INFO, "rfkill: WLAN soft blocked");
new_blocked = 1;
} else {
wpa_printf(MSG_INFO, "rfkill: WLAN unblocked");
new_blocked = 0;
}
if (new_blocked != rfkill->blocked) {
rfkill->blocked = new_blocked;
if (new_blocked)
rfkill->cfg->blocked_cb(rfkill->cfg->ctx);
else
rfkill->cfg->unblocked_cb(rfkill->cfg->ctx);
}
}
struct rfkill_data * rfkill_init(struct rfkill_config *cfg)
{
struct rfkill_data *rfkill;
struct rfkill_event event;
ssize_t len;
char *phy = NULL, *rfk_phy;
char buf[24 + IFNAMSIZ + 1];
char buf2[31 + 11 + 1];
int found = 0;
rfkill = os_zalloc(sizeof(*rfkill));
if (rfkill == NULL)
return NULL;
os_snprintf(buf, sizeof(buf), "/sys/class/net/%s/phy80211",
cfg->ifname);
phy = realpath(buf, NULL);
if (!phy) {
wpa_printf(MSG_INFO, "rfkill: Cannot get wiphy information");
goto fail;
}
rfkill->cfg = cfg;
rfkill->fd = open("/dev/rfkill", O_RDONLY);
if (rfkill->fd < 0) {
wpa_printf(MSG_INFO, "rfkill: Cannot open RFKILL control "
"device");
goto fail;
}
if (fcntl(rfkill->fd, F_SETFL, O_NONBLOCK) < 0) {
wpa_printf(MSG_ERROR, "rfkill: Cannot set non-blocking mode: "
"%s", strerror(errno));
goto fail2;
}
for (;;) {
len = read(rfkill->fd, &event, sizeof(event));
if (len < 0) {
if (errno == EAGAIN)
break; /* No more entries */
wpa_printf(MSG_ERROR, "rfkill: Event read failed: %s",
strerror(errno));
break;
}
if (len != RFKILL_EVENT_SIZE_V1) {
wpa_printf(MSG_DEBUG, "rfkill: Unexpected event size "
"%d (expected %d)",
(int) len, RFKILL_EVENT_SIZE_V1);
continue;
}
if (event.op != RFKILL_OP_ADD ||
event.type != RFKILL_TYPE_WLAN)
continue;
os_snprintf(buf2, sizeof(buf2),
"/sys/class/rfkill/rfkill%d/device", event.idx);
rfk_phy = realpath(buf2, NULL);
if (!rfk_phy)
goto fail2;
found = os_strcmp(phy, rfk_phy) == 0;
free(rfk_phy);
if (!found)
continue;
wpa_printf(MSG_DEBUG, "rfkill: initial event: idx=%u type=%d "
"op=%u soft=%u hard=%u",
event.idx, event.type, event.op, event.soft,
event.hard);
rfkill->idx = event.idx;
if (event.hard) {
wpa_printf(MSG_INFO, "rfkill: WLAN hard blocked");
rfkill->blocked = 1;
} else if (event.soft) {
wpa_printf(MSG_INFO, "rfkill: WLAN soft blocked");
rfkill->blocked = 1;
}
break;
}
if (!found)
goto fail2;
free(phy);
eloop_register_read_sock(rfkill->fd, rfkill_receive, rfkill, NULL);
return rfkill;
fail2:
close(rfkill->fd);
fail:
os_free(rfkill);
/* use standard free function to match realpath() */
free(phy);
return NULL;
}
void rfkill_deinit(struct rfkill_data *rfkill)
{
if (rfkill == NULL)
return;
if (rfkill->fd >= 0) {
eloop_unregister_read_sock(rfkill->fd);
close(rfkill->fd);
}
os_free(rfkill->cfg);
os_free(rfkill);
}
int rfkill_is_blocked(struct rfkill_data *rfkill)
{
if (rfkill == NULL)
return 0;
return rfkill->blocked;
}