/****************************************************************************
 * drivers/devfreq/devfreq_procfs.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 <sys/stat.h>

#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <execinfo.h>
#include <stdio.h>
#include <string.h>

#include <nuttx/devfreq.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/procfs.h>
#include <nuttx/kmalloc.h>

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

/* Determines the size of an intermediate buffer that must be large enough
 * to handle the longest line generated by this logic.
 */

#define DEVFREQ_LINELEN 256

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

struct devfreq_procfs_s
{
  struct procfs_file_s base;
  FAR struct devfreq_s *devfreq;
};

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

/* File system methods */

static int     devfreq_open(FAR struct file *filep,
                            FAR const char *relpath,
                            int oflags, mode_t mode);
static int     devfreq_close(FAR struct file *filep);
static ssize_t devfreq_read(FAR struct file *filep,
                            FAR char *buffer,
                            size_t buflen);
static ssize_t devfreq_write(FAR struct file *filep,
                             FAR const char *buffer,
                             size_t buflen);
static int     devfreq_opendir(FAR const char *relpath,
                               FAR struct fs_dirent_s **dir);
static int     devfreq_readdir(FAR struct fs_dirent_s *dir,
                               FAR struct dirent *entry);
static int     devfreq_closedir(FAR struct fs_dirent_s *dir);
static int     devfreq_rewinddir(FAR struct fs_dirent_s *dir);
static int     devfreq_stat(FAR const char *relpath, FAR struct stat *buf);

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

static const struct procfs_operations g_devfreq_operations =
{
  .open       = devfreq_open,         /* open */
  .close      = devfreq_close,        /* close */
  .read       = devfreq_read,         /* read */
  .write      = devfreq_write,        /* write */
  .poll       = NULL,                 /* poll */

  .opendir    = devfreq_opendir,      /* opendir */
  .closedir   = devfreq_closedir,     /* closedir */
  .readdir    = devfreq_readdir,      /* readdir */
  .rewinddir  = devfreq_rewinddir,    /* rewinddir */
  .stat       = devfreq_stat,         /* stat */
};

static const struct procfs_entry_s g_devfreq_procfs_root =
{
  "devfreq", &g_devfreq_operations, PROCFS_DIR_TYPE
};

static const struct procfs_entry_s g_devfreq_procfs_entry =
{
  "devfreq/**", &g_devfreq_operations, PROCFS_UNKOWN_TYPE
};

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

/****************************************************************************
 * Name: devfreq_open
 ****************************************************************************/

static int devfreq_open(FAR struct file *filep, FAR const char *relpath,
                        int oflags, mode_t mode)
{
  FAR struct devfreq_s *devfreq;
  FAR struct devfreq_procfs_s *devfreq_procfs;

  relpath += strlen("devfreq/");
  devfreq = devfreq_find_by_name(relpath);
  if (!devfreq)
    {
      return -ENOENT;
    }

  devfreq_procfs = kmm_zalloc(sizeof(struct devfreq_procfs_s));
  if (!devfreq_procfs)
    {
      return -ENOMEM;
    }

  devfreq_procfs->devfreq = devfreq;
  filep->f_priv = devfreq_procfs;
  return 0;
}

/****************************************************************************
 * Name: devfreq_close
 ****************************************************************************/

static int devfreq_close(FAR struct file *filep)
{
  DEBUGASSERT(filep->f_priv);

  kmm_free(filep->f_priv);
  filep->f_priv = NULL;
  return 0;
}

/****************************************************************************
 * Name: devfreq_read
 ****************************************************************************/

static ssize_t devfreq_read(FAR struct file *filep,
                            FAR char *buffer, size_t buflen)
{
  FAR struct devfreq_procfs_s *devfreq_procfs = filep->f_priv;
  FAR struct devfreq_s *devfreq = devfreq_procfs->devfreq;
#ifdef CONFIG_DEVFREQ_PROCFS_QOS
  FAR struct qos_request_s *qos;
  void **stack;
  int depth;
#endif
  off_t offset = filep->f_pos;
  size_t i;

  nxmutex_lock(&devfreq->lock);

  procfs_sprintf(buffer, buflen, &offset,
                 " devfreq:     %s\n"
                 " governor:    %s\n"
                 " cur_freq:    %"PRIu32"\n"
                 " suspended:   %s\n",
                 devfreq->name,
                 devfreq->governor->name,
                 devfreq->cur,
                 devfreq->suspended ? "True" : "False");

  if (devfreq->freq_table)
    {
      procfs_sprintf(buffer, buflen, &offset, " freq_table: ");
      for (i = 0; devfreq->freq_table[i] != DEVFREQ_ENTRY_END; i++)
        {
          if (devfreq->freq_table[i] == DEVFREQ_ENTRY_INVALID)
            {
              continue;
            }

          procfs_sprintf(buffer, buflen, &offset,
                         " %"PRIu32"", devfreq->freq_table[i]);
        }

        procfs_sprintf(buffer, buflen, &offset, "\n");
    }

#ifdef CONFIG_DEVFREQ_PROCFS_QOS
  procfs_sprintf(buffer, buflen, &offset,
                 " qos_list(min, max, backtrace):\n");
  plist_for_each_entry(qos, &devfreq->constraints.min_requests, min_req)
    {
      stack = backtrace_get(qos->backtrace, &depth);
      procfs_sprintf(buffer, buflen, &offset,
                     " %"PRIu32", %"PRIu32",",
                     qos->min_req.prio, qos->max_req.prio);
      for (i = 0; i < depth; i++)
        {
          procfs_sprintf(buffer, buflen, &offset, " %p", stack[i]);
        }

      procfs_sprintf(buffer, buflen, &offset, "\n");
    }
#endif

  nxmutex_unlock(&devfreq->lock);

  if (offset < 0)
    {
      offset = -offset;
    }
  else
    {
      offset = 0;
    }

  filep->f_pos += offset;
  return offset;
}

/****************************************************************************
 * Name: devfreq_write
 ****************************************************************************/

static ssize_t devfreq_write(FAR struct file *filep,
                             FAR const char *buffer, size_t buflen)
{
  return buflen;
}

/****************************************************************************
 * Name: devfreq_opendir
 *
 * Description:
 *   Open a directory for read access
 *
 ****************************************************************************/

static int devfreq_opendir(FAR const char *relpath,
                           FAR struct fs_dirent_s **dir)
{
  FAR struct procfs_dir_priv_s *level1;

  level1 = kmm_zalloc(sizeof(struct procfs_dir_priv_s));
  if (!level1)
    {
      *dir = NULL;
      return -ENOMEM;
    }

  level1->level = 1;

  level1->nentries = UINT16_MAX;

  *dir = (FAR struct fs_dirent_s *)level1;
  return 0;
}

/****************************************************************************
 * Name: devfreq_closedir
 *
 * Description:
 *   Close the directory listing
 *
 ****************************************************************************/

static int devfreq_closedir(FAR struct fs_dirent_s *dir)
{
  kmm_free(dir);
  return 0;
}

/****************************************************************************
 * Name: devfreq_readdir
 *
 * Description:
 *   Read the next directory entry
 *
 ****************************************************************************/

static int devfreq_readdir(FAR struct fs_dirent_s *dir,
                           FAR struct dirent *entry)
{
  FAR struct devfreq_s *devfreq;
  FAR struct procfs_dir_priv_s *level1;

  DEBUGASSERT(dir);
  level1 = (FAR struct procfs_dir_priv_s *)dir;
  devfreq = devfreq_find_by_index(level1->index);
  if (!devfreq)
    {
      return -ENOENT;
    }

  entry->d_type = DTYPE_FILE;
  strlcpy(entry->d_name, devfreq->name, NAME_MAX);
  level1->index++;
  return 0;
}

/****************************************************************************
 * Name: devfreq_rewinddir
 *
 * Description:
 *   Reset directory read to the first entry
 *
 ****************************************************************************/

static int devfreq_rewinddir(FAR struct fs_dirent_s *dir)
{
  FAR struct procfs_dir_priv_s *level1;

  DEBUGASSERT(dir);
  level1 = (FAR struct procfs_dir_priv_s *)dir;
  level1->index = 0;
  return 0;
}

/****************************************************************************
 * Name: devfreq_stat
 *
 * Description:
 *   Return information about a file or directory
 *
 ****************************************************************************/

static int devfreq_stat(FAR const char *relpath, FAR struct stat *buf)
{
  FAR struct devfreq_s *devfreq;

  memset(buf, 0, sizeof(struct stat));

  if (strcmp(relpath, "devfreq") == 0 || strcmp(relpath, "devfreq/") == 0)
    {
      buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
    }
  else
    {
      relpath += strlen("devfreq/");
      devfreq = devfreq_find_by_name(relpath);
      if (!devfreq)
        {
          return -ENOENT;
        }

      buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
    }

  return 0;
}

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

/****************************************************************************
 * Name: devfreq_procfs_initialize
 *
 * Description:
 *   initialize procfs for devfreq, called by devfreq_initialize()
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void devfreq_procfs_initialize(void)
{
  int ret;

  ret = procfs_register(&g_devfreq_procfs_root);
  if (ret == 0)
    {
      ret = procfs_register(&g_devfreq_procfs_entry);
    }

  DEBUGASSERT(ret == 0);
}