/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: HalDevices.cs // // Note: // using System; using System.Collections; using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Threading; using Microsoft.Singularity; using Microsoft.Singularity.Io; using Microsoft.Singularity.Hal.Acpi; namespace Microsoft.Singularity.Hal { // Lockstep states for MP initialization. The bootstrap // processor (BSP) sets states beginning Bsp and the // upcoming Application Processor (AP) sets states beginning // with Ap. internal enum MpSyncState : int { BspWaitingForAp = 0, ApOnline = 1, BspWaitingForApCalibration = 2, ApCalibrationDone = 3, BspStartingTscCorrelation = 4, ApTscCorrelationDone = 5, BspWaitingApRunning = 6, ApRunning = 7 } [CLSCompliant(false)] public sealed class HalDevices { private const byte PicBaseVector = 0x60; private const byte ApicBaseVector = 0x70; private static IoApic [] ioApics; private static HalPic halPic; // dumb wrapper for APIC for kernel private static Apic apic; private static Pic pic; // for squelching pic interrupts private static HalTimer halTimer; // wrapper for APIC timer for kernel private static ApicTimer apicTimer; private static PMTimer pmTimer; private static Timer8254 stallTimer; private static RTClock rtClock; private static HalClock halClock; private static HalMemory halMemory; private static int processorCount; private static volatile MpSyncState mpSyncState; // TSC skew calculation variables private const int TscCalibrationRounds = 20; private static volatile uint tsc0Lo; private static volatile uint tsc0Hi; private static ulong cpu0CyclesPerSecond; // Kernel thread stack variables private static Thread nextKernelThread; private static UIntPtr nextKernelStackBegin; private static UIntPtr nextKernelStackLimit; public static void InitializeBsp(Processor rootProcessor) { DebugStub.Print("HalDevices.Initialize()\n"); AcpiTables.Parse(); pmTimer = AcpiTables.GetPMTimer(); // Get PIC resources. Pic is masked by default. PnpConfig picConfig = (PnpConfig)IoSystem.YieldResources("/pnp/PNP0000", typeof(Pic)); pic = new Pic(picConfig); pic.Initialize(PicBaseVector); // Parse MP Table and create IO apics MpResources.ParseMpTables(); ioApics = IoApic.CreateIOApics(); apic = new Apic(ioApics); apic.Initialize(ApicBaseVector); halPic = new HalPic(apic); // Apic timer is used to provide one-shot timer interrupts. apicTimer = new ApicTimer(apic); apicTimer.Initialize(); halTimer = new HalTimer(apicTimer); // Calibrate timers Calibrate.CpuCycleCounter(pmTimer); Calibrate.ApicTimer(pmTimer, apicTimer); // Store BSPs clock rate cpu0CyclesPerSecond = Processor.CyclesPerSecond; // Legacy timer is used to time stalls when starting CPUs. PnpConfig i8254Config = (PnpConfig)IoSystem.YieldResources("/pnp/PNP0100", typeof(Timer8254)); stallTimer = new Timer8254(i8254Config); // Real-time clock PnpConfig rtClockConfig = (PnpConfig)IoSystem.YieldResources("/pnp/PNP0B00", typeof(RTClock)); rtClock = new RTClock(rtClockConfig, apic); // Compose HalClock halClock = new HalClock(apic, rtClock, new PMClock(pmTimer)); SystemClock.Initialize(halClock, TimeSpan.FromHours(8).Ticks); rootProcessor.AddPic(halPic); rootProcessor.AddTimer(halTimer.Interrupt, halTimer); rootProcessor.AddClock(halClock.Interrupt, halClock); InitializeProcessorCount(); DebugReportProcessors(); // ------------------------------------------------ // haryadi: add Srat tables to the Processor halMemory = new HalMemory(AcpiTables.GetSrat()); Processor.AddHalMemory(halMemory); Processor.SetIdtTable(); apicTimer.Start(); // Get the screen resources. Since we have metadata above to // declare all fixed resources used by the screen, // YieldResources("") will keep the resource tracking correct: Console.Screen = new HalScreen(IoSystem.YieldResources("", typeof(HalScreen))); apic.DumpState(); foreach (IoApic ioApic in ioApics) { ioApic.DumpRedirectionEntries(); } pic.DumpRegisters(); } public static void InitializeAp(Processor processor) { Processor.SetIdtTable(); Thread.BindKernelThread(nextKernelThread, nextKernelStackBegin, nextKernelStackLimit); // Fleeting check that allocator works. Object o = new Object(); if (o == null) DebugStub.Break(); apic.Initialize(); apicTimer.Initialize(); processor.AddPic(halPic); processor.AddTimer(halTimer.Interrupt, halTimer); processor.AddClock(halClock.Interrupt, halClock); SetMpSyncState(MpSyncState.ApOnline); WaitForMpSyncState(MpSyncState.BspWaitingForApCalibration); // Calibrate timers ulong t1 = Processor.CycleCount; Calibrate.CpuCycleCounter(pmTimer); Calibrate.ApicTimer(pmTimer, apicTimer); ulong t2 = Processor.CycleCount; Tracing.Log(Tracing.Audit, "Calibration time {0}", new UIntPtr((uint)(t2 - t1))); SetMpSyncState(MpSyncState.ApCalibrationDone); // Calculate tsc skew long [] skewEstimates = new long[TscCalibrationRounds]; for (int i = 0; i < TscCalibrationRounds; i++) { ApSync(out skewEstimates[i]); } bool success = false; WaitForMpSyncState(MpSyncState.BspWaitingApRunning, 100000, out success); SetMpSyncState(MpSyncState.ApRunning); // Sort skew estimates and picked median Array.Sort(skewEstimates); Tracing.Log(Tracing.Audit, "Skew estimate {0}", new UIntPtr((int)(skewEstimates[TscCalibrationRounds / 2]))); Tracing.SetTscOffset((skewEstimates[TscCalibrationRounds / 2])); apicTimer.Start(); } public static void Initialize(Processor processor) { if (processor.Id == 0) { InitializeBsp(processor); IoSystem.RegisterKernelDriver( typeof(HpetResources), new IoDeviceCreate(Hpet.CreateDevice) ); } else { InitializeAp(processor); } } public static void Finalize() { rtClock.Finalize(); rtClock = null; apicTimer.Finalize(); apicTimer = null; apic.Finalize(); apic = null; pic.Finalize(); pic = null; } [NoHeapAllocation] public static bool InternalInterrupt(byte interrupt) { if (interrupt >= PicBaseVector && interrupt < ApicBaseVector) { // Spurious PIC interrupt. // Appears to happen once on test boards even though PIC // is masked out and no interrupt was raised before masking. pic.DumpRegisters(); pic.AckIrq((byte) (interrupt - PicBaseVector)); return true; } return false; } [NoHeapAllocation] internal static void StallProcessor(uint microseconds) { ulong todo = microseconds * (Processor.CyclesPerSecond / 1000000); ulong last = Processor.CycleCount; ulong elapsed = 0; while (elapsed < todo) { // Keep track of how much time has elapsed. ulong now = Processor.CycleCount; elapsed += (now - last); last = now; } } internal static void SwitchToHpetClock(Hpet hpet) { DebugStub.Print("Switching to HPET clock"); halClock.SwitchToHpetClock( new HpetClock(hpet, (ulong)halClock.GetKernelTicks() + 1000u) ); } [Conditional("SINGULARITY_MP")] [NoHeapAllocation] private static void SetMpSyncState(MpSyncState newState) { Tracing.Log(Tracing.Debug, "Changing MP sync state {0} -> {1}", (uint)mpSyncState, (uint)newState); mpSyncState = newState; } [Conditional("SINGULARITY_MP")] [NoHeapAllocation] private static void WaitForMpSyncState(MpSyncState newState, uint microseconds, out bool success) { Tracing.Log( Tracing.Debug, "Waiting for MP sync state {0} -> {1} ({2} microseconds)", (uint)mpSyncState, (uint)newState, microseconds ); ulong todo = microseconds * (Processor.CyclesPerSecond / 1000000); ulong last = Processor.CycleCount; ulong elapsed = 0; success = false; while (elapsed < todo) { ulong now = Processor.CycleCount; elapsed += (now - last); last = now; if (HalDevices.mpSyncState == newState) { success = true; return; } } Tracing.Log(Tracing.Debug, "Mp sync state timed out"); } [Conditional("SINGULARITY_MP")] [NoHeapAllocation] private static void WaitForMpSyncState(MpSyncState newState) { Tracing.Log( Tracing.Debug, "Waiting for MP sync state {0} -> {1} (indefinite)", (uint)mpSyncState, (uint)newState ); while (HalDevices.mpSyncState != newState) ; } public static byte GetMaximumIrq() { return apic.MaximumIrq; } // // Adding and removing interrupts from the Pic. // [NoHeapAllocation] public static void EnableIoInterrupt(byte irq) { apic.EnableIrq(irq); } [NoHeapAllocation] public static void DisableIoInterrupt(byte irq) { apic.DisableIrq(irq); } private static void InitializeProcessorCount() { Madt madt = AcpiTables.GetMadt(); if (madt != null) { processorCount = madt.GetLocalApics().Count; } else if (MpResources.ProcessorEntries != null) { processorCount = MpResources.ProcessorEntries.Count; } else { processorCount = 1; } } [NoHeapAllocation] private static int GetProcessorCount() { return processorCount; } private static void DebugReportProcessors() { #if SINGULARITY_MP string kernelType = "Multi"; #else string kernelType = "Uni"; #endif DebugStub.Print("{0}processor kernel on {1} processor system.\n", __arglist(kernelType, GetProcessorCount())); } // Runs Bootstrap processor component of TSC offset computation. [Conditional("SINGULARITY_MP")] [NoHeapAllocation] private static void BspSync(uint microseconds) { ulong todo = microseconds * (Processor.CyclesPerSecond / 1000000); ulong last = Processor.CycleCount; ulong elapsed = 0; SetMpSyncState(MpSyncState.BspStartingTscCorrelation); elapsed = 0; while (elapsed < todo && mpSyncState != MpSyncState.ApTscCorrelationDone) { // Keep track of how much time has elapsed. ulong now = Processor.CycleCount; elapsed += (now - last); last = now; // Order of these assignments is important tsc0Lo = (uint)(now & 0xffffffff); tsc0Hi = (uint)(now >> 32); // Yield hardware thread on HT boxes Thread.NativeNoOp(); } } [Conditional("SINGULARITY_MP")] [NoHeapAllocation] private static void ApSync(out long tscOffset) { WaitForMpSyncState(MpSyncState.BspStartingTscCorrelation); ulong now = 0; uint tscLo; uint tscHi; uint tscTmp; for (int i = 0; i < 5; i++) { do { now = Processor.CycleCount; tscLo = tsc0Lo; tscHi = tsc0Hi; tscTmp = tsc0Lo; // Yield hardware thread on HT boxes Thread.NativeNoOp(); } while (tscTmp != tscLo); } ulong tsc0 = (((ulong)tsc0Hi) << 32) + tsc0Lo; if (tsc0 > now) { Tracing.Log(Tracing.Audit, "Skew tsc-{0}", new UIntPtr(tsc0 - now)); } else { Tracing.Log(Tracing.Audit, "Skew tsc+{0}", new UIntPtr(now - tsc0)); } tscOffset = (long)(now - tsc0); SetMpSyncState(MpSyncState.ApTscCorrelationDone); } [Conditional("SINGULARITY_MP")] [NoHeapAllocation] private static void AnnounceApFail(int nextCpu) { MpSyncState localMpState = mpSyncState; DebugStub.Print("MpState {0}", __arglist(localMpState)); DebugStub.Break(); BootInfo bi = BootInfo.GetBootInfo(); DebugStub.Print("Cpu {0} failed. ", __arglist(nextCpu)); DebugStub.Print("GotBack-> Status {0} Count {1:x8}\n", __arglist(bi.MpStatus32, bi.MpCpuCount)); } [Conditional("SINGULARITY_MP")] // [NoHeapAllocation] private static void StartApProcessorsInternal() { // Currently we fire up all processors. They // serialize on a spin lock in low memory. int expectedCpus = GetProcessorCount(); int nextCpu = 1; bool en = Processor.DisableInterrupts(); try { do { DebugStub.Print("Starting CPU {0} / {1}\n", __arglist(nextCpu + 1, expectedCpus)); SetMpSyncState(MpSyncState.BspWaitingForAp); Tracing.Log(Tracing.Audit, "Initializing cpu {0}", (uint)nextCpu); nextKernelThread = Thread.PrepareKernelThread(); MpBootInfo.PrepareForCpuStart(nextCpu); MpBootInfo mbi = MpBootInfo.GetMpBootInfo(); nextKernelStackBegin = mbi.KernelStackBegin; nextKernelStackLimit = mbi.KernelStackLimit; Tracing.Log(Tracing.Audit, "Starting cpu {0}. Stack start {1:x8} base {2:x8} limit {3:x8})\n", new UIntPtr(nextCpu), mbi.KernelStackBegin, mbi.KernelStack, mbi.KernelStackLimit); if (nextCpu == 1) { BootInfo bi = BootInfo.GetBootInfo(); apic.BroadcastStartupIPI((byte) (bi.MpEnter32 >> 12)); } bool success = false; WaitForMpSyncState(MpSyncState.ApOnline, 500000, out success); if (!success) { AnnounceApFail(nextCpu); continue; } SetMpSyncState(MpSyncState.BspWaitingForApCalibration); WaitForMpSyncState(MpSyncState.ApCalibrationDone, 1000000, out success); if (!success) { AnnounceApFail(nextCpu); continue; } for (int i = 0; i < TscCalibrationRounds; i++) { BspSync(100000); } SetMpSyncState(MpSyncState.BspWaitingApRunning); WaitForMpSyncState(MpSyncState.ApRunning, 100000, out success); if (!success) { AnnounceApFail(nextCpu); continue; } Processor.RestoreInterrupts(en); en = false; DebugStub.Print("Cpu {0} running ({1} remaining).\n", __arglist(nextCpu, expectedCpus - nextCpu - 1)); } while (++nextCpu < expectedCpus); DebugStub.Print("Done."); } finally { DebugStub.Print("Reached finally block in HalDevices.StartApProcessorsInternal()"); Processor.RestoreInterrupts(en); } } // [NoHeapAllocation] public static void StartApProcessors() { if (GetProcessorCount() > 1) { StartApProcessorsInternal(); } } [NoHeapAllocation] public static void FreezeProcessors() { apic.BroadcastFreezeIPI(); } [NoHeapAllocation] public static void SendFixedIPI(byte vector, int from, int to) { apic.SendFixedIPI(vector, from, to); } [NoHeapAllocation] public static void BroadcastFixedIPI(byte vector, bool includeSelf) { apic.BroadcastFixedIPI(vector, includeSelf); } [NoHeapAllocation] public static void ClearFixedIPI() { apic.ClearFixedIPI(); } } }