// SPDX-License-Identifier: GPL-2.0-or-later
/* RxRPC Tx data buffering.
 *
 * Copyright (C) 2022 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/slab.h>
#include "ar-internal.h"

static atomic_t rxrpc_txbuf_debug_ids;
atomic_t rxrpc_nr_txbuf;

/*
 * Allocate and partially initialise a data transmission buffer.
 */
struct rxrpc_txbuf *rxrpc_alloc_data_txbuf(struct rxrpc_call *call, size_t data_size,
					   size_t data_align, gfp_t gfp)
{
	struct rxrpc_txbuf *txb;
	size_t total, doff, jsize = sizeof(struct rxrpc_jumbo_header);
	void *buf;

	txb = kzalloc_obj(*txb, gfp);
	if (!txb)
		return NULL;

	/* We put a jumbo header in the buffer, but not a full wire header to
	 * avoid delayed-corruption problems with zerocopy.
	 */
	doff = round_up(jsize, data_align);
	total = doff + data_size;

	data_align = umax(data_align, L1_CACHE_BYTES);
	mutex_lock(&call->conn->tx_data_alloc_lock);
	buf = page_frag_alloc_align(&call->conn->tx_data_alloc, total, gfp,
				    data_align);
	mutex_unlock(&call->conn->tx_data_alloc_lock);
	if (!buf) {
		kfree(txb);
		return NULL;
	}

	refcount_set(&txb->ref, 1);
	txb->call_debug_id	= call->debug_id;
	txb->debug_id		= atomic_inc_return(&rxrpc_txbuf_debug_ids);
	txb->alloc_size		= data_size;
	txb->space		= data_size;
	txb->offset		= 0;
	txb->flags		= call->conn->out_clientflag;
	txb->seq		= call->send_top + 1;
	txb->data		= buf + doff;

	trace_rxrpc_txbuf(txb->debug_id, txb->call_debug_id, txb->seq, 1,
			  rxrpc_txbuf_alloc_data);

	atomic_inc(&rxrpc_nr_txbuf);
	return txb;
}

void rxrpc_see_txbuf(struct rxrpc_txbuf *txb, enum rxrpc_txbuf_trace what)
{
	int r = refcount_read(&txb->ref);

	trace_rxrpc_txbuf(txb->debug_id, txb->call_debug_id, txb->seq, r, what);
}

static void rxrpc_free_txbuf(struct rxrpc_txbuf *txb)
{
	trace_rxrpc_txbuf(txb->debug_id, txb->call_debug_id, txb->seq, 0,
			  rxrpc_txbuf_free);
	if (txb->data)
		page_frag_free(txb->data);
	kfree(txb);
	atomic_dec(&rxrpc_nr_txbuf);
}

void rxrpc_put_txbuf(struct rxrpc_txbuf *txb, enum rxrpc_txbuf_trace what)
{
	unsigned int debug_id, call_debug_id;
	rxrpc_seq_t seq;
	bool dead;
	int r;

	if (txb) {
		debug_id = txb->debug_id;
		call_debug_id = txb->call_debug_id;
		seq = txb->seq;
		dead = __refcount_dec_and_test(&txb->ref, &r);
		trace_rxrpc_txbuf(debug_id, call_debug_id, seq, r - 1, what);
		if (dead)
			rxrpc_free_txbuf(txb);
	}
}
