/****************************************************************************
 * drivers/misc/rpmsgdev.c
 *
 * 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 <sys/types.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <poll.h>
#include <limits.h>
#include <debug.h>

#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/video/fb.h>
#include <nuttx/mutex.h>
#include <nuttx/rptun/openamp.h>

#include "rpmsgdev.h"

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

#ifndef rpmsgdeverr
#  define rpmsgdeverr(fmt, ...)
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct rpmsgdev_priv_s
{
  uint64_t filep;    /* store server file pointer */
  bool     nonblock; /* true: open with O_NONBLOCK */
};

struct rpmsgdev_s
{
  struct rpmsg_endpoint ept;         /* Rpmsg endpoint */
  FAR const char       *remotecpu;   /* The server cpu name */
  FAR const char       *remotepath;  /* The device path in the server cpu */
  sem_t                 wait;        /* Wait sem, used for preventing any
                                      * opreation until the connection
                                      * between two cpu established.
                                      */
};

/* Rpmsg device cookie used to handle the response from the remote cpu */

struct rpmsgdev_cookie_s
{
  sem_t     sem;     /* Semaphore used fo rpmsg */
  int       result;  /* The return value of the remote call */
  FAR void *data;    /* The return data buffer of the remote call */
};

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

/* The file operation functions */

static int     rpmsgdev_open(FAR struct file *filep);
static int     rpmsgdev_close(FAR struct file *filep);
static void    rpmsgdev_wait_cb(FAR struct pollfd *fds);
static int     rpmsgdev_wait(FAR struct file *filep, pollevent_t events);
static ssize_t rpmsgdev_read(FAR struct file *filep, FAR char *buffer,
                             size_t buflen);
static ssize_t rpmsgdev_write(FAR struct file *filep, FAR const char *buffer,
                              size_t buflen);
static off_t   rpmsgdev_seek(FAR struct file *filep, off_t offset,
                             int whence);
static ssize_t rpmsgdev_ioctl_arglen(int cmd);
static int     rpmsgdev_ioctl(FAR struct file *filep, int cmd,
                              unsigned long arg);
static int     rpmsgdev_poll(FAR struct file *filep, FAR struct pollfd *fds,
                             bool setup);

/* Functions for sending data to the remote cpu */

static int     rpmsgdev_send_recv(FAR struct rpmsgdev_s *priv,
                                  uint32_t command, bool copy,
                                  FAR struct rpmsgdev_header_s *msg,
                                  int len, FAR void *data);
static FAR void *rpmsgdev_get_tx_payload_buffer(FAR struct rpmsgdev_s *priv,
                                                FAR uint32_t *len);

/* Functions handle the responses from the remote cpu */

static int     rpmsgdev_default_handler(FAR struct rpmsg_endpoint *ept,
                                        FAR void *data, size_t len,
                                        uint32_t src, FAR void *priv);
static int     rpmsgdev_read_handler(FAR struct rpmsg_endpoint *ept,
                                     FAR void *data, size_t len,
                                     uint32_t src, FAR void *priv);
static int     rpmsgdev_ioctl_handler(FAR struct rpmsg_endpoint *ept,
                                      FAR void *data, size_t len,
                                      uint32_t src, FAR void *priv);
static int     rpmsgdev_notify_handler(FAR struct rpmsg_endpoint *ept,
                                       FAR void *data, size_t len,
                                       uint32_t src, FAR void *priv);

/* Functions for creating communication with remote cpu */

static void    rpmsgdev_device_created(struct rpmsg_device *rdev,
                                       FAR void *priv_);
static void    rpmsgdev_device_destroy(struct rpmsg_device *rdev,
                                       FAR void *priv_);
static int     rpmsgdev_ept_cb(FAR struct rpmsg_endpoint *ept,
                               FAR void *data, size_t len, uint32_t src,
                               FAR void *priv);
static void    rpmsgdev_ns_bound(struct rpmsg_endpoint *ept);

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

/* Rpmsg device response handler table */

static const rpmsg_ept_cb g_rpmsgdev_handler[] =
{
  [RPMSGDEV_OPEN]   = rpmsgdev_default_handler,
  [RPMSGDEV_CLOSE]  = rpmsgdev_default_handler,
  [RPMSGDEV_READ]   = rpmsgdev_read_handler,
  [RPMSGDEV_WRITE]  = rpmsgdev_default_handler,
  [RPMSGDEV_LSEEK]  = rpmsgdev_default_handler,
  [RPMSGDEV_IOCTL]  = rpmsgdev_ioctl_handler,
  [RPMSGDEV_POLL]   = rpmsgdev_default_handler,
  [RPMSGDEV_NOTIFY] = rpmsgdev_notify_handler,
};

/* File operations */

const struct file_operations g_rpmsgdev_ops =
{
  rpmsgdev_open,          /* open */
  rpmsgdev_close,         /* close */
  rpmsgdev_read,          /* read */
  rpmsgdev_write,         /* write */
  rpmsgdev_seek,          /* seek */
  rpmsgdev_ioctl,         /* ioctl */
  NULL,                   /* mmap */
  NULL,                   /* truncate */
  rpmsgdev_poll           /* poll */
};

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

/****************************************************************************
 * Name: rpmsgdev_open
 *
 * Description:
 *   Rpmsg-device open operation
 *
 * Parameters:
 *   filep  - the file instance
 *
 * Returned Values:
 *   OK on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

static int rpmsgdev_open(FAR struct file *filep)
{
  FAR struct rpmsgdev_s *dev;
  FAR struct rpmsgdev_priv_s *priv;
  struct rpmsgdev_open_s msg;
  int ret;

  /* Sanity checks */

  DEBUGASSERT(filep->f_inode != NULL);

  /* Get the mountpoint inode reference from the file structure and the
   * mountpoint private data from the inode structure
   */

  dev = filep->f_inode->i_private;
  DEBUGASSERT(dev != NULL);

  priv = (FAR struct rpmsgdev_priv_s *)kmm_zalloc(sizeof(*priv));
  if (priv == NULL)
    {
      return -ENOMEM;
    }

  /* Try to open the device in the remote cpu, open with O_NONBLOCK
   * by default to avoid the server rptun thread blocked in read/write
   * operations.
   */

  msg.flags = filep->f_oflags | O_NONBLOCK;
  ret = rpmsgdev_send_recv(dev, RPMSGDEV_OPEN, true, &msg.header,
                           sizeof(msg), NULL);
  if (ret < 0)
    {
      rpmsgdeverr("open failed, ret=%d\n", ret);
      kmm_free(priv);
      return ret;
    }

  priv->filep    = msg.filep;
  priv->nonblock = (filep->f_oflags & O_NONBLOCK) != 0;

  /* Attach the private date to the struct file instance */

  filep->f_priv = priv;

  return ret;
}

/****************************************************************************
 * Name: rpmsgdev_close
 *
 * Description:
 *   Rpmsg-device close operation
 *
 * Parameters:
 *   filep  - the file instance
 *
 * Returned Values:
 *   OK on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

static int rpmsgdev_close(FAR struct file *filep)
{
  FAR struct rpmsgdev_s *dev;
  FAR struct rpmsgdev_priv_s *priv;
  struct rpmsgdev_close_s msg;
  int ret;

  /* Sanity checks */

  DEBUGASSERT(filep->f_inode != NULL);

  /* Recover our private data from the struct file instance */

  dev  = filep->f_inode->i_private;
  priv = filep->f_priv;
  DEBUGASSERT(dev != NULL && priv != NULL);

  /* Try to close the device in the remote cpu */

  msg.filep = priv->filep;
  ret = rpmsgdev_send_recv(dev, RPMSGDEV_CLOSE, true, &msg.header,
                           sizeof(msg), NULL);
  if (ret < 0)
    {
      rpmsgdeverr("close failed, ret=%d\n", ret);
      return ret;
    }

  filep->f_priv = NULL;
  kmm_free(priv);

  return ret;
}

/****************************************************************************
 * Name: rpmsgdev_wait_cb
 *
 * Description:
 *   Rpmsg-device read/write operation wait callback function
 *
 * Parameters:
 *   fds  - The structure describing the events to be monitored.
 *
 * Returned Values:
 *   None
 *
 ****************************************************************************/

static void rpmsgdev_wait_cb(FAR struct pollfd *fds)
{
  int semcount = 0;
  FAR sem_t *pollsem = (FAR sem_t *)fds->arg;

  nxsem_get_value(pollsem, &semcount);
  if (semcount < 1)
    {
      nxsem_post(pollsem);
    }
}

/****************************************************************************
 * Name: rpmsgdev_wait
 *
 * Description:
 *   Rpmsg-device read/write operation wait function, this function will be
 *   called in the rpmsgdev_read()/rpmsgdev_write() when the open flags is
 *   not NONBLOCKED to avoid the server rptun thread blocked in file_read()
 *   or file_write(). By calling this function before sending the READ or
 *   WRITE command to server, a simulated blocked read/write operation is
 *   achived.
 *
 * Parameters:
 *   filep  - the file instance
 *   events - the events to be monitored
 *
 * Returned Values:
 *   OK on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

static int rpmsgdev_wait(FAR struct file *filep, pollevent_t events)
{
  int ret;
  sem_t sem;
  struct pollfd fds;

  nxsem_init(&sem, 0, 0);

  fds.events  = events;
  fds.arg     = &sem;
  fds.cb      = rpmsgdev_wait_cb;
  events     |= POLLERR | POLLHUP;

  while (1)
    {
      ret = rpmsgdev_poll(filep, &fds, true);
      if (ret < 0)
        {
          return ret;
        }

      ret = nxsem_wait(&sem);
      rpmsgdev_poll(filep, &fds, false);
      if (ret < 0)
        {
          return ret;
        }

      if ((fds.revents & events) != 0)
        {
          break;
        }
    }

  return ret;
}

/****************************************************************************
 * Name: rpmsgdev_read
 *
 * Description:
 *   Rpmsg-device read operation
 *
 * Parameters:
 *   filep  - the file instance
 *   buffer - the read buffer pointer
 *   buflen - the read buffer length
 *
 * Returned Values:
 *   The positive non-zero number of bytes read on success, 0 on if an
 *   end-of-file condition, or a negated errno value on any failure.
 *
 ****************************************************************************/

static ssize_t rpmsgdev_read(FAR struct file *filep, FAR char *buffer,
                             size_t buflen)
{
  FAR struct rpmsgdev_s *dev;
  FAR struct rpmsgdev_priv_s *priv;
  struct rpmsgdev_read_s msg;
  struct iovec read;
  ssize_t ret;

  if (buffer == NULL)
    {
      return -EINVAL;
    }

  /* Sanity checks */

  DEBUGASSERT(filep->f_inode != NULL);

  /* Recover our private data from the struct file instance */

  dev  = filep->f_inode->i_private;
  priv = filep->f_priv;
  DEBUGASSERT(dev != NULL && priv != NULL);

  /* If the open flags is not nonblock, should poll the device for
   * read ready first to avoid the server rptun thread blocked in
   * device read operation.
   */

  if (priv->nonblock == false)
    {
      ret = rpmsgdev_wait(filep, POLLIN);
      if (ret < 0)
        {
          return ret;
        }
    }

  /* Call the host to perform the read */

  read.iov_base = buffer;
  read.iov_len  = 0;

  msg.filep = priv->filep;
  msg.count = buflen;

  ret = rpmsgdev_send_recv(dev, RPMSGDEV_READ, true, &msg.header,
                           sizeof(msg) - 1, &read);

  return read.iov_len ? read.iov_len : ret;
}

/****************************************************************************
 * Name: rpmsgdev_write
 *
 * Description:
 *   Rpmsg-device write operation
 *
 * Parameters:
 *   filep  - the file instance
 *   buffer - the write buffer pointer
 *   buflen - the write buffer length
 *
 * Returned Values:
 *   On success, the number of bytes written are returned (zero indicates
 *   nothing was written).  On any failure, a negated errno value is returned
 *   (see comments withwrite() for a description of the appropriate errno
 *   values).
 *
 ****************************************************************************/

static ssize_t rpmsgdev_write(FAR struct file *filep, const char *buffer,
                              size_t buflen)
{
  FAR struct rpmsgdev_s *dev;
  FAR struct rpmsgdev_priv_s *priv;
  FAR struct rpmsgdev_write_s *msg;
  struct rpmsgdev_cookie_s cookie;
  uint32_t space;
  size_t written = 0;
  int ret;

  if (buffer == NULL)
    {
      return -EINVAL;
    }

  DEBUGASSERT(filep->f_inode != NULL);

  /* Recover our private data from the struct file instance */

  dev  = filep->f_inode->i_private;
  priv = filep->f_priv;
  DEBUGASSERT(dev != NULL && priv != NULL);

  /* If the open flags is not nonblock, should poll the device for
   * write ready first to avoid the server rptun thread blocked in
   * device write operation.
   */

  if (priv->nonblock == false)
    {
      ret = rpmsgdev_wait(filep, POLLOUT);
      if (ret < 0)
        {
          return ret;
        }
    }

  /* Perform the rpmsg write */

  memset(&cookie, 0, sizeof(cookie));
  nxsem_init(&cookie.sem, 0, 0);

  while (written < buflen)
    {
      msg = rpmsgdev_get_tx_payload_buffer(dev, &space);
      if (msg == NULL)
        {
          ret = -ENOMEM;
          goto out;
        }

      space -= sizeof(*msg) - 1;
      if (space >= buflen - written)
        {
          /* Send complete, set cookie is valid, need ack */

          space = buflen - written;
          msg->header.cookie = (uintptr_t)&cookie;
        }
      else
        {
          /* Not send complete, set cookie invalid, do not need ack */

          msg->header.cookie = 0;
        }

      msg->header.command = RPMSGDEV_WRITE;
      msg->header.result  = -ENXIO;
      msg->filep          = priv->filep;
      msg->count          = space;
      memcpy(msg->buf, buffer + written, space);

      ret = rpmsg_send_nocopy(&dev->ept, msg, sizeof(*msg) - 1 + space);
      if (ret < 0)
        {
          goto out;
        }

      written += space;
    }

  ret = rpmsg_wait(&dev->ept, &cookie.sem);
  if (ret < 0)
    {
      goto out;
    }

  ret = cookie.result;

out:
  nxsem_destroy(&cookie.sem);
  return ret < 0 ? ret : buflen;
}

/****************************************************************************
 * Name: rpmsgdev_seek
 *
 * Description:
 *   Rpmsg-device seek operation
 *
 * Parameters:
 *   file     File structure instance
 *   offset   Defines the offset to position to
 *   whence   Defines how to use offset
 *
 * Returned Values:
 *   The resulting offset on success.  A negated errno value is returned on
 *   any failure (see lseek comments).
 *
 ****************************************************************************/

static off_t rpmsgdev_seek(FAR struct file *filep, off_t offset, int whence)
{
  FAR struct rpmsgdev_s *dev;
  FAR struct rpmsgdev_priv_s *priv;
  struct rpmsgdev_lseek_s msg;
  int ret;

  /* Sanity checks */

  DEBUGASSERT(filep->f_inode != NULL);

  /* Recover our private data from the struct file instance */

  dev  = filep->f_inode->i_private;
  priv = filep->f_priv;
  DEBUGASSERT(dev != NULL && priv != NULL);

  /* Call our internal routine to perform the seek */

  msg.filep  = priv->filep;
  msg.offset = offset;
  msg.whence = whence;

  ret = rpmsgdev_send_recv(dev, RPMSGDEV_LSEEK, true, &msg.header,
                           sizeof(msg), NULL);
  if (ret >= 0)
    {
      filep->f_pos = msg.offset;
    }

  return ret;
}

/****************************************************************************
 * Name: rpmsgdev_ioctl_arglen
 *
 * Description:
 *   Get rpmsg device ioctl argument length according to the command
 *
 * Parameters:
 *   cmd - the ioctl command
 *
 * Returned Values:
 *   0        - ioctl command not support
 *   positive - the argument length
 *
 ****************************************************************************/

static ssize_t rpmsgdev_ioctl_arglen(int cmd)
{
  switch (cmd)
    {
      case FIONBIO:
      case FIONWRITE:
      case FIONREAD:
      case FIONSPACE:
      case FBIOSET_POWER:
      case FBIOGET_POWER:
        return sizeof(int);
      default:
        return -ENOTTY;
    }
}

/****************************************************************************
 * Name: rpmsgdev_ioctl
 *
 * Description:
 *   Rpmsg-device ioctl operation
 *
 * Parameters:
 *   filep  - the file instance
 *   cmd    - the ioctl command
 *   arg    - the ioctl arguments
 *
 * Returned Values:
 *   OK on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

static int rpmsgdev_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct rpmsgdev_s *dev;
  FAR struct rpmsgdev_priv_s *priv;
  FAR struct rpmsgdev_ioctl_s *msg;
  uint32_t space;
  ssize_t arglen;
  size_t msglen;
  int ret;

  /* Sanity checks */

  DEBUGASSERT(filep->f_inode != NULL);

  /* Recover our private data from the struct file instance */

  dev  = filep->f_inode->i_private;
  priv = filep->f_priv;
  DEBUGASSERT(dev != NULL && priv != NULL);

  /* Call our internal routine to perform the ioctl */

  arglen = rpmsgdev_ioctl_arglen(cmd);
  if (arglen < 0)
    {
      return arglen;
    }

  msglen = sizeof(*msg) + arglen - 1;

  msg = rpmsgdev_get_tx_payload_buffer(dev, &space);
  if (msg == NULL)
    {
      return -ENOMEM;
    }

  msg->filep   = priv->filep;
  msg->request = cmd;
  msg->arg     = arg;
  msg->arglen  = arglen;

  if (arglen > 0)
    {
      memcpy(msg->buf, (FAR void *)(uintptr_t)arg, arglen);
    }

  ret = rpmsgdev_send_recv(dev, RPMSGDEV_IOCTL, false, &msg->header,
                           msglen, arglen > 0 ? (FAR void *)arg : NULL);
  if (cmd == FIONBIO && ret >= 0)
    {
      int *nonblock = (FAR int *)(uintptr_t)arg;
      priv->nonblock = *nonblock;
    }

  return ret;
}

/****************************************************************************
 * Name: rpmsgdev_poll
 *
 * Description:
 *   Rpmsg-device poll operation
 *
 * Parameters:
 *   filep - the file instance
 *   fds   - The structure describing the events to be monitored.
 *   setup - true: Setup up the poll; false: Teardown the poll
 *
 * Returned Values:
 *   OK on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

static int rpmsgdev_poll(FAR struct file *filep, FAR struct pollfd *fds,
                         bool setup)
{
  FAR struct rpmsgdev_s *dev;
  FAR struct rpmsgdev_priv_s *priv;
  struct rpmsgdev_poll_s msg;

  /* Sanity checks */

  DEBUGASSERT(filep->f_inode != NULL);

  /* Recover our private data from the struct file instance */

  dev  = filep->f_inode->i_private;
  priv = filep->f_priv;
  DEBUGASSERT(dev != NULL && priv != NULL);

  /* Setup or teardown the poll */

  msg.filep  = priv->filep;
  msg.events = fds->events;
  msg.setup  = setup;
  msg.fds    = (uint64_t)(uintptr_t)fds;

  return rpmsgdev_send_recv(dev, RPMSGDEV_POLL, true, &msg.header,
                            sizeof(msg), NULL);
}

/****************************************************************************
 * Name: rpmsgdev_get_tx_payload_buffer
 *
 * Description:
 *   Get the rpmsg device tx payload, the buffer is from the rpmsg
 *   share memory that can be accessed by local and remote cpu.
 *
 * Parameters:
 *   priv  - The rpmsg-device handle
 *   len   - The got memroy size
 *
 * Returned Values:
 *   NULL     - failure
 *   not NULL - success
 *
 ****************************************************************************/

static FAR void *rpmsgdev_get_tx_payload_buffer(FAR struct rpmsgdev_s *priv,
                                                FAR uint32_t *len)
{
  int sval;

  nxsem_get_value(&priv->wait, &sval);
  if (sval <= 0)
    {
      rpmsg_wait(&priv->ept, &priv->wait);
      rpmsg_post(&priv->ept, &priv->wait);
    }

  return rpmsg_get_tx_payload_buffer(&priv->ept, len, true);
}

/****************************************************************************
 * Name: rpmsgdev_send_recv
 *
 * Description:
 *   Send and receive the rpmsg data.
 *
 * Parameters:
 *   priv    - rpmsg device handle
 *   command - the command, RPMSGDEV_OPEN, RPMSGDEV_CLOSE, RPMSGDEV_READ,
 *                          RPMSGDEV_WRITE, RPMSGDEV_IOCTL
 *   copy    - true, send a message across to the remote processor, and the
 *                   tx buffer will be alloced inside function rpmsg_send()
 *             false, send a message in tx buffer reserved by
 *                    rpmsg_get_tx_payload_buffer() across to the remote
 *                    processor.
 *   msg     - the message header
 *   len     - length of the payload
 *   data    - the data
 *
 * Returned Values:
 *   OK on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

static int rpmsgdev_send_recv(FAR struct rpmsgdev_s *priv,
                              uint32_t command, bool copy,
                              FAR struct rpmsgdev_header_s *msg,
                              int len, FAR void *data)
{
  struct rpmsgdev_cookie_s cookie;
  int ret;

  memset(&cookie, 0, sizeof(cookie));
  nxsem_init(&cookie.sem, 0, 0);

  if (data != NULL)
    {
      cookie.data = data;
    }
  else if (copy)
    {
      cookie.data = msg;
    }

  msg->command = command;
  msg->result  = -ENXIO;
  msg->cookie  = (uintptr_t)&cookie;

  if (copy)
    {
      ret = rpmsg_send(&priv->ept, msg, len);
    }
  else
    {
      ret = rpmsg_send_nocopy(&priv->ept, msg, len);
    }

  if (ret < 0)
    {
      goto fail;
    }

  ret = rpmsg_wait(&priv->ept, &cookie.sem);
  if (ret >= 0)
    {
      ret = cookie.result;
    }

fail:
  nxsem_destroy(&cookie.sem);
  return ret;
}

/****************************************************************************
 * Name: rpmsgdev_default_handler
 *
 * Description:
 *   Default rpmsg-device response handler, this function will be called to
 *   process the return message of rpmsgdev_open(), rpmsgdev_close() and
 *   rpmsgdev_write().
 *
 * Parameters:
 *   ept  - The rpmsg endpoint
 *   data - The return message
 *   len  - The return message length
 *   src  - unknow
 *   priv - unknow
 *
 * Returned Values:
 *   Always OK
 *
 ****************************************************************************/

static int rpmsgdev_default_handler(FAR struct rpmsg_endpoint *ept,
                                    FAR void *data, size_t len,
                                    uint32_t src, FAR void *priv)
{
  FAR struct rpmsgdev_header_s *header = data;
  FAR struct rpmsgdev_cookie_s *cookie =
      (FAR struct rpmsgdev_cookie_s *)(uintptr_t)header->cookie;

  cookie->result = header->result;
  if (cookie->result >= 0 && cookie->data)
    {
      memcpy(cookie->data, data, len);
    }

  rpmsg_post(ept, &cookie->sem);
  return 0;
}

/****************************************************************************
 * Name: rpmsgdev_read_handler
 *
 * Description:
 *   Rpmsg-device read response handler, this function will be called to
 *   process the return message of rpmsgdev_read().
 *
 * Parameters:
 *   ept  - The rpmsg endpoint
 *   data - The return message
 *   len  - The return message length
 *   src  - unknow
 *   priv - unknow
 *
 * Returned Values:
 *   Always OK
 *
 ****************************************************************************/

static int rpmsgdev_read_handler(FAR struct rpmsg_endpoint *ept,
                                 FAR void *data, size_t len,
                                 uint32_t src, FAR void *priv)
{
  FAR struct rpmsgdev_header_s *header = data;
  FAR struct rpmsgdev_cookie_s *cookie =
      (FAR struct rpmsgdev_cookie_s *)(uintptr_t)header->cookie;
  FAR struct rpmsgdev_read_s *rsp = data;
  FAR struct iovec *read = cookie->data;

  cookie->result = header->result;
  if (cookie->result > 0)
    {
      memcpy(read->iov_base + read->iov_len, rsp->buf, cookie->result);
      read->iov_len += cookie->result;
    }

  if (cookie->result <= 0 || read->iov_len >= rsp->count)
    {
      rpmsg_post(ept, &cookie->sem);
    }

  return 0;
}

/****************************************************************************
 * Name: rpmsgdev_ioctl_handler
 *
 * Description:
 *   Rpmsg-device ioctl response handler, this function will be called to
 *   process the return message of rpmsgdev_ioctl().
 *
 * Parameters:
 *   ept  - The rpmsg endpoint
 *   data - The return message
 *   len  - The return message length
 *   src  - unknow
 *   priv - unknow
 *
 * Returned Values:
 *   Always OK
 *
 ****************************************************************************/

static int rpmsgdev_ioctl_handler(FAR struct rpmsg_endpoint *ept,
                                  FAR void *data, size_t len,
                                  uint32_t src, FAR void *priv)
{
  FAR struct rpmsgdev_header_s *header = data;
  FAR struct rpmsgdev_cookie_s *cookie =
      (FAR struct rpmsgdev_cookie_s *)(uintptr_t)header->cookie;
  FAR struct rpmsgdev_ioctl_s *rsp = data;

  cookie->result = header->result;
  if (cookie->result >= 0 && rsp->arglen > 0)
    {
      memcpy(cookie->data, (FAR void *)(uintptr_t)rsp->buf, rsp->arglen);
    }

  rpmsg_post(ept, &cookie->sem);
  return 0;
}

/****************************************************************************
 * Name: rpmsgdev_notify_handler
 *
 * Description:
 *   Rpmsg-device poll notify handler.
 *
 * Parameters:
 *   ept  - The rpmsg endpoint
 *   data - The return message
 *   len  - The return message length
 *   src  - unknow
 *   priv - unknow
 *
 * Returned Values:
 *   Always OK
 *
 ****************************************************************************/

static int rpmsgdev_notify_handler(FAR struct rpmsg_endpoint *ept,
                                   FAR void *data, size_t len,
                                   uint32_t src, FAR void *priv)
{
  FAR struct rpmsgdev_notify_s *rsp = data;
  FAR struct pollfd *fds;

  fds = (FAR struct pollfd *)(uintptr_t)rsp->fds;
  poll_notify(&fds, 1, rsp->revents);

  return 0;
}

/****************************************************************************
 * Name: rpmsgdev_ns_bound
 *
 * Description:
 *   Rpmsg device end point service bound callback function , called when
 *   remote end point address is received.
 *
 * Parameters:
 *   ept  - The rpmsg-device end point
 *
 * Returned Values:
 *   None
 *
 ****************************************************************************/

static void rpmsgdev_ns_bound(FAR struct rpmsg_endpoint *ept)
{
  FAR struct rpmsgdev_s *priv = ept->priv;

  rpmsg_post(&priv->ept, &priv->wait);
}

/****************************************************************************
 * Name: rpmsgdev_device_created
 *
 * Description:
 *   Rpmsg device create function, this function will be called by rptun to
 *   create a rpmsg-device end point.
 *
 * Parameters:
 *   rdev  - The rpmsg-device end point
 *   priv_ - Rpmsg-device handle
 *
 * Returned Values:
 *   None
 *
 ****************************************************************************/

static void rpmsgdev_device_created(FAR struct rpmsg_device *rdev,
                                    FAR void *priv_)
{
  FAR struct rpmsgdev_s *priv = priv_;
  char buf[RPMSG_NAME_SIZE];

  if (strcmp(priv->remotecpu, rpmsg_get_cpuname(rdev)) == 0)
    {
      priv->ept.priv = priv;
      priv->ept.ns_bound_cb = rpmsgdev_ns_bound;
      snprintf(buf, sizeof(buf), "%s%s", RPMSGDEV_NAME_PREFIX,
               priv->remotepath);
      rpmsg_create_ept(&priv->ept, rdev, buf,
                       RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
                       rpmsgdev_ept_cb, NULL);
    }
}

/****************************************************************************
 * Name: rpmsgdev_device_destroy
 *
 * Description:
 *   Rpmsg device destroy function, this function will be called by rptun to
 *   destroy rpmsg-device end point.
 *
 * Parameters:
 *   rdev  - The rpmsg-device end point
 *   priv_ - Rpmsg-device handle
 *
 * Returned Values:
 *   None
 *
 ****************************************************************************/

static void rpmsgdev_device_destroy(FAR struct rpmsg_device *rdev,
                                    FAR void *priv_)
{
  FAR struct rpmsgdev_s *priv = priv_;

  if (strcmp(priv->remotecpu, rpmsg_get_cpuname(rdev)) == 0)
    {
      rpmsg_destroy_ept(&priv->ept);
    }
}

/****************************************************************************
 * Name: rpmsgdev_ept_cb
 *
 * Description:
 *   Rpmsg device end point callback function, this function will be called
 *   when receive the remote cpu message.
 *
 * Parameters:
 *   ept  - The rpmsg-device end point
 *   data - The received data
 *   len  - The received data length
 *   src  - unknow
 *   priv - unknow
 *
 * Returned Values:
 *   OK on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

static int rpmsgdev_ept_cb(FAR struct rpmsg_endpoint *ept,
                           FAR void *data, size_t len, uint32_t src,
                           FAR void *priv)
{
  FAR struct rpmsgdev_header_s *header = data;
  uint32_t command = header->command;

  if (command < nitems(g_rpmsgdev_handler))
    {
      return g_rpmsgdev_handler[command](ept, data, len, src, priv);
    }

  return -EINVAL;
}

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

/****************************************************************************
 * Name: rpmsgdev_register
 *
 * Description:
 *   Rpmsg-device client initialize function, the client cpu should call
 *   this function in the board initialize process.
 *
 * Parameters:
 *   remotecpu  - the server cpu name
 *   remotepath - the device you want to access in the remote cpu
 *   localpath  - the device path in local cpu, if NULL, the localpath is
 *                same as the remotepath, provide this argument to supoort
 *                custom device path
 *
 * Returned Values:
 *   OK on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

int rpmsgdev_register(FAR const char *remotecpu, FAR const char *remotepath,
                      FAR const char *localpath)
{
  FAR struct rpmsgdev_s *dev;
  int ret;

  /* Arguments check */

  if (remotecpu == NULL || remotepath == NULL)
    {
      return -EINVAL;
    }

  dev = kmm_zalloc(sizeof(*dev));
  if (dev == NULL)
    {
      return -ENOMEM;
    }

  /* Initialize the rpmsg device */

  dev->remotecpu  = remotecpu;
  dev->remotepath = remotepath;

  nxsem_init(&dev->wait, 0, 0);

  /* Register the rpmsg callback */

  ret = rpmsg_register_callback(dev,
                                rpmsgdev_device_created,
                                rpmsgdev_device_destroy,
                                NULL,
                                NULL);
  if (ret < 0)
    {
      rpmsgdeverr("register callback failed, ret=%d\n", ret);
      goto fail;
    }

  /* Register driver, using the remotepath if localpath is NULL */

  if (localpath == NULL)
    {
      localpath = remotepath;
    }

  ret = register_driver(localpath, &g_rpmsgdev_ops, 0666, dev);
  if (ret < 0)
    {
      rpmsgdeverr("register driver failed, ret=%d\n", ret);
      goto fail_with_rpmsg;
    }

  return OK;

fail_with_rpmsg:
  rpmsg_unregister_callback(dev,
                            rpmsgdev_device_created,
                            rpmsgdev_device_destroy,
                            NULL,
                            NULL);

fail:
  nxsem_destroy(&dev->wait);
  kmm_free(dev);

  return ret;
}