singrdk/base/Kernel/Singularity/MpExecution.cs

774 lines
27 KiB
C#

///////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: MpExecution.cs
//
// Note:
// The purpose of this class is to control the execution state of
// processors on MP systems. Specifically, to stop processors running
// while the debugger is active and while a GC is running.
//
// NB These functions may be called with the debugger
// communications lock held. Any calls to DebugStub.Print or
// its ilk will cause processor to spin on lock forever.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Singularity.Hal;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.X86;
namespace Microsoft.Singularity
{
[CLSCompliant(false)]
public class MpExecution
{
// Enumeration of Processor states
const int Uninitialized = 0x00; // Processor state uninitialized
const int Running = 0x01; // Processor running normally
const int TargetFrozen = 0x02; // Processor should stop
const int TargetThaw = 0x04; // Processor should continue
const int FreezeActive = 0x08; // Processor is active during freeze
const int FreezeOwner = 0x10; // Processor initiating freeze
static internal volatile bool FreezeRequested;
static SpinLock freezeLock;
static volatile int freezeCount;
static unsafe ProcessorContext* activeCpuContext;
static unsafe ProcessorContext* ownerCpuContext;
[ NoHeapAllocation ]
static internal unsafe void AddProcessorContext(ProcessorContext* context)
{
// Add processor to list of processors in MP system. Careful
// to avoid adding processor mid-freeze or without lock.
start:
freezeLock.Acquire();
try {
if (FreezeRequested)
goto start;
ProcessorContext* head = Processor.processorTable[0].context;
context->nextProcessorContext = head->nextProcessorContext;
head->nextProcessorContext = context;
context->ipiFreeze = Running;
// From this point on the processor is visible
// in the debugger
DebugStub.AddProcessor(context->cpuId);
}
finally {
freezeLock.Release();
}
}
[ AccessedByRuntime("referenced from halkd.cpp") ]
[ NoHeapAllocation ]
[ Conditional("SINGULARITY_MP") ]
static internal unsafe void FreezeAllProcessors()
{
// This method is only called on MP systems when the
// number of running processors is greater than 1.
freezeLock.Acquire();
try {
freezeCount++;
if (FreezeRequested == true) {
// Processors are already frozen
return;
}
ownerCpuContext = Processor.GetCurrentProcessorContext();
activeCpuContext = ownerCpuContext;
if (activeCpuContext->nextProcessorContext ==
activeCpuContext) {
return;
}
FreezeRequested = true;
activeCpuContext->ipiFreeze = FreezeActive | FreezeOwner;
HalDevices.FreezeProcessors();
// Wait until all running processors have gone into freeze
ProcessorContext* context = activeCpuContext->nextProcessorContext;
do {
if ((context->ipiFreeze & (TargetFrozen|FreezeActive))
!= 0) {
context = context->nextProcessorContext;
}
} while (context->nextProcessorContext != activeCpuContext);
}
finally {
freezeLock.Release();
}
}
[ NoHeapAllocation ]
static internal unsafe void
FreezeProcessor(ref ThreadContext threadContext)
{
ProcessorContext* context = Processor.GetCurrentProcessorContext();
if (context->ipiFreeze == Uninitialized) {
// Processor was still being initialized when IPI issued
while (FreezeRequested == true)
; // just spin until thaw occurs
return;
}
context->ipiFreeze = TargetFrozen;
while (context->ipiFreeze != TargetThaw && FreezeRequested) {
if ((context->ipiFreeze & FreezeActive) == FreezeActive) {
//
// This processor has been made the active processor
//
activeCpuContext = context;
// Pass state over to debugger stub
if (DebugStub.TrapForProcessorSwitch(ref threadContext)) {
// We're returning to Freeze owner, make it active
context->ipiFreeze &= ~FreezeActive;
ownerCpuContext->ipiFreeze |= FreezeActive;
}
}
}
context->ipiFreeze = Running;
}
// Return true maps to ContinueNextProcessor
// Return false maps to ContinueProcessorReselected
[ AccessedByRuntime("referenced from halkd.cpp") ]
[ NoHeapAllocation ]
static internal unsafe bool SwitchFrozenProcessor(int cpuId)
{
ProcessorContext* currentContext = Processor.GetCurrentProcessorContext();
if (currentContext->cpuId == cpuId) {
// No processor to switch to.
return false;
}
ProcessorContext* context = currentContext->nextProcessorContext;
do {
if (context->cpuId == cpuId) {
currentContext->ipiFreeze &= ~FreezeActive;
context->ipiFreeze |= FreezeActive;
goto WaitForWakeupOrReselection;
}
context = context->nextProcessorContext;
} while (context != activeCpuContext);
// cpuId not found so no processor to switch to.
return false;
WaitForWakeupOrReselection:
// If this processor is in the frozen state already, return
// (it'll fall back into FreezeProcessor() loop). NB Initiating
// processor can never be in this state.
if (currentContext->ipiFreeze == TargetFrozen) {
return true;
}
// This processor is the freeze owner, wait to be
// reselected as the active processor.
while ((currentContext->ipiFreeze & FreezeActive) != FreezeActive)
;
return false;
}
[ AccessedByRuntime("referenced from halkd.cpp") ]
[ NoHeapAllocation ]
[ Conditional("SINGULARITY_MP") ]
static internal unsafe void ThawAllProcessors()
{
// This method is only called on MP systems when the
// number of running processors is greater than 1.
freezeLock.Acquire();
try {
if (FreezeRequested == false) {
return;
}
freezeCount--;
if (freezeCount != 0) {
return;
}
FreezeRequested = false;
ProcessorContext* context = activeCpuContext;
do {
context->ipiFreeze = TargetThaw;
context = context->nextProcessorContext;
} while (context != activeCpuContext);
}
finally {
freezeLock.Release();
}
}
// -------------------------------------------------------------------
// haryadi -- circular buffer for ping pong integer storage
private const int MPBUFFERSIZE = 5;
private struct IntrPingPong
{
public SpinLock spinLock;
public int head;
public int tail;
public int [] buffer;
}
public struct ApImage
{
// public UIntPtr virtAddr;
// public UIntPtr phyAddr;
// public UIntPtr length;
public UIntPtr entryPoint;
}
private struct IntrApImage
{
public SpinLock spinLock;
public int head;
public int tail;
public ApImage [] buffer;
}
static private IntrPingPong [] intrPingPong;
static private IntrApImage [] intrApImage;
/////////////////////////////////////////////////////////////
// PingPongInt routines
[ NoHeapAllocation ]
static internal unsafe void StartPingPongInt(int from, int to,
byte vector)
{
intrPingPong[to].spinLock.Acquire();
try {
HalDevices.SendFixedIPI(vector, from, to);
}
finally {
intrPingPong[to].spinLock.Release();
}
}
[ NoHeapAllocation ]
static internal unsafe int GetIntrPingPong(int cpuId)
{
int head = intrPingPong[cpuId].head;
int tail = intrPingPong[cpuId].tail;
int max = MPBUFFERSIZE;
int retval;
// empty
if (head == tail) {
return -1;
}
retval = intrPingPong[cpuId].buffer[head];
intrPingPong[cpuId].head = (head + 1) % max;
// DebugStub.WriteLine("HSG: Get p{0} it[{1}] = {2}",
// __arglist(cpuId, head, retval));
return retval;
}
[ NoHeapAllocation ]
static internal unsafe bool PutIntrPingPong(int cpuId,
int task)
{
int tail = intrPingPong[cpuId].tail;
int head = intrPingPong[cpuId].head;
int max = MPBUFFERSIZE;
// full
if ((tail == max - 1 && head == 0) ||
(tail + 1 == head)) {
return false;
}
intrPingPong[cpuId].buffer[tail] = task;
intrPingPong[cpuId].tail = (tail + 1) % max;
// DebugStub.WriteLine("HSG: Put p{0} it[{1}] = {2}",
// __arglist(cpuId, tail, task));
return true;
}
/////////////////////////////////////////////////////////////
// ApImage routines
[ NoHeapAllocation ]
static internal void SendApImage(int from, int to)
{
byte vector = (byte)EVectors.ApImage;
intrApImage[to].spinLock.Acquire();
try {
HalDevices.SendFixedIPI(vector, from, to);
}
finally {
intrApImage[to].spinLock.Release();
}
}
[ NoHeapAllocation ]
static internal bool GetIntrApImage(int cpuId,
out ApImage apImage)
{
int head = intrApImage[cpuId].head;
int tail = intrApImage[cpuId].tail;
int max = MPBUFFERSIZE;
// empty
if (head == tail) {
apImage.entryPoint = 0;
return false;
}
apImage = intrApImage[cpuId].buffer[head];
intrApImage[cpuId].head = (head + 1) % max;
return true;
}
[ NoHeapAllocation ]
static internal bool PutIntrApImage(int cpuId,
UIntPtr entryPoint)
{
int tail = intrApImage[cpuId].tail;
int head = intrApImage[cpuId].head;
int max = MPBUFFERSIZE;
// full
if ((tail == max - 1 && head == 0) ||
(tail + 1 == head)) {
return false;
}
intrApImage[cpuId].buffer[tail].entryPoint = entryPoint;
intrApImage[cpuId].tail = (tail + 1) % max;
return true;
}
/////////////////////////////////////////////////////////////
// haryadi: AbiCall structures
// *************** DEPRECATED if MpCall is working ***************
// Note: AbiCall buffers should not be treated as queue.
// When BSP receives IPI and the corresponding AbiCall buffer, the
// buffer should not be released (unlike queue). The BSP will call
// the actual ABI and store the return value in the same buffer.
// The AP will obtain the return value from the buffer and then release
// the buffer.
public struct AbiCall
{
// We need to know the 'position', so
// that when the abi is completed we know
// where we must put the value
public int position;
public int retVal;
public int argVal;
public bool validRetVal;
public bool valid;
public bool served;
}
private struct IntrAbiCall
{
public SpinLock spinLock;
public int head;
public int tail;
public AbiCall [] buffer;
}
static private IntrAbiCall [] intrAbiCall;
/*
/////////////////////////////////////////////////////////////
// AbiCall routines
// PutAbiCall: the caller passes the ABI arguments through
// the "AbiCall". The rest of the function just store the
// argVal and initialize the slot
[ NoHeapAllocation ]
static internal int PutAbiCall(int cpuId,
AbiCall abiCall)
{
int pos = -1;
intrAbiCall[cpuId].spinLock.Acquire();
try {
// Loop to find free abi call slot. This is slow. In
// the future we should have a helper queue that
// points to free slots, hence avoid traversing.
for (int i = 0; i < intrAbiCall[cpuId].buffer.Length; i++) {
if (!intrAbiCall[cpuId].buffer[i].valid) {
// initialize fields and copy the abi arguments
intrAbiCall[cpuId].buffer[i].valid = true;
intrAbiCall[cpuId].buffer[i].served = false;
intrAbiCall[cpuId].buffer[i].validRetVal = false;
intrAbiCall[cpuId].buffer[i].retVal = 0;
intrAbiCall[cpuId].buffer[i].argVal = abiCall.argVal;
// the caller need to know on which abi call it
// should wait, so we copy back for the caller
abiCall = intrAbiCall[cpuId].buffer[i];
pos = i;
break;
}
}
}
finally {
intrAbiCall[cpuId].spinLock.Release();
}
return pos;
}
// SendAbiCall: Send notice to the receiver processor that
// there is abi call that needs to be served
[ NoHeapAllocation ]
static internal void SendAbiCall(int from, int to)
{
byte vector = (byte)EVectors.AbiCall;
intrAbiCall[to].spinLock.Acquire();
try {
HalDevices.SendFixedIPI(vector, from, to);
}
finally {
intrAbiCall[to].spinLock.Release();
}
}
// WaitAbiCall: The caller should wait until the retval is
// ready. Currently we just loop. In the future, busy loop is
// not necessary when scheduler comes into play.
[ NoHeapAllocation ]
static internal void WaitAbiCall(int cpuId, int pos,
out AbiCall abiCall)
{
// wait for validRetVal;
while (!intrAbiCall[cpuId].buffer[pos].validRetVal) {
// spin loop until ready
}
// if reach here we have a validRetVal, copy back to caller
intrAbiCall[cpuId].spinLock.Acquire();
try {
abiCall = intrAbiCall[cpuId].buffer[pos];
}
finally {
intrAbiCall[cpuId].spinLock.Release();
}
}
// ReleaseAbiCall: release the abi slot
[ NoHeapAllocation ]
static internal void ReleaseAbiCall(int cpuId, int pos)
{
intrAbiCall[cpuId].spinLock.Acquire();
try {
if (pos < intrAbiCall[cpuId].buffer.Length) {
intrAbiCall[cpuId].buffer[pos].valid = false;
}
}
finally {
intrAbiCall[cpuId].spinLock.Release();
}
}
// GetAbiCall: We should find valid but !served abi calls. The
// caller should loop until all abi calls are served.
[ NoHeapAllocation ]
static internal bool GetAbiCall(int cpuId, out AbiCall abiCall)
{
bool isUnservedAbi = false;
intrAbiCall[cpuId].spinLock.Acquire();
try {
abiCall = intrAbiCall[cpuId].buffer[0]; // init
for (int i = 0; i < intrAbiCall[cpuId].buffer.Length; i++) {
if (intrAbiCall[cpuId].buffer[i].valid &&
!intrAbiCall[cpuId].buffer[i].served) {
intrAbiCall[cpuId].buffer[i].served = true;
abiCall = intrAbiCall[cpuId].buffer[i];
isUnservedAbi = true;
break;
}
}
}
finally {
intrAbiCall[cpuId].spinLock.Release();
}
return isUnservedAbi;
}
// ReturnAbiCall: The abi call has finished, copy the return
// value and set validRetVal to true
[ NoHeapAllocation ]
static internal void ReturnAbiCall(int cpuId, AbiCall abiCall)
{
intrAbiCall[cpuId].spinLock.Acquire();
try {
int pos = abiCall.position;
if (abiCall.position < intrAbiCall[cpuId].buffer.Length) {
intrAbiCall[cpuId].buffer[pos] = abiCall;
intrAbiCall[cpuId].buffer[pos].validRetVal = true;
}
}
finally {
intrAbiCall[cpuId].spinLock.Release();
}
}
*/
/////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////
// haryadi: MpCall structures
// Note: MpCall buffers should not be treated as queue.
// When BSP receives IPI and the corresponding MpCall buffer, the
// buffer should not be released (unlike queue). The BSP will call
// the actual ABI and store the return value in the same buffer.
// The AP will obtain the return value from the buffer and then release
// the buffer.
public class MpCall
{
public MpCall() {}
// We need to know the 'position', so
// that when the abi is completed we know
// where we must put the value
public int abiNum;
public int position;
// public int argVal;
// public int retVal;
public const int MAX_RET_SIZE = 20;
public const int MAX_ARG_SIZE = 100;
public byte [] retBuffer = new byte [MAX_RET_SIZE];
public byte [] argBuffer = new byte [MAX_ARG_SIZE];
public bool validRetVal;
public bool valid;
public bool served;
}
private struct IntrMpCall
{
public SpinLock spinLock;
public int head;
public int tail;
public MpCall [] buffer;
}
static private IntrMpCall [] intrMpCall;
/////////////////////////////////////////////////////////////
// MpCall routines
[ NoHeapAllocation ]
static internal MpCall ReserveMpCall(int cpuId)
{
intrMpCall[cpuId].spinLock.Acquire();
try {
// Loop to find free abi call slot. This is slow. In
// the future we should have a helper queue that
// points to free slots, hence avoid traversing.
for (int i = 0; i < intrMpCall[cpuId].buffer.Length; i++) {
if (!intrMpCall[cpuId].buffer[i].valid) {
intrMpCall[cpuId].buffer[i].valid = true;
intrMpCall[cpuId].buffer[i].served = false;
intrMpCall[cpuId].buffer[i].validRetVal = false;
// the caller need to know on which abi call it
// should wait, so we copy back for the caller
return intrMpCall[cpuId].buffer[i];
}
}
}
finally {
intrMpCall[cpuId].spinLock.Release();
}
return null;
}
// SendMpCall: Send notice to the receiver processor that
// there is abi call that needs to be served
[ NoHeapAllocation ]
static internal void SendMpCall(int from, int to)
{
byte vector = (byte)EVectors.AbiCall;
intrMpCall[to].spinLock.Acquire();
try {
HalDevices.SendFixedIPI(vector, from, to);
}
finally {
intrMpCall[to].spinLock.Release();
}
}
// WaitMpCall: The caller should wait until the retval is
// ready. Currently we just loop. In the future, busy loop is
// not necessary when scheduler comes into play.
[ NoHeapAllocation ]
static internal void WaitMpCall(int cpuId, int pos)
{
// wait for validRetVal;
while (!intrMpCall[cpuId].buffer[pos].validRetVal) {
// spin loop until ready
}
}
// ReleaseMpCall: release the abi slot
[ NoHeapAllocation ]
static internal void ReleaseMpCall(int cpuId, int pos)
{
intrMpCall[cpuId].spinLock.Acquire();
try {
if (pos < intrMpCall[cpuId].buffer.Length) {
intrMpCall[cpuId].buffer[pos].valid = false;
}
}
finally {
intrMpCall[cpuId].spinLock.Release();
}
}
// GetMpCall: We should find valid but !served abi calls. The
// caller should loop until all abi calls are served.
[ NoHeapAllocation ]
static internal MpCall GetMpCall(int cpuId)
{
intrMpCall[cpuId].spinLock.Acquire();
try {
for (int i = 0; i < intrMpCall[cpuId].buffer.Length; i++) {
if (intrMpCall[cpuId].buffer[i].valid &&
!intrMpCall[cpuId].buffer[i].served) {
intrMpCall[cpuId].buffer[i].served = true;
return intrMpCall[cpuId].buffer[i];
}
}
}
finally {
intrMpCall[cpuId].spinLock.Release();
}
return null;
}
// ReturnMpCall: The abi call has finished, copy the return
// value and set validRetVal to true
[ NoHeapAllocation ]
static internal void ReturnMpCall(int cpuId, int pos)
{
intrMpCall[cpuId].spinLock.Acquire();
try {
if (pos < intrMpCall[cpuId].buffer.Length) {
intrMpCall[cpuId].buffer[pos].validRetVal = true;
}
}
finally {
intrMpCall[cpuId].spinLock.Release();
}
}
/////////////////////////////////////////////////////////////
// Initialization function
internal static void Initialize()
{
freezeLock = new SpinLock();
InitializePingPong();
InitializeApImage();
InitializeAbiCall();
InitializeMpCall();
}
// initialize circular buffer for all processors
internal static void InitializePingPong()
{
intrPingPong =
new IntrPingPong [MpBootInfo.MAX_CPU];
for (int i = 0; i < intrPingPong.Length; i++) {
intrPingPong[i].head = 0;
intrPingPong[i].tail = 0;
intrPingPong[i].buffer = new int [MPBUFFERSIZE];
intrPingPong[i].spinLock = new SpinLock();
}
}
// initialize circular buffer for all processors
internal static void InitializeApImage()
{
intrApImage =
new IntrApImage [MpBootInfo.MAX_CPU];
for (int i = 0; i < intrApImage.Length; i++) {
intrApImage[i].head = 0;
intrApImage[i].tail = 0;
intrApImage[i].buffer = new ApImage [MPBUFFERSIZE];
intrApImage[i].spinLock = new SpinLock();
}
}
// initialize circular buffer for abi calls
internal static void InitializeAbiCall()
{
intrAbiCall =
new IntrAbiCall [MpBootInfo.MAX_CPU];
for (int i = 0; i < intrAbiCall.Length; i++) {
intrAbiCall[i].head = 0;
intrAbiCall[i].tail = 0;
intrAbiCall[i].buffer = new AbiCall [MPBUFFERSIZE];
for (int j = 0; j < intrAbiCall[i].buffer.Length; j++) {
intrAbiCall[i].buffer[j].position = j;
intrAbiCall[i].buffer[j].valid = false;
}
intrAbiCall[i].spinLock = new SpinLock();
}
}
// initialize circular buffer for abi calls
internal static void InitializeMpCall()
{
intrMpCall =
new IntrMpCall [MpBootInfo.MAX_CPU];
for (int i = 0; i < intrMpCall.Length; i++) {
intrMpCall[i].head = 0;
intrMpCall[i].tail = 0;
intrMpCall[i].buffer = new MpCall [MPBUFFERSIZE];
for (int j = 0; j < intrMpCall[i].buffer.Length; j++) {
intrMpCall[i].buffer[j] = new MpCall();
intrMpCall[i].buffer[j].position = j;
intrMpCall[i].buffer[j].valid = false;
}
intrMpCall[i].spinLock = new SpinLock();
}
}
// Test ..
internal static void Test()
{
}
}
}