So this took many hours and much frustration, but I've got an updated libdivecomputer tree at
https://github.com/torvalds/libdc-for-dirk
which has the iostream code from Jef's upstream libdivecomputer.
I tried very hard to actually make use of the new iostream code, and in fact it does help get rid of one particularly ugly hack of ours: we used to hook into the serial code with a particularly ugly little hack using the RETURN_IF_CUSTOM_SERIAL() macro.
We now avoid that, and actually create our own custom iostream for that case, and then the new iostream code does things right.
HOWEVER.
We don't actually use Jef's "custom" iostream layer for it, and most particularly, we don't actually expose that to the outside. The actual outside interface remains our trusty old "dc_custom_io_t", and the special custom serial case is actually just a trivial wrapper inside libdivecomputer for that. It's only used for the legacy serial emulation (ie notably Shearwater and OSTC). The Scubapro G2 and EON Steel end up using the subsurface dc_custom_io_t model directly, since for them it's not just a serial stream.
End result: it all looks ok. I've tested it here with my EON Steel (both BLE and USB), my Scubapro G2 (also BLE and USB) and my Shearwater Perdix AI (BLE only, obviously).
But I only pushed to my own "libdc-for-disk" repository, because this is a *big* change, and I'd like at least Dirk to check that it works for his cases too.
Jef: I basically ended up using the "custom" approach for just the internal serial emulation. I *considered* just changing your "custom.c" file to make that work for me, but ended up just instead adding a "src/custom_io.c" file instead. It's pretty close to your custom.c file, but it is explicitly designed to interface with our "dc_custom_io_t". As mentioned elsewhere, I don't think your custom implementation actually is usable for outsiders.
Dirk - caveat emptor. I think it's all good. It looks sane. It passes my tests. But the diffs to both of the parents in that merge is complicated, and the merge does a fair amount of things that were not in either parent since it only made sense as part of the merged state.
I'm actually fairly happy with the end result, but the merge itself was painful enough that you really should check it out.
Linus