Репозиторий Sisyphus
Последнее обновление: 1 октября 2023 | Пакетов: 18631 | Посещений: 37893169
en ru br
Репозитории ALT
S:20090401-alt1.qa1
5.1: 0.1-alt1
www.altlinux.org/Changes

Группа :: Система/Ядро и оборудование
Пакет: hdapsd

 Главная   Изменения   Спек   Патчи   Исходники   Загрузить   Gear   Bugs and FR  Repocop 

/*
* hdapsd.c - Read from the HDAPS (HardDrive Active Protection System)
* and protect the drive if motion over threshold...
*
* Derived from pivot.c by Robert Love.
*
* Copyright (C) 2005-2007 Jon Escombe <lists@dresco.co.uk>
* Robert Love <rml@novell.com>
* Shem Multinymous <multinymous@gmail.com>
*
* "Why does that kid keep dropping his laptop?"
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <ctype.h>
#include <sys/utsname.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <getopt.h>
#include <linux/input.h>

#define PID_FILE "/var/run/hdapsd.pid"
#define SYSFS_POSITION_FILE "/sys/devices/platform/hdaps/position"
#define MOUSE_ACTIVITY_FILE "/sys/devices/platform/hdaps/keyboard_activity"
#define KEYBD_ACTIVITY_FILE "/sys/devices/platform/hdaps/mouse_activity"
#define SAMPLING_RATE_FILE "/sys/devices/platform/hdaps/sampling_rate"
#define POSITION_INPUTDEV "/dev/input/hdaps/accelerometer-event"
#define BUF_LEN 40

#define FREEZE_SECONDS 1 /* period to freeze disk */
#define REFREEZE_SECONDS 0.1 /* period after which to re-freeze disk */
#define FREEZE_EXTRA_SECONDS 4 /* additional timeout for kernel timer */
#define DEFAULT_SAMPLING_RATE 50 /* default sampling frequency */
#define SIGUSR1_SLEEP_SEC 8 /* how long to sleep upon SIGUSR1 */
/* Magic threshold tweak factors, determined experimentally to make a
* threshold of 10-20 behave reasonably.
*/
#define VELOC_ADJUST 30.0
#define ACCEL_ADJUST (VELOC_ADJUST * 60)
#define AVG_VELOC_ADJUST 3.0

/* History depth for velocity average, in seconds */
#define AVG_DEPTH_SEC 0.3

/* Parameters for adaptive threshold */
#define RECENT_PARK_SEC 3.0 /* How recent is "recently parked"? */
#define THRESH_ADAPT_SEC 1.0 /* How often to (potentially) change
* the adaptive threshold? */
#define THRESH_INCREASE_FACTOR 1.1 /* Increase factor when recently
* parked but user is typing */
#define THRESH_DECREASE_FACTOR 0.9985 /* Decrease factor when not recently
* parked, per THRESH_ADAPT_SEC sec. */
#define NEAR_THRESH_FACTOR 0.8 /* Fraction of threshold considered
* being near the threshold. */

/* Threshold for *continued* parking, as fraction of normal threshold */
#define PARKED_THRESH_FACTOR NEAR_THRESH_FACTOR /* >= NEAR_THRESH_FACTOR */

static int verbose = 0;
static int pause_now = 0;
static int dry_run = 0;
static int poll_sysfs = 0;
static int sampling_rate;

char pid_file[BUF_LEN] = "";
int hdaps_input_fd = 0;


/*
* slurp_file - read the content of a file (up to BUF_LEN-1) into a string.
*
* We open and close the file on every invocation, which is lame but due to
* several features of sysfs files:
*
* (a) Sysfs files are seekable.
* (b) Seeking to zero and then rereading does not seem to work.
*
* If I were king--and I will be one day--I would have made sysfs files
* nonseekable and only able to return full-size reads.
*/
static int slurp_file(const char* filename, char* buf)
{
int ret;
int fd = open (filename, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "open(%s): %s\n", filename, strerror(errno));
return fd;
}

ret = read (fd, buf, BUF_LEN-1);
if (ret < 0) {
fprintf(stderr, "read(%s): %s\n", filename, strerror(errno));
} else {
buf[ret] = 0; /* null-terminate so we can parse safely */
ret = 0;
}

if (close (fd))
fprintf(stderr, "close(%s): %s\n", filename, strerror(errno));

return ret;
}

/*
* read_position_from_sysfs() - read the (x,y) position pair from hdaps via sysfs files
* This method is not recommended for frequent polling, since it causes unnecessary interrupts
* and a phase difference between hdaps-to-EC polling vs. hdapsd-to-hdaps polling.
*/
static int read_position_from_sysfs (int *x, int *y)
{
char buf[BUF_LEN];
int ret;
if ((ret = slurp_file(SYSFS_POSITION_FILE, buf)))
return ret;
return (sscanf (buf, "(%d,%d)\n", x, y) != 2);
}

/*
* read_int() - read an integer from a file
*/
static int read_int (const char* filename)
{
char buf[BUF_LEN];
int ret;
if ((ret = slurp_file(filename, buf)))
return ret;
if (sscanf (buf, "%d\n", &ret) != 1)
return -EIO;
return ret;
}

/*
* get_km_activity() - returns 1 if there is keyboard or mouse activity
*/
static int get_km_activity()
{
if (read_int(MOUSE_ACTIVITY_FILE)==1)
return 1;
if (read_int(KEYBD_ACTIVITY_FILE)==1)
return 1;
return 0;
}


/*
* read_position_from_inputdev() - read the (x,y) position pair and time from hdaps
* via the hdaps input device. Blocks there is a change in position.
* The x and y arguments should contain the last read values, since if one of them
* doesn't change it will not be assigned.
*/
static int read_position_from_inputdev (int *x, int *y, double *utime)
{
struct input_event ev;
int len, done = 0;
*utime = 0;
while (1) {
len = read(hdaps_input_fd, &ev, sizeof(struct input_event));
if (len < 0) {
fprintf(stderr, "ERROR: failed reading %s (%s).\n", POSITION_INPUTDEV, strerror(errno));
return len;
}
if (len < (int)sizeof(struct input_event)) {
fprintf(stderr, "ERROR: short read from %s (%d bytes).\n", POSITION_INPUTDEV, len);
return -EIO;
}
switch (ev.type) {
case EV_ABS: /* new X or Y */
switch (ev.code) {
case ABS_X:
*x = ev.value;
break;
case ABS_Y:
*y = ev.value;
break;
default:
continue;
}
break;
case EV_SYN: /* X and Y now reflect latest measurement */
done = 1;
break;
default:
continue;
}
if (!*utime) /* first event's time is closest to reality */
*utime = ev.time.tv_sec + ev.time.tv_usec/1000000.0;
if (done)
return 0;
}
}


/*
* write_protect() - park/unpark
*/
static int write_protect (const char *path, int val)
{
int fd, ret;
char buf[BUF_LEN];

if (dry_run)
return 0;

snprintf(buf, BUF_LEN, "%d", val);

fd = open (path, O_WRONLY);
if (fd < 0) {
perror ("open");
return fd;
}

ret = write (fd, buf, strlen(buf));

if (ret < 0) {
perror ("write");
goto out;
}
ret = 0;

out:
if (close (fd))
perror ("close");

return ret;
}

double get_utime (void)
{
struct timeval tv;
int ret = gettimeofday(&tv, NULL);
if (ret) {
perror("gettimeofday");
exit(1);
}
return tv.tv_sec + tv.tv_usec/1000000.0;
}

/* Handler for SIGUSR1, sleeps for a few seconds. Useful when suspending laptop. */
void SIGUSR1_handler(int sig)
{
signal(SIGUSR1, SIGUSR1_handler);
pause_now=1;
}

/* Handler for SIGTERM, deletes the pidfile and exits. */
void SIGTERM_handler(int sig)
{
signal(SIGTERM, SIGTERM_handler);
unlink(pid_file);
exit(0);
}

/*
* usage() - display usage instructions and exit
*/
void usage()
{
printf("Usage: hdapsd [OPTIONS]\n");
printf("\n");
printf("Required options:\n");
printf(" -d --device=<device> <device> is likely to be hda or sda.\n");
printf(" -s --sensitivity=<sensitivity> A suggested starting <sensitivity> is 15.\n");
printf("Additional options:\n");
printf(" -a --adaptive Adaptive threshold (automatic\n");
printf(" increase when the built-in\n");
printf(" keyboard/mouse are used).\n");
printf(" -v --verbose Get verbose statistics.\n");
printf(" -b --background Run the process in the background.\n");
printf(" -p --pidfile[=<pidfile>] Create a pid file when running\n");
printf(" in background.\n");
printf(" If <pidfile> is not specified,\n");
printf(" it's set to %s.\n", PID_FILE);
printf(" -t --dry-run Don't actually park the drive.\n");
printf(" -y --poll-sysfs Force use of sysfs interface to accelerometer.\n");
printf("\n");
printf("You can send SIGUSR1 to deactivate hdapsd for %d seconds.\n",
SIGUSR1_SLEEP_SEC);
printf("\n");
exit(1);
}

/*
* check_thresh() - compare a value to the threshold
*/
void check_thresh(double val_sqr, double thresh, int* above, int* near,
char* reason_out, char reason_mark)
{
if (val_sqr > thresh*thresh*NEAR_THRESH_FACTOR*NEAR_THRESH_FACTOR) {
*near = 1;
*reason_out = tolower(reason_mark);
}
if (val_sqr > thresh*thresh) {
*above = 1;
*reason_out = toupper(reason_mark);
}
}

/*
* analyze() - make a decision on whether to park given present readouts
* (remembers some past data in local static variables).
* Computes and checks 3 values:
* velocity: current position - prev position / time delta
* acceleration: current velocity - prev velocity / time delta
* average velocity: exponentially decaying average of velocity,
* weighed by time delta.
* The velocity and acceleration tests respond quickly to short sharp shocks,
* while the average velocity test catches long, smooth movements (and
* averages out measurement noise).
* The adaptive threshold, if enabled, increases when (built-in) keyboard or
* mouse activity happens shortly after some value was above or near the
* adaptive threshold. The adaptive threshold slowly decreases back to the
* base threshold when no value approaches it.
*/

int analyze(int x, int y, double unow, double base_threshold,
int adaptive, int parked)
{
static int x_last = 0, y_last = 0;
static double unow_last = 0, x_veloc_last = 0, y_veloc_last = 0;
static double x_avg_veloc = 0, y_avg_veloc = 0;
static int history = 0; /* how many recent valid samples? */
static double adaptive_threshold = -1; /* current adaptive thresh */
static int last_thresh_change = 0; /* last adaptive thresh change */
static int last_near_thresh = 0; /* last time we were near thresh */
static int last_km_activity; /* last time kbd/mouse activity seen */

double udelta, x_delta, y_delta, x_veloc, y_veloc, x_accel, y_accel;
double veloc_sqr, accel_sqr, avg_veloc_sqr;
double exp_weight;
double threshold; /* transient threshold for this iteration */
char reason[4]; /* "which threshold reached?" string for verbose */
int recently_near_thresh;
int above=0, near=0; /* above threshold, near threshold */

/* Adaptive threshold adjustment */
if (adaptive_threshold<0) /* first invocation */
adaptive_threshold = base_threshold;
recently_near_thresh = unow < last_near_thresh + RECENT_PARK_SEC;
if (adaptive && recently_near_thresh && get_km_activity())
last_km_activity = unow;
if (adaptive && unow > last_thresh_change + THRESH_ADAPT_SEC) {
if (recently_near_thresh) {
if (last_km_activity > last_near_thresh &&
last_km_activity > last_thresh_change) {
/* Near threshold and k/m activity */
adaptive_threshold *= THRESH_INCREASE_FACTOR;
last_thresh_change = unow;
}
} else {
/* Recently never near threshold */
adaptive_threshold *= THRESH_DECREASE_FACTOR;
if (adaptive_threshold < base_threshold)
adaptive_threshold = base_threshold;
last_thresh_change = unow;
}
}

/* compute deltas */
udelta = unow - unow_last;
x_delta = x - x_last;
y_delta = y - y_last;

/* compute velocity */
x_veloc = x_delta/udelta;
y_veloc = y_delta/udelta;
veloc_sqr = x_veloc*x_veloc + y_veloc*y_veloc;

/* compute acceleration */
x_accel = (x_veloc - x_veloc_last)/udelta;
y_accel = (y_veloc - y_veloc_last)/udelta;
accel_sqr = x_accel*x_accel + y_accel*y_accel;

/* compute exponentially-decaying velocity average */
exp_weight = udelta/AVG_DEPTH_SEC; /* weight of this sample */
exp_weight = 1 - 1.0/(1+exp_weight); /* softly clamped to 1 */
x_avg_veloc = exp_weight*x_veloc + (1-exp_weight)*x_avg_veloc;
y_avg_veloc = exp_weight*y_veloc + (1-exp_weight)*y_avg_veloc;
avg_veloc_sqr = x_avg_veloc*x_avg_veloc + y_avg_veloc*y_avg_veloc;

threshold = adaptive_threshold;
if (parked) /* when parked, be reluctant to unpark */
threshold *= PARKED_THRESH_FACTOR;

/* Threshold test (uses Pythagoras's theorem) */
strcpy(reason, " ");

check_thresh(veloc_sqr, threshold*VELOC_ADJUST,
&above, &near, reason+0, 'V');
check_thresh(accel_sqr, threshold*ACCEL_ADJUST,
&above, &near, reason+1, 'A');
check_thresh(avg_veloc_sqr, threshold*AVG_VELOC_ADJUST,
&above, &near, reason+2, 'X');

if (verbose) {
printf("dt=%5.3f "
"dpos=(%3g,%3g) "
"vel=(%6.1f,%6.1f)*%g "
"acc=(%6.1f,%6.1f)*%g "
"avg_vel=(%6.1f,%6.1f)*%g "
"thr=%.1f "
"%s\n",
udelta,
x_delta, y_delta,
x_veloc/VELOC_ADJUST,
y_veloc/VELOC_ADJUST,
VELOC_ADJUST*1.0,
x_accel/ACCEL_ADJUST,
y_accel/ACCEL_ADJUST,
ACCEL_ADJUST*1.0,
x_avg_veloc/AVG_VELOC_ADJUST,
y_avg_veloc/AVG_VELOC_ADJUST,
AVG_VELOC_ADJUST*1.0,
threshold,
reason);
}

if (udelta>1.0) { /* Too much time since last (resume from suspend?) */
history = 0;
x_avg_veloc = y_avg_veloc = 0;
}

if (history<2) { /* Not enough data for meaningful result */
above = 0;
near = 0;
++history;
}

if (near)
last_near_thresh = unow;

x_last = x;
y_last = y;
x_veloc_last = x_veloc;
y_veloc_last = y_veloc;
unow_last = unow;

return above;
}

/*
* main() - loop forever, reading the hdaps values and
* parking/unparking as necessary
*/
int main (int argc, char** argv)
{
struct utsname sysinfo;
int c, park_now, protect_factor;
int x=0, y=0;
int fd, i, ret, threshold = 0, background = 0, adaptive=0,
pidfile = 0, parked = 0;
char protect_file[BUF_LEN] = "";
double unow = 0, parked_utime = 0;
time_t now;

if (uname(&sysinfo) < 0 || strcmp("2.6.27", sysinfo.release) <= 0)
protect_factor = 1000;
else
protect_factor = 1;

for (;;) {

struct option longopts[] =
{
{"device", required_argument, NULL, 'd'},
{"sensitivity", required_argument, NULL, 's'},
{"adaptive", no_argument, NULL, 'a'},
{"verbose", no_argument, NULL, 'v'},
{"background", no_argument, NULL, 'b'},
{"pidfile", optional_argument, NULL, 'p'},
{"dry-run", no_argument, NULL, 't'},
{"poll-sysfs", no_argument, NULL, 'y'},
{NULL, 0, NULL, 0}
};

c = getopt_long(argc, argv, "d:s:vbap::ty", longopts, NULL);
if (c < 0)
break;
switch (c) {
case 'd':
if (protect_factor != 1)
snprintf(protect_file, BUF_LEN,
"/sys/block/%s/device/unload_heads", optarg);
else
snprintf(protect_file, BUF_LEN,
"/sys/block/%s/queue/protect", optarg);
break;
case 's':
threshold = atoi(optarg);
break;
case 'b':
background = 1;
break;
case 'a':
adaptive = 1;
break;
case 'v':
verbose = 1;
break;
case 'p':
pidfile = 1;
if (optarg == NULL) {
snprintf(pid_file, BUF_LEN, "%s", PID_FILE);
} else {
snprintf(pid_file, BUF_LEN, "%s", optarg);
}
break;
case 't':
printf("Dry run, will not actually park heads or freeze queue.\n");
dry_run = 1;
break;
case 'y':
poll_sysfs = 1;
break;
default:
usage();
break;
}
}

if (!threshold || !strlen(protect_file))
usage(argv);

if (!poll_sysfs) {
hdaps_input_fd = open(POSITION_INPUTDEV, O_RDONLY);
if (hdaps_input_fd<0) {
fprintf(stderr,
"WARNING: Cannot open hdaps position input file %s (%s). "
"You may be using an incompatible version of the hdaps module, "
"or missing the required udev rule. "
"Falling back to reading the position from sysfs (uses more power). "
"Use '-y' to silence this warning.\n",
POSITION_INPUTDEV, strerror(errno));
poll_sysfs = 1;
}
}

if (background) {
verbose = 0;
if (pidfile) {
fd = open (pid_file, O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
perror ("open(pid_file)");
return 1;
}
}
daemon(0,0);
if (pidfile) {
char buf[BUF_LEN];
snprintf (buf, BUF_LEN, "%d\n", getpid());
ret = write (fd, buf, strlen(buf));
if (ret < 0) {
perror ("write(pid_file)");
return 1;
}
if (close (fd)) {
perror ("close(pid_file)");
return 1;
}
}
}

mlockall(MCL_FUTURE);

if (verbose) {
printf("protect_file: %s\n", protect_file);
printf("threshold: %i\n", threshold);
printf("read_method: %s\n", poll_sysfs ? "poll-sysfs" : "input-dev");
}

/* check the protect attribute exists */
/* wait for it if it's not there (in case the attribute hasn't been created yet) */
fd = open (protect_file, O_RDWR);
if (background)
for (i=0; fd < 0 && i < 100; ++i) {
usleep (100000); /* 10 Hz */
fd = open (protect_file, O_RDWR);
}
if (fd < 0) {
perror ("open(protect_file)");
return 1;
}
close (fd);

/* see if we can read the sensor */
/* wait for it if it's not there (in case the attribute hasn't been created yet) */
ret = read_position_from_sysfs (&x, &y);
if (background)
for (i=0; ret && i < 100; ++i) {
usleep (100000); /* 10 Hz */
ret = read_position_from_sysfs (&x, &y);
}
if (ret)
return 1;

/* adapt to the driver's sampling rate */
sampling_rate = read_int(SAMPLING_RATE_FILE);
if (sampling_rate <= 0)
sampling_rate = DEFAULT_SAMPLING_RATE;;
if (verbose)
printf("sampling_rate: %d\n", sampling_rate);

signal(SIGUSR1, SIGUSR1_handler);

if (background && pidfile) {
signal(SIGTERM, SIGTERM_handler);
}

while (1) {
if (poll_sysfs) {
usleep (1000000/sampling_rate);
ret = read_position_from_sysfs (&x, &y);
unow = get_utime(); /* microsec */
} else {
double oldunow = unow;
int oldx = x, oldy = y;
ret = read_position_from_inputdev (&x, &y, &unow);

/* The input device issues events only when the position changed.
* The analysis state needs to know how long the position remained
* unchanged, so send analyze() a fake retroactive update before sending
* the new one. */
if (!ret && oldunow && unow-oldunow > 1.5/sampling_rate)
analyze(oldx, oldy, unow-1.0/sampling_rate, threshold, adaptive, parked);

}

if (ret) {
if (verbose)
printf("readout error (%d)\n", ret);
continue;
}

now = time((time_t *)NULL); /* sec */

park_now = analyze(x, y, unow, threshold, adaptive, parked);

if (park_now && !pause_now) {
if (!parked || unow>parked_utime+REFREEZE_SECONDS) {
/* Not frozen or freeze about to expire */
write_protect(protect_file,
(FREEZE_SECONDS+FREEZE_EXTRA_SECONDS) * protect_factor);
/* Write protect before any output (xterm, or
* whatever else is handling our stdout, may be
* swapped out).
*/
if (!parked)
printf("%.24s: parking\n", ctime(&now));
parked = 1;
parked_utime = unow;
}
} else {
if (parked &&
(pause_now || unow>parked_utime+FREEZE_SECONDS)) {
/* Sanity check */
if (!dry_run && !read_int(protect_file))
printf("\nError! Not parked when we "
"thought we were... (paged out "
"and timer expired?)\n");
/* Freeze has expired */
write_protect(protect_file, 0); /* unprotect */
parked = 0;
printf("%.24s: un-parking\n", ctime(&now));
}
while (pause_now) {
pause_now=0;
printf("%.24s: pausing for %d seconds\n",
ctime(&now), SIGUSR1_SLEEP_SEC);
sleep(SIGUSR1_SLEEP_SEC);
}
}

}

munlockall();
return ret;
}
 
дизайн и разработка: Vladimir Lettiev aka crux © 2004-2005, Andrew Avramenko aka liks © 2007-2008
текущий майнтейнер: Michael Shigorin