8.2 Data structure design
Proper data structure design is one of the keys to making UEFI Drivers both simple and easy to maintain. If a UEFI Driver writer fails to include fields in a private context data structure, then it may require a complex algorithm to retrieve the required data through the various UEFI services. By designing-in the proper fields, these complex algorithms are avoided, resulting in a driver with a smaller executable footprint. Static data, commonly accessed data, and services related to the management of a device should all be placed in a private context data structure.
Another key requirement is that the private context data structure must be easy to find when an I/O service produced by the driver is called. The I/O services produced by a driver are exported through protocol interfaces, and all protocol interfaces include a This parameter as the first argument. The This parameter is a pointer to the protocol interface containing the I/O service being called. The data structure design presented here shows how the This pointer passed into an I/O service can be used to easily gain access to the private context data structure.
A private context data structure is typically composed of the following types of fields:
- A signature for the data structure
- The handle of the controller or the child that is being managed or produced
- The group of protocol interfaces that are being consumed
- The group of protocol interfaces that are being produced
- Private data fields and services that are used to manage a specific controller
The signature is useful when debugging UEFI drivers. Signatures are composed of
four ASCII characters in a data field of type UINTN
and must be the first
field of the structure with the field name of Signature. When memory dumps
are performed, signatures stand out by making the beginning of specific data
structures easy to identify. Memory dump tools with search capabilities can
also be used to find specific private context data structures in memory. In
addition, debug builds of UEFI drivers can perform signature checks whenever
these private context data structures are accessed. If the signature does not
match, then an ASSERT()
may be generated. If one of these ASSERT()
messages
is observed, a UEFI driver was likely passed in a bad or corrupt This pointer
or the contents of the data structure that This refers too has been corrupted.
Device drivers typically store the handle of the device they are managing in a private context data structure. This mechanism provides quick access to the device handle if needed during I/O operations or driver-related operations. Root bridge drivers and bus drivers typically store the handle of the child that was created, and a hybrid driver typically stores both the handle of the bus controller and the handle of the child controller produced.
The group of consumed protocol interfaces is the set of pointers to the
protocol interfaces that are opened in the Start()
function of the driver's
EFI_DRIVER_BINDING_PROTOCOL
. As each protocol interface is opened using the
UEFI Boot Service OpenProtocol()
, a pointer to the consumed protocol
interface is stored in the private context data structure. These same protocols
must be closed in the Stop()
function of the driver's
EFI_DRIVER_BINDING_PROTOCOL
with calls to the UEFI Boot Service
CloseProtocol()
. Basically, the stop section should mirror the start section
of the driver, closing all protocols that were started.
The group of produced protocol interfaces declares the storage for the protocols that the driver produces. These protocols typically provide software abstractions for consoles or boot devices.
The number and type of private data fields vary from driver to driver. These fields contain the context information for a device that is not contained in the consumed or produced protocols. For example, a driver for a mass storage device may store information about the characteristics of the mass storage device such as the number of cylinders, number of heads, and number of sectors on the physical mass storage device managed by the driver.
Appendix A contains the generic template for the <<DriverName>>
.h file with
the declaration of a private context data structure that can be used for root
bridge drivers, device drivers, bus drivers, or hybrid drivers. The #define
statement above the private context data structure declaration using the
SIGNATURE_32() macro is used to initialize the Signature field when the private
context data structure is allocated. This same #define statement is used to
verify the Signature field whenever a driver accesses the private context data
structure.
A set of macros below the private context data structure declaration help
retrieve a pointer to the private context data structure from a This pointer
for each of the produced protocols using the CR()
macro introduced above.
These macros are the simple mechanisms that allow private data fields to be
accessed from the services in each of the produced protocols.
The example below shows an example of the private context data structure from
the DiskIoDxe
driver in the MdeModulePkg
. It contains the #define
statement for the data structure's signature. In this case, the signature is
the ASCII string "dskI
". The example also contains a pointer to the only
protocol that this driver consumes; the Block I/O Protocol. It contains storage
for the only protocol this driver produces; the Disk I/O Protocol. It does not
have any additional private data fields. The macro at the bottom retrieves the
private context data structure from a pointer to the field called DiskIo
that
is a pointer to the one protocol that this driver produces.
Example 113-Simple private context data structure
#define DISK_IO_PRIVATE_DATA_SIGNATURE SIGNATURE_32 ('d','s','k','I')
typedef struct {
UINTN Signature;
EFI_DISK_IO_PROTOCOL DiskIo;
EFI_BLOCK_IO_PROTOCOL *BlockIo;
} DISK_IO_PRIVATE_DATA;
#define DISK_IO_PRIVATE_DATA_FROM_THIS(a) \
CR (a, DISK_IO_PRIVATE_DATA, DiskIo, DISK_IO_PRIVATE_DATA_SIGNATURE)
The following example shows a more complex private context data structure
from the EhciDxe
driver in the MdeModulePkg
that manages PCI EHCI
controllers and produces
USB Host Controller 2 Protocols. It contains the Signature field that is set
to "ehci
". It also contains pointers to the consumed protocol; the PCI I/O
Protocol, and storage for the USB Host Controller 2 Protocol that is produced
by this driver. In addition, there are a large number of private data fields
that are used during initialization and all supported USB transaction types.
Details on how these private fields are used can be found in the source code to
the EHCI driver in EDK II.
Example 114-Complex private context data structure
#define USB2_HC_DEV_SIGNATURE SIGNATURE_32 ('e', 'h', 'c', 'i')
typedef struct {
UINTN Signature;
EFI_USB2_HC_PROTOCOL Usb2Hc;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT64 OriginalPciAttributes;
USBHC_MEM_POOL *MemPool;
EHC_QTD *ShortReadStop;
EFI_EVENT PollTimer;
EFI_EVENT ExitBootServiceEvent;
EHC_QH *ReclaimHead;
VOID *PeriodFrame;
VOID *PeriodFrameHost;
VOID *PeriodFrameMap;
EHC_QH *PeriodOne;
LIST_ENTRY AsyncIntTransfers;
UINT32 HcStructParams;
UINT32 HcCapParams;
UINT32 CapLen;
EFI_UNICODE_STRING_TABLE *ControllerNameTable;
} USB2_HC_DEV;
#define EHC_FROM_THIS(a) \
CR(a, USB2_HC_DEV, Usb2Hc, USB2_HC_DEV_SIGNATURE)