├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── bsdfan.1 ├── bsdfan.c ├── bsdfan.conf ├── common.h ├── mystring.c ├── mystring.h ├── parser.c ├── parser.h ├── rc.d └── bsdfan ├── system.c └── system.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.swp 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, claudiozz 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROG= bsdfan 2 | SRCS= bsdfan.c parser.c mystring.c system.c 3 | 4 | BINDIR= ${LOCALBASE}/bin 5 | MANDIR= ${LOCALBASE}/man/man 6 | 7 | WARNS= 9 8 | 9 | MANFILTER= sed 's|/usr/local|${LOCALBASE}|g' 10 | CFLAGS+= -DLOCALBASE='"${LOCALBASE}"' 11 | 12 | .include 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NAME 2 | bsdfan - a simple utility to manage thinkpads fan on FreeBSD 3 | 4 | SYNOPSIS 5 | bsdfan [-d] [-c config-file ] 6 | 7 | DESCRIPTION 8 | bsdfan automaticallty regulates the fan speed by the sensor temperature 9 | and the user configuration. 10 | 11 | 12 | OPTIONS 13 | -d Start bsdfan as a daemon 14 | 15 | -c config-file 16 | Use the alternate system wide config-file instead of 17 | /usr/local/etc/bsdfan.conf. 18 | 19 | FILES 20 | /usr/local/etc/bsdfan.conf 21 | The system wide configuration file. Overridden by the -c 22 | option. 23 | 24 | Here the user can specifies the levels the fan will be set to. 25 | They range from 0 (fan idle) to 7 (full speed), it's not neces- 26 | sary to use all levels. Levels must be declared in ascending 27 | order by level_number and are declared as follows: 28 | 29 | level(level_number,level_min_temperature,level_max_temperature) 30 | 31 | where level_number is the level number as described above from 0 32 | to 7, 33 | 34 | level_min_temperature is the temperature below which the level 35 | will be decreased. 36 | 37 | level_max_temperature is the maximum temperature beyond which 38 | the level will be increased. 39 | 40 | level_min_temperature for the first level must be equal to 0. 41 | levl_max_temperature for the last level must be greater than 42 | 150. 43 | 44 | An example is provided with the software. 45 | 46 | -------------------------------------------------------------------------------- /bsdfan.1: -------------------------------------------------------------------------------- 1 | .TH BSDFAN 1 "November 4, 2014" BSDFAN "User Manual" 2 | .SH NAME 3 | bsdfan \- a simple utility to manage thinkpads fan on FreeBSD 4 | .SH SYNOPSIS 5 | .B bsdfan [-d] [-c 6 | .I config-file 7 | .B ] 8 | .SH DESCRIPTION 9 | .B bsdfan 10 | automaticallty regulates the fan speed by the sensor temperature and the user configuration. 11 | 12 | .SH OPTIONS 13 | .IP -d 14 | Start bsdfan as a daemon 15 | .IP "-c config-file" 16 | Use the alternate system wide 17 | .I config-file 18 | instead of 19 | .IR /usr/local/etc/bsdfan.conf . 20 | .SH FILES 21 | .I /usr/local/etc/bsdfan.conf 22 | .RS 23 | The system wide configuration file. 24 | Overridden by the 25 | .B -c 26 | option. 27 | 28 | Here the user can specifies the levels the fan will be set to. 29 | They range from 0 (fan idle) to 7 (full speed), it's not necessary to use all levels. 30 | Levels must be declared in ascending order by level_number and are declared as follows: 31 | 32 | level(level_number,level_min_temperature,level_max_temperature) 33 | 34 | where level_number is the level number as described above from 0 to 7, 35 | 36 | level_min_temperature is the temperature below which the level will be decreased. 37 | 38 | level_max_temperature is the maximum temperature beyond which the level will be increased. 39 | 40 | level_min_temperature for the first level must be equal to 0. 41 | levl_max_temperature for the last level must be greater than 150. 42 | 43 | An example is provided with the software. 44 | .SH AUTHOR 45 | Claudio Zumbo 46 | -------------------------------------------------------------------------------- /bsdfan.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "common.h" 11 | #include "parser.h" 12 | #include "system.h" 13 | 14 | #define INVALID_ARGUMENT_ERROR "Arugment not recognised" 15 | #define MODULE_LOAD_ERROR "Module acpi_ibm.ko load failed" 16 | #define DEFAULT_CONF_PATH LOCALBASE "/etc/bsdfan.conf" 17 | 18 | #define VERSION "0.1" 19 | 20 | static void 21 | autofan(int sig __unused) 22 | { 23 | setFan(AUTO,NULL); 24 | exit(EXIT_FAILURE); 25 | } 26 | 27 | int main(int argc, char *argv[]) 28 | { 29 | const char *confpath= DEFAULT_CONF_PATH; 30 | char op; 31 | bool daemonize = false; 32 | const struct Config *conf; 33 | 34 | if (kldfind("acpi_ibm")== -1) 35 | if(kldload("acpi_ibm")==-1) 36 | err(EXIT_FAILURE, MODULE_LOAD_ERROR); 37 | 38 | while( (op = getopt(argc, argv, "vdc:")) != -1) 39 | { 40 | switch(op) 41 | { 42 | case 'v': 43 | printf("Version: %s\n",VERSION); 44 | exit(0); 45 | break; 46 | case 'd': 47 | daemonize = true; 48 | break; 49 | case 'c': 50 | confpath=optarg; 51 | break; 52 | case '?': 53 | errx(EXIT_FAILURE, INVALID_ARGUMENT_ERROR); 54 | break; 55 | default: 56 | break; 57 | } 58 | } 59 | 60 | conf = readConfig(confpath); 61 | 62 | /* when exit you must reset the fan to AUTO */ 63 | (void) signal (SIGINT, autofan); 64 | (void) signal (SIGTERM, autofan); 65 | (void) signal (SIGHUP, autofan); 66 | (void) signal (SIGSEGV, autofan); 67 | 68 | setFan(MANUAL,conf->levels); 69 | 70 | int oldtemp=0; 71 | 72 | if(daemonize) 73 | daemon(0,0); 74 | 75 | while(1) 76 | { 77 | int time =2; 78 | 79 | int cur_temp = getTemp(); 80 | 81 | if(oldtemp-cur_temp>2) 82 | time =1; 83 | 84 | oldtemp = cur_temp; 85 | 86 | adjustLevel(cur_temp,conf); 87 | 88 | sleep(time); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /bsdfan.conf: -------------------------------------------------------------------------------- 1 | #Levels are defined with: level(level_number, level_min_temperature, level_max_temperature) 2 | #level_number goes from 0 (fan not active) to 7 (fan at full speed) 3 | #not all levels have to be used 4 | 5 | #the first level 'level_min_temperature' must be equal to 0 and the last level 'level_max_temperature' must be >150 6 | #please define levels in ascending order by level_number 7 | #be careful 8 | 9 | #sample config for my T400 10 | 11 | level (0,0,53) 12 | level (1,51,60) 13 | level (2,53,61) 14 | level (3,55,63) 15 | level (4,58,65) 16 | level (5,60,66) 17 | level (7,63,32767) 18 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | #define MALLOC_ERROR "Memory allocation error" 2 | 3 | struct Level 4 | { 5 | int number; 6 | int min_max[2]; 7 | }; 8 | 9 | struct Config 10 | { 11 | struct Level *levels; 12 | int levels_size; 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /mystring.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "mystring.h" 11 | 12 | #define SUBSTRING_ERROR "Character not found: %c" 13 | 14 | const char * 15 | removeLeadingSpaces(const char *buff) 16 | { 17 | while (*buff == ' ' || *buff == '\t') 18 | buff++; 19 | 20 | return buff; 21 | } 22 | 23 | int 24 | getNumber(const char *buff, char c, const char *errString, char **pend) 25 | { 26 | char *end; 27 | long value; 28 | 29 | errno = 0; 30 | value = strtol(buff, &end, 10); /* Must it always be decimal? */ 31 | 32 | if (errno) 33 | err(EX_CONFIG, errString, buff); 34 | 35 | if (*end != c) { 36 | errx(EX_CONFIG, SUBSTRING_ERROR, c); 37 | } 38 | 39 | if (value < 0) 40 | errx(EX_CONFIG, "Don't be negative (%ld)", value); 41 | 42 | if (value > INT_MAX) 43 | errx(EX_CONFIG, "Value %ld is entirely too large", value); 44 | 45 | *pend = end + 1; 46 | 47 | return (int)value; 48 | } 49 | 50 | bool 51 | isSameString(const char *a, const char *b) 52 | { 53 | return strcmp(a, b) == 0; 54 | } 55 | 56 | bool 57 | isEmptyString(const char *s) { 58 | s += strspn(s, " \t\n"); 59 | return *s == '\0'; 60 | } 61 | -------------------------------------------------------------------------------- /mystring.h: -------------------------------------------------------------------------------- 1 | int getNumber(const char *buff, char c, const char *errString, char **pend); 2 | bool isEmptyString(const char *s); 3 | const char * removeLeadingSpaces(const char *buff); 4 | bool isSameString(const char *a, const char *b); 5 | -------------------------------------------------------------------------------- /parser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "common.h" 9 | #include "mystring.h" 10 | #include "parser.h" 11 | 12 | #define CONFIG_ERROR "Could not find config file" 13 | #define PARSING_ERROR "Error parsing conifg, the following argument was not understood: %s" 14 | #define LEVEL_ERROR "Could not find '('" 15 | #define LEVEL_VAL_ERROR "Could not parse level value: %s" 16 | #define INVALID_LEVEL_ERROR "Level out of range: %d" 17 | #define LEVEL_RANGE_ERROR "This parsed range is not admissible: %s" 18 | #define SANITY_CHECK_ERROR "The levels defined in the config are not in an ascending order by level_number " 19 | #define SANITY_CHECK_TEMP_RANGE_ERROR "First level min temperature >0 or last level max temperature <150, dangerous" 20 | #define SANITY_CHECK_TEMP_ERROR "There's a gap between the temperatures defined (level n.maxlevels = NULL; 42 | config->levels_size = 0; 43 | 44 | while (fgets(buff, sizeof(buff), fp) != NULL) 45 | { 46 | if (buff[0]=='#' || isEmptyString(buff)) 47 | continue; 48 | 49 | const char *format = removeLeadingSpaces(buff); 50 | 51 | if (format[0]=='#') 52 | continue; 53 | 54 | char read[6]; 55 | strncpy(read, format, sizeof(read)-1); 56 | read[5]='\0'; 57 | 58 | if (isSameString(read, "level")) { 59 | config->levels = realloc(config->levels,sizeof(config->levels)+sizeof(struct Level)); 60 | 61 | if(config->levels==NULL) 62 | err(EX_SOFTWARE, MALLOC_ERROR); 63 | 64 | config->levels[config->levels_size] = *getLevel(format); 65 | config->levels_size++; 66 | } else 67 | errx(EX_CONFIG, PARSING_ERROR, read); 68 | } 69 | 70 | fclose(fp); 71 | sanityCheck(config->levels,config->levels_size); 72 | return config; 73 | } 74 | 75 | static void 76 | sanityCheck(const struct Level *levels, int levels_size) 77 | { 78 | if(levels[0].min_max[0]!=0 || levels[levels_size-1].min_max[1]<150) 79 | errx(EX_CONFIG, SANITY_CHECK_TEMP_RANGE_ERROR); 80 | 81 | for(int i=0;i=levels[i+1].number) 83 | errx(EX_CONFIG, SANITY_CHECK_ERROR); 84 | else if (levels[i].min_max[1]number = getNumber(p, ',', LEVEL_VAL_ERROR, &p); 106 | if (level->number > 7) 107 | errx(EX_CONFIG, INVALID_LEVEL_ERROR, level->number); 108 | 109 | level->min_max[0] = getNumber(p, ',', LEVEL_VAL_ERROR, &p); 110 | level->min_max[1] = getNumber(p, ')', LEVEL_VAL_ERROR, &p); 111 | 112 | if (level->min_max[0] >= level->min_max[1]) 113 | errx(EX_CONFIG, LEVEL_RANGE_ERROR, buff); 114 | 115 | return level; 116 | } 117 | -------------------------------------------------------------------------------- /parser.h: -------------------------------------------------------------------------------- 1 | const struct Config * readConfig(const char *path); 2 | -------------------------------------------------------------------------------- /rc.d/bsdfan: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # $FreeBSD$ 4 | 5 | # PROVIDE: bsdfan 6 | # REQUIRE: NETWORKING SERVERS DAEMON LOGIN 7 | # KEYWORD: shutdown nojail 8 | 9 | . /etc/rc.subr 10 | 11 | name=bsdfan 12 | desc="Start bsdfan" 13 | rcvar=bsdfan_enable 14 | 15 | load_rc_config $name 16 | 17 | command="/usr/local/sbin/${name}" 18 | start_cmd="${name}_start" 19 | 20 | bsdfan_start() 21 | { 22 | ${command} -d 23 | } 24 | 25 | run_rc_command "$1" 26 | -------------------------------------------------------------------------------- /system.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "common.h" 10 | #include "system.h" 11 | 12 | #define INVALID_MODE_ERROR "Invalid fan mode" 13 | #define PERMISSION_ERROR "Not enough permissions" 14 | #define KELVIN_TO_CELSIUS(t) ((t-2732+5)/10) 15 | 16 | static int idx_cur_level; 17 | static int mib_set_fan_level[4]; 18 | static int mib_get_temp_level[4]; 19 | static int mib_get_temp_level_alt[5]; 20 | static bool temp_alt = false; 21 | 22 | static void levelDown(const struct Level *levels); 23 | static void levelUp(const struct Level *levels); 24 | 25 | /*sets fan in either manual or auto mode, in manual sets the lowest defined level and calculates mib_get_fan_level and mib_get_temp_level*/ 26 | void 27 | setFan(int mode, const struct Level *levels) 28 | { 29 | switch (mode) { 30 | case MANUAL: 31 | case AUTO: 32 | /*set fan in manual mode*/ 33 | if (sysctlbyname("dev.acpi_ibm.0.fan",NULL,NULL,&mode,sizeof(int)) == -1) 34 | err(EX_NOPERM, PERMISSION_ERROR); 35 | 36 | if (mode == AUTO) 37 | break; 38 | 39 | /*get mib for dev.acpi_ibm.0.fan_level*/ 40 | size_t len=4; 41 | sysctlnametomib("dev.acpi_ibm.0.fan_level",mib_set_fan_level,&len); 42 | 43 | /*get mib for dev.acpi_ibm.0.thermal*/ 44 | len = 4; 45 | if (sysctlnametomib("dev.acpi_ibm.0.thermal",mib_get_temp_level,&len) == -1) 46 | { 47 | len = 5; 48 | /*get mib for hw.acpi.thermal.tz0.temperature*/ 49 | sysctlnametomib("hw.acpi.thermal.tz0.temperature",mib_get_temp_level_alt,&len); 50 | temp_alt=true; 51 | } 52 | 53 | /*set lowest available level*/ 54 | len = 4; 55 | 56 | sysctl(mib_set_fan_level,4,NULL,NULL,&levels[0].number,sizeof(int)); 57 | idx_cur_level = 0; 58 | break; 59 | default: 60 | errx(EX_SOFTWARE, INVALID_MODE_ERROR); 61 | } 62 | } 63 | 64 | void 65 | adjustLevel(int t, const struct Config *conf) /*t for temp*/ 66 | { 67 | if( t < conf->levels[idx_cur_level].min_max[0] ) 68 | levelDown(conf->levels); 69 | 70 | else if( t > conf->levels[idx_cur_level].min_max[1]) 71 | levelUp(conf->levels); 72 | } 73 | 74 | static void 75 | levelUp(const struct Level *levels) 76 | { 77 | sysctl(mib_set_fan_level,4,NULL,NULL,&levels[idx_cur_level+1].number,sizeof(int)); 78 | idx_cur_level++; 79 | } 80 | 81 | static void 82 | levelDown(const struct Level *levels) 83 | { 84 | sysctl(mib_set_fan_level,4,NULL,NULL,&levels[idx_cur_level-1].number,sizeof(int)); 85 | idx_cur_level--; 86 | } 87 | 88 | int 89 | getTemp() 90 | { 91 | int temp[8]={0}; 92 | size_t len = 8*sizeof(int); 93 | if(temp_alt) 94 | { 95 | sysctl(mib_get_temp_level_alt,5,&temp,&len,NULL,0); 96 | return KELVIN_TO_CELSIUS(temp[0]); 97 | } 98 | else 99 | sysctl(mib_get_temp_level,4,&temp,&len,NULL,0); 100 | 101 | return temp[0]; 102 | 103 | } 104 | -------------------------------------------------------------------------------- /system.h: -------------------------------------------------------------------------------- 1 | #include 2 | #define MANUAL 0 3 | #define AUTO 1 4 | 5 | void setFan(int mode, const struct Level *levels); 6 | void adjustLevel(int t, const struct Config *conf); 7 | int getTemp(void); 8 | --------------------------------------------------------------------------------