diff options
-rw-r--r-- | include/net/sctp/stream_sched.h | 72 | ||||
-rw-r--r-- | include/net/sctp/structs.h | 15 | ||||
-rw-r--r-- | include/uapi/linux/sctp.h | 6 | ||||
-rw-r--r-- | net/sctp/Makefile | 2 | ||||
-rw-r--r-- | net/sctp/outqueue.c | 59 | ||||
-rw-r--r-- | net/sctp/sm_sideeffect.c | 3 | ||||
-rw-r--r-- | net/sctp/stream.c | 88 | ||||
-rw-r--r-- | net/sctp/stream_sched.c | 270 |
8 files changed, 477 insertions, 38 deletions
diff --git a/include/net/sctp/stream_sched.h b/include/net/sctp/stream_sched.h new file mode 100644 index 000000000000..c676550a4c7d --- /dev/null +++ b/include/net/sctp/stream_sched.h | |||
@@ -0,0 +1,72 @@ | |||
1 | /* SCTP kernel implementation | ||
2 | * (C) Copyright Red Hat Inc. 2017 | ||
3 | * | ||
4 | * These are definitions used by the stream schedulers, defined in RFC | ||
5 | * draft ndata (https://tools.ietf.org/html/draft-ietf-tsvwg-sctp-ndata-11) | ||
6 | * | ||
7 | * This SCTP implementation is free software; | ||
8 | * you can redistribute it and/or modify it under the terms of | ||
9 | * the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2, or (at your option) | ||
11 | * any later version. | ||
12 | * | ||
13 | * This SCTP implementation is distributed in the hope that it | ||
14 | * will be useful, but WITHOUT ANY WARRANTY; without even the implied | ||
15 | * ************************ | ||
16 | * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
17 | * See the GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with GNU CC; see the file COPYING. If not, see | ||
21 | * <http://www.gnu.org/licenses/>. | ||
22 | * | ||
23 | * Please send any bug reports or fixes you make to the | ||
24 | * email addresses: | ||
25 | * lksctp developers <linux-sctp@vger.kernel.org> | ||
26 | * | ||
27 | * Written or modified by: | ||
28 | * Marcelo Ricardo Leitner <marcelo.leitner@gmail.com> | ||
29 | */ | ||
30 | |||
31 | #ifndef __sctp_stream_sched_h__ | ||
32 | #define __sctp_stream_sched_h__ | ||
33 | |||
34 | struct sctp_sched_ops { | ||
35 | /* Property handling for a given stream */ | ||
36 | int (*set)(struct sctp_stream *stream, __u16 sid, __u16 value, | ||
37 | gfp_t gfp); | ||
38 | int (*get)(struct sctp_stream *stream, __u16 sid, __u16 *value); | ||
39 | |||
40 | /* Init the specific scheduler */ | ||
41 | int (*init)(struct sctp_stream *stream); | ||
42 | /* Init a stream */ | ||
43 | int (*init_sid)(struct sctp_stream *stream, __u16 sid, gfp_t gfp); | ||
44 | /* Frees the entire thing */ | ||
45 | void (*free)(struct sctp_stream *stream); | ||
46 | |||
47 | /* Enqueue a chunk */ | ||
48 | void (*enqueue)(struct sctp_outq *q, struct sctp_datamsg *msg); | ||
49 | /* Dequeue a chunk */ | ||
50 | struct sctp_chunk *(*dequeue)(struct sctp_outq *q); | ||
51 | /* Called only if the chunk fit the packet */ | ||
52 | void (*dequeue_done)(struct sctp_outq *q, struct sctp_chunk *chunk); | ||
53 | /* Sched all chunks already enqueued */ | ||
54 | void (*sched_all)(struct sctp_stream *steam); | ||
55 | /* Unched all chunks already enqueued */ | ||
56 | void (*unsched_all)(struct sctp_stream *steam); | ||
57 | }; | ||
58 | |||
59 | int sctp_sched_set_sched(struct sctp_association *asoc, | ||
60 | enum sctp_sched_type sched); | ||
61 | int sctp_sched_get_sched(struct sctp_association *asoc); | ||
62 | int sctp_sched_set_value(struct sctp_association *asoc, __u16 sid, | ||
63 | __u16 value, gfp_t gfp); | ||
64 | int sctp_sched_get_value(struct sctp_association *asoc, __u16 sid, | ||
65 | __u16 *value); | ||
66 | void sctp_sched_dequeue_done(struct sctp_outq *q, struct sctp_chunk *ch); | ||
67 | |||
68 | void sctp_sched_dequeue_common(struct sctp_outq *q, struct sctp_chunk *ch); | ||
69 | int sctp_sched_init_sid(struct sctp_stream *stream, __u16 sid, gfp_t gfp); | ||
70 | struct sctp_sched_ops *sctp_sched_ops_from_stream(struct sctp_stream *stream); | ||
71 | |||
72 | #endif /* __sctp_stream_sched_h__ */ | ||
diff --git a/include/net/sctp/structs.h b/include/net/sctp/structs.h index c48f7999fe9b..3c22a30fd71b 100644 --- a/include/net/sctp/structs.h +++ b/include/net/sctp/structs.h | |||
@@ -84,7 +84,6 @@ struct sctp_ulpq; | |||
84 | struct sctp_ep_common; | 84 | struct sctp_ep_common; |
85 | struct crypto_shash; | 85 | struct crypto_shash; |
86 | struct sctp_stream; | 86 | struct sctp_stream; |
87 | struct sctp_stream_out; | ||
88 | 87 | ||
89 | 88 | ||
90 | #include <net/sctp/tsnmap.h> | 89 | #include <net/sctp/tsnmap.h> |
@@ -531,8 +530,12 @@ struct sctp_chunk { | |||
531 | /* How many times this chunk have been sent, for prsctp RTX policy */ | 530 | /* How many times this chunk have been sent, for prsctp RTX policy */ |
532 | int sent_count; | 531 | int sent_count; |
533 | 532 | ||
534 | /* This is our link to the per-transport transmitted list. */ | 533 | union { |
535 | struct list_head transmitted_list; | 534 | /* This is our link to the per-transport transmitted list. */ |
535 | struct list_head transmitted_list; | ||
536 | /* List in specific stream outq */ | ||
537 | struct list_head stream_list; | ||
538 | }; | ||
536 | 539 | ||
537 | /* This field is used by chunks that hold fragmented data. | 540 | /* This field is used by chunks that hold fragmented data. |
538 | * For the first fragment this is the list that holds the rest of | 541 | * For the first fragment this is the list that holds the rest of |
@@ -1019,6 +1022,9 @@ struct sctp_outq { | |||
1019 | /* Data pending that has never been transmitted. */ | 1022 | /* Data pending that has never been transmitted. */ |
1020 | struct list_head out_chunk_list; | 1023 | struct list_head out_chunk_list; |
1021 | 1024 | ||
1025 | /* Stream scheduler being used */ | ||
1026 | struct sctp_sched_ops *sched; | ||
1027 | |||
1022 | unsigned int out_qlen; /* Total length of queued data chunks. */ | 1028 | unsigned int out_qlen; /* Total length of queued data chunks. */ |
1023 | 1029 | ||
1024 | /* Error of send failed, may used in SCTP_SEND_FAILED event. */ | 1030 | /* Error of send failed, may used in SCTP_SEND_FAILED event. */ |
@@ -1325,6 +1331,7 @@ struct sctp_inithdr_host { | |||
1325 | struct sctp_stream_out_ext { | 1331 | struct sctp_stream_out_ext { |
1326 | __u64 abandoned_unsent[SCTP_PR_INDEX(MAX) + 1]; | 1332 | __u64 abandoned_unsent[SCTP_PR_INDEX(MAX) + 1]; |
1327 | __u64 abandoned_sent[SCTP_PR_INDEX(MAX) + 1]; | 1333 | __u64 abandoned_sent[SCTP_PR_INDEX(MAX) + 1]; |
1334 | struct list_head outq; /* chunks enqueued by this stream */ | ||
1328 | }; | 1335 | }; |
1329 | 1336 | ||
1330 | struct sctp_stream_out { | 1337 | struct sctp_stream_out { |
@@ -1342,6 +1349,8 @@ struct sctp_stream { | |||
1342 | struct sctp_stream_in *in; | 1349 | struct sctp_stream_in *in; |
1343 | __u16 outcnt; | 1350 | __u16 outcnt; |
1344 | __u16 incnt; | 1351 | __u16 incnt; |
1352 | /* Current stream being sent, if any */ | ||
1353 | struct sctp_stream_out *out_curr; | ||
1345 | }; | 1354 | }; |
1346 | 1355 | ||
1347 | #define SCTP_STREAM_CLOSED 0x00 | 1356 | #define SCTP_STREAM_CLOSED 0x00 |
diff --git a/include/uapi/linux/sctp.h b/include/uapi/linux/sctp.h index 6217ff8500a1..4487e7625ddb 100644 --- a/include/uapi/linux/sctp.h +++ b/include/uapi/linux/sctp.h | |||
@@ -1088,4 +1088,10 @@ struct sctp_add_streams { | |||
1088 | uint16_t sas_outstrms; | 1088 | uint16_t sas_outstrms; |
1089 | }; | 1089 | }; |
1090 | 1090 | ||
1091 | /* SCTP Stream schedulers */ | ||
1092 | enum sctp_sched_type { | ||
1093 | SCTP_SS_FCFS, | ||
1094 | SCTP_SS_MAX = SCTP_SS_FCFS | ||
1095 | }; | ||
1096 | |||
1091 | #endif /* _UAPI_SCTP_H */ | 1097 | #endif /* _UAPI_SCTP_H */ |
diff --git a/net/sctp/Makefile b/net/sctp/Makefile index 70f1b570bab9..0f6e6d1d69fd 100644 --- a/net/sctp/Makefile +++ b/net/sctp/Makefile | |||
@@ -12,7 +12,7 @@ sctp-y := sm_statetable.o sm_statefuns.o sm_sideeffect.o \ | |||
12 | inqueue.o outqueue.o ulpqueue.o \ | 12 | inqueue.o outqueue.o ulpqueue.o \ |
13 | tsnmap.o bind_addr.o socket.o primitive.o \ | 13 | tsnmap.o bind_addr.o socket.o primitive.o \ |
14 | output.o input.o debug.o stream.o auth.o \ | 14 | output.o input.o debug.o stream.o auth.o \ |
15 | offload.o | 15 | offload.o stream_sched.o |
16 | 16 | ||
17 | sctp_probe-y := probe.o | 17 | sctp_probe-y := probe.o |
18 | 18 | ||
diff --git a/net/sctp/outqueue.c b/net/sctp/outqueue.c index 746b07b7937d..4db012aa25f7 100644 --- a/net/sctp/outqueue.c +++ b/net/sctp/outqueue.c | |||
@@ -50,6 +50,7 @@ | |||
50 | 50 | ||
51 | #include <net/sctp/sctp.h> | 51 | #include <net/sctp/sctp.h> |
52 | #include <net/sctp/sm.h> | 52 | #include <net/sctp/sm.h> |
53 | #include <net/sctp/stream_sched.h> | ||
53 | 54 | ||
54 | /* Declare internal functions here. */ | 55 | /* Declare internal functions here. */ |
55 | static int sctp_acked(struct sctp_sackhdr *sack, __u32 tsn); | 56 | static int sctp_acked(struct sctp_sackhdr *sack, __u32 tsn); |
@@ -72,32 +73,38 @@ static void sctp_outq_flush(struct sctp_outq *q, int rtx_timeout, gfp_t gfp); | |||
72 | 73 | ||
73 | /* Add data to the front of the queue. */ | 74 | /* Add data to the front of the queue. */ |
74 | static inline void sctp_outq_head_data(struct sctp_outq *q, | 75 | static inline void sctp_outq_head_data(struct sctp_outq *q, |
75 | struct sctp_chunk *ch) | 76 | struct sctp_chunk *ch) |
76 | { | 77 | { |
78 | struct sctp_stream_out_ext *oute; | ||
79 | __u16 stream; | ||
80 | |||
77 | list_add(&ch->list, &q->out_chunk_list); | 81 | list_add(&ch->list, &q->out_chunk_list); |
78 | q->out_qlen += ch->skb->len; | 82 | q->out_qlen += ch->skb->len; |
83 | |||
84 | stream = sctp_chunk_stream_no(ch); | ||
85 | oute = q->asoc->stream.out[stream].ext; | ||
86 | list_add(&ch->stream_list, &oute->outq); | ||
79 | } | 87 | } |
80 | 88 | ||
81 | /* Take data from the front of the queue. */ | 89 | /* Take data from the front of the queue. */ |
82 | static inline struct sctp_chunk *sctp_outq_dequeue_data(struct sctp_outq *q) | 90 | static inline struct sctp_chunk *sctp_outq_dequeue_data(struct sctp_outq *q) |
83 | { | 91 | { |
84 | struct sctp_chunk *ch = NULL; | 92 | return q->sched->dequeue(q); |
85 | |||
86 | if (!list_empty(&q->out_chunk_list)) { | ||
87 | struct list_head *entry = q->out_chunk_list.next; | ||
88 | |||
89 | ch = list_entry(entry, struct sctp_chunk, list); | ||
90 | list_del_init(entry); | ||
91 | q->out_qlen -= ch->skb->len; | ||
92 | } | ||
93 | return ch; | ||
94 | } | 93 | } |
94 | |||
95 | /* Add data chunk to the end of the queue. */ | 95 | /* Add data chunk to the end of the queue. */ |
96 | static inline void sctp_outq_tail_data(struct sctp_outq *q, | 96 | static inline void sctp_outq_tail_data(struct sctp_outq *q, |
97 | struct sctp_chunk *ch) | 97 | struct sctp_chunk *ch) |
98 | { | 98 | { |
99 | struct sctp_stream_out_ext *oute; | ||
100 | __u16 stream; | ||
101 | |||
99 | list_add_tail(&ch->list, &q->out_chunk_list); | 102 | list_add_tail(&ch->list, &q->out_chunk_list); |
100 | q->out_qlen += ch->skb->len; | 103 | q->out_qlen += ch->skb->len; |
104 | |||
105 | stream = sctp_chunk_stream_no(ch); | ||
106 | oute = q->asoc->stream.out[stream].ext; | ||
107 | list_add_tail(&ch->stream_list, &oute->outq); | ||
101 | } | 108 | } |
102 | 109 | ||
103 | /* | 110 | /* |
@@ -207,6 +214,7 @@ void sctp_outq_init(struct sctp_association *asoc, struct sctp_outq *q) | |||
207 | INIT_LIST_HEAD(&q->retransmit); | 214 | INIT_LIST_HEAD(&q->retransmit); |
208 | INIT_LIST_HEAD(&q->sacked); | 215 | INIT_LIST_HEAD(&q->sacked); |
209 | INIT_LIST_HEAD(&q->abandoned); | 216 | INIT_LIST_HEAD(&q->abandoned); |
217 | sctp_sched_set_sched(asoc, SCTP_SS_FCFS); | ||
210 | } | 218 | } |
211 | 219 | ||
212 | /* Free the outqueue structure and any related pending chunks. | 220 | /* Free the outqueue structure and any related pending chunks. |
@@ -258,6 +266,7 @@ static void __sctp_outq_teardown(struct sctp_outq *q) | |||
258 | 266 | ||
259 | /* Throw away any leftover data chunks. */ | 267 | /* Throw away any leftover data chunks. */ |
260 | while ((chunk = sctp_outq_dequeue_data(q)) != NULL) { | 268 | while ((chunk = sctp_outq_dequeue_data(q)) != NULL) { |
269 | sctp_sched_dequeue_done(q, chunk); | ||
261 | 270 | ||
262 | /* Mark as send failure. */ | 271 | /* Mark as send failure. */ |
263 | sctp_chunk_fail(chunk, q->error); | 272 | sctp_chunk_fail(chunk, q->error); |
@@ -391,13 +400,14 @@ static int sctp_prsctp_prune_unsent(struct sctp_association *asoc, | |||
391 | struct sctp_outq *q = &asoc->outqueue; | 400 | struct sctp_outq *q = &asoc->outqueue; |
392 | struct sctp_chunk *chk, *temp; | 401 | struct sctp_chunk *chk, *temp; |
393 | 402 | ||
403 | q->sched->unsched_all(&asoc->stream); | ||
404 | |||
394 | list_for_each_entry_safe(chk, temp, &q->out_chunk_list, list) { | 405 | list_for_each_entry_safe(chk, temp, &q->out_chunk_list, list) { |
395 | if (!SCTP_PR_PRIO_ENABLED(chk->sinfo.sinfo_flags) || | 406 | if (!SCTP_PR_PRIO_ENABLED(chk->sinfo.sinfo_flags) || |
396 | chk->sinfo.sinfo_timetolive <= sinfo->sinfo_timetolive) | 407 | chk->sinfo.sinfo_timetolive <= sinfo->sinfo_timetolive) |
397 | continue; | 408 | continue; |
398 | 409 | ||
399 | list_del_init(&chk->list); | 410 | sctp_sched_dequeue_common(q, chk); |
400 | q->out_qlen -= chk->skb->len; | ||
401 | asoc->sent_cnt_removable--; | 411 | asoc->sent_cnt_removable--; |
402 | asoc->abandoned_unsent[SCTP_PR_INDEX(PRIO)]++; | 412 | asoc->abandoned_unsent[SCTP_PR_INDEX(PRIO)]++; |
403 | if (chk->sinfo.sinfo_stream < asoc->stream.outcnt) { | 413 | if (chk->sinfo.sinfo_stream < asoc->stream.outcnt) { |
@@ -415,6 +425,8 @@ static int sctp_prsctp_prune_unsent(struct sctp_association *asoc, | |||
415 | break; | 425 | break; |
416 | } | 426 | } |
417 | 427 | ||
428 | q->sched->sched_all(&asoc->stream); | ||
429 | |||
418 | return msg_len; | 430 | return msg_len; |
419 | } | 431 | } |
420 | 432 | ||
@@ -1033,22 +1045,9 @@ static void sctp_outq_flush(struct sctp_outq *q, int rtx_timeout, gfp_t gfp) | |||
1033 | while ((chunk = sctp_outq_dequeue_data(q)) != NULL) { | 1045 | while ((chunk = sctp_outq_dequeue_data(q)) != NULL) { |
1034 | __u32 sid = ntohs(chunk->subh.data_hdr->stream); | 1046 | __u32 sid = ntohs(chunk->subh.data_hdr->stream); |
1035 | 1047 | ||
1036 | /* RFC 2960 6.5 Every DATA chunk MUST carry a valid | ||
1037 | * stream identifier. | ||
1038 | */ | ||
1039 | if (chunk->sinfo.sinfo_stream >= asoc->stream.outcnt) { | ||
1040 | |||
1041 | /* Mark as failed send. */ | ||
1042 | sctp_chunk_fail(chunk, SCTP_ERROR_INV_STRM); | ||
1043 | if (asoc->peer.prsctp_capable && | ||
1044 | SCTP_PR_PRIO_ENABLED(chunk->sinfo.sinfo_flags)) | ||
1045 | asoc->sent_cnt_removable--; | ||
1046 | sctp_chunk_free(chunk); | ||
1047 | continue; | ||
1048 | } | ||
1049 | |||
1050 | /* Has this chunk expired? */ | 1048 | /* Has this chunk expired? */ |
1051 | if (sctp_chunk_abandoned(chunk)) { | 1049 | if (sctp_chunk_abandoned(chunk)) { |
1050 | sctp_sched_dequeue_done(q, chunk); | ||
1052 | sctp_chunk_fail(chunk, 0); | 1051 | sctp_chunk_fail(chunk, 0); |
1053 | sctp_chunk_free(chunk); | 1052 | sctp_chunk_free(chunk); |
1054 | continue; | 1053 | continue; |
@@ -1070,6 +1069,7 @@ static void sctp_outq_flush(struct sctp_outq *q, int rtx_timeout, gfp_t gfp) | |||
1070 | new_transport = asoc->peer.active_path; | 1069 | new_transport = asoc->peer.active_path; |
1071 | if (new_transport->state == SCTP_UNCONFIRMED) { | 1070 | if (new_transport->state == SCTP_UNCONFIRMED) { |
1072 | WARN_ONCE(1, "Attempt to send packet on unconfirmed path."); | 1071 | WARN_ONCE(1, "Attempt to send packet on unconfirmed path."); |
1072 | sctp_sched_dequeue_done(q, chunk); | ||
1073 | sctp_chunk_fail(chunk, 0); | 1073 | sctp_chunk_fail(chunk, 0); |
1074 | sctp_chunk_free(chunk); | 1074 | sctp_chunk_free(chunk); |
1075 | continue; | 1075 | continue; |
@@ -1133,6 +1133,11 @@ static void sctp_outq_flush(struct sctp_outq *q, int rtx_timeout, gfp_t gfp) | |||
1133 | else | 1133 | else |
1134 | asoc->stats.oodchunks++; | 1134 | asoc->stats.oodchunks++; |
1135 | 1135 | ||
1136 | /* Only now it's safe to consider this | ||
1137 | * chunk as sent, sched-wise. | ||
1138 | */ | ||
1139 | sctp_sched_dequeue_done(q, chunk); | ||
1140 | |||
1136 | break; | 1141 | break; |
1137 | 1142 | ||
1138 | default: | 1143 | default: |
diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c index e6a2974e020e..402bfbb888cd 100644 --- a/net/sctp/sm_sideeffect.c +++ b/net/sctp/sm_sideeffect.c | |||
@@ -50,6 +50,7 @@ | |||
50 | #include <net/sock.h> | 50 | #include <net/sock.h> |
51 | #include <net/sctp/sctp.h> | 51 | #include <net/sctp/sctp.h> |
52 | #include <net/sctp/sm.h> | 52 | #include <net/sctp/sm.h> |
53 | #include <net/sctp/stream_sched.h> | ||
53 | 54 | ||
54 | static int sctp_cmd_interpreter(enum sctp_event event_type, | 55 | static int sctp_cmd_interpreter(enum sctp_event event_type, |
55 | union sctp_subtype subtype, | 56 | union sctp_subtype subtype, |
@@ -1089,6 +1090,8 @@ static void sctp_cmd_send_msg(struct sctp_association *asoc, | |||
1089 | 1090 | ||
1090 | list_for_each_entry(chunk, &msg->chunks, frag_list) | 1091 | list_for_each_entry(chunk, &msg->chunks, frag_list) |
1091 | sctp_outq_tail(&asoc->outqueue, chunk, gfp); | 1092 | sctp_outq_tail(&asoc->outqueue, chunk, gfp); |
1093 | |||
1094 | asoc->outqueue.sched->enqueue(&asoc->outqueue, msg); | ||
1092 | } | 1095 | } |
1093 | 1096 | ||
1094 | 1097 | ||
diff --git a/net/sctp/stream.c b/net/sctp/stream.c index 055ca25bbc91..5ea33a2c453b 100644 --- a/net/sctp/stream.c +++ b/net/sctp/stream.c | |||
@@ -32,8 +32,61 @@ | |||
32 | * Xin Long <lucien.xin@gmail.com> | 32 | * Xin Long <lucien.xin@gmail.com> |
33 | */ | 33 | */ |
34 | 34 | ||
35 | #include <linux/list.h> | ||
35 | #include <net/sctp/sctp.h> | 36 | #include <net/sctp/sctp.h> |
36 | #include <net/sctp/sm.h> | 37 | #include <net/sctp/sm.h> |
38 | #include <net/sctp/stream_sched.h> | ||
39 | |||
40 | /* Migrates chunks from stream queues to new stream queues if needed, | ||
41 | * but not across associations. Also, removes those chunks to streams | ||
42 | * higher than the new max. | ||
43 | */ | ||
44 | static void sctp_stream_outq_migrate(struct sctp_stream *stream, | ||
45 | struct sctp_stream *new, __u16 outcnt) | ||
46 | { | ||
47 | struct sctp_association *asoc; | ||
48 | struct sctp_chunk *ch, *temp; | ||
49 | struct sctp_outq *outq; | ||
50 | int i; | ||
51 | |||
52 | asoc = container_of(stream, struct sctp_association, stream); | ||
53 | outq = &asoc->outqueue; | ||
54 | |||
55 | list_for_each_entry_safe(ch, temp, &outq->out_chunk_list, list) { | ||
56 | __u16 sid = sctp_chunk_stream_no(ch); | ||
57 | |||
58 | if (sid < outcnt) | ||
59 | continue; | ||
60 | |||
61 | sctp_sched_dequeue_common(outq, ch); | ||
62 | /* No need to call dequeue_done here because | ||
63 | * the chunks are not scheduled by now. | ||
64 | */ | ||
65 | |||
66 | /* Mark as failed send. */ | ||
67 | sctp_chunk_fail(ch, SCTP_ERROR_INV_STRM); | ||
68 | if (asoc->peer.prsctp_capable && | ||
69 | SCTP_PR_PRIO_ENABLED(ch->sinfo.sinfo_flags)) | ||
70 | asoc->sent_cnt_removable--; | ||
71 | |||
72 | sctp_chunk_free(ch); | ||
73 | } | ||
74 | |||
75 | if (new) { | ||
76 | /* Here we actually move the old ext stuff into the new | ||
77 | * buffer, because we want to keep it. Then | ||
78 | * sctp_stream_update will swap ->out pointers. | ||
79 | */ | ||
80 | for (i = 0; i < outcnt; i++) { | ||
81 | kfree(new->out[i].ext); | ||
82 | new->out[i].ext = stream->out[i].ext; | ||
83 | stream->out[i].ext = NULL; | ||
84 | } | ||
85 | } | ||
86 | |||
87 | for (i = outcnt; i < stream->outcnt; i++) | ||
88 | kfree(stream->out[i].ext); | ||
89 | } | ||
37 | 90 | ||
38 | static int sctp_stream_alloc_out(struct sctp_stream *stream, __u16 outcnt, | 91 | static int sctp_stream_alloc_out(struct sctp_stream *stream, __u16 outcnt, |
39 | gfp_t gfp) | 92 | gfp_t gfp) |
@@ -87,7 +140,8 @@ static int sctp_stream_alloc_in(struct sctp_stream *stream, __u16 incnt, | |||
87 | int sctp_stream_init(struct sctp_stream *stream, __u16 outcnt, __u16 incnt, | 140 | int sctp_stream_init(struct sctp_stream *stream, __u16 outcnt, __u16 incnt, |
88 | gfp_t gfp) | 141 | gfp_t gfp) |
89 | { | 142 | { |
90 | int i; | 143 | struct sctp_sched_ops *sched = sctp_sched_ops_from_stream(stream); |
144 | int i, ret = 0; | ||
91 | 145 | ||
92 | gfp |= __GFP_NOWARN; | 146 | gfp |= __GFP_NOWARN; |
93 | 147 | ||
@@ -97,6 +151,11 @@ int sctp_stream_init(struct sctp_stream *stream, __u16 outcnt, __u16 incnt, | |||
97 | if (outcnt == stream->outcnt) | 151 | if (outcnt == stream->outcnt) |
98 | goto in; | 152 | goto in; |
99 | 153 | ||
154 | /* Filter out chunks queued on streams that won't exist anymore */ | ||
155 | sched->unsched_all(stream); | ||
156 | sctp_stream_outq_migrate(stream, NULL, outcnt); | ||
157 | sched->sched_all(stream); | ||
158 | |||
100 | i = sctp_stream_alloc_out(stream, outcnt, gfp); | 159 | i = sctp_stream_alloc_out(stream, outcnt, gfp); |
101 | if (i) | 160 | if (i) |
102 | return i; | 161 | return i; |
@@ -105,20 +164,27 @@ int sctp_stream_init(struct sctp_stream *stream, __u16 outcnt, __u16 incnt, | |||
105 | for (i = 0; i < stream->outcnt; i++) | 164 | for (i = 0; i < stream->outcnt; i++) |
106 | stream->out[i].state = SCTP_STREAM_OPEN; | 165 | stream->out[i].state = SCTP_STREAM_OPEN; |
107 | 166 | ||
167 | sched->init(stream); | ||
168 | |||
108 | in: | 169 | in: |
109 | if (!incnt) | 170 | if (!incnt) |
110 | return 0; | 171 | goto out; |
111 | 172 | ||
112 | i = sctp_stream_alloc_in(stream, incnt, gfp); | 173 | i = sctp_stream_alloc_in(stream, incnt, gfp); |
113 | if (i) { | 174 | if (i) { |
114 | kfree(stream->out); | 175 | ret = -ENOMEM; |
115 | stream->out = NULL; | 176 | goto free; |
116 | return -ENOMEM; | ||
117 | } | 177 | } |
118 | 178 | ||
119 | stream->incnt = incnt; | 179 | stream->incnt = incnt; |
180 | goto out; | ||
120 | 181 | ||
121 | return 0; | 182 | free: |
183 | sched->free(stream); | ||
184 | kfree(stream->out); | ||
185 | stream->out = NULL; | ||
186 | out: | ||
187 | return ret; | ||
122 | } | 188 | } |
123 | 189 | ||
124 | int sctp_stream_init_ext(struct sctp_stream *stream, __u16 sid) | 190 | int sctp_stream_init_ext(struct sctp_stream *stream, __u16 sid) |
@@ -130,13 +196,15 @@ int sctp_stream_init_ext(struct sctp_stream *stream, __u16 sid) | |||
130 | return -ENOMEM; | 196 | return -ENOMEM; |
131 | stream->out[sid].ext = soute; | 197 | stream->out[sid].ext = soute; |
132 | 198 | ||
133 | return 0; | 199 | return sctp_sched_init_sid(stream, sid, GFP_KERNEL); |
134 | } | 200 | } |
135 | 201 | ||
136 | void sctp_stream_free(struct sctp_stream *stream) | 202 | void sctp_stream_free(struct sctp_stream *stream) |
137 | { | 203 | { |
204 | struct sctp_sched_ops *sched = sctp_sched_ops_from_stream(stream); | ||
138 | int i; | 205 | int i; |
139 | 206 | ||
207 | sched->free(stream); | ||
140 | for (i = 0; i < stream->outcnt; i++) | 208 | for (i = 0; i < stream->outcnt; i++) |
141 | kfree(stream->out[i].ext); | 209 | kfree(stream->out[i].ext); |
142 | kfree(stream->out); | 210 | kfree(stream->out); |
@@ -156,6 +224,10 @@ void sctp_stream_clear(struct sctp_stream *stream) | |||
156 | 224 | ||
157 | void sctp_stream_update(struct sctp_stream *stream, struct sctp_stream *new) | 225 | void sctp_stream_update(struct sctp_stream *stream, struct sctp_stream *new) |
158 | { | 226 | { |
227 | struct sctp_sched_ops *sched = sctp_sched_ops_from_stream(stream); | ||
228 | |||
229 | sched->unsched_all(stream); | ||
230 | sctp_stream_outq_migrate(stream, new, new->outcnt); | ||
159 | sctp_stream_free(stream); | 231 | sctp_stream_free(stream); |
160 | 232 | ||
161 | stream->out = new->out; | 233 | stream->out = new->out; |
@@ -163,6 +235,8 @@ void sctp_stream_update(struct sctp_stream *stream, struct sctp_stream *new) | |||
163 | stream->outcnt = new->outcnt; | 235 | stream->outcnt = new->outcnt; |
164 | stream->incnt = new->incnt; | 236 | stream->incnt = new->incnt; |
165 | 237 | ||
238 | sched->sched_all(stream); | ||
239 | |||
166 | new->out = NULL; | 240 | new->out = NULL; |
167 | new->in = NULL; | 241 | new->in = NULL; |
168 | } | 242 | } |
diff --git a/net/sctp/stream_sched.c b/net/sctp/stream_sched.c new file mode 100644 index 000000000000..40a9a9de2b98 --- /dev/null +++ b/net/sctp/stream_sched.c | |||
@@ -0,0 +1,270 @@ | |||
1 | /* SCTP kernel implementation | ||
2 | * (C) Copyright Red Hat Inc. 2017 | ||
3 | * | ||
4 | * This file is part of the SCTP kernel implementation | ||
5 | * | ||
6 | * These functions manipulate sctp stream queue/scheduling. | ||
7 | * | ||
8 | * This SCTP implementation is free software; | ||
9 | * you can redistribute it and/or modify it under the terms of | ||
10 | * the GNU General Public License as published by | ||
11 | * the Free Software Foundation; either version 2, or (at your option) | ||
12 | * any later version. | ||
13 | * | ||
14 | * This SCTP implementation is distributed in the hope that it | ||
15 | * will be useful, but WITHOUT ANY WARRANTY; without even the implied | ||
16 | * ************************ | ||
17 | * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
18 | * See the GNU General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU General Public License | ||
21 | * along with GNU CC; see the file COPYING. If not, see | ||
22 | * <http://www.gnu.org/licenses/>. | ||
23 | * | ||
24 | * Please send any bug reports or fixes you make to the | ||
25 | * email addresched(es): | ||
26 | * lksctp developers <linux-sctp@vger.kernel.org> | ||
27 | * | ||
28 | * Written or modified by: | ||
29 | * Marcelo Ricardo Leitner <marcelo.leitner@gmail.com> | ||
30 | */ | ||
31 | |||
32 | #include <linux/list.h> | ||
33 | #include <net/sctp/sctp.h> | ||
34 | #include <net/sctp/sm.h> | ||
35 | #include <net/sctp/stream_sched.h> | ||
36 | |||
37 | /* First Come First Serve (a.k.a. FIFO) | ||
38 | * RFC DRAFT ndata Section 3.1 | ||
39 | */ | ||
40 | static int sctp_sched_fcfs_set(struct sctp_stream *stream, __u16 sid, | ||
41 | __u16 value, gfp_t gfp) | ||
42 | { | ||
43 | return 0; | ||
44 | } | ||
45 | |||
46 | static int sctp_sched_fcfs_get(struct sctp_stream *stream, __u16 sid, | ||
47 | __u16 *value) | ||
48 | { | ||
49 | *value = 0; | ||
50 | return 0; | ||
51 | } | ||
52 | |||
53 | static int sctp_sched_fcfs_init(struct sctp_stream *stream) | ||
54 | { | ||
55 | return 0; | ||
56 | } | ||
57 | |||
58 | static int sctp_sched_fcfs_init_sid(struct sctp_stream *stream, __u16 sid, | ||
59 | gfp_t gfp) | ||
60 | { | ||
61 | return 0; | ||
62 | } | ||
63 | |||
64 | static void sctp_sched_fcfs_free(struct sctp_stream *stream) | ||
65 | { | ||
66 | } | ||
67 | |||
68 | static void sctp_sched_fcfs_enqueue(struct sctp_outq *q, | ||
69 | struct sctp_datamsg *msg) | ||
70 | { | ||
71 | } | ||
72 | |||
73 | static struct sctp_chunk *sctp_sched_fcfs_dequeue(struct sctp_outq *q) | ||
74 | { | ||
75 | struct sctp_stream *stream = &q->asoc->stream; | ||
76 | struct sctp_chunk *ch = NULL; | ||
77 | struct list_head *entry; | ||
78 | |||
79 | if (list_empty(&q->out_chunk_list)) | ||
80 | goto out; | ||
81 | |||
82 | if (stream->out_curr) { | ||
83 | ch = list_entry(stream->out_curr->ext->outq.next, | ||
84 | struct sctp_chunk, stream_list); | ||
85 | } else { | ||
86 | entry = q->out_chunk_list.next; | ||
87 | ch = list_entry(entry, struct sctp_chunk, list); | ||
88 | } | ||
89 | |||
90 | sctp_sched_dequeue_common(q, ch); | ||
91 | |||
92 | out: | ||
93 | return ch; | ||
94 | } | ||
95 | |||
96 | static void sctp_sched_fcfs_dequeue_done(struct sctp_outq *q, | ||
97 | struct sctp_chunk *chunk) | ||
98 | { | ||
99 | } | ||
100 | |||
101 | static void sctp_sched_fcfs_sched_all(struct sctp_stream *stream) | ||
102 | { | ||
103 | } | ||
104 | |||
105 | static void sctp_sched_fcfs_unsched_all(struct sctp_stream *stream) | ||
106 | { | ||
107 | } | ||
108 | |||
109 | static struct sctp_sched_ops sctp_sched_fcfs = { | ||
110 | .set = sctp_sched_fcfs_set, | ||
111 | .get = sctp_sched_fcfs_get, | ||
112 | .init = sctp_sched_fcfs_init, | ||
113 | .init_sid = sctp_sched_fcfs_init_sid, | ||
114 | .free = sctp_sched_fcfs_free, | ||
115 | .enqueue = sctp_sched_fcfs_enqueue, | ||
116 | .dequeue = sctp_sched_fcfs_dequeue, | ||
117 | .dequeue_done = sctp_sched_fcfs_dequeue_done, | ||
118 | .sched_all = sctp_sched_fcfs_sched_all, | ||
119 | .unsched_all = sctp_sched_fcfs_unsched_all, | ||
120 | }; | ||
121 | |||
122 | /* API to other parts of the stack */ | ||
123 | |||
124 | struct sctp_sched_ops *sctp_sched_ops[] = { | ||
125 | &sctp_sched_fcfs, | ||
126 | }; | ||
127 | |||
128 | int sctp_sched_set_sched(struct sctp_association *asoc, | ||
129 | enum sctp_sched_type sched) | ||
130 | { | ||
131 | struct sctp_sched_ops *n = sctp_sched_ops[sched]; | ||
132 | struct sctp_sched_ops *old = asoc->outqueue.sched; | ||
133 | struct sctp_datamsg *msg = NULL; | ||
134 | struct sctp_chunk *ch; | ||
135 | int i, ret = 0; | ||
136 | |||
137 | if (old == n) | ||
138 | return ret; | ||
139 | |||
140 | if (sched > SCTP_SS_MAX) | ||
141 | return -EINVAL; | ||
142 | |||
143 | if (old) { | ||
144 | old->free(&asoc->stream); | ||
145 | |||
146 | /* Give the next scheduler a clean slate. */ | ||
147 | for (i = 0; i < asoc->stream.outcnt; i++) { | ||
148 | void *p = asoc->stream.out[i].ext; | ||
149 | |||
150 | if (!p) | ||
151 | continue; | ||
152 | |||
153 | p += offsetofend(struct sctp_stream_out_ext, outq); | ||
154 | memset(p, 0, sizeof(struct sctp_stream_out_ext) - | ||
155 | offsetofend(struct sctp_stream_out_ext, outq)); | ||
156 | } | ||
157 | } | ||
158 | |||
159 | asoc->outqueue.sched = n; | ||
160 | n->init(&asoc->stream); | ||
161 | for (i = 0; i < asoc->stream.outcnt; i++) { | ||
162 | if (!asoc->stream.out[i].ext) | ||
163 | continue; | ||
164 | |||
165 | ret = n->init_sid(&asoc->stream, i, GFP_KERNEL); | ||
166 | if (ret) | ||
167 | goto err; | ||
168 | } | ||
169 | |||
170 | /* We have to requeue all chunks already queued. */ | ||
171 | list_for_each_entry(ch, &asoc->outqueue.out_chunk_list, list) { | ||
172 | if (ch->msg == msg) | ||
173 | continue; | ||
174 | msg = ch->msg; | ||
175 | n->enqueue(&asoc->outqueue, msg); | ||
176 | } | ||
177 | |||
178 | return ret; | ||
179 | |||
180 | err: | ||
181 | n->free(&asoc->stream); | ||
182 | asoc->outqueue.sched = &sctp_sched_fcfs; /* Always safe */ | ||
183 | |||
184 | return ret; | ||
185 | } | ||
186 | |||
187 | int sctp_sched_get_sched(struct sctp_association *asoc) | ||
188 | { | ||
189 | int i; | ||
190 | |||
191 | for (i = 0; i <= SCTP_SS_MAX; i++) | ||
192 | if (asoc->outqueue.sched == sctp_sched_ops[i]) | ||
193 | return i; | ||
194 | |||
195 | return 0; | ||
196 | } | ||
197 | |||
198 | int sctp_sched_set_value(struct sctp_association *asoc, __u16 sid, | ||
199 | __u16 value, gfp_t gfp) | ||
200 | { | ||
201 | if (sid >= asoc->stream.outcnt) | ||
202 | return -EINVAL; | ||
203 | |||
204 | if (!asoc->stream.out[sid].ext) { | ||
205 | int ret; | ||
206 | |||
207 | ret = sctp_stream_init_ext(&asoc->stream, sid); | ||
208 | if (ret) | ||
209 | return ret; | ||
210 | } | ||
211 | |||
212 | return asoc->outqueue.sched->set(&asoc->stream, sid, value, gfp); | ||
213 | } | ||
214 | |||
215 | int sctp_sched_get_value(struct sctp_association *asoc, __u16 sid, | ||
216 | __u16 *value) | ||
217 | { | ||
218 | if (sid >= asoc->stream.outcnt) | ||
219 | return -EINVAL; | ||
220 | |||
221 | if (!asoc->stream.out[sid].ext) | ||
222 | return 0; | ||
223 | |||
224 | return asoc->outqueue.sched->get(&asoc->stream, sid, value); | ||
225 | } | ||
226 | |||
227 | void sctp_sched_dequeue_done(struct sctp_outq *q, struct sctp_chunk *ch) | ||
228 | { | ||
229 | if (!list_is_last(&ch->frag_list, &ch->msg->chunks)) { | ||
230 | struct sctp_stream_out *sout; | ||
231 | __u16 sid; | ||
232 | |||
233 | /* datamsg is not finish, so save it as current one, | ||
234 | * in case application switch scheduler or a higher | ||
235 | * priority stream comes in. | ||
236 | */ | ||
237 | sid = sctp_chunk_stream_no(ch); | ||
238 | sout = &q->asoc->stream.out[sid]; | ||
239 | q->asoc->stream.out_curr = sout; | ||
240 | return; | ||
241 | } | ||
242 | |||
243 | q->asoc->stream.out_curr = NULL; | ||
244 | q->sched->dequeue_done(q, ch); | ||
245 | } | ||
246 | |||
247 | /* Auxiliary functions for the schedulers */ | ||
248 | void sctp_sched_dequeue_common(struct sctp_outq *q, struct sctp_chunk *ch) | ||
249 | { | ||
250 | list_del_init(&ch->list); | ||
251 | list_del_init(&ch->stream_list); | ||
252 | q->out_qlen -= ch->skb->len; | ||
253 | } | ||
254 | |||
255 | int sctp_sched_init_sid(struct sctp_stream *stream, __u16 sid, gfp_t gfp) | ||
256 | { | ||
257 | struct sctp_sched_ops *sched = sctp_sched_ops_from_stream(stream); | ||
258 | |||
259 | INIT_LIST_HEAD(&stream->out[sid].ext->outq); | ||
260 | return sched->init_sid(stream, sid, gfp); | ||
261 | } | ||
262 | |||
263 | struct sctp_sched_ops *sctp_sched_ops_from_stream(struct sctp_stream *stream) | ||
264 | { | ||
265 | struct sctp_association *asoc; | ||
266 | |||
267 | asoc = container_of(stream, struct sctp_association, stream); | ||
268 | |||
269 | return asoc->outqueue.sched; | ||
270 | } | ||