/****************************************************************************
 * drivers/modem/alt1250/alt1250.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 <nuttx/config.h>

#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <poll.h>
#include <errno.h>
#include <nuttx/wireless/lte/lte_ioctl.h>
#include <nuttx/modem/alt1250.h>
#include <assert.h>

#include "altcom_pkt.h"
#include "altcom_hdlr.h"
#include "altmdm.h"

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

#define WRITE_OK 0
#define WRITE_NG 1

#define rel_evtbufinst(inst, dev) unlock_evtbufinst(inst, dev)

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

/* Character driver methods. */

static int alt1250_open(FAR struct file *filep);
static int alt1250_close(FAR struct file *filep);
static ssize_t alt1250_read(FAR struct file *filep, FAR char *buffer,
                            size_t len);
static int alt1250_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
static int alt1250_poll(FAR struct file *filep, FAR struct pollfd *fds,
                        bool setup);

parse_handler_t alt1250_additional_parsehdlr(uint16_t, uint8_t);
compose_handler_t alt1250_additional_composehdlr(uint32_t, FAR uint8_t *,
                                                 size_t);

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

/* This the vtable that supports the character driver interface. */

static const struct file_operations g_alt1250fops =
{
  alt1250_open,  /* open */
  alt1250_close, /* close */
  alt1250_read,  /* read */
  NULL,          /* write */
  NULL,          /* seek */
  alt1250_ioctl, /* ioctl */
  alt1250_poll,  /* poll */
};
static uint8_t g_recvbuff[ALTCOM_RX_PKT_SIZE_MAX];
static uint8_t g_sendbuff[ALTCOM_PKT_SIZE_MAX];

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

/****************************************************************************
 * Name: add_list
 ****************************************************************************/

static void add_list(FAR struct alt_queue_s *head,
                     FAR struct alt_container_s *list)
{
  FAR struct alt_container_s *next;

  nxmutex_lock(&head->lock);

  while (list != NULL)
    {
      next = (FAR struct alt_container_s *)sq_next(&list->node);

      sq_next(&list->node) = NULL;
      sq_addlast(&list->node, &head->queue);

      list = next;
    }

  nxmutex_unlock(&head->lock);
}

/****************************************************************************
 * Name: remove_list_all
 ****************************************************************************/

static FAR struct alt_container_s *remove_list_all(
  FAR struct alt_queue_s *head)
{
  FAR struct alt_container_s *list;

  nxmutex_lock(&head->lock);

  list = (FAR struct alt_container_s *)sq_peek(&head->queue);
  sq_init(&head->queue);

  nxmutex_unlock(&head->lock);

  return list;
}

/****************************************************************************
 * Name: remove_list
 ****************************************************************************/

static FAR struct alt_container_s *remove_list(FAR struct alt_queue_s *head,
                                            uint16_t cmdid, uint16_t transid)
{
  FAR struct alt_container_s *list;

  nxmutex_lock(&head->lock);

  list = (FAR struct alt_container_s *)sq_peek(&head->queue);
  while (list != NULL)
    {
      if ((list->altcid == cmdid) && (list->alttid == transid))
        {
          sq_rem(&list->node, &head->queue);
          sq_next(&list->node) = NULL;
          break;
        }

      list = (FAR struct alt_container_s *)sq_next(&list->node);
    }

  nxmutex_unlock(&head->lock);

  return list;
}

/****************************************************************************
 * Name: set_senddisable
 ****************************************************************************/

static void set_senddisable(FAR struct alt1250_dev_s *dev, bool disable)
{
  nxmutex_lock(&dev->senddisablelock);

  dev->senddisable = disable;

  nxmutex_unlock(&dev->senddisablelock);
}

/****************************************************************************
 * Name: is_senddisable
 ****************************************************************************/

static bool is_senddisable(FAR struct alt1250_dev_s *dev)
{
  bool disable;

  nxmutex_lock(&dev->senddisablelock);

  disable = dev->senddisable;

  nxmutex_unlock(&dev->senddisablelock);

  return disable;
}

/****************************************************************************
 * Name: read_evtbitmap
 ****************************************************************************/

static ssize_t read_data(FAR struct alt1250_dev_s *dev,
                         FAR struct alt_readdata_s *rdata)
{
  int idx;

  nxmutex_lock(&dev->evtmaplock);

  /* change status to NOT WRITABLE */

  for (idx = 0; idx < (sizeof(uint64_t) * 8); idx++)
    {
      if ((dev->evtbitmap & (1ULL << idx)) != 0)
        {
          if (dev->evtbuff->ninst >= idx)
            {
              FAR alt_evtbuf_inst_t *inst = &dev->evtbuff->inst[idx];

              nxmutex_lock(&inst->stat_lock);

              inst->stat = ALTEVTBUF_ST_NOTWRITABLE;

              nxmutex_unlock(&inst->stat_lock);
            }
        }
    }

  rdata->evtbitmap = dev->evtbitmap;
  rdata->head = remove_list_all(&dev->replylist);

  if (dev->evtbitmap & ALT1250_EVTBIT_RESET)
    {
      /* Resume sending because daemon has been notified of the reset
       * reliably.
       */

      set_senddisable(dev, false);
    }

  dev->evtbitmap = 0ULL;

  nxmutex_unlock(&dev->evtmaplock);

  return sizeof(struct alt_readdata_s);
}

/****************************************************************************
 * Name: write_evtbitmap
 ****************************************************************************/

static void write_evtbitmap(FAR struct alt1250_dev_s *dev,
                            uint64_t bitmap)
{
  nxmutex_lock(&dev->evtmaplock);

  dev->evtbitmap |= bitmap;

  if ((dev->evtbitmap & ALT1250_EVTBIT_RESET) != 0)
    {
      dev->evtbitmap = ALT1250_EVTBIT_RESET;
    }

  m_info("write bitmap: 0x%llx\n", bitmap);

  nxmutex_unlock(&dev->evtmaplock);
}

/****************************************************************************
 * Name: write_evtbitmapwithlist
 ****************************************************************************/

static void write_evtbitmapwithlist(FAR struct alt1250_dev_s *dev,
  uint64_t bitmap, FAR struct alt_container_s *container)
{
  nxmutex_lock(&dev->evtmaplock);

  dev->evtbitmap |= bitmap;

  if (dev->evtbitmap & ALT1250_EVTBIT_RESET)
    {
      dev->evtbitmap = ALT1250_EVTBIT_RESET;
    }

  add_list(&dev->replylist, container);

  nxmutex_unlock(&dev->evtmaplock);
}

/****************************************************************************
 * Name: is_evtbitmap_avail
 ****************************************************************************/

static bool is_evtbitmap_avail(FAR struct alt1250_dev_s *dev)
{
  bool ret;

  nxmutex_lock(&dev->evtmaplock);

  /* 0 means it is not available, otherwise it is available. */

  ret = (0ULL != dev->evtbitmap);

  nxmutex_unlock(&dev->evtmaplock);

  return ret;
}

/****************************************************************************
 * Name: add_evtbuff
 ****************************************************************************/

static void add_evtbuff(FAR struct alt1250_dev_s *dev,
                        FAR struct alt_evtbuffer_s *buff)
{
  dev->evtbuff = buff;
}

/****************************************************************************
 * Name: write_evtbuff_byidx
 ****************************************************************************/

static int write_evtbuff_byidx(FAR struct alt1250_dev_s *dev,
  uint64_t idx, CODE void(*write_func)(FAR void *outp[], FAR void *inp),
  FAR void *inp)
{
  int ret = WRITE_NG;

  nxmutex_lock(&dev->evtmaplock);

  if (dev->evtbuff)
    {
      if (dev->evtbuff->ninst >= idx)
        {
          FAR alt_evtbuf_inst_t *inst = &dev->evtbuff->inst[idx];

          nxmutex_lock(&inst->stat_lock);
          if (inst->stat == ALTEVTBUF_ST_WRITABLE)
            {
              write_func(inst->outparam, inp);
              dev->evtbitmap |= (1ULL << idx);
              ret = WRITE_OK;
            }

          nxmutex_unlock(&inst->stat_lock);
        }
    }

  nxmutex_unlock(&dev->evtmaplock);

  return ret;
}

/****************************************************************************
 * Name: lock_evtbuffinst
 ****************************************************************************/

static void lock_evtbuffinst(FAR alt_evtbuf_inst_t *inst,
                             FAR struct alt1250_dev_s *dev)
{
  nxmutex_lock(&dev->evtmaplock);
  nxmutex_lock(&inst->stat_lock);
}

/****************************************************************************
 * Name: unlock_evtbufinst
 ****************************************************************************/

static void unlock_evtbufinst(FAR alt_evtbuf_inst_t *inst,
                              FAR struct alt1250_dev_s *dev)
{
  nxmutex_unlock(&inst->stat_lock);
  nxmutex_unlock(&dev->evtmaplock);
}

/****************************************************************************
 * Name: search_evtbufinst
 ****************************************************************************/

static FAR alt_evtbuf_inst_t *search_evtbufinst(uint16_t cid,
  FAR uint64_t *bitmap, FAR struct alt1250_dev_s *dev)
{
  FAR alt_evtbuf_inst_t *ret = NULL;
  unsigned int i;

  *bitmap = 0ULL;

  for (i = 0; i < dev->evtbuff->ninst; i++)
    {
      ret = &dev->evtbuff->inst[i];

      if (ret->altcid == cid)
        {
          *bitmap = 1ULL << i;
          return ret;
        }
    }

  return NULL;
}

/****************************************************************************
 * Name: cid_to_searchable
 ****************************************************************************/

static uint16_t cid_to_searchable(uint16_t cid, uint8_t altver)
{
  uint16_t cidv1;

  cid &= ~ALTCOM_CMDID_REPLY_BIT;
  if (altver == ALTCOM_VER4)
    {
      /* Change the command ID to Version 1
       * Even if it cannot be converted, try to search the table
       * using the original command ID.
       */

      cidv1 = convert_cid2v1(cid);
      if (cidv1 != APICMDID_UNKNOWN)
        {
          cid = cidv1;
        }
    }

  return cid;
}

/****************************************************************************
 * Name: get_bitmap
 ****************************************************************************/

static uint64_t get_bitmap(FAR struct alt1250_dev_s *dev, uint16_t cid,
                           uint8_t altver)
{
  uint64_t bitmap = 0ULL;

  cid = cid_to_searchable(cid, altver);

  search_evtbufinst(cid, &bitmap, dev);

  return bitmap;
}

/****************************************************************************
 * Name: get_evtbuffinst_withlock
 ****************************************************************************/

static FAR alt_evtbuf_inst_t *get_evtbuffinst_withlock(
  FAR struct alt1250_dev_s *dev, uint16_t cid, uint8_t altver,
  FAR uint64_t *bitmap)
{
  FAR alt_evtbuf_inst_t *inst = NULL;
  FAR alt_evtbuf_inst_t *ret = NULL;

  cid = cid_to_searchable(cid, altver);

  if (cid == APICMDID_SOCK_SELECT)
    {
      ret = &dev->select_inst;

      lock_evtbuffinst(ret, dev);

      ret->outparam = dev->select_container->outparam;
      ret->outparamlen = dev->select_container->outparamlen;

      search_evtbufinst(cid, bitmap, dev);
    }
  else
    {
      inst = search_evtbufinst(cid, bitmap, dev);
      if (inst)
        {
          lock_evtbuffinst(inst, dev);

          if (inst->stat == ALTEVTBUF_ST_WRITABLE)
            {
              ret = inst;
            }
          else
            {
              unlock_evtbufinst(inst, dev);
            }
        }
    }

  return ret;
}

/****************************************************************************
 * Name: write_restart_param
 ****************************************************************************/

static void write_restart_param(FAR void *outp[], FAR void *buff)
{
  FAR int *out_reason = (FAR int *)outp[0];
  FAR int *in_reason = (FAR int *)buff;

  *out_reason = *in_reason;
}

/****************************************************************************
 * Name: pollnotify
 ****************************************************************************/

static void pollnotify(FAR struct alt1250_dev_s *dev)
{
  nxmutex_lock(&dev->pfdlock);

  if (dev->pfd != NULL)
    {
      /* If poll() waits, notify  */

      poll_notify(&dev->pfd, 1, POLLIN);
    }

  nxmutex_unlock(&dev->pfdlock);
}

/****************************************************************************
 * Name: get_composehdlr
 ****************************************************************************/

compose_handler_t get_composehdlr(uint32_t cmdid, FAR uint8_t *payload,
                                  size_t size)
{
  compose_handler_t ret;

  ret = alt1250_composehdlr(cmdid);

#ifdef CONFIG_MODEM_ALT1250_ADDITIONAL_FUNC
  if (ret == NULL)
    {
      ret = alt1250_additional_composehdlr(cmdid, payload, size);
    }
#endif

  return ret;
}

/****************************************************************************
 * Name: get_parsehdlr
 ****************************************************************************/

parse_handler_t get_parsehdlr(uint16_t altcid, uint8_t altver)
{
  parse_handler_t ret;

  ret = alt1250_parsehdlr(altcid, altver);

#ifdef CONFIG_MODEM_ALT1250_ADDITIONAL_FUNC
  if (ret == NULL)
    {
      ret = alt1250_additional_parsehdlr(altcid, altver);
    }
#endif

  return ret;
}

/****************************************************************************
 * Name: alt1250_power_control
 ****************************************************************************/

static int alt1250_power_control(FAR struct alt1250_dev_s *dev,
                                 FAR struct alt_power_s *req)
{
  int ret = OK;

  switch (req->cmdid)
    {
      case LTE_CMDID_POWERON:
        ret = altmdm_poweron();
        break;

      case LTE_CMDID_POWEROFF:
        ret = altmdm_poweroff();
        break;

      case LTE_CMDID_TAKEWLOCK:
        ret = altmdm_take_wlock();
        break;

      case LTE_CMDID_GIVEWLOCK:
        ret = altmdm_give_wlock();
        break;

      default:
        ret = -EINVAL;
        break;
    }

  return ret;
}

/****************************************************************************
 * Name: make_altcomcmd_and_send
 ****************************************************************************/

static int make_altcomcmd_and_send(FAR struct alt1250_dev_s *dev,
                                   FAR alt_container_t *req)
{
  int ret = OK;
  compose_handler_t handler;
  uint8_t altver;
  uint16_t cid;
  uint16_t tid;
  FAR uint8_t *payload;
  int remainlen;
  int pos;

  m_info("send request: command ID=0x%08lx\n", req->cmdid);

  payload = get_payload((FAR struct altcom_cmdhdr_s *)g_sendbuff);

  handler = get_composehdlr(req->cmdid & ~LTE_CMDOPT_ASYNC_BIT, payload,
                            ALTCOM_PAYLOAD_SIZE_MAX);
  if (handler)
    {
      altver = altmdm_get_protoversion();
      if ((altver == ALTCOM_VERX) || is_senddisable(dev))
        {
          ret = -ENETDOWN;
        }
      else
        {
          ret = handler(req->inparam, req->inparamlen, altver, payload,
                        ALTCOM_PAYLOAD_SIZE_MAX, &cid);

          ret = (ret > ALTCOM_PAYLOAD_SIZE_MAX) ? -ENOSPC : ret;

          if (ret >= 0)
            {
              tid = altcom_make_header(
                (FAR struct altcom_cmdhdr_s *)g_sendbuff,
                altver, cid, ret);

              req->altcid = cid | ALTCOM_CMDID_REPLY_BIT;
              req->alttid = tid;

              if (req->outparam != NULL)
                {
                  add_list(&dev->waitlist, req);
                }

              remainlen = get_pktlen(altver, (uint16_t)ret);
              pos = 0;

              /* If the modem sleeps during the split transmission,
               * the receive buffer of the modem will be cleared.
               * Therefore, split packets sent before sleep will be
               * discarded. To avoid this, do not enter sleep state
               * until all the packets have been sent.
               */

              altmdm_take_wlock();

              do
                {
                  ret = altmdm_write(&g_sendbuff[pos], remainlen);
                  if (ret < 0)
                    {
                      break;
                    }
                  else
                    {
                      m_info(
                        "write success: size=%d, cid=0x%04x tid=0x%04x\n",
                        ret, cid, tid);
                      remainlen -= ret;
                      pos += ret;
                    }
                }
              while (remainlen > 0);

              altmdm_give_wlock();

              if (ret < 0)
                {
                  m_err("altmdm_write() failed: %d\n", ret);
                  ret = -ENETDOWN;

                  /* If the container is not left in the waitlist,
                   * it has already been processed by the recvthread.
                   * ENETRESET is returned to the caller to indicate that
                   * the container has been processed.
                   */

                  if ((req->outparam != NULL) && (remove_list(&dev->waitlist,
                       req->altcid, req->alttid) == NULL))
                    {
                      ret = -ENETRESET;
                    }
                }
              else
                {
                  m_info("write success: size=%d, cid=0x%04x tid=0x%04x\n",
                         ret, cid, tid);
                  ret = OK;
                }
            }
          else
            {
              m_err("handler() failed: %d\n", ret);
            }
        }
    }
  else
    {
      ret = -ENOSYS;
    }

  return ret;
}

/****************************************************************************
 * Name: exchange_selectcontainer
 ****************************************************************************/

static int exchange_selectcontainer(FAR struct alt1250_dev_s *dev,
                                    FAR alt_container_t **container)
{
  FAR alt_container_t *newcontainer;

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

  nxmutex_lock(&dev->select_inst.stat_lock);

  newcontainer = *container;
  *container = dev->select_container;
  dev->select_container = newcontainer;

  nxmutex_unlock(&dev->select_inst.stat_lock);

  return OK;
}

/****************************************************************************
 * Name: altcom_recvthread
 ****************************************************************************/

static void altcom_recvthread(FAR void *arg)
{
  int ret;
  FAR struct alt1250_dev_s *dev = (FAR struct alt1250_dev_s *)arg;
  bool is_running = true;
  FAR struct alt_container_s *head;
  FAR struct alt_container_s *container;
  uint16_t cid;
  uint16_t tid;
  uint8_t altver;
  parse_handler_t handler;
  uint64_t bitmap = 0ULL;
  int recvedlen = 0;

  m_info("recv thread start\n");

  altmdm_init(dev->spi, dev->lower);

  while (is_running)
    {
      ret = altmdm_read(g_recvbuff + recvedlen,
                        ALTCOM_RX_PKT_SIZE_MAX - recvedlen);

      /* Normal packet received */

      if (ret >= 0)
        {
          m_info("read packet %d bytes\n", ret);

          recvedlen += ret;

          ret = altcom_is_pkt_ok(g_recvbuff, recvedlen);
          if (ret > 0)
            {
              /* Cases in which fragmented packets are received.
               * Therefore, the receive process is performed again.
               */

              m_info("This is fragmented packet received. remain len: %d\n",
                     ret);
              continue;
            }

          if (ret < 0)
            {
              /* Forced reset of modem due to packet format error detected */

              m_err("[altcom] Forced modem reset due to parse failure\n");

              altmdm_reset();
            }
          else
            {
              bool is_discard = false;

              /* parse ALTCOM command ID and transaction ID from header */

              cid = parse_cid((FAR struct altcom_cmdhdr_s *)g_recvbuff);
              tid = parse_tid((FAR struct altcom_cmdhdr_s *)g_recvbuff);
              altver = get_altver(
                    (FAR struct altcom_cmdhdr_s *)g_recvbuff);

              m_info("receive cid:0x%04x tid:0x%04x\n", cid, tid);

              /* Is error indication packet?
               * This packet is a response to a command that is not supported
               * by the ALT1250. The header of the request packet is included
               * in the contents of the this packet.
               */

              if (is_errind(cid))
                {
                  /* Get ALTCOM command ID and transaction ID
                   * from error indication packet
                   */

                  cid = parse_cid4errind(
                    (FAR struct altcom_cmdhdr_s *)g_recvbuff);
                  tid = parse_tid4errind(
                    (FAR struct altcom_cmdhdr_s *)g_recvbuff);

                  m_info("receive errind cid:0x%04x tid:0x%04x\n", cid, tid);

                  container = remove_list(&dev->waitlist, cid, tid);
                  if (container != NULL)
                    {
                      /* It means that requested command not implemented
                       * by modem
                       */

                      container->result = -ENOSYS;
                    }
                  else
                    {
                      /* Discard the event packet */

                      is_discard = true;

                      m_warn("container is not found\n");
                    }
                }
              else
                {
                  container = remove_list(&dev->waitlist, cid, tid);

                  handler = get_parsehdlr(cid, altver);
                  if (handler)
                    {
                      FAR uint8_t *payload = get_payload(
                        (FAR struct altcom_cmdhdr_s *)g_recvbuff);

                      if (container)
                        {
                          m_info("handler and container is found\n");

                          bitmap = get_bitmap(dev, cid, altver);

                          /* Perform parse handler */

                          container->result = handler(dev, payload,
                            get_payload_len(
                              (FAR struct altcom_cmdhdr_s *)g_recvbuff),
                            altver, container->outparam,
                            container->outparamlen, &bitmap);
                        }
                      else
                        {
                          FAR alt_evtbuf_inst_t *inst;

                          m_warn("container is not found\n");

                          /* If the state of the instance is NotWritable,
                           * instanse will be returned as NULL.
                           */

                          inst = get_evtbuffinst_withlock(dev, cid, altver,
                                                          &bitmap);
                          if (inst)
                            {
                              /* Perform parse handler */

                              ret = handler(dev, payload, get_payload_len(
                                (FAR struct altcom_cmdhdr_s *)g_recvbuff),
                                altver, inst->outparam, inst->outparamlen,
                                &bitmap);

                              unlock_evtbufinst(inst, dev);

                              if (ret >= 0)
                                {
                                  write_evtbitmap(dev, bitmap);
                                }
                              else
                                {
                                  /* Discard the event packet */

                                  is_discard = true;
                                }
                            }
                          else
                            {
                              /* Discard the event packet */

                              is_discard = true;
                            }
                        }
                    }
                  else if (container)
                    {
                      container->result = -ENOSYS;
                      m_warn("handler is not found\n");
                    }
                  else
                    {
                      /* Discard the event packet */

                      is_discard = true;

                      m_warn("container and handler is not found\n");
                    }
                }

              if (container)
                {
                  if (container->cmdid & LTE_CMDOPT_ASYNC_BIT)
                    {
                      bitmap |= ALT1250_EVTBIT_REPLY;
                    }
                  else
                    {
                      bitmap = ALT1250_EVTBIT_REPLY;
                    }

                  write_evtbitmapwithlist(dev, bitmap, container);
                }

              if (is_discard)
                {
                  dev->discardcnt++;
                  m_err("discard event %lu\n", dev->discardcnt);
                }
              else
                {
                  pollnotify(dev);
                }
            }
        }
      else
        {
          switch (ret)
            {
              case ALTMDM_RETURN_RESET_PKT:
                {
                  m_info("recieve ALTMDM_RETURN_RESET_PKT\n");
                  set_senddisable(dev, true);
                }
                break;

              case ALTMDM_RETURN_RESET_V1:
              case ALTMDM_RETURN_RESET_V4:
                {
                  uint32_t reason = altmdm_get_reset_reason();

                  m_info("recieve ALTMDM_RETURN_RESET_V1/V4\n");

                  ret = write_evtbuff_byidx(dev, 0, write_restart_param,
                    (FAR void *)&reason);

                  /* If there is a waiting list,
                   * replace it with the replay list.
                   */

                  head = remove_list_all(&dev->waitlist);

                  write_evtbitmapwithlist(dev, ALT1250_EVTBIT_RESET, head);
                  pollnotify(dev);
                }
                break;

              case ALTMDM_RETURN_EXIT:
                {
                  m_info("recieve ALTMDM_RETURN_EXIT\n");
                  is_running = false;
                }
                break;

              default:
                DEBUGASSERT(0);
                break;
            }
        }

      recvedlen = 0;
    }

  m_info("recv thread end\n");

  pthread_exit(0);
}

/****************************************************************************
 * Name: alt1250_open
 ****************************************************************************/

static int alt1250_open(FAR struct file *filep)
{
  FAR struct inode *inode;
  FAR struct alt1250_dev_s *dev;
  int ret = OK;

  /* Get our private data structure */

  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
  inode = filep->f_inode;

  dev = (FAR struct alt1250_dev_s *)inode->i_private;
  DEBUGASSERT(dev);

  nxmutex_lock(&dev->refslock);

  if (dev->crefs > 0)
    {
      ret = -EPERM;
    }

  /* Increment the count of open references on the driver */

  dev->crefs++;

  nxmutex_unlock(&dev->refslock);

  if (ret == OK)
    {
      nxsem_init(&dev->waitlist.lock, 0, 1);
      nxsem_init(&dev->replylist.lock, 0, 1);
      nxsem_init(&dev->evtmaplock, 0, 1);
      nxsem_init(&dev->pfdlock, 0, 1);
      nxsem_init(&dev->senddisablelock, 0, 1);
      nxsem_init(&dev->select_inst.stat_lock, 0, 1);

      sq_init(&dev->waitlist.queue);
      sq_init(&dev->replylist.queue);

      dev->senddisable = true;

      ret = pthread_create(&dev->recvthread, NULL,
        (pthread_startroutine_t)altcom_recvthread,
        (pthread_addr_t)dev);
      if (ret < 0)
        {
          m_err("thread create failed: %d\n", errno);
          ret = -errno;

          nxsem_destroy(&dev->waitlist.lock);
          nxsem_destroy(&dev->replylist.lock);
          nxsem_destroy(&dev->evtmaplock);
          nxsem_destroy(&dev->pfdlock);
          nxsem_destroy(&dev->senddisablelock);
          nxsem_destroy(&dev->select_inst.stat_lock);

          nxmutex_lock(&dev->refslock);
          dev->crefs--;
          nxmutex_unlock(&dev->refslock);
        }
      else
        {
          pthread_setname_np(dev->recvthread, "altcom_recvthread");
        }
    }

  return ret;
}

/****************************************************************************
 * Name: alt1250_close
 ****************************************************************************/

static int alt1250_close(FAR struct file *filep)
{
  FAR struct inode *inode;
  FAR struct alt1250_dev_s *dev;
  int ret = OK;

  /* Get our private data structure */

  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
  inode = filep->f_inode;

  dev = (FAR struct alt1250_dev_s *)inode->i_private;
  DEBUGASSERT(dev);

  nxmutex_lock(&dev->refslock);

  if (dev->crefs == 0)
    {
      ret = -EPERM;
    }
  else
    {
      /* Decrement the count of open references on the driver */

      dev->crefs--;
    }

  nxmutex_unlock(&dev->refslock);

  if (ret == OK)
    {
      nxsem_destroy(&dev->waitlist.lock);
      nxsem_destroy(&dev->replylist.lock);
      nxsem_destroy(&dev->evtmaplock);
      nxsem_destroy(&dev->pfdlock);
      nxsem_destroy(&dev->senddisablelock);
      nxsem_destroy(&dev->select_inst.stat_lock);

      altmdm_fin();
      pthread_join(dev->recvthread, NULL);
    }

  return ret;
}

/****************************************************************************
 * Name: alt1250_read
 ****************************************************************************/

static ssize_t alt1250_read(FAR struct file *filep, FAR char *buffer,
                            size_t len)
{
  FAR struct inode *inode;
  FAR struct alt1250_dev_s *dev;

  /* Get our private data structure */

  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
  inode = filep->f_inode;

  dev = (FAR struct alt1250_dev_s *)inode->i_private;
  DEBUGASSERT(dev);

  if (len != sizeof(struct alt_readdata_s))
    {
      return -EINVAL;
    }

  return read_data(dev, (FAR struct alt_readdata_s *)buffer);
}

/****************************************************************************
 * Name: alt1250_ioctl
 ****************************************************************************/

static int alt1250_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct inode *inode;
  FAR struct alt1250_dev_s *dev;
  int ret = OK;

  /* Get our private data structure */

  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
  inode = filep->f_inode;

  dev = (FAR struct alt1250_dev_s *)inode->i_private;
  DEBUGASSERT(dev);

  switch (cmd)
    {
      case ALT1250_IOC_POWER:
        {
          FAR struct alt_power_s *req = (FAR struct alt_power_s *)arg;

          /* Performs power control or power consumption control
           * of the modem.
           */

          ret = alt1250_power_control(dev, req);
        }
        break;

      case ALT1250_IOC_SEND:
        {
          FAR alt_container_t *req = (FAR alt_container_t *)arg;

          ret = make_altcomcmd_and_send(dev, req);
        }
        break;

      case ALT1250_IOC_SETEVTBUFF:
        {
          FAR struct alt_evtbuffer_s *buff =
            (FAR struct alt_evtbuffer_s *)arg;
          add_evtbuff(dev, buff);
        }
        break;

      case ALT1250_IOC_EXCHGCONTAINER:
        {
          FAR alt_container_t **container = (FAR alt_container_t **)arg;

          ret = exchange_selectcontainer(dev, container);
        }
        break;

      default:
        ret = -EINVAL;
        break;
    }

  return ret;
}

/****************************************************************************
 * Name: alt1250_poll
 ****************************************************************************/

static int alt1250_poll(FAR struct file *filep, FAR struct pollfd *fds,
                        bool setup)
{
  FAR struct inode *inode;
  FAR struct alt1250_dev_s *dev;
  int ret = OK;

  /* Get our private data structure */

  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
  inode = filep->f_inode;

  dev = (FAR struct alt1250_dev_s *)inode->i_private;
  DEBUGASSERT(dev);

  /* Are we setting up the poll?  Or tearing it down? */

  if (setup)
    {
      /* Ignore waits that do not include POLLIN */

      if ((fds->events & POLLIN) == 0)
        {
          ret = -EDEADLK;
          goto errout;
        }

      nxmutex_lock(&dev->pfdlock);

      if (is_evtbitmap_avail(dev))
        {
          poll_notify(&fds, 1, POLLIN);
        }
      else
        {
          dev->pfd = fds;
        }

      nxmutex_unlock(&dev->pfdlock);
    }
  else
    {
      nxmutex_lock(&dev->pfdlock);
      dev->pfd = NULL;
      nxmutex_unlock(&dev->pfdlock);
    }

errout:
  return ret;
}

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

FAR void *alt1250_register(FAR const char *devpath,
                           FAR struct spi_dev_s *dev,
                           FAR const struct alt1250_lower_s *lower)
{
  FAR struct alt1250_dev_s *priv;
  int ret;

  priv = (FAR struct alt1250_dev_s *)
    kmm_malloc(sizeof(struct alt1250_dev_s));
  if (!priv)
    {
      m_err("Failed to allocate instance.\n");
      return NULL;
    }

  memset(priv, 0, sizeof(struct alt1250_dev_s));

  priv->spi = dev;
  priv->lower = lower;

  nxsem_init(&priv->refslock, 0, 1);

  ret = register_driver(devpath, &g_alt1250fops, 0666, priv);
  if (ret < 0)
    {
      m_err("Failed to register driver: %d\n", ret);
      kmm_free(priv);
      return NULL;
    }

  return priv;
}

uint64_t get_event_lapibuffer(FAR struct alt1250_dev_s *dev,
                              uint32_t lapicmdid,
                              alt_evtbuf_inst_t **inst)
{
  FAR alt_evtbuf_inst_t *evtinst = NULL;
  unsigned int i;
  uint64_t ret = 0ULL;

  for (i = 0; i < dev->evtbuff->ninst; i++)
    {
      evtinst = &dev->evtbuff->inst[i];

      if (evtinst->cmdid == lapicmdid)
        {
          nxmutex_lock(&evtinst->stat_lock);

          if (evtinst->stat == ALTEVTBUF_ST_WRITABLE)
            {
              *inst = evtinst;
              ret = 1ULL << i;
            }

          nxmutex_unlock(&evtinst->stat_lock);
          break;
        }
    }

  return ret;
}
