diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 85f3fe27..404630fb 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -36,6 +36,8 @@ problems. he also tested lots of things, and helped us out on several occasions. * Fons Rademakers Author of the maxilife module (which could only be written under NDA) +* Merlin Hughes + Author of the AMD756 module and sensord. * Simon Vogl Wrote the new i2c kernel driver. This driver is indispensable for the diff --git a/Makefile b/Makefile index bf9ea90e..91170cf2 100644 --- a/Makefile +++ b/Makefile @@ -133,7 +133,7 @@ SRCDIRS := mkpatch ifeq ($(COMPILE_KERNEL),1) SRCDIRS += kernel kernel/busses kernel/chips kernel/include endif -SRCDIRS += lib prog/sensors prog/dump prog/detect etc +SRCDIRS += lib prog/sensors prog/sensord prog/dump prog/detect etc # Some often-used commands with default options MKDIR := mkdir -p diff --git a/doc/progs b/doc/progs index 9952287c..c9bb9a30 100644 --- a/doc/progs +++ b/doc/progs @@ -51,6 +51,12 @@ this package. * prog/mkdev/mkdev.sh (written in bash, not installed) This script creates the /dev/i2c-* files. Please check the source before you run it, as you may need to set some variables first. +* prog/sensord/sensord (written in C, installed by `make install-prog-sensord') + This daemon logs sensor information every 5 minutes to the log file + /var/log/sensors, in a similar format to sensors (below). This is a bit + more lightweight than executing sensors every 5 minutes, depending on how + you look at things. Automatically picks up configuration updates and + responds to SIGHUP. * prog/sensors/sensors (written in C, installed by `make install') This pretty-prints the information provided by the modules. It is a normal console application, using stdin and stdout. It is essential, because diff --git a/prog/sensord/Module.mk b/prog/sensord/Module.mk new file mode 100644 index 00000000..9a863ee0 --- /dev/null +++ b/prog/sensord/Module.mk @@ -0,0 +1,51 @@ +# Module.mk - Makefile for a Linux module for reading sensor data. +# Copyright (c) 1998, 1999 Frodo Looijaard +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# Note that MODULE_DIR (the directory in which this file resides) is a +# 'simply expanded variable'. That means that its value is substituted +# verbatim in the rules, until it is redefined. +MODULE_DIR := prog/sensord +PROGSENSORDDIR := $(MODULE_DIR) + +PROGSENSORDMAN1DIR := $(MANDIR)/man8 +PROGSENSORDMAN1FILES := $(MODULE_DIR)/sensord.8 + +# Regrettably, even 'simply expanded variables' will not put their currently +# defined value verbatim into the command-list of rules... +PROGSENSORDTARGETS := $(MODULE_DIR)/sensord +PROGSENSORDSOURCES := $(MODULE_DIR)/sensord.c $(MODULE_DIR)/chips.c + +# Include all dependency files. We use '.rd' to indicate this will create +# executables. +INCLUDEFILES += $(PROGSENSORDSOURCES:.c=.rd) + +$(PROGSENSORDTARGETS): $(PROGSENSORDSOURCES:.c=.ro) lib/$(LIBSHBASENAME) + $(CC) -o $@ $(PROGSENSORDSOURCES:.c=.ro) -Llib -lsensors + +all-prog-sensord: $(PROGSENSORDTARGETS) +all :: all-prog-sensord + +install-prog-sensord: all-prog-sensord + mkdir -p $(SBINDIR) $(PROGSENSORDMAN1DIR) + $(INSTALL) -o root -g root -m 755 $(PROGSENSORDTARGETS) $(SBINDIR) + $(INSTALL) -o $(MANOWN) -g $(MANGRP) -m 644 $(PROGSENSORDMAN1FILES) $(PROGSENSORDMAN1DIR) +# install :: install-prog-sensord + +clean-prog-sensord: + $(RM) $(PROGSENSORDDIR)/*.rd $(PROGSENSORDDIR)/*.ro + $(RM) $(PROGSENSORDTARGETS) +clean :: clean-prog-sensord diff --git a/prog/sensord/chips.c b/prog/sensord/chips.c new file mode 100644 index 00000000..e16b458c --- /dev/null +++ b/prog/sensord/chips.c @@ -0,0 +1,465 @@ +/* + * sensord + * + * A daemon that logs all sensor information to /var/log/sensors + * every 5 minutes. + * + * Copyright (c) 1999 Merlin Hughes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "lib/sensors.h" +#include "lib/chips.h" + +#include "sensord.h" + +/* missing: "gl520sm", "thmc50", "adm1022" */ + +/* I don't support gl518sm-r00 - but neither does sensors */ + +/* Should the maxilife funky fan stuff be in sensors.conf? */ + +/* I don't support alarms. */ + +/** formatters **/ + +static char buff[4096]; + +static char * +fmtValu_0 +(double values[]) { + sprintf (buff, "%.0f", values[0]); + return buff; +} + +static char * +fmtTemps_0 +(double values[]) { + sprintf (buff, "%.0f C (limit = %.0f C, hysteresis = %.0f C)", values[0], values[1], values[2]); + return buff; +} + +static char * +fmtTemps_1 +(double values[]) { + sprintf (buff, "%.1f C (limit = %.1f C, hysteresis = %.1f C)", values[0], values[1], values[2]); + return buff; +} + +static char * +fmtVolt_2 +(double values[]) { + sprintf (buff, "%+.2f V", values[0]); + return buff; +} + +static char * +fmtVolts_2 +(double values[]) { + sprintf (buff, "%+.2f V (min = %+.2f V, max = %+.2f V)", values[0], values[1], values[2]); + return buff; +} + +static char * +fmtFans_0 +(double values[]) { + sprintf (buff, "%.0f RPM (min = %.0f RPM, div = %.0f)", values[0], values[1], values[2]); + return buff; +} + +static char * +fmtMHz_2 +(double values[]) { + sprintf (buff, "%.2f MHz (min = %.2f MHz, max = %.2f MHz)", values[0], values[1], values[2]); + return buff; +} + +/** LM75 **/ + +static char *lm75_names[] = { + SENSORS_LM75_PREFIX, NULL +}; + +static FeatureDescriptor lm75_features[] = { + { fmtTemps_1, SENSORS_LM75_TEMP, + { SENSORS_LM75_TEMP, SENSORS_LM75_TEMP_HYST, SENSORS_LM75_TEMP_OVER, -1 } }, + { NULL } +}; + +/** ADM1021 **/ + +static char *adm1021_names[] = { + SENSORS_ADM1021_PREFIX, NULL +}; + +static FeatureDescriptor adm1021_features[] = { + { fmtTemps_0, SENSORS_ADM1021_TEMP, + { SENSORS_ADM1021_TEMP, SENSORS_ADM1021_TEMP_OVER, SENSORS_ADM1021_TEMP_HYST, -1 } }, + { fmtTemps_0, SENSORS_ADM1021_REMOTE_TEMP, + { SENSORS_ADM1021_REMOTE_TEMP, SENSORS_ADM1021_REMOTE_TEMP_OVER, SENSORS_ADM1021_REMOTE_TEMP_HYST, -1 } }, + { fmtValu_0, SENSORS_ADM1021_DIE_CODE, + { SENSORS_ADM1021_DIE_CODE, -1 } }, + { NULL } +}; + +/** MAX1617 **/ + +static char *max1617_names[] = { + SENSORS_MAX1617_PREFIX, SENSORS_MAX1617A_PREFIX, SENSORS_THMC10_PREFIX, SENSORS_LM84_PREFIX, SENSORS_GL523_PREFIX, NULL +}; + +static FeatureDescriptor max1617_features[] = { + { fmtTemps_0, SENSORS_ADM1021_TEMP, + { SENSORS_ADM1021_TEMP, SENSORS_ADM1021_TEMP_OVER, SENSORS_ADM1021_TEMP_HYST, -1 } }, + { fmtTemps_0, SENSORS_ADM1021_REMOTE_TEMP, + { SENSORS_ADM1021_REMOTE_TEMP, SENSORS_ADM1021_REMOTE_TEMP_OVER, SENSORS_ADM1021_REMOTE_TEMP_HYST, -1 } }, + { NULL } +}; + +/** ADM9240 **/ + +static char *adm9240_names[] = { + SENSORS_ADM9240_PREFIX, SENSORS_DS1780_PREFIX, SENSORS_LM81_PREFIX, NULL +}; + +static FeatureDescriptor adm9240_features[] = { + { fmtVolts_2, SENSORS_ADM9240_IN0, + { SENSORS_ADM9240_IN0, SENSORS_ADM9240_IN0_MIN, SENSORS_ADM9240_IN0_MAX, -1 } }, + { fmtVolts_2, SENSORS_ADM9240_IN1, + { SENSORS_ADM9240_IN1, SENSORS_ADM9240_IN1_MIN, SENSORS_ADM9240_IN1_MAX, -1 } }, + { fmtVolts_2, SENSORS_ADM9240_IN2, + { SENSORS_ADM9240_IN2, SENSORS_ADM9240_IN2_MIN, SENSORS_ADM9240_IN2_MAX, -1 } }, + { fmtVolts_2, SENSORS_ADM9240_IN3, + { SENSORS_ADM9240_IN3, SENSORS_ADM9240_IN3_MIN, SENSORS_ADM9240_IN3_MAX, -1 } }, + { fmtVolts_2, SENSORS_ADM9240_IN4, + { SENSORS_ADM9240_IN4, SENSORS_ADM9240_IN4_MIN, SENSORS_ADM9240_IN4_MAX, -1 } }, + { fmtVolts_2, SENSORS_ADM9240_IN5, + { SENSORS_ADM9240_IN5, SENSORS_ADM9240_IN5_MIN, SENSORS_ADM9240_IN5_MAX, -1 } }, + { fmtFans_0, SENSORS_ADM9240_FAN1, + { SENSORS_ADM9240_FAN1, SENSORS_ADM9240_FAN1_MIN, SENSORS_ADM9240_FAN1_DIV, -1 } }, + { fmtFans_0, SENSORS_ADM9240_FAN2, + { SENSORS_ADM9240_FAN2, SENSORS_ADM9240_FAN2_MIN, SENSORS_ADM9240_FAN2_DIV, -1 } }, + { fmtTemps_0, SENSORS_ADM9240_TEMP, + { SENSORS_ADM9240_TEMP, SENSORS_ADM9240_TEMP_OVER, SENSORS_ADM9240_TEMP_HYST, -1 } }, + { fmtVolt_2, SENSORS_ADM9240_VID, + { SENSORS_ADM9240_VID, -1 } }, + { NULL } +}; + +/** SIS5595 **/ + +static char *sis5595_names[] = { + SENSORS_SIS5595_PREFIX, NULL +}; + +static FeatureDescriptor sis5595_features[] = { + { fmtVolts_2, SENSORS_SIS5595_IN0, + { SENSORS_SIS5595_IN0, SENSORS_SIS5595_IN0_MIN, SENSORS_SIS5595_IN0_MAX, -1 } }, + { fmtVolts_2, SENSORS_SIS5595_IN1, + { SENSORS_SIS5595_IN1, SENSORS_SIS5595_IN1_MIN, SENSORS_SIS5595_IN1_MAX, -1 } }, + { fmtVolts_2, SENSORS_SIS5595_IN2, + { SENSORS_SIS5595_IN2, SENSORS_SIS5595_IN2_MIN, SENSORS_SIS5595_IN2_MAX, -1 } }, + { fmtVolts_2, SENSORS_SIS5595_IN3, + { SENSORS_SIS5595_IN3, SENSORS_SIS5595_IN3_MIN, SENSORS_SIS5595_IN3_MAX, -1 } }, + { fmtFans_0, SENSORS_SIS5595_FAN1, + { SENSORS_SIS5595_FAN1, SENSORS_SIS5595_FAN1_MIN, SENSORS_SIS5595_FAN1_DIV, -1 } }, + { fmtFans_0, SENSORS_SIS5595_FAN2, + { SENSORS_SIS5595_FAN2, SENSORS_SIS5595_FAN2_MIN, SENSORS_SIS5595_FAN2_DIV, -1 } }, + { fmtTemps_0, SENSORS_SIS5595_TEMP, + { SENSORS_SIS5595_TEMP, SENSORS_SIS5595_TEMP_OVER, SENSORS_SIS5595_TEMP_HYST, -1 } }, + { NULL } +}; + +/** LM78 **/ + +static char *lm78_names[] = { + SENSORS_LM78_PREFIX, SENSORS_LM78J_PREFIX, SENSORS_LM79_PREFIX, /*"sis5595",*/ NULL +}; + +static FeatureDescriptor lm78_features[] = { + { fmtVolts_2, SENSORS_LM78_IN0, + { SENSORS_LM78_IN0, SENSORS_LM78_IN0_MIN, SENSORS_LM78_IN0_MAX, -1 } }, + { fmtVolts_2, SENSORS_LM78_IN1, + { SENSORS_LM78_IN1, SENSORS_LM78_IN1_MIN, SENSORS_LM78_IN1_MAX, -1 } }, + { fmtVolts_2, SENSORS_LM78_IN2, + { SENSORS_LM78_IN2, SENSORS_LM78_IN2_MIN, SENSORS_LM78_IN2_MAX, -1 } }, + { fmtVolts_2, SENSORS_LM78_IN3, + { SENSORS_LM78_IN3, SENSORS_LM78_IN3_MIN, SENSORS_LM78_IN3_MAX, -1 } }, + { fmtVolts_2, SENSORS_LM78_IN4, + { SENSORS_LM78_IN4, SENSORS_LM78_IN4_MIN, SENSORS_LM78_IN4_MAX, -1 } }, + { fmtVolts_2, SENSORS_LM78_IN5, + { SENSORS_LM78_IN5, SENSORS_LM78_IN5_MIN, SENSORS_LM78_IN5_MAX, -1 } }, + { fmtVolts_2, SENSORS_LM78_IN6, + { SENSORS_LM78_IN6, SENSORS_LM78_IN6_MIN, SENSORS_LM78_IN6_MAX, -1 } }, + { fmtFans_0, SENSORS_LM78_FAN1, + { SENSORS_LM78_FAN1, SENSORS_LM78_FAN1_MIN, SENSORS_LM78_FAN1_DIV, -1 } }, + { fmtFans_0, SENSORS_LM78_FAN2, + { SENSORS_LM78_FAN2, SENSORS_LM78_FAN2_MIN, SENSORS_LM78_FAN2_DIV, -1 } }, + { fmtFans_0, SENSORS_LM78_FAN3, + { SENSORS_LM78_FAN3, SENSORS_LM78_FAN3_MIN, SENSORS_LM78_FAN3_DIV, -1 } }, + { fmtTemps_0, SENSORS_LM78_TEMP, + { SENSORS_LM78_TEMP, SENSORS_LM78_TEMP_OVER, SENSORS_LM78_TEMP_HYST, -1 } }, + { fmtVolt_2, SENSORS_LM78_VID, + { SENSORS_LM78_VID, -1 } }, + { NULL } +}; + +/** GL518 **/ + +static char *gl518_names[] = { + SENSORS_GL518_PREFIX, NULL +}; + +static FeatureDescriptor gl518_features[] = { + { fmtVolts_2, SENSORS_GL518_VDD, + { SENSORS_GL518_VDD, SENSORS_GL518_VDD_MIN, SENSORS_GL518_VDD_MAX, -1 } }, + { fmtVolts_2, SENSORS_GL518_VIN1, + { SENSORS_GL518_VIN1, SENSORS_GL518_VIN1_MIN, SENSORS_GL518_VIN1_MAX, -1 } }, + { fmtVolts_2, SENSORS_GL518_VIN2, + { SENSORS_GL518_VIN2, SENSORS_GL518_VIN2_MIN, SENSORS_GL518_VIN2_MAX, -1 } }, + { fmtVolts_2, SENSORS_GL518_VIN3, + { SENSORS_GL518_VIN3, SENSORS_GL518_VIN3_MIN, SENSORS_GL518_VIN3_MAX, -1 } }, + { fmtFans_0, SENSORS_GL518_FAN1, + { SENSORS_GL518_FAN1, SENSORS_GL518_FAN1_MIN, SENSORS_GL518_FAN1_DIV, -1 } }, + { fmtFans_0, SENSORS_GL518_FAN2, + { SENSORS_GL518_FAN2, SENSORS_GL518_FAN2_MIN, SENSORS_GL518_FAN2_DIV, -1 } }, + { fmtTemps_0, SENSORS_GL518_TEMP, + { SENSORS_GL518_TEMP, SENSORS_GL518_TEMP_OVER, SENSORS_GL518_TEMP_HYST, -1 } }, + { NULL } +}; + +/** LM80 **/ + +static char * +fmtTemps_LM80 +(double values[]) { + sprintf (buff, "%.0f C (hot limit = %.0f C, hot hysteresis = %.0f C, os limit = %.0f C, os hysteresis = %.0f C)", values[0], values[1], values[2], values[3], values[4]); + return buff; +} + +static char *lm80_names[] = { + SENSORS_LM80_PREFIX, NULL +}; + +static FeatureDescriptor lm80_features[] = { + { fmtVolts_2, SENSORS_LM80_IN0, + { SENSORS_LM80_IN0, SENSORS_LM80_IN0_MIN, SENSORS_LM80_IN0_MAX, -1 } }, + { fmtVolts_2, SENSORS_LM80_IN1, + { SENSORS_LM80_IN1, SENSORS_LM80_IN1_MIN, SENSORS_LM80_IN1_MAX, -1 } }, + { fmtVolts_2, SENSORS_LM80_IN2, + { SENSORS_LM80_IN2, SENSORS_LM80_IN2_MIN, SENSORS_LM80_IN2_MAX, -1 } }, + { fmtVolts_2, SENSORS_LM80_IN3, + { SENSORS_LM80_IN3, SENSORS_LM80_IN3_MIN, SENSORS_LM80_IN3_MAX, -1 } }, + { fmtVolts_2, SENSORS_LM80_IN4, + { SENSORS_LM80_IN4, SENSORS_LM80_IN4_MIN, SENSORS_LM80_IN4_MAX, -1 } }, + { fmtVolts_2, SENSORS_LM80_IN5, + { SENSORS_LM80_IN5, SENSORS_LM80_IN5_MIN, SENSORS_LM80_IN5_MAX, -1 } }, + { fmtVolts_2, SENSORS_LM80_IN6, + { SENSORS_LM80_IN6, SENSORS_LM80_IN6_MIN, SENSORS_LM80_IN6_MAX, -1 } }, + { fmtFans_0, SENSORS_LM80_FAN1, + { SENSORS_LM80_FAN1, SENSORS_LM80_FAN1_MIN, SENSORS_LM80_FAN1_DIV, -1 } }, + { fmtFans_0, SENSORS_LM80_FAN2, + { SENSORS_LM80_FAN2, SENSORS_LM80_FAN2_MIN, SENSORS_LM80_FAN2_DIV, -1 } }, + { fmtTemps_LM80, SENSORS_LM80_TEMP, + { SENSORS_LM80_TEMP, SENSORS_LM80_TEMP_HOT_MAX, SENSORS_LM80_TEMP_HOT_HYST, SENSORS_LM80_TEMP_OS_MAX, SENSORS_LM80_TEMP_OS_HYST, -1 } }, + { NULL } +}; + +/** W83781D **/ + +static char *w83781d_names[] = { + SENSORS_W83781D_PREFIX, NULL +}; + +static FeatureDescriptor w83781d_features[] = { + { fmtVolts_2, SENSORS_W83781D_IN0, + { SENSORS_W83781D_IN0, SENSORS_W83781D_IN0_MIN, SENSORS_W83781D_IN0_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83781D_IN1, + { SENSORS_W83781D_IN1, SENSORS_W83781D_IN1_MIN, SENSORS_W83781D_IN1_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83781D_IN2, + { SENSORS_W83781D_IN2, SENSORS_W83781D_IN2_MIN, SENSORS_W83781D_IN2_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83781D_IN3, + { SENSORS_W83781D_IN3, SENSORS_W83781D_IN3_MIN, SENSORS_W83781D_IN3_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83781D_IN4, + { SENSORS_W83781D_IN4, SENSORS_W83781D_IN4_MIN, SENSORS_W83781D_IN4_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83781D_IN5, + { SENSORS_W83781D_IN5, SENSORS_W83781D_IN5_MIN, SENSORS_W83781D_IN5_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83781D_IN6, + { SENSORS_W83781D_IN6, SENSORS_W83781D_IN6_MIN, SENSORS_W83781D_IN6_MAX, -1 } }, + { fmtFans_0, SENSORS_W83781D_FAN1, + { SENSORS_W83781D_FAN1, SENSORS_W83781D_FAN1_MIN, SENSORS_W83781D_FAN1_DIV, -1 } }, + { fmtFans_0, SENSORS_W83781D_FAN2, + { SENSORS_W83781D_FAN2, SENSORS_W83781D_FAN2_MIN, SENSORS_W83781D_FAN2_DIV, -1 } }, + { fmtFans_0, SENSORS_W83781D_FAN3, + { SENSORS_W83781D_FAN3, SENSORS_W83781D_FAN3_MIN, SENSORS_W83781D_FAN3_DIV, -1 } }, + { fmtTemps_1, SENSORS_W83781D_TEMP1, + { SENSORS_W83781D_TEMP1, SENSORS_W83781D_TEMP1_OVER, SENSORS_W83781D_TEMP1_HYST, -1 } }, + { fmtTemps_1, SENSORS_W83781D_TEMP2, + { SENSORS_W83781D_TEMP2, SENSORS_W83781D_TEMP2_OVER, SENSORS_W83781D_TEMP2_HYST, -1 } }, + { fmtTemps_1, SENSORS_W83781D_TEMP3, + { SENSORS_W83781D_TEMP3, SENSORS_W83781D_TEMP3_OVER, SENSORS_W83781D_TEMP3_HYST, -1 } }, + { fmtVolt_2, SENSORS_W83781D_VID, + { SENSORS_W83781D_VID, -1 } }, + { NULL } +}; + +/** W83782D **/ + +static char * +fmtTemps_W8378x +(double values[]) { + int sensorID = (int) values[3]; + char *sensor = (sensorID == 1) ? "PII/Celeron diode" : (sensorID == 2) ? "3904 transistor" : "thermistor"; /* Is this still right? */ + sprintf (buff, "%.1f C (limit = %.1f C, hysteresis = %.1f C, sensor = %s)", values[0], values[1], values[2], sensor); + return buff; +} + +static char *w83782d_names[] = { + SENSORS_W83782D_PREFIX, SENSORS_W83627HF_PREFIX, SENSORS_AS99127F_PREFIX, NULL +}; + +static FeatureDescriptor w83782d_features[] = { + { fmtVolts_2, SENSORS_W83782D_IN0, + { SENSORS_W83782D_IN0, SENSORS_W83782D_IN0_MIN, SENSORS_W83782D_IN0_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83782D_IN1, + { SENSORS_W83782D_IN1, SENSORS_W83782D_IN1_MIN, SENSORS_W83782D_IN1_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83782D_IN2, + { SENSORS_W83782D_IN2, SENSORS_W83782D_IN2_MIN, SENSORS_W83782D_IN2_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83782D_IN3, + { SENSORS_W83782D_IN3, SENSORS_W83782D_IN3_MIN, SENSORS_W83782D_IN3_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83782D_IN4, + { SENSORS_W83782D_IN4, SENSORS_W83782D_IN4_MIN, SENSORS_W83782D_IN4_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83782D_IN5, + { SENSORS_W83782D_IN5, SENSORS_W83782D_IN5_MIN, SENSORS_W83782D_IN5_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83782D_IN7, + { SENSORS_W83782D_IN7, SENSORS_W83782D_IN7_MIN, SENSORS_W83782D_IN7_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83782D_IN8, + { SENSORS_W83782D_IN8, SENSORS_W83782D_IN8_MIN, SENSORS_W83782D_IN8_MAX, -1 } }, + { fmtFans_0, SENSORS_W83782D_FAN1, + { SENSORS_W83782D_FAN1, SENSORS_W83782D_FAN1_MIN, SENSORS_W83782D_FAN1_DIV, -1 } }, + { fmtFans_0, SENSORS_W83782D_FAN2, + { SENSORS_W83782D_FAN2, SENSORS_W83782D_FAN2_MIN, SENSORS_W83782D_FAN2_DIV, -1 } }, + { fmtFans_0, SENSORS_W83782D_FAN3, + { SENSORS_W83782D_FAN3, SENSORS_W83782D_FAN3_MIN, SENSORS_W83782D_FAN3_DIV, -1 } }, + { fmtTemps_W8378x, SENSORS_W83782D_TEMP1, + { SENSORS_W83782D_TEMP1, SENSORS_W83782D_TEMP1_OVER, SENSORS_W83782D_TEMP1_HYST, SENSORS_W83782D_SENS1, -1 } }, + { fmtTemps_W8378x, SENSORS_W83782D_TEMP2, + { SENSORS_W83782D_TEMP2, SENSORS_W83782D_TEMP2_OVER, SENSORS_W83782D_TEMP2_HYST, SENSORS_W83782D_SENS2, -1 } }, + { fmtTemps_W8378x, SENSORS_W83782D_TEMP3, + { SENSORS_W83782D_TEMP3, SENSORS_W83782D_TEMP3_OVER, SENSORS_W83782D_TEMP3_HYST, SENSORS_W83782D_SENS3, -1 } }, + { fmtVolt_2, SENSORS_W83782D_VID, + { SENSORS_W83782D_VID, -1 } }, + { NULL } +}; + +/** W83783S **/ + +static char *w83783s_names[] = { + SENSORS_W83783S_PREFIX, NULL +}; + +static FeatureDescriptor w83783s_features[] = { + { fmtVolts_2, SENSORS_W83783S_IN0, + { SENSORS_W83783S_IN0, SENSORS_W83783S_IN0_MIN, SENSORS_W83783S_IN0_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83783S_IN1, + { SENSORS_W83783S_IN1, SENSORS_W83783S_IN1_MIN, SENSORS_W83783S_IN1_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83783S_IN2, + { SENSORS_W83783S_IN2, SENSORS_W83783S_IN2_MIN, SENSORS_W83783S_IN2_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83783S_IN3, + { SENSORS_W83783S_IN3, SENSORS_W83783S_IN3_MIN, SENSORS_W83783S_IN3_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83783S_IN4, + { SENSORS_W83783S_IN4, SENSORS_W83783S_IN4_MIN, SENSORS_W83783S_IN4_MAX, -1 } }, + { fmtVolts_2, SENSORS_W83783S_IN5, + { SENSORS_W83783S_IN5, SENSORS_W83783S_IN5_MIN, SENSORS_W83783S_IN5_MAX, -1 } }, + { fmtFans_0, SENSORS_W83783S_FAN1, + { SENSORS_W83783S_FAN1, SENSORS_W83783S_FAN1_MIN, SENSORS_W83783S_FAN1_DIV, -1 } }, + { fmtFans_0, SENSORS_W83783S_FAN2, + { SENSORS_W83783S_FAN2, SENSORS_W83783S_FAN2_MIN, SENSORS_W83783S_FAN2_DIV, -1 } }, + { fmtFans_0, SENSORS_W83783S_FAN3, + { SENSORS_W83783S_FAN3, SENSORS_W83783S_FAN3_MIN, SENSORS_W83783S_FAN3_DIV, -1 } }, + { fmtTemps_W8378x, SENSORS_W83783S_TEMP1, + { SENSORS_W83783S_TEMP1, SENSORS_W83783S_TEMP1_OVER, SENSORS_W83783S_TEMP1_HYST, SENSORS_W83783S_SENS1, -1 } }, + { fmtTemps_W8378x, SENSORS_W83783S_TEMP2, + { SENSORS_W83783S_TEMP2, SENSORS_W83783S_TEMP2_OVER, SENSORS_W83783S_TEMP2_HYST, SENSORS_W83783S_SENS2, -1 } }, + { fmtVolt_2, SENSORS_W83783S_VID, + { SENSORS_W83783S_VID, -1 } }, + { NULL } +}; + +/** MAXILIFE **/ + +static char * +fmtFans_Maxilife +(double values[]) { + if (values[0] < 0) { + sprintf (buff, "Off (min = %.0f RPM, div = %.0f)", values[1], values[2]); + } else { + sprintf (buff, "%.0f RPM (min = %.0f RPM, div = %.0f)", values[0] / values[2], values[1] / values[2], values[2]); + } + return buff; +} + +static char *maxilife_names[] = { + SENSORS_MAXI_CG_PREFIX, SENSORS_MAXI_CO_PREFIX, SENSORS_MAXI_AS_PREFIX, NULL +}; + +static FeatureDescriptor maxilife_features[] = { + { fmtTemps_1, SENSORS_MAXI_CG_TEMP1, + { SENSORS_MAXI_CG_TEMP1, SENSORS_MAXI_CG_TEMP1_MAX, SENSORS_MAXI_CG_TEMP1_HYST, -1 } }, + { fmtTemps_1, SENSORS_MAXI_CG_TEMP2, + { SENSORS_MAXI_CG_TEMP2, SENSORS_MAXI_CG_TEMP2_MAX, SENSORS_MAXI_CG_TEMP2_HYST, -1 } }, + { fmtTemps_1, SENSORS_MAXI_CG_TEMP3, + { SENSORS_MAXI_CG_TEMP3, SENSORS_MAXI_CG_TEMP3_MAX, SENSORS_MAXI_CG_TEMP3_HYST, -1 } }, + { fmtTemps_1, SENSORS_MAXI_CG_TEMP4, + { SENSORS_MAXI_CG_TEMP4, SENSORS_MAXI_CG_TEMP4_MAX, SENSORS_MAXI_CG_TEMP4_HYST, -1 } }, + { fmtTemps_1, SENSORS_MAXI_CG_TEMP4, + { SENSORS_MAXI_CG_TEMP5, SENSORS_MAXI_CG_TEMP5_MAX, SENSORS_MAXI_CG_TEMP4_HYST, -1 } }, + { fmtFans_Maxilife, SENSORS_MAXI_CG_FAN1, + { SENSORS_MAXI_CG_FAN1, SENSORS_MAXI_CG_FAN1_MIN, SENSORS_MAXI_CG_FAN1_DIV, -1 } }, + { fmtFans_Maxilife, SENSORS_MAXI_CG_FAN2, + { SENSORS_MAXI_CG_FAN2, SENSORS_MAXI_CG_FAN2_MIN, SENSORS_MAXI_CG_FAN2_DIV, -1 } }, + { fmtFans_Maxilife, SENSORS_MAXI_CG_FAN3, + { SENSORS_MAXI_CG_FAN3, SENSORS_MAXI_CG_FAN3_MIN, SENSORS_MAXI_CG_FAN3_DIV, -1 } }, + { fmtMHz_2, SENSORS_MAXI_CG_PLL, + { SENSORS_MAXI_CG_PLL, SENSORS_MAXI_CG_PLL_MIN, SENSORS_MAXI_CG_PLL_MAX, -1 } }, + { fmtVolts_2, SENSORS_MAXI_CG_VID1, + { SENSORS_MAXI_CG_VID1, SENSORS_MAXI_CG_VID1_MIN, SENSORS_MAXI_CG_VID1_MAX, -1 } }, + { fmtVolts_2, SENSORS_MAXI_CG_VID2, + { SENSORS_MAXI_CG_VID2, SENSORS_MAXI_CG_VID2_MIN, SENSORS_MAXI_CG_VID2_MAX, -1 } }, + { fmtVolts_2, SENSORS_MAXI_CG_VID3, + { SENSORS_MAXI_CG_VID3, SENSORS_MAXI_CG_VID3_MIN, SENSORS_MAXI_CG_VID3_MAX, -1 } }, + { fmtVolts_2, SENSORS_MAXI_CG_VID4, + { SENSORS_MAXI_CG_VID4, SENSORS_MAXI_CG_VID4_MIN, SENSORS_MAXI_CG_VID4_MAX, -1 } }, + { NULL } +}; + +/** ALL **/ + +ChipDescriptor knownChips[] = { + { lm75_names, lm75_features }, + { adm1021_names, adm1021_features }, + { max1617_names, max1617_features }, + { adm9240_names, adm9240_features }, + { lm78_names, lm78_features }, + { sis5595_names, sis5595_features }, + { lm80_names, lm80_features }, + { gl518_names, gl518_features }, + { w83781d_names, w83781d_features }, + { w83782d_names, w83782d_features }, + { w83783s_names, w83783s_features }, + { maxilife_names, maxilife_features }, + { NULL } +}; diff --git a/prog/sensord/sensord.8 b/prog/sensord/sensord.8 new file mode 100644 index 00000000..21c284b3 --- /dev/null +++ b/prog/sensord/sensord.8 @@ -0,0 +1,112 @@ +.\" Copyright 1999 Merlin Hughes +.\" sensord is distributed under the GPL +.\" +.\" Permission is granted to make and distribute verbatim copies of this +.\" manual provided the copyright notice and this permission notice are +.\" preserved on all copies. +.\" +.\" Permission is granted to copy and distribute modified versions of this +.\" manual under the conditions for verbatim copying, provided that the +.\" entire resulting derived work is distributed under the terms of a +.\" permission notice identical to this one +.\" +.\" Since the Linux kernel and libraries are constantly changing, this +.\" manual page may be incorrect or out-of-date. The author(s) assume no +.\" responsibility for errors or omissions, or for damages resulting from +.\" the use of the information contained herein. The author(s) may not +.\" have taken the same level of care in the production of this manual, +.\" which is licensed free of charge, as they might when working +.\" professionally. +.\" +.\" Formatted or processed versions of this manual, if unaccompanied by +.\" the source, must acknowledge the copyright and authors of this work. +.\" +.TH sensord 8 "December 2, 1999" "Version 0.2.0" "Linux System Administration" +.SH NAME +sensord \- Sensor information logging daemon. +.SH SYNOPSIS +.B sensord [ +.I options +.B ] + +.SH DESCRIPTION +.B Sensord +is a daemon that records in a log file the readings of all sensor chips at +an interval of five minutes, or as configured on the command line. + +.B Sensord +knows about certain chips, and outputs nicely formatted readings for them; but +it can also display the information of unknown chips, as long as +.I libsensors +knows about them. + +.SH OPTIONS +.IP "-c config-file" +Specify a configuration file. If no file is specified, the name `sensors.conf' +is used. If the name does not contain a leading directory slash, or this parameter +is not present, the following paths are examined: +.BR /etc , +.BR /usr/lib/sensors , +.BR /usr/local/lib/sensors , +.BR /usr/lib , +.BR /usr/local/lib . +.IP "-i interval" +Specifies the interval, in seconds, between sensor readings. The default +is 300 seconds (5 minutes). +.IP "-l log-file" +Specify the log file to which to append. If no file is specified, the +default log file `/var/log/sensors' is created. Note that this file is +interpreted relative to the root directory `/', not the current directory. +.IP -h +Prints a help message and exits. +.IP -v +Displays the program version and exits. +.SH SIGNALS +Upon receipt of a SIGHUP (see +.BR signal (7) +for details) this daemon closes and reopens its log file. Use this to rotate +log-files: Rename the old log file and then HUP this daemon; it will +commence writing to a new log file. + +Upon receipt of a SIGTERM (see +.BR signal (7) +for details) this daemon should gracefully shut down. +.SH ERRORS +Errors encountered by this daemon are logged to +.BR syslogd (8) +under the program name `sensord', after which the daemon aborts +with a failure note in its own log file. +.SH NOTES +It is expected that all required modules are loaded prior to this daemon +being started. Typically, this is achieved with an entry in +`/etc/rc.d/...' containing +.BR modprobe (1) +commands to load the required modules before starting the daemon. +.SH BUGS +.B Sensord +does not currently support reading alarm events from sensor devices. +.SH FILES +.I /etc/sensors.conf +.RS +The system wide configuration file. See +.BR sensors.conf (5) +for further details. +.RE +.I /var/log/sensors +.RS +The default sensor log file. +.RE +.SH "CONFORMING TO" +lm_sensors-2.x +.SH SEE ALSO +sensors.conf(5) +.SH AUTHORS +.B Sensord +was written by Merlin Hughes . Chip-specific formatting +code was ripped from +.BR sensors +which was written by Frodo Looijaard . + + + + diff --git a/prog/sensord/sensord.c b/prog/sensord/sensord.c new file mode 100644 index 00000000..20af9562 --- /dev/null +++ b/prog/sensord/sensord.c @@ -0,0 +1,375 @@ +/* + * sensord + * + * A daemon that logs all sensor information to /var/log/sensors + * every 5 minutes. + * + * Copyright (c) 1999 Merlin Hughes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/sensors.h" + +#include "sensord.h" + +static char *version = "0.2.0"; + +static char *sensorsCfgPaths[] = { + "/etc", "/usr/lib/sensors", "/usr/local/lib/sensors", "/usr/lib", "/usr/local/lib", NULL +}; +static char *sensorsCfgFile = "sensors.conf"; +static char *sensorsLogFile = "/var/log/sensors"; +static int sleepTime = 5 * 60; + +static int cfgLoaded = 0; +time_t cfgLastModified; + +static FILE *sensorsLog = NULL; + +static volatile sig_atomic_t done = 0; +static volatile sig_atomic_t rotate = 0; + +static void +signalHandler +(int sig) { + signal (sig, signalHandler); + switch (sig) { + case SIGHUP: + rotate = 1; + break; + case SIGTERM: + done = 1; + break; + } +} + +static void +initSignals +(void) { + /* I should use sigaction but... */ + signal (SIGHUP, signalHandler); + signal (SIGTERM, signalHandler); +} + +static char * +now +(void) { + time_t now; + char *str; + int len; + static char buff[1024]; + + time (&now); + str = ctime (&now); + len = strlen (str); + memcpy (buff, str, len - 1); + buff[len - 1] = '\0'; + + return buff; +} + +static int +readUnknownChip +(const sensors_chip_name *chip) { + const sensors_feature_data *sensor; + int index0 = 0, index1 = 0; + int ret = 0; + + while (!done && (ret == 0) && ((sensor = sensors_get_all_features (*chip, &index0, &index1)) != NULL)) { + char *label = NULL; + double value; + + if (sensors_get_label (*chip, sensor->number, &label)) { + syslog (LOG_ERR, "Error getting sensor label: %s/%s", chip->prefix, sensor->name); + ret = 20; + } else if (!(sensor->mode & SENSORS_MODE_R)) { + fprintf (sensorsLog, "\t%s: %s\n", sensor->name, label); + } else if (sensors_get_feature (*chip, sensor->number, &value)) { + syslog (LOG_ERR, "Error getting sensor data: %s/%s", chip->prefix, sensor->name); + ret = 21; + } else { + fprintf (sensorsLog, "\t%s%s: %s: %.2f\n", (sensor->mapping == SENSORS_NO_MAPPING) ? "" : "-", sensor->name, label, value); + } + if (label) + free (label); + } + + return ret; +} + +static int +readKnownChip +(const sensors_chip_name *chip, ChipDescriptor *descriptor) { + FeatureDescriptor *features = descriptor->features; + int index, subindex; + int ret = 0; + + for (index = 0; !done && (ret == 0) && features[index].format; ++ index) { + FeatureDescriptor *feature = features + index; + char *label = NULL; + double values[MAX_DATA]; + + if (sensors_get_label (*chip, feature->labelNumber, &label)) { + syslog (LOG_ERR, "Error getting sensor label: %s/#%d", chip->prefix, feature->labelNumber); + ret = 20; + } + for (subindex = 0; !done && (ret == 0) && (feature->dataNumbers[subindex] >= 0); ++ subindex) { + if (sensors_get_feature (*chip, feature->dataNumbers[subindex], values + subindex)) { + syslog (LOG_ERR, "Error getting sensor data: %s/#%d", chip->prefix, feature->dataNumbers[subindex]); + ret = 21; + } + } + if (!done && (ret == 0)) + fprintf (sensorsLog, "\t%s: %s\n"/*, sensor->name*/, label, feature->format (values)); + if (label) + free (label); + } +} + +static int +readChip +(const sensors_chip_name *chip) { + const char *adapter, *algorithm; + int index, subindex, chipindex = -1; + int ret = 0; + + if (chip->bus == SENSORS_CHIP_NAME_BUS_ISA) + fprintf (sensorsLog, "%s: Chip: %s-isa-%04x\n", now (), chip->prefix, chip->addr); + else + fprintf (sensorsLog, "%s: Chip: %s-i2c-%d-%02x\n", now (), chip->prefix, chip->bus, chip->addr); + adapter = sensors_get_adapter_name (chip->bus); + if (adapter) + fprintf (sensorsLog, "%s: Adapter: %s\n", now (), adapter); + algorithm = sensors_get_algorithm_name (chip->bus); + if (algorithm) + fprintf (sensorsLog, "%s: Algorithm: %s\n", now (), algorithm); + /* assert adapter || algorithm */ + + for (index = 0; knownChips[index].names; ++ index) + for (subindex = 0; knownChips[index].names[subindex]; ++ subindex) + if (!strcmp (chip->prefix, knownChips[index].names[subindex])) + chipindex = index; + + if (chipindex >= 0) + ret = readKnownChip (chip, knownChips + chipindex); + else + ret = readUnknownChip (chip); + + return ret; +} + +static int +readChips +(void) { + const sensors_chip_name *chip; + int index = 0; + int ret = 0; + + fprintf (sensorsLog, "%s: Sense.\n", now ()); + fflush (sensorsLog); + + while (!done && (ret == 0) && ((chip = sensors_get_detected_chips (&index)) != NULL)) { + ret = readChip (chip); + } + + fprintf (sensorsLog, "%s: Done.\n", now ()); + fflush (sensorsLog); + + return ret; +} + +static int +rotateLog +(void) { + int ret = 0; + + if (sensorsLog != NULL) { + fprintf (sensorsLog, "%s: Rotate.\n", now ()); + fclose (sensorsLog); + } + if (!(sensorsLog = fopen (sensorsLogFile, "a"))) { + syslog (LOG_ERR, "Error opening sensors log: %s", sensorsLogFile); + ret = 1; + } else { + fprintf (sensorsLog, "%s: Started.\n", now ()); + fflush (sensorsLog); + } + rotate = 0; + + return ret; +} + +char cfgPath[4096] = ""; + +static int +initSensors +(void) { + struct stat stats; + FILE *cfg = NULL; + int ret = 0; + + if (!cfgPath[0]) { + if (sensorsCfgFile[0] == '/') { + strcpy (cfgPath, sensorsCfgFile); + } else { + int index; + for (index = 0; sensorsCfgPaths[index]; ++ index) { + sprintf (cfgPath, "%s/%s", sensorsCfgPaths[index], sensorsCfgFile); + if (stat (cfgPath, &stats) == 0) + break; + } + if (!sensorsCfgPaths[index]) { + syslog (LOG_ERR, "Error locating sensors configuration: %s", sensorsCfgFile); + return 9; + } + } + } + + if (stat (cfgPath, &stats) < 0) { + syslog (LOG_ERR, "Error stating sensors configuration: %s", cfgPath); + ret = 10; + } else if (!cfgLoaded || (difftime (stats.st_mtime, cfgLastModified) > 0.0)) { + if (!(cfg = fopen (cfgPath, "r"))) { + syslog (LOG_ERR, "Error opening sensors configuration: %s", cfgPath); + ret = 11; + } else if (sensors_init (cfg)) { + syslog (LOG_ERR, "Error loading sensors configuration: %s", cfgPath); + ret = 11; + } else { + cfgLastModified = stats.st_mtime; + cfgLoaded = 1; + } + if (cfg) + fclose (cfg); + } + + return ret; +} + +static int +sensord +(void) { + int ret = 0; + + openlog ("sensord", 0, LOG_DAEMON); + + initSignals (); + + while (!done && (ret == 0)) { + if (rotate || (sensorsLog == NULL)) + ret = rotateLog (); + if (ret == 0) + ret = initSensors (); + if (ret == 0) + ret = readChips (); + if (!done && !rotate && (ret == 0)) + sleep (sleepTime); + } + + if (cfgLoaded) + sensors_cleanup (); + + if (sensorsLog) { + fprintf (sensorsLog, "%s: %s.\n", now (), ret ? "Failed" : "Stopped"); + fclose (sensorsLog); + } + + closelog (); + + return ret; +} + +static void +syntaxError +(void) { + printf ("Syntax: sensord [options]\n" + "\t-i -- seconds between samples (default 300)\n" + "\t-l -- log file (default /var/log/sensors)\n" + "\t-c -- configuration file (default /etc/sensors.conf)\n" + "\t-v -- display version and exit\n" + "\t-h -- display help and exit\n"); + exit (EXIT_FAILURE); +} + +static void +parseArgs +(int argc, char **argv) { + int i; + + for (i = 1; i < argc; ++ i) { + if (!strcmp (argv[i], "-i") && (i < argc - 1)) { + sleepTime = atoi (argv[++ i]); + } else if (!strcmp (argv[i], "-l") && (i < argc - 1)) { + sensorsLogFile = argv[++ i]; + } else if (!strcmp (argv[i], "-c") && (i < argc - 1)) { + sensorsCfgFile = argv[++ i]; + } else if (!strcmp (argv[i], "-v")) { + printf ("sensord version %s\n", version); + exit (EXIT_SUCCESS); + } else { + syntaxError (); + } + } +} + +static void +daemonize +(void) { + int pid; + + if ((pid = fork ()) == -1) { + fprintf (stderr, "Error forking daemon\n"); + exit (EXIT_FAILURE); + } else if (pid != 0) { + exit (EXIT_SUCCESS); + } + + if (chdir ("/") < 0) { + perror ("chdir()"); + exit (EXIT_FAILURE); + } + + if (setsid () < 0) { + perror ("setsid()"); + exit (EXIT_FAILURE); + } + + close (STDIN_FILENO); + close (STDOUT_FILENO); + close (STDERR_FILENO); +} + +int +main +(int argc, char **argv) { + parseArgs (argc, argv); + daemonize (); + return sensord (); +} diff --git a/prog/sensord/sensord.h b/prog/sensord/sensord.h new file mode 100644 index 00000000..aa080a81 --- /dev/null +++ b/prog/sensord/sensord.h @@ -0,0 +1,39 @@ +/* + * sensord + * + * A daemon that logs all sensor information to /var/log/sensors + * every 5 minutes. + * + * Copyright (c) 1999 Merlin Hughes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define MAX_DATA 5 + +typedef char * (*FormatterFN) (double[]); + +typedef struct { + FormatterFN format; + int labelNumber; + int dataNumbers[MAX_DATA + 1]; +} FeatureDescriptor; + +typedef struct { + char **names; + FeatureDescriptor *features; +} ChipDescriptor; + +extern ChipDescriptor knownChips[];