Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when you need to add, remove, edit, list, or change the boot default of an nvfancontrol fan profile on a Jetson/Tegra (Orin, Thor) target. Triggers: edit fan profile, tune fan curve.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 139% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 187% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 120% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 159% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 205% | 0% |
Edit the per-board nvfancontrol configuration so the device boots with the desired fan curve / control mode / governor / default profile. BSP-side only — all writes land in the overlay tracker, the upstream bsp_image copy is read-only.
This skill handles BSP-side edits to the per-board nvfancontrol configuration file: adding profiles, removing profiles, editing the temp → PWM / RPM curves, changing the boot default profile / control mode / governor, and listing defined profiles. Applies on Jetson / Tegra platforms (T234 Orin, T264 Thor).
POLLING_INTERVAL <seconds>
<FAN <index>>
TMARGIN <ENABLED|DISABLED>
FAN_GOVERNOR <type> {
STEP_SIZE <int>
}
FAN_CONTROL <close_loop|open_loop> {
RPM_TOLERANCE <rpm>
}
FAN_PROFILE <name> {
# TEMP HYST PWM RPM
<T0> <H0> <P0> <R0>
...
}
FAN_PROFILE <name> { ... } # one or more profiles
THERMAL_GROUP <id> {
GROUP_MAX_TEMP <C>
# zone-name <coeffs csv> <max-temp>
<zone> <coeffs> <max-temp>
...
}
FAN_DEFAULT_CONTROL <close_loop|open_loop>
FAN_DEFAULT_PROFILE <name>
FAN_DEFAULT_GOVERNOR <type>
KICKSTART_PWM <0..255>Rules:
TEMP HYST PWM RPM. Sort points by ascending TEMP; the daemon interpolates between them.PWM is 0..255 (8-bit duty cycle); RPM is the close-loop target speed. A trailing 0 0 row at the high end pins the fan off above GROUP_MAX_TEMP.HYST is hysteresis (°C) at that point — the controller waits HYST degrees of cooling before stepping the curve down.FAN_DEFAULT_PROFILE must reference an existing FAN_PROFILE block in the same <FAN N>. nvfancontrol fails to start if the default names a missing profile.FAN_DEFAULT_CONTROL = close_loop (drives toward target RPM, requires tach) or open_loop (writes PWM directly).FAN_DEFAULT_GOVERNOR = cont (continuous interpolation) and other family-specific values; copy verbatim from existing per-board files when introducing one.THERMAL_GROUP maps thermal zones to the controller's input. Coefficients are a 20-element CSV — copy verbatim from existing entries; values vary by chip family and zone.<FAN N> is one block per fan index (typical: <FAN 1>). Block boundaries <...> and {...} are strict; preserve indentation matching neighboring lines.Resolve the active profile per ../../context/target-platform-contract.md. Refuse and route in these cases:
| Condition | Refuse with | |---|---| | No active profile, or active: NA | Route to /jetson-set-target or /jetson-init-target. | | Profile lacks bsp_image: block | Route to /jetson-init-image. | | <bsp_image.root_path>/Linux_for_Tegra/ missing | Route to /jetson-init-image. | | <source.root_path>/Linux_for_Tegra/ missing or not a git repo | Route to /jetson-init-source. |
Resolve paths:
<bsp_image.root_path> from bsp_image.root_path: if present, else <workspace>/Image.<source.root_path> from source.root_path: if present, else <workspace>/Source.<bsp_image.root_path> is read-only for this skill; every write lands under <source.root_path> (the overlay tracker). This is the workflow invariant in ../../context/bsp-customization-workflow.md#workflow-invariants — hand-editing upstream silently destroys the diff trail and makes /jetson-promote-image a noop.
The conf this skill edits has the relative path:
Linux_for_Tegra/rootfs/etc/nvpower/nvfancontrol/nvfancontrol_<active-sku>.confIt lives in two roots; the skill walks both:
| Role | Location | Skill writes? | |---|---|---| | Detection + pristine source | <bsp_image.root_path>/Linux_for_Tegra/rootfs/etc/nvpower/nvfancontrol/ | no — read-only | | Overlay edit target + git commit | <source.root_path>/Linux_for_Tegra/rootfs/etc/nvpower/nvfancontrol/ | yes |
Subsequent sections refer to the per-board file to mean the overlay copy under <source.root_path>. Operations 1–4 all read, edit, and save against that overlay copy. The <bsp_image.root_path> copy is read once during the "Resolving <active-sku>" detection step and once during the Overlay edit recipe's pristine-import step, then never touched again.
<active-sku> — which file to editFilename conventions vary per product family:
nvfancontrol_<module.id>_<module.sku>.conf — most Orin parts (e.g. nvfancontrol_p3767_0000.conf, nvfancontrol_p3701_0008.conf).nvfancontrol_<module.id>_<module.sku>_<carrier.id>_<carrier.sku>.conf — Thor variants where the carrier disambiguates (e.g. nvfancontrol_p3834_0008_p4071_0000.conf).nvfancontrol_<carrier>_<sku>_<rev>.conf — IGX revision variants (e.g. nvfancontrol_p3740_0002_b01.conf).The nvfancontrol daemon resolves the right file at startup based on the booted hardware's DT compatible plus board IDs (no helper script — it's done inside the binary). To map BSP-side without a running target, against <bsp_image.root_path>:
<bsp_image.root_path>/Linux_for_Tegra/rootfs/etc/nvpower/nvfancontrol/.<module.id>_<module.sku> from the active profile.<carrier.id>_<carrier.sku> and (when relevant) the carrier revision tag.compatible and pick the file whose name aligns with the resolved board / carrier.<bsp_image.root_path>/Linux_for_Tegra/rootfs/etc/nvpower/nvfancontrol/.Don't blindly compose a filename — the naming convention varies by product family.
The active-SKU file is rarely the only conf that should carry a customization. After editing it, apply the same edit to every sibling in the propagation set so the change survives regardless of which module / baseboard SKU is booted:
reference_devkit in the active profile, or jetson-derive-carrier fork ancestry). For a BSP that contains only the reference (no derived carriers), this is the same file as the active and the rule reduces to a no-op.nvfancontrol_*.conf produced by jetson-derive-carrier for a custom carrier on top of the same module SKU."Apply the same edit" ≠ blanket file copy. Port the changed FAN_PROFILE / control lines into each sibling; preserve every other line. Sibling confs often hold carrier-specific deltas (different THERMAL_GROUP coefficients for a different thermal solution, different tach RPM ceilings) that must stay intact — a blanket overwrite would mis-tune the fan on those carriers. Blanket-copying is safe only when both confs were byte-identical before the edit.
Follow the canonical Off-skill edits recipe in the workflow doc — pristine import + customization commit pair, both gated by the preview gate. Apply once per run, covering every per-board file the run touches (the active conf plus every sibling in the Propagation set).
Concrete substitutions for this skill:
<rel>/<file> is rootfs/etc/nvpower/nvfancontrol/<conf>.import pristine: <comma-separated rel paths of imported confs>, body Source: <bsp_image.root_path>/Linux_for_Tegra/ (BSP <bsp_image.version>).
jetson-customize-fan: <summary>, body lines like nvfancontrol_p3767_0000.conf: added FAN_PROFILE static (open-loop, PWM=255 flat), FAN_DEFAULT_CONTROL close_loop -> open_loop, FAN_DEFAULT_PROFILE quiet -> static.
Pick the operation that matches the user's intent and follow the matching subsection. All write-side operations (1–4) must first apply the Overlay edit recipe.
After any write-side operation, run the Deploy chain (## Deploy) to land the change on the device.
Add an aggressive profile to a P3767-0000 (Orin Nano dev kit) target and pin it as the boot default:
/jetson-customize-fan
> add a profile called "aggressive" with the curve {0:255, 40:150, 80:0}
> set FAN_DEFAULT_PROFILE to aggressiveSoften the fan ramp on the existing quiet profile (raise HYST, lower mid-PWM):
/jetson-customize-fan
> edit FAN_PROFILE quiet — raise HYST to 5 from 30 °C up, drop PWM at 60 °C to 80List which profiles the active SKU currently defines and which is the boot default:
/jetson-customize-fan
> list defined fan profilesApply the Overlay edit recipe first.
<FAN N> block, add a new FAN_PROFILE <name> { ... } between existing profiles. Daemon parse order doesn't matter, but grouping with siblings keeps the file scannable.<name> lowercase, no whitespace (e.g. quiet, cool, aggressive).TEMP HYST PWM RPM tuples, ascending TEMP. End with at least one row above GROUP_MAX_TEMP so behavior at over-temp is defined.Example skeleton:
FAN_PROFILE aggressive {
#TEMP HYST PWM RPM
0 0 255 6000
20 2 255 6000
40 2 150 3500
60 2 50 1500
80 0 0 0
105 0 0 0
}Validate (see Rules):
TEMP lies within 0..GROUP_MAX_TEMP of the THERMAL_GROUP.PWM ∈ [0, 255]; RPM ≤ the platform's tach-reported max (varies by fan part).TEMP.If you intend the new profile to be the boot default, follow Operation 4.
Apply the Overlay edit recipe first.
FAN_PROFILE <name> { ... } block including all curve rows and the closing }.<name> matches the trailing FAN_DEFAULT_PROFILE, point FAN_DEFAULT_PROFILE at a remaining profile — otherwise the daemon fails to start.bash grep -rn "nvfancontrol.*profile\|FAN_PROFILE" \ Linux_for_Tegra/rootfs/etc 2>/dev/null
The remaining profiles do not need renaming.
Apply the Overlay edit recipe first.
FAN_PROFILE <name>. Keep the 4-column TEMP HYST PWM RPM shape.TEMP ordering; insert or remove rows as needed.HYST to tune anti-oscillation: too low → fan thrashes near a curve point; too high → fan lags reality.Edits to KICKSTART_PWM, RPM_TOLERANCE (inside FAN_CONTROL), or STEP_SIZE (inside FAN_GOVERNOR) sit outside the profiles but tune the same fan; same per-board file.
Apply the Overlay edit recipe first.
Edit the trailing default lines inside the <FAN N> block:
FAN_DEFAULT_CONTROL <close_loop|open_loop>
FAN_DEFAULT_PROFILE <name>
FAN_DEFAULT_GOVERNOR <type>FAN_DEFAULT_PROFILE must reference an existing FAN_PROFILE in the same <FAN N> block. FAN_DEFAULT_CONTROL and FAN_DEFAULT_GOVERNOR must reference values the binary supports — copy from existing per-board files when changing.
This is a read-only operation; no overlay-tracker setup is needed. Run against whichever copy you want to inspect (<bsp_image.root_path>/... for the pristine state, <source.root_path>/... for the post-edit state):
bashgrep -E '^[[:space:]]*FAN_PROFILE ' <per-board file> grep -E '^[[:space:]]*FAN_DEFAULT_' <per-board file>
The first prints every profile name in the file; the second prints the boot defaults (control / profile / governor).
/etc/nvpower/nvfancontrol/ directly. Live tuning requires reboot via Deploy, or the side-channel scp + systemctl restart flow described in ## Deploy.
<source.root_path> only;the <bsp_image.root_path> copy is read-only and is rewritten by /jetson-promote-image. Hand-editing bsp_image is silently lost on the next /jetson-init-image re-extract.
the platform — this skill does not interpolate or copy curves across chip families.
by design: only the changed FAN_PROFILE / control lines are ported, never a blanket file overwrite, since sibling confs may hold carrier-specific THERMAL_GROUP / RPM ceilings.
confs when introducing new THERMAL_GROUP coefficients or FAN_DEFAULT_GOVERNOR values.
| Error | Cause | Solution | |---|---|---| | nvfancontrol.service fails to start after edit | FAN_DEFAULT_PROFILE references a profile that was removed or renamed | Set FAN_DEFAULT_PROFILE to an existing FAN_PROFILE <name> in the same <FAN N>. | | Fan thrashes near a curve point | HYST too low | Raise HYST on the affected row (typical 2–5 °C). | | Fan lags reality | HYST too high | Lower HYST on the affected row. | | Fan never spins up at high temp | Curve missing a row above GROUP_MAX_TEMP, or trailing 0 0 truncates above ambient | Add a high-temp tuple with non-zero PWM/RPM; pin the over-temp row only above GROUP_MAX_TEMP. | | Daemon parse error referencing column count | Curve row not 4-column TEMP HYST PWM RPM | Restore 4-column shape; remove trailing whitespace and stray columns. | | RPM target never reached in close-loop | Target above tach-reported max, or FAN_CONTROL set to open_loop | Lower RPM to within the fan's mechanical max, or switch FAN_DEFAULT_CONTROL to close_loop. | | Change vanished after /jetson-init-image re-extract | Edit landed in <bsp_image.root_path> instead of <source.root_path> overlay | Re-apply via the Overlay edit recipe so the change is committed in the overlay tracker. | | Sibling carrier boots with wrong tach ceiling after propagation | Blanket file copy clobbered carrier-specific THERMAL_GROUP / RPM rows | Port only the changed lines per Propagation set; restore the carrier's original THERMAL_GROUP. |
The customization commit in the overlay tracker does not reach the device on its own. The Deploy chain:
/jetson-promote-image — copies every tracked file in the overlayinto <bsp_image.root_path>/Linux_for_Tegra/. Diff-aware (skip byte-identical); uses sudo cp -p for rootfs/* destinations.
/jetson-flash-image — flashes the updated bsp_image to thedevice.
<source.root_path>/Linux_for_Tegra/rootfs/etc/nvpower/nvfancontrol/<conf>directly to the running target's /etc/nvpower/nvfancontrol/<conf>, then sudo systemctl restart nvfancontrol.service (or reboot).
Editing <source.root_path>/... without committing — or editing <bsp_image.root_path>/... directly — does nothing for /jetson-promote-image and is silently lost on the next /jetson-init-image re-extract.
Other measured skills in the registry, with their headline benchmark lift.