/****************************************************************************
 * drivers/rpmsg/rpmsg_port.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <debug.h>
#include <execinfo.h>
#include <stdio.h>
#include <syslog.h>

#include <time.h>

#include <nuttx/irq.h>
#include <nuttx/kmalloc.h>
#include <nuttx/nuttx.h>

#include "rpmsg_port.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define RPMSG_PORT_TIMEOUT_MS       15000

#define RPMSG_PORT_BUF_TO_NODE(q,b) ((q)->node + ((FAR void *)(b) - (q)->buf) / (q)->len)
#define RPMSG_PORT_NODE_TO_BUF(q,n) ((q)->buf + (((n) - (q)->node)) * (q)->len)

#define rpmsg_port_from_rdev(d) container_of(d, struct rpmsg_port_s, rdev)
#define rpmsg_port_hdr_from_buf(r) container_of(r, struct rpmsg_port_header_s, buf)

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static void rpmsg_port_dump(FAR struct rpmsg_s *rpmsg, bool verbose);
static int rpmsg_port_get_timestamp(FAR struct rpmsg_s *rpmsg,
                                    FAR const void *data,
                                    FAR struct rpmsg_timestamp_s *ts);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const struct rpmsg_ops_s g_rpmsg_port_ops =
{
  NULL,
  NULL,
  NULL,
  NULL,
  rpmsg_port_dump,
  rpmsg_port_get_timestamp,
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: rpmsg_port_post
 ****************************************************************************/

static void rpmsg_port_post(FAR sem_t *sem)
{
  int count = 0;

  nxsem_get_value(sem, &count);
  while (count++ <= 0)
    {
      nxsem_post(sem);
    }
}

/****************************************************************************
 * Name: rpmsg_port_add_node
 ****************************************************************************/

static void rpmsg_port_add_node(FAR struct rpmsg_port_list_s *list,
                                FAR struct list_node *node)
{
  irqstate_t flags;

  flags = spin_lock_irqsave(&list->lock);
  list_add_tail(&list->head, node);
  list->num++;
  spin_unlock_irqrestore(&list->lock, flags);
}

/****************************************************************************
 * Name: rpmsg_port_remove_node
 ****************************************************************************/

static FAR struct list_node *
rpmsg_port_remove_node(FAR struct rpmsg_port_list_s *list)
{
  FAR struct list_node *node;
  irqstate_t flags;

  flags = spin_lock_irqsave(&list->lock);
  node = list_remove_head(&list->head);
  if (node != NULL)
    {
      list->num--;
    }

  spin_unlock_irqrestore(&list->lock, flags);
  return node;
}

/****************************************************************************
 * Name: rpmsg_port_destroy_queue
 *
 * Description:
 *   Free memory alloced by rpmsg_port_create_queue.
 *
 ****************************************************************************/

static void rpmsg_port_destroy_queue(FAR struct rpmsg_port_queue_s *queue)
{
  if (queue->alloced)
    {
      kmm_free(queue->buf);
    }

  kmm_free(queue->node);
  nxsem_destroy(&queue->free.sem);
  nxsem_destroy(&queue->ready.sem);
}

/****************************************************************************
 * Name: rpmsg_port_create_queue
 ****************************************************************************/

static int rpmsg_port_create_queue(FAR struct rpmsg_port_queue_s *queue,
                                   uint16_t count, uint16_t len,
                                   FAR void *buf)
{
  FAR struct list_node *node;

  node = kmm_malloc(count * sizeof(struct list_node));
  if (node == NULL)
    {
      return -ENOMEM;
    }

  queue->node = node;

  /* Check if buffer space needed to be malloced internal. */

  if (buf == NULL)
    {
      buf = kmm_memalign(64, count * len);
      if (buf == NULL)
        {
          kmm_free(queue->node);
          return -ENOMEM;
        }

      queue->alloced = true;
    }

  queue->len = len;
  queue->buf = buf;

  /* Init free list */

  spin_lock_init(&queue->free.lock);
  nxsem_init(&queue->free.sem, 0, 0);
  list_initialize(&queue->free.head);
  while (count--)
    {
      rpmsg_port_add_node(&queue->free, node);
      node++;
    }

  /* Init ready list */

  spin_lock_init(&queue->ready.lock);
  nxsem_init(&queue->ready.sem, 0, 0);
  list_initialize(&queue->ready.head);

  return 0;
}

/****************************************************************************
 * Name: rpmsg_port_create_queues
 ****************************************************************************/

static int
rpmsg_port_create_queues(FAR struct rpmsg_port_s *port,
                         FAR const struct rpmsg_port_config_s *cfg)
{
  int ret;

  ret = rpmsg_port_create_queue(&port->txq, cfg->txnum,
                                cfg->txlen, cfg->txbuf);
  if (ret < 0)
    {
      return ret;
    }

  ret = rpmsg_port_create_queue(&port->rxq, cfg->rxnum,
                                cfg->rxlen, cfg->rxbuf);
  if (ret < 0)
    {
      rpmsg_port_destroy_queue(&port->txq);
      return ret;
    }

  port->txq.port = port;
  port->rxq.port = port;
  return 0;
}

/****************************************************************************
 * Name: rpmsg_port_destroy_queues
 ****************************************************************************/

static void rpmsg_port_destroy_queues(FAR struct rpmsg_port_s *port)
{
  rpmsg_port_destroy_queue(&port->txq);
  rpmsg_port_destroy_queue(&port->rxq);
}

/****************************************************************************
 * Name: rpmsg_port_get_tx_payload_buffer
 ****************************************************************************/

static FAR void *
rpmsg_port_get_tx_payload_buffer(FAR struct rpmsg_device *rdev,
                                 FAR uint32_t *len, int wait,
                                 uint8_t priority)
{
  FAR struct rpmsg_port_s *port = rpmsg_port_from_rdev(rdev);
  FAR struct rpmsg_port_header_s *hdr =
    rpmsg_port_queue_get_available_buffer(&port->txq, wait);

  if (hdr == NULL)
    {
      return NULL;
    }

  *len = hdr->len - sizeof(struct rpmsg_port_header_s) -
         sizeof(struct rpmsg_hdr) - sizeof(struct rpmsg_timestamp_s);

  return RPMSG_LOCATE_DATA(hdr->buf);
}

/****************************************************************************
 * Name: rpmsg_port_send_offchannel_nocopy
 ****************************************************************************/

static int rpmsg_port_send_offchannel_nocopy(FAR struct rpmsg_device *rdev,
                                             uint32_t src, uint32_t dst,
                                             FAR const void *data, int len)
{
  FAR struct rpmsg_port_s *port = rpmsg_port_from_rdev(rdev);
  FAR struct rpmsg_port_header_s *hdr;
  FAR struct rpmsg_hdr *rphdr;

  rphdr = RPMSG_LOCATE_HDR(data);
  rphdr->dst = dst;
  rphdr->src = src;
  rphdr->len = len;
  rphdr->reserved = 0;
  rphdr->flags = 0;

  hdr = rpmsg_port_hdr_from_buf(rphdr);
  hdr->len = sizeof(struct rpmsg_port_header_s) +
             sizeof(struct rpmsg_hdr) + len;

  rpmsg_port_queue_add_buffer(&port->txq, hdr);
  if (port->ops->notify_tx_ready)
    {
      port->ops->notify_tx_ready(port);
    }

  return len;
}

/****************************************************************************
 * Name: rpmsg_port_send_offchannel_raw
 ****************************************************************************/

static int rpmsg_port_send_offchannel_raw(FAR struct rpmsg_device *rdev,
                                          uint32_t src, uint32_t dst,
                                          FAR const void *data, int len,
                                          int wait, uint8_t priority)
{
  uint32_t buflen;
  FAR void *buf;

  buf = rpmsg_port_get_tx_payload_buffer(rdev, &buflen, wait, priority);
  if (buf == NULL)
    {
      return RPMSG_ERR_NO_BUFF;
    }

  RPMSG_ASSERT(len <= buflen, "Send size larger than buffer size\n");
  memcpy(buf, data, len);

  return rpmsg_port_send_offchannel_nocopy(rdev, src, dst, buf, len);
}

/****************************************************************************
 * Name: rpmsg_port_hold_rx_buffer
 ****************************************************************************/

static void rpmsg_port_hold_rx_buffer(FAR struct rpmsg_device *rdev,
                                      FAR void *rxbuf)
{
  FAR struct rpmsg_hdr *rphdr = RPMSG_LOCATE_HDR(rxbuf);

  atomic_add(&rphdr->reserved, 1 << RPMSG_BUF_HELD_SHIFT);
}

/****************************************************************************
 * Name: rpmsg_port_release_rx_buffer
 ****************************************************************************/

static void rpmsg_port_release_rx_buffer(FAR struct rpmsg_device *rdev,
                                         FAR void *rxbuf)
{
  FAR struct rpmsg_port_s *port = rpmsg_port_from_rdev(rdev);
  FAR struct rpmsg_hdr *rphdr = RPMSG_LOCATE_HDR(rxbuf);
  FAR struct rpmsg_port_header_s *hdr = rpmsg_port_hdr_from_buf(rphdr);
  uint32_t reserved =
    atomic_sub(&rphdr->reserved, 1 << RPMSG_BUF_HELD_SHIFT);

  if ((reserved & RPMSG_BUF_HELD_MASK) == (1 << RPMSG_BUF_HELD_SHIFT))
    {
      rpmsg_port_queue_return_buffer(&port->rxq, hdr);
      if (port->ops->notify_rx_free)
        {
          port->ops->notify_rx_free(port);
        }
    }
}

/****************************************************************************
 * Name: rpmsg_port_release_tx_buffer
 ****************************************************************************/

static int rpmsg_port_release_tx_buffer(FAR struct rpmsg_device *rdev,
                                        FAR void *txbuf)
{
  FAR struct rpmsg_port_s *port = rpmsg_port_from_rdev(rdev);
  FAR struct rpmsg_hdr *rphdr = RPMSG_LOCATE_HDR(txbuf);
  FAR struct rpmsg_port_header_s *hdr = rpmsg_port_hdr_from_buf(rphdr);

  rpmsg_port_queue_return_buffer(&port->txq, hdr);
  return RPMSG_SUCCESS;
}

/****************************************************************************
 * Name: rpmsg_port_get_tx_buffer_size
 ****************************************************************************/

static int rpmsg_port_get_tx_buffer_size(FAR struct rpmsg_device *rdev)
{
  FAR struct rpmsg_port_s *port = rpmsg_port_from_rdev(rdev);

  return port->txq.len - sizeof(struct rpmsg_port_header_s) -
         sizeof(struct rpmsg_hdr) - sizeof(struct rpmsg_timestamp_s);
}

/****************************************************************************
 * Name: rpmsg_port_get_rx_buffer_size
 ****************************************************************************/

static int rpmsg_port_get_rx_buffer_size(FAR struct rpmsg_device *rdev)
{
  FAR struct rpmsg_port_s *port = rpmsg_port_from_rdev(rdev);

  return port->rxq.len - sizeof(struct rpmsg_port_header_s) -
         sizeof(struct rpmsg_hdr) - sizeof(struct rpmsg_timestamp_s);
}

/****************************************************************************
 * Name: rpmsg_port_get_timestamp
 ****************************************************************************/

static int rpmsg_port_get_timestamp(FAR struct rpmsg_s *rpmsg,
                                    FAR const void *data,
                                    FAR struct rpmsg_timestamp_s *ts)
{
  FAR struct rpmsg_port_s *port = (FAR struct rpmsg_port_s *)rpmsg;
  FAR const struct rpmsg_hdr *rphdr = RPMSG_LOCATE_HDR(data);
  FAR const struct rpmsg_port_header_s *hdr = rpmsg_port_hdr_from_buf(rphdr);
  FAR const struct rpmsg_timestamp_s *rpts =
    (FAR const struct rpmsg_timestamp_s *)((uint8_t *)hdr + port->rxq.len -
                                           sizeof(struct rpmsg_timestamp_s));

  if (hdr->len > port->rxq.len - sizeof(struct rpmsg_timestamp_s))
    {
      return -EINVAL;
    }

  ts->tx_nsec = rpts->tx_nsec;
  ts->rx_nsec = rpts->rx_nsec;

  return RPMSG_SUCCESS;
}

/****************************************************************************
 * Name: rpmsg_port_rx_callback
 ****************************************************************************/

static void rpmsg_port_rx_callback(FAR struct rpmsg_port_s *port,
                                   FAR struct rpmsg_port_header_s *hdr)
{
  FAR struct rpmsg_device *rdev = &port->rdev;
  FAR struct rpmsg_hdr *rphdr = (FAR struct rpmsg_hdr *)hdr->buf;
  FAR void *data = RPMSG_LOCATE_DATA(rphdr);
  FAR struct rpmsg_endpoint *ept;
  size_t len = rphdr->len + sizeof(*rphdr);
  int status = 0;

  metal_mutex_acquire(&rdev->lock);
  ept = rpmsg_get_ept_from_addr(rdev, rphdr->dst);
  rpmsg_ept_incref(ept);
  metal_mutex_release(&rdev->lock);
  rpmsg_port_hold_rx_buffer(rdev, data);

  if (ept != NULL)
    {
      metal_mutex_acquire(&rdev->lock);
      if (ept->dest_addr == RPMSG_ADDR_ANY)
        {
          ept->dest_addr = rphdr->src;
          metal_mutex_release(&rdev->lock);
          if (ept->ns_bound_cb != NULL)
            {
              ept->ns_bound_cb(ept);
            }
        }
      else
        {
          metal_mutex_release(&rdev->lock);
        }

      rpmsg_trace(ept->name, false, rphdr, len,
                  "[Port] rx ept->cb start cb:%p rdev:%p hdr:%p len:%d",
                  ept, ept->name, ept->cb, rdev, rphdr, len);

      status = ept->cb(ept, data, rphdr->len, rphdr->src, ept->priv);

      rpmsg_trace(ept->name, false, NULL, 0,
                  "[Port] rx ept->cb start cb:%p rdev:%p hdr:%p len:%d",
                  ept, ept->name, ept->cb, rdev, rphdr, len);

      if (status < 0 && status != RPMSG_SUCCESS_BUFFER_RELEASED)
        {
          RPMSG_ASSERT(0, "unexpected callback status\n");
        }
    }

  if (status != RPMSG_SUCCESS_BUFFER_RELEASED)
    {
      rpmsg_port_release_rx_buffer(rdev, data);
    }

  rpmsg_ept_decref(ept);
}

/****************************************************************************
 * Name: rpmsg_port_ns_callback
 ****************************************************************************/

static int rpmsg_port_ns_callback(FAR struct rpmsg_endpoint *ept,
                                  FAR void *data, size_t len, uint32_t src,
                                  FAR void *priv)
{
  FAR struct rpmsg_device *rdev = ept->rdev;
  FAR struct rpmsg_ns_msg *msg = data;
  FAR const char *name = msg->name;
  uint32_t dest = msg->addr;
  bool decref = false;

  if (len != sizeof(*msg))
    {
      return RPMSG_SUCCESS;
    }

  metal_mutex_acquire(&rdev->lock);
  ept = rpmsg_get_endpoint(rdev, name, RPMSG_ADDR_ANY, dest);

  if (msg->flags == RPMSG_NS_DESTROY)
    {
      if (ept != NULL)
        {
          ept->dest_addr = RPMSG_ADDR_ANY;
          if (ept->release_cb != NULL)
            {
              rpmsg_ept_incref(ept);
              decref = true;
            }
        }

      metal_mutex_release(&rdev->lock);
      if (ept != NULL && ept->ns_unbind_cb != NULL)
        {
          ept->ns_unbind_cb(ept);
        }

      if (rdev->ns_unbind_cb != NULL)
        {
          rdev->ns_unbind_cb(rdev, name, dest);
        }

      if (decref)
        {
          rpmsg_ept_decref(ept);
        }
    }
  else if (msg->flags == RPMSG_NS_CREATE)
    {
      if (ept == NULL)
        {
          metal_mutex_release(&rdev->lock);
          if (rdev->ns_bind_cb != NULL)
            {
              rdev->ns_bind_cb(rdev, name, dest);
            }
        }
      else if (ept->dest_addr == RPMSG_ADDR_ANY)
        {
          ept->dest_addr = dest;
          metal_mutex_release(&rdev->lock);
          if (ept->name[0] && rdev->support_ack)
            {
              rpmsg_send_ns_message(ept, RPMSG_NS_CREATE_ACK);
            }

          /* Notify application that the endpoint has been bound */

          if (ept->ns_bound_cb != NULL)
            {
              ept->ns_bound_cb(ept);
            }
        }
      else
        {
          metal_mutex_release(&rdev->lock);
        }
    }
  else
    {
      /* RPMSG_NS_CREATE_ACK */

      if (ept != NULL && ept->dest_addr == RPMSG_ADDR_ANY)
        {
          ept->dest_addr = dest;
          metal_mutex_release(&rdev->lock);

          if (ept->ns_bound_cb != NULL)
            {
              ept->ns_bound_cb(ept);
            }
        }
      else
        {
          metal_mutex_release(&rdev->lock);
        }
    }

  return RPMSG_SUCCESS;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: rpmsg_port_initialize
 ****************************************************************************/

int rpmsg_port_initialize(FAR struct rpmsg_port_s *port,
                          FAR const struct rpmsg_port_config_s *cfg,
                          FAR const struct rpmsg_port_ops_s *ops)
{
  FAR struct rpmsg_device *rdev;
  int ret;

  if (port == NULL || cfg == NULL || ops == NULL)
    {
      return -EINVAL;
    }

  ret = rpmsg_port_create_queues(port, cfg);
  if (ret < 0)
    {
      return ret;
    }

  port->ops = ops;
  strlcpy(port->rpmsg.cpuname, cfg->remotecpu, RPMSG_NAME_SIZE);

  rdev = &port->rdev;
  memset(rdev, 0, sizeof(*rdev));
  metal_mutex_init(&rdev->lock);
  rdev->ns_bind_cb = rpmsg_ns_bind;
  rdev->ns_unbind_cb = rpmsg_ns_unbind;
  rdev->ops.send_offchannel_raw = rpmsg_port_send_offchannel_raw;
  rdev->ops.hold_rx_buffer = rpmsg_port_hold_rx_buffer;
  rdev->ops.release_rx_buffer = rpmsg_port_release_rx_buffer;
  rdev->ops.get_tx_payload_buffer = rpmsg_port_get_tx_payload_buffer;
  rdev->ops.send_offchannel_nocopy = rpmsg_port_send_offchannel_nocopy;
  rdev->ops.release_tx_buffer = rpmsg_port_release_tx_buffer;
  rdev->ops.get_rx_buffer_size = rpmsg_port_get_rx_buffer_size;
  rdev->ops.get_tx_buffer_size = rpmsg_port_get_tx_buffer_size;

  metal_list_init(&rdev->endpoints);

  rdev->support_ack = true;
  rdev->support_ns = true;

  port->ops->register_callback(port, rpmsg_port_rx_callback);

  return 0;
}

/****************************************************************************
 * Name: rpmsg_port_uninitialize
 ****************************************************************************/

void rpmsg_port_uninitialize(FAR struct rpmsg_port_s *port)
{
  FAR struct rpmsg_s *rpmsg = &port->rpmsg;
  FAR struct rpmsg_device *rdev = rpmsg_get_rdev_by_rpmsg(rpmsg);

  metal_mutex_deinit(&rdev->lock);
  rpmsg_port_destroy_queues(port);
}

/****************************************************************************
 * Name: rpmsg_port_queue_get_available_buffer
 ****************************************************************************/

FAR struct rpmsg_port_header_s *
rpmsg_port_queue_get_available_buffer(FAR struct rpmsg_port_queue_s *queue,
                                      bool wait)
{
  FAR struct rpmsg_port_s *port = queue->port;
  FAR struct rpmsg_port_header_s *hdr;
  FAR struct list_node *node;
  int ret;

  for (; ; )
    {
      node = rpmsg_port_remove_node(&queue->free);
      if (node)
        {
          hdr = RPMSG_PORT_NODE_TO_BUF(queue, node);
          hdr->len = queue->len;
          return hdr;
        }
      else if (!wait)
        {
          return NULL;
        }

      ret = port->ops->notify_queue_noavail ?
            port->ops->notify_queue_noavail(port, queue) : -ENOTSUP;
      if (ret == -ENOTSUP)
        {
          ret = nxsem_tickwait_uninterruptible(
            &queue->free.sem, MSEC2TICK(RPMSG_PORT_TIMEOUT_MS));
          if (ret == -ETIMEDOUT)
            {
              rpmsgerr("Get buffer timeout, Dump debug informations:\n");
              dump_stack();
              rpmsg_port_dump(&port->rpmsg, true);
            }
        }
    }
}

/****************************************************************************
 * Name: rpmsg_port_queue_return_buffer
 ****************************************************************************/

FAR void rpmsg_port_queue_return_buffer(FAR struct rpmsg_port_queue_s *queue,
                                        FAR struct rpmsg_port_header_s *hdr)
{
  FAR struct list_node *node = RPMSG_PORT_BUF_TO_NODE(queue, hdr);

  rpmsg_port_add_node(&queue->free, node);
  rpmsg_port_post(&queue->free.sem);
}

/****************************************************************************
 * Name: rpmsg_port_queue_get_buffer
 ****************************************************************************/

FAR struct rpmsg_port_header_s *
rpmsg_port_queue_get_buffer(FAR struct rpmsg_port_queue_s *queue, bool wait)
{
  FAR struct list_node *node;

  for (; ; )
    {
      node = rpmsg_port_remove_node(&queue->ready);
      if (node)
        {
          return RPMSG_PORT_NODE_TO_BUF(queue, node);
        }
      else if (!wait)
        {
          return NULL;
        }

      nxsem_wait_uninterruptible(&queue->ready.sem);
    }
}

/****************************************************************************
 * Name: rpmsg_port_queue_add_buffer
 ****************************************************************************/

void rpmsg_port_queue_add_buffer(FAR struct rpmsg_port_queue_s *queue,
                                 FAR struct rpmsg_port_header_s *hdr)
{
  FAR struct list_node *node = RPMSG_PORT_BUF_TO_NODE(queue, hdr);

  rpmsg_port_add_node(&queue->ready, node);
  rpmsg_port_post(&queue->ready.sem);
}

/****************************************************************************
 * Name: rpmsg_port_update_timestamp
 ****************************************************************************/

void rpmsg_port_update_timestamp(FAR struct rpmsg_port_queue_s *queue,
                                 FAR struct rpmsg_port_header_s *hdr,
                                 bool tx)
{
  FAR struct rpmsg_timestamp_s *rpts =
    (FAR struct rpmsg_timestamp_s *)((uint8_t *)hdr + queue->len -
                                     sizeof(struct rpmsg_timestamp_s));
  struct timespec ts;

  clock_gettime(CLOCK_MONOTONIC, &ts);
  if (tx)
    {
      rpts->tx_nsec = 1000000000ull * ts.tv_sec + ts.tv_nsec;
    }
  else if (hdr->len <= queue->len - sizeof(struct rpmsg_timestamp_s))
    {
      rpts->rx_nsec = 1000000000ull * ts.tv_sec + ts.tv_nsec;
    }
}

/****************************************************************************
 * Name: rpmsg_port_drop_packets
 ****************************************************************************/

void rpmsg_port_drop_packets(FAR struct rpmsg_port_s *rport, uint8_t type)
{
  FAR struct rpmsg_port_header_s *hdr;

  if (type & RPMSG_PORT_DROP_TXQ)
    {
      while (!!(hdr = rpmsg_port_queue_get_buffer(&rport->txq, false)))
        {
          rpmsg_port_queue_return_buffer(&rport->txq, hdr);
        }
    }

  if (type & RPMSG_PORT_DROP_RXQ)
    {
      while (!!(hdr = rpmsg_port_queue_get_buffer(&rport->rxq, false)))
        {
          rpmsg_port_queue_return_buffer(&rport->rxq, hdr);
        }
    }
}

/****************************************************************************
 * Name: rpmsg_port_register
 ****************************************************************************/

int rpmsg_port_register(FAR struct rpmsg_port_s *port,
                        FAR const char *local_cpuname)
{
  char name[64];
  int ret;

  if (local_cpuname)
    {
      strlcpy(port->rpmsg.local_cpuname, local_cpuname, RPMSG_NAME_SIZE);
    }

  rpmsgvbs("start register %s\n", port->rpmsg.cpuname);
  snprintf(name, sizeof(name), "/dev/rpmsg/%s", port->rpmsg.cpuname);
  ret = rpmsg_register(name, &port->rpmsg, &g_rpmsg_port_ops, 0);
  if (ret < 0)
    {
      return ret;
    }

  rpmsg_register_endpoint(&port->rdev, &port->rdev.ns_ept, "NS",
                          RPMSG_NS_EPT_ADDR, RPMSG_NS_EPT_ADDR,
                          rpmsg_port_ns_callback, NULL, port);
  rpmsg_device_created(&port->rpmsg);
  rpmsgvbs("register %s success\n", port->rpmsg.cpuname);
  return ret;
}

/****************************************************************************
 * Name: rpmsg_port_unregister
 ****************************************************************************/

void rpmsg_port_unregister(FAR struct rpmsg_port_s *port)
{
  char name[64];

  rpmsgvbs("start unregister %s\n", port->rpmsg.cpuname);
  snprintf(name, sizeof(name), "/dev/rpmsg/%s", port->rpmsg.cpuname);

  rpmsg_device_destory(&port->rpmsg);
  rpmsg_unregister(name, &port->rpmsg);
  rpmsgvbs("unregister %s success\n", port->rpmsg.cpuname);
}

/****************************************************************************
 * Name: rpmsg_port_dump_buffer
 ****************************************************************************/

static void rpmsg_port_dump_buffer(FAR struct rpmsg_device *rdev,
                                   FAR struct rpmsg_port_queue_s *queue,
                                   bool rx)
{
  FAR struct list_node *node;
  irqstate_t flags = spin_lock_irqsave(&queue->ready.lock);

  syslog(LOG_EMERG, "rpmsg_port queue %s: {used: %u, avail: %u}\n",
                    rx ? "RX" : "TX",
                    rpmsg_port_queue_nused(queue),
                    rpmsg_port_queue_navail(queue));
  syslog(LOG_EMERG, "rpmsg buffer list:\n");
  list_for_every(&queue->ready.head, node)
    {
      FAR struct rpmsg_port_header_s *hdr =
        RPMSG_PORT_NODE_TO_BUF(queue, node);
      FAR struct rpmsg_hdr *rphdr = (FAR struct rpmsg_hdr *)hdr->buf;
      FAR struct rpmsg_endpoint *ept;

      ept = rpmsg_get_ept_from_addr(rdev, rx ? rphdr->dst : rphdr->src);
      if (ept)
        {
          syslog(LOG_EMERG, " %s buffer %p hold by %s\n",
                            rx ? "RX" : "TX", rphdr, ept->name);
        }
    }

  spin_unlock_irqrestore(&queue->ready.lock, flags);
}

/****************************************************************************
 * Name: rpmsg_port_dump
 ****************************************************************************/

static void rpmsg_port_dump(FAR struct rpmsg_s *rpmsg, bool verbose)
{
  FAR struct rpmsg_port_s *port = (FAR struct rpmsg_port_s *)rpmsg;
  FAR struct rpmsg_device *rdev = rpmsg_get_rdev_by_rpmsg(rpmsg);
  bool needunlock = false;

  if (!up_interrupt_context() && !sched_idletask())
    {
      metal_mutex_acquire(&rdev->lock);
      needunlock = true;
    }

  syslog(LOG_EMERG, "Remote: %s state: %d\n",
                    port->rpmsg.cpuname, rpmsg_is_running(rdev));
  if (verbose)
    {
      rpmsg_dump_epts(rdev);

      rpmsg_port_dump_buffer(rdev, &port->rxq, true);
      rpmsg_port_dump_buffer(rdev, &port->txq, false);
    }

  if (needunlock)
    {
      metal_mutex_release(&rdev->lock);
    }

  if (port->ops->dump)
    {
      port->ops->dump(port, verbose);
    }
}