Modified the Win32 datalink for MSTP to be more effecient by using semaphores for dataready signals. Also increased task priority. Still not fast enough - needs to respond to PFM and Token within 20ms to be compliant.
This commit is contained in:
@@ -45,55 +45,6 @@
|
|||||||
#define MAX_HEADER (2+1+1+1+2+1+2+1)
|
#define MAX_HEADER (2+1+1+1+2+1+2+1)
|
||||||
#define MAX_MPDU (MAX_HEADER+MAX_PDU)
|
#define MAX_MPDU (MAX_HEADER+MAX_PDU)
|
||||||
|
|
||||||
/* The value 255 is used to denote broadcast when used as a */
|
|
||||||
/* destination address but is not allowed as a value for a station. */
|
|
||||||
/* Station addresses for master nodes can be 0-127. */
|
|
||||||
/* Station addresses for slave nodes can be 127-254. */
|
|
||||||
#define MSTP_BROADCAST_ADDRESS 255
|
|
||||||
|
|
||||||
/* MS/TP Frame Type */
|
|
||||||
/* Frame Types 8 through 127 are reserved by ASHRAE. */
|
|
||||||
#define FRAME_TYPE_TOKEN 0
|
|
||||||
#define FRAME_TYPE_POLL_FOR_MASTER 1
|
|
||||||
#define FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER 2
|
|
||||||
#define FRAME_TYPE_TEST_REQUEST 3
|
|
||||||
#define FRAME_TYPE_TEST_RESPONSE 4
|
|
||||||
#define FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY 5
|
|
||||||
#define FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY 6
|
|
||||||
#define FRAME_TYPE_REPLY_POSTPONED 7
|
|
||||||
/* Frame Types 128 through 255: Proprietary Frames */
|
|
||||||
/* These frames are available to vendors as proprietary (non-BACnet) frames. */
|
|
||||||
/* The first two octets of the Data field shall specify the unique vendor */
|
|
||||||
/* identification code, most significant octet first, for the type of */
|
|
||||||
/* vendor-proprietary frame to be conveyed. The length of the data portion */
|
|
||||||
/* of a Proprietary frame shall be in the range of 2 to 501 octets. */
|
|
||||||
#define FRAME_TYPE_PROPRIETARY_MIN 128
|
|
||||||
#define FRAME_TYPE_PROPRIETARY_MAX 255
|
|
||||||
/* The initial CRC16 checksum value */
|
|
||||||
#define CRC16_INITIAL_VALUE (0xFFFF)
|
|
||||||
|
|
||||||
/* receive FSM states */
|
|
||||||
typedef enum {
|
|
||||||
MSTP_RECEIVE_STATE_IDLE = 0,
|
|
||||||
MSTP_RECEIVE_STATE_PREAMBLE = 1,
|
|
||||||
MSTP_RECEIVE_STATE_HEADER = 2,
|
|
||||||
MSTP_RECEIVE_STATE_HEADER_CRC = 3,
|
|
||||||
MSTP_RECEIVE_STATE_DATA = 4
|
|
||||||
} MSTP_RECEIVE_STATE;
|
|
||||||
|
|
||||||
/* master node FSM states */
|
|
||||||
typedef enum {
|
|
||||||
MSTP_MASTER_STATE_INITIALIZE = 0,
|
|
||||||
MSTP_MASTER_STATE_IDLE = 1,
|
|
||||||
MSTP_MASTER_STATE_USE_TOKEN = 2,
|
|
||||||
MSTP_MASTER_STATE_WAIT_FOR_REPLY = 3,
|
|
||||||
MSTP_MASTER_STATE_DONE_WITH_TOKEN = 4,
|
|
||||||
MSTP_MASTER_STATE_PASS_TOKEN = 5,
|
|
||||||
MSTP_MASTER_STATE_NO_TOKEN = 6,
|
|
||||||
MSTP_MASTER_STATE_POLL_FOR_MASTER = 7,
|
|
||||||
MSTP_MASTER_STATE_ANSWER_DATA_REQUEST = 8
|
|
||||||
} MSTP_MASTER_STATE;
|
|
||||||
|
|
||||||
typedef struct dlmstp_packet {
|
typedef struct dlmstp_packet {
|
||||||
bool ready; /* true if ready to be sent or received */
|
bool ready; /* true if ready to be sent or received */
|
||||||
BACNET_ADDRESS address; /* source address */
|
BACNET_ADDRESS address; /* source address */
|
||||||
|
|||||||
@@ -105,10 +105,6 @@
|
|||||||
/* of a frame the node is transmitting: 20 bit times. */
|
/* of a frame the node is transmitting: 20 bit times. */
|
||||||
#define Tframe_gap 20
|
#define Tframe_gap 20
|
||||||
|
|
||||||
/* The time without a DataAvailable or ReceiveError event before declaration */
|
|
||||||
/* of loss of token: 500 milliseconds. */
|
|
||||||
#define Tno_token 500
|
|
||||||
|
|
||||||
/* The maximum time after the end of the stop bit of the final */
|
/* The maximum time after the end of the stop bit of the final */
|
||||||
/* octet of a transmitted frame before a node must disable its */
|
/* octet of a transmitted frame before a node must disable its */
|
||||||
/* EIA-485 driver: 15 bit times. */
|
/* EIA-485 driver: 15 bit times. */
|
||||||
@@ -121,12 +117,6 @@
|
|||||||
the reply may be in the transmit queue */
|
the reply may be in the transmit queue */
|
||||||
#define Treply_delay 10
|
#define Treply_delay 10
|
||||||
|
|
||||||
/* The minimum time without a DataAvailable or ReceiveError event */
|
|
||||||
/* that a node must wait for a station to begin replying to a */
|
|
||||||
/* confirmed request: 255 milliseconds. (Implementations may use */
|
|
||||||
/* larger values for this timeout, not to exceed 300 milliseconds.) */
|
|
||||||
#define Treply_timeout 255
|
|
||||||
|
|
||||||
/* Repeater turnoff delay. The duration of a continuous logical one state */
|
/* Repeater turnoff delay. The duration of a continuous logical one state */
|
||||||
/* at the active input port of an MS/TP repeater after which the repeater */
|
/* at the active input port of an MS/TP repeater after which the repeater */
|
||||||
/* will enter the IDLE state: 29 bit times < Troff < 40 bit times. */
|
/* will enter the IDLE state: 29 bit times < Troff < 40 bit times. */
|
||||||
@@ -141,12 +131,6 @@
|
|||||||
/* 15 milliseconds. */
|
/* 15 milliseconds. */
|
||||||
#define Tusage_delay 15
|
#define Tusage_delay 15
|
||||||
|
|
||||||
/* The minimum time without a DataAvailable or ReceiveError event that a */
|
|
||||||
/* node must wait for a remote node to begin using a token or replying to */
|
|
||||||
/* a Poll For Master frame: 20 milliseconds. (Implementations may use */
|
|
||||||
/* larger values for this timeout, not to exceed 100 milliseconds.) */
|
|
||||||
#define Tusage_timeout 20
|
|
||||||
|
|
||||||
/* we need to be able to increment without rolling over */
|
/* we need to be able to increment without rolling over */
|
||||||
#define INCREMENT_AND_LIMIT_UINT8(x) {if (x < 0xFF) x++;}
|
#define INCREMENT_AND_LIMIT_UINT8(x) {if (x < 0xFF) x++;}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,72 @@
|
|||||||
#include "bacdef.h"
|
#include "bacdef.h"
|
||||||
#include "dlmstp.h"
|
#include "dlmstp.h"
|
||||||
|
|
||||||
|
/* The value 255 is used to denote broadcast when used as a */
|
||||||
|
/* destination address but is not allowed as a value for a station. */
|
||||||
|
/* Station addresses for master nodes can be 0-127. */
|
||||||
|
/* Station addresses for slave nodes can be 127-254. */
|
||||||
|
#define MSTP_BROADCAST_ADDRESS 255
|
||||||
|
|
||||||
|
/* MS/TP Frame Type */
|
||||||
|
/* Frame Types 8 through 127 are reserved by ASHRAE. */
|
||||||
|
#define FRAME_TYPE_TOKEN 0
|
||||||
|
#define FRAME_TYPE_POLL_FOR_MASTER 1
|
||||||
|
#define FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER 2
|
||||||
|
#define FRAME_TYPE_TEST_REQUEST 3
|
||||||
|
#define FRAME_TYPE_TEST_RESPONSE 4
|
||||||
|
#define FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY 5
|
||||||
|
#define FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY 6
|
||||||
|
#define FRAME_TYPE_REPLY_POSTPONED 7
|
||||||
|
/* Frame Types 128 through 255: Proprietary Frames */
|
||||||
|
/* These frames are available to vendors as proprietary (non-BACnet) frames. */
|
||||||
|
/* The first two octets of the Data field shall specify the unique vendor */
|
||||||
|
/* identification code, most significant octet first, for the type of */
|
||||||
|
/* vendor-proprietary frame to be conveyed. The length of the data portion */
|
||||||
|
/* of a Proprietary frame shall be in the range of 2 to 501 octets. */
|
||||||
|
#define FRAME_TYPE_PROPRIETARY_MIN 128
|
||||||
|
#define FRAME_TYPE_PROPRIETARY_MAX 255
|
||||||
|
/* The initial CRC16 checksum value */
|
||||||
|
#define CRC16_INITIAL_VALUE (0xFFFF)
|
||||||
|
|
||||||
|
/* receive FSM states */
|
||||||
|
typedef enum {
|
||||||
|
MSTP_RECEIVE_STATE_IDLE = 0,
|
||||||
|
MSTP_RECEIVE_STATE_PREAMBLE = 1,
|
||||||
|
MSTP_RECEIVE_STATE_HEADER = 2,
|
||||||
|
MSTP_RECEIVE_STATE_HEADER_CRC = 3,
|
||||||
|
MSTP_RECEIVE_STATE_DATA = 4
|
||||||
|
} MSTP_RECEIVE_STATE;
|
||||||
|
|
||||||
|
/* master node FSM states */
|
||||||
|
typedef enum {
|
||||||
|
MSTP_MASTER_STATE_INITIALIZE = 0,
|
||||||
|
MSTP_MASTER_STATE_IDLE = 1,
|
||||||
|
MSTP_MASTER_STATE_USE_TOKEN = 2,
|
||||||
|
MSTP_MASTER_STATE_WAIT_FOR_REPLY = 3,
|
||||||
|
MSTP_MASTER_STATE_DONE_WITH_TOKEN = 4,
|
||||||
|
MSTP_MASTER_STATE_PASS_TOKEN = 5,
|
||||||
|
MSTP_MASTER_STATE_NO_TOKEN = 6,
|
||||||
|
MSTP_MASTER_STATE_POLL_FOR_MASTER = 7,
|
||||||
|
MSTP_MASTER_STATE_ANSWER_DATA_REQUEST = 8
|
||||||
|
} MSTP_MASTER_STATE;
|
||||||
|
|
||||||
|
/* The time without a DataAvailable or ReceiveError event before declaration */
|
||||||
|
/* of loss of token: 500 milliseconds. */
|
||||||
|
#define Tno_token 500
|
||||||
|
|
||||||
|
/* The minimum time without a DataAvailable or ReceiveError event */
|
||||||
|
/* that a node must wait for a station to begin replying to a */
|
||||||
|
/* confirmed request: 255 milliseconds. (Implementations may use */
|
||||||
|
/* larger values for this timeout, not to exceed 300 milliseconds.) */
|
||||||
|
#define Treply_timeout 255
|
||||||
|
|
||||||
|
/* The minimum time without a DataAvailable or ReceiveError event that a */
|
||||||
|
/* node must wait for a remote node to begin using a token or replying to */
|
||||||
|
/* a Poll For Master frame: 20 milliseconds. (Implementations may use */
|
||||||
|
/* larger values for this timeout, not to exceed 100 milliseconds.) */
|
||||||
|
#define Tusage_timeout 20
|
||||||
|
|
||||||
|
|
||||||
struct mstp_port_struct_t {
|
struct mstp_port_struct_t {
|
||||||
MSTP_RECEIVE_STATE receive_state;
|
MSTP_RECEIVE_STATE receive_state;
|
||||||
/* When a master node is powered up or reset, */
|
/* When a master node is powered up or reset, */
|
||||||
|
|||||||
@@ -25,10 +25,9 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#if PRINT_ENABLED
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#endif
|
|
||||||
#include "bacdef.h"
|
#include "bacdef.h"
|
||||||
#include "mstp.h"
|
#include "mstp.h"
|
||||||
#include "dlmstp.h"
|
#include "dlmstp.h"
|
||||||
@@ -44,6 +43,9 @@ uint16_t MSTP_Packets = 0;
|
|||||||
|
|
||||||
/* packet queues */
|
/* packet queues */
|
||||||
static DLMSTP_PACKET Receive_Packet;
|
static DLMSTP_PACKET Receive_Packet;
|
||||||
|
static HANDLE Receive_Packet_Flag;
|
||||||
|
/* mechanism to wait for a frame in state machine */
|
||||||
|
HANDLE Received_Frame_Flag;
|
||||||
static DLMSTP_PACKET Transmit_Packet;
|
static DLMSTP_PACKET Transmit_Packet;
|
||||||
/* local MS/TP port data - shared with RS-485 */
|
/* local MS/TP port data - shared with RS-485 */
|
||||||
volatile struct mstp_port_struct_t MSTP_Port;
|
volatile struct mstp_port_struct_t MSTP_Port;
|
||||||
@@ -69,6 +71,12 @@ void dlmstp_reinit(void)
|
|||||||
void dlmstp_cleanup(void)
|
void dlmstp_cleanup(void)
|
||||||
{
|
{
|
||||||
/* nothing to do for static buffers */
|
/* nothing to do for static buffers */
|
||||||
|
if (Received_Frame_Flag) {
|
||||||
|
CloseHandle(Received_Frame_Flag);
|
||||||
|
}
|
||||||
|
if (Receive_Packet_Flag) {
|
||||||
|
CloseHandle(Receive_Packet_Flag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* returns number of bytes sent on success, zero on failure */
|
/* returns number of bytes sent on success, zero on failure */
|
||||||
@@ -117,25 +125,29 @@ uint16_t dlmstp_receive(
|
|||||||
unsigned timeout) /* milliseconds to wait for a packet */
|
unsigned timeout) /* milliseconds to wait for a packet */
|
||||||
{
|
{
|
||||||
uint16_t pdu_len = 0;
|
uint16_t pdu_len = 0;
|
||||||
|
DWORD wait_status = 0;
|
||||||
|
|
||||||
/* see if there is a packet available, and a place
|
/* see if there is a packet available, and a place
|
||||||
to put the reply (if necessary) and process it */
|
to put the reply (if necessary) and process it */
|
||||||
if (Receive_Packet.ready) {
|
wait_status = WaitForSingleObject(Receive_Packet_Flag,timeout);
|
||||||
if (Receive_Packet.pdu_len) {
|
if (wait_status == WAIT_OBJECT_0) {
|
||||||
MSTP_Packets++;
|
if (Receive_Packet.ready) {
|
||||||
if (src) {
|
if (Receive_Packet.pdu_len) {
|
||||||
memmove(src,
|
MSTP_Packets++;
|
||||||
&Receive_Packet.address,
|
if (src) {
|
||||||
sizeof(Receive_Packet.address));
|
memmove(src,
|
||||||
|
&Receive_Packet.address,
|
||||||
|
sizeof(Receive_Packet.address));
|
||||||
|
}
|
||||||
|
if (pdu) {
|
||||||
|
memmove(pdu,
|
||||||
|
&Receive_Packet.pdu,
|
||||||
|
sizeof(Receive_Packet.pdu));
|
||||||
|
}
|
||||||
|
pdu_len = Receive_Packet.pdu_len;
|
||||||
}
|
}
|
||||||
if (pdu) {
|
Receive_Packet.ready = false;
|
||||||
memmove(pdu,
|
|
||||||
&Receive_Packet.pdu,
|
|
||||||
sizeof(Receive_Packet.pdu));
|
|
||||||
}
|
|
||||||
pdu_len = Receive_Packet.pdu_len;
|
|
||||||
}
|
}
|
||||||
Receive_Packet.ready = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pdu_len;
|
return pdu_len;
|
||||||
@@ -145,7 +157,7 @@ static void dlmstp_receive_fsm_task(void *pArg)
|
|||||||
{
|
{
|
||||||
bool received_frame;
|
bool received_frame;
|
||||||
|
|
||||||
//(void)SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
(void)SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
||||||
while (TRUE) {
|
while (TRUE) {
|
||||||
/* only do receive state machine while we don't have a frame */
|
/* only do receive state machine while we don't have a frame */
|
||||||
if ((MSTP_Port.ReceivedValidFrame == false) &&
|
if ((MSTP_Port.ReceivedValidFrame == false) &&
|
||||||
@@ -155,8 +167,10 @@ static void dlmstp_receive_fsm_task(void *pArg)
|
|||||||
MSTP_Receive_Frame_FSM(&MSTP_Port);
|
MSTP_Receive_Frame_FSM(&MSTP_Port);
|
||||||
received_frame = MSTP_Port.ReceivedValidFrame ||
|
received_frame = MSTP_Port.ReceivedValidFrame ||
|
||||||
MSTP_Port.ReceivedInvalidFrame;
|
MSTP_Port.ReceivedInvalidFrame;
|
||||||
if (received_frame)
|
if (received_frame) {
|
||||||
|
ReleaseSemaphore(Received_Frame_Flag, 1, NULL);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
} while (MSTP_Port.DataAvailable);
|
} while (MSTP_Port.DataAvailable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,14 +178,27 @@ static void dlmstp_receive_fsm_task(void *pArg)
|
|||||||
|
|
||||||
static void dlmstp_master_fsm_task(void *pArg)
|
static void dlmstp_master_fsm_task(void *pArg)
|
||||||
{
|
{
|
||||||
//(void)SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
DWORD dwMilliseconds = 0;
|
||||||
|
|
||||||
|
(void)SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
||||||
while (TRUE) {
|
while (TRUE) {
|
||||||
/* only do master state machine while rx is idle */
|
switch (MSTP_Port.master_state) {
|
||||||
if (MSTP_Port.receive_state == MSTP_RECEIVE_STATE_IDLE) {
|
case MSTP_MASTER_STATE_IDLE:
|
||||||
while (MSTP_Master_Node_FSM(&MSTP_Port)) {
|
dwMilliseconds = Tno_token;
|
||||||
Sleep(0);
|
break;
|
||||||
};
|
case MSTP_MASTER_STATE_WAIT_FOR_REPLY:
|
||||||
|
dwMilliseconds = Treply_timeout;
|
||||||
|
break;
|
||||||
|
case MSTP_MASTER_STATE_POLL_FOR_MASTER:
|
||||||
|
dwMilliseconds = Tusage_timeout;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dwMilliseconds = 0;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
if (dwMilliseconds)
|
||||||
|
WaitForSingleObject(Received_Frame_Flag,dwMilliseconds);
|
||||||
|
MSTP_Master_Node_FSM(&MSTP_Port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +239,7 @@ uint16_t MSTP_Put_Receive(
|
|||||||
volatile struct mstp_port_struct_t *mstp_port)
|
volatile struct mstp_port_struct_t *mstp_port)
|
||||||
{
|
{
|
||||||
uint16_t pdu_len = 0;
|
uint16_t pdu_len = 0;
|
||||||
|
BOOL rc;
|
||||||
|
|
||||||
if (!Receive_Packet.ready) {
|
if (!Receive_Packet.ready) {
|
||||||
/* bounds check - maybe this should send an abort? */
|
/* bounds check - maybe this should send an abort? */
|
||||||
@@ -225,6 +253,7 @@ uint16_t MSTP_Put_Receive(
|
|||||||
mstp_port->SourceAddress);
|
mstp_port->SourceAddress);
|
||||||
Receive_Packet.pdu_len = mstp_port->DataLength;
|
Receive_Packet.pdu_len = mstp_port->DataLength;
|
||||||
Receive_Packet.ready = true;
|
Receive_Packet.ready = true;
|
||||||
|
rc = ReleaseSemaphore(Receive_Packet_Flag, 1, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pdu_len;
|
return pdu_len;
|
||||||
@@ -380,6 +409,14 @@ bool dlmstp_init(char *ifname)
|
|||||||
/* initialize packet queue */
|
/* initialize packet queue */
|
||||||
Receive_Packet.ready = false;
|
Receive_Packet.ready = false;
|
||||||
Receive_Packet.pdu_len = 0;
|
Receive_Packet.pdu_len = 0;
|
||||||
|
Receive_Packet_Flag = CreateSemaphore (NULL,0,1,"dlmstpReceivePacket");
|
||||||
|
if (Receive_Packet_Flag == NULL)
|
||||||
|
exit(1);
|
||||||
|
Received_Frame_Flag = CreateSemaphore (NULL,0,1,"dlsmtpReceiveFrame");
|
||||||
|
if (Received_Frame_Flag == NULL) {
|
||||||
|
CloseHandle(Receive_Packet_Flag);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
/* initialize hardware */
|
/* initialize hardware */
|
||||||
/* initialize hardware */
|
/* initialize hardware */
|
||||||
if (ifname) {
|
if (ifname) {
|
||||||
@@ -494,7 +531,7 @@ int main(int argc, char *argv[])
|
|||||||
dlmstp_init(Network_Interface);
|
dlmstp_init(Network_Interface);
|
||||||
/* forever task */
|
/* forever task */
|
||||||
for (;;) {
|
for (;;) {
|
||||||
pdu_len = dlmstp_receive(NULL,NULL,0,0);
|
pdu_len = dlmstp_receive(NULL,NULL,0,INFINITE);
|
||||||
#if 0
|
#if 0
|
||||||
MSTP_Create_And_Send_Frame(
|
MSTP_Create_And_Send_Frame(
|
||||||
&MSTP_Port,
|
&MSTP_Port,
|
||||||
@@ -503,7 +540,6 @@ int main(int argc, char *argv[])
|
|||||||
MSTP_Port.This_Station,
|
MSTP_Port.This_Station,
|
||||||
NULL, 0);
|
NULL, 0);
|
||||||
#endif
|
#endif
|
||||||
Sleep(10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -325,8 +325,7 @@ void RS485_Check_UART_Data(struct mstp_port_struct_t *mstp_port)
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (dwRead) {
|
if (dwRead) {
|
||||||
mstp_port->DataRegister = lpBuf[0]; /* FIXME: Get this data from UART or buffer */
|
mstp_port->DataRegister = lpBuf[0];
|
||||||
/* if data is ready, */
|
|
||||||
mstp_port->DataAvailable = TRUE;
|
mstp_port->DataAvailable = TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user