/*
 *   NVRAM WakeUp
 *   Copyright (C) 2001-2005, Sergei Haller.
 *   Copyright (C) 2002, Bernhard Rosenkraenzer <bero@arklinux.org>
 *
 *   $Id: guess.c 835 2005-03-16 22:43:39Z bistr-o-math $
 *
 *   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_guess_c \
     "$Id: guess.c 835 2005-03-16 22:43:39Z bistr-o-math $"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>

#include "nvram-wakeup.h"

#define  MAXFILES     4     /* We need four files ro guess the values */
/*
 *  defined in nvram-wakeup.h:
 *
 *       MAXNVRAMSIZE 0x100 -- possible addresses 0x00, .., 0xFF
 */
#define  MINNVRAMSIZE 0x72  /*       but at least 0x00, .., 0x71 */

unsigned char byte[MAXFILES][MAXNVRAMSIZE];


/*
 * unsigned char calculate_read( const unsigned char b, 
 *                               const unsigned char nr, 
 *                               const unsigned char sh,
 *                               const unsigned int bcd) {
 *      unsigned char msk = 255 >> (8-nr) << sh;
 *      unsigned char retval = (msk & b) >> sh;
 *      if (bcd)
 *           retval = (retval >> 4)*10 + ((unsigned char)(retval << 4) >> 4);
 *      return retval;
 * }
 */

/* suppose nr == 5, sh == 1;
 *  b = b7 ... b0 and c = c7 ... c0 (in binary notation).
 *
 * this function compares if 
 *
 *   b7 b6 0 0 0 0 0 b0  ==   c7 c6 0 0 0 0 0 c0
 */
unsigned char is_equal_else( const unsigned char b, 
                             const unsigned char c, 
                             const unsigned char nr, 
                             const unsigned char sh ) {
     unsigned char not_msk = ~(255 >> (8-nr) << sh);
     return (not_msk & b) == (not_msk & c);
}



int could_it_be(const unsigned int  *test,
                const unsigned int  *stat,
                const          char *str,
                const unsigned int  addr,
                const unsigned int  nr) {
     unsigned int i, shift, could_be, retval=0, differs_else, reset_day, reset_mon, rtc_day_0_is_c0, rtc_mon_0_is_c0;

     if (strncmp(str, "rtc", 3) == 0) {
          /* in BCD notation */

          could_be = 1;
          differs_else = 0;
          reset_day = 0;
          reset_mon = 0;
          rtc_day_0_is_c0 = 0;
          rtc_mon_0_is_c0 = 0;

          for (i = 0; i<MAXFILES && could_be; i++) {
               could_be = could_be && test[i] == bcd2bin(calculate_read(byte[i][addr], nr, 0));
               if (    !could_be                  /* the current byte couldn't be    */
                    && !stat[i]                   /* wakeup disabled                 */
                    && !strcmp(str, "rtc_day")    /* we're checking day              */
                   ) {
                    if (0 == bcd2bin(calculate_read(byte[i][addr], nr, 0))) {
                           /* 0 is stored instead of the day */
                         could_be = 1; 
                         reset_day = 1;
                    }
                    if ( reset_day &&
                         0xC0 == bcd2bin(calculate_read(byte[i][addr], nr, 0))) {
                           /* 0xC0 is stored instead of the day */
                         rtc_day_0_is_c0 = 1;
                    }
               }
               if (    !could_be                  /* the current byte couldn't be    */
                    && !stat[i]                   /* wakeup disabled                 */
                    && !strcmp(str, "rtc_mon")    /* we're checking month            */
                   ) {
                    if (0 == bcd2bin(calculate_read(byte[i][addr], nr, 0))) {
                           /* 0 is stored instead of the month */
                         could_be = 1; 
                         reset_mon = 1;
                    }
                    if ( reset_mon &&
                         0xC0 == bcd2bin(calculate_read(byte[i][addr], nr, 0))) {
                           /* 0xC0 is stored instead of the month */
                         rtc_mon_0_is_c0 = 1;
                    }
               }
/*
 *                if (!could_be)
 *                     fprintf(stderr,"  i=%d, calc_rd=%u\n",i,bcd2bin(calculate_read(byte[i][addr], nr, 0)));
 */
          }
     

          for (i = 1; i<MAXFILES && could_be && !differs_else; i++)
               differs_else = differs_else || !is_equal_else(byte[i-1][addr], byte[i][addr], nr, 0);

          if (could_be) {
               retval=1;
               printf("%-16s = 0x%02X%s\n", str,  addr, differs_else?"   # but differs somewhere else":"");
               if (reset_day)
                    printf("reset_day        = ON\n");
               if (rtc_day_0_is_c0)
                    printf("rtc_day_0_is_c0  = ON\n");
               if (reset_mon)
                    printf("reset_mon        = ON\n");
               if (rtc_mon_0_is_c0)
                    printf("rtc_mon_0_is_c0  = ON\n");
          }
     } else {
          /* not in BCD notation */
          for (shift = 0; shift <= 8-nr ; shift++) {

               could_be = 1;
               differs_else = 0;

               for (i = 0; i<MAXFILES && could_be; i++)
                    could_be = could_be && test[i] == calculate_read(byte[i][addr], nr, shift);
     
               for (i = 1; i<MAXFILES && could_be && !differs_else; i++)
                    differs_else = differs_else || !is_equal_else(byte[i-1][addr], byte[i][addr], nr, shift);

               if (could_be) {
                    retval=1;
                    printf("addr_%-11s = 0x%02X%s\n", str,  addr, differs_else?"   # but differs somewhere else":"");
                    if (shift)
                         printf("shift_%-10s = %u\n", str, shift);
               }
          }
     }
     
     

     return retval;
}

void leave(int no) {
     fprintf(stderr, "Please read README.mb for more information\n");
     exit(no);
}


/* nvram-wakeup -s 2014412700  --> 01, 00:00:00 */ 
/* nvram-wakeup -s 2015320694  --> 11, 12:13:14 */ 
/* nvram-wakeup -s 2014412699  --> 31, 23:59:59 */ 



int main(int argc, char **argv) {
           unsigned int  act_size = MAXNVRAMSIZE;
                    int  retval, fd[MAXFILES], is_diff; 
           unsigned int  i, addr, sz_of_diff_addr=0, sz_of_chk_addr=0;
           unsigned char *diff_addr = malloc(sz_of_diff_addr), *addr_ptr,
                         *chk_addr  = malloc(sz_of_chk_addr);
     const unsigned int  mon [] = { 12, 10,  1,  1},
                         day [] = { 31, 11,  1,  1},
                         hour[] = { 23, 12,  0,  0},
                         min [] = { 59, 13,  0,  0},
                         sec [] = { 59, 14,  0,  0},
                         stat[] = {  1,  1,  1,  0};
                    char *files[MAXFILES]; 

     set_progname(argv[0]);
     enable_debug();
     
     fprintf(stderr, CVSREV_guess_c "\n");

     if (argc>1) {
          fprintf(stderr, "Too many arguments.\n");
          leave(1);
     }

     /* open the files */
     for (i = 0; i<MAXFILES; i++) {
          if ((files[i] = malloc (sizeof("dd.hh.mm.ss+"))) == NULL) {
               fprintf(stderr, "Couldn't allocate enough memory (%d Bytes) for a string.\n", sizeof("dd.hh.mm.ss+"));
               leave(1);
          }
          sprintf(files[i], "%02u.%02u.%02u.%02u%c", day[i], hour[i], min[i], sec[i], stat[i]?'+':'-');
          fd[i] = open (files[i], O_RDONLY);
          if ( fd[i] ==  -1 ) {
               fprintf(stderr, "Couldn't open file %s readonly\n", files[i]);
               leave(1);
          }
          fprintf(stderr, "Opened file %s readonly...\n", files[i]);
     }
     
     for (addr = 0; addr<act_size; addr++) {
          for (i = 0; i<MAXFILES; i++) {
               retval = read(fd[i], &byte[i][addr], 1);
               if (retval <= 0) {
                    fprintf(stderr, "Couldn't read byte 0x%02X from %s\n", addr, files[i]);
                    if (addr<MINNVRAMSIZE)
                         leave(1);
                    else {
                         act_size = addr;
                         break;
                    }
               }
          }
          is_diff = 0;
          if (addr == 0x72 || addr == 0x74 || addr == 0x76 || 
              addr == 0x78 || addr == 0x79 || addr == 0x7A || addr == 0x7B )
               /* those bytes contain rtc date and time */
               continue; /* to next addr */
     
          for (i = 1; i<MAXFILES && !is_diff; i++)
               is_diff = is_diff || byte[i-1][addr] != byte[i][addr];
               
          if (is_diff) {
               sz_of_diff_addr++;
               if ((diff_addr = realloc (diff_addr, sz_of_diff_addr)) == NULL) {
                    fprintf(stderr, "Couldn't allocate enough memory (%d Bytes) for an array.\n", sz_of_diff_addr);
                    leave(1);
               }

               /* remember the address of this byte */
               *(diff_addr+sz_of_diff_addr-1) = addr;
          }
     }
     fprintf(stderr, "Assuming size of files %u bytes\n", act_size);
     
     printf("\n");
     printf("################################################\n");
     printf("##  Mainboard autodetection information:\n"        );
     printf("##\n"                                              );
     printf("##    - Mainboard vendor:   %s%s%s\n", QUOTE(board_vendor() ), VALUE(board_vendor() ), QUOTE(board_vendor() ) );
     printf("##    - Mainboard type:     %s%s%s\n", QUOTE(board_type()   ), VALUE(board_type()   ), QUOTE(board_type()   ) );
     printf("##    - Mainboard revision: %s%s%s\n", QUOTE(board_version()), VALUE(board_version()), QUOTE(board_version()) );
     printf("##    - BIOS vendor:        %s%s%s\n", QUOTE(bios_vendor()  ), VALUE(bios_vendor()  ), QUOTE(bios_vendor()  ) );
     printf("##    - BIOS version:       %s%s%s\n", QUOTE(bios_version() ), VALUE(bios_version() ), QUOTE(bios_version() ) );
     printf("##    - BIOS release:       %s%s%s\n", QUOTE(bios_release() ), VALUE(bios_release() ), QUOTE(bios_release() ) );
     printf("\n");

     /* let's go */
     for (addr_ptr = diff_addr; 
          addr_ptr < diff_addr+sz_of_diff_addr;
          addr_ptr++) {
          
          int could_be = 0;
          
          addr = *addr_ptr;
          
          fprintf(stderr, "    checking 0x%02X:", addr);
          for (i = 0; i<MAXFILES; i++)
               fprintf(stderr, " 0x%02X", byte[i][addr]);
          fprintf(stderr, "\n");
          
          if (could_it_be(stat, stat, "stat", addr, 1)) could_be = 1;

          if (could_it_be(mon,  stat, "mon",  addr, 4)) could_be = 1;
          if (could_it_be(day,  stat, "day",  addr, 5)) could_be = 1;
          if (could_it_be(hour, stat, "hour", addr, 5)) could_be = 1;
          if (could_it_be(min,  stat, "min",  addr, 6)) could_be = 1;
          if (could_it_be(sec,  stat, "sec",  addr, 6)) could_be = 1;

          if (could_it_be(mon,  stat, "rtc_mon",  addr, 5)) could_be = 1;
          if (could_it_be(day,  stat, "rtc_day",  addr, 6)) could_be = 1;
          if (could_it_be(hour, stat, "rtc_hour", addr, 6)) could_be = 1;
          if (could_it_be(min,  stat, "rtc_min",  addr, 7)) could_be = 1;
          if (could_it_be(sec,  stat, "rtc_sec",  addr, 7)) could_be = 1;

          
          if (! could_be) {

               sz_of_chk_addr++;
               if ((chk_addr = realloc (chk_addr, sz_of_chk_addr)) == NULL) {
                    fprintf(stderr, "Couldn't allocate enough memory (%d Bytes) for an array.\n", sz_of_chk_addr);
                    leave(1);
               }

               /* remember the address of this byte. it must be a checksum byte */
               *(chk_addr+sz_of_chk_addr-1) = addr;
          }
     }
     if (sz_of_chk_addr == 1) {
          /* only one known checksum byte */
          printf("addr_chk_h       = 0x%02X   # guessed\n", chk_addr[0]-1);
          printf("addr_chk_l       = 0x%02X\n",             chk_addr[0]  );
     }
     else if (sz_of_chk_addr == 2) {
          /* both checksum bytes seem to be known */
          printf("addr_chk_h       = 0x%02X\n", (chk_addr[0]<chk_addr[1]?chk_addr[0]:chk_addr[1]));
          printf("addr_chk_l       = 0x%02X\n", (chk_addr[0]>chk_addr[1]?chk_addr[0]:chk_addr[1]));
     }
     else {
          fprintf(stderr, "Couldn't guess checksum addresses (out of %u).\n", sz_of_chk_addr);
     }
     printf("\n");
     return 0;
}

