4ms / stm32mp1-baremetal

Baremetal framework and example projects for the STM32MP15x Cortex-A7 based MPU
Other
148 stars 28 forks source link

USB example #2

Closed antisvin closed 1 year ago

antisvin commented 2 years ago

Hi Dan,

I've been watching progress here for a while and would like to know if you've considered adding USB support as one of the examples. It's sort of possible to add USB device/host with OTG peripheral on A7 cores despite only Linux being supported by ST officially, see here - https://community.st.com/s/question/0D53W00000GYk3RSAT/how-to-manage-usb-with-a-baremetal-application-

Supposingly working implementation is at https://github.com/ua1arn/hftrx/tree/develop/src/hal

Regards, Stas

danngreen commented 2 years ago

Yes I would like to add USB support, but I haven't used USB on any embedded projects, so I'm a bit unsure what direction to go. Do you have experience? What type of features would you find useful? (HID, DFU, MSC, Audio, MIDI...) I think a DFU loader would be a nice convenience, and USB MIDI would be great, too (which is a subset of USB Audio, I think).

That's good to hear the H7 USB libraries work on the MP1 (perhaps with some minor tweaking). The STM32Cube_FW_H7 library contains ST's USB library. Looks like the STM32H743I-EVAL folder has the quite a few USB example applications: https://github.com/STMicroelectronics/STM32CubeH7/tree/master/Projects/STM32H743I-EVAL/Applications/USB_Host https://github.com/STMicroelectronics/STM32CubeH7/tree/master/Projects/STM32H743I-EVAL/Applications/USB_Device

antisvin commented 2 years ago

Well the reason why I'm interested in this is to try porting OWL firmware. It's officially running on F4, but I did port it to H7 for Daisy Patch some time ago and H7 is supported in upstream firmware nowadays (except that there's no released devices yet). If that would be too untrivial, I could use it just to experiment with NEON and such, but I already use Bela for this.

As for USB on OWL, it creates a composite audio + MIDI device and a host. I wasn't involved in writing any of USB code, but I know that there's a fairly long way from what ST provides in their samples to a working solution. In bootloader mode OWL configures a MIDI-only device that accepts firmware in sysex format and some other commands. I'm not sure what's the preferred approach for this is on MP1, I guess it should be writing to SD card so fatfs would have to be integrated.

You might to have a look at Martin's usb* files here - https://github.com/pingdynasty/OpenWare/tree/master/Source . In case of my particular use case, OWL requires a MIDI device to be usable, everything else is optional. Obviously having a host is very welcome when there's available hardware.

Regarding audio devices, this can be challenging if you're new to USB (I certainly am!), as far as I understand synchronous audio (that OWL currently supports) is easier to implement but would give you synchronization issues for audio input. So it would be replaced with async audio at some point of future, for now it's configured as having output channels only.

danngreen commented 2 years ago

Nice, so the H7 support for USB is already working in the master branch of OpenWare? I'll make some time to look at the usb implementation. I see the CubeH7 and ST USB libraries in that repo, so that points to "yes".

I should mention that U-boot configures and uses one of the USB ports on the MP1 for the USB-gadget feature. So it may be possible to spring-board off that.

The Daisy has a USB DFU feature, right? That could be another starting point.

antisvin commented 2 years ago

Yes it works on H7, I have 3 different devices that can run OpenWare on that MCU family. But no changes were required for porting that USB code to H7, I gave it a go on Daisy before there was any official support for H7 and it just worked as is. ST middleware for USB is not platform-specific. So only the Cube middleware driver would differ per MCU family.

Daisy uses the system DFU bootloader flashed by ST, it doesn't have its own. They have plans to add one for flashing QSPI eventually as it runs on H750 and 128kb of internal flash is too limiting. Besides that they need some way to deploy multiple patches.

danngreen commented 2 years ago

I've had some time to look at the OpenWare USB setup. I used the Magus project as an example to study since there's an H7 version (Magus3).

It looks like there are two STM32Cube HAL drivers that the USB in Magus depends on, which aren't in the MP1 HAL. Those files are stm32h7xx_hal_pcd.c and stm32h7xx_ll_usb.c

So, they'd both need to be ported.

The LL USB driver will probably take the most work to port. It's mostly register access to the USB peripheral. Looking at the Reference Manuals for H7 vs MP1, the USB peripherals look pretty similar. I haven't checked every bit in the registers, but scanning over the register maps shows they're close.

The PCD driver shouldn't be too hard to port once the LL USB driver is ported. It mostly calls lower level functions in stm32h7xxll usb.c, although it does interact with some USB registers directly as well.

Once those two files are ported, it looks like the middleware can be work as-is. Or at least, we'll be a whole lot closer.

antisvin commented 2 years ago

Those files have been ported in another project that I've mentioned - https://github.com/ua1arn/hftrx/tree/develop/lib/Drivers/STM32MP1xx_HAL_Driver/Src . My understanding that USB peripheral is identical, just has different base address according to this note in his code:

  // CID:
  // H7: 0x0000 2300
  // MP1: 0x0000 4000
  if (((USBx->CID & (0x1U << 8)) != 0U) && (hc->core_speed == USBH_HS_SPEED))
danngreen commented 2 years ago

Aha! Yes, so it's already done then. Those files (and also the stm32mp1xx_hal_hcd driver files, which might be needed too?) have the "h7" comment headers still.

danngreen commented 2 years ago

So, a diff reveals the stm32mp1_ll_usb.c/h files are almost identical to the H7 STM32CubeHAL version except for some added USBPHYC initialization code. The function USB_HS_PHYCInit() does a lot of this init, and more is in USB_Core_Init() and USB_Dev_Init(). The H7 doesn’t have a separate USBPHYC peripheral, but I’m not really sure what the PHYC actually does. USBC-specific PHY interface, I suppose? Anyways the USBPHYC init code in hftrx references some code from the u-boot source here:

https://github.com/u-boot/u-boot/blob/master/drivers/phy/phy-stm32-usbphyc.c

It looks like the MP1-ported code modifies the same registers, just using CMSIS device structs instead of the u-boot wrappers. But there could be more going on here than I realize. Interestingly, there is a function in F7 HAL with the same name USB_HS_PHYCInit():

https://github.com/STMicroelectronics/STM32CubeF7/blob/c7c5ec99c7482ea8bcdbf0a869c930af4547088f/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_ll_usb.c#L1376

So…long story short, I’ll have to read up on this peripheral.

The other change of substance doesn't seem to have to anything to do with the PHYC. It's two lines commented out:

    /* make sure to reserve 18 fifo Locations for DMA buffers */
    USBx->GDFIFOCFG &= ~(0xFFFFU << 16);
    USBx->GDFIFOCFG |= 0x3EEU << 16;

The hal_pcd driver files are identical except for HAL_RCC_GetAXISSFreq() is used instead of HAL_RCC_GetHCLKFreq(), and some things marked "Renesas specific hardware". And some alignment and casts to maybe avoid compiler warnings, I'd guess.

danngreen commented 2 years ago

Oh and BTW, that bit about USBx->CID, I looked into it, and CID is the "Core ID" register. There's a USB OTG_CID register in the MP1 with offset 0x4000 that contains a 32-bit "application-programmable" PRODUCT_ID.

Regarding peripheral Base Address, it looks like some umm.. "creative" macros are being used for this, so watch out. To see what I mean, check out USB_Dev_Init() in ll_usb.c:

HAL_StatusTypeDef USB_DevInit(USB_OTG_GlobalTypeDef *USBx, USB_OTG_CfgTypeDef cfg)
{
  uint32_t USBx_BASE = (uint32_t)USBx;
[…]
  USBx_DEVICE->DCFG |= DCFG_FRAME_INTERVAL_80;

USBx_BASE is a local variable set to the address of the HAL Handle that's passed in. But it's not used in the function anywhere. Instead, we see USBx_DEVICE (and others) being used, which are defined in stm32h7xx_ll_usb.h

#define USBx_DEVICE     ((USB_OTG_DeviceTypeDef *)(USBx_BASE + USB_OTG_DEVICE_BASE))

So USBx_DEVICE and friends are macros that silently use a local variable :/

Anyways... all this is to say, the ported drivers look good (except I don't know about the PHYC stuff), and might just work to drop into an OpenWare project (if the hardware is there, of course), at least for getting a MIDI-only device working, like you mentioned the Owl bootloader requires. Writing to a fatfs is not a hard, I've used this library. Also u-boot already has fatfs support.

I'll try to come up with a simple USB example project for this repo to test out the ported drivers. Maybe USB MIDI if it's not too complex, though maybe something more simple to start. I'll play around...

antisvin commented 2 years ago

The MP1 docs list the following peripherals for USB:

2 × USB 2.0 high-speed Host + 1 × USB 2.0 full-speed OTG simultaneously
– or 1 × USB 2.0 high-speed Host + 1 × USB 2.0 high-speed OTG simultaneously

Or from their wiki:

USBH (2 x HS Host) - AXIM bus - https://wiki.st.com/stm32mpu/wiki/USBH_internal_peripheral OTG (HS/FS) - AHB - https://wiki.st.com/stm32mpu/wiki/OTG_internal_peripheral USBPHYC(USB 2 x PHY control) - APB4 - https://wiki.st.com/stm32mpu/wiki/USBPHYC_internal_peripheral

So there are 2 HS PHY on USBH and 1 FS or HS PHY on OTG. You can only use the second HS PHY on USBH or OTG HS PHY, not both at once. This is determined by UTMI switch controlled by USBPHYC. USBPHYC is also used for tuning/calibrating USBH PLL.

The 4 USB type A ports on ST Discovery boards are connected to USBH1 via a hub. There's no support for USB hubs in ST USB middleware, so they become unusable due to this. There is third party library for using hubs written for F4 - https://github.com/mori-br/STM32F4HUB . But it's probably too much of an effort to use with OpenWare.

USB type C port on DK is connected to OTG HS, this should be usable if it gets configured via USBPHYC. USBH2 is not used on this board.

Also, according to block diagram in https://my.st.com/resource/en/datasheet/stm32mp157c.pdf page 20, OTG is connected to M4 core over AHB, while other 2 peripherals are connected to A7 core. Ref manual also says that OTG can raise interrupts with GIC or NVIC. OTG FS/HS pins are different and only HS is broken out on Discovery board, but there are boards where OTG FS is available (i.e. STMP157-OLinuXino-LIME2-EXT)

To make things more challenging, OpenWare uses FreeRTOS and that's not ported to Cortex-A7. There's a port for A9 and it's supposed to be very similar. But if M4 core can use OTG, it makes more sense to try to run firmware on M4 core and load patches to A7. So I guess I'll start with rproc example and try to run it on M4 core.

danngreen commented 2 years ago

Ok, thanks for explaining the PHYC, that makes more sense now.

Also, according to block diagram in https://my.st.com/resource/en/datasheet/stm32mp157c.pdf page 20, OTG is connected to M4 core over AHB, while other 2 peripherals are connected to A7 core. Ref manual also says that OTG can raise interrupts with GIC or NVIC.

Hmm... you might still be able to use the OTG from the A7, since the AHB-AXI bus connects the two domains. At least, I've been able to access other peripherals and memory regions on the M4 side from A7 cores.

OTG FS/HS pins are different and only HS is broken out on Discovery board, but there are boards where OTG FS is available (i.e. STMP157-OLinuXino-LIME2-EXT)

Actually I think the OTG FS pins are broken out on the Discovery board. The Arduino connector (CN13) pins 9 and 10 are PA11 and PA12, which are OTG_FS_DP and _DM. Also PA8 is connected to the EXP_GPIO header (CN2), pin 7 aka EXP_GPIO4, which is OTG_FS/HS_SOF. But OTG_FS/HS_ID is connected to the HDMI chip and a 10k pull-up, with no break-out. I don't know if that's a required pin for USB functionality.

The OSD32MP1-BRK board is similar to the DK board except the USB_DP1/DM1 are connected to through-hole pins instead of a hub. So that could be used. USBH2/OTG_HS connect to a micro USB jack, instead of a USB-C jack. But, none of the OTG_FS pins, nor OTG_HS_VBUS/ID/SOF are accessible, so OTG on that board is not an option.

In any case, you might be right about having an easier time getting USB to run on the M4 core since there's more existing code out there for that. Also it sounds like a suitable task for a co-processor. You might want to also try the copro_embedded example. That technique has become my preferred method because it makes debugging easier (only one elf file to reload). I have some updated Makefile bits for dependency detection, that I should probably merge back in. But YMMV, both are fine techniques.

FreeRTOS

I actually read through the ISR handler and startup code for the Cortex-A port of FreeRTOS, when figuring out how to get things running on the A7. It's not much different, you're right (at least those bits)

antisvin commented 2 years ago

you might still be able to use the OTG from the A7

Yes A7 should work, it's the opposite actually - they described USB as usable by A7 core only in most official docs. So initially I thought that I can't get a usable USB device on M4 at all.

I don't know if that's a required pin for USB functionality.

It's used in dual mode to determine if connected device is a host or device. But OpenWare doesn't support dual mode and in fixed device or host role that pin is unused. However, OTG FS is interesting only if both USB hosts are utilized. On discovery board only the first one is used, so it makes more sense to use OTG in HS mode instead.

I've considered using engineering mode initially to debug M4 core as A7 core won't be particularly useful until I can load code to it dynamically. And there's a long way till that becomes possible (if ever). For now I've tried building libneon/libmathneon and want to make a few patches to test if they work correctly, then benchmark the same code against Bela.

danngreen commented 2 years ago

Got it. I haven't had luck with engineering mode and debugging the M4 core, but it's worth trying. Let me know how it goes for you. Debugging is finicky on the MP1: both openocd and Segger gdb just fail/disconnect sometimes. What debugger are you using?

Also what's libneon/libmathneon? I'm familiar with NE10, but can't find any info on libneon.

antisvin commented 2 years ago

I'm just using builtin st-link with openocd and cortex-debug in vscode. And yes, it's not working that great (on A7, haven't tried with M4 yet).

Also what's libneon/libmathneon?

I did mean to write libne10, typed the wrong name somehow.

And libmathneon is from here - https://github.com/giuliomoro/math-neon.git

Also there's some nice code in https://github.com/odevices/er-301/blob/master/hal/simd.c , might perform better than similar functions in libmathneon as it has block-based processing. And generally lots of objects in ER-301 can be ported - https://github.com/odevices/er-301/tree/master/mods/core/objects . They have some glue for Lua bindins that is not needed, but are heavily utilizing NEON because A8 FPU performs poorly.

OWL libraries use CMSIS DSP for many of its functions, but it's mostly written for cortex-M. However at least it has block-based functions that should be more friendly to autovectorization.

danngreen commented 2 years ago

I'm circling back around to this, and started a branch for it: https://github.com/4ms/stm32mp1-baremetal/tree/simple-usb [Edit: this has been merged into master]

It's not working yet. I'm doing basically a 3-way merge of the the LL USB and HAL PCD drivers from CubeH7, and the drivers in the hftrx project, and the u-boot USB Gadget code.

BTW, ST has a great USB training course which covers their library as well as USB basics. I've been learning a lot from it: https://www.youtube.com/playlist?list=PLnMKNibPkDnFFRBVD206EfnnHhQZI4Hxa

antisvin commented 2 years ago

Very nice, I'll be keeping an eye on this.

Since MSC was added, something useful may be in this PR for adding the same thing to libDaisy. Specifically their notes about modification to Cube exports: https://github.com/electro-smith/libDaisy/blob/029bad7a5ec9ba4c0d556fcecbcfdff6e6f812fd/Middlewares/notes.md

danngreen commented 2 years ago

Just pushed some commits that demonstrate a working 128MB Mass Storage Class Device. I wrote it for the OSD32BRK board, and you can just connect a USB cable to a computer and a new drive will be detected. It seems to be working well.

I had problems with the OTG_IRQ not being consistent, so I moved the event loop into the main loop. This is also how the USB Gadget driver in U-Boot works, and that works pretty well. I want to look into how the MSC driver for STLinux works, if it uses IRQs or polling. But for now, a USB device is working!

danngreen commented 2 years ago

Edit: I merged into master and renamed the USB project, so here's the updated link to the project: https://github.com/4ms/stm32mp1-baremetal/tree/master/examples/usb_msc_device

danngreen commented 2 years ago

Just an update: I fixed the IRQ issue. It needed to be Level-sensitive, not Edge-sensitive.

Next, I'm not sure if I want to try a MIDI device, or an MSC host.

antisvin commented 2 years ago

LibDaisy has both, I personally am more interested in MIDI. MIDI device requires adding a parser for MIDI data which is also present in libDaisy, but it might be less complete than what OWL has. At the very least it currently only has a deserializer and no serializer.

LibDaisy devs ended up merging MSC host and SDMMC code to be supported at once (as separate FatFS volumes), so maybe it's something to consider doing here if there are plans to add that peripheral too.

They've also added CDC class from the very beginning, it could be useful for logging.

danngreen commented 2 years ago

LibDaisy has both, I personally am more interested in MIDI. MIDI device requires adding a parser for MIDI data which is also present in libDaisy, but it might be less complete than what OWL has. At the very least it currently only has a deserializer and no serializer.

I'd like to keep the examples to the minimum features, so I'd probably do basic parsing: MIDI echo or maybe simple random Note on/off output plus some interaction with MIDI input (LED on/off in response to CC). Edit: I misunderstood what you meant by "parser", after reading the MIDI USB Spec, I see there's some packet processing required before getting the raw MIDI data.

LibDaisy devs ended up merging MSC host and SDMMC code to be supported at once (as separate FatFS volumes), so maybe it's something to consider doing here if there are plans to add that peripheral too.

I wasn't planning on showing how to use the SDMMC since it should just be a matter of using the ST HAL drivers, plus FatFs. I'm going to be adding this in my own projects, so if it ends up being non-trivial I'll show it here. But otherwise, it should just be a matter of changing the STORAGE_Read/Write etc functions to use the ST HAL SDMMC read/write commands.

They've also added CDC class from the very beginning, it could be useful for logging.

Yeah that's a good one too. I played with a CDC device before doing the MSC device. I might clean it up and make it an example here. It would save an FTDI cable in the case of the OSD32-BRK or a custom board (but not offer much practical use for the Discovery board, since it already has a USB-UART).

danngreen commented 1 year ago

I started working on an audio-class USB MIDI Host yesterday and it came together a lot faster than I expected. I have a working example on my personal repo: https://github.com/danngreen/stm32mp1-baremetal/tree/usb-midi

I can attach a USB MIDI keyboard using a micro->Type A adaptor (the "OTG" adaptor) and it prints Note messages, SysEx, etc. I took code from both OpenWare and hftrx.

It runs on the OSD32 board. I presume it will work on the Discovery board as well, but the STUSB1600 chip might need a command or two to enable VBUS Sourcing. I plan to try that next.

The OSD32 board is normally powered by the USB jack, so you have to power the unit with the header pins (apply 5V to VIN and GND).

antisvin commented 1 year ago

A few notes not related to code:

  1. The company name is Rebel Technology, not OpenLabs (and there is some synth manufacturer with this name too!)
  2. Original code is GPL and you should license at least that app accordingly. I understand that you're just exploring device capabilities, but people will happily borrow this as a public domain code otherwise. It took a while for Martin to get audio working (basically it got rewritten to support async USB audio device) and they've made a product with Befaco (AC/DC) that is intended to be used as an audio interface, so that's a sensitive matter.

Other than that, OWL code had a problem with USB MIDI not working if you reconnect the controller, but somehow it was manifesting only on F4. It looks like MP1 is free of this issue just like H7?

danngreen commented 1 year ago
  1. The company name is Rebel Technology, not OpenLabs (and there is some synth manufacturer with this name too!)

You must be referring to one of the commit messages? Sorry, yes I meant to say OpenWare.

  1. Original code is GPL and you should license at least that app accordingly. I understand that you're just exploring device capabilities, but people will happily borrow this as a public domain code otherwise.

Good catch on the license, you’re right I’ve been just exploring possibilities. I don’t want anyone to get the wrong idea, so I’ve taken my branch down until I figure out the licensing.

It took a while for Martin to get audio working (basically it got rewritten to support async USB audio device) and they've made a product with Befaco (AC/DC) that is intended to be used as an audio interface, so that's a sensitive matter.

I think you’re thinking of the Martin/RebelTech’s USB Audio Device code perhaps? That’s a different set of files than the USB MIDI Host code that I was looking at. The only usb-related files from RebelTech/OpenWare that ended up in the working example I made is the USB MIDI Host driver (usb_midi.cpp/h). And I’m pretty sure Martin/RebelTech based those on the Dekrispator_v2 usb_MIDI.c project, as you can see at the top of the file in the OpenWare version:

/**
 * USB Host MIDI Driver
 * Based on code by Xavier Halgand @MrBlueXav
 */

It looks like the Dekrispator_v2 code is released under GPL v2, too. My version looks a lot more like the Dekrispator_v2 version than the OpenWare version, so I may just roll back to using the original and release under GPL. (Or if I find the time, I’d love to re-write it from scratch so I can use lambdas with captures instead of C functions for callbacks... but we'll see about that!

I was also using some basic utilities from from RebelTech/OpenWare (MIDI event enums, and a circular buffer), but I'll re-implement those on my own before “releasing”, as I want to keep the code as simple as possible.

I appreciate you bringing this up so I don’t step on anyones toes! I respect the work others have done, and I only want to build upon that, hopefully making it easier for others to port their projects from H7 to MP1.

Other than that, OWL code had a problem with USB MIDI not working if you reconnect the controller, but somehow it was manifesting only on F4. It looks like MP1 is free of this issue just like H7?

I didn’t have that issue, but I only did basic testing with two different keyboards, both of which happened to be made by Arturia. I was able to unplug and plug, and also power on with a keyboard plugged in. Didn't have any issues (so far).

I used the ST USB Host library with the modifications made in the hftrx project (https://github.com/ua1arn/hftrx/tree/master/lib/Middlewares/ST/STM32_USB_Host_Library). These modifications support USB Hubs (which in itself seems to be a port of the Mori HUB library you mentioned in a previous comment: https://github.com/mori-br/STM32F4HUB/blob/master/Project/Middlewares/ST/STM32_USB_Host_Library/Class/HUB/usbh_hub.c)

danngreen commented 1 year ago

A little update: I started over from scratch and wrote my own MIDI Host driver and supporting files. I found out there's very little difference between a minimally functional MIDI Host and a CDC Host, so I based the core logic on ST's CDC driver.

I'm pretty happy with how the MIDI host driver turned out, it's C++-friendly (can pass any sort of lambda/function object for callbacks), and the resource handles are managed more elegantly IMO (no globals, no dynamic allocation or USBH_malloc/free). My branch is here:

https://github.com/danngreen/stm32mp1-baremetal/tree/usb-midi-host/examples/usb_midi_audio_host

Still TODO is the Discovery board VBus issue, after which I'll merge this into master.

danngreen commented 1 year ago

Ok, it's merged!

https://github.com/4ms/stm32mp1-baremetal/tree/master/examples/usb_midi_audio_host

I added support for the Discovery board by enabling the STUSB1600 interface IC to operate in "Source power role" mode. This automatically detects a device connected/disconnected to the USB-C port (CN7) and turns on/off the 5V VBus line to the port. It works seamlessly, plugging and unplugging multiple times presents no problems.

Also, on another project I'm working on, I can confirm USB OTG works perfectly in both host and device modes on the M4 core. I didn't have to change anything (just use the NVIC instead of GIC).

So, is there anything else you would like to see related to USB? If not, can we close this issue?

antisvin commented 1 year ago

Dan, It's certainly OK to close this and the project looks complete to me. The only grumble I have is that having the word audio in project name makes you think that it's some kind of composite audio device. Maybe it should be renamed to just usb_midi_host to prevent this.

Btw, is connecting to the audio control interface really necessary here? I think OWL's MIDI host code doesn't check for its presence at all and it seems like it can be omitted safely.

danngreen commented 1 year ago

having the word audio in project name makes you think that it's some kind of composite audio device. Maybe it should be renamed to just usb_midi_host to prevent this.

Good point, I was referencing the USB class type (Audio Class), but it's misleading since there's also an AudioStreaming subclass, but this is the MidiStreaming subclass. I'll change the name.

Btw, is connecting to the audio control interface really necessary here? I think OWL's MIDI host code doesn't check for its presence at all and it seems like it can be omitted safely.

For the small number of devices I tested, it wasn't necessary to do anything with the control interface. However, strictly speaking, the AudioControl Interface Subclass is mandatory, and any MIDI USB device not implementing it is technically not compliant (see https://www.usb.org/sites/default/files/audio10.pdf under Section 3.1). So I put that check and the warning message in case I (or someone later) comes across a MIDI device that doesn't work: it might be a helpful place to start by checking if it has an AudioControl interface.

A more robust and compliant host driver would want to read the AudioControl interface descriptors to find out if there are multiple MIDIStreaming interfaces. It might also want to: