/****************************************************************************
 * drivers/reset/reset_rpmsg.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 <errno.h>
#include <debug.h>
#include <semaphore.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <sys/param.h>

#include <nuttx/kmalloc.h>
#include <nuttx/list.h>
#include <nuttx/rptun/openamp.h>
#include <nuttx/reset/reset.h>
#include <nuttx/reset/reset-controller.h>

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

#define RESET_RPMSG_EPT_NAME    "rpmsg-reset"

#define RESET_RPMSG_ACQUIRE      0
#define RESET_RPMSG_RELEASE      1
#define RESET_RPMSG_RESET        2
#define RESET_RPMSG_ASSERT       3
#define RESET_RPMSG_DEASSERT     4
#define RESET_RPMSG_STATUS       5

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

struct reset_rpmsg_cookie_s
{
  int32_t                        result;
  sem_t                          sem;
};

struct reset_rpmsg_server_s
{
  struct rpmsg_endpoint          ept;
  struct list_node               list;
  mutex_t                        lock;
};

struct reset_rpmsg_client_s
{
  struct rpmsg_endpoint          ept;
  struct list_node               node;
  sem_t                          sem;
  char                           cpuname[0];
};

begin_packed_struct struct reset_rpmsg_header_s
{
  uint32_t                       command : 31;
  uint32_t                       response : 1;
  int32_t                        result;
  uint64_t                       cookie;
} end_packed_struct;

begin_packed_struct struct rpmsg_reset_msg
{
  struct reset_rpmsg_header_s    header;
  int                            id;
  bool                           shared;
  bool                           acquired;
  char                           name[0];
} end_packed_struct;

struct reset_rpmsg_s
{
  FAR struct reset_control *rstc;
  struct list_node          node;
  unsigned int              count;
};

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

static int reset_rpmsg_acquire_handler(FAR struct rpmsg_endpoint *ept,
                                       FAR void *data, size_t len,
                                       uint32_t src, FAR void *priv);

static int reset_rpmsg_release_handler(FAR struct rpmsg_endpoint *ept,
                                       FAR void *data, size_t len,
                                       uint32_t src, FAR void *priv);

static int reset_rpmsg_reset_handler(FAR struct rpmsg_endpoint *ept,
                                     FAR void *data, size_t len,
                                     uint32_t src, FAR void *priv);

static int reset_rpmsg_assert_handler(FAR struct rpmsg_endpoint *ept,
                                      FAR void *data, size_t len,
                                      uint32_t src, FAR void *priv);

static int reset_rpmsg_deassert_handler(FAR struct rpmsg_endpoint *ept,
                                        FAR void *data, size_t len,
                                        uint32_t src, FAR void *priv);

static int reset_rpmsg_status_handler(FAR struct rpmsg_endpoint *ept,
                                      FAR void *data, size_t len,
                                      uint32_t src, FAR void *priv);

static int reset_rpmsg_acquire(FAR struct reset_controller_dev *rcdev,
                               unsigned int id, bool shared,
                               bool acquired);

static int reset_rpmsg_release(FAR struct reset_controller_dev *rcdev,
                               unsigned int id);

static int reset_rpmsg_reset(FAR struct reset_controller_dev *rcdev,
                             unsigned int id);

static int reset_rpmsg_assert(FAR struct reset_controller_dev *rcdev,
                              unsigned int id);

static int reset_rpmsg_deassert(FAR struct reset_controller_dev *rcdev,
                                unsigned int id);

static int reset_rpmsg_status(FAR struct reset_controller_dev *rcdev,
                              unsigned int id);

static int reset_rpmsg_ept_cb(FAR struct rpmsg_endpoint *ept,
                              FAR void *data, size_t len,
                              uint32_t src, FAR void *priv);

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

static mutex_t g_reset_rpmsg_lock = NXMUTEX_INITIALIZER;
static struct list_node g_reset_rpmsg_client =
                        LIST_INITIAL_VALUE(g_reset_rpmsg_client);

static const rpmsg_ept_cb g_reset_rpmsg_handler[] =
{
  [RESET_RPMSG_ACQUIRE]  = reset_rpmsg_acquire_handler,
  [RESET_RPMSG_RELEASE]  = reset_rpmsg_release_handler,
  [RESET_RPMSG_RESET]    = reset_rpmsg_reset_handler,
  [RESET_RPMSG_ASSERT]   = reset_rpmsg_assert_handler,
  [RESET_RPMSG_DEASSERT] = reset_rpmsg_deassert_handler,
  [RESET_RPMSG_STATUS]   = reset_rpmsg_status_handler,
};

static const struct reset_control_ops g_reset_rpmsg_ops =
{
  .acquire  = reset_rpmsg_acquire,
  .release  = reset_rpmsg_release,
  .reset    = reset_rpmsg_reset,
  .assert   = reset_rpmsg_assert,
  .deassert = reset_rpmsg_deassert,
  .status   = reset_rpmsg_status,
};

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

static void reset_rpmsg_client_created(FAR struct rpmsg_device *rdev,
                                       FAR void *priv)
{
  FAR struct reset_rpmsg_client_s *client = priv;

  if (client == NULL)
    {
      return;
    }

  if (strcmp(client->cpuname, rpmsg_get_cpuname(rdev)) == 0)
    {
      rpmsg_create_ept(&client->ept, rdev, RESET_RPMSG_EPT_NAME,
                       RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
                       reset_rpmsg_ept_cb, NULL);

      nxsem_post(&client->sem);
    }
}

static void reset_rpmsg_client_destroy(FAR struct rpmsg_device *rdev,
                                       FAR void *priv)
{
  FAR struct reset_rpmsg_client_s *client = priv;

  if (client == NULL)
    {
      return;
    }

  if (strcmp(client->cpuname, rpmsg_get_cpuname(rdev)) == 0)
    {
      nxsem_wait(&client->sem);
      rpmsg_destroy_ept(&client->ept);
    }
}

static FAR struct reset_rpmsg_client_s *
reset_rpmsg_get_client(FAR const char *name)
{
  FAR struct reset_rpmsg_client_s *client;
  FAR const char *slash = strchr(name, '/');

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

  nxmutex_lock(&g_reset_rpmsg_lock);

  list_for_every_entry(&g_reset_rpmsg_client, client,
                       struct reset_rpmsg_client_s, node)
    {
      if (strncmp(client->cpuname, name, slash - name) == 0)
        {
          goto out; /* Find the target, exit */
        }
    }

  client = kmm_zalloc(sizeof(*client) + slash - name + 1);
  if (client == NULL)
    {
      goto out;
    }

  memcpy(client->cpuname, name, slash - name);
  list_add_head(&g_reset_rpmsg_client, &client->node);
  nxmutex_unlock(&g_reset_rpmsg_lock);

  rpmsg_register_callback(client,
                          reset_rpmsg_client_created,
                          reset_rpmsg_client_destroy,
                          NULL,
                          NULL);
  return client;

out:
  nxmutex_unlock(&g_reset_rpmsg_lock);
  return client;
}

static struct rpmsg_endpoint *rpmsg_reset_get_ept(FAR const char **name)
{
  FAR struct reset_rpmsg_client_s *client;
  int ret = 0;

  client = reset_rpmsg_get_client(*name);
  if (client == NULL)
    {
      return NULL;
    }

  if (!is_rpmsg_ept_ready(&client->ept))
    {
      ret = nxsem_wait_uninterruptible(&client->sem);
      if (ret < 0)
        {
          return NULL;
        }

      nxsem_post(&client->sem);
    }

  *name += strlen(client->cpuname) + 1;
  return &client->ept;
}

static int reset_rpmsg_sendrecv(FAR struct rpmsg_endpoint *ept,
                                uint32_t command,
                                FAR struct reset_rpmsg_header_s *msg,
                                int len)
{
  struct reset_rpmsg_cookie_s cookie =
    {
      0
    };

  int ret;

  nxsem_init(&cookie.sem, 0, 0);
  msg->command = command;
  msg->response = 0;
  msg->result = -ENXIO;
  msg->cookie = (uintptr_t)&cookie;
  ret = rpmsg_send_nocopy(ept, msg, len);
  if (ret >= 0)
    {
      ret = nxsem_wait_uninterruptible(&cookie.sem);
      if (ret >= 0)
        {
          ret = cookie.result;
        }
    }
  else
    {
      rpmsg_release_tx_buffer(ept, msg);
    }

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

static int reset_rpmsg_acquire(FAR struct reset_controller_dev *rcdev,
                               unsigned int id, bool shared,
                               bool acquired)
{
  FAR struct rpmsg_endpoint *ept;
  FAR struct rpmsg_reset_msg *msg;
  FAR const char *name = rcdev->name;
  uint32_t len;

  ept = rpmsg_reset_get_ept(&name);
  if (ept == NULL)
    {
      return -ENODEV;
    }

  len = sizeof(*msg) + strlen(name) + 1;
  msg = rpmsg_get_tx_payload_buffer(ept, &len, true);
  if (msg == NULL)
    {
      return -ENOMEM;
    }

  /* We have to pass name, id, shared, acquired for the remote
   * to create rstc, after that only need to pass name and id.
   */

  strlcpy(msg->name, name, len - sizeof(*msg));
  msg->id = id;
  msg->shared = shared;
  msg->acquired = acquired;
  return reset_rpmsg_sendrecv(ept, RESET_RPMSG_ACQUIRE,
                              (struct reset_rpmsg_header_s *)msg,
                              len);
}

static int reset_rpmsg_release(FAR struct reset_controller_dev *rcdev,
                               unsigned int id)
{
  FAR struct rpmsg_endpoint *ept;
  FAR struct rpmsg_reset_msg *msg;
  FAR const char *name = rcdev->name;
  uint32_t len;

  ept = rpmsg_reset_get_ept(&name);
  if (ept == NULL)
    {
      return -ENODEV;
    }

  len = sizeof(*msg) + strlen(name) + 1;
  msg = rpmsg_get_tx_payload_buffer(ept, &len, true);
  if (msg == NULL)
    {
      return -ENOMEM;
    }

  strlcpy(msg->name, name, len - sizeof(*msg));
  msg->id = id;
  return reset_rpmsg_sendrecv(ept, RESET_RPMSG_RELEASE,
                              (struct reset_rpmsg_header_s *)msg,
                              len);
}

static int reset_rpmsg_reset(FAR struct reset_controller_dev *rcdev,
                             unsigned int id)
{
  FAR struct rpmsg_endpoint *ept;
  FAR struct rpmsg_reset_msg *msg;
  FAR const char *name = rcdev->name;
  uint32_t len;

  ept = rpmsg_reset_get_ept(&name);
  if (ept == NULL)
    {
      return -ENODEV;
    }

  len = sizeof(*msg) + strlen(name) + 1;
  msg = rpmsg_get_tx_payload_buffer(ept, &len, true);
  if (msg == NULL)
    {
      return -ENOMEM;
    }

  strlcpy(msg->name, name, len - sizeof(*msg));
  msg->id = id;
  return reset_rpmsg_sendrecv(ept, RESET_RPMSG_RESET,
                              (struct reset_rpmsg_header_s *)msg,
                              len);
}

static int reset_rpmsg_assert(FAR struct reset_controller_dev *rcdev,
                              unsigned int id)
{
  FAR struct rpmsg_endpoint *ept;
  FAR struct rpmsg_reset_msg *msg;
  FAR const char *name = rcdev->name;
  uint32_t len;

  ept = rpmsg_reset_get_ept(&name);
  if (ept == NULL)
    {
      return -ENODEV;
    }

  len = sizeof(*msg) + strlen(name) + 1;
  msg = rpmsg_get_tx_payload_buffer(ept, &len, true);
  if (msg == NULL)
    {
      return -ENOMEM;
    }

  strlcpy(msg->name, name, len - sizeof(*msg));
  msg->id = id;
  return reset_rpmsg_sendrecv(ept, RESET_RPMSG_ASSERT,
                              (struct reset_rpmsg_header_s *)msg,
                              len);
}

static int reset_rpmsg_deassert(FAR struct reset_controller_dev *rcdev,
                                unsigned int id)
{
  FAR struct rpmsg_endpoint *ept;
  FAR struct rpmsg_reset_msg *msg;
  FAR const char *name = rcdev->name;
  uint32_t len;

  ept = rpmsg_reset_get_ept(&name);
  if (ept == NULL)
    {
      return -ENODEV;
    }

  len = sizeof(*msg) + strlen(name) + 1;
  msg = rpmsg_get_tx_payload_buffer(ept, &len, true);
  if (msg == NULL)
    {
      return -ENOMEM;
    }

  strlcpy(msg->name, name, len - sizeof(*msg));
  msg->id = id;
  return reset_rpmsg_sendrecv(ept, RESET_RPMSG_DEASSERT,
                              (struct reset_rpmsg_header_s *)msg,
                              len);
}

static int reset_rpmsg_status(FAR struct reset_controller_dev *rcdev,
                              unsigned int id)
{
  FAR struct rpmsg_endpoint *ept;
  FAR struct rpmsg_reset_msg *msg;
  FAR const char *name = rcdev->name;
  uint32_t len;

  ept = rpmsg_reset_get_ept(&name);
  if (ept == NULL)
    {
      return -ENODEV;
    }

  len = sizeof(*msg) + strlen(name) + 1;
  msg = rpmsg_get_tx_payload_buffer(ept, &len, true);
  if (msg == NULL)
    {
      return -ENOMEM;
    }

  strlcpy(msg->name, name, len - sizeof(*msg));
  msg->id = id;
  return reset_rpmsg_sendrecv(ept, RESET_RPMSG_STATUS,
                              (struct reset_rpmsg_header_s *)msg,
                              len);
}

static FAR struct reset_control *
reset_rpmsg_find(FAR struct rpmsg_endpoint *ept,
                 FAR const char *name, unsigned int id)
{
  FAR struct reset_rpmsg_server_s *server = ept->priv;
  FAR struct reset_rpmsg_s *reset;

  nxmutex_lock(&server->lock);
  list_for_every_entry(&server->list, reset,
                       struct reset_rpmsg_s, node)
    {
      if (strcmp(reset->rstc->rcdev->name, name) == 0)
        {
          if (reset->rstc->id == id)
            {
              nxmutex_unlock(&server->lock);
              return reset->rstc;
            }
        }
    }

  nxmutex_unlock(&server->lock);
  return NULL;
}

static int reset_rpmsg_acquire_handler(FAR struct rpmsg_endpoint *ept,
                                       FAR void *data, size_t len,
                                       uint32_t src, FAR void *priv)
{
  FAR struct reset_rpmsg_server_s *server = ept->priv;
  FAR struct rpmsg_reset_msg *msg = data;
  FAR struct reset_control *rstc = NULL;
  FAR struct reset_rpmsg_s *reset;

  nxmutex_lock(&server->lock);
  list_for_every_entry(&server->list, reset,
                       struct reset_rpmsg_s, node)
    {
      if (strcmp(reset->rstc->rcdev->name, msg->name) == 0)
        {
          if (reset->rstc->id == msg->id)
            {
              reset->count++;
              nxmutex_unlock(&server->lock);
              rstc = reset->rstc;
              goto out;
            }
        }
    }

  reset = kmm_zalloc(sizeof(*reset));
  if (reset == NULL)
    {
      nxmutex_unlock(&server->lock);
      goto out;
    }

  reset->rstc = reset_control_get(msg->name, msg->id,
                                  msg->shared, msg->acquired);
  if (reset->rstc == NULL)
    {
      kmm_free(reset);
      nxmutex_unlock(&server->lock);
      goto out;
    }

  reset->count++;
  rstc = reset->rstc;
  list_add_head(&server->list, &reset->node);
  nxmutex_unlock(&server->lock);

out:
  msg->header.result = rstc ? 0 : -ENOENT;
  return rpmsg_send(ept, data, len);
}

static int reset_rpmsg_release_handler(FAR struct rpmsg_endpoint *ept,
                                       FAR void *data, size_t len,
                                       uint32_t src, FAR void *priv)
{
  FAR struct reset_rpmsg_server_s *server = ept->priv;
  FAR struct rpmsg_reset_msg *msg = data;
  FAR struct reset_rpmsg_s *reset;

  nxmutex_lock(&server->lock);
  list_for_every_entry(&server->list, reset,
                       struct reset_rpmsg_s, node)
    {
      if (strcmp(reset->rstc->rcdev->name, msg->name) == 0)
        {
          if (reset->rstc->id == msg->id)
            {
              msg->header.result = 0;
              if (--reset->count == 0)
                {
                  reset_control_put(reset->rstc);
                  list_delete(&reset->node);
                  kmm_free(reset);
                  break;
                }
            }
        }
    }

  nxmutex_unlock(&server->lock);
  return rpmsg_send(ept, data, len);
}

static int reset_rpmsg_reset_handler(FAR struct rpmsg_endpoint *ept,
                                     FAR void *data, size_t len,
                                     uint32_t src, FAR void *priv)
{
  FAR struct rpmsg_reset_msg *msg = data;
  FAR struct reset_control *rstc = reset_rpmsg_find(ept, msg->name, msg->id);

  msg->header.result = reset_control_reset(rstc);
  return rpmsg_send(ept, data, len);
}

static int reset_rpmsg_assert_handler(FAR struct rpmsg_endpoint *ept,
                                      FAR void *data, size_t len,
                                      uint32_t src, FAR void *priv)
{
  FAR struct rpmsg_reset_msg *msg = data;
  FAR struct reset_control *rstc = reset_rpmsg_find(ept, msg->name, msg->id);

  msg->header.result = reset_control_assert(rstc);
  return rpmsg_send(ept, data, len);
}

static int reset_rpmsg_deassert_handler(FAR struct rpmsg_endpoint *ept,
                                        FAR void *data, size_t len,
                                        uint32_t src, FAR void *priv)
{
  FAR struct rpmsg_reset_msg *msg = data;
  FAR struct reset_control *rstc = reset_rpmsg_find(ept, msg->name, msg->id);

  msg->header.result = reset_control_deassert(rstc);
  return rpmsg_send(ept, data, len);
}

static int reset_rpmsg_status_handler(FAR struct rpmsg_endpoint *ept,
                                      FAR void *data, size_t len,
                                      uint32_t src, FAR void *priv)
{
  FAR struct rpmsg_reset_msg *msg = data;
  FAR struct reset_control *rstc = reset_rpmsg_find(ept, msg->name, msg->id);

  msg->header.result = reset_control_status(rstc);
  return rpmsg_send(ept, data, len);
}

static int reset_rpmsg_ept_cb(FAR struct rpmsg_endpoint *ept,
                              FAR void *data, size_t len,
                              uint32_t src, FAR void *priv)
{
  FAR struct reset_rpmsg_header_s *header = data;
  uint32_t cmd = header->command;
  int ret = -EINVAL;

  struct reset_rpmsg_cookie_s *cookie =
         (struct reset_rpmsg_cookie_s *)(uintptr_t)header->cookie;

  if (cookie && header->response)
    {
      cookie->result = header->result;
      nxsem_post(&cookie->sem);
      ret = 0;
    }
  else if (cmd < nitems(g_reset_rpmsg_handler)
           && g_reset_rpmsg_handler[cmd])
    {
      header->response = 1;
      ret = g_reset_rpmsg_handler[cmd](ept, data, len, src, priv);
    }

  return ret;
}

static void reset_rpmsg_server_unbind(FAR struct rpmsg_endpoint *ept)
{
  FAR struct reset_rpmsg_server_s *server = ept->priv;
  FAR struct reset_rpmsg_s *reset;
  FAR struct reset_rpmsg_s *tmp;

  list_for_every_entry_safe(&server->list, reset, tmp,
                            struct reset_rpmsg_s, node)
    {
      reset_control_put(reset->rstc);
      list_delete(&reset->node);
      kmm_free(reset);
    }

  rpmsg_destroy_ept(ept);
  nxmutex_destroy(&server->lock);
  kmm_free(server);
}

static bool reset_rpmsg_server_match(FAR struct rpmsg_device *rdev,
                                     FAR void *priv,
                                     FAR const char *name,
                                     uint32_t dest)
{
  return strcmp(name, RESET_RPMSG_EPT_NAME) == 0;
}

static void reset_rpmsg_server_bind(FAR struct rpmsg_device *rdev,
                                    FAR void *priv,
                                    FAR const char *name,
                                    uint32_t dest)
{
  FAR struct reset_rpmsg_server_s *server;

  server = kmm_zalloc(sizeof(struct reset_rpmsg_server_s));
  if (server == NULL)
    {
      return;
    }

  server->ept.priv = server;
  list_initialize(&server->list);
  nxmutex_init(&server->lock);

  rpmsg_create_ept(&server->ept, rdev, name,
                   RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
                   reset_rpmsg_ept_cb,
                   reset_rpmsg_server_unbind);
}

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

/****************************************************************************
 * Name: reset_rpmsg_get
 *
 * Description:
 *
 * Input Parameters:
 *
 *   name - the name of the remote reset controller
 *
 * Returned Value:
 *
 *   Reset controller pointer
 *
 ****************************************************************************/

FAR struct reset_controller_dev *reset_rpmsg_get(FAR const char *name)
{
  FAR struct reset_controller_dev *rcdev;
  size_t len = strlen(name) + 1;
  int ret;

  rcdev = kmm_zalloc(sizeof(struct reset_controller_dev) + len);
  if (rcdev == NULL)
    {
      return NULL;
    }

  rcdev->name = (FAR char *)(rcdev + 1);
  memcpy((FAR char *)rcdev->name, name, len);
  rcdev->ops = &g_reset_rpmsg_ops;

  ret = reset_controller_register(rcdev);
  if (ret < 0)
    {
      kmm_free(rcdev);
      return NULL;
    }

  return rcdev;
}

/****************************************************************************
 * Name: reset_rpmsg_server_init
 *
 * Description:
 *
 *   Establish rpmsg channel for the operations of the
 *   remote reset controller
 *
 * Input Parameters:
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

int reset_rpmsg_server_init(void)
{
  return rpmsg_register_callback(NULL,
                                 NULL,
                                 NULL,
                                 reset_rpmsg_server_match,
                                 reset_rpmsg_server_bind);
}