Files
lm-sensors/kernel/chips/bmcsensors.c
T
Mark D. Studebaker b0295fe904 move initialization to kernel thread to fix oops
Ticket #1573

--------------

Another update for his issue [kernel panic triggered by bmcsensors}:

The source of the problem are the non-atomic kernel mallocs for device
registration called from an interrupt context - this doesn't seem to be
supported and as observed can crash the kernel.

A possible solution  is to create a kernel thread on initialisation,
leave that thread hanging round while the device scan runs, and finaly
signal the init thread when device scan is complete to finish
initialisation be registing the sensors found during scan.

A patch implementing such a scheme was created by Yvon JEGOU, I'll post
it here again.

Thanks, Martin

--------------

Hi Mark,


>> I don't fully understand... sorry if I'm slow.
>> is the problem doing mallocs from an interrupt context or non-atomic
>> mallocs?
>> what's non-atomic about the malloc?
>> how did we get in an interrupt context?


Being no kernel programmer this is fairly hard form me to answer; lets
see how far I get

The problem is non-atomic kmallocs from an interrupt context.

Non-atomic refers to the flags specified in the kmalloc call (GFP_ATOMIC
vs. GFP_KERNEL) (Ref. http://lwn.net/Articles/22909/)

Scanning for sensors is triggered by sending an ipmi message in
bmcsensors_reserve_sdr. Building the list of sensors and finally
creating the required proc entries happens on subsequent reception of
ipmi messages in the ipmi call-back. If I understand correctly how
things interact, that would be in an interrupt context.

Where bmcsensors blows up when trying to register lots of sensors is the

-> bmcsensors_command (which is the i2c-ipmi call-back)
-> bmcsensors_msg_handler
-> bmcsensors_rcv_msg
-> bmcsensors_rcv_sdr_msg
-> bmcsensors_build_proc_table
-> i2c_register_entry
-> create_proc_entry
-> proc_create
-> kmalloc

sequence.

My limited understanding of kernel programming is that if memory is
allocated in an interrupt context, GFP_ATOMIC must be used.

While the kmallocs done directly by bmcsensors_build_proc_table would be
fixable, the calls in proc_create_entry are obviously not changeable.
This makes proc_create and its callers unsafe for use in an interrupt
context.

The patch works around this limitation by creating a separate kernel
thread from sm_bmcsensors_init and calling bmcsensors_build_proc_table
from this thread after scanning for sesnsors has finished.

Hope I've managed to adequately explain what I think is wrong.

Bye, Martin


git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@2482 7894878c-1315-0410-8ee3-d5d059ff63e0
2004-04-27 01:10:49 +00:00

1112 lines
31 KiB
C

/*
bmcsensors.c - Part of lm_sensors, Linux kernel modules
for hardware monitoring
Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
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 <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/ipmi.h>
#include <linux/init.h>
#include <asm/io.h>
/* for kernel thread ... */
#include <asm/atomic.h>
#include <asm/semaphore.h>
#include <linux/smp_lock.h>
#include <asm/errno.h>
#include "version.h"
/*
#define DEBUG 1
*/
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
SENSORS_INSMOD_1(bmcsensors);
struct bmcsensors_data {
struct semaphore lock;
int sysctl_id;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 alarms;
};
static int bmcsensors_attach_adapter(struct i2c_adapter *adapter);
static int bmcsensors_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int bmcsensors_detach_client(struct i2c_client *client);
static int bmcsensors_command(struct i2c_client *client, unsigned int cmd,
void *arg);
static void bmcsensors_update_client(struct i2c_client *client);
static void bmcsensors_reserve_sdr(void);
static void bmc_do_pause(unsigned int amount); /* YJ for debug */
static void bmcsensors_all(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
#if 0
static void bmcsensors_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void bmcsensors_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void bmcsensors_pwm(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
#endif
static void bmcsensors_get_sdr(u16 resid, u16 record, u8 offset);
static void bmcsensors_get_reading(struct i2c_client *client, int i);
static struct i2c_driver bmcsensors_driver = {
.owner = THIS_MODULE,
.name = "BMC Sensors driver",
.id = I2C_DRIVERID_BMCSENSORS,
.flags = I2C_DF_NOTIFY,
.attach_adapter = bmcsensors_attach_adapter,
.detach_client = bmcsensors_detach_client,
.command = bmcsensors_command,
};
static struct bmcsensors_data bmc_data;
struct i2c_client bmc_client = {
"BMC Sensors",
1, /* fake should be 0 */
0,
0,
NULL, /* adapter */
&bmcsensors_driver,
& bmc_data,
0
};
static int bmcsensors_initialized;
#define MAX_SDR_ENTRIES 100
#define SDR_LIMITS 8
#define SDR_MAX_ID_LENGTH 16
#define SDR_MAX_UNPACKED_ID_LENGTH ((SDR_MAX_ID_LENGTH * 4 / 3) + 2)
struct sdrdata {
/* reverse lookup from sysctl */
int sysctl;
/* retrieved from SDR, not expected to change */
u8 stype;
u8 number;
u8 capab;
u16 thresh_mask;
u8 format;
u8 linear;
s16 m;
s16 b;
u8 k;
u8 nominal;
u8 limits[SDR_LIMITS];
int lim1, lim2; /* index into limits for reported upper and lower limit */
u8 lim1_write, lim2_write;
u8 string_type;
u8 id_length;
u8 id[SDR_MAX_ID_LENGTH];
/* retrieved from reading */
u8 reading;
u8 status;
u8 thresholds;
};
static struct sdrdata sdrd[MAX_SDR_ENTRIES];
static int sdrd_count;
/* -- SENSORS SYSCTL START -- */
#define BMC_SYSCTL_IN1 1000
#define BMC_SYSCTL_TEMP1 1100
#define BMC_SYSCTL_CURR1 1200
#define BMC_SYSCTL_FAN1 1300
#define BMC_SYSCTL_ALARMS 5000
/* -- SENSORS SYSCTL END -- */
#define MAX_PROC_ENTRIES (MAX_SDR_ENTRIES + 5)
#define MAX_PROCNAME_SIZE 8
static ctl_table *bmcsensors_dir_table;
static char *bmcsensors_proc_name_pool;
#define IPMI_SDR_SIZE 67
static int ipmi_sdr_partial_size = IPMI_SDR_SIZE;
static struct ipmi_msg tx_message; /* send message */
static unsigned char tx_msg_data[IPMI_MAX_MSG_LENGTH + 50];
static unsigned char rx_msg_data[IPMI_MAX_MSG_LENGTH + 50]; /* sloppy */
static int rx_msg_data_offset;
static int msgid; /* API to IPMI is long but we'll let i2c-ipmi convert */
static u16 resid;
static u16 nextrecord;
static int errorcount;
enum states {STATE_INIT, STATE_RESERVE, STATE_SDR, STATE_SDRPARTIAL,
STATE_READING, STATE_UNCANCEL, STATE_PROCTABLE, STATE_DONE};
/* YJ : added extra state STATE_PROCTABLE for thread activity */
static int state;
static int receive_counter;
/* IPMI Message defs */
/* Network Function Codes */
#define IPMI_NETFN_SENSOR 0x04
#define IPMI_NETFN_STORAGE 0x0A
/* Commands */
#define IPMI_RESERVE_SDR 0x22
#define IPMI_GET_SDR 0x23
#define IPMI_GET_SENSOR_STATE_READING 0x2D
/* SDR defs */
#define STYPE_TEMP 0x01
#define STYPE_VOLT 0x02
#define STYPE_CURR 0x03
#define STYPE_FAN 0x04
/* do we really need maximums per-type? */
#define STYPE_MAX 4 /* the last sensor type we are interested in */
static u8 bmcs_count[STYPE_MAX + 1];
static const u8 bmcs_max[STYPE_MAX + 1] = {0, 20, 40, 20, 20};
/* YJ: on poweredge 1750, we need ^^ */
/************************************/
/* YJ ... */
static int thread_pid= 0;
static DECLARE_MUTEX_LOCKED(bmc_sem);
/* ... YJ */
/************************************/
/* unpack based on string type, convert to normal, null terminate */
static void ipmi_sprintf(u8 * to, u8 * from, u8 type, u8 length)
{
static const u8 *bcdplus = "0123456789 -.:,_";
int i;
switch (type) {
case 0: /* unicode */
for(i = 0; i < length; i++)
*to++ = *from++ & 0x7f;
*to = 0;
break;
case 1: /* BCD Plus */
for(i = 0; i < length; i++)
*to++ = bcdplus[*from++ & 0x0f];
*to = 0;
break;
case 2: /* packed ascii */ /* if not a mult. of 3 this will run over */
for(i = 0; i < length; i += 3) {
*to++ = *from & 0x3f;
*to++ = *from++ >> 6 | ((*from & 0xf) << 2);
*to++ = *from++ >> 4 | ((*from & 0x3) << 4);
*to++ = (*from++ >> 2) & 0x3f;
}
*to = 0;
break;
case 3: /* normal */
if(length > 1)
memcpy(to, from, length);
to[length] = 0;
break;
}
}
static const char * threshold_text[] = {
"upper non-recoverable threshold",
"upper critical threshold",
"upper non-critical threshold",
"lower non-recoverable threshold",
"lower critical threshold",
"lower non-critical threshold",
"positive-going hysteresis",
"negative-going hysteresis" /* unused */
};
/* select two out of the 8 possible readable thresholds, and place indexes into the limits
array into lim1 and lim2. Set writable flags */
static void bmcsensors_select_thresholds(struct sdrdata * sd)
{
u8 capab = sd->capab;
u16 mask = sd->thresh_mask;
sd->lim1 = -1;
sd->lim2 = -1;
sd->lim1_write = 0;
sd->lim2_write = 0;
if(((capab & 0x0c) == 0x04) || /* readable thresholds ? */
((capab & 0x0c) == 0x08)) {
/* select upper threshold */
if(mask & 0x10) { /* upper crit */
sd->lim1 = 1;
if((capab & 0x0c) == 0x08 && (mask & 0x1000))
sd->lim1_write = 1;
}
else if(mask & 0x20) { /* upper non-recov */
sd->lim1 = 0;
if((capab & 0x0c) == 0x08 && (mask & 0x2000))
sd->lim1_write = 1;
}
else if(mask & 0x08) { /* upper non-crit */
sd->lim1 = 2;
if((capab & 0x0c) == 0x08 && (mask & 0x0800))
sd->lim1_write = 1;
}
/* select lower threshold */
if((((capab & 0x30) == 0x10) || /* readable ? */
((capab & 0x30) == 0x20)) && /* pos hyst */
sd->stype == STYPE_TEMP)
sd->lim2 = 6;
else if(mask & 0x02) { /* lower crit */
sd->lim2 = 4;
if((capab & 0x0c) == 0x08 && (mask & 0x0200))
sd->lim2_write = 1;
}
else if(mask & 0x04) { /* lower non-recov */
sd->lim2 = 3;
if((capab & 0x0c) == 0x08 && (mask & 0x0400))
sd->lim2_write = 1;
}
else if(mask & 0x01) { /* lower non-crit */
sd->lim2 = 5;
if((capab & 0x0c) == 0x08 && (mask & 0x0100))
sd->lim2_write = 1;
}
}
/* fixme swap lim1/lim2 if m < 0 */
if(sd->lim1 >= 0)
printk(KERN_INFO "bmcsensors.o: using %s for upper limit\n",
threshold_text[sd->lim1]);
#ifdef DEBUG
else
printk(KERN_INFO "bmcsensors.o: no readable upper limit\n");
#endif
if(sd->lim2 >= 0)
printk(KERN_INFO "bmcsensors.o: using %s for lower limit\n",
threshold_text[sd->lim2]);
#ifdef DEBUG
else
printk(KERN_INFO "bmcsensors.o: no readable lower limit\n");
#endif
}
/* After we have received all the SDR entries and picked out the ones
we are interested in, build a table of the /proc entries and register with i2c.
*/
static void bmcsensors_build_proc_table()
{
int i;
int temps = 0, volts = 0, currs = 0, fans = 0;
u8 id[SDR_MAX_UNPACKED_ID_LENGTH];
printk(KERN_INFO "bmcsensors.o: building proc table\n");
if(!(bmcsensors_dir_table = kmalloc((sdrd_count + 1) * sizeof(struct ctl_table), GFP_KERNEL))) {
printk(KERN_ERR "bmcsensors.o: no memory\n");
return; /* do more than this */ /* ^^ add 1 or more for alarms, etc. */
}
if(!(bmcsensors_proc_name_pool = kmalloc((sdrd_count + 0) * MAX_PROCNAME_SIZE, GFP_KERNEL))) {
kfree(bmcsensors_dir_table);
printk(KERN_ERR "bmcsensors.o: no memory\n");
return; /* do more than this */ /* ^^ add 1 or more for alarms, etc. */
}
for(i = 0; i < sdrd_count; i++) {
bmcsensors_dir_table[i].procname = bmcsensors_proc_name_pool + (i * MAX_PROCNAME_SIZE);
bmcsensors_dir_table[i].data = NULL;
bmcsensors_dir_table[i].maxlen = 0;
bmcsensors_dir_table[i].child = NULL;
bmcsensors_dir_table[i].proc_handler = &i2c_proc_real;
bmcsensors_dir_table[i].strategy = &i2c_sysctl_real;
bmcsensors_dir_table[i].de = NULL;
switch(sdrd[i].stype) {
case(STYPE_TEMP) :
bmcsensors_dir_table[i].ctl_name = BMC_SYSCTL_TEMP1 + temps;
sprintf((char *)bmcsensors_dir_table[i].procname, "temp%d", ++temps);
bmcsensors_dir_table[i].extra1 = &bmcsensors_all;
break;
case(STYPE_VOLT) :
bmcsensors_dir_table[i].ctl_name = BMC_SYSCTL_IN1 + volts;
sprintf((char *)bmcsensors_dir_table[i].procname, "in%d", ++volts);
bmcsensors_dir_table[i].extra1 = &bmcsensors_all;
break;
case(STYPE_CURR) :
bmcsensors_dir_table[i].ctl_name = BMC_SYSCTL_CURR1 + currs;
sprintf((char *)bmcsensors_dir_table[i].procname, "curr%d", ++currs);
bmcsensors_dir_table[i].extra1 = &bmcsensors_all;
break;
case(STYPE_FAN) :
bmcsensors_dir_table[i].ctl_name = BMC_SYSCTL_FAN1 + fans;
sprintf((char *)bmcsensors_dir_table[i].procname, "fan%d", ++fans);
bmcsensors_dir_table[i].extra1 = &bmcsensors_all;
break;
default: /* ?? */
printk(KERN_INFO "bmcsensors.o: unk stype\n");
continue;
}
sdrd[i].sysctl = bmcsensors_dir_table[i].ctl_name;
printk(KERN_INFO "bmcsensors.o: registering sensor %d: (type 0x%.2x) "
"(fmt=%d; m=%d; b=%d; k1=%d; k2=%d; cap=0x%.2x; mask=0x%.4x)\n",
i, sdrd[i].stype, sdrd[i].format,
sdrd[i].m, sdrd[i].b,sdrd[i].k & 0xf, sdrd[i].k >> 4,
sdrd[i].capab, sdrd[i].thresh_mask);
if(sdrd[i].id_length > 0) {
ipmi_sprintf(id, sdrd[i].id, sdrd[i].string_type, sdrd[i].id_length);
printk(KERN_INFO "bmcsensors.o: sensors.conf: label %s \"%s\"\n",
bmcsensors_dir_table[i].procname, id);
}
bmcsensors_select_thresholds(sdrd + i);
if(sdrd[i].linear != 0) {
printk(KERN_INFO
"bmcsensors.o: sensor %d: nonlinear function 0x%.2x unsupported, expect bad results\n",
i, sdrd[i].linear);
}
if((sdrd[i].format & 0x03) == 0x02) {
printk(KERN_INFO
"bmcsensors.o: sensor %d: 1's complement format unsupported, expect bad results\n",
i);
} else if((sdrd[i].format & 0x03) == 0x03) {
printk(KERN_INFO
"bmcsensors.o: sensor %d: threshold sensor only, no readings available",
i);
}
if(sdrd[i].lim1_write || sdrd[i].lim2_write)
bmcsensors_dir_table[i].mode = 0644;
else
bmcsensors_dir_table[i].mode = 0444;
}
bmcsensors_dir_table[sdrd_count].ctl_name = 0;
if ((i = i2c_register_entry(&bmc_client, "bmc",
bmcsensors_dir_table)) < 0) {
printk(KERN_INFO "bmcsensors.o: i2c registration failed.\n");
kfree(bmcsensors_dir_table);
kfree(bmcsensors_proc_name_pool);
return;
}
bmcsensors_initialized = 3;
bmc_data.sysctl_id = i;
printk(KERN_INFO "bmcsensors.o: %d reservations cancelled\n", errorcount);
printk(KERN_INFO "bmcsensors.o: registered %d temp, %d volt, %d current, %d fan sensors\n",
temps, volts, currs, fans);
/*
This completes the initialization. The first userspace read
of a /proc value will force the first
bmcsensors_update_client() which starts the
reading of the sensors themselves via IPMI messages.
*/
}
/* Process a sensor reading response */
static int bmcsensors_rcv_reading_msg(struct ipmi_msg *msg)
{
if(receive_counter >= sdrd_count) {
/* shouldn't happen */
receive_counter = 0;
return STATE_DONE;
}
sdrd[receive_counter].reading = msg->data[1];
sdrd[receive_counter].status = msg->data[2];
sdrd[receive_counter].thresholds = msg->data[3];
#ifdef DEBUG
printk(KERN_DEBUG "bmcsensors.o: sensor %d (type %d) reading %d\n",
receive_counter, sdrd[receive_counter].stype, msg->data[1]);
#endif
if(++receive_counter >= sdrd_count) {
receive_counter = 0;
return STATE_DONE;
}
/* don't really need to pass client */
bmcsensors_get_reading(&bmc_client, receive_counter);
return STATE_READING;
}
/* Process an SDR response, save the SDR's we like in the sdrd table */
static int bmcsensors_rcv_sdr_msg(struct ipmi_msg *msg, int state)
{
u16 record;
int type;
int stype;
int id_length;
int i;
int rstate = STATE_SDR;
unsigned char * data;
u8 id[SDR_MAX_UNPACKED_ID_LENGTH];
if(msg->data[0] != 0) {
/* cut request in half and try again */
ipmi_sdr_partial_size /= 2;
if(ipmi_sdr_partial_size < 8) {
printk(KERN_INFO "bmcsensors.o: IPMI buffers too small, giving up\n");
up (&bmc_sem); /* should wait for thread exit ! */
return STATE_DONE;
}
#ifdef DEBUG
printk(KERN_INFO "bmcsensors.o: Reducing SDR request size to %d\n", ipmi_sdr_partial_size);
#endif
bmcsensors_get_sdr(0, 0, 0);
return STATE_SDR;
}
if(ipmi_sdr_partial_size < IPMI_SDR_SIZE) {
if(rx_msg_data_offset == 0) {
memcpy(rx_msg_data, msg->data, ipmi_sdr_partial_size + 3);
rx_msg_data_offset = ipmi_sdr_partial_size + 3;
} else {
memcpy(rx_msg_data + rx_msg_data_offset, msg->data + 3, ipmi_sdr_partial_size);
rx_msg_data_offset += ipmi_sdr_partial_size;
}
if(rx_msg_data_offset > rx_msg_data[7] + 7) {
/* got last chunk */
rx_msg_data_offset = 0;
data = rx_msg_data;
} else {
/* get more */
record = (rx_msg_data[4] << 8) | rx_msg_data[3];
bmcsensors_get_sdr(resid, record, rx_msg_data_offset - 3);
return STATE_SDR;
}
} else {
data = msg->data; /* got it in one chunk */
}
nextrecord = (data[2] << 8) | data[1];
/* printk(KERN_INFO "bmcsensors.o: nextrecord %d \n", nextrecord); */
type = data[6];
if(type == 1 || type == 2) { /* known SDR type */
/*
version = data[5];
owner = data[8];
lun = data[9];
entity = data[11];
init = data[13];
*/
stype = data[15];
if(stype <= STYPE_MAX) { /* known sensor type */
if(bmcs_count[stype] >= bmcs_max[stype]) {
if(bmcs_max[stype] > 0)
printk(KERN_INFO
"bmcsensors.o: Limit of %d exceeded for sensor type 0x%x\n",
bmcs_max[stype], stype);
#ifdef DEBUG
else
printk(KERN_INFO
"bmcsensors.o: Ignoring unsupported sensor type 0x%x\n",
stype);
#endif
} else if(sdrd_count >= MAX_SDR_ENTRIES) {
printk(KERN_INFO
"bmcsensors.o: Limit of %d exceeded for total sensors\n",
MAX_SDR_ENTRIES);
nextrecord = 0xffff;
} else if(data[16] != 0x01) {
if(type == 1)
ipmi_sprintf(id, &data[51], data[50] >> 6, data[50] & 0x1f);
else
ipmi_sprintf(id, &data[35], data[34] >> 6, data[34] & 0x1f);
printk(KERN_INFO
"bmcsensors.o: skipping non-threshold sensor \"%s\"\n",
id);
} else {
/* add entry to sdrd table */
sdrd[sdrd_count].stype = stype;
sdrd[sdrd_count].number = data[10];
sdrd[sdrd_count].capab = data[14];
sdrd[sdrd_count].thresh_mask = (((u16) data[22]) << 8) | data[21];
if(type == 1) {
sdrd[sdrd_count].format = data[24] >> 6;
sdrd[sdrd_count].linear = data[26] & 0x7f;
sdrd[sdrd_count].m = data[27];
sdrd[sdrd_count].m |= ((u16) (data[28] & 0xc0)) << 2;
if(sdrd[sdrd_count].m & 0x0200)
sdrd[sdrd_count].m |= 0xfc00; /* sign extend */
sdrd[sdrd_count].b = data[29];
sdrd[sdrd_count].b |= ((u16) (data[30] & 0xc0)) << 2;
if(sdrd[sdrd_count].b & 0x0200)
sdrd[sdrd_count].b |= 0xfc00; /* sign extend */
sdrd[sdrd_count].k = data[32];
sdrd[sdrd_count].nominal = data[34];
for(i = 0; i < SDR_LIMITS; i++) /* assume readable */
sdrd[sdrd_count].limits[i] = data[39 + i];
sdrd[sdrd_count].string_type = data[50] >> 6;
id_length = data[50] & 0x1f;
memcpy(sdrd[sdrd_count].id, &data[51], id_length);
sdrd[sdrd_count].id_length = id_length;
} else {
sdrd[sdrd_count].m = 1;
sdrd[sdrd_count].b = 0;
sdrd[sdrd_count].k = 0;
sdrd[sdrd_count].string_type = data[34] >> 6;
id_length = data[34] & 0x1f;
if(id_length > 0)
memcpy(sdrd[sdrd_count].id, &data[35], id_length);
sdrd[sdrd_count].id_length = id_length;
/* limits?? */
}
bmcs_count[stype]++;
sdrd_count++;
if (sdrd_count>=MAX_SDR_ENTRIES) nextrecord = 0xffff; /*YJ*/
}
}
#ifdef DEBUG
/* peek at the other SDR types */
} else if(type == 0x10 || type == 0x11 || type == 0x12) {
ipmi_sprintf(id, data + 19, data[18]>>6, data[18] & 0x1f);
if(type == 0x10) {
printk(KERN_INFO "bmcsensors.o: Generic Device acc=0x%x; slv=0x%x; lun=0x%x; type=0x%x; \"%s\"\n",
data[8], data[9], data[10], data[13], id);
} else if(type == 0x11) {
printk(KERN_INFO "bmcsensors.o: FRU Device acc=0x%x; slv=0x%x; log=0x%x; ch=0x%x; type=0x%x; \"%s\"\n",
data[8], data[9], data[10], data[11], data[13], id);
} else {
printk(KERN_INFO "bmcsensors.o: Mgmt Ctllr Device slv=0x%x; \"%s\"\n",
data[8], id);
}
} else if(type == 0x14) {
printk(KERN_INFO "bmcsensors.o: Message Channel Info Records:\n");
for(i = 0; i < 8; i++) {
printk(KERN_INFO "bmcsensors.o: Channel %d info 0x%x\n",
i, data[9 + i]);
}
} else {
printk(KERN_INFO "bmcsensors.o: Skipping SDR type 0x%x\n", type);
#endif
}
if (nextrecord>=6224) {
nextrecord = 0xffff; /*YJ stop sensor scan on poweredge 1750 */
}
if(nextrecord == 0xFFFF) {
if(sdrd_count == 0) {
printk(KERN_INFO "bmcsensors.o: No recognized sensors found.\n");
/* unregister?? */
rstate = STATE_DONE;
up (&bmc_sem); /* should wait for thread exit !!! */
} else {
/* YJ ...*/
printk(KERN_INFO "bmcsensors.o: all sensors detected\n");
rstate = STATE_PROCTABLE;
/* YJ bmcsensors_build_proc_table() call by thread */
/* ... YJ */
}
} else {
bmcsensors_get_sdr(0, nextrecord, 0);
}
return rstate;
}
/* Process incoming messages based on internal state */
static void bmcsensors_rcv_msg(struct ipmi_msg *msg)
{
switch(state) {
case STATE_INIT:
case STATE_RESERVE:
resid = (((u16)msg->data[2]) << 8) | msg->data[1];
#ifdef DEBUG
printk(KERN_DEBUG "bmcsensors.o: Got first resid 0x%.4x\n", resid);
#endif
bmcsensors_get_sdr(0, 0, 0);
state = STATE_SDR;
break;
case STATE_SDR:
case STATE_SDRPARTIAL:
state = bmcsensors_rcv_sdr_msg(msg, state);
/*YJ ...*/
if (state==STATE_PROCTABLE){
#ifdef DEBUG
printk(KERN_DEBUG "releasing thread\n");
#endif
up (&bmc_sem);
}
/*YJ bmcsensors_build_proc_table() called by thread */
/* ... YJ */
break;
case STATE_READING:
state = bmcsensors_rcv_reading_msg(msg);
break;
case STATE_UNCANCEL:
resid = (((u16)msg->data[2]) << 8) | msg->data[1];
#ifdef DEBUG
printk(KERN_DEBUG "bmcsensors.o: Got new resid 0x%.4x\n", resid);
#endif
rx_msg_data_offset = 0;
bmcsensors_get_sdr(0, nextrecord, 0);
state = STATE_SDR;
break;
case STATE_DONE:
case STATE_PROCTABLE:
break;
default:
state = STATE_INIT;
}
}
/* Incoming message handler */
static void bmcsensors_msg_handler(struct ipmi_recv_msg *msg,
void * handler_data)
{
if(state == STATE_SDR && msg->msg.data[0] == 0xc5) {
/* )(*&@(*&#@$ reservation cancelled, get new resid */
if(++errorcount > 275) {
printk(KERN_ERR
"bmcsensors.o: Too many reservations cancelled, giving up\n");
state = STATE_DONE;
up (&bmc_sem); /* YJ : should make sure thread exited ! */
} else {
#ifdef DEBUG
printk(KERN_DEBUG
"bmcsensors.o: resid 0x%04x cancelled, getting new one\n", resid);
#endif
bmcsensors_reserve_sdr();
state = STATE_UNCANCEL;
}
} else if (msg->msg.data[0] != 0 && msg->msg.data[0] != 0xca &&
msg->msg.data[0] != 0xce) {
/* YJ : accept 0xce */
printk(KERN_ERR
"bmcsensors.o: Error 0x%x on cmd 0x%x/0x%x; state = %d; probably fatal.\n",
msg->msg.data[0], msg->msg.netfn & 0xfe, msg->msg.cmd, state);
} else {
bmcsensors_rcv_msg(&(msg->msg));
}
ipmi_free_recv_msg(msg);
}
/* callback from i2c-ipmi */
static int bmcsensors_command(struct i2c_client *client, unsigned int cmd, void *arg)
{
bmcsensors_msg_handler((struct ipmi_recv_msg *) arg, NULL);
return 0;
}
/************** Message Sending **************/
/* Send an IPMI message */
static void bmcsensors_send_message(struct ipmi_msg * msg)
{
#ifdef DEBUG
printk(KERN_INFO "bmcsensors.o: Send BMC msg, cmd: 0x%x\n",
msg->cmd);
#endif
bmc_client.adapter->algo->slave_send((struct i2c_adapter *) &bmc_client,
(char *) msg, msgid++);
}
/* Compose and send a "reserve SDR" message */
static void bmcsensors_reserve_sdr(void)
{
tx_message.netfn = IPMI_NETFN_STORAGE;
tx_message.cmd = IPMI_RESERVE_SDR;
tx_message.data_len = 0;
tx_message.data = NULL;
printk(KERN_INFO "bmcsensors.o: reserve_sdr...\n");
bmcsensors_send_message(&tx_message);
}
/* Componse and send a "get SDR" message */
static void bmcsensors_get_sdr(u16 res_id, u16 record, u8 offset)
{
#ifdef DEBUG
printk(KERN_DEBUG "bmcsensors.o: Get SDR 0x%x 0x%x 0x%x\n",
res_id, record, offset);
#endif
tx_message.netfn = IPMI_NETFN_STORAGE;
tx_message.cmd = IPMI_GET_SDR;
tx_message.data_len = 6;
tx_message.data = tx_msg_data;
tx_msg_data[0] = res_id & 0xff;
tx_msg_data[1] = res_id >> 8;
tx_msg_data[2] = record & 0xff;
tx_msg_data[3] = record >> 8;
tx_msg_data[4] = offset;
tx_msg_data[5] = ipmi_sdr_partial_size;
bmcsensors_send_message(&tx_message);
}
/* Compose and send a "get sensor reading" message */
static void bmcsensors_get_reading(struct i2c_client *client, int i)
{
tx_message.netfn = IPMI_NETFN_SENSOR;
tx_message.cmd = IPMI_GET_SENSOR_STATE_READING;
tx_message.data_len = 1;
tx_message.data = tx_msg_data;
tx_msg_data[0] = sdrd[i].number;
bmcsensors_send_message(&tx_message);
}
/**************** Initialization ****************/
static int bmcsensors_attach_adapter(struct i2c_adapter *adapter)
{
printk(KERN_INFO "bmcsensors.o: attach_adapter...\n");
if(adapter->algo->id != I2C_ALGO_IPMI){
printk(KERN_INFO "bmcsensors.o: attach_adapter, expected 0x%x, got 0x%x\n", I2C_ALGO_IPMI, adapter->algo->id);
return 0;
}
if(bmcsensors_initialized >= 2) {
printk(KERN_INFO "bmcsensors.o: Additional IPMI adapter not supported\n");
return 0;
}
return bmcsensors_detect(adapter, 0, 0, 0);
}
static int bmcsensors_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int err, i;
bmc_client.id = 0;
bmc_client.adapter = adapter;
bmc_data.valid = 0;
if ((err = i2c_attach_client(&bmc_client))) {
printk(KERN_ERR "attach client error in bmcsensors_detect()\n");
return err;
}
bmcsensors_initialized = 2;
state = STATE_INIT;
sdrd_count = 0;
receive_counter = 0;
rx_msg_data_offset = 0;
errorcount = 0;
ipmi_sdr_partial_size = IPMI_SDR_SIZE;
for(i = 0; i <= STYPE_MAX; i++)
bmcs_count[i] = 0;
/* send our first message, which kicks things off */
printk(KERN_INFO "bmcsensors.o: Registered client, scanning for sensors...\n");
bmcsensors_reserve_sdr();
/* don't call i2c_register_entry until we scan the SDR's */
return 0;
}
static int bmcsensors_detach_client(struct i2c_client *client)
{
int err;
if(bmcsensors_initialized >= 3) {
kfree(bmcsensors_dir_table);
kfree(bmcsensors_proc_name_pool);
i2c_deregister_entry(((struct bmcsensors_data *) (client->data))->
sysctl_id);
}
if ((err = i2c_detach_client(client))) {
/*
printk
("bmcsensors.o: Client deregistration failed, client not detached.\n");
*/
return err;
}
bmcsensors_initialized = 1;
return 0;
}
static void bmc_do_pause(unsigned int amount)
{
current->state = TASK_INTERRUPTIBLE;
schedule_timeout(amount);
}
static void bmcsensors_update_client(struct i2c_client *client)
{
struct bmcsensors_data *data = client->data;
int j = 0;
/*
down(&data->update_lock);
*/
/* if within 3 seconds you get old data */
if ((jiffies - data->last_updated > 3 * HZ) ||
(jiffies < data->last_updated) || !data->valid) {
/* don't start an update cycle if one already in progress */
if(state != STATE_READING) {
state = STATE_READING;
#ifdef DEBUG
printk(KERN_DEBUG "bmcsensors.o: starting update\n", j);
#endif
bmcsensors_get_reading(client, 0);
}
/* wait 4 seconds max */
while(state == STATE_READING && j++ < 100)
bmc_do_pause(HZ / 25);
#ifdef DEBUG
printk("bmcsensors.o: update complete; j = %d\n", j);
#endif
data->last_updated = jiffies;
data->valid = 1;
}
/*
up(&data->update_lock);
*/
}
/************* /proc callback helper functions *********/
/* need better way to map from sysctl to sdrd record number */
static struct sdrdata * find_sdrd(int sysctl)
{
int i;
for(i = 0; i < sdrd_count; i++)
if(sdrd[i].sysctl == sysctl)
return sdrd + i;
return NULL;
}
/* IPMI V1.5 Section 30 */
static const int exps[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000};
/* Return 0 for fan, 2 for temp, 3 for voltage
We could make it variable based on the accuracy (= log10(m * 10**k2));
this would work for /proc output, however libsensors resolution
is statically set in lib/chips.c */
static int decplaces(struct sdrdata *sd)
{
switch(sd->stype) {
case STYPE_TEMP:
return 2;
case STYPE_CURR:
case STYPE_VOLT:
return 3;
case STYPE_FAN:
default:
return 0;
}
}
/* convert a raw value to a reading. IMPI V1.5 Section 30 */
static long conv_val(int value, struct sdrdata *sd)
{
u8 k1, k2;
long r;
r = value * sd->m;
k1 = sd->k & 0x0f;
k2 = sd->k >> 4;
if(k1 < 8)
r += sd->b * exps[k1];
else
r += sd->b / exps[16 - k1];
r *= exps[decplaces(sd)];
if(k2 < 8)
r *= exps[k2];
else
r /= exps[16 - k2];
return r;
}
/************** /proc callbacks *****************/
static void bmcsensors_all(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct sdrdata *sd;
/*
struct bmcsensors_data *data = client->data;
*/
if((sd = find_sdrd(ctl_name)) == NULL) {
*nrels_mag = 0;
return;
}
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = decplaces(sd);
else if (operation == SENSORS_PROC_REAL_READ) {
bmcsensors_update_client(client);
if(sd->lim2 >= 0) {
if(sd->stype == STYPE_TEMP) /* upper limit first */
results[0] =
conv_val(sd->limits[sd->lim1], sd);
else /* lower limit first */
results[0] =
conv_val(sd->limits[sd->lim2], sd);
} else
results[0] = 0;
if(sd->stype == STYPE_FAN) { /* lower limit only */
results[1] = conv_val(sd->reading, sd);
*nrels_mag = 2;
} else {
if(sd->lim1 >= 0) {
if(sd->stype == STYPE_TEMP) { /* lower 2nd */
results[1] =
conv_val(sd->limits[sd->lim2], sd);
if(sd->lim2 == 6) /* pos. thresh. */
results[1] = results[0] -
results[1];
} else /* upper 2nd */
results[1] =
conv_val(sd->limits[sd->lim1], sd);
} else
results[1] = 0;
results[2] = conv_val(sd->reading, sd);
*nrels_mag = 3;
}
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
/* unimplemented */
}
}
}
#if 0
static void bmcsensors_alarms(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct bmcsensors_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
bmcsensors_update_client(client);
results[0] = data->alarms;
*nrels_mag = 1;
}
}
#endif
/* YJ ... */
static int bmc_thread(void *dummy){
lock_kernel();
daemonize();
unlock_kernel();
strcpy(current->comm, "bmc-sensors");
if(down_interruptible(&bmc_sem)) {
printk("exiting...");
thread_pid= 0;
up (&bmc_sem);
return 0;
}
if (state == STATE_PROCTABLE){
bmcsensors_build_proc_table();
state = STATE_DONE;
printk(KERN_INFO "bmcsensors.o: bmcsensor thread done\n" );
}
thread_pid= 0;
up (&bmc_sem);
return 0;
}
/* ... YJ */
static int __init sm_bmcsensors_init(void)
{
printk(KERN_INFO "bmcsensors.o version %s (%s)\n", LM_VERSION, LM_DATE);
/* YJ ... */
init_MUTEX_LOCKED(&bmc_sem);
thread_pid= kernel_thread(bmc_thread, NULL, 0);
if (thread_pid<0){
printk(KERN_ERR "bmcsensors.o : Could not initialize bmc thread. Aborting\n");
return 0;
}
/* ... YJ */
return i2c_add_driver(&bmcsensors_driver);
}
static void __exit sm_bmcsensors_exit(void)
{
int j;
j= 0;
/* YJ ... */
printk(KERN_INFO "bmcsensors.o sleeping a while\n");
while( (j++ < 50)){
bmc_do_pause(HZ / 25);
}
if (thread_pid > 0){
printk(KERN_INFO "bmcsensors.o stopping kernel thread\n");
state= STATE_DONE;
j= 0;
up (&bmc_sem);
printk(KERN_INFO "bmcsensors.o waiting...\n");
/* make sure kernel thread does not access driver memory any more */
while((thread_pid > 0)&& (j++ < 100)){
bmc_do_pause(HZ / 25);
}
printk(KERN_INFO "bmcsensors.o OK\n");
}
j= 0;
/* sleep for debug... not necessary ? */
printk(KERN_INFO "bmcsensors.o sleeping again\n");
while( (j++ < 50)){
bmc_do_pause(HZ / 25);
}
/* ...YJ */
printk(KERN_INFO "bmcsensors.o i2c cleanup\n");
i2c_del_driver(&bmcsensors_driver);
}
MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>");
MODULE_DESCRIPTION("IPMI BMC sensors");
MODULE_LICENSE("GPL");
module_init(sm_bmcsensors_init);
module_exit(sm_bmcsensors_exit);