mirror of
https://github.com/reactos/reactos.git
synced 2025-08-05 06:52:56 +00:00
implemented NdisMAllocateMapRegisters, NdisMFreeMapRegisters, NdisMQueryMapRegisterCount, and NdisMRegisterIoPortRange
svn path=/trunk/; revision=5979
This commit is contained in:
parent
f82864342a
commit
dc354a8e32
1 changed files with 232 additions and 36 deletions
|
@ -4,13 +4,14 @@
|
||||||
* FILE: ndis/io.c
|
* FILE: ndis/io.c
|
||||||
* PURPOSE: I/O related routines
|
* PURPOSE: I/O related routines
|
||||||
* PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
|
* PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
|
||||||
|
* Vizzini (vizzini@plasmic.com)
|
||||||
* REVISIONS:
|
* REVISIONS:
|
||||||
* CSH 01/08-2000 Created
|
* CSH 01/08-2000 Created
|
||||||
|
* 8-20-2003 Vizzini - DMA support
|
||||||
*/
|
*/
|
||||||
#include <ndissys.h>
|
#include <ndissys.h>
|
||||||
#include <miniport.h>
|
#include <miniport.h>
|
||||||
|
|
||||||
|
|
||||||
VOID STDCALL HandleDeferredProcessing(
|
VOID STDCALL HandleDeferredProcessing(
|
||||||
IN PKDPC Dpc,
|
IN PKDPC Dpc,
|
||||||
IN PVOID DeferredContext,
|
IN PVOID DeferredContext,
|
||||||
|
@ -138,6 +139,7 @@ NdisImmediateReadPortUchar(
|
||||||
IN ULONG Port,
|
IN ULONG Port,
|
||||||
OUT PUCHAR Data)
|
OUT PUCHAR Data)
|
||||||
{
|
{
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
||||||
*Data = READ_PORT_UCHAR((PUCHAR)Port); // FIXME: What to do with WrapperConfigurationContext?
|
*Data = READ_PORT_UCHAR((PUCHAR)Port); // FIXME: What to do with WrapperConfigurationContext?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,6 +154,7 @@ NdisImmediateReadPortUlong(
|
||||||
IN ULONG Port,
|
IN ULONG Port,
|
||||||
OUT PULONG Data)
|
OUT PULONG Data)
|
||||||
{
|
{
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
||||||
*Data = READ_PORT_ULONG((PULONG)Port); // FIXME: What to do with WrapperConfigurationContext?
|
*Data = READ_PORT_ULONG((PULONG)Port); // FIXME: What to do with WrapperConfigurationContext?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,6 +169,7 @@ NdisImmediateReadPortUshort(
|
||||||
IN ULONG Port,
|
IN ULONG Port,
|
||||||
OUT PUSHORT Data)
|
OUT PUSHORT Data)
|
||||||
{
|
{
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
||||||
*Data = READ_PORT_USHORT((PUSHORT)Port); // FIXME: What to do with WrapperConfigurationContext?
|
*Data = READ_PORT_USHORT((PUSHORT)Port); // FIXME: What to do with WrapperConfigurationContext?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,6 +184,7 @@ NdisImmediateWritePortUchar(
|
||||||
IN ULONG Port,
|
IN ULONG Port,
|
||||||
IN UCHAR Data)
|
IN UCHAR Data)
|
||||||
{
|
{
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
||||||
WRITE_PORT_UCHAR((PUCHAR)Port, Data); // FIXME: What to do with WrapperConfigurationContext?
|
WRITE_PORT_UCHAR((PUCHAR)Port, Data); // FIXME: What to do with WrapperConfigurationContext?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,6 +199,7 @@ NdisImmediateWritePortUlong(
|
||||||
IN ULONG Port,
|
IN ULONG Port,
|
||||||
IN ULONG Data)
|
IN ULONG Data)
|
||||||
{
|
{
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
||||||
WRITE_PORT_ULONG((PULONG)Port, Data); // FIXME: What to do with WrapperConfigurationContext?
|
WRITE_PORT_ULONG((PULONG)Port, Data); // FIXME: What to do with WrapperConfigurationContext?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,25 +214,173 @@ NdisImmediateWritePortUshort(
|
||||||
IN ULONG Port,
|
IN ULONG Port,
|
||||||
IN USHORT Data)
|
IN USHORT Data)
|
||||||
{
|
{
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
||||||
WRITE_PORT_USHORT((PUSHORT)Port, Data); // FIXME: What to do with WrapperConfigurationContext?
|
WRITE_PORT_USHORT((PUSHORT)Port, Data); // FIXME: What to do with WrapperConfigurationContext?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IO_ALLOCATION_ACTION NdisMapRegisterCallback (
|
||||||
|
IN PDEVICE_OBJECT DeviceObject,
|
||||||
|
IN PIRP Irp,
|
||||||
|
IN PVOID MapRegisterBase,
|
||||||
|
IN PVOID Context)
|
||||||
/*
|
/*
|
||||||
* @unimplemented
|
* FUNCTION: Called back during reservation of map registers
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
PLOGICAL_ADAPTER Adapter = (PLOGICAL_ADAPTER)Context;
|
||||||
|
PADAPTER_MAP_REGISTER_LIST Register = ExAllocatePool(NonPagedPool, sizeof(ADAPTER_MAP_REGISTER_LIST));
|
||||||
|
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
||||||
|
|
||||||
|
if(!Register)
|
||||||
|
{
|
||||||
|
NDIS_DbgPrint(MIN_TRACE, ("Insufficient resources\n"));
|
||||||
|
KeSetEvent(&Adapter->DmaEvent, 0, FALSE);
|
||||||
|
return DeallocateObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
Register->MapRegister = MapRegisterBase;
|
||||||
|
Register->NumRegisters = Adapter->MapRegistersRequested;
|
||||||
|
|
||||||
|
ExInterlockedInsertTailList(&Adapter->MapRegisterList.ListEntry, &Register->ListEntry, &Adapter->DmaLock);
|
||||||
|
|
||||||
|
KeSetEvent(&Adapter->DmaEvent, 0, FALSE);
|
||||||
|
|
||||||
|
/* XXX this is only the thing to do for busmaster NICs */
|
||||||
|
return DeallocateObjectKeepRegisters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @implemented
|
||||||
*/
|
*/
|
||||||
NDIS_STATUS
|
NDIS_STATUS
|
||||||
EXPORT
|
EXPORT
|
||||||
NdisMAllocateMapRegisters(
|
NdisMAllocateMapRegisters(
|
||||||
IN NDIS_HANDLE MiniportAdapterHandle,
|
IN NDIS_HANDLE MiniportAdapterHandle,
|
||||||
IN UINT DmaChannel,
|
IN UINT DmaChannel,
|
||||||
IN BOOLEAN Dma32BitAddresses,
|
IN BOOLEAN DmaSize,
|
||||||
IN ULONG PhysicalMapRegistersNeeded,
|
IN ULONG BaseMapRegistersNeeded,
|
||||||
IN ULONG MaximumPhysicalMapping)
|
IN ULONG MaximumBufferSize)
|
||||||
|
/*
|
||||||
|
* FUNCTION: Allocate map registers for use in DMA transfers
|
||||||
|
* ARGUMENTS:
|
||||||
|
* MiniportAdapterHandle: Passed in to MiniportInitialize
|
||||||
|
* DmaChannel: DMA channel to use
|
||||||
|
* DmaSize: bit width of DMA transfers
|
||||||
|
* BaseMapRegistersNeeded: number of map registers requested
|
||||||
|
* MaximumBufferSize: largest single buffer transferred
|
||||||
|
* RETURNS:
|
||||||
|
* NDIS_STATUS_SUCCESS on success
|
||||||
|
* NDIS_STATUS_RESOURCES on failure
|
||||||
|
* NOTES:
|
||||||
|
* - the win2k ddk and the nt4 ddk have conflicting prototypes for this.
|
||||||
|
* I'm implementing the 2k one.
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED
|
DEVICE_DESCRIPTION Description;
|
||||||
|
PADAPTER_OBJECT AdapterObject = 0;
|
||||||
|
UINT MapRegistersRequired = 0;
|
||||||
|
UINT MapRegistersPerBaseRegister = 0;
|
||||||
|
ULONG AvailableMapRegisters;
|
||||||
|
NTSTATUS NtStatus;
|
||||||
|
PLOGICAL_ADAPTER Adapter = 0;
|
||||||
|
PDEVICE_OBJECT DeviceObject = 0;
|
||||||
|
KIRQL OldIrql;
|
||||||
|
|
||||||
return NDIS_STATUS_FAILURE;
|
NDIS_DbgPrint(MAX_TRACE, ("called: Handle 0x%x, DmaChannel 0x%x, DmaSize 0x%x, BaseMapRegsNeeded: 0x%x, MaxBuffer: 0x%x.\n",
|
||||||
|
MiniportAdapterHandle, DmaChannel, DmaSize, BaseMapRegistersNeeded, MaximumBufferSize));
|
||||||
|
|
||||||
|
memset(&Description,0,sizeof(Description));
|
||||||
|
|
||||||
|
Adapter = (PLOGICAL_ADAPTER)MiniportAdapterHandle;
|
||||||
|
DeviceObject = Adapter->NdisMiniportBlock.DeviceObject;
|
||||||
|
|
||||||
|
InitializeListHead(&Adapter->MapRegisterList.ListEntry);
|
||||||
|
KeInitializeEvent(&Adapter->DmaEvent, NotificationEvent, FALSE);
|
||||||
|
KeInitializeSpinLock(&Adapter->DmaLock);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* map registers correlate to physical pages. ndis documents a
|
||||||
|
* maximum of 64 map registers that it will return.
|
||||||
|
* at 4k pages, a 1514-byte buffer can span not more than 2 pages.
|
||||||
|
*
|
||||||
|
* the number of registers required for a given physical mapping
|
||||||
|
* is (first register + last register + one per page size),
|
||||||
|
* given that physical mapping is > 2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* unhandled corner case: 1-byte max buffer size */
|
||||||
|
MapRegistersPerBaseRegister = 2 + MaximumBufferSize / PAGE_SIZE;
|
||||||
|
MapRegistersRequired = BaseMapRegistersNeeded * MapRegistersPerBaseRegister;
|
||||||
|
|
||||||
|
if(MapRegistersRequired > 64)
|
||||||
|
{
|
||||||
|
NDIS_DbgPrint(MID_TRACE, ("Request for too many map registers: %d\n", MapRegistersRequired));
|
||||||
|
return NDIS_STATUS_RESOURCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
Description.Version = DEVICE_DESCRIPTION_VERSION;
|
||||||
|
Description.Master = TRUE; /* implied by calling this function */
|
||||||
|
Description.ScatterGather = FALSE; /* implied by calling this function */
|
||||||
|
Description.DemandMode = 0; /* unused due to bus master */
|
||||||
|
Description.AutoInitialize = 0; /* unused due to bus master */
|
||||||
|
Description.Dma32BitAddresses = DmaSize;
|
||||||
|
Description.IgnoreCount = 0; /* unused due to bus master */
|
||||||
|
Description.Reserved1 = 0;
|
||||||
|
Description.Reserved2 = 0;
|
||||||
|
Description.BusNumber = Adapter->BusNumber;
|
||||||
|
Description.DmaChannel = 0; /* unused due to bus master */
|
||||||
|
Description.InterfaceType = Adapter->BusType;
|
||||||
|
Description.DmaChannel = 0; /* unused due to bus master */
|
||||||
|
Description.DmaWidth = 0; /* unused (i think) due to bus master */
|
||||||
|
Description.DmaSpeed = 0; /* unused (i think) due to bus master */
|
||||||
|
Description.MaximumLength = 0; /* unused (i think) due to bus master */
|
||||||
|
Description.DmaPort = 0; /* unused due to bus type */
|
||||||
|
|
||||||
|
AvailableMapRegisters = MapRegistersRequired;
|
||||||
|
AdapterObject = HalGetAdapter(&Description, &AvailableMapRegisters);
|
||||||
|
|
||||||
|
if(!AdapterObject)
|
||||||
|
{
|
||||||
|
NDIS_DbgPrint(MIN_TRACE, ("Unable to allocate an adapter object; bailing out\n"));
|
||||||
|
return NDIS_STATUS_RESOURCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
Adapter->AdapterObject = AdapterObject;
|
||||||
|
|
||||||
|
if(AvailableMapRegisters < MapRegistersRequired)
|
||||||
|
{
|
||||||
|
NDIS_DbgPrint(MIN_TRACE, ("Didn't get enough map registers from hal - requested 0x%x, got 0x%x\n",
|
||||||
|
MapRegistersRequired, AvailableMapRegisters));
|
||||||
|
return NDIS_STATUS_RESOURCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
|
||||||
|
|
||||||
|
Adapter->MapRegistersRequested = MapRegistersRequired;
|
||||||
|
|
||||||
|
NtStatus = IoAllocateAdapterChannel(AdapterObject, DeviceObject,
|
||||||
|
MapRegistersRequired, NdisMapRegisterCallback, Adapter);
|
||||||
|
|
||||||
|
KeLowerIrql(OldIrql);
|
||||||
|
|
||||||
|
if(!NT_SUCCESS(NtStatus))
|
||||||
|
{
|
||||||
|
NDIS_DbgPrint(MIN_TRACE, ("IoAllocateAdapterChannel failed: 0x%x\n", NtStatus));
|
||||||
|
return NDIS_STATUS_RESOURCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
NtStatus = KeWaitForSingleObject(&Adapter->DmaEvent, Executive, KernelMode, FALSE, 0);
|
||||||
|
|
||||||
|
if(!NT_SUCCESS(NtStatus))
|
||||||
|
{
|
||||||
|
NDIS_DbgPrint(MIN_TRACE, ("KeWaitForSingleObject failed: 0x%x\n", NtStatus));
|
||||||
|
return NDIS_STATUS_RESOURCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("returning success\n"));
|
||||||
|
return NDIS_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -272,6 +426,7 @@ NdisMDeregisterInterrupt(
|
||||||
* Interrupt = Pointer to interrupt object
|
* Interrupt = Pointer to interrupt object
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
||||||
IoDisconnectInterrupt(Interrupt->InterruptObject);
|
IoDisconnectInterrupt(Interrupt->InterruptObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,20 +451,44 @@ NdisMDeregisterIoPortRange(
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
||||||
|
|
||||||
/* Thank you */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @unimplemented
|
* @implemented
|
||||||
*/
|
*/
|
||||||
VOID
|
VOID
|
||||||
EXPORT
|
EXPORT
|
||||||
NdisMFreeMapRegisters(
|
NdisMFreeMapRegisters(
|
||||||
IN NDIS_HANDLE MiniportAdapterHandle)
|
IN NDIS_HANDLE MiniportAdapterHandle)
|
||||||
|
/*
|
||||||
|
* FUNCTION: Free previously allocated map registers
|
||||||
|
* ARGUMENTS:
|
||||||
|
* MiniportAdapterHandle: Handle originally passed in to MiniportInitialize
|
||||||
|
* NOTES:
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED
|
KIRQL OldIrql;
|
||||||
|
PLOGICAL_ADAPTER Adapter = (PLOGICAL_ADAPTER)MiniportAdapterHandle;
|
||||||
|
PADAPTER_OBJECT AdapterObject = Adapter->AdapterObject;
|
||||||
|
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
||||||
|
|
||||||
|
KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
|
||||||
|
|
||||||
|
while(!IsListEmpty(&Adapter->MapRegisterList.ListEntry))
|
||||||
|
{
|
||||||
|
PADAPTER_MAP_REGISTER_LIST Register = (PADAPTER_MAP_REGISTER_LIST)RemoveTailList(&Adapter->MapRegisterList.ListEntry);
|
||||||
|
if(Register)
|
||||||
|
{
|
||||||
|
IoFreeMapRegisters(AdapterObject, Register->MapRegister, Register->NumRegisters);
|
||||||
|
ExFreePool(Register);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
NDIS_DbgPrint(MIN_TRACE,("Internal NDIS error - Register is 0\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
KeLowerIrql(OldIrql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -362,7 +541,6 @@ NdisMRegisterDmaChannel(
|
||||||
return NDIS_STATUS_FAILURE;
|
return NDIS_STATUS_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @implemented
|
* @implemented
|
||||||
*/
|
*/
|
||||||
|
@ -416,8 +594,8 @@ NdisMRegisterInterrupt(
|
||||||
|
|
||||||
Adapter->NdisMiniportBlock.Interrupt = Interrupt;
|
Adapter->NdisMiniportBlock.Interrupt = Interrupt;
|
||||||
|
|
||||||
MappedIRQ = HalGetInterruptVector(Internal, /* Adapter->AdapterType, */
|
MappedIRQ = HalGetInterruptVector(Adapter->BusType,
|
||||||
0,
|
Adapter->BusNumber,
|
||||||
InterruptLevel,
|
InterruptLevel,
|
||||||
InterruptVector,
|
InterruptVector,
|
||||||
&DIrql,
|
&DIrql,
|
||||||
|
@ -472,34 +650,52 @@ NdisMRegisterIoPortRange(
|
||||||
* Status of operation
|
* Status of operation
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
#if 0
|
PHYSICAL_ADDRESS PortAddress, TranslatedAddress;
|
||||||
NTSTATUS Status;
|
|
||||||
BOOLEAN ConflictDetected;
|
|
||||||
PLOGICAL_ADAPTER Adapter = GET_LOGICAL_ADAPTER(MiniportAdapterHandle);
|
PLOGICAL_ADAPTER Adapter = GET_LOGICAL_ADAPTER(MiniportAdapterHandle);
|
||||||
PMINIPORT_DRIVER Miniport = Adapter->Miniport;
|
ULONG AddressSpace = 1; /* FIXME The HAL handles this wrong atm */
|
||||||
|
|
||||||
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
NDIS_DbgPrint(MAX_TRACE, ("Called - InitialPort 0x%x, NumberOfPorts 0x%x\n", InitialPort, NumberOfPorts));
|
||||||
|
|
||||||
/* Non-PnP hardware. NT5 function */
|
memset(&PortAddress, 0, sizeof(PortAddress));
|
||||||
Status = IoReportResourceForDetection(Miniport->DriverObject,
|
|
||||||
NULL,
|
|
||||||
0,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
0,
|
|
||||||
&ConflictDetected);
|
|
||||||
return NDIS_STATUS_FAILURE;
|
|
||||||
#else
|
|
||||||
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
|
|
||||||
|
|
||||||
/* It's yours! */
|
/* this might be a hack - ndis5 miniports seem to specify 0 */
|
||||||
*PortOffset = (PVOID)InitialPort;
|
if(InitialPort)
|
||||||
|
PortAddress = RtlConvertUlongToLargeInteger(InitialPort);
|
||||||
|
else
|
||||||
|
PortAddress = Adapter->BaseIoAddress;
|
||||||
|
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Translating address 0x%x 0x%x\n", PortAddress.u.HighPart, PortAddress.u.LowPart));
|
||||||
|
|
||||||
|
/* FIXME: hard-coded bus number */
|
||||||
|
if(!HalTranslateBusAddress(Adapter->BusType, 0, PortAddress, &AddressSpace, &TranslatedAddress))
|
||||||
|
{
|
||||||
|
NDIS_DbgPrint(MIN_TRACE, ("Unable to translate address\n"));
|
||||||
|
return NDIS_STATUS_RESOURCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Hal returned AddressSpace=0x%x TranslatedAddress=0x%x 0x%x\n",
|
||||||
|
AddressSpace, TranslatedAddress.u.HighPart, TranslatedAddress.u.LowPart));
|
||||||
|
|
||||||
|
if(AddressSpace)
|
||||||
|
{
|
||||||
|
ASSERT(TranslatedAddress.u.HighPart == 0);
|
||||||
|
*PortOffset = (PVOID) TranslatedAddress.u.LowPart;
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Returning 0x%x\n", *PortOffset));
|
||||||
|
return NDIS_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("calling MmMapIoSpace\n"));
|
||||||
|
|
||||||
|
*PortOffset = 0;
|
||||||
|
*PortOffset = MmMapIoSpace(TranslatedAddress, NumberOfPorts, 0);
|
||||||
|
NDIS_DbgPrint(MAX_TRACE, ("Returning 0x%x for port range\n", *PortOffset));
|
||||||
|
|
||||||
|
if(!*PortOffset)
|
||||||
|
return NDIS_STATUS_RESOURCES;
|
||||||
|
|
||||||
return NDIS_STATUS_SUCCESS;
|
return NDIS_STATUS_SUCCESS;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @unimplemented
|
* @unimplemented
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue