Comment 102 for bug 971061

juanmanuel (rockerito99) wrote :

FINALLY!! I found the exact problem _and_ a SOLUTION!!! (NOTE: see attached program)

The problem is that Samsung's embedded controller stops sending GPE events if it still is waiting for previous GPE events to be queried from it.

It will remain in that state until one hits the reset hole in the back of the latop, or flashes a bios, or queries the events.

---> But how will the OS query the events if the GPE interrupt isn't produced anymore? Classic chicken and egg. <----

So I made a small program that does just that -- it queries events from the EC until there are no more to query. AND IT WORKED!! This restores the normal behavior, and AC, Battery and LID events start to be produced again. One would ideally run it after resume from sleep. I based it after observing normal behavior in <linuxkernel>/drivers/acpi/ec.c, and afterwards behaviour with laptop in "problem state".

Following is the program (I will attach it too). You can either:

     1) Run it by hand after resume from sleep.

     2) Or Automatically run it from /usr/lib/pm-utils/sleep.d/99-your-script-here (you can use 95led as basis).

     3) Or Insert this code in the linux kernel at the beginning of the acpi_ec_unblock_transactions_early() function in "<linuxkernel>/drivers/acpi/ec.c" and recompile the kernel. The cmd and data ports are known in ec.c so they don't need to be hardcoded there.

PS: Other things that I already tried that didn't fix it:
    1) Please note that I already tried commenting "acpi_disable_all_gpes()" from "<linuxkernel>/drivers/acpi/sleep.c" and this didn't have an effect (I guess unreplied events were already accumulated in the EC prior to resume).
    2) I also tried reverting to calling acpi_enable() on sleep.c instead of setting the ACPI_BITREG_SCI_ENABLE. No effect, issue still resurfaced after some suspends.
    3) I also tried commenting out the call to acpi_ec_unblock_transactions_early() in sleep.c, but no effect.

PS2: Another way to force the embedded controller to be in a problem state that I discovered, is to:
      1) echo disable > /sys/firmware/acpi/interrupts/gpe17
      2) plug, unplug, plug, unplug, plug, unplug, plug, unplug (8 actions)
      3) echo enable > /sys/firmware/acpi/interrupts/gpe17

Following are the two files with fixes which I'll attach too:

NOTE: you must check that embedded controller ports are 0x62 and 0x66 looking in your DSDT. RUN AT YOUR OWN RISK!!!

---- samsung_fix_ec_events.c ------------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <sys/io.h>

/* Compile with:
 * gcc -o samsung_fix_ec_events samsung_fix_ec_events.c
 * Run as:
 * sudo ./samsung_fix_ec_events
 * You may copy it to /usr/local/bin and then call it
 * automatically after resume from sleep with a /usr/lib/pm-utils/sleep.d script:
 * sudo cp ./samsung_fix_ec_events /usr/local/bin/
 */

/* Constants: */
enum ec_command {
    /* Standard command for querying LID, Battery and AC events: */
    ACPI_EC_COMMAND_QUERY = 0x84,

    /* ATTENTION: PLEASE edit the following two according to your DSDT.
     * For "Samsung Series 5 NP530U3C", they are 0x62 and 0x66 respectively.
     * They seem to be the same for many other samsung models, but if you don't check
     * them first in the H_EC._CRS section of your DSDT, you run it at YOUR OWN RISK: */
    EC_DATA_PORT = 0x62,
    EC_COMMAND_PORT = 0x66
};

int main (int argc, char** argv) {
    char status = 0;
    char data = 0;
    int count = 0;

    if (iopl(3)) {
        printf("Error: Permission to read/write to EmbeddedController port not granted.\n");
        return 1;
    }

    /* Query AC, Battery, LID, etc. events until there are no more.
     * This clears them for the EC so that it can send them again in the future, thus unblocking the EC. */
    do {
        outb(ACPI_EC_COMMAND_QUERY, EC_COMMAND_PORT);
        status = inb(EC_COMMAND_PORT);
        data = inb(EC_DATA_PORT);
        /* printf("CommandQuery 0x84, status=0x%x, data=0x%x\n", status, data); */
    } while (data != 0 && ++count < 10000); /* Note: No less than a thousand max count */

    printf("EmbeddedController GPE events flushed. New events can be produced now.\n");

    return 0;
}
----END samsung_fix_ec_events.c ------------------------------------------

----- 99samsung_fix_ec_events ----------------------------------------------
#!/bin/sh
# NOTE: Put this file in: /usr/lib/pm-utils/sleep.d/99samsung_fix_ec_events
#
# On some samsung laptops (series 5 2012, series 9 2011, etc) , if many EC
# GPE events are produced during sleep (AC plugged/unplugged, battery % change
# change, LID open/close, etc), and they are not queried, the
# EmbeddedController stops sending them and this creates a chicken and egg
# situation, that can only be resolved either by hitting the reset button in
# the back while powered off, or by simply forcing a query for the events here after resume.

case "$1" in
 hibernate|suspend)
  ;;
 thaw|resume)
  #NOTE: edit this path if necessary to point to the program with the fix:
  /usr/local/bin/samsung_fix_ec_events
  ;;
 *) exit $NA
  ;;
esac

exit 0

----- END 99samsung_fix_ec_events -------------------------------------------