The engine accepts the frame, and no completion path is armed¶
Measured on an XGS 3300, 2026-10-06, after the association belongs to an
interface. The result there reproduces -
FPCNTR_FROM_WIRE_TO_IPSEC_ENCR 13 to 18 on a second flow - and this page is about where those
frames go.
THIS PAGE'S TITLE IS WRONG. The engine accepts the frame and returns it: crypto_issued and
crypto_processed, read by name from the live process, are equal, and their totals account for every
frame this page measured. See the crypto path completes. The filename
is kept so existing links work; what is still true here is the dpdk log, the FP_PKT_DUMP negative
and the anatomy of crypto_pkt_submit.
Two further conclusions on this page were wrong, and both were corrected the same day by the same method that produced them. One is retracted in its own section below. The other is this page's title: the counter it rests on means classified for encryption, not accepted by the engine - its only increment site is in a function that never touches a crypto device. Both ends of the crypto path do exist and are found in both ends exist, which also says what is left.
The fast path has a log, and this is where it is¶
Nobody had read it. On the coprocessor, usfp's standard output and standard error are both one
pipe, and the process on the other end is:
logger -t dpdk -p user.info
So every line the fast path writes is in /var/log/messages on the coprocessor, tagged dpdk.
The format is the source file and line that wrote it:
info dpdk: usfp_main.c[997] launching worker 15 on core 17
That is a general instrument, not a crypto one, and it had been missing for the whole project.
And the coprocessor's kernel module logs IPsec events by name. Proven by accident, from this project's own writes:
error: ipsec_fpop_sa_del Attempted to free a valid SA, saidx=5
So usfp_rh.ko reports an IPsec refusal with the function and the index. If an association or a
submit were being refused inside the module, it would say so, in those words.
What that rules out¶
esp_pre_crypto contains six fp_log call sites. Through every trial in which frames were
accepted by the engine, not one of them fired. Six log sites in a 13 KB function are error paths;
none firing says the ESP preparation ran to its end.
So the frame is prepared. It is not handed to the hardware by this path, which a first reading of this page got wrong by reading half a function - see the anatomy below.
FP_PKT_DUMP, bit 4 of fw_cfg, produces no log output at all. It was set for one window with
frames going to the engine, and the dpdk log gained nothing. Worth recording so that it is not
tried again as a way to see inside the fast path.
What crypto_pkt_submit actually does, read to its end this time¶
An earlier version of this page said it "calls exactly two functions - the SA lookup and
esp_pre_crypto". It calls eight things. The function is 2,024 bytes - 506 instructions - and that
sentence was written from the first 258 of them. A truncated read of a function is not a reading of
it, and this is the second time today that reading too little produced a confident sentence.
In full, and in order of what it means rather than of address:
| what | how |
|---|---|
| the association | bl sadb_hw_entry_get |
| the ESP preparation | bl esp_pre_crypto |
| a lock | bl usfp_lock_acquire_slowpath |
| two crypto ops allocated | two indirect calls, both rte_mempool_get |
| the op initialised | strh #0x101 at +0, strb #1 at +2 of the allocated object |
| two error reports | bl fp_log |
| an assertion | bl __rte_panic |
The two indirect calls are the same thing twice, one single and one bulk. Both load a function pointer
from 0x745b00 + index * 128 at offset +0xb8, with the index bounded by cmp #0xf / b.hi - a
sixteen-entry table of 128-byte entries, which is rte_mempool_ops_table and its
RTE_MEMPOOL_MAX_OPS_IDX. The call takes (pool, &obj, n) and a non-zero return branches to the
failure path, which is rte_mempool_get's contract and not an enqueue's.
What follows the allocation says what the object is: 0x0101 into the first halfword and 1 into the
third byte is a rte_crypto_op with type = 1 symmetric, status = 1 not-processed and
sess_type = 1 with-session.
So this path builds the operation and does not submit it. esp_pre_crypto has no indirect call at
all, so it does not submit it either. Whatever hands the op to the crypto device is somewhere else -
and pmode_hwevt_worker_loop has 43 indirect call sites, which is where to look and is not read yet.
That is a coherent account of the measurement: a frame counted at FROM_WIRE_TO_IPSEC_ENCR has had
its operation built, and FPCNTR_RX_IPSEC staying at zero is what an operation that is never enqueued
looks like. It is an account, not a finding - nothing here has established that the enqueue is
missing rather than elsewhere.
The event crypto adapter is not used at all, and that is not evidence about the completion¶
This section replaces a wrong conclusion, drawn first and corrected an hour later. It said that
rte_event_crypto_adapter_queue_pair_add having no call site meant no completion path was armed.
The call count is a fact; the conclusion does not follow, and the reason it does not is worth more
than the claim was.
| symbol | direct call sites in usfp |
|---|---|
sadb_hw_entry_get |
3 |
esp_pre_crypto |
1 |
rte_event_crypto_adapter_caps_get |
2 |
rte_event_crypto_adapter_create_ext |
1 - from rte_event_crypto_adapter_create |
eca_crypto_adapter_run |
1 - from eca_service_func |
rte_event_crypto_adapter_create |
0 |
rte_event_crypto_adapter_start |
0 |
rte_event_crypto_adapter_stop |
0 |
rte_event_crypto_adapter_service_id_get |
0 |
rte_event_crypto_adapter_queue_pair_add |
0 |
The two non-zero rows in that family are the library calling itself. The application never touches the event crypto adapter, so nothing about it is half-configured - it is linked-in dead code, which a statically linked DPDK is full of.
And the path that is used cannot be counted this way: rte_cryptodev_enqueue_burst and
rte_cryptodev_dequeue_burst are static inline in DPDK's own header, so they are not functions in
this binary and a symbol search cannot see them. An absence of symbols says nothing about an inline
call. That is what the first reading got wrong.
What the search does give, and these are solid:
rte_cryptodev_sym_session_createis called fromsadb_hw_entry_get- the association lookup creates the crypto session lazily, which is why an association has to exist before a frame can be prepared.rte_cryptodev_configure,rte_cryptodev_queue_pair_setupandrte_cryptodev_starteach have exactly one call site, all insidefp_state_init, in a loop whose bound comes fromrte_cryptodev_count. So crypto is configured and started at startup - or skipped entirely and silently, if that count is zero.- the count is not zero for want of hardware. On the coprocessor there are two CPT physical
functions on the kernel's
octeontx-cptdriver and seventeen virtual functions, all seventeen bound tovfio-pci- available to userspace, which is what DPDK needs.usfp's command line carries no device allowlist, so they are probed.
Lesson¶
Two negatives were worth keeping here and one was worth retracting, which is the same lesson from both sides. Six log sites in the function under test, silent, says that function succeeded - no instrument on the host could have said so. But a symbol with no call site says a path does not exist only if that path would have been a symbol, and in a statically linked DPDK the hot paths are inline headers and the cold ones are dead library code. The first reading of that table had it exactly backwards: it treated a dead library function as a missing step, and could not see the live inline one at all.
When a stage neither refuses nor completes, go looking for its other end - and check whether the end you are looking for is the kind of thing your instrument can see.
Where this leaves the question¶
It has moved twice and the second move is the solid one. It was the engine refuses the frame before consulting the association; then the association's interface was wrong; and now the engine accepts the frame, the ESP preparation runs to its end, and nothing comes back.
What is not established is why. The crypto device is configured and started by fp_state_init, the
hardware is bound to vfio-pci and probed, and the enqueue and dequeue are inline calls this project
cannot find by symbol. So the next step is to read esp_pre_crypto and the worker loop as code rather
than as a symbol table, and to find whether anything polls the cryptodev for completions at all.
Lesson¶
Two negatives here were worth more than a positive would have been, and both came from asking what should have been noisy. Six log sites in the function under test, silent, says the function succeeded - no instrument on the host could have said that. A symbol with no call site and no reference anywhere says a path does not exist - which is a stronger statement than any measurement of a path that does. When a stage neither refuses nor completes, stop measuring the stage and go looking for its other end.
The discussion of this page is held in the project's GitHub Discussions. Showing it here loads giscus from giscus.app and GitHub, and writing in it needs a GitHub account - what that means.