/* SCTP kernel implementation
* (C) Copyright IBM Corp. 2001, 2004
* Copyright (c) 1999-2000 Cisco, Inc.
* Copyright (c) 1999-2001 Motorola, Inc.
* Copyright (c) 2001 Intel Corp.
*
* This file is part of the SCTP kernel implementation
*
* These functions manipulate sctp tsn mapping array.
*
* 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 address(es):
* lksctp developers <linux-sctp@vger.kernel.org>
*
* Written or modified by:
* Xin Long <lucien.xin@gmail.com>
*/
#include <linux/list.h>
#include <net/sctp/sctp.h>
#include <net/sctp/sm.h>
#include <net/sctp/stream_sched.h>
/* Migrates chunks from stream queues to new stream queues if needed,
* but not across associations. Also, removes those chunks to streams
* higher than the new max.
*/
static void sctp_stream_outq_migrate(struct sctp_stream *stream,
struct sctp_stream *new, __u16 outcnt)
{
struct sctp_association *asoc;
struct sctp_chunk *ch, *temp;
struct sctp_outq *outq;
int i;
asoc = container_of(stream, struct sctp_association, stream);
outq = &asoc->outqueue;
list_for_each_entry_safe(ch, temp, &outq->out_chunk_list, list) {
__u16 sid = sctp_chunk_stream_no(ch);
if (sid < outcnt)
continue;
sctp_sched_dequeue_common(outq, ch);
/* No need to call dequeue_done here because
* the chunks are not scheduled by now.
*/
/* Mark as failed send. */
sctp_chunk_fail(ch, (__force __u32)SCTP_ERROR_INV_STRM);
if (asoc->peer.prsctp_capable &&
SCTP_PR_PRIO_ENABLED(ch->sinfo.sinfo_flags))
asoc->sent_cnt_removable--;
sctp_chunk_free(ch);
}
if (new) {
/* Here we actually move the old ext stuff into the new
* buffer, because we want to keep it. Then
* sctp_stream_update will swap ->out pointers.
*/
for (i = 0; i < outcnt; i++) {
kfree(new->out[i].ext);
new->out[i].ext = stream->out[i].ext;
stream->out[i].ext = NULL;
}
}
for (i = outcnt; i < stream->outcnt; i++)
kfree(stream->out[i].ext);
}
static int sctp_stream_alloc_out(struct sctp_stream *stream, __u16 outcnt,
gfp_t gfp)
{
struct sctp_stream_out *out;
out = kmalloc_array(outcnt, sizeof(*out), gfp);
if (!out)
return -ENOMEM;
if (stream->out) {
memcpy(out, stream->out, min(outcnt, stream->outcnt) *
sizeof(*out));
kfree(stream->out);
}
if (outcnt > stream->outcnt)
memset(out + stream->outcnt, 0,
(outcnt - stream->outcnt) * sizeof(*out));
stream->out = out;
return 0;
}
static int sctp_stream_alloc_in(struct sctp_stream *stream, __u16 incnt,
gfp_t gfp)
{
struct sctp_stream_in *in;
in = kmalloc_array(incnt, sizeof(*stream->in), gfp);
if (!in)
return -ENOMEM;
if (stream->in) {
memcpy(in, stream->in, min(incnt, stream->incnt) *
sizeof(*in));
kfree(stream->in);
}
if (incnt > stream->incnt)
memset(in + stream->incnt, 0,
(incnt - stream->incnt) * sizeof(*in));
stream->in = in;
return 0;
}
int sctp_stream_init(struct sctp_stream *stream, __u16 outcnt, __u16 incnt,
gfp_t gfp)
{
struct sctp_sched_ops *sched = sctp_sched_ops_from_stream(stream);
int i, ret = 0;
gfp |= __GFP_NOWARN;
/* Initial stream->out size may be very big, so free it and alloc
* a new one with new outcnt to save memory if needed.
*/
if (outcnt == stream->outcnt)
goto in;
/* Filter out chunks queued on streams that won't exist anymore */
sched->unsched_all(stream);
sctp_stream_outq_migrate(stream, NULL, outcnt);
sched->sched_all(stream);
ret = sctp_stream_alloc_out(stream, outcnt, gfp);
if (ret)
goto out;
stream->outcnt = outcnt;
for (i = 0; i < stream->outcnt