Skip to content

Instantly share code, notes, and snippets.

@dododevs
Created February 15, 2021 18:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dododevs/e979ead47b2e54e4df51a9f0a94b2e06 to your computer and use it in GitHub Desktop.
Save dododevs/e979ead47b2e54e4df51a9f0a94b2e06 to your computer and use it in GitHub Desktop.
Macbook Air 13" 6,2 (mid 2013) SD Card Reader on Linux

Macbook Air 13" 6,2 (mid-2013) SD Card Reader on Linux

The problem

Macbook Air 6,2's hardware comes with a decently annoying issue: the processor's southbridge (codenamed Lynx Point) has a problem with the internal SD card reader (with USB 3.0 devices in general) which makes it unusable after returning from S3 state (suspend-to-ram). The device spontaneously detaches and doesn't appear to come back on when the system resumes. More on that on Wikipedia.

On Mac OS X and macOS the issue doesn't seem to be present - the driver probably takes care of it - but Linux distributions don't currently have a viable solution.

However, there is a workaround.

The workaround

@samuelsadok came up with a solution here. Looking at the schematics for his machine (a similarly-aged Macbook Pro) he found a GPIO pin on the processor's PCH bound to the enabling signal on the SD card reader controller IC. Controlling this pin allowed him to basically shut the reader down and power it up again after suspend. This seems to do the trick, but there's a catch.

The catch

On the Macbook Air 6,2 however, the GPIO controller is not exposed and thus not advertised to the system as an actual present device attached to the computer. It's there and there is a driver for it, but the system doesn't see it and therefore doesn't initialize the driver to interface with it.

Talking directly with @andy-shev, the developer of the newer driver available on more recent versions of the Linux kernel, I received crucial guidance on how to operate it and its older counterpart on this machine.

The preliminary check

Before going on, you might want to ensure that your machine is eligible for the proposed workaround. You can do so using the inteltool tool from coreboot. It's better to manually fetch and compile it from the source. Then you can run it.

$ sudo inteltool

The output should look like this:

CPU: ID 0x40651, Processor Type 0x0, Family 0x6, Model 0x45, Stepping 0x1
Northbridge: 8086:0a04 (4th generation (Haswell family) Core Processor ULT)
Southbridge: 8086:9c43 (Lynx Point Low Power Premium SKU)
IGD: 8086:0a26 (unknown)

If it doesn't, this fix probably won't work for you. You can try to skip to the testing section to check whether a driver is available and already loaded but the GPIO you'll want to control may differ in that case. Check the schematics for your machine to be sure.

Even though none of the action we're about to perform should be irreversibly dangerous, I retain no responsability. If you want to follow this guide, you will do it at your own risk.

The working workaround

Putting all together, what's needed is a way to inform the kernel about the GPIO controller in order to be able to wake the SD card reader up after suspend.

Writing the SSDT overlay and compiling it

This can be achieved through an SSDT overlay i.e. adding an entry to the ACPI tables loaded at boot writing a custom tailored ACPI excerpt. This one is courtesy of @andy-shev and it works great.

DefinitionBlock ("lpgpio.aml", "SSDT", 5, "", "LPGPIO", 1)
{
    External (\_SB, DeviceObj)

    Device (GPI0)
    {
        Method (_HID, 0, NotSerialized)
        {
            Return ("INT33C7")
        }

        Name (RBUF, ResourceTemplate ()
        {
            DWordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
                0x00000000,         // Granularity
                0x00000800,         // Range Minimum
                0x00000BFF,         // Range Maximum
                0x00000000,         // Translation Offset
                0x00000400,         // Length
                ,, )
        })
        Method (_CRS, 0, Serialized)
        {
            Return (RBUF)
        }

        Method (_STA, 0, NotSerialized)
        {
            Return (0x0F)
        }
    }
}

Saving this as lpgpio.asl and then running

$ iasl lpgpio.asl

produces a file lpgpio.aml that can now be used.

Note that you might need to install the ACPI tools in order to be able to use iasl. You can do that on Debian/Ubuntu based distros in the following way:

$ sudo apt install acpica-tools

On other distributions, look for the acpica-tools package or the iasl program.

The generated file can now be loaded in two different ways, as described here. The method that uses EFI variables is the only discussed here.

Storing the ACPI SSDT in an EFI variable

To begin, check whether /sys/firmware/efi/efivars is a valid path on your system. It should point to a directory, possibly containing a number of files. If that's not the case, try mounting the efivarsfs by running the following command:

$ sudo mount -t efivarfs none

Once everything is set, create the EFI variable file as follows (assuming the lpgpio.aml file from before is in the current working directory).

$ echo -ne "\007\000\000\000" | cat - lpgpio.aml > lpgpio.efivar

Now generate a random UUID and assign it to a variable guid.

guid="$(cat /proc/sys/kernel/random/uuid)"

Then assign whatever name to the EFI variable, assign it to name and keep it in mind.

name=lpgpio

Finally, write the file to the efivarsfs.

$ sudo dd if=lpgpio.efivar of=/sys/firmware/efi/efivars/$name-$guid bs=$(stat -c %s lpgpio.efivar)

The EFI variable file will be called (necessarily) <name>-<guid> and bs=$(stat -c lpgpio.efivar) ensures the write is done in a single operation, all at once.

Loading the ACPI SSDT from the EFI variable

The last step needed is loading the SSDT at boot. To do so, it is sufficient to modify the kernel boot options and add the following one

efivar_ssdt=<EFI variable name from before>

When using GRUB, this can be achieved by editing /etc/default/grub and adding this option to the GRUB_CMDLINE_LINUX_DEFAULT variable. It will look something like this:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash efivar_ssdt=<EFI variable name from before>"

Then update GRUB by issuing

$ sudo update-grub

Done. In order to apply the changes a reboot is needed.

Testing the GPIO controller

Upon reboot the GPIO controller should be recognized and initialized with the appropriate driver. To make sure it is, you can install the gpiod package and run

$ sudo gpiodetect

No output, no luck. You may try loading the SSDT through initrd as described in the guide linked above. If an output is present instead, it should look something like this:

gpiochip0 [INT33C7:00] (94 lines)

To further test the GPIO controller, try and get a value from a pin.

$ sudo gpioget 0 44

This gets the current value for pin 44 on the chip 0 (the only one that should be present).

While you're at it, you can test that the workaround we're about to implement actually works. The idea is to set the value for GPIO 44 to 0 and then back to 1. Test this after a suspend cycle and you should see your SD card reader magically appearing again.

$ sudo gpioset 0 44=0 && sleep 1 && sudo gpioset 0 44=1

If everything works, you're all set.

Finishing it up

In order to automate the proposed fix, create a script to be executed before the system enters S3 state and after it resumes. Create a file in the /lib/systemd/system-sleep/ folder and name it however you want. Then write the following snippet into it.

#!/bin/bash

if [ "$2" == "suspend" ]; then
case "$1" in
    pre)
        gpioset 0 44=0
        ;;
    post)
        gpioset 0 44=1
        ;;
    *)
        ;;
esac
fi

Make it executable using

$ sudo chmod +x /lib/systemd/system-sleep/<script name>

and reboot.

That's it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment