michael@0: /*- michael@0: * Copyright (c) 2010-2012, by Michael Tuexen. All rights reserved. michael@0: * Copyright (c) 2010-2012, by Randall Stewart. All rights reserved. michael@0: * Copyright (c) 2010-2012, by Robin Seggelmann. All rights reserved. michael@0: * michael@0: * Redistribution and use in source and binary forms, with or without michael@0: * modification, are permitted provided that the following conditions are met: michael@0: * michael@0: * a) Redistributions of source code must retain the above copyright notice, michael@0: * this list of conditions and the following disclaimer. michael@0: * michael@0: * b) Redistributions in binary form must reproduce the above copyright michael@0: * notice, this list of conditions and the following disclaimer in michael@0: * the documentation and/or other materials provided with the distribution. michael@0: * michael@0: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, michael@0: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE michael@0: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE michael@0: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR michael@0: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF michael@0: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS michael@0: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN michael@0: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) michael@0: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF michael@0: * THE POSSIBILITY OF SUCH DAMAGE. michael@0: */ michael@0: michael@0: #ifdef __FreeBSD__ michael@0: #include michael@0: __FBSDID("$FreeBSD: head/sys/netinet/sctp_ss_functions.c 235828 2012-05-23 11:26:28Z tuexen $"); michael@0: #endif michael@0: michael@0: #include michael@0: #if defined(__Userspace__) michael@0: #include michael@0: #endif michael@0: michael@0: /* michael@0: * Default simple round-robin algorithm. michael@0: * Just interates the streams in the order they appear. michael@0: */ michael@0: michael@0: static void michael@0: sctp_ss_default_add(struct sctp_tcb *, struct sctp_association *, michael@0: struct sctp_stream_out *, michael@0: struct sctp_stream_queue_pending *, int); michael@0: michael@0: static void michael@0: sctp_ss_default_remove(struct sctp_tcb *, struct sctp_association *, michael@0: struct sctp_stream_out *, michael@0: struct sctp_stream_queue_pending *, int); michael@0: michael@0: static void michael@0: sctp_ss_default_init(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: int holds_lock) michael@0: { michael@0: uint16_t i; michael@0: michael@0: TAILQ_INIT(&asoc->ss_data.out_wheel); michael@0: /* michael@0: * If there is data in the stream queues already, michael@0: * the scheduler of an existing association has michael@0: * been changed. We need to add all stream queues michael@0: * to the wheel. michael@0: */ michael@0: for (i = 0; i < stcb->asoc.streamoutcnt; i++) { michael@0: stcb->asoc.ss_functions.sctp_ss_add_to_stream(stcb, &stcb->asoc, michael@0: &stcb->asoc.strmout[i], michael@0: NULL, holds_lock); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_default_clear(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: int clear_values SCTP_UNUSED, int holds_lock) michael@0: { michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: while (!TAILQ_EMPTY(&asoc->ss_data.out_wheel)) { michael@0: struct sctp_stream_out *strq = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: TAILQ_REMOVE(&asoc->ss_data.out_wheel, TAILQ_FIRST(&asoc->ss_data.out_wheel), ss_params.rr.next_spoke); michael@0: strq->ss_params.rr.next_spoke.tqe_next = NULL; michael@0: strq->ss_params.rr.next_spoke.tqe_prev = NULL; michael@0: } michael@0: asoc->last_out_stream = NULL; michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_default_init_stream(struct sctp_stream_out *strq, struct sctp_stream_out *with_strq SCTP_UNUSED) michael@0: { michael@0: strq->ss_params.rr.next_spoke.tqe_next = NULL; michael@0: strq->ss_params.rr.next_spoke.tqe_prev = NULL; michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_default_add(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: struct sctp_stream_out *strq, michael@0: struct sctp_stream_queue_pending *sp SCTP_UNUSED, int holds_lock) michael@0: { michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: /* Add to wheel if not already on it and stream queue not empty */ michael@0: if (!TAILQ_EMPTY(&strq->outqueue) && michael@0: (strq->ss_params.rr.next_spoke.tqe_next == NULL) && michael@0: (strq->ss_params.rr.next_spoke.tqe_prev == NULL)) { michael@0: TAILQ_INSERT_TAIL(&asoc->ss_data.out_wheel, michael@0: strq, ss_params.rr.next_spoke); michael@0: } michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static int michael@0: sctp_ss_default_is_empty(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc) michael@0: { michael@0: if (TAILQ_EMPTY(&asoc->ss_data.out_wheel)) { michael@0: return (1); michael@0: } else { michael@0: return (0); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_default_remove(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: struct sctp_stream_out *strq, michael@0: struct sctp_stream_queue_pending *sp SCTP_UNUSED, int holds_lock) michael@0: { michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: /* Remove from wheel if stream queue is empty and actually is on the wheel */ michael@0: if (TAILQ_EMPTY(&strq->outqueue) && michael@0: (strq->ss_params.rr.next_spoke.tqe_next != NULL || michael@0: strq->ss_params.rr.next_spoke.tqe_prev != NULL)) { michael@0: if (asoc->last_out_stream == strq) { michael@0: asoc->last_out_stream = TAILQ_PREV(asoc->last_out_stream, michael@0: sctpwheel_listhead, michael@0: ss_params.rr.next_spoke); michael@0: if (asoc->last_out_stream == NULL) { michael@0: asoc->last_out_stream = TAILQ_LAST(&asoc->ss_data.out_wheel, michael@0: sctpwheel_listhead); michael@0: } michael@0: if (asoc->last_out_stream == strq) { michael@0: asoc->last_out_stream = NULL; michael@0: } michael@0: } michael@0: TAILQ_REMOVE(&asoc->ss_data.out_wheel, strq, ss_params.rr.next_spoke); michael@0: strq->ss_params.rr.next_spoke.tqe_next = NULL; michael@0: strq->ss_params.rr.next_spoke.tqe_prev = NULL; michael@0: } michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: michael@0: static struct sctp_stream_out * michael@0: sctp_ss_default_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, michael@0: struct sctp_association *asoc) michael@0: { michael@0: struct sctp_stream_out *strq, *strqt; michael@0: michael@0: strqt = asoc->last_out_stream; michael@0: default_again: michael@0: /* Find the next stream to use */ michael@0: if (strqt == NULL) { michael@0: strq = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: } else { michael@0: strq = TAILQ_NEXT(strqt, ss_params.rr.next_spoke); michael@0: if (strq == NULL) { michael@0: strq = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: } michael@0: } michael@0: michael@0: /* If CMT is off, we must validate that michael@0: * the stream in question has the first michael@0: * item pointed towards are network destination michael@0: * requested by the caller. Note that if we michael@0: * turn out to be locked to a stream (assigning michael@0: * TSN's then we must stop, since we cannot michael@0: * look for another stream with data to send michael@0: * to that destination). In CMT's case, by michael@0: * skipping this check, we will send one michael@0: * data packet towards the requested net. michael@0: */ michael@0: if (net != NULL && strq != NULL && michael@0: SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) { michael@0: if (TAILQ_FIRST(&strq->outqueue) && michael@0: TAILQ_FIRST(&strq->outqueue)->net != NULL && michael@0: TAILQ_FIRST(&strq->outqueue)->net != net) { michael@0: if (strq == asoc->last_out_stream) { michael@0: return (NULL); michael@0: } else { michael@0: strqt = strq; michael@0: goto default_again; michael@0: } michael@0: } michael@0: } michael@0: return (strq); michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_default_scheduled(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net SCTP_UNUSED, michael@0: struct sctp_association *asoc SCTP_UNUSED, michael@0: struct sctp_stream_out *strq, int moved_how_much SCTP_UNUSED) michael@0: { michael@0: asoc->last_out_stream = strq; michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_default_packet_done(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net SCTP_UNUSED, michael@0: struct sctp_association *asoc SCTP_UNUSED) michael@0: { michael@0: /* Nothing to be done here */ michael@0: return; michael@0: } michael@0: michael@0: static int michael@0: sctp_ss_default_get_value(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc SCTP_UNUSED, michael@0: struct sctp_stream_out *strq SCTP_UNUSED, uint16_t *value SCTP_UNUSED) michael@0: { michael@0: /* Nothing to be done here */ michael@0: return (-1); michael@0: } michael@0: michael@0: static int michael@0: sctp_ss_default_set_value(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc SCTP_UNUSED, michael@0: struct sctp_stream_out *strq SCTP_UNUSED, uint16_t value SCTP_UNUSED) michael@0: { michael@0: /* Nothing to be done here */ michael@0: return (-1); michael@0: } michael@0: michael@0: /* michael@0: * Real round-robin algorithm. michael@0: * Always interates the streams in ascending order. michael@0: */ michael@0: static void michael@0: sctp_ss_rr_add(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: struct sctp_stream_out *strq, michael@0: struct sctp_stream_queue_pending *sp SCTP_UNUSED, int holds_lock) michael@0: { michael@0: struct sctp_stream_out *strqt; michael@0: michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: if (!TAILQ_EMPTY(&strq->outqueue) && michael@0: (strq->ss_params.rr.next_spoke.tqe_next == NULL) && michael@0: (strq->ss_params.rr.next_spoke.tqe_prev == NULL)) { michael@0: if (TAILQ_EMPTY(&asoc->ss_data.out_wheel)) { michael@0: TAILQ_INSERT_HEAD(&asoc->ss_data.out_wheel, strq, ss_params.rr.next_spoke); michael@0: } else { michael@0: strqt = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: while (strqt != NULL && (strqt->stream_no < strq->stream_no)) { michael@0: strqt = TAILQ_NEXT(strqt, ss_params.rr.next_spoke); michael@0: } michael@0: if (strqt != NULL) { michael@0: TAILQ_INSERT_BEFORE(strqt, strq, ss_params.rr.next_spoke); michael@0: } else { michael@0: TAILQ_INSERT_TAIL(&asoc->ss_data.out_wheel, strq, ss_params.rr.next_spoke); michael@0: } michael@0: } michael@0: } michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * Real round-robin per packet algorithm. michael@0: * Always interates the streams in ascending order and michael@0: * only fills messages of the same stream in a packet. michael@0: */ michael@0: static struct sctp_stream_out * michael@0: sctp_ss_rrp_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net SCTP_UNUSED, michael@0: struct sctp_association *asoc) michael@0: { michael@0: return (asoc->last_out_stream); michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_rrp_packet_done(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, michael@0: struct sctp_association *asoc) michael@0: { michael@0: struct sctp_stream_out *strq, *strqt; michael@0: michael@0: strqt = asoc->last_out_stream; michael@0: rrp_again: michael@0: /* Find the next stream to use */ michael@0: if (strqt == NULL) { michael@0: strq = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: } else { michael@0: strq = TAILQ_NEXT(strqt, ss_params.rr.next_spoke); michael@0: if (strq == NULL) { michael@0: strq = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: } michael@0: } michael@0: michael@0: /* If CMT is off, we must validate that michael@0: * the stream in question has the first michael@0: * item pointed towards are network destination michael@0: * requested by the caller. Note that if we michael@0: * turn out to be locked to a stream (assigning michael@0: * TSN's then we must stop, since we cannot michael@0: * look for another stream with data to send michael@0: * to that destination). In CMT's case, by michael@0: * skipping this check, we will send one michael@0: * data packet towards the requested net. michael@0: */ michael@0: if (net != NULL && strq != NULL && michael@0: SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) { michael@0: if (TAILQ_FIRST(&strq->outqueue) && michael@0: TAILQ_FIRST(&strq->outqueue)->net != NULL && michael@0: TAILQ_FIRST(&strq->outqueue)->net != net) { michael@0: if (strq == asoc->last_out_stream) { michael@0: strq = NULL; michael@0: } else { michael@0: strqt = strq; michael@0: goto rrp_again; michael@0: } michael@0: } michael@0: } michael@0: asoc->last_out_stream = strq; michael@0: return; michael@0: } michael@0: michael@0: michael@0: /* michael@0: * Priority algorithm. michael@0: * Always prefers streams based on their priority id. michael@0: */ michael@0: static void michael@0: sctp_ss_prio_clear(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: int clear_values, int holds_lock) michael@0: { michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: while (!TAILQ_EMPTY(&asoc->ss_data.out_wheel)) { michael@0: struct sctp_stream_out *strq = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: if (clear_values) { michael@0: strq->ss_params.prio.priority = 0; michael@0: } michael@0: TAILQ_REMOVE(&asoc->ss_data.out_wheel, TAILQ_FIRST(&asoc->ss_data.out_wheel), ss_params.prio.next_spoke); michael@0: strq->ss_params.prio.next_spoke.tqe_next = NULL; michael@0: strq->ss_params.prio.next_spoke.tqe_prev = NULL; michael@0: michael@0: } michael@0: asoc->last_out_stream = NULL; michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_prio_init_stream(struct sctp_stream_out *strq, struct sctp_stream_out *with_strq) michael@0: { michael@0: strq->ss_params.prio.next_spoke.tqe_next = NULL; michael@0: strq->ss_params.prio.next_spoke.tqe_prev = NULL; michael@0: if (with_strq != NULL) { michael@0: strq->ss_params.prio.priority = with_strq->ss_params.prio.priority; michael@0: } else { michael@0: strq->ss_params.prio.priority = 0; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_prio_add(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED, michael@0: int holds_lock) michael@0: { michael@0: struct sctp_stream_out *strqt; michael@0: michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: /* Add to wheel if not already on it and stream queue not empty */ michael@0: if (!TAILQ_EMPTY(&strq->outqueue) && michael@0: (strq->ss_params.prio.next_spoke.tqe_next == NULL) && michael@0: (strq->ss_params.prio.next_spoke.tqe_prev == NULL)) { michael@0: if (TAILQ_EMPTY(&asoc->ss_data.out_wheel)) { michael@0: TAILQ_INSERT_HEAD(&asoc->ss_data.out_wheel, strq, ss_params.prio.next_spoke); michael@0: } else { michael@0: strqt = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: while (strqt != NULL && strqt->ss_params.prio.priority < strq->ss_params.prio.priority) { michael@0: strqt = TAILQ_NEXT(strqt, ss_params.prio.next_spoke); michael@0: } michael@0: if (strqt != NULL) { michael@0: TAILQ_INSERT_BEFORE(strqt, strq, ss_params.prio.next_spoke); michael@0: } else { michael@0: TAILQ_INSERT_TAIL(&asoc->ss_data.out_wheel, strq, ss_params.prio.next_spoke); michael@0: } michael@0: } michael@0: } michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_prio_remove(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED, michael@0: int holds_lock) michael@0: { michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: /* Remove from wheel if stream queue is empty and actually is on the wheel */ michael@0: if (TAILQ_EMPTY(&strq->outqueue) && michael@0: (strq->ss_params.prio.next_spoke.tqe_next != NULL || michael@0: strq->ss_params.prio.next_spoke.tqe_prev != NULL)) { michael@0: if (asoc->last_out_stream == strq) { michael@0: asoc->last_out_stream = TAILQ_PREV(asoc->last_out_stream, sctpwheel_listhead, michael@0: ss_params.prio.next_spoke); michael@0: if (asoc->last_out_stream == NULL) { michael@0: asoc->last_out_stream = TAILQ_LAST(&asoc->ss_data.out_wheel, michael@0: sctpwheel_listhead); michael@0: } michael@0: if (asoc->last_out_stream == strq) { michael@0: asoc->last_out_stream = NULL; michael@0: } michael@0: } michael@0: TAILQ_REMOVE(&asoc->ss_data.out_wheel, strq, ss_params.prio.next_spoke); michael@0: strq->ss_params.prio.next_spoke.tqe_next = NULL; michael@0: strq->ss_params.prio.next_spoke.tqe_prev = NULL; michael@0: } michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static struct sctp_stream_out* michael@0: sctp_ss_prio_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, michael@0: struct sctp_association *asoc) michael@0: { michael@0: struct sctp_stream_out *strq, *strqt, *strqn; michael@0: michael@0: strqt = asoc->last_out_stream; michael@0: prio_again: michael@0: /* Find the next stream to use */ michael@0: if (strqt == NULL) { michael@0: strq = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: } else { michael@0: strqn = TAILQ_NEXT(strqt, ss_params.prio.next_spoke); michael@0: if (strqn != NULL && michael@0: strqn->ss_params.prio.priority == strqt->ss_params.prio.priority) { michael@0: strq = strqn; michael@0: } else { michael@0: strq = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: } michael@0: } michael@0: michael@0: /* If CMT is off, we must validate that michael@0: * the stream in question has the first michael@0: * item pointed towards are network destination michael@0: * requested by the caller. Note that if we michael@0: * turn out to be locked to a stream (assigning michael@0: * TSN's then we must stop, since we cannot michael@0: * look for another stream with data to send michael@0: * to that destination). In CMT's case, by michael@0: * skipping this check, we will send one michael@0: * data packet towards the requested net. michael@0: */ michael@0: if (net != NULL && strq != NULL && michael@0: SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) { michael@0: if (TAILQ_FIRST(&strq->outqueue) && michael@0: TAILQ_FIRST(&strq->outqueue)->net != NULL && michael@0: TAILQ_FIRST(&strq->outqueue)->net != net) { michael@0: if (strq == asoc->last_out_stream) { michael@0: return (NULL); michael@0: } else { michael@0: strqt = strq; michael@0: goto prio_again; michael@0: } michael@0: } michael@0: } michael@0: return (strq); michael@0: } michael@0: michael@0: static int michael@0: sctp_ss_prio_get_value(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc SCTP_UNUSED, michael@0: struct sctp_stream_out *strq, uint16_t *value) michael@0: { michael@0: if (strq == NULL) { michael@0: return (-1); michael@0: } michael@0: *value = strq->ss_params.prio.priority; michael@0: return (1); michael@0: } michael@0: michael@0: static int michael@0: sctp_ss_prio_set_value(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: struct sctp_stream_out *strq, uint16_t value) michael@0: { michael@0: if (strq == NULL) { michael@0: return (-1); michael@0: } michael@0: strq->ss_params.prio.priority = value; michael@0: sctp_ss_prio_remove(stcb, asoc, strq, NULL, 1); michael@0: sctp_ss_prio_add(stcb, asoc, strq, NULL, 1); michael@0: return (1); michael@0: } michael@0: michael@0: /* michael@0: * Fair bandwidth algorithm. michael@0: * Maintains an equal troughput per stream. michael@0: */ michael@0: static void michael@0: sctp_ss_fb_clear(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: int clear_values, int holds_lock) michael@0: { michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: while (!TAILQ_EMPTY(&asoc->ss_data.out_wheel)) { michael@0: struct sctp_stream_out *strq = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: if (clear_values) { michael@0: strq->ss_params.fb.rounds = -1; michael@0: } michael@0: TAILQ_REMOVE(&asoc->ss_data.out_wheel, TAILQ_FIRST(&asoc->ss_data.out_wheel), ss_params.fb.next_spoke); michael@0: strq->ss_params.fb.next_spoke.tqe_next = NULL; michael@0: strq->ss_params.fb.next_spoke.tqe_prev = NULL; michael@0: } michael@0: asoc->last_out_stream = NULL; michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_fb_init_stream(struct sctp_stream_out *strq, struct sctp_stream_out *with_strq) michael@0: { michael@0: strq->ss_params.fb.next_spoke.tqe_next = NULL; michael@0: strq->ss_params.fb.next_spoke.tqe_prev = NULL; michael@0: if (with_strq != NULL) { michael@0: strq->ss_params.fb.rounds = with_strq->ss_params.fb.rounds; michael@0: } else { michael@0: strq->ss_params.fb.rounds = -1; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_fb_add(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED, michael@0: int holds_lock) michael@0: { michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: if (!TAILQ_EMPTY(&strq->outqueue) && michael@0: (strq->ss_params.fb.next_spoke.tqe_next == NULL) && michael@0: (strq->ss_params.fb.next_spoke.tqe_prev == NULL)) { michael@0: if (strq->ss_params.fb.rounds < 0) michael@0: strq->ss_params.fb.rounds = TAILQ_FIRST(&strq->outqueue)->length; michael@0: TAILQ_INSERT_TAIL(&asoc->ss_data.out_wheel, strq, ss_params.fb.next_spoke); michael@0: } michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_fb_remove(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED, michael@0: int holds_lock) michael@0: { michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: /* Remove from wheel if stream queue is empty and actually is on the wheel */ michael@0: if (TAILQ_EMPTY(&strq->outqueue) && michael@0: (strq->ss_params.fb.next_spoke.tqe_next != NULL || michael@0: strq->ss_params.fb.next_spoke.tqe_prev != NULL)) { michael@0: if (asoc->last_out_stream == strq) { michael@0: asoc->last_out_stream = TAILQ_PREV(asoc->last_out_stream, sctpwheel_listhead, michael@0: ss_params.fb.next_spoke); michael@0: if (asoc->last_out_stream == NULL) { michael@0: asoc->last_out_stream = TAILQ_LAST(&asoc->ss_data.out_wheel, michael@0: sctpwheel_listhead); michael@0: } michael@0: if (asoc->last_out_stream == strq) { michael@0: asoc->last_out_stream = NULL; michael@0: } michael@0: } michael@0: TAILQ_REMOVE(&asoc->ss_data.out_wheel, strq, ss_params.fb.next_spoke); michael@0: strq->ss_params.fb.next_spoke.tqe_next = NULL; michael@0: strq->ss_params.fb.next_spoke.tqe_prev = NULL; michael@0: } michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static struct sctp_stream_out* michael@0: sctp_ss_fb_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, michael@0: struct sctp_association *asoc) michael@0: { michael@0: struct sctp_stream_out *strq = NULL, *strqt; michael@0: michael@0: if (asoc->last_out_stream == NULL || michael@0: TAILQ_FIRST(&asoc->ss_data.out_wheel) == TAILQ_LAST(&asoc->ss_data.out_wheel, sctpwheel_listhead)) { michael@0: strqt = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: } else { michael@0: strqt = TAILQ_NEXT(asoc->last_out_stream, ss_params.fb.next_spoke); michael@0: } michael@0: do { michael@0: if ((strqt != NULL) && michael@0: ((SCTP_BASE_SYSCTL(sctp_cmt_on_off) > 0) || michael@0: (SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0 && michael@0: (net == NULL || (TAILQ_FIRST(&strqt->outqueue) && TAILQ_FIRST(&strqt->outqueue)->net == NULL) || michael@0: (net != NULL && TAILQ_FIRST(&strqt->outqueue) && TAILQ_FIRST(&strqt->outqueue)->net != NULL && michael@0: TAILQ_FIRST(&strqt->outqueue)->net == net))))) { michael@0: if ((strqt->ss_params.fb.rounds >= 0) && (strq == NULL || michael@0: strqt->ss_params.fb.rounds < strq->ss_params.fb.rounds)) { michael@0: strq = strqt; michael@0: } michael@0: } michael@0: if (strqt != NULL) { michael@0: strqt = TAILQ_NEXT(strqt, ss_params.fb.next_spoke); michael@0: } else { michael@0: strqt = TAILQ_FIRST(&asoc->ss_data.out_wheel); michael@0: } michael@0: } while (strqt != strq); michael@0: return (strq); michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_fb_scheduled(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net SCTP_UNUSED, michael@0: struct sctp_association *asoc, struct sctp_stream_out *strq, michael@0: int moved_how_much SCTP_UNUSED) michael@0: { michael@0: struct sctp_stream_out *strqt; michael@0: int subtract; michael@0: michael@0: subtract = strq->ss_params.fb.rounds; michael@0: TAILQ_FOREACH(strqt, &asoc->ss_data.out_wheel, ss_params.fb.next_spoke) { michael@0: strqt->ss_params.fb.rounds -= subtract; michael@0: if (strqt->ss_params.fb.rounds < 0) michael@0: strqt->ss_params.fb.rounds = 0; michael@0: } michael@0: if (TAILQ_FIRST(&strq->outqueue)) { michael@0: strq->ss_params.fb.rounds = TAILQ_FIRST(&strq->outqueue)->length; michael@0: } else { michael@0: strq->ss_params.fb.rounds = -1; michael@0: } michael@0: asoc->last_out_stream = strq; michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * First-come, first-serve algorithm. michael@0: * Maintains the order provided by the application. michael@0: */ michael@0: static void michael@0: sctp_ss_fcfs_add(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp, michael@0: int holds_lock); michael@0: michael@0: static void michael@0: sctp_ss_fcfs_init(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: int holds_lock) michael@0: { michael@0: uint32_t x, n = 0, add_more = 1; michael@0: struct sctp_stream_queue_pending *sp; michael@0: uint16_t i; michael@0: michael@0: TAILQ_INIT(&asoc->ss_data.out_list); michael@0: /* michael@0: * If there is data in the stream queues already, michael@0: * the scheduler of an existing association has michael@0: * been changed. We can only cycle through the michael@0: * stream queues and add everything to the FCFS michael@0: * queue. michael@0: */ michael@0: while (add_more) { michael@0: add_more = 0; michael@0: for (i = 0; i < stcb->asoc.streamoutcnt; i++) { michael@0: sp = TAILQ_FIRST(&stcb->asoc.strmout[i].outqueue); michael@0: x = 0; michael@0: /* Find n. message in current stream queue */ michael@0: while (sp != NULL && x < n) { michael@0: sp = TAILQ_NEXT(sp, next); michael@0: x++; michael@0: } michael@0: if (sp != NULL) { michael@0: sctp_ss_fcfs_add(stcb, &stcb->asoc, &stcb->asoc.strmout[i], sp, holds_lock); michael@0: add_more = 1; michael@0: } michael@0: } michael@0: n++; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_fcfs_clear(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: int clear_values, int holds_lock) michael@0: { michael@0: if (clear_values) { michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: while (!TAILQ_EMPTY(&asoc->ss_data.out_list)) { michael@0: TAILQ_REMOVE(&asoc->ss_data.out_list, TAILQ_FIRST(&asoc->ss_data.out_list), ss_next); michael@0: } michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_fcfs_init_stream(struct sctp_stream_out *strq SCTP_UNUSED, struct sctp_stream_out *with_strq SCTP_UNUSED) michael@0: { michael@0: /* Nothing to be done here */ michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_fcfs_add(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: struct sctp_stream_out *strq SCTP_UNUSED, struct sctp_stream_queue_pending *sp, michael@0: int holds_lock) michael@0: { michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: if (sp && (sp->ss_next.tqe_next == NULL) && michael@0: (sp->ss_next.tqe_prev == NULL)) { michael@0: TAILQ_INSERT_TAIL(&asoc->ss_data.out_list, sp, ss_next); michael@0: } michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static int michael@0: sctp_ss_fcfs_is_empty(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc) michael@0: { michael@0: if (TAILQ_EMPTY(&asoc->ss_data.out_list)) { michael@0: return (1); michael@0: } else { michael@0: return (0); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: sctp_ss_fcfs_remove(struct sctp_tcb *stcb, struct sctp_association *asoc, michael@0: struct sctp_stream_out *strq SCTP_UNUSED, struct sctp_stream_queue_pending *sp, michael@0: int holds_lock) michael@0: { michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_LOCK(stcb); michael@0: } michael@0: if (sp && michael@0: ((sp->ss_next.tqe_next != NULL) || michael@0: (sp->ss_next.tqe_prev != NULL))) { michael@0: TAILQ_REMOVE(&asoc->ss_data.out_list, sp, ss_next); michael@0: } michael@0: if (holds_lock == 0) { michael@0: SCTP_TCB_SEND_UNLOCK(stcb); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: michael@0: static struct sctp_stream_out * michael@0: sctp_ss_fcfs_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net, michael@0: struct sctp_association *asoc) michael@0: { michael@0: struct sctp_stream_out *strq; michael@0: struct sctp_stream_queue_pending *sp; michael@0: michael@0: sp = TAILQ_FIRST(&asoc->ss_data.out_list); michael@0: default_again: michael@0: if (sp != NULL) { michael@0: strq = &asoc->strmout[sp->stream]; michael@0: } else { michael@0: strq = NULL; michael@0: } michael@0: michael@0: /* michael@0: * If CMT is off, we must validate that michael@0: * the stream in question has the first michael@0: * item pointed towards are network destination michael@0: * requested by the caller. Note that if we michael@0: * turn out to be locked to a stream (assigning michael@0: * TSN's then we must stop, since we cannot michael@0: * look for another stream with data to send michael@0: * to that destination). In CMT's case, by michael@0: * skipping this check, we will send one michael@0: * data packet towards the requested net. michael@0: */ michael@0: if (net != NULL && strq != NULL && michael@0: SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) { michael@0: if (TAILQ_FIRST(&strq->outqueue) && michael@0: TAILQ_FIRST(&strq->outqueue)->net != NULL && michael@0: TAILQ_FIRST(&strq->outqueue)->net != net) { michael@0: sp = TAILQ_NEXT(sp, ss_next); michael@0: goto default_again; michael@0: } michael@0: } michael@0: return (strq); michael@0: } michael@0: michael@0: struct sctp_ss_functions sctp_ss_functions[] = { michael@0: /* SCTP_SS_DEFAULT */ michael@0: { michael@0: #if defined(__Windows__) || defined(__Userspace_os_Windows) michael@0: sctp_ss_default_init, michael@0: sctp_ss_default_clear, michael@0: sctp_ss_default_init_stream, michael@0: sctp_ss_default_add, michael@0: sctp_ss_default_is_empty, michael@0: sctp_ss_default_remove, michael@0: sctp_ss_default_select, michael@0: sctp_ss_default_scheduled, michael@0: sctp_ss_default_packet_done, michael@0: sctp_ss_default_get_value, michael@0: sctp_ss_default_set_value michael@0: #else michael@0: .sctp_ss_init = sctp_ss_default_init, michael@0: .sctp_ss_clear = sctp_ss_default_clear, michael@0: .sctp_ss_init_stream = sctp_ss_default_init_stream, michael@0: .sctp_ss_add_to_stream = sctp_ss_default_add, michael@0: .sctp_ss_is_empty = sctp_ss_default_is_empty, michael@0: .sctp_ss_remove_from_stream = sctp_ss_default_remove, michael@0: .sctp_ss_select_stream = sctp_ss_default_select, michael@0: .sctp_ss_scheduled = sctp_ss_default_scheduled, michael@0: .sctp_ss_packet_done = sctp_ss_default_packet_done, michael@0: .sctp_ss_get_value = sctp_ss_default_get_value, michael@0: .sctp_ss_set_value = sctp_ss_default_set_value michael@0: #endif michael@0: }, michael@0: /* SCTP_SS_ROUND_ROBIN */ michael@0: { michael@0: #if defined(__Windows__) || defined(__Userspace_os_Windows) michael@0: sctp_ss_default_init, michael@0: sctp_ss_default_clear, michael@0: sctp_ss_default_init_stream, michael@0: sctp_ss_rr_add, michael@0: sctp_ss_default_is_empty, michael@0: sctp_ss_default_remove, michael@0: sctp_ss_default_select, michael@0: sctp_ss_default_scheduled, michael@0: sctp_ss_default_packet_done, michael@0: sctp_ss_default_get_value, michael@0: sctp_ss_default_set_value michael@0: #else michael@0: .sctp_ss_init = sctp_ss_default_init, michael@0: .sctp_ss_clear = sctp_ss_default_clear, michael@0: .sctp_ss_init_stream = sctp_ss_default_init_stream, michael@0: .sctp_ss_add_to_stream = sctp_ss_rr_add, michael@0: .sctp_ss_is_empty = sctp_ss_default_is_empty, michael@0: .sctp_ss_remove_from_stream = sctp_ss_default_remove, michael@0: .sctp_ss_select_stream = sctp_ss_default_select, michael@0: .sctp_ss_scheduled = sctp_ss_default_scheduled, michael@0: .sctp_ss_packet_done = sctp_ss_default_packet_done, michael@0: .sctp_ss_get_value = sctp_ss_default_get_value, michael@0: .sctp_ss_set_value = sctp_ss_default_set_value michael@0: #endif michael@0: }, michael@0: /* SCTP_SS_ROUND_ROBIN_PACKET */ michael@0: { michael@0: #if defined(__Windows__) || defined(__Userspace_os_Windows) michael@0: sctp_ss_default_init, michael@0: sctp_ss_default_clear, michael@0: sctp_ss_default_init_stream, michael@0: sctp_ss_rr_add, michael@0: sctp_ss_default_is_empty, michael@0: sctp_ss_default_remove, michael@0: sctp_ss_rrp_select, michael@0: sctp_ss_default_scheduled, michael@0: sctp_ss_rrp_packet_done, michael@0: sctp_ss_default_get_value, michael@0: sctp_ss_default_set_value michael@0: #else michael@0: .sctp_ss_init = sctp_ss_default_init, michael@0: .sctp_ss_clear = sctp_ss_default_clear, michael@0: .sctp_ss_init_stream = sctp_ss_default_init_stream, michael@0: .sctp_ss_add_to_stream = sctp_ss_rr_add, michael@0: .sctp_ss_is_empty = sctp_ss_default_is_empty, michael@0: .sctp_ss_remove_from_stream = sctp_ss_default_remove, michael@0: .sctp_ss_select_stream = sctp_ss_rrp_select, michael@0: .sctp_ss_scheduled = sctp_ss_default_scheduled, michael@0: .sctp_ss_packet_done = sctp_ss_rrp_packet_done, michael@0: .sctp_ss_get_value = sctp_ss_default_get_value, michael@0: .sctp_ss_set_value = sctp_ss_default_set_value michael@0: #endif michael@0: }, michael@0: /* SCTP_SS_PRIORITY */ michael@0: { michael@0: #if defined(__Windows__) || defined(__Userspace_os_Windows) michael@0: sctp_ss_default_init, michael@0: sctp_ss_prio_clear, michael@0: sctp_ss_prio_init_stream, michael@0: sctp_ss_prio_add, michael@0: sctp_ss_default_is_empty, michael@0: sctp_ss_prio_remove, michael@0: sctp_ss_prio_select, michael@0: sctp_ss_default_scheduled, michael@0: sctp_ss_default_packet_done, michael@0: sctp_ss_prio_get_value, michael@0: sctp_ss_prio_set_value michael@0: #else michael@0: .sctp_ss_init = sctp_ss_default_init, michael@0: .sctp_ss_clear = sctp_ss_prio_clear, michael@0: .sctp_ss_init_stream = sctp_ss_prio_init_stream, michael@0: .sctp_ss_add_to_stream = sctp_ss_prio_add, michael@0: .sctp_ss_is_empty = sctp_ss_default_is_empty, michael@0: .sctp_ss_remove_from_stream = sctp_ss_prio_remove, michael@0: .sctp_ss_select_stream = sctp_ss_prio_select, michael@0: .sctp_ss_scheduled = sctp_ss_default_scheduled, michael@0: .sctp_ss_packet_done = sctp_ss_default_packet_done, michael@0: .sctp_ss_get_value = sctp_ss_prio_get_value, michael@0: .sctp_ss_set_value = sctp_ss_prio_set_value michael@0: #endif michael@0: }, michael@0: /* SCTP_SS_FAIR_BANDWITH */ michael@0: { michael@0: #if defined(__Windows__) || defined(__Userspace_os_Windows) michael@0: sctp_ss_default_init, michael@0: sctp_ss_fb_clear, michael@0: sctp_ss_fb_init_stream, michael@0: sctp_ss_fb_add, michael@0: sctp_ss_default_is_empty, michael@0: sctp_ss_fb_remove, michael@0: sctp_ss_fb_select, michael@0: sctp_ss_fb_scheduled, michael@0: sctp_ss_default_packet_done, michael@0: sctp_ss_default_get_value, michael@0: sctp_ss_default_set_value michael@0: #else michael@0: .sctp_ss_init = sctp_ss_default_init, michael@0: .sctp_ss_clear = sctp_ss_fb_clear, michael@0: .sctp_ss_init_stream = sctp_ss_fb_init_stream, michael@0: .sctp_ss_add_to_stream = sctp_ss_fb_add, michael@0: .sctp_ss_is_empty = sctp_ss_default_is_empty, michael@0: .sctp_ss_remove_from_stream = sctp_ss_fb_remove, michael@0: .sctp_ss_select_stream = sctp_ss_fb_select, michael@0: .sctp_ss_scheduled = sctp_ss_fb_scheduled, michael@0: .sctp_ss_packet_done = sctp_ss_default_packet_done, michael@0: .sctp_ss_get_value = sctp_ss_default_get_value, michael@0: .sctp_ss_set_value = sctp_ss_default_set_value michael@0: #endif michael@0: }, michael@0: /* SCTP_SS_FIRST_COME */ michael@0: { michael@0: #if defined(__Windows__) || defined(__Userspace_os_Windows) michael@0: sctp_ss_fcfs_init, michael@0: sctp_ss_fcfs_clear, michael@0: sctp_ss_fcfs_init_stream, michael@0: sctp_ss_fcfs_add, michael@0: sctp_ss_fcfs_is_empty, michael@0: sctp_ss_fcfs_remove, michael@0: sctp_ss_fcfs_select, michael@0: sctp_ss_default_scheduled, michael@0: sctp_ss_default_packet_done, michael@0: sctp_ss_default_get_value, michael@0: sctp_ss_default_set_value michael@0: #else michael@0: .sctp_ss_init = sctp_ss_fcfs_init, michael@0: .sctp_ss_clear = sctp_ss_fcfs_clear, michael@0: .sctp_ss_init_stream = sctp_ss_fcfs_init_stream, michael@0: .sctp_ss_add_to_stream = sctp_ss_fcfs_add, michael@0: .sctp_ss_is_empty = sctp_ss_fcfs_is_empty, michael@0: .sctp_ss_remove_from_stream = sctp_ss_fcfs_remove, michael@0: .sctp_ss_select_stream = sctp_ss_fcfs_select, michael@0: .sctp_ss_scheduled = sctp_ss_default_scheduled, michael@0: .sctp_ss_packet_done = sctp_ss_default_packet_done, michael@0: .sctp_ss_get_value = sctp_ss_default_get_value, michael@0: .sctp_ss_set_value = sctp_ss_default_set_value michael@0: #endif michael@0: } michael@0: };