#include "opt_altq.h"
#include "opt_inet.h"
#include "opt_inet6.h"
#ifdef ALTQ_CBQ
#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/callout.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/thread.h>
#include <net/if.h>
#include <net/ifq_var.h>
#include <net/netmsg2.h>
#include <net/netisr2.h>
#include <netinet/in.h>
#include <net/pf/pfvar.h>
#include <net/altq/altq.h>
#include <net/altq/altq_cbq.h>
#include <sys/thread2.h>
#define CBQ_SUBQ_INDEX ALTQ_SUBQ_INDEX_DEFAULT
#define CBQ_LOCK(ifq) \
ALTQ_SQ_LOCK(&(ifq)->altq_subq[CBQ_SUBQ_INDEX])
#define CBQ_UNLOCK(ifq) \
ALTQ_SQ_UNLOCK(&(ifq)->altq_subq[CBQ_SUBQ_INDEX])
#define CBQ_ASSERT_LOCKED(ifq) \
ALTQ_SQ_ASSERT_LOCKED(&(ifq)->altq_subq[CBQ_SUBQ_INDEX])
static int cbq_class_destroy(cbq_state_t *, struct rm_class *);
static struct rm_class *clh_to_clp(cbq_state_t *, uint32_t);
static int cbq_clear_interface(cbq_state_t *);
static int cbq_request(struct ifaltq_subque *, int, void *);
static int cbq_enqueue(struct ifaltq_subque *, struct mbuf *,
struct altq_pktattr *);
static struct mbuf *cbq_dequeue(struct ifaltq_subque *, int);
static void cbqrestart(struct ifaltq *);
static void get_class_stats(class_stats_t *, struct rm_class *);
static void cbq_purge(cbq_state_t *);
static int
cbq_class_destroy(cbq_state_t *cbqp, struct rm_class *cl)
{
int i;
rmc_delete_class(&cbqp->ifnp, cl);
for (i = 0; i < CBQ_MAX_CLASSES; i++)
if (cbqp->cbq_class_tbl[i] == cl)
cbqp->cbq_class_tbl[i] = NULL;
if (cl == cbqp->ifnp.root_)
cbqp->ifnp.root_ = NULL;
if (cl == cbqp->ifnp.default_)
cbqp->ifnp.default_ = NULL;
return (0);
}
static struct rm_class *
clh_to_clp(cbq_state_t *cbqp, uint32_t chandle)
{
int i;
struct rm_class *cl;
if (chandle == 0)
return (NULL);
i = chandle % CBQ_MAX_CLASSES;
if ((cl = cbqp->cbq_class_tbl[i]) != NULL &&
cl->stats_.handle == chandle)
return (cl);
for (i = 0; i < CBQ_MAX_CLASSES; i++)
if ((cl = cbqp->cbq_class_tbl[i]) != NULL &&
cl->stats_.handle == chandle)
return (cl);
return (NULL);
}
static int
cbq_clear_interface(cbq_state_t *cbqp)
{
int again, i;
struct rm_class *cl;
do {
again = 0;
for (i = 0; i < CBQ_MAX_CLASSES; i++) {
if ((cl = cbqp->cbq_class_tbl[i]) != NULL) {
if (is_a_parent_class(cl))
again++;
else {
cbq_class_destroy(cbqp, cl);
cbqp->cbq_class_tbl[i] = NULL;
if (cl == cbqp->ifnp.root_)
cbqp->ifnp.root_ = NULL;
if (cl == cbqp->ifnp.default_)
cbqp->ifnp.default_ = NULL;
}
}
}
} while (again);
return (0);
}
static int
cbq_request(struct ifaltq_subque *ifsq, int req, void *arg)
{
struct ifaltq *ifq = ifsq->ifsq_altq;
cbq_state_t *cbqp = (cbq_state_t *)ifq->altq_disc;
crit_enter();
switch (req) {
case ALTRQ_PURGE:
if (ifsq_get_index(ifsq) == CBQ_SUBQ_INDEX) {
cbq_purge(cbqp);
} else {
ifsq_classic_request(ifsq, ALTRQ_PURGE, NULL);
}
break;
}
crit_exit();
return (0);
}
static void
get_class_stats(class_stats_t *statsp, struct rm_class *cl)
{
statsp->xmit_cnt = cl->stats_.xmit_cnt;
statsp->drop_cnt = cl->stats_.drop_cnt;
statsp->over = cl->stats_.over;
statsp->borrows = cl->stats_.borrows;
statsp->overactions = cl->stats_.overactions;
statsp->delays = cl->stats_.delays;
statsp->depth = cl->depth_;
statsp->priority = cl->pri_;
statsp->maxidle = cl->maxidle_;
statsp->minidle = cl->minidle_;
statsp->offtime = cl->offtime_;
statsp->qmax = qlimit(cl->q_);
statsp->ns_per_byte = cl->ns_per_byte_;
statsp->wrr_allot = cl->w_allotment_;
statsp->qcnt = qlen(cl->q_);
statsp->avgidle = cl->avgidle_;
statsp->qtype = qtype(cl->q_);
#ifdef ALTQ_RED
if (q_is_red(cl->q_))
red_getstats(cl->red_, &statsp->red[0]);
#endif
#ifdef ALTQ_RIO
if (q_is_rio(cl->q_))
rio_getstats((rio_t *)cl->red_, &statsp->red[0]);
#endif
}
int
cbq_pfattach(struct pf_altq *a, struct ifaltq *ifq)
{
return altq_attach(ifq, ALTQT_CBQ, a->altq_disc, ifq_mapsubq_default,
cbq_enqueue, cbq_dequeue, cbq_request, NULL, NULL);
}
int
cbq_add_altq(struct pf_altq *a)
{
cbq_state_t *cbqp;
struct ifnet *ifp;
ifnet_lock();
if ((ifp = ifunit(a->ifname)) == NULL) {
ifnet_unlock();
return (EINVAL);
}
if (!ifq_is_ready(&ifp->if_snd)) {
ifnet_unlock();
return (ENODEV);
}
cbqp = kmalloc(sizeof(*cbqp), M_ALTQ, M_WAITOK | M_ZERO);
callout_init(&cbqp->cbq_callout);
cbqp->cbq_qlen = 0;
cbqp->ifnp.ifq_ = &ifp->if_snd;
ifq_purge_all(&ifp->if_snd);
ifnet_unlock();
a->altq_disc = cbqp;
return (0);
}
int
cbq_remove_altq(struct pf_altq *a)
{
cbq_state_t *cbqp;
struct ifaltq *ifq;
if ((cbqp = a->altq_disc) == NULL)
return (EINVAL);
a->altq_disc = NULL;
ifq = cbqp->ifnp.ifq_;
CBQ_LOCK(ifq);
cbq_clear_interface(cbqp);
if (cbqp->ifnp.default_)
cbq_class_destroy(cbqp, cbqp->ifnp.default_);
if (cbqp->ifnp.root_)
cbq_class_destroy(cbqp, cbqp->ifnp.root_);
CBQ_UNLOCK(ifq);
kfree(cbqp, M_ALTQ);
return (0);
}
static int
cbq_add_queue_locked(struct pf_altq *a, cbq_state_t *cbqp)
{
struct rm_class *borrow, *parent;
struct rm_class *cl;
struct cbq_opts *opts;
int i;
KKASSERT(a->qid != 0);
i = a->qid % CBQ_MAX_CLASSES;
if (cbqp->cbq_class_tbl[i] != NULL) {
for (i = 0; i < CBQ_MAX_CLASSES; i++)
if (cbqp->cbq_class_tbl[i] == NULL)
break;
if (i == CBQ_MAX_CLASSES)
return (EINVAL);
}
opts = &a->pq_u.cbq_opts;
if (a->priority >= CBQ_MAXPRI)
return (EINVAL);
parent = clh_to_clp(cbqp, a->parent_qid);
if (opts->flags & CBQCLF_BORROW)
borrow = parent;
else
borrow = NULL;
if (parent == NULL && (opts->flags & CBQCLF_ROOTCLASS) == 0) {
kprintf("cbq_add_queue: no parent class!\n");
return (EINVAL);
}
if ((borrow != parent) && (borrow != NULL)) {
kprintf("cbq_add_class: borrow class != parent\n");
return (EINVAL);
}
switch (opts->flags & CBQCLF_CLASSMASK) {
case CBQCLF_ROOTCLASS:
if (parent != NULL)
return (EINVAL);
if (cbqp->ifnp.root_)
return (EINVAL);
break;
case CBQCLF_DEFCLASS:
if (cbqp->ifnp.default_)
return (EINVAL);
break;
case 0:
if (a->qid == 0)
return (EINVAL);
break;
default:
return (EINVAL);
}
if ((opts->flags & CBQCLF_CLASSMASK) == CBQCLF_ROOTCLASS) {
rmc_init(cbqp->ifnp.ifq_, &cbqp->ifnp, opts->ns_per_byte,
cbqrestart, a->qlimit, RM_MAXQUEUED,
opts->maxidle, opts->minidle, opts->offtime,
opts->flags);
cl = cbqp->ifnp.root_;
} else {
cl = rmc_newclass(a->priority,
&cbqp->ifnp, opts->ns_per_byte,
rmc_delay_action, a->qlimit, parent, borrow,
opts->maxidle, opts->minidle, opts->offtime,
opts->pktsize, opts->flags);
}
if (cl == NULL)
return (ENOMEM);
cl->stats_.handle = a->qid;
cl->stats_.depth = cl->depth_;
cbqp->cbq_class_tbl[i] = cl;
if ((opts->flags & CBQCLF_CLASSMASK) == CBQCLF_DEFCLASS)
cbqp->ifnp.default_ = cl;
return (0);
}
int
cbq_add_queue(struct pf_altq *a)
{
cbq_state_t *cbqp;
struct ifaltq *ifq;
int error;
if (a->qid == 0)
return (EINVAL);
if ((cbqp = a->altq_disc) == NULL)
return (EINVAL);
ifq = cbqp->ifnp.ifq_;
CBQ_LOCK(ifq);
error = cbq_add_queue_locked(a, cbqp);
CBQ_UNLOCK(ifq);
return error;
}
static int
cbq_remove_queue_locked(struct pf_altq *a, cbq_state_t *cbqp)
{
struct rm_class *cl;
int i;
if ((cl = clh_to_clp(cbqp, a->qid)) == NULL)
return (EINVAL);
if (is_a_parent_class(cl))
return (EINVAL);
rmc_delete_class(&cbqp->ifnp, cl);
for (i = 0; i < CBQ_MAX_CLASSES; i++)
if (cbqp->cbq_class_tbl[i] == cl) {
cbqp->cbq_class_tbl[i] = NULL;
if (cl == cbqp->ifnp.root_)
cbqp->ifnp.root_ = NULL;
if (cl == cbqp->ifnp.default_)
cbqp->ifnp.default_ = NULL;
break;
}
return (0);
}
int
cbq_remove_queue(struct pf_altq *a)
{
cbq_state_t *cbqp;
struct ifaltq *ifq;
int error;
if ((cbqp = a->altq_disc) == NULL)
return (EINVAL);
ifq = cbqp->ifnp.ifq_;
CBQ_LOCK(ifq);
error = cbq_remove_queue_locked(a, cbqp);
CBQ_UNLOCK(ifq);
return error;
}
int
cbq_getqstats(struct pf_altq *a, void *ubuf, int *nbytes)
{
cbq_state_t *cbqp;
struct rm_class *cl;
class_stats_t stats;
int error = 0;
struct ifaltq *ifq;
if (*nbytes < sizeof(stats))
return (EINVAL);
ifnet_lock();
if ((cbqp = altq_lookup(a->ifname, ALTQT_CBQ)) == NULL) {
ifnet_unlock();
return (EBADF);
}
ifq = cbqp->ifnp.ifq_;
CBQ_LOCK(ifq);
if ((cl = clh_to_clp(cbqp, a->qid)) == NULL) {
CBQ_UNLOCK(ifq);
ifnet_unlock();
return (EINVAL);
}
get_class_stats(&stats, cl);
CBQ_UNLOCK(ifq);
ifnet_unlock();
if ((error = copyout((caddr_t)&stats, ubuf, sizeof(stats))) != 0)
return (error);
*nbytes = sizeof(stats);
return (0);
}
static int
cbq_enqueue(struct ifaltq_subque *ifsq, struct mbuf *m,
struct altq_pktattr *pktattr __unused)
{
struct ifaltq *ifq = ifsq->ifsq_altq;
cbq_state_t *cbqp = (cbq_state_t *)ifq->altq_disc;
struct rm_class *cl;
int len;
if (ifsq_get_index(ifsq) != CBQ_SUBQ_INDEX) {
ifsq_classic_request(ifsq, ALTRQ_PURGE, NULL);
m_freem(m);
return (ENOBUFS);
}
M_ASSERTPKTHDR(m);
if (m->m_pkthdr.fw_flags & PF_MBUF_STRUCTURE)
cl = clh_to_clp(cbqp, m->m_pkthdr.pf.qid);
else
cl = NULL;
if (cl == NULL) {
cl = cbqp->ifnp.default_;
if (cl == NULL) {
m_freem(m);
return (ENOBUFS);
}
}
crit_enter();
cl->pktattr_ = NULL;
len = m_pktlen(m);
if (rmc_queue_packet(cl, m) != 0) {
PKTCNTR_ADD(&cl->stats_.drop_cnt, len);
crit_exit();
return (ENOBUFS);
}
++cbqp->cbq_qlen;
ALTQ_SQ_PKTCNT_INC(ifsq);
crit_exit();
return (0);
}
static struct mbuf *
cbq_dequeue(struct ifaltq_subque *ifsq, int op)
{
struct ifaltq *ifq = ifsq->ifsq_altq;
cbq_state_t *cbqp = (cbq_state_t *)ifq->altq_disc;
struct mbuf *m;
if (ifsq_get_index(ifsq) != CBQ_SUBQ_INDEX) {
ifsq_classic_request(ifsq, ALTRQ_PURGE, NULL);
return NULL;
}
crit_enter();
m = rmc_dequeue_next(&cbqp->ifnp, op);
if (m && op == ALTDQ_REMOVE) {
--cbqp->cbq_qlen;
ALTQ_SQ_PKTCNT_DEC(ifsq);
rmc_update_class_util(&cbqp->ifnp);
}
crit_exit();
return (m);
}
static void
cbqrestart(struct ifaltq *ifq)
{
cbq_state_t *cbqp;
CBQ_ASSERT_LOCKED(ifq);
if (!ifq_is_enabled(ifq))
return;
if ((cbqp = (cbq_state_t *)ifq->altq_disc) == NULL)
return;
if (cbqp->cbq_qlen > 0) {
struct ifnet *ifp = ifq->altq_ifp;
struct ifaltq_subque *ifsq = &ifq->altq_subq[CBQ_SUBQ_INDEX];
CBQ_UNLOCK(ifq);
ifsq_serialize_hw(ifsq);
if (ifp->if_start && !ifsq_is_oactive(ifsq))
(*ifp->if_start)(ifp, ifsq);
ifsq_deserialize_hw(ifsq);
CBQ_LOCK(ifq);
}
}
static void
cbq_purge(cbq_state_t *cbqp)
{
struct rm_class *cl;
int i;
for (i = 0; i < CBQ_MAX_CLASSES; i++) {
if ((cl = cbqp->cbq_class_tbl[i]) != NULL)
rmc_dropall(cl);
}
if (ifq_is_enabled(cbqp->ifnp.ifq_))
ALTQ_SQ_CNTR_RESET(&cbqp->ifnp.ifq_->altq_subq[CBQ_SUBQ_INDEX]);
}
#endif