summaryrefslogblamecommitdiffstats
path: root/drivers/hwmon/k8temp.c
blob: 418496f13020ba58f57e23eece28417a48466aea (plain) (tree)
1
2
3
4


                                                         
                                                         




























                                                                       
                          






                                                                      
                                 







                                                                                 
                                                                     
                        


































































                                                                                    
                 

                                                             
                                                                  

                                    


                                                                          









                                                                         
                                                  



                                                                        

                                     






























                                                                    





                                                                 
                           
                                 





                                                                        


                                          





                                                                    
 







                                                                             

         







                                                                     

























































                                                                                     
                                                 




                                                                        
                 





                                                             
                                                            
 

                                               

























                                                                  
                                                 





























                                                                  
                                                     




                                                      
/*
 * k8temp.c - Linux kernel module for hardware monitoring
 *
 * Copyright (C) 2006 Rudolf Marek <r.marek@assembler.cz>
 *
 * Inspired from the w83785 and amd756 drivers.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

#include <linux/module.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/pci.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <asm/processor.h>

#define TEMP_FROM_REG(val)	(((((val) >> 16) & 0xff) - 49) * 1000)
#define REG_TEMP	0xe4
#define SEL_PLACE	0x40
#define SEL_CORE	0x04

struct k8temp_data {
	struct device *hwmon_dev;
	struct mutex update_lock;
	const char *name;
	char valid;		/* zero until following fields are valid */
	unsigned long last_updated;	/* in jiffies */

	/* registers values */
	u8 sensorsp;		/* sensor presence bits - SEL_CORE & SEL_PLACE */
	u32 temp[2][2];		/* core, place */
	u8 swap_core_select;    /* meaning of SEL_CORE is inverted */
	u32 temp_offset;
};

static struct k8temp_data *k8temp_update_device(struct device *dev)
{
	struct k8temp_data *data = dev_get_drvdata(dev);
	struct pci_dev *pdev = to_pci_dev(dev);
	u8 tmp;

	mutex_lock(&data->update_lock);

	if (!data->valid
	    || time_after(jiffies, data->last_updated + HZ)) {
		pci_read_config_byte(pdev, REG_TEMP, &tmp);
		tmp &= ~(SEL_PLACE | SEL_CORE);		/* Select sensor 0, core0 */
		pci_write_config_byte(pdev, REG_TEMP, tmp);
		pci_read_config_dword(pdev, REG_TEMP, &data->temp[0][0]);

		if (data->sensorsp & SEL_PLACE) {
			tmp |= SEL_PLACE;	/* Select sensor 1, core0 */
			pci_write_config_byte(pdev, REG_TEMP, tmp);
			pci_read_config_dword(pdev, REG_TEMP,
					      &data->temp[0][1]);
		}

		if (data->sensorsp & SEL_CORE) {
			tmp &= ~SEL_PLACE;	/* Select sensor 0, core1 */
			tmp |= SEL_CORE;
			pci_write_config_byte(pdev, REG_TEMP, tmp);
			pci_read_config_dword(pdev, REG_TEMP,
					      &data->temp[1][0]);

			if (data->sensorsp & SEL_PLACE) {
				tmp |= SEL_PLACE;	/* Select sensor 1, core1 */
				pci_write_config_byte(pdev, REG_TEMP, tmp);
				pci_read_config_dword(pdev, REG_TEMP,
						      &data->temp[1][1]);
			}
		}

		data->last_updated = jiffies;
		data->valid = 1;
	}

	mutex_unlock(&data->update_lock);
	return data;
}

/*
 * Sysfs stuff
 */

static ssize_t show_name(struct device *dev, struct device_attribute
			 *devattr, char *buf)
{
	struct k8temp_data *data = dev_get_drvdata(dev);

	return sprintf(buf, "%s\n", data->name);
}


static ssize_t show_temp(struct device *dev,
			 struct device_attribute *devattr, char *buf)
{
	struct sensor_device_attribute_2 *attr =
	    to_sensor_dev_attr_2(devattr);
	int core = attr->nr;
	int place = attr->index;
	int temp;
	struct k8temp_data *data = k8temp_update_device(dev);

	if (data->swap_core_select && (data->sensorsp & SEL_CORE))
		core = core ? 0 : 1;

	temp = TEMP_FROM_REG(data->temp[core][place]) + data->temp_offset;

	return sprintf(buf, "%d\n", temp);
}

/* core, place */

static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0);
static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1);
static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 1, 0);
static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, show_temp, NULL, 1, 1);
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);

static const struct pci_device_id k8temp_ids[] = {
	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_K8_NB_MISC) },
	{ 0 },
};

MODULE_DEVICE_TABLE(pci, k8temp_ids);

static int __devinit is_rev_g_desktop(u8 model)
{
	u32 brandidx;

	if (model < 0x69)
		return 0;

	if (model == 0xc1 || model == 0x6c || model == 0x7c)
		return 0;

	/*
	 * Differentiate between AM2 and ASB1.
	 * See "Constructing the processor Name String" in "Revision
	 * Guide for AMD NPT Family 0Fh Processors" (33610).
	 */
	brandidx = cpuid_ebx(0x80000001);
	brandidx = (brandidx >> 9) & 0x1f;

	/* Single core */
	if ((model == 0x6f || model == 0x7f) &&
	    (brandidx == 0x7 || brandidx == 0x9 || brandidx == 0xc))
		return 0;

	/* Dual core */
	if (model == 0x6b &&
	    (brandidx == 0xb || brandidx == 0xc))
		return 0;

	return 1;
}

static int __devinit k8temp_probe(struct pci_dev *pdev,
				  const struct pci_device_id *id)
{
	int err;
	u8 scfg;
	u32 temp;
	u8 model, stepping;
	struct k8temp_data *data;

	if (!(data = kzalloc(sizeof(struct k8temp_data), GFP_KERNEL))) {
		err = -ENOMEM;
		goto exit;
	}

	model = boot_cpu_data.x86_model;
	stepping = boot_cpu_data.x86_mask;

	/* feature available since SH-C0, exclude older revisions */
	if (((model == 4) && (stepping == 0)) ||
	    ((model == 5) && (stepping <= 1))) {
		err = -ENODEV;
		goto exit_free;
	}

	/*
	 * AMD NPT family 0fh, i.e. RevF and RevG:
	 * meaning of SEL_CORE bit is inverted
	 */
	if (model >= 0x40) {
		data->swap_core_select = 1;
		dev_warn(&pdev->dev, "Temperature readouts might be wrong - "
			 "check erratum #141\n");
	}

	/*
	 * RevG desktop CPUs (i.e. no socket S1G1 or ASB1 parts) need
	 * additional offset, otherwise reported temperature is below
	 * ambient temperature
	 */
	if (is_rev_g_desktop(model))
		data->temp_offset = 21000;

	pci_read_config_byte(pdev, REG_TEMP, &scfg);
	scfg &= ~(SEL_PLACE | SEL_CORE);		/* Select sensor 0, core0 */
	pci_write_config_byte(pdev, REG_TEMP, scfg);
	pci_read_config_byte(pdev, REG_TEMP, &scfg);

	if (scfg & (SEL_PLACE | SEL_CORE)) {
		dev_err(&pdev->dev, "Configuration bit(s) stuck at 1!\n");
		err = -ENODEV;
		goto exit_free;
	}

	scfg |= (SEL_PLACE | SEL_CORE);
	pci_write_config_byte(pdev, REG_TEMP, scfg);

	/* now we know if we can change core and/or sensor */
	pci_read_config_byte(pdev, REG_TEMP, &data->sensorsp);

	if (data->sensorsp & SEL_PLACE) {
		scfg &= ~SEL_CORE;	/* Select sensor 1, core0 */
		pci_write_config_byte(pdev, REG_TEMP, scfg);
		pci_read_config_dword(pdev, REG_TEMP, &temp);
		scfg |= SEL_CORE;	/* prepare for next selection */
		if (!((temp >> 16) & 0xff))	/* if temp is 0 -49C is not likely */
			data->sensorsp &= ~SEL_PLACE;
	}

	if (data->sensorsp & SEL_CORE) {
		scfg &= ~SEL_PLACE;	/* Select sensor 0, core1 */
		pci_write_config_byte(pdev, REG_TEMP, scfg);
		pci_read_config_dword(pdev, REG_TEMP, &temp);
		if (!((temp >> 16) & 0xff))	/* if temp is 0 -49C is not likely */
			data->sensorsp &= ~SEL_CORE;
	}

	data->name = "k8temp";
	mutex_init(&data->update_lock);
	dev_set_drvdata(&pdev->dev, data);

	/* Register sysfs hooks */
	err = device_create_file(&pdev->dev,
			   &sensor_dev_attr_temp1_input.dev_attr);
	if (err)
		goto exit_remove;

	/* sensor can be changed and reports something */
	if (data->sensorsp & SEL_PLACE) {
		err = device_create_file(&pdev->dev,
				   &sensor_dev_attr_temp2_input.dev_attr);
		if (err)
			goto exit_remove;
	}

	/* core can be changed and reports something */
	if (data->sensorsp & SEL_CORE) {
		err = device_create_file(&pdev->dev,
				   &sensor_dev_attr_temp3_input.dev_attr);
		if (err)
			goto exit_remove;
		if (data->sensorsp & SEL_PLACE) {
			err = device_create_file(&pdev->dev,
					   &sensor_dev_attr_temp4_input.
					   dev_attr);
			if (err)
				goto exit_remove;
		}
	}

	err = device_create_file(&pdev->dev, &dev_attr_name);
	if (err)
		goto exit_remove;

	data->hwmon_dev = hwmon_device_register(&pdev->dev);

	if (IS_ERR(data->hwmon_dev)) {
		err = PTR_ERR(data->hwmon_dev);
		goto exit_remove;
	}

	return 0;

exit_remove:
	device_remove_file(&pdev->dev,
			   &sensor_dev_attr_temp1_input.dev_attr);
	device_remove_file(&pdev->dev,
			   &sensor_dev_attr_temp2_input.dev_attr);
	device_remove_file(&pdev->dev,
			   &sensor_dev_attr_temp3_input.dev_attr);
	device_remove_file(&pdev->dev,
			   &sensor_dev_attr_temp4_input.dev_attr);
	device_remove_file(&pdev->dev, &dev_attr_name);
exit_free:
	dev_set_drvdata(&pdev->dev, NULL);
	kfree(data);
exit:
	return err;
}

static void __devexit k8temp_remove(struct pci_dev *pdev)
{
	struct k8temp_data *data = dev_get_drvdata(&pdev->dev);

	hwmon_device_unregister(data->hwmon_dev);
	device_remove_file(&pdev->dev,
			   &sensor_dev_attr_temp1_input.dev_attr);
	device_remove_file(&pdev->dev,
			   &sensor_dev_attr_temp2_input.dev_attr);
	device_remove_file(&pdev->dev,
			   &sensor_dev_attr_temp3_input.dev_attr);
	device_remove_file(&pdev->dev,
			   &sensor_dev_attr_temp4_input.dev_attr);
	device_remove_file(&pdev->dev, &dev_attr_name);
	dev_set_drvdata(&pdev->dev, NULL);
	kfree(data);
}

static struct pci_driver k8temp_driver = {
	.name = "k8temp",
	.id_table = k8temp_ids,
	.probe = k8temp_probe,
	.remove = __devexit_p(k8temp_remove),
};

static int __init k8temp_init(void)
{
	return pci_register_driver(&k8temp_driver);
}

static void __exit k8temp_exit(void)
{
	pci_unregister_driver(&k8temp_driver);
}

MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
MODULE_DESCRIPTION("AMD K8 core temperature monitor");
MODULE_LICENSE("GPL");

module_init(k8temp_init)
module_exit(k8temp_exit)
an> | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Impact: cleanup, no code changed Remove an ugly #ifdef CONFIG_SMP from panic(), by providing an smp_send_stop() wrapper on UP too. LKML-Reference: <49B91A7E.76E4.0078.0@novell.com> Signed-off-by: Ingo Molnar <mingo@elte.hu> | * | | | | | | panic: decrease oops_in_progress only after having done the panicIngo Molnar2009-03-131-1/+1 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Impact: eliminate secondary warnings during panic() We can panic() in a number of difficult, atomic contexts, hence we use bust_spinlocks(1) in panic() to increase oops_in_progress, which prevents various debug checks we have in place. But in practice this protection only covers the first few printk's done by panic() - it does not cover the later attempt to stop all other CPUs and kexec(). If a secondary warning triggers in one of those facilities that can make the panic message scroll off. So do bust_spinlocks(0) only much later in panic(). (which code is only reached if panic policy is relaxed that it can return after a warning message) Reported-by: Jan Beulich <jbeulich@novell.com> LKML-Reference: <49B91A7E.76E4.0078.0@novell.com> Signed-off-by: Ingo Molnar <mingo@elte.hu> | * | | | | | | Merge branch 'x86/core' into core/ipiIngo Molnar2009-03-13522-18699/+20722 | |\ \ \ \ \ \ \ | * | | | | | | | generic-ipi: eliminate WARN_ON()s during oops/panicIngo Molnar2009-03-131-3/+4 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Do not output smp-call related warnings in the oops/panic codepath. Reported-by: Jan Beulich <jbeulich@novell.com> Acked-by: Peter Zijlstra <peterz@infradead.org> LKML-Reference: <49B91A7E.76E4.0078.0@novell.com> Signed-off-by: Ingo Molnar <mingo@elte.hu> | * | | | | | | | Merge branch 'linus' into core/ipiIngo Molnar2009-03-13556-4468/+8955 | |\ \ \ \ \ \ \ \ | * | | | | | | | | generic-ipi: cleanupsIngo Molnar2009-02-251-76/+86 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Andrew pointed out that there's some small amount of style rot in kernel/smp.c. Clean it up. Reported-by: Andrew Morton <akpm@linux-foundation.org> Cc: Nick Piggin <npiggin@suse.de> Cc: Jens Axboe <jens.axboe@oracle.com> Cc: Peter Zijlstra <peterz@infradead.org> Signed-off-by: Ingo Molnar <mingo@elte.hu> | * | | | | | | | | generic-ipi: remove CSD_FLAG_WAITPeter Zijlstra2009-02-255-71/+28Star | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Oleg noticed that we don't strictly need CSD_FLAG_WAIT, rework the code so that we can use CSD_FLAG_LOCK for both purposes. Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl> Cc: Oleg Nesterov <oleg@redhat.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Nick Piggin <npiggin@suse.de> Cc: Jens Axboe <jens.axboe@oracle.com> Cc: "Paul E. McKenney" <paulmck@linux.vnet.ibm.com> Cc: Rusty Russell <rusty@rustcorp.com.au> Signed-off-by: Ingo Molnar <mingo@elte.hu> | * | | | | | | | | generic-ipi: remove kmalloc()Peter Zijlstra2009-02-251-98/+166 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Remove the use of kmalloc() from the smp_call_function_*() calls. Steven's generic-ipi patch (d7240b98: generic-ipi: use per cpu data for single cpu ipi calls) started the discussion on the use of kmalloc() in this code and fixed the smp_call_function_single(.wait=0) fallback case. In this patch we complete this by also providing means for the _many() call, which fully removes the need for kmalloc() in this code. The problem with the _many() call is that other cpus might still be observing our entry when we're done with it. It solved this by dynamically allocating data elements and RCU-freeing it. We solve it by using a single per-cpu entry which provides static storage and solves one half of the problem (avoiding referencing freed data). The other half, ensuring the queue iteration it still possible, is done by placing re-used entries at the head of the list. This means that if someone was still iterating that entry when it got moved, he will now re-visit the entries on the list he had already seen, but avoids skipping over entries like would have happened had we placed the new entry at the end. Furthermore, visiting entries twice is not a problem, since we remove our cpu from the entry's cpumask once its called. Many thanks to Oleg for his suggestions and him poking holes in my earlier attempts. Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl> Cc: Oleg Nesterov <oleg@redhat.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Nick Piggin <npiggin@suse.de> Cc: Jens Axboe <jens.axboe@oracle.com> Cc: "Paul E. McKenney" <paulmck@linux.vnet.ibm.com> Cc: Rusty Russell <rusty@rustcorp.com.au> Signed-off-by: Ingo Molnar <mingo@elte.hu> | * | | | | | | | | generic IPI: simplify barriers and lockingNick Piggin2009-02-251-39/+44 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Simplify the barriers in generic remote function call interrupt code. Firstly, just unconditionally take the lock and check the list in the generic_call_function_single_interrupt IPI handler. As we've just taken an IPI here, the chances are fairly high that there will be work on the list for us, so do the locking unconditionally. This removes the tricky lockless list_empty check and dubious barriers. The change looks bigger than it is because it is just removing an outer loop. Secondly, clarify architecture specific IPI locking rules. Generic code has no tools to impose any sane ordering on IPIs if they go outside normal cache coherency, ergo the arch code must make them appear to obey cache coherency as a "memory operation" to initiate an IPI, and a "memory operation" to receive one. This way at least they can be reasoned about in generic code, and smp_mb used to provide ordering. The combination of these two changes means that explict barriers can be taken out of queue handling for the single case -- shared data is explicitly locked, and ipi ordering must conform to that, so no barriers needed. An extra barrier is needed in the many handler, so as to ensure we load the list element after the IPI is received. Does any architecture actually *need* these barriers? For the initiator I could see it, but for the handler I would be surprised. So the other thing we could do for simplicity is just to require that, rather than just matching with cache coherency, we just require a full barrier before generating an IPI, and after receiving an IPI. In which case, the smp_mb()s can go away. But just for now, we'll be on the safe side and use the barriers (they're in the slow case anyway). Signed-off-by: Nick Piggin <npiggin@suse.de> Acked-by: Peter Zijlstra <a.p.zijlstra@chello.nl> Cc: linux-arch@vger.kernel.org Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Jens Axboe <jens.axboe@oracle.com> Cc: Oleg Nesterov <oleg@redhat.com> Cc: Suresh Siddha <suresh.b.siddha@intel.com> Signed-off-by: Ingo Molnar <mingo@elte.hu> * | | | | | | | | | Merge branch 'locking-for-linus' of ↵Linus Torvalds2009-04-044-19/+13Star |\ \ \ \ \ \ \ \ \ \ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip * 'locking-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip: locking: rename trace_softirq_[enter|exit] => lockdep_softirq_[enter|exit] lockdep: remove duplicate CONFIG_DEBUG_LOCKDEP definitions lockdep: require framepointers for x86 lockdep: remove extra "irq" string lockdep: fix incorrect state name | * \ \ \ \ \ \ \ \ \ Merge branch 'linus' into locking-for-linusIngo Molnar2009-03-31