I got DMA disk transfers partially working last night. The SIMH version of CP/M already had support for DMA access implemented in its BIOS, so I implemented a compatible port interface on the z80ctrl. The same disk images that are mounted on A: through H: will be mirrored to I: through J:. When you use drive letters I through J, the disk images are read via DMA, whereas when you use A through H, they are read using Altair Floppy emulation.
This division is a function of the SIMH CP/M BIOS since it sends drive numbers 0 through 8 to the DMA port for drives I through J. My DMA implementation does not modify the disk number it receives from CP/M, so the drives end up being mirrored. The CP/M BIOS could be modified to use DMA for all drives, but as I am currently debugging it, this layout is convenient. It allows me to test the DMA functionality when I want to, or ignore it when I just want a working drive.
The DMA access only uses a single port: 0xFD. A READ or WRITE command, followed by 6 bytes specifying the drive, sector, track, and DMA address is written to the port, and then a byte is read from the port to initiate the command. The z80ctrl initiates a DMA transfer at the end of the IORQ cycle using the previously saved command and it's parameters. There is also a PARAM command that allows CP/M to interrogate the drive controller about the characteristics of the drive. The DMA disk emulation code is here and the IORQ handler calls the DMA function here.
So far it's working well enough that I can go to the drive and run DIR and get a directory listing, but it's still buggy. I'm getting some duplicate entries in my directory listing and I get a lot of bad sector errors. I intentionally haven't allowed write support until I get the bugs worked out. As expected it's considerably faster than the old method that transfers one byte at a time through programmed I/O on the Z80.
Separately, I have also implemented a bootloader directly in the AVR, which is working very well. There is a new "boot" command that directly loads the boot sector from the disk mounted on drive 0 into RAM and jumps to it. I have gone ahead and removed the sboot and dboot commands which utilized the traditional Z80 boot ROMs. Perhaps removing them so soon was a bit aggressive, but if anyone reports any problems with the new bootloader I can bring them back.
Update: DMA is fully working and stable now. I just needed to fix a stupid mistake I made on the sectors per track (had 32 hex when it should have been 32 decimal/20 hex), and I needed to disable interrupts during the timing critical parts where the bus mastering takes place. Overall it's a little over 2X as fast as the traditional Altair Floppy emulation. Not as much as I had hoped for, but there is quite a bit of overhead in setting up a DMA transfer that I didn't think about. I think that I could probably squeeze out some additional performance by increasing the amount of data I read in a single DMA transfer from 128 to 256 at least, possibly more. That would require tweaking the SIMH CP/M BIOS though to do deblocking.
The debugging features of the z80ctrl really came in handy while debugging the DMA features and it's really validated the effort I put into them. I set an IO read break point on 0xFD to figure out where in memory the code was that was receiving disk parameters from the DMA port. Then I single stepped until I found where it was copying the data that it read from the port. Then I dumped that data and compared it with data at the same location in the SIMH emulator. That's how I discovered that I had made a mistake with 32 hex vs 32 decimal for the sectors per track in the disk parameter block that my emulation sends to CP/M. The debugger also came in handy when writing the DMA bootloader and several other times throughout the project.
Lastly, I've also just implemented binary load and save commands so programs can be loaded in from binary as well as hex files. I used this to load up the ROM from the RC2014 and got BASIC and the bootloader to display their initial messages, but they use interrupt-driven serial input, so I need to figure out a way to forward the interrupts from the AVR to the Z80 to get that working. I wanted to get interrupts working anyway in order to support a timer, so I will probably work on that next.