/*	$OpenBSD: percpu.h,v 1.9 2023/09/16 09:33:27 mpi Exp $ */

/*
 * Copyright (c) 2016 David Gwynne <dlg@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_PERCPU_H_
#define _SYS_PERCPU_H_

#ifndef CACHELINESIZE
#define CACHELINESIZE 64
#endif

#ifndef __upunused /* this should go in param.h */
#ifdef MULTIPROCESSOR
#define __upunused
#else
#define __upunused __attribute__((__unused__))
#endif
#endif

struct cpumem {
	void		*mem;
};

struct cpumem_iter {
	unsigned int	cpu;
} __upunused;

struct counters_ref {
	uint64_t	 g;
	uint64_t	*c;
};

#ifdef _KERNEL

#include <sys/atomic.h>

struct pool;

struct cpumem	*cpumem_get(struct pool *);
void		 cpumem_put(struct pool *, struct cpumem *);

struct cpumem	*cpumem_malloc(size_t, int);
struct cpumem	*cpumem_malloc_ncpus(struct cpumem *, size_t, int);
void		 cpumem_free(struct cpumem *, int, size_t);

void		*cpumem_first(struct cpumem_iter *, struct cpumem *);
void		*cpumem_next(struct cpumem_iter *, struct cpumem *);

static inline void *
cpumem_enter(struct cpumem *cm)
{
#ifdef MULTIPROCESSOR
	return (cm[cpu_number()].mem);
#else
	return (cm);
#endif
}

static inline void
cpumem_leave(struct cpumem *cm, void *mem)
{
	/* KDASSERT? */
}

#ifdef MULTIPROCESSOR

#define CPUMEM_BOOT_MEMORY(_name, _sz)					\
static struct {								\
	unsigned char	mem[_sz];					\
	struct cpumem	cpumem;						\
} __aligned(CACHELINESIZE) _name##_boot_cpumem = {			\
	.cpumem = { _name##_boot_cpumem.mem }				\
}

#define CPUMEM_BOOT_INITIALIZER(_name)					\
	{ &_name##_boot_cpumem.cpumem }

#else /* MULTIPROCESSOR */

#define CPUMEM_BOOT_MEMORY(_name, _sz)					\
static struct {								\
	unsigned char	mem[_sz];					\
} __aligned(sizeof(uint64_t)) _name##_boot_cpumem

#define CPUMEM_BOOT_INITIALIZER(_name)					\
	{ (struct cpumem *)&_name##_boot_cpumem.mem }

#endif /* MULTIPROCESSOR */

#define CPUMEM_FOREACH(_var, _iter, _cpumem)				\
	for ((_var) = cpumem_first((_iter), (_cpumem));			\
	    (_var) != NULL;						\
	    (_var) = cpumem_next((_iter), (_cpumem)))

/*
 * per cpu counters
 */

struct cpumem	*counters_alloc(unsigned int);
struct cpumem	*counters_alloc_ncpus(struct cpumem *, unsigned int);
void		 counters_free(struct cpumem *, unsigned int);
void		 counters_read(struct cpumem *, uint64_t *, unsigned int,
		     uint64_t *);
void		 counters_zero(struct cpumem *, unsigned int);

static inline uint64_t *
counters_enter(struct counters_ref *ref, struct cpumem *cm)
{
	ref->c = cpumem_enter(cm);
#ifdef MULTIPROCESSOR
	ref->g = ++(*ref->c); /* make the generation number odd */
	membar_producer();
	return (ref->c + 1);
#else
	return (ref->c);
#endif
}

static inline void
counters_leave(struct counters_ref *ref, struct cpumem *cm)
{
#ifdef MULTIPROCESSOR
	membar_producer();
	(*ref->c) = ++ref->g; /* make the generation number even again */
#endif
	cpumem_leave(cm, ref->c);
}

static inline void
counters_inc(struct cpumem *cm, unsigned int c)
{
	struct counters_ref ref;
	uint64_t *counters;

	counters = counters_enter(&ref, cm);
	counters[c]++;
	counters_leave(&ref, cm);
}

static inline void
counters_dec(struct cpumem *cm, unsigned int c)
{
	struct counters_ref ref;
	uint64_t *counters;

	counters = counters_enter(&ref, cm);
	counters[c]--;
	counters_leave(&ref, cm);
}

static inline void
counters_add(struct cpumem *cm, unsigned int c, uint64_t v)
{
	struct counters_ref ref;
	uint64_t *counters;

	counters = counters_enter(&ref, cm);
	counters[c] += v;
	counters_leave(&ref, cm);
}

static inline void
counters_pkt(struct cpumem *cm, unsigned int c, unsigned int b, uint64_t v)
{
	struct counters_ref ref;
	uint64_t *counters;

	counters = counters_enter(&ref, cm);
	counters[c]++;
	counters[b] += v;
	counters_leave(&ref, cm);
}

#ifdef MULTIPROCESSOR
#define COUNTERS_BOOT_MEMORY(_name, _n)					\
	CPUMEM_BOOT_MEMORY(_name, ((_n) + 1) * sizeof(uint64_t))
#else
#define COUNTERS_BOOT_MEMORY(_name, _n)					\
	CPUMEM_BOOT_MEMORY(_name, (_n) * sizeof(uint64_t))
#endif

#define COUNTERS_BOOT_INITIALIZER(_name)	CPUMEM_BOOT_INITIALIZER(_name)

#endif /* _KERNEL */
#endif /* _SYS_PERCPU_H_ */