Dedicate Fan Controller Program under Boot Camp (Open Source)
Hi, friends:
I got my own program which a dedicated fan controller. It been tested on my new Mac Mini early 2009 (Windows XP). And my source code is open. Everybody should use this to build up a nice GUI program.
NOTE: The program is only a proof of concept. So it only works but lack of functionalities.
1. The program need GIVEIO device driver support. You can get it by install speedfan. Or you can download the standalone package from anywhere.
2. It is a console command line program. Not easy to use, but easy for script. You can add temperature sensor detect function in it.
3. How to get the 'key' definitions of Apple SMC. Please install smcFanControl under OSX. There is a console program in /Application/smcFanControl.app/Contents/Resource/Source. It named 'smc'. Use the command line 'smc -l' to get all 'Key' definitions.
4. For the Apple SMC programming, please refer to Linux Kernel applesmc.c which a kernel extension. It is nice program to communicate with SMC under Linux. The code should help you and me.
BTW: Sorry for my poor English. I am from China. If you got better idea, please email me:
iam.liuzhong@gmail.com
[codebox]
#include "stdlib.h"
#include "windows.h"
//Console inp and out functions
#include "conio.h"
#define APPLESMC_DATA_PORT 0x300
/* command/status port used by Apple SMC */
#define APPLESMC_CMD_PORT 0x304
#define APPLESMC_STATUS_MASK 0x0f
#define APPLESMC_READ_CMD 0x10
#define APPLESMC_WRITE_CMD 0x11
#define APPLESMC_GET_KEY_BY_INDEX_CMD 0x12
#define APPLESMC_GET_KEY_TYPE_CMD 0x13
SC_HANDLE hSCMan = NULL;
BOOL IsWinNT = FALSE;
BOOL IsDriverLoaded = FALSE;
// Return Value // Meaning
enum { DRIVER_ALREADY_INSTALLED=100, //100 Driver is already Installed
DRIVER_INSTALL_SUCCESS, //101
DRIVER_INSTALL_FAILURE, //102
DRIVER_ALREADY_UNINSTALLED, //103
DRIVER_UNINSTALL_SUCCESS, //104
DRIVER_UNINSTALL_FAILURE, //105
DRIVER_NOT_INSTALLED, //106
DRIVER_ALREADY_STARTED, //107
DRIVER_IN_USE
};
// SCM & GIVEIO control
BOOL InitSCM()
{
if ((hSCMan = OpenSCManager(NULL, NULL,SC_MANAGER_ALL_ACCESS)) == NULL)
{
printf("ERROR: Can't connect to Service Control Manager.\n");
return FALSE;
}
return TRUE;
}
BOOL ShutDownSCM(SC_HANDLE hSCMan)
{
return CloseServiceHandle(hSCMan);
}
DWORD DriverInstall(LPSTR lpPath, LPSTR lpDriver)
{
BOOL dwStatus = 0;
SC_HANDLE hService = NULL;
// add to service control manager's database
if ((hService = CreateService(hSCMan,
lpDriver,
lpDriver,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
lpPath, NULL, NULL, NULL, NULL, NULL)) == NULL)
dwStatus = GetLastError();
else
CloseServiceHandle(hService);
return dwStatus;
}
DWORD DriverRemove(LPSTR lpDriver)
{
BOOL dwStatus = 0;
SC_HANDLE hService = NULL;
// get a handle to the service
if ((hService = OpenService(hSCMan, lpDriver, SERVICE_ALL_ACCESS)) != NULL)
{
// remove the driver
if (!DeleteService(hService)) dwStatus = GetLastError();
}
else dwStatus = GetLastError();
if (hService != NULL) CloseServiceHandle(hService);
return dwStatus;
}
///////////////////////////////////////////////////////////////
// FUNC: GetDriverStatus
// DESC: Returns a Bool; 0 -> GiveIO Driver NOT loaded
// 1 -> GiveIO Driver LOADED
///////////////////////////////////////////////////////////////
int GetDriverStatus()
{
return IsDriverLoaded;
}
BOOL AttachDrv()
{
HANDLE h;
h = CreateFile("\\\\.\\giveio", GENERIC_READ, 0, NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);
if(h == INVALID_HANDLE_VALUE)
{
printf("ERROR: Couldn't access giveio device.\n");
return FALSE;
}
CloseHandle(h);
return TRUE;
}
BOOL AttachDIO()
{
OSVERSIONINFO osvi;
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
IsWinNT = osvi.dwMajorVersion == 3 || osvi.dwMajorVersion == 4 || osvi.dwMajorVersion == 5 || osvi.dwMajorVersion == 6;
if(IsWinNT)
{
//Load the DirectIO Driver and attach it to this process
//try opening SCM ; if failed Bail out
if(!InitSCM()) return FALSE;
//Install the Driver
char szDrvPath[MAX_PATH];
GetSystemDirectory(szDrvPath,MAX_PATH);
lstrcat(szDrvPath,"\\Drivers\\GiveIO.sys");
DWORD dwRet = DriverInstall(szDrvPath,"giveio");
if(dwRet != 0 && dwRet != 0x00000431) //Success or already installed
{
printf("ERROR: Could not initialize GiveIO.sys Driver.\n");
return FALSE;
}
if(AttachDrv())
{
IsDriverLoaded = TRUE ;
return TRUE ; // Successful PROCESS_ATTACH
}
else
{
DriverRemove("giveio");
return FALSE;
}
}
return TRUE;
}
BOOL DetachDIO()
{
// Perform any necessary cleanup.
//if it is WinNT unload the giveIO driver
if(IsWinNT)
{
DriverRemove("giveio");
IsDriverLoaded = FALSE ;
return ShutDownSCM(hSCMan); //No Error Check

}
return FALSE;
}
short OutPort( int PortAddress, int PortData )
{
short Dummy;
Dummy = (short)(_outp( PortAddress, PortData ));
return(Dummy);
}
short InPort( int PortAddress )
{
short PortData;
PortData = (short)(_inp( PortAddress ));
return( PortData );
}
int wait_status(short val)
{
unsigned int i;
val = val & APPLESMC_STATUS_MASK;
for (i = 0; i < 200; i++) {
if ((InPort(APPLESMC_CMD_PORT) & APPLESMC_STATUS_MASK) == val) return 0;
Sleep(10);
}
return -1;
}
static int applesmc_read_key(const char* key, short* buffer, short len)
{
int i;
OutPort(APPLESMC_CMD_PORT, APPLESMC_READ_CMD);
if (wait_status(0x0c)) return -1;
for (i = 0; i < 4; i++) {
OutPort(APPLESMC_DATA_PORT, key
);
if (wait_status(0x04)) return -1;
}
OutPort(APPLESMC_DATA_PORT, len);
for (i = 0; i < len; i++) {
if (wait_status(0x05)) return -1;
buffer = InPort(APPLESMC_DATA_PORT);
}
return 0;
}
static int applesmc_write_key(const char* key, short* buffer, short len)
{
int i;
OutPort(APPLESMC_CMD_PORT, APPLESMC_WRITE_CMD);
if (wait_status(0x0c)) return -1;
for (i = 0; i < 4; i++) {
OutPort(APPLESMC_DATA_PORT, key);
if (wait_status(0x04)) return -1;
}
OutPort(APPLESMC_DATA_PORT, len);
for (i = 0; i < len; i++) {
if (wait_status(0x04)) return -1;
OutPort(APPLESMC_DATA_PORT, buffer);
}
return 0;
}
int main(int argc, char* argv[])
{
short speed[2];
char MINSPEED[5] = "F0Mn";
int st = 0;
AttachDIO();
st = GetDriverStatus();
printf("Driver Status: %d\n", st);
if (st != 1) return 1;
if (argc == 2) {
printf("Set the Minimal speed: %s\n", argv[1]);
st = atoi(argv[1]);
st = st * 4;
speed[0] = st >> 8;
speed[1] = st & 0x00FF;
for (int i = 0; i < 120; i++)
{
if (applesmc_write_key(MINSPEED, speed, 2) == 0) break;
Sleep(1000);
}
if (i == 120) {
printf("Failed, retry again!\n");
return 1;
}
printf("Update Successful\n");
}
for (int i = 0; i < 120; i++)
{
memset(speed, 0, sizeof(speed));
if (applesmc_read_key(MINSPEED, speed, 2) == 0) break;
Sleep(1000);
}
if (i == 120) {
printf("Failed, retry again!\n");
return 1;
}
st = (speed[0] * 0xFF + speed[1]) / 4;
printf("Current Minimal speed: %d\n", st);
printf("All OK!\n");
return 0;
}
[/codebox]
Usage: smc [Target minimal fan speed you like]
Two more points:
1. Speedfan lack of support to Apple SMC device driver. Speedfan just only find the SMBus devices on Motherboard. For ex. my Mac Mini has a nvidia nForce motherboard. Speedfan find the I/O Port 2140 & 2100 which all are nvidia SMBus devices. And nvidia did not provide any control method from OS to control fan or something. At least the symptom apply for my Macmini.
2. InputRemapper. A greate tool, It can work well with Apple SMC interfaces. But the fan control did not work for some sort of Mac. And the code not updated for a long time. By my test, Inputremapper may add some retry mechanism. Because I try to update the SMC chip which failed time to time. So my program will retry 120 times untill success.