Attachment 'smcfancontrol.c'
Download 1 /*
2 * smcfancontrol.c : fan control daemon for Apple Intel Mac Pro
3 * based on cmp-daemon 0.21 from http://aur.archlinux.org/packages.php?ID=21391
4 *
5 * Copyright (C) 2009 Alexey Anikeenko
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <time.h>
30 #include <signal.h>
31 #include <string.h>
32 #include <syslog.h>
33
34 #define SYSFS_PATH_MAX 256
35 #define SPEED_STEP_MAX 20
36
37 static const char *PIDFILE = "/var/run/smcfancontrol.pid";
38 static const char *SMCDIR = "/sys/devices/platform/applesmc.768";
39 static const char *CORETEMP_PREFIX = "/sys/devices/platform/coretemp";
40
41 static const struct timespec smc_write_delay = { 0, 5000000 }; /* 5 ms delay between writes to smc controller */
42
43 /* Reference data for dual-Xeon_X5482 Mac Pro:
44 const int FANS_EFI_MIN[] = { 500, 800, 600, 600 };
45 const int FANS_EFI_MAX[] = { 2900, 2900, 2900, 2800 };
46 const char *FANS_LABEL[] = { "CPU_MEM", "IO", "EXHAUST", "PS" };
47 */
48
49 const int FANS[] = { 1, 2, 3 }; /* suffixes of fans under control */
50 const int FANS_MIN[] = { 800, 800, 600 };
51 const int FANS_MAX[] = { 2200, 2200, 2200 };
52 const int FANS_NUM = sizeof(FANS)/sizeof(FANS[0]);
53
54 const int SENSORS[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; /* suffixes of used coretemp sensors */
55 const int SENSORS_NUM = sizeof(SENSORS)/sizeof(SENSORS[0]);
56
57 static int get_sensors_temp(void);
58 static void set_fans_manual(int);
59 static void set_fans_min(int);
60
61
62 /*
63 * Fan speed depends linearly on the speed step and changes
64 * between FAN_MIN and FAN_MAX for steps in range [0, SPEED_STEP_MAX].
65 * This function converts current temperature to speed step
66 * and should be calibrated for your box!
67 */
68 int temperature_to_speed_step(int t)
69 {
70 int step = ((t-45)*SPEED_STEP_MAX)/(75-45);
71
72 if (step < 0 ) step = 0;
73 if (step > SPEED_STEP_MAX ) step = SPEED_STEP_MAX;
74
75 return step;
76 }
77
78
79 /* clean up and exit */
80 static void clean_exit_with_status(int status)
81 {
82 set_fans_manual(0); /* set fans to automatic control */
83 syslog(LOG_NOTICE, "exiting\n");
84 unlink(PIDFILE); /* remove pidfile */
85 exit(status);
86 }
87
88 /* signal handler for clean exit */
89 static void clean_exit(int sig __attribute__((unused)))
90 {
91 clean_exit_with_status(EXIT_SUCCESS);
92 }
93
94
95 /* set fanX_min (min rotation speed) according to current speed step */
96 static void set_fans_min(int speed_step)
97 {
98 int i;
99 FILE *file;
100 char fan_path[SYSFS_PATH_MAX];
101
102 /* for fans under control write rpm to fanX_min */
103 for(i=0; i < FANS_NUM; i++ )
104 {
105 /* fan speed depends linearly on the speed step and changes
106 * between FAN_MIN and FAN_MAX for steps in range [0, SPEED_STEP_MAX] */
107 int speed = FANS_MIN[i] + speed_step*(FANS_MAX[i]-FANS_MIN[i])/SPEED_STEP_MAX;
108
109 sprintf(fan_path, "%s/fan%d_min", SMCDIR, FANS[i]);
110 nanosleep(&smc_write_delay, NULL);
111 if ((file=fopen(fan_path, "w")) != NULL) {
112 fprintf(file, "%d", speed);
113 fclose(file);
114 }
115 else {
116 syslog(LOG_WARNING, "Error writing to %s, check if applesmc module loaded", fan_path);
117 }
118 }
119 }
120
121
122 /* write value to fanX_manual */
123 static void set_fans_manual(int value)
124 {
125 int i;
126 FILE *file;
127 char fan_path[SYSFS_PATH_MAX];
128
129 /* for fans under control write value to fanX_manual */
130 for(i=0; i < FANS_NUM; i++ )
131 {
132 sprintf(fan_path, "%s/fan%d_manual", SMCDIR, FANS[i]);
133 nanosleep(&smc_write_delay, NULL);
134 if ((file=fopen(fan_path, "w")) != NULL) {
135 fprintf(file, "%d", value);
136 fclose(file);
137 }
138 else {
139 syslog(LOG_WARNING, "Error writing to %s, check if applesmc module loaded", fan_path);
140 }
141 }
142 }
143
144
145 /* sort function for qsort: integers descending order */
146 static int compare_int_desc(const void *a, const void *b) {
147 int *da, *db;
148 da = (int *)a;
149 db = (int *)b;
150
151 return *db - *da;
152 }
153
154
155 /* return average of two largest temperatures, degrees */
156 static int get_sensors_temp(void)
157 {
158 int i;
159 int t[SENSORS_NUM];
160 char sensor_path[SYSFS_PATH_MAX];
161 FILE *file;
162
163 /* for all sensors read temp1_input */
164 for(i=0; i < SENSORS_NUM; i++ )
165 {
166 sprintf(sensor_path, "%s.%d/temp1_input", CORETEMP_PREFIX, SENSORS[i]);
167 if ((file=fopen(sensor_path, "r")) != NULL) {
168 fscanf(file, "%d", &(t[i])); /* read temperature in millidegrees */
169 fclose(file);
170 }
171 else {
172 syslog(LOG_ERR, "Error reading %s, check if coretemp module loaded and number of sensors", sensor_path);
173 clean_exit_with_status(EXIT_FAILURE);
174 }
175 }
176
177 /* sort temperature values, descending order */
178 qsort(t, SENSORS_NUM, sizeof(t[0]), compare_int_desc);
179
180 /* calculate average of two largest temps (if have more than one sensor) */
181 int avg = 0, cnt = 0;
182 for(i=0; (i < 2) && (i < SENSORS_NUM); i++) {
183 avg += t[i];
184 cnt ++;
185 }
186 avg = avg/cnt;
187
188 /* return temperature in degrees */
189 return avg/1000;
190 }
191
192
193 /* lock entire file */
194 static int lock_fd(int fd)
195 {
196 struct flock lock;
197
198 lock.l_type = F_WRLCK;
199 lock.l_start = 0;
200 lock.l_whence = SEEK_SET;
201 lock.l_len = 0;
202
203 return fcntl(fd, F_SETLK, &lock);
204 }
205
206
207 /* creates and locks pidfile */
208 static void create_pidfile(void)
209 {
210 int fd;
211 FILE *f;
212
213 /* open the pidfile */
214 fd = open(PIDFILE, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
215 if (fd < 0) {
216 syslog(LOG_ERR, "can't open %s: %s\n", PIDFILE, strerror(errno));
217 exit(EXIT_FAILURE);
218 }
219 /* lock pid file */
220 if (lock_fd(fd) < 0) {
221 if (errno == EACCES || errno == EAGAIN) {
222 syslog(LOG_ERR, "daemon already running");
223 } else {
224 syslog(LOG_ERR, "can't lock %s: %s", PIDFILE, strerror(errno));
225 }
226 exit(EXIT_FAILURE);
227 }
228
229 /* write pid */
230 ftruncate(fd, 0);
231 f = fdopen(fd, "w");
232 fprintf(f, "%d\n", getpid());
233 fflush(f);
234 /* keep file open and locked */
235 }
236
237
238 /* become a daemon and open log */
239 void daemonize(void)
240 {
241 int i, maxfd;
242 int fd0, fd1, fd2;
243 pid_t pid;
244
245 /* clear file creation mask */
246 umask(0);
247
248 /* fork and become a session leader */
249 if ((pid=fork()) < 0) {
250 fprintf(stderr, "fork failed\n");
251 exit(EXIT_FAILURE);
252 }
253 else if (pid != 0) { /* parent */
254 exit(EXIT_SUCCESS);
255 }
256 setsid();
257
258 /* change current working directory to the root */
259 if (chdir("/") < 0) {
260 fprintf(stderr, "can't change directory to /");
261 exit(EXIT_FAILURE);
262 }
263
264 /* close all open file descriptors */
265 maxfd = sysconf(_SC_OPEN_MAX);
266 for (i=0; i < maxfd; i++)
267 close(i);
268
269 /* set up stdin, stdout, stderr to /dev/null */
270 fd0 = open("/dev/null", O_RDWR);
271 fd1 = dup(fd0);
272 fd2 = dup(fd0);
273
274 /* open log */
275 openlog("smcfancontrol", LOG_PID, LOG_DAEMON);
276 if (fd0 != STDIN_FILENO || fd1 != STDOUT_FILENO || fd2 != STDERR_FILENO) {
277 syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
278 exit(EXIT_FAILURE);
279 }
280 }
281
282
283 int main(int argc, char *argv[])
284 {
285 struct timespec sleep_period; /* delay between sensor polls */
286 int t, t_old; /* current and previous temperature */
287 int speed_step, old_speed_step; /* current and old speed step */
288 int cold, hot; /* number of consecutive temperature (de/in)creases */
289
290 /* become a daemon */
291 daemonize();
292
293 syslog(LOG_INFO, "starting up\n");
294
295 /* trap key signals */
296 signal(SIGTERM, clean_exit);
297 signal(SIGQUIT, clean_exit);
298 signal(SIGINT, clean_exit);
299 signal(SIGHUP, SIG_IGN);
300
301 /* create pidfile and lock it */
302 create_pidfile();
303
304 /* set fans to automatic control */
305 set_fans_manual(0);
306
307 /* initial temperatures */
308 t = t_old = get_sensors_temp();
309 cold = hot = 1;
310
311 /* init speed step and set fans */
312 speed_step = temperature_to_speed_step(t);
313 set_fans_min(speed_step);
314 old_speed_step = speed_step;
315
316 /* set sleep timer to 0.5 sec */
317 sleep_period.tv_sec = 0;
318 sleep_period.tv_nsec = 500000000;
319
320 /* main loop */
321 while(1)
322 {
323 t = get_sensors_temp();
324
325 if ( t < t_old ) { /* it's getting colder */
326 cold++;
327 hot = 0;
328 }
329 if ( t > t_old ) { /* it's getting hotter */
330 hot++;
331 cold = 0;
332 }
333
334 if ( (cold==2) || (hot==2) )
335 {
336 speed_step = temperature_to_speed_step(t);
337
338 if ( speed_step != old_speed_step) {
339 set_fans_min(speed_step);
340 syslog(LOG_INFO, "changed to speed step %d (of %d) at temperature %d", speed_step, SPEED_STEP_MAX, t);
341 old_speed_step = speed_step;
342 }
343
344 cold = 0;
345 hot = 0;
346 }
347
348 if( nanosleep(&sleep_period, NULL) < 0 && errno != EINTR )
349 {
350 clean_exit_with_status(EXIT_FAILURE);
351 }
352
353 t_old = t;
354 }
355
356 clean_exit_with_status(EXIT_SUCCESS);
357
358 return 0;
359 }
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.