/****************************************************************************
 * drivers/segger/note_sysview.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 <stddef.h>
#include <stdio.h>
#include <syslog.h>

#include <nuttx/clock.h>
#include <nuttx/sched.h>
#include <nuttx/sched_note.h>
#include <nuttx/note/note_driver.h>
#include <nuttx/rtt.h>

#include <SEGGER_RTT.h>
#include <SEGGER_SYSVIEW.h>

#include "sched/sched.h"

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

struct note_sysview_driver_s
{
  struct note_driver_s driver;
  unsigned int irq[CONFIG_SMP_NCPUS];
};

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

static void note_sysview_start(FAR struct note_driver_s *drv,
                               FAR struct tcb_s *tcb);
static void note_sysview_stop(FAR struct note_driver_s *drv,
                              FAR struct tcb_s *tcb);
#ifdef CONFIG_SCHED_INSTRUMENTATION_SWITCH
static void note_sysview_suspend(FAR struct note_driver_s *drv,
                                 FAR struct tcb_s *tcb);
static void note_sysview_resume(FAR struct note_driver_s *drv,
                                FAR struct tcb_s *tcb);
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER
static void note_sysview_irqhandler(FAR struct note_driver_s *drv, int irq,
                                    FAR void *handler, bool enter);
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL
static void note_sysview_syscall_enter(FAR struct note_driver_s *drv,
                                       int nr, int argc, va_list *ap);
static void note_sysview_syscall_leave(FAR struct note_driver_s *drv,
                                       int nr, uintptr_t result);
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_HEAP
static void note_sysview_heap(FAR struct note_driver_s *drv,
                              uint8_t event, FAR void *heap, FAR void *mem,
                              size_t size, size_t curused);
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_WDOG
static void note_sysview_wdog(FAR struct note_driver_s *drv, uint8_t event,
                              FAR void *handler, FAR const void *arg);
#endif

#ifdef CONFIG_SCHED_INSTRUMENTATION_DUMP
static void note_sysview_vprintf(FAR struct note_driver_s *drv,
                                 uint8_t level, uintptr_t ip,
                                 FAR const char *fmt, va_list va);
#endif

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

static const struct note_driver_ops_s g_note_sysview_ops =
{
  NULL,                       /* add */
  note_sysview_start,         /* start */
  note_sysview_stop,          /* stop */
#ifdef CONFIG_SCHED_INSTRUMENTATION_SWITCH
  note_sysview_suspend,       /* suspend */
  note_sysview_resume,        /* resume */
#endif
#ifdef CONFIG_SMP
  NULL,                       /* cpu_start */
  NULL,                       /* cpu_started */
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_PREEMPTION
  NULL,                       /* preemption */
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_CSECTION
  NULL,                       /* csection */
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_SPINLOCKS
  NULL,                       /* spinlock */
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL
  note_sysview_syscall_enter, /* syscall_enter */
  note_sysview_syscall_leave, /* syscall_leave */
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER
  note_sysview_irqhandler,    /* irqhandler */
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_WDOG
  note_sysview_wdog,          /* wdog */
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_HEAP
  note_sysview_heap,          /* heap */
#endif

#ifdef CONFIG_SCHED_INSTRUMENTATION_DUMP
  NULL,                       /* event */
  note_sysview_vprintf,       /* vprintf */
#endif
};

static struct note_sysview_driver_s g_note_sysview_driver =
{
  {
#ifdef CONFIG_SCHED_INSTRUMENTATION_FILTER
    "sysview",
    {
      {
        CONFIG_SEGGER_SYSVIEW_FILTER_DEFAULT_MODE,
#  ifdef CONFIG_SMP
        CONFIG_SEGGER_SYSVIEW_CPUSET
#  endif
      },
    },
#endif
    &g_note_sysview_ops
  }
};

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

/****************************************************************************
 * Name: note_sysview_send_taskinfo
 ****************************************************************************/

static void note_sysview_send_taskinfo(FAR struct tcb_s *tcb)
{
  SEGGER_SYSVIEW_TASKINFO info;

  info.TaskID    = tcb->pid;
  info.sName     = get_task_name(tcb);
  info.Prio      = tcb->sched_priority;
  info.StackBase = (uintptr_t)tcb->stack_base_ptr;
  info.StackSize = tcb->adj_stack_size;

  SEGGER_SYSVIEW_SendTaskInfo(&info);
}

/****************************************************************************
 * Name: note_sysview_get_time
 ****************************************************************************/

static uint64_t note_sysview_get_time(void)
{
  return TICK2USEC(clock_systime_ticks());
}

/****************************************************************************
 * Name: note_sysview_send_tasklist
 ****************************************************************************/

static void note_sysview_send_tasklist(void)
{
  int i;

  for (i = 0; i < g_npidhash; i++)
    {
      if (g_pidhash[i] != NULL)
        {
          note_sysview_send_taskinfo(g_pidhash[i]);
        }
    }
}

/****************************************************************************
 * Name: note_sysview_send_description
 ****************************************************************************/

static void note_sysview_send_description(void)
{
  SEGGER_SYSVIEW_SendSysDesc("N="SEGGER_SYSVIEW_APP_NAME);
  SEGGER_SYSVIEW_SendSysDesc("D="CONFIG_LIBC_HOSTNAME);
  SEGGER_SYSVIEW_SendSysDesc("O=NuttX");
}

/****************************************************************************
 * Name: note_sysview_*
 *
 * Description:
 *   Hooks to scheduler monitor
 *
 * Input Parameters:
 *   Varies
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void note_sysview_start(FAR struct note_driver_s *drv,
                               FAR struct tcb_s *tcb)
{
  SEGGER_SYSVIEW_OnTaskCreate(tcb->pid);
  note_sysview_send_taskinfo(tcb);
}

static void note_sysview_stop(FAR struct note_driver_s *drv,
                              FAR struct tcb_s *tcb)
{
  SEGGER_SYSVIEW_OnTaskTerminate(tcb->pid);
}

#ifdef CONFIG_SCHED_INSTRUMENTATION_SWITCH
static void note_sysview_suspend(FAR struct note_driver_s *drv,
                                 FAR struct tcb_s *tcb)
{
  if (!up_interrupt_context())
    {
      SEGGER_SYSVIEW_OnTaskStopExec();
    }
}

static void note_sysview_resume(FAR struct note_driver_s *drv,
                                FAR struct tcb_s *tcb)
{
  if (!up_interrupt_context())
    {
      if (is_idle_task(tcb))
        {
          SEGGER_SYSVIEW_OnIdle();
        }
      else
        {
          SEGGER_SYSVIEW_OnTaskStartExec(tcb->pid);
        }
    }
}
#endif

#ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER
static void note_sysview_irqhandler(FAR struct note_driver_s *drv, int irq,
                                    FAR void *handler, bool enter)
{
  FAR struct note_sysview_driver_s *driver =
      (FAR struct note_sysview_driver_s *)drv;

  if (enter)
    {
      driver->irq[this_cpu()] = irq;

      SEGGER_SYSVIEW_OnTaskStopExec();
      SEGGER_SYSVIEW_RecordEnterISR();
    }
  else
    {
      SEGGER_SYSVIEW_RecordExitISR();

      if (up_interrupt_context())
        {
          FAR struct tcb_s *tcb = this_task();

          if (tcb && !is_idle_task(tcb))
            {
              SEGGER_SYSVIEW_OnTaskStartExec(tcb->pid);
            }
          else
            {
              SEGGER_SYSVIEW_OnIdle();
            }
        }

      driver->irq[this_cpu()] = 0;
    }
}
#endif

#ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL
static void note_sysview_syscall_enter(FAR struct note_driver_s *drv, int nr,
                                       int argc, va_list *ap)
{
  FAR struct note_sysview_driver_s *driver =
      (FAR struct note_sysview_driver_s *)drv;
  nr -= CONFIG_SYS_RESERVED;

  /* Set the name marker if the current syscall nr is not active */

  if (NOTE_FILTER_SYSCALLMASK_ISSET(nr,
                          &driver->driver.filter.syscall_mask) == 0)
    {
      /* Set the name marker */

      SEGGER_SYSVIEW_NameMarker(nr, g_funcnames[nr]);

      /* Mark the syscall active */

      NOTE_FILTER_SYSCALLMASK_SET(nr, &driver->driver.filter.syscall_mask);

      /* Use the Syscall "0" to identify whether the syscall is enabled,
       * if the host tool is closed abnormally, use this bit to clear
       * the active set.
       */

      if (NOTE_FILTER_SYSCALLMASK_ISSET(0,
                              &driver->driver.filter.syscall_mask) == 0)
        {
          NOTE_FILTER_SYSCALLMASK_SET(0,
                              &driver->driver.filter.syscall_mask);
        }
    }

  SEGGER_SYSVIEW_MarkStart(nr);
}

static void note_sysview_syscall_leave(FAR struct note_driver_s *drv,
                                       int nr, uintptr_t result)
{
  FAR struct note_sysview_driver_s *driver =
      (FAR struct note_sysview_driver_s *)drv;
  nr -= CONFIG_SYS_RESERVED;

  if (NOTE_FILTER_SYSCALLMASK_ISSET(nr, &driver->driver.filter.syscall_mask))
    {
      SEGGER_SYSVIEW_MarkStop(nr);
    }
}
#endif

#ifdef CONFIG_SCHED_INSTRUMENTATION_HEAP
static void note_sysview_heap(FAR struct note_driver_s *drv,
                              uint8_t event, FAR void *heap, FAR void *mem,
                              size_t size, size_t curused)
{
  switch (event)
    {
      case NOTE_HEAP_ALLOC:
      case NOTE_HEAP_FREE:
        {
          U32 value = (U32)curused;
          const SEGGER_SYSVIEW_DATA_SAMPLE data =
            {
              .ID = (U32)(uintptr_t)heap,
              .pU32_Value = &value,
            };

          SEGGER_SYSVIEW_SampleData(&data);
          if (event == NOTE_HEAP_ALLOC)
            {
              SEGGER_SYSVIEW_HeapAlloc(heap, mem, size);
            }
          else
            {
              SEGGER_SYSVIEW_HeapFree(heap, mem);
            }

          break;
        }

      case NOTE_HEAP_ADD:
        {
          char name[32];
          SEGGER_SYSVIEW_DATA_REGISTER data =
            {
              .ID = (U32)(uintptr_t)heap,
              .DataType = SEGGER_SYSVIEW_TYPE_U32,
              .Offset = 0,
              .RangeMin = 0,
              .RangeMax = 0,
              .ScalingFactor = 1.f,
              .sUnit = "B",
              .sName = name,
            };

          snprintf(name, sizeof(name), "Heap%p", heap);

          SEGGER_SYSVIEW_RegisterData(&data);
          SEGGER_SYSVIEW_HeapDefine(heap, mem, size, 0);
          break;
        }

      default:
        break;
    }
}
#endif

#ifdef CONFIG_SCHED_INSTRUMENTATION_WDOG
static void note_sysview_wdog(FAR struct note_driver_s *drv, uint8_t event,
                              FAR void *handler, FAR const void *arg)
{
  if (event == NOTE_WDOG_ENTER)
    {
      SEGGER_SYSVIEW_RecordEnterTimer((uintptr_t)handler);
    }
  else if (event == NOTE_WDOG_LEAVE)
    {
      SEGGER_SYSVIEW_RecordExitTimer();
    }
}
#endif

#ifdef CONFIG_SCHED_INSTRUMENTATION_DUMP
static void note_sysview_vprintf(FAR struct note_driver_s *drv,
                                 uint8_t level, uintptr_t ip,
                                 FAR const char *fmt, va_list va)
{
  SEGGER_SYSVIEW_VPrintfHost(fmt, &va);
}
#endif

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

/****************************************************************************
 * Name: note_sysview_get_interrupt_id
 *
 * Description:
 *   Retrieve the Id of the currently active interrupt.
 *
 ****************************************************************************/

unsigned int note_sysview_get_interrupt_id(void)
{
  return g_note_sysview_driver.irq[this_cpu()];
}

/****************************************************************************
 * Name: note_sysview_initialize
 *
 * Description:
 *   Initializes the SYSVIEW module.
 *
 * Input Parameters:
 *   None.
 *
 * Returned Value:
 *   Zero on succress. A negated errno value is returned on a failure.
 *
 ****************************************************************************/

int note_sysview_initialize(void)
{
  unsigned long freq = perf_getfreq();
  int ret;

  static const SEGGER_SYSVIEW_OS_API g_sysview_trace_api =
    {
      note_sysview_get_time,
      note_sysview_send_tasklist,
    };

  if (freq == 0)
    {
      syslog(LOG_ERR, "up_perf isn't initialized, sysview isn't available");
      PANIC();
    }

  SEGGER_SYSVIEW_Init(freq, freq, &g_sysview_trace_api,
                      note_sysview_send_description);

#if CONFIG_SEGGER_SYSVIEW_RAM_BASE != 0
  SEGGER_SYSVIEW_SetRAMBase(CONFIG_SEGGER_SYSVIEW_RAM_BASE);
#endif

  SEGGER_SYSVIEW_Start();
  ret = note_driver_register(&g_note_sysview_driver.driver);
  syslog(LOG_NOTICE, "SEGGER RTT Control Block Address: %#" PRIxPTR "\n",
                      (uintptr_t)&_SEGGER_RTT +
                      CONFIG_SEGGER_RTT_UNCACHED_OFF);
  return ret;
}