This is the eighth part in a series examining in detail the changes made in the UEFI 2.4 specification. This time we focus on a series of related changes to supporting the NVMe specification, which "defines an optimized register interface, command set and feature set for PCI Express (PCIe®)-based Solid-State Drives (SSDs)."
Of course, if you want to boot from a device under UEFI, the standard mechanism is to write a driver that consumes the PCI I/O protocol and produces the Block I/O protocol. In fact, there is such a sample driver out on the tianocore.org EDK2 project repository (MdeModulePkg/Bus/Pci/NvmExpressDxe).
So if this support is standard (and both PCI I/O and Block I/O have been in the specification from day 1), then what is left to do? Good question. It all has to do with how UEFI represents hardware devices as device paths. A device path is a series of records (called nodes) that describe the logical path from the software running on the CPU through the various buses (like PCI and USB) and industry standard protocols (like SCSI or IPv4) until it reaches the logical or physical device. The UEFI firmware maintains a mapping between device paths and the handle associated with the device.
For example (using UEFI standard text representation of these binary records):
PciRoot(0)/PCI(1,0)/SAS(0x31000004CF13F6BD, 0, SAS)
This refers to a SAS (Serial-Attached SCSI) device attached to a SAS controller PCI device on device 1, function 0 on the first PCI root bus in the system. That device is further identified by 0x31000004cf13f6b (the SAS address), 0 (the SAS unit number) and the keyword SAS which refers to how the device has been configured.
Using the LocateDevicePath() function provided by UEFI, you can pass in a device path and the handle of the device (if any) will be returned to you. With that handle, you can then find other interfaces produced by the driver(s) managing that device, such as Block I/O.
What happens for devices based on newer or crazier technologies? Well, the UEFI specification allows you to create your own node type, called a "vendor-defined" type (see section 18.104.22.168 of the UEFI specification). In that node, you place a GUID that you create to differentiate you from all other vendor-defined nodes. The UEFI firmware doesn't really care, it just performs binary matches. And, in fact, some so-called vendor-defined nodes have made there way into the specification (see SAS or VTF-100).
These nodes tend to get really long, because the GUID guarantees that it will take at least 16 extra bytes. So the UEFI specification added a new messaging device path node (type 23) which is much shorter, containing only the required bits to identify the device uniquely behind the NVMe(r) controller: the Namespace Identifier (4 bytes) and the Extended Unique Identifier (EUI-64). 16 bytes rather than the 32 bytes that would be required if the vendor-defined version was created.
Of course, it seems that the text representation of this node was left out of the update (see table 88). So, for now, NVM device paths look like this:
I expect they'll make the text version prettier. But for now, the binary version is short and sweet and lets us find and boot from NVMe devices in UEFI 2.4.