/*
 * Copyright (c) 2017, Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#ifndef ZEPHYR_ARCH_XTENSA_INCLUDE_XTENSA_ASM2_CONTEXT_H_
#define ZEPHYR_ARCH_XTENSA_INCLUDE_XTENSA_ASM2_CONTEXT_H_

#if defined(__XT_CLANG__)
#include <xtensa/xtensa-types.h>
#endif

#include <xtensa/corebits.h>
#include <xtensa/config/core-isa.h>
#include <xtensa/config/tie.h>

/*
 * Stack frame layout for a saved processor context, in memory order,
 * high to low address:
 *
 * SP-0 <-- Interrupted stack pointer points here
 *
 * SP-4   Caller A3 spill slot \
 * SP-8   Caller A2 spill slot |
 * SP-12  Caller A1 spill slot + (Part of ABI standard)
 * SP-16  Caller A0 spill slot /
 *
 * SP-20  Saved A3
 * SP-24  Saved A2
 * SP-28  Unused (not "Saved A1" because the SP is saved externally as a handle)
 * SP-32  Saved A0
 *
 * SP-36  Saved PC (address to jump to following restore)
 * SP-40  Saved/interrupted PS special register
 *
 * SP-44  Saved SAR special register
 *
 * SP-48  Saved LBEG special register (if loops enabled)
 * SP-52  Saved LEND special register (if loops enabled)
 * SP-56  Saved LCOUNT special register (if loops enabled)
 *
 * SP-60  Saved SCOMPARE special register (if S32C1I enabled)
 *
 * SP-64  Saved EXCCAUSE special register
 *
 * SP-68  Saved THREADPTR special register (if processor has thread pointer)
 *
 *       (The above fixed-size region is known as the "base save area" in the
 *        code below)
 *
 * - 18 FPU registers (if FPU is present and CONFIG_FPU_SHARING enabled)
 *
 * - Saved A7 \
 * - Saved A6 |
 * - Saved A5 +- If not in-use by another frame
 * - Saved A4 /
 *
 * - Saved A11 \
 * - Saved A10 |
 * - Saved A9  +- If not in-use by another frame
 * - Saved A8  /
 *
 * - Saved A15 \
 * - Saved A14 |
 * - Saved A13 +- If not in-use by another frame
 * - Saved A12 /
 *
 * - Saved intermediate stack pointer (points to low word of base save
 *   area, i.e. the saved LCOUNT or SAR).  The pointer to this value
 *   (i.e. the final stack pointer) is stored externally as the
 *   "restore handle" in the thread context.
 *
 * Essentially, you can recover a pointer to the BSA by loading *SP.
 * Adding the fixed BSA size to that gets you back to the
 * original/interrupted stack pointer.
 */

#ifndef __ASSEMBLER__

#include <stdint.h>
#include <zephyr/toolchain.h>

/* Stack needs to aligned on 16-bytes as mentioned on Xtensa ISA.
 * So we pad _xtensa_irq_bsa_t to achieve that. The followings
 * are see how much space is taken depending on features enabled.
 */

#if XCHAL_HAVE_FP && defined(CONFIG_CPU_HAS_FPU) && defined(CONFIG_FPU_SHARING)
# define _BSA_PADDING_FPU		(sizeof(uintptr_t) * 18U)
#else
# define _BSA_PADDING_FPU		(0)
#endif

#if defined(CONFIG_XTENSA_EAGER_HIFI_SHARING)
# define _BSA_PADDING_HIFI		(XCHAL_CP1_SA_SIZE + XCHAL_CP1_SA_ALIGN)
#else
# define _BSA_PADDING_HIFI		(0)
#endif

#if XCHAL_HAVE_THREADPTR
# define _BSA_PADDING_THREADPTR		(sizeof(uintptr_t))
#else
# define _BSA_PADDING_THREADPTR		(0)
#endif

#if XCHAL_HAVE_S32C1I
# define _BSA_PADDING_S32C1I		(sizeof(uintptr_t))
#else
# define _BSA_PADDING_S32C1I		(0)
#endif

#if XCHAL_HAVE_LOOPS
# define _BSA_PADDING_LOOPS		(sizeof(uintptr_t) * 3U)
#else
# define _BSA_PADDING_LOOPS		(0)
#endif

/* Must have fields regardless of features. */
#define _BSA_PADDING_COMMON		(sizeof(uintptr_t) * 13U)

/* Raw size by adding up all the above. */
#define _BSA_PADDING_BASE_SIZE		\
	(_BSA_PADDING_FPU + \
	 _BSA_PADDING_HIFI + \
	 _BSA_PADDING_THREADPTR + \
	 _BSA_PADDING_S32C1I + \
	 _BSA_PADDING_LOOPS + \
	 _BSA_PADDING_COMMON)

/* Each stack frame always has a pointer to BSA so we add
 * that (+4) to the BSA size before padding the BSA to have
 * size aligned on 16 bytes. Each group of high registers to
 * be saved (totally 3 groups) consists of 4 registers which
 * are 16 bytes already. So each type of stack frame
 * (A[3, 7, 11, 15]) do not need any further padding as long
 * as the BSA struct is of correct size.
 */
#define _BSA_PADDING_PADDED_SIZE	\
	((((_BSA_PADDING_BASE_SIZE + 4) + 15) / 16 * 16) - 4)

/* How many extra bytes needed. */
#define _BSA_PADDING_NEEDED		\
	(_BSA_PADDING_PADDED_SIZE - _BSA_PADDING_BASE_SIZE)

/**
 * Base Save Area (BSA) during interrupt.
 *
 * This saves the registers during interrupt entrance
 * so they can be restored later.
 *
 * Note that only A0-A3 are saved here. High registers
 * are saved after the BSA.
 */
struct xtensa_irq_base_save_area {
#if XCHAL_HAVE_THREADPTR
	uintptr_t threadptr;
#endif

#if XCHAL_HAVE_S32C1I
	uintptr_t scompare1;
#endif

	uintptr_t exccause;
	uintptr_t excvaddr;

#if XCHAL_HAVE_LOOPS
	uintptr_t lcount;
	uintptr_t lend;
	uintptr_t lbeg;
#endif

	uintptr_t sar;
	uintptr_t ps;
	uintptr_t pc;
	uintptr_t a0;
	uintptr_t scratch;
	uintptr_t a2;
	uintptr_t a3;

#if XCHAL_HAVE_FP && defined(CONFIG_CPU_HAS_FPU) && defined(CONFIG_FPU_SHARING)
	uintptr_t fcr;
	uintptr_t fsr;
	uintptr_t fpu0;
	uintptr_t fpu1;
	uintptr_t fpu2;
	uintptr_t fpu3;
	uintptr_t fpu4;
	uintptr_t fpu5;
	uintptr_t fpu6;
	uintptr_t fpu7;
	uintptr_t fpu8;
	uintptr_t fpu9;
	uintptr_t fpu10;
	uintptr_t fpu11;
	uintptr_t fpu12;
	uintptr_t fpu13;
	uintptr_t fpu14;
	uintptr_t fpu15;
#endif

#if defined(CONFIG_XTENSA_EAGER_HIFI_SHARING)

	/*
	 * Carve space for the registers used by the HiFi audio engine
	 * coprocessor (which is always CP1). Carve additional space to
	 * manage alignment at run-time as we can not yet guarantee the
	 * alignment of the BSA.
	 */

	uint8_t  hifi[XCHAL_CP1_SA_SIZE + XCHAL_CP1_SA_ALIGN];
#endif

	uintptr_t padding[_BSA_PADDING_NEEDED / sizeof(uintptr_t)];

	uintptr_t caller_a0;
	uintptr_t caller_a1;
	uintptr_t caller_a2;
	uintptr_t caller_a3;
};

typedef struct xtensa_irq_base_save_area _xtensa_irq_bsa_t;

#undef _BSA_PADDING_NEEDED
#undef _BSA_PADDING_PADDED_SIZE
#undef _BSA_PADDING_BASE_SIZE
#undef _BSA_PADDING_COMMON
#undef _BSA_PADDING_LOOPS
#undef _BSA_PADDING_S32C1I
#undef _BSA_PADDING_THREADPTR
#undef _BSA_PADDING_HIFI
#undef _BSA_PADDING_FPU

/**
 * Raw interrupt stack frame.
 *
 * This provides a raw interrupt stack frame to make it
 * easier to construct general purpose code in loops.
 * Avoid using this if possible.
 */
struct xtensa_irq_stack_frame_raw {
	_xtensa_irq_bsa_t *ptr_to_bsa;

	struct {
		uintptr_t r0;
		uintptr_t r1;
		uintptr_t r2;
		uintptr_t r3;
	} blks[3];
};

typedef struct xtensa_irq_stack_frame_raw _xtensa_irq_stack_frame_raw_t;

/**
 * Interrupt stack frame containing A0 - A15.
 */
struct xtensa_irq_stack_frame_a15 {
	_xtensa_irq_bsa_t *ptr_to_bsa;

	uintptr_t a12;
	uintptr_t a13;
	uintptr_t a14;
	uintptr_t a15;

	uintptr_t a8;
	uintptr_t a9;
	uintptr_t a10;
	uintptr_t a11;

	uintptr_t a4;
	uintptr_t a5;
	uintptr_t a6;
	uintptr_t a7;

	_xtensa_irq_bsa_t bsa;
};

typedef struct xtensa_irq_stack_frame_a15 _xtensa_irq_stack_frame_a15_t;

/**
 * Interrupt stack frame containing A0 - A11.
 */
struct xtensa_irq_stack_frame_a11 {
	_xtensa_irq_bsa_t *ptr_to_bsa;

	uintptr_t a8;
	uintptr_t a9;
	uintptr_t a10;
	uintptr_t a11;

	uintptr_t a4;
	uintptr_t a5;
	uintptr_t a6;
	uintptr_t a7;

	_xtensa_irq_bsa_t bsa;
};

typedef struct xtensa_irq_stack_frame_a11 _xtensa_irq_stack_frame_a11_t;

/**
 * Interrupt stack frame containing A0 - A7.
 */
struct xtensa_irq_stack_frame_a7 {
	_xtensa_irq_bsa_t *ptr_to_bsa;

	uintptr_t a4;
	uintptr_t a5;
	uintptr_t a6;
	uintptr_t a7;

	_xtensa_irq_bsa_t bsa;
};

typedef struct xtensa_irq_stack_frame_a7 _xtensa_irq_stack_frame_a7_t;

/**
 * Interrupt stack frame containing A0 - A3.
 */
struct xtensa_irq_stack_frame_a3 {
	_xtensa_irq_bsa_t *ptr_to_bsa;

	_xtensa_irq_bsa_t bsa;
};

typedef struct xtensa_irq_stack_frame_a3 _xtensa_irq_stack_frame_a3_t;

#endif /* __ASSEMBLER__ */

#endif /* ZEPHYR_ARCH_XTENSA_INCLUDE_XTENSA_ASM2_CONTEXT_H_ */
