/****************************************************************************
 * drivers/rpmsg/rpmsg_router_edge.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 <nuttx/config.h>

#include <debug.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>

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

#include "rpmsg_router.h"

/****************************************************************************
 * Rpmsg-router Model:
 *
 *  +------+       +------+       +------+
 *  | edge |<----->| hub  |<----->| edge |
 *  +------+       +------+       +------+
 *
 * Description:
 *    edge CPUs (edge) are physically linked to the central router cpu (hub),
 *    edge CPUs' communication reply on hub cpu message forwarding.
 *
 ****************************************************************************/

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

#define rpmsg_router_edge_from_rdev(d) \
  container_of(d, struct rpmsg_router_edge_s, rdev)

#define RPMSG_ROUTER_EDGE_NAME_SIZE (2 * RPMSG_NAME_SIZE)

#define RPMSG_ROUTER_USER_NAME_SIZE \
  (RPMSG_NAME_SIZE - RPMSG_ROUTER_NAME_PREFIX_LEN - RPMSG_ROUTER_CPUNAME_LEN)

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

struct rpmsg_router_edge_s
{
  struct rpmsg_s      rpmsg;
  struct rpmsg_device rdev;
  struct rpmsg_device *hubdev;
  char                name[RPMSG_ROUTER_EDGE_NAME_SIZE];

  /* Tx/Rx buffer size */

  uint32_t            tx_len;
  uint32_t            rx_len;
};

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

static int
rpmsg_router_edge_get_rx_buffer_size(FAR struct rpmsg_device *rdev);
static int
rpmsg_router_edge_get_tx_buffer_size(FAR struct rpmsg_device *rdev);

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

static const struct rpmsg_ops_s g_rpmsg_router_edge_ops =
{
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
};

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

/****************************************************************************
 * Name: rpmsg_router_edge_get_tx_payload_buffer
 ****************************************************************************/

static FAR void *
rpmsg_router_edge_get_tx_payload_buffer(FAR struct rpmsg_device *rdev,
                                        FAR uint32_t *len, int wait,
                                        uint8_t priority)
{
  FAR struct rpmsg_router_edge_s *edge = rpmsg_router_edge_from_rdev(rdev);
  FAR struct rpmsg_device *hubdev = edge->hubdev;
  FAR void *buf;

  if (!hubdev->ops.get_tx_payload_buffer)
    {
      return NULL;
    }

  buf = hubdev->ops.get_tx_payload_buffer(hubdev, len, wait, priority);
  *len = edge->tx_len;
  return buf;
}

/****************************************************************************
 * Name: rpmsg_router_edge_hold_rx_buffer
 ****************************************************************************/

static void rpmsg_router_edge_hold_rx_buffer(FAR struct rpmsg_device *rdev,
                                             FAR void *rxbuf)
{
  FAR struct rpmsg_router_edge_s *edge = rpmsg_router_edge_from_rdev(rdev);
  FAR struct rpmsg_device *hubdev = edge->hubdev;

  if (!hubdev->ops.hold_rx_buffer)
    {
      return;
    }

  hubdev->ops.hold_rx_buffer(hubdev, rxbuf);
}

/****************************************************************************
 * Name: rpmsg_router_edge_release_rx_buffer
 ****************************************************************************/

static void
rpmsg_router_edge_release_rx_buffer(FAR struct rpmsg_device *rdev,
                                    FAR void *rxbuf)
{
  struct rpmsg_router_edge_s *edge = rpmsg_router_edge_from_rdev(rdev);
  struct rpmsg_device *hubdev = edge->hubdev;

  if (!hubdev->ops.release_rx_buffer)
    {
      return;
    }

  hubdev->ops.release_rx_buffer(hubdev, rxbuf);
}

/****************************************************************************
 * Name: rpmsg_router_edge_release_tx_buffer
 ****************************************************************************/

static int rpmsg_router_edge_release_tx_buffer(FAR struct rpmsg_device *rdev,
                                               FAR void *txbuf)
{
  struct rpmsg_router_edge_s *edge = rpmsg_router_edge_from_rdev(rdev);
  struct rpmsg_device *hubdev = edge->hubdev;

  if (!hubdev->ops.release_tx_buffer)
    {
      return RPMSG_ERR_PERM;
    }

  return hubdev->ops.release_tx_buffer(hubdev, txbuf);
}

/****************************************************************************
 * Name: rpmsg_router_edge_send_nocopy
 ****************************************************************************/

static int rpmsg_router_edge_send_nocopy(FAR struct rpmsg_device *rdev,
                                         uint32_t src, uint32_t dst,
                                         FAR const void *data, int len)
{
  struct rpmsg_router_edge_s *edge = rpmsg_router_edge_from_rdev(rdev);
  struct rpmsg_device *hubdev = edge->hubdev;

  if (!hubdev->ops.send_offchannel_nocopy)
    {
      return RPMSG_ERR_PARAM;
    }

  return hubdev->ops.send_offchannel_nocopy(hubdev, src, dst, data, len);
}

/****************************************************************************
 * Name: rpmsg_router_edge_get_rx_buffer_size
 ****************************************************************************/

static int
rpmsg_router_edge_get_rx_buffer_size(FAR struct rpmsg_device *rdev)
{
  FAR struct rpmsg_router_edge_s *edge = rpmsg_router_edge_from_rdev(rdev);

  return edge->rx_len;
}

/****************************************************************************
 * Name: rpmsg_router_edge_get_tx_buffer_size
 ****************************************************************************/

static int
rpmsg_router_edge_get_tx_buffer_size(FAR struct rpmsg_device *rdev)
{
  FAR struct rpmsg_router_edge_s *edge = rpmsg_router_edge_from_rdev(rdev);

  return edge->tx_len;
}

/****************************************************************************
 * Name: rpmsg_router_edge_cb
 *
 * Description:
 *   This is the callback function for edge core.
 *   It will receive data from real rpmsg channel by ept(r:cpu:name),
 *   and find the corresponding user ept, then processing data through
 *   the user ept callback.
 *
 * Parameters:
 *   ept - rpmsg_endpoint for communicating with router core (r:cpu:name)
 *   data - received data
 *   len - received data length
 *   src - source address
 *   priv - save user rpmsg_endpoint generally
 *
 * Returned Values:
 *   0 on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

static int rpmsg_router_edge_cb(FAR struct rpmsg_endpoint *ept,
                                FAR void *data, size_t len,
                                uint32_t src, FAR void *priv)
{
  FAR struct rpmsg_endpoint *usr_ept = priv;

  if (!usr_ept)
    {
      return 0;
    }

  /* Processing data through the user ept callback */

  return usr_ept->cb(usr_ept, data, len, src, usr_ept->priv);
}

/****************************************************************************
 * Name: rpmsg_router_edge_bound
 *
 * Description:
 *   This is the callback function for edge core.
 *   It will be called when the edge core is bound to the router core
 *   by r:cpu:name, save the destination address of the edge core, and
 *   call the bound function of the user endpoint.
 *
 * Parameters:
 *   ept - rpmsg_endpoint for communicating with router core (r:cpu:name)
 *
 ****************************************************************************/

static void rpmsg_router_edge_bound(FAR struct rpmsg_endpoint *ept)
{
  FAR struct rpmsg_endpoint *usr_ept = ept->priv;

  if (!usr_ept)
    {
      rpmsgerr("Try to get user ept failed.\n");
      return;
    }

  usr_ept->dest_addr = ept->dest_addr;
  if (usr_ept->ns_bound_cb)
    {
      usr_ept->ns_bound_cb(usr_ept);
    }
}

/****************************************************************************
 * Name: rpmsg_router_edge_unbind
 *
 * Description:
 *   This is the unbind callback function for edge core.
 *
 * Parameters:
 *   ept - rpmsg_endpoint for communicating with router core (r:cpu:name)
 *
 ****************************************************************************/

static void rpmsg_router_edge_unbind(FAR struct rpmsg_endpoint *ept)
{
  FAR struct rpmsg_endpoint *usr_ept = ept->priv;

  if (!usr_ept)
    {
      rpmsgerr("Try to get user ept failed.\n");
      return;
    }

  usr_ept->dest_addr = RPMSG_ADDR_ANY;
  if (usr_ept->ns_unbind_cb)
    {
      usr_ept->ns_unbind_cb(usr_ept);
    }
}

/****************************************************************************
 * Name: rpmsg_router_edge_release
 ****************************************************************************/

static void rpmsg_router_edge_release(FAR struct rpmsg_endpoint *ept)
{
  kmm_free(ept);
}

/****************************************************************************
 * Name: rpmsg_router_edge_send_offchannel_raw
 *
 * Description:
 *   This function sends normal rpmsg message or ns message to remote device.
 *   If the destination address is RPMSG_NS_EPT_ADDR, it will create a new
 *   endpoint(r:cpu:name) for real communication, and save the user endpoint
 *   information in the private field of the new endpoint.
 *
 * Parameters:
 *   rdev - rpmsg_device for router core
 *   src - source address
 *   dst - destination address
 *   data - data to send
 *   len - data length
 *   wait - boolean, wait or not for buffer to become available
 *   priority - message priority
 *
 * Returned Values:
 *   size of data sent or negative value for failure.
 *
 ****************************************************************************/

static int
rpmsg_router_edge_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)
{
  FAR struct rpmsg_router_edge_s *edge = rpmsg_router_edge_from_rdev(rdev);
  FAR struct rpmsg_ns_msg *ns_msg = (FAR struct rpmsg_ns_msg *)data;
  FAR struct rpmsg_device *hubdev = edge->hubdev;
  FAR struct rpmsg_endpoint *usr_ept;
  FAR struct rpmsg_endpoint *ept;
  char name[RPMSG_ROUTER_USER_NAME_SIZE];
  int ret;

  /* Send normal rpmsg "message" to remote device */

  if (dst != RPMSG_NS_EPT_ADDR)
    {
      if (!hubdev->ops.send_offchannel_raw)
        {
          return RPMSG_ERR_PARAM;
        }

      return hubdev->ops.send_offchannel_raw(hubdev, src,
                                             dst, data, len, wait, priority);
    }

  /* Try to get user ept firstly */

  metal_mutex_acquire(&rdev->lock);
  usr_ept = rpmsg_get_endpoint(rdev, ns_msg->name, src, dst);
  metal_mutex_release(&rdev->lock);
  if (!usr_ept)
    {
      rpmsgerr("Try to get user ept failed.\n");
      return RPMSG_ERR_PARAM;
    }

  /* Set hub endpoint name(r:cpu:name) for real communication */

  strlcpy(name, ns_msg->name, sizeof(name));
  snprintf(ns_msg->name, sizeof(ns_msg->name),
           RPMSG_ROUTER_NAME_PREFIX"%s:%s", edge->rpmsg.cpuname, name);

  if (ns_msg->flags == RPMSG_NS_DESTROY)
    {
      /* Processing RPMSG_NS_DESTROY message */

      metal_mutex_acquire(&hubdev->lock);
      ept = rpmsg_get_endpoint(hubdev, ns_msg->name,
                               RPMSG_ADDR_ANY, usr_ept->dest_addr);
      metal_mutex_release(&hubdev->lock);
      if (!ept)
        {
          rpmsgerr("Try to get router endpoint (r:ept) failed.\n");
          return RPMSG_ERR_PARAM;
        }

      /* Destroy endpoint(r:cpu:name) of real communication */

      rpmsg_destroy_ept(ept);
      return 0;
    }
  else
    {
      /* Processing RPMSG_NS_CREATE or RPMSG_NS_CREATE_ACK message */

      ept = kmm_zalloc(sizeof(*ept));
      if (!ept)
        {
          return -ENOMEM;
        }

      /* Save user endpoint */

      ept->priv = usr_ept;
      ept->ns_bound_cb = rpmsg_router_edge_bound;
      ept->release_cb = rpmsg_router_edge_release;

      /* Create endpoint (r:cpu:name) for real communication */

      ret = rpmsg_create_ept(ept, hubdev, ns_msg->name,
                             RPMSG_ADDR_ANY,
                             ns_msg->flags == RPMSG_NS_CREATE_ACK ?
                             usr_ept->dest_addr : RPMSG_ADDR_ANY,
                             rpmsg_router_edge_cb,
                             rpmsg_router_edge_unbind);
      if (ret < 0)
        {
          rpmsgerr("Create router endpoint failed: %d\n", ret);
          kmm_free(ept);
        }

      return ret;
    }
}

/****************************************************************************
 * Name: rpmsg_router_edge_match
 *
 * Description:
 *   This function is used to match the edge core device.
 *   rpmsg_router_edge_bind will be called if the device is matched.
 *
 * Parameters:
 *   rdev - real rpmsg device
 *   priv - rpmsg router device for edge core
 *   name - endpoint name (r:cpu:name)
 *   dest - destination address
 *
 * Returned Values:
 *   true on success; false on failure.
 *
 ****************************************************************************/

static bool rpmsg_router_edge_match(FAR struct rpmsg_device *rdev,
                                    FAR void *priv, FAR const char *name,
                                    uint32_t dest)
{
  FAR struct rpmsg_router_edge_s *edge = priv;

  if (strncmp(name, RPMSG_ROUTER_NAME_PREFIX, RPMSG_ROUTER_NAME_PREFIX_LEN))
    {
      return false;
    }

  return !strncmp(name + RPMSG_ROUTER_NAME_PREFIX_LEN, edge->rpmsg.cpuname,
                  strlen(edge->rpmsg.cpuname));
}

/****************************************************************************
 * Name: rpmsg_router_edge_bind
 *
 * Description:
 *   This function is used to bind the edge core device.
 *   It will try to find rpmsg_user_ns_bind_cb by user ept name.
 *
 * Parameters:
 *   rdev - real rpmsg device
 *   priv - rpmsg router device for edge core
 *   name - endpoint name (r:cpu:name)
 *   dest - destination address
 *
 ****************************************************************************/

static void rpmsg_router_edge_bind(FAR struct rpmsg_device *rdev,
                                   FAR void *priv, FAR const char *name,
                                   uint32_t dest)
{
  FAR struct rpmsg_router_edge_s *edge = priv;
  FAR struct rpmsg_device *edgedev = &edge->rdev;

  edgedev->ns_bind_cb(edgedev,
                      name + RPMSG_ROUTER_NAME_PREFIX_LEN +
                      strlen(edge->rpmsg.cpuname) + 1, dest);
}

/****************************************************************************
 * Name: rpmsg_router_edge_destroy
 *
 * Description:
 *   This function is used to destroy the edge core device.
 *
 * Parameters:
 *   edge - rpmsg router edge device
 *
 ****************************************************************************/

static void rpmsg_router_edge_destroy(FAR struct rpmsg_router_edge_s *edge)
{
  rpmsg_unregister_callback(edge, NULL, NULL,
                            rpmsg_router_edge_match,
                            rpmsg_router_edge_bind);
  rpmsg_unregister(edge->name, &edge->rpmsg);
  rpmsg_device_destory(&edge->rpmsg);
  kmm_free(edge);
}

/****************************************************************************
 * Name: rpmsg_router_edge_create
 *
 * Description:
 *   This function is used to create the edge core device.
 *
 * Parameters:
 *   hubdev - rpmsg device for router hub
 *   msg - sync message from router hub
 *   remotecpu - remote edge cpu name
 *
 * Returned Values:
 *   edge device on success; NULL on failure.
 *
 ****************************************************************************/

static FAR struct rpmsg_router_edge_s *
rpmsg_router_edge_create(FAR struct rpmsg_device *hubdev,
                         FAR struct rpmsg_router_s *msg,
                         FAR const char *remotecpu)
{
  FAR struct rpmsg_router_edge_s *edge;
  FAR struct rpmsg_device *rdev;
  int ret;

  /* Create the router edge device */

  edge = kmm_zalloc(sizeof(*edge));
  if (!edge)
    {
      return NULL;
    }

  strlcpy(edge->rpmsg.cpuname, remotecpu, RPMSG_ROUTER_CPUNAME_LEN);
  strlcpy(edge->rpmsg.local_cpuname, msg->cpuname, RPMSG_ROUTER_CPUNAME_LEN);
  edge->rx_len = msg->rx_len;
  edge->tx_len = msg->tx_len;
  edge->hubdev = hubdev;

  /* Initialize router rpmsg device */

  rdev = &edge->rdev;
  metal_mutex_init(&rdev->lock);
  rdev->ns_bind_cb = rpmsg_ns_bind;
  rdev->ns_unbind_cb = rpmsg_ns_unbind;
  rdev->ops.hold_rx_buffer = rpmsg_router_edge_hold_rx_buffer;
  rdev->ops.release_rx_buffer = rpmsg_router_edge_release_rx_buffer;
  rdev->ops.release_tx_buffer = rpmsg_router_edge_release_tx_buffer;
  rdev->ops.send_offchannel_nocopy = rpmsg_router_edge_send_nocopy;
  rdev->ops.send_offchannel_raw = rpmsg_router_edge_send_offchannel_raw;
  rdev->ops.get_tx_payload_buffer = rpmsg_router_edge_get_tx_payload_buffer;
  rdev->ops.get_rx_buffer_size = rpmsg_router_edge_get_rx_buffer_size;
  rdev->ops.get_tx_buffer_size = rpmsg_router_edge_get_tx_buffer_size;

  metal_list_init(&rdev->endpoints);
  rdev->support_ack = true;
  rdev->support_ns = true;

  /* Register rpmsg for edge core */

  snprintf(edge->name, sizeof(edge->name), "/dev/rpmsg/%s",
           edge->rpmsg.cpuname);
  ret = rpmsg_register(edge->name, &edge->rpmsg,
                       &g_rpmsg_router_edge_ops, 0);
  if (ret < 0)
    {
      rpmsgerr("rpmsg_register failed: %d\n", ret);
      goto free;
    }

  /* Register callback for edge core */

  ret = rpmsg_register_callback(edge, NULL, NULL,
                                rpmsg_router_edge_match,
                                rpmsg_router_edge_bind);

  if (ret < 0)
    {
      rpmsgerr("Register rpmsg callback failed: %d\n", ret);
      goto unregister;
    }

  /* Broadcast device_created to all registers */

  rpmsg_device_created(&edge->rpmsg);
  return edge;

unregister:
  rpmsg_unregister(edge->name, &edge->rpmsg);
free:
  kmm_free(edge);
  return NULL;
}

/****************************************************************************
 * Name: rpmsg_router_cb
 *
 * Description:
 *   This function is used to receive sync message from router core,
 *   and create or destroy the edge core device.
 *
 * Parameters:
 *   ept - endpoint for synchronizing ready messages
 *   data - received data
 *   len - received data length
 *   src - source address
 *   priv - private data
 *
 * Returned Values:
 *   0 on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

static int rpmsg_router_cb(FAR struct rpmsg_endpoint *ept,
                           FAR void *data, size_t len,
                           uint32_t src, FAR void *priv)
{
  FAR struct rpmsg_router_s *msg = data;
  FAR struct rpmsg_router_edge_s *edge;

  /* Destroy the router edge device
   * or set pm state for edge device
   */

  if (msg->cmd == RPMSG_ROUTER_DESTROY)
    {
      edge = ept->priv;

      if (edge)
        {
          rpmsg_router_edge_destroy(edge);
          ept->priv = NULL;
        }

      return 0;
    }
  else if (msg->cmd == RPMSG_ROUTER_SUSPEND ||
           msg->cmd == RPMSG_ROUTER_RESUME)
    {
      edge = ept->priv;
      if (edge == NULL)
        {
          return -EINVAL;
        }

      if (msg->cmd == RPMSG_ROUTER_SUSPEND)
        {
          rpmsg_modify_signals(&edge->rpmsg, 0, RPMSG_SIGNAL_RUNNING);
        }
      else
        {
          rpmsg_modify_signals(&edge->rpmsg, RPMSG_SIGNAL_RUNNING, 0);
        }

      return 0;
    }

  /* Create the router edge device */

  edge = rpmsg_router_edge_create(ept->rdev, msg,
                                  ept->name + RPMSG_ROUTER_NAME_LEN);
  if (!edge)
    {
      return -ENODEV;
    }

  ept->priv = edge;
  return 0;
}

/****************************************************************************
 * Name: rpmsg_router_unbind
 *
 * Description:
 *   This function is used to destroy the sync endpoint
 *   when another edge core is disconnected.
 *
 * Parameters:
 *   ept - rpmsg endpoint for synchronizing message.
 *
 ****************************************************************************/

static void rpmsg_router_unbind(FAR struct rpmsg_endpoint *ept)
{
  struct rpmsg_router_edge_s *edge = ept->priv;

  if (edge)
    {
      rpmsg_router_edge_destroy(edge);
      ept->priv = NULL;
    }

  rpmsg_destroy_ept(ept);
  kmm_free(ept);
}

/****************************************************************************
 * Name: rpmsg_router_match
 *
 * Description:
 *   This function is used to match the endpoint for
 *   synchronizing ready messages.
 *
 * Parameters:
 *   rdev - real rpmsg device
 *   priv - rpmsg_router_priv
 *   name - endpoint name
 *   dest - destination address
 *
 * Returned Values:
 *   true on success; false on failure.
 *
 ****************************************************************************/

static bool rpmsg_router_match(FAR struct rpmsg_device *rdev, FAR void *priv,
                               FAR const char *name, uint32_t dest)
{
  return !strncmp(name, RPMSG_ROUTER_NAME, RPMSG_ROUTER_NAME_LEN);
}

/****************************************************************************
 * Name: rpmsg_router_bind
 *
 * Description:
 *   This function is used to bind the endpoint for
 *   synchronizing ready messages.
 *
 * Parameters:
 *   rdev - real rpmsg device
 *   priv - private data
 *   name - endpoint name
 *   dest - destination address
 *
 ****************************************************************************/

static void rpmsg_router_bind(FAR struct rpmsg_device *rdev, FAR void *priv,
                              FAR const char *name, uint32_t dest)
{
  FAR struct rpmsg_endpoint *ept;
  int ret;

  ept = kmm_zalloc(sizeof(*ept));
  DEBUGASSERT(ept);

  ret = rpmsg_create_ept(ept, rdev, name, RPMSG_ADDR_ANY, dest,
                         rpmsg_router_cb, rpmsg_router_unbind);
  if (ret < 0)
    {
      rpmsgerr("Create router endpoint failed: %d\n", ret);
      kmm_free(ept);
    }
}

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

/****************************************************************************
 * Name: rpmsg_router_edge_init
 *
 * Description:
 *   This function is used to initialize the edge core.
 *
 * Returned Values:
 *   OK on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

int rpmsg_router_edge_init(void)
{
  /* Register callback for listening sync message from router hub */

  return rpmsg_register_callback(NULL, NULL, NULL,
                                 rpmsg_router_match,
                                 rpmsg_router_bind);
}