linux/drivers/net/ethernet/intel/i40e/i40e_ddp.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/* Copyright(c) 2013 - 2018 Intel Corporation. */
   3
   4#include "i40e.h"
   5
   6#include <linux/firmware.h>
   7
   8/**
   9 * i40e_ddp_profiles_eq - checks if DDP profiles are the equivalent
  10 * @a: new profile info
  11 * @b: old profile info
  12 *
  13 * checks if DDP profiles are the equivalent.
  14 * Returns true if profiles are the same.
  15 **/
  16static bool i40e_ddp_profiles_eq(struct i40e_profile_info *a,
  17                                 struct i40e_profile_info *b)
  18{
  19        return a->track_id == b->track_id &&
  20                !memcmp(&a->version, &b->version, sizeof(a->version)) &&
  21                !memcmp(&a->name, &b->name, I40E_DDP_NAME_SIZE);
  22}
  23
  24/**
  25 * i40e_ddp_does_profile_exist - checks if DDP profile loaded already
  26 * @hw: HW data structure
  27 * @pinfo: DDP profile information structure
  28 *
  29 * checks if DDP profile loaded already.
  30 * Returns >0 if the profile exists.
  31 * Returns  0 if the profile is absent.
  32 * Returns <0 if error.
  33 **/
  34static int i40e_ddp_does_profile_exist(struct i40e_hw *hw,
  35                                       struct i40e_profile_info *pinfo)
  36{
  37        struct i40e_ddp_profile_list *profile_list;
  38        u8 buff[I40E_PROFILE_LIST_SIZE];
  39        i40e_status status;
  40        int i;
  41
  42        status = i40e_aq_get_ddp_list(hw, buff, I40E_PROFILE_LIST_SIZE, 0,
  43                                      NULL);
  44        if (status)
  45                return -1;
  46
  47        profile_list = (struct i40e_ddp_profile_list *)buff;
  48        for (i = 0; i < profile_list->p_count; i++) {
  49                if (i40e_ddp_profiles_eq(pinfo, &profile_list->p_info[i]))
  50                        return 1;
  51        }
  52        return 0;
  53}
  54
  55/**
  56 * i40e_ddp_profiles_overlap - checks if DDP profiles overlap.
  57 * @new: new profile info
  58 * @old: old profile info
  59 *
  60 * checks if DDP profiles overlap.
  61 * Returns true if profiles are overlap.
  62 **/
  63static bool i40e_ddp_profiles_overlap(struct i40e_profile_info *new,
  64                                      struct i40e_profile_info *old)
  65{
  66        unsigned int group_id_old = (u8)((old->track_id & 0x00FF0000) >> 16);
  67        unsigned int group_id_new = (u8)((new->track_id & 0x00FF0000) >> 16);
  68
  69        /* 0x00 group must be only the first */
  70        if (group_id_new == 0)
  71                return true;
  72        /* 0xFF group is compatible with anything else */
  73        if (group_id_new == 0xFF || group_id_old == 0xFF)
  74                return false;
  75        /* otherwise only profiles from the same group are compatible*/
  76        return group_id_old != group_id_new;
  77}
  78
  79/**
  80 * i40e_ddp_does_profile_overlap - checks if DDP overlaps with existing one.
  81 * @hw: HW data structure
  82 * @pinfo: DDP profile information structure
  83 *
  84 * checks if DDP profile overlaps with existing one.
  85 * Returns >0 if the profile overlaps.
  86 * Returns  0 if the profile is ok.
  87 * Returns <0 if error.
  88 **/
  89static int i40e_ddp_does_profile_overlap(struct i40e_hw *hw,
  90                                         struct i40e_profile_info *pinfo)
  91{
  92        struct i40e_ddp_profile_list *profile_list;
  93        u8 buff[I40E_PROFILE_LIST_SIZE];
  94        i40e_status status;
  95        int i;
  96
  97        status = i40e_aq_get_ddp_list(hw, buff, I40E_PROFILE_LIST_SIZE, 0,
  98                                      NULL);
  99        if (status)
 100                return -EIO;
 101
 102        profile_list = (struct i40e_ddp_profile_list *)buff;
 103        for (i = 0; i < profile_list->p_count; i++) {
 104                if (i40e_ddp_profiles_overlap(pinfo,
 105                                              &profile_list->p_info[i]))
 106                        return 1;
 107        }
 108        return 0;
 109}
 110
 111/**
 112 * i40e_add_pinfo
 113 * @hw: pointer to the hardware structure
 114 * @profile: pointer to the profile segment of the package
 115 * @profile_info_sec: buffer for information section
 116 * @track_id: package tracking id
 117 *
 118 * Register a profile to the list of loaded profiles.
 119 */
 120static enum i40e_status_code
 121i40e_add_pinfo(struct i40e_hw *hw, struct i40e_profile_segment *profile,
 122               u8 *profile_info_sec, u32 track_id)
 123{
 124        struct i40e_profile_section_header *sec;
 125        struct i40e_profile_info *pinfo;
 126        i40e_status status;
 127        u32 offset = 0, info = 0;
 128
 129        sec = (struct i40e_profile_section_header *)profile_info_sec;
 130        sec->tbl_size = 1;
 131        sec->data_end = sizeof(struct i40e_profile_section_header) +
 132                        sizeof(struct i40e_profile_info);
 133        sec->section.type = SECTION_TYPE_INFO;
 134        sec->section.offset = sizeof(struct i40e_profile_section_header);
 135        sec->section.size = sizeof(struct i40e_profile_info);
 136        pinfo = (struct i40e_profile_info *)(profile_info_sec +
 137                                             sec->section.offset);
 138        pinfo->track_id = track_id;
 139        pinfo->version = profile->version;
 140        pinfo->op = I40E_DDP_ADD_TRACKID;
 141
 142        /* Clear reserved field */
 143        memset(pinfo->reserved, 0, sizeof(pinfo->reserved));
 144        memcpy(pinfo->name, profile->name, I40E_DDP_NAME_SIZE);
 145
 146        status = i40e_aq_write_ddp(hw, (void *)sec, sec->data_end,
 147                                   track_id, &offset, &info, NULL);
 148        return status;
 149}
 150
 151/**
 152 * i40e_del_pinfo - delete DDP profile info from NIC
 153 * @hw: HW data structure
 154 * @profile: DDP profile segment to be deleted
 155 * @profile_info_sec: DDP profile section header
 156 * @track_id: track ID of the profile for deletion
 157 *
 158 * Removes DDP profile from the NIC.
 159 **/
 160static enum i40e_status_code
 161i40e_del_pinfo(struct i40e_hw *hw, struct i40e_profile_segment *profile,
 162               u8 *profile_info_sec, u32 track_id)
 163{
 164        struct i40e_profile_section_header *sec;
 165        struct i40e_profile_info *pinfo;
 166        i40e_status status;
 167        u32 offset = 0, info = 0;
 168
 169        sec = (struct i40e_profile_section_header *)profile_info_sec;
 170        sec->tbl_size = 1;
 171        sec->data_end = sizeof(struct i40e_profile_section_header) +
 172                        sizeof(struct i40e_profile_info);
 173        sec->section.type = SECTION_TYPE_INFO;
 174        sec->section.offset = sizeof(struct i40e_profile_section_header);
 175        sec->section.size = sizeof(struct i40e_profile_info);
 176        pinfo = (struct i40e_profile_info *)(profile_info_sec +
 177                                             sec->section.offset);
 178        pinfo->track_id = track_id;
 179        pinfo->version = profile->version;
 180        pinfo->op = I40E_DDP_REMOVE_TRACKID;
 181
 182        /* Clear reserved field */
 183        memset(pinfo->reserved, 0, sizeof(pinfo->reserved));
 184        memcpy(pinfo->name, profile->name, I40E_DDP_NAME_SIZE);
 185
 186        status = i40e_aq_write_ddp(hw, (void *)sec, sec->data_end,
 187                                   track_id, &offset, &info, NULL);
 188        return status;
 189}
 190
 191/**
 192 * i40e_ddp_is_pkg_hdr_valid - performs basic pkg header integrity checks
 193 * @netdev: net device structure (for logging purposes)
 194 * @pkg_hdr: pointer to package header
 195 * @size_huge: size of the whole DDP profile package in size_t
 196 *
 197 * Checks correctness of pkg header: Version, size too big/small, and
 198 * all segment offsets alignment and boundaries. This function lets
 199 * reject non DDP profile file to be loaded by administrator mistake.
 200 **/
 201static bool i40e_ddp_is_pkg_hdr_valid(struct net_device *netdev,
 202                                      struct i40e_package_header *pkg_hdr,
 203                                      size_t size_huge)
 204{
 205        u32 size = 0xFFFFFFFFU & size_huge;
 206        u32 pkg_hdr_size;
 207        u32 segment;
 208
 209        if (!pkg_hdr)
 210                return false;
 211
 212        if (pkg_hdr->version.major > 0) {
 213                struct i40e_ddp_version ver = pkg_hdr->version;
 214
 215                netdev_err(netdev, "Unsupported DDP profile version %u.%u.%u.%u",
 216                           ver.major, ver.minor, ver.update, ver.draft);
 217                return false;
 218        }
 219        if (size_huge > size) {
 220                netdev_err(netdev, "Invalid DDP profile - size is bigger than 4G");
 221                return false;
 222        }
 223        if (size < (sizeof(struct i40e_package_header) +
 224                sizeof(struct i40e_metadata_segment) + sizeof(u32) * 2)) {
 225                netdev_err(netdev, "Invalid DDP profile - size is too small.");
 226                return false;
 227        }
 228
 229        pkg_hdr_size = sizeof(u32) * (pkg_hdr->segment_count + 2U);
 230        if (size < pkg_hdr_size) {
 231                netdev_err(netdev, "Invalid DDP profile - too many segments");
 232                return false;
 233        }
 234        for (segment = 0; segment < pkg_hdr->segment_count; ++segment) {
 235                u32 offset = pkg_hdr->segment_offset[segment];
 236
 237                if (0xFU & offset) {
 238                        netdev_err(netdev,
 239                                   "Invalid DDP profile %u segment alignment",
 240                                   segment);
 241                        return false;
 242                }
 243                if (pkg_hdr_size > offset || offset >= size) {
 244                        netdev_err(netdev,
 245                                   "Invalid DDP profile %u segment offset",
 246                                   segment);
 247                        return false;
 248                }
 249        }
 250
 251        return true;
 252}
 253
 254/**
 255 * i40e_ddp_load - performs DDP loading
 256 * @netdev: net device structure
 257 * @data: buffer containing recipe file
 258 * @size: size of the buffer
 259 * @is_add: true when loading profile, false when rolling back the previous one
 260 *
 261 * Checks correctness and loads DDP profile to the NIC. The function is
 262 * also used for rolling back previously loaded profile.
 263 **/
 264int i40e_ddp_load(struct net_device *netdev, const u8 *data, size_t size,
 265                  bool is_add)
 266{
 267        u8 profile_info_sec[sizeof(struct i40e_profile_section_header) +
 268                            sizeof(struct i40e_profile_info)];
 269        struct i40e_metadata_segment *metadata_hdr;
 270        struct i40e_profile_segment *profile_hdr;
 271        struct i40e_profile_info pinfo;
 272        struct i40e_package_header *pkg_hdr;
 273        i40e_status status;
 274        struct i40e_netdev_priv *np = netdev_priv(netdev);
 275        struct i40e_vsi *vsi = np->vsi;
 276        struct i40e_pf *pf = vsi->back;
 277        u32 track_id;
 278        int istatus;
 279
 280        pkg_hdr = (struct i40e_package_header *)data;
 281        if (!i40e_ddp_is_pkg_hdr_valid(netdev, pkg_hdr, size))
 282                return -EINVAL;
 283
 284        if (size < (sizeof(struct i40e_package_header) +
 285                    sizeof(struct i40e_metadata_segment) + sizeof(u32) * 2)) {
 286                netdev_err(netdev, "Invalid DDP recipe size.");
 287                return -EINVAL;
 288        }
 289
 290        /* Find beginning of segment data in buffer */
 291        metadata_hdr = (struct i40e_metadata_segment *)
 292                i40e_find_segment_in_package(SEGMENT_TYPE_METADATA, pkg_hdr);
 293        if (!metadata_hdr) {
 294                netdev_err(netdev, "Failed to find metadata segment in DDP recipe.");
 295                return -EINVAL;
 296        }
 297
 298        track_id = metadata_hdr->track_id;
 299        profile_hdr = (struct i40e_profile_segment *)
 300                i40e_find_segment_in_package(SEGMENT_TYPE_I40E, pkg_hdr);
 301        if (!profile_hdr) {
 302                netdev_err(netdev, "Failed to find profile segment in DDP recipe.");
 303                return -EINVAL;
 304        }
 305
 306        pinfo.track_id = track_id;
 307        pinfo.version = profile_hdr->version;
 308        if (is_add)
 309                pinfo.op = I40E_DDP_ADD_TRACKID;
 310        else
 311                pinfo.op = I40E_DDP_REMOVE_TRACKID;
 312
 313        memcpy(pinfo.name, profile_hdr->name, I40E_DDP_NAME_SIZE);
 314
 315        /* Check if profile data already exists*/
 316        istatus = i40e_ddp_does_profile_exist(&pf->hw, &pinfo);
 317        if (istatus < 0) {
 318                netdev_err(netdev, "Failed to fetch loaded profiles.");
 319                return istatus;
 320        }
 321        if (is_add) {
 322                if (istatus > 0) {
 323                        netdev_err(netdev, "DDP profile already loaded.");
 324                        return -EINVAL;
 325                }
 326                istatus = i40e_ddp_does_profile_overlap(&pf->hw, &pinfo);
 327                if (istatus < 0) {
 328                        netdev_err(netdev, "Failed to fetch loaded profiles.");
 329                        return istatus;
 330                }
 331                if (istatus > 0) {
 332                        netdev_err(netdev, "DDP profile overlaps with existing one.");
 333                        return -EINVAL;
 334                }
 335        } else {
 336                if (istatus == 0) {
 337                        netdev_err(netdev,
 338                                   "DDP profile for deletion does not exist.");
 339                        return -EINVAL;
 340                }
 341        }
 342
 343        /* Load profile data */
 344        if (is_add) {
 345                status = i40e_write_profile(&pf->hw, profile_hdr, track_id);
 346                if (status) {
 347                        if (status == I40E_ERR_DEVICE_NOT_SUPPORTED) {
 348                                netdev_err(netdev,
 349                                           "Profile is not supported by the device.");
 350                                return -EPERM;
 351                        }
 352                        netdev_err(netdev, "Failed to write DDP profile.");
 353                        return -EIO;
 354                }
 355        } else {
 356                status = i40e_rollback_profile(&pf->hw, profile_hdr, track_id);
 357                if (status) {
 358                        netdev_err(netdev, "Failed to remove DDP profile.");
 359                        return -EIO;
 360                }
 361        }
 362
 363        /* Add/remove profile to/from profile list in FW */
 364        if (is_add) {
 365                status = i40e_add_pinfo(&pf->hw, profile_hdr, profile_info_sec,
 366                                        track_id);
 367                if (status) {
 368                        netdev_err(netdev, "Failed to add DDP profile info.");
 369                        return -EIO;
 370                }
 371        } else {
 372                status = i40e_del_pinfo(&pf->hw, profile_hdr, profile_info_sec,
 373                                        track_id);
 374                if (status) {
 375                        netdev_err(netdev, "Failed to restore DDP profile info.");
 376                        return -EIO;
 377                }
 378        }
 379
 380        return 0;
 381}
 382
 383/**
 384 * i40e_ddp_restore - restore previously loaded profile and remove from list
 385 * @pf: PF data struct
 386 *
 387 * Restores previously loaded profile stored on the list in driver memory.
 388 * After rolling back removes entry from the list.
 389 **/
 390static int i40e_ddp_restore(struct i40e_pf *pf)
 391{
 392        struct i40e_ddp_old_profile_list *entry;
 393        struct net_device *netdev = pf->vsi[pf->lan_vsi]->netdev;
 394        int status = 0;
 395
 396        if (!list_empty(&pf->ddp_old_prof)) {
 397                entry = list_first_entry(&pf->ddp_old_prof,
 398                                         struct i40e_ddp_old_profile_list,
 399                                         list);
 400                status = i40e_ddp_load(netdev, entry->old_ddp_buf,
 401                                       entry->old_ddp_size, false);
 402                list_del(&entry->list);
 403                kfree(entry);
 404        }
 405        return status;
 406}
 407
 408/**
 409 * i40e_ddp_flash - callback function for ethtool flash feature
 410 * @netdev: net device structure
 411 * @flash: kernel flash structure
 412 *
 413 * Ethtool callback function used for loading and unloading DDP profiles.
 414 **/
 415int i40e_ddp_flash(struct net_device *netdev, struct ethtool_flash *flash)
 416{
 417        const struct firmware *ddp_config;
 418        struct i40e_netdev_priv *np = netdev_priv(netdev);
 419        struct i40e_vsi *vsi = np->vsi;
 420        struct i40e_pf *pf = vsi->back;
 421        int status = 0;
 422
 423        /* Check for valid region first */
 424        if (flash->region != I40_DDP_FLASH_REGION) {
 425                netdev_err(netdev, "Requested firmware region is not recognized by this driver.");
 426                return -EINVAL;
 427        }
 428        if (pf->hw.bus.func != 0) {
 429                netdev_err(netdev, "Any DDP operation is allowed only on Phy0 NIC interface");
 430                return -EINVAL;
 431        }
 432
 433        /* If the user supplied "-" instead of file name rollback previously
 434         * stored profile.
 435         */
 436        if (strncmp(flash->data, "-", 2) != 0) {
 437                struct i40e_ddp_old_profile_list *list_entry;
 438                char profile_name[sizeof(I40E_DDP_PROFILE_PATH)
 439                                  + I40E_DDP_PROFILE_NAME_MAX];
 440
 441                profile_name[sizeof(profile_name) - 1] = 0;
 442                strncpy(profile_name, I40E_DDP_PROFILE_PATH,
 443                        sizeof(profile_name) - 1);
 444                strncat(profile_name, flash->data, I40E_DDP_PROFILE_NAME_MAX);
 445                /* Load DDP recipe. */
 446                status = request_firmware(&ddp_config, profile_name,
 447                                          &netdev->dev);
 448                if (status) {
 449                        netdev_err(netdev, "DDP recipe file request failed.");
 450                        return status;
 451                }
 452
 453                status = i40e_ddp_load(netdev, ddp_config->data,
 454                                       ddp_config->size, true);
 455
 456                if (!status) {
 457                        list_entry =
 458                          kzalloc(sizeof(struct i40e_ddp_old_profile_list) +
 459                                  ddp_config->size, GFP_KERNEL);
 460                        if (!list_entry) {
 461                                netdev_info(netdev, "Failed to allocate memory for previous DDP profile data.");
 462                                netdev_info(netdev, "New profile loaded but roll-back will be impossible.");
 463                        } else {
 464                                memcpy(list_entry->old_ddp_buf,
 465                                       ddp_config->data, ddp_config->size);
 466                                list_entry->old_ddp_size = ddp_config->size;
 467                                list_add(&list_entry->list, &pf->ddp_old_prof);
 468                        }
 469                }
 470
 471                release_firmware(ddp_config);
 472        } else {
 473                if (!list_empty(&pf->ddp_old_prof)) {
 474                        status = i40e_ddp_restore(pf);
 475                } else {
 476                        netdev_warn(netdev, "There is no DDP profile to restore.");
 477                        status = -ENOENT;
 478                }
 479        }
 480        return status;
 481}
 482