BasicTimeImpl.mesa: low-level time facilities
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) March 31, 1986 8:12:42 pm PST
Sturgis, March 27, 1986 10:54:01 am PST
DIRECTORY
Basics USING [LongDiv, LongDivMod, LongMult, LongNumber],
BasicTime USING [DayOfWeek, MonthOfYear, Pulses, Unpacked, unspecifiedZone, Zone, ZoneAndDST],
BasicTimeExtra USING[DST],
File USING [ SystemVolume, Volume],
GermSwap USING [switches],
PhysicalVolume USING [GetPhysical, Physical, PhysicalInfo, PhysicalRC, WriteTimeParameters],
Process USING [Detach],
ProcessorFace USING [GetClockPulses, GetGreenwichMeanTime, gmtEpoch, microsecondsPerHundredPulses, SetGreenwichMeanTime],
PupDefs USING [GetFreePupBuffer, MsToTocks, PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake, ReturnFreePupBuffer, SetPupContentsBytes],
PupTypes USING [allHosts, allNets, fillInSocketID, miscSrvSoc, Pair];
BasicTimeImpl: CEDAR MONITOR
IMPORTS Basics, File, GermSwap, PhysicalVolume, Process, ProcessorFace, PupDefs
EXPORTS BasicTime, BasicTimeExtra, 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 {
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
ControlledUnpack: PUBLIC PROC [time: GMT, zone: BasicTime.Zone, dst: BasicTimeExtra.DST ← unspecified, beginDST: [0..366] ← 0, endDST: [0..366] ← 0] RETURNS [unpacked: BasicTime.Unpacked] = {
secs, mins, hrs: CARD;
IF time < earliestGMT OR time > latestGMT THEN ERROR OutOfRange[];
secs ← time - gmtOrigin;
mins ← secs / 60 - (IF zone = BasicTime.unspecifiedZone THEN 0 ELSE zone);
hrs ← mins / 60;
unpacked.second ← secs MOD 60;
unpacked.minute ← mins MOD 60;
unpacked.zone ← (IF zone = BasicTime.unspecifiedZone THEN 0 ELSE 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 zone = BasicTime.unspecifiedZone
OR dst = no
OR (dst = unspecified AND NOT CheckDateGE[unpacked, beginDST, 2])
OR (dst = unspecified AND CheckDateGE[unpacked, endDST, 1])
THEN EXIT;
hrs ← hrs+1;
unpacked.dst ← yes;
ENDLOOP;
};
Unpack: PUBLIC PROC [time: GMT] RETURNS [ unpacked: BasicTime.Unpacked ] = {
tp: BasicTime.ZoneAndDST = GetZoneAndDST[];
RETURN[ControlledUnpack[time, tp.zone, unspecified, tp.beginDST, tp.endDST]];
};
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: PupTypes.Pair = LOOPHOLE[ProcessorFace.GetClockPulses[]];
soc: PupDefs.PupSocket = PupDefs.PupSocketMake[
PupTypes.fillInSocketID,
[net: PupTypes.allNets, host: PupTypes.allHosts, socket: PupTypes.miscSrvSoc],
PupDefs.MsToTocks[500]];
THROUGH [0..10) DO
{
how often to transmit request
ENABLE UNWIND => PupDefs.PupSocketDestroy[soc];
send: PupDefs.PupBuffer = PupDefs.GetFreePupBuffer[];
send.pupType ← dateAltoRequest;
send.pupID ← id;
PupDefs.SetPupContentsBytes[send, 0];
soc.put[send];
DO
recv: PupDefs.PupBuffer = soc.get[];
IF recv = NIL THEN EXIT;
IF recv.pupType = dateAltoIs AND recv.pupID = id
THEN {
PupTime: TYPE = MACHINE DEPENDENT RECORD [
timeHigh, timeLow: CARDINAL,
direction: { west(0), east(1) },
zone: [0..127],
zoneMinutes: [0..255],
beginDST, endDST: CARDINAL
];
data: PupTime = LOOPHOLE[@recv.pupWords, LONG POINTER TO PupTime]^;
PupDefs.ReturnFreePupBuffer[recv];
physTP ← [
direction: IF data.direction = west THEN west ELSE east,
zone: data.zone,
zoneMinutes: data.zoneMinutes,
beginDST: data.beginDST,
endDST: data.endDST];
time ← LOOPHOLE[
Basics.LongNumber[num[lowbits: data.timeLow, highbits: data.timeHigh]]];
GOTO done
}
ELSE PupDefs.ReturnFreePupBuffer[recv];
ENDLOOP;
}
REPEAT
done => physTPOK ← TRUE;
FINISHED => physTPOK ← FALSE
ENDLOOP;
PupDefs.PupSocketDestroy[soc];
};
TRUSTED {
Process.Detach [FORK SetTime[]];
};
}.