macvtap: do not zerocopy if iov needs more pages than MAX_SKB_FRAGS
[platform/adaptation/renesas_rcar/renesas_kernel.git] / drivers / net / macvtap.c
index a7c5654..a98fb0e 100644 (file)
@@ -698,6 +698,28 @@ static int macvtap_skb_to_vnet_hdr(const struct sk_buff *skb,
        return 0;
 }
 
+static unsigned long iov_pages(const struct iovec *iv, int offset,
+                              unsigned long nr_segs)
+{
+       unsigned long seg, base;
+       int pages = 0, len, size;
+
+       while (nr_segs && (offset >= iv->iov_len)) {
+               offset -= iv->iov_len;
+               ++iv;
+               --nr_segs;
+       }
+
+       for (seg = 0; seg < nr_segs; seg++) {
+               base = (unsigned long)iv[seg].iov_base + offset;
+               len = iv[seg].iov_len - offset;
+               size = ((base & ~PAGE_MASK) + len + ~PAGE_MASK) >> PAGE_SHIFT;
+               pages += size;
+               offset = 0;
+       }
+
+       return pages;
+}
 
 /* Get packet from user space buffer */
 static ssize_t macvtap_get_user(struct macvtap_queue *q, struct msghdr *m,
@@ -744,31 +766,15 @@ static ssize_t macvtap_get_user(struct macvtap_queue *q, struct msghdr *m,
        if (unlikely(count > UIO_MAXIOV))
                goto err;
 
-       if (m && m->msg_control && sock_flag(&q->sk, SOCK_ZEROCOPY))
-               zerocopy = true;
-
-       if (zerocopy) {
-               /* Userspace may produce vectors with count greater than
-                * MAX_SKB_FRAGS, so we need to linearize parts of the skb
-                * to let the rest of data to be fit in the frags.
-                */
-               if (count > MAX_SKB_FRAGS) {
-                       copylen = iov_length(iv, count - MAX_SKB_FRAGS);
-                       if (copylen < vnet_hdr_len)
-                               copylen = 0;
-                       else
-                               copylen -= vnet_hdr_len;
-               }
-               /* There are 256 bytes to be copied in skb, so there is enough
-                * room for skb expand head in case it is used.
-                * The rest buffer is mapped from userspace.
-                */
-               if (copylen < vnet_hdr.hdr_len)
-                       copylen = vnet_hdr.hdr_len;
-               if (!copylen)
-                       copylen = GOODCOPY_LEN;
+       if (m && m->msg_control && sock_flag(&q->sk, SOCK_ZEROCOPY)) {
+               copylen = vnet_hdr.hdr_len ? vnet_hdr.hdr_len : GOODCOPY_LEN;
                linear = copylen;
-       } else {
+               if (iov_pages(iv, vnet_hdr_len + copylen, count)
+                   <= MAX_SKB_FRAGS)
+                       zerocopy = true;
+       }
+
+       if (!zerocopy) {
                copylen = len;
                linear = vnet_hdr.hdr_len;
        }
@@ -780,9 +786,15 @@ static ssize_t macvtap_get_user(struct macvtap_queue *q, struct msghdr *m,
 
        if (zerocopy)
                err = zerocopy_sg_from_iovec(skb, iv, vnet_hdr_len, count);
-       else
+       else {
                err = skb_copy_datagram_from_iovec(skb, 0, iv, vnet_hdr_len,
                                                   len);
+               if (!err && m && m->msg_control) {
+                       struct ubuf_info *uarg = m->msg_control;
+                       uarg->callback(uarg, false);
+               }
+       }
+
        if (err)
                goto err_kfree;