/****************************************************************************
 * arch/risc-v/src/common/riscv_internal.h
 *
 * 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.
 *
 ****************************************************************************/

#ifndef __ARCH_RISCV_SRC_COMMON_RISCV_INTERNAL_H
#define __ARCH_RISCV_SRC_COMMON_RISCV_INTERNAL_H

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#ifndef __ASSEMBLY__
#  include <nuttx/compiler.h>
#  include <nuttx/sched.h>
#  include <sys/types.h>
#  include <stdint.h>
#  include <syscall.h>
#endif

#include <nuttx/irq.h>

#include "riscv_common_memorymap.h"

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

#if defined(CONFIG_ARCH_QPFPU)
#  define FLOAD     __STR(flq)
#  define FSTORE    __STR(fsq)
#elif defined(CONFIG_ARCH_DPFPU)
#  define FLOAD     __STR(fld)
#  define FSTORE    __STR(fsd)
#else
#  define FLOAD     __STR(flw)
#  define FSTORE    __STR(fsw)
#endif

#ifdef CONFIG_ARCH_RV32
#  define REGLOAD   __STR(lw)
#  define REGSTORE  __STR(sw)
#else
#  define REGLOAD   __STR(ld)
#  define REGSTORE  __STR(sd)
#endif

/* This is the value used to mark the stack for subsequent stack monitoring
 * logic.
 */

#define STACK_COLOR    0xdeadbeef
#define HEAP_COLOR     'h'

#define STACK_FRAME_SIZE __XSTR(STACKFRAME_ALIGN)

/* Interrupt Stack macros */
#define INT_STACK_SIZE  (STACKFRAME_ALIGN_DOWN(CONFIG_ARCH_INTERRUPTSTACK))

/* Determine which (if any) console driver to use.  If a console is enabled
 * and no other console device is specified, then a serial console is
 * assumed.
 */

#ifndef CONFIG_DEV_CONSOLE
#  undef  USE_SERIALDRIVER
#  undef  USE_EARLYSERIALINIT
#else
#  if defined(CONFIG_CONSOLE_SYSLOG)
#    undef  USE_SERIALDRIVER
#    undef  USE_EARLYSERIALINIT
#  else
#    define USE_SERIALDRIVER 1
#    define USE_EARLYSERIALINIT 1
#  endif
#endif

/* Return values from riscv_check_pmp_access */

#define PMP_ACCESS_OFF      (0)     /* Access for area not set */
#define PMP_ACCESS_DENIED   (-1)    /* Access set and denied */
#define PMP_ACCESS_FULL     (1)     /* Access set and allowed */

#ifndef __ASSEMBLY__

/* Use ASM as rv64ilp32 compiler generated address is limited */

static inline uint8_t getreg8(const volatile uintreg_t a)
{
  uint8_t v;
  __asm__ __volatile__("lb %0, 0(%1)" : "=r" (v) : "r" (a));
  return v;
}

static inline void putreg8(uint8_t v, const volatile uintreg_t a)
{
  __asm__ __volatile__("sb %0, 0(%1)" : : "r" (v), "r" (a));
}

static inline uint16_t getreg16(const volatile uintreg_t a)
{
  uint16_t v;
  __asm__ __volatile__("lh %0, 0(%1)" : "=r" (v) : "r" (a));
  return v;
}

static inline void putreg16(uint16_t v, const volatile uintreg_t a)
{
  __asm__ __volatile__("sh %0, 0(%1)" : : "r" (v), "r" (a));
}

static inline uint32_t getreg32(const volatile uintreg_t a)
{
  uint32_t v;
  __asm__ __volatile__("lw %0, 0(%1)" : "=r" (v) : "r" (a));
  return v;
}

static inline void putreg32(uint32_t v, const volatile uintreg_t a)
{
  __asm__ __volatile__("sw %0, 0(%1)" : : "r" (v), "r" (a));
}

static inline uint64_t getreg64(const volatile uintreg_t a)
{
  uint64_t v;
  __asm__ __volatile__("ld %0, 0(%1)" : "=r" (v) : "r" (a));
  return v;
}

static inline void putreg64(uint64_t v, const volatile uintreg_t a)
{
  __asm__ __volatile__("sd %0, 0(%1)" : : "r" (v), "r" (a));
}

#define riscv_append_pmp_region(a, b, s) \
  riscv_config_pmp_region(riscv_next_free_pmp_region(), a, b, s)

#endif

/****************************************************************************
 * Public Types
 ****************************************************************************/

#undef EXTERN
#if defined(__cplusplus)
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif

/****************************************************************************
* Public Function Prototypes
  ***************************************************************************/

#ifndef __ASSEMBLY__
/* Atomic modification of registers */

void modifyreg32(uintreg_t addr, uint32_t clearbits, uint32_t setbits);

/* Memory allocation ********************************************************/

#if CONFIG_MM_REGIONS > 1
void riscv_addregion(void);
#else
#  define riscv_addregion()
#endif

/* IRQ initialization *******************************************************/

void riscv_ack_irq(int irq);

void riscv_sigdeliver(void);
int riscv_swint(int irq, void *context, void *arg);
uintptr_t riscv_get_newintctx(void);
void riscv_set_idleintctx(void);
void riscv_exception_attach(void);

#ifdef CONFIG_ARCH_FPU
void riscv_fpuconfig(void);
void riscv_savefpu(uintreg_t *regs, uintreg_t *fregs);
void riscv_restorefpu(uintreg_t *regs, uintreg_t *fregs);

/* Get FPU register save area */

static inline uintreg_t *riscv_fpuregs(struct tcb_s *tcb)
{
#ifdef CONFIG_ARCH_LAZYFPU
  /* With lazy FPU the registers are simply in tcb */

  return tcb->xcp.fregs;
#else
  /* Otherwise they are after the integer registers */

  return (uintreg_t *)((uintptr_t)tcb->xcp.regs + INT_XCPT_SIZE);
#endif
}
#else
#  define riscv_fpuconfig()
#  define riscv_savefpu(regs, fregs)
#  define riscv_restorefpu(regs, fregs)
#  define riscv_fpuregs(tcb)
#endif

#ifdef CONFIG_ARCH_RV_ISA_V
void riscv_vpuconfig(void);
void riscv_savevpu(uintptr_t *regs, uintptr_t *vregs);
void riscv_restorevpu(uintptr_t *regs, uintptr_t *vregs);

/* Get VPU register save area */

static inline uintptr_t *riscv_vpuregs(struct tcb_s *tcb)
{
  return tcb->xcp.vregs;
}
#else
#  define riscv_vpuconfig()
#  define riscv_savevpu(regs, vregs)
#  define riscv_restorevpu(regs, vregs)
#  define riscv_vpuregs(tcb)
#endif

/* Save / restore context of task */

static inline void riscv_savecontext(struct tcb_s *tcb)
{
#ifdef CONFIG_ARCH_FPU
  /* Save current process FPU state to TCB */

  riscv_savefpu(tcb->xcp.regs, riscv_fpuregs(tcb));
#endif

#ifdef CONFIG_ARCH_RV_ISA_V
  /* Save current process VPU state to TCB */

  riscv_savevpu(tcb->xcp.regs, riscv_vpuregs(tcb));
#endif
}

static inline void riscv_restorecontext(struct tcb_s *tcb)
{
#ifdef CONFIG_ARCH_FPU
  /* Restore FPU state for next process */

  riscv_restorefpu(tcb->xcp.regs, riscv_fpuregs(tcb));
#endif

#ifdef CONFIG_ARCH_RV_ISA_V
  /* Restore VPU state for next process */

  riscv_restorevpu(tcb->xcp.regs, riscv_vpuregs(tcb));
#endif
}

#ifdef CONFIG_ARCH_RISCV_INTXCPT_EXTENSIONS
void riscv_initial_extctx_state(struct tcb_s *tcb);
#endif

/* RISC-V PMP Config ********************************************************/

int riscv_config_pmp_region(uintptr_t region, uintptr_t attr,
                            uintptr_t base, uintptr_t size);

int riscv_check_pmp_access(uintptr_t attr, uintptr_t base, uintptr_t size);
int riscv_configured_pmp_regions(void);
int riscv_next_free_pmp_region(void);

/* Power management *********************************************************/

#ifdef CONFIG_PM
void riscv_pminitialize(void);
#else
#  define riscv_pminitialize()
#endif

/* DMA **********************************************************************/

#ifdef CONFIG_ARCH_DMA
void weak_function riscv_dma_initialize(void);
#endif

/* Low level serial output **************************************************/

void riscv_lowputc(char ch);
void riscv_lowputs(const char *str);

#ifdef USE_SERIALDRIVER
void riscv_serialinit(void);
#endif

#ifdef USE_EARLYSERIALINIT
void riscv_earlyserialinit(void);
#endif

/* Networking ***************************************************************/

/* Defined in board/xyz_network.c for board-specific Ethernet
 * implementations, or chip/xyx_ethernet.c for chip-specific Ethernet
 * implementations.
 */

#if defined(CONFIG_NET) && !defined(CONFIG_NETDEV_LATEINIT)
void riscv_netinitialize(void);
#else
#  define riscv_netinitialize()
#endif

/* Exception Handler ********************************************************/

uintreg_t *riscv_doirq(int irq, uintreg_t *regs);
int riscv_exception(int mcause, void *regs, void *args);
int riscv_fillpage(int mcause, void *regs, void *args);
int riscv_misaligned(int irq, void *context, void *arg);

/* Debug ********************************************************************/

#ifdef CONFIG_STACK_COLORATION
size_t riscv_stack_check(uintptr_t alloc, size_t size);
void riscv_stack_color(void *stackbase, size_t nbytes);
#endif

#if defined(CONFIG_STACK_COLORATION) && \
    defined(CONFIG_ARCH_INTERRUPTSTACK) && CONFIG_ARCH_INTERRUPTSTACK > 15
void riscv_color_intstack(void);
#else
#  define riscv_color_intstack()
#endif

#ifdef CONFIG_SMP
void riscv_cpu_boot(int cpu);
int riscv_smp_call_handler(int irq, void *c, void *arg);
#endif

/****************************************************************************
 * Name: riscv_mhartid
 *
 * Description:
 *   Context aware way to query hart id (physical core ID)
 *
 *   The function riscv_mhartid is designed to retrieve the hardware thread
 *   ID (hartid) in different execution modes of RISC-V. Its behavior depends
 *   on the configuration and execution mode:
 *
 *   - In machine mode, riscv_mhartid reads directly from the CSR mhartid.
 *   - In supervisor mode, the hartid is stored in the percpu structure
 *     during boot because supervisor mode does not have access to CSR
 *     `shartid`. The SBI (Supervisor Binary Interface) provides the hartid
 *     in the a0 register (as per SBI ABI requirements), and it is the
 *     responsibility of the payload OS to store this value internally.
 *     We use the percpu scratch register for this purpose, as it is the only
 *     location that is unique for each CPU and non-volatile.
 *
 *   Note: In flat (machine) mode, you could still read the hartid from CSR
 *   mhartid even if CONFIG_RISCV_PERCPU_SCRATCH is enabled.
 *
 * Returned Value:
 *   Hart id
 *
 ****************************************************************************/

uintptr_t riscv_mhartid(void);

#ifdef CONFIG_ARCH_RV_CPUID_MAP
/****************************************************************************
 * Name: riscv_hartid_to_cpuid / riscv_cpuid_to_hartid
 *
 * Description:
 *   CPU ID mapping functions for systems where physical hart IDs don't match
 *   logical CPU IDs.
 *
 ****************************************************************************/

int riscv_hartid_to_cpuid(int hart);
int riscv_cpuid_to_hartid(int cpu);
#else
#define riscv_hartid_to_cpuid(hart) (hart)
#define riscv_cpuid_to_hartid(cpu)  (cpu)
#endif /* CONFIG_ARCH_RV_CPUID_MAP */

/* If kernel runs in Supervisor mode, a system call trampoline is needed */

#ifdef CONFIG_ARCH_USE_S_MODE
void *riscv_perform_syscall(uintreg_t *regs);
#endif

void riscv_jump_to_user(uintptr_t entry, uintreg_t a0, uintreg_t a1,
                        uintreg_t a2, uintreg_t sp,
                        uintreg_t *regs) noreturn_function;

/* Context switching via system calls ***************************************/

/****************************************************************************
 * Name: riscv_fullcontextrestore
 *
 * Description:
 *   Restores the full context.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#define riscv_fullcontextrestore()    \
  do                                  \
    {                                 \
      sys_call0(SYS_restore_context); \
    }                                 \
  while (1)

/****************************************************************************
 * Name: riscv_switchcontext
 *
 * Description:
 *   Switches the context.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#define riscv_switchcontext()        \
  do                                 \
    {                                \
      sys_call0(SYS_switch_context); \
    }                                \
  while (0)

#undef EXTERN
#ifdef __cplusplus
}
#endif
#endif /* __ASSEMBLY__ */

#endif /* __ARCH_RISCV_SRC_COMMON_RISCV_INTERNAL_H */