#ifndef SPINLOCK_H
#define SPINLOCK_H

#include <stdint.h>

typedef struct {
    volatile uint32_t ticket;
    volatile uint32_t serving;
} spinlock_t;

#define SPINLOCK_INIT { .ticket = 0, .serving = 0 }

static inline void spinlock_acquire(spinlock_t* lock) {
    uint32_t my_ticket = __atomic_fetch_add(&lock->ticket, 1, __ATOMIC_RELAXED);
    while (__atomic_load_n(&lock->serving, __ATOMIC_ACQUIRE) != my_ticket)
        asm volatile("pause");
}

static inline void spinlock_release(spinlock_t* lock) {
    __atomic_fetch_add(&lock->serving, 1, __ATOMIC_RELEASE);
}

static inline int spinlock_try_acquire(spinlock_t* lock) {
    uint32_t serving = __atomic_load_n(&lock->serving, __ATOMIC_RELAXED);
    uint32_t ticket  = __atomic_load_n(&lock->ticket,  __ATOMIC_RELAXED);
    if (serving != ticket) return 0;
    uint32_t expected = serving;
    return __atomic_compare_exchange_n(&lock->ticket, &expected, serving + 1,
                                       0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED);
}

static inline uint64_t spinlock_acquire_irqsave(spinlock_t* lock) {
    uint64_t flags;
    asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory");
    spinlock_acquire(lock);
    return flags;
}

static inline void spinlock_release_irqrestore(spinlock_t* lock, uint64_t flags) {
    spinlock_release(lock);
    asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc");
}

#endif