0001
0002
0003
0004 #include "i40e.h"
0005
0006 #include <linux/firmware.h>
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016 static bool i40e_ddp_profiles_eq(struct i40e_profile_info *a,
0017 struct i40e_profile_info *b)
0018 {
0019 return a->track_id == b->track_id &&
0020 !memcmp(&a->version, &b->version, sizeof(a->version)) &&
0021 !memcmp(&a->name, &b->name, I40E_DDP_NAME_SIZE);
0022 }
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034 static int i40e_ddp_does_profile_exist(struct i40e_hw *hw,
0035 struct i40e_profile_info *pinfo)
0036 {
0037 struct i40e_ddp_profile_list *profile_list;
0038 u8 buff[I40E_PROFILE_LIST_SIZE];
0039 i40e_status status;
0040 int i;
0041
0042 status = i40e_aq_get_ddp_list(hw, buff, I40E_PROFILE_LIST_SIZE, 0,
0043 NULL);
0044 if (status)
0045 return -1;
0046
0047 profile_list = (struct i40e_ddp_profile_list *)buff;
0048 for (i = 0; i < profile_list->p_count; i++) {
0049 if (i40e_ddp_profiles_eq(pinfo, &profile_list->p_info[i]))
0050 return 1;
0051 }
0052 return 0;
0053 }
0054
0055
0056
0057
0058
0059
0060
0061
0062
0063 static bool i40e_ddp_profiles_overlap(struct i40e_profile_info *new,
0064 struct i40e_profile_info *old)
0065 {
0066 unsigned int group_id_old = (u8)((old->track_id & 0x00FF0000) >> 16);
0067 unsigned int group_id_new = (u8)((new->track_id & 0x00FF0000) >> 16);
0068
0069
0070 if (group_id_new == 0)
0071 return true;
0072
0073 if (group_id_new == 0xFF || group_id_old == 0xFF)
0074 return false;
0075
0076 return group_id_old != group_id_new;
0077 }
0078
0079
0080
0081
0082
0083
0084
0085
0086
0087
0088
0089 static int i40e_ddp_does_profile_overlap(struct i40e_hw *hw,
0090 struct i40e_profile_info *pinfo)
0091 {
0092 struct i40e_ddp_profile_list *profile_list;
0093 u8 buff[I40E_PROFILE_LIST_SIZE];
0094 i40e_status status;
0095 int i;
0096
0097 status = i40e_aq_get_ddp_list(hw, buff, I40E_PROFILE_LIST_SIZE, 0,
0098 NULL);
0099 if (status)
0100 return -EIO;
0101
0102 profile_list = (struct i40e_ddp_profile_list *)buff;
0103 for (i = 0; i < profile_list->p_count; i++) {
0104 if (i40e_ddp_profiles_overlap(pinfo,
0105 &profile_list->p_info[i]))
0106 return 1;
0107 }
0108 return 0;
0109 }
0110
0111
0112
0113
0114
0115
0116
0117
0118
0119
0120 static enum i40e_status_code
0121 i40e_add_pinfo(struct i40e_hw *hw, struct i40e_profile_segment *profile,
0122 u8 *profile_info_sec, u32 track_id)
0123 {
0124 struct i40e_profile_section_header *sec;
0125 struct i40e_profile_info *pinfo;
0126 i40e_status status;
0127 u32 offset = 0, info = 0;
0128
0129 sec = (struct i40e_profile_section_header *)profile_info_sec;
0130 sec->tbl_size = 1;
0131 sec->data_end = sizeof(struct i40e_profile_section_header) +
0132 sizeof(struct i40e_profile_info);
0133 sec->section.type = SECTION_TYPE_INFO;
0134 sec->section.offset = sizeof(struct i40e_profile_section_header);
0135 sec->section.size = sizeof(struct i40e_profile_info);
0136 pinfo = (struct i40e_profile_info *)(profile_info_sec +
0137 sec->section.offset);
0138 pinfo->track_id = track_id;
0139 pinfo->version = profile->version;
0140 pinfo->op = I40E_DDP_ADD_TRACKID;
0141
0142
0143 memset(pinfo->reserved, 0, sizeof(pinfo->reserved));
0144 memcpy(pinfo->name, profile->name, I40E_DDP_NAME_SIZE);
0145
0146 status = i40e_aq_write_ddp(hw, (void *)sec, sec->data_end,
0147 track_id, &offset, &info, NULL);
0148 return status;
0149 }
0150
0151
0152
0153
0154
0155
0156
0157
0158
0159
0160 static enum i40e_status_code
0161 i40e_del_pinfo(struct i40e_hw *hw, struct i40e_profile_segment *profile,
0162 u8 *profile_info_sec, u32 track_id)
0163 {
0164 struct i40e_profile_section_header *sec;
0165 struct i40e_profile_info *pinfo;
0166 i40e_status status;
0167 u32 offset = 0, info = 0;
0168
0169 sec = (struct i40e_profile_section_header *)profile_info_sec;
0170 sec->tbl_size = 1;
0171 sec->data_end = sizeof(struct i40e_profile_section_header) +
0172 sizeof(struct i40e_profile_info);
0173 sec->section.type = SECTION_TYPE_INFO;
0174 sec->section.offset = sizeof(struct i40e_profile_section_header);
0175 sec->section.size = sizeof(struct i40e_profile_info);
0176 pinfo = (struct i40e_profile_info *)(profile_info_sec +
0177 sec->section.offset);
0178 pinfo->track_id = track_id;
0179 pinfo->version = profile->version;
0180 pinfo->op = I40E_DDP_REMOVE_TRACKID;
0181
0182
0183 memset(pinfo->reserved, 0, sizeof(pinfo->reserved));
0184 memcpy(pinfo->name, profile->name, I40E_DDP_NAME_SIZE);
0185
0186 status = i40e_aq_write_ddp(hw, (void *)sec, sec->data_end,
0187 track_id, &offset, &info, NULL);
0188 return status;
0189 }
0190
0191
0192
0193
0194
0195
0196
0197
0198
0199
0200
0201 static bool i40e_ddp_is_pkg_hdr_valid(struct net_device *netdev,
0202 struct i40e_package_header *pkg_hdr,
0203 size_t size_huge)
0204 {
0205 u32 size = 0xFFFFFFFFU & size_huge;
0206 u32 pkg_hdr_size;
0207 u32 segment;
0208
0209 if (!pkg_hdr)
0210 return false;
0211
0212 if (pkg_hdr->version.major > 0) {
0213 struct i40e_ddp_version ver = pkg_hdr->version;
0214
0215 netdev_err(netdev, "Unsupported DDP profile version %u.%u.%u.%u",
0216 ver.major, ver.minor, ver.update, ver.draft);
0217 return false;
0218 }
0219 if (size_huge > size) {
0220 netdev_err(netdev, "Invalid DDP profile - size is bigger than 4G");
0221 return false;
0222 }
0223 if (size < (sizeof(struct i40e_package_header) +
0224 sizeof(struct i40e_metadata_segment) + sizeof(u32) * 2)) {
0225 netdev_err(netdev, "Invalid DDP profile - size is too small.");
0226 return false;
0227 }
0228
0229 pkg_hdr_size = sizeof(u32) * (pkg_hdr->segment_count + 2U);
0230 if (size < pkg_hdr_size) {
0231 netdev_err(netdev, "Invalid DDP profile - too many segments");
0232 return false;
0233 }
0234 for (segment = 0; segment < pkg_hdr->segment_count; ++segment) {
0235 u32 offset = pkg_hdr->segment_offset[segment];
0236
0237 if (0xFU & offset) {
0238 netdev_err(netdev,
0239 "Invalid DDP profile %u segment alignment",
0240 segment);
0241 return false;
0242 }
0243 if (pkg_hdr_size > offset || offset >= size) {
0244 netdev_err(netdev,
0245 "Invalid DDP profile %u segment offset",
0246 segment);
0247 return false;
0248 }
0249 }
0250
0251 return true;
0252 }
0253
0254
0255
0256
0257
0258
0259
0260
0261
0262
0263
0264 int i40e_ddp_load(struct net_device *netdev, const u8 *data, size_t size,
0265 bool is_add)
0266 {
0267 u8 profile_info_sec[sizeof(struct i40e_profile_section_header) +
0268 sizeof(struct i40e_profile_info)];
0269 struct i40e_metadata_segment *metadata_hdr;
0270 struct i40e_profile_segment *profile_hdr;
0271 struct i40e_profile_info pinfo;
0272 struct i40e_package_header *pkg_hdr;
0273 i40e_status status;
0274 struct i40e_netdev_priv *np = netdev_priv(netdev);
0275 struct i40e_vsi *vsi = np->vsi;
0276 struct i40e_pf *pf = vsi->back;
0277 u32 track_id;
0278 int istatus;
0279
0280 pkg_hdr = (struct i40e_package_header *)data;
0281 if (!i40e_ddp_is_pkg_hdr_valid(netdev, pkg_hdr, size))
0282 return -EINVAL;
0283
0284 if (size < (sizeof(struct i40e_package_header) +
0285 sizeof(struct i40e_metadata_segment) + sizeof(u32) * 2)) {
0286 netdev_err(netdev, "Invalid DDP recipe size.");
0287 return -EINVAL;
0288 }
0289
0290
0291 metadata_hdr = (struct i40e_metadata_segment *)
0292 i40e_find_segment_in_package(SEGMENT_TYPE_METADATA, pkg_hdr);
0293 if (!metadata_hdr) {
0294 netdev_err(netdev, "Failed to find metadata segment in DDP recipe.");
0295 return -EINVAL;
0296 }
0297
0298 track_id = metadata_hdr->track_id;
0299 profile_hdr = (struct i40e_profile_segment *)
0300 i40e_find_segment_in_package(SEGMENT_TYPE_I40E, pkg_hdr);
0301 if (!profile_hdr) {
0302 netdev_err(netdev, "Failed to find profile segment in DDP recipe.");
0303 return -EINVAL;
0304 }
0305
0306 pinfo.track_id = track_id;
0307 pinfo.version = profile_hdr->version;
0308 if (is_add)
0309 pinfo.op = I40E_DDP_ADD_TRACKID;
0310 else
0311 pinfo.op = I40E_DDP_REMOVE_TRACKID;
0312
0313 memcpy(pinfo.name, profile_hdr->name, I40E_DDP_NAME_SIZE);
0314
0315
0316 istatus = i40e_ddp_does_profile_exist(&pf->hw, &pinfo);
0317 if (istatus < 0) {
0318 netdev_err(netdev, "Failed to fetch loaded profiles.");
0319 return istatus;
0320 }
0321 if (is_add) {
0322 if (istatus > 0) {
0323 netdev_err(netdev, "DDP profile already loaded.");
0324 return -EINVAL;
0325 }
0326 istatus = i40e_ddp_does_profile_overlap(&pf->hw, &pinfo);
0327 if (istatus < 0) {
0328 netdev_err(netdev, "Failed to fetch loaded profiles.");
0329 return istatus;
0330 }
0331 if (istatus > 0) {
0332 netdev_err(netdev, "DDP profile overlaps with existing one.");
0333 return -EINVAL;
0334 }
0335 } else {
0336 if (istatus == 0) {
0337 netdev_err(netdev,
0338 "DDP profile for deletion does not exist.");
0339 return -EINVAL;
0340 }
0341 }
0342
0343
0344 if (is_add) {
0345 status = i40e_write_profile(&pf->hw, profile_hdr, track_id);
0346 if (status) {
0347 if (status == I40E_ERR_DEVICE_NOT_SUPPORTED) {
0348 netdev_err(netdev,
0349 "Profile is not supported by the device.");
0350 return -EPERM;
0351 }
0352 netdev_err(netdev, "Failed to write DDP profile.");
0353 return -EIO;
0354 }
0355 } else {
0356 status = i40e_rollback_profile(&pf->hw, profile_hdr, track_id);
0357 if (status) {
0358 netdev_err(netdev, "Failed to remove DDP profile.");
0359 return -EIO;
0360 }
0361 }
0362
0363
0364 if (is_add) {
0365 status = i40e_add_pinfo(&pf->hw, profile_hdr, profile_info_sec,
0366 track_id);
0367 if (status) {
0368 netdev_err(netdev, "Failed to add DDP profile info.");
0369 return -EIO;
0370 }
0371 } else {
0372 status = i40e_del_pinfo(&pf->hw, profile_hdr, profile_info_sec,
0373 track_id);
0374 if (status) {
0375 netdev_err(netdev, "Failed to restore DDP profile info.");
0376 return -EIO;
0377 }
0378 }
0379
0380 return 0;
0381 }
0382
0383
0384
0385
0386
0387
0388
0389
0390 static int i40e_ddp_restore(struct i40e_pf *pf)
0391 {
0392 struct i40e_ddp_old_profile_list *entry;
0393 struct net_device *netdev = pf->vsi[pf->lan_vsi]->netdev;
0394 int status = 0;
0395
0396 if (!list_empty(&pf->ddp_old_prof)) {
0397 entry = list_first_entry(&pf->ddp_old_prof,
0398 struct i40e_ddp_old_profile_list,
0399 list);
0400 status = i40e_ddp_load(netdev, entry->old_ddp_buf,
0401 entry->old_ddp_size, false);
0402 list_del(&entry->list);
0403 kfree(entry);
0404 }
0405 return status;
0406 }
0407
0408
0409
0410
0411
0412
0413
0414
0415 int i40e_ddp_flash(struct net_device *netdev, struct ethtool_flash *flash)
0416 {
0417 const struct firmware *ddp_config;
0418 struct i40e_netdev_priv *np = netdev_priv(netdev);
0419 struct i40e_vsi *vsi = np->vsi;
0420 struct i40e_pf *pf = vsi->back;
0421 int status = 0;
0422
0423
0424 if (flash->region != I40_DDP_FLASH_REGION) {
0425 netdev_err(netdev, "Requested firmware region is not recognized by this driver.");
0426 return -EINVAL;
0427 }
0428 if (pf->hw.bus.func != 0) {
0429 netdev_err(netdev, "Any DDP operation is allowed only on Phy0 NIC interface");
0430 return -EINVAL;
0431 }
0432
0433
0434
0435
0436 if (strncmp(flash->data, "-", 2) != 0) {
0437 struct i40e_ddp_old_profile_list *list_entry;
0438 char profile_name[sizeof(I40E_DDP_PROFILE_PATH)
0439 + I40E_DDP_PROFILE_NAME_MAX];
0440
0441 profile_name[sizeof(profile_name) - 1] = 0;
0442 strncpy(profile_name, I40E_DDP_PROFILE_PATH,
0443 sizeof(profile_name) - 1);
0444 strncat(profile_name, flash->data, I40E_DDP_PROFILE_NAME_MAX);
0445
0446 status = request_firmware(&ddp_config, profile_name,
0447 &netdev->dev);
0448 if (status) {
0449 netdev_err(netdev, "DDP recipe file request failed.");
0450 return status;
0451 }
0452
0453 status = i40e_ddp_load(netdev, ddp_config->data,
0454 ddp_config->size, true);
0455
0456 if (!status) {
0457 list_entry =
0458 kzalloc(sizeof(struct i40e_ddp_old_profile_list) +
0459 ddp_config->size, GFP_KERNEL);
0460 if (!list_entry) {
0461 netdev_info(netdev, "Failed to allocate memory for previous DDP profile data.");
0462 netdev_info(netdev, "New profile loaded but roll-back will be impossible.");
0463 } else {
0464 memcpy(list_entry->old_ddp_buf,
0465 ddp_config->data, ddp_config->size);
0466 list_entry->old_ddp_size = ddp_config->size;
0467 list_add(&list_entry->list, &pf->ddp_old_prof);
0468 }
0469 }
0470
0471 release_firmware(ddp_config);
0472 } else {
0473 if (!list_empty(&pf->ddp_old_prof)) {
0474 status = i40e_ddp_restore(pf);
0475 } else {
0476 netdev_warn(netdev, "There is no DDP profile to restore.");
0477 status = -ENOENT;
0478 }
0479 }
0480 return status;
0481 }