613 lines
22 KiB
C#
613 lines
22 KiB
C#
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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;
|
|
}
|
|
}
|
|
}
|