DevPcBios.cpp revision c98fb3e16fcd571a790eab772c0c66173d225205
/* $Id$ */
/** @file
* PC BIOS Device.
*/
/*
* Copyright (C) 2006-2007 innotek GmbH
*
* 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 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.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DEV_PC_BIOS
#include "Builtins.h"
#include "Builtins2.h"
#include "DevPcBios.h"
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* The boot device.
*/
typedef enum DEVPCBIOSBOOT
{
/**
* PC Bios instance data structure.
*/
typedef struct DEVPCBIOS
{
/** Pointer back to the device instance. */
/** Boot devices (ordered). */
/** Ram Size (in bytes). */
/** Bochs shutdown index. */
/** Floppy device. */
char *pszFDDevice;
/** Harddisk device. */
char *pszHDDevice;
/** Bios message buffer. */
char szMsg[256];
/** Bios message buffer index. */
/** Current logo data offset. */
/** Use built-in or loaded logo. */
bool fDefaultLogo;
/** The size of the BIOS logo data. */
/** The BIOS logo data. */
/** The name of the logo file. */
char *pszLogoFile;
/** The LAN boot ROM data. */
/** The name of the LAN boot ROM file. */
char *pszLanBootFile;
/** The DMI tables. */
/** The boot countdown (in seconds). */
} DEVPCBIOS, *PDEVPCBIOS;
/** @todo The logo stuff shared with the BIOS goes into a header of course. */
/**
* PC Bios logo data structure.
*/
typedef struct LOGOHDR
{
/** Signature (LOGO_HDR_MAGIC/0x66BB). */
/** Fade in - boolean. */
/** Fade out - boolean. */
/** Logo time (msec). */
/** Show setup - boolean. */
/** Logo file size. */
#pragma pack()
/** PC port for Logo I/O */
#define LOGO_IO_PORT 0x506
/** The value of the LOGOHDR::u16Signature field. */
#define LOGO_HDR_MAGIC 0x66BB
/** The value which will switch you the default logo. */
#define LOGO_DEFAULT_LOGO 0xFFFF
#pragma pack(1)
/** DMI header */
typedef struct DMIHDR
{
} *PDMIHDR;
/** DMI BIOS information */
typedef struct DMIBIOSINF
{
} *PDMIBIOSINF;
/** DMI system information */
typedef struct DMISYSTEMINF
{
} *PDMISYSTEMINF;
/** MPS floating pointer structure */
typedef struct MPSFLOATPTR
{
} *PMPSFLOATPTR;
/** MPS config table header */
typedef struct MPSCFGTBLHEADER
{
} *PMPSCFGTBLHEADER;
/** MPS processor entry */
typedef struct MPSPROCENTRY
{
} *PMPSPROCENTRY;
/** MPS bus entry */
typedef struct MPSBUSENTRY
{
} *PMPSBUSENTRY;
typedef struct MPSIOAPICENTRY
{
} *PMPSIOAPICENTRY;
/** MPS I/O-Interrupt entry */
typedef struct MPSIOINTERRUPTENTRY
{
} *PMPSIOIRQENTRY;
#pragma pack()
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
static DECLCALLBACK(int) logoIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb);
static DECLCALLBACK(int) logoIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
/**
* Write to CMOS memory.
* This is used by the init complete code.
*/
{
#if 1
#else
#endif
}
/* -=-=-=-=-=-=- based on code from pc.c -=-=-=-=-=-=- */
/**
* Initializes the CMOS data for one harddisk.
*/
static void pcbiosCmosInitHardDisk(PPDMDEVINS pDevIns, int offType, int offInfo, PPDMIBLOCKBIOS pBlockBios)
{
{
if (VBOX_SUCCESS(rc))
{
Log2(("pcbiosCmosInitHardDisk: offInfo=%#x: CHS=%d/%d/%d\n", offInfo, cCylinders, cHeads, cSectors));
pcbiosCmosWrite(pDevIns, offInfo + 0, RT_MIN(cCylinders, 16383) & 0xff); /* 1Bh - (AMI) First Hard Disk (type 47) user defined: # of Cylinders, LSB */
pcbiosCmosWrite(pDevIns, offInfo + 1, RT_MIN(cCylinders, 16383) >> 8); /* 1Ch - (AMI) First Hard Disk user defined: # of Cylinders, High Byte */
pcbiosCmosWrite(pDevIns, offInfo + 2, cHeads); /* 1Dh - (AMI) First Hard Disk user defined: Number of Heads */
pcbiosCmosWrite(pDevIns, offInfo + 3, 0xff); /* 1Eh - (AMI) First Hard Disk user defined: Write Precompensation Cylinder, Low Byte */
pcbiosCmosWrite(pDevIns, offInfo + 4, 0xff); /* 1Fh - (AMI) First Hard Disk user defined: Write Precompensation Cylinder, High Byte */
pcbiosCmosWrite(pDevIns, offInfo + 5, 0xc0 | ((cHeads > 8) << 3)); /* 20h - (AMI) First Hard Disk user defined: Control Byte */
pcbiosCmosWrite(pDevIns, offInfo + 6, 0xff); /* 21h - (AMI) First Hard Disk user defined: Landing Zone, Low Byte */
pcbiosCmosWrite(pDevIns, offInfo + 7, 0xff); /* 22h - (AMI) First Hard Disk user defined: Landing Zone, High Byte */
pcbiosCmosWrite(pDevIns, offInfo + 8, cSectors); /* 23h - (AMI) First Hard Disk user defined: # of Sectors per track */
return;
}
}
}
/**
* Get BIOS boot code from enmBootDevice in order
*
* @todo r=bird: This is a rather silly function since the conversion is 1:1.
*/
{
{
case DEVPCBIOSBOOT_NONE:
return 0;
case DEVPCBIOSBOOT_FLOPPY:
return 1;
case DEVPCBIOSBOOT_HD:
return 2;
case DEVPCBIOSBOOT_DVD:
return 3;
case DEVPCBIOSBOOT_LAN:
return 4;
default:
return 0;
}
}
/**
* Init complete notification.
* This routine will write information needed by the bios to the CMOS.
*
* @returns VBOX status code.
* @param pDevIns The device instance.
* a description of standard and non-standard CMOS registers.
*/
{
unsigned i;
LogFlow(("pcbiosInitComplete:\n"));
/*
* Memory sizes.
*/
/* base memory. */
/* Extended memory, up to 65MB */
/* Bochs BIOS specific? Anyway, it's the amount of memory above 16MB */
{
}
else
u32 = 0;
/*
* Bochs BIOS specifics - boot device.
* We do both new and old (ami-style) settings.
* See rombios.c line ~7215 (int19_function).
*/
/* This is an extension. Bochs BIOS normally supports only 3 boot devices. */
/*
* Floppy drive type.
*/
{
if (VBOX_SUCCESS(rc))
}
u32 = 0;
if (apFDs[0])
{
default: AssertFailed(); break;
}
if (apFDs[1])
{
default: AssertFailed(); break;
}
/*
* Equipment byte.
*/
switch (u32)
{
default:u32 = 0; break; /* floppy not installed. */
}
/*
* Harddisks.
*/
{
if (VBOX_SUCCESS(rc))
}
u32 = (apHDs[0] ? 0xf0 : 0) | (apHDs[1] ? 0x0f : 0); /* 0Fh means extended and points to 1Ah, 1Bh */
if (apHDs[0])
pcbiosCmosInitHardDisk(pDevIns, 0x19, 0x1b, apHDs[0]); /* 19h - First Extended Hard Disk Drive Type */
if (apHDs[1])
pcbiosCmosInitHardDisk(pDevIns, 0x1a, 0x24, apHDs[1]); /* 1Ah - Second Extended Hard Disk Drive Type */
/*
* Translation type - Bochs BIOS specific.
*/
u32 = 0;
for (i = 0; i < 4; i++)
{
if (apHDs[i])
{
{
if (VBOX_FAILURE(rc))
{
}
{
/* Disk <= 512 MByte not needing LBA translation. */
}
else if (cSectors != 63 || (cHeads != 16 && cHeads != 32 && cHeads != 64 && cHeads != 128 && cHeads != 255))
{
/* Disk with strange geometry. Using LBA here can
* break booting of the guest OS. Especially operating
* systems from Microsoft are sensitive to BIOS CHS not
* matching what the partition table says. */
}
else
}
switch (enmTranslation)
{
case PDMBIOSTRANSLATION_AUTO: /* makes gcc happy */
case PDMBIOSTRANSLATION_NONE:
/* u32 |= 0 << (i * 2) */
break;
default:
case PDMBIOSTRANSLATION_LBA:
break;
}
}
}
LogFlow(("pcbiosInitComplete: returns VINF_SUCCESS\n"));
return VINF_SUCCESS;
}
/**
* Port I/O Handler for IN operations.
*
* @returns VBox status code.
*
* @param pDevIns The device instance.
* @param pvUser User argument - ignored.
* @param Port Port number used for the IN operation.
* @param pu32 Where to store the result.
* @param cb Number of bytes read.
*/
static DECLCALLBACK(int) pcbiosIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
{
return VERR_IOM_IOPORT_UNUSED;
}
/**
* Port I/O Handler for OUT operations.
*
* @returns VBox status code.
*
* @param pDevIns The device instance.
* @param pvUser User argument - ignored.
* @param Port Port number used for the IN operation.
* @param u32 The value to output.
* @param cb The value size in bytes.
*/
static DECLCALLBACK(int) pcbiosIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
{
/*
* Bochs BIOS Panic
*/
if ( cb == 2
&& ( Port == 0x400
|| Port == 0x401))
{
return VERR_INTERNAL_ERROR;
}
/*
* Bochs BIOS char printing.
*/
if ( cb == 1
&& ( Port == 0x402
|| Port == 0x403))
{
/* The raw version. */
switch (u32)
{
}
/* The readable, buffered version. */
{
}
else
{
{
}
}
return VINF_SUCCESS;
}
/*
* Bochs BIOS shutdown request.
*/
{
static const unsigned char szShutdown[] = "Shutdown";
{
{
LogRel(("8900h shutdown request.\n"));
return PDMDevHlpVMPowerOff(pDevIns);
}
}
else
return VINF_SUCCESS;
}
/* not in use. */
return VINF_SUCCESS;
}
/**
* LOGO port I/O Handler for IN operations.
*
* @returns VBox status code.
*
* @param pDevIns The device instance.
* @param pvUser User argument - ignored.
* @param uPort Port number used for the IN operation.
* @param pu32 Where to store the result.
* @param cb Number of bytes read.
*/
static DECLCALLBACK(int) logoIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
{
PRTUINT64U p;
if (pData->fDefaultLogo)
{
/*
* Default bios logo.
*/
{
Log(("logoIOPortRead: Requested address is out of Logo data!!! offLogoData=%#x(%d) cbLogo=%#x(%d)\n",
return VINF_SUCCESS;
}
}
else
{
/*
* Custom logo.
*/
{
Log(("logoIOPortRead: Requested address is out of Logo data!!! offLogoData=%#x(%d) cbLogo=%#x(%d)\n",
return VINF_SUCCESS;
}
}
switch (cb)
{
//case 8: *pu32 = p->au64[0]; break;
default: AssertFailed(); break;
}
Log(("logoIOPortRead: LogoOffset=%#x(%d) cb=%#x %.*Vhxs\n", pData->offLogoData, pData->offLogoData, cb, cb, pu32));
return VINF_SUCCESS;
}
/**
* LOGO port I/O Handler for OUT operations.
*
* @returns VBox status code.
*
* @param pDevIns The device instance.
* @param pvUser User argument - ignored.
* @param uPort Port number used for the IN operation.
* @param u32 The value to output.
* @param cb The value size in bytes.
*/
static DECLCALLBACK(int) logoIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
{
/* Switch to default BIOS logo or change logo data offset. */
if ( cb == 2
&& u32 == LOGO_DEFAULT_LOGO)
{
pData->fDefaultLogo = true;
pData->offLogoData = 0;
}
else
return VINF_SUCCESS;
}
/**
* Construct the DMI table.
*
* @param table pointer to DMI table.
*/
{
int iStrNr;
iStrNr = 1;
/* any more?? */
;
/* any more?? */
;
/* any more?? */
;
*pszStr++ = '\0';
iStrNr = 1;
pSystemInf->u8SKUNumber = 0;
*pszStr++ = '\0';
("VBOX_DMI_TABLE_SIZE=%d, actual DMI table size is %d",
}
/**
* Calculate a simple checksum for the MPS table.
*
* @param data data
* @param len size of data
*/
{
return -u8Sum;
}
/**
* Construct the MPS table. Only applicable if IOAPIC is active.
*
* @param pDevIns The device instance data.
* @param addr physical address in guest memory.
*/
{
/* configuration table */
pCfgTab->u32OemTablePtr = 0;
pCfgTab->u16OemTableSize = 0;
+ 1 /* ISA Bus */
+ 16 /* Interrupts */;
pCfgTab->u16ExtTableLength = 0;
pCfgTab->u8ExtTableChecksxum = 0;
pCfgTab->u8Reserved = 0;
if (u32Eax >= 1)
{
/* Local APIC will be enabled later so override it here. Since we provide
* an MP table we have an IOAPIC and therefore a Local APIC. */
}
/* one processor so far */
pProcEntry->u8LocalApicId = 0;
pProcEntry->u32Reserved[0] =
/* ISA bus */
/* PCI bus? */
* MP spec: "The configuration table contains one or more entries for I/O APICs.
* ... At least one I/O APIC must be enabled." */
for (int i = 0; i < 16; i++, pIrqEntry++)
{
trigger mode = conforms to bus */
pIrqEntry->u8SrcBusIrq = i;
pIrqEntry->u8DstIOAPICInt = i;
}
("VBOX_MPS_TABLE_SIZE=%d, maximum allowed size is %d",
floatPtr.u8Checksum = 0;
floatPtr.au8Feature[0] = 0;
}
/**
* Reset notification.
*
* @returns VBox status.
* @param pDevIns The device instance data.
*/
{
LogFlow(("pcbiosReset:\n"));
pData->fDefaultLogo = false;
pData->offLogoData = 0;
/** @todo Should we perhaps do pcbiosInitComplete() on reset? */
#if 1
/*
* Paranoia: Check that the BIOS ROM hasn't changed.
*/
/* the low ROM mapping. */
{
}
/* the high ROM mapping. */
pb2 = &g_abPcBiosBinary[0];
{
}
#endif
}
/**
* Destruct a device instance.
*
* Most VM resources are freed by the VM. This callback is provided so that any non-VM
* resources can be freed correctly.
*
* @param pDevIns The device instance data.
*/
{
LogFlow(("pcbiosDestruct:\n"));
/*
* Free MM heap pointers.
*/
if (pData->pu8LanBoot)
{
}
if (pData->pszLanBootFile)
{
}
{
}
if (pData->pszLogoFile)
{
}
return VINF_SUCCESS;
}
/**
* Convert config value to DEVPCBIOSBOOT.
*
* @returns VBox status code.
* @param pCfgHandle Configuration handle.
* @param pszParam The name of the value to read.
* @param penmBoot Where to store the boot method.
*/
static int pcbiosBootFromCfg(PPDMDEVINS pDevIns, PCFGMNODE pCfgHandle, const char *pszParam, DEVPCBIOSBOOT *penmBoot)
{
char *psz;
if (VBOX_FAILURE(rc))
N_("Configuration error: Querying \"%s\" as a string failed"),
pszParam);
else
{
N_("Configuration error: The \"%s\" value \"%s\" is unknown.\n"),
}
return rc;
}
/**
* Construct a device instance for a VM.
*
* @returns VBox status.
* @param pDevIns The device instance data.
* If the registration structure is needed, pDevIns->pDevReg points to it.
* @param iInstance Instance number. Use this to figure out which registers and such to use.
* The device number is also found in pDevIns->iInstance, but since it's
* likely to be freqently used PDM passes it as parameter.
* @param pCfgHandle Configuration node handle for the device. Use this to obtain the configuration
* of the device instance. It's also found in pDevIns->pCfgHandle, but like
* iInstance it's expected to be used a bit in this function.
*/
{
unsigned i;
int rc;
int cb;
/*
* Validate configuration.
*/
"BootDevice0\0"
"BootDevice1\0"
"BootDevice2\0"
"BootDevice3\0"
"RamSize\0"
"HardDiskDevice\0"
"FloppyDevice\0"
"FadeIn\0"
"FadeOut\0"
"LogoTime\0"
"LogoFile\0"
"ShowBootMenu\0"
"DelayBoot\0"
"LanBootRom\0"
"IOAPIC\0"))
N_("Invalid configuraton for device pcbios device"));
/*
* Init the data.
*/
if (VBOX_FAILURE(rc))
N_("Configuration error: Querying \"RamSize\" as integer failed"));
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
else if (VBOX_FAILURE (rc))
N_("Configuration error: Failed to read \"IOAPIC\"."));
static const char * const s_apszBootDevices[] = { "BootDevice0", "BootDevice1", "BootDevice2", "BootDevice3" };
{
if (VBOX_FAILURE(rc))
return rc;
}
if (VBOX_FAILURE(rc))
N_("Configuration error: Querying \"HardDiskDevice\" as a string failed"));
if (VBOX_FAILURE(rc))
N_("Configuration error: Querying \"FloppyDevice\" as a string failed"));
/*
* Register I/O Ports and PC BIOS.
*/
if (VBOX_FAILURE(rc))
return rc;
if (VBOX_FAILURE(rc))
return rc;
if (VBOX_FAILURE(rc))
return rc;
/*
* Map the BIOS into memory.
* There are two mappings:
* 1. 0x000e0000 to 0x000fffff contains the last 128 kb of the bios.
* The bios code might be 64 kb in size, and will then start at 0xf0000.
* 2. 0xfffxxxxx to 0xffffffff contains the entire bios.
*/
("g_cbPcBiosBinary=%#x\n", g_cbPcBiosBinary));
if (VBOX_FAILURE(rc))
return rc;
&g_abPcBiosBinary[0], "PC BIOS - 0xffffffff");
if (VBOX_FAILURE(rc))
return rc;
/*
* Register the BIOS Logo port
*/
rc = PDMDevHlpIOPortRegister(pDevIns, LOGO_IO_PORT, 1, NULL, logoIOPortWrite, logoIOPortRead, NULL, NULL, "PC BIOS - Logo port");
if (VBOX_FAILURE(rc))
return rc;
/*
* Construct the logo header.
*/
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
else if (VBOX_FAILURE(rc))
N_("Configuration error: Querying \"FadeIn\" as integer failed"));
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
else if (VBOX_FAILURE(rc))
N_("Configuration error: Querying \"FadeOut\" as integer failed"));
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
else if (VBOX_FAILURE(rc))
N_("Configuration error: Querying \"LogoTime\" as integer failed"));
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
LogoHdr.u8ShowBootMenu = 0;
else if (VBOX_FAILURE(rc))
N_("Configuration error: Querying \"ShowBootMenu\" as integer failed"));
/*
* Get the Logo file name.
*/
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
else if (VBOX_FAILURE(rc))
N_("Configuration error: Querying \"LogoFile\" as a string failed"));
else if (!*pData->pszLogoFile)
{
}
/*
* Determine the logo size, open any specified logo file in the process.
*/
if (pData->pszLogoFile)
{
if (VBOX_SUCCESS(rc))
{
if (VBOX_SUCCESS(rc))
{
if ( cbFile > 0
&& cbFile < LOGO_MAX_SIZE)
else
}
}
if (VBOX_FAILURE(rc))
{
/*
* Ignore failure and fall back to the default logo.
*/
}
}
/*
* Allocate buffer for the logo data.
* RT_MAX() is applied to let us fall back to default logo on read failure.
*/
pData->pu8Logo = (uint8_t *)PDMDevHlpMMHeapAlloc(pDevIns, RT_MAX(pData->cbLogo, g_cbPcDefBiosLogo + sizeof(LogoHdr)));
{
/*
* Write the logo header.
*/
/*
* Write the logo bitmap.
*/
if (pData->pszLogoFile)
{
if (VBOX_FAILURE(rc))
{
}
}
else
/*
* Call reset to set values and stuff.
*/
rc = VINF_SUCCESS;
}
else
rc = VERR_NO_MEMORY;
/* cleanup */
if (FileLogo != NIL_RTFILE)
/*
* Get the LAN boot ROM file name.
*/
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
{
rc = VINF_SUCCESS;
}
else if (VBOX_FAILURE(rc))
N_("Configuration error: Querying \"LanBootRom\" as a string failed"));
else if (!*pData->pszLanBootFile)
{
}
#ifdef VBOX_DO_NOT_LINK_LANBOOT
/*
* Determine the LAN boot ROM size, open specified ROM file in the process.
*/
if (pData->pszLanBootFile)
{
if (VBOX_SUCCESS(rc))
{
if (VBOX_SUCCESS(rc))
{
|| cbFileLanBoot > _32K)
}
}
if (VBOX_FAILURE(rc))
{
/*
* Ignore failure and fall back to no LAN boot ROM.
*/
Log(("pcbiosConstruct: Failed to open LAN boot ROM file '%s', rc=%Vrc!\n", pData->pszLanBootFile, rc));
}
}
/*
* Get the LAN boot ROM data.
*/
if (pData->pszLanBootFile)
{
/*
* Allocate buffer for the LAN boot ROM data.
*/
if (pu8LanBoot)
{
if (VBOX_FAILURE(rc))
{
pu8LanBoot = NULL;
}
rc = VINF_SUCCESS;
}
else
rc = VERR_NO_MEMORY;
}
else
/* cleanup */
if (FileLanBoot != NIL_RTFILE)
#else /* !VBOX_DO_NOT_LINK_LANBOOT */
#endif /* !VBOX_DO_NOT_LINK_LANBOOT */
/*
* Map the Network Boot ROM into memory.
* Currently there is a fixed mapping: 0x000c8000 to 0x000cffff contains
* the (up to) 32 kb ROM image.
*/
if (pu8LanBoot)
rc = PDMDevHlpROMRegister(pDevIns, VBOX_LANBOOT_SEG << 4, cbFileLanBoot, pu8LanBoot, "Net Boot ROM");
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
{
pData->uBootDelay = 0;
rc = VINF_SUCCESS;
}
else
{
if (VBOX_FAILURE(rc))
N_("Configuration error: Querying \"DelayBoot\" as integer failed"));
}
return rc;
}
/**
* The device registration structure.
*/
const PDMDEVREG g_DevicePcBios =
{
/* u32Version */
/* szDeviceName */
"pcbios",
/* szGCMod */
"",
/* szR0Mod */
"",
/* pszDescription */
"PC BIOS Device",
/* fFlags */
/* fClass */
/* cMaxInstances */
1,
/* cbInstance */
sizeof(DEVPCBIOS),
/* pfnConstruct */
/* pfnDestruct */
/* pfnRelocate */
NULL,
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
NULL,
/* pfnReset */
/* pfnSuspend */
NULL,
/* pfnResume */
NULL,
/* pfnAttach */
NULL,
/* pfnDetach */
NULL,
/* pfnQueryInterface. */
NULL,
/* pfnInitComplete. */
};