2 * hdapsd.c - Read from the HDAPS (Hard Drive Active Protection System)
3 * and protect the drive if motion over threshold...
5 * Derived from pivot.c by Robert Love.
7 * Copyright (C) 2005-2009 Jon Escombe <lists@dresco.co.uk>
8 * Robert Love <rml@novell.com>
9 * Shem Multinymous <multinymous@gmail.com>
10 * Elias Oltmanns <eo@nebensachen.de>
11 * Evgeni Golov <sargentd@die-welt.net>
13 * "Why does that kid keep dropping his laptop?"
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
41 #include <sys/utsname.h>
45 #include <linux/input.h>
48 #define PID_FILE "/var/run/hdapsd.pid"
49 #define SYSFS_POSITION_FILE "/sys/devices/platform/hdaps/position"
50 #define MOUSE_ACTIVITY_FILE "/sys/devices/platform/hdaps/keyboard_activity"
51 #define KEYBD_ACTIVITY_FILE "/sys/devices/platform/hdaps/mouse_activity"
52 #define SAMPLING_RATE_FILE "/sys/devices/platform/hdaps/sampling_rate"
53 #define POSITION_INPUTDEV "/dev/input/hdaps/accelerometer-event"
56 #define FREEZE_SECONDS 1 /* period to freeze disk */
57 #define REFREEZE_SECONDS 0.1 /* period after which to re-freeze disk */
58 #define FREEZE_EXTRA_SECONDS 4 /* additional timeout for kernel timer */
59 #define DEFAULT_SAMPLING_RATE 50 /* default sampling frequency */
60 #define SIGUSR1_SLEEP_SEC 8 /* how long to sleep upon SIGUSR1 */
61 /* Magic threshold tweak factors, determined experimentally to make a
62 * threshold of 10-20 behave reasonably.
64 #define VELOC_ADJUST 30.0
65 #define ACCEL_ADJUST (VELOC_ADJUST * 60)
66 #define AVG_VELOC_ADJUST 3.0
68 /* History depth for velocity average, in seconds */
69 #define AVG_DEPTH_SEC 0.3
71 /* Parameters for adaptive threshold */
72 #define RECENT_PARK_SEC 3.0 /* How recent is "recently parked"? */
73 #define THRESH_ADAPT_SEC 1.0 /* How often to (potentially) change
74 * the adaptive threshold? */
75 #define THRESH_INCREASE_FACTOR 1.1 /* Increase factor when recently
76 * parked but user is typing */
77 #define THRESH_DECREASE_FACTOR 0.9985 /* Decrease factor when not recently
78 * parked, per THRESH_ADAPT_SEC sec. */
79 #define NEAR_THRESH_FACTOR 0.8 /* Fraction of threshold considered
80 * being near the threshold. */
82 /* Threshold for *continued* parking, as fraction of normal threshold */
83 #define PARKED_THRESH_FACTOR NEAR_THRESH_FACTOR /* >= NEAR_THRESH_FACTOR */
85 static int verbose
= 0;
86 static int pause_now
= 0;
87 static int dry_run
= 0;
88 static int poll_sysfs
= 0;
89 static int sampling_rate
;
90 static int running
= 1;
91 static int background
= 0;
92 static int dosyslog
= 0;
94 char pid_file
[BUF_LEN
] = "";
95 int hdaps_input_fd
= 0;
99 char protect_file
[BUF_LEN
];
103 struct list
*disklist
= NULL
;
106 * printlog (stream, fmt) - print the formatted message to syslog
107 * or to the defined stream
110 void printlog (FILE *stream
, const char *fmt
, ...)
113 int len
= sizeof(fmt
);
118 vsnprintf(msg
, len
+1024, fmt
, ap
);
122 syslog(LOG_INFO
, msg
);
124 now
= time((time_t *)NULL
);
125 fprintf(stream
, "%.24s: %s\n", ctime(&now
), msg
);
130 * slurp_file - read the content of a file (up to BUF_LEN-1) into a string.
132 * We open and close the file on every invocation, which is lame but due to
133 * several features of sysfs files:
135 * (a) Sysfs files are seekable.
136 * (b) Seeking to zero and then rereading does not seem to work.
138 * If I were king--and I will be one day--I would have made sysfs files
139 * nonseekable and only able to return full-size reads.
141 static int slurp_file(const char* filename
, char* buf
)
144 int fd
= open (filename
, O_RDONLY
);
146 printlog(stderr
, "Could not open %s: %s.\nDo you have the hdaps module loaded?", filename
, strerror(errno
));
150 ret
= read (fd
, buf
, BUF_LEN
-1);
152 printlog(stderr
, "Could not read from %s: %s", filename
, strerror(errno
));
154 buf
[ret
] = 0; /* null-terminate so we can parse safely */
159 printlog(stderr
, "Could not close %s: %s", filename
, strerror(errno
));
165 * read_position_from_sysfs() - read the (x,y) position pair from hdaps via sysfs files
166 * This method is not recommended for frequent polling, since it causes unnecessary interrupts
167 * and a phase difference between hdaps-to-EC polling vs. hdapsd-to-hdaps polling.
169 static int read_position_from_sysfs (int *x
, int *y
)
173 if ((ret
= slurp_file(SYSFS_POSITION_FILE
, buf
)))
175 return (sscanf (buf
, "(%d,%d)\n", x
, y
) != 2);
179 * read_int() - read an integer from a file
181 static int read_int (const char* filename
)
185 if ((ret
= slurp_file(filename
, buf
)))
187 if (sscanf (buf
, "%d\n", &ret
) != 1)
193 * get_km_activity() - returns 1 if there is keyboard or mouse activity
195 static int get_km_activity()
197 if (read_int(MOUSE_ACTIVITY_FILE
)==1)
199 if (read_int(KEYBD_ACTIVITY_FILE
)==1)
206 * read_position_from_inputdev() - read the (x,y) position pair and time from hdaps
207 * via the hdaps input device. Blocks there is a change in position.
208 * The x and y arguments should contain the last read values, since if one of them
209 * doesn't change it will not be assigned.
211 static int read_position_from_inputdev (int *x
, int *y
, double *utime
)
213 struct input_event ev
;
217 len
= read(hdaps_input_fd
, &ev
, sizeof(struct input_event
));
219 printlog(stderr
, "ERROR: failed reading %s (%s).", POSITION_INPUTDEV
, strerror(errno
));
222 if (len
< (int)sizeof(struct input_event
)) {
223 printlog(stderr
, "ERROR: short read from %s (%d bytes).", POSITION_INPUTDEV
, len
);
227 case EV_ABS
: /* new X or Y */
239 case EV_SYN
: /* X and Y now reflect latest measurement */
245 if (!*utime
) /* first event's time is closest to reality */
246 *utime
= ev
.time
.tv_sec
+ ev
.time
.tv_usec
/1000000.0;
254 * write_protect() - park/unpark
256 static int write_protect (const char *path
, int val
)
264 snprintf(buf
, BUF_LEN
, "%d", val
);
266 fd
= open (path
, O_WRONLY
);
268 printlog (stderr
, "Could not open %s", path
);
272 ret
= write (fd
, buf
, strlen(buf
));
275 printlog (stderr
, "Could not write to %s.\nDoes your kernel/drive support IDLE_IMMEDIATE with UNLOAD?", path
);
282 printlog (stderr
, "Could not close %s", path
);
287 double get_utime (void)
290 int ret
= gettimeofday(&tv
, NULL
);
292 perror("gettimeofday");
295 return tv
.tv_sec
+ tv
.tv_usec
/1000000.0;
298 /* Handler for SIGUSR1, sleeps for a few seconds. Useful when suspending laptop. */
299 void SIGUSR1_handler(int sig
)
301 signal(SIGUSR1
, SIGUSR1_handler
);
305 /* Handler for SIGTERM, deletes the pidfile and exits. */
306 void SIGTERM_handler(int sig
)
308 signal(SIGTERM
, SIGTERM_handler
);
313 * version() - display version information and exit
317 printf(PACKAGE_STRING
"\n");
322 * usage() - display usage instructions and exit
326 printf("Usage: "PACKAGE_NAME
" [OPTIONS]\n");
328 printf("Required options:\n");
329 printf(" -d --device=<device> <device> is likely to be hda or sda.\n");
330 printf(" Can be given multiple times\n");
331 printf(" to protect multiple devices.\n");
333 printf("Additional options:\n");
334 printf(" -s --sensitivity=<sensitivity> How sensitive "PACKAGE_NAME
" should be to movements.\n");
335 printf(" Defaults to 15, higher value means less\n");
336 printf(" sensitive.\n");
337 printf(" -a --adaptive Adaptive threshold (automatic increase\n");
338 printf(" when the built-in keyboard/mouse are used).\n");
339 printf(" -v --verbose Get verbose statistics.\n");
340 printf(" -b --background Run the process in the background.\n");
341 printf(" -p --pidfile[=<pidfile>] Create a pid file when running\n");
342 printf(" in background.\n");
343 printf(" If <pidfile> is not specified,\n");
344 printf(" it's set to %s.\n", PID_FILE
);
345 printf(" -t --dry-run Don't actually park the drive.\n");
346 printf(" -y --poll-sysfs Force use of sysfs interface to\n");
347 printf(" accelerometer.\n");
348 printf(" -l --syslog Log to syslog instead of stdout/stderr.\n");
350 printf(" -V --version Display version information and exit.\n");
351 printf(" -h --help Display this message and exit.\n");
353 printf("You can send SIGUSR1 to deactivate "PACKAGE_NAME
" for %d seconds.\n",
356 printf("Send bugs, comments and suggestions to "PACKAGE_BUGREPORT
"\n");
361 * check_thresh() - compare a value to the threshold
363 void check_thresh(double val_sqr
, double thresh
, int* above
, int* near
,
364 char* reason_out
, char reason_mark
)
366 if (val_sqr
> thresh
*thresh
*NEAR_THRESH_FACTOR
*NEAR_THRESH_FACTOR
) {
368 *reason_out
= tolower(reason_mark
);
370 if (val_sqr
> thresh
*thresh
) {
372 *reason_out
= toupper(reason_mark
);
377 * analyze() - make a decision on whether to park given present readouts
378 * (remembers some past data in local static variables).
379 * Computes and checks 3 values:
380 * velocity: current position - prev position / time delta
381 * acceleration: current velocity - prev velocity / time delta
382 * average velocity: exponentially decaying average of velocity,
383 * weighed by time delta.
384 * The velocity and acceleration tests respond quickly to short sharp shocks,
385 * while the average velocity test catches long, smooth movements (and
386 * averages out measurement noise).
387 * The adaptive threshold, if enabled, increases when (built-in) keyboard or
388 * mouse activity happens shortly after some value was above or near the
389 * adaptive threshold. The adaptive threshold slowly decreases back to the
390 * base threshold when no value approaches it.
393 int analyze(int x
, int y
, double unow
, double base_threshold
,
394 int adaptive
, int parked
)
396 static int x_last
= 0, y_last
= 0;
397 static double unow_last
= 0, x_veloc_last
= 0, y_veloc_last
= 0;
398 static double x_avg_veloc
= 0, y_avg_veloc
= 0;
399 static int history
= 0; /* how many recent valid samples? */
400 static double adaptive_threshold
= -1; /* current adaptive thresh */
401 static int last_thresh_change
= 0; /* last adaptive thresh change */
402 static int last_near_thresh
= 0; /* last time we were near thresh */
403 static int last_km_activity
; /* last time kbd/mouse activity seen */
405 double udelta
, x_delta
, y_delta
, x_veloc
, y_veloc
, x_accel
, y_accel
;
406 double veloc_sqr
, accel_sqr
, avg_veloc_sqr
;
408 double threshold
; /* transient threshold for this iteration */
409 char reason
[4]; /* "which threshold reached?" string for verbose */
410 int recently_near_thresh
;
411 int above
=0, near
=0; /* above threshold, near threshold */
413 /* Adaptive threshold adjustment */
414 if (adaptive_threshold
<0) /* first invocation */
415 adaptive_threshold
= base_threshold
;
416 recently_near_thresh
= unow
< last_near_thresh
+ RECENT_PARK_SEC
;
417 if (adaptive
&& recently_near_thresh
&& get_km_activity())
418 last_km_activity
= unow
;
419 if (adaptive
&& unow
> last_thresh_change
+ THRESH_ADAPT_SEC
) {
420 if (recently_near_thresh
) {
421 if (last_km_activity
> last_near_thresh
&&
422 last_km_activity
> last_thresh_change
) {
423 /* Near threshold and k/m activity */
424 adaptive_threshold
*= THRESH_INCREASE_FACTOR
;
425 last_thresh_change
= unow
;
428 /* Recently never near threshold */
429 adaptive_threshold
*= THRESH_DECREASE_FACTOR
;
430 if (adaptive_threshold
< base_threshold
)
431 adaptive_threshold
= base_threshold
;
432 last_thresh_change
= unow
;
437 udelta
= unow
- unow_last
;
438 x_delta
= x
- x_last
;
439 y_delta
= y
- y_last
;
441 /* compute velocity */
442 x_veloc
= x_delta
/udelta
;
443 y_veloc
= y_delta
/udelta
;
444 veloc_sqr
= x_veloc
*x_veloc
+ y_veloc
*y_veloc
;
446 /* compute acceleration */
447 x_accel
= (x_veloc
- x_veloc_last
)/udelta
;
448 y_accel
= (y_veloc
- y_veloc_last
)/udelta
;
449 accel_sqr
= x_accel
*x_accel
+ y_accel
*y_accel
;
451 /* compute exponentially-decaying velocity average */
452 exp_weight
= udelta
/AVG_DEPTH_SEC
; /* weight of this sample */
453 exp_weight
= 1 - 1.0/(1+exp_weight
); /* softly clamped to 1 */
454 x_avg_veloc
= exp_weight
*x_veloc
+ (1-exp_weight
)*x_avg_veloc
;
455 y_avg_veloc
= exp_weight
*y_veloc
+ (1-exp_weight
)*y_avg_veloc
;
456 avg_veloc_sqr
= x_avg_veloc
*x_avg_veloc
+ y_avg_veloc
*y_avg_veloc
;
458 threshold
= adaptive_threshold
;
459 if (parked
) /* when parked, be reluctant to unpark */
460 threshold
*= PARKED_THRESH_FACTOR
;
462 /* Threshold test (uses Pythagoras's theorem) */
465 check_thresh(veloc_sqr
, threshold
*VELOC_ADJUST
,
466 &above
, &near
, reason
+0, 'V');
467 check_thresh(accel_sqr
, threshold
*ACCEL_ADJUST
,
468 &above
, &near
, reason
+1, 'A');
469 check_thresh(avg_veloc_sqr
, threshold
*AVG_VELOC_ADJUST
,
470 &above
, &near
, reason
+2, 'X');
475 "vel=(%6.1f,%6.1f)*%g "
476 "acc=(%6.1f,%6.1f)*%g "
477 "avg_vel=(%6.1f,%6.1f)*%g "
482 x_veloc
/VELOC_ADJUST
,
483 y_veloc
/VELOC_ADJUST
,
485 x_accel
/ACCEL_ADJUST
,
486 y_accel
/ACCEL_ADJUST
,
488 x_avg_veloc
/AVG_VELOC_ADJUST
,
489 y_avg_veloc
/AVG_VELOC_ADJUST
,
490 AVG_VELOC_ADJUST
*1.0,
495 if (udelta
>1.0) { /* Too much time since last (resume from suspend?) */
497 x_avg_veloc
= y_avg_veloc
= 0;
500 if (history
<2) { /* Not enough data for meaningful result */
507 last_near_thresh
= unow
;
511 x_veloc_last
= x_veloc
;
512 y_veloc_last
= y_veloc
;
519 * add_disk (disk) - add the given disk to the global disklist
521 void add_disk (char* disk
) {
522 struct utsname sysinfo
;
523 char protect_file
[BUF_LEN
] = "";
524 if (uname(&sysinfo
) < 0 || strcmp("2.6.27", sysinfo
.release
) <= 0)
525 snprintf(protect_file
, BUF_LEN
, "/sys/block/%s/device/unload_heads", disk
);
527 snprintf(protect_file
, BUF_LEN
, "/sys/block/%s/queue/protect", disk
);
529 if (disklist
== NULL
) {
530 disklist
= (struct list
*)malloc(sizeof(struct list
));
531 if (disklist
== NULL
) {
532 printlog(stderr
, "Error allocating memory.");
536 strncpy(disklist
->name
,disk
,BUF_LEN
);
537 strncpy(disklist
->protect_file
,protect_file
,BUF_LEN
);
538 disklist
->next
= NULL
;
542 struct list
*p
= disklist
;
543 while (p
->next
!= NULL
)
545 p
->next
= (struct list
*)malloc(sizeof(struct list
));
546 if (p
->next
== NULL
) {
547 printlog(stderr
, "Error allocating memory.");
551 strncpy(p
->next
->name
,disk
,BUF_LEN
);
552 strncpy(p
->next
->protect_file
,protect_file
,BUF_LEN
);
553 p
->next
->next
= NULL
;
559 * free_disk (disk) - free the allocated memory
561 void free_disk (struct list
*disk
) {
563 if (disk
->next
!= NULL
)
564 free_disk(disk
->next
);
570 * main() - loop forever, reading the hdaps values and
571 * parking/unparking as necessary
573 int main (int argc
, char** argv
)
575 struct utsname sysinfo
;
576 int c
, park_now
, protect_factor
;
578 int fd
, i
, ret
, threshold
= 15, adaptive
=0,
579 pidfile
= 0, parked
= 0;
580 double unow
= 0, parked_utime
= 0;
582 if (uname(&sysinfo
) < 0 || strcmp("2.6.27", sysinfo
.release
) <= 0)
583 protect_factor
= 1000;
587 struct option longopts
[] =
589 {"device", required_argument
, NULL
, 'd'},
590 {"sensitivity", required_argument
, NULL
, 's'},
591 {"adaptive", no_argument
, NULL
, 'a'},
592 {"verbose", no_argument
, NULL
, 'v'},
593 {"background", no_argument
, NULL
, 'b'},
594 {"pidfile", optional_argument
, NULL
, 'p'},
595 {"dry-run", no_argument
, NULL
, 't'},
596 {"poll-sysfs", no_argument
, NULL
, 'y'},
597 {"version", no_argument
, NULL
, 'V'},
598 {"help", no_argument
, NULL
, 'h'},
599 {"syslog", no_argument
, NULL
, 'l'},
603 openlog(PACKAGE_NAME
, LOG_PID
, LOG_DAEMON
);
605 while ((c
= getopt_long(argc
, argv
, "d:s:vbap::tyVhl", longopts
, NULL
)) != -1) {
611 threshold
= atoi(optarg
);
624 if (optarg
== NULL
) {
625 snprintf(pid_file
, BUF_LEN
, "%s", PID_FILE
);
627 snprintf(pid_file
, BUF_LEN
, "%s", optarg
);
631 printlog(stdout
, "Dry run, will not actually park heads or freeze queue.");
650 if (!threshold
|| disklist
== NULL
)
654 hdaps_input_fd
= open(POSITION_INPUTDEV
, O_RDONLY
);
655 if (hdaps_input_fd
<0) {
657 "WARNING: Cannot open hdaps position input file %s (%s). "
658 "You may be using an incompatible version of the hdaps module, "
659 "or missing the required udev rule.\n"
660 "Falling back to reading the position from sysfs (uses more power).\n"
661 "Use '-y' to silence this warning.",
662 POSITION_INPUTDEV
, strerror(errno
));
670 fd
= open (pid_file
, O_WRONLY
| O_CREAT
, 0644);
672 printlog (stderr
, "Could not create pidfile: %s", pid_file
);
679 snprintf (buf
, BUF_LEN
, "%d\n", getpid());
680 ret
= write (fd
, buf
, strlen(buf
));
682 printlog (stderr
, "Could not write to pidfile %s", pid_file
);
686 printlog (stderr
, "Could not close pidfile %s", pid_file
);
692 mlockall(MCL_FUTURE
);
695 struct list
*p
= disklist
;
697 printf("disk: %s\n", p
->name
);
700 printf("threshold: %i\n", threshold
);
701 printf("read_method: %s\n", poll_sysfs
? "poll-sysfs" : "input-dev");
704 printlog(stdout
, "Starting "PACKAGE_NAME
);
706 /* check the protect attribute exists */
707 /* wait for it if it's not there (in case the attribute hasn't been created yet) */
708 struct list
*p
= disklist
;
710 fd
= open (p
->protect_file
, O_RDWR
);
712 for (i
=0; fd
< 0 && i
< 100; ++i
) {
713 usleep (100000); /* 10 Hz */
714 fd
= open (p
->protect_file
, O_RDWR
);
717 printlog (stderr
, "Could not open %s\nDoes your kernel/drive support IDLE_IMMEDIATE with UNLOAD?", p
->protect_file
);
725 /* see if we can read the sensor */
726 /* wait for it if it's not there (in case the attribute hasn't been created yet) */
727 ret
= read_position_from_sysfs (&x
, &y
);
729 for (i
=0; ret
&& i
< 100; ++i
) {
730 usleep (100000); /* 10 Hz */
731 ret
= read_position_from_sysfs (&x
, &y
);
736 /* adapt to the driver's sampling rate */
737 sampling_rate
= read_int(SAMPLING_RATE_FILE
);
738 if (sampling_rate
<= 0)
739 sampling_rate
= DEFAULT_SAMPLING_RATE
;;
741 printf("sampling_rate: %d\n", sampling_rate
);
743 signal(SIGUSR1
, SIGUSR1_handler
);
745 signal(SIGTERM
, SIGTERM_handler
);
749 usleep (1000000/sampling_rate
);
750 ret
= read_position_from_sysfs (&x
, &y
);
751 unow
= get_utime(); /* microsec */
753 double oldunow
= unow
;
754 int oldx
= x
, oldy
= y
;
755 ret
= read_position_from_inputdev (&x
, &y
, &unow
);
757 /* The input device issues events only when the position changed.
758 * The analysis state needs to know how long the position remained
759 * unchanged, so send analyze() a fake retroactive update before sending
761 if (!ret
&& oldunow
&& unow
-oldunow
> 1.5/sampling_rate
)
762 analyze(oldx
, oldy
, unow
-1.0/sampling_rate
, threshold
, adaptive
, parked
);
768 printf("readout error (%d)\n", ret
);
772 park_now
= analyze(x
, y
, unow
, threshold
, adaptive
, parked
);
774 if (park_now
&& !pause_now
) {
775 if (!parked
|| unow
>parked_utime
+REFREEZE_SECONDS
) {
776 /* Not frozen or freeze about to expire */
777 struct list
*p
= disklist
;
779 write_protect(p
->protect_file
,
780 (FREEZE_SECONDS
+FREEZE_EXTRA_SECONDS
) * protect_factor
);
783 /* Write protect before any output (xterm, or
784 * whatever else is handling our stdout, may be
788 printlog(stdout
, "parking");
794 (pause_now
|| unow
>parked_utime
+FREEZE_SECONDS
)) {
796 struct list
*p
= disklist
;
798 if (!dry_run
&& !read_int(p
->protect_file
))
799 printlog(stderr
, "Error! Not parked when we "
800 "thought we were... (paged out "
801 "and timer expired?)");
802 /* Freeze has expired */
803 write_protect(p
->protect_file
, 0); /* unprotect */
807 printlog(stdout
, "un-parking");
811 printlog(stdout
, "pausing for %d seconds", SIGUSR1_SLEEP_SEC
);
812 sleep(SIGUSR1_SLEEP_SEC
);
819 printlog(stdout
, "Terminating "PACKAGE_NAME
);