diff options
Diffstat (limited to 'netwerk/sctp/src/netinet/sctp_ss_functions.c')
-rw-r--r-- | netwerk/sctp/src/netinet/sctp_ss_functions.c | 1121 |
1 files changed, 1121 insertions, 0 deletions
diff --git a/netwerk/sctp/src/netinet/sctp_ss_functions.c b/netwerk/sctp/src/netinet/sctp_ss_functions.c new file mode 100644 index 0000000000..07060c3806 --- /dev/null +++ b/netwerk/sctp/src/netinet/sctp_ss_functions.c @@ -0,0 +1,1121 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2010-2012, by Michael Tuexen. All rights reserved. + * Copyright (c) 2010-2012, by Randall Stewart. All rights reserved. + * Copyright (c) 2010-2012, by Robin Seggelmann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * a) Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * b) 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 THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 <netinet/sctp_os.h> +#include <netinet/sctp_pcb.h> + +/* + * Default simple round-robin algorithm. + * Just iterates the streams in the order they appear. + */ + +static void +sctp_ss_default_add(struct sctp_tcb *, struct sctp_association *, + struct sctp_stream_out *, + struct sctp_stream_queue_pending *); + +static void +sctp_ss_default_remove(struct sctp_tcb *, struct sctp_association *, + struct sctp_stream_out *, + struct sctp_stream_queue_pending *); + +static void +sctp_ss_default_init(struct sctp_tcb *stcb, struct sctp_association *asoc) +{ + uint16_t i; + + SCTP_TCB_LOCK_ASSERT(stcb); + + asoc->ss_data.locked_on_sending = NULL; + asoc->ss_data.last_out_stream = NULL; + TAILQ_INIT(&asoc->ss_data.out.wheel); + /* + * If there is data in the stream queues already, + * the scheduler of an existing association has + * been changed. We need to add all stream queues + * to the wheel. + */ + for (i = 0; i < asoc->streamoutcnt; i++) { + stcb->asoc.ss_functions.sctp_ss_add_to_stream(stcb, asoc, + &asoc->strmout[i], + NULL); + } + return; +} + +static void +sctp_ss_default_clear(struct sctp_tcb *stcb, struct sctp_association *asoc, + bool clear_values SCTP_UNUSED) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + while (!TAILQ_EMPTY(&asoc->ss_data.out.wheel)) { + struct sctp_stream_out *strq; + + strq = TAILQ_FIRST(&asoc->ss_data.out.wheel); + KASSERT(strq->ss_params.scheduled, ("strq %p not scheduled", (void *)strq)); + TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.ss.rr.next_spoke); + strq->ss_params.scheduled = false; + } + asoc->ss_data.last_out_stream = NULL; + return; +} + +static void +sctp_ss_default_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + if (with_strq != NULL) { + if (stcb->asoc.ss_data.locked_on_sending == with_strq) { + stcb->asoc.ss_data.locked_on_sending = strq; + } + if (stcb->asoc.ss_data.last_out_stream == with_strq) { + stcb->asoc.ss_data.last_out_stream = strq; + } + } + strq->ss_params.scheduled = false; + return; +} + +static void +sctp_ss_default_add(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_stream_out *strq, + struct sctp_stream_queue_pending *sp SCTP_UNUSED) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + /* Add to wheel if not already on it and stream queue not empty */ + if (!TAILQ_EMPTY(&strq->outqueue) && !strq->ss_params.scheduled) { + TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, + strq, ss_params.ss.rr.next_spoke); + strq->ss_params.scheduled = true; + } + return; +} + +static bool +sctp_ss_default_is_empty(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + return (TAILQ_EMPTY(&asoc->ss_data.out.wheel)); +} + +static void +sctp_ss_default_remove(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_stream_out *strq, + struct sctp_stream_queue_pending *sp SCTP_UNUSED) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + /* Remove from wheel if stream queue is empty and actually is on the wheel */ + if (TAILQ_EMPTY(&strq->outqueue) && strq->ss_params.scheduled) { + if (asoc->ss_data.last_out_stream == strq) { + asoc->ss_data.last_out_stream = TAILQ_PREV(asoc->ss_data.last_out_stream, + sctpwheel_listhead, + ss_params.ss.rr.next_spoke); + if (asoc->ss_data.last_out_stream == NULL) { + asoc->ss_data.last_out_stream = TAILQ_LAST(&asoc->ss_data.out.wheel, + sctpwheel_listhead); + } + if (asoc->ss_data.last_out_stream == strq) { + asoc->ss_data.last_out_stream = NULL; + } + } + if (asoc->ss_data.locked_on_sending == strq) { + asoc->ss_data.locked_on_sending = NULL; + } + TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.ss.rr.next_spoke); + strq->ss_params.scheduled = false; + } + return; +} + +static struct sctp_stream_out * +sctp_ss_default_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, + struct sctp_association *asoc) +{ + struct sctp_stream_out *strq, *strqt; + + SCTP_TCB_LOCK_ASSERT(stcb); + + if (asoc->ss_data.locked_on_sending != NULL) { + KASSERT(asoc->ss_data.locked_on_sending->ss_params.scheduled, + ("locked_on_sending %p not scheduled", + (void *)asoc->ss_data.locked_on_sending)); + return (asoc->ss_data.locked_on_sending); + } + strqt = asoc->ss_data.last_out_stream; + KASSERT(strqt == NULL || strqt->ss_params.scheduled, + ("last_out_stream %p not scheduled", (void *)strqt)); +default_again: + /* Find the next stream to use */ + if (strqt == NULL) { + strq = TAILQ_FIRST(&asoc->ss_data.out.wheel); + } else { + strq = TAILQ_NEXT(strqt, ss_params.ss.rr.next_spoke); + if (strq == NULL) { + strq = TAILQ_FIRST(&asoc->ss_data.out.wheel); + } + } + KASSERT(strq == NULL || strq->ss_params.scheduled, + ("strq %p not scheduled", (void *)strq)); + + /* If CMT is off, we must validate that + * the stream in question has the first + * item pointed towards are network destination + * requested by the caller. Note that if we + * turn out to be locked to a stream (assigning + * TSN's then we must stop, since we cannot + * look for another stream with data to send + * to that destination). In CMT's case, by + * skipping this check, we will send one + * data packet towards the requested net. + */ + if (net != NULL && strq != NULL && + SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) { + if (TAILQ_FIRST(&strq->outqueue) && + TAILQ_FIRST(&strq->outqueue)->net != NULL && + TAILQ_FIRST(&strq->outqueue)->net != net) { + if (strq == asoc->ss_data.last_out_stream) { + return (NULL); + } else { + strqt = strq; + goto default_again; + } + } + } + return (strq); +} + +static void +sctp_ss_default_scheduled(struct sctp_tcb *stcb, + struct sctp_nets *net SCTP_UNUSED, + struct sctp_association *asoc, + struct sctp_stream_out *strq, + int moved_how_much SCTP_UNUSED) +{ + struct sctp_stream_queue_pending *sp; + + KASSERT(strq != NULL, ("strq is NULL")); + KASSERT(strq->ss_params.scheduled, ("strq %p is not scheduled", (void *)strq)); + SCTP_TCB_LOCK_ASSERT(stcb); + + asoc->ss_data.last_out_stream = strq; + if (asoc->idata_supported == 0) { + sp = TAILQ_FIRST(&strq->outqueue); + if ((sp != NULL) && (sp->some_taken == 1)) { + asoc->ss_data.locked_on_sending = strq; + } else { + asoc->ss_data.locked_on_sending = NULL; + } + } else { + asoc->ss_data.locked_on_sending = NULL; + } + return; +} + +static void +sctp_ss_default_packet_done(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net SCTP_UNUSED, + struct sctp_association *asoc SCTP_UNUSED) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + /* Nothing to be done here */ + return; +} + +static int +sctp_ss_default_get_value(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc SCTP_UNUSED, + struct sctp_stream_out *strq SCTP_UNUSED, uint16_t *value SCTP_UNUSED) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + /* Nothing to be done here */ + return (-1); +} + +static int +sctp_ss_default_set_value(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc SCTP_UNUSED, + struct sctp_stream_out *strq SCTP_UNUSED, uint16_t value SCTP_UNUSED) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + /* Nothing to be done here */ + return (-1); +} + +static bool +sctp_ss_default_is_user_msgs_incomplete(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc) +{ + struct sctp_stream_out *strq; + struct sctp_stream_queue_pending *sp; + + SCTP_TCB_LOCK_ASSERT(stcb); + + if (asoc->stream_queue_cnt != 1) { + return (false); + } + strq = asoc->ss_data.locked_on_sending; + if (strq == NULL) { + return (false); + } + sp = TAILQ_FIRST(&strq->outqueue); + if (sp == NULL) { + return (false); + } + return (sp->msg_is_complete == 0); +} + +/* + * Real round-robin algorithm. + * Always iterates the streams in ascending order. + */ +static void +sctp_ss_rr_add(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_stream_out *strq, + struct sctp_stream_queue_pending *sp SCTP_UNUSED) +{ + struct sctp_stream_out *strqt; + + SCTP_TCB_LOCK_ASSERT(stcb); + + if (!TAILQ_EMPTY(&strq->outqueue) && !strq->ss_params.scheduled) { + if (TAILQ_EMPTY(&asoc->ss_data.out.wheel)) { + TAILQ_INSERT_HEAD(&asoc->ss_data.out.wheel, strq, ss_params.ss.rr.next_spoke); + } else { + strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel); + while (strqt != NULL && (strqt->sid < strq->sid)) { + strqt = TAILQ_NEXT(strqt, ss_params.ss.rr.next_spoke); + } + if (strqt != NULL) { + TAILQ_INSERT_BEFORE(strqt, strq, ss_params.ss.rr.next_spoke); + } else { + TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, strq, ss_params.ss.rr.next_spoke); + } + } + strq->ss_params.scheduled = true; + } + return; +} + +/* + * Real round-robin per packet algorithm. + * Always iterates the streams in ascending order and + * only fills messages of the same stream in a packet. + */ +static struct sctp_stream_out * +sctp_ss_rrp_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net SCTP_UNUSED, + struct sctp_association *asoc) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + return (asoc->ss_data.last_out_stream); +} + +static void +sctp_ss_rrp_packet_done(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, + struct sctp_association *asoc) +{ + struct sctp_stream_out *strq, *strqt; + + SCTP_TCB_LOCK_ASSERT(stcb); + + strqt = asoc->ss_data.last_out_stream; + KASSERT(strqt == NULL || strqt->ss_params.scheduled, + ("last_out_stream %p not scheduled", (void *)strqt)); +rrp_again: + /* Find the next stream to use */ + if (strqt == NULL) { + strq = TAILQ_FIRST(&asoc->ss_data.out.wheel); + } else { + strq = TAILQ_NEXT(strqt, ss_params.ss.rr.next_spoke); + if (strq == NULL) { + strq = TAILQ_FIRST(&asoc->ss_data.out.wheel); + } + } + KASSERT(strq == NULL || strq->ss_params.scheduled, + ("strq %p not scheduled", (void *)strq)); + + /* If CMT is off, we must validate that + * the stream in question has the first + * item pointed towards are network destination + * requested by the caller. Note that if we + * turn out to be locked to a stream (assigning + * TSN's then we must stop, since we cannot + * look for another stream with data to send + * to that destination). In CMT's case, by + * skipping this check, we will send one + * data packet towards the requested net. + */ + if (net != NULL && strq != NULL && + SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) { + if (TAILQ_FIRST(&strq->outqueue) && + TAILQ_FIRST(&strq->outqueue)->net != NULL && + TAILQ_FIRST(&strq->outqueue)->net != net) { + if (strq == asoc->ss_data.last_out_stream) { + strq = NULL; + } else { + strqt = strq; + goto rrp_again; + } + } + } + asoc->ss_data.last_out_stream = strq; + return; +} + +/* + * Priority algorithm. + * Always prefers streams based on their priority id. + */ +static void +sctp_ss_prio_clear(struct sctp_tcb *stcb, struct sctp_association *asoc, + bool clear_values) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + while (!TAILQ_EMPTY(&asoc->ss_data.out.wheel)) { + struct sctp_stream_out *strq; + + strq = TAILQ_FIRST(&asoc->ss_data.out.wheel); + KASSERT(strq->ss_params.scheduled, ("strq %p not scheduled", (void *)strq)); + if (clear_values) { + strq->ss_params.ss.prio.priority = 0; + } + TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.ss.prio.next_spoke); + strq->ss_params.scheduled = false; + } + asoc->ss_data.last_out_stream = NULL; + return; +} + +static void +sctp_ss_prio_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + if (with_strq != NULL) { + if (stcb->asoc.ss_data.locked_on_sending == with_strq) { + stcb->asoc.ss_data.locked_on_sending = strq; + } + if (stcb->asoc.ss_data.last_out_stream == with_strq) { + stcb->asoc.ss_data.last_out_stream = strq; + } + } + strq->ss_params.scheduled = false; + if (with_strq != NULL) { + strq->ss_params.ss.prio.priority = with_strq->ss_params.ss.prio.priority; + } else { + strq->ss_params.ss.prio.priority = 0; + } + return; +} + +static void +sctp_ss_prio_add(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED) +{ + struct sctp_stream_out *strqt; + + SCTP_TCB_LOCK_ASSERT(stcb); + + /* Add to wheel if not already on it and stream queue not empty */ + if (!TAILQ_EMPTY(&strq->outqueue) && !strq->ss_params.scheduled) { + if (TAILQ_EMPTY(&asoc->ss_data.out.wheel)) { + TAILQ_INSERT_HEAD(&asoc->ss_data.out.wheel, strq, ss_params.ss.prio.next_spoke); + } else { + strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel); + while (strqt != NULL && strqt->ss_params.ss.prio.priority < strq->ss_params.ss.prio.priority) { + strqt = TAILQ_NEXT(strqt, ss_params.ss.prio.next_spoke); + } + if (strqt != NULL) { + TAILQ_INSERT_BEFORE(strqt, strq, ss_params.ss.prio.next_spoke); + } else { + TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, strq, ss_params.ss.prio.next_spoke); + } + } + strq->ss_params.scheduled = true; + } + return; +} + +static void +sctp_ss_prio_remove(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + /* Remove from wheel if stream queue is empty and actually is on the wheel */ + if (TAILQ_EMPTY(&strq->outqueue) && strq->ss_params.scheduled) { + if (asoc->ss_data.last_out_stream == strq) { + asoc->ss_data.last_out_stream = TAILQ_PREV(asoc->ss_data.last_out_stream, + sctpwheel_listhead, + ss_params.ss.prio.next_spoke); + if (asoc->ss_data.last_out_stream == NULL) { + asoc->ss_data.last_out_stream = TAILQ_LAST(&asoc->ss_data.out.wheel, + sctpwheel_listhead); + } + if (asoc->ss_data.last_out_stream == strq) { + asoc->ss_data.last_out_stream = NULL; + } + } + if (asoc->ss_data.locked_on_sending == strq) { + asoc->ss_data.locked_on_sending = NULL; + } + TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.ss.prio.next_spoke); + strq->ss_params.scheduled = false; + } + return; +} + +static struct sctp_stream_out* +sctp_ss_prio_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, + struct sctp_association *asoc) +{ + struct sctp_stream_out *strq, *strqt, *strqn; + + SCTP_TCB_LOCK_ASSERT(stcb); + + if (asoc->ss_data.locked_on_sending != NULL) { + KASSERT(asoc->ss_data.locked_on_sending->ss_params.scheduled, + ("locked_on_sending %p not scheduled", + (void *)asoc->ss_data.locked_on_sending)); + return (asoc->ss_data.locked_on_sending); + } + strqt = asoc->ss_data.last_out_stream; + KASSERT(strqt == NULL || strqt->ss_params.scheduled, + ("last_out_stream %p not scheduled", (void *)strqt)); +prio_again: + /* Find the next stream to use */ + if (strqt == NULL) { + strq = TAILQ_FIRST(&asoc->ss_data.out.wheel); + } else { + strqn = TAILQ_NEXT(strqt, ss_params.ss.prio.next_spoke); + if (strqn != NULL && + strqn->ss_params.ss.prio.priority == strqt->ss_params.ss.prio.priority) { + strq = strqn; + } else { + strq = TAILQ_FIRST(&asoc->ss_data.out.wheel); + } + } + KASSERT(strq == NULL || strq->ss_params.scheduled, + ("strq %p not scheduled", (void *)strq)); + + /* If CMT is off, we must validate that + * the stream in question has the first + * item pointed towards are network destination + * requested by the caller. Note that if we + * turn out to be locked to a stream (assigning + * TSN's then we must stop, since we cannot + * look for another stream with data to send + * to that destination). In CMT's case, by + * skipping this check, we will send one + * data packet towards the requested net. + */ + if (net != NULL && strq != NULL && + SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) { + if (TAILQ_FIRST(&strq->outqueue) && + TAILQ_FIRST(&strq->outqueue)->net != NULL && + TAILQ_FIRST(&strq->outqueue)->net != net) { + if (strq == asoc->ss_data.last_out_stream) { + return (NULL); + } else { + strqt = strq; + goto prio_again; + } + } + } + return (strq); +} + +static int +sctp_ss_prio_get_value(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc SCTP_UNUSED, + struct sctp_stream_out *strq, uint16_t *value) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + if (strq == NULL) { + return (-1); + } + *value = strq->ss_params.ss.prio.priority; + return (1); +} + +static int +sctp_ss_prio_set_value(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_stream_out *strq, uint16_t value) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + if (strq == NULL) { + return (-1); + } + strq->ss_params.ss.prio.priority = value; + sctp_ss_prio_remove(stcb, asoc, strq, NULL); + sctp_ss_prio_add(stcb, asoc, strq, NULL); + return (1); +} + +/* + * Fair bandwidth algorithm. + * Maintains an equal throughput per stream. + */ +static void +sctp_ss_fb_clear(struct sctp_tcb *stcb, struct sctp_association *asoc, + bool clear_values) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + while (!TAILQ_EMPTY(&asoc->ss_data.out.wheel)) { + struct sctp_stream_out *strq; + + strq = TAILQ_FIRST(&asoc->ss_data.out.wheel); + KASSERT(strq->ss_params.scheduled, ("strq %p not scheduled", (void *)strq)); + if (clear_values) { + strq->ss_params.ss.fb.rounds = -1; + } + TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.ss.fb.next_spoke); + strq->ss_params.scheduled = false; + } + asoc->ss_data.last_out_stream = NULL; + return; +} + +static void +sctp_ss_fb_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + if (with_strq != NULL) { + if (stcb->asoc.ss_data.locked_on_sending == with_strq) { + stcb->asoc.ss_data.locked_on_sending = strq; + } + if (stcb->asoc.ss_data.last_out_stream == with_strq) { + stcb->asoc.ss_data.last_out_stream = strq; + } + } + strq->ss_params.scheduled = false; + if (with_strq != NULL) { + strq->ss_params.ss.fb.rounds = with_strq->ss_params.ss.fb.rounds; + } else { + strq->ss_params.ss.fb.rounds = -1; + } + return; +} + +static void +sctp_ss_fb_add(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + if (!TAILQ_EMPTY(&strq->outqueue) && !strq->ss_params.scheduled) { + if (strq->ss_params.ss.fb.rounds < 0) + strq->ss_params.ss.fb.rounds = TAILQ_FIRST(&strq->outqueue)->length; + TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, strq, ss_params.ss.fb.next_spoke); + strq->ss_params.scheduled = true; + } + return; +} + +static void +sctp_ss_fb_remove(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + /* Remove from wheel if stream queue is empty and actually is on the wheel */ + if (TAILQ_EMPTY(&strq->outqueue) && strq->ss_params.scheduled) { + if (asoc->ss_data.last_out_stream == strq) { + asoc->ss_data.last_out_stream = TAILQ_PREV(asoc->ss_data.last_out_stream, + sctpwheel_listhead, + ss_params.ss.fb.next_spoke); + if (asoc->ss_data.last_out_stream == NULL) { + asoc->ss_data.last_out_stream = TAILQ_LAST(&asoc->ss_data.out.wheel, + sctpwheel_listhead); + } + if (asoc->ss_data.last_out_stream == strq) { + asoc->ss_data.last_out_stream = NULL; + } + } + if (asoc->ss_data.locked_on_sending == strq) { + asoc->ss_data.locked_on_sending = NULL; + } + TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.ss.fb.next_spoke); + strq->ss_params.scheduled = false; + } + return; +} + +static struct sctp_stream_out* +sctp_ss_fb_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, + struct sctp_association *asoc) +{ + struct sctp_stream_out *strq = NULL, *strqt; + + SCTP_TCB_LOCK_ASSERT(stcb); + + if (asoc->ss_data.locked_on_sending != NULL) { + KASSERT(asoc->ss_data.locked_on_sending->ss_params.scheduled, + ("locked_on_sending %p not scheduled", + (void *)asoc->ss_data.locked_on_sending)); + return (asoc->ss_data.locked_on_sending); + } + if (asoc->ss_data.last_out_stream == NULL || + TAILQ_FIRST(&asoc->ss_data.out.wheel) == TAILQ_LAST(&asoc->ss_data.out.wheel, sctpwheel_listhead)) { + strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel); + } else { + strqt = TAILQ_NEXT(asoc->ss_data.last_out_stream, ss_params.ss.fb.next_spoke); + } + do { + if ((strqt != NULL) && + ((SCTP_BASE_SYSCTL(sctp_cmt_on_off) > 0) || + (SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0 && + (net == NULL || (TAILQ_FIRST(&strqt->outqueue) && TAILQ_FIRST(&strqt->outqueue)->net == NULL) || + (net != NULL && TAILQ_FIRST(&strqt->outqueue) && TAILQ_FIRST(&strqt->outqueue)->net != NULL && + TAILQ_FIRST(&strqt->outqueue)->net == net))))) { + if ((strqt->ss_params.ss.fb.rounds >= 0) && + ((strq == NULL) || + (strqt->ss_params.ss.fb.rounds < strq->ss_params.ss.fb.rounds))) { + strq = strqt; + } + } + if (strqt != NULL) { + strqt = TAILQ_NEXT(strqt, ss_params.ss.fb.next_spoke); + } else { + strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel); + } + } while (strqt != strq); + return (strq); +} + +static void +sctp_ss_fb_scheduled(struct sctp_tcb *stcb, struct sctp_nets *net SCTP_UNUSED, + struct sctp_association *asoc, struct sctp_stream_out *strq, + int moved_how_much SCTP_UNUSED) +{ + struct sctp_stream_queue_pending *sp; + struct sctp_stream_out *strqt; + int subtract; + + SCTP_TCB_LOCK_ASSERT(stcb); + + if (asoc->idata_supported == 0) { + sp = TAILQ_FIRST(&strq->outqueue); + if ((sp != NULL) && (sp->some_taken == 1)) { + asoc->ss_data.locked_on_sending = strq; + } else { + asoc->ss_data.locked_on_sending = NULL; + } + } else { + asoc->ss_data.locked_on_sending = NULL; + } + subtract = strq->ss_params.ss.fb.rounds; + TAILQ_FOREACH(strqt, &asoc->ss_data.out.wheel, ss_params.ss.fb.next_spoke) { + strqt->ss_params.ss.fb.rounds -= subtract; + if (strqt->ss_params.ss.fb.rounds < 0) + strqt->ss_params.ss.fb.rounds = 0; + } + if (TAILQ_FIRST(&strq->outqueue)) { + strq->ss_params.ss.fb.rounds = TAILQ_FIRST(&strq->outqueue)->length; + } else { + strq->ss_params.ss.fb.rounds = -1; + } + asoc->ss_data.last_out_stream = strq; + return; +} + +/* + * First-come, first-serve algorithm. + * Maintains the order provided by the application. + */ +static void +sctp_ss_fcfs_add(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_stream_out *strq SCTP_UNUSED, + struct sctp_stream_queue_pending *sp); + +static void +sctp_ss_fcfs_init(struct sctp_tcb *stcb, struct sctp_association *asoc) +{ + uint32_t x, n = 0, add_more = 1; + struct sctp_stream_queue_pending *sp; + uint16_t i; + + SCTP_TCB_LOCK_ASSERT(stcb); + + TAILQ_INIT(&asoc->ss_data.out.list); + /* + * If there is data in the stream queues already, + * the scheduler of an existing association has + * been changed. We can only cycle through the + * stream queues and add everything to the FCFS + * queue. + */ + while (add_more) { + add_more = 0; + for (i = 0; i < asoc->streamoutcnt; i++) { + sp = TAILQ_FIRST(&asoc->strmout[i].outqueue); + x = 0; + /* Find n. message in current stream queue */ + while (sp != NULL && x < n) { + sp = TAILQ_NEXT(sp, next); + x++; + } + if (sp != NULL) { + sctp_ss_fcfs_add(stcb, asoc, &asoc->strmout[i], sp); + add_more = 1; + } + } + n++; + } + return; +} + +static void +sctp_ss_fcfs_clear(struct sctp_tcb *stcb, struct sctp_association *asoc, + bool clear_values SCTP_UNUSED) +{ + struct sctp_stream_queue_pending *sp; + + SCTP_TCB_LOCK_ASSERT(stcb); + + while (!TAILQ_EMPTY(&asoc->ss_data.out.list)) { + sp = TAILQ_FIRST(&asoc->ss_data.out.list); + KASSERT(sp->scheduled, ("sp %p not scheduled", (void *)sp)); + TAILQ_REMOVE(&asoc->ss_data.out.list, sp, ss_next); + sp->scheduled = false; + } + asoc->ss_data.last_out_stream = NULL; + return; +} + +static void +sctp_ss_fcfs_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + if (with_strq != NULL) { + if (stcb->asoc.ss_data.locked_on_sending == with_strq) { + stcb->asoc.ss_data.locked_on_sending = strq; + } + if (stcb->asoc.ss_data.last_out_stream == with_strq) { + stcb->asoc.ss_data.last_out_stream = strq; + } + } + strq->ss_params.scheduled = false; + return; +} + +static void +sctp_ss_fcfs_add(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_stream_out *strq SCTP_UNUSED, struct sctp_stream_queue_pending *sp) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + if (!sp->scheduled) { + TAILQ_INSERT_TAIL(&asoc->ss_data.out.list, sp, ss_next); + sp->scheduled = true; + } + return; +} + +static bool +sctp_ss_fcfs_is_empty(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + return (TAILQ_EMPTY(&asoc->ss_data.out.list)); +} + +static void +sctp_ss_fcfs_remove(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_stream_out *strq SCTP_UNUSED, struct sctp_stream_queue_pending *sp) +{ + SCTP_TCB_LOCK_ASSERT(stcb); + + if (sp->scheduled) { + TAILQ_REMOVE(&asoc->ss_data.out.list, sp, ss_next); + sp->scheduled = false; + } + return; +} + +static struct sctp_stream_out * +sctp_ss_fcfs_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, + struct sctp_association *asoc) +{ + struct sctp_stream_out *strq; + struct sctp_stream_queue_pending *sp; + + SCTP_TCB_LOCK_ASSERT(stcb); + + if (asoc->ss_data.locked_on_sending) { + return (asoc->ss_data.locked_on_sending); + } + sp = TAILQ_FIRST(&asoc->ss_data.out.list); +default_again: + if (sp != NULL) { + strq = &asoc->strmout[sp->sid]; + } else { + strq = NULL; + } + + /* + * If CMT is off, we must validate that + * the stream in question has the first + * item pointed towards are network destination + * requested by the caller. Note that if we + * turn out to be locked to a stream (assigning + * TSN's then we must stop, since we cannot + * look for another stream with data to send + * to that destination). In CMT's case, by + * skipping this check, we will send one + * data packet towards the requested net. + */ + if (net != NULL && strq != NULL && + SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) { + if (TAILQ_FIRST(&strq->outqueue) && + TAILQ_FIRST(&strq->outqueue)->net != NULL && + TAILQ_FIRST(&strq->outqueue)->net != net) { + sp = TAILQ_NEXT(sp, ss_next); + goto default_again; + } + } + return (strq); +} + +static void +sctp_ss_fcfs_scheduled(struct sctp_tcb *stcb, + struct sctp_nets *net SCTP_UNUSED, + struct sctp_association *asoc, + struct sctp_stream_out *strq, + int moved_how_much SCTP_UNUSED) +{ + struct sctp_stream_queue_pending *sp; + + KASSERT(strq != NULL, ("strq is NULL")); + asoc->ss_data.last_out_stream = strq; + if (asoc->idata_supported == 0) { + sp = TAILQ_FIRST(&strq->outqueue); + if ((sp != NULL) && (sp->some_taken == 1)) { + asoc->ss_data.locked_on_sending = strq; + } else { + asoc->ss_data.locked_on_sending = NULL; + } + } else { + asoc->ss_data.locked_on_sending = NULL; + } + return; +} + +const struct sctp_ss_functions sctp_ss_functions[] = { +/* SCTP_SS_DEFAULT */ +{ +#if defined(_WIN32) + sctp_ss_default_init, + sctp_ss_default_clear, + sctp_ss_default_init_stream, + sctp_ss_default_add, + sctp_ss_default_is_empty, + sctp_ss_default_remove, + sctp_ss_default_select, + sctp_ss_default_scheduled, + sctp_ss_default_packet_done, + sctp_ss_default_get_value, + sctp_ss_default_set_value, + sctp_ss_default_is_user_msgs_incomplete +#else + .sctp_ss_init = sctp_ss_default_init, + .sctp_ss_clear = sctp_ss_default_clear, + .sctp_ss_init_stream = sctp_ss_default_init_stream, + .sctp_ss_add_to_stream = sctp_ss_default_add, + .sctp_ss_is_empty = sctp_ss_default_is_empty, + .sctp_ss_remove_from_stream = sctp_ss_default_remove, + .sctp_ss_select_stream = sctp_ss_default_select, + .sctp_ss_scheduled = sctp_ss_default_scheduled, + .sctp_ss_packet_done = sctp_ss_default_packet_done, + .sctp_ss_get_value = sctp_ss_default_get_value, + .sctp_ss_set_value = sctp_ss_default_set_value, + .sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete +#endif +}, +/* SCTP_SS_RR */ +{ +#if defined(_WIN32) + sctp_ss_default_init, + sctp_ss_default_clear, + sctp_ss_default_init_stream, + sctp_ss_rr_add, + sctp_ss_default_is_empty, + sctp_ss_default_remove, + sctp_ss_default_select, + sctp_ss_default_scheduled, + sctp_ss_default_packet_done, + sctp_ss_default_get_value, + sctp_ss_default_set_value, + sctp_ss_default_is_user_msgs_incomplete +#else + .sctp_ss_init = sctp_ss_default_init, + .sctp_ss_clear = sctp_ss_default_clear, + .sctp_ss_init_stream = sctp_ss_default_init_stream, + .sctp_ss_add_to_stream = sctp_ss_rr_add, + .sctp_ss_is_empty = sctp_ss_default_is_empty, + .sctp_ss_remove_from_stream = sctp_ss_default_remove, + .sctp_ss_select_stream = sctp_ss_default_select, + .sctp_ss_scheduled = sctp_ss_default_scheduled, + .sctp_ss_packet_done = sctp_ss_default_packet_done, + .sctp_ss_get_value = sctp_ss_default_get_value, + .sctp_ss_set_value = sctp_ss_default_set_value, + .sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete +#endif +}, +/* SCTP_SS_RR_PKT */ +{ +#if defined(_WIN32) + sctp_ss_default_init, + sctp_ss_default_clear, + sctp_ss_default_init_stream, + sctp_ss_rr_add, + sctp_ss_default_is_empty, + sctp_ss_default_remove, + sctp_ss_rrp_select, + sctp_ss_default_scheduled, + sctp_ss_rrp_packet_done, + sctp_ss_default_get_value, + sctp_ss_default_set_value, + sctp_ss_default_is_user_msgs_incomplete +#else + .sctp_ss_init = sctp_ss_default_init, + .sctp_ss_clear = sctp_ss_default_clear, + .sctp_ss_init_stream = sctp_ss_default_init_stream, + .sctp_ss_add_to_stream = sctp_ss_rr_add, + .sctp_ss_is_empty = sctp_ss_default_is_empty, + .sctp_ss_remove_from_stream = sctp_ss_default_remove, + .sctp_ss_select_stream = sctp_ss_rrp_select, + .sctp_ss_scheduled = sctp_ss_default_scheduled, + .sctp_ss_packet_done = sctp_ss_rrp_packet_done, + .sctp_ss_get_value = sctp_ss_default_get_value, + .sctp_ss_set_value = sctp_ss_default_set_value, + .sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete +#endif +}, +/* SCTP_SS_PRIO */ +{ +#if defined(_WIN32) + sctp_ss_default_init, + sctp_ss_prio_clear, + sctp_ss_prio_init_stream, + sctp_ss_prio_add, + sctp_ss_default_is_empty, + sctp_ss_prio_remove, + sctp_ss_prio_select, + sctp_ss_default_scheduled, + sctp_ss_default_packet_done, + sctp_ss_prio_get_value, + sctp_ss_prio_set_value, + sctp_ss_default_is_user_msgs_incomplete +#else + .sctp_ss_init = sctp_ss_default_init, + .sctp_ss_clear = sctp_ss_prio_clear, + .sctp_ss_init_stream = sctp_ss_prio_init_stream, + .sctp_ss_add_to_stream = sctp_ss_prio_add, + .sctp_ss_is_empty = sctp_ss_default_is_empty, + .sctp_ss_remove_from_stream = sctp_ss_prio_remove, + .sctp_ss_select_stream = sctp_ss_prio_select, + .sctp_ss_scheduled = sctp_ss_default_scheduled, + .sctp_ss_packet_done = sctp_ss_default_packet_done, + .sctp_ss_get_value = sctp_ss_prio_get_value, + .sctp_ss_set_value = sctp_ss_prio_set_value, + .sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete +#endif +}, +/* SCTP_SS_FB */ +{ +#if defined(_WIN32) + sctp_ss_default_init, + sctp_ss_fb_clear, + sctp_ss_fb_init_stream, + sctp_ss_fb_add, + sctp_ss_default_is_empty, + sctp_ss_fb_remove, + sctp_ss_fb_select, + sctp_ss_fb_scheduled, + sctp_ss_default_packet_done, + sctp_ss_default_get_value, + sctp_ss_default_set_value, + sctp_ss_default_is_user_msgs_incomplete +#else + .sctp_ss_init = sctp_ss_default_init, + .sctp_ss_clear = sctp_ss_fb_clear, + .sctp_ss_init_stream = sctp_ss_fb_init_stream, + .sctp_ss_add_to_stream = sctp_ss_fb_add, + .sctp_ss_is_empty = sctp_ss_default_is_empty, + .sctp_ss_remove_from_stream = sctp_ss_fb_remove, + .sctp_ss_select_stream = sctp_ss_fb_select, + .sctp_ss_scheduled = sctp_ss_fb_scheduled, + .sctp_ss_packet_done = sctp_ss_default_packet_done, + .sctp_ss_get_value = sctp_ss_default_get_value, + .sctp_ss_set_value = sctp_ss_default_set_value, + .sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete +#endif +}, +/* SCTP_SS_FCFS */ +{ +#if defined(_WIN32) + sctp_ss_fcfs_init, + sctp_ss_fcfs_clear, + sctp_ss_fcfs_init_stream, + sctp_ss_fcfs_add, + sctp_ss_fcfs_is_empty, + sctp_ss_fcfs_remove, + sctp_ss_fcfs_select, + sctp_ss_fcfs_scheduled, + sctp_ss_default_packet_done, + sctp_ss_default_get_value, + sctp_ss_default_set_value, + sctp_ss_default_is_user_msgs_incomplete +#else + .sctp_ss_init = sctp_ss_fcfs_init, + .sctp_ss_clear = sctp_ss_fcfs_clear, + .sctp_ss_init_stream = sctp_ss_fcfs_init_stream, + .sctp_ss_add_to_stream = sctp_ss_fcfs_add, + .sctp_ss_is_empty = sctp_ss_fcfs_is_empty, + .sctp_ss_remove_from_stream = sctp_ss_fcfs_remove, + .sctp_ss_select_stream = sctp_ss_fcfs_select, + .sctp_ss_scheduled = sctp_ss_fcfs_scheduled, + .sctp_ss_packet_done = sctp_ss_default_packet_done, + .sctp_ss_get_value = sctp_ss_default_get_value, + .sctp_ss_set_value = sctp_ss_default_set_value, + .sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete +#endif +} +}; |