/****************************************************************************
 * arch/arm/src/arm_a_r/arm_hwdebug.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 <nuttx/arch.h>

#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <debug.h>
#include <arch/armv7-a/cp14.h>

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

/* Watchpoint and breakpoint control register share bits */

#define CP14_DBGBWCR_E                       CP14_DBGBCR_E
#define CP14_DBGBWCR_PAC_USER                CP14_DBGBCR_PAC_USER
#define CP14_DBGBWCR_LSC_OFFSET              CP14_DBGBCR_LSC_OFFSET
#define CP14_DBGBWCR_LSC_EXECUTE             CP14_DBGBCR_LSC_EXECUTE
#define CP14_DBGBWCR_LSC_LOAD                CP14_DBGBCR_LSC_LOAD
#define CP14_DBGBWCR_LSC_STORE               CP14_DBGBCR_LSC_STORE
#define CP14_DBGBWCR_BAS_OFFSET              CP14_DBGBCR_BAS_OFFSET
#define CP14_DBGBWCR_BAS_LEN_1               CP14_DBGBCR_BAS_LEN_1
#define CP14_DBGBWCR_BAS_LEN_2               CP14_DBGBCR_BAS_LEN_2
#define CP14_DBGBWCR_BAS_LEN_4               CP14_DBGBCR_BAS_LEN_4
#define CP14_DBGBWCR_BAS_LEN_8               CP14_DBGBCR_BAS_LEN_8

/* Encode the bp control register field as a 32-bit value */

#define CP14_DBGBWCR_VAL(type, len) \
  (arm_convert_type(type) | arm_convert_size(len) | \
   CP14_DBGBCR_PAC_ALL | CP14_DBGBWCR_E)

/****************************************************************************
 * Private Type
 ****************************************************************************/

struct arm_debugpoint_s
{
  int type;
  void *addr;
  size_t size;
  debug_callback_t callback;
  void *arg;
};

struct arm_debug_s
{
  /* Breakpoint currently in use for each BRP, WRP */

  struct arm_debugpoint_s brps[CP14_DBGDIDR_MAX_BRP];
  struct arm_debugpoint_s wrps[CP14_DBGDIDR_MAX_WRP];
};

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

static DEFINE_PER_CPU_BSS(struct arm_debug_s, g_arm_debug);
#define g_arm_debug this_cpu_var(g_arm_debug)

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

static uint32_t arm_convert_type(int type)
{
  switch (type)
    {
      case DEBUGPOINT_WATCHPOINT_RO:
        return CP14_DBGBWCR_LSC_LOAD << CP14_DBGBWCR_LSC_OFFSET;

      case DEBUGPOINT_WATCHPOINT_WO:
        return CP14_DBGBWCR_LSC_STORE << CP14_DBGBWCR_LSC_OFFSET;

      case DEBUGPOINT_WATCHPOINT_RW:
        return (CP14_DBGBWCR_LSC_LOAD | CP14_DBGBWCR_LSC_STORE)
               << CP14_DBGBWCR_LSC_OFFSET;

      case DEBUGPOINT_BREAKPOINT:
      case DEBUGPOINT_STEPPOINT:
      default:
        return CP14_DBGBWCR_LSC_EXECUTE << CP14_DBGBWCR_LSC_OFFSET;
    }
}

static uint32_t arm_convert_size(size_t len)
{
  switch (len)
    {
      case 1:
        return CP14_DBGBWCR_BAS_LEN_1 << CP14_DBGBWCR_BAS_OFFSET;

      case 2:
        return CP14_DBGBWCR_BAS_LEN_2 << CP14_DBGBWCR_BAS_OFFSET;

      case 4:
        return CP14_DBGBWCR_BAS_LEN_4 << CP14_DBGBWCR_BAS_OFFSET;

      case 8:
      default:
        return CP14_DBGBWCR_BAS_LEN_8 << CP14_DBGBWCR_BAS_OFFSET;
    }
}

/* Determine number of usable WRPs available. */

static int arm_get_num_wrps(void)
{
  return ((CP14_GET(DBGDIDR) >> CP14_DBGDIDR_WRPS_OFFSET)
          & CP14_DBGDIDR_WRPS_MASK) + 1;
}

/* Determine number of usable BRPs available. */

static int arm_get_num_brps(void)
{
  return ((CP14_GET(DBGDIDR) >> CP14_DBGDIDR_BRPS_OFFSET)
          & CP14_DBGDIDR_BRPS_MASK) + 1;
}

/****************************************************************************
 * Name: up_watchpoint_add
 *
 * Description:
 *   Add a watchpoint on the address.
 *
 * Input Parameters:
 *  type - The type of the watchpoint
 *  addr - The address to be watched
 *  size - The size of the address to be watched
 *
 * Returned Value:
 *  Index in wprs array on success; a negated errno value on failure
 *
 * Notes:
 *  The size of the watchpoint is determined by the hardware.
 *
 ****************************************************************************/

static int arm_watchpoint_add(int type, uint32_t addr, size_t size)
{
  int num = arm_get_num_wrps();
  int i;

  for (i = 0; i < num; i++)
    {
      if (!(CP14_GETN(DBGWCR, i) & CP14_DBGBWCR_E))
        {
          CP14_SETN(DBGWVR, i, CP14_MASK_ADDR(addr));
          CP14_SETN(DBGWCR, i, CP14_DBGBWCR_VAL(type, size));
          return i;
        }
    }

  return -ENOSPC;
}

/****************************************************************************
 * Name: arm_watchpoint_remove
 *
 * Description:
 *   Remove a watchpoint on the address.
 *
 * Input Parameters:
 *   addr - The address to be watched.
 *
 * Returned Value:
 *  Index in wprs array on success; a negated errno value on failure
 *
 ****************************************************************************/

static int arm_watchpoint_remove(uint32_t addr)
{
  int num = arm_get_num_wrps();
  int i;

  for (i = 0; i < num; i++)
    {
      if (CP14_GETN(DBGWVR, i) == CP14_MASK_ADDR(addr))
        {
          CP14_SETN(DBGWCR, i, 0);
          return i;
        }
    }

  return -ENOENT;
}

/****************************************************************************
 * Name: arm_breakpoint_add
 *
 * Description:
 *   Add a breakpoint on addr.
 *
 * Input Parameters:
 *  addr - The address to break.
 *
 * Returned Value:
 *  Index in bprs array on success; a negated errno value on failure
 *
 * Notes:
 *  1. If breakpoint is already set, it will do nothing.
 *  2. If all comparators are in use, it will return -1.
 *  3. When the breakpoint trigger, if enable monitor exception already ,
 *     will cause a debug monitor exception, oaddr=0x4020392ctherwise will
 *     cause a hard fault.
 *
 ****************************************************************************/

static int arm_breakpoint_add(uintptr_t addr)
{
  int num = arm_get_num_brps();
  int i;

  for (i = 0; i < num; i++)
    {
      if (!(CP14_GETN(DBGBCR, i) & CP14_DBGBWCR_E))
        {
          CP14_SETN(DBGBVR, i, CP14_MASK_ADDR(addr));
          CP14_SETN(DBGBCR, i, CP14_DBGBWCR_VAL(DEBUGPOINT_BREAKPOINT, 8));
          return i;
        }
    }

  return -ENOSPC;
}

/****************************************************************************
 * Name: arm_breakpoint_remove
 *
 * Description:
 *   Remove a breakpoint on addr.
 *
 * Input Parameters:
 *  addr - The address to remove.
 *
 * Returned Value:
 *  Index in bprs array on success; a negated errno value on failure
 *
 ****************************************************************************/

static int arm_breakpoint_remove(uintptr_t addr)
{
  int num = arm_get_num_brps();
  int i;

  for (i = 0; i < num; i++)
    {
      if (CP14_GETN(DBGBVR, i) == CP14_MASK_ADDR(addr))
        {
          CP14_SETN(DBGBCR, i, 0);
          return i;
        }
    }

  return -ENOENT;
}

static void arm_watchpoint_match(uint32_t addr)
{
  struct arm_debugpoint_s *dp = g_arm_debug.wrps;
  int num = arm_get_num_wrps();
  int i;

  for (i = 0; i < num; i++)
    {
      if (CP14_MASK_ADDR(dp[i].addr) == CP14_MASK_ADDR(addr))
        {
          dp[i].callback(dp[i].type, dp[i].addr,
                         dp[i].size, dp[i].arg);
          break;
        }
    }
}

static void arm_breakpoint_match(uint32_t addr)
{
  struct arm_debugpoint_s *dp = g_arm_debug.brps;
  int num = arm_get_num_brps();
  int i;

  for (i = 0; i < num; i++)
    {
      if (CP14_MASK_ADDR(dp[i].addr) == CP14_MASK_ADDR(addr))
        {
          dp[i].callback(dp[i].type, dp[i].addr,
                         dp[i].size, dp[i].arg);
          break;
        }
    }
}

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

/****************************************************************************
 * Name: arm_enable_dbgmonitor
 *
 * Description:
 *   This function enables the debug monitor exception.
 *
 ****************************************************************************/

int arm_enable_dbgmonitor(void)
{
  /* Determine how many BRPs/WRPs are available.
   * And work out the maximum supported watchpoint length.
   */

  binfo("found %d breakpoint and %d watchpoint registers.\n",
        arm_get_num_brps(), arm_get_num_wrps());

  /* If monitor mode is already enabled, just return. */

  if (CP14_GET(DBGDSCRINT) & CP14_DBGDSCRINT_MDBGEN)
    {
      return OK;
    }

  CP14_MOD(DBGDSCREXT, CP14_DBGDSCRINT_MDBGEN, CP14_DBGDSCRINT_MDBGEN);

  /* Check that the write made it through. */

  if (!(CP14_GET(DBGDSCRINT) & CP14_DBGDSCRINT_MDBGEN))
    {
      return -EPERM;
    }

  return OK;
}

/****************************************************************************
 * Name: arm_dbgmonitor
 *
 * Description:
 *   This is Debug Monitor exception handler.  This function is entered when
 *   the processor enters debug mode.  The debug monitor handler will handle
 *   debug events, and resume execution.
 *
 ****************************************************************************/

int arm_dbgmonitor(int irq, void *context, void *arg)
{
  switch (((CP14_GET(DBGDSCRINT)) >> CP14_DBGDSCRINT_MOE_OFFSET)
          & CP14_DBGDSCRINT_MOE_MASK)
    {
      case CP14_DBGDSCRINT_MOE_BREAKPOINT:
      case CP14_DBGDSCRINT_MOE_CFI_BREAKPOINT:
        arm_breakpoint_match((uintptr_t)context);
        break;
      case CP14_DBGDSCRINT_MOE_ASYNC_WATCHPOINT:
      case CP14_DBGDSCRINT_MOE_SYNC_WATCHPOINT:
        arm_watchpoint_match((uintptr_t)context);
        break;
    }

  return OK;
}

/****************************************************************************
 * Name: up_debugpoint_add
 *
 * Description:
 *   Add a debugpoint.
 *
 * Input Parameters:
 *   type     - The debugpoint type. optional value:
 *              DEBUGPOINT_WATCHPOINT_RO - Read only watchpoint.
 *              DEBUGPOINT_WATCHPOINT_WO - Write only watchpoint.
 *              DEBUGPOINT_WATCHPOINT_RW - Read and write watchpoint.
 *              DEBUGPOINT_BREAKPOINT    - Breakpoint.
 *              DEBUGPOINT_STEPPOINT     - Single step.
 *   addr     - The address to be debugged.
 *   size     - The watchpoint size. only for watchpoint.
 *   callback - The callback function when debugpoint triggered.
 *              if NULL, the debugpoint will be removed.
 *   arg      - The argument of callback function.
 *
 * Returned Value:
 *  Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

int up_debugpoint_add(int type, void *addr, size_t size,
                      debug_callback_t callback, void *arg)
{
  struct arm_debugpoint_s *dp;
  int ret = -EINVAL;

  if (type == DEBUGPOINT_BREAKPOINT)
    {
      ret = arm_breakpoint_add((uintptr_t)addr);
      dp = g_arm_debug.brps;
    }
  else if (type == DEBUGPOINT_WATCHPOINT_RO ||
           type == DEBUGPOINT_WATCHPOINT_WO ||
           type == DEBUGPOINT_WATCHPOINT_RW)
    {
      ret = arm_watchpoint_add(type, (uintptr_t)addr, size);
      dp = g_arm_debug.wrps;
    }

  if (ret < 0)
    {
      return ret;
    }

  dp[ret].type = type;
  dp[ret].addr = addr;
  dp[ret].size = size;
  dp[ret].callback = callback;
  dp[ret].arg = arg;

  return OK;
}

/****************************************************************************
 * Name: up_debugpoint_remove
 *
 * Description:
 *   Remove a debugpoint.
 *
 * Input Parameters:
 *   type     - The debugpoint type. optional value:
 *              DEBUGPOINT_WATCHPOINT_RO - Read only watchpoint.
 *              DEBUGPOINT_WATCHPOINT_WO - Write only watchpoint.
 *              DEBUGPOINT_WATCHPOINT_RW - Read and write watchpoint.
 *              DEBUGPOINT_BREAKPOINT    - Breakpoint.
 *              DEBUGPOINT_STEPPOINT     - Single step.
 *   addr     - The address to be debugged.
 *   size     - The watchpoint size. only for watchpoint.
 *
 * Returned Value:
 *  Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

int up_debugpoint_remove(int type, void *addr, size_t size)
{
  struct arm_debugpoint_s *dp;
  int ret = -EINVAL;

  if (type == DEBUGPOINT_BREAKPOINT)
    {
      ret = arm_breakpoint_remove((uintptr_t)addr);
      dp = g_arm_debug.brps;
    }
  else if (type == DEBUGPOINT_WATCHPOINT_RO ||
           type == DEBUGPOINT_WATCHPOINT_WO ||
           type == DEBUGPOINT_WATCHPOINT_RW)
    {
      ret = arm_watchpoint_remove((uintptr_t)addr);
      dp = g_arm_debug.wrps;
    }

  if (ret < 0)
    {
      return ret;
    }

  dp[ret].type = 0;
  dp[ret].addr = 0;
  dp[ret].size = 0;
  dp[ret].callback = NULL;
  dp[ret].arg = NULL;

  return OK;
}