/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: HalDevices.cs // // Note: // // #define DEBUG_PCI_INTERRUPT_TRANSLATION 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.Hal; using Microsoft.Singularity.Hal.Acpi; using Microsoft.Singularity.Io; 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, BspWaitingApRunning = 6, ApRunning = 7 } [CLSCompliant(false)] public sealed class HalDevicesApic : HalDevices { private const byte PicBaseVector = 0x60; private const byte ApicBaseVector = 0x70; private static IoApic [] ioApics; private static HalDevicesApic devices; private static HalClockApic halClock; private static ApicTimer halTimer; private static Apic halPic; private static PicStub pic; // for squelching pic interrupts private static PMTimer pmTimer; private static Timer8254Apic stallTimer; private static RTClockApic rtClock; private static HalMemory halMemory; private static int processorCount; private static volatile MpSyncState mpSyncState; private static HalScreenDirect halScreen; public static HalDevices Create() { devices = new HalDevicesApic(); return devices; } private void InitializeBsp(Processor rootProcessor) { DebugStub.Print("HalDevicesApic.Initialize()\n"); pmTimer = AcpiTables.GetPMTimer(); // Get PIC resources. Pic is masked by default. PnpConfig picConfig = (PnpConfig)IoSystem.YieldResources("/pnp/PNP0000", typeof(PicStub)); pic = new PicStub(picConfig); pic.Initialize(PicBaseVector); // Parse MP Table and create IO apics MpResources.ParseMpTables(); ioApics = IoApic.CreateIOApics(); halPic = new Apic(ioApics); halPic.Initialize(ApicBaseVector); // Apic timer is used to provide one-shot timer interrupts. halTimer = new ApicTimer(halPic); halTimer.Initialize(); // Calibrate timers Calibrate.CpuCycleCounter(pmTimer); Calibrate.ApicTimer(pmTimer, halTimer); // Legacy timer is used to time stalls when starting CPUs. PnpConfig i8254Config = (PnpConfig)IoSystem.YieldResources("/pnp/PNP0100", typeof(Timer8254Apic)); stallTimer = new Timer8254Apic(i8254Config); // Real-time clock PnpConfig rtClockConfig = (PnpConfig)IoSystem.YieldResources("/pnp/PNP0B00", typeof(RTClockApic)); rtClock = new RTClockApic(rtClockConfig, halPic); // Compose our HalClock from the component clocks we have available halClock = new HalClockApic(halPic, 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(); halTimer.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: halScreen = new HalScreenDirect(IoSystem.YieldResources("", typeof(HalScreen))); Console.Screen = (HalScreen)halScreen; halPic.DumpState(); foreach (IoApic ioApic in ioApics) { ioApic.DumpRedirectionEntries(); } pic.DumpRegisters(); } private void InitializeAp(Processor processor) { // Thread.BindKernelThread(nextKernelThread, // nextKernelStackBegin, // nextKernelStackLimit); // Fleeting check that allocator works. Object o = new Object(); if (o == null) DebugStub.Break(); halPic.Initialize(); halTimer.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, halTimer); ulong t2 = Processor.CycleCount; Tracing.Log(Tracing.Audit, "Calibration time {0}", new UIntPtr((uint)(t2 - t1))); SetMpSyncState(MpSyncState.ApCalibrationDone); bool success = false; WaitForMpSyncState(MpSyncState.BspWaitingApRunning, 100000, ref success); SetMpSyncState(MpSyncState.ApRunning); halTimer.Start(); } public override void Initialize(Processor processor) { Cpu thisCpu = (Cpu) Platform.CurrentCpu; if (processor.Id == 0) { InitializeBsp(processor); IoSystem.RegisterKernelDriver( typeof(HpetResources), new IoDeviceCreate(Hpet.CreateDevice) ); } else { InitializeAp(processor); } } public override void ReleaseResources() { rtClock.ReleaseResources(); rtClock = null; halTimer.ReleaseResources(); halTimer = null; halPic.ReleaseResources(); halPic = null; pic.ReleaseResources(); pic = null; } [NoHeapAllocation] public override 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.WriteLine("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, ref 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 (HalDevicesApic.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 (HalDevicesApic.mpSyncState != newState) ; } [NoHeapAllocation] public override byte GetMaximumIrq() { return halPic.MaximumIrq; } // // Adding and removing interrupts from the Pic. // [NoHeapAllocation] public override void EnableIoInterrupt(byte irq) { halPic.EnableIrq(irq); } [NoHeapAllocation] public override void DisableIoInterrupt(byte irq) { halPic.DisableIrq(irq); } private 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; } // // Platform.ThePlatform.CpuRealCount was setup by // the boot loader and should be the same that we read here. // DebugStub.Assert(Platform.ThePlatform.CpuRealCount == processorCount); } // // This returns the available physical processors, which may be more // than the actual processors started. // [NoHeapAllocation] public override 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, devices.GetProcessorCount())); } [Conditional("SINGULARITY_MP")] [NoHeapAllocation] private static void AnnounceApFail(int nextCpu) { MpSyncState localMpState = mpSyncState; DebugStub.Print("MpState {0}", __arglist(localMpState)); DebugStub.Break(); Platform bi = Platform.ThePlatform; DebugStub.Print("Cpu {0} failed. ", __arglist(nextCpu)); DebugStub.Print("GotBack-> Status {0} Count {1:x8}\n", __arglist(bi.MpStatus32, bi.MpCpuCount)); } // // CPU's specifies the limit of the number of processors we will // start since they may have been limited from the command line. // [Conditional("SINGULARITY_MP")] // [NoHeapAllocation] private static void StartApProcessorsInternal(int cpus) { // Kernel thread stack variables Thread nextKernelThread; UIntPtr nextKernelStackBegin; UIntPtr nextKernelStackLimit; // // We fire up all processors unless limited by the // cpus argument. They are started sequentially and // serialize on a spin lock in low memory. // int expectedCpus = devices.GetProcessorCount(); DebugStub.Assert(cpus <= expectedCpus); // // cpus may be less than the real expected CPU's due to // the command line argument passed to Main() // if (cpus < expectedCpus) { expectedCpus = cpus; } // If only 1 cpu, we are done if (cpus == 1) { return; } int nextCpu = 1; int nextCpuRealId; Platform p = Platform.ThePlatform; nextCpuRealId = p.ApicId; nextCpuRealId++; 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(Processor.processorTable[nextCpu]); Platform.ThePlatform.MpBootInfo.PrepareForCpuStart(nextCpu); nextKernelStackBegin = Platform.ThePlatform.MpBootInfo.KernelStackBegin; nextKernelStackLimit = Platform.ThePlatform.MpBootInfo.KernelStackLimit; // // Set it on the processor object so it may be set when the starting // processor initializes // Processor.processorTable[nextCpu].InitializeKernelThreadState( nextKernelThread, nextKernelStackBegin, nextKernelStackLimit); Tracing.Log(Tracing.Audit, "Starting cpu {0}. Stack start {1:x8} limit {3:x8})\n", new UIntPtr(nextCpu), nextKernelStackBegin, nextKernelStackLimit); DebugStub.WriteLine("Attempting to start cpu real id {0}\n", __arglist(nextCpuRealId)); Platform bi = Platform.ThePlatform; halPic.SendSingleStartupIPI((uint)nextCpuRealId, (byte) (bi.MpEnter32 >> 12)); nextCpuRealId++; WaitForMpSyncState(MpSyncState.ApOnline); SetMpSyncState(MpSyncState.BspWaitingForApCalibration); WaitForMpSyncState(MpSyncState.ApCalibrationDone); SetMpSyncState(MpSyncState.BspWaitingApRunning); WaitForMpSyncState(MpSyncState.ApRunning); Processor.RestoreInterrupts(en); en = false; DebugStub.Print("Cpu {0} running ({1} remaining).\n", __arglist(nextCpu, expectedCpus - nextCpu - 1)); } while (++nextCpu < expectedCpus); DebugStub.Print("Done."); Platform np = Platform.ThePlatform; } finally { DebugStub.Print("Reached finally block in StartApProcessorsInternal()"); Processor.RestoreInterrupts(en); } } // [NoHeapAllocation] public override void StartApProcessors(int cpus) { if (GetProcessorCount() > 1) { StartApProcessorsInternal(cpus); } } // // Reset the Ap processors so there is no way they will be woken // up by a debug NMI while in the processor of shutdown/warmboot. // [NoHeapAllocation] public override void ResetApProcessors() { // // Send SIPI to reset them back to real mode with everything // flushed and spinning in the BIOS and not OS or OS boot loader // code. // halPic.BroadcastResetIPI(); } // // This is a NMI freeze for the debugger // [NoHeapAllocation] public override void FreezeProcessors() { halPic.BroadcastFreezeIPI(); } [NoHeapAllocation] public override void SendFixedIPI(byte vector, int from, int to) { halPic.SendFixedIPI(vector, from, to); } [NoHeapAllocation] public override void BroadcastFixedIPI(byte vector, bool includeSelf) { halPic.BroadcastFixedIPI(vector, includeSelf); } [NoHeapAllocation] public override void ClearFixedIPI(int interrupt) { // Native does not need the interrupt parameter halPic.ClearFixedIPI(interrupt); } public override byte TranslatePciInterrupt(byte currentInterruptLine, byte pciInterruptPin, PciPort pciPort) { // // The pin number in the tables is zero based, while the pin number // from the PCI config space is 1-based. So convert to the table space. // pciInterruptPin -= 1; #if DEBUG_PCI_INTERRUPT_TRANSLATION DebugStub.Print("HalApic: Translating interrupt for PCI device 0x{0:x} pin {1} on Bus 0x{2:x}\n", __arglist(pciPort.Device, pciInterruptPin, pciPort.Bus)); #endif // // By default, if we can't find the device, we'll return the current // interrupt line. // byte resultLine = currentInterruptLine; foreach (MpInterruptEntry interruptEntry in MpResources.IoInterruptEntries) { #if DEBUG_PCI_INTERRUPT_TRANSLATION DebugStub.Print("Found interrupt entry bus 0x{0:x} busIrq 0x{1:x} => ApicId 0x{2:x} ApicLine 0x{3:x}\n", __arglist(interruptEntry.BusId, interruptEntry.BusIrq, interruptEntry.ApicId, interruptEntry.ApicLine)); #endif // // According to the MPS spec: for PCI buses, the bus IRQ in the table entry // contains the pin in the low two bits, and the device number in the next // five bits (and the rest are reserved). // if ((interruptEntry.BusId == pciPort.Bus) && ((interruptEntry.BusIrq & 3) == pciInterruptPin) && (((interruptEntry.BusIrq >> 2) & ((1 << 5) - 1)) == pciPort.Device)) { #if DEBUG_PCI_INTERRUPT_TRANSLATION DebugStub.Print("Found device 0x{0:x} connected to IoApic Id 0x{1:x}, line 0x{2:x}\n", __arglist(pciPort.Device, interruptEntry.ApicId, interruptEntry.ApicLine)); #endif // // And then we need to translate it into a global interrupt number. // foreach (Acpi.IoApic ioApic in AcpiTables.GetMadt().GetIoApics()) { #if DEBUG_PCI_INTERRUPT_TRANSLATION DebugStub.Print("Found IoApic 0x{0:x}, interrupt base 0x{1:x}\n", __arglist(ioApic.Id, ioApic.InterruptBase)); #endif if (ioApic.Id == interruptEntry.ApicId) { resultLine = (byte)(ioApic.InterruptBase + interruptEntry.ApicLine); #if DEBUG_PCI_INTERRUPT_TRANSLATION DebugStub.Print("Using global interrupt line 0x{0:x}\n", __arglist(resultLine)); #endif return resultLine; } } break; } } #if DEBUG_PCI_INTERRUPT_TRANSLATION DebugStub.Print("HalApic: Translated PCI device 0x{0:x} pin {1} on bus 0x{2:x} to global interrupt 0x{3:x}\n", __arglist(pciPort.Device, pciInterruptPin, pciPort.Bus, resultLine)); #endif return resultLine; } } }