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]