Skip to content

Host QoS with mlnx_qos: trust state, prio2tc, ETS

S2·E2Two identical nodes that are not identical · Building D, day two of the bring-up, four days out

S2·E2Apply~30 minsources checked todaylab mutates hardwareverified against DOCA-Host Ethernet QoS page re-fetched 2026-09-07 (option table confirmed to contain neither --buffer_size nor --prio2buffer), Ethernet QoS generated PDF, MLNX_OFED 23.10 LTS QoS page, DOCA QoS Configuration page, Cumulus Linux 5.18 RoCE page

Builds on: ToS, DSCP, PCP, SL: where marking gets lost

Before you read: what do you already know?

3 quick questions. Wrong answers are fine and expected; trying first makes the lesson stick.

After this lesson you can

  • Read a full mlnx_qos output block and state the trust state the DSCP-to-priority map the PFC row and the scheduling algorithm of every traffic class.
  • Apply mlnx_qos to move a host from the default PCP trust to DSCP trust with a chosen dscp2prio entry and roll the change back.
  • Choose between strict ets and vendor for a traffic class and justify the choice using NVIDIA's stated starvation and minimum-bandwidth semantics.
  • Identify when mlnx_qos is the wrong tool - BlueField Embedded Host mode and DPU rate limiting - and name what replaces it.

Episode 2 — Two identical nodes that are not identical

The situation · Building D, day two of the bring-up, four days out

Day two of the bring-up, four days out. Two PowerEdge nodes off the same order, same firmware, same cabling, and an ib_write_bw run clean one way and half rate the other. The Dell SE has spent the morning swapping optics and added a spreadsheet row for them. The night-shift operator has arrived early with a label maker and an offer to relabel the rack, which is the second-best idea in the room.

You stop the cable hunt and print the whole host QoS state at once. mlnx_qos is documented as a centralized tool that talks directly to the driver and needs no DCBX daemon, which is why it belongs first on any RoCE ticket.[1] Node A reports Priority trust state: dscp. Node B reports pcp — the default you get by not choosing, since NVIDIA states the default trust state is PCP.[1] Nothing about the fabric is wrong: the two ends of one flow disagree about which field on the frame is allowed to mean anything.

That is why trust is explicit rather than inferred. A tagged frame carries both PCP and DSCP and nothing forces them to agree; an untagged frame carries only DSCP.[1] Making it explicit is what makes it auditable in one line — and the same output carries the dscp2prio table, the PFC row and every class’s scheduler beside it.[1]

Two hosts that disagree about trust produce symptoms that look exactly like a broken fabric. The operator labels node B while you save the before state, because there is no undo. Read it block by block.

1One command, four blocks, and the line that decides everything

mlnx_qos is “a centralized tool used to configure QoS features of the local host. It communicates directly with the driver thus does not require setting up a DCBX daemon on the system.”[1] Its syntax is mlnx_qos -i <interface> [options], and run with no options it prints the host’s entire QoS state — which is why it is the first command on any RoCE ticket.[1][2]

The canonical output has four blocks. NVIDIA’s own sample, abridged:[1][8]

DCBX mode: OS controlled
Priority trust state: dscp
dscp2prio mapping:
        prio:0 dscp:07,06,05,04,03,02,01,00,
        prio:1 dscp:15,14,13,12,11,10,09,08,
        prio:2 dscp:23,22,21,20,19,18,17,16,
        prio:3 dscp:31,30,29,28,27,26,25,24,
        prio:4 dscp:39,38,37,36,35,34,33,32,
        prio:5 dscp:47,46,45,44,43,42,41,40,
        prio:6 dscp:55,54,53,52,51,50,49,48,
        prio:7 dscp:63,62,61,60,59,58,57,56,
Cable len: 7
PFC configuration:
        priority    0   1   2   3   4   5   6   7
        enabled     0   0   0   0   0   0   0   0
tc: 0 ratelimit: unlimited, tsa: vendor

Read them in this order. DCBX mode says who owns the configuration, firmware or the OS.[1] Priority trust state says which packet field the NIC prioritizes on, and it is the line that decides whether an application’s DSCP has any effect at all — “The default trust state is PCP. Ethernet packets are prioritized based on the value of the field (PCP/DSCP).”[1] The dscp2prio mapping block is the DSCP-to-priority table. The PFC configuration row and the eight tc: lines say what can be paused and how each class is scheduled.

The default map deserves a second look because operators keep rebuilding it by hand. It groups DSCP in eights, so DSCP 24-31 already lands on priority 3 and DSCP 48-55 on priority 6, with no --dscp2prio at all.[1] That is exactly the split the switch side uses: Cumulus defaults RoCE data to DSCP 26 on switch priority 3 and CNP to DSCP 48 on switch priority 6.[5] Host and switch agree out of the box on everything except the trust state.

DSCP
ToS 104 · SL 3 · PCP 3 · prio 3NVUE default for RoCE data → switch priority 3
wire order — first byte on the wire at the topEthernet MAC headerethertype = IPv4 / IPv614 BIPv4 headerDSCP 26 · ECN bits 6–720 BDSCP (6 bits) + ECN (2 bits)UDP headersport = flow hash · dport 47918 BBTHopcode · dest QP · PSN · FECN/BECN12 BRDMA payloadpath MTU 256…4096 (RC/UC)≤ MTUICRCrx_icrc_encapsulated4 BEthernet FCSrecomputed at every hop4 B
step 1 / 6marking lost
marking chain · RoCEv2 · trust pcp

1. App sets a ToS

Carried by: nothing on the wire yet — this lives in the address vector

RDMA-CM apps set it per connection with rdma_set_option(..., RDMA_OPTION_ID_TOS, value); "If a TOS is not set, the default value (0) is used." The CM default can also be moved without touching code: cma_roce_tos -d <ib_device> -t <TOS>, or the RDMA-CM configfs entry. An app that calls rdma_set_option still overrides both.

ib_write_bw -T 104 -R        # CM path
ib_write_bw --tclass=104      # AV path, no CM
# no code change at all:
echo "tclass=104" > /sys/class/infiniband/mlx5_0/tc/1/traffic_class
echo "tclass=16,src_ip=1.1.1.2,dst_ip=1.1.1.0/24" > /sys/class/infiniband/mlx5_0/tc/1/traffic_class
echo "tclass=-1" > /sys/class/infiniband/mlx5_0/tc/1/traffic_class   # delete
FAE angle

Failure mode #1 of three: the app never set a ToS, so the traffic is DSCP 0 / priority 0 no matter how good the switch config is. Force DSCP via .../tc/<port>/traffic_class proves it in one command — global wins over rules, negative values disable, valid range 0–255. The sibling file is ttl.

Verdict for this frame

trust pcp with an untagged frame: there is no PCP on the wire, so every packet is treated as priority 0 — including the DSCP you set.

Counter that proves it: ethtool -S <netdev> | grep prio — traffic sits in prio0 while your PFC priority stays idle.

Arithmetic for DSCP 26
ToS = DSCP × 4 = 104 · SL = ToS >> 5 = 3 · PCP = SL & 7 = 3 (no VLAN tag → not on the wire)
default dscp2prio → priority 3 · sk_prio: n/a — ToS 104 is not one of the four table values (0, 8, 16, 24)
DOCA — Ethernet QoS · Cumulus — RoCE defaults

⚠ marks a number no fetched NVIDIA or IETF page states: the GRH, BTH and ICRC byte sizes (the IBTA spec is members-only) and whether a switch classifies RoCEv1 on the GRH traffic-class byte.

Start in the default trust state - pcp - on an untagged frame, then flip trust to dscp. The frame does not change; what the device is willing to read does.

2The option surface, exactly as documented

Every flag below is quoted from the Ethernet QoS option table.[1] -f LIST, --pfc=LIST: “Set priority flow control for each priority. LIST is a comma separated value for each priority starting from 0 to 7. Example: 0,0,0,0,1,1,1,1 enable PFC on TC4-7”.[1] -p LIST, --prio_tc=LIST: “Maps UPs to TCs. LIST is 8 comma-separated TC numbers. Example: 0,0,0,0,1,1,1,1 maps UPs 0-3 to TC0, and UPs 4-7 to TC1”.[1] -s LIST, --tsa=LIST takes strict, ets or vendor per class.[1] -t LIST, --tcbw=LIST sets minimum guaranteed percentages that “must sum to 100”, and values on non-ETS classes “are ignored but must be present”.[1] -r LIST, --ratelimit=LIST is Gbps per class, where “For an unlimited ratelimit, set the ratelimit to 0”, with a documented tolerance of roughly 10 percent.[1] --trust=TRUST is “set priority trust state to pcp or dscp”; --dscp2prio=DSCP2PRIO takes 'set,30,2' to map DSCP 30 to priority 2 and 'del,30,2' to reset it; --cable_len=CABLE_LEN sets “cable_len for buffer’s xoff and xon thresholds”; -d DCBX chooses fw or os.[1]

Two semantics matter more than the syntax. Strict priority: “TC 7 is the highest priority, TC 0 is lowest”, strict classes also outrank all ETS classes, and “This property needs to be used with care, as it may easily cause starvation of other TCs.”[1] ETS is the opposite kind of promise — it “exploits the time periods in which the offered load of a particular Traffic Class (TC) is less than its minimum allocated bandwidth by allowing the difference to be available to other traffic classes”, so the percentages are floors, not ceilings.[1] That is the pairing the switch side uses too: CNP strict, RoCE and default traffic at 50 percent each under weighted round robin.[5] vendor is the third legal value and the one NVIDIA’s own sample output shows on every class, but the option table names it without defining its scheduling behaviour, re-checked 2026-09-07.[1][2] Read a tsa: vendor line as “driver default, not a decision anyone made”, and if the class matters, set the algorithm you intend explicitly with -s.

3Applying it: the DSCP-trust host recipe and its rollback

The host half of a DSCP-based lossless RoCE deployment is three settings: trust DSCP, map the fabric’s RoCE DSCP to its priority, and enable PFC on that priority.[1][5] A Juniper validated design that configures NVIDIA hosts uses exactly this shape, mlnx_qos -i <interface> --pfc <0/1>,<0/1>,... followed by a bare mlnx_qos -i <interface> to verify.[7]

mlnx_qos -i <if> > /tmp/qos-before.txt   # this file is your rollback plan
mlnx_qos -i <if> --trust dscp
mlnx_qos -i <if> --dscp2prio set,26,3
mlnx_qos -i <if> --pfc 0,0,0,1,0,0,0,0
mlnx_qos -i <if>                          # diff against /tmp/qos-before.txt

Two cautions about that block. First, the exact three-line combination is widely circulated but the NVIDIA knowledge-base article carrying it as a canonical recipe could not be fetched for this course, so treat the strings as assembled from the documented option table rather than as a quoted NVIDIA recipe; the DSCP 26 to priority 3 default underneath it is confirmed.[5][1] Second, --dscp2prio set,26,3 is redundant on a stock host, because the default map already puts 24-31 on priority 3 — it is worth running anyway when a previous operator may have edited the map, and worth checking rather than assuming.[1]

Ordering has one hard rule: “Setting the Trust State mode shall be done before enabling SR-IOV in order to propagate the Trust State to the VFs.”[1] On a host that is already running VFs, changing trust on the PF does not retroactively fix them, which is why this step belongs in provisioning and not in a live-troubleshooting session. NVIDIA’s own Spectrum-X host profile encodes the same decisions as declarative fields — Trust: dscp, ToS 96, and a PFC bitmap of "00010000" — so the recipe above is the manual form of a profile the NIC Configuration Operator applies for you.[6]

◐ Level 2 — limited (same annual cycle)
  • Profile doca-all is "other profiles" in the matrix → Level 2. Every component is in cycle 25 (Oct 2025 → Jul 2026 (3.2 → 3.5)) → supported until the next October GA.
  • This is exactly the Spectrum-X validated stack v2.3.1 combination (Sep 2026).
  • Any FW or mode change on PowerEdge needs a full power cycle, not a warm reboot (Dell KB 000300192; NVIDIA modes page).

Matrix (policy): doca-ofed ↔ FW/BF-FW-Bundle = L1 · doca-ofed ↔ BF-Bundle = L1 · other profiles ↔ FW or BF-Bundle = L2 · DOCA-DPU ↔ BF-FW-Bundle = L2 · DOCA Services ↔ BF-Bundle/FW = L2. source ↗

⚠ = not confirmed on a fetched primary source (hover for why). Facts as of DOCA 3.5.0 (Sep 2026). Selections are saved.

Before you trust any flag list, pin the stack. mlnx_qos ships with the host driver package, so its option surface moves with the DOCA-Host or MLNX_OFED release on the machine - which is why the lab step below records mlnx_qos --help verbatim.

4Where mlnx_qos is the wrong tool

On BlueField the tool has a documented trap: “When working in Embedded Host mode, using mlnx_qos on both the host and Arm will result with undefined behavior. Users must only use mlnx_qos from the Arm.”[4] This is not a warning about conflicting values; it is a statement that the result is undefined. On a Dell PowerEdge with a BlueField-3 in DPU mode, the QoS conversation belongs to the Arm side, and a customer’s x86 runbook that sets host QoS needs to be rewritten before it is run, not after it misbehaves.

Rate limiting on the DPU is a different subsystem entirely. DOCA documents devlink port function rate add|del|set|show with tx_max and tx_share, plus set parent for grouping functions into a shared rate group — not mlnx_qos -r.[4] Reaching for mlnx_qos to rate-limit a DPU function is a category error that will silently do nothing useful.

The third boundary is the switch. Everything in this lesson stops at the NIC: user priorities, traffic classes and PFC enables on the host. The switch has its own vocabulary — switch priority, traffic class, buffer pool — and its own defaults, and the two only meet on the wire through DSCP or PCP.[5] When a customer says “we set priority 3”, always ask which side of the wire they set it on.

Move a host to DSCP trust and prove it, with a rollback that works

Context: a Dell-lab ConnectX host, ens1f0, that a customer wants moved to a DSCP-trust lossless-RoCE posture on priority 3. Nothing here touches firmware.

  1. Capture the before state. This file is the rollback plan; mlnx_qos has no undo:[1]
    ibdev2netdev
    mlnx_qos -i ens1f0 | tee /tmp/qos-before.txt
    mlnx_qos --help | tee /tmp/qos-help.txt
    Expected: a Priority trust state: line (probably pcp, the default), the eight-line dscp2prio block, an all-zero PFC row and eight tc: lines.[1] The help capture settles for this exact driver version whether --buffer_size and --prio2buffer exist, which the doc table does not answer.[2]
  2. Confirm the fabric’s intent before changing the host. If the switch side is Cumulus at defaults, RoCE is DSCP 26 on switch priority 3 and CNP is DSCP 48 on switch priority 6.[5] Write those two numbers down; they are the contract the host must match.
  3. Change trust state:
    mlnx_qos -i ens1f0 --trust dscp
    Expected: Priority trust state: dscp on the next read. If this host will later run SR-IOV, note that VFs inherit trust at creation, so this must happen before VFs are enabled.[1]
  4. Make the DSCP mapping explicit rather than assumed:
    mlnx_qos -i ens1f0 --dscp2prio set,26,3
    Expected: prio:3 now lists 26 among its DSCP values. On a stock map it already did — the value of running it is that it survives a host where someone edited the map.[1]
  5. Enable PFC on priority 3 only:
    mlnx_qos -i ens1f0 --pfc 0,0,0,1,0,0,0,0
    Expected: the PFC row now reads enabled 0 0 0 1 0 0 0 0.[1] Remember that this is one endpoint of a hop-by-hop mechanism: PFC “must be enabled on all endpoints and switches in the flow path”.[9]
  6. Verify by diff, not by eye:
    mlnx_qos -i ens1f0 > /tmp/qos-after.txt
    diff /tmp/qos-before.txt /tmp/qos-after.txt
    Expected: exactly three changed regions — trust state, the priority-3 DSCP line if it moved, and the PFC row. Any fourth change is something you did not intend.
  7. Rollback if needed: re-apply the values read from /tmp/qos-before.txt--trust pcp, --dscp2prio del,26,3 if you added an entry that was not there, and --pfc 0,0,0,0,0,0,0,0 if PFC was previously all zero.[1]

Episode 2 — Before the noon call

How it ended

You save mlnx_qos -i <if> to a file, move node B to DSCP trust, confirm DSCP 26 still lands on priority 3 in the default map, enable PFC on priority 3, and diff against the saved output.[1] The reverse direction comes up to line rate and the label on node B is finally true. You flag the provisioning order too: trust must be set before SR-IOV is enabled, or the VFs keep the old state while every command on the host reports the right answer.[1] Then the network lead runs the whole pod instead of one pair — clean at eight nodes, a third slower at sixty-four — and asks whether PFC is really on every hop, which is the only place it counts.[9]

Lab

Mutating steps ahead. Needs a maintenance window, out-of-band access (BMC/iDRAC/rshim console) and a rollback path. Record the pre-flight inventory before changing anything. Never on a production host.

Goal: on the Dell-lab ConnectX or BlueField-3 host, capture the real option surface and perform one reversible trust change. Steps 1-2 are read-only; steps 3-5 are mutating and each names its rollback.

  1. Pre-flight inventory, read-only:
    ibdev2netdev
    mlnx_qos -i <if> | tee /tmp/qos-before.txt
    mlnx_qos --help | tee /tmp/qos-help.txt
    mlnx_qos -i <if> -a | head -40
    Expected: a full four-block output plus a per-TC listing. Record whether --buffer_size and --prio2buffer appear in the help output; the documentation table does not list them, so this capture is the authoritative answer for this driver version.[2][1]
  2. BlueField check. If the device is a BlueField-3 in Embedded Host mode, stop and move to the Arm console before any write: running mlnx_qos from both sides is documented as undefined behavior.[4] On the Arm, repeat step 1 there.
  3. Mutating - trust state. Rollback: mlnx_qos -i <if> --trust pcp if the before file said pcp.[1]
    mlnx_qos -i <if> --trust dscp
    mlnx_qos -i <if> | grep -i "trust"
    Expected: Priority trust state: dscp.
  4. Mutating - DSCP map entry. Rollback: mlnx_qos -i <if> --dscp2prio del,26,3, which NVIDIA documents as resetting that DSCP back to the default priority-0 entry - so only run the rollback if the entry was not already present in your before file.[1]
    mlnx_qos -i <if> --dscp2prio set,26,3
    mlnx_qos -i <if> | sed -n "/dscp2prio/,/Cable/p"
  5. Mutating - PFC on priority 3. Rollback: re-apply the PFC vector from /tmp/qos-before.txt, normally --pfc 0,0,0,0,0,0,0,0.[1]
    mlnx_qos -i <if> --pfc 0,0,0,1,0,0,0,0
    mlnx_qos -i <if> | sed -n "/PFC configuration/,+3p"
    Expected: enabled 0 0 0 1 0 0 0 0. Do not claim the path is lossless from this - PFC must be enabled on every endpoint and switch in the path.[9]
  6. Verify and close out:
    mlnx_qos -i <if> > /tmp/qos-after.txt && diff /tmp/qos-before.txt /tmp/qos-after.txt
    Then perform every rollback in reverse order and confirm the diff is empty. Attach /tmp/qos-help.txt to the module notes - it is the answer to an open question the vendor documentation does not resolve.[2]

Retrieval check

10 questions from memory. Answer before looking anything up; misses become flashcards.

Explain it to a Dell SE

Explain to a Dell SE, in five sentences, what the four blocks of mlnx_qos output mean and which single line decides whether the customer's DSCP marking has any effect on this host.

14 flashcards for this lesson — 0 in deck. Spaced review lives at /review.

Sources

Facts in this lesson were checked against DOCA-Host Ethernet QoS page re-fetched 2026-09-07 (option table confirmed to contain neither --buffer_size nor --prio2buffer), Ethernet QoS generated PDF, MLNX_OFED 23.10 LTS QoS page, DOCA QoS Configuration page, Cumulus Linux 5.18 RoCE page. Dates are when each page was fetched.

  1. Ethernet QoS (DOCA-Host) - generated PDF · fetched 2026-09-07 · DOCA 3.5.0
  2. Ethernet QoS (DOCA-Host, current HTML) · fetched 2026-09-07 · DOCA 3.5.0
  3. Quality of Service (QoS) - MLNX_OFED 23.10-2.1.3.1 LTS · fetched 2026-09-07
  4. QoS Configuration (DOCA SDK, BlueField) · fetched 2026-09-07 · DOCA 3.5.0
  5. RDMA over Converged Ethernet - RoCE (Cumulus Linux 5.18) · fetched 2026-09-07
  6. Spectrum-X Configuration - NVIDIA NIC Configuration Operator · fetched 2026-09-07
  7. NVIDIA Configuration - Juniper Validated Design (AI DC with NVIDIA and WEKA) · fetched 2026-09-07
  8. Quality of Service (QoS) - MLNX_OFED 5.9-0.5.6.0 · fetched 2026-09-07
  9. RDMA over Converged Ethernet (DOCA-Host) - generated PDF · fetched 2026-09-07 · DOCA 3.5.0

The same idea elsewhere

Other lessons that cover this ground, sometimes from another course's angle.