// See LICENSE.Sifive for license details.
#ifndef SIFIVE_SMP
#define SIFIVE_SMP
#include "platform.h"

// The maximum number of HARTs this code supports
#ifndef MAX_HARTS
#define MAX_HARTS 32
#endif
#define CLINT_END_HART_IPI CLINT_CTRL_ADDR + (MAX_HARTS*4)
#define CLINT1_END_HART_IPI CLINT1_CTRL_ADDR + (MAX_HARTS*4)

// The hart that non-SMP tests should run on
#ifndef NONSMP_HART
#define NONSMP_HART 0
#endif

/* If your test cannot handle multiple-threads, use this:
 *   smp_disable(reg1)
 */
#define smp_disable(reg1, reg2)			 \
  csrr reg1, mhartid				;\
  li   reg2, NONSMP_HART			;\
  beq  reg1, reg2, hart0_entry			;\
42:						;\
  wfi    					;\
  j 42b						;\
hart0_entry:

/* If your test needs to temporarily block multiple-threads, do this:
 *    smp_pause(reg1, reg2)
 *    ... single-threaded work ...
 *    smp_resume(reg1, reg2)
 *    ... multi-threaded work ...
 */

#define smp_pause(reg1, reg2)	 \
  li reg2, 0x8			;\
  csrw mie, reg2		;\
  li   reg1, NONSMP_HART	;\
  csrr reg2, mhartid		;\
  bne  reg1, reg2, 42f

#ifdef CLINT1_CTRL_ADDR
// If a second CLINT exists, then make sure we:
// 1) Trigger a software interrupt on all harts of both CLINTs.
// 2) Locate your own hart's software interrupt pending register and clear it.
// 3) Wait for all harts on both CLINTs to clear their software interrupt
//    pending register.
// WARNING: This code makes these assumptions, which are only true for Fadu as
// of now:
// 1) hart0 uses CLINT0 at offset 0
// 2) hart2 uses CLINT1 at offset 0
// 3) hart3 uses CLINT1 at offset 1
// 4) There are no other harts or CLINTs in the system.
#define smp_resume(reg1, reg2)	 \
  /* Trigger software interrupt on CLINT0 */ \
  li reg1, CLINT_CTRL_ADDR	;\
41:				;\
  li reg2, 1			;\
  sw reg2, 0(reg1)		;\
  addi reg1, reg1, 4		;\
  li reg2, CLINT_END_HART_IPI	;\
  blt reg1, reg2, 41b		;\
  /* Trigger software interrupt on CLINT1 */ \
  li reg1, CLINT1_CTRL_ADDR	;\
41:				;\
  li reg2, 1			;\
  sw reg2, 0(reg1)		;\
  addi reg1, reg1, 4		;\
  li reg2, CLINT1_END_HART_IPI	;\
  blt reg1, reg2, 41b		;\
  /* Wait to receive software interrupt */ \
42:				;\
  wfi    			;\
  csrr reg2, mip		;\
  andi reg2, reg2, 0x8		;\
  beqz reg2, 42b		;\
  /* Clear own software interrupt bit */ \
  csrr reg2, mhartid		;\
  bnez reg2, 41f; \
  /* hart0 case: Use CLINT0 */ \
  li reg1, CLINT_CTRL_ADDR	;\
  slli reg2, reg2, 2		;\
  add reg2, reg2, reg1		;\
  sw zero, 0(reg2)		;\
  j 42f; \
41: \
  /* hart 2, 3 case: Use CLINT1 and remap hart IDs to 0 and 1 */ \
  li reg1, CLINT1_CTRL_ADDR	;\
  addi reg2, reg2, -2; \
  slli reg2, reg2, 2		;\
  add reg2, reg2, reg1		;\
  sw zero, 0(reg2)		; \
42: \
  /* Wait for all software interrupt bits to be cleared on CLINT0 */ \
  li reg1, CLINT_CTRL_ADDR	;\
41:				;\
  lw reg2, 0(reg1)		;\
  bnez reg2, 41b		;\
  addi reg1, reg1, 4		;\
  li reg2, CLINT_END_HART_IPI	;\
  blt reg1, reg2, 41b; \
  /* Wait for all software interrupt bits to be cleared on CLINT1 */ \
  li reg1, CLINT1_CTRL_ADDR	;\
41:				;\
  lw reg2, 0(reg1)		;\
  bnez reg2, 41b		;\
  addi reg1, reg1, 4		;\
  li reg2, CLINT1_END_HART_IPI	;\
  blt reg1, reg2, 41b; \
  /* End smp_resume() */

#else

#define smp_resume(reg1, reg2)	 \
  li reg1, CLINT_CTRL_ADDR	;\
41:				;\
  li reg2, 1			;\
  sw reg2, 0(reg1)		;\
  addi reg1, reg1, 4		;\
  li reg2, CLINT_END_HART_IPI	;\
  blt reg1, reg2, 41b		;\
42:				;\
  wfi    			;\
  csrr reg2, mip		;\
  andi reg2, reg2, 0x8		;\
  beqz reg2, 42b		;\
  li reg1, CLINT_CTRL_ADDR	;\
  csrr reg2, mhartid		;\
  slli reg2, reg2, 2		;\
  add reg2, reg2, reg1		;\
  sw zero, 0(reg2)		;\
41:				;\
  lw reg2, 0(reg1)		;\
  bnez reg2, 41b		;\
  addi reg1, reg1, 4		;\
  li reg2, CLINT_END_HART_IPI	;\
  blt reg1, reg2, 41b

#endif  /* ifdef CLINT1_CTRL_ADDR */

#endif
