1/*
2 * Copyright 2007, Intel Corporation
3 *
4 * This file is part of PowerTOP
5 *
6 * This program file is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; version 2 of the License.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13 * for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program in a file named COPYING; if not, write to the
17 * Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301 USA
20 *
21 * Authors:
22 * 	Arjan van de Ven <arjan@linux.intel.com>
23 */
24
25#include <unistd.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <stdint.h>
30#include <sys/types.h>
31#include <dirent.h>
32#include <linux/types.h>
33#include <net/if.h>
34#include <linux/sockios.h>
35#include <sys/ioctl.h>
36
37/* work around a bug in debian -- it exposes kernel internal types to userspace */
38#define u64 __u64
39#define u32 __u32
40#define u16 __u16
41#define u8 __u8
42#include <linux/ethtool.h>
43#undef u64
44#undef u32
45#undef u16
46#undef u8
47
48
49
50#include "powertop.h"
51
52
53static char wireless_nic[32];
54static char rfkill_path[PATH_MAX];
55static char powersave_path[PATH_MAX];
56
57static int rfkill_enabled(void)
58{
59	FILE *file;
60	char val;
61	if (strlen(rfkill_path)<2)
62		return 0;
63	if (access(rfkill_path, W_OK))
64		return 0;
65
66	file = fopen(rfkill_path, "r");
67	if (!file)
68		return 0;
69	val = fgetc(file);
70	fclose(file);
71	if (val != '0') /* already rfkill'd */
72		return 1;
73	return 0;
74}
75
76int check_unused_wiresless_up(void)
77{
78	FILE *file;
79	char val;
80	char line[1024];
81	if (strlen(rfkill_path)<2)
82		return 0;
83	if (access(rfkill_path, W_OK))
84		return 0;
85
86	file = fopen(rfkill_path, "r");
87	if (!file)
88		return 0;
89	val = fgetc(file);
90	fclose(file);
91	if (val != '0') /* already rfkill'd */
92		return -1;
93
94	sprintf(line,"iwconfig %s 2> /dev/null", wireless_nic);
95	file = popen(line, "r");
96	if (!file)
97		return 0;
98	while (!feof(file)) {
99		memset(line, 0, 1024);
100		if (fgets(line, 1023, file) == 0)
101			break;
102		if (strstr(line, "Mode:Managed") && strstr(line,"Access Point: Not-Associated")) {
103			pclose(file);
104			return 1;
105		}
106	}
107	pclose(file);
108	return 0;
109}
110
111
112static int need_wireless_suggest(char *iface)
113{
114	FILE *file;
115	char line[1024];
116	int ret = 0;
117
118	if (rfkill_enabled())
119		return 0;
120
121	sprintf(line, "/sbin/iwpriv %s get_power 2> /dev/null", iface);
122	file = popen(line, "r");
123	if (!file)
124		return 0;
125	while (!feof(file)) {
126		memset(line, 0, 1024);
127		if (fgets(line, 1023, file)==NULL)
128			break;
129		if (strstr(line, "Power save level: 6 (AC)")) {
130			ret = 1;
131			break;
132		}
133	}
134	pclose(file);
135	return ret;
136}
137
138
139static int need_wireless_suggest_new(void)
140{
141	FILE *file;
142	char val;
143	if (strlen(powersave_path)<2)
144		return 0;
145	if (access(powersave_path, W_OK))
146		return 0;
147
148	if (rfkill_enabled())
149		return 0;
150
151	file = fopen(powersave_path, "r");
152	if (!file)
153		return 0;
154	val = fgetc(file);
155	fclose(file);
156	if (val <= '5' && val >= '0') /* already in powersave */
157		return 0;
158
159	return 1;
160}
161
162void find_4965(void)
163{
164	static int tried_4965 = 0;
165	DIR *dir;
166	struct dirent *dirent;
167	char pathname[PATH_MAX];
168
169	if (tried_4965++)
170		return;
171
172	dir = opendir("/sys/bus/pci/drivers/iwl4965");
173	while (dir && (dirent = readdir(dir))) {
174		if (dirent->d_name[0]=='.')
175			continue;
176		sprintf(pathname, "/sys/bus/pci/drivers/iwl4965/%s/power_level", dirent->d_name);
177		if (!access(pathname, W_OK))
178			strcpy(powersave_path, pathname);
179	}
180	if (dir)
181		closedir(dir);
182	dir = opendir("/sys/bus/pci/drivers/iwl3945");
183	if (!dir)
184		return;
185	while ((dirent = readdir(dir))) {
186		if (dirent->d_name[0]=='.')
187			continue;
188		sprintf(pathname, "/sys/bus/pci/drivers/iwl3945/%s/power_level", dirent->d_name);
189		if (!access(pathname, W_OK))
190			strcpy(powersave_path, pathname);
191	}
192
193	closedir(dir);
194
195}
196
197
198void find_wireless_nic(void)
199{
200	static int found = 0;
201	FILE *file;
202	int sock;
203	struct ifreq ifr;
204	struct ethtool_value ethtool;
205	struct ethtool_drvinfo driver;
206	int ifaceup = 0;
207	int ret;
208
209	if (found++)
210		return;
211
212	wireless_nic[0] = 0;
213	rfkill_path[0] = 0;
214	powersave_path[0] = 0;
215
216	strcpy(wireless_nic, "wlan0");
217
218	file = popen("/sbin/iwpriv -a 2> /dev/null", "r");
219	if (!file)
220		return;
221	while (!feof(file)) {
222		char line[1024];
223		memset(line, 0, 1024);
224		if (fgets(line, 1023, file)==NULL)
225			break;
226		if (strstr(line, "get_power:Power save level")) {
227			char *c;
228			c = strchr(line, ' ');
229			if (c) *c = 0;
230			strcpy(wireless_nic, line);
231		}
232		if (strstr(line, "wlan0:"))
233			strcpy(wireless_nic, "wlan0");
234	}
235	pclose(file);
236
237
238	if (strlen(wireless_nic)==0)
239		return;
240
241
242	memset(&ifr, 0, sizeof(struct ifreq));
243	memset(&ethtool, 0, sizeof(struct ethtool_value));
244
245	sock = socket(AF_INET, SOCK_DGRAM, 0);
246	if (sock<0)
247		return;
248
249	strcpy(ifr.ifr_name, wireless_nic);
250
251	/* Check if the interface is up */
252	ret = ioctl(sock, SIOCGIFFLAGS, &ifr);
253	if (ret<0) {
254		close(sock);
255		return;
256	}
257
258	ifaceup = 0;
259	if (ifr.ifr_flags & (IFF_UP | IFF_RUNNING))
260		ifaceup = 1;
261
262	memset(&driver, 0, sizeof(driver));
263	driver.cmd = ETHTOOL_GDRVINFO;
264        ifr.ifr_data = (void*) &driver;
265        ret = ioctl(sock, SIOCETHTOOL, &ifr);
266
267	sprintf(rfkill_path,"/sys/bus/pci/devices/%s/rfkill/rfkill0/state", driver.bus_info);
268	sprintf(powersave_path,"/sys/bus/pci/devices/%s/power_level", driver.bus_info);
269	close(sock);
270}
271
272void activate_wireless_suggestion(void)
273{
274	char line[1024];
275	sprintf(line, "/sbin/iwpriv %s set_power 5 2> /dev/null", wireless_nic);
276	system(line);
277}
278void activate_wireless_suggestion_new(void)
279{
280	FILE *file;
281	file = fopen(powersave_path, "w");
282	if (!file)
283		return;
284	fprintf(file,"1\n");
285	fclose(file);
286}
287
288void activate_rfkill_suggestion(void)
289{
290	FILE *file;
291	file = fopen(rfkill_path, "w");
292	if (!file)
293		return;
294	fprintf(file,"1\n");
295	fclose(file);
296}
297void suggest_wireless_powersave(void)
298{
299	char sug[1024];
300	int ret;
301
302	if (strlen(wireless_nic)==0)
303		find_wireless_nic();
304	find_4965();
305	ret = check_unused_wiresless_up();
306
307	if (ret >= 0 && need_wireless_suggest(wireless_nic)) {
308		sprintf(sug, _("Suggestion: Enable wireless power saving mode by executing the following command:\n "
309			       " iwpriv %s set_power 5 \n"
310			       "This will sacrifice network performance slightly to save power."), wireless_nic);
311		add_suggestion(sug, 20, 'W', _(" W - Enable wireless power saving "), activate_wireless_suggestion);
312	}
313	if (ret >= 0 && need_wireless_suggest_new()) {
314		sprintf(sug, _("Suggestion: Enable wireless power saving mode by executing the following command:\n "
315			       " echo 5 > %s \n"
316			       "This will sacrifice network performance slightly to save power."), powersave_path);
317		add_suggestion(sug, 20, 'W', _(" W - Enable wireless power saving "), activate_wireless_suggestion_new);
318	}
319	if (ret>0) {
320		sprintf(sug, _("Suggestion: Disable the unused WIFI radio by executing the following command:\n "
321			       " echo 1 > %s \n"), rfkill_path);
322		add_suggestion(sug, 60, 'I', _(" I - disable WIFI Radio "), activate_rfkill_suggestion);
323
324	}
325}
326