blob: 86c3ee1b83556eb8a991b5992f2bbe07c3f11329 [file] [log] [blame]
/*
* sch_plug.c Queue traffic until an explicit release command
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* The operation of the buffer is as follows:
* When a checkpoint begins, a plug is inserted into the
* network queue by a netlink request (it operates by storing
* a pointer to the next packet which arrives and blocking dequeue
* when that packet is at the head of the queue).
* When a checkpoint completes (the backup acknowledges receipt),
* currently-queued packets are released.
* So it supports two operations, plug and unplug.
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <net/pkt_sched.h>
#define FIFO_BUF (10*1024*1024)
#define TCQ_PLUG 0
#define TCQ_UNPLUG 1
struct plug_sched_data {
/*
* This packet is the first packet which should not be
* delivered. If it is NULL, plug_enqueue will set it to the
* next packet it sees.
*/
struct sk_buff *stop;
};
struct tc_plug_qopt {
/* 0: reset stop packet pointer
* 1: dequeue to stop pointer */
int action;
};
static int skb_remove_foreign_references(struct sk_buff *skb)
{
return !skb_linearize(skb);
}
static int plug_enqueue(struct sk_buff *skb, struct Qdisc* sch)
{
struct plug_sched_data *q = qdisc_priv(sch);
if (likely(sch->qstats.backlog + skb->len <= FIFO_BUF)) {
if (!q->stop)
q->stop = skb;
if (!skb_remove_foreign_references(skb)) {
printk(KERN_DEBUG "error removing foreign ref\n");
return qdisc_reshape_fail(skb, sch);
}
return qdisc_enqueue_tail(skb, sch);
}
printk(KERN_WARNING "queue reported full: %d,%d\n",
sch->qstats.backlog, skb->len);
return qdisc_reshape_fail(skb, sch);
}
/* dequeue doesn't actually dequeue until the release command is
* received. */
static struct sk_buff *plug_dequeue(struct Qdisc* sch)
{
struct plug_sched_data *q = qdisc_priv(sch);
struct sk_buff *peek;
if (sch->flags & TCQ_F_THROTTLED)
return NULL;
peek = (struct sk_buff *)((sch->q).next);
/* this pointer comparison may be shady */
if (peek == q->stop) {
/*
* This is the tail of the last round. Release it and
* block the queue
*/
sch->flags |= TCQ_F_THROTTLED;
return NULL;
}
return qdisc_dequeue_head(sch);
}
static int plug_init(struct Qdisc *sch, struct nlattr *opt)
{
sch->flags |= TCQ_F_THROTTLED;
return 0;
}
/*
* receives two messages:
* 0: checkpoint queue (set stop to next packet)
* 1: dequeue until stop
*/
static int plug_change(struct Qdisc *sch, struct nlattr *opt)
{
struct plug_sched_data *q = qdisc_priv(sch);
struct tc_plug_qopt *msg;
if (!opt || nla_len(opt) < sizeof(*msg))
return -EINVAL;
msg = nla_data(opt);
if (msg->action == TCQ_PLUG) {
/* reset stop */
q->stop = NULL;
} else if (msg->action == TCQ_UNPLUG) {
/* dequeue */
sch->flags &= ~TCQ_F_THROTTLED;
netif_schedule_queue(sch->dev_queue);
} else {
return -EINVAL;
}
return 0;
}
struct Qdisc_ops plug_qdisc_ops = {
.id = "plug",
.priv_size = sizeof(struct plug_sched_data),
.enqueue = plug_enqueue,
.dequeue = plug_dequeue,
.peek = qdisc_peek_head,
.init = plug_init,
.change = plug_change,
.owner = THIS_MODULE,
};
static int __init plug_module_init(void)
{
return register_qdisc(&plug_qdisc_ops);
}
static void __exit plug_module_exit(void)
{
unregister_qdisc(&plug_qdisc_ops);
}
module_init(plug_module_init)
module_exit(plug_module_exit)
MODULE_LICENSE("GPL");