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