162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* Copyright(c) 2013 - 2018 Intel Corporation. */ 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci#include "i40e.h" 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/firmware.h> 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci/** 962306a36Sopenharmony_ci * i40e_ddp_profiles_eq - checks if DDP profiles are the equivalent 1062306a36Sopenharmony_ci * @a: new profile info 1162306a36Sopenharmony_ci * @b: old profile info 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * checks if DDP profiles are the equivalent. 1462306a36Sopenharmony_ci * Returns true if profiles are the same. 1562306a36Sopenharmony_ci **/ 1662306a36Sopenharmony_cistatic bool i40e_ddp_profiles_eq(struct i40e_profile_info *a, 1762306a36Sopenharmony_ci struct i40e_profile_info *b) 1862306a36Sopenharmony_ci{ 1962306a36Sopenharmony_ci return a->track_id == b->track_id && 2062306a36Sopenharmony_ci !memcmp(&a->version, &b->version, sizeof(a->version)) && 2162306a36Sopenharmony_ci !memcmp(&a->name, &b->name, I40E_DDP_NAME_SIZE); 2262306a36Sopenharmony_ci} 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci/** 2562306a36Sopenharmony_ci * i40e_ddp_does_profile_exist - checks if DDP profile loaded already 2662306a36Sopenharmony_ci * @hw: HW data structure 2762306a36Sopenharmony_ci * @pinfo: DDP profile information structure 2862306a36Sopenharmony_ci * 2962306a36Sopenharmony_ci * checks if DDP profile loaded already. 3062306a36Sopenharmony_ci * Returns >0 if the profile exists. 3162306a36Sopenharmony_ci * Returns 0 if the profile is absent. 3262306a36Sopenharmony_ci * Returns <0 if error. 3362306a36Sopenharmony_ci **/ 3462306a36Sopenharmony_cistatic int i40e_ddp_does_profile_exist(struct i40e_hw *hw, 3562306a36Sopenharmony_ci struct i40e_profile_info *pinfo) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci struct i40e_ddp_profile_list *profile_list; 3862306a36Sopenharmony_ci u8 buff[I40E_PROFILE_LIST_SIZE]; 3962306a36Sopenharmony_ci int status; 4062306a36Sopenharmony_ci int i; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci status = i40e_aq_get_ddp_list(hw, buff, I40E_PROFILE_LIST_SIZE, 0, 4362306a36Sopenharmony_ci NULL); 4462306a36Sopenharmony_ci if (status) 4562306a36Sopenharmony_ci return -1; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci profile_list = (struct i40e_ddp_profile_list *)buff; 4862306a36Sopenharmony_ci for (i = 0; i < profile_list->p_count; i++) { 4962306a36Sopenharmony_ci if (i40e_ddp_profiles_eq(pinfo, &profile_list->p_info[i])) 5062306a36Sopenharmony_ci return 1; 5162306a36Sopenharmony_ci } 5262306a36Sopenharmony_ci return 0; 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci/** 5662306a36Sopenharmony_ci * i40e_ddp_profiles_overlap - checks if DDP profiles overlap. 5762306a36Sopenharmony_ci * @new: new profile info 5862306a36Sopenharmony_ci * @old: old profile info 5962306a36Sopenharmony_ci * 6062306a36Sopenharmony_ci * checks if DDP profiles overlap. 6162306a36Sopenharmony_ci * Returns true if profiles are overlap. 6262306a36Sopenharmony_ci **/ 6362306a36Sopenharmony_cistatic bool i40e_ddp_profiles_overlap(struct i40e_profile_info *new, 6462306a36Sopenharmony_ci struct i40e_profile_info *old) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci unsigned int group_id_old = (u8)((old->track_id & 0x00FF0000) >> 16); 6762306a36Sopenharmony_ci unsigned int group_id_new = (u8)((new->track_id & 0x00FF0000) >> 16); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci /* 0x00 group must be only the first */ 7062306a36Sopenharmony_ci if (group_id_new == 0) 7162306a36Sopenharmony_ci return true; 7262306a36Sopenharmony_ci /* 0xFF group is compatible with anything else */ 7362306a36Sopenharmony_ci if (group_id_new == 0xFF || group_id_old == 0xFF) 7462306a36Sopenharmony_ci return false; 7562306a36Sopenharmony_ci /* otherwise only profiles from the same group are compatible*/ 7662306a36Sopenharmony_ci return group_id_old != group_id_new; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci/** 8062306a36Sopenharmony_ci * i40e_ddp_does_profile_overlap - checks if DDP overlaps with existing one. 8162306a36Sopenharmony_ci * @hw: HW data structure 8262306a36Sopenharmony_ci * @pinfo: DDP profile information structure 8362306a36Sopenharmony_ci * 8462306a36Sopenharmony_ci * checks if DDP profile overlaps with existing one. 8562306a36Sopenharmony_ci * Returns >0 if the profile overlaps. 8662306a36Sopenharmony_ci * Returns 0 if the profile is ok. 8762306a36Sopenharmony_ci * Returns <0 if error. 8862306a36Sopenharmony_ci **/ 8962306a36Sopenharmony_cistatic int i40e_ddp_does_profile_overlap(struct i40e_hw *hw, 9062306a36Sopenharmony_ci struct i40e_profile_info *pinfo) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci struct i40e_ddp_profile_list *profile_list; 9362306a36Sopenharmony_ci u8 buff[I40E_PROFILE_LIST_SIZE]; 9462306a36Sopenharmony_ci int status; 9562306a36Sopenharmony_ci int i; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci status = i40e_aq_get_ddp_list(hw, buff, I40E_PROFILE_LIST_SIZE, 0, 9862306a36Sopenharmony_ci NULL); 9962306a36Sopenharmony_ci if (status) 10062306a36Sopenharmony_ci return -EIO; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci profile_list = (struct i40e_ddp_profile_list *)buff; 10362306a36Sopenharmony_ci for (i = 0; i < profile_list->p_count; i++) { 10462306a36Sopenharmony_ci if (i40e_ddp_profiles_overlap(pinfo, 10562306a36Sopenharmony_ci &profile_list->p_info[i])) 10662306a36Sopenharmony_ci return 1; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci return 0; 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci/** 11262306a36Sopenharmony_ci * i40e_add_pinfo 11362306a36Sopenharmony_ci * @hw: pointer to the hardware structure 11462306a36Sopenharmony_ci * @profile: pointer to the profile segment of the package 11562306a36Sopenharmony_ci * @profile_info_sec: buffer for information section 11662306a36Sopenharmony_ci * @track_id: package tracking id 11762306a36Sopenharmony_ci * 11862306a36Sopenharmony_ci * Register a profile to the list of loaded profiles. 11962306a36Sopenharmony_ci */ 12062306a36Sopenharmony_cistatic int 12162306a36Sopenharmony_cii40e_add_pinfo(struct i40e_hw *hw, struct i40e_profile_segment *profile, 12262306a36Sopenharmony_ci u8 *profile_info_sec, u32 track_id) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci struct i40e_profile_section_header *sec; 12562306a36Sopenharmony_ci struct i40e_profile_info *pinfo; 12662306a36Sopenharmony_ci u32 offset = 0, info = 0; 12762306a36Sopenharmony_ci int status; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci sec = (struct i40e_profile_section_header *)profile_info_sec; 13062306a36Sopenharmony_ci sec->tbl_size = 1; 13162306a36Sopenharmony_ci sec->data_end = sizeof(struct i40e_profile_section_header) + 13262306a36Sopenharmony_ci sizeof(struct i40e_profile_info); 13362306a36Sopenharmony_ci sec->section.type = SECTION_TYPE_INFO; 13462306a36Sopenharmony_ci sec->section.offset = sizeof(struct i40e_profile_section_header); 13562306a36Sopenharmony_ci sec->section.size = sizeof(struct i40e_profile_info); 13662306a36Sopenharmony_ci pinfo = (struct i40e_profile_info *)(profile_info_sec + 13762306a36Sopenharmony_ci sec->section.offset); 13862306a36Sopenharmony_ci pinfo->track_id = track_id; 13962306a36Sopenharmony_ci pinfo->version = profile->version; 14062306a36Sopenharmony_ci pinfo->op = I40E_DDP_ADD_TRACKID; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci /* Clear reserved field */ 14362306a36Sopenharmony_ci memset(pinfo->reserved, 0, sizeof(pinfo->reserved)); 14462306a36Sopenharmony_ci memcpy(pinfo->name, profile->name, I40E_DDP_NAME_SIZE); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci status = i40e_aq_write_ddp(hw, (void *)sec, sec->data_end, 14762306a36Sopenharmony_ci track_id, &offset, &info, NULL); 14862306a36Sopenharmony_ci return status; 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci/** 15262306a36Sopenharmony_ci * i40e_del_pinfo - delete DDP profile info from NIC 15362306a36Sopenharmony_ci * @hw: HW data structure 15462306a36Sopenharmony_ci * @profile: DDP profile segment to be deleted 15562306a36Sopenharmony_ci * @profile_info_sec: DDP profile section header 15662306a36Sopenharmony_ci * @track_id: track ID of the profile for deletion 15762306a36Sopenharmony_ci * 15862306a36Sopenharmony_ci * Removes DDP profile from the NIC. 15962306a36Sopenharmony_ci **/ 16062306a36Sopenharmony_cistatic int 16162306a36Sopenharmony_cii40e_del_pinfo(struct i40e_hw *hw, struct i40e_profile_segment *profile, 16262306a36Sopenharmony_ci u8 *profile_info_sec, u32 track_id) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci struct i40e_profile_section_header *sec; 16562306a36Sopenharmony_ci struct i40e_profile_info *pinfo; 16662306a36Sopenharmony_ci u32 offset = 0, info = 0; 16762306a36Sopenharmony_ci int status; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci sec = (struct i40e_profile_section_header *)profile_info_sec; 17062306a36Sopenharmony_ci sec->tbl_size = 1; 17162306a36Sopenharmony_ci sec->data_end = sizeof(struct i40e_profile_section_header) + 17262306a36Sopenharmony_ci sizeof(struct i40e_profile_info); 17362306a36Sopenharmony_ci sec->section.type = SECTION_TYPE_INFO; 17462306a36Sopenharmony_ci sec->section.offset = sizeof(struct i40e_profile_section_header); 17562306a36Sopenharmony_ci sec->section.size = sizeof(struct i40e_profile_info); 17662306a36Sopenharmony_ci pinfo = (struct i40e_profile_info *)(profile_info_sec + 17762306a36Sopenharmony_ci sec->section.offset); 17862306a36Sopenharmony_ci pinfo->track_id = track_id; 17962306a36Sopenharmony_ci pinfo->version = profile->version; 18062306a36Sopenharmony_ci pinfo->op = I40E_DDP_REMOVE_TRACKID; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* Clear reserved field */ 18362306a36Sopenharmony_ci memset(pinfo->reserved, 0, sizeof(pinfo->reserved)); 18462306a36Sopenharmony_ci memcpy(pinfo->name, profile->name, I40E_DDP_NAME_SIZE); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci status = i40e_aq_write_ddp(hw, (void *)sec, sec->data_end, 18762306a36Sopenharmony_ci track_id, &offset, &info, NULL); 18862306a36Sopenharmony_ci return status; 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci/** 19262306a36Sopenharmony_ci * i40e_ddp_is_pkg_hdr_valid - performs basic pkg header integrity checks 19362306a36Sopenharmony_ci * @netdev: net device structure (for logging purposes) 19462306a36Sopenharmony_ci * @pkg_hdr: pointer to package header 19562306a36Sopenharmony_ci * @size_huge: size of the whole DDP profile package in size_t 19662306a36Sopenharmony_ci * 19762306a36Sopenharmony_ci * Checks correctness of pkg header: Version, size too big/small, and 19862306a36Sopenharmony_ci * all segment offsets alignment and boundaries. This function lets 19962306a36Sopenharmony_ci * reject non DDP profile file to be loaded by administrator mistake. 20062306a36Sopenharmony_ci **/ 20162306a36Sopenharmony_cistatic bool i40e_ddp_is_pkg_hdr_valid(struct net_device *netdev, 20262306a36Sopenharmony_ci struct i40e_package_header *pkg_hdr, 20362306a36Sopenharmony_ci size_t size_huge) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci u32 size = 0xFFFFFFFFU & size_huge; 20662306a36Sopenharmony_ci u32 pkg_hdr_size; 20762306a36Sopenharmony_ci u32 segment; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci if (!pkg_hdr) 21062306a36Sopenharmony_ci return false; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci if (pkg_hdr->version.major > 0) { 21362306a36Sopenharmony_ci struct i40e_ddp_version ver = pkg_hdr->version; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci netdev_err(netdev, "Unsupported DDP profile version %u.%u.%u.%u", 21662306a36Sopenharmony_ci ver.major, ver.minor, ver.update, ver.draft); 21762306a36Sopenharmony_ci return false; 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci if (size_huge > size) { 22062306a36Sopenharmony_ci netdev_err(netdev, "Invalid DDP profile - size is bigger than 4G"); 22162306a36Sopenharmony_ci return false; 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci if (size < (sizeof(struct i40e_package_header) + sizeof(u32) + 22462306a36Sopenharmony_ci sizeof(struct i40e_metadata_segment) + sizeof(u32) * 2)) { 22562306a36Sopenharmony_ci netdev_err(netdev, "Invalid DDP profile - size is too small."); 22662306a36Sopenharmony_ci return false; 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci pkg_hdr_size = sizeof(u32) * (pkg_hdr->segment_count + 2U); 23062306a36Sopenharmony_ci if (size < pkg_hdr_size) { 23162306a36Sopenharmony_ci netdev_err(netdev, "Invalid DDP profile - too many segments"); 23262306a36Sopenharmony_ci return false; 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci for (segment = 0; segment < pkg_hdr->segment_count; ++segment) { 23562306a36Sopenharmony_ci u32 offset = pkg_hdr->segment_offset[segment]; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci if (0xFU & offset) { 23862306a36Sopenharmony_ci netdev_err(netdev, 23962306a36Sopenharmony_ci "Invalid DDP profile %u segment alignment", 24062306a36Sopenharmony_ci segment); 24162306a36Sopenharmony_ci return false; 24262306a36Sopenharmony_ci } 24362306a36Sopenharmony_ci if (pkg_hdr_size > offset || offset >= size) { 24462306a36Sopenharmony_ci netdev_err(netdev, 24562306a36Sopenharmony_ci "Invalid DDP profile %u segment offset", 24662306a36Sopenharmony_ci segment); 24762306a36Sopenharmony_ci return false; 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci } 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci return true; 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci/** 25562306a36Sopenharmony_ci * i40e_ddp_load - performs DDP loading 25662306a36Sopenharmony_ci * @netdev: net device structure 25762306a36Sopenharmony_ci * @data: buffer containing recipe file 25862306a36Sopenharmony_ci * @size: size of the buffer 25962306a36Sopenharmony_ci * @is_add: true when loading profile, false when rolling back the previous one 26062306a36Sopenharmony_ci * 26162306a36Sopenharmony_ci * Checks correctness and loads DDP profile to the NIC. The function is 26262306a36Sopenharmony_ci * also used for rolling back previously loaded profile. 26362306a36Sopenharmony_ci **/ 26462306a36Sopenharmony_ciint i40e_ddp_load(struct net_device *netdev, const u8 *data, size_t size, 26562306a36Sopenharmony_ci bool is_add) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci u8 profile_info_sec[sizeof(struct i40e_profile_section_header) + 26862306a36Sopenharmony_ci sizeof(struct i40e_profile_info)]; 26962306a36Sopenharmony_ci struct i40e_metadata_segment *metadata_hdr; 27062306a36Sopenharmony_ci struct i40e_profile_segment *profile_hdr; 27162306a36Sopenharmony_ci struct i40e_profile_info pinfo; 27262306a36Sopenharmony_ci struct i40e_package_header *pkg_hdr; 27362306a36Sopenharmony_ci struct i40e_netdev_priv *np = netdev_priv(netdev); 27462306a36Sopenharmony_ci struct i40e_vsi *vsi = np->vsi; 27562306a36Sopenharmony_ci struct i40e_pf *pf = vsi->back; 27662306a36Sopenharmony_ci u32 track_id; 27762306a36Sopenharmony_ci int istatus; 27862306a36Sopenharmony_ci int status; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci pkg_hdr = (struct i40e_package_header *)data; 28162306a36Sopenharmony_ci if (!i40e_ddp_is_pkg_hdr_valid(netdev, pkg_hdr, size)) 28262306a36Sopenharmony_ci return -EINVAL; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci if (size < (sizeof(struct i40e_package_header) + sizeof(u32) + 28562306a36Sopenharmony_ci sizeof(struct i40e_metadata_segment) + sizeof(u32) * 2)) { 28662306a36Sopenharmony_ci netdev_err(netdev, "Invalid DDP recipe size."); 28762306a36Sopenharmony_ci return -EINVAL; 28862306a36Sopenharmony_ci } 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci /* Find beginning of segment data in buffer */ 29162306a36Sopenharmony_ci metadata_hdr = (struct i40e_metadata_segment *) 29262306a36Sopenharmony_ci i40e_find_segment_in_package(SEGMENT_TYPE_METADATA, pkg_hdr); 29362306a36Sopenharmony_ci if (!metadata_hdr) { 29462306a36Sopenharmony_ci netdev_err(netdev, "Failed to find metadata segment in DDP recipe."); 29562306a36Sopenharmony_ci return -EINVAL; 29662306a36Sopenharmony_ci } 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci track_id = metadata_hdr->track_id; 29962306a36Sopenharmony_ci profile_hdr = (struct i40e_profile_segment *) 30062306a36Sopenharmony_ci i40e_find_segment_in_package(SEGMENT_TYPE_I40E, pkg_hdr); 30162306a36Sopenharmony_ci if (!profile_hdr) { 30262306a36Sopenharmony_ci netdev_err(netdev, "Failed to find profile segment in DDP recipe."); 30362306a36Sopenharmony_ci return -EINVAL; 30462306a36Sopenharmony_ci } 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci pinfo.track_id = track_id; 30762306a36Sopenharmony_ci pinfo.version = profile_hdr->version; 30862306a36Sopenharmony_ci if (is_add) 30962306a36Sopenharmony_ci pinfo.op = I40E_DDP_ADD_TRACKID; 31062306a36Sopenharmony_ci else 31162306a36Sopenharmony_ci pinfo.op = I40E_DDP_REMOVE_TRACKID; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci memcpy(pinfo.name, profile_hdr->name, I40E_DDP_NAME_SIZE); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci /* Check if profile data already exists*/ 31662306a36Sopenharmony_ci istatus = i40e_ddp_does_profile_exist(&pf->hw, &pinfo); 31762306a36Sopenharmony_ci if (istatus < 0) { 31862306a36Sopenharmony_ci netdev_err(netdev, "Failed to fetch loaded profiles."); 31962306a36Sopenharmony_ci return istatus; 32062306a36Sopenharmony_ci } 32162306a36Sopenharmony_ci if (is_add) { 32262306a36Sopenharmony_ci if (istatus > 0) { 32362306a36Sopenharmony_ci netdev_err(netdev, "DDP profile already loaded."); 32462306a36Sopenharmony_ci return -EINVAL; 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci istatus = i40e_ddp_does_profile_overlap(&pf->hw, &pinfo); 32762306a36Sopenharmony_ci if (istatus < 0) { 32862306a36Sopenharmony_ci netdev_err(netdev, "Failed to fetch loaded profiles."); 32962306a36Sopenharmony_ci return istatus; 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci if (istatus > 0) { 33262306a36Sopenharmony_ci netdev_err(netdev, "DDP profile overlaps with existing one."); 33362306a36Sopenharmony_ci return -EINVAL; 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci } else { 33662306a36Sopenharmony_ci if (istatus == 0) { 33762306a36Sopenharmony_ci netdev_err(netdev, 33862306a36Sopenharmony_ci "DDP profile for deletion does not exist."); 33962306a36Sopenharmony_ci return -EINVAL; 34062306a36Sopenharmony_ci } 34162306a36Sopenharmony_ci } 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci /* Load profile data */ 34462306a36Sopenharmony_ci if (is_add) { 34562306a36Sopenharmony_ci status = i40e_write_profile(&pf->hw, profile_hdr, track_id); 34662306a36Sopenharmony_ci if (status) { 34762306a36Sopenharmony_ci if (status == -ENODEV) { 34862306a36Sopenharmony_ci netdev_err(netdev, 34962306a36Sopenharmony_ci "Profile is not supported by the device."); 35062306a36Sopenharmony_ci return -EPERM; 35162306a36Sopenharmony_ci } 35262306a36Sopenharmony_ci netdev_err(netdev, "Failed to write DDP profile."); 35362306a36Sopenharmony_ci return -EIO; 35462306a36Sopenharmony_ci } 35562306a36Sopenharmony_ci } else { 35662306a36Sopenharmony_ci status = i40e_rollback_profile(&pf->hw, profile_hdr, track_id); 35762306a36Sopenharmony_ci if (status) { 35862306a36Sopenharmony_ci netdev_err(netdev, "Failed to remove DDP profile."); 35962306a36Sopenharmony_ci return -EIO; 36062306a36Sopenharmony_ci } 36162306a36Sopenharmony_ci } 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci /* Add/remove profile to/from profile list in FW */ 36462306a36Sopenharmony_ci if (is_add) { 36562306a36Sopenharmony_ci status = i40e_add_pinfo(&pf->hw, profile_hdr, profile_info_sec, 36662306a36Sopenharmony_ci track_id); 36762306a36Sopenharmony_ci if (status) { 36862306a36Sopenharmony_ci netdev_err(netdev, "Failed to add DDP profile info."); 36962306a36Sopenharmony_ci return -EIO; 37062306a36Sopenharmony_ci } 37162306a36Sopenharmony_ci } else { 37262306a36Sopenharmony_ci status = i40e_del_pinfo(&pf->hw, profile_hdr, profile_info_sec, 37362306a36Sopenharmony_ci track_id); 37462306a36Sopenharmony_ci if (status) { 37562306a36Sopenharmony_ci netdev_err(netdev, "Failed to restore DDP profile info."); 37662306a36Sopenharmony_ci return -EIO; 37762306a36Sopenharmony_ci } 37862306a36Sopenharmony_ci } 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci return 0; 38162306a36Sopenharmony_ci} 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci/** 38462306a36Sopenharmony_ci * i40e_ddp_restore - restore previously loaded profile and remove from list 38562306a36Sopenharmony_ci * @pf: PF data struct 38662306a36Sopenharmony_ci * 38762306a36Sopenharmony_ci * Restores previously loaded profile stored on the list in driver memory. 38862306a36Sopenharmony_ci * After rolling back removes entry from the list. 38962306a36Sopenharmony_ci **/ 39062306a36Sopenharmony_cistatic int i40e_ddp_restore(struct i40e_pf *pf) 39162306a36Sopenharmony_ci{ 39262306a36Sopenharmony_ci struct i40e_ddp_old_profile_list *entry; 39362306a36Sopenharmony_ci struct net_device *netdev = pf->vsi[pf->lan_vsi]->netdev; 39462306a36Sopenharmony_ci int status = 0; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci if (!list_empty(&pf->ddp_old_prof)) { 39762306a36Sopenharmony_ci entry = list_first_entry(&pf->ddp_old_prof, 39862306a36Sopenharmony_ci struct i40e_ddp_old_profile_list, 39962306a36Sopenharmony_ci list); 40062306a36Sopenharmony_ci status = i40e_ddp_load(netdev, entry->old_ddp_buf, 40162306a36Sopenharmony_ci entry->old_ddp_size, false); 40262306a36Sopenharmony_ci list_del(&entry->list); 40362306a36Sopenharmony_ci kfree(entry); 40462306a36Sopenharmony_ci } 40562306a36Sopenharmony_ci return status; 40662306a36Sopenharmony_ci} 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci/** 40962306a36Sopenharmony_ci * i40e_ddp_flash - callback function for ethtool flash feature 41062306a36Sopenharmony_ci * @netdev: net device structure 41162306a36Sopenharmony_ci * @flash: kernel flash structure 41262306a36Sopenharmony_ci * 41362306a36Sopenharmony_ci * Ethtool callback function used for loading and unloading DDP profiles. 41462306a36Sopenharmony_ci **/ 41562306a36Sopenharmony_ciint i40e_ddp_flash(struct net_device *netdev, struct ethtool_flash *flash) 41662306a36Sopenharmony_ci{ 41762306a36Sopenharmony_ci const struct firmware *ddp_config; 41862306a36Sopenharmony_ci struct i40e_netdev_priv *np = netdev_priv(netdev); 41962306a36Sopenharmony_ci struct i40e_vsi *vsi = np->vsi; 42062306a36Sopenharmony_ci struct i40e_pf *pf = vsi->back; 42162306a36Sopenharmony_ci int status = 0; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci /* Check for valid region first */ 42462306a36Sopenharmony_ci if (flash->region != I40_DDP_FLASH_REGION) { 42562306a36Sopenharmony_ci netdev_err(netdev, "Requested firmware region is not recognized by this driver."); 42662306a36Sopenharmony_ci return -EINVAL; 42762306a36Sopenharmony_ci } 42862306a36Sopenharmony_ci if (pf->hw.bus.func != 0) { 42962306a36Sopenharmony_ci netdev_err(netdev, "Any DDP operation is allowed only on Phy0 NIC interface"); 43062306a36Sopenharmony_ci return -EINVAL; 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci /* If the user supplied "-" instead of file name rollback previously 43462306a36Sopenharmony_ci * stored profile. 43562306a36Sopenharmony_ci */ 43662306a36Sopenharmony_ci if (strncmp(flash->data, "-", 2) != 0) { 43762306a36Sopenharmony_ci struct i40e_ddp_old_profile_list *list_entry; 43862306a36Sopenharmony_ci char profile_name[sizeof(I40E_DDP_PROFILE_PATH) 43962306a36Sopenharmony_ci + I40E_DDP_PROFILE_NAME_MAX]; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci profile_name[sizeof(profile_name) - 1] = 0; 44262306a36Sopenharmony_ci strncpy(profile_name, I40E_DDP_PROFILE_PATH, 44362306a36Sopenharmony_ci sizeof(profile_name) - 1); 44462306a36Sopenharmony_ci strncat(profile_name, flash->data, I40E_DDP_PROFILE_NAME_MAX); 44562306a36Sopenharmony_ci /* Load DDP recipe. */ 44662306a36Sopenharmony_ci status = request_firmware(&ddp_config, profile_name, 44762306a36Sopenharmony_ci &netdev->dev); 44862306a36Sopenharmony_ci if (status) { 44962306a36Sopenharmony_ci netdev_err(netdev, "DDP recipe file request failed."); 45062306a36Sopenharmony_ci return status; 45162306a36Sopenharmony_ci } 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci status = i40e_ddp_load(netdev, ddp_config->data, 45462306a36Sopenharmony_ci ddp_config->size, true); 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci if (!status) { 45762306a36Sopenharmony_ci list_entry = 45862306a36Sopenharmony_ci kzalloc(sizeof(struct i40e_ddp_old_profile_list) + 45962306a36Sopenharmony_ci ddp_config->size, GFP_KERNEL); 46062306a36Sopenharmony_ci if (!list_entry) { 46162306a36Sopenharmony_ci netdev_info(netdev, "Failed to allocate memory for previous DDP profile data."); 46262306a36Sopenharmony_ci netdev_info(netdev, "New profile loaded but roll-back will be impossible."); 46362306a36Sopenharmony_ci } else { 46462306a36Sopenharmony_ci memcpy(list_entry->old_ddp_buf, 46562306a36Sopenharmony_ci ddp_config->data, ddp_config->size); 46662306a36Sopenharmony_ci list_entry->old_ddp_size = ddp_config->size; 46762306a36Sopenharmony_ci list_add(&list_entry->list, &pf->ddp_old_prof); 46862306a36Sopenharmony_ci } 46962306a36Sopenharmony_ci } 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci release_firmware(ddp_config); 47262306a36Sopenharmony_ci } else { 47362306a36Sopenharmony_ci if (!list_empty(&pf->ddp_old_prof)) { 47462306a36Sopenharmony_ci status = i40e_ddp_restore(pf); 47562306a36Sopenharmony_ci } else { 47662306a36Sopenharmony_ci netdev_warn(netdev, "There is no DDP profile to restore."); 47762306a36Sopenharmony_ci status = -ENOENT; 47862306a36Sopenharmony_ci } 47962306a36Sopenharmony_ci } 48062306a36Sopenharmony_ci return status; 48162306a36Sopenharmony_ci} 482