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.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stephane D'Alu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,541 @@
1
+ # tribble-control
2
+
3
+ Power, flash and monitor the boards plugged into a switchable USB hub,
4
+ from the host that owns the hub.
5
+
6
+ Two kinds of hub. The ExSYS 16-port managed hub, the default, switches
7
+ VBUS on each of its sixteen sockets independently, over an FT232 serial
8
+ line internal to the hub; `tribble-control` drives that line. Any
9
+ standard USB hub with per-port power switching is the other (`hub =
10
+ usb`), switched on the bus itself with hub-class requests through
11
+ `usbconfig`, and so on FreeBSD only for now. For a socket carrying a
12
+ board with a debug probe the tool also speaks SWD through openocd and
13
+ reads the board's console over USB CDC. Everything else on the hub is a
14
+ load it can switch and nothing more.
15
+
16
+ ```text
17
+ host owning the hub
18
+ │
19
+ ┌───────────────┴───────────────┐
20
+ │ │
21
+ the control path: USB data: the SWD probe
22
+ an FT232 line inside and the CDC console
23
+ the hub, or the bus itself
24
+ │ │
25
+ ▾ ▾
26
+ ┌───────────────────────────────────────────────┐
27
+ │ ExSYS 16-port managed hub, or any hub │
28
+ │ that switches its own ports, 1 to N │
29
+ └───┬───────┬───────┬───────┬─────────┬─────────┘
30
+ │ │ │ │ │
31
+ ▾ ▾ ▾ ▾ ▾
32
+ board board board ... load the hub
33
+ only powers
34
+ ```
35
+
36
+ A file you write — the **configuration** — says which board sits on which
37
+ port, how to reach it, and which ports must never lose power. The tool
38
+ switches the bench it is told about and nothing else.
39
+
40
+ ```sh
41
+ tribble-control -C tribble.conf usb status
42
+ tribble-control -C tribble.conf flash zephyr.hex alpha beta
43
+ tribble-control -C tribble.conf connect --off gamma | tee run.log
44
+ ```
45
+
46
+ The full manual — every option, every configuration key, the hub protocol,
47
+ recipes and traps — is the man page. This file is the short way in.
48
+
49
+
50
+ ## Requirements
51
+
52
+ `tribble-control` runs on the machine owning the hub, not on a
53
+ workstation: it reaches the hub over something plugged into that
54
+ machine — an FT232 line, or the USB bus itself — and needs a local
55
+ openocd to reach a board.
56
+
57
+ * **Ruby 3.1** or later.
58
+ * **openocd**, for every command that reaches a board over SWD:
59
+ `flash`, `reset` and `connect --reset`. It is not a gem and
60
+ installing the gem will not bring it — `pkg install openocd`,
61
+ `apt install openocd`. It is expected at `/usr/bin/openocd`;
62
+ elsewhere pass `--openocd=/usr/local/bin/openocd`, or
63
+ `--openocd=openocd` to have `PATH` answer. `flash` and `reset`
64
+ check for it before they switch a single port.
65
+ * **Linux or FreeBSD.** Every command works on both, by every
66
+ selection method. The two hosts answer "where is this board
67
+ plugged in" differently — Linux states it in `/sys/bus/usb`, and
68
+ FreeBSD is asked to walk its sysctl tree instead — and the one
69
+ thing FreeBSD cannot do is see a device that no driver claimed,
70
+ there being no node for it at all. A probe that enumerates and
71
+ attaches nothing is invisible there rather than serial-less.
72
+
73
+ One step has never run on real hardware: on FreeBSD, placing an
74
+ ExSYS hub's sockets in the USB tree, which `--method usb` (`connect`,
75
+ `serial`) builds a board's path from. It is derived from where the
76
+ hub's FT232 control line sits, and has only been tested against a
77
+ recorded sysctl tree; on Linux the same geometry was checked against a
78
+ live hub. Its failure would be a console opened on the wrong board,
79
+ so the first time on a FreeBSD host with an ExSYS hub, check that
80
+ `connect` names the board you expect, or use `--method serial`.
81
+ * **For `hub = usb`: FreeBSD, and membership of group `operator`.**
82
+ That hub is switched with `usbconfig` hub-class requests, which is
83
+ FreeBSD's command — Linux has none that issues an arbitrary control
84
+ request to a hub — so the backend refuses to open on any other host.
85
+ Root is not required: the ugen nodes are `root:operator` 0660, and
86
+ the kernel demands the driver privilege only for SET_ADDRESS,
87
+ SET_CONFIG and SET_INTERFACE, so a hub-class port feature request
88
+ passes. `pw groupmod operator -m <user>`, then log in again.
89
+
90
+
91
+ ## Installing it
92
+
93
+ Install it on the hub host as a gem, which brings the Ruby
94
+ dependencies with it and needs nothing else:
95
+
96
+ ```sh
97
+ gem install tribble-control # a release, from rubygems.org
98
+ rake install # from a checkout
99
+ ```
100
+
101
+ A release is what to install anywhere results are recorded: its version
102
+ names exactly one tree. A checkout built with `rake install` carries
103
+ the number of the last release whatever has been committed since, so
104
+ `tribble-control --version` cannot tell the two apart.
105
+
106
+ Or, on a host that should not gain gems system-wide, deploy the
107
+ checkout and vendor the bundle beside it:
108
+
109
+ ```sh
110
+ bundle install --path vendor
111
+ bundle binstubs tribble-control
112
+ ./bin/tribble-control --man
113
+ ```
114
+
115
+ The binstub sets that bundle up before loading anything, so it is what
116
+ such a host should call.
117
+
118
+
119
+ ## The configuration
120
+
121
+ Nearly every command needs one, given with `-C`/`--config`. Without
122
+ it, a file named `tribble-control.conf` in the current directory is
123
+ used if there is one; neither the home directory nor `/etc` is ever
124
+ consulted, and giving `-C` turns that lookup off. It describes a setup
125
+ rather than the tool, so it lives with whatever owns the setup;
126
+ `examples/tribble.conf` is a commented file to start from. Deploy the
127
+ two together — they are read together, and a hub with one of them
128
+ fresh and the other stale switches the wrong ports.
129
+
130
+ If the two must move separately, move the **tool first**. Every
131
+ top-level key this tool does not know is taken for a board, and a board
132
+ must declare a port, so a configuration carrying a newer key meets an
133
+ older tool as `devlist entry 'device' has no port`. That is a refusal
134
+ before anything is switched rather than a bench in the wrong state —
135
+ but the command does not run, so upgrade in that order.
136
+
137
+ ```text
138
+ # Which kind of hub, and which one. 'exsys' is the default: an ExSYS
139
+ # hub, named by its FT232's serial number. 'usb' is any hub with
140
+ # per-port power switching, named by its own serial, and only that kind
141
+ # takes a 'switch' line.
142
+ hub = exsys
143
+ device = AL03GD7X
144
+
145
+ # What must never be powered down: ports by number, entries by name,
146
+ # and every port the file does not mention unless 'undeclared = no'.
147
+ protect {
148
+ ports = [ 13, 14, 15, 16 ]
149
+ nodes = [ rpi ]
150
+ }
151
+
152
+ # What a KIND of board is, said once instead of on every board.
153
+ types {
154
+ nrf52840-mdk {
155
+ interface = cmsis-dap # openocd interface script: which probe
156
+ target = nrf52 # openocd target script: which chip
157
+ baud = 230400 # console speed
158
+ }
159
+ }
160
+
161
+ # Every other key is a device. `port` is required.
162
+ alpha {
163
+ type = nrf52840-mdk
164
+ serial = '102636...97969902' # the PROBE's serial, pasted whole
165
+ port = 1
166
+ }
167
+
168
+ # No serial: still switchable, still has a console, but it cannot be
169
+ # reset or flashed in parallel.
170
+ gamma {
171
+ port = 2
172
+ }
173
+
174
+ # Something the bench only feeds. Declaring it is what lets 'protect'
175
+ # name it, and what puts 'rpi' in the 'usb status' row for port 12.
176
+ rpi {
177
+ port = 12
178
+ }
179
+
180
+ # `port = none` keeps the record of a board that has left the bench.
181
+ # It is never selected, switched or flashed, and its serial survives.
182
+ retired {
183
+ serial = '102636...97969902'
184
+ port = none
185
+ }
186
+ ```
187
+
188
+ A device's own keys win over the type's. Read a probe serial off a
189
+ powered board with `tribble-control serial <name>` and paste it whole:
190
+ 48 hex characters for CMSIS-DAP, 12 for J-Link.
191
+
192
+
193
+ ## Which hub
194
+
195
+ Two things to settle: which *kind* of hub, and which hub of that kind.
196
+
197
+ The kind is `hub =` at the top of the configuration, or `--hub` on the
198
+ command line: `exsys` (the default) or `usb`. It names what the hub
199
+ **is**, not which tool drives it on this host, so the same
200
+ configuration line keeps working the day a Linux implementation lands.
201
+ It cannot be read off the
202
+ `device =` value, either — a USB path such as `1-1.1` names an FT232's
203
+ socket for an ExSYS hub and the hub itself for a usb hub — so it has to
204
+ be said.
205
+
206
+ An ExSYS hub on a host with one hub needs to be told nothing: the tool
207
+ looks for the FTDI 0403:6001 that is a hub's control adapter and drives
208
+ the one it finds.
209
+
210
+ A host with two is two benches, and it refuses to guess — that FTDI id
211
+ is every FT232 on the machine, so the first one enumerated is a coin
212
+ toss, and a command that reaches the wrong hub is not an error
213
+ anywhere: the ports exist, the frames are accepted, and the boards that
214
+ go dark are on the other bench. It lists what it found instead, with
215
+ serials:
216
+
217
+ ```text
218
+ tribble-control: unable to auto-detect the hub control line: 2 FTDI
219
+ 0403:6001 adapters on this host (found: A50285BI on /dev/ttyUSB0,
220
+ AL03GD7X on /dev/ttyUSB1). Name the one to drive with -d, or with a
221
+ 'device =' line in the configuration.
222
+ ```
223
+
224
+ `exsys-usb discover`, from the exsys gem, lists them the same way
225
+ without switching anything:
226
+
227
+ ```text
228
+ /dev/ttyUSB0 A50285BI 1-1.2.4.4
229
+ /dev/ttyUSB1 AL03GD7X 1-1.3.4.4
230
+ ```
231
+
232
+ Put that serial in the configuration, as `device =` at the top, and
233
+ `-C` alone selects a bench — which is already the thing every command
234
+ has to say. `-d` overrides it for the one-off.
235
+
236
+ Either takes three shapes, told apart by what they look like:
237
+
238
+ | Written | Means | Stays with |
239
+ | :------------- | :--------------------------- | :----------- |
240
+ | `AL03GD7X` | the FT232's serial number | the adapter |
241
+ | `1-1.2.4.4` | a USB path on this host | the socket |
242
+ | `/dev/ttyUSB1` | the serial line itself | nothing |
243
+
244
+ The last is the one not to write down. The `1` in `/dev/ttyUSB1` is
245
+ not the hub's number, and not the USB device number either — it is the
246
+ usbserial layer's index, and it is the lowest one free when that
247
+ adapter is probed. So it depends on what else attached first, and it
248
+ is reused: unplug whatever holds `ttyUSB0` and the next thing to attach
249
+ takes `ttyUSB0`. Two hubs can swap names across a reboot, or while the
250
+ machine is up.
251
+
252
+ Between the other two: a serial names *this particular hub* and follows
253
+ it to another socket or another machine, which is usually what a bench
254
+ wants. A USB path names *whatever is plugged into that socket*, a
255
+ replacement hub included. Reach for the path when the hub's EEPROM
256
+ carries no serial — then it is the only stable name it has — or when
257
+ the socket is the fixed thing. Both platforms report one, by different
258
+ means: Linux states it in `/sys`, and on FreeBSD it is walked out of
259
+ the sysctl tree. The numbering is each host's own, so a path names a
260
+ socket on the machine that reported it and does not travel.
261
+
262
+ A `hub = usb` is named the same three ways, in its own shapes:
263
+
264
+ | Written | Means | Stays with |
265
+ | :------------- | :-------------------------- | :---------- |
266
+ | `AC0528515619` | the hub's own serial number | the hub |
267
+ | `1-1.1` | a USB path on this host | the socket |
268
+ | ugen1.4 | that device, used as given | nothing |
269
+
270
+ A ugen name is this kind's `/dev/ttyUSB1` and carries the same warning:
271
+ the number is enumeration order — ugen1.4 is the fourth device the
272
+ second controller attached — so a replug renumbers it, and a
273
+ configuration naming a hub that way points at whatever attached in its
274
+ place. Keep it
275
+ for the one-off; write the serial, or the path when the socket is the
276
+ fixed thing.
277
+
278
+ Auto-detection refuses the same way: one candidate is taken, two or more
279
+ are listed and the command stops, each candidate with its serial, its
280
+ ugen name, its USB path and its port count — the count being the only
281
+ thing that tells two of the same part apart when neither has a serial.
282
+ Root hubs are never candidates. They are the controller a host's own
283
+ sockets hang off, and a port of one has no `PORT_POWER` to clear, so
284
+ offering one would offer a hub every command against it then failed on.
285
+
286
+ The ports are the hub's own: 1 to the `bNbrPorts` its hub descriptor
287
+ reports, read once when the hub is opened. The fixed sixteen is the
288
+ ExSYS hub's alone, and `protect { undeclared = yes }` earns its keep
289
+ here — on a dock, one hub port feeds the next hub in the chain and
290
+ another feeds the Ethernet adapter, and neither is a port to sweep off.
291
+
292
+
293
+ ## What "off" does
294
+
295
+ The ExSYS hub cuts VBUS: `usb off` takes the power away from the socket
296
+ and the board on it stops. A standard hub may do that, or may only take
297
+ the port off the bus, depending on whether a power switch is wired to
298
+ the socket at all — and no software can tell the two apart, because a
299
+ hub with none still reports the port unpowered and drops the link, so
300
+ the device vanishes from the host and comes back either way. The
301
+ operator says which, with `switch =` at the top of a `hub = usb`
302
+ configuration:
303
+
304
+ | Written | Means |
305
+ | :-------------- | :----------------------------------------------- |
306
+ | `switch = link` | the default: `off` takes the port off the bus |
307
+ | `switch = vbus` | `off` cuts the socket's power |
308
+
309
+ On an ExSYS hub the key is refused rather than ignored: that hub always
310
+ cuts power, and a line that changes nothing is a line somebody will
311
+ trust.
312
+
313
+ Find out which yours is by watching a board's LED. Put a board that
314
+ lights up on a port, run `usb off` for that port, and look: an LED that
315
+ goes out means `vbus`, an LED that stays lit while the board disappears
316
+ from the host means `link`. Test the socket you will actually use — the
317
+ USB 2 and USB 3 sides of one socket are different ports on different
318
+ hubs, and a hub may switch neither.
319
+ `examples/vbus-check` runs the recipe: give it the options you would
320
+ give `tribble-control` and the port, it cuts the port for five seconds,
321
+ restores it even on Ctrl-C, and prints the `switch =` line your answer
322
+ implies.
323
+
324
+ Under `switch = link` everything that powers down still works. `usb
325
+ off`, `toggle` and `set` behave, the board vanishes from the host
326
+ exactly as a power cut would, and `--method power` still identifies a
327
+ board by being the only one openocd can see. What does not happen is
328
+ the board restarting. So every power-down warns once, naming the ports
329
+ that stay powered:
330
+
331
+ ```text
332
+ ugen1.4 cuts the link, not the power: the board(s) on port(s) 1 2 stay
333
+ powered (switch = link)
334
+ ```
335
+
336
+ and the after-flash power cycle (`power_cycle = after-flash`, the
337
+ DWM1001's trap) is skipped with a warning rather than pretended — a
338
+ cycle that only re-enumerates the probe leaves the board in the very
339
+ state the cycle exists to clear.
340
+
341
+ After every switch the port's status is read back, and the command fails
342
+ if the power bit did not follow. That read-back is the tool's only
343
+ measurement of whether a hub switches at all: a hub that accepts
344
+ `CLEAR_FEATURE(PORT_POWER)`, answers OK and leaves the port up would
345
+ otherwise have `usb off` report success on a bench it never touched.
346
+ Nothing is refused on what a hub *declares* about its switching, for the
347
+ same reason — the dock's Genesys hub declares ganged and switches per
348
+ port anyway.
349
+
350
+
351
+ ## Protected ports
352
+
353
+ A hub port carries whatever is plugged into it, and cutting VBUS on a
354
+ single-board computer reboots it mid-write. So powering a port *down*
355
+ is guarded, and powering one *up* is not:
356
+
357
+ * Ports the configuration does not mention are protected. `protect {
358
+ undeclared = no }` lifts that for the unmentioned ones.
359
+ * Ports `protect` names are protected whichever mode is in force —
360
+ `ports` for a number, `nodes` for the name of a configuration entry,
361
+ which protects whichever port that entry says the board is on.
362
+ * `-F`/`--force` lifts both. Without a configuration at all,
363
+ `tribble-control` refuses to power anything down.
364
+
365
+ The two keys this replaced — `reserved`, and `undeclared` at the top of
366
+ the file — are refused by name, each with the line to write instead. A
367
+ configuration written for 0.2.0 or earlier does not load and does not
368
+ switch anything until its protection is rewritten as the block above.
369
+
370
+ `usb status` prints the hub's own view next to the configuration's, so
371
+ you can see what is on and what may be switched before switching it.
372
+ The protection is only ever as current as the file: a stale
373
+ configuration guards the ports it used to know about.
374
+
375
+
376
+ ## Commands
377
+
378
+ | Command | What it does |
379
+ | :-------- | :-------------------------------------------------- |
380
+ | `usb` | Port power: `status`, `on`, `off`, `toggle`, `set`. |
381
+ | `serial` | Print a board's debug-probe serial number. |
382
+ | `flash` | Write a firmware image to one or more boards. |
383
+ | `reset` | Reboot boards over SWD. |
384
+ | `connect` | Open board consoles, print their lines, summarise. |
385
+
386
+ Every command takes device names or port numbers interchangeably, and
387
+ acts on every board on the bench when given neither (each declared
388
+ device but those with `port = none`). Note that `serial`
389
+ prints a probe's identifying number, not console text — reading a
390
+ board's console is `connect`.
391
+
392
+ Naming a board is not the same as telling openocd which one to talk
393
+ to, and `-m`/`--method` is how that is decided. Each command accepts
394
+ only the methods that make sense for it and uses the first as its
395
+ default:
396
+
397
+ | Method | Addresses a board by | Needs |
398
+ | :------- | :------------------------- | :----------------- |
399
+ | `serial` | its debug probe's serial | `serial =` on each |
400
+ | `usb` | its USB path under the hub | a visible topology |
401
+ | `power` | being the only one powered | nothing |
402
+
403
+ | Command | Methods accepted | Default |
404
+ | :-------- | :---------------------- | :------- |
405
+ | `usb` | none — it acts on ports | — |
406
+ | `serial` | `usb`, `power` | `usb` |
407
+ | `flash` | `serial`, `power` | `serial` |
408
+ | `reset` | `serial` | `serial` |
409
+ | `connect` | `usb`, `serial` | `usb` |
410
+
411
+ `serial` is the default for `flash` and `reset` because it is the fast
412
+ one: every board stays powered and they are done at the same time, one
413
+ openocd apiece. It needs a `serial =` on each selected board, and
414
+ aborts if one is missing.
415
+
416
+ `power` is the fallback that needs no configuration: it identifies a
417
+ board by being the only one powered. It costs a power cycle per board
418
+ and **leaves the bench powered off** when it finishes — every declared
419
+ board whose port may be switched, that is; one the configuration
420
+ protects stays powered and says so. Run `usb on` afterwards to bring
421
+ the bench back up. A probe left powered on such a port would be a
422
+ second adapter in front of openocd, so a run that finds more than one
423
+ probe console once a board is up is refused.
424
+
425
+
426
+ ## Reading a console
427
+
428
+ `connect` opens the console of each selected board, prefixes every line
429
+ with the board's name, and prints it. What those lines *mean* is not
430
+ this tool's business — the strings worth counting belong to whatever
431
+ firmware is on the bench this month. So `connect` hands each line to a
432
+ **tally**, which counts whatever it likes and writes the `SUMMARY`
433
+ line.
434
+
435
+ Two tallies ship: `lines` counts lines, and `none` writes no summary at
436
+ all. Anything that knows a firmware's strings is a block registered by
437
+ a Ruby file named with `-r`/`--require` and chosen with `tally =` in
438
+ the configuration, for the whole bench or for one board. A run can
439
+ override the configuration with `connect --tally NAME` for every board it
440
+ captures, or `--tally DEV=NAME` for one, which is how a board reflashed
441
+ with other firmware is read without editing the file:
442
+
443
+ ```sh
444
+ tribble-control -C tribble.conf -r twr-tally.rb connect --tally twr --tally D4=none
445
+ ```
446
+
447
+ See DESIGN.md and the manual's TALLIES section.
448
+
449
+
450
+ ## The manual
451
+
452
+ The full manual is a man page, `man/man1/tribble-control.1`, and ships
453
+ inside the gem.
454
+
455
+ ```sh
456
+ tribble-control --man # renders the shipped page, wherever it is
457
+ rake man:install # PREFIX=/usr/local, for real `man` access
458
+ mandoc man/man1/tribble-control.1
459
+ ```
460
+
461
+ `--man` renders the page with the first of `mandoc`, `groff` or `nroff`
462
+ it finds, pages it when the output is a terminal, and hands plain text
463
+ to a pipe. RubyGems installs nothing outside the gem directory, so
464
+ `gem install` alone will not make `man tribble-control` work; the page
465
+ ships at `man/man1/` inside the gem precisely so the gem's `man`
466
+ directory can serve as a `MANPATH` entry if you would rather not copy
467
+ it anywhere.
468
+
469
+ `--help` lists the commands, and `CMD --help` the options of one.
470
+
471
+
472
+ ## Tests
473
+
474
+ ```sh
475
+ rake # everything that needs no hub, and the linter
476
+ rake test # the minitest suite: configuration, types, tallies, the
477
+ # openocd command line, the ExSYS hub exchange against
478
+ # a pty emulator and the usb one against a fake
479
+ # usbconfig
480
+ rake lint # rubocop, gating at zero offences
481
+ rake test:bench # the regression suite, which needs the bench
482
+ ```
483
+
484
+ The bench suite drives a **deployed** copy over ssh, because that is
485
+ the machine with the hub on the end of a serial line. There is no
486
+ default host — one lab's address does not belong in a tool meant to
487
+ drive any ExSYS hub — so it refuses to start without one:
488
+
489
+ ```sh
490
+ sh test/test-tribble-control HOST [tribble-control-path] [tally-path]
491
+ TRIBBLE_HOST=<host> rake test:bench # also TRIBBLE_PATH, TRIBBLE_TALLY
492
+ ```
493
+
494
+ It is written to be safe to run on a live bench: nothing writes flash
495
+ or switches a port off unless `TRIBBLE_TEST_FLASH=1` asks for the
496
+ power-cycle gate. One test resets a board deliberately, as a positive
497
+ control, and the `connect` and `flash` tests power the ports of the
498
+ boards they name up, which is the benign direction.
499
+
500
+ The second argument points the suite at a different copy of the tool —
501
+ a deliberately broken one, say, since a test that has never been seen
502
+ to fail is not evidence. The third names the tally, which any command
503
+ that opens a console needs when the configuration asks for one by name.
504
+
505
+
506
+ ## Hacking on it
507
+
508
+ DESIGN.md is the companion to this file: how the pieces fit, and where
509
+ the seams are for a new board family, a new probe, a new host platform,
510
+ a new command or a new tally.
511
+
512
+
513
+ ## The name
514
+
515
+ Tribbles are the small, furry, gentle and extremely numerous creatures
516
+ of ["The Trouble with Tribbles"][episode] (*Star Trek*, 1967, written by
517
+ David Gerrold). McCoy works out why there are so many: they are born
518
+ pregnant, and spend over half their metabolism reproducing. By the end
519
+ of the episode they have filled the ship, and Kirk is [shoulder-deep in
520
+ them][kirk].
521
+
522
+ A bench fills up the same way. One board becomes three, three become
523
+ eleven, they are identical, every one of them wants power, and not one
524
+ of them will tell you which socket it is sitting in. That last part is
525
+ what the configuration is for.
526
+
527
+ ![Tribble props from the Star Trek exhibit at the Henry Ford Museum][photo]
528
+
529
+ Photo by Joe Ross, [CC BY-SA 2.0][licence], via [Wikimedia Commons][page].
530
+
531
+ [episode]: https://en.wikipedia.org/wiki/The_Trouble_with_Tribbles
532
+ [kirk]: https://en.wikipedia.org/wiki/Tribble#/media/File:ST_TroubleWithTribbles.jpg
533
+ [photo]: https://commons.wikimedia.org/wiki/Special:FilePath/Tribbles!_-_Star_Trek_-_Exploring_New_Worlds_Exhibit_at_the_Henry_Ford_Museum.jpg?width=480
534
+ [page]: https://commons.wikimedia.org/wiki/File:Tribbles!_-_Star_Trek_-_Exploring_New_Worlds_Exhibit_at_the_Henry_Ford_Museum.jpg
535
+ [licence]: https://creativecommons.org/licenses/by-sa/2.0/
536
+
537
+
538
+ ## License
539
+
540
+ MIT. See LICENSE. The photograph above is not mine and is not MIT; it
541
+ carries the CC BY-SA 2.0 licence credited with it.