Contribute
Register

[Guide] Disabling discrete graphics in dual-GPU laptops

RehabMan

Moderator
Joined
May 2, 2012
Messages
181,112
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 show you how to disable the discrete graphics device with DSDT/SSDT edits in "switched" dual-GPU laptops (eg. Intel+Nvidia[Optimus] and Intel+Radeon).

Because only the Intel device can be used in these laptops, the discrete device is generally left active and using power, contributing to heat, noise, and battery usage. Although the device can usually be disabled in BIOS, it is better to disable it with a custom ACPI setup so the device can still be used when booting Windows.

Although it is a relatively simple patch (sometimes only a one line change), because of the fact that the patch is done to one or more SSDTs, there are many pitfalls to fall into. Also, certain machine/ACPI configurations require different approaches, leading to more complexity. The example DSDT/SSDT set that this guide uses is one of the more complex setups, so it covers most of the issues you might run into with your own.

You should follow this walkthrough with the example before patching your own.

The laptop being used for this guide/example is an "Asus UX303LN" with Intel HD4400 + Nvidia. The Clover F4 extracted tables (ACPI/origin directory) are provided in an attachment to this post.

Note: There is also a full hotpatch guide (for this same computer) here: https://www.tonymacx86.com/threads/guide-using-clover-to-hotpatch-acpi.200137/


Patching requirements

The goal is quite simple. These machines provide an _OFF method, usually in an SSDT, that can be called to power down the discrete device. The easiest fix is to call _OFF from the corresponding _INI method. Note that the _OFF method may be in DSDT or may have a different name (GPOF, OPOF, _PS3, etc.)

Certain implementations of _OFF cannot be called from _INI as they access the EC (Embedded Controller) space. For these machines, _OFF (or a portion of it) must be delayed until _REG (when Arg0==3 and Arg1==1, see ACPI spec for more information regarding _REG). In some cases, calling from _REG is too late and it is either not effective or leads to a crash. In such a case, editing _OFF to remove EC dependencies is necessary, so it can be called from _INI. The code that was removed from _OFF is then inserted into _REG so the effect is the same although the EC work is done later. This is the case with the example ACPI setup in this guide.

Note #1: Not all _OFF implementations have EC related code. In that case, no need to move any such code to _REG as it simply didn't exist in the first place.

Note: #2: Not all ACPI sets have an _INI at the _OFF path. In that case, you simply add an _INI that calls off. Easiest to add it just before the _OFF method:
Code:
Method(_INI) { _OFF() }


Basic patching

It is important to understand how to extract, disassemble and patch your DSDT/SSDT files, and how to install them for your bootloader, etc. This subject is covered in my guide: http://www.tonymacx86.com/yosemite-laptop-support/152573-guide-patching-laptop-dsdt-ssdts.html. You should be familiar with it before attempting this guide.

Because patching for the basics is important, we'll do that first here before getting started on the patching required for disabling the Nvidia card in the example.

This guide will assume that most common rename patches are done in config.plist/ACPI/DSDT/Patches. And it will assume that AutoMerge=true (relatively new Clover feature, added by RehabMan). These patches and settings are default in the guide plists: https://www.tonymacx86.com/threads/guide-booting-the-os-x-installer-on-laptops-with-clover.148093/

In the ACPI patching guide (linked above), the kind of ACPI configuration assumed by this guide is "Partial hotpatch with patched SSDTs", as described in "Recommended configurations".

First, the files are extracted (you can simply download the example files), then disassembled with iasl:
Code:
iasl -da -dl *.aml

After disassembly we can proceed to patch the various files. Because we have common renames in config.plist, and are using AutoMerge=true, we only need to apply functional patches not covered by the defaults from config.plist. And there is no need to correct errors in a file when it requires no patching. The file is, instead, simply omitted from ACPI/patched.

For DSDT.dsl:
"Fix PARSEOP_ZERO Error (agressive)"
"Fix ADBG Error"
"Rename _DSM methods to XDSM"
"IRQ Fix"
"SMBUS Fix"
"OS Check Fix (Windows 8)"
"Add IMEI"
"RTC Fix"
"Fix _WAK Arg0 v2"
"ASUS N55SL/VivoBook"
"USB3 _PRW 0x6D (instant wake)"
"Audio Layout 12"

Since our config.plist has the common renames, none of the SSDTs need any patching, except those required to disable the discrete graphics device. At this point, we are ready to look at the SSDTs to determine the patches needed to disable the device.


Patching for discrete disable

Remember the goal? Call _OFF from _INI...

Now... how to find the SSDT that has _OFF. We can use grep:
Code:
grep -l Method.*_OFF *.dsl

Which shows:
Code:
SSDT-7.dsl
SSDT-8.dsl

Also, it is nice to know where the _INI methods are:
Code:
grep -l Method.*_INI *.dsl

Which shows:
Code:
DSDT.dsl
SSDT-6.dsl
SSDT-7.dsl
SSDT-8.dsl

Note: SSDT-7 and SSDT-8 are listed again. This is likely where the _OFF and associated _INI are located...

We could also open each file individually into MaciASL and search for _OFF and _INI, but using grep is a bit easier and faster.

When we open SSDT-7.dsl and look for "Method (_INI", we find this:
Code:
        Method (_INI, 0, NotSerialized)  // _INI: Initialize
        {
            Store (Zero, \_SB.PCI0.RP05.PEGP._ADR)
        }

This is a typical _INI method for a discrete device, and one that we want to patch so it calls _OFF.

If we click in the method body, we can see the ACPI "path" that this method lives in. MaciASL shows it on the status bar: SSDT -> \_SB.PCI0.RP05.PEGP -> _INI. Now we know that the path to _OFF should also be \_SB.PCI0.RP05.PEGP._OFF.

So, we know that _OFF is either in SSDT-7.dsl or SSDT-8.dsl. If you open SSDT-7.dsl and look for _OFF, you find it is a method inside a PowerResource macro. This is not the _OFF method we want. Open SSDT-8.dsl and look for _OFF there and you'll find a regular _OFF method, which is what we are looking for. So now we know where _OFF is. We need to know, as it is necessary to review the code for potential EC access.

In SSDT-8.dsl, _OFF is defined as follows:
Code:
        Method (_OFF, 0, Serialized)  // _OFF: Power Off
        {
            If (LEqual (CTXT, Zero))
            {
                \_SB.PCI0.LPCB.EC0.SPIN (0x96, Zero)
                If (LNotEqual (GPRF, One))
                {
                    Store (VGAR, VGAB)
                }

                Store (One, CTXT)
            }

            SGOF ()
        }

If you examine the code, you can see it accesses the EC (\_SB.PCI0.LPCB.EC0.SPIN (0x96, Zero). This is going to be a problem and will prevent _OFF from executing fully when called from _INI. Keep in mind it is not always this obvious. The code could have called a method not directly related to the EC, which in turn calls a method in the EC (indirect access). So you may need to do some digging. This example was obvious as the method path includes EC0.

The best way to deal with the EC access is to remove the offending code from _OFF.

We can do it manually, or use a patch like this:
Code:
into method label _OFF parent_label \_SB.PCI0.RP05.PEGP code_regex .*EC.* removeall_matched;

The modified _OFF method is this:
Code:
        Method (_OFF, 0, Serialized)  // _OFF: Power Off
        {
            If (LEqual (CTXT, Zero))
            {
                If (LNotEqual (GPRF, One))
                {
                    Store (VGAR, VGAB)
                }

                Store (One, CTXT)
            }

            SGOF ()
        }

After making this change, attempt to compile SSDT-8.dsl. You will notice it has errors. Really, we should have taken care of these errors before making changes. But we can fix them now by applying the "Cleanup/Fix Errors (SSDT)" patch. That removes some of the errors, but there is still one additional error at line 620:
Code:
       Store (TCAP, \_PR.CPU0._PTC ())

This code is invalid, and in fact will be skipped by the ACPI runtime. \_PR.CPU0._PTC is a method, and of course it is not valid to store something "into" a method. We can simply comment this code:
Code:
//       Store (TCAP, \_PR.CPU0._PTC ())

We should still have the line that we removed, \_SB.PCI0.LPCB.EC0.SPIN (0x96, Zero), executed in _REG, so keep that in mind.

Now that we have fixed _OFF, let's call it from _INI, defined in SSDT-7.dsl.

We can apply patch "Disable from _INI (SSDT)". But our method's path is slightly different from what is common, so we need to modify the patch. Also, we are calling to _OFF which is defined in SSDT-8.dsl, so we need the External declaration.

The modified patch follows:
Code:
into method label _INI parent_label \_SB.PCI0.RP05.PEGP insert
begin
//added to turn nvidia/radeon off\n
External(\_SB.PCI0.RP05.PEGP._OFF, MethodObj)\n
_OFF()\n
end;

After applying this patch, the patched _INI now reads:
Code:
        Method (_INI, 0, NotSerialized)  // _INI: Initialize
        {
            Store (Zero, \_SB.PCI0.RP05.PEGP._ADR)
            //added to turn nvidia/radeon off
            External(\_SB.PCI0.RP05.PEGP._OFF, MethodObj)
            _OFF()
        }

Now we need to turn our attention to DSDT _REG. The _REG method needs to do the EC work that _OFF used to do before we patched it.

Here is the original _REG (for EC0) in DSDT:
Code:
            Method (_REG, 2, NotSerialized)  // _REG: Region Availability
            {
                If (LEqual (Arg0, 0x03))
                {
                    Store (Arg1, ECFL)
                }
            }

There is a patch in my repo to call _OFF from _REG. We can use it as a base for what we need:
Code:
into method label _REG parent_hid PNP0C09 insert
begin
//added to turn nvidia/radeon off\n
If (LAnd(LEqual(Arg0,3),LEqual(Arg1,1)))\n
{\n
    External(\_SB.PCI0.PEG0.PEGP._OFF, MethodObj)\n
    \_SB.PCI0.PEG0.PEGP._OFF()\n
}\n
end;

Instead of calling _OFF, we want it to call SPIN as the original _OFF did.

The modified patch is:
Code:
into method label _REG parent_hid PNP0C09 insert
begin
//added to turn nvidia/radeon off\n
If (LAnd(LEqual(Arg0,3),LEqual(Arg1,1)))\n
{\n
    \_SB.PCI0.LPCB.EC0.SPIN (0x96, Zero)\n
}\n
end;

The modified _REG after patching:
Code:
            Method (_REG, 2, NotSerialized)  // _REG: Region Availability
            {
                If (LEqual (Arg0, 0x03))
                {
                    Store (Arg1, ECFL)
                }
                //added to turn nvidia/radeon off
                If (LAnd(LEqual(Arg0,3),LEqual(Arg1,1)))
                {
                    \_SB.PCI0.LPCB.EC0.SPIN (0x96, Zero)
                }
            }

And that concludes patching. At this point all the patched files can be compiled and placed in ACPI/patched.

Only the patched files are placed in ACPI/patched:
DSDT.aml (has core patches, _REG patch)
SSDT-7.aml (has _INI patch)
SSDT-8.aml (has _OFF patch)

Note that this example is complex and not all laptops will have the same configuration. For most, the _INI and _OFF associated with the discrete device are in the same SSDT. In that case it is not necessary to use the External declaration. You can use the "Call _OFF from _INI (SSDT)" patch directly. Some even have it in DSDT (again you can use the patch directly). And not all _OFF methods access the EC, so the movement of the EC related code to _REG is not needed.

It is also possible that the EC related code is more than one line. All such code should be moved to _REG.


Sleep/Wake Problems

Some laptops have issues with sleep/wake or even shutdown/restart with this patch in place. This was the case with the HP ProBook (with Radeon). The fix is to re-enable the card before sleeping and disable it on wake.

There is a patch in the repository to accomplish this. It is called "Disable/Enable on _WAK/_PTS (DSDT)".

But since this DSDT has _OFF/_ON at a different path than the patch expects, we need to modify it.

Original patch:
Code:
into method label _PTS code_regex ([\s\S]*) replace_matched
begin
External(\\_SB.PCI0.PEG0.PEGP._ON, MethodObj)\n
If (CondRefOf(\\_SB.PCI0.PEG0.PEGP._ON)) { \\_SB.PCI0.PEG0.PEGP._ON() }\n
%1
end;

into method label _WAK code_regex (Return\s+\(.*) replace_matched
begin
External(\\_SB.PCI0.PEG0.PEGP._OFF, MethodObj)\n
If (CondRefOf(\\_SB.PCI0.PEG0.PEGP._OFF)) { \\_SB.PCI0.PEG0.PEGP._OFF() }\n
%1
end;

Modified patch (PEG0 changed to RP05):
Code:
into method label _PTS code_regex ([\s\S]*) replace_matched
begin
External(\\_SB.PCI0.RP05.PEGP._ON, MethodObj)\n
If (CondRefOf(\\_SB.PCI0.RP05.PEGP._ON)) { \\_SB.PCI0.RP05.PEGP._ON() }\n
%1
end;

into method label _WAK code_regex (Return\s+\(.*) replace_matched
begin
External(\\_SB.PCI0.RP05.PEGP._OFF, MethodObj)\n
If (CondRefOf(\\_SB.PCI0.RP05.PEGP._OFF)) { \\_SB.PCI0.RP05.PEGP._OFF() }\n
%1
end;

If your laptop doesn't have problems with sleep/wake/restart/shutdown with the discrete device disabled, then you do not need this patch. You need to test your laptop first to determine if it is needed.

Note: If you patched _OFF to remove EC related code, and you're calling _OFF from _WAK, you may need to also execute that removed code in _WAK. One way to do that is to have an XOFF that is the original/un-patched _OFF that you can call from _WAK instead of calling the (patched) _OFF.


Older laptops

You will notice that older laptops may not have _OFF, or may have an empty _OFF. In such cases, you may notice that _PS3 is required. In some cases, _PS3 will not work unless the invoke the associated _DSM with appropriate params. Details are in post #96 of this thread: http://www.tonymacx86.com/yosemite-...graphics-dual-gpu-laptops-10.html#post1056748


Problem Reporting

If you have problems with your DSDT/SSDTs in patching or fixing errors, please provide all the native files that you're working with.

If you attempt to disable your discrete card according to this guide and it is not working, please provide "Problem Reporting" data as per FAQ.

Read FAQ, "Problem Reporting". Carefully. Attach all requested files/output.
https://www.tonymacx86.com/threads/faq-read-first-laptop-frequent-questions.164990/



Sample Attachment

The following files are used as the example ACPI set in this guide:
 

Attachments

  • ASUS_UX303LN_origin.zip
    59.2 KB · Views: 3,261
Last edited:
Wow, wonderful !!
:thumbup:

Can I translate this post and publish on a Chinese forum ?
 
Wow, wonderful !!
:thumbup:

Can I translate this post and publish on a Chinese forum ?

I was wondering how long it would be before you asked... Not long!

As long as the original is linked, and credit given, I'm ok with it...
 
I was wondering how long it would be before you asked... Not long!

As long as the original is linked, and credit given, I'm ok with it...

Yeah, of course. :) I will give credits to you and link the original post. :)
 
I tried the patches on both SSDT and DSDT from your repo to disable Nvidia. I have attached the patched SSDT and DSDT.

But it didn't got disabled.
 

Attachments

  • DSDT.aml
    38.3 KB · Views: 896
  • SSDT-5.aml
    4.8 KB · Views: 912
I tried the patches on both SSDT and DSDT from your repo to disable Nvidia. I have attached the patched SSDT and DSDT.

But it didn't got disabled.

Please read post #1, "Problem reporting"
 
Code:
kextstat|grep -y acpiplat
kextstat|grep -y appleintelcpu

Prashants-MacBook-Air:~ prashant$ kextstat|grep -y acpiplat
13 2 0xffffff7f82f5c000 0x65000 0x65000 com.apple.driver.AppleACPIPlatform (3.1) <12 11 7 6 5 4 3 1>
Prashants-MacBook-Air:~ prashant$ kextstat|grep -y appleintelcpu
21 0 0xffffff7f82b87000 0x2b000 0x2b000 com.apple.driver.AppleIntelCPUPowerManagement (218.0.0) <7 6 5 4 3 1>
30 0 0xffffff7f82b82000 0x3000 0x3000 com.apple.driver.AppleIntelCPUPowerManagementClient (218.0.0) <7 6 5 4 3 1>

I have attached all the files
 

Attachments

  • RehabMan.zip
    449.5 KB · Views: 990
...
I have attached all the files

You did not follow the guide. Note that there is still GFX0 references in SSDT-1.aml (patchmatic output), although it has been renamed to IGPU in DSDT. Renames must always be balanced.

Also, make sure you haven't dropped any SSDTs that are required (my guess is you have them all, but not possible to verify without seeing native files).

Also, not a good idea to call _OFF from both _REG and _INI. Call from _INI first, and only call from _REG if you think it is necessary (and them remove call from _INI).
 
You did not follow the guide. Note that there is still GFX0 references in SSDT-1.aml (patchmatic output), although it has been renamed to IGPU in DSDT. Renames must always be balanced.

Also, make sure you haven't dropped any SSDTs that are required (my guess is you have them all, but not possible to verify without seeing native files).

Also, not a good idea to call _OFF from both _REG and _INI. Call from _INI first, and only call from _REG if you think it is necessary (and them remove call from _INI).

I think it got disabled. Not sure though but it can't be seen in system information.
 

Attachments

  • RehabMan.zip
    399.7 KB · Views: 1,075
Back
Top