1094bb20bSChuck Lever /* 2094bb20bSChuck Lever * linux/net/sunrpc/socklib.c 3094bb20bSChuck Lever * 4094bb20bSChuck Lever * Common socket helper routines for RPC client and server 5094bb20bSChuck Lever * 6094bb20bSChuck Lever * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> 7094bb20bSChuck Lever */ 8094bb20bSChuck Lever 9fb286bb2SHerbert Xu #include <linux/compiler.h> 10fb286bb2SHerbert Xu #include <linux/netdevice.h> 11fb286bb2SHerbert Xu #include <linux/skbuff.h> 12094bb20bSChuck Lever #include <linux/types.h> 13094bb20bSChuck Lever #include <linux/pagemap.h> 14094bb20bSChuck Lever #include <linux/udp.h> 15094bb20bSChuck Lever #include <linux/sunrpc/xdr.h> 16094bb20bSChuck Lever 17094bb20bSChuck Lever 18094bb20bSChuck Lever /** 199d292316SChuck Lever * xdr_skb_read_bits - copy some data bits from skb to internal buffer 20094bb20bSChuck Lever * @desc: sk_buff copy helper 21094bb20bSChuck Lever * @to: copy destination 22094bb20bSChuck Lever * @len: number of bytes to copy 23094bb20bSChuck Lever * 24094bb20bSChuck Lever * Possibly called several times to iterate over an sk_buff and copy 25094bb20bSChuck Lever * data out of it. 26094bb20bSChuck Lever */ 279d292316SChuck Lever size_t xdr_skb_read_bits(skb_reader_t *desc, void *to, size_t len) 28094bb20bSChuck Lever { 29094bb20bSChuck Lever if (len > desc->count) 30094bb20bSChuck Lever len = desc->count; 319d292316SChuck Lever if (unlikely(skb_copy_bits(desc->skb, desc->offset, to, len))) 32094bb20bSChuck Lever return 0; 33094bb20bSChuck Lever desc->count -= len; 34094bb20bSChuck Lever desc->offset += len; 35094bb20bSChuck Lever return len; 36094bb20bSChuck Lever } 37094bb20bSChuck Lever 38094bb20bSChuck Lever /** 399d292316SChuck Lever * xdr_skb_read_and_csum_bits - copy and checksum from skb to buffer 40094bb20bSChuck Lever * @desc: sk_buff copy helper 41094bb20bSChuck Lever * @to: copy destination 42094bb20bSChuck Lever * @len: number of bytes to copy 43094bb20bSChuck Lever * 44094bb20bSChuck Lever * Same as skb_read_bits, but calculate a checksum at the same time. 45094bb20bSChuck Lever */ 469d292316SChuck Lever static size_t xdr_skb_read_and_csum_bits(skb_reader_t *desc, void *to, size_t len) 47094bb20bSChuck Lever { 485f92a738SAl Viro unsigned int pos; 495f92a738SAl Viro __wsum csum2; 50094bb20bSChuck Lever 51094bb20bSChuck Lever if (len > desc->count) 52094bb20bSChuck Lever len = desc->count; 53094bb20bSChuck Lever pos = desc->offset; 54094bb20bSChuck Lever csum2 = skb_copy_and_csum_bits(desc->skb, pos, to, len, 0); 55094bb20bSChuck Lever desc->csum = csum_block_add(desc->csum, csum2, pos); 56094bb20bSChuck Lever desc->count -= len; 57094bb20bSChuck Lever desc->offset += len; 58094bb20bSChuck Lever return len; 59094bb20bSChuck Lever } 60094bb20bSChuck Lever 61094bb20bSChuck Lever /** 62094bb20bSChuck Lever * xdr_partial_copy_from_skb - copy data out of an skb 63094bb20bSChuck Lever * @xdr: target XDR buffer 64094bb20bSChuck Lever * @base: starting offset 65094bb20bSChuck Lever * @desc: sk_buff copy helper 66094bb20bSChuck Lever * @copy_actor: virtual method for copying data 67094bb20bSChuck Lever * 68094bb20bSChuck Lever */ 69094bb20bSChuck Lever ssize_t xdr_partial_copy_from_skb(struct xdr_buf *xdr, unsigned int base, skb_reader_t *desc, skb_read_actor_t copy_actor) 70094bb20bSChuck Lever { 71094bb20bSChuck Lever struct page **ppage = xdr->pages; 72094bb20bSChuck Lever unsigned int len, pglen = xdr->page_len; 73094bb20bSChuck Lever ssize_t copied = 0; 74094bb20bSChuck Lever int ret; 75094bb20bSChuck Lever 76094bb20bSChuck Lever len = xdr->head[0].iov_len; 77094bb20bSChuck Lever if (base < len) { 78094bb20bSChuck Lever len -= base; 79094bb20bSChuck Lever ret = copy_actor(desc, (char *)xdr->head[0].iov_base + base, len); 80094bb20bSChuck Lever copied += ret; 81094bb20bSChuck Lever if (ret != len || !desc->count) 82094bb20bSChuck Lever goto out; 83094bb20bSChuck Lever base = 0; 84094bb20bSChuck Lever } else 85094bb20bSChuck Lever base -= len; 86094bb20bSChuck Lever 87094bb20bSChuck Lever if (unlikely(pglen == 0)) 88094bb20bSChuck Lever goto copy_tail; 89094bb20bSChuck Lever if (unlikely(base >= pglen)) { 90094bb20bSChuck Lever base -= pglen; 91094bb20bSChuck Lever goto copy_tail; 92094bb20bSChuck Lever } 93094bb20bSChuck Lever if (base || xdr->page_base) { 94094bb20bSChuck Lever pglen -= base; 95094bb20bSChuck Lever base += xdr->page_base; 96094bb20bSChuck Lever ppage += base >> PAGE_CACHE_SHIFT; 97094bb20bSChuck Lever base &= ~PAGE_CACHE_MASK; 98094bb20bSChuck Lever } 99094bb20bSChuck Lever do { 100094bb20bSChuck Lever char *kaddr; 101094bb20bSChuck Lever 102094bb20bSChuck Lever /* ACL likes to be lazy in allocating pages - ACLs 103094bb20bSChuck Lever * are small by default but can get huge. */ 104094bb20bSChuck Lever if (unlikely(*ppage == NULL)) { 105094bb20bSChuck Lever *ppage = alloc_page(GFP_ATOMIC); 106094bb20bSChuck Lever if (unlikely(*ppage == NULL)) { 107094bb20bSChuck Lever if (copied == 0) 108094bb20bSChuck Lever copied = -ENOMEM; 109094bb20bSChuck Lever goto out; 110094bb20bSChuck Lever } 111094bb20bSChuck Lever } 112094bb20bSChuck Lever 113094bb20bSChuck Lever len = PAGE_CACHE_SIZE; 114094bb20bSChuck Lever kaddr = kmap_atomic(*ppage, KM_SKB_SUNRPC_DATA); 115094bb20bSChuck Lever if (base) { 116094bb20bSChuck Lever len -= base; 117094bb20bSChuck Lever if (pglen < len) 118094bb20bSChuck Lever len = pglen; 119094bb20bSChuck Lever ret = copy_actor(desc, kaddr + base, len); 120094bb20bSChuck Lever base = 0; 121094bb20bSChuck Lever } else { 122094bb20bSChuck Lever if (pglen < len) 123094bb20bSChuck Lever len = pglen; 124094bb20bSChuck Lever ret = copy_actor(desc, kaddr, len); 125094bb20bSChuck Lever } 126094bb20bSChuck Lever flush_dcache_page(*ppage); 127094bb20bSChuck Lever kunmap_atomic(kaddr, KM_SKB_SUNRPC_DATA); 128094bb20bSChuck Lever copied += ret; 129094bb20bSChuck Lever if (ret != len || !desc->count) 130094bb20bSChuck Lever goto out; 131094bb20bSChuck Lever ppage++; 132094bb20bSChuck Lever } while ((pglen -= len) != 0); 133094bb20bSChuck Lever copy_tail: 134094bb20bSChuck Lever len = xdr->tail[0].iov_len; 135094bb20bSChuck Lever if (base < len) 136094bb20bSChuck Lever copied += copy_actor(desc, (char *)xdr->tail[0].iov_base + base, len - base); 137094bb20bSChuck Lever out: 138094bb20bSChuck Lever return copied; 139094bb20bSChuck Lever } 140094bb20bSChuck Lever 141094bb20bSChuck Lever /** 142094bb20bSChuck Lever * csum_partial_copy_to_xdr - checksum and copy data 143094bb20bSChuck Lever * @xdr: target XDR buffer 144094bb20bSChuck Lever * @skb: source skb 145094bb20bSChuck Lever * 146094bb20bSChuck Lever * We have set things up such that we perform the checksum of the UDP 147094bb20bSChuck Lever * packet in parallel with the copies into the RPC client iovec. -DaveM 148094bb20bSChuck Lever */ 149094bb20bSChuck Lever int csum_partial_copy_to_xdr(struct xdr_buf *xdr, struct sk_buff *skb) 150094bb20bSChuck Lever { 151094bb20bSChuck Lever skb_reader_t desc; 152094bb20bSChuck Lever 153094bb20bSChuck Lever desc.skb = skb; 154094bb20bSChuck Lever desc.offset = sizeof(struct udphdr); 155094bb20bSChuck Lever desc.count = skb->len - desc.offset; 156094bb20bSChuck Lever 157094bb20bSChuck Lever if (skb->ip_summed == CHECKSUM_UNNECESSARY) 158094bb20bSChuck Lever goto no_checksum; 159094bb20bSChuck Lever 160094bb20bSChuck Lever desc.csum = csum_partial(skb->data, desc.offset, skb->csum); 1619d292316SChuck Lever if (xdr_partial_copy_from_skb(xdr, 0, &desc, xdr_skb_read_and_csum_bits) < 0) 162094bb20bSChuck Lever return -1; 163094bb20bSChuck Lever if (desc.offset != skb->len) { 1645f92a738SAl Viro __wsum csum2; 165094bb20bSChuck Lever csum2 = skb_checksum(skb, desc.offset, skb->len - desc.offset, 0); 166094bb20bSChuck Lever desc.csum = csum_block_add(desc.csum, csum2, desc.offset); 167094bb20bSChuck Lever } 168094bb20bSChuck Lever if (desc.count) 169094bb20bSChuck Lever return -1; 170d3bc23e7SAl Viro if (csum_fold(desc.csum)) 171094bb20bSChuck Lever return -1; 17284fa7933SPatrick McHardy if (unlikely(skb->ip_summed == CHECKSUM_COMPLETE)) 173fb286bb2SHerbert Xu netdev_rx_csum_fault(skb->dev); 174094bb20bSChuck Lever return 0; 175094bb20bSChuck Lever no_checksum: 1769d292316SChuck Lever if (xdr_partial_copy_from_skb(xdr, 0, &desc, xdr_skb_read_bits) < 0) 177094bb20bSChuck Lever return -1; 178094bb20bSChuck Lever if (desc.count) 179094bb20bSChuck Lever return -1; 180094bb20bSChuck Lever return 0; 181094bb20bSChuck Lever } 182