mirror of
https://github.com/apache/nuttx.git
synced 2026-05-28 03:45:50 +08:00
drivers/mmcsd: Add MMC_IOC_CMD ioctl
Signed-off-by: helei8 <helei8@xiaomi.com>
This commit is contained in:
committed by
Alan Carvalho de Assis
parent
ff68d9ac04
commit
4b4004b874
@@ -27,6 +27,12 @@ menuconfig MMCSD
|
|||||||
|
|
||||||
if MMCSD
|
if MMCSD
|
||||||
|
|
||||||
|
config MMCSD_IOCSUPPORT
|
||||||
|
int "Enable MMC/SD ioctl support"
|
||||||
|
default y
|
||||||
|
---help---
|
||||||
|
Disable it to save some code size if required.
|
||||||
|
|
||||||
config MMCSD_NSLOTS
|
config MMCSD_NSLOTS
|
||||||
int "Number of MMC/SD slots"
|
int "Number of MMC/SD slots"
|
||||||
default 1
|
default 1
|
||||||
|
|||||||
@@ -221,6 +221,12 @@ static int mmcsd_probe(FAR struct mmcsd_state_s *priv);
|
|||||||
static int mmcsd_removed(FAR struct mmcsd_state_s *priv);
|
static int mmcsd_removed(FAR struct mmcsd_state_s *priv);
|
||||||
static int mmcsd_hwinitialize(FAR struct mmcsd_state_s *priv);
|
static int mmcsd_hwinitialize(FAR struct mmcsd_state_s *priv);
|
||||||
static void mmcsd_hwuninitialize(FAR struct mmcsd_state_s *priv);
|
static void mmcsd_hwuninitialize(FAR struct mmcsd_state_s *priv);
|
||||||
|
#ifdef CONFIG_MMCSD_IOCSUPPORT
|
||||||
|
static int mmcsd_iocmd(FAR struct mmcsd_state_s *priv,
|
||||||
|
FAR struct mmc_ioc_cmd *ic_ptr);
|
||||||
|
static int mmcsd_multi_iocmd(FAR struct mmcsd_state_s *priv,
|
||||||
|
FAR struct mmc_ioc_multi_cmd *imc_ptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Private Data
|
* Private Data
|
||||||
@@ -2342,6 +2348,30 @@ static int mmcsd_ioctl(FAR struct inode *inode, int cmd, unsigned long arg)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
#ifdef CONFIG_MMCSD_IOCSUPPORT
|
||||||
|
case MMC_IOC_CMD: /* MMCSD device ioctl commands */
|
||||||
|
{
|
||||||
|
finfo("MMC_IOC_CMD\n");
|
||||||
|
ret = mmcsd_iocmd(priv, (FAR struct mmc_ioc_cmd *)arg);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("ERROR: mmcsd_iocmd failed: %d\n", ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MMC_IOC_MULTI_CMD: /* MMCSD device ioctl muti commands */
|
||||||
|
{
|
||||||
|
finfo("MMC_IOC_MULTI_CMD\n");
|
||||||
|
ret = mmcsd_multi_iocmd(priv, (FAR struct mmc_ioc_multi_cmd *)arg);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("ERROR: mmcsd_iocmd failed: %d\n", ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ret = -ENOTTY;
|
ret = -ENOTTY;
|
||||||
break;
|
break;
|
||||||
@@ -2760,6 +2790,359 @@ static int mmcsd_read_csd(FAR struct mmcsd_state_s *priv)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: mmcsd_general_cmd_write
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Send cmd56 data, one sector size
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int mmcsd_general_cmd_write(FAR struct mmcsd_state_s *priv,
|
||||||
|
FAR const uint8_t *buffer,
|
||||||
|
off_t startblock)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
DEBUGASSERT(priv != NULL && buffer != NULL);
|
||||||
|
|
||||||
|
/* Check if the card is locked or write protected (either via software or
|
||||||
|
* via the mechanical write protect on the card)
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (mmcsd_wrprotected(priv))
|
||||||
|
{
|
||||||
|
ferr("ERROR: Card is locked or write protected\n");
|
||||||
|
return -EPERM;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_SDIO_DMA) && defined(CONFIG_ARCH_HAVE_SDIO_PREFLIGHT)
|
||||||
|
/* If we think we are going to perform a DMA transfer, make sure that we
|
||||||
|
* will be able to before we commit the card to the operation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ((priv->caps & SDIO_CAPS_DMASUPPORTED) != 0)
|
||||||
|
{
|
||||||
|
ret = SDIO_DMAPREFLIGHT(priv->dev, buffer, priv->blocksize);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Verify that the card is ready for the transfer. The card may still be
|
||||||
|
* busy from the preceding write transfer. It would be simpler to check
|
||||||
|
* for write busy at the end of each write, rather than at the beginning of
|
||||||
|
* each read AND write, but putting the busy-wait at the beginning of the
|
||||||
|
* transfer allows for more overlap and, hopefully, better performance
|
||||||
|
*/
|
||||||
|
|
||||||
|
ret = mmcsd_transferready(priv);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("ERROR: Card not ready: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select the block size for the card */
|
||||||
|
|
||||||
|
ret = mmcsd_setblocklen(priv, priv->blocksize);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("ERROR: mmcsd_setblocklen failed: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If Controller does not need DMA setup before the write then send CMD56
|
||||||
|
* now.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ((priv->caps & SDIO_CAPS_DMABEFOREWRITE) == 0)
|
||||||
|
{
|
||||||
|
/* Send CMD56, WRITE_BLOCK, and verify good R1 status is returned */
|
||||||
|
|
||||||
|
mmcsd_sendcmdpoll(priv, MMCSD_CMD56WR, startblock);
|
||||||
|
ret = mmcsd_recv_r1(priv, MMCSD_CMD56WR);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("ERROR: mmcsd_recv_r1 for CMD56 failed: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Configure SDIO controller hardware for the write transfer */
|
||||||
|
|
||||||
|
SDIO_BLOCKSETUP(priv->dev, priv->blocksize, 1);
|
||||||
|
SDIO_WAITENABLE(priv->dev,
|
||||||
|
SDIOWAIT_TRANSFERDONE | SDIOWAIT_TIMEOUT | SDIOWAIT_ERROR,
|
||||||
|
MMCSD_BLOCK_WDATADELAY);
|
||||||
|
|
||||||
|
#ifdef CONFIG_SDIO_DMA
|
||||||
|
if ((priv->caps & SDIO_CAPS_DMASUPPORTED) != 0)
|
||||||
|
{
|
||||||
|
ret = SDIO_DMASENDSETUP(priv->dev, buffer, priv->blocksize);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
finfo("SDIO_DMASENDSETUP: error %d\n", ret);
|
||||||
|
SDIO_CANCEL(priv->dev);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
SDIO_SENDSETUP(priv->dev, buffer, priv->blocksize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If Controller needs DMA setup before write then only send CMD24 now. */
|
||||||
|
|
||||||
|
if ((priv->caps & SDIO_CAPS_DMABEFOREWRITE) != 0)
|
||||||
|
{
|
||||||
|
/* Send CMD56, WRITE_BLOCK, and verify good R1 status is returned */
|
||||||
|
|
||||||
|
mmcsd_sendcmdpoll(priv, MMCSD_CMD56WR, startblock);
|
||||||
|
ret = mmcsd_recv_r1(priv, MMCSD_CMD56WR);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("ERROR: mmcsd_recv_r1 for CMD56 failed: %d\n", ret);
|
||||||
|
SDIO_CANCEL(priv->dev);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for the transfer to complete */
|
||||||
|
|
||||||
|
ret = mmcsd_eventwait(priv, SDIOWAIT_TIMEOUT | SDIOWAIT_ERROR);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("ERROR: CMD56 transfer failed: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flag that a write transfer is pending that we will have to check for
|
||||||
|
* write complete at the beginning of the next transfer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
priv->wrbusy = true;
|
||||||
|
|
||||||
|
#if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE)
|
||||||
|
/* Arm the write complete detection with timeout */
|
||||||
|
|
||||||
|
SDIO_WAITENABLE(priv->dev, SDIOWAIT_WRCOMPLETE | SDIOWAIT_TIMEOUT,
|
||||||
|
MMCSD_BLOCK_WDATADELAY);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* On success, return OK */
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: mmcsd_general_cmd_read
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Read cmd56 data, one sector size
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int mmcsd_general_cmd_read(FAR struct mmcsd_state_s *priv,
|
||||||
|
FAR uint8_t *buffer, off_t startblock)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
DEBUGASSERT(priv != NULL && buffer != NULL);
|
||||||
|
|
||||||
|
/* Check if the card is locked */
|
||||||
|
|
||||||
|
if (priv->locked)
|
||||||
|
{
|
||||||
|
ferr("ERROR: Card is locked\n");
|
||||||
|
return -EPERM;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_SDIO_DMA) && defined(CONFIG_ARCH_HAVE_SDIO_PREFLIGHT)
|
||||||
|
/* If we think we are going to perform a DMA transfer, make sure that we
|
||||||
|
* will be able to before we commit the card to the operation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ((priv->caps & SDIO_CAPS_DMASUPPORTED) != 0)
|
||||||
|
{
|
||||||
|
ret = SDIO_DMAPREFLIGHT(priv->dev, buffer, priv->blocksize);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Verify that the card is ready for the transfer. The card may still be
|
||||||
|
* busy from the preceding write transfer. It would be simpler to check
|
||||||
|
* for write busy at the end of each write, rather than at the beginning of
|
||||||
|
* each read AND write, but putting the busy-wait at the beginning of the
|
||||||
|
* transfer allows for more overlap and, hopefully, better performance
|
||||||
|
*/
|
||||||
|
|
||||||
|
ret = mmcsd_transferready(priv);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("ERROR: Card not ready: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select the block size for the card */
|
||||||
|
|
||||||
|
ret = mmcsd_setblocklen(priv, priv->blocksize);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("ERROR: mmcsd_setblocklen failed: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Configure SDIO controller hardware for the read transfer */
|
||||||
|
|
||||||
|
SDIO_BLOCKSETUP(priv->dev, priv->blocksize, 1);
|
||||||
|
SDIO_WAITENABLE(priv->dev,
|
||||||
|
SDIOWAIT_TRANSFERDONE | SDIOWAIT_TIMEOUT | SDIOWAIT_ERROR,
|
||||||
|
MMCSD_BLOCK_RDATADELAY);
|
||||||
|
|
||||||
|
#ifdef CONFIG_SDIO_DMA
|
||||||
|
if ((priv->caps & SDIO_CAPS_DMASUPPORTED) != 0)
|
||||||
|
{
|
||||||
|
ret = SDIO_DMARECVSETUP(priv->dev, buffer, priv->blocksize);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
finfo("SDIO_DMARECVSETUP: error %d\n", ret);
|
||||||
|
SDIO_CANCEL(priv->dev);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
SDIO_RECVSETUP(priv->dev, buffer, priv->blocksize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send CMD56: Read a sector size data and verify that good R1
|
||||||
|
* status is returned.
|
||||||
|
*/
|
||||||
|
|
||||||
|
mmcsd_sendcmdpoll(priv, MMCSD_CMD56RD, startblock);
|
||||||
|
ret = mmcsd_recv_r1(priv, MMCSD_CMD56RD);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("ERROR: mmcsd_recv_r1 for CMD56 failed: %d\n", ret);
|
||||||
|
SDIO_CANCEL(priv->dev);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Then wait for the data transfer to complete */
|
||||||
|
|
||||||
|
ret = mmcsd_eventwait(priv, SDIOWAIT_TIMEOUT | SDIOWAIT_ERROR);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("ERROR: CMD56 transfer failed: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return value: OK */
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_MMCSD_IOCSUPPORT
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: mmcsd_iocmd
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* MMCSD device ioctl commands.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int mmcsd_iocmd(FAR struct mmcsd_state_s *priv,
|
||||||
|
FAR struct mmc_ioc_cmd *ic_ptr)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
DEBUGASSERT(priv != NULL && ic_ptr != NULL);
|
||||||
|
|
||||||
|
if (!ic_ptr->is_acmd)
|
||||||
|
{
|
||||||
|
uint32_t opcode = ic_ptr->opcode & MMCSD_CMDIDX_MASK;
|
||||||
|
switch (opcode)
|
||||||
|
{
|
||||||
|
case MMCSD_CMDIDX56: /* support general commands */
|
||||||
|
{
|
||||||
|
if (ic_ptr->write_flag)
|
||||||
|
{
|
||||||
|
ret = mmcsd_general_cmd_write(priv,
|
||||||
|
(FAR uint8_t *)(uintptr_t)(ic_ptr->data_ptr),
|
||||||
|
ic_ptr->arg);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("mmcsd_iocmd MMCSD_CMDIDX56 write failed.\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret = mmcsd_general_cmd_read(priv,
|
||||||
|
(FAR uint8_t *)(uintptr_t)(ic_ptr->data_ptr),
|
||||||
|
ic_ptr->arg);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("mmcsd_iocmd MMCSD_CMDIDX56 read failed.\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ferr("mmcsd_iocmd opcode unsupported.\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: mmcsd_multi_iocmd
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* MMCSD device ioctl multi commands.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int mmcsd_multi_iocmd(FAR struct mmcsd_state_s *priv,
|
||||||
|
FAR struct mmc_ioc_multi_cmd *imc_ptr)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
DEBUGASSERT(priv != NULL && imc_ptr != NULL);
|
||||||
|
|
||||||
|
if (imc_ptr->num_of_cmds > MMC_IOC_MAX_CMDS)
|
||||||
|
{
|
||||||
|
ferr("mmcsd_multi_iocmd too many cmds.\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < imc_ptr->num_of_cmds; ++i)
|
||||||
|
{
|
||||||
|
ret = mmcsd_iocmd(priv, &imc_ptr->cmds[i]);
|
||||||
|
if (ret != OK)
|
||||||
|
{
|
||||||
|
ferr("cmds %d failed.\n", i);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: mmcsd_sdinitialize
|
* Name: mmcsd_sdinitialize
|
||||||
*
|
*
|
||||||
|
|||||||
+49
-7
@@ -36,7 +36,12 @@
|
|||||||
|
|
||||||
/* mmcsd ioctl */
|
/* mmcsd ioctl */
|
||||||
|
|
||||||
#define MMC_RPMB_TRANSFER _MMCSDIOC(0x0000)
|
#define MMC_IOC_CMD _MMCSDIOC(0x0000)
|
||||||
|
#define MMC_IOC_MULTI_CMD _MMCSDIOC(0x0001)
|
||||||
|
|
||||||
|
#define MMC_IOC_MAX_BYTES (512L * 1024)
|
||||||
|
#define MMC_IOC_MAX_CMDS 255
|
||||||
|
#define mmc_ioc_cmd_set_data(ic, ptr) (ic).data_ptr = (uint64_t)(unsigned long)(ptr)
|
||||||
|
|
||||||
/* rpmb request */
|
/* rpmb request */
|
||||||
|
|
||||||
@@ -49,6 +54,49 @@
|
|||||||
* Public Types
|
* Public Types
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
|
struct mmc_ioc_cmd
|
||||||
|
{
|
||||||
|
/* Direction of data: nonzero = write, zero = read.
|
||||||
|
* Bit 31 selects 'Reliable Write' for RPMB.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int write_flag;
|
||||||
|
|
||||||
|
/* Application-specific command. true = precede with CMD55 */
|
||||||
|
|
||||||
|
int is_acmd;
|
||||||
|
|
||||||
|
uint32_t opcode;
|
||||||
|
uint32_t arg;
|
||||||
|
uint32_t response[4]; /* CMD response */
|
||||||
|
unsigned int flags;
|
||||||
|
unsigned int blksz;
|
||||||
|
unsigned int blocks;
|
||||||
|
|
||||||
|
/* For 64-bit machines, the next member, ``uint64_t data_ptr``, wants to
|
||||||
|
* be 8-byte aligned. Make sure this struct is the same size when
|
||||||
|
* built for 32-bit.
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint32_t pad;
|
||||||
|
|
||||||
|
/* DAT buffer */
|
||||||
|
|
||||||
|
uint64_t data_ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* struct mmc_ioc_multi_cmd - multi command information
|
||||||
|
* @num_of_cmds: Number of commands to send. Must be equal to or less than
|
||||||
|
* MMC_IOC_MAX_CMDS.
|
||||||
|
* @cmds: Array of commands with length equal to 'num_of_cmds'
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct mmc_ioc_multi_cmd
|
||||||
|
{
|
||||||
|
uint64_t num_of_cmds;
|
||||||
|
struct mmc_ioc_cmd cmds[1];
|
||||||
|
};
|
||||||
|
|
||||||
struct mmc_rpmb_frame_s
|
struct mmc_rpmb_frame_s
|
||||||
{
|
{
|
||||||
uint8_t stuff[196];
|
uint8_t stuff[196];
|
||||||
@@ -62,12 +110,6 @@ struct mmc_rpmb_frame_s
|
|||||||
uint16_t req_resp;
|
uint16_t req_resp;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mmc_rpmb_transfer_s
|
|
||||||
{
|
|
||||||
unsigned int num_of_frames;
|
|
||||||
struct mmc_rpmb_frame_s frames[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Public Functions Definitions
|
* Public Functions Definitions
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|||||||
Reference in New Issue
Block a user