/*
 *   NVRAM WakeUp
 *   Copyright (C) 2001-2005, Sergei Haller.
 *
 *   $Id: nvram-wakeup.c 902 2008-09-19 20:29:46Z tiber $
 *
 *   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#define CVSREV_nvram_wakeup_c \
     "$Id: nvram-wakeup.c 902 2008-09-19 20:29:46Z tiber $"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#include <linux/nvram.h>

#include <linux/rtc.h>
#include <sys/ioctl.h>
#include <sys/io.h>

#include <time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#include <string.h>
#include <syslog.h>

#include "nvram-wakeup.h"

/* Use bcd2bin if necessary... */
#define FROMBCD(x) (b.bcd ? bcd2bin(x) : x)
#define TOBCD(x)   (b.bcd ? bin2bcd(x) : x)

/* this one is special for the addr_day ... */
#define FROM_D_BCD(x) (b.bcd & !b.day_no_bcd ? bcd2bin(x) : x)
#define TO_D_BCD(x)   (b.bcd & !b.day_no_bcd ? bin2bcd(x) : x)

#include "cvs_revs.h"

static int     handle_cmdline(int *argc, char ***argv);
static time_t  vdr_abs_time_t = -1;
unsigned char  oBytes[MAXNVRAMSIZE];
unsigned char  nBytes[MAXNVRAMSIZE];
int            nvramSize = 0; /* the real size of nvram (will become 114 in most cases) */
long int       wakeupbefore = WAKEUP_BEFORE;
char *         _config_file = NULL;
char *         _iw_string = NULL;
int            _nowrite = OFF;
int            _directisa = OFF;
int            _check_time = ON;

struct biosinfo     b;
     
int assign_byte(      unsigned char **oPtr,
                      unsigned char **nPtr,
                const unsigned int    addr,
                const          char  *str) {
     if (addr < nvramSize) {
          *oPtr = &oBytes[addr];
          *nPtr = &nBytes[addr];
          nvprintf(LOG_DEBUG, "value of the %-12s byte is: 0x%02X.\n", str, **oPtr);
          return 0;
     }
     else {
          nvprintf(LOG_ERR, "%s (0x%02X) is beyond the end of nvram\n", str, addr);
          if (_directisa == OFF)
               nvprintf(LOG_ERR, "You might want to use the --directisa command line option.\n");
          return 1;
     }
}


int main(int argc, char **argv) {
     int                 retval;
     int                 addr, changed = 0,
                         need_reboot = 0;
     unsigned char       *oChkH,    *oChkL;
     unsigned char       *nChkH,    *nChkL;
     unsigned int         oChk=0,    nChk=0;
     unsigned char       *oChkH2,   *oChkL2;
     unsigned char       *nChkH2,   *nChkL2;
     unsigned int         oChk2=0,   nChk2=0;
     unsigned char       *oStat,    *nStat; 
     unsigned char       *oMon,     *nMon; 
     unsigned char       *oDay,     *nDay; 
     unsigned char       *oWdays,   *nWdays; 
     unsigned char       *oHour,    *nHour; 
     unsigned char       *oMin,     *nMin; 
     unsigned char       *oSec,     *nSec; 
     unsigned char       *oRtcDay,  *nRtcDay; 
     unsigned char       *oRtcMon,  *nRtcMon; 
     struct rtc_time      oRtc_tm,   nRtc_tm;

     struct tm           vdr_abs_tm;
     struct tm           *(*get_the_time)(const time_t *);
     time_t              now;
     

     /* learn who we really are */
     set_progname(argv[0]);

     /* handle the commandline  */
     handle_cmdline(&argc, &argv);

     /* get bios info first... */
     if (_config_file) {
          /* read config file */
          if (!readconf(&b, _config_file)) {
               nvprintf(LOG_ERR, "Reading configuration file returned with errors.\n");
               exit(2);
          }
     }
     else if (_iw_string) {
          /* get corresponding config */
          if (!get_bios_info_by_iw(&b, _iw_string)) {
               nvprintf(LOG_ERR, "Infowriter '%s' not found.\n", _iw_string);
               exit(2);
          }
     }
     else {
          switch (get_bios_info_by_dmi(&b)) {
               case -1: /* all errors already printed */
                        exit(2);
                        break;  
               case  0: nvprintf(LOG_ERR, "Your mainboard is currently not supported.\n"                    );
                        nvprintf(LOG_ERR, "Please try determining the addresses and sending the following\n");
                        nvprintf(LOG_ERR, "information back to the maintainers:\n"                          );
                        nvprintf(LOG_ERR, " - The addresses you found out (read README.mb)\n"               );
                        nvprintf(LOG_ERR, " - Mainboard vendor:   %s%s%s\n", QUOTE(board_vendor() ), VALUE(board_vendor() ), QUOTE(board_vendor() ) );
                        nvprintf(LOG_ERR, " - Mainboard type:     %s%s%s\n", QUOTE(board_type()   ), VALUE(board_type()   ), QUOTE(board_type()   ) );
                        nvprintf(LOG_ERR, " - Mainboard revision: %s%s%s\n", QUOTE(board_version()), VALUE(board_version()), QUOTE(board_version()) );
                        nvprintf(LOG_ERR, " - BIOS vendor:        %s%s%s\n", QUOTE(bios_vendor()  ), VALUE(bios_vendor()  ), QUOTE(bios_vendor()  ) );
                        nvprintf(LOG_ERR, " - BIOS version:       %s%s%s\n", QUOTE(bios_version() ), VALUE(bios_version() ), QUOTE(bios_version() ) );
                        nvprintf(LOG_ERR, " - BIOS release:       %s%s%s\n", QUOTE(bios_release() ), VALUE(bios_release() ), QUOTE(bios_release() ) );
                        exit(2);
                        break;
               case  1: break; /* everything is ok. doing nothing */
          }
     }

     nvprintf(LOG_DEBUG, "Using following bios info:\n"                      );
     nvprintf(LOG_DEBUG, "   need_reboot      = %d\n",     b.need_reboot     );
     nvprintf(LOG_DEBUG, "   addr_chk_h       = 0x%02X\n", b.addr_chk_h      );
     nvprintf(LOG_DEBUG, "   addr_chk_l       = 0x%02X\n", b.addr_chk_l      );
     nvprintf(LOG_DEBUG, "   addr_chk_h2      = 0x%02X\n", b.addr_chk_h2     );
     nvprintf(LOG_DEBUG, "   addr_chk_l2      = 0x%02X\n", b.addr_chk_l2     );
     nvprintf(LOG_DEBUG, "   addr_stat        = 0x%02X\n", b.addr_stat       );
     nvprintf(LOG_DEBUG, "   addr_mon         = 0x%02X\n", b.addr_mon        );
     nvprintf(LOG_DEBUG, "   addr_day         = 0x%02X\n", b.addr_day        );
     nvprintf(LOG_DEBUG, "   addr_wdays       = 0x%02X\n", b.addr_wdays      );
     nvprintf(LOG_DEBUG, "   addr_hour        = 0x%02X\n", b.addr_hour       );
     nvprintf(LOG_DEBUG, "   addr_min         = 0x%02X\n", b.addr_min        );
     nvprintf(LOG_DEBUG, "   addr_sec         = 0x%02X\n", b.addr_sec        );
     nvprintf(LOG_DEBUG, "   shift_stat       = %d\n",     b.shift_stat      );
     nvprintf(LOG_DEBUG, "   shift_mon        = %d\n",     b.shift_mon       );
     nvprintf(LOG_DEBUG, "   shift_day        = %d\n",     b.shift_day       );
     nvprintf(LOG_DEBUG, "   shift_wdays      = %d\n",     b.shift_wdays     );
     nvprintf(LOG_DEBUG, "   shift_hour       = %d\n",     b.shift_hour      );
     nvprintf(LOG_DEBUG, "   shift_min        = %d\n",     b.shift_min       );
     nvprintf(LOG_DEBUG, "   shift_sec        = %d\n",     b.shift_sec       );
     nvprintf(LOG_DEBUG, "   rtc_time         = %d\n",     b.rtc_time        );
     nvprintf(LOG_DEBUG, "   rtc_day          = 0x%02X\n", b.rtc_day         );
     nvprintf(LOG_DEBUG, "   rtc_mon          = 0x%02X\n", b.rtc_mon         );
     nvprintf(LOG_DEBUG, "   rtc_day_0_is_c0  = %d\n",     b.rtc_day_0_is_c0 );
     nvprintf(LOG_DEBUG, "   rtc_mon_0_is_c0  = %d\n",     b.rtc_mon_0_is_c0 );
     nvprintf(LOG_DEBUG, "   reset_day        = %d\n",     b.reset_day       );
     nvprintf(LOG_DEBUG, "   reset_mon        = %d\n",     b.reset_mon       );
     nvprintf(LOG_DEBUG, "   nr_stat          = %d\n",     b.nr_stat         );
     nvprintf(LOG_DEBUG, "   nr_mon           = %d\n",     b.nr_mon          );
     nvprintf(LOG_DEBUG, "   nr_day           = %d\n",     b.nr_day          );
     nvprintf(LOG_DEBUG, "   nr_hour          = %d\n",     b.nr_hour         );
     nvprintf(LOG_DEBUG, "   nr_min           = %d\n",     b.nr_min          );
     nvprintf(LOG_DEBUG, "   nr_sec           = %d\n",     b.nr_sec          );
     nvprintf(LOG_DEBUG, "   nr_rtc_day       = %d\n",     b.nr_rtc_day      );
     nvprintf(LOG_DEBUG, "   nr_rtc_mon       = %d\n",     b.nr_rtc_mon      );
     nvprintf(LOG_DEBUG, "   nr_wdays         = %d\n",     b.nr_wdays        );
     nvprintf(LOG_DEBUG, "   bcd              = %d\n",     b.bcd             );
     nvprintf(LOG_DEBUG, "   day_hack         = %d\n",     b.day_hack        );
     nvprintf(LOG_DEBUG, "   upper_method     = %d\n",     b.upper_method    );
     nvprintf(LOG_DEBUG, "   chk_method       = %d\n",     b.chk_method      );

     if (b.chk_method == FSC) {
          nvprintf(LOG_ERR, "Checksum algorithm by FSC is unsupported!\n");
          exit(2);
     }

     /* open the rtc device */
     nvprintf(LOG_DEBUG, "Opening %s in O_RDONLY mode...\n", RTC_DEV);
     fd_rtc = open(RTC_DEV, O_RDONLY);
     if (fd_rtc == -1) {
          nvprintf(LOG_ERR, "%s: %m\n", RTC_DEV);
          exit(2);
     }

     /* First, we have to know if the hardware clock runs local or UTC/GMT time */
     switch (compare_ltm_rtc()) {
     case -1:
          get_the_time = NULL;
          nvprintf(LOG_ERR, "RTC is not synchronously with system time!\n");
          nvprintf(LOG_ERR, "RTC isn't running in localtime nor in UTC/GMT time!\n");
          exit(2);
          break;
     case  0:
          get_the_time = localtime;
          nvprintf(LOG_DEBUG, "RTC is running in localtime!\n");
          break;
     case  1:
          get_the_time = gmtime;
          nvprintf(LOG_DEBUG, "RTC is running in UTC/GMT!\n");
          break;
     default:
          get_the_time = NULL;
          exit(2);
          break;
     }

     now = time(NULL);
     nvprintf(LOG_DEBUG, "Test (this should be the current time of the hardware clock): %s", 
                         asctime((*get_the_time)(&now)));

     /* close the rtc device again */
     close(fd_rtc);

     /* Fill oBytes with 0s */
     memset(oBytes, 0, sizeof(oBytes));

     /* Now we read whole nvram ... */
     open_nvram(_directisa, O_RDONLY);
     nvramSize = read_whole_nvram(_directisa, b.upper_method, oBytes);
     close_nvram(_directisa);

     /* print a nice xxd-like table of nvram */
     xxd(oBytes, nvramSize, LOG_DEBUG);

     /* Copy whole contents of oBytes to nBytes */
     memcpy(nBytes, oBytes, sizeof(oBytes));

     if (b.addr_stat   && assign_byte(&oStat,    &nStat,    b.addr_stat,   "addr_stat"  )) exit(2);
     if (b.addr_mon    && assign_byte(&oMon,     &nMon,     b.addr_mon,    "addr_mon"   )) exit(2);
     if (b.addr_day    && assign_byte(&oDay,     &nDay,     b.addr_day,    "addr_day"   )) exit(2);
     if (b.addr_wdays  && assign_byte(&oWdays,   &nWdays,   b.addr_wdays,  "addr_wdays" )) exit(2);
     if (b.addr_hour   && assign_byte(&oHour,    &nHour,    b.addr_hour,   "addr_hour"  )) exit(2);
     if (b.addr_min    && assign_byte(&oMin,     &nMin,     b.addr_min,    "addr_min"   )) exit(2);
     if (b.addr_sec    && assign_byte(&oSec,     &nSec,     b.addr_sec,    "addr_sec"   )) exit(2);
     if (b.rtc_day     && assign_byte(&oRtcDay,  &nRtcDay,  b.rtc_day,     "rtc_day"    )) exit(2);
     if (b.rtc_mon     && assign_byte(&oRtcMon,  &nRtcMon,  b.rtc_mon,     "rtc_mon"    )) exit(2);
     if (b.addr_chk_h  && assign_byte(&oChkH,    &nChkH,    b.addr_chk_h,  "addr_chk_h" )) exit(2);
     if (b.addr_chk_l  && assign_byte(&oChkL,    &nChkL,    b.addr_chk_l,  "addr_chk_l" )) exit(2);
     if (b.addr_chk_h2 && assign_byte(&oChkH2,   &nChkH2,   b.addr_chk_h2, "addr_chk_h2")) exit(2);
     if (b.addr_chk_l2 && assign_byte(&oChkL2,   &nChkL2,   b.addr_chk_l2, "addr_chk_l2")) exit(2);

     if (b.rtc_time == ON) {
          /* open the rtc device */
          nvprintf(LOG_DEBUG, "Opening %s in O_RDONLY mode...\n", RTC_DEV);
          fd_rtc   = open( RTC_DEV, O_RDONLY );
          if (fd_rtc ==  -1) {
               nvprintf(LOG_ERR, "%s: %m\n", RTC_DEV);
               exit(2);
          }
          /* we read rtc alarm in this case */
          retval = ioctl(fd_rtc, RTC_ALM_READ, &oRtc_tm);
          if (retval == -1) {
               nvprintf(LOG_ERR, "%s: %m\n", "ioctl RTC_ALM_READ");
               exit(2);
          }
          /* close the rtc device again */
          close(fd_rtc);
     }


     /* now put the two checksum bytes into one checksum */
     if (b.addr_chk_h && b.addr_chk_l) {
         oChk = (*oChkH << 8) | *oChkL;
         nvprintf(LOG_DEBUG, "Checksum is: 0x%04X.\n", oChk);
     }
     if(b.addr_chk_h2) {
         oChk2 = (*oChkH2 << 8) | *oChkL2;
         nvprintf(LOG_DEBUG, "Checksum2 is: 0x%04X.\n", oChk2);
     }


/* Now we will check all values for their correctness / plausibility... */

     if (b.addr_mon && FROMBCD(calculate_read(*oMon, b.nr_mon, b.shift_mon)) > 12) {
          nvprintf(LOG_ERR, "WakeUp Month value (%02u) not correct.\n",
                             FROMBCD(calculate_read(*oMon, b.nr_mon, b.shift_mon)));
          exit(2);
     }

     if (b.addr_day) {
          int day = 0;

          if (b.day_hack)
               /* on this boards the day is split over the day byte and the stat byte */
               day  = calculate_read(*oStat, b.day_hack,          8-b.day_hack ) 
                    | calculate_read(*oDay,  b.nr_day-b.day_hack, 0            )<<b.day_hack;
          else
               day  = FROM_D_BCD(calculate_read(*oDay, b.nr_day, b.shift_day));

          if (day > 31) {
               nvprintf(LOG_ERR, "WakeUp Day value (%02u) not correct.\n", day);
               exit(2);
          }
     }

     if (b.addr_wdays) {
         /* I think, there's nothing to check - every bit could be set */
         /* or not. The only thing I could think of is that at least   */
         /* one bit has to be set. But I don't really know...          */
     }

     if (b.addr_hour && FROMBCD(calculate_read(*oHour, b.nr_hour, b.shift_hour)) > 23) {
          nvprintf(LOG_ERR, "WakeUp Hour value (%02u) not correct.\n",
                             FROMBCD(calculate_read(*oHour, b.nr_hour, b.shift_hour)));
          exit(2);
     }

     if (b.addr_min && FROMBCD(calculate_read(*oMin, b.nr_min, b.shift_min)) > 59) {
          nvprintf(LOG_ERR, "WakeUp Minute value (%02u) not correct.\n",
                             FROMBCD(calculate_read(*oMin, b.nr_min, b.shift_min)));
          exit(2);
     }

     if (b.addr_sec && FROMBCD(calculate_read(*oSec, b.nr_sec, b.shift_sec)) > 59) {
          nvprintf(LOG_ERR, "WakeUp Second value (%02u) not correct.\n",
                             FROMBCD(calculate_read(*oSec, b.nr_sec, b.shift_sec)));
          exit(2);
     }

     if(b.rtc_day) {
          int day = calculate_read(*oRtcDay, b.nr_rtc_day, 0);
          if (b.rtc_day_0_is_c0 && day == 0xC0)
               day=0;
          day = bcd2bin(day);
          if (day > 31) {
               nvprintf(LOG_ERR, "WakeUp rtcDay value (%02u) not correct.\n",
                                  day);
               exit(2);
         }
     }
     if(b.rtc_mon) {
          int mon = calculate_read(*oRtcMon, b.nr_rtc_mon, 0);
          if (b.rtc_mon_0_is_c0 && mon == 0xC0)
               mon = 0;
          mon = bcd2bin(mon);
          if (mon > 12) {
               nvprintf(LOG_ERR, "WakeUp rtcMon value (%02u) not correct.\n",
                                  mon);
               exit(2);
         }
     }

     /* we should compare both values in case they are      */
     /* stored in both places.                              */
     /* for now we will just output both values below       */
     if (b.addr_day  && b.rtc_day ) {}
     if (b.addr_mon  && b.rtc_mon ) {}

     /* we should compare the values with those from nvram  */
     /* in case they are stored in both places.             */
     /* for now we will just output both values below       */
     if(b.rtc_time == ON) {}

     /* Output stored values */
     nvprintf(LOG_NOTICE, "\n");
     nvprintf(LOG_NOTICE, "All values are displayed as they are stored in the nvram/rtc.\n");
     nvprintf(LOG_NOTICE, "(and do not correspond necessarily to the system date/time)\n");
     nvprintf(LOG_NOTICE, "\n");

     nvprintf(LOG_NOTICE, "WakeUp  : %s (0x%02X)\n",
                           calculate_read(*oStat, b.nr_stat, b.shift_stat) ? "Enabled" : "Disabled", *oStat);
     if (b.addr_mon)
          nvprintf(LOG_NOTICE, "Month   : %02u (0x%02X)\n",
                                FROMBCD(calculate_read(*oMon, b.nr_mon, b.shift_mon)), *oMon);

     if (b.addr_day) {
          int d, od;
          if (b.day_hack) {
               d = calculate_read(*oStat, b.day_hack,         8-b.day_hack) 
                 | calculate_read(*oDay, b.nr_day-b.day_hack, 0            )<<b.day_hack;
               od = *oStat<<8|*oDay;
          } else {
               d  = FROM_D_BCD(calculate_read(*oDay, b.nr_day, b.shift_day));
               od = *oDay;
          }
          nvprintf(LOG_NOTICE, "Day     : %02u (0x%02X)\n", d, od);
     }

     if (b.addr_wdays) {
          unsigned char c = calculate_read(*oWdays, b.nr_wdays, b.shift_wdays);
          nvprintf(LOG_NOTICE, "Weekdays: %c%c%c%c%c%c%c (0x%02X)\n",
                    c & (1 << 1) ? 'M' : '-', /* Monday    */
                    c & (1 << 2) ? 'T' : '-', /* Tuesday   */
                    c & (1 << 3) ? 'W' : '-', /* Wednesday */
                    c & (1 << 4) ? 'T' : '-', /* Thursday  */
                    c & (1 << 5) ? 'F' : '-', /* Friday    */
                    c & (1 << 6) ? 'S' : '-', /* Saturday  */
                    c & (1     ) ? 'S' : '-', /* Sunday    */
                    *oWdays);
     }

     if (b.addr_hour)
          nvprintf(LOG_NOTICE, "Hour    : %02u (0x%02X)\n",
                                FROMBCD(calculate_read(*oHour, b.nr_hour, b.shift_hour)), *oHour);

     if (b.addr_min)
          nvprintf(LOG_NOTICE, "Minute  : %02u (0x%02X)\n",
                                FROMBCD(calculate_read(*oMin,  b.nr_min,  b.shift_min)),  *oMin );

     if (b.addr_sec)
          nvprintf(LOG_NOTICE, "Second  : %02u (0x%02X)\n",
                                FROMBCD(calculate_read(*oSec,  b.nr_sec,  b.shift_sec)),  *oSec );

     if (b.rtc_mon) {
          int mon = calculate_read(*oRtcMon, b.nr_rtc_mon, 0);
          if (b.rtc_mon_0_is_c0 && mon==0XC0)
               mon=0;
          mon = bcd2bin(mon);
          nvprintf(LOG_NOTICE, "rtcMon  : %02u (0x%02X)\n",
                                mon, *oRtcMon);
     }

     if (b.rtc_day) {
          int day = calculate_read(*oRtcDay, b.nr_rtc_day, 0);
          if (b.rtc_day_0_is_c0 && day==0xC0)
               day=0;
          day = bcd2bin(day);
          nvprintf(LOG_NOTICE, "rtcDay  : %02u (0x%02X)\n",
                                day, *oRtcDay);
     }

     if (b.rtc_time==ON) {
         /* for now we will just output rtc values. read above.     */
         nvprintf(LOG_NOTICE, "rtcHour : %02u\n", oRtc_tm.tm_hour);
         nvprintf(LOG_NOTICE, "rtcMin  : %02u\n", oRtc_tm.tm_min );
         nvprintf(LOG_NOTICE, "rtcSec  : %02u\n", oRtc_tm.tm_sec );
     }

     if (b.addr_chk_h && b.addr_chk_l)
         nvprintf(LOG_NOTICE, "Checksum: 0x%04X\n", oChk);
     if (b.addr_chk_h2 && b.addr_chk_l2)
         nvprintf(LOG_NOTICE, "Checksum2:0x%04X\n", oChk2);
     nvprintf(LOG_NOTICE, "\n");

     if (vdr_abs_time_t == -1) {
          /* There is no time specified to be set. We are done. */
          exit(0);
     }
     
/* Calculate all new values to be written back into nvram */
     
     if (( vdr_abs_time_t != 0 ) && 
         ( _check_time ) &&
         ( vdr_abs_time_t < time(NULL) + (NEED_TO_SHTDWN+wakeupbefore)*60 )) {
          /*
           *  If the time to be set is not at least 
           *             (NEED_TO_SHTDWN + wakeupbefore) minutes
           *  in the future,
           *                       do nothing
           */
          nvprintf(LOG_ERR, "Do NOT write into nvram. Wake Up time must be\n");
          nvprintf(LOG_ERR, "at least %d minutes in the future.\n", (NEED_TO_SHTDWN + wakeupbefore));
          exit(2);
     }
     if (vdr_abs_time_t == 0) {
          /*
           *  we have to disable Wakeup
           */
          if (calculate_read(*nStat, b.nr_stat, b.shift_stat)) {
               *nStat = calculate_write(*nStat, OFF, b.nr_stat, b.shift_stat);

               /* this board stores rtc_day/rtc_mon=0 if WakeUp is disabled */
               if (b.rtc_mon  && b.reset_mon  == ON)
                   *nRtcMon  = b.rtc_mon_0_is_c0  ? 0xC0 : bin2bcd(calculate_write(*nRtcMon,  0, b.nr_rtc_mon,  0));
               if (b.rtc_day  && b.reset_day  == ON)
                   *nRtcDay = b.rtc_day_0_is_c0 ? 0xC0 : bin2bcd(calculate_write(*nRtcDay, 0, b.nr_rtc_day, 0));
          }

          nvprintf(LOG_NOTICE, "Disabling (0x%02X) WakeUp-on-RTC in nvram.\n", *nStat);

          if (b.rtc_mon  && b.reset_mon  == ON)
               nvprintf(LOG_NOTICE, "New rtcMon  : %02u (0x%02X)\n", 
                                     bcd2bin(calculate_read(*nRtcMon,  b.nr_rtc_mon,  0)), *nRtcMon );
          if (b.rtc_day  && b.reset_day  == ON)
               nvprintf(LOG_NOTICE, "New rtcDay  : %02u (0x%02X)\n", 
                                     bcd2bin(calculate_read(*nRtcDay, b.nr_rtc_day, 0)), *nRtcDay);
     } else {
          /*
           *  we have to enable Wakeup, but
           *  we want to wake up wakeupbefore minutes earlier.
           */
          vdr_abs_time_t -= wakeupbefore*60;               
          vdr_abs_tm = *get_the_time(&vdr_abs_time_t);

          if (!calculate_read(*nStat, b.nr_stat, b.shift_stat))
               *nStat = calculate_write(*nStat, ON, b.nr_stat, b.shift_stat);
          
          if (b.addr_mon && calculate_read(*nMon, b.nr_mon, b.shift_mon) != (vdr_abs_tm.tm_mon+1))
               *nMon = calculate_write(*nMon, vdr_abs_tm.tm_mon+1, b.nr_mon, b.shift_mon);

          if (b.addr_day) {
               if (b.day_hack) {
                    int day = calculate_read(*nStat, b.day_hack,           8-b.day_hack) 
                             | calculate_read(*nDay, b.nr_day-b.day_hack, 0            )<<b.day_hack;
                    if (day != vdr_abs_tm.tm_mday) {
                         *nStat = calculate_write(*nStat, vdr_abs_tm.tm_mday & (0x0F >> (4-b.day_hack)), b.day_hack,           8-b.day_hack);
                         *nDay = calculate_write(*nDay, vdr_abs_tm.tm_mday >> b.day_hack,            b.nr_day-b.day_hack, 0            );
                    }
               } else {
                    if (FROM_D_BCD(calculate_read(*nDay, b.nr_day, b.shift_day)) != vdr_abs_tm.tm_mday)
                         *nDay = calculate_write(*nDay, TO_D_BCD(vdr_abs_tm.tm_mday), b.nr_day, b.shift_day);
               }
          }

          if (b.addr_wdays) {
               unsigned char weekday = 1 << vdr_abs_tm.tm_wday;
               nvprintf(LOG_DEBUG, "vdr_abs_tm.tm_wday = %d\n", vdr_abs_tm.tm_wday);
               if (calculate_read(*nWdays, b.nr_wdays, b.shift_wdays) != weekday)
                    *nWdays = calculate_write(*nWdays, weekday, b.nr_wdays, b.shift_wdays);
          }

          if (b.addr_hour) {
               if ( FROMBCD(calculate_read(*nHour, b.nr_hour, b.shift_hour)) != vdr_abs_tm.tm_hour)
                  *nHour = calculate_write(*nHour, TOBCD(vdr_abs_tm.tm_hour), b.nr_hour, b.shift_hour);
          }

          if (b.addr_min) {
               if ( FROMBCD(calculate_read(*nMin, b.nr_min, b.shift_min)) != vdr_abs_tm.tm_min)
                    *nMin = calculate_write(*nMin, TOBCD(vdr_abs_tm.tm_min), b.nr_min, b.shift_min);
          }

          if (b.addr_sec) {
               if ( FROMBCD(calculate_read(*nSec, b.nr_sec, b.shift_sec)) != vdr_abs_tm.tm_sec)
                    *nSec = calculate_write(*nSec, TOBCD(vdr_abs_tm.tm_sec % 60), b.nr_sec, b.shift_sec);
          }

          if (b.rtc_mon) {
               if ( bcd2bin(calculate_read(*nRtcMon, b.nr_rtc_mon, 0)) != (vdr_abs_tm.tm_mon+1))
                    *nRtcMon = calculate_write(*nRtcMon, bin2bcd(vdr_abs_tm.tm_mon+1), b.nr_rtc_mon, 0);
          }

          if (b.rtc_day) {
               if ( bcd2bin(calculate_read(*nRtcDay, b.nr_rtc_day, 0)) != vdr_abs_tm.tm_mday)
                    *nRtcDay = calculate_write(*nRtcDay, bin2bcd(vdr_abs_tm.tm_mday), b.nr_rtc_day, 0);
          }

          if (b.rtc_time==ON) {
               nRtc_tm.tm_sec   = vdr_abs_tm.tm_sec  ;
               nRtc_tm.tm_min   = vdr_abs_tm.tm_min  ;
               nRtc_tm.tm_hour  = vdr_abs_tm.tm_hour ;
          /* we shouldn't need the following lines ...
           *   nRtc_tm.tm_mday  = vdr_abs_tm.tm_mday ;
           *   nRtc_tm.tm_mon   = vdr_abs_tm.tm_mon  ;
           *   nRtc_tm.tm_year  = vdr_abs_tm.tm_year ;
           *   nRtc_tm.tm_wday  = vdr_abs_tm.tm_wday ;
           *   nRtc_tm.tm_yday  = vdr_abs_tm.tm_yday ;
           *   nRtc_tm.tm_isdst = vdr_abs_tm.tm_isdst;
           */
          }


          nvprintf(LOG_NOTICE, "Enabling (0x%02X) WakeUp-on-RTC in nvram.\n", *nStat);
          if (b.addr_mon)
               nvprintf(LOG_NOTICE, "New Month   : %02u (0x%02X)\n", 
                                     FROMBCD(calculate_read(*nMon, b.nr_mon, b.shift_mon)), *nMon);

          if (b.addr_day) {
               int d, nd;
               if (b.day_hack) {
                    d = calculate_read(*nStat, b.day_hack,           8-b.day_hack) 
                      | calculate_read(*nDay, b.nr_day-b.day_hack, 0            )<<b.day_hack;
                    nd = *nStat<<8|*nDay;
               } else {
                    d  = FROM_D_BCD(calculate_read(*nDay, b.nr_day, b.shift_day));
                    nd = *nDay;
               }
               nvprintf(LOG_NOTICE, "New Day     : %02u (0x%02X)\n",  d, nd);
          }
       
          if (b.addr_wdays) {
               unsigned char c = calculate_read(*nWdays, b.nr_wdays, b.shift_wdays);
               nvprintf(LOG_NOTICE, "New Weekdays: %c%c%c%c%c%c%c (0x%02X)\n",
                      c & (1 << 1) ? 'M' : '-', /* Monday    */
                      c & (1 << 2) ? 'T' : '-', /* Tuesday   */
                      c & (1 << 3) ? 'W' : '-', /* Wednesday */
                      c & (1 << 4) ? 'T' : '-', /* Thursday  */
                      c & (1 << 5) ? 'F' : '-', /* Friday    */
                      c & (1 << 6) ? 'S' : '-', /* Saturday  */
                      c & (1     ) ? 'S' : '-', /* Sunday    */
                      *nWdays);
          }

          if (b.addr_hour) {
               nvprintf(LOG_NOTICE, "New Hour    : %02u (0x%02X)\n",
                     FROMBCD(calculate_read(*nHour, b.nr_hour, b.shift_hour)), *nHour);
          }

          if (b.addr_min) {
               nvprintf(LOG_NOTICE, "New Minute  : %02u (0x%02X)\n", 
                              FROMBCD(calculate_read(*nMin, b.nr_min, b.shift_min)), *nMin);
          }

          if (b.addr_sec) {
               nvprintf(LOG_NOTICE, "New Second  : %02u (0x%02X)\n", 
                            FROMBCD(calculate_read(*nSec, b.nr_sec, b.shift_sec)), *nSec);
          }

          if (b.rtc_mon) {
               nvprintf(LOG_NOTICE, "New rtcMon  : %02u (0x%02X)\n", 
                            bcd2bin(calculate_read(*nRtcMon, b.nr_rtc_mon, 0)), *nRtcMon);
          }

          if (b.rtc_day) {
               nvprintf(LOG_NOTICE, "New rtcDay  : %02u (0x%02X)\n", 
                            bcd2bin(calculate_read(*nRtcDay, b.nr_rtc_day, 0)), *nRtcDay);
          }

          if (b.rtc_time == ON) {
               nvprintf(LOG_NOTICE, "New rtcHour : %02u\n", nRtc_tm.tm_hour);
               nvprintf(LOG_NOTICE, "New rtcMin  : %02u\n", nRtc_tm.tm_min );
               nvprintf(LOG_NOTICE, "New rtcSec  : %02u\n", nRtc_tm.tm_sec );
          }

     }

     /*
      *  the month needs          4 bits,
      *  the day and hour need    5 bits each,
      *  the minute and second -- 6 bits each,
      *  the weekday vector    -- 7 bits,
      *  the status needs only 1 bit.
      *
      *  So the only possible mix is if the status bit is stored in
      *  the same byte with either one of the others.
      */

     if (b.addr_chk_h && b.addr_chk_l)
          nChk  = oChk;
          
     if (b.addr_chk_h2 && b.addr_chk_l2) {
          nChk2 = oChk2;
          nChk2 += *nStat - *oStat;
     } else if (b.addr_chk_h && b.addr_chk_l) {
          if (b.chk_method == DELL)
               nChk  += *oStat - *nStat;
          else
               nChk  += *nStat - *oStat;
     }

     if (*nStat - *oStat)
          need_reboot |= (b.need_reboot) & ON_STAT;

     if (b.addr_mon) {
          if (*nMon  - *oMon) {
               need_reboot |= (b.need_reboot) & ON_MON;
               if ((b.addr_stat != b.addr_mon) && b.addr_chk_h && b.addr_chk_l) {
                    if (b.chk_method == DELL)
                         {}
                    else
                         nChk  += *nMon  - *oMon;
               }
          }
     }

     if (b.addr_day) {
          if (*nDay - *oDay) {
               need_reboot |= (b.need_reboot) & ON_DAY;
               if ((b.addr_day  != b.addr_stat) && b.addr_chk_h && b.addr_chk_l) {
                    if (b.chk_method == DELL)
                         {}
                    else
                         nChk  += *nDay - *oDay;
               }
          }
     }

     if (b.addr_wdays) {
          if (*nWdays - *oWdays) {
               need_reboot |= (b.need_reboot) & ON_WDAYS;
               if (b.addr_wdays!=b.addr_stat && b.addr_chk_h && b.addr_chk_l) {
                    if (b.chk_method == DELL)
                         {}
                    else
                         nChk  += *nWdays - *oWdays;
               }
          }
     }

     if (b.addr_hour) {
          if (*nHour - *oHour) {
               need_reboot |= (b.need_reboot) & ON_HOUR;
               if ((b.addr_hour!=b.addr_stat) && b.addr_chk_h && b.addr_chk_l) {
                    if (b.chk_method == DELL)
                         {}
                    else
                         nChk  += *nHour - *oHour;
               }
          }
     }

     if (b.addr_min) {
          if (*nMin  - *oMin) {
               need_reboot |= (b.need_reboot) & ON_MIN;
               if ((b.addr_min != b.addr_stat) && b.addr_chk_h && b.addr_chk_l) {
                    if (b.chk_method == DELL)
                         {}
                    else
                         nChk  += *nMin  - *oMin;
               }
          }
     }

     if (b.addr_sec) {
          if (*nSec  - *oSec) {
               need_reboot |= (b.need_reboot) & ON_SEC;
               if ((b.addr_sec != b.addr_stat) && b.addr_chk_h && b.addr_chk_l) {
                    if (b.chk_method == DELL)
                         {}
                    else
                         nChk  += *nSec  - *oSec;
               }
          }
     }

     if (b.addr_chk_h && b.addr_chk_l) {
          *nChkL =  nChk & 0x00FF;
          *nChkH = (nChk & 0xFF00) >> 8; /*  in the case of an overflow we just throw away
                                          *  the bits higher than the 16 bits we could store
                                          */
     }

     if (b.addr_chk_h2 && b.addr_chk_l2) {
          *nChkL2 =  nChk2 & 0x00FF;
          *nChkH2 = (nChk2 & 0xFF00) >> 8;
     }

     if (b.addr_chk_h && b.addr_chk_l)
          nvprintf(LOG_NOTICE, "New Checksum: 0x%02X%02X\n", *nChkH, *nChkL);
     if (b.addr_chk_h2 && b.addr_chk_l2)
          nvprintf(LOG_NOTICE, "New Checksum2: 0x%02X%02X\n", *nChkH2, *nChkL2);
     nvprintf(LOG_NOTICE, "\n");

     /* Check if something changed and write the values back, if needed ... */
     for (addr=0; addr < nvramSize ; addr++)
          if (oBytes[addr] - nBytes[addr]) {
               nvprintf(LOG_DEBUG, "Byte 0x%02X is changed: 0x%02X -> 0x%02X\n", addr, oBytes[addr], nBytes[addr]);
               /* Now really WRITING into /dev/nvram   */
               if (!changed) {
                    if (! _nowrite) {
                         nvprintf(LOG_NOTICE, "Now really WRITING into %s...\n", NVRAM_DEV);
                         open_nvram( _directisa, O_WRONLY );
                    }
                    changed = 1;
               }
               if (! _nowrite) {
                    write_back(_directisa, b.upper_method, addr, nBytes);
               }
          }
     if (! _nowrite) {
          /* close the nvram device again */
          if (changed) close_nvram(_directisa);
     }

     if (b.rtc_time == ON) {
          if (vdr_abs_time_t != 0 &&
             (nRtc_tm.tm_sec  != oRtc_tm.tm_sec ||
              nRtc_tm.tm_min  != oRtc_tm.tm_min ||
              nRtc_tm.tm_hour != oRtc_tm.tm_hour  )  ) {

               nvprintf(LOG_DEBUG, "RTC time is changed. Setting RTC alarm into %s\n", RTC_DEV);

               /* Setting RTC alarm into /dev/rtc   */
               if (!changed) {
                    if (! _nowrite)
                         nvprintf(LOG_NOTICE, "Setting RTC alarm into %s...\n", RTC_DEV);
                    changed = 1;
               } else {
                    if (! _nowrite)
                         nvprintf(LOG_NOTICE, "And setting RTC alarm into %s...\n", RTC_DEV);
               }
            
               if (! _nowrite) {
                    nvprintf(LOG_NOTICE, "\n");

                    /* open the rtc device */
                    nvprintf(LOG_DEBUG, "Opening %s in O_RDONLY mode...\n", RTC_DEV);
                    fd_rtc   = open( RTC_DEV, O_RDONLY );
                    if (fd_rtc ==  -1) {
                         nvprintf(LOG_ERR, "%s: %m\n", RTC_DEV);
                         exit(2);
                    }
       
                    retval = ioctl(fd_rtc, RTC_ALM_SET, &nRtc_tm);
                    if (retval == -1) {
                       nvprintf(LOG_CRIT, "Ioctl RTC_ALM_SET failed: %m\n");
                       exit(2);
                    }

                    /* Enable alarm interrupts */
                    /* Do we need this?        */
                    /*
                     *  retval = ioctl(fd_rtc, RTC_AIE_ON, 0);
                     *  if (retval == -1) {
                     *       nvprintf(LOG_CRIT, "ioctl RTC_AIE_ON: %m\n");
                     *       exit(2);
                     *  }
                     */

                    /* close the rtc device again */
                    close(fd_rtc);
               }
          }
     }

     nvprintf(LOG_DEBUG, "need_reboot: %d\n", need_reboot);

     if (!changed)
        /* there was nothing to do: all values were already correct */ 
        exit(0);
     else {
          if (_nowrite) {
               nvprintf(LOG_NOTICE, "   Actually, nothing was written into %s nor into %s.\n", NVRAM_DEV, RTC_DEV);
               nvprintf(LOG_NOTICE, "   (since --nowrite option was used).\n");
               nvprintf(LOG_NOTICE, "   \n");
               exit(2);
          } else {
               if (need_reboot) {
                    nvprintf(LOG_NOTICE, "   The changes to take effect, you must reboot your computer now.\n");
                    nvprintf(LOG_NOTICE, "   \n");
                    exit(1);
               } else {
                    /* don't need to reboot. */
                    exit(0);
               }
          }
     }
} /* end main */


/*
 * Parse command line arguments
 */
struct option opts[] = {
     {"settime",      1, 0, 's'},
     {"disable",      0, 0, 'd'},
     {"configfile",   1, 0, 'C'},
     {"iwname",       1, 0, 'I'},
     {"directisa",    0, 0, 'A'},
     {"nowrite",      0, 0, 'N'},
     {"debug",        0, 0, 'D'},
     {"syslog",       0, 0, 'l'},
     {"wakeupbefore", 1, 0, 'w'},
     {"version",      0, 0, 'v'},
     {"help",         0, 0, 'h'},
     {"nocheck",      0, 0, 'n'},
     {NULL,           0, 0,  0 },
};

const char *optstring = "s:dC:I:ANDlw:vhn";

const char *opts_help[] = {
     "Set the given wakeup date/time (given as a time_t value).",          /* settime    */
     "Disable WakeUp. Equivalent to --settime=0.",                         /* disable    */
     "Read board configuration from specified configuration file.",        /* configfile */
     "Specify the IW (infowriter) name.",                                  /* iwname     */
     "Use direct ISA access to read/write nvram instead of /dev/nvram.",   /* directisa  */
     "Don't write any values (in /dev/nvram or /dev/rtc). For testing.",   /* nowrite    */
     "Enable printing debug messages.",                                    /* debug      */
     "Log all output via syslogd instead of stdout/stderr.",               /* syslog     */
     "Start that many minutes before the wakeuptime (default is 5).",      /* wakeupbefore */
     "Print version information.",                                         /* version    */
     "Print this message (always to stderr, regardless of --syslog).",     /* help       */
     "Don't check if the time argument is in the future",                  /* nocheck    */
};

void print_usage(void) {
     int max, size;
     struct option *opt;
     const char **hlp;

     nvprintf(LOG_INFO, "\n");
     nvprintf(LOG_INFO, "Usage: %s [OPTIONS]\n", get_progname());
     max = 0;
     for (opt = opts; opt->name; opt++) {
             size = strlen(opt->name);
             if (size > max)
                     max = size;
     }

     for (opt = opts, hlp = opts_help; opt->name; opt++, hlp++)
               nvprintf(LOG_INFO, "  -%c, --%-*s%s\n", opt->val, max+2, opt->name, *hlp);

     nvprintf(LOG_INFO, "\n");
     nvprintf(LOG_INFO, "  All specified wakeup times are times at which the PC should be up and running.\n");
     nvprintf(LOG_INFO, "\n");
     exit(2);

}

/*
 *      opterr = 0;
 */

static int
handle_cmdline(int *argc, char ***argv)
{
     for (;;) {
          int i = getopt_long(*argc, *argv, optstring, opts, NULL);
          if (i == -1)
               break;
          switch (i) {
/*
 *           case '?':
 *                nvprintf(LOG_ERR, "invalid option: %d,%s,%c\n", optind, (*argv)[optind-1], optopt);
 *                print_usage();
 *                break;
 */
          case 's':
               if ( strlen(optarg) > strspn(optarg, "0123456789") ) {
                    /* if the time is not a number */
                    nvprintf(LOG_ERR, "\"%s\" is not a number\n", optarg);
                    print_usage();
               }
               else if ( vdr_abs_time_t >= 0 && strtol(optarg, NULL, 10) != vdr_abs_time_t ) {
                    /* if two different times specified at the same time */
                    nvprintf(LOG_ERR, "two different times specified at the same time\n");
                    print_usage();
               } 
               else {
                    vdr_abs_time_t = strtol(optarg, NULL, 10);
               }
               break;
          case 'd':
               if (vdr_abs_time_t > 0) {
                    /* if -s positivetime and -d options given at the same time */
                    nvprintf(LOG_ERR, "-s %u and -d specified at the same time\n",
                                      (unsigned int)vdr_abs_time_t);
                    print_usage();
               } 
               else {
                    vdr_abs_time_t = 0;
               }
               break;
          case 'C':
               if (_config_file && strcmp(_config_file, optarg)) {
                    nvprintf(LOG_ERR, "Two different configuration files specified.\n");
                    print_usage();
               }
               _config_file = optarg;
               break;
          case 'I':
               if (_iw_string && strcmp(_iw_string, optarg)) {
                    nvprintf(LOG_ERR, "Two different infowriter strings specified.\n");
                    print_usage();
               }
               _iw_string = optarg;
               break;
          case 'D':
               enable_debug();

               nvprintf(LOG_DEBUG, "Printing debug messages enbled.\n");
               for(i=0; CVS_ALL[i]; i++)
                    nvprintf(LOG_DEBUG, "%s\n", CVS_ALL[i]);
               nvprintf(LOG_DEBUG, "Built at: " __DATE__ " " __TIME__ "\n");

               break;
          case 'A':
               nvprintf(LOG_DEBUG, "Direct ISA access enabled.\n");
               _directisa = ON;
               break;
          case 'N':
               _nowrite = ON;
               break;
          case 'n':
               _check_time = OFF;
               break;
          case 'l':
               enable_syslog();
               openlog( get_progname(), LOG_CONS || LOG_PID, LOG_USER );
               fprintf(stderr, "--syslog parameter specified. All output goes to the syslog\n");
               break;
          case 'v':
               nvprintf(LOG_NOTICE, "NVRAM WakeUp - Copyright (C) 2001-2005, Sergei Haller.\n");
               nvprintf(LOG_NOTICE, PACKAGE "-" VERSION "\n");
               exit(0);
          case 'w':
               if ( strlen(optarg) > strspn(optarg, "0123456789") ) {
                    /* if the argument is not a number */
                    nvprintf(LOG_ERR, "\"%s\" is not a number\n", optarg);
                    print_usage();
               }
               else if ( strtol(optarg, NULL, 10) < 0 ) {
                    /* argument must be >= 0 */
                    nvprintf(LOG_ERR, "wakeupbefore must be bigger or equal 0.\n");
                    print_usage();
               } 
               else {
                    wakeupbefore = strtol(optarg, NULL, 10);
               }
               break;
          case 'h':
          default:
               print_usage();
               break;
          }
     }
 
     *argc -= optind;
     *argv += optind;
     
     if (*argc > 0) {
          nvprintf(LOG_ERR, "invalid parameter: %s\n",
                            *argv[0]);
          print_usage();
     }
     
     if (_config_file && _iw_string)
          nvprintf(LOG_WARNING, "Configuration file and IW name specified at command line -- ignoring IW name\n");
     
     return 0;
}



