Cedar Nucleus: low-level time facilities
BasicTimeImpl.mesa
Andrew Birrell November 30, 1983 9:31 am
Last Edited by: Levin, December 16, 1983 3:44 pm
DIRECTORY
Basics USING[ LongDiv, LongDivMod, LongMult, LongNumber ],
BasicTime USING[ DayOfWeek, MonthOfYear, Pulses, Unpacked, unspecifiedZone, Zone, ZoneAndDST ],
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, PhysicalVolume--TimeParameters-- =
BEGIN
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[ LONG CARDINAL ] =
{ RETURN[MultThenDiv[p, ProcessorFace.microsecondsPerHundredPulses, 100]] };
PulsesToSeconds: PUBLIC PROC[p: BasicTime.Pulses] RETURNS[s: REAL ] =
BEGIN
s ← p;
s ← ( s * ProcessorFace.microsecondsPerHundredPulses ) / 1e8;
END;
MicrosecondsToPulses: PUBLIC PROC[m: LONG CARDINAL] RETURNS[BasicTime.Pulses ] =
{ RETURN[MultThenDiv[m, 100, ProcessorFace.microsecondsPerHundredPulses]] };
MultThenDiv: PROC [m1: LONG CARDINAL, m2: CARDINAL, dv: CARDINAL] RETURNS [result: LONG CARDINAL] = TRUSTED
BEGIN
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: LONG CARDINAL, junk: CARDINAL],
higher => [junk: CARDINAL, highlong: LONG CARDINAL],
ENDCASE];
t.lowlong ← Basics.LongMult[mm1.lowbits, m2];
IF mm1.highbits # 0 THEN
BEGIN
t.highlong ← Basics.LongMult[mm1.highbits, m2] + t.mid;
IF t.high # 0 THEN
BEGIN
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
END;
END;
t.high is 0, so let mesa do the work...
RETURN[t.lowlong/LONG[dv]]
END;
Part II: Packed times
GMT: PUBLIC TYPE = INT;
gmtBaseYear: CARDINAL = 1968;
gmtOrigin: GMT = 0; -- representation for midnight, January 1st 1968 GMT
alto1968: LONG CARDINAL = -- our time origin in Alto/Pup format --
LONG[ (gmtBaseYear-1901) * 365 + (gmtBaseYear-1901)/4 ] * 24 * 60 * 60;
altoExpiry: LONG CARDINAL = LAST[LONG CARDINAL] - 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 ENTRY PROC RETURNS [GMT] = -- N.B.: serialize access to ProcessorFace
TRUSTED BEGIN
ENABLE UNWIND => NULL;
prTime: LONG CARDINAL ← ProcessorFace.GetGreenwichMeanTime[];
IF prTime = ProcessorFace.gmtEpoch -- means "unset clock"
THEN BEGIN
ok: BOOL;
[ok, prTime] ← SetTimeAndTP[];
IF NOT ok THEN ERROR TimeNotKnown;
END;
RETURN[prTime-alto1968+gmtOrigin ]
END;
Period: PUBLIC PROC[from, to: GMT] RETURNS [INT] =
{ RETURN[to-from] };
Update: PUBLIC PROC[base: GMT, period: INT] RETURNS [GMT] =
BEGIN
IF period IN [gmtOrigin-base .. gmtExpiry-base]
THEN RETURN[base+period]
ELSE ERROR OutOfRange;
END;
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.
ToPupTime: PUBLIC PROC[g: GMT] RETURNS[ LONG CARDINAL ] =
{ RETURN[ alto1968+(g-gmtOrigin) ] };
Returns time according to the Alto/Pup time standard. No errors.
ToNSTime: PUBLIC PROC[g: GMT] RETURNS[ LONG CARDINAL ] =
{ RETURN[alto1968+(g-gmtOrigin)] };
Returns time according to the Xerox NS protocol time standard. No errors.
FromPupTime: PUBLIC PROC[p: LONG CARDINAL] RETURNS[ GMT ] =
{ IF p >= alto1968 THEN RETURN[(p-alto1968)+gmtOrigin] ELSE ERROR OutOfRange };
Accepts time according to the Alto/Pup time standard. Raises "OutOfRange" for times earlier than 1968.
FromNSTime: PUBLIC PROC[p: LONG CARDINAL] RETURNS[ GMT ] =
{ IF p >= alto1968 THEN RETURN[(p-alto1968)+gmtOrigin] ELSE ERROR OutOfRange };
Accepts time according to the Xerox NS protocol time standard. Raises "OutOfRange" for times beyond about 2036.
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 ] =
BEGIN
tp: BasicTime.ZoneAndDST = GetZoneAndDST[];
secs, mins, hrs: LONG CARDINAL;
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;
BEGIN
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;
END;
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;
END;
Pack: PUBLIC PROC[unpacked: BasicTime.Unpacked] RETURNS[ time: GMT ] =
BEGIN
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 =>
BEGIN
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;
END;
yes => zone ← zone - 60;
no => NULL;
ENDCASE => ERROR;
RETURN[ gmtOrigin + ((LONG[daysSinceBase] * 24 + unpacked.hour) * 60 + unpacked.minute + zone) * 60 + unpacked.second ]
END;
Raises "OutOfRange" for times before 1968 or after 2036.
CheckDateGE: PROC[unpacked: BasicTime.Unpacked, dstDay, dstHour: CARDINAL] RETURNS[BOOL] =
BEGIN
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 =>
BEGIN
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
END
END;
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]
];
knownTP: BasicTime.ZoneAndDST;
knownTPValid: BOOLFALSE;
SetTime: PUBLIC ENTRY PROC =
{ ENABLE UNWIND => NULL; [] ← SetTimeAndTP[] };
SetZoneAndDST: PUBLIC ENTRY PROC[tp: BasicTime.ZoneAndDST] =
BEGIN
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];
END;
GetZoneAndDST: PUBLIC ENTRY PROC RETURNS[tp: BasicTime.ZoneAndDST] =
BEGIN
ENABLE UNWIND => NULL;
IF knownTPValid OR SetTimeAndTP[].ok OR TPFromDisk[] THEN RETURN[knownTP];
RETURN[ [zone: BasicTime.unspecifiedZone, beginDST: 366, endDST: 366] ];
END;
SetTimeAndTP: INTERNAL PROC RETURNS[ok: BOOL, secs: LONG CARDINAL--NS/Pilot--] =
BEGIN
sysPhys: PhysicalVolume.Physical = GetSysPhys[];
netTP: TimeParameters;
ok ← FALSE;
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;
IF NOT knownTPValid
THEN BEGIN
IF sysPhys # NIL THEN [] ← PhysicalVolume.WriteTimeParameters[sysPhys, TRUE, netTP];
ConvertPhysTP[netTP];
END;
TRUSTED{ProcessorFace.SetGreenwichMeanTime[secs]};
END;
TPFromDisk: INTERNAL PROC RETURNS[BOOL] =
BEGIN
sysPhys: PhysicalVolume.Physical = GetSysPhys[];
sysPhysStatus: PhysicalVolume.PhysicalRC;
physTP: TimeParameters;
physTPOK: BOOL;
IF sysPhys = NIL 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?
END;
ConvertPhysTP: PROC[physTP: TimeParameters] =
BEGIN
knownTP ← [
zone: physTP.zone*60 + physTP.zoneMinutes,
beginDST: physTP.beginDST,
endDST: physTP.endDST];
IF physTP.direction # west THEN knownTP.zone ← -knownTP.zone;
knownTPValid ← TRUE;
END;
GetSysPhys: PROC RETURNS[PhysicalVolume.Physical] =
BEGIN
sys: File.Volume = File.SystemVolume[];
RETURN[ IF sys = NIL THEN NIL ELSE PhysicalVolume.GetPhysical[sys] ]
END;
TimeFromEthernet: PROC RETURNS[physTPOK: BOOL, physTP: TimeParameters, time: LONG CARDINAL] =
BEGIN
physTPOK ← FALSE;
END;
TimeFromEthernetOne: PROC RETURNS[physTPOK: BOOL, physTP: TimeParameters, time: LONG CARDINAL] =
TRUSTED BEGIN
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) -- how often to transmit request
DO BEGIN
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 BEGIN
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
END
ELSE PupDefs.ReturnFreePupBuffer[recv];
ENDLOOP;
END
REPEAT
done => physTPOK ← TRUE;
FINISHED => physTPOK ← FALSE
ENDLOOP;
PupDefs.PupSocketDestroy[soc];
END;
TRUSTED{ Process.Detach[FORK SetTime[]] };
END.