// SPDX-License-Identifier: GPL-2.0
 /*
- * Copyright (C) 2017 - Cambridge Greys Limited
+ * Copyright (C) 2017 - 2019 Cambridge Greys Limited
  * Copyright (C) 2011 - 2014 Cisco Systems Inc
  * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
  * Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org) and
 #include <linux/skbuff.h>
 #include <linux/slab.h>
 #include <linux/interrupt.h>
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include <uapi/linux/filter.h>
 #include <init.h>
 #include <irq_kern.h>
 #include <irq_user.h>
        return ETH_MAX_PACKET;
 }
 
+static char *get_bpf_file(struct arglist *def)
+{
+       return uml_vector_fetch_arg(def, "bpffile");
+}
+
+static bool get_bpf_flash(struct arglist *def)
+{
+       char *allow = uml_vector_fetch_arg(def, "bpfflash");
+       long result;
+
+       if (allow != NULL) {
+               if (kstrtoul(allow, 10, &result) == 0)
+                       return (allow > 0);
+       }
+       return false;
+}
+
 static int get_depth(struct arglist *def)
 {
        char *mtu = uml_vector_fetch_arg(def, "depth");
        int vec_rx = VECTOR_RX;
        int vec_tx = VECTOR_TX;
        long parsed;
+       int result = 0;
 
        if (vector != NULL) {
                if (kstrtoul(vector, 10, &parsed) == 0) {
                }
        }
 
+       if (get_bpf_flash(def))
+               result = VECTOR_BPF_FLASH;
 
        if (strncmp(transport, TRANS_TAP, TRANS_TAP_LEN) == 0)
-               return 0;
+               return result;
        if (strncmp(transport, TRANS_HYBRID, TRANS_HYBRID_LEN) == 0)
-               return (vec_rx | VECTOR_BPF);
+               return (result | vec_rx | VECTOR_BPF);
        if (strncmp(transport, TRANS_RAW, TRANS_RAW_LEN) == 0)
-               return (vec_rx | vec_tx | VECTOR_QDISC_BYPASS);
-       return (vec_rx | vec_tx);
+               return (result | vec_rx | vec_tx | VECTOR_QDISC_BYPASS);
+       return (result | vec_rx | vec_tx);
 }
 
 
        }
        tasklet_kill(&vp->tx_poll);
        if (vp->fds->rx_fd > 0) {
+               if (vp->bpf)
+                       uml_vector_detach_bpf(vp->fds->rx_fd, vp->bpf);
                os_close_file(vp->fds->rx_fd);
                vp->fds->rx_fd = -1;
        }
                os_close_file(vp->fds->tx_fd);
                vp->fds->tx_fd = -1;
        }
+       if (vp->bpf != NULL)
+               kfree(vp->bpf->filter);
        kfree(vp->bpf);
+       vp->bpf = NULL;
        kfree(vp->fds->remote_addr);
        kfree(vp->transport_data);
        kfree(vp->header_rxbuffer);
        netif_start_queue(vp->dev);
        netif_wake_queue(vp->dev);
 }
+
 static int vector_net_open(struct net_device *dev)
 {
        struct vector_private *vp = netdev_priv(dev);
        vp->opened = true;
        spin_unlock_irqrestore(&vp->lock, flags);
 
+       vp->bpf = uml_vector_user_bpf(get_bpf_file(vp->parsed));
+
        vp->fds = uml_vector_user_open(vp->unit, vp->parsed);
 
        if (vp->fds == NULL)
                if (!uml_raw_enable_qdisc_bypass(vp->fds->rx_fd))
                        vp->options |= VECTOR_BPF;
        }
-       if ((vp->options & VECTOR_BPF) != 0)
-               vp->bpf = uml_vector_default_bpf(vp->fds->rx_fd, dev->dev_addr);
+       if (((vp->options & VECTOR_BPF) != 0) && (vp->bpf == NULL))
+               vp->bpf = uml_vector_default_bpf(dev->dev_addr);
+
+       if (vp->bpf != NULL)
+               uml_vector_attach_bpf(vp->fds->rx_fd, vp->bpf);
 
        netif_start_queue(dev);
 
        strlcpy(info->version, DRIVER_VERSION, sizeof(info->version));
 }
 
+static int vector_net_load_bpf_flash(struct net_device *dev,
+                               struct ethtool_flash *efl)
+{
+       struct vector_private *vp = netdev_priv(dev);
+       struct vector_device *vdevice;
+       const struct firmware *fw;
+       int result = 0;
+
+       if (!(vp->options & VECTOR_BPF_FLASH)) {
+               netdev_err(dev, "loading firmware not permitted: %s\n", efl->data);
+               return -1;
+       }
+
+       spin_lock(&vp->lock);
+
+       if (vp->bpf != NULL) {
+               if (vp->opened)
+                       uml_vector_detach_bpf(vp->fds->rx_fd, vp->bpf);
+               kfree(vp->bpf->filter);
+               vp->bpf->filter = NULL;
+       } else {
+               vp->bpf = kmalloc(sizeof(struct sock_fprog), GFP_KERNEL);
+               if (vp->bpf == NULL) {
+                       netdev_err(dev, "failed to allocate memory for firmware\n");
+                       goto flash_fail;
+               }
+       }
+
+       vdevice = find_device(vp->unit);
+
+       if (request_firmware(&fw, efl->data, &vdevice->pdev.dev))
+               goto flash_fail;
+
+       vp->bpf->filter = kmemdup(fw->data, fw->size, GFP_KERNEL);
+       if (!vp->bpf->filter)
+               goto free_buffer;
+
+       vp->bpf->len = fw->size / sizeof(struct sock_filter);
+       release_firmware(fw);
+
+       if (vp->opened)
+               result = uml_vector_attach_bpf(vp->fds->rx_fd, vp->bpf);
+
+       spin_unlock(&vp->lock);
+
+       return result;
+
+free_buffer:
+       release_firmware(fw);
+
+flash_fail:
+       spin_unlock(&vp->lock);
+       if (vp->bpf != NULL)
+               kfree(vp->bpf->filter);
+       kfree(vp->bpf);
+       vp->bpf = NULL;
+       return -1;
+}
+
 static void vector_get_ringparam(struct net_device *netdev,
                                struct ethtool_ringparam *ring)
 {
        .get_ethtool_stats = vector_get_ethtool_stats,
        .get_coalesce   = vector_get_coalesce,
        .set_coalesce   = vector_set_coalesce,
+       .flash_device   = vector_net_load_bpf_flash,
 };
 
 
                .in_write_poll          = false,
                .coalesce               = 2,
                .req_size               = get_req_size(def),
-               .in_error               = false
-               });
+               .in_error               = false,
+               .bpf                    = NULL
+       });
 
        dev->features = dev->hw_features = (NETIF_F_SG | NETIF_F_FRAGLIST);
        tasklet_init(&vp->tx_poll, vector_tx_poll, (unsigned long)vp);
 
 #define VECTOR_TX (1 << 1)
 #define VECTOR_BPF (1 << 2)
 #define VECTOR_QDISC_BYPASS (1 << 3)
+#define VECTOR_BPF_FLASH (1 << 4)
 
 #define ETH_MAX_PACKET 1500
 #define ETH_HEADER_OTHER 32 /* just in case someone decides to go mad on QnQ */
 
+#define MAX_FILTER_PROG (2 << 16)
+
 struct vector_queue {
        struct mmsghdr *mmsg_vector;
        void **skbuff_vector;
        bool in_write_poll;
        bool in_error;
 
+       /* guest allowed to use ethtool flash to load bpf */
+       bool bpf_via_flash;
+
        /* ethtool stats */
 
        struct vector_estats estats;
-       void *bpf;
+       struct sock_fprog *bpf;
 
        char user[0];
 };
 
 #define TUN_GET_F_FAIL "tapraw: TUNGETFEATURES failed: %s"
 #define L2TPV3_BIND_FAIL "l2tpv3_open : could not bind socket err=%i"
 #define UNIX_BIND_FAIL "unix_open : could not bind socket err=%i"
-#define BPF_ATTACH_FAIL "Failed to attach filter size %d to %d, err %d\n"
+#define BPF_ATTACH_FAIL "Failed to attach filter size %d prog %px to %d, err %d\n"
+#define BPF_DETACH_FAIL "Failed to detach filter size %d prog %px to %d, err %d\n"
 
 #define MAX_UN_LEN 107
 
        else
                return -errno;
 }
-int uml_vector_attach_bpf(int fd, void *bpf, int bpf_len)
+int uml_vector_attach_bpf(int fd, void *bpf)
 {
-       int err = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, bpf, bpf_len);
+       struct sock_fprog *prog = bpf;
+
+       int err = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, bpf, sizeof(struct sock_fprog));
 
        if (err < 0)
-               printk(KERN_ERR BPF_ATTACH_FAIL, bpf_len, fd, -errno);
+               printk(KERN_ERR BPF_ATTACH_FAIL, prog->len, prog->filter, fd, -errno);
        return err;
 }
 
-#define DEFAULT_BPF_LEN 6
+int uml_vector_detach_bpf(int fd, void *bpf)
+{
+       struct sock_fprog *prog = bpf;
 
-void *uml_vector_default_bpf(int fd, void *mac)
+       int err = setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, bpf, sizeof(struct sock_fprog));
+       if (err < 0)
+               printk(KERN_ERR BPF_DETACH_FAIL, prog->len, prog->filter, fd, -errno);
+       return err;
+}
+void *uml_vector_default_bpf(void *mac)
 {
        struct sock_filter *bpf;
        uint32_t *mac1 = (uint32_t *)(mac + 2);
        uint16_t *mac2 = (uint16_t *) mac;
-       struct sock_fprog bpf_prog = {
-               .len = 6,
-               .filter = NULL,
-       };
+       struct sock_fprog *bpf_prog;
 
+       bpf_prog = uml_kmalloc(sizeof(struct sock_fprog), UM_GFP_KERNEL);
+       if (bpf_prog) {
+               bpf_prog->len = DEFAULT_BPF_LEN;
+               bpf_prog->filter = NULL;
+       } else {
+               return NULL;
+       }
        bpf = uml_kmalloc(
                sizeof(struct sock_filter) * DEFAULT_BPF_LEN, UM_GFP_KERNEL);
-       if (bpf != NULL) {
-               bpf_prog.filter = bpf;
+       if (bpf) {
+               bpf_prog->filter = bpf;
                /* ld   [8] */
                bpf[0] = (struct sock_filter){ 0x20, 0, 0, 0x00000008 };
                /* jeq  #0xMAC[2-6] jt 2 jf 5*/
                bpf[4] = (struct sock_filter){ 0x6, 0, 0, 0x00000000 };
                /* ret  #0x40000 */
                bpf[5] = (struct sock_filter){ 0x6, 0, 0, 0x00040000 };
-               if (uml_vector_attach_bpf(
-                       fd, &bpf_prog, sizeof(struct sock_fprog)) < 0) {
-                       kfree(bpf);
-                       bpf = NULL;
-               }
+       } else {
+               kfree(bpf_prog);
+               bpf_prog = NULL;
        }
-       return bpf;
+       return bpf_prog;
 }
 
+/* Note - this function requires a valid mac being passed as an arg */
+
+void *uml_vector_user_bpf(char *filename)
+{
+       struct sock_filter *bpf;
+       struct sock_fprog *bpf_prog;
+       struct stat statbuf;
+       int res, ffd = -1;
+
+       if (filename == NULL)
+               return NULL;
+
+       if (stat(filename, &statbuf) < 0) {
+               printk(KERN_ERR "Error %d reading bpf file", -errno);
+               return false;
+       }
+       bpf_prog = uml_kmalloc(sizeof(struct sock_fprog), UM_GFP_KERNEL);
+       if (bpf_prog != NULL) {
+               bpf_prog->len = statbuf.st_size / sizeof(struct sock_filter);
+               bpf_prog->filter = NULL;
+       }
+       ffd = os_open_file(filename, of_read(OPENFLAGS()), 0);
+       if (ffd < 0) {
+               printk(KERN_ERR "Error %d opening bpf file", -errno);
+               goto bpf_failed;
+       }
+       bpf = uml_kmalloc(statbuf.st_size, UM_GFP_KERNEL);
+       if (bpf == NULL) {
+               printk(KERN_ERR "Failed to allocate bpf buffer");
+               goto bpf_failed;
+       }
+       bpf_prog->filter = bpf;
+       res = os_read_file(ffd, bpf, statbuf.st_size);
+       if (res < statbuf.st_size) {
+               printk(KERN_ERR "Failed to read bpf program %s, error %d", filename, res);
+               kfree(bpf);
+               goto bpf_failed;
+       }
+       os_close_file(ffd);
+       return bpf_prog;
+bpf_failed:
+       if (ffd > 0)
+               os_close_file(ffd);
+       kfree(bpf_prog);
+       return NULL;
+}
 
 #define TRANS_BESS "bess"
 #define TRANS_BESS_LEN strlen(TRANS_BESS)
 
+#define DEFAULT_BPF_LEN 6
+
 #ifndef IPPROTO_GRE
 #define IPPROTO_GRE 0x2F
 #endif
        unsigned int vlen,
        unsigned int flags
 );
-extern void *uml_vector_default_bpf(int fd, void *mac);
-extern int uml_vector_attach_bpf(int fd, void *bpf, int bpf_len);
+extern void *uml_vector_default_bpf(void *mac);
+extern void *uml_vector_user_bpf(char *filename);
+extern int uml_vector_attach_bpf(int fd, void *bpf);
+extern int uml_vector_detach_bpf(int fd, void *bpf);
 extern bool uml_raw_enable_qdisc_bypass(int fd);
 extern bool uml_raw_enable_vnet_headers(int fd);
 extern bool uml_tap_enable_vnet_headers(int fd);