Flashing updated firmware on deployed devices is cumbersome. Provide a
mechanism to load a custom KPU (Key Parse Unit) profile directly from
the filesystem at module load time.
When the rvu_af module is loaded with the kpu_profile parameter, the
specified profile is read from /lib/firmware/kpu and programmed into
the KPU registers. Add npc_kpu_profile_cam2 for the extended cam format
used by filesystem-loaded profiles and support ptype/ptype_mask in
npc_config_kpucam when profile->from_fs is set.
Usage:
1. Copy the KPU profile file to /lib/firmware/kpu.
2. Build OCTEONTX2_AF as a module.
3. Load: insmod rvu_af.ko kpu_profile=<profile_name>
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
.../ethernet/marvell/octeontx2/af/cn20k/npc.c | 40 ++-
.../net/ethernet/marvell/octeontx2/af/npc.h | 17 ++
.../net/ethernet/marvell/octeontx2/af/rvu.h | 6 +-
.../ethernet/marvell/octeontx2/af/rvu_npc.c | 274 +++++++++++++++---
.../ethernet/marvell/octeontx2/af/rvu_npc.h | 17 ++
.../ethernet/marvell/octeontx2/af/rvu_reg.h | 1 +
6 files changed, 303 insertions(+), 52 deletions(-)
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/cn20k/npc.c b/drivers/net/ethernet/marvell/octeontx2/af/cn20k/npc.c
index 69439ff76e10..a3015b12a20b 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/cn20k/npc.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/cn20k/npc.c
@@ -521,13 +521,17 @@ npc_program_single_kpm_profile(struct rvu *rvu, int blkaddr,
int kpm, int start_entry,
const struct npc_kpu_profile *profile)
{
+ int num_cam_entries, num_action_entries;
int entry, num_entries, max_entries;
u64 idx;
- if (profile->cam_entries != profile->action_entries) {
+ num_cam_entries = npc_get_num_kpu_cam_entries(rvu, profile);
+ num_action_entries = npc_get_num_kpu_action_entries(rvu, profile);
+
+ if (num_cam_entries != num_action_entries) {
dev_err(rvu->dev,
"kpm%d: CAM and action entries [%d != %d] not equal\n",
- kpm, profile->cam_entries, profile->action_entries);
+ kpm, num_cam_entries, num_action_entries);
WARN(1, "Fatal error\n");
return;
@@ -536,16 +540,18 @@ npc_program_single_kpm_profile(struct rvu *rvu, int blkaddr,
max_entries = rvu->hw->npc_kpu_entries / 2;
entry = start_entry;
/* Program CAM match entries for previous kpm extracted data */
- num_entries = min_t(int, profile->cam_entries, max_entries);
+ num_entries = min_t(int, num_cam_entries, max_entries);
for (idx = 0; entry < num_entries + start_entry; entry++, idx++)
- npc_config_kpmcam(rvu, blkaddr, &profile->cam[idx],
+ npc_config_kpmcam(rvu, blkaddr,
+ npc_get_kpu_cam_nth_entry(rvu, profile, idx),
kpm, entry);
entry = start_entry;
/* Program this kpm's actions */
- num_entries = min_t(int, profile->action_entries, max_entries);
+ num_entries = min_t(int, num_action_entries, max_entries);
for (idx = 0; entry < num_entries + start_entry; entry++, idx++)
- npc_config_kpmaction(rvu, blkaddr, &profile->action[idx],
+ npc_config_kpmaction(rvu, blkaddr,
+ npc_get_kpu_action_nth_entry(rvu, profile, idx),
kpm, entry, false);
}
@@ -611,20 +617,23 @@ npc_enable_kpm_entry(struct rvu *rvu, int blkaddr, int kpm, int num_entries)
static void npc_program_kpm_profile(struct rvu *rvu, int blkaddr, int num_kpms)
{
const struct npc_kpu_profile *profile1, *profile2;
+ int pfl1_num_cam_entries, pfl2_num_cam_entries;
int idx, total_cam_entries;
for (idx = 0; idx < num_kpms; idx++) {
profile1 = &rvu->kpu.kpu[idx];
+ pfl1_num_cam_entries = npc_get_num_kpu_cam_entries(rvu, profile1);
npc_program_single_kpm_profile(rvu, blkaddr, idx, 0, profile1);
profile2 = &rvu->kpu.kpu[idx + KPU_OFFSET];
+ pfl2_num_cam_entries = npc_get_num_kpu_cam_entries(rvu, profile2);
+
npc_program_single_kpm_profile(rvu, blkaddr, idx,
- profile1->cam_entries,
+ pfl1_num_cam_entries,
profile2);
- total_cam_entries = profile1->cam_entries +
- profile2->cam_entries;
+ total_cam_entries = pfl1_num_cam_entries + pfl2_num_cam_entries;
npc_enable_kpm_entry(rvu, blkaddr, idx, total_cam_entries);
rvu_write64(rvu, blkaddr, NPC_AF_KPMX_PASS2_OFFSET(idx),
- profile1->cam_entries);
+ pfl1_num_cam_entries);
/* Enable the KPUs associated with this KPM */
rvu_write64(rvu, blkaddr, NPC_AF_KPUX_CFG(idx), 0x01);
rvu_write64(rvu, blkaddr, NPC_AF_KPUX_CFG(idx + KPU_OFFSET),
@@ -634,6 +643,7 @@ static void npc_program_kpm_profile(struct rvu *rvu, int blkaddr, int num_kpms)
void npc_cn20k_parser_profile_init(struct rvu *rvu, int blkaddr)
{
+ struct npc_kpu_profile_action *act;
struct rvu_hwinfo *hw = rvu->hw;
int num_pkinds, idx;
@@ -665,9 +675,15 @@ void npc_cn20k_parser_profile_init(struct rvu *rvu, int blkaddr)
num_pkinds = rvu->kpu.pkinds;
num_pkinds = min_t(int, hw->npc_pkinds, num_pkinds);
- for (idx = 0; idx < num_pkinds; idx++)
- npc_config_kpmaction(rvu, blkaddr, &rvu->kpu.ikpu[idx],
+ /* Cn20k does not support Custom profile from filesystem */
+ for (idx = 0; idx < num_pkinds; idx++) {
+ act = npc_get_ikpu_nth_entry(rvu, idx);
+ if (!act)
+ continue;
+
+ npc_config_kpmaction(rvu, blkaddr, act,
0, idx, true);
+ }
/* Program KPM CAM and Action profiles */
npc_program_kpm_profile(rvu, blkaddr, hw->npc_kpms);
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/npc.h b/drivers/net/ethernet/marvell/octeontx2/af/npc.h
index cefc5d70f3e4..c8c0cb68535c 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/npc.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/npc.h
@@ -265,6 +265,19 @@ struct npc_kpu_profile_cam {
u16 dp2_mask;
} __packed;
+struct npc_kpu_profile_cam2 {
+ u8 state;
+ u8 state_mask;
+ u16 dp0;
+ u16 dp0_mask;
+ u16 dp1;
+ u16 dp1_mask;
+ u16 dp2;
+ u16 dp2_mask;
+ u8 ptype;
+ u8 ptype_mask;
+} __packed;
+
struct npc_kpu_profile_action {
u8 errlev;
u8 errcode;
@@ -290,6 +303,10 @@ struct npc_kpu_profile {
int action_entries;
struct npc_kpu_profile_cam *cam;
struct npc_kpu_profile_action *action;
+ int cam_entries2;
+ int action_entries2;
+ struct npc_kpu_profile_action *action2;
+ struct npc_kpu_profile_cam2 *cam2;
};
/* NPC KPU register formats */
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
index a466181cf908..eb2e4ccf070d 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
@@ -553,8 +553,9 @@ struct npc_kpu_profile_adapter {
const char *name;
u64 version;
const struct npc_lt_def_cfg *lt_def;
- const struct npc_kpu_profile_action *ikpu; /* array[pkinds] */
- const struct npc_kpu_profile *kpu; /* array[kpus] */
+ struct npc_kpu_profile_action *ikpu; /* array[pkinds] */
+ struct npc_kpu_profile_action *ikpu2; /* array[pkinds] */
+ struct npc_kpu_profile *kpu; /* array[kpus] */
union npc_mcam_key_prfl {
struct npc_mcam_kex *mkex;
/* used for cn9k and cn10k */
@@ -564,6 +565,7 @@ struct npc_kpu_profile_adapter {
bool custom;
size_t pkinds;
size_t kpus;
+ bool from_fs;
};
#define RVU_SWITCH_LBK_CHAN 63
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
index 8d260bcfbf38..8d8feb7742e8 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
@@ -1582,8 +1582,12 @@ static void npc_config_kpucam(struct rvu *rvu, int blkaddr,
const struct npc_kpu_profile_cam *kpucam,
int kpu, int entry)
{
+ const struct npc_kpu_profile_cam2 *kpucam2 = (void *)kpucam;
+ struct npc_kpu_profile_adapter *profile = &rvu->kpu;
struct npc_kpu_cam cam0 = {0};
struct npc_kpu_cam cam1 = {0};
+ u64 *val = (u64 *)&cam1;
+ u64 *mask = (u64 *)&cam0;
cam1.state = kpucam->state & kpucam->state_mask;
cam1.dp0_data = kpucam->dp0 & kpucam->dp0_mask;
@@ -1595,6 +1599,14 @@ static void npc_config_kpucam(struct rvu *rvu, int blkaddr,
cam0.dp1_data = ~kpucam->dp1 & kpucam->dp1_mask;
cam0.dp2_data = ~kpucam->dp2 & kpucam->dp2_mask;
+ if (profile->from_fs) {
+ u8 ptype = kpucam2->ptype;
+ u8 pmask = kpucam2->ptype_mask;
+
+ *val |= FIELD_PREP(GENMASK_ULL(57, 56), ptype & pmask);
+ *mask |= FIELD_PREP(GENMASK_ULL(57, 56), ~ptype & pmask);
+ }
+
rvu_write64(rvu, blkaddr,
NPC_AF_KPUX_ENTRYX_CAMX(kpu, entry, 0), *(u64 *)&cam0);
rvu_write64(rvu, blkaddr,
@@ -1606,34 +1618,104 @@ u64 npc_enable_mask(int count)
return (((count) < 64) ? ~(BIT_ULL(count) - 1) : (0x00ULL));
}
+struct npc_kpu_profile_action *
+npc_get_ikpu_nth_entry(struct rvu *rvu, int n)
+{
+ struct npc_kpu_profile_adapter *profile = &rvu->kpu;
+
+ if (profile->from_fs)
+ return &profile->ikpu2[n];
+
+ return &profile->ikpu[n];
+}
+
+int
+npc_get_num_kpu_cam_entries(struct rvu *rvu,
+ const struct npc_kpu_profile *kpu_pfl)
+{
+ struct npc_kpu_profile_adapter *profile = &rvu->kpu;
+
+ if (profile->from_fs)
+ return kpu_pfl->cam_entries2;
+
+ return kpu_pfl->cam_entries;
+}
+
+struct npc_kpu_profile_cam *
+npc_get_kpu_cam_nth_entry(struct rvu *rvu,
+ const struct npc_kpu_profile *kpu_pfl, int n)
+{
+ struct npc_kpu_profile_adapter *profile = &rvu->kpu;
+
+ if (profile->from_fs)
+ return (void *)&kpu_pfl->cam2[n];
+
+ return (void *)&kpu_pfl->cam[n];
+}
+
+int
+npc_get_num_kpu_action_entries(struct rvu *rvu,
+ const struct npc_kpu_profile *kpu_pfl)
+{
+ struct npc_kpu_profile_adapter *profile = &rvu->kpu;
+
+ if (profile->from_fs)
+ return kpu_pfl->action_entries2;
+
+ return kpu_pfl->action_entries;
+}
+
+struct npc_kpu_profile_action *
+npc_get_kpu_action_nth_entry(struct rvu *rvu,
+ const struct npc_kpu_profile *kpu_pfl,
+ int n)
+{
+ struct npc_kpu_profile_adapter *profile = &rvu->kpu;
+
+ if (profile->from_fs)
+ return (void *)&kpu_pfl->action2[n];
+
+ return (void *)&kpu_pfl->action[n];
+}
+
static void npc_program_kpu_profile(struct rvu *rvu, int blkaddr, int kpu,
const struct npc_kpu_profile *profile)
{
+ int num_cam_entries, num_action_entries;
int entry, num_entries, max_entries;
u64 entry_mask;
- if (profile->cam_entries != profile->action_entries) {
+ num_cam_entries = npc_get_num_kpu_cam_entries(rvu, profile);
+ num_action_entries = npc_get_num_kpu_action_entries(rvu, profile);
+
+ if (num_cam_entries != num_action_entries) {
dev_err(rvu->dev,
"KPU%d: CAM and action entries [%d != %d] not equal\n",
- kpu, profile->cam_entries, profile->action_entries);
+ kpu, num_cam_entries, num_action_entries);
}
max_entries = rvu->hw->npc_kpu_entries;
+ WARN(num_cam_entries > max_entries,
+ "KPU%u: err: hw max entries=%u, input entries=%u\n",
+ kpu, rvu->hw->npc_kpu_entries, num_cam_entries);
+
/* Program CAM match entries for previous KPU extracted data */
- num_entries = min_t(int, profile->cam_entries, max_entries);
+ num_entries = min_t(int, num_cam_entries, max_entries);
for (entry = 0; entry < num_entries; entry++)
npc_config_kpucam(rvu, blkaddr,
- &profile->cam[entry], kpu, entry);
+ (void *)npc_get_kpu_cam_nth_entry(rvu, profile, entry),
+ kpu, entry);
/* Program this KPU's actions */
- num_entries = min_t(int, profile->action_entries, max_entries);
+ num_entries = min_t(int, num_action_entries, max_entries);
for (entry = 0; entry < num_entries; entry++)
- npc_config_kpuaction(rvu, blkaddr, &profile->action[entry],
+ npc_config_kpuaction(rvu, blkaddr,
+ (void *)npc_get_kpu_action_nth_entry(rvu, profile, entry),
kpu, entry, false);
/* Enable all programmed entries */
- num_entries = min_t(int, profile->action_entries, profile->cam_entries);
+ num_entries = min_t(int, num_action_entries, num_cam_entries);
entry_mask = npc_enable_mask(num_entries);
/* Disable first KPU_MAX_CST_ENT entries for built-in profile */
if (!rvu->kpu.custom)
@@ -1677,26 +1759,49 @@ static void npc_prepare_default_kpu(struct rvu *rvu,
npc_cn20k_update_action_entries_n_flags(rvu, profile);
}
+static int npc_alloc_kpu_cam2_n_action2(struct rvu *rvu, int kpu_num,
+ int num_entries)
+{
+ struct npc_kpu_profile_adapter *adapter = &rvu->kpu;
+ struct npc_kpu_profile *kpu;
+
+ kpu = &adapter->kpu[kpu_num];
+
+ kpu->cam2 = devm_kcalloc(rvu->dev, num_entries,
+ sizeof(*kpu->cam2), GFP_KERNEL);
+ if (!kpu->cam2)
+ return -ENOMEM;
+
+ kpu->action2 = devm_kcalloc(rvu->dev, num_entries,
+ sizeof(*kpu->action2), GFP_KERNEL);
+ if (!kpu->action2)
+ return -ENOMEM;
+
+ return 0;
+}
+
static int npc_apply_custom_kpu(struct rvu *rvu,
- struct npc_kpu_profile_adapter *profile)
+ struct npc_kpu_profile_adapter *profile,
+ bool from_fs, int *fw_kpus)
{
size_t hdr_sz = sizeof(struct npc_kpu_profile_fwdata), offset = 0;
struct npc_kpu_profile_action *action;
+ struct npc_kpu_profile_fwdata *sfw;
struct npc_kpu_profile_fwdata *fw;
+ struct npc_kpu_profile_cam2 *cam2;
struct npc_kpu_profile_cam *cam;
struct npc_kpu_fwdata *fw_kpu;
- int entries;
- u16 kpu, entry;
+ int entries, ret, entry, kpu;
if (is_cn20k(rvu->pdev))
return npc_cn20k_apply_custom_kpu(rvu, profile);
- fw = rvu->kpu_fwdata;
-
if (rvu->kpu_fwdata_sz < hdr_sz) {
dev_warn(rvu->dev, "Invalid KPU profile size\n");
return -EINVAL;
}
+
+ fw = rvu->kpu_fwdata;
if (le64_to_cpu(fw->signature) != KPU_SIGN) {
dev_warn(rvu->dev, "Invalid KPU profile signature %llx\n",
fw->signature);
@@ -1730,32 +1835,89 @@ static int npc_apply_custom_kpu(struct rvu *rvu,
return -EINVAL;
}
+ *fw_kpus = fw->kpus;
+
+ sfw = devm_kcalloc(rvu->dev, 1, sizeof(*sfw), GFP_KERNEL);
+ if (!sfw)
+ return -ENOMEM;
+
+ memcpy(sfw, fw, sizeof(*sfw));
+
profile->custom = 1;
- profile->name = fw->name;
+ profile->name = sfw->name;
profile->version = le64_to_cpu(fw->version);
- profile->mcam_kex_prfl.mkex = &fw->mkex;
- profile->lt_def = &fw->lt_def;
+ profile->mcam_kex_prfl.mkex = &sfw->mkex;
+ profile->lt_def = &sfw->lt_def;
+
+ /* Binary blob contains ikpu actions entries at start of data[0] */
+ if (from_fs) {
+ profile->ikpu2 = devm_kcalloc(rvu->dev, 1,
+ sizeof(ikpu_action_entries),
+ GFP_KERNEL);
+ if (!profile->ikpu2)
+ return -ENOMEM;
+
+ action = (struct npc_kpu_profile_action *)(fw->data + offset);
+
+ if (rvu->kpu_fwdata_sz < hdr_sz + sizeof(ikpu_action_entries))
+ return -ENOMEM;
+
+ memcpy((void *)profile->ikpu2, action, sizeof(ikpu_action_entries));
+ offset += sizeof(ikpu_action_entries);
+ }
for (kpu = 0; kpu < fw->kpus; kpu++) {
fw_kpu = (struct npc_kpu_fwdata *)(fw->data + offset);
- if (fw_kpu->entries > KPU_MAX_CST_ENT)
- dev_warn(rvu->dev,
- "Too many custom entries on KPU%d: %d > %d\n",
- kpu, fw_kpu->entries, KPU_MAX_CST_ENT);
- entries = min(fw_kpu->entries, KPU_MAX_CST_ENT);
- cam = (struct npc_kpu_profile_cam *)fw_kpu->data;
- offset += sizeof(*fw_kpu) + fw_kpu->entries * sizeof(*cam);
+ if (!from_fs) {
+ if (fw_kpu->entries > KPU_MAX_CST_ENT)
+ dev_warn(rvu->dev,
+ "Too many custom entries on KPU%d: %d > %d\n",
+ kpu, fw_kpu->entries, KPU_MAX_CST_ENT);
+ entries = min(fw_kpu->entries, KPU_MAX_CST_ENT);
+ cam = (struct npc_kpu_profile_cam *)fw_kpu->data;
+ offset += sizeof(*fw_kpu) + fw_kpu->entries * sizeof(*cam);
+ action = (struct npc_kpu_profile_action *)(fw->data + offset);
+ offset += fw_kpu->entries * sizeof(*action);
+ if (rvu->kpu_fwdata_sz < hdr_sz + offset) {
+ dev_warn(rvu->dev,
+ "Profile size mismatch on KPU%i parsing.\n",
+ kpu + 1);
+ return -EINVAL;
+ }
+ for (entry = 0; entry < entries; entry++) {
+ profile->kpu[kpu].cam[entry] = cam[entry];
+ profile->kpu[kpu].action[entry] = action[entry];
+ }
+ continue;
+ }
+ entries = fw_kpu->entries;
+ dev_info(rvu->dev,
+ "Loading %u entries on KPU%d\n", entries, kpu);
+
+ cam2 = (struct npc_kpu_profile_cam2 *)fw_kpu->data;
+ offset += sizeof(*fw_kpu) + fw_kpu->entries * sizeof(*cam2);
action = (struct npc_kpu_profile_action *)(fw->data + offset);
offset += fw_kpu->entries * sizeof(*action);
if (rvu->kpu_fwdata_sz < hdr_sz + offset) {
dev_warn(rvu->dev,
- "Profile size mismatch on KPU%i parsing.\n",
+ "profile size mismatch on kpu%i parsing.\n",
kpu + 1);
return -EINVAL;
}
+
+ profile->kpu[kpu].cam_entries2 = entries;
+ profile->kpu[kpu].action_entries2 = entries;
+ ret = npc_alloc_kpu_cam2_n_action2(rvu, kpu, entries);
+ if (ret) {
+ dev_warn(rvu->dev,
+ "profile entry allocation failed for kpu=%d for %d entries\n",
+ kpu, entries);
+ return -EINVAL;
+ }
+
for (entry = 0; entry < entries; entry++) {
- profile->kpu[kpu].cam[entry] = cam[entry];
- profile->kpu[kpu].action[entry] = action[entry];
+ profile->kpu[kpu].cam2[entry] = cam2[entry];
+ profile->kpu[kpu].action2[entry] = action[entry];
}
}
@@ -1852,7 +2014,10 @@ void npc_load_kpu_profile(struct rvu *rvu)
struct npc_kpu_profile_adapter *profile = &rvu->kpu;
const char *kpu_profile = rvu->kpu_pfl_name;
const struct firmware *fw = NULL;
- bool retry_fwdb = false;
+ int len, ret, fw_kpus = 0;
+ char *path;
+
+ profile->from_fs = false;
/* If user not specified profile customization */
if (!strncmp(kpu_profile, def_pfl_name, KPU_NAME_LEN))
@@ -1865,27 +2030,56 @@ void npc_load_kpu_profile(struct rvu *rvu)
* Firmware database method.
* Default KPU profile.
*/
- if (!request_firmware_direct(&fw, kpu_profile, rvu->dev)) {
- dev_info(rvu->dev, "Loading KPU profile from firmware: %s\n",
- kpu_profile);
+
+ /* No support for filesystem KPU loading */
+ if (is_cn20k(rvu->pdev))
+ goto load_image_fwdb;
+
+#define PDIR "kpu/"
+ len = strlen(kpu_profile) + sizeof(PDIR);
+ path = kmalloc(len, GFP_KERNEL);
+ if (!path)
+ return;
+
+ strscpy(path, PDIR, len);
+ strcat(path, kpu_profile);
+ if (!request_firmware_direct(&fw, path, rvu->dev)) {
+ dev_info(rvu->dev, "Loading KPU profile from filesystem: %s\n",
+ path);
rvu->kpu_fwdata = kzalloc(fw->size, GFP_KERNEL);
if (rvu->kpu_fwdata) {
memcpy(rvu->kpu_fwdata, fw->data, fw->size);
rvu->kpu_fwdata_sz = fw->size;
}
release_firmware(fw);
- retry_fwdb = true;
- goto program_kpu;
+ kfree(path);
+
+ ret = npc_apply_custom_kpu(rvu, profile, true, &fw_kpus);
+ kfree(rvu->kpu_fwdata);
+ rvu->kpu_fwdata = NULL;
+
+ if (ret) {
+ rvu->kpu_fwdata_sz = 0;
+ npc_prepare_default_kpu(rvu, profile);
+ dev_err(rvu->dev,
+ "Loading KPU profile from filesystem failed\n");
+ goto load_image_fwdb;
+ }
+
+ rvu->kpu.kpus = fw_kpus;
+ profile->from_fs = true;
+ return;
}
+ kfree(path);
load_image_fwdb:
/* Loading the KPU profile using firmware database */
if (npc_load_kpu_profile_fwdb(rvu, kpu_profile))
goto revert_to_default;
-program_kpu:
/* Apply profile customization if firmware was loaded. */
- if (!rvu->kpu_fwdata_sz || npc_apply_custom_kpu(rvu, profile)) {
+ if (!rvu->kpu_fwdata_sz ||
+ npc_apply_custom_kpu(rvu, profile, false, &fw_kpus)) {
/* If image from firmware filesystem fails to load or invalid
* retry with firmware database method.
*/
@@ -1899,10 +2093,6 @@ void npc_load_kpu_profile(struct rvu *rvu)
}
rvu->kpu_fwdata = NULL;
rvu->kpu_fwdata_sz = 0;
- if (retry_fwdb) {
- retry_fwdb = false;
- goto load_image_fwdb;
- }
}
dev_warn(rvu->dev,
@@ -1926,6 +2116,7 @@ void npc_load_kpu_profile(struct rvu *rvu)
static void npc_parser_profile_init(struct rvu *rvu, int blkaddr)
{
+ struct npc_kpu_profile_adapter *profile = &rvu->kpu;
struct rvu_hwinfo *hw = rvu->hw;
int num_pkinds, num_kpus, idx;
@@ -1949,7 +2140,9 @@ static void npc_parser_profile_init(struct rvu *rvu, int blkaddr)
num_pkinds = min_t(int, hw->npc_pkinds, num_pkinds);
for (idx = 0; idx < num_pkinds; idx++)
- npc_config_kpuaction(rvu, blkaddr, &rvu->kpu.ikpu[idx], 0, idx, true);
+ npc_config_kpuaction(rvu, blkaddr,
+ npc_get_ikpu_nth_entry(rvu, idx),
+ 0, idx, true);
/* Program KPU CAM and Action profiles */
num_kpus = rvu->kpu.kpus;
@@ -1957,6 +2150,11 @@ static void npc_parser_profile_init(struct rvu *rvu, int blkaddr)
for (idx = 0; idx < num_kpus; idx++)
npc_program_kpu_profile(rvu, blkaddr, idx, &rvu->kpu.kpu[idx]);
+
+ if (profile->from_fs) {
+ rvu_write64(rvu, blkaddr, NPC_AF_PKINDX_TYPE(54), 0x03);
+ rvu_write64(rvu, blkaddr, NPC_AF_PKINDX_TYPE(58), 0x03);
+ }
}
void npc_mcam_rsrcs_deinit(struct rvu *rvu)
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.h
index 83c5e32e2afc..662f6693cfe9 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.h
@@ -18,4 +18,21 @@ int npc_fwdb_prfl_img_map(struct rvu *rvu, void __iomem **prfl_img_addr,
void npc_mcam_clear_bit(struct npc_mcam *mcam, u16 index);
void npc_mcam_set_bit(struct npc_mcam *mcam, u16 index);
+
+struct npc_kpu_profile_action *
+npc_get_ikpu_nth_entry(struct rvu *rvu, int n);
+
+int
+npc_get_num_kpu_cam_entries(struct rvu *rvu,
+ const struct npc_kpu_profile *kpu_pfl);
+struct npc_kpu_profile_cam *
+npc_get_kpu_cam_nth_entry(struct rvu *rvu,
+ const struct npc_kpu_profile *kpu_pfl, int n);
+
+int
+npc_get_num_kpu_action_entries(struct rvu *rvu,
+ const struct npc_kpu_profile *kpu_pfl);
+struct npc_kpu_profile_action *
+npc_get_kpu_action_nth_entry(struct rvu *rvu,
+ const struct npc_kpu_profile *kpu_pfl, int n);
#endif /* RVU_NPC_H */
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_reg.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu_reg.h
index 62cdc714ba57..ab89b8c6e490 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_reg.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_reg.h
@@ -596,6 +596,7 @@
#define NPC_AF_INTFX_KEX_CFG(a) (0x01010 | (a) << 8)
#define NPC_AF_PKINDX_ACTION0(a) (0x80000ull | (a) << 6)
#define NPC_AF_PKINDX_ACTION1(a) (0x80008ull | (a) << 6)
+#define NPC_AF_PKINDX_TYPE(a) (0x80010ull | (a) << 6)
#define NPC_AF_PKINDX_CPI_DEFX(a, b) (0x80020ull | (a) << 6 | (b) << 3)
#define NPC_AF_KPUX_ENTRYX_CAMX(a, b, c) \
(0x100000 | (a) << 14 | (b) << 6 | (c) << 3)
--
2.43.0
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
octeontx2-af: npc: Support for custom KPU profile from filesystem
Flashing updated firmware on deployed devices is cumbersome. Provide a
mechanism to load a custom KPU (Key Parse Unit) profile directly from
the filesystem at module load time.
When the rvu_af module is loaded with the kpu_profile parameter, the
specified profile is read from /lib/firmware/kpu and programmed into
the KPU registers. Add npc_kpu_profile_cam2 for the extended cam format
used by filesystem-loaded profiles and support ptype/ptype_mask in
npc_config_kpucam when profile->from_fs is set.
Link: https://lore.kernel.org/netdev/20260310172100.890161-1-horms@kernel.org/
Link: https://lore.kernel.org/netdev/20260317033214.646969-2-kuba@kernel.org/
> diff --git a/drivers/net/ethernet/marvell/octeontx2/af/cn20k/npc.c b/drivers/net/ethernet/marvell/octeontx2/af/cn20k/npc.c
> index 69439ff76e10..a3015b12a20b 100644
> --- a/drivers/net/ethernet/marvell/octeontx2/af/cn20k/npc.c
> +++ b/drivers/net/ethernet/marvell/octeontx2/af/cn20k/npc.c
[ ... ]
> @@ -665,9 +675,15 @@ void npc_cn20k_parser_profile_init(struct rvu *rvu, int blkaddr)
> num_pkinds = rvu->kpu.pkinds;
> num_pkinds = min_t(int, hw->npc_pkinds, num_pkinds);
>
> - for (idx = 0; idx < num_pkinds; idx++)
> - npc_config_kpmaction(rvu, blkaddr, &rvu->kpu.ikpu[idx],
> + /* Cn20k does not support Custom profile from filesystem */
> + for (idx = 0; idx < num_pkinds; idx++) {
> + act = npc_get_ikpu_nth_entry(rvu, idx);
> + if (!act)
> + continue;
> +
> + npc_config_kpmaction(rvu, blkaddr, act,
> 0, idx, true);
> + }
When a filesystem-loaded profile is used on cn20k (from_fs=true),
npc_get_ikpu_nth_entry() returns &profile->ikpu2[idx]:
drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c:npc_get_ikpu_nth_entry() {
struct npc_kpu_profile_adapter *profile = &rvu->kpu;
if (profile->from_fs)
return &profile->ikpu2[idx];
return &profile->ikpu[n];
}
However, ikpu2 is never allocated for the cn20k path. The allocation
happens inside npc_apply_custom_kpu() in the from_fs block:
drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c:npc_apply_custom_kpu() {
if (is_cn20k(rvu->pdev))
return npc_cn20k_apply_custom_kpu(rvu, profile);
...
if (from_fs) {
profile->ikpu2 = devm_kcalloc(rvu->dev, 1,
sizeof(ikpu_action_entries),
GFP_KERNEL);
...
}
}
But this from_fs block is never reached for cn20k because the function
returns early via npc_cn20k_apply_custom_kpu().
Does this code dereference a NULL-offset pointer causing a kernel crash?
The same issue appears to affect cam2 and action2 pointers which are
allocated in npc_alloc_kpu_cam2_n_action2() but never called for cn20k.
This was raised by Jakub Kicinski during v4 review on 2026-03-16.
> diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
> index 8d260bcfbf38..8d8feb7742e8 100644
> --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
> +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
[ ... ]
> @@ -1730,32 +1835,89 @@ static int npc_apply_custom_kpu(struct rvu *rvu,
> return -EINVAL;
> }
>
> + *fw_kpus = fw->kpus;
> +
> + sfw = devm_kcalloc(rvu->dev, 1, sizeof(*sfw), GFP_KERNEL);
> + if (!sfw)
> + return -ENOMEM;
> +
> + memcpy(sfw, fw, sizeof(*sfw));
> +
> profile->custom = 1;
> - profile->name = fw->name;
> + profile->name = sfw->name;
> profile->version = le64_to_cpu(fw->version);
> - profile->mcam_kex_prfl.mkex = &fw->mkex;
> - profile->lt_def = &fw->lt_def;
> + profile->mcam_kex_prfl.mkex = &sfw->mkex;
> + profile->lt_def = &sfw->lt_def;
> +
> + /* Binary blob contains ikpu actions entries at start of data[0] */
> + if (from_fs) {
> + profile->ikpu2 = devm_kcalloc(rvu->dev, 1,
> + sizeof(ikpu_action_entries),
> + GFP_KERNEL);
> + if (!profile->ikpu2)
> + return -ENOMEM;
> +
> + action = (struct npc_kpu_profile_action *)(fw->data + offset);
> +
> + if (rvu->kpu_fwdata_sz < hdr_sz + sizeof(ikpu_action_entries))
> + return -ENOMEM;
> +
> + memcpy((void *)profile->ikpu2, action, sizeof(ikpu_action_entries));
> + offset += sizeof(ikpu_action_entries);
> + }
> +
> for (kpu = 0; kpu < fw->kpus; kpu++) {
> fw_kpu = (struct npc_kpu_fwdata *)(fw->data + offset);
> - if (fw_kpu->entries > KPU_MAX_CST_ENT)
> - dev_warn(rvu->dev,
> - "Too many custom entries on KPU%d: %d > %d\n",
> - kpu, fw_kpu->entries, KPU_MAX_CST_ENT);
> - entries = min(fw_kpu->entries, KPU_MAX_CST_ENT);
> - cam = (struct npc_kpu_profile_cam *)fw_kpu->data;
> - offset += sizeof(*fw_kpu) + fw_kpu->entries * sizeof(*cam);
> + if (!from_fs) {
> + if (fw_kpu->entries > KPU_MAX_CST_ENT)
> + dev_warn(rvu->dev,
> + "Too many custom entries on KPU%d: %d > %d\n",
> + kpu, fw_kpu->entries, KPU_MAX_CST_ENT);
> + entries = min(fw_kpu->entries, KPU_MAX_CST_ENT);
> + cam = (struct npc_kpu_profile_cam *)fw_kpu->data;
> + offset += sizeof(*fw_kpu) + fw_kpu->entries * sizeof(*cam);
> + action = (struct npc_kpu_profile_action *)(fw->data + offset);
> + offset += fw_kpu->entries * sizeof(*action);
> + if (rvu->kpu_fwdata_sz < hdr_sz + offset) {
> + dev_warn(rvu->dev,
> + "Profile size mismatch on KPU%i parsing.\n",
> + kpu + 1);
> + return -EINVAL;
> + }
> + for (entry = 0; entry < entries; entry++) {
> + profile->kpu[kpu].cam[entry] = cam[entry];
> + profile->kpu[kpu].action[entry] = action[entry];
> + }
> + continue;
> + }
> + entries = fw_kpu->entries;
> + dev_info(rvu->dev,
> + "Loading %u entries on KPU%d\n", entries, kpu);
> +
> + cam2 = (struct npc_kpu_profile_cam2 *)fw_kpu->data;
> + offset += sizeof(*fw_kpu) + fw_kpu->entries * sizeof(*cam2);
> action = (struct npc_kpu_profile_action *)(fw->data + offset);
> offset += fw_kpu->entries * sizeof(*action);
> if (rvu->kpu_fwdata_sz < hdr_sz + offset) {
> dev_warn(rvu->dev,
> - "Profile size mismatch on KPU%i parsing.\n",
> + "profile size mismatch on kpu%i parsing.\n",
> kpu + 1);
> return -EINVAL;
> }
> +
> + profile->kpu[kpu].cam_entries2 = entries;
> + profile->kpu[kpu].action_entries2 = entries;
> + ret = npc_alloc_kpu_cam2_n_action2(rvu, kpu, entries);
> + if (ret) {
> + dev_warn(rvu->dev,
> + "profile entry allocation failed for kpu=%d for %d entries\n",
> + kpu, entries);
> + return -EINVAL;
> + }
> +
> for (entry = 0; entry < entries; entry++) {
> - profile->kpu[kpu].cam[entry] = cam[entry];
> - profile->kpu[kpu].action[entry] = action[entry];
> + profile->kpu[kpu].cam2[entry] = cam2[entry];
> + profile->kpu[kpu].action2[entry] = action[entry];
> }
> }
Could this loop overflow and hang the kernel if firmware specifies more
than 65535 entries?
The loop variable 'entry' is declared as int at the top of the function,
but in the from_fs path, 'entries' is read directly from the firmware
blob:
entries = fw_kpu->entries;
In the non-from_fs path, entries is capped:
entries = min(fw_kpu->entries, KPU_MAX_CST_ENT);
But in the from_fs path there is no cap on entries. If a corrupt or
malicious firmware file specifies fw_kpu->entries with a very large
value, the loop could run for an extremely long time or cause other
issues.
The kernel should not rely on filesystem data being well-formed.
Validation is needed for the from_fs case.
This was raised by Simon Horman during v3 review on 2026-03-10. The
author acknowledged this in v3 saying they would fix it in v5, but the
issue persists in the current version.
On 2026-03-20 at 22:24:32, Simon Horman (horms@kernel.org) wrote:
>
> > @@ -665,9 +675,15 @@ void npc_cn20k_parser_profile_init(struct rvu *rvu, int blkaddr)
> > num_pkinds = rvu->kpu.pkinds;
> > num_pkinds = min_t(int, hw->npc_pkinds, num_pkinds);
> >
> > - for (idx = 0; idx < num_pkinds; idx++)
> > - npc_config_kpmaction(rvu, blkaddr, &rvu->kpu.ikpu[idx],
> > + /* Cn20k does not support Custom profile from filesystem */
> > + for (idx = 0; idx < num_pkinds; idx++) {
> > + act = npc_get_ikpu_nth_entry(rvu, idx);
> > + if (!act)
> > + continue;
> > +
> > + npc_config_kpmaction(rvu, blkaddr, act,
> > 0, idx, true);
> > + }
>
> When a filesystem-loaded profile is used on cn20k (from_fs=true),
> npc_get_ikpu_nth_entry() returns &profile->ikpu2[idx]:
>
> drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c:npc_get_ikpu_nth_entry() {
> struct npc_kpu_profile_adapter *profile = &rvu->kpu;
>
> if (profile->from_fs)
> return &profile->ikpu2[idx];
>
> return &profile->ikpu[n];
> }
>
> However, ikpu2 is never allocated for the cn20k path. The allocation
> happens inside npc_apply_custom_kpu() in the from_fs block:
>
> drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c:npc_apply_custom_kpu() {
> if (is_cn20k(rvu->pdev))
> return npc_cn20k_apply_custom_kpu(rvu, profile);
> ...
> if (from_fs) {
> profile->ikpu2 = devm_kcalloc(rvu->dev, 1,
> sizeof(ikpu_action_entries),
> GFP_KERNEL);
> ...
> }
> }
>
> But this from_fs block is never reached for cn20k because the function
> returns early via npc_cn20k_apply_custom_kpu().
>
> Does this code dereference a NULL-offset pointer causing a kernel crash?
>
> The same issue appears to affect cam2 and action2 pointers which are
> allocated in npc_alloc_kpu_cam2_n_action2() but never called for cn20k.
>
> This was raised by Jakub Kicinski during v4 review on 2026-03-16.
This is false positive. from_fs is never set to "true" for cn20k. In npc_load_kpu_profile(), if soc is
cn20k, it skips "loading from filesystem" method. So npc_get_ikpu_nth_entry()
will never return ikpu2.
>
> > diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
> > index 8d260bcfbf38..8d8feb7742e8 100644
> > --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
> > +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
>
> [ ... ]
>
> > @@ -1730,32 +1835,89 @@ static int npc_apply_custom_kpu(struct rvu *rvu,
> > return -EINVAL;
> > }
> >
> > + *fw_kpus = fw->kpus;
> > +
> > + sfw = devm_kcalloc(rvu->dev, 1, sizeof(*sfw), GFP_KERNEL);
> > + if (!sfw)
> > + return -ENOMEM;
> > +
> > + memcpy(sfw, fw, sizeof(*sfw));
> > +
> > profile->custom = 1;
> > - profile->name = fw->name;
> > + profile->name = sfw->name;
> > profile->version = le64_to_cpu(fw->version);
> > - profile->mcam_kex_prfl.mkex = &fw->mkex;
> > - profile->lt_def = &fw->lt_def;
> > + profile->mcam_kex_prfl.mkex = &sfw->mkex;
> > + profile->lt_def = &sfw->lt_def;
> > +
> > + /* Binary blob contains ikpu actions entries at start of data[0] */
> > + if (from_fs) {
> > + profile->ikpu2 = devm_kcalloc(rvu->dev, 1,
> > + sizeof(ikpu_action_entries),
> > + GFP_KERNEL);
> > + if (!profile->ikpu2)
> > + return -ENOMEM;
> > +
> > + action = (struct npc_kpu_profile_action *)(fw->data + offset);
> > +
> > + if (rvu->kpu_fwdata_sz < hdr_sz + sizeof(ikpu_action_entries))
> > + return -ENOMEM;
> > +
> > + memcpy((void *)profile->ikpu2, action, sizeof(ikpu_action_entries));
> > + offset += sizeof(ikpu_action_entries);
> > + }
> > +
> > for (kpu = 0; kpu < fw->kpus; kpu++) {
> > fw_kpu = (struct npc_kpu_fwdata *)(fw->data + offset);
> > - if (fw_kpu->entries > KPU_MAX_CST_ENT)
> > - dev_warn(rvu->dev,
> > - "Too many custom entries on KPU%d: %d > %d\n",
> > - kpu, fw_kpu->entries, KPU_MAX_CST_ENT);
> > - entries = min(fw_kpu->entries, KPU_MAX_CST_ENT);
> > - cam = (struct npc_kpu_profile_cam *)fw_kpu->data;
> > - offset += sizeof(*fw_kpu) + fw_kpu->entries * sizeof(*cam);
> > + if (!from_fs) {
> > + if (fw_kpu->entries > KPU_MAX_CST_ENT)
> > + dev_warn(rvu->dev,
> > + "Too many custom entries on KPU%d: %d > %d\n",
> > + kpu, fw_kpu->entries, KPU_MAX_CST_ENT);
> > + entries = min(fw_kpu->entries, KPU_MAX_CST_ENT);
> > + cam = (struct npc_kpu_profile_cam *)fw_kpu->data;
> > + offset += sizeof(*fw_kpu) + fw_kpu->entries * sizeof(*cam);
> > + action = (struct npc_kpu_profile_action *)(fw->data + offset);
> > + offset += fw_kpu->entries * sizeof(*action);
> > + if (rvu->kpu_fwdata_sz < hdr_sz + offset) {
> > + dev_warn(rvu->dev,
> > + "Profile size mismatch on KPU%i parsing.\n",
> > + kpu + 1);
> > + return -EINVAL;
> > + }
> > + for (entry = 0; entry < entries; entry++) {
> > + profile->kpu[kpu].cam[entry] = cam[entry];
> > + profile->kpu[kpu].action[entry] = action[entry];
> > + }
> > + continue;
> > + }
> > + entries = fw_kpu->entries;
> > + dev_info(rvu->dev,
> > + "Loading %u entries on KPU%d\n", entries, kpu);
> > +
> > + cam2 = (struct npc_kpu_profile_cam2 *)fw_kpu->data;
> > + offset += sizeof(*fw_kpu) + fw_kpu->entries * sizeof(*cam2);
> > action = (struct npc_kpu_profile_action *)(fw->data + offset);
> > offset += fw_kpu->entries * sizeof(*action);
> > if (rvu->kpu_fwdata_sz < hdr_sz + offset) {
> > dev_warn(rvu->dev,
> > - "Profile size mismatch on KPU%i parsing.\n",
> > + "profile size mismatch on kpu%i parsing.\n",
> > kpu + 1);
> > return -EINVAL;
> > }
> > +
> > + profile->kpu[kpu].cam_entries2 = entries;
> > + profile->kpu[kpu].action_entries2 = entries;
> > + ret = npc_alloc_kpu_cam2_n_action2(rvu, kpu, entries);
> > + if (ret) {
> > + dev_warn(rvu->dev,
> > + "profile entry allocation failed for kpu=%d for %d entries\n",
> > + kpu, entries);
> > + return -EINVAL;
> > + }
> > +
> > for (entry = 0; entry < entries; entry++) {
> > - profile->kpu[kpu].cam[entry] = cam[entry];
> > - profile->kpu[kpu].action[entry] = action[entry];
> > + profile->kpu[kpu].cam2[entry] = cam2[entry];
> > + profile->kpu[kpu].action2[entry] = action[entry];
> > }
> > }
>
> Could this loop overflow and hang the kernel if firmware specifies more
> than 65535 entries?
>
> The loop variable 'entry' is declared as int at the top of the function,
> but in the from_fs path, 'entries' is read directly from the firmware
> blob:
>
> entries = fw_kpu->entries;
>
> In the non-from_fs path, entries is capped:
>
> entries = min(fw_kpu->entries, KPU_MAX_CST_ENT);
>
> But in the from_fs path there is no cap on entries. If a corrupt or
> malicious firmware file specifies fw_kpu->entries with a very large
> value, the loop could run for an extremely long time or cause other
> issues.
>
> The kernel should not rely on filesystem data being well-formed.
> Validation is needed for the from_fs case.
>
> This was raised by Simon Horman during v3 review on 2026-03-10. The
> author acknowledged this in v3 saying they would fix it in v5, but the
> issue persists in the current version.
ACK. Last AI review suggest is - either to use "int" as loop variable or do a capping
on the value. I did modify it to "int".
On 2026-03-23 at 07:17:33, Ratheesh Kannoth (rkannoth@marvell.com) wrote:
> On 2026-03-20 at 22:24:32, Simon Horman (horms@kernel.org) wrote:
> >
> > > @@ -665,9 +675,15 @@ void npc_cn20k_parser_profile_init(struct rvu *rvu, int blkaddr)
> > > num_pkinds = rvu->kpu.pkinds;
> > > num_pkinds = min_t(int, hw->npc_pkinds, num_pkinds);
> > >
> > > - for (idx = 0; idx < num_pkinds; idx++)
> > > - npc_config_kpmaction(rvu, blkaddr, &rvu->kpu.ikpu[idx],
> > > + /* Cn20k does not support Custom profile from filesystem */
> > > + for (idx = 0; idx < num_pkinds; idx++) {
> > > + act = npc_get_ikpu_nth_entry(rvu, idx);
> > > + if (!act)
> > > + continue;
> > > +
> > > + npc_config_kpmaction(rvu, blkaddr, act,
> > > 0, idx, true);
> > > + }
> >
> > When a filesystem-loaded profile is used on cn20k (from_fs=true),
> > npc_get_ikpu_nth_entry() returns &profile->ikpu2[idx]:
> >
> > drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c:npc_get_ikpu_nth_entry() {
> > struct npc_kpu_profile_adapter *profile = &rvu->kpu;
> >
> > if (profile->from_fs)
> > return &profile->ikpu2[idx];
> >
> > return &profile->ikpu[n];
> > }
> >
> > However, ikpu2 is never allocated for the cn20k path. The allocation
> > happens inside npc_apply_custom_kpu() in the from_fs block:
> >
> > drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c:npc_apply_custom_kpu() {
> > if (is_cn20k(rvu->pdev))
> > return npc_cn20k_apply_custom_kpu(rvu, profile);
> > ...
> > if (from_fs) {
> > profile->ikpu2 = devm_kcalloc(rvu->dev, 1,
> > sizeof(ikpu_action_entries),
> > GFP_KERNEL);
> > ...
> > }
> > }
> >
> > But this from_fs block is never reached for cn20k because the function
> > returns early via npc_cn20k_apply_custom_kpu().
> >
> > Does this code dereference a NULL-offset pointer causing a kernel crash?
> >
> > The same issue appears to affect cam2 and action2 pointers which are
> > allocated in npc_alloc_kpu_cam2_n_action2() but never called for cn20k.
> >
> > This was raised by Jakub Kicinski during v4 review on 2026-03-16.
> This is false positive. from_fs is never set to "true" for cn20k. In npc_load_kpu_profile(), if soc is
> cn20k, it skips "loading from filesystem" method. So npc_get_ikpu_nth_entry()
> will never return ikpu2.
>
> >
> > > diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
> > > index 8d260bcfbf38..8d8feb7742e8 100644
> > > --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
> > > +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
> >
> > [ ... ]
> >
> > > @@ -1730,32 +1835,89 @@ static int npc_apply_custom_kpu(struct rvu *rvu,
> > > return -EINVAL;
> > > }
> > >
> > > + *fw_kpus = fw->kpus;
> > > +
> > > + sfw = devm_kcalloc(rvu->dev, 1, sizeof(*sfw), GFP_KERNEL);
> > > + if (!sfw)
> > > + return -ENOMEM;
> > > +
> > > + memcpy(sfw, fw, sizeof(*sfw));
> > > +
> > > profile->custom = 1;
> > > - profile->name = fw->name;
> > > + profile->name = sfw->name;
> > > profile->version = le64_to_cpu(fw->version);
> > > - profile->mcam_kex_prfl.mkex = &fw->mkex;
> > > - profile->lt_def = &fw->lt_def;
> > > + profile->mcam_kex_prfl.mkex = &sfw->mkex;
> > > + profile->lt_def = &sfw->lt_def;
> > > +
> > > + /* Binary blob contains ikpu actions entries at start of data[0] */
> > > + if (from_fs) {
> > > + profile->ikpu2 = devm_kcalloc(rvu->dev, 1,
> > > + sizeof(ikpu_action_entries),
> > > + GFP_KERNEL);
> > > + if (!profile->ikpu2)
> > > + return -ENOMEM;
> > > +
> > > + action = (struct npc_kpu_profile_action *)(fw->data + offset);
> > > +
> > > + if (rvu->kpu_fwdata_sz < hdr_sz + sizeof(ikpu_action_entries))
> > > + return -ENOMEM;
> > > +
> > > + memcpy((void *)profile->ikpu2, action, sizeof(ikpu_action_entries));
> > > + offset += sizeof(ikpu_action_entries);
> > > + }
> > > +
> > > for (kpu = 0; kpu < fw->kpus; kpu++) {
> > > fw_kpu = (struct npc_kpu_fwdata *)(fw->data + offset);
> > > - if (fw_kpu->entries > KPU_MAX_CST_ENT)
> > > - dev_warn(rvu->dev,
> > > - "Too many custom entries on KPU%d: %d > %d\n",
> > > - kpu, fw_kpu->entries, KPU_MAX_CST_ENT);
> > > - entries = min(fw_kpu->entries, KPU_MAX_CST_ENT);
> > > - cam = (struct npc_kpu_profile_cam *)fw_kpu->data;
> > > - offset += sizeof(*fw_kpu) + fw_kpu->entries * sizeof(*cam);
> > > + if (!from_fs) {
> > > + if (fw_kpu->entries > KPU_MAX_CST_ENT)
> > > + dev_warn(rvu->dev,
> > > + "Too many custom entries on KPU%d: %d > %d\n",
> > > + kpu, fw_kpu->entries, KPU_MAX_CST_ENT);
> > > + entries = min(fw_kpu->entries, KPU_MAX_CST_ENT);
> > > + cam = (struct npc_kpu_profile_cam *)fw_kpu->data;
> > > + offset += sizeof(*fw_kpu) + fw_kpu->entries * sizeof(*cam);
> > > + action = (struct npc_kpu_profile_action *)(fw->data + offset);
> > > + offset += fw_kpu->entries * sizeof(*action);
> > > + if (rvu->kpu_fwdata_sz < hdr_sz + offset) {
> > > + dev_warn(rvu->dev,
> > > + "Profile size mismatch on KPU%i parsing.\n",
> > > + kpu + 1);
> > > + return -EINVAL;
> > > + }
> > > + for (entry = 0; entry < entries; entry++) {
> > > + profile->kpu[kpu].cam[entry] = cam[entry];
> > > + profile->kpu[kpu].action[entry] = action[entry];
> > > + }
> > > + continue;
> > > + }
> > > + entries = fw_kpu->entries;
> > > + dev_info(rvu->dev,
> > > + "Loading %u entries on KPU%d\n", entries, kpu);
> > > +
> > > + cam2 = (struct npc_kpu_profile_cam2 *)fw_kpu->data;
> > > + offset += sizeof(*fw_kpu) + fw_kpu->entries * sizeof(*cam2);
> > > action = (struct npc_kpu_profile_action *)(fw->data + offset);
> > > offset += fw_kpu->entries * sizeof(*action);
> > > if (rvu->kpu_fwdata_sz < hdr_sz + offset) {
> > > dev_warn(rvu->dev,
> > > - "Profile size mismatch on KPU%i parsing.\n",
> > > + "profile size mismatch on kpu%i parsing.\n",
> > > kpu + 1);
> > > return -EINVAL;
> > > }
> > > +
> > > + profile->kpu[kpu].cam_entries2 = entries;
> > > + profile->kpu[kpu].action_entries2 = entries;
> > > + ret = npc_alloc_kpu_cam2_n_action2(rvu, kpu, entries);
> > > + if (ret) {
> > > + dev_warn(rvu->dev,
> > > + "profile entry allocation failed for kpu=%d for %d entries\n",
> > > + kpu, entries);
> > > + return -EINVAL;
> > > + }
> > > +
> > > for (entry = 0; entry < entries; entry++) {
> > > - profile->kpu[kpu].cam[entry] = cam[entry];
> > > - profile->kpu[kpu].action[entry] = action[entry];
> > > + profile->kpu[kpu].cam2[entry] = cam2[entry];
> > > + profile->kpu[kpu].action2[entry] = action[entry];
> > > }
> > > }
> >
> > Could this loop overflow and hang the kernel if firmware specifies more
> > than 65535 entries?
> >
> > The loop variable 'entry' is declared as int at the top of the function,
> > but in the from_fs path, 'entries' is read directly from the firmware
> > blob:
> >
> > entries = fw_kpu->entries;
> >
> > In the non-from_fs path, entries is capped:
> >
> > entries = min(fw_kpu->entries, KPU_MAX_CST_ENT);
> >
> > But in the from_fs path there is no cap on entries. If a corrupt or
> > malicious firmware file specifies fw_kpu->entries with a very large
> > value, the loop could run for an extremely long time or cause other
> > issues.
> >
> > The kernel should not rely on filesystem data being well-formed.
> > Validation is needed for the from_fs case.
> >
> > This was raised by Simon Horman during v3 review on 2026-03-10. The
> > author acknowledged this in v3 saying they would fix it in v5, but the
> > issue persists in the current version.
> ACK. Last AI review suggest is - either to use "int" as loop variable or do a capping
> on the value. I did modify it to "int".
Hi Simon,
I noticed that this concern has been raised again even after adding "CAPPING"
https://netdev-ai.bots.linux.dev/ai-review.html?id=c0bd3eea-bbc4-46ae-86da-c295d1100233
"If a corrupt or malicious firmware file in /lib/firmware/kpu specifies fw_kpu->entries with a very
large value that doesn't exceed rvu->hw->npc_kpu_entries, the loop could run for an extremely long time."
I would like to clarify that the following line in the code effectively caps the value of entries,
even in cases where the value read from the filesystem may be corrupted:
entries = min(fw_kpu->entries, rvu->hw->npc_kpu_entries);
Here, rvu->hw->npc_kpu_entries is derived from hardware registers and represents the
maximum limit supported by the hardware.
Please let me know if you see any gaps in this reasoning or if further safeguards are required.
© 2016 - 2026 Red Hat, Inc.