Patching the ACPI DMAR table to allow TPM2.0

The Intel Corporation 8 Series HECI has support for a TPM2.0 device which can be enabled by the BIOS. Unfortunately, this firmware TPM device uses DMA accesses (instead of MMIO as used by newer HECIs) to interact with the operating system and most BIOS do not include an appropriate entry in the DMAR table to allow the IOMMU to work along the firmware TPM.

This blogpost aims to help you understand the procedure needed to patch the DMAR ACPI table provided by your system’s firmware to allow the TPM2.0 device through. Unfortunately, given the way the Linux kernel handles RMRR entries, you will also need a kernel patch to alias the HECIs function 7 (used by the firmware TPM application) to the function 0 (the only one that is exposed).

To patch the DMAR table you will need the following tools: iasl (which you will probably need to install), lspci, cat, grep, a text editor and a way to modify your initramfs (I usually create my own although you may be able to create a cpio image with the table in a way similar to how microcodes are loaded).

The first step is extracting your system’s DMAR table in binary format. You can do so by running the following command: cat /sys/firmware/acpi/tables/DMAR > dmar.bin

To be able to modify the table you need to convert it into text format. You do this using iasl as follows: iasl dmar.bin

You also need to find out at which address the TPM2.0 device is. You can do by looking /proc/iomem for a device with a name similar to MSFT0101:00. In my system this could be done by running the following command: grep MSFT /proc/iomem | tail -1 You should get an entry similar to this: xxxxxxxx-yyyyyyyy : MSFT0101:00. Here xxxxxxxx is the lower end of the memory range and yyyyyyyy the higher end.

You need to find also the “physical” path to your HECI. Use the following command to do so: lspci -nn | grep HECI. The entry will look something as follows: ii:jj.k Communication controller [0780]: Intel Corporation 8 Series HECI #0 [llll:mmmm] (rev 04). Notice that ii:jj.k are placeholders used to patch the DMAR table. Similary, llll:mmmm is a placeholder you might need if you need to patch the kernel to add another quirk. Note that k should be 0 for my patch to work.

Now open the dmar.dsl file in your favorite editor and perform the appropriate changes. You just have to add the code, found below this paragraph, above the line at the end of the file where it says Raw Table Data: (if you put it below that line, your entry will instead be ignored). Remember that you need to replace xxxxxxxx and yyyyyyyy with the entries you obtained before using grep. You will also need to replace ii, jj and kk with the values you obtained using lspci. All three values are extacly two digits so you need to add zeros to the left to ensure they are two digits. For example, if your value for k on lspci is 0 you should replace kk with 00.

Subtable Type : 0001
Length : 0020
Reserved : 0000
PCI Segment Number : 0000
Base Address : xxxxxxxx
End Address (limit) : yyyyyyyy
Device Scope Type : 01
Entry Length : 08
Reserved : 0000
Enumeration ID : 00
PCI Bus Number : ii
PCI Path : jj,kk

You will also need to increase the value of the entry in the original file labeled Oem Revision. Adding one to whichever number in the field works. Although all that is needed is that it is larger than the original value (so the table is marked as newer and therefore used when overriding the original table provided by your system’s manufacturer).

Now we need to compile the new table. We do so using iasl: iasl dmar.dsl If the command worked correctly, you should see something as follows on your terminal: Compilation successful. 0 Errors, 0 Warnings, 0 Remarks. The new table file will be called dmar.aml

Finally you need to modify your initramfs so that the generated file is on kernel/firmware/acpi/dmar.aml the way to do this will depend on your distribution. If you want to create a new initrd that can be concatenated with your existing one, you can do as follows: mkdir -p kernel/firmware/acpi && cp dmar.aml kernel/firmware/acpi && find kernel -print0 | cpio -ov -0 --format=newc > my-initramfs.cpio Then you need to move my-initramfs.cpio to your /boot folder and make sure you configure your bootloader to include it when loading your kernel.

If all went correctly after you reboot you should see something like the following on dmesg:

ACPI: DMAR ACPI table found in initrd [kernel/firmware/acpi/dmar.aml]
ACPI: Table Upgrade: override [DMAR

For the TPM to work you will also need to patch the kernel to add a PCI quirk for the HECI. You can check Linux bug 108251 for the patch. Usually all you will need to do (if the TPM shows as function 7 on your dmesg log and the HECI shows on lspci with function 0) is just add an entry similar to the one below in drivers/pci/quirks.c Preferably after the definition of quirk_dma_func7_alias If your HECI uses a function other than 7 you may need to create your own function. Similarly, if your HECI is at a function other than 0 you will also need your own function too. If the value from lspci labeled as llll is 8086 then you can leave PCI_VENDOR_ID_INTEL as is, otherwise you will need to replace with llll or (if you plan to upstream the patch) the appropriate macro. Similarly you will need to replace mmmm with the device ID of your HECI.

DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0xmmmm,
quirk_dma_func7_alias);

I hope you found this entry useful and can now enjoy both your TPM 2.0 device and the protection provided by the IOMMU.