Laptop

Clevo N141WU 風扇冷卻時的噪音

  • January 16, 2019

我剛從丹麥 PC 商店買了一台Clevo N141WU(在系統 76 上它被稱為 galago pro)。

它主要工作得非常好,但是當風扇旋轉時(在繁重的工作量之後)它開始發出非常高的聲音並且風扇停止(聽起來風扇沒有獲得旋轉所需的電壓)。

我打電話給商店,他們的解決方案是一些 Windows 軟體,但 PC 沒有 Windows,我買它是為了執行 Linux(因為它與 galago pro 相同,我認為它會工作)。

由於筆記型電腦從 system76 執行 Linux,我認為它應該是可行的。

我應該安裝什麼讓它執行得更好,或者有人知道讓粉絲開心的 BIOS 技巧嗎?

我正在執行 Solus 3.X,其中 x 是您想花時間插入的許多九;-)

使用鍵盤快捷鍵Fn+ 1(在有關 system76 galago pro 的大聲風扇的執行緒中找到)兩次將打開和關閉風扇。這將消除聲音,直到下一個硬負載消失。

自最初發布以來,我發現了兩件事:

  • system76 有一些韌體更新,但誰知道他們是否願意將它發送給另一個經銷商的筆記型電腦(我會很好地問他們)
  • System76 在 ubuntu 中有一個名為 system76-dkms 的軟體包,它可能提供風扇控制,但它不在 Solus 儲存庫中。(今晚我可能會在 Solus irc 中詢問包裝的工作原理。)

我使用下面的程式碼在 Windows 10 中取得了成功。它處理風扇可能出現的兩種故障,即:“風扇突然停止,風扇佔空比=0”和“風扇突然停止,rpm > 10000,並且可以聽到來自風扇的電雜訊”。它需要一個載入 Winring0 的程序,例如在後台執行的 ThrottleStop。我沒有在安裝了 Clevo Control Center 的情況下對其進行測試。它與 MinGW-w64 一起編譯\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;
}

我還沒有移植程式碼以在基於 linux 的 oses 中使用。這樣做需要:

  • 用and替換WInp(port)andWOutp(port, value)函式,inb(port)``outb(value, port)
  • 在此程式碼片段ioperm的開頭添加,
  • 替換Sleep(milliseconds)usleep(microseconds),
  • 清理所有現在無用的包含、定義、結構和句柄,
  • 替換GetModuleFileNameA為等效功能。

引用自:https://unix.stackexchange.com/questions/484673