linux/drivers/net/ethernet/mellanox/mlx5/core/sf/devlink.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
   2/* Copyright (c) 2020 Mellanox Technologies Ltd */
   3
   4#include <linux/mlx5/driver.h>
   5#include "eswitch.h"
   6#include "priv.h"
   7#include "sf/dev/dev.h"
   8#include "mlx5_ifc_vhca_event.h"
   9#include "vhca_event.h"
  10#include "ecpf.h"
  11
  12struct mlx5_sf {
  13        struct devlink_port dl_port;
  14        unsigned int port_index;
  15        u32 controller;
  16        u16 id;
  17        u16 hw_fn_id;
  18        u16 hw_state;
  19};
  20
  21struct mlx5_sf_table {
  22        struct mlx5_core_dev *dev; /* To refer from notifier context. */
  23        struct xarray port_indices; /* port index based lookup. */
  24        refcount_t refcount;
  25        struct completion disable_complete;
  26        struct mutex sf_state_lock; /* Serializes sf state among user cmds & vhca event handler. */
  27        struct notifier_block esw_nb;
  28        struct notifier_block vhca_nb;
  29        u8 ecpu: 1;
  30};
  31
  32static struct mlx5_sf *
  33mlx5_sf_lookup_by_index(struct mlx5_sf_table *table, unsigned int port_index)
  34{
  35        return xa_load(&table->port_indices, port_index);
  36}
  37
  38static struct mlx5_sf *
  39mlx5_sf_lookup_by_function_id(struct mlx5_sf_table *table, unsigned int fn_id)
  40{
  41        unsigned long index;
  42        struct mlx5_sf *sf;
  43
  44        xa_for_each(&table->port_indices, index, sf) {
  45                if (sf->hw_fn_id == fn_id)
  46                        return sf;
  47        }
  48        return NULL;
  49}
  50
  51static int mlx5_sf_id_insert(struct mlx5_sf_table *table, struct mlx5_sf *sf)
  52{
  53        return xa_insert(&table->port_indices, sf->port_index, sf, GFP_KERNEL);
  54}
  55
  56static void mlx5_sf_id_erase(struct mlx5_sf_table *table, struct mlx5_sf *sf)
  57{
  58        xa_erase(&table->port_indices, sf->port_index);
  59}
  60
  61static struct mlx5_sf *
  62mlx5_sf_alloc(struct mlx5_sf_table *table, struct mlx5_eswitch *esw,
  63              u32 controller, u32 sfnum, struct netlink_ext_ack *extack)
  64{
  65        unsigned int dl_port_index;
  66        struct mlx5_sf *sf;
  67        u16 hw_fn_id;
  68        int id_err;
  69        int err;
  70
  71        if (!mlx5_esw_offloads_controller_valid(esw, controller)) {
  72                NL_SET_ERR_MSG_MOD(extack, "Invalid controller number");
  73                return ERR_PTR(-EINVAL);
  74        }
  75
  76        id_err = mlx5_sf_hw_table_sf_alloc(table->dev, controller, sfnum);
  77        if (id_err < 0) {
  78                err = id_err;
  79                goto id_err;
  80        }
  81
  82        sf = kzalloc(sizeof(*sf), GFP_KERNEL);
  83        if (!sf) {
  84                err = -ENOMEM;
  85                goto alloc_err;
  86        }
  87        sf->id = id_err;
  88        hw_fn_id = mlx5_sf_sw_to_hw_id(table->dev, controller, sf->id);
  89        dl_port_index = mlx5_esw_vport_to_devlink_port_index(table->dev, hw_fn_id);
  90        sf->port_index = dl_port_index;
  91        sf->hw_fn_id = hw_fn_id;
  92        sf->hw_state = MLX5_VHCA_STATE_ALLOCATED;
  93        sf->controller = controller;
  94
  95        err = mlx5_sf_id_insert(table, sf);
  96        if (err)
  97                goto insert_err;
  98
  99        return sf;
 100
 101insert_err:
 102        kfree(sf);
 103alloc_err:
 104        mlx5_sf_hw_table_sf_free(table->dev, controller, id_err);
 105id_err:
 106        if (err == -EEXIST)
 107                NL_SET_ERR_MSG_MOD(extack, "SF already exist. Choose different sfnum");
 108        return ERR_PTR(err);
 109}
 110
 111static void mlx5_sf_free(struct mlx5_sf_table *table, struct mlx5_sf *sf)
 112{
 113        mlx5_sf_id_erase(table, sf);
 114        mlx5_sf_hw_table_sf_free(table->dev, sf->controller, sf->id);
 115        kfree(sf);
 116}
 117
 118static struct mlx5_sf_table *mlx5_sf_table_try_get(struct mlx5_core_dev *dev)
 119{
 120        struct mlx5_sf_table *table = dev->priv.sf_table;
 121
 122        if (!table)
 123                return NULL;
 124
 125        return refcount_inc_not_zero(&table->refcount) ? table : NULL;
 126}
 127
 128static void mlx5_sf_table_put(struct mlx5_sf_table *table)
 129{
 130        if (refcount_dec_and_test(&table->refcount))
 131                complete(&table->disable_complete);
 132}
 133
 134static enum devlink_port_fn_state mlx5_sf_to_devlink_state(u8 hw_state)
 135{
 136        switch (hw_state) {
 137        case MLX5_VHCA_STATE_ACTIVE:
 138        case MLX5_VHCA_STATE_IN_USE:
 139                return DEVLINK_PORT_FN_STATE_ACTIVE;
 140        case MLX5_VHCA_STATE_INVALID:
 141        case MLX5_VHCA_STATE_ALLOCATED:
 142        case MLX5_VHCA_STATE_TEARDOWN_REQUEST:
 143        default:
 144                return DEVLINK_PORT_FN_STATE_INACTIVE;
 145        }
 146}
 147
 148static enum devlink_port_fn_opstate mlx5_sf_to_devlink_opstate(u8 hw_state)
 149{
 150        switch (hw_state) {
 151        case MLX5_VHCA_STATE_IN_USE:
 152        case MLX5_VHCA_STATE_TEARDOWN_REQUEST:
 153                return DEVLINK_PORT_FN_OPSTATE_ATTACHED;
 154        case MLX5_VHCA_STATE_INVALID:
 155        case MLX5_VHCA_STATE_ALLOCATED:
 156        case MLX5_VHCA_STATE_ACTIVE:
 157        default:
 158                return DEVLINK_PORT_FN_OPSTATE_DETACHED;
 159        }
 160}
 161
 162static bool mlx5_sf_is_active(const struct mlx5_sf *sf)
 163{
 164        return sf->hw_state == MLX5_VHCA_STATE_ACTIVE || sf->hw_state == MLX5_VHCA_STATE_IN_USE;
 165}
 166
 167int mlx5_devlink_sf_port_fn_state_get(struct devlink_port *dl_port,
 168                                      enum devlink_port_fn_state *state,
 169                                      enum devlink_port_fn_opstate *opstate,
 170                                      struct netlink_ext_ack *extack)
 171{
 172        struct mlx5_core_dev *dev = devlink_priv(dl_port->devlink);
 173        struct mlx5_sf_table *table;
 174        struct mlx5_sf *sf;
 175        int err = 0;
 176
 177        table = mlx5_sf_table_try_get(dev);
 178        if (!table)
 179                return -EOPNOTSUPP;
 180
 181        sf = mlx5_sf_lookup_by_index(table, dl_port->index);
 182        if (!sf) {
 183                err = -EOPNOTSUPP;
 184                goto sf_err;
 185        }
 186        mutex_lock(&table->sf_state_lock);
 187        *state = mlx5_sf_to_devlink_state(sf->hw_state);
 188        *opstate = mlx5_sf_to_devlink_opstate(sf->hw_state);
 189        mutex_unlock(&table->sf_state_lock);
 190sf_err:
 191        mlx5_sf_table_put(table);
 192        return err;
 193}
 194
 195static int mlx5_sf_activate(struct mlx5_core_dev *dev, struct mlx5_sf *sf,
 196                            struct netlink_ext_ack *extack)
 197{
 198        int err;
 199
 200        if (mlx5_sf_is_active(sf))
 201                return 0;
 202        if (sf->hw_state != MLX5_VHCA_STATE_ALLOCATED) {
 203                NL_SET_ERR_MSG_MOD(extack, "SF is inactivated but it is still attached");
 204                return -EBUSY;
 205        }
 206
 207        err = mlx5_cmd_sf_enable_hca(dev, sf->hw_fn_id);
 208        if (err)
 209                return err;
 210
 211        sf->hw_state = MLX5_VHCA_STATE_ACTIVE;
 212        return 0;
 213}
 214
 215static int mlx5_sf_deactivate(struct mlx5_core_dev *dev, struct mlx5_sf *sf)
 216{
 217        int err;
 218
 219        if (!mlx5_sf_is_active(sf))
 220                return 0;
 221
 222        err = mlx5_cmd_sf_disable_hca(dev, sf->hw_fn_id);
 223        if (err)
 224                return err;
 225
 226        sf->hw_state = MLX5_VHCA_STATE_TEARDOWN_REQUEST;
 227        return 0;
 228}
 229
 230static int mlx5_sf_state_set(struct mlx5_core_dev *dev, struct mlx5_sf_table *table,
 231                             struct mlx5_sf *sf,
 232                             enum devlink_port_fn_state state,
 233                             struct netlink_ext_ack *extack)
 234{
 235        int err = 0;
 236
 237        mutex_lock(&table->sf_state_lock);
 238        if (state == mlx5_sf_to_devlink_state(sf->hw_state))
 239                goto out;
 240        if (state == DEVLINK_PORT_FN_STATE_ACTIVE)
 241                err = mlx5_sf_activate(dev, sf, extack);
 242        else if (state == DEVLINK_PORT_FN_STATE_INACTIVE)
 243                err = mlx5_sf_deactivate(dev, sf);
 244        else
 245                err = -EINVAL;
 246out:
 247        mutex_unlock(&table->sf_state_lock);
 248        return err;
 249}
 250
 251int mlx5_devlink_sf_port_fn_state_set(struct devlink_port *dl_port,
 252                                      enum devlink_port_fn_state state,
 253                                      struct netlink_ext_ack *extack)
 254{
 255        struct mlx5_core_dev *dev = devlink_priv(dl_port->devlink);
 256        struct mlx5_sf_table *table;
 257        struct mlx5_sf *sf;
 258        int err;
 259
 260        table = mlx5_sf_table_try_get(dev);
 261        if (!table) {
 262                NL_SET_ERR_MSG_MOD(extack,
 263                                   "Port state set is only supported in eswitch switchdev mode or SF ports are disabled.");
 264                return -EOPNOTSUPP;
 265        }
 266        sf = mlx5_sf_lookup_by_index(table, dl_port->index);
 267        if (!sf) {
 268                err = -ENODEV;
 269                goto out;
 270        }
 271
 272        err = mlx5_sf_state_set(dev, table, sf, state, extack);
 273out:
 274        mlx5_sf_table_put(table);
 275        return err;
 276}
 277
 278static int mlx5_sf_add(struct mlx5_core_dev *dev, struct mlx5_sf_table *table,
 279                       const struct devlink_port_new_attrs *new_attr,
 280                       struct netlink_ext_ack *extack,
 281                       unsigned int *new_port_index)
 282{
 283        struct mlx5_eswitch *esw = dev->priv.eswitch;
 284        struct mlx5_sf *sf;
 285        int err;
 286
 287        sf = mlx5_sf_alloc(table, esw, new_attr->controller, new_attr->sfnum, extack);
 288        if (IS_ERR(sf))
 289                return PTR_ERR(sf);
 290
 291        err = mlx5_esw_offloads_sf_vport_enable(esw, &sf->dl_port, sf->hw_fn_id,
 292                                                new_attr->controller, new_attr->sfnum);
 293        if (err)
 294                goto esw_err;
 295        *new_port_index = sf->port_index;
 296        return 0;
 297
 298esw_err:
 299        mlx5_sf_free(table, sf);
 300        return err;
 301}
 302
 303static int
 304mlx5_sf_new_check_attr(struct mlx5_core_dev *dev, const struct devlink_port_new_attrs *new_attr,
 305                       struct netlink_ext_ack *extack)
 306{
 307        if (new_attr->flavour != DEVLINK_PORT_FLAVOUR_PCI_SF) {
 308                NL_SET_ERR_MSG_MOD(extack, "Driver supports only SF port addition");
 309                return -EOPNOTSUPP;
 310        }
 311        if (new_attr->port_index_valid) {
 312                NL_SET_ERR_MSG_MOD(extack,
 313                                   "Driver does not support user defined port index assignment");
 314                return -EOPNOTSUPP;
 315        }
 316        if (!new_attr->sfnum_valid) {
 317                NL_SET_ERR_MSG_MOD(extack,
 318                                   "User must provide unique sfnum. Driver does not support auto assignment");
 319                return -EOPNOTSUPP;
 320        }
 321        if (new_attr->controller_valid && new_attr->controller &&
 322            !mlx5_core_is_ecpf_esw_manager(dev)) {
 323                NL_SET_ERR_MSG_MOD(extack, "External controller is unsupported");
 324                return -EOPNOTSUPP;
 325        }
 326        if (new_attr->pfnum != PCI_FUNC(dev->pdev->devfn)) {
 327                NL_SET_ERR_MSG_MOD(extack, "Invalid pfnum supplied");
 328                return -EOPNOTSUPP;
 329        }
 330        return 0;
 331}
 332
 333int mlx5_devlink_sf_port_new(struct devlink *devlink,
 334                             const struct devlink_port_new_attrs *new_attr,
 335                             struct netlink_ext_ack *extack,
 336                             unsigned int *new_port_index)
 337{
 338        struct mlx5_core_dev *dev = devlink_priv(devlink);
 339        struct mlx5_sf_table *table;
 340        int err;
 341
 342        err = mlx5_sf_new_check_attr(dev, new_attr, extack);
 343        if (err)
 344                return err;
 345
 346        table = mlx5_sf_table_try_get(dev);
 347        if (!table) {
 348                NL_SET_ERR_MSG_MOD(extack,
 349                                   "Port add is only supported in eswitch switchdev mode or SF ports are disabled.");
 350                return -EOPNOTSUPP;
 351        }
 352        err = mlx5_sf_add(dev, table, new_attr, extack, new_port_index);
 353        mlx5_sf_table_put(table);
 354        return err;
 355}
 356
 357static void mlx5_sf_dealloc(struct mlx5_sf_table *table, struct mlx5_sf *sf)
 358{
 359        if (sf->hw_state == MLX5_VHCA_STATE_ALLOCATED) {
 360                mlx5_sf_free(table, sf);
 361        } else if (mlx5_sf_is_active(sf)) {
 362                /* Even if its active, it is treated as in_use because by the time,
 363                 * it is disabled here, it may getting used. So it is safe to
 364                 * always look for the event to ensure that it is recycled only after
 365                 * firmware gives confirmation that it is detached by the driver.
 366                 */
 367                mlx5_cmd_sf_disable_hca(table->dev, sf->hw_fn_id);
 368                mlx5_sf_hw_table_sf_deferred_free(table->dev, sf->controller, sf->id);
 369                kfree(sf);
 370        } else {
 371                mlx5_sf_hw_table_sf_deferred_free(table->dev, sf->controller, sf->id);
 372                kfree(sf);
 373        }
 374}
 375
 376int mlx5_devlink_sf_port_del(struct devlink *devlink, unsigned int port_index,
 377                             struct netlink_ext_ack *extack)
 378{
 379        struct mlx5_core_dev *dev = devlink_priv(devlink);
 380        struct mlx5_eswitch *esw = dev->priv.eswitch;
 381        struct mlx5_sf_table *table;
 382        struct mlx5_sf *sf;
 383        int err = 0;
 384
 385        table = mlx5_sf_table_try_get(dev);
 386        if (!table) {
 387                NL_SET_ERR_MSG_MOD(extack,
 388                                   "Port del is only supported in eswitch switchdev mode or SF ports are disabled.");
 389                return -EOPNOTSUPP;
 390        }
 391        sf = mlx5_sf_lookup_by_index(table, port_index);
 392        if (!sf) {
 393                err = -ENODEV;
 394                goto sf_err;
 395        }
 396
 397        mlx5_esw_offloads_sf_vport_disable(esw, sf->hw_fn_id);
 398        mlx5_sf_id_erase(table, sf);
 399
 400        mutex_lock(&table->sf_state_lock);
 401        mlx5_sf_dealloc(table, sf);
 402        mutex_unlock(&table->sf_state_lock);
 403sf_err:
 404        mlx5_sf_table_put(table);
 405        return err;
 406}
 407
 408static bool mlx5_sf_state_update_check(const struct mlx5_sf *sf, u8 new_state)
 409{
 410        if (sf->hw_state == MLX5_VHCA_STATE_ACTIVE && new_state == MLX5_VHCA_STATE_IN_USE)
 411                return true;
 412
 413        if (sf->hw_state == MLX5_VHCA_STATE_IN_USE && new_state == MLX5_VHCA_STATE_ACTIVE)
 414                return true;
 415
 416        if (sf->hw_state == MLX5_VHCA_STATE_TEARDOWN_REQUEST &&
 417            new_state == MLX5_VHCA_STATE_ALLOCATED)
 418                return true;
 419
 420        return false;
 421}
 422
 423static int mlx5_sf_vhca_event(struct notifier_block *nb, unsigned long opcode, void *data)
 424{
 425        struct mlx5_sf_table *table = container_of(nb, struct mlx5_sf_table, vhca_nb);
 426        const struct mlx5_vhca_state_event *event = data;
 427        bool update = false;
 428        struct mlx5_sf *sf;
 429
 430        table = mlx5_sf_table_try_get(table->dev);
 431        if (!table)
 432                return 0;
 433
 434        mutex_lock(&table->sf_state_lock);
 435        sf = mlx5_sf_lookup_by_function_id(table, event->function_id);
 436        if (!sf)
 437                goto sf_err;
 438
 439        /* When driver is attached or detached to a function, an event
 440         * notifies such state change.
 441         */
 442        update = mlx5_sf_state_update_check(sf, event->new_vhca_state);
 443        if (update)
 444                sf->hw_state = event->new_vhca_state;
 445sf_err:
 446        mutex_unlock(&table->sf_state_lock);
 447        mlx5_sf_table_put(table);
 448        return 0;
 449}
 450
 451static void mlx5_sf_table_enable(struct mlx5_sf_table *table)
 452{
 453        init_completion(&table->disable_complete);
 454        refcount_set(&table->refcount, 1);
 455}
 456
 457static void mlx5_sf_deactivate_all(struct mlx5_sf_table *table)
 458{
 459        struct mlx5_eswitch *esw = table->dev->priv.eswitch;
 460        unsigned long index;
 461        struct mlx5_sf *sf;
 462
 463        /* At this point, no new user commands can start and no vhca event can
 464         * arrive. It is safe to destroy all user created SFs.
 465         */
 466        xa_for_each(&table->port_indices, index, sf) {
 467                mlx5_esw_offloads_sf_vport_disable(esw, sf->hw_fn_id);
 468                mlx5_sf_id_erase(table, sf);
 469                mlx5_sf_dealloc(table, sf);
 470        }
 471}
 472
 473static void mlx5_sf_table_disable(struct mlx5_sf_table *table)
 474{
 475        if (!refcount_read(&table->refcount))
 476                return;
 477
 478        /* Balances with refcount_set; drop the reference so that new user cmd cannot start
 479         * and new vhca event handler cannot run.
 480         */
 481        mlx5_sf_table_put(table);
 482        wait_for_completion(&table->disable_complete);
 483
 484        mlx5_sf_deactivate_all(table);
 485}
 486
 487static int mlx5_sf_esw_event(struct notifier_block *nb, unsigned long event, void *data)
 488{
 489        struct mlx5_sf_table *table = container_of(nb, struct mlx5_sf_table, esw_nb);
 490        const struct mlx5_esw_event_info *mode = data;
 491
 492        switch (mode->new_mode) {
 493        case MLX5_ESWITCH_OFFLOADS:
 494                mlx5_sf_table_enable(table);
 495                break;
 496        case MLX5_ESWITCH_NONE:
 497                mlx5_sf_table_disable(table);
 498                break;
 499        default:
 500                break;
 501        }
 502
 503        return 0;
 504}
 505
 506static bool mlx5_sf_table_supported(const struct mlx5_core_dev *dev)
 507{
 508        return dev->priv.eswitch && MLX5_ESWITCH_MANAGER(dev) &&
 509               mlx5_sf_hw_table_supported(dev);
 510}
 511
 512int mlx5_sf_table_init(struct mlx5_core_dev *dev)
 513{
 514        struct mlx5_sf_table *table;
 515        int err;
 516
 517        if (!mlx5_sf_table_supported(dev) || !mlx5_vhca_event_supported(dev))
 518                return 0;
 519
 520        table = kzalloc(sizeof(*table), GFP_KERNEL);
 521        if (!table)
 522                return -ENOMEM;
 523
 524        mutex_init(&table->sf_state_lock);
 525        table->dev = dev;
 526        xa_init(&table->port_indices);
 527        dev->priv.sf_table = table;
 528        refcount_set(&table->refcount, 0);
 529        table->esw_nb.notifier_call = mlx5_sf_esw_event;
 530        err = mlx5_esw_event_notifier_register(dev->priv.eswitch, &table->esw_nb);
 531        if (err)
 532                goto reg_err;
 533
 534        table->vhca_nb.notifier_call = mlx5_sf_vhca_event;
 535        err = mlx5_vhca_event_notifier_register(table->dev, &table->vhca_nb);
 536        if (err)
 537                goto vhca_err;
 538
 539        return 0;
 540
 541vhca_err:
 542        mlx5_esw_event_notifier_unregister(dev->priv.eswitch, &table->esw_nb);
 543reg_err:
 544        mutex_destroy(&table->sf_state_lock);
 545        kfree(table);
 546        dev->priv.sf_table = NULL;
 547        return err;
 548}
 549
 550void mlx5_sf_table_cleanup(struct mlx5_core_dev *dev)
 551{
 552        struct mlx5_sf_table *table = dev->priv.sf_table;
 553
 554        if (!table)
 555                return;
 556
 557        mlx5_vhca_event_notifier_unregister(table->dev, &table->vhca_nb);
 558        mlx5_esw_event_notifier_unregister(dev->priv.eswitch, &table->esw_nb);
 559        WARN_ON(refcount_read(&table->refcount));
 560        mutex_destroy(&table->sf_state_lock);
 561        WARN_ON(!xa_empty(&table->port_indices));
 562        kfree(table);
 563}
 564