tribble-control 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1483 @@
1
+ .Dd October 7, 2026
2
+ .Dt TRIBBLE-CONTROL 1
3
+ .Os
4
+ .Sh NAME
5
+ .Nm tribble-control
6
+ .Nd power, flash and monitor the devices on a switchable USB hub
7
+ .Sh SYNOPSIS
8
+ .Nm
9
+ .Op Fl d Ar DEV
10
+ .Op Fl -hub Ar KIND
11
+ .Op Fl C Ar FILE
12
+ .Op Fl m Ar TYPE
13
+ .Op Fl W Ar SECONDS
14
+ .Op Fl p Ar STRING
15
+ .Op Fl -openocd Ar PATH
16
+ .Op Fl F
17
+ .Op Fl v
18
+ .Op Fl -debug Ns Oo = Ns Ar FILE Oc
19
+ .Ar command
20
+ .Op command options
21
+ .Op Ar PORT | DEVNAME ...
22
+ .Nm
23
+ .Fl -man | -help | -version
24
+ .Sh DESCRIPTION
25
+ The hub switches its ports independently, and there are two kinds of
26
+ it.
27
+ The ExSYS managed hub, the default, switches VBUS on each of its
28
+ sixteen sockets over an FT232 serial line wired inside it, and
29
+ tribble\-control drives that line.
30
+ Any standard USB hub with per\-port power switching is the other
31
+ \(em 'hub = usb' in the configuration, or \-\-hub usb \(em and it is switched on
32
+ the bus itself, with hub\-class requests through
33
+ .Xr usbconfig 8 ,
34
+ which makes it
35
+ .Fx
36
+ only for now.
37
+ See THE HUB.
38
+ For a board on either that
39
+ carries a debug probe, tribble\-control additionally speaks SWD through
40
+ openocd and reads the board's console over USB CDC.
41
+ Which probe and
42
+ which chip a board needs is the configuration's business (interface= and
43
+ target=), so the family it belongs to is not built in; the bench is
44
+ nRF52 today, and that is the default, not a requirement.
45
+ Everything
46
+ else on the hub is only an electrical load \(em see PROTECTED PORTS.
47
+ .Bd -literal
48
+ tribble\-control \-C tribble.conf flash zephyr.hex alpha beta
49
+ stdbuf \-oL tribble\-control \-C tribble.conf connect \-\-off gamma epsilon \e
50
+ | tee twr.log
51
+ .Ed
52
+ .Pp
53
+ It lives on the host owning the hub, alongside tribble.conf.
54
+ It is a gem, so
55
+ .Ic gem install tribble-control
56
+ \(em from a built .gem, or
57
+ .Ic rake install
58
+ in a checkout \(em puts
59
+ .Nm
60
+ on PATH with its dependencies and needs nothing further.
61
+ .Pp
62
+ A checkout with a vendored bundle works too, and is what a host that
63
+ should not have gems installed system\-wide wants:
64
+ .Ic bundle install --path vendor
65
+ followed by
66
+ .Ic bundle binstubs tribble-control
67
+ writes bin/tribble\-control, which sets that bundle up before loading
68
+ anything.
69
+ Run that one from the directory holding the bundle.
70
+ .Sh THE HUB
71
+ .Bd -literal
72
+ the host running tribble\-control
73
+ │
74
+ │ FT232R ──▸ /dev/ttyUSB0 the hub's control line. It is
75
+ │ 9600 8N1 internal to the hub: it is NOT
76
+ ▾ one of the 16 switched sockets.
77
+ ┌───────────────────────────────────────────────────────────────────┐
78
+ │ ExSYS 16\-port managed USB hub │
79
+ │ │
80
+ │ bank 1 bank 2 bank 3 bank 4 │
81
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
82
+ │ │ 1 │ │ 5 │ │ 9 │ │ 13 │ │
83
+ │ │ 2 │ │ 6 │ │ 10 │ │ 14 │ │
84
+ │ │ 3 │ │ 7 │ │ 11 │ │ 15 │ │
85
+ │ │ 4 │ │ 8 │ │ 12 │ │ 16 │ │
86
+ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
87
+ └───────────────────────────────────────────────────────────────────┘
88
+ Sixteen independently switched sockets. What is on each is the
89
+ configuration's business, not this diagram's: a socket may carry a board
90
+ the tool drives over SWD, or something it only powers and never
91
+ talks to, or nothing at all. Sockets in the second class are what
92
+ PROTECTED PORTS exists for.
93
+ .Ed
94
+ .Pp
95
+ Ports are numbered 1 to 16 across four internal banks of four.
96
+ These are
97
+ the hub's own numbers, the ones the configuration and every command use; whether
98
+ the sockets are silkscreened that way is another matter, so count banks
99
+ left to right and sockets top to bottom if you are looking for one by hand.
100
+ .Pp
101
+ The FT232 control adapter enumerates at the last position of the hub's
102
+ internal tree (USB path 1\-1.2.4.4), which makes it look as though it
103
+ occupies port 16.
104
+ It does not: it is wired inside the hub and is not
105
+ switched.
106
+ Port 16 is an ordinary socket like the other fifteen.
107
+ The one
108
+ consequence is that a board must never be put on port 16: \-\-method usb
109
+ would compute the path 1\-1.2.4.4 for it and address the FT232 instead.
110
+ .Pp
111
+ A port that only supplies power tells the tool nothing about what is on
112
+ it, so if you need to reboot one device in particular, establish which
113
+ socket it is physically first.
114
+ Nothing on such a port communicates with
115
+ the host, and the configuration records a name and a port, not what is plugged
116
+ in.
117
+ .Pp
118
+ The host running tribble\-control is not powered from the hub, so no hub
119
+ port can cut power to the machine you are typing on, and \-\-force is safe
120
+ in that one narrow respect.
121
+ .Pp
122
+ Requirements: Ruby 3.1, and openocd \(em everything that reaches a
123
+ board over SWD shells out to it.
124
+ The Ruby dependencies come with the gem; openocd does not, being no
125
+ gem of anybody's.
126
+ It is expected at /usr/bin/openocd and found elsewhere with
127
+ \-\-openocd; flash and reset check for it before switching anything, and
128
+ connect \-\-reset when it reaches for it.
129
+ .Ss A standard hub with per-port power switching
130
+ Everything above is the ExSYS hub.
131
+ \&'hub = usb' at the top of a configuration, or \-\-hub usb on the command line,
132
+ drives any hub that switches its own ports instead.
133
+ Nothing about it is an agreement with one manufacturer: what is spoken
134
+ is the USB hub class, which every hub answers \(em the hub descriptor
135
+ says how many ports there are, GET_STATUS says whether one is powered,
136
+ and SET_FEATURE/CLEAR_FEATURE of PORT_POWER switches it.
137
+ The ports are that hub's own, 1 to the bNbrPorts its descriptor
138
+ reports, read once when the hub is opened.
139
+ The fixed sixteen, and the 4\-by\-4 geometry \-\-method usb rests on, are
140
+ the ExSYS hub's alone.
141
+ .Pp
142
+ The kind has to be said because it cannot be read off the
143
+ .Cm device
144
+ value: a USB path such as 1\-1.1 names the FT232's socket for an ExSYS
145
+ hub and the hub itself for a usb hub, so the same line would mean two
146
+ different hubs.
147
+ It names what the hub IS and not which tool drives it, so a configuration
148
+ written today keeps working when another host learns to drive such a
149
+ hub.
150
+ .Pp
151
+ Today that host is
152
+ .Fx
153
+ only:
154
+ .Xr usbconfig 8
155
+ is what issues the requests, and no other host here has a command that
156
+ sends an arbitrary control request to a hub.
157
+ Root is not among the requirements.
158
+ The ugen nodes are root:operator 0660, and the kernel asks for the
159
+ driver privilege only for SET_ADDRESS, SET_CONFIG and SET_INTERFACE,
160
+ so a hub\-class port feature request passes: membership of group
161
+ operator is the whole of it
162
+ .Pq Ic pw groupmod operator -m yourname ,
163
+ then log in again.
164
+ Without it every request answers "Permission denied" and
165
+ .Nm
166
+ says so, naming the group.
167
+ .Pp
168
+ Which hub, when the host has more than one, works as it does for the
169
+ other kind: one candidate is taken, two or more are refused and listed
170
+ \(em each with its serial, its ugen name, its USB path and its port
171
+ count, semicolons between them, the count being the only thing that
172
+ tells two of the same part apart when neither carries a serial.
173
+ Root hubs are never candidates.
174
+ They are the controller a host's own sockets hang off, and a port of
175
+ one has no PORT_POWER to clear, so offering one would offer a hub every
176
+ command against it then failed on.
177
+ See
178
+ .Fl d
179
+ under
180
+ .Sx OPTIONS
181
+ for the three shapes a name may take.
182
+ .Pp
183
+ What 'off' then does to the socket is the one thing software cannot
184
+ find out, and
185
+ .Cm switch
186
+ in the configuration is how it is told.
187
+ A hub with no power switch wired still reports the port unpowered and
188
+ still drops the link, so the device vanishes from the host and comes
189
+ back either way.
190
+ .Bl -tag -width "switch = link" -compact
191
+ .It Li "switch = link"
192
+ The default.
193
+ \&'off' takes the port off the bus and the board stays powered.
194
+ .It Li "switch = vbus"
195
+ \&'off' cuts the socket's power.
196
+ .El
197
+ .Pp
198
+ Watch a board's LED to find out which this hub is: put a board that
199
+ lights up on a port, run 'usb off' for that port, and look.
200
+ An LED that goes out is vbus; an LED that stays lit while the board
201
+ disappears from the host is link.
202
+ Test the socket the bench will use \(em the USB 2 and USB 3 sides of one
203
+ physical socket are different ports on different hubs, and a hub may
204
+ switch neither.
205
+ The ExSYS hub refuses the key rather than ignoring it: it always cuts
206
+ power, and a line that changes nothing is a line somebody will trust.
207
+ .Pp
208
+ Under 'switch = link' everything that powers down still works.
209
+ \&'usb off', 'toggle' and 'set' behave, the board vanishes from the host
210
+ exactly as a power cut would, and \-\-method power still identifies a
211
+ board by being the only one openocd can see.
212
+ What does not happen is the board restarting, so every power\-down
213
+ warns once, naming the ports that stay powered, and the after\-flash
214
+ power cycle is skipped with a warning rather than pretended \(em see
215
+ .Sx TRAPS .
216
+ .Pp
217
+ After every switch the port's status is read back, and the command
218
+ fails if the power bit did not follow.
219
+ That read\-back is the tool's only measurement of whether a hub
220
+ switches at all: one that accepts CLEAR_FEATURE(PORT_POWER), answers OK
221
+ and leaves the port up would otherwise have 'usb off' report success on
222
+ a bench it never touched.
223
+ Nothing is refused on what a hub DECLARES about its switching, for the
224
+ same reason \(em the dock's Genesys hub declares ganged and switches per
225
+ port anyway.
226
+ .Sh OPTIONS
227
+ Ordering matters, and it is the thing most likely to waste your afternoon.
228
+ Global options come before the command; a command's own options come after
229
+ the command but before its sub\-action.
230
+ So:
231
+ .Bd -literal
232
+ tribble\-control \-C tribble.conf \-\-force usb off 14 global, then cmd
233
+ tribble\-control \-C tribble.conf usb \-\-default=false set gamma:on
234
+ .Ed
235
+ .Pp
236
+ and NOT 'usb set \-\-default=false ...', which parses the flag as an argument
237
+ to 'set' and fails with "invalid argument".
238
+ .Pp
239
+ Global options, which go before the command:
240
+ .Bl -tag -width "-W, --warm-up=SECONDS"
241
+ .It Fl d , Fl -device Ns = Ns Ar DEV
242
+ Which hub to drive, in one of three shapes, told apart by what they
243
+ look like:
244
+ .Bl -tag -width "1-1.2.4.4" -compact
245
+ .It Pa /dev/ttyUSB1
246
+ a
247
+ .Sq /
248
+ in it, so the serial line itself, used as given \(em the same rule
249
+ .Fl -openocd
250
+ uses to tell a path from a name
251
+ .It Cm 1-1.2.4.4
252
+ the USB path shape, so the adapter sitting in that socket on this host
253
+ .It Cm AL03GD7X
254
+ anything else, so the FT232's serial number
255
+ .El
256
+ .Pp
257
+ With
258
+ .Fl -hub Cm usb
259
+ the three shapes name a hub of that kind instead:
260
+ .Bl -tag -width "1-1.2.4.4" -compact
261
+ .It Cm ugen1.4
262
+ the ugen shape, so that device, used as given
263
+ .It Cm 1-1.1
264
+ the USB path shape, so the hub in that socket on this host
265
+ .It Cm AC0528515619
266
+ anything else, so the hub's own serial number
267
+ .El
268
+ .Pp
269
+ ugen1.4 is that kind's /dev/ttyUSB1 and carries the same warning below:
270
+ the number is enumeration order \(em ugen1.4 is the fourth device the
271
+ second controller attached \(em so a replug renumbers it, and a configuration
272
+ naming a hub that way points at whatever attached in its place.
273
+ Write the serial, which follows the hub, or the USB path, which follows
274
+ the socket.
275
+ .Pp
276
+ Three answers, in the order they are trusted: this option, then the
277
+ configuration's own
278
+ .Cm device
279
+ line, then the host, which is searched for an FTDI 0403:6001 and
280
+ normally turns up /dev/ttyUSB0.
281
+ .Pp
282
+ That last one is a guess, and it is made only when there is nothing to
283
+ guess between.
284
+ An FTDI 0403:6001 is the hub's control adapter and also every other
285
+ FT232 on the host, so with two of them attached
286
+ .Nm
287
+ refuses and lists what it found, with serials, rather than switching the
288
+ ports of whichever enumerated first.
289
+ A host owning more than one bench says which in each configuration, so that
290
+ .Fl C
291
+ alone selects one; this option is for the one\-off.
292
+ .Pp
293
+ The serial line itself is the worst of the three to write down.
294
+ The number in /dev/ttyUSB1 is not the hub's, and not the USB device
295
+ number either: it is the usbserial layer's own index (ttyU on FreeBSD,
296
+ allocated the same way), and it is the lowest one free when that
297
+ adapter is probed.
298
+ So it depends on what else attached first, and it is reused \(em unplug
299
+ whatever holds ttyUSB0 and the next thing to attach takes ttyUSB0.
300
+ Two hubs can swap names across a reboot, or while the machine is up,
301
+ and every configuration naming them that way then points at the other bench,
302
+ silently and in a way no command can detect.
303
+ .Pp
304
+ The other two are both stable, and they answer different questions.
305
+ A serial stays with the ADAPTER \(em move the hub to another socket, or
306
+ another machine, and its serial goes with it \(em and is the usual
307
+ want, naming one particular hub.
308
+ A USB path stays with the SOCKET: whatever is plugged in there answers
309
+ to it, a replacement hub included.
310
+ Use the path for a hub whose EEPROM carries no serial, that being the
311
+ only stable name such a one has, and for a bench where the socket is
312
+ the fixed thing.
313
+ .Pp
314
+ Both platforms report a path, by different means: Linux states it in
315
+ /sys, and on FreeBSD it is walked out of the sysctl tree, each device's
316
+ %location giving the port it occupies on its parent and %parent naming
317
+ that parent.
318
+ The numbering is each host's own, though \(em FreeBSD counts buses from
319
+ 0 and Linux from 1 \(em so a path names a socket on the machine that
320
+ reported it and does not travel to another.
321
+ Read the ones on this host with
322
+ .Ic exsys-usb discover ,
323
+ which the exsys gem installs and which lists one adapter per line, its
324
+ line then its serial \(em or by asking
325
+ .Nm
326
+ for a serial that does not exist, since the refusal lists them.
327
+ .It Fl -hub Ns = Ns Ar KIND
328
+ Which kind of hub to drive:
329
+ .Cm exsys
330
+ (the default) or
331
+ .Cm usb .
332
+ Overrides the configuration's own
333
+ .Cm hub
334
+ line.
335
+ The option takes those two words only; a configuration naming another kind is
336
+ refused at load with both listed.
337
+ See
338
+ .Sx THE HUB .
339
+ .It Fl C , Fl -config Ns = Ns Ar FILE
340
+ The configuration.
341
+ Required for any power\-down at all, and by any command taking device
342
+ names.
343
+ Without
344
+ .Fl C ,
345
+ a file named
346
+ .Pa tribble-control.conf
347
+ in the current directory is used if there is one; neither the home
348
+ directory nor /etc is ever consulted, and given
349
+ .Fl C
350
+ the current directory is not either.
351
+ .Ic usb on
352
+ and
353
+ .Ic usb status
354
+ work without it.
355
+ .It Fl m , Fl -method Ns = Ns Ar TYPE
356
+ Device selection:
357
+ .Cm power , usb
358
+ or
359
+ .Cm serial .
360
+ See
361
+ .Sx DEVICE SELECTION .
362
+ Rejected if the command does not support it.
363
+ .It Fl W , Fl -warm-up Ns = Ns Ar SECONDS
364
+ Delay after powering a port on, before touching the board.
365
+ Default 5.
366
+ .It Fl F , Fl -force
367
+ Switch ports the configuration does not allow.
368
+ See
369
+ .Sx PROTECTED PORTS .
370
+ .It Fl p , Fl -password Ns = Ns Ar STRING
371
+ Supplies the ExSYS hub's password; it does not change it.
372
+ Default
373
+ .Sq pass .
374
+ A wrong one makes the hub answer E01 and the command abort loudly, not
375
+ silently.
376
+ A usb hub has no password, so the option is refused there rather than
377
+ ignored.
378
+ .It Fl r , Fl -require Ns = Ns Ar FILE Ns Op , Ns Ar FILE ...
379
+ Ruby files to load before anything else runs, for the tallies they
380
+ register.
381
+ Repeatable: every
382
+ .Fl r
383
+ given is loaded, in order.
384
+ An empty name, from \-\-require= or a doubled comma, is refused.
385
+ See
386
+ .Sx TALLIES .
387
+ .It Fl -openocd Ns = Ns Ar PATH
388
+ openocd binary.
389
+ Default /usr/bin/openocd; a name with no
390
+ .Sq /
391
+ in it is looked up in PATH, which is how to reach it on a host that
392
+ keeps it elsewhere (FreeBSD: /usr/local/bin).
393
+ Checked before any port is switched, by the commands that always need
394
+ it.
395
+ .It Fl -debug Ns Oo = Ns Ar FILE Oc
396
+ Show debug output \(em chiefly the openocd command line issued for each
397
+ board, which is the thing worth having when a flash behaves oddly.
398
+ With
399
+ .Ar FILE ,
400
+ the whole log is copied there as well as shown, appending, so a long
401
+ run can be kept without being watched.
402
+ An unwritable path is refused before any port is switched.
403
+ .It Fl v , Fl -verbose , Fl -no-verbose
404
+ Run verbosely.
405
+ .It Fl V , Fl -version
406
+ Print the tribble\-control and ExSYS library versions and exit.
407
+ .It Fl h , Fl -help
408
+ Usage, including the command list.
409
+ .El
410
+ .Pp
411
+ Per\-command: 'usb' takes \-\-default=BOOL, 'connect' takes \-\-off.
412
+ Both are
413
+ described under COMMANDS.
414
+ .Sh "HUB PROTOCOL, AND WHAT IT CANNOT TELL YOU"
415
+ The ExSYS hub speaks a small ASCII protocol over the FT232 line.
416
+ Recovered from
417
+ the vendor's own cusba64 binary (which ships unstripped), the complete
418
+ vocabulary is eight commands, each terminated by CR, answering 'G...' on
419
+ success and 'E01' on error:
420
+ .Bd -literal
421
+ ?Q query: an id, the port count and the firmware no password
422
+ GP get port states no password
423
+ SP set port states password
424
+ FP set port states, and save as the power\-on state password
425
+ WP save the current states as the power\-on state password
426
+ CP change password password
427
+ RD restore factory defaults password
428
+ RH reset the whole hub password
429
+ .Ed
430
+ .Pp
431
+ ?Q answers a single string: an id, then the port count, then the
432
+ firmware version \(em CENTOS000516v02 on a 16\-port hub running v02.
433
+ It carries no port states; cusba64 fills its On= and Off= display from
434
+ a separate GP.
435
+ .Pp
436
+ The exsys gem implements all eight as of 1.0, ?Q as #query, and the
437
+ port count it answers decides what "every port" covers rather than
438
+ being assumed.
439
+ It holds an exclusive lock on the line for the whole of a
440
+ read\-modify\-write, so two processes cannot lose each other's changes
441
+ between the GP and the SP, and it refuses an empty port list rather
442
+ than reading it as every port.
443
+ The default password is
444
+ \&'pass', right\-padded to 8 characters.
445
+ .Pp
446
+ tribble\-control only ever issues SP, never FP or WP: port states are set for
447
+ the here and now and never written to the hub's flash, so nothing it does
448
+ survives a hub power cycle.
449
+ Note also that RD, the only recovery from a
450
+ forgotten password, drops every port \(em so changing the password with CP
451
+ is close to a one\-way door on a hub carrying anything that minds losing
452
+ power without notice.
453
+ .Pp
454
+ There is no per\-port current or voltage telemetry, and no way to bolt it
455
+ on: the protocol has no such command, cusba64 contains no such code, and
456
+ USB 2.x/3.x hubs do not report per\-port draw to the host under any
457
+ circumstances.
458
+ A device's bMaxPower descriptor is what it asks for at
459
+ enumeration, not what it consumes, and a pure power load never enumerates
460
+ at all.
461
+ Measuring consumption needs an inline USB power meter or an
462
+ instrumented bench supply.
463
+ .Pp
464
+ Two of these commands are worth fearing.
465
+ RH resets the hub and RD
466
+ restores factory defaults; either drops every port, which power\-cycles
467
+ everything on the hub, protected ports included.
468
+ tribble\-control never
469
+ issues them, and the exsys gem only does so via #reset and
470
+ #factory_reset, both of which require an explicit confirm: true.
471
+ .Ss What a usb hub is asked
472
+ A hub of the other kind needs no gem and no serial line: the switching
473
+ is on the bus itself, and the whole vocabulary is four control requests
474
+ issued through
475
+ .Xr usbconfig 8 ,
476
+ by absolute path so that nothing depends on whoever's PATH the command
477
+ was started with.
478
+ .Bd -literal
479
+ /usr/sbin/usbconfig \-d ugenB.D do_request ...
480
+
481
+ 0xa0 0x06 0x2900 0 9 hub descriptor, USB 2
482
+ 0xa0 0x06 0x2a00 0 12 hub descriptor, SuperSpeed
483
+ 0xa3 0x00 0x0000 P 4 GET_PORT_STATUS of port P
484
+ 0x23 0x03 0x0008 P 0 SET_FEATURE PORT_POWER, port P
485
+ 0x23 0x01 0x0008 P 0 CLEAR_FEATURE PORT_POWER, port P
486
+ .Ed
487
+ .Pp
488
+ Both descriptors are asked for and whichever the hub answers decides
489
+ how its port status word is read from then on; the other is refused,
490
+ which is the only way to learn which kind of hub this is.
491
+ The hubs themselves are found with '/sbin/sysctl \-e dev.uhub'.
492
+ .Pp
493
+ Nothing here outlives the hub either: PORT_POWER is set for the here
494
+ and now, exactly as SP is above, and no descriptor is written.
495
+ And there is no per\-port telemetry on this side either, for the reason
496
+ already given \(em no USB hub reports per\-port draw to the host.
497
+ .Sh CONFIGURATION
498
+ A UCL file mapping a device name to a hub port, and optionally to the
499
+ board's debug\-probe serial number.
500
+ It is what \-C/\-\-config points at (or, failing that, a
501
+ .Pa tribble-control.conf
502
+ in the current directory), and it
503
+ is the only thing that tells tribble\-control which ports it may touch.
504
+ .Bd -literal
505
+ device = AL03GD7X # which hub these ports are on
506
+ hub = exsys # which KIND; 'usb' is the other
507
+ protect { # what must not lose power;
508
+ undeclared = yes # see PROTECTED PORTS
509
+ ports = [ 13, 14, 15, 16 ]
510
+ nodes = [ rpi ]
511
+ }
512
+
513
+ alpha { serial = '1026360216055e5b0000...97969902', port = 1 }
514
+ beta { port = 2 }
515
+ gamma { serial = '1026360213072dde0000...97969902', port = 4 }
516
+ retired { serial = '1026360202c4dc0f0000...97969902', port = none }
517
+ rpi { port = 12 }
518
+ .Ed
519
+ .Pp
520
+ Indentation is not significant, but separators are: two keys on one line
521
+ need a comma between them, which is why the entries above carry one and
522
+ one\-key\-per\-line entries do not.
523
+ A CMSIS\-DAP serial is 48 hex
524
+ characters and a J\-Link one 12, and both are pasted whole \(em the ones
525
+ above are elided in the middle for width.
526
+ .Pp
527
+ Every device entry must carry a port, and 'port = none' is how one says it
528
+ is a record rather than a board on the bench.
529
+ \&'retired' above is the
530
+ case: it stopped enumerating, its port was given to another board, and the
531
+ entry stays so that its serial is not lost.
532
+ Such an entry is left out of
533
+ everything \(em never selected, never switched, never flashed \(em and naming
534
+ it says so ("device 'retired' is not on the bench").
535
+ A MISSING port is an
536
+ error, not a synonym for none: deleting the port line is exactly what
537
+ happens when a port is reassigned, and inferring "gone" from a line
538
+ somebody forgot would drop a live board silently.
539
+ Two entries claiming
540
+ one port is refused at load, none excepted.
541
+ .Pp
542
+ Every key other than 'device', 'hub', 'switch', 'protect',
543
+ \&'tally' and 'types' is a device.
544
+ A command
545
+ given no device names operates on all of them.
546
+ Names may be used anywhere
547
+ a port number can be, in every command.
548
+ .Pp
549
+ A board with no serial= cannot be reached by 'reset' or by \-\-method
550
+ serial, both of which address a board by its serial number: the run
551
+ aborts with "Device without serial".
552
+ Fix it by running 'serial' (see
553
+ RECIPES) and pasting the value in.
554
+ .Pp
555
+ Optional keys describe a board whose probe or console differs from
556
+ the default:
557
+ .Bd -literal
558
+ epsilon { serial = '000760040233', interface = jlink, baud = 115200,
559
+ port = 7 }
560
+ .Ed
561
+ .Bl -tag -width power_cycle
562
+ .It Cm interface
563
+ The openocd interface script, without the .cfg:
564
+ .Cm cmsis-dap
565
+ (the default, a DAPLink probe such as the MDK's) or
566
+ .Cm jlink
567
+ (a J\-Link OB, as on the DWM1001\-DEV).
568
+ Used by
569
+ .Ic flash , reset
570
+ and
571
+ .Ic connect --reset .
572
+ With
573
+ .Cm jlink ,
574
+ serial= is the J\-Link serial number as udev reports it; leading zeros
575
+ are accepted.
576
+ .It Cm target
577
+ The openocd target script, without the .cfg:
578
+ .Cm nrf52
579
+ (the default, which is what the whole bench runs) or whatever openocd
580
+ calls the chip on a board of another family.
581
+ Used wherever interface= is.
582
+ The two are independent: the probe says how to reach the chip, the
583
+ target says which chip it is.
584
+ .It Cm transport
585
+ The transport openocd selects:
586
+ .Cm swd
587
+ (the default) or whatever the chip and the probe both speak, commonly
588
+ .Cm jtag .
589
+ .Cm none
590
+ selects nothing and leaves the choice to the interface script; it is
591
+ spelt as for
592
+ .Cm work_area ,
593
+ in any case, or null, or \-.
594
+ Anything else that is not a name \(em a boolean such as no, a number, an
595
+ empty string \(em is refused.
596
+ Making
597
+ .Cm target
598
+ a key and leaving this one fixed would be half a fix: a chip reached
599
+ over JTAG would take the right target script and then fail on a
600
+ transport it has not got.
601
+ .It Cm work_area
602
+ How much target RAM openocd may borrow for its flash algorithms,
603
+ default 0x4000, written as openocd wants it.
604
+ .Cm none
605
+ says nothing and leaves the target script to choose.
606
+ 16 KB is nothing to a large part and more than some have in total, so
607
+ it belongs to the chip rather than to this tool.
608
+ .It Cm baud
609
+ The console speed
610
+ .Ic connect
611
+ opens, default 230400 (what the MDK overlay sets).
612
+ The stock DWM1001\-DEV devicetree runs its console at 115200.
613
+ .It Cm tally
614
+ Which tally reads this board's console, overriding the file's own
615
+ .Cm tally
616
+ line.
617
+ See
618
+ .Sx TALLIES .
619
+ .It Cm power_cycle
620
+ The moments at which the board must have its port powered off and on
621
+ again: one name, or a list of them.
622
+ Known:
623
+ .Bl -tag -width after-flash
624
+ .It Cm after-flash
625
+ After a successful flash.
626
+ The DWM1001\-DEV needs it: after openocd's reset init / write / reset
627
+ run its DW1000 no longer reports transmissions (the board resolves
628
+ nothing and logs "our TX timestamps are missing") until the module is
629
+ power\-cycled; a plain SWD reset is not enough.
630
+ .El
631
+ .Pp
632
+ A name nothing acts on is not an error, so a new moment can be recorded
633
+ here before the command that honours it exists.
634
+ It lives here, and not only as the
635
+ .Ic flash --power-cycle
636
+ option, so that a flash by hand on the hub cannot forget it.
637
+ .El
638
+ .Pp
639
+ \&'device' is the hub these ports are on: the serial number of its
640
+ FT232 control adapter, a USB path such as 1-1.2.4.4, or the serial line
641
+ itself if the value has a '/' in it.
642
+ Quote a serial that is all digits: an unquoted one is read as a number,
643
+ and a number has no leading zeros, so 00760040233 arrives as 760040233
644
+ and matches nothing.
645
+ The refusal lists what the host really has, which is the moment to
646
+ notice.
647
+ .Pp
648
+ The key is newer than the rest, which matters when a configuration and a
649
+ .Nm
650
+ are deployed separately: every top-level key the tool does not know is
651
+ taken for a board, and a board must declare a port, so this line meets
652
+ an older
653
+ .Nm
654
+ as "devlist entry 'device' has no port".
655
+ Nothing is switched and nothing is damaged \(em the command refuses at
656
+ load \(em but upgrade the tool before adding the line.
657
+ A configuration is one bench and a bench is one hub, so the file that says
658
+ which board is on which port is where to say which hub those ports
659
+ belong to; with it, \-C alone selects a bench on a host that owns two.
660
+ Write the serial, or the USB path, rather than the serial line \(em a
661
+ ttyUSB number is the lowest index free when the adapter was probed, so
662
+ it depends on what else attached first and is reused when something
663
+ detaches, and neither of the other two does.
664
+ See
665
+ .Fl d
666
+ under
667
+ .Sx OPTIONS
668
+ for how to read one.
669
+ \&'hub' is which KIND of hub that is: 'exsys', the default and
670
+ everything described so far, or 'usb' for any hub that switches its own
671
+ ports.
672
+ \&'switch' is what such a hub's 'off' does to the socket: 'link', the
673
+ default, takes the port off the bus and leaves the board powered, and
674
+ \&'vbus' cuts the power.
675
+ It applies to 'hub = usb' alone, and an ExSYS hub refuses it rather
676
+ than ignoring it.
677
+ Both are described under
678
+ .Sx THE HUB ,
679
+ and \-\-hub overrides the first for a one\-off.
680
+ .Pp
681
+ \&'protect' says what must never be powered down: ports by number,
682
+ configuration entries by name, and every port the file does not mention
683
+ unless it says otherwise.
684
+ It is described under
685
+ .Sx PROTECTED PORTS .
686
+ \&'tally' sets the bench's default tally, described under
687
+ .Sx TALLIES .
688
+ \&'types' holds settings a device can inherit, described next.
689
+ .Ss Types
690
+ A bench is usually a handful of boards of two or three kinds, and what
691
+ a kind is \(em which probe, which chip, which transport, how fast its
692
+ console runs \(em is the same on every board of that kind.
693
+ Written out per device that is the same four lines eleven times, and a
694
+ reader has to compare them all to find the one that differs.
695
+ .Pp
696
+ \&'types' says it once:
697
+ .Bd -literal
698
+ types {
699
+ nrf52840-mdk {
700
+ interface = cmsis-dap
701
+ target = nrf52
702
+ transport = swd
703
+ baud = 230400
704
+ }
705
+ dwm1001-dev {
706
+ interface = jlink
707
+ target = nrf52
708
+ transport = swd
709
+ baud = 115200
710
+ power_cycle = after-flash
711
+ }
712
+ }
713
+
714
+ alpha { type = nrf52840-mdk, serial = '1026...9902', port = 1 }
715
+ delta { type = dwm1001-dev, serial = '000760040233', port = 7 }
716
+ epsilon { type = nrf52840-mdk, baud = 9600, port = 2 }
717
+ .Ed
718
+ .Pp
719
+ A device's own key wins over its type's: a type is what a kind of board
720
+ has in common, and an entry saying otherwise is saying it about itself.
721
+ \&'epsilon' above is an nRF52840\-MDK whose console has been moved.
722
+ .Pp
723
+ Three rules, each of them refusing something at load rather than
724
+ letting it read as working:
725
+ .Bl -bullet
726
+ .It
727
+ A type nothing defines is an error.
728
+ A board that asked for the jlink type and silently got the cmsis\-dap
729
+ default is a flash through the wrong probe, reported as a success.
730
+ .It
731
+ A type may not set
732
+ .Cm port
733
+ or
734
+ .Cm serial .
735
+ Both name one particular board \(em a port is where a single board is
736
+ plugged in, a serial is one physical probe \(em so a type that set
737
+ either would be saying that every board of that kind is the same board.
738
+ .It
739
+ Types do not nest.
740
+ A type is a block of settings, not a thing that can have a type of its
741
+ own.
742
+ .El
743
+ .Pp
744
+ A misspelled 'protetc' becomes a device named protetc, which would
745
+ silently unprotect every port the block named.
746
+ That one is caught: every
747
+ device entry must carry a port, so the misspelling is refused at load
748
+ with "configuration entry 'protetc' has no port".
749
+ A key misspelled INSIDE the block is caught by name, the block taking
750
+ \&'undeclared', 'ports' and 'nodes' and nothing else \(em a 'port = [ 13 ]'
751
+ quietly ignored would read as protection and be none.
752
+ Validation stops there,
753
+ though \(em a serial, an interface or a baud that is wrong is wrong
754
+ silently \(em so after editing this file still run 'usb status' and read
755
+ the names and the protected column before you trust it.
756
+ .Sh PROTECTED PORTS
757
+ Some things on the hub are there for power alone: a single\-board computer,
758
+ a powered peripheral, anything the bench feeds but does not drive.
759
+ Nothing
760
+ communicates with them, so nothing notices if their VBUS disappears \(em they
761
+ simply reboot, uncleanly, mid\-write.
762
+ One configuration block keeps that from
763
+ happening, with three keys.
764
+ .Pp
765
+ \&'undeclared' decides the fate of every port the configuration does not mention:
766
+ .Bl -tag -width "undeclared = yes"
767
+ .It Li "undeclared = yes"
768
+ The default.
769
+ Only ports declared in the configuration may be powered down.
770
+ .It Li "undeclared = no"
771
+ Any port the hub has may be powered down.
772
+ .El
773
+ .Pp
774
+ \&'ports' and 'nodes' name what is never powered down whichever way that
775
+ falls, so either protects a port even when that port is a declared
776
+ device.
777
+ \&'ports' takes port numbers, 'nodes' the names of configuration entries:
778
+ .Bd -literal
779
+ protect {
780
+ undeclared = yes
781
+ ports = [ 13, 14, 15, 16 ]
782
+ nodes = [ rpi ]
783
+ }
784
+ .Ed
785
+ .Pp
786
+ Prefer the name where there is an entry to name.
787
+ The port number is then written once rather than twice, the protection
788
+ follows the board if it moves socket, and 'usb status' prints the name
789
+ next to the protected port instead of a dash.
790
+ A name no entry declares is refused at load: a misspelling there would
791
+ protect nothing while reading, in the file, exactly like protection.
792
+ A name whose entry says 'port = none' is allowed and protects nothing,
793
+ there being no port to keep powered.
794
+ .Pp
795
+ Together:
796
+ .Bd -literal
797
+ tribble\-control powers down a port only if the configuration allows it,
798
+ and never one the configuration protects.
799
+ .Ed
800
+ .Pp
801
+ This is enforced at every path that can cut power, not just the obvious
802
+ one: 'usb off', 'usb toggle', 'usb set' with a port set to off, the
803
+ all\-ports\-off that begins \-\-method power, and 'connect \-\-off'.
804
+ You do not
805
+ have to reason about which commands are dangerous; they all go through the
806
+ same gate.
807
+ .Pp
808
+ So a bare 'usb off' means "every switchable port", not "every port the
809
+ hub has".
810
+ Naming a
811
+ protected port is an error, not a silent skip:
812
+ .Bd -literal
813
+ $ tribble\-control \-C tribble.conf usb off 14
814
+ tribble\-control: refusing to power down port(s) 14: protected by the
815
+ configuration (use \-\-force)
816
+ .Ed
817
+ .Pp
818
+ Without a configuration there is no safe set, so power\-down is
819
+ refused outright.
820
+ .Pp
821
+ \-F/\-\-force lifts both rules and restores the old "every port" behaviour.
822
+ It is the only way to deliberately reboot a power\-only device, and it is a
823
+ hard VBUS cut, not a shutdown: the device loses power mid\-write, with
824
+ whatever consequences that has for its filesystem.
825
+ Shut it down over the
826
+ network first if you care about what is on it.
827
+ It cannot strand you,
828
+ though \(em the host runs off its own supply, not off the hub.
829
+ .Pp
830
+ Powering *on* is never guarded: 'usb on' with no arguments still turns on
831
+ every port the hub has, which is the state the bench should normally sit
832
+ in.
833
+ .Sh DEVICE SELECTION
834
+ Flashing one of six identical boards means telling openocd which one.
835
+ There are three ways, chosen with \-m/\-\-method:
836
+ .Bd -literal
837
+ ┌─────────┬─────────────────┬──────────────┬────────────────────────┐
838
+ │ method │ addresses a │ requires │ power while the job │
839
+ │ │ board by │ │ runs │
840
+ ├─────────┼─────────────────┼──────────────┼────────────────────────┤
841
+ │ serial │ its debug probe │ serial= for │ every selected port │
842
+ │ │ serial number │ every board │ on; boards in parallel │
843
+ ├─────────┼─────────────────┼──────────────┼────────────────────────┤
844
+ │ usb │ its USB path, │ a USB path │ every selected port │
845
+ │ │ root.bank.slot │ for the hub │ on; boards in sequence │
846
+ ├─────────┼─────────────────┼──────────────┼────────────────────────┤
847
+ │ power │ being the only │ nothing │ exactly one board at │
848
+ │ │ board powered │ │ a time, rest cut │
849
+ └─────────┴─────────────────┴──────────────┴────────────────────────┘
850
+ .Ed
851
+ .Pp
852
+ Which to use:
853
+ .Bl -tag -width serial
854
+ .It Cm serial
855
+ The default for
856
+ .Ic flash
857
+ and
858
+ .Ic reset .
859
+ Boards stay powered and are done in parallel, so it is much faster and
860
+ far less disruptive.
861
+ Aborts if any selected board lacks a serial=.
862
+ .It Cm power
863
+ Needs no configuration at all \(em it identifies a board by being the
864
+ only one powered \(em and is the fallback for a board whose serial= is
865
+ missing or wrong.
866
+ Slowest by far: every board costs a power cycle plus the warm\-up, so
867
+ the whole bench is sequential rounds.
868
+ It also leaves every switchable declared board POWERED OFF when it
869
+ finishes, because each port is switched off again after its turn.
870
+ A board whose port the configuration protects stays powered, and says so.
871
+ Run
872
+ .Ic usb on
873
+ afterwards to bring the bench back up.
874
+ .Pp
875
+ Because a protected or undeclared port stays powered, a probe on one
876
+ would be a second adapter in front of openocd, which then picks between
877
+ them by itself.
878
+ So once a board is powered, the run is refused if more than one probe
879
+ console is present: use serial, or \-\-force to power those ports down
880
+ too.
881
+ A probe that shows no CDC console cannot be counted.
882
+ .It Cm usb
883
+ What
884
+ .Ic connect
885
+ uses.
886
+ Addresses by USB topology, so it needs no serials.
887
+ On Linux the topology is read from /sys/bus/usb; on FreeBSD it is
888
+ walked out of the sysctl tree, which cannot see a device no driver has
889
+ claimed.
890
+ .El
891
+ .Pp
892
+ Each command accepts only the methods that make sense for it, and uses the
893
+ first as its default:
894
+ .Bd -literal
895
+ command methods accepted default
896
+ \-\-\-\-\-\-\- \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \-\-\-\-\-\-\-
897
+ usb (none: acts on ports) n/a
898
+ serial usb, power usb
899
+ flash serial, power serial
900
+ reset serial serial
901
+ connect usb, serial usb
902
+ .Ed
903
+ .Pp
904
+ \-\-method usb derives the board's USB location from the hub's own geometry,
905
+ four banks of four:
906
+ .Bd -literal
907
+ port 5 ─┬─▸ bank 2 bank is (port minus 1) div 4, plus 1
908
+ └─▸ slot 1 slot is (port minus 1) mod 4, plus 1
909
+
910
+ hub root path ............. 1\-1.2
911
+ openocd adapter usb location 1\-1.2.2.1
912
+ └───┘ │ │
913
+ │ │ └── slot
914
+ │ └──── bank
915
+ └──────── hub root
916
+ .Ed
917
+ .Pp
918
+ That geometry is the ExSYS hub's own.
919
+ A usb hub has none to account for, so a port there is one component
920
+ below the hub's own path: a board on port 3 of the hub at 1\-1.1 is at
921
+ 1\-1.1.3.
922
+ .Pp
923
+ The location is documentation of intent rather than a selector: openocd
924
+ 0.12 ignores 'adapter usb location' for CMSIS\-DAP, reaching the same
925
+ adapter whichever path it is given, including one that does not exist.
926
+ What selects is 'adapter serial', which this method passes as well
927
+ whenever the configuration has one.
928
+ A board with no serial= is therefore
929
+ addressed by nothing in particular, and with more than one adapter
930
+ powered that is a board chosen at random.
931
+ .Pp
932
+ After any power\-on, \-W/\-\-warm\-up seconds (default 5) pass before the boards
933
+ are touched, to let them enumerate.
934
+ .Sh COMMANDS
935
+ Every command takes device names or port numbers interchangeably, and
936
+ operates on every board on the bench when given neither: each declared
937
+ device but those with port = none.
938
+ .Bl -tag -width Ds
939
+ .It Ic usb Cm status | on | off | toggle | set Oo Ar PORT | DEVNAME Oc ...
940
+ Drive hub port power directly.
941
+ \&'set' takes PORT:STATE pairs, where
942
+ STATE is any of on/off/1/0/true/false/t/f.
943
+ With no arguments, 'on'
944
+ means every port the hub has, while 'off' and 'toggle' mean every
945
+ switchable port (see PROTECTED PORTS).
946
+ On a hub whose
947
+ .Cm switch
948
+ is link, everything that powers down says so, naming the ports that
949
+ stay powered.
950
+ .Pp
951
+ \-\-default=BOOL sets what happens to ports 'set' does not name; it goes
952
+ before the word 'set'.
953
+ Without \-\-force it is applied only to
954
+ switchable ports, so it cannot sweep a protected port off.
955
+ \&'status' is read\-only: it asks the hub for its port states and prints
956
+ every port with its device name, its state, and whether the configuration
957
+ lets tribble\-control switch it.
958
+ Run it before anything involving
959
+ \-\-force.
960
+ Note that 'usb on' does not wait out \-\-warm\-up; it returns as soon as
961
+ the hub acknowledges.
962
+ .Pp
963
+ Selection method: none.
964
+ Exit status: 0, or 1 on error.
965
+ .It Ic serial Oo Ar PORT | DEVNAME Oc ...
966
+ Report each board's debug\-probe serial number, read from the USB
967
+ descriptor the kernel already holds.
968
+ Prints one line
969
+ per board, "Serial for alpha: 1026...".
970
+ Use it to populate the configuration.
971
+ .Pp
972
+ Selection methods: usb (default), power.
973
+ Under
974
+ .Cm usb
975
+ it powers the boards' own
976
+ ports up and leaves the rest of the bench alone \(em asking a board its
977
+ serial is not a reason to black out the hub.
978
+ .Cm power
979
+ is the fallback that needs no topology at all, at the price of cutting
980
+ the bench; see
981
+ .Sx TRAPS .
982
+ Exit status: 1 if any
983
+ board could not be read.
984
+ .Pp
985
+ The serial comes from the operating system rather than from openocd,
986
+ which does not report it: openocd 0.12 prints neither a CMSIS\-DAP
987
+ "Serial# =" line nor a J\-Link "S/N" one, at any debug level.
988
+ Asking
989
+ the OS also means no board has to be the only one powered, which
990
+ asking openocd would have required, since it cannot choose between
991
+ adapters by itself.
992
+ .It Ic flash Ar FIRMWARE Oo Ar PORT | DEVNAME Oc ...
993
+ openocd 'flash write_image erase FIRMWARE' then 'reset run', against
994
+ the board's target= script (nrf52 by default) over its interface=
995
+ script (cmsis\-dap by default), on its transport= (swd by default).
996
+ FIRMWARE is resolved by
997
+ openocd relative to the current directory, and is checked to be a
998
+ readable file before any port is switched.
999
+ Every selected board is attempted even if an earlier one failed; the
1000
+ exit status reflects the whole run.
1001
+ A board whose configuration entry lists after\-flash under power_cycle (see
1002
+ CONFIGURATION: the DWM1001\-DEV) has its port powered off and on again after
1003
+ a successful flash; \-\-power\-cycle, before FIRMWARE, does the same for
1004
+ any board.
1005
+ .Pp
1006
+ Selection methods: serial (default), power.
1007
+ Exit status: 1 if any
1008
+ board failed.
1009
+ .It Ic reset Oo Ar PORT | DEVNAME Oc ...
1010
+ openocd 'reset run'.
1011
+ .Pp
1012
+ Selection method: serial only, so it cannot address a board that has
1013
+ no serial= in the configuration.
1014
+ Exit status: 1 if any board failed.
1015
+ .It Ic connect Oo Ar PORT | DEVNAME Oc ...
1016
+ Open each board's CDC console at its baud= (default 230400) and
1017
+ stream it, prefixing every line with <DEVNAME>.
1018
+ Every line is also handed to the board's tally, and on exit each board
1019
+ prints a SUMMARY line with whatever that tally has to say.
1020
+ What the lines MEAN is not decided here \(em see
1021
+ .Sx TALLIES .
1022
+ .Pp
1023
+ Boards are streamed concurrently, one thread each, which is what
1024
+ makes a two\-way\-ranging capture meaningful; the "in sequence" in the
1025
+ method table above describes the order boards are set up in, not the
1026
+ monitoring.
1027
+ .Pp
1028
+ \-\-off powers down every declared board first, for a clean start \-\-
1029
+ every declared board, not only the ones named on the command line \-\-
1030
+ and then powers the selected ones back up.
1031
+ .Pp
1032
+ \-\-reset resets each board over SWD once its console is open.
1033
+ Without
1034
+ it you see only what a board says from the moment the reader attaches,
1035
+ which on an already\-running board is nothing at all until the next
1036
+ ranging exchange: the banner and the driver's init lines \(em including
1037
+ any "Failed to initialize" \(em were printed seconds after power\-on and
1038
+ are gone.
1039
+ Use it whenever you care about why a board is quiet, and
1040
+ note that \-\-off alone does NOT substitute: the port is powered back up
1041
+ before the console is opened, so boot output still escapes.
1042
+ .Pp
1043
+ \-\-duration=SECONDS bounds the capture; the default is 600.
1044
+ .Pp
1045
+ \-\-command=CMD types CMD at each board's shell once it is up (two
1046
+ seconds after a \-\-reset, half a second otherwise) and is the way to
1047
+ reach anything the firmware gates behind a run\-time setting.
1048
+ The
1049
+ case it exists for: redskin compiles spank's log level to warning,
1050
+ but the ranging results are logged at info, so a capture counts zero
1051
+ exchanges on a bench that is in fact working perfectly \-\-
1052
+ .Bd -literal
1053
+ \-\-command='spank syslog info' # the whole info stream
1054
+ \-\-command='spank report all' # just the distances, cheaper
1055
+ .Ed
1056
+ .Pp
1057
+ turns them on without reflashing.
1058
+ Prefer the second: the report path
1059
+ is written straight out by the port instead of going through the
1060
+ syslog, so it is not silenced by the compiled\-in level and costs the
1061
+ firmware less in a loop that is timing sensitive.
1062
+ It is sent once,
1063
+ to every selected board, on a separate write handle; the command is
1064
+ not echoed by tribble\-control, but the board's own echo and reply
1065
+ appear in the capture like any other output.
1066
+ Give it once \-\-
1067
+ repeating the option keeps only the last.
1068
+ ALWAYS run it under 'stdbuf \-oL' if you are piping the output
1069
+ anywhere.
1070
+ Without that, the pipe buffers in 4 KB blocks and 'tee'
1071
+ shows nothing for minutes.
1072
+ .Pp
1073
+ \-\-interactive is \-\-command without the limit of one command decided
1074
+ in advance: standard input is forwarded to the board, a line at a
1075
+ time, and the console stays open until end of input instead of for
1076
+ a \-\-duration.
1077
+ One device only \(em there is one standard input, and
1078
+ two boards sharing it could not be told apart or addressed
1079
+ separately.
1080
+ What the board replies appears in the stream like
1081
+ anything else, prefixed with its name, so what was typed and what
1082
+ came back read in order.
1083
+ It is meant to be run through a terminal \(em 'bench connect' opens it
1084
+ over ssh \-t, which is what supplies the editing and the echo of the
1085
+ line being typed; a pipe works too, and sends what it is given.
1086
+ .Pp
1087
+ \-\-tally=NAME reads every selected board's console with tally NAME
1088
+ for this run, whatever the configuration says; \-\-tally=DEV=NAME does
1089
+ so for board DEV alone.
1090
+ See
1091
+ .Sx TALLIES .
1092
+ .Pp
1093
+ A console that cannot be opened or read, or whose tally raises, prints
1094
+ .Dl <DEVNAME> ERROR: message
1095
+ instead of its SUMMARY line.
1096
+ .Pp
1097
+ Selection methods: usb (default), serial.
1098
+ Exit status:
1099
+ 1 on error, or if any selected board's console was not found or could
1100
+ not be read; otherwise 0, which says nothing about what the boards
1101
+ printed.
1102
+ .El
1103
+ .Sh TALLIES
1104
+ .Nm
1105
+ opens a board's console, prefixes each line with the board's name and
1106
+ prints it.
1107
+ What a line MEANS it does not know, and deliberately does not: the
1108
+ strings worth counting belong to whatever firmware is on the bench this
1109
+ month, they change when that firmware changes, and none of them are
1110
+ facts about a USB hub.
1111
+ .Pp
1112
+ A tally is where that knowledge goes.
1113
+ .Ic connect
1114
+ hands it every line and asks it, once the capture ends, for the text of
1115
+ the board's SUMMARY line.
1116
+ Two come built in:
1117
+ .Bl -tag -width lines
1118
+ .It Cm lines
1119
+ How many lines the board printed.
1120
+ The default, and all a tool that knows nothing about the firmware can
1121
+ honestly say.
1122
+ .It Cm none
1123
+ Nothing: the lines are printed, and no SUMMARY follows them.
1124
+ .El
1125
+ .Pp
1126
+ Any other is a block, registered by a Ruby file named with
1127
+ .Fl r :
1128
+ .Bd -literal
1129
+ # twr-tally.rb
1130
+ class TwrTally
1131
+ def initialize(device) = @ok = 0
1132
+ def <<(line)
1133
+ @ok += 1 if line =~ /whatever this firmware calls success/
1134
+ self
1135
+ end
1136
+ def summary = "ok=#{@ok}"
1137
+ end
1138
+
1139
+ TribbleControl::Tally.register(:twr) {|device| TwrTally.new(device) }
1140
+ .Ed
1141
+ .Pp
1142
+ The object the block returns needs two methods:
1143
+ .Ic <<
1144
+ takes a line and returns anything, and
1145
+ .Ic summary
1146
+ returns the SUMMARY text, or nil for no SUMMARY line at all.
1147
+ The block is called once per board per run, so a tally may keep
1148
+ whatever state it likes without sharing it with another board's.
1149
+ .Pp
1150
+ Which tally a board uses is a configuration key.
1151
+ At the top of the file it sets the bench's default, and inside a device
1152
+ entry it overrides that for one board:
1153
+ .Bd -literal
1154
+ tally = twr # every board on this bench
1155
+
1156
+ D4 { port = 7, tally = lines } # except this one
1157
+ .Ed
1158
+ .Pp
1159
+ The configuration is about boards, not about what is flashed on them,
1160
+ so a run can override it.
1161
+ .Ic connect Fl \-tally Ns = Ns Ar NAME
1162
+ reads every selected board with NAME, and
1163
+ .Fl \-tally Ns = Ns Ar DEV Ns = Ns Ar NAME
1164
+ reads board DEV with NAME; DEV is a device name or a port number, as on
1165
+ the command line.
1166
+ The option may be repeated, or given a comma\-separated list, and every
1167
+ value counts:
1168
+ .Bd -literal
1169
+ connect \-\-tally twr \-\-tally D4=none # D4 runs the probe firmware
1170
+ connect \-\-tally twr,D4=none # the same
1171
+ .Ed
1172
+ .Pp
1173
+ For each board the first that applies wins: its DEV=NAME, the run's
1174
+ NAME, its own
1175
+ .Cm tally
1176
+ key (or its type's), the file's
1177
+ .Cm tally ,
1178
+ then
1179
+ .Cm lines .
1180
+ Two run\-wide names, two names for one board, a DEV the run does not
1181
+ capture, or an empty NAME (\-\-tally= or a doubled comma) are refused
1182
+ rather than resolved by order or left to the configuration.
1183
+ .Pp
1184
+ Every board's tally is built before a port is switched, so a name
1185
+ nothing has registered stops the run before \-\-off cuts the bench.
1186
+ A name nothing has registered is an error, not a silent fall back to
1187
+ counting lines: a configuration asking for twr on a run that forgot
1188
+ .Fl r
1189
+ would otherwise capture the whole bench and report line counts, which
1190
+ reads exactly like a firmware that has gone quiet.
1191
+ .Sh RECIPES
1192
+ Read the serials of every board, to fill in the configuration.
1193
+ This cycles the
1194
+ bench and leaves it powered off:
1195
+ .Bd -literal
1196
+ tribble\-control \-C tribble.conf serial
1197
+ tribble\-control \-C tribble.conf usb on
1198
+ .Ed
1199
+ .Pp
1200
+ Add a board on a new port.
1201
+ \&'serial 7' will not work: an undeclared port
1202
+ has no name, and the tool refuses to power ports the configuration never
1203
+ mentions.
1204
+ Declare a stub first, then read the serial and fill it in:
1205
+ .Bd -literal
1206
+ echo "X1 { port = 11 }" >> tribble.conf
1207
+ tribble\-control \-C tribble.conf serial X1
1208
+ .Ed
1209
+ .Pp
1210
+ Pick a port nothing else declares.
1211
+ Two entries on one port is refused at
1212
+ load time, for every command including 'usb status', so a collision here
1213
+ leaves the configuration unusable until it is edited back.
1214
+ .Pp
1215
+ Flash the whole bench, then just two boards:
1216
+ .Bd -literal
1217
+ tribble\-control \-C tribble.conf flash zephyr.hex
1218
+ tribble\-control \-C tribble.conf flash zephyr.hex alpha beta
1219
+ .Ed
1220
+ .Pp
1221
+ Flash in parallel, leaving the bench powered.
1222
+ Needs a serial for every
1223
+ board named, so fix the gaps first (see CONFIGURATION):
1224
+ .Bd -literal
1225
+ tribble\-control \-C tribble.conf \-\-method serial flash zephyr.hex
1226
+ .Ed
1227
+ .Pp
1228
+ Run a ranging capture to a log, for ten minutes:
1229
+ .Bd -literal
1230
+ stdbuf \-oL tribble\-control \-C tribble.conf connect \-\-off gamma epsilon \e
1231
+ | tee twr.log
1232
+ .Ed
1233
+ .Pp
1234
+ Find out why a board is silent \(em capture its boot, including the driver
1235
+ init lines, for one minute:
1236
+ .Bd -literal
1237
+ stdbuf \-oL tribble\-control \-C tribble.conf connect \-\-off \-\-reset \e
1238
+ \-\-duration=60 gamma beta | tee boot.log
1239
+ .Ed
1240
+ .Pp
1241
+ Capture ranging results from a stock redskin build.
1242
+ Without the command
1243
+ the log level hides them and the SUMMARY reads ok=0 / tx\-rate=NaN:
1244
+ .Bd -literal
1245
+ stdbuf \-oL tribble\-control \-C tribble.conf connect \-\-off \-\-reset \e
1246
+ \-\-command='spank syslog info' \-\-duration=60 gamma beta | tee twr.log
1247
+ .Ed
1248
+ .Pp
1249
+ Power\-cycle one board without disturbing the others:
1250
+ .Bd -literal
1251
+ tribble\-control \-C tribble.conf usb off gamma && sleep 2 && \e
1252
+ tribble\-control \-C tribble.conf usb on gamma
1253
+ .Ed
1254
+ .Pp
1255
+ Deliberately reboot a device on a protected port (the only case for
1256
+ \-\-force).
1257
+ This is a hard power cut; shut the device down over the
1258
+ network first:
1259
+ .Bd -literal
1260
+ tribble\-control \-C tribble.conf \-\-force usb off 14 && sleep 2 && \e
1261
+ tribble\-control \-C tribble.conf usb on 14
1262
+ .Ed
1263
+ .Pp
1264
+ Find out what a usb hub's 'off' really does, before writing
1265
+ \&'switch = vbus' in a configuration.
1266
+ Put a board whose LED lights up on a port, watch the LED, and cut that
1267
+ port:
1268
+ .Bd -literal
1269
+ tribble\-control \-\-hub usb \-C dock.conf usb status
1270
+ tribble\-control \-\-hub usb \-C dock.conf usb off gamma
1271
+ .Ed
1272
+ .Pp
1273
+ An LED that goes out is vbus; an LED that stays lit while the board
1274
+ disappears from the host is link.
1275
+ Do it on the socket the bench will actually use: the USB 2 and USB 3
1276
+ sides of one physical socket are different ports on different hubs, and
1277
+ a hub may switch neither.
1278
+ .Pp
1279
+ The script shipped as examples/vbus\-check runs that recipe: it takes
1280
+ the options you would give
1281
+ .Nm
1282
+ and the port, cuts the port for five seconds, restores it even on
1283
+ Ctrl\-C, and prints the 'switch =' line your answer implies.
1284
+ .Bd -literal
1285
+ examples/vbus\-check \-\-hub usb \-C dock.conf gamma
1286
+ examples/vbus\-check \-n \-\-hub usb \-d AC0528515619 \-F 2 # dry run
1287
+ .Ed
1288
+ .Pp
1289
+ List the switchable hubs this host has, to fill in 'device':
1290
+ .Bd -literal
1291
+ tribble\-control \-\-hub usb usb status
1292
+ .Ed
1293
+ .Pp
1294
+ With one candidate that command drives it.
1295
+ With two or more it refuses and lists them \(em serial, ugen name, USB
1296
+ path and port count \(em which is what to copy into the configuration.
1297
+ .Pp
1298
+ Flash from a script, failing the build if any board failed:
1299
+ .Bd -literal
1300
+ #!/bin/sh
1301
+ cd /root || exit 1
1302
+ exec tribble\-control \-C tribble.conf flash zephyr.hex
1303
+ .Ed
1304
+ .Sh TRAPS
1305
+ Exit status.
1306
+ Any command exits 1 if it aborts with an error \(em a refused
1307
+ power\-down, an unknown device name, a hub that answers E01.
1308
+ On top of
1309
+ that, flash, reset and serial exit 1 when they ran to completion but some
1310
+ board failed; usb and connect report nothing about the work itself, so a
1311
+ 0 from 'connect' says only that it started.
1312
+ Beware that piping into
1313
+ \&'tee' hands you tee's status, not tribble\-control's \(em which is exactly
1314
+ what the capture recipe does, so do not use that shape in a script
1315
+ without a pipefail or a temporary file.
1316
+ .Pp
1317
+ \-\-method power leaves every switchable declared board powered off when
1318
+ it finishes.
1319
+ Nothing defaults to it any more: flash defaults to serial, reset is
1320
+ serial only, and serial and connect default to usb.
1321
+ You get it by asking
1322
+ for it with \-m power, and then you want 'usb on' afterwards to bring the
1323
+ bench back.
1324
+ .Pp
1325
+ connect stops after 600 seconds unless \-\-duration says otherwise; Ctrl\-C
1326
+ is the other way out of a shorter run.
1327
+ Under \-\-interactive there is no
1328
+ timer at all: it ends on end of input (Ctrl\-D) or on Ctrl\-C.
1329
+ .Pp
1330
+ A SUMMARY counting nothing is not proof that a board said nothing.
1331
+ A tally counts the strings one firmware prints, so the wrong tally on a
1332
+ board, or the right one on a firmware whose log level hides the lines,
1333
+ both report a healthy board as an idle one.
1334
+ Check which tally the board is using
1335
+ .Pq Sx TALLIES
1336
+ and that the firmware is logging at all before concluding the radio is
1337
+ dead; \-\-command is how a log level is raised at run time.
1338
+ .Pp
1339
+ \-\-debug is what makes a run verbose; the default is quiet.
1340
+ What it exists to reveal is the openocd command line issued for each
1341
+ board.
1342
+ \-\-debug=FILE also writes the log where it says.
1343
+ If you have a script that greps a run for its openocd invocations, it
1344
+ now has to ask for them.
1345
+ .Pp
1346
+ FreeBSD does everything Linux does, by different means, with one
1347
+ limit worth knowing.
1348
+ Nothing there states a USB path: it is walked out of the sysctl tree,
1349
+ each device's %location giving the port it occupies on its parent and
1350
+ %parent naming that parent, up to a root hub.
1351
+ Note also that openocd is not at
1352
+ /usr/bin/openocd there: pass \-\-openocd=/usr/local/bin/openocd, or just
1353
+ \-\-openocd=openocd and let PATH answer.
1354
+ .Pp
1355
+ The limit is that FreeBSD has no node for a device NO driver claimed
1356
+ \(em there is no dev.ugen \(em so such a device cannot be seen at all.
1357
+ A probe that enumerates and attaches nothing is invisible there rather
1358
+ than serial\-less, and
1359
+ .Fl m Cm usb
1360
+ will report no serial for it.
1361
+ Every probe the bench carries attaches something: a DAPLink is umodem,
1362
+ umass and usbhid at once, a J\-Link OB is umodem, and all of them
1363
+ report the device's serial.
1364
+ .Pp
1365
+ .Ic connect
1366
+ works anyway, with
1367
+ .Fl m Cm serial :
1368
+ the console is found by the serial of the probe in front of the board,
1369
+ which both a DAPLink and a J\-Link OB report, and which the configuration
1370
+ already carries in order to address that board for flashing.
1371
+ No USB tree is walked at all, on either host, which is what makes it
1372
+ the method to reach for when a topology is in doubt.
1373
+ .Pp
1374
+ .Ic serial
1375
+ works there too, with
1376
+ .Fl m Cm power .
1377
+ It has to identify a board it has no serial for yet, which is what
1378
+ .Cm power
1379
+ is for: it cuts every switchable port, brings up one board at a time,
1380
+ and the probe then enumerated is the only one there is.
1381
+ No topology, no descriptor path, nothing Linux has that FreeBSD has
1382
+ not.
1383
+ It costs a cycle of the whole bench and leaves it powered off \(em run
1384
+ .Ic usb on
1385
+ afterwards \(em which is why
1386
+ .Cm usb
1387
+ remains the default wherever the hub's USB path is known.
1388
+ .Pp
1389
+ A probe on a port the configuration protects stays powered through all of
1390
+ that, so it is enumerated alongside the board being asked.
1391
+ .Nm
1392
+ says so and stops rather than attributing one board's serial to
1393
+ another.
1394
+ .Pp
1395
+ \-\-method usb trusts the 4\-by\-4 geometry.
1396
+ A board on port 16 would resolve
1397
+ to 1\-1.2.4.4, which is where the FT232 control adapter already sits, so
1398
+ port 16 is not usable for a board addressed that way.
1399
+ .Pp
1400
+ Two hubs on one host are two benches, and only the configuration can tell
1401
+ them apart.
1402
+ Auto\-detection matches an FTDI 0403:6001, which every FT232 on the host
1403
+ is, so it is refused outright once there is more than one \(em name the
1404
+ hub with
1405
+ .Cm device
1406
+ in each configuration, by the serial of its FT232, and the command that
1407
+ already says which configuration says which hub as well.
1408
+ A command that reaches the wrong hub is not an error anywhere: the
1409
+ ports exist, the frames are accepted, and the boards that go dark are
1410
+ on the other bench.
1411
+ .Pp
1412
+ The hub password defaults to 'pass' (\-p/\-\-password to change it).
1413
+ It
1414
+ guards writes to the hub, not reads.
1415
+ .Pp
1416
+ \&'switch = link' means nothing restarts.
1417
+ A board on a port taken off the bus vanishes from the host exactly as a
1418
+ power cut would, so every command that powers down still works and
1419
+ \-\-method power still selects a board by being the only one visible \(em
1420
+ but the board keeps running and keeps its state.
1421
+ Every power\-down says so once, naming the ports that stay powered, and
1422
+ .Ic flash
1423
+ skips the after\-flash power cycle with a warning instead of pretending:
1424
+ a cycle that only re\-enumerates the probe would leave the board in the
1425
+ very state power_cycle exists to clear.
1426
+ .Pp
1427
+ The power bit is not the same bit on both kinds of hub.
1428
+ A USB 2 hub reports it in 0x0100 of wPortStatus; a SuperSpeed hub
1429
+ reports it in 0x0200 and puts the link state in bits 5\-8, so reading a
1430
+ SuperSpeed port with the USB 2 bit answers "unpowered" for a port that
1431
+ is fine.
1432
+ Which hub descriptor the hub ANSWERS \(em 0x29 or 0x2a \(em is what
1433
+ decides how its status word is read from then on.
1434
+ .Pp
1435
+ .Xr usbconfig 8
1436
+ exits 0 for a request the hub refused, printing REQUEST = <ERROR>, and
1437
+ exits 0 for a device it could not even find.
1438
+ The printed text is the truth; the status says nothing.
1439
+ .Pp
1440
+ A dock's hub chain may reset on its own.
1441
+ The pair of TUSB8041 hubs on the dock this was written against detached
1442
+ and re\-attached every few minutes under test, taking every board with
1443
+ them.
1444
+ A hub that flaps is a poor bench hub whatever its descriptor declares,
1445
+ and the tool cannot tell that apart from a bench somebody unplugged.
1446
+ .Pp
1447
+ The LED test is the only proof that a hub switches VBUS.
1448
+ The read\-back after a switch proves the hub did what it was told, not
1449
+ that the socket lost power: a hub with no power switch wired clears the
1450
+ bit and drops the link, reports itself unpowered, and leaves the board
1451
+ running.
1452
+ Watch a board's LED through 'usb off', on the socket the bench will
1453
+ use, and write the answer down as
1454
+ .Cm switch .
1455
+ .Sh FILES
1456
+ .Bl -tag -width /usr/bin/openocd
1457
+ .It Pa ./tribble-control.conf
1458
+ The configuration, used when
1459
+ .Fl C
1460
+ is not given and this file exists in the current directory.
1461
+ Named explicitly with
1462
+ .Fl C ,
1463
+ it may be at any path and under any name: it describes a bench rather
1464
+ than the tool, so it lives with whatever owns the bench.
1465
+ See
1466
+ .Sx CONFIGURATION .
1467
+ .It Pa /usr/bin/openocd
1468
+ The openocd binary, overridable with
1469
+ .Fl -openocd .
1470
+ .It Pa /usr/sbin/usbconfig
1471
+ How a usb hub is switched, and not overridable: this program switches
1472
+ benches, and what it runs must not depend on whoever's PATH it was
1473
+ started with.
1474
+ .It Pa /sbin/sysctl
1475
+ How the hubs of a
1476
+ .Fx
1477
+ host are found, read as 'sysctl \-e dev.uhub'.
1478
+ .El
1479
+ .Sh SEE ALSO
1480
+ .Xr openocd 1 ,
1481
+ .Xr usbconfig 8
1482
+ .Sh AUTHORS
1483
+ .An Stephane D'Alu Aq Mt sdalu@sdalu.com