/*	$OpenBSD: srp.h,v 1.15 2020/05/09 10:18:27 jca Exp $ */

/*
 * Copyright (c) 2014 Jonathan Matthew <jmatthew@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#ifndef _SYS_SRP_H_
#define _SYS_SRP_H_

#include <sys/refcnt.h>

#ifndef __upunused
#ifdef MULTIPROCESSOR
#define __upunused
#else
#define __upunused __attribute__((__unused__))
#endif
#endif /* __upunused */

struct srp {
	void			*ref;
};

#define SRP_INITIALIZER() { NULL }

struct srp_hazard {
	struct srp		*sh_p;
	void			*sh_v;
};

struct srp_ref {
	struct srp_hazard	*hz;
} __upunused;

#define SRP_HAZARD_NUM 16

struct srp_gc {
	void			(*srp_gc_dtor)(void *, void *);
	void			*srp_gc_cookie;
	struct refcnt		srp_gc_refcnt;
};

#define SRP_GC_INITIALIZER(_d, _c) { (_d), (_c), REFCNT_INITIALIZER() }

/*
 * singly linked list built by following srps
 */

struct srpl_rc {
	void			(*srpl_ref)(void *, void *);
	struct srp_gc		srpl_gc;
};
#define srpl_cookie		srpl_gc.srp_gc_cookie

#define SRPL_RC_INITIALIZER(_r, _u, _c) { _r, SRP_GC_INITIALIZER(_u, _c) }

struct srpl {
	struct srp		sl_head;
};

#define SRPL_HEAD(name, type)		struct srpl

#define SRPL_ENTRY(type)						\
struct {								\
	struct srp		se_next;				\
}

#ifdef _KERNEL

void		 srp_startup(void);
void		 srp_gc_init(struct srp_gc *, void (*)(void *, void *), void *);
void		*srp_swap_locked(struct srp *, void *);
void		 srp_update_locked(struct srp_gc *, struct srp *, void *);
void		*srp_get_locked(struct srp *);
void		 srp_gc_finalize(struct srp_gc *);

void		 srp_init(struct srp *);

#ifdef MULTIPROCESSOR
void		*srp_swap(struct srp *, void *);
void		 srp_update(struct srp_gc *, struct srp *, void *);
void		 srp_finalize(void *, const char *);
void		*srp_enter(struct srp_ref *, struct srp *);
void		*srp_follow(struct srp_ref *, struct srp *);
void		 srp_leave(struct srp_ref *);
#else /* MULTIPROCESSOR */

static inline void *
srp_enter(struct srp_ref *sr, struct srp *srp)
{
	sr->hz = NULL;
	return srp->ref;
}

#define srp_swap(_srp, _v)		srp_swap_locked((_srp), (_v))
#define srp_update(_gc, _srp, _v)	srp_update_locked((_gc), (_srp), (_v))
#define srp_finalize(_v, _wchan)	((void)0)
#define srp_follow(_sr, _srp)		srp_enter(_sr, _srp)
#define srp_leave(_sr)			do { } while (0)
#endif /* MULTIPROCESSOR */


void		srpl_rc_init(struct srpl_rc *, void (*)(void *, void *),
		    void (*)(void *, void *), void *);

#define SRPL_INIT(_sl)			srp_init(&(_sl)->sl_head)

#define SRPL_FIRST(_sr, _sl)		srp_enter((_sr), &(_sl)->sl_head)
#define SRPL_NEXT(_sr, _e, _ENTRY)	srp_enter((_sr), &(_e)->_ENTRY.se_next)
#define SRPL_FOLLOW(_sr, _e, _ENTRY)	srp_follow((_sr), &(_e)->_ENTRY.se_next)

#define SRPL_FOREACH(_c, _sr, _sl, _ENTRY)				\
	for ((_c) = SRPL_FIRST(_sr, _sl);				\
	    (_c) != NULL; 						\
	    (_c) = SRPL_FOLLOW(_sr, _c, _ENTRY))

#define SRPL_LEAVE(_sr)			srp_leave((_sr))

#define SRPL_FIRST_LOCKED(_sl)		srp_get_locked(&(_sl)->sl_head)
#define SRPL_EMPTY_LOCKED(_sl)		(SRPL_FIRST_LOCKED(_sl) == NULL)

#define SRPL_NEXT_LOCKED(_e, _ENTRY)					\
	srp_get_locked(&(_e)->_ENTRY.se_next)

#define SRPL_FOREACH_LOCKED(_c, _sl, _ENTRY)				\
	for ((_c) = SRPL_FIRST_LOCKED(_sl);				\
	    (_c) != NULL;						\
	    (_c) = SRPL_NEXT_LOCKED((_c), _ENTRY))

#define SRPL_FOREACH_SAFE_LOCKED(_c, _sl, _ENTRY, _tc)			\
	for ((_c) = SRPL_FIRST_LOCKED(_sl);				\
	    (_c) && ((_tc) = SRPL_NEXT_LOCKED(_c, _ENTRY), 1);		\
	    (_c) = (_tc))

#define SRPL_INSERT_HEAD_LOCKED(_rc, _sl, _e, _ENTRY) do {		\
	void *head;							\
									\
	srp_init(&(_e)->_ENTRY.se_next);				\
									\
	head = SRPL_FIRST_LOCKED(_sl);					\
	if (head != NULL) {						\
		(_rc)->srpl_ref(&(_rc)->srpl_cookie, head);		\
		srp_update_locked(&(_rc)->srpl_gc,			\
		    &(_e)->_ENTRY.se_next, head);	 		\
	}								\
									\
	(_rc)->srpl_ref(&(_rc)->srpl_cookie, _e);			\
	srp_update_locked(&(_rc)->srpl_gc, &(_sl)->sl_head, (_e));	\
} while (0)

#define SRPL_INSERT_AFTER_LOCKED(_rc, _se, _e, _ENTRY) do {		\
	void *next;							\
									\
	srp_init(&(_e)->_ENTRY.se_next);				\
									\
	next = SRPL_NEXT_LOCKED(_se, _ENTRY);				\
	if (next != NULL) {						\
		(_rc)->srpl_ref(&(_rc)->srpl_cookie, next);		\
		srp_update_locked(&(_rc)->srpl_gc,			\
		    &(_e)->_ENTRY.se_next, next);	 		\
	}								\
									\
	(_rc)->srpl_ref(&(_rc)->srpl_cookie, _e);			\
	srp_update_locked(&(_rc)->srpl_gc,				\
	    &(_se)->_ENTRY.se_next, (_e));				\
} while (0)

#define SRPL_REMOVE_LOCKED(_rc, _sl, _e, _type, _ENTRY) do {		\
	struct srp *ref;						\
	struct _type *c, *n;						\
									\
	ref = &(_sl)->sl_head;						\
	while ((c = srp_get_locked(ref)) != (_e))			\
		ref = &c->_ENTRY.se_next;				\
									\
	n = SRPL_NEXT_LOCKED(c, _ENTRY);				\
	if (n != NULL)							\
		(_rc)->srpl_ref(&(_rc)->srpl_cookie, n);		\
	srp_update_locked(&(_rc)->srpl_gc, ref, n);			\
	srp_update_locked(&(_rc)->srpl_gc, &c->_ENTRY.se_next, NULL);	\
} while (0)

#endif /* _KERNEL */

#endif /* _SYS_SRP_H_ */
