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; knownTP: BasicTime.ZoneAndDST; knownTPValid: BOOL _ FALSE; OutOfRange: PUBLIC ERROR = CODE; TimeNotKnown: PUBLIC ERROR = CODE; TimeParametersNotKnown: PUBLIC ERROR = CODE; PulsesToMicroseconds: PUBLIC PROC [p: BasicTime.Pulses] RETURNS [ CARD ] = { RETURN [MultThenDiv[p, ProcessorFace.microsecondsPerHundredPulses, 100]]; }; PulsesToSeconds: PUBLIC PROC [p: BasicTime.Pulses] RETURNS [s: REAL ] = { 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]; 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 }; }; RETURN [t.lowlong/LONG[dv]] }; 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 { 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] = { IF period IN [gmtOrigin-base .. gmtExpiry-base] THEN RETURN [base+period] ELSE ERROR OutOfRange; }; ToPupTime: PUBLIC PROC [g: GMT] RETURNS [ CARD ] = { RETURN [ alto1968+(g-gmtOrigin) ]; }; ToNSTime: PUBLIC PROC [g: GMT] RETURNS [ CARD ] = { RETURN [alto1968+(g-gmtOrigin)]; }; FromPupTime: PUBLIC PROC [p: CARD] RETURNS [ GMT ] = { IF p >= alto1968 THEN RETURN [(p-alto1968)+gmtOrigin] ELSE ERROR OutOfRange; }; FromNSTime: PUBLIC PROC [p: CARD] RETURNS [ GMT ] = { IF p >= alto1968 THEN RETURN [(p-alto1968)+gmtOrigin] ELSE ERROR OutOfRange; }; 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 ] = { 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 } }; --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: BOOL _ FALSE, 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 { RETURN [ProcessorFace.GetGreenwichMeanTime[]]; }; LockedSetTime: ENTRY PROC [netTP: TimeParameters, secs: CARD--NS/Pilot--] = { 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]; }; }. ²BasicTimeImpl.mesa: low-level time facilities Copyright c 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 The monitor locks protects access to ProcessorFace (GetGreenwichMeanTime & SetGreenwichMeanTime) global frame (knownTP, knownTPValid) disk parameters (PhysicalVolume.WriteTimeParameters & PhysicalVolume.PhysicalInfo) Global variables 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 Part I: Fine-grain timer 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. Copied from Pilot's SystemImpl. Have to do triple divide t.high is 0, so let mesa do the work... Part II: Packed times means "unset clock" 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. Returns time according to the Alto/Pup time standard. No errors. Returns time according to the Xerox NS protocol time standard. No errors. Accepts time according to the Alto/Pup time standard. Raises "OutOfRange" for times earlier than 1968. Accepts time according to the Xerox NS protocol time standard. Raises "OutOfRange" for times beyond about 2036. Part III: Packing and unpacking Raises "OutOfRange" for times before 1968 or after 2036. Part IV: Finding and setting time and zone/dst information This is an ENTRY procedure to protect ProcessorFace.GetGreenwichMeanTime from interference by ProcessorFace.SetGreenwichMeanTime. This is an ENTRY procedure to protect ConvertPhysTP and ProcessorFace.SetGreenwichMeanTime. Κ˜codešœ.™.Kšœ Οmœ1™Kšœž™žšžœžœ#˜/Kšžœžœ˜Kšžœžœ ˜—Kšœ˜K˜—š Ÿ œžœžœžœžœžœ˜4KšœA™AKšžœ˜"Kšœ˜K˜—š Ÿœžœžœžœžœžœ˜3KšœJ™JKšžœ˜ Kšœ˜K˜—š Ÿ œžœžœžœžœžœ˜6Kšœg™gKš žœžœžœžœžœ ˜LKšœ˜K˜—š Ÿ œžœžœžœžœžœ˜5Kšœp™pKš žœžœžœžœžœ ˜LKšœ˜K˜——šœ™K˜šœ žœžœžœ˜5Kšœ=˜=K˜—šœ,  ˜LK˜—š Ÿœžœžœžœžœ%˜LKšœ+˜+Kšœžœ˜Kšžœžœžœžœ˜BKšœ˜šœ˜Kšœžœ%žœžœ ˜=—Kšœ˜Kšœžœ˜Kšœžœ˜Kšœžœ%žœžœ ˜MKšœ˜šžœ  ˜#Kšœžœ ˜#Kšœžœ˜,Kšœ žœ#˜6Kšœžœžœ˜Bšœžœ *˜GKšœžœ)˜C—Kšœ*˜*Kšœ žœžœ  ˜QKšœ5˜5Kšœ ˜ Kšžœžœ˜ šžœžœ˜-Kšžœ˜!Kšžœžœ˜3—Kšžœ˜Kšžœžœ)˜Lšžœžœžœ˜*Kšžœžœ˜$—Kšžœžœž˜Kšžœ˜Kšœ4˜4Kšœžœ˜šœ˜šœ žœ ˜3Kšœžœžœžœ˜5—Kšœ2˜2Kšœ$žœ˜E—Kšœ˜Kšžœ˜Kšžœ$˜&Kšžœžœ&˜,Kšžœ$˜&Kšžœžœ˜ K˜ Kšœ˜—Kšžœ˜Kšœ˜K˜—š Ÿœžœžœžœ žœ˜JKšœ8™8Kšœ+˜+Kšœžœ˜,šœžœžœ˜9Kšžœžœ ˜Kšžœ˜!—Kšœ"žœ˜(šœžœ ˜(Kšœ- ˜FKšœ I˜KKš œžœ žœžœžœžœžœ˜L—šœžœ˜Kšœ$˜$Kšœ˜Kš œžœžœžœ  œ˜/—šœ˜Kšžœ*˜,Kšžœ˜šžœžœ$˜+Kšžœ˜ Kšžœžœ˜$——šžœž˜šœ˜Kšœ ˜ šžœžœž˜#Kšœžœ˜*šžœž˜&Kšœžœ˜.—Kšžœ˜—Kšžœ%žœžœ˜Kšžœ%žœ#˜MKšžœ˜—Kšœ˜—Kšœ˜Kšœžœ˜ —Kšžœžœ˜Kšžœžœ]˜xKšœ˜K˜—š Ÿ œžœ0žœžœžœ˜^Kšœžœ-˜:šžœžœž˜Kšœžœžœ ˜@Kšœžœžœ ˜K˜K˜Kšœ ˜/K˜K˜—K˜šŸœžœžœžœ˜ Kšœ˜Kšœ˜K˜—šŸ œžœžœžœ˜?Kšžœžœžœ˜šœ˜Kšœ žœ žœžœ˜.Kšœžœ˜Kšœ žœ žœ˜!Kšœ˜Kšœ˜—Kšœ0˜0Kšžœ žœžœ2žœ ˜WKšœ˜Kšœ˜K˜—šŸ œžœžœžœ˜AKšžœžœžœ˜Kš žœžœžœžœžœ ˜KKšžœC˜IKšœ˜K˜—šŸ œžœžœžœžœžœ  œ˜cK˜Kšžœžœžœ )˜NKšœ'˜'Kšžœžœžœ+˜9Kšžœžœžœžœ˜Kšœ˜Kšœ˜K˜—š Ÿ œžœžœžœžœžœ˜4Kšœ žœq™Kšžœ(˜.Kšœ˜K˜—š Ÿ œžœžœž  œ˜MKšœ žœK™[šžœžœžœ˜Kšœ0˜0Kšžœ žœžœ2žœ ˜TKšœ˜Kšœ˜—Kšžœ,˜3Kšœ˜K˜—š Ÿ œžœžœžœžœ˜)Kšœ0˜0Kšœ)˜)K˜Kšœ žœ˜Kš žœ žœžœžœžœžœ˜5šœ@˜@Kšœ%˜%—Kš žœžœžœ žœžœžœ˜:Kšœ˜Kšžœžœ 0˜>Kšœ˜K˜—šŸ œžœžœ˜9˜ K˜*K˜K˜—Kšžœžœ˜=Kšœžœ˜Kšœ˜K˜—šŸ œžœžœ˜6K˜'Kš žœžœžœžœžœžœ#˜FKšœ˜K˜—š Ÿœžœžœ žœ žœ˜WKšœ žœ˜Kšœ˜K˜—š Ÿœžœžœ žœ žœžœ˜bKšœ žœ˜+šœ5˜5KšœD˜DKšœ˜—Kšœ˜šžœžœžœ ž˜Kšœ4˜4Kšœ˜K˜ Kšœ˜Kšœ˜šž˜Kšœ˜Kšžœžœžœžœ˜šžœžœ žœ˜.˜ Kšœ žœžœžœ˜:Kšœ˜Kšœ ˜ Kšœ˜Kšœ˜—Kšœ%˜%Kšœ˜Kšžœ ˜ —Kšœ˜Kšžœ˜—šž˜Kšœžœ˜Kšžœž˜—Kšžœ˜—Kšœ˜Kšœ˜K˜——Kšœ˜K˜K˜—…—.Eί