PTR     sys_connect
        PTR     sys_accept
        PTR     sys_sendto
-       PTR     sys_recvfrom
+       PTR     compat_sys_recvfrom
        PTR     compat_sys_sendmsg              /* 6045 */
        PTR     compat_sys_recvmsg
        PTR     sys_shutdown
 
        PTR     sys_getsockname
        PTR     sys_getsockopt
        PTR     sys_listen
-       PTR     sys_recv                        /* 4175 */
-       PTR     sys_recvfrom
+       PTR     compat_sys_recv                 /* 4175 */
+       PTR     compat_sys_recvfrom
        PTR     compat_sys_recvmsg
        PTR     sys_send
        PTR     compat_sys_sendmsg
 
 SIGN1(sys32_umask, sys_umask, %o0)
 SIGN3(sys32_tgkill, sys_tgkill, %o0, %o1, %o2)
 SIGN1(sys32_sendto, sys_sendto, %o0)
-SIGN1(sys32_recvfrom, sys_recvfrom, %o0)
+SIGN1(sys32_recvfrom, compat_sys_recvfrom, %o0)
 SIGN3(sys32_socket, sys_socket, %o0, %o1, %o2)
 SIGN2(sys32_connect, sys_connect, %o0, %o2)
 SIGN2(sys32_bind, sys_bind, %o0, %o2)
 
 };
 #define IW_EV_COMPAT_LCP_LEN offsetof(struct __compat_iw_event, pointer)
 #define IW_EV_COMPAT_POINT_OFF offsetof(struct compat_iw_point, length)
+
+/* Size of the various events for compat */
+#define IW_EV_COMPAT_CHAR_LEN  (IW_EV_COMPAT_LCP_LEN + IFNAMSIZ)
+#define IW_EV_COMPAT_UINT_LEN  (IW_EV_COMPAT_LCP_LEN + sizeof(__u32))
+#define IW_EV_COMPAT_FREQ_LEN  (IW_EV_COMPAT_LCP_LEN + sizeof(struct iw_freq))
+#define IW_EV_COMPAT_PARAM_LEN (IW_EV_COMPAT_LCP_LEN + sizeof(struct iw_param))
+#define IW_EV_COMPAT_ADDR_LEN  (IW_EV_COMPAT_LCP_LEN + sizeof(struct sockaddr))
+#define IW_EV_COMPAT_QUAL_LEN  (IW_EV_COMPAT_LCP_LEN + sizeof(struct iw_quality))
 #define IW_EV_COMPAT_POINT_LEN \
        (IW_EV_COMPAT_LCP_LEN + sizeof(struct compat_iw_point) - \
         IW_EV_COMPAT_POINT_OFF)
 
 
 if NET
 
+config WANT_COMPAT_NETLINK_MESSAGES
+       bool
+       help
+         This option can be selected by other options that need compat
+         netlink messages.
+
+config COMPAT_NETLINK_MESSAGES
+       def_bool y
+       depends on COMPAT
+       depends on WIRELESS_EXT || WANT_COMPAT_NETLINK_MESSAGES
+       help
+         This option makes it possible to send different netlink messages
+         to tasks depending on whether the task is a compat task or not. To
+         achieve this, you need to set skb_shinfo(skb)->frag_list to the
+         compat skb before sending the skb, the netlink code will sort out
+         which message to actually pass to the task.
+
+         Newly written code should NEVER need this option but do
+         compat-independent messages instead!
+
 menu "Networking options"
 
 source "net/packet/Kconfig"
 
        return sys_recvmsg(fd, (struct msghdr __user *)msg, flags | MSG_CMSG_COMPAT);
 }
 
+asmlinkage long compat_sys_recv(int fd, void __user *buf, size_t len, unsigned flags)
+{
+       return sys_recv(fd, buf, len, flags | MSG_CMSG_COMPAT);
+}
+
+asmlinkage long compat_sys_recvfrom(int fd, void __user *buf, size_t len,
+                                   unsigned flags, struct sockaddr __user *addr,
+                                   int __user *addrlen)
+{
+       return sys_recvfrom(fd, buf, len, flags | MSG_CMSG_COMPAT, addr, addrlen);
+}
+
 asmlinkage long compat_sys_socketcall(int call, u32 __user *args)
 {
        int ret;
                ret = sys_sendto(a0, compat_ptr(a1), a[2], a[3], compat_ptr(a[4]), a[5]);
                break;
        case SYS_RECV:
-               ret = sys_recv(a0, compat_ptr(a1), a[2], a[3]);
+               ret = compat_sys_recv(a0, compat_ptr(a1), a[2], a[3]);
                break;
        case SYS_RECVFROM:
-               ret = sys_recvfrom(a0, compat_ptr(a1), a[2], a[3], compat_ptr(a[4]), compat_ptr(a[5]));
+               ret = compat_sys_recvfrom(a0, compat_ptr(a1), a[2], a[3],
+                                         compat_ptr(a[4]), compat_ptr(a[5]));
                break;
        case SYS_SHUTDOWN:
                ret = sys_shutdown(a0,a1);
 
        struct netlink_sock *nlk = nlk_sk(sk);
        int noblock = flags&MSG_DONTWAIT;
        size_t copied;
-       struct sk_buff *skb;
+       struct sk_buff *skb, *frag __maybe_unused = NULL;
        int err;
 
        if (flags&MSG_OOB)
        if (skb == NULL)
                goto out;
 
+#ifdef CONFIG_COMPAT_NETLINK_MESSAGES
+       if (unlikely(skb_shinfo(skb)->frag_list)) {
+               bool need_compat = !!(flags & MSG_CMSG_COMPAT);
+
+               /*
+                * If this skb has a frag_list, then here that means that
+                * we will have to use the frag_list skb for compat tasks
+                * and the regular skb for non-compat tasks.
+                *
+                * The skb might (and likely will) be cloned, so we can't
+                * just reset frag_list and go on with things -- we need to
+                * keep that. For the compat case that's easy -- simply get
+                * a reference to the compat skb and free the regular one
+                * including the frag. For the non-compat case, we need to
+                * avoid sending the frag to the user -- so assign NULL but
+                * restore it below before freeing the skb.
+                */
+               if (need_compat) {
+                       struct sk_buff *compskb = skb_shinfo(skb)->frag_list;
+                       skb_get(compskb);
+                       kfree_skb(skb);
+                       skb = compskb;
+               } else {
+                       frag = skb_shinfo(skb)->frag_list;
+                       skb_shinfo(skb)->frag_list = NULL;
+               }
+       }
+#endif
+
        msg->msg_namelen = 0;
 
        copied = skb->len;
        siocb->scm->creds = *NETLINK_CREDS(skb);
        if (flags & MSG_TRUNC)
                copied = skb->len;
+
+#ifdef CONFIG_COMPAT_NETLINK_MESSAGES
+       skb_shinfo(skb)->frag_list = frag;
+#endif
+
        skb_free_datagram(sk, skb);
 
        if (nlk->cb && atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2)
 
        IW_EV_QUAL_LEN,                 /* IW_HEADER_TYPE_QUAL */
 };
 
+#ifdef CONFIG_COMPAT
+static const int compat_event_type_size[] = {
+       IW_EV_COMPAT_LCP_LEN,           /* IW_HEADER_TYPE_NULL */
+       0,
+       IW_EV_COMPAT_CHAR_LEN,          /* IW_HEADER_TYPE_CHAR */
+       0,
+       IW_EV_COMPAT_UINT_LEN,          /* IW_HEADER_TYPE_UINT */
+       IW_EV_COMPAT_FREQ_LEN,          /* IW_HEADER_TYPE_FREQ */
+       IW_EV_COMPAT_ADDR_LEN,          /* IW_HEADER_TYPE_ADDR */
+       0,
+       IW_EV_COMPAT_POINT_LEN,         /* Without variable payload */
+       IW_EV_COMPAT_PARAM_LEN,         /* IW_HEADER_TYPE_PARAM */
+       IW_EV_COMPAT_QUAL_LEN,          /* IW_HEADER_TYPE_QUAL */
+};
+#endif
 
 /************************ COMMON SUBROUTINES ************************/
 /*
        struct sk_buff *skb;
        struct nlmsghdr *nlh;
        struct nlattr *nla;
+#ifdef CONFIG_COMPAT
+       struct __compat_iw_event *compat_event;
+       struct compat_iw_point compat_wrqu;
+       struct sk_buff *compskb;
+#endif
+
+       /*
+        * Nothing in the kernel sends scan events with data, be safe.
+        * This is necessary because we cannot fix up scan event data
+        * for compat, due to being contained in 'extra', but normally
+        * applications are required to retrieve the scan data anyway
+        * and no data is included in the event, this codifies that
+        * practice.
+        */
+       if (WARN_ON(cmd == SIOCGIWSCAN && extra))
+               extra = NULL;
 
        /* Get the description of the Event */
        if (cmd <= SIOCIWLAST) {
                memcpy(((char *) event) + hdr_len, extra, extra_len);
 
        nlmsg_end(skb, nlh);
+#ifdef CONFIG_COMPAT
+       hdr_len = compat_event_type_size[descr->header_type];
+       event_len = hdr_len + extra_len;
 
+       compskb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+       if (!compskb) {
+               kfree_skb(skb);
+               return;
+       }
+
+       /* Send via the RtNetlink event channel */
+       nlh = rtnetlink_ifinfo_prep(dev, compskb);
+       if (WARN_ON(!nlh)) {
+               kfree_skb(skb);
+               kfree_skb(compskb);
+               return;
+       }
+
+       /* Add the wireless events in the netlink packet */
+       nla = nla_reserve(compskb, IFLA_WIRELESS, event_len);
+       if (!nla) {
+               kfree_skb(skb);
+               kfree_skb(compskb);
+               return;
+       }
+       compat_event = nla_data(nla);
+
+       compat_event->len = event_len;
+       compat_event->cmd = cmd;
+       if (descr->header_type == IW_HEADER_TYPE_POINT) {
+               compat_wrqu.length = wrqu->data.length;
+               compat_wrqu.flags = wrqu->data.flags;
+               memcpy(&compat_event->pointer,
+                       ((char *) &compat_wrqu) + IW_EV_COMPAT_POINT_OFF,
+                       hdr_len - IW_EV_COMPAT_LCP_LEN);
+               if (extra_len)
+                       memcpy(((char *) compat_event) + hdr_len,
+                               extra, extra_len);
+       } else {
+               /* extra_len must be zero, so no if (extra) needed */
+               memcpy(&compat_event->pointer, wrqu,
+                       hdr_len - IW_EV_COMPAT_LCP_LEN);
+       }
+
+       nlmsg_end(compskb, nlh);
+
+       skb_shinfo(skb)->frag_list = compskb;
+#endif
        skb_queue_tail(&dev_net(dev)->wext_nlevents, skb);
        schedule_work(&wireless_nlevent_work);
 }