|WARNING| net/bnxt: fix global table scope shutdown order

dpdklab at iol.unh.edu dpdklab at iol.unh.edu
Thu Oct 1 20:14:38 CEST 2026


Test-Label: ai-code-review
Test-Status: WARNING
https://dpdk.org/patch/170488

_AI Code Review_

=== Patch Review: series.patch (via Claude) ===
Review date: 2026-10-01

# DPDK Patch Review: net/bnxt: fix global table scope shutdown order

## Summary

This patch adds reference counting for GLOBAL-scope table resources and reorders teardown to prevent crashes and firmware errors during shutdown. The changes introduce tracking state (`tbl_scope_type`, `glb_tbl_scope_fid_cnt`) and modify both init and deinit paths.

---

## Errors

### 1. `ulp_tfc_tbl_scope_deinit`: resource leak on early error paths

**Error path analysis:**

- If `tsid_get` fails - goto cleanup - early return, but `have_scope = false` so no cleanup attempted
- If `fid_get` fails - goto cleanup - early return, but `have_scope = false` so no cleanup attempted
- If `acquire_fdb_lock` fails - proceeds with teardown using conservative `our_fid_cnt = 1`

The first two error paths skip all cleanup operations (cpm_free, fid_rem, mem_free) even though `tsid_get` or `fid_get` failure may occur *after* those resources were allocated. If the context was initialized but these getters fail due to corruption or invalid state, the resources remain allocated in firmware/hardware.

**Suggested fix:**

```c
/* At function entry, record whether context appears valid */
bool ctx_was_initialized = (tfcp != NULL && bp->ulp_ctx != NULL);

/* ... existing tsid_get and fid_get logic ... */

cleanup:
	if (!have_scope) {
		/* If context was initialized but we couldn't get tsid/fid,
		 * log the leak but don't attempt cleanup with invalid state */
		if (ctx_was_initialized)
			BNXT_DRV_DBG(WARNING,
				     "Context initialized but tsid/fid unavailable; cannot clean up scope resources");
		return;
	}
```

Alternatively, if `tsid_get`/`fid_get` failure always means "no resources to clean up", document that invariant in a comment so reviewers understand the early return is safe.

---

### 2. `ulp_tfc_tbl_scope_init`: rollback path resource leak

**Error:**

The rollback path calls three cleanup operations but ignores all their return codes (overwrites `rc` each time) and then returns generic `-1`. If any rollback operation fails, those failures are logged but the function does not communicate *which* cleanup succeeded/failed to the caller, making recovery or diagnostics difficult.

More critically, **each rollback step overwrites `rc` without checking it**, so if `cpm_free` fails but `mem_free` succeeds, the `cpm_free` failure is lost and the function returns `-1` (generic failure) with no indication that partial cleanup happened.

**Suggested fix:**

Track rollback failures separately:

```c
rollback:
	/* Rollback: only FID in scope (glb_tbl_scope_fid_cnt_inc never ran). */
	{
		int rollback_rc;
		bool rollback_failed = false;

		rollback_rc = tfc_tbl_scope_cpm_free(tfcp, tsid);
		if (rollback_rc) {
			BNXT_DRV_DBG(ERR, "Rollback: cpm_free failed TSID:%d FID:%d rc=%d",
				     tsid, fid, rollback_rc);
			rollback_failed = true;
		}
		rollback_rc = tfc_tbl_scope_mem_free(tfcp, fid, tsid, 0);
		if (rollback_rc) {
			BNXT_DRV_DBG(ERR, "Rollback: mem_free failed TSID:%d FID:%d rc=%d",
				     tsid, fid, rollback_rc);
			rollback_failed = true;
		}
		rollback_rc = tfc_tbl_scope_fid_rem(tfcp, fid, tsid, NULL);
		if (rollback_rc) {
			BNXT_DRV_DBG(ERR, "Rollback: fid_rem failed TSID:%d FID:%d rc=%d",
				     tsid, fid, rollback_rc);
			rollback_failed = true;
		}

		if (rollback_failed)
			BNXT_DRV_DBG(ERR, "Rollback incomplete: some resources may be leaked TSID:%d FID:%d",
				     tsid, fid);
	}
	return -EAGAIN; /* or other meaningful code */
```

---

### 3. `ulp_tfc_tbl_scope_deinit`: double cleanup attempt if lock acquisition fails

**Error:**

When `acquire_fdb_lock` fails, the code sets `our_fid_cnt = 1` (conservative) and proceeds to cleanup. However, the lock failure may indicate the lock is corrupted or the context is in an inconsistent state. Proceeding with cleanup operations (cpm_free, fid_rem, mem_free) without the lock could race with another thread that successfully acquired the lock and is also performing cleanup, leading to double-free or use-after-free if both threads call `mem_free` on the same tsid.

**Suggested fix:**

If lock acquisition fails, either:
- Skip cleanup entirely and return early (accepting the resource leak but avoiding double-free), or
- Retry lock acquisition a few times before proceeding, and if retries fail, log a critical error and skip cleanup:

```c
if (bnxt_ulp_cntxt_acquire_fdb_lock(bp->ulp_ctx)) {
	BNXT_DRV_DBG(ERR,
		     "acquire_fdb_lock failed; cannot safely perform teardown (concurrent cleanup may be in progress)");
	/* Do not proceed without lock; cleanup may be unsafe */
	return;
}
```

---

### 4. `ulp_tfc_tbl_scope_init`: lock held across blocking operations

**Error:**

The lock acquired via `acquire_fdb_lock` is held while calling `glb_tbl_scope_fid_cnt_inc` and `tbl_scope_type_set`. If these operations block (e.g., due to internal locking or I/O), or if an error in `fid_cnt_inc` causes an early return before `release_fdb_lock`, the lock is held longer than necessary or leaked.

Additionally, if `fid_cnt_inc` fails, the code does **not** goto `rollback` -- it returns immediately after releasing the lock, leaving the CPM allocated (via `tfc_tbl_scope_cpm_alloc`) but never adding the FID to the reference count. This is a **resource leak**: the CPM is allocated but the error path does not clean it up.

**Suggested fix:**

```c
if (bnxt_ulp_cntxt_acquire_fdb_lock(bp->ulp_ctx)) {
	BNXT_DRV_DBG(ERR, "acquire_fdb_lock failed after CPM alloc");
	goto rollback;
}
if (scope_type == CFA_SCOPE_TYPE_GLOBAL) {
	rc = bnxt_ulp_cntxt_glb_tbl_scope_fid_cnt_inc(bp->ulp_ctx);
	if (rc) {
		BNXT_DRV_DBG(ERR, "Failed to increment glb_tbl_scope_fid_cnt");
		bnxt_ulp_cntxt_release_fdb_lock(bp->ulp_ctx);
		goto rollback;  /* MUST rollback CPM allocation */
	}
}
rc = bnxt_ulp_cntxt_tbl_scope_type_set(bp->ulp_ctx, scope_type);
if (rc) {
	/* Decrement count we just incremented before releasing lock */
	if (scope_type == CFA_SCOPE_TYPE_GLOBAL)
		bnxt_ulp_cntxt_glb_tbl_scope_fid_cnt_dec(bp->ulp_ctx);
	bnxt_ulp_cntxt_release_fdb_lock(bp->ulp_ctx);
	goto rollback;
}
bnxt_ulp_cntxt_release_fdb_lock(bp->ulp_ctx);
```

---

### 5. `bnxt_ulp_cntxt_glb_tbl_scope_fid_cnt_dec`: underflow not prevented, only detected

**Error:**

The function checks if `fid_cnt == 0` and returns `-EINVAL`, but this is **after** the damage is done if the check is bypassed or if concurrent decrements race. The current check prevents decrement-past-zero only when called correctly, but does not use atomics, so two threads could both read `fid_cnt=1`, both pass the check, and both decrement, leaving `fid_cnt` at `-1` (underflow).

Additionally, the `-EINVAL` return is checked only in one place (`ulp_tfc_tbl_scope_deinit`), where the code logs a warning and continues. If the count is already zero, `dec` should not be called at all -- this is a logic error in the caller, and continuing with `our_fid_cnt = 1` is a workaround that masks the bug.

**Suggested fix:**

If the reference count is meant to be protected by `acquire_fdb_lock`, document that lock must be held when calling `_dec` and `_inc`. If concurrent access is possible, use atomics:

```c
int32_t
bnxt_ulp_cntxt_glb_tbl_scope_fid_cnt_dec(struct bnxt_ulp_context *ulp_ctx)
{
	uint16_t old_val;
	if (ulp_ctx == NULL || ulp_ctx->cfg_data == NULL)
		return -EINVAL;
	old_val = rte_atomic_load_explicit(&ulp_ctx->cfg_data->glb_tbl_scope_fid_cnt,
					   rte_memory_order_acquire);
	if (old_val == 0)
		return -EINVAL;
	rte_atomic_fetch_sub_explicit(&ulp_ctx->cfg_data->glb_tbl_scope_fid_cnt, 1,
				      rte_memory_order_acq_rel);
	return 0;
}
```

Or, if the lock is always held, add an assertion:

```c
/* REQUIRES: caller holds acquire_fdb_lock */
if (ulp_ctx->cfg_data->glb_tbl_scope_fid_cnt == 0) {
	/* BUG: caller logic error - count already zero */
	RTE_ASSERT(0);
	return -EINVAL;
}
ulp_ctx->cfg_data->glb_tbl_scope_fid_cnt--;
```

---

## Warnings

### 1. `ulp_tfc_tbl_scope_deinit`: log level inconsistency

**Warning:**

`fid_get` failure logs `ERR`, but `acquire_fdb_lock` failure logs `ERR` and proceeds with teardown. The lock failure is treated as non-fatal (code continues), but `fid_get` failure is treated as fatal (early return). This inconsistency is confusing: if lock failure is recoverable, why not `fid_get` failure?

**Suggested fix:**

Ensure log levels match the code path. If lock failure is truly an error but the code proceeds with a conservative fallback, log it as `WARNING` instead of `ERR`:

```c
BNXT_DRV_DBG(WARNING, "acquire_fdb_lock failed, proceeding with teardown using conservative fid_cnt");
```

Or, if lock failure should prevent teardown, make it an `ERR` and `goto cleanup;`.

---

### 2. Missing release notes update

The commit message references `Fixes:` and `Cc: stable at dpdk.org`, indicating this is a bug fix for a previously released feature. However, there is no update to `doc/guides/rel_notes/` documenting the fix. While stable backports may not always require release notes in the current release, significant fixes that affect production stability (crash prevention) should be documented.

**Suggested fix:**

Add a "Fixed Issues" entry in the current release notes (e.g., `doc/guides/rel_notes/release_25_03.rst`):

```rst
* **net/bnxt: Fixed crash during global table scope teardown.**

  Corrected the shutdown order for GLOBAL table scopes to prevent crashes
  and firmware errors when unloading the driver with active DPDK ports.
```

---

### 3. `ulp_tfc_tbl_scope_init`: return code `-1` instead of meaningful errno

**Warning:**

On rollback failure, the function returns `-1` (generic failure). DPDK convention prefers specific negative errno values (`-EAGAIN`, `-EIO`, `-ENOMEM`, etc.) to help callers diagnose the failure.

**Suggested fix:**

```c
return -EAGAIN; /* or -EIO if hardware/firmware error */
```

---

### 4. New global state not zeroed on context allocation

**Warning:**

The patch adds `tbl_scope_type` and `glb_tbl_scope_fid_cnt` to `struct bnxt_ulp_data` but does not show initialization to known values (zero / `CFA_SCOPE_TYPE_INVALID`) at context creation. If the context memory is not zeroed, these fields may contain garbage, leading to incorrect ref-count logic or scope-type checks.

**Suggested fix:**

Verify that `bnxt_ulp_data` is allocated with `rte_zmalloc` or explicitly zeroed. If this patch is part of a series where initialization is added in a later patch, disregard this warning.

---

## Info

### 1. Comment style: code is well-documented but could benefit from function-level Doxygen

**Info:**

The new helper functions (`tbl_scope_type_get`, `fid_cnt_inc`, etc.) in `bnxt_ulp_tfc.h` are declared but lack Doxygen comments explaining their purpose, parameters, and return values. While internal APIs do not strictly require Doxygen, adding brief comments would improve maintainability, especially for ref-count increment/decrement which have specific lock requirements.

**Suggested addition:**

```c
/**
 * Get the table scope type for the ULP context.
 *
 * @param ulp_ctx ULP context pointer.
 * @param scope_type Pointer to store the scope type.
 * @return 0 on success, -EINVAL if parameters are NULL.
 */
int32_t
bnxt_ulp_cntxt_tbl_scope_type_get(struct bnxt_ulp_context *ulp_ctx,
				  enum cfa_scope_type *scope_type);
```

---

### 2. Consider adding a state machine diagram in commit message

**Info:**

The commit message explains the teardown order change (`cpm_free -> fid_rem -> mem_free`) but a visual diagram showing state transitions during init and deinit would help future reviewers understand the fix. This is optional but would be valuable given the complexity.

---

## Conclusion

**Critical issues that must be fixed before merge:**
- Error #1: Resource leak on early error paths in deinit
- Error #2: Rollback path ignores partial cleanup failures
- Error #3: Double cleanup risk if lock acquisition fails
- Error #4: Lock held during blocking ops; missing rollback on fid_cnt_inc failure
- Error #5: Reference count underflow not prevented atomically

**Should fix:**
- Warning #1: Log level inconsistency
- Warning #2: Missing release notes
- Warning #3: Return `-1` instead of specific errno
- Warning #4: Global state initialization not shown

**Consider:**
- Info #1: Add Doxygen comments
- Info #2: Add state diagram to commit message

The patch addresses a real correctness bug (crash during teardown), but the error handling in both init and deinit paths introduces new potential bugs (leaks, double-cleanup, race conditions) that must be resolved.


More information about the test-report mailing list