tosc-rs / mnemos

An Operating System for Building Small Computers
https://mnemos.dev
Apache License 2.0
234 stars 14 forks source link

feat(d1): allocate DMA channels from the DMAC #280

Closed hawkw closed 10 months ago

hawkw commented 10 months ago

Currently, the assignment of D1 DMAC channels to drivers is somewhat ad-hoc. We hard-code channel 0 as belonging to the UART, and channel 1 as belonging to the SPI_DBI driver, with the interrupt handler for DMAC IRQs specifically dispatching those channel's IRQs to those drivers manually.

There are some issues with this. The exclusive use of DMA channels is not enforced by the current API, because you can (unsafely) summon_channel whenever you want and use a DMA channel that someone else is already using. Furthermore, adding new drivers that also use DMA channels requires hard-coding those additional channel IRQs in the interrupt handler, which must then be kept in sync with the channels actually used by the driver. Also, when a driver wants to wait asynchronously on a DMA transfer, it has to ensure that the DMAC ISR will wake its hard-coded WaitCell, and the driver code is responsible for manually pre-registering its waker with that WaitCell before actually starting the DMA request. This is unfortunate, and it would be nicer if there was an async fn for running DMA transfers that the driver could call without having to repeat all of that code.

This branch improves the way we use the D1's DMAC. In particular, rather than hard-coding specific driver-provided WaitCells to correspond to specific DMA channels, we now have the Dmac struct track which channels are in use, and allow drivers to allocate any channel from the DMAC. It will provide them with the next available channel if any are not already claimed. The DMAC now owns a WaitCell for each DMA channel, which is used to provide an async Channel::run_descriptor method. This method handles pre-registering the waker, starting the DMA request, and ensuring the task is woken, and stops the DMA transfer when it's complete.

The allocation of channels by the DMAC means that adding new drivers that also use DMA is easier: we don't need to track who owns which channel. If we don't run a specific driver based on config, any DMA channels it would use are now available to other drivers, rather than being hardcoded. This way, we can have more than 16 driver implementations that use DMA, as long as we don't enable them all at once.

In a follow-up, I'm also going to add dynamic allocation of DMA channels. Currently, DMA channels are still allocated permanently, and drivers allocate them as soon as they're created and hold onto the channel forever. However, we can modify the Channel type to also indicate to the DMAC that it's free when the Channel is dropped, allowing it to be reallocated (potentially by a different driver). If all 16 DMA channels are in use at the same time, a driver could then wait until one becomes available. If drivers then make more short-lived claims on DMA channels, rather than holding one channel for their entire lifetime, this would thenallow us to have more than 16 driver implementations which use DMA, or allow driver implementations to freely use multiple DMA channels at the same time, without having to worry about running out of channels.