Contribute
Register

[Guide] HackrNVMeFamily co-existence with IONVMeFamily using class-code spoof

Status
Not open for further replies.

RehabMan

Moderator
Joined
May 2, 2012
Messages
181,058
Motherboard
Intel DH67BL
CPU
i7-2600K
Graphics
HD 3000
Mac
  1. MacBook Air
Mobile Phone
  1. iOS
Overview

The purpose of this guide is to explain how to use the class-code spoof technique to keep un-patched IONVMeFamily.kext (incompatible with most PC NVMe SSDs) from loading such that HackrNVMeFamily can be used along side it in EFI/Clover/kexts or installed to the system volume (/Library/Extensions, /System/Library/Extensions).

I first published the technique on insanelymac: http://www.insanelymac.com/forum/to...er-macos-sierra-is-ready/page-29#entry2322636

You should be familiar with the patch_nvme.sh script as discussed in the patch-nvme README on github: https://github.com/RehabMan/patch-nvme

Although there are many ways to patch IONVMeFamily, only the class-code spoof and HackrNVMeFamily is to be discussed here. It is my opinion that it is the best way. Keeping this post focused only on this single technique, used from installation through post-installation, is the least confusing way to approach the topic.

There are two different ways to implement the class-code spoof:
- SSDT with _DSM method to inject the modified class-code
OR
- using config.plist/Devices/Arbitrary to inject the modified class-code

Because using config.plist/Devices/Arbitrary precludes use of some commonly used Clover features (config.plist/Graphics/Inject), this post will discuss only the SSDT method.

Assumptions:
- installation of Windows 10 on the target
- running install of macOS/OS X (same version as you plan for the target) on existing hack or Mac
- basic config.plist editing skills
- basic MaciASL usage
- basic Clover knowledge
- basic knowledge of installing/injecting kexts

The MaciASL used by this guide is available here: https://bitbucket.org/RehabMan/os-x-maciasl-patchmatic/downloads/
Make sure you have ACPI 6.1 selected in MaciASL->Preferences->iASL.

Note: There is better support for NVMe SSDs with 512 block size in 10.13 High Sierra. As a result, with 10.13, you do not need such extensive patches.


Determining the ACPI path of your NVMe device(s)

In order to create an SSDT to inject class-code, you must know the ACPI path that describes where your SSD is located in ACPI namespace. You can use Windows Device Manager, or advanced users can determine it from ioreg. Both methods are described at the insanelymac link provided in the patch-nvme README.

Because this guide is intended to be used prior to installation, it will use Windows Device Manager. You will find your NVMe device in Windows Device Manager under "Storage Controllers" -> "Standard NVM Express Controller". Right click and choose Properties. Then click on the "Details" tab. Then find "BIOS device name" within the "Property" dropdown. Select it. Your NVMe SSD ACPI path will be displayed in the "Value" box.

post-1031260-0-50712400-1479777087.png


If you have more than one NVMe SSD, you must find the ACPI path for each one.

Note: It is also possible that your SSD has no ACPI identity. I don't have such a computer at the moment (and the situation is probably rare), but in that case you may be able to determine only the ACPI identity of the parent (in the case of my NUC, the parent is _SB.PCI0.RP13), and slightly different SSDT would have to be created. If you don't see "BIOS device name", the SSD path is probably without one or more ACPI identities. In that case look for "Location paths" and record the path that starts with "ACPI(_SB_)". Be sure to read "Examples with incomplete ACPI identities" for additional information and instructions.


Creating SSDT_NVMe-Pcc.aml

Below you'll find a template you can use for your own SSDT that injects class-code. This is the actual SSDT that I use on my NUC6i7KYK with a Samsung 950 Pro NVMe SSD:
Code:
// Inject bogus class-code for NVMe SSD to prevent IONVMeFamily.kext from loading
DefinitionBlock("", "SSDT", 2, "hack", "NVMe-Pcc", 0)
{
    External(_SB.PCI0.RP13.PXSX, DeviceObj)
    Method(_SB.PCI0.RP13.PXSX._DSM, 4)
    {
        If (!Arg2) { Return (Buffer() { 0x03 } ) }
        Return(Package()
        {
            "class-code", Buffer() { 0xff, 0x08, 0x01, 0x00 },
            "built-in", Buffer() { 0 },
        })
    }
}
//EOF

The path shown above _SB.PCI0.RP13.PXSX is the path that my NUC6i7KYK uses. You will notice the same path was shown in the image from Windows Device Manager. You will need to replace both instances of it in the above SSDT to match the path for your own NVMe SSD.

Note regarding "built-in": In all my experience it has been enough only to have an ACPI identity to make the device appear builtin, not external. But some report that their device showing as external... so I add "built-in" as we never want an M.2 SSD to appear external.

If your SSD had no ACPI identity (eg. no ACPI identity for the M.2 port/device), you would need to create one. For example, if the OEM had left out the PXSX identity, it would have been required that I add one:
Code:
// Inject bogus class-code for NVMe SSD to prevent IONVMeFamily.kext from loading
DefinitionBlock("", "SSDT", 2, "hack", "NVMe-Pcc", 0)
{
    External(_SB.PCI0.RP13, DeviceObj)
    // Add PXSX device under RP13
    Device(_SB.PCI0.RP13.PXSX) { Name(_ADR, 0) }
    /// Now we can inject the method at RP13.PXSX
    Method(_SB.PCI0.RP13.PXSX._DSM, 4)
    {
        If (!Arg2) { Return (Buffer() { 0x03 } ) }
        Return(Package()
        {
            "class-code", Buffer() { 0xff, 0x08, 0x01, 0x00 },
            "built-in", Buffer() { 0 },
        })
    }
}
//EOF

I don't know what is displayed in Windows Device Manager in that case, although it is predictable what ioreg would look like. Of course, the idea here is to create the SSD before installing macOS, so let me know if you have a situation with no ACPI identity for the SSD.

And if you have multiple NVMe SSDs, you must create a _DSM method for each. For example, my NUC also has a M.2 port at RP09 for an SSD. Currently, I have an SM951/AHCI installed there, so it is not NVMe. But if it was an NVMe drive, my SSDT would have to modified to include a _DSM method for it as well:

Code:
// Inject bogus class-code for NVMe SSD to prevent IONVMeFamily.kext from loading
DefinitionBlock("", "SSDT", 2, "hack", "NVMe-Pcc", 0)
{
    External(_SB.PCI0.RP09.PXSX, DeviceObj)
    Method(_SB.PCI0.RP09.PXSX._DSM, 4)
    {
        If (!Arg2) { Return (Buffer() { 0x03 } ) }
        Return(Package()
        {
            "class-code", Buffer() { 0xff, 0x08, 0x01, 0x00 },
            "built-in", Buffer() { 0 },
        })
    }
    External(_SB.PCI0.RP13.PXSX, DeviceObj)
    Method(_SB.PCI0.RP13.PXSX._DSM, 4)
    {
        If (!Arg2) { Return (Buffer() { 0x03 } ) }
        Return(Package()
        {
            "class-code", Buffer() { 0xff, 0x08, 0x01, 0x00 },
            "built-in", Buffer() { 0 },
        })
    }
}
//EOF

In order to use this SSDT, you must compile it as AML. You should use my version of MaciASL, which is linked above. Make sure you have MaciASL->Preferences->iASL set to ACPI 6.1.

After running MaciASL, close the default window (which has your DSDT), then choose File->New to create an empty document. Paste the code and edit as appropriate for your ACPI path, then use File.Save As, "SSDT_NVMe-Pcc.aml", Format: ACPI Machine Language Binary. Save to a location you can find, such as your desktop.

Now you can copy the SSDT_NVMe-Pcc.aml file such that Clover can load it. If you're preparing your USB, you'll place it on the EFI for USB, otherwise it will be on the EFI for your boot drive. In any case, it goes to EFI/Clover/ACPI/patched/SSDT_NVMe-Pcc.aml.

Note: If you're using config.plist/ACPI/SortedOrder (to set SSDT load order for patched OEM SSDTs), you must be certain SSDT_NVMe-Pcc.aml is in the SortedOrder list. When SortedOrder is specified, SSDTs in ACPI/patched that are not in SortedOrder are ignored by Clover. For most desktop scenarios and laptop scenarios where Clover ACPI hotpatching is being used, SortedOrder will not be specified and copying the file to ACPI/patched is sufficient to cause it be loaded.


Examples with incomplete ACPI identities

As mentioned in "Determining the ACPI path of your NVMe device(s)" above, your SSD path may be incomplete therefore without ACPI identities that lead all the way to the leaf node of the SSD. It is not possible to inject properties without a full ACPI path available (not even with config.plist/Devices/Arbitrary). In this case, we must complete the missing ACPI identity(s) for each node in the path including the leaf node that represents the SSD.

This is a simple example that shows an ACPI identity all the way to the parent of the SSD, but the SSD leaf node is without an ACPI identity:
ssd.jpg


We can derive the path of the parent from ACPI(_SB_)#ACPI(PCI0)#ACPI(POP4) as _SB.PCI0.POP4, but the leaf node representing the SSD, #PCI(000), we will need to define ourselves as it doesn't exist in the OEM ACPI code.

The result is this:
Code:
// Inject bogus class-code for NVMe SSD to prevent IONVMeFamily.kext from loading
DefinitionBlock("", "SSDT", 2, "hack", "NVMe-Pcc", 0)
{
    External(_SB.PCI0.P0P4, DeviceObj)
    Device(_SB.PCI0.P0P4.SSD0) { Name(_ADR, 0) } // adding SSD0 identity under _SB.PCI0.P0P4
    Method(_SB.PCI0.P0P4.SSD0._DSM, 4)
    {
        If (!Arg2) { Return (Buffer() { 0x03 } ) }
        Return(Package()
        {
            "class-code", Buffer() { 0xff, 0x08, 0x01, 0x00 },
            "built-in", Buffer() { 0 },
        })
    }
}
//EOF

And here is a bit more complex example of the SSD behind a PCI bridge. The PCI bridge, #PCI(0800), is without an ACPI identity as is the SSD, #PCI(000), itself:
Capture4.PNG


The path to the parent of the PCI bridge can be derived from ACPI(_SB_)#ACPI(PCI0)#ACPI(PEG0)#ACPI(PEGP) as _SB.PCI0.PEG0.PEGP, but for the #PCI(0800)#PCI(0000), we must create the ACPI identity.

The result is as follows:
Code:
// Inject bogus class-code for NVMe SSD to prevent IONVMeFamily.kext from loading
DefinitionBlock("", "SSDT", 2, "hack", "NVMe-Pcc", 0)
{
    External(_SB.PCI0.PEG0.PEGP, DeviceObj)
    // create identities for the bridge @8 and SSD0 @0
    Device(_SB.PCI0.PEG0.PEGP.PBR8)
    {
        Name(_ADR, 0x00080000)  // corresponds to #PCI(0800), MSW byte reversed
        Device(SSD0) { Name(_ADR, 0) } // corresponds to #PCI(0000)
    }
    // now we can inject the _DSM at the newly created ACPI path
    Method(_SB.PCI0.PEG0.PEGP.PBR8.SSD0._DSM, 4)
    {
        If (!Arg2) { Return (Buffer() { 0x03 } ) }
        Return(Package()
        {
            "class-code", Buffer() { 0xff, 0x08, 0x01, 0x00 },
            "built-in", Buffer() { 0 },
        })
    }
}
//EOF


Special injections required for 10.11.x and Samsung 960 EVO (or other 144d:a804 NVMe device)

Because the 10.11.x IONVMeFamily.kext has a direct IOName match for pci144d,a804, which happens to be the device-id for the Samsung 960 EVO, if you have that combination (10.11.x + 960 EVO), you must inject some additional properties to prevent the native IONVMeFamily.kext from loading.

For example:
Code:
// Inject bogus class-code for NVMe SSD to prevent IONVMeFamily.kext from loading
// also inject "compatible", "IOName", and "name" such that 10.11.x IONVMeFamily
// does not match on pci144d,a804 (Samsung 960 EVO)
DefinitionBlock("", "SSDT", 2, "hack", "NVMe-Pcc", 0)
{
    External(_SB.PCI0.RP13.PXSX, DeviceObj)
    Method(_SB.PCI0.RP13.PXSX._DSM, 4)
    {
        If (!Arg2) { Return (Buffer() { 0x03 } ) }
        Return(Package()
        {
            "class-code", Buffer() { 0xff, 0x08, 0x01, 0x00 },
            "built-in", Buffer() { 0 },
            "compatible", Buffer() { "pci144d,a801" },
            "IOName", "pci144d,a801",
            "name", Buffer() { "pci144d,a801" },
        })
    }
}
//EOF

My initial/complete explanation is here: https://www.tonymacx86.com/threads/...stall-el-capitan-on-nvme.210837/#post-1409008


Existing _DSM methods

It is possible your OEM already defined a _DSM method at the ACPI path that defines your NVMe SSD in ACPI namespace. ACPI does not allow duplicate symbols at the same path, so if you have an existing _DSM, the _DSM we are trying to add via SSDT_NVMe-Pcc.aml will be ignored.

Although you can patch DSDT (or any OEM SSDT that may have the offending _DSM), I like to use a Clover patch to rename all _DSM methods to XDSM. Clover ACPI patches are specified at config.plist/ACPI/DSDT/Patches.

An entry for _DSM->XDSM would be:
Comment: Change all _DSM to XDSM
Find: <5f44534d>
Replace: <5844534d>

As it appears in Xcode:
Screen Shot 2016-12-20 at 5.03.33 PM.png


Renaming all _DSM to XDSM insures no existing _DSM at the NVMe SSD path. The _DSM injection via SSDT_NVMe-Pcc.aml will not work if there is an existing _DSM at that path.

You can find the _DSM to XDSM patch in config_patches.plist in the patch-nvme github project. It makes for easy copy/paste into your own config.plist.

Note: If you already have a statically patched DSDT.aml in ACPI/patched, you should not use the _DSM->XDSM patch. Instead, make sure you removed the _DSM at the path (or renamed to XDSM) in your patched DSDT (or patched SSDTs) at ACPI/patched/DSDT.aml. Typically, this would be done by applying "Remove _DSM methods", or "Rename _DSM methods to XDSM" as per my laptop ACPI patching guide.


Creating HackrNVMeFamily*.kext with the spoofed class-code

This is described in the patch-nvme README quite clearly, but here is a quick overview with special attention to the --spoof option.

As per patch-nvme README, we must make a copy of the github project:

Code:
mkdir ~/Projects
cd Projects
git clone https://github.com/RehabMan/patch-nvme.git patch-nvme.git
cd patch-nvme.git

Since your NVMe SSD will not be using the standard class-code, but instead a non-standard one, the resulting HackrNVMeFamily*.kext must use the non-standard IOPCIClassMatch instead of the standard one.

To do this we use the --spoof option. For example, to create HackrNVMeFamily for 10.12.2:
Code:
cd ~/Projects/patch-nvme.git
./patch_nvme.sh --spoof

If you're running 10.12.2, the result will be HackrNVMeFamily-10_12_2-spoof.kext which can be placed in EFI/Clover/kexts/Other for injection via Clover (installer or recovery scenario), or installed to the system volume (post-install scenario).

You can install kexts easily with Terminal. For example, to install the generated HackrNVMeFamily-10_12_2-spoof.kext:
Code:
cd ~/Projects/patch-nvme.git
sudo cp -R HackrNVMeFamily-10_12_2-spoof.kext /Library/Extensions

You can rebuild cache to verify cache can be built without error:
Code:
sudo kextcache -i /


Summary

To use the resulting HackrNVMeFamily*.kext:
- make sure the SSDT_NVMe-Pcc.aml is in EFI/Clover/ACPI/patched
- make sure existing _DSM methods at the NVMe SSD ACPI path are renamed or deleted
- make sure you use the --spoof option to generate HackrNVMeFamily*.kext
- install the HackrNVMeFamily*.kext or inject it via EFI/Clover/kexts

Note: Keep in mind that because Clover is typically configured with config.plist/SystemParameters/InjectKexts="Detect" that once FakeSMC.kext is installed to the system volume (as is typical), Clover will ignore EFI/Clover/kexts. Therefore, at post-install, you should always install HackrNVMeFamily*.kext to the system volume. Installing only FakeSMC.kext without HackrNVMeFamily*.kext in this scenario will result in an un-bootable system.

By using the SSDT, we inject a bogus class-code such that IONVMeFamily.kext does not match and therefore does not load. But because of the modified IOPCIClassMatch in the HackrNVMeFamily Info.plist created by the patch_nvme.sh script with the --spoof option, it does match on the modified class-code, and is patched for the 512 byte block size used by typical PC NVMe SSDs.

By using the class-code spoof from the very beginning, you're using the same mechanism during installation and post-installation. And once the class-code spoof is implemented, there is never any chance of IONVMeFamily.kext causing a problem. Updating is simplified as well, since you can most likely use the previous HackrNVMeFamily for the updated system until you have a chance to create a new HackrNVMeFamily after the update. And because IONVMeFamily was never deleted, it is always easy to create the new HackrNVMeFamily*.kext after the update is complete and the patch set for that version is available.

System Updates

Once you have the class-code spoof in place, system updates are simplified because it is not necessary to delete IONVMeFamily.kext. But you should still plan to update HackrNVMeFamily*.kext *after* each update such that you have current NVMe driver code.

The procedures are somewhat obvious, but listed here (example given for update from 10.12.2 to 10.12.3):

- update via App Store
- wait for RehabMan to update the patch-nvme github project
- update your copy of the patch-nvme github repo
Code:
cd ~/Projects/patch-nvme.git
git pull
- create new HackrNVMeFamily*.kext
Code:
./patch_nvme.sh --spoof
- remove previous HackrNVMeFamily*.kext (eg. HackrNVMeFamily-10_12_2-spoof.kext)
Code:
sudo rm -Rf /Library/Extensions/HackrNVMeFamily*.kext
- install new HackrNVMeFamily-10_12_3.kext
Code:
sudo cp -R HackrNVMeFamily-10_12_3-spoof.kext /Library/Extensions


Problem Reporting

Provide a detailed and concise description of the problem.

If your drive is not recognized by the macOS/OS X installer or you are getting a panic...

Provide image from Windows Device Manager showing the ACPI namespace path of your SSD.

Attach EFI/Clover folder as ZIP (press F4 at main Clover screen before collecting). Please eliminate 'themes' directory. Provide only EFI/Clover, not the entire EFI folder.
 
Last edited:
Thank you, RehabMan, for this guide. The good news is that the guide clarifies a lot of the information in your patch-nvme README on github: https://github.com/RehabMan/patch-nvme for those of us who don't have your in depth knowledge.
 
Thank you! With this guide I was able to get my 960 EVO working with just a few minutes of effort.
 
Thanks RehabMan for making your work so freely available to others. I have been struggling to get a stable, non-panicky version of OS X 10.12.2 installed on my 950 using your guide, but I am a noob at hacking OS X, so I expected this not to be easy, and I have by no means given up. I am currently trying to follow your recently updated, nicely detailed guide, but I haven't been able to determine the ACPI path of my 950 Pro NVMe SSD using the method you described. I have attached a screen shot to show you that the 'BIOS device option' isn't available for me (although that option is available for other hardware). I tried out ioreg also, but I didn't get very far. Is there a trick I am missing that will display the BIOS device option? I am using Windows 10 Pro. My Samsung 950 Pro is showing under Disk drives (it's currently the only SSD/HD I have connected and UEFI Windows 10 Pro is installed on it).
 

Attachments

  • Capture.PNG
    Capture.PNG
    88.5 KB · Views: 2,349
Last edited:
Thanks RehabMan for making your work so freely available to others. I have been struggling to get a stable, non-panicky version of OS X 10.12.2 installed on my 950 using your guide, but I am a noob at hacking OS X, so I expected this not to be easy, and I have by no means given up. I am currently trying to follow your recently updated, nicely detailed guide, but I haven't been able to determine the ACPI path of my 950 Pro NVMe SSD using the method you described. I have attached a screen shot to show you that the 'BIOS device option' isn't available for me (although that option is available for other hardware). I tried out ioreg also, but I didn't get very far. Is there a trick I am missing that will display the BIOS device option? I am using Windows 10 Pro. My Samsung 950 Pro is showing under Disk drives (it's currently the only SSD/HD I have connected and UEFI Windows 10 Pro is installed on it).

As per post #1: Not "BIOS device option". Instead look for "BIOS device name".

The properties are not listed in alpha order, so it is not possible to determine from your image that it is not present. Note the scrollbar in your image. You need to look for it carefully throughout the list.

You might also look for "Location paths". For example on my NUC6i7KYK:
Capture-NVMe2.PNG


It shows: ACPI(_SB_)#ACPI(PCI0)#ACPI(RP13)#ACPI(PXSX), from which you can derive _SB.PCI0.RP13.PXSX
 
Last edited:
As per post #1: Not "BIOS device option". Instead look for "BIOS device name".

The properties are not listed in alpha order, so it is not possible to determine from your image that it is not present. Note the scrollbar in your image. You need to look for it carefully throughout the list.

I do not see 'BIOS' anywhere in my list. As stated in prior post, this option is available for different devices under the Device Manager (just not for my 950 Pro SSD).

I appreciate the help! :)

-------------

Update: Got it, thanks!
 
Following your example, I derive _SB.PCI0.PEG0.PEGP

It may have more path components without ACPI identities. Likely behind a PCI bridge...
If you have an ioreg, it would be more clear.

But guessing, might be something like:
Code:
// Inject bogus class-code for NVMe SSD to prevent IONVMeFamily.kext from loading
DefinitionBlock("", "SSDT", 2, "hack", "NVMe-Pcc", 0)
{
    External(_SB.PCI0.PEG0.PEGP, DeviceObj)
    Device(_SB.PCI0.PEG0.PEGP.PCI8)
    {
        Name(_ADR, 0x00080000)
        Device(SSD0) { Name(_ADR, 0) }
    }
    Method(_SB.PCI0.PEG0.PEGP.PCI8.SSD0._DSM, 4)
    {
        If (!Arg2) { Return (Buffer() { 0x03 } ) }
        Return(Package()
        {
            "class-code", Buffer() { 0xff, 0x08, 0x01, 0x00 },
        })
    }
}
//EOF
 
Last edited:
Status
Not open for further replies.
Back
Top