2022-10-01 06:36:22 +02:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2022 Nordic Semiconductor ASA
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <zephyr/ipc/icmsg.h>
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <zephyr/drivers/mbox.h>
|
|
|
|
#include <zephyr/sys/atomic.h>
|
2023-10-24 12:24:49 +02:00
|
|
|
#include <zephyr/ipc/pbuf.h>
|
2023-08-07 13:51:53 +02:00
|
|
|
#include <zephyr/init.h>
|
2022-10-01 06:36:22 +02:00
|
|
|
|
2023-04-24 12:37:46 +02:00
|
|
|
#define BOND_NOTIFY_REPEAT_TO K_MSEC(CONFIG_IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS)
|
|
|
|
#define SHMEM_ACCESS_TO K_MSEC(CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_TO_MS)
|
2022-11-11 16:09:46 +01:00
|
|
|
|
2022-10-01 06:36:22 +02:00
|
|
|
static const uint8_t magic[] = {0x45, 0x6d, 0x31, 0x6c, 0x31, 0x4b,
|
|
|
|
0x30, 0x72, 0x6e, 0x33, 0x6c, 0x69, 0x34};
|
|
|
|
|
2024-06-14 11:11:27 +02:00
|
|
|
#ifdef CONFIG_MULTITHREADING
|
2024-06-24 20:59:51 +10:00
|
|
|
#if defined(CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE)
|
2023-08-07 13:51:53 +02:00
|
|
|
static K_THREAD_STACK_DEFINE(icmsg_stack, CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_STACK_SIZE);
|
|
|
|
static struct k_work_q icmsg_workq;
|
|
|
|
static struct k_work_q *const workq = &icmsg_workq;
|
|
|
|
#else
|
|
|
|
static struct k_work_q *const workq = &k_sys_work_q;
|
|
|
|
#endif
|
2024-06-14 11:11:27 +02:00
|
|
|
static void mbox_callback_process(struct k_work *item);
|
|
|
|
#else
|
|
|
|
static void mbox_callback_process(struct icmsg_data_t *dev_data);
|
|
|
|
#endif
|
2023-08-07 13:51:53 +02:00
|
|
|
|
2022-10-01 06:36:22 +02:00
|
|
|
static int mbox_deinit(const struct icmsg_config_t *conf,
|
|
|
|
struct icmsg_data_t *dev_data)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
2024-02-23 13:23:10 +01:00
|
|
|
err = mbox_set_enabled_dt(&conf->mbox_rx, 0);
|
2022-10-01 06:36:22 +02:00
|
|
|
if (err != 0) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2024-02-23 13:23:10 +01:00
|
|
|
err = mbox_register_callback_dt(&conf->mbox_rx, NULL, NULL);
|
2022-10-01 06:36:22 +02:00
|
|
|
if (err != 0) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2024-06-14 11:11:27 +02:00
|
|
|
#ifdef CONFIG_MULTITHREADING
|
2022-10-01 06:36:22 +02:00
|
|
|
(void)k_work_cancel(&dev_data->mbox_work);
|
2022-11-15 15:12:25 +01:00
|
|
|
(void)k_work_cancel_delayable(&dev_data->notify_work);
|
2024-06-14 11:11:27 +02:00
|
|
|
#endif
|
2022-10-01 06:36:22 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-06-14 11:11:27 +02:00
|
|
|
static bool is_endpoint_ready(struct icmsg_data_t *dev_data)
|
|
|
|
{
|
|
|
|
return atomic_get(&dev_data->state) == ICMSG_STATE_READY;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_MULTITHREADING
|
2022-11-15 15:12:25 +01:00
|
|
|
static void notify_process(struct k_work *item)
|
|
|
|
{
|
|
|
|
struct k_work_delayable *dwork = k_work_delayable_from_work(item);
|
|
|
|
struct icmsg_data_t *dev_data =
|
|
|
|
CONTAINER_OF(dwork, struct icmsg_data_t, notify_work);
|
|
|
|
|
2024-02-23 13:23:10 +01:00
|
|
|
(void)mbox_send_dt(&dev_data->cfg->mbox_tx, NULL);
|
2022-11-15 15:12:25 +01:00
|
|
|
|
|
|
|
atomic_t state = atomic_get(&dev_data->state);
|
|
|
|
|
|
|
|
if (state != ICMSG_STATE_READY) {
|
|
|
|
int ret;
|
|
|
|
|
2023-08-07 13:51:53 +02:00
|
|
|
ret = k_work_reschedule_for_queue(workq, dwork, BOND_NOTIFY_REPEAT_TO);
|
2022-11-15 15:12:25 +01:00
|
|
|
__ASSERT_NO_MSG(ret >= 0);
|
|
|
|
(void)ret;
|
|
|
|
}
|
|
|
|
}
|
2024-06-14 11:11:27 +02:00
|
|
|
#else
|
|
|
|
static void notify_process(struct icmsg_data_t *dev_data)
|
2022-11-11 08:04:55 +01:00
|
|
|
{
|
2024-06-14 11:11:27 +02:00
|
|
|
(void)mbox_send_dt(&dev_data->cfg->mbox_tx, NULL);
|
|
|
|
#if defined(CONFIG_SYS_CLOCK_EXISTS)
|
|
|
|
int64_t start = k_uptime_get();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
while (false == is_endpoint_ready(dev_data)) {
|
|
|
|
mbox_callback_process(dev_data);
|
|
|
|
|
|
|
|
#if defined(CONFIG_SYS_CLOCK_EXISTS)
|
|
|
|
if ((k_uptime_get() - start) > CONFIG_IPC_SERVICE_ICMSG_BOND_NOTIFY_REPEAT_TO_MS) {
|
|
|
|
#endif
|
|
|
|
(void)mbox_send_dt(&dev_data->cfg->mbox_tx, NULL);
|
|
|
|
#if defined(CONFIG_SYS_CLOCK_EXISTS)
|
|
|
|
start = k_uptime_get();
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
}
|
2022-11-11 08:04:55 +01:00
|
|
|
}
|
2024-06-14 11:11:27 +02:00
|
|
|
#endif
|
2022-11-11 08:04:55 +01:00
|
|
|
|
2024-06-14 11:11:27 +02:00
|
|
|
#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC
|
2023-06-20 14:42:22 +02:00
|
|
|
static int reserve_tx_buffer_if_unused(struct icmsg_data_t *dev_data)
|
2022-11-11 16:09:46 +01:00
|
|
|
{
|
2023-06-20 14:42:22 +02:00
|
|
|
int ret = k_mutex_lock(&dev_data->tx_lock, SHMEM_ACCESS_TO);
|
2022-11-11 16:09:46 +01:00
|
|
|
|
2023-04-24 12:37:46 +02:00
|
|
|
if (ret < 0) {
|
|
|
|
return ret;
|
|
|
|
}
|
2024-06-14 11:11:27 +02:00
|
|
|
|
2023-10-24 12:24:49 +02:00
|
|
|
return 0;
|
2022-11-11 16:09:46 +01:00
|
|
|
}
|
|
|
|
|
2023-06-20 14:42:22 +02:00
|
|
|
static int release_tx_buffer(struct icmsg_data_t *dev_data)
|
2022-11-11 16:09:46 +01:00
|
|
|
{
|
2023-06-20 14:42:22 +02:00
|
|
|
return k_mutex_unlock(&dev_data->tx_lock);
|
2023-06-16 08:53:04 +02:00
|
|
|
}
|
2024-06-14 11:11:27 +02:00
|
|
|
#endif
|
2023-06-16 08:53:04 +02:00
|
|
|
|
2023-10-24 12:24:49 +02:00
|
|
|
static uint32_t data_available(struct icmsg_data_t *dev_data)
|
2022-11-11 08:04:55 +01:00
|
|
|
{
|
2023-10-24 12:24:49 +02:00
|
|
|
return pbuf_read(dev_data->rx_pb, NULL, 0);
|
2022-11-11 08:04:55 +01:00
|
|
|
}
|
|
|
|
|
2024-06-14 11:11:27 +02:00
|
|
|
#ifdef CONFIG_MULTITHREADING
|
2022-11-11 08:04:55 +01:00
|
|
|
static void submit_mbox_work(struct icmsg_data_t *dev_data)
|
|
|
|
{
|
2023-08-07 13:51:53 +02:00
|
|
|
if (k_work_submit_to_queue(workq, &dev_data->mbox_work) < 0) {
|
2022-11-11 08:04:55 +01:00
|
|
|
/* The mbox processing work is never canceled.
|
|
|
|
* The negative error code should never be seen.
|
|
|
|
*/
|
|
|
|
__ASSERT_NO_MSG(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void submit_work_if_buffer_free(struct icmsg_data_t *dev_data)
|
|
|
|
{
|
|
|
|
submit_mbox_work(dev_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void submit_work_if_buffer_free_and_data_available(
|
|
|
|
struct icmsg_data_t *dev_data)
|
|
|
|
{
|
2023-10-24 12:24:49 +02:00
|
|
|
if (!data_available(dev_data)) {
|
2022-11-11 08:04:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
submit_mbox_work(dev_data);
|
|
|
|
}
|
2024-06-14 11:11:27 +02:00
|
|
|
#else
|
|
|
|
static void submit_if_buffer_free(struct icmsg_data_t *dev_data)
|
|
|
|
{
|
|
|
|
mbox_callback_process(dev_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void submit_if_buffer_free_and_data_available(
|
|
|
|
struct icmsg_data_t *dev_data)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (!data_available(dev_data)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mbox_callback_process(dev_data);
|
|
|
|
}
|
|
|
|
#endif
|
2022-11-11 08:04:55 +01:00
|
|
|
|
2024-06-14 11:11:27 +02:00
|
|
|
#ifdef CONFIG_MULTITHREADING
|
2022-10-01 06:36:22 +02:00
|
|
|
static void mbox_callback_process(struct k_work *item)
|
2024-06-14 11:11:27 +02:00
|
|
|
#else
|
|
|
|
static void mbox_callback_process(struct icmsg_data_t *dev_data)
|
|
|
|
#endif
|
2022-10-01 06:36:22 +02:00
|
|
|
{
|
2024-06-14 11:11:27 +02:00
|
|
|
#ifdef CONFIG_MULTITHREADING
|
2022-10-01 06:36:22 +02:00
|
|
|
struct icmsg_data_t *dev_data = CONTAINER_OF(item, struct icmsg_data_t, mbox_work);
|
2024-06-14 11:11:27 +02:00
|
|
|
#endif
|
2022-10-01 06:36:22 +02:00
|
|
|
|
|
|
|
atomic_t state = atomic_get(&dev_data->state);
|
|
|
|
|
2023-10-24 12:24:49 +02:00
|
|
|
uint32_t len = data_available(dev_data);
|
2022-10-01 06:36:22 +02:00
|
|
|
|
2023-06-16 08:53:04 +02:00
|
|
|
if (len == 0) {
|
|
|
|
/* Unlikely, no data in buffer. */
|
2022-10-01 06:36:22 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-10-24 12:24:49 +02:00
|
|
|
uint8_t rx_buffer[len];
|
|
|
|
|
|
|
|
len = pbuf_read(dev_data->rx_pb, rx_buffer, len);
|
|
|
|
|
2022-10-01 06:36:22 +02:00
|
|
|
if (state == ICMSG_STATE_READY) {
|
|
|
|
if (dev_data->cb->received) {
|
2024-06-14 11:11:27 +02:00
|
|
|
dev_data->cb->received(rx_buffer, len, dev_data->ctx);
|
2022-10-01 06:36:22 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
__ASSERT_NO_MSG(state == ICMSG_STATE_BUSY);
|
2023-06-16 08:53:04 +02:00
|
|
|
|
2023-11-02 15:24:14 +01:00
|
|
|
/* Allow magic number longer than sizeof(magic) for future protocol version. */
|
|
|
|
bool endpoint_invalid = (len < sizeof(magic) ||
|
|
|
|
memcmp(magic, rx_buffer, sizeof(magic)));
|
2023-06-16 08:53:04 +02:00
|
|
|
|
|
|
|
if (endpoint_invalid) {
|
2022-10-01 06:36:22 +02:00
|
|
|
__ASSERT_NO_MSG(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dev_data->cb->bound) {
|
|
|
|
dev_data->cb->bound(dev_data->ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
atomic_set(&dev_data->state, ICMSG_STATE_READY);
|
|
|
|
}
|
2024-06-14 11:11:27 +02:00
|
|
|
#ifdef CONFIG_MULTITHREADING
|
2022-11-11 08:04:55 +01:00
|
|
|
submit_work_if_buffer_free_and_data_available(dev_data);
|
2024-06-14 11:11:27 +02:00
|
|
|
#else
|
|
|
|
submit_if_buffer_free_and_data_available(dev_data);
|
|
|
|
#endif
|
2022-10-01 06:36:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void mbox_callback(const struct device *instance, uint32_t channel,
|
|
|
|
void *user_data, struct mbox_msg *msg_data)
|
|
|
|
{
|
|
|
|
struct icmsg_data_t *dev_data = user_data;
|
2024-06-14 11:11:27 +02:00
|
|
|
#ifdef CONFIG_MULTITHREADING
|
2022-11-11 08:04:55 +01:00
|
|
|
submit_work_if_buffer_free(dev_data);
|
2024-06-14 11:11:27 +02:00
|
|
|
#else
|
|
|
|
submit_if_buffer_free(dev_data);
|
|
|
|
#endif
|
2022-10-01 06:36:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mbox_init(const struct icmsg_config_t *conf,
|
|
|
|
struct icmsg_data_t *dev_data)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
2024-06-14 11:11:27 +02:00
|
|
|
#ifdef CONFIG_MULTITHREADING
|
2022-10-01 06:36:22 +02:00
|
|
|
k_work_init(&dev_data->mbox_work, mbox_callback_process);
|
2022-11-15 15:12:25 +01:00
|
|
|
k_work_init_delayable(&dev_data->notify_work, notify_process);
|
2024-06-14 11:11:27 +02:00
|
|
|
#endif
|
2022-10-01 06:36:22 +02:00
|
|
|
|
2024-02-23 13:23:10 +01:00
|
|
|
err = mbox_register_callback_dt(&conf->mbox_rx, mbox_callback, dev_data);
|
2022-10-01 06:36:22 +02:00
|
|
|
if (err != 0) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2024-02-23 13:23:10 +01:00
|
|
|
return mbox_set_enabled_dt(&conf->mbox_rx, 1);
|
2022-10-01 06:36:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int icmsg_open(const struct icmsg_config_t *conf,
|
|
|
|
struct icmsg_data_t *dev_data,
|
|
|
|
const struct ipc_service_cb *cb, void *ctx)
|
|
|
|
{
|
|
|
|
if (!atomic_cas(&dev_data->state, ICMSG_STATE_OFF, ICMSG_STATE_BUSY)) {
|
|
|
|
/* Already opened. */
|
|
|
|
return -EALREADY;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_data->cb = cb;
|
|
|
|
dev_data->ctx = ctx;
|
2022-11-15 15:12:25 +01:00
|
|
|
dev_data->cfg = conf;
|
2022-10-01 06:36:22 +02:00
|
|
|
|
2023-04-24 12:37:46 +02:00
|
|
|
#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC
|
2023-06-20 14:42:22 +02:00
|
|
|
k_mutex_init(&dev_data->tx_lock);
|
2023-04-24 12:37:46 +02:00
|
|
|
#endif
|
|
|
|
|
2023-10-24 12:24:49 +02:00
|
|
|
int ret = pbuf_init(dev_data->tx_pb);
|
2023-03-29 17:33:42 +02:00
|
|
|
|
2023-10-24 12:24:49 +02:00
|
|
|
if (ret < 0) {
|
|
|
|
__ASSERT(false, "Incorrect configuration");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize local copies of rx_pb. */
|
|
|
|
dev_data->rx_pb->data.wr_idx = 0;
|
|
|
|
dev_data->rx_pb->data.rd_idx = 0;
|
|
|
|
|
|
|
|
ret = pbuf_write(dev_data->tx_pb, magic, sizeof(magic));
|
2022-10-01 06:36:22 +02:00
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
__ASSERT_NO_MSG(false);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret < (int)sizeof(magic)) {
|
|
|
|
__ASSERT_NO_MSG(ret == sizeof(magic));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-03-29 17:33:42 +02:00
|
|
|
ret = mbox_init(conf, dev_data);
|
|
|
|
if (ret) {
|
|
|
|
return ret;
|
|
|
|
}
|
2024-06-14 11:11:27 +02:00
|
|
|
#ifdef CONFIG_MULTITHREADING
|
2023-08-07 13:51:53 +02:00
|
|
|
ret = k_work_schedule_for_queue(workq, &dev_data->notify_work, K_NO_WAIT);
|
2022-11-15 15:12:25 +01:00
|
|
|
if (ret < 0) {
|
2022-10-01 06:36:22 +02:00
|
|
|
return ret;
|
|
|
|
}
|
2024-06-14 11:11:27 +02:00
|
|
|
#else
|
|
|
|
notify_process(dev_data);
|
|
|
|
#endif
|
2022-10-01 06:36:22 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int icmsg_close(const struct icmsg_config_t *conf,
|
|
|
|
struct icmsg_data_t *dev_data)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = mbox_deinit(conf, dev_data);
|
|
|
|
if (ret) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
atomic_set(&dev_data->state, ICMSG_STATE_OFF);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int icmsg_send(const struct icmsg_config_t *conf,
|
|
|
|
struct icmsg_data_t *dev_data,
|
|
|
|
const void *msg, size_t len)
|
|
|
|
{
|
|
|
|
int ret;
|
2022-11-11 16:09:46 +01:00
|
|
|
int write_ret;
|
2024-06-14 11:11:27 +02:00
|
|
|
#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC
|
2022-11-11 16:09:46 +01:00
|
|
|
int release_ret;
|
2024-06-14 11:11:27 +02:00
|
|
|
#endif
|
2022-10-01 06:36:22 +02:00
|
|
|
int sent_bytes;
|
|
|
|
|
2022-11-11 08:04:55 +01:00
|
|
|
if (!is_endpoint_ready(dev_data)) {
|
2022-10-01 06:36:22 +02:00
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Empty message is not allowed */
|
|
|
|
if (len == 0) {
|
|
|
|
return -ENODATA;
|
|
|
|
}
|
|
|
|
|
2024-06-14 11:11:27 +02:00
|
|
|
#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC
|
2023-06-20 14:42:22 +02:00
|
|
|
ret = reserve_tx_buffer_if_unused(dev_data);
|
2023-04-24 12:37:46 +02:00
|
|
|
if (ret < 0) {
|
2022-11-11 16:09:46 +01:00
|
|
|
return -ENOBUFS;
|
|
|
|
}
|
2024-06-14 11:11:27 +02:00
|
|
|
#endif
|
2022-11-11 16:09:46 +01:00
|
|
|
|
2023-10-24 12:24:49 +02:00
|
|
|
write_ret = pbuf_write(dev_data->tx_pb, msg, len);
|
2024-06-14 11:11:27 +02:00
|
|
|
|
|
|
|
#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC
|
2023-06-20 14:42:22 +02:00
|
|
|
release_ret = release_tx_buffer(dev_data);
|
2022-11-11 16:09:46 +01:00
|
|
|
__ASSERT_NO_MSG(!release_ret);
|
2024-06-14 11:11:27 +02:00
|
|
|
#endif
|
2022-11-11 16:09:46 +01:00
|
|
|
|
|
|
|
if (write_ret < 0) {
|
|
|
|
return write_ret;
|
|
|
|
} else if (write_ret < len) {
|
|
|
|
return -EBADMSG;
|
|
|
|
}
|
|
|
|
sent_bytes = write_ret;
|
|
|
|
|
|
|
|
__ASSERT_NO_MSG(conf->mbox_tx.dev != NULL);
|
|
|
|
|
2024-02-23 13:23:10 +01:00
|
|
|
ret = mbox_send_dt(&conf->mbox_tx, NULL);
|
2022-11-11 16:09:46 +01:00
|
|
|
if (ret) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sent_bytes;
|
|
|
|
}
|
|
|
|
|
2024-06-24 20:59:51 +10:00
|
|
|
#if defined(CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE)
|
2023-08-07 13:51:53 +02:00
|
|
|
|
|
|
|
static int work_q_init(void)
|
|
|
|
{
|
|
|
|
struct k_work_queue_config cfg = {
|
|
|
|
.name = "icmsg_workq",
|
|
|
|
};
|
|
|
|
|
|
|
|
k_work_queue_start(&icmsg_workq,
|
|
|
|
icmsg_stack,
|
|
|
|
K_KERNEL_STACK_SIZEOF(icmsg_stack),
|
|
|
|
CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_PRIORITY, &cfg);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
SYS_INIT(work_q_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
|
|
|
|
|
|
|
#endif
|