1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* SCTP kernel implementation |
3 | * (C) Copyright Red Hat Inc. 2017 |
4 | * |
5 | * This file is part of the SCTP kernel implementation |
6 | * |
7 | * These functions manipulate sctp stream queue/scheduling. |
8 | * |
9 | * Please send any bug reports or fixes you make to the |
10 | * email addresched(es): |
11 | * lksctp developers <linux-sctp@vger.kernel.org> |
12 | * |
13 | * Written or modified by: |
14 | * Marcelo Ricardo Leitner <marcelo.leitner@gmail.com> |
15 | */ |
16 | |
17 | #include <linux/list.h> |
18 | #include <net/sctp/sctp.h> |
19 | #include <net/sctp/sm.h> |
20 | #include <net/sctp/stream_sched.h> |
21 | |
22 | /* Priority handling |
23 | * RFC DRAFT ndata section 3.4 |
24 | */ |
25 | |
26 | static void sctp_sched_prio_unsched_all(struct sctp_stream *stream); |
27 | |
28 | static struct sctp_stream_priorities *sctp_sched_prio_head_get(struct sctp_stream_priorities *p) |
29 | { |
30 | p->users++; |
31 | return p; |
32 | } |
33 | |
34 | static void sctp_sched_prio_head_put(struct sctp_stream_priorities *p) |
35 | { |
36 | if (p && --p->users == 0) |
37 | kfree(objp: p); |
38 | } |
39 | |
40 | static struct sctp_stream_priorities *sctp_sched_prio_new_head( |
41 | struct sctp_stream *stream, int prio, gfp_t gfp) |
42 | { |
43 | struct sctp_stream_priorities *p; |
44 | |
45 | p = kmalloc(size: sizeof(*p), flags: gfp); |
46 | if (!p) |
47 | return NULL; |
48 | |
49 | INIT_LIST_HEAD(list: &p->prio_sched); |
50 | INIT_LIST_HEAD(list: &p->active); |
51 | p->next = NULL; |
52 | p->prio = prio; |
53 | p->users = 1; |
54 | |
55 | return p; |
56 | } |
57 | |
58 | static struct sctp_stream_priorities *sctp_sched_prio_get_head( |
59 | struct sctp_stream *stream, int prio, gfp_t gfp) |
60 | { |
61 | struct sctp_stream_priorities *p; |
62 | int i; |
63 | |
64 | /* Look into scheduled priorities first, as they are sorted and |
65 | * we can find it fast IF it's scheduled. |
66 | */ |
67 | list_for_each_entry(p, &stream->prio_list, prio_sched) { |
68 | if (p->prio == prio) |
69 | return sctp_sched_prio_head_get(p); |
70 | if (p->prio > prio) |
71 | break; |
72 | } |
73 | |
74 | /* No luck. So we search on all streams now. */ |
75 | for (i = 0; i < stream->outcnt; i++) { |
76 | if (!SCTP_SO(stream, i)->ext) |
77 | continue; |
78 | |
79 | p = SCTP_SO(stream, i)->ext->prio_head; |
80 | if (!p) |
81 | /* Means all other streams won't be initialized |
82 | * as well. |
83 | */ |
84 | break; |
85 | if (p->prio == prio) |
86 | return sctp_sched_prio_head_get(p); |
87 | } |
88 | |
89 | /* If not even there, allocate a new one. */ |
90 | return sctp_sched_prio_new_head(stream, prio, gfp); |
91 | } |
92 | |
93 | static void sctp_sched_prio_next_stream(struct sctp_stream_priorities *p) |
94 | { |
95 | struct list_head *pos; |
96 | |
97 | pos = p->next->prio_list.next; |
98 | if (pos == &p->active) |
99 | pos = pos->next; |
100 | p->next = list_entry(pos, struct sctp_stream_out_ext, prio_list); |
101 | } |
102 | |
103 | static bool sctp_sched_prio_unsched(struct sctp_stream_out_ext *soute) |
104 | { |
105 | bool scheduled = false; |
106 | |
107 | if (!list_empty(head: &soute->prio_list)) { |
108 | struct sctp_stream_priorities *prio_head = soute->prio_head; |
109 | |
110 | /* Scheduled */ |
111 | scheduled = true; |
112 | |
113 | if (prio_head->next == soute) |
114 | /* Try to move to the next stream */ |
115 | sctp_sched_prio_next_stream(p: prio_head); |
116 | |
117 | list_del_init(entry: &soute->prio_list); |
118 | |
119 | /* Also unsched the priority if this was the last stream */ |
120 | if (list_empty(head: &prio_head->active)) { |
121 | list_del_init(entry: &prio_head->prio_sched); |
122 | /* If there is no stream left, clear next */ |
123 | prio_head->next = NULL; |
124 | } |
125 | } |
126 | |
127 | return scheduled; |
128 | } |
129 | |
130 | static void sctp_sched_prio_sched(struct sctp_stream *stream, |
131 | struct sctp_stream_out_ext *soute) |
132 | { |
133 | struct sctp_stream_priorities *prio, *prio_head; |
134 | |
135 | prio_head = soute->prio_head; |
136 | |
137 | /* Nothing to do if already scheduled */ |
138 | if (!list_empty(head: &soute->prio_list)) |
139 | return; |
140 | |
141 | /* Schedule the stream. If there is a next, we schedule the new |
142 | * one before it, so it's the last in round robin order. |
143 | * If there isn't, we also have to schedule the priority. |
144 | */ |
145 | if (prio_head->next) { |
146 | list_add(new: &soute->prio_list, head: prio_head->next->prio_list.prev); |
147 | return; |
148 | } |
149 | |
150 | list_add(new: &soute->prio_list, head: &prio_head->active); |
151 | prio_head->next = soute; |
152 | |
153 | list_for_each_entry(prio, &stream->prio_list, prio_sched) { |
154 | if (prio->prio > prio_head->prio) { |
155 | list_add(new: &prio_head->prio_sched, head: prio->prio_sched.prev); |
156 | return; |
157 | } |
158 | } |
159 | |
160 | list_add_tail(new: &prio_head->prio_sched, head: &stream->prio_list); |
161 | } |
162 | |
163 | static int sctp_sched_prio_set(struct sctp_stream *stream, __u16 sid, |
164 | __u16 prio, gfp_t gfp) |
165 | { |
166 | struct sctp_stream_out *sout = SCTP_SO(stream, sid); |
167 | struct sctp_stream_out_ext *soute = sout->ext; |
168 | struct sctp_stream_priorities *prio_head, *old; |
169 | bool reschedule = false; |
170 | |
171 | old = soute->prio_head; |
172 | if (old && old->prio == prio) |
173 | return 0; |
174 | |
175 | prio_head = sctp_sched_prio_get_head(stream, prio, gfp); |
176 | if (!prio_head) |
177 | return -ENOMEM; |
178 | |
179 | reschedule = sctp_sched_prio_unsched(soute); |
180 | soute->prio_head = prio_head; |
181 | if (reschedule) |
182 | sctp_sched_prio_sched(stream, soute); |
183 | |
184 | sctp_sched_prio_head_put(p: old); |
185 | return 0; |
186 | } |
187 | |
188 | static int sctp_sched_prio_get(struct sctp_stream *stream, __u16 sid, |
189 | __u16 *value) |
190 | { |
191 | *value = SCTP_SO(stream, sid)->ext->prio_head->prio; |
192 | return 0; |
193 | } |
194 | |
195 | static int sctp_sched_prio_init(struct sctp_stream *stream) |
196 | { |
197 | INIT_LIST_HEAD(list: &stream->prio_list); |
198 | |
199 | return 0; |
200 | } |
201 | |
202 | static int sctp_sched_prio_init_sid(struct sctp_stream *stream, __u16 sid, |
203 | gfp_t gfp) |
204 | { |
205 | INIT_LIST_HEAD(list: &SCTP_SO(stream, sid)->ext->prio_list); |
206 | return sctp_sched_prio_set(stream, sid, prio: 0, gfp); |
207 | } |
208 | |
209 | static void sctp_sched_prio_free_sid(struct sctp_stream *stream, __u16 sid) |
210 | { |
211 | sctp_sched_prio_head_put(SCTP_SO(stream, sid)->ext->prio_head); |
212 | SCTP_SO(stream, sid)->ext->prio_head = NULL; |
213 | } |
214 | |
215 | static void sctp_sched_prio_enqueue(struct sctp_outq *q, |
216 | struct sctp_datamsg *msg) |
217 | { |
218 | struct sctp_stream *stream; |
219 | struct sctp_chunk *ch; |
220 | __u16 sid; |
221 | |
222 | ch = list_first_entry(&msg->chunks, struct sctp_chunk, frag_list); |
223 | sid = sctp_chunk_stream_no(ch); |
224 | stream = &q->asoc->stream; |
225 | sctp_sched_prio_sched(stream, SCTP_SO(stream, sid)->ext); |
226 | } |
227 | |
228 | static struct sctp_chunk *sctp_sched_prio_dequeue(struct sctp_outq *q) |
229 | { |
230 | struct sctp_stream *stream = &q->asoc->stream; |
231 | struct sctp_stream_priorities *prio; |
232 | struct sctp_stream_out_ext *soute; |
233 | struct sctp_chunk *ch = NULL; |
234 | |
235 | /* Bail out quickly if queue is empty */ |
236 | if (list_empty(head: &q->out_chunk_list)) |
237 | goto out; |
238 | |
239 | /* Find which chunk is next. It's easy, it's either the current |
240 | * one or the first chunk on the next active stream. |
241 | */ |
242 | if (stream->out_curr) { |
243 | soute = stream->out_curr->ext; |
244 | } else { |
245 | prio = list_entry(stream->prio_list.next, |
246 | struct sctp_stream_priorities, prio_sched); |
247 | soute = prio->next; |
248 | } |
249 | ch = list_entry(soute->outq.next, struct sctp_chunk, stream_list); |
250 | sctp_sched_dequeue_common(q, ch); |
251 | |
252 | out: |
253 | return ch; |
254 | } |
255 | |
256 | static void sctp_sched_prio_dequeue_done(struct sctp_outq *q, |
257 | struct sctp_chunk *ch) |
258 | { |
259 | struct sctp_stream_priorities *prio; |
260 | struct sctp_stream_out_ext *soute; |
261 | __u16 sid; |
262 | |
263 | /* Last chunk on that msg, move to the next stream on |
264 | * this priority. |
265 | */ |
266 | sid = sctp_chunk_stream_no(ch); |
267 | soute = SCTP_SO(&q->asoc->stream, sid)->ext; |
268 | prio = soute->prio_head; |
269 | |
270 | sctp_sched_prio_next_stream(p: prio); |
271 | |
272 | if (list_empty(head: &soute->outq)) |
273 | sctp_sched_prio_unsched(soute); |
274 | } |
275 | |
276 | static void sctp_sched_prio_sched_all(struct sctp_stream *stream) |
277 | { |
278 | struct sctp_association *asoc; |
279 | struct sctp_stream_out *sout; |
280 | struct sctp_chunk *ch; |
281 | |
282 | asoc = container_of(stream, struct sctp_association, stream); |
283 | list_for_each_entry(ch, &asoc->outqueue.out_chunk_list, list) { |
284 | __u16 sid; |
285 | |
286 | sid = sctp_chunk_stream_no(ch); |
287 | sout = SCTP_SO(stream, sid); |
288 | if (sout->ext) |
289 | sctp_sched_prio_sched(stream, soute: sout->ext); |
290 | } |
291 | } |
292 | |
293 | static void sctp_sched_prio_unsched_all(struct sctp_stream *stream) |
294 | { |
295 | struct sctp_stream_priorities *p, *tmp; |
296 | struct sctp_stream_out_ext *soute, *souttmp; |
297 | |
298 | list_for_each_entry_safe(p, tmp, &stream->prio_list, prio_sched) |
299 | list_for_each_entry_safe(soute, souttmp, &p->active, prio_list) |
300 | sctp_sched_prio_unsched(soute); |
301 | } |
302 | |
303 | static struct sctp_sched_ops sctp_sched_prio = { |
304 | .set = sctp_sched_prio_set, |
305 | .get = sctp_sched_prio_get, |
306 | .init = sctp_sched_prio_init, |
307 | .init_sid = sctp_sched_prio_init_sid, |
308 | .free_sid = sctp_sched_prio_free_sid, |
309 | .enqueue = sctp_sched_prio_enqueue, |
310 | .dequeue = sctp_sched_prio_dequeue, |
311 | .dequeue_done = sctp_sched_prio_dequeue_done, |
312 | .sched_all = sctp_sched_prio_sched_all, |
313 | .unsched_all = sctp_sched_prio_unsched_all, |
314 | }; |
315 | |
316 | void sctp_sched_ops_prio_init(void) |
317 | { |
318 | sctp_sched_ops_register(sched: SCTP_SS_PRIO, sched_ops: &sctp_sched_prio); |
319 | } |
320 | |