// SPDX-License-Identifier: MPL-2.0
#ifndef SH_LIB_SMP_H
#define SH_LIB_SMP_H
#include "std/type.h"
#include "std/status.h"
#include "cpu/ap.h"
#include "cpu/serial.h"
#include "cpu/asm.h"
// Write GS base register
inline void sh_smp_write_gs_base(sh_uint64 value) {
  sh_uint32 low=(sh_uint32)(value);
  sh_uint32 high=(sh_uint32)(value>>32);
  __asm__ volatile ("wrmsr"::"c"(0xC0000101),"a"(low),"d"(high));
}
// Return CPU struct of this CPU
inline sh_ap_CPU_STRUCT *sh_smp_gs_base() {
  sh_uint32 low,high;
  __asm__ volatile ("rdmsr":"=a"(low),"=d"(high):"c"(0xC0000101));
  return (sh_ap_CPU_STRUCT*)(((sh_uint64)high<<32)|low);
}
// Lock structure, unlocked lock have lapic_id to SH_UINT32_MAX
typedef struct {
  volatile sh_uint32 spinlock;
  sh_uint32 lapic_id;
} sh_SPIN_LOCK;
// Create an unlocked lock, intended for creation of global variables.
#define SH_LOCK() {.spinlock=0,.lapic_id=SH_UINT32_MAX}
// Create an unlocked lock, intended for creation of local variables
#define SH_LOCK_LOCAL() ((sh_SPIN_LOCK){.spinlock=0,.lapic_id=SH_UINT32_MAX})
// Lock a spinlock
inline void sh_spin_lock(sh_SPIN_LOCK *lock) {
  while (__atomic_test_and_set(&lock->spinlock,__ATOMIC_ACQUIRE)) {
    while (lock->spinlock) {
      __asm__ volatile ("pause");
    }
  }
  if (sh_smp_gs_base()==SH_NULLPTR) {
    sh_serial_send_byte('!');
    while (SH_TRUE) {}
  }
  lock->lapic_id=sh_smp_gs_base()->lapic_id;
}
// Unlock a spinlock
inline void sh_spin_unlock(sh_SPIN_LOCK *lock) {
  lock->lapic_id=SH_UINT32_MAX;
  __atomic_clear(&lock->spinlock,__ATOMIC_RELEASE);
}
// Try to lock a spinlock, doesn't wait for it to be unlocked, return SH_TRUE if lock is now owned by us
inline sh_bool sh_spin_trylock(sh_SPIN_LOCK *lock) {
  if (__atomic_test_and_set(&lock->spinlock,__ATOMIC_ACQUIRE)) return SH_FALSE;
  lock->lapic_id=sh_smp_gs_base()->lapic_id;
  return SH_TRUE;
}
// Return which CPU is locking the lock
inline sh_uint32 sh_spin_wholock(sh_SPIN_LOCK *lock) {
  return lock->lapic_id;
}
// Set CPU count (aka max_cpu_id+1)
void sh_smp_set_cpu_count(sh_int16 cpu_count);
// Return CPU count, return -1 if this value isn't initialized
sh_int16 sh_smp_get_cpu_count();
// Memory barrier
inline void sh_mb() {
  sh_asm_mfence();
}
// Read memory barrier
inline void sh_rmb() {
  sh_asm_lfence();
}
// Write memory barrier
inline void sh_wmb() {
  sh_asm_sfence();
}
#endif
