<> <> <> <> 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 <> OutOfRange: PUBLIC ERROR = CODE; TimeNotKnown: PUBLIC ERROR = CODE; TimeParametersNotKnown: PUBLIC ERROR = CODE; <> 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 <> 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]; <> 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; <> RETURN[t.lowlong/LONG[dv]] END; <> 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; <> ToPupTime: PUBLIC PROC[g: GMT] RETURNS[ LONG CARDINAL ] = { RETURN[ alto1968+(g-gmtOrigin) ] }; <> ToNSTime: PUBLIC PROC[g: GMT] RETURNS[ LONG CARDINAL ] = { RETURN[alto1968+(g-gmtOrigin)] }; <> FromPupTime: PUBLIC PROC[p: LONG CARDINAL] RETURNS[ GMT ] = { IF p >= alto1968 THEN RETURN[(p-alto1968)+gmtOrigin] ELSE ERROR OutOfRange }; <> FromNSTime: PUBLIC PROC[p: LONG CARDINAL] 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 ] = 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; <> 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; <> --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: BOOL _ FALSE; 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.