Implemented UNDI ethernet interface, receiving frames already appears to work.
git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@19628 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -70,12 +70,23 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
mac_addr_t fMACAddress;
|
mac_addr_t fMACAddress;
|
||||||
|
bool fRxFinished;
|
||||||
|
PXE_STRUCT * fPxeData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
UNDI::UNDI()
|
UNDI::UNDI()
|
||||||
|
: fMACAddress()
|
||||||
|
, fRxFinished(true)
|
||||||
|
, fPxeData(NULL)
|
||||||
{
|
{
|
||||||
TRACE("UNDI::UNDI\n");
|
TRACE("UNDI::UNDI\n");
|
||||||
|
|
||||||
|
fPxeData = pxe_undi_find_data();
|
||||||
|
if (!fPxeData)
|
||||||
|
panic("can't find !PXE structure");
|
||||||
|
|
||||||
|
TRACE("PXE API entrypoint at %04x:%04x\n", fPxeData->EntryPointSP.seg, fPxeData->EntryPointSP.ofs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -89,6 +100,60 @@ status_t
|
|||||||
UNDI::Init()
|
UNDI::Init()
|
||||||
{
|
{
|
||||||
TRACE("UNDI::Init\n");
|
TRACE("UNDI::Init\n");
|
||||||
|
|
||||||
|
PXENV_UNDI_GET_INFORMATION get_info;
|
||||||
|
PXENV_UNDI_GET_STATE get_state;
|
||||||
|
PXENV_UNDI_OPEN undi_open;
|
||||||
|
uint16 res;
|
||||||
|
|
||||||
|
undi_open.OpenFlag = 0;
|
||||||
|
undi_open.PktFilter = FLTR_DIRECTED | FLTR_BRDCST | FLTR_PRMSCS;
|
||||||
|
undi_open.R_Mcast_Buf.MCastAddrCount = 0;
|
||||||
|
|
||||||
|
res = call_pxe_bios(fPxeData, UNDI_OPEN, &undi_open);
|
||||||
|
if (res != 0 || get_state.Status != 0) {
|
||||||
|
TRACE("PXENV_UNDI_OPEN failed, res %x, status %x\n", res, undi_open.Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
res = call_pxe_bios(fPxeData, UNDI_GET_STATE, &get_state);
|
||||||
|
if (res != 0 || get_state.Status != 0) {
|
||||||
|
TRACE("PXENV_UNDI_GET_STATE failed, res %x, status %x\n", res, get_state.Status);
|
||||||
|
} else {
|
||||||
|
switch (get_state.UNDIstate) {
|
||||||
|
case PXE_UNDI_GET_STATE_STARTED:
|
||||||
|
TRACE("PXE_UNDI_GET_STATE_STARTED\n");
|
||||||
|
break;
|
||||||
|
case PXE_UNDI_GET_STATE_INITIALIZED:
|
||||||
|
TRACE("PXE_UNDI_GET_STATE_INITIALIZED\n");
|
||||||
|
break;
|
||||||
|
case PXE_UNDI_GET_STATE_OPENED:
|
||||||
|
TRACE("PXE_UNDI_GET_STATE_OPENED\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
TRACE("unknown undi state 0x%02x\n", get_state.UNDIstate);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = call_pxe_bios(fPxeData, UNDI_GET_INFORMATION, &get_info);
|
||||||
|
if (res != 0 || get_info.Status != 0) {
|
||||||
|
TRACE("PXENV_UNDI_GET_INFORMATION failed, res %x, status %x\n", res, get_info.Status);
|
||||||
|
return B_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE("Status = %x\n", get_info.Status);
|
||||||
|
TRACE("BaseIo = %x\n", get_info.BaseIo);
|
||||||
|
TRACE("IntNumber = %x\n", get_info.IntNumber);
|
||||||
|
TRACE("MaxTranUnit = %x\n", get_info.MaxTranUnit);
|
||||||
|
TRACE("HwType = %x\n", get_info.HwType);
|
||||||
|
TRACE("HwAddrLen = %x\n", get_info.HwAddrLen);
|
||||||
|
TRACE("RxBufCt = %x\n", get_info.RxBufCt);
|
||||||
|
TRACE("TxBufCt = %x\n", get_info.TxBufCt);
|
||||||
|
|
||||||
|
fMACAddress = get_info.CurrentNodeAddress;
|
||||||
|
|
||||||
|
TRACE("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", fMACAddress[0], fMACAddress[1], fMACAddress[2], fMACAddress[3], fMACAddress[4], fMACAddress[5]);
|
||||||
|
|
||||||
return B_OK;
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,11 +164,15 @@ UNDI::MACAddress() const
|
|||||||
return fMACAddress;
|
return fMACAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void *
|
void *
|
||||||
UNDI::AllocateSendReceiveBuffer(size_t size)
|
UNDI::AllocateSendReceiveBuffer(size_t size)
|
||||||
{
|
{
|
||||||
TRACE("UNDI::AllocateSendReceiveBuffer, size %ld\n", size);
|
TRACE("UNDI::AllocateSendReceiveBuffer, size %ld\n", size);
|
||||||
return 0;
|
if (size > 0x3000)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return (void *)0x500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -117,16 +186,120 @@ UNDI::FreeSendReceiveBuffer(void *buffer)
|
|||||||
ssize_t
|
ssize_t
|
||||||
UNDI::Send(const void *buffer, size_t size)
|
UNDI::Send(const void *buffer, size_t size)
|
||||||
{
|
{
|
||||||
TRACE("UNDI::Send, size %ld\n", size);
|
TRACE("UNDI::Send, buffer %p, size %ld\n", buffer, size);
|
||||||
|
|
||||||
|
hex_dump(buffer, size);
|
||||||
|
|
||||||
|
PXENV_UNDI_TRANSMIT undi_tx;
|
||||||
|
PXENV_UNDI_TBD undi_tbd;
|
||||||
|
|
||||||
|
undi_tx.Protocol = P_UNKNOWN;
|
||||||
|
undi_tx.XmitFlag = XMT_DESTADDR;
|
||||||
|
undi_tx.DestAddr.seg = SEG((char *)buffer + 16);
|
||||||
|
undi_tx.DestAddr.ofs = OFS((char *)buffer + 16);
|
||||||
|
undi_tx.TBD.seg = SEG(&undi_tbd);
|
||||||
|
undi_tx.TBD.ofs = OFS(&undi_tbd);
|
||||||
|
|
||||||
|
undi_tbd.ImmedLength = size;
|
||||||
|
undi_tbd.Xmit.seg = SEG(buffer);
|
||||||
|
undi_tbd.Xmit.ofs = OFS(buffer);
|
||||||
|
undi_tbd.DataBlkCount = 1;
|
||||||
|
undi_tbd.DataBlock[0].TDPtrType = 1;
|
||||||
|
undi_tbd.DataBlock[0].TDRsvdByte = 0;
|
||||||
|
undi_tbd.DataBlock[0].TDDataLen = size;
|
||||||
|
undi_tbd.DataBlock[0].TDDataPtr.seg = SEG(buffer);
|
||||||
|
undi_tbd.DataBlock[0].TDDataPtr.ofs = OFS(buffer);
|
||||||
|
|
||||||
|
uint16 res = call_pxe_bios(fPxeData, UNDI_TRANSMIT, &undi_tx);
|
||||||
|
if (res != 0 || undi_tx.Status != 0) {
|
||||||
|
TRACE("UNDI_TRANSMIT failed, res %x, status %x\n", res, undi_tx.Status);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE("UNDI_TRANSMIT success\n");
|
||||||
|
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
UNDI::Receive(void *buffer, size_t size)
|
UNDI::Receive(void *buffer, size_t size)
|
||||||
{
|
{
|
||||||
TRACE("UNDI::Receive, size %ld\n", size);
|
//TRACE("UNDI::Receive, buffer %p, size %ld\n", buffer, size);
|
||||||
|
|
||||||
|
PXENV_UNDI_ISR undi_isr;
|
||||||
|
uint16 res;
|
||||||
|
|
||||||
|
if (!fRxFinished) {
|
||||||
|
TRACE("continue receive...\n");
|
||||||
|
|
||||||
|
undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
|
||||||
|
res = call_pxe_bios(fPxeData, UNDI_ISR, &undi_isr);
|
||||||
|
if (res != 0 || undi_isr.Status != 0) {
|
||||||
|
TRACE("PXENV_UNDI_ISR_IN_GET_NEXT failed, res %x, status %x\n", res, undi_isr.Status);
|
||||||
|
fRxFinished = true;
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_START;
|
||||||
|
|
||||||
|
res = call_pxe_bios(fPxeData, UNDI_ISR, &undi_isr);
|
||||||
|
if (res != 0 || undi_isr.Status != 0) {
|
||||||
|
TRACE("PXENV_UNDI_ISR_IN_START failed, res %x, status %x\n", res, undi_isr.Status);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (undi_isr.FuncFlag != PXENV_UNDI_ISR_OUT_OURS) {
|
||||||
|
// TRACE("not ours\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send EOI to pic ?
|
||||||
|
|
||||||
|
TRACE("PXENV_UNDI_ISR_OUT_OURS\n");
|
||||||
|
|
||||||
|
undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
|
||||||
|
res = call_pxe_bios(fPxeData, UNDI_ISR, &undi_isr);
|
||||||
|
if (res != 0 || undi_isr.Status != 0) {
|
||||||
|
TRACE("PXENV_UNDI_ISR_IN_PROCESS failed, res %x, status %x\n", res, undi_isr.Status);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (undi_isr.FuncFlag) {
|
||||||
|
case PXENV_UNDI_ISR_OUT_TRANSMIT:
|
||||||
|
TRACE("PXENV_UNDI_ISR_OUT_TRANSMIT\n");
|
||||||
|
fRxFinished = false;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case PXENV_UNDI_ISR_OUT_RECEIVE:
|
||||||
|
TRACE("PXENV_UNDI_ISR_OUT_RECEIVE\n");
|
||||||
|
TRACE("BufferLength %d\n", undi_isr.BufferLength);
|
||||||
|
TRACE("FrameLength %d\n", undi_isr.FrameLength);
|
||||||
|
TRACE("FrameHeaderLength %d\n", undi_isr.FrameHeaderLength);
|
||||||
|
if (size > undi_isr.BufferLength)
|
||||||
|
size = undi_isr.BufferLength;
|
||||||
|
memcpy(buffer, (const void *)(undi_isr.Frame.seg * 16 + undi_isr.Frame.ofs), size);
|
||||||
|
hex_dump(buffer, size);
|
||||||
|
fRxFinished = false;
|
||||||
|
return size;
|
||||||
|
|
||||||
|
case PXENV_UNDI_ISR_OUT_BUSY:
|
||||||
|
TRACE("PXENV_UNDI_ISR_OUT_BUSY\n");
|
||||||
|
fRxFinished = true;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case PXENV_UNDI_ISR_OUT_DONE:
|
||||||
|
TRACE("PXENV_UNDI_ISR_OUT_DONE\n");
|
||||||
|
fRxFinished = true;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
default:
|
||||||
|
TRACE("default!!!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -135,14 +308,13 @@ platform_net_stack_init()
|
|||||||
{
|
{
|
||||||
TRACE("platform_net_stack_init\n");
|
TRACE("platform_net_stack_init\n");
|
||||||
|
|
||||||
pxe_undi_init();
|
|
||||||
|
|
||||||
UNDI *interface = new(nothrow) UNDI;
|
UNDI *interface = new(nothrow) UNDI;
|
||||||
if (!interface)
|
if (!interface)
|
||||||
return B_NO_MEMORY;
|
return B_NO_MEMORY;
|
||||||
|
|
||||||
status_t error = interface->Init();
|
status_t error = interface->Init();
|
||||||
if (error != B_OK) {
|
if (error != B_OK) {
|
||||||
|
TRACE("platform_net_stack_init: interface init failed\n");
|
||||||
delete interface;
|
delete interface;
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,6 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PXE_STRUCT *gPxeData;
|
|
||||||
|
|
||||||
|
|
||||||
PXE_STRUCT *
|
PXE_STRUCT *
|
||||||
pxe_undi_find_data()
|
pxe_undi_find_data()
|
||||||
{
|
{
|
||||||
@@ -35,39 +30,3 @@ pxe_undi_find_data()
|
|||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
pxe_undi_init()
|
|
||||||
{
|
|
||||||
TRACE("pxe_undi_init\n");
|
|
||||||
|
|
||||||
gPxeData = pxe_undi_find_data();
|
|
||||||
if (!gPxeData)
|
|
||||||
panic("can't find !PXE structure");
|
|
||||||
|
|
||||||
TRACE("entrypoint at %p\n", *(uint32 *)&gPxeData->EntryPointSP);
|
|
||||||
|
|
||||||
|
|
||||||
PXENV_UNDI_GET_INFORMATION get_info;
|
|
||||||
|
|
||||||
memset(&get_info, 0, sizeof(get_info));
|
|
||||||
|
|
||||||
TRACE("PXENV_UNDI_GET_INFORMATION at %p\n", &get_info);
|
|
||||||
|
|
||||||
uint16 res = call_pxe_bios(gPxeData, UNDI_GET_INFORMATION, &get_info);
|
|
||||||
|
|
||||||
TRACE("res = %04x\n", res);
|
|
||||||
|
|
||||||
TRACE("Status = %x\n", get_info.Status);
|
|
||||||
TRACE("BaseIo = %x\n", get_info.BaseIo);
|
|
||||||
TRACE("IntNumber = %x\n", get_info.IntNumber);
|
|
||||||
TRACE("MaxTranUnit = %x\n", get_info.MaxTranUnit);
|
|
||||||
TRACE("HwType = %x\n", get_info.HwType);
|
|
||||||
TRACE("HwAddrLen = %x\n", get_info.HwAddrLen);
|
|
||||||
TRACE("MAC = %02x:%02x:%02x:%02x:%02x:%02x\n", get_info.CurrentNodeAddress[0], get_info.CurrentNodeAddress[1],
|
|
||||||
get_info.CurrentNodeAddress[2], get_info.CurrentNodeAddress[3],
|
|
||||||
get_info.CurrentNodeAddress[4], get_info.CurrentNodeAddress[5] );
|
|
||||||
TRACE("RxBufCt = %x\n", get_info.RxBufCt);
|
|
||||||
TRACE("TxBufCt = %x\n", get_info.TxBufCt);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,11 +8,17 @@
|
|||||||
|
|
||||||
extern "C" uint16 call_pxe_bios(void *pxe, uint16 opcode, void *param);
|
extern "C" uint16 call_pxe_bios(void *pxe, uint16 opcode, void *param);
|
||||||
|
|
||||||
void pxe_undi_init();
|
#define UNDI_OPEN 0x0006
|
||||||
|
#define UNDI_TRANSMIT 0x0008
|
||||||
|
|
||||||
|
|
||||||
#define UNDI_GET_INFORMATION 0x000C
|
#define UNDI_GET_INFORMATION 0x000C
|
||||||
|
#define UNDI_ISR 0x0014
|
||||||
|
#define UNDI_GET_STATE 0x0015
|
||||||
|
|
||||||
|
#define SEG(ptr) ((uint16)(((uint32)(ptr)) >> 4))
|
||||||
|
#define OFS(ptr) (((uint16)(ptr)) & 15)
|
||||||
|
|
||||||
|
#define MAC_ADDR_LEN 16
|
||||||
|
typedef uint8 MAC_ADDR[MAC_ADDR_LEN];
|
||||||
|
|
||||||
struct SEGSEL
|
struct SEGSEL
|
||||||
{
|
{
|
||||||
@@ -21,8 +27,8 @@ struct SEGSEL
|
|||||||
|
|
||||||
struct SEGOFF16
|
struct SEGOFF16
|
||||||
{
|
{
|
||||||
uint16 seg;
|
|
||||||
uint16 ofs;
|
uint16 ofs;
|
||||||
|
uint16 seg;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SEGDESC
|
struct SEGDESC
|
||||||
@@ -54,6 +60,34 @@ struct PXE_STRUCT
|
|||||||
SEGDESC BC_CodeWrite;
|
SEGDESC BC_CodeWrite;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PXENV_UNDI_MCAST_ADDRESS
|
||||||
|
{
|
||||||
|
#define MAXNUM_MCADDR 8
|
||||||
|
uint16 MCastAddrCount;
|
||||||
|
MAC_ADDR McastAddr[MAXNUM_MCADDR];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PXENV_UNDI_OPEN
|
||||||
|
{
|
||||||
|
uint16 Status;
|
||||||
|
uint16 OpenFlag;
|
||||||
|
uint16 PktFilter;
|
||||||
|
#define FLTR_DIRECTED 0x0001
|
||||||
|
#define FLTR_BRDCST 0x0002
|
||||||
|
#define FLTR_PRMSCS 0x0004
|
||||||
|
#define FLTR_SRC_RTG 0x0008
|
||||||
|
PXENV_UNDI_MCAST_ADDRESS R_Mcast_Buf;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PXENV_UNDI_GET_STATE
|
||||||
|
{
|
||||||
|
#define PXE_UNDI_GET_STATE_STARTED 1
|
||||||
|
#define PXE_UNDI_GET_STATE_INITIALIZED 2
|
||||||
|
#define PXE_UNDI_GET_STATE_OPENED 3
|
||||||
|
uint16 Status;
|
||||||
|
uint8 UNDIstate;
|
||||||
|
};
|
||||||
|
|
||||||
struct PXENV_UNDI_GET_INFORMATION
|
struct PXENV_UNDI_GET_INFORMATION
|
||||||
{
|
{
|
||||||
uint16 Status;
|
uint16 Status;
|
||||||
@@ -66,12 +100,75 @@ struct PXENV_UNDI_GET_INFORMATION
|
|||||||
#define IEEE_TYPE 6
|
#define IEEE_TYPE 6
|
||||||
#define ARCNET_TYPE 7
|
#define ARCNET_TYPE 7
|
||||||
uint16 HwAddrLen;
|
uint16 HwAddrLen;
|
||||||
uint8 CurrentNodeAddress[16];
|
MAC_ADDR CurrentNodeAddress;
|
||||||
uint8 PermNodeAddress[16];
|
MAC_ADDR PermNodeAddress;
|
||||||
SEGSEL ROMAddress;
|
SEGSEL ROMAddress;
|
||||||
uint16 RxBufCt;
|
uint16 RxBufCt;
|
||||||
uint16 TxBufCt;
|
uint16 TxBufCt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PXENV_UNDI_TRANSMIT
|
||||||
|
{
|
||||||
|
uint16 Status;
|
||||||
|
uint8 Protocol;
|
||||||
|
#define P_UNKNOWN 0
|
||||||
|
#define P_IP 1
|
||||||
|
#define P_ARP 2
|
||||||
|
#define P_RARP 3
|
||||||
|
uint8 XmitFlag;
|
||||||
|
#define XMT_DESTADDR 0x0000
|
||||||
|
#define XMT_BROADCAST 0x0001
|
||||||
|
SEGOFF16 DestAddr;
|
||||||
|
SEGOFF16 TBD;
|
||||||
|
uint32 Reserved[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MAX_DATA_BLKS 8
|
||||||
|
struct PXENV_UNDI_TBD
|
||||||
|
{
|
||||||
|
uint16 ImmedLength;
|
||||||
|
SEGOFF16 Xmit;
|
||||||
|
uint16 DataBlkCount;
|
||||||
|
struct DataBlk
|
||||||
|
{
|
||||||
|
uint8 TDPtrType;
|
||||||
|
uint8 TDRsvdByte;
|
||||||
|
uint16 TDDataLen;
|
||||||
|
SEGOFF16 TDDataPtr;
|
||||||
|
} DataBlock[MAX_DATA_BLKS];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PXENV_UNDI_ISR
|
||||||
|
{
|
||||||
|
uint16 Status;
|
||||||
|
uint16 FuncFlag;
|
||||||
|
uint16 BufferLength;
|
||||||
|
uint16 FrameLength;
|
||||||
|
uint16 FrameHeaderLength;
|
||||||
|
SEGOFF16 Frame;
|
||||||
|
uint8 ProtType;
|
||||||
|
uint8 PktType;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PXENV_UNDI_ISR_IN_START 1
|
||||||
|
#define PXENV_UNDI_ISR_IN_PROCESS 2
|
||||||
|
#define PXENV_UNDI_ISR_IN_GET_NEXT 3
|
||||||
|
|
||||||
|
/* One of these will be returned for
|
||||||
|
PXENV_UNDI_ISR_IN_START
|
||||||
|
*/
|
||||||
|
#define PXENV_UNDI_ISR_OUT_OURS 0
|
||||||
|
#define PXENV_UNDI_USR_OUT_NOT_OURS 1
|
||||||
|
|
||||||
|
/* One of these will be returned for
|
||||||
|
PXENV_UNDI_ISR_IN_PROCESS and
|
||||||
|
PXENV_UNDI_ISR_IN_GET_NEXT
|
||||||
|
*/
|
||||||
|
#define PXENV_UNDI_ISR_OUT_DONE 0
|
||||||
|
#define PXENV_UNDI_ISR_OUT_TRANSMIT 2
|
||||||
|
#define PXENV_UNDI_ISR_OUT_RECEIVE 3
|
||||||
|
#define PXENV_UNDI_ISR_OUT_BUSY 4
|
||||||
|
|
||||||
|
PXE_STRUCT * pxe_undi_find_data();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user