| // 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 ([email protected]) |
| */ |
| |
| #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_wire_header *whdr; |
| struct rxrpc_txbuf *txb; |
| size_t total, hoff; |
| void *buf; |
| |
| txb = kzalloc(sizeof(*txb), gfp); |
| if (!txb) |
| return NULL; |
| |
| hoff = round_up(sizeof(*whdr), data_align) - sizeof(*whdr); |
| total = hoff + sizeof(*whdr) + 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; |
| } |
| |
| whdr = buf + hoff; |
| |
| 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 = sizeof(*whdr); |
| txb->flags = call->conn->out_clientflag; |
| txb->seq = call->send_top + 1; |
| txb->nr_kvec = 1; |
| txb->kvec[0].iov_base = whdr; |
| txb->kvec[0].iov_len = sizeof(*whdr); |
| |
| whdr->epoch = htonl(call->conn->proto.epoch); |
| whdr->cid = htonl(call->cid); |
| whdr->callNumber = htonl(call->call_id); |
| whdr->seq = htonl(txb->seq); |
| whdr->type = RXRPC_PACKET_TYPE_DATA; |
| whdr->flags = 0; |
| whdr->userStatus = 0; |
| whdr->securityIndex = call->security_ix; |
| whdr->_rsvd = 0; |
| whdr->serviceId = htons(call->dest_srx.srx_service); |
| |
| 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_get_txbuf(struct rxrpc_txbuf *txb, enum rxrpc_txbuf_trace what) |
| { |
| int r; |
| |
| __refcount_inc(&txb->ref, &r); |
| trace_rxrpc_txbuf(txb->debug_id, txb->call_debug_id, txb->seq, r + 1, what); |
| } |
| |
| 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) |
| { |
| int i; |
| |
| trace_rxrpc_txbuf(txb->debug_id, txb->call_debug_id, txb->seq, 0, |
| rxrpc_txbuf_free); |
| for (i = 0; i < txb->nr_kvec; i++) |
| if (txb->kvec[i].iov_base && |
| !is_zero_pfn(page_to_pfn(virt_to_page(txb->kvec[i].iov_base)))) |
| page_frag_free(txb->kvec[i].iov_base); |
| 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); |
| } |
| } |