/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _LINUX_FILE_REF_H
#define _LINUX_FILE_REF_H

#include <linux/atomic.h>
#include <linux/preempt.h>
#include <linux/types.h>



#ifdef CONFIG_64BIT
#define FILE_REF_ONEREF		0x0000000000000000UL
#define FILE_REF_MAXREF		0x7FFFFFFFFFFFFFFFUL
#define FILE_REF_SATURATED	0xA000000000000000UL
#define FILE_REF_RELEASED	0xC000000000000000UL
#define FILE_REF_DEAD		0xE000000000000000UL
#define FILE_REF_NOREF		0xFFFFFFFFFFFFFFFFUL
#else
#define FILE_REF_ONEREF		0x00000000U
#define FILE_REF_MAXREF		0x7FFFFFFFU
#define FILE_REF_SATURATED	0xA0000000U
#define FILE_REF_RELEASED	0xC0000000U
#define FILE_REF_DEAD		0xE0000000U
#define FILE_REF_NOREF		0xFFFFFFFFU
#endif

typedef struct {
#ifdef CONFIG_64BIT
	atomic64_t refcnt;
#else
	atomic_t refcnt;
#endif
} file_ref_t;


static inline void file_ref_init(file_ref_t *ref, unsigned long cnt)
{
	atomic_long_set(&ref->refcnt, cnt - 1);
}

bool __file_ref_put(file_ref_t *ref, unsigned long cnt);


static __always_inline __must_check bool file_ref_get(file_ref_t *ref)
{
	
	return !atomic_long_add_negative(1, &ref->refcnt);
}


static __always_inline void file_ref_inc(file_ref_t *ref)
{
	long prior = atomic_long_fetch_inc_relaxed(&ref->refcnt);
	WARN_ONCE(prior < 0, "file_ref_inc() on a released file reference");
}


static __always_inline __must_check bool file_ref_put(file_ref_t *ref)
{
	long cnt;

	
	guard(preempt)();
	
	cnt = atomic_long_dec_return(&ref->refcnt);
	if (cnt >= 0)
		return false;
	return __file_ref_put(ref, cnt);
}


static __always_inline __must_check bool file_ref_put_close(file_ref_t *ref)
{
	long old;

	old = atomic_long_read(&ref->refcnt);
	if (likely(old == FILE_REF_ONEREF)) {
		if (likely(atomic_long_try_cmpxchg(&ref->refcnt, &old, FILE_REF_DEAD)))
			return true;
	}
	return file_ref_put(ref);
}


static inline unsigned long file_ref_read(file_ref_t *ref)
{
	unsigned long c = atomic_long_read(&ref->refcnt);

	
	return c >= FILE_REF_RELEASED ? 0 : c + 1;
}


static inline unsigned long __file_ref_read_raw(file_ref_t *ref)
{
	return atomic_long_read(&ref->refcnt);
}

#endif
