forked from clopez/dellfan
-
Notifications
You must be signed in to change notification settings - Fork 1
/
dellfan.c
266 lines (230 loc) · 7.98 KB
/
dellfan.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/*
* dellfan - user space utility to control the fan speed on Dell Laptops.
*
* SMM Management code from i8k. See file drivers/char/i8k.c at Linux kernel.
*
* Copyright (C) 2001 Massimo Dal Zotto <[email protected]>
* Copyright (C) 2014 Carlos Alberto Lopez Perez <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/io.h>
/* This is some information about the codes for different functions on the Dell SMBIOS */
/*
http://brouille.tuxfamily.org/junk/fan.c
cmd function (thanks to Andrew Paprocki)
0x00a3 get current speed indicator of a fan (args: fan)
0x01a3 set speed of a fan (args: fan & speed)
0x02a3 get RPM of a fan (args: fan)
0x03a3 ??? (1 byte)
0x04a3 get nominal fan speed (2 args)
0x05a3 get fan tolerance speed (2 args)
0x10a3 get sensor temperature (1 arg: sensor#)
0x11a3 ???
0x12a3 arg 0x0003=NBSVC-Query
arg 0x0000=NBSVC-Clear
arg 0x122=NBSVC-Start Trend
arg 0x0100=NBSVC-Stop Trend
arg 0x02??=NBSVC-Read
0x21a3 ??? (2 args: 1 byte (oder 0x16) + 1 byte)
0x22a3 get charger info (1 arg)
0x23a3 ??? (4 args: 2x 1 byte, 1xword, 1xdword)
0x24a3 get adaptor info status (1 arg oder 0x03)
0x30a3 ??? (no args)
0x31a3 ??? (no args)
0x32a3 ??? (no args)
0x33a3 ??? (no args)
0x36a3 get hotkey scancode list (args see diags)
0x37a3 ??? (no args)
0x40a3 get docking state (no args)
0xf0a3 ??? (2 args)
0xfea3 check SMBIOS version (1 arg)
0xffa3 check SMBIOS interface (returns:"DELLDIAG")
*/
/* The codes for DISABLE_BIOS_* were obtained experimentally on a E6420 with the
* following algorithm:
* probing many codes in a loop
* putting the speed to maximum, sleeping some seconds, and checking the speed back.
* Check the function probecodes()
*/
#define DISABLE_BIOS_METHOD1 0x30a3
#define ENABLE_BIOS_METHOD1 0x31a3
#define DISABLE_BIOS_METHOD2 0x34a3
#define ENABLE_BIOS_METHOD2 0x35a3
#define ENABLE_FN 0x32a3
#define SET_FAN 0x01a3
#define GET_FAN 0x00a3
#define FN_STATUS 0x0025
int init_ioperm(void)
{
if (ioperm(0xb2, 4, 1))
perror("ioperm:");
if (ioperm(0x84, 4, 1))
perror("ioperm:");
}
struct smm_regs {
unsigned int eax;
unsigned int ebx __attribute__ ((packed));
unsigned int ecx __attribute__ ((packed));
unsigned int edx __attribute__ ((packed));
unsigned int esi __attribute__ ((packed));
unsigned int edi __attribute__ ((packed));
};
static int i8k_smm(struct smm_regs *regs)
{
int rc;
int eax = regs->eax;
asm volatile("pushq %%rax\n\t"
"movl 0(%%rax),%%edx\n\t"
"pushq %%rdx\n\t"
"movl 4(%%rax),%%ebx\n\t"
"movl 8(%%rax),%%ecx\n\t"
"movl 12(%%rax),%%edx\n\t"
"movl 16(%%rax),%%esi\n\t"
"movl 20(%%rax),%%edi\n\t"
"popq %%rax\n\t"
"out %%al,$0xb2\n\t"
"out %%al,$0x84\n\t"
"xchgq %%rax,(%%rsp)\n\t"
"movl %%ebx,4(%%rax)\n\t"
"movl %%ecx,8(%%rax)\n\t"
"movl %%edx,12(%%rax)\n\t"
"movl %%esi,16(%%rax)\n\t"
"movl %%edi,20(%%rax)\n\t"
"popq %%rdx\n\t"
"movl %%edx,0(%%rax)\n\t"
"pushfq\n\t"
"popq %%rax\n\t"
"andl $1,%%eax\n"
:"=a"(rc)
: "a"(regs)
: "%ebx", "%ecx", "%edx", "%esi", "%edi");
if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
return -1;
return 0;
}
int send(unsigned int cmd, unsigned int arg) {
struct smm_regs regs = { .eax = cmd, };
regs.ebx = arg;
i8k_smm(®s);
return regs.eax ;
}
/* sets the speed and returns the speed of the fan after that */
int set_speed(int speed) {
if ( speed == 0 ) {
send(SET_FAN,0x0000);
} else if ( speed == 1 ) {
send(SET_FAN,0x0100);
} else if ( speed == 2 ) {
send(SET_FAN,0x0200);
} else {
printf("Ignoring unknown speed: %d\n",speed);
}
// return the speed the fan has now
return send(GET_FAN,0);
}
int probecodes (void) {
printf("Please tune the startcode by editing the source code\n");
/* Remove the following exit call to enable this routine.
*
* *WARNING:* Proving for random codes in the SMBIOS management can
* cause unexpected behaviour (even crashes or data loss) on your machine.
*
* USE AT YOUR OWN RISK!!
*/
exit(EXIT_FAILURE);
/* If you want to test this fast, use startcode=0x30a0 (for example)
* and you should see that the code 0x30a3 is detected.
* If you want to test all the codes then use startcode=0x0000 but
* this will take a while.
*/
int startcode=0x0001;
int fanstatus, trycode;
// Set the speed to 2, sleep 3 seconds and get the speed of the fan
set_speed(2);
sleep(3);
fanstatus = send(GET_FAN,0);
if (fanstatus == 2 ) {
printf ("Your fan status is already set to full speed.\n"
"In order for this to work, please set it to auto (enable BIOS control)\n"
"And stop any process that is consuming CPU resources\n");
exit(EXIT_FAILURE);
}
for (trycode=startcode; trycode <= 0xFFFF; trycode++) {
printf ("Probing code: %#06x\n",trycode);
// Send the code
send(trycode,0);
// Set the speed to 2, sleep 3 seconds and get the speed of the fan
set_speed(2);
sleep(3);
fanstatus = send(GET_FAN,0);
// If the fan is still 2 this is because the previous code disabled
// the BIOS control of the fan.
// Please ensure that your system is idle when running this (otherwise
// the BIOS could set the fan to max speed to cool down your system
// because of the load)
if (fanstatus == 2 ) {
printf ("The code %#06x disabled the FAN control!!!\n",trycode);
printf ("Enabling BIOS control back...\n");
send(ENABLE_BIOS_METHOD1,0);
sleep(3);
fanstatus = send(GET_FAN,0);
if (fanstatus == 2 ) {
printf ("ERROR: Unable to bring BIOS control back.\n");
exit (EXIT_FAILURE);
}
}
}
}
int main(int argc, char **argv) {
int speed, disable;
unsigned int tries;
unsigned int xarg;
if (geteuid() != 0) {
printf("need root privileges\n");
exit(EXIT_FAILURE);
}
if (argc < 2) {
printf ("Use: %s speed [disable]\n",argv[0]);
printf ("\tspeed = {0,1,2}\n");
printf ("\t\t\t[Use speed=9 to probe for SMBIOS codes to disable the BIOS fan control.]\n");
printf ("\tdisable = {0,1}\n");
exit(EXIT_FAILURE);
}
speed = atoi(argv[1]);
init_ioperm();
if (speed == 9)
return probecodes();
if (argc > 2) {
disable = atoi(argv[2]);
if (disable == 1) {
send(DISABLE_BIOS_METHOD1,0);
printf ("BIOS CONTROL DISABLED\n");
}
else if (disable == 0) {
send(ENABLE_BIOS_METHOD1,0);
printf ("BIOS CONTROL ENABLED\n");
}
else {
printf ("Use 0 to enable bios control or 1 to disable it\n");
exit(EXIT_FAILURE);
}
}
printf ("Setting speed to: %d\n", speed );
printf ("Speed is now at: %d\n", set_speed(speed));
}