FireWolf Pl.

A Place of Freedom

@FireWolf1 month ago

11/10
23:28
macOS Mojave

Coffee Lake Intel UHD Graphics 630 on macOS Mojave: A nearly ultimate solution to the kernel panic due to division by zero in the framebuffer driver

Hi folks! Times goes fast. I have some new findings to share with you. Now it’s time to write a new post.

If this page looks strange due to font or layout issues, you can find a pretty-printed GitBook version at here.

>> Introduction

Nowadays, it might not be easy to make the integrated graphics card, namely Intel UHD Graphics 630, fully work on Apple’s latest macOS Mojave without any issues on a Coffee Lake-based laptop. In addition to the minimum requirement of DVMT pre-allocated memory that I identified three years ago, some built-in displays may not report valid values of certain capabilities, resulting in the graphics driver failing to verify those values and reporting a fatal error by triggering a kernel panic.

To be more specific, from the perspective of the DisplayPort physical layer, a display or a monitor is identified as a sink device, while a GPU in our computer can be viewed as a source device. The main link between the source device and the sink device is used to transfer video and audio streams and is established with either 1, 2 or 4 lanes enabled depending on the specific requirements. Each lane is associated with a valid link rate, namely 1.62 Gbps (RBR), 2.7 Gbps (HBR), 5.4 Gbps (HBR2) and 8.1 Gbps (HBR3) per lane. In addition, there is also an AUX channel that is used by the source device to discover capabilities of the sink device, such as the rendering capabilities and preferences specified by EDID and the link transport capabilities specified by DisplayPort Configuration Data (DPCD). These capabilities are then used by the graphics driver to perform the link training to establish the final stable and safe link between the graphics card the display.

In this article, I want to place emphasis on invalid values read from DPCD of the built-in display, and discuss how these invalid values affect Apple’s framebuffer driver, namely AppleIntelCFLGraphicsFramebuffer, so that the internal 4K display does not work properly on some non-Apple laptops until a fix is applied.

>> Recall the story

In order to make the integrated graphics card work under macOS, we need to first assure that DVMT pre-allocated memory is set to a value that is greater than 32 MB. Since the internal display has a 4K resolution, 64 MB DVMT is set by default in the BIOS, but now we have to patch the CoreDisplay framework to unlock the pixel clock limitation. Lastly, we need to inject a valid ig-platform-id to try to load the graphics driver, and 0x3E9B0000 is a commonly used one for Intel UHD Graphics 630 on laptops.

However, things don’t work as expected, and an invisible kernel panic happens right before the internal display is lighted up. It is invisible because it looks like an immediate reboot after the graphics driver loads, but we can obtain a detailed panic report as follows.

Anonymous UUID: A6C52317-1B2D-E00D-241C-DBCE7C091990
Sat Sep 29 13:17:09 2018
*** Panic Report ***
panic(cpu 8 caller 0xffffff80004d781d): Kernel trap at 0xffffff7f837537d0, type 0=divide error, registers:
CR0: 0x0000000080010033, CR2: 0xffffff81f645e000, CR3: 0x00000004512ce05c, CR4: 0x00000000003626e0
RAX: 0x017d68fdc0000000, RBX: 0x017d68fdc0000000, RCX: 0x0100000100000000, RDX: 0x0000000000000000
RSP: 0xffffff81e8ba3540, RBP: 0xffffff81e8ba3570, RSI: 0xffffff81e8ba3388, RDI: 0xffffff81c816c000
R8: 0x00000003169e9807, R9: 0x0000000000000000, R10: 0xffffff81c8178d00, R11: 0x0000000000000000
R12: 0x000000001fc8bfd0, R13: 0x0000000000000000, R14: 0xffffff81e8ba3588, R15: 0x0000000009a7ec80
RFL: 0x0000000000010246, RIP: 0xffffff7f837537d0, CS: 0x0000000000000008, SS: 0x0000000000000010
Fault CR2: 0xffffff81f645e000, Error code: 0x0000000000000000, Fault CPU: 0x8, PL: 0, VF: 0
Backtrace (CPU 8), Frame : Return Address
0xffffff81e8ba3010 : 0xffffff80003aba9d
0xffffff81e8ba3060 : 0xffffff80004e5bd3
0xffffff81e8ba30a0 : 0xffffff80004d75fa
0xffffff81e8ba3110 : 0xffffff8000358ca0
0xffffff81e8ba3130 : 0xffffff80003ab4b7
0xffffff81e8ba3250 : 0xffffff80003ab303
0xffffff81e8ba32c0 : 0xffffff80004d781d
0xffffff81e8ba3430 : 0xffffff8000358ca0
0xffffff81e8ba3450 : 0xffffff7f837537d0
0xffffff81e8ba3570 : 0xffffff7f837514bb
0xffffff81e8ba3990 : 0xffffff7f837296e5
0xffffff81e8ba3a00 : 0xffffff7f814dd7c6
0xffffff81e8ba3a40 : 0xffffff7f814dd67b
0xffffff81e8ba3a90 : 0xffffff8000a83f68
0xffffff81e8ba3ae0 : 0xffffff7f814e3c79
0xffffff81e8ba3b30 : 0xffffff8000a8d3ef
0xffffff81e8ba3c70 : 0xffffff8000492234
0xffffff81e8ba3d80 : 0xffffff80003b118d
0xffffff81e8ba3dd0 : 0xffffff800038bb45
0xffffff81e8ba3e50 : 0xffffff80003a04fe
0xffffff81e8ba3ef0 : 0xffffff80004bed4b
0xffffff81e8ba3fa0 : 0xffffff8000359486
Kernel Extensions in backtrace:
com.apple.iokit.IOGraphicsFamily(530.9)@0xffffff7f814c1000->0xffffff7f8150bfff
dependency: com.apple.iokit.IOPCIFamily(2.9)@0xffffff7f80c95000
com.apple.driver.AppleIntelCFLGraphicsFramebuffer(12.0.2)@0xffffff7f83711000->0xffffff7f83912fff
dependency: com.apple.iokit.IOPCIFamily(2.9)@0xffffff7f80c95000
dependency: com.apple.iokit.IOACPIFamily(1.4)@0xffffff7f80d10000
dependency: com.apple.iokit.IOAcceleratorFamily2(400.25)@0xffffff7f82ef0000
dependency: com.apple.iokit.IOReportFamily(47)@0xffffff7f80ddb000
dependency: com.apple.AppleGraphicsDeviceControl(3.22.18)@0xffffff7f817d8000
dependency: com.apple.iokit.IOGraphicsFamily(530.9)@0xffffff7f814c1000

This report indicates that a kernel panic is triggered because of a divided-by-zero error, and we can further know that it is triggered inside a function named AppleIntelFramebufferController::SetupDPTimings(AppleIntelFramebuffer*, AppleIntelDisplayPath*, AppleIntelFramebufferController::CRTCParams*) after we disassemble the executable file.

//
// Assembly code of AppleIntelCFLGraphicsFramebuffer on macOS 10.12.1 beta 1
//
loc_427cb:
00000000000427cb xor edx, edx
00000000000427cd mov rax, rbx
00000000000427d0 div r13             // Trigger the kernel panic
00000000000427d3 mov rsi, rax
00000000000427d6 cmp rsi, 0x1000000
00000000000427dd jb loc_427e8
int AppleIntelFramebufferController::SetupDPTimings(AppleIntelFramebufferController* this,
                                                    AppleIntelFramebuffer* framebuffer, 
                                                    AppleIntelDisplayPath* displayPath, 
                                                    AppleIntelFramebufferController::CRTCParams* params)
{
    // Parameters are passed in registers
    // %rdi = arg0 (Hidden `this` pointer)
    // %rsi = arg1 (framebuffer)     // <- Focus on this parameter
    // %rdx = arg2 (displayPath)
    // %rcx = arg3 (parameters)

    // ......

    rdx = framebuffer->field_0x25bc; // 0x42786: movl 0x25bc(%rsi), %edx
    r13 = r15 * 8;                   // 0x4278c: leal (,%rdx,8), %r13d
    r13 = r13 * r15;                 // 0x42794: imulq %r15, %r13

    if (rdx == 0 || r15 == 0)
    {
        kprintf("[IGFB][ERROR ] fActiveNumberOfLanes = %d, linkSymbolClock = %llu.. 
                 One of them is 0 for FB%x - will lead to DivideByZero panic. 
                 PixelClock = %llu!!!!!\n", rdx, r15, framebuffer->field_0x01dc, r12");
    }

    rsi = rbx / r13;

    // ......
}

The previous research has shown that the kernel panic is triggered because the number of active lanes for a specific framebuffer port is 0, and I have provided a compromise solution by setting it to a non-zero constant value. Unfortunately, it turns out that this is a bad solution and might be directly related to no display after the laptop wakes up. Consequently, our new mission now is to keep digging up the framebuffer driver and find out why this field is initialized to 0.

>> Find the nature of the issue

Now the question becomes why the framebuffer driver initializes the field that represents the number of lanes to 0. Since there is not too much information about it, we need to analyze the callers that invoke SetupDPTimings(), and there is only one such function, namely AppleIntelFramebufferController::SetupClocks(AppleIntelFramebuffer*, AppleIntelDisplayPath*, IODetailedTimingInformationV2 const*, AppleIntelFramebufferController::CRTCParams*).

It is a really huge function, but there are several lines before invoking SetupDPTimings() that draw my attention.

//
// Assembly code of AppleIntelCFLGraphicsFramebuffer on macOS 10.12.2 beta 1
//
000000000004219e         movzxb     0x2387(%rbx), %eax      // %eax = framebuffer->field_0x2387
00000000000421a5         movl       %eax, 0x25c0(%rbx)      // framebuffer->field_0x25c0 = %eax
00000000000421ab         movzxb     0x2389(%rbx), %eax      // %eax = framebuffer->field_0x2389
00000000000421b2         movl       %eax, 0x25bc(%rbx)      // framebuffer->field_0x25bc = %eax

Recall that the field at 0x25b in an AppleIntelFramebuffer instance represents the number of lanes, so we can know from the above lines that the value of this field is assigned by another field at 0x2389, and a similar value assignment also happens on the field at 0x25c0.

//
// Assembly code of AppleIntelCFLGraphicsFramebuffer on macOS 10.12.2 beta 1
//
000000000004213a         incq       qword_aa928+53648       // Used for debugging only, ignore this.
0000000000042141         cmpb       $0x0, 0x2387(%rbx)      // if framebuffer->field_0x2387 == 0
0000000000042148         je         loc_4215a               // then goto 0x4215a and read DPCD again

000000000004214a         incq       qword_aa928+53664       // Used for debugging only, ignore this.
0000000000042151         cmpb       $0x0, 0x2389(%rbx)      // if framebuffer->field_0x2389 == 0
0000000000042158         jne        loc_4219e               // then goto 0x4215a and read DPCD again
                                                            // else goto 0x4219e and record the data

loc_4215a:
000000000004215a         incq       qword_aa928+53656       // Used for debugging only, ignore this.
0000000000042161         leaq       aIgfbInvParas, %rdi     // arg0 of kprintf()
// "[IGFB][ERROR  ] Invalid link parameters..re-attempt a DPCD read\\n"
0000000000042168         xorl       %eax, %eax
000000000004216a         call       _kprintf                // Print to the kernel logs
000000000004216f         movq       %r12, %rdi              // %rdi holds the reference to `this`
0000000000042172         call       __ZN31AppleIntelFramebufferController15enableVDDForAuxEv
0000000000042177         movq       %r12, %rdi              // arg0: `this`
000000000004217a         movq       %rbx, %rsi              // arg1: framebuffer instance
000000000004217d         movq       %r15, %rdx              // arg2: displayPath instance
0000000000042180         call      
__ZN31AppleIntelFramebufferController11GetDPCDInfoEP21AppleIntelFramebufferP21AppleIntelDisplayPath
0000000000042185         testl      %eax, %eax              // if retVal == 0
0000000000042187         je         loc_4219e               // (success) then goto 0x4219e 
                                                            // (failure) else DPCD read fails again

0000000000042189         incq       qword_aa928+53672       // Used for debugging only, ignore this.
0000000000042190         leaq       aIgfberrorDpcdR, %rdi   // arg0 of kprintf()                    
// "[IGFB][ERROR  ] DPCD read re-attempt too failed\\n"        
0000000000042197         xorl       %eax, %eax
0000000000042199         call       _kprintf                // Print to the kernel logs

loc_4219e:
000000000004219e         movzxb     0x2387(%rbx), %eax      // %eax = framebuffer->field_0x2387
00000000000421a5         movl       %eax, 0x25c0(%rbx)      // framebuffer->field_0x25c0 = %eax
00000000000421ab         movzxb     0x2389(%rbx), %eax      // %eax = framebuffer->field_0x2389
00000000000421b2         movl       %eax, 0x25bc(%rbx)      // framebuffer->field_0x25bc = %eax

If we scroll up a little bit, we can find that the driver compares the values of the field at 0x2387 and 0x2389 against 0. According to the first parameter value (passed in register %rdi) of the kprintf call at offset 0x4216a, non-zero values indicate that link parameters are valid, and therefore it is needless to perform another DPCD read. Since we have already known that framebuffer->field_0x2389 is 0, we expect to see the line [IGFB][ERROR ] Invalid link parameters..re-attempt a DPCD read\\n is printed in the kernel logs. In addition, the driver also checks the return value of GetDPCDInfo() against 0. If it is not 0, the driver then prints the line [IGFB][ERROR ] DPCD read re-attempt too failed\\n, indicating the failure of a DPCD read. In summary, we now need to dump and examine the kernel log to figure out whether DPCD is read without any error or not.

// Inside `AppleIntelFramebufferController::SetupClocks()`
kernel: (AppleIntelCFLGraphicsFramebuffer) [IGFB][ERROR  ] Invalid link parameters..re-attempt a DPCD read

// Inside `AppleIntelFramebufferController::GetDPCDInfo()`
kernel: (AppleIntelCFLGraphicsFramebuffer) [IGFB][INFO   ] FB0: OUI for display
kernel: (AppleIntelCFLGraphicsFramebuffer) [IGFB][INFO   ]  38 EC 11 0
kernel: (AppleIntelCFLGraphicsFramebuffer) [IGFB][INFO   ]  0 0 0 0
kernel: (AppleIntelCFLGraphicsFramebuffer) [IGFB][INFO   ] FB0: Display port config ver is 1.4

// `AppleIntelFramebufferController::GetDPCDInfo()` returns
// Back to `AppleIntelFramebufferController::SetupClocks()`
kernel: (AppleIntelCFLGraphicsFramebuffer) [IGFB][ERROR  ] DPCD read re-attempt too failed

We can clearly see that DPCD read fails from the above kernel logs. To be more specific, GetDPCDInfo() returned with an error after printing the DisplayPort config version, so apparently somethings went wrong or some checks were not passed after the version report. Therefore, we now need to analyze the function GetDPCDInfo().

Again, this is a huge function that is responsible for reading DPCD from the sink device, verifying the values and saving them into the current framebuffer instance. Since it may not be straightforward to figure out what’s going on in this function by interpreting the assembly block by block, let’s take a look at my partial translated version instead.

//
// Represents the first 16 fields of the receiver capabilities
//
// Main Reference:
// - DisplayPort Specification Version 1.2
//
// Side Reference:
// - struct intel_dp @ line 1073 in intel_drv.h (Linux 4.19 Kernel)
// - DP_RECEIVER_CAP_SIZE @ line 964 in drm_dp_helper.h
struct DPCDCap16 // 16 bytes
{
    // DPCD Revision (DP Config Version)
    // Value: 0x10, 0x11, 0x12, 0x13, 0x14
    UInt8 revision;

    // Maximum Link Rate
    // Value: 0x1E (HBR3) 8.1 Gbps
    //        0x14 (HBR2) 5.4 Gbps
    //        0x0C (3_24) 3.24 Gbps
    //        0x0A (HBR)  2.7 Gbps
    //        0x06 (RBR)  1.62 Gbps
    // Reference: 0x0C is used by Apple internally.
    UInt8 maxLinkRate;

    // Maximum Number of Lanes
    // Value: 0x1 (HBR2)
    //        0x2 (HBR)
    //        0x4 (RBR)
    // Side Notes:
    // (1) Bit 7 is used to indicate whether the link is capable of enhanced framing.
    // (2) Bit 6 is used to indicate whether TPS3 is supported.
    UInt8 maxLaneCount;

    // Maximum Downspread
    UInt8 maxDownspread;

    // Other fields omitted in this struct
    // Detailed information can be found in the specification
    UInt8 others[12];
};
/**
    Read the DisplayPort configuration data and save them into the given framebuffer instance

    @param this The hidden implicit `this` pointer
    @param framebuffer A framebuffer instance
    @param displayPath A display path instance
    @return 0 on success, negative values otherwise.
    @note This is just a small proportion of the complete procedure in assembly.
          Translated and documented by FireWolf @ FireWolf Pl.
          Translated pseudocode is only used for research purpose and illustrating the issue.
 */
int AppleIntelFramebufferController::GetDPCDInfo(AppleIntelFramebufferController* this,
                                                 AppleIntelFramebuffer* framebuffer,
                                                 AppleIntelDisplayPath* displayPath)
{
    // Parameters are passed in registers
    // %r15 = %rdi (arg0) Hidden `this`          // movq        %rdi, %r15
    // %r12 = %rsi (arg1) framebuffer            // movq        %rsi, %r12
    // %r14 = %rdx (arg2) displayPath            // movq        %rdx, %r14

    // Prior knowledge:
    // displayPath->field_0x28 (as an Int32) is not 3.
    // This field (@0x28) probably represents the power state of the sink.
    switch (displayPath->field_0x28)
    {
        case 0x2:
        {
            // Function Prototype:
            //
            // /**
            //      Sets the power state of the sink
            //
            //      @param this The hidden implicit `this` pointer
            //      @param framebuffer A framebuffer instance
            //      @param state The new power state
            //      @param displayPath A display path instance
            //      @return 0 on success, negative values otherwise.
            //      @note This function writes `state` to the register at 0x600 over the AUX channel.
            //  */
            // int AppleIntelFramebufferController::SetDPPowerState(AppleIntelFramebufferController* this,
            //                                                      AppleIntelFramebuffer* framebuffer,
            //                                                      UInt8 state,
            //                                                      AppleIntelDisplayPath* displayPath)
            //

            // This function call sets the state to D0. (Reference: DisplayPort Spec, Address Space 0x600)
            if (AppleIntelFramebufferController::SetDPPowerState(this, framebuffer, 
                                                                 0x1, displayPath) != 0)
            {
                // Negative Case
                return retVal_of_SetDPPowerState;
            }

            break;
        }

        case 0x3:
        {
            // Check the sink power state recorded in the controller instance?
            if ((*(this->field_0x1c30) + 0x58) != 0)
            {
                // Same as 0x2 case
                // Set the power state to D0 and check the return value
            }

            break;
        }

        default: break;            
    }

    // Read the first 16 fields of the receiver capabilities.
    // The address mapping for DPCD starts at 0x0.
    // This local variable resides at [%rbp - 0x48].
    UInt8 dpcd_caps[16];    
    // To be straightforward, I will use a struct to represent it.
    // struct DPCDCap16 dpcd_caps;

    // These fields are accessed over AUX channel, and some of them are read only.
    // Apple uses helper functions to accomplish the field accesses.
    //
    // Prototypes of helper functions:
    //
    // Parameters are inferred from the assembly after carefully examined the caller prologue
    // 
    // /**
    //      Read the DisplayPort configuration data over the AUX channel
    //
    //      @param this The hidden implicit `this` pointer
    //      @param framebuffer A framebuffer instance
    //      @param address A valid DisplayPort address mapped for DPCD
    //      @param length The number of bytes to read starting from the given `address`
    //      @param buffer A non-null buffer to save the data read from DPCD
    //      @param displayPath A display path instance
    //      @return 0 on success, negative values otherwise.
    //  */
    // int AppleIntelFramebufferController::ReadAUX(AppleIntelFramebufferController* this,
    //                                              AppleIntelFramebuffer* framebuffer,
    //                                              UInt32 address,
    //                                              UInt16 length,
    //                                              void* buffer,
    //                                              AppleIntelDisplayPath* displayPath);
    // 
    // /**
    //      Write the DisplayPort configuration data over the AUX channel
    //
    //      @param this The hidden implicit `this` pointer
    //      @param framebuffer A framebuffer instance
    //      @param address A valid DisplayPort address mapped for DPCD
    //      @param length The number of bytes to write starting from the given `address`
    //      @param buffer A non-null buffer that contains the data to write to `address`
    //      @param displayPath A display path instance
    //      @return 0 on success, negative values otherwise.
    //  */
    // int AppleIntelFramebufferController::WriteAUX(AppleIntelFramebufferController* this,
    //                                               AppleIntelFramebuffer* framebuffer,
    //                                               UInt32 address,
    //                                               UInt16 length,
    //                                               void* buffer,
    //                                               AppleIntelDisplayPath* displayPath);

    // In my case, the driver reads the DPCD data successfully.
    if (AppleIntelFramebufferController::ReadAUX(this, framebuffer, 0x0, 16, dpcd_caps, displayPath) != 0)
    {
        // Failed to read fields over the AUX channel.
        return error;
    }

    // This condition is true in my case.
    // So the below fields are zeroed in the framebuffer instance.
    if (displayPath->field_0x28 != 3)
    {
        // Zero out all fields that start from 0x2384 and end at 0x2479
        // bzero(((UInt8*) framebuffer) + 0x2384, 0xfc);
        bzero(framebuffer->field_0x2384, 0xfc);
    }

    // As indicated in the DisplayPort 1.2 specification, 
    // the receiver may have some extended capabilities.
    // So the driver reads the extended DPCD data at 0x2200,
    // and override the original capabilities if necessary.
    UInt8 dpcd_extended_caps[16];

    if (AppleIntelFramebufferController::ReadAUX(this, framebuffer, 0x2200, 16, dpcd_extended_caps) == 0)
    {
        // Check whether overriding is required.
        // Override the capabilities if necessary.

        // In my case, extended capabilities are all zeroes,
        // and overriding is not required.
    }

    // Check the capabilities and save them in the framebuffer instance.
    framebuffer->field_0x2384 = dpcd_caps->revision;        // 0x14 stands for 1.4
    framebuffer->field_0x2385 = dpcd_caps->revision & 0x0F; // 1.[4]
    framebuffer->field_0x2386 = dpcd_caps->revision >> 0x4; // [1].4

    // Before checking the link rate, the driver reads the OUI for the connected display.
    // IEEE OUI is 3 bytes long, followed by 6 bytes representing the identity string of the sink device.
    // For some reasons, Apple only reads the first 8 bytes and then prints them in hexadeciaml value.
    UInt8 oui[8]; 
    AppleIntelFramebufferController::ReadAUX(this, framebuffer, 0x400, 0x8, oui);
    kprintf("[IGFB][INFO   ] FB%d: OUI for display\n", (Int32) framebuffer->field_0x1dc);

    int index = 0;                                  // xorl        %ebx, %ebx
    while (index < 8)                               // cmpq        $0x8, %rbx
    {
        kprintf("[IGFB][INFO   ]  %X %X %X %X \n",  // %rdi holds the string reference
                oui[index + 0],                     // movzbl    -0x48(%rbp,%rbx), %esi
                oui[index + 1],                     // movzbl    -0x47(%rbp,%rbx), %edx
                oui[index + 2],                     // movzbl    -0x46(%rbp,%rbx), %ecx
                oui[index + 3])                     // movzbl    -0x45(%rbp,%rbx), %r8d

        index += 4;                                 // addq        $0x4, %rbx
    }

    kprintf("[IGFB][INFO   ] FB%d: Display port config ver is %d.%d\n",
            (Int32) framebuffer->field_0x1dc,
            framebuffer->field_0x2386 & 0xFF,
            framebuffer->field_0x2385 & 0xFF);

    // %rcx holds the final value of the maximum link rate
    int maxLinkRate = 0;

    // %rax holds the initial value that reads from DPCD
    // %rax = dpcd_caps->maxLinkRate
    switch (dpcd_caps->maxLinkRate)
    {
        case 0x1E:
        {
            // TODO: What is this check?
            // displayPath->field_0x455 is a byte (bit fields)
            if ((displayPath->field_0x455 & 0x8) == 0)
            {
                // Downgrad from 8.1 Gbps (HBR3) to 5.4 Gbps (HBR2)
                maxLinkRate = 0x14;

                // Same check as the case 0x14
                if (*((Int8*) (*(this->field_0x1c30) + 0x58)) < 0)
                {
                    // Downgrade from 5.4 Gbps (HBR2) to 2.7 Gbps (HBR) 
                    maxLinkRate = 0xA;
                }
            }
            else
            {
                maxLinkRate = 0x1E;
            }
        }

        case 0x14:
        {
            maxLinkRate = 0x14;

            // Check the sink power state recorded in the controller instance?
            // I didn't dig into this field; is a negative value possible?
            // In my case, this condition is false, so the link rate remains the same.
            if ((*(this->field_0x1c30) + 0x58) < 0)
            {
                // Downgrade from 5.4 Gbps (HBR2) to 2.7 Gbps (HBR) 
                maxLinkRate = 0xA;
            }
        }

        case 0xA:
        {
            maxLinkRate = 0xA;
        }

        case 0x6:
        {
            maxLinkRate = 0x6;
        }

        default:
        {
            // Use 1.62 Gbps as the fallback value
            maxLinkRate = 0x6;

            // Negative case: 
            // Return from here, leaving fields in the framebuffer instance zeroed.
            return error;
        }
    }

    // Field 0x2387 records the final max link rate value instead of the original one.
    framebuffer->field_0x2387 = maxLinkRate;

    if ((displayPath->field_0x455 & 0x8) != 0)
    {
        // As mentioned above, this condition is false.
        if (displayPath->field_0x28 == 0x3)
        {
            // 0xC is a special value used by Apple internally.
            framebuffer->field_0x2387 = 0xC;
        }
    }

    // Further investigation has shown that the max link rate is not downgraded in my case.
    // (Apple writes 0x14 to the register at 0x100.)

    _kprintf("[IGFB][INFO   ] FB%d Maximum link rate is %#x\n", 
             framebuffer->field_0x1dc, 
             dpcd_caps->maxLinkRate);

    // %rax now holds the value of maximum lane count
    // %rax = dpcd_caps->maxLaneCount
    // Field 0x2389 records the maximum number of lanes
    framebuffer->field_0x2389 = dpcd_caps->maxLaneCount;

    switch (dpcd_caps->maxLaneCount)
    {
        case 0x1:
        case 0x2:
        case 0x4:
            break;

        default:
            // Unsupported/Invalid number of lanes
            return error;
    }

    // The code below just keeps decoding the DPCD data and save
    // them to the corresponding fields in the framebuffer object.

    // ... more code ...
}

Initially, this function checks and adjusts the power state of the sink device accordingly. This part runs without any issues and is not of our interest. However, it is worth knowing that the value of the field at 0x28 in an AppleIntelDisplayPath instance is not 3. This can be checked by placing a “breakpoint” at an appropriate place and dump the register values. Let’s treat this as our prior knowledge, as we will use it later.

Now let’s focus on the local variable dpcd_caps which is a byte array of length 16. Recall from the introduction section that DPCD fields are accessed over the AUX channel, and Apple uses two helper functions, namely ReadAUX() and WriteAUX(), to accomplish the task. According to the DisplayPort specification, the address mapping for DPCD fields starts at 0x0. We are only interested in the first 16 capabilities fields, so we perform a DPCD read from the address 0x0 to 0xF over the AUX channel and tell the helper function to save the values in the given dpcd_caps buffer. ReadAUX() surely returns with no error, otherwise we won’t be able to see the Display port config version line in the kernel log.

Next, all fields in the framebuffer instance that start from 0x2384 and end at 0x2479 are zeroed. In other words, fields at offsets such as 0x2387 and 0x2389 are left zeroed if GetDPCDInfo() returns early than expected. Now we have got the answer, but we still need to figure out which part fails and leads to an error return value. Since we have not seen any output after the line of reporting config version, we can conclude that the verification of the maximum link rate must have failed. In other words, the maximum link rate value reported by DPCD is not one of the supported values, namely 0x06 (RBR), 0x0A (HBR), 0x14 (HBR2) and 0x1E (HBR3). Besides, the final link rate value is saved to the field at 0x2387, and the maximum number of lanes is saved to the field at 0x2389. Further investigation and debugging have shown that the reported value is 0x00 in my case.

Now the mission is completed, and we have found that several fields in a framebuffer instance are zeroed and not recorded at all due to an invalid value of maximum link rate reported by DPCD.

>> A simple but not optimal solution

We have successfully identified the real issue that causes the kernel panic, so it’s time to find a new solution and we have to go back to the assembly.

//
// Assembly code of AppleIntelCFLGraphicsFramebuffer on macOS 10.12.2 beta 1
//
// `dpcd_caps` is a local variable and therefore resides on the stack.
// It is a byte array of length 16 and starts at address [%rbp - 0x40].
// `dpcd_caps->maxLinkRate` is the 2nd element in the array, so its address is [%rbp - 0x3F].
//
0000000000008dd9         movb       -0x3F(%rbp), %al
0000000000008ddc         cmpb       $0x13, %al
......
0000000000009791         movb       -0x3F(%rbp), %al
0000000000009794         movb       %al, 0x2472(%r12)

A quick and simple fix is to provide a valid link rate value before the verification starts. However, we cannot simply set the value to the register %rax, because later the local variable is referenced again and %rax is used for other purposes in between.

//
// Assembly code of AppleIntelCFLGraphicsFramebuffer on macOS 10.12.2 beta 1
//
// The graphics driver uses 17 lines (0x8d62 - 0x8daf) to just print out the OUI information.
// We can replace some of these lines with our code to set a custom link rate to the `dpcd_caps` buffer.
//
008d62         movl       0x1dc(%r12), %esi           // arg1: %rsi = framebuffer->field_0x1dc (fb port)
008d6a         leaq       aIgfbinfoFbdOui, %rdi       // arg0: %rdi = 
                                                      // "[IGFB][INFO   ] FB%d: OUI for display\\n"
008d71         xorl       %eax, %eax                     
008d73         call       _kprintf                    // Print to the kernel logs

008d78         leaq       aIgfbinfoXXXXN, %r14        // %r14 = "[IGFB][INFO   ]  %X %X %X %X \\n"
008d7f         xorl       %ebx, %ebx
008d81         incq       qword_aa928+6048            // Used for debugging only, ignore this
008d88         movzxb     -0x48(%rbp,%rbx), %esi      // arg1: *(%rbp + %rbx - 0x48) (oui[index * 4 + 0])
008d8d         movzxb     -0x47(%rbp,%rbx), %edx      // arg2: *(%rbp + %rbx - 0x47) (oui[index * 4 + 1])
008d92         movzxb     -0x46(%rbp,%rbx), %ecx      // arg3: *(%rbp + %rbx - 0x46) (oui[index * 4 + 2])
008d97         movzxb     -0x45(%rbp,%rbx), %r8d      // arg4: *(%rbp + %rbx - 0x45) (oui[index * 4 + 3])
008d9d         xorl       %eax, %eax
008d9f         movq       %r14, %rdi                  // arg0: "[IGFB][INFO   ]  %X %X %X %X \\n"                          
008da2         call       _kprintf                    // Print to the kernel logs                
008da7         addq       $0x4, %rbx                  // index += 4
008dab         cmpq       $0x8, %rbx                  // if rbx < 8
008daf         jb         loc_8d81                    // then print the rest elements in the OUI bytes
                                                      // else print the version and verify the link rate

008db1         movl       0x01dc(%r12), %esi          // arg1: %rsi = framebuffer->field_0x01dc (fb port)
008db9         movzxb     0x2386(%r12), %edx          // arg2: %rdx = framebuffer->field_0x2386 ([1].4)  
008dc2         movzxb     0x2385(%r12), %ecx          // arg3: %rcx = framebuffer->field_0x2385 (1.[4])
008dcb         leaq       aIgfbinfoFbdDis, %rdi       // arg0: %rdi = 
                          "[IGFB][INFO   ] FB%d: Display port config ver is %d.%d\\n"

008dd2         xorl       %eax, %eax
008dd4         call       _kprintf                    // Print to the kernel logs                       
008dd9         movb       var_3F(%rbp), %al           // %rax = dpcd_caps->maxLinkRate (dpcd_caps[1])
008ddc         cmpb       $0x13, %al                  // -> Start to verify the link rate
008dde         jg         loc_8df5                    // -> Handle each case of link rate

Since GetDPCDInfo() just prints the OUI for the connected display and the DisplayPort version before the link rate verification, we can replace those lines with our assembly code that writes a valid value to dpcd_caps->maxLinkRate.

//
// [Simple Solution]
// 
// Assembly code of setting a custom maximum link rate value to the `dpcd_caps` buffer
// @Author: FireWolf
//
movb           $0x14, %al           // %rax = 0x14 (Link Rate = 5.4 Gbps, HBR2)
movb           $al, -0x3F(%rbp)     // dpcd_caps[1] = %rax

However, this fix also introduces a new problem that the custom maximum link rate value is used for EVERY connected display. To be more specific, the value 0x14 (HBR2, 5.4 Gbps) in the above example is used to establish a link between the integrated GPU and our built-in 4K display but might not be appropriate for an external 1080p display. In such cases, the maximum link rate reported by a low-resolution display could be a smaller value than 0x14. Therefore, using an inappropriate value might lead to a link training failure or even a hardware damage.

>> A complex but much better solution

In order to avoid such issues, we need a smarter way to solve this problem. Recall that the first framebuffer port of 0x3E9B0000 has a type of LVDS, indicating that port 0 is an internal display. Therefore, a better solution is to write the custom link rate value if and only if the graphics driver is reading DPCD of the built-in display. In other words, we only apply the fix if and only if the current framebuffer number is 0. This is doable because the field at 0x1dc in a framebuffer instance exactly represents the port number. Although this solution introduces an if-else statement (a comparison and a conditional jump in assembly), it has no negative impact on any externally connected displays.

//
// [Better Solution]
//
// Assembly code of conditionally setting a custom maximum link rate value to the `dpcd_caps` buffer
// @Author: FireWolf
//
cmpl        $0x0, 0x1dc(%r12)  // 9 bytes   if ( (*(UInt32*) framebuffer->field_0x1dc) != 0 )
jne         loc_8db1           // 2 bytes   then start to verify the current link rate value
movb        $0x14, -0x3F(%rbp) // 4 bytes   else dpcd_caps->maxLinkRate = 0x14

As a rule of thumb, we need to find a place where the probability of register allocations being changed in future releases is relatively slow and global variables or constants (e.g. string literal) that highly depend on the offset are not referenced. A good place for the patch above is where the driver prints the OUI information of a display before starting to verify the link rate. Now let’s take a look at what the assembly code looks like after the patch is applied.

//
// Assembly code after the patch is applied
//
008d9d         xorl       %eax, %eax
008d9f         movq       %r14, %rdi                  // arg0: "[IGFB][INFO   ]  %X %X %X %X \\n"                          
008da2         cmpl       $0x0, 0x1dc(%r12)           // **NEW:** Check the framebuffer port number
008dab         jne        loc_8db1                    // **NEW:** If not 0, then jump to 0x8db1
008dad         movb       $0x14, var_3F(%rbp)         // **NEW:** Else write the custom link rate

// loc_8db1: (Print the config version and verify the link rate)
008db1         movl       0x1dc(%r12), %esi           // arg1: %rsi = framebuffer->field_0x1dc  (fb port number)
008db9         movzxb     0x2386(%r12), %edx          // arg2: %rdx = framebuffer->field_0x2386 ([1].4)
008dc2         movzxb     0x2385(%r12), %ecx          // arg3: %rcx = framebuffer->field_0x2385 (1.[4])
008dcb         leaq       aIgfbinfoFbdDis, %rdi       // arg0: %rdi = 
                          "[IGFB][INFO   ] FB%d: Display port config ver is %d.%d\\n"
008dd2         xorl       %eax, %eax
008dd4         call       _kprintf                    // Print to the kernel logs                       
008dd9         movb       var_3F(%rbp), %al           // %rax = dpcd_caps->maxLinkRate (dpcd_caps[1])
008ddc         cmpb       $0x13, %al                  // -> Start to verify the link rate
008dde         jg         loc_8df5                    // -> Handle each case of link rate

For laptops with a built-in 4K display, I would recommend to use 0x14 (HBR2, 5.4 Gbps) as the maximum link rate.
For laptops with a built-in 1080p display, I would recommend to use 0x0A (HBR, 2.7 Gbps) instead.
If those values don’t work on your devices, you may want to try 0x06 (RBR, 1.62 Gbps) and 0x1E (HBR3, 8.4 Gbps) instead.
In other words, you just replace the last two bytes C1 14 with C1 06 or C1 1E accordingly.

Clover KextsToPatch [Binary]

Supported OS: macOS Mojave 10.14, 10.14.1, 10.14.2

Info: Set the maximum link rate in DPCD buffer to 0x14 (HBR2) for laptops with 4K display
Name: AppleIntelCFLGraphicsFramebuffer
Find: E8 00 00 00 00 48 83 C3 04 48 83 FB 08 72 D0
Repl: 41 83 BC 24 DC 01 00 00 00 75 04 C6 45 C1 14
Info: Set the maximum link rate in DPCD buffer to 0x0A (HBR) for laptops with 1080p or below display
Name: AppleIntelCFLGraphicsFramebuffer
Find: E8 00 00 00 00 48 83 C3 04 48 83 FB 08 72 D0
Repl: 41 83 BC 24 DC 01 00 00 00 75 04 C6 45 C1 0A

Clover KextsToPatch [Property List]

Supported OS: macOS Mojave 10.14, 10.14.1, 10.14.2

<key>KextsToPatch</key>
<array>
<dict>
<key>Comment</key>
<string>Set the maximum link rate in DPCD buffer to 0x14 (HBR2) for laptops with 4K display (by FireWolf)</string>
<key>Disabled</key>
<false/>
<key>Find</key>
<data>
6AAAAABIg8MESIP7CHLQ
</data>
<key>InfoPlistPatch</key>
<false/>
<key>Name</key>
<string>AppleIntelCFLGraphicsFramebuffer</string>
<key>Replace</key>
<data>
QYO8JNwBAAAAdQTGRcEU
</data>
</dict>
<dict>
<key>Comment</key>
<string>Set the maximum link rate in DPCD buffer to 0x0A (HBR) for laptops with 1080p or below display (by FireWolf)</string>
<key>Disabled</key>
<false/>
<key>Find</key>
<data>
6AAAAABIg8MESIP7CHLQ
</data>
<key>InfoPlistPatch</key>
<false/>
<key>Name</key>
<string>AppleIntelCFLGraphicsFramebuffer</string>
<key>Replace</key>
<data>
QYO8JNwBAAAAdQTGRcEK
</data>
</dict>
</array>

>> Verify the solution

To verify whether the patch has been applied successfully and is working as expected, we can examine the register value at DisplayPort address 0x100 in addition to no sign of a kernel panic. According to the DisplayPort specification, 0x100 is the address of the LINK_BW_SET register and therefore the graphics driver (i.e. the source) is required to write the final link rate value to it. Fortunately, Apple provides a command line tool named AGDCDiagnose to dump values at some ranges of DisplayPort addresses. It also prints other information related to the graphics devices and the drivers, but right now we only focus on the DPCD data.

You may find AGDCDiagnose in /System/Library/Extensions/AppleGraphicsControl.kext/Contents/MacOS/ and use AGDCDiagnose -a to dump all data.

//
// Sample report generated by AGDCDiagnose (Version: 3.25.6)
//
// Notes: 
// (1) Some "irrelevant" lines are omitted in the below report.
// (2) Test environment: Intel Iris Pro Graphics 580 with a 1080p display connected.
//                       macOS Mojave 10.14.1 (18B75)
//                       Skylake Intel(R) Core(TM) i7-6770HQ CPU
// 
## Register Dump Port 4 - Start ## 
- 000000: 0x11 0x0a 0x84 0x01 0x01 0x00 0x01 0x00 0x02 0x02 0x06 0x00 0x00 0x00 0x00 0x00
Reg: 000000: 11 : DPCD_REV: 1.1
Reg: 000001: 0a : MAX_LINK_RATE: HBR
Reg: 000002: 84 : MAX_LANE_COUNT: 4, TPS3_SUPPORTED: 0, ENHANCED_FRAME_CAP: 1
Reg: 000003: 01 : MAX_DOWNSPREAD: 0.5% down, NO_AUX_HANDSHAKE_LINK_TRAINING: 0
Reg: 000004: 01 : NORP: 1
Reg: 000005: 00 : DOWNSTREAMPORT_PRESENT: DWN_STRM_PORT_PRESENT: 0, 
                                          DWN_STRM_PORT_TYPE: [0] DisplayPort, 
                                          FORMAT_CONVERSION: 0, 
                                          DETAILED_CAP_INFO_AVAILABLE: 0
Reg: 000006: 01 : MAIN_LINK_CHANNEL_CODING: ANSI 8B/10B
Reg: 000007: 00 : DOWN_STREAM_PORT_COUNT: DWN_STRM_PORT_COUNT: 0, MSA_TIMING_PAR_IGNORED: 0, OUI: 0
Reg: 000008: 02 : RECEIVE_PORT0_CAP_0: LOCAL_EDID_PRESENT: 1, ASSOCIATED_TO_PRECEDING_PORT: 0
Reg: 000009: 02 : RECEIVE_PORT0_CAP_1: BUFFER_SIZE: 96
Reg: 00000a: 06 : RECEIVE_PORT1_CAP_0:
Reg: 00000b: 00 : RECEIVE_PORT1_CAP_1:
Reg: 00000c: 00 : I2C Speed: 
Reg: 00000d: 00 : eDP_CONFIGURATION_CAP: ALTERNATE_SCRAMBLER_RESET_CAPABLE: 0, FRAMING_CHANGE_CAPABLE: 0
Reg: 00000e: 00 : TRAINING_AUX_RD_INTERVAL: 100 us, EXTENDED_RECEIVER_CAPABILITY_FIELD_PRESENT: NO
Reg: 00000f: 00 : ADAPTER_CAP: FORCE_LOAD_SENSE_CAP: 0, ALTERNATE_I2C_PATTERN_CAP: 0

- 000100: 0x0a 0x84
Reg: 000100: 0a : LINK_BW_SET: HBR
Reg: 000101: 84 : LANE_COUNT_SET: LANE_COUNT_SET 4, ENHANCED_FRAME_EN: 1

Let’s first learn how to interpret the report by looking at a sample. The above report is obtained from an Intel Skull Canyon NUC equipped with Intel Iris Pro Graphics 580 and a 1080p display connected via a DisplayPort cable. This is the case where the connected display reports a valid link rate value and everything works as expected.

16 bytes are read from the DisplayPort address at 0x0, which represents the first 16 capabilities of the connected display and corresponds to the dpcd_caps buffer in GetDPCDInfo() function. On the other hand, 2 bytes are read from 0x100, indicating how the graphics driver configures the link between the GPU and the display. We only focus on aforementioned registers, and you can find detailed descriptions for the rest registers in the specification if you are interested in them.

You can see that the MAX_LINK_RATE value (at address 0x1) is 0x0A, indicating that the display cannot support a link that has a rate greater than 0x0A. As a result, the graphics driver set the final link rate, namely the LINK_BW_SET register at address 0x100 to 0x0A.

In comparison, another sample report is generated when a 4K display is connected to the same NUC. Now a higher value of MAX_LINK_RATE is reported in order to light up the 4K display.

// TODO: ADD THE SAMPLE REPORT GENERATED ON A 4K DISPLAY

Now let’s take a look at the report generated on a problematic laptop. This is the case where the internal display does not report a valid link rate value and therefore causes GetDPCDInfo() failing to return 0.

//
// Sample report generated by AGDCDiagnose (Version: 3.28.4)
//
// Notes: 
// (1) Some "irrelevant" lines are omitted in the below report.
// (2) Test environment: Intel UHD Graphics 630 with the built-in 4K display
//                       macOS Mojave 10.14.2 Beta 2 (18C38b)
//                       Coffee Lake Intel(R) Core(TM) i7-8750H CPU
// 
## Register Dump Port 1 - Start ## 
- 000000: 0x14 0x00 0xc4 0xc1 0x00 0x00 0x01 0xc0 0x02 0x00 0x00 0x00 0x00 0x0b 0x00 0x00
Reg: 000000: 14 : DPCD_REV: 1.4
Reg: 000001: 00 : MAX_LINK_RATE: ???
Reg: 000002: c4 : MAX_LANE_COUNT: 4, TPS3_SUPPORTED: 1, ENHANCED_FRAME_CAP: 1
Reg: 000003: c1 : MAX_DOWNSPREAD: 0.5% down, NO_AUX_HANDSHAKE_LINK_TRAINING: 1
Reg: 000004: 00 : NORP: 0
Reg: 000005: 00 : DOWNSTREAMPORT_PRESENT: DWN_STRM_PORT_PRESENT: 0, 
                                          DWN_STRM_PORT_TYPE: [0] DisplayPort, 
                                          FORMAT_CONVERSION: 0, 
                                          DETAILED_CAP_INFO_AVAILABLE: 0
Reg: 000006: 01 : MAIN_LINK_CHANNEL_CODING: ANSI 8B/10B
Reg: 000007: c0 : DOWN_STREAM_PORT_COUNT: DWN_STRM_PORT_COUNT: 0, MSA_TIMING_PAR_IGNORED: 1, OUI: 1
Reg: 000008: 02 : RECEIVE_PORT0_CAP_0: LOCAL_EDID_PRESENT: 1, ASSOCIATED_TO_PRECEDING_PORT: 0
Reg: 000009: 00 : RECEIVE_PORT0_CAP_1: BUFFER_SIZE: 32
Reg: 00000a: 00 : RECEIVE_PORT1_CAP_0:
Reg: 00000b: 00 : RECEIVE_PORT1_CAP_1:
Reg: 00000c: 00 : I2C Speed: 
Reg: 00000d: 0b : eDP_CONFIGURATION_CAP: ALTERNATE_SCRAMBLER_RESET_CAPABLE: 1, FRAMING_CHANGE_CAPABLE: 1
Reg: 00000e: 00 : TRAINING_AUX_RD_INTERVAL: 100 us, EXTENDED_RECEIVER_CAPABILITY_FIELD_PRESENT: NO
Reg: 00000f: 00 : ADAPTER_CAP: FORCE_LOAD_SENSE_CAP: 0, ALTERNATE_I2C_PATTERN_CAP: 0

- 000100: 0x14 0x84
Reg: 000100: 14 : LINK_BW_SET: HBR2
Reg: 000101: 84 : LANE_COUNT_SET: LANE_COUNT_SET 4, ENHANCED_FRAME_EN: 1

The MAX_LINK_RATE register reports an invalid value of 0x00, but the graphics driver has set 0x14 to the LINK_BW_SET register, indicating that the patch has been applied successfully and is working as expected.

>> Is this the end?

While it is still a mystery why the maximum link rate reported by the internal display is zero, I believe that this is the almost ultimate solution I can find so far. This patch is written in an elegant way without having other negative side effects such as no display after waking up and no signal on external monitors. Besides, it is easy to apply this fix via WhateverGreen by hooking the call of ReadAUX(). Since the framebuffer instance is also passed in ReadAUX(), we could check the port number at there and decide whether or not to modify the given DPCD buffer.

//
// Code example illustrating how to apply the fix in hooked ReadAUX()
// @Author: FireWolf
//
int AppleIntelFramebufferController::ReadAUX(AppleIntelFramebufferController* this,
                                             AppleIntelFramebuffer* framebuffer,
                                             UInt32 address,
                                             UInt16 length,
                                             void* buffer,
                                             AppleIntelDisplayPath* displayPath)
{
    // Call the original ReadAUX()
    int retVal = orgReadAUX(this, framebuffer, address, length, buffer, display);

    // Guard: Check the address
    if (address != 0)
    {
        return retVal;
    }

    UInt32 port = *reinterpret_cast(reinterpret_cast(framebuffer) + 0x1dc);

    // Guard: Check the framebuffer port number
    if (port != 0)
    {
        return retVal;
    }

    UInt8* dpcd_caps = reinterpret_cast(buffer);

    // A better way is to read the value specified by the user instead of using a constant at here
    // e.g. Users can define a property, say cfl-dpcd-max-link-rate, in Clover's config.plist.
    // Or even better, allow users to define a property to specify the framebuffer port number.
    dpcd_caps[1] = 0x14;

    return retVal;
}

Furthermore, it is worth knowing that Intel’s Linux graphics driver handles this issue by setting a fallback value (0x6, RBR, 1.62 Gbps) and returning no error. In comparison, Apple’s Intel graphics driver does the same thing but returns an error. Although it is completely reasonable for Apple’s driver to return an error on reading an invalid value, I think, with no responsibility, that it would be better to try every possible link rate value from the largest to the smallest one and pick the one that actually works during link training.

So far, the internal 4K display and the graphics accelerations work great without any issue, but this is still not the end of the story. I will keep researching on this issue and try to figure out why the maximum link rate reported by the display is zero. Is this related to eDP 1.4? Let’s see what happens!

>> Relevant Notes, Additional Resources and References

1. Section 1.7 Overview of DisplayPort in VESA DisplayPort Specification Rev 1.2.

This section talks about transport channels, layered designs and terms used in DisplayPort.

2. Section 2.9.3.1 Address Mapping for Link Configuration/Management in VESA DisplayPort Specification Rev 1.2.

The table in this section provides detailed information about address mapping and and purposes of each register in DPCD.

3. Intel Graphics Driver in Linux 4.19.1 kernel

Check the two helpers that handle link rates and bandwidth at line 151 and line 169 in file drm_dp_helper.c.

Intel’s driver has multiple references to these helpers. For example, there is a function at line 133 in intel_dp.c that sets the link rate to the intel_dp instance.

Besides, there is a helper named drm_dp_link_configure at line 467 in drm_dp_helper.c,which configures the link rate and writes the final result to the DP_LINK_BW_SET register at 0x100. The idea is the same as Apple’s WriteAUX().

>> Update Logs

1. [2018.11.11] Initial Release.

Coffee Lake Intel UHD Graphics 630 on macOS Mojave: A nearly ultimate solution to the kernel panic due to division by zero in the framebuffer driver