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 
19 struct rfkill_event {
20 	u32 idx;
21 	u8 type;
22 	u8 op;
23 	u8 soft;
24 	u8 hard;
25 } STRUCT_PACKED;
26 
27 enum rfkill_operation {
28 	RFKILL_OP_ADD = 0,
29 	RFKILL_OP_DEL,
30 	RFKILL_OP_CHANGE,
31 	RFKILL_OP_CHANGE_ALL,
32 };
33 
34 enum 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 
47 struct rfkill_data {
48 	struct rfkill_config *cfg;
49 	int fd;
50 	int blocked;
51 	uint32_t idx;
52 };
53 
54 
55 static 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 
103 struct 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 
193 fail2:
194 	close(rfkill->fd);
195 fail:
196 	os_free(rfkill);
197 	/* use standard free function to match realpath() */
198 	free(phy);
199 	return NULL;
200 }
201 
202 
203 void 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 
218 int rfkill_is_blocked(struct rfkill_data *rfkill)
219 {
220 	if (rfkill == NULL)
221 		return 0;
222 
223 	return rfkill->blocked;
224 }
225