1/*
2 * Linux rfkill helper functions for driver wrappers
3 * Copyright (c) 2010, Jouni Malinen <j@w1.fi>
4 *
5 * This software may be distributed under the terms of the BSD license.
6 * See README for more details.
7 */
8
9#include "includes.h"
10#include <fcntl.h>
11#include <limits.h>
12
13#include "utils/common.h"
14#include "utils/eloop.h"
15#include "rfkill.h"
16
17#define RFKILL_EVENT_SIZE_V1 8
18
19struct rfkill_event {
20	u32 idx;
21	u8 type;
22	u8 op;
23	u8 soft;
24	u8 hard;
25} STRUCT_PACKED;
26
27enum rfkill_operation {
28	RFKILL_OP_ADD = 0,
29	RFKILL_OP_DEL,
30	RFKILL_OP_CHANGE,
31	RFKILL_OP_CHANGE_ALL,
32};
33
34enum rfkill_type {
35	RFKILL_TYPE_ALL = 0,
36	RFKILL_TYPE_WLAN,
37	RFKILL_TYPE_BLUETOOTH,
38	RFKILL_TYPE_UWB,
39	RFKILL_TYPE_WIMAX,
40	RFKILL_TYPE_WWAN,
41	RFKILL_TYPE_GPS,
42	RFKILL_TYPE_FM,
43	NUM_RFKILL_TYPES,
44};
45
46
47struct rfkill_data {
48	struct rfkill_config *cfg;
49	int fd;
50	int blocked;
51	uint32_t idx;
52};
53
54
55static void rfkill_receive(int sock, void *eloop_ctx, void *sock_ctx)
56{
57	struct rfkill_data *rfkill = eloop_ctx;
58	struct rfkill_event event;
59	ssize_t len;
60	int new_blocked;
61
62	len = read(rfkill->fd, &event, sizeof(event));
63	if (len < 0) {
64		wpa_printf(MSG_ERROR, "rfkill: Event read failed: %s",
65			   strerror(errno));
66		return;
67	}
68	if (len != RFKILL_EVENT_SIZE_V1) {
69		wpa_printf(MSG_DEBUG, "rfkill: Unexpected event size "
70			   "%d (expected %d)",
71			   (int) len, RFKILL_EVENT_SIZE_V1);
72		return;
73	}
74	if (event.op != RFKILL_OP_CHANGE || event.idx != rfkill->idx)
75		return;
76
77	wpa_printf(MSG_DEBUG, "rfkill: event: idx=%u type=%d "
78		   "op=%u soft=%u hard=%u",
79		   event.idx, event.type, event.op, event.soft,
80		   event.hard);
81
82	if (event.hard) {
83		wpa_printf(MSG_INFO, "rfkill: WLAN hard blocked");
84		new_blocked = 1;
85	} else if (event.soft) {
86		wpa_printf(MSG_INFO, "rfkill: WLAN soft blocked");
87		new_blocked = 1;
88	} else {
89		wpa_printf(MSG_INFO, "rfkill: WLAN unblocked");
90		new_blocked = 0;
91	}
92
93	if (new_blocked != rfkill->blocked) {
94		rfkill->blocked = new_blocked;
95		if (new_blocked)
96			rfkill->cfg->blocked_cb(rfkill->cfg->ctx);
97		else
98			rfkill->cfg->unblocked_cb(rfkill->cfg->ctx);
99	}
100}
101
102
103struct rfkill_data * rfkill_init(struct rfkill_config *cfg)
104{
105	struct rfkill_data *rfkill;
106	struct rfkill_event event;
107	ssize_t len;
108	char *phy = NULL, *rfk_phy;
109	char buf[24 + IFNAMSIZ + 1];
110	char buf2[31 + 11 + 1];
111	int found = 0;
112
113	rfkill = os_zalloc(sizeof(*rfkill));
114	if (rfkill == NULL)
115		return NULL;
116
117	os_snprintf(buf, sizeof(buf), "/sys/class/net/%s/phy80211",
118		    cfg->ifname);
119	phy = realpath(buf, NULL);
120	if (!phy) {
121		wpa_printf(MSG_INFO, "rfkill: Cannot get wiphy information");
122		goto fail;
123	}
124
125	rfkill->cfg = cfg;
126	rfkill->fd = open("/dev/rfkill", O_RDONLY);
127	if (rfkill->fd < 0) {
128		wpa_printf(MSG_INFO, "rfkill: Cannot open RFKILL control "
129			   "device");
130		goto fail;
131	}
132
133	if (fcntl(rfkill->fd, F_SETFL, O_NONBLOCK) < 0) {
134		wpa_printf(MSG_ERROR, "rfkill: Cannot set non-blocking mode: "
135			   "%s", strerror(errno));
136		goto fail2;
137	}
138
139	for (;;) {
140		len = read(rfkill->fd, &event, sizeof(event));
141		if (len < 0) {
142			if (errno == EAGAIN)
143				break; /* No more entries */
144			wpa_printf(MSG_ERROR, "rfkill: Event read failed: %s",
145				   strerror(errno));
146			break;
147		}
148		if (len != RFKILL_EVENT_SIZE_V1) {
149			wpa_printf(MSG_DEBUG, "rfkill: Unexpected event size "
150				   "%d (expected %d)",
151				   (int) len, RFKILL_EVENT_SIZE_V1);
152			continue;
153		}
154		if (event.op != RFKILL_OP_ADD ||
155		    event.type != RFKILL_TYPE_WLAN)
156			continue;
157
158		os_snprintf(buf2, sizeof(buf2),
159			    "/sys/class/rfkill/rfkill%d/device", event.idx);
160		rfk_phy = realpath(buf2, NULL);
161		if (!rfk_phy)
162			goto fail2;
163		found = os_strcmp(phy, rfk_phy) == 0;
164		free(rfk_phy);
165
166		if (!found)
167			continue;
168
169		wpa_printf(MSG_DEBUG, "rfkill: initial event: idx=%u type=%d "
170			   "op=%u soft=%u hard=%u",
171			   event.idx, event.type, event.op, event.soft,
172			   event.hard);
173
174		rfkill->idx = event.idx;
175		if (event.hard) {
176			wpa_printf(MSG_INFO, "rfkill: WLAN hard blocked");
177			rfkill->blocked = 1;
178		} else if (event.soft) {
179			wpa_printf(MSG_INFO, "rfkill: WLAN soft blocked");
180			rfkill->blocked = 1;
181		}
182		break;
183	}
184
185	if (!found)
186		goto fail2;
187
188	free(phy);
189	eloop_register_read_sock(rfkill->fd, rfkill_receive, rfkill, NULL);
190
191	return rfkill;
192
193fail2:
194	close(rfkill->fd);
195fail:
196	os_free(rfkill);
197	/* use standard free function to match realpath() */
198	free(phy);
199	return NULL;
200}
201
202
203void rfkill_deinit(struct rfkill_data *rfkill)
204{
205	if (rfkill == NULL)
206		return;
207
208	if (rfkill->fd >= 0) {
209		eloop_unregister_read_sock(rfkill->fd);
210		close(rfkill->fd);
211	}
212
213	os_free(rfkill->cfg);
214	os_free(rfkill);
215}
216
217
218int rfkill_is_blocked(struct rfkill_data *rfkill)
219{
220	if (rfkill == NULL)
221		return 0;
222
223	return rfkill->blocked;
224}
225