/*
 * Copyright (c) 2007-2021 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */

/*
 * Copyright (c) 1991-1997 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Network Research
 *      Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/mbuf.h>
#include <sys/errno.h>
#include <sys/random.h>
#include <sys/kernel_types.h>
#include <sys/sysctl.h>

#include <net/if.h>
#include <net/net_osdep.h>
#include <net/classq/classq.h>

#include <libkern/libkern.h>

#if SKYWALK
#include <skywalk/os_skywalk_private.h>
#endif /* SKYWALK */

u_int32_t classq_verbose = 0;   /* more noise if greater than 1 */

SYSCTL_NODE(_net, OID_AUTO, classq, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "classq");

SYSCTL_UINT(_net_classq, OID_AUTO, verbose, CTLFLAG_RW | CTLFLAG_LOCKED,
    &classq_verbose, 0, "Class queue verbosity level");

void
_qinit(class_queue_t *q, int type, int lim, classq_pkt_type_t ptype)
{
	switch (ptype) {
	case QP_MBUF:
		MBUFQ_INIT(&qmbufq(q));
		break;

#if SKYWALK
	case QP_PACKET:
		KPKTQ_INIT(&qkpktq(q));
		break;
#endif /* SKYWALK */

	default:
		VERIFY(0);
		/* NOTREACHED */
	}

	qlimit(q) = lim;
	qlen(q) = 0;
	qsize(q) = 0;
	qtype(q) = type;
	qptype(q) = ptype;
	qstate(q) = QS_RUNNING;
}

/* add a packet at the tail of the queue */
void
_addq(class_queue_t *q, classq_pkt_t *pkt)
{
	uint32_t size = 0;

	ASSERT(pkt->cp_ptype == qptype(q));

	switch (qptype(q)) {
	case QP_MBUF: {
		struct mbuf *m = pkt->cp_mbuf;
		MBUFQ_ENQUEUE(&qmbufq(q), m);
		size = m_length(m);
		break;
	}

#if SKYWALK
	case QP_PACKET: {
		struct __kern_packet *kp = pkt->cp_kpkt;
		KPKTQ_ENQUEUE(&qkpktq(q), kp);
		size = kp->pkt_length;
		break;
	}
#endif /* SKYWALK */

	default:
		VERIFY(0);
		/* NOTREACHED */
		__builtin_unreachable();
	}

	qlen(q)++;
	VERIFY(qlen(q) != 0);
	qsize(q) += size;
}

/* add one or more packets at the tail of the queue */
void
_addq_multi(class_queue_t *q, classq_pkt_t *pkt_head, classq_pkt_t *pkt_tail,
    u_int32_t cnt, u_int64_t size)
{
	ASSERT(pkt_head->cp_ptype == qptype(q));
	ASSERT(pkt_tail->cp_ptype == qptype(q));
	switch (qptype(q)) {
	case QP_MBUF: {
		struct mbuf *m_head = pkt_head->cp_mbuf;
		struct mbuf *m_tail = pkt_tail->cp_mbuf;
		MBUFQ_ENQUEUE_MULTI(&qmbufq(q), m_head, m_tail);
		break;
	}

#if SKYWALK
	case QP_PACKET: {
		struct __kern_packet *kp_head = pkt_head->cp_kpkt;
		struct __kern_packet *kp_tail = pkt_tail->cp_kpkt;
		KPKTQ_ENQUEUE_MULTI(&qkpktq(q), kp_head, kp_tail, cnt);
		break;
	}
#endif /* SKYWALK */

	default:
		VERIFY(0);
		/* NOTREACHED */
		__builtin_unreachable();
	}

	qlen(q) += cnt;
	qsize(q) += size;
}

/* get a packet at the head of the queue */
void
_getq(class_queue_t *q, classq_pkt_t *pkt)
{
	uint32_t pkt_len = 0;

	switch (qptype(q)) {
	case QP_MBUF: {
		MBUFQ_DEQUEUE(&qmbufq(q), pkt->cp_mbuf);
		if (__probable(pkt->cp_mbuf != NULL)) {
			CLASSQ_PKT_INIT_MBUF(pkt, pkt->cp_mbuf);
			pkt_len = m_length(pkt->cp_mbuf);
		}
		break;
	}

#if SKYWALK
	case QP_PACKET: {
		KPKTQ_DEQUEUE(&qkpktq(q), pkt->cp_kpkt);
		if (__probable(pkt->cp_kpkt != NULL)) {
			CLASSQ_PKT_INIT_PACKET(pkt, pkt->cp_kpkt);
			pkt_len = pkt->cp_kpkt->pkt_length;
		}
		break;
	}
#endif /* SKYWALK */

	default:
		VERIFY(0);
		/* NOTREACHED */
		__builtin_unreachable();
	}

	if (pkt->cp_mbuf == NULL) {
		VERIFY(qlen(q) == 0);
		if (qsize(q) > 0) {
			qsize(q) = 0;
		}
		return;
	}
	VERIFY(qlen(q) > 0);
	qlen(q)--;

	/* qsize is an approximation, so adjust if necessary */
	if (((int)qsize(q) - pkt_len) > 0) {
		qsize(q) -= pkt_len;
	} else if (qsize(q) != 0) {
		qsize(q) = 0;
	}
}

static void
_getq_flow_or_scidx(class_queue_t *q, classq_pkt_t *pkt, u_int32_t val,
    boolean_t isflowid)
{
	uint32_t pkt_len = 0;

	switch (qptype(q)) {
	case QP_MBUF: {
		struct mbuf *m, *m_tmp;

		MBUFQ_FOREACH_SAFE(m, &qmbufq(q), m_tmp) {
			if ((isflowid && (val == 0 ||
			    ((m->m_flags & M_PKTHDR) &&
			    m->m_pkthdr.pkt_flowid == val))) ||
			    (!isflowid &&
			    MBUF_SCIDX(mbuf_get_service_class(m)) < val)) {
				/* remove it from the class queue */
				MBUFQ_REMOVE(&qmbufq(q), m);
				MBUFQ_NEXT(m) = NULL;
				break;
			}
		}
		if (__probable(m != NULL)) {
			CLASSQ_PKT_INIT_MBUF(pkt, m);
			pkt_len = m_length(m);
		}
		break;
	}

#if SKYWALK
	case QP_PACKET: {
		struct __kern_packet *kp, *kp_tmp;

		KPKTQ_FOREACH_SAFE(kp, &qkpktq(q), kp_tmp) {
			if ((isflowid && (val == 0 ||
			    (kp->pkt_flow_token == val))) || (!isflowid &&
			    MBUF_SCIDX(kp->pkt_svc_class) < val)) {
				/* remove it from the class queue */
				KPKTQ_REMOVE(&qkpktq(q), kp);
				KPKTQ_NEXT(kp) = NULL;
				break;
			}
		}
		if (__probable(kp != NULL)) {
			CLASSQ_PKT_INIT_PACKET(pkt, kp);
			pkt_len = kp->pkt_length;
		}
		break;
	}
#endif /* SKYWALK */

	default:
		VERIFY(0);
		/* NOTREACHED */
		__builtin_unreachable();
	}

	if (pkt->cp_mbuf != NULL) {
		VERIFY(qlen(q) > 0);
		qlen(q)--;

		/* qsize is an approximation, so adjust if necessary */
		if (((int)qsize(q) - pkt_len) > 0) {
			qsize(q) -= pkt_len;
		} else if (qsize(q) != 0) {
			qsize(q) = 0;
		}
	}
}

/* get a packet of a specific flow beginning from the head of the queue */
void
_getq_flow(class_queue_t *q, classq_pkt_t *pkt, u_int32_t flow)
{
	return _getq_flow_or_scidx(q, pkt, flow, TRUE);
}

/* Get a packet whose MBUF_SCIDX() < scidx from head of queue */
void
_getq_scidx_lt(class_queue_t *q, classq_pkt_t *pkt, u_int32_t scidx)
{
	return _getq_flow_or_scidx(q, pkt, scidx, FALSE);
}

/* get all packets (chained) starting from the head of the queue */
void
_getq_all(class_queue_t *q, classq_pkt_t *first, classq_pkt_t *last,
    u_int32_t *qlenp, u_int64_t *qsizep)
{
	switch (qptype(q)) {
	case QP_MBUF:
		first->cp_mbuf = MBUFQ_FIRST(&qmbufq(q));
		if (__probable(first->cp_mbuf != NULL)) {
			CLASSQ_PKT_INIT_MBUF(first, first->cp_mbuf);
		}
		if (last != NULL) {
			last->cp_mbuf = MBUFQ_LAST(&qmbufq(q));
			if (__probable(last->cp_mbuf != NULL)) {
				CLASSQ_PKT_INIT_MBUF(last, last->cp_mbuf);
			}
		}
		MBUFQ_INIT(&qmbufq(q));
		break;

#if SKYWALK
	case QP_PACKET:
		first->cp_kpkt = KPKTQ_FIRST(&qkpktq(q));
		if (__probable(first->cp_kpkt != NULL)) {
			CLASSQ_PKT_INIT_PACKET(first, first->cp_kpkt);
		}
		if (last != NULL) {
			last->cp_kpkt = KPKTQ_LAST(&qkpktq(q));
			if (__probable(last->cp_kpkt != NULL)) {
				CLASSQ_PKT_INIT_PACKET(last, last->cp_kpkt);
			}
		}
		KPKTQ_INIT(&qkpktq(q));
		break;
#endif /* SKYWALK */

	default:
		VERIFY(0);
		/* NOTREACHED */
		__builtin_unreachable();
	}

	if (qlenp != NULL) {
		*qlenp = qlen(q);
	}
	if (qsizep != NULL) {
		*qsizep = qsize(q);
	}

	qlen(q) = 0;
	qsize(q) = 0;
}

static inline struct mbuf *
_getq_tail_mbuf(class_queue_t *q)
{
	struct mq_head *head = &qmbufq(q);
	struct mbuf *m = MBUFQ_LAST(head);

	if (m != NULL) {
		struct mbuf *n = MBUFQ_FIRST(head);

		while (n != NULL) {
			struct mbuf *next = MBUFQ_NEXT(n);
			if (next == m) {
				MBUFQ_NEXT(n) = NULL;
				break;
			}
			n = next;
		}
		VERIFY(n != NULL ||
		    (qlen(q) == 1 && m == MBUFQ_FIRST(head)));
		VERIFY(qlen(q) > 0);
		--qlen(q);

		/* qsize is an approximation, so adjust if necessary */
		if (((int)qsize(q) - m_length(m)) > 0) {
			qsize(q) -= m_length(m);
		} else if (qsize(q) != 0) {
			qsize(q) = 0;
		}

		if (qempty(q)) {
			VERIFY(m == MBUFQ_FIRST(head));
			MBUFQ_INIT(head);
		} else {
			VERIFY(n != NULL);
			head->mq_last = &MBUFQ_NEXT(n);
		}
	}
	return m;
}

/* drop a packet at the tail of the queue */
void
_getq_tail(class_queue_t *q, classq_pkt_t *pkt)
{
	switch (qptype(q)) {
	case QP_MBUF:
		pkt->cp_mbuf = _getq_tail_mbuf(q);
		if (__probable(pkt->cp_mbuf != NULL)) {
			CLASSQ_PKT_INIT_MBUF(pkt, pkt->cp_mbuf);
		}
		break;

#if SKYWALK
	case QP_PACKET: /* XXX: Add support for Kernel packet when needed */
#endif /* SKYWALK */
	default:
		VERIFY(0);
		/* NOTREACHED */
		__builtin_unreachable();
	}
}

static inline struct mbuf *
_getq_random_mbuf(class_queue_t *q)
{
	struct mq_head *head = &qmbufq(q);
	struct mbuf *m = NULL;
	unsigned int n;
	u_int32_t rnd;

	/* XXX: Add support for Kernel packet when needed */
	VERIFY((qptype(q) == QP_MBUF));

	n = qlen(q);
	if (n == 0) {
		VERIFY(MBUFQ_EMPTY(head));
		if (qsize(q) > 0) {
			qsize(q) = 0;
		}
		return NULL;
	}

	m = MBUFQ_FIRST(head);
	read_frandom(&rnd, sizeof(rnd));
	n = (rnd % n) + 1;

	if (n == 1) {
		if ((MBUFQ_FIRST(head) = MBUFQ_NEXT(m)) == NULL) {
			(head)->mq_last = &MBUFQ_FIRST(head);
		}
	} else {
		struct mbuf *p = NULL;

		VERIFY(n > 1);
		while (n--) {
			if (MBUFQ_NEXT(m) == NULL) {
				break;
			}
			p = m;
			m = MBUFQ_NEXT(m);
		}
		VERIFY(p != NULL && MBUFQ_NEXT(p) == m);

		if ((MBUFQ_NEXT(p) = MBUFQ_NEXT(m)) == NULL) {
			(head)->mq_last = &MBUFQ_NEXT(p);
		}
	}

	VERIFY(qlen(q) > 0);
	--qlen(q);

	/* qsize is an approximation, so adjust if necessary */
	if (((int)qsize(q) - m_length(m)) > 0) {
		qsize(q) -= m_length(m);
	} else if (qsize(q) != 0) {
		qsize(q) = 0;
	}

	MBUFQ_NEXT(m) = NULL;

	return m;
}

/* randomly select a packet in the queue */
void
_getq_random(class_queue_t *q, classq_pkt_t *pkt)
{
	switch (qptype(q)) {
	case QP_MBUF:
		pkt->cp_mbuf = _getq_random_mbuf(q);
		if (__probable(pkt->cp_mbuf != NULL)) {
			CLASSQ_PKT_INIT_MBUF(pkt, pkt->cp_mbuf);
		}
		break;

#if SKYWALK
	case QP_PACKET: /* XXX: Add support for Kernel packet when needed */
#endif /* SKYWALK */
	default:
		VERIFY(0);
		/* NOTREACHED */
		__builtin_unreachable();
	}
}

static inline void
_removeq_mbuf(class_queue_t *q, struct mbuf *m)
{
	struct mq_head *head = &qmbufq(q);
	struct mbuf *m0, **mtail;

	m0 = MBUFQ_FIRST(head);
	if (m0 == NULL) {
		return;
	}

	if (m0 != m) {
		while (m0 != NULL && MBUFQ_NEXT(m0) != m) {
			m0 = MBUFQ_NEXT(m0);
		}
		if (m0 == NULL) {
			return;
		}

		mtail = &MBUFQ_NEXT(m0);
	} else {
		mtail = &MBUFQ_FIRST(head);
	}

	*mtail = MBUFQ_NEXT(m);
	if (*mtail == NULL) {
		head->mq_last = mtail;
	}

	VERIFY(qlen(q) > 0);
	--qlen(q);

	/* qsize is an approximation, so adjust if necessary */
	if (((int)qsize(q) - m_length(m)) > 0) {
		qsize(q) -= m_length(m);
	} else if (qsize(q) != 0) {
		qsize(q) = 0;
	}

	MBUFQ_NEXT(m) = NULL;
}

/* remove a packet from the queue */
void
_removeq(class_queue_t *q, classq_pkt_t *pkt)
{
	switch (qptype(q)) {
	case QP_MBUF:
		ASSERT(pkt->cp_ptype == QP_MBUF);
		_removeq_mbuf(q, pkt->cp_mbuf);
		break;

#if SKYWALK
	case QP_PACKET: /* XXX: Add support for Kernel packet when needed */
#endif /* SKYWALK */
	default:
		VERIFY(0);
		/* NOTREACHED */
		__builtin_unreachable();
	}
}

void
_flushq(class_queue_t *q)
{
	(void) _flushq_flow(q, 0, NULL, NULL);
}

static inline void
_flushq_flow_mbuf(class_queue_t *q, u_int32_t flow, u_int32_t *cnt,
    u_int32_t *len)
{
	MBUFQ_HEAD(mq_freeq) freeq;
	struct mbuf *m, *m_tmp;
	u_int32_t c = 0, l = 0;

	MBUFQ_INIT(&freeq);

	MBUFQ_FOREACH_SAFE(m, &qmbufq(q), m_tmp) {
		if (flow == 0 || ((m->m_flags & M_PKTHDR) &&
		    m->m_pkthdr.pkt_flowid == flow)) {
			/* remove it from the class queue */
			MBUFQ_REMOVE(&qmbufq(q), m);
			MBUFQ_NEXT(m) = NULL;

			/* and add it to the free queue */
			MBUFQ_ENQUEUE(&freeq, m);

			l += m_length(m);
			c++;
		}
	}
	VERIFY(c == 0 || !MBUFQ_EMPTY(&freeq));

	if (c > 0) {
		VERIFY(qlen(q) >= c);
		qlen(q) -= c;

		/* qsize is an approximation, so adjust if necessary */
		if (((int)qsize(q) - l) > 0) {
			qsize(q) -= l;
		} else if (qsize(q) != 0) {
			qsize(q) = 0;
		}
	}

	if (!MBUFQ_EMPTY(&freeq)) {
		m_freem_list(MBUFQ_FIRST(&freeq));
	}

	if (cnt != NULL) {
		*cnt = c;
	}
	if (len != NULL) {
		*len = l;
	}
}

#if SKYWALK
static inline void
_flushq_flow_kpkt(class_queue_t *q, u_int32_t flow, u_int32_t *cnt,
    u_int32_t *len)
{
	KPKTQ_HEAD(pq_freeq) freeq;
	struct __kern_packet *p, *p_tmp;
	u_int32_t c = 0, l = 0;

	KPKTQ_INIT(&freeq);

	KPKTQ_FOREACH_SAFE(p, &qkpktq(q), p_tmp) {
		if (flow == 0 || p->pkt_flow_token == flow) {
			/* remove it from the class queue */
			KPKTQ_REMOVE(&qkpktq(q), p);
			KPKTQ_NEXT(p) = NULL;

			/* and add it to the free queue */
			KPKTQ_ENQUEUE(&freeq, p);

			l += p->pkt_length;
			c++;
		}
	}
	VERIFY(c == 0 || !KPKTQ_EMPTY(&freeq));

	if (c > 0) {
		VERIFY(qlen(q) >= c);
		qlen(q) -= c;

		/* qsize is an approximation, so adjust if necessary */
		if (((int)qsize(q) - l) > 0) {
			qsize(q) -= l;
		} else if (qsize(q) != 0) {
			qsize(q) = 0;
		}
	}

	if (!KPKTQ_EMPTY(&freeq)) {
		pp_free_packet_chain(KPKTQ_FIRST(&freeq), NULL);
	}

	if (cnt != NULL) {
		*cnt = c;
	}
	if (len != NULL) {
		*len = l;
	}
}
#endif /* SKYWALK */

void
_flushq_flow(class_queue_t *q, u_int32_t flow, u_int32_t *cnt, u_int32_t *len)
{
	switch (qptype(q)) {
	case QP_MBUF:
		_flushq_flow_mbuf(q, flow, cnt, len);
		break;

#if SKYWALK
	case QP_PACKET:
		_flushq_flow_kpkt(q, flow, cnt, len);
		break;

#endif /* SKYWALK */
	default:
		VERIFY(0);
		/* NOTREACHED */
		__builtin_unreachable();
	}
}
