SELMAll.cpp revision c878e6343c3e9807aeeb3eb35cb064ea8e83560b
/* $Id$ */
/** @file
* SELM All contexts.
*/
/*
* Copyright (C) 2006-2007 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_SELM
#include "SELMInternal.h"
#ifndef IN_RING0
/**
* Converts a GC selector based address to a flat address.
*
* No limit checks are done. Use the SELMToFlat*() or SELMValidate*() functions
* for that.
*
* @returns Flat address.
* @param pVM VM Handle.
* @param Sel Selector part.
* @param Addr Address part.
* @remarks Don't use when in long mode.
*/
{
/** @todo check the limit. */
if (!(Sel & X86_SEL_LDT))
else
{
/** @todo handle LDT pages not present! */
}
}
#endif /* !IN_RING0 */
/**
* Converts a GC selector based address to a flat address.
*
* No limit checks are done. Use the SELMToFlat*() or SELMValidate*() functions
* for that.
*
* @returns Flat address.
* @param pVM VM Handle.
* @param SelReg Selector register
* @param pCtxCore CPU context
* @param Addr Address part.
*/
{
int rc;
/*
* Deal with real & v86 mode first.
*/
{
if (CPUMAreHiddenSelRegsValid(pVM))
else
}
#ifdef IN_RING0
#else
/** @todo when we're in 16 bits mode, we should cut off the address as well.. */
if (!CPUMAreHiddenSelRegsValid(pVM))
#endif
/* 64 bits mode: CS, DS, ES and SS are treated as if each segment base is 0 (Intel� 64 and IA-32 Architectures Software Developer's Manual: 3.4.2.1). */
{
switch (SelReg)
{
case DIS_SELREG_FS:
case DIS_SELREG_GS:
default:
return Addr; /* base 0 */
}
}
/* AMD64 manual: compatibility mode ignores the high 32 bits when calculating an effective address. */
}
/**
* Converts a GC selector based address to a flat address.
*
* Some basic checking is done, but not all kinds yet.
*
* @returns VBox status
* @param pVM VM Handle.
* @param SelReg Selector register
* @param pCtxCore CPU context
* @param Addr Address part.
* @param fFlags SELMTOFLAT_FLAGS_*
* GDT entires are valid.
* @param ppvGC Where to store the GC flat address.
*/
VMMDECL(int) SELMToFlatEx(PVM pVM, DIS_SELREG SelReg, PCCPUMCTXCORE pCtxCore, RTGCPTR Addr, unsigned fFlags, PRTGCPTR ppvGC)
{
/*
* Fetch the selector first.
*/
/*
* Deal with real & v86 mode first.
*/
{
if (ppvGC)
{
if ( pHiddenSel
else
}
return VINF_SUCCESS;
}
/** @todo when we're in 16 bits mode, we should cut off the address as well.. */
#ifndef IN_RC
if ( pHiddenSel
{
bool fCheckLimit = true;
/* 64 bits mode: CS, DS, ES and SS are treated as if each segment base is 0 (Intel� 64 and IA-32 Architectures Software Developer's Manual: 3.4.2.1). */
{
fCheckLimit = false;
switch (SelReg)
{
case DIS_SELREG_FS:
case DIS_SELREG_GS:
break;
default:
break;
}
}
else
{
/* AMD64 manual: compatibility mode ignores the high 32 bits when calculating an effective address. */
}
/*
* Check if present.
*/
if (u1Present)
{
/*
* Type check.
*/
switch (u4Type)
{
/** Read only selector type. */
case X86_SEL_TYPE_RO:
case X86_SEL_TYPE_RO_ACC:
case X86_SEL_TYPE_RW:
case X86_SEL_TYPE_RW_ACC:
case X86_SEL_TYPE_EO:
case X86_SEL_TYPE_EO_ACC:
case X86_SEL_TYPE_ER:
case X86_SEL_TYPE_ER_ACC:
if (!(fFlags & SELMTOFLAT_FLAGS_NO_PL))
{
/** @todo fix this mess */
}
/* check limit. */
return VERR_OUT_OF_SELECTOR_BOUNDS;
/* ok */
if (ppvGC)
return VINF_SUCCESS;
case X86_SEL_TYPE_EO_CONF:
case X86_SEL_TYPE_EO_CONF_ACC:
case X86_SEL_TYPE_ER_CONF:
case X86_SEL_TYPE_ER_CONF_ACC:
if (!(fFlags & SELMTOFLAT_FLAGS_NO_PL))
{
/** @todo fix this mess */
}
/* check limit. */
return VERR_OUT_OF_SELECTOR_BOUNDS;
/* ok */
if (ppvGC)
return VINF_SUCCESS;
case X86_SEL_TYPE_RO_DOWN:
case X86_SEL_TYPE_RO_DOWN_ACC:
case X86_SEL_TYPE_RW_DOWN:
case X86_SEL_TYPE_RW_DOWN_ACC:
if (!(fFlags & SELMTOFLAT_FLAGS_NO_PL))
{
/** @todo fix this mess */
}
/* check limit. */
if (fCheckLimit)
{
return VERR_OUT_OF_SELECTOR_BOUNDS;
return VERR_OUT_OF_SELECTOR_BOUNDS;
}
/* ok */
if (ppvGC)
return VINF_SUCCESS;
default:
return VERR_INVALID_SELECTOR;
}
}
}
# ifndef IN_RING0
else
# endif
#endif /* !IN_RC */
#ifndef IN_RING0
{
if (!(Sel & X86_SEL_LDT))
{
if ( !(fFlags & SELMTOFLAT_FLAGS_HYPER)
return VERR_INVALID_SELECTOR;
}
else
{
return VERR_INVALID_SELECTOR;
/** @todo handle LDT page(s) not present! */
}
/* calc limit. */
/* calc address assuming straight stuff. */
/* Cut the address to 32 bits. */
pvFlat &= 0xffffffff;
/*
* Check if present.
*/
if (u1Present)
{
/*
* Type check.
*/
# define BOTH(a, b) ((a << 16) | b)
{
/** Read only selector type. */
if (!(fFlags & SELMTOFLAT_FLAGS_NO_PL))
{
/** @todo fix this mess */
}
/* check limit. */
return VERR_OUT_OF_SELECTOR_BOUNDS;
/* ok */
if (ppvGC)
return VINF_SUCCESS;
if (!(fFlags & SELMTOFLAT_FLAGS_NO_PL))
{
/** @todo fix this mess */
}
/* check limit. */
return VERR_OUT_OF_SELECTOR_BOUNDS;
/* ok */
if (ppvGC)
return VINF_SUCCESS;
if (!(fFlags & SELMTOFLAT_FLAGS_NO_PL))
{
/** @todo fix this mess */
}
/* check limit. */
return VERR_OUT_OF_SELECTOR_BOUNDS;
return VERR_OUT_OF_SELECTOR_BOUNDS;
/* ok */
if (ppvGC)
return VINF_SUCCESS;
case BOTH(0,X86_SEL_TYPE_SYS_286_TSS_AVAIL):
case BOTH(0,X86_SEL_TYPE_SYS_LDT):
case BOTH(0,X86_SEL_TYPE_SYS_286_TSS_BUSY):
case BOTH(0,X86_SEL_TYPE_SYS_286_CALL_GATE):
case BOTH(0,X86_SEL_TYPE_SYS_TASK_GATE):
case BOTH(0,X86_SEL_TYPE_SYS_286_INT_GATE):
case BOTH(0,X86_SEL_TYPE_SYS_286_TRAP_GATE):
case BOTH(0,X86_SEL_TYPE_SYS_386_TSS_AVAIL):
case BOTH(0,X86_SEL_TYPE_SYS_386_TSS_BUSY):
case BOTH(0,X86_SEL_TYPE_SYS_386_CALL_GATE):
case BOTH(0,X86_SEL_TYPE_SYS_386_INT_GATE):
case BOTH(0,X86_SEL_TYPE_SYS_386_TRAP_GATE):
if (!(fFlags & SELMTOFLAT_FLAGS_NO_PL))
{
/** @todo fix this mess */
}
/* check limit. */
return VERR_OUT_OF_SELECTOR_BOUNDS;
/* ok */
if (ppvGC)
return VINF_SUCCESS;
default:
return VERR_INVALID_SELECTOR;
}
}
}
#endif /* !IN_RING0 */
return VERR_SELECTOR_NOT_PRESENT;
}
#ifndef IN_RING0
/**
* Converts a GC selector based address to a flat address.
*
* Some basic checking is done, but not all kinds yet.
*
* @returns VBox status
* @param pVM VM Handle.
* @param eflags Current eflags
* @param Sel Selector part.
* @param Addr Address part.
* @param pHiddenSel Hidden selector register (can be NULL)
* @param fFlags SELMTOFLAT_FLAGS_*
* GDT entires are valid.
* @param ppvGC Where to store the GC flat address.
* @param pcb Where to store the bytes from *ppvGC which can be accessed according to
* the selector. NULL is allowed.
* @remarks Don't use when in long mode.
*/
VMMDECL(int) SELMToFlatBySelEx(PVM pVM, X86EFLAGS eflags, RTSEL Sel, RTGCPTR Addr, CPUMSELREGHID *pHiddenSel, unsigned fFlags, PRTGCPTR ppvGC, uint32_t *pcb)
{
/*
* Deal with real & v86 mode first.
*/
{
if (ppvGC)
{
if ( pHiddenSel
else
}
if (pcb)
return VINF_SUCCESS;
}
/** @todo when we're in 16 bits mode, we should cut off the address as well.. */
if ( pHiddenSel
{
|| !CPUMIsGuestInLongMode(pVCpu))
{
/* AMD64 manual: compatibility mode ignores the high 32 bits when calculating an effective address. */
pvFlat &= 0xffffffff;
}
}
else
{
if (!(Sel & X86_SEL_LDT))
{
if ( !(fFlags & SELMTOFLAT_FLAGS_HYPER)
return VERR_INVALID_SELECTOR;
}
else
{
return VERR_INVALID_SELECTOR;
/** @todo handle LDT page(s) not present! */
}
/* calc limit. */
/* calc address assuming straight stuff. */
/* Cut the address to 32 bits. */
pvFlat &= 0xffffffff;
}
/*
* Check if present.
*/
if (u1Present)
{
/*
* Type check.
*/
#define BOTH(a, b) ((a << 16) | b)
{
/** Read only selector type. */
if (!(fFlags & SELMTOFLAT_FLAGS_NO_PL))
{
/** @todo fix this mess */
}
/* check limit. */
return VERR_OUT_OF_SELECTOR_BOUNDS;
/* ok */
if (ppvGC)
if (pcb)
return VINF_SUCCESS;
if (!(fFlags & SELMTOFLAT_FLAGS_NO_PL))
{
/** @todo fix this mess */
}
/* check limit. */
return VERR_OUT_OF_SELECTOR_BOUNDS;
/* ok */
if (ppvGC)
if (pcb)
return VINF_SUCCESS;
if (!(fFlags & SELMTOFLAT_FLAGS_NO_PL))
{
/** @todo fix this mess */
}
/* check limit. */
return VERR_OUT_OF_SELECTOR_BOUNDS;
return VERR_OUT_OF_SELECTOR_BOUNDS;
/* ok */
if (ppvGC)
if (pcb)
return VINF_SUCCESS;
case BOTH(0,X86_SEL_TYPE_SYS_286_TSS_AVAIL):
case BOTH(0,X86_SEL_TYPE_SYS_LDT):
case BOTH(0,X86_SEL_TYPE_SYS_286_TSS_BUSY):
case BOTH(0,X86_SEL_TYPE_SYS_286_CALL_GATE):
case BOTH(0,X86_SEL_TYPE_SYS_TASK_GATE):
case BOTH(0,X86_SEL_TYPE_SYS_286_INT_GATE):
case BOTH(0,X86_SEL_TYPE_SYS_286_TRAP_GATE):
case BOTH(0,X86_SEL_TYPE_SYS_386_TSS_AVAIL):
case BOTH(0,X86_SEL_TYPE_SYS_386_TSS_BUSY):
case BOTH(0,X86_SEL_TYPE_SYS_386_CALL_GATE):
case BOTH(0,X86_SEL_TYPE_SYS_386_INT_GATE):
case BOTH(0,X86_SEL_TYPE_SYS_386_TRAP_GATE):
if (!(fFlags & SELMTOFLAT_FLAGS_NO_PL))
{
/** @todo fix this mess */
}
/* check limit. */
return VERR_OUT_OF_SELECTOR_BOUNDS;
/* ok */
if (ppvGC)
if (pcb)
return VINF_SUCCESS;
default:
return VERR_INVALID_SELECTOR;
}
}
return VERR_SELECTOR_NOT_PRESENT;
}
#endif /* !IN_RING0 */
/**
* Validates and converts a GC selector based code address to a flat
* address when in real or v8086 mode.
*
* @returns VINF_SUCCESS.
* @param pVM VM Handle.
* @param SelCS Selector part.
* @param pHidCS The hidden CS register part. Optional.
* @param Addr Address part.
* @param ppvFlat Where to store the flat address.
*/
DECLINLINE(int) selmValidateAndConvertCSAddrRealMode(PVM pVM, RTSEL SelCS, PCPUMSELREGHID pHidCS, RTGCPTR Addr, PRTGCPTR ppvFlat)
{
else
return VINF_SUCCESS;
}
#ifndef IN_RING0
/**
* Validates and converts a GC selector based code address to a flat
*
* @returns VBox status code.
* @param pVM VM Handle.
* @param SelCPL Current privilege level. Get this from SS - CS might be conforming!
* A full selector can be passed, we'll only use the RPL part.
* @param SelCS Selector part.
* @param Addr Address part.
* @param ppvFlat Where to store the flat address.
* @param pcBits Where to store the segment bitness (16/32/64). Optional.
*/
DECLINLINE(int) selmValidateAndConvertCSAddrStd(PVM pVM, RTSEL SelCPL, RTSEL SelCS, RTGCPTR Addr, PRTGCPTR ppvFlat, uint32_t *pcBits)
{
/** @todo validate limit! */
if (!(SelCS & X86_SEL_LDT))
else
{
/** @todo handle LDT page(s) not present! */
}
/*
* Check if present.
*/
{
/*
* Type check.
*/
{
/*
* Check level.
*/
)
{
/*
* Limit check.
*/
{
/* Cut the address to 32 bits. */
*ppvFlat &= 0xffffffff;
if (pcBits)
return VINF_SUCCESS;
}
return VERR_OUT_OF_SELECTOR_BOUNDS;
}
return VERR_INVALID_RPL;
}
return VERR_NOT_CODE_SELECTOR;
}
return VERR_SELECTOR_NOT_PRESENT;
}
#endif /* !IN_RING0 */
/**
* Validates and converts a GC selector based code address to a flat
*
* @returns VBox status code.
* @param pVCpu VMCPU Handle.
* @param SelCPL Current privilege level. Get this from SS - CS might be conforming!
* A full selector can be passed, we'll only use the RPL part.
* @param SelCS Selector part.
* @param Addr Address part.
* @param ppvFlat Where to store the flat address.
*/
DECLINLINE(int) selmValidateAndConvertCSAddrHidden(PVMCPU pVCpu, RTSEL SelCPL, RTSEL SelCS, PCPUMSELREGHID pHidCS, RTGCPTR Addr, PRTGCPTR ppvFlat)
{
/*
* Check if present.
*/
{
/*
* Type check.
*/
{
/*
* Check level.
*/
)
{
/* 64 bits mode: CS, DS, ES and SS are treated as if each segment base is 0 (Intel� 64 and IA-32 Architectures Software Developer's Manual: 3.4.2.1). */
{
return VINF_SUCCESS;
}
/*
* Limit check. Note that the limit in the hidden register is the
* final value. The granularity bit was included in its calculation.
*/
{
return VINF_SUCCESS;
}
return VERR_OUT_OF_SELECTOR_BOUNDS;
}
Log(("Invalid RPL Attr.n.u4Type=%x cpl=%x dpl=%x\n", pHidCS->Attr.n.u4Type, uLevel, pHidCS->Attr.n.u2Dpl));
return VERR_INVALID_RPL;
}
return VERR_NOT_CODE_SELECTOR;
}
return VERR_SELECTOR_NOT_PRESENT;
}
#ifdef IN_RC
/**
* Validates and converts a GC selector based code address to a flat address.
*
* This is like SELMValidateAndConvertCSAddr + SELMIsSelector32Bit but with
* invalid hidden CS data. It's customized for dealing efficiently with CS
* at GC trap time.
*
* @returns VBox status code.
* @param pVM VM Handle.
* @param eflags Current eflags
* @param SelCPL Current privilege level. Get this from SS - CS might be conforming!
* A full selector can be passed, we'll only use the RPL part.
* @param SelCS Selector part.
* @param Addr Address part.
* @param ppvFlat Where to store the flat address.
* @param pcBits Where to store the 64-bit/32-bit/16-bit indicator.
*/
VMMDECL(int) SELMValidateAndConvertCSAddrGCTrap(PVM pVM, X86EFLAGS eflags, RTSEL SelCPL, RTSEL SelCS, RTGCPTR Addr, PRTGCPTR ppvFlat, uint32_t *pcBits)
{
{
*pcBits = 16;
}
}
#endif /* IN_RC */
/**
* Validates and converts a GC selector based code address to a flat address.
*
* @returns VBox status code.
* @param pVM VM Handle.
* @param eflags Current eflags
* @param SelCPL Current privilege level. Get this from SS - CS might be conforming!
* A full selector can be passed, we'll only use the RPL part.
* @param SelCS Selector part.
* @param pHiddenSel The hidden CS selector register.
* @param Addr Address part.
* @param ppvFlat Where to store the flat address.
*/
VMMDECL(int) SELMValidateAndConvertCSAddr(PVM pVM, X86EFLAGS eflags, RTSEL SelCPL, RTSEL SelCS, CPUMSELREGHID *pHiddenCSSel, RTGCPTR Addr, PRTGCPTR ppvFlat)
{
#ifdef IN_RING0
#else
/** @todo when we're in 16 bits mode, we should cut off the address as well? (like in selmValidateAndConvertCSAddrRealMode) */
if (!CPUMAreHiddenSelRegsValid(pVM))
#endif
}
#ifndef IN_RING0
/**
* Return the cpu mode corresponding to the (CS) selector
*
* @returns DISCPUMODE according to the selector type (16, 32 or 64 bits)
* @param pVM VM Handle.
* @param Sel The selector.
*/
{
/** @todo validate limit! */
if (!(Sel & X86_SEL_LDT))
else
{
/** @todo handle LDT page(s) not present! */
}
}
#endif /* !IN_RING0 */
/**
* Return the cpu mode corresponding to the (CS) selector
*
* @returns DISCPUMODE according to the selector type (16, 32 or 64 bits)
* @param pVM VM Handle.
* @param eflags Current eflags register
* @param Sel The selector.
* @param pHiddenSel The hidden selector register.
*/
VMMDECL(DISCPUMODE) SELMGetCpuModeFromSelector(PVM pVM, X86EFLAGS eflags, RTSEL Sel, CPUMSELREGHID *pHiddenSel)
{
#ifdef IN_RING0
#else /* !IN_RING0 */
if (!CPUMAreHiddenSelRegsValid(pVM))
{
/*
* Deal with real & v86 mode first.
*/
return CPUMODE_16BIT;
}
#endif /* !IN_RING0 */
return CPUMODE_64BIT;
/* Else compatibility or 32 bits mode. */
}
/**
* Returns Hypervisor's Trap 08 (\#DF) selector.
*
* @returns Hypervisor's Trap 08 (\#DF) selector.
* @param pVM VM Handle.
*/
{
}
/**
* Sets EIP of Hypervisor's Trap 08 (\#DF) TSS.
*
* @param pVM VM Handle.
* @param u32EIP EIP of Trap 08 handler.
*/
{
}
/**
* Sets ss:esp for ring1 in main Hypervisor's TSS.
*
* @param pVM VM Handle.
* @param ss Ring1 SS register value. Pass 0 if invalid.
* @param esp Ring1 ESP register value.
*/
{
}
#ifndef IN_RING0
/**
* Gets ss:esp for ring1 in main Hypervisor's TSS.
*
* Returns SS=0 if the ring-1 stack isn't valid.
*
* @returns VBox status code.
* @param pVM VM Handle.
* @param pSS Ring1 SS register value.
* @param pEsp Ring1 ESP register value.
*/
{
{
int rc;
# ifdef IN_RC
bool fTriedAlready = false;
rc = MMGCRamRead(pVM, &tss.ss0, (RCPTRTYPE(void *))(GCPtrTss + RT_OFFSETOF(VBOXTSS, ss0)), sizeof(tss.ss0));
rc |= MMGCRamRead(pVM, &tss.esp0, (RCPTRTYPE(void *))(GCPtrTss + RT_OFFSETOF(VBOXTSS, esp0)), sizeof(tss.esp0));
# ifdef DEBUG
rc |= MMGCRamRead(pVM, &tss.offIoBitmap, (RCPTRTYPE(void *))(GCPtrTss + RT_OFFSETOF(VBOXTSS, offIoBitmap)), sizeof(tss.offIoBitmap));
# endif
if (RT_FAILURE(rc))
{
if (!fTriedAlready)
{
/* Shadow page might be out of sync. Sync and try again */
/** @todo might cross page boundary */
fTriedAlready = true;
if (rc != VINF_SUCCESS)
return rc;
goto l_tryagain;
}
return rc;
}
# else /* !IN_RC */
/* Reading too much. Could be cheaper than two seperate calls though. */
if (RT_FAILURE(rc))
{
return rc;
}
# endif /* !IN_RC */
# ifdef LOG_ENABLED
ssr0 &= ~1;
# endif
/* Update our TSS structure for the guest's ring 1 stack */
}
return VINF_SUCCESS;
}
#endif /* !IN_RING0 */
/**
* Returns Guest TSS pointer
*
* @returns Pointer to the guest TSS, RTRCPTR_MAX if not being monitored.
* @param pVM VM Handle.
*/
{
}
#ifndef IN_RING0
/**
* Gets the hypervisor code selector (CS).
* @returns CS selector.
* @param pVM The VM handle.
*/
{
}
/**
* Gets the 64-mode hypervisor code selector (CS64).
* @returns CS selector.
* @param pVM The VM handle.
*/
{
}
/**
* Gets the hypervisor data selector (DS).
* @returns DS selector.
* @param pVM The VM handle.
*/
{
}
/**
* Gets the hypervisor TSS selector.
* @returns TSS selector.
* @param pVM The VM handle.
*/
{
}
/**
* Gets the hypervisor TSS Trap 8 selector.
* @returns TSS Trap 8 selector.
* @param pVM The VM handle.
*/
{
}
/**
* Gets the address for the hypervisor GDT.
*
* @returns The GDT address.
* @param pVM The VM handle.
* @remark This is intended only for very special use, like in the world
* switchers. Don't exploit this API!
*/
{
/*
* Always convert this from the HC pointer since we can be
* called before the first relocation and have to work correctly
* without having dependencies on the relocation order.
*/
}
#endif /* !IN_RING0 */
/**
* Gets info about the current TSS.
*
* @returns VBox status code.
* @retval VINF_SUCCESS if we've got a TSS loaded.
* @retval VERR_SELM_NO_TSS if we haven't got a TSS (rather unlikely).
*
* @param pVM The VM handle.
* @param pVCpu VMCPU Handle.
* @param pGCPtrTss Where to store the TSS address.
* @param pcbTss Where to store the TSS size limit.
* @param pfCanHaveIOBitmap Where to store the can-have-I/O-bitmap indicator. (optional)
*/
VMMDECL(int) SELMGetTSSInfo(PVM pVM, PVMCPU pVCpu, PRTGCUINTPTR pGCPtrTss, PRTGCUINTPTR pcbTss, bool *pfCanHaveIOBitmap)
{
/*
* The TR hidden register is always valid.
*/
if (!(tr & X86_SEL_MASK))
return VERR_SELM_NO_TSS;
if (pfCanHaveIOBitmap)
return VINF_SUCCESS;
}
/**
* Notification callback which is called whenever there is a chance that a CR3
* value might have changed.
* This is called by PGM.
*
* @param pVM The VM handle
* @param pVCpu The VMCPU handle
*/
{
/** @todo SMP support!! */
}