The Microsoft Devices team is excited to announce the release of an open source framework for Windows driver developers — Driver Module Framework (DMF). With DMF, not only can you easily develop simple and structured Windows Driver Framework (WDF) drivers but also share code amongst your drivers.Over the years Surface organization developed many products such as Pro, Studio, Laptop, Book with unique, innovative hardware capabilities. To light up these capabilities, we often needed to write drivers and firmware. Even though these products had commonalities in terms of how they interfaced with the hardware, individual product teams worked in isolation and built either their own drivers from scratch or copied based on their awareness of existing code and modified to suit their needs. This did help in meeting their immediate business priorities, but led to tremendous duplication of code and maintenance overhead. Developers with varied level of experience often created many divergent solutions to solve the same problem and the code lacked structure and quality.
About three years ago, the team decided to take a holistic look at drivers written for various Surface products and started an effort to structure code in a way that allows for maximum reuse of code with goals to improve the efficiency, serviceability, and scalability of teams to build new products.
We started by breaking down individual functionalities in drivers into a shareable code base. This iterative effort led to the creation of DMF: an extension to WDF that provides you with a library of new WDF Objects called DMF Modules. Modules allow interaction with one another, WDF, and hardware in a structured manner.
Today, all WDF drivers on the team are written by using DMF. Modules are well tested and can be reused or extended later to meet new requirements. Besides having the benefit of well-architected drivers, bug fixes are now efficient. A bug fix in a Module is automatically applied to all the drivers that were built using the Module.
As part of the open source effort, we have shared many Modules that provide solutions for commonly faced problems.
Let’s start by looking at a typical design of WDF driver.
In this design, the driver maintains state in a device context and the code is divided into units that access the device context and communicate among themselves. You, as the driver developer, are responsible for making sure that accesses to the device context are synchronized and strict locking hierarchy is adhered to when units communicate with each other to avoid corruption and deadlock. As WDF calls into the driver, you are also responsible for dispatching work to each of the units as needed. It is hard to know the flow of communication and keep access to the device context properly synchronized.
If you want to reuse for example FIFO code in another driver. You need to understand the complex interaction between the units before extracting the code and the fields used to store the state. Often times, that is error prone.
Now let’s improve the architecture by using DMF. Here the driver is built by using a client Module and several prebuilt generic utility Modules. We are going to refer to code that uses a Module as the client driver. Between WDF and individual Modules, there is a thin arbitration layer (DMF) to bind Modules and dispatch WDF events to each Module. Now, Modules communicate with each other and the client driver in a well-defined manner as shown by the arrows. Instead of all the Modules sharing the device context, each one uses its own context area to maintain its state.
Here are some key differences between a traditional WDF and DMF-based WDF driver:
WDF communicates with DMF, while DMF communicates with the driver.
The device context (shown in green) exists independently in each Module and in the client driver-specific code. Each smaller device context holds only the elements that are needed for that Module. No Module can access another Module’s device context.
The WDF callbacks (shown in red) now exist independently in each Module and in the client-specific code. WDF calls into the client driver. DMF intercepts that call and dispatches it to each Module in the tree of instantiated Modules. Each Module handles each callback as it sees fit. Finally, DMF dispatches the callbacks to the client driver’s callbacks.
Finally, note the arrows. The arrows specifically show the flow among Modules and the client-specific code. In this example, client-specific code can only communicate with three Modules: ACPI, Button, and Stream. It cannot communicate with GPIO, FIFO, List, or Thread. ACPI cannot communicate with FIFO, etc. Even without looking at source code, we have a good idea of how data flows in this driver.
To summarize, each Module is a self-contained single unit. It has its own code, context, and callbacks. This makes the code easy to reuse. Organizing drivers in this way solves many problems.
DMF follows design and interaction patterns of WDF. DMF does not replace WDF nor does it restrict the driver from using OS interfaces directly. DMF makes it easier for you to break down the tasks that a device driver must perform into smaller units. Then, those smaller, self-contained units that perform a single task can be written as Modules.
1. DMF Modules use interaction patterns consistent with existing WDF objects. Just like any WDF object, a DMF Module:
Has a CONFIG structure, methods, and event callbacks.
Can be parented to other WDF objects for life time management.
2. Modules within a client driver are organized into a tree structure that is maintained by the core of DMF. The core is responsible for creating, opening, closing, and destroying Modules. In turn each Module is responsible allocating and freeing its own resources.
3. Modules that are siblings of each other cannot communicate directly. Only Modules in a parent-child relationship can communicate with each other; only parent Modules can communicate with their children Modules (with the exception of callbacks).
4. Modules can be extended to meet new requirements, or a new Module can be created by combining multiple Modules.
5. Module design is analogous to a class in an object-oriented programming pattern. If you are familiar with the Universal Extensible Firmware Interface (UEFI) Driver Execution Environment (DXE), you will find similarity with Modules.)
C++ Analogous Concept
C++ Object Private Data (members). Also, importantly this is analogous to WDF drivers’ “device context”.
C++ Object Constructor Parameters
Module Public Methods
C++ Object Public Functions
Module Private Methods
C++ Object Private Functions
C++ “this” pointer
The code that instantiates the C++ object.
6. Each Module has its own locks. Each Module is responsible for locking its own private context. Thus, locking is granular but, at the same time, efficient and easy to understand.
7. Modules can be used by the client driver or by other Modules; are agnostic about the code that uses them.
8. Modules can use any number of child Modules. Child Modules, in turn, can use any number of Child Modules.
9. DMF provides default implementations for many driver routines such as DriverEntry, AddDevice, WDF Queue creation, so that simple driver (aka Container driver) can be created with as little code as possible.
10. The client driver only needs to instantiate the Module that the driver needs. Child Modules (if any) required are automatically instantiated by DMF on behalf of the driver.
11. Modules support inheritance. If you want to modify or add capabilities to an existing Module X, it is easy to make a Module Y that is a parent of X. Y then reuses X but provides other functionality as needed.
12. Modules can directly receive all WDF callbacks that are received by the client driver.
13. DMF supports all WDF drivers: KMDF/UMDF, Bus/Filter/Function Drivers, Miniport Drivers, C / C++ Drivers.
14. You can easily integrate DMF into their existing drivers or easily write new drivers from scratch.
The current release of DMF includes these Modules:
Modules for buffer management
Provides a list of zero or more pre-allocated buffers with bounds-checking validation and timer logic to manage lifetime of buffers. This is a fundamental block used by many other Modules.
This data structure is composed of two DMF_BufferPool instances providing producer/consumer usage pattern.
Provides a “ping-pong” buffer that allows the Client to read from one part of the buffer which contains fully transmitted/processed data while another part of the Client code continues populating a new partially filled buffer.
Provides a classic ring-buffer data structure.
Allows the Client to enqueue work. That work is then processed synchronously. Client may optionally wait for work to complete. This is a good example of how Modules can use other Modules to create more complex Modules.
Modules for task management
This Module enhances the WDFWORKITEM primitive in two ways: 1. Allows Client to enqueue work items even if they are already enqueued. 2. Allows a call specific context to be enqueued.
Allows the Client to specify that an operation should happen a single time, either for the lifetime of the machine or for the current boot cycle.
This Module creates a thread and associated “stop” event, a ‘work ready” event and a provides callback function to the Client. An internal callback function waits for the above events and calls the Client callback when the “work-ready” event is set.
Modules for notification
Allows the client driver to easily set up an event in Kernel-mode that is also accessible in User-mode.
Allows a Client to notify User-mode of events using the classic IOCTL from User-mode pattern. Data can be sent with the event.
Modules for accessing various I/O targets
Allows the client driver to send a WDFREQUEST to its own stack.
Allows the client driver to query ACPI in various ways. It allows the Client to easily query/invoke/evaluate DSM methods.
Allows the Client to send requests to WDFIOTARGET in a continuous manner. It is similar to the WDF UsbContinuousReader object but it works for all WDFIOTARGETS.
Allows the Client to register for a PnP Notification an interface and automatically manage the arrival and removal of the associated device. It also allows the Client send requests in a continuous manner using DMF_ContinuousRequestTarget as a Child Module.
Allows the client driver to communicate with the GPIO pins exposed by the GPIO WDFIOTARGET. This Module automatically looks for the GPIO resources and opens the appropriate targets based on settings set by the Client.
Allows the client driver to communicate with an underlying HID WDFIOTARGET. Methods are provided that allow the Client to work with input/output/feature reports.
Allows the Client to communicate with an I2C bus that is exposed by SPB. This Module automatically looks for the I2C resources and opens the appropriate targets based on settings set by the Client.
Contains code that builds and send requests. It can be used with any WDFIOTARGET.
Allows the client driver to communicate with a Resource Hub WDFIOTARGET. The Module contains all the code need to parse the type resource and other information.
Allows the client driver to open Serial IO Stream target and send requests.
Allows the client driver to communicate with an SPI bus that is exposed by SPB.
Miscellaneous utility Modules
Allows the client driver to register for and receive asynchronous notifications via ACPI.
Allows the client driver to cause a thread to sleep and cancel that sleep at any time.
Allows the client driver to communicate with the device interfaces exposed by MSGPIOWIN32. These interfaces allow the driver to inject button and tablet related messages.
Allows the client driver to easily write data to the Windows Crash Dump file when the system crashes.
Allows the Client to more easily handle IOCTLs. Client provides a table of supported IOCTLS, the kind of access required, and the minimum/maximum sizes of the input/output buffers as well as a callback that processes the IOCTL.
Allows the client driver to easily create PDOs for enumerating virtual devices.
Provides functions that allow the client driver to work easily with the Registry.
Allows the client driver to read the SMBIOS data which the Module retrieves using WMI.
This Module is a wrapper around the VHF API. Modules use this Module as a Child Module to create virtual HID devices. Those parent Modules expose Methods that are specific to that virtual HID device.
Provides an example of how to create a Virtual HID device using DMF_VirtualHidDeviceVhf.
Allows the client driver work with the WMI API easily.
DMF and its Modules, templates and sample code have been shared for public use on Github. We will continue to improve the code and add new Modules in the open-source repository. Also, look forward to more sample drivers that show different features of DMF and to help you understand the various ways DMF can be used.
DMF has been designed with the goal to develop and maintain quality drivers with maximum efficiency and maintainability. It’s now being shared to the world as open source so the Windows ecosystem can leverage this framework and utility-modules to write quality drivers. WDF over a decade ago brought tremendous improvement over WDM and changed the driver landscape, however it has not provided an ability for a 3rd party driver house to create extensions/libraries that plug seamlessly into the WDF messaging model and can be shared across drivers.
We are confident that DMF will help you develop and maintain quality drivers with maximum efficiency and maintainability.
Look out for more posts about DMF. We intend to answer your questions, so please post your comments on Github!