2 * rtcwake -- enter a system sleep state until specified wakeup time.
4 * This uses cross-platform Linux interfaces to enter a system sleep state,
5 * and leave it no later than a specified time. It uses any RTC framework
6 * driver that supports standard driver model wakeup flags.
8 * This is normally used like the old "apmsleep" utility, to wake from a
9 * suspend state like ACPI S1 (standby) or S3 (suspend-to-RAM). Most
10 * platforms can implement those without analogues of BIOS, APM, or ACPI.
12 * On some systems, this can also be used like "nvram-wakeup", waking
13 * from states like ACPI S4 (suspend to disk). Not all systems have
14 * persistent media that are appropriate for such suspend modes.
16 * The best way to set the system's RTC is so that it holds the current
17 * time in UTC. Use the "-l" flag to tell this program that the system
18 * RTC uses a local timezone instead (maybe you dual-boot MS-Windows).
19 * That flag should not be needed on systems with adjtime support.
32 #include <sys/ioctl.h>
34 #include <sys/types.h>
36 #include <linux/rtc.h>
40 /* constants from legacy PC/AT hardware */
47 static char *progname;
49 #define VERSION_STRING "rtcwake from " PACKAGE_STRING
50 #define RTC_PATH "/sys/class/rtc/%s/device/power/wakeup"
51 #define SYS_POWER_STATE_PATH "/sys/power/state"
52 #define ADJTIME_PATH "/etc/adjtime"
53 #define DEFAULT_DEVICE "/dev/rtc0"
54 #define DEFAULT_MODE "standby"
62 static unsigned verbose;
63 enum ClockMode clock_mode = CM_AUTO;
65 static struct option long_options[] = {
66 {"auto", no_argument, 0, 'a'},
67 {"local", no_argument, 0, 'l'},
68 {"utc", no_argument, 0, 'u'},
69 {"verbose", no_argument, 0, 'v'},
70 {"version", no_argument, 0, 'V'},
71 {"help", no_argument, 0, 'h'},
72 {"mode", required_argument, 0, 'm'},
73 {"device", required_argument, 0, 'd'},
74 {"seconds", required_argument, 0, 's'},
75 {"time", required_argument, 0, 't'},
79 static void usage(int retval)
81 printf(_("usage: %s [options]\n"
82 " -d | --device <device> select rtc device (rtc0|rtc1|...)\n"
83 " -l | --local RTC uses local timezone\n"
84 " -m | --mode standby|mem|... sleep mode\n"
85 " -s | --seconds <seconds> seconds to sleep\n"
86 " -t | --time <time_t> time to wake\n"
87 " -u | --utc RTC uses UTC\n"
88 " -v | --verbose verbose messages\n"
89 " -V | --version show version\n"),
94 static int is_wakeup_enabled(const char *devname)
99 /* strip the '/dev/' from the devname here */
100 snprintf(buf, sizeof buf, RTC_PATH, devname + strlen("/dev/"));
106 s = fgets(buf, sizeof buf, f);
111 s = strchr(buf, '\n');
116 /* wakeup events could be disabled or not supported */
117 return strcmp(buf, "enabled") == 0;
120 /* all times should be in UTC */
121 static time_t sys_time;
122 static time_t rtc_time;
124 static int get_basetimes(int fd)
129 /* this process works in RTC time, except when working
130 * with the system clock (which always uses UTC).
132 if (clock_mode == CM_UTC)
133 setenv("TZ", "UTC", 1);
136 /* read rtc and system clocks "at the same time", or as
137 * precisely (+/- a second) as we can read them.
139 if (ioctl(fd, RTC_RD_TIME, &rtc) < 0) {
140 perror(_("read rtc time"));
144 if (sys_time == (time_t)-1) {
145 perror(_("read system time"));
149 /* convert rtc_time to normal arithmetic-friendly form,
150 * updating tm.tm_wday as used by asctime().
152 memset(&tm, 0, sizeof tm);
153 tm.tm_sec = rtc.tm_sec;
154 tm.tm_min = rtc.tm_min;
155 tm.tm_hour = rtc.tm_hour;
156 tm.tm_mday = rtc.tm_mday;
157 tm.tm_mon = rtc.tm_mon;
158 tm.tm_year = rtc.tm_year;
159 tm.tm_isdst = rtc.tm_isdst; /* stays unspecified? */
160 rtc_time = mktime(&tm);
162 if (rtc_time == (time_t)-1) {
163 perror(_("convert rtc time"));
168 /* Unless the system uses UTC, either delta or tzone
169 * reflects a seconds offset from UTC. The value can
170 * help sort out problems like bugs in your C library.
172 printf("\tdelta = %ld\n", sys_time - rtc_time);
173 printf("\ttzone = %ld\n", timezone);
175 printf("\ttzname = %s\n", tzname[daylight]);
176 gmtime_r(&rtc_time, &tm);
177 printf("\tsystime = %ld, (UTC) %s",
178 (long) sys_time, asctime(gmtime(&sys_time)));
179 printf("\trtctime = %ld, (UTC) %s",
180 (long) rtc_time, asctime(&tm));
186 static int setup_alarm(int fd, time_t *wakeup)
189 struct rtc_wkalrm wake;
191 /* The wakeup time is in POSIX time (more or less UTC).
192 * Ideally RTCs use that same time; but PCs can't do that
193 * if they need to boot MS-Windows. Messy...
195 * When clock_mode == CM_UTC this process's timezone is UTC,
196 * so we'll pass a UTC date to the RTC.
198 * Else clock_mode == CM_LOCAL so the time given to the RTC
199 * will instead use the local time zone.
201 tm = localtime(wakeup);
203 wake.time.tm_sec = tm->tm_sec;
204 wake.time.tm_min = tm->tm_min;
205 wake.time.tm_hour = tm->tm_hour;
206 wake.time.tm_mday = tm->tm_mday;
207 wake.time.tm_mon = tm->tm_mon;
208 wake.time.tm_year = tm->tm_year;
209 /* wday, yday, and isdst fields are unused by Linux */
210 wake.time.tm_wday = -1;
211 wake.time.tm_yday = -1;
212 wake.time.tm_isdst = -1;
215 /* First try the preferred RTC_WKALM_SET */
216 if (ioctl(fd, RTC_WKALM_SET, &wake) < 0) {
218 /* Fall back on the non-preferred way of setting wakeups; only
219 * works for alarms < 24 hours from now */
220 if ((rtc_time + (24 * 60 * 60)) > *wakeup) {
221 if (ioctl(fd, RTC_ALM_SET, &wake.time) < 0) {
222 perror(_("set rtc alarm"));
225 if (ioctl(fd, RTC_AIE_ON, 0) < 0) {
226 perror(_("enable rtc alarm"));
230 perror(_("set rtc wake alarm"));
238 static void suspend_system(const char *suspend)
240 FILE *f = fopen(SYS_POWER_STATE_PATH, "w");
243 perror(SYS_POWER_STATE_PATH);
247 fprintf(f, "%s\n", suspend);
250 /* this executes after wake from suspend */
255 static int read_clock_mode(void)
258 char linebuf[MAX_LINE];
260 fp = fopen(ADJTIME_PATH, "r");
264 /* skip first line */
265 if (!fgets(linebuf, MAX_LINE, fp)) {
270 /* skip second line */
271 if (!fgets(linebuf, MAX_LINE, fp)) {
276 /* read third line */
277 if (!fgets(linebuf, MAX_LINE, fp)) {
282 if (strncmp(linebuf, "UTC", 3) == 0)
284 else if (strncmp(linebuf, "LOCAL", 5) == 0)
285 clock_mode = CM_LOCAL;
292 int main(int argc, char **argv)
294 char *devname = DEFAULT_DEVICE;
295 unsigned seconds = 0;
296 char *suspend = DEFAULT_MODE;
302 setlocale(LC_ALL, "");
303 bindtextdomain(PACKAGE, LOCALEDIR);
306 progname = basename(argv[0]);
308 while ((t = getopt_long(argc, argv, "ahd:lm:s:t:uVv",
309 long_options, NULL)) != EOF) {
312 /* CM_AUTO is default */
316 devname = strdup(optarg);
320 clock_mode = CM_LOCAL;
323 /* what system power mode to use? for now handle only
324 * standardized mode names; eventually when systems
325 * define their own state names, parse
328 * "on" is used just to test the RTC alarm mechanism,
329 * bypassing all the wakeup-from-sleep infrastructure.
332 if (strcmp(optarg, "standby") == 0
333 || strcmp(optarg, "mem") == 0
334 || strcmp(optarg, "disk") == 0
335 || strcmp(optarg, "on") == 0
336 || strcmp(optarg, "no") == 0
338 suspend = strdup(optarg);
342 _("%s: unrecognized suspend state '%s'\n"),
346 /* alarm time, seconds-to-sleep (relative) */
351 _("%s: illegal interval %s seconds\n"),
358 /* alarm time, time_t (absolute, seconds since
365 _("%s: illegal time_t value %s\n"),
381 printf(_("%s: version %s\n"), progname, VERSION_STRING);
392 if (clock_mode == CM_AUTO) {
393 if (read_clock_mode() < 0) {
394 printf(_("%s: assuming RTC uses UTC ...\n"), progname);
399 printf(clock_mode == CM_UTC ? _("Using UTC time.\n") :
400 _("Using local time.\n"));
402 if (!alarm && !seconds) {
403 fprintf(stderr, _("%s: must provide wake time\n"), progname);
407 /* when devname doesn't start with /dev, append it */
408 if (strncmp(devname, "/dev/", strlen("/dev/")) != 0) {
411 new_devname = malloc(strlen(devname) + strlen("/dev/") + 1);
413 perror(_("malloc() failed"));
417 strcpy(new_devname, "/dev/");
418 strcat(new_devname, devname);
420 devname = new_devname;
423 if (strcmp(suspend, "on") != 0 && strcmp(suspend, "no") != 0
424 && !is_wakeup_enabled(devname)) {
425 fprintf(stderr, _("%s: %s not enabled for wakeup events\n"),
430 /* this RTC must exist and (if we'll sleep) be wakeup-enabled */
431 fd = open(devname, O_RDONLY);
437 /* relative or absolute alarm time, normalized to time_t */
438 if (get_basetimes(fd) < 0)
441 printf(_("alarm %ld, sys_time %ld, rtc_time %ld, seconds %u\n"),
442 alarm, sys_time, rtc_time, seconds);
444 if (alarm < sys_time) {
446 _("%s: time doesn't go backward to %s\n"),
447 progname, ctime(&alarm));
450 alarm += sys_time - rtc_time;
452 alarm = rtc_time + seconds + 1;
453 if (setup_alarm(fd, &alarm) < 0)
456 printf(_("%s: wakeup from \"%s\" using %s at %s\n"),
457 progname, suspend, devname,
462 if (strcmp(suspend, "no") == 0)
464 else if (strcmp(suspend, "on") != 0) {
466 suspend_system(suspend);
471 t = read(fd, &data, sizeof data);
473 perror(_("rtc read"));
477 printf("... %s: %03lx\n", devname, data);
478 } while (!(data & RTC_AF));
481 if (ioctl(fd, RTC_AIE_OFF, 0) < 0)
482 perror(_("disable rtc alarm interrupt"));