0

I just bought a Clevo N141WU (at system 76 it's known as the galago pro) from a Danish PC shop.

It mostly works really nicely, but when the fan is spinning down (after a hard workload) it starts making a really high pitched sound and the fan stops (it sounds like the fan isn't getting the needed voltage to spin).

I've called the shop, and their solution was some Windows software, but the PC came without Windows and I bought it to run Linux in the first place (since it was the same as the galago pro I thought it would work).

Since the laptop runs Linux from system76, I think it should be doable.

Is there anything I should install to make it run more better, or does someone know the bios trick to make the fan happy?

I'm running Solus 3.X where x is however many nines you want to spend your time inserting ;-)

Using the keyboard shortcut Fn+1 (found in a thread about the loud fans of the system76 galago pro) twice will turn on and off the fan. This will remove the sound until the next hard load is gone.

I've found two things since originally posting:

  • system76 has some firmware update, but who knows if they'd be willing to send it to someone with a laptop from another reseller (I'll ask them nicely)
  • System76 has a package in ubuntu called system76-dkms which might provide fan control, but it's not in the Solus repo. (I'll probably ask around in the Solus irc about how packaging works tonight.)
  • First step is to find out how the fan is controlled: via BIOS/ACPI, via something else? Not sure how many alternatives are there. One is via lm-sensors and fancontrold, and you can set the pulse width limits (too small pulses are probably making the high-pitched noise). – dirkt Nov 29 '18 at 9:55
  • I'm not sure, It would seem that both bios and ACPI are possible, as the windows program should be able to control the fans (I was told by the guy at the company) and System76 seems to have a firmware update that allows control from the bios (I cannot access it currently) – Thorbjørn E. K. Christensen Nov 29 '18 at 13:11
  • @ThorbjørnE.K.Christensen I have your same laptop, albeit by another reseller, and I have the same problem. What i want to say is that when the fan hangs up, the fan speed readings are 2x-3x times more than the maximum allowed fan speed (max is 6500rpm, but when it happens they're ⁓13000rpm). So i suspect it has to do with the fan receiving directives to spin faster than allowed and it breaks. I've not found a solution yet but a bios update changed when the lockup happens (before was at the peak of the stress, now it's right after it). What is the windows program you were suggested? – user2464424 Dec 26 '18 at 20:24
  • The list is in Danish but the program is the one with type: ‘software‘ and name: ‘Controlcenter 2.0‘ from the list – Thorbjørn E. K. Christensen Dec 26 '18 at 20:55
  • @ThorbjørnE.K.Christensen Ah ok it's the Clevo Control Center, i use it all the time on windows 10 and so do most Clevo users. It's a very barebones program with little fan controls. it has only "automatic" (and a simple slider that multiplies fan speed) and "maximum". It won't fix the problem unless you set it on maximum permanently (equivalent to Fn+1 key combo). I suppose a bandaid way to fix the problem is create a program that detects if fan speed surpasses the value of, say, 7000rpm and "defibrillates" the fan by setting it to max and back to auto rapidly. – user2464424 Dec 26 '18 at 22:58
1

I was successful in Windows 10 with the code below. It handles both failures that the fan can have, being: "fan stops suddenly with fan duty=0" and "fan stops suddenly with rpm > 10000 with a electric noise that can be heard coming from the fan". It requires a program that loads Winring0 such as ThrottleStop running in the background. I have not tested it with Clevo Control Center installed. It compiles with MinGW-w64 with \yourmingwpath\i686-w64-mingw32-gcc.exe \yoursourcepath\main.c -o \yourexepath\main.exe -Wall -mwindows

#define UNICODE 1
#define _UNICODE 1

#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include <stddef.h>

#define OLS_TYPE 40000
#define IOCTL_OLS_READ_IO_PORT_BYTE CTL_CODE(OLS_TYPE, 0x833, METHOD_BUFFERED, FILE_READ_ACCESS)
#define IOCTL_OLS_WRITE_IO_PORT_BYTE CTL_CODE(OLS_TYPE, 0x836, METHOD_BUFFERED, FILE_WRITE_ACCESS)

#define EC_SC 0x66
#define EC_DATA 0x62

#define IBF 1
#define OBF 0
#define EC_SC_READ_CMD 0x80

typedef struct _OLS_WRITE_IO_PORT_INPUT {
    ULONG   PortNumber; 
    union {
        ULONG   LongData;
        USHORT  ShortData;
        UCHAR   CharData;
    };
}   OLS_WRITE_IO_PORT_INPUT;

HANDLE hDevice = INVALID_HANDLE_VALUE;
char filename[1024] = {0};

WORD WInp(WORD port) {
    FILE *outlog;
    unsigned int error = 0;

    DWORD   returnedLength = 0;
    WORD    value = 0;
    BOOL    bResult = FALSE;
    bResult = DeviceIoControl(hDevice,
                            IOCTL_OLS_READ_IO_PORT_BYTE,
                            &port, sizeof(port),
                            &value, sizeof(value),
                            &returnedLength,
                            NULL );
    if (bResult) {
        /*outlog = fopen(filename, "ab");
        fprintf(outlog, "port=%d, value=%d, retlength=%d\n", port, value, (int)returnedLength);
        fclose(outlog);*/
        return value;
    } else {
        error = GetLastError();
        outlog = fopen(filename, "ab");
        fprintf(outlog, "DeviceIoControl (read) failed. Error %d.\n", error);
        fclose(outlog);
        CloseHandle(hDevice);
        return 0;
    }
}

WORD WOutp(WORD port, BYTE value) {
    FILE *outlog;
    unsigned int error = 0;

    DWORD   returnedLength = 0;
    BOOL    bResult = FALSE;
    DWORD   length = 0;
    OLS_WRITE_IO_PORT_INPUT inBuf;
    inBuf.CharData = value;
    inBuf.PortNumber = port;
    length = offsetof(OLS_WRITE_IO_PORT_INPUT, CharData) + sizeof(inBuf.CharData);
    bResult = DeviceIoControl(hDevice,
                            IOCTL_OLS_WRITE_IO_PORT_BYTE,
                            &inBuf, length,
                            NULL, 0,
                            &returnedLength,
                            NULL);
    if (bResult) {
        /*outlog = fopen(filename, "ab");
        fprintf(outlog, "port=%d, value=%d, retlength=%d\n", port, value, (int)returnedLength);
        fclose(outlog);*/
        return value;
    } else {
        error = GetLastError();
        outlog = fopen(filename, "ab");
        fprintf(outlog, "DeviceIoControl (write) failed. Error %d.\n", error);
        fclose(outlog);
        CloseHandle(hDevice);
        return 0;
    }
}

int wait_ec(const unsigned int port, const unsigned int flag, const char value) {
    int i = 0;
    unsigned char data = WInp(port);

    while (((data >> flag)&0x1)!=value) {
        Sleep(1);
        if (i>10) {
            //printf( "Still waiting on port 0x%x, data=0x%x, flag=0x%x, value=0x%x, i=%d\n", port, data, flag, value, i);
            return 0;
        }
        i++;
        data = WInp(port);
    }
    //printf( "Succeeded port 0x%x, data=0x%x, flag=0x%x, value=0x%x, i=%d\n", port, data, flag, value, i);
    return 0;
}

unsigned char read_ec(const unsigned int port) {
    wait_ec(EC_SC, IBF, 0);
    WOutp(EC_SC, EC_SC_READ_CMD);
    wait_ec(EC_SC, IBF, 0);
    WOutp(EC_DATA, port);
    wait_ec(EC_SC, OBF, 1);
    return WInp(EC_DATA);
}

void do_ec(const unsigned int cmd, const unsigned int port, const unsigned char value) {
    wait_ec(EC_SC, IBF, 0);
    WOutp(EC_SC, cmd);
    wait_ec(EC_SC, IBF, 0);
    WOutp(EC_DATA, port);
    wait_ec(EC_SC, IBF, 0);
    WOutp(EC_DATA, value);
    wait_ec(EC_SC, IBF, 0);
    return;
}

void write_fan_duty(int duty_percentage) {
    do_ec(0x99, 0x01, (int)(((double) duty_percentage) / 100.0 * 255.0));
    //FILE *outlog = fopen(filename, "ab");
    //fprintf(outlog, "Fan set to %d\n", duty_percentage);
    //fclose(outlog);
    return;
}

int main(){
    // get the path of this executable and append "stdout.txt\0" to it for the log file.
    int i = GetModuleFileNameA(NULL, filename, 1024);
    for (;i>0 && filename[i] != '\\';i--) {}
    char *dest=&filename[i+1], *src="stdout.txt\0";
    for (i=0;i<11;i++) dest[i]=src[i];

    FILE *outlog;
    outlog = fopen(filename, "wb"); // clear the log at every start
    fclose(outlog);
    unsigned int error = 0;

    // I could loop CreateFile until a valid handle is returned (which means that WinRing0_1_2_0 got started by throttlestop)
    // but windows defender blocks the program at start for a few seconds with 100% core usage if i do that.

    Sleep(3000); // ... so this is what i have to do instead. Disgusting.

    hDevice = CreateFile(L"\\\\.\\WinRing0_1_2_0",
                        GENERIC_READ | GENERIC_WRITE,
                        0,
                        NULL,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);

    if (hDevice == INVALID_HANDLE_VALUE) {
        error = GetLastError();
        if (error == ERROR_ACCESS_DENIED) {
            outlog = fopen(filename, "ab");
            fprintf(outlog, "CreateFile failed. Please retry as administrator.\n");
            fclose(outlog);
        } else if (error == ERROR_FILE_NOT_FOUND) {
            outlog = fopen(filename, "ab");
            fprintf(outlog, "CreateFile failed. The WinRing0 driver is probably not loaded yet.\n");
            fclose(outlog);
        } else {
            outlog = fopen(filename, "ab");
            fprintf(outlog, "CreateFile failed. Error %d.\n", error);
            fclose(outlog);
        }
        return 0;
    }

    int val_duty, raw_rpm, val_rpm, temp, last_valid_duty=50;
    while (1) {
        val_duty = (int) ((double) (read_ec(0xCE)) / 255.0 * 100.0);
        raw_rpm = (read_ec(0xD0) << 8) + (read_ec(0xD1));
        if (raw_rpm == 0)
            val_rpm = 0;
        else
            val_rpm = 2156220 / raw_rpm;
        temp = read_ec(0x07);

        //outlog = fopen(filename, "ab");
        //fprintf(outlog, "FAN Duty: %d%%, FAN RPMs: %d RPM, CPU Temp: %d°C\n", val_duty, val_rpm, temp);
        //fclose(outlog);

        if (val_rpm > 10000 || val_duty == 0) {
            // there are two malfunctions that can happen:
            // - fan stops suddenly with fan duty=0
            // - fan stops suddenly with rpm > 10000 with a electric noise that can be heard coming from the fan.
            outlog = fopen(filename, "ab");
            fprintf(outlog, "MALFUNCTION DETECTED: val_rpm=%d, val_duty=%d\n", val_rpm, val_duty);
            fclose(outlog);
            // Panic :O
            if (last_valid_duty<80) {
                write_fan_duty(last_valid_duty+20);
            } else {
                write_fan_duty(last_valid_duty-20);
            }
        } else {
            // This is the custom fan curve code. Can be adjusted to your liking.
            // It's required because i don't know to to set the fan back to "automatic" without manual intervention.
            // Can definitely conflict with other fan speed programs, so be careful.
            // Writes to fan speed are limited to only if the target fan duty changes.
            if (temp<55) {
                if (last_valid_duty > 32 || last_valid_duty < 29) write_fan_duty(31);
            } else if (temp<60) {
                if (last_valid_duty > 42 || last_valid_duty < 39) write_fan_duty(41);
            } else if (temp<65) {
                if (last_valid_duty > 52 || last_valid_duty < 49) write_fan_duty(51);
            } else if (temp<70) {
                if (last_valid_duty > 62 || last_valid_duty < 59) write_fan_duty(61);
            } else if (temp<75) {
                if (last_valid_duty > 72 || last_valid_duty < 69) write_fan_duty(71);
            } else if (temp<80) {
                if (last_valid_duty > 82 || last_valid_duty < 79) write_fan_duty(81);
            } else if (temp<85) {
                if (last_valid_duty > 92 || last_valid_duty < 89) write_fan_duty(91);
            } else {
                if (last_valid_duty < 98) write_fan_duty(100);
            }
            last_valid_duty = val_duty;
        }
        Sleep(200);
    }
    return 0;
}

I have not ported the code for usage in linux-based oses tho. Doing so would require:

  • replacing WInp(port) and WOutp(port, value) functions with inb(port) and outb(value, port),
  • adding ioperm at the start like in this code snippet,
  • replacing Sleep(milliseconds) with usleep(microseconds),
  • cleaning up all the now useless includes, defines, structs and handles,
  • replacing GetModuleFileNameA with an equivalent function.
  • Hi, sorry for being slow, the Hollidays happened, and thus I'll be back in a few days ;-) – Thorbjørn E. K. Christensen Jan 12 at 21:14
  • If I replace every thing you say, then compile this. Should I then add the program to my startup programs or just run it sometimes? – Thorbjørn E. K. Christensen Jan 30 at 13:50
  • @ThorbjørnE.K.Christensen Once you are done testing it and you are satisfied, the program should be run at startup and stay running in the background. You can delay it by increasing the very first sleep() you see in the main function. If while you test something goes wrong and the program breaks the fan, simply shutting off and turning back on the system will reset the fan curve to the internal default without further harm (a restart is not enough). – user2464424 Jan 30 at 21:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.