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

It has been two years since I identified the maximum link rate issue on my laptop.
Now it's time for me to post the ultimate solution and conclude this series.
If this page looks strange due to font or layout issues, you could find a pretty-printed version at here.

>> Recap of the story

Previously, we have found that the maximum link rate reported by the builtin display is zero and hence the field used as a divider is left zeroed. We have provided a solution to the kernel panic by injecting a valid link rate value via WhateverGreen. However, users are still required to have prior knowledge of a link rate that works for their builtin display. As a result, it is not a perfect solution, and ideally the graphics driver should be able to find a valid value behind the scene.

>> The ultimate solution

The builtin display conforms to the eDP 1.4 standard, and under this circumstance a link rate of zero has a "special" meaning. eDP 1.4 panels allow the graphics driver to specify a link rate value other than traditional ones, such as RBR, HBR, HBR2 and HBR3. A table can be found in the DPCD register at 0x010. It contains up to 8 link rate values supported by the panel and is terminated by a zero value. The values are encoded as a multiple of 200 Kbps and sorted in ascending order, so the maximum link rate is the last non-zero element.

For example, the following table is extracted from a Ice Lake laptop equipped with a 1080p display. The maximum link rate value is 0xA (HBR).

Raw Register Values 8100 10800 12150 13500 0 0 0 0
Link Rates in Gbps 1.62 2.16 2.43 2.7 X X X X
Decimal Link Rates 0x6 0x8 0x9 0xA X X X X

In comparison, the table extracted from my laptop with a 4K display reports that the maximum link rate value is 0x14 (HBR2).

Raw Register Values 8100 10800 12150 13500 16200 21600 27000 0
Link Rates in Gbps 1.62 2.16 2.43 2.7 3.24 4.32 5.4 X
Decimal Link Rates 0x6 0x8 0x9 0xA 0xC 0x10 0x14 X

Consequently, we can probe the maximum link rate from the table, and we no longer require users to specify a value for their laptop explicitly. As such, we can implement a helper function to extend our current solution.

// Existing wrapper function to inject a valid maximum link rate value (pseudocode) IOReturn AppleIntelFramebufferController::wrapReadAUX(AppleIntelFramebufferController* this, AppleIntelFramebuffer* framebuffer, UInt32 address, void* buffer, UInt32 length, AppleIntelDisplayPath* displayPath) { // Ensure that the driver is reading from DPCD 0x000 for the builtin display // (Code Omitted) DPCDCap16* caps = (DPCDCap16*) buffer; // OLD: Inject a valid link rate value specified by the user caps->maxLinkRate = userMaxLinkRate; // Now: Inject the link rate found in the table caps->maxLinkRate = AppleIntelFramebufferController::probeMaxLinkRate(this, framebuffer, displayPath); // ... } // Helper function to probe the maximum link rate value from the table (pseudocode) UInt8 AppleIntelFramebufferController::probeMaxLinkRate(AppleIntelFramebufferController* this, AppleIntelFramebuffer* framebuffer, AppleIntelDisplayPath* displayPath) { // Guard: Read from the DPCD register at 0x700 to ensure that the panel conforms to eDP 1.4 UInt8 version; AppleIntelFramebufferController::ReadAUX(this, framebuffer, 0x700, &version, 1, displayPath); if (version < EDP_VERSION_1_4) { // The builtin display is not eDP 1.4. return 0; } // Guard: Read all supported link rates // Each rate is an unsigned 16-bit integer; // There are at most 8 rate values UInt16 rates[8] = {0}; AppleIntelFramebufferController::ReadAUX(this, framebuffer, 0x010, rates, 16, displayPath); // Find the last non-zero element UInt16 maxLinkRate = LastNonZeroElement(rates); // Convert it to the decimal representation // The link rate is encoded as a multiple of 200 Kbps // The decimal value is encoded as a multiple of 0.27 Gbps maxLinkRate *= 200; maxLinkRate /= 270000; // Guard: Ensure that the decimal value is supported by the driver return IsSupportedByDriver(maxLinkRate) ? maxLinkRate : 0; }

We first read from the DPCD register at 0x700 to ensure that the builtin display conforms to the eDP 1.4 standard. Subsequently, since each rate is stored in an unsigned 16-bit integer, we read 16 bytes from the DPCD register at 0x010 to extract the table. We then traverse the table to find the maximum link rate and convert it to the decimal representation. Finally, we verify the value to ensure that it is supported by the graphics driver, otherwise we return 0 to indicate an error. Note that we have converted the previous solution to a fallback mechanism, so that users still have a chance to specify a valid link rate manually if the one found in the table is not supported.

>> Conclusion

To summarize, we have found that an "invalid" maximum link rate value may be reported by the builtin display and subsequently a kernel panic due to a division-by-zero is triggered, but it is still possible to find one from the table specific to eDP 1.4. We have upgraded our existing solution, so that users should no longer be required to specify a value manually. The new solution will be shipped in WhateverGreen v1.4.4, and this is the true end of the story of the maximum link rate issue.

>> Relevant Notes, Additional Resources and References

  1. Intel Graphics Driver for Linux (Linux Kernel 5.8.3)

  2. The graphics driver chooses an appropriate link rate for a display and writes the decimal value to the DPCD register 0x100. If 0x0 is written to 0x100, the display would use the link rate in the table. In this case, the DPCD register 0x115 specifies the index of the rate to be used by the display. For example, if the driver writes 0x05 to 0x115, the selected link rate is 2.16 Gbps and is still capable of lighting up the builtin 4K display.

>> Update

Revision 0: Initial Release

>> License

This article is licensed under Attribution-NonCommercial 4.0 International License.

Copyright (C) 2020 FireWolf @ FireWolf Pl. All Rights Reserved.