diff options
Diffstat (limited to 'drivers/net/tun.c')
-rw-r--r-- | drivers/net/tun.c | 62 |
1 files changed, 38 insertions, 24 deletions
diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 5cdcf92eb310..db690a372260 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c | |||
@@ -1035,6 +1035,29 @@ static int zerocopy_sg_from_iovec(struct sk_buff *skb, const struct iovec *from, | |||
1035 | return 0; | 1035 | return 0; |
1036 | } | 1036 | } |
1037 | 1037 | ||
1038 | static unsigned long iov_pages(const struct iovec *iv, int offset, | ||
1039 | unsigned long nr_segs) | ||
1040 | { | ||
1041 | unsigned long seg, base; | ||
1042 | int pages = 0, len, size; | ||
1043 | |||
1044 | while (nr_segs && (offset >= iv->iov_len)) { | ||
1045 | offset -= iv->iov_len; | ||
1046 | ++iv; | ||
1047 | --nr_segs; | ||
1048 | } | ||
1049 | |||
1050 | for (seg = 0; seg < nr_segs; seg++) { | ||
1051 | base = (unsigned long)iv[seg].iov_base + offset; | ||
1052 | len = iv[seg].iov_len - offset; | ||
1053 | size = ((base & ~PAGE_MASK) + len + ~PAGE_MASK) >> PAGE_SHIFT; | ||
1054 | pages += size; | ||
1055 | offset = 0; | ||
1056 | } | ||
1057 | |||
1058 | return pages; | ||
1059 | } | ||
1060 | |||
1038 | /* Get packet from user space buffer */ | 1061 | /* Get packet from user space buffer */ |
1039 | static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, | 1062 | static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, |
1040 | void *msg_control, const struct iovec *iv, | 1063 | 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, | |||
1082 | return -EINVAL; | 1105 | return -EINVAL; |
1083 | } | 1106 | } |
1084 | 1107 | ||
1085 | if (msg_control) | 1108 | if (msg_control) { |
1086 | zerocopy = true; | 1109 | /* There are 256 bytes to be copied in skb, so there is |
1087 | 1110 | * enough room for skb expand head in case it is used. | |
1088 | if (zerocopy) { | ||
1089 | /* Userspace may produce vectors with count greater than | ||
1090 | * MAX_SKB_FRAGS, so we need to linearize parts of the skb | ||
1091 | * to let the rest of data to be fit in the frags. | ||
1092 | */ | ||
1093 | if (count > MAX_SKB_FRAGS) { | ||
1094 | copylen = iov_length(iv, count - MAX_SKB_FRAGS); | ||
1095 | if (copylen < offset) | ||
1096 | copylen = 0; | ||
1097 | else | ||
1098 | copylen -= offset; | ||
1099 | } else | ||
1100 | copylen = 0; | ||
1101 | /* There are 256 bytes to be copied in skb, so there is enough | ||
1102 | * room for skb expand head in case it is used. | ||
1103 | * The rest of the buffer is mapped from userspace. | 1111 | * The rest of the buffer is mapped from userspace. |
1104 | */ | 1112 | */ |
1105 | if (copylen < gso.hdr_len) | 1113 | copylen = gso.hdr_len ? gso.hdr_len : GOODCOPY_LEN; |
1106 | copylen = gso.hdr_len; | ||
1107 | if (!copylen) | ||
1108 | copylen = GOODCOPY_LEN; | ||
1109 | linear = copylen; | 1114 | linear = copylen; |
1110 | } else { | 1115 | if (iov_pages(iv, offset + copylen, count) <= MAX_SKB_FRAGS) |
1116 | zerocopy = true; | ||
1117 | } | ||
1118 | |||
1119 | if (!zerocopy) { | ||
1111 | copylen = len; | 1120 | copylen = len; |
1112 | linear = gso.hdr_len; | 1121 | linear = gso.hdr_len; |
1113 | } | 1122 | } |
@@ -1121,8 +1130,13 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, | |||
1121 | 1130 | ||
1122 | if (zerocopy) | 1131 | if (zerocopy) |
1123 | err = zerocopy_sg_from_iovec(skb, iv, offset, count); | 1132 | err = zerocopy_sg_from_iovec(skb, iv, offset, count); |
1124 | else | 1133 | else { |
1125 | err = skb_copy_datagram_from_iovec(skb, 0, iv, offset, len); | 1134 | err = skb_copy_datagram_from_iovec(skb, 0, iv, offset, len); |
1135 | if (!err && msg_control) { | ||
1136 | struct ubuf_info *uarg = msg_control; | ||
1137 | uarg->callback(uarg, false); | ||
1138 | } | ||
1139 | } | ||
1126 | 1140 | ||
1127 | if (err) { | 1141 | if (err) { |
1128 | tun->dev->stats.rx_dropped++; | 1142 | tun->dev->stats.rx_dropped++; |