stream_sched_prio.c 9.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
/* SCTP kernel implementation
 * (C) Copyright Red Hat Inc. 2017
 *
 * This file is part of the SCTP kernel implementation
 *
 * These functions manipulate sctp stream queue/scheduling.
 *
 * This SCTP implementation is free software;
 * you can redistribute it and/or modify it under the terms of
 * the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This SCTP implementation is distributed in the hope that it
 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
 *                 ************************
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNU CC; see the file COPYING.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * Please send any bug reports or fixes you make to the
 * email addresched(es):
 *    lksctp developers <linux-sctp@vger.kernel.org>
 *
 * Written or modified by:
 *    Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
 */

#include <linux/list.h>
#include <net/sctp/sctp.h>
#include <net/sctp/sm.h>
#include <net/sctp/stream_sched.h>

/* Priority handling
 * RFC DRAFT ndata section 3.4
 */

static void sctp_sched_prio_unsched_all(struct sctp_stream *stream);

static struct sctp_stream_priorities *sctp_sched_prio_new_head(
			struct sctp_stream *stream, int prio, gfp_t gfp)
{
	struct sctp_stream_priorities *p;

	p = kmalloc(sizeof(*p), gfp);
	if (!p)
		return NULL;

	INIT_LIST_HEAD(&p->prio_sched);
	INIT_LIST_HEAD(&p->active);
	p->next = NULL;
	p->prio = prio;

	return p;
}

static struct sctp_stream_priorities *sctp_sched_prio_get_head(
			struct sctp_stream *stream, int prio, gfp_t gfp)
{
	struct sctp_stream_priorities *p;
	int i;

	/* Look into scheduled priorities first, as they are sorted and
	 * we can find it fast IF it's scheduled.
	 */
	list_for_each_entry(p, &stream->prio_list, prio_sched) {
		if (p->prio == prio)
			return p;
		if (p->prio > prio)
			break;
	}

	/* No luck. So we search on all streams now. */
	for (i = 0; i < stream->outcnt; i++) {
78
		if (!SCTP_SO(stream, i)->ext)
79 80
			continue;

81
		p = SCTP_SO(stream, i)->ext->prio_head;
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
		if (!p)
			/* Means all other streams won't be initialized
			 * as well.
			 */
			break;
		if (p->prio == prio)
			return p;
	}

	/* If not even there, allocate a new one. */
	return sctp_sched_prio_new_head(stream, prio, gfp);
}

static void sctp_sched_prio_next_stream(struct sctp_stream_priorities *p)
{
	struct list_head *pos;

	pos = p->next->prio_list.next;
	if (pos == &p->active)
		pos = pos->next;
	p->next = list_entry(pos, struct sctp_stream_out_ext, prio_list);
}

static bool sctp_sched_prio_unsched(struct sctp_stream_out_ext *soute)
{
	bool scheduled = false;

	if (!list_empty(&soute->prio_list)) {
		struct sctp_stream_priorities *prio_head = soute->prio_head;

		/* Scheduled */
		scheduled = true;

		if (prio_head->next == soute)
			/* Try to move to the next stream */
			sctp_sched_prio_next_stream(prio_head);

		list_del_init(&soute->prio_list);

		/* Also unsched the priority if this was the last stream */
		if (list_empty(&prio_head->active)) {
			list_del_init(&prio_head->prio_sched);
			/* If there is no stream left, clear next */
			prio_head->next = NULL;
		}
	}

	return scheduled;
}

static void sctp_sched_prio_sched(struct sctp_stream *stream,
				  struct sctp_stream_out_ext *soute)
{
	struct sctp_stream_priorities *prio, *prio_head;

	prio_head = soute->prio_head;

	/* Nothing to do if already scheduled */
	if (!list_empty(&soute->prio_list))
		return;

	/* Schedule the stream. If there is a next, we schedule the new
	 * one before it, so it's the last in round robin order.
	 * If there isn't, we also have to schedule the priority.
	 */
	if (prio_head->next) {
		list_add(&soute->prio_list, prio_head->next->prio_list.prev);
		return;
	}

	list_add(&soute->prio_list, &prio_head->active);
	prio_head->next = soute;

	list_for_each_entry(prio, &stream->prio_list, prio_sched) {
		if (prio->prio > prio_head->prio) {
			list_add(&prio_head->prio_sched, prio->prio_sched.prev);
			return;
		}
	}

	list_add_tail(&prio_head->prio_sched, &stream->prio_list);
}

static int sctp_sched_prio_set(struct sctp_stream *stream, __u16 sid,
			       __u16 prio, gfp_t gfp)
{
168
	struct sctp_stream_out *sout = SCTP_SO(stream, sid);
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
	struct sctp_stream_out_ext *soute = sout->ext;
	struct sctp_stream_priorities *prio_head, *old;
	bool reschedule = false;
	int i;

	prio_head = sctp_sched_prio_get_head(stream, prio, gfp);
	if (!prio_head)
		return -ENOMEM;

	reschedule = sctp_sched_prio_unsched(soute);
	old = soute->prio_head;
	soute->prio_head = prio_head;
	if (reschedule)
		sctp_sched_prio_sched(stream, soute);

	if (!old)
		/* Happens when we set the priority for the first time */
		return 0;

	for (i = 0; i < stream->outcnt; i++) {
189
		soute = SCTP_SO(stream, i)->ext;
190 191 192 193 194 195 196 197 198 199 200 201 202 203
		if (soute && soute->prio_head == old)
			/* It's still in use, nothing else to do here. */
			return 0;
	}

	/* No hits, we are good to free it. */
	kfree(old);

	return 0;
}

static int sctp_sched_prio_get(struct sctp_stream *stream, __u16 sid,
			       __u16 *value)
{
204
	*value = SCTP_SO(stream, sid)->ext->prio_head->prio;
205 206 207 208 209 210 211 212 213 214 215 216 217
	return 0;
}

static int sctp_sched_prio_init(struct sctp_stream *stream)
{
	INIT_LIST_HEAD(&stream->prio_list);

	return 0;
}

static int sctp_sched_prio_init_sid(struct sctp_stream *stream, __u16 sid,
				    gfp_t gfp)
{
218
	INIT_LIST_HEAD(&SCTP_SO(stream, sid)->ext->prio_list);
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
	return sctp_sched_prio_set(stream, sid, 0, gfp);
}

static void sctp_sched_prio_free(struct sctp_stream *stream)
{
	struct sctp_stream_priorities *prio, *n;
	LIST_HEAD(list);
	int i;

	/* As we don't keep a list of priorities, to avoid multiple
	 * frees we have to do it in 3 steps:
	 *   1. unsched everyone, so the lists are free to use in 2.
	 *   2. build the list of the priorities
	 *   3. free the list
	 */
	sctp_sched_prio_unsched_all(stream);
	for (i = 0; i < stream->outcnt; i++) {
236
		if (!SCTP_SO(stream, i)->ext)
237
			continue;
238
		prio = SCTP_SO(stream, i)->ext->prio_head;
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
		if (prio && list_empty(&prio->prio_sched))
			list_add(&prio->prio_sched, &list);
	}
	list_for_each_entry_safe(prio, n, &list, prio_sched) {
		list_del_init(&prio->prio_sched);
		kfree(prio);
	}
}

static void sctp_sched_prio_enqueue(struct sctp_outq *q,
				    struct sctp_datamsg *msg)
{
	struct sctp_stream *stream;
	struct sctp_chunk *ch;
	__u16 sid;

	ch = list_first_entry(&msg->chunks, struct sctp_chunk, frag_list);
	sid = sctp_chunk_stream_no(ch);
	stream = &q->asoc->stream;
258
	sctp_sched_prio_sched(stream, SCTP_SO(stream, sid)->ext);
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
}

static struct sctp_chunk *sctp_sched_prio_dequeue(struct sctp_outq *q)
{
	struct sctp_stream *stream = &q->asoc->stream;
	struct sctp_stream_priorities *prio;
	struct sctp_stream_out_ext *soute;
	struct sctp_chunk *ch = NULL;

	/* Bail out quickly if queue is empty */
	if (list_empty(&q->out_chunk_list))
		goto out;

	/* Find which chunk is next. It's easy, it's either the current
	 * one or the first chunk on the next active stream.
	 */
	if (stream->out_curr) {
		soute = stream->out_curr->ext;
	} else {
		prio = list_entry(stream->prio_list.next,
				  struct sctp_stream_priorities, prio_sched);
		soute = prio->next;
	}
	ch = list_entry(soute->outq.next, struct sctp_chunk, stream_list);
	sctp_sched_dequeue_common(q, ch);

out:
	return ch;
}

static void sctp_sched_prio_dequeue_done(struct sctp_outq *q,
					 struct sctp_chunk *ch)
{
	struct sctp_stream_priorities *prio;
	struct sctp_stream_out_ext *soute;
	__u16 sid;

	/* Last chunk on that msg, move to the next stream on
	 * this priority.
	 */
	sid = sctp_chunk_stream_no(ch);
300
	soute = SCTP_SO(&q->asoc->stream, sid)->ext;
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
	prio = soute->prio_head;

	sctp_sched_prio_next_stream(prio);

	if (list_empty(&soute->outq))
		sctp_sched_prio_unsched(soute);
}

static void sctp_sched_prio_sched_all(struct sctp_stream *stream)
{
	struct sctp_association *asoc;
	struct sctp_stream_out *sout;
	struct sctp_chunk *ch;

	asoc = container_of(stream, struct sctp_association, stream);
	list_for_each_entry(ch, &asoc->outqueue.out_chunk_list, list) {
		__u16 sid;

		sid = sctp_chunk_stream_no(ch);
320
		sout = SCTP_SO(stream, sid);
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
		if (sout->ext)
			sctp_sched_prio_sched(stream, sout->ext);
	}
}

static void sctp_sched_prio_unsched_all(struct sctp_stream *stream)
{
	struct sctp_stream_priorities *p, *tmp;
	struct sctp_stream_out_ext *soute, *souttmp;

	list_for_each_entry_safe(p, tmp, &stream->prio_list, prio_sched)
		list_for_each_entry_safe(soute, souttmp, &p->active, prio_list)
			sctp_sched_prio_unsched(soute);
}

X
Xin Long 已提交
336
static struct sctp_sched_ops sctp_sched_prio = {
337 338 339 340 341 342 343 344 345 346 347
	.set = sctp_sched_prio_set,
	.get = sctp_sched_prio_get,
	.init = sctp_sched_prio_init,
	.init_sid = sctp_sched_prio_init_sid,
	.free = sctp_sched_prio_free,
	.enqueue = sctp_sched_prio_enqueue,
	.dequeue = sctp_sched_prio_dequeue,
	.dequeue_done = sctp_sched_prio_dequeue_done,
	.sched_all = sctp_sched_prio_sched_all,
	.unsched_all = sctp_sched_prio_unsched_all,
};
X
Xin Long 已提交
348 349 350 351 352

void sctp_sched_ops_prio_init(void)
{
	sctp_sched_ops_register(SCTP_SS_PRIO, &sctp_sched_prio);
}