|
@@ -1035,6 +1035,29 @@ static int zerocopy_sg_from_iovec(struct sk_buff *skb, const struct iovec *from,
|
|
|
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 tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
|
|
|
void *msg_control, const struct iovec *iv,
|
|
@@ -1082,32 +1105,18 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
|
- if (msg_control)
|
|
|
- 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 < offset)
|
|
|
- copylen = 0;
|
|
|
- else
|
|
|
- copylen -= offset;
|
|
|
- } else
|
|
|
- copylen = 0;
|
|
|
- /* There are 256 bytes to be copied in skb, so there is enough
|
|
|
- * room for skb expand head in case it is used.
|
|
|
+ if (msg_control) {
|
|
|
+ /* 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 of the buffer is mapped from userspace.
|
|
|
*/
|
|
|
- if (copylen < gso.hdr_len)
|
|
|
- copylen = gso.hdr_len;
|
|
|
- if (!copylen)
|
|
|
- copylen = GOODCOPY_LEN;
|
|
|
+ copylen = gso.hdr_len ? gso.hdr_len : GOODCOPY_LEN;
|
|
|
linear = copylen;
|
|
|
- } else {
|
|
|
+ if (iov_pages(iv, offset + copylen, count) <= MAX_SKB_FRAGS)
|
|
|
+ zerocopy = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!zerocopy) {
|
|
|
copylen = len;
|
|
|
linear = gso.hdr_len;
|
|
|
}
|
|
@@ -1121,8 +1130,13 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
|
|
|
|
|
|
if (zerocopy)
|
|
|
err = zerocopy_sg_from_iovec(skb, iv, offset, count);
|
|
|
- else
|
|
|
+ else {
|
|
|
err = skb_copy_datagram_from_iovec(skb, 0, iv, offset, len);
|
|
|
+ if (!err && msg_control) {
|
|
|
+ struct ubuf_info *uarg = msg_control;
|
|
|
+ uarg->callback(uarg, false);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
if (err) {
|
|
|
tun->dev->stats.rx_dropped++;
|