Before the modem can put a sample where you want it, a dozen independent things must be right: the AD9361 calibrated and tuned at the right rate, a profile applied after that tuning, the DAC fed from the modem, the transceiver in an emitting state, the loopback switches on the right side, the TX egress open and the datapath claimed. Getting any of them wrong produces a chain that looks healthy and transmits nothing.
The chain layer enumerates these prerequisites, reads them back from the board on every request, and establishes the missing ones in the only order that works.
Checking the chain#
satlinkctl chain status # judged against the target the board is wired for
satlinkctl chain status --target air # "what would it take to go on air?"GET /api/v1/chain returns the same, with one entry per condition:
| Field | Meaning |
|---|---|
key | The condition's name (below) |
stage | The group it belongs to |
source | board (read from a register or IIO attribute now) or session (held by the daemon since boot) |
required, observed | What the target needs, and what was read |
verdict | met, unmet or unknown |
breaks | What goes wrong when it is not met |
An unreadable fact is unknown, and unknown counts as not met: the chain refuses to build a
plan on a register it could not read.
The conditions#
| Key | Stage | Applies to | Required |
|---|---|---|---|
calibrated | unconditioned | device loopback, air | The AD9361's analog calibrations have run since boot |
rate | unconditioned | device loopback, air | The AD9361 sample rate is exactly what the active profile's symbol rate needs (at most 13 MSa/s without a profile) |
tuning | unconditioned | device loopback, air | The LVDS digital interface was tuned, and not lost to a later calibration |
profile | unprofiled | all | A profile is applied (the named one, when you name one) |
profile-order | unprofiled | device loopback, air | The profile was applied after the last tuning (a tuning overwrites part of what a profile sets) |
modulator | misrouted | all | The selected modulator is the enabled one |
dac-source | misrouted | device loopback, air | dac_data_sel = 2 (DMA): the DAC takes the modem's samples |
ensm | misrouted | device loopback, air | The AD9361 ENSM is in fdd |
pl-mux | misrouted | all | GLOBAL_CTRL[0] is 1 for the PL loopback, 0 otherwise |
device-loopback | misrouted | device loopback, air | The AD9361's internal digital loopback is closed (device loopback) or open (air) |
tx-egress | not-armed | all | The modem's TX egress is open |
datapath | not-armed | all | satlinkd holds the modem's DMA devices |
The overall stage is the stage of the first unmet condition: unconditioned, unprofiled,
misrouted, not-armed, or ready.
Bringing the chain up#
satlinkctl chain up --target air --profile rf_loopback
satlinkctl chain up --target pl --profile pl_loopbackPOST /api/v1/chain/condition with {"target": "air", "profile": "rf_loopback"} does the same.
With no profile named, the currently applied one is used; with none applied, the request is
refused rather than guessing, because the profile decides the frame length, the coding and the
frequency.
Only the missing steps run, always in this order:
| Step | Runs when | Why here |
|---|---|---|
calibrate | device loopback or air, not calibrated since boot | It resets the AD9361, so everything else must come after it |
set-ensm | after a calibration, or when the ENSM is not fdd | The tuning needs a clocked data port; this is also what undoes the RF kill switch |
tune | after a calibration, or when the rate, tuning or ENSM is wrong | Sets the AD9361 rate (the profile's, or --rate) and sweeps the LVDS delays at that rate. It drives the DAC to a test pattern during the sweep |
apply-profile | after any tune, or when the profile is missing, wrong or older than the tuning | A tuning invalidates the profile |
set-mode | after any earlier step, or when a switch is wrong | A profile apply rewrites the loopback mux and the LOs, so the mode is set last |
start-tx | when the TX egress is closed | |
enable-datapath | when the datapath is not held |
The result lists the steps run and the chain state re-read afterwards. For the air target the
verdict states all three doors explicitly: pl-mux 0, device-loopback open,
dac-source 2 (DMA), reaches_the_connector: true.
Manual steps#
The individual steps are available when you need them, but taking them out of order undoes the earlier ones:
satlinkctl rf calibrate # resets the part: FIRST, before anything else
satlinkctl rf tune --rate 2500000
satlinkctl profile apply rf_loopback
satlinkctl rf air # or: rf rf (device loopback), rf pl (PL loopback)
satlinkctl radio start-tx
satlinkctl datapath enablerf air and rf rf refuse when the interface was never tuned or when the profile was applied
before the last tuning; --force overrides the refusal.
Chain targets#
--target | Scenario chain.target | Meaning |
|---|---|---|
pl-loopback or pl | pl | TX → channel emulator → RX inside the FPGA. The AD9361 is not used |
device-loopback | device-loopback | Through the AD9361's internal digital loopback, before the mixers. Nothing leaves the board; LOs, gains and attenuation have no effect |
air | air | Out through the mixers to TX1 |
rf is accepted by the CLI as an alias of the device loopback, but scenarios deliberately refuse
it: an "RF" mode that emits nothing is exactly the confusion that makes an analyser on the
connector show nothing.
Recovering the receiver without a reboot#
The receiver's loops can get stuck: symbols flow, the lock flags read true, and the deframer never finds its marker. A clear flushes the AGC, the timing loop and the carrier loop in signal-flow order, preserving every other bit of their control registers:
satlinkctl chain recover # POST /api/v1/chain/recoverIt says what it pulsed; it does not say the link is healed. Check that with a byte-exact loopback or with frame counters that move, never with the lock flags.
Scenario runs on the air path do this automatically: after the profile apply, the engine pulses the clear 500 ms after its traffic feed starts, so the loops re-acquire on a live signal. A clear issued before traffic flows does not help. A profile apply also clears the loops.
Scenarios and the chain#
Every scenario run conditions the chain first, for a target derived from its profile
(radio.loopback: true → pl, otherwise air) unless the scenario declares one, and
refuses to start when the target cannot be reached: no run record, no report. See
Scenarios.