[comp.os.minix] AST-6pack clock support

dtynan@sultra.UUCP (Dermot Tynan) (09/10/88)

Ok, herein is a shar-file with some changes to date.c to a) fix a bug with
the set time function, and b), to allow automatic loading of the date from
an AST SixPack.  Use it, abuse it, whatever, I don't care.  Just let me
know how it goes.  Also, if you need help applying the diffs to your
particular hardware, just let me know...  Regards...
						- Der
BTW, this was done for 1.2 only.  for later versions, you're on your own - at
least until I can get my hands on a diff for 1.3 (help someone, please!!!!).

PS: To Barry McMullin, NIHE-D, did you get my message??  Send me mail with
    your EXACT (uucp) mail path.  Thanks.
	-----cut here-----cut here-----cut here-----cut here-----
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh to create the files:
#	README
#	date.diff
#	datelow.s
# This archive created: Fri Sep  9 18:42:49 1988
#	by: dtynan@sultra.uucp (Dermot Tynan @ Tynan Computers)
export PATH; PATH=/bin:$PATH
echo shar: extracting "'README'" '(6072 characters)'
if test -f 'README'
then
	echo shar: will not over-write existing file "'README'"
else
cat << \SHAR_EOF > 'README'
	MINIX `date` utility -- mods for AST Research SixPack.
	======================================================

OK, given the `complexity' of the change, I thought I'd include this README
file, to give you an idea of how I implemented this change.  Basically, the
change accomplishes two things;
	i)  It fixes a bug with the 1.2 release `date' program (leap years
	    not handled correctly.
	ii) It adds an option to allow users with an AST Research SixPack
	    card to automatically load the date.
As mentioned above, I did all the work with MINIX version 1.2 (I don't have
version 1.3 - yet!), so if you're trying to add this to 1.3, you'll have to
check to see if anything has changed.  I've never seen 1.3, but I'd guess
that the `leap-year' bug has been fixed - maybe not.  Anyway, this package
contains three files;
	i)   README		This (rather long) diatribe.
	ii)  date.diff		A context `diff' of the date.c source (1.2)
	iii) datelow.s		A low-level (assembly) module to read/write
				the clock/calendar chip.

The leapyear bug.  When you set the date manually, on the system, this
year (after february), the new system date is set one day later.

	The leapyear bug was caused by a strange phenomenon in the date.c
code.  Namely, when setting the date, time() and cv_time() are called first,
to load the `tm' structure with the current date/time.  cv_time() would
increment days_per_month[1] (# of days in february) if the current year is
a leap year.  However, when converting the entered ASCII time to seconds
since epoch (Jan  1, 1970) in set_time(), another check was made to see if
the current year is a leap-year, and if so, again days_per_month[1] was
incremented (now february has 30 days!).  The simple fix was to have the
code set days_per_month[1] to 29, instead of incrementing it.  This is
also the smarter way to do it, as it requires less instructions.  In this
way, it doesn't matter how many times the array is changed, it will always
be changed to 29.  Also, we don't have to worry about resetting it, when
we're done.  I guess this problem never showed up before, because the stuff
was probably beta tested last year (not a leapyear).

	Now for the *real* stuff.  The AST SixPack uses a NSC clock/calendar
chip.  I quote the AST manual;
	"For further information on the clock-calendar, see the
	 data sheet for the National Semiconductor MM58167A Real
	 Time Clock."
Ditto.  Anyway, the real things you have to know are outlined here.
Firstly, a new option was added (-l) to load the time from this chip, and
feed it to the normal set_time() routine.  To do this, a routine called
readclk() was added to date.c, also, two assembly-language routines were
created in the file datelow.s, to support the low-level interface to the
chip.  Basically, the chip is composed of 32 registers.  To access any of
these registers, you write the register number to 0x2c0, and read/write to
0x2c1 to access the actual register data (this is accomplished by
gtclk()/stclk()).  Of these 32 registers, we are only interested in seven.
These contain the year, month, day, hour, minute, second and last-month.
The table `field[]' contains the register numbers for the first six.  The
last register (last-month) is for a special purpose.  There is no real
mechanism in the chip to maintain year information, it is assumed that you
will turn the machine on at least once a year.  The year is maintained as
follows;  Two non-volatile RAM locations in the chip are used.  One stores
the year (- 1980), and the other stores the last month.  All register
contents are in BCD.  Basically, the first thing we do, when reading the
date/time from the chip, is load the current month counter (0x01 -> 0x12)
then, we load the last-month counter.  If it is zero (meaning, this is the
first time the chip has been used), or it is not the same as the current
month, we write the current month out to the RAM location.  In this way,
we can tell when the month rolls over.  If last-month is greater than
this month, the year has rolled over, so we must increment it.  Simple?
Unfortunately, if it has been more than 11 months since you accessed the
system, the year will be wrong - but so what, if you expect the clock to
be accurate over that kind of time-range, you should go work for NBS.
The next area of interest here, is in making sure that the date/time doesn't
roll over while we're reading it.  Case in point.  It is new years eve,
1987.  One second to midnight.  Or in other words, Dec 31 23:59:59 1987.
We read the year register, and the time rolls over.  We finish the read, and
what we got was;  Jan  1, 00:00:00 1987.  Boy, are we wrong!  The easiest
way to fix this, is to load the seconds register into a temporary register.
Next, load the date/time as per usual.  Now, load the seconds register
again.  Is it LESS than the first value we got?  Yes, then BINGO.  The chip
rolled on us.  Do it again.  You'll notice that the routine will actually
keep trying, rather than just retrying once.  This is because in a multi-
tasking OS (Yes, even MINIX!), the process could get pre-empted, and come
back some time later.  This method isn't foolproof, but it does the job.
The date/time string, once collected, is passed to set_time() much the
same way that the manual input is.  The *only* thing missing now, is a way
to set the date/time at the chip.  This I leave as an exercise for those
of you who are interested in such a thing.  I don't think it should be part
of the date.c package, as this is not really a UN*X/MINIX function.  It
should probably be a standalone package (setclock?), which requires a uid
of 0 (root).

Although this modification is *very* device specific, I have tried to make
this application note as generic as possible, so that those of you with
different kinds of systems (AMIGA et al, different clock chips, etc).
Could modify this code as required.  Happy Hacking...
						- Der
--
Reply:	dtynan@sultra.UUCP		(Dermot Tynan @ Tynan Computers)
	{mips,pyramid}!sultra!dtynan
	Cast a cold eye on life, on death.  Horseman, pass by...    [WBY]
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'date.diff'" '(4756 characters)'
if test -f 'date.diff'
then
	echo shar: will not over-write existing file "'date.diff'"
else
cat << \SHAR_EOF > 'date.diff'
*** date.c.old	Fri Sep  9 10:22:37 1988
--- date.c	Fri Sep  9 18:33:41 1988
***************
*** 1,4
  /* date - print or set the date		Author: Adri Koppes */
  
  #include "stdio.h"
  

--- 1,5 -----
  /* date - print or set the date		Author: Adri Koppes */
+ /*					Last Mod: Dermot Tynan */
  
  #include "stdio.h"
  
***************
*** 2,7
  
  #include "stdio.h"
  
  int qflag;
  
  int days_per_month[] =

--- 3,11 -----
  
  #include "stdio.h"
  
+ #define NFIELDS		6
+ #define PROMPT	"\nPlease enter date (MMDDYYhhmmss).  Then hit RETURN.\n"
+ 
  int qflag;
  
  int days_per_month[] =
***************
*** 6,11
  
  int days_per_month[] =
    { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  char *months[] =
    { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

--- 10,17 -----
  
  int days_per_month[] =
    { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ int field[] =
+   { 7, 6, 10, 4, 3, 2 };
  char *months[] =
    { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
***************
*** 33,39
    s_p_day = 24 * s_p_hour;
    s_p_year = 365 * s_p_day;
    if (argc == 2) {
! 	if (*argv[1] == '-' && (argv[1][1] | 0x60) == 'q') {
  		/* Query option. */
  		char time_buf[15];
  

--- 39,45 -----
    s_p_day = 24 * s_p_hour;
    s_p_year = 365 * s_p_day;
    if (argc == 2) {
! 	if (*argv[1] == '-') {
  		/* Query option. */
  		char time_buf[15];
  
***************
*** 37,46
  		/* Query option. */
  		char time_buf[15];
  
! 		qflag = 1;
! 		freopen(stdin, "/dev/tty0", "r");
! 		printf("\nPlease enter date (MMDDYYhhmmss).  Then hit RETURN.\n");
! 		gets(time_buf);
  		set_time(time_buf);
  		exit(0);
  	}

--- 43,57 -----
  		/* Query option. */
  		char time_buf[15];
  
! 		if ((argv[1][1] | 0x60) == 'q') {
! 			qflag = 1;
! 			freopen(stdin, "/dev/tty0", "r");
! 			printf(PROMPT);
! 			gets(time_buf);
! 		} else if ((argv[1][1] | 0x60) == 'l')
! 			readclk(time_buf);
! 		else
! 			usage();
  		set_time(time_buf);
  		exit(0);
  	}
***************
*** 48,54
    } else {
  	time(&t);
  	cv_time(t);
! 	printf("%s %s %d %02d:%02d:%02d %d\n", days[(t / s_p_day) % 7], months[tm.month], tm.day, tm.hour, tm.min, tm.sec, tm.year); 
    }
    exit(0);
  }

--- 59,65 -----
    } else {
  	time(&t);
  	cv_time(t);
! 	printf("%s %s %d %02d:%02d:%02d %d\n", days[(int)(t / s_p_day) % 7], months[tm.month], tm.day, tm.hour, tm.min, tm.sec, tm.year); 
    }
    exit(0);
  }
***************
*** 69,75
  	t -= s_p_year;
    }
    if (((tm.year + 2) % 4) == 0)
! 	days_per_month[1]++;
    tm.year += 1970;
    while ( t >= (days_per_month[tm.month] * s_p_day))
  	t -= days_per_month[tm.month++] * s_p_day;

--- 80,86 -----
  	t -= s_p_year;
    }
    if (((tm.year + 2) % 4) == 0)
! 	days_per_month[1] = 29;
    tm.year += 1970;
    while ( t >= (days_per_month[tm.month] * s_p_day))
  	t -= days_per_month[tm.month++] * s_p_day;
***************
*** 118,124
    ct = tm.year * s_p_year;
    ct += ((tm.year + 1) / 4) * s_p_day;
    if (((tm.year + 2) % 4) == 0)
! 	days_per_month[1]++;
    len = 0;
    tm.month--;
    while (len < tm.month)

--- 129,135 -----
    ct = tm.year * s_p_year;
    ct += ((tm.year + 1) / 4) * s_p_day;
    if (((tm.year + 2) % 4) == 0)
! 	days_per_month[1] = 29;
    len = 0;
    tm.month--;
    while (len < tm.month)
***************
*** 127,134
    ct += tm.hour * s_p_hour;
    ct += tm.min * s_p_min;
    ct += tm.sec;
-   if (days_per_month[1] > 28)
- 	days_per_month[1] = 28;
    if (stime(&ct)) {
  	fprintf(stderr, "Set date not allowed\n");
    } else {

--- 138,143 -----
    ct += tm.hour * s_p_hour;
    ct += tm.min * s_p_min;
    ct += tm.sec;
    if (stime(&ct)) {
  	fprintf(stderr, "Set date not allowed\n");
    } else {
***************
*** 136,141
    }
  }
  
  conv(ptr, max)
  char **ptr;
  int max;

--- 145,175 -----
    }
  }
  
+ readclk(timbuf)
+ char *timbuf;
+ {
+   register i, j;
+   int secs;
+   char *cp;
+ 
+   i = gtclk(7);					/* get current month */
+   if ((j = gtclk(9)) == 0 || i != j)
+ 	stclk(9, j);
+   if (j > i)					/* Year rollover */
+ 	stclk(10, gtclk(10) + 1);
+   while (1) {
+ 	secs = gtclk(2);			/* Get seconds */
+ 	for (i = 0, cp = timbuf; i < NFIELDS; i++, cp += 2) {
+ 		j = gtclk(field[i]);
+ 		if (i == 2)
+ 			j += 0x80;
+ 		sprintf(cp, "%02x", j);		/* Date/time is BCD! */
+ 	}
+ 	if (gtclk(2) < secs)
+ 		return;
+   }
+ }
+ 
  conv(ptr, max)
  char **ptr;
  int max;
***************
*** 158,164
  
  usage()
  {
!   if (qflag==0) fprintf(stderr, "Usage: date [-q] [[MMDDYY]hhmm[ss]]\n");
    exit(1);
  }
  

--- 192,198 -----
  
  usage()
  {
!   if (qflag==0) fprintf(stderr, "Usage: date [-q] [-l] [[MMDDYY]hhmm[ss]]\n");
    exit(1);
  }

SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'datelow.s'" '(1157 characters)'
if test -f 'datelow.s'
then
	echo shar: will not over-write existing file "'datelow.s'"
else
cat << \SHAR_EOF > 'datelow.s'
|
| Low level routines to get and set AST Sixpack clock-calendar.
|
| Basically, the operation of the MM58167A is simple.  There are 32 ports
| which can be accessed by the user.  These hold the current date and time.
| To load the date, one simply reads the year, month, day, hour and minute
| from the clock-calendar chip.  To set them, one simply writes to the
| appropriate register.  To read or write a register, the 5-bit register
| number is output to port address 0x2c0, and then a read or write to port
| address 0x2c1 (the data port) accomplishes the actual read/write.
|
| Written by:	Dermot Tynan @ Tynan Computers,
|		..!pyramid!sultra!dtynan
|

	.text
	.globl	_gtclk, _stclk

|
| gtclk(portno)
| int portno;
|
| This routine returns the 8-bit value from port `portno', as an integer.
|
_gtclk:	push	bp
	mov	bp,sp
	mov	ax,*4(bp)
	and	ax,#0x1f
	mov	dx,#0x2c0
	outb
	inc	dx
	inb
	pop	bp
	ret

|
| stclk(portno, data)
| int portno, data;
|
| This routine sets the given register (portno) with the value `data'.
|
_stclk:	push	bp
	mov	bp,sp
	mov	ax,*4(bp)
	andb	al,*0x1f
	mov	dx,#0x2c0
	outb
	inc	dx
	mov	ax,*6(bp)
	outb
	pop	bp
	ret

|
| The end.
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0
-- 
Reply:	dtynan@sultra.UUCP		(Dermot Tynan @ Tynan Computers)
	{mips,pyramid}!sultra!dtynan
	Cast a cold eye on life, on death.  Horseman, pass by...    [WBY]

dtynan@sultra.UUCP (Dermot Tynan) (09/10/88)

> + 	if (gtclk(2) < secs)
> + 		return;
> +   }

OK, so no-one's perfect.  Here's what happened.  I finished the code late
last night (*very* late!), and while reviewing it today, noticed that the
above comparison was '>' - I thought about this, and came to the conclusion;
"That *couldn't* work it should be "less-than".  I changed it, posted the
diffs, etc, and THEN realized that I had been right originally (Am I the only
one who does that?? -- I once deleted a whole block of code for a system I
was developing for a client, because it looked like total BS, only to realize
the next day, that it was actually correct.  Thank God for backups!).  Anyway,
in case you hadn't already noticed, change the above line to read;
	if (gtclk(2) > secs)
		return;

						Sorry!
						- Der
--
Reply:	dtynan@sultra.UUCP		(Dermot Tynan @ Tynan Computers)
	{mips,pyramid}!sultra!dtynan
	Cast a cold eye on life, on death.  Horseman, pass by...    [WBY]