BasicTimeImpl.mesa: low-level time facilities
Copyright © 1985 by Xerox Corporation. All rights reserved.
Andrew Birrell November 30, 1983 9:31 am
Levin, December 16, 1983 3:44 pm
Russ Atkinson (RRA) June 6, 1985 3:54:51 am PDT
Hal Murray, June 3, 1986 3:31:31 pm PDT
DIRECTORY
Basics USING [LongDiv, LongDivMod, LongMult, LongNumber],
BasicTime USING [DayOfWeek, MonthOfYear, Pulses, Unpacked, unspecifiedZone, Zone, ZoneAndDST],
Endian USING [FWORD, CardFromF],
File USING [ SystemVolume, Volume],
GermSwap USING [switches],
PhysicalVolume USING [GetPhysical, Physical, PhysicalInfo, PhysicalRC, WriteTimeParameters],
ProcessorFace USING [GetGreenwichMeanTime, gmtEpoch, microsecondsPerHundredPulses, SetGreenwichMeanTime],
Pup USING [allHosts, allNets],
PupBuffer USING [Buffer],
PupSocket USING [AllocBuffer, Broadcast, CreateEphemeral, Destroy, FreeBuffer, Get, GetUniqueID, SetNoErrors, SetUserBytes, Socket],
PupWKS USING [misc];
BasicTimeImpl: CEDAR MONITOR
IMPORTS Basics, Endian, File, GermSwap, PhysicalVolume, ProcessorFace, PupSocket
EXPORTS BasicTime, PhysicalVolume = {
CARD: TYPE = LONG CARDINAL;
The monitor locks protects access to
ProcessorFace (GetGreenwichMeanTime & SetGreenwichMeanTime)
global frame (knownTP, knownTPValid)
disk parameters (PhysicalVolume.WriteTimeParameters & PhysicalVolume.PhysicalInfo)
Global variables
knownTP: BasicTime.ZoneAndDST;
knownTPValid: BOOLFALSE;
Sampling this need not be locked because it can only change in ONE direction, and that will only happen after knownTP is set correctly.
Errors
OutOfRange: PUBLIC ERROR = CODE;
TimeNotKnown: PUBLIC ERROR = CODE;
TimeParametersNotKnown: PUBLIC ERROR = CODE;
Part I: Fine-grain timer
PulsesToMicroseconds: PUBLIC PROC [p: BasicTime.Pulses] RETURNS [ CARD ] = {
RETURN [MultThenDiv[p, ProcessorFace.microsecondsPerHundredPulses, 100]];
};
PulsesToSeconds: PUBLIC PROC [p: BasicTime.Pulses] RETURNS [s: REAL ] = {
Notice that we sample ProcessorFace.microsecondsPerHundredPulses every time through this routine. The (minor) extra cost is paid to deal with possibly changing clocks. Also, we are biased towards floating point multiplies as being faster than 32-bit multiplies.
RETURN [p*(ProcessorFace.microsecondsPerHundredPulses*1.0e-8)];
};
MicrosecondsToPulses: PUBLIC PROC [m: CARD] RETURNS [BasicTime.Pulses ] = {
RETURN [MultThenDiv[m, 100, ProcessorFace.microsecondsPerHundredPulses]];
};
MultThenDiv: PROC [m1: CARD, m2: CARDINAL, dv: CARDINAL] RETURNS [result: CARD] = TRUSTED {
Copied from Pilot's SystemImpl.
OPEN mm1: LOOPHOLE[m1, num Basics.LongNumber];
t: MACHINE DEPENDENT RECORD [ SELECT OVERLAID * FROM
separate => [low, mid, high: CARDINAL],
lower => [lowlong: CARD, junk: CARDINAL],
higher => [junk: CARDINAL, highlong: CARD],
ENDCASE];
t.lowlong ← Basics.LongMult[mm1.lowbits, m2];
IF mm1.highbits # 0 THEN {
t.highlong ← Basics.LongMult[mm1.highbits, m2] + t.mid;
IF t.high # 0 THEN {
OPEN q: LOOPHOLE[result, num Basics.LongNumber];
Have to do triple divide
IF t.high >= dv THEN t.high ← t.high MOD dv; -- overflow; lowbits will be right
[quotient: q.highbits, remainder: t.mid] ← Basics.LongDivMod[t.highlong, dv];
q.lowbits ← Basics.LongDiv[t.lowlong, dv];
RETURN
};
};
t.high is 0, so let mesa do the work...
RETURN [t.lowlong/LONG[dv]]
};
Part II: Packed times
GMT: PUBLIC TYPE = INT;
gmtBaseYear: CARDINAL = 1968;
gmtOrigin: GMT = 0; -- representation for midnight, January 1st 1968 GMT
alto1968: CARD = -- our time origin in Alto/Pup format --
LONG[ (gmtBaseYear-1901) * 365 + (gmtBaseYear-1901)/4 ] * 24 * 60 * 60;
altoExpiry: CARD = LAST[CARD] - alto1968;
gmtExpiry: GMT = -- intersection of Alto expiry and 31 bits, minus "nullGMT" representation --
gmtOrigin + MIN[altoExpiry, LAST[INT] - gmtOrigin - 1] - 1;
earliestGMT: PUBLIC GMT ← gmtOrigin + LONG[LAST[BasicTime.Zone]] * 60;
latestGMT: PUBLIC GMT ← gmtExpiry;
Now: PUBLIC PROC RETURNS [GMT] = TRUSTED {
prTime: CARD ← LockedGetTime[];
IF prTime = ProcessorFace.gmtEpoch THEN {
means "unset clock"
ok: BOOL;
[ok, prTime] ← SetTimeAndTP[];
IF NOT ok THEN ERROR TimeNotKnown;
};
RETURN [prTime-alto1968+gmtOrigin ]
};
Period: PUBLIC PROC [from, to: GMT] RETURNS [INT] = {
RETURN [to-from];
};
Update: PUBLIC PROC [base: GMT, period: INT] RETURNS [GMT] = {
Returns the time "period" seconds later than "base" (earlier iff "period" is negative). Raises "OutOfRange" if the result would be before 1968 or after 2036.
IF period IN [gmtOrigin-base .. gmtExpiry-base]
THEN RETURN [base+period]
ELSE ERROR OutOfRange;
};
ToPupTime: PUBLIC PROC [g: GMT] RETURNS [ CARD ] = {
Returns time according to the Alto/Pup time standard. No errors.
RETURN [ alto1968+(g-gmtOrigin) ];
};
ToNSTime: PUBLIC PROC [g: GMT] RETURNS [ CARD ] = {
Returns time according to the Xerox NS protocol time standard. No errors.
RETURN [alto1968+(g-gmtOrigin)];
};
FromPupTime: PUBLIC PROC [p: CARD] RETURNS [ GMT ] = {
Accepts time according to the Alto/Pup time standard. Raises "OutOfRange" for times earlier than 1968.
IF p >= alto1968 THEN RETURN [(p-alto1968)+gmtOrigin] ELSE ERROR OutOfRange;
};
FromNSTime: PUBLIC PROC [p: CARD] RETURNS [ GMT ] = {
Accepts time according to the Xerox NS protocol time standard. Raises "OutOfRange" for times beyond about 2036.
IF p >= alto1968 THEN RETURN [(p-alto1968)+gmtOrigin] ELSE ERROR OutOfRange;
};
Part III: Packing and unpacking
monthTable: ARRAY BasicTime.MonthOfYear OF CARDINAL =
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
startWeekday: BasicTime.DayOfWeek = Monday; -- January 1st 1968 was a Monday
Unpack: PUBLIC PROC [time: GMT] RETURNS [ unpacked: BasicTime.Unpacked ] = {
tp: BasicTime.ZoneAndDST = GetZoneAndDST[];
secs, mins, hrs: CARD;
IF time < earliestGMT OR time > latestGMT THEN ERROR OutOfRange[];
secs ← time - gmtOrigin;
mins ← secs / 60 -
(IF tp.zone = BasicTime.unspecifiedZone THEN 0 ELSE tp.zone);
hrs ← mins / 60;
unpacked.second ← secs MOD 60;
unpacked.minute ← mins MOD 60;
unpacked.zone ← (IF tp.zone = BasicTime.unspecifiedZone THEN 0 ELSE tp.zone);
unpacked.dst ← no;
DO -- once if not dst, twice if dst
daysSinceBase: CARDINAL = hrs / 24;
daysInFourYears: CARDINAL = ( 3*365 + 366 );
fourYears: CARDINAL = daysSinceBase / daysInFourYears;
daysBeyondFourYears: CARDINAL = daysSinceBase MOD daysInFourYears;
pseudoDaysBeyond: CARDINAL = -- corrected as if every year had 366 days
daysBeyondFourYears + (MAX[31+29,daysBeyondFourYears]-(31+29))/365;
oddYears: [0..4) = pseudoDaysBeyond / 366;
dayOfYear: CARDINAL = (pseudoDaysBeyond MOD 366)+1; -- one's origin, 366-day year
unpacked.year ← gmtBaseYear + 4*fourYears + oddYears;
unpacked.weekday ← startWeekday;
THROUGH [0..daysSinceBase MOD 7)
DO unpacked.weekday ← SUCC[unpacked.weekday];
IF unpacked.weekday = unspecified
THEN unpacked.weekday ← FIRST[BasicTime.DayOfWeek];
ENDLOOP;
FOR month: BasicTime.MonthOfYear IN BasicTime.MonthOfYear[January..December]
DO IF dayOfYear <= monthTable[SUCC[month]]
THEN {unpacked.month ← month; EXIT};
REPEAT FINISHED => ERROR
ENDLOOP;
unpacked.day ← dayOfYear-monthTable[unpacked.month];
unpacked.hour ← hrs MOD 24;
{
yearStart: CARDINAL = fourYears * daysInFourYears +
(IF oddYears = 0 THEN 0 ELSE 366 + (oddYears-1)*365);
unpacked.daysThisYear ← daysSinceBase - yearStart;
unpacked.secondsThisYear ← secs - ((LONG[yearStart] * 24) * 60) * 60;
};
IF unpacked.dst = yes
OR tp.zone = BasicTime.unspecifiedZone
OR NOT CheckDateGE[unpacked, tp.beginDST, 2]
OR CheckDateGE[unpacked, tp.endDST, 1]
THEN EXIT;
hrs ← hrs+1;
unpacked.dst ← yes;
ENDLOOP;
};
Pack: PUBLIC PROC [unpacked: BasicTime.Unpacked] RETURNS [ time: GMT ] = {
Raises "OutOfRange" for times before 1968 or after 2036.
tp: BasicTime.ZoneAndDST = GetZoneAndDST[];
daysInFourYears: CARDINAL = ( 3*365 + 366 );
yearsSinceBase: CARDINAL = IF unpacked.year < gmtBaseYear
THEN ERROR OutOfRange
ELSE unpacked.year - gmtBaseYear;
oddYears: [0..4) = yearsSinceBase MOD 4;
day: CARDINAL = -- day number in year --
monthTable[unpacked.month] + unpacked.day - 1--adjust to zero origin--
- -- monthTable assumes all February's have 29 days, so correct for that --
(IF oddYears#0 AND unpacked.month NOT IN [January..February] THEN 1 ELSE 0);
daysSinceBase: CARDINAL =
(yearsSinceBase/4) * daysInFourYears
+ oddYears * 365 + day
+ (IF oddYears # 0 THEN 1 ELSE 0) --leap day--;
zone: [-720-60..+721] ←
IF unpacked.zone # BasicTime.unspecifiedZone
THEN unpacked.zone
ELSE IF tp.zone # BasicTime.unspecifiedZone
THEN tp.zone
ELSE ERROR TimeParametersNotKnown[];
SELECT unpacked.dst FROM
unspecified => {
unpacked.weekday ← startWeekday;
THROUGH [0..daysSinceBase MOD 7) DO
unpacked.weekday ← SUCC[unpacked.weekday];
IF unpacked.weekday = unspecified THEN
unpacked.weekday ← FIRST[BasicTime.DayOfWeek];
ENDLOOP;
IF tp.zone = BasicTime.unspecifiedZone THEN ERROR TimeParametersNotKnown[];
IF CheckDateGE[unpacked,tp.beginDST,2] AND ~CheckDateGE[unpacked,tp.endDST,2]
THEN zone ← zone - 60;
};
yes => zone ← zone - 60;
no => NULL;
ENDCASE => ERROR;
RETURN [ gmtOrigin + ((LONG[daysSinceBase] * 24 + unpacked.hour) * 60 + unpacked.minute + zone) * 60 + unpacked.second ]
};
CheckDateGE: PROC [unpacked: BasicTime.Unpacked, dstDay, dstHour: CARDINAL] RETURNS [BOOL] = {
day: CARDINAL ← monthTable[unpacked.month] + unpacked.day;
SELECT TRUE FROM
day < dstDay-6 => RETURN [FALSE]; -- before the interesting week
day > dstDay => RETURN [TRUE]; -- after the interesting week
unpacked.weekday = Sunday => RETURN [unpacked.hour >= dstHour]; -- critical Sunday
ENDCASE => {
THROUGH BasicTime.DayOfWeek[FIRST[BasicTime.DayOfWeek]..unpacked.weekday) DO
day ← day-1;
ENDLOOP; -- calculate day number of preceding Sunday
RETURN [day > dstDay-6] -- before/after the critical Sunday
}
};
Part IV: Finding and setting time and zone/dst information
--VolumeFormat.--TimeParameters: PUBLIC TYPE = MACHINE DEPENDENT RECORD[
direction(0:0..0): { west(0), east(1) }, -- California is west
zone(0:1..4): [0..12],
zoneMinutes(1:0..6): [0..60),
beginDST(0:5..15): [0..366], -- April 30 is 121
endDST(1:7..15): [0..366]
];
SetTime: PUBLIC PROC = TRUSTED {
[] ← SetTimeAndTP[];
};
SetZoneAndDST: PUBLIC ENTRY PROC [tp: BasicTime.ZoneAndDST] = {
ENABLE UNWIND => NULL;
clientTP: TimeParameters = [
direction: IF tp.zone > 0 THEN west ELSE east,
zone: ABS[tp.zone] / 60,
zoneMinutes: ABS[tp.zone] MOD 60,
beginDST: tp.beginDST,
endDST: tp.endDST];
sysPhys: PhysicalVolume.Physical = GetSysPhys[];
IF sysPhys # NIL THEN [] ← PhysicalVolume.WriteTimeParameters[sysPhys, TRUE, clientTP];
ConvertPhysTP[clientTP];
};
GetZoneAndDST: PUBLIC PROC RETURNS [tp: BasicTime.ZoneAndDST] = {
ENABLE UNWIND => NULL;
IF knownTPValid OR SetTimeAndTP[].ok OR TPFromDisk[] THEN RETURN [knownTP];
RETURN [ [zone: BasicTime.unspecifiedZone, beginDST: 366, endDST: 366] ];
};
SetTimeAndTP: PROC RETURNS [ok: BOOLFALSE, secs: CARD ← ProcessorFace.gmtEpoch --NS/Pilot--] = {
netTP: TimeParameters;
IF GermSwap.switches[c] THEN RETURN; -- => communication package isn't running
[ok, netTP, secs] ← TimeFromEthernet[];
IF NOT ok THEN [ok, netTP, secs] ← TimeFromEthernetOne[];
IF NOT ok THEN RETURN;
LockedSetTime[netTP, secs];
};
LockedGetTime: ENTRY PROC RETURNS [CARD] = TRUSTED {
This is an ENTRY procedure to protect ProcessorFace.GetGreenwichMeanTime from interference by ProcessorFace.SetGreenwichMeanTime.
RETURN [ProcessorFace.GetGreenwichMeanTime[]];
};
LockedSetTime: ENTRY PROC [netTP: TimeParameters, secs: CARD--NS/Pilot--] = {
This is an ENTRY procedure to protect ConvertPhysTP and ProcessorFace.SetGreenwichMeanTime.
IF NOT knownTPValid THEN {
sysPhys: PhysicalVolume.Physical = GetSysPhys[];
IF sysPhys # NIL THEN [] ← PhysicalVolume.WriteTimeParameters[sysPhys, TRUE, netTP];
ConvertPhysTP[netTP];
};
TRUSTED {ProcessorFace.SetGreenwichMeanTime[secs]};
};
TPFromDisk: ENTRY PROC RETURNS [BOOL] = {
sysPhys: PhysicalVolume.Physical = GetSysPhys[];
sysPhysStatus: PhysicalVolume.PhysicalRC;
physTP: TimeParameters;
physTPOK: BOOL;
IF sysPhys = NIL OR knownTPValid THEN RETURN [FALSE];
[rootStatus: sysPhysStatus, timeValid: physTPOK, time: physTP] ←
PhysicalVolume.PhysicalInfo[sysPhys];
IF sysPhysStatus # ok OR NOT physTPOK THEN RETURN [FALSE];
ConvertPhysTP[physTP];
RETURN [TRUE] -- even although we haven't set the clock value?
};
ConvertPhysTP: INTERNAL PROC [physTP: TimeParameters] = {
knownTP ← [
zone: physTP.zone*60 + physTP.zoneMinutes,
beginDST: physTP.beginDST,
endDST: physTP.endDST];
IF physTP.direction # west THEN knownTP.zone ← -knownTP.zone;
knownTPValid ← TRUE;
};
GetSysPhys: PROC RETURNS [PhysicalVolume.Physical] = {
sys: File.Volume = File.SystemVolume[];
RETURN [ IF sys = NIL THEN NIL ELSE PhysicalVolume.GetPhysical[sys] ];
};
TimeFromEthernet: PROC
RETURNS [physTPOK: BOOL, physTP: TimeParameters, time: CARD] = {
physTPOK ← FALSE;
};
TimeFromEthernetOne: PROC
RETURNS [physTPOK: BOOL, physTP: TimeParameters, time: CARD] = TRUSTED {
id: Endian.FWORD = PupSocket.GetUniqueID[];
socket: PupSocket.Socket = PupSocket.CreateEphemeral[
remote: [net: Pup.allNets, host: Pup.allHosts, socket: PupWKS.misc],
getTimeout: 5000];
PupSocket.SetNoErrors[socket];
FOR i: NAT IN [0..10) DO
b: PupBuffer.Buffer ← PupSocket.AllocBuffer[socket];
b.type ← altoTimeRequest;
b.id ← id;
PupSocket.SetUserBytes[b, 0];
PupSocket.Broadcast[socket, b];
DO
b ← PupSocket.Get[socket];
IF b = NIL THEN EXIT;
IF b.type = altoTimeReply AND b.id = id THEN {
physTP ← [
direction: IF b.time.direction = west THEN west ELSE east,
zone: b.time.zone,
zoneMinutes: b.time.zoneMinutes,
beginDST: b.time.beginDST,
endDST: b.time.endDST];
time ← Endian.CardFromF[b.time.time];
PupSocket.FreeBuffer[b];
GOTO Done; };
PupSocket.FreeBuffer[b];
ENDLOOP;
REPEAT
Done => physTPOK ← TRUE;
FINISHED => physTPOK ← FALSE
ENDLOOP;
PupSocket.Destroy[socket];
};
}.