usb: gadget: rndis: use %z format specifier for size_t
[firefly-linux-kernel-4.4.55.git] / drivers / usb / gadget / function / rndis.c
index 70d3917cc00364e71ca0900557c09fd02ce56bc3..16ae1d28daf6755d5f348c960e54725cddd164b0 100644 (file)
 
 #include "rndis.h"
 
+int rndis_ul_max_pkt_per_xfer_rcvd;
+module_param(rndis_ul_max_pkt_per_xfer_rcvd, int, S_IRUGO);
+MODULE_PARM_DESC(rndis_ul_max_pkt_per_xfer_rcvd,
+               "Max num of REMOTE_NDIS_PACKET_MSGs received in a single transfer");
+
+int rndis_ul_max_xfer_size_rcvd;
+module_param(rndis_ul_max_xfer_size_rcvd, int, S_IRUGO);
+MODULE_PARM_DESC(rndis_ul_max_xfer_size_rcvd,
+               "Max size of bus transfer received");
+
 
 /* The driver for your USB chip needs to support ep0 OUT to work with
  * RNDIS, plus all three CDC Ethernet endpoints (interrupt not optional).
@@ -579,12 +589,12 @@ static int rndis_init_response(struct rndis_params *params,
        resp->MinorVersion = cpu_to_le32(RNDIS_MINOR_VERSION);
        resp->DeviceFlags = cpu_to_le32(RNDIS_DF_CONNECTIONLESS);
        resp->Medium = cpu_to_le32(RNDIS_MEDIUM_802_3);
-       resp->MaxPacketsPerTransfer = cpu_to_le32(1);
-       resp->MaxTransferSize = cpu_to_le32(
-                 params->dev->mtu
+       resp->MaxPacketsPerTransfer = cpu_to_le32(params->max_pkt_per_xfer);
+       resp->MaxTransferSize = cpu_to_le32(params->max_pkt_per_xfer *
+               (params->dev->mtu
                + sizeof(struct ethhdr)
                + sizeof(struct rndis_packet_msg_type)
-               + 22);
+               + 22));
        resp->PacketAlignmentFactor = cpu_to_le32(0);
        resp->AFListOffset = cpu_to_le32(0);
        resp->AFListSize = cpu_to_le32(0);
@@ -681,6 +691,13 @@ static int rndis_reset_response(struct rndis_params *params,
        rndis_reset_cmplt_type *resp;
        rndis_resp_t *r;
 
+       u32 length;
+       u8 *xbuf;
+
+       /* drain the response queue */
+       while ((xbuf = rndis_get_next_response(configNr, &length)))
+               rndis_free_response(configNr, xbuf);
+
        r = rndis_add_response(params, sizeof(rndis_reset_cmplt_type));
        if (!r)
                return -ENOMEM;
@@ -957,6 +974,8 @@ int rndis_set_param_dev(struct rndis_params *params, struct net_device *dev,
        params->dev = dev;
        params->filter = cdc_filter;
 
+       rndis_ul_max_xfer_size_rcvd = 0;
+       rndis_ul_max_pkt_per_xfer_rcvd = 0;
        return 0;
 }
 EXPORT_SYMBOL_GPL(rndis_set_param_dev);
@@ -989,6 +1008,13 @@ int rndis_set_param_medium(struct rndis_params *params, u32 medium, u32 speed)
 }
 EXPORT_SYMBOL_GPL(rndis_set_param_medium);
 
+void rndis_set_max_pkt_xfer(u8 configNr, u8 max_pkt_per_xfer)
+{
+       pr_debug("%s:\n", __func__);
+
+       rndis_per_dev_params[configNr].max_pkt_per_xfer = max_pkt_per_xfer;
+}
+
 void rndis_add_hdr(struct sk_buff *skb)
 {
        struct rndis_packet_msg_type *header;
@@ -1061,23 +1087,73 @@ int rndis_rm_hdr(struct gether *port,
                        struct sk_buff *skb,
                        struct sk_buff_head *list)
 {
-       /* tmp points to a struct rndis_packet_msg_type */
-       __le32 *tmp = (void *)skb->data;
+       int num_pkts = 1;
 
-       /* MessageType, MessageLength */
-       if (cpu_to_le32(RNDIS_MSG_PACKET)
-                       != get_unaligned(tmp++)) {
-               dev_kfree_skb_any(skb);
-               return -EINVAL;
-       }
-       tmp++;
+       if (skb->len > rndis_ul_max_xfer_size_rcvd)
+               rndis_ul_max_xfer_size_rcvd = skb->len;
+
+       while (skb->len) {
+               struct rndis_packet_msg_type *hdr;
+               struct sk_buff          *skb2;
+               u32             msg_len, data_offset, data_len;
 
-       /* DataOffset, DataLength */
-       if (!skb_pull(skb, get_unaligned_le32(tmp++) + 8)) {
-               dev_kfree_skb_any(skb);
-               return -EOVERFLOW;
+               /* some rndis hosts send extra byte to avoid zlp, ignore it */
+               if (skb->len == 1) {
+                       dev_kfree_skb_any(skb);
+                       return 0;
+               }
+
+               if (skb->len < sizeof *hdr) {
+                       pr_err("invalid rndis pkt: skblen:%u hdr_len:%zu",
+                                       skb->len, sizeof *hdr);
+                       dev_kfree_skb_any(skb);
+                       return -EINVAL;
+               }
+
+               hdr = (void *)skb->data;
+               msg_len = le32_to_cpu(hdr->MessageLength);
+               data_offset = le32_to_cpu(hdr->DataOffset);
+               data_len = le32_to_cpu(hdr->DataLength);
+
+               if (skb->len < msg_len ||
+                               ((data_offset + data_len + 8) > msg_len)) {
+                       pr_err("invalid rndis message: %d/%d/%d/%d, len:%d\n",
+                                       le32_to_cpu(hdr->MessageType),
+                                       msg_len, data_offset, data_len, skb->len);
+                       dev_kfree_skb_any(skb);
+                       return -EOVERFLOW;
+               }
+               if (le32_to_cpu(hdr->MessageType) != RNDIS_MSG_PACKET) {
+                       pr_err("invalid rndis message: %d/%d/%d/%d, len:%d\n",
+                                       le32_to_cpu(hdr->MessageType),
+                                       msg_len, data_offset, data_len, skb->len);
+                       dev_kfree_skb_any(skb);
+                       return -EINVAL;
+               }
+
+               skb_pull(skb, data_offset + 8);
+
+               if (msg_len == skb->len) {
+                       skb_trim(skb, data_len);
+                       break;
+               }
+
+               skb2 = skb_clone(skb, GFP_ATOMIC);
+               if (!skb2) {
+                       pr_err("%s:skb clone failed\n", __func__);
+                       dev_kfree_skb_any(skb);
+                       return -ENOMEM;
+               }
+
+               skb_pull(skb, msg_len - sizeof *hdr);
+               skb_trim(skb2, data_len);
+               skb_queue_tail(list, skb2);
+
+               num_pkts++;
        }
-       skb_trim(skb, get_unaligned_le32(tmp++));
+
+       if (num_pkts > rndis_ul_max_pkt_per_xfer_rcvd)
+               rndis_ul_max_pkt_per_xfer_rcvd = num_pkts;
 
        skb_queue_tail(list, skb);
        return 0;