mirror of https://github.com/acidanthera/audk.git
609 lines
19 KiB
C
609 lines
19 KiB
C
/** @file
|
|
|
|
Copyright (c) 2017 - 2018, Intel Corporation. All rights reserved.<BR>
|
|
This program and the accompanying materials
|
|
are licensed and made available under the terms and conditions of the BSD License
|
|
which accompanies this distribution. The full text of the license may be found at
|
|
http://opensource.org/licenses/bsd-license.php.
|
|
|
|
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
|
|
|
**/
|
|
|
|
#ifndef _DMAR_PROTECTION_H_
|
|
#define _DMAR_PROTECTION_H_
|
|
|
|
#include <Uefi.h>
|
|
#include <PiDxe.h>
|
|
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/UefiBootServicesTableLib.h>
|
|
#include <Library/IoLib.h>
|
|
#include <Library/PciSegmentLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/UefiLib.h>
|
|
#include <Library/CacheMaintenanceLib.h>
|
|
#include <Library/PerformanceLib.h>
|
|
#include <Library/PrintLib.h>
|
|
#include <Library/ReportStatusCodeLib.h>
|
|
|
|
#include <Guid/EventGroup.h>
|
|
#include <Guid/Acpi.h>
|
|
|
|
#include <Protocol/DxeSmmReadyToLock.h>
|
|
#include <Protocol/PciRootBridgeIo.h>
|
|
#include <Protocol/PciIo.h>
|
|
#include <Protocol/PciEnumerationComplete.h>
|
|
#include <Protocol/PlatformVtdPolicy.h>
|
|
#include <Protocol/IoMmu.h>
|
|
|
|
#include <IndustryStandard/Pci.h>
|
|
#include <IndustryStandard/DmaRemappingReportingTable.h>
|
|
#include <IndustryStandard/Vtd.h>
|
|
|
|
#define VTD_64BITS_ADDRESS(Lo, Hi) (LShiftU64 (Lo, 12) | LShiftU64 (Hi, 32))
|
|
|
|
#define ALIGN_VALUE_UP(Value, Alignment) (((Value) + (Alignment) - 1) & (~((Alignment) - 1)))
|
|
#define ALIGN_VALUE_LOW(Value, Alignment) ((Value) & (~((Alignment) - 1)))
|
|
|
|
#define VTD_TPL_LEVEL TPL_NOTIFY
|
|
|
|
//
|
|
// This is the initial max PCI DATA number.
|
|
// The number may be enlarged later.
|
|
//
|
|
#define MAX_VTD_PCI_DATA_NUMBER 0x100
|
|
|
|
typedef struct {
|
|
UINT8 DeviceType;
|
|
VTD_SOURCE_ID PciSourceId;
|
|
EDKII_PLATFORM_VTD_PCI_DEVICE_ID PciDeviceId;
|
|
// for statistic analysis
|
|
UINTN AccessCount;
|
|
} PCI_DEVICE_DATA;
|
|
|
|
typedef struct {
|
|
BOOLEAN IncludeAllFlag;
|
|
UINTN PciDeviceDataNumber;
|
|
UINTN PciDeviceDataMaxNumber;
|
|
PCI_DEVICE_DATA *PciDeviceData;
|
|
} PCI_DEVICE_INFORMATION;
|
|
|
|
typedef struct {
|
|
UINTN VtdUnitBaseAddress;
|
|
UINT16 Segment;
|
|
VTD_CAP_REG CapReg;
|
|
VTD_ECAP_REG ECapReg;
|
|
VTD_ROOT_ENTRY *RootEntryTable;
|
|
VTD_EXT_ROOT_ENTRY *ExtRootEntryTable;
|
|
VTD_SECOND_LEVEL_PAGING_ENTRY *FixedSecondLevelPagingEntry;
|
|
BOOLEAN HasDirtyContext;
|
|
BOOLEAN HasDirtyPages;
|
|
PCI_DEVICE_INFORMATION PciDeviceInfo;
|
|
} VTD_UNIT_INFORMATION;
|
|
|
|
//
|
|
// This is the initial max ACCESS request.
|
|
// The number may be enlarged later.
|
|
//
|
|
#define MAX_VTD_ACCESS_REQUEST 0x100
|
|
|
|
typedef struct {
|
|
UINT16 Segment;
|
|
VTD_SOURCE_ID SourceId;
|
|
UINT64 BaseAddress;
|
|
UINT64 Length;
|
|
UINT64 IoMmuAccess;
|
|
} VTD_ACCESS_REQUEST;
|
|
|
|
|
|
/**
|
|
The scan bus callback function.
|
|
|
|
It is called in PCI bus scan for each PCI device under the bus.
|
|
|
|
@param[in] Context The context of the callback.
|
|
@param[in] Segment The segment of the source.
|
|
@param[in] Bus The bus of the source.
|
|
@param[in] Device The device of the source.
|
|
@param[in] Function The function of the source.
|
|
|
|
@retval EFI_SUCCESS The specific PCI device is processed in the callback.
|
|
**/
|
|
typedef
|
|
EFI_STATUS
|
|
(EFIAPI *SCAN_BUS_FUNC_CALLBACK_FUNC) (
|
|
IN VOID *Context,
|
|
IN UINT16 Segment,
|
|
IN UINT8 Bus,
|
|
IN UINT8 Device,
|
|
IN UINT8 Function
|
|
);
|
|
|
|
extern EFI_ACPI_DMAR_HEADER *mAcpiDmarTable;
|
|
|
|
extern UINTN mVtdUnitNumber;
|
|
extern VTD_UNIT_INFORMATION *mVtdUnitInformation;
|
|
|
|
extern UINT64 mBelow4GMemoryLimit;
|
|
extern UINT64 mAbove4GMemoryLimit;
|
|
|
|
extern EDKII_PLATFORM_VTD_POLICY_PROTOCOL *mPlatformVTdPolicy;
|
|
|
|
/**
|
|
Prepare VTD configuration.
|
|
**/
|
|
VOID
|
|
PrepareVtdConfig (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Setup VTd translation table.
|
|
|
|
@retval EFI_SUCCESS Setup translation table successfully.
|
|
@retval EFI_OUT_OF_RESOURCE Setup translation table fail.
|
|
**/
|
|
EFI_STATUS
|
|
SetupTranslationTable (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Enable DMAR translation.
|
|
|
|
@retval EFI_SUCCESS DMAR translation is enabled.
|
|
@retval EFI_DEVICE_ERROR DMAR translation is not enabled.
|
|
**/
|
|
EFI_STATUS
|
|
EnableDmar (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Disable DMAR translation.
|
|
|
|
@retval EFI_SUCCESS DMAR translation is disabled.
|
|
@retval EFI_DEVICE_ERROR DMAR translation is not disabled.
|
|
**/
|
|
EFI_STATUS
|
|
DisableDmar (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Invalid VTd global IOTLB.
|
|
|
|
@param[in] VtdIndex The index of VTd engine.
|
|
|
|
@retval EFI_SUCCESS VTd global IOTLB is invalidated.
|
|
@retval EFI_DEVICE_ERROR VTd global IOTLB is not invalidated.
|
|
**/
|
|
EFI_STATUS
|
|
InvalidateVtdIOTLBGlobal (
|
|
IN UINTN VtdIndex
|
|
);
|
|
|
|
/**
|
|
Dump VTd registers.
|
|
|
|
@param[in] VtdIndex The index of VTd engine.
|
|
**/
|
|
VOID
|
|
DumpVtdRegs (
|
|
IN UINTN VtdIndex
|
|
);
|
|
|
|
/**
|
|
Dump VTd registers for all VTd engine.
|
|
**/
|
|
VOID
|
|
DumpVtdRegsAll (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Dump VTd capability registers.
|
|
|
|
@param[in] CapReg The capability register.
|
|
**/
|
|
VOID
|
|
DumpVtdCapRegs (
|
|
IN VTD_CAP_REG *CapReg
|
|
);
|
|
|
|
/**
|
|
Dump VTd extended capability registers.
|
|
|
|
@param[in] ECapReg The extended capability register.
|
|
**/
|
|
VOID
|
|
DumpVtdECapRegs (
|
|
IN VTD_ECAP_REG *ECapReg
|
|
);
|
|
|
|
/**
|
|
Register PCI device to VTd engine.
|
|
|
|
@param[in] VtdIndex The index of VTd engine.
|
|
@param[in] Segment The segment of the source.
|
|
@param[in] SourceId The SourceId of the source.
|
|
@param[in] DeviceType The DMAR device scope type.
|
|
@param[in] CheckExist TRUE: ERROR will be returned if the PCI device is already registered.
|
|
FALSE: SUCCESS will be returned if the PCI device is registered.
|
|
|
|
@retval EFI_SUCCESS The PCI device is registered.
|
|
@retval EFI_OUT_OF_RESOURCES No enough resource to register a new PCI device.
|
|
@retval EFI_ALREADY_STARTED The device is already registered.
|
|
**/
|
|
EFI_STATUS
|
|
RegisterPciDevice (
|
|
IN UINTN VtdIndex,
|
|
IN UINT16 Segment,
|
|
IN VTD_SOURCE_ID SourceId,
|
|
IN UINT8 DeviceType,
|
|
IN BOOLEAN CheckExist
|
|
);
|
|
|
|
/**
|
|
The scan bus callback function to always enable page attribute.
|
|
|
|
@param[in] Context The context of the callback.
|
|
@param[in] Segment The segment of the source.
|
|
@param[in] Bus The bus of the source.
|
|
@param[in] Device The device of the source.
|
|
@param[in] Function The function of the source.
|
|
|
|
@retval EFI_SUCCESS The VTd entry is updated to always enable all DMA access for the specific device.
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScanBusCallbackRegisterPciDevice (
|
|
IN VOID *Context,
|
|
IN UINT16 Segment,
|
|
IN UINT8 Bus,
|
|
IN UINT8 Device,
|
|
IN UINT8 Function
|
|
);
|
|
|
|
/**
|
|
Scan PCI bus and invoke callback function for each PCI devices under the bus.
|
|
|
|
@param[in] Context The context of the callback function.
|
|
@param[in] Segment The segment of the source.
|
|
@param[in] Bus The bus of the source.
|
|
@param[in] Callback The callback function in PCI scan.
|
|
|
|
@retval EFI_SUCCESS The PCI devices under the bus are scaned.
|
|
**/
|
|
EFI_STATUS
|
|
ScanPciBus (
|
|
IN VOID *Context,
|
|
IN UINT16 Segment,
|
|
IN UINT8 Bus,
|
|
IN SCAN_BUS_FUNC_CALLBACK_FUNC Callback
|
|
);
|
|
|
|
/**
|
|
Dump the PCI device information managed by this VTd engine.
|
|
|
|
@param[in] VtdIndex The index of VTd engine.
|
|
**/
|
|
VOID
|
|
DumpPciDeviceInfo (
|
|
IN UINTN VtdIndex
|
|
);
|
|
|
|
/**
|
|
Find the VTd index by the Segment and SourceId.
|
|
|
|
@param[in] Segment The segment of the source.
|
|
@param[in] SourceId The SourceId of the source.
|
|
@param[out] ExtContextEntry The ExtContextEntry of the source.
|
|
@param[out] ContextEntry The ContextEntry of the source.
|
|
|
|
@return The index of the VTd engine.
|
|
@retval (UINTN)-1 The VTd engine is not found.
|
|
**/
|
|
UINTN
|
|
FindVtdIndexByPciDevice (
|
|
IN UINT16 Segment,
|
|
IN VTD_SOURCE_ID SourceId,
|
|
OUT VTD_EXT_CONTEXT_ENTRY **ExtContextEntry,
|
|
OUT VTD_CONTEXT_ENTRY **ContextEntry
|
|
);
|
|
|
|
/**
|
|
Get the DMAR ACPI table.
|
|
|
|
@retval EFI_SUCCESS The DMAR ACPI table is got.
|
|
@retval EFI_ALREADY_STARTED The DMAR ACPI table has been got previously.
|
|
@retval EFI_NOT_FOUND The DMAR ACPI table is not found.
|
|
**/
|
|
EFI_STATUS
|
|
GetDmarAcpiTable (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Parse DMAR DRHD table.
|
|
|
|
@return EFI_SUCCESS The DMAR DRHD table is parsed.
|
|
**/
|
|
EFI_STATUS
|
|
ParseDmarAcpiTableDrhd (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Parse DMAR RMRR table.
|
|
|
|
@return EFI_SUCCESS The DMAR RMRR table is parsed.
|
|
**/
|
|
EFI_STATUS
|
|
ParseDmarAcpiTableRmrr (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Dump DMAR context entry table.
|
|
|
|
@param[in] RootEntry DMAR root entry.
|
|
**/
|
|
VOID
|
|
DumpDmarContextEntryTable (
|
|
IN VTD_ROOT_ENTRY *RootEntry
|
|
);
|
|
|
|
/**
|
|
Dump DMAR extended context entry table.
|
|
|
|
@param[in] ExtRootEntry DMAR extended root entry.
|
|
**/
|
|
VOID
|
|
DumpDmarExtContextEntryTable (
|
|
IN VTD_EXT_ROOT_ENTRY *ExtRootEntry
|
|
);
|
|
|
|
/**
|
|
Dump DMAR second level paging entry.
|
|
|
|
@param[in] SecondLevelPagingEntry The second level paging entry.
|
|
**/
|
|
VOID
|
|
DumpSecondLevelPagingEntry (
|
|
IN VOID *SecondLevelPagingEntry
|
|
);
|
|
|
|
/**
|
|
Set VTd attribute for a system memory.
|
|
|
|
@param[in] VtdIndex The index used to identify a VTd engine.
|
|
@param[in] DomainIdentifier The domain ID of the source.
|
|
@param[in] SecondLevelPagingEntry The second level paging entry in VTd table for the device.
|
|
@param[in] BaseAddress The base of device memory address to be used as the DMA memory.
|
|
@param[in] Length The length of device memory address to be used as the DMA memory.
|
|
@param[in] IoMmuAccess The IOMMU access.
|
|
|
|
@retval EFI_SUCCESS The IoMmuAccess is set for the memory range specified by BaseAddress and Length.
|
|
@retval EFI_INVALID_PARAMETER BaseAddress is not IoMmu Page size aligned.
|
|
@retval EFI_INVALID_PARAMETER Length is not IoMmu Page size aligned.
|
|
@retval EFI_INVALID_PARAMETER Length is 0.
|
|
@retval EFI_INVALID_PARAMETER IoMmuAccess specified an illegal combination of access.
|
|
@retval EFI_UNSUPPORTED The bit mask of IoMmuAccess is not supported by the IOMMU.
|
|
@retval EFI_UNSUPPORTED The IOMMU does not support the memory range specified by BaseAddress and Length.
|
|
@retval EFI_OUT_OF_RESOURCES There are not enough resources available to modify the IOMMU access.
|
|
@retval EFI_DEVICE_ERROR The IOMMU device reported an error while attempting the operation.
|
|
**/
|
|
EFI_STATUS
|
|
SetPageAttribute (
|
|
IN UINTN VtdIndex,
|
|
IN UINT16 DomainIdentifier,
|
|
IN VTD_SECOND_LEVEL_PAGING_ENTRY *SecondLevelPagingEntry,
|
|
IN UINT64 BaseAddress,
|
|
IN UINT64 Length,
|
|
IN UINT64 IoMmuAccess
|
|
);
|
|
|
|
/**
|
|
Set VTd attribute for a system memory.
|
|
|
|
@param[in] Segment The Segment used to identify a VTd engine.
|
|
@param[in] SourceId The SourceId used to identify a VTd engine and table entry.
|
|
@param[in] BaseAddress The base of device memory address to be used as the DMA memory.
|
|
@param[in] Length The length of device memory address to be used as the DMA memory.
|
|
@param[in] IoMmuAccess The IOMMU access.
|
|
|
|
@retval EFI_SUCCESS The IoMmuAccess is set for the memory range specified by BaseAddress and Length.
|
|
@retval EFI_INVALID_PARAMETER BaseAddress is not IoMmu Page size aligned.
|
|
@retval EFI_INVALID_PARAMETER Length is not IoMmu Page size aligned.
|
|
@retval EFI_INVALID_PARAMETER Length is 0.
|
|
@retval EFI_INVALID_PARAMETER IoMmuAccess specified an illegal combination of access.
|
|
@retval EFI_UNSUPPORTED The bit mask of IoMmuAccess is not supported by the IOMMU.
|
|
@retval EFI_UNSUPPORTED The IOMMU does not support the memory range specified by BaseAddress and Length.
|
|
@retval EFI_OUT_OF_RESOURCES There are not enough resources available to modify the IOMMU access.
|
|
@retval EFI_DEVICE_ERROR The IOMMU device reported an error while attempting the operation.
|
|
**/
|
|
EFI_STATUS
|
|
SetAccessAttribute (
|
|
IN UINT16 Segment,
|
|
IN VTD_SOURCE_ID SourceId,
|
|
IN UINT64 BaseAddress,
|
|
IN UINT64 Length,
|
|
IN UINT64 IoMmuAccess
|
|
);
|
|
|
|
/**
|
|
Return the index of PCI data.
|
|
|
|
@param[in] VtdIndex The index used to identify a VTd engine.
|
|
@param[in] Segment The Segment used to identify a VTd engine.
|
|
@param[in] SourceId The SourceId used to identify a VTd engine and table entry.
|
|
|
|
@return The index of the PCI data.
|
|
@retval (UINTN)-1 The PCI data is not found.
|
|
**/
|
|
UINTN
|
|
GetPciDataIndex (
|
|
IN UINTN VtdIndex,
|
|
IN UINT16 Segment,
|
|
IN VTD_SOURCE_ID SourceId
|
|
);
|
|
|
|
/**
|
|
Dump VTd registers if there is error.
|
|
**/
|
|
VOID
|
|
DumpVtdIfError (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Initialize platform VTd policy.
|
|
**/
|
|
VOID
|
|
InitializePlatformVTdPolicy (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Always enable the VTd page attribute for the device.
|
|
|
|
@param[in] Segment The Segment used to identify a VTd engine.
|
|
@param[in] SourceId The SourceId used to identify a VTd engine and table entry.
|
|
|
|
@retval EFI_SUCCESS The VTd entry is updated to always enable all DMA access for the specific device.
|
|
**/
|
|
EFI_STATUS
|
|
AlwaysEnablePageAttribute (
|
|
IN UINT16 Segment,
|
|
IN VTD_SOURCE_ID SourceId
|
|
);
|
|
|
|
/**
|
|
Convert the DeviceHandle to SourceId and Segment.
|
|
|
|
@param[in] DeviceHandle The device who initiates the DMA access request.
|
|
@param[out] Segment The Segment used to identify a VTd engine.
|
|
@param[out] SourceId The SourceId used to identify a VTd engine and table entry.
|
|
|
|
@retval EFI_SUCCESS The Segment and SourceId are returned.
|
|
@retval EFI_INVALID_PARAMETER DeviceHandle is an invalid handle.
|
|
@retval EFI_UNSUPPORTED DeviceHandle is unknown by the IOMMU.
|
|
**/
|
|
EFI_STATUS
|
|
DeviceHandleToSourceId (
|
|
IN EFI_HANDLE DeviceHandle,
|
|
OUT UINT16 *Segment,
|
|
OUT VTD_SOURCE_ID *SourceId
|
|
);
|
|
|
|
/**
|
|
Get device information from mapping.
|
|
|
|
@param[in] Mapping The mapping.
|
|
@param[out] DeviceAddress The device address of the mapping.
|
|
@param[out] NumberOfPages The number of pages of the mapping.
|
|
|
|
@retval EFI_SUCCESS The device information is returned.
|
|
@retval EFI_INVALID_PARAMETER The mapping is invalid.
|
|
**/
|
|
EFI_STATUS
|
|
GetDeviceInfoFromMapping (
|
|
IN VOID *Mapping,
|
|
OUT EFI_PHYSICAL_ADDRESS *DeviceAddress,
|
|
OUT UINTN *NumberOfPages
|
|
);
|
|
|
|
/**
|
|
Initialize DMA protection.
|
|
**/
|
|
VOID
|
|
InitializeDmaProtection (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Allocate zero pages.
|
|
|
|
@param[in] Pages the number of pages.
|
|
|
|
@return the page address.
|
|
@retval NULL No resource to allocate pages.
|
|
**/
|
|
VOID *
|
|
EFIAPI
|
|
AllocateZeroPages (
|
|
IN UINTN Pages
|
|
);
|
|
|
|
/**
|
|
Flush VTD page table and context table memory.
|
|
|
|
This action is to make sure the IOMMU engine can get final data in memory.
|
|
|
|
@param[in] VtdIndex The index used to identify a VTd engine.
|
|
@param[in] Base The base address of memory to be flushed.
|
|
@param[in] Size The size of memory in bytes to be flushed.
|
|
**/
|
|
VOID
|
|
FlushPageTableMemory (
|
|
IN UINTN VtdIndex,
|
|
IN UINTN Base,
|
|
IN UINTN Size
|
|
);
|
|
|
|
/**
|
|
Get PCI device information from DMAR DevScopeEntry.
|
|
|
|
@param[in] Segment The segment number.
|
|
@param[in] DmarDevScopeEntry DMAR DevScopeEntry
|
|
@param[out] Bus The bus number.
|
|
@param[out] Device The device number.
|
|
@param[out] Function The function number.
|
|
|
|
@retval EFI_SUCCESS The PCI device information is returned.
|
|
**/
|
|
EFI_STATUS
|
|
GetPciBusDeviceFunction (
|
|
IN UINT16 Segment,
|
|
IN EFI_ACPI_DMAR_DEVICE_SCOPE_STRUCTURE_HEADER *DmarDevScopeEntry,
|
|
OUT UINT8 *Bus,
|
|
OUT UINT8 *Device,
|
|
OUT UINT8 *Function
|
|
);
|
|
|
|
/**
|
|
Append VTd Access Request to global.
|
|
|
|
@param[in] Segment The Segment used to identify a VTd engine.
|
|
@param[in] SourceId The SourceId used to identify a VTd engine and table entry.
|
|
@param[in] BaseAddress The base of device memory address to be used as the DMA memory.
|
|
@param[in] Length The length of device memory address to be used as the DMA memory.
|
|
@param[in] IoMmuAccess The IOMMU access.
|
|
|
|
@retval EFI_SUCCESS The IoMmuAccess is set for the memory range specified by BaseAddress and Length.
|
|
@retval EFI_INVALID_PARAMETER BaseAddress is not IoMmu Page size aligned.
|
|
@retval EFI_INVALID_PARAMETER Length is not IoMmu Page size aligned.
|
|
@retval EFI_INVALID_PARAMETER Length is 0.
|
|
@retval EFI_INVALID_PARAMETER IoMmuAccess specified an illegal combination of access.
|
|
@retval EFI_UNSUPPORTED The bit mask of IoMmuAccess is not supported by the IOMMU.
|
|
@retval EFI_UNSUPPORTED The IOMMU does not support the memory range specified by BaseAddress and Length.
|
|
@retval EFI_OUT_OF_RESOURCES There are not enough resources available to modify the IOMMU access.
|
|
@retval EFI_DEVICE_ERROR The IOMMU device reported an error while attempting the operation.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
RequestAccessAttribute (
|
|
IN UINT16 Segment,
|
|
IN VTD_SOURCE_ID SourceId,
|
|
IN UINT64 BaseAddress,
|
|
IN UINT64 Length,
|
|
IN UINT64 IoMmuAccess
|
|
);
|
|
|
|
#endif
|