/* $NetBSD: altq_priq.c,v 1.29 2025/01/08 13:00:04 joe Exp $ */
/* $KAME: altq_priq.c,v 1.13 2005/04/13 03:44:25 suz Exp $ */
/*
* Copyright (C) 2000-2003
* Sony Computer Science Laboratories Inc. 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.
*
* THIS SOFTWARE IS PROVIDED BY SONY CSL 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 SONY CSL 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.
*/
/*
* bring the interface back to the initial state by discarding
* all the filters and classes.
*/
static int
priq_clear_interface(struct priq_if *pif)
{
struct priq_class *cl;
int pri;
#ifdef ALTQ3_CLFIER_COMPAT
/* free the filters for this interface */
acc_discard_filters(&pif->pif_classifier, NULL, 1);
#endif
/* clear out the classes */
for (pri = 0; pri <= pif->pif_maxpri; pri++)
if ((cl = pif->pif_classes[pri]) != NULL)
priq_class_destroy(cl);
return 0;
}
static int
priq_request(struct ifaltq *ifq, int req, void *arg)
{
struct priq_if *pif = (struct priq_if *)ifq->altq_disc;
/* discard all the queued packets on the interface */
static void
priq_purge(struct priq_if *pif)
{
struct priq_class *cl;
int pri;
for (pri = 0; pri <= pif->pif_maxpri; pri++) {
if ((cl = pif->pif_classes[pri]) != NULL && !qempty(cl->cl_q))
priq_purgeq(cl);
}
if (ALTQ_IS_ENABLED(pif->pif_ifq))
pif->pif_ifq->ifq_len = 0;
}
static struct priq_class *
priq_class_create(struct priq_if *pif, int pri, int qlimit, int flags, int qid)
{
struct priq_class *cl;
int s;
#ifndef ALTQ_RED
if (flags & PRCF_RED) {
#ifdef ALTQ_DEBUG
printf("priq_class_create: RED not configured for PRIQ!\n");
#endif
return NULL;
}
#endif
if ((cl = pif->pif_classes[pri]) != NULL) {
/* modify the class instead of creating a new one */
s = splnet();
if (!qempty(cl->cl_q))
priq_purgeq(cl);
splx(s);
#ifdef ALTQ_RIO
if (q_is_rio(cl->cl_q))
rio_destroy((rio_t *)cl->cl_red);
#endif
#ifdef ALTQ_RED
if (q_is_red(cl->cl_q))
red_destroy(cl->cl_red);
#endif
} else {
cl = malloc(sizeof(struct priq_class), M_DEVBUF,
M_WAITOK|M_ZERO);
if (cl == NULL)
return NULL;
static int
priq_class_destroy(struct priq_class *cl)
{
struct priq_if *pif;
int s, pri;
s = splnet();
#ifdef ALTQ3_CLFIER_COMPAT
/* delete filters referencing to this class */
acc_discard_filters(&cl->cl_pif->pif_classifier, cl, 0);
#endif
if (!qempty(cl->cl_q))
priq_purgeq(cl);
pif = cl->cl_pif;
pif->pif_classes[cl->cl_pri] = NULL;
if (pif->pif_maxpri == cl->cl_pri) {
for (pri = cl->cl_pri; pri >= 0; pri--)
if (pif->pif_classes[pri] != NULL) {
pif->pif_maxpri = pri;
break;
}
if (pri < 0)
pif->pif_maxpri = -1;
}
splx(s);
if (cl->cl_red != NULL) {
#ifdef ALTQ_RIO
if (q_is_rio(cl->cl_q))
rio_destroy((rio_t *)cl->cl_red);
#endif
#ifdef ALTQ_RED
if (q_is_red(cl->cl_q))
red_destroy(cl->cl_red);
#endif
}
free(cl->cl_q, M_DEVBUF);
free(cl, M_DEVBUF);
return 0;
}
/*
* priq_enqueue is an enqueue function to be registered to
* (*altq_enqueue) in struct ifaltq.
*/
static int
priq_enqueue(struct ifaltq *ifq, struct mbuf *m)
{
struct altq_pktattr pktattr;
struct priq_if *pif = (struct priq_if *)ifq->altq_disc;
struct priq_class *cl;
struct m_tag *t;
int len;
/* grab class set by classifier */
if ((m->m_flags & M_PKTHDR) == 0) {
/* should not happen */
printf("altq: packet for %s does not have pkthdr\n",
ifq->altq_ifp->if_xname);
m_freem(m);
return ENOBUFS;
}
cl = NULL;
if ((t = m_tag_find(m, PACKET_TAG_ALTQ_QID)) != NULL)
cl = clh_to_clp(pif, ((struct altq_tag *)(t+1))->qid);
#ifdef ALTQ3_COMPAT
else if (ifq->altq_flags & ALTQF_CLASSIFY)
cl = m->m_pkthdr.pattr_class;
#endif
if (cl == NULL) {
cl = pif->pif_default;
if (cl == NULL) {
m_freem(m);
return ENOBUFS;
}
}
#ifdef ALTQ3_COMPAT
if (m->m_pkthdr.pattr_af != AF_UNSPEC) {
pktattr.pattr_class = m->m_pkthdr.pattr_class;
pktattr.pattr_af = m->m_pkthdr.pattr_af;
pktattr.pattr_hdr = m->m_pkthdr.pattr_hdr;
cl->cl_pktattr = &pktattr; /* save proto hdr used by ECN */
} else
#endif
cl->cl_pktattr = NULL;
len = m_pktlen(m);
if (priq_addq(cl, m) != 0) {
/* drop occurred. mbuf was freed in priq_addq. */
PKTCNTR_ADD(&cl->cl_dropcnt, len);
return ENOBUFS;
}
IFQ_INC_LEN(ifq);
/* successfully queued. */
return 0;
}
/*
* priq_dequeue is a dequeue function to be registered to
* (*altq_dequeue) in struct ifaltq.
*
* note: ALTDQ_POLL returns the next packet without removing the packet
* from the queue. ALTDQ_REMOVE is a normal dequeue operation.
* ALTDQ_REMOVE must return the same packet if called immediately
* after ALTDQ_POLL.
*/
static struct mbuf *
priq_dequeue(struct ifaltq *ifq, int op)
{
struct priq_if *pif = (struct priq_if *)ifq->altq_disc;
struct priq_class *cl;
struct mbuf *m;
int pri;
if (IFQ_IS_EMPTY(ifq))
/* no packet in the queue */
return NULL;
for (pri = pif->pif_maxpri; pri >= 0; pri--) {
if ((cl = pif->pif_classes[pri]) != NULL &&
!qempty(cl->cl_q)) {
if (op == ALTDQ_POLL)
return priq_pollq(cl);
m = priq_getq(cl);
if (m != NULL) {
IFQ_DEC_LEN(ifq);
if (qempty(cl->cl_q))
cl->cl_period++;
PKTCNTR_ADD(&cl->cl_xmitcnt, m_pktlen(m));
}
return m;
}
}
return NULL;
}
static int
priq_addq(struct priq_class *cl, struct mbuf *m)
{
#ifdef ALTQ_RIO
if (q_is_rio(cl->cl_q))
return rio_addq((rio_t *)cl->cl_red, cl->cl_q, m,
cl->cl_pktattr);
#endif
#ifdef ALTQ_RED
if (q_is_red(cl->cl_q))
return red_addq(cl->cl_red, cl->cl_q, m, cl->cl_pktattr);
#endif
if (qlen(cl->cl_q) >= qlimit(cl->cl_q)) {
m_freem(m);
return -1;
}
if (cl->cl_flags & PRCF_CLEARDSCP)
write_dsfield(m, cl->cl_pktattr, 0);
sp->qtype = qtype(cl->cl_q);
#ifdef ALTQ_RED
if (q_is_red(cl->cl_q))
red_getstats(cl->cl_red, &sp->red[0]);
#endif
#ifdef ALTQ_RIO
if (q_is_rio(cl->cl_q))
rio_getstats((rio_t *)cl->cl_red, &sp->red[0]);
#endif
}
/* convert a class handle to the corresponding class pointer */
static struct priq_class *
clh_to_clp(struct priq_if *pif, u_int32_t chandle)
{
struct priq_class *cl;
int idx;
if (chandle == 0)
return NULL;
for (idx = pif->pif_maxpri; idx >= 0; idx--)
if ((cl = pif->pif_classes[idx]) != NULL &&
cl->cl_handle == chandle)
return cl;
/* remove this interface from the pif list */
if (pif_list == pif)
pif_list = pif->pif_next;
else {
struct priq_if *p;
for (p = pif_list; p != NULL; p = p->pif_next)
if (p->pif_next == pif) {
p->pif_next = pif->pif_next;
break;
}
ASSERT(p != NULL);
}
free(pif, M_DEVBUF);
}
/*
* priq device interface
*/
int
priqopen(dev_t dev, int flag, int fmt,
struct lwp *l)
{
/* everything will be done when the queueing scheme is attached. */
return 0;
}
int
priqclose(dev_t dev, int flag, int fmt,
struct lwp *l)
{
struct priq_if *pif;
while ((pif = pif_list) != NULL) {
/* destroy all */
if (ALTQ_IS_ENABLED(pif->pif_ifq))
altq_disable(pif->pif_ifq);
int error = altq_detach(pif->pif_ifq);
switch (error) {
case 0:
case ENXIO: /* already disabled */
break;
default:
return error;
}
priq_detach(pif);
}
return 0;
}
int
priqioctl(dev_t dev, ioctlcmd_t cmd, void *addr, int flag,
struct lwp *l)
{
struct priq_if *pif;
struct priq_interface *ifacep;
int error = 0;
if ((pif = altq_lookup(ap->iface.ifname, ALTQT_PRIQ)) == NULL)
return EBADF;
if (ap->pri < 0 || ap->pri >= PRIQ_MAXPRI)
return EINVAL;
if ((cl = clh_to_clp(pif, ap->class_handle)) == NULL)
return EINVAL;
/*
* if priority is changed, move the class to the new priority
*/
if (pif->pif_classes[ap->pri] != cl) {
if (pif->pif_classes[ap->pri] != NULL)
return EEXIST;
pif->pif_classes[cl->cl_pri] = NULL;
pif->pif_classes[ap->pri] = cl;
cl->cl_pri = ap->pri;
}
/* call priq_class_create to change class parameters */
if ((cl = priq_class_create(pif, ap->pri,
ap->qlimit, ap->flags, ap->class_handle)) == NULL)
return ENOMEM;
return 0;
}
static int
priqcmd_class_stats(struct priq_class_stats *ap)
{
struct priq_if *pif;
struct priq_class *cl;
struct priq_classstats stats, *usp;
int pri, error;
if ((pif = altq_lookup(ap->iface.ifname, ALTQT_PRIQ)) == NULL)
return EBADF;
ap->maxpri = pif->pif_maxpri;
/* then, read the next N classes in the tree */
usp = ap->stats;
for (pri = 0; pri <= pif->pif_maxpri; pri++) {
cl = pif->pif_classes[pri];
memset(&stats, 0, sizeof(stats));
if (cl != NULL)
get_class_stats(&stats, cl);
if ((error = copyout((void *)&stats, (void *)usp++,
sizeof(stats))) != 0)
return error;
}
return 0;
}