struct bpf_prog;
 struct net_device;
 struct pci_dev;
+struct sk_buff;
 struct tc_to_netdev;
 struct sk_buff;
 struct nfp_app;
  * @extra_cap: extra capabilities string
  * @vnic_init: init vNICs (assign port types, etc.)
  * @vnic_clean:        clean up app's vNIC state
+ * @start:     start application logic
+ * @stop:      stop application logic
+ * @ctrl_msg_rx:    control message handler
  * @setup_tc:  setup TC ndo
  * @tc_busy:   TC HW offload busy (rules loaded)
  * @xdp_offload:    offload an XDP program
                         unsigned int id);
        void (*vnic_clean)(struct nfp_app *app, struct nfp_net *nn);
 
+       int (*start)(struct nfp_app *app);
+       void (*stop)(struct nfp_app *app);
+
+       void (*ctrl_msg_rx)(struct nfp_app *app, struct sk_buff *skb);
+
        int (*setup_tc)(struct nfp_app *app, struct net_device *netdev,
                        u32 handle, __be16 proto, struct tc_to_netdev *tc);
        bool (*tc_busy)(struct nfp_app *app, struct nfp_net *nn);
  * @pdev:      backpointer to PCI device
  * @pf:                backpointer to NFP PF structure
  * @cpp:       pointer to the CPP handle
+ * @ctrl:      pointer to ctrl vNIC struct
  * @type:      pointer to const application ops and info
  */
 struct nfp_app {
        struct nfp_pf *pf;
        struct nfp_cpp *cpp;
 
+       struct nfp_net *ctrl;
+
        const struct nfp_app_type *type;
 };
 
                app->type->vnic_clean(app, nn);
 }
 
+static inline int nfp_app_start(struct nfp_app *app, struct nfp_net *ctrl)
+{
+       app->ctrl = ctrl;
+       if (!app->type->start)
+               return 0;
+       return app->type->start(app);
+}
+
+static inline void nfp_app_stop(struct nfp_app *app)
+{
+       if (!app->type->stop)
+               return;
+       app->type->stop(app);
+}
+
 static inline const char *nfp_app_name(struct nfp_app *app)
 {
        if (!app)
        return app->type->name;
 }
 
+static inline bool nfp_app_needs_ctrl_vnic(struct nfp_app *app)
+{
+       return app && app->type->ctrl_msg_rx;
+}
+
 static inline bool nfp_app_ctrl_has_meta(struct nfp_app *app)
 {
        return app->type->ctrl_has_meta;
        return app->type->xdp_offload(app, nn, prog);
 }
 
+static inline bool nfp_app_ctrl_tx(struct nfp_app *app, struct sk_buff *skb)
+{
+       return nfp_ctrl_tx(app->ctrl, skb);
+}
+
+static inline void nfp_app_ctrl_rx(struct nfp_app *app, struct sk_buff *skb)
+{
+       app->type->ctrl_msg_rx(app, skb);
+}
+
+struct sk_buff *nfp_app_ctrl_msg_alloc(struct nfp_app *app, unsigned int size);
+
 struct nfp_app *nfp_app_alloc(struct nfp_pf *pf, enum nfp_app_id id);
 void nfp_app_free(struct nfp_app *app);
 
 
  * @cpp:               Pointer to the CPP handle
  * @app:               Pointer to the APP handle
  * @data_vnic_bar:     Pointer to the CPP area for the data vNICs' BARs
+ * @ctrl_vnic_bar:     Pointer to the CPP area for the ctrl vNIC's BAR
  * @qc_area:           Pointer to the CPP area for the queues
  * @irq_entries:       Array of MSI-X entries for all vNICs
  * @limit_vfs:         Number of VFs supported by firmware (~0 for PCI limit)
  * @num_vfs:           Number of SR-IOV VFs enabled
  * @fw_loaded:         Is the firmware loaded?
+ * @ctrl_vnic:         Pointer to the control vNIC if available
  * @eth_tbl:           NSP ETH table
  * @nspi:              NSP identification info
  * @hwmon_dev:         pointer to hwmon device
        struct nfp_app *app;
 
        struct nfp_cpp_area *data_vnic_bar;
+       struct nfp_cpp_area *ctrl_vnic_bar;
        struct nfp_cpp_area *qc_area;
 
        struct msix_entry *irq_entries;
 
        bool fw_loaded;
 
+       struct nfp_net *ctrl_vnic;
+
        struct nfp_eth_table *eth_tbl;
        struct nfp_nsp_identify *nspi;
 
 void
 nfp_net_get_mac_addr(struct nfp_net *nn, struct nfp_cpp *cpp, unsigned int id);
 
+bool nfp_ctrl_tx(struct nfp_net *nn, struct sk_buff *skb);
+
 #endif /* NFP_MAIN_H */
 
 
 static void nfp_net_pf_free_vnics(struct nfp_pf *pf)
 {
-       struct nfp_net *nn;
+       struct nfp_net *nn, *next;
 
-       while (!list_empty(&pf->vnics)) {
-               nn = list_first_entry(&pf->vnics, struct nfp_net, vnic_list);
-               nfp_net_pf_free_vnic(pf, nn);
-       }
+       list_for_each_entry_safe(nn, next, &pf->vnics, vnic_list)
+               if (nfp_net_is_data_vnic(nn))
+                       nfp_net_pf_free_vnic(pf, nn);
 }
 
 static struct nfp_net *
        nn->stride_rx = stride;
        nn->stride_tx = stride;
 
-       err = nfp_app_vnic_init(pf->app, nn, eth_id);
-       if (err) {
-               nfp_net_free(nn);
-               return ERR_PTR(err);
+       if (needs_netdev) {
+               err = nfp_app_vnic_init(pf->app, nn, eth_id);
+               if (err) {
+                       nfp_net_free(nn);
+                       return ERR_PTR(err);
+               }
        }
 
        pf->num_vnics++;
        /* Finish vNIC init and register */
        id = 0;
        list_for_each_entry(nn, &pf->vnics, vnic_list) {
+               if (!nfp_net_is_data_vnic(nn))
+                       continue;
                err = nfp_net_pf_init_vnic(pf, nn, id);
                if (err)
                        goto err_prev_deinit;
 
 err_prev_deinit:
        list_for_each_entry_continue_reverse(nn, &pf->vnics, vnic_list)
-               nfp_net_pf_clean_vnic(pf, nn);
+               if (nfp_net_is_data_vnic(nn))
+                       nfp_net_pf_clean_vnic(pf, nn);
        return err;
 }
 
-static int nfp_net_pf_app_init(struct nfp_pf *pf)
+static int
+nfp_net_pf_app_init(struct nfp_pf *pf, u8 __iomem *qc_bar, unsigned int stride)
 {
+       u8 __iomem *ctrl_bar;
        int err;
 
        pf->app = nfp_app_alloc(pf, nfp_net_pf_get_app_id(pf));
        if (err)
                goto err_free;
 
+       if (!nfp_app_needs_ctrl_vnic(pf->app))
+               return 0;
+
+       ctrl_bar = nfp_net_pf_map_rtsym(pf, "net.ctrl", "_pf%u_net_ctrl_bar",
+                                       NFP_PF_CSR_SLICE_SIZE,
+                                       &pf->ctrl_vnic_bar);
+       if (IS_ERR(ctrl_bar)) {
+               err = PTR_ERR(ctrl_bar);
+               goto err_free;
+       }
+
+       pf->ctrl_vnic = nfp_net_pf_alloc_vnic(pf, false, ctrl_bar, qc_bar,
+                                             stride, 0);
+       if (IS_ERR(pf->ctrl_vnic)) {
+               err = PTR_ERR(pf->ctrl_vnic);
+               goto err_unmap;
+       }
+
        return 0;
 
+err_unmap:
+       nfp_cpp_area_release_free(pf->ctrl_vnic_bar);
 err_free:
        nfp_app_free(pf->app);
        return err;
 
 static void nfp_net_pf_app_clean(struct nfp_pf *pf)
 {
+       if (pf->ctrl_vnic) {
+               nfp_net_pf_free_vnic(pf, pf->ctrl_vnic);
+               nfp_cpp_area_release_free(pf->ctrl_vnic_bar);
+       }
        nfp_app_free(pf->app);
        pf->app = NULL;
 }
 
+static int nfp_net_pf_app_start_ctrl(struct nfp_pf *pf)
+{
+       int err;
+
+       if (!pf->ctrl_vnic)
+               return 0;
+       err = nfp_net_pf_init_vnic(pf, pf->ctrl_vnic, 0);
+       if (err)
+               return err;
+
+       err = nfp_ctrl_open(pf->ctrl_vnic);
+       if (err)
+               goto err_clean_ctrl;
+
+       return 0;
+
+err_clean_ctrl:
+       nfp_net_pf_clean_vnic(pf, pf->ctrl_vnic);
+       return err;
+}
+
+static void nfp_net_pf_app_stop_ctrl(struct nfp_pf *pf)
+{
+       if (!pf->ctrl_vnic)
+               return;
+       nfp_ctrl_close(pf->ctrl_vnic);
+       nfp_net_pf_clean_vnic(pf, pf->ctrl_vnic);
+}
+
+static int nfp_net_pf_app_start(struct nfp_pf *pf)
+{
+       int err;
+
+       err = nfp_net_pf_app_start_ctrl(pf);
+       if (err)
+               return err;
+
+       err = nfp_app_start(pf->app, pf->ctrl_vnic);
+       if (err)
+               goto err_ctrl_stop;
+
+       return 0;
+
+err_ctrl_stop:
+       nfp_net_pf_app_stop_ctrl(pf);
+       return err;
+}
+
+static void nfp_net_pf_app_stop(struct nfp_pf *pf)
+{
+       nfp_app_stop(pf->app);
+       nfp_net_pf_app_stop_ctrl(pf);
+}
+
 static void nfp_net_pci_remove_finish(struct nfp_pf *pf)
 {
+       nfp_net_pf_app_stop(pf);
+       /* stop app first, to avoid double free of ctrl vNIC's ddir */
        nfp_net_debugfs_dir_clean(&pf->ddir);
 
        nfp_net_pf_free_irqs(pf);
                goto err_ctrl_unmap;
        }
 
-       err = nfp_net_pf_app_init(pf);
+       err = nfp_net_pf_app_init(pf, qc_bar, stride);
        if (err)
                goto err_unmap_qc;
 
        if (err)
                goto err_free_vnics;
 
-       err = nfp_net_pf_init_vnics(pf);
+       err = nfp_net_pf_app_start(pf);
        if (err)
                goto err_free_irqs;
 
+       err = nfp_net_pf_init_vnics(pf);
+       if (err)
+               goto err_stop_app;
+
        mutex_unlock(&pf->lock);
 
        return 0;
 
+err_stop_app:
+       nfp_net_pf_app_stop(pf);
 err_free_irqs:
        nfp_net_pf_free_irqs(pf);
 err_free_vnics:
                goto out;
 
        list_for_each_entry(nn, &pf->vnics, vnic_list)
-               nfp_net_pf_clean_vnic(pf, nn);
+               if (nfp_net_is_data_vnic(nn))
+                       nfp_net_pf_clean_vnic(pf, nn);
 
        nfp_net_pf_free_vnics(pf);