While the chief purpose of PDM is to manager of devices their drivers, it also serves as somewhere to put usful things like cross context queues, cross context synchronization (like critsect), VM centric thread management, asynchronous I/O framework, and so on.
When all device modules have been successfully loaded PDM will instantiate those devices which are configured for the VM. Note that a device may have more than one instance, see network adaptors for instance. When instantiating a device PDM provides device instance memory and a callback table (aka Device Helpers / DevHlp) with the VM APIs which the device instance is trusted with.
Some devices are trusted devices, most are not. The trusted devices are an integrated part of the VM and can obtain the VM handle from their device instance handles, thus enabling them to call any VM api. Untrusted devices can only use the callbacks provided during device instantiation.
The main purpose in having DevHlps rather than just giving all the devices the VM handle and let them call the internal VM APIs directly, is both to create a binary interface that can be supported accross releases and to create a barrier between devices and the VM. (The trusted / untrusted bit hasn't turned out to be of much use btw., but it's easy to maintain so there isn't any point in removing it.)
A device can provide a ring-0 and/or a raw-mode context extension to improve the VM performance by handling exits and traps (respectively) without requiring context switches (to ring-3). Callbacks for MMIO and I/O ports can needs to be registered specifically for the additional contexts for this to make sense. Also, the device has to be trusted to be loaded into R0/RC because of the extra privilege it entails. Note that raw-mode code and data will be subject to relocation.VMM and/or other device and PDM will work like a mediator for these. The typical pattern is that the device calls a special registration device helper with a set of callbacks, PDM responds by copying this and providing a pointer to a set helper callbacks for that particular kind of device. Unlike interfaces where the callback table pointer is used a 'this' pointer, these arrangements will use the device instance pointer (PPDMDEVINS) as a kind of 'this' pointer.
For an example of this kind of setup, see the PIC. The PIC registers itself by calling PDMDEVHLPR3::pfnPICRegister. PDM saves the device instance, copies the callback tables (PDMPICREG), resolving the ring-0 and raw-mode addresses in the process, and hands back the pointer to a set of helper methods (PDMPICHLPR3). The PCI device then queries the ring-0 and raw-mode helpers using PDMPICHLPR3::pfnGetR0Helpers and PDMPICHLPR3::pfnGetRCHelpers. The PCI device repeates ths pfnGetRCHelpers call in it's relocation method since the address changes when RC is relocated.
The way USB devices work differs greatly from other devices though since they aren't attaches directly to the PCI/ISA/whatever system buses but via a USB host control (OHCI, UHCI or EHCI). USB devices handles USB requests (URBs) and does not register I/O ports, MMIO ranges or PCI bus devices/functions.
For instance take a DVD/CD drive. This can be connected to a SCSI controller, an ATA controller or a SATA controller. The basics of the DVD/CD drive implementation remains the same - eject, insert, read, seek, and such. (For the scsi case, you might wanna speak SCSI directly to, but that can of course be fixed - see SCSI passthru.) So, it makes much sense to have a generic CD/DVD driver which implements this.
Then the media 'inserted' into the DVD/CD drive can be a ISO image, or it can be read from a real CD or DVD drive (there are probably other custom formats someone could desire to read or construct too). So, it would make sense to have abstracted interfaces for dealing with this in a generic way so the cdrom unit doesn't have to implement it all. Thus we have created the CDROM/DVD media driver family.
So, for this example the IDE controller #1 (i.e. secondary) will have the DVD/CD Driver attached to it's LUN #0 (master). When a media is mounted the DVD/CD Driver will have a ISO, HostDVD or RAW (media) Driver attached.
It is possible to configure many levels of drivers inserting filters, loggers, or whatever you desire into the chain. We're using this for network sniffing for instance.
The drivers are loaded in a similar manner to that of the device, namely by iterating a keyspace in CFGM, load the modules listed there and call 'VBoxDriversRegister' with a callback table.
An interface here means a function table contained within the device or driver instance data. Its method are invoked with the function table pointer as the first argument and they will calculate the address of the device or driver instance data from it. (This is one of the aspects which *might* have been better done in C++.)
One thing these APIs all have in common is that resources will be associated with a device / driver and automatically freed after it has been destroyed if the destructor didn't do this.PDM Async I/O API provides a somewhat platform agnostic interface for asynchronous I/O. For reasons of performance and complexcity this does not build upon any IPRT API.
A queue can also be used by another thread (a I/O worker for instance) to send work / events over to the EMT.
A task can also be scheduled by another thread (a I/O worker for instance) as a mean of getting something done in EMT.
The general usage pattern for threads in the employ of devices and drivers is that they shuffle data or requests while the VM is running and stop doing this when the VM is paused or powered down. Rogue threads running while the VM is paused can cause the state to change during saving or have other unwanted side effects. The PDM Threads API ensures that this won't happen.