struct mlx5_sf {
        struct devlink_port dl_port;
        unsigned int port_index;
+       u32 controller;
        u16 id;
        u16 hw_fn_id;
        u16 hw_state;
 }
 
 static struct mlx5_sf *
-mlx5_sf_alloc(struct mlx5_sf_table *table, u32 sfnum, struct netlink_ext_ack *extack)
+mlx5_sf_alloc(struct mlx5_sf_table *table, struct mlx5_eswitch *esw,
+             u32 controller, u32 sfnum, struct netlink_ext_ack *extack)
 {
        unsigned int dl_port_index;
        struct mlx5_sf *sf;
        int id_err;
        int err;
 
-       id_err = mlx5_sf_hw_table_sf_alloc(table->dev, sfnum);
+       if (!mlx5_esw_offloads_controller_valid(esw, controller)) {
+               NL_SET_ERR_MSG_MOD(extack, "Invalid controller number");
+               return ERR_PTR(-EINVAL);
+       }
+
+       id_err = mlx5_sf_hw_table_sf_alloc(table->dev, controller, sfnum);
        if (id_err < 0) {
                err = id_err;
                goto id_err;
                goto alloc_err;
        }
        sf->id = id_err;
-       hw_fn_id = mlx5_sf_sw_to_hw_id(table->dev, sf->id);
+       hw_fn_id = mlx5_sf_sw_to_hw_id(table->dev, controller, sf->id);
        dl_port_index = mlx5_esw_vport_to_devlink_port_index(table->dev, hw_fn_id);
        sf->port_index = dl_port_index;
        sf->hw_fn_id = hw_fn_id;
        sf->hw_state = MLX5_VHCA_STATE_ALLOCATED;
+       sf->controller = controller;
 
        err = mlx5_sf_id_insert(table, sf);
        if (err)
 insert_err:
        kfree(sf);
 alloc_err:
-       mlx5_sf_hw_table_sf_free(table->dev, id_err);
+       mlx5_sf_hw_table_sf_free(table->dev, controller, id_err);
 id_err:
        if (err == -EEXIST)
                NL_SET_ERR_MSG_MOD(extack, "SF already exist. Choose different sfnum");
 static void mlx5_sf_free(struct mlx5_sf_table *table, struct mlx5_sf *sf)
 {
        mlx5_sf_id_erase(table, sf);
-       mlx5_sf_hw_table_sf_free(table->dev, sf->id);
+       mlx5_sf_hw_table_sf_free(table->dev, sf->controller, sf->id);
        kfree(sf);
 }
 
        struct mlx5_sf *sf;
        int err;
 
-       sf = mlx5_sf_alloc(table, new_attr->sfnum, extack);
+       sf = mlx5_sf_alloc(table, esw, new_attr->controller, new_attr->sfnum, extack);
        if (IS_ERR(sf))
                return PTR_ERR(sf);
 
        err = mlx5_esw_offloads_sf_vport_enable(esw, &sf->dl_port, sf->hw_fn_id,
-                                               new_attr->sfnum);
+                                               new_attr->controller, new_attr->sfnum);
        if (err)
                goto esw_err;
        *new_port_index = sf->port_index;
                                   "User must provide unique sfnum. Driver does not support auto assignment");
                return -EOPNOTSUPP;
        }
-       if (new_attr->controller_valid && new_attr->controller) {
+       if (new_attr->controller_valid && new_attr->controller &&
+           !mlx5_core_is_ecpf_esw_manager(dev)) {
                NL_SET_ERR_MSG_MOD(extack, "External controller is unsupported");
                return -EOPNOTSUPP;
        }
                 * firmware gives confirmation that it is detached by the driver.
                 */
                mlx5_cmd_sf_disable_hca(table->dev, sf->hw_fn_id);
-               mlx5_sf_hw_table_sf_deferred_free(table->dev, sf->id);
+               mlx5_sf_hw_table_sf_deferred_free(table->dev, sf->controller, sf->id);
                kfree(sf);
        } else {
-               mlx5_sf_hw_table_sf_deferred_free(table->dev, sf->id);
+               mlx5_sf_hw_table_sf_deferred_free(table->dev, sf->controller, sf->id);
                kfree(sf);
        }
 }
 
 #include "ecpf.h"
 #include "vhca_event.h"
 #include "mlx5_core.h"
+#include "eswitch.h"
 
 struct mlx5_sf_hw {
        u32 usr_sfnum;
 
 enum mlx5_sf_hwc_index {
        MLX5_SF_HWC_LOCAL,
+       MLX5_SF_HWC_EXTERNAL,
        MLX5_SF_HWC_MAX,
 };
 
        struct mlx5_sf_hwc_table hwc[MLX5_SF_HWC_MAX];
 };
 
-u16 mlx5_sf_sw_to_hw_id(const struct mlx5_core_dev *dev, u16 sw_id)
+static struct mlx5_sf_hwc_table *
+mlx5_sf_controller_to_hwc(struct mlx5_core_dev *dev, u32 controller)
 {
-       struct mlx5_sf_hwc_table *hwc = &dev->priv.sf_hw_table->hwc[MLX5_SF_HWC_LOCAL];
+       int idx = !!controller;
 
-       return hwc->start_fn_id + sw_id;
+       return &dev->priv.sf_hw_table->hwc[idx];
 }
 
-static u16 mlx5_sf_hw_to_sw_id(const struct mlx5_core_dev *dev, u16 hw_id)
+u16 mlx5_sf_sw_to_hw_id(struct mlx5_core_dev *dev, u32 controller, u16 sw_id)
 {
-       struct mlx5_sf_hwc_table *hwc = &dev->priv.sf_hw_table->hwc[MLX5_SF_HWC_LOCAL];
+       struct mlx5_sf_hwc_table *hwc;
+
+       hwc = mlx5_sf_controller_to_hwc(dev, controller);
+       return hwc->start_fn_id + sw_id;
+}
 
+static u16 mlx5_sf_hw_to_sw_id(struct mlx5_sf_hwc_table *hwc, u16 hw_id)
+{
        return hw_id - hwc->start_fn_id;
 }
 
-static int mlx5_sf_hw_table_id_alloc(struct mlx5_sf_hw_table *table, u32 usr_sfnum)
+static struct mlx5_sf_hwc_table *
+mlx5_sf_table_fn_to_hwc(struct mlx5_sf_hw_table *table, u16 fn_id)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(table->hwc); i++) {
+               if (table->hwc[i].max_fn &&
+                   fn_id >= table->hwc[i].start_fn_id &&
+                   fn_id < (table->hwc[i].start_fn_id + table->hwc[i].max_fn))
+                       return &table->hwc[i];
+       }
+       return NULL;
+}
+
+static int mlx5_sf_hw_table_id_alloc(struct mlx5_sf_hw_table *table, u32 controller,
+                                    u32 usr_sfnum)
 {
        struct mlx5_sf_hwc_table *hwc;
        int i;
 
-       hwc = &table->hwc[MLX5_SF_HWC_LOCAL];
+       hwc = mlx5_sf_controller_to_hwc(table->dev, controller);
+       if (!hwc->sfs)
+               return -ENOSPC;
 
        /* Check if sf with same sfnum already exists or not. */
        for (i = 0; i < hwc->max_fn; i++) {
        return -ENOSPC;
 }
 
-static void mlx5_sf_hw_table_id_free(struct mlx5_sf_hw_table *table, int id)
+static void mlx5_sf_hw_table_id_free(struct mlx5_sf_hw_table *table, u32 controller, int id)
 {
-       struct mlx5_sf_hwc_table *hwc = &table->hwc[MLX5_SF_HWC_LOCAL];
+       struct mlx5_sf_hwc_table *hwc;
 
+       hwc = mlx5_sf_controller_to_hwc(table->dev, controller);
        hwc->sfs[id].allocated = false;
        hwc->sfs[id].pending_delete = false;
 }
 
-int mlx5_sf_hw_table_sf_alloc(struct mlx5_core_dev *dev, u32 usr_sfnum)
+int mlx5_sf_hw_table_sf_alloc(struct mlx5_core_dev *dev, u32 controller, u32 usr_sfnum)
 {
        struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table;
        u16 hw_fn_id;
                return -EOPNOTSUPP;
 
        mutex_lock(&table->table_lock);
-       sw_id = mlx5_sf_hw_table_id_alloc(table, usr_sfnum);
+       sw_id = mlx5_sf_hw_table_id_alloc(table, controller, usr_sfnum);
        if (sw_id < 0) {
                err = sw_id;
                goto exist_err;
        }
 
-       hw_fn_id = mlx5_sf_sw_to_hw_id(dev, sw_id);
+       hw_fn_id = mlx5_sf_sw_to_hw_id(dev, controller, sw_id);
        err = mlx5_cmd_alloc_sf(dev, hw_fn_id);
        if (err)
                goto err;
        if (err)
                goto vhca_err;
 
+       if (controller) {
+               /* If this SF is for external controller, SF manager
+                * needs to arm firmware to receive the events.
+                */
+               err = mlx5_vhca_event_arm(dev, hw_fn_id);
+               if (err)
+                       goto vhca_err;
+       }
+
        mutex_unlock(&table->table_lock);
        return sw_id;
 
 vhca_err:
        mlx5_cmd_dealloc_sf(dev, hw_fn_id);
 err:
-       mlx5_sf_hw_table_id_free(table, sw_id);
+       mlx5_sf_hw_table_id_free(table, controller, sw_id);
 exist_err:
        mutex_unlock(&table->table_lock);
        return err;
 }
 
-static void _mlx5_sf_hw_table_sf_free(struct mlx5_core_dev *dev, u16 id)
+void mlx5_sf_hw_table_sf_free(struct mlx5_core_dev *dev, u32 controller, u16 id)
 {
        struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table;
        u16 hw_fn_id;
 
-       hw_fn_id = mlx5_sf_sw_to_hw_id(dev, id);
+       mutex_lock(&table->table_lock);
+       hw_fn_id = mlx5_sf_sw_to_hw_id(dev, controller, id);
        mlx5_cmd_dealloc_sf(dev, hw_fn_id);
-       mlx5_sf_hw_table_id_free(table, id);
+       mlx5_sf_hw_table_id_free(table, controller, id);
+       mutex_unlock(&table->table_lock);
 }
 
-void mlx5_sf_hw_table_sf_free(struct mlx5_core_dev *dev, u16 id)
+static void mlx5_sf_hw_table_hwc_sf_free(struct mlx5_core_dev *dev,
+                                        struct mlx5_sf_hwc_table *hwc, int idx)
 {
-       struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table;
-
-       mutex_lock(&table->table_lock);
-       _mlx5_sf_hw_table_sf_free(dev, id);
-       mutex_unlock(&table->table_lock);
+       mlx5_cmd_dealloc_sf(dev, hwc->start_fn_id + idx);
+       hwc->sfs[idx].allocated = false;
+       hwc->sfs[idx].pending_delete = false;
 }
 
-void mlx5_sf_hw_table_sf_deferred_free(struct mlx5_core_dev *dev, u16 id)
+void mlx5_sf_hw_table_sf_deferred_free(struct mlx5_core_dev *dev, u32 controller, u16 id)
 {
        struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table;
        u32 out[MLX5_ST_SZ_DW(query_vhca_state_out)] = {};
        u8 state;
        int err;
 
-       hw_fn_id = mlx5_sf_sw_to_hw_id(dev, id);
-       hwc = &table->hwc[MLX5_SF_HWC_LOCAL];
+       hw_fn_id = mlx5_sf_sw_to_hw_id(dev, controller, id);
+       hwc = mlx5_sf_controller_to_hwc(dev, controller);
        mutex_lock(&table->table_lock);
        err = mlx5_cmd_query_vhca_state(dev, hw_fn_id, out, sizeof(out));
        if (err)
 
        for (i = 0; i < hwc->max_fn; i++) {
                if (hwc->sfs[i].allocated)
-                       _mlx5_sf_hw_table_sf_free(dev, i);
+                       mlx5_sf_hw_table_hwc_sf_free(dev, hwc, i);
        }
 }
 
 static void mlx5_sf_hw_table_dealloc_all(struct mlx5_sf_hw_table *table)
 {
+       mlx5_sf_hw_table_hwc_dealloc_all(table->dev, &table->hwc[MLX5_SF_HWC_EXTERNAL]);
        mlx5_sf_hw_table_hwc_dealloc_all(table->dev, &table->hwc[MLX5_SF_HWC_LOCAL]);
 }
 
 {
        struct mlx5_sf_hw *sfs;
 
+       if (!max_fn)
+               return 0;
+
        sfs = kcalloc(max_fn, sizeof(*sfs), GFP_KERNEL);
        if (!sfs)
                return -ENOMEM;
 int mlx5_sf_hw_table_init(struct mlx5_core_dev *dev)
 {
        struct mlx5_sf_hw_table *table;
+       u16 max_ext_fn = 0;
+       u16 ext_base_id;
+       u16 max_fn = 0;
        u16 base_id;
-       u16 max_fn;
        int err;
 
-       if (!mlx5_sf_supported(dev) || !mlx5_vhca_event_supported(dev))
+       if (!mlx5_vhca_event_supported(dev))
+               return 0;
+
+       if (mlx5_sf_supported(dev))
+               max_fn = mlx5_sf_max_functions(dev);
+
+       err = mlx5_esw_sf_max_hpf_functions(dev, &max_ext_fn, &ext_base_id);
+       if (err)
+               return err;
+
+       if (!max_fn && !max_ext_fn)
                return 0;
 
-       max_fn = mlx5_sf_max_functions(dev);
        table = kzalloc(sizeof(*table), GFP_KERNEL);
        if (!table)
                return -ENOMEM;
        if (err)
                goto table_err;
 
-       mlx5_core_dbg(dev, "SF HW table: max sfs = %d\n", max_fn);
+       err = mlx5_sf_hw_table_hwc_init(&table->hwc[MLX5_SF_HWC_EXTERNAL],
+                                       max_ext_fn, ext_base_id);
+       if (err)
+               goto ext_err;
+
+       mlx5_core_dbg(dev, "SF HW table: max sfs = %d, ext sfs = %d\n", max_fn, max_ext_fn);
        return 0;
 
+ext_err:
+       mlx5_sf_hw_table_hwc_cleanup(&table->hwc[MLX5_SF_HWC_LOCAL]);
 table_err:
        mutex_destroy(&table->table_lock);
        kfree(table);
                return;
 
        mutex_destroy(&table->table_lock);
+       mlx5_sf_hw_table_hwc_cleanup(&table->hwc[MLX5_SF_HWC_EXTERNAL]);
        mlx5_sf_hw_table_hwc_cleanup(&table->hwc[MLX5_SF_HWC_LOCAL]);
        kfree(table);
 }
        if (event->new_vhca_state != MLX5_VHCA_STATE_ALLOCATED)
                return 0;
 
-       hwc = &table->hwc[MLX5_SF_HWC_LOCAL];
-       sw_id = mlx5_sf_hw_to_sw_id(table->dev, event->function_id);
+       hwc = mlx5_sf_table_fn_to_hwc(table, event->function_id);
+       if (!hwc)
+               return 0;
+
+       sw_id = mlx5_sf_hw_to_sw_id(hwc, event->function_id);
        sf_hw = &hwc->sfs[sw_id];
 
        mutex_lock(&table->table_lock);
         * Hence recycle the sf hardware id for reuse.
         */
        if (sf_hw->allocated && sf_hw->pending_delete)
-               _mlx5_sf_hw_table_sf_free(table->dev, sw_id);
+               mlx5_sf_hw_table_hwc_sf_free(table->dev, hwc, sw_id);
        mutex_unlock(&table->table_lock);
        return 0;
 }