diff options
Diffstat (limited to 'drivers')
209 files changed, 21001 insertions, 3819 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index ae01d86..707650a 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -85,4 +85,6 @@ source "drivers/auxdisplay/Kconfig" source "drivers/kvm/Kconfig" source "drivers/uio/Kconfig" + +source "drivers/lguest/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index c34c8ef..0ea8e32 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -73,6 +73,7 @@ obj-$(CONFIG_ISDN) += isdn/ obj-$(CONFIG_EDAC) += edac/ obj-$(CONFIG_MCA) += mca/ obj-$(CONFIG_EISA) += eisa/ +obj-$(CONFIG_LGUEST_GUEST) += lguest/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ obj-$(CONFIG_MMC) += mmc/ obj-$(CONFIG_NEW_LEDS) += leds/ diff --git a/drivers/acpi/sleep/main.c b/drivers/acpi/sleep/main.c index bc7e16e..42127c0 100644 --- a/drivers/acpi/sleep/main.c +++ b/drivers/acpi/sleep/main.c @@ -217,10 +217,26 @@ static void acpi_hibernation_finish(void) } } +static int acpi_hibernation_pre_restore(void) +{ + acpi_status status; + + status = acpi_hw_disable_all_gpes(); + + return ACPI_SUCCESS(status) ? 0 : -EFAULT; +} + +static void acpi_hibernation_restore_cleanup(void) +{ + acpi_hw_enable_all_runtime_gpes(); +} + static struct hibernation_ops acpi_hibernation_ops = { .prepare = acpi_hibernation_prepare, .enter = acpi_hibernation_enter, .finish = acpi_hibernation_finish, + .pre_restore = acpi_hibernation_pre_restore, + .restore_cleanup = acpi_hibernation_restore_cleanup, }; #endif /* CONFIG_SOFTWARE_SUSPEND */ diff --git a/drivers/acpi/sleep/poweroff.c b/drivers/acpi/sleep/poweroff.c index d9801ef..39e40d5 100644 --- a/drivers/acpi/sleep/poweroff.c +++ b/drivers/acpi/sleep/poweroff.c @@ -39,7 +39,13 @@ int acpi_sleep_prepare(u32 acpi_state) #ifdef CONFIG_PM -void acpi_power_off(void) +static void acpi_power_off_prepare(void) +{ + /* Prepare to power off the system */ + acpi_sleep_prepare(ACPI_STATE_S5); +} + +static void acpi_power_off(void) { /* acpi_sleep_prepare(ACPI_STATE_S5) should have already been called */ printk("%s called\n", __FUNCTION__); @@ -48,30 +54,6 @@ void acpi_power_off(void) acpi_enter_sleep_state(ACPI_STATE_S5); } -static int acpi_shutdown(struct sys_device *x) -{ - switch (system_state) { - case SYSTEM_POWER_OFF: - /* Prepare to power off the system */ - return acpi_sleep_prepare(ACPI_STATE_S5); - case SYSTEM_SUSPEND_DISK: - /* Prepare to suspend the system to disk */ - return acpi_sleep_prepare(ACPI_STATE_S4); - default: - return 0; - } -} - -static struct sysdev_class acpi_sysclass = { - set_kset_name("acpi"), - .shutdown = acpi_shutdown -}; - -static struct sys_device device_acpi = { - .id = 0, - .cls = &acpi_sysclass, -}; - static int acpi_poweroff_init(void) { if (!acpi_disabled) { @@ -81,13 +63,8 @@ static int acpi_poweroff_init(void) status = acpi_get_sleep_type_data(ACPI_STATE_S5, &type_a, &type_b); if (ACPI_SUCCESS(status)) { - int error; - error = sysdev_class_register(&acpi_sysclass); - if (!error) - error = sysdev_register(&device_acpi); - if (!error) - pm_power_off = acpi_power_off; - return error; + pm_power_off_prepare = acpi_power_off_prepare; + pm_power_off = acpi_power_off; } } return 0; diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index fff1780..966a5e2 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -5,6 +5,6 @@ obj-$(CONFIG_PM_TRACE) += trace.o ifeq ($(CONFIG_DEBUG_DRIVER),y) EXTRA_CFLAGS += -DDEBUG endif -ifeq ($(CONFIG_PM_DEBUG),y) +ifeq ($(CONFIG_PM_VERBOSE),y) EXTRA_CFLAGS += -DDEBUG endif diff --git a/drivers/block/Makefile b/drivers/block/Makefile index 3e31532..819c829 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -30,3 +30,4 @@ obj-$(CONFIG_BLK_DEV_SX8) += sx8.o obj-$(CONFIG_BLK_DEV_UB) += ub.o obj-$(CONFIG_XEN_BLKDEV_FRONTEND) += xen-blkfront.o +obj-$(CONFIG_LGUEST_GUEST) += lguest_blk.o diff --git a/drivers/block/lguest_blk.c b/drivers/block/lguest_blk.c new file mode 100644 index 0000000..1634c2d --- /dev/null +++ b/drivers/block/lguest_blk.c @@ -0,0 +1,275 @@ +/* A simple block driver for lguest. + * + * Copyright 2006 Rusty Russell <rusty@rustcorp.com.au> IBM Corporation + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +//#define DEBUG +#include <linux/init.h> +#include <linux/types.h> +#include <linux/blkdev.h> +#include <linux/interrupt.h> +#include <linux/lguest_bus.h> + +static char next_block_index = 'a'; + +struct blockdev +{ + spinlock_t lock; + + /* The disk structure for the kernel. */ + struct gendisk *disk; + + /* The major number for this disk. */ + int major; + int irq; + + unsigned long phys_addr; + /* The mapped block page. */ + struct lguest_block_page *lb_page; + + /* We only have a single request outstanding at a time. */ + struct lguest_dma dma; + struct request *req; +}; + +/* Jens gave me this nice helper to end all chunks of a request. */ +static void end_entire_request(struct request *req, int uptodate) +{ + if (end_that_request_first(req, uptodate, req->hard_nr_sectors)) + BUG(); + add_disk_randomness(req->rq_disk); + blkdev_dequeue_request(req); + end_that_request_last(req, uptodate); +} + +static irqreturn_t lgb_irq(int irq, void *_bd) +{ + struct blockdev *bd = _bd; + unsigned long flags; + + if (!bd->req) { + pr_debug("No work!\n"); + return IRQ_NONE; + } + + if (!bd->lb_page->result) { + pr_debug("No result!\n"); + return IRQ_NONE; + } + + spin_lock_irqsave(&bd->lock, flags); + end_entire_request(bd->req, bd->lb_page->result == 1); + bd->req = NULL; + bd->dma.used_len = 0; + blk_start_queue(bd->disk->queue); + spin_unlock_irqrestore(&bd->lock, flags); + return IRQ_HANDLED; +} + +static unsigned int req_to_dma(struct request *req, struct lguest_dma *dma) +{ + unsigned int i = 0, idx, len = 0; + struct bio *bio; + + rq_for_each_bio(bio, req) { + struct bio_vec *bvec; + bio_for_each_segment(bvec, bio, idx) { + BUG_ON(i == LGUEST_MAX_DMA_SECTIONS); + BUG_ON(!bvec->bv_len); + dma->addr[i] = page_to_phys(bvec->bv_page) + + bvec->bv_offset; + dma->len[i] = bvec->bv_len; + len += bvec->bv_len; + i++; + } + } + if (i < LGUEST_MAX_DMA_SECTIONS) + dma->len[i] = 0; + return len; +} + +static void empty_dma(struct lguest_dma *dma) +{ + dma->len[0] = 0; +} + +static void setup_req(struct blockdev *bd, + int type, struct request *req, struct lguest_dma *dma) +{ + bd->lb_page->type = type; + bd->lb_page->sector = req->sector; + bd->lb_page->result = 0; + bd->req = req; + bd->lb_page->bytes = req_to_dma(req, dma); +} + +static void do_write(struct blockdev *bd, struct request *req) +{ + struct lguest_dma send; + + pr_debug("lgb: WRITE sector %li\n", (long)req->sector); + setup_req(bd, 1, req, &send); + + lguest_send_dma(bd->phys_addr, &send); +} + +static void do_read(struct blockdev *bd, struct request *req) +{ + struct lguest_dma ping; + + pr_debug("lgb: READ sector %li\n", (long)req->sector); + setup_req(bd, 0, req, &bd->dma); + + empty_dma(&ping); + lguest_send_dma(bd->phys_addr, &ping); +} + +static void do_lgb_request(request_queue_t *q) +{ + struct blockdev *bd; + struct request *req; + +again: + req = elv_next_request(q); + if (!req) + return; + + bd = req->rq_disk->private_data; + /* Sometimes we get repeated requests after blk_stop_queue. */ + if (bd->req) + return; + + if (!blk_fs_request(req)) { + pr_debug("Got non-command 0x%08x\n", req->cmd_type); + req->errors++; + end_entire_request(req, 0); + goto again; + } + + if (rq_data_dir(req) == WRITE) + do_write(bd, req); + else + do_read(bd, req); + + /* Wait for interrupt to tell us it's done. */ + blk_stop_queue(q); +} + +static struct block_device_operations lguestblk_fops = { + .owner = THIS_MODULE, +}; + +static int lguestblk_probe(struct lguest_device *lgdev) +{ + struct blockdev *bd; + int err; + int irqflags = IRQF_SHARED; + + bd = kmalloc(sizeof(*bd), GFP_KERNEL); + if (!bd) + return -ENOMEM; + + spin_lock_init(&bd->lock); + bd->irq = lgdev_irq(lgdev); + bd->req = NULL; + bd->dma.used_len = 0; + bd->dma.len[0] = 0; + bd->phys_addr = (lguest_devices[lgdev->index].pfn << PAGE_SHIFT); + + bd->lb_page = lguest_map(bd->phys_addr, 1); + if (!bd->lb_page) { + err = -ENOMEM; + goto out_free_bd; + } + + bd->major = register_blkdev(0, "lguestblk"); + if (bd->major < 0) { + err = bd->major; + goto out_unmap; + } + + bd->disk = alloc_disk(1); + if (!bd->disk) { + err = -ENOMEM; + goto out_unregister_blkdev; + } + + bd->disk->queue = blk_init_queue(do_lgb_request, &bd->lock); + if (!bd->disk->queue) { + err = -ENOMEM; + goto out_put_disk; + } + + /* We can only handle a certain number of sg entries */ + blk_queue_max_hw_segments(bd->disk->queue, LGUEST_MAX_DMA_SECTIONS); + /* Buffers must not cross page boundaries */ + blk_queue_segment_boundary(bd->disk->queue, PAGE_SIZE-1); + + sprintf(bd->disk->disk_name, "lgb%c", next_block_index++); + if (lguest_devices[lgdev->index].features & LGUEST_DEVICE_F_RANDOMNESS) + irqflags |= IRQF_SAMPLE_RANDOM; + err = request_irq(bd->irq, lgb_irq, irqflags, bd->disk->disk_name, bd); + if (err) + goto out_cleanup_queue; + + err = lguest_bind_dma(bd->phys_addr, &bd->dma, 1, bd->irq); + if (err) + goto out_free_irq; + + bd->disk->major = bd->major; + bd->disk->first_minor = 0; + bd->disk->private_data = bd; + bd->disk->fops = &lguestblk_fops; + /* This is initialized to the disk size by the other end. */ + set_capacity(bd->disk, bd->lb_page->num_sectors); + add_disk(bd->disk); + + printk(KERN_INFO "%s: device %i at major %d\n", + bd->disk->disk_name, lgdev->index, bd->major); + + lgdev->private = bd; + return 0; + +out_free_irq: + free_irq(bd->irq, bd); +out_cleanup_queue: + blk_cleanup_queue(bd->disk->queue); +out_put_disk: + put_disk(bd->disk); +out_unregister_blkdev: + unregister_blkdev(bd->major, "lguestblk"); +out_unmap: + lguest_unmap(bd->lb_page); +out_free_bd: + kfree(bd); + return err; +} + +static struct lguest_driver lguestblk_drv = { + .name = "lguestblk", + .owner = THIS_MODULE, + .device_type = LGUEST_DEVICE_T_BLOCK, + .probe = lguestblk_probe, +}; + +static __init int lguestblk_init(void) +{ + return register_lguest_driver(&lguestblk_drv); +} +module_init(lguestblk_init); + +MODULE_DESCRIPTION("Lguest block driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/block/sx8.c b/drivers/block/sx8.c index 54509eb..949ae93 100644 --- a/drivers/block/sx8.c +++ b/drivers/block/sx8.c @@ -1608,7 +1608,7 @@ static int carm_init_one (struct pci_dev *pdev, const struct pci_device_id *ent) } #endif - host = kmalloc(sizeof(*host), GFP_KERNEL); + host = kzalloc(sizeof(*host), GFP_KERNEL); if (!host) { printk(KERN_ERR DRV_NAME "(%s): memory alloc failure\n", pci_name(pdev)); @@ -1616,7 +1616,6 @@ static int carm_init_one (struct pci_dev *pdev, const struct pci_device_id *ent) goto err_out_regions; } - memset(host, 0, sizeof(*host)); host->pdev = pdev; host->flags = pci_dac ? FL_DAC : 0; spin_lock_init(&host->lock); diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 8852b8d..4e6f387 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -42,6 +42,7 @@ obj-$(CONFIG_SYNCLINK_GT) += synclink_gt.o obj-$(CONFIG_N_HDLC) += n_hdlc.o obj-$(CONFIG_AMIGA_BUILTIN_SERIAL) += amiserial.o obj-$(CONFIG_SX) += sx.o generic_serial.o +obj-$(CONFIG_LGUEST_GUEST) += hvc_lguest.o obj-$(CONFIG_RIO) += rio/ generic_serial.o obj-$(CONFIG_HVC_CONSOLE) += hvc_vio.o hvsi.o obj-$(CONFIG_HVC_ISERIES) += hvc_iseries.o diff --git a/drivers/char/amiserial.c b/drivers/char/amiserial.c index 7b02bf1..3d468f5 100644 --- a/drivers/char/amiserial.c +++ b/drivers/char/amiserial.c @@ -1721,12 +1721,11 @@ static int get_async_struct(int line, struct async_struct **ret_info) *ret_info = sstate->info; return 0; } - info = kmalloc(sizeof(struct async_struct), GFP_KERNEL); + info = kzalloc(sizeof(struct async_struct), GFP_KERNEL); if (!info) { sstate->count--; return -ENOMEM; } - memset(info, 0, sizeof(struct async_struct)); #ifdef DECLARE_WAITQUEUE init_waitqueue_head(&info->open_wait); init_waitqueue_head(&info->close_wait); diff --git a/drivers/char/drm/via_dmablit.c b/drivers/char/drm/via_dmablit.c index fdb8609..832de1d 100644 --- a/drivers/char/drm/via_dmablit.c +++ b/drivers/char/drm/via_dmablit.c @@ -273,10 +273,9 @@ via_alloc_desc_pages(drm_via_sg_info_t *vsg) vsg->num_desc_pages = (vsg->num_desc + vsg->descriptors_per_page - 1) / vsg->descriptors_per_page; - if (NULL == (vsg->desc_pages = kmalloc(sizeof(void *) * vsg->num_desc_pages, GFP_KERNEL))) + if (NULL == (vsg->desc_pages = kcalloc(vsg->num_desc_pages, sizeof(void *), GFP_KERNEL))) return DRM_ERR(ENOMEM); - memset(vsg->desc_pages, 0, sizeof(void *) * vsg->num_desc_pages); vsg->state = dr_via_desc_pages_alloc; for (i=0; i<vsg->num_desc_pages; ++i) { if (NULL == (vsg->desc_pages[i] = diff --git a/drivers/char/esp.c b/drivers/char/esp.c index 74cd511..2e7ae42 100644 --- a/drivers/char/esp.c +++ b/drivers/char/esp.c @@ -2459,7 +2459,7 @@ static int __init espserial_init(void) return 1; } - info = kmalloc(sizeof(struct esp_struct), GFP_KERNEL); + info = kzalloc(sizeof(struct esp_struct), GFP_KERNEL); if (!info) { @@ -2469,7 +2469,6 @@ static int __init espserial_init(void) return 1; } - memset((void *)info, 0, sizeof(struct esp_struct)); spin_lock_init(&info->lock); /* rx_trigger, tx_trigger are needed by autoconfig */ info->config.rx_trigger = rx_trigger; @@ -2527,7 +2526,7 @@ static int __init espserial_init(void) if (!dma) info->stat_flags |= ESP_STAT_NEVER_DMA; - info = kmalloc(sizeof(struct esp_struct), GFP_KERNEL); + info = kzalloc(sizeof(struct esp_struct), GFP_KERNEL); if (!info) { printk(KERN_ERR "Couldn't allocate memory for esp serial device information\n"); @@ -2536,7 +2535,6 @@ static int __init espserial_init(void) return 0; } - memset((void *)info, 0, sizeof(struct esp_struct)); /* rx_trigger, tx_trigger are needed by autoconfig */ info->config.rx_trigger = rx_trigger; info->config.tx_trigger = tx_trigger; diff --git a/drivers/char/hvc_lguest.c b/drivers/char/hvc_lguest.c new file mode 100644 index 0000000..e7b889e --- /dev/null +++ b/drivers/char/hvc_lguest.c @@ -0,0 +1,102 @@ +/* Simple console for lguest. + * + * Copyright (C) 2006 Rusty Russell, IBM Corporation + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <linux/err.h> +#include <linux/init.h> +#include <linux/lguest_bus.h> +#include "hvc_console.h" + +static char inbuf[256]; +static struct lguest_dma cons_input = { .used_len = 0, + .addr[0] = __pa(inbuf), + .len[0] = sizeof(inbuf), + .len[1] = 0 }; + +static int put_chars(u32 vtermno, const char *buf, int count) +{ + struct lguest_dma dma; + + /* FIXME: what if it's over a page boundary? */ + dma.len[0] = count; + dma.len[1] = 0; + dma.addr[0] = __pa(buf); + + lguest_send_dma(LGUEST_CONSOLE_DMA_KEY, &dma); + return count; +} + +static int get_chars(u32 vtermno, char *buf, int count) +{ + static int cons_offset; + + if (!cons_input.used_len) + return 0; + + if (cons_input.used_len - cons_offset < count) + count = cons_input.used_len - cons_offset; + + memcpy(buf, inbuf + cons_offset, count); + cons_offset += count; + if (cons_offset == cons_input.used_len) { + cons_offset = 0; + cons_input.used_len = 0; + } + return count; +} + +static struct hv_ops lguest_cons = { + .get_chars = get_chars, + .put_chars = put_chars, +}; + +static int __init cons_init(void) +{ + if (strcmp(paravirt_ops.name, "lguest") != 0) + return 0; + + return hvc_instantiate(0, 0, &lguest_cons); +} +console_initcall(cons_init); + +static int lguestcons_probe(struct lguest_device *lgdev) +{ + int err; + + lgdev->private = hvc_alloc(0, lgdev_irq(lgdev), &lguest_cons, 256); + if (IS_ERR(lgdev->private)) + return PTR_ERR(lgdev->private); + + err = lguest_bind_dma(LGUEST_CONSOLE_DMA_KEY, &cons_input, 1, + lgdev_irq(lgdev)); + if (err) + printk("lguest console: failed to bind buffer.\n"); + return err; +} + +static struct lguest_driver lguestcons_drv = { + .name = "lguestcons", + .owner = THIS_MODULE, + .device_type = LGUEST_DEVICE_T_CONSOLE, + .probe = lguestcons_probe, +}; + +static int __init hvc_lguest_init(void) +{ + return register_lguest_driver(&lguestcons_drv); +} +module_init(hvc_lguest_init); diff --git a/drivers/char/hvcs.c b/drivers/char/hvcs.c index 207f734..17f96e0 100644 --- a/drivers/char/hvcs.c +++ b/drivers/char/hvcs.c @@ -784,12 +784,10 @@ static int __devinit hvcs_probe( return -EFAULT; } - hvcsd = kmalloc(sizeof(*hvcsd), GFP_KERNEL); + hvcsd = kzalloc(sizeof(*hvcsd), GFP_KERNEL); if (!hvcsd) return -ENODEV; - /* hvcsd->tty is zeroed out with the memset */ - memset(hvcsd, 0x00, sizeof(*hvcsd)); spin_lock_init(&hvcsd->lock); /* Automatically incs the refcount the first time */ diff --git a/drivers/char/ip2/ip2main.c b/drivers/char/ip2/ip2main.c index 83c7258..6005b52 100644 --- a/drivers/char/ip2/ip2main.c +++ b/drivers/char/ip2/ip2main.c @@ -425,9 +425,7 @@ cleanup_module(void) printk(KERN_ERR "IP2: failed to unregister tty driver (%d)\n", err); } put_tty_driver(ip2_tty_driver); - if ( ( err = unregister_chrdev ( IP2_IPL_MAJOR, pcIpl ) ) ) { - printk(KERN_ERR "IP2: failed to unregister IPL driver (%d)\n", err); - } + unregister_chrdev(IP2_IPL_MAJOR, pcIpl); remove_proc_entry("ip2mem", &proc_root); // free memory diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index b5df7e6..6a01dd9 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -2639,10 +2639,9 @@ int ipmi_register_smi(struct ipmi_smi_handlers *handlers, return -ENODEV; } - intf = kmalloc(sizeof(*intf), GFP_KERNEL); + intf = kzalloc(sizeof(*intf), GFP_KERNEL); if (!intf) return -ENOMEM; - memset(intf, 0, sizeof(*intf)); intf->ipmi_version_major = ipmi_version_major(device_id); intf->ipmi_version_minor = ipmi_version_minor(device_id); diff --git a/drivers/char/mbcs.c b/drivers/char/mbcs.c index 57f9115..7ee5d944 100644 --- a/drivers/char/mbcs.c +++ b/drivers/char/mbcs.c @@ -39,14 +39,14 @@ #else #define DBG(fmt...) #endif -int mbcs_major; +static int mbcs_major; -LIST_HEAD(soft_list); +static LIST_HEAD(soft_list); /* * file operations */ -const struct file_operations mbcs_ops = { +static const struct file_operations mbcs_ops = { .open = mbcs_open, .llseek = mbcs_sram_llseek, .read = mbcs_sram_read, @@ -377,7 +377,7 @@ dmaread_exit: return rv; } -int mbcs_open(struct inode *ip, struct file *fp) +static int mbcs_open(struct inode *ip, struct file *fp) { struct mbcs_soft *soft; int minor; @@ -394,7 +394,7 @@ int mbcs_open(struct inode *ip, struct file *fp) return -ENODEV; } -ssize_t mbcs_sram_read(struct file * fp, char __user *buf, size_t len, loff_t * off) +static ssize_t mbcs_sram_read(struct file * fp, char __user *buf, size_t len, loff_t * off) { struct cx_dev *cx_dev = fp->private_data; struct mbcs_soft *soft = cx_dev->soft; @@ -418,7 +418,7 @@ ssize_t mbcs_sram_read(struct file * fp, char __user *buf, size_t len, loff_t * return rv; } -ssize_t +static ssize_t mbcs_sram_write(struct file * fp, const char __user *buf, size_t len, loff_t * off) { struct cx_dev *cx_dev = fp->private_data; @@ -443,7 +443,7 @@ mbcs_sram_write(struct file * fp, const char __user *buf, size_t len, loff_t * o return rv; } -loff_t mbcs_sram_llseek(struct file * filp, loff_t off, int whence) +static loff_t mbcs_sram_llseek(struct file * filp, loff_t off, int whence) { loff_t newpos; @@ -491,7 +491,7 @@ static void mbcs_gscr_pioaddr_set(struct mbcs_soft *soft) soft->gscr_addr = mbcs_pioaddr(soft, MBCS_GSCR_START); } -int mbcs_gscr_mmap(struct file *fp, struct vm_area_struct *vma) +static int mbcs_gscr_mmap(struct file *fp, struct vm_area_struct *vma) { struct cx_dev *cx_dev = fp->private_data; struct mbcs_soft *soft = cx_dev->soft; @@ -793,7 +793,7 @@ static int mbcs_remove(struct cx_dev *dev) return 0; } -const struct cx_device_id __devinitdata mbcs_id_table[] = { +static const struct cx_device_id __devinitdata mbcs_id_table[] = { { .part_num = MBCS_PART_NUM, .mfg_num = MBCS_MFG_NUM, @@ -807,7 +807,7 @@ const struct cx_device_id __devinitdata mbcs_id_table[] = { MODULE_DEVICE_TABLE(cx, mbcs_id_table); -struct cx_drv mbcs_driver = { +static struct cx_drv mbcs_driver = { .name = DEVICE_NAME, .id_table = mbcs_id_table, .probe = mbcs_probe, @@ -816,12 +816,7 @@ struct cx_drv mbcs_driver = { static void __exit mbcs_exit(void) { - int rv; - - rv = unregister_chrdev(mbcs_major, DEVICE_NAME); - if (rv < 0) - DBG(KERN_ALERT "Error in unregister_chrdev: %d\n", rv); - + unregister_chrdev(mbcs_major, DEVICE_NAME); cx_driver_unregister(&mbcs_driver); } diff --git a/drivers/char/mbcs.h b/drivers/char/mbcs.h index e7fd47e..c9905a3 100644 --- a/drivers/char/mbcs.h +++ b/drivers/char/mbcs.h @@ -542,12 +542,12 @@ struct mbcs_soft { struct semaphore algolock; }; -extern int mbcs_open(struct inode *ip, struct file *fp); -extern ssize_t mbcs_sram_read(struct file *fp, char __user *buf, size_t len, +static int mbcs_open(struct inode *ip, struct file *fp); +static ssize_t mbcs_sram_read(struct file *fp, char __user *buf, size_t len, loff_t * off); -extern ssize_t mbcs_sram_write(struct file *fp, const char __user *buf, size_t len, +static ssize_t mbcs_sram_write(struct file *fp, const char __user *buf, size_t len, loff_t * off); -extern loff_t mbcs_sram_llseek(struct file *filp, loff_t off, int whence); -extern int mbcs_gscr_mmap(struct file *fp, struct vm_area_struct *vma); +static loff_t mbcs_sram_llseek(struct file *filp, loff_t off, int whence); +static int mbcs_gscr_mmap(struct file *fp, struct vm_area_struct *vma); #endif // __MBCS_H__ diff --git a/drivers/char/pcmcia/synclink_cs.c b/drivers/char/pcmcia/synclink_cs.c index 13808f6..2b88931 100644 --- a/drivers/char/pcmcia/synclink_cs.c +++ b/drivers/char/pcmcia/synclink_cs.c @@ -540,13 +540,12 @@ static int mgslpc_probe(struct pcmcia_device *link) if (debug_level >= DEBUG_LEVEL_INFO) printk("mgslpc_attach\n"); - info = kmalloc(sizeof(MGSLPC_INFO), GFP_KERNEL); + info = kzalloc(sizeof(MGSLPC_INFO), GFP_KERNEL); if (!info) { printk("Error can't allocate device instance data\n"); return -ENOMEM; } - memset(info, 0, sizeof(MGSLPC_INFO)); info->magic = MGSLPC_MAGIC; INIT_WORK(&info->task, bh_handler); info->max_frame_size = 4096; diff --git a/drivers/char/random.c b/drivers/char/random.c index 7f52712..397c714 100644 --- a/drivers/char/random.c +++ b/drivers/char/random.c @@ -693,9 +693,14 @@ static void xfer_secondary_pool(struct entropy_store *r, size_t nbytes) if (r->pull && r->entropy_count < nbytes * 8 && r->entropy_count < r->poolinfo->POOLBITS) { - int bytes = max_t(int, random_read_wakeup_thresh / 8, - min_t(int, nbytes, sizeof(tmp))); + /* If we're limited, always leave two wakeup worth's BITS */ int rsvd = r->limit ? 0 : random_read_wakeup_thresh/4; + int bytes = nbytes; + + /* pull at least as many as BYTES as wakeup BITS */ + bytes = max_t(int, bytes, random_read_wakeup_thresh / 8); + /* but never more than the buffer size */ + bytes = min_t(int, bytes, sizeof(tmp)); DEBUG_ENT("going to reseed %s with %d bits " "(%d of %d requested)\n", diff --git a/drivers/char/rio/rio_linux.c b/drivers/char/rio/rio_linux.c index 294e9cb..0ce9667 100644 --- a/drivers/char/rio/rio_linux.c +++ b/drivers/char/rio/rio_linux.c @@ -803,9 +803,7 @@ static void *ckmalloc(int size) { void *p; - p = kmalloc(size, GFP_KERNEL); - if (p) - memset(p, 0, size); + p = kzalloc(size, GFP_KERNEL); return p; } diff --git a/drivers/char/rio/riocmd.c b/drivers/char/rio/riocmd.c index 8cc60b6..7321d00 100644 --- a/drivers/char/rio/riocmd.c +++ b/drivers/char/rio/riocmd.c @@ -556,9 +556,7 @@ struct CmdBlk *RIOGetCmdBlk(void) { struct CmdBlk *CmdBlkP; - CmdBlkP = kmalloc(sizeof(struct CmdBlk), GFP_ATOMIC); - if (CmdBlkP) - memset(CmdBlkP, 0, sizeof(struct CmdBlk)); + CmdBlkP = kzalloc(sizeof(struct CmdBlk), GFP_ATOMIC); return CmdBlkP; } diff --git a/drivers/char/rio/riotable.c b/drivers/char/rio/riotable.c index 7e98835..991119c 100644 --- a/drivers/char/rio/riotable.c +++ b/drivers/char/rio/riotable.c @@ -863,8 +863,7 @@ int RIOReMapPorts(struct rio_info *p, struct Host *HostP, struct Map *HostMapP) if (PortP->TxRingBuffer) memset(PortP->TxRingBuffer, 0, p->RIOBufferSize); else if (p->RIOBufferSize) { - PortP->TxRingBuffer = kmalloc(p->RIOBufferSize, GFP_KERNEL); - memset(PortP->TxRingBuffer, 0, p->RIOBufferSize); + PortP->TxRingBuffer = kzalloc(p->RIOBufferSize, GFP_KERNEL); } PortP->TxBufferOut = 0; PortP->TxBufferIn = 0; diff --git a/drivers/char/rocket.c b/drivers/char/rocket.c index 0270080..56cbba7 100644 --- a/drivers/char/rocket.c +++ b/drivers/char/rocket.c @@ -635,12 +635,11 @@ static void init_r_port(int board, int aiop, int chan, struct pci_dev *pci_dev) ctlp = sCtlNumToCtlPtr(board); /* Get a r_port struct for the port, fill it in and save it globally, indexed by line number */ - info = kmalloc(sizeof (struct r_port), GFP_KERNEL); + info = kzalloc(sizeof (struct r_port), GFP_KERNEL); if (!info) { printk(KERN_INFO "Couldn't allocate info struct for line #%d\n", line); return; } - memset(info, 0, sizeof (struct r_port)); info->magic = RPORT_MAGIC; info->line = line; diff --git a/drivers/char/stallion.c b/drivers/char/stallion.c index 93d0bb8..4a80b2f 100644 --- a/drivers/char/stallion.c +++ b/drivers/char/stallion.c @@ -4795,7 +4795,6 @@ static void __exit stallion_module_exit(void) { struct stlbrd *brdp; unsigned int i, j; - int retval; pr_debug("cleanup_module()\n"); @@ -4818,9 +4817,7 @@ static void __exit stallion_module_exit(void) for (i = 0; i < 4; i++) class_device_destroy(stallion_class, MKDEV(STL_SIOMEMMAJOR, i)); - if ((retval = unregister_chrdev(STL_SIOMEMMAJOR, "staliomem"))) - printk("STALLION: failed to un-register serial memory device, " - "errno=%d\n", -retval); + unregister_chrdev(STL_SIOMEMMAJOR, "staliomem"); class_destroy(stallion_class); pci_unregister_driver(&stl_pcidriver); diff --git a/drivers/char/synclink.c b/drivers/char/synclink.c index f53e51d..fdc256b 100644 --- a/drivers/char/synclink.c +++ b/drivers/char/synclink.c @@ -4324,13 +4324,12 @@ static struct mgsl_struct* mgsl_allocate_device(void) { struct mgsl_struct *info; - info = kmalloc(sizeof(struct mgsl_struct), + info = kzalloc(sizeof(struct mgsl_struct), GFP_KERNEL); if (!info) { printk("Error can't allocate device instance data\n"); } else { - memset(info, 0, sizeof(struct mgsl_struct)); info->magic = MGSL_MAGIC; INIT_WORK(&info->task, mgsl_bh_handler); info->max_frame_size = 4096; diff --git a/drivers/char/synclink_gt.c b/drivers/char/synclink_gt.c index 428b514..372a37e 100644 --- a/drivers/char/synclink_gt.c +++ b/drivers/char/synclink_gt.c @@ -3414,13 +3414,12 @@ static struct slgt_info *alloc_dev(int adapter_num, int port_num, struct pci_dev { struct slgt_info *info; - info = kmalloc(sizeof(struct slgt_info), GFP_KERNEL); + info = kzalloc(sizeof(struct slgt_info), GFP_KERNEL); if (!info) { DBGERR(("%s device alloc failed adapter=%d port=%d\n", driver_name, adapter_num, port_num)); } else { - memset(info, 0, sizeof(struct slgt_info)); info->magic = MGSL_MAGIC; INIT_WORK(&info->task, bh_handler); info->max_frame_size = 4096; diff --git a/drivers/char/synclinkmp.c b/drivers/char/synclinkmp.c index a65407b..c63013b 100644 --- a/drivers/char/synclinkmp.c +++ b/drivers/char/synclinkmp.c @@ -3786,14 +3786,13 @@ static SLMP_INFO *alloc_dev(int adapter_num, int port_num, struct pci_dev *pdev) { SLMP_INFO *info; - info = kmalloc(sizeof(SLMP_INFO), + info = kzalloc(sizeof(SLMP_INFO), GFP_KERNEL); if (!info) { printk("%s(%d) Error can't allocate device instance data for adapter %d, port %d\n", __FILE__,__LINE__, adapter_num, port_num); } else { - memset(info, 0, sizeof(SLMP_INFO)); info->magic = MGSL_MAGIC; INIT_WORK(&info->task, bh_handler); info->max_frame_size = 4096; diff --git a/drivers/char/viotape.c b/drivers/char/viotape.c index db57277..e12275d 100644 --- a/drivers/char/viotape.c +++ b/drivers/char/viotape.c @@ -1098,15 +1098,10 @@ static int chg_state(int index, unsigned char new_state, struct file *file) /* Cleanup */ static void __exit viotap_exit(void) { - int ret; - remove_proc_entry("iSeries/viotape", NULL); vio_unregister_driver(&viotape_driver); class_destroy(tape_class); - ret = unregister_chrdev(VIOTAPE_MAJOR, "viotape"); - if (ret < 0) - printk(VIOTAPE_KERN_WARN "Error unregistering device: %d\n", - ret); + unregister_chrdev(VIOTAPE_MAJOR, "viotape"); if (viotape_unitinfo) dma_free_coherent(iSeries_vio_dev, sizeof(viotape_unitinfo[0]) * VIOTAPE_MAX_TAPE, diff --git a/drivers/char/watchdog/mpcore_wdt.c b/drivers/char/watchdog/mpcore_wdt.c index e88947f..0d2b277 100644 --- a/drivers/char/watchdog/mpcore_wdt.c +++ b/drivers/char/watchdog/mpcore_wdt.c @@ -328,12 +328,11 @@ static int __devinit mpcore_wdt_probe(struct platform_device *dev) goto err_out; } - wdt = kmalloc(sizeof(struct mpcore_wdt), GFP_KERNEL); + wdt = kzalloc(sizeof(struct mpcore_wdt), GFP_KERNEL); if (!wdt) { ret = -ENOMEM; goto err_out; } - memset(wdt, 0, sizeof(struct mpcore_wdt)); wdt->dev = &dev->dev; wdt->irq = platform_get_irq(dev, 0); diff --git a/drivers/char/watchdog/pcwd_usb.c b/drivers/char/watchdog/pcwd_usb.c index 1e7a671..0f3fd6c 100644 --- a/drivers/char/watchdog/pcwd_usb.c +++ b/drivers/char/watchdog/pcwd_usb.c @@ -626,12 +626,11 @@ static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_devi maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); /* allocate memory for our device and initialize it */ - usb_pcwd = kmalloc (sizeof(struct usb_pcwd_private), GFP_KERNEL); + usb_pcwd = kzalloc (sizeof(struct usb_pcwd_private), GFP_KERNEL); if (usb_pcwd == NULL) { printk(KERN_ERR PFX "Out of memory\n"); goto error; } - memset (usb_pcwd, 0x00, sizeof (*usb_pcwd)); usb_pcwd_device = usb_pcwd; diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index debf1d8..1724c41 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -3,18 +3,18 @@ # Copyright (c) 2003 Linux Networx # Licensed and distributed under the GPL # -# $Id: Kconfig,v 1.4.2.7 2005/07/08 22:05:38 dsp_llnl Exp $ -# menuconfig EDAC - tristate "EDAC - error detection and reporting (EXPERIMENTAL)" + bool "EDAC - error detection and reporting (EXPERIMENTAL)" depends on HAS_IOMEM - depends on X86 && EXPERIMENTAL + depends on EXPERIMENTAL + depends on X86 || MIPS || PPC help EDAC is designed to report errors in the core system. These are low-level errors that are reported in the CPU or - supporting chipset: memory errors, cache errors, PCI errors, - thermal throttling, etc.. If unsure, select 'Y'. + supporting chipset or other subsystems: + memory errors, cache errors, PCI errors, thermal throttling, etc.. + If unsure, select 'Y'. If this code is reporting problems on your system, please see the EDAC project web pages for more information at: @@ -73,6 +73,14 @@ config EDAC_E752X Support for error detection and correction on the Intel E7520, E7525, E7320 server chipsets. +config EDAC_I82443BXGX + tristate "Intel 82443BX/GX (440BX/GX)" + depends on EDAC_MM_EDAC && PCI && X86_32 + depends on BROKEN + help + Support for error detection and correction on the Intel + 82443BX/GX memory controllers (440BX/GX chipsets). + config EDAC_I82875P tristate "Intel 82875p (D82875P, E7210)" depends on EDAC_MM_EDAC && PCI && X86_32 @@ -80,6 +88,20 @@ config EDAC_I82875P Support for error detection and correction on the Intel DP82785P and E7210 server chipsets. +config EDAC_I82975X + tristate "Intel 82975x (D82975x)" + depends on EDAC_MM_EDAC && PCI && X86 + help + Support for error detection and correction on the Intel + DP82975x server chipsets. + +config EDAC_I3000 + tristate "Intel 3000/3010" + depends on EDAC_MM_EDAC && PCI && X86_32 + help + Support for error detection and correction on the Intel + 3000 and 3010 server chipsets. + config EDAC_I82860 tristate "Intel 82860" depends on EDAC_MM_EDAC && PCI && X86_32 @@ -94,15 +116,20 @@ config EDAC_R82600 Support for error detection and correction on the Radisys 82600 embedded chipset. -choice - prompt "Error detecting method" - default EDAC_POLL +config EDAC_I5000 + tristate "Intel Greencreek/Blackford chipset" + depends on EDAC_MM_EDAC && X86 && PCI + help + Support for error detection and correction the Intel + Greekcreek/Blackford chipsets. -config EDAC_POLL - bool "Poll for errors" +config EDAC_PASEMI + tristate "PA Semi PWRficient" + depends on EDAC_MM_EDAC && PCI + depends on PPC help - Poll the chipset periodically to detect errors. + Support for error detection and correction on PA Semi + PWRficient. -endchoice endif # EDAC diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index 93137fd..02c09f0 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -5,14 +5,27 @@ # This file may be distributed under the terms of the # GNU General Public License. # -# $Id: Makefile,v 1.4.2.3 2005/07/08 22:05:38 dsp_llnl Exp $ -obj-$(CONFIG_EDAC_MM_EDAC) += edac_mc.o +obj-$(CONFIG_EDAC) := edac_stub.o +obj-$(CONFIG_EDAC_MM_EDAC) += edac_core.o + +edac_core-objs := edac_mc.o edac_device.o edac_mc_sysfs.o edac_pci_sysfs.o +edac_core-objs += edac_module.o edac_device_sysfs.o + +ifdef CONFIG_PCI +edac_core-objs += edac_pci.o edac_pci_sysfs.o +endif + obj-$(CONFIG_EDAC_AMD76X) += amd76x_edac.o +obj-$(CONFIG_EDAC_I5000) += i5000_edac.o obj-$(CONFIG_EDAC_E7XXX) += e7xxx_edac.o obj-$(CONFIG_EDAC_E752X) += e752x_edac.o +obj-$(CONFIG_EDAC_I82443BXGX) += i82443bxgx_edac.o obj-$(CONFIG_EDAC_I82875P) += i82875p_edac.o +obj-$(CONFIG_EDAC_I82975X) += i82975x_edac.o +obj-$(CONFIG_EDAC_I3000) += i3000_edac.o obj-$(CONFIG_EDAC_I82860) += i82860_edac.o obj-$(CONFIG_EDAC_R82600) += r82600_edac.o +obj-$(CONFIG_EDAC_PASEMI) += pasemi_edac.o diff --git a/drivers/edac/amd76x_edac.c b/drivers/edac/amd76x_edac.c index f79f6b5..f220754 100644 --- a/drivers/edac/amd76x_edac.c +++ b/drivers/edac/amd76x_edac.c @@ -17,9 +17,9 @@ #include <linux/pci.h> #include <linux/pci_ids.h> #include <linux/slab.h> -#include "edac_mc.h" +#include "edac_core.h" -#define AMD76X_REVISION " Ver: 2.0.1 " __DATE__ +#define AMD76X_REVISION " Ver: 2.0.2 " __DATE__ #define EDAC_MOD_STR "amd76x_edac" #define amd76x_printk(level, fmt, arg...) \ @@ -86,13 +86,13 @@ struct amd76x_dev_info { static const struct amd76x_dev_info amd76x_devs[] = { [AMD761] = { - .ctl_name = "AMD761" - }, + .ctl_name = "AMD761"}, [AMD762] = { - .ctl_name = "AMD762" - }, + .ctl_name = "AMD762"}, }; +static struct edac_pci_ctl_info *amd76x_pci; + /** * amd76x_get_error_info - fetch error information * @mci: Memory controller @@ -102,21 +102,21 @@ static const struct amd76x_dev_info amd76x_devs[] = { * on the chip so that further errors will be reported */ static void amd76x_get_error_info(struct mem_ctl_info *mci, - struct amd76x_error_info *info) + struct amd76x_error_info *info) { struct pci_dev *pdev; pdev = to_pci_dev(mci->dev); pci_read_config_dword(pdev, AMD76X_ECC_MODE_STATUS, - &info->ecc_mode_status); + &info->ecc_mode_status); if (info->ecc_mode_status & BIT(8)) pci_write_bits32(pdev, AMD76X_ECC_MODE_STATUS, - (u32) BIT(8), (u32) BIT(8)); + (u32) BIT(8), (u32) BIT(8)); if (info->ecc_mode_status & BIT(9)) pci_write_bits32(pdev, AMD76X_ECC_MODE_STATUS, - (u32) BIT(9), (u32) BIT(9)); + (u32) BIT(9), (u32) BIT(9)); } /** @@ -130,7 +130,8 @@ static void amd76x_get_error_info(struct mem_ctl_info *mci, * then attempt to handle and clean up after the error */ static int amd76x_process_error_info(struct mem_ctl_info *mci, - struct amd76x_error_info *info, int handle_errors) + struct amd76x_error_info *info, + int handle_errors) { int error_found; u32 row; @@ -138,7 +139,7 @@ static int amd76x_process_error_info(struct mem_ctl_info *mci, error_found = 0; /* - * Check for an uncorrectable error + * Check for an uncorrectable error */ if (info->ecc_mode_status & BIT(8)) { error_found = 1; @@ -146,12 +147,12 @@ static int amd76x_process_error_info(struct mem_ctl_info *mci, if (handle_errors) { row = (info->ecc_mode_status >> 4) & 0xf; edac_mc_handle_ue(mci, mci->csrows[row].first_page, 0, - row, mci->ctl_name); + row, mci->ctl_name); } } /* - * Check for a correctable error + * Check for a correctable error */ if (info->ecc_mode_status & BIT(9)) { error_found = 1; @@ -159,7 +160,7 @@ static int amd76x_process_error_info(struct mem_ctl_info *mci, if (handle_errors) { row = info->ecc_mode_status & 0xf; edac_mc_handle_ce(mci, mci->csrows[row].first_page, 0, - 0, row, 0, mci->ctl_name); + 0, row, 0, mci->ctl_name); } } @@ -182,7 +183,7 @@ static void amd76x_check(struct mem_ctl_info *mci) } static void amd76x_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, - enum edac_type edac_mode) + enum edac_type edac_mode) { struct csrow_info *csrow; u32 mba, mba_base, mba_mask, dms; @@ -193,8 +194,7 @@ static void amd76x_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, /* find the DRAM Chip Select Base address and mask */ pci_read_config_dword(pdev, - AMD76X_MEM_BASE_ADDR + (index * 4), - &mba); + AMD76X_MEM_BASE_ADDR + (index * 4), &mba); if (!(mba & BIT(0))) continue; @@ -238,7 +238,7 @@ static int amd76x_probe1(struct pci_dev *pdev, int dev_idx) debugf0("%s()\n", __func__); pci_read_config_dword(pdev, AMD76X_ECC_MODE_STATUS, &ems); ems_mode = (ems >> 10) & 0x3; - mci = edac_mc_alloc(0, AMD76X_NR_CSROWS, AMD76X_NR_CHANS); + mci = edac_mc_alloc(0, AMD76X_NR_CSROWS, AMD76X_NR_CHANS, 0); if (mci == NULL) { return -ENOMEM; @@ -249,24 +249,36 @@ static int amd76x_probe1(struct pci_dev *pdev, int dev_idx) mci->mtype_cap = MEM_FLAG_RDDR; mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_EC | EDAC_FLAG_SECDED; mci->edac_cap = ems_mode ? - (EDAC_FLAG_EC | EDAC_FLAG_SECDED) : EDAC_FLAG_NONE; + (EDAC_FLAG_EC | EDAC_FLAG_SECDED) : EDAC_FLAG_NONE; mci->mod_name = EDAC_MOD_STR; mci->mod_ver = AMD76X_REVISION; mci->ctl_name = amd76x_devs[dev_idx].ctl_name; + mci->dev_name = pci_name(pdev); mci->edac_check = amd76x_check; mci->ctl_page_to_phys = NULL; amd76x_init_csrows(mci, pdev, ems_modes[ems_mode]); - amd76x_get_error_info(mci, &discard); /* clear counters */ + amd76x_get_error_info(mci, &discard); /* clear counters */ /* Here we assume that we will never see multiple instances of this * type of memory controller. The ID is therefore hardcoded to 0. */ - if (edac_mc_add_mc(mci,0)) { + if (edac_mc_add_mc(mci)) { debugf3("%s(): failed edac_mc_add_mc()\n", __func__); goto fail; } + /* allocating generic PCI control info */ + amd76x_pci = edac_pci_create_generic_ctl(&pdev->dev, EDAC_MOD_STR); + if (!amd76x_pci) { + printk(KERN_WARNING + "%s(): Unable to create PCI control\n", + __func__); + printk(KERN_WARNING + "%s(): PCI error report via EDAC not setup\n", + __func__); + } + /* get this far and it's successful */ debugf3("%s(): success\n", __func__); return 0; @@ -278,7 +290,7 @@ fail: /* returns count (>= 0), or negative on error */ static int __devinit amd76x_init_one(struct pci_dev *pdev, - const struct pci_device_id *ent) + const struct pci_device_id *ent) { debugf0("%s()\n", __func__); @@ -300,6 +312,9 @@ static void __devexit amd76x_remove_one(struct pci_dev *pdev) debugf0("%s()\n", __func__); + if (amd76x_pci) + edac_pci_release_generic_ctl(amd76x_pci); + if ((mci = edac_mc_del_mc(&pdev->dev)) == NULL) return; @@ -308,16 +323,14 @@ static void __devexit amd76x_remove_one(struct pci_dev *pdev) static const struct pci_device_id amd76x_pci_tbl[] __devinitdata = { { - PCI_VEND_DEV(AMD, FE_GATE_700C), PCI_ANY_ID, PCI_ANY_ID, 0, 0, - AMD762 - }, + PCI_VEND_DEV(AMD, FE_GATE_700C), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + AMD762}, { - PCI_VEND_DEV(AMD, FE_GATE_700E), PCI_ANY_ID, PCI_ANY_ID, 0, 0, - AMD761 - }, + PCI_VEND_DEV(AMD, FE_GATE_700E), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + AMD761}, { - 0, - } /* 0 terminated list. */ + 0, + } /* 0 terminated list. */ }; MODULE_DEVICE_TABLE(pci, amd76x_pci_tbl); diff --git a/drivers/edac/e752x_edac.c b/drivers/edac/e752x_edac.c index 8bcc887..3bba224 100644 --- a/drivers/edac/e752x_edac.c +++ b/drivers/edac/e752x_edac.c @@ -22,13 +22,16 @@ #include <linux/pci.h> #include <linux/pci_ids.h> #include <linux/slab.h> -#include "edac_mc.h" +#include <linux/edac.h> +#include "edac_core.h" -#define E752X_REVISION " Ver: 2.0.1 " __DATE__ +#define E752X_REVISION " Ver: 2.0.2 " __DATE__ #define EDAC_MOD_STR "e752x_edac" static int force_function_unhide; +static struct edac_pci_ctl_info *e752x_pci; + #define e752x_printk(level, fmt, arg...) \ edac_printk(level, "e752x", fmt, ##arg) @@ -203,25 +206,22 @@ static const struct e752x_dev_info e752x_devs[] = { [E7520] = { .err_dev = PCI_DEVICE_ID_INTEL_7520_1_ERR, .ctl_dev = PCI_DEVICE_ID_INTEL_7520_0, - .ctl_name = "E7520" - }, + .ctl_name = "E7520"}, [E7525] = { .err_dev = PCI_DEVICE_ID_INTEL_7525_1_ERR, .ctl_dev = PCI_DEVICE_ID_INTEL_7525_0, - .ctl_name = "E7525" - }, + .ctl_name = "E7525"}, [E7320] = { .err_dev = PCI_DEVICE_ID_INTEL_7320_1_ERR, .ctl_dev = PCI_DEVICE_ID_INTEL_7320_0, - .ctl_name = "E7320" - }, + .ctl_name = "E7320"}, }; static unsigned long ctl_page_to_phys(struct mem_ctl_info *mci, - unsigned long page) + unsigned long page) { u32 remap; - struct e752x_pvt *pvt = (struct e752x_pvt *) mci->pvt_info; + struct e752x_pvt *pvt = (struct e752x_pvt *)mci->pvt_info; debugf3("%s()\n", __func__); @@ -241,13 +241,13 @@ static unsigned long ctl_page_to_phys(struct mem_ctl_info *mci, } static void do_process_ce(struct mem_ctl_info *mci, u16 error_one, - u32 sec1_add, u16 sec1_syndrome) + u32 sec1_add, u16 sec1_syndrome) { u32 page; int row; int channel; int i; - struct e752x_pvt *pvt = (struct e752x_pvt *) mci->pvt_info; + struct e752x_pvt *pvt = (struct e752x_pvt *)mci->pvt_info; debugf3("%s()\n", __func__); @@ -261,7 +261,8 @@ static void do_process_ce(struct mem_ctl_info *mci, u16 error_one, e752x_printk(KERN_WARNING, "Test row %d Table %d %d %d %d %d %d %d %d\n", row, pvt->map[0], pvt->map[1], pvt->map[2], pvt->map[3], - pvt->map[4], pvt->map[5], pvt->map[6], pvt->map[7]); + pvt->map[4], pvt->map[5], pvt->map[6], + pvt->map[7]); /* test for channel remapping */ for (i = 0; i < 8; i++) { @@ -275,24 +276,22 @@ static void do_process_ce(struct mem_ctl_info *mci, u16 error_one, row = i; else e752x_mc_printk(mci, KERN_WARNING, - "row %d not found in remap table\n", row); + "row %d not found in remap table\n", + row); } else row = edac_mc_find_csrow_by_page(mci, page); /* 0 = channel A, 1 = channel B */ channel = !(error_one & 1); - if (!pvt->map_type) - row = 7 - row; - /* e752x mc reads 34:6 of the DRAM linear address */ edac_mc_handle_ce(mci, page, offset_in_page(sec1_add << 4), sec1_syndrome, row, channel, "e752x CE"); } static inline void process_ce(struct mem_ctl_info *mci, u16 error_one, - u32 sec1_add, u16 sec1_syndrome, int *error_found, - int handle_error) + u32 sec1_add, u16 sec1_syndrome, int *error_found, + int handle_error) { *error_found = 1; @@ -301,11 +300,11 @@ static inline void process_ce(struct mem_ctl_info *mci, u16 error_one, } static void do_process_ue(struct mem_ctl_info *mci, u16 error_one, - u32 ded_add, u32 scrb_add) + u32 ded_add, u32 scrb_add) { u32 error_2b, block_page; int row; - struct e752x_pvt *pvt = (struct e752x_pvt *) mci->pvt_info; + struct e752x_pvt *pvt = (struct e752x_pvt *)mci->pvt_info; debugf3("%s()\n", __func__); @@ -316,14 +315,14 @@ static void do_process_ue(struct mem_ctl_info *mci, u16 error_one, block_page = error_2b >> (PAGE_SHIFT - 4); row = pvt->mc_symmetric ? - /* chip select are bits 14 & 13 */ + /* chip select are bits 14 & 13 */ ((block_page >> 1) & 3) : edac_mc_find_csrow_by_page(mci, block_page); /* e752x mc reads 34:6 of the DRAM linear address */ edac_mc_handle_ue(mci, block_page, - offset_in_page(error_2b << 4), - row, "e752x UE from Read"); + offset_in_page(error_2b << 4), + row, "e752x UE from Read"); } if (error_one & 0x0404) { error_2b = scrb_add; @@ -332,19 +331,20 @@ static void do_process_ue(struct mem_ctl_info *mci, u16 error_one, block_page = error_2b >> (PAGE_SHIFT - 4); row = pvt->mc_symmetric ? - /* chip select are bits 14 & 13 */ + /* chip select are bits 14 & 13 */ ((block_page >> 1) & 3) : edac_mc_find_csrow_by_page(mci, block_page); /* e752x mc reads 34:6 of the DRAM linear address */ edac_mc_handle_ue(mci, block_page, - offset_in_page(error_2b << 4), - row, "e752x UE from Scruber"); + offset_in_page(error_2b << 4), + row, "e752x UE from Scruber"); } } static inline void process_ue(struct mem_ctl_info *mci, u16 error_one, - u32 ded_add, u32 scrb_add, int *error_found, int handle_error) + u32 ded_add, u32 scrb_add, int *error_found, + int handle_error) { *error_found = 1; @@ -353,7 +353,7 @@ static inline void process_ue(struct mem_ctl_info *mci, u16 error_one, } static inline void process_ue_no_info_wr(struct mem_ctl_info *mci, - int *error_found, int handle_error) + int *error_found, int handle_error) { *error_found = 1; @@ -365,24 +365,24 @@ static inline void process_ue_no_info_wr(struct mem_ctl_info *mci, } static void do_process_ded_retry(struct mem_ctl_info *mci, u16 error, - u32 retry_add) + u32 retry_add) { u32 error_1b, page; int row; - struct e752x_pvt *pvt = (struct e752x_pvt *) mci->pvt_info; + struct e752x_pvt *pvt = (struct e752x_pvt *)mci->pvt_info; error_1b = retry_add; - page = error_1b >> (PAGE_SHIFT - 4); /* convert the addr to 4k page */ - row = pvt->mc_symmetric ? - ((page >> 1) & 3) : /* chip select are bits 14 & 13 */ + page = error_1b >> (PAGE_SHIFT - 4); /* convert the addr to 4k page */ + row = pvt->mc_symmetric ? ((page >> 1) & 3) : /* chip select are bits 14 & 13 */ edac_mc_find_csrow_by_page(mci, page); e752x_mc_printk(mci, KERN_WARNING, - "CE page 0x%lx, row %d : Memory read retry\n", - (long unsigned int) page, row); + "CE page 0x%lx, row %d : Memory read retry\n", + (long unsigned int)page, row); } static inline void process_ded_retry(struct mem_ctl_info *mci, u16 error, - u32 retry_add, int *error_found, int handle_error) + u32 retry_add, int *error_found, + int handle_error) { *error_found = 1; @@ -391,7 +391,7 @@ static inline void process_ded_retry(struct mem_ctl_info *mci, u16 error, } static inline void process_threshold_ce(struct mem_ctl_info *mci, u16 error, - int *error_found, int handle_error) + int *error_found, int handle_error) { *error_found = 1; @@ -420,7 +420,7 @@ static void do_global_error(int fatal, u32 errors) } static inline void global_error(int fatal, u32 errors, int *error_found, - int handle_error) + int handle_error) { *error_found = 1; @@ -447,7 +447,7 @@ static void do_hub_error(int fatal, u8 errors) } static inline void hub_error(int fatal, u8 errors, int *error_found, - int handle_error) + int handle_error) { *error_found = 1; @@ -505,7 +505,7 @@ static void do_sysbus_error(int fatal, u32 errors) } static inline void sysbus_error(int fatal, u32 errors, int *error_found, - int handle_error) + int handle_error) { *error_found = 1; @@ -514,7 +514,7 @@ static inline void sysbus_error(int fatal, u32 errors, int *error_found, } static void e752x_check_hub_interface(struct e752x_error_info *info, - int *error_found, int handle_error) + int *error_found, int handle_error) { u8 stat8; @@ -522,33 +522,32 @@ static void e752x_check_hub_interface(struct e752x_error_info *info, stat8 = info->hi_ferr; - if(stat8 & 0x7f) { /* Error, so process */ + if (stat8 & 0x7f) { /* Error, so process */ stat8 &= 0x7f; - if(stat8 & 0x2b) + if (stat8 & 0x2b) hub_error(1, stat8 & 0x2b, error_found, handle_error); - if(stat8 & 0x54) + if (stat8 & 0x54) hub_error(0, stat8 & 0x54, error_found, handle_error); } - //pci_read_config_byte(dev,E752X_HI_NERR,&stat8); stat8 = info->hi_nerr; - if(stat8 & 0x7f) { /* Error, so process */ + if (stat8 & 0x7f) { /* Error, so process */ stat8 &= 0x7f; if (stat8 & 0x2b) hub_error(1, stat8 & 0x2b, error_found, handle_error); - if(stat8 & 0x54) + if (stat8 & 0x54) hub_error(0, stat8 & 0x54, error_found, handle_error); } } static void e752x_check_sysbus(struct e752x_error_info *info, - int *error_found, int handle_error) + int *error_found, int handle_error) { u32 stat32, error32; @@ -556,47 +555,47 @@ static void e752x_check_sysbus(struct e752x_error_info *info, stat32 = info->sysbus_ferr + (info->sysbus_nerr << 16); if (stat32 == 0) - return; /* no errors */ + return; /* no errors */ error32 = (stat32 >> 16) & 0x3ff; stat32 = stat32 & 0x3ff; - if(stat32 & 0x087) + if (stat32 & 0x087) sysbus_error(1, stat32 & 0x087, error_found, handle_error); - if(stat32 & 0x378) + if (stat32 & 0x378) sysbus_error(0, stat32 & 0x378, error_found, handle_error); - if(error32 & 0x087) + if (error32 & 0x087) sysbus_error(1, error32 & 0x087, error_found, handle_error); - if(error32 & 0x378) + if (error32 & 0x378) sysbus_error(0, error32 & 0x378, error_found, handle_error); } -static void e752x_check_membuf (struct e752x_error_info *info, - int *error_found, int handle_error) +static void e752x_check_membuf(struct e752x_error_info *info, + int *error_found, int handle_error) { u8 stat8; stat8 = info->buf_ferr; - if (stat8 & 0x0f) { /* Error, so process */ + if (stat8 & 0x0f) { /* Error, so process */ stat8 &= 0x0f; membuf_error(stat8, error_found, handle_error); } stat8 = info->buf_nerr; - if (stat8 & 0x0f) { /* Error, so process */ + if (stat8 & 0x0f) { /* Error, so process */ stat8 &= 0x0f; membuf_error(stat8, error_found, handle_error); } } -static void e752x_check_dram (struct mem_ctl_info *mci, - struct e752x_error_info *info, int *error_found, - int handle_error) +static void e752x_check_dram(struct mem_ctl_info *mci, + struct e752x_error_info *info, int *error_found, + int handle_error) { u16 error_one, error_next; @@ -604,55 +603,52 @@ static void e752x_check_dram (struct mem_ctl_info *mci, error_next = info->dram_nerr; /* decode and report errors */ - if(error_one & 0x0101) /* check first error correctable */ + if (error_one & 0x0101) /* check first error correctable */ process_ce(mci, error_one, info->dram_sec1_add, - info->dram_sec1_syndrome, error_found, - handle_error); + info->dram_sec1_syndrome, error_found, handle_error); - if(error_next & 0x0101) /* check next error correctable */ + if (error_next & 0x0101) /* check next error correctable */ process_ce(mci, error_next, info->dram_sec2_add, - info->dram_sec2_syndrome, error_found, - handle_error); + info->dram_sec2_syndrome, error_found, handle_error); - if(error_one & 0x4040) + if (error_one & 0x4040) process_ue_no_info_wr(mci, error_found, handle_error); - if(error_next & 0x4040) + if (error_next & 0x4040) process_ue_no_info_wr(mci, error_found, handle_error); - if(error_one & 0x2020) + if (error_one & 0x2020) process_ded_retry(mci, error_one, info->dram_retr_add, - error_found, handle_error); + error_found, handle_error); - if(error_next & 0x2020) + if (error_next & 0x2020) process_ded_retry(mci, error_next, info->dram_retr_add, - error_found, handle_error); + error_found, handle_error); - if(error_one & 0x0808) - process_threshold_ce(mci, error_one, error_found, - handle_error); + if (error_one & 0x0808) + process_threshold_ce(mci, error_one, error_found, handle_error); - if(error_next & 0x0808) + if (error_next & 0x0808) process_threshold_ce(mci, error_next, error_found, - handle_error); + handle_error); - if(error_one & 0x0606) + if (error_one & 0x0606) process_ue(mci, error_one, info->dram_ded_add, - info->dram_scrb_add, error_found, handle_error); + info->dram_scrb_add, error_found, handle_error); - if(error_next & 0x0606) + if (error_next & 0x0606) process_ue(mci, error_next, info->dram_ded_add, - info->dram_scrb_add, error_found, handle_error); + info->dram_scrb_add, error_found, handle_error); } -static void e752x_get_error_info (struct mem_ctl_info *mci, - struct e752x_error_info *info) +static void e752x_get_error_info(struct mem_ctl_info *mci, + struct e752x_error_info *info) { struct pci_dev *dev; struct e752x_pvt *pvt; memset(info, 0, sizeof(*info)); - pvt = (struct e752x_pvt *) mci->pvt_info; + pvt = (struct e752x_pvt *)mci->pvt_info; dev = pvt->dev_d0f1; pci_read_config_dword(dev, E752X_FERR_GLOBAL, &info->ferr_global); @@ -661,8 +657,7 @@ static void e752x_get_error_info (struct mem_ctl_info *mci, pci_read_config_word(dev, E752X_SYSBUS_FERR, &info->sysbus_ferr); pci_read_config_byte(dev, E752X_BUF_FERR, &info->buf_ferr); - pci_read_config_word(dev, E752X_DRAM_FERR, - &info->dram_ferr); + pci_read_config_word(dev, E752X_DRAM_FERR, &info->dram_ferr); pci_read_config_dword(dev, E752X_DRAM_SEC1_ADD, &info->dram_sec1_add); pci_read_config_word(dev, E752X_DRAM_SEC1_SYNDROME, @@ -688,7 +683,7 @@ static void e752x_get_error_info (struct mem_ctl_info *mci, if (info->dram_ferr) pci_write_bits16(pvt->bridge_ck, E752X_DRAM_FERR, - info->dram_ferr, info->dram_ferr); + info->dram_ferr, info->dram_ferr); pci_write_config_dword(dev, E752X_FERR_GLOBAL, info->ferr_global); @@ -701,8 +696,7 @@ static void e752x_get_error_info (struct mem_ctl_info *mci, pci_read_config_word(dev, E752X_SYSBUS_NERR, &info->sysbus_nerr); pci_read_config_byte(dev, E752X_BUF_NERR, &info->buf_nerr); - pci_read_config_word(dev, E752X_DRAM_NERR, - &info->dram_nerr); + pci_read_config_word(dev, E752X_DRAM_NERR, &info->dram_nerr); pci_read_config_dword(dev, E752X_DRAM_SEC2_ADD, &info->dram_sec2_add); pci_read_config_word(dev, E752X_DRAM_SEC2_SYNDROME, @@ -722,15 +716,16 @@ static void e752x_get_error_info (struct mem_ctl_info *mci, if (info->dram_nerr) pci_write_bits16(pvt->bridge_ck, E752X_DRAM_NERR, - info->dram_nerr, info->dram_nerr); + info->dram_nerr, info->dram_nerr); pci_write_config_dword(dev, E752X_NERR_GLOBAL, info->nerr_global); } } -static int e752x_process_error_info (struct mem_ctl_info *mci, - struct e752x_error_info *info, int handle_errors) +static int e752x_process_error_info(struct mem_ctl_info *mci, + struct e752x_error_info *info, + int handle_errors) { u32 error32, stat32; int error_found; @@ -776,26 +771,38 @@ static inline int dual_channel_active(u16 ddrcsr) return (((ddrcsr >> 12) & 3) == 3); } +/* Remap csrow index numbers if map_type is "reverse" + */ +static inline int remap_csrow_index(struct mem_ctl_info *mci, int index) +{ + struct e752x_pvt *pvt = mci->pvt_info; + + if (!pvt->map_type) + return (7 - index); + + return (index); +} + static void e752x_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, - u16 ddrcsr) + u16 ddrcsr) { struct csrow_info *csrow; unsigned long last_cumul_size; int index, mem_dev, drc_chan; - int drc_drbg; /* DRB granularity 0=64mb, 1=128mb */ - int drc_ddim; /* DRAM Data Integrity Mode 0=none, 2=edac */ + int drc_drbg; /* DRB granularity 0=64mb, 1=128mb */ + int drc_ddim; /* DRAM Data Integrity Mode 0=none, 2=edac */ u8 value; u32 dra, drc, cumul_size; dra = 0; - for (index=0; index < 4; index++) { + for (index = 0; index < 4; index++) { u8 dra_reg; - pci_read_config_byte(pdev, E752X_DRA+index, &dra_reg); + pci_read_config_byte(pdev, E752X_DRA + index, &dra_reg); dra |= dra_reg << (index * 8); } pci_read_config_dword(pdev, E752X_DRC, &drc); drc_chan = dual_channel_active(ddrcsr); - drc_drbg = drc_chan + 1; /* 128 in dual mode, 64 in single */ + drc_drbg = drc_chan + 1; /* 128 in dual mode, 64 in single */ drc_ddim = (drc >> 20) & 0x3; /* The dram row boundary (DRB) reg values are boundary address for @@ -806,7 +813,7 @@ static void e752x_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, for (last_cumul_size = index = 0; index < mci->nr_csrows; index++) { /* mem_dev 0=x8, 1=x4 */ mem_dev = (dra >> (index * 4 + 2)) & 0x3; - csrow = &mci->csrows[index]; + csrow = &mci->csrows[remap_csrow_index(mci, index)]; mem_dev = (mem_dev == 2); pci_read_config_byte(pdev, E752X_DRB + index, &value); @@ -843,10 +850,10 @@ static void e752x_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, } static void e752x_init_mem_map_table(struct pci_dev *pdev, - struct e752x_pvt *pvt) + struct e752x_pvt *pvt) { int index; - u8 value, last, row, stat8; + u8 value, last, row; last = 0; row = 0; @@ -858,7 +865,7 @@ static void e752x_init_mem_map_table(struct pci_dev *pdev, /* no dimm in the slot, so flag it as empty */ pvt->map[index] = 0xff; pvt->map[index + 1] = 0xff; - } else { /* there is a dimm in the slot */ + } else { /* there is a dimm in the slot */ pvt->map[index] = row; row++; last = value; @@ -866,31 +873,25 @@ static void e752x_init_mem_map_table(struct pci_dev *pdev, * sided */ pci_read_config_byte(pdev, E752X_DRB + index + 1, - &value); - pvt->map[index + 1] = (value == last) ? - 0xff : /* the dimm is single sided, - so flag as empty */ - row; /* this is a double sided dimm - to save the next row # */ + &value); + + /* the dimm is single sided, so flag as empty */ + /* this is a double sided dimm to save the next row #*/ + pvt->map[index + 1] = (value == last) ? 0xff : row; row++; last = value; } } - - /* set the map type. 1 = normal, 0 = reversed */ - pci_read_config_byte(pdev, E752X_DRM, &stat8); - pvt->map_type = ((stat8 & 0x0f) > ((stat8 >> 4) & 0x0f)); } /* Return 0 on success or 1 on failure. */ static int e752x_get_devs(struct pci_dev *pdev, int dev_idx, - struct e752x_pvt *pvt) + struct e752x_pvt *pvt) { struct pci_dev *dev; pvt->bridge_ck = pci_get_device(PCI_VENDOR_ID_INTEL, - pvt->dev_info->err_dev, - pvt->bridge_ck); + pvt->dev_info->err_dev, pvt->bridge_ck); if (pvt->bridge_ck == NULL) pvt->bridge_ck = pci_scan_single_device(pdev->bus, @@ -898,13 +899,13 @@ static int e752x_get_devs(struct pci_dev *pdev, int dev_idx, if (pvt->bridge_ck == NULL) { e752x_printk(KERN_ERR, "error reporting device not found:" - "vendor %x device 0x%x (broken BIOS?)\n", - PCI_VENDOR_ID_INTEL, e752x_devs[dev_idx].err_dev); + "vendor %x device 0x%x (broken BIOS?)\n", + PCI_VENDOR_ID_INTEL, e752x_devs[dev_idx].err_dev); return 1; } dev = pci_get_device(PCI_VENDOR_ID_INTEL, e752x_devs[dev_idx].ctl_dev, - NULL); + NULL); if (dev == NULL) goto fail; @@ -942,12 +943,22 @@ static int e752x_probe1(struct pci_dev *pdev, int dev_idx) struct mem_ctl_info *mci; struct e752x_pvt *pvt; u16 ddrcsr; - int drc_chan; /* Number of channels 0=1chan,1=2chan */ + int drc_chan; /* Number of channels 0=1chan,1=2chan */ struct e752x_error_info discard; debugf0("%s(): mci\n", __func__); debugf0("Starting Probe1\n"); + /* make sure error reporting method is sane */ + switch (edac_op_state) { + case EDAC_OPSTATE_POLL: + case EDAC_OPSTATE_NMI: + break; + default: + edac_op_state = EDAC_OPSTATE_POLL; + break; + } + /* check to see if device 0 function 1 is enabled; if it isn't, we * assume the BIOS has reserved it for a reason and is expecting * exclusive access, we take care not to violate that assumption and @@ -966,7 +977,7 @@ static int e752x_probe1(struct pci_dev *pdev, int dev_idx) /* Dual channel = 1, Single channel = 0 */ drc_chan = dual_channel_active(ddrcsr); - mci = edac_mc_alloc(sizeof(*pvt), E752X_NR_CSROWS, drc_chan + 1); + mci = edac_mc_alloc(sizeof(*pvt), E752X_NR_CSROWS, drc_chan + 1, 0); if (mci == NULL) { return -ENOMEM; @@ -975,14 +986,14 @@ static int e752x_probe1(struct pci_dev *pdev, int dev_idx) debugf3("%s(): init mci\n", __func__); mci->mtype_cap = MEM_FLAG_RDDR; mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED | - EDAC_FLAG_S4ECD4ED; + EDAC_FLAG_S4ECD4ED; /* FIXME - what if different memory types are in different csrows? */ mci->mod_name = EDAC_MOD_STR; mci->mod_ver = E752X_REVISION; mci->dev = &pdev->dev; debugf3("%s(): init pvt\n", __func__); - pvt = (struct e752x_pvt *) mci->pvt_info; + pvt = (struct e752x_pvt *)mci->pvt_info; pvt->dev_info = &e752x_devs[dev_idx]; pvt->mc_symmetric = ((ddrcsr & 0x10) != 0); @@ -993,16 +1004,20 @@ static int e752x_probe1(struct pci_dev *pdev, int dev_idx) debugf3("%s(): more mci init\n", __func__); mci->ctl_name = pvt->dev_info->ctl_name; + mci->dev_name = pci_name(pdev); mci->edac_check = e752x_check; mci->ctl_page_to_phys = ctl_page_to_phys; - e752x_init_csrows(mci, pdev, ddrcsr); - e752x_init_mem_map_table(pdev, pvt); - - /* set the map type. 1 = normal, 0 = reversed */ + /* set the map type. 1 = normal, 0 = reversed + * Must be set before e752x_init_csrows in case csrow mapping + * is reversed. + */ pci_read_config_byte(pdev, E752X_DRM, &stat8); pvt->map_type = ((stat8 & 0x0f) > ((stat8 >> 4) & 0x0f)); + e752x_init_csrows(mci, pdev, ddrcsr); + e752x_init_mem_map_table(pdev, pvt); + mci->edac_cap |= EDAC_FLAG_NONE; debugf3("%s(): tolm, remapbase, remaplimit\n", __func__); @@ -1014,19 +1029,29 @@ static int e752x_probe1(struct pci_dev *pdev, int dev_idx) pci_read_config_word(pdev, E752X_REMAPLIMIT, &pci_data); pvt->remaplimit = ((u32) pci_data) << 14; e752x_printk(KERN_INFO, - "tolm = %x, remapbase = %x, remaplimit = %x\n", pvt->tolm, - pvt->remapbase, pvt->remaplimit); + "tolm = %x, remapbase = %x, remaplimit = %x\n", + pvt->tolm, pvt->remapbase, pvt->remaplimit); /* Here we assume that we will never see multiple instances of this * type of memory controller. The ID is therefore hardcoded to 0. */ - if (edac_mc_add_mc(mci,0)) { + if (edac_mc_add_mc(mci)) { debugf3("%s(): failed edac_mc_add_mc()\n", __func__); goto fail; } e752x_init_error_reporting_regs(pvt); - e752x_get_error_info(mci, &discard); /* clear other MCH errors */ + e752x_get_error_info(mci, &discard); /* clear other MCH errors */ + + /* allocating generic PCI control info */ + e752x_pci = edac_pci_create_generic_ctl(&pdev->dev, EDAC_MOD_STR); + if (!e752x_pci) { + printk(KERN_WARNING + "%s(): Unable to create PCI control\n", __func__); + printk(KERN_WARNING + "%s(): PCI error report via EDAC not setup\n", + __func__); + } /* get this far and it's successful */ debugf3("%s(): success\n", __func__); @@ -1043,12 +1068,12 @@ fail: /* returns count (>= 0), or negative on error */ static int __devinit e752x_init_one(struct pci_dev *pdev, - const struct pci_device_id *ent) + const struct pci_device_id *ent) { debugf0("%s()\n", __func__); /* wake up and enable device */ - if(pci_enable_device(pdev) < 0) + if (pci_enable_device(pdev) < 0) return -EIO; return e752x_probe1(pdev, ent->driver_data); @@ -1061,10 +1086,13 @@ static void __devexit e752x_remove_one(struct pci_dev *pdev) debugf0("%s()\n", __func__); + if (e752x_pci) + edac_pci_release_generic_ctl(e752x_pci); + if ((mci = edac_mc_del_mc(&pdev->dev)) == NULL) return; - pvt = (struct e752x_pvt *) mci->pvt_info; + pvt = (struct e752x_pvt *)mci->pvt_info; pci_dev_put(pvt->dev_d0f0); pci_dev_put(pvt->dev_d0f1); pci_dev_put(pvt->bridge_ck); @@ -1073,20 +1101,17 @@ static void __devexit e752x_remove_one(struct pci_dev *pdev) static const struct pci_device_id e752x_pci_tbl[] __devinitdata = { { - PCI_VEND_DEV(INTEL, 7520_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, - E7520 - }, + PCI_VEND_DEV(INTEL, 7520_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + E7520}, { - PCI_VEND_DEV(INTEL, 7525_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, - E7525 - }, + PCI_VEND_DEV(INTEL, 7525_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + E7525}, { - PCI_VEND_DEV(INTEL, 7320_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, - E7320 - }, + PCI_VEND_DEV(INTEL, 7320_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + E7320}, { - 0, - } /* 0 terminated list. */ + 0, + } /* 0 terminated list. */ }; MODULE_DEVICE_TABLE(pci, e752x_pci_tbl); @@ -1122,5 +1147,6 @@ MODULE_DESCRIPTION("MC support for Intel e752x memory controllers"); module_param(force_function_unhide, int, 0444); MODULE_PARM_DESC(force_function_unhide, "if BIOS sets Dev0:Fun1 up as hidden:" -" 1=force unhide and hope BIOS doesn't fight driver for Dev0:Fun1 access"); - + " 1=force unhide and hope BIOS doesn't fight driver for Dev0:Fun1 access"); +module_param(edac_op_state, int, 0444); +MODULE_PARM_DESC(edac_op_state, "EDAC Error Reporting state: 0=Poll,1=NMI"); diff --git a/drivers/edac/e7xxx_edac.c b/drivers/edac/e7xxx_edac.c index 310d91b..96ecc49 100644 --- a/drivers/edac/e7xxx_edac.c +++ b/drivers/edac/e7xxx_edac.c @@ -27,9 +27,10 @@ #include <linux/pci.h> #include <linux/pci_ids.h> #include <linux/slab.h> -#include "edac_mc.h" +#include <linux/edac.h> +#include "edac_core.h" -#define E7XXX_REVISION " Ver: 2.0.1 " __DATE__ +#define E7XXX_REVISION " Ver: 2.0.2 " __DATE__ #define EDAC_MOD_STR "e7xxx_edac" #define e7xxx_printk(level, fmt, arg...) \ @@ -143,23 +144,21 @@ struct e7xxx_error_info { u32 dram_uelog_add; }; +static struct edac_pci_ctl_info *e7xxx_pci; + static const struct e7xxx_dev_info e7xxx_devs[] = { [E7500] = { .err_dev = PCI_DEVICE_ID_INTEL_7500_1_ERR, - .ctl_name = "E7500" - }, + .ctl_name = "E7500"}, [E7501] = { .err_dev = PCI_DEVICE_ID_INTEL_7501_1_ERR, - .ctl_name = "E7501" - }, + .ctl_name = "E7501"}, [E7505] = { .err_dev = PCI_DEVICE_ID_INTEL_7505_1_ERR, - .ctl_name = "E7505" - }, + .ctl_name = "E7505"}, [E7205] = { .err_dev = PCI_DEVICE_ID_INTEL_7205_1_ERR, - .ctl_name = "E7205" - }, + .ctl_name = "E7205"}, }; /* FIXME - is this valid for both SECDED and S4ECD4ED? */ @@ -180,15 +179,15 @@ static inline int e7xxx_find_channel(u16 syndrome) } static unsigned long ctl_page_to_phys(struct mem_ctl_info *mci, - unsigned long page) + unsigned long page) { u32 remap; - struct e7xxx_pvt *pvt = (struct e7xxx_pvt *) mci->pvt_info; + struct e7xxx_pvt *pvt = (struct e7xxx_pvt *)mci->pvt_info; debugf3("%s()\n", __func__); if ((page < pvt->tolm) || - ((page >= 0x100000) && (page < pvt->remapbase))) + ((page >= 0x100000) && (page < pvt->remapbase))) return page; remap = (page - pvt->tolm) + pvt->remapbase; @@ -200,8 +199,7 @@ static unsigned long ctl_page_to_phys(struct mem_ctl_info *mci, return pvt->tolm - 1; } -static void process_ce(struct mem_ctl_info *mci, - struct e7xxx_error_info *info) +static void process_ce(struct mem_ctl_info *mci, struct e7xxx_error_info *info) { u32 error_1b, page; u16 syndrome; @@ -212,7 +210,7 @@ static void process_ce(struct mem_ctl_info *mci, /* read the error address */ error_1b = info->dram_celog_add; /* FIXME - should use PAGE_SHIFT */ - page = error_1b >> 6; /* convert the address to 4k page */ + page = error_1b >> 6; /* convert the address to 4k page */ /* read the syndrome */ syndrome = info->dram_celog_syndrome; /* FIXME - check for -1 */ @@ -228,8 +226,7 @@ static void process_ce_no_info(struct mem_ctl_info *mci) edac_mc_handle_ce_no_info(mci, "e7xxx CE log register overflow"); } -static void process_ue(struct mem_ctl_info *mci, - struct e7xxx_error_info *info) +static void process_ue(struct mem_ctl_info *mci, struct e7xxx_error_info *info) { u32 error_2b, block_page; int row; @@ -238,7 +235,7 @@ static void process_ue(struct mem_ctl_info *mci, /* read the error address */ error_2b = info->dram_uelog_add; /* FIXME - should use PAGE_SHIFT */ - block_page = error_2b >> 6; /* convert to 4k address */ + block_page = error_2b >> 6; /* convert to 4k address */ row = edac_mc_find_csrow_by_page(mci, block_page); edac_mc_handle_ue(mci, block_page, 0, row, "e7xxx UE"); } @@ -249,16 +246,14 @@ static void process_ue_no_info(struct mem_ctl_info *mci) edac_mc_handle_ue_no_info(mci, "e7xxx UE log register overflow"); } -static void e7xxx_get_error_info (struct mem_ctl_info *mci, - struct e7xxx_error_info *info) +static void e7xxx_get_error_info(struct mem_ctl_info *mci, + struct e7xxx_error_info *info) { struct e7xxx_pvt *pvt; - pvt = (struct e7xxx_pvt *) mci->pvt_info; - pci_read_config_byte(pvt->bridge_ck, E7XXX_DRAM_FERR, - &info->dram_ferr); - pci_read_config_byte(pvt->bridge_ck, E7XXX_DRAM_NERR, - &info->dram_nerr); + pvt = (struct e7xxx_pvt *)mci->pvt_info; + pci_read_config_byte(pvt->bridge_ck, E7XXX_DRAM_FERR, &info->dram_ferr); + pci_read_config_byte(pvt->bridge_ck, E7XXX_DRAM_NERR, &info->dram_nerr); if ((info->dram_ferr & 1) || (info->dram_nerr & 1)) { pci_read_config_dword(pvt->bridge_ck, E7XXX_DRAM_CELOG_ADD, @@ -279,8 +274,9 @@ static void e7xxx_get_error_info (struct mem_ctl_info *mci, pci_write_bits8(pvt->bridge_ck, E7XXX_DRAM_NERR, 0x03, 0x03); } -static int e7xxx_process_error_info (struct mem_ctl_info *mci, - struct e7xxx_error_info *info, int handle_errors) +static int e7xxx_process_error_info(struct mem_ctl_info *mci, + struct e7xxx_error_info *info, + int handle_errors) { int error_found; @@ -341,7 +337,6 @@ static inline int dual_channel_active(u32 drc, int dev_idx) return (dev_idx == E7501) ? ((drc >> 22) & 0x1) : 1; } - /* Return DRB granularity (0=32mb, 1=64mb). */ static inline int drb_granularity(u32 drc, int dev_idx) { @@ -349,9 +344,8 @@ static inline int drb_granularity(u32 drc, int dev_idx) return (dev_idx == E7501) ? ((drc >> 18) & 0x3) : 1; } - static void e7xxx_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, - int dev_idx, u32 drc) + int dev_idx, u32 drc) { unsigned long last_cumul_size; int index; @@ -419,10 +413,21 @@ static int e7xxx_probe1(struct pci_dev *pdev, int dev_idx) struct e7xxx_error_info discard; debugf0("%s(): mci\n", __func__); + + /* make sure error reporting method is sane */ + switch (edac_op_state) { + case EDAC_OPSTATE_POLL: + case EDAC_OPSTATE_NMI: + break; + default: + edac_op_state = EDAC_OPSTATE_POLL; + break; + } + pci_read_config_dword(pdev, E7XXX_DRC, &drc); drc_chan = dual_channel_active(drc, dev_idx); - mci = edac_mc_alloc(sizeof(*pvt), E7XXX_NR_CSROWS, drc_chan + 1); + mci = edac_mc_alloc(sizeof(*pvt), E7XXX_NR_CSROWS, drc_chan + 1, 0); if (mci == NULL) return -ENOMEM; @@ -430,17 +435,16 @@ static int e7xxx_probe1(struct pci_dev *pdev, int dev_idx) debugf3("%s(): init mci\n", __func__); mci->mtype_cap = MEM_FLAG_RDDR; mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED | - EDAC_FLAG_S4ECD4ED; + EDAC_FLAG_S4ECD4ED; /* FIXME - what if different memory types are in different csrows? */ mci->mod_name = EDAC_MOD_STR; mci->mod_ver = E7XXX_REVISION; mci->dev = &pdev->dev; debugf3("%s(): init pvt\n", __func__); - pvt = (struct e7xxx_pvt *) mci->pvt_info; + pvt = (struct e7xxx_pvt *)mci->pvt_info; pvt->dev_info = &e7xxx_devs[dev_idx]; pvt->bridge_ck = pci_get_device(PCI_VENDOR_ID_INTEL, - pvt->dev_info->err_dev, - pvt->bridge_ck); + pvt->dev_info->err_dev, pvt->bridge_ck); if (!pvt->bridge_ck) { e7xxx_printk(KERN_ERR, "error reporting device not found:" @@ -451,6 +455,7 @@ static int e7xxx_probe1(struct pci_dev *pdev, int dev_idx) debugf3("%s(): more mci init\n", __func__); mci->ctl_name = pvt->dev_info->ctl_name; + mci->dev_name = pci_name(pdev); mci->edac_check = e7xxx_check; mci->ctl_page_to_phys = ctl_page_to_phys; e7xxx_init_csrows(mci, pdev, dev_idx, drc); @@ -473,11 +478,22 @@ static int e7xxx_probe1(struct pci_dev *pdev, int dev_idx) /* Here we assume that we will never see multiple instances of this * type of memory controller. The ID is therefore hardcoded to 0. */ - if (edac_mc_add_mc(mci,0)) { + if (edac_mc_add_mc(mci)) { debugf3("%s(): failed edac_mc_add_mc()\n", __func__); goto fail1; } + /* allocating generic PCI control info */ + e7xxx_pci = edac_pci_create_generic_ctl(&pdev->dev, EDAC_MOD_STR); + if (!e7xxx_pci) { + printk(KERN_WARNING + "%s(): Unable to create PCI control\n", + __func__); + printk(KERN_WARNING + "%s(): PCI error report via EDAC not setup\n", + __func__); + } + /* get this far and it's successful */ debugf3("%s(): success\n", __func__); return 0; @@ -493,7 +509,7 @@ fail0: /* returns count (>= 0), or negative on error */ static int __devinit e7xxx_init_one(struct pci_dev *pdev, - const struct pci_device_id *ent) + const struct pci_device_id *ent) { debugf0("%s()\n", __func__); @@ -509,34 +525,33 @@ static void __devexit e7xxx_remove_one(struct pci_dev *pdev) debugf0("%s()\n", __func__); + if (e7xxx_pci) + edac_pci_release_generic_ctl(e7xxx_pci); + if ((mci = edac_mc_del_mc(&pdev->dev)) == NULL) return; - pvt = (struct e7xxx_pvt *) mci->pvt_info; + pvt = (struct e7xxx_pvt *)mci->pvt_info; pci_dev_put(pvt->bridge_ck); edac_mc_free(mci); } static const struct pci_device_id e7xxx_pci_tbl[] __devinitdata = { { - PCI_VEND_DEV(INTEL, 7205_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, - E7205 - }, + PCI_VEND_DEV(INTEL, 7205_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + E7205}, { - PCI_VEND_DEV(INTEL, 7500_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, - E7500 - }, + PCI_VEND_DEV(INTEL, 7500_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + E7500}, { - PCI_VEND_DEV(INTEL, 7501_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, - E7501 - }, + PCI_VEND_DEV(INTEL, 7501_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + E7501}, { - PCI_VEND_DEV(INTEL, 7505_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, - E7505 - }, + PCI_VEND_DEV(INTEL, 7505_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + E7505}, { - 0, - } /* 0 terminated list. */ + 0, + } /* 0 terminated list. */ }; MODULE_DEVICE_TABLE(pci, e7xxx_pci_tbl); @@ -563,5 +578,7 @@ module_exit(e7xxx_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Linux Networx (http://lnxi.com) Thayne Harbaugh et al\n" - "Based on.work by Dan Hollis et al"); + "Based on.work by Dan Hollis et al"); MODULE_DESCRIPTION("MC support for Intel e7xxx memory controllers"); +module_param(edac_op_state, int, 0444); +MODULE_PARM_DESC(edac_op_state, "EDAC Error Reporting state: 0=Poll,1=NMI"); diff --git a/drivers/edac/edac_mc.h b/drivers/edac/edac_core.h index 713444c..4e6bad1 100644 --- a/drivers/edac/edac_mc.h +++ b/drivers/edac/edac_core.h @@ -1,6 +1,7 @@ /* - * MC kernel module - * (C) 2003 Linux Networx (http://lnxi.com) + * Defines, structures, APIs for edac_core module + * + * (C) 2007 Linux Networx (http://lnxi.com) * This file may be distributed under the terms of the * GNU General Public License. * @@ -11,12 +12,13 @@ * NMI handling support added by * Dave Peterson <dsp@llnl.gov> <dave_peterson@pobox.com> * - * $Id: edac_mc.h,v 1.4.2.10 2005/10/05 00:43:44 dsp_llnl Exp $ + * Refactored for multi-source files: + * Doug Thompson <norsk5@xmission.com> * */ -#ifndef _EDAC_MC_H_ -#define _EDAC_MC_H_ +#ifndef _EDAC_CORE_H_ +#define _EDAC_CORE_H_ #include <linux/kernel.h> #include <linux/types.h> @@ -30,9 +32,14 @@ #include <linux/completion.h> #include <linux/kobject.h> #include <linux/platform_device.h> +#include <linux/sysdev.h> +#include <linux/workqueue.h> +#include <linux/version.h> #define EDAC_MC_LABEL_LEN 31 -#define MC_PROC_NAME_MAX_LEN 7 +#define EDAC_DEVICE_NAME_LEN 31 +#define EDAC_ATTRIB_VALUE_LEN 15 +#define MC_PROC_NAME_MAX_LEN 7 #if PAGE_SHIFT < 20 #define PAGES_TO_MiB( pages ) ( ( pages ) >> ( 20 - PAGE_SHIFT ) ) @@ -49,6 +56,14 @@ #define edac_mc_chipset_printk(mci, level, prefix, fmt, arg...) \ printk(level "EDAC " prefix " MC%d: " fmt, mci->mc_idx, ##arg) +/* edac_device printk */ +#define edac_device_printk(ctl, level, fmt, arg...) \ + printk(level "EDAC DEVICE%d: " fmt, ctl->dev_idx, ##arg) + +/* edac_pci printk */ +#define edac_pci_printk(ctl, level, fmt, arg...) \ + printk(level "EDAC PCI%d: " fmt, ctl->pci_idx, ##arg) + /* prefixes for edac_printk() and edac_mc_printk() */ #define EDAC_MC "MC" #define EDAC_PCI "PCI" @@ -60,7 +75,7 @@ extern int edac_debug_level; #define edac_debug_printk(level, fmt, arg...) \ do { \ if (level <= edac_debug_level) \ - edac_printk(KERN_DEBUG, EDAC_DEBUG, fmt, ##arg); \ + edac_printk(KERN_EMERG, EDAC_DEBUG, fmt, ##arg); \ } while(0) #define debugf0( ... ) edac_debug_printk(0, __VA_ARGS__ ) @@ -69,7 +84,7 @@ extern int edac_debug_level; #define debugf3( ... ) edac_debug_printk(3, __VA_ARGS__ ) #define debugf4( ... ) edac_debug_printk(4, __VA_ARGS__ ) -#else /* !CONFIG_EDAC_DEBUG */ +#else /* !CONFIG_EDAC_DEBUG */ #define debugf0( ... ) #define debugf1( ... ) @@ -77,18 +92,14 @@ extern int edac_debug_level; #define debugf3( ... ) #define debugf4( ... ) -#endif /* !CONFIG_EDAC_DEBUG */ +#endif /* !CONFIG_EDAC_DEBUG */ #define BIT(x) (1 << (x)) #define PCI_VEND_DEV(vend, dev) PCI_VENDOR_ID_ ## vend, \ PCI_DEVICE_ID_ ## vend ## _ ## dev -#if defined(CONFIG_X86) && defined(CONFIG_PCI) -#define dev_name(dev) pci_name(to_pci_dev(dev)) -#else -#define dev_name(dev) to_platform_device(dev)->name -#endif +#define dev_name(dev) (dev)->dev_name /* memory devices */ enum dev_type { @@ -124,8 +135,9 @@ enum mem_type { MEM_DDR, /* Double data rate SDRAM */ MEM_RDDR, /* Registered Double data rate SDRAM */ MEM_RMBS, /* Rambus DRAM */ - MEM_DDR2, /* DDR2 RAM */ - MEM_FB_DDR2, /* fully buffered DDR2 */ + MEM_DDR2, /* DDR2 RAM */ + MEM_FB_DDR2, /* fully buffered DDR2 */ + MEM_RDDR2, /* Registered DDR2 RAM */ }; #define MEM_FLAG_EMPTY BIT(MEM_EMPTY) @@ -141,6 +153,7 @@ enum mem_type { #define MEM_FLAG_RMBS BIT(MEM_RMBS) #define MEM_FLAG_DDR2 BIT(MEM_DDR2) #define MEM_FLAG_FB_DDR2 BIT(MEM_FB_DDR2) +#define MEM_FLAG_RDDR2 BIT(MEM_RDDR2) /* chipset Error Detection and Correction capabilities and mode */ enum edac_type { @@ -181,16 +194,23 @@ enum scrub_type { }; #define SCRUB_FLAG_SW_PROG BIT(SCRUB_SW_PROG) -#define SCRUB_FLAG_SW_SRC BIT(SCRUB_SW_SRC_CORR) -#define SCRUB_FLAG_SW_PROG_SRC BIT(SCRUB_SW_PROG_SRC_CORR) +#define SCRUB_FLAG_SW_SRC BIT(SCRUB_SW_SRC) +#define SCRUB_FLAG_SW_PROG_SRC BIT(SCRUB_SW_PROG_SRC) #define SCRUB_FLAG_SW_TUN BIT(SCRUB_SW_SCRUB_TUNABLE) #define SCRUB_FLAG_HW_PROG BIT(SCRUB_HW_PROG) -#define SCRUB_FLAG_HW_SRC BIT(SCRUB_HW_SRC_CORR) -#define SCRUB_FLAG_HW_PROG_SRC BIT(SCRUB_HW_PROG_SRC_CORR) +#define SCRUB_FLAG_HW_SRC BIT(SCRUB_HW_SRC) +#define SCRUB_FLAG_HW_PROG_SRC BIT(SCRUB_HW_PROG_SRC) #define SCRUB_FLAG_HW_TUN BIT(SCRUB_HW_TUNABLE) /* FIXME - should have notify capabilities: NMI, LOG, PROC, etc */ +/* EDAC internal operation states */ +#define OP_ALLOC 0x100 +#define OP_RUNNING_POLL 0x201 +#define OP_RUNNING_INTERRUPT 0x202 +#define OP_RUNNING_POLL_INTR 0x203 +#define OP_OFFLINE 0x300 + /* * There are several things to be aware of that aren't at all obvious: * @@ -276,7 +296,7 @@ enum scrub_type { struct channel_info { int chan_idx; /* channel index */ u32 ce_count; /* Correctable Errors for this CHANNEL */ - char label[EDAC_MC_LABEL_LEN + 1]; /* DIMM label on motherboard */ + char label[EDAC_MC_LABEL_LEN + 1]; /* DIMM label on motherboard */ struct csrow_info *csrow; /* the parent */ }; @@ -297,15 +317,29 @@ struct csrow_info { struct mem_ctl_info *mci; /* the parent */ struct kobject kobj; /* sysfs kobject for this csrow */ - struct completion kobj_complete; - /* FIXME the number of CHANNELs might need to become dynamic */ + /* channel information for this csrow */ u32 nr_channels; struct channel_info *channels; }; +/* mcidev_sysfs_attribute structure + * used for driver sysfs attributes and in mem_ctl_info + * sysfs top level entries + */ +struct mcidev_sysfs_attribute { + struct attribute attr; + ssize_t (*show)(struct mem_ctl_info *,char *); + ssize_t (*store)(struct mem_ctl_info *, const char *,size_t); +}; + +/* MEMORY controller information structure + */ struct mem_ctl_info { - struct list_head link; /* for global list of mem_ctl_info structs */ + struct list_head link; /* for global list of mem_ctl_info structs */ + + struct module *owner; /* Module owner of this control struct */ + unsigned long mtype_cap; /* memory types supported by mc */ unsigned long edac_ctl_cap; /* Mem controller EDAC capabilities */ unsigned long edac_cap; /* configuration capabilities - this is @@ -322,14 +356,15 @@ struct mem_ctl_info { /* Translates sdram memory scrub rate given in bytes/sec to the internal representation and configures whatever else needs to be configured. - */ - int (*set_sdram_scrub_rate) (struct mem_ctl_info *mci, u32 *bw); + */ + int (*set_sdram_scrub_rate) (struct mem_ctl_info * mci, u32 * bw); /* Get the current sdram memory scrub rate from the internal representation and converts it to the closest matching bandwith in bytes/sec. - */ - int (*get_sdram_scrub_rate) (struct mem_ctl_info *mci, u32 *bw); + */ + int (*get_sdram_scrub_rate) (struct mem_ctl_info * mci, u32 * bw); + /* pointer to edac checking routine */ void (*edac_check) (struct mem_ctl_info * mci); @@ -340,7 +375,7 @@ struct mem_ctl_info { */ /* FIXME - why not send the phys page to begin with? */ unsigned long (*ctl_page_to_phys) (struct mem_ctl_info * mci, - unsigned long page); + unsigned long page); int mc_idx; int nr_csrows; struct csrow_info *csrows; @@ -353,6 +388,7 @@ struct mem_ctl_info { const char *mod_name; const char *mod_ver; const char *ctl_name; + const char *dev_name; char proc_name[MC_PROC_NAME_MAX_LEN + 1]; void *pvt_info; u32 ue_noinfo_count; /* Uncorrectable Errors w/o info */ @@ -369,14 +405,327 @@ struct mem_ctl_info { /* edac sysfs device control */ struct kobject edac_mci_kobj; - struct completion kobj_complete; + + /* Additional top controller level attributes, but specified + * by the low level driver. + * + * Set by the low level driver to provide attributes at the + * controller level, same level as 'ue_count' and 'ce_count' above. + * An array of structures, NULL terminated + * + * If attributes are desired, then set to array of attributes + * If no attributes are desired, leave NULL + */ + struct mcidev_sysfs_attribute *mc_driver_sysfs_attributes; + + /* work struct for this MC */ + struct delayed_work work; + + /* the internal state of this controller instance */ + int op_state; +}; + +/* + * The following are the structures to provide for a generic + * or abstract 'edac_device'. This set of structures and the + * code that implements the APIs for the same, provide for + * registering EDAC type devices which are NOT standard memory. + * + * CPU caches (L1 and L2) + * DMA engines + * Core CPU swithces + * Fabric switch units + * PCIe interface controllers + * other EDAC/ECC type devices that can be monitored for + * errors, etc. + * + * It allows for a 2 level set of hiearchry. For example: + * + * cache could be composed of L1, L2 and L3 levels of cache. + * Each CPU core would have its own L1 cache, while sharing + * L2 and maybe L3 caches. + * + * View them arranged, via the sysfs presentation: + * /sys/devices/system/edac/.. + * + * mc/ <existing memory device directory> + * cpu/cpu0/.. <L1 and L2 block directory> + * /L1-cache/ce_count + * /ue_count + * /L2-cache/ce_count + * /ue_count + * cpu/cpu1/.. <L1 and L2 block directory> + * /L1-cache/ce_count + * /ue_count + * /L2-cache/ce_count + * /ue_count + * ... + * + * the L1 and L2 directories would be "edac_device_block's" + */ + +struct edac_device_counter { + u32 ue_count; + u32 ce_count; +}; + +/* forward reference */ +struct edac_device_ctl_info; +struct edac_device_block; + +/* edac_dev_sysfs_attribute structure + * used for driver sysfs attributes in mem_ctl_info + * for extra controls and attributes: + * like high level error Injection controls + */ +struct edac_dev_sysfs_attribute { + struct attribute attr; + ssize_t (*show)(struct edac_device_ctl_info *, char *); + ssize_t (*store)(struct edac_device_ctl_info *, const char *, size_t); +}; + +/* edac_dev_sysfs_block_attribute structure + * + * used in leaf 'block' nodes for adding controls/attributes + * + * each block in each instance of the containing control structure + * can have an array of the following. The show and store functions + * will be filled in with the show/store function in the + * low level driver. + * + * The 'value' field will be the actual value field used for + * counting + */ +struct edac_dev_sysfs_block_attribute { + struct attribute attr; + ssize_t (*show)(struct kobject *, struct attribute *, char *); + ssize_t (*store)(struct kobject *, struct attribute *, + const char *, size_t); + struct edac_device_block *block; + + unsigned int value; +}; + +/* device block control structure */ +struct edac_device_block { + struct edac_device_instance *instance; /* Up Pointer */ + char name[EDAC_DEVICE_NAME_LEN + 1]; + + struct edac_device_counter counters; /* basic UE and CE counters */ + + int nr_attribs; /* how many attributes */ + + /* this block's attributes, could be NULL */ + struct edac_dev_sysfs_block_attribute *block_attributes; + + /* edac sysfs device control */ + struct kobject kobj; +}; + +/* device instance control structure */ +struct edac_device_instance { + struct edac_device_ctl_info *ctl; /* Up pointer */ + char name[EDAC_DEVICE_NAME_LEN + 4]; + + struct edac_device_counter counters; /* instance counters */ + + u32 nr_blocks; /* how many blocks */ + struct edac_device_block *blocks; /* block array */ + + /* edac sysfs device control */ + struct kobject kobj; +}; + + +/* + * Abstract edac_device control info structure + * + */ +struct edac_device_ctl_info { + /* for global list of edac_device_ctl_info structs */ + struct list_head link; + + struct module *owner; /* Module owner of this control struct */ + + int dev_idx; + + /* Per instance controls for this edac_device */ + int log_ue; /* boolean for logging UEs */ + int log_ce; /* boolean for logging CEs */ + int panic_on_ue; /* boolean for panic'ing on an UE */ + unsigned poll_msec; /* number of milliseconds to poll interval */ + unsigned long delay; /* number of jiffies for poll_msec */ + + /* Additional top controller level attributes, but specified + * by the low level driver. + * + * Set by the low level driver to provide attributes at the + * controller level, same level as 'ue_count' and 'ce_count' above. + * An array of structures, NULL terminated + * + * If attributes are desired, then set to array of attributes + * If no attributes are desired, leave NULL + */ + struct edac_dev_sysfs_attribute *sysfs_attributes; + + /* pointer to main 'edac' class in sysfs */ + struct sysdev_class *edac_class; + + /* the internal state of this controller instance */ + int op_state; + /* work struct for this instance */ + struct delayed_work work; + + /* pointer to edac polling checking routine: + * If NOT NULL: points to polling check routine + * If NULL: Then assumes INTERRUPT operation, where + * MC driver will receive events + */ + void (*edac_check) (struct edac_device_ctl_info * edac_dev); + + struct device *dev; /* pointer to device structure */ + + const char *mod_name; /* module name */ + const char *ctl_name; /* edac controller name */ + const char *dev_name; /* pci/platform/etc... name */ + + void *pvt_info; /* pointer to 'private driver' info */ + + unsigned long start_time; /* edac_device load start time (jiffies) */ + + /* these are for safe removal of mc devices from global list while + * NMI handlers may be traversing list + */ + struct rcu_head rcu; + struct completion removal_complete; + + /* sysfs top name under 'edac' directory + * and instance name: + * cpu/cpu0/... + * cpu/cpu1/... + * cpu/cpu2/... + * ... + */ + char name[EDAC_DEVICE_NAME_LEN + 1]; + + /* Number of instances supported on this control structure + * and the array of those instances + */ + u32 nr_instances; + struct edac_device_instance *instances; + + /* Event counters for the this whole EDAC Device */ + struct edac_device_counter counters; + + /* edac sysfs device control for the 'name' + * device this structure controls + */ + struct kobject kobj; }; +/* To get from the instance's wq to the beginning of the ctl structure */ +#define to_edac_mem_ctl_work(w) \ + container_of(w, struct mem_ctl_info, work) + +#define to_edac_device_ctl_work(w) \ + container_of(w,struct edac_device_ctl_info,work) + +/* + * The alloc() and free() functions for the 'edac_device' control info + * structure. A MC driver will allocate one of these for each edac_device + * it is going to control/register with the EDAC CORE. + */ +extern struct edac_device_ctl_info *edac_device_alloc_ctl_info( + unsigned sizeof_private, + char *edac_device_name, unsigned nr_instances, + char *edac_block_name, unsigned nr_blocks, + unsigned offset_value, + struct edac_dev_sysfs_block_attribute *block_attributes, + unsigned nr_attribs, + int device_index); + +/* The offset value can be: + * -1 indicating no offset value + * 0 for zero-based block numbers + * 1 for 1-based block number + * other for other-based block number + */ +#define BLOCK_OFFSET_VALUE_OFF ((unsigned) -1) + +extern void edac_device_free_ctl_info(struct edac_device_ctl_info *ctl_info); + #ifdef CONFIG_PCI +struct edac_pci_counter { + atomic_t pe_count; + atomic_t npe_count; +}; + +/* + * Abstract edac_pci control info structure + * + */ +struct edac_pci_ctl_info { + /* for global list of edac_pci_ctl_info structs */ + struct list_head link; + + int pci_idx; + + struct sysdev_class *edac_class; /* pointer to class */ + + /* the internal state of this controller instance */ + int op_state; + /* work struct for this instance */ + struct delayed_work work; + + /* pointer to edac polling checking routine: + * If NOT NULL: points to polling check routine + * If NULL: Then assumes INTERRUPT operation, where + * MC driver will receive events + */ + void (*edac_check) (struct edac_pci_ctl_info * edac_dev); + + struct device *dev; /* pointer to device structure */ + + const char *mod_name; /* module name */ + const char *ctl_name; /* edac controller name */ + const char *dev_name; /* pci/platform/etc... name */ + + void *pvt_info; /* pointer to 'private driver' info */ + + unsigned long start_time; /* edac_pci load start time (jiffies) */ + + /* these are for safe removal of devices from global list while + * NMI handlers may be traversing list + */ + struct rcu_head rcu; + struct completion complete; + + /* sysfs top name under 'edac' directory + * and instance name: + * cpu/cpu0/... + * cpu/cpu1/... + * cpu/cpu2/... + * ... + */ + char name[EDAC_DEVICE_NAME_LEN + 1]; + + /* Event counters for the this whole EDAC Device */ + struct edac_pci_counter counters; + + /* edac sysfs device control for the 'name' + * device this structure controls + */ + struct kobject kobj; + struct completion kobj_complete; +}; + +#define to_edac_pci_ctl_work(w) \ + container_of(w, struct edac_pci_ctl_info,work) + /* write all or some bits in a byte-register*/ static inline void pci_write_bits8(struct pci_dev *pdev, int offset, u8 value, - u8 mask) + u8 mask) { if (mask != 0xff) { u8 buf; @@ -392,7 +741,7 @@ static inline void pci_write_bits8(struct pci_dev *pdev, int offset, u8 value, /* write all or some bits in a word-register*/ static inline void pci_write_bits16(struct pci_dev *pdev, int offset, - u16 value, u16 mask) + u16 value, u16 mask) { if (mask != 0xffff) { u16 buf; @@ -408,7 +757,7 @@ static inline void pci_write_bits16(struct pci_dev *pdev, int offset, /* write all or some bits in a dword-register*/ static inline void pci_write_bits32(struct pci_dev *pdev, int offset, - u32 value, u32 mask) + u32 value, u32 mask) { if (mask != 0xffff) { u32 buf; @@ -422,20 +771,16 @@ static inline void pci_write_bits32(struct pci_dev *pdev, int offset, pci_write_config_dword(pdev, offset, value); } -#endif /* CONFIG_PCI */ +#endif /* CONFIG_PCI */ -#ifdef CONFIG_EDAC_DEBUG -void edac_mc_dump_channel(struct channel_info *chan); -void edac_mc_dump_mci(struct mem_ctl_info *mci); -void edac_mc_dump_csrow(struct csrow_info *csrow); -#endif /* CONFIG_EDAC_DEBUG */ - -extern int edac_mc_add_mc(struct mem_ctl_info *mci,int mc_idx); -extern struct mem_ctl_info * edac_mc_del_mc(struct device *dev); +extern struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows, + unsigned nr_chans, int edac_index); +extern int edac_mc_add_mc(struct mem_ctl_info *mci); +extern void edac_mc_free(struct mem_ctl_info *mci); +extern struct mem_ctl_info *edac_mc_find(int idx); +extern struct mem_ctl_info *edac_mc_del_mc(struct device *dev); extern int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci, - unsigned long page); -extern void edac_mc_scrub_block(unsigned long page, unsigned long offset, - u32 size); + unsigned long page); /* * The no info errors are used when error overflows are reported. @@ -448,34 +793,59 @@ extern void edac_mc_scrub_block(unsigned long page, unsigned long offset, * statement clutter and extra function arguments. */ extern void edac_mc_handle_ce(struct mem_ctl_info *mci, - unsigned long page_frame_number, unsigned long offset_in_page, - unsigned long syndrome, int row, int channel, - const char *msg); + unsigned long page_frame_number, + unsigned long offset_in_page, + unsigned long syndrome, int row, int channel, + const char *msg); extern void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci, - const char *msg); + const char *msg); extern void edac_mc_handle_ue(struct mem_ctl_info *mci, - unsigned long page_frame_number, unsigned long offset_in_page, - int row, const char *msg); + unsigned long page_frame_number, + unsigned long offset_in_page, int row, + const char *msg); extern void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci, - const char *msg); -extern void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci, - unsigned int csrow, - unsigned int channel0, - unsigned int channel1, - char *msg); -extern void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci, - unsigned int csrow, - unsigned int channel, - char *msg); + const char *msg); +extern void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci, unsigned int csrow, + unsigned int channel0, unsigned int channel1, + char *msg); +extern void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci, unsigned int csrow, + unsigned int channel, char *msg); /* - * This kmalloc's and initializes all the structures. - * Can't be used if all structures don't have the same lifetime. + * edac_device APIs */ -extern struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows, - unsigned nr_chans); +extern int edac_device_add_device(struct edac_device_ctl_info *edac_dev); +extern struct edac_device_ctl_info *edac_device_del_device(struct device *dev); +extern void edac_device_handle_ue(struct edac_device_ctl_info *edac_dev, + int inst_nr, int block_nr, const char *msg); +extern void edac_device_handle_ce(struct edac_device_ctl_info *edac_dev, + int inst_nr, int block_nr, const char *msg); -/* Free an mc previously allocated by edac_mc_alloc() */ -extern void edac_mc_free(struct mem_ctl_info *mci); +/* + * edac_pci APIs + */ +extern struct edac_pci_ctl_info *edac_pci_alloc_ctl_info(unsigned int sz_pvt, + const char *edac_pci_name); + +extern void edac_pci_free_ctl_info(struct edac_pci_ctl_info *pci); + +extern void edac_pci_reset_delay_period(struct edac_pci_ctl_info *pci, + unsigned long value); + +extern int edac_pci_add_device(struct edac_pci_ctl_info *pci, int edac_idx); +extern struct edac_pci_ctl_info *edac_pci_del_device(struct device *dev); + +extern struct edac_pci_ctl_info *edac_pci_create_generic_ctl( + struct device *dev, + const char *mod_name); + +extern void edac_pci_release_generic_ctl(struct edac_pci_ctl_info *pci); +extern int edac_pci_create_sysfs(struct edac_pci_ctl_info *pci); +extern void edac_pci_remove_sysfs(struct edac_pci_ctl_info *pci); + +/* + * edac misc APIs + */ +extern char *edac_op_state_to_string(int op_state); -#endif /* _EDAC_MC_H_ */ +#endif /* _EDAC_CORE_H_ */ diff --git a/drivers/edac/edac_device.c b/drivers/edac/edac_device.c new file mode 100644 index 0000000..f3690a6 --- /dev/null +++ b/drivers/edac/edac_device.c @@ -0,0 +1,746 @@ + +/* + * edac_device.c + * (C) 2007 www.douglaskthompson.com + * + * This file may be distributed under the terms of the + * GNU General Public License. + * + * Written by Doug Thompson <norsk5@xmission.com> + * + * edac_device API implementation + * 19 Jan 2007 + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <linux/sysctl.h> +#include <linux/highmem.h> +#include <linux/timer.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/sysdev.h> +#include <linux/ctype.h> +#include <linux/workqueue.h> +#include <asm/uaccess.h> +#include <asm/page.h> + +#include "edac_core.h" +#include "edac_module.h" + +/* lock for the list: 'edac_device_list', manipulation of this list + * is protected by the 'device_ctls_mutex' lock + */ +static DEFINE_MUTEX(device_ctls_mutex); +static struct list_head edac_device_list = LIST_HEAD_INIT(edac_device_list); + +#ifdef CONFIG_EDAC_DEBUG +static void edac_device_dump_device(struct edac_device_ctl_info *edac_dev) +{ + debugf3("\tedac_dev = %p dev_idx=%d \n", edac_dev, edac_dev->dev_idx); + debugf4("\tedac_dev->edac_check = %p\n", edac_dev->edac_check); + debugf3("\tdev = %p\n", edac_dev->dev); + debugf3("\tmod_name:ctl_name = %s:%s\n", + edac_dev->mod_name, edac_dev->ctl_name); + debugf3("\tpvt_info = %p\n\n", edac_dev->pvt_info); +} +#endif /* CONFIG_EDAC_DEBUG */ + + +/* + * edac_device_alloc_ctl_info() + * Allocate a new edac device control info structure + * + * The control structure is allocated in complete chunk + * from the OS. It is in turn sub allocated to the + * various objects that compose the struture + * + * The structure has a 'nr_instance' array within itself. + * Each instance represents a major component + * Example: L1 cache and L2 cache are 2 instance components + * + * Within each instance is an array of 'nr_blocks' blockoffsets + */ +struct edac_device_ctl_info *edac_device_alloc_ctl_info( + unsigned sz_private, + char *edac_device_name, unsigned nr_instances, + char *edac_block_name, unsigned nr_blocks, + unsigned offset_value, /* zero, 1, or other based offset */ + struct edac_dev_sysfs_block_attribute *attrib_spec, unsigned nr_attrib, + int device_index) +{ + struct edac_device_ctl_info *dev_ctl; + struct edac_device_instance *dev_inst, *inst; + struct edac_device_block *dev_blk, *blk_p, *blk; + struct edac_dev_sysfs_block_attribute *dev_attrib, *attrib_p, *attrib; + unsigned total_size; + unsigned count; + unsigned instance, block, attr; + void *pvt; + int err; + + debugf4("%s() instances=%d blocks=%d\n", + __func__, nr_instances, nr_blocks); + + /* Calculate the size of memory we need to allocate AND + * determine the offsets of the various item arrays + * (instance,block,attrib) from the start of an allocated structure. + * We want the alignment of each item (instance,block,attrib) + * to be at least as stringent as what the compiler would + * provide if we could simply hardcode everything into a single struct. + */ + dev_ctl = (struct edac_device_ctl_info *)NULL; + + /* Calc the 'end' offset past end of ONE ctl_info structure + * which will become the start of the 'instance' array + */ + dev_inst = edac_align_ptr(&dev_ctl[1], sizeof(*dev_inst)); + + /* Calc the 'end' offset past the instance array within the ctl_info + * which will become the start of the block array + */ + dev_blk = edac_align_ptr(&dev_inst[nr_instances], sizeof(*dev_blk)); + + /* Calc the 'end' offset past the dev_blk array + * which will become the start of the attrib array, if any. + */ + count = nr_instances * nr_blocks; + dev_attrib = edac_align_ptr(&dev_blk[count], sizeof(*dev_attrib)); + + /* Check for case of when an attribute array is specified */ + if (nr_attrib > 0) { + /* calc how many nr_attrib we need */ + count *= nr_attrib; + + /* Calc the 'end' offset past the attributes array */ + pvt = edac_align_ptr(&dev_attrib[count], sz_private); + } else { + /* no attribute array specificed */ + pvt = edac_align_ptr(dev_attrib, sz_private); + } + + /* 'pvt' now points to where the private data area is. + * At this point 'pvt' (like dev_inst,dev_blk and dev_attrib) + * is baselined at ZERO + */ + total_size = ((unsigned long)pvt) + sz_private; + + /* Allocate the amount of memory for the set of control structures */ + dev_ctl = kzalloc(total_size, GFP_KERNEL); + if (dev_ctl == NULL) + return NULL; + + /* Adjust pointers so they point within the actual memory we + * just allocated rather than an imaginary chunk of memory + * located at address 0. + * 'dev_ctl' points to REAL memory, while the others are + * ZERO based and thus need to be adjusted to point within + * the allocated memory. + */ + dev_inst = (struct edac_device_instance *) + (((char *)dev_ctl) + ((unsigned long)dev_inst)); + dev_blk = (struct edac_device_block *) + (((char *)dev_ctl) + ((unsigned long)dev_blk)); + dev_attrib = (struct edac_dev_sysfs_block_attribute *) + (((char *)dev_ctl) + ((unsigned long)dev_attrib)); + pvt = sz_private ? (((char *)dev_ctl) + ((unsigned long)pvt)) : NULL; + + /* Begin storing the information into the control info structure */ + dev_ctl->dev_idx = device_index; + dev_ctl->nr_instances = nr_instances; + dev_ctl->instances = dev_inst; + dev_ctl->pvt_info = pvt; + + /* Name of this edac device */ + snprintf(dev_ctl->name,sizeof(dev_ctl->name),"%s",edac_device_name); + + debugf4("%s() edac_dev=%p next after end=%p\n", + __func__, dev_ctl, pvt + sz_private ); + + /* Initialize every Instance */ + for (instance = 0; instance < nr_instances; instance++) { + inst = &dev_inst[instance]; + inst->ctl = dev_ctl; + inst->nr_blocks = nr_blocks; + blk_p = &dev_blk[instance * nr_blocks]; + inst->blocks = blk_p; + + /* name of this instance */ + snprintf(inst->name, sizeof(inst->name), + "%s%u", edac_device_name, instance); + + /* Initialize every block in each instance */ + for (block = 0; block < nr_blocks; block++) { + blk = &blk_p[block]; + blk->instance = inst; + snprintf(blk->name, sizeof(blk->name), + "%s%d", edac_block_name, block+offset_value); + + debugf4("%s() instance=%d inst_p=%p block=#%d " + "block_p=%p name='%s'\n", + __func__, instance, inst, block, + blk, blk->name); + + /* if there are NO attributes OR no attribute pointer + * then continue on to next block iteration + */ + if ((nr_attrib == 0) || (attrib_spec == NULL)) + continue; + + /* setup the attribute array for this block */ + blk->nr_attribs = nr_attrib; + attrib_p = &dev_attrib[block*nr_instances*nr_attrib]; + blk->block_attributes = attrib_p; + + debugf4("%s() THIS BLOCK_ATTRIB=%p\n", + __func__, blk->block_attributes); + + /* Initialize every user specified attribute in this + * block with the data the caller passed in + * Each block gets its own copy of pointers, + * and its unique 'value' + */ + for (attr = 0; attr < nr_attrib; attr++) { + attrib = &attrib_p[attr]; + + /* populate the unique per attrib + * with the code pointers and info + */ + attrib->attr = attrib_spec[attr].attr; + attrib->show = attrib_spec[attr].show; + attrib->store = attrib_spec[attr].store; + + attrib->block = blk; /* up link */ + + debugf4("%s() alloc-attrib=%p attrib_name='%s' " + "attrib-spec=%p spec-name=%s\n", + __func__, attrib, attrib->attr.name, + &attrib_spec[attr], + attrib_spec[attr].attr.name + ); + } + } + } + + /* Mark this instance as merely ALLOCATED */ + dev_ctl->op_state = OP_ALLOC; + + /* + * Initialize the 'root' kobj for the edac_device controller + */ + err = edac_device_register_sysfs_main_kobj(dev_ctl); + if (err) { + kfree(dev_ctl); + return NULL; + } + + /* at this point, the root kobj is valid, and in order to + * 'free' the object, then the function: + * edac_device_unregister_sysfs_main_kobj() must be called + * which will perform kobj unregistration and the actual free + * will occur during the kobject callback operation + */ + + return dev_ctl; +} +EXPORT_SYMBOL_GPL(edac_device_alloc_ctl_info); + +/* + * edac_device_free_ctl_info() + * frees the memory allocated by the edac_device_alloc_ctl_info() + * function + */ +void edac_device_free_ctl_info(struct edac_device_ctl_info *ctl_info) +{ + edac_device_unregister_sysfs_main_kobj(ctl_info); +} +EXPORT_SYMBOL_GPL(edac_device_free_ctl_info); + +/* + * find_edac_device_by_dev + * scans the edac_device list for a specific 'struct device *' + * + * lock to be held prior to call: device_ctls_mutex + * + * Return: + * pointer to control structure managing 'dev' + * NULL if not found on list + */ +static struct edac_device_ctl_info *find_edac_device_by_dev(struct device *dev) +{ + struct edac_device_ctl_info *edac_dev; + struct list_head *item; + + debugf0("%s()\n", __func__); + + list_for_each(item, &edac_device_list) { + edac_dev = list_entry(item, struct edac_device_ctl_info, link); + + if (edac_dev->dev == dev) + return edac_dev; + } + + return NULL; +} + +/* + * add_edac_dev_to_global_list + * Before calling this function, caller must + * assign a unique value to edac_dev->dev_idx. + * + * lock to be held prior to call: device_ctls_mutex + * + * Return: + * 0 on success + * 1 on failure. + */ +static int add_edac_dev_to_global_list(struct edac_device_ctl_info *edac_dev) +{ + struct list_head *item, *insert_before; + struct edac_device_ctl_info *rover; + + insert_before = &edac_device_list; + + /* Determine if already on the list */ + rover = find_edac_device_by_dev(edac_dev->dev); + if (unlikely(rover != NULL)) + goto fail0; + + /* Insert in ascending order by 'dev_idx', so find position */ + list_for_each(item, &edac_device_list) { + rover = list_entry(item, struct edac_device_ctl_info, link); + + if (rover->dev_idx >= edac_dev->dev_idx) { + if (unlikely(rover->dev_idx == edac_dev->dev_idx)) + goto fail1; + + insert_before = item; + break; + } + } + + list_add_tail_rcu(&edac_dev->link, insert_before); + return 0; + +fail0: + edac_printk(KERN_WARNING, EDAC_MC, + "%s (%s) %s %s already assigned %d\n", + rover->dev->bus_id, dev_name(rover), + rover->mod_name, rover->ctl_name, rover->dev_idx); + return 1; + +fail1: + edac_printk(KERN_WARNING, EDAC_MC, + "bug in low-level driver: attempt to assign\n" + " duplicate dev_idx %d in %s()\n", rover->dev_idx, + __func__); + return 1; +} + +/* + * complete_edac_device_list_del + * + * callback function when reference count is zero + */ +static void complete_edac_device_list_del(struct rcu_head *head) +{ + struct edac_device_ctl_info *edac_dev; + + edac_dev = container_of(head, struct edac_device_ctl_info, rcu); + INIT_LIST_HEAD(&edac_dev->link); + complete(&edac_dev->removal_complete); +} + +/* + * del_edac_device_from_global_list + * + * remove the RCU, setup for a callback call, + * then wait for the callback to occur + */ +static void del_edac_device_from_global_list(struct edac_device_ctl_info + *edac_device) +{ + list_del_rcu(&edac_device->link); + + init_completion(&edac_device->removal_complete); + call_rcu(&edac_device->rcu, complete_edac_device_list_del); + wait_for_completion(&edac_device->removal_complete); +} + +/** + * edac_device_find + * Search for a edac_device_ctl_info structure whose index is 'idx'. + * + * If found, return a pointer to the structure. + * Else return NULL. + * + * Caller must hold device_ctls_mutex. + */ +struct edac_device_ctl_info *edac_device_find(int idx) +{ + struct list_head *item; + struct edac_device_ctl_info *edac_dev; + + /* Iterate over list, looking for exact match of ID */ + list_for_each(item, &edac_device_list) { + edac_dev = list_entry(item, struct edac_device_ctl_info, link); + + if (edac_dev->dev_idx >= idx) { + if (edac_dev->dev_idx == idx) + return edac_dev; + + /* not on list, so terminate early */ + break; + } + } + + return NULL; +} +EXPORT_SYMBOL_GPL(edac_device_find); + +/* + * edac_device_workq_function + * performs the operation scheduled by a workq request + * + * this workq is embedded within an edac_device_ctl_info + * structure, that needs to be polled for possible error events. + * + * This operation is to acquire the list mutex lock + * (thus preventing insertation or deletion) + * and then call the device's poll function IFF this device is + * running polled and there is a poll function defined. + */ +static void edac_device_workq_function(struct work_struct *work_req) +{ + struct delayed_work *d_work = (struct delayed_work *)work_req; + struct edac_device_ctl_info *edac_dev = to_edac_device_ctl_work(d_work); + + mutex_lock(&device_ctls_mutex); + + /* Only poll controllers that are running polled and have a check */ + if ((edac_dev->op_state == OP_RUNNING_POLL) && + (edac_dev->edac_check != NULL)) { + edac_dev->edac_check(edac_dev); + } + + mutex_unlock(&device_ctls_mutex); + + /* Reschedule the workq for the next time period to start again + * if the number of msec is for 1 sec, then adjust to the next + * whole one second to save timers fireing all over the period + * between integral seconds + */ + if (edac_dev->poll_msec == 1000) + queue_delayed_work(edac_workqueue, &edac_dev->work, + round_jiffies(edac_dev->delay)); + else + queue_delayed_work(edac_workqueue, &edac_dev->work, + edac_dev->delay); +} + +/* + * edac_device_workq_setup + * initialize a workq item for this edac_device instance + * passing in the new delay period in msec + */ +void edac_device_workq_setup(struct edac_device_ctl_info *edac_dev, + unsigned msec) +{ + debugf0("%s()\n", __func__); + + /* take the arg 'msec' and set it into the control structure + * to used in the time period calculation + * then calc the number of jiffies that represents + */ + edac_dev->poll_msec = msec; + edac_dev->delay = msecs_to_jiffies(msec); + + INIT_DELAYED_WORK(&edac_dev->work, edac_device_workq_function); + + /* optimize here for the 1 second case, which will be normal value, to + * fire ON the 1 second time event. This helps reduce all sorts of + * timers firing on sub-second basis, while they are happy + * to fire together on the 1 second exactly + */ + if (edac_dev->poll_msec == 1000) + queue_delayed_work(edac_workqueue, &edac_dev->work, + round_jiffies(edac_dev->delay)); + else + queue_delayed_work(edac_workqueue, &edac_dev->work, + edac_dev->delay); +} + +/* + * edac_device_workq_teardown + * stop the workq processing on this edac_dev + */ +void edac_device_workq_teardown(struct edac_device_ctl_info *edac_dev) +{ + int status; + + status = cancel_delayed_work(&edac_dev->work); + if (status == 0) { + /* workq instance might be running, wait for it */ + flush_workqueue(edac_workqueue); + } +} + +/* + * edac_device_reset_delay_period + * + * need to stop any outstanding workq queued up at this time + * because we will be resetting the sleep time. + * Then restart the workq on the new delay + */ +void edac_device_reset_delay_period(struct edac_device_ctl_info *edac_dev, + unsigned long value) +{ + /* cancel the current workq request, without the mutex lock */ + edac_device_workq_teardown(edac_dev); + + /* acquire the mutex before doing the workq setup */ + mutex_lock(&device_ctls_mutex); + + /* restart the workq request, with new delay value */ + edac_device_workq_setup(edac_dev, value); + + mutex_unlock(&device_ctls_mutex); +} + +/** + * edac_device_add_device: Insert the 'edac_dev' structure into the + * edac_device global list and create sysfs entries associated with + * edac_device structure. + * @edac_device: pointer to the edac_device structure to be added to the list + * 'edac_device' structure. + * + * Return: + * 0 Success + * !0 Failure + */ +int edac_device_add_device(struct edac_device_ctl_info *edac_dev) +{ + debugf0("%s()\n", __func__); + +#ifdef CONFIG_EDAC_DEBUG + if (edac_debug_level >= 3) + edac_device_dump_device(edac_dev); +#endif + mutex_lock(&device_ctls_mutex); + + if (add_edac_dev_to_global_list(edac_dev)) + goto fail0; + + /* set load time so that error rate can be tracked */ + edac_dev->start_time = jiffies; + + /* create this instance's sysfs entries */ + if (edac_device_create_sysfs(edac_dev)) { + edac_device_printk(edac_dev, KERN_WARNING, + "failed to create sysfs device\n"); + goto fail1; + } + + /* If there IS a check routine, then we are running POLLED */ + if (edac_dev->edac_check != NULL) { + /* This instance is NOW RUNNING */ + edac_dev->op_state = OP_RUNNING_POLL; + + /* + * enable workq processing on this instance, + * default = 1000 msec + */ + edac_device_workq_setup(edac_dev, 1000); + } else { + edac_dev->op_state = OP_RUNNING_INTERRUPT; + } + + /* Report action taken */ + edac_device_printk(edac_dev, KERN_INFO, + "Giving out device to module '%s' controller " + "'%s': DEV '%s' (%s)\n", + edac_dev->mod_name, + edac_dev->ctl_name, + dev_name(edac_dev), + edac_op_state_to_string(edac_dev->op_state)); + + mutex_unlock(&device_ctls_mutex); + return 0; + +fail1: + /* Some error, so remove the entry from the lsit */ + del_edac_device_from_global_list(edac_dev); + +fail0: + mutex_unlock(&device_ctls_mutex); + return 1; +} +EXPORT_SYMBOL_GPL(edac_device_add_device); + +/** + * edac_device_del_device: + * Remove sysfs entries for specified edac_device structure and + * then remove edac_device structure from global list + * + * @pdev: + * Pointer to 'struct device' representing edac_device + * structure to remove. + * + * Return: + * Pointer to removed edac_device structure, + * OR NULL if device not found. + */ +struct edac_device_ctl_info *edac_device_del_device(struct device *dev) +{ + struct edac_device_ctl_info *edac_dev; + + debugf0("%s()\n", __func__); + + mutex_lock(&device_ctls_mutex); + + /* Find the structure on the list, if not there, then leave */ + edac_dev = find_edac_device_by_dev(dev); + if (edac_dev == NULL) { + mutex_unlock(&device_ctls_mutex); + return NULL; + } + + /* mark this instance as OFFLINE */ + edac_dev->op_state = OP_OFFLINE; + + /* clear workq processing on this instance */ + edac_device_workq_teardown(edac_dev); + + /* deregister from global list */ + del_edac_device_from_global_list(edac_dev); + + mutex_unlock(&device_ctls_mutex); + + /* Tear down the sysfs entries for this instance */ + edac_device_remove_sysfs(edac_dev); + + edac_printk(KERN_INFO, EDAC_MC, + "Removed device %d for %s %s: DEV %s\n", + edac_dev->dev_idx, + edac_dev->mod_name, edac_dev->ctl_name, dev_name(edac_dev)); + + return edac_dev; +} +EXPORT_SYMBOL_GPL(edac_device_del_device); + +static inline int edac_device_get_log_ce(struct edac_device_ctl_info *edac_dev) +{ + return edac_dev->log_ce; +} + +static inline int edac_device_get_log_ue(struct edac_device_ctl_info *edac_dev) +{ + return edac_dev->log_ue; +} + +static inline int edac_device_get_panic_on_ue(struct edac_device_ctl_info + *edac_dev) +{ + return edac_dev->panic_on_ue; +} + +/* + * edac_device_handle_ce + * perform a common output and handling of an 'edac_dev' CE event + */ +void edac_device_handle_ce(struct edac_device_ctl_info *edac_dev, + int inst_nr, int block_nr, const char *msg) +{ + struct edac_device_instance *instance; + struct edac_device_block *block = NULL; + + if ((inst_nr >= edac_dev->nr_instances) || (inst_nr < 0)) { + edac_device_printk(edac_dev, KERN_ERR, + "INTERNAL ERROR: 'instance' out of range " + "(%d >= %d)\n", inst_nr, + edac_dev->nr_instances); + return; + } + + instance = edac_dev->instances + inst_nr; + + if ((block_nr >= instance->nr_blocks) || (block_nr < 0)) { + edac_device_printk(edac_dev, KERN_ERR, + "INTERNAL ERROR: instance %d 'block' " + "out of range (%d >= %d)\n", + inst_nr, block_nr, + instance->nr_blocks); + return; + } + + if (instance->nr_blocks > 0) { + block = instance->blocks + block_nr; + block->counters.ce_count++; + } + + /* Propogate the count up the 'totals' tree */ + instance->counters.ce_count++; + edac_dev->counters.ce_count++; + + if (edac_device_get_log_ce(edac_dev)) + edac_device_printk(edac_dev, KERN_WARNING, + "CE: %s instance: %s block: %s '%s'\n", + edac_dev->ctl_name, instance->name, + block ? block->name : "N/A", msg); +} +EXPORT_SYMBOL_GPL(edac_device_handle_ce); + +/* + * edac_device_handle_ue + * perform a common output and handling of an 'edac_dev' UE event + */ +void edac_device_handle_ue(struct edac_device_ctl_info *edac_dev, + int inst_nr, int block_nr, const char *msg) +{ + struct edac_device_instance *instance; + struct edac_device_block *block = NULL; + + if ((inst_nr >= edac_dev->nr_instances) || (inst_nr < 0)) { + edac_device_printk(edac_dev, KERN_ERR, + "INTERNAL ERROR: 'instance' out of range " + "(%d >= %d)\n", inst_nr, + edac_dev->nr_instances); + return; + } + + instance = edac_dev->instances + inst_nr; + + if ((block_nr >= instance->nr_blocks) || (block_nr < 0)) { + edac_device_printk(edac_dev, KERN_ERR, + "INTERNAL ERROR: instance %d 'block' " + "out of range (%d >= %d)\n", + inst_nr, block_nr, + instance->nr_blocks); + return; + } + + if (instance->nr_blocks > 0) { + block = instance->blocks + block_nr; + block->counters.ue_count++; + } + + /* Propogate the count up the 'totals' tree */ + instance->counters.ue_count++; + edac_dev->counters.ue_count++; + + if (edac_device_get_log_ue(edac_dev)) + edac_device_printk(edac_dev, KERN_EMERG, + "UE: %s instance: %s block: %s '%s'\n", + edac_dev->ctl_name, instance->name, + block ? block->name : "N/A", msg); + + if (edac_device_get_panic_on_ue(edac_dev)) + panic("EDAC %s: UE instance: %s block %s '%s'\n", + edac_dev->ctl_name, instance->name, + block ? block->name : "N/A", msg); +} +EXPORT_SYMBOL_GPL(edac_device_handle_ue); diff --git a/drivers/edac/edac_device_sysfs.c b/drivers/edac/edac_device_sysfs.c new file mode 100644 index 0000000..70b837f --- /dev/null +++ b/drivers/edac/edac_device_sysfs.c @@ -0,0 +1,896 @@ +/* + * file for managing the edac_device class of devices for EDAC + * + * (C) 2007 SoftwareBitMaker (http://www.softwarebitmaker.com) + * + * This file may be distributed under the terms of the + * GNU General Public License. + * + * Written Doug Thompson <norsk5@xmission.com> + * + */ + +#include <linux/ctype.h> +#include <linux/module.h> + +#include "edac_core.h" +#include "edac_module.h" + +#define EDAC_DEVICE_SYMLINK "device" + +#define to_edacdev(k) container_of(k, struct edac_device_ctl_info, kobj) +#define to_edacdev_attr(a) container_of(a, struct edacdev_attribute, attr) + + +/* + * Set of edac_device_ctl_info attribute store/show functions + */ + +/* 'log_ue' */ +static ssize_t edac_device_ctl_log_ue_show(struct edac_device_ctl_info + *ctl_info, char *data) +{ + return sprintf(data, "%u\n", ctl_info->log_ue); +} + +static ssize_t edac_device_ctl_log_ue_store(struct edac_device_ctl_info + *ctl_info, const char *data, + size_t count) +{ + /* if parameter is zero, turn off flag, if non-zero turn on flag */ + ctl_info->log_ue = (simple_strtoul(data, NULL, 0) != 0); + + return count; +} + +/* 'log_ce' */ +static ssize_t edac_device_ctl_log_ce_show(struct edac_device_ctl_info + *ctl_info, char *data) +{ + return sprintf(data, "%u\n", ctl_info->log_ce); +} + +static ssize_t edac_device_ctl_log_ce_store(struct edac_device_ctl_info + *ctl_info, const char *data, + size_t count) +{ + /* if parameter is zero, turn off flag, if non-zero turn on flag */ + ctl_info->log_ce = (simple_strtoul(data, NULL, 0) != 0); + + return count; +} + +/* 'panic_on_ue' */ +static ssize_t edac_device_ctl_panic_on_ue_show(struct edac_device_ctl_info + *ctl_info, char *data) +{ + return sprintf(data, "%u\n", ctl_info->panic_on_ue); +} + +static ssize_t edac_device_ctl_panic_on_ue_store(struct edac_device_ctl_info + *ctl_info, const char *data, + size_t count) +{ + /* if parameter is zero, turn off flag, if non-zero turn on flag */ + ctl_info->panic_on_ue = (simple_strtoul(data, NULL, 0) != 0); + + return count; +} + +/* 'poll_msec' show and store functions*/ +static ssize_t edac_device_ctl_poll_msec_show(struct edac_device_ctl_info + *ctl_info, char *data) +{ + return sprintf(data, "%u\n", ctl_info->poll_msec); +} + +static ssize_t edac_device_ctl_poll_msec_store(struct edac_device_ctl_info + *ctl_info, const char *data, + size_t count) +{ + unsigned long value; + + /* get the value and enforce that it is non-zero, must be at least + * one millisecond for the delay period, between scans + * Then cancel last outstanding delay for the work request + * and set a new one. + */ + value = simple_strtoul(data, NULL, 0); + edac_device_reset_delay_period(ctl_info, value); + + return count; +} + +/* edac_device_ctl_info specific attribute structure */ +struct ctl_info_attribute { + struct attribute attr; + ssize_t(*show) (struct edac_device_ctl_info *, char *); + ssize_t(*store) (struct edac_device_ctl_info *, const char *, size_t); +}; + +#define to_ctl_info(k) container_of(k, struct edac_device_ctl_info, kobj) +#define to_ctl_info_attr(a) container_of(a,struct ctl_info_attribute,attr) + +/* Function to 'show' fields from the edac_dev 'ctl_info' structure */ +static ssize_t edac_dev_ctl_info_show(struct kobject *kobj, + struct attribute *attr, char *buffer) +{ + struct edac_device_ctl_info *edac_dev = to_ctl_info(kobj); + struct ctl_info_attribute *ctl_info_attr = to_ctl_info_attr(attr); + + if (ctl_info_attr->show) + return ctl_info_attr->show(edac_dev, buffer); + return -EIO; +} + +/* Function to 'store' fields into the edac_dev 'ctl_info' structure */ +static ssize_t edac_dev_ctl_info_store(struct kobject *kobj, + struct attribute *attr, + const char *buffer, size_t count) +{ + struct edac_device_ctl_info *edac_dev = to_ctl_info(kobj); + struct ctl_info_attribute *ctl_info_attr = to_ctl_info_attr(attr); + + if (ctl_info_attr->store) + return ctl_info_attr->store(edac_dev, buffer, count); + return -EIO; +} + +/* edac_dev file operations for an 'ctl_info' */ +static struct sysfs_ops device_ctl_info_ops = { + .show = edac_dev_ctl_info_show, + .store = edac_dev_ctl_info_store +}; + +#define CTL_INFO_ATTR(_name,_mode,_show,_store) \ +static struct ctl_info_attribute attr_ctl_info_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +}; + +/* Declare the various ctl_info attributes here and their respective ops */ +CTL_INFO_ATTR(log_ue, S_IRUGO | S_IWUSR, + edac_device_ctl_log_ue_show, edac_device_ctl_log_ue_store); +CTL_INFO_ATTR(log_ce, S_IRUGO | S_IWUSR, + edac_device_ctl_log_ce_show, edac_device_ctl_log_ce_store); +CTL_INFO_ATTR(panic_on_ue, S_IRUGO | S_IWUSR, + edac_device_ctl_panic_on_ue_show, + edac_device_ctl_panic_on_ue_store); +CTL_INFO_ATTR(poll_msec, S_IRUGO | S_IWUSR, + edac_device_ctl_poll_msec_show, edac_device_ctl_poll_msec_store); + +/* Base Attributes of the EDAC_DEVICE ECC object */ +static struct ctl_info_attribute *device_ctrl_attr[] = { + &attr_ctl_info_panic_on_ue, + &attr_ctl_info_log_ue, + &attr_ctl_info_log_ce, + &attr_ctl_info_poll_msec, + NULL, +}; + +/* + * edac_device_ctrl_master_release + * + * called when the reference count for the 'main' kobj + * for a edac_device control struct reaches zero + * + * Reference count model: + * One 'main' kobject for each control structure allocated. + * That main kobj is initially set to one AND + * the reference count for the EDAC 'core' module is + * bumped by one, thus added 'keep in memory' dependency. + * + * Each new internal kobj (in instances and blocks) then + * bumps the 'main' kobject. + * + * When they are released their release functions decrement + * the 'main' kobj. + * + * When the main kobj reaches zero (0) then THIS function + * is called which then decrements the EDAC 'core' module. + * When the module reference count reaches zero then the + * module no longer has dependency on keeping the release + * function code in memory and module can be unloaded. + * + * This will support several control objects as well, each + * with its own 'main' kobj. + */ +static void edac_device_ctrl_master_release(struct kobject *kobj) +{ + struct edac_device_ctl_info *edac_dev = to_edacdev(kobj); + + debugf4("%s() control index=%d\n", __func__, edac_dev->dev_idx); + + /* decrement the EDAC CORE module ref count */ + module_put(edac_dev->owner); + + /* free the control struct containing the 'main' kobj + * passed in to this routine + */ + kfree(edac_dev); +} + +/* ktype for the main (master) kobject */ +static struct kobj_type ktype_device_ctrl = { + .release = edac_device_ctrl_master_release, + .sysfs_ops = &device_ctl_info_ops, + .default_attrs = (struct attribute **)device_ctrl_attr, +}; + +/* + * edac_device_register_sysfs_main_kobj + * + * perform the high level setup for the new edac_device instance + * + * Return: 0 SUCCESS + * !0 FAILURE + */ +int edac_device_register_sysfs_main_kobj(struct edac_device_ctl_info *edac_dev) +{ + struct sysdev_class *edac_class; + int err; + + debugf1("%s()\n", __func__); + + /* get the /sys/devices/system/edac reference */ + edac_class = edac_get_edac_class(); + if (edac_class == NULL) { + debugf1("%s() no edac_class error\n", __func__); + err = -ENODEV; + goto err_out; + } + + /* Point to the 'edac_class' this instance 'reports' to */ + edac_dev->edac_class = edac_class; + + /* Init the devices's kobject */ + memset(&edac_dev->kobj, 0, sizeof(struct kobject)); + edac_dev->kobj.ktype = &ktype_device_ctrl; + + /* set this new device under the edac_class kobject */ + edac_dev->kobj.parent = &edac_class->kset.kobj; + + /* generate sysfs "..../edac/<name>" */ + debugf4("%s() set name of kobject to: %s\n", __func__, edac_dev->name); + err = kobject_set_name(&edac_dev->kobj, "%s", edac_dev->name); + if (err) + goto err_out; + + /* Record which module 'owns' this control structure + * and bump the ref count of the module + */ + edac_dev->owner = THIS_MODULE; + + if (!try_module_get(edac_dev->owner)) { + err = -ENODEV; + goto err_out; + } + + /* register */ + err = kobject_register(&edac_dev->kobj); + if (err) { + debugf1("%s()Failed to register '.../edac/%s'\n", + __func__, edac_dev->name); + goto err_kobj_reg; + } + + /* At this point, to 'free' the control struct, + * edac_device_unregister_sysfs_main_kobj() must be used + */ + + debugf4("%s() Registered '.../edac/%s' kobject\n", + __func__, edac_dev->name); + + return 0; + + /* Error exit stack */ +err_kobj_reg: + module_put(edac_dev->owner); + +err_out: + return err; +} + +/* + * edac_device_unregister_sysfs_main_kobj: + * the '..../edac/<name>' kobject + */ +void edac_device_unregister_sysfs_main_kobj( + struct edac_device_ctl_info *edac_dev) +{ + debugf0("%s()\n", __func__); + debugf4("%s() name of kobject is: %s\n", + __func__, kobject_name(&edac_dev->kobj)); + + /* + * Unregister the edac device's kobject and + * allow for reference count to reach 0 at which point + * the callback will be called to: + * a) module_put() this module + * b) 'kfree' the memory + */ + kobject_unregister(&edac_dev->kobj); +} + +/* edac_dev -> instance information */ + +/* + * Set of low-level instance attribute show functions + */ +static ssize_t instance_ue_count_show(struct edac_device_instance *instance, + char *data) +{ + return sprintf(data, "%u\n", instance->counters.ue_count); +} + +static ssize_t instance_ce_count_show(struct edac_device_instance *instance, + char *data) +{ + return sprintf(data, "%u\n", instance->counters.ce_count); +} + +#define to_instance(k) container_of(k, struct edac_device_instance, kobj) +#define to_instance_attr(a) container_of(a,struct instance_attribute,attr) + +/* DEVICE instance kobject release() function */ +static void edac_device_ctrl_instance_release(struct kobject *kobj) +{ + struct edac_device_instance *instance; + + debugf1("%s()\n", __func__); + + /* map from this kobj to the main control struct + * and then dec the main kobj count + */ + instance = to_instance(kobj); + kobject_put(&instance->ctl->kobj); +} + +/* instance specific attribute structure */ +struct instance_attribute { + struct attribute attr; + ssize_t(*show) (struct edac_device_instance *, char *); + ssize_t(*store) (struct edac_device_instance *, const char *, size_t); +}; + +/* Function to 'show' fields from the edac_dev 'instance' structure */ +static ssize_t edac_dev_instance_show(struct kobject *kobj, + struct attribute *attr, char *buffer) +{ + struct edac_device_instance *instance = to_instance(kobj); + struct instance_attribute *instance_attr = to_instance_attr(attr); + + if (instance_attr->show) + return instance_attr->show(instance, buffer); + return -EIO; +} + +/* Function to 'store' fields into the edac_dev 'instance' structure */ +static ssize_t edac_dev_instance_store(struct kobject *kobj, + struct attribute *attr, + const char *buffer, size_t count) +{ + struct edac_device_instance *instance = to_instance(kobj); + struct instance_attribute *instance_attr = to_instance_attr(attr); + + if (instance_attr->store) + return instance_attr->store(instance, buffer, count); + return -EIO; +} + +/* edac_dev file operations for an 'instance' */ +static struct sysfs_ops device_instance_ops = { + .show = edac_dev_instance_show, + .store = edac_dev_instance_store +}; + +#define INSTANCE_ATTR(_name,_mode,_show,_store) \ +static struct instance_attribute attr_instance_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +}; + +/* + * Define attributes visible for the edac_device instance object + * Each contains a pointer to a show and an optional set + * function pointer that does the low level output/input + */ +INSTANCE_ATTR(ce_count, S_IRUGO, instance_ce_count_show, NULL); +INSTANCE_ATTR(ue_count, S_IRUGO, instance_ue_count_show, NULL); + +/* list of edac_dev 'instance' attributes */ +static struct instance_attribute *device_instance_attr[] = { + &attr_instance_ce_count, + &attr_instance_ue_count, + NULL, +}; + +/* The 'ktype' for each edac_dev 'instance' */ +static struct kobj_type ktype_instance_ctrl = { + .release = edac_device_ctrl_instance_release, + .sysfs_ops = &device_instance_ops, + .default_attrs = (struct attribute **)device_instance_attr, +}; + +/* edac_dev -> instance -> block information */ + +#define to_block(k) container_of(k, struct edac_device_block, kobj) +#define to_block_attr(a) \ + container_of(a, struct edac_dev_sysfs_block_attribute, attr) + +/* + * Set of low-level block attribute show functions + */ +static ssize_t block_ue_count_show(struct kobject *kobj, + struct attribute *attr, char *data) +{ + struct edac_device_block *block = to_block(kobj); + + return sprintf(data, "%u\n", block->counters.ue_count); +} + +static ssize_t block_ce_count_show(struct kobject *kobj, + struct attribute *attr, char *data) +{ + struct edac_device_block *block = to_block(kobj); + + return sprintf(data, "%u\n", block->counters.ce_count); +} + +/* DEVICE block kobject release() function */ +static void edac_device_ctrl_block_release(struct kobject *kobj) +{ + struct edac_device_block *block; + + debugf1("%s()\n", __func__); + + /* get the container of the kobj */ + block = to_block(kobj); + + /* map from 'block kobj' to 'block->instance->controller->main_kobj' + * now 'release' the block kobject + */ + kobject_put(&block->instance->ctl->kobj); +} + + +/* Function to 'show' fields from the edac_dev 'block' structure */ +static ssize_t edac_dev_block_show(struct kobject *kobj, + struct attribute *attr, char *buffer) +{ + struct edac_dev_sysfs_block_attribute *block_attr = + to_block_attr(attr); + + if (block_attr->show) + return block_attr->show(kobj, attr, buffer); + return -EIO; +} + +/* Function to 'store' fields into the edac_dev 'block' structure */ +static ssize_t edac_dev_block_store(struct kobject *kobj, + struct attribute *attr, + const char *buffer, size_t count) +{ + struct edac_dev_sysfs_block_attribute *block_attr; + + block_attr = to_block_attr(attr); + + if (block_attr->store) + return block_attr->store(kobj, attr, buffer, count); + return -EIO; +} + +/* edac_dev file operations for a 'block' */ +static struct sysfs_ops device_block_ops = { + .show = edac_dev_block_show, + .store = edac_dev_block_store +}; + +#define BLOCK_ATTR(_name,_mode,_show,_store) \ +static struct edac_dev_sysfs_block_attribute attr_block_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +}; + +BLOCK_ATTR(ce_count, S_IRUGO, block_ce_count_show, NULL); +BLOCK_ATTR(ue_count, S_IRUGO, block_ue_count_show, NULL); + +/* list of edac_dev 'block' attributes */ +static struct edac_dev_sysfs_block_attribute *device_block_attr[] = { + &attr_block_ce_count, + &attr_block_ue_count, + NULL, +}; + +/* The 'ktype' for each edac_dev 'block' */ +static struct kobj_type ktype_block_ctrl = { + .release = edac_device_ctrl_block_release, + .sysfs_ops = &device_block_ops, + .default_attrs = (struct attribute **)device_block_attr, +}; + +/* block ctor/dtor code */ + +/* + * edac_device_create_block + */ +static int edac_device_create_block(struct edac_device_ctl_info *edac_dev, + struct edac_device_instance *instance, + struct edac_device_block *block) +{ + int i; + int err; + struct edac_dev_sysfs_block_attribute *sysfs_attrib; + struct kobject *main_kobj; + + debugf4("%s() Instance '%s' inst_p=%p block '%s' block_p=%p\n", + __func__, instance->name, instance, block->name, block); + debugf4("%s() block kobj=%p block kobj->parent=%p\n", + __func__, &block->kobj, &block->kobj.parent); + + /* init this block's kobject */ + memset(&block->kobj, 0, sizeof(struct kobject)); + block->kobj.parent = &instance->kobj; + block->kobj.ktype = &ktype_block_ctrl; + + err = kobject_set_name(&block->kobj, "%s", block->name); + if (err) + return err; + + /* bump the main kobject's reference count for this controller + * and this instance is dependant on the main + */ + main_kobj = kobject_get(&edac_dev->kobj); + if (!main_kobj) { + err = -ENODEV; + goto err_out; + } + + /* Add this block's kobject */ + err = kobject_register(&block->kobj); + if (err) { + debugf1("%s() Failed to register instance '%s'\n", + __func__, block->name); + kobject_put(main_kobj); + err = -ENODEV; + goto err_out; + } + + /* If there are driver level block attributes, then added them + * to the block kobject + */ + sysfs_attrib = block->block_attributes; + if (sysfs_attrib && block->nr_attribs) { + for (i = 0; i < block->nr_attribs; i++, sysfs_attrib++) { + + debugf4("%s() creating block attrib='%s' " + "attrib->%p to kobj=%p\n", + __func__, + sysfs_attrib->attr.name, + sysfs_attrib, &block->kobj); + + /* Create each block_attribute file */ + err = sysfs_create_file(&block->kobj, + &sysfs_attrib->attr); + if (err) + goto err_on_attrib; + } + } + + return 0; + + /* Error unwind stack */ +err_on_attrib: + kobject_unregister(&block->kobj); + +err_out: + return err; +} + +/* + * edac_device_delete_block(edac_dev,block); + */ +static void edac_device_delete_block(struct edac_device_ctl_info *edac_dev, + struct edac_device_block *block) +{ + struct edac_dev_sysfs_block_attribute *sysfs_attrib; + int i; + + /* if this block has 'attributes' then we need to iterate over the list + * and 'remove' the attributes on this block + */ + sysfs_attrib = block->block_attributes; + if (sysfs_attrib && block->nr_attribs) { + for (i = 0; i < block->nr_attribs; i++, sysfs_attrib++) { + + /* remove each block_attrib file */ + sysfs_remove_file(&block->kobj, + (struct attribute *) sysfs_attrib); + } + } + + /* unregister this block's kobject, SEE: + * edac_device_ctrl_block_release() callback operation + */ + kobject_unregister(&block->kobj); +} + +/* instance ctor/dtor code */ + +/* + * edac_device_create_instance + * create just one instance of an edac_device 'instance' + */ +static int edac_device_create_instance(struct edac_device_ctl_info *edac_dev, + int idx) +{ + int i, j; + int err; + struct edac_device_instance *instance; + struct kobject *main_kobj; + + instance = &edac_dev->instances[idx]; + + /* Init the instance's kobject */ + memset(&instance->kobj, 0, sizeof(struct kobject)); + + /* set this new device under the edac_device main kobject */ + instance->kobj.parent = &edac_dev->kobj; + instance->kobj.ktype = &ktype_instance_ctrl; + instance->ctl = edac_dev; + + err = kobject_set_name(&instance->kobj, "%s", instance->name); + if (err) + goto err_out; + + /* bump the main kobject's reference count for this controller + * and this instance is dependant on the main + */ + main_kobj = kobject_get(&edac_dev->kobj); + if (!main_kobj) { + err = -ENODEV; + goto err_out; + } + + /* Formally register this instance's kobject */ + err = kobject_register(&instance->kobj); + if (err != 0) { + debugf2("%s() Failed to register instance '%s'\n", + __func__, instance->name); + kobject_put(main_kobj); + goto err_out; + } + + debugf4("%s() now register '%d' blocks for instance %d\n", + __func__, instance->nr_blocks, idx); + + /* register all blocks of this instance */ + for (i = 0; i < instance->nr_blocks; i++) { + err = edac_device_create_block(edac_dev, instance, + &instance->blocks[i]); + if (err) { + /* If any fail, remove all previous ones */ + for (j = 0; j < i; j++) + edac_device_delete_block(edac_dev, + &instance->blocks[j]); + goto err_release_instance_kobj; + } + } + + debugf4("%s() Registered instance %d '%s' kobject\n", + __func__, idx, instance->name); + + return 0; + + /* error unwind stack */ +err_release_instance_kobj: + kobject_unregister(&instance->kobj); + +err_out: + return err; +} + +/* + * edac_device_remove_instance + * remove an edac_device instance + */ +static void edac_device_delete_instance(struct edac_device_ctl_info *edac_dev, + int idx) +{ + struct edac_device_instance *instance; + int i; + + instance = &edac_dev->instances[idx]; + + /* unregister all blocks in this instance */ + for (i = 0; i < instance->nr_blocks; i++) + edac_device_delete_block(edac_dev, &instance->blocks[i]); + + /* unregister this instance's kobject, SEE: + * edac_device_ctrl_instance_release() for callback operation + */ + kobject_unregister(&instance->kobj); +} + +/* + * edac_device_create_instances + * create the first level of 'instances' for this device + * (ie 'cache' might have 'cache0', 'cache1', 'cache2', etc + */ +static int edac_device_create_instances(struct edac_device_ctl_info *edac_dev) +{ + int i, j; + int err; + + debugf0("%s()\n", __func__); + + /* iterate over creation of the instances */ + for (i = 0; i < edac_dev->nr_instances; i++) { + err = edac_device_create_instance(edac_dev, i); + if (err) { + /* unwind previous instances on error */ + for (j = 0; j < i; j++) + edac_device_delete_instance(edac_dev, j); + return err; + } + } + + return 0; +} + +/* + * edac_device_delete_instances(edac_dev); + * unregister all the kobjects of the instances + */ +static void edac_device_delete_instances(struct edac_device_ctl_info *edac_dev) +{ + int i; + + /* iterate over creation of the instances */ + for (i = 0; i < edac_dev->nr_instances; i++) + edac_device_delete_instance(edac_dev, i); +} + +/* edac_dev sysfs ctor/dtor code */ + +/* + * edac_device_add_main_sysfs_attributes + * add some attributes to this instance's main kobject + */ +static int edac_device_add_main_sysfs_attributes( + struct edac_device_ctl_info *edac_dev) +{ + struct edac_dev_sysfs_attribute *sysfs_attrib; + int err = 0; + + sysfs_attrib = edac_dev->sysfs_attributes; + if (sysfs_attrib) { + /* iterate over the array and create an attribute for each + * entry in the list + */ + while (sysfs_attrib->attr.name != NULL) { + err = sysfs_create_file(&edac_dev->kobj, + (struct attribute*) sysfs_attrib); + if (err) + goto err_out; + + sysfs_attrib++; + } + } + +err_out: + return err; +} + +/* + * edac_device_remove_main_sysfs_attributes + * remove any attributes to this instance's main kobject + */ +static void edac_device_remove_main_sysfs_attributes( + struct edac_device_ctl_info *edac_dev) +{ + struct edac_dev_sysfs_attribute *sysfs_attrib; + + /* if there are main attributes, defined, remove them. First, + * point to the start of the array and iterate over it + * removing each attribute listed from this device's instance's kobject + */ + sysfs_attrib = edac_dev->sysfs_attributes; + if (sysfs_attrib) { + while (sysfs_attrib->attr.name != NULL) { + sysfs_remove_file(&edac_dev->kobj, + (struct attribute *) sysfs_attrib); + sysfs_attrib++; + } + } +} + +/* + * edac_device_create_sysfs() Constructor + * + * accept a created edac_device control structure + * and 'export' it to sysfs. The 'main' kobj should already have been + * created. 'instance' and 'block' kobjects should be registered + * along with any 'block' attributes from the low driver. In addition, + * the main attributes (if any) are connected to the main kobject of + * the control structure. + * + * Return: + * 0 Success + * !0 Failure + */ +int edac_device_create_sysfs(struct edac_device_ctl_info *edac_dev) +{ + int err; + struct kobject *edac_kobj = &edac_dev->kobj; + + debugf0("%s() idx=%d\n", __func__, edac_dev->dev_idx); + + /* go create any main attributes callers wants */ + err = edac_device_add_main_sysfs_attributes(edac_dev); + if (err) { + debugf0("%s() failed to add sysfs attribs\n", __func__); + goto err_out; + } + + /* create a symlink from the edac device + * to the platform 'device' being used for this + */ + err = sysfs_create_link(edac_kobj, + &edac_dev->dev->kobj, EDAC_DEVICE_SYMLINK); + if (err) { + debugf0("%s() sysfs_create_link() returned err= %d\n", + __func__, err); + goto err_remove_main_attribs; + } + + /* Create the first level instance directories + * In turn, the nested blocks beneath the instances will + * be registered as well + */ + err = edac_device_create_instances(edac_dev); + if (err) { + debugf0("%s() edac_device_create_instances() " + "returned err= %d\n", __func__, err); + goto err_remove_link; + } + + + debugf4("%s() create-instances done, idx=%d\n", + __func__, edac_dev->dev_idx); + + return 0; + + /* Error unwind stack */ +err_remove_link: + /* remove the sym link */ + sysfs_remove_link(&edac_dev->kobj, EDAC_DEVICE_SYMLINK); + +err_remove_main_attribs: + edac_device_remove_main_sysfs_attributes(edac_dev); + +err_out: + return err; +} + +/* + * edac_device_remove_sysfs() destructor + * + * given an edac_device struct, tear down the kobject resources + */ +void edac_device_remove_sysfs(struct edac_device_ctl_info *edac_dev) +{ + debugf0("%s()\n", __func__); + + /* remove any main attributes for this device */ + edac_device_remove_main_sysfs_attributes(edac_dev); + + /* remove the device sym link */ + sysfs_remove_link(&edac_dev->kobj, EDAC_DEVICE_SYMLINK); + + /* walk the instance/block kobject tree, deconstructing it */ + edac_device_delete_instances(edac_dev); +} diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c index 804875d..4471be3 100644 --- a/drivers/edac/edac_mc.c +++ b/drivers/edac/edac_mc.c @@ -27,1200 +27,20 @@ #include <linux/list.h> #include <linux/sysdev.h> #include <linux/ctype.h> -#include <linux/kthread.h> -#include <linux/freezer.h> +#include <linux/edac.h> #include <asm/uaccess.h> #include <asm/page.h> #include <asm/edac.h> -#include "edac_mc.h" - -#define EDAC_MC_VERSION "Ver: 2.0.1 " __DATE__ - - -#ifdef CONFIG_EDAC_DEBUG -/* Values of 0 to 4 will generate output */ -int edac_debug_level = 1; -EXPORT_SYMBOL_GPL(edac_debug_level); -#endif - -/* EDAC Controls, setable by module parameter, and sysfs */ -static int log_ue = 1; -static int log_ce = 1; -static int panic_on_ue; -static int poll_msec = 1000; +#include "edac_core.h" +#include "edac_module.h" /* lock to memory controller's control array */ -static DECLARE_MUTEX(mem_ctls_mutex); +static DEFINE_MUTEX(mem_ctls_mutex); static struct list_head mc_devices = LIST_HEAD_INIT(mc_devices); -static struct task_struct *edac_thread; - -#ifdef CONFIG_PCI -static int check_pci_parity = 0; /* default YES check PCI parity */ -static int panic_on_pci_parity; /* default no panic on PCI Parity */ -static atomic_t pci_parity_count = ATOMIC_INIT(0); - -static struct kobject edac_pci_kobj; /* /sys/devices/system/edac/pci */ -static struct completion edac_pci_kobj_complete; -#endif /* CONFIG_PCI */ - -/* START sysfs data and methods */ - - -static const char *mem_types[] = { - [MEM_EMPTY] = "Empty", - [MEM_RESERVED] = "Reserved", - [MEM_UNKNOWN] = "Unknown", - [MEM_FPM] = "FPM", - [MEM_EDO] = "EDO", - [MEM_BEDO] = "BEDO", - [MEM_SDR] = "Unbuffered-SDR", - [MEM_RDR] = "Registered-SDR", - [MEM_DDR] = "Unbuffered-DDR", - [MEM_RDDR] = "Registered-DDR", - [MEM_RMBS] = "RMBS" -}; - -static const char *dev_types[] = { - [DEV_UNKNOWN] = "Unknown", - [DEV_X1] = "x1", - [DEV_X2] = "x2", - [DEV_X4] = "x4", - [DEV_X8] = "x8", - [DEV_X16] = "x16", - [DEV_X32] = "x32", - [DEV_X64] = "x64" -}; - -static const char *edac_caps[] = { - [EDAC_UNKNOWN] = "Unknown", - [EDAC_NONE] = "None", - [EDAC_RESERVED] = "Reserved", - [EDAC_PARITY] = "PARITY", - [EDAC_EC] = "EC", - [EDAC_SECDED] = "SECDED", - [EDAC_S2ECD2ED] = "S2ECD2ED", - [EDAC_S4ECD4ED] = "S4ECD4ED", - [EDAC_S8ECD8ED] = "S8ECD8ED", - [EDAC_S16ECD16ED] = "S16ECD16ED" -}; - -/* sysfs object: /sys/devices/system/edac */ -static struct sysdev_class edac_class = { - set_kset_name("edac"), -}; - -/* sysfs object: - * /sys/devices/system/edac/mc - */ -static struct kobject edac_memctrl_kobj; - -/* We use these to wait for the reference counts on edac_memctrl_kobj and - * edac_pci_kobj to reach 0. - */ -static struct completion edac_memctrl_kobj_complete; - -/* - * /sys/devices/system/edac/mc; - * data structures and methods - */ -static ssize_t memctrl_int_show(void *ptr, char *buffer) -{ - int *value = (int*) ptr; - return sprintf(buffer, "%u\n", *value); -} - -static ssize_t memctrl_int_store(void *ptr, const char *buffer, size_t count) -{ - int *value = (int*) ptr; - - if (isdigit(*buffer)) - *value = simple_strtoul(buffer, NULL, 0); - - return count; -} - -struct memctrl_dev_attribute { - struct attribute attr; - void *value; - ssize_t (*show)(void *,char *); - ssize_t (*store)(void *, const char *, size_t); -}; - -/* Set of show/store abstract level functions for memory control object */ -static ssize_t memctrl_dev_show(struct kobject *kobj, - struct attribute *attr, char *buffer) -{ - struct memctrl_dev_attribute *memctrl_dev; - memctrl_dev = (struct memctrl_dev_attribute*)attr; - - if (memctrl_dev->show) - return memctrl_dev->show(memctrl_dev->value, buffer); - - return -EIO; -} - -static ssize_t memctrl_dev_store(struct kobject *kobj, struct attribute *attr, - const char *buffer, size_t count) -{ - struct memctrl_dev_attribute *memctrl_dev; - memctrl_dev = (struct memctrl_dev_attribute*)attr; - - if (memctrl_dev->store) - return memctrl_dev->store(memctrl_dev->value, buffer, count); - - return -EIO; -} - -static struct sysfs_ops memctrlfs_ops = { - .show = memctrl_dev_show, - .store = memctrl_dev_store -}; - -#define MEMCTRL_ATTR(_name,_mode,_show,_store) \ -struct memctrl_dev_attribute attr_##_name = { \ - .attr = {.name = __stringify(_name), .mode = _mode }, \ - .value = &_name, \ - .show = _show, \ - .store = _store, \ -}; - -#define MEMCTRL_STRING_ATTR(_name,_data,_mode,_show,_store) \ -struct memctrl_dev_attribute attr_##_name = { \ - .attr = {.name = __stringify(_name), .mode = _mode }, \ - .value = _data, \ - .show = _show, \ - .store = _store, \ -}; - -/* csrow<id> control files */ -MEMCTRL_ATTR(panic_on_ue,S_IRUGO|S_IWUSR,memctrl_int_show,memctrl_int_store); -MEMCTRL_ATTR(log_ue,S_IRUGO|S_IWUSR,memctrl_int_show,memctrl_int_store); -MEMCTRL_ATTR(log_ce,S_IRUGO|S_IWUSR,memctrl_int_show,memctrl_int_store); -MEMCTRL_ATTR(poll_msec,S_IRUGO|S_IWUSR,memctrl_int_show,memctrl_int_store); - -/* Base Attributes of the memory ECC object */ -static struct memctrl_dev_attribute *memctrl_attr[] = { - &attr_panic_on_ue, - &attr_log_ue, - &attr_log_ce, - &attr_poll_msec, - NULL, -}; - -/* Main MC kobject release() function */ -static void edac_memctrl_master_release(struct kobject *kobj) -{ - debugf1("%s()\n", __func__); - complete(&edac_memctrl_kobj_complete); -} - -static struct kobj_type ktype_memctrl = { - .release = edac_memctrl_master_release, - .sysfs_ops = &memctrlfs_ops, - .default_attrs = (struct attribute **) memctrl_attr, -}; - -/* Initialize the main sysfs entries for edac: - * /sys/devices/system/edac - * - * and children - * - * Return: 0 SUCCESS - * !0 FAILURE - */ -static int edac_sysfs_memctrl_setup(void) -{ - int err = 0; - - debugf1("%s()\n", __func__); - - /* create the /sys/devices/system/edac directory */ - err = sysdev_class_register(&edac_class); - - if (err) { - debugf1("%s() error=%d\n", __func__, err); - return err; - } - - /* Init the MC's kobject */ - memset(&edac_memctrl_kobj, 0, sizeof (edac_memctrl_kobj)); - edac_memctrl_kobj.parent = &edac_class.kset.kobj; - edac_memctrl_kobj.ktype = &ktype_memctrl; - - /* generate sysfs "..../edac/mc" */ - err = kobject_set_name(&edac_memctrl_kobj,"mc"); - - if (err) - goto fail; - - /* FIXME: maybe new sysdev_create_subdir() */ - err = kobject_register(&edac_memctrl_kobj); - - if (err) { - debugf1("Failed to register '.../edac/mc'\n"); - goto fail; - } - - debugf1("Registered '.../edac/mc' kobject\n"); - - return 0; - -fail: - sysdev_class_unregister(&edac_class); - return err; -} - -/* - * MC teardown: - * the '..../edac/mc' kobject followed by '..../edac' itself - */ -static void edac_sysfs_memctrl_teardown(void) -{ - debugf0("MC: " __FILE__ ": %s()\n", __func__); - - /* Unregister the MC's kobject and wait for reference count to reach - * 0. - */ - init_completion(&edac_memctrl_kobj_complete); - kobject_unregister(&edac_memctrl_kobj); - wait_for_completion(&edac_memctrl_kobj_complete); - - /* Unregister the 'edac' object */ - sysdev_class_unregister(&edac_class); -} - -#ifdef CONFIG_PCI -static ssize_t edac_pci_int_show(void *ptr, char *buffer) -{ - int *value = ptr; - return sprintf(buffer,"%d\n",*value); -} - -static ssize_t edac_pci_int_store(void *ptr, const char *buffer, size_t count) -{ - int *value = ptr; - - if (isdigit(*buffer)) - *value = simple_strtoul(buffer,NULL,0); - - return count; -} - -struct edac_pci_dev_attribute { - struct attribute attr; - void *value; - ssize_t (*show)(void *,char *); - ssize_t (*store)(void *, const char *,size_t); -}; - -/* Set of show/store abstract level functions for PCI Parity object */ -static ssize_t edac_pci_dev_show(struct kobject *kobj, struct attribute *attr, - char *buffer) -{ - struct edac_pci_dev_attribute *edac_pci_dev; - edac_pci_dev= (struct edac_pci_dev_attribute*)attr; - - if (edac_pci_dev->show) - return edac_pci_dev->show(edac_pci_dev->value, buffer); - return -EIO; -} - -static ssize_t edac_pci_dev_store(struct kobject *kobj, - struct attribute *attr, const char *buffer, size_t count) -{ - struct edac_pci_dev_attribute *edac_pci_dev; - edac_pci_dev= (struct edac_pci_dev_attribute*)attr; - - if (edac_pci_dev->show) - return edac_pci_dev->store(edac_pci_dev->value, buffer, count); - return -EIO; -} - -static struct sysfs_ops edac_pci_sysfs_ops = { - .show = edac_pci_dev_show, - .store = edac_pci_dev_store -}; - -#define EDAC_PCI_ATTR(_name,_mode,_show,_store) \ -struct edac_pci_dev_attribute edac_pci_attr_##_name = { \ - .attr = {.name = __stringify(_name), .mode = _mode }, \ - .value = &_name, \ - .show = _show, \ - .store = _store, \ -}; - -#define EDAC_PCI_STRING_ATTR(_name,_data,_mode,_show,_store) \ -struct edac_pci_dev_attribute edac_pci_attr_##_name = { \ - .attr = {.name = __stringify(_name), .mode = _mode }, \ - .value = _data, \ - .show = _show, \ - .store = _store, \ -}; - -/* PCI Parity control files */ -EDAC_PCI_ATTR(check_pci_parity, S_IRUGO|S_IWUSR, edac_pci_int_show, - edac_pci_int_store); -EDAC_PCI_ATTR(panic_on_pci_parity, S_IRUGO|S_IWUSR, edac_pci_int_show, - edac_pci_int_store); -EDAC_PCI_ATTR(pci_parity_count, S_IRUGO, edac_pci_int_show, NULL); - -/* Base Attributes of the memory ECC object */ -static struct edac_pci_dev_attribute *edac_pci_attr[] = { - &edac_pci_attr_check_pci_parity, - &edac_pci_attr_panic_on_pci_parity, - &edac_pci_attr_pci_parity_count, - NULL, -}; - -/* No memory to release */ -static void edac_pci_release(struct kobject *kobj) -{ - debugf1("%s()\n", __func__); - complete(&edac_pci_kobj_complete); -} - -static struct kobj_type ktype_edac_pci = { - .release = edac_pci_release, - .sysfs_ops = &edac_pci_sysfs_ops, - .default_attrs = (struct attribute **) edac_pci_attr, -}; - -/** - * edac_sysfs_pci_setup() - * - */ -static int edac_sysfs_pci_setup(void) -{ - int err; - - debugf1("%s()\n", __func__); - - memset(&edac_pci_kobj, 0, sizeof(edac_pci_kobj)); - edac_pci_kobj.parent = &edac_class.kset.kobj; - edac_pci_kobj.ktype = &ktype_edac_pci; - err = kobject_set_name(&edac_pci_kobj, "pci"); - - if (!err) { - /* Instanstiate the csrow object */ - /* FIXME: maybe new sysdev_create_subdir() */ - err = kobject_register(&edac_pci_kobj); - - if (err) - debugf1("Failed to register '.../edac/pci'\n"); - else - debugf1("Registered '.../edac/pci' kobject\n"); - } - - return err; -} - -static void edac_sysfs_pci_teardown(void) -{ - debugf0("%s()\n", __func__); - init_completion(&edac_pci_kobj_complete); - kobject_unregister(&edac_pci_kobj); - wait_for_completion(&edac_pci_kobj_complete); -} - - -static u16 get_pci_parity_status(struct pci_dev *dev, int secondary) -{ - int where; - u16 status; - - where = secondary ? PCI_SEC_STATUS : PCI_STATUS; - pci_read_config_word(dev, where, &status); - - /* If we get back 0xFFFF then we must suspect that the card has been - * pulled but the Linux PCI layer has not yet finished cleaning up. - * We don't want to report on such devices - */ - - if (status == 0xFFFF) { - u32 sanity; - - pci_read_config_dword(dev, 0, &sanity); - - if (sanity == 0xFFFFFFFF) - return 0; - } - - status &= PCI_STATUS_DETECTED_PARITY | PCI_STATUS_SIG_SYSTEM_ERROR | - PCI_STATUS_PARITY; - - if (status) - /* reset only the bits we are interested in */ - pci_write_config_word(dev, where, status); - - return status; -} - -typedef void (*pci_parity_check_fn_t) (struct pci_dev *dev); - -/* Clear any PCI parity errors logged by this device. */ -static void edac_pci_dev_parity_clear(struct pci_dev *dev) -{ - u8 header_type; - - get_pci_parity_status(dev, 0); - - /* read the device TYPE, looking for bridges */ - pci_read_config_byte(dev, PCI_HEADER_TYPE, &header_type); - - if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) - get_pci_parity_status(dev, 1); -} - -/* - * PCI Parity polling - * - */ -static void edac_pci_dev_parity_test(struct pci_dev *dev) -{ - u16 status; - u8 header_type; - - /* read the STATUS register on this device - */ - status = get_pci_parity_status(dev, 0); - - debugf2("PCI STATUS= 0x%04x %s\n", status, dev->dev.bus_id ); - - /* check the status reg for errors */ - if (status) { - if (status & (PCI_STATUS_SIG_SYSTEM_ERROR)) - edac_printk(KERN_CRIT, EDAC_PCI, - "Signaled System Error on %s\n", - pci_name(dev)); - - if (status & (PCI_STATUS_PARITY)) { - edac_printk(KERN_CRIT, EDAC_PCI, - "Master Data Parity Error on %s\n", - pci_name(dev)); - - atomic_inc(&pci_parity_count); - } - - if (status & (PCI_STATUS_DETECTED_PARITY)) { - edac_printk(KERN_CRIT, EDAC_PCI, - "Detected Parity Error on %s\n", - pci_name(dev)); - - atomic_inc(&pci_parity_count); - } - } - - /* read the device TYPE, looking for bridges */ - pci_read_config_byte(dev, PCI_HEADER_TYPE, &header_type); - - debugf2("PCI HEADER TYPE= 0x%02x %s\n", header_type, dev->dev.bus_id ); - - if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { - /* On bridges, need to examine secondary status register */ - status = get_pci_parity_status(dev, 1); - - debugf2("PCI SEC_STATUS= 0x%04x %s\n", - status, dev->dev.bus_id ); - - /* check the secondary status reg for errors */ - if (status) { - if (status & (PCI_STATUS_SIG_SYSTEM_ERROR)) - edac_printk(KERN_CRIT, EDAC_PCI, "Bridge " - "Signaled System Error on %s\n", - pci_name(dev)); - - if (status & (PCI_STATUS_PARITY)) { - edac_printk(KERN_CRIT, EDAC_PCI, "Bridge " - "Master Data Parity Error on " - "%s\n", pci_name(dev)); - - atomic_inc(&pci_parity_count); - } - - if (status & (PCI_STATUS_DETECTED_PARITY)) { - edac_printk(KERN_CRIT, EDAC_PCI, "Bridge " - "Detected Parity Error on %s\n", - pci_name(dev)); - - atomic_inc(&pci_parity_count); - } - } - } -} - -/* - * pci_dev parity list iterator - * Scan the PCI device list for one iteration, looking for SERRORs - * Master Parity ERRORS or Parity ERRORs on primary or secondary devices - */ -static inline void edac_pci_dev_parity_iterator(pci_parity_check_fn_t fn) -{ - struct pci_dev *dev = NULL; - - /* request for kernel access to the next PCI device, if any, - * and while we are looking at it have its reference count - * bumped until we are done with it - */ - while((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { - fn(dev); - } -} - -static void do_pci_parity_check(void) -{ - unsigned long flags; - int before_count; - - debugf3("%s()\n", __func__); - - if (!check_pci_parity) - return; - - before_count = atomic_read(&pci_parity_count); - - /* scan all PCI devices looking for a Parity Error on devices and - * bridges - */ - local_irq_save(flags); - edac_pci_dev_parity_iterator(edac_pci_dev_parity_test); - local_irq_restore(flags); - - /* Only if operator has selected panic on PCI Error */ - if (panic_on_pci_parity) { - /* If the count is different 'after' from 'before' */ - if (before_count != atomic_read(&pci_parity_count)) - panic("EDAC: PCI Parity Error"); - } -} - -static inline void clear_pci_parity_errors(void) -{ - /* Clear any PCI bus parity errors that devices initially have logged - * in their registers. - */ - edac_pci_dev_parity_iterator(edac_pci_dev_parity_clear); -} - -#else /* CONFIG_PCI */ - -/* pre-process these away */ -#define do_pci_parity_check() -#define clear_pci_parity_errors() -#define edac_sysfs_pci_teardown() -#define edac_sysfs_pci_setup() (0) - -#endif /* CONFIG_PCI */ - -/* EDAC sysfs CSROW data structures and methods - */ - -/* Set of more default csrow<id> attribute show/store functions */ -static ssize_t csrow_ue_count_show(struct csrow_info *csrow, char *data, int private) -{ - return sprintf(data,"%u\n", csrow->ue_count); -} - -static ssize_t csrow_ce_count_show(struct csrow_info *csrow, char *data, int private) -{ - return sprintf(data,"%u\n", csrow->ce_count); -} - -static ssize_t csrow_size_show(struct csrow_info *csrow, char *data, int private) -{ - return sprintf(data,"%u\n", PAGES_TO_MiB(csrow->nr_pages)); -} - -static ssize_t csrow_mem_type_show(struct csrow_info *csrow, char *data, int private) -{ - return sprintf(data,"%s\n", mem_types[csrow->mtype]); -} - -static ssize_t csrow_dev_type_show(struct csrow_info *csrow, char *data, int private) -{ - return sprintf(data,"%s\n", dev_types[csrow->dtype]); -} - -static ssize_t csrow_edac_mode_show(struct csrow_info *csrow, char *data, int private) -{ - return sprintf(data,"%s\n", edac_caps[csrow->edac_mode]); -} - -/* show/store functions for DIMM Label attributes */ -static ssize_t channel_dimm_label_show(struct csrow_info *csrow, - char *data, int channel) -{ - return snprintf(data, EDAC_MC_LABEL_LEN,"%s", - csrow->channels[channel].label); -} - -static ssize_t channel_dimm_label_store(struct csrow_info *csrow, - const char *data, - size_t count, - int channel) -{ - ssize_t max_size = 0; - - max_size = min((ssize_t)count,(ssize_t)EDAC_MC_LABEL_LEN-1); - strncpy(csrow->channels[channel].label, data, max_size); - csrow->channels[channel].label[max_size] = '\0'; - - return max_size; -} - -/* show function for dynamic chX_ce_count attribute */ -static ssize_t channel_ce_count_show(struct csrow_info *csrow, - char *data, - int channel) -{ - return sprintf(data, "%u\n", csrow->channels[channel].ce_count); -} - -/* csrow specific attribute structure */ -struct csrowdev_attribute { - struct attribute attr; - ssize_t (*show)(struct csrow_info *,char *,int); - ssize_t (*store)(struct csrow_info *, const char *,size_t,int); - int private; -}; - -#define to_csrow(k) container_of(k, struct csrow_info, kobj) -#define to_csrowdev_attr(a) container_of(a, struct csrowdev_attribute, attr) - -/* Set of show/store higher level functions for default csrow attributes */ -static ssize_t csrowdev_show(struct kobject *kobj, - struct attribute *attr, - char *buffer) -{ - struct csrow_info *csrow = to_csrow(kobj); - struct csrowdev_attribute *csrowdev_attr = to_csrowdev_attr(attr); - - if (csrowdev_attr->show) - return csrowdev_attr->show(csrow, - buffer, - csrowdev_attr->private); - return -EIO; -} - -static ssize_t csrowdev_store(struct kobject *kobj, struct attribute *attr, - const char *buffer, size_t count) -{ - struct csrow_info *csrow = to_csrow(kobj); - struct csrowdev_attribute * csrowdev_attr = to_csrowdev_attr(attr); - - if (csrowdev_attr->store) - return csrowdev_attr->store(csrow, - buffer, - count, - csrowdev_attr->private); - return -EIO; -} - -static struct sysfs_ops csrowfs_ops = { - .show = csrowdev_show, - .store = csrowdev_store -}; - -#define CSROWDEV_ATTR(_name,_mode,_show,_store,_private) \ -struct csrowdev_attribute attr_##_name = { \ - .attr = {.name = __stringify(_name), .mode = _mode }, \ - .show = _show, \ - .store = _store, \ - .private = _private, \ -}; - -/* default cwrow<id>/attribute files */ -CSROWDEV_ATTR(size_mb,S_IRUGO,csrow_size_show,NULL,0); -CSROWDEV_ATTR(dev_type,S_IRUGO,csrow_dev_type_show,NULL,0); -CSROWDEV_ATTR(mem_type,S_IRUGO,csrow_mem_type_show,NULL,0); -CSROWDEV_ATTR(edac_mode,S_IRUGO,csrow_edac_mode_show,NULL,0); -CSROWDEV_ATTR(ue_count,S_IRUGO,csrow_ue_count_show,NULL,0); -CSROWDEV_ATTR(ce_count,S_IRUGO,csrow_ce_count_show,NULL,0); - -/* default attributes of the CSROW<id> object */ -static struct csrowdev_attribute *default_csrow_attr[] = { - &attr_dev_type, - &attr_mem_type, - &attr_edac_mode, - &attr_size_mb, - &attr_ue_count, - &attr_ce_count, - NULL, -}; - - -/* possible dynamic channel DIMM Label attribute files */ -CSROWDEV_ATTR(ch0_dimm_label,S_IRUGO|S_IWUSR, - channel_dimm_label_show, - channel_dimm_label_store, - 0 ); -CSROWDEV_ATTR(ch1_dimm_label,S_IRUGO|S_IWUSR, - channel_dimm_label_show, - channel_dimm_label_store, - 1 ); -CSROWDEV_ATTR(ch2_dimm_label,S_IRUGO|S_IWUSR, - channel_dimm_label_show, - channel_dimm_label_store, - 2 ); -CSROWDEV_ATTR(ch3_dimm_label,S_IRUGO|S_IWUSR, - channel_dimm_label_show, - channel_dimm_label_store, - 3 ); -CSROWDEV_ATTR(ch4_dimm_label,S_IRUGO|S_IWUSR, - channel_dimm_label_show, - channel_dimm_label_store, - 4 ); -CSROWDEV_ATTR(ch5_dimm_label,S_IRUGO|S_IWUSR, - channel_dimm_label_show, - channel_dimm_label_store, - 5 ); - -/* Total possible dynamic DIMM Label attribute file table */ -static struct csrowdev_attribute *dynamic_csrow_dimm_attr[] = { - &attr_ch0_dimm_label, - &attr_ch1_dimm_label, - &attr_ch2_dimm_label, - &attr_ch3_dimm_label, - &attr_ch4_dimm_label, - &attr_ch5_dimm_label -}; - -/* possible dynamic channel ce_count attribute files */ -CSROWDEV_ATTR(ch0_ce_count,S_IRUGO|S_IWUSR, - channel_ce_count_show, - NULL, - 0 ); -CSROWDEV_ATTR(ch1_ce_count,S_IRUGO|S_IWUSR, - channel_ce_count_show, - NULL, - 1 ); -CSROWDEV_ATTR(ch2_ce_count,S_IRUGO|S_IWUSR, - channel_ce_count_show, - NULL, - 2 ); -CSROWDEV_ATTR(ch3_ce_count,S_IRUGO|S_IWUSR, - channel_ce_count_show, - NULL, - 3 ); -CSROWDEV_ATTR(ch4_ce_count,S_IRUGO|S_IWUSR, - channel_ce_count_show, - NULL, - 4 ); -CSROWDEV_ATTR(ch5_ce_count,S_IRUGO|S_IWUSR, - channel_ce_count_show, - NULL, - 5 ); - -/* Total possible dynamic ce_count attribute file table */ -static struct csrowdev_attribute *dynamic_csrow_ce_count_attr[] = { - &attr_ch0_ce_count, - &attr_ch1_ce_count, - &attr_ch2_ce_count, - &attr_ch3_ce_count, - &attr_ch4_ce_count, - &attr_ch5_ce_count -}; - - -#define EDAC_NR_CHANNELS 6 - -/* Create dynamic CHANNEL files, indexed by 'chan', under specifed CSROW */ -static int edac_create_channel_files(struct kobject *kobj, int chan) -{ - int err=-ENODEV; - - if (chan >= EDAC_NR_CHANNELS) - return err; - - /* create the DIMM label attribute file */ - err = sysfs_create_file(kobj, - (struct attribute *) dynamic_csrow_dimm_attr[chan]); - - if (!err) { - /* create the CE Count attribute file */ - err = sysfs_create_file(kobj, - (struct attribute *) dynamic_csrow_ce_count_attr[chan]); - } else { - debugf1("%s() dimm labels and ce_count files created", __func__); - } - - return err; -} - -/* No memory to release for this kobj */ -static void edac_csrow_instance_release(struct kobject *kobj) -{ - struct csrow_info *cs; - - cs = container_of(kobj, struct csrow_info, kobj); - complete(&cs->kobj_complete); -} - -/* the kobj_type instance for a CSROW */ -static struct kobj_type ktype_csrow = { - .release = edac_csrow_instance_release, - .sysfs_ops = &csrowfs_ops, - .default_attrs = (struct attribute **) default_csrow_attr, -}; - -/* Create a CSROW object under specifed edac_mc_device */ -static int edac_create_csrow_object( - struct kobject *edac_mci_kobj, - struct csrow_info *csrow, - int index) -{ - int err = 0; - int chan; - - memset(&csrow->kobj, 0, sizeof(csrow->kobj)); - - /* generate ..../edac/mc/mc<id>/csrow<index> */ - - csrow->kobj.parent = edac_mci_kobj; - csrow->kobj.ktype = &ktype_csrow; - - /* name this instance of csrow<id> */ - err = kobject_set_name(&csrow->kobj,"csrow%d",index); - if (err) - goto error_exit; - - /* Instanstiate the csrow object */ - err = kobject_register(&csrow->kobj); - if (!err) { - /* Create the dyanmic attribute files on this csrow, - * namely, the DIMM labels and the channel ce_count - */ - for (chan = 0; chan < csrow->nr_channels; chan++) { - err = edac_create_channel_files(&csrow->kobj,chan); - if (err) - break; - } - } - -error_exit: - return err; -} - -/* default sysfs methods and data structures for the main MCI kobject */ - -static ssize_t mci_reset_counters_store(struct mem_ctl_info *mci, - const char *data, size_t count) -{ - int row, chan; - - mci->ue_noinfo_count = 0; - mci->ce_noinfo_count = 0; - mci->ue_count = 0; - mci->ce_count = 0; - - for (row = 0; row < mci->nr_csrows; row++) { - struct csrow_info *ri = &mci->csrows[row]; - - ri->ue_count = 0; - ri->ce_count = 0; - - for (chan = 0; chan < ri->nr_channels; chan++) - ri->channels[chan].ce_count = 0; - } - - mci->start_time = jiffies; - return count; -} - -/* memory scrubbing */ -static ssize_t mci_sdram_scrub_rate_store(struct mem_ctl_info *mci, - const char *data, size_t count) -{ - u32 bandwidth = -1; - - if (mci->set_sdram_scrub_rate) { - - memctrl_int_store(&bandwidth, data, count); - - if (!(*mci->set_sdram_scrub_rate)(mci, &bandwidth)) { - edac_printk(KERN_DEBUG, EDAC_MC, - "Scrub rate set successfully, applied: %d\n", - bandwidth); - } else { - /* FIXME: error codes maybe? */ - edac_printk(KERN_DEBUG, EDAC_MC, - "Scrub rate set FAILED, could not apply: %d\n", - bandwidth); - } - } else { - /* FIXME: produce "not implemented" ERROR for user-side. */ - edac_printk(KERN_WARNING, EDAC_MC, - "Memory scrubbing 'set'control is not implemented!\n"); - } - return count; -} - -static ssize_t mci_sdram_scrub_rate_show(struct mem_ctl_info *mci, char *data) -{ - u32 bandwidth = -1; - - if (mci->get_sdram_scrub_rate) { - if (!(*mci->get_sdram_scrub_rate)(mci, &bandwidth)) { - edac_printk(KERN_DEBUG, EDAC_MC, - "Scrub rate successfully, fetched: %d\n", - bandwidth); - } else { - /* FIXME: error codes maybe? */ - edac_printk(KERN_DEBUG, EDAC_MC, - "Scrub rate fetch FAILED, got: %d\n", - bandwidth); - } - } else { - /* FIXME: produce "not implemented" ERROR for user-side. */ - edac_printk(KERN_WARNING, EDAC_MC, - "Memory scrubbing 'get' control is not implemented!\n"); - } - return sprintf(data, "%d\n", bandwidth); -} - -/* default attribute files for the MCI object */ -static ssize_t mci_ue_count_show(struct mem_ctl_info *mci, char *data) -{ - return sprintf(data,"%d\n", mci->ue_count); -} - -static ssize_t mci_ce_count_show(struct mem_ctl_info *mci, char *data) -{ - return sprintf(data,"%d\n", mci->ce_count); -} - -static ssize_t mci_ce_noinfo_show(struct mem_ctl_info *mci, char *data) -{ - return sprintf(data,"%d\n", mci->ce_noinfo_count); -} - -static ssize_t mci_ue_noinfo_show(struct mem_ctl_info *mci, char *data) -{ - return sprintf(data,"%d\n", mci->ue_noinfo_count); -} - -static ssize_t mci_seconds_show(struct mem_ctl_info *mci, char *data) -{ - return sprintf(data,"%ld\n", (jiffies - mci->start_time) / HZ); -} - -static ssize_t mci_ctl_name_show(struct mem_ctl_info *mci, char *data) -{ - return sprintf(data,"%s\n", mci->ctl_name); -} - -static ssize_t mci_size_mb_show(struct mem_ctl_info *mci, char *data) -{ - int total_pages, csrow_idx; - - for (total_pages = csrow_idx = 0; csrow_idx < mci->nr_csrows; - csrow_idx++) { - struct csrow_info *csrow = &mci->csrows[csrow_idx]; - - if (!csrow->nr_pages) - continue; - - total_pages += csrow->nr_pages; - } - - return sprintf(data,"%u\n", PAGES_TO_MiB(total_pages)); -} - -struct mcidev_attribute { - struct attribute attr; - ssize_t (*show)(struct mem_ctl_info *,char *); - ssize_t (*store)(struct mem_ctl_info *, const char *,size_t); -}; - -#define to_mci(k) container_of(k, struct mem_ctl_info, edac_mci_kobj) -#define to_mcidev_attr(a) container_of(a, struct mcidev_attribute, attr) - -/* MCI show/store functions for top most object */ -static ssize_t mcidev_show(struct kobject *kobj, struct attribute *attr, - char *buffer) -{ - struct mem_ctl_info *mem_ctl_info = to_mci(kobj); - struct mcidev_attribute * mcidev_attr = to_mcidev_attr(attr); - - if (mcidev_attr->show) - return mcidev_attr->show(mem_ctl_info, buffer); - - return -EIO; -} - -static ssize_t mcidev_store(struct kobject *kobj, struct attribute *attr, - const char *buffer, size_t count) -{ - struct mem_ctl_info *mem_ctl_info = to_mci(kobj); - struct mcidev_attribute * mcidev_attr = to_mcidev_attr(attr); - - if (mcidev_attr->store) - return mcidev_attr->store(mem_ctl_info, buffer, count); - - return -EIO; -} - -static struct sysfs_ops mci_ops = { - .show = mcidev_show, - .store = mcidev_store -}; - -#define MCIDEV_ATTR(_name,_mode,_show,_store) \ -struct mcidev_attribute mci_attr_##_name = { \ - .attr = {.name = __stringify(_name), .mode = _mode }, \ - .show = _show, \ - .store = _store, \ -}; - -/* default Control file */ -MCIDEV_ATTR(reset_counters,S_IWUSR,NULL,mci_reset_counters_store); - -/* default Attribute files */ -MCIDEV_ATTR(mc_name,S_IRUGO,mci_ctl_name_show,NULL); -MCIDEV_ATTR(size_mb,S_IRUGO,mci_size_mb_show,NULL); -MCIDEV_ATTR(seconds_since_reset,S_IRUGO,mci_seconds_show,NULL); -MCIDEV_ATTR(ue_noinfo_count,S_IRUGO,mci_ue_noinfo_show,NULL); -MCIDEV_ATTR(ce_noinfo_count,S_IRUGO,mci_ce_noinfo_show,NULL); -MCIDEV_ATTR(ue_count,S_IRUGO,mci_ue_count_show,NULL); -MCIDEV_ATTR(ce_count,S_IRUGO,mci_ce_count_show,NULL); - -/* memory scrubber attribute file */ -MCIDEV_ATTR(sdram_scrub_rate,S_IRUGO|S_IWUSR,mci_sdram_scrub_rate_show,mci_sdram_scrub_rate_store); - -static struct mcidev_attribute *mci_attr[] = { - &mci_attr_reset_counters, - &mci_attr_mc_name, - &mci_attr_size_mb, - &mci_attr_seconds_since_reset, - &mci_attr_ue_noinfo_count, - &mci_attr_ce_noinfo_count, - &mci_attr_ue_count, - &mci_attr_ce_count, - &mci_attr_sdram_scrub_rate, - NULL -}; - -/* - * Release of a MC controlling instance - */ -static void edac_mci_instance_release(struct kobject *kobj) -{ - struct mem_ctl_info *mci; - - mci = to_mci(kobj); - debugf0("%s() idx=%d\n", __func__, mci->mc_idx); - complete(&mci->kobj_complete); -} - -static struct kobj_type ktype_mci = { - .release = edac_mci_instance_release, - .sysfs_ops = &mci_ops, - .default_attrs = (struct attribute **) mci_attr, -}; - - -#define EDAC_DEVICE_SYMLINK "device" - -/* - * Create a new Memory Controller kobject instance, - * mc<id> under the 'mc' directory - * - * Return: - * 0 Success - * !0 Failure - */ -static int edac_create_sysfs_mci_device(struct mem_ctl_info *mci) -{ - int i; - int err; - struct csrow_info *csrow; - struct kobject *edac_mci_kobj=&mci->edac_mci_kobj; - - debugf0("%s() idx=%d\n", __func__, mci->mc_idx); - memset(edac_mci_kobj, 0, sizeof(*edac_mci_kobj)); - - /* set the name of the mc<id> object */ - err = kobject_set_name(edac_mci_kobj,"mc%d",mci->mc_idx); - if (err) - return err; - - /* link to our parent the '..../edac/mc' object */ - edac_mci_kobj->parent = &edac_memctrl_kobj; - edac_mci_kobj->ktype = &ktype_mci; - - /* register the mc<id> kobject */ - err = kobject_register(edac_mci_kobj); - if (err) - return err; - - /* create a symlink for the device */ - err = sysfs_create_link(edac_mci_kobj, &mci->dev->kobj, - EDAC_DEVICE_SYMLINK); - if (err) - goto fail0; - - /* Make directories for each CSROW object - * under the mc<id> kobject - */ - for (i = 0; i < mci->nr_csrows; i++) { - csrow = &mci->csrows[i]; - - /* Only expose populated CSROWs */ - if (csrow->nr_pages > 0) { - err = edac_create_csrow_object(edac_mci_kobj,csrow,i); - if (err) - goto fail1; - } - } - - return 0; - - /* CSROW error: backout what has already been registered, */ -fail1: - for ( i--; i >= 0; i--) { - if (csrow->nr_pages > 0) { - init_completion(&csrow->kobj_complete); - kobject_unregister(&mci->csrows[i].kobj); - wait_for_completion(&csrow->kobj_complete); - } - } - -fail0: - init_completion(&mci->kobj_complete); - kobject_unregister(edac_mci_kobj); - wait_for_completion(&mci->kobj_complete); - return err; -} - -/* - * remove a Memory Controller instance - */ -static void edac_remove_sysfs_mci_device(struct mem_ctl_info *mci) -{ - int i; - - debugf0("%s()\n", __func__); - - /* remove all csrow kobjects */ - for (i = 0; i < mci->nr_csrows; i++) { - if (mci->csrows[i].nr_pages > 0) { - init_completion(&mci->csrows[i].kobj_complete); - kobject_unregister(&mci->csrows[i].kobj); - wait_for_completion(&mci->csrows[i].kobj_complete); - } - } - - sysfs_remove_link(&mci->edac_mci_kobj, EDAC_DEVICE_SYMLINK); - init_completion(&mci->kobj_complete); - kobject_unregister(&mci->edac_mci_kobj); - wait_for_completion(&mci->kobj_complete); -} - -/* END OF sysfs data and methods */ - #ifdef CONFIG_EDAC_DEBUG -void edac_mc_dump_channel(struct channel_info *chan) +static void edac_mc_dump_channel(struct channel_info *chan) { debugf4("\tchannel = %p\n", chan); debugf4("\tchannel->chan_idx = %d\n", chan->chan_idx); @@ -1228,25 +48,21 @@ void edac_mc_dump_channel(struct channel_info *chan) debugf4("\tchannel->label = '%s'\n", chan->label); debugf4("\tchannel->csrow = %p\n\n", chan->csrow); } -EXPORT_SYMBOL_GPL(edac_mc_dump_channel); -void edac_mc_dump_csrow(struct csrow_info *csrow) +static void edac_mc_dump_csrow(struct csrow_info *csrow) { debugf4("\tcsrow = %p\n", csrow); debugf4("\tcsrow->csrow_idx = %d\n", csrow->csrow_idx); - debugf4("\tcsrow->first_page = 0x%lx\n", - csrow->first_page); + debugf4("\tcsrow->first_page = 0x%lx\n", csrow->first_page); debugf4("\tcsrow->last_page = 0x%lx\n", csrow->last_page); debugf4("\tcsrow->page_mask = 0x%lx\n", csrow->page_mask); debugf4("\tcsrow->nr_pages = 0x%x\n", csrow->nr_pages); - debugf4("\tcsrow->nr_channels = %d\n", - csrow->nr_channels); + debugf4("\tcsrow->nr_channels = %d\n", csrow->nr_channels); debugf4("\tcsrow->channels = %p\n", csrow->channels); debugf4("\tcsrow->mci = %p\n\n", csrow->mci); } -EXPORT_SYMBOL_GPL(edac_mc_dump_csrow); -void edac_mc_dump_mci(struct mem_ctl_info *mci) +static void edac_mc_dump_mci(struct mem_ctl_info *mci) { debugf3("\tmci = %p\n", mci); debugf3("\tmci->mtype_cap = %lx\n", mci->mtype_cap); @@ -1256,13 +72,11 @@ void edac_mc_dump_mci(struct mem_ctl_info *mci) debugf3("\tmci->nr_csrows = %d, csrows = %p\n", mci->nr_csrows, mci->csrows); debugf3("\tdev = %p\n", mci->dev); - debugf3("\tmod_name:ctl_name = %s:%s\n", - mci->mod_name, mci->ctl_name); + debugf3("\tmod_name:ctl_name = %s:%s\n", mci->mod_name, mci->ctl_name); debugf3("\tpvt_info = %p\n\n", mci->pvt_info); } -EXPORT_SYMBOL_GPL(edac_mc_dump_mci); -#endif /* CONFIG_EDAC_DEBUG */ +#endif /* CONFIG_EDAC_DEBUG */ /* 'ptr' points to a possibly unaligned item X such that sizeof(X) is 'size'. * Adjust 'ptr' so that its alignment is at least as stringent as what the @@ -1271,7 +85,7 @@ EXPORT_SYMBOL_GPL(edac_mc_dump_mci); * If 'size' is a constant, the compiler will optimize this whole function * down to either a no-op or the addition of a constant to the value of 'ptr'. */ -static inline char * align_ptr(void *ptr, unsigned size) +void *edac_align_ptr(void *ptr, unsigned size) { unsigned align, r; @@ -1288,14 +102,14 @@ static inline char * align_ptr(void *ptr, unsigned size) else if (size > sizeof(char)) align = sizeof(short); else - return (char *) ptr; + return (char *)ptr; r = size % align; if (r == 0) - return (char *) ptr; + return (char *)ptr; - return (char *) (((unsigned long) ptr) + align - r); + return (void *)(((unsigned long)ptr) + align - r); } /** @@ -1315,7 +129,7 @@ static inline char * align_ptr(void *ptr, unsigned size) * struct mem_ctl_info pointer */ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows, - unsigned nr_chans) + unsigned nr_chans, int edac_index) { struct mem_ctl_info *mci; struct csrow_info *csi, *csrow; @@ -1323,30 +137,32 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows, void *pvt; unsigned size; int row, chn; + int err; /* Figure out the offsets of the various items from the start of an mc * structure. We want the alignment of each item to be at least as * stringent as what the compiler would provide if we could simply * hardcode everything into a single struct. */ - mci = (struct mem_ctl_info *) 0; - csi = (struct csrow_info *)align_ptr(&mci[1], sizeof(*csi)); - chi = (struct channel_info *) - align_ptr(&csi[nr_csrows], sizeof(*chi)); - pvt = align_ptr(&chi[nr_chans * nr_csrows], sz_pvt); - size = ((unsigned long) pvt) + sz_pvt; - - if ((mci = kmalloc(size, GFP_KERNEL)) == NULL) + mci = (struct mem_ctl_info *)0; + csi = edac_align_ptr(&mci[1], sizeof(*csi)); + chi = edac_align_ptr(&csi[nr_csrows], sizeof(*chi)); + pvt = edac_align_ptr(&chi[nr_chans * nr_csrows], sz_pvt); + size = ((unsigned long)pvt) + sz_pvt; + + mci = kzalloc(size, GFP_KERNEL); + if (mci == NULL) return NULL; /* Adjust pointers so they point within the memory we just allocated * rather than an imaginary chunk of memory located at address 0. */ - csi = (struct csrow_info *) (((char *) mci) + ((unsigned long) csi)); - chi = (struct channel_info *) (((char *) mci) + ((unsigned long) chi)); - pvt = sz_pvt ? (((char *) mci) + ((unsigned long) pvt)) : NULL; + csi = (struct csrow_info *)(((char *)mci) + ((unsigned long)csi)); + chi = (struct channel_info *)(((char *)mci) + ((unsigned long)chi)); + pvt = sz_pvt ? (((char *)mci) + ((unsigned long)pvt)) : NULL; - memset(mci, 0, size); /* clear all fields */ + /* setup index and various internal pointers */ + mci->mc_idx = edac_index; mci->csrows = csi; mci->pvt_info = pvt; mci->nr_csrows = nr_csrows; @@ -1366,17 +182,35 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows, } } + mci->op_state = OP_ALLOC; + + /* + * Initialize the 'root' kobj for the edac_mc controller + */ + err = edac_mc_register_sysfs_main_kobj(mci); + if (err) { + kfree(mci); + return NULL; + } + + /* at this point, the root kobj is valid, and in order to + * 'free' the object, then the function: + * edac_mc_unregister_sysfs_main_kobj() must be called + * which will perform kobj unregistration and the actual free + * will occur during the kobject callback operation + */ return mci; } EXPORT_SYMBOL_GPL(edac_mc_alloc); /** - * edac_mc_free: Free a previously allocated 'mci' structure + * edac_mc_free + * 'Free' a previously allocated 'mci' structure * @mci: pointer to a struct mem_ctl_info structure */ void edac_mc_free(struct mem_ctl_info *mci) { - kfree(mci); + edac_mc_unregister_sysfs_main_kobj(mci); } EXPORT_SYMBOL_GPL(edac_mc_free); @@ -1397,18 +231,136 @@ static struct mem_ctl_info *find_mci_by_dev(struct device *dev) return NULL; } +/* + * handler for EDAC to check if NMI type handler has asserted interrupt + */ +static int edac_mc_assert_error_check_and_clear(void) +{ + int old_state; + + if (edac_op_state == EDAC_OPSTATE_POLL) + return 1; + + old_state = edac_err_assert; + edac_err_assert = 0; + + return old_state; +} + +/* + * edac_mc_workq_function + * performs the operation scheduled by a workq request + */ +static void edac_mc_workq_function(struct work_struct *work_req) +{ + struct delayed_work *d_work = (struct delayed_work *)work_req; + struct mem_ctl_info *mci = to_edac_mem_ctl_work(d_work); + + mutex_lock(&mem_ctls_mutex); + + /* if this control struct has movd to offline state, we are done */ + if (mci->op_state == OP_OFFLINE) { + mutex_unlock(&mem_ctls_mutex); + return; + } + + /* Only poll controllers that are running polled and have a check */ + if (edac_mc_assert_error_check_and_clear() && (mci->edac_check != NULL)) + mci->edac_check(mci); + + /* + * FIXME: temp place holder for PCI checks, + * goes away when we break out PCI + */ + edac_pci_do_parity_check(); + + mutex_unlock(&mem_ctls_mutex); + + /* Reschedule */ + queue_delayed_work(edac_workqueue, &mci->work, + msecs_to_jiffies(edac_mc_get_poll_msec())); +} + +/* + * edac_mc_workq_setup + * initialize a workq item for this mci + * passing in the new delay period in msec + * + * locking model: + * + * called with the mem_ctls_mutex held + */ +static void edac_mc_workq_setup(struct mem_ctl_info *mci, unsigned msec) +{ + debugf0("%s()\n", __func__); + + /* if this instance is not in the POLL state, then simply return */ + if (mci->op_state != OP_RUNNING_POLL) + return; + + INIT_DELAYED_WORK(&mci->work, edac_mc_workq_function); + queue_delayed_work(edac_workqueue, &mci->work, msecs_to_jiffies(msec)); +} + +/* + * edac_mc_workq_teardown + * stop the workq processing on this mci + * + * locking model: + * + * called WITHOUT lock held + */ +static void edac_mc_workq_teardown(struct mem_ctl_info *mci) +{ + int status; + + /* if not running POLL, leave now */ + if (mci->op_state == OP_RUNNING_POLL) { + status = cancel_delayed_work(&mci->work); + if (status == 0) { + debugf0("%s() not canceled, flush the queue\n", + __func__); + + /* workq instance might be running, wait for it */ + flush_workqueue(edac_workqueue); + } + } +} + +/* + * edac_reset_delay_period + */ +static void edac_reset_delay_period(struct mem_ctl_info *mci, unsigned long value) +{ + /* cancel the current workq request */ + edac_mc_workq_teardown(mci); + + /* lock the list of devices for the new setup */ + mutex_lock(&mem_ctls_mutex); + + /* restart the workq request, with new delay value */ + edac_mc_workq_setup(mci, value); + + mutex_unlock(&mem_ctls_mutex); +} + /* Return 0 on success, 1 on failure. * Before calling this function, caller must * assign a unique value to mci->mc_idx. + * + * locking model: + * + * called with the mem_ctls_mutex lock held */ -static int add_mc_to_global_list (struct mem_ctl_info *mci) +static int add_mc_to_global_list(struct mem_ctl_info *mci) { struct list_head *item, *insert_before; struct mem_ctl_info *p; insert_before = &mc_devices; - if (unlikely((p = find_mci_by_dev(mci->dev)) != NULL)) + p = find_mci_by_dev(mci->dev); + if (unlikely(p != NULL)) goto fail0; list_for_each(item, &mc_devices) { @@ -1424,18 +376,19 @@ static int add_mc_to_global_list (struct mem_ctl_info *mci) } list_add_tail_rcu(&mci->link, insert_before); + atomic_inc(&edac_handlers); return 0; fail0: edac_printk(KERN_WARNING, EDAC_MC, - "%s (%s) %s %s already assigned %d\n", p->dev->bus_id, - dev_name(p->dev), p->mod_name, p->ctl_name, p->mc_idx); + "%s (%s) %s %s already assigned %d\n", p->dev->bus_id, + dev_name(mci), p->mod_name, p->ctl_name, p->mc_idx); return 1; fail1: edac_printk(KERN_WARNING, EDAC_MC, - "bug in low-level driver: attempt to assign\n" - " duplicate mc_idx %d in %s()\n", p->mc_idx, __func__); + "bug in low-level driver: attempt to assign\n" + " duplicate mc_idx %d in %s()\n", p->mc_idx, __func__); return 1; } @@ -1450,6 +403,7 @@ static void complete_mc_list_del(struct rcu_head *head) static void del_mc_from_global_list(struct mem_ctl_info *mci) { + atomic_dec(&edac_handlers); list_del_rcu(&mci->link); init_completion(&mci->complete); call_rcu(&mci->rcu, complete_mc_list_del); @@ -1457,6 +411,34 @@ static void del_mc_from_global_list(struct mem_ctl_info *mci) } /** + * edac_mc_find: Search for a mem_ctl_info structure whose index is 'idx'. + * + * If found, return a pointer to the structure. + * Else return NULL. + * + * Caller must hold mem_ctls_mutex. + */ +struct mem_ctl_info *edac_mc_find(int idx) +{ + struct list_head *item; + struct mem_ctl_info *mci; + + list_for_each(item, &mc_devices) { + mci = list_entry(item, struct mem_ctl_info, link); + + if (mci->mc_idx >= idx) { + if (mci->mc_idx == idx) + return mci; + + break; + } + } + + return NULL; +} +EXPORT_SYMBOL(edac_mc_find); + +/** * edac_mc_add_mc: Insert the 'mci' structure into the mci global list and * create sysfs entries associated with mci structure * @mci: pointer to the mci structure to be added to the list @@ -1468,10 +450,10 @@ static void del_mc_from_global_list(struct mem_ctl_info *mci) */ /* FIXME - should a warning be printed if no error detection? correction? */ -int edac_mc_add_mc(struct mem_ctl_info *mci, int mc_idx) +int edac_mc_add_mc(struct mem_ctl_info *mci) { debugf0("%s()\n", __func__); - mci->mc_idx = mc_idx; + #ifdef CONFIG_EDAC_DEBUG if (edac_debug_level >= 3) edac_mc_dump_mci(mci); @@ -1484,12 +466,12 @@ int edac_mc_add_mc(struct mem_ctl_info *mci, int mc_idx) edac_mc_dump_csrow(&mci->csrows[i]); for (j = 0; j < mci->csrows[i].nr_channels; j++) - edac_mc_dump_channel( - &mci->csrows[i].channels[j]); + edac_mc_dump_channel(&mci->csrows[i]. + channels[j]); } } #endif - down(&mem_ctls_mutex); + mutex_lock(&mem_ctls_mutex); if (add_mc_to_global_list(mci)) goto fail0; @@ -1503,18 +485,28 @@ int edac_mc_add_mc(struct mem_ctl_info *mci, int mc_idx) goto fail1; } + /* If there IS a check routine, then we are running POLLED */ + if (mci->edac_check != NULL) { + /* This instance is NOW RUNNING */ + mci->op_state = OP_RUNNING_POLL; + + edac_mc_workq_setup(mci, edac_mc_get_poll_msec()); + } else { + mci->op_state = OP_RUNNING_INTERRUPT; + } + /* Report action taken */ - edac_mc_printk(mci, KERN_INFO, "Giving out device to %s %s: DEV %s\n", - mci->mod_name, mci->ctl_name, dev_name(mci->dev)); + edac_mc_printk(mci, KERN_INFO, "Giving out device to '%s' '%s':" + " DEV %s\n", mci->mod_name, mci->ctl_name, dev_name(mci)); - up(&mem_ctls_mutex); + mutex_unlock(&mem_ctls_mutex); return 0; fail1: del_mc_from_global_list(mci); fail0: - up(&mem_ctls_mutex); + mutex_unlock(&mem_ctls_mutex); return 1; } EXPORT_SYMBOL_GPL(edac_mc_add_mc); @@ -1526,29 +518,41 @@ EXPORT_SYMBOL_GPL(edac_mc_add_mc); * * Return pointer to removed mci structure, or NULL if device not found. */ -struct mem_ctl_info * edac_mc_del_mc(struct device *dev) +struct mem_ctl_info *edac_mc_del_mc(struct device *dev) { struct mem_ctl_info *mci; - debugf0("MC: %s()\n", __func__); - down(&mem_ctls_mutex); + debugf0("%s()\n", __func__); + + mutex_lock(&mem_ctls_mutex); - if ((mci = find_mci_by_dev(dev)) == NULL) { - up(&mem_ctls_mutex); + /* find the requested mci struct in the global list */ + mci = find_mci_by_dev(dev); + if (mci == NULL) { + mutex_unlock(&mem_ctls_mutex); return NULL; } - edac_remove_sysfs_mci_device(mci); + /* marking MCI offline */ + mci->op_state = OP_OFFLINE; + del_mc_from_global_list(mci); - up(&mem_ctls_mutex); + mutex_unlock(&mem_ctls_mutex); + + /* flush workq processes and remove sysfs */ + edac_mc_workq_teardown(mci); + edac_remove_sysfs_mci_device(mci); + edac_printk(KERN_INFO, EDAC_MC, "Removed device %d for %s %s: DEV %s\n", mci->mc_idx, - mci->mod_name, mci->ctl_name, dev_name(mci->dev)); + mci->mod_name, mci->ctl_name, dev_name(mci)); + return mci; } EXPORT_SYMBOL_GPL(edac_mc_del_mc); -void edac_mc_scrub_block(unsigned long page, unsigned long offset, u32 size) +static void edac_mc_scrub_block(unsigned long page, unsigned long offset, + u32 size) { struct page *pg; void *virt_addr; @@ -1557,7 +561,7 @@ void edac_mc_scrub_block(unsigned long page, unsigned long offset, u32 size) debugf3("%s()\n", __func__); /* ECC error page was not in our memory. Ignore it. */ - if(!pfn_valid(page)) + if (!pfn_valid(page)) return; /* Find the actual page structure then map it and fix */ @@ -1577,7 +581,6 @@ void edac_mc_scrub_block(unsigned long page, unsigned long offset, u32 size) if (PageHighMem(pg)) local_irq_restore(flags); } -EXPORT_SYMBOL_GPL(edac_mc_scrub_block); /* FIXME - should return -1 */ int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci, unsigned long page) @@ -1611,7 +614,7 @@ int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci, unsigned long page) if (row == -1) edac_mc_printk(mci, KERN_ERR, "could not look up page error address %lx\n", - (unsigned long) page); + (unsigned long)page); return row; } @@ -1620,8 +623,9 @@ EXPORT_SYMBOL_GPL(edac_mc_find_csrow_by_page); /* FIXME - setable log (warning/emerg) levels */ /* FIXME - integrate with evlog: http://evlog.sourceforge.net/ */ void edac_mc_handle_ce(struct mem_ctl_info *mci, - unsigned long page_frame_number, unsigned long offset_in_page, - unsigned long syndrome, int row, int channel, const char *msg) + unsigned long page_frame_number, + unsigned long offset_in_page, unsigned long syndrome, + int row, int channel, const char *msg) { unsigned long remapped_page; @@ -1647,7 +651,7 @@ void edac_mc_handle_ce(struct mem_ctl_info *mci, return; } - if (log_ce) + if (edac_mc_get_log_ce()) /* FIXME - put in DIMM location */ edac_mc_printk(mci, KERN_WARNING, "CE page 0x%lx, offset 0x%lx, grain %d, syndrome " @@ -1671,18 +675,18 @@ void edac_mc_handle_ce(struct mem_ctl_info *mci, * page - which can then be scrubbed. */ remapped_page = mci->ctl_page_to_phys ? - mci->ctl_page_to_phys(mci, page_frame_number) : - page_frame_number; + mci->ctl_page_to_phys(mci, page_frame_number) : + page_frame_number; edac_mc_scrub_block(remapped_page, offset_in_page, - mci->csrows[row].grain); + mci->csrows[row].grain); } } EXPORT_SYMBOL_GPL(edac_mc_handle_ce); void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci, const char *msg) { - if (log_ce) + if (edac_mc_get_log_ce()) edac_mc_printk(mci, KERN_WARNING, "CE - no information available: %s\n", msg); @@ -1692,8 +696,8 @@ void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci, const char *msg) EXPORT_SYMBOL_GPL(edac_mc_handle_ce_no_info); void edac_mc_handle_ue(struct mem_ctl_info *mci, - unsigned long page_frame_number, unsigned long offset_in_page, - int row, const char *msg) + unsigned long page_frame_number, + unsigned long offset_in_page, int row, const char *msg) { int len = EDAC_MC_LABEL_LEN * 4; char labels[len + 1]; @@ -1714,26 +718,26 @@ void edac_mc_handle_ue(struct mem_ctl_info *mci, } chars = snprintf(pos, len + 1, "%s", - mci->csrows[row].channels[0].label); + mci->csrows[row].channels[0].label); len -= chars; pos += chars; for (chan = 1; (chan < mci->csrows[row].nr_channels) && (len > 0); - chan++) { + chan++) { chars = snprintf(pos, len + 1, ":%s", - mci->csrows[row].channels[chan].label); + mci->csrows[row].channels[chan].label); len -= chars; pos += chars; } - if (log_ue) + if (edac_mc_get_log_ue()) edac_mc_printk(mci, KERN_EMERG, "UE page 0x%lx, offset 0x%lx, grain %d, row %d, " "labels \"%s\": %s\n", page_frame_number, - offset_in_page, mci->csrows[row].grain, row, labels, - msg); + offset_in_page, mci->csrows[row].grain, row, + labels, msg); - if (panic_on_ue) + if (edac_mc_get_panic_on_ue()) panic("EDAC MC%d: UE page 0x%lx, offset 0x%lx, grain %d, " "row %d, labels \"%s\": %s\n", mci->mc_idx, page_frame_number, offset_in_page, @@ -1746,10 +750,10 @@ EXPORT_SYMBOL_GPL(edac_mc_handle_ue); void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci, const char *msg) { - if (panic_on_ue) + if (edac_mc_get_panic_on_ue()) panic("EDAC MC%d: Uncorrected Error", mci->mc_idx); - if (log_ue) + if (edac_mc_get_log_ue()) edac_mc_printk(mci, KERN_WARNING, "UE - no information available: %s\n", msg); mci->ue_noinfo_count++; @@ -1757,16 +761,14 @@ void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci, const char *msg) } EXPORT_SYMBOL_GPL(edac_mc_handle_ue_no_info); - /************************************************************* * On Fully Buffered DIMM modules, this help function is * called to process UE events */ void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci, - unsigned int csrow, - unsigned int channela, - unsigned int channelb, - char *msg) + unsigned int csrow, + unsigned int channela, + unsigned int channelb, char *msg) { int len = EDAC_MC_LABEL_LEN * 4; char labels[len + 1]; @@ -1808,20 +810,21 @@ void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci, /* Generate the DIMM labels from the specified channels */ chars = snprintf(pos, len + 1, "%s", mci->csrows[csrow].channels[channela].label); - len -= chars; pos += chars; + len -= chars; + pos += chars; chars = snprintf(pos, len + 1, "-%s", mci->csrows[csrow].channels[channelb].label); - if (log_ue) + if (edac_mc_get_log_ue()) edac_mc_printk(mci, KERN_EMERG, "UE row %d, channel-a= %d channel-b= %d " "labels \"%s\": %s\n", csrow, channela, channelb, labels, msg); - if (panic_on_ue) + if (edac_mc_get_panic_on_ue()) panic("UE row %d, channel-a= %d channel-b= %d " - "labels \"%s\": %s\n", csrow, channela, - channelb, labels, msg); + "labels \"%s\": %s\n", csrow, channela, + channelb, labels, msg); } EXPORT_SYMBOL(edac_mc_handle_fbd_ue); @@ -1830,9 +833,7 @@ EXPORT_SYMBOL(edac_mc_handle_fbd_ue); * called to process CE events */ void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci, - unsigned int csrow, - unsigned int channel, - char *msg) + unsigned int csrow, unsigned int channel, char *msg) { /* Ensure boundary values */ @@ -1853,13 +854,12 @@ void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci, return; } - if (log_ce) + if (edac_mc_get_log_ce()) /* FIXME - put in DIMM location */ edac_mc_printk(mci, KERN_WARNING, "CE row %d, channel %d, label \"%s\": %s\n", csrow, channel, - mci->csrows[csrow].channels[channel].label, - msg); + mci->csrows[csrow].channels[channel].label, msg); mci->ce_count++; mci->csrows[csrow].ce_count++; @@ -1867,17 +867,16 @@ void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci, } EXPORT_SYMBOL(edac_mc_handle_fbd_ce); - /* * Iterate over all MC instances and check for ECC, et al, errors */ -static inline void check_mc_devices(void) +void edac_check_mc_devices(void) { struct list_head *item; struct mem_ctl_info *mci; debugf3("%s()\n", __func__); - down(&mem_ctls_mutex); + mutex_lock(&mem_ctls_mutex); list_for_each(item, &mc_devices) { mci = list_entry(item, struct mem_ctl_info, link); @@ -1886,120 +885,5 @@ static inline void check_mc_devices(void) mci->edac_check(mci); } - up(&mem_ctls_mutex); -} - -/* - * Check MC status every poll_msec. - * Check PCI status every poll_msec as well. - * - * This where the work gets done for edac. - * - * SMP safe, doesn't use NMI, and auto-rate-limits. - */ -static void do_edac_check(void) -{ - debugf3("%s()\n", __func__); - check_mc_devices(); - do_pci_parity_check(); -} - -static int edac_kernel_thread(void *arg) -{ - set_freezable(); - while (!kthread_should_stop()) { - do_edac_check(); - - /* goto sleep for the interval */ - schedule_timeout_interruptible((HZ * poll_msec) / 1000); - try_to_freeze(); - } - - return 0; + mutex_unlock(&mem_ctls_mutex); } - -/* - * edac_mc_init - * module initialization entry point - */ -static int __init edac_mc_init(void) -{ - edac_printk(KERN_INFO, EDAC_MC, EDAC_MC_VERSION "\n"); - - /* - * Harvest and clear any boot/initialization PCI parity errors - * - * FIXME: This only clears errors logged by devices present at time of - * module initialization. We should also do an initial clear - * of each newly hotplugged device. - */ - clear_pci_parity_errors(); - - /* Create the MC sysfs entries */ - if (edac_sysfs_memctrl_setup()) { - edac_printk(KERN_ERR, EDAC_MC, - "Error initializing sysfs code\n"); - return -ENODEV; - } - - /* Create the PCI parity sysfs entries */ - if (edac_sysfs_pci_setup()) { - edac_sysfs_memctrl_teardown(); - edac_printk(KERN_ERR, EDAC_MC, - "EDAC PCI: Error initializing sysfs code\n"); - return -ENODEV; - } - - /* create our kernel thread */ - edac_thread = kthread_run(edac_kernel_thread, NULL, "kedac"); - - if (IS_ERR(edac_thread)) { - /* remove the sysfs entries */ - edac_sysfs_memctrl_teardown(); - edac_sysfs_pci_teardown(); - return PTR_ERR(edac_thread); - } - - return 0; -} - -/* - * edac_mc_exit() - * module exit/termination functioni - */ -static void __exit edac_mc_exit(void) -{ - debugf0("%s()\n", __func__); - kthread_stop(edac_thread); - - /* tear down the sysfs device */ - edac_sysfs_memctrl_teardown(); - edac_sysfs_pci_teardown(); -} - -module_init(edac_mc_init); -module_exit(edac_mc_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Linux Networx (http://lnxi.com) Thayne Harbaugh et al\n" - "Based on work by Dan Hollis et al"); -MODULE_DESCRIPTION("Core library routines for MC reporting"); - -module_param(panic_on_ue, int, 0644); -MODULE_PARM_DESC(panic_on_ue, "Panic on uncorrected error: 0=off 1=on"); -#ifdef CONFIG_PCI -module_param(check_pci_parity, int, 0644); -MODULE_PARM_DESC(check_pci_parity, "Check for PCI bus parity errors: 0=off 1=on"); -module_param(panic_on_pci_parity, int, 0644); -MODULE_PARM_DESC(panic_on_pci_parity, "Panic on PCI Bus Parity error: 0=off 1=on"); -#endif -module_param(log_ue, int, 0644); -MODULE_PARM_DESC(log_ue, "Log uncorrectable error to console: 0=off 1=on"); -module_param(log_ce, int, 0644); -MODULE_PARM_DESC(log_ce, "Log correctable error to console: 0=off 1=on"); -module_param(poll_msec, int, 0644); -MODULE_PARM_DESC(poll_msec, "Polling period in milliseconds"); -#ifdef CONFIG_EDAC_DEBUG -module_param(edac_debug_level, int, 0644); -MODULE_PARM_DESC(edac_debug_level, "Debug level"); -#endif diff --git a/drivers/edac/edac_mc_sysfs.c b/drivers/edac/edac_mc_sysfs.c new file mode 100644 index 0000000..cd090b0 --- /dev/null +++ b/drivers/edac/edac_mc_sysfs.c @@ -0,0 +1,1024 @@ +/* + * edac_mc kernel module + * (C) 2005-2007 Linux Networx (http://lnxi.com) + * + * This file may be distributed under the terms of the + * GNU General Public License. + * + * Written Doug Thompson <norsk5@xmission.com> www.softwarebitmaker.com + * + */ + +#include <linux/ctype.h> +#include <linux/bug.h> + +#include "edac_core.h" +#include "edac_module.h" + + +/* MC EDAC Controls, setable by module parameter, and sysfs */ +static int edac_mc_log_ue = 1; +static int edac_mc_log_ce = 1; +static int edac_mc_panic_on_ue; +static int edac_mc_poll_msec = 1000; + +/* Getter functions for above */ +int edac_mc_get_log_ue(void) +{ + return edac_mc_log_ue; +} + +int edac_mc_get_log_ce(void) +{ + return edac_mc_log_ce; +} + +int edac_mc_get_panic_on_ue(void) +{ + return edac_mc_panic_on_ue; +} + +/* this is temporary */ +int edac_mc_get_poll_msec(void) +{ + return edac_mc_poll_msec; +} + +/* Parameter declarations for above */ +module_param(edac_mc_panic_on_ue, int, 0644); +MODULE_PARM_DESC(edac_mc_panic_on_ue, "Panic on uncorrected error: 0=off 1=on"); +module_param(edac_mc_log_ue, int, 0644); +MODULE_PARM_DESC(edac_mc_log_ue, + "Log uncorrectable error to console: 0=off 1=on"); +module_param(edac_mc_log_ce, int, 0644); +MODULE_PARM_DESC(edac_mc_log_ce, + "Log correctable error to console: 0=off 1=on"); +module_param(edac_mc_poll_msec, int, 0644); +MODULE_PARM_DESC(edac_mc_poll_msec, "Polling period in milliseconds"); + +/* + * various constants for Memory Controllers + */ +static const char *mem_types[] = { + [MEM_EMPTY] = "Empty", + [MEM_RESERVED] = "Reserved", + [MEM_UNKNOWN] = "Unknown", + [MEM_FPM] = "FPM", + [MEM_EDO] = "EDO", + [MEM_BEDO] = "BEDO", + [MEM_SDR] = "Unbuffered-SDR", + [MEM_RDR] = "Registered-SDR", + [MEM_DDR] = "Unbuffered-DDR", + [MEM_RDDR] = "Registered-DDR", + [MEM_RMBS] = "RMBS", + [MEM_DDR2] = "Unbuffered-DDR2", + [MEM_FB_DDR2] = "FullyBuffered-DDR2", + [MEM_RDDR2] = "Registered-DDR2" +}; + +static const char *dev_types[] = { + [DEV_UNKNOWN] = "Unknown", + [DEV_X1] = "x1", + [DEV_X2] = "x2", + [DEV_X4] = "x4", + [DEV_X8] = "x8", + [DEV_X16] = "x16", + [DEV_X32] = "x32", + [DEV_X64] = "x64" +}; + +static const char *edac_caps[] = { + [EDAC_UNKNOWN] = "Unknown", + [EDAC_NONE] = "None", + [EDAC_RESERVED] = "Reserved", + [EDAC_PARITY] = "PARITY", + [EDAC_EC] = "EC", + [EDAC_SECDED] = "SECDED", + [EDAC_S2ECD2ED] = "S2ECD2ED", + [EDAC_S4ECD4ED] = "S4ECD4ED", + [EDAC_S8ECD8ED] = "S8ECD8ED", + [EDAC_S16ECD16ED] = "S16ECD16ED" +}; + + + +/* + * /sys/devices/system/edac/mc; + * data structures and methods + */ +static ssize_t memctrl_int_show(void *ptr, char *buffer) +{ + int *value = (int *)ptr; + return sprintf(buffer, "%u\n", *value); +} + +static ssize_t memctrl_int_store(void *ptr, const char *buffer, size_t count) +{ + int *value = (int *)ptr; + + if (isdigit(*buffer)) + *value = simple_strtoul(buffer, NULL, 0); + + return count; +} + + +/* EDAC sysfs CSROW data structures and methods + */ + +/* Set of more default csrow<id> attribute show/store functions */ +static ssize_t csrow_ue_count_show(struct csrow_info *csrow, char *data, + int private) +{ + return sprintf(data, "%u\n", csrow->ue_count); +} + +static ssize_t csrow_ce_count_show(struct csrow_info *csrow, char *data, + int private) +{ + return sprintf(data, "%u\n", csrow->ce_count); +} + +static ssize_t csrow_size_show(struct csrow_info *csrow, char *data, + int private) +{ + return sprintf(data, "%u\n", PAGES_TO_MiB(csrow->nr_pages)); +} + +static ssize_t csrow_mem_type_show(struct csrow_info *csrow, char *data, + int private) +{ + return sprintf(data, "%s\n", mem_types[csrow->mtype]); +} + +static ssize_t csrow_dev_type_show(struct csrow_info *csrow, char *data, + int private) +{ + return sprintf(data, "%s\n", dev_types[csrow->dtype]); +} + +static ssize_t csrow_edac_mode_show(struct csrow_info *csrow, char *data, + int private) +{ + return sprintf(data, "%s\n", edac_caps[csrow->edac_mode]); +} + +/* show/store functions for DIMM Label attributes */ +static ssize_t channel_dimm_label_show(struct csrow_info *csrow, + char *data, int channel) +{ + return snprintf(data, EDAC_MC_LABEL_LEN, "%s", + csrow->channels[channel].label); +} + +static ssize_t channel_dimm_label_store(struct csrow_info *csrow, + const char *data, + size_t count, int channel) +{ + ssize_t max_size = 0; + + max_size = min((ssize_t) count, (ssize_t) EDAC_MC_LABEL_LEN - 1); + strncpy(csrow->channels[channel].label, data, max_size); + csrow->channels[channel].label[max_size] = '\0'; + + return max_size; +} + +/* show function for dynamic chX_ce_count attribute */ +static ssize_t channel_ce_count_show(struct csrow_info *csrow, + char *data, int channel) +{ + return sprintf(data, "%u\n", csrow->channels[channel].ce_count); +} + +/* csrow specific attribute structure */ +struct csrowdev_attribute { + struct attribute attr; + ssize_t(*show) (struct csrow_info *, char *, int); + ssize_t(*store) (struct csrow_info *, const char *, size_t, int); + int private; +}; + +#define to_csrow(k) container_of(k, struct csrow_info, kobj) +#define to_csrowdev_attr(a) container_of(a, struct csrowdev_attribute, attr) + +/* Set of show/store higher level functions for default csrow attributes */ +static ssize_t csrowdev_show(struct kobject *kobj, + struct attribute *attr, char *buffer) +{ + struct csrow_info *csrow = to_csrow(kobj); + struct csrowdev_attribute *csrowdev_attr = to_csrowdev_attr(attr); + + if (csrowdev_attr->show) + return csrowdev_attr->show(csrow, + buffer, csrowdev_attr->private); + return -EIO; +} + +static ssize_t csrowdev_store(struct kobject *kobj, struct attribute *attr, + const char *buffer, size_t count) +{ + struct csrow_info *csrow = to_csrow(kobj); + struct csrowdev_attribute *csrowdev_attr = to_csrowdev_attr(attr); + + if (csrowdev_attr->store) + return csrowdev_attr->store(csrow, + buffer, + count, csrowdev_attr->private); + return -EIO; +} + +static struct sysfs_ops csrowfs_ops = { + .show = csrowdev_show, + .store = csrowdev_store +}; + +#define CSROWDEV_ATTR(_name,_mode,_show,_store,_private) \ +static struct csrowdev_attribute attr_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ + .private = _private, \ +}; + +/* default cwrow<id>/attribute files */ +CSROWDEV_ATTR(size_mb, S_IRUGO, csrow_size_show, NULL, 0); +CSROWDEV_ATTR(dev_type, S_IRUGO, csrow_dev_type_show, NULL, 0); +CSROWDEV_ATTR(mem_type, S_IRUGO, csrow_mem_type_show, NULL, 0); +CSROWDEV_ATTR(edac_mode, S_IRUGO, csrow_edac_mode_show, NULL, 0); +CSROWDEV_ATTR(ue_count, S_IRUGO, csrow_ue_count_show, NULL, 0); +CSROWDEV_ATTR(ce_count, S_IRUGO, csrow_ce_count_show, NULL, 0); + +/* default attributes of the CSROW<id> object */ +static struct csrowdev_attribute *default_csrow_attr[] = { + &attr_dev_type, + &attr_mem_type, + &attr_edac_mode, + &attr_size_mb, + &attr_ue_count, + &attr_ce_count, + NULL, +}; + +/* possible dynamic channel DIMM Label attribute files */ +CSROWDEV_ATTR(ch0_dimm_label, S_IRUGO | S_IWUSR, + channel_dimm_label_show, channel_dimm_label_store, 0); +CSROWDEV_ATTR(ch1_dimm_label, S_IRUGO | S_IWUSR, + channel_dimm_label_show, channel_dimm_label_store, 1); +CSROWDEV_ATTR(ch2_dimm_label, S_IRUGO | S_IWUSR, + channel_dimm_label_show, channel_dimm_label_store, 2); +CSROWDEV_ATTR(ch3_dimm_label, S_IRUGO | S_IWUSR, + channel_dimm_label_show, channel_dimm_label_store, 3); +CSROWDEV_ATTR(ch4_dimm_label, S_IRUGO | S_IWUSR, + channel_dimm_label_show, channel_dimm_label_store, 4); +CSROWDEV_ATTR(ch5_dimm_label, S_IRUGO | S_IWUSR, + channel_dimm_label_show, channel_dimm_label_store, 5); + +/* Total possible dynamic DIMM Label attribute file table */ +static struct csrowdev_attribute *dynamic_csrow_dimm_attr[] = { + &attr_ch0_dimm_label, + &attr_ch1_dimm_label, + &attr_ch2_dimm_label, + &attr_ch3_dimm_label, + &attr_ch4_dimm_label, + &attr_ch5_dimm_label +}; + +/* possible dynamic channel ce_count attribute files */ +CSROWDEV_ATTR(ch0_ce_count, S_IRUGO | S_IWUSR, channel_ce_count_show, NULL, 0); +CSROWDEV_ATTR(ch1_ce_count, S_IRUGO | S_IWUSR, channel_ce_count_show, NULL, 1); +CSROWDEV_ATTR(ch2_ce_count, S_IRUGO | S_IWUSR, channel_ce_count_show, NULL, 2); +CSROWDEV_ATTR(ch3_ce_count, S_IRUGO | S_IWUSR, channel_ce_count_show, NULL, 3); +CSROWDEV_ATTR(ch4_ce_count, S_IRUGO | S_IWUSR, channel_ce_count_show, NULL, 4); +CSROWDEV_ATTR(ch5_ce_count, S_IRUGO | S_IWUSR, channel_ce_count_show, NULL, 5); + +/* Total possible dynamic ce_count attribute file table */ +static struct csrowdev_attribute *dynamic_csrow_ce_count_attr[] = { + &attr_ch0_ce_count, + &attr_ch1_ce_count, + &attr_ch2_ce_count, + &attr_ch3_ce_count, + &attr_ch4_ce_count, + &attr_ch5_ce_count +}; + +#define EDAC_NR_CHANNELS 6 + +/* Create dynamic CHANNEL files, indexed by 'chan', under specifed CSROW */ +static int edac_create_channel_files(struct kobject *kobj, int chan) +{ + int err = -ENODEV; + + if (chan >= EDAC_NR_CHANNELS) + return err; + + /* create the DIMM label attribute file */ + err = sysfs_create_file(kobj, + (struct attribute *) + dynamic_csrow_dimm_attr[chan]); + + if (!err) { + /* create the CE Count attribute file */ + err = sysfs_create_file(kobj, + (struct attribute *) + dynamic_csrow_ce_count_attr[chan]); + } else { + debugf1("%s() dimm labels and ce_count files created", + __func__); + } + + return err; +} + +/* No memory to release for this kobj */ +static void edac_csrow_instance_release(struct kobject *kobj) +{ + struct mem_ctl_info *mci; + struct csrow_info *cs; + + debugf1("%s()\n", __func__); + + cs = container_of(kobj, struct csrow_info, kobj); + mci = cs->mci; + + kobject_put(&mci->edac_mci_kobj); +} + +/* the kobj_type instance for a CSROW */ +static struct kobj_type ktype_csrow = { + .release = edac_csrow_instance_release, + .sysfs_ops = &csrowfs_ops, + .default_attrs = (struct attribute **)default_csrow_attr, +}; + +/* Create a CSROW object under specifed edac_mc_device */ +static int edac_create_csrow_object(struct mem_ctl_info *mci, + struct csrow_info *csrow, int index) +{ + struct kobject *kobj_mci = &mci->edac_mci_kobj; + struct kobject *kobj; + int chan; + int err; + + /* generate ..../edac/mc/mc<id>/csrow<index> */ + memset(&csrow->kobj, 0, sizeof(csrow->kobj)); + csrow->mci = mci; /* include container up link */ + csrow->kobj.parent = kobj_mci; + csrow->kobj.ktype = &ktype_csrow; + + /* name this instance of csrow<id> */ + err = kobject_set_name(&csrow->kobj, "csrow%d", index); + if (err) + goto err_out; + + /* bump the mci instance's kobject's ref count */ + kobj = kobject_get(&mci->edac_mci_kobj); + if (!kobj) { + err = -ENODEV; + goto err_out; + } + + /* Instanstiate the csrow object */ + err = kobject_register(&csrow->kobj); + if (err) + goto err_release_top_kobj; + + /* At this point, to release a csrow kobj, one must + * call the kobject_unregister and allow that tear down + * to work the releasing + */ + + /* Create the dyanmic attribute files on this csrow, + * namely, the DIMM labels and the channel ce_count + */ + for (chan = 0; chan < csrow->nr_channels; chan++) { + err = edac_create_channel_files(&csrow->kobj, chan); + if (err) { + /* special case the unregister here */ + kobject_unregister(&csrow->kobj); + goto err_out; + } + } + + return 0; + + /* error unwind stack */ +err_release_top_kobj: + kobject_put(&mci->edac_mci_kobj); + +err_out: + return err; +} + +/* default sysfs methods and data structures for the main MCI kobject */ + +static ssize_t mci_reset_counters_store(struct mem_ctl_info *mci, + const char *data, size_t count) +{ + int row, chan; + + mci->ue_noinfo_count = 0; + mci->ce_noinfo_count = 0; + mci->ue_count = 0; + mci->ce_count = 0; + + for (row = 0; row < mci->nr_csrows; row++) { + struct csrow_info *ri = &mci->csrows[row]; + + ri->ue_count = 0; + ri->ce_count = 0; + + for (chan = 0; chan < ri->nr_channels; chan++) + ri->channels[chan].ce_count = 0; + } + + mci->start_time = jiffies; + return count; +} + +/* memory scrubbing */ +static ssize_t mci_sdram_scrub_rate_store(struct mem_ctl_info *mci, + const char *data, size_t count) +{ + u32 bandwidth = -1; + + if (mci->set_sdram_scrub_rate) { + + memctrl_int_store(&bandwidth, data, count); + + if (!(*mci->set_sdram_scrub_rate) (mci, &bandwidth)) { + edac_printk(KERN_DEBUG, EDAC_MC, + "Scrub rate set successfully, applied: %d\n", + bandwidth); + } else { + /* FIXME: error codes maybe? */ + edac_printk(KERN_DEBUG, EDAC_MC, + "Scrub rate set FAILED, could not apply: %d\n", + bandwidth); + } + } else { + /* FIXME: produce "not implemented" ERROR for user-side. */ + edac_printk(KERN_WARNING, EDAC_MC, + "Memory scrubbing 'set'control is not implemented!\n"); + } + return count; +} + +static ssize_t mci_sdram_scrub_rate_show(struct mem_ctl_info *mci, char *data) +{ + u32 bandwidth = -1; + + if (mci->get_sdram_scrub_rate) { + if (!(*mci->get_sdram_scrub_rate) (mci, &bandwidth)) { + edac_printk(KERN_DEBUG, EDAC_MC, + "Scrub rate successfully, fetched: %d\n", + bandwidth); + } else { + /* FIXME: error codes maybe? */ + edac_printk(KERN_DEBUG, EDAC_MC, + "Scrub rate fetch FAILED, got: %d\n", + bandwidth); + } + } else { + /* FIXME: produce "not implemented" ERROR for user-side. */ + edac_printk(KERN_WARNING, EDAC_MC, + "Memory scrubbing 'get' control is not implemented\n"); + } + return sprintf(data, "%d\n", bandwidth); +} + +/* default attribute files for the MCI object */ +static ssize_t mci_ue_count_show(struct mem_ctl_info *mci, char *data) +{ + return sprintf(data, "%d\n", mci->ue_count); +} + +static ssize_t mci_ce_count_show(struct mem_ctl_info *mci, char *data) +{ + return sprintf(data, "%d\n", mci->ce_count); +} + +static ssize_t mci_ce_noinfo_show(struct mem_ctl_info *mci, char *data) +{ + return sprintf(data, "%d\n", mci->ce_noinfo_count); +} + +static ssize_t mci_ue_noinfo_show(struct mem_ctl_info *mci, char *data) +{ + return sprintf(data, "%d\n", mci->ue_noinfo_count); +} + +static ssize_t mci_seconds_show(struct mem_ctl_info *mci, char *data) +{ + return sprintf(data, "%ld\n", (jiffies - mci->start_time) / HZ); +} + +static ssize_t mci_ctl_name_show(struct mem_ctl_info *mci, char *data) +{ + return sprintf(data, "%s\n", mci->ctl_name); +} + +static ssize_t mci_size_mb_show(struct mem_ctl_info *mci, char *data) +{ + int total_pages, csrow_idx; + + for (total_pages = csrow_idx = 0; csrow_idx < mci->nr_csrows; + csrow_idx++) { + struct csrow_info *csrow = &mci->csrows[csrow_idx]; + + if (!csrow->nr_pages) + continue; + + total_pages += csrow->nr_pages; + } + + return sprintf(data, "%u\n", PAGES_TO_MiB(total_pages)); +} + +#define to_mci(k) container_of(k, struct mem_ctl_info, edac_mci_kobj) +#define to_mcidev_attr(a) container_of(a,struct mcidev_sysfs_attribute,attr) + +/* MCI show/store functions for top most object */ +static ssize_t mcidev_show(struct kobject *kobj, struct attribute *attr, + char *buffer) +{ + struct mem_ctl_info *mem_ctl_info = to_mci(kobj); + struct mcidev_sysfs_attribute *mcidev_attr = to_mcidev_attr(attr); + + if (mcidev_attr->show) + return mcidev_attr->show(mem_ctl_info, buffer); + + return -EIO; +} + +static ssize_t mcidev_store(struct kobject *kobj, struct attribute *attr, + const char *buffer, size_t count) +{ + struct mem_ctl_info *mem_ctl_info = to_mci(kobj); + struct mcidev_sysfs_attribute *mcidev_attr = to_mcidev_attr(attr); + + if (mcidev_attr->store) + return mcidev_attr->store(mem_ctl_info, buffer, count); + + return -EIO; +} + +/* Intermediate show/store table */ +static struct sysfs_ops mci_ops = { + .show = mcidev_show, + .store = mcidev_store +}; + +#define MCIDEV_ATTR(_name,_mode,_show,_store) \ +static struct mcidev_sysfs_attribute mci_attr_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +}; + +/* default Control file */ +MCIDEV_ATTR(reset_counters, S_IWUSR, NULL, mci_reset_counters_store); + +/* default Attribute files */ +MCIDEV_ATTR(mc_name, S_IRUGO, mci_ctl_name_show, NULL); +MCIDEV_ATTR(size_mb, S_IRUGO, mci_size_mb_show, NULL); +MCIDEV_ATTR(seconds_since_reset, S_IRUGO, mci_seconds_show, NULL); +MCIDEV_ATTR(ue_noinfo_count, S_IRUGO, mci_ue_noinfo_show, NULL); +MCIDEV_ATTR(ce_noinfo_count, S_IRUGO, mci_ce_noinfo_show, NULL); +MCIDEV_ATTR(ue_count, S_IRUGO, mci_ue_count_show, NULL); +MCIDEV_ATTR(ce_count, S_IRUGO, mci_ce_count_show, NULL); + +/* memory scrubber attribute file */ +MCIDEV_ATTR(sdram_scrub_rate, S_IRUGO | S_IWUSR, mci_sdram_scrub_rate_show, + mci_sdram_scrub_rate_store); + +static struct mcidev_sysfs_attribute *mci_attr[] = { + &mci_attr_reset_counters, + &mci_attr_mc_name, + &mci_attr_size_mb, + &mci_attr_seconds_since_reset, + &mci_attr_ue_noinfo_count, + &mci_attr_ce_noinfo_count, + &mci_attr_ue_count, + &mci_attr_ce_count, + &mci_attr_sdram_scrub_rate, + NULL +}; + + +/* + * Release of a MC controlling instance + * + * each MC control instance has the following resources upon entry: + * a) a ref count on the top memctl kobj + * b) a ref count on this module + * + * this function must decrement those ref counts and then + * issue a free on the instance's memory + */ +static void edac_mci_control_release(struct kobject *kobj) +{ + struct mem_ctl_info *mci; + + mci = to_mci(kobj); + + debugf0("%s() mci instance idx=%d releasing\n", __func__, mci->mc_idx); + + /* decrement the module ref count */ + module_put(mci->owner); + + /* free the mci instance memory here */ + kfree(mci); +} + +static struct kobj_type ktype_mci = { + .release = edac_mci_control_release, + .sysfs_ops = &mci_ops, + .default_attrs = (struct attribute **)mci_attr, +}; + +/* show/store, tables, etc for the MC kset */ + + +struct memctrl_dev_attribute { + struct attribute attr; + void *value; + ssize_t(*show) (void *, char *); + ssize_t(*store) (void *, const char *, size_t); +}; + +/* Set of show/store abstract level functions for memory control object */ +static ssize_t memctrl_dev_show(struct kobject *kobj, + struct attribute *attr, char *buffer) +{ + struct memctrl_dev_attribute *memctrl_dev; + memctrl_dev = (struct memctrl_dev_attribute *)attr; + + if (memctrl_dev->show) + return memctrl_dev->show(memctrl_dev->value, buffer); + + return -EIO; +} + +static ssize_t memctrl_dev_store(struct kobject *kobj, struct attribute *attr, + const char *buffer, size_t count) +{ + struct memctrl_dev_attribute *memctrl_dev; + memctrl_dev = (struct memctrl_dev_attribute *)attr; + + if (memctrl_dev->store) + return memctrl_dev->store(memctrl_dev->value, buffer, count); + + return -EIO; +} + +static struct sysfs_ops memctrlfs_ops = { + .show = memctrl_dev_show, + .store = memctrl_dev_store +}; + +#define MEMCTRL_ATTR(_name, _mode, _show, _store) \ +static struct memctrl_dev_attribute attr_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .value = &_name, \ + .show = _show, \ + .store = _store, \ +}; + +#define MEMCTRL_STRING_ATTR(_name, _data, _mode, _show, _store) \ +static struct memctrl_dev_attribute attr_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .value = _data, \ + .show = _show, \ + .store = _store, \ +}; + +/* csrow<id> control files */ +MEMCTRL_ATTR(edac_mc_panic_on_ue, + S_IRUGO | S_IWUSR, memctrl_int_show, memctrl_int_store); + +MEMCTRL_ATTR(edac_mc_log_ue, + S_IRUGO | S_IWUSR, memctrl_int_show, memctrl_int_store); + +MEMCTRL_ATTR(edac_mc_log_ce, + S_IRUGO | S_IWUSR, memctrl_int_show, memctrl_int_store); + +MEMCTRL_ATTR(edac_mc_poll_msec, + S_IRUGO | S_IWUSR, memctrl_int_show, memctrl_int_store); + +/* Base Attributes of the memory ECC object */ +static struct memctrl_dev_attribute *memctrl_attr[] = { + &attr_edac_mc_panic_on_ue, + &attr_edac_mc_log_ue, + &attr_edac_mc_log_ce, + &attr_edac_mc_poll_msec, + NULL, +}; + + +/* the ktype for the mc_kset internal kobj */ +static struct kobj_type ktype_mc_set_attribs = { + .sysfs_ops = &memctrlfs_ops, + .default_attrs = (struct attribute **)memctrl_attr, +}; + +/* EDAC memory controller sysfs kset: + * /sys/devices/system/edac/mc + */ +static struct kset mc_kset = { + .kobj = {.name = "mc", .ktype = &ktype_mc_set_attribs }, + .ktype = &ktype_mci, +}; + + +/* + * edac_mc_register_sysfs_main_kobj + * + * setups and registers the main kobject for each mci + */ +int edac_mc_register_sysfs_main_kobj(struct mem_ctl_info *mci) +{ + struct kobject *kobj_mci; + int err; + + debugf1("%s()\n", __func__); + + kobj_mci = &mci->edac_mci_kobj; + + /* Init the mci's kobject */ + memset(kobj_mci, 0, sizeof(*kobj_mci)); + + /* this instance become part of the mc_kset */ + kobj_mci->kset = &mc_kset; + + /* set the name of the mc<id> object */ + err = kobject_set_name(kobj_mci, "mc%d", mci->mc_idx); + if (err) + goto fail_out; + + /* Record which module 'owns' this control structure + * and bump the ref count of the module + */ + mci->owner = THIS_MODULE; + + /* bump ref count on this module */ + if (!try_module_get(mci->owner)) { + err = -ENODEV; + goto fail_out; + } + + /* register the mc<id> kobject to the mc_kset */ + err = kobject_register(kobj_mci); + if (err) { + debugf1("%s()Failed to register '.../edac/mc%d'\n", + __func__, mci->mc_idx); + goto kobj_reg_fail; + } + + /* At this point, to 'free' the control struct, + * edac_mc_unregister_sysfs_main_kobj() must be used + */ + + debugf1("%s() Registered '.../edac/mc%d' kobject\n", + __func__, mci->mc_idx); + + return 0; + + /* Error exit stack */ + +kobj_reg_fail: + module_put(mci->owner); + +fail_out: + return err; +} + +/* + * edac_mc_register_sysfs_main_kobj + * + * tears down and the main mci kobject from the mc_kset + */ +void edac_mc_unregister_sysfs_main_kobj(struct mem_ctl_info *mci) +{ + /* delete the kobj from the mc_kset */ + kobject_unregister(&mci->edac_mci_kobj); +} + +#define EDAC_DEVICE_SYMLINK "device" + +/* + * edac_create_mci_instance_attributes + * create MC driver specific attributes at the topmost level + * directory of this mci instance. + */ +static int edac_create_mci_instance_attributes(struct mem_ctl_info *mci) +{ + int err; + struct mcidev_sysfs_attribute *sysfs_attrib; + + /* point to the start of the array and iterate over it + * adding each attribute listed to this mci instance's kobject + */ + sysfs_attrib = mci->mc_driver_sysfs_attributes; + + while (sysfs_attrib && sysfs_attrib->attr.name) { + err = sysfs_create_file(&mci->edac_mci_kobj, + (struct attribute*) sysfs_attrib); + if (err) { + return err; + } + + sysfs_attrib++; + } + + return 0; +} + +/* + * edac_remove_mci_instance_attributes + * remove MC driver specific attributes at the topmost level + * directory of this mci instance. + */ +static void edac_remove_mci_instance_attributes(struct mem_ctl_info *mci) +{ + struct mcidev_sysfs_attribute *sysfs_attrib; + + /* point to the start of the array and iterate over it + * adding each attribute listed to this mci instance's kobject + */ + sysfs_attrib = mci->mc_driver_sysfs_attributes; + + /* loop if there are attributes and until we hit a NULL entry */ + while (sysfs_attrib && sysfs_attrib->attr.name) { + sysfs_remove_file(&mci->edac_mci_kobj, + (struct attribute *) sysfs_attrib); + sysfs_attrib++; + } +} + + +/* + * Create a new Memory Controller kobject instance, + * mc<id> under the 'mc' directory + * + * Return: + * 0 Success + * !0 Failure + */ +int edac_create_sysfs_mci_device(struct mem_ctl_info *mci) +{ + int i; + int err; + struct csrow_info *csrow; + struct kobject *kobj_mci = &mci->edac_mci_kobj; + + debugf0("%s() idx=%d\n", __func__, mci->mc_idx); + + /* create a symlink for the device */ + err = sysfs_create_link(kobj_mci, &mci->dev->kobj, + EDAC_DEVICE_SYMLINK); + if (err) { + debugf1("%s() failure to create symlink\n", __func__); + goto fail0; + } + + /* If the low level driver desires some attributes, + * then create them now for the driver. + */ + if (mci->mc_driver_sysfs_attributes) { + err = edac_create_mci_instance_attributes(mci); + if (err) { + debugf1("%s() failure to create mci attributes\n", + __func__); + goto fail0; + } + } + + /* Make directories for each CSROW object under the mc<id> kobject + */ + for (i = 0; i < mci->nr_csrows; i++) { + csrow = &mci->csrows[i]; + + /* Only expose populated CSROWs */ + if (csrow->nr_pages > 0) { + err = edac_create_csrow_object(mci, csrow, i); + if (err) { + debugf1("%s() failure: create csrow %d obj\n", + __func__, i); + goto fail1; + } + } + } + + return 0; + + /* CSROW error: backout what has already been registered, */ +fail1: + for (i--; i >= 0; i--) { + if (csrow->nr_pages > 0) { + kobject_unregister(&mci->csrows[i].kobj); + } + } + + /* remove the mci instance's attributes, if any */ + edac_remove_mci_instance_attributes(mci); + + /* remove the symlink */ + sysfs_remove_link(kobj_mci, EDAC_DEVICE_SYMLINK); + +fail0: + return err; +} + +/* + * remove a Memory Controller instance + */ +void edac_remove_sysfs_mci_device(struct mem_ctl_info *mci) +{ + int i; + + debugf0("%s()\n", __func__); + + /* remove all csrow kobjects */ + for (i = 0; i < mci->nr_csrows; i++) { + if (mci->csrows[i].nr_pages > 0) { + debugf0("%s() unreg csrow-%d\n", __func__, i); + kobject_unregister(&mci->csrows[i].kobj); + } + } + + debugf0("%s() remove_link\n", __func__); + + /* remove the symlink */ + sysfs_remove_link(&mci->edac_mci_kobj, EDAC_DEVICE_SYMLINK); + + debugf0("%s() remove_mci_instance\n", __func__); + + /* remove this mci instance's attribtes */ + edac_remove_mci_instance_attributes(mci); + + debugf0("%s() unregister this mci kobj\n", __func__); + + /* unregister this instance's kobject */ + kobject_unregister(&mci->edac_mci_kobj); +} + + + + +/* + * edac_setup_sysfs_mc_kset(void) + * + * Initialize the mc_kset for the 'mc' entry + * This requires creating the top 'mc' directory with a kset + * and its controls/attributes. + * + * To this 'mc' kset, instance 'mci' will be grouped as children. + * + * Return: 0 SUCCESS + * !0 FAILURE error code + */ +int edac_sysfs_setup_mc_kset(void) +{ + int err = 0; + struct sysdev_class *edac_class; + + debugf1("%s()\n", __func__); + + /* get the /sys/devices/system/edac class reference */ + edac_class = edac_get_edac_class(); + if (edac_class == NULL) { + debugf1("%s() no edac_class error=%d\n", __func__, err); + goto fail_out; + } + + /* Init the MC's kobject */ + mc_kset.kobj.parent = &edac_class->kset.kobj; + + /* register the mc_kset */ + err = kset_register(&mc_kset); + if (err) { + debugf1("%s() Failed to register '.../edac/mc'\n", __func__); + goto fail_out; + } + + debugf1("%s() Registered '.../edac/mc' kobject\n", __func__); + + return 0; + + + /* error unwind stack */ +fail_out: + return err; +} + +/* + * edac_sysfs_teardown_mc_kset + * + * deconstruct the mc_ket for memory controllers + */ +void edac_sysfs_teardown_mc_kset(void) +{ + kset_unregister(&mc_kset); +} + diff --git a/drivers/edac/edac_module.c b/drivers/edac/edac_module.c new file mode 100644 index 0000000..e0c4a40 --- /dev/null +++ b/drivers/edac/edac_module.c @@ -0,0 +1,222 @@ +/* + * edac_module.c + * + * (C) 2007 www.softwarebitmaker.com + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + * + * Author: Doug Thompson <dougthompson@xmission.com> + * + */ +#include <linux/edac.h> + +#include "edac_core.h" +#include "edac_module.h" + +#define EDAC_VERSION "Ver: 2.1.0 " __DATE__ + +#ifdef CONFIG_EDAC_DEBUG +/* Values of 0 to 4 will generate output */ +int edac_debug_level = 2; +EXPORT_SYMBOL_GPL(edac_debug_level); +#endif + +/* scope is to module level only */ +struct workqueue_struct *edac_workqueue; + +/* + * sysfs object: /sys/devices/system/edac + * need to export to other files in this modules + */ +static struct sysdev_class edac_class = { + set_kset_name("edac"), +}; +static int edac_class_valid; + +/* + * edac_op_state_to_string() + */ +char *edac_op_state_to_string(int opstate) +{ + if (opstate == OP_RUNNING_POLL) + return "POLLED"; + else if (opstate == OP_RUNNING_INTERRUPT) + return "INTERRUPT"; + else if (opstate == OP_RUNNING_POLL_INTR) + return "POLL-INTR"; + else if (opstate == OP_ALLOC) + return "ALLOC"; + else if (opstate == OP_OFFLINE) + return "OFFLINE"; + + return "UNKNOWN"; +} + +/* + * edac_get_edac_class() + * + * return pointer to the edac class of 'edac' + */ +struct sysdev_class *edac_get_edac_class(void) +{ + struct sysdev_class *classptr = NULL; + + if (edac_class_valid) + classptr = &edac_class; + + return classptr; +} + +/* + * edac_register_sysfs_edac_name() + * + * register the 'edac' into /sys/devices/system + * + * return: + * 0 success + * !0 error + */ +static int edac_register_sysfs_edac_name(void) +{ + int err; + + /* create the /sys/devices/system/edac directory */ + err = sysdev_class_register(&edac_class); + + if (err) { + debugf1("%s() error=%d\n", __func__, err); + return err; + } + + edac_class_valid = 1; + return 0; +} + +/* + * sysdev_class_unregister() + * + * unregister the 'edac' from /sys/devices/system + */ +static void edac_unregister_sysfs_edac_name(void) +{ + /* only if currently registered, then unregister it */ + if (edac_class_valid) + sysdev_class_unregister(&edac_class); + + edac_class_valid = 0; +} + +/* + * edac_workqueue_setup + * initialize the edac work queue for polling operations + */ +static int edac_workqueue_setup(void) +{ + edac_workqueue = create_singlethread_workqueue("edac-poller"); + if (edac_workqueue == NULL) + return -ENODEV; + else + return 0; +} + +/* + * edac_workqueue_teardown + * teardown the edac workqueue + */ +static void edac_workqueue_teardown(void) +{ + if (edac_workqueue) { + flush_workqueue(edac_workqueue); + destroy_workqueue(edac_workqueue); + edac_workqueue = NULL; + } +} + +/* + * edac_init + * module initialization entry point + */ +static int __init edac_init(void) +{ + int err = 0; + + edac_printk(KERN_INFO, EDAC_MC, EDAC_VERSION "\n"); + + /* + * Harvest and clear any boot/initialization PCI parity errors + * + * FIXME: This only clears errors logged by devices present at time of + * module initialization. We should also do an initial clear + * of each newly hotplugged device. + */ + edac_pci_clear_parity_errors(); + + /* + * perform the registration of the /sys/devices/system/edac class object + */ + if (edac_register_sysfs_edac_name()) { + edac_printk(KERN_ERR, EDAC_MC, + "Error initializing 'edac' kobject\n"); + err = -ENODEV; + goto error; + } + + /* + * now set up the mc_kset under the edac class object + */ + err = edac_sysfs_setup_mc_kset(); + if (err) + goto sysfs_setup_fail; + + /* Setup/Initialize the workq for this core */ + err = edac_workqueue_setup(); + if (err) { + edac_printk(KERN_ERR, EDAC_MC, "init WorkQueue failure\n"); + goto workq_fail; + } + + return 0; + + /* Error teardown stack */ +workq_fail: + edac_sysfs_teardown_mc_kset(); + +sysfs_setup_fail: + edac_unregister_sysfs_edac_name(); + +error: + return err; +} + +/* + * edac_exit() + * module exit/termination function + */ +static void __exit edac_exit(void) +{ + debugf0("%s()\n", __func__); + + /* tear down the various subsystems */ + edac_workqueue_teardown(); + edac_sysfs_teardown_mc_kset(); + edac_unregister_sysfs_edac_name(); +} + +/* + * Inform the kernel of our entry and exit points + */ +module_init(edac_init); +module_exit(edac_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Doug Thompson www.softwarebitmaker.com, et al"); +MODULE_DESCRIPTION("Core library routines for EDAC reporting"); + +/* refer to *_sysfs.c files for parameters that are exported via sysfs */ + +#ifdef CONFIG_EDAC_DEBUG +module_param(edac_debug_level, int, 0644); +MODULE_PARM_DESC(edac_debug_level, "Debug level"); +#endif diff --git a/drivers/edac/edac_module.h b/drivers/edac/edac_module.h new file mode 100644 index 0000000..a2134df --- /dev/null +++ b/drivers/edac/edac_module.h @@ -0,0 +1,77 @@ + +/* + * edac_module.h + * + * For defining functions/data for within the EDAC_CORE module only + * + * written by doug thompson <norsk5@xmission.h> + */ + +#ifndef __EDAC_MODULE_H__ +#define __EDAC_MODULE_H__ + +#include <linux/sysdev.h> + +#include "edac_core.h" + +/* + * INTERNAL EDAC MODULE: + * EDAC memory controller sysfs create/remove functions + * and setup/teardown functions + * + * edac_mc objects + */ +extern int edac_sysfs_setup_mc_kset(void); +extern void edac_sysfs_teardown_mc_kset(void); +extern int edac_mc_register_sysfs_main_kobj(struct mem_ctl_info *mci); +extern void edac_mc_unregister_sysfs_main_kobj(struct mem_ctl_info *mci); +extern int edac_create_sysfs_mci_device(struct mem_ctl_info *mci); +extern void edac_remove_sysfs_mci_device(struct mem_ctl_info *mci); +extern void edac_check_mc_devices(void); +extern int edac_get_log_ue(void); +extern int edac_get_log_ce(void); +extern int edac_get_panic_on_ue(void); +extern int edac_mc_get_log_ue(void); +extern int edac_mc_get_log_ce(void); +extern int edac_mc_get_panic_on_ue(void); +extern int edac_get_poll_msec(void); +extern int edac_mc_get_poll_msec(void); + +extern int edac_device_register_sysfs_main_kobj( + struct edac_device_ctl_info *edac_dev); +extern void edac_device_unregister_sysfs_main_kobj( + struct edac_device_ctl_info *edac_dev); +extern int edac_device_create_sysfs(struct edac_device_ctl_info *edac_dev); +extern void edac_device_remove_sysfs(struct edac_device_ctl_info *edac_dev); +extern struct sysdev_class *edac_get_edac_class(void); + +/* edac core workqueue: single CPU mode */ +extern struct workqueue_struct *edac_workqueue; +extern void edac_device_workq_setup(struct edac_device_ctl_info *edac_dev, + unsigned msec); +extern void edac_device_workq_teardown(struct edac_device_ctl_info *edac_dev); +extern void edac_device_reset_delay_period(struct edac_device_ctl_info + *edac_dev, unsigned long value); +extern void *edac_align_ptr(void *ptr, unsigned size); + +/* + * EDAC PCI functions + */ +#ifdef CONFIG_PCI +extern void edac_pci_do_parity_check(void); +extern void edac_pci_clear_parity_errors(void); +extern int edac_sysfs_pci_setup(void); +extern void edac_sysfs_pci_teardown(void); +extern int edac_pci_get_check_errors(void); +extern int edac_pci_get_poll_msec(void); +#else /* CONFIG_PCI */ +/* pre-process these away */ +#define edac_pci_do_parity_check() +#define edac_pci_clear_parity_errors() +#define edac_sysfs_pci_setup() (0) +#define edac_sysfs_pci_teardown() +#define edac_pci_get_check_errors() +#define edac_pci_get_poll_msec() +#endif /* CONFIG_PCI */ + +#endif /* __EDAC_MODULE_H__ */ diff --git a/drivers/edac/edac_pci.c b/drivers/edac/edac_pci.c new file mode 100644 index 0000000..d9cd5e0 --- /dev/null +++ b/drivers/edac/edac_pci.c @@ -0,0 +1,433 @@ +/* + * EDAC PCI component + * + * Author: Dave Jiang <djiang@mvista.com> + * + * 2007 (c) MontaVista Software, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + * + */ +#include <linux/module.h> +#include <linux/types.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <linux/sysctl.h> +#include <linux/highmem.h> +#include <linux/timer.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/sysdev.h> +#include <linux/ctype.h> +#include <linux/workqueue.h> +#include <asm/uaccess.h> +#include <asm/page.h> + +#include "edac_core.h" +#include "edac_module.h" + +static DEFINE_MUTEX(edac_pci_ctls_mutex); +static struct list_head edac_pci_list = LIST_HEAD_INIT(edac_pci_list); + +static inline void edac_lock_pci_list(void) +{ + mutex_lock(&edac_pci_ctls_mutex); +} + +static inline void edac_unlock_pci_list(void) +{ + mutex_unlock(&edac_pci_ctls_mutex); +} + +/* + * The alloc() and free() functions for the 'edac_pci' control info + * structure. The chip driver will allocate one of these for each + * edac_pci it is going to control/register with the EDAC CORE. + */ +struct edac_pci_ctl_info *edac_pci_alloc_ctl_info(unsigned int sz_pvt, + const char *edac_pci_name) +{ + struct edac_pci_ctl_info *pci; + void *pvt; + unsigned int size; + + pci = (struct edac_pci_ctl_info *)0; + pvt = edac_align_ptr(&pci[1], sz_pvt); + size = ((unsigned long)pvt) + sz_pvt; + + if ((pci = kzalloc(size, GFP_KERNEL)) == NULL) + return NULL; + + pvt = sz_pvt ? ((char *)pci) + ((unsigned long)pvt) : NULL; + + pci->pvt_info = pvt; + + pci->op_state = OP_ALLOC; + + snprintf(pci->name, strlen(edac_pci_name) + 1, "%s", edac_pci_name); + + return pci; +} + +EXPORT_SYMBOL_GPL(edac_pci_alloc_ctl_info); + +/* + * edac_pci_free_ctl_info() + * frees the memory allocated by edac_pci_alloc_ctl_info() function + */ +void edac_pci_free_ctl_info(struct edac_pci_ctl_info *pci) +{ + kfree(pci); +} + +EXPORT_SYMBOL_GPL(edac_pci_free_ctl_info); + +/* + * find_edac_pci_by_dev() + * scans the edac_pci list for a specific 'struct device *' + */ +static struct edac_pci_ctl_info *find_edac_pci_by_dev(struct device *dev) +{ + struct edac_pci_ctl_info *pci; + struct list_head *item; + + debugf3("%s()\n", __func__); + + list_for_each(item, &edac_pci_list) { + pci = list_entry(item, struct edac_pci_ctl_info, link); + + if (pci->dev == dev) + return pci; + } + + return NULL; +} + +/* + * add_edac_pci_to_global_list + * Before calling this function, caller must assign a unique value to + * edac_dev->pci_idx. + * Return: + * 0 on success + * 1 on failure + */ +static int add_edac_pci_to_global_list(struct edac_pci_ctl_info *pci) +{ + struct list_head *item, *insert_before; + struct edac_pci_ctl_info *rover; + + insert_before = &edac_pci_list; + + /* Determine if already on the list */ + if (unlikely((rover = find_edac_pci_by_dev(pci->dev)) != NULL)) + goto fail0; + + /* Insert in ascending order by 'pci_idx', so find position */ + list_for_each(item, &edac_pci_list) { + rover = list_entry(item, struct edac_pci_ctl_info, link); + + if (rover->pci_idx >= pci->pci_idx) { + if (unlikely(rover->pci_idx == pci->pci_idx)) + goto fail1; + + insert_before = item; + break; + } + } + + list_add_tail_rcu(&pci->link, insert_before); + return 0; + +fail0: + edac_printk(KERN_WARNING, EDAC_PCI, + "%s (%s) %s %s already assigned %d\n", + rover->dev->bus_id, dev_name(rover), + rover->mod_name, rover->ctl_name, rover->pci_idx); + return 1; + +fail1: + edac_printk(KERN_WARNING, EDAC_PCI, + "but in low-level driver: attempt to assign\n" + "\tduplicate pci_idx %d in %s()\n", rover->pci_idx, + __func__); + return 1; +} + +/* + * complete_edac_pci_list_del + */ +static void complete_edac_pci_list_del(struct rcu_head *head) +{ + struct edac_pci_ctl_info *pci; + + pci = container_of(head, struct edac_pci_ctl_info, rcu); + INIT_LIST_HEAD(&pci->link); + complete(&pci->complete); +} + +/* + * del_edac_pci_from_global_list + */ +static void del_edac_pci_from_global_list(struct edac_pci_ctl_info *pci) +{ + list_del_rcu(&pci->link); + init_completion(&pci->complete); + call_rcu(&pci->rcu, complete_edac_pci_list_del); + wait_for_completion(&pci->complete); +} + +/* + * edac_pci_find() + * Search for an edac_pci_ctl_info structure whose index is 'idx' + * + * If found, return a pointer to the structure + * Else return NULL. + * + * Caller must hold pci_ctls_mutex. + */ +struct edac_pci_ctl_info *edac_pci_find(int idx) +{ + struct list_head *item; + struct edac_pci_ctl_info *pci; + + /* Iterage over list, looking for exact match of ID */ + list_for_each(item, &edac_pci_list) { + pci = list_entry(item, struct edac_pci_ctl_info, link); + + if (pci->pci_idx >= idx) { + if (pci->pci_idx == idx) + return pci; + + /* not on list, so terminate early */ + break; + } + } + + return NULL; +} + +EXPORT_SYMBOL_GPL(edac_pci_find); + +/* + * edac_pci_workq_function() + * performs the operation scheduled by a workq request + */ +static void edac_pci_workq_function(struct work_struct *work_req) +{ + struct delayed_work *d_work = (struct delayed_work *)work_req; + struct edac_pci_ctl_info *pci = to_edac_pci_ctl_work(d_work); + + edac_lock_pci_list(); + + if ((pci->op_state == OP_RUNNING_POLL) && + (pci->edac_check != NULL) && (edac_pci_get_check_errors())) + pci->edac_check(pci); + + edac_unlock_pci_list(); + + /* Reschedule */ + queue_delayed_work(edac_workqueue, &pci->work, + msecs_to_jiffies(edac_pci_get_poll_msec())); +} + +/* + * edac_pci_workq_setup() + * initialize a workq item for this edac_pci instance + * passing in the new delay period in msec + */ +static void edac_pci_workq_setup(struct edac_pci_ctl_info *pci, + unsigned int msec) +{ + debugf0("%s()\n", __func__); + + INIT_DELAYED_WORK(&pci->work, edac_pci_workq_function); + queue_delayed_work(edac_workqueue, &pci->work, + msecs_to_jiffies(edac_pci_get_poll_msec())); +} + +/* + * edac_pci_workq_teardown() + * stop the workq processing on this edac_pci instance + */ +static void edac_pci_workq_teardown(struct edac_pci_ctl_info *pci) +{ + int status; + + status = cancel_delayed_work(&pci->work); + if (status == 0) + flush_workqueue(edac_workqueue); +} + +/* + * edac_pci_reset_delay_period + */ +void edac_pci_reset_delay_period(struct edac_pci_ctl_info *pci, + unsigned long value) +{ + edac_lock_pci_list(); + + edac_pci_workq_teardown(pci); + + edac_pci_workq_setup(pci, value); + + edac_unlock_pci_list(); +} + +EXPORT_SYMBOL_GPL(edac_pci_reset_delay_period); + +/* + * edac_pci_add_device: Insert the 'edac_dev' structure into the + * edac_pci global list and create sysfs entries associated with + * edac_pci structure. + * @pci: pointer to the edac_device structure to be added to the list + * @edac_idx: A unique numeric identifier to be assigned to the + * 'edac_pci' structure. + * + * Return: + * 0 Success + * !0 Failure + */ +int edac_pci_add_device(struct edac_pci_ctl_info *pci, int edac_idx) +{ + debugf0("%s()\n", __func__); + + pci->pci_idx = edac_idx; + + edac_lock_pci_list(); + + if (add_edac_pci_to_global_list(pci)) + goto fail0; + + pci->start_time = jiffies; + + if (edac_pci_create_sysfs(pci)) { + edac_pci_printk(pci, KERN_WARNING, + "failed to create sysfs pci\n"); + goto fail1; + } + + if (pci->edac_check != NULL) { + pci->op_state = OP_RUNNING_POLL; + + edac_pci_workq_setup(pci, 1000); + } else { + pci->op_state = OP_RUNNING_INTERRUPT; + } + + edac_pci_printk(pci, KERN_INFO, + "Giving out device to module '%s' controller '%s':" + " DEV '%s' (%s)\n", + pci->mod_name, + pci->ctl_name, + dev_name(pci), edac_op_state_to_string(pci->op_state)); + + edac_unlock_pci_list(); + return 0; + +fail1: + del_edac_pci_from_global_list(pci); +fail0: + edac_unlock_pci_list(); + return 1; +} + +EXPORT_SYMBOL_GPL(edac_pci_add_device); + +/* + * edac_pci_del_device() + * Remove sysfs entries for specified edac_pci structure and + * then remove edac_pci structure from global list + * + * @dev: + * Pointer to 'struct device' representing edac_pci structure + * to remove + * + * Return: + * Pointer to removed edac_pci structure, + * or NULL if device not found + */ +struct edac_pci_ctl_info *edac_pci_del_device(struct device *dev) +{ + struct edac_pci_ctl_info *pci; + + debugf0("%s()\n", __func__); + + edac_lock_pci_list(); + + if ((pci = find_edac_pci_by_dev(dev)) == NULL) { + edac_unlock_pci_list(); + return NULL; + } + + pci->op_state = OP_OFFLINE; + + edac_pci_workq_teardown(pci); + + edac_pci_remove_sysfs(pci); + + del_edac_pci_from_global_list(pci); + + edac_unlock_pci_list(); + + edac_printk(KERN_INFO, EDAC_PCI, + "Removed device %d for %s %s: DEV %s\n", + pci->pci_idx, pci->mod_name, pci->ctl_name, dev_name(pci)); + + return pci; +} + +EXPORT_SYMBOL_GPL(edac_pci_del_device); + +void edac_pci_generic_check(struct edac_pci_ctl_info *pci) +{ + edac_pci_do_parity_check(); +} + +static int edac_pci_idx; +#define EDAC_PCI_GENCTL_NAME "EDAC PCI controller" + +struct edac_pci_gen_data { + int edac_idx; +}; + +struct edac_pci_ctl_info *edac_pci_create_generic_ctl(struct device *dev, + const char *mod_name) +{ + struct edac_pci_ctl_info *pci; + struct edac_pci_gen_data *pdata; + + pci = edac_pci_alloc_ctl_info(sizeof(*pdata), EDAC_PCI_GENCTL_NAME); + if (!pci) + return NULL; + + pdata = pci->pvt_info; + pci->dev = dev; + dev_set_drvdata(pci->dev, pci); + pci->dev_name = pci_name(to_pci_dev(dev)); + + pci->mod_name = mod_name; + pci->ctl_name = EDAC_PCI_GENCTL_NAME; + pci->edac_check = edac_pci_generic_check; + + pdata->edac_idx = edac_pci_idx++; + + if (edac_pci_add_device(pci, pdata->edac_idx) > 0) { + debugf3("%s(): failed edac_pci_add_device()\n", __func__); + edac_pci_free_ctl_info(pci); + return NULL; + } + + return pci; +} + +EXPORT_SYMBOL_GPL(edac_pci_create_generic_ctl); + +void edac_pci_release_generic_ctl(struct edac_pci_ctl_info *pci) +{ + edac_pci_del_device(pci->dev); + edac_pci_free_ctl_info(pci); +} + +EXPORT_SYMBOL_GPL(edac_pci_release_generic_ctl); diff --git a/drivers/edac/edac_pci_sysfs.c b/drivers/edac/edac_pci_sysfs.c new file mode 100644 index 0000000..fac94ca --- /dev/null +++ b/drivers/edac/edac_pci_sysfs.c @@ -0,0 +1,620 @@ +/* + * (C) 2005, 2006 Linux Networx (http://lnxi.com) + * This file may be distributed under the terms of the + * GNU General Public License. + * + * Written Doug Thompson <norsk5@xmission.com> + * + */ +#include <linux/module.h> +#include <linux/sysdev.h> +#include <linux/ctype.h> + +#include "edac_core.h" +#include "edac_module.h" + +#ifdef CONFIG_PCI + +#define EDAC_PCI_SYMLINK "device" + +static int check_pci_errors; /* default YES check PCI parity */ +static int edac_pci_panic_on_pe; /* default no panic on PCI Parity */ +static int edac_pci_log_pe = 1; /* log PCI parity errors */ +static int edac_pci_log_npe = 1; /* log PCI non-parity error errors */ +static atomic_t pci_parity_count = ATOMIC_INIT(0); +static atomic_t pci_nonparity_count = ATOMIC_INIT(0); +static int edac_pci_poll_msec = 1000; + +static struct kobject edac_pci_kobj; /* /sys/devices/system/edac/pci */ +static struct completion edac_pci_kobj_complete; +static atomic_t edac_pci_sysfs_refcount = ATOMIC_INIT(0); + +int edac_pci_get_check_errors(void) +{ + return check_pci_errors; +} + +int edac_pci_get_log_pe(void) +{ + return edac_pci_log_pe; +} + +int edac_pci_get_log_npe(void) +{ + return edac_pci_log_npe; +} + +int edac_pci_get_panic_on_pe(void) +{ + return edac_pci_panic_on_pe; +} + +int edac_pci_get_poll_msec(void) +{ + return edac_pci_poll_msec; +} + +/**************************** EDAC PCI sysfs instance *******************/ +static ssize_t instance_pe_count_show(struct edac_pci_ctl_info *pci, char *data) +{ + return sprintf(data, "%u\n", atomic_read(&pci->counters.pe_count)); +} + +static ssize_t instance_npe_count_show(struct edac_pci_ctl_info *pci, + char *data) +{ + return sprintf(data, "%u\n", atomic_read(&pci->counters.npe_count)); +} + +#define to_instance(k) container_of(k, struct edac_pci_ctl_info, kobj) +#define to_instance_attr(a) container_of(a, struct instance_attribute, attr) + +/* DEVICE instance kobject release() function */ +static void edac_pci_instance_release(struct kobject *kobj) +{ + struct edac_pci_ctl_info *pci; + + debugf1("%s()\n", __func__); + + pci = to_instance(kobj); + complete(&pci->kobj_complete); +} + +/* instance specific attribute structure */ +struct instance_attribute { + struct attribute attr; + ssize_t(*show) (struct edac_pci_ctl_info *, char *); + ssize_t(*store) (struct edac_pci_ctl_info *, const char *, size_t); +}; + +/* Function to 'show' fields from the edac_pci 'instance' structure */ +static ssize_t edac_pci_instance_show(struct kobject *kobj, + struct attribute *attr, char *buffer) +{ + struct edac_pci_ctl_info *pci = to_instance(kobj); + struct instance_attribute *instance_attr = to_instance_attr(attr); + + if (instance_attr->show) + return instance_attr->show(pci, buffer); + return -EIO; +} + +/* Function to 'store' fields into the edac_pci 'instance' structure */ +static ssize_t edac_pci_instance_store(struct kobject *kobj, + struct attribute *attr, + const char *buffer, size_t count) +{ + struct edac_pci_ctl_info *pci = to_instance(kobj); + struct instance_attribute *instance_attr = to_instance_attr(attr); + + if (instance_attr->store) + return instance_attr->store(pci, buffer, count); + return -EIO; +} + +static struct sysfs_ops pci_instance_ops = { + .show = edac_pci_instance_show, + .store = edac_pci_instance_store +}; + +#define INSTANCE_ATTR(_name, _mode, _show, _store) \ +static struct instance_attribute attr_instance_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +}; + +INSTANCE_ATTR(pe_count, S_IRUGO, instance_pe_count_show, NULL); +INSTANCE_ATTR(npe_count, S_IRUGO, instance_npe_count_show, NULL); + +/* pci instance attributes */ +static struct instance_attribute *pci_instance_attr[] = { + &attr_instance_pe_count, + &attr_instance_npe_count, + NULL +}; + +/* the ktype for pci instance */ +static struct kobj_type ktype_pci_instance = { + .release = edac_pci_instance_release, + .sysfs_ops = &pci_instance_ops, + .default_attrs = (struct attribute **)pci_instance_attr, +}; + +static int edac_pci_create_instance_kobj(struct edac_pci_ctl_info *pci, int idx) +{ + int err; + + pci->kobj.parent = &edac_pci_kobj; + pci->kobj.ktype = &ktype_pci_instance; + + err = kobject_set_name(&pci->kobj, "pci%d", idx); + if (err) + return err; + + err = kobject_register(&pci->kobj); + if (err != 0) { + debugf2("%s() failed to register instance pci%d\n", + __func__, idx); + return err; + } + + debugf1("%s() Register instance 'pci%d' kobject\n", __func__, idx); + + return 0; +} + +static void +edac_pci_delete_instance_kobj(struct edac_pci_ctl_info *pci, int idx) +{ + init_completion(&pci->kobj_complete); + kobject_unregister(&pci->kobj); + wait_for_completion(&pci->kobj_complete); +} + +/***************************** EDAC PCI sysfs root **********************/ +#define to_edacpci(k) container_of(k, struct edac_pci_ctl_info, kobj) +#define to_edacpci_attr(a) container_of(a, struct edac_pci_attr, attr) + +static ssize_t edac_pci_int_show(void *ptr, char *buffer) +{ + int *value = ptr; + return sprintf(buffer, "%d\n", *value); +} + +static ssize_t edac_pci_int_store(void *ptr, const char *buffer, size_t count) +{ + int *value = ptr; + + if (isdigit(*buffer)) + *value = simple_strtoul(buffer, NULL, 0); + + return count; +} + +struct edac_pci_dev_attribute { + struct attribute attr; + void *value; + ssize_t(*show) (void *, char *); + ssize_t(*store) (void *, const char *, size_t); +}; + +/* Set of show/store abstract level functions for PCI Parity object */ +static ssize_t edac_pci_dev_show(struct kobject *kobj, struct attribute *attr, + char *buffer) +{ + struct edac_pci_dev_attribute *edac_pci_dev; + edac_pci_dev = (struct edac_pci_dev_attribute *)attr; + + if (edac_pci_dev->show) + return edac_pci_dev->show(edac_pci_dev->value, buffer); + return -EIO; +} + +static ssize_t edac_pci_dev_store(struct kobject *kobj, + struct attribute *attr, const char *buffer, + size_t count) +{ + struct edac_pci_dev_attribute *edac_pci_dev; + edac_pci_dev = (struct edac_pci_dev_attribute *)attr; + + if (edac_pci_dev->show) + return edac_pci_dev->store(edac_pci_dev->value, buffer, count); + return -EIO; +} + +static struct sysfs_ops edac_pci_sysfs_ops = { + .show = edac_pci_dev_show, + .store = edac_pci_dev_store +}; + +#define EDAC_PCI_ATTR(_name,_mode,_show,_store) \ +static struct edac_pci_dev_attribute edac_pci_attr_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .value = &_name, \ + .show = _show, \ + .store = _store, \ +}; + +#define EDAC_PCI_STRING_ATTR(_name,_data,_mode,_show,_store) \ +static struct edac_pci_dev_attribute edac_pci_attr_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .value = _data, \ + .show = _show, \ + .store = _store, \ +}; + +/* PCI Parity control files */ +EDAC_PCI_ATTR(check_pci_errors, S_IRUGO | S_IWUSR, edac_pci_int_show, + edac_pci_int_store); +EDAC_PCI_ATTR(edac_pci_log_pe, S_IRUGO | S_IWUSR, edac_pci_int_show, + edac_pci_int_store); +EDAC_PCI_ATTR(edac_pci_log_npe, S_IRUGO | S_IWUSR, edac_pci_int_show, + edac_pci_int_store); +EDAC_PCI_ATTR(edac_pci_panic_on_pe, S_IRUGO | S_IWUSR, edac_pci_int_show, + edac_pci_int_store); +EDAC_PCI_ATTR(pci_parity_count, S_IRUGO, edac_pci_int_show, NULL); +EDAC_PCI_ATTR(pci_nonparity_count, S_IRUGO, edac_pci_int_show, NULL); + +/* Base Attributes of the memory ECC object */ +static struct edac_pci_dev_attribute *edac_pci_attr[] = { + &edac_pci_attr_check_pci_errors, + &edac_pci_attr_edac_pci_log_pe, + &edac_pci_attr_edac_pci_log_npe, + &edac_pci_attr_edac_pci_panic_on_pe, + &edac_pci_attr_pci_parity_count, + &edac_pci_attr_pci_nonparity_count, + NULL, +}; + +/* No memory to release */ +static void edac_pci_release(struct kobject *kobj) +{ + struct edac_pci_ctl_info *pci; + + pci = to_edacpci(kobj); + + debugf1("%s()\n", __func__); + complete(&pci->kobj_complete); +} + +static struct kobj_type ktype_edac_pci = { + .release = edac_pci_release, + .sysfs_ops = &edac_pci_sysfs_ops, + .default_attrs = (struct attribute **)edac_pci_attr, +}; + +/** + * edac_sysfs_pci_setup() + * + * setup the sysfs for EDAC PCI attributes + * assumes edac_class has already been initialized + */ +int edac_pci_register_main_kobj(void) +{ + int err; + struct sysdev_class *edac_class; + + debugf1("%s()\n", __func__); + + edac_class = edac_get_edac_class(); + if (edac_class == NULL) { + debugf1("%s() no edac_class\n", __func__); + return -ENODEV; + } + + edac_pci_kobj.ktype = &ktype_edac_pci; + + edac_pci_kobj.parent = &edac_class->kset.kobj; + + err = kobject_set_name(&edac_pci_kobj, "pci"); + if (err) + return err; + + /* Instanstiate the pci object */ + /* FIXME: maybe new sysdev_create_subdir() */ + err = kobject_register(&edac_pci_kobj); + + if (err) { + debugf1("Failed to register '.../edac/pci'\n"); + return err; + } + + debugf1("Registered '.../edac/pci' kobject\n"); + + return 0; +} + +/* + * edac_pci_unregister_main_kobj() + * + * perform the sysfs teardown for the PCI attributes + */ +void edac_pci_unregister_main_kobj(void) +{ + debugf0("%s()\n", __func__); + init_completion(&edac_pci_kobj_complete); + kobject_unregister(&edac_pci_kobj); + wait_for_completion(&edac_pci_kobj_complete); +} + +int edac_pci_create_sysfs(struct edac_pci_ctl_info *pci) +{ + int err; + struct kobject *edac_kobj = &pci->kobj; + + if (atomic_inc_return(&edac_pci_sysfs_refcount) == 1) { + err = edac_pci_register_main_kobj(); + if (err) { + atomic_dec(&edac_pci_sysfs_refcount); + return err; + } + } + + err = edac_pci_create_instance_kobj(pci, pci->pci_idx); + if (err) { + if (atomic_dec_return(&edac_pci_sysfs_refcount) == 0) + edac_pci_unregister_main_kobj(); + } + + debugf0("%s() idx=%d\n", __func__, pci->pci_idx); + + err = sysfs_create_link(edac_kobj, &pci->dev->kobj, EDAC_PCI_SYMLINK); + if (err) { + debugf0("%s() sysfs_create_link() returned err= %d\n", + __func__, err); + return err; + } + + return 0; +} + +void edac_pci_remove_sysfs(struct edac_pci_ctl_info *pci) +{ + debugf0("%s()\n", __func__); + + edac_pci_delete_instance_kobj(pci, pci->pci_idx); + + sysfs_remove_link(&pci->kobj, EDAC_PCI_SYMLINK); + + if (atomic_dec_return(&edac_pci_sysfs_refcount) == 0) + edac_pci_unregister_main_kobj(); +} + +/************************ PCI error handling *************************/ +static u16 get_pci_parity_status(struct pci_dev *dev, int secondary) +{ + int where; + u16 status; + + where = secondary ? PCI_SEC_STATUS : PCI_STATUS; + pci_read_config_word(dev, where, &status); + + /* If we get back 0xFFFF then we must suspect that the card has been + * pulled but the Linux PCI layer has not yet finished cleaning up. + * We don't want to report on such devices + */ + + if (status == 0xFFFF) { + u32 sanity; + + pci_read_config_dword(dev, 0, &sanity); + + if (sanity == 0xFFFFFFFF) + return 0; + } + + status &= PCI_STATUS_DETECTED_PARITY | PCI_STATUS_SIG_SYSTEM_ERROR | + PCI_STATUS_PARITY; + + if (status) + /* reset only the bits we are interested in */ + pci_write_config_word(dev, where, status); + + return status; +} + +typedef void (*pci_parity_check_fn_t) (struct pci_dev * dev); + +/* Clear any PCI parity errors logged by this device. */ +static void edac_pci_dev_parity_clear(struct pci_dev *dev) +{ + u8 header_type; + + get_pci_parity_status(dev, 0); + + /* read the device TYPE, looking for bridges */ + pci_read_config_byte(dev, PCI_HEADER_TYPE, &header_type); + + if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) + get_pci_parity_status(dev, 1); +} + +/* + * PCI Parity polling + * + */ +static void edac_pci_dev_parity_test(struct pci_dev *dev) +{ + u16 status; + u8 header_type; + + /* read the STATUS register on this device + */ + status = get_pci_parity_status(dev, 0); + + debugf2("PCI STATUS= 0x%04x %s\n", status, dev->dev.bus_id); + + /* check the status reg for errors */ + if (status) { + if (status & (PCI_STATUS_SIG_SYSTEM_ERROR)) { + edac_printk(KERN_CRIT, EDAC_PCI, + "Signaled System Error on %s\n", + pci_name(dev)); + atomic_inc(&pci_nonparity_count); + } + + if (status & (PCI_STATUS_PARITY)) { + edac_printk(KERN_CRIT, EDAC_PCI, + "Master Data Parity Error on %s\n", + pci_name(dev)); + + atomic_inc(&pci_parity_count); + } + + if (status & (PCI_STATUS_DETECTED_PARITY)) { + edac_printk(KERN_CRIT, EDAC_PCI, + "Detected Parity Error on %s\n", + pci_name(dev)); + + atomic_inc(&pci_parity_count); + } + } + + /* read the device TYPE, looking for bridges */ + pci_read_config_byte(dev, PCI_HEADER_TYPE, &header_type); + + debugf2("PCI HEADER TYPE= 0x%02x %s\n", header_type, dev->dev.bus_id); + + if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { + /* On bridges, need to examine secondary status register */ + status = get_pci_parity_status(dev, 1); + + debugf2("PCI SEC_STATUS= 0x%04x %s\n", status, dev->dev.bus_id); + + /* check the secondary status reg for errors */ + if (status) { + if (status & (PCI_STATUS_SIG_SYSTEM_ERROR)) { + edac_printk(KERN_CRIT, EDAC_PCI, "Bridge " + "Signaled System Error on %s\n", + pci_name(dev)); + atomic_inc(&pci_nonparity_count); + } + + if (status & (PCI_STATUS_PARITY)) { + edac_printk(KERN_CRIT, EDAC_PCI, "Bridge " + "Master Data Parity Error on " + "%s\n", pci_name(dev)); + + atomic_inc(&pci_parity_count); + } + + if (status & (PCI_STATUS_DETECTED_PARITY)) { + edac_printk(KERN_CRIT, EDAC_PCI, "Bridge " + "Detected Parity Error on %s\n", + pci_name(dev)); + + atomic_inc(&pci_parity_count); + } + } + } +} + +/* + * pci_dev parity list iterator + * Scan the PCI device list for one iteration, looking for SERRORs + * Master Parity ERRORS or Parity ERRORs on primary or secondary devices + */ +static inline void edac_pci_dev_parity_iterator(pci_parity_check_fn_t fn) +{ + struct pci_dev *dev = NULL; + + /* request for kernel access to the next PCI device, if any, + * and while we are looking at it have its reference count + * bumped until we are done with it + */ + while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { + fn(dev); + } +} + +/* + * edac_pci_do_parity_check + * + * performs the actual PCI parity check operation + */ +void edac_pci_do_parity_check(void) +{ + unsigned long flags; + int before_count; + + debugf3("%s()\n", __func__); + + if (!check_pci_errors) + return; + + before_count = atomic_read(&pci_parity_count); + + /* scan all PCI devices looking for a Parity Error on devices and + * bridges + */ + local_irq_save(flags); + edac_pci_dev_parity_iterator(edac_pci_dev_parity_test); + local_irq_restore(flags); + + /* Only if operator has selected panic on PCI Error */ + if (edac_pci_get_panic_on_pe()) { + /* If the count is different 'after' from 'before' */ + if (before_count != atomic_read(&pci_parity_count)) + panic("EDAC: PCI Parity Error"); + } +} + +void edac_pci_clear_parity_errors(void) +{ + /* Clear any PCI bus parity errors that devices initially have logged + * in their registers. + */ + edac_pci_dev_parity_iterator(edac_pci_dev_parity_clear); +} +void edac_pci_handle_pe(struct edac_pci_ctl_info *pci, const char *msg) +{ + + /* global PE counter incremented by edac_pci_do_parity_check() */ + atomic_inc(&pci->counters.pe_count); + + if (edac_pci_get_log_pe()) + edac_pci_printk(pci, KERN_WARNING, + "Parity Error ctl: %s %d: %s\n", + pci->ctl_name, pci->pci_idx, msg); + + /* + * poke all PCI devices and see which one is the troublemaker + * panic() is called if set + */ + edac_pci_do_parity_check(); +} + +EXPORT_SYMBOL_GPL(edac_pci_handle_pe); + +void edac_pci_handle_npe(struct edac_pci_ctl_info *pci, const char *msg) +{ + + /* global NPE counter incremented by edac_pci_do_parity_check() */ + atomic_inc(&pci->counters.npe_count); + + if (edac_pci_get_log_npe()) + edac_pci_printk(pci, KERN_WARNING, + "Non-Parity Error ctl: %s %d: %s\n", + pci->ctl_name, pci->pci_idx, msg); + + /* + * poke all PCI devices and see which one is the troublemaker + * panic() is called if set + */ + edac_pci_do_parity_check(); +} + +EXPORT_SYMBOL_GPL(edac_pci_handle_npe); + +/* + * Define the PCI parameter to the module + */ +module_param(check_pci_errors, int, 0644); +MODULE_PARM_DESC(check_pci_errors, + "Check for PCI bus parity errors: 0=off 1=on"); +module_param(edac_pci_panic_on_pe, int, 0644); +MODULE_PARM_DESC(edac_pci_panic_on_pe, + "Panic on PCI Bus Parity error: 0=off 1=on"); + +#endif /* CONFIG_PCI */ diff --git a/drivers/edac/edac_stub.c b/drivers/edac/edac_stub.c new file mode 100644 index 0000000..20b428a --- /dev/null +++ b/drivers/edac/edac_stub.c @@ -0,0 +1,46 @@ +/* + * common EDAC components that must be in kernel + * + * Author: Dave Jiang <djiang@mvista.com> + * + * 2007 (c) MontaVista Software, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + * + */ +#include <linux/module.h> +#include <linux/edac.h> +#include <asm/atomic.h> +#include <asm/edac.h> + +int edac_op_state = EDAC_OPSTATE_INVAL; +EXPORT_SYMBOL_GPL(edac_op_state); + +atomic_t edac_handlers = ATOMIC_INIT(0); +EXPORT_SYMBOL_GPL(edac_handlers); + +int edac_err_assert = 0; +EXPORT_SYMBOL_GPL(edac_err_assert); + +/* + * called to determine if there is an EDAC driver interested in + * knowing an event (such as NMI) occurred + */ +int edac_handler_set(void) +{ + if (edac_op_state == EDAC_OPSTATE_POLL) + return 0; + + return atomic_read(&edac_handlers); +} +EXPORT_SYMBOL_GPL(edac_handler_set); + +/* + * handler for NMI type of interrupts to assert error + */ +void edac_atomic_assert_error(void) +{ + edac_err_assert++; +} +EXPORT_SYMBOL_GPL(edac_atomic_assert_error); diff --git a/drivers/edac/i3000_edac.c b/drivers/edac/i3000_edac.c new file mode 100644 index 0000000..0ecfdc4 --- /dev/null +++ b/drivers/edac/i3000_edac.c @@ -0,0 +1,506 @@ +/* + * Intel 3000/3010 Memory Controller kernel module + * Copyright (C) 2007 Akamai Technologies, Inc. + * Shamelessly copied from: + * Intel D82875P Memory Controller kernel module + * (C) 2003 Linux Networx (http://lnxi.com) + * + * This file may be distributed under the terms of the + * GNU General Public License. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/slab.h> +#include "edac_core.h" + +#define I3000_REVISION "1.1" + +#define EDAC_MOD_STR "i3000_edac" + +#define I3000_RANKS 8 +#define I3000_RANKS_PER_CHANNEL 4 +#define I3000_CHANNELS 2 + +/* Intel 3000 register addresses - device 0 function 0 - DRAM Controller */ + +#define I3000_MCHBAR 0x44 /* MCH Memory Mapped Register BAR */ +#define I3000_MCHBAR_MASK 0xffffc000 +#define I3000_MMR_WINDOW_SIZE 16384 + +#define I3000_EDEAP 0x70 /* Extended DRAM Error Address Pointer (8b) + * + * 7:1 reserved + * 0 bit 32 of address + */ +#define I3000_DEAP 0x58 /* DRAM Error Address Pointer (32b) + * + * 31:7 address + * 6:1 reserved + * 0 Error channel 0/1 + */ +#define I3000_DEAP_GRAIN (1 << 7) +#define I3000_DEAP_PFN(edeap, deap) ((((edeap) & 1) << (32 - PAGE_SHIFT)) | \ + ((deap) >> PAGE_SHIFT)) +#define I3000_DEAP_OFFSET(deap) ((deap) & ~(I3000_DEAP_GRAIN-1) & ~PAGE_MASK) +#define I3000_DEAP_CHANNEL(deap) ((deap) & 1) + +#define I3000_DERRSYN 0x5c /* DRAM Error Syndrome (8b) + * + * 7:0 DRAM ECC Syndrome + */ + +#define I3000_ERRSTS 0xc8 /* Error Status Register (16b) + * + * 15:12 reserved + * 11 MCH Thermal Sensor Event for SMI/SCI/SERR + * 10 reserved + * 9 LOCK to non-DRAM Memory Flag (LCKF) + * 8 Received Refresh Timeout Flag (RRTOF) + * 7:2 reserved + * 1 Multiple-bit DRAM ECC Error Flag (DMERR) + * 0 Single-bit DRAM ECC Error Flag (DSERR) + */ +#define I3000_ERRSTS_BITS 0x0b03 /* bits which indicate errors */ +#define I3000_ERRSTS_UE 0x0002 +#define I3000_ERRSTS_CE 0x0001 + +#define I3000_ERRCMD 0xca /* Error Command (16b) + * + * 15:12 reserved + * 11 SERR on MCH Thermal Sensor Event (TSESERR) + * 10 reserved + * 9 SERR on LOCK to non-DRAM Memory (LCKERR) + * 8 SERR on DRAM Refresh Timeout (DRTOERR) + * 7:2 reserved + * 1 SERR Multiple-Bit DRAM ECC Error (DMERR) + * 0 SERR on Single-Bit ECC Error (DSERR) + */ + +/* Intel MMIO register space - device 0 function 0 - MMR space */ + +#define I3000_DRB_SHIFT 25 /* 32MiB grain */ + +#define I3000_C0DRB 0x100 /* Channel 0 DRAM Rank Boundary (8b x 4) + * + * 7:0 Channel 0 DRAM Rank Boundary Address + */ +#define I3000_C1DRB 0x180 /* Channel 1 DRAM Rank Boundary (8b x 4) + * + * 7:0 Channel 1 DRAM Rank Boundary Address + */ + +#define I3000_C0DRA 0x108 /* Channel 0 DRAM Rank Attribute (8b x 2) + * + * 7 reserved + * 6:4 DRAM odd Rank Attribute + * 3 reserved + * 2:0 DRAM even Rank Attribute + * + * Each attribute defines the page + * size of the corresponding rank: + * 000: unpopulated + * 001: reserved + * 010: 4 KB + * 011: 8 KB + * 100: 16 KB + * Others: reserved + */ +#define I3000_C1DRA 0x188 /* Channel 1 DRAM Rank Attribute (8b x 2) */ +#define ODD_RANK_ATTRIB(dra) (((dra) & 0x70) >> 4) +#define EVEN_RANK_ATTRIB(dra) ((dra) & 0x07) + +#define I3000_C0DRC0 0x120 /* DRAM Controller Mode 0 (32b) + * + * 31:30 reserved + * 29 Initialization Complete (IC) + * 28:11 reserved + * 10:8 Refresh Mode Select (RMS) + * 7 reserved + * 6:4 Mode Select (SMS) + * 3:2 reserved + * 1:0 DRAM Type (DT) + */ + +#define I3000_C0DRC1 0x124 /* DRAM Controller Mode 1 (32b) + * + * 31 Enhanced Addressing Enable (ENHADE) + * 30:0 reserved + */ + +enum i3000p_chips { + I3000 = 0, +}; + +struct i3000_dev_info { + const char *ctl_name; +}; + +struct i3000_error_info { + u16 errsts; + u8 derrsyn; + u8 edeap; + u32 deap; + u16 errsts2; +}; + +static const struct i3000_dev_info i3000_devs[] = { + [I3000] = { + .ctl_name = "i3000"}, +}; + +static struct pci_dev *mci_pdev; +static int i3000_registered = 1; +static struct edac_pci_ctl_info *i3000_pci; + +static void i3000_get_error_info(struct mem_ctl_info *mci, + struct i3000_error_info *info) +{ + struct pci_dev *pdev; + + pdev = to_pci_dev(mci->dev); + + /* + * This is a mess because there is no atomic way to read all the + * registers at once and the registers can transition from CE being + * overwritten by UE. + */ + pci_read_config_word(pdev, I3000_ERRSTS, &info->errsts); + if (!(info->errsts & I3000_ERRSTS_BITS)) + return; + pci_read_config_byte(pdev, I3000_EDEAP, &info->edeap); + pci_read_config_dword(pdev, I3000_DEAP, &info->deap); + pci_read_config_byte(pdev, I3000_DERRSYN, &info->derrsyn); + pci_read_config_word(pdev, I3000_ERRSTS, &info->errsts2); + + /* + * If the error is the same for both reads then the first set + * of reads is valid. If there is a change then there is a CE + * with no info and the second set of reads is valid and + * should be UE info. + */ + if ((info->errsts ^ info->errsts2) & I3000_ERRSTS_BITS) { + pci_read_config_byte(pdev, I3000_EDEAP, &info->edeap); + pci_read_config_dword(pdev, I3000_DEAP, &info->deap); + pci_read_config_byte(pdev, I3000_DERRSYN, &info->derrsyn); + } + + /* Clear any error bits. + * (Yes, we really clear bits by writing 1 to them.) + */ + pci_write_bits16(pdev, I3000_ERRSTS, I3000_ERRSTS_BITS, + I3000_ERRSTS_BITS); +} + +static int i3000_process_error_info(struct mem_ctl_info *mci, + struct i3000_error_info *info, + int handle_errors) +{ + int row, multi_chan; + int pfn, offset, channel; + + multi_chan = mci->csrows[0].nr_channels - 1; + + if (!(info->errsts & I3000_ERRSTS_BITS)) + return 0; + + if (!handle_errors) + return 1; + + if ((info->errsts ^ info->errsts2) & I3000_ERRSTS_BITS) { + edac_mc_handle_ce_no_info(mci, "UE overwrote CE"); + info->errsts = info->errsts2; + } + + pfn = I3000_DEAP_PFN(info->edeap, info->deap); + offset = I3000_DEAP_OFFSET(info->deap); + channel = I3000_DEAP_CHANNEL(info->deap); + + row = edac_mc_find_csrow_by_page(mci, pfn); + + if (info->errsts & I3000_ERRSTS_UE) + edac_mc_handle_ue(mci, pfn, offset, row, "i3000 UE"); + else + edac_mc_handle_ce(mci, pfn, offset, info->derrsyn, row, + multi_chan ? channel : 0, "i3000 CE"); + + return 1; +} + +static void i3000_check(struct mem_ctl_info *mci) +{ + struct i3000_error_info info; + + debugf1("MC%d: %s()\n", mci->mc_idx, __func__); + i3000_get_error_info(mci, &info); + i3000_process_error_info(mci, &info, 1); +} + +static int i3000_is_interleaved(const unsigned char *c0dra, + const unsigned char *c1dra, + const unsigned char *c0drb, + const unsigned char *c1drb) +{ + int i; + + /* If the channels aren't populated identically then + * we're not interleaved. + */ + for (i = 0; i < I3000_RANKS_PER_CHANNEL / 2; i++) + if (ODD_RANK_ATTRIB(c0dra[i]) != ODD_RANK_ATTRIB(c1dra[i]) || + EVEN_RANK_ATTRIB(c0dra[i]) != + EVEN_RANK_ATTRIB(c1dra[i])) + return 0; + + /* If the rank boundaries for the two channels are different + * then we're not interleaved. + */ + for (i = 0; i < I3000_RANKS_PER_CHANNEL; i++) + if (c0drb[i] != c1drb[i]) + return 0; + + return 1; +} + +static int i3000_probe1(struct pci_dev *pdev, int dev_idx) +{ + int rc; + int i; + struct mem_ctl_info *mci = NULL; + unsigned long last_cumul_size; + int interleaved, nr_channels; + unsigned char dra[I3000_RANKS / 2], drb[I3000_RANKS]; + unsigned char *c0dra = dra, *c1dra = &dra[I3000_RANKS_PER_CHANNEL / 2]; + unsigned char *c0drb = drb, *c1drb = &drb[I3000_RANKS_PER_CHANNEL]; + unsigned long mchbar; + void *window; + + debugf0("MC: %s()\n", __func__); + + pci_read_config_dword(pdev, I3000_MCHBAR, (u32 *) & mchbar); + mchbar &= I3000_MCHBAR_MASK; + window = ioremap_nocache(mchbar, I3000_MMR_WINDOW_SIZE); + if (!window) { + printk(KERN_ERR "i3000: cannot map mmio space at 0x%lx\n", + mchbar); + return -ENODEV; + } + + c0dra[0] = readb(window + I3000_C0DRA + 0); /* ranks 0,1 */ + c0dra[1] = readb(window + I3000_C0DRA + 1); /* ranks 2,3 */ + c1dra[0] = readb(window + I3000_C1DRA + 0); /* ranks 0,1 */ + c1dra[1] = readb(window + I3000_C1DRA + 1); /* ranks 2,3 */ + + for (i = 0; i < I3000_RANKS_PER_CHANNEL; i++) { + c0drb[i] = readb(window + I3000_C0DRB + i); + c1drb[i] = readb(window + I3000_C1DRB + i); + } + + iounmap(window); + + /* Figure out how many channels we have. + * + * If we have what the datasheet calls "asymmetric channels" + * (essentially the same as what was called "virtual single + * channel mode" in the i82875) then it's a single channel as + * far as EDAC is concerned. + */ + interleaved = i3000_is_interleaved(c0dra, c1dra, c0drb, c1drb); + nr_channels = interleaved ? 2 : 1; + mci = edac_mc_alloc(0, I3000_RANKS / nr_channels, nr_channels, 0); + if (!mci) + return -ENOMEM; + + debugf3("MC: %s(): init mci\n", __func__); + + mci->dev = &pdev->dev; + mci->mtype_cap = MEM_FLAG_DDR2; + + mci->edac_ctl_cap = EDAC_FLAG_SECDED; + mci->edac_cap = EDAC_FLAG_SECDED; + + mci->mod_name = EDAC_MOD_STR; + mci->mod_ver = I3000_REVISION; + mci->ctl_name = i3000_devs[dev_idx].ctl_name; + mci->dev_name = pci_name(pdev); + mci->edac_check = i3000_check; + mci->ctl_page_to_phys = NULL; + + /* + * The dram rank boundary (DRB) reg values are boundary addresses + * for each DRAM rank with a granularity of 32MB. DRB regs are + * cumulative; the last one will contain the total memory + * contained in all ranks. + * + * If we're in interleaved mode then we're only walking through + * the ranks of controller 0, so we double all the values we see. + */ + for (last_cumul_size = i = 0; i < mci->nr_csrows; i++) { + u8 value; + u32 cumul_size; + struct csrow_info *csrow = &mci->csrows[i]; + + value = drb[i]; + cumul_size = value << (I3000_DRB_SHIFT - PAGE_SHIFT); + if (interleaved) + cumul_size <<= 1; + debugf3("MC: %s(): (%d) cumul_size 0x%x\n", + __func__, i, cumul_size); + if (cumul_size == last_cumul_size) { + csrow->mtype = MEM_EMPTY; + continue; + } + + csrow->first_page = last_cumul_size; + csrow->last_page = cumul_size - 1; + csrow->nr_pages = cumul_size - last_cumul_size; + last_cumul_size = cumul_size; + csrow->grain = I3000_DEAP_GRAIN; + csrow->mtype = MEM_DDR2; + csrow->dtype = DEV_UNKNOWN; + csrow->edac_mode = EDAC_UNKNOWN; + } + + /* Clear any error bits. + * (Yes, we really clear bits by writing 1 to them.) + */ + pci_write_bits16(pdev, I3000_ERRSTS, I3000_ERRSTS_BITS, + I3000_ERRSTS_BITS); + + rc = -ENODEV; + if (edac_mc_add_mc(mci)) { + debugf3("MC: %s(): failed edac_mc_add_mc()\n", __func__); + goto fail; + } + + /* allocating generic PCI control info */ + i3000_pci = edac_pci_create_generic_ctl(&pdev->dev, EDAC_MOD_STR); + if (!i3000_pci) { + printk(KERN_WARNING + "%s(): Unable to create PCI control\n", + __func__); + printk(KERN_WARNING + "%s(): PCI error report via EDAC not setup\n", + __func__); + } + + /* get this far and it's successful */ + debugf3("MC: %s(): success\n", __func__); + return 0; + + fail: + if (mci) + edac_mc_free(mci); + + return rc; +} + +/* returns count (>= 0), or negative on error */ +static int __devinit i3000_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int rc; + + debugf0("MC: %s()\n", __func__); + + if (pci_enable_device(pdev) < 0) + return -EIO; + + rc = i3000_probe1(pdev, ent->driver_data); + if (mci_pdev == NULL) + mci_pdev = pci_dev_get(pdev); + + return rc; +} + +static void __devexit i3000_remove_one(struct pci_dev *pdev) +{ + struct mem_ctl_info *mci; + + debugf0("%s()\n", __func__); + + if (i3000_pci) + edac_pci_release_generic_ctl(i3000_pci); + + if ((mci = edac_mc_del_mc(&pdev->dev)) == NULL) + return; + + edac_mc_free(mci); +} + +static const struct pci_device_id i3000_pci_tbl[] __devinitdata = { + { + PCI_VEND_DEV(INTEL, 3000_HB), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + I3000}, + { + 0, + } /* 0 terminated list. */ +}; + +MODULE_DEVICE_TABLE(pci, i3000_pci_tbl); + +static struct pci_driver i3000_driver = { + .name = EDAC_MOD_STR, + .probe = i3000_init_one, + .remove = __devexit_p(i3000_remove_one), + .id_table = i3000_pci_tbl, +}; + +static int __init i3000_init(void) +{ + int pci_rc; + + debugf3("MC: %s()\n", __func__); + pci_rc = pci_register_driver(&i3000_driver); + if (pci_rc < 0) + goto fail0; + + if (mci_pdev == NULL) { + i3000_registered = 0; + mci_pdev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_3000_HB, NULL); + if (!mci_pdev) { + debugf0("i3000 pci_get_device fail\n"); + pci_rc = -ENODEV; + goto fail1; + } + + pci_rc = i3000_init_one(mci_pdev, i3000_pci_tbl); + if (pci_rc < 0) { + debugf0("i3000 init fail\n"); + pci_rc = -ENODEV; + goto fail1; + } + } + + return 0; + +fail1: + pci_unregister_driver(&i3000_driver); + +fail0: + if (mci_pdev) + pci_dev_put(mci_pdev); + + return pci_rc; +} + +static void __exit i3000_exit(void) +{ + debugf3("MC: %s()\n", __func__); + + pci_unregister_driver(&i3000_driver); + if (!i3000_registered) { + i3000_remove_one(mci_pdev); + pci_dev_put(mci_pdev); + } +} + +module_init(i3000_init); +module_exit(i3000_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Akamai Technologies Arthur Ulfeldt/Jason Uhlenkott"); +MODULE_DESCRIPTION("MC support for Intel 3000 memory hub controllers"); diff --git a/drivers/edac/i5000_edac.c b/drivers/edac/i5000_edac.c new file mode 100644 index 0000000..96f7e63 --- /dev/null +++ b/drivers/edac/i5000_edac.c @@ -0,0 +1,1505 @@ +/* + * Intel 5000(P/V/X) class Memory Controllers kernel module + * + * This file may be distributed under the terms of the + * GNU General Public License. + * + * Written by Douglas Thompson Linux Networx (http://lnxi.com) + * norsk5@xmission.com + * + * This module is based on the following document: + * + * Intel 5000X Chipset Memory Controller Hub (MCH) - Datasheet + * http://developer.intel.com/design/chipsets/datashts/313070.htm + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/slab.h> +#include <linux/edac.h> +#include <asm/mmzone.h> + +#include "edac_core.h" + +/* + * Alter this version for the I5000 module when modifications are made + */ +#define I5000_REVISION " Ver: 2.0.12 " __DATE__ +#define EDAC_MOD_STR "i5000_edac" + +#define i5000_printk(level, fmt, arg...) \ + edac_printk(level, "i5000", fmt, ##arg) + +#define i5000_mc_printk(mci, level, fmt, arg...) \ + edac_mc_chipset_printk(mci, level, "i5000", fmt, ##arg) + +#ifndef PCI_DEVICE_ID_INTEL_FBD_0 +#define PCI_DEVICE_ID_INTEL_FBD_0 0x25F5 +#endif +#ifndef PCI_DEVICE_ID_INTEL_FBD_1 +#define PCI_DEVICE_ID_INTEL_FBD_1 0x25F6 +#endif + +/* Device 16, + * Function 0: System Address + * Function 1: Memory Branch Map, Control, Errors Register + * Function 2: FSB Error Registers + * + * All 3 functions of Device 16 (0,1,2) share the SAME DID + */ +#define PCI_DEVICE_ID_INTEL_I5000_DEV16 0x25F0 + +/* OFFSETS for Function 0 */ + +/* OFFSETS for Function 1 */ +#define AMBASE 0x48 +#define MAXCH 0x56 +#define MAXDIMMPERCH 0x57 +#define TOLM 0x6C +#define REDMEMB 0x7C +#define RED_ECC_LOCATOR(x) ((x) & 0x3FFFF) +#define REC_ECC_LOCATOR_EVEN(x) ((x) & 0x001FF) +#define REC_ECC_LOCATOR_ODD(x) ((x) & 0x3FE00) +#define MIR0 0x80 +#define MIR1 0x84 +#define MIR2 0x88 +#define AMIR0 0x8C +#define AMIR1 0x90 +#define AMIR2 0x94 + +#define FERR_FAT_FBD 0x98 +#define NERR_FAT_FBD 0x9C +#define EXTRACT_FBDCHAN_INDX(x) (((x)>>28) & 0x3) +#define FERR_FAT_FBDCHAN 0x30000000 +#define FERR_FAT_M3ERR 0x00000004 +#define FERR_FAT_M2ERR 0x00000002 +#define FERR_FAT_M1ERR 0x00000001 +#define FERR_FAT_MASK (FERR_FAT_M1ERR | \ + FERR_FAT_M2ERR | \ + FERR_FAT_M3ERR) + +#define FERR_NF_FBD 0xA0 + +/* Thermal and SPD or BFD errors */ +#define FERR_NF_M28ERR 0x01000000 +#define FERR_NF_M27ERR 0x00800000 +#define FERR_NF_M26ERR 0x00400000 +#define FERR_NF_M25ERR 0x00200000 +#define FERR_NF_M24ERR 0x00100000 +#define FERR_NF_M23ERR 0x00080000 +#define FERR_NF_M22ERR 0x00040000 +#define FERR_NF_M21ERR 0x00020000 + +/* Correctable errors */ +#define FERR_NF_M20ERR 0x00010000 +#define FERR_NF_M19ERR 0x00008000 +#define FERR_NF_M18ERR 0x00004000 +#define FERR_NF_M17ERR 0x00002000 + +/* Non-Retry or redundant Retry errors */ +#define FERR_NF_M16ERR 0x00001000 +#define FERR_NF_M15ERR 0x00000800 +#define FERR_NF_M14ERR 0x00000400 +#define FERR_NF_M13ERR 0x00000200 + +/* Uncorrectable errors */ +#define FERR_NF_M12ERR 0x00000100 +#define FERR_NF_M11ERR 0x00000080 +#define FERR_NF_M10ERR 0x00000040 +#define FERR_NF_M9ERR 0x00000020 +#define FERR_NF_M8ERR 0x00000010 +#define FERR_NF_M7ERR 0x00000008 +#define FERR_NF_M6ERR 0x00000004 +#define FERR_NF_M5ERR 0x00000002 +#define FERR_NF_M4ERR 0x00000001 + +#define FERR_NF_UNCORRECTABLE (FERR_NF_M12ERR | \ + FERR_NF_M11ERR | \ + FERR_NF_M10ERR | \ + FERR_NF_M8ERR | \ + FERR_NF_M7ERR | \ + FERR_NF_M6ERR | \ + FERR_NF_M5ERR | \ + FERR_NF_M4ERR) +#define FERR_NF_CORRECTABLE (FERR_NF_M20ERR | \ + FERR_NF_M19ERR | \ + FERR_NF_M18ERR | \ + FERR_NF_M17ERR) +#define FERR_NF_DIMM_SPARE (FERR_NF_M27ERR | \ + FERR_NF_M28ERR) +#define FERR_NF_THERMAL (FERR_NF_M26ERR | \ + FERR_NF_M25ERR | \ + FERR_NF_M24ERR | \ + FERR_NF_M23ERR) +#define FERR_NF_SPD_PROTOCOL (FERR_NF_M22ERR) +#define FERR_NF_NORTH_CRC (FERR_NF_M21ERR) +#define FERR_NF_NON_RETRY (FERR_NF_M13ERR | \ + FERR_NF_M14ERR | \ + FERR_NF_M15ERR) + +#define NERR_NF_FBD 0xA4 +#define FERR_NF_MASK (FERR_NF_UNCORRECTABLE | \ + FERR_NF_CORRECTABLE | \ + FERR_NF_DIMM_SPARE | \ + FERR_NF_THERMAL | \ + FERR_NF_SPD_PROTOCOL | \ + FERR_NF_NORTH_CRC | \ + FERR_NF_NON_RETRY) + +#define EMASK_FBD 0xA8 +#define EMASK_FBD_M28ERR 0x08000000 +#define EMASK_FBD_M27ERR 0x04000000 +#define EMASK_FBD_M26ERR 0x02000000 +#define EMASK_FBD_M25ERR 0x01000000 +#define EMASK_FBD_M24ERR 0x00800000 +#define EMASK_FBD_M23ERR 0x00400000 +#define EMASK_FBD_M22ERR 0x00200000 +#define EMASK_FBD_M21ERR 0x00100000 +#define EMASK_FBD_M20ERR 0x00080000 +#define EMASK_FBD_M19ERR 0x00040000 +#define EMASK_FBD_M18ERR 0x00020000 +#define EMASK_FBD_M17ERR 0x00010000 + +#define EMASK_FBD_M15ERR 0x00004000 +#define EMASK_FBD_M14ERR 0x00002000 +#define EMASK_FBD_M13ERR 0x00001000 +#define EMASK_FBD_M12ERR 0x00000800 +#define EMASK_FBD_M11ERR 0x00000400 +#define EMASK_FBD_M10ERR 0x00000200 +#define EMASK_FBD_M9ERR 0x00000100 +#define EMASK_FBD_M8ERR 0x00000080 +#define EMASK_FBD_M7ERR 0x00000040 +#define EMASK_FBD_M6ERR 0x00000020 +#define EMASK_FBD_M5ERR 0x00000010 +#define EMASK_FBD_M4ERR 0x00000008 +#define EMASK_FBD_M3ERR 0x00000004 +#define EMASK_FBD_M2ERR 0x00000002 +#define EMASK_FBD_M1ERR 0x00000001 + +#define ENABLE_EMASK_FBD_FATAL_ERRORS (EMASK_FBD_M1ERR | \ + EMASK_FBD_M2ERR | \ + EMASK_FBD_M3ERR) + +#define ENABLE_EMASK_FBD_UNCORRECTABLE (EMASK_FBD_M4ERR | \ + EMASK_FBD_M5ERR | \ + EMASK_FBD_M6ERR | \ + EMASK_FBD_M7ERR | \ + EMASK_FBD_M8ERR | \ + EMASK_FBD_M9ERR | \ + EMASK_FBD_M10ERR | \ + EMASK_FBD_M11ERR | \ + EMASK_FBD_M12ERR) +#define ENABLE_EMASK_FBD_CORRECTABLE (EMASK_FBD_M17ERR | \ + EMASK_FBD_M18ERR | \ + EMASK_FBD_M19ERR | \ + EMASK_FBD_M20ERR) +#define ENABLE_EMASK_FBD_DIMM_SPARE (EMASK_FBD_M27ERR | \ + EMASK_FBD_M28ERR) +#define ENABLE_EMASK_FBD_THERMALS (EMASK_FBD_M26ERR | \ + EMASK_FBD_M25ERR | \ + EMASK_FBD_M24ERR | \ + EMASK_FBD_M23ERR) +#define ENABLE_EMASK_FBD_SPD_PROTOCOL (EMASK_FBD_M22ERR) +#define ENABLE_EMASK_FBD_NORTH_CRC (EMASK_FBD_M21ERR) +#define ENABLE_EMASK_FBD_NON_RETRY (EMASK_FBD_M15ERR | \ + EMASK_FBD_M14ERR | \ + EMASK_FBD_M13ERR) + +#define ENABLE_EMASK_ALL (ENABLE_EMASK_FBD_NON_RETRY | \ + ENABLE_EMASK_FBD_NORTH_CRC | \ + ENABLE_EMASK_FBD_SPD_PROTOCOL | \ + ENABLE_EMASK_FBD_THERMALS | \ + ENABLE_EMASK_FBD_DIMM_SPARE | \ + ENABLE_EMASK_FBD_FATAL_ERRORS | \ + ENABLE_EMASK_FBD_CORRECTABLE | \ + ENABLE_EMASK_FBD_UNCORRECTABLE) + +#define ERR0_FBD 0xAC +#define ERR1_FBD 0xB0 +#define ERR2_FBD 0xB4 +#define MCERR_FBD 0xB8 +#define NRECMEMA 0xBE +#define NREC_BANK(x) (((x)>>12) & 0x7) +#define NREC_RDWR(x) (((x)>>11) & 1) +#define NREC_RANK(x) (((x)>>8) & 0x7) +#define NRECMEMB 0xC0 +#define NREC_CAS(x) (((x)>>16) & 0xFFFFFF) +#define NREC_RAS(x) ((x) & 0x7FFF) +#define NRECFGLOG 0xC4 +#define NREEECFBDA 0xC8 +#define NREEECFBDB 0xCC +#define NREEECFBDC 0xD0 +#define NREEECFBDD 0xD4 +#define NREEECFBDE 0xD8 +#define REDMEMA 0xDC +#define RECMEMA 0xE2 +#define REC_BANK(x) (((x)>>12) & 0x7) +#define REC_RDWR(x) (((x)>>11) & 1) +#define REC_RANK(x) (((x)>>8) & 0x7) +#define RECMEMB 0xE4 +#define REC_CAS(x) (((x)>>16) & 0xFFFFFF) +#define REC_RAS(x) ((x) & 0x7FFF) +#define RECFGLOG 0xE8 +#define RECFBDA 0xEC +#define RECFBDB 0xF0 +#define RECFBDC 0xF4 +#define RECFBDD 0xF8 +#define RECFBDE 0xFC + +/* OFFSETS for Function 2 */ + +/* + * Device 21, + * Function 0: Memory Map Branch 0 + * + * Device 22, + * Function 0: Memory Map Branch 1 + */ +#define PCI_DEVICE_ID_I5000_BRANCH_0 0x25F5 +#define PCI_DEVICE_ID_I5000_BRANCH_1 0x25F6 + +#define AMB_PRESENT_0 0x64 +#define AMB_PRESENT_1 0x66 +#define MTR0 0x80 +#define MTR1 0x84 +#define MTR2 0x88 +#define MTR3 0x8C + +#define NUM_MTRS 4 +#define CHANNELS_PER_BRANCH (2) + +/* Defines to extract the vaious fields from the + * MTRx - Memory Technology Registers + */ +#define MTR_DIMMS_PRESENT(mtr) ((mtr) & (0x1 << 8)) +#define MTR_DRAM_WIDTH(mtr) ((((mtr) >> 6) & 0x1) ? 8 : 4) +#define MTR_DRAM_BANKS(mtr) ((((mtr) >> 5) & 0x1) ? 8 : 4) +#define MTR_DRAM_BANKS_ADDR_BITS(mtr) ((MTR_DRAM_BANKS(mtr) == 8) ? 3 : 2) +#define MTR_DIMM_RANK(mtr) (((mtr) >> 4) & 0x1) +#define MTR_DIMM_RANK_ADDR_BITS(mtr) (MTR_DIMM_RANK(mtr) ? 2 : 1) +#define MTR_DIMM_ROWS(mtr) (((mtr) >> 2) & 0x3) +#define MTR_DIMM_ROWS_ADDR_BITS(mtr) (MTR_DIMM_ROWS(mtr) + 13) +#define MTR_DIMM_COLS(mtr) ((mtr) & 0x3) +#define MTR_DIMM_COLS_ADDR_BITS(mtr) (MTR_DIMM_COLS(mtr) + 10) + +#ifdef CONFIG_EDAC_DEBUG +static char *numrow_toString[] = { + "8,192 - 13 rows", + "16,384 - 14 rows", + "32,768 - 15 rows", + "reserved" +}; + +static char *numcol_toString[] = { + "1,024 - 10 columns", + "2,048 - 11 columns", + "4,096 - 12 columns", + "reserved" +}; +#endif + +/* Enumeration of supported devices */ +enum i5000_chips { + I5000P = 0, + I5000V = 1, /* future */ + I5000X = 2 /* future */ +}; + +/* Device name and register DID (Device ID) */ +struct i5000_dev_info { + const char *ctl_name; /* name for this device */ + u16 fsb_mapping_errors; /* DID for the branchmap,control */ +}; + +/* Table of devices attributes supported by this driver */ +static const struct i5000_dev_info i5000_devs[] = { + [I5000P] = { + .ctl_name = "I5000", + .fsb_mapping_errors = PCI_DEVICE_ID_INTEL_I5000_DEV16, + }, +}; + +struct i5000_dimm_info { + int megabytes; /* size, 0 means not present */ + int dual_rank; +}; + +#define MAX_CHANNELS 6 /* max possible channels */ +#define MAX_CSROWS (8*2) /* max possible csrows per channel */ + +/* driver private data structure */ +struct i5000_pvt { + struct pci_dev *system_address; /* 16.0 */ + struct pci_dev *branchmap_werrors; /* 16.1 */ + struct pci_dev *fsb_error_regs; /* 16.2 */ + struct pci_dev *branch_0; /* 21.0 */ + struct pci_dev *branch_1; /* 22.0 */ + + u16 tolm; /* top of low memory */ + u64 ambase; /* AMB BAR */ + + u16 mir0, mir1, mir2; + + u16 b0_mtr[NUM_MTRS]; /* Memory Technlogy Reg */ + u16 b0_ambpresent0; /* Branch 0, Channel 0 */ + u16 b0_ambpresent1; /* Brnach 0, Channel 1 */ + + u16 b1_mtr[NUM_MTRS]; /* Memory Technlogy Reg */ + u16 b1_ambpresent0; /* Branch 1, Channel 8 */ + u16 b1_ambpresent1; /* Branch 1, Channel 1 */ + + /* DIMM infomation matrix, allocating architecture maximums */ + struct i5000_dimm_info dimm_info[MAX_CSROWS][MAX_CHANNELS]; + + /* Actual values for this controller */ + int maxch; /* Max channels */ + int maxdimmperch; /* Max DIMMs per channel */ +}; + +/* I5000 MCH error information retrieved from Hardware */ +struct i5000_error_info { + + /* These registers are always read from the MC */ + u32 ferr_fat_fbd; /* First Errors Fatal */ + u32 nerr_fat_fbd; /* Next Errors Fatal */ + u32 ferr_nf_fbd; /* First Errors Non-Fatal */ + u32 nerr_nf_fbd; /* Next Errors Non-Fatal */ + + /* These registers are input ONLY if there was a Recoverable Error */ + u32 redmemb; /* Recoverable Mem Data Error log B */ + u16 recmema; /* Recoverable Mem Error log A */ + u32 recmemb; /* Recoverable Mem Error log B */ + + /* These registers are input ONLY if there was a + * Non-Recoverable Error */ + u16 nrecmema; /* Non-Recoverable Mem log A */ + u16 nrecmemb; /* Non-Recoverable Mem log B */ + +}; + +static struct edac_pci_ctl_info *i5000_pci; + +/* + * i5000_get_error_info Retrieve the hardware error information from + * the hardware and cache it in the 'info' + * structure + */ +static void i5000_get_error_info(struct mem_ctl_info *mci, + struct i5000_error_info *info) +{ + struct i5000_pvt *pvt; + u32 value; + + pvt = mci->pvt_info; + + /* read in the 1st FATAL error register */ + pci_read_config_dword(pvt->branchmap_werrors, FERR_FAT_FBD, &value); + + /* Mask only the bits that the doc says are valid + */ + value &= (FERR_FAT_FBDCHAN | FERR_FAT_MASK); + + /* If there is an error, then read in the */ + /* NEXT FATAL error register and the Memory Error Log Register A */ + if (value & FERR_FAT_MASK) { + info->ferr_fat_fbd = value; + + /* harvest the various error data we need */ + pci_read_config_dword(pvt->branchmap_werrors, + NERR_FAT_FBD, &info->nerr_fat_fbd); + pci_read_config_word(pvt->branchmap_werrors, + NRECMEMA, &info->nrecmema); + pci_read_config_word(pvt->branchmap_werrors, + NRECMEMB, &info->nrecmemb); + + /* Clear the error bits, by writing them back */ + pci_write_config_dword(pvt->branchmap_werrors, + FERR_FAT_FBD, value); + } else { + info->ferr_fat_fbd = 0; + info->nerr_fat_fbd = 0; + info->nrecmema = 0; + info->nrecmemb = 0; + } + + /* read in the 1st NON-FATAL error register */ + pci_read_config_dword(pvt->branchmap_werrors, FERR_NF_FBD, &value); + + /* If there is an error, then read in the 1st NON-FATAL error + * register as well */ + if (value & FERR_NF_MASK) { + info->ferr_nf_fbd = value; + + /* harvest the various error data we need */ + pci_read_config_dword(pvt->branchmap_werrors, + NERR_NF_FBD, &info->nerr_nf_fbd); + pci_read_config_word(pvt->branchmap_werrors, + RECMEMA, &info->recmema); + pci_read_config_dword(pvt->branchmap_werrors, + RECMEMB, &info->recmemb); + pci_read_config_dword(pvt->branchmap_werrors, + REDMEMB, &info->redmemb); + + /* Clear the error bits, by writing them back */ + pci_write_config_dword(pvt->branchmap_werrors, + FERR_NF_FBD, value); + } else { + info->ferr_nf_fbd = 0; + info->nerr_nf_fbd = 0; + info->recmema = 0; + info->recmemb = 0; + info->redmemb = 0; + } +} + +/* + * i5000_process_fatal_error_info(struct mem_ctl_info *mci, + * struct i5000_error_info *info, + * int handle_errors); + * + * handle the Intel FATAL errors, if any + */ +static void i5000_process_fatal_error_info(struct mem_ctl_info *mci, + struct i5000_error_info *info, + int handle_errors) +{ + char msg[EDAC_MC_LABEL_LEN + 1 + 90]; + u32 allErrors; + int branch; + int channel; + int bank; + int rank; + int rdwr; + int ras, cas; + + /* mask off the Error bits that are possible */ + allErrors = (info->ferr_fat_fbd & FERR_FAT_MASK); + if (!allErrors) + return; /* if no error, return now */ + + /* ONLY ONE of the possible error bits will be set, as per the docs */ + i5000_mc_printk(mci, KERN_ERR, + "FATAL ERRORS Found!!! 1st FATAL Err Reg= 0x%x\n", + allErrors); + + branch = EXTRACT_FBDCHAN_INDX(info->ferr_fat_fbd); + channel = branch; + + /* Use the NON-Recoverable macros to extract data */ + bank = NREC_BANK(info->nrecmema); + rank = NREC_RANK(info->nrecmema); + rdwr = NREC_RDWR(info->nrecmema); + ras = NREC_RAS(info->nrecmemb); + cas = NREC_CAS(info->nrecmemb); + + debugf0("\t\tCSROW= %d Channels= %d,%d (Branch= %d " + "DRAM Bank= %d rdwr= %s ras= %d cas= %d)\n", + rank, channel, channel + 1, branch >> 1, bank, + rdwr ? "Write" : "Read", ras, cas); + + /* Only 1 bit will be on */ + if (allErrors & FERR_FAT_M1ERR) { + i5000_mc_printk(mci, KERN_ERR, + "Alert on non-redundant retry or fast " + "reset timeout\n"); + + } else if (allErrors & FERR_FAT_M2ERR) { + i5000_mc_printk(mci, KERN_ERR, + "Northbound CRC error on non-redundant " + "retry\n"); + + } else if (allErrors & FERR_FAT_M3ERR) { + i5000_mc_printk(mci, KERN_ERR, + ">Tmid Thermal event with intelligent " + "throttling disabled\n"); + } + + /* Form out message */ + snprintf(msg, sizeof(msg), + "(Branch=%d DRAM-Bank=%d RDWR=%s RAS=%d CAS=%d " + "FATAL Err=0x%x)", + branch >> 1, bank, rdwr ? "Write" : "Read", ras, cas, + allErrors); + + /* Call the helper to output message */ + edac_mc_handle_fbd_ue(mci, rank, channel, channel + 1, msg); +} + +/* + * i5000_process_fatal_error_info(struct mem_ctl_info *mci, + * struct i5000_error_info *info, + * int handle_errors); + * + * handle the Intel NON-FATAL errors, if any + */ +static void i5000_process_nonfatal_error_info(struct mem_ctl_info *mci, + struct i5000_error_info *info, + int handle_errors) +{ + char msg[EDAC_MC_LABEL_LEN + 1 + 90]; + u32 allErrors; + u32 ue_errors; + u32 ce_errors; + u32 misc_errors; + int branch; + int channel; + int bank; + int rank; + int rdwr; + int ras, cas; + + /* mask off the Error bits that are possible */ + allErrors = (info->ferr_nf_fbd & FERR_NF_MASK); + if (!allErrors) + return; /* if no error, return now */ + + /* ONLY ONE of the possible error bits will be set, as per the docs */ + i5000_mc_printk(mci, KERN_WARNING, + "NON-FATAL ERRORS Found!!! 1st NON-FATAL Err " + "Reg= 0x%x\n", allErrors); + + ue_errors = allErrors & FERR_NF_UNCORRECTABLE; + if (ue_errors) { + debugf0("\tUncorrected bits= 0x%x\n", ue_errors); + + branch = EXTRACT_FBDCHAN_INDX(info->ferr_nf_fbd); + channel = branch; + bank = NREC_BANK(info->nrecmema); + rank = NREC_RANK(info->nrecmema); + rdwr = NREC_RDWR(info->nrecmema); + ras = NREC_RAS(info->nrecmemb); + cas = NREC_CAS(info->nrecmemb); + + debugf0 + ("\t\tCSROW= %d Channels= %d,%d (Branch= %d " + "DRAM Bank= %d rdwr= %s ras= %d cas= %d)\n", + rank, channel, channel + 1, branch >> 1, bank, + rdwr ? "Write" : "Read", ras, cas); + + /* Form out message */ + snprintf(msg, sizeof(msg), + "(Branch=%d DRAM-Bank=%d RDWR=%s RAS=%d " + "CAS=%d, UE Err=0x%x)", + branch >> 1, bank, rdwr ? "Write" : "Read", ras, cas, + ue_errors); + + /* Call the helper to output message */ + edac_mc_handle_fbd_ue(mci, rank, channel, channel + 1, msg); + } + + /* Check correctable errors */ + ce_errors = allErrors & FERR_NF_CORRECTABLE; + if (ce_errors) { + debugf0("\tCorrected bits= 0x%x\n", ce_errors); + + branch = EXTRACT_FBDCHAN_INDX(info->ferr_nf_fbd); + + channel = 0; + if (REC_ECC_LOCATOR_ODD(info->redmemb)) + channel = 1; + + /* Convert channel to be based from zero, instead of + * from branch base of 0 */ + channel += branch; + + bank = REC_BANK(info->recmema); + rank = REC_RANK(info->recmema); + rdwr = REC_RDWR(info->recmema); + ras = REC_RAS(info->recmemb); + cas = REC_CAS(info->recmemb); + + debugf0("\t\tCSROW= %d Channel= %d (Branch %d " + "DRAM Bank= %d rdwr= %s ras= %d cas= %d)\n", + rank, channel, branch >> 1, bank, + rdwr ? "Write" : "Read", ras, cas); + + /* Form out message */ + snprintf(msg, sizeof(msg), + "(Branch=%d DRAM-Bank=%d RDWR=%s RAS=%d " + "CAS=%d, CE Err=0x%x)", branch >> 1, bank, + rdwr ? "Write" : "Read", ras, cas, ce_errors); + + /* Call the helper to output message */ + edac_mc_handle_fbd_ce(mci, rank, channel, msg); + } + + /* See if any of the thermal errors have fired */ + misc_errors = allErrors & FERR_NF_THERMAL; + if (misc_errors) { + i5000_printk(KERN_WARNING, "\tTHERMAL Error, bits= 0x%x\n", + misc_errors); + } + + /* See if any of the thermal errors have fired */ + misc_errors = allErrors & FERR_NF_NON_RETRY; + if (misc_errors) { + i5000_printk(KERN_WARNING, "\tNON-Retry Errors, bits= 0x%x\n", + misc_errors); + } + + /* See if any of the thermal errors have fired */ + misc_errors = allErrors & FERR_NF_NORTH_CRC; + if (misc_errors) { + i5000_printk(KERN_WARNING, + "\tNORTHBOUND CRC Error, bits= 0x%x\n", + misc_errors); + } + + /* See if any of the thermal errors have fired */ + misc_errors = allErrors & FERR_NF_SPD_PROTOCOL; + if (misc_errors) { + i5000_printk(KERN_WARNING, + "\tSPD Protocol Error, bits= 0x%x\n", + misc_errors); + } + + /* See if any of the thermal errors have fired */ + misc_errors = allErrors & FERR_NF_DIMM_SPARE; + if (misc_errors) { + i5000_printk(KERN_WARNING, "\tDIMM-Spare Error, bits= 0x%x\n", + misc_errors); + } +} + +/* + * i5000_process_error_info Process the error info that is + * in the 'info' structure, previously retrieved from hardware + */ +static void i5000_process_error_info(struct mem_ctl_info *mci, + struct i5000_error_info *info, + int handle_errors) +{ + /* First handle any fatal errors that occurred */ + i5000_process_fatal_error_info(mci, info, handle_errors); + + /* now handle any non-fatal errors that occurred */ + i5000_process_nonfatal_error_info(mci, info, handle_errors); +} + +/* + * i5000_clear_error Retrieve any error from the hardware + * but do NOT process that error. + * Used for 'clearing' out of previous errors + * Called by the Core module. + */ +static void i5000_clear_error(struct mem_ctl_info *mci) +{ + struct i5000_error_info info; + + i5000_get_error_info(mci, &info); +} + +/* + * i5000_check_error Retrieve and process errors reported by the + * hardware. Called by the Core module. + */ +static void i5000_check_error(struct mem_ctl_info *mci) +{ + struct i5000_error_info info; + debugf4("MC%d: " __FILE__ ": %s()\n", mci->mc_idx, __func__); + i5000_get_error_info(mci, &info); + i5000_process_error_info(mci, &info, 1); +} + +/* + * i5000_get_devices Find and perform 'get' operation on the MCH's + * device/functions we want to reference for this driver + * + * Need to 'get' device 16 func 1 and func 2 + */ +static int i5000_get_devices(struct mem_ctl_info *mci, int dev_idx) +{ + //const struct i5000_dev_info *i5000_dev = &i5000_devs[dev_idx]; + struct i5000_pvt *pvt; + struct pci_dev *pdev; + + pvt = mci->pvt_info; + + /* Attempt to 'get' the MCH register we want */ + pdev = NULL; + while (1) { + pdev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_I5000_DEV16, pdev); + + /* End of list, leave */ + if (pdev == NULL) { + i5000_printk(KERN_ERR, + "'system address,Process Bus' " + "device not found:" + "vendor 0x%x device 0x%x FUNC 1 " + "(broken BIOS?)\n", + PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_I5000_DEV16); + + return 1; + } + + /* Scan for device 16 func 1 */ + if (PCI_FUNC(pdev->devfn) == 1) + break; + } + + pvt->branchmap_werrors = pdev; + + /* Attempt to 'get' the MCH register we want */ + pdev = NULL; + while (1) { + pdev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_I5000_DEV16, pdev); + + if (pdev == NULL) { + i5000_printk(KERN_ERR, + "MC: 'branchmap,control,errors' " + "device not found:" + "vendor 0x%x device 0x%x Func 2 " + "(broken BIOS?)\n", + PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_I5000_DEV16); + + pci_dev_put(pvt->branchmap_werrors); + return 1; + } + + /* Scan for device 16 func 1 */ + if (PCI_FUNC(pdev->devfn) == 2) + break; + } + + pvt->fsb_error_regs = pdev; + + debugf1("System Address, processor bus- PCI Bus ID: %s %x:%x\n", + pci_name(pvt->system_address), + pvt->system_address->vendor, pvt->system_address->device); + debugf1("Branchmap, control and errors - PCI Bus ID: %s %x:%x\n", + pci_name(pvt->branchmap_werrors), + pvt->branchmap_werrors->vendor, pvt->branchmap_werrors->device); + debugf1("FSB Error Regs - PCI Bus ID: %s %x:%x\n", + pci_name(pvt->fsb_error_regs), + pvt->fsb_error_regs->vendor, pvt->fsb_error_regs->device); + + pdev = NULL; + pdev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_I5000_BRANCH_0, pdev); + + if (pdev == NULL) { + i5000_printk(KERN_ERR, + "MC: 'BRANCH 0' device not found:" + "vendor 0x%x device 0x%x Func 0 (broken BIOS?)\n", + PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_I5000_BRANCH_0); + + pci_dev_put(pvt->branchmap_werrors); + pci_dev_put(pvt->fsb_error_regs); + return 1; + } + + pvt->branch_0 = pdev; + + /* If this device claims to have more than 2 channels then + * fetch Branch 1's information + */ + if (pvt->maxch >= CHANNELS_PER_BRANCH) { + pdev = NULL; + pdev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_I5000_BRANCH_1, pdev); + + if (pdev == NULL) { + i5000_printk(KERN_ERR, + "MC: 'BRANCH 1' device not found:" + "vendor 0x%x device 0x%x Func 0 " + "(broken BIOS?)\n", + PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_I5000_BRANCH_1); + + pci_dev_put(pvt->branchmap_werrors); + pci_dev_put(pvt->fsb_error_regs); + pci_dev_put(pvt->branch_0); + return 1; + } + + pvt->branch_1 = pdev; + } + + return 0; +} + +/* + * i5000_put_devices 'put' all the devices that we have + * reserved via 'get' + */ +static void i5000_put_devices(struct mem_ctl_info *mci) +{ + struct i5000_pvt *pvt; + + pvt = mci->pvt_info; + + pci_dev_put(pvt->branchmap_werrors); /* FUNC 1 */ + pci_dev_put(pvt->fsb_error_regs); /* FUNC 2 */ + pci_dev_put(pvt->branch_0); /* DEV 21 */ + + /* Only if more than 2 channels do we release the second branch */ + if (pvt->maxch >= CHANNELS_PER_BRANCH) + pci_dev_put(pvt->branch_1); /* DEV 22 */ +} + +/* + * determine_amb_resent + * + * the information is contained in NUM_MTRS different registers + * determineing which of the NUM_MTRS requires knowing + * which channel is in question + * + * 2 branches, each with 2 channels + * b0_ambpresent0 for channel '0' + * b0_ambpresent1 for channel '1' + * b1_ambpresent0 for channel '2' + * b1_ambpresent1 for channel '3' + */ +static int determine_amb_present_reg(struct i5000_pvt *pvt, int channel) +{ + int amb_present; + + if (channel < CHANNELS_PER_BRANCH) { + if (channel & 0x1) + amb_present = pvt->b0_ambpresent1; + else + amb_present = pvt->b0_ambpresent0; + } else { + if (channel & 0x1) + amb_present = pvt->b1_ambpresent1; + else + amb_present = pvt->b1_ambpresent0; + } + + return amb_present; +} + +/* + * determine_mtr(pvt, csrow, channel) + * + * return the proper MTR register as determine by the csrow and channel desired + */ +static int determine_mtr(struct i5000_pvt *pvt, int csrow, int channel) +{ + int mtr; + + if (channel < CHANNELS_PER_BRANCH) + mtr = pvt->b0_mtr[csrow >> 1]; + else + mtr = pvt->b1_mtr[csrow >> 1]; + + return mtr; +} + +/* + */ +static void decode_mtr(int slot_row, u16 mtr) +{ + int ans; + + ans = MTR_DIMMS_PRESENT(mtr); + + debugf2("\tMTR%d=0x%x: DIMMs are %s\n", slot_row, mtr, + ans ? "Present" : "NOT Present"); + if (!ans) + return; + + debugf2("\t\tWIDTH: x%d\n", MTR_DRAM_WIDTH(mtr)); + debugf2("\t\tNUMBANK: %d bank(s)\n", MTR_DRAM_BANKS(mtr)); + debugf2("\t\tNUMRANK: %s\n", MTR_DIMM_RANK(mtr) ? "double" : "single"); + debugf2("\t\tNUMROW: %s\n", numrow_toString[MTR_DIMM_ROWS(mtr)]); + debugf2("\t\tNUMCOL: %s\n", numcol_toString[MTR_DIMM_COLS(mtr)]); +} + +static void handle_channel(struct i5000_pvt *pvt, int csrow, int channel, + struct i5000_dimm_info *dinfo) +{ + int mtr; + int amb_present_reg; + int addrBits; + + mtr = determine_mtr(pvt, csrow, channel); + if (MTR_DIMMS_PRESENT(mtr)) { + amb_present_reg = determine_amb_present_reg(pvt, channel); + + /* Determine if there is a DIMM present in this DIMM slot */ + if (amb_present_reg & (1 << (csrow >> 1))) { + dinfo->dual_rank = MTR_DIMM_RANK(mtr); + + if (!((dinfo->dual_rank == 0) && + ((csrow & 0x1) == 0x1))) { + /* Start with the number of bits for a Bank + * on the DRAM */ + addrBits = MTR_DRAM_BANKS_ADDR_BITS(mtr); + /* Add thenumber of ROW bits */ + addrBits += MTR_DIMM_ROWS_ADDR_BITS(mtr); + /* add the number of COLUMN bits */ + addrBits += MTR_DIMM_COLS_ADDR_BITS(mtr); + + addrBits += 6; /* add 64 bits per DIMM */ + addrBits -= 20; /* divide by 2^^20 */ + addrBits -= 3; /* 8 bits per bytes */ + + dinfo->megabytes = 1 << addrBits; + } + } + } +} + +/* + * calculate_dimm_size + * + * also will output a DIMM matrix map, if debug is enabled, for viewing + * how the DIMMs are populated + */ +static void calculate_dimm_size(struct i5000_pvt *pvt) +{ + struct i5000_dimm_info *dinfo; + int csrow, max_csrows; + char *p, *mem_buffer; + int space, n; + int channel; + + /* ================= Generate some debug output ================= */ + space = PAGE_SIZE; + mem_buffer = p = kmalloc(space, GFP_KERNEL); + if (p == NULL) { + i5000_printk(KERN_ERR, "MC: %s:%s() kmalloc() failed\n", + __FILE__, __func__); + return; + } + + n = snprintf(p, space, "\n"); + p += n; + space -= n; + + /* Scan all the actual CSROWS (which is # of DIMMS * 2) + * and calculate the information for each DIMM + * Start with the highest csrow first, to display it first + * and work toward the 0th csrow + */ + max_csrows = pvt->maxdimmperch * 2; + for (csrow = max_csrows - 1; csrow >= 0; csrow--) { + + /* on an odd csrow, first output a 'boundary' marker, + * then reset the message buffer */ + if (csrow & 0x1) { + n = snprintf(p, space, "---------------------------" + "--------------------------------"); + p += n; + space -= n; + debugf2("%s\n", mem_buffer); + p = mem_buffer; + space = PAGE_SIZE; + } + n = snprintf(p, space, "csrow %2d ", csrow); + p += n; + space -= n; + + for (channel = 0; channel < pvt->maxch; channel++) { + dinfo = &pvt->dimm_info[csrow][channel]; + handle_channel(pvt, csrow, channel, dinfo); + n = snprintf(p, space, "%4d MB | ", dinfo->megabytes); + p += n; + space -= n; + } + n = snprintf(p, space, "\n"); + p += n; + space -= n; + } + + /* Output the last bottom 'boundary' marker */ + n = snprintf(p, space, "---------------------------" + "--------------------------------\n"); + p += n; + space -= n; + + /* now output the 'channel' labels */ + n = snprintf(p, space, " "); + p += n; + space -= n; + for (channel = 0; channel < pvt->maxch; channel++) { + n = snprintf(p, space, "channel %d | ", channel); + p += n; + space -= n; + } + n = snprintf(p, space, "\n"); + p += n; + space -= n; + + /* output the last message and free buffer */ + debugf2("%s\n", mem_buffer); + kfree(mem_buffer); +} + +/* + * i5000_get_mc_regs read in the necessary registers and + * cache locally + * + * Fills in the private data members + */ +static void i5000_get_mc_regs(struct mem_ctl_info *mci) +{ + struct i5000_pvt *pvt; + u32 actual_tolm; + u16 limit; + int slot_row; + int maxch; + int maxdimmperch; + int way0, way1; + + pvt = mci->pvt_info; + + pci_read_config_dword(pvt->system_address, AMBASE, + (u32 *) & pvt->ambase); + pci_read_config_dword(pvt->system_address, AMBASE + sizeof(u32), + ((u32 *) & pvt->ambase) + sizeof(u32)); + + maxdimmperch = pvt->maxdimmperch; + maxch = pvt->maxch; + + debugf2("AMBASE= 0x%lx MAXCH= %d MAX-DIMM-Per-CH= %d\n", + (long unsigned int)pvt->ambase, pvt->maxch, pvt->maxdimmperch); + + /* Get the Branch Map regs */ + pci_read_config_word(pvt->branchmap_werrors, TOLM, &pvt->tolm); + pvt->tolm >>= 12; + debugf2("\nTOLM (number of 256M regions) =%u (0x%x)\n", pvt->tolm, + pvt->tolm); + + actual_tolm = pvt->tolm << 28; + debugf2("Actual TOLM byte addr=%u (0x%x)\n", actual_tolm, actual_tolm); + + pci_read_config_word(pvt->branchmap_werrors, MIR0, &pvt->mir0); + pci_read_config_word(pvt->branchmap_werrors, MIR1, &pvt->mir1); + pci_read_config_word(pvt->branchmap_werrors, MIR2, &pvt->mir2); + + /* Get the MIR[0-2] regs */ + limit = (pvt->mir0 >> 4) & 0x0FFF; + way0 = pvt->mir0 & 0x1; + way1 = pvt->mir0 & 0x2; + debugf2("MIR0: limit= 0x%x WAY1= %u WAY0= %x\n", limit, way1, way0); + limit = (pvt->mir1 >> 4) & 0x0FFF; + way0 = pvt->mir1 & 0x1; + way1 = pvt->mir1 & 0x2; + debugf2("MIR1: limit= 0x%x WAY1= %u WAY0= %x\n", limit, way1, way0); + limit = (pvt->mir2 >> 4) & 0x0FFF; + way0 = pvt->mir2 & 0x1; + way1 = pvt->mir2 & 0x2; + debugf2("MIR2: limit= 0x%x WAY1= %u WAY0= %x\n", limit, way1, way0); + + /* Get the MTR[0-3] regs */ + for (slot_row = 0; slot_row < NUM_MTRS; slot_row++) { + int where = MTR0 + (slot_row * sizeof(u32)); + + pci_read_config_word(pvt->branch_0, where, + &pvt->b0_mtr[slot_row]); + + debugf2("MTR%d where=0x%x B0 value=0x%x\n", slot_row, where, + pvt->b0_mtr[slot_row]); + + if (pvt->maxch >= CHANNELS_PER_BRANCH) { + pci_read_config_word(pvt->branch_1, where, + &pvt->b1_mtr[slot_row]); + debugf2("MTR%d where=0x%x B1 value=0x%x\n", slot_row, + where, pvt->b0_mtr[slot_row]); + } else { + pvt->b1_mtr[slot_row] = 0; + } + } + + /* Read and dump branch 0's MTRs */ + debugf2("\nMemory Technology Registers:\n"); + debugf2(" Branch 0:\n"); + for (slot_row = 0; slot_row < NUM_MTRS; slot_row++) { + decode_mtr(slot_row, pvt->b0_mtr[slot_row]); + } + pci_read_config_word(pvt->branch_0, AMB_PRESENT_0, + &pvt->b0_ambpresent0); + debugf2("\t\tAMB-Branch 0-present0 0x%x:\n", pvt->b0_ambpresent0); + pci_read_config_word(pvt->branch_0, AMB_PRESENT_1, + &pvt->b0_ambpresent1); + debugf2("\t\tAMB-Branch 0-present1 0x%x:\n", pvt->b0_ambpresent1); + + /* Only if we have 2 branchs (4 channels) */ + if (pvt->maxch < CHANNELS_PER_BRANCH) { + pvt->b1_ambpresent0 = 0; + pvt->b1_ambpresent1 = 0; + } else { + /* Read and dump branch 1's MTRs */ + debugf2(" Branch 1:\n"); + for (slot_row = 0; slot_row < NUM_MTRS; slot_row++) { + decode_mtr(slot_row, pvt->b1_mtr[slot_row]); + } + pci_read_config_word(pvt->branch_1, AMB_PRESENT_0, + &pvt->b1_ambpresent0); + debugf2("\t\tAMB-Branch 1-present0 0x%x:\n", + pvt->b1_ambpresent0); + pci_read_config_word(pvt->branch_1, AMB_PRESENT_1, + &pvt->b1_ambpresent1); + debugf2("\t\tAMB-Branch 1-present1 0x%x:\n", + pvt->b1_ambpresent1); + } + + /* Go and determine the size of each DIMM and place in an + * orderly matrix */ + calculate_dimm_size(pvt); +} + +/* + * i5000_init_csrows Initialize the 'csrows' table within + * the mci control structure with the + * addressing of memory. + * + * return: + * 0 success + * 1 no actual memory found on this MC + */ +static int i5000_init_csrows(struct mem_ctl_info *mci) +{ + struct i5000_pvt *pvt; + struct csrow_info *p_csrow; + int empty, channel_count; + int max_csrows; + int mtr; + int csrow_megs; + int channel; + int csrow; + + pvt = mci->pvt_info; + + channel_count = pvt->maxch; + max_csrows = pvt->maxdimmperch * 2; + + empty = 1; /* Assume NO memory */ + + for (csrow = 0; csrow < max_csrows; csrow++) { + p_csrow = &mci->csrows[csrow]; + + p_csrow->csrow_idx = csrow; + + /* use branch 0 for the basis */ + mtr = pvt->b0_mtr[csrow >> 1]; + + /* if no DIMMS on this row, continue */ + if (!MTR_DIMMS_PRESENT(mtr)) + continue; + + /* FAKE OUT VALUES, FIXME */ + p_csrow->first_page = 0 + csrow * 20; + p_csrow->last_page = 9 + csrow * 20; + p_csrow->page_mask = 0xFFF; + + p_csrow->grain = 8; + + csrow_megs = 0; + for (channel = 0; channel < pvt->maxch; channel++) { + csrow_megs += pvt->dimm_info[csrow][channel].megabytes; + } + + p_csrow->nr_pages = csrow_megs << 8; + + /* Assume DDR2 for now */ + p_csrow->mtype = MEM_FB_DDR2; + + /* ask what device type on this row */ + if (MTR_DRAM_WIDTH(mtr)) + p_csrow->dtype = DEV_X8; + else + p_csrow->dtype = DEV_X4; + + p_csrow->edac_mode = EDAC_S8ECD8ED; + + empty = 0; + } + + return empty; +} + +/* + * i5000_enable_error_reporting + * Turn on the memory reporting features of the hardware + */ +static void i5000_enable_error_reporting(struct mem_ctl_info *mci) +{ + struct i5000_pvt *pvt; + u32 fbd_error_mask; + + pvt = mci->pvt_info; + + /* Read the FBD Error Mask Register */ + pci_read_config_dword(pvt->branchmap_werrors, EMASK_FBD, + &fbd_error_mask); + + /* Enable with a '0' */ + fbd_error_mask &= ~(ENABLE_EMASK_ALL); + + pci_write_config_dword(pvt->branchmap_werrors, EMASK_FBD, + fbd_error_mask); +} + +/* + * i5000_get_dimm_and_channel_counts(pdev, &num_csrows, &num_channels) + * + * ask the device how many channels are present and how many CSROWS + * as well + */ +static void i5000_get_dimm_and_channel_counts(struct pci_dev *pdev, + int *num_dimms_per_channel, + int *num_channels) +{ + u8 value; + + /* Need to retrieve just how many channels and dimms per channel are + * supported on this memory controller + */ + pci_read_config_byte(pdev, MAXDIMMPERCH, &value); + *num_dimms_per_channel = (int)value *2; + + pci_read_config_byte(pdev, MAXCH, &value); + *num_channels = (int)value; +} + +/* + * i5000_probe1 Probe for ONE instance of device to see if it is + * present. + * return: + * 0 for FOUND a device + * < 0 for error code + */ +static int i5000_probe1(struct pci_dev *pdev, int dev_idx) +{ + struct mem_ctl_info *mci; + struct i5000_pvt *pvt; + int num_channels; + int num_dimms_per_channel; + int num_csrows; + + debugf0("MC: " __FILE__ ": %s(), pdev bus %u dev=0x%x fn=0x%x\n", + __func__, + pdev->bus->number, + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn)); + + /* We only are looking for func 0 of the set */ + if (PCI_FUNC(pdev->devfn) != 0) + return -ENODEV; + + /* make sure error reporting method is sane */ + switch (edac_op_state) { + case EDAC_OPSTATE_POLL: + case EDAC_OPSTATE_NMI: + break; + default: + edac_op_state = EDAC_OPSTATE_POLL; + break; + } + + /* Ask the devices for the number of CSROWS and CHANNELS so + * that we can calculate the memory resources, etc + * + * The Chipset will report what it can handle which will be greater + * or equal to what the motherboard manufacturer will implement. + * + * As we don't have a motherboard identification routine to determine + * actual number of slots/dimms per channel, we thus utilize the + * resource as specified by the chipset. Thus, we might have + * have more DIMMs per channel than actually on the mobo, but this + * allows the driver to support upto the chipset max, without + * some fancy mobo determination. + */ + i5000_get_dimm_and_channel_counts(pdev, &num_dimms_per_channel, + &num_channels); + num_csrows = num_dimms_per_channel * 2; + + debugf0("MC: %s(): Number of - Channels= %d DIMMS= %d CSROWS= %d\n", + __func__, num_channels, num_dimms_per_channel, num_csrows); + + /* allocate a new MC control structure */ + mci = edac_mc_alloc(sizeof(*pvt), num_csrows, num_channels, 0); + + if (mci == NULL) + return -ENOMEM; + + debugf0("MC: " __FILE__ ": %s(): mci = %p\n", __func__, mci); + + mci->dev = &pdev->dev; /* record ptr to the generic device */ + + pvt = mci->pvt_info; + pvt->system_address = pdev; /* Record this device in our private */ + pvt->maxch = num_channels; + pvt->maxdimmperch = num_dimms_per_channel; + + /* 'get' the pci devices we want to reserve for our use */ + if (i5000_get_devices(mci, dev_idx)) + goto fail0; + + /* Time to get serious */ + i5000_get_mc_regs(mci); /* retrieve the hardware registers */ + + mci->mc_idx = 0; + mci->mtype_cap = MEM_FLAG_FB_DDR2; + mci->edac_ctl_cap = EDAC_FLAG_NONE; + mci->edac_cap = EDAC_FLAG_NONE; + mci->mod_name = "i5000_edac.c"; + mci->mod_ver = I5000_REVISION; + mci->ctl_name = i5000_devs[dev_idx].ctl_name; + mci->dev_name = pci_name(pdev); + mci->ctl_page_to_phys = NULL; + + /* Set the function pointer to an actual operation function */ + mci->edac_check = i5000_check_error; + + /* initialize the MC control structure 'csrows' table + * with the mapping and control information */ + if (i5000_init_csrows(mci)) { + debugf0("MC: Setting mci->edac_cap to EDAC_FLAG_NONE\n" + " because i5000_init_csrows() returned nonzero " + "value\n"); + mci->edac_cap = EDAC_FLAG_NONE; /* no csrows found */ + } else { + debugf1("MC: Enable error reporting now\n"); + i5000_enable_error_reporting(mci); + } + + /* add this new MC control structure to EDAC's list of MCs */ + if (edac_mc_add_mc(mci)) { + debugf0("MC: " __FILE__ + ": %s(): failed edac_mc_add_mc()\n", __func__); + /* FIXME: perhaps some code should go here that disables error + * reporting if we just enabled it + */ + goto fail1; + } + + i5000_clear_error(mci); + + /* allocating generic PCI control info */ + i5000_pci = edac_pci_create_generic_ctl(&pdev->dev, EDAC_MOD_STR); + if (!i5000_pci) { + printk(KERN_WARNING + "%s(): Unable to create PCI control\n", + __func__); + printk(KERN_WARNING + "%s(): PCI error report via EDAC not setup\n", + __func__); + } + + return 0; + + /* Error exit unwinding stack */ +fail1: + + i5000_put_devices(mci); + +fail0: + edac_mc_free(mci); + return -ENODEV; +} + +/* + * i5000_init_one constructor for one instance of device + * + * returns: + * negative on error + * count (>= 0) + */ +static int __devinit i5000_init_one(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int rc; + + debugf0("MC: " __FILE__ ": %s()\n", __func__); + + /* wake up device */ + rc = pci_enable_device(pdev); + if (rc == -EIO) + return rc; + + /* now probe and enable the device */ + return i5000_probe1(pdev, id->driver_data); +} + +/* + * i5000_remove_one destructor for one instance of device + * + */ +static void __devexit i5000_remove_one(struct pci_dev *pdev) +{ + struct mem_ctl_info *mci; + + debugf0(__FILE__ ": %s()\n", __func__); + + if (i5000_pci) + edac_pci_release_generic_ctl(i5000_pci); + + if ((mci = edac_mc_del_mc(&pdev->dev)) == NULL) + return; + + /* retrieve references to resources, and free those resources */ + i5000_put_devices(mci); + + edac_mc_free(mci); +} + +/* + * pci_device_id table for which devices we are looking for + * + * The "E500P" device is the first device supported. + */ +static const struct pci_device_id i5000_pci_tbl[] __devinitdata = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_I5000_DEV16), + .driver_data = I5000P}, + + {0,} /* 0 terminated list. */ +}; + +MODULE_DEVICE_TABLE(pci, i5000_pci_tbl); + +/* + * i5000_driver pci_driver structure for this module + * + */ +static struct pci_driver i5000_driver = { + .name = __stringify(KBUILD_BASENAME), + .probe = i5000_init_one, + .remove = __devexit_p(i5000_remove_one), + .id_table = i5000_pci_tbl, +}; + +/* + * i5000_init Module entry function + * Try to initialize this module for its devices + */ +static int __init i5000_init(void) +{ + int pci_rc; + + debugf2("MC: " __FILE__ ": %s()\n", __func__); + + pci_rc = pci_register_driver(&i5000_driver); + + return (pci_rc < 0) ? pci_rc : 0; +} + +/* + * i5000_exit() Module exit function + * Unregister the driver + */ +static void __exit i5000_exit(void) +{ + debugf2("MC: " __FILE__ ": %s()\n", __func__); + pci_unregister_driver(&i5000_driver); +} + +module_init(i5000_init); +module_exit(i5000_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR + ("Linux Networx (http://lnxi.com) Doug Thompson <norsk5@xmission.com>"); +MODULE_DESCRIPTION("MC Driver for Intel I5000 memory controllers - " + I5000_REVISION); +module_param(edac_op_state, int, 0444); +MODULE_PARM_DESC(edac_op_state, "EDAC Error Reporting state: 0=Poll,1=NMI"); diff --git a/drivers/edac/i82443bxgx_edac.c b/drivers/edac/i82443bxgx_edac.c new file mode 100644 index 0000000..83bfe37 --- /dev/null +++ b/drivers/edac/i82443bxgx_edac.c @@ -0,0 +1,402 @@ +/* + * Intel 82443BX/GX (440BX/GX chipset) Memory Controller EDAC kernel + * module (C) 2006 Tim Small + * + * This file may be distributed under the terms of the GNU General + * Public License. + * + * Written by Tim Small <tim@buttersideup.com>, based on work by Linux + * Networx, Thayne Harbaugh, Dan Hollis <goemon at anime dot net> and + * others. + * + * 440GX fix by Jason Uhlenkott <juhlenko@akamai.com>. + * + * Written with reference to 82443BX Host Bridge Datasheet: + * http://www.intel.com/design/chipsets/440/documentation.htm + * references to this document given in []. + * + * This module doesn't support the 440LX, but it may be possible to + * make it do so (the 440LX's register definitions are different, but + * not completely so - I haven't studied them in enough detail to know + * how easy this would be). + */ + +#include <linux/module.h> +#include <linux/init.h> + +#include <linux/pci.h> +#include <linux/pci_ids.h> + +#include <linux/slab.h> + +#include "edac_core.h" + +#define I82443_REVISION "0.1" + +#define EDAC_MOD_STR "i82443bxgx_edac" + +/* The 82443BX supports SDRAM, or EDO (EDO for mobile only), "Memory + * Size: 8 MB to 512 MB (1GB with Registered DIMMs) with eight memory + * rows" "The 82443BX supports multiple-bit error detection and + * single-bit error correction when ECC mode is enabled and + * single/multi-bit error detection when correction is disabled. + * During writes to the DRAM, the 82443BX generates ECC for the data + * on a QWord basis. Partial QWord writes require a read-modify-write + * cycle when ECC is enabled." +*/ + +/* "Additionally, the 82443BX ensures that the data is corrected in + * main memory so that accumulation of errors is prevented. Another + * error within the same QWord would result in a double-bit error + * which is unrecoverable. This is known as hardware scrubbing since + * it requires no software intervention to correct the data in memory." + */ + +/* [Also see page 100 (section 4.3), "DRAM Interface"] + * [Also see page 112 (section 4.6.1.4), ECC] + */ + +#define I82443BXGX_NR_CSROWS 8 +#define I82443BXGX_NR_CHANS 1 +#define I82443BXGX_NR_DIMMS 4 + +/* 82443 PCI Device 0 */ +#define I82443BXGX_NBXCFG 0x50 /* 32bit register starting at this PCI + * config space offset */ +#define I82443BXGX_NBXCFG_OFFSET_NON_ECCROW 24 /* Array of bits, zero if + * row is non-ECC */ +#define I82443BXGX_NBXCFG_OFFSET_DRAM_FREQ 12 /* 2 bits,00=100MHz,10=66 MHz */ + +#define I82443BXGX_NBXCFG_OFFSET_DRAM_INTEGRITY 7 /* 2 bits: */ +#define I82443BXGX_NBXCFG_INTEGRITY_NONE 0x0 /* 00 = Non-ECC */ +#define I82443BXGX_NBXCFG_INTEGRITY_EC 0x1 /* 01 = EC (only) */ +#define I82443BXGX_NBXCFG_INTEGRITY_ECC 0x2 /* 10 = ECC */ +#define I82443BXGX_NBXCFG_INTEGRITY_SCRUB 0x3 /* 11 = ECC + HW Scrub */ + +#define I82443BXGX_NBXCFG_OFFSET_ECC_DIAG_ENABLE 6 + +/* 82443 PCI Device 0 */ +#define I82443BXGX_EAP 0x80 /* 32bit register starting at this PCI + * config space offset, Error Address + * Pointer Register */ +#define I82443BXGX_EAP_OFFSET_EAP 12 /* High 20 bits of error address */ +#define I82443BXGX_EAP_OFFSET_MBE BIT(1) /* Err at EAP was multi-bit (W1TC) */ +#define I82443BXGX_EAP_OFFSET_SBE BIT(0) /* Err at EAP was single-bit (W1TC) */ + +#define I82443BXGX_ERRCMD 0x90 /* 8bit register starting at this PCI + * config space offset. */ +#define I82443BXGX_ERRCMD_OFFSET_SERR_ON_MBE BIT(1) /* 1 = enable */ +#define I82443BXGX_ERRCMD_OFFSET_SERR_ON_SBE BIT(0) /* 1 = enable */ + +#define I82443BXGX_ERRSTS 0x91 /* 16bit register starting at this PCI + * config space offset. */ +#define I82443BXGX_ERRSTS_OFFSET_MBFRE 5 /* 3 bits - first err row multibit */ +#define I82443BXGX_ERRSTS_OFFSET_MEF BIT(4) /* 1 = MBE occurred */ +#define I82443BXGX_ERRSTS_OFFSET_SBFRE 1 /* 3 bits - first err row singlebit */ +#define I82443BXGX_ERRSTS_OFFSET_SEF BIT(0) /* 1 = SBE occurred */ + +#define I82443BXGX_DRAMC 0x57 /* 8bit register starting at this PCI + * config space offset. */ +#define I82443BXGX_DRAMC_OFFSET_DT 3 /* 2 bits, DRAM Type */ +#define I82443BXGX_DRAMC_DRAM_IS_EDO 0 /* 00 = EDO */ +#define I82443BXGX_DRAMC_DRAM_IS_SDRAM 1 /* 01 = SDRAM */ +#define I82443BXGX_DRAMC_DRAM_IS_RSDRAM 2 /* 10 = Registered SDRAM */ + +#define I82443BXGX_DRB 0x60 /* 8x 8bit registers starting at this PCI + * config space offset. */ + +/* FIXME - don't poll when ECC disabled? */ + +struct i82443bxgx_edacmc_error_info { + u32 eap; +}; + +static struct edac_pci_ctl_info *i82443bxgx_pci; + +static void i82443bxgx_edacmc_get_error_info(struct mem_ctl_info *mci, + struct i82443bxgx_edacmc_error_info + *info) +{ + struct pci_dev *pdev; + pdev = to_pci_dev(mci->dev); + pci_read_config_dword(pdev, I82443BXGX_EAP, &info->eap); + if (info->eap & I82443BXGX_EAP_OFFSET_SBE) + /* Clear error to allow next error to be reported [p.61] */ + pci_write_bits32(pdev, I82443BXGX_EAP, + I82443BXGX_EAP_OFFSET_SBE, + I82443BXGX_EAP_OFFSET_SBE); + + if (info->eap & I82443BXGX_EAP_OFFSET_MBE) + /* Clear error to allow next error to be reported [p.61] */ + pci_write_bits32(pdev, I82443BXGX_EAP, + I82443BXGX_EAP_OFFSET_MBE, + I82443BXGX_EAP_OFFSET_MBE); +} + +static int i82443bxgx_edacmc_process_error_info(struct mem_ctl_info *mci, + struct + i82443bxgx_edacmc_error_info + *info, int handle_errors) +{ + int error_found = 0; + u32 eapaddr, page, pageoffset; + + /* bits 30:12 hold the 4kb block in which the error occurred + * [p.61] */ + eapaddr = (info->eap & 0xfffff000); + page = eapaddr >> PAGE_SHIFT; + pageoffset = eapaddr - (page << PAGE_SHIFT); + + if (info->eap & I82443BXGX_EAP_OFFSET_SBE) { + error_found = 1; + if (handle_errors) + edac_mc_handle_ce(mci, page, pageoffset, + /* 440BX/GX don't make syndrome information + * available */ + 0, edac_mc_find_csrow_by_page(mci, page), 0, + mci->ctl_name); + } + + if (info->eap & I82443BXGX_EAP_OFFSET_MBE) { + error_found = 1; + if (handle_errors) + edac_mc_handle_ue(mci, page, pageoffset, + edac_mc_find_csrow_by_page(mci, page), + mci->ctl_name); + } + + return error_found; +} + +static void i82443bxgx_edacmc_check(struct mem_ctl_info *mci) +{ + struct i82443bxgx_edacmc_error_info info; + + debugf1("MC%d: " __FILE__ ": %s()\n", mci->mc_idx, __func__); + i82443bxgx_edacmc_get_error_info(mci, &info); + i82443bxgx_edacmc_process_error_info(mci, &info, 1); +} + +static void i82443bxgx_init_csrows(struct mem_ctl_info *mci, + struct pci_dev *pdev, + enum edac_type edac_mode, + enum mem_type mtype) +{ + struct csrow_info *csrow; + int index; + u8 drbar, dramc; + u32 row_base, row_high_limit, row_high_limit_last; + + pci_read_config_byte(pdev, I82443BXGX_DRAMC, &dramc); + row_high_limit_last = 0; + for (index = 0; index < mci->nr_csrows; index++) { + csrow = &mci->csrows[index]; + pci_read_config_byte(pdev, I82443BXGX_DRB + index, &drbar); + debugf1("MC%d: " __FILE__ ": %s() Row=%d DRB = %#0x\n", + mci->mc_idx, __func__, index, drbar); + row_high_limit = ((u32) drbar << 23); + /* find the DRAM Chip Select Base address and mask */ + debugf1("MC%d: " __FILE__ ": %s() Row=%d, " + "Boundry Address=%#0x, Last = %#0x \n", + mci->mc_idx, __func__, index, row_high_limit, + row_high_limit_last); + + /* 440GX goes to 2GB, represented with a DRB of 0. */ + if (row_high_limit_last && !row_high_limit) + row_high_limit = 1UL << 31; + + /* This row is empty [p.49] */ + if (row_high_limit == row_high_limit_last) + continue; + row_base = row_high_limit_last; + csrow->first_page = row_base >> PAGE_SHIFT; + csrow->last_page = (row_high_limit >> PAGE_SHIFT) - 1; + csrow->nr_pages = csrow->last_page - csrow->first_page + 1; + /* EAP reports in 4kilobyte granularity [61] */ + csrow->grain = 1 << 12; + csrow->mtype = mtype; + /* I don't think 440BX can tell you device type? FIXME? */ + csrow->dtype = DEV_UNKNOWN; + /* Mode is global to all rows on 440BX */ + csrow->edac_mode = edac_mode; + row_high_limit_last = row_high_limit; + } +} + +static int i82443bxgx_edacmc_probe1(struct pci_dev *pdev, int dev_idx) +{ + struct mem_ctl_info *mci; + u8 dramc; + u32 nbxcfg, ecc_mode; + enum mem_type mtype; + enum edac_type edac_mode; + + debugf0("MC: " __FILE__ ": %s()\n", __func__); + + /* Something is really hosed if PCI config space reads from + * the MC aren't working. + */ + if (pci_read_config_dword(pdev, I82443BXGX_NBXCFG, &nbxcfg)) + return -EIO; + + mci = edac_mc_alloc(0, I82443BXGX_NR_CSROWS, I82443BXGX_NR_CHANS, 0); + + if (mci == NULL) + return -ENOMEM; + + debugf0("MC: " __FILE__ ": %s(): mci = %p\n", __func__, mci); + mci->dev = &pdev->dev; + mci->mtype_cap = MEM_FLAG_EDO | MEM_FLAG_SDR | MEM_FLAG_RDR; + mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_EC | EDAC_FLAG_SECDED; + pci_read_config_byte(pdev, I82443BXGX_DRAMC, &dramc); + switch ((dramc >> I82443BXGX_DRAMC_OFFSET_DT) & (BIT(0) | BIT(1))) { + case I82443BXGX_DRAMC_DRAM_IS_EDO: + mtype = MEM_EDO; + break; + case I82443BXGX_DRAMC_DRAM_IS_SDRAM: + mtype = MEM_SDR; + break; + case I82443BXGX_DRAMC_DRAM_IS_RSDRAM: + mtype = MEM_RDR; + break; + default: + debugf0("Unknown/reserved DRAM type value " + "in DRAMC register!\n"); + mtype = -MEM_UNKNOWN; + } + + if ((mtype == MEM_SDR) || (mtype == MEM_RDR)) + mci->edac_cap = mci->edac_ctl_cap; + else + mci->edac_cap = EDAC_FLAG_NONE; + + mci->scrub_cap = SCRUB_FLAG_HW_SRC; + pci_read_config_dword(pdev, I82443BXGX_NBXCFG, &nbxcfg); + ecc_mode = ((nbxcfg >> I82443BXGX_NBXCFG_OFFSET_DRAM_INTEGRITY) & + (BIT(0) | BIT(1))); + + mci->scrub_mode = (ecc_mode == I82443BXGX_NBXCFG_INTEGRITY_SCRUB) + ? SCRUB_HW_SRC : SCRUB_NONE; + + switch (ecc_mode) { + case I82443BXGX_NBXCFG_INTEGRITY_NONE: + edac_mode = EDAC_NONE; + break; + case I82443BXGX_NBXCFG_INTEGRITY_EC: + edac_mode = EDAC_EC; + break; + case I82443BXGX_NBXCFG_INTEGRITY_ECC: + case I82443BXGX_NBXCFG_INTEGRITY_SCRUB: + edac_mode = EDAC_SECDED; + break; + default: + debugf0("%s(): Unknown/reserved ECC state " + "in NBXCFG register!\n", __func__); + edac_mode = EDAC_UNKNOWN; + break; + } + + i82443bxgx_init_csrows(mci, pdev, edac_mode, mtype); + + /* Many BIOSes don't clear error flags on boot, so do this + * here, or we get "phantom" errors occuring at module-load + * time. */ + pci_write_bits32(pdev, I82443BXGX_EAP, + (I82443BXGX_EAP_OFFSET_SBE | + I82443BXGX_EAP_OFFSET_MBE), + (I82443BXGX_EAP_OFFSET_SBE | + I82443BXGX_EAP_OFFSET_MBE)); + + mci->mod_name = EDAC_MOD_STR; + mci->mod_ver = I82443_REVISION; + mci->ctl_name = "I82443BXGX"; + mci->dev_name = pci_name(pdev); + mci->edac_check = i82443bxgx_edacmc_check; + mci->ctl_page_to_phys = NULL; + + if (edac_mc_add_mc(mci)) { + debugf3("%s(): failed edac_mc_add_mc()\n", __func__); + goto fail; + } + + /* allocating generic PCI control info */ + i82443bxgx_pci = edac_pci_create_generic_ctl(&pdev->dev, EDAC_MOD_STR); + if (!i82443bxgx_pci) { + printk(KERN_WARNING + "%s(): Unable to create PCI control\n", + __func__); + printk(KERN_WARNING + "%s(): PCI error report via EDAC not setup\n", + __func__); + } + + debugf3("MC: " __FILE__ ": %s(): success\n", __func__); + return 0; + +fail: + edac_mc_free(mci); + return -ENODEV; +} + +EXPORT_SYMBOL_GPL(i82443bxgx_edacmc_probe1); + +/* returns count (>= 0), or negative on error */ +static int __devinit i82443bxgx_edacmc_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + debugf0("MC: " __FILE__ ": %s()\n", __func__); + + /* don't need to call pci_device_enable() */ + return i82443bxgx_edacmc_probe1(pdev, ent->driver_data); +} + +static void __devexit i82443bxgx_edacmc_remove_one(struct pci_dev *pdev) +{ + struct mem_ctl_info *mci; + + debugf0(__FILE__ ": %s()\n", __func__); + + if (i82443bxgx_pci) + edac_pci_release_generic_ctl(i82443bxgx_pci); + + if ((mci = edac_mc_del_mc(&pdev->dev)) == NULL) + return; + + edac_mc_free(mci); +} + +EXPORT_SYMBOL_GPL(i82443bxgx_edacmc_remove_one); + +static const struct pci_device_id i82443bxgx_pci_tbl[] __devinitdata = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443BX_0)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443BX_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443GX_0)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443GX_2)}, + {0,} /* 0 terminated list. */ +}; + +MODULE_DEVICE_TABLE(pci, i82443bxgx_pci_tbl); + +static struct pci_driver i82443bxgx_edacmc_driver = { + .name = EDAC_MOD_STR, + .probe = i82443bxgx_edacmc_init_one, + .remove = __devexit_p(i82443bxgx_edacmc_remove_one), + .id_table = i82443bxgx_pci_tbl, +}; + +static int __init i82443bxgx_edacmc_init(void) +{ + return pci_register_driver(&i82443bxgx_edacmc_driver); +} + +static void __exit i82443bxgx_edacmc_exit(void) +{ + pci_unregister_driver(&i82443bxgx_edacmc_driver); +} + +module_init(i82443bxgx_edacmc_init); +module_exit(i82443bxgx_edacmc_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tim Small <tim@buttersideup.com> - WPAD"); +MODULE_DESCRIPTION("EDAC MC support for Intel 82443BX/GX memory controllers"); diff --git a/drivers/edac/i82860_edac.c b/drivers/edac/i82860_edac.c index e4bb298..f5ecd2c 100644 --- a/drivers/edac/i82860_edac.c +++ b/drivers/edac/i82860_edac.c @@ -14,9 +14,9 @@ #include <linux/pci.h> #include <linux/pci_ids.h> #include <linux/slab.h> -#include "edac_mc.h" +#include "edac_core.h" -#define I82860_REVISION " Ver: 2.0.1 " __DATE__ +#define I82860_REVISION " Ver: 2.0.2 " __DATE__ #define EDAC_MOD_STR "i82860_edac" #define i82860_printk(level, fmt, arg...) \ @@ -54,16 +54,16 @@ struct i82860_error_info { static const struct i82860_dev_info i82860_devs[] = { [I82860] = { - .ctl_name = "i82860" - }, + .ctl_name = "i82860"}, }; -static struct pci_dev *mci_pdev = NULL; /* init dev: in case that AGP code +static struct pci_dev *mci_pdev; /* init dev: in case that AGP code * has already registered driver */ +static struct edac_pci_ctl_info *i82860_pci; static void i82860_get_error_info(struct mem_ctl_info *mci, - struct i82860_error_info *info) + struct i82860_error_info *info) { struct pci_dev *pdev; @@ -91,13 +91,13 @@ static void i82860_get_error_info(struct mem_ctl_info *mci, if ((info->errsts ^ info->errsts2) & 0x0003) { pci_read_config_dword(pdev, I82860_EAP, &info->eap); - pci_read_config_word(pdev, I82860_DERRCTL_STS, - &info->derrsyn); + pci_read_config_word(pdev, I82860_DERRCTL_STS, &info->derrsyn); } } static int i82860_process_error_info(struct mem_ctl_info *mci, - struct i82860_error_info *info, int handle_errors) + struct i82860_error_info *info, + int handle_errors) { int row; @@ -136,7 +136,7 @@ static void i82860_check(struct mem_ctl_info *mci) static void i82860_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev) { unsigned long last_cumul_size; - u16 mchcfg_ddim; /* DRAM Data Integrity Mode 0=none, 2=edac */ + u16 mchcfg_ddim; /* DRAM Data Integrity Mode 0=none, 2=edac */ u16 value; u32 cumul_size; struct csrow_info *csrow; @@ -155,7 +155,7 @@ static void i82860_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev) csrow = &mci->csrows[index]; pci_read_config_word(pdev, I82860_GBA + index * 2, &value); cumul_size = (value & I82860_GBA_MASK) << - (I82860_GBA_SHIFT - PAGE_SHIFT); + (I82860_GBA_SHIFT - PAGE_SHIFT); debugf3("%s(): (%d) cumul_size 0x%x\n", __func__, index, cumul_size); @@ -186,7 +186,7 @@ static int i82860_probe1(struct pci_dev *pdev, int dev_idx) the channel and the GRA registers map to physical devices so we are going to make 1 channel for group. */ - mci = edac_mc_alloc(0, 16, 1); + mci = edac_mc_alloc(0, 16, 1, 0); if (!mci) return -ENOMEM; @@ -200,19 +200,31 @@ static int i82860_probe1(struct pci_dev *pdev, int dev_idx) mci->mod_name = EDAC_MOD_STR; mci->mod_ver = I82860_REVISION; mci->ctl_name = i82860_devs[dev_idx].ctl_name; + mci->dev_name = pci_name(pdev); mci->edac_check = i82860_check; mci->ctl_page_to_phys = NULL; i82860_init_csrows(mci, pdev); - i82860_get_error_info(mci, &discard); /* clear counters */ + i82860_get_error_info(mci, &discard); /* clear counters */ /* Here we assume that we will never see multiple instances of this * type of memory controller. The ID is therefore hardcoded to 0. */ - if (edac_mc_add_mc(mci,0)) { + if (edac_mc_add_mc(mci)) { debugf3("%s(): failed edac_mc_add_mc()\n", __func__); goto fail; } + /* allocating generic PCI control info */ + i82860_pci = edac_pci_create_generic_ctl(&pdev->dev, EDAC_MOD_STR); + if (!i82860_pci) { + printk(KERN_WARNING + "%s(): Unable to create PCI control\n", + __func__); + printk(KERN_WARNING + "%s(): PCI error report via EDAC not setup\n", + __func__); + } + /* get this far and it's successful */ debugf3("%s(): success\n", __func__); @@ -225,7 +237,7 @@ fail: /* returns count (>= 0), or negative on error */ static int __devinit i82860_init_one(struct pci_dev *pdev, - const struct pci_device_id *ent) + const struct pci_device_id *ent) { int rc; @@ -249,6 +261,9 @@ static void __devexit i82860_remove_one(struct pci_dev *pdev) debugf0("%s()\n", __func__); + if (i82860_pci) + edac_pci_release_generic_ctl(i82860_pci); + if ((mci = edac_mc_del_mc(&pdev->dev)) == NULL) return; @@ -257,12 +272,11 @@ static void __devexit i82860_remove_one(struct pci_dev *pdev) static const struct pci_device_id i82860_pci_tbl[] __devinitdata = { { - PCI_VEND_DEV(INTEL, 82860_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, - I82860 - }, + PCI_VEND_DEV(INTEL, 82860_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + I82860}, { - 0, - } /* 0 terminated list. */ + 0, + } /* 0 terminated list. */ }; MODULE_DEVICE_TABLE(pci, i82860_pci_tbl); @@ -329,5 +343,5 @@ module_exit(i82860_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Red Hat Inc. (http://www.redhat.com) " - "Ben Woodard <woodard@redhat.com>"); + "Ben Woodard <woodard@redhat.com>"); MODULE_DESCRIPTION("ECC support for Intel 82860 memory hub controllers"); diff --git a/drivers/edac/i82875p_edac.c b/drivers/edac/i82875p_edac.c index 2800b3e..031abad 100644 --- a/drivers/edac/i82875p_edac.c +++ b/drivers/edac/i82875p_edac.c @@ -18,9 +18,9 @@ #include <linux/pci.h> #include <linux/pci_ids.h> #include <linux/slab.h> -#include "edac_mc.h" +#include "edac_core.h" -#define I82875P_REVISION " Ver: 2.0.1 " __DATE__ +#define I82875P_REVISION " Ver: 2.0.2 " __DATE__ #define EDAC_MOD_STR "i82875p_edac" #define i82875p_printk(level, fmt, arg...) \ @@ -174,18 +174,19 @@ struct i82875p_error_info { static const struct i82875p_dev_info i82875p_devs[] = { [I82875P] = { - .ctl_name = "i82875p" - }, + .ctl_name = "i82875p"}, }; -static struct pci_dev *mci_pdev = NULL; /* init dev: in case that AGP code has +static struct pci_dev *mci_pdev; /* init dev: in case that AGP code has * already registered driver */ static int i82875p_registered = 1; +static struct edac_pci_ctl_info *i82875p_pci; + static void i82875p_get_error_info(struct mem_ctl_info *mci, - struct i82875p_error_info *info) + struct i82875p_error_info *info) { struct pci_dev *pdev; @@ -197,38 +198,39 @@ static void i82875p_get_error_info(struct mem_ctl_info *mci, * overwritten by UE. */ pci_read_config_word(pdev, I82875P_ERRSTS, &info->errsts); + + if (!(info->errsts & 0x0081)) + return; + pci_read_config_dword(pdev, I82875P_EAP, &info->eap); pci_read_config_byte(pdev, I82875P_DES, &info->des); pci_read_config_byte(pdev, I82875P_DERRSYN, &info->derrsyn); pci_read_config_word(pdev, I82875P_ERRSTS, &info->errsts2); - pci_write_bits16(pdev, I82875P_ERRSTS, 0x0081, 0x0081); - /* * If the error is the same then we can for both reads then * the first set of reads is valid. If there is a change then * there is a CE no info and the second set of reads is valid * and should be UE info. */ - if (!(info->errsts2 & 0x0081)) - return; - if ((info->errsts ^ info->errsts2) & 0x0081) { pci_read_config_dword(pdev, I82875P_EAP, &info->eap); pci_read_config_byte(pdev, I82875P_DES, &info->des); - pci_read_config_byte(pdev, I82875P_DERRSYN, - &info->derrsyn); + pci_read_config_byte(pdev, I82875P_DERRSYN, &info->derrsyn); } + + pci_write_bits16(pdev, I82875P_ERRSTS, 0x0081, 0x0081); } static int i82875p_process_error_info(struct mem_ctl_info *mci, - struct i82875p_error_info *info, int handle_errors) + struct i82875p_error_info *info, + int handle_errors) { int row, multi_chan; multi_chan = mci->csrows[0].nr_channels - 1; - if (!(info->errsts2 & 0x0081)) + if (!(info->errsts & 0x0081)) return 0; if (!handle_errors) @@ -263,10 +265,12 @@ static void i82875p_check(struct mem_ctl_info *mci) /* Return 0 on success or 1 on failure. */ static int i82875p_setup_overfl_dev(struct pci_dev *pdev, - struct pci_dev **ovrfl_pdev, void __iomem **ovrfl_window) + struct pci_dev **ovrfl_pdev, + void __iomem **ovrfl_window) { struct pci_dev *dev; void __iomem *window; + int err; *ovrfl_pdev = NULL; *ovrfl_window = NULL; @@ -284,14 +288,19 @@ static int i82875p_setup_overfl_dev(struct pci_dev *pdev, if (dev == NULL) return 1; - pci_bus_add_device(dev); + err = pci_bus_add_device(dev); + if (err) { + i82875p_printk(KERN_ERR, + "%s(): pci_bus_add_device() Failed\n", + __func__); + } } *ovrfl_pdev = dev; if (pci_enable_device(dev)) { i82875p_printk(KERN_ERR, "%s(): Failed to enable overflow " - "device\n", __func__); + "device\n", __func__); return 1; } @@ -307,7 +316,7 @@ static int i82875p_setup_overfl_dev(struct pci_dev *pdev, if (window == NULL) { i82875p_printk(KERN_ERR, "%s(): Failed to ioremap bar6\n", - __func__); + __func__); goto fail1; } @@ -325,21 +334,20 @@ fail0: return 1; } - /* Return 1 if dual channel mode is active. Else return 0. */ static inline int dual_channel_active(u32 drc) { return (drc >> 21) & 0x1; } - static void i82875p_init_csrows(struct mem_ctl_info *mci, - struct pci_dev *pdev, void __iomem *ovrfl_window, u32 drc) + struct pci_dev *pdev, + void __iomem * ovrfl_window, u32 drc) { struct csrow_info *csrow; unsigned long last_cumul_size; u8 value; - u32 drc_ddim; /* DRAM Data Integrity Mode 0=none,2=edac */ + u32 drc_ddim; /* DRAM Data Integrity Mode 0=none,2=edac */ u32 cumul_size; int index; @@ -392,7 +400,7 @@ static int i82875p_probe1(struct pci_dev *pdev, int dev_idx) drc = readl(ovrfl_window + I82875P_DRC); nr_chans = dual_channel_active(drc) + 1; mci = edac_mc_alloc(sizeof(*pvt), I82875P_NR_CSROWS(nr_chans), - nr_chans); + nr_chans, 0); if (!mci) { rc = -ENOMEM; @@ -407,23 +415,35 @@ static int i82875p_probe1(struct pci_dev *pdev, int dev_idx) mci->mod_name = EDAC_MOD_STR; mci->mod_ver = I82875P_REVISION; mci->ctl_name = i82875p_devs[dev_idx].ctl_name; + mci->dev_name = pci_name(pdev); mci->edac_check = i82875p_check; mci->ctl_page_to_phys = NULL; debugf3("%s(): init pvt\n", __func__); - pvt = (struct i82875p_pvt *) mci->pvt_info; + pvt = (struct i82875p_pvt *)mci->pvt_info; pvt->ovrfl_pdev = ovrfl_pdev; pvt->ovrfl_window = ovrfl_window; i82875p_init_csrows(mci, pdev, ovrfl_window, drc); - i82875p_get_error_info(mci, &discard); /* clear counters */ + i82875p_get_error_info(mci, &discard); /* clear counters */ /* Here we assume that we will never see multiple instances of this * type of memory controller. The ID is therefore hardcoded to 0. */ - if (edac_mc_add_mc(mci,0)) { + if (edac_mc_add_mc(mci)) { debugf3("%s(): failed edac_mc_add_mc()\n", __func__); goto fail1; } + /* allocating generic PCI control info */ + i82875p_pci = edac_pci_create_generic_ctl(&pdev->dev, EDAC_MOD_STR); + if (!i82875p_pci) { + printk(KERN_WARNING + "%s(): Unable to create PCI control\n", + __func__); + printk(KERN_WARNING + "%s(): PCI error report via EDAC not setup\n", + __func__); + } + /* get this far and it's successful */ debugf3("%s(): success\n", __func__); return 0; @@ -442,7 +462,7 @@ fail0: /* returns count (>= 0), or negative on error */ static int __devinit i82875p_init_one(struct pci_dev *pdev, - const struct pci_device_id *ent) + const struct pci_device_id *ent) { int rc; @@ -467,10 +487,13 @@ static void __devexit i82875p_remove_one(struct pci_dev *pdev) debugf0("%s()\n", __func__); + if (i82875p_pci) + edac_pci_release_generic_ctl(i82875p_pci); + if ((mci = edac_mc_del_mc(&pdev->dev)) == NULL) return; - pvt = (struct i82875p_pvt *) mci->pvt_info; + pvt = (struct i82875p_pvt *)mci->pvt_info; if (pvt->ovrfl_window) iounmap(pvt->ovrfl_window); @@ -488,12 +511,11 @@ static void __devexit i82875p_remove_one(struct pci_dev *pdev) static const struct pci_device_id i82875p_pci_tbl[] __devinitdata = { { - PCI_VEND_DEV(INTEL, 82875_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, - I82875P - }, + PCI_VEND_DEV(INTEL, 82875_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + I82875P}, { - 0, - } /* 0 terminated list. */ + 0, + } /* 0 terminated list. */ }; MODULE_DEVICE_TABLE(pci, i82875p_pci_tbl); @@ -517,7 +539,7 @@ static int __init i82875p_init(void) if (mci_pdev == NULL) { mci_pdev = pci_get_device(PCI_VENDOR_ID_INTEL, - PCI_DEVICE_ID_INTEL_82875_0, NULL); + PCI_DEVICE_ID_INTEL_82875_0, NULL); if (!mci_pdev) { debugf0("875p pci_get_device fail\n"); diff --git a/drivers/edac/i82975x_edac.c b/drivers/edac/i82975x_edac.c new file mode 100644 index 0000000..0ee8884 --- /dev/null +++ b/drivers/edac/i82975x_edac.c @@ -0,0 +1,666 @@ +/* + * Intel 82975X Memory Controller kernel module + * (C) 2007 aCarLab (India) Pvt. Ltd. (http://acarlab.com) + * (C) 2007 jetzbroadband (http://jetzbroadband.com) + * This file may be distributed under the terms of the + * GNU General Public License. + * + * Written by Arvind R. + * Copied from i82875p_edac.c source: + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/slab.h> + +#include "edac_core.h" + +#define I82975X_REVISION " Ver: 1.0.0 " __DATE__ +#define EDAC_MOD_STR "i82975x_edac" + +#define i82975x_printk(level, fmt, arg...) \ + edac_printk(level, "i82975x", fmt, ##arg) + +#define i82975x_mc_printk(mci, level, fmt, arg...) \ + edac_mc_chipset_printk(mci, level, "i82975x", fmt, ##arg) + +#ifndef PCI_DEVICE_ID_INTEL_82975_0 +#define PCI_DEVICE_ID_INTEL_82975_0 0x277c +#endif /* PCI_DEVICE_ID_INTEL_82975_0 */ + +#define I82975X_NR_CSROWS(nr_chans) (8/(nr_chans)) + +/* Intel 82975X register addresses - device 0 function 0 - DRAM Controller */ +#define I82975X_EAP 0x58 /* Dram Error Address Pointer (32b) + * + * 31:7 128 byte cache-line address + * 6:1 reserved + * 0 0: CH0; 1: CH1 + */ + +#define I82975X_DERRSYN 0x5c /* Dram Error SYNdrome (8b) + * + * 7:0 DRAM ECC Syndrome + */ + +#define I82975X_DES 0x5d /* Dram ERRor DeSTination (8b) + * 0h: Processor Memory Reads + * 1h:7h reserved + * More - See Page 65 of Intel DocSheet. + */ + +#define I82975X_ERRSTS 0xc8 /* Error Status Register (16b) + * + * 15:12 reserved + * 11 Thermal Sensor Event + * 10 reserved + * 9 non-DRAM lock error (ndlock) + * 8 Refresh Timeout + * 7:2 reserved + * 1 ECC UE (multibit DRAM error) + * 0 ECC CE (singlebit DRAM error) + */ + +/* Error Reporting is supported by 3 mechanisms: + 1. DMI SERR generation ( ERRCMD ) + 2. SMI DMI generation ( SMICMD ) + 3. SCI DMI generation ( SCICMD ) +NOTE: Only ONE of the three must be enabled +*/ +#define I82975X_ERRCMD 0xca /* Error Command (16b) + * + * 15:12 reserved + * 11 Thermal Sensor Event + * 10 reserved + * 9 non-DRAM lock error (ndlock) + * 8 Refresh Timeout + * 7:2 reserved + * 1 ECC UE (multibit DRAM error) + * 0 ECC CE (singlebit DRAM error) + */ + +#define I82975X_SMICMD 0xcc /* Error Command (16b) + * + * 15:2 reserved + * 1 ECC UE (multibit DRAM error) + * 0 ECC CE (singlebit DRAM error) + */ + +#define I82975X_SCICMD 0xce /* Error Command (16b) + * + * 15:2 reserved + * 1 ECC UE (multibit DRAM error) + * 0 ECC CE (singlebit DRAM error) + */ + +#define I82975X_XEAP 0xfc /* Extended Dram Error Address Pointer (8b) + * + * 7:1 reserved + * 0 Bit32 of the Dram Error Address + */ + +#define I82975X_MCHBAR 0x44 /* + * + * 31:14 Base Addr of 16K memory-mapped + * configuration space + * 13:1 reserverd + * 0 mem-mapped config space enable + */ + +/* NOTE: Following addresses have to indexed using MCHBAR offset (44h, 32b) */ +/* Intel 82975x memory mapped register space */ + +#define I82975X_DRB_SHIFT 25 /* fixed 32MiB grain */ + +#define I82975X_DRB 0x100 /* DRAM Row Boundary (8b x 8) + * + * 7 set to 1 in highest DRB of + * channel if 4GB in ch. + * 6:2 upper boundary of rank in + * 32MB grains + * 1:0 set to 0 + */ +#define I82975X_DRB_CH0R0 0x100 +#define I82975X_DRB_CH0R1 0x101 +#define I82975X_DRB_CH0R2 0x102 +#define I82975X_DRB_CH0R3 0x103 +#define I82975X_DRB_CH1R0 0x180 +#define I82975X_DRB_CH1R1 0x181 +#define I82975X_DRB_CH1R2 0x182 +#define I82975X_DRB_CH1R3 0x183 + + +#define I82975X_DRA 0x108 /* DRAM Row Attribute (4b x 8) + * defines the PAGE SIZE to be used + * for the rank + * 7 reserved + * 6:4 row attr of odd rank, i.e. 1 + * 3 reserved + * 2:0 row attr of even rank, i.e. 0 + * + * 000 = unpopulated + * 001 = reserved + * 010 = 4KiB + * 011 = 8KiB + * 100 = 16KiB + * others = reserved + */ +#define I82975X_DRA_CH0R01 0x108 +#define I82975X_DRA_CH0R23 0x109 +#define I82975X_DRA_CH1R01 0x188 +#define I82975X_DRA_CH1R23 0x189 + + +#define I82975X_BNKARC 0x10e /* Type of device in each rank - Bank Arch (16b) + * + * 15:8 reserved + * 7:6 Rank 3 architecture + * 5:4 Rank 2 architecture + * 3:2 Rank 1 architecture + * 1:0 Rank 0 architecture + * + * 00 => x16 devices; i.e 4 banks + * 01 => x8 devices; i.e 8 banks + */ +#define I82975X_C0BNKARC 0x10e +#define I82975X_C1BNKARC 0x18e + + + +#define I82975X_DRC 0x120 /* DRAM Controller Mode0 (32b) + * + * 31:30 reserved + * 29 init complete + * 28:11 reserved, according to Intel + * 22:21 number of channels + * 00=1 01=2 in 82875 + * seems to be ECC mode + * bits in 82975 in Asus + * P5W + * 19:18 Data Integ Mode + * 00=none 01=ECC in 82875 + * 10:8 refresh mode + * 7 reserved + * 6:4 mode select + * 3:2 reserved + * 1:0 DRAM type 10=Second Revision + * DDR2 SDRAM + * 00, 01, 11 reserved + */ +#define I82975X_DRC_CH0M0 0x120 +#define I82975X_DRC_CH1M0 0x1A0 + + +#define I82975X_DRC_M1 0x124 /* DRAM Controller Mode1 (32b) + * 31 0=Standard Address Map + * 1=Enhanced Address Map + * 30:0 reserved + */ + +#define I82975X_DRC_CH0M1 0x124 +#define I82975X_DRC_CH1M1 0x1A4 + +enum i82975x_chips { + I82975X = 0, +}; + +struct i82975x_pvt { + void __iomem *mch_window; +}; + +struct i82975x_dev_info { + const char *ctl_name; +}; + +struct i82975x_error_info { + u16 errsts; + u32 eap; + u8 des; + u8 derrsyn; + u16 errsts2; + u8 chan; /* the channel is bit 0 of EAP */ + u8 xeap; /* extended eap bit */ +}; + +static const struct i82975x_dev_info i82975x_devs[] = { + [I82975X] = { + .ctl_name = "i82975x" + }, +}; + +static struct pci_dev *mci_pdev; /* init dev: in case that AGP code has + * already registered driver + */ + +static int i82975x_registered = 1; + +static void i82975x_get_error_info(struct mem_ctl_info *mci, + struct i82975x_error_info *info) +{ + struct pci_dev *pdev; + + pdev = to_pci_dev(mci->dev); + + /* + * This is a mess because there is no atomic way to read all the + * registers at once and the registers can transition from CE being + * overwritten by UE. + */ + pci_read_config_word(pdev, I82975X_ERRSTS, &info->errsts); + pci_read_config_dword(pdev, I82975X_EAP, &info->eap); + pci_read_config_byte(pdev, I82975X_XEAP, &info->xeap); + pci_read_config_byte(pdev, I82975X_DES, &info->des); + pci_read_config_byte(pdev, I82975X_DERRSYN, &info->derrsyn); + pci_read_config_word(pdev, I82975X_ERRSTS, &info->errsts2); + + pci_write_bits16(pdev, I82975X_ERRSTS, 0x0003, 0x0003); + + /* + * If the error is the same then we can for both reads then + * the first set of reads is valid. If there is a change then + * there is a CE no info and the second set of reads is valid + * and should be UE info. + */ + if (!(info->errsts2 & 0x0003)) + return; + + if ((info->errsts ^ info->errsts2) & 0x0003) { + pci_read_config_dword(pdev, I82975X_EAP, &info->eap); + pci_read_config_byte(pdev, I82975X_XEAP, &info->xeap); + pci_read_config_byte(pdev, I82975X_DES, &info->des); + pci_read_config_byte(pdev, I82975X_DERRSYN, + &info->derrsyn); + } +} + +static int i82975x_process_error_info(struct mem_ctl_info *mci, + struct i82975x_error_info *info, int handle_errors) +{ + int row, multi_chan, chan; + + multi_chan = mci->csrows[0].nr_channels - 1; + + if (!(info->errsts2 & 0x0003)) + return 0; + + if (!handle_errors) + return 1; + + if ((info->errsts ^ info->errsts2) & 0x0003) { + edac_mc_handle_ce_no_info(mci, "UE overwrote CE"); + info->errsts = info->errsts2; + } + + chan = info->eap & 1; + info->eap >>= 1; + if (info->xeap ) + info->eap |= 0x80000000; + info->eap >>= PAGE_SHIFT; + row = edac_mc_find_csrow_by_page(mci, info->eap); + + if (info->errsts & 0x0002) + edac_mc_handle_ue(mci, info->eap, 0, row, "i82975x UE"); + else + edac_mc_handle_ce(mci, info->eap, 0, info->derrsyn, row, + multi_chan ? chan : 0, + "i82975x CE"); + + return 1; +} + +static void i82975x_check(struct mem_ctl_info *mci) +{ + struct i82975x_error_info info; + + debugf1("MC%d: %s()\n", mci->mc_idx, __func__); + i82975x_get_error_info(mci, &info); + i82975x_process_error_info(mci, &info, 1); +} + +/* Return 1 if dual channel mode is active. Else return 0. */ +static int dual_channel_active(void __iomem *mch_window) +{ + /* + * We treat interleaved-symmetric configuration as dual-channel - EAP's + * bit-0 giving the channel of the error location. + * + * All other configurations are treated as single channel - the EAP's + * bit-0 will resolve ok in symmetric area of mixed + * (symmetric/asymmetric) configurations + */ + u8 drb[4][2]; + int row; + int dualch; + + for (dualch = 1, row = 0; dualch && (row < 4); row++) { + drb[row][0] = readb(mch_window + I82975X_DRB + row); + drb[row][1] = readb(mch_window + I82975X_DRB + row + 0x80); + dualch = dualch && (drb[row][0] == drb[row][1]); + } + return dualch; +} + +static enum dev_type i82975x_dram_type(void __iomem *mch_window, int rank) +{ + /* + * ASUS P5W DH either does not program this register or programs + * it wrong! + * ECC is possible on i92975x ONLY with DEV_X8 which should mean 'val' + * for each rank should be 01b - the LSB of the word should be 0x55; + * but it reads 0! + */ + return DEV_X8; +} + +static void i82975x_init_csrows(struct mem_ctl_info *mci, + struct pci_dev *pdev, void __iomem *mch_window) +{ + struct csrow_info *csrow; + unsigned long last_cumul_size; + u8 value; + u32 cumul_size; + int index; + + last_cumul_size = 0; + + /* + * 82875 comment: + * The dram row boundary (DRB) reg values are boundary address + * for each DRAM row with a granularity of 32 or 64MB (single/dual + * channel operation). DRB regs are cumulative; therefore DRB7 will + * contain the total memory contained in all eight rows. + * + * FIXME: + * EDAC currently works for Dual-channel Interleaved configuration. + * Other configurations, which the chip supports, need fixing/testing. + * + */ + + for (index = 0; index < mci->nr_csrows; index++) { + csrow = &mci->csrows[index]; + + value = readb(mch_window + I82975X_DRB + index + + ((index >= 4) ? 0x80 : 0)); + cumul_size = value; + cumul_size <<= (I82975X_DRB_SHIFT - PAGE_SHIFT); + debugf3("%s(): (%d) cumul_size 0x%x\n", __func__, index, + cumul_size); + if (cumul_size == last_cumul_size) + continue; /* not populated */ + + csrow->first_page = last_cumul_size; + csrow->last_page = cumul_size - 1; + csrow->nr_pages = cumul_size - last_cumul_size; + last_cumul_size = cumul_size; + csrow->grain = 1 << 7; /* I82975X_EAP has 128B resolution */ + csrow->mtype = MEM_DDR; /* i82975x supports only DDR2 */ + csrow->dtype = i82975x_dram_type(mch_window, index); + csrow->edac_mode = EDAC_SECDED; /* only supported */ + } +} + +/* #define i82975x_DEBUG_IOMEM */ + +#ifdef i82975x_DEBUG_IOMEM +static void i82975x_print_dram_timings(void __iomem *mch_window) +{ + /* + * The register meanings are from Intel specs; + * (shows 13-5-5-5 for 800-DDR2) + * Asus P5W Bios reports 15-5-4-4 + * What's your religion? + */ + static const int caslats[4] = { 5, 4, 3, 6 }; + u32 dtreg[2]; + + dtreg[0] = readl(mch_window + 0x114); + dtreg[1] = readl(mch_window + 0x194); + i82975x_printk(KERN_INFO, "DRAM Timings : Ch0 Ch1\n" + " RAS Active Min = %d %d\n" + " CAS latency = %d %d\n" + " RAS to CAS = %d %d\n" + " RAS precharge = %d %d\n", + (dtreg[0] >> 19 ) & 0x0f, + (dtreg[1] >> 19) & 0x0f, + caslats[(dtreg[0] >> 8) & 0x03], + caslats[(dtreg[1] >> 8) & 0x03], + ((dtreg[0] >> 4) & 0x07) + 2, + ((dtreg[1] >> 4) & 0x07) + 2, + (dtreg[0] & 0x07) + 2, + (dtreg[1] & 0x07) + 2 + ); + +} +#endif + +static int i82975x_probe1(struct pci_dev *pdev, int dev_idx) +{ + int rc = -ENODEV; + struct mem_ctl_info *mci; + struct i82975x_pvt *pvt; + void __iomem *mch_window; + u32 mchbar; + u32 drc[2]; + struct i82975x_error_info discard; + int chans; +#ifdef i82975x_DEBUG_IOMEM + u8 c0drb[4]; + u8 c1drb[4]; +#endif + + debugf0("%s()\n", __func__); + + pci_read_config_dword(pdev, I82975X_MCHBAR, &mchbar); + if (!(mchbar & 1)) { + debugf3("%s(): failed, MCHBAR disabled!\n", __func__); + goto fail0; + } + mchbar &= 0xffffc000; /* bits 31:14 used for 16K window */ + mch_window = ioremap_nocache(mchbar, 0x1000); + +#ifdef i82975x_DEBUG_IOMEM + i82975x_printk(KERN_INFO, "MCHBAR real = %0x, remapped = %p\n", + mchbar, mch_window); + + c0drb[0] = readb(mch_window + I82975X_DRB_CH0R0); + c0drb[1] = readb(mch_window + I82975X_DRB_CH0R1); + c0drb[2] = readb(mch_window + I82975X_DRB_CH0R2); + c0drb[3] = readb(mch_window + I82975X_DRB_CH0R3); + c1drb[0] = readb(mch_window + I82975X_DRB_CH1R0); + c1drb[1] = readb(mch_window + I82975X_DRB_CH1R1); + c1drb[2] = readb(mch_window + I82975X_DRB_CH1R2); + c1drb[3] = readb(mch_window + I82975X_DRB_CH1R3); + i82975x_printk(KERN_INFO, "DRBCH0R0 = 0x%02x\n", c0drb[0]); + i82975x_printk(KERN_INFO, "DRBCH0R1 = 0x%02x\n", c0drb[1]); + i82975x_printk(KERN_INFO, "DRBCH0R2 = 0x%02x\n", c0drb[2]); + i82975x_printk(KERN_INFO, "DRBCH0R3 = 0x%02x\n", c0drb[3]); + i82975x_printk(KERN_INFO, "DRBCH1R0 = 0x%02x\n", c1drb[0]); + i82975x_printk(KERN_INFO, "DRBCH1R1 = 0x%02x\n", c1drb[1]); + i82975x_printk(KERN_INFO, "DRBCH1R2 = 0x%02x\n", c1drb[2]); + i82975x_printk(KERN_INFO, "DRBCH1R3 = 0x%02x\n", c1drb[3]); +#endif + + drc[0] = readl(mch_window + I82975X_DRC_CH0M0); + drc[1] = readl(mch_window + I82975X_DRC_CH1M0); +#ifdef i82975x_DEBUG_IOMEM + i82975x_printk(KERN_INFO, "DRC_CH0 = %0x, %s\n", drc[0], + ((drc[0] >> 21) & 3) == 1 ? + "ECC enabled" : "ECC disabled"); + i82975x_printk(KERN_INFO, "DRC_CH1 = %0x, %s\n", drc[1], + ((drc[1] >> 21) & 3) == 1 ? + "ECC enabled" : "ECC disabled"); + + i82975x_printk(KERN_INFO, "C0 BNKARC = %0x\n", + readw(mch_window + I82975X_C0BNKARC)); + i82975x_printk(KERN_INFO, "C1 BNKARC = %0x\n", + readw(mch_window + I82975X_C1BNKARC)); + i82975x_print_dram_timings(mch_window); + goto fail1; +#endif + if (!(((drc[0] >> 21) & 3) == 1 || ((drc[1] >> 21) & 3) == 1)) { + i82975x_printk(KERN_INFO, "ECC disabled on both channels.\n"); + goto fail1; + } + + chans = dual_channel_active(mch_window) + 1; + + /* assuming only one controller, index thus is 0 */ + mci = edac_mc_alloc(sizeof(*pvt), I82975X_NR_CSROWS(chans), + chans, 0); + if (!mci) { + rc = -ENOMEM; + goto fail1; + } + + debugf3("%s(): init mci\n", __func__); + mci->dev = &pdev->dev; + mci->mtype_cap = MEM_FLAG_DDR; + mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED; + mci->edac_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED; + mci->mod_name = EDAC_MOD_STR; + mci->mod_ver = I82975X_REVISION; + mci->ctl_name = i82975x_devs[dev_idx].ctl_name; + mci->edac_check = i82975x_check; + mci->ctl_page_to_phys = NULL; + debugf3("%s(): init pvt\n", __func__); + pvt = (struct i82975x_pvt *) mci->pvt_info; + pvt->mch_window = mch_window; + i82975x_init_csrows(mci, pdev, mch_window); + i82975x_get_error_info(mci, &discard); /* clear counters */ + + /* finalize this instance of memory controller with edac core */ + if (edac_mc_add_mc(mci)) { + debugf3("%s(): failed edac_mc_add_mc()\n", __func__); + goto fail2; + } + + /* get this far and it's successful */ + debugf3("%s(): success\n", __func__); + return 0; + +fail2: + edac_mc_free(mci); + +fail1: + iounmap(mch_window); +fail0: + return rc; +} + +/* returns count (>= 0), or negative on error */ +static int __devinit i82975x_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int rc; + + debugf0("%s()\n", __func__); + + if (pci_enable_device(pdev) < 0) + return -EIO; + + rc = i82975x_probe1(pdev, ent->driver_data); + + if (mci_pdev == NULL) + mci_pdev = pci_dev_get(pdev); + + return rc; +} + +static void __devexit i82975x_remove_one(struct pci_dev *pdev) +{ + struct mem_ctl_info *mci; + struct i82975x_pvt *pvt; + + debugf0("%s()\n", __func__); + + mci = edac_mc_del_mc(&pdev->dev); + if (mci == NULL) + return; + + pvt = mci->pvt_info; + if (pvt->mch_window) + iounmap( pvt->mch_window ); + + edac_mc_free(mci); +} + +static const struct pci_device_id i82975x_pci_tbl[] __devinitdata = { + { + PCI_VEND_DEV(INTEL, 82975_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, + I82975X + }, + { + 0, + } /* 0 terminated list. */ +}; + +MODULE_DEVICE_TABLE(pci, i82975x_pci_tbl); + +static struct pci_driver i82975x_driver = { + .name = EDAC_MOD_STR, + .probe = i82975x_init_one, + .remove = __devexit_p(i82975x_remove_one), + .id_table = i82975x_pci_tbl, +}; + +static int __init i82975x_init(void) +{ + int pci_rc; + + debugf3("%s()\n", __func__); + + pci_rc = pci_register_driver(&i82975x_driver); + if (pci_rc < 0) + goto fail0; + + if (mci_pdev == NULL) { + mci_pdev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_82975_0, NULL); + + if (!mci_pdev) { + debugf0("i82975x pci_get_device fail\n"); + pci_rc = -ENODEV; + goto fail1; + } + + pci_rc = i82975x_init_one(mci_pdev, i82975x_pci_tbl); + + if (pci_rc < 0) { + debugf0("i82975x init fail\n"); + pci_rc = -ENODEV; + goto fail1; + } + } + + return 0; + +fail1: + pci_unregister_driver(&i82975x_driver); + +fail0: + if (mci_pdev != NULL) + pci_dev_put(mci_pdev); + + return pci_rc; +} + +static void __exit i82975x_exit(void) +{ + debugf3("%s()\n", __func__); + + pci_unregister_driver(&i82975x_driver); + + if (!i82975x_registered) { + i82975x_remove_one(mci_pdev); + pci_dev_put(mci_pdev); + } +} + +module_init(i82975x_init); +module_exit(i82975x_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Arvind R. <arvind@acarlab.com>"); +MODULE_DESCRIPTION("MC support for Intel 82975 memory hub controllers"); diff --git a/drivers/edac/pasemi_edac.c b/drivers/edac/pasemi_edac.c new file mode 100644 index 0000000..e66cdd4 --- /dev/null +++ b/drivers/edac/pasemi_edac.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2006-2007 PA Semi, Inc + * + * Author: Egor Martovetsky <egor@pasemi.com> + * Maintained by: Olof Johansson <olof@lixom.net> + * + * Driver for the PWRficient onchip memory controllers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/slab.h> +#include "edac_core.h" + +#define MODULE_NAME "pasemi_edac" + +#define MCCFG_MCEN 0x300 +#define MCCFG_MCEN_MMC_EN 0x00000001 +#define MCCFG_ERRCOR 0x388 +#define MCCFG_ERRCOR_RNK_FAIL_DET_EN 0x00000100 +#define MCCFG_ERRCOR_ECC_GEN_EN 0x00000010 +#define MCCFG_ERRCOR_ECC_CRR_EN 0x00000001 +#define MCCFG_SCRUB 0x384 +#define MCCFG_SCRUB_RGLR_SCRB_EN 0x00000001 +#define MCDEBUG_ERRCTL1 0x728 +#define MCDEBUG_ERRCTL1_RFL_LOG_EN 0x00080000 +#define MCDEBUG_ERRCTL1_MBE_LOG_EN 0x00040000 +#define MCDEBUG_ERRCTL1_SBE_LOG_EN 0x00020000 +#define MCDEBUG_ERRSTA 0x730 +#define MCDEBUG_ERRSTA_RFL_STATUS 0x00000004 +#define MCDEBUG_ERRSTA_MBE_STATUS 0x00000002 +#define MCDEBUG_ERRSTA_SBE_STATUS 0x00000001 +#define MCDEBUG_ERRCNT1 0x734 +#define MCDEBUG_ERRCNT1_SBE_CNT_OVRFLO 0x00000080 +#define MCDEBUG_ERRLOG1A 0x738 +#define MCDEBUG_ERRLOG1A_MERR_TYPE_M 0x30000000 +#define MCDEBUG_ERRLOG1A_MERR_TYPE_NONE 0x00000000 +#define MCDEBUG_ERRLOG1A_MERR_TYPE_SBE 0x10000000 +#define MCDEBUG_ERRLOG1A_MERR_TYPE_MBE 0x20000000 +#define MCDEBUG_ERRLOG1A_MERR_TYPE_RFL 0x30000000 +#define MCDEBUG_ERRLOG1A_MERR_BA_M 0x00700000 +#define MCDEBUG_ERRLOG1A_MERR_BA_S 20 +#define MCDEBUG_ERRLOG1A_MERR_CS_M 0x00070000 +#define MCDEBUG_ERRLOG1A_MERR_CS_S 16 +#define MCDEBUG_ERRLOG1A_SYNDROME_M 0x0000ffff +#define MCDRAM_RANKCFG 0x114 +#define MCDRAM_RANKCFG_EN 0x00000001 +#define MCDRAM_RANKCFG_TYPE_SIZE_M 0x000001c0 +#define MCDRAM_RANKCFG_TYPE_SIZE_S 6 + +#define PASEMI_EDAC_NR_CSROWS 8 +#define PASEMI_EDAC_NR_CHANS 1 +#define PASEMI_EDAC_ERROR_GRAIN 64 + +static int last_page_in_mmc; +static int system_mmc_id; + + +static u32 pasemi_edac_get_error_info(struct mem_ctl_info *mci) +{ + struct pci_dev *pdev = to_pci_dev(mci->dev); + u32 tmp; + + pci_read_config_dword(pdev, MCDEBUG_ERRSTA, + &tmp); + + tmp &= (MCDEBUG_ERRSTA_RFL_STATUS | MCDEBUG_ERRSTA_MBE_STATUS + | MCDEBUG_ERRSTA_SBE_STATUS); + + if (tmp) { + if (tmp & MCDEBUG_ERRSTA_SBE_STATUS) + pci_write_config_dword(pdev, MCDEBUG_ERRCNT1, + MCDEBUG_ERRCNT1_SBE_CNT_OVRFLO); + pci_write_config_dword(pdev, MCDEBUG_ERRSTA, tmp); + } + + return tmp; +} + +static void pasemi_edac_process_error_info(struct mem_ctl_info *mci, u32 errsta) +{ + struct pci_dev *pdev = to_pci_dev(mci->dev); + u32 errlog1a; + u32 cs; + + if (!errsta) + return; + + pci_read_config_dword(pdev, MCDEBUG_ERRLOG1A, &errlog1a); + + cs = (errlog1a & MCDEBUG_ERRLOG1A_MERR_CS_M) >> + MCDEBUG_ERRLOG1A_MERR_CS_S; + + /* uncorrectable/multi-bit errors */ + if (errsta & (MCDEBUG_ERRSTA_MBE_STATUS | + MCDEBUG_ERRSTA_RFL_STATUS)) { + edac_mc_handle_ue(mci, mci->csrows[cs].first_page, 0, + cs, mci->ctl_name); + } + + /* correctable/single-bit errors */ + if (errsta & MCDEBUG_ERRSTA_SBE_STATUS) { + edac_mc_handle_ce(mci, mci->csrows[cs].first_page, 0, + 0, cs, 0, mci->ctl_name); + } +} + +static void pasemi_edac_check(struct mem_ctl_info *mci) +{ + u32 errsta; + + errsta = pasemi_edac_get_error_info(mci); + if (errsta) + pasemi_edac_process_error_info(mci, errsta); +} + +static int pasemi_edac_init_csrows(struct mem_ctl_info *mci, + struct pci_dev *pdev, + enum edac_type edac_mode) +{ + struct csrow_info *csrow; + u32 rankcfg; + int index; + + for (index = 0; index < mci->nr_csrows; index++) { + csrow = &mci->csrows[index]; + + pci_read_config_dword(pdev, + MCDRAM_RANKCFG + (index * 12), + &rankcfg); + + if (!(rankcfg & MCDRAM_RANKCFG_EN)) + continue; + + switch ((rankcfg & MCDRAM_RANKCFG_TYPE_SIZE_M) >> + MCDRAM_RANKCFG_TYPE_SIZE_S) { + case 0: + csrow->nr_pages = 128 << (20 - PAGE_SHIFT); + break; + case 1: + csrow->nr_pages = 256 << (20 - PAGE_SHIFT); + break; + case 2: + case 3: + csrow->nr_pages = 512 << (20 - PAGE_SHIFT); + break; + case 4: + csrow->nr_pages = 1024 << (20 - PAGE_SHIFT); + break; + case 5: + csrow->nr_pages = 2048 << (20 - PAGE_SHIFT); + break; + default: + edac_mc_printk(mci, KERN_ERR, + "Unrecognized Rank Config. rankcfg=%u\n", + rankcfg); + return -EINVAL; + } + + csrow->first_page = last_page_in_mmc; + csrow->last_page = csrow->first_page + csrow->nr_pages - 1; + last_page_in_mmc += csrow->nr_pages; + csrow->page_mask = 0; + csrow->grain = PASEMI_EDAC_ERROR_GRAIN; + csrow->mtype = MEM_DDR; + csrow->dtype = DEV_UNKNOWN; + csrow->edac_mode = edac_mode; + } + return 0; +} + +static int __devinit pasemi_edac_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct mem_ctl_info *mci = NULL; + u32 errctl1, errcor, scrub, mcen; + + pci_read_config_dword(pdev, MCCFG_MCEN, &mcen); + if (!(mcen & MCCFG_MCEN_MMC_EN)) + return -ENODEV; + + /* + * We should think about enabling other error detection later on + */ + + pci_read_config_dword(pdev, MCDEBUG_ERRCTL1, &errctl1); + errctl1 |= MCDEBUG_ERRCTL1_SBE_LOG_EN | + MCDEBUG_ERRCTL1_MBE_LOG_EN | + MCDEBUG_ERRCTL1_RFL_LOG_EN; + pci_write_config_dword(pdev, MCDEBUG_ERRCTL1, errctl1); + + mci = edac_mc_alloc(0, PASEMI_EDAC_NR_CSROWS, PASEMI_EDAC_NR_CHANS, + system_mmc_id++); + + if (mci == NULL) + return -ENOMEM; + + pci_read_config_dword(pdev, MCCFG_ERRCOR, &errcor); + errcor |= MCCFG_ERRCOR_RNK_FAIL_DET_EN | + MCCFG_ERRCOR_ECC_GEN_EN | + MCCFG_ERRCOR_ECC_CRR_EN; + + mci->dev = &pdev->dev; + mci->mtype_cap = MEM_FLAG_DDR | MEM_FLAG_RDDR; + mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_EC | EDAC_FLAG_SECDED; + mci->edac_cap = (errcor & MCCFG_ERRCOR_ECC_GEN_EN) ? + ((errcor & MCCFG_ERRCOR_ECC_CRR_EN) ? + (EDAC_FLAG_EC | EDAC_FLAG_SECDED) : EDAC_FLAG_EC) : + EDAC_FLAG_NONE; + mci->mod_name = MODULE_NAME; + mci->dev_name = pci_name(pdev); + mci->ctl_name = "pasemi,1682m-mc"; + mci->edac_check = pasemi_edac_check; + mci->ctl_page_to_phys = NULL; + pci_read_config_dword(pdev, MCCFG_SCRUB, &scrub); + mci->scrub_cap = SCRUB_FLAG_HW_PROG | SCRUB_FLAG_HW_SRC; + mci->scrub_mode = + ((errcor & MCCFG_ERRCOR_ECC_CRR_EN) ? SCRUB_FLAG_HW_SRC : 0) | + ((scrub & MCCFG_SCRUB_RGLR_SCRB_EN) ? SCRUB_FLAG_HW_PROG : 0); + + if (pasemi_edac_init_csrows(mci, pdev, + (mci->edac_cap & EDAC_FLAG_SECDED) ? + EDAC_SECDED : + ((mci->edac_cap & EDAC_FLAG_EC) ? + EDAC_EC : EDAC_NONE))) + goto fail; + + /* + * Clear status + */ + pasemi_edac_get_error_info(mci); + + if (edac_mc_add_mc(mci)) + goto fail; + + /* get this far and it's successful */ + return 0; + +fail: + edac_mc_free(mci); + return -ENODEV; +} + +static void __devexit pasemi_edac_remove(struct pci_dev *pdev) +{ + struct mem_ctl_info *mci = edac_mc_del_mc(&pdev->dev); + + if (!mci) + return; + + edac_mc_free(mci); +} + + +static const struct pci_device_id pasemi_edac_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_PASEMI, 0xa00a) }, +}; + +MODULE_DEVICE_TABLE(pci, pasemi_edac_pci_tbl); + +static struct pci_driver pasemi_edac_driver = { + .name = MODULE_NAME, + .probe = pasemi_edac_probe, + .remove = __devexit_p(pasemi_edac_remove), + .id_table = pasemi_edac_pci_tbl, +}; + +static int __init pasemi_edac_init(void) +{ + return pci_register_driver(&pasemi_edac_driver); +} + +static void __exit pasemi_edac_exit(void) +{ + pci_unregister_driver(&pasemi_edac_driver); +} + +module_init(pasemi_edac_init); +module_exit(pasemi_edac_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Egor Martovetsky <egor@pasemi.com>"); +MODULE_DESCRIPTION("MC support for PA Semi PA6T-1682M memory controller"); diff --git a/drivers/edac/r82600_edac.c b/drivers/edac/r82600_edac.c index a49cf0a..e25f712 100644 --- a/drivers/edac/r82600_edac.c +++ b/drivers/edac/r82600_edac.c @@ -11,7 +11,7 @@ * * Written with reference to 82600 High Integration Dual PCI System * Controller Data Book: - * http://www.radisys.com/files/support_downloads/007-01277-0002.82600DataBook.pdf + * www.radisys.com/files/support_downloads/007-01277-0002.82600DataBook.pdf * references to this document given in [] */ @@ -20,9 +20,9 @@ #include <linux/pci.h> #include <linux/pci_ids.h> #include <linux/slab.h> -#include "edac_mc.h" +#include "edac_core.h" -#define R82600_REVISION " Ver: 2.0.1 " __DATE__ +#define R82600_REVISION " Ver: 2.0.2 " __DATE__ #define EDAC_MOD_STR "r82600_edac" #define r82600_printk(level, fmt, arg...) \ @@ -131,10 +131,12 @@ struct r82600_error_info { u32 eapr; }; -static unsigned int disable_hardware_scrub = 0; +static unsigned int disable_hardware_scrub; -static void r82600_get_error_info (struct mem_ctl_info *mci, - struct r82600_error_info *info) +static struct edac_pci_ctl_info *r82600_pci; + +static void r82600_get_error_info(struct mem_ctl_info *mci, + struct r82600_error_info *info) { struct pci_dev *pdev; @@ -144,18 +146,19 @@ static void r82600_get_error_info (struct mem_ctl_info *mci, if (info->eapr & BIT(0)) /* Clear error to allow next error to be reported [p.62] */ pci_write_bits32(pdev, R82600_EAP, - ((u32) BIT(0) & (u32) BIT(1)), - ((u32) BIT(0) & (u32) BIT(1))); + ((u32) BIT(0) & (u32) BIT(1)), + ((u32) BIT(0) & (u32) BIT(1))); if (info->eapr & BIT(1)) /* Clear error to allow next error to be reported [p.62] */ pci_write_bits32(pdev, R82600_EAP, - ((u32) BIT(0) & (u32) BIT(1)), - ((u32) BIT(0) & (u32) BIT(1))); + ((u32) BIT(0) & (u32) BIT(1)), + ((u32) BIT(0) & (u32) BIT(1))); } -static int r82600_process_error_info (struct mem_ctl_info *mci, - struct r82600_error_info *info, int handle_errors) +static int r82600_process_error_info(struct mem_ctl_info *mci, + struct r82600_error_info *info, + int handle_errors) { int error_found; u32 eapaddr, page; @@ -172,25 +175,24 @@ static int r82600_process_error_info (struct mem_ctl_info *mci, * granularity (upper 19 bits only) */ page = eapaddr >> PAGE_SHIFT; - if (info->eapr & BIT(0)) { /* CE? */ + if (info->eapr & BIT(0)) { /* CE? */ error_found = 1; if (handle_errors) - edac_mc_handle_ce(mci, page, 0, /* not avail */ + edac_mc_handle_ce(mci, page, 0, /* not avail */ syndrome, edac_mc_find_csrow_by_page(mci, page), - 0, /* channel */ - mci->ctl_name); + 0, mci->ctl_name); } - if (info->eapr & BIT(1)) { /* UE? */ + if (info->eapr & BIT(1)) { /* UE? */ error_found = 1; if (handle_errors) /* 82600 doesn't give enough info */ edac_mc_handle_ue(mci, page, 0, - edac_mc_find_csrow_by_page(mci, page), - mci->ctl_name); + edac_mc_find_csrow_by_page(mci, page), + mci->ctl_name); } return error_found; @@ -211,11 +213,11 @@ static inline int ecc_enabled(u8 dramcr) } static void r82600_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, - u8 dramcr) + u8 dramcr) { struct csrow_info *csrow; int index; - u8 drbar; /* SDRAM Row Boundry Address Register */ + u8 drbar; /* SDRAM Row Boundry Address Register */ u32 row_high_limit, row_high_limit_last; u32 reg_sdram, ecc_on, row_base; @@ -276,7 +278,7 @@ static int r82600_probe1(struct pci_dev *pdev, int dev_idx) debugf2("%s(): sdram refresh rate = %#0x\n", __func__, sdram_refresh_rate); debugf2("%s(): DRAMC register = %#0x\n", __func__, dramcr); - mci = edac_mc_alloc(0, R82600_NR_CSROWS, R82600_NR_CHANS); + mci = edac_mc_alloc(0, R82600_NR_CSROWS, R82600_NR_CHANS, 0); if (mci == NULL) return -ENOMEM; @@ -305,15 +307,16 @@ static int r82600_probe1(struct pci_dev *pdev, int dev_idx) mci->mod_name = EDAC_MOD_STR; mci->mod_ver = R82600_REVISION; mci->ctl_name = "R82600"; + mci->dev_name = pci_name(pdev); mci->edac_check = r82600_check; mci->ctl_page_to_phys = NULL; r82600_init_csrows(mci, pdev, dramcr); - r82600_get_error_info(mci, &discard); /* clear counters */ + r82600_get_error_info(mci, &discard); /* clear counters */ /* Here we assume that we will never see multiple instances of this * type of memory controller. The ID is therefore hardcoded to 0. */ - if (edac_mc_add_mc(mci,0)) { + if (edac_mc_add_mc(mci)) { debugf3("%s(): failed edac_mc_add_mc()\n", __func__); goto fail; } @@ -326,6 +329,17 @@ static int r82600_probe1(struct pci_dev *pdev, int dev_idx) pci_write_bits32(pdev, R82600_EAP, BIT(31), BIT(31)); } + /* allocating generic PCI control info */ + r82600_pci = edac_pci_create_generic_ctl(&pdev->dev, EDAC_MOD_STR); + if (!r82600_pci) { + printk(KERN_WARNING + "%s(): Unable to create PCI control\n", + __func__); + printk(KERN_WARNING + "%s(): PCI error report via EDAC not setup\n", + __func__); + } + debugf3("%s(): success\n", __func__); return 0; @@ -336,7 +350,7 @@ fail: /* returns count (>= 0), or negative on error */ static int __devinit r82600_init_one(struct pci_dev *pdev, - const struct pci_device_id *ent) + const struct pci_device_id *ent) { debugf0("%s()\n", __func__); @@ -350,6 +364,9 @@ static void __devexit r82600_remove_one(struct pci_dev *pdev) debugf0("%s()\n", __func__); + if (r82600_pci) + edac_pci_release_generic_ctl(r82600_pci); + if ((mci = edac_mc_del_mc(&pdev->dev)) == NULL) return; @@ -358,11 +375,11 @@ static void __devexit r82600_remove_one(struct pci_dev *pdev) static const struct pci_device_id r82600_pci_tbl[] __devinitdata = { { - PCI_DEVICE(PCI_VENDOR_ID_RADISYS, R82600_BRIDGE_ID) - }, + PCI_DEVICE(PCI_VENDOR_ID_RADISYS, R82600_BRIDGE_ID) + }, { - 0, - } /* 0 terminated list. */ + 0, + } /* 0 terminated list. */ }; MODULE_DEVICE_TABLE(pci, r82600_pci_tbl); @@ -389,7 +406,7 @@ module_exit(r82600_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Tim Small <tim@buttersideup.com> - WPAD Ltd. " - "on behalf of EADS Astrium"); + "on behalf of EADS Astrium"); MODULE_DESCRIPTION("MC support for Radisys 82600 memory controllers"); module_param(disable_hardware_scrub, bool, 0644); diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 13eea47..dbdca6f 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -29,17 +29,34 @@ config HWMON_VID default n config SENSORS_ABITUGURU - tristate "Abit uGuru" + tristate "Abit uGuru (rev 1 & 2)" depends on EXPERIMENTAL help - If you say yes here you get support for the Abit uGuru chips - sensor part. The voltage and frequency control parts of the Abit - uGuru are not supported. The Abit uGuru chip can be found on Abit - uGuru featuring motherboards (most modern Abit motherboards). + If you say yes here you get support for the sensor part of the first + and second revision of the Abit uGuru chip. The voltage and frequency + control parts of the Abit uGuru are not supported. The Abit uGuru + chip can be found on Abit uGuru featuring motherboards (most modern + Abit motherboards from before end 2005). For more info and a list + of which motherboards have which revision see + Documentation/hwmon/abituguru This driver can also be built as a module. If so, the module will be called abituguru. +config SENSORS_ABITUGURU3 + tristate "Abit uGuru (rev 3)" + depends on HWMON && EXPERIMENTAL + help + If you say yes here you get support for the sensor part of the + third revision of the Abit uGuru chip. Only reading the sensors + and their settings is supported. The third revision of the Abit + uGuru chip can be found on recent Abit motherboards (since end + 2005). For more info and a list of which motherboards have which + revision see Documentation/hwmon/abituguru3 + + This driver can also be built as a module. If so, the module + will be called abituguru3. + config SENSORS_AD7418 tristate "Analog Devices AD7416, AD7417 and AD7418" depends on I2C && EXPERIMENTAL @@ -250,12 +267,10 @@ config SENSORS_CORETEMP config SENSORS_IT87 tristate "ITE IT87xx and compatibles" - depends on I2C - select I2C_ISA select HWMON_VID help If you say yes here you get support for ITE IT8705F, IT8712F, - IT8716F and IT8718F sensor chips, and the SiS960 clone. + IT8716F, IT8718F and IT8726F sensor chips, and the SiS960 clone. This driver can also be built as a module. If so, the module will be called it87. @@ -365,8 +380,8 @@ config SENSORS_LM90 depends on I2C help If you say yes here you get support for National Semiconductor LM90, - LM86, LM89 and LM99, Analog Devices ADM1032 and Maxim MAX6657 and - MAX6658 sensor chips. + LM86, LM89 and LM99, Analog Devices ADM1032 and Maxim MAX6657, + MAX6658, MAX6659, MAX6680 and MAX6681 sensor chips. The Analog Devices ADT7461 sensor chip is also supported, but only if found in ADM1032 compatibility mode. @@ -384,6 +399,17 @@ config SENSORS_LM92 This driver can also be built as a module. If so, the module will be called lm92. +config SENSORS_LM93 + tristate "National Semiconductor LM93 and compatibles" + depends on HWMON && I2C + select HWMON_VID + help + If you say yes here you get support for National Semiconductor LM93 + sensor chips. + + This driver can also be built as a module. If so, the module + will be called lm93. + config SENSORS_MAX1619 tristate "Maxim MAX1619 sensor chip" depends on I2C @@ -405,8 +431,6 @@ config SENSORS_MAX6650 config SENSORS_PC87360 tristate "National Semiconductor PC87360 family" - depends on I2C && EXPERIMENTAL - select I2C_ISA select HWMON_VID help If you say yes here you get access to the hardware monitoring @@ -433,8 +457,7 @@ config SENSORS_PC87427 config SENSORS_SIS5595 tristate "Silicon Integrated Systems Corp. SiS5595" - depends on I2C && PCI && EXPERIMENTAL - select I2C_ISA + depends on PCI help If you say yes here you get support for the integrated sensors in SiS5595 South Bridges. @@ -442,6 +465,18 @@ config SENSORS_SIS5595 This driver can also be built as a module. If so, the module will be called sis5595. +config SENSORS_DME1737 + tristate "SMSC DME1737 and compatibles" + depends on I2C && EXPERIMENTAL + select HWMON_VID + help + If you say yes here you get support for the hardware monitoring + and fan control features of the SMSC DME1737 (and compatibles + like the Asus A8000) Super-I/O chip. + + This driver can also be built as a module. If so, the module + will be called dme1737. + config SENSORS_SMSC47M1 tristate "SMSC LPC47M10x and compatibles" help @@ -487,8 +522,7 @@ config SENSORS_SMSC47B397 config SENSORS_VIA686A tristate "VIA686A" - depends on I2C && PCI - select I2C_ISA + depends on PCI help If you say yes here you get support for the integrated sensors in Via 686A/B South Bridges. @@ -509,9 +543,8 @@ config SENSORS_VT1211 config SENSORS_VT8231 tristate "VIA VT8231" - depends on I2C && PCI && EXPERIMENTAL + depends on PCI select HWMON_VID - select I2C_ISA help If you say yes here then you get support for the integrated sensors in the VIA VT8231 device. @@ -584,17 +617,16 @@ config SENSORS_W83627HF will be called w83627hf. config SENSORS_W83627EHF - tristate "Winbond W83627EHF" - depends on I2C && EXPERIMENTAL - select I2C_ISA + tristate "Winbond W83627EHF/DHG" + select HWMON_VID help - If you say yes here you get preliminary support for the hardware + If you say yes here you get support for the hardware monitoring functionality of the Winbond W83627EHF Super-I/O chip. - Only fan and temperature inputs are supported at the moment, while - the chip does much more than that. This driver also supports the W83627EHG, which is the lead-free - version of the W83627EHF. + version of the W83627EHF, and the W83627DHG, which is a similar + chip suited for specific Intel processors that use PECI such as + the Core 2 Duo. This driver can also be built as a module. If so, the module will be called w83627ehf. diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index cfaf338..59f81fa 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o +obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o obj-$(CONFIG_SENSORS_AD7418) += ad7418.o obj-$(CONFIG_SENSORS_ADM1021) += adm1021.o obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o @@ -25,6 +26,7 @@ obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_AMS) += ams/ obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o +obj-$(CONFIG_SENSORS_DME1737) += dme1737.o obj-$(CONFIG_SENSORS_DS1621) += ds1621.o obj-$(CONFIG_SENSORS_F71805F) += f71805f.o obj-$(CONFIG_SENSORS_FSCHER) += fscher.o @@ -45,6 +47,7 @@ obj-$(CONFIG_SENSORS_LM85) += lm85.o obj-$(CONFIG_SENSORS_LM87) += lm87.o obj-$(CONFIG_SENSORS_LM90) += lm90.o obj-$(CONFIG_SENSORS_LM92) += lm92.o +obj-$(CONFIG_SENSORS_LM93) += lm93.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o obj-$(CONFIG_SENSORS_MAX6650) += max6650.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o diff --git a/drivers/hwmon/abituguru.c b/drivers/hwmon/abituguru.c index bede4d9..d575ee9 100644 --- a/drivers/hwmon/abituguru.c +++ b/drivers/hwmon/abituguru.c @@ -16,9 +16,9 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* - This driver supports the sensor part of the custom Abit uGuru chip found - on Abit uGuru motherboards. Note: because of lack of specs the CPU / RAM / - etc voltage & frequency control is not supported! + This driver supports the sensor part of the first and second revision of + the custom Abit uGuru chip found on Abit uGuru motherboards. Note: because + of lack of specs the CPU/RAM voltage & frequency control is not supported! */ #include <linux/module.h> #include <linux/sched.h> @@ -31,6 +31,7 @@ #include <linux/platform_device.h> #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> +#include <linux/dmi.h> #include <asm/io.h> /* Banks */ @@ -418,7 +419,7 @@ static int __devinit abituguru_detect_bank1_sensor_type(struct abituguru_data *data, u8 sensor_addr) { - u8 val, buf[3]; + u8 val, test_flag, buf[3]; int i, ret = -ENODEV; /* error is the most common used retval :| */ /* If overriden by the user return the user selected type */ @@ -436,7 +437,7 @@ abituguru_detect_bank1_sensor_type(struct abituguru_data *data, return -ENODEV; /* Test val is sane / usable for sensor type detection. */ - if ((val < 10u) || (val > 240u)) { + if ((val < 10u) || (val > 250u)) { printk(KERN_WARNING ABIT_UGURU_NAME ": bank1-sensor: %d reading (%d) too close to limits, " "unable to determine sensor type, skipping sensor\n", @@ -449,10 +450,20 @@ abituguru_detect_bank1_sensor_type(struct abituguru_data *data, ABIT_UGURU_DEBUG(2, "testing bank1 sensor %d\n", (int)sensor_addr); /* Volt sensor test, enable volt low alarm, set min value ridicously - high. If its a volt sensor this should always give us an alarm. */ - buf[0] = ABIT_UGURU_VOLT_LOW_ALARM_ENABLE; - buf[1] = 245; - buf[2] = 250; + high, or vica versa if the reading is very high. If its a volt + sensor this should always give us an alarm. */ + if (val <= 240u) { + buf[0] = ABIT_UGURU_VOLT_LOW_ALARM_ENABLE; + buf[1] = 245; + buf[2] = 250; + test_flag = ABIT_UGURU_VOLT_LOW_ALARM_FLAG; + } else { + buf[0] = ABIT_UGURU_VOLT_HIGH_ALARM_ENABLE; + buf[1] = 5; + buf[2] = 10; + test_flag = ABIT_UGURU_VOLT_HIGH_ALARM_FLAG; + } + if (abituguru_write(data, ABIT_UGURU_SENSOR_BANK1 + 2, sensor_addr, buf, 3) != 3) goto abituguru_detect_bank1_sensor_type_exit; @@ -469,13 +480,13 @@ abituguru_detect_bank1_sensor_type(struct abituguru_data *data, sensor_addr, buf, 3, ABIT_UGURU_MAX_RETRIES) != 3) goto abituguru_detect_bank1_sensor_type_exit; - if (buf[0] & ABIT_UGURU_VOLT_LOW_ALARM_FLAG) { + if (buf[0] & test_flag) { ABIT_UGURU_DEBUG(2, " found volt sensor\n"); ret = ABIT_UGURU_IN_SENSOR; goto abituguru_detect_bank1_sensor_type_exit; } else ABIT_UGURU_DEBUG(2, " alarm raised during volt " - "sensor test, but volt low flag not set\n"); + "sensor test, but volt range flag not set\n"); } else ABIT_UGURU_DEBUG(2, " alarm not raised during volt sensor " "test\n"); @@ -1287,6 +1298,7 @@ abituguru_probe_error: for (i = 0; i < ARRAY_SIZE(abituguru_sysfs_attr); i++) device_remove_file(&pdev->dev, &abituguru_sysfs_attr[i].dev_attr); + platform_set_drvdata(pdev, NULL); kfree(data); return res; } @@ -1296,13 +1308,13 @@ static int __devexit abituguru_remove(struct platform_device *pdev) int i; struct abituguru_data *data = platform_get_drvdata(pdev); - platform_set_drvdata(pdev, NULL); hwmon_device_unregister(data->class_dev); for (i = 0; data->sysfs_attr[i].dev_attr.attr.name; i++) device_remove_file(&pdev->dev, &data->sysfs_attr[i].dev_attr); for (i = 0; i < ARRAY_SIZE(abituguru_sysfs_attr); i++) device_remove_file(&pdev->dev, &abituguru_sysfs_attr[i].dev_attr); + platform_set_drvdata(pdev, NULL); kfree(data); return 0; @@ -1436,6 +1448,15 @@ static int __init abituguru_init(void) int address, err; struct resource res = { .flags = IORESOURCE_IO }; +#ifdef CONFIG_DMI + char *board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + + /* safety check, refuse to load on non Abit motherboards */ + if (!force && (!board_vendor || + strcmp(board_vendor, "http://www.abit.com.tw/"))) + return -ENODEV; +#endif + address = abituguru_detect(); if (address < 0) return address; diff --git a/drivers/hwmon/abituguru3.c b/drivers/hwmon/abituguru3.c new file mode 100644 index 0000000..a003d10 --- /dev/null +++ b/drivers/hwmon/abituguru3.c @@ -0,0 +1,1140 @@ +/* + abituguru3.c Copyright (c) 2006 Hans de Goede <j.w.r.degoede@hhs.nl> + + 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. +*/ +/* + This driver supports the sensor part of revision 3 of the custom Abit uGuru + chip found on newer Abit uGuru motherboards. Note: because of lack of specs + only reading the sensors and their settings is supported. +*/ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <asm/io.h> + +/* uGuru3 bank addresses */ +#define ABIT_UGURU3_SETTINGS_BANK 0x01 +#define ABIT_UGURU3_SENSORS_BANK 0x08 +#define ABIT_UGURU3_MISC_BANK 0x09 +#define ABIT_UGURU3_ALARMS_START 0x1E +#define ABIT_UGURU3_SETTINGS_START 0x24 +#define ABIT_UGURU3_VALUES_START 0x80 +#define ABIT_UGURU3_BOARD_ID 0x0A +/* uGuru3 sensor bank flags */ /* Alarm if: */ +#define ABIT_UGURU3_TEMP_HIGH_ALARM_ENABLE 0x01 /* temp over warn */ +#define ABIT_UGURU3_VOLT_HIGH_ALARM_ENABLE 0x02 /* volt over max */ +#define ABIT_UGURU3_VOLT_LOW_ALARM_ENABLE 0x04 /* volt under min */ +#define ABIT_UGURU3_TEMP_HIGH_ALARM_FLAG 0x10 /* temp is over warn */ +#define ABIT_UGURU3_VOLT_HIGH_ALARM_FLAG 0x20 /* volt is over max */ +#define ABIT_UGURU3_VOLT_LOW_ALARM_FLAG 0x40 /* volt is under min */ +#define ABIT_UGURU3_FAN_LOW_ALARM_ENABLE 0x01 /* fan under min */ +#define ABIT_UGURU3_BEEP_ENABLE 0x08 /* beep if alarm */ +#define ABIT_UGURU3_SHUTDOWN_ENABLE 0x80 /* shutdown if alarm */ +/* sensor types */ +#define ABIT_UGURU3_IN_SENSOR 0 +#define ABIT_UGURU3_TEMP_SENSOR 1 +#define ABIT_UGURU3_FAN_SENSOR 2 + +/* Timeouts / Retries, if these turn out to need a lot of fiddling we could + convert them to params. Determined by trial and error. I assume this is + cpu-speed independent, since the ISA-bus and not the CPU should be the + bottleneck. */ +#define ABIT_UGURU3_WAIT_TIMEOUT 250 +/* Normally the 0xAC at the end of synchronize() is reported after the + first read, but sometimes not and we need to poll */ +#define ABIT_UGURU3_SYNCHRONIZE_TIMEOUT 5 +/* utility macros */ +#define ABIT_UGURU3_NAME "abituguru3" +#define ABIT_UGURU3_DEBUG(format, arg...) \ + if (verbose) \ + printk(KERN_DEBUG ABIT_UGURU3_NAME ": " format , ## arg) + +/* Macros to help calculate the sysfs_names array length */ +#define ABIT_UGURU3_MAX_NO_SENSORS 26 +/* sum of strlen +1 of: in??_input\0, in??_{min,max}\0, in??_{min,max}_alarm\0, + in??_{min,max}_alarm_enable\0, in??_beep\0, in??_shutdown\0, in??_label\0 */ +#define ABIT_UGURU3_IN_NAMES_LENGTH (11 + 2 * 9 + 2 * 15 + 2 * 22 + 10 + 14 + 11) +/* sum of strlen +1 of: temp??_input\0, temp??_max\0, temp??_crit\0, + temp??_alarm\0, temp??_alarm_enable\0, temp??_beep\0, temp??_shutdown\0, + temp??_label\0 */ +#define ABIT_UGURU3_TEMP_NAMES_LENGTH (13 + 11 + 12 + 13 + 20 + 12 + 16 + 13) +/* sum of strlen +1 of: fan??_input\0, fan??_min\0, fan??_alarm\0, + fan??_alarm_enable\0, fan??_beep\0, fan??_shutdown\0, fan??_label\0 */ +#define ABIT_UGURU3_FAN_NAMES_LENGTH (12 + 10 + 12 + 19 + 11 + 15 + 12) +/* Worst case scenario 16 in sensors (longest names_length) and the rest + temp sensors (second longest names_length). */ +#define ABIT_UGURU3_SYSFS_NAMES_LENGTH (16 * ABIT_UGURU3_IN_NAMES_LENGTH + \ + (ABIT_UGURU3_MAX_NO_SENSORS - 16) * ABIT_UGURU3_TEMP_NAMES_LENGTH) + +/* All the macros below are named identical to the openguru2 program + reverse engineered by Louis Kruger, hence the names might not be 100% + logical. I could come up with better names, but I prefer keeping the names + identical so that this driver can be compared with his work more easily. */ +/* Two i/o-ports are used by uGuru */ +#define ABIT_UGURU3_BASE 0x00E0 +#define ABIT_UGURU3_CMD 0x00 +#define ABIT_UGURU3_DATA 0x04 +#define ABIT_UGURU3_REGION_LENGTH 5 +/* The wait_xxx functions return this on success and the last contents + of the DATA register (0-255) on failure. */ +#define ABIT_UGURU3_SUCCESS -1 +/* uGuru status flags */ +#define ABIT_UGURU3_STATUS_READY_FOR_READ 0x01 +#define ABIT_UGURU3_STATUS_BUSY 0x02 + + +/* Structures */ +struct abituguru3_sensor_info { + const char* name; + int port; + int type; + int multiplier; + int divisor; + int offset; +}; + +struct abituguru3_motherboard_info { + u16 id; + const char *name; + /* + 1 -> end of sensors indicated by a sensor with name == NULL */ + struct abituguru3_sensor_info sensors[ABIT_UGURU3_MAX_NO_SENSORS + 1]; +}; + +/* For the Abit uGuru, we need to keep some data in memory. + The structure is dynamically allocated, at the same time when a new + abituguru3 device is allocated. */ +struct abituguru3_data { + struct class_device *class_dev; /* hwmon registered device */ + struct mutex update_lock; /* protect access to data and uGuru */ + unsigned short addr; /* uguru base address */ + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* For convenience the sysfs attr and their names are generated + automatically. We have max 10 entries per sensor (for in sensors) */ + struct sensor_device_attribute_2 sysfs_attr[ABIT_UGURU3_MAX_NO_SENSORS + * 10]; + + /* Buffer to store the dynamically generated sysfs names */ + char sysfs_names[ABIT_UGURU3_SYSFS_NAMES_LENGTH]; + + /* Pointer to the sensors info for the detected motherboard */ + const struct abituguru3_sensor_info *sensors; + + /* The abituguru3 supports upto 48 sensors, and thus has registers + sets for 48 sensors, for convienence reasons / simplicity of the + code we always read and store all registers for all 48 sensors */ + + /* Alarms for all 48 sensors (1 bit per sensor) */ + u8 alarms[48/8]; + + /* Value of all 48 sensors */ + u8 value[48]; + + /* Settings of all 48 sensors, note in and temp sensors (the first 32 + sensors) have 3 bytes of settings, while fans only have 2 bytes, + for convenience we use 3 bytes for all sensors */ + u8 settings[48][3]; +}; + + +/* Constants */ +static const struct abituguru3_motherboard_info abituguru3_motherboards[] = { + { 0x000C, "unknown", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System ", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS FAN", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 35, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x000D, "Abit AW8", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System ", 25, 1, 1, 1, 0 }, + { "PWM1", 26, 1, 1, 1, 0 }, + { "PWM2", 27, 1, 1, 1, 0 }, + { "PWM3", 28, 1, 1, 1, 0 }, + { "PWM4", 29, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 35, 2, 60, 1, 0 }, + { "AUX2 Fan", 36, 2, 60, 1, 0 }, + { "AUX3 Fan", 37, 2, 60, 1, 0 }, + { "AUX4 Fan", 38, 2, 60, 1, 0 }, + { "AUX5 Fan", 39, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x000E, "AL-8", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System ", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x000F, "unknown", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System ", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0010, "Abit NI8 SLI GR", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "NB 1.4V", 4, 0, 10, 1, 0 }, + { "SB 1.5V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "SYS", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 35, 2, 60, 1, 0 }, + { "OTES1 Fan", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0011, "Abit AT8 32X", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 20, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VDDA 2.5V", 6, 0, 20, 1, 0 }, + { "NB 1.8V", 4, 0, 10, 1, 0 }, + { "NB 1.8V Dual", 5, 0, 10, 1, 0 }, + { "HTV 1.2", 3, 0, 10, 1, 0 }, + { "PCIE 1.2V", 12, 0, 10, 1, 0 }, + { "NB 1.2V", 13, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "NB", 25, 1, 1, 1, 0 }, + { "System", 26, 1, 1, 1, 0 }, + { "PWM", 27, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 35, 2, 60, 1, 0 }, + { "AUX2 Fan", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0012, "Abit AN8 32X", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 20, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "HyperTransport", 3, 0, 10, 1, 0 }, + { "CPU VDDA 2.5V", 5, 0, 20, 1, 0 }, + { "NB", 4, 0, 10, 1, 0 }, + { "SB", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "SYS", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0013, "unknown", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System ", 25, 1, 1, 1, 0 }, + { "PWM1", 26, 1, 1, 1, 0 }, + { "PWM2", 27, 1, 1, 1, 0 }, + { "PWM3", 28, 1, 1, 1, 0 }, + { "PWM4", 29, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 35, 2, 60, 1, 0 }, + { "AUX2 Fan", 36, 2, 60, 1, 0 }, + { "AUX3 Fan", 37, 2, 60, 1, 0 }, + { "AUX4 Fan", 38, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0014, "Abit AB9 Pro", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System ", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0015, "unknown", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 20, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "HyperTransport", 3, 0, 10, 1, 0 }, + { "CPU VDDA 2.5V", 5, 0, 20, 1, 0 }, + { "NB", 4, 0, 10, 1, 0 }, + { "SB", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "SYS", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 33, 2, 60, 1, 0 }, + { "AUX2 Fan", 35, 2, 60, 1, 0 }, + { "AUX3 Fan", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0016, "AW9D-MAX", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR2", 1, 0, 20, 1, 0 }, + { "DDR2 VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System ", 25, 1, 1, 1, 0 }, + { "PWM1", 26, 1, 1, 1, 0 }, + { "PWM2", 27, 1, 1, 1, 0 }, + { "PWM3", 28, 1, 1, 1, 0 }, + { "PWM4", 29, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 35, 2, 60, 1, 0 }, + { "AUX2 Fan", 36, 2, 60, 1, 0 }, + { "AUX3 Fan", 37, 2, 60, 1, 0 }, + { "OTES1 Fan", 38, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0017, "unknown", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR2", 1, 0, 20, 1, 0 }, + { "DDR2 VTT", 2, 0, 10, 1, 0 }, + { "HyperTransport", 3, 0, 10, 1, 0 }, + { "CPU VDDA 2.5V", 6, 0, 20, 1, 0 }, + { "NB 1.8V", 4, 0, 10, 1, 0 }, + { "NB 1.2V ", 13, 0, 10, 1, 0 }, + { "SB 1.2V", 5, 0, 10, 1, 0 }, + { "PCIE 1.2V", 12, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "ATX +3.3V", 10, 0, 20, 1, 0 }, + { "ATX 5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System ", 26, 1, 1, 1, 0 }, + { "PWM", 27, 1, 1, 1, 0 }, + { "CPU FAN", 32, 2, 60, 1, 0 }, + { "SYS FAN", 34, 2, 60, 1, 0 }, + { "AUX1 FAN", 35, 2, 60, 1, 0 }, + { "AUX2 FAN", 36, 2, 60, 1, 0 }, + { "AUX3 FAN", 37, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0018, "unknown", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR2", 1, 0, 20, 1, 0 }, + { "DDR2 VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT", 3, 0, 10, 1, 0 }, + { "MCH 1.25V", 4, 0, 10, 1, 0 }, + { "ICHIO 1.5V", 5, 0, 10, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System ", 25, 1, 1, 1, 0 }, + { "PWM Phase1", 26, 1, 1, 1, 0 }, + { "PWM Phase2", 27, 1, 1, 1, 0 }, + { "PWM Phase3", 28, 1, 1, 1, 0 }, + { "PWM Phase4", 29, 1, 1, 1, 0 }, + { "PWM Phase5", 30, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 33, 2, 60, 1, 0 }, + { "AUX2 Fan", 35, 2, 60, 1, 0 }, + { "AUX3 Fan", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0019, "unknown", { + { "CPU Core", 7, 0, 10, 1, 0 }, + { "DDR2", 13, 0, 20, 1, 0 }, + { "DDR2 VTT", 14, 0, 10, 1, 0 }, + { "CPU VTT", 3, 0, 20, 1, 0 }, + { "NB 1.2V ", 4, 0, 10, 1, 0 }, + { "SB 1.5V", 6, 0, 10, 1, 0 }, + { "HyperTransport", 5, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 12, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "ATX +3.3V", 10, 0, 20, 1, 0 }, + { "ATX 5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System ", 25, 1, 1, 1, 0 }, + { "PWM Phase1", 26, 1, 1, 1, 0 }, + { "PWM Phase2", 27, 1, 1, 1, 0 }, + { "PWM Phase3", 28, 1, 1, 1, 0 }, + { "PWM Phase4", 29, 1, 1, 1, 0 }, + { "PWM Phase5", 30, 1, 1, 1, 0 }, + { "CPU FAN", 32, 2, 60, 1, 0 }, + { "SYS FAN", 34, 2, 60, 1, 0 }, + { "AUX1 FAN", 33, 2, 60, 1, 0 }, + { "AUX2 FAN", 35, 2, 60, 1, 0 }, + { "AUX3 FAN", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x001A, "unknown", { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR2", 1, 0, 20, 1, 0 }, + { "DDR2 VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH 1.25V", 4, 0, 10, 1, 0 }, + { "ICHIO 1.5V", 5, 0, 10, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (8-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System ", 25, 1, 1, 1, 0 }, + { "PWM ", 26, 1, 1, 1, 0 }, + { "PWM Phase2", 27, 1, 1, 1, 0 }, + { "PWM Phase3", 28, 1, 1, 1, 0 }, + { "PWM Phase4", 29, 1, 1, 1, 0 }, + { "PWM Phase5", 30, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 33, 2, 60, 1, 0 }, + { "AUX2 Fan", 35, 2, 60, 1, 0 }, + { "AUX3 Fan", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0000, NULL, { { NULL, 0, 0, 0, 0, 0 } } } +}; + + +/* Insmod parameters */ +static int force; +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Set to one to force detection."); +/* Default verbose is 1, since this driver is still in the testing phase */ +static int verbose = 1; +module_param(verbose, bool, 0644); +MODULE_PARM_DESC(verbose, "Enable/disable verbose error reporting"); + + +/* wait while the uguru is busy (usually after a write) */ +static int abituguru3_wait_while_busy(struct abituguru3_data *data) +{ + u8 x; + int timeout = ABIT_UGURU3_WAIT_TIMEOUT; + + while ((x = inb_p(data->addr + ABIT_UGURU3_DATA)) & + ABIT_UGURU3_STATUS_BUSY) { + timeout--; + if (timeout == 0) + return x; + /* sleep a bit before our last try, to give the uGuru3 one + last chance to respond. */ + if (timeout == 1) + msleep(1); + } + return ABIT_UGURU3_SUCCESS; +} + +/* wait till uguru is ready to be read */ +static int abituguru3_wait_for_read(struct abituguru3_data *data) +{ + u8 x; + int timeout = ABIT_UGURU3_WAIT_TIMEOUT; + + while (!((x = inb_p(data->addr + ABIT_UGURU3_DATA)) & + ABIT_UGURU3_STATUS_READY_FOR_READ)) { + timeout--; + if (timeout == 0) + return x; + /* sleep a bit before our last try, to give the uGuru3 one + last chance to respond. */ + if (timeout == 1) + msleep(1); + } + return ABIT_UGURU3_SUCCESS; +} + +/* This synchronizes us with the uGuru3's protocol state machine, this + must be done before each command. */ +static int abituguru3_synchronize(struct abituguru3_data *data) +{ + int x, timeout = ABIT_UGURU3_SYNCHRONIZE_TIMEOUT; + + if ((x = abituguru3_wait_while_busy(data)) != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("synchronize timeout during initial busy " + "wait, status: 0x%02x\n", x); + return -EIO; + } + + outb(0x20, data->addr + ABIT_UGURU3_DATA); + if ((x = abituguru3_wait_while_busy(data)) != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("synchronize timeout after sending 0x20, " + "status: 0x%02x\n", x); + return -EIO; + } + + outb(0x10, data->addr + ABIT_UGURU3_CMD); + if ((x = abituguru3_wait_while_busy(data)) != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("synchronize timeout after sending 0x10, " + "status: 0x%02x\n", x); + return -EIO; + } + + outb(0x00, data->addr + ABIT_UGURU3_CMD); + if ((x = abituguru3_wait_while_busy(data)) != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("synchronize timeout after sending 0x00, " + "status: 0x%02x\n", x); + return -EIO; + } + + if ((x = abituguru3_wait_for_read(data)) != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("synchronize timeout waiting for read, " + "status: 0x%02x\n", x); + return -EIO; + } + + while ((x = inb(data->addr + ABIT_UGURU3_CMD)) != 0xAC) { + timeout--; + if (timeout == 0) { + ABIT_UGURU3_DEBUG("synchronize timeout cmd does not " + "hold 0xAC after synchronize, cmd: 0x%02x\n", + x); + return -EIO; + } + msleep(1); + } + return 0; +} + +/* Read count bytes from sensor sensor_addr in bank bank_addr and store the + result in buf */ +static int abituguru3_read(struct abituguru3_data *data, u8 bank, u8 offset, + u8 count, u8 *buf) +{ + int i, x; + + if ((x = abituguru3_synchronize(data))) + return x; + + outb(0x1A, data->addr + ABIT_UGURU3_DATA); + if ((x = abituguru3_wait_while_busy(data)) != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("read from 0x%02x:0x%02x timed out after " + "sending 0x1A, status: 0x%02x\n", (unsigned int)bank, + (unsigned int)offset, x); + return -EIO; + } + + outb(bank, data->addr + ABIT_UGURU3_CMD); + if ((x = abituguru3_wait_while_busy(data)) != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("read from 0x%02x:0x%02x timed out after " + "sending the bank, status: 0x%02x\n", + (unsigned int)bank, (unsigned int)offset, x); + return -EIO; + } + + outb(offset, data->addr + ABIT_UGURU3_CMD); + if ((x = abituguru3_wait_while_busy(data)) != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("read from 0x%02x:0x%02x timed out after " + "sending the offset, status: 0x%02x\n", + (unsigned int)bank, (unsigned int)offset, x); + return -EIO; + } + + outb(count, data->addr + ABIT_UGURU3_CMD); + if ((x = abituguru3_wait_while_busy(data)) != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("read from 0x%02x:0x%02x timed out after " + "sending the count, status: 0x%02x\n", + (unsigned int)bank, (unsigned int)offset, x); + return -EIO; + } + + for (i = 0; i < count; i++) { + if ((x = abituguru3_wait_for_read(data)) != + ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("timeout reading byte %d from " + "0x%02x:0x%02x, status: 0x%02x\n", i, + (unsigned int)bank, (unsigned int)offset, x); + break; + } + buf[i] = inb(data->addr + ABIT_UGURU3_CMD); + } + return i; +} + +/* Sensor settings are stored 1 byte per offset with the bytes + placed add consecutive offsets. */ +int abituguru3_read_increment_offset(struct abituguru3_data *data, u8 bank, + u8 offset, u8 count, u8 *buf, int offset_count) +{ + int i, x; + + for (i = 0; i < offset_count; i++) + if ((x = abituguru3_read(data, bank, offset + i, count, + buf + i * count)) != count) + return i * count + (i && (x < 0)) ? 0 : x; + + return i * count; +} + +/* Following are the sysfs callback functions. These functions expect: + sensor_device_attribute_2->index: index into the data->sensors array + sensor_device_attribute_2->nr: register offset, bitmask or NA. */ +static struct abituguru3_data *abituguru3_update_device(struct device *dev); + +static ssize_t show_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int value; + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru3_data *data = abituguru3_update_device(dev); + const struct abituguru3_sensor_info *sensor; + + if (!data) + return -EIO; + + sensor = &data->sensors[attr->index]; + + /* are we reading a setting, or is this a normal read? */ + if (attr->nr) + value = data->settings[sensor->port][attr->nr]; + else + value = data->value[sensor->port]; + + /* convert the value */ + value = (value * sensor->multiplier) / sensor->divisor + + sensor->offset; + + /* alternatively we could update the sensors settings struct for this, + but then its contents would differ from the windows sw ini files */ + if (sensor->type == ABIT_UGURU3_TEMP_SENSOR) + value *= 1000; + + return sprintf(buf, "%d\n", value); +} + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int port; + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru3_data *data = abituguru3_update_device(dev); + + if (!data) + return -EIO; + + port = data->sensors[attr->index].port; + + /* See if the alarm bit for this sensor is set and if a bitmask is + given in attr->nr also check if the alarm matches the type of alarm + we're looking for (for volt it can be either low or high). The type + is stored in a few readonly bits in the settings of the sensor. */ + if ((data->alarms[port / 8] & (0x01 << (port % 8))) && + (!attr->nr || (data->settings[port][0] & attr->nr))) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_mask(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru3_data *data = dev_get_drvdata(dev); + + if (data->settings[data->sensors[attr->index].port][0] & attr->nr) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru3_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->sensors[attr->index].name); +} + +static ssize_t show_name(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + return sprintf(buf, "%s\n", ABIT_UGURU3_NAME); +} + +/* Sysfs attr templates, the real entries are generated automatically. */ +static const +struct sensor_device_attribute_2 abituguru3_sysfs_templ[3][10] = { { + SENSOR_ATTR_2(in%d_input, 0444, show_value, NULL, 0, 0), + SENSOR_ATTR_2(in%d_min, 0444, show_value, NULL, 1, 0), + SENSOR_ATTR_2(in%d_max, 0444, show_value, NULL, 2, 0), + SENSOR_ATTR_2(in%d_min_alarm, 0444, show_alarm, NULL, + ABIT_UGURU3_VOLT_LOW_ALARM_FLAG, 0), + SENSOR_ATTR_2(in%d_max_alarm, 0444, show_alarm, NULL, + ABIT_UGURU3_VOLT_HIGH_ALARM_FLAG, 0), + SENSOR_ATTR_2(in%d_beep, 0444, show_mask, NULL, + ABIT_UGURU3_BEEP_ENABLE, 0), + SENSOR_ATTR_2(in%d_shutdown, 0444, show_mask, NULL, + ABIT_UGURU3_SHUTDOWN_ENABLE, 0), + SENSOR_ATTR_2(in%d_min_alarm_enable, 0444, show_mask, NULL, + ABIT_UGURU3_VOLT_LOW_ALARM_ENABLE, 0), + SENSOR_ATTR_2(in%d_max_alarm_enable, 0444, show_mask, NULL, + ABIT_UGURU3_VOLT_HIGH_ALARM_ENABLE, 0), + SENSOR_ATTR_2(in%d_label, 0444, show_label, NULL, 0, 0) + }, { + SENSOR_ATTR_2(temp%d_input, 0444, show_value, NULL, 0, 0), + SENSOR_ATTR_2(temp%d_max, 0444, show_value, NULL, 1, 0), + SENSOR_ATTR_2(temp%d_crit, 0444, show_value, NULL, 2, 0), + SENSOR_ATTR_2(temp%d_alarm, 0444, show_alarm, NULL, 0, 0), + SENSOR_ATTR_2(temp%d_beep, 0444, show_mask, NULL, + ABIT_UGURU3_BEEP_ENABLE, 0), + SENSOR_ATTR_2(temp%d_shutdown, 0444, show_mask, NULL, + ABIT_UGURU3_SHUTDOWN_ENABLE, 0), + SENSOR_ATTR_2(temp%d_alarm_enable, 0444, show_mask, NULL, + ABIT_UGURU3_TEMP_HIGH_ALARM_ENABLE, 0), + SENSOR_ATTR_2(temp%d_label, 0444, show_label, NULL, 0, 0) + }, { + SENSOR_ATTR_2(fan%d_input, 0444, show_value, NULL, 0, 0), + SENSOR_ATTR_2(fan%d_min, 0444, show_value, NULL, 1, 0), + SENSOR_ATTR_2(fan%d_alarm, 0444, show_alarm, NULL, 0, 0), + SENSOR_ATTR_2(fan%d_beep, 0444, show_mask, NULL, + ABIT_UGURU3_BEEP_ENABLE, 0), + SENSOR_ATTR_2(fan%d_shutdown, 0444, show_mask, NULL, + ABIT_UGURU3_SHUTDOWN_ENABLE, 0), + SENSOR_ATTR_2(fan%d_alarm_enable, 0444, show_mask, NULL, + ABIT_UGURU3_FAN_LOW_ALARM_ENABLE, 0), + SENSOR_ATTR_2(fan%d_label, 0444, show_label, NULL, 0, 0) +} }; + +static struct sensor_device_attribute_2 abituguru3_sysfs_attr[] = { + SENSOR_ATTR_2(name, 0444, show_name, NULL, 0, 0), +}; + +static int __devinit abituguru3_probe(struct platform_device *pdev) +{ + const int no_sysfs_attr[3] = { 10, 8, 7 }; + int sensor_index[3] = { 0, 1, 1 }; + struct abituguru3_data *data; + int i, j, type, used, sysfs_names_free, sysfs_attr_i, res = -ENODEV; + char *sysfs_filename; + u8 buf[2]; + u16 id; + + if (!(data = kzalloc(sizeof(struct abituguru3_data), GFP_KERNEL))) + return -ENOMEM; + + data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start; + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + /* Read the motherboard ID */ + if ((i = abituguru3_read(data, ABIT_UGURU3_MISC_BANK, + ABIT_UGURU3_BOARD_ID, 2, buf)) != 2) { + goto abituguru3_probe_error; + } + + /* Completely read the uGuru to see if one really is there */ + if (!abituguru3_update_device(&pdev->dev)) + goto abituguru3_probe_error; + + /* lookup the ID in our motherboard table */ + id = ((u16)buf[0] << 8) | (u16)buf[1]; + for (i = 0; abituguru3_motherboards[i].id; i++) + if (abituguru3_motherboards[i].id == id) + break; + if (!abituguru3_motherboards[i].id) { + printk(KERN_ERR ABIT_UGURU3_NAME ": error unknown motherboard " + "ID: %04X. Please report this to the abituguru3 " + "maintainer (see MAINTAINERS)\n", (unsigned int)id); + goto abituguru3_probe_error; + } + data->sensors = abituguru3_motherboards[i].sensors; + printk(KERN_INFO ABIT_UGURU3_NAME ": found Abit uGuru3, motherboard " + "ID: %04X (%s)\n", (unsigned int)id, + abituguru3_motherboards[i].name); + + /* Fill the sysfs attr array */ + sysfs_attr_i = 0; + sysfs_filename = data->sysfs_names; + sysfs_names_free = ABIT_UGURU3_SYSFS_NAMES_LENGTH; + for (i = 0; data->sensors[i].name; i++) { + /* Fail safe check, this should never happen! */ + if (i >= ABIT_UGURU3_MAX_NO_SENSORS) { + printk(KERN_ERR ABIT_UGURU3_NAME + ": Fatal error motherboard has more sensors " + "then ABIT_UGURU3_MAX_NO_SENSORS. This should " + "never happen please report to the abituguru3 " + "maintainer (see MAINTAINERS)\n"); + res = -ENAMETOOLONG; + goto abituguru3_probe_error; + } + type = data->sensors[i].type; + for (j = 0; j < no_sysfs_attr[type]; j++) { + used = snprintf(sysfs_filename, sysfs_names_free, + abituguru3_sysfs_templ[type][j].dev_attr.attr. + name, sensor_index[type]) + 1; + data->sysfs_attr[sysfs_attr_i] = + abituguru3_sysfs_templ[type][j]; + data->sysfs_attr[sysfs_attr_i].dev_attr.attr.name = + sysfs_filename; + data->sysfs_attr[sysfs_attr_i].index = i; + sysfs_filename += used; + sysfs_names_free -= used; + sysfs_attr_i++; + } + sensor_index[type]++; + } + /* Fail safe check, this should never happen! */ + if (sysfs_names_free < 0) { + printk(KERN_ERR ABIT_UGURU3_NAME + ": Fatal error ran out of space for sysfs attr names. " + "This should never happen please report to the " + "abituguru3 maintainer (see MAINTAINERS)\n"); + res = -ENAMETOOLONG; + goto abituguru3_probe_error; + } + + /* Register sysfs hooks */ + for (i = 0; i < sysfs_attr_i; i++) + if (device_create_file(&pdev->dev, + &data->sysfs_attr[i].dev_attr)) + goto abituguru3_probe_error; + for (i = 0; i < ARRAY_SIZE(abituguru3_sysfs_attr); i++) + if (device_create_file(&pdev->dev, + &abituguru3_sysfs_attr[i].dev_attr)) + goto abituguru3_probe_error; + + data->class_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->class_dev)) { + res = PTR_ERR(data->class_dev); + goto abituguru3_probe_error; + } + + return 0; /* success */ + +abituguru3_probe_error: + for (i = 0; data->sysfs_attr[i].dev_attr.attr.name; i++) + device_remove_file(&pdev->dev, &data->sysfs_attr[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(abituguru3_sysfs_attr); i++) + device_remove_file(&pdev->dev, + &abituguru3_sysfs_attr[i].dev_attr); + kfree(data); + return res; +} + +static int __devexit abituguru3_remove(struct platform_device *pdev) +{ + int i; + struct abituguru3_data *data = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + hwmon_device_unregister(data->class_dev); + for (i = 0; data->sysfs_attr[i].dev_attr.attr.name; i++) + device_remove_file(&pdev->dev, &data->sysfs_attr[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(abituguru3_sysfs_attr); i++) + device_remove_file(&pdev->dev, + &abituguru3_sysfs_attr[i].dev_attr); + kfree(data); + + return 0; +} + +static struct abituguru3_data *abituguru3_update_device(struct device *dev) +{ + int i; + struct abituguru3_data *data = dev_get_drvdata(dev); + + mutex_lock(&data->update_lock); + if (!data->valid || time_after(jiffies, data->last_updated + HZ)) { + /* Clear data->valid while updating */ + data->valid = 0; + /* Read alarms */ + if (abituguru3_read_increment_offset(data, + ABIT_UGURU3_SETTINGS_BANK, + ABIT_UGURU3_ALARMS_START, + 1, data->alarms, 48/8) != (48/8)) + goto LEAVE_UPDATE; + /* Read in and temp sensors (3 byte settings / sensor) */ + for (i = 0; i < 32; i++) { + if (abituguru3_read(data, ABIT_UGURU3_SENSORS_BANK, + ABIT_UGURU3_VALUES_START + i, + 1, &data->value[i]) != 1) + goto LEAVE_UPDATE; + if (abituguru3_read_increment_offset(data, + ABIT_UGURU3_SETTINGS_BANK, + ABIT_UGURU3_SETTINGS_START + i * 3, + 1, + data->settings[i], 3) != 3) + goto LEAVE_UPDATE; + } + /* Read temp sensors (2 byte settings / sensor) */ + for (i = 0; i < 16; i++) { + if (abituguru3_read(data, ABIT_UGURU3_SENSORS_BANK, + ABIT_UGURU3_VALUES_START + 32 + i, + 1, &data->value[32 + i]) != 1) + goto LEAVE_UPDATE; + if (abituguru3_read_increment_offset(data, + ABIT_UGURU3_SETTINGS_BANK, + ABIT_UGURU3_SETTINGS_START + 32 * 3 + + i * 2, 1, + data->settings[32 + i], 2) != 2) + goto LEAVE_UPDATE; + } + data->last_updated = jiffies; + data->valid = 1; + } +LEAVE_UPDATE: + mutex_unlock(&data->update_lock); + if (data->valid) + return data; + else + return NULL; +} + +#ifdef CONFIG_PM +static int abituguru3_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct abituguru3_data *data = platform_get_drvdata(pdev); + /* make sure all communications with the uguru3 are done and no new + ones are started */ + mutex_lock(&data->update_lock); + return 0; +} + +static int abituguru3_resume(struct platform_device *pdev) +{ + struct abituguru3_data *data = platform_get_drvdata(pdev); + mutex_unlock(&data->update_lock); + return 0; +} +#else +#define abituguru3_suspend NULL +#define abituguru3_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver abituguru3_driver = { + .driver = { + .owner = THIS_MODULE, + .name = ABIT_UGURU3_NAME, + }, + .probe = abituguru3_probe, + .remove = __devexit_p(abituguru3_remove), + .suspend = abituguru3_suspend, + .resume = abituguru3_resume +}; + +static int __init abituguru3_detect(void) +{ + /* See if there is an uguru3 there. An idle uGuru3 will hold 0x00 or + 0x08 at DATA and 0xAC at CMD. Sometimes the uGuru3 will hold 0x05 + at CMD instead, why is unknown. So we test for 0x05 too. */ + u8 data_val = inb_p(ABIT_UGURU3_BASE + ABIT_UGURU3_DATA); + u8 cmd_val = inb_p(ABIT_UGURU3_BASE + ABIT_UGURU3_CMD); + if (((data_val == 0x00) || (data_val == 0x08)) && + ((cmd_val == 0xAC) || (cmd_val == 0x05))) + return ABIT_UGURU3_BASE; + + ABIT_UGURU3_DEBUG("no Abit uGuru3 found, data = 0x%02X, cmd = " + "0x%02X\n", (unsigned int)data_val, (unsigned int)cmd_val); + + if (force) { + printk(KERN_INFO ABIT_UGURU3_NAME ": Assuming Abit uGuru3 is " + "present because of \"force\" parameter\n"); + return ABIT_UGURU3_BASE; + } + + /* No uGuru3 found */ + return -ENODEV; +} + +static struct platform_device *abituguru3_pdev; + +static int __init abituguru3_init(void) +{ + int address, err; + struct resource res = { .flags = IORESOURCE_IO }; + + address = abituguru3_detect(); + if (address < 0) + return address; + + err = platform_driver_register(&abituguru3_driver); + if (err) + goto exit; + + abituguru3_pdev = platform_device_alloc(ABIT_UGURU3_NAME, address); + if (!abituguru3_pdev) { + printk(KERN_ERR ABIT_UGURU3_NAME + ": Device allocation failed\n"); + err = -ENOMEM; + goto exit_driver_unregister; + } + + res.start = address; + res.end = address + ABIT_UGURU3_REGION_LENGTH - 1; + res.name = ABIT_UGURU3_NAME; + + err = platform_device_add_resources(abituguru3_pdev, &res, 1); + if (err) { + printk(KERN_ERR ABIT_UGURU3_NAME + ": Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(abituguru3_pdev); + if (err) { + printk(KERN_ERR ABIT_UGURU3_NAME + ": Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(abituguru3_pdev); +exit_driver_unregister: + platform_driver_unregister(&abituguru3_driver); +exit: + return err; +} + +static void __exit abituguru3_exit(void) +{ + platform_device_unregister(abituguru3_pdev); + platform_driver_unregister(&abituguru3_driver); +} + +MODULE_AUTHOR("Hans de Goede <j.w.r.degoede@hhs.nl>"); +MODULE_DESCRIPTION("Abit uGuru3 Sensor device"); +MODULE_LICENSE("GPL"); + +module_init(abituguru3_init); +module_exit(abituguru3_exit); diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 6d54c8c..7c17952 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -318,7 +318,7 @@ exit: } #ifdef CONFIG_HOTPLUG_CPU -void coretemp_device_remove(unsigned int cpu) +static void coretemp_device_remove(unsigned int cpu) { struct pdev_entry *p, *n; mutex_lock(&pdev_list_mutex); diff --git a/drivers/hwmon/dme1737.c b/drivers/hwmon/dme1737.c new file mode 100644 index 0000000..be3aaa5 --- /dev/null +++ b/drivers/hwmon/dme1737.c @@ -0,0 +1,2080 @@ +/* + * dme1737.c - driver for the SMSC DME1737 and Asus A8000 Super-I/O chips + * integrated hardware monitoring features. + * Copyright (c) 2007 Juerg Haefliger <juergh@gmail.com> + * + * This driver is based on the LM85 driver. The hardware monitoring + * capabilities of the DME1737 are very similar to the LM85 with some + * additional features. Even though the DME1737 is a Super-I/O chip, the + * hardware monitoring registers are only accessible via SMBus. + * + * 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/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/hwmon-vid.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <asm/io.h> + +/* Module load parameters */ +static int force_start; +module_param(force_start, bool, 0); +MODULE_PARM_DESC(force_start, "Force the chip to start monitoring inputs"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {0x2c, 0x2d, 0x2e, I2C_CLIENT_END}; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(dme1737); + +/* --------------------------------------------------------------------- + * Registers + * + * The sensors are defined as follows: + * + * Voltages Temperatures + * -------- ------------ + * in0 +5VTR (+5V stdby) temp1 Remote diode 1 + * in1 Vccp (proc core) temp2 Internal temp + * in2 VCC (internal +3.3V) temp3 Remote diode 2 + * in3 +5V + * in4 +12V + * in5 VTR (+3.3V stby) + * in6 Vbat + * + * --------------------------------------------------------------------- */ + +/* Voltages (in) numbered 0-6 (ix) */ +#define DME1737_REG_IN(ix) ((ix) < 5 ? 0x20 + (ix) \ + : 0x94 + (ix)) +#define DME1737_REG_IN_MIN(ix) ((ix) < 5 ? 0x44 + (ix) * 2 \ + : 0x91 + (ix) * 2) +#define DME1737_REG_IN_MAX(ix) ((ix) < 5 ? 0x45 + (ix) * 2 \ + : 0x92 + (ix) * 2) + +/* Temperatures (temp) numbered 0-2 (ix) */ +#define DME1737_REG_TEMP(ix) (0x25 + (ix)) +#define DME1737_REG_TEMP_MIN(ix) (0x4e + (ix) * 2) +#define DME1737_REG_TEMP_MAX(ix) (0x4f + (ix) * 2) +#define DME1737_REG_TEMP_OFFSET(ix) ((ix) == 0 ? 0x1f \ + : 0x1c + (ix)) + +/* Voltage and temperature LSBs + * The LSBs (4 bits each) are stored in 5 registers with the following layouts: + * IN_TEMP_LSB(0) = [in5, in6] + * IN_TEMP_LSB(1) = [temp3, temp1] + * IN_TEMP_LSB(2) = [in4, temp2] + * IN_TEMP_LSB(3) = [in3, in0] + * IN_TEMP_LSB(4) = [in2, in1] */ +#define DME1737_REG_IN_TEMP_LSB(ix) (0x84 + (ix)) +static const u8 DME1737_REG_IN_LSB[] = {3, 4, 4, 3, 2, 0, 0}; +static const u8 DME1737_REG_IN_LSB_SHL[] = {4, 4, 0, 0, 0, 0, 4}; +static const u8 DME1737_REG_TEMP_LSB[] = {1, 2, 1}; +static const u8 DME1737_REG_TEMP_LSB_SHL[] = {4, 4, 0}; + +/* Fans numbered 0-5 (ix) */ +#define DME1737_REG_FAN(ix) ((ix) < 4 ? 0x28 + (ix) * 2 \ + : 0xa1 + (ix) * 2) +#define DME1737_REG_FAN_MIN(ix) ((ix) < 4 ? 0x54 + (ix) * 2 \ + : 0xa5 + (ix) * 2) +#define DME1737_REG_FAN_OPT(ix) ((ix) < 4 ? 0x90 + (ix) \ + : 0xb2 + (ix)) +#define DME1737_REG_FAN_MAX(ix) (0xb4 + (ix)) /* only for fan[4-5] */ + +/* PWMs numbered 0-2, 4-5 (ix) */ +#define DME1737_REG_PWM(ix) ((ix) < 3 ? 0x30 + (ix) \ + : 0xa1 + (ix)) +#define DME1737_REG_PWM_CONFIG(ix) (0x5c + (ix)) /* only for pwm[0-2] */ +#define DME1737_REG_PWM_MIN(ix) (0x64 + (ix)) /* only for pwm[0-2] */ +#define DME1737_REG_PWM_FREQ(ix) ((ix) < 3 ? 0x5f + (ix) \ + : 0xa3 + (ix)) +/* The layout of the ramp rate registers is different from the other pwm + * registers. The bits for the 3 PWMs are stored in 2 registers: + * PWM_RR(0) = [OFF3, OFF2, OFF1, RES, RR1E, RR1-2, RR1-1, RR1-0] + * PWM_RR(1) = [RR2E, RR2-2, RR2-1, RR2-0, RR3E, RR3-2, RR3-1, RR3-0] */ +#define DME1737_REG_PWM_RR(ix) (0x62 + (ix)) /* only for pwm[0-2] */ + +/* Thermal zones 0-2 */ +#define DME1737_REG_ZONE_LOW(ix) (0x67 + (ix)) +#define DME1737_REG_ZONE_ABS(ix) (0x6a + (ix)) +/* The layout of the hysteresis registers is different from the other zone + * registers. The bits for the 3 zones are stored in 2 registers: + * ZONE_HYST(0) = [H1-3, H1-2, H1-1, H1-0, H2-3, H2-2, H2-1, H2-0] + * ZONE_HYST(1) = [H3-3, H3-2, H3-1, H3-0, RES, RES, RES, RES] */ +#define DME1737_REG_ZONE_HYST(ix) (0x6d + (ix)) + +/* Alarm registers and bit mapping + * The 3 8-bit alarm registers will be concatenated to a single 32-bit + * alarm value [0, ALARM3, ALARM2, ALARM1]. */ +#define DME1737_REG_ALARM1 0x41 +#define DME1737_REG_ALARM2 0x42 +#define DME1737_REG_ALARM3 0x83 +static const u8 DME1737_BIT_ALARM_IN[] = {0, 1, 2, 3, 8, 16, 17}; +static const u8 DME1737_BIT_ALARM_TEMP[] = {4, 5, 6}; +static const u8 DME1737_BIT_ALARM_FAN[] = {10, 11, 12, 13, 22, 23}; + +/* Miscellaneous registers */ +#define DME1737_REG_COMPANY 0x3e +#define DME1737_REG_VERSTEP 0x3f +#define DME1737_REG_CONFIG 0x40 +#define DME1737_REG_CONFIG2 0x7f +#define DME1737_REG_VID 0x43 +#define DME1737_REG_TACH_PWM 0x81 + +/* --------------------------------------------------------------------- + * Misc defines + * --------------------------------------------------------------------- */ + +/* Chip identification */ +#define DME1737_COMPANY_SMSC 0x5c +#define DME1737_VERSTEP 0x88 +#define DME1737_VERSTEP_MASK 0xf8 + +/* --------------------------------------------------------------------- + * Data structures and manipulation thereof + * --------------------------------------------------------------------- */ + +struct dme1737_data { + struct i2c_client client; + struct class_device *class_dev; + + struct mutex update_lock; + int valid; /* !=0 if following fields are valid */ + unsigned long last_update; /* in jiffies */ + unsigned long last_vbat; /* in jiffies */ + + u8 vid; + u8 pwm_rr_en; + u8 has_pwm; + u8 has_fan; + + /* Register values */ + u16 in[7]; + u8 in_min[7]; + u8 in_max[7]; + s16 temp[3]; + s8 temp_min[3]; + s8 temp_max[3]; + s8 temp_offset[3]; + u8 config; + u8 config2; + u8 vrm; + u16 fan[6]; + u16 fan_min[6]; + u8 fan_max[2]; + u8 fan_opt[6]; + u8 pwm[6]; + u8 pwm_min[3]; + u8 pwm_config[3]; + u8 pwm_acz[3]; + u8 pwm_freq[6]; + u8 pwm_rr[2]; + u8 zone_low[3]; + u8 zone_abs[3]; + u8 zone_hyst[2]; + u32 alarms; +}; + +/* Nominal voltage values */ +static const int IN_NOMINAL[] = {5000, 2250, 3300, 5000, 12000, 3300, 3300}; + +/* Voltage input + * Voltage inputs have 16 bits resolution, limit values have 8 bits + * resolution. */ +static inline int IN_FROM_REG(int reg, int ix, int res) +{ + return (reg * IN_NOMINAL[ix] + (3 << (res - 3))) / (3 << (res - 2)); +} + +static inline int IN_TO_REG(int val, int ix) +{ + return SENSORS_LIMIT((val * 192 + IN_NOMINAL[ix] / 2) / + IN_NOMINAL[ix], 0, 255); +} + +/* Temperature input + * The register values represent temperatures in 2's complement notation from + * -127 degrees C to +127 degrees C. Temp inputs have 16 bits resolution, limit + * values have 8 bits resolution. */ +static inline int TEMP_FROM_REG(int reg, int res) +{ + return (reg * 1000) >> (res - 8); +} + +static inline int TEMP_TO_REG(int val) +{ + return SENSORS_LIMIT((val < 0 ? val - 500 : val + 500) / 1000, + -128, 127); +} + +/* Temperature range */ +static const int TEMP_RANGE[] = {2000, 2500, 3333, 4000, 5000, 6666, 8000, + 10000, 13333, 16000, 20000, 26666, 32000, + 40000, 53333, 80000}; + +static inline int TEMP_RANGE_FROM_REG(int reg) +{ + return TEMP_RANGE[(reg >> 4) & 0x0f]; +} + +static int TEMP_RANGE_TO_REG(int val, int reg) +{ + int i; + + for (i = 15; i > 0; i--) { + if (val > (TEMP_RANGE[i] + TEMP_RANGE[i - 1] + 1) / 2) { + break; + } + } + + return (reg & 0x0f) | (i << 4); +} + +/* Temperature hysteresis + * Register layout: + * reg[0] = [H1-3, H1-2, H1-1, H1-0, H2-3, H2-2, H2-1, H2-0] + * reg[1] = [H3-3, H3-2, H3-1, H3-0, xxxx, xxxx, xxxx, xxxx] */ +static inline int TEMP_HYST_FROM_REG(int reg, int ix) +{ + return (((ix == 1) ? reg : reg >> 4) & 0x0f) * 1000; +} + +static inline int TEMP_HYST_TO_REG(int val, int ix, int reg) +{ + int hyst = SENSORS_LIMIT((val + 500) / 1000, 0, 15); + + return (ix == 1) ? (reg & 0xf0) | hyst : (reg & 0x0f) | (hyst << 4); +} + +/* Fan input RPM */ +static inline int FAN_FROM_REG(int reg, int tpc) +{ + return (reg == 0 || reg == 0xffff) ? 0 : + (tpc == 0) ? 90000 * 60 / reg : tpc * reg; +} + +static inline int FAN_TO_REG(int val, int tpc) +{ + return SENSORS_LIMIT((tpc == 0) ? 90000 * 60 / val : val / tpc, + 0, 0xffff); +} + +/* Fan TPC (tach pulse count) + * Converts a register value to a TPC multiplier or returns 0 if the tachometer + * is configured in legacy (non-tpc) mode */ +static inline int FAN_TPC_FROM_REG(int reg) +{ + return (reg & 0x20) ? 0 : 60 >> (reg & 0x03); +} + +/* Fan type + * The type of a fan is expressed in number of pulses-per-revolution that it + * emits */ +static inline int FAN_TYPE_FROM_REG(int reg) +{ + int edge = (reg >> 1) & 0x03; + + return (edge > 0) ? 1 << (edge - 1) : 0; +} + +static inline int FAN_TYPE_TO_REG(int val, int reg) +{ + int edge = (val == 4) ? 3 : val; + + return (reg & 0xf9) | (edge << 1); +} + +/* Fan max RPM */ +static const int FAN_MAX[] = {0x54, 0x38, 0x2a, 0x21, 0x1c, 0x18, 0x15, 0x12, + 0x11, 0x0f, 0x0e}; + +static int FAN_MAX_FROM_REG(int reg) +{ + int i; + + for (i = 10; i > 0; i--) { + if (reg == FAN_MAX[i]) { + break; + } + } + + return 1000 + i * 500; +} + +static int FAN_MAX_TO_REG(int val) +{ + int i; + + for (i = 10; i > 0; i--) { + if (val > (1000 + (i - 1) * 500)) { + break; + } + } + + return FAN_MAX[i]; +} + +/* PWM enable + * Register to enable mapping: + * 000: 2 fan on zone 1 auto + * 001: 2 fan on zone 2 auto + * 010: 2 fan on zone 3 auto + * 011: 0 fan full on + * 100: -1 fan disabled + * 101: 2 fan on hottest of zones 2,3 auto + * 110: 2 fan on hottest of zones 1,2,3 auto + * 111: 1 fan in manual mode */ +static inline int PWM_EN_FROM_REG(int reg) +{ + static const int en[] = {2, 2, 2, 0, -1, 2, 2, 1}; + + return en[(reg >> 5) & 0x07]; +} + +static inline int PWM_EN_TO_REG(int val, int reg) +{ + int en = (val == 1) ? 7 : 3; + + return (reg & 0x1f) | ((en & 0x07) << 5); +} + +/* PWM auto channels zone + * Register to auto channels zone mapping (ACZ is a bitfield with bit x + * corresponding to zone x+1): + * 000: 001 fan on zone 1 auto + * 001: 010 fan on zone 2 auto + * 010: 100 fan on zone 3 auto + * 011: 000 fan full on + * 100: 000 fan disabled + * 101: 110 fan on hottest of zones 2,3 auto + * 110: 111 fan on hottest of zones 1,2,3 auto + * 111: 000 fan in manual mode */ +static inline int PWM_ACZ_FROM_REG(int reg) +{ + static const int acz[] = {1, 2, 4, 0, 0, 6, 7, 0}; + + return acz[(reg >> 5) & 0x07]; +} + +static inline int PWM_ACZ_TO_REG(int val, int reg) +{ + int acz = (val == 4) ? 2 : val - 1; + + return (reg & 0x1f) | ((acz & 0x07) << 5); +} + +/* PWM frequency */ +static const int PWM_FREQ[] = {11, 15, 22, 29, 35, 44, 59, 88, + 15000, 20000, 30000, 25000, 0, 0, 0, 0}; + +static inline int PWM_FREQ_FROM_REG(int reg) +{ + return PWM_FREQ[reg & 0x0f]; +} + +static int PWM_FREQ_TO_REG(int val, int reg) +{ + int i; + + /* the first two cases are special - stupid chip design! */ + if (val > 27500) { + i = 10; + } else if (val > 22500) { + i = 11; + } else { + for (i = 9; i > 0; i--) { + if (val > (PWM_FREQ[i] + PWM_FREQ[i - 1] + 1) / 2) { + break; + } + } + } + + return (reg & 0xf0) | i; +} + +/* PWM ramp rate + * Register layout: + * reg[0] = [OFF3, OFF2, OFF1, RES, RR1-E, RR1-2, RR1-1, RR1-0] + * reg[1] = [RR2-E, RR2-2, RR2-1, RR2-0, RR3-E, RR3-2, RR3-1, RR3-0] */ +static const u8 PWM_RR[] = {206, 104, 69, 41, 26, 18, 10, 5}; + +static inline int PWM_RR_FROM_REG(int reg, int ix) +{ + int rr = (ix == 1) ? reg >> 4 : reg; + + return (rr & 0x08) ? PWM_RR[rr & 0x07] : 0; +} + +static int PWM_RR_TO_REG(int val, int ix, int reg) +{ + int i; + + for (i = 0; i < 7; i++) { + if (val > (PWM_RR[i] + PWM_RR[i + 1] + 1) / 2) { + break; + } + } + + return (ix == 1) ? (reg & 0x8f) | (i << 4) : (reg & 0xf8) | i; +} + +/* PWM ramp rate enable */ +static inline int PWM_RR_EN_FROM_REG(int reg, int ix) +{ + return PWM_RR_FROM_REG(reg, ix) ? 1 : 0; +} + +static inline int PWM_RR_EN_TO_REG(int val, int ix, int reg) +{ + int en = (ix == 1) ? 0x80 : 0x08; + + return val ? reg | en : reg & ~en; +} + +/* PWM min/off + * The PWM min/off bits are part of the PMW ramp rate register 0 (see above for + * the register layout). */ +static inline int PWM_OFF_FROM_REG(int reg, int ix) +{ + return (reg >> (ix + 5)) & 0x01; +} + +static inline int PWM_OFF_TO_REG(int val, int ix, int reg) +{ + return (reg & ~(1 << (ix + 5))) | ((val & 0x01) << (ix + 5)); +} + +/* --------------------------------------------------------------------- + * Device I/O access + * --------------------------------------------------------------------- */ + +static u8 dme1737_read(struct i2c_client *client, u8 reg) +{ + s32 val = i2c_smbus_read_byte_data(client, reg); + + if (val < 0) { + dev_warn(&client->dev, "Read from register 0x%02x failed! " + "Please report to the driver maintainer.\n", reg); + } + + return val; +} + +static s32 dme1737_write(struct i2c_client *client, u8 reg, u8 value) +{ + s32 res = i2c_smbus_write_byte_data(client, reg, value); + + if (res < 0) { + dev_warn(&client->dev, "Write to register 0x%02x failed! " + "Please report to the driver maintainer.\n", reg); + } + + return res; +} + +static struct dme1737_data *dme1737_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct dme1737_data *data = i2c_get_clientdata(client); + int ix; + u8 lsb[5]; + + mutex_lock(&data->update_lock); + + /* Enable a Vbat monitoring cycle every 10 mins */ + if (time_after(jiffies, data->last_vbat + 600 * HZ) || !data->valid) { + dme1737_write(client, DME1737_REG_CONFIG, dme1737_read(client, + DME1737_REG_CONFIG) | 0x10); + data->last_vbat = jiffies; + } + + /* Sample register contents every 1 sec */ + if (time_after(jiffies, data->last_update + HZ) || !data->valid) { + data->vid = dme1737_read(client, DME1737_REG_VID) & 0x3f; + + /* In (voltage) registers */ + for (ix = 0; ix < ARRAY_SIZE(data->in); ix++) { + /* Voltage inputs are stored as 16 bit values even + * though they have only 12 bits resolution. This is + * to make it consistent with the temp inputs. */ + data->in[ix] = dme1737_read(client, + DME1737_REG_IN(ix)) << 8; + data->in_min[ix] = dme1737_read(client, + DME1737_REG_IN_MIN(ix)); + data->in_max[ix] = dme1737_read(client, + DME1737_REG_IN_MAX(ix)); + } + + /* Temp registers */ + for (ix = 0; ix < ARRAY_SIZE(data->temp); ix++) { + /* Temp inputs are stored as 16 bit values even + * though they have only 12 bits resolution. This is + * to take advantage of implicit conversions between + * register values (2's complement) and temp values + * (signed decimal). */ + data->temp[ix] = dme1737_read(client, + DME1737_REG_TEMP(ix)) << 8; + data->temp_min[ix] = dme1737_read(client, + DME1737_REG_TEMP_MIN(ix)); + data->temp_max[ix] = dme1737_read(client, + DME1737_REG_TEMP_MAX(ix)); + data->temp_offset[ix] = dme1737_read(client, + DME1737_REG_TEMP_OFFSET(ix)); + } + + /* In and temp LSB registers + * The LSBs are latched when the MSBs are read, so the order in + * which the registers are read (MSB first, then LSB) is + * important! */ + for (ix = 0; ix < ARRAY_SIZE(lsb); ix++) { + lsb[ix] = dme1737_read(client, + DME1737_REG_IN_TEMP_LSB(ix)); + } + for (ix = 0; ix < ARRAY_SIZE(data->in); ix++) { + data->in[ix] |= (lsb[DME1737_REG_IN_LSB[ix]] << + DME1737_REG_IN_LSB_SHL[ix]) & 0xf0; + } + for (ix = 0; ix < ARRAY_SIZE(data->temp); ix++) { + data->temp[ix] |= (lsb[DME1737_REG_TEMP_LSB[ix]] << + DME1737_REG_TEMP_LSB_SHL[ix]) & 0xf0; + } + + /* Fan registers */ + for (ix = 0; ix < ARRAY_SIZE(data->fan); ix++) { + /* Skip reading registers if optional fans are not + * present */ + if (!(data->has_fan & (1 << ix))) { + continue; + } + data->fan[ix] = dme1737_read(client, + DME1737_REG_FAN(ix)); + data->fan[ix] |= dme1737_read(client, + DME1737_REG_FAN(ix) + 1) << 8; + data->fan_min[ix] = dme1737_read(client, + DME1737_REG_FAN_MIN(ix)); + data->fan_min[ix] |= dme1737_read(client, + DME1737_REG_FAN_MIN(ix) + 1) << 8; + data->fan_opt[ix] = dme1737_read(client, + DME1737_REG_FAN_OPT(ix)); + /* fan_max exists only for fan[5-6] */ + if (ix > 3) { + data->fan_max[ix - 4] = dme1737_read(client, + DME1737_REG_FAN_MAX(ix)); + } + } + + /* PWM registers */ + for (ix = 0; ix < ARRAY_SIZE(data->pwm); ix++) { + /* Skip reading registers if optional PWMs are not + * present */ + if (!(data->has_pwm & (1 << ix))) { + continue; + } + data->pwm[ix] = dme1737_read(client, + DME1737_REG_PWM(ix)); + data->pwm_freq[ix] = dme1737_read(client, + DME1737_REG_PWM_FREQ(ix)); + /* pwm_config and pwm_min exist only for pwm[1-3] */ + if (ix < 3) { + data->pwm_config[ix] = dme1737_read(client, + DME1737_REG_PWM_CONFIG(ix)); + data->pwm_min[ix] = dme1737_read(client, + DME1737_REG_PWM_MIN(ix)); + } + } + for (ix = 0; ix < ARRAY_SIZE(data->pwm_rr); ix++) { + data->pwm_rr[ix] = dme1737_read(client, + DME1737_REG_PWM_RR(ix)); + } + + /* Thermal zone registers */ + for (ix = 0; ix < ARRAY_SIZE(data->zone_low); ix++) { + data->zone_low[ix] = dme1737_read(client, + DME1737_REG_ZONE_LOW(ix)); + data->zone_abs[ix] = dme1737_read(client, + DME1737_REG_ZONE_ABS(ix)); + } + for (ix = 0; ix < ARRAY_SIZE(data->zone_hyst); ix++) { + data->zone_hyst[ix] = dme1737_read(client, + DME1737_REG_ZONE_HYST(ix)); + } + + /* Alarm registers */ + data->alarms = dme1737_read(client, + DME1737_REG_ALARM1); + /* Bit 7 tells us if the other alarm registers are non-zero and + * therefore also need to be read */ + if (data->alarms & 0x80) { + data->alarms |= dme1737_read(client, + DME1737_REG_ALARM2) << 8; + data->alarms |= dme1737_read(client, + DME1737_REG_ALARM3) << 16; + } + + data->last_update = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* --------------------------------------------------------------------- + * Voltage sysfs attributes + * ix = [0-5] + * --------------------------------------------------------------------- */ + +#define SYS_IN_INPUT 0 +#define SYS_IN_MIN 1 +#define SYS_IN_MAX 2 +#define SYS_IN_ALARM 3 + +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dme1737_data *data = dme1737_update_device(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SYS_IN_INPUT: + res = IN_FROM_REG(data->in[ix], ix, 16); + break; + case SYS_IN_MIN: + res = IN_FROM_REG(data->in_min[ix], ix, 8); + break; + case SYS_IN_MAX: + res = IN_FROM_REG(data->in_max[ix], ix, 8); + break; + case SYS_IN_ALARM: + res = (data->alarms >> DME1737_BIT_ALARM_IN[ix]) & 0x01; + break; + default: + res = 0; + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static ssize_t set_in(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct dme1737_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + long val = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + switch (fn) { + case SYS_IN_MIN: + data->in_min[ix] = IN_TO_REG(val, ix); + dme1737_write(client, DME1737_REG_IN_MIN(ix), + data->in_min[ix]); + break; + case SYS_IN_MAX: + data->in_max[ix] = IN_TO_REG(val, ix); + dme1737_write(client, DME1737_REG_IN_MAX(ix), + data->in_max[ix]); + break; + default: + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * Temperature sysfs attributes + * ix = [0-2] + * --------------------------------------------------------------------- */ + +#define SYS_TEMP_INPUT 0 +#define SYS_TEMP_MIN 1 +#define SYS_TEMP_MAX 2 +#define SYS_TEMP_OFFSET 3 +#define SYS_TEMP_ALARM 4 +#define SYS_TEMP_FAULT 5 + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dme1737_data *data = dme1737_update_device(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SYS_TEMP_INPUT: + res = TEMP_FROM_REG(data->temp[ix], 16); + break; + case SYS_TEMP_MIN: + res = TEMP_FROM_REG(data->temp_min[ix], 8); + break; + case SYS_TEMP_MAX: + res = TEMP_FROM_REG(data->temp_max[ix], 8); + break; + case SYS_TEMP_OFFSET: + res = TEMP_FROM_REG(data->temp_offset[ix], 8); + break; + case SYS_TEMP_ALARM: + res = (data->alarms >> DME1737_BIT_ALARM_TEMP[ix]) & 0x01; + break; + case SYS_TEMP_FAULT: + res = (data->temp[ix] == 0x0800); + break; + default: + res = 0; + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct dme1737_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + long val = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + switch (fn) { + case SYS_TEMP_MIN: + data->temp_min[ix] = TEMP_TO_REG(val); + dme1737_write(client, DME1737_REG_TEMP_MIN(ix), + data->temp_min[ix]); + break; + case SYS_TEMP_MAX: + data->temp_max[ix] = TEMP_TO_REG(val); + dme1737_write(client, DME1737_REG_TEMP_MAX(ix), + data->temp_max[ix]); + break; + case SYS_TEMP_OFFSET: + data->temp_offset[ix] = TEMP_TO_REG(val); + dme1737_write(client, DME1737_REG_TEMP_OFFSET(ix), + data->temp_offset[ix]); + break; + default: + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * Zone sysfs attributes + * ix = [0-2] + * --------------------------------------------------------------------- */ + +#define SYS_ZONE_AUTO_CHANNELS_TEMP 0 +#define SYS_ZONE_AUTO_POINT1_TEMP_HYST 1 +#define SYS_ZONE_AUTO_POINT1_TEMP 2 +#define SYS_ZONE_AUTO_POINT2_TEMP 3 +#define SYS_ZONE_AUTO_POINT3_TEMP 4 + +static ssize_t show_zone(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dme1737_data *data = dme1737_update_device(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SYS_ZONE_AUTO_CHANNELS_TEMP: + /* check config2 for non-standard temp-to-zone mapping */ + if ((ix == 1) && (data->config2 & 0x02)) { + res = 4; + } else { + res = 1 << ix; + } + break; + case SYS_ZONE_AUTO_POINT1_TEMP_HYST: + res = TEMP_FROM_REG(data->zone_low[ix], 8) - + TEMP_HYST_FROM_REG(data->zone_hyst[ix == 2], ix); + break; + case SYS_ZONE_AUTO_POINT1_TEMP: + res = TEMP_FROM_REG(data->zone_low[ix], 8); + break; + case SYS_ZONE_AUTO_POINT2_TEMP: + /* pwm_freq holds the temp range bits in the upper nibble */ + res = TEMP_FROM_REG(data->zone_low[ix], 8) + + TEMP_RANGE_FROM_REG(data->pwm_freq[ix]); + break; + case SYS_ZONE_AUTO_POINT3_TEMP: + res = TEMP_FROM_REG(data->zone_abs[ix], 8); + break; + default: + res = 0; + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static ssize_t set_zone(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct dme1737_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + long val = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + switch (fn) { + case SYS_ZONE_AUTO_POINT1_TEMP_HYST: + /* Refresh the cache */ + data->zone_low[ix] = dme1737_read(client, + DME1737_REG_ZONE_LOW(ix)); + /* Modify the temp hyst value */ + data->zone_hyst[ix == 2] = TEMP_HYST_TO_REG( + TEMP_FROM_REG(data->zone_low[ix], 8) - + val, ix, dme1737_read(client, + DME1737_REG_ZONE_HYST(ix == 2))); + dme1737_write(client, DME1737_REG_ZONE_HYST(ix == 2), + data->zone_hyst[ix == 2]); + break; + case SYS_ZONE_AUTO_POINT1_TEMP: + data->zone_low[ix] = TEMP_TO_REG(val); + dme1737_write(client, DME1737_REG_ZONE_LOW(ix), + data->zone_low[ix]); + break; + case SYS_ZONE_AUTO_POINT2_TEMP: + /* Refresh the cache */ + data->zone_low[ix] = dme1737_read(client, + DME1737_REG_ZONE_LOW(ix)); + /* Modify the temp range value (which is stored in the upper + * nibble of the pwm_freq register) */ + data->pwm_freq[ix] = TEMP_RANGE_TO_REG(val - + TEMP_FROM_REG(data->zone_low[ix], 8), + dme1737_read(client, + DME1737_REG_PWM_FREQ(ix))); + dme1737_write(client, DME1737_REG_PWM_FREQ(ix), + data->pwm_freq[ix]); + break; + case SYS_ZONE_AUTO_POINT3_TEMP: + data->zone_abs[ix] = TEMP_TO_REG(val); + dme1737_write(client, DME1737_REG_ZONE_ABS(ix), + data->zone_abs[ix]); + break; + default: + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * Fan sysfs attributes + * ix = [0-5] + * --------------------------------------------------------------------- */ + +#define SYS_FAN_INPUT 0 +#define SYS_FAN_MIN 1 +#define SYS_FAN_MAX 2 +#define SYS_FAN_ALARM 3 +#define SYS_FAN_TYPE 4 + +static ssize_t show_fan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dme1737_data *data = dme1737_update_device(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SYS_FAN_INPUT: + res = FAN_FROM_REG(data->fan[ix], + ix < 4 ? 0 : + FAN_TPC_FROM_REG(data->fan_opt[ix])); + break; + case SYS_FAN_MIN: + res = FAN_FROM_REG(data->fan_min[ix], + ix < 4 ? 0 : + FAN_TPC_FROM_REG(data->fan_opt[ix])); + break; + case SYS_FAN_MAX: + /* only valid for fan[5-6] */ + res = FAN_MAX_FROM_REG(data->fan_max[ix - 4]); + break; + case SYS_FAN_ALARM: + res = (data->alarms >> DME1737_BIT_ALARM_FAN[ix]) & 0x01; + break; + case SYS_FAN_TYPE: + /* only valid for fan[1-4] */ + res = FAN_TYPE_FROM_REG(data->fan_opt[ix]); + break; + default: + res = 0; + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static ssize_t set_fan(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct dme1737_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + long val = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + switch (fn) { + case SYS_FAN_MIN: + if (ix < 4) { + data->fan_min[ix] = FAN_TO_REG(val, 0); + } else { + /* Refresh the cache */ + data->fan_opt[ix] = dme1737_read(client, + DME1737_REG_FAN_OPT(ix)); + /* Modify the fan min value */ + data->fan_min[ix] = FAN_TO_REG(val, + FAN_TPC_FROM_REG(data->fan_opt[ix])); + } + dme1737_write(client, DME1737_REG_FAN_MIN(ix), + data->fan_min[ix] & 0xff); + dme1737_write(client, DME1737_REG_FAN_MIN(ix) + 1, + data->fan_min[ix] >> 8); + break; + case SYS_FAN_MAX: + /* Only valid for fan[5-6] */ + data->fan_max[ix - 4] = FAN_MAX_TO_REG(val); + dme1737_write(client, DME1737_REG_FAN_MAX(ix), + data->fan_max[ix - 4]); + break; + case SYS_FAN_TYPE: + /* Only valid for fan[1-4] */ + if (!(val == 1 || val == 2 || val == 4)) { + count = -EINVAL; + dev_warn(&client->dev, "Fan type value %ld not " + "supported. Choose one of 1, 2, or 4.\n", + val); + goto exit; + } + data->fan_opt[ix] = FAN_TYPE_TO_REG(val, dme1737_read(client, + DME1737_REG_FAN_OPT(ix))); + dme1737_write(client, DME1737_REG_FAN_OPT(ix), + data->fan_opt[ix]); + break; + default: + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } +exit: + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * PWM sysfs attributes + * ix = [0-4] + * --------------------------------------------------------------------- */ + +#define SYS_PWM 0 +#define SYS_PWM_FREQ 1 +#define SYS_PWM_ENABLE 2 +#define SYS_PWM_RAMP_RATE 3 +#define SYS_PWM_AUTO_CHANNELS_ZONE 4 +#define SYS_PWM_AUTO_PWM_MIN 5 +#define SYS_PWM_AUTO_POINT1_PWM 6 +#define SYS_PWM_AUTO_POINT2_PWM 7 + +static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dme1737_data *data = dme1737_update_device(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SYS_PWM: + if (PWM_EN_FROM_REG(data->pwm_config[ix]) == 0) { + res = 255; + } else { + res = data->pwm[ix]; + } + break; + case SYS_PWM_FREQ: + res = PWM_FREQ_FROM_REG(data->pwm_freq[ix]); + break; + case SYS_PWM_ENABLE: + if (ix > 3) { + res = 1; /* pwm[5-6] hard-wired to manual mode */ + } else { + res = PWM_EN_FROM_REG(data->pwm_config[ix]); + } + break; + case SYS_PWM_RAMP_RATE: + /* Only valid for pwm[1-3] */ + res = PWM_RR_FROM_REG(data->pwm_rr[ix > 0], ix); + break; + case SYS_PWM_AUTO_CHANNELS_ZONE: + /* Only valid for pwm[1-3] */ + if (PWM_EN_FROM_REG(data->pwm_config[ix]) == 2) { + res = PWM_ACZ_FROM_REG(data->pwm_config[ix]); + } else { + res = data->pwm_acz[ix]; + } + break; + case SYS_PWM_AUTO_PWM_MIN: + /* Only valid for pwm[1-3] */ + if (PWM_OFF_FROM_REG(data->pwm_rr[0], ix)) { + res = data->pwm_min[ix]; + } else { + res = 0; + } + break; + case SYS_PWM_AUTO_POINT1_PWM: + /* Only valid for pwm[1-3] */ + res = data->pwm_min[ix]; + break; + case SYS_PWM_AUTO_POINT2_PWM: + /* Only valid for pwm[1-3] */ + res = 255; /* hard-wired */ + break; + default: + res = 0; + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static struct attribute *dme1737_attr_pwm[]; +static void dme1737_chmod_file(struct i2c_client*, struct attribute*, mode_t); + +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct dme1737_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + long val = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + switch (fn) { + case SYS_PWM: + data->pwm[ix] = SENSORS_LIMIT(val, 0, 255); + dme1737_write(client, DME1737_REG_PWM(ix), data->pwm[ix]); + break; + case SYS_PWM_FREQ: + data->pwm_freq[ix] = PWM_FREQ_TO_REG(val, dme1737_read(client, + DME1737_REG_PWM_FREQ(ix))); + dme1737_write(client, DME1737_REG_PWM_FREQ(ix), + data->pwm_freq[ix]); + break; + case SYS_PWM_ENABLE: + /* Only valid for pwm[1-3] */ + if (val < 0 || val > 2) { + count = -EINVAL; + dev_warn(&client->dev, "PWM enable %ld not " + "supported. Choose one of 0, 1, or 2.\n", + val); + goto exit; + } + /* Refresh the cache */ + data->pwm_config[ix] = dme1737_read(client, + DME1737_REG_PWM_CONFIG(ix)); + if (val == PWM_EN_FROM_REG(data->pwm_config[ix])) { + /* Bail out if no change */ + goto exit; + } + /* Do some housekeeping if we are currently in auto mode */ + if (PWM_EN_FROM_REG(data->pwm_config[ix]) == 2) { + /* Save the current zone channel assignment */ + data->pwm_acz[ix] = PWM_ACZ_FROM_REG( + data->pwm_config[ix]); + /* Save the current ramp rate state and disable it */ + data->pwm_rr[ix > 0] = dme1737_read(client, + DME1737_REG_PWM_RR(ix > 0)); + data->pwm_rr_en &= ~(1 << ix); + if (PWM_RR_EN_FROM_REG(data->pwm_rr[ix > 0], ix)) { + data->pwm_rr_en |= (1 << ix); + data->pwm_rr[ix > 0] = PWM_RR_EN_TO_REG(0, ix, + data->pwm_rr[ix > 0]); + dme1737_write(client, + DME1737_REG_PWM_RR(ix > 0), + data->pwm_rr[ix > 0]); + } + } + /* Set the new PWM mode */ + switch (val) { + case 0: + /* Change permissions of pwm[ix] to read-only */ + dme1737_chmod_file(client, dme1737_attr_pwm[ix], + S_IRUGO); + /* Turn fan fully on */ + data->pwm_config[ix] = PWM_EN_TO_REG(0, + data->pwm_config[ix]); + dme1737_write(client, DME1737_REG_PWM_CONFIG(ix), + data->pwm_config[ix]); + break; + case 1: + /* Turn on manual mode */ + data->pwm_config[ix] = PWM_EN_TO_REG(1, + data->pwm_config[ix]); + dme1737_write(client, DME1737_REG_PWM_CONFIG(ix), + data->pwm_config[ix]); + /* Change permissions of pwm[ix] to read-writeable */ + dme1737_chmod_file(client, dme1737_attr_pwm[ix], + S_IRUGO | S_IWUSR); + break; + case 2: + /* Change permissions of pwm[ix] to read-only */ + dme1737_chmod_file(client, dme1737_attr_pwm[ix], + S_IRUGO); + /* Turn on auto mode using the saved zone channel + * assignment */ + data->pwm_config[ix] = PWM_ACZ_TO_REG( + data->pwm_acz[ix], + data->pwm_config[ix]); + dme1737_write(client, DME1737_REG_PWM_CONFIG(ix), + data->pwm_config[ix]); + /* Enable PWM ramp rate if previously enabled */ + if (data->pwm_rr_en & (1 << ix)) { + data->pwm_rr[ix > 0] = PWM_RR_EN_TO_REG(1, ix, + dme1737_read(client, + DME1737_REG_PWM_RR(ix > 0))); + dme1737_write(client, + DME1737_REG_PWM_RR(ix > 0), + data->pwm_rr[ix > 0]); + } + break; + } + break; + case SYS_PWM_RAMP_RATE: + /* Only valid for pwm[1-3] */ + /* Refresh the cache */ + data->pwm_config[ix] = dme1737_read(client, + DME1737_REG_PWM_CONFIG(ix)); + data->pwm_rr[ix > 0] = dme1737_read(client, + DME1737_REG_PWM_RR(ix > 0)); + /* Set the ramp rate value */ + if (val > 0) { + data->pwm_rr[ix > 0] = PWM_RR_TO_REG(val, ix, + data->pwm_rr[ix > 0]); + } + /* Enable/disable the feature only if the associated PWM + * output is in automatic mode. */ + if (PWM_EN_FROM_REG(data->pwm_config[ix]) == 2) { + data->pwm_rr[ix > 0] = PWM_RR_EN_TO_REG(val > 0, ix, + data->pwm_rr[ix > 0]); + } + dme1737_write(client, DME1737_REG_PWM_RR(ix > 0), + data->pwm_rr[ix > 0]); + break; + case SYS_PWM_AUTO_CHANNELS_ZONE: + /* Only valid for pwm[1-3] */ + if (!(val == 1 || val == 2 || val == 4 || + val == 6 || val == 7)) { + count = -EINVAL; + dev_warn(&client->dev, "PWM auto channels zone %ld " + "not supported. Choose one of 1, 2, 4, 6, " + "or 7.\n", val); + goto exit; + } + /* Refresh the cache */ + data->pwm_config[ix] = dme1737_read(client, + DME1737_REG_PWM_CONFIG(ix)); + if (PWM_EN_FROM_REG(data->pwm_config[ix]) == 2) { + /* PWM is already in auto mode so update the temp + * channel assignment */ + data->pwm_config[ix] = PWM_ACZ_TO_REG(val, + data->pwm_config[ix]); + dme1737_write(client, DME1737_REG_PWM_CONFIG(ix), + data->pwm_config[ix]); + } else { + /* PWM is not in auto mode so we save the temp + * channel assignment for later use */ + data->pwm_acz[ix] = val; + } + break; + case SYS_PWM_AUTO_PWM_MIN: + /* Only valid for pwm[1-3] */ + /* Refresh the cache */ + data->pwm_min[ix] = dme1737_read(client, + DME1737_REG_PWM_MIN(ix)); + /* There are only 2 values supported for the auto_pwm_min + * value: 0 or auto_point1_pwm. So if the temperature drops + * below the auto_point1_temp_hyst value, the fan either turns + * off or runs at auto_point1_pwm duty-cycle. */ + if (val > ((data->pwm_min[ix] + 1) / 2)) { + data->pwm_rr[0] = PWM_OFF_TO_REG(1, ix, + dme1737_read(client, + DME1737_REG_PWM_RR(0))); + + } else { + data->pwm_rr[0] = PWM_OFF_TO_REG(0, ix, + dme1737_read(client, + DME1737_REG_PWM_RR(0))); + + } + dme1737_write(client, DME1737_REG_PWM_RR(0), + data->pwm_rr[0]); + break; + case SYS_PWM_AUTO_POINT1_PWM: + /* Only valid for pwm[1-3] */ + data->pwm_min[ix] = SENSORS_LIMIT(val, 0, 255); + dme1737_write(client, DME1737_REG_PWM_MIN(ix), + data->pwm_min[ix]); + break; + default: + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } +exit: + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * Miscellaneous sysfs attributes + * --------------------------------------------------------------------- */ + +static ssize_t show_vrm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct dme1737_data *data = i2c_get_clientdata(client); + + return sprintf(buf, "%d\n", data->vrm); +} + +static ssize_t set_vrm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct dme1737_data *data = i2c_get_clientdata(client); + long val = simple_strtol(buf, NULL, 10); + + data->vrm = val; + return count; +} + +static ssize_t show_vid(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dme1737_data *data = dme1737_update_device(dev); + + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} + +/* --------------------------------------------------------------------- + * Sysfs device attribute defines and structs + * --------------------------------------------------------------------- */ + +/* Voltages 0-6 */ + +#define SENSOR_DEVICE_ATTR_IN(ix) \ +static SENSOR_DEVICE_ATTR_2(in##ix##_input, S_IRUGO, \ + show_in, NULL, SYS_IN_INPUT, ix); \ +static SENSOR_DEVICE_ATTR_2(in##ix##_min, S_IRUGO | S_IWUSR, \ + show_in, set_in, SYS_IN_MIN, ix); \ +static SENSOR_DEVICE_ATTR_2(in##ix##_max, S_IRUGO | S_IWUSR, \ + show_in, set_in, SYS_IN_MAX, ix); \ +static SENSOR_DEVICE_ATTR_2(in##ix##_alarm, S_IRUGO, \ + show_in, NULL, SYS_IN_ALARM, ix) + +SENSOR_DEVICE_ATTR_IN(0); +SENSOR_DEVICE_ATTR_IN(1); +SENSOR_DEVICE_ATTR_IN(2); +SENSOR_DEVICE_ATTR_IN(3); +SENSOR_DEVICE_ATTR_IN(4); +SENSOR_DEVICE_ATTR_IN(5); +SENSOR_DEVICE_ATTR_IN(6); + +/* Temperatures 1-3 */ + +#define SENSOR_DEVICE_ATTR_TEMP(ix) \ +static SENSOR_DEVICE_ATTR_2(temp##ix##_input, S_IRUGO, \ + show_temp, NULL, SYS_TEMP_INPUT, ix-1); \ +static SENSOR_DEVICE_ATTR_2(temp##ix##_min, S_IRUGO | S_IWUSR, \ + show_temp, set_temp, SYS_TEMP_MIN, ix-1); \ +static SENSOR_DEVICE_ATTR_2(temp##ix##_max, S_IRUGO | S_IWUSR, \ + show_temp, set_temp, SYS_TEMP_MAX, ix-1); \ +static SENSOR_DEVICE_ATTR_2(temp##ix##_offset, S_IRUGO, \ + show_temp, set_temp, SYS_TEMP_OFFSET, ix-1); \ +static SENSOR_DEVICE_ATTR_2(temp##ix##_alarm, S_IRUGO, \ + show_temp, NULL, SYS_TEMP_ALARM, ix-1); \ +static SENSOR_DEVICE_ATTR_2(temp##ix##_fault, S_IRUGO, \ + show_temp, NULL, SYS_TEMP_FAULT, ix-1) + +SENSOR_DEVICE_ATTR_TEMP(1); +SENSOR_DEVICE_ATTR_TEMP(2); +SENSOR_DEVICE_ATTR_TEMP(3); + +/* Zones 1-3 */ + +#define SENSOR_DEVICE_ATTR_ZONE(ix) \ +static SENSOR_DEVICE_ATTR_2(zone##ix##_auto_channels_temp, S_IRUGO, \ + show_zone, NULL, SYS_ZONE_AUTO_CHANNELS_TEMP, ix-1); \ +static SENSOR_DEVICE_ATTR_2(zone##ix##_auto_point1_temp_hyst, S_IRUGO, \ + show_zone, set_zone, SYS_ZONE_AUTO_POINT1_TEMP_HYST, ix-1); \ +static SENSOR_DEVICE_ATTR_2(zone##ix##_auto_point1_temp, S_IRUGO, \ + show_zone, set_zone, SYS_ZONE_AUTO_POINT1_TEMP, ix-1); \ +static SENSOR_DEVICE_ATTR_2(zone##ix##_auto_point2_temp, S_IRUGO, \ + show_zone, set_zone, SYS_ZONE_AUTO_POINT2_TEMP, ix-1); \ +static SENSOR_DEVICE_ATTR_2(zone##ix##_auto_point3_temp, S_IRUGO, \ + show_zone, set_zone, SYS_ZONE_AUTO_POINT3_TEMP, ix-1) + +SENSOR_DEVICE_ATTR_ZONE(1); +SENSOR_DEVICE_ATTR_ZONE(2); +SENSOR_DEVICE_ATTR_ZONE(3); + +/* Fans 1-4 */ + +#define SENSOR_DEVICE_ATTR_FAN_1TO4(ix) \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_input, S_IRUGO, \ + show_fan, NULL, SYS_FAN_INPUT, ix-1); \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_min, S_IRUGO | S_IWUSR, \ + show_fan, set_fan, SYS_FAN_MIN, ix-1); \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_alarm, S_IRUGO, \ + show_fan, NULL, SYS_FAN_ALARM, ix-1); \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_type, S_IRUGO | S_IWUSR, \ + show_fan, set_fan, SYS_FAN_TYPE, ix-1) + +SENSOR_DEVICE_ATTR_FAN_1TO4(1); +SENSOR_DEVICE_ATTR_FAN_1TO4(2); +SENSOR_DEVICE_ATTR_FAN_1TO4(3); +SENSOR_DEVICE_ATTR_FAN_1TO4(4); + +/* Fans 5-6 */ + +#define SENSOR_DEVICE_ATTR_FAN_5TO6(ix) \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_input, S_IRUGO, \ + show_fan, NULL, SYS_FAN_INPUT, ix-1); \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_min, S_IRUGO | S_IWUSR, \ + show_fan, set_fan, SYS_FAN_MIN, ix-1); \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_alarm, S_IRUGO, \ + show_fan, NULL, SYS_FAN_ALARM, ix-1); \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_max, S_IRUGO | S_IWUSR, \ + show_fan, set_fan, SYS_FAN_MAX, ix-1) + +SENSOR_DEVICE_ATTR_FAN_5TO6(5); +SENSOR_DEVICE_ATTR_FAN_5TO6(6); + +/* PWMs 1-3 */ + +#define SENSOR_DEVICE_ATTR_PWM_1TO3(ix) \ +static SENSOR_DEVICE_ATTR_2(pwm##ix, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_freq, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_FREQ, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_enable, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_ENABLE, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_ramp_rate, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_RAMP_RATE, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_auto_channels_zone, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_AUTO_CHANNELS_ZONE, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_auto_pwm_min, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_AUTO_PWM_MIN, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_auto_point1_pwm, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_AUTO_POINT1_PWM, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_auto_point2_pwm, S_IRUGO, \ + show_pwm, NULL, SYS_PWM_AUTO_POINT2_PWM, ix-1) + +SENSOR_DEVICE_ATTR_PWM_1TO3(1); +SENSOR_DEVICE_ATTR_PWM_1TO3(2); +SENSOR_DEVICE_ATTR_PWM_1TO3(3); + +/* PWMs 5-6 */ + +#define SENSOR_DEVICE_ATTR_PWM_5TO6(ix) \ +static SENSOR_DEVICE_ATTR_2(pwm##ix, S_IRUGO | S_IWUSR, \ + show_pwm, set_pwm, SYS_PWM, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_freq, S_IRUGO | S_IWUSR, \ + show_pwm, set_pwm, SYS_PWM_FREQ, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_enable, S_IRUGO, \ + show_pwm, NULL, SYS_PWM_ENABLE, ix-1) + +SENSOR_DEVICE_ATTR_PWM_5TO6(5); +SENSOR_DEVICE_ATTR_PWM_5TO6(6); + +/* Misc */ + +static DEVICE_ATTR(vrm, S_IRUGO | S_IWUSR, show_vrm, set_vrm); +static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid, NULL); + +#define SENSOR_DEV_ATTR_IN(ix) \ +&sensor_dev_attr_in##ix##_input.dev_attr.attr, \ +&sensor_dev_attr_in##ix##_min.dev_attr.attr, \ +&sensor_dev_attr_in##ix##_max.dev_attr.attr, \ +&sensor_dev_attr_in##ix##_alarm.dev_attr.attr + +/* These attributes are read-writeable only if the chip is *not* locked */ +#define SENSOR_DEV_ATTR_TEMP_LOCK(ix) \ +&sensor_dev_attr_temp##ix##_offset.dev_attr.attr + +#define SENSOR_DEV_ATTR_TEMP(ix) \ +SENSOR_DEV_ATTR_TEMP_LOCK(ix), \ +&sensor_dev_attr_temp##ix##_input.dev_attr.attr, \ +&sensor_dev_attr_temp##ix##_min.dev_attr.attr, \ +&sensor_dev_attr_temp##ix##_max.dev_attr.attr, \ +&sensor_dev_attr_temp##ix##_alarm.dev_attr.attr, \ +&sensor_dev_attr_temp##ix##_fault.dev_attr.attr + +/* These attributes are read-writeable only if the chip is *not* locked */ +#define SENSOR_DEV_ATTR_ZONE_LOCK(ix) \ +&sensor_dev_attr_zone##ix##_auto_point1_temp_hyst.dev_attr.attr, \ +&sensor_dev_attr_zone##ix##_auto_point1_temp.dev_attr.attr, \ +&sensor_dev_attr_zone##ix##_auto_point2_temp.dev_attr.attr, \ +&sensor_dev_attr_zone##ix##_auto_point3_temp.dev_attr.attr + +#define SENSOR_DEV_ATTR_ZONE(ix) \ +SENSOR_DEV_ATTR_ZONE_LOCK(ix), \ +&sensor_dev_attr_zone##ix##_auto_channels_temp.dev_attr.attr + +#define SENSOR_DEV_ATTR_FAN_1TO4(ix) \ +&sensor_dev_attr_fan##ix##_input.dev_attr.attr, \ +&sensor_dev_attr_fan##ix##_min.dev_attr.attr, \ +&sensor_dev_attr_fan##ix##_alarm.dev_attr.attr, \ +&sensor_dev_attr_fan##ix##_type.dev_attr.attr + +#define SENSOR_DEV_ATTR_FAN_5TO6(ix) \ +&sensor_dev_attr_fan##ix##_input.dev_attr.attr, \ +&sensor_dev_attr_fan##ix##_min.dev_attr.attr, \ +&sensor_dev_attr_fan##ix##_alarm.dev_attr.attr, \ +&sensor_dev_attr_fan##ix##_max.dev_attr.attr + +/* These attributes are read-writeable only if the chip is *not* locked */ +#define SENSOR_DEV_ATTR_PWM_1TO3_LOCK(ix) \ +&sensor_dev_attr_pwm##ix##_freq.dev_attr.attr, \ +&sensor_dev_attr_pwm##ix##_enable.dev_attr.attr, \ +&sensor_dev_attr_pwm##ix##_ramp_rate.dev_attr.attr, \ +&sensor_dev_attr_pwm##ix##_auto_channels_zone.dev_attr.attr, \ +&sensor_dev_attr_pwm##ix##_auto_pwm_min.dev_attr.attr, \ +&sensor_dev_attr_pwm##ix##_auto_point1_pwm.dev_attr.attr + +#define SENSOR_DEV_ATTR_PWM_1TO3(ix) \ +SENSOR_DEV_ATTR_PWM_1TO3_LOCK(ix), \ +&sensor_dev_attr_pwm##ix.dev_attr.attr, \ +&sensor_dev_attr_pwm##ix##_auto_point2_pwm.dev_attr.attr + +/* These attributes are read-writeable only if the chip is *not* locked */ +#define SENSOR_DEV_ATTR_PWM_5TO6_LOCK(ix) \ +&sensor_dev_attr_pwm##ix.dev_attr.attr, \ +&sensor_dev_attr_pwm##ix##_freq.dev_attr.attr + +#define SENSOR_DEV_ATTR_PWM_5TO6(ix) \ +SENSOR_DEV_ATTR_PWM_5TO6_LOCK(ix), \ +&sensor_dev_attr_pwm##ix##_enable.dev_attr.attr + +/* This struct holds all the attributes that are always present and need to be + * created unconditionally. The attributes that need modification of their + * permissions are created read-only and write permissions are added or removed + * on the fly when required */ +static struct attribute *dme1737_attr[] ={ + /* Voltages */ + SENSOR_DEV_ATTR_IN(0), + SENSOR_DEV_ATTR_IN(1), + SENSOR_DEV_ATTR_IN(2), + SENSOR_DEV_ATTR_IN(3), + SENSOR_DEV_ATTR_IN(4), + SENSOR_DEV_ATTR_IN(5), + SENSOR_DEV_ATTR_IN(6), + /* Temperatures */ + SENSOR_DEV_ATTR_TEMP(1), + SENSOR_DEV_ATTR_TEMP(2), + SENSOR_DEV_ATTR_TEMP(3), + /* Zones */ + SENSOR_DEV_ATTR_ZONE(1), + SENSOR_DEV_ATTR_ZONE(2), + SENSOR_DEV_ATTR_ZONE(3), + /* Misc */ + &dev_attr_vrm.attr, + &dev_attr_cpu0_vid.attr, + NULL +}; + +static const struct attribute_group dme1737_group = { + .attrs = dme1737_attr, +}; + +/* The following structs hold the PWM attributes, some of which are optional. + * Their creation depends on the chip configuration which is determined during + * module load. */ +static struct attribute *dme1737_attr_pwm1[] = { + SENSOR_DEV_ATTR_PWM_1TO3(1), + NULL +}; +static struct attribute *dme1737_attr_pwm2[] = { + SENSOR_DEV_ATTR_PWM_1TO3(2), + NULL +}; +static struct attribute *dme1737_attr_pwm3[] = { + SENSOR_DEV_ATTR_PWM_1TO3(3), + NULL +}; +static struct attribute *dme1737_attr_pwm5[] = { + SENSOR_DEV_ATTR_PWM_5TO6(5), + NULL +}; +static struct attribute *dme1737_attr_pwm6[] = { + SENSOR_DEV_ATTR_PWM_5TO6(6), + NULL +}; + +static const struct attribute_group dme1737_pwm_group[] = { + { .attrs = dme1737_attr_pwm1 }, + { .attrs = dme1737_attr_pwm2 }, + { .attrs = dme1737_attr_pwm3 }, + { .attrs = NULL }, + { .attrs = dme1737_attr_pwm5 }, + { .attrs = dme1737_attr_pwm6 }, +}; + +/* The following structs hold the fan attributes, some of which are optional. + * Their creation depends on the chip configuration which is determined during + * module load. */ +static struct attribute *dme1737_attr_fan1[] = { + SENSOR_DEV_ATTR_FAN_1TO4(1), + NULL +}; +static struct attribute *dme1737_attr_fan2[] = { + SENSOR_DEV_ATTR_FAN_1TO4(2), + NULL +}; +static struct attribute *dme1737_attr_fan3[] = { + SENSOR_DEV_ATTR_FAN_1TO4(3), + NULL +}; +static struct attribute *dme1737_attr_fan4[] = { + SENSOR_DEV_ATTR_FAN_1TO4(4), + NULL +}; +static struct attribute *dme1737_attr_fan5[] = { + SENSOR_DEV_ATTR_FAN_5TO6(5), + NULL +}; +static struct attribute *dme1737_attr_fan6[] = { + SENSOR_DEV_ATTR_FAN_5TO6(6), + NULL +}; + +static const struct attribute_group dme1737_fan_group[] = { + { .attrs = dme1737_attr_fan1 }, + { .attrs = dme1737_attr_fan2 }, + { .attrs = dme1737_attr_fan3 }, + { .attrs = dme1737_attr_fan4 }, + { .attrs = dme1737_attr_fan5 }, + { .attrs = dme1737_attr_fan6 }, +}; + +/* The permissions of all of the following attributes are changed to read- + * writeable if the chip is *not* locked. Otherwise they stay read-only. */ +static struct attribute *dme1737_attr_lock[] = { + /* Temperatures */ + SENSOR_DEV_ATTR_TEMP_LOCK(1), + SENSOR_DEV_ATTR_TEMP_LOCK(2), + SENSOR_DEV_ATTR_TEMP_LOCK(3), + /* Zones */ + SENSOR_DEV_ATTR_ZONE_LOCK(1), + SENSOR_DEV_ATTR_ZONE_LOCK(2), + SENSOR_DEV_ATTR_ZONE_LOCK(3), + NULL +}; + +static const struct attribute_group dme1737_lock_group = { + .attrs = dme1737_attr_lock, +}; + +/* The permissions of the following PWM attributes are changed to read- + * writeable if the chip is *not* locked and the respective PWM is available. + * Otherwise they stay read-only. */ +static struct attribute *dme1737_attr_pwm1_lock[] = { + SENSOR_DEV_ATTR_PWM_1TO3_LOCK(1), + NULL +}; +static struct attribute *dme1737_attr_pwm2_lock[] = { + SENSOR_DEV_ATTR_PWM_1TO3_LOCK(2), + NULL +}; +static struct attribute *dme1737_attr_pwm3_lock[] = { + SENSOR_DEV_ATTR_PWM_1TO3_LOCK(3), + NULL +}; +static struct attribute *dme1737_attr_pwm5_lock[] = { + SENSOR_DEV_ATTR_PWM_5TO6_LOCK(5), + NULL +}; +static struct attribute *dme1737_attr_pwm6_lock[] = { + SENSOR_DEV_ATTR_PWM_5TO6_LOCK(6), + NULL +}; + +static const struct attribute_group dme1737_pwm_lock_group[] = { + { .attrs = dme1737_attr_pwm1_lock }, + { .attrs = dme1737_attr_pwm2_lock }, + { .attrs = dme1737_attr_pwm3_lock }, + { .attrs = NULL }, + { .attrs = dme1737_attr_pwm5_lock }, + { .attrs = dme1737_attr_pwm6_lock }, +}; + +/* Pwm[1-3] are read-writeable if the associated pwm is in manual mode and the + * chip is not locked. Otherwise they are read-only. */ +static struct attribute *dme1737_attr_pwm[] = { + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, +}; + +/* --------------------------------------------------------------------- + * Super-IO functions + * --------------------------------------------------------------------- */ + +static inline int dme1737_sio_inb(int sio_cip, int reg) +{ + outb(reg, sio_cip); + return inb(sio_cip + 1); +} + +static inline void dme1737_sio_outb(int sio_cip, int reg, int val) +{ + outb(reg, sio_cip); + outb(val, sio_cip + 1); +} + +static int dme1737_sio_get_features(int sio_cip, struct i2c_client *client) +{ + struct dme1737_data *data = i2c_get_clientdata(client); + int err = 0, reg; + u16 addr; + + /* Enter configuration mode */ + outb(0x55, sio_cip); + + /* Check device ID + * The DME1737 can return either 0x78 or 0x77 as its device ID. */ + reg = dme1737_sio_inb(sio_cip, 0x20); + if (!(reg == 0x77 || reg == 0x78)) { + err = -ENODEV; + goto exit; + } + + /* Select logical device A (runtime registers) */ + dme1737_sio_outb(sio_cip, 0x07, 0x0a); + + /* Get the base address of the runtime registers */ + if (!(addr = (dme1737_sio_inb(sio_cip, 0x60) << 8) | + dme1737_sio_inb(sio_cip, 0x61))) { + err = -ENODEV; + goto exit; + } + + /* Read the runtime registers to determine which optional features + * are enabled and available. Bits [3:2] of registers 0x43-0x46 are set + * to '10' if the respective feature is enabled. */ + if ((inb(addr + 0x43) & 0x0c) == 0x08) { /* fan6 */ + data->has_fan |= (1 << 5); + } + if ((inb(addr + 0x44) & 0x0c) == 0x08) { /* pwm6 */ + data->has_pwm |= (1 << 5); + } + if ((inb(addr + 0x45) & 0x0c) == 0x08) { /* fan5 */ + data->has_fan |= (1 << 4); + } + if ((inb(addr + 0x46) & 0x0c) == 0x08) { /* pwm5 */ + data->has_pwm |= (1 << 4); + } + +exit: + /* Exit configuration mode */ + outb(0xaa, sio_cip); + + return err; +} + +/* --------------------------------------------------------------------- + * Device detection, registration and initialization + * --------------------------------------------------------------------- */ + +static struct i2c_driver dme1737_driver; + +static void dme1737_chmod_file(struct i2c_client *client, + struct attribute *attr, mode_t mode) +{ + if (sysfs_chmod_file(&client->dev.kobj, attr, mode)) { + dev_warn(&client->dev, "Failed to change permissions of %s.\n", + attr->name); + } +} + +static void dme1737_chmod_group(struct i2c_client *client, + const struct attribute_group *group, + mode_t mode) +{ + struct attribute **attr; + + for (attr = group->attrs; *attr; attr++) { + dme1737_chmod_file(client, *attr, mode); + } +} + +static int dme1737_init_client(struct i2c_client *client) +{ + struct dme1737_data *data = i2c_get_clientdata(client); + int ix; + u8 reg; + + data->config = dme1737_read(client, DME1737_REG_CONFIG); + /* Inform if part is not monitoring/started */ + if (!(data->config & 0x01)) { + if (!force_start) { + dev_err(&client->dev, "Device is not monitoring. " + "Use the force_start load parameter to " + "override.\n"); + return -EFAULT; + } + + /* Force monitoring */ + data->config |= 0x01; + dme1737_write(client, DME1737_REG_CONFIG, data->config); + } + /* Inform if part is not ready */ + if (!(data->config & 0x04)) { + dev_err(&client->dev, "Device is not ready.\n"); + return -EFAULT; + } + + data->config2 = dme1737_read(client, DME1737_REG_CONFIG2); + /* Check if optional fan3 input is enabled */ + if (data->config2 & 0x04) { + data->has_fan |= (1 << 2); + } + + /* Fan4 and pwm3 are only available if the client's I2C address + * is the default 0x2e. Otherwise the I/Os associated with these + * functions are used for addr enable/select. */ + if (client->addr == 0x2e) { + data->has_fan |= (1 << 3); + data->has_pwm |= (1 << 2); + } + + /* Determine if the optional fan[5-6] and/or pwm[5-6] are enabled. + * For this, we need to query the runtime registers through the + * Super-IO LPC interface. Try both config ports 0x2e and 0x4e. */ + if (dme1737_sio_get_features(0x2e, client) && + dme1737_sio_get_features(0x4e, client)) { + dev_warn(&client->dev, "Failed to query Super-IO for optional " + "features.\n"); + } + + /* Fan1, fan2, pwm1, and pwm2 are always present */ + data->has_fan |= 0x03; + data->has_pwm |= 0x03; + + dev_info(&client->dev, "Optional features: pwm3=%s, pwm5=%s, pwm6=%s, " + "fan3=%s, fan4=%s, fan5=%s, fan6=%s.\n", + (data->has_pwm & (1 << 2)) ? "yes" : "no", + (data->has_pwm & (1 << 4)) ? "yes" : "no", + (data->has_pwm & (1 << 5)) ? "yes" : "no", + (data->has_fan & (1 << 2)) ? "yes" : "no", + (data->has_fan & (1 << 3)) ? "yes" : "no", + (data->has_fan & (1 << 4)) ? "yes" : "no", + (data->has_fan & (1 << 5)) ? "yes" : "no"); + + reg = dme1737_read(client, DME1737_REG_TACH_PWM); + /* Inform if fan-to-pwm mapping differs from the default */ + if (reg != 0xa4) { + dev_warn(&client->dev, "Non-standard fan to pwm mapping: " + "fan1->pwm%d, fan2->pwm%d, fan3->pwm%d, " + "fan4->pwm%d. Please report to the driver " + "maintainer.\n", + (reg & 0x03) + 1, ((reg >> 2) & 0x03) + 1, + ((reg >> 4) & 0x03) + 1, ((reg >> 6) & 0x03) + 1); + } + + /* Switch pwm[1-3] to manual mode if they are currently disabled and + * set the duty-cycles to 0% (which is identical to the PWMs being + * disabled). */ + if (!(data->config & 0x02)) { + for (ix = 0; ix < 3; ix++) { + data->pwm_config[ix] = dme1737_read(client, + DME1737_REG_PWM_CONFIG(ix)); + if ((data->has_pwm & (1 << ix)) && + (PWM_EN_FROM_REG(data->pwm_config[ix]) == -1)) { + dev_info(&client->dev, "Switching pwm%d to " + "manual mode.\n", ix + 1); + data->pwm_config[ix] = PWM_EN_TO_REG(1, + data->pwm_config[ix]); + dme1737_write(client, DME1737_REG_PWM(ix), 0); + dme1737_write(client, + DME1737_REG_PWM_CONFIG(ix), + data->pwm_config[ix]); + } + } + } + + /* Initialize the default PWM auto channels zone (acz) assignments */ + data->pwm_acz[0] = 1; /* pwm1 -> zone1 */ + data->pwm_acz[1] = 2; /* pwm2 -> zone2 */ + data->pwm_acz[2] = 4; /* pwm3 -> zone3 */ + + /* Set VRM */ + data->vrm = vid_which_vrm(); + + return 0; +} + +static int dme1737_detect(struct i2c_adapter *adapter, int address, + int kind) +{ + u8 company, verstep = 0; + struct i2c_client *client; + struct dme1737_data *data; + int ix, err = 0; + const char *name; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + goto exit; + } + + if (!(data = kzalloc(sizeof(struct dme1737_data), GFP_KERNEL))) { + err = -ENOMEM; + goto exit; + } + + client = &data->client; + i2c_set_clientdata(client, data); + client->addr = address; + client->adapter = adapter; + client->driver = &dme1737_driver; + + /* A negative kind means that the driver was loaded with no force + * parameter (default), so we must identify the chip. */ + if (kind < 0) { + company = dme1737_read(client, DME1737_REG_COMPANY); + verstep = dme1737_read(client, DME1737_REG_VERSTEP); + + if (!((company == DME1737_COMPANY_SMSC) && + ((verstep & DME1737_VERSTEP_MASK) == DME1737_VERSTEP))) { + err = -ENODEV; + goto exit_kfree; + } + } + + kind = dme1737; + name = "dme1737"; + + /* Fill in the remaining client fields and put it into the global + * list */ + strlcpy(client->name, name, I2C_NAME_SIZE); + mutex_init(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(client))) { + goto exit_kfree; + } + + /* Initialize the DME1737 chip */ + if ((err = dme1737_init_client(client))) { + goto exit_detach; + } + + /* Create standard sysfs attributes */ + if ((err = sysfs_create_group(&client->dev.kobj, &dme1737_group))) { + goto exit_detach; + } + + /* Create fan sysfs attributes */ + for (ix = 0; ix < ARRAY_SIZE(dme1737_fan_group); ix++) { + if (data->has_fan & (1 << ix)) { + if ((err = sysfs_create_group(&client->dev.kobj, + &dme1737_fan_group[ix]))) { + goto exit_remove; + } + } + } + + /* Create PWM sysfs attributes */ + for (ix = 0; ix < ARRAY_SIZE(dme1737_pwm_group); ix++) { + if (data->has_pwm & (1 << ix)) { + if ((err = sysfs_create_group(&client->dev.kobj, + &dme1737_pwm_group[ix]))) { + goto exit_remove; + } + } + } + + /* Inform if the device is locked. Otherwise change the permissions of + * selected attributes from read-only to read-writeable. */ + if (data->config & 0x02) { + dev_info(&client->dev, "Device is locked. Some attributes " + "will be read-only.\n"); + } else { + /* Change permissions of standard attributes */ + dme1737_chmod_group(client, &dme1737_lock_group, + S_IRUGO | S_IWUSR); + + /* Change permissions of PWM attributes */ + for (ix = 0; ix < ARRAY_SIZE(dme1737_pwm_lock_group); ix++) { + if (data->has_pwm & (1 << ix)) { + dme1737_chmod_group(client, + &dme1737_pwm_lock_group[ix], + S_IRUGO | S_IWUSR); + } + } + + /* Change permissions of pwm[1-3] if in manual mode */ + for (ix = 0; ix < 3; ix++) { + if ((data->has_pwm & (1 << ix)) && + (PWM_EN_FROM_REG(data->pwm_config[ix]) == 1)) { + dme1737_chmod_file(client, + dme1737_attr_pwm[ix], + S_IRUGO | S_IWUSR); + } + } + } + + /* Register device */ + data->class_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->class_dev)) { + err = PTR_ERR(data->class_dev); + goto exit_remove; + } + + dev_info(&adapter->dev, "Found a DME1737 chip at 0x%02x " + "(rev 0x%02x)\n", client->addr, verstep); + + return 0; + +exit_remove: + for (ix = 0; ix < ARRAY_SIZE(dme1737_fan_group); ix++) { + if (data->has_fan & (1 << ix)) { + sysfs_remove_group(&client->dev.kobj, + &dme1737_fan_group[ix]); + } + } + for (ix = 0; ix < ARRAY_SIZE(dme1737_pwm_group); ix++) { + if (data->has_pwm & (1 << ix)) { + sysfs_remove_group(&client->dev.kobj, + &dme1737_pwm_group[ix]); + } + } + sysfs_remove_group(&client->dev.kobj, &dme1737_group); +exit_detach: + i2c_detach_client(client); +exit_kfree: + kfree(data); +exit: + return err; +} + +static int dme1737_attach_adapter(struct i2c_adapter *adapter) +{ + if (!(adapter->class & I2C_CLASS_HWMON)) { + return 0; + } + + return i2c_probe(adapter, &addr_data, dme1737_detect); +} + +static int dme1737_detach_client(struct i2c_client *client) +{ + struct dme1737_data *data = i2c_get_clientdata(client); + int ix, err; + + hwmon_device_unregister(data->class_dev); + + for (ix = 0; ix < ARRAY_SIZE(dme1737_fan_group); ix++) { + if (data->has_fan & (1 << ix)) { + sysfs_remove_group(&client->dev.kobj, + &dme1737_fan_group[ix]); + } + } + for (ix = 0; ix < ARRAY_SIZE(dme1737_pwm_group); ix++) { + if (data->has_pwm & (1 << ix)) { + sysfs_remove_group(&client->dev.kobj, + &dme1737_pwm_group[ix]); + } + } + sysfs_remove_group(&client->dev.kobj, &dme1737_group); + + if ((err = i2c_detach_client(client))) { + return err; + } + + kfree(data); + return 0; +} + +static struct i2c_driver dme1737_driver = { + .driver = { + .name = "dme1737", + }, + .attach_adapter = dme1737_attach_adapter, + .detach_client = dme1737_detach_client, +}; + +static int __init dme1737_init(void) +{ + return i2c_add_driver(&dme1737_driver); +} + +static void __exit dme1737_exit(void) +{ + i2c_del_driver(&dme1737_driver); +} + +MODULE_AUTHOR("Juerg Haefliger <juergh@gmail.com>"); +MODULE_DESCRIPTION("DME1737 sensors"); +MODULE_LICENSE("GPL"); + +module_init(dme1737_init); +module_exit(dme1737_exit); diff --git a/drivers/hwmon/ds1621.c b/drivers/hwmon/ds1621.c index d5ac422..1212d6b 100644 --- a/drivers/hwmon/ds1621.c +++ b/drivers/hwmon/ds1621.c @@ -27,6 +27,7 @@ #include <linux/jiffies.h> #include <linux/i2c.h> #include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> #include <linux/err.h> #include <linux/mutex.h> #include <linux/sysfs.h> @@ -52,9 +53,11 @@ MODULE_PARM_DESC(polarity, "Output's polarity: 0 = active high, 1 = active low") #define DS1621_REG_CONFIG_DONE 0x80 /* The DS1621 registers */ -#define DS1621_REG_TEMP 0xAA /* word, RO */ -#define DS1621_REG_TEMP_MIN 0xA2 /* word, RW */ -#define DS1621_REG_TEMP_MAX 0xA1 /* word, RW */ +static const u8 DS1621_REG_TEMP[3] = { + 0xAA, /* input, word, RO */ + 0xA2, /* min, word, RW */ + 0xA1, /* max, word, RW */ +}; #define DS1621_REG_CONF 0xAC /* byte, RW */ #define DS1621_COM_START 0xEE /* no data */ #define DS1621_COM_STOP 0x22 /* no data */ @@ -63,10 +66,7 @@ MODULE_PARM_DESC(polarity, "Output's polarity: 0 = active high, 1 = active low") #define DS1621_ALARM_TEMP_HIGH 0x40 #define DS1621_ALARM_TEMP_LOW 0x20 -/* Conversions. Rounding and limit checking is only done on the TO_REG - variants. Note that you should be a bit careful with which arguments - these macros are called: arguments may be evaluated more than once. - Fixing this is just not worth it. */ +/* Conversions */ #define ALARMS_FROM_REG(val) ((val) & \ (DS1621_ALARM_TEMP_HIGH | DS1621_ALARM_TEMP_LOW)) @@ -78,7 +78,7 @@ struct ds1621_data { char valid; /* !=0 if following fields are valid */ unsigned long last_updated; /* In jiffies */ - u16 temp, temp_min, temp_max; /* Register values, word */ + u16 temp[3]; /* Register values, word */ u8 conf; /* Register encoding, combined */ }; @@ -101,7 +101,7 @@ static struct i2c_driver ds1621_driver = { /* All registers are word-sized, except for the configuration register. DS1621 uses a high-byte first convention, which is exactly opposite to - the usual practice. */ + the SMBus standard. */ static int ds1621_read_value(struct i2c_client *client, u8 reg) { if (reg == DS1621_REG_CONF) @@ -110,9 +110,6 @@ static int ds1621_read_value(struct i2c_client *client, u8 reg) return swab16(i2c_smbus_read_word_data(client, reg)); } -/* All registers are word-sized, except for the configuration register. - DS1621 uses a high-byte first convention, which is exactly opposite to - the usual practice. */ static int ds1621_write_value(struct i2c_client *client, u8 reg, u16 value) { if (reg == DS1621_REG_CONF) @@ -139,50 +136,61 @@ static void ds1621_init_client(struct i2c_client *client) i2c_smbus_write_byte(client, DS1621_COM_START); } -#define show(value) \ -static ssize_t show_##value(struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - struct ds1621_data *data = ds1621_update_client(dev); \ - return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->value)); \ +static ssize_t show_temp(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ds1621_data *data = ds1621_update_client(dev); + return sprintf(buf, "%d\n", + LM75_TEMP_FROM_REG(data->temp[attr->index])); } -show(temp); -show(temp_min); -show(temp_max); - -#define set_temp(suffix, value, reg) \ -static ssize_t set_temp_##suffix(struct device *dev, struct device_attribute *attr, const char *buf, \ - size_t count) \ -{ \ - struct i2c_client *client = to_i2c_client(dev); \ - struct ds1621_data *data = ds1621_update_client(dev); \ - u16 val = LM75_TEMP_TO_REG(simple_strtoul(buf, NULL, 10)); \ - \ - mutex_lock(&data->update_lock); \ - data->value = val; \ - ds1621_write_value(client, reg, data->value); \ - mutex_unlock(&data->update_lock); \ - return count; \ -} +static ssize_t set_temp(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct i2c_client *client = to_i2c_client(dev); + struct ds1621_data *data = ds1621_update_client(dev); + u16 val = LM75_TEMP_TO_REG(simple_strtoul(buf, NULL, 10)); -set_temp(min, temp_min, DS1621_REG_TEMP_MIN); -set_temp(max, temp_max, DS1621_REG_TEMP_MAX); + mutex_lock(&data->update_lock); + data->temp[attr->index] = val; + ds1621_write_value(client, DS1621_REG_TEMP[attr->index], + data->temp[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} -static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t show_alarms(struct device *dev, struct device_attribute *da, + char *buf) { struct ds1621_data *data = ds1621_update_client(dev); return sprintf(buf, "%d\n", ALARMS_FROM_REG(data->conf)); } +static ssize_t show_alarm(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ds1621_data *data = ds1621_update_client(dev); + return sprintf(buf, "%d\n", !!(data->conf & attr->index)); +} + static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL); -static DEVICE_ATTR(temp1_input, S_IRUGO , show_temp, NULL); -static DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO , show_temp_min, set_temp_min); -static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp, set_temp, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp, set_temp, 2); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, + DS1621_ALARM_TEMP_LOW); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, + DS1621_ALARM_TEMP_HIGH); static struct attribute *ds1621_attributes[] = { - &dev_attr_temp1_input.attr, - &dev_attr_temp1_min.attr, - &dev_attr_temp1_max.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, &dev_attr_alarms.attr, NULL }; @@ -204,9 +212,9 @@ static int ds1621_detect(struct i2c_adapter *adapter, int address, int kind) { int conf, temp; - struct i2c_client *new_client; + struct i2c_client *client; struct ds1621_data *data; - int err = 0; + int i, err = 0; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA @@ -221,55 +229,44 @@ static int ds1621_detect(struct i2c_adapter *adapter, int address, goto exit; } - new_client = &data->client; - i2c_set_clientdata(new_client, data); - new_client->addr = address; - new_client->adapter = adapter; - new_client->driver = &ds1621_driver; - new_client->flags = 0; - + client = &data->client; + i2c_set_clientdata(client, data); + client->addr = address; + client->adapter = adapter; + client->driver = &ds1621_driver; /* Now, we do the remaining detection. It is lousy. */ if (kind < 0) { /* The NVB bit should be low if no EEPROM write has been requested during the latest 10ms, which is highly improbable in our case. */ - conf = ds1621_read_value(new_client, DS1621_REG_CONF); + conf = ds1621_read_value(client, DS1621_REG_CONF); if (conf & DS1621_REG_CONFIG_NVB) goto exit_free; /* The 7 lowest bits of a temperature should always be 0. */ - temp = ds1621_read_value(new_client, DS1621_REG_TEMP); - if (temp & 0x007f) - goto exit_free; - temp = ds1621_read_value(new_client, DS1621_REG_TEMP_MIN); - if (temp & 0x007f) - goto exit_free; - temp = ds1621_read_value(new_client, DS1621_REG_TEMP_MAX); - if (temp & 0x007f) - goto exit_free; + for (i = 0; i < ARRAY_SIZE(data->temp); i++) { + temp = ds1621_read_value(client, DS1621_REG_TEMP[i]); + if (temp & 0x007f) + goto exit_free; + } } - /* Determine the chip type - only one kind supported! */ - if (kind <= 0) - kind = ds1621; - /* Fill in remaining client fields and put it into the global list */ - strlcpy(new_client->name, "ds1621", I2C_NAME_SIZE); - data->valid = 0; + strlcpy(client->name, "ds1621", I2C_NAME_SIZE); mutex_init(&data->update_lock); /* Tell the I2C layer a new client has arrived */ - if ((err = i2c_attach_client(new_client))) + if ((err = i2c_attach_client(client))) goto exit_free; /* Initialize the DS1621 chip */ - ds1621_init_client(new_client); + ds1621_init_client(client); /* Register sysfs hooks */ - if ((err = sysfs_create_group(&new_client->dev.kobj, &ds1621_group))) + if ((err = sysfs_create_group(&client->dev.kobj, &ds1621_group))) goto exit_detach; - data->class_dev = hwmon_device_register(&new_client->dev); + data->class_dev = hwmon_device_register(&client->dev); if (IS_ERR(data->class_dev)) { err = PTR_ERR(data->class_dev); goto exit_remove_files; @@ -278,9 +275,9 @@ static int ds1621_detect(struct i2c_adapter *adapter, int address, return 0; exit_remove_files: - sysfs_remove_group(&new_client->dev.kobj, &ds1621_group); + sysfs_remove_group(&client->dev.kobj, &ds1621_group); exit_detach: - i2c_detach_client(new_client); + i2c_detach_client(client); exit_free: kfree(data); exit: @@ -314,23 +311,21 @@ static struct ds1621_data *ds1621_update_client(struct device *dev) if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || !data->valid) { + int i; dev_dbg(&client->dev, "Starting ds1621 update\n"); data->conf = ds1621_read_value(client, DS1621_REG_CONF); - data->temp = ds1621_read_value(client, DS1621_REG_TEMP); - - data->temp_min = ds1621_read_value(client, - DS1621_REG_TEMP_MIN); - data->temp_max = ds1621_read_value(client, - DS1621_REG_TEMP_MAX); + for (i = 0; i < ARRAY_SIZE(data->temp); i++) + data->temp[i] = ds1621_read_value(client, + DS1621_REG_TEMP[i]); /* reset alarms if necessary */ new_conf = data->conf; - if (data->temp > data->temp_min) + if (data->temp[0] > data->temp[1]) /* input > min */ new_conf &= ~DS1621_ALARM_TEMP_LOW; - if (data->temp < data->temp_max) + if (data->temp[0] < data->temp[2]) /* input < max */ new_conf &= ~DS1621_ALARM_TEMP_HIGH; if (data->conf != new_conf) ds1621_write_value(client, DS1621_REG_CONF, diff --git a/drivers/hwmon/f71805f.c b/drivers/hwmon/f71805f.c index cdbe309..6f60715 100644 --- a/drivers/hwmon/f71805f.c +++ b/drivers/hwmon/f71805f.c @@ -127,6 +127,13 @@ superio_exit(int base) #define F71805F_REG_TEMP_HIGH(nr) (0x54 + 2 * (nr)) #define F71805F_REG_TEMP_HYST(nr) (0x55 + 2 * (nr)) #define F71805F_REG_TEMP_MODE 0x01 +/* pwm/fan pwmnr from 0 to 2, auto point apnr from 0 to 2 */ +/* map Fintek numbers to our numbers as follows: 9->0, 5->1, 1->2 */ +#define F71805F_REG_PWM_AUTO_POINT_TEMP(pwmnr, apnr) \ + (0xA0 + 0x10 * (pwmnr) + (2 - (apnr))) +#define F71805F_REG_PWM_AUTO_POINT_FAN(pwmnr, apnr) \ + (0xA4 + 0x10 * (pwmnr) + \ + 2 * (2 - (apnr))) #define F71805F_REG_START 0x00 /* status nr from 0 to 2 */ @@ -144,6 +151,11 @@ superio_exit(int base) * Data structures and manipulation thereof */ +struct f71805f_auto_point { + u8 temp[3]; + u16 fan[3]; +}; + struct f71805f_data { unsigned short addr; const char *name; @@ -170,6 +182,7 @@ struct f71805f_data { u8 temp_hyst[3]; u8 temp_mode; unsigned long alarms; + struct f71805f_auto_point auto_points[3]; }; struct f71805f_sio_data { @@ -312,7 +325,7 @@ static void f71805f_write16(struct f71805f_data *data, u8 reg, u16 val) static struct f71805f_data *f71805f_update_device(struct device *dev) { struct f71805f_data *data = dev_get_drvdata(dev); - int nr; + int nr, apnr; mutex_lock(&data->update_lock); @@ -342,6 +355,18 @@ static struct f71805f_data *f71805f_update_device(struct device *dev) F71805F_REG_TEMP_HYST(nr)); } data->temp_mode = f71805f_read8(data, F71805F_REG_TEMP_MODE); + for (nr = 0; nr < 3; nr++) { + for (apnr = 0; apnr < 3; apnr++) { + data->auto_points[nr].temp[apnr] = + f71805f_read8(data, + F71805F_REG_PWM_AUTO_POINT_TEMP(nr, + apnr)); + data->auto_points[nr].fan[apnr] = + f71805f_read16(data, + F71805F_REG_PWM_AUTO_POINT_FAN(nr, + apnr)); + } + } data->last_limits = jiffies; } @@ -705,6 +730,70 @@ static ssize_t set_pwm_freq(struct device *dev, struct device_attribute return count; } +static ssize_t show_pwm_auto_point_temp(struct device *dev, + struct device_attribute *devattr, + char* buf) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + int pwmnr = attr->nr; + int apnr = attr->index; + + return sprintf(buf, "%ld\n", + temp_from_reg(data->auto_points[pwmnr].temp[apnr])); +} + +static ssize_t set_pwm_auto_point_temp(struct device *dev, + struct device_attribute *devattr, + const char* buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + int pwmnr = attr->nr; + int apnr = attr->index; + unsigned long val = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->auto_points[pwmnr].temp[apnr] = temp_to_reg(val); + f71805f_write8(data, F71805F_REG_PWM_AUTO_POINT_TEMP(pwmnr, apnr), + data->auto_points[pwmnr].temp[apnr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_auto_point_fan(struct device *dev, + struct device_attribute *devattr, + char* buf) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + int pwmnr = attr->nr; + int apnr = attr->index; + + return sprintf(buf, "%ld\n", + fan_from_reg(data->auto_points[pwmnr].fan[apnr])); +} + +static ssize_t set_pwm_auto_point_fan(struct device *dev, + struct device_attribute *devattr, + const char* buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + int pwmnr = attr->nr; + int apnr = attr->index; + unsigned long val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->auto_points[pwmnr].fan[apnr] = fan_to_reg(val); + f71805f_write16(data, F71805F_REG_PWM_AUTO_POINT_FAN(pwmnr, apnr), + data->auto_points[pwmnr].fan[apnr]); + mutex_unlock(&data->update_lock); + + return count; +} + static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -932,6 +1021,63 @@ static SENSOR_DEVICE_ATTR(pwm3_freq, S_IRUGO | S_IWUSR, show_pwm_freq, set_pwm_freq, 2); static SENSOR_DEVICE_ATTR(pwm3_mode, S_IRUGO, show_pwm_mode, NULL, 2); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point1_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 0, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point1_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 0, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point2_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 0, 1); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point2_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 0, 1); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point3_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 0, 2); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point3_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 0, 2); + +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point1_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 1, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point1_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 1, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point2_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 1, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point2_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 1, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point3_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 1, 2); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point3_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 1, 2); + +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point1_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point1_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point2_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point2_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point3_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 2, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point3_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 2, 2); + static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); @@ -1014,6 +1160,25 @@ static struct attribute *f71805f_attributes[] = { &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, &sensor_dev_attr_temp3_type.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_fan.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_fan.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_fan.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_fan.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_fan.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_fan.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_fan.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_fan.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_fan.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, &sensor_dev_attr_in1_alarm.dev_attr.attr, &sensor_dev_attr_in2_alarm.dev_attr.attr, @@ -1242,12 +1407,12 @@ static int __devexit f71805f_remove(struct platform_device *pdev) struct resource *res; int i; - platform_set_drvdata(pdev, NULL); hwmon_device_unregister(data->class_dev); sysfs_remove_group(&pdev->dev.kobj, &f71805f_group); for (i = 0; i < 4; i++) sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]); sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq); + platform_set_drvdata(pdev, NULL); kfree(data); res = platform_get_resource(pdev, IORESOURCE_IO, 0); @@ -1290,15 +1455,12 @@ static int __init f71805f_device_add(unsigned short address, goto exit_device_put; } - pdev->dev.platform_data = kmalloc(sizeof(struct f71805f_sio_data), - GFP_KERNEL); - if (!pdev->dev.platform_data) { - err = -ENOMEM; + err = platform_device_add_data(pdev, sio_data, + sizeof(struct f71805f_sio_data)); + if (err) { printk(KERN_ERR DRVNAME ": Platform data allocation failed\n"); goto exit_device_put; } - memcpy(pdev->dev.platform_data, sio_data, - sizeof(struct f71805f_sio_data)); err = platform_device_add(pdev); if (err) { diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index 62afc63..eff6036 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -6,6 +6,7 @@ IT8712F Super I/O chip w/LPC interface IT8716F Super I/O chip w/LPC interface IT8718F Super I/O chip w/LPC interface + IT8726F Super I/O chip w/LPC interface Sis950 A clone of the IT8705F Copyright (C) 2001 Chris Gauthron <chrisg@0-in.com> @@ -30,8 +31,7 @@ #include <linux/init.h> #include <linux/slab.h> #include <linux/jiffies.h> -#include <linux/i2c.h> -#include <linux/i2c-isa.h> +#include <linux/platform_device.h> #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> #include <linux/hwmon-vid.h> @@ -40,10 +40,12 @@ #include <linux/sysfs.h> #include <asm/io.h> +#define DRVNAME "it87" -static unsigned short isa_address; enum chips { it87, it8712, it8716, it8718 }; +static struct platform_device *pdev; + #define REG 0x2e /* The register to read/write */ #define DEV 0x07 /* Register: Logical device select */ #define VAL 0x2f /* The value to read/write */ @@ -97,6 +99,7 @@ superio_exit(void) #define IT8705F_DEVID 0x8705 #define IT8716F_DEVID 0x8716 #define IT8718F_DEVID 0x8718 +#define IT8726F_DEVID 0x8726 #define IT87_ACT_REG 0x30 #define IT87_BASE_REG 0x60 @@ -110,10 +113,6 @@ static int update_vbat; /* Not all BIOSes properly configure the PWM registers */ static int fix_pwm_polarity; -/* Values read from Super-I/O config space */ -static u16 chip_type; -static u8 vid_value; - /* Many IT87 constants specified below */ /* Length of ISA address segment */ @@ -214,13 +213,20 @@ static const unsigned int pwm_freq[8] = { }; +struct it87_sio_data { + enum chips type; + /* Values read from Super-I/O config space */ + u8 vid_value; +}; + /* For each registered chip, we need to keep some data in memory. The structure is dynamically allocated. */ struct it87_data { - struct i2c_client client; struct class_device *class_dev; enum chips type; + unsigned short addr; + const char *name; struct mutex update_lock; char valid; /* !=0 if following fields are valid */ unsigned long last_updated; /* In jiffies */ @@ -245,26 +251,25 @@ struct it87_data { }; -static int it87_detect(struct i2c_adapter *adapter); -static int it87_detach_client(struct i2c_client *client); +static int it87_probe(struct platform_device *pdev); +static int it87_remove(struct platform_device *pdev); -static int it87_read_value(struct i2c_client *client, u8 reg); -static void it87_write_value(struct i2c_client *client, u8 reg, u8 value); +static int it87_read_value(struct it87_data *data, u8 reg); +static void it87_write_value(struct it87_data *data, u8 reg, u8 value); static struct it87_data *it87_update_device(struct device *dev); -static int it87_check_pwm(struct i2c_client *client); -static void it87_init_client(struct i2c_client *client, struct it87_data *data); +static int it87_check_pwm(struct device *dev); +static void it87_init_device(struct platform_device *pdev); -static struct i2c_driver it87_isa_driver = { +static struct platform_driver it87_driver = { .driver = { .owner = THIS_MODULE, - .name = "it87-isa", + .name = DRVNAME, }, - .attach_adapter = it87_detect, - .detach_client = it87_detach_client, + .probe = it87_probe, + .remove = __devexit_p(it87_remove), }; - static ssize_t show_in(struct device *dev, struct device_attribute *attr, char *buf) { @@ -301,13 +306,12 @@ static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); data->in_min[nr] = IN_TO_REG(val); - it87_write_value(client, IT87_REG_VIN_MIN(nr), + it87_write_value(data, IT87_REG_VIN_MIN(nr), data->in_min[nr]); mutex_unlock(&data->update_lock); return count; @@ -318,13 +322,12 @@ static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); data->in_max[nr] = IN_TO_REG(val); - it87_write_value(client, IT87_REG_VIN_MAX(nr), + it87_write_value(data, IT87_REG_VIN_MAX(nr), data->in_max[nr]); mutex_unlock(&data->update_lock); return count; @@ -392,13 +395,12 @@ static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); int val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); data->temp_high[nr] = TEMP_TO_REG(val); - it87_write_value(client, IT87_REG_TEMP_HIGH(nr), data->temp_high[nr]); + it87_write_value(data, IT87_REG_TEMP_HIGH(nr), data->temp_high[nr]); mutex_unlock(&data->update_lock); return count; } @@ -408,13 +410,12 @@ static ssize_t set_temp_min(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); int val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); data->temp_low[nr] = TEMP_TO_REG(val); - it87_write_value(client, IT87_REG_TEMP_LOW(nr), data->temp_low[nr]); + it87_write_value(data, IT87_REG_TEMP_LOW(nr), data->temp_low[nr]); mutex_unlock(&data->update_lock); return count; } @@ -451,8 +452,7 @@ static ssize_t set_sensor(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); int val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); @@ -468,7 +468,7 @@ static ssize_t set_sensor(struct device *dev, struct device_attribute *attr, mutex_unlock(&data->update_lock); return -EINVAL; } - it87_write_value(client, IT87_REG_TEMP_ENABLE, data->sensor); + it87_write_value(data, IT87_REG_TEMP_ENABLE, data->sensor); mutex_unlock(&data->update_lock); return count; } @@ -542,13 +542,12 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); int val = simple_strtol(buf, NULL, 10); u8 reg; mutex_lock(&data->update_lock); - reg = it87_read_value(client, IT87_REG_FAN_DIV); + reg = it87_read_value(data, IT87_REG_FAN_DIV); switch (nr) { case 0: data->fan_div[nr] = reg & 0x07; break; case 1: data->fan_div[nr] = (reg >> 3) & 0x07; break; @@ -556,7 +555,7 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, } data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); - it87_write_value(client, IT87_REG_FAN_MIN(nr), data->fan_min[nr]); + it87_write_value(data, IT87_REG_FAN_MIN(nr), data->fan_min[nr]); mutex_unlock(&data->update_lock); return count; } @@ -566,14 +565,13 @@ static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); unsigned long val = simple_strtoul(buf, NULL, 10); int min; u8 old; mutex_lock(&data->update_lock); - old = it87_read_value(client, IT87_REG_FAN_DIV); + old = it87_read_value(data, IT87_REG_FAN_DIV); /* Save fan min limit */ min = FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr])); @@ -594,11 +592,11 @@ static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, val |= (data->fan_div[1] & 0x07) << 3; if (data->fan_div[2] == 3) val |= 0x1 << 6; - it87_write_value(client, IT87_REG_FAN_DIV, val); + it87_write_value(data, IT87_REG_FAN_DIV, val); /* Restore fan min limit */ data->fan_min[nr] = FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); - it87_write_value(client, IT87_REG_FAN_MIN(nr), data->fan_min[nr]); + it87_write_value(data, IT87_REG_FAN_MIN(nr), data->fan_min[nr]); mutex_unlock(&data->update_lock); return count; @@ -609,8 +607,7 @@ static ssize_t set_pwm_enable(struct device *dev, struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); int val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); @@ -618,17 +615,17 @@ static ssize_t set_pwm_enable(struct device *dev, if (val == 0) { int tmp; /* make sure the fan is on when in on/off mode */ - tmp = it87_read_value(client, IT87_REG_FAN_CTL); - it87_write_value(client, IT87_REG_FAN_CTL, tmp | (1 << nr)); + tmp = it87_read_value(data, IT87_REG_FAN_CTL); + it87_write_value(data, IT87_REG_FAN_CTL, tmp | (1 << nr)); /* set on/off mode */ data->fan_main_ctrl &= ~(1 << nr); - it87_write_value(client, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); + it87_write_value(data, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); } else if (val == 1) { /* set SmartGuardian mode */ data->fan_main_ctrl |= (1 << nr); - it87_write_value(client, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); + it87_write_value(data, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); /* set saved pwm value, clear FAN_CTLX PWM mode bit */ - it87_write_value(client, IT87_REG_PWM(nr), PWM_TO_REG(data->manual_pwm_ctl[nr])); + it87_write_value(data, IT87_REG_PWM(nr), PWM_TO_REG(data->manual_pwm_ctl[nr])); } else { mutex_unlock(&data->update_lock); return -EINVAL; @@ -643,8 +640,7 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); int val = simple_strtol(buf, NULL, 10); if (val < 0 || val > 255) @@ -653,15 +649,14 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->manual_pwm_ctl[nr] = val; if (data->fan_main_ctrl & (1 << nr)) - it87_write_value(client, IT87_REG_PWM(nr), PWM_TO_REG(data->manual_pwm_ctl[nr])); + it87_write_value(data, IT87_REG_PWM(nr), PWM_TO_REG(data->manual_pwm_ctl[nr])); mutex_unlock(&data->update_lock); return count; } static ssize_t set_pwm_freq(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); unsigned long val = simple_strtoul(buf, NULL, 10); int i; @@ -672,9 +667,9 @@ static ssize_t set_pwm_freq(struct device *dev, } mutex_lock(&data->update_lock); - data->fan_ctl = it87_read_value(client, IT87_REG_FAN_CTL) & 0x8f; + data->fan_ctl = it87_read_value(data, IT87_REG_FAN_CTL) & 0x8f; data->fan_ctl |= i << 4; - it87_write_value(client, IT87_REG_FAN_CTL, data->fan_ctl); + it87_write_value(data, IT87_REG_FAN_CTL, data->fan_ctl); mutex_unlock(&data->update_lock); return count; @@ -729,15 +724,14 @@ static ssize_t set_fan16_min(struct device *dev, struct device_attribute *attr, { struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); int val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); data->fan_min[nr] = FAN16_TO_REG(val); - it87_write_value(client, IT87_REG_FAN_MIN(nr), + it87_write_value(data, IT87_REG_FAN_MIN(nr), data->fan_min[nr] & 0xff); - it87_write_value(client, IT87_REG_FANX_MIN(nr), + it87_write_value(data, IT87_REG_FANX_MIN(nr), data->fan_min[nr] >> 8); mutex_unlock(&data->update_lock); return count; @@ -775,8 +769,7 @@ show_vrm_reg(struct device *dev, struct device_attribute *attr, char *buf) static ssize_t store_vrm_reg(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); u32 val; val = simple_strtoul(buf, NULL, 10); @@ -794,6 +787,14 @@ show_vid_reg(struct device *dev, struct device_attribute *attr, char *buf) } static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid_reg, NULL); +static ssize_t show_name(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct it87_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + static struct attribute *it87_attributes[] = { &sensor_dev_attr_in0_input.dev_attr.attr, &sensor_dev_attr_in1_input.dev_attr.attr, @@ -835,6 +836,7 @@ static struct attribute *it87_attributes[] = { &sensor_dev_attr_temp3_type.dev_attr.attr, &dev_attr_alarms.attr, + &dev_attr_name.attr, NULL }; @@ -877,17 +879,36 @@ static const struct attribute_group it87_group_opt = { }; /* SuperIO detection - will change isa_address if a chip is found */ -static int __init it87_find(unsigned short *address) +static int __init it87_find(unsigned short *address, + struct it87_sio_data *sio_data) { int err = -ENODEV; + u16 chip_type; superio_enter(); chip_type = superio_inw(DEVID); - if (chip_type != IT8712F_DEVID - && chip_type != IT8716F_DEVID - && chip_type != IT8718F_DEVID - && chip_type != IT8705F_DEVID) - goto exit; + + switch (chip_type) { + case IT8705F_DEVID: + sio_data->type = it87; + break; + case IT8712F_DEVID: + sio_data->type = it8712; + break; + case IT8716F_DEVID: + case IT8726F_DEVID: + sio_data->type = it8716; + break; + case IT8718F_DEVID: + sio_data->type = it8718; + break; + case 0xffff: /* No device at all */ + goto exit; + default: + pr_debug(DRVNAME ": Unsupported chip (DEVID=0x%x)\n", + chip_type); + goto exit; + } superio_select(PME); if (!(superio_inb(IT87_ACT_REG) & 0x01)) { @@ -911,7 +932,7 @@ static int __init it87_find(unsigned short *address) superio_select(GPIO); if (chip_type == it8718) - vid_value = superio_inb(IT87_SIO_VID_REG); + sio_data->vid_value = superio_inb(IT87_SIO_VID_REG); reg = superio_inb(IT87_SIO_PINX2_REG); if (reg & (1 << 0)) @@ -925,18 +946,26 @@ exit: return err; } -/* This function is called by i2c_probe */ -static int it87_detect(struct i2c_adapter *adapter) +static int __devinit it87_probe(struct platform_device *pdev) { - struct i2c_client *new_client; struct it87_data *data; + struct resource *res; + struct device *dev = &pdev->dev; + struct it87_sio_data *sio_data = dev->platform_data; int err = 0; - const char *name; int enable_pwm_interface; - - /* Reserve the ISA region */ - if (!request_region(isa_address, IT87_EXTENT, - it87_isa_driver.driver.name)){ + static const char *names[] = { + "it87", + "it8712", + "it8716", + "it8718", + }; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!request_region(res->start, IT87_EXTENT, DRVNAME)) { + dev_err(dev, "Failed to request region 0x%lx-0x%lx\n", + (unsigned long)res->start, + (unsigned long)(res->start + IT87_EXTENT - 1)); err = -EBUSY; goto ERROR0; } @@ -946,129 +975,104 @@ static int it87_detect(struct i2c_adapter *adapter) goto ERROR1; } - new_client = &data->client; - i2c_set_clientdata(new_client, data); - new_client->addr = isa_address; - new_client->adapter = adapter; - new_client->driver = &it87_isa_driver; + data->addr = res->start; + data->type = sio_data->type; + data->name = names[sio_data->type]; /* Now, we do the remaining detection. */ - if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80) - || it87_read_value(new_client, IT87_REG_CHIPID) != 0x90) { + if ((it87_read_value(data, IT87_REG_CONFIG) & 0x80) + || it87_read_value(data, IT87_REG_CHIPID) != 0x90) { err = -ENODEV; goto ERROR2; } - /* Determine the chip type. */ - switch (chip_type) { - case IT8712F_DEVID: - data->type = it8712; - name = "it8712"; - break; - case IT8716F_DEVID: - data->type = it8716; - name = "it8716"; - break; - case IT8718F_DEVID: - data->type = it8718; - name = "it8718"; - break; - default: - data->type = it87; - name = "it87"; - } + platform_set_drvdata(pdev, data); - /* Fill in the remaining client fields and put it into the global list */ - strlcpy(new_client->name, name, I2C_NAME_SIZE); mutex_init(&data->update_lock); - /* Tell the I2C layer a new client has arrived */ - if ((err = i2c_attach_client(new_client))) - goto ERROR2; - /* Check PWM configuration */ - enable_pwm_interface = it87_check_pwm(new_client); + enable_pwm_interface = it87_check_pwm(dev); /* Initialize the IT87 chip */ - it87_init_client(new_client, data); + it87_init_device(pdev); /* Register sysfs hooks */ - if ((err = sysfs_create_group(&new_client->dev.kobj, &it87_group))) - goto ERROR3; + if ((err = sysfs_create_group(&dev->kobj, &it87_group))) + goto ERROR2; /* Do not create fan files for disabled fans */ if (data->type == it8716 || data->type == it8718) { /* 16-bit tachometers */ if (data->has_fan & (1 << 0)) { - if ((err = device_create_file(&new_client->dev, + if ((err = device_create_file(dev, &sensor_dev_attr_fan1_input16.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_fan1_min16.dev_attr))) goto ERROR4; } if (data->has_fan & (1 << 1)) { - if ((err = device_create_file(&new_client->dev, + if ((err = device_create_file(dev, &sensor_dev_attr_fan2_input16.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_fan2_min16.dev_attr))) goto ERROR4; } if (data->has_fan & (1 << 2)) { - if ((err = device_create_file(&new_client->dev, + if ((err = device_create_file(dev, &sensor_dev_attr_fan3_input16.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_fan3_min16.dev_attr))) goto ERROR4; } } else { /* 8-bit tachometers with clock divider */ if (data->has_fan & (1 << 0)) { - if ((err = device_create_file(&new_client->dev, + if ((err = device_create_file(dev, &sensor_dev_attr_fan1_input.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_fan1_min.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_fan1_div.dev_attr))) goto ERROR4; } if (data->has_fan & (1 << 1)) { - if ((err = device_create_file(&new_client->dev, + if ((err = device_create_file(dev, &sensor_dev_attr_fan2_input.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_fan2_min.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_fan2_div.dev_attr))) goto ERROR4; } if (data->has_fan & (1 << 2)) { - if ((err = device_create_file(&new_client->dev, + if ((err = device_create_file(dev, &sensor_dev_attr_fan3_input.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_fan3_min.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_fan3_div.dev_attr))) goto ERROR4; } } if (enable_pwm_interface) { - if ((err = device_create_file(&new_client->dev, + if ((err = device_create_file(dev, &sensor_dev_attr_pwm1_enable.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_pwm2_enable.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_pwm3_enable.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_pwm1.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_pwm2.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &sensor_dev_attr_pwm3.dev_attr)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &dev_attr_pwm1_freq)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &dev_attr_pwm2_freq)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &dev_attr_pwm3_freq))) goto ERROR4; } @@ -1077,15 +1081,15 @@ static int it87_detect(struct i2c_adapter *adapter) || data->type == it8718) { data->vrm = vid_which_vrm(); /* VID reading from Super-I/O config space if available */ - data->vid = vid_value; - if ((err = device_create_file(&new_client->dev, + data->vid = sio_data->vid_value; + if ((err = device_create_file(dev, &dev_attr_vrm)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(dev, &dev_attr_cpu0_vid))) goto ERROR4; } - data->class_dev = hwmon_device_register(&new_client->dev); + data->class_dev = hwmon_device_register(dev); if (IS_ERR(data->class_dev)) { err = PTR_ERR(data->class_dev); goto ERROR4; @@ -1094,31 +1098,27 @@ static int it87_detect(struct i2c_adapter *adapter) return 0; ERROR4: - sysfs_remove_group(&new_client->dev.kobj, &it87_group); - sysfs_remove_group(&new_client->dev.kobj, &it87_group_opt); -ERROR3: - i2c_detach_client(new_client); + sysfs_remove_group(&dev->kobj, &it87_group); + sysfs_remove_group(&dev->kobj, &it87_group_opt); ERROR2: + platform_set_drvdata(pdev, NULL); kfree(data); ERROR1: - release_region(isa_address, IT87_EXTENT); + release_region(res->start, IT87_EXTENT); ERROR0: return err; } -static int it87_detach_client(struct i2c_client *client) +static int __devexit it87_remove(struct platform_device *pdev) { - struct it87_data *data = i2c_get_clientdata(client); - int err; + struct it87_data *data = platform_get_drvdata(pdev); hwmon_device_unregister(data->class_dev); - sysfs_remove_group(&client->dev.kobj, &it87_group); - sysfs_remove_group(&client->dev.kobj, &it87_group_opt); + sysfs_remove_group(&pdev->dev.kobj, &it87_group); + sysfs_remove_group(&pdev->dev.kobj, &it87_group_opt); - if ((err = i2c_detach_client(client))) - return err; - - release_region(client->addr, IT87_EXTENT); + release_region(data->addr, IT87_EXTENT); + platform_set_drvdata(pdev, NULL); kfree(data); return 0; @@ -1127,28 +1127,29 @@ static int it87_detach_client(struct i2c_client *client) /* Must be called with data->update_lock held, except during initialization. We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks, would slow down the IT87 access and should not be necessary. */ -static int it87_read_value(struct i2c_client *client, u8 reg) +static int it87_read_value(struct it87_data *data, u8 reg) { - outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET); - return inb_p(client->addr + IT87_DATA_REG_OFFSET); + outb_p(reg, data->addr + IT87_ADDR_REG_OFFSET); + return inb_p(data->addr + IT87_DATA_REG_OFFSET); } /* Must be called with data->update_lock held, except during initialization. We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks, would slow down the IT87 access and should not be necessary. */ -static void it87_write_value(struct i2c_client *client, u8 reg, u8 value) +static void it87_write_value(struct it87_data *data, u8 reg, u8 value) { - outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET); - outb_p(value, client->addr + IT87_DATA_REG_OFFSET); + outb_p(reg, data->addr + IT87_ADDR_REG_OFFSET); + outb_p(value, data->addr + IT87_DATA_REG_OFFSET); } /* Return 1 if and only if the PWM interface is safe to use */ -static int it87_check_pwm(struct i2c_client *client) +static int __devinit it87_check_pwm(struct device *dev) { + struct it87_data *data = dev_get_drvdata(dev); /* Some BIOSes fail to correctly configure the IT87 fans. All fans off * and polarity set to active low is sign that this is the case so we * disable pwm control to protect the user. */ - int tmp = it87_read_value(client, IT87_REG_FAN_CTL); + int tmp = it87_read_value(data, IT87_REG_FAN_CTL); if ((tmp & 0x87) == 0) { if (fix_pwm_polarity) { /* The user asks us to attempt a chip reconfiguration. @@ -1158,7 +1159,7 @@ static int it87_check_pwm(struct i2c_client *client) u8 pwm[3]; for (i = 0; i < 3; i++) - pwm[i] = it87_read_value(client, + pwm[i] = it87_read_value(data, IT87_REG_PWM(i)); /* If any fan is in automatic pwm mode, the polarity @@ -1166,26 +1167,26 @@ static int it87_check_pwm(struct i2c_client *client) * better don't change anything (but still disable the * PWM interface). */ if (!((pwm[0] | pwm[1] | pwm[2]) & 0x80)) { - dev_info(&client->dev, "Reconfiguring PWM to " + dev_info(dev, "Reconfiguring PWM to " "active high polarity\n"); - it87_write_value(client, IT87_REG_FAN_CTL, + it87_write_value(data, IT87_REG_FAN_CTL, tmp | 0x87); for (i = 0; i < 3; i++) - it87_write_value(client, + it87_write_value(data, IT87_REG_PWM(i), 0x7f & ~pwm[i]); return 1; } - dev_info(&client->dev, "PWM configuration is " + dev_info(dev, "PWM configuration is " "too broken to be fixed\n"); } - dev_info(&client->dev, "Detected broken BIOS " + dev_info(dev, "Detected broken BIOS " "defaults, disabling PWM interface\n"); return 0; } else if (fix_pwm_polarity) { - dev_info(&client->dev, "PWM configuration looks " + dev_info(dev, "PWM configuration looks " "sane, won't touch\n"); } @@ -1193,8 +1194,9 @@ static int it87_check_pwm(struct i2c_client *client) } /* Called when we have found a new IT87. */ -static void it87_init_client(struct i2c_client *client, struct it87_data *data) +static void __devinit it87_init_device(struct platform_device *pdev) { + struct it87_data *data = platform_get_drvdata(pdev); int tmp, i; /* initialize to sane defaults: @@ -1214,48 +1216,48 @@ static void it87_init_client(struct i2c_client *client, struct it87_data *data) * means -1 degree C, which surprisingly doesn't trigger an alarm, * but is still confusing, so change to 127 degrees C. */ for (i = 0; i < 8; i++) { - tmp = it87_read_value(client, IT87_REG_VIN_MIN(i)); + tmp = it87_read_value(data, IT87_REG_VIN_MIN(i)); if (tmp == 0xff) - it87_write_value(client, IT87_REG_VIN_MIN(i), 0); + it87_write_value(data, IT87_REG_VIN_MIN(i), 0); } for (i = 0; i < 3; i++) { - tmp = it87_read_value(client, IT87_REG_TEMP_HIGH(i)); + tmp = it87_read_value(data, IT87_REG_TEMP_HIGH(i)); if (tmp == 0xff) - it87_write_value(client, IT87_REG_TEMP_HIGH(i), 127); + it87_write_value(data, IT87_REG_TEMP_HIGH(i), 127); } /* Check if temperature channnels are reset manually or by some reason */ - tmp = it87_read_value(client, IT87_REG_TEMP_ENABLE); + tmp = it87_read_value(data, IT87_REG_TEMP_ENABLE); if ((tmp & 0x3f) == 0) { /* Temp1,Temp3=thermistor; Temp2=thermal diode */ tmp = (tmp & 0xc0) | 0x2a; - it87_write_value(client, IT87_REG_TEMP_ENABLE, tmp); + it87_write_value(data, IT87_REG_TEMP_ENABLE, tmp); } data->sensor = tmp; /* Check if voltage monitors are reset manually or by some reason */ - tmp = it87_read_value(client, IT87_REG_VIN_ENABLE); + tmp = it87_read_value(data, IT87_REG_VIN_ENABLE); if ((tmp & 0xff) == 0) { /* Enable all voltage monitors */ - it87_write_value(client, IT87_REG_VIN_ENABLE, 0xff); + it87_write_value(data, IT87_REG_VIN_ENABLE, 0xff); } /* Check if tachometers are reset manually or by some reason */ - data->fan_main_ctrl = it87_read_value(client, IT87_REG_FAN_MAIN_CTRL); + data->fan_main_ctrl = it87_read_value(data, IT87_REG_FAN_MAIN_CTRL); if ((data->fan_main_ctrl & 0x70) == 0) { /* Enable all fan tachometers */ data->fan_main_ctrl |= 0x70; - it87_write_value(client, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); + it87_write_value(data, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); } data->has_fan = (data->fan_main_ctrl >> 4) & 0x07; /* Set tachometers to 16-bit mode if needed */ if (data->type == it8716 || data->type == it8718) { - tmp = it87_read_value(client, IT87_REG_FAN_16BIT); + tmp = it87_read_value(data, IT87_REG_FAN_16BIT); if (~tmp & 0x07 & data->has_fan) { - dev_dbg(&client->dev, + dev_dbg(&pdev->dev, "Setting fan1-3 to 16-bit mode\n"); - it87_write_value(client, IT87_REG_FAN_16BIT, + it87_write_value(data, IT87_REG_FAN_16BIT, tmp | 0x07); } } @@ -1265,7 +1267,7 @@ static void it87_init_client(struct i2c_client *client, struct it87_data *data) for (i = 0; i < 3; i++) { if (data->fan_main_ctrl & (1 << i)) { /* pwm mode */ - tmp = it87_read_value(client, IT87_REG_PWM(i)); + tmp = it87_read_value(data, IT87_REG_PWM(i)); if (tmp & 0x80) { /* automatic pwm - not yet implemented, but * leave the settings made by the BIOS alone @@ -1279,15 +1281,14 @@ static void it87_init_client(struct i2c_client *client, struct it87_data *data) } /* Start monitoring */ - it87_write_value(client, IT87_REG_CONFIG, - (it87_read_value(client, IT87_REG_CONFIG) & 0x36) + it87_write_value(data, IT87_REG_CONFIG, + (it87_read_value(data, IT87_REG_CONFIG) & 0x36) | (update_vbat ? 0x41 : 0x01)); } static struct it87_data *it87_update_device(struct device *dev) { - struct i2c_client *client = to_i2c_client(dev); - struct it87_data *data = i2c_get_clientdata(client); + struct it87_data *data = dev_get_drvdata(dev); int i; mutex_lock(&data->update_lock); @@ -1298,20 +1299,20 @@ static struct it87_data *it87_update_device(struct device *dev) if (update_vbat) { /* Cleared after each update, so reenable. Value returned by this read will be previous value */ - it87_write_value(client, IT87_REG_CONFIG, - it87_read_value(client, IT87_REG_CONFIG) | 0x40); + it87_write_value(data, IT87_REG_CONFIG, + it87_read_value(data, IT87_REG_CONFIG) | 0x40); } for (i = 0; i <= 7; i++) { data->in[i] = - it87_read_value(client, IT87_REG_VIN(i)); + it87_read_value(data, IT87_REG_VIN(i)); data->in_min[i] = - it87_read_value(client, IT87_REG_VIN_MIN(i)); + it87_read_value(data, IT87_REG_VIN_MIN(i)); data->in_max[i] = - it87_read_value(client, IT87_REG_VIN_MAX(i)); + it87_read_value(data, IT87_REG_VIN_MAX(i)); } /* in8 (battery) has no limit registers */ data->in[8] = - it87_read_value(client, IT87_REG_VIN(8)); + it87_read_value(data, IT87_REG_VIN(8)); for (i = 0; i < 3; i++) { /* Skip disabled fans */ @@ -1319,46 +1320,47 @@ static struct it87_data *it87_update_device(struct device *dev) continue; data->fan_min[i] = - it87_read_value(client, IT87_REG_FAN_MIN(i)); - data->fan[i] = it87_read_value(client, + it87_read_value(data, IT87_REG_FAN_MIN(i)); + data->fan[i] = it87_read_value(data, IT87_REG_FAN(i)); /* Add high byte if in 16-bit mode */ if (data->type == it8716 || data->type == it8718) { - data->fan[i] |= it87_read_value(client, + data->fan[i] |= it87_read_value(data, IT87_REG_FANX(i)) << 8; - data->fan_min[i] |= it87_read_value(client, + data->fan_min[i] |= it87_read_value(data, IT87_REG_FANX_MIN(i)) << 8; } } for (i = 0; i < 3; i++) { data->temp[i] = - it87_read_value(client, IT87_REG_TEMP(i)); + it87_read_value(data, IT87_REG_TEMP(i)); data->temp_high[i] = - it87_read_value(client, IT87_REG_TEMP_HIGH(i)); + it87_read_value(data, IT87_REG_TEMP_HIGH(i)); data->temp_low[i] = - it87_read_value(client, IT87_REG_TEMP_LOW(i)); + it87_read_value(data, IT87_REG_TEMP_LOW(i)); } /* Newer chips don't have clock dividers */ if ((data->has_fan & 0x07) && data->type != it8716 && data->type != it8718) { - i = it87_read_value(client, IT87_REG_FAN_DIV); + i = it87_read_value(data, IT87_REG_FAN_DIV); data->fan_div[0] = i & 0x07; data->fan_div[1] = (i >> 3) & 0x07; data->fan_div[2] = (i & 0x40) ? 3 : 1; } data->alarms = - it87_read_value(client, IT87_REG_ALARM1) | - (it87_read_value(client, IT87_REG_ALARM2) << 8) | - (it87_read_value(client, IT87_REG_ALARM3) << 16); - data->fan_main_ctrl = it87_read_value(client, IT87_REG_FAN_MAIN_CTRL); - data->fan_ctl = it87_read_value(client, IT87_REG_FAN_CTL); - - data->sensor = it87_read_value(client, IT87_REG_TEMP_ENABLE); + it87_read_value(data, IT87_REG_ALARM1) | + (it87_read_value(data, IT87_REG_ALARM2) << 8) | + (it87_read_value(data, IT87_REG_ALARM3) << 16); + data->fan_main_ctrl = it87_read_value(data, + IT87_REG_FAN_MAIN_CTRL); + data->fan_ctl = it87_read_value(data, IT87_REG_FAN_CTL); + + data->sensor = it87_read_value(data, IT87_REG_TEMP_ENABLE); /* The 8705 does not have VID capability */ if (data->type == it8712 || data->type == it8716) { - data->vid = it87_read_value(client, IT87_REG_VID); + data->vid = it87_read_value(data, IT87_REG_VID); /* The older IT8712F revisions had only 5 VID pins, but we assume it is always safe to read 6 bits. */ data->vid &= 0x3f; @@ -1372,24 +1374,85 @@ static struct it87_data *it87_update_device(struct device *dev) return data; } +static int __init it87_device_add(unsigned short address, + const struct it87_sio_data *sio_data) +{ + struct resource res = { + .start = address , + .end = address + IT87_EXTENT - 1, + .name = DRVNAME, + .flags = IORESOURCE_IO, + }; + int err; + + pdev = platform_device_alloc(DRVNAME, address); + if (!pdev) { + err = -ENOMEM; + printk(KERN_ERR DRVNAME ": Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + printk(KERN_ERR DRVNAME ": Device resource addition failed " + "(%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add_data(pdev, sio_data, + sizeof(struct it87_sio_data)); + if (err) { + printk(KERN_ERR DRVNAME ": Platform data allocation failed\n"); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n", + err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + static int __init sm_it87_init(void) { - int res; + int err; + unsigned short isa_address=0; + struct it87_sio_data sio_data; + + err = it87_find(&isa_address, &sio_data); + if (err) + return err; + err = platform_driver_register(&it87_driver); + if (err) + return err; - if ((res = it87_find(&isa_address))) - return res; - return i2c_isa_add_driver(&it87_isa_driver); + err = it87_device_add(isa_address, &sio_data); + if (err){ + platform_driver_unregister(&it87_driver); + return err; + } + + return 0; } static void __exit sm_it87_exit(void) { - i2c_isa_del_driver(&it87_isa_driver); + platform_device_unregister(pdev); + platform_driver_unregister(&it87_driver); } MODULE_AUTHOR("Chris Gauthron <chrisg@0-in.com>, " "Jean Delvare <khali@linux-fr.org>"); -MODULE_DESCRIPTION("IT8705F/8712F/8716F/8718F, SiS950 driver"); +MODULE_DESCRIPTION("IT8705F/8712F/8716F/8718F/8726F, SiS950 driver"); module_param(update_vbat, bool, 0); MODULE_PARM_DESC(update_vbat, "Update vbat if set else return powerup value"); module_param(fix_pwm_polarity, bool, 0); diff --git a/drivers/hwmon/lm63.c b/drivers/hwmon/lm63.c index d69f3cf..2162d69 100644 --- a/drivers/hwmon/lm63.c +++ b/drivers/hwmon/lm63.c @@ -364,7 +364,7 @@ static DEVICE_ATTR(temp2_crit_hyst, S_IWUSR | S_IRUGO, show_temp2_crit_hyst, /* Individual alarm files */ static SENSOR_DEVICE_ATTR(fan1_min_alarm, S_IRUGO, show_alarm, NULL, 0); static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 1); -static SENSOR_DEVICE_ATTR(temp2_input_fault, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 2); static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_alarm, NULL, 3); static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 4); static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 6); @@ -383,7 +383,7 @@ static struct attribute *lm63_attributes[] = { &dev_attr_temp2_crit_hyst.attr, &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_input_fault.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index feb87b4..654c0f7 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -223,14 +223,14 @@ static SENSOR_DEVICE_ATTR(temp4_crit, S_IRUGO, show_temp, NULL, 8); /* Individual alarm files */ static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 0); static SENSOR_DEVICE_ATTR(temp3_crit_alarm, S_IRUGO, show_alarm, NULL, 1); -static SENSOR_DEVICE_ATTR(temp3_input_fault, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 2); static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_alarm, NULL, 4); static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 6); static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 8); static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO, show_alarm, NULL, 9); -static SENSOR_DEVICE_ATTR(temp4_input_fault, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_alarm, NULL, 10); static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_alarm, NULL, 12); -static SENSOR_DEVICE_ATTR(temp2_input_fault, S_IRUGO, show_alarm, NULL, 13); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 13); static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 15); /* Raw alarm file for compatibility */ static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL); @@ -245,7 +245,7 @@ static struct attribute *lm83_attributes[] = { &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_input_fault.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, &dev_attr_alarms.attr, @@ -266,9 +266,9 @@ static struct attribute *lm83_attributes_opt[] = { &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_input_fault.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_input_fault.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, NULL }; diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 6882ce7..48833ff 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -43,6 +43,13 @@ * variants. The extra address and features of the MAX6659 are not * supported by this driver. * + * This driver also supports the MAX6680 and MAX6681, two other sensor + * chips made by Maxim. These are quite similar to the other Maxim + * chips. Complete datasheet can be obtained at: + * http://www.maxim-ic.com/quick_view2.cfm/qv_pk/3370 + * The MAX6680 and MAX6681 only differ in the pinout so they can be + * treated identically. + * * This driver also supports the ADT7461 chip from Analog Devices but * only in its "compatability mode". If an ADT7461 chip is found but * is configured in non-compatible mode (where its temperature @@ -84,20 +91,25 @@ /* * Addresses to scan * Address is fully defined internally and cannot be changed except for - * MAX6659. + * MAX6659, MAX6680 and MAX6681. * LM86, LM89, LM90, LM99, ADM1032, ADM1032-1, ADT7461, MAX6657 and MAX6658 * have address 0x4c. * ADM1032-2, ADT7461-2, LM89-1, and LM99-1 have address 0x4d. * MAX6659 can have address 0x4c, 0x4d or 0x4e (unsupported). + * MAX6680 and MAX6681 can have address 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, + * 0x4c, 0x4d or 0x4e. */ -static unsigned short normal_i2c[] = { 0x4c, 0x4d, I2C_CLIENT_END }; +static unsigned short normal_i2c[] = { 0x18, 0x19, 0x1a, + 0x29, 0x2a, 0x2b, + 0x4c, 0x4d, 0x4e, + I2C_CLIENT_END }; /* * Insmod parameters */ -I2C_CLIENT_INSMOD_6(lm90, adm1032, lm99, lm86, max6657, adt7461); +I2C_CLIENT_INSMOD_7(lm90, adm1032, lm99, lm86, max6657, adt7461, max6680); /* * The LM90 registers @@ -359,7 +371,7 @@ static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO, show_temphyst, NULL, 4); /* Individual alarm files */ static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 0); static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 1); -static SENSOR_DEVICE_ATTR(temp2_input_fault, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 2); static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_alarm, NULL, 3); static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 4); static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, 5); @@ -381,7 +393,7 @@ static struct attribute *lm90_attributes[] = { &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_input_fault.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, @@ -429,7 +441,7 @@ static DEVICE_ATTR(pec, S_IWUSR | S_IRUGO, show_pec, set_pec); */ /* The ADM1032 supports PEC but not on write byte transactions, so we need - to explicitely ask for a transaction without PEC. */ + to explicitly ask for a transaction without PEC. */ static inline s32 adm1032_write_byte(struct i2c_client *client, u8 value) { return i2c_smbus_xfer(client->adapter, client->addr, @@ -525,7 +537,8 @@ static int lm90_detect(struct i2c_adapter *adapter, int address, int kind) ®_convrate) < 0) goto exit_free; - if (man_id == 0x01) { /* National Semiconductor */ + if ((address == 0x4C || address == 0x4D) + && man_id == 0x01) { /* National Semiconductor */ u8 reg_config2; if (lm90_read_reg(new_client, LM90_REG_R_CONFIG2, @@ -548,7 +561,8 @@ static int lm90_detect(struct i2c_adapter *adapter, int address, int kind) } } } else - if (man_id == 0x41) { /* Analog Devices */ + if ((address == 0x4C || address == 0x4D) + && man_id == 0x41) { /* Analog Devices */ if ((chip_id & 0xF0) == 0x40 /* ADM1032 */ && (reg_config1 & 0x3F) == 0x00 && reg_convrate <= 0x0A) { @@ -562,18 +576,30 @@ static int lm90_detect(struct i2c_adapter *adapter, int address, int kind) } else if (man_id == 0x4D) { /* Maxim */ /* - * The Maxim variants do NOT have a chip_id register. - * Reading from that address will return the last read - * value, which in our case is those of the man_id - * register. Likewise, the config1 register seems to - * lack a low nibble, so the value will be those of the - * previous read, so in our case those of the man_id - * register. + * The MAX6657, MAX6658 and MAX6659 do NOT have a + * chip_id register. Reading from that address will + * return the last read value, which in our case is + * those of the man_id register. Likewise, the config1 + * register seems to lack a low nibble, so the value + * will be those of the previous read, so in our case + * those of the man_id register. */ if (chip_id == man_id + && (address == 0x4F || address == 0x4D) && (reg_config1 & 0x1F) == (man_id & 0x0F) && reg_convrate <= 0x09) { kind = max6657; + } else + /* The chip_id register of the MAX6680 and MAX6681 + * holds the revision of the chip. + * the lowest bit of the config1 register is unused + * and should return zero when read, so should the + * second to last bit of config1 (software reset) + */ + if (chip_id == 0x01 + && (reg_config1 & 0x03) == 0x00 + && reg_convrate <= 0x07) { + kind = max6680; } } @@ -599,6 +625,8 @@ static int lm90_detect(struct i2c_adapter *adapter, int address, int kind) name = "lm86"; } else if (kind == max6657) { name = "max6657"; + } else if (kind == max6680) { + name = "max6680"; } else if (kind == adt7461) { name = "adt7461"; } @@ -646,7 +674,8 @@ exit: static void lm90_init_client(struct i2c_client *client) { - u8 config; + u8 config, config_orig; + struct lm90_data *data = i2c_get_clientdata(client); /* * Start the conversions. @@ -657,9 +686,20 @@ static void lm90_init_client(struct i2c_client *client) dev_warn(&client->dev, "Initialization failed!\n"); return; } - if (config & 0x40) - i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, - config & 0xBF); /* run */ + config_orig = config; + + /* + * Put MAX6680/MAX8881 into extended resolution (bit 0x10, + * 0.125 degree resolution) and range (0x08, extend range + * to -64 degree) mode for the remote temperature sensor. + */ + if (data->kind == max6680) { + config |= 0x18; + } + + config &= 0xBF; /* run */ + if (config != config_orig) /* Only write if changed */ + i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, config); } static int lm90_detach_client(struct i2c_client *client) diff --git a/drivers/hwmon/lm93.c b/drivers/hwmon/lm93.c new file mode 100644 index 0000000..23edf4f --- /dev/null +++ b/drivers/hwmon/lm93.c @@ -0,0 +1,2655 @@ +/* + lm93.c - Part of lm_sensors, Linux kernel modules for hardware monitoring + + Author/Maintainer: Mark M. Hoffman <mhoffman@lightlink.com> + Copyright (c) 2004 Utilitek Systems, Inc. + + derived in part from lm78.c: + Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> + + derived in part from lm85.c: + Copyright (c) 2002, 2003 Philip Pokorny <ppokorny@penguincomputing.com> + Copyright (c) 2003 Margit Schubert-While <margitsw@t-online.de> + + derived in part from w83l785ts.c: + Copyright (c) 2003-2004 Jean Delvare <khali@linux-fr.org> + + Ported to Linux 2.6 by Eric J. Bowersox <ericb@aspsys.com> + Copyright (c) 2005 Aspen Systems, Inc. + + Adapted to 2.6.20 by Carsten Emde <cbe@osadl.org> + Copyright (c) 2006 Carsten Emde, Open Source Automation Development Lab + + Modified for mainline integration by Hans J. Koch <hjk@linutronix.de> + Copyright (c) 2007 Hans J. Koch, Linutronix GmbH + + 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/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/hwmon-vid.h> +#include <linux/err.h> +#include <linux/delay.h> + +/* LM93 REGISTER ADDRESSES */ + +/* miscellaneous */ +#define LM93_REG_MFR_ID 0x3e +#define LM93_REG_VER 0x3f +#define LM93_REG_STATUS_CONTROL 0xe2 +#define LM93_REG_CONFIG 0xe3 +#define LM93_REG_SLEEP_CONTROL 0xe4 + +/* alarm values start here */ +#define LM93_REG_HOST_ERROR_1 0x48 + +/* voltage inputs: in1-in16 (nr => 0-15) */ +#define LM93_REG_IN(nr) (0x56 + (nr)) +#define LM93_REG_IN_MIN(nr) (0x90 + (nr) * 2) +#define LM93_REG_IN_MAX(nr) (0x91 + (nr) * 2) + +/* temperature inputs: temp1-temp4 (nr => 0-3) */ +#define LM93_REG_TEMP(nr) (0x50 + (nr)) +#define LM93_REG_TEMP_MIN(nr) (0x78 + (nr) * 2) +#define LM93_REG_TEMP_MAX(nr) (0x79 + (nr) * 2) + +/* temp[1-4]_auto_boost (nr => 0-3) */ +#define LM93_REG_BOOST(nr) (0x80 + (nr)) + +/* #PROCHOT inputs: prochot1-prochot2 (nr => 0-1) */ +#define LM93_REG_PROCHOT_CUR(nr) (0x67 + (nr) * 2) +#define LM93_REG_PROCHOT_AVG(nr) (0x68 + (nr) * 2) +#define LM93_REG_PROCHOT_MAX(nr) (0xb0 + (nr)) + +/* fan tach inputs: fan1-fan4 (nr => 0-3) */ +#define LM93_REG_FAN(nr) (0x6e + (nr) * 2) +#define LM93_REG_FAN_MIN(nr) (0xb4 + (nr) * 2) + +/* pwm outputs: pwm1-pwm2 (nr => 0-1, reg => 0-3) */ +#define LM93_REG_PWM_CTL(nr,reg) (0xc8 + (reg) + (nr) * 4) +#define LM93_PWM_CTL1 0x0 +#define LM93_PWM_CTL2 0x1 +#define LM93_PWM_CTL3 0x2 +#define LM93_PWM_CTL4 0x3 + +/* GPIO input state */ +#define LM93_REG_GPI 0x6b + +/* vid inputs: vid1-vid2 (nr => 0-1) */ +#define LM93_REG_VID(nr) (0x6c + (nr)) + +/* vccp1 & vccp2: VID relative inputs (nr => 0-1) */ +#define LM93_REG_VCCP_LIMIT_OFF(nr) (0xb2 + (nr)) + +/* temp[1-4]_auto_boost_hyst */ +#define LM93_REG_BOOST_HYST_12 0xc0 +#define LM93_REG_BOOST_HYST_34 0xc1 +#define LM93_REG_BOOST_HYST(nr) (0xc0 + (nr)/2) + +/* temp[1-4]_auto_pwm_[min|hyst] */ +#define LM93_REG_PWM_MIN_HYST_12 0xc3 +#define LM93_REG_PWM_MIN_HYST_34 0xc4 +#define LM93_REG_PWM_MIN_HYST(nr) (0xc3 + (nr)/2) + +/* prochot_override & prochot_interval */ +#define LM93_REG_PROCHOT_OVERRIDE 0xc6 +#define LM93_REG_PROCHOT_INTERVAL 0xc7 + +/* temp[1-4]_auto_base (nr => 0-3) */ +#define LM93_REG_TEMP_BASE(nr) (0xd0 + (nr)) + +/* temp[1-4]_auto_offsets (step => 0-11) */ +#define LM93_REG_TEMP_OFFSET(step) (0xd4 + (step)) + +/* #PROCHOT & #VRDHOT PWM ramp control */ +#define LM93_REG_PWM_RAMP_CTL 0xbf + +/* miscellaneous */ +#define LM93_REG_SFC1 0xbc +#define LM93_REG_SFC2 0xbd +#define LM93_REG_GPI_VID_CTL 0xbe +#define LM93_REG_SF_TACH_TO_PWM 0xe0 + +/* error masks */ +#define LM93_REG_GPI_ERR_MASK 0xec +#define LM93_REG_MISC_ERR_MASK 0xed + +/* LM93 REGISTER VALUES */ +#define LM93_MFR_ID 0x73 +#define LM93_MFR_ID_PROTOTYPE 0x72 + +/* SMBus capabilities */ +#define LM93_SMBUS_FUNC_FULL (I2C_FUNC_SMBUS_BYTE_DATA | \ + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA) +#define LM93_SMBUS_FUNC_MIN (I2C_FUNC_SMBUS_BYTE_DATA | \ + I2C_FUNC_SMBUS_WORD_DATA) + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(lm93); + +static int disable_block; +module_param(disable_block, bool, 0); +MODULE_PARM_DESC(disable_block, + "Set to non-zero to disable SMBus block data transactions."); + +static int init; +module_param(init, bool, 0); +MODULE_PARM_DESC(init, "Set to non-zero to force chip initialization."); + +static int vccp_limit_type[2] = {0,0}; +module_param_array(vccp_limit_type, int, NULL, 0); +MODULE_PARM_DESC(vccp_limit_type, "Configures in7 and in8 limit modes."); + +static int vid_agtl; +module_param(vid_agtl, int, 0); +MODULE_PARM_DESC(vid_agtl, "Configures VID pin input thresholds."); + +/* Driver data */ +static struct i2c_driver lm93_driver; + +/* LM93 BLOCK READ COMMANDS */ +static const struct { u8 cmd; u8 len; } lm93_block_read_cmds[12] = { + { 0xf2, 8 }, + { 0xf3, 8 }, + { 0xf4, 6 }, + { 0xf5, 16 }, + { 0xf6, 4 }, + { 0xf7, 8 }, + { 0xf8, 12 }, + { 0xf9, 32 }, + { 0xfa, 8 }, + { 0xfb, 8 }, + { 0xfc, 16 }, + { 0xfd, 9 }, +}; + +/* ALARMS: SYSCTL format described further below + REG: 64 bits in 8 registers, as immediately below */ +struct block1_t { + u8 host_status_1; + u8 host_status_2; + u8 host_status_3; + u8 host_status_4; + u8 p1_prochot_status; + u8 p2_prochot_status; + u8 gpi_status; + u8 fan_status; +}; + +/* + * Client-specific data + */ +struct lm93_data { + struct i2c_client client; + struct class_device *class_dev; + + struct mutex update_lock; + unsigned long last_updated; /* In jiffies */ + + /* client update function */ + void (*update)(struct lm93_data *, struct i2c_client *); + + char valid; /* !=0 if following fields are valid */ + + /* register values, arranged by block read groups */ + struct block1_t block1; + + /* temp1 - temp4: unfiltered readings + temp1 - temp2: filtered readings */ + u8 block2[6]; + + /* vin1 - vin16: readings */ + u8 block3[16]; + + /* prochot1 - prochot2: readings */ + struct { + u8 cur; + u8 avg; + } block4[2]; + + /* fan counts 1-4 => 14-bits, LE, *left* justified */ + u16 block5[4]; + + /* block6 has a lot of data we don't need */ + struct { + u8 min; + u8 max; + } temp_lim[3]; + + /* vin1 - vin16: low and high limits */ + struct { + u8 min; + u8 max; + } block7[16]; + + /* fan count limits 1-4 => same format as block5 */ + u16 block8[4]; + + /* pwm control registers (2 pwms, 4 regs) */ + u8 block9[2][4]; + + /* auto/pwm base temp and offset temp registers */ + struct { + u8 base[4]; + u8 offset[12]; + } block10; + + /* master config register */ + u8 config; + + /* VID1 & VID2 => register format, 6-bits, right justified */ + u8 vid[2]; + + /* prochot1 - prochot2: limits */ + u8 prochot_max[2]; + + /* vccp1 & vccp2 (in7 & in8): VID relative limits (register format) */ + u8 vccp_limits[2]; + + /* GPIO input state (register format, i.e. inverted) */ + u8 gpi; + + /* #PROCHOT override (register format) */ + u8 prochot_override; + + /* #PROCHOT intervals (register format) */ + u8 prochot_interval; + + /* Fan Boost Temperatures (register format) */ + u8 boost[4]; + + /* Fan Boost Hysteresis (register format) */ + u8 boost_hyst[2]; + + /* Temperature Zone Min. PWM & Hysteresis (register format) */ + u8 auto_pwm_min_hyst[2]; + + /* #PROCHOT & #VRDHOT PWM Ramp Control */ + u8 pwm_ramp_ctl; + + /* miscellaneous setup regs */ + u8 sfc1; + u8 sfc2; + u8 sf_tach_to_pwm; + + /* The two PWM CTL2 registers can read something other than what was + last written for the OVR_DC field (duty cycle override). So, we + save the user-commanded value here. */ + u8 pwm_override[2]; +}; + +/* VID: mV + REG: 6-bits, right justified, *always* using Intel VRM/VRD 10 */ +static int LM93_VID_FROM_REG(u8 reg) +{ + return vid_from_reg((reg & 0x3f), 100); +} + +/* min, max, and nominal register values, per channel (u8) */ +static const u8 lm93_vin_reg_min[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, +}; +static const u8 lm93_vin_reg_max[16] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, +}; +/* Values from the datasheet. They're here for documentation only. +static const u8 lm93_vin_reg_nom[16] = { + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x40, 0xc0, +}; +*/ + +/* min, max, and nominal voltage readings, per channel (mV)*/ +static const unsigned long lm93_vin_val_min[16] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3000, +}; + +static const unsigned long lm93_vin_val_max[16] = { + 1236, 1236, 1236, 1600, 2000, 2000, 1600, 1600, + 4400, 6500, 3333, 2625, 1312, 1312, 1236, 3600, +}; +/* Values from the datasheet. They're here for documentation only. +static const unsigned long lm93_vin_val_nom[16] = { + 927, 927, 927, 1200, 1500, 1500, 1200, 1200, + 3300, 5000, 2500, 1969, 984, 984, 309, 3300, +}; +*/ + +static unsigned LM93_IN_FROM_REG(int nr, u8 reg) +{ + const long uV_max = lm93_vin_val_max[nr] * 1000; + const long uV_min = lm93_vin_val_min[nr] * 1000; + + const long slope = (uV_max - uV_min) / + (lm93_vin_reg_max[nr] - lm93_vin_reg_min[nr]); + const long intercept = uV_min - slope * lm93_vin_reg_min[nr]; + + return (slope * reg + intercept + 500) / 1000; +} + +/* IN: mV, limits determined by channel nr + REG: scaling determined by channel nr */ +static u8 LM93_IN_TO_REG(int nr, unsigned val) +{ + /* range limit */ + const long mV = SENSORS_LIMIT(val, + lm93_vin_val_min[nr], lm93_vin_val_max[nr]); + + /* try not to lose too much precision here */ + const long uV = mV * 1000; + const long uV_max = lm93_vin_val_max[nr] * 1000; + const long uV_min = lm93_vin_val_min[nr] * 1000; + + /* convert */ + const long slope = (uV_max - uV_min) / + (lm93_vin_reg_max[nr] - lm93_vin_reg_min[nr]); + const long intercept = uV_min - slope * lm93_vin_reg_min[nr]; + + u8 result = ((uV - intercept + (slope/2)) / slope); + result = SENSORS_LIMIT(result, + lm93_vin_reg_min[nr], lm93_vin_reg_max[nr]); + return result; +} + +/* vid in mV, upper == 0 indicates low limit, otherwise upper limit */ +static unsigned LM93_IN_REL_FROM_REG(u8 reg, int upper, int vid) +{ + const long uV_offset = upper ? (((reg >> 4 & 0x0f) + 1) * 12500) : + (((reg >> 0 & 0x0f) + 1) * -25000); + const long uV_vid = vid * 1000; + return (uV_vid + uV_offset + 5000) / 10000; +} + +#define LM93_IN_MIN_FROM_REG(reg,vid) LM93_IN_REL_FROM_REG(reg,0,vid) +#define LM93_IN_MAX_FROM_REG(reg,vid) LM93_IN_REL_FROM_REG(reg,1,vid) + +/* vid in mV , upper == 0 indicates low limit, otherwise upper limit + upper also determines which nibble of the register is returned + (the other nibble will be 0x0) */ +static u8 LM93_IN_REL_TO_REG(unsigned val, int upper, int vid) +{ + long uV_offset = vid * 1000 - val * 10000; + if (upper) { + uV_offset = SENSORS_LIMIT(uV_offset, 12500, 200000); + return (u8)((uV_offset / 12500 - 1) << 4); + } else { + uV_offset = SENSORS_LIMIT(uV_offset, -400000, -25000); + return (u8)((uV_offset / -25000 - 1) << 0); + } +} + +/* TEMP: 1/1000 degrees C (-128C to +127C) + REG: 1C/bit, two's complement */ +static int LM93_TEMP_FROM_REG(u8 reg) +{ + return (s8)reg * 1000; +} + +#define LM93_TEMP_MIN (-128000) +#define LM93_TEMP_MAX ( 127000) + +/* TEMP: 1/1000 degrees C (-128C to +127C) + REG: 1C/bit, two's complement */ +static u8 LM93_TEMP_TO_REG(int temp) +{ + int ntemp = SENSORS_LIMIT(temp, LM93_TEMP_MIN, LM93_TEMP_MAX); + ntemp += (ntemp<0 ? -500 : 500); + return (u8)(ntemp / 1000); +} + +/* Determine 4-bit temperature offset resolution */ +static int LM93_TEMP_OFFSET_MODE_FROM_REG(u8 sfc2, int nr) +{ + /* mode: 0 => 1C/bit, nonzero => 0.5C/bit */ + return sfc2 & (nr < 2 ? 0x10 : 0x20); +} + +/* This function is common to all 4-bit temperature offsets + reg is 4 bits right justified + mode 0 => 1C/bit, mode !0 => 0.5C/bit */ +static int LM93_TEMP_OFFSET_FROM_REG(u8 reg, int mode) +{ + return (reg & 0x0f) * (mode ? 5 : 10); +} + +#define LM93_TEMP_OFFSET_MIN ( 0) +#define LM93_TEMP_OFFSET_MAX0 (150) +#define LM93_TEMP_OFFSET_MAX1 ( 75) + +/* This function is common to all 4-bit temperature offsets + returns 4 bits right justified + mode 0 => 1C/bit, mode !0 => 0.5C/bit */ +static u8 LM93_TEMP_OFFSET_TO_REG(int off, int mode) +{ + int factor = mode ? 5 : 10; + + off = SENSORS_LIMIT(off, LM93_TEMP_OFFSET_MIN, + mode ? LM93_TEMP_OFFSET_MAX1 : LM93_TEMP_OFFSET_MAX0); + return (u8)((off + factor/2) / factor); +} + +/* 0 <= nr <= 3 */ +static int LM93_TEMP_AUTO_OFFSET_FROM_REG(u8 reg, int nr, int mode) +{ + /* temp1-temp2 (nr=0,1) use lower nibble */ + if (nr < 2) + return LM93_TEMP_OFFSET_FROM_REG(reg & 0x0f, mode); + + /* temp3-temp4 (nr=2,3) use upper nibble */ + else + return LM93_TEMP_OFFSET_FROM_REG(reg >> 4 & 0x0f, mode); +} + +/* TEMP: 1/10 degrees C (0C to +15C (mode 0) or +7.5C (mode non-zero)) + REG: 1.0C/bit (mode 0) or 0.5C/bit (mode non-zero) + 0 <= nr <= 3 */ +static u8 LM93_TEMP_AUTO_OFFSET_TO_REG(u8 old, int off, int nr, int mode) +{ + u8 new = LM93_TEMP_OFFSET_TO_REG(off, mode); + + /* temp1-temp2 (nr=0,1) use lower nibble */ + if (nr < 2) + return (old & 0xf0) | (new & 0x0f); + + /* temp3-temp4 (nr=2,3) use upper nibble */ + else + return (new << 4 & 0xf0) | (old & 0x0f); +} + +static int LM93_AUTO_BOOST_HYST_FROM_REGS(struct lm93_data *data, int nr, + int mode) +{ + u8 reg; + + switch (nr) { + case 0: + reg = data->boost_hyst[0] & 0x0f; + break; + case 1: + reg = data->boost_hyst[0] >> 4 & 0x0f; + break; + case 2: + reg = data->boost_hyst[1] & 0x0f; + break; + case 3: + default: + reg = data->boost_hyst[1] >> 4 & 0x0f; + break; + } + + return LM93_TEMP_FROM_REG(data->boost[nr]) - + LM93_TEMP_OFFSET_FROM_REG(reg, mode); +} + +static u8 LM93_AUTO_BOOST_HYST_TO_REG(struct lm93_data *data, long hyst, + int nr, int mode) +{ + u8 reg = LM93_TEMP_OFFSET_TO_REG( + (LM93_TEMP_FROM_REG(data->boost[nr]) - hyst), mode); + + switch (nr) { + case 0: + reg = (data->boost_hyst[0] & 0xf0) | (reg & 0x0f); + break; + case 1: + reg = (reg << 4 & 0xf0) | (data->boost_hyst[0] & 0x0f); + break; + case 2: + reg = (data->boost_hyst[1] & 0xf0) | (reg & 0x0f); + break; + case 3: + default: + reg = (reg << 4 & 0xf0) | (data->boost_hyst[1] & 0x0f); + break; + } + + return reg; +} + +/* PWM: 0-255 per sensors documentation + REG: 0-13 as mapped below... right justified */ +typedef enum { LM93_PWM_MAP_HI_FREQ, LM93_PWM_MAP_LO_FREQ } pwm_freq_t; +static int lm93_pwm_map[2][16] = { + { + 0x00, /* 0.00% */ 0x40, /* 25.00% */ + 0x50, /* 31.25% */ 0x60, /* 37.50% */ + 0x70, /* 43.75% */ 0x80, /* 50.00% */ + 0x90, /* 56.25% */ 0xa0, /* 62.50% */ + 0xb0, /* 68.75% */ 0xc0, /* 75.00% */ + 0xd0, /* 81.25% */ 0xe0, /* 87.50% */ + 0xf0, /* 93.75% */ 0xff, /* 100.00% */ + 0xff, 0xff, /* 14, 15 are reserved and should never occur */ + }, + { + 0x00, /* 0.00% */ 0x40, /* 25.00% */ + 0x49, /* 28.57% */ 0x52, /* 32.14% */ + 0x5b, /* 35.71% */ 0x64, /* 39.29% */ + 0x6d, /* 42.86% */ 0x76, /* 46.43% */ + 0x80, /* 50.00% */ 0x89, /* 53.57% */ + 0x92, /* 57.14% */ 0xb6, /* 71.43% */ + 0xdb, /* 85.71% */ 0xff, /* 100.00% */ + 0xff, 0xff, /* 14, 15 are reserved and should never occur */ + }, +}; + +static int LM93_PWM_FROM_REG(u8 reg, pwm_freq_t freq) +{ + return lm93_pwm_map[freq][reg & 0x0f]; +} + +/* round up to nearest match */ +static u8 LM93_PWM_TO_REG(int pwm, pwm_freq_t freq) +{ + int i; + for (i = 0; i < 13; i++) + if (pwm <= lm93_pwm_map[freq][i]) + break; + + /* can fall through with i==13 */ + return (u8)i; +} + +static int LM93_FAN_FROM_REG(u16 regs) +{ + const u16 count = le16_to_cpu(regs) >> 2; + return count==0 ? -1 : count==0x3fff ? 0: 1350000 / count; +} + +/* + * RPM: (82.5 to 1350000) + * REG: 14-bits, LE, *left* justified + */ +static u16 LM93_FAN_TO_REG(long rpm) +{ + u16 count, regs; + + if (rpm == 0) { + count = 0x3fff; + } else { + rpm = SENSORS_LIMIT(rpm, 1, 1000000); + count = SENSORS_LIMIT((1350000 + rpm) / rpm, 1, 0x3ffe); + } + + regs = count << 2; + return cpu_to_le16(regs); +} + +/* PWM FREQ: HZ + REG: 0-7 as mapped below */ +static int lm93_pwm_freq_map[8] = { + 22500, 96, 84, 72, 60, 48, 36, 12 +}; + +static int LM93_PWM_FREQ_FROM_REG(u8 reg) +{ + return lm93_pwm_freq_map[reg & 0x07]; +} + +/* round up to nearest match */ +static u8 LM93_PWM_FREQ_TO_REG(int freq) +{ + int i; + for (i = 7; i > 0; i--) + if (freq <= lm93_pwm_freq_map[i]) + break; + + /* can fall through with i==0 */ + return (u8)i; +} + +/* TIME: 1/100 seconds + * REG: 0-7 as mapped below */ +static int lm93_spinup_time_map[8] = { + 0, 10, 25, 40, 70, 100, 200, 400, +}; + +static int LM93_SPINUP_TIME_FROM_REG(u8 reg) +{ + return lm93_spinup_time_map[reg >> 5 & 0x07]; +} + +/* round up to nearest match */ +static u8 LM93_SPINUP_TIME_TO_REG(int time) +{ + int i; + for (i = 0; i < 7; i++) + if (time <= lm93_spinup_time_map[i]) + break; + + /* can fall through with i==8 */ + return (u8)i; +} + +#define LM93_RAMP_MIN 0 +#define LM93_RAMP_MAX 75 + +static int LM93_RAMP_FROM_REG(u8 reg) +{ + return (reg & 0x0f) * 5; +} + +/* RAMP: 1/100 seconds + REG: 50mS/bit 4-bits right justified */ +static u8 LM93_RAMP_TO_REG(int ramp) +{ + ramp = SENSORS_LIMIT(ramp, LM93_RAMP_MIN, LM93_RAMP_MAX); + return (u8)((ramp + 2) / 5); +} + +/* PROCHOT: 0-255, 0 => 0%, 255 => > 96.6% + * REG: (same) */ +static u8 LM93_PROCHOT_TO_REG(long prochot) +{ + prochot = SENSORS_LIMIT(prochot, 0, 255); + return (u8)prochot; +} + +/* PROCHOT-INTERVAL: 73 - 37200 (1/100 seconds) + * REG: 0-9 as mapped below */ +static int lm93_interval_map[10] = { + 73, 146, 290, 580, 1170, 2330, 4660, 9320, 18600, 37200, +}; + +static int LM93_INTERVAL_FROM_REG(u8 reg) +{ + return lm93_interval_map[reg & 0x0f]; +} + +/* round up to nearest match */ +static u8 LM93_INTERVAL_TO_REG(long interval) +{ + int i; + for (i = 0; i < 9; i++) + if (interval <= lm93_interval_map[i]) + break; + + /* can fall through with i==9 */ + return (u8)i; +} + +/* GPIO: 0-255, GPIO0 is LSB + * REG: inverted */ +static unsigned LM93_GPI_FROM_REG(u8 reg) +{ + return ~reg & 0xff; +} + +/* alarm bitmask definitions + The LM93 has nearly 64 bits of error status... I've pared that down to + what I think is a useful subset in order to fit it into 32 bits. + + Especially note that the #VRD_HOT alarms are missing because we provide + that information as values in another sysfs file. + + If libsensors is extended to support 64 bit values, this could be revisited. +*/ +#define LM93_ALARM_IN1 0x00000001 +#define LM93_ALARM_IN2 0x00000002 +#define LM93_ALARM_IN3 0x00000004 +#define LM93_ALARM_IN4 0x00000008 +#define LM93_ALARM_IN5 0x00000010 +#define LM93_ALARM_IN6 0x00000020 +#define LM93_ALARM_IN7 0x00000040 +#define LM93_ALARM_IN8 0x00000080 +#define LM93_ALARM_IN9 0x00000100 +#define LM93_ALARM_IN10 0x00000200 +#define LM93_ALARM_IN11 0x00000400 +#define LM93_ALARM_IN12 0x00000800 +#define LM93_ALARM_IN13 0x00001000 +#define LM93_ALARM_IN14 0x00002000 +#define LM93_ALARM_IN15 0x00004000 +#define LM93_ALARM_IN16 0x00008000 +#define LM93_ALARM_FAN1 0x00010000 +#define LM93_ALARM_FAN2 0x00020000 +#define LM93_ALARM_FAN3 0x00040000 +#define LM93_ALARM_FAN4 0x00080000 +#define LM93_ALARM_PH1_ERR 0x00100000 +#define LM93_ALARM_PH2_ERR 0x00200000 +#define LM93_ALARM_SCSI1_ERR 0x00400000 +#define LM93_ALARM_SCSI2_ERR 0x00800000 +#define LM93_ALARM_DVDDP1_ERR 0x01000000 +#define LM93_ALARM_DVDDP2_ERR 0x02000000 +#define LM93_ALARM_D1_ERR 0x04000000 +#define LM93_ALARM_D2_ERR 0x08000000 +#define LM93_ALARM_TEMP1 0x10000000 +#define LM93_ALARM_TEMP2 0x20000000 +#define LM93_ALARM_TEMP3 0x40000000 + +static unsigned LM93_ALARMS_FROM_REG(struct block1_t b1) +{ + unsigned result; + result = b1.host_status_2 & 0x3f; + + if (vccp_limit_type[0]) + result |= (b1.host_status_4 & 0x10) << 2; + else + result |= b1.host_status_2 & 0x40; + + if (vccp_limit_type[1]) + result |= (b1.host_status_4 & 0x20) << 2; + else + result |= b1.host_status_2 & 0x80; + + result |= b1.host_status_3 << 8; + result |= (b1.fan_status & 0x0f) << 16; + result |= (b1.p1_prochot_status & 0x80) << 13; + result |= (b1.p2_prochot_status & 0x80) << 14; + result |= (b1.host_status_4 & 0xfc) << 20; + result |= (b1.host_status_1 & 0x07) << 28; + return result; +} + +#define MAX_RETRIES 5 + +static u8 lm93_read_byte(struct i2c_client *client, u8 reg) +{ + int value, i; + + /* retry in case of read errors */ + for (i=1; i<=MAX_RETRIES; i++) { + if ((value = i2c_smbus_read_byte_data(client, reg)) >= 0) { + return value; + } else { + dev_warn(&client->dev,"lm93: read byte data failed, " + "address 0x%02x.\n", reg); + mdelay(i + 3); + } + + } + + /* <TODO> what to return in case of error? */ + dev_err(&client->dev,"lm93: All read byte retries failed!!\n"); + return 0; +} + +static int lm93_write_byte(struct i2c_client *client, u8 reg, u8 value) +{ + int result; + + /* <TODO> how to handle write errors? */ + result = i2c_smbus_write_byte_data(client, reg, value); + + if (result < 0) + dev_warn(&client->dev,"lm93: write byte data failed, " + "0x%02x at address 0x%02x.\n", value, reg); + + return result; +} + +static u16 lm93_read_word(struct i2c_client *client, u8 reg) +{ + int value, i; + + /* retry in case of read errors */ + for (i=1; i<=MAX_RETRIES; i++) { + if ((value = i2c_smbus_read_word_data(client, reg)) >= 0) { + return value; + } else { + dev_warn(&client->dev,"lm93: read word data failed, " + "address 0x%02x.\n", reg); + mdelay(i + 3); + } + + } + + /* <TODO> what to return in case of error? */ + dev_err(&client->dev,"lm93: All read word retries failed!!\n"); + return 0; +} + +static int lm93_write_word(struct i2c_client *client, u8 reg, u16 value) +{ + int result; + + /* <TODO> how to handle write errors? */ + result = i2c_smbus_write_word_data(client, reg, value); + + if (result < 0) + dev_warn(&client->dev,"lm93: write word data failed, " + "0x%04x at address 0x%02x.\n", value, reg); + + return result; +} + +static u8 lm93_block_buffer[I2C_SMBUS_BLOCK_MAX]; + +/* + read block data into values, retry if not expected length + fbn => index to lm93_block_read_cmds table + (Fixed Block Number - section 14.5.2 of LM93 datasheet) +*/ +static void lm93_read_block(struct i2c_client *client, u8 fbn, u8 *values) +{ + int i, result=0; + + for (i = 1; i <= MAX_RETRIES; i++) { + result = i2c_smbus_read_block_data(client, + lm93_block_read_cmds[fbn].cmd, lm93_block_buffer); + + if (result == lm93_block_read_cmds[fbn].len) { + break; + } else { + dev_warn(&client->dev,"lm93: block read data failed, " + "command 0x%02x.\n", + lm93_block_read_cmds[fbn].cmd); + mdelay(i + 3); + } + } + + if (result == lm93_block_read_cmds[fbn].len) { + memcpy(values,lm93_block_buffer,lm93_block_read_cmds[fbn].len); + } else { + /* <TODO> what to do in case of error? */ + } +} + +static struct lm93_data *lm93_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + const unsigned long interval = HZ + (HZ / 2); + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + interval) || + !data->valid) { + + data->update(data, client); + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + return data; +} + +/* update routine for data that has no corresponding SMBus block command */ +static void lm93_update_client_common(struct lm93_data *data, + struct i2c_client *client) +{ + int i; + u8 *ptr; + + /* temp1 - temp4: limits */ + for (i = 0; i < 4; i++) { + data->temp_lim[i].min = + lm93_read_byte(client, LM93_REG_TEMP_MIN(i)); + data->temp_lim[i].max = + lm93_read_byte(client, LM93_REG_TEMP_MAX(i)); + } + + /* config register */ + data->config = lm93_read_byte(client, LM93_REG_CONFIG); + + /* vid1 - vid2: values */ + for (i = 0; i < 2; i++) + data->vid[i] = lm93_read_byte(client, LM93_REG_VID(i)); + + /* prochot1 - prochot2: limits */ + for (i = 0; i < 2; i++) + data->prochot_max[i] = lm93_read_byte(client, + LM93_REG_PROCHOT_MAX(i)); + + /* vccp1 - vccp2: VID relative limits */ + for (i = 0; i < 2; i++) + data->vccp_limits[i] = lm93_read_byte(client, + LM93_REG_VCCP_LIMIT_OFF(i)); + + /* GPIO input state */ + data->gpi = lm93_read_byte(client, LM93_REG_GPI); + + /* #PROCHOT override state */ + data->prochot_override = lm93_read_byte(client, + LM93_REG_PROCHOT_OVERRIDE); + + /* #PROCHOT intervals */ + data->prochot_interval = lm93_read_byte(client, + LM93_REG_PROCHOT_INTERVAL); + + /* Fan Boost Termperature registers */ + for (i = 0; i < 4; i++) + data->boost[i] = lm93_read_byte(client, LM93_REG_BOOST(i)); + + /* Fan Boost Temperature Hyst. registers */ + data->boost_hyst[0] = lm93_read_byte(client, LM93_REG_BOOST_HYST_12); + data->boost_hyst[1] = lm93_read_byte(client, LM93_REG_BOOST_HYST_34); + + /* Temperature Zone Min. PWM & Hysteresis registers */ + data->auto_pwm_min_hyst[0] = + lm93_read_byte(client, LM93_REG_PWM_MIN_HYST_12); + data->auto_pwm_min_hyst[1] = + lm93_read_byte(client, LM93_REG_PWM_MIN_HYST_34); + + /* #PROCHOT & #VRDHOT PWM Ramp Control register */ + data->pwm_ramp_ctl = lm93_read_byte(client, LM93_REG_PWM_RAMP_CTL); + + /* misc setup registers */ + data->sfc1 = lm93_read_byte(client, LM93_REG_SFC1); + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sf_tach_to_pwm = lm93_read_byte(client, + LM93_REG_SF_TACH_TO_PWM); + + /* write back alarm values to clear */ + for (i = 0, ptr = (u8 *)(&data->block1); i < 8; i++) + lm93_write_byte(client, LM93_REG_HOST_ERROR_1 + i, *(ptr + i)); +} + +/* update routine which uses SMBus block data commands */ +static void lm93_update_client_full(struct lm93_data *data, + struct i2c_client *client) +{ + dev_dbg(&client->dev,"starting device update (block data enabled)\n"); + + /* in1 - in16: values & limits */ + lm93_read_block(client, 3, (u8 *)(data->block3)); + lm93_read_block(client, 7, (u8 *)(data->block7)); + + /* temp1 - temp4: values */ + lm93_read_block(client, 2, (u8 *)(data->block2)); + + /* prochot1 - prochot2: values */ + lm93_read_block(client, 4, (u8 *)(data->block4)); + + /* fan1 - fan4: values & limits */ + lm93_read_block(client, 5, (u8 *)(data->block5)); + lm93_read_block(client, 8, (u8 *)(data->block8)); + + /* pmw control registers */ + lm93_read_block(client, 9, (u8 *)(data->block9)); + + /* alarm values */ + lm93_read_block(client, 1, (u8 *)(&data->block1)); + + /* auto/pwm registers */ + lm93_read_block(client, 10, (u8 *)(&data->block10)); + + lm93_update_client_common(data, client); +} + +/* update routine which uses SMBus byte/word data commands only */ +static void lm93_update_client_min(struct lm93_data *data, + struct i2c_client *client) +{ + int i,j; + u8 *ptr; + + dev_dbg(&client->dev,"starting device update (block data disabled)\n"); + + /* in1 - in16: values & limits */ + for (i = 0; i < 16; i++) { + data->block3[i] = + lm93_read_byte(client, LM93_REG_IN(i)); + data->block7[i].min = + lm93_read_byte(client, LM93_REG_IN_MIN(i)); + data->block7[i].max = + lm93_read_byte(client, LM93_REG_IN_MAX(i)); + } + + /* temp1 - temp4: values */ + for (i = 0; i < 4; i++) { + data->block2[i] = + lm93_read_byte(client, LM93_REG_TEMP(i)); + } + + /* prochot1 - prochot2: values */ + for (i = 0; i < 2; i++) { + data->block4[i].cur = + lm93_read_byte(client, LM93_REG_PROCHOT_CUR(i)); + data->block4[i].avg = + lm93_read_byte(client, LM93_REG_PROCHOT_AVG(i)); + } + + /* fan1 - fan4: values & limits */ + for (i = 0; i < 4; i++) { + data->block5[i] = + lm93_read_word(client, LM93_REG_FAN(i)); + data->block8[i] = + lm93_read_word(client, LM93_REG_FAN_MIN(i)); + } + + /* pwm control registers */ + for (i = 0; i < 2; i++) { + for (j = 0; j < 4; j++) { + data->block9[i][j] = + lm93_read_byte(client, LM93_REG_PWM_CTL(i,j)); + } + } + + /* alarm values */ + for (i = 0, ptr = (u8 *)(&data->block1); i < 8; i++) { + *(ptr + i) = + lm93_read_byte(client, LM93_REG_HOST_ERROR_1 + i); + } + + /* auto/pwm (base temp) registers */ + for (i = 0; i < 4; i++) { + data->block10.base[i] = + lm93_read_byte(client, LM93_REG_TEMP_BASE(i)); + } + + /* auto/pwm (offset temp) registers */ + for (i = 0; i < 12; i++) { + data->block10.offset[i] = + lm93_read_byte(client, LM93_REG_TEMP_OFFSET(i)); + } + + lm93_update_client_common(data, client); +} + +/* following are the sysfs callback functions */ +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", LM93_IN_FROM_REG(nr, data->block3[nr])); +} + +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 0); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in, NULL, 2); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_in, NULL, 3); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, show_in, NULL, 4); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_in, NULL, 5); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_in, NULL, 6); +static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, show_in, NULL, 7); +static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, show_in, NULL, 8); +static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, show_in, NULL, 9); +static SENSOR_DEVICE_ATTR(in11_input, S_IRUGO, show_in, NULL, 10); +static SENSOR_DEVICE_ATTR(in12_input, S_IRUGO, show_in, NULL, 11); +static SENSOR_DEVICE_ATTR(in13_input, S_IRUGO, show_in, NULL, 12); +static SENSOR_DEVICE_ATTR(in14_input, S_IRUGO, show_in, NULL, 13); +static SENSOR_DEVICE_ATTR(in15_input, S_IRUGO, show_in, NULL, 14); +static SENSOR_DEVICE_ATTR(in16_input, S_IRUGO, show_in, NULL, 15); + +static ssize_t show_in_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + int vccp = nr - 6; + long rc, vid; + + if ((nr==6 || nr==7) && (vccp_limit_type[vccp])) { + vid = LM93_VID_FROM_REG(data->vid[vccp]); + rc = LM93_IN_MIN_FROM_REG(data->vccp_limits[vccp], vid); + } + else { + rc = LM93_IN_FROM_REG(nr, data->block7[nr].min); \ + } + return sprintf(buf, "%ld\n", rc); \ +} + +static ssize_t store_in_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + int vccp = nr - 6; + long vid; + + mutex_lock(&data->update_lock); + if ((nr==6 || nr==7) && (vccp_limit_type[vccp])) { + vid = LM93_VID_FROM_REG(data->vid[vccp]); + data->vccp_limits[vccp] = (data->vccp_limits[vccp] & 0xf0) | + LM93_IN_REL_TO_REG(val, 0, vid); + lm93_write_byte(client, LM93_REG_VCCP_LIMIT_OFF(vccp), + data->vccp_limits[vccp]); + } + else { + data->block7[nr].min = LM93_IN_TO_REG(nr,val); + lm93_write_byte(client, LM93_REG_IN_MIN(nr), + data->block7[nr].min); + } + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(in1_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 0); +static SENSOR_DEVICE_ATTR(in2_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 1); +static SENSOR_DEVICE_ATTR(in3_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 2); +static SENSOR_DEVICE_ATTR(in4_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 3); +static SENSOR_DEVICE_ATTR(in5_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 4); +static SENSOR_DEVICE_ATTR(in6_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 5); +static SENSOR_DEVICE_ATTR(in7_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 6); +static SENSOR_DEVICE_ATTR(in8_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 7); +static SENSOR_DEVICE_ATTR(in9_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 8); +static SENSOR_DEVICE_ATTR(in10_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 9); +static SENSOR_DEVICE_ATTR(in11_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 10); +static SENSOR_DEVICE_ATTR(in12_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 11); +static SENSOR_DEVICE_ATTR(in13_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 12); +static SENSOR_DEVICE_ATTR(in14_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 13); +static SENSOR_DEVICE_ATTR(in15_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 14); +static SENSOR_DEVICE_ATTR(in16_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 15); + +static ssize_t show_in_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + int vccp = nr - 6; + long rc, vid; + + if ((nr==6 || nr==7) && (vccp_limit_type[vccp])) { + vid = LM93_VID_FROM_REG(data->vid[vccp]); + rc = LM93_IN_MAX_FROM_REG(data->vccp_limits[vccp],vid); + } + else { + rc = LM93_IN_FROM_REG(nr,data->block7[nr].max); \ + } + return sprintf(buf,"%ld\n",rc); \ +} + +static ssize_t store_in_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + int vccp = nr - 6; + long vid; + + mutex_lock(&data->update_lock); + if ((nr==6 || nr==7) && (vccp_limit_type[vccp])) { + vid = LM93_VID_FROM_REG(data->vid[vccp]); + data->vccp_limits[vccp] = (data->vccp_limits[vccp] & 0x0f) | + LM93_IN_REL_TO_REG(val, 1, vid); + lm93_write_byte(client, LM93_REG_VCCP_LIMIT_OFF(vccp), + data->vccp_limits[vccp]); + } + else { + data->block7[nr].max = LM93_IN_TO_REG(nr,val); + lm93_write_byte(client, LM93_REG_IN_MAX(nr), + data->block7[nr].max); + } + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(in1_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 0); +static SENSOR_DEVICE_ATTR(in2_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 1); +static SENSOR_DEVICE_ATTR(in3_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 2); +static SENSOR_DEVICE_ATTR(in4_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 3); +static SENSOR_DEVICE_ATTR(in5_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 4); +static SENSOR_DEVICE_ATTR(in6_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 5); +static SENSOR_DEVICE_ATTR(in7_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 6); +static SENSOR_DEVICE_ATTR(in8_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 7); +static SENSOR_DEVICE_ATTR(in9_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 8); +static SENSOR_DEVICE_ATTR(in10_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 9); +static SENSOR_DEVICE_ATTR(in11_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 10); +static SENSOR_DEVICE_ATTR(in12_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 11); +static SENSOR_DEVICE_ATTR(in13_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 12); +static SENSOR_DEVICE_ATTR(in14_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 13); +static SENSOR_DEVICE_ATTR(in15_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 14); +static SENSOR_DEVICE_ATTR(in16_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 15); + +static ssize_t show_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",LM93_TEMP_FROM_REG(data->block2[nr])); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); + +static ssize_t show_temp_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",LM93_TEMP_FROM_REG(data->temp_lim[nr].min)); +} + +static ssize_t store_temp_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->temp_lim[nr].min = LM93_TEMP_TO_REG(val); + lm93_write_byte(client, LM93_REG_TEMP_MIN(nr), data->temp_lim[nr].min); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, + show_temp_min, store_temp_min, 0); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, + show_temp_min, store_temp_min, 1); +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, + show_temp_min, store_temp_min, 2); + +static ssize_t show_temp_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",LM93_TEMP_FROM_REG(data->temp_lim[nr].max)); +} + +static ssize_t store_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->temp_lim[nr].max = LM93_TEMP_TO_REG(val); + lm93_write_byte(client, LM93_REG_TEMP_MAX(nr), data->temp_lim[nr].max); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, + show_temp_max, store_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, + show_temp_max, store_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, + show_temp_max, store_temp_max, 2); + +static ssize_t show_temp_auto_base(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",LM93_TEMP_FROM_REG(data->block10.base[nr])); +} + +static ssize_t store_temp_auto_base(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->block10.base[nr] = LM93_TEMP_TO_REG(val); + lm93_write_byte(client, LM93_REG_TEMP_BASE(nr), data->block10.base[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_auto_base, S_IWUSR | S_IRUGO, + show_temp_auto_base, store_temp_auto_base, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_base, S_IWUSR | S_IRUGO, + show_temp_auto_base, store_temp_auto_base, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_base, S_IWUSR | S_IRUGO, + show_temp_auto_base, store_temp_auto_base, 2); + +static ssize_t show_temp_auto_boost(struct device *dev, + struct device_attribute *attr,char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",LM93_TEMP_FROM_REG(data->boost[nr])); +} + +static ssize_t store_temp_auto_boost(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->boost[nr] = LM93_TEMP_TO_REG(val); + lm93_write_byte(client, LM93_REG_BOOST(nr), data->boost[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_auto_boost, S_IWUSR | S_IRUGO, + show_temp_auto_boost, store_temp_auto_boost, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_boost, S_IWUSR | S_IRUGO, + show_temp_auto_boost, store_temp_auto_boost, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_boost, S_IWUSR | S_IRUGO, + show_temp_auto_boost, store_temp_auto_boost, 2); + +static ssize_t show_temp_auto_boost_hyst(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + int mode = LM93_TEMP_OFFSET_MODE_FROM_REG(data->sfc2, nr); + return sprintf(buf,"%d\n", + LM93_AUTO_BOOST_HYST_FROM_REGS(data, nr, mode)); +} + +static ssize_t store_temp_auto_boost_hyst(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + /* force 0.5C/bit mode */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sfc2 |= ((nr < 2) ? 0x10 : 0x20); + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); + data->boost_hyst[nr/2] = LM93_AUTO_BOOST_HYST_TO_REG(data, val, nr, 1); + lm93_write_byte(client, LM93_REG_BOOST_HYST(nr), + data->boost_hyst[nr/2]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_auto_boost_hyst, S_IWUSR | S_IRUGO, + show_temp_auto_boost_hyst, + store_temp_auto_boost_hyst, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_boost_hyst, S_IWUSR | S_IRUGO, + show_temp_auto_boost_hyst, + store_temp_auto_boost_hyst, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_boost_hyst, S_IWUSR | S_IRUGO, + show_temp_auto_boost_hyst, + store_temp_auto_boost_hyst, 2); + +static ssize_t show_temp_auto_offset(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *s_attr = to_sensor_dev_attr_2(attr); + int nr = s_attr->index; + int ofs = s_attr->nr; + struct lm93_data *data = lm93_update_device(dev); + int mode = LM93_TEMP_OFFSET_MODE_FROM_REG(data->sfc2, nr); + return sprintf(buf,"%d\n", + LM93_TEMP_AUTO_OFFSET_FROM_REG(data->block10.offset[ofs], + nr,mode)); +} + +static ssize_t store_temp_auto_offset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *s_attr = to_sensor_dev_attr_2(attr); + int nr = s_attr->index; + int ofs = s_attr->nr; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + /* force 0.5C/bit mode */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sfc2 |= ((nr < 2) ? 0x10 : 0x20); + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); + data->block10.offset[ofs] = LM93_TEMP_AUTO_OFFSET_TO_REG( + data->block10.offset[ofs], val, nr, 1); + lm93_write_byte(client, LM93_REG_TEMP_OFFSET(ofs), + data->block10.offset[ofs]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset1, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 0, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset2, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 1, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset3, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 2, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset4, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 3, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset5, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 4, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset6, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 5, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset7, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 6, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset8, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 7, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset9, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 8, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset10, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 9, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset11, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 10, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset12, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 11, 0); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset1, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 0, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset2, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 1, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset3, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 2, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset4, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 3, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset5, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 4, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset6, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 5, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset7, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 6, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset8, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 7, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset9, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 8, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset10, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 9, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset11, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 10, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset12, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 11, 1); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset1, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 0, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset2, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 1, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset3, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 2, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset4, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 3, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset5, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 4, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset6, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 5, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset7, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 6, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset8, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 7, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset9, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 8, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset10, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 9, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset11, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 10, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset12, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 11, 2); + +static ssize_t show_temp_auto_pwm_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + u8 reg, ctl4; + struct lm93_data *data = lm93_update_device(dev); + reg = data->auto_pwm_min_hyst[nr/2] >> 4 & 0x0f; + ctl4 = data->block9[nr][LM93_PWM_CTL4]; + return sprintf(buf,"%d\n",LM93_PWM_FROM_REG(reg, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : LM93_PWM_MAP_HI_FREQ)); +} + +static ssize_t store_temp_auto_pwm_min(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + u8 reg, ctl4; + + mutex_lock(&data->update_lock); + reg = lm93_read_byte(client, LM93_REG_PWM_MIN_HYST(nr)); + ctl4 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr,LM93_PWM_CTL4)); + reg = (reg & 0x0f) | + LM93_PWM_TO_REG(val, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : + LM93_PWM_MAP_HI_FREQ) << 4; + data->auto_pwm_min_hyst[nr/2] = reg; + lm93_write_byte(client, LM93_REG_PWM_MIN_HYST(nr), reg); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_auto_pwm_min, S_IWUSR | S_IRUGO, + show_temp_auto_pwm_min, + store_temp_auto_pwm_min, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_pwm_min, S_IWUSR | S_IRUGO, + show_temp_auto_pwm_min, + store_temp_auto_pwm_min, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_pwm_min, S_IWUSR | S_IRUGO, + show_temp_auto_pwm_min, + store_temp_auto_pwm_min, 2); + +static ssize_t show_temp_auto_offset_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + int mode = LM93_TEMP_OFFSET_MODE_FROM_REG(data->sfc2, nr); + return sprintf(buf,"%d\n",LM93_TEMP_OFFSET_FROM_REG( + data->auto_pwm_min_hyst[nr/2], mode)); +} + +static ssize_t store_temp_auto_offset_hyst(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + u8 reg; + + mutex_lock(&data->update_lock); + /* force 0.5C/bit mode */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sfc2 |= ((nr < 2) ? 0x10 : 0x20); + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); + reg = data->auto_pwm_min_hyst[nr/2]; + reg = (reg & 0xf0) | (LM93_TEMP_OFFSET_TO_REG(val, 1) & 0x0f); + data->auto_pwm_min_hyst[nr/2] = reg; + lm93_write_byte(client, LM93_REG_PWM_MIN_HYST(nr), reg); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_auto_offset_hyst, S_IWUSR | S_IRUGO, + show_temp_auto_offset_hyst, + store_temp_auto_offset_hyst, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_offset_hyst, S_IWUSR | S_IRUGO, + show_temp_auto_offset_hyst, + store_temp_auto_offset_hyst, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_offset_hyst, S_IWUSR | S_IRUGO, + show_temp_auto_offset_hyst, + store_temp_auto_offset_hyst, 2); + +static ssize_t show_fan_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *s_attr = to_sensor_dev_attr(attr); + int nr = s_attr->index; + struct lm93_data *data = lm93_update_device(dev); + + return sprintf(buf,"%d\n",LM93_FAN_FROM_REG(data->block5[nr])); +} + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan_input, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan_input, NULL, 3); + +static ssize_t show_fan_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + + return sprintf(buf,"%d\n",LM93_FAN_FROM_REG(data->block8[nr])); +} + +static ssize_t store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->block8[nr] = LM93_FAN_TO_REG(val); + lm93_write_word(client,LM93_REG_FAN_MIN(nr),data->block8[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan3_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 2); +static SENSOR_DEVICE_ATTR(fan4_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 3); + +/* some tedious bit-twiddling here to deal with the register format: + + data->sf_tach_to_pwm: (tach to pwm mapping bits) + + bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + T4:P2 T4:P1 T3:P2 T3:P1 T2:P2 T2:P1 T1:P2 T1:P1 + + data->sfc2: (enable bits) + + bit | 3 | 2 | 1 | 0 + T4 T3 T2 T1 +*/ + +static ssize_t show_fan_smart_tach(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + long rc = 0; + int mapping; + + /* extract the relevant mapping */ + mapping = (data->sf_tach_to_pwm >> (nr * 2)) & 0x03; + + /* if there's a mapping and it's enabled */ + if (mapping && ((data->sfc2 >> nr) & 0x01)) + rc = mapping; + return sprintf(buf,"%ld\n",rc); +} + +/* helper function - must grab data->update_lock before calling + fan is 0-3, indicating fan1-fan4 */ +static void lm93_write_fan_smart_tach(struct i2c_client *client, + struct lm93_data *data, int fan, long value) +{ + /* insert the new mapping and write it out */ + data->sf_tach_to_pwm = lm93_read_byte(client, LM93_REG_SF_TACH_TO_PWM); + data->sf_tach_to_pwm &= ~(0x3 << fan * 2); + data->sf_tach_to_pwm |= value << fan * 2; + lm93_write_byte(client, LM93_REG_SF_TACH_TO_PWM, data->sf_tach_to_pwm); + + /* insert the enable bit and write it out */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + if (value) + data->sfc2 |= 1 << fan; + else + data->sfc2 &= ~(1 << fan); + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); +} + +static ssize_t store_fan_smart_tach(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + /* sanity test, ignore the write otherwise */ + if (0 <= val && val <= 2) { + /* can't enable if pwm freq is 22.5KHz */ + if (val) { + u8 ctl4 = lm93_read_byte(client, + LM93_REG_PWM_CTL(val-1,LM93_PWM_CTL4)); + if ((ctl4 & 0x07) == 0) + val = 0; + } + lm93_write_fan_smart_tach(client, data, nr, val); + } + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(fan1_smart_tach, S_IWUSR | S_IRUGO, + show_fan_smart_tach, store_fan_smart_tach, 0); +static SENSOR_DEVICE_ATTR(fan2_smart_tach, S_IWUSR | S_IRUGO, + show_fan_smart_tach, store_fan_smart_tach, 1); +static SENSOR_DEVICE_ATTR(fan3_smart_tach, S_IWUSR | S_IRUGO, + show_fan_smart_tach, store_fan_smart_tach, 2); +static SENSOR_DEVICE_ATTR(fan4_smart_tach, S_IWUSR | S_IRUGO, + show_fan_smart_tach, store_fan_smart_tach, 3); + +static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + u8 ctl2, ctl4; + long rc; + + ctl2 = data->block9[nr][LM93_PWM_CTL2]; + ctl4 = data->block9[nr][LM93_PWM_CTL4]; + if (ctl2 & 0x01) /* show user commanded value if enabled */ + rc = data->pwm_override[nr]; + else /* show present h/w value if manual pwm disabled */ + rc = LM93_PWM_FROM_REG(ctl2 >> 4, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : LM93_PWM_MAP_HI_FREQ); + return sprintf(buf,"%ld\n",rc); +} + +static ssize_t store_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + u8 ctl2, ctl4; + + mutex_lock(&data->update_lock); + ctl2 = lm93_read_byte(client,LM93_REG_PWM_CTL(nr,LM93_PWM_CTL2)); + ctl4 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr,LM93_PWM_CTL4)); + ctl2 = (ctl2 & 0x0f) | LM93_PWM_TO_REG(val,(ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : LM93_PWM_MAP_HI_FREQ) << 4; + /* save user commanded value */ + data->pwm_override[nr] = LM93_PWM_FROM_REG(ctl2 >> 4, + (ctl4 & 0x07) ? LM93_PWM_MAP_LO_FREQ : + LM93_PWM_MAP_HI_FREQ); + lm93_write_byte(client,LM93_REG_PWM_CTL(nr,LM93_PWM_CTL2),ctl2); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 1); + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + u8 ctl2; + long rc; + + ctl2 = data->block9[nr][LM93_PWM_CTL2]; + if (ctl2 & 0x01) /* manual override enabled ? */ + rc = ((ctl2 & 0xF0) == 0xF0) ? 0 : 1; + else + rc = 2; + return sprintf(buf,"%ld\n",rc); +} + +static ssize_t store_pwm_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + u8 ctl2; + + mutex_lock(&data->update_lock); + ctl2 = lm93_read_byte(client,LM93_REG_PWM_CTL(nr,LM93_PWM_CTL2)); + + switch (val) { + case 0: + ctl2 |= 0xF1; /* enable manual override, set PWM to max */ + break; + case 1: ctl2 |= 0x01; /* enable manual override */ + break; + case 2: ctl2 &= ~0x01; /* disable manual override */ + break; + default: + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + lm93_write_byte(client,LM93_REG_PWM_CTL(nr,LM93_PWM_CTL2),ctl2); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + show_pwm_enable, store_pwm_enable, 0); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, + show_pwm_enable, store_pwm_enable, 1); + +static ssize_t show_pwm_freq(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + u8 ctl4; + + ctl4 = data->block9[nr][LM93_PWM_CTL4]; + return sprintf(buf,"%d\n",LM93_PWM_FREQ_FROM_REG(ctl4)); +} + +/* helper function - must grab data->update_lock before calling + pwm is 0-1, indicating pwm1-pwm2 + this disables smart tach for all tach channels bound to the given pwm */ +static void lm93_disable_fan_smart_tach(struct i2c_client *client, + struct lm93_data *data, int pwm) +{ + int mapping = lm93_read_byte(client, LM93_REG_SF_TACH_TO_PWM); + int mask; + + /* collapse the mapping into a mask of enable bits */ + mapping = (mapping >> pwm) & 0x55; + mask = mapping & 0x01; + mask |= (mapping & 0x04) >> 1; + mask |= (mapping & 0x10) >> 2; + mask |= (mapping & 0x40) >> 3; + + /* disable smart tach according to the mask */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sfc2 &= ~mask; + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); +} + +static ssize_t store_pwm_freq(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + u8 ctl4; + + mutex_lock(&data->update_lock); + ctl4 = lm93_read_byte(client,LM93_REG_PWM_CTL(nr,LM93_PWM_CTL4)); + ctl4 = (ctl4 & 0xf8) | LM93_PWM_FREQ_TO_REG(val); + data->block9[nr][LM93_PWM_CTL4] = ctl4; + /* ctl4 == 0 -> 22.5KHz -> disable smart tach */ + if (!ctl4) + lm93_disable_fan_smart_tach(client, data, nr); + lm93_write_byte(client, LM93_REG_PWM_CTL(nr,LM93_PWM_CTL4), ctl4); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_freq, S_IWUSR | S_IRUGO, + show_pwm_freq, store_pwm_freq, 0); +static SENSOR_DEVICE_ATTR(pwm2_freq, S_IWUSR | S_IRUGO, + show_pwm_freq, store_pwm_freq, 1); + +static ssize_t show_pwm_auto_channels(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",data->block9[nr][LM93_PWM_CTL1]); +} + +static ssize_t store_pwm_auto_channels(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->block9[nr][LM93_PWM_CTL1] = SENSORS_LIMIT(val, 0, 255); + lm93_write_byte(client, LM93_REG_PWM_CTL(nr,LM93_PWM_CTL1), + data->block9[nr][LM93_PWM_CTL1]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_auto_channels, S_IWUSR | S_IRUGO, + show_pwm_auto_channels, store_pwm_auto_channels, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_channels, S_IWUSR | S_IRUGO, + show_pwm_auto_channels, store_pwm_auto_channels, 1); + +static ssize_t show_pwm_auto_spinup_min(struct device *dev, + struct device_attribute *attr,char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + u8 ctl3, ctl4; + + ctl3 = data->block9[nr][LM93_PWM_CTL3]; + ctl4 = data->block9[nr][LM93_PWM_CTL4]; + return sprintf(buf,"%d\n", + LM93_PWM_FROM_REG(ctl3 & 0x0f, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : LM93_PWM_MAP_HI_FREQ)); +} + +static ssize_t store_pwm_auto_spinup_min(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + u8 ctl3, ctl4; + + mutex_lock(&data->update_lock); + ctl3 = lm93_read_byte(client,LM93_REG_PWM_CTL(nr, LM93_PWM_CTL3)); + ctl4 = lm93_read_byte(client,LM93_REG_PWM_CTL(nr, LM93_PWM_CTL4)); + ctl3 = (ctl3 & 0xf0) | LM93_PWM_TO_REG(val, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : + LM93_PWM_MAP_HI_FREQ); + data->block9[nr][LM93_PWM_CTL3] = ctl3; + lm93_write_byte(client,LM93_REG_PWM_CTL(nr, LM93_PWM_CTL3), ctl3); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_auto_spinup_min, S_IWUSR | S_IRUGO, + show_pwm_auto_spinup_min, + store_pwm_auto_spinup_min, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_spinup_min, S_IWUSR | S_IRUGO, + show_pwm_auto_spinup_min, + store_pwm_auto_spinup_min, 1); + +static ssize_t show_pwm_auto_spinup_time(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",LM93_SPINUP_TIME_FROM_REG( + data->block9[nr][LM93_PWM_CTL3])); +} + +static ssize_t store_pwm_auto_spinup_time(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + u8 ctl3; + + mutex_lock(&data->update_lock); + ctl3 = lm93_read_byte(client,LM93_REG_PWM_CTL(nr, LM93_PWM_CTL3)); + ctl3 = (ctl3 & 0x1f) | (LM93_SPINUP_TIME_TO_REG(val) << 5 & 0xe0); + data->block9[nr][LM93_PWM_CTL3] = ctl3; + lm93_write_byte(client,LM93_REG_PWM_CTL(nr, LM93_PWM_CTL3), ctl3); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_auto_spinup_time, S_IWUSR | S_IRUGO, + show_pwm_auto_spinup_time, + store_pwm_auto_spinup_time, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_spinup_time, S_IWUSR | S_IRUGO, + show_pwm_auto_spinup_time, + store_pwm_auto_spinup_time, 1); + +static ssize_t show_pwm_auto_prochot_ramp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n", + LM93_RAMP_FROM_REG(data->pwm_ramp_ctl >> 4 & 0x0f)); +} + +static ssize_t store_pwm_auto_prochot_ramp(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + u8 ramp; + + mutex_lock(&data->update_lock); + ramp = lm93_read_byte(client, LM93_REG_PWM_RAMP_CTL); + ramp = (ramp & 0x0f) | (LM93_RAMP_TO_REG(val) << 4 & 0xf0); + lm93_write_byte(client, LM93_REG_PWM_RAMP_CTL, ramp); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR(pwm_auto_prochot_ramp, S_IRUGO | S_IWUSR, + show_pwm_auto_prochot_ramp, + store_pwm_auto_prochot_ramp); + +static ssize_t show_pwm_auto_vrdhot_ramp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n", + LM93_RAMP_FROM_REG(data->pwm_ramp_ctl & 0x0f)); +} + +static ssize_t store_pwm_auto_vrdhot_ramp(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + u8 ramp; + + mutex_lock(&data->update_lock); + ramp = lm93_read_byte(client, LM93_REG_PWM_RAMP_CTL); + ramp = (ramp & 0xf0) | (LM93_RAMP_TO_REG(val) & 0x0f); + lm93_write_byte(client, LM93_REG_PWM_RAMP_CTL, ramp); + mutex_unlock(&data->update_lock); + return 0; +} + +static DEVICE_ATTR(pwm_auto_vrdhot_ramp, S_IRUGO | S_IWUSR, + show_pwm_auto_vrdhot_ramp, + store_pwm_auto_vrdhot_ramp); + +static ssize_t show_vid(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",LM93_VID_FROM_REG(data->vid[nr])); +} + +static SENSOR_DEVICE_ATTR(vid1, S_IRUGO, show_vid, NULL, 0); +static SENSOR_DEVICE_ATTR(vid2, S_IRUGO, show_vid, NULL, 1); + +static ssize_t show_prochot(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",data->block4[nr].cur); +} + +static SENSOR_DEVICE_ATTR(prochot1, S_IRUGO, show_prochot, NULL, 0); +static SENSOR_DEVICE_ATTR(prochot2, S_IRUGO, show_prochot, NULL, 1); + +static ssize_t show_prochot_avg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",data->block4[nr].avg); +} + +static SENSOR_DEVICE_ATTR(prochot1_avg, S_IRUGO, show_prochot_avg, NULL, 0); +static SENSOR_DEVICE_ATTR(prochot2_avg, S_IRUGO, show_prochot_avg, NULL, 1); + +static ssize_t show_prochot_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",data->prochot_max[nr]); +} + +static ssize_t store_prochot_max(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->prochot_max[nr] = LM93_PROCHOT_TO_REG(val); + lm93_write_byte(client, LM93_REG_PROCHOT_MAX(nr), + data->prochot_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(prochot1_max, S_IWUSR | S_IRUGO, + show_prochot_max, store_prochot_max, 0); +static SENSOR_DEVICE_ATTR(prochot2_max, S_IWUSR | S_IRUGO, + show_prochot_max, store_prochot_max, 1); + +static const u8 prochot_override_mask[] = { 0x80, 0x40 }; + +static ssize_t show_prochot_override(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n", + (data->prochot_override & prochot_override_mask[nr]) ? 1 : 0); +} + +static ssize_t store_prochot_override(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + if (val) + data->prochot_override |= prochot_override_mask[nr]; + else + data->prochot_override &= (~prochot_override_mask[nr]); + lm93_write_byte(client, LM93_REG_PROCHOT_OVERRIDE, + data->prochot_override); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(prochot1_override, S_IWUSR | S_IRUGO, + show_prochot_override, store_prochot_override, 0); +static SENSOR_DEVICE_ATTR(prochot2_override, S_IWUSR | S_IRUGO, + show_prochot_override, store_prochot_override, 1); + +static ssize_t show_prochot_interval(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + u8 tmp; + if (nr==1) + tmp = (data->prochot_interval & 0xf0) >> 4; + else + tmp = data->prochot_interval & 0x0f; + return sprintf(buf,"%d\n",LM93_INTERVAL_FROM_REG(tmp)); +} + +static ssize_t store_prochot_interval(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + u8 tmp; + + mutex_lock(&data->update_lock); + tmp = lm93_read_byte(client, LM93_REG_PROCHOT_INTERVAL); + if (nr==1) + tmp = (tmp & 0x0f) | (LM93_INTERVAL_TO_REG(val) << 4); + else + tmp = (tmp & 0xf0) | LM93_INTERVAL_TO_REG(val); + data->prochot_interval = tmp; + lm93_write_byte(client, LM93_REG_PROCHOT_INTERVAL, tmp); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(prochot1_interval, S_IWUSR | S_IRUGO, + show_prochot_interval, store_prochot_interval, 0); +static SENSOR_DEVICE_ATTR(prochot2_interval, S_IWUSR | S_IRUGO, + show_prochot_interval, store_prochot_interval, 1); + +static ssize_t show_prochot_override_duty_cycle(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",data->prochot_override & 0x0f); +} + +static ssize_t store_prochot_override_duty_cycle(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->prochot_override = (data->prochot_override & 0xf0) | + SENSORS_LIMIT(val, 0, 15); + lm93_write_byte(client, LM93_REG_PROCHOT_OVERRIDE, + data->prochot_override); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR(prochot_override_duty_cycle, S_IRUGO | S_IWUSR, + show_prochot_override_duty_cycle, + store_prochot_override_duty_cycle); + +static ssize_t show_prochot_short(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",(data->config & 0x10) ? 1 : 0); +} + +static ssize_t store_prochot_short(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm93_data *data = i2c_get_clientdata(client); + u32 val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + if (val) + data->config |= 0x10; + else + data->config &= ~0x10; + lm93_write_byte(client, LM93_REG_CONFIG, data->config); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR(prochot_short, S_IRUGO | S_IWUSR, + show_prochot_short, store_prochot_short); + +static ssize_t show_vrdhot(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n", + data->block1.host_status_1 & (1 << (nr+4)) ? 1 : 0); +} + +static SENSOR_DEVICE_ATTR(vrdhot1, S_IRUGO, show_vrdhot, NULL, 0); +static SENSOR_DEVICE_ATTR(vrdhot2, S_IRUGO, show_vrdhot, NULL, 1); + +static ssize_t show_gpio(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",LM93_GPI_FROM_REG(data->gpi)); +} + +static DEVICE_ATTR(gpio, S_IRUGO, show_gpio, NULL); + +static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf,"%d\n",LM93_ALARMS_FROM_REG(data->block1)); +} + +static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL); + +static struct attribute *lm93_attrs[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in11_input.dev_attr.attr, + &sensor_dev_attr_in12_input.dev_attr.attr, + &sensor_dev_attr_in13_input.dev_attr.attr, + &sensor_dev_attr_in14_input.dev_attr.attr, + &sensor_dev_attr_in15_input.dev_attr.attr, + &sensor_dev_attr_in16_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in8_min.dev_attr.attr, + &sensor_dev_attr_in9_min.dev_attr.attr, + &sensor_dev_attr_in10_min.dev_attr.attr, + &sensor_dev_attr_in11_min.dev_attr.attr, + &sensor_dev_attr_in12_min.dev_attr.attr, + &sensor_dev_attr_in13_min.dev_attr.attr, + &sensor_dev_attr_in14_min.dev_attr.attr, + &sensor_dev_attr_in15_min.dev_attr.attr, + &sensor_dev_attr_in16_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in8_max.dev_attr.attr, + &sensor_dev_attr_in9_max.dev_attr.attr, + &sensor_dev_attr_in10_max.dev_attr.attr, + &sensor_dev_attr_in11_max.dev_attr.attr, + &sensor_dev_attr_in12_max.dev_attr.attr, + &sensor_dev_attr_in13_max.dev_attr.attr, + &sensor_dev_attr_in14_max.dev_attr.attr, + &sensor_dev_attr_in15_max.dev_attr.attr, + &sensor_dev_attr_in16_max.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp1_auto_base.dev_attr.attr, + &sensor_dev_attr_temp2_auto_base.dev_attr.attr, + &sensor_dev_attr_temp3_auto_base.dev_attr.attr, + &sensor_dev_attr_temp1_auto_boost.dev_attr.attr, + &sensor_dev_attr_temp2_auto_boost.dev_attr.attr, + &sensor_dev_attr_temp3_auto_boost.dev_attr.attr, + &sensor_dev_attr_temp1_auto_boost_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_auto_boost_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_auto_boost_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset1.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset2.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset3.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset4.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset5.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset6.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset7.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset8.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset9.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset10.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset11.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset12.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset1.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset2.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset3.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset4.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset5.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset6.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset7.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset8.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset9.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset10.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset11.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset12.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset1.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset2.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset3.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset4.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset5.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset6.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset7.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset8.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset9.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset10.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset11.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset12.dev_attr.attr, + &sensor_dev_attr_temp1_auto_pwm_min.dev_attr.attr, + &sensor_dev_attr_temp2_auto_pwm_min.dev_attr.attr, + &sensor_dev_attr_temp3_auto_pwm_min.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset_hyst.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan1_smart_tach.dev_attr.attr, + &sensor_dev_attr_fan2_smart_tach.dev_attr.attr, + &sensor_dev_attr_fan3_smart_tach.dev_attr.attr, + &sensor_dev_attr_fan4_smart_tach.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_freq.dev_attr.attr, + &sensor_dev_attr_pwm2_freq.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_channels.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_channels.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_spinup_min.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_spinup_min.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_spinup_time.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_spinup_time.dev_attr.attr, + &dev_attr_pwm_auto_prochot_ramp.attr, + &dev_attr_pwm_auto_vrdhot_ramp.attr, + &sensor_dev_attr_vid1.dev_attr.attr, + &sensor_dev_attr_vid2.dev_attr.attr, + &sensor_dev_attr_prochot1.dev_attr.attr, + &sensor_dev_attr_prochot2.dev_attr.attr, + &sensor_dev_attr_prochot1_avg.dev_attr.attr, + &sensor_dev_attr_prochot2_avg.dev_attr.attr, + &sensor_dev_attr_prochot1_max.dev_attr.attr, + &sensor_dev_attr_prochot2_max.dev_attr.attr, + &sensor_dev_attr_prochot1_override.dev_attr.attr, + &sensor_dev_attr_prochot2_override.dev_attr.attr, + &sensor_dev_attr_prochot1_interval.dev_attr.attr, + &sensor_dev_attr_prochot2_interval.dev_attr.attr, + &dev_attr_prochot_override_duty_cycle.attr, + &dev_attr_prochot_short.attr, + &sensor_dev_attr_vrdhot1.dev_attr.attr, + &sensor_dev_attr_vrdhot2.dev_attr.attr, + &dev_attr_gpio.attr, + &dev_attr_alarms.attr, + NULL +}; + +static struct attribute_group lm93_attr_grp = { + .attrs = lm93_attrs, +}; + +static void lm93_init_client(struct i2c_client *client) +{ + int i; + u8 reg; + + /* configure VID pin input thresholds */ + reg = lm93_read_byte(client, LM93_REG_GPI_VID_CTL); + lm93_write_byte(client, LM93_REG_GPI_VID_CTL, + reg | (vid_agtl ? 0x03 : 0x00)); + + if (init) { + /* enable #ALERT pin */ + reg = lm93_read_byte(client, LM93_REG_CONFIG); + lm93_write_byte(client, LM93_REG_CONFIG, reg | 0x08); + + /* enable ASF mode for BMC status registers */ + reg = lm93_read_byte(client, LM93_REG_STATUS_CONTROL); + lm93_write_byte(client, LM93_REG_STATUS_CONTROL, reg | 0x02); + + /* set sleep state to S0 */ + lm93_write_byte(client, LM93_REG_SLEEP_CONTROL, 0); + + /* unmask #VRDHOT and dynamic VCCP (if nec) error events */ + reg = lm93_read_byte(client, LM93_REG_MISC_ERR_MASK); + reg &= ~0x03; + reg &= ~(vccp_limit_type[0] ? 0x10 : 0); + reg &= ~(vccp_limit_type[1] ? 0x20 : 0); + lm93_write_byte(client, LM93_REG_MISC_ERR_MASK, reg); + } + + /* start monitoring */ + reg = lm93_read_byte(client, LM93_REG_CONFIG); + lm93_write_byte(client, LM93_REG_CONFIG, reg | 0x01); + + /* spin until ready */ + for (i=0; i<20; i++) { + msleep(10); + if ((lm93_read_byte(client, LM93_REG_CONFIG) & 0x80) == 0x80) + return; + } + + dev_warn(&client->dev,"timed out waiting for sensor " + "chip to signal ready!\n"); +} + +static int lm93_detect(struct i2c_adapter *adapter, int address, int kind) +{ + struct lm93_data *data; + struct i2c_client *client; + + int err = -ENODEV, func; + void (*update)(struct lm93_data *, struct i2c_client *); + + /* choose update routine based on bus capabilities */ + func = i2c_get_functionality(adapter); + if ( ((LM93_SMBUS_FUNC_FULL & func) == LM93_SMBUS_FUNC_FULL) && + (!disable_block) ) { + dev_dbg(&adapter->dev,"using SMBus block data transactions\n"); + update = lm93_update_client_full; + } else if ((LM93_SMBUS_FUNC_MIN & func) == LM93_SMBUS_FUNC_MIN) { + dev_dbg(&adapter->dev,"disabled SMBus block data " + "transactions\n"); + update = lm93_update_client_min; + } else { + dev_dbg(&adapter->dev,"detect failed, " + "smbus byte and/or word data not supported!\n"); + goto err_out; + } + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access lm78_{read,write}_value. */ + + if ( !(data = kzalloc(sizeof(struct lm93_data), GFP_KERNEL))) { + dev_dbg(&adapter->dev,"out of memory!\n"); + err = -ENOMEM; + goto err_out; + } + + client = &data->client; + i2c_set_clientdata(client, data); + client->addr = address; + client->adapter = adapter; + client->driver = &lm93_driver; + + /* detection */ + if (kind < 0) { + int mfr = lm93_read_byte(client, LM93_REG_MFR_ID); + + if (mfr != 0x01) { + dev_dbg(&adapter->dev,"detect failed, " + "bad manufacturer id 0x%02x!\n", mfr); + goto err_free; + } + } + + if (kind <= 0) { + int ver = lm93_read_byte(client, LM93_REG_VER); + + if ((ver == LM93_MFR_ID) || (ver == LM93_MFR_ID_PROTOTYPE)) { + kind = lm93; + } else { + dev_dbg(&adapter->dev,"detect failed, " + "bad version id 0x%02x!\n", ver); + if (kind == 0) + dev_dbg(&adapter->dev, + "(ignored 'force' parameter)\n"); + goto err_free; + } + } + + /* fill in remaining client fields */ + strlcpy(client->name, "lm93", I2C_NAME_SIZE); + dev_dbg(&adapter->dev,"loading %s at %d,0x%02x\n", + client->name, i2c_adapter_id(client->adapter), + client->addr); + + /* housekeeping */ + data->valid = 0; + data->update = update; + mutex_init(&data->update_lock); + + /* tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(client))) + goto err_free; + + /* initialize the chip */ + lm93_init_client(client); + + err = sysfs_create_group(&client->dev.kobj, &lm93_attr_grp); + if (err) + goto err_detach; + + /* Register hwmon driver class */ + data->class_dev = hwmon_device_register(&client->dev); + if ( !IS_ERR(data->class_dev)) + return 0; + + err = PTR_ERR(data->class_dev); + dev_err(&client->dev, "error registering hwmon device.\n"); + sysfs_remove_group(&client->dev.kobj, &lm93_attr_grp); +err_detach: + i2c_detach_client(client); +err_free: + kfree(data); +err_out: + return err; +} + +/* This function is called when: + * lm93_driver is inserted (when this module is loaded), for each + available adapter + * when a new adapter is inserted (and lm93_driver is still present) */ +static int lm93_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_probe(adapter, &addr_data, lm93_detect); +} + +static int lm93_detach_client(struct i2c_client *client) +{ + struct lm93_data *data = i2c_get_clientdata(client); + int err = 0; + + hwmon_device_unregister(data->class_dev); + sysfs_remove_group(&client->dev.kobj, &lm93_attr_grp); + + err = i2c_detach_client(client); + if (!err) + kfree(data); + return err; +} + +static struct i2c_driver lm93_driver = { + .driver = { + .name = "lm93", + }, + .attach_adapter = lm93_attach_adapter, + .detach_client = lm93_detach_client, +}; + +static int __init lm93_init(void) +{ + return i2c_add_driver(&lm93_driver); +} + +static void __exit lm93_exit(void) +{ + i2c_del_driver(&lm93_driver); +} + +MODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>, " + "Hans J. Koch <hjk@linutronix.de"); +MODULE_DESCRIPTION("LM93 driver"); +MODULE_LICENSE("GPL"); + +module_init(lm93_init); +module_exit(lm93_exit); diff --git a/drivers/hwmon/pc87360.c b/drivers/hwmon/pc87360.c index c8a21be..cb72526c 100644 --- a/drivers/hwmon/pc87360.c +++ b/drivers/hwmon/pc87360.c @@ -1,7 +1,7 @@ /* * pc87360.c - Part of lm_sensors, Linux kernel modules * for hardware monitoring - * Copyright (C) 2004 Jean Delvare <khali@linux-fr.org> + * Copyright (C) 2004, 2007 Jean Delvare <khali@linux-fr.org> * * Copied from smsc47m1.c: * Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com> @@ -37,8 +37,7 @@ #include <linux/init.h> #include <linux/slab.h> #include <linux/jiffies.h> -#include <linux/i2c.h> -#include <linux/i2c-isa.h> +#include <linux/platform_device.h> #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> #include <linux/hwmon-vid.h> @@ -47,12 +46,10 @@ #include <asm/io.h> static u8 devid; -static unsigned short address; +static struct platform_device *pdev; static unsigned short extra_isa[3]; static u8 confreg[4]; -enum chips { any_chip, pc87360, pc87363, pc87364, pc87365, pc87366 }; - static int init = 1; module_param(init, int, 0); MODULE_PARM_DESC(init, @@ -178,11 +175,11 @@ static inline u8 PWM_TO_REG(int val, int inv) ((val) + 500) / 1000) /* - * Client data (each client gets its own) + * Device data */ struct pc87360_data { - struct i2c_client client; + const char *name; struct class_device *class_dev; struct mutex lock; struct mutex update_lock; @@ -222,27 +219,28 @@ struct pc87360_data { * Functions declaration */ -static int pc87360_detect(struct i2c_adapter *adapter); -static int pc87360_detach_client(struct i2c_client *client); +static int pc87360_probe(struct platform_device *pdev); +static int pc87360_remove(struct platform_device *pdev); static int pc87360_read_value(struct pc87360_data *data, u8 ldi, u8 bank, u8 reg); static void pc87360_write_value(struct pc87360_data *data, u8 ldi, u8 bank, u8 reg, u8 value); -static void pc87360_init_client(struct i2c_client *client, int use_thermistors); +static void pc87360_init_device(struct platform_device *pdev, + int use_thermistors); static struct pc87360_data *pc87360_update_device(struct device *dev); /* - * Driver data (common to all clients) + * Driver data */ -static struct i2c_driver pc87360_driver = { +static struct platform_driver pc87360_driver = { .driver = { .owner = THIS_MODULE, .name = "pc87360", }, - .attach_adapter = pc87360_detect, - .detach_client = pc87360_detach_client, + .probe = pc87360_probe, + .remove = __devexit_p(pc87360_remove), }; /* @@ -281,8 +279,7 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute *devattr, size_t count) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); long fan_min = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); @@ -347,8 +344,7 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr, con size_t count) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); @@ -410,8 +406,7 @@ static ssize_t set_in_min(struct device *dev, struct device_attribute *devattr, size_t count) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); @@ -425,8 +420,7 @@ static ssize_t set_in_max(struct device *dev, struct device_attribute *devattr, size_t count) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); @@ -511,8 +505,7 @@ static ssize_t show_vrm(struct device *dev, struct device_attribute *attr, char } static ssize_t set_vrm(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); data->vrm = simple_strtoul(buf, NULL, 10); return count; } @@ -584,8 +577,7 @@ static ssize_t set_therm_min(struct device *dev, struct device_attribute *devatt size_t count) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); @@ -599,8 +591,7 @@ static ssize_t set_therm_max(struct device *dev, struct device_attribute *devatt size_t count) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); @@ -614,8 +605,7 @@ static ssize_t set_therm_crit(struct device *dev, struct device_attribute *devat size_t count) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); @@ -715,8 +705,7 @@ static ssize_t set_temp_min(struct device *dev, struct device_attribute *devattr size_t count) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); @@ -730,8 +719,7 @@ static ssize_t set_temp_max(struct device *dev, struct device_attribute *devattr size_t count) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); @@ -745,8 +733,7 @@ static ssize_t set_temp_crit(struct device *dev, struct device_attribute *devatt size_t count) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); @@ -818,6 +805,14 @@ static const struct attribute_group pc8736x_temp_group = { .attrs = pc8736x_temp_attr_array, }; +static ssize_t show_name(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87360_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + /* * Device detection, registration and update */ @@ -912,28 +907,18 @@ static int __init pc87360_find(int sioaddr, u8 *devid, unsigned short *addresses return 0; } -static int pc87360_detect(struct i2c_adapter *adapter) +static int __devinit pc87360_probe(struct platform_device *pdev) { int i; - struct i2c_client *client; struct pc87360_data *data; int err = 0; const char *name = "pc87360"; int use_thermistors = 0; - struct device *dev; + struct device *dev = &pdev->dev; if (!(data = kzalloc(sizeof(struct pc87360_data), GFP_KERNEL))) return -ENOMEM; - client = &data->client; - dev = &client->dev; - i2c_set_clientdata(client, data); - client->addr = address; - mutex_init(&data->lock); - client->adapter = adapter; - client->driver = &pc87360_driver; - client->flags = 0; - data->fannr = 2; data->innr = 0; data->tempnr = 0; @@ -960,15 +945,17 @@ static int pc87360_detect(struct i2c_adapter *adapter) break; } - strlcpy(client->name, name, sizeof(client->name)); + data->name = name; data->valid = 0; + mutex_init(&data->lock); mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); for (i = 0; i < 3; i++) { if (((data->address[i] = extra_isa[i])) && !request_region(extra_isa[i], PC87360_EXTENT, pc87360_driver.driver.name)) { - dev_err(&client->dev, "Region 0x%x-0x%x already " + dev_err(dev, "Region 0x%x-0x%x already " "in use!\n", extra_isa[i], extra_isa[i]+PC87360_EXTENT-1); for (i--; i >= 0; i--) @@ -982,9 +969,6 @@ static int pc87360_detect(struct i2c_adapter *adapter) if (data->fannr) data->fan_conf = confreg[0] | (confreg[1] << 8); - if ((err = i2c_attach_client(client))) - goto ERROR2; - /* Use the correct reference voltage Unless both the VLM and the TMS logical devices agree to use an external Vref, the internal one is used. */ @@ -996,7 +980,7 @@ static int pc87360_detect(struct i2c_adapter *adapter) PC87365_REG_TEMP_CONFIG); } data->in_vref = (i&0x02) ? 3025 : 2966; - dev_dbg(&client->dev, "Using %s reference voltage\n", + dev_dbg(dev, "Using %s reference voltage\n", (i&0x02) ? "external" : "internal"); data->vid_conf = confreg[3]; @@ -1015,18 +999,18 @@ static int pc87360_detect(struct i2c_adapter *adapter) if (devid == 0xe9 && data->address[1]) /* PC87366 */ use_thermistors = confreg[2] & 0x40; - pc87360_init_client(client, use_thermistors); + pc87360_init_device(pdev, use_thermistors); } /* Register all-or-nothing sysfs groups */ if (data->innr && - (err = sysfs_create_group(&client->dev.kobj, + (err = sysfs_create_group(&dev->kobj, &pc8736x_vin_group))) goto ERROR3; if (data->innr == 14 && - (err = sysfs_create_group(&client->dev.kobj, + (err = sysfs_create_group(&dev->kobj, &pc8736x_therm_group))) goto ERROR3; @@ -1067,7 +1051,10 @@ static int pc87360_detect(struct i2c_adapter *adapter) goto ERROR3; } - data->class_dev = hwmon_device_register(&client->dev); + if ((err = device_create_file(dev, &dev_attr_name))) + goto ERROR3; + + data->class_dev = hwmon_device_register(dev); if (IS_ERR(data->class_dev)) { err = PTR_ERR(data->class_dev); goto ERROR3; @@ -1075,14 +1062,12 @@ static int pc87360_detect(struct i2c_adapter *adapter) return 0; ERROR3: + device_remove_file(dev, &dev_attr_name); /* can still remove groups whose members were added individually */ - sysfs_remove_group(&client->dev.kobj, &pc8736x_temp_group); - sysfs_remove_group(&client->dev.kobj, &pc8736x_fan_group); - sysfs_remove_group(&client->dev.kobj, &pc8736x_therm_group); - sysfs_remove_group(&client->dev.kobj, &pc8736x_vin_group); - - i2c_detach_client(client); -ERROR2: + sysfs_remove_group(&dev->kobj, &pc8736x_temp_group); + sysfs_remove_group(&dev->kobj, &pc8736x_fan_group); + sysfs_remove_group(&dev->kobj, &pc8736x_therm_group); + sysfs_remove_group(&dev->kobj, &pc8736x_vin_group); for (i = 0; i < 3; i++) { if (data->address[i]) { release_region(data->address[i], PC87360_EXTENT); @@ -1093,20 +1078,18 @@ ERROR1: return err; } -static int pc87360_detach_client(struct i2c_client *client) +static int __devexit pc87360_remove(struct platform_device *pdev) { - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = platform_get_drvdata(pdev); int i; hwmon_device_unregister(data->class_dev); - sysfs_remove_group(&client->dev.kobj, &pc8736x_temp_group); - sysfs_remove_group(&client->dev.kobj, &pc8736x_fan_group); - sysfs_remove_group(&client->dev.kobj, &pc8736x_therm_group); - sysfs_remove_group(&client->dev.kobj, &pc8736x_vin_group); - - if ((i = i2c_detach_client(client))) - return i; + device_remove_file(&pdev->dev, &dev_attr_name); + sysfs_remove_group(&pdev->dev.kobj, &pc8736x_temp_group); + sysfs_remove_group(&pdev->dev.kobj, &pc8736x_fan_group); + sysfs_remove_group(&pdev->dev.kobj, &pc8736x_therm_group); + sysfs_remove_group(&pdev->dev.kobj, &pc8736x_vin_group); for (i = 0; i < 3; i++) { if (data->address[i]) { @@ -1144,9 +1127,10 @@ static void pc87360_write_value(struct pc87360_data *data, u8 ldi, u8 bank, mutex_unlock(&(data->lock)); } -static void pc87360_init_client(struct i2c_client *client, int use_thermistors) +static void pc87360_init_device(struct platform_device *pdev, + int use_thermistors) { - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = platform_get_drvdata(pdev); int i, nr; const u8 init_in[14] = { 2, 2, 2, 2, 2, 2, 2, 1, 1, 3, 1, 2, 2, 2 }; const u8 init_temp[3] = { 2, 2, 1 }; @@ -1155,7 +1139,7 @@ static void pc87360_init_client(struct i2c_client *client, int use_thermistors) if (init >= 2 && data->innr) { reg = pc87360_read_value(data, LD_IN, NO_BANK, PC87365_REG_IN_CONVRATE); - dev_info(&client->dev, "VLM conversion set to " + dev_info(&pdev->dev, "VLM conversion set to " "1s period, 160us delay\n"); pc87360_write_value(data, LD_IN, NO_BANK, PC87365_REG_IN_CONVRATE, @@ -1169,7 +1153,7 @@ static void pc87360_init_client(struct i2c_client *client, int use_thermistors) reg = pc87360_read_value(data, LD_IN, i, PC87365_REG_IN_STATUS); if (!(reg & 0x01)) { - dev_dbg(&client->dev, "Forcibly " + dev_dbg(&pdev->dev, "Forcibly " "enabling in%d\n", i); pc87360_write_value(data, LD_IN, i, PC87365_REG_IN_STATUS, @@ -1193,7 +1177,7 @@ static void pc87360_init_client(struct i2c_client *client, int use_thermistors) reg = pc87360_read_value(data, LD_TEMP, i, PC87365_REG_TEMP_STATUS); if (!(reg & 0x01)) { - dev_dbg(&client->dev, "Forcibly " + dev_dbg(&pdev->dev, "Forcibly " "enabling temp%d\n", i+1); pc87360_write_value(data, LD_TEMP, i, PC87365_REG_TEMP_STATUS, @@ -1210,7 +1194,7 @@ static void pc87360_init_client(struct i2c_client *client, int use_thermistors) reg = pc87360_read_value(data, LD_TEMP, (i-11)/2, PC87365_REG_TEMP_STATUS); if (reg & 0x01) { - dev_dbg(&client->dev, "Skipping " + dev_dbg(&pdev->dev, "Skipping " "temp%d, pin already in use " "by temp%d\n", i-7, (i-11)/2); continue; @@ -1220,7 +1204,7 @@ static void pc87360_init_client(struct i2c_client *client, int use_thermistors) reg = pc87360_read_value(data, LD_IN, i, PC87365_REG_IN_STATUS); if (!(reg & 0x01)) { - dev_dbg(&client->dev, "Forcibly " + dev_dbg(&pdev->dev, "Forcibly " "enabling temp%d\n", i-7); pc87360_write_value(data, LD_IN, i, PC87365_REG_TEMP_STATUS, @@ -1234,7 +1218,7 @@ static void pc87360_init_client(struct i2c_client *client, int use_thermistors) reg = pc87360_read_value(data, LD_IN, NO_BANK, PC87365_REG_IN_CONFIG); if (reg & 0x01) { - dev_dbg(&client->dev, "Forcibly " + dev_dbg(&pdev->dev, "Forcibly " "enabling monitoring (VLM)\n"); pc87360_write_value(data, LD_IN, NO_BANK, PC87365_REG_IN_CONFIG, @@ -1246,7 +1230,7 @@ static void pc87360_init_client(struct i2c_client *client, int use_thermistors) reg = pc87360_read_value(data, LD_TEMP, NO_BANK, PC87365_REG_TEMP_CONFIG); if (reg & 0x01) { - dev_dbg(&client->dev, "Forcibly enabling " + dev_dbg(&pdev->dev, "Forcibly enabling " "monitoring (TMS)\n"); pc87360_write_value(data, LD_TEMP, NO_BANK, PC87365_REG_TEMP_CONFIG, @@ -1268,9 +1252,9 @@ static void pc87360_init_client(struct i2c_client *client, int use_thermistors) } } -static void pc87360_autodiv(struct i2c_client *client, int nr) +static void pc87360_autodiv(struct device *dev, int nr) { - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); u8 old_min = data->fan_min[nr]; /* Increase clock divider if needed and possible */ @@ -1280,7 +1264,7 @@ static void pc87360_autodiv(struct i2c_client *client, int nr) data->fan_status[nr] += 0x20; data->fan_min[nr] >>= 1; data->fan[nr] >>= 1; - dev_dbg(&client->dev, "Increasing " + dev_dbg(dev, "Increasing " "clock divider to %d for fan %d\n", FAN_DIV_FROM_REG(data->fan_status[nr]), nr+1); } @@ -1292,7 +1276,7 @@ static void pc87360_autodiv(struct i2c_client *client, int nr) data->fan_status[nr] -= 0x20; data->fan_min[nr] <<= 1; data->fan[nr] <<= 1; - dev_dbg(&client->dev, "Decreasing " + dev_dbg(dev, "Decreasing " "clock divider to %d for fan %d\n", FAN_DIV_FROM_REG(data->fan_status[nr]), nr+1); @@ -1309,14 +1293,13 @@ static void pc87360_autodiv(struct i2c_client *client, int nr) static struct pc87360_data *pc87360_update_device(struct device *dev) { - struct i2c_client *client = to_i2c_client(dev); - struct pc87360_data *data = i2c_get_clientdata(client); + struct pc87360_data *data = dev_get_drvdata(dev); u8 i; mutex_lock(&data->update_lock); if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) { - dev_dbg(&client->dev, "Data update\n"); + dev_dbg(dev, "Data update\n"); /* Fans */ for (i = 0; i < data->fannr; i++) { @@ -1330,7 +1313,7 @@ static struct pc87360_data *pc87360_update_device(struct device *dev) LD_FAN, NO_BANK, PC87360_REG_FAN_MIN(i)); /* Change clock divider if needed */ - pc87360_autodiv(client, i); + pc87360_autodiv(dev, i); /* Clear bits and write new divider */ pc87360_write_value(data, LD_FAN, NO_BANK, PC87360_REG_FAN_STATUS(i), @@ -1418,9 +1401,53 @@ static struct pc87360_data *pc87360_update_device(struct device *dev) return data; } +static int __init pc87360_device_add(unsigned short address) +{ + struct resource res = { + .name = "pc87360", + .flags = IORESOURCE_IO, + }; + int err, i; + + pdev = platform_device_alloc("pc87360", address); + if (!pdev) { + err = -ENOMEM; + printk(KERN_ERR "pc87360: Device allocation failed\n"); + goto exit; + } + + for (i = 0; i < 3; i++) { + if (!extra_isa[i]) + continue; + res.start = extra_isa[i]; + res.end = extra_isa[i] + PC87360_EXTENT - 1; + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + printk(KERN_ERR "pc87360: Device resource[%d] " + "addition failed (%d)\n", i, err); + goto exit_device_put; + } + } + + err = platform_device_add(pdev); + if (err) { + printk(KERN_ERR "pc87360: Device addition failed (%d)\n", + err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + static int __init pc87360_init(void) { - int i; + int err, i; + unsigned short address = 0; if (pc87360_find(0x2e, &devid, extra_isa) && pc87360_find(0x4e, &devid, extra_isa)) { @@ -1443,12 +1470,27 @@ static int __init pc87360_init(void) return -ENODEV; } - return i2c_isa_add_driver(&pc87360_driver); + err = platform_driver_register(&pc87360_driver); + if (err) + goto exit; + + /* Sets global pdev as a side effect */ + err = pc87360_device_add(address); + if (err) + goto exit_driver; + + return 0; + + exit_driver: + platform_driver_unregister(&pc87360_driver); + exit: + return err; } static void __exit pc87360_exit(void) { - i2c_isa_del_driver(&pc87360_driver); + platform_device_unregister(pdev); + platform_driver_unregister(&pc87360_driver); } diff --git a/drivers/hwmon/pc87427.c b/drivers/hwmon/pc87427.c index 29354fa..2915bc4 100644 --- a/drivers/hwmon/pc87427.c +++ b/drivers/hwmon/pc87427.c @@ -484,7 +484,6 @@ static int __devexit pc87427_remove(struct platform_device *pdev) struct resource *res; int i; - platform_set_drvdata(pdev, NULL); hwmon_device_unregister(data->class_dev); device_remove_file(&pdev->dev, &dev_attr_name); for (i = 0; i < 8; i++) { @@ -492,6 +491,7 @@ static int __devexit pc87427_remove(struct platform_device *pdev) continue; sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]); } + platform_set_drvdata(pdev, NULL); kfree(data); res = platform_get_resource(pdev, IORESOURCE_IO, 0); diff --git a/drivers/hwmon/sis5595.c b/drivers/hwmon/sis5595.c index 3f40026..83321b2 100644 --- a/drivers/hwmon/sis5595.c +++ b/drivers/hwmon/sis5595.c @@ -54,9 +54,9 @@ #include <linux/slab.h> #include <linux/ioport.h> #include <linux/pci.h> -#include <linux/i2c.h> -#include <linux/i2c-isa.h> +#include <linux/platform_device.h> #include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> #include <linux/err.h> #include <linux/init.h> #include <linux/jiffies.h> @@ -72,17 +72,13 @@ module_param(force_addr, ushort, 0); MODULE_PARM_DESC(force_addr, "Initialize the base address of the sensors"); -/* Device address - Note that we can't determine the ISA address until we have initialized - our module */ -static unsigned short address; +static struct platform_device *pdev; /* Many SIS5595 constants specified below */ /* Length of ISA address segment */ #define SIS5595_EXTENT 8 /* PCI Config Registers */ -#define SIS5595_REVISION_REG 0x08 #define SIS5595_BASE_REG 0x68 #define SIS5595_PIN_REG 0x7A #define SIS5595_ENABLE_REG 0x7B @@ -165,7 +161,8 @@ static inline u8 DIV_TO_REG(int val) /* For each registered chip, we need to keep some data in memory. The structure is dynamically allocated. */ struct sis5595_data { - struct i2c_client client; + unsigned short addr; + const char *name; struct class_device *class_dev; struct mutex lock; @@ -189,102 +186,88 @@ struct sis5595_data { static struct pci_dev *s_bridge; /* pointer to the (only) sis5595 */ -static int sis5595_detect(struct i2c_adapter *adapter); -static int sis5595_detach_client(struct i2c_client *client); +static int sis5595_probe(struct platform_device *pdev); +static int sis5595_remove(struct platform_device *pdev); -static int sis5595_read_value(struct i2c_client *client, u8 reg); -static int sis5595_write_value(struct i2c_client *client, u8 reg, u8 value); +static int sis5595_read_value(struct sis5595_data *data, u8 reg); +static void sis5595_write_value(struct sis5595_data *data, u8 reg, u8 value); static struct sis5595_data *sis5595_update_device(struct device *dev); -static void sis5595_init_client(struct i2c_client *client); +static void sis5595_init_device(struct sis5595_data *data); -static struct i2c_driver sis5595_driver = { +static struct platform_driver sis5595_driver = { .driver = { .owner = THIS_MODULE, .name = "sis5595", }, - .attach_adapter = sis5595_detect, - .detach_client = sis5595_detach_client, + .probe = sis5595_probe, + .remove = __devexit_p(sis5595_remove), }; /* 4 Voltages */ -static ssize_t show_in(struct device *dev, char *buf, int nr) +static ssize_t show_in(struct device *dev, struct device_attribute *da, + char *buf) { struct sis5595_data *data = sis5595_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%d\n", IN_FROM_REG(data->in[nr])); } -static ssize_t show_in_min(struct device *dev, char *buf, int nr) +static ssize_t show_in_min(struct device *dev, struct device_attribute *da, + char *buf) { struct sis5595_data *data = sis5595_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%d\n", IN_FROM_REG(data->in_min[nr])); } -static ssize_t show_in_max(struct device *dev, char *buf, int nr) +static ssize_t show_in_max(struct device *dev, struct device_attribute *da, + char *buf) { struct sis5595_data *data = sis5595_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%d\n", IN_FROM_REG(data->in_max[nr])); } -static ssize_t set_in_min(struct device *dev, const char *buf, - size_t count, int nr) +static ssize_t set_in_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct sis5595_data *data = i2c_get_clientdata(client); + struct sis5595_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); data->in_min[nr] = IN_TO_REG(val); - sis5595_write_value(client, SIS5595_REG_IN_MIN(nr), data->in_min[nr]); + sis5595_write_value(data, SIS5595_REG_IN_MIN(nr), data->in_min[nr]); mutex_unlock(&data->update_lock); return count; } -static ssize_t set_in_max(struct device *dev, const char *buf, - size_t count, int nr) +static ssize_t set_in_max(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct sis5595_data *data = i2c_get_clientdata(client); + struct sis5595_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); data->in_max[nr] = IN_TO_REG(val); - sis5595_write_value(client, SIS5595_REG_IN_MAX(nr), data->in_max[nr]); + sis5595_write_value(data, SIS5595_REG_IN_MAX(nr), data->in_max[nr]); mutex_unlock(&data->update_lock); return count; } #define show_in_offset(offset) \ -static ssize_t \ - show_in##offset (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_in(dev, buf, offset); \ -} \ -static DEVICE_ATTR(in##offset##_input, S_IRUGO, \ - show_in##offset, NULL); \ -static ssize_t \ - show_in##offset##_min (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_in_min(dev, buf, offset); \ -} \ -static ssize_t \ - show_in##offset##_max (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_in_max(dev, buf, offset); \ -} \ -static ssize_t set_in##offset##_min (struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - return set_in_min(dev, buf, count, offset); \ -} \ -static ssize_t set_in##offset##_max (struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - return set_in_max(dev, buf, count, offset); \ -} \ -static DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ - show_in##offset##_min, set_in##offset##_min); \ -static DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ - show_in##offset##_max, set_in##offset##_max); +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ + show_in_min, set_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ + show_in_max, set_in_max, offset); show_in_offset(0); show_in_offset(1); @@ -307,13 +290,12 @@ static ssize_t show_temp_over(struct device *dev, struct device_attribute *attr, static ssize_t set_temp_over(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct sis5595_data *data = i2c_get_clientdata(client); + struct sis5595_data *data = dev_get_drvdata(dev); long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); data->temp_over = TEMP_TO_REG(val); - sis5595_write_value(client, SIS5595_REG_TEMP_OVER, data->temp_over); + sis5595_write_value(data, SIS5595_REG_TEMP_OVER, data->temp_over); mutex_unlock(&data->update_lock); return count; } @@ -326,13 +308,12 @@ static ssize_t show_temp_hyst(struct device *dev, struct device_attribute *attr, static ssize_t set_temp_hyst(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct sis5595_data *data = i2c_get_clientdata(client); + struct sis5595_data *data = dev_get_drvdata(dev); long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); data->temp_hyst = TEMP_TO_REG(val); - sis5595_write_value(client, SIS5595_REG_TEMP_HYST, data->temp_hyst); + sis5595_write_value(data, SIS5595_REG_TEMP_HYST, data->temp_hyst); mutex_unlock(&data->update_lock); return count; } @@ -344,37 +325,47 @@ static DEVICE_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR, show_temp_hyst, set_temp_hyst); /* 2 Fans */ -static ssize_t show_fan(struct device *dev, char *buf, int nr) +static ssize_t show_fan(struct device *dev, struct device_attribute *da, + char *buf) { struct sis5595_data *data = sis5595_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr], DIV_FROM_REG(data->fan_div[nr])) ); } -static ssize_t show_fan_min(struct device *dev, char *buf, int nr) +static ssize_t show_fan_min(struct device *dev, struct device_attribute *da, + char *buf) { struct sis5595_data *data = sis5595_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf,"%d\n", FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr])) ); } -static ssize_t set_fan_min(struct device *dev, const char *buf, - size_t count, int nr) +static ssize_t set_fan_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct sis5595_data *data = i2c_get_clientdata(client); + struct sis5595_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); - sis5595_write_value(client, SIS5595_REG_FAN_MIN(nr), data->fan_min[nr]); + sis5595_write_value(data, SIS5595_REG_FAN_MIN(nr), data->fan_min[nr]); mutex_unlock(&data->update_lock); return count; } -static ssize_t show_fan_div(struct device *dev, char *buf, int nr) +static ssize_t show_fan_div(struct device *dev, struct device_attribute *da, + char *buf) { struct sis5595_data *data = sis5595_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr]) ); } @@ -382,11 +373,12 @@ static ssize_t show_fan_div(struct device *dev, char *buf, int nr) determined in part by the fan divisor. This follows the principle of least surprise; the user doesn't expect the fan minimum to change just because the divisor changed. */ -static ssize_t set_fan_div(struct device *dev, const char *buf, - size_t count, int nr) +static ssize_t set_fan_div(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct sis5595_data *data = i2c_get_clientdata(client); + struct sis5595_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; unsigned long min; unsigned long val = simple_strtoul(buf, NULL, 10); int reg; @@ -394,7 +386,7 @@ static ssize_t set_fan_div(struct device *dev, const char *buf, mutex_lock(&data->update_lock); min = FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr])); - reg = sis5595_read_value(client, SIS5595_REG_FANDIV); + reg = sis5595_read_value(data, SIS5595_REG_FANDIV); switch (val) { case 1: data->fan_div[nr] = 0; break; @@ -402,7 +394,7 @@ static ssize_t set_fan_div(struct device *dev, const char *buf, case 4: data->fan_div[nr] = 2; break; case 8: data->fan_div[nr] = 3; break; default: - dev_err(&client->dev, "fan_div value %ld not " + dev_err(dev, "fan_div value %ld not " "supported. Choose one of 1, 2, 4 or 8!\n", val); mutex_unlock(&data->update_lock); return -EINVAL; @@ -416,55 +408,25 @@ static ssize_t set_fan_div(struct device *dev, const char *buf, reg = (reg & 0x3f) | (data->fan_div[nr] << 6); break; } - sis5595_write_value(client, SIS5595_REG_FANDIV, reg); + sis5595_write_value(data, SIS5595_REG_FANDIV, reg); data->fan_min[nr] = FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); - sis5595_write_value(client, SIS5595_REG_FAN_MIN(nr), data->fan_min[nr]); + sis5595_write_value(data, SIS5595_REG_FAN_MIN(nr), data->fan_min[nr]); mutex_unlock(&data->update_lock); return count; } #define show_fan_offset(offset) \ -static ssize_t show_fan_##offset (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_fan(dev, buf, offset - 1); \ -} \ -static ssize_t show_fan_##offset##_min (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_fan_min(dev, buf, offset - 1); \ -} \ -static ssize_t show_fan_##offset##_div (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_fan_div(dev, buf, offset - 1); \ -} \ -static ssize_t set_fan_##offset##_min (struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - return set_fan_min(dev, buf, count, offset - 1); \ -} \ -static DEVICE_ATTR(fan##offset##_input, S_IRUGO, show_fan_##offset, NULL);\ -static DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ - show_fan_##offset##_min, set_fan_##offset##_min); +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ + show_fan, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, set_fan_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \ + show_fan_div, set_fan_div, offset - 1); show_fan_offset(1); show_fan_offset(2); -static ssize_t set_fan_1_div(struct device *dev, struct device_attribute *attr, const char *buf, - size_t count) -{ - return set_fan_div(dev, buf, count, 0) ; -} - -static ssize_t set_fan_2_div(struct device *dev, struct device_attribute *attr, const char *buf, - size_t count) -{ - return set_fan_div(dev, buf, count, 1) ; -} -static DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, - show_fan_1_div, set_fan_1_div); -static DEVICE_ATTR(fan2_div, S_IRUGO | S_IWUSR, - show_fan_2_div, set_fan_2_div); - /* Alarms */ static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, char *buf) { @@ -473,28 +435,37 @@ static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, ch } static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL); +static ssize_t show_name(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sis5595_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + static struct attribute *sis5595_attributes[] = { - &dev_attr_in0_input.attr, - &dev_attr_in0_min.attr, - &dev_attr_in0_max.attr, - &dev_attr_in1_input.attr, - &dev_attr_in1_min.attr, - &dev_attr_in1_max.attr, - &dev_attr_in2_input.attr, - &dev_attr_in2_min.attr, - &dev_attr_in2_max.attr, - &dev_attr_in3_input.attr, - &dev_attr_in3_min.attr, - &dev_attr_in3_max.attr, - - &dev_attr_fan1_input.attr, - &dev_attr_fan1_min.attr, - &dev_attr_fan1_div.attr, - &dev_attr_fan2_input.attr, - &dev_attr_fan2_min.attr, - &dev_attr_fan2_div.attr, + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, &dev_attr_alarms.attr, + &dev_attr_name.attr, NULL }; @@ -503,9 +474,9 @@ static const struct attribute_group sis5595_group = { }; static struct attribute *sis5595_attributes_opt[] = { - &dev_attr_in4_input.attr, - &dev_attr_in4_min.attr, - &dev_attr_in4_max.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, &dev_attr_temp1_input.attr, &dev_attr_temp1_max.attr, @@ -518,68 +489,35 @@ static const struct attribute_group sis5595_group_opt = { }; /* This is called when the module is loaded */ -static int sis5595_detect(struct i2c_adapter *adapter) +static int __devinit sis5595_probe(struct platform_device *pdev) { int err = 0; int i; - struct i2c_client *new_client; struct sis5595_data *data; + struct resource *res; char val; - u16 a; - if (force_addr) - address = force_addr & ~(SIS5595_EXTENT - 1); /* Reserve the ISA region */ - if (!request_region(address, SIS5595_EXTENT, + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!request_region(res->start, SIS5595_EXTENT, sis5595_driver.driver.name)) { err = -EBUSY; goto exit; } - if (force_addr) { - dev_warn(&adapter->dev, "forcing ISA address 0x%04X\n", address); - if (PCIBIOS_SUCCESSFUL != - pci_write_config_word(s_bridge, SIS5595_BASE_REG, address)) - goto exit_release; - if (PCIBIOS_SUCCESSFUL != - pci_read_config_word(s_bridge, SIS5595_BASE_REG, &a)) - goto exit_release; - if ((a & ~(SIS5595_EXTENT - 1)) != address) - /* doesn't work for some chips? */ - goto exit_release; - } - - if (PCIBIOS_SUCCESSFUL != - pci_read_config_byte(s_bridge, SIS5595_ENABLE_REG, &val)) { - goto exit_release; - } - if ((val & 0x80) == 0) { - if (PCIBIOS_SUCCESSFUL != - pci_write_config_byte(s_bridge, SIS5595_ENABLE_REG, - val | 0x80)) - goto exit_release; - if (PCIBIOS_SUCCESSFUL != - pci_read_config_byte(s_bridge, SIS5595_ENABLE_REG, &val)) - goto exit_release; - if ((val & 0x80) == 0) - /* doesn't work for some chips! */ - goto exit_release; - } if (!(data = kzalloc(sizeof(struct sis5595_data), GFP_KERNEL))) { err = -ENOMEM; goto exit_release; } - new_client = &data->client; - new_client->addr = address; mutex_init(&data->lock); - i2c_set_clientdata(new_client, data); - new_client->adapter = adapter; - new_client->driver = &sis5595_driver; - new_client->flags = 0; + mutex_init(&data->update_lock); + data->addr = res->start; + data->name = "sis5595"; + platform_set_drvdata(pdev, data); /* Check revision and pin registers to determine whether 4 or 5 voltages */ - pci_read_config_byte(s_bridge, SIS5595_REVISION_REG, &(data->revision)); + pci_read_config_byte(s_bridge, PCI_REVISION_ID, &data->revision); /* 4 voltages, 1 temp */ data->maxins = 3; if (data->revision >= REV2MIN) { @@ -589,47 +527,37 @@ static int sis5595_detect(struct i2c_adapter *adapter) data->maxins = 4; } - /* Fill in the remaining client fields and put it into the global list */ - strlcpy(new_client->name, "sis5595", I2C_NAME_SIZE); - - data->valid = 0; - mutex_init(&data->update_lock); - - /* Tell the I2C layer a new client has arrived */ - if ((err = i2c_attach_client(new_client))) - goto exit_free; - /* Initialize the SIS5595 chip */ - sis5595_init_client(new_client); + sis5595_init_device(data); /* A few vars need to be filled upon startup */ for (i = 0; i < 2; i++) { - data->fan_min[i] = sis5595_read_value(new_client, + data->fan_min[i] = sis5595_read_value(data, SIS5595_REG_FAN_MIN(i)); } /* Register sysfs hooks */ - if ((err = sysfs_create_group(&new_client->dev.kobj, &sis5595_group))) - goto exit_detach; + if ((err = sysfs_create_group(&pdev->dev.kobj, &sis5595_group))) + goto exit_free; if (data->maxins == 4) { - if ((err = device_create_file(&new_client->dev, - &dev_attr_in4_input)) - || (err = device_create_file(&new_client->dev, - &dev_attr_in4_min)) - || (err = device_create_file(&new_client->dev, - &dev_attr_in4_max))) + if ((err = device_create_file(&pdev->dev, + &sensor_dev_attr_in4_input.dev_attr)) + || (err = device_create_file(&pdev->dev, + &sensor_dev_attr_in4_min.dev_attr)) + || (err = device_create_file(&pdev->dev, + &sensor_dev_attr_in4_max.dev_attr))) goto exit_remove_files; } else { - if ((err = device_create_file(&new_client->dev, + if ((err = device_create_file(&pdev->dev, &dev_attr_temp1_input)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(&pdev->dev, &dev_attr_temp1_max)) - || (err = device_create_file(&new_client->dev, + || (err = device_create_file(&pdev->dev, &dev_attr_temp1_max_hyst))) goto exit_remove_files; } - data->class_dev = hwmon_device_register(&new_client->dev); + data->class_dev = hwmon_device_register(&pdev->dev); if (IS_ERR(data->class_dev)) { err = PTR_ERR(data->class_dev); goto exit_remove_files; @@ -638,32 +566,26 @@ static int sis5595_detect(struct i2c_adapter *adapter) return 0; exit_remove_files: - sysfs_remove_group(&new_client->dev.kobj, &sis5595_group); - sysfs_remove_group(&new_client->dev.kobj, &sis5595_group_opt); -exit_detach: - i2c_detach_client(new_client); + sysfs_remove_group(&pdev->dev.kobj, &sis5595_group); + sysfs_remove_group(&pdev->dev.kobj, &sis5595_group_opt); exit_free: kfree(data); exit_release: - release_region(address, SIS5595_EXTENT); + release_region(res->start, SIS5595_EXTENT); exit: return err; } -static int sis5595_detach_client(struct i2c_client *client) +static int __devexit sis5595_remove(struct platform_device *pdev) { - struct sis5595_data *data = i2c_get_clientdata(client); - int err; + struct sis5595_data *data = platform_get_drvdata(pdev); hwmon_device_unregister(data->class_dev); - sysfs_remove_group(&client->dev.kobj, &sis5595_group); - sysfs_remove_group(&client->dev.kobj, &sis5595_group_opt); - - if ((err = i2c_detach_client(client))) - return err; - - release_region(client->addr, SIS5595_EXTENT); + sysfs_remove_group(&pdev->dev.kobj, &sis5595_group); + sysfs_remove_group(&pdev->dev.kobj, &sis5595_group_opt); + release_region(data->addr, SIS5595_EXTENT); + platform_set_drvdata(pdev, NULL); kfree(data); return 0; @@ -671,41 +593,37 @@ static int sis5595_detach_client(struct i2c_client *client) /* ISA access must be locked explicitly. */ -static int sis5595_read_value(struct i2c_client *client, u8 reg) +static int sis5595_read_value(struct sis5595_data *data, u8 reg) { int res; - struct sis5595_data *data = i2c_get_clientdata(client); mutex_lock(&data->lock); - outb_p(reg, client->addr + SIS5595_ADDR_REG_OFFSET); - res = inb_p(client->addr + SIS5595_DATA_REG_OFFSET); + outb_p(reg, data->addr + SIS5595_ADDR_REG_OFFSET); + res = inb_p(data->addr + SIS5595_DATA_REG_OFFSET); mutex_unlock(&data->lock); return res; } -static int sis5595_write_value(struct i2c_client *client, u8 reg, u8 value) +static void sis5595_write_value(struct sis5595_data *data, u8 reg, u8 value) { - struct sis5595_data *data = i2c_get_clientdata(client); mutex_lock(&data->lock); - outb_p(reg, client->addr + SIS5595_ADDR_REG_OFFSET); - outb_p(value, client->addr + SIS5595_DATA_REG_OFFSET); + outb_p(reg, data->addr + SIS5595_ADDR_REG_OFFSET); + outb_p(value, data->addr + SIS5595_DATA_REG_OFFSET); mutex_unlock(&data->lock); - return 0; } /* Called when we have found a new SIS5595. */ -static void sis5595_init_client(struct i2c_client *client) +static void __devinit sis5595_init_device(struct sis5595_data *data) { - u8 config = sis5595_read_value(client, SIS5595_REG_CONFIG); + u8 config = sis5595_read_value(data, SIS5595_REG_CONFIG); if (!(config & 0x01)) - sis5595_write_value(client, SIS5595_REG_CONFIG, + sis5595_write_value(data, SIS5595_REG_CONFIG, (config & 0xf7) | 0x01); } static struct sis5595_data *sis5595_update_device(struct device *dev) { - struct i2c_client *client = to_i2c_client(dev); - struct sis5595_data *data = i2c_get_clientdata(client); + struct sis5595_data *data = dev_get_drvdata(dev); int i; mutex_lock(&data->update_lock); @@ -715,35 +633,35 @@ static struct sis5595_data *sis5595_update_device(struct device *dev) for (i = 0; i <= data->maxins; i++) { data->in[i] = - sis5595_read_value(client, SIS5595_REG_IN(i)); + sis5595_read_value(data, SIS5595_REG_IN(i)); data->in_min[i] = - sis5595_read_value(client, + sis5595_read_value(data, SIS5595_REG_IN_MIN(i)); data->in_max[i] = - sis5595_read_value(client, + sis5595_read_value(data, SIS5595_REG_IN_MAX(i)); } for (i = 0; i < 2; i++) { data->fan[i] = - sis5595_read_value(client, SIS5595_REG_FAN(i)); + sis5595_read_value(data, SIS5595_REG_FAN(i)); data->fan_min[i] = - sis5595_read_value(client, + sis5595_read_value(data, SIS5595_REG_FAN_MIN(i)); } if (data->maxins == 3) { data->temp = - sis5595_read_value(client, SIS5595_REG_TEMP); + sis5595_read_value(data, SIS5595_REG_TEMP); data->temp_over = - sis5595_read_value(client, SIS5595_REG_TEMP_OVER); + sis5595_read_value(data, SIS5595_REG_TEMP_OVER); data->temp_hyst = - sis5595_read_value(client, SIS5595_REG_TEMP_HYST); + sis5595_read_value(data, SIS5595_REG_TEMP_HYST); } - i = sis5595_read_value(client, SIS5595_REG_FANDIV); + i = sis5595_read_value(data, SIS5595_REG_FANDIV); data->fan_div[0] = (i >> 4) & 0x03; data->fan_div[1] = i >> 6; data->alarms = - sis5595_read_value(client, SIS5595_REG_ALARM1) | - (sis5595_read_value(client, SIS5595_REG_ALARM2) << 8); + sis5595_read_value(data, SIS5595_REG_ALARM1) | + (sis5595_read_value(data, SIS5595_REG_ALARM2) << 8); data->last_updated = jiffies; data->valid = 1; } @@ -774,10 +692,50 @@ static int blacklist[] __devinitdata = { PCI_DEVICE_ID_SI_5598, 0 }; +static int __devinit sis5595_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + SIS5595_EXTENT - 1, + .name = "sis5595", + .flags = IORESOURCE_IO, + }; + int err; + + pdev = platform_device_alloc("sis5595", address); + if (!pdev) { + err = -ENOMEM; + printk(KERN_ERR "sis5595: Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + printk(KERN_ERR "sis5595: Device resource addition failed " + "(%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + printk(KERN_ERR "sis5595: Device addition failed (%d)\n", + err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + static int __devinit sis5595_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { - u16 val; + u16 address; + u8 enable; int *i; for (i = blacklist; *i != 0; i++) { @@ -790,27 +748,68 @@ static int __devinit sis5595_pci_probe(struct pci_dev *dev, } } + force_addr &= ~(SIS5595_EXTENT - 1); + if (force_addr) { + dev_warn(&dev->dev, "Forcing ISA address 0x%x\n", force_addr); + pci_write_config_word(dev, SIS5595_BASE_REG, force_addr); + } + if (PCIBIOS_SUCCESSFUL != - pci_read_config_word(dev, SIS5595_BASE_REG, &val)) + pci_read_config_word(dev, SIS5595_BASE_REG, &address)) { + dev_err(&dev->dev, "Failed to read ISA address\n"); return -ENODEV; + } - address = val & ~(SIS5595_EXTENT - 1); - if (address == 0 && force_addr == 0) { + address &= ~(SIS5595_EXTENT - 1); + if (!address) { dev_err(&dev->dev, "Base address not set - upgrade BIOS or use force_addr=0xaddr\n"); return -ENODEV; } + if (force_addr && address != force_addr) { + /* doesn't work for some chips? */ + dev_err(&dev->dev, "Failed to force ISA address\n"); + return -ENODEV; + } - s_bridge = pci_dev_get(dev); - if (i2c_isa_add_driver(&sis5595_driver)) { - pci_dev_put(s_bridge); - s_bridge = NULL; + if (PCIBIOS_SUCCESSFUL != + pci_read_config_byte(dev, SIS5595_ENABLE_REG, &enable)) { + dev_err(&dev->dev, "Failed to read enable register\n"); + return -ENODEV; + } + if (!(enable & 0x80)) { + if ((PCIBIOS_SUCCESSFUL != + pci_write_config_byte(dev, SIS5595_ENABLE_REG, + enable | 0x80)) + || (PCIBIOS_SUCCESSFUL != + pci_read_config_byte(dev, SIS5595_ENABLE_REG, &enable)) + || (!(enable & 0x80))) { + /* doesn't work for some chips! */ + dev_err(&dev->dev, "Failed to enable HWM device\n"); + return -ENODEV; + } } + if (platform_driver_register(&sis5595_driver)) { + dev_dbg(&dev->dev, "Failed to register sis5595 driver\n"); + goto exit; + } + + s_bridge = pci_dev_get(dev); + /* Sets global pdev as a side effect */ + if (sis5595_device_add(address)) + goto exit_unregister; + /* Always return failure here. This is to allow other drivers to bind * to this pci device. We don't really want to have control over the * pci device, we only wanted to read as few register values from it. */ return -ENODEV; + +exit_unregister: + pci_dev_put(dev); + platform_driver_unregister(&sis5595_driver); +exit: + return -ENODEV; } static struct pci_driver sis5595_pci_driver = { @@ -828,7 +827,8 @@ static void __exit sm_sis5595_exit(void) { pci_unregister_driver(&sis5595_pci_driver); if (s_bridge != NULL) { - i2c_isa_del_driver(&sis5595_driver); + platform_device_unregister(pdev); + platform_driver_unregister(&sis5595_driver); pci_dev_put(s_bridge); s_bridge = NULL; } diff --git a/drivers/hwmon/smsc47b397.c b/drivers/hwmon/smsc47b397.c index 943abbd..45266b3 100644 --- a/drivers/hwmon/smsc47b397.c +++ b/drivers/hwmon/smsc47b397.c @@ -174,6 +174,8 @@ static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); REG: count of 90kHz pulses / revolution */ static int fan_from_reg(u16 reg) { + if (reg == 0 || reg == 0xffff) + return 0; return 90000 * 60 / reg; } @@ -333,7 +335,7 @@ static int __init smsc47b397_find(unsigned short *addr) superio_enter(); id = superio_inb(SUPERIO_REG_DEVID); - if ((id != 0x6f) && (id != 0x81)) { + if ((id != 0x6f) && (id != 0x81) && (id != 0x85)) { superio_exit(); return -ENODEV; } @@ -346,7 +348,8 @@ static int __init smsc47b397_find(unsigned short *addr) printk(KERN_INFO DRVNAME ": found SMSC %s " "(base address 0x%04x, revision %u)\n", - id == 0x81 ? "SCH5307-NS" : "LPC47B397-NC", *addr, rev); + id == 0x81 ? "SCH5307-NS" : id == 0x85 ? "SCH5317" : + "LPC47B397-NC", *addr, rev); superio_exit(); return 0; diff --git a/drivers/hwmon/smsc47m1.c b/drivers/hwmon/smsc47m1.c index 1e21c8c..1de2f2b 100644 --- a/drivers/hwmon/smsc47m1.c +++ b/drivers/hwmon/smsc47m1.c @@ -597,6 +597,7 @@ static int __devinit smsc47m1_probe(struct platform_device *pdev) error_remove_files: sysfs_remove_group(&dev->kobj, &smsc47m1_group); error_free: + platform_set_drvdata(pdev, NULL); kfree(data); error_release: release_region(res->start, SMSC_EXTENT); @@ -608,12 +609,12 @@ static int __devexit smsc47m1_remove(struct platform_device *pdev) struct smsc47m1_data *data = platform_get_drvdata(pdev); struct resource *res; - platform_set_drvdata(pdev, NULL); hwmon_device_unregister(data->class_dev); sysfs_remove_group(&pdev->dev.kobj, &smsc47m1_group); res = platform_get_resource(pdev, IORESOURCE_IO, 0); release_region(res->start, SMSC_EXTENT); + platform_set_drvdata(pdev, NULL); kfree(data); return 0; @@ -693,15 +694,12 @@ static int __init smsc47m1_device_add(unsigned short address, goto exit_device_put; } - pdev->dev.platform_data = kmalloc(sizeof(struct smsc47m1_sio_data), - GFP_KERNEL); - if (!pdev->dev.platform_data) { - err = -ENOMEM; + err = platform_device_add_data(pdev, sio_data, + sizeof(struct smsc47m1_sio_data)); + if (err) { printk(KERN_ERR DRVNAME ": Platform data allocation failed\n"); goto exit_device_put; } - memcpy(pdev->dev.platform_data, sio_data, - sizeof(struct smsc47m1_sio_data)); err = platform_device_add(pdev); if (err) { diff --git a/drivers/hwmon/smsc47m192.c b/drivers/hwmon/smsc47m192.c index a012f39..d3a3ba0 100644 --- a/drivers/hwmon/smsc47m192.c +++ b/drivers/hwmon/smsc47m192.c @@ -31,6 +31,7 @@ #include <linux/hwmon-vid.h> #include <linux/err.h> #include <linux/sysfs.h> +#include <linux/mutex.h> /* Addresses to scan */ static unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END }; @@ -97,7 +98,7 @@ static inline int TEMP_FROM_REG(s8 val) struct smsc47m192_data { struct i2c_client client; struct class_device *class_dev; - struct semaphore update_lock; + struct mutex update_lock; char valid; /* !=0 if following fields are valid */ unsigned long last_updated; /* In jiffies */ @@ -164,11 +165,11 @@ static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, struct smsc47m192_data *data = i2c_get_clientdata(client); unsigned long val = simple_strtoul(buf, NULL, 10); - down(&data->update_lock); + mutex_lock(&data->update_lock); data->in_min[nr] = IN_TO_REG(val, nr); i2c_smbus_write_byte_data(client, SMSC47M192_REG_IN_MIN(nr), data->in_min[nr]); - up(&data->update_lock); + mutex_unlock(&data->update_lock); return count; } @@ -181,11 +182,11 @@ static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, struct smsc47m192_data *data = i2c_get_clientdata(client); unsigned long val = simple_strtoul(buf, NULL, 10); - down(&data->update_lock); + mutex_lock(&data->update_lock); data->in_max[nr] = IN_TO_REG(val, nr); i2c_smbus_write_byte_data(client, SMSC47M192_REG_IN_MAX(nr), data->in_max[nr]); - up(&data->update_lock); + mutex_unlock(&data->update_lock); return count; } @@ -243,11 +244,11 @@ static ssize_t set_temp_min(struct device *dev, struct device_attribute *attr, struct smsc47m192_data *data = i2c_get_clientdata(client); long val = simple_strtol(buf, NULL, 10); - down(&data->update_lock); + mutex_lock(&data->update_lock); data->temp_min[nr] = TEMP_TO_REG(val); i2c_smbus_write_byte_data(client, SMSC47M192_REG_TEMP_MIN[nr], data->temp_min[nr]); - up(&data->update_lock); + mutex_unlock(&data->update_lock); return count; } @@ -260,11 +261,11 @@ static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, struct smsc47m192_data *data = i2c_get_clientdata(client); long val = simple_strtol(buf, NULL, 10); - down(&data->update_lock); + mutex_lock(&data->update_lock); data->temp_max[nr] = TEMP_TO_REG(val); i2c_smbus_write_byte_data(client, SMSC47M192_REG_TEMP_MAX[nr], data->temp_max[nr]); - up(&data->update_lock); + mutex_unlock(&data->update_lock); return count; } @@ -287,7 +288,7 @@ static ssize_t set_temp_offset(struct device *dev, struct device_attribute u8 sfr = i2c_smbus_read_byte_data(client, SMSC47M192_REG_SFR); long val = simple_strtol(buf, NULL, 10); - down(&data->update_lock); + mutex_lock(&data->update_lock); data->temp_offset[nr] = TEMP_TO_REG(val); if (nr>1) i2c_smbus_write_byte_data(client, @@ -303,7 +304,7 @@ static ssize_t set_temp_offset(struct device *dev, struct device_attribute } else if ((sfr & 0x10) == (nr==0 ? 0x10 : 0)) i2c_smbus_write_byte_data(client, SMSC47M192_REG_TEMP_OFFSET(nr), 0); - up(&data->update_lock); + mutex_unlock(&data->update_lock); return count; } @@ -360,8 +361,8 @@ static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 0x0010); static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 0x0020); static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 0x0040); -static SENSOR_DEVICE_ATTR(temp2_input_fault, S_IRUGO, show_alarm, NULL, 0x4000); -static SENSOR_DEVICE_ATTR(temp3_input_fault, S_IRUGO, show_alarm, NULL, 0x8000); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 0x4000); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 0x8000); static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0x0001); static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 0x0002); static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 0x0004); @@ -411,13 +412,13 @@ static struct attribute *smsc47m192_attributes[] = { &sensor_dev_attr_temp2_min.dev_attr.attr, &sensor_dev_attr_temp2_offset.dev_attr.attr, &sensor_dev_attr_temp2_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_input_fault.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, &sensor_dev_attr_temp3_input.dev_attr.attr, &sensor_dev_attr_temp3_max.dev_attr.attr, &sensor_dev_attr_temp3_min.dev_attr.attr, &sensor_dev_attr_temp3_offset.dev_attr.attr, &sensor_dev_attr_temp3_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_input_fault.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, &dev_attr_cpu0_vid.attr, &dev_attr_vrm.attr, @@ -531,7 +532,7 @@ static int smsc47m192_detect(struct i2c_adapter *adapter, int address, /* Fill in the remaining client fields and put into the global list */ strlcpy(client->name, "smsc47m192", I2C_NAME_SIZE); data->vrm = vid_which_vrm(); - init_MUTEX(&data->update_lock); + mutex_init(&data->update_lock); /* Tell the I2C layer a new client has arrived */ if ((err = i2c_attach_client(client))) @@ -594,7 +595,7 @@ static struct smsc47m192_data *smsc47m192_update_device(struct device *dev) struct smsc47m192_data *data = i2c_get_clientdata(client); int i, config; - down(&data->update_lock); + mutex_lock(&data->update_lock); if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || !data->valid) { @@ -645,7 +646,7 @@ static struct smsc47m192_data *smsc47m192_update_device(struct device *dev) data->valid = 1; } - up(&data->update_lock); + mutex_unlock(&data->update_lock); return data; } diff --git a/drivers/hwmon/via686a.c b/drivers/hwmon/via686a.c index 9a440c8..24a6851 100644 --- a/drivers/hwmon/via686a.c +++ b/drivers/hwmon/via686a.c @@ -34,9 +34,9 @@ #include <linux/slab.h> #include <linux/pci.h> #include <linux/jiffies.h> -#include <linux/i2c.h> -#include <linux/i2c-isa.h> +#include <linux/platform_device.h> #include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> #include <linux/err.h> #include <linux/init.h> #include <linux/mutex.h> @@ -51,10 +51,7 @@ module_param(force_addr, ushort, 0); MODULE_PARM_DESC(force_addr, "Initialize the base address of the sensors"); -/* Device address - Note that we can't determine the ISA address until we have initialized - our module */ -static unsigned short address; +static struct platform_device *pdev; /* The Via 686a southbridge has a LM78-like chip integrated on the same IC. @@ -295,7 +292,8 @@ static inline long TEMP_FROM_REG10(u16 val) /* For each registered chip, we need to keep some data in memory. The structure is dynamically allocated. */ struct via686a_data { - struct i2c_client client; + unsigned short addr; + const char *name; struct class_device *class_dev; struct mutex update_lock; char valid; /* !=0 if following fields are valid */ @@ -315,98 +313,85 @@ struct via686a_data { static struct pci_dev *s_bridge; /* pointer to the (only) via686a */ -static int via686a_detect(struct i2c_adapter *adapter); -static int via686a_detach_client(struct i2c_client *client); +static int via686a_probe(struct platform_device *pdev); +static int via686a_remove(struct platform_device *pdev); -static inline int via686a_read_value(struct i2c_client *client, u8 reg) +static inline int via686a_read_value(struct via686a_data *data, u8 reg) { - return (inb_p(client->addr + reg)); + return inb_p(data->addr + reg); } -static inline void via686a_write_value(struct i2c_client *client, u8 reg, +static inline void via686a_write_value(struct via686a_data *data, u8 reg, u8 value) { - outb_p(value, client->addr + reg); + outb_p(value, data->addr + reg); } static struct via686a_data *via686a_update_device(struct device *dev); -static void via686a_init_client(struct i2c_client *client); +static void via686a_init_device(struct via686a_data *data); /* following are the sysfs callback functions */ /* 7 voltage sensors */ -static ssize_t show_in(struct device *dev, char *buf, int nr) { +static ssize_t show_in(struct device *dev, struct device_attribute *da, + char *buf) { struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%ld\n", IN_FROM_REG(data->in[nr], nr)); } -static ssize_t show_in_min(struct device *dev, char *buf, int nr) { +static ssize_t show_in_min(struct device *dev, struct device_attribute *da, + char *buf) { struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%ld\n", IN_FROM_REG(data->in_min[nr], nr)); } -static ssize_t show_in_max(struct device *dev, char *buf, int nr) { +static ssize_t show_in_max(struct device *dev, struct device_attribute *da, + char *buf) { struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%ld\n", IN_FROM_REG(data->in_max[nr], nr)); } -static ssize_t set_in_min(struct device *dev, const char *buf, - size_t count, int nr) { - struct i2c_client *client = to_i2c_client(dev); - struct via686a_data *data = i2c_get_clientdata(client); +static ssize_t set_in_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { + struct via686a_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); data->in_min[nr] = IN_TO_REG(val, nr); - via686a_write_value(client, VIA686A_REG_IN_MIN(nr), + via686a_write_value(data, VIA686A_REG_IN_MIN(nr), data->in_min[nr]); mutex_unlock(&data->update_lock); return count; } -static ssize_t set_in_max(struct device *dev, const char *buf, - size_t count, int nr) { - struct i2c_client *client = to_i2c_client(dev); - struct via686a_data *data = i2c_get_clientdata(client); +static ssize_t set_in_max(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { + struct via686a_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); data->in_max[nr] = IN_TO_REG(val, nr); - via686a_write_value(client, VIA686A_REG_IN_MAX(nr), + via686a_write_value(data, VIA686A_REG_IN_MAX(nr), data->in_max[nr]); mutex_unlock(&data->update_lock); return count; } #define show_in_offset(offset) \ -static ssize_t \ - show_in##offset (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_in(dev, buf, offset); \ -} \ -static ssize_t \ - show_in##offset##_min (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_in_min(dev, buf, offset); \ -} \ -static ssize_t \ - show_in##offset##_max (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_in_max(dev, buf, offset); \ -} \ -static ssize_t set_in##offset##_min (struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - return set_in_min(dev, buf, count, offset); \ -} \ -static ssize_t set_in##offset##_max (struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - return set_in_max(dev, buf, count, offset); \ -} \ -static DEVICE_ATTR(in##offset##_input, S_IRUGO, show_in##offset, NULL);\ -static DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ - show_in##offset##_min, set_in##offset##_min); \ -static DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ - show_in##offset##_max, set_in##offset##_max); +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ + show_in_min, set_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ + show_in_max, set_in_max, offset); show_in_offset(0); show_in_offset(1); @@ -415,150 +400,128 @@ show_in_offset(3); show_in_offset(4); /* 3 temperatures */ -static ssize_t show_temp(struct device *dev, char *buf, int nr) { +static ssize_t show_temp(struct device *dev, struct device_attribute *da, + char *buf) { struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%ld\n", TEMP_FROM_REG10(data->temp[nr])); } -static ssize_t show_temp_over(struct device *dev, char *buf, int nr) { +static ssize_t show_temp_over(struct device *dev, struct device_attribute *da, + char *buf) { struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%ld\n", TEMP_FROM_REG(data->temp_over[nr])); } -static ssize_t show_temp_hyst(struct device *dev, char *buf, int nr) { +static ssize_t show_temp_hyst(struct device *dev, struct device_attribute *da, + char *buf) { struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%ld\n", TEMP_FROM_REG(data->temp_hyst[nr])); } -static ssize_t set_temp_over(struct device *dev, const char *buf, - size_t count, int nr) { - struct i2c_client *client = to_i2c_client(dev); - struct via686a_data *data = i2c_get_clientdata(client); +static ssize_t set_temp_over(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { + struct via686a_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; int val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); data->temp_over[nr] = TEMP_TO_REG(val); - via686a_write_value(client, VIA686A_REG_TEMP_OVER[nr], + via686a_write_value(data, VIA686A_REG_TEMP_OVER[nr], data->temp_over[nr]); mutex_unlock(&data->update_lock); return count; } -static ssize_t set_temp_hyst(struct device *dev, const char *buf, - size_t count, int nr) { - struct i2c_client *client = to_i2c_client(dev); - struct via686a_data *data = i2c_get_clientdata(client); +static ssize_t set_temp_hyst(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { + struct via686a_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; int val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); data->temp_hyst[nr] = TEMP_TO_REG(val); - via686a_write_value(client, VIA686A_REG_TEMP_HYST[nr], + via686a_write_value(data, VIA686A_REG_TEMP_HYST[nr], data->temp_hyst[nr]); mutex_unlock(&data->update_lock); return count; } #define show_temp_offset(offset) \ -static ssize_t show_temp_##offset (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_temp(dev, buf, offset - 1); \ -} \ -static ssize_t \ -show_temp_##offset##_over (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_temp_over(dev, buf, offset - 1); \ -} \ -static ssize_t \ -show_temp_##offset##_hyst (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_temp_hyst(dev, buf, offset - 1); \ -} \ -static ssize_t set_temp_##offset##_over (struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - return set_temp_over(dev, buf, count, offset - 1); \ -} \ -static ssize_t set_temp_##offset##_hyst (struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - return set_temp_hyst(dev, buf, count, offset - 1); \ -} \ -static DEVICE_ATTR(temp##offset##_input, S_IRUGO, show_temp_##offset, NULL);\ -static DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR, \ - show_temp_##offset##_over, set_temp_##offset##_over); \ -static DEVICE_ATTR(temp##offset##_max_hyst, S_IRUGO | S_IWUSR, \ - show_temp_##offset##_hyst, set_temp_##offset##_hyst); +static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, \ + show_temp, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR, \ + show_temp_over, set_temp_over, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max_hyst, S_IRUGO | S_IWUSR, \ + show_temp_hyst, set_temp_hyst, offset - 1); show_temp_offset(1); show_temp_offset(2); show_temp_offset(3); /* 2 Fans */ -static ssize_t show_fan(struct device *dev, char *buf, int nr) { +static ssize_t show_fan(struct device *dev, struct device_attribute *da, + char *buf) { struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr], DIV_FROM_REG(data->fan_div[nr])) ); } -static ssize_t show_fan_min(struct device *dev, char *buf, int nr) { +static ssize_t show_fan_min(struct device *dev, struct device_attribute *da, + char *buf) { struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr])) ); } -static ssize_t show_fan_div(struct device *dev, char *buf, int nr) { +static ssize_t show_fan_div(struct device *dev, struct device_attribute *da, + char *buf) { struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr]) ); } -static ssize_t set_fan_min(struct device *dev, const char *buf, - size_t count, int nr) { - struct i2c_client *client = to_i2c_client(dev); - struct via686a_data *data = i2c_get_clientdata(client); +static ssize_t set_fan_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { + struct via686a_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; int val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); - via686a_write_value(client, VIA686A_REG_FAN_MIN(nr+1), data->fan_min[nr]); + via686a_write_value(data, VIA686A_REG_FAN_MIN(nr+1), data->fan_min[nr]); mutex_unlock(&data->update_lock); return count; } -static ssize_t set_fan_div(struct device *dev, const char *buf, - size_t count, int nr) { - struct i2c_client *client = to_i2c_client(dev); - struct via686a_data *data = i2c_get_clientdata(client); +static ssize_t set_fan_div(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { + struct via686a_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; int val = simple_strtol(buf, NULL, 10); int old; mutex_lock(&data->update_lock); - old = via686a_read_value(client, VIA686A_REG_FANDIV); + old = via686a_read_value(data, VIA686A_REG_FANDIV); data->fan_div[nr] = DIV_TO_REG(val); old = (old & 0x0f) | (data->fan_div[1] << 6) | (data->fan_div[0] << 4); - via686a_write_value(client, VIA686A_REG_FANDIV, old); + via686a_write_value(data, VIA686A_REG_FANDIV, old); mutex_unlock(&data->update_lock); return count; } #define show_fan_offset(offset) \ -static ssize_t show_fan_##offset (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_fan(dev, buf, offset - 1); \ -} \ -static ssize_t show_fan_##offset##_min (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_fan_min(dev, buf, offset - 1); \ -} \ -static ssize_t show_fan_##offset##_div (struct device *dev, struct device_attribute *attr, char *buf) \ -{ \ - return show_fan_div(dev, buf, offset - 1); \ -} \ -static ssize_t set_fan_##offset##_min (struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - return set_fan_min(dev, buf, count, offset - 1); \ -} \ -static ssize_t set_fan_##offset##_div (struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - return set_fan_div(dev, buf, count, offset - 1); \ -} \ -static DEVICE_ATTR(fan##offset##_input, S_IRUGO, show_fan_##offset, NULL);\ -static DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ - show_fan_##offset##_min, set_fan_##offset##_min); \ -static DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \ - show_fan_##offset##_div, set_fan_##offset##_div); +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ + show_fan, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, set_fan_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \ + show_fan_div, set_fan_div, offset - 1); show_fan_offset(1); show_fan_offset(2); @@ -570,41 +533,50 @@ static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, ch } static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL); +static ssize_t show_name(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct via686a_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + static struct attribute *via686a_attributes[] = { - &dev_attr_in0_input.attr, - &dev_attr_in1_input.attr, - &dev_attr_in2_input.attr, - &dev_attr_in3_input.attr, - &dev_attr_in4_input.attr, - &dev_attr_in0_min.attr, - &dev_attr_in1_min.attr, - &dev_attr_in2_min.attr, - &dev_attr_in3_min.attr, - &dev_attr_in4_min.attr, - &dev_attr_in0_max.attr, - &dev_attr_in1_max.attr, - &dev_attr_in2_max.attr, - &dev_attr_in3_max.attr, - &dev_attr_in4_max.attr, - - &dev_attr_temp1_input.attr, - &dev_attr_temp2_input.attr, - &dev_attr_temp3_input.attr, - &dev_attr_temp1_max.attr, - &dev_attr_temp2_max.attr, - &dev_attr_temp3_max.attr, - &dev_attr_temp1_max_hyst.attr, - &dev_attr_temp2_max_hyst.attr, - &dev_attr_temp3_max_hyst.attr, - - &dev_attr_fan1_input.attr, - &dev_attr_fan2_input.attr, - &dev_attr_fan1_min.attr, - &dev_attr_fan2_min.attr, - &dev_attr_fan1_div.attr, - &dev_attr_fan2_div.attr, + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, &dev_attr_alarms.attr, + &dev_attr_name.attr, NULL }; @@ -612,58 +584,29 @@ static const struct attribute_group via686a_group = { .attrs = via686a_attributes, }; -/* The driver. I choose to use type i2c_driver, as at is identical to both - smbus_driver and isa_driver, and clients could be of either kind */ -static struct i2c_driver via686a_driver = { +static struct platform_driver via686a_driver = { .driver = { .owner = THIS_MODULE, .name = "via686a", }, - .attach_adapter = via686a_detect, - .detach_client = via686a_detach_client, + .probe = via686a_probe, + .remove = __devexit_p(via686a_remove), }; /* This is called when the module is loaded */ -static int via686a_detect(struct i2c_adapter *adapter) +static int __devinit via686a_probe(struct platform_device *pdev) { - struct i2c_client *new_client; struct via686a_data *data; - int err = 0; - const char client_name[] = "via686a"; - u16 val; - - /* 8231 requires multiple of 256, we enforce that on 686 as well */ - if (force_addr) { - address = force_addr & 0xFF00; - dev_warn(&adapter->dev, "forcing ISA address 0x%04X\n", - address); - if (PCIBIOS_SUCCESSFUL != - pci_write_config_word(s_bridge, VIA686A_BASE_REG, address)) - return -ENODEV; - } - if (PCIBIOS_SUCCESSFUL != - pci_read_config_word(s_bridge, VIA686A_ENABLE_REG, &val)) - return -ENODEV; - if (!(val & 0x0001)) { - if (force_addr) { - dev_info(&adapter->dev, "enabling sensors\n"); - if (PCIBIOS_SUCCESSFUL != - pci_write_config_word(s_bridge, VIA686A_ENABLE_REG, - val | 0x0001)) - return -ENODEV; - } else { - dev_warn(&adapter->dev, "sensors disabled - enable " - "with force_addr=0x%x\n", address); - return -ENODEV; - } - } + struct resource *res; + int err; /* Reserve the ISA region */ - if (!request_region(address, VIA686A_EXTENT, + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!request_region(res->start, VIA686A_EXTENT, via686a_driver.driver.name)) { - dev_err(&adapter->dev, "region 0x%x already in use!\n", - address); + dev_err(&pdev->dev, "Region 0x%lx-0x%lx already in use!\n", + (unsigned long)res->start, (unsigned long)res->end); return -ENODEV; } @@ -672,30 +615,19 @@ static int via686a_detect(struct i2c_adapter *adapter) goto exit_release; } - new_client = &data->client; - i2c_set_clientdata(new_client, data); - new_client->addr = address; - new_client->adapter = adapter; - new_client->driver = &via686a_driver; - new_client->flags = 0; - - /* Fill in the remaining client fields and put into the global list */ - strlcpy(new_client->name, client_name, I2C_NAME_SIZE); - - data->valid = 0; + platform_set_drvdata(pdev, data); + data->addr = res->start; + data->name = "via686a"; mutex_init(&data->update_lock); - /* Tell the I2C layer a new client has arrived */ - if ((err = i2c_attach_client(new_client))) - goto exit_free; /* Initialize the VIA686A chip */ - via686a_init_client(new_client); + via686a_init_device(data); /* Register sysfs hooks */ - if ((err = sysfs_create_group(&new_client->dev.kobj, &via686a_group))) - goto exit_detach; + if ((err = sysfs_create_group(&pdev->dev.kobj, &via686a_group))) + goto exit_free; - data->class_dev = hwmon_device_register(&new_client->dev); + data->class_dev = hwmon_device_register(&pdev->dev); if (IS_ERR(data->class_dev)) { err = PTR_ERR(data->class_dev); goto exit_remove_files; @@ -704,51 +636,46 @@ static int via686a_detect(struct i2c_adapter *adapter) return 0; exit_remove_files: - sysfs_remove_group(&new_client->dev.kobj, &via686a_group); -exit_detach: - i2c_detach_client(new_client); + sysfs_remove_group(&pdev->dev.kobj, &via686a_group); exit_free: kfree(data); exit_release: - release_region(address, VIA686A_EXTENT); + release_region(res->start, VIA686A_EXTENT); return err; } -static int via686a_detach_client(struct i2c_client *client) +static int __devexit via686a_remove(struct platform_device *pdev) { - struct via686a_data *data = i2c_get_clientdata(client); - int err; + struct via686a_data *data = platform_get_drvdata(pdev); hwmon_device_unregister(data->class_dev); - sysfs_remove_group(&client->dev.kobj, &via686a_group); + sysfs_remove_group(&pdev->dev.kobj, &via686a_group); - if ((err = i2c_detach_client(client))) - return err; - - release_region(client->addr, VIA686A_EXTENT); + release_region(data->addr, VIA686A_EXTENT); + platform_set_drvdata(pdev, NULL); kfree(data); return 0; } -static void via686a_init_client(struct i2c_client *client) +static void __devinit via686a_init_device(struct via686a_data *data) { u8 reg; /* Start monitoring */ - reg = via686a_read_value(client, VIA686A_REG_CONFIG); - via686a_write_value(client, VIA686A_REG_CONFIG, (reg|0x01)&0x7F); + reg = via686a_read_value(data, VIA686A_REG_CONFIG); + via686a_write_value(data, VIA686A_REG_CONFIG, (reg | 0x01) & 0x7F); /* Configure temp interrupt mode for continuous-interrupt operation */ - via686a_write_value(client, VIA686A_REG_TEMP_MODE, - via686a_read_value(client, VIA686A_REG_TEMP_MODE) & - !(VIA686A_TEMP_MODE_MASK | VIA686A_TEMP_MODE_CONTINUOUS)); + reg = via686a_read_value(data, VIA686A_REG_TEMP_MODE); + via686a_write_value(data, VIA686A_REG_TEMP_MODE, + (reg & ~VIA686A_TEMP_MODE_MASK) + | VIA686A_TEMP_MODE_CONTINUOUS); } static struct via686a_data *via686a_update_device(struct device *dev) { - struct i2c_client *client = to_i2c_client(dev); - struct via686a_data *data = i2c_get_clientdata(client); + struct via686a_data *data = dev_get_drvdata(dev); int i; mutex_lock(&data->update_lock); @@ -757,27 +684,27 @@ static struct via686a_data *via686a_update_device(struct device *dev) || !data->valid) { for (i = 0; i <= 4; i++) { data->in[i] = - via686a_read_value(client, VIA686A_REG_IN(i)); - data->in_min[i] = via686a_read_value(client, + via686a_read_value(data, VIA686A_REG_IN(i)); + data->in_min[i] = via686a_read_value(data, VIA686A_REG_IN_MIN (i)); data->in_max[i] = - via686a_read_value(client, VIA686A_REG_IN_MAX(i)); + via686a_read_value(data, VIA686A_REG_IN_MAX(i)); } for (i = 1; i <= 2; i++) { data->fan[i - 1] = - via686a_read_value(client, VIA686A_REG_FAN(i)); - data->fan_min[i - 1] = via686a_read_value(client, + via686a_read_value(data, VIA686A_REG_FAN(i)); + data->fan_min[i - 1] = via686a_read_value(data, VIA686A_REG_FAN_MIN(i)); } for (i = 0; i <= 2; i++) { - data->temp[i] = via686a_read_value(client, + data->temp[i] = via686a_read_value(data, VIA686A_REG_TEMP[i]) << 2; data->temp_over[i] = - via686a_read_value(client, + via686a_read_value(data, VIA686A_REG_TEMP_OVER[i]); data->temp_hyst[i] = - via686a_read_value(client, + via686a_read_value(data, VIA686A_REG_TEMP_HYST[i]); } /* add in lower 2 bits @@ -785,23 +712,23 @@ static struct via686a_data *via686a_update_device(struct device *dev) temp2 uses bits 5-4 of VIA686A_REG_TEMP_LOW23 temp3 uses bits 7-6 of VIA686A_REG_TEMP_LOW23 */ - data->temp[0] |= (via686a_read_value(client, + data->temp[0] |= (via686a_read_value(data, VIA686A_REG_TEMP_LOW1) & 0xc0) >> 6; data->temp[1] |= - (via686a_read_value(client, VIA686A_REG_TEMP_LOW23) & + (via686a_read_value(data, VIA686A_REG_TEMP_LOW23) & 0x30) >> 4; data->temp[2] |= - (via686a_read_value(client, VIA686A_REG_TEMP_LOW23) & + (via686a_read_value(data, VIA686A_REG_TEMP_LOW23) & 0xc0) >> 6; - i = via686a_read_value(client, VIA686A_REG_FANDIV); + i = via686a_read_value(data, VIA686A_REG_FANDIV); data->fan_div[0] = (i >> 4) & 0x03; data->fan_div[1] = i >> 6; data->alarms = - via686a_read_value(client, + via686a_read_value(data, VIA686A_REG_ALARM1) | - (via686a_read_value(client, VIA686A_REG_ALARM2) << 8); + (via686a_read_value(data, VIA686A_REG_ALARM2) << 8); data->last_updated = jiffies; data->valid = 1; } @@ -818,32 +745,102 @@ static struct pci_device_id via686a_pci_ids[] = { MODULE_DEVICE_TABLE(pci, via686a_pci_ids); +static int __devinit via686a_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + VIA686A_EXTENT - 1, + .name = "via686a", + .flags = IORESOURCE_IO, + }; + int err; + + pdev = platform_device_alloc("via686a", address); + if (!pdev) { + err = -ENOMEM; + printk(KERN_ERR "via686a: Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + printk(KERN_ERR "via686a: Device resource addition failed " + "(%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + printk(KERN_ERR "via686a: Device addition failed (%d)\n", + err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + static int __devinit via686a_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { - u16 val; + u16 address, val; + if (force_addr) { + address = force_addr & ~(VIA686A_EXTENT - 1); + dev_warn(&dev->dev, "Forcing ISA address 0x%x\n", address); + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(dev, VIA686A_BASE_REG, address | 1)) + return -ENODEV; + } if (PCIBIOS_SUCCESSFUL != pci_read_config_word(dev, VIA686A_BASE_REG, &val)) return -ENODEV; address = val & ~(VIA686A_EXTENT - 1); - if (address == 0 && force_addr == 0) { + if (address == 0) { dev_err(&dev->dev, "base address not set - upgrade BIOS " "or use force_addr=0xaddr\n"); return -ENODEV; } - s_bridge = pci_dev_get(dev); - if (i2c_isa_add_driver(&via686a_driver)) { - pci_dev_put(s_bridge); - s_bridge = NULL; + if (PCIBIOS_SUCCESSFUL != + pci_read_config_word(dev, VIA686A_ENABLE_REG, &val)) + return -ENODEV; + if (!(val & 0x0001)) { + if (!force_addr) { + dev_warn(&dev->dev, "Sensors disabled, enable " + "with force_addr=0x%x\n", address); + return -ENODEV; + } + + dev_warn(&dev->dev, "Enabling sensors\n"); + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(dev, VIA686A_ENABLE_REG, + val | 0x0001)) + return -ENODEV; } + if (platform_driver_register(&via686a_driver)) + goto exit; + + /* Sets global pdev as a side effect */ + if (via686a_device_add(address)) + goto exit_unregister; + /* Always return failure here. This is to allow other drivers to bind * to this pci device. We don't really want to have control over the * pci device, we only wanted to read as few register values from it. */ + s_bridge = pci_dev_get(dev); + return -ENODEV; + +exit_unregister: + platform_driver_unregister(&via686a_driver); +exit: return -ENODEV; } @@ -862,7 +859,8 @@ static void __exit sm_via686a_exit(void) { pci_unregister_driver(&via686a_pci_driver); if (s_bridge != NULL) { - i2c_isa_del_driver(&via686a_driver); + platform_device_unregister(pdev); + platform_driver_unregister(&via686a_driver); pci_dev_put(s_bridge); s_bridge = NULL; } diff --git a/drivers/hwmon/vt8231.c b/drivers/hwmon/vt8231.c index a6a4aa0..c604972 100644 --- a/drivers/hwmon/vt8231.c +++ b/drivers/hwmon/vt8231.c @@ -29,8 +29,7 @@ #include <linux/slab.h> #include <linux/pci.h> #include <linux/jiffies.h> -#include <linux/i2c.h> -#include <linux/i2c-isa.h> +#include <linux/platform_device.h> #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> #include <linux/hwmon-vid.h> @@ -42,10 +41,7 @@ static int force_addr; module_param(force_addr, int, 0); MODULE_PARM_DESC(force_addr, "Initialize the base address of the sensors"); -/* Device address - Note that we can't determine the ISA address until we have initialized - our module */ -static unsigned short isa_address; +static struct platform_device *pdev; #define VT8231_EXTENT 0x80 #define VT8231_BASE_REG 0x70 @@ -148,7 +144,9 @@ static inline u8 FAN_TO_REG(long rpm, int div) #define FAN_FROM_REG(val, div) ((val) == 0 ? 0 : 1310720 / ((val) * (div))) struct vt8231_data { - struct i2c_client client; + unsigned short addr; + const char *name; + struct mutex update_lock; struct class_device *class_dev; char valid; /* !=0 if following fields are valid */ @@ -168,20 +166,20 @@ struct vt8231_data { }; static struct pci_dev *s_bridge; -static int vt8231_detect(struct i2c_adapter *adapter); -static int vt8231_detach_client(struct i2c_client *client); +static int vt8231_probe(struct platform_device *pdev); +static int vt8231_remove(struct platform_device *pdev); static struct vt8231_data *vt8231_update_device(struct device *dev); -static void vt8231_init_client(struct i2c_client *client); +static void vt8231_init_device(struct vt8231_data *data); -static inline int vt8231_read_value(struct i2c_client *client, u8 reg) +static inline int vt8231_read_value(struct vt8231_data *data, u8 reg) { - return inb_p(client->addr + reg); + return inb_p(data->addr + reg); } -static inline void vt8231_write_value(struct i2c_client *client, u8 reg, +static inline void vt8231_write_value(struct vt8231_data *data, u8 reg, u8 value) { - outb_p(value, client->addr + reg); + outb_p(value, data->addr + reg); } /* following are the sysfs callback functions */ @@ -220,13 +218,12 @@ static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, { struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct vt8231_data *data = i2c_get_clientdata(client); + struct vt8231_data *data = dev_get_drvdata(dev); unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); data->in_min[nr] = SENSORS_LIMIT(((val * 958) / 10000) + 3, 0, 255); - vt8231_write_value(client, regvoltmin[nr], data->in_min[nr]); + vt8231_write_value(data, regvoltmin[nr], data->in_min[nr]); mutex_unlock(&data->update_lock); return count; } @@ -236,13 +233,12 @@ static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, { struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct vt8231_data *data = i2c_get_clientdata(client); + struct vt8231_data *data = dev_get_drvdata(dev); unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); data->in_max[nr] = SENSORS_LIMIT(((val * 958) / 10000) + 3, 0, 255); - vt8231_write_value(client, regvoltmax[nr], data->in_max[nr]); + vt8231_write_value(data, regvoltmax[nr], data->in_max[nr]); mutex_unlock(&data->update_lock); return count; } @@ -278,14 +274,13 @@ static ssize_t show_in5_max(struct device *dev, struct device_attribute *attr, static ssize_t set_in5_min(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct vt8231_data *data = i2c_get_clientdata(client); + struct vt8231_data *data = dev_get_drvdata(dev); unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); data->in_min[5] = SENSORS_LIMIT(((val * 958 * 34) / (10000 * 54)) + 3, 0, 255); - vt8231_write_value(client, regvoltmin[5], data->in_min[5]); + vt8231_write_value(data, regvoltmin[5], data->in_min[5]); mutex_unlock(&data->update_lock); return count; } @@ -293,14 +288,13 @@ static ssize_t set_in5_min(struct device *dev, struct device_attribute *attr, static ssize_t set_in5_max(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct vt8231_data *data = i2c_get_clientdata(client); + struct vt8231_data *data = dev_get_drvdata(dev); unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); data->in_max[5] = SENSORS_LIMIT(((val * 958 * 34) / (10000 * 54)) + 3, 0, 255); - vt8231_write_value(client, regvoltmax[5], data->in_max[5]); + vt8231_write_value(data, regvoltmax[5], data->in_max[5]); mutex_unlock(&data->update_lock); return count; } @@ -348,26 +342,24 @@ static ssize_t show_temp0_min(struct device *dev, struct device_attribute *attr, static ssize_t set_temp0_max(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct vt8231_data *data = i2c_get_clientdata(client); + struct vt8231_data *data = dev_get_drvdata(dev); int val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); data->temp_max[0] = SENSORS_LIMIT((val + 500) / 1000, 0, 255); - vt8231_write_value(client, regtempmax[0], data->temp_max[0]); + vt8231_write_value(data, regtempmax[0], data->temp_max[0]); mutex_unlock(&data->update_lock); return count; } static ssize_t set_temp0_min(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct vt8231_data *data = i2c_get_clientdata(client); + struct vt8231_data *data = dev_get_drvdata(dev); int val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); data->temp_min[0] = SENSORS_LIMIT((val + 500) / 1000, 0, 255); - vt8231_write_value(client, regtempmin[0], data->temp_min[0]); + vt8231_write_value(data, regtempmin[0], data->temp_min[0]); mutex_unlock(&data->update_lock); return count; } @@ -404,13 +396,12 @@ static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, { struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct vt8231_data *data = i2c_get_clientdata(client); + struct vt8231_data *data = dev_get_drvdata(dev); int val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); data->temp_max[nr] = SENSORS_LIMIT(TEMP_MAXMIN_TO_REG(val), 0, 255); - vt8231_write_value(client, regtempmax[nr], data->temp_max[nr]); + vt8231_write_value(data, regtempmax[nr], data->temp_max[nr]); mutex_unlock(&data->update_lock); return count; } @@ -419,13 +410,12 @@ static ssize_t set_temp_min(struct device *dev, struct device_attribute *attr, { struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct vt8231_data *data = i2c_get_clientdata(client); + struct vt8231_data *data = dev_get_drvdata(dev); int val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); data->temp_min[nr] = SENSORS_LIMIT(TEMP_MAXMIN_TO_REG(val), 0, 255); - vt8231_write_value(client, regtempmin[nr], data->temp_min[nr]); + vt8231_write_value(data, regtempmin[nr], data->temp_min[nr]); mutex_unlock(&data->update_lock); return count; } @@ -486,13 +476,12 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, { struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; - struct i2c_client *client = to_i2c_client(dev); - struct vt8231_data *data = i2c_get_clientdata(client); + struct vt8231_data *data = dev_get_drvdata(dev); int val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); - vt8231_write_value(client, VT8231_REG_FAN_MIN(nr), data->fan_min[nr]); + vt8231_write_value(data, VT8231_REG_FAN_MIN(nr), data->fan_min[nr]); mutex_unlock(&data->update_lock); return count; } @@ -500,12 +489,11 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct vt8231_data *data = i2c_get_clientdata(client); + struct vt8231_data *data = dev_get_drvdata(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); unsigned long val = simple_strtoul(buf, NULL, 10); int nr = sensor_attr->index; - int old = vt8231_read_value(client, VT8231_REG_FANDIV); + int old = vt8231_read_value(data, VT8231_REG_FANDIV); long min = FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr])); @@ -516,7 +504,7 @@ static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, case 4: data->fan_div[nr] = 2; break; case 8: data->fan_div[nr] = 3; break; default: - dev_err(&client->dev, "fan_div value %ld not supported." + dev_err(dev, "fan_div value %ld not supported." "Choose one of 1, 2, 4 or 8!\n", val); mutex_unlock(&data->update_lock); return -EINVAL; @@ -524,10 +512,10 @@ static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, /* Correct the fan minimum speed */ data->fan_min[nr] = FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); - vt8231_write_value(client, VT8231_REG_FAN_MIN(nr), data->fan_min[nr]); + vt8231_write_value(data, VT8231_REG_FAN_MIN(nr), data->fan_min[nr]); old = (old & 0x0f) | (data->fan_div[1] << 6) | (data->fan_div[0] << 4); - vt8231_write_value(client, VT8231_REG_FANDIV, old); + vt8231_write_value(data, VT8231_REG_FANDIV, old); mutex_unlock(&data->update_lock); return count; } @@ -551,9 +539,16 @@ static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, struct vt8231_data *data = vt8231_update_device(dev); return sprintf(buf, "%d\n", data->alarms); } - static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL); +static ssize_t show_name(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct vt8231_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + static struct attribute *vt8231_attributes_temps[6][4] = { { &dev_attr_temp1_input.attr, @@ -648,6 +643,7 @@ static struct attribute *vt8231_attributes[] = { &sensor_dev_attr_fan1_div.dev_attr.attr, &sensor_dev_attr_fan2_div.dev_attr.attr, &dev_attr_alarms.attr, + &dev_attr_name.attr, NULL }; @@ -655,13 +651,13 @@ static const struct attribute_group vt8231_group = { .attrs = vt8231_attributes, }; -static struct i2c_driver vt8231_driver = { +static struct platform_driver vt8231_driver = { .driver = { .owner = THIS_MODULE, .name = "vt8231", }, - .attach_adapter = vt8231_detect, - .detach_client = vt8231_detach_client, + .probe = vt8231_probe, + .remove = __devexit_p(vt8231_remove), }; static struct pci_device_id vt8231_pci_ids[] = { @@ -680,40 +676,18 @@ static struct pci_driver vt8231_pci_driver = { .probe = vt8231_pci_probe, }; -int vt8231_detect(struct i2c_adapter *adapter) +int vt8231_probe(struct platform_device *pdev) { - struct i2c_client *client; + struct resource *res; struct vt8231_data *data; int err = 0, i; - u16 val; - - /* 8231 requires multiple of 256 */ - if (force_addr) { - isa_address = force_addr & 0xFF00; - dev_warn(&adapter->dev, "forcing ISA address 0x%04X\n", - isa_address); - if (PCIBIOS_SUCCESSFUL != pci_write_config_word(s_bridge, - VT8231_BASE_REG, isa_address)) - return -ENODEV; - } - - if (PCIBIOS_SUCCESSFUL != - pci_read_config_word(s_bridge, VT8231_ENABLE_REG, &val)) - return -ENODEV; - - if (!(val & 0x0001)) { - dev_warn(&adapter->dev, "enabling sensors\n"); - if (PCIBIOS_SUCCESSFUL != - pci_write_config_word(s_bridge, VT8231_ENABLE_REG, - val | 0x0001)) - return -ENODEV; - } /* Reserve the ISA region */ - if (!request_region(isa_address, VT8231_EXTENT, - vt8231_pci_driver.name)) { - dev_err(&adapter->dev, "region 0x%x already in use!\n", - isa_address); + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!request_region(res->start, VT8231_EXTENT, + vt8231_driver.driver.name)) { + dev_err(&pdev->dev, "Region 0x%lx-0x%lx already in use!\n", + (unsigned long)res->start, (unsigned long)res->end); return -ENODEV; } @@ -722,33 +696,23 @@ int vt8231_detect(struct i2c_adapter *adapter) goto exit_release; } - client = &data->client; - i2c_set_clientdata(client, data); - client->addr = isa_address; - client->adapter = adapter; - client->driver = &vt8231_driver; - - /* Fill in the remaining client fields and put into the global list */ - strlcpy(client->name, "vt8231", I2C_NAME_SIZE); + platform_set_drvdata(pdev, data); + data->addr = res->start; + data->name = "vt8231"; mutex_init(&data->update_lock); - - /* Tell the I2C layer a new client has arrived */ - if ((err = i2c_attach_client(client))) - goto exit_free; - - vt8231_init_client(client); + vt8231_init_device(data); /* Register sysfs hooks */ - if ((err = sysfs_create_group(&client->dev.kobj, &vt8231_group))) - goto exit_detach; + if ((err = sysfs_create_group(&pdev->dev.kobj, &vt8231_group))) + goto exit_free; /* Must update device information to find out the config field */ - data->uch_config = vt8231_read_value(client, VT8231_REG_UCH_CONFIG); + data->uch_config = vt8231_read_value(data, VT8231_REG_UCH_CONFIG); for (i = 0; i < ARRAY_SIZE(vt8231_group_temps); i++) { if (ISTEMP(i, data->uch_config)) { - if ((err = sysfs_create_group(&client->dev.kobj, + if ((err = sysfs_create_group(&pdev->dev.kobj, &vt8231_group_temps[i]))) goto exit_remove_files; } @@ -756,13 +720,13 @@ int vt8231_detect(struct i2c_adapter *adapter) for (i = 0; i < ARRAY_SIZE(vt8231_group_volts); i++) { if (ISVOLT(i, data->uch_config)) { - if ((err = sysfs_create_group(&client->dev.kobj, + if ((err = sysfs_create_group(&pdev->dev.kobj, &vt8231_group_volts[i]))) goto exit_remove_files; } } - data->class_dev = hwmon_device_register(&client->dev); + data->class_dev = hwmon_device_register(&pdev->dev); if (IS_ERR(data->class_dev)) { err = PTR_ERR(data->class_dev); goto exit_remove_files; @@ -771,56 +735,52 @@ int vt8231_detect(struct i2c_adapter *adapter) exit_remove_files: for (i = 0; i < ARRAY_SIZE(vt8231_group_volts); i++) - sysfs_remove_group(&client->dev.kobj, &vt8231_group_volts[i]); + sysfs_remove_group(&pdev->dev.kobj, &vt8231_group_volts[i]); for (i = 0; i < ARRAY_SIZE(vt8231_group_temps); i++) - sysfs_remove_group(&client->dev.kobj, &vt8231_group_temps[i]); + sysfs_remove_group(&pdev->dev.kobj, &vt8231_group_temps[i]); + + sysfs_remove_group(&pdev->dev.kobj, &vt8231_group); - sysfs_remove_group(&client->dev.kobj, &vt8231_group); -exit_detach: - i2c_detach_client(client); exit_free: + platform_set_drvdata(pdev, NULL); kfree(data); + exit_release: - release_region(isa_address, VT8231_EXTENT); + release_region(res->start, VT8231_EXTENT); return err; } -static int vt8231_detach_client(struct i2c_client *client) +static int vt8231_remove(struct platform_device *pdev) { - struct vt8231_data *data = i2c_get_clientdata(client); - int err, i; + struct vt8231_data *data = platform_get_drvdata(pdev); + int i; hwmon_device_unregister(data->class_dev); for (i = 0; i < ARRAY_SIZE(vt8231_group_volts); i++) - sysfs_remove_group(&client->dev.kobj, &vt8231_group_volts[i]); + sysfs_remove_group(&pdev->dev.kobj, &vt8231_group_volts[i]); for (i = 0; i < ARRAY_SIZE(vt8231_group_temps); i++) - sysfs_remove_group(&client->dev.kobj, &vt8231_group_temps[i]); + sysfs_remove_group(&pdev->dev.kobj, &vt8231_group_temps[i]); - sysfs_remove_group(&client->dev.kobj, &vt8231_group); + sysfs_remove_group(&pdev->dev.kobj, &vt8231_group); - if ((err = i2c_detach_client(client))) { - return err; - } - - release_region(client->addr, VT8231_EXTENT); + release_region(data->addr, VT8231_EXTENT); + platform_set_drvdata(pdev, NULL); kfree(data); - return 0; } -static void vt8231_init_client(struct i2c_client *client) +static void vt8231_init_device(struct vt8231_data *data) { - vt8231_write_value(client, VT8231_REG_TEMP1_CONFIG, 0); - vt8231_write_value(client, VT8231_REG_TEMP2_CONFIG, 0); + vt8231_write_value(data, VT8231_REG_TEMP1_CONFIG, 0); + vt8231_write_value(data, VT8231_REG_TEMP2_CONFIG, 0); } static struct vt8231_data *vt8231_update_device(struct device *dev) { - struct i2c_client *client = to_i2c_client(dev); - struct vt8231_data *data = i2c_get_clientdata(client); + struct vt8231_data *data = dev_get_drvdata(dev); int i; u16 low; @@ -830,41 +790,41 @@ static struct vt8231_data *vt8231_update_device(struct device *dev) || !data->valid) { for (i = 0; i < 6; i++) { if (ISVOLT(i, data->uch_config)) { - data->in[i] = vt8231_read_value(client, + data->in[i] = vt8231_read_value(data, regvolt[i]); - data->in_min[i] = vt8231_read_value(client, + data->in_min[i] = vt8231_read_value(data, regvoltmin[i]); - data->in_max[i] = vt8231_read_value(client, + data->in_max[i] = vt8231_read_value(data, regvoltmax[i]); } } for (i = 0; i < 2; i++) { - data->fan[i] = vt8231_read_value(client, + data->fan[i] = vt8231_read_value(data, VT8231_REG_FAN(i)); - data->fan_min[i] = vt8231_read_value(client, + data->fan_min[i] = vt8231_read_value(data, VT8231_REG_FAN_MIN(i)); } - low = vt8231_read_value(client, VT8231_REG_TEMP_LOW01); + low = vt8231_read_value(data, VT8231_REG_TEMP_LOW01); low = (low >> 6) | ((low & 0x30) >> 2) - | (vt8231_read_value(client, VT8231_REG_TEMP_LOW25) << 4); + | (vt8231_read_value(data, VT8231_REG_TEMP_LOW25) << 4); for (i = 0; i < 6; i++) { if (ISTEMP(i, data->uch_config)) { - data->temp[i] = (vt8231_read_value(client, + data->temp[i] = (vt8231_read_value(data, regtemp[i]) << 2) | ((low >> (2 * i)) & 0x03); - data->temp_max[i] = vt8231_read_value(client, + data->temp_max[i] = vt8231_read_value(data, regtempmax[i]); - data->temp_min[i] = vt8231_read_value(client, + data->temp_min[i] = vt8231_read_value(data, regtempmin[i]); } } - i = vt8231_read_value(client, VT8231_REG_FANDIV); + i = vt8231_read_value(data, VT8231_REG_FANDIV); data->fan_div[0] = (i >> 4) & 0x03; data->fan_div[1] = i >> 6; - data->alarms = vt8231_read_value(client, VT8231_REG_ALARM1) | - (vt8231_read_value(client, VT8231_REG_ALARM2) << 8); + data->alarms = vt8231_read_value(data, VT8231_REG_ALARM1) | + (vt8231_read_value(data, VT8231_REG_ALARM2) << 8); /* Set alarm flags correctly */ if (!data->fan[0] && data->fan_min[0]) { @@ -888,33 +848,102 @@ static struct vt8231_data *vt8231_update_device(struct device *dev) return data; } +static int __devinit vt8231_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + VT8231_EXTENT - 1, + .name = "vt8231", + .flags = IORESOURCE_IO, + }; + int err; + + pdev = platform_device_alloc("vt8231", address); + if (!pdev) { + err = -ENOMEM; + printk(KERN_ERR "vt8231: Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + printk(KERN_ERR "vt8231: Device resource addition failed " + "(%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + printk(KERN_ERR "vt8231: Device addition failed (%d)\n", + err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + static int __devinit vt8231_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { - u16 val; + u16 address, val; + if (force_addr) { + address = force_addr & 0xff00; + dev_warn(&dev->dev, "Forcing ISA address 0x%x\n", + address); + + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(dev, VT8231_BASE_REG, address | 1)) + return -ENODEV; + } if (PCIBIOS_SUCCESSFUL != pci_read_config_word(dev, VT8231_BASE_REG, &val)) return -ENODEV; - isa_address = val & ~(VT8231_EXTENT - 1); - if (isa_address == 0 && force_addr == 0) { + address = val & ~(VT8231_EXTENT - 1); + if (address == 0) { dev_err(&dev->dev, "base address not set -\ upgrade BIOS or use force_addr=0xaddr\n"); return -ENODEV; } - s_bridge = pci_dev_get(dev); + if (PCIBIOS_SUCCESSFUL != pci_read_config_word(dev, VT8231_ENABLE_REG, + &val)) + return -ENODEV; - if (i2c_isa_add_driver(&vt8231_driver)) { - pci_dev_put(s_bridge); - s_bridge = NULL; + if (!(val & 0x0001)) { + dev_warn(&dev->dev, "enabling sensors\n"); + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(dev, VT8231_ENABLE_REG, + val | 0x0001)) + return -ENODEV; } + if (platform_driver_register(&vt8231_driver)) + goto exit; + + /* Sets global pdev as a side effect */ + if (vt8231_device_add(address)) + goto exit_unregister; + /* Always return failure here. This is to allow other drivers to bind * to this pci device. We don't really want to have control over the * pci device, we only wanted to read as few register values from it. */ + + /* We do, however, mark ourselves as using the PCI device to stop it + getting unloaded. */ + s_bridge = pci_dev_get(dev); + return -ENODEV; + +exit_unregister: + platform_driver_unregister(&vt8231_driver); +exit: return -ENODEV; } @@ -927,7 +956,8 @@ static void __exit sm_vt8231_exit(void) { pci_unregister_driver(&vt8231_pci_driver); if (s_bridge != NULL) { - i2c_isa_del_driver(&vt8231_driver); + platform_device_unregister(pdev); + platform_driver_unregister(&vt8231_driver); pci_dev_put(s_bridge); s_bridge = NULL; } diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 30a7640..c51ae2e 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -41,41 +41,39 @@ #include <linux/module.h> #include <linux/init.h> #include <linux/slab.h> -#include <linux/i2c.h> -#include <linux/i2c-isa.h> +#include <linux/jiffies.h> +#include <linux/platform_device.h> #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> +#include <linux/hwmon-vid.h> #include <linux/err.h> #include <linux/mutex.h> #include <asm/io.h> #include "lm75.h" -/* The actual ISA address is read from Super-I/O configuration space */ -static unsigned short address; +enum kinds { w83627ehf, w83627dhg }; -/* - * Super-I/O constants and functions - */ +/* used to set data->name = w83627ehf_device_names[data->sio_kind] */ +static const char * w83627ehf_device_names[] = { + "w83627ehf", + "w83627dhg", +}; + +#define DRVNAME "w83627ehf" /* - * The three following globals are initialized in w83627ehf_find(), before - * the i2c-isa device is created. Otherwise, they could be stored in - * w83627ehf_data. This is ugly, but necessary, and when the driver is next - * updated to become a platform driver, the globals will disappear. + * Super-I/O constants and functions */ -static int REG; /* The register to read/write */ -static int VAL; /* The value to read/write */ -/* The w83627ehf/ehg have 10 voltage inputs, but the w83627dhg has 9. This - * value is also used in w83627ehf_detect() to export a device name in sysfs - * (e.g. w83627ehf or w83627dhg) */ -static int w83627ehf_num_in; #define W83627EHF_LD_HWM 0x0b #define SIO_REG_LDSEL 0x07 /* Logical device select */ #define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_EN_VRM10 0x2C /* GPIO3, GPIO4 selection */ #define SIO_REG_ENABLE 0x30 /* Logical device enable */ #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ +#define SIO_REG_VID_CTRL 0xF0 /* VID control */ +#define SIO_REG_VID_DATA 0xF1 /* VID data */ #define SIO_W83627EHF_ID 0x8850 #define SIO_W83627EHG_ID 0x8860 @@ -83,38 +81,38 @@ static int w83627ehf_num_in; #define SIO_ID_MASK 0xFFF0 static inline void -superio_outb(int reg, int val) +superio_outb(int ioreg, int reg, int val) { - outb(reg, REG); - outb(val, VAL); + outb(reg, ioreg); + outb(val, ioreg + 1); } static inline int -superio_inb(int reg) +superio_inb(int ioreg, int reg) { - outb(reg, REG); - return inb(VAL); + outb(reg, ioreg); + return inb(ioreg + 1); } static inline void -superio_select(int ld) +superio_select(int ioreg, int ld) { - outb(SIO_REG_LDSEL, REG); - outb(ld, VAL); + outb(SIO_REG_LDSEL, ioreg); + outb(ld, ioreg + 1); } static inline void -superio_enter(void) +superio_enter(int ioreg) { - outb(0x87, REG); - outb(0x87, REG); + outb(0x87, ioreg); + outb(0x87, ioreg); } static inline void -superio_exit(void) +superio_exit(int ioreg) { - outb(0x02, REG); - outb(0x02, VAL); + outb(0x02, ioreg); + outb(0x02, ioreg + 1); } /* @@ -124,8 +122,8 @@ superio_exit(void) #define IOREGION_ALIGNMENT ~7 #define IOREGION_OFFSET 5 #define IOREGION_LENGTH 2 -#define ADDR_REG_OFFSET 5 -#define DATA_REG_OFFSET 6 +#define ADDR_REG_OFFSET 0 +#define DATA_REG_OFFSET 1 #define W83627EHF_REG_BANK 0x4E #define W83627EHF_REG_CONFIG 0x40 @@ -255,7 +253,9 @@ static inline u8 in_to_reg(u32 val, u8 nr) */ struct w83627ehf_data { - struct i2c_client client; + int addr; /* IO base of hw monitor block */ + const char *name; + struct class_device *class_dev; struct mutex lock; @@ -264,6 +264,7 @@ struct w83627ehf_data { unsigned long last_updated; /* In jiffies */ /* Register values */ + u8 in_num; /* number of in inputs we have */ u8 in[10]; /* Register value */ u8 in_max[10]; /* Register value */ u8 in_min[10]; /* Register value */ @@ -271,6 +272,7 @@ struct w83627ehf_data { u8 fan_min[5]; u8 fan_div[5]; u8 has_fan; /* some fan inputs can be disabled */ + u8 temp_type[3]; s8 temp1; s8 temp1_max; s8 temp1_max_hyst; @@ -288,6 +290,14 @@ struct w83627ehf_data { u8 fan_min_output[4]; /* minimum fan speed */ u8 fan_stop_time[4]; + + u8 vid; + u8 vrm; +}; + +struct w83627ehf_sio_data { + int sioreg; + enum kinds kind; }; static inline int is_word_sized(u16 reg) @@ -303,156 +313,152 @@ static inline int is_word_sized(u16 reg) nothing for registers which live in bank 0. For others, they respectively set the bank register to the correct value (before the register is accessed), and back to 0 (afterwards). */ -static inline void w83627ehf_set_bank(struct i2c_client *client, u16 reg) +static inline void w83627ehf_set_bank(struct w83627ehf_data *data, u16 reg) { if (reg & 0xff00) { - outb_p(W83627EHF_REG_BANK, client->addr + ADDR_REG_OFFSET); - outb_p(reg >> 8, client->addr + DATA_REG_OFFSET); + outb_p(W83627EHF_REG_BANK, data->addr + ADDR_REG_OFFSET); + outb_p(reg >> 8, data->addr + DATA_REG_OFFSET); } } -static inline void w83627ehf_reset_bank(struct i2c_client *client, u16 reg) +static inline void w83627ehf_reset_bank(struct w83627ehf_data *data, u16 reg) { if (reg & 0xff00) { - outb_p(W83627EHF_REG_BANK, client->addr + ADDR_REG_OFFSET); - outb_p(0, client->addr + DATA_REG_OFFSET); + outb_p(W83627EHF_REG_BANK, data->addr + ADDR_REG_OFFSET); + outb_p(0, data->addr + DATA_REG_OFFSET); } } -static u16 w83627ehf_read_value(struct i2c_client *client, u16 reg) +static u16 w83627ehf_read_value(struct w83627ehf_data *data, u16 reg) { - struct w83627ehf_data *data = i2c_get_clientdata(client); int res, word_sized = is_word_sized(reg); mutex_lock(&data->lock); - w83627ehf_set_bank(client, reg); - outb_p(reg & 0xff, client->addr + ADDR_REG_OFFSET); - res = inb_p(client->addr + DATA_REG_OFFSET); + w83627ehf_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); + res = inb_p(data->addr + DATA_REG_OFFSET); if (word_sized) { outb_p((reg & 0xff) + 1, - client->addr + ADDR_REG_OFFSET); - res = (res << 8) + inb_p(client->addr + DATA_REG_OFFSET); + data->addr + ADDR_REG_OFFSET); + res = (res << 8) + inb_p(data->addr + DATA_REG_OFFSET); } - w83627ehf_reset_bank(client, reg); + w83627ehf_reset_bank(data, reg); mutex_unlock(&data->lock); return res; } -static int w83627ehf_write_value(struct i2c_client *client, u16 reg, u16 value) +static int w83627ehf_write_value(struct w83627ehf_data *data, u16 reg, u16 value) { - struct w83627ehf_data *data = i2c_get_clientdata(client); int word_sized = is_word_sized(reg); mutex_lock(&data->lock); - w83627ehf_set_bank(client, reg); - outb_p(reg & 0xff, client->addr + ADDR_REG_OFFSET); + w83627ehf_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); if (word_sized) { - outb_p(value >> 8, client->addr + DATA_REG_OFFSET); + outb_p(value >> 8, data->addr + DATA_REG_OFFSET); outb_p((reg & 0xff) + 1, - client->addr + ADDR_REG_OFFSET); + data->addr + ADDR_REG_OFFSET); } - outb_p(value & 0xff, client->addr + DATA_REG_OFFSET); - w83627ehf_reset_bank(client, reg); + outb_p(value & 0xff, data->addr + DATA_REG_OFFSET); + w83627ehf_reset_bank(data, reg); mutex_unlock(&data->lock); return 0; } /* This function assumes that the caller holds data->update_lock */ -static void w83627ehf_write_fan_div(struct i2c_client *client, int nr) +static void w83627ehf_write_fan_div(struct w83627ehf_data *data, int nr) { - struct w83627ehf_data *data = i2c_get_clientdata(client); u8 reg; switch (nr) { case 0: - reg = (w83627ehf_read_value(client, W83627EHF_REG_FANDIV1) & 0xcf) + reg = (w83627ehf_read_value(data, W83627EHF_REG_FANDIV1) & 0xcf) | ((data->fan_div[0] & 0x03) << 4); /* fan5 input control bit is write only, compute the value */ reg |= (data->has_fan & (1 << 4)) ? 1 : 0; - w83627ehf_write_value(client, W83627EHF_REG_FANDIV1, reg); - reg = (w83627ehf_read_value(client, W83627EHF_REG_VBAT) & 0xdf) + w83627ehf_write_value(data, W83627EHF_REG_FANDIV1, reg); + reg = (w83627ehf_read_value(data, W83627EHF_REG_VBAT) & 0xdf) | ((data->fan_div[0] & 0x04) << 3); - w83627ehf_write_value(client, W83627EHF_REG_VBAT, reg); + w83627ehf_write_value(data, W83627EHF_REG_VBAT, reg); break; case 1: - reg = (w83627ehf_read_value(client, W83627EHF_REG_FANDIV1) & 0x3f) + reg = (w83627ehf_read_value(data, W83627EHF_REG_FANDIV1) & 0x3f) | ((data->fan_div[1] & 0x03) << 6); /* fan5 input control bit is write only, compute the value */ reg |= (data->has_fan & (1 << 4)) ? 1 : 0; - w83627ehf_write_value(client, W83627EHF_REG_FANDIV1, reg); - reg = (w83627ehf_read_value(client, W83627EHF_REG_VBAT) & 0xbf) + w83627ehf_write_value(data, W83627EHF_REG_FANDIV1, reg); + reg = (w83627ehf_read_value(data, W83627EHF_REG_VBAT) & 0xbf) | ((data->fan_div[1] & 0x04) << 4); - w83627ehf_write_value(client, W83627EHF_REG_VBAT, reg); + w83627ehf_write_value(data, W83627EHF_REG_VBAT, reg); break; case 2: - reg = (w83627ehf_read_value(client, W83627EHF_REG_FANDIV2) & 0x3f) + reg = (w83627ehf_read_value(data, W83627EHF_REG_FANDIV2) & 0x3f) | ((data->fan_div[2] & 0x03) << 6); - w83627ehf_write_value(client, W83627EHF_REG_FANDIV2, reg); - reg = (w83627ehf_read_value(client, W83627EHF_REG_VBAT) & 0x7f) + w83627ehf_write_value(data, W83627EHF_REG_FANDIV2, reg); + reg = (w83627ehf_read_value(data, W83627EHF_REG_VBAT) & 0x7f) | ((data->fan_div[2] & 0x04) << 5); - w83627ehf_write_value(client, W83627EHF_REG_VBAT, reg); + w83627ehf_write_value(data, W83627EHF_REG_VBAT, reg); break; case 3: - reg = (w83627ehf_read_value(client, W83627EHF_REG_DIODE) & 0xfc) + reg = (w83627ehf_read_value(data, W83627EHF_REG_DIODE) & 0xfc) | (data->fan_div[3] & 0x03); - w83627ehf_write_value(client, W83627EHF_REG_DIODE, reg); - reg = (w83627ehf_read_value(client, W83627EHF_REG_SMI_OVT) & 0x7f) + w83627ehf_write_value(data, W83627EHF_REG_DIODE, reg); + reg = (w83627ehf_read_value(data, W83627EHF_REG_SMI_OVT) & 0x7f) | ((data->fan_div[3] & 0x04) << 5); - w83627ehf_write_value(client, W83627EHF_REG_SMI_OVT, reg); + w83627ehf_write_value(data, W83627EHF_REG_SMI_OVT, reg); break; case 4: - reg = (w83627ehf_read_value(client, W83627EHF_REG_DIODE) & 0x73) + reg = (w83627ehf_read_value(data, W83627EHF_REG_DIODE) & 0x73) | ((data->fan_div[4] & 0x03) << 2) | ((data->fan_div[4] & 0x04) << 5); - w83627ehf_write_value(client, W83627EHF_REG_DIODE, reg); + w83627ehf_write_value(data, W83627EHF_REG_DIODE, reg); break; } } static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) { - struct i2c_client *client = to_i2c_client(dev); - struct w83627ehf_data *data = i2c_get_clientdata(client); + struct w83627ehf_data *data = dev_get_drvdata(dev); int pwmcfg = 0, tolerance = 0; /* shut up the compiler */ int i; mutex_lock(&data->update_lock); - if (time_after(jiffies, data->last_updated + HZ) + if (time_after(jiffies, data->last_updated + HZ + HZ/2) || !data->valid) { /* Fan clock dividers */ - i = w83627ehf_read_value(client, W83627EHF_REG_FANDIV1); + i = w83627ehf_read_value(data, W83627EHF_REG_FANDIV1); data->fan_div[0] = (i >> 4) & 0x03; data->fan_div[1] = (i >> 6) & 0x03; - i = w83627ehf_read_value(client, W83627EHF_REG_FANDIV2); + i = w83627ehf_read_value(data, W83627EHF_REG_FANDIV2); data->fan_div[2] = (i >> 6) & 0x03; - i = w83627ehf_read_value(client, W83627EHF_REG_VBAT); + i = w83627ehf_read_value(data, W83627EHF_REG_VBAT); data->fan_div[0] |= (i >> 3) & 0x04; data->fan_div[1] |= (i >> 4) & 0x04; data->fan_div[2] |= (i >> 5) & 0x04; if (data->has_fan & ((1 << 3) | (1 << 4))) { - i = w83627ehf_read_value(client, W83627EHF_REG_DIODE); + i = w83627ehf_read_value(data, W83627EHF_REG_DIODE); data->fan_div[3] = i & 0x03; data->fan_div[4] = ((i >> 2) & 0x03) | ((i >> 5) & 0x04); } if (data->has_fan & (1 << 3)) { - i = w83627ehf_read_value(client, W83627EHF_REG_SMI_OVT); + i = w83627ehf_read_value(data, W83627EHF_REG_SMI_OVT); data->fan_div[3] |= (i >> 5) & 0x04; } /* Measured voltages and limits */ - for (i = 0; i < w83627ehf_num_in; i++) { - data->in[i] = w83627ehf_read_value(client, + for (i = 0; i < data->in_num; i++) { + data->in[i] = w83627ehf_read_value(data, W83627EHF_REG_IN(i)); - data->in_min[i] = w83627ehf_read_value(client, + data->in_min[i] = w83627ehf_read_value(data, W83627EHF_REG_IN_MIN(i)); - data->in_max[i] = w83627ehf_read_value(client, + data->in_max[i] = w83627ehf_read_value(data, W83627EHF_REG_IN_MAX(i)); } @@ -461,9 +467,9 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) if (!(data->has_fan & (1 << i))) continue; - data->fan[i] = w83627ehf_read_value(client, + data->fan[i] = w83627ehf_read_value(data, W83627EHF_REG_FAN[i]); - data->fan_min[i] = w83627ehf_read_value(client, + data->fan_min[i] = w83627ehf_read_value(data, W83627EHF_REG_FAN_MIN[i]); /* If we failed to measure the fan speed and clock @@ -471,16 +477,16 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) time */ if (data->fan[i] == 0xff && data->fan_div[i] < 0x07) { - dev_dbg(&client->dev, "Increasing fan%d " + dev_dbg(dev, "Increasing fan%d " "clock divider from %u to %u\n", i + 1, div_from_reg(data->fan_div[i]), div_from_reg(data->fan_div[i] + 1)); data->fan_div[i]++; - w83627ehf_write_fan_div(client, i); + w83627ehf_write_fan_div(data, i); /* Preserve min limit if possible */ if (data->fan_min[i] >= 2 && data->fan_min[i] != 255) - w83627ehf_write_value(client, + w83627ehf_write_value(data, W83627EHF_REG_FAN_MIN[i], (data->fan_min[i] /= 2)); } @@ -489,9 +495,9 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) for (i = 0; i < 4; i++) { /* pwmcfg, tolarance mapped for i=0, i=1 to same reg */ if (i != 1) { - pwmcfg = w83627ehf_read_value(client, + pwmcfg = w83627ehf_read_value(data, W83627EHF_REG_PWM_ENABLE[i]); - tolerance = w83627ehf_read_value(client, + tolerance = w83627ehf_read_value(data, W83627EHF_REG_TOLERANCE[i]); } data->pwm_mode[i] = @@ -500,14 +506,14 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) data->pwm_enable[i] = ((pwmcfg >> W83627EHF_PWM_ENABLE_SHIFT[i]) & 3) + 1; - data->pwm[i] = w83627ehf_read_value(client, + data->pwm[i] = w83627ehf_read_value(data, W83627EHF_REG_PWM[i]); - data->fan_min_output[i] = w83627ehf_read_value(client, + data->fan_min_output[i] = w83627ehf_read_value(data, W83627EHF_REG_FAN_MIN_OUTPUT[i]); - data->fan_stop_time[i] = w83627ehf_read_value(client, + data->fan_stop_time[i] = w83627ehf_read_value(data, W83627EHF_REG_FAN_STOP_TIME[i]); data->target_temp[i] = - w83627ehf_read_value(client, + w83627ehf_read_value(data, W83627EHF_REG_TARGET[i]) & (data->pwm_mode[i] == 1 ? 0x7f : 0xff); data->tolerance[i] = (tolerance >> (i == 1 ? 4 : 0)) @@ -515,26 +521,26 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) } /* Measured temperatures and limits */ - data->temp1 = w83627ehf_read_value(client, + data->temp1 = w83627ehf_read_value(data, W83627EHF_REG_TEMP1); - data->temp1_max = w83627ehf_read_value(client, + data->temp1_max = w83627ehf_read_value(data, W83627EHF_REG_TEMP1_OVER); - data->temp1_max_hyst = w83627ehf_read_value(client, + data->temp1_max_hyst = w83627ehf_read_value(data, W83627EHF_REG_TEMP1_HYST); for (i = 0; i < 2; i++) { - data->temp[i] = w83627ehf_read_value(client, + data->temp[i] = w83627ehf_read_value(data, W83627EHF_REG_TEMP[i]); - data->temp_max[i] = w83627ehf_read_value(client, + data->temp_max[i] = w83627ehf_read_value(data, W83627EHF_REG_TEMP_OVER[i]); - data->temp_max_hyst[i] = w83627ehf_read_value(client, + data->temp_max_hyst[i] = w83627ehf_read_value(data, W83627EHF_REG_TEMP_HYST[i]); } - data->alarms = w83627ehf_read_value(client, + data->alarms = w83627ehf_read_value(data, W83627EHF_REG_ALARM1) | - (w83627ehf_read_value(client, + (w83627ehf_read_value(data, W83627EHF_REG_ALARM2) << 8) | - (w83627ehf_read_value(client, + (w83627ehf_read_value(data, W83627EHF_REG_ALARM3) << 16); data->last_updated = jiffies; @@ -567,15 +573,14 @@ static ssize_t \ store_in_##reg (struct device *dev, struct device_attribute *attr, \ const char *buf, size_t count) \ { \ - struct i2c_client *client = to_i2c_client(dev); \ - struct w83627ehf_data *data = i2c_get_clientdata(client); \ + struct w83627ehf_data *data = dev_get_drvdata(dev); \ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ u32 val = simple_strtoul(buf, NULL, 10); \ \ mutex_lock(&data->update_lock); \ data->in_##reg[nr] = in_to_reg(val, nr); \ - w83627ehf_write_value(client, W83627EHF_REG_IN_##REG(nr), \ + w83627ehf_write_value(data, W83627EHF_REG_IN_##REG(nr), \ data->in_##reg[nr]); \ mutex_unlock(&data->update_lock); \ return count; \ @@ -673,8 +678,7 @@ static ssize_t store_fan_min(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct w83627ehf_data *data = i2c_get_clientdata(client); + struct w83627ehf_data *data = dev_get_drvdata(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; unsigned int val = simple_strtoul(buf, NULL, 10); @@ -716,18 +720,25 @@ store_fan_min(struct device *dev, struct device_attribute *attr, /* Write both the fan clock divider (if it changed) and the new fan min (unconditionally) */ if (new_div != data->fan_div[nr]) { - if (new_div > data->fan_div[nr]) - data->fan[nr] >>= (data->fan_div[nr] - new_div); - else - data->fan[nr] <<= (new_div - data->fan_div[nr]); + /* Preserve the fan speed reading */ + if (data->fan[nr] != 0xff) { + if (new_div > data->fan_div[nr]) + data->fan[nr] >>= new_div - data->fan_div[nr]; + else if (data->fan[nr] & 0x80) + data->fan[nr] = 0xff; + else + data->fan[nr] <<= data->fan_div[nr] - new_div; + } dev_dbg(dev, "fan%u clock divider changed from %u to %u\n", nr + 1, div_from_reg(data->fan_div[nr]), div_from_reg(new_div)); data->fan_div[nr] = new_div; - w83627ehf_write_fan_div(client, nr); + w83627ehf_write_fan_div(data, nr); + /* Give the chip time to sample a new speed value */ + data->last_updated = jiffies; } - w83627ehf_write_value(client, W83627EHF_REG_FAN_MIN[nr], + w83627ehf_write_value(data, W83627EHF_REG_FAN_MIN[nr], data->fan_min[nr]); mutex_unlock(&data->update_lock); @@ -788,13 +799,12 @@ static ssize_t \ store_temp1_##reg(struct device *dev, struct device_attribute *attr, \ const char *buf, size_t count) \ { \ - struct i2c_client *client = to_i2c_client(dev); \ - struct w83627ehf_data *data = i2c_get_clientdata(client); \ + struct w83627ehf_data *data = dev_get_drvdata(dev); \ u32 val = simple_strtoul(buf, NULL, 10); \ \ mutex_lock(&data->update_lock); \ data->temp1_##reg = temp1_to_reg(val, -128000, 127000); \ - w83627ehf_write_value(client, W83627EHF_REG_TEMP1_##REG, \ + w83627ehf_write_value(data, W83627EHF_REG_TEMP1_##REG, \ data->temp1_##reg); \ mutex_unlock(&data->update_lock); \ return count; \ @@ -822,15 +832,14 @@ static ssize_t \ store_##reg(struct device *dev, struct device_attribute *attr, \ const char *buf, size_t count) \ { \ - struct i2c_client *client = to_i2c_client(dev); \ - struct w83627ehf_data *data = i2c_get_clientdata(client); \ + struct w83627ehf_data *data = dev_get_drvdata(dev); \ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ u32 val = simple_strtoul(buf, NULL, 10); \ \ mutex_lock(&data->update_lock); \ data->reg[nr] = LM75_TEMP_TO_REG(val); \ - w83627ehf_write_value(client, W83627EHF_REG_TEMP_##REG[nr], \ + w83627ehf_write_value(data, W83627EHF_REG_TEMP_##REG[nr], \ data->reg[nr]); \ mutex_unlock(&data->update_lock); \ return count; \ @@ -838,6 +847,15 @@ store_##reg(struct device *dev, struct device_attribute *attr, \ store_temp_reg(OVER, temp_max); store_temp_reg(HYST, temp_max_hyst); +static ssize_t +show_temp_type(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + return sprintf(buf, "%d\n", (int)data->temp_type[nr]); +} + static struct sensor_device_attribute sda_temp[] = { SENSOR_ATTR(temp1_input, S_IRUGO, show_temp1, NULL, 0), SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 0), @@ -857,6 +875,9 @@ static struct sensor_device_attribute sda_temp[] = { SENSOR_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4), SENSOR_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 5), SENSOR_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13), + SENSOR_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0), + SENSOR_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1), + SENSOR_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2), }; #define show_pwm_reg(reg) \ @@ -877,8 +898,7 @@ static ssize_t store_pwm_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct w83627ehf_data *data = i2c_get_clientdata(client); + struct w83627ehf_data *data = dev_get_drvdata(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; u32 val = simple_strtoul(buf, NULL, 10); @@ -887,12 +907,12 @@ store_pwm_mode(struct device *dev, struct device_attribute *attr, if (val > 1) return -EINVAL; mutex_lock(&data->update_lock); - reg = w83627ehf_read_value(client, W83627EHF_REG_PWM_ENABLE[nr]); + reg = w83627ehf_read_value(data, W83627EHF_REG_PWM_ENABLE[nr]); data->pwm_mode[nr] = val; reg &= ~(1 << W83627EHF_PWM_MODE_SHIFT[nr]); if (!val) reg |= 1 << W83627EHF_PWM_MODE_SHIFT[nr]; - w83627ehf_write_value(client, W83627EHF_REG_PWM_ENABLE[nr], reg); + w83627ehf_write_value(data, W83627EHF_REG_PWM_ENABLE[nr], reg); mutex_unlock(&data->update_lock); return count; } @@ -901,15 +921,14 @@ static ssize_t store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct w83627ehf_data *data = i2c_get_clientdata(client); + struct w83627ehf_data *data = dev_get_drvdata(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; u32 val = SENSORS_LIMIT(simple_strtoul(buf, NULL, 10), 0, 255); mutex_lock(&data->update_lock); data->pwm[nr] = val; - w83627ehf_write_value(client, W83627EHF_REG_PWM[nr], val); + w83627ehf_write_value(data, W83627EHF_REG_PWM[nr], val); mutex_unlock(&data->update_lock); return count; } @@ -918,8 +937,7 @@ static ssize_t store_pwm_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct w83627ehf_data *data = i2c_get_clientdata(client); + struct w83627ehf_data *data = dev_get_drvdata(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; u32 val = simple_strtoul(buf, NULL, 10); @@ -928,11 +946,11 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, if (!val || (val > 2)) /* only modes 1 and 2 are supported */ return -EINVAL; mutex_lock(&data->update_lock); - reg = w83627ehf_read_value(client, W83627EHF_REG_PWM_ENABLE[nr]); + reg = w83627ehf_read_value(data, W83627EHF_REG_PWM_ENABLE[nr]); data->pwm_enable[nr] = val; reg &= ~(0x03 << W83627EHF_PWM_ENABLE_SHIFT[nr]); reg |= (val - 1) << W83627EHF_PWM_ENABLE_SHIFT[nr]; - w83627ehf_write_value(client, W83627EHF_REG_PWM_ENABLE[nr], reg); + w83627ehf_write_value(data, W83627EHF_REG_PWM_ENABLE[nr], reg); mutex_unlock(&data->update_lock); return count; } @@ -955,15 +973,14 @@ static ssize_t store_target_temp(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct w83627ehf_data *data = i2c_get_clientdata(client); + struct w83627ehf_data *data = dev_get_drvdata(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; u8 val = temp1_to_reg(simple_strtoul(buf, NULL, 10), 0, 127000); mutex_lock(&data->update_lock); data->target_temp[nr] = val; - w83627ehf_write_value(client, W83627EHF_REG_TARGET[nr], val); + w83627ehf_write_value(data, W83627EHF_REG_TARGET[nr], val); mutex_unlock(&data->update_lock); return count; } @@ -972,8 +989,7 @@ static ssize_t store_tolerance(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct w83627ehf_data *data = i2c_get_clientdata(client); + struct w83627ehf_data *data = dev_get_drvdata(dev); struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; u16 reg; @@ -981,13 +997,13 @@ store_tolerance(struct device *dev, struct device_attribute *attr, u8 val = temp1_to_reg(simple_strtoul(buf, NULL, 10), 0, 15000); mutex_lock(&data->update_lock); - reg = w83627ehf_read_value(client, W83627EHF_REG_TOLERANCE[nr]); + reg = w83627ehf_read_value(data, W83627EHF_REG_TOLERANCE[nr]); data->tolerance[nr] = val; if (nr == 1) reg = (reg & 0x0f) | (val << 4); else reg = (reg & 0xf0) | val; - w83627ehf_write_value(client, W83627EHF_REG_TOLERANCE[nr], reg); + w83627ehf_write_value(data, W83627EHF_REG_TOLERANCE[nr], reg); mutex_unlock(&data->update_lock); return count; } @@ -1058,14 +1074,13 @@ static ssize_t \ store_##reg(struct device *dev, struct device_attribute *attr, \ const char *buf, size_t count) \ {\ - struct i2c_client *client = to_i2c_client(dev); \ - struct w83627ehf_data *data = i2c_get_clientdata(client); \ + struct w83627ehf_data *data = dev_get_drvdata(dev); \ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ u32 val = SENSORS_LIMIT(simple_strtoul(buf, NULL, 10), 1, 255); \ mutex_lock(&data->update_lock); \ data->reg[nr] = val; \ - w83627ehf_write_value(client, W83627EHF_REG_##REG[nr], val); \ + w83627ehf_write_value(data, W83627EHF_REG_##REG[nr], val); \ mutex_unlock(&data->update_lock); \ return count; \ } @@ -1087,21 +1102,28 @@ static ssize_t \ store_##reg(struct device *dev, struct device_attribute *attr, \ const char *buf, size_t count) \ { \ - struct i2c_client *client = to_i2c_client(dev); \ - struct w83627ehf_data *data = i2c_get_clientdata(client); \ + struct w83627ehf_data *data = dev_get_drvdata(dev); \ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); \ int nr = sensor_attr->index; \ u8 val = step_time_to_reg(simple_strtoul(buf, NULL, 10), \ data->pwm_mode[nr]); \ mutex_lock(&data->update_lock); \ data->reg[nr] = val; \ - w83627ehf_write_value(client, W83627EHF_REG_##REG[nr], val); \ + w83627ehf_write_value(data, W83627EHF_REG_##REG[nr], val); \ mutex_unlock(&data->update_lock); \ return count; \ } \ fan_time_functions(fan_stop_time, FAN_STOP_TIME) +static ssize_t show_name(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); static struct sensor_device_attribute sda_sf3_arrays_fan4[] = { SENSOR_ATTR(pwm4_stop_time, S_IWUSR | S_IRUGO, show_fan_stop_time, @@ -1125,8 +1147,16 @@ static struct sensor_device_attribute sda_sf3_arrays[] = { store_fan_min_output, 2), }; +static ssize_t +show_vid(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} +static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid, NULL); + /* - * Driver and client management + * Driver and device management */ static void w83627ehf_device_remove_files(struct device *dev) @@ -1134,12 +1164,13 @@ static void w83627ehf_device_remove_files(struct device *dev) /* some entries in the following arrays may not have been used in * device_create_file(), but device_remove_file() will ignore them */ int i; + struct w83627ehf_data *data = dev_get_drvdata(dev); for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays); i++) device_remove_file(dev, &sda_sf3_arrays[i].dev_attr); for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan4); i++) device_remove_file(dev, &sda_sf3_arrays_fan4[i].dev_attr); - for (i = 0; i < w83627ehf_num_in; i++) { + for (i = 0; i < data->in_num; i++) { device_remove_file(dev, &sda_in_input[i].dev_attr); device_remove_file(dev, &sda_in_alarm[i].dev_attr); device_remove_file(dev, &sda_in_min[i].dev_attr); @@ -1160,43 +1191,64 @@ static void w83627ehf_device_remove_files(struct device *dev) } for (i = 0; i < ARRAY_SIZE(sda_temp); i++) device_remove_file(dev, &sda_temp[i].dev_attr); -} -static struct i2c_driver w83627ehf_driver; + device_remove_file(dev, &dev_attr_name); + if (data->vid != 0x3f) + device_remove_file(dev, &dev_attr_cpu0_vid); +} -static void w83627ehf_init_client(struct i2c_client *client) +/* Get the monitoring functions started */ +static inline void __devinit w83627ehf_init_device(struct w83627ehf_data *data) { int i; - u8 tmp; + u8 tmp, diode; /* Start monitoring is needed */ - tmp = w83627ehf_read_value(client, W83627EHF_REG_CONFIG); + tmp = w83627ehf_read_value(data, W83627EHF_REG_CONFIG); if (!(tmp & 0x01)) - w83627ehf_write_value(client, W83627EHF_REG_CONFIG, + w83627ehf_write_value(data, W83627EHF_REG_CONFIG, tmp | 0x01); /* Enable temp2 and temp3 if needed */ for (i = 0; i < 2; i++) { - tmp = w83627ehf_read_value(client, + tmp = w83627ehf_read_value(data, W83627EHF_REG_TEMP_CONFIG[i]); if (tmp & 0x01) - w83627ehf_write_value(client, + w83627ehf_write_value(data, W83627EHF_REG_TEMP_CONFIG[i], tmp & 0xfe); } + + /* Enable VBAT monitoring if needed */ + tmp = w83627ehf_read_value(data, W83627EHF_REG_VBAT); + if (!(tmp & 0x01)) + w83627ehf_write_value(data, W83627EHF_REG_VBAT, tmp | 0x01); + + /* Get thermal sensor types */ + diode = w83627ehf_read_value(data, W83627EHF_REG_DIODE); + for (i = 0; i < 3; i++) { + if ((tmp & (0x02 << i))) + data->temp_type[i] = (diode & (0x10 << i)) ? 1 : 2; + else + data->temp_type[i] = 4; /* thermistor */ + } } -static int w83627ehf_detect(struct i2c_adapter *adapter) +static int __devinit w83627ehf_probe(struct platform_device *pdev) { - struct i2c_client *client; + struct device *dev = &pdev->dev; + struct w83627ehf_sio_data *sio_data = dev->platform_data; struct w83627ehf_data *data; - struct device *dev; - u8 fan4pin, fan5pin; + struct resource *res; + u8 fan4pin, fan5pin, en_vrm10; int i, err = 0; - if (!request_region(address + IOREGION_OFFSET, IOREGION_LENGTH, - w83627ehf_driver.driver.name)) { + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!request_region(res->start, IOREGION_LENGTH, DRVNAME)) { err = -EBUSY; + dev_err(dev, "Failed to request region 0x%lx-0x%lx\n", + (unsigned long)res->start, + (unsigned long)res->start + IOREGION_LENGTH - 1); goto exit; } @@ -1205,41 +1257,47 @@ static int w83627ehf_detect(struct i2c_adapter *adapter) goto exit_release; } - client = &data->client; - i2c_set_clientdata(client, data); - client->addr = address; + data->addr = res->start; mutex_init(&data->lock); - client->adapter = adapter; - client->driver = &w83627ehf_driver; - client->flags = 0; - dev = &client->dev; - - if (w83627ehf_num_in == 9) - strlcpy(client->name, "w83627dhg", I2C_NAME_SIZE); - else /* just say ehf. 627EHG is 627EHF in lead-free packaging. */ - strlcpy(client->name, "w83627ehf", I2C_NAME_SIZE); - - data->valid = 0; mutex_init(&data->update_lock); + data->name = w83627ehf_device_names[sio_data->kind]; + platform_set_drvdata(pdev, data); - /* Tell the i2c layer a new client has arrived */ - if ((err = i2c_attach_client(client))) - goto exit_free; + /* 627EHG and 627EHF have 10 voltage inputs; DHG has 9 */ + data->in_num = (sio_data->kind == w83627dhg) ? 9 : 10; /* Initialize the chip */ - w83627ehf_init_client(client); - - /* A few vars need to be filled upon startup */ - for (i = 0; i < 5; i++) - data->fan_min[i] = w83627ehf_read_value(client, - W83627EHF_REG_FAN_MIN[i]); + w83627ehf_init_device(data); + + data->vrm = vid_which_vrm(); + superio_enter(sio_data->sioreg); + /* Set VID input sensibility if needed. In theory the BIOS should + have set it, but in practice it's not always the case. */ + en_vrm10 = superio_inb(sio_data->sioreg, SIO_REG_EN_VRM10); + if ((en_vrm10 & 0x08) && data->vrm != 100) { + dev_warn(dev, "Setting VID input voltage to TTL\n"); + superio_outb(sio_data->sioreg, SIO_REG_EN_VRM10, + en_vrm10 & ~0x08); + } else if (!(en_vrm10 & 0x08) && data->vrm == 100) { + dev_warn(dev, "Setting VID input voltage to VRM10\n"); + superio_outb(sio_data->sioreg, SIO_REG_EN_VRM10, + en_vrm10 | 0x08); + } + /* Read VID value */ + superio_select(sio_data->sioreg, W83627EHF_LD_HWM); + if (superio_inb(sio_data->sioreg, SIO_REG_VID_CTRL) & 0x80) + data->vid = superio_inb(sio_data->sioreg, SIO_REG_VID_DATA) & 0x3f; + else { + dev_info(dev, "VID pins in output mode, CPU VID not " + "available\n"); + data->vid = 0x3f; + } /* fan4 and fan5 share some pins with the GPIO and serial flash */ - superio_enter(); - fan5pin = superio_inb(0x24) & 0x2; - fan4pin = superio_inb(0x29) & 0x6; - superio_exit(); + fan5pin = superio_inb(sio_data->sioreg, 0x24) & 0x2; + fan4pin = superio_inb(sio_data->sioreg, 0x29) & 0x6; + superio_exit(sio_data->sioreg); /* It looks like fan4 and fan5 pins can be alternatively used as fan on/off switches, but fan5 control is write only :/ @@ -1248,7 +1306,7 @@ static int w83627ehf_detect(struct i2c_adapter *adapter) is not the default. */ data->has_fan = 0x07; /* fan1, fan2 and fan3 */ - i = w83627ehf_read_value(client, W83627EHF_REG_FANDIV1); + i = w83627ehf_read_value(data, W83627EHF_REG_FANDIV1); if ((i & (1 << 2)) && (!fan4pin)) data->has_fan |= (1 << 3); if (!(i & (1 << 1)) && (!fan5pin)) @@ -1268,7 +1326,7 @@ static int w83627ehf_detect(struct i2c_adapter *adapter) goto exit_remove; } - for (i = 0; i < w83627ehf_num_in; i++) + for (i = 0; i < data->in_num; i++) if ((err = device_create_file(dev, &sda_in_input[i].dev_attr)) || (err = device_create_file(dev, &sda_in_alarm[i].dev_attr)) @@ -1308,6 +1366,16 @@ static int w83627ehf_detect(struct i2c_adapter *adapter) if ((err = device_create_file(dev, &sda_temp[i].dev_attr))) goto exit_remove; + err = device_create_file(dev, &dev_attr_name); + if (err) + goto exit_remove; + + if (data->vid != 0x3f) { + err = device_create_file(dev, &dev_attr_cpu0_vid); + if (err) + goto exit_remove; + } + data->class_dev = hwmon_device_register(dev); if (IS_ERR(data->class_dev)) { err = PTR_ERR(data->class_dev); @@ -1318,95 +1386,172 @@ static int w83627ehf_detect(struct i2c_adapter *adapter) exit_remove: w83627ehf_device_remove_files(dev); - i2c_detach_client(client); -exit_free: kfree(data); + platform_set_drvdata(pdev, NULL); exit_release: - release_region(address + IOREGION_OFFSET, IOREGION_LENGTH); + release_region(res->start, IOREGION_LENGTH); exit: return err; } -static int w83627ehf_detach_client(struct i2c_client *client) +static int __devexit w83627ehf_remove(struct platform_device *pdev) { - struct w83627ehf_data *data = i2c_get_clientdata(client); - int err; + struct w83627ehf_data *data = platform_get_drvdata(pdev); hwmon_device_unregister(data->class_dev); - w83627ehf_device_remove_files(&client->dev); - - if ((err = i2c_detach_client(client))) - return err; - release_region(client->addr + IOREGION_OFFSET, IOREGION_LENGTH); + w83627ehf_device_remove_files(&pdev->dev); + release_region(data->addr, IOREGION_LENGTH); + platform_set_drvdata(pdev, NULL); kfree(data); return 0; } -static struct i2c_driver w83627ehf_driver = { +static struct platform_driver w83627ehf_driver = { .driver = { .owner = THIS_MODULE, - .name = "w83627ehf", + .name = DRVNAME, }, - .attach_adapter = w83627ehf_detect, - .detach_client = w83627ehf_detach_client, + .probe = w83627ehf_probe, + .remove = __devexit_p(w83627ehf_remove), }; -static int __init w83627ehf_find(int sioaddr, unsigned short *addr) +/* w83627ehf_find() looks for a '627 in the Super-I/O config space */ +static int __init w83627ehf_find(int sioaddr, unsigned short *addr, + struct w83627ehf_sio_data *sio_data) { + static const char __initdata sio_name_W83627EHF[] = "W83627EHF"; + static const char __initdata sio_name_W83627EHG[] = "W83627EHG"; + static const char __initdata sio_name_W83627DHG[] = "W83627DHG"; + u16 val; + const char *sio_name; - REG = sioaddr; - VAL = sioaddr + 1; - superio_enter(); + superio_enter(sioaddr); - val = (superio_inb(SIO_REG_DEVID) << 8) - | superio_inb(SIO_REG_DEVID + 1); + val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) + | superio_inb(sioaddr, SIO_REG_DEVID + 1); switch (val & SIO_ID_MASK) { - case SIO_W83627DHG_ID: - w83627ehf_num_in = 9; - break; case SIO_W83627EHF_ID: + sio_data->kind = w83627ehf; + sio_name = sio_name_W83627EHF; + break; case SIO_W83627EHG_ID: - w83627ehf_num_in = 10; + sio_data->kind = w83627ehf; + sio_name = sio_name_W83627EHG; + break; + case SIO_W83627DHG_ID: + sio_data->kind = w83627dhg; + sio_name = sio_name_W83627DHG; break; default: - printk(KERN_WARNING "w83627ehf: unsupported chip ID: 0x%04x\n", - val); - superio_exit(); + if (val != 0xffff) + pr_debug(DRVNAME ": unsupported chip ID: 0x%04x\n", + val); + superio_exit(sioaddr); return -ENODEV; } - superio_select(W83627EHF_LD_HWM); - val = (superio_inb(SIO_REG_ADDR) << 8) - | superio_inb(SIO_REG_ADDR + 1); + /* We have a known chip, find the HWM I/O address */ + superio_select(sioaddr, W83627EHF_LD_HWM); + val = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) + | superio_inb(sioaddr, SIO_REG_ADDR + 1); *addr = val & IOREGION_ALIGNMENT; if (*addr == 0) { - superio_exit(); + printk(KERN_ERR DRVNAME ": Refusing to enable a Super-I/O " + "device with a base I/O port 0.\n"); + superio_exit(sioaddr); return -ENODEV; } /* Activate logical device if needed */ - val = superio_inb(SIO_REG_ENABLE); - if (!(val & 0x01)) - superio_outb(SIO_REG_ENABLE, val | 0x01); + val = superio_inb(sioaddr, SIO_REG_ENABLE); + if (!(val & 0x01)) { + printk(KERN_WARNING DRVNAME ": Forcibly enabling Super-I/O. " + "Sensor is probably unusable.\n"); + superio_outb(sioaddr, SIO_REG_ENABLE, val | 0x01); + } + + superio_exit(sioaddr); + pr_info(DRVNAME ": Found %s chip at %#x\n", sio_name, *addr); + sio_data->sioreg = sioaddr; - superio_exit(); return 0; } +/* when Super-I/O functions move to a separate file, the Super-I/O + * bus will manage the lifetime of the device and this module will only keep + * track of the w83627ehf driver. But since we platform_device_alloc(), we + * must keep track of the device */ +static struct platform_device *pdev; + static int __init sensors_w83627ehf_init(void) { - if (w83627ehf_find(0x2e, &address) - && w83627ehf_find(0x4e, &address)) + int err; + unsigned short address; + struct resource res; + struct w83627ehf_sio_data sio_data; + + /* initialize sio_data->kind and sio_data->sioreg. + * + * when Super-I/O functions move to a separate file, the Super-I/O + * driver will probe 0x2e and 0x4e and auto-detect the presence of a + * w83627ehf hardware monitor, and call probe() */ + if (w83627ehf_find(0x2e, &address, &sio_data) && + w83627ehf_find(0x4e, &address, &sio_data)) return -ENODEV; - return i2c_isa_add_driver(&w83627ehf_driver); + err = platform_driver_register(&w83627ehf_driver); + if (err) + goto exit; + + if (!(pdev = platform_device_alloc(DRVNAME, address))) { + err = -ENOMEM; + printk(KERN_ERR DRVNAME ": Device allocation failed\n"); + goto exit_unregister; + } + + err = platform_device_add_data(pdev, &sio_data, + sizeof(struct w83627ehf_sio_data)); + if (err) { + printk(KERN_ERR DRVNAME ": Platform data allocation failed\n"); + goto exit_device_put; + } + + memset(&res, 0, sizeof(res)); + res.name = DRVNAME; + res.start = address + IOREGION_OFFSET; + res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; + res.flags = IORESOURCE_IO; + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + printk(KERN_ERR DRVNAME ": Device resource addition failed " + "(%d)\n", err); + goto exit_device_put; + } + + /* platform_device_add calls probe() */ + err = platform_device_add(pdev); + if (err) { + printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n", + err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit_unregister: + platform_driver_unregister(&w83627ehf_driver); +exit: + return err; } static void __exit sensors_w83627ehf_exit(void) { - i2c_isa_del_driver(&w83627ehf_driver); + platform_device_unregister(pdev); + platform_driver_unregister(&w83627ehf_driver); } MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); diff --git a/drivers/hwmon/w83627hf.c b/drivers/hwmon/w83627hf.c index 12cb40a..1ce7817 100644 --- a/drivers/hwmon/w83627hf.c +++ b/drivers/hwmon/w83627hf.c @@ -220,6 +220,18 @@ static const u8 regpwm[] = { W83627THF_REG_PWM1, W83627THF_REG_PWM2, #define W836X7HF_REG_PWM(type, nr) (((type) == w83627hf) ? \ regpwm_627hf[(nr) - 1] : regpwm[(nr) - 1]) +#define W83627HF_REG_PWM_FREQ 0x5C /* Only for the 627HF */ + +#define W83637HF_REG_PWM_FREQ1 0x00 /* 697HF/687THF too */ +#define W83637HF_REG_PWM_FREQ2 0x02 /* 697HF/687THF too */ +#define W83637HF_REG_PWM_FREQ3 0x10 /* 687THF too */ + +static const u8 W83637HF_REG_PWM_FREQ[] = { W83637HF_REG_PWM_FREQ1, + W83637HF_REG_PWM_FREQ2, + W83637HF_REG_PWM_FREQ3 }; + +#define W83627HF_BASE_PWM_FREQ 46870 + #define W83781D_REG_I2C_ADDR 0x48 #define W83781D_REG_I2C_SUBADDR 0x4A @@ -267,6 +279,49 @@ static int TEMP_FROM_REG(u8 reg) #define PWM_TO_REG(val) (SENSORS_LIMIT((val),0,255)) +static inline unsigned long pwm_freq_from_reg_627hf(u8 reg) +{ + unsigned long freq; + freq = W83627HF_BASE_PWM_FREQ >> reg; + return freq; +} +static inline u8 pwm_freq_to_reg_627hf(unsigned long val) +{ + u8 i; + /* Only 5 dividers (1 2 4 8 16) + Search for the nearest available frequency */ + for (i = 0; i < 4; i++) { + if (val > (((W83627HF_BASE_PWM_FREQ >> i) + + (W83627HF_BASE_PWM_FREQ >> (i+1))) / 2)) + break; + } + return i; +} + +static inline unsigned long pwm_freq_from_reg(u8 reg) +{ + /* Clock bit 8 -> 180 kHz or 24 MHz */ + unsigned long clock = (reg & 0x80) ? 180000UL : 24000000UL; + + reg &= 0x7f; + /* This should not happen but anyway... */ + if (reg == 0) + reg++; + return (clock / (reg << 8)); +} +static inline u8 pwm_freq_to_reg(unsigned long val) +{ + /* Minimum divider value is 0x01 and maximum is 0x7F */ + if (val >= 93750) /* The highest we can do */ + return 0x01; + if (val >= 720) /* Use 24 MHz clock */ + return (24000000UL / (val << 8)); + if (val < 6) /* The lowest we can do */ + return 0xFF; + else /* Use 180 kHz clock */ + return (0x80 | (180000UL / (val << 8))); +} + #define BEEP_MASK_FROM_REG(val) (val) #define BEEP_MASK_TO_REG(val) ((val) & 0xffffff) #define BEEP_ENABLE_TO_REG(val) ((val)?1:0) @@ -316,6 +371,7 @@ struct w83627hf_data { u32 beep_mask; /* Register encoding, combined */ u8 beep_enable; /* Boolean */ u8 pwm[3]; /* Register value */ + u8 pwm_freq[3]; /* Register value */ u16 sens[3]; /* 782D/783S only. 1 = pentium diode; 2 = 3904 diode; 3000-5000 = thermistor beta. @@ -852,6 +908,64 @@ sysfs_pwm(2); sysfs_pwm(3); static ssize_t +show_pwm_freq_reg(struct device *dev, char *buf, int nr) +{ + struct w83627hf_data *data = w83627hf_update_device(dev); + if (data->type == w83627hf) + return sprintf(buf, "%ld\n", + pwm_freq_from_reg_627hf(data->pwm_freq[nr - 1])); + else + return sprintf(buf, "%ld\n", + pwm_freq_from_reg(data->pwm_freq[nr - 1])); +} + +static ssize_t +store_pwm_freq_reg(struct device *dev, const char *buf, size_t count, int nr) +{ + struct w83627hf_data *data = dev_get_drvdata(dev); + static const u8 mask[]={0xF8, 0x8F}; + u32 val; + + val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + + if (data->type == w83627hf) { + data->pwm_freq[nr - 1] = pwm_freq_to_reg_627hf(val); + w83627hf_write_value(data, W83627HF_REG_PWM_FREQ, + (data->pwm_freq[nr - 1] << ((nr - 1)*4)) | + (w83627hf_read_value(data, + W83627HF_REG_PWM_FREQ) & mask[nr - 1])); + } else { + data->pwm_freq[nr - 1] = pwm_freq_to_reg(val); + w83627hf_write_value(data, W83637HF_REG_PWM_FREQ[nr - 1], + data->pwm_freq[nr - 1]); + } + + mutex_unlock(&data->update_lock); + return count; +} + +#define sysfs_pwm_freq(offset) \ +static ssize_t show_regs_pwm_freq_##offset(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + return show_pwm_freq_reg(dev, buf, offset); \ +} \ +static ssize_t \ +store_regs_pwm_freq_##offset(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + return store_pwm_freq_reg(dev, buf, count, offset); \ +} \ +static DEVICE_ATTR(pwm##offset##_freq, S_IRUGO | S_IWUSR, \ + show_regs_pwm_freq_##offset, store_regs_pwm_freq_##offset); + +sysfs_pwm_freq(1); +sysfs_pwm_freq(2); +sysfs_pwm_freq(3); + +static ssize_t show_sensor_reg(struct device *dev, char *buf, int nr) { struct w83627hf_data *data = w83627hf_update_device(dev); @@ -1077,6 +1191,9 @@ static struct attribute *w83627hf_attributes_opt[] = { &dev_attr_pwm3.attr, + &dev_attr_pwm1_freq.attr, + &dev_attr_pwm2_freq.attr, + &dev_attr_pwm3_freq.attr, NULL }; @@ -1139,7 +1256,9 @@ static int __devinit w83627hf_probe(struct platform_device *pdev) || (err = device_create_file(dev, &dev_attr_in5_max)) || (err = device_create_file(dev, &dev_attr_in6_input)) || (err = device_create_file(dev, &dev_attr_in6_min)) - || (err = device_create_file(dev, &dev_attr_in6_max))) + || (err = device_create_file(dev, &dev_attr_in6_max)) + || (err = device_create_file(dev, &dev_attr_pwm1_freq)) + || (err = device_create_file(dev, &dev_attr_pwm2_freq))) goto ERROR4; if (data->type != w83697hf) @@ -1169,6 +1288,12 @@ static int __devinit w83627hf_probe(struct platform_device *pdev) if ((err = device_create_file(dev, &dev_attr_pwm3))) goto ERROR4; + if (data->type == w83637hf || data->type == w83687thf) + if ((err = device_create_file(dev, &dev_attr_pwm1_freq)) + || (err = device_create_file(dev, &dev_attr_pwm2_freq)) + || (err = device_create_file(dev, &dev_attr_pwm3_freq))) + goto ERROR4; + data->class_dev = hwmon_device_register(dev); if (IS_ERR(data->class_dev)) { err = PTR_ERR(data->class_dev); @@ -1181,6 +1306,7 @@ static int __devinit w83627hf_probe(struct platform_device *pdev) sysfs_remove_group(&dev->kobj, &w83627hf_group); sysfs_remove_group(&dev->kobj, &w83627hf_group_opt); ERROR3: + platform_set_drvdata(pdev, NULL); kfree(data); ERROR1: release_region(res->start, WINB_REGION_SIZE); @@ -1193,11 +1319,11 @@ static int __devexit w83627hf_remove(struct platform_device *pdev) struct w83627hf_data *data = platform_get_drvdata(pdev); struct resource *res; - platform_set_drvdata(pdev, NULL); hwmon_device_unregister(data->class_dev); sysfs_remove_group(&pdev->dev.kobj, &w83627hf_group); sysfs_remove_group(&pdev->dev.kobj, &w83627hf_group_opt); + platform_set_drvdata(pdev, NULL); kfree(data); res = platform_get_resource(pdev, IORESOURCE_IO, 0); @@ -1472,6 +1598,20 @@ static struct w83627hf_data *w83627hf_update_device(struct device *dev) (data->type == w83627hf || data->type == w83697hf)) break; } + if (data->type == w83627hf) { + u8 tmp = w83627hf_read_value(data, + W83627HF_REG_PWM_FREQ); + data->pwm_freq[0] = tmp & 0x07; + data->pwm_freq[1] = (tmp >> 4) & 0x07; + } else if (data->type != w83627thf) { + for (i = 1; i <= 3; i++) { + data->pwm_freq[i - 1] = + w83627hf_read_value(data, + W83637HF_REG_PWM_FREQ[i - 1]); + if (i == 2 && (data->type == w83697hf)) + break; + } + } data->temp = w83627hf_read_value(data, W83781D_REG_TEMP(1)); data->temp_max = @@ -1548,15 +1688,12 @@ static int __init w83627hf_device_add(unsigned short address, goto exit_device_put; } - pdev->dev.platform_data = kmalloc(sizeof(struct w83627hf_sio_data), - GFP_KERNEL); - if (!pdev->dev.platform_data) { - err = -ENOMEM; + err = platform_device_add_data(pdev, sio_data, + sizeof(struct w83627hf_sio_data)); + if (err) { printk(KERN_ERR DRVNAME ": Platform data allocation failed\n"); goto exit_device_put; } - memcpy(pdev->dev.platform_data, sio_data, - sizeof(struct w83627hf_sio_data)); err = platform_device_add(pdev); if (err) { diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 1c77e14..da16478 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -237,9 +237,6 @@ config I2C_IOP3XX This driver can also be built as a module. If so, the module will be called i2c-iop3xx. -config I2C_ISA - tristate - config I2C_IXP4XX tristate "IXP4xx GPIO-Based I2C Interface (DEPRECATED)" depends on ARCH_IXP4XX diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index a6db4e3..5b752e4 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -18,7 +18,6 @@ obj-$(CONFIG_I2C_I801) += i2c-i801.o obj-$(CONFIG_I2C_I810) += i2c-i810.o obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o -obj-$(CONFIG_I2C_ISA) += i2c-isa.o obj-$(CONFIG_I2C_IXP2000) += i2c-ixp2000.o obj-$(CONFIG_I2C_IXP4XX) += i2c-ixp4xx.o obj-$(CONFIG_I2C_POWERMAC) += i2c-powermac.o diff --git a/drivers/i2c/busses/i2c-isa.c b/drivers/i2c/busses/i2c-isa.c deleted file mode 100644 index b0e1370..0000000 --- a/drivers/i2c/busses/i2c-isa.c +++ /dev/null @@ -1,192 +0,0 @@ -/* - i2c-isa.c - an i2c-core-like thing for ISA hardware monitoring chips - Copyright (C) 2005 Jean Delvare <khali@linux-fr.org> - - Based on the i2c-isa pseudo-adapter from the lm_sensors project - Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> - - 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. -*/ - -/* This implements an i2c-core-like thing for ISA hardware monitoring - chips. Such chips are linked to the i2c subsystem for historical - reasons (because the early ISA hardware monitoring chips such as the - LM78 had both an I2C and an ISA interface). They used to be - registered with the main i2c-core, but as a first step in the - direction of a clean separation between I2C and ISA chip drivers, - we now have this separate core for ISA ones. It is significantly - more simple than the real one, of course, because we don't have to - handle multiple busses: there is only one (fake) ISA adapter. - It is worth noting that we still rely on i2c-core for some things - at the moment - but hopefully this won't last. */ - -#include <linux/init.h> -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/errno.h> -#include <linux/i2c.h> -#include <linux/i2c-isa.h> -#include <linux/platform_device.h> -#include <linux/completion.h> - -/* Exported by i2c-core for i2c-isa only */ -extern void i2c_adapter_dev_release(struct device *dev); -extern struct class i2c_adapter_class; - -static u32 isa_func(struct i2c_adapter *adapter); - -/* This is the actual algorithm we define */ -static const struct i2c_algorithm isa_algorithm = { - .functionality = isa_func, -}; - -/* There can only be one... */ -static struct i2c_adapter isa_adapter = { - .owner = THIS_MODULE, - .id = I2C_HW_ISA, - .class = I2C_CLASS_HWMON, - .algo = &isa_algorithm, - .name = "ISA main adapter", -}; - -/* We can't do a thing... */ -static u32 isa_func(struct i2c_adapter *adapter) -{ - return 0; -} - - -/* We implement an interface which resembles i2c_{add,del}_driver, - but for i2c-isa drivers. We don't have to remember and handle lists - of drivers and adapters so this is much more simple, of course. */ - -int i2c_isa_add_driver(struct i2c_driver *driver) -{ - int res; - - /* Add the driver to the list of i2c drivers in the driver core */ - driver->driver.bus = &i2c_bus_type; - res = driver_register(&driver->driver); - if (res) - return res; - dev_dbg(&isa_adapter.dev, "Driver %s registered\n", driver->driver.name); - - /* Now look for clients */ - res = driver->attach_adapter(&isa_adapter); - if (res) { - dev_dbg(&isa_adapter.dev, - "Driver %s failed to attach adapter, unregistering\n", - driver->driver.name); - driver_unregister(&driver->driver); - } - return res; -} - -int i2c_isa_del_driver(struct i2c_driver *driver) -{ - struct list_head *item, *_n; - struct i2c_client *client; - int res; - - /* Detach all clients belonging to this one driver */ - list_for_each_safe(item, _n, &isa_adapter.clients) { - client = list_entry(item, struct i2c_client, list); - if (client->driver != driver) - continue; - dev_dbg(&isa_adapter.dev, "Detaching client %s at 0x%x\n", - client->name, client->addr); - if ((res = driver->detach_client(client))) { - dev_err(&isa_adapter.dev, "Failed, driver " - "%s not unregistered!\n", - driver->driver.name); - return res; - } - } - - /* Get the driver off the core list */ - driver_unregister(&driver->driver); - dev_dbg(&isa_adapter.dev, "Driver %s unregistered\n", driver->driver.name); - - return 0; -} - - -static int __init i2c_isa_init(void) -{ - int err; - - mutex_init(&isa_adapter.clist_lock); - INIT_LIST_HEAD(&isa_adapter.clients); - - isa_adapter.nr = ANY_I2C_ISA_BUS; - isa_adapter.dev.parent = &platform_bus; - sprintf(isa_adapter.dev.bus_id, "i2c-%d", isa_adapter.nr); - isa_adapter.dev.release = &i2c_adapter_dev_release; - isa_adapter.dev.class = &i2c_adapter_class; - err = device_register(&isa_adapter.dev); - if (err) { - printk(KERN_ERR "i2c-isa: Failed to register device\n"); - goto exit; - } - - dev_dbg(&isa_adapter.dev, "%s registered\n", isa_adapter.name); - - return 0; - -exit: - return err; -} - -static void __exit i2c_isa_exit(void) -{ -#ifdef DEBUG - struct list_head *item, *_n; - struct i2c_client *client = NULL; -#endif - - /* There should be no more active client */ -#ifdef DEBUG - dev_dbg(&isa_adapter.dev, "Looking for clients\n"); - list_for_each_safe(item, _n, &isa_adapter.clients) { - client = list_entry(item, struct i2c_client, list); - dev_err(&isa_adapter.dev, "Driver %s still has an active " - "ISA client at 0x%x\n", client->driver->driver.name, - client->addr); - } - if (client != NULL) - return; -#endif - - /* Clean up the sysfs representation */ - dev_dbg(&isa_adapter.dev, "Unregistering from sysfs\n"); - init_completion(&isa_adapter.dev_released); - device_unregister(&isa_adapter.dev); - - /* Wait for sysfs to drop all references */ - dev_dbg(&isa_adapter.dev, "Waiting for sysfs completion\n"); - wait_for_completion(&isa_adapter.dev_released); - - dev_dbg(&isa_adapter.dev, "%s unregistered\n", isa_adapter.name); -} - -EXPORT_SYMBOL(i2c_isa_add_driver); -EXPORT_SYMBOL(i2c_isa_del_driver); - -MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); -MODULE_DESCRIPTION("ISA bus access through i2c"); -MODULE_LICENSE("GPL"); - -module_init(i2c_isa_init); -module_exit(i2c_isa_exit); diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index 6971a62..d663e69 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -288,7 +288,6 @@ void i2c_adapter_dev_release(struct device *dev) struct i2c_adapter *adap = to_i2c_adapter(dev); complete(&adap->dev_released); } -EXPORT_SYMBOL_GPL(i2c_adapter_dev_release); /* exported to i2c-isa */ static ssize_t show_adapter_name(struct device *dev, struct device_attribute *attr, char *buf) @@ -307,7 +306,6 @@ struct class i2c_adapter_class = { .name = "i2c-adapter", .dev_attrs = i2c_adapter_attrs, }; -EXPORT_SYMBOL_GPL(i2c_adapter_class); /* exported to i2c-isa */ static void i2c_scan_static_board_info(struct i2c_adapter *adapter) { diff --git a/drivers/ide/ide-io.c b/drivers/ide/ide-io.c index c5b5011..f9de798 100644 --- a/drivers/ide/ide-io.c +++ b/drivers/ide/ide-io.c @@ -55,7 +55,7 @@ #include <asm/bitops.h> static int __ide_end_request(ide_drive_t *drive, struct request *rq, - int uptodate, int nr_sectors) + int uptodate, unsigned int nr_bytes) { int ret = 1; @@ -64,7 +64,7 @@ static int __ide_end_request(ide_drive_t *drive, struct request *rq, * complete the whole request right now */ if (blk_noretry_request(rq) && end_io_error(uptodate)) - nr_sectors = rq->hard_nr_sectors; + nr_bytes = rq->hard_nr_sectors << 9; if (!blk_fs_request(rq) && end_io_error(uptodate) && !rq->errors) rq->errors = -EIO; @@ -78,7 +78,7 @@ static int __ide_end_request(ide_drive_t *drive, struct request *rq, HWGROUP(drive)->hwif->ide_dma_on(drive); } - if (!end_that_request_first(rq, uptodate, nr_sectors)) { + if (!end_that_request_chunk(rq, uptodate, nr_bytes)) { add_disk_randomness(rq->rq_disk); if (!list_empty(&rq->queuelist)) blkdev_dequeue_request(rq); @@ -103,6 +103,7 @@ static int __ide_end_request(ide_drive_t *drive, struct request *rq, int ide_end_request (ide_drive_t *drive, int uptodate, int nr_sectors) { + unsigned int nr_bytes = nr_sectors << 9; struct request *rq; unsigned long flags; int ret = 1; @@ -114,10 +115,14 @@ int ide_end_request (ide_drive_t *drive, int uptodate, int nr_sectors) spin_lock_irqsave(&ide_lock, flags); rq = HWGROUP(drive)->rq; - if (!nr_sectors) - nr_sectors = rq->hard_cur_sectors; + if (!nr_bytes) { + if (blk_pc_request(rq)) + nr_bytes = rq->data_len; + else + nr_bytes = rq->hard_cur_sectors << 9; + } - ret = __ide_end_request(drive, rq, uptodate, nr_sectors); + ret = __ide_end_request(drive, rq, uptodate, nr_bytes); spin_unlock_irqrestore(&ide_lock, flags); return ret; diff --git a/drivers/ide/mips/swarm.c b/drivers/ide/mips/swarm.c index 6e935d7..c2e2957 100644 --- a/drivers/ide/mips/swarm.c +++ b/drivers/ide/mips/swarm.c @@ -165,12 +165,11 @@ static int __devinit swarm_ide_init_module(void) goto out; } - if (!(pldev = kmalloc(sizeof (*pldev), GFP_KERNEL))) { + if (!(pldev = kzalloc(sizeof (*pldev), GFP_KERNEL))) { err = -ENOMEM; goto out_unregister_driver; } - memset (pldev, 0, sizeof (*pldev)); pldev->name = swarm_ide_string; pldev->id = 0; pldev->dev.release = swarm_ide_platform_release; diff --git a/drivers/infiniband/core/addr.c b/drivers/infiniband/core/addr.c index a91001c..c5c33d3 100644 --- a/drivers/infiniband/core/addr.c +++ b/drivers/infiniband/core/addr.c @@ -295,10 +295,9 @@ int rdma_resolve_ip(struct rdma_addr_client *client, struct addr_req *req; int ret = 0; - req = kmalloc(sizeof *req, GFP_KERNEL); + req = kzalloc(sizeof *req, GFP_KERNEL); if (!req) return -ENOMEM; - memset(req, 0, sizeof *req); if (src_addr) memcpy(&req->src_addr, src_addr, ip_addr_size(src_addr)); diff --git a/drivers/infiniband/hw/cxgb3/iwch_cm.c b/drivers/infiniband/hw/cxgb3/iwch_cm.c index 5dc68cd..9574088 100644 --- a/drivers/infiniband/hw/cxgb3/iwch_cm.c +++ b/drivers/infiniband/hw/cxgb3/iwch_cm.c @@ -229,9 +229,8 @@ static void *alloc_ep(int size, gfp_t gfp) { struct iwch_ep_common *epc; - epc = kmalloc(size, gfp); + epc = kzalloc(size, gfp); if (epc) { - memset(epc, 0, size); kref_init(&epc->kref); spin_lock_init(&epc->lock); init_waitqueue_head(&epc->waitq); diff --git a/drivers/input/serio/ambakmi.c b/drivers/input/serio/ambakmi.c index 5a7b49c..b10ffae 100644 --- a/drivers/input/serio/ambakmi.c +++ b/drivers/input/serio/ambakmi.c @@ -117,15 +117,13 @@ static int amba_kmi_probe(struct amba_device *dev, void *id) if (ret) return ret; - kmi = kmalloc(sizeof(struct amba_kmi_port), GFP_KERNEL); - io = kmalloc(sizeof(struct serio), GFP_KERNEL); + kmi = kzalloc(sizeof(struct amba_kmi_port), GFP_KERNEL); + io = kzalloc(sizeof(struct serio), GFP_KERNEL); if (!kmi || !io) { ret = -ENOMEM; goto out; } - memset(kmi, 0, sizeof(struct amba_kmi_port)); - memset(io, 0, sizeof(struct serio)); io->id.type = SERIO_8042; io->write = amba_kmi_write; diff --git a/drivers/input/serio/pcips2.c b/drivers/input/serio/pcips2.c index ea5e3c6..1b404f9 100644 --- a/drivers/input/serio/pcips2.c +++ b/drivers/input/serio/pcips2.c @@ -140,15 +140,13 @@ static int __devinit pcips2_probe(struct pci_dev *dev, const struct pci_device_i if (ret) goto disable; - ps2if = kmalloc(sizeof(struct pcips2_data), GFP_KERNEL); - serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + ps2if = kzalloc(sizeof(struct pcips2_data), GFP_KERNEL); + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); if (!ps2if || !serio) { ret = -ENOMEM; goto release; } - memset(ps2if, 0, sizeof(struct pcips2_data)); - memset(serio, 0, sizeof(struct serio)); serio->id.type = SERIO_8042; serio->write = pcips2_write; diff --git a/drivers/input/serio/sa1111ps2.c b/drivers/input/serio/sa1111ps2.c index d31ece8..2ad8878 100644 --- a/drivers/input/serio/sa1111ps2.c +++ b/drivers/input/serio/sa1111ps2.c @@ -234,15 +234,13 @@ static int __devinit ps2_probe(struct sa1111_dev *dev) struct serio *serio; int ret; - ps2if = kmalloc(sizeof(struct ps2if), GFP_KERNEL); - serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + ps2if = kzalloc(sizeof(struct ps2if), GFP_KERNEL); + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); if (!ps2if || !serio) { ret = -ENOMEM; goto free; } - memset(ps2if, 0, sizeof(struct ps2if)); - memset(serio, 0, sizeof(struct serio)); serio->id.type = SERIO_8042; serio->write = ps2_write; diff --git a/drivers/isdn/sc/card.h b/drivers/isdn/sc/card.h index 4fbfa82..5992f63 100644 --- a/drivers/isdn/sc/card.h +++ b/drivers/isdn/sc/card.h @@ -125,7 +125,7 @@ int sendmessage(int card, unsigned int procid, unsigned int type, int receivemessage(int card, RspMessage *rspmsg); int sc_ioctl(int card, scs_ioctl *data); int setup_buffers(int card, int c); -void check_reset(unsigned long data); +void sc_check_reset(unsigned long data); void check_phystat(unsigned long data); #endif /* CARD_H */ diff --git a/drivers/isdn/sc/command.c b/drivers/isdn/sc/command.c index b7bb7cb..0e4969c 100644 --- a/drivers/isdn/sc/command.c +++ b/drivers/isdn/sc/command.c @@ -344,7 +344,7 @@ int reset(int card) spin_lock_irqsave(&sc_adapter[card]->lock, flags); init_timer(&sc_adapter[card]->reset_timer); - sc_adapter[card]->reset_timer.function = check_reset; + sc_adapter[card]->reset_timer.function = sc_check_reset; sc_adapter[card]->reset_timer.data = card; sc_adapter[card]->reset_timer.expires = jiffies + CHECKRESET_TIME; add_timer(&sc_adapter[card]->reset_timer); diff --git a/drivers/isdn/sc/timer.c b/drivers/isdn/sc/timer.c index cc1b886..91fbe0d 100644 --- a/drivers/isdn/sc/timer.c +++ b/drivers/isdn/sc/timer.c @@ -43,7 +43,7 @@ static void setup_ports(int card) * Then, check to see if the signate has been set. Next, set the * signature to a known value and issue a startproc if needed. */ -void check_reset(unsigned long data) +void sc_check_reset(unsigned long data) { unsigned long flags; unsigned long sig; diff --git a/drivers/kvm/Kconfig b/drivers/kvm/Kconfig index 33fa28a..2f661e5 100644 --- a/drivers/kvm/Kconfig +++ b/drivers/kvm/Kconfig @@ -11,7 +11,6 @@ if VIRTUALIZATION config KVM tristate "Kernel-based Virtual Machine (KVM) support" depends on X86 && EXPERIMENTAL - depends on X86_CMPXCHG64 || 64BIT ---help--- Support hosting fully virtualized guest machines using hardware virtualization extensions. You will need a fairly recent diff --git a/drivers/lguest/Kconfig b/drivers/lguest/Kconfig new file mode 100644 index 0000000..43d901f --- /dev/null +++ b/drivers/lguest/Kconfig @@ -0,0 +1,20 @@ +config LGUEST + tristate "Linux hypervisor example code" + depends on X86 && PARAVIRT && NET && EXPERIMENTAL && !X86_PAE + select LGUEST_GUEST + select HVC_DRIVER + ---help--- + This is a very simple module which allows you to run + multiple instances of the same Linux kernel, using the + "lguest" command found in the Documentation/lguest directory. + Note that "lguest" is pronounced to rhyme with "fell quest", + not "rustyvisor". See Documentation/lguest/lguest.txt. + + If unsure, say N. If curious, say M. If masochistic, say Y. + +config LGUEST_GUEST + bool + help + The guest needs code built-in, even if the host has lguest + support as a module. The drivers are tiny, so we build them + in too. diff --git a/drivers/lguest/Makefile b/drivers/lguest/Makefile new file mode 100644 index 0000000..55382c7 --- /dev/null +++ b/drivers/lguest/Makefile @@ -0,0 +1,7 @@ +# Guest requires the paravirt_ops replacement and the bus driver. +obj-$(CONFIG_LGUEST_GUEST) += lguest.o lguest_asm.o lguest_bus.o + +# Host requires the other files, which can be a module. +obj-$(CONFIG_LGUEST) += lg.o +lg-y := core.o hypercalls.o page_tables.o interrupts_and_traps.o \ + segments.o io.o lguest_user.o switcher.o diff --git a/drivers/lguest/core.c b/drivers/lguest/core.c new file mode 100644 index 0000000..ce909ec --- /dev/null +++ b/drivers/lguest/core.c @@ -0,0 +1,462 @@ +/* World's simplest hypervisor, to test paravirt_ops and show + * unbelievers that virtualization is the future. Plus, it's fun! */ +#include <linux/module.h> +#include <linux/stringify.h> +#include <linux/stddef.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/cpu.h> +#include <linux/freezer.h> +#include <asm/paravirt.h> +#include <asm/desc.h> +#include <asm/pgtable.h> +#include <asm/uaccess.h> +#include <asm/poll.h> +#include <asm/highmem.h> +#include <asm/asm-offsets.h> +#include <asm/i387.h> +#include "lg.h" + +/* Found in switcher.S */ +extern char start_switcher_text[], end_switcher_text[], switch_to_guest[]; +extern unsigned long default_idt_entries[]; + +/* Every guest maps the core switcher code. */ +#define SHARED_SWITCHER_PAGES \ + DIV_ROUND_UP(end_switcher_text - start_switcher_text, PAGE_SIZE) +/* Pages for switcher itself, then two pages per cpu */ +#define TOTAL_SWITCHER_PAGES (SHARED_SWITCHER_PAGES + 2 * NR_CPUS) + +/* We map at -4M for ease of mapping into the guest (one PTE page). */ +#define SWITCHER_ADDR 0xFFC00000 + +static struct vm_struct *switcher_vma; +static struct page **switcher_page; + +static int cpu_had_pge; +static struct { + unsigned long offset; + unsigned short segment; +} lguest_entry; + +/* This One Big lock protects all inter-guest data structures. */ +DEFINE_MUTEX(lguest_lock); +static DEFINE_PER_CPU(struct lguest *, last_guest); + +/* FIXME: Make dynamic. */ +#define MAX_LGUEST_GUESTS 16 +struct lguest lguests[MAX_LGUEST_GUESTS]; + +/* Offset from where switcher.S was compiled to where we've copied it */ +static unsigned long switcher_offset(void) +{ + return SWITCHER_ADDR - (unsigned long)start_switcher_text; +} + +/* This cpu's struct lguest_pages. */ +static struct lguest_pages *lguest_pages(unsigned int cpu) +{ + return &(((struct lguest_pages *) + (SWITCHER_ADDR + SHARED_SWITCHER_PAGES*PAGE_SIZE))[cpu]); +} + +static __init int map_switcher(void) +{ + int i, err; + struct page **pagep; + + switcher_page = kmalloc(sizeof(switcher_page[0])*TOTAL_SWITCHER_PAGES, + GFP_KERNEL); + if (!switcher_page) { + err = -ENOMEM; + goto out; + } + + for (i = 0; i < TOTAL_SWITCHER_PAGES; i++) { + unsigned long addr = get_zeroed_page(GFP_KERNEL); + if (!addr) { + err = -ENOMEM; + goto free_some_pages; + } + switcher_page[i] = virt_to_page(addr); + } + + switcher_vma = __get_vm_area(TOTAL_SWITCHER_PAGES * PAGE_SIZE, + VM_ALLOC, SWITCHER_ADDR, VMALLOC_END); + if (!switcher_vma) { + err = -ENOMEM; + printk("lguest: could not map switcher pages high\n"); + goto free_pages; + } + + pagep = switcher_page; + err = map_vm_area(switcher_vma, PAGE_KERNEL, &pagep); + if (err) { + printk("lguest: map_vm_area failed: %i\n", err); + goto free_vma; + } + memcpy(switcher_vma->addr, start_switcher_text, + end_switcher_text - start_switcher_text); + + /* Fix up IDT entries to point into copied text. */ + for (i = 0; i < IDT_ENTRIES; i++) + default_idt_entries[i] += switcher_offset(); + + for_each_possible_cpu(i) { + struct lguest_pages *pages = lguest_pages(i); + struct lguest_ro_state *state = &pages->state; + + /* These fields are static: rest done in copy_in_guest_info */ + state->host_gdt_desc.size = GDT_SIZE-1; + state->host_gdt_desc.address = (long)get_cpu_gdt_table(i); + store_idt(&state->host_idt_desc); + state->guest_idt_desc.size = sizeof(state->guest_idt)-1; + state->guest_idt_desc.address = (long)&state->guest_idt; + state->guest_gdt_desc.size = sizeof(state->guest_gdt)-1; + state->guest_gdt_desc.address = (long)&state->guest_gdt; + state->guest_tss.esp0 = (long)(&pages->regs + 1); + state->guest_tss.ss0 = LGUEST_DS; + /* No I/O for you! */ + state->guest_tss.io_bitmap_base = sizeof(state->guest_tss); + setup_default_gdt_entries(state); + setup_default_idt_entries(state, default_idt_entries); + + /* Setup LGUEST segments on all cpus */ + get_cpu_gdt_table(i)[GDT_ENTRY_LGUEST_CS] = FULL_EXEC_SEGMENT; + get_cpu_gdt_table(i)[GDT_ENTRY_LGUEST_DS] = FULL_SEGMENT; + } + + /* Initialize entry point into switcher. */ + lguest_entry.offset = (long)switch_to_guest + switcher_offset(); + lguest_entry.segment = LGUEST_CS; + + printk(KERN_INFO "lguest: mapped switcher at %p\n", + switcher_vma->addr); + return 0; + +free_vma: + vunmap(switcher_vma->addr); +free_pages: + i = TOTAL_SWITCHER_PAGES; +free_some_pages: + for (--i; i >= 0; i--) + __free_pages(switcher_page[i], 0); + kfree(switcher_page); +out: + return err; +} + +static void unmap_switcher(void) +{ + unsigned int i; + + vunmap(switcher_vma->addr); + for (i = 0; i < TOTAL_SWITCHER_PAGES; i++) + __free_pages(switcher_page[i], 0); +} + +/* IN/OUT insns: enough to get us past boot-time probing. */ +static int emulate_insn(struct lguest *lg) +{ + u8 insn; + unsigned int insnlen = 0, in = 0, shift = 0; + unsigned long physaddr = guest_pa(lg, lg->regs->eip); + + /* This only works for addresses in linear mapping... */ + if (lg->regs->eip < lg->page_offset) + return 0; + lgread(lg, &insn, physaddr, 1); + + /* Operand size prefix means it's actually for ax. */ + if (insn == 0x66) { + shift = 16; + insnlen = 1; + lgread(lg, &insn, physaddr + insnlen, 1); + } + + switch (insn & 0xFE) { + case 0xE4: /* in <next byte>,%al */ + insnlen += 2; + in = 1; + break; + case 0xEC: /* in (%dx),%al */ + insnlen += 1; + in = 1; + break; + case 0xE6: /* out %al,<next byte> */ + insnlen += 2; + break; + case 0xEE: /* out %al,(%dx) */ + insnlen += 1; + break; + default: + return 0; + } + + if (in) { + /* Lower bit tells is whether it's a 16 or 32 bit access */ + if (insn & 0x1) + lg->regs->eax = 0xFFFFFFFF; + else + lg->regs->eax |= (0xFFFF << shift); + } + lg->regs->eip += insnlen; + return 1; +} + +int lguest_address_ok(const struct lguest *lg, + unsigned long addr, unsigned long len) +{ + return (addr+len) / PAGE_SIZE < lg->pfn_limit && (addr+len >= addr); +} + +/* Just like get_user, but don't let guest access lguest binary. */ +u32 lgread_u32(struct lguest *lg, unsigned long addr) +{ + u32 val = 0; + + /* Don't let them access lguest binary */ + if (!lguest_address_ok(lg, addr, sizeof(val)) + || get_user(val, (u32 __user *)addr) != 0) + kill_guest(lg, "bad read address %#lx", addr); + return val; +} + +void lgwrite_u32(struct lguest *lg, unsigned long addr, u32 val) +{ + if (!lguest_address_ok(lg, addr, sizeof(val)) + || put_user(val, (u32 __user *)addr) != 0) + kill_guest(lg, "bad write address %#lx", addr); +} + +void lgread(struct lguest *lg, void *b, unsigned long addr, unsigned bytes) +{ + if (!lguest_address_ok(lg, addr, bytes) + || copy_from_user(b, (void __user *)addr, bytes) != 0) { + /* copy_from_user should do this, but as we rely on it... */ + memset(b, 0, bytes); + kill_guest(lg, "bad read address %#lx len %u", addr, bytes); + } +} + +void lgwrite(struct lguest *lg, unsigned long addr, const void *b, + unsigned bytes) +{ + if (!lguest_address_ok(lg, addr, bytes) + || copy_to_user((void __user *)addr, b, bytes) != 0) + kill_guest(lg, "bad write address %#lx len %u", addr, bytes); +} + +static void set_ts(void) +{ + u32 cr0; + + cr0 = read_cr0(); + if (!(cr0 & 8)) + write_cr0(cr0|8); +} + +static void copy_in_guest_info(struct lguest *lg, struct lguest_pages *pages) +{ + if (__get_cpu_var(last_guest) != lg || lg->last_pages != pages) { + __get_cpu_var(last_guest) = lg; + lg->last_pages = pages; + lg->changed = CHANGED_ALL; + } + + /* These are pretty cheap, so we do them unconditionally. */ + pages->state.host_cr3 = __pa(current->mm->pgd); + map_switcher_in_guest(lg, pages); + pages->state.guest_tss.esp1 = lg->esp1; + pages->state.guest_tss.ss1 = lg->ss1; + + /* Copy direct trap entries. */ + if (lg->changed & CHANGED_IDT) + copy_traps(lg, pages->state.guest_idt, default_idt_entries); + + /* Copy all GDT entries but the TSS. */ + if (lg->changed & CHANGED_GDT) + copy_gdt(lg, pages->state.guest_gdt); + /* If only the TLS entries have changed, copy them. */ + else if (lg->changed & CHANGED_GDT_TLS) + copy_gdt_tls(lg, pages->state.guest_gdt); + + lg->changed = 0; +} + +static void run_guest_once(struct lguest *lg, struct lguest_pages *pages) +{ + unsigned int clobber; + + copy_in_guest_info(lg, pages); + + /* Put eflags on stack, lcall does rest: suitable for iret return. */ + asm volatile("pushf; lcall *lguest_entry" + : "=a"(clobber), "=b"(clobber) + : "0"(pages), "1"(__pa(lg->pgdirs[lg->pgdidx].pgdir)) + : "memory", "%edx", "%ecx", "%edi", "%esi"); +} + +int run_guest(struct lguest *lg, unsigned long __user *user) +{ + while (!lg->dead) { + unsigned int cr2 = 0; /* Damn gcc */ + + /* Hypercalls first: we might have been out to userspace */ + do_hypercalls(lg); + if (lg->dma_is_pending) { + if (put_user(lg->pending_dma, user) || + put_user(lg->pending_key, user+1)) + return -EFAULT; + return sizeof(unsigned long)*2; + } + + if (signal_pending(current)) + return -ERESTARTSYS; + + /* If Waker set break_out, return to Launcher. */ + if (lg->break_out) + return -EAGAIN; + + maybe_do_interrupt(lg); + + try_to_freeze(); + + if (lg->dead) + break; + + if (lg->halted) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + continue; + } + + local_irq_disable(); + + /* Even if *we* don't want FPU trap, guest might... */ + if (lg->ts) + set_ts(); + + /* Don't let Guest do SYSENTER: we can't handle it. */ + if (boot_cpu_has(X86_FEATURE_SEP)) + wrmsr(MSR_IA32_SYSENTER_CS, 0, 0); + + run_guest_once(lg, lguest_pages(raw_smp_processor_id())); + + /* Save cr2 now if we page-faulted. */ + if (lg->regs->trapnum == 14) + cr2 = read_cr2(); + else if (lg->regs->trapnum == 7) + math_state_restore(); + + if (boot_cpu_has(X86_FEATURE_SEP)) + wrmsr(MSR_IA32_SYSENTER_CS, __KERNEL_CS, 0); + local_irq_enable(); + + switch (lg->regs->trapnum) { + case 13: /* We've intercepted a GPF. */ + if (lg->regs->errcode == 0) { + if (emulate_insn(lg)) + continue; + } + break; + case 14: /* We've intercepted a page fault. */ + if (demand_page(lg, cr2, lg->regs->errcode)) + continue; + + /* If lguest_data is NULL, this won't hurt. */ + if (put_user(cr2, &lg->lguest_data->cr2)) + kill_guest(lg, "Writing cr2"); + break; + case 7: /* We've intercepted a Device Not Available fault. */ + /* If they don't want to know, just absorb it. */ + if (!lg->ts) + continue; + break; + case 32 ... 255: /* Real interrupt, fall thru */ + cond_resched(); + case LGUEST_TRAP_ENTRY: /* Handled at top of loop */ + continue; + } + + if (deliver_trap(lg, lg->regs->trapnum)) + continue; + + kill_guest(lg, "unhandled trap %li at %#lx (%#lx)", + lg->regs->trapnum, lg->regs->eip, + lg->regs->trapnum == 14 ? cr2 : lg->regs->errcode); + } + return -ENOENT; +} + +int find_free_guest(void) +{ + unsigned int i; + for (i = 0; i < MAX_LGUEST_GUESTS; i++) + if (!lguests[i].tsk) + return i; + return -1; +} + +static void adjust_pge(void *on) +{ + if (on) + write_cr4(read_cr4() | X86_CR4_PGE); + else + write_cr4(read_cr4() & ~X86_CR4_PGE); +} + +static int __init init(void) +{ + int err; + + if (paravirt_enabled()) { + printk("lguest is afraid of %s\n", paravirt_ops.name); + return -EPERM; + } + + err = map_switcher(); + if (err) + return err; + + err = init_pagetables(switcher_page, SHARED_SWITCHER_PAGES); + if (err) { + unmap_switcher(); + return err; + } + lguest_io_init(); + + err = lguest_device_init(); + if (err) { + free_pagetables(); + unmap_switcher(); + return err; + } + lock_cpu_hotplug(); + if (cpu_has_pge) { /* We have a broader idea of "global". */ + cpu_had_pge = 1; + on_each_cpu(adjust_pge, (void *)0, 0, 1); + clear_bit(X86_FEATURE_PGE, boot_cpu_data.x86_capability); + } + unlock_cpu_hotplug(); + return 0; +} + +static void __exit fini(void) +{ + lguest_device_remove(); + free_pagetables(); + unmap_switcher(); + lock_cpu_hotplug(); + if (cpu_had_pge) { + set_bit(X86_FEATURE_PGE, boot_cpu_data.x86_capability); + on_each_cpu(adjust_pge, (void *)1, 0, 1); + } + unlock_cpu_hotplug(); +} + +module_init(init); +module_exit(fini); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Rusty Russell <rusty@rustcorp.com.au>"); diff --git a/drivers/lguest/hypercalls.c b/drivers/lguest/hypercalls.c new file mode 100644 index 0000000..ea52ca4 --- /dev/null +++ b/drivers/lguest/hypercalls.c @@ -0,0 +1,192 @@ +/* Actual hypercalls, which allow guests to actually do something. + Copyright (C) 2006 Rusty Russell IBM Corporation + + 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 St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include <linux/uaccess.h> +#include <linux/syscalls.h> +#include <linux/mm.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <irq_vectors.h> +#include "lg.h" + +static void do_hcall(struct lguest *lg, struct lguest_regs *regs) +{ + switch (regs->eax) { + case LHCALL_FLUSH_ASYNC: + break; + case LHCALL_LGUEST_INIT: + kill_guest(lg, "already have lguest_data"); + break; + case LHCALL_CRASH: { + char msg[128]; + lgread(lg, msg, regs->edx, sizeof(msg)); + msg[sizeof(msg)-1] = '\0'; + kill_guest(lg, "CRASH: %s", msg); + break; + } + case LHCALL_FLUSH_TLB: + if (regs->edx) + guest_pagetable_clear_all(lg); + else + guest_pagetable_flush_user(lg); + break; + case LHCALL_GET_WALLCLOCK: { + struct timespec ts; + ktime_get_real_ts(&ts); + regs->eax = ts.tv_sec; + break; + } + case LHCALL_BIND_DMA: + regs->eax = bind_dma(lg, regs->edx, regs->ebx, + regs->ecx >> 8, regs->ecx & 0xFF); + break; + case LHCALL_SEND_DMA: + send_dma(lg, regs->edx, regs->ebx); + break; + case LHCALL_LOAD_GDT: + load_guest_gdt(lg, regs->edx, regs->ebx); + break; + case LHCALL_LOAD_IDT_ENTRY: + load_guest_idt_entry(lg, regs->edx, regs->ebx, regs->ecx); + break; + case LHCALL_NEW_PGTABLE: + guest_new_pagetable(lg, regs->edx); + break; + case LHCALL_SET_STACK: + guest_set_stack(lg, regs->edx, regs->ebx, regs->ecx); + break; + case LHCALL_SET_PTE: + guest_set_pte(lg, regs->edx, regs->ebx, mkgpte(regs->ecx)); + break; + case LHCALL_SET_PMD: + guest_set_pmd(lg, regs->edx, regs->ebx); + break; + case LHCALL_LOAD_TLS: + guest_load_tls(lg, regs->edx); + break; + case LHCALL_SET_CLOCKEVENT: + guest_set_clockevent(lg, regs->edx); + break; + case LHCALL_TS: + lg->ts = regs->edx; + break; + case LHCALL_HALT: + lg->halted = 1; + break; + default: + kill_guest(lg, "Bad hypercall %li\n", regs->eax); + } +} + +/* We always do queued calls before actual hypercall. */ +static void do_async_hcalls(struct lguest *lg) +{ + unsigned int i; + u8 st[LHCALL_RING_SIZE]; + + if (copy_from_user(&st, &lg->lguest_data->hcall_status, sizeof(st))) + return; + + for (i = 0; i < ARRAY_SIZE(st); i++) { + struct lguest_regs regs; + unsigned int n = lg->next_hcall; + + if (st[n] == 0xFF) + break; + + if (++lg->next_hcall == LHCALL_RING_SIZE) + lg->next_hcall = 0; + + if (get_user(regs.eax, &lg->lguest_data->hcalls[n].eax) + || get_user(regs.edx, &lg->lguest_data->hcalls[n].edx) + || get_user(regs.ecx, &lg->lguest_data->hcalls[n].ecx) + || get_user(regs.ebx, &lg->lguest_data->hcalls[n].ebx)) { + kill_guest(lg, "Fetching async hypercalls"); + break; + } + + do_hcall(lg, ®s); + if (put_user(0xFF, &lg->lguest_data->hcall_status[n])) { + kill_guest(lg, "Writing result for async hypercall"); + break; + } + + if (lg->dma_is_pending) + break; + } +} + +static void initialize(struct lguest *lg) +{ + u32 tsc_speed; + + if (lg->regs->eax != LHCALL_LGUEST_INIT) { + kill_guest(lg, "hypercall %li before LGUEST_INIT", + lg->regs->eax); + return; + } + + /* We only tell the guest to use the TSC if it's reliable. */ + if (boot_cpu_has(X86_FEATURE_CONSTANT_TSC) && !check_tsc_unstable()) + tsc_speed = tsc_khz; + else + tsc_speed = 0; + + lg->lguest_data = (struct lguest_data __user *)lg->regs->edx; + /* We check here so we can simply copy_to_user/from_user */ + if (!lguest_address_ok(lg, lg->regs->edx, sizeof(*lg->lguest_data))) { + kill_guest(lg, "bad guest page %p", lg->lguest_data); + return; + } + if (get_user(lg->noirq_start, &lg->lguest_data->noirq_start) + || get_user(lg->noirq_end, &lg->lguest_data->noirq_end) + /* We reserve the top pgd entry. */ + || put_user(4U*1024*1024, &lg->lguest_data->reserve_mem) + || put_user(tsc_speed, &lg->lguest_data->tsc_khz) + || put_user(lg->guestid, &lg->lguest_data->guestid)) + kill_guest(lg, "bad guest page %p", lg->lguest_data); + + /* This is the one case where the above accesses might have + * been the first write to a Guest page. This may have caused + * a copy-on-write fault, but the Guest might be referring to + * the old (read-only) page. */ + guest_pagetable_clear_all(lg); +} + +/* Even if we go out to userspace and come back, we don't want to do + * the hypercall again. */ +static void clear_hcall(struct lguest *lg) +{ + lg->regs->trapnum = 255; +} + +void do_hypercalls(struct lguest *lg) +{ + if (unlikely(!lg->lguest_data)) { + if (lg->regs->trapnum == LGUEST_TRAP_ENTRY) { + initialize(lg); + clear_hcall(lg); + } + return; + } + + do_async_hcalls(lg); + if (!lg->dma_is_pending && lg->regs->trapnum == LGUEST_TRAP_ENTRY) { + do_hcall(lg, lg->regs); + clear_hcall(lg); + } +} diff --git a/drivers/lguest/interrupts_and_traps.c b/drivers/lguest/interrupts_and_traps.c new file mode 100644 index 0000000..d9de5bb --- /dev/null +++ b/drivers/lguest/interrupts_and_traps.c @@ -0,0 +1,268 @@ +#include <linux/uaccess.h> +#include "lg.h" + +static unsigned long idt_address(u32 lo, u32 hi) +{ + return (lo & 0x0000FFFF) | (hi & 0xFFFF0000); +} + +static int idt_type(u32 lo, u32 hi) +{ + return (hi >> 8) & 0xF; +} + +static int idt_present(u32 lo, u32 hi) +{ + return (hi & 0x8000); +} + +static void push_guest_stack(struct lguest *lg, unsigned long *gstack, u32 val) +{ + *gstack -= 4; + lgwrite_u32(lg, *gstack, val); +} + +static void set_guest_interrupt(struct lguest *lg, u32 lo, u32 hi, int has_err) +{ + unsigned long gstack; + u32 eflags, ss, irq_enable; + + /* If they want a ring change, we use new stack and push old ss/esp */ + if ((lg->regs->ss&0x3) != GUEST_PL) { + gstack = guest_pa(lg, lg->esp1); + ss = lg->ss1; + push_guest_stack(lg, &gstack, lg->regs->ss); + push_guest_stack(lg, &gstack, lg->regs->esp); + } else { + gstack = guest_pa(lg, lg->regs->esp); + ss = lg->regs->ss; + } + + /* We use IF bit in eflags to indicate whether irqs were disabled + (it's always 0, since irqs are enabled when guest is running). */ + eflags = lg->regs->eflags; + if (get_user(irq_enable, &lg->lguest_data->irq_enabled)) + irq_enable = 0; + eflags |= (irq_enable & X86_EFLAGS_IF); + + push_guest_stack(lg, &gstack, eflags); + push_guest_stack(lg, &gstack, lg->regs->cs); + push_guest_stack(lg, &gstack, lg->regs->eip); + + if (has_err) + push_guest_stack(lg, &gstack, lg->regs->errcode); + + /* Change the real stack so switcher returns to trap handler */ + lg->regs->ss = ss; + lg->regs->esp = gstack + lg->page_offset; + lg->regs->cs = (__KERNEL_CS|GUEST_PL); + lg->regs->eip = idt_address(lo, hi); + + /* Disable interrupts for an interrupt gate. */ + if (idt_type(lo, hi) == 0xE) + if (put_user(0, &lg->lguest_data->irq_enabled)) + kill_guest(lg, "Disabling interrupts"); +} + +void maybe_do_interrupt(struct lguest *lg) +{ + unsigned int irq; + DECLARE_BITMAP(blk, LGUEST_IRQS); + struct desc_struct *idt; + + if (!lg->lguest_data) + return; + + /* Mask out any interrupts they have blocked. */ + if (copy_from_user(&blk, lg->lguest_data->blocked_interrupts, + sizeof(blk))) + return; + + bitmap_andnot(blk, lg->irqs_pending, blk, LGUEST_IRQS); + + irq = find_first_bit(blk, LGUEST_IRQS); + if (irq >= LGUEST_IRQS) + return; + + if (lg->regs->eip >= lg->noirq_start && lg->regs->eip < lg->noirq_end) + return; + + /* If they're halted, we re-enable interrupts. */ + if (lg->halted) { + /* Re-enable interrupts. */ + if (put_user(X86_EFLAGS_IF, &lg->lguest_data->irq_enabled)) + kill_guest(lg, "Re-enabling interrupts"); + lg->halted = 0; + } else { + /* Maybe they have interrupts disabled? */ + u32 irq_enabled; + if (get_user(irq_enabled, &lg->lguest_data->irq_enabled)) + irq_enabled = 0; + if (!irq_enabled) + return; + } + + idt = &lg->idt[FIRST_EXTERNAL_VECTOR+irq]; + if (idt_present(idt->a, idt->b)) { + clear_bit(irq, lg->irqs_pending); + set_guest_interrupt(lg, idt->a, idt->b, 0); + } +} + +static int has_err(unsigned int trap) +{ + return (trap == 8 || (trap >= 10 && trap <= 14) || trap == 17); +} + +int deliver_trap(struct lguest *lg, unsigned int num) +{ + u32 lo = lg->idt[num].a, hi = lg->idt[num].b; + + if (!idt_present(lo, hi)) + return 0; + set_guest_interrupt(lg, lo, hi, has_err(num)); + return 1; +} + +static int direct_trap(const struct lguest *lg, + const struct desc_struct *trap, + unsigned int num) +{ + /* Hardware interrupts don't go to guest (except syscall). */ + if (num >= FIRST_EXTERNAL_VECTOR && num != SYSCALL_VECTOR) + return 0; + + /* We intercept page fault (demand shadow paging & cr2 saving) + protection fault (in/out emulation) and device not + available (TS handling), and hypercall */ + if (num == 14 || num == 13 || num == 7 || num == LGUEST_TRAP_ENTRY) + return 0; + + /* Interrupt gates (0xE) or not present (0x0) can't go direct. */ + return idt_type(trap->a, trap->b) == 0xF; +} + +void pin_stack_pages(struct lguest *lg) +{ + unsigned int i; + + for (i = 0; i < lg->stack_pages; i++) + pin_page(lg, lg->esp1 - i * PAGE_SIZE); +} + +void guest_set_stack(struct lguest *lg, u32 seg, u32 esp, unsigned int pages) +{ + /* You cannot have a stack segment with priv level 0. */ + if ((seg & 0x3) != GUEST_PL) + kill_guest(lg, "bad stack segment %i", seg); + if (pages > 2) + kill_guest(lg, "bad stack pages %u", pages); + lg->ss1 = seg; + lg->esp1 = esp; + lg->stack_pages = pages; + pin_stack_pages(lg); +} + +/* Set up trap in IDT. */ +static void set_trap(struct lguest *lg, struct desc_struct *trap, + unsigned int num, u32 lo, u32 hi) +{ + u8 type = idt_type(lo, hi); + + if (!idt_present(lo, hi)) { + trap->a = trap->b = 0; + return; + } + + if (type != 0xE && type != 0xF) + kill_guest(lg, "bad IDT type %i", type); + + trap->a = ((__KERNEL_CS|GUEST_PL)<<16) | (lo&0x0000FFFF); + trap->b = (hi&0xFFFFEF00); +} + +void load_guest_idt_entry(struct lguest *lg, unsigned int num, u32 lo, u32 hi) +{ + /* Guest never handles: NMI, doublefault, hypercall, spurious irq. */ + if (num == 2 || num == 8 || num == 15 || num == LGUEST_TRAP_ENTRY) + return; + + lg->changed |= CHANGED_IDT; + if (num < ARRAY_SIZE(lg->idt)) + set_trap(lg, &lg->idt[num], num, lo, hi); + else if (num == SYSCALL_VECTOR) + set_trap(lg, &lg->syscall_idt, num, lo, hi); +} + +static void default_idt_entry(struct desc_struct *idt, + int trap, + const unsigned long handler) +{ + u32 flags = 0x8e00; + + /* They can't "int" into any of them except hypercall. */ + if (trap == LGUEST_TRAP_ENTRY) + flags |= (GUEST_PL << 13); + + idt->a = (LGUEST_CS<<16) | (handler&0x0000FFFF); + idt->b = (handler&0xFFFF0000) | flags; +} + +void setup_default_idt_entries(struct lguest_ro_state *state, + const unsigned long *def) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(state->guest_idt); i++) + default_idt_entry(&state->guest_idt[i], i, def[i]); +} + +void copy_traps(const struct lguest *lg, struct desc_struct *idt, + const unsigned long *def) +{ + unsigned int i; + + /* All hardware interrupts are same whatever the guest: only the + * traps might be different. */ + for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++) { + if (direct_trap(lg, &lg->idt[i], i)) + idt[i] = lg->idt[i]; + else + default_idt_entry(&idt[i], i, def[i]); + } + i = SYSCALL_VECTOR; + if (direct_trap(lg, &lg->syscall_idt, i)) + idt[i] = lg->syscall_idt; + else + default_idt_entry(&idt[i], i, def[i]); +} + +void guest_set_clockevent(struct lguest *lg, unsigned long delta) +{ + ktime_t expires; + + if (unlikely(delta == 0)) { + /* Clock event device is shutting down. */ + hrtimer_cancel(&lg->hrt); + return; + } + + expires = ktime_add_ns(ktime_get_real(), delta); + hrtimer_start(&lg->hrt, expires, HRTIMER_MODE_ABS); +} + +static enum hrtimer_restart clockdev_fn(struct hrtimer *timer) +{ + struct lguest *lg = container_of(timer, struct lguest, hrt); + + set_bit(0, lg->irqs_pending); + if (lg->halted) + wake_up_process(lg->tsk); + return HRTIMER_NORESTART; +} + +void init_clockdev(struct lguest *lg) +{ + hrtimer_init(&lg->hrt, CLOCK_REALTIME, HRTIMER_MODE_ABS); + lg->hrt.function = clockdev_fn; +} diff --git a/drivers/lguest/io.c b/drivers/lguest/io.c new file mode 100644 index 0000000..06bdba2 --- /dev/null +++ b/drivers/lguest/io.c @@ -0,0 +1,399 @@ +/* Simple I/O model for guests, based on shared memory. + * Copyright (C) 2006 Rusty Russell IBM Corporation + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <linux/types.h> +#include <linux/futex.h> +#include <linux/jhash.h> +#include <linux/mm.h> +#include <linux/highmem.h> +#include <linux/uaccess.h> +#include "lg.h" + +static struct list_head dma_hash[61]; + +void lguest_io_init(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dma_hash); i++) + INIT_LIST_HEAD(&dma_hash[i]); +} + +/* FIXME: allow multi-page lengths. */ +static int check_dma_list(struct lguest *lg, const struct lguest_dma *dma) +{ + unsigned int i; + + for (i = 0; i < LGUEST_MAX_DMA_SECTIONS; i++) { + if (!dma->len[i]) + return 1; + if (!lguest_address_ok(lg, dma->addr[i], dma->len[i])) + goto kill; + if (dma->len[i] > PAGE_SIZE) + goto kill; + /* We could do over a page, but is it worth it? */ + if ((dma->addr[i] % PAGE_SIZE) + dma->len[i] > PAGE_SIZE) + goto kill; + } + return 1; + +kill: + kill_guest(lg, "bad DMA entry: %u@%#lx", dma->len[i], dma->addr[i]); + return 0; +} + +static unsigned int hash(const union futex_key *key) +{ + return jhash2((u32*)&key->both.word, + (sizeof(key->both.word)+sizeof(key->both.ptr))/4, + key->both.offset) + % ARRAY_SIZE(dma_hash); +} + +static inline int key_eq(const union futex_key *a, const union futex_key *b) +{ + return (a->both.word == b->both.word + && a->both.ptr == b->both.ptr + && a->both.offset == b->both.offset); +} + +/* Must hold read lock on dmainfo owner's current->mm->mmap_sem */ +static void unlink_dma(struct lguest_dma_info *dmainfo) +{ + BUG_ON(!mutex_is_locked(&lguest_lock)); + dmainfo->interrupt = 0; + list_del(&dmainfo->list); + drop_futex_key_refs(&dmainfo->key); +} + +static int unbind_dma(struct lguest *lg, + const union futex_key *key, + unsigned long dmas) +{ + int i, ret = 0; + + for (i = 0; i < LGUEST_MAX_DMA; i++) { + if (key_eq(key, &lg->dma[i].key) && dmas == lg->dma[i].dmas) { + unlink_dma(&lg->dma[i]); + ret = 1; + break; + } + } + return ret; +} + +int bind_dma(struct lguest *lg, + unsigned long ukey, unsigned long dmas, u16 numdmas, u8 interrupt) +{ + unsigned int i; + int ret = 0; + union futex_key key; + struct rw_semaphore *fshared = ¤t->mm->mmap_sem; + + if (interrupt >= LGUEST_IRQS) + return 0; + + mutex_lock(&lguest_lock); + down_read(fshared); + if (get_futex_key((u32 __user *)ukey, fshared, &key) != 0) { + kill_guest(lg, "bad dma key %#lx", ukey); + goto unlock; + } + get_futex_key_refs(&key); + + if (interrupt == 0) + ret = unbind_dma(lg, &key, dmas); + else { + for (i = 0; i < LGUEST_MAX_DMA; i++) { + if (lg->dma[i].interrupt) + continue; + + lg->dma[i].dmas = dmas; + lg->dma[i].num_dmas = numdmas; + lg->dma[i].next_dma = 0; + lg->dma[i].key = key; + lg->dma[i].guestid = lg->guestid; + lg->dma[i].interrupt = interrupt; + list_add(&lg->dma[i].list, &dma_hash[hash(&key)]); + ret = 1; + goto unlock; + } + } + drop_futex_key_refs(&key); +unlock: + up_read(fshared); + mutex_unlock(&lguest_lock); + return ret; +} + +/* lgread from another guest */ +static int lgread_other(struct lguest *lg, + void *buf, u32 addr, unsigned bytes) +{ + if (!lguest_address_ok(lg, addr, bytes) + || access_process_vm(lg->tsk, addr, buf, bytes, 0) != bytes) { + memset(buf, 0, bytes); + kill_guest(lg, "bad address in registered DMA struct"); + return 0; + } + return 1; +} + +/* lgwrite to another guest */ +static int lgwrite_other(struct lguest *lg, u32 addr, + const void *buf, unsigned bytes) +{ + if (!lguest_address_ok(lg, addr, bytes) + || (access_process_vm(lg->tsk, addr, (void *)buf, bytes, 1) + != bytes)) { + kill_guest(lg, "bad address writing to registered DMA"); + return 0; + } + return 1; +} + +static u32 copy_data(struct lguest *srclg, + const struct lguest_dma *src, + const struct lguest_dma *dst, + struct page *pages[]) +{ + unsigned int totlen, si, di, srcoff, dstoff; + void *maddr = NULL; + + totlen = 0; + si = di = 0; + srcoff = dstoff = 0; + while (si < LGUEST_MAX_DMA_SECTIONS && src->len[si] + && di < LGUEST_MAX_DMA_SECTIONS && dst->len[di]) { + u32 len = min(src->len[si] - srcoff, dst->len[di] - dstoff); + + if (!maddr) + maddr = kmap(pages[di]); + + /* FIXME: This is not completely portable, since + archs do different things for copy_to_user_page. */ + if (copy_from_user(maddr + (dst->addr[di] + dstoff)%PAGE_SIZE, + (void *__user)src->addr[si], len) != 0) { + kill_guest(srclg, "bad address in sending DMA"); + totlen = 0; + break; + } + + totlen += len; + srcoff += len; + dstoff += len; + if (srcoff == src->len[si]) { + si++; + srcoff = 0; + } + if (dstoff == dst->len[di]) { + kunmap(pages[di]); + maddr = NULL; + di++; + dstoff = 0; + } + } + + if (maddr) + kunmap(pages[di]); + + return totlen; +} + +/* Src is us, ie. current. */ +static u32 do_dma(struct lguest *srclg, const struct lguest_dma *src, + struct lguest *dstlg, const struct lguest_dma *dst) +{ + int i; + u32 ret; + struct page *pages[LGUEST_MAX_DMA_SECTIONS]; + + if (!check_dma_list(dstlg, dst) || !check_dma_list(srclg, src)) + return 0; + + /* First get the destination pages */ + for (i = 0; i < LGUEST_MAX_DMA_SECTIONS; i++) { + if (dst->len[i] == 0) + break; + if (get_user_pages(dstlg->tsk, dstlg->mm, + dst->addr[i], 1, 1, 1, pages+i, NULL) + != 1) { + kill_guest(dstlg, "Error mapping DMA pages"); + ret = 0; + goto drop_pages; + } + } + + /* Now copy until we run out of src or dst. */ + ret = copy_data(srclg, src, dst, pages); + +drop_pages: + while (--i >= 0) + put_page(pages[i]); + return ret; +} + +static int dma_transfer(struct lguest *srclg, + unsigned long udma, + struct lguest_dma_info *dst) +{ + struct lguest_dma dst_dma, src_dma; + struct lguest *dstlg; + u32 i, dma = 0; + + dstlg = &lguests[dst->guestid]; + /* Get our dma list. */ + lgread(srclg, &src_dma, udma, sizeof(src_dma)); + + /* We can't deadlock against them dmaing to us, because this + * is all under the lguest_lock. */ + down_read(&dstlg->mm->mmap_sem); + + for (i = 0; i < dst->num_dmas; i++) { + dma = (dst->next_dma + i) % dst->num_dmas; + if (!lgread_other(dstlg, &dst_dma, + dst->dmas + dma * sizeof(struct lguest_dma), + sizeof(dst_dma))) { + goto fail; + } + if (!dst_dma.used_len) + break; + } + if (i != dst->num_dmas) { + unsigned long used_lenp; + unsigned int ret; + + ret = do_dma(srclg, &src_dma, dstlg, &dst_dma); + /* Put used length in src. */ + lgwrite_u32(srclg, + udma+offsetof(struct lguest_dma, used_len), ret); + if (ret == 0 && src_dma.len[0] != 0) + goto fail; + + /* Make sure destination sees contents before length. */ + wmb(); + used_lenp = dst->dmas + + dma * sizeof(struct lguest_dma) + + offsetof(struct lguest_dma, used_len); + lgwrite_other(dstlg, used_lenp, &ret, sizeof(ret)); + dst->next_dma++; + } + up_read(&dstlg->mm->mmap_sem); + + /* Do this last so dst doesn't simply sleep on lock. */ + set_bit(dst->interrupt, dstlg->irqs_pending); + wake_up_process(dstlg->tsk); + return i == dst->num_dmas; + +fail: + up_read(&dstlg->mm->mmap_sem); + return 0; +} + +void send_dma(struct lguest *lg, unsigned long ukey, unsigned long udma) +{ + union futex_key key; + int empty = 0; + struct rw_semaphore *fshared = ¤t->mm->mmap_sem; + +again: + mutex_lock(&lguest_lock); + down_read(fshared); + if (get_futex_key((u32 __user *)ukey, fshared, &key) != 0) { + kill_guest(lg, "bad sending DMA key"); + goto unlock; + } + /* Shared mapping? Look for other guests... */ + if (key.shared.offset & 1) { + struct lguest_dma_info *i; + list_for_each_entry(i, &dma_hash[hash(&key)], list) { + if (i->guestid == lg->guestid) + continue; + if (!key_eq(&key, &i->key)) + continue; + + empty += dma_transfer(lg, udma, i); + break; + } + if (empty == 1) { + /* Give any recipients one chance to restock. */ + up_read(¤t->mm->mmap_sem); + mutex_unlock(&lguest_lock); + empty++; + goto again; + } + } else { + /* Private mapping: tell our userspace. */ + lg->dma_is_pending = 1; + lg->pending_dma = udma; + lg->pending_key = ukey; + } +unlock: + up_read(fshared); + mutex_unlock(&lguest_lock); +} + +void release_all_dma(struct lguest *lg) +{ + unsigned int i; + + BUG_ON(!mutex_is_locked(&lguest_lock)); + + down_read(&lg->mm->mmap_sem); + for (i = 0; i < LGUEST_MAX_DMA; i++) { + if (lg->dma[i].interrupt) + unlink_dma(&lg->dma[i]); + } + up_read(&lg->mm->mmap_sem); +} + +/* Userspace wants a dma buffer from this guest. */ +unsigned long get_dma_buffer(struct lguest *lg, + unsigned long ukey, unsigned long *interrupt) +{ + unsigned long ret = 0; + union futex_key key; + struct lguest_dma_info *i; + struct rw_semaphore *fshared = ¤t->mm->mmap_sem; + + mutex_lock(&lguest_lock); + down_read(fshared); + if (get_futex_key((u32 __user *)ukey, fshared, &key) != 0) { + kill_guest(lg, "bad registered DMA buffer"); + goto unlock; + } + list_for_each_entry(i, &dma_hash[hash(&key)], list) { + if (key_eq(&key, &i->key) && i->guestid == lg->guestid) { + unsigned int j; + for (j = 0; j < i->num_dmas; j++) { + struct lguest_dma dma; + + ret = i->dmas + j * sizeof(struct lguest_dma); + lgread(lg, &dma, ret, sizeof(dma)); + if (dma.used_len == 0) + break; + } + *interrupt = i->interrupt; + break; + } + } +unlock: + up_read(fshared); + mutex_unlock(&lguest_lock); + return ret; +} + diff --git a/drivers/lguest/lg.h b/drivers/lguest/lg.h new file mode 100644 index 0000000..3e2ddfb --- /dev/null +++ b/drivers/lguest/lg.h @@ -0,0 +1,261 @@ +#ifndef _LGUEST_H +#define _LGUEST_H + +#include <asm/desc.h> + +#define GDT_ENTRY_LGUEST_CS 10 +#define GDT_ENTRY_LGUEST_DS 11 +#define LGUEST_CS (GDT_ENTRY_LGUEST_CS * 8) +#define LGUEST_DS (GDT_ENTRY_LGUEST_DS * 8) + +#ifndef __ASSEMBLY__ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/stringify.h> +#include <linux/binfmts.h> +#include <linux/futex.h> +#include <linux/lguest.h> +#include <linux/lguest_launcher.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <asm/semaphore.h> +#include "irq_vectors.h" + +#define GUEST_PL 1 + +struct lguest_regs +{ + /* Manually saved part. */ + unsigned long ebx, ecx, edx; + unsigned long esi, edi, ebp; + unsigned long gs; + unsigned long eax; + unsigned long fs, ds, es; + unsigned long trapnum, errcode; + /* Trap pushed part */ + unsigned long eip; + unsigned long cs; + unsigned long eflags; + unsigned long esp; + unsigned long ss; +}; + +void free_pagetables(void); +int init_pagetables(struct page **switcher_page, unsigned int pages); + +/* Full 4G segment descriptors, suitable for CS and DS. */ +#define FULL_EXEC_SEGMENT ((struct desc_struct){0x0000ffff, 0x00cf9b00}) +#define FULL_SEGMENT ((struct desc_struct){0x0000ffff, 0x00cf9300}) + +struct lguest_dma_info +{ + struct list_head list; + union futex_key key; + unsigned long dmas; + u16 next_dma; + u16 num_dmas; + u16 guestid; + u8 interrupt; /* 0 when not registered */ +}; + +/* We have separate types for the guest's ptes & pgds and the shadow ptes & + * pgds. Since this host might use three-level pagetables and the guest and + * shadow pagetables don't, we can't use the normal pte_t/pgd_t. */ +typedef union { + struct { unsigned flags:12, pfn:20; }; + struct { unsigned long val; } raw; +} spgd_t; +typedef union { + struct { unsigned flags:12, pfn:20; }; + struct { unsigned long val; } raw; +} spte_t; +typedef union { + struct { unsigned flags:12, pfn:20; }; + struct { unsigned long val; } raw; +} gpgd_t; +typedef union { + struct { unsigned flags:12, pfn:20; }; + struct { unsigned long val; } raw; +} gpte_t; +#define mkgpte(_val) ((gpte_t){.raw.val = _val}) +#define mkgpgd(_val) ((gpgd_t){.raw.val = _val}) + +struct pgdir +{ + unsigned long cr3; + spgd_t *pgdir; +}; + +/* This is a guest-specific page (mapped ro) into the guest. */ +struct lguest_ro_state +{ + /* Host information we need to restore when we switch back. */ + u32 host_cr3; + struct Xgt_desc_struct host_idt_desc; + struct Xgt_desc_struct host_gdt_desc; + u32 host_sp; + + /* Fields which are used when guest is running. */ + struct Xgt_desc_struct guest_idt_desc; + struct Xgt_desc_struct guest_gdt_desc; + struct i386_hw_tss guest_tss; + struct desc_struct guest_idt[IDT_ENTRIES]; + struct desc_struct guest_gdt[GDT_ENTRIES]; +}; + +/* We have two pages shared with guests, per cpu. */ +struct lguest_pages +{ + /* This is the stack page mapped rw in guest */ + char spare[PAGE_SIZE - sizeof(struct lguest_regs)]; + struct lguest_regs regs; + + /* This is the host state & guest descriptor page, ro in guest */ + struct lguest_ro_state state; +} __attribute__((aligned(PAGE_SIZE))); + +#define CHANGED_IDT 1 +#define CHANGED_GDT 2 +#define CHANGED_GDT_TLS 4 /* Actually a subset of CHANGED_GDT */ +#define CHANGED_ALL 3 + +/* The private info the thread maintains about the guest. */ +struct lguest +{ + /* At end of a page shared mapped over lguest_pages in guest. */ + unsigned long regs_page; + struct lguest_regs *regs; + struct lguest_data __user *lguest_data; + struct task_struct *tsk; + struct mm_struct *mm; /* == tsk->mm, but that becomes NULL on exit */ + u16 guestid; + u32 pfn_limit; + u32 page_offset; + u32 cr2; + int halted; + int ts; + u32 next_hcall; + u32 esp1; + u8 ss1; + + /* Do we need to stop what we're doing and return to userspace? */ + int break_out; + wait_queue_head_t break_wq; + + /* Bitmap of what has changed: see CHANGED_* above. */ + int changed; + struct lguest_pages *last_pages; + + /* We keep a small number of these. */ + u32 pgdidx; + struct pgdir pgdirs[4]; + + /* Cached wakeup: we hold a reference to this task. */ + struct task_struct *wake; + + unsigned long noirq_start, noirq_end; + int dma_is_pending; + unsigned long pending_dma; /* struct lguest_dma */ + unsigned long pending_key; /* address they're sending to */ + + unsigned int stack_pages; + u32 tsc_khz; + + struct lguest_dma_info dma[LGUEST_MAX_DMA]; + + /* Dead? */ + const char *dead; + + /* The GDT entries copied into lguest_ro_state when running. */ + struct desc_struct gdt[GDT_ENTRIES]; + + /* The IDT entries: some copied into lguest_ro_state when running. */ + struct desc_struct idt[FIRST_EXTERNAL_VECTOR+LGUEST_IRQS]; + struct desc_struct syscall_idt; + + /* Virtual clock device */ + struct hrtimer hrt; + + /* Pending virtual interrupts */ + DECLARE_BITMAP(irqs_pending, LGUEST_IRQS); +}; + +extern struct lguest lguests[]; +extern struct mutex lguest_lock; + +/* core.c: */ +u32 lgread_u32(struct lguest *lg, unsigned long addr); +void lgwrite_u32(struct lguest *lg, unsigned long addr, u32 val); +void lgread(struct lguest *lg, void *buf, unsigned long addr, unsigned len); +void lgwrite(struct lguest *lg, unsigned long, const void *buf, unsigned len); +int find_free_guest(void); +int lguest_address_ok(const struct lguest *lg, + unsigned long addr, unsigned long len); +int run_guest(struct lguest *lg, unsigned long __user *user); + + +/* interrupts_and_traps.c: */ +void maybe_do_interrupt(struct lguest *lg); +int deliver_trap(struct lguest *lg, unsigned int num); +void load_guest_idt_entry(struct lguest *lg, unsigned int i, u32 low, u32 hi); +void guest_set_stack(struct lguest *lg, u32 seg, u32 esp, unsigned int pages); +void pin_stack_pages(struct lguest *lg); +void setup_default_idt_entries(struct lguest_ro_state *state, + const unsigned long *def); +void copy_traps(const struct lguest *lg, struct desc_struct *idt, + const unsigned long *def); +void guest_set_clockevent(struct lguest *lg, unsigned long delta); +void init_clockdev(struct lguest *lg); + +/* segments.c: */ +void setup_default_gdt_entries(struct lguest_ro_state *state); +void setup_guest_gdt(struct lguest *lg); +void load_guest_gdt(struct lguest *lg, unsigned long table, u32 num); +void guest_load_tls(struct lguest *lg, unsigned long tls_array); +void copy_gdt(const struct lguest *lg, struct desc_struct *gdt); +void copy_gdt_tls(const struct lguest *lg, struct desc_struct *gdt); + +/* page_tables.c: */ +int init_guest_pagetable(struct lguest *lg, unsigned long pgtable); +void free_guest_pagetable(struct lguest *lg); +void guest_new_pagetable(struct lguest *lg, unsigned long pgtable); +void guest_set_pmd(struct lguest *lg, unsigned long cr3, u32 i); +void guest_pagetable_clear_all(struct lguest *lg); +void guest_pagetable_flush_user(struct lguest *lg); +void guest_set_pte(struct lguest *lg, unsigned long cr3, + unsigned long vaddr, gpte_t val); +void map_switcher_in_guest(struct lguest *lg, struct lguest_pages *pages); +int demand_page(struct lguest *info, unsigned long cr2, int errcode); +void pin_page(struct lguest *lg, unsigned long vaddr); + +/* lguest_user.c: */ +int lguest_device_init(void); +void lguest_device_remove(void); + +/* io.c: */ +void lguest_io_init(void); +int bind_dma(struct lguest *lg, + unsigned long key, unsigned long udma, u16 numdmas, u8 interrupt); +void send_dma(struct lguest *info, unsigned long key, unsigned long udma); +void release_all_dma(struct lguest *lg); +unsigned long get_dma_buffer(struct lguest *lg, unsigned long key, + unsigned long *interrupt); + +/* hypercalls.c: */ +void do_hypercalls(struct lguest *lg); + +#define kill_guest(lg, fmt...) \ +do { \ + if (!(lg)->dead) { \ + (lg)->dead = kasprintf(GFP_ATOMIC, fmt); \ + if (!(lg)->dead) \ + (lg)->dead = ERR_PTR(-ENOMEM); \ + } \ +} while(0) + +static inline unsigned long guest_pa(struct lguest *lg, unsigned long vaddr) +{ + return vaddr - lg->page_offset; +} +#endif /* __ASSEMBLY__ */ +#endif /* _LGUEST_H */ diff --git a/drivers/lguest/lguest.c b/drivers/lguest/lguest.c new file mode 100644 index 0000000..b9a58b7 --- /dev/null +++ b/drivers/lguest/lguest.c @@ -0,0 +1,621 @@ +/* + * Lguest specific paravirt-ops implementation + * + * Copyright (C) 2006, Rusty Russell <rusty@rustcorp.com.au> IBM Corporation. + * + * 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, GOOD TITLE or + * NON INFRINGEMENT. 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/kernel.h> +#include <linux/start_kernel.h> +#include <linux/string.h> +#include <linux/console.h> +#include <linux/screen_info.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/lguest.h> +#include <linux/lguest_launcher.h> +#include <linux/lguest_bus.h> +#include <asm/paravirt.h> +#include <asm/param.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/desc.h> +#include <asm/setup.h> +#include <asm/e820.h> +#include <asm/mce.h> +#include <asm/io.h> +//#include <asm/sched-clock.h> + +/* Declarations for definitions in lguest_guest.S */ +extern char lguest_noirq_start[], lguest_noirq_end[]; +extern const char lgstart_cli[], lgend_cli[]; +extern const char lgstart_sti[], lgend_sti[]; +extern const char lgstart_popf[], lgend_popf[]; +extern const char lgstart_pushf[], lgend_pushf[]; +extern const char lgstart_iret[], lgend_iret[]; +extern void lguest_iret(void); + +struct lguest_data lguest_data = { + .hcall_status = { [0 ... LHCALL_RING_SIZE-1] = 0xFF }, + .noirq_start = (u32)lguest_noirq_start, + .noirq_end = (u32)lguest_noirq_end, + .blocked_interrupts = { 1 }, /* Block timer interrupts */ +}; +struct lguest_device_desc *lguest_devices; + +static enum paravirt_lazy_mode lazy_mode; +static void lguest_lazy_mode(enum paravirt_lazy_mode mode) +{ + if (mode == PARAVIRT_LAZY_FLUSH) { + if (unlikely(lazy_mode != PARAVIRT_LAZY_NONE)) + hcall(LHCALL_FLUSH_ASYNC, 0, 0, 0); + } else { + lazy_mode = mode; + if (mode == PARAVIRT_LAZY_NONE) + hcall(LHCALL_FLUSH_ASYNC, 0, 0, 0); + } +} + +static void lazy_hcall(unsigned long call, + unsigned long arg1, + unsigned long arg2, + unsigned long arg3) +{ + if (lazy_mode == PARAVIRT_LAZY_NONE) + hcall(call, arg1, arg2, arg3); + else + async_hcall(call, arg1, arg2, arg3); +} + +void async_hcall(unsigned long call, + unsigned long arg1, unsigned long arg2, unsigned long arg3) +{ + /* Note: This code assumes we're uniprocessor. */ + static unsigned int next_call; + unsigned long flags; + + local_irq_save(flags); + if (lguest_data.hcall_status[next_call] != 0xFF) { + /* Table full, so do normal hcall which will flush table. */ + hcall(call, arg1, arg2, arg3); + } else { + lguest_data.hcalls[next_call].eax = call; + lguest_data.hcalls[next_call].edx = arg1; + lguest_data.hcalls[next_call].ebx = arg2; + lguest_data.hcalls[next_call].ecx = arg3; + /* Make sure host sees arguments before "valid" flag. */ + wmb(); + lguest_data.hcall_status[next_call] = 0; + if (++next_call == LHCALL_RING_SIZE) + next_call = 0; + } + local_irq_restore(flags); +} + +void lguest_send_dma(unsigned long key, struct lguest_dma *dma) +{ + dma->used_len = 0; + hcall(LHCALL_SEND_DMA, key, __pa(dma), 0); +} + +int lguest_bind_dma(unsigned long key, struct lguest_dma *dmas, + unsigned int num, u8 irq) +{ + if (!hcall(LHCALL_BIND_DMA, key, __pa(dmas), (num << 8) | irq)) + return -ENOMEM; + return 0; +} + +void lguest_unbind_dma(unsigned long key, struct lguest_dma *dmas) +{ + hcall(LHCALL_BIND_DMA, key, __pa(dmas), 0); +} + +/* For guests, device memory can be used as normal memory, so we cast away the + * __iomem to quieten sparse. */ +void *lguest_map(unsigned long phys_addr, unsigned long pages) +{ + return (__force void *)ioremap(phys_addr, PAGE_SIZE*pages); +} + +void lguest_unmap(void *addr) +{ + iounmap((__force void __iomem *)addr); +} + +static unsigned long save_fl(void) +{ + return lguest_data.irq_enabled; +} + +static void restore_fl(unsigned long flags) +{ + /* FIXME: Check if interrupt pending... */ + lguest_data.irq_enabled = flags; +} + +static void irq_disable(void) +{ + lguest_data.irq_enabled = 0; +} + +static void irq_enable(void) +{ + /* FIXME: Check if interrupt pending... */ + lguest_data.irq_enabled = X86_EFLAGS_IF; +} + +static void lguest_write_idt_entry(struct desc_struct *dt, + int entrynum, u32 low, u32 high) +{ + write_dt_entry(dt, entrynum, low, high); + hcall(LHCALL_LOAD_IDT_ENTRY, entrynum, low, high); +} + +static void lguest_load_idt(const struct Xgt_desc_struct *desc) +{ + unsigned int i; + struct desc_struct *idt = (void *)desc->address; + + for (i = 0; i < (desc->size+1)/8; i++) + hcall(LHCALL_LOAD_IDT_ENTRY, i, idt[i].a, idt[i].b); +} + +static void lguest_load_gdt(const struct Xgt_desc_struct *desc) +{ + BUG_ON((desc->size+1)/8 != GDT_ENTRIES); + hcall(LHCALL_LOAD_GDT, __pa(desc->address), GDT_ENTRIES, 0); +} + +static void lguest_write_gdt_entry(struct desc_struct *dt, + int entrynum, u32 low, u32 high) +{ + write_dt_entry(dt, entrynum, low, high); + hcall(LHCALL_LOAD_GDT, __pa(dt), GDT_ENTRIES, 0); +} + +static void lguest_load_tls(struct thread_struct *t, unsigned int cpu) +{ + lazy_hcall(LHCALL_LOAD_TLS, __pa(&t->tls_array), cpu, 0); +} + +static void lguest_set_ldt(const void *addr, unsigned entries) +{ +} + +static void lguest_load_tr_desc(void) +{ +} + +static void lguest_cpuid(unsigned int *eax, unsigned int *ebx, + unsigned int *ecx, unsigned int *edx) +{ + int function = *eax; + + native_cpuid(eax, ebx, ecx, edx); + switch (function) { + case 1: /* Basic feature request. */ + /* We only allow kernel to see SSE3, CMPXCHG16B and SSSE3 */ + *ecx &= 0x00002201; + /* SSE, SSE2, FXSR, MMX, CMOV, CMPXCHG8B, FPU. */ + *edx &= 0x07808101; + /* Host wants to know when we flush kernel pages: set PGE. */ + *edx |= 0x00002000; + break; + case 0x80000000: + /* Futureproof this a little: if they ask how much extended + * processor information, limit it to known fields. */ + if (*eax > 0x80000008) + *eax = 0x80000008; + break; + } +} + +static unsigned long current_cr0, current_cr3; +static void lguest_write_cr0(unsigned long val) +{ + lazy_hcall(LHCALL_TS, val & 8, 0, 0); + current_cr0 = val; +} + +static unsigned long lguest_read_cr0(void) +{ + return current_cr0; +} + +static void lguest_clts(void) +{ + lazy_hcall(LHCALL_TS, 0, 0, 0); + current_cr0 &= ~8U; +} + +static unsigned long lguest_read_cr2(void) +{ + return lguest_data.cr2; +} + +static void lguest_write_cr3(unsigned long cr3) +{ + lazy_hcall(LHCALL_NEW_PGTABLE, cr3, 0, 0); + current_cr3 = cr3; +} + +static unsigned long lguest_read_cr3(void) +{ + return current_cr3; +} + +/* Used to enable/disable PGE, but we don't care. */ +static unsigned long lguest_read_cr4(void) +{ + return 0; +} + +static void lguest_write_cr4(unsigned long val) +{ +} + +static void lguest_set_pte_at(struct mm_struct *mm, unsigned long addr, + pte_t *ptep, pte_t pteval) +{ + *ptep = pteval; + lazy_hcall(LHCALL_SET_PTE, __pa(mm->pgd), addr, pteval.pte_low); +} + +/* We only support two-level pagetables at the moment. */ +static void lguest_set_pmd(pmd_t *pmdp, pmd_t pmdval) +{ + *pmdp = pmdval; + lazy_hcall(LHCALL_SET_PMD, __pa(pmdp)&PAGE_MASK, + (__pa(pmdp)&(PAGE_SIZE-1))/4, 0); +} + +/* FIXME: Eliminate all callers of this. */ +static void lguest_set_pte(pte_t *ptep, pte_t pteval) +{ + *ptep = pteval; + /* Don't bother with hypercall before initial setup. */ + if (current_cr3) + lazy_hcall(LHCALL_FLUSH_TLB, 1, 0, 0); +} + +static void lguest_flush_tlb_single(unsigned long addr) +{ + /* Simply set it to zero, and it will fault back in. */ + lazy_hcall(LHCALL_SET_PTE, current_cr3, addr, 0); +} + +static void lguest_flush_tlb_user(void) +{ + lazy_hcall(LHCALL_FLUSH_TLB, 0, 0, 0); +} + +static void lguest_flush_tlb_kernel(void) +{ + lazy_hcall(LHCALL_FLUSH_TLB, 1, 0, 0); +} + +static void disable_lguest_irq(unsigned int irq) +{ + set_bit(irq, lguest_data.blocked_interrupts); +} + +static void enable_lguest_irq(unsigned int irq) +{ + clear_bit(irq, lguest_data.blocked_interrupts); + /* FIXME: If it's pending? */ +} + +static struct irq_chip lguest_irq_controller = { + .name = "lguest", + .mask = disable_lguest_irq, + .mask_ack = disable_lguest_irq, + .unmask = enable_lguest_irq, +}; + +static void __init lguest_init_IRQ(void) +{ + unsigned int i; + + for (i = 0; i < LGUEST_IRQS; i++) { + int vector = FIRST_EXTERNAL_VECTOR + i; + if (vector != SYSCALL_VECTOR) { + set_intr_gate(vector, interrupt[i]); + set_irq_chip_and_handler(i, &lguest_irq_controller, + handle_level_irq); + } + } + irq_ctx_init(smp_processor_id()); +} + +static unsigned long lguest_get_wallclock(void) +{ + return hcall(LHCALL_GET_WALLCLOCK, 0, 0, 0); +} + +static cycle_t lguest_clock_read(void) +{ + if (lguest_data.tsc_khz) + return native_read_tsc(); + else + return jiffies; +} + +/* This is what we tell the kernel is our clocksource. */ +static struct clocksource lguest_clock = { + .name = "lguest", + .rating = 400, + .read = lguest_clock_read, +}; + +/* We also need a "struct clock_event_device": Linux asks us to set it to go + * off some time in the future. Actually, James Morris figured all this out, I + * just applied the patch. */ +static int lguest_clockevent_set_next_event(unsigned long delta, + struct clock_event_device *evt) +{ + if (delta < LG_CLOCK_MIN_DELTA) { + if (printk_ratelimit()) + printk(KERN_DEBUG "%s: small delta %lu ns\n", + __FUNCTION__, delta); + return -ETIME; + } + hcall(LHCALL_SET_CLOCKEVENT, delta, 0, 0); + return 0; +} + +static void lguest_clockevent_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + switch (mode) { + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + /* A 0 argument shuts the clock down. */ + hcall(LHCALL_SET_CLOCKEVENT, 0, 0, 0); + break; + case CLOCK_EVT_MODE_ONESHOT: + /* This is what we expect. */ + break; + case CLOCK_EVT_MODE_PERIODIC: + BUG(); + } +} + +/* This describes our primitive timer chip. */ +static struct clock_event_device lguest_clockevent = { + .name = "lguest", + .features = CLOCK_EVT_FEAT_ONESHOT, + .set_next_event = lguest_clockevent_set_next_event, + .set_mode = lguest_clockevent_set_mode, + .rating = INT_MAX, + .mult = 1, + .shift = 0, + .min_delta_ns = LG_CLOCK_MIN_DELTA, + .max_delta_ns = LG_CLOCK_MAX_DELTA, +}; + +/* This is the Guest timer interrupt handler (hardware interrupt 0). We just + * call the clockevent infrastructure and it does whatever needs doing. */ +static void lguest_time_irq(unsigned int irq, struct irq_desc *desc) +{ + unsigned long flags; + + /* Don't interrupt us while this is running. */ + local_irq_save(flags); + lguest_clockevent.event_handler(&lguest_clockevent); + local_irq_restore(flags); +} + +static void lguest_time_init(void) +{ + set_irq_handler(0, lguest_time_irq); + + /* We use the TSC if the Host tells us we can, otherwise a dumb + * jiffies-based clock. */ + if (lguest_data.tsc_khz) { + lguest_clock.shift = 22; + lguest_clock.mult = clocksource_khz2mult(lguest_data.tsc_khz, + lguest_clock.shift); + lguest_clock.mask = CLOCKSOURCE_MASK(64); + lguest_clock.flags = CLOCK_SOURCE_IS_CONTINUOUS; + } else { + /* To understand this, start at kernel/time/jiffies.c... */ + lguest_clock.shift = 8; + lguest_clock.mult = (((u64)NSEC_PER_SEC<<8)/ACTHZ) << 8; + lguest_clock.mask = CLOCKSOURCE_MASK(32); + } + clocksource_register(&lguest_clock); + + /* We can't set cpumask in the initializer: damn C limitations! */ + lguest_clockevent.cpumask = cpumask_of_cpu(0); + clockevents_register_device(&lguest_clockevent); + + enable_lguest_irq(0); +} + +static void lguest_load_esp0(struct tss_struct *tss, + struct thread_struct *thread) +{ + lazy_hcall(LHCALL_SET_STACK, __KERNEL_DS|0x1, thread->esp0, + THREAD_SIZE/PAGE_SIZE); +} + +static void lguest_set_debugreg(int regno, unsigned long value) +{ + /* FIXME: Implement */ +} + +static void lguest_wbinvd(void) +{ +} + +#ifdef CONFIG_X86_LOCAL_APIC +static void lguest_apic_write(unsigned long reg, unsigned long v) +{ +} + +static unsigned long lguest_apic_read(unsigned long reg) +{ + return 0; +} +#endif + +static void lguest_safe_halt(void) +{ + hcall(LHCALL_HALT, 0, 0, 0); +} + +static void lguest_power_off(void) +{ + hcall(LHCALL_CRASH, __pa("Power down"), 0, 0); +} + +static int lguest_panic(struct notifier_block *nb, unsigned long l, void *p) +{ + hcall(LHCALL_CRASH, __pa(p), 0, 0); + return NOTIFY_DONE; +} + +static struct notifier_block paniced = { + .notifier_call = lguest_panic +}; + +static __init char *lguest_memory_setup(void) +{ + /* We do this here because lockcheck barfs if before start_kernel */ + atomic_notifier_chain_register(&panic_notifier_list, &paniced); + + add_memory_region(E820_MAP->addr, E820_MAP->size, E820_MAP->type); + return "LGUEST"; +} + +static const struct lguest_insns +{ + const char *start, *end; +} lguest_insns[] = { + [PARAVIRT_PATCH(irq_disable)] = { lgstart_cli, lgend_cli }, + [PARAVIRT_PATCH(irq_enable)] = { lgstart_sti, lgend_sti }, + [PARAVIRT_PATCH(restore_fl)] = { lgstart_popf, lgend_popf }, + [PARAVIRT_PATCH(save_fl)] = { lgstart_pushf, lgend_pushf }, +}; +static unsigned lguest_patch(u8 type, u16 clobber, void *insns, unsigned len) +{ + unsigned int insn_len; + + /* Don't touch it if we don't have a replacement */ + if (type >= ARRAY_SIZE(lguest_insns) || !lguest_insns[type].start) + return paravirt_patch_default(type, clobber, insns, len); + + insn_len = lguest_insns[type].end - lguest_insns[type].start; + + /* Similarly if we can't fit replacement. */ + if (len < insn_len) + return paravirt_patch_default(type, clobber, insns, len); + + memcpy(insns, lguest_insns[type].start, insn_len); + return insn_len; +} + +__init void lguest_init(void *boot) +{ + /* Copy boot parameters first. */ + memcpy(&boot_params, boot, PARAM_SIZE); + memcpy(boot_command_line, __va(boot_params.hdr.cmd_line_ptr), + COMMAND_LINE_SIZE); + + paravirt_ops.name = "lguest"; + paravirt_ops.paravirt_enabled = 1; + paravirt_ops.kernel_rpl = 1; + + paravirt_ops.save_fl = save_fl; + paravirt_ops.restore_fl = restore_fl; + paravirt_ops.irq_disable = irq_disable; + paravirt_ops.irq_enable = irq_enable; + paravirt_ops.load_gdt = lguest_load_gdt; + paravirt_ops.memory_setup = lguest_memory_setup; + paravirt_ops.cpuid = lguest_cpuid; + paravirt_ops.write_cr3 = lguest_write_cr3; + paravirt_ops.flush_tlb_user = lguest_flush_tlb_user; + paravirt_ops.flush_tlb_single = lguest_flush_tlb_single; + paravirt_ops.flush_tlb_kernel = lguest_flush_tlb_kernel; + paravirt_ops.set_pte = lguest_set_pte; + paravirt_ops.set_pte_at = lguest_set_pte_at; + paravirt_ops.set_pmd = lguest_set_pmd; +#ifdef CONFIG_X86_LOCAL_APIC + paravirt_ops.apic_write = lguest_apic_write; + paravirt_ops.apic_write_atomic = lguest_apic_write; + paravirt_ops.apic_read = lguest_apic_read; +#endif + paravirt_ops.load_idt = lguest_load_idt; + paravirt_ops.iret = lguest_iret; + paravirt_ops.load_esp0 = lguest_load_esp0; + paravirt_ops.load_tr_desc = lguest_load_tr_desc; + paravirt_ops.set_ldt = lguest_set_ldt; + paravirt_ops.load_tls = lguest_load_tls; + paravirt_ops.set_debugreg = lguest_set_debugreg; + paravirt_ops.clts = lguest_clts; + paravirt_ops.read_cr0 = lguest_read_cr0; + paravirt_ops.write_cr0 = lguest_write_cr0; + paravirt_ops.init_IRQ = lguest_init_IRQ; + paravirt_ops.read_cr2 = lguest_read_cr2; + paravirt_ops.read_cr3 = lguest_read_cr3; + paravirt_ops.read_cr4 = lguest_read_cr4; + paravirt_ops.write_cr4 = lguest_write_cr4; + paravirt_ops.write_gdt_entry = lguest_write_gdt_entry; + paravirt_ops.write_idt_entry = lguest_write_idt_entry; + paravirt_ops.patch = lguest_patch; + paravirt_ops.safe_halt = lguest_safe_halt; + paravirt_ops.get_wallclock = lguest_get_wallclock; + paravirt_ops.time_init = lguest_time_init; + paravirt_ops.set_lazy_mode = lguest_lazy_mode; + paravirt_ops.wbinvd = lguest_wbinvd; + + hcall(LHCALL_LGUEST_INIT, __pa(&lguest_data), 0, 0); + + /* We use top of mem for initial pagetables. */ + init_pg_tables_end = __pa(pg0); + + asm volatile ("mov %0, %%fs" : : "r" (__KERNEL_DS) : "memory"); + + reserve_top_address(lguest_data.reserve_mem); + + lockdep_init(); + + paravirt_disable_iospace(); + + cpu_detect(&new_cpu_data); + /* head.S usually sets up the first capability word, so do it here. */ + new_cpu_data.x86_capability[0] = cpuid_edx(1); + + /* Math is always hard! */ + new_cpu_data.hard_math = 1; + +#ifdef CONFIG_X86_MCE + mce_disabled = 1; +#endif + +#ifdef CONFIG_ACPI + acpi_disabled = 1; + acpi_ht = 0; +#endif + + add_preferred_console("hvc", 0, NULL); + + pm_power_off = lguest_power_off; + start_kernel(); +} diff --git a/drivers/lguest/lguest_asm.S b/drivers/lguest/lguest_asm.S new file mode 100644 index 0000000..00046c5 --- /dev/null +++ b/drivers/lguest/lguest_asm.S @@ -0,0 +1,56 @@ +#include <linux/linkage.h> +#include <linux/lguest.h> +#include <asm/asm-offsets.h> +#include <asm/thread_info.h> + +/* FIXME: Once asm/processor-flags.h goes in, include that */ +#define X86_EFLAGS_IF 0x00000200 + +/* + * This is where we begin: we have a magic signature which the launcher looks + * for. The plan is that the Linux boot protocol will be extended with a + * "platform type" field which will guide us here from the normal entry point, + * but for the moment this suffices. We pass the virtual address of the boot + * info to lguest_init(). + * + * We put it in .init.text will be discarded after boot. + */ +.section .init.text, "ax", @progbits +.ascii "GenuineLguest" + /* Set up initial stack. */ + movl $(init_thread_union+THREAD_SIZE),%esp + movl %esi, %eax + addl $__PAGE_OFFSET, %eax + jmp lguest_init + +/* The templates for inline patching. */ +#define LGUEST_PATCH(name, insns...) \ + lgstart_##name: insns; lgend_##name:; \ + .globl lgstart_##name; .globl lgend_##name + +LGUEST_PATCH(cli, movl $0, lguest_data+LGUEST_DATA_irq_enabled) +LGUEST_PATCH(sti, movl $X86_EFLAGS_IF, lguest_data+LGUEST_DATA_irq_enabled) +LGUEST_PATCH(popf, movl %eax, lguest_data+LGUEST_DATA_irq_enabled) +LGUEST_PATCH(pushf, movl lguest_data+LGUEST_DATA_irq_enabled, %eax) + +.text +/* These demark the EIP range where host should never deliver interrupts. */ +.global lguest_noirq_start +.global lguest_noirq_end + +/* + * We move eflags word to lguest_data.irq_enabled to restore interrupt state. + * For page faults, gpfs and virtual interrupts, the hypervisor has saved + * eflags manually, otherwise it was delivered directly and so eflags reflects + * the real machine IF state, ie. interrupts on. Since the kernel always dies + * if it takes such a trap with interrupts disabled anyway, turning interrupts + * back on unconditionally here is OK. + */ +ENTRY(lguest_iret) + pushl %eax + movl 12(%esp), %eax +lguest_noirq_start: + movl %eax,%ss:lguest_data+LGUEST_DATA_irq_enabled + popl %eax + iret +lguest_noirq_end: diff --git a/drivers/lguest/lguest_bus.c b/drivers/lguest/lguest_bus.c new file mode 100644 index 0000000..18d6ab2 --- /dev/null +++ b/drivers/lguest/lguest_bus.c @@ -0,0 +1,148 @@ +#include <linux/init.h> +#include <linux/bootmem.h> +#include <linux/lguest_bus.h> +#include <asm/io.h> + +static ssize_t type_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct lguest_device *dev = container_of(_dev,struct lguest_device,dev); + return sprintf(buf, "%hu", lguest_devices[dev->index].type); +} +static ssize_t features_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct lguest_device *dev = container_of(_dev,struct lguest_device,dev); + return sprintf(buf, "%hx", lguest_devices[dev->index].features); +} +static ssize_t pfn_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct lguest_device *dev = container_of(_dev,struct lguest_device,dev); + return sprintf(buf, "%u", lguest_devices[dev->index].pfn); +} +static ssize_t status_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct lguest_device *dev = container_of(_dev,struct lguest_device,dev); + return sprintf(buf, "%hx", lguest_devices[dev->index].status); +} +static ssize_t status_store(struct device *_dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lguest_device *dev = container_of(_dev,struct lguest_device,dev); + if (sscanf(buf, "%hi", &lguest_devices[dev->index].status) != 1) + return -EINVAL; + return count; +} +static struct device_attribute lguest_dev_attrs[] = { + __ATTR_RO(type), + __ATTR_RO(features), + __ATTR_RO(pfn), + __ATTR(status, 0644, status_show, status_store), + __ATTR_NULL +}; + +static int lguest_dev_match(struct device *_dev, struct device_driver *_drv) +{ + struct lguest_device *dev = container_of(_dev,struct lguest_device,dev); + struct lguest_driver *drv = container_of(_drv,struct lguest_driver,drv); + + return (drv->device_type == lguest_devices[dev->index].type); +} + +struct lguest_bus { + struct bus_type bus; + struct device dev; +}; + +static struct lguest_bus lguest_bus = { + .bus = { + .name = "lguest", + .match = lguest_dev_match, + .dev_attrs = lguest_dev_attrs, + }, + .dev = { + .parent = NULL, + .bus_id = "lguest", + } +}; + +static int lguest_dev_probe(struct device *_dev) +{ + int ret; + struct lguest_device *dev = container_of(_dev,struct lguest_device,dev); + struct lguest_driver *drv = container_of(dev->dev.driver, + struct lguest_driver, drv); + + lguest_devices[dev->index].status |= LGUEST_DEVICE_S_DRIVER; + ret = drv->probe(dev); + if (ret == 0) + lguest_devices[dev->index].status |= LGUEST_DEVICE_S_DRIVER_OK; + return ret; +} + +int register_lguest_driver(struct lguest_driver *drv) +{ + if (!lguest_devices) + return 0; + + drv->drv.bus = &lguest_bus.bus; + drv->drv.name = drv->name; + drv->drv.owner = drv->owner; + drv->drv.probe = lguest_dev_probe; + + return driver_register(&drv->drv); +} +EXPORT_SYMBOL_GPL(register_lguest_driver); + +static void add_lguest_device(unsigned int index) +{ + struct lguest_device *new; + + lguest_devices[index].status |= LGUEST_DEVICE_S_ACKNOWLEDGE; + new = kmalloc(sizeof(struct lguest_device), GFP_KERNEL); + if (!new) { + printk(KERN_EMERG "Cannot allocate lguest device %u\n", index); + lguest_devices[index].status |= LGUEST_DEVICE_S_FAILED; + return; + } + + new->index = index; + new->private = NULL; + memset(&new->dev, 0, sizeof(new->dev)); + new->dev.parent = &lguest_bus.dev; + new->dev.bus = &lguest_bus.bus; + sprintf(new->dev.bus_id, "%u", index); + if (device_register(&new->dev) != 0) { + printk(KERN_EMERG "Cannot register lguest device %u\n", index); + lguest_devices[index].status |= LGUEST_DEVICE_S_FAILED; + kfree(new); + } +} + +static void scan_devices(void) +{ + unsigned int i; + + for (i = 0; i < LGUEST_MAX_DEVICES; i++) + if (lguest_devices[i].type) + add_lguest_device(i); +} + +static int __init lguest_bus_init(void) +{ + if (strcmp(paravirt_ops.name, "lguest") != 0) + return 0; + + /* Devices are in page above top of "normal" mem. */ + lguest_devices = lguest_map(max_pfn<<PAGE_SHIFT, 1); + + if (bus_register(&lguest_bus.bus) != 0 + || device_register(&lguest_bus.dev) != 0) + panic("lguest bus registration failed"); + + scan_devices(); + return 0; +} +postcore_initcall(lguest_bus_init); diff --git a/drivers/lguest/lguest_user.c b/drivers/lguest/lguest_user.c new file mode 100644 index 0000000..e90d7a7 --- /dev/null +++ b/drivers/lguest/lguest_user.c @@ -0,0 +1,236 @@ +/* Userspace control of the guest, via /dev/lguest. */ +#include <linux/uaccess.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include "lg.h" + +static void setup_regs(struct lguest_regs *regs, unsigned long start) +{ + /* Write out stack in format lguest expects, so we can switch to it. */ + regs->ds = regs->es = regs->ss = __KERNEL_DS|GUEST_PL; + regs->cs = __KERNEL_CS|GUEST_PL; + regs->eflags = 0x202; /* Interrupts enabled. */ + regs->eip = start; + /* esi points to our boot information (physical address 0) */ +} + +/* + addr */ +static long user_get_dma(struct lguest *lg, const u32 __user *input) +{ + unsigned long key, udma, irq; + + if (get_user(key, input) != 0) + return -EFAULT; + udma = get_dma_buffer(lg, key, &irq); + if (!udma) + return -ENOENT; + + /* We put irq number in udma->used_len. */ + lgwrite_u32(lg, udma + offsetof(struct lguest_dma, used_len), irq); + return udma; +} + +/* To force the Guest to stop running and return to the Launcher, the + * Waker sets writes LHREQ_BREAK and the value "1" to /dev/lguest. The + * Launcher then writes LHREQ_BREAK and "0" to release the Waker. */ +static int break_guest_out(struct lguest *lg, const u32 __user *input) +{ + unsigned long on; + + /* Fetch whether they're turning break on or off.. */ + if (get_user(on, input) != 0) + return -EFAULT; + + if (on) { + lg->break_out = 1; + /* Pop it out (may be running on different CPU) */ + wake_up_process(lg->tsk); + /* Wait for them to reset it */ + return wait_event_interruptible(lg->break_wq, !lg->break_out); + } else { + lg->break_out = 0; + wake_up(&lg->break_wq); + return 0; + } +} + +/* + irq */ +static int user_send_irq(struct lguest *lg, const u32 __user *input) +{ + u32 irq; + + if (get_user(irq, input) != 0) + return -EFAULT; + if (irq >= LGUEST_IRQS) + return -EINVAL; + set_bit(irq, lg->irqs_pending); + return 0; +} + +static ssize_t read(struct file *file, char __user *user, size_t size,loff_t*o) +{ + struct lguest *lg = file->private_data; + + if (!lg) + return -EINVAL; + + /* If you're not the task which owns the guest, go away. */ + if (current != lg->tsk) + return -EPERM; + + if (lg->dead) { + size_t len; + + if (IS_ERR(lg->dead)) + return PTR_ERR(lg->dead); + + len = min(size, strlen(lg->dead)+1); + if (copy_to_user(user, lg->dead, len) != 0) + return -EFAULT; + return len; + } + + if (lg->dma_is_pending) + lg->dma_is_pending = 0; + + return run_guest(lg, (unsigned long __user *)user); +} + +/* Take: pfnlimit, pgdir, start, pageoffset. */ +static int initialize(struct file *file, const u32 __user *input) +{ + struct lguest *lg; + int err, i; + u32 args[4]; + + /* We grab the Big Lguest lock, which protects the global array + * "lguests" and multiple simultaneous initializations. */ + mutex_lock(&lguest_lock); + + if (file->private_data) { + err = -EBUSY; + goto unlock; + } + + if (copy_from_user(args, input, sizeof(args)) != 0) { + err = -EFAULT; + goto unlock; + } + + i = find_free_guest(); + if (i < 0) { + err = -ENOSPC; + goto unlock; + } + lg = &lguests[i]; + lg->guestid = i; + lg->pfn_limit = args[0]; + lg->page_offset = args[3]; + lg->regs_page = get_zeroed_page(GFP_KERNEL); + if (!lg->regs_page) { + err = -ENOMEM; + goto release_guest; + } + lg->regs = (void *)lg->regs_page + PAGE_SIZE - sizeof(*lg->regs); + + err = init_guest_pagetable(lg, args[1]); + if (err) + goto free_regs; + + setup_regs(lg->regs, args[2]); + setup_guest_gdt(lg); + init_clockdev(lg); + lg->tsk = current; + lg->mm = get_task_mm(lg->tsk); + init_waitqueue_head(&lg->break_wq); + lg->last_pages = NULL; + file->private_data = lg; + + mutex_unlock(&lguest_lock); + + return sizeof(args); + +free_regs: + free_page(lg->regs_page); +release_guest: + memset(lg, 0, sizeof(*lg)); +unlock: + mutex_unlock(&lguest_lock); + return err; +} + +static ssize_t write(struct file *file, const char __user *input, + size_t size, loff_t *off) +{ + struct lguest *lg = file->private_data; + u32 req; + + if (get_user(req, input) != 0) + return -EFAULT; + input += sizeof(req); + + if (req != LHREQ_INITIALIZE && !lg) + return -EINVAL; + if (lg && lg->dead) + return -ENOENT; + + /* If you're not the task which owns the Guest, you can only break */ + if (lg && current != lg->tsk && req != LHREQ_BREAK) + return -EPERM; + + switch (req) { + case LHREQ_INITIALIZE: + return initialize(file, (const u32 __user *)input); + case LHREQ_GETDMA: + return user_get_dma(lg, (const u32 __user *)input); + case LHREQ_IRQ: + return user_send_irq(lg, (const u32 __user *)input); + case LHREQ_BREAK: + return break_guest_out(lg, (const u32 __user *)input); + default: + return -EINVAL; + } +} + +static int close(struct inode *inode, struct file *file) +{ + struct lguest *lg = file->private_data; + + if (!lg) + return 0; + + mutex_lock(&lguest_lock); + /* Cancels the hrtimer set via LHCALL_SET_CLOCKEVENT. */ + hrtimer_cancel(&lg->hrt); + release_all_dma(lg); + free_guest_pagetable(lg); + mmput(lg->mm); + if (!IS_ERR(lg->dead)) + kfree(lg->dead); + free_page(lg->regs_page); + memset(lg, 0, sizeof(*lg)); + mutex_unlock(&lguest_lock); + return 0; +} + +static struct file_operations lguest_fops = { + .owner = THIS_MODULE, + .release = close, + .write = write, + .read = read, +}; +static struct miscdevice lguest_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "lguest", + .fops = &lguest_fops, +}; + +int __init lguest_device_init(void) +{ + return misc_register(&lguest_dev); +} + +void __exit lguest_device_remove(void) +{ + misc_deregister(&lguest_dev); +} diff --git a/drivers/lguest/page_tables.c b/drivers/lguest/page_tables.c new file mode 100644 index 0000000..1b0ba09 --- /dev/null +++ b/drivers/lguest/page_tables.c @@ -0,0 +1,411 @@ +/* Shadow page table operations. + * Copyright (C) Rusty Russell IBM Corporation 2006. + * GPL v2 and any later version */ +#include <linux/mm.h> +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/random.h> +#include <linux/percpu.h> +#include <asm/tlbflush.h> +#include "lg.h" + +#define PTES_PER_PAGE_SHIFT 10 +#define PTES_PER_PAGE (1 << PTES_PER_PAGE_SHIFT) +#define SWITCHER_PGD_INDEX (PTES_PER_PAGE - 1) + +static DEFINE_PER_CPU(spte_t *, switcher_pte_pages); +#define switcher_pte_page(cpu) per_cpu(switcher_pte_pages, cpu) + +static unsigned vaddr_to_pgd_index(unsigned long vaddr) +{ + return vaddr >> (PAGE_SHIFT + PTES_PER_PAGE_SHIFT); +} + +/* These access the shadow versions (ie. the ones used by the CPU). */ +static spgd_t *spgd_addr(struct lguest *lg, u32 i, unsigned long vaddr) +{ + unsigned int index = vaddr_to_pgd_index(vaddr); + + if (index >= SWITCHER_PGD_INDEX) { + kill_guest(lg, "attempt to access switcher pages"); + index = 0; + } + return &lg->pgdirs[i].pgdir[index]; +} + +static spte_t *spte_addr(struct lguest *lg, spgd_t spgd, unsigned long vaddr) +{ + spte_t *page = __va(spgd.pfn << PAGE_SHIFT); + BUG_ON(!(spgd.flags & _PAGE_PRESENT)); + return &page[(vaddr >> PAGE_SHIFT) % PTES_PER_PAGE]; +} + +/* These access the guest versions. */ +static unsigned long gpgd_addr(struct lguest *lg, unsigned long vaddr) +{ + unsigned int index = vaddr >> (PAGE_SHIFT + PTES_PER_PAGE_SHIFT); + return lg->pgdirs[lg->pgdidx].cr3 + index * sizeof(gpgd_t); +} + +static unsigned long gpte_addr(struct lguest *lg, + gpgd_t gpgd, unsigned long vaddr) +{ + unsigned long gpage = gpgd.pfn << PAGE_SHIFT; + BUG_ON(!(gpgd.flags & _PAGE_PRESENT)); + return gpage + ((vaddr>>PAGE_SHIFT) % PTES_PER_PAGE) * sizeof(gpte_t); +} + +/* Do a virtual -> physical mapping on a user page. */ +static unsigned long get_pfn(unsigned long virtpfn, int write) +{ + struct page *page; + unsigned long ret = -1UL; + + down_read(¤t->mm->mmap_sem); + if (get_user_pages(current, current->mm, virtpfn << PAGE_SHIFT, + 1, write, 1, &page, NULL) == 1) + ret = page_to_pfn(page); + up_read(¤t->mm->mmap_sem); + return ret; +} + +static spte_t gpte_to_spte(struct lguest *lg, gpte_t gpte, int write) +{ + spte_t spte; + unsigned long pfn; + + /* We ignore the global flag. */ + spte.flags = (gpte.flags & ~_PAGE_GLOBAL); + pfn = get_pfn(gpte.pfn, write); + if (pfn == -1UL) { + kill_guest(lg, "failed to get page %u", gpte.pfn); + /* Must not put_page() bogus page on cleanup. */ + spte.flags = 0; + } + spte.pfn = pfn; + return spte; +} + +static void release_pte(spte_t pte) +{ + if (pte.flags & _PAGE_PRESENT) + put_page(pfn_to_page(pte.pfn)); +} + +static void check_gpte(struct lguest *lg, gpte_t gpte) +{ + if ((gpte.flags & (_PAGE_PWT|_PAGE_PSE)) || gpte.pfn >= lg->pfn_limit) + kill_guest(lg, "bad page table entry"); +} + +static void check_gpgd(struct lguest *lg, gpgd_t gpgd) +{ + if ((gpgd.flags & ~_PAGE_TABLE) || gpgd.pfn >= lg->pfn_limit) + kill_guest(lg, "bad page directory entry"); +} + +/* FIXME: We hold reference to pages, which prevents them from being + swapped. It'd be nice to have a callback when Linux wants to swap out. */ + +/* We fault pages in, which allows us to update accessed/dirty bits. + * Return true if we got page. */ +int demand_page(struct lguest *lg, unsigned long vaddr, int errcode) +{ + gpgd_t gpgd; + spgd_t *spgd; + unsigned long gpte_ptr; + gpte_t gpte; + spte_t *spte; + + gpgd = mkgpgd(lgread_u32(lg, gpgd_addr(lg, vaddr))); + if (!(gpgd.flags & _PAGE_PRESENT)) + return 0; + + spgd = spgd_addr(lg, lg->pgdidx, vaddr); + if (!(spgd->flags & _PAGE_PRESENT)) { + /* Get a page of PTEs for them. */ + unsigned long ptepage = get_zeroed_page(GFP_KERNEL); + /* FIXME: Steal from self in this case? */ + if (!ptepage) { + kill_guest(lg, "out of memory allocating pte page"); + return 0; + } + check_gpgd(lg, gpgd); + spgd->raw.val = (__pa(ptepage) | gpgd.flags); + } + + gpte_ptr = gpte_addr(lg, gpgd, vaddr); + gpte = mkgpte(lgread_u32(lg, gpte_ptr)); + + /* No page? */ + if (!(gpte.flags & _PAGE_PRESENT)) + return 0; + + /* Write to read-only page? */ + if ((errcode & 2) && !(gpte.flags & _PAGE_RW)) + return 0; + + /* User access to a non-user page? */ + if ((errcode & 4) && !(gpte.flags & _PAGE_USER)) + return 0; + + check_gpte(lg, gpte); + gpte.flags |= _PAGE_ACCESSED; + if (errcode & 2) + gpte.flags |= _PAGE_DIRTY; + + /* We're done with the old pte. */ + spte = spte_addr(lg, *spgd, vaddr); + release_pte(*spte); + + /* We don't make it writable if this isn't a write: later + * write will fault so we can set dirty bit in guest. */ + if (gpte.flags & _PAGE_DIRTY) + *spte = gpte_to_spte(lg, gpte, 1); + else { + gpte_t ro_gpte = gpte; + ro_gpte.flags &= ~_PAGE_RW; + *spte = gpte_to_spte(lg, ro_gpte, 0); + } + + /* Now we update dirty/accessed on guest. */ + lgwrite_u32(lg, gpte_ptr, gpte.raw.val); + return 1; +} + +/* This is much faster than the full demand_page logic. */ +static int page_writable(struct lguest *lg, unsigned long vaddr) +{ + spgd_t *spgd; + unsigned long flags; + + spgd = spgd_addr(lg, lg->pgdidx, vaddr); + if (!(spgd->flags & _PAGE_PRESENT)) + return 0; + + flags = spte_addr(lg, *spgd, vaddr)->flags; + return (flags & (_PAGE_PRESENT|_PAGE_RW)) == (_PAGE_PRESENT|_PAGE_RW); +} + +void pin_page(struct lguest *lg, unsigned long vaddr) +{ + if (!page_writable(lg, vaddr) && !demand_page(lg, vaddr, 2)) + kill_guest(lg, "bad stack page %#lx", vaddr); +} + +static void release_pgd(struct lguest *lg, spgd_t *spgd) +{ + if (spgd->flags & _PAGE_PRESENT) { + unsigned int i; + spte_t *ptepage = __va(spgd->pfn << PAGE_SHIFT); + for (i = 0; i < PTES_PER_PAGE; i++) + release_pte(ptepage[i]); + free_page((long)ptepage); + spgd->raw.val = 0; + } +} + +static void flush_user_mappings(struct lguest *lg, int idx) +{ + unsigned int i; + for (i = 0; i < vaddr_to_pgd_index(lg->page_offset); i++) + release_pgd(lg, lg->pgdirs[idx].pgdir + i); +} + +void guest_pagetable_flush_user(struct lguest *lg) +{ + flush_user_mappings(lg, lg->pgdidx); +} + +static unsigned int find_pgdir(struct lguest *lg, unsigned long pgtable) +{ + unsigned int i; + for (i = 0; i < ARRAY_SIZE(lg->pgdirs); i++) + if (lg->pgdirs[i].cr3 == pgtable) + break; + return i; +} + +static unsigned int new_pgdir(struct lguest *lg, + unsigned long cr3, + int *blank_pgdir) +{ + unsigned int next; + + next = random32() % ARRAY_SIZE(lg->pgdirs); + if (!lg->pgdirs[next].pgdir) { + lg->pgdirs[next].pgdir = (spgd_t *)get_zeroed_page(GFP_KERNEL); + if (!lg->pgdirs[next].pgdir) + next = lg->pgdidx; + else + /* There are no mappings: you'll need to re-pin */ + *blank_pgdir = 1; + } + lg->pgdirs[next].cr3 = cr3; + /* Release all the non-kernel mappings. */ + flush_user_mappings(lg, next); + + return next; +} + +void guest_new_pagetable(struct lguest *lg, unsigned long pgtable) +{ + int newpgdir, repin = 0; + + newpgdir = find_pgdir(lg, pgtable); + if (newpgdir == ARRAY_SIZE(lg->pgdirs)) + newpgdir = new_pgdir(lg, pgtable, &repin); + lg->pgdidx = newpgdir; + if (repin) + pin_stack_pages(lg); +} + +static void release_all_pagetables(struct lguest *lg) +{ + unsigned int i, j; + + for (i = 0; i < ARRAY_SIZE(lg->pgdirs); i++) + if (lg->pgdirs[i].pgdir) + for (j = 0; j < SWITCHER_PGD_INDEX; j++) + release_pgd(lg, lg->pgdirs[i].pgdir + j); +} + +void guest_pagetable_clear_all(struct lguest *lg) +{ + release_all_pagetables(lg); + pin_stack_pages(lg); +} + +static void do_set_pte(struct lguest *lg, int idx, + unsigned long vaddr, gpte_t gpte) +{ + spgd_t *spgd = spgd_addr(lg, idx, vaddr); + if (spgd->flags & _PAGE_PRESENT) { + spte_t *spte = spte_addr(lg, *spgd, vaddr); + release_pte(*spte); + if (gpte.flags & (_PAGE_DIRTY | _PAGE_ACCESSED)) { + check_gpte(lg, gpte); + *spte = gpte_to_spte(lg, gpte, gpte.flags&_PAGE_DIRTY); + } else + spte->raw.val = 0; + } +} + +void guest_set_pte(struct lguest *lg, + unsigned long cr3, unsigned long vaddr, gpte_t gpte) +{ + /* Kernel mappings must be changed on all top levels. */ + if (vaddr >= lg->page_offset) { + unsigned int i; + for (i = 0; i < ARRAY_SIZE(lg->pgdirs); i++) + if (lg->pgdirs[i].pgdir) + do_set_pte(lg, i, vaddr, gpte); + } else { + int pgdir = find_pgdir(lg, cr3); + if (pgdir != ARRAY_SIZE(lg->pgdirs)) + do_set_pte(lg, pgdir, vaddr, gpte); + } +} + +void guest_set_pmd(struct lguest *lg, unsigned long cr3, u32 idx) +{ + int pgdir; + + if (idx >= SWITCHER_PGD_INDEX) + return; + + pgdir = find_pgdir(lg, cr3); + if (pgdir < ARRAY_SIZE(lg->pgdirs)) + release_pgd(lg, lg->pgdirs[pgdir].pgdir + idx); +} + +int init_guest_pagetable(struct lguest *lg, unsigned long pgtable) +{ + /* We assume this in flush_user_mappings, so check now */ + if (vaddr_to_pgd_index(lg->page_offset) >= SWITCHER_PGD_INDEX) + return -EINVAL; + lg->pgdidx = 0; + lg->pgdirs[lg->pgdidx].cr3 = pgtable; + lg->pgdirs[lg->pgdidx].pgdir = (spgd_t*)get_zeroed_page(GFP_KERNEL); + if (!lg->pgdirs[lg->pgdidx].pgdir) + return -ENOMEM; + return 0; +} + +void free_guest_pagetable(struct lguest *lg) +{ + unsigned int i; + + release_all_pagetables(lg); + for (i = 0; i < ARRAY_SIZE(lg->pgdirs); i++) + free_page((long)lg->pgdirs[i].pgdir); +} + +/* Caller must be preempt-safe */ +void map_switcher_in_guest(struct lguest *lg, struct lguest_pages *pages) +{ + spte_t *switcher_pte_page = __get_cpu_var(switcher_pte_pages); + spgd_t switcher_pgd; + spte_t regs_pte; + + /* Since switcher less that 4MB, we simply mug top pte page. */ + switcher_pgd.pfn = __pa(switcher_pte_page) >> PAGE_SHIFT; + switcher_pgd.flags = _PAGE_KERNEL; + lg->pgdirs[lg->pgdidx].pgdir[SWITCHER_PGD_INDEX] = switcher_pgd; + + /* Map our regs page over stack page. */ + regs_pte.pfn = __pa(lg->regs_page) >> PAGE_SHIFT; + regs_pte.flags = _PAGE_KERNEL; + switcher_pte_page[(unsigned long)pages/PAGE_SIZE%PTES_PER_PAGE] + = regs_pte; +} + +static void free_switcher_pte_pages(void) +{ + unsigned int i; + + for_each_possible_cpu(i) + free_page((long)switcher_pte_page(i)); +} + +static __init void populate_switcher_pte_page(unsigned int cpu, + struct page *switcher_page[], + unsigned int pages) +{ + unsigned int i; + spte_t *pte = switcher_pte_page(cpu); + + for (i = 0; i < pages; i++) { + pte[i].pfn = page_to_pfn(switcher_page[i]); + pte[i].flags = _PAGE_PRESENT|_PAGE_ACCESSED; + } + + /* We only map this CPU's pages, so guest can't see others. */ + i = pages + cpu*2; + + /* First page (regs) is rw, second (state) is ro. */ + pte[i].pfn = page_to_pfn(switcher_page[i]); + pte[i].flags = _PAGE_PRESENT|_PAGE_ACCESSED|_PAGE_RW; + pte[i+1].pfn = page_to_pfn(switcher_page[i+1]); + pte[i+1].flags = _PAGE_PRESENT|_PAGE_ACCESSED; +} + +__init int init_pagetables(struct page **switcher_page, unsigned int pages) +{ + unsigned int i; + + for_each_possible_cpu(i) { + switcher_pte_page(i) = (spte_t *)get_zeroed_page(GFP_KERNEL); + if (!switcher_pte_page(i)) { + free_switcher_pte_pages(); + return -ENOMEM; + } + populate_switcher_pte_page(i, switcher_page, pages); + } + return 0; +} + +void free_pagetables(void) +{ + free_switcher_pte_pages(); +} diff --git a/drivers/lguest/segments.c b/drivers/lguest/segments.c new file mode 100644 index 0000000..1b2cfe8 --- /dev/null +++ b/drivers/lguest/segments.c @@ -0,0 +1,125 @@ +#include "lg.h" + +static int desc_ok(const struct desc_struct *gdt) +{ + /* MBZ=0, P=1, DT=1 */ + return ((gdt->b & 0x00209000) == 0x00009000); +} + +static int segment_present(const struct desc_struct *gdt) +{ + return gdt->b & 0x8000; +} + +static int ignored_gdt(unsigned int num) +{ + return (num == GDT_ENTRY_TSS + || num == GDT_ENTRY_LGUEST_CS + || num == GDT_ENTRY_LGUEST_DS + || num == GDT_ENTRY_DOUBLEFAULT_TSS); +} + +/* We don't allow removal of CS, DS or SS; it doesn't make sense. */ +static void check_segment_use(struct lguest *lg, unsigned int desc) +{ + if (lg->regs->gs / 8 == desc) + lg->regs->gs = 0; + if (lg->regs->fs / 8 == desc) + lg->regs->fs = 0; + if (lg->regs->es / 8 == desc) + lg->regs->es = 0; + if (lg->regs->ds / 8 == desc + || lg->regs->cs / 8 == desc + || lg->regs->ss / 8 == desc) + kill_guest(lg, "Removed live GDT entry %u", desc); +} + +static void fixup_gdt_table(struct lguest *lg, unsigned start, unsigned end) +{ + unsigned int i; + + for (i = start; i < end; i++) { + /* We never copy these ones to real gdt */ + if (ignored_gdt(i)) + continue; + + /* We could fault in switch_to_guest if they are using + * a removed segment. */ + if (!segment_present(&lg->gdt[i])) { + check_segment_use(lg, i); + continue; + } + + if (!desc_ok(&lg->gdt[i])) + kill_guest(lg, "Bad GDT descriptor %i", i); + + /* DPL 0 presumably means "for use by guest". */ + if ((lg->gdt[i].b & 0x00006000) == 0) + lg->gdt[i].b |= (GUEST_PL << 13); + + /* Set accessed bit, since gdt isn't writable. */ + lg->gdt[i].b |= 0x00000100; + } +} + +void setup_default_gdt_entries(struct lguest_ro_state *state) +{ + struct desc_struct *gdt = state->guest_gdt; + unsigned long tss = (unsigned long)&state->guest_tss; + + /* Hypervisor segments. */ + gdt[GDT_ENTRY_LGUEST_CS] = FULL_EXEC_SEGMENT; + gdt[GDT_ENTRY_LGUEST_DS] = FULL_SEGMENT; + + /* This is the one which we *cannot* copy from guest, since tss + is depended on this lguest_ro_state, ie. this cpu. */ + gdt[GDT_ENTRY_TSS].a = 0x00000067 | (tss << 16); + gdt[GDT_ENTRY_TSS].b = 0x00008900 | (tss & 0xFF000000) + | ((tss >> 16) & 0x000000FF); +} + +void setup_guest_gdt(struct lguest *lg) +{ + lg->gdt[GDT_ENTRY_KERNEL_CS] = FULL_EXEC_SEGMENT; + lg->gdt[GDT_ENTRY_KERNEL_DS] = FULL_SEGMENT; + lg->gdt[GDT_ENTRY_KERNEL_CS].b |= (GUEST_PL << 13); + lg->gdt[GDT_ENTRY_KERNEL_DS].b |= (GUEST_PL << 13); +} + +/* This is a fast version for the common case where only the three TLS entries + * have changed. */ +void copy_gdt_tls(const struct lguest *lg, struct desc_struct *gdt) +{ + unsigned int i; + + for (i = GDT_ENTRY_TLS_MIN; i <= GDT_ENTRY_TLS_MAX; i++) + gdt[i] = lg->gdt[i]; +} + +void copy_gdt(const struct lguest *lg, struct desc_struct *gdt) +{ + unsigned int i; + + for (i = 0; i < GDT_ENTRIES; i++) + if (!ignored_gdt(i)) + gdt[i] = lg->gdt[i]; +} + +void load_guest_gdt(struct lguest *lg, unsigned long table, u32 num) +{ + if (num > ARRAY_SIZE(lg->gdt)) + kill_guest(lg, "too many gdt entries %i", num); + + lgread(lg, lg->gdt, table, num * sizeof(lg->gdt[0])); + fixup_gdt_table(lg, 0, ARRAY_SIZE(lg->gdt)); + lg->changed |= CHANGED_GDT; +} + +void guest_load_tls(struct lguest *lg, unsigned long gtls) +{ + struct desc_struct *tls = &lg->gdt[GDT_ENTRY_TLS_MIN]; + + lgread(lg, tls, gtls, sizeof(*tls)*GDT_ENTRY_TLS_ENTRIES); + fixup_gdt_table(lg, GDT_ENTRY_TLS_MIN, GDT_ENTRY_TLS_MAX+1); + lg->changed |= CHANGED_GDT_TLS; +} diff --git a/drivers/lguest/switcher.S b/drivers/lguest/switcher.S new file mode 100644 index 0000000..eadd4cc --- /dev/null +++ b/drivers/lguest/switcher.S @@ -0,0 +1,159 @@ +/* This code sits at 0xFFC00000 to do the low-level guest<->host switch. + + There is are two pages above us for this CPU (struct lguest_pages). + The second page (struct lguest_ro_state) becomes read-only after the + context switch. The first page (the stack for traps) remains writable, + but while we're in here, the guest cannot be running. +*/ +#include <linux/linkage.h> +#include <asm/asm-offsets.h> +#include "lg.h" + +.text +ENTRY(start_switcher_text) + +/* %eax points to lguest pages for this CPU. %ebx contains cr3 value. + All normal registers can be clobbered! */ +ENTRY(switch_to_guest) + /* Save host segments on host stack. */ + pushl %es + pushl %ds + pushl %gs + pushl %fs + /* With CONFIG_FRAME_POINTER, gcc doesn't let us clobber this! */ + pushl %ebp + /* Save host stack. */ + movl %esp, LGUEST_PAGES_host_sp(%eax) + /* Switch to guest stack: if we get NMI we expect to be there. */ + movl %eax, %edx + addl $LGUEST_PAGES_regs, %edx + movl %edx, %esp + /* Switch to guest's GDT, IDT. */ + lgdt LGUEST_PAGES_guest_gdt_desc(%eax) + lidt LGUEST_PAGES_guest_idt_desc(%eax) + /* Switch to guest's TSS while GDT still writable. */ + movl $(GDT_ENTRY_TSS*8), %edx + ltr %dx + /* Set host's TSS GDT entry to available (clear byte 5 bit 2). */ + movl (LGUEST_PAGES_host_gdt_desc+2)(%eax), %edx + andb $0xFD, (GDT_ENTRY_TSS*8 + 5)(%edx) + /* Switch to guest page tables: lguest_pages->state now read-only. */ + movl %ebx, %cr3 + /* Restore guest regs */ + popl %ebx + popl %ecx + popl %edx + popl %esi + popl %edi + popl %ebp + popl %gs + popl %eax + popl %fs + popl %ds + popl %es + /* Skip error code and trap number */ + addl $8, %esp + iret + +#define SWITCH_TO_HOST \ + /* Save guest state */ \ + pushl %es; \ + pushl %ds; \ + pushl %fs; \ + pushl %eax; \ + pushl %gs; \ + pushl %ebp; \ + pushl %edi; \ + pushl %esi; \ + pushl %edx; \ + pushl %ecx; \ + pushl %ebx; \ + /* Load lguest ds segment for convenience. */ \ + movl $(LGUEST_DS), %eax; \ + movl %eax, %ds; \ + /* Figure out where we are, based on stack (at top of regs). */ \ + movl %esp, %eax; \ + subl $LGUEST_PAGES_regs, %eax; \ + /* Put trap number in %ebx before we switch cr3 and lose it. */ \ + movl LGUEST_PAGES_regs_trapnum(%eax), %ebx; \ + /* Switch to host page tables (host GDT, IDT and stack are in host \ + mem, so need this first) */ \ + movl LGUEST_PAGES_host_cr3(%eax), %edx; \ + movl %edx, %cr3; \ + /* Set guest's TSS to available (clear byte 5 bit 2). */ \ + andb $0xFD, (LGUEST_PAGES_guest_gdt+GDT_ENTRY_TSS*8+5)(%eax); \ + /* Switch to host's GDT & IDT. */ \ + lgdt LGUEST_PAGES_host_gdt_desc(%eax); \ + lidt LGUEST_PAGES_host_idt_desc(%eax); \ + /* Switch to host's stack. */ \ + movl LGUEST_PAGES_host_sp(%eax), %esp; \ + /* Switch to host's TSS */ \ + movl $(GDT_ENTRY_TSS*8), %edx; \ + ltr %dx; \ + popl %ebp; \ + popl %fs; \ + popl %gs; \ + popl %ds; \ + popl %es + +/* Return to run_guest_once. */ +return_to_host: + SWITCH_TO_HOST + iret + +deliver_to_host: + SWITCH_TO_HOST + /* Decode IDT and jump to hosts' irq handler. When that does iret, it + * will return to run_guest_once. This is a feature. */ + movl (LGUEST_PAGES_host_idt_desc+2)(%eax), %edx + leal (%edx,%ebx,8), %eax + movzwl (%eax),%edx + movl 4(%eax), %eax + xorw %ax, %ax + orl %eax, %edx + jmp *%edx + +/* Real hardware interrupts are delivered straight to the host. Others + cause us to return to run_guest_once so it can decide what to do. Note + that some of these are overridden by the guest to deliver directly, and + never enter here (see load_guest_idt_entry). */ +.macro IRQ_STUB N TARGET + .data; .long 1f; .text; 1: + /* Make an error number for most traps, which don't have one. */ + .if (\N <> 8) && (\N < 10 || \N > 14) && (\N <> 17) + pushl $0 + .endif + pushl $\N + jmp \TARGET + ALIGN +.endm + +.macro IRQ_STUBS FIRST LAST TARGET + irq=\FIRST + .rept \LAST-\FIRST+1 + IRQ_STUB irq \TARGET + irq=irq+1 + .endr +.endm + +/* We intercept every interrupt, because we may need to switch back to + * host. Unfortunately we can't tell them apart except by entry + * point, so we need 256 entry points. + */ +.data +.global default_idt_entries +default_idt_entries: +.text + IRQ_STUBS 0 1 return_to_host /* First two traps */ + IRQ_STUB 2 handle_nmi /* NMI */ + IRQ_STUBS 3 31 return_to_host /* Rest of traps */ + IRQ_STUBS 32 127 deliver_to_host /* Real interrupts */ + IRQ_STUB 128 return_to_host /* System call (overridden) */ + IRQ_STUBS 129 255 deliver_to_host /* Other real interrupts */ + +/* We ignore NMI and return. */ +handle_nmi: + addl $8, %esp + iret + +ENTRY(end_switcher_text) diff --git a/drivers/macintosh/macio_asic.c b/drivers/macintosh/macio_asic.c index c96b7fe..ec9e5f3 100644 --- a/drivers/macintosh/macio_asic.c +++ b/drivers/macintosh/macio_asic.c @@ -365,10 +365,9 @@ static struct macio_dev * macio_add_one_device(struct macio_chip *chip, if (np == NULL) return NULL; - dev = kmalloc(sizeof(*dev), GFP_KERNEL); + dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return NULL; - memset(dev, 0, sizeof(*dev)); dev->bus = &chip->lbus; dev->media_bay = in_bay; diff --git a/drivers/macintosh/smu.c b/drivers/macintosh/smu.c index f8e1a13..d409f67 100644 --- a/drivers/macintosh/smu.c +++ b/drivers/macintosh/smu.c @@ -1053,10 +1053,9 @@ static int smu_open(struct inode *inode, struct file *file) struct smu_private *pp; unsigned long flags; - pp = kmalloc(sizeof(struct smu_private), GFP_KERNEL); + pp = kzalloc(sizeof(struct smu_private), GFP_KERNEL); if (pp == 0) return -ENOMEM; - memset(pp, 0, sizeof(struct smu_private)); spin_lock_init(&pp->lock); pp->mode = smu_file_commands; init_waitqueue_head(&pp->wait); diff --git a/drivers/macintosh/therm_pm72.c b/drivers/macintosh/therm_pm72.c index 3d90fc0..e43554e 100644 --- a/drivers/macintosh/therm_pm72.c +++ b/drivers/macintosh/therm_pm72.c @@ -318,10 +318,9 @@ static struct i2c_client *attach_i2c_chip(int id, const char *name) if (adap == NULL) return NULL; - clt = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + clt = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); if (clt == NULL) return NULL; - memset(clt, 0, sizeof(struct i2c_client)); clt->addr = (id >> 1) & 0x7f; clt->adapter = adap; diff --git a/drivers/macintosh/therm_windtunnel.c b/drivers/macintosh/therm_windtunnel.c index 3d0354e..5452da1 100644 --- a/drivers/macintosh/therm_windtunnel.c +++ b/drivers/macintosh/therm_windtunnel.c @@ -431,9 +431,8 @@ do_probe( struct i2c_adapter *adapter, int addr, int kind ) | I2C_FUNC_SMBUS_WRITE_BYTE) ) return 0; - if( !(cl=kmalloc(sizeof(*cl), GFP_KERNEL)) ) + if( !(cl=kzalloc(sizeof(*cl), GFP_KERNEL)) ) return -ENOMEM; - memset( cl, 0, sizeof(struct i2c_client) ); cl->addr = addr; cl->adapter = adapter; diff --git a/drivers/macintosh/windfarm_lm75_sensor.c b/drivers/macintosh/windfarm_lm75_sensor.c index a0fabf3..7e10c3a 100644 --- a/drivers/macintosh/windfarm_lm75_sensor.c +++ b/drivers/macintosh/windfarm_lm75_sensor.c @@ -117,10 +117,9 @@ static struct wf_lm75_sensor *wf_lm75_create(struct i2c_adapter *adapter, DBG("wf_lm75: creating %s device at address 0x%02x\n", ds1775 ? "ds1775" : "lm75", addr); - lm = kmalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL); + lm = kzalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL); if (lm == NULL) return NULL; - memset(lm, 0, sizeof(struct wf_lm75_sensor)); /* Usual rant about sensor names not beeing very consistent in * the device-tree, oh well ... diff --git a/drivers/md/dm-raid1.c b/drivers/md/dm-raid1.c index 1a876f9..144071e 100644 --- a/drivers/md/dm-raid1.c +++ b/drivers/md/dm-raid1.c @@ -951,13 +951,12 @@ static struct mirror_set *alloc_context(unsigned int nr_mirrors, len = sizeof(*ms) + (sizeof(ms->mirror[0]) * nr_mirrors); - ms = kmalloc(len, GFP_KERNEL); + ms = kzalloc(len, GFP_KERNEL); if (!ms) { ti->error = "Cannot allocate mirror context"; return NULL; } - memset(ms, 0, len); spin_lock_init(&ms->lock); ms->ti = ti; diff --git a/drivers/media/dvb/cinergyT2/cinergyT2.c b/drivers/media/dvb/cinergyT2/cinergyT2.c index 5a1449f..28929b6 100644 --- a/drivers/media/dvb/cinergyT2/cinergyT2.c +++ b/drivers/media/dvb/cinergyT2/cinergyT2.c @@ -905,12 +905,11 @@ static int cinergyt2_probe (struct usb_interface *intf, struct cinergyt2 *cinergyt2; int err; - if (!(cinergyt2 = kmalloc (sizeof(struct cinergyt2), GFP_KERNEL))) { + if (!(cinergyt2 = kzalloc (sizeof(struct cinergyt2), GFP_KERNEL))) { dprintk(1, "out of memory?!?\n"); return -ENOMEM; } - memset (cinergyt2, 0, sizeof (struct cinergyt2)); usb_set_intfdata (intf, (void *) cinergyt2); mutex_init(&cinergyt2->sem); diff --git a/drivers/media/video/cpia2/cpia2_core.c b/drivers/media/video/cpia2/cpia2_core.c index 55aab8d..a76bd78 100644 --- a/drivers/media/video/cpia2/cpia2_core.c +++ b/drivers/media/video/cpia2/cpia2_core.c @@ -2224,15 +2224,13 @@ struct camera_data *cpia2_init_camera_struct(void) { struct camera_data *cam; - cam = kmalloc(sizeof(*cam), GFP_KERNEL); + cam = kzalloc(sizeof(*cam), GFP_KERNEL); if (!cam) { ERR("couldn't kmalloc cpia2 struct\n"); return NULL; } - /* Default everything to 0 */ - memset(cam, 0, sizeof(struct camera_data)); cam->present = 1; mutex_init(&cam->busy_lock); diff --git a/drivers/media/video/msp3400-driver.c b/drivers/media/video/msp3400-driver.c index 507b1d4..11cfcf1 100644 --- a/drivers/media/video/msp3400-driver.c +++ b/drivers/media/video/msp3400-driver.c @@ -812,10 +812,9 @@ static int msp_attach(struct i2c_adapter *adapter, int address, int kind) int msp_product, msp_prod_hi, msp_prod_lo; int msp_rom; - client = kmalloc(sizeof(*client), GFP_KERNEL); + client = kzalloc(sizeof(*client), GFP_KERNEL); if (client == NULL) return -ENOMEM; - memset(client, 0, sizeof(*client)); client->addr = address; client->adapter = adapter; client->driver = &i2c_driver; diff --git a/drivers/media/video/planb.c b/drivers/media/video/planb.c index 1455a8f..4ab1af7 100644 --- a/drivers/media/video/planb.c +++ b/drivers/media/video/planb.c @@ -353,9 +353,8 @@ static int planb_prepare_open(struct planb *pb) * PLANB_DUMMY)*sizeof(struct dbdma_cmd) +(PLANB_MAXLINES*((PLANB_MAXPIXELS+7)& ~7))/8 +MAX_GBUFFERS*sizeof(unsigned int); - if ((pb->priv_space = kmalloc (size, GFP_KERNEL)) == 0) + if ((pb->priv_space = kzalloc (size, GFP_KERNEL)) == 0) return -ENOMEM; - memset ((void *) pb->priv_space, 0, size); pb->overlay_last1 = pb->ch1_cmd = (volatile struct dbdma_cmd *) DBDMA_ALIGN (pb->priv_space); pb->overlay_last2 = pb->ch2_cmd = pb->ch1_cmd + pb->tab_size; diff --git a/drivers/media/video/usbvideo/vicam.c b/drivers/media/video/usbvideo/vicam.c index 2d9c0dd..ff55512 100644 --- a/drivers/media/video/usbvideo/vicam.c +++ b/drivers/media/video/usbvideo/vicam.c @@ -1130,13 +1130,12 @@ vicam_probe( struct usb_interface *intf, const struct usb_device_id *id) } if ((cam = - kmalloc(sizeof (struct vicam_camera), GFP_KERNEL)) == NULL) { + kzalloc(sizeof (struct vicam_camera), GFP_KERNEL)) == NULL) { printk(KERN_WARNING "could not allocate kernel memory for vicam_camera struct\n"); return -ENOMEM; } - memset(cam, 0, sizeof (struct vicam_camera)); cam->shutter_speed = 15; diff --git a/drivers/mfd/mcp-core.c b/drivers/mfd/mcp-core.c index 75f401d..b4ed57e 100644 --- a/drivers/mfd/mcp-core.c +++ b/drivers/mfd/mcp-core.c @@ -200,9 +200,8 @@ struct mcp *mcp_host_alloc(struct device *parent, size_t size) { struct mcp *mcp; - mcp = kmalloc(sizeof(struct mcp) + size, GFP_KERNEL); + mcp = kzalloc(sizeof(struct mcp) + size, GFP_KERNEL); if (mcp) { - memset(mcp, 0, sizeof(struct mcp) + size); spin_lock_init(&mcp->lock); mcp->attached_device.parent = parent; mcp->attached_device.bus = &mcp_bus_type; diff --git a/drivers/mfd/ucb1x00-core.c b/drivers/mfd/ucb1x00-core.c index 149810a..e03f1bc 100644 --- a/drivers/mfd/ucb1x00-core.c +++ b/drivers/mfd/ucb1x00-core.c @@ -484,12 +484,11 @@ static int ucb1x00_probe(struct mcp *mcp) goto err_disable; } - ucb = kmalloc(sizeof(struct ucb1x00), GFP_KERNEL); + ucb = kzalloc(sizeof(struct ucb1x00), GFP_KERNEL); ret = -ENOMEM; if (!ucb) goto err_disable; - memset(ucb, 0, sizeof(struct ucb1x00)); ucb->cdev.class = &ucb1x00_class; ucb->cdev.dev = &mcp->attached_device; diff --git a/drivers/misc/asus-laptop.c b/drivers/misc/asus-laptop.c index 7798f590e..f753060 100644 --- a/drivers/misc/asus-laptop.c +++ b/drivers/misc/asus-laptop.c @@ -979,10 +979,9 @@ static int asus_hotk_add(struct acpi_device *device) printk(ASUS_NOTICE "Asus Laptop Support version %s\n", ASUS_LAPTOP_VERSION); - hotk = kmalloc(sizeof(struct asus_hotk), GFP_KERNEL); + hotk = kzalloc(sizeof(struct asus_hotk), GFP_KERNEL); if (!hotk) return -ENOMEM; - memset(hotk, 0, sizeof(struct asus_hotk)); hotk->handle = device->handle; strcpy(acpi_device_name(device), ASUS_HOTK_DEVICE_NAME); diff --git a/drivers/misc/ibmasm/command.c b/drivers/misc/ibmasm/command.c index b5df347..6497872 100644 --- a/drivers/misc/ibmasm/command.c +++ b/drivers/misc/ibmasm/command.c @@ -41,18 +41,16 @@ struct command *ibmasm_new_command(struct service_processor *sp, size_t buffer_s if (buffer_size > IBMASM_CMD_MAX_BUFFER_SIZE) return NULL; - cmd = kmalloc(sizeof(struct command), GFP_KERNEL); + cmd = kzalloc(sizeof(struct command), GFP_KERNEL); if (cmd == NULL) return NULL; - memset(cmd, 0, sizeof(*cmd)); - cmd->buffer = kmalloc(buffer_size, GFP_KERNEL); + cmd->buffer = kzalloc(buffer_size, GFP_KERNEL); if (cmd->buffer == NULL) { kfree(cmd); return NULL; } - memset(cmd->buffer, 0, buffer_size); cmd->buffer_size = buffer_size; kobject_init(&cmd->kobj); diff --git a/drivers/misc/ibmasm/ibmasmfs.c b/drivers/misc/ibmasm/ibmasmfs.c index eb7b073..22a7e8b 100644 --- a/drivers/misc/ibmasm/ibmasmfs.c +++ b/drivers/misc/ibmasm/ibmasmfs.c @@ -563,11 +563,10 @@ static ssize_t remote_settings_file_write(struct file *file, const char __user * if (*offset != 0) return 0; - buff = kmalloc (count + 1, GFP_KERNEL); + buff = kzalloc (count + 1, GFP_KERNEL); if (!buff) return -ENOMEM; - memset(buff, 0x0, count + 1); if (copy_from_user(buff, ubuff, count)) { kfree(buff); diff --git a/drivers/misc/ibmasm/module.c b/drivers/misc/ibmasm/module.c index fb03a85..4f9d4a9 100644 --- a/drivers/misc/ibmasm/module.c +++ b/drivers/misc/ibmasm/module.c @@ -77,13 +77,12 @@ static int __devinit ibmasm_init_one(struct pci_dev *pdev, const struct pci_devi /* vnc client won't work without bus-mastering */ pci_set_master(pdev); - sp = kmalloc(sizeof(struct service_processor), GFP_KERNEL); + sp = kzalloc(sizeof(struct service_processor), GFP_KERNEL); if (sp == NULL) { dev_err(&pdev->dev, "Failed to allocate memory\n"); result = -ENOMEM; goto error_kmalloc; } - memset(sp, 0, sizeof(struct service_processor)); spin_lock_init(&sp->lock); INIT_LIST_HEAD(&sp->command_queue); diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index cbd4b6e..93fe2e5 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -414,13 +414,12 @@ static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card) return ERR_PTR(-ENOSPC); __set_bit(devidx, dev_use); - md = kmalloc(sizeof(struct mmc_blk_data), GFP_KERNEL); + md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL); if (!md) { ret = -ENOMEM; goto out; } - memset(md, 0, sizeof(struct mmc_blk_data)); /* * Set the read-only status based on the supported commands diff --git a/drivers/net/Makefile b/drivers/net/Makefile index aaaa0a0..336af06 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -177,6 +177,7 @@ obj-$(CONFIG_ZORRO8390) += zorro8390.o obj-$(CONFIG_HPLANCE) += hplance.o 7990.o obj-$(CONFIG_MVME147_NET) += mvme147.o 7990.o obj-$(CONFIG_EQUALIZER) += eql.o +obj-$(CONFIG_LGUEST_GUEST) += lguest_net.o obj-$(CONFIG_MIPS_JAZZ_SONIC) += jazzsonic.o obj-$(CONFIG_MIPS_AU1X00_ENET) += au1000_eth.o obj-$(CONFIG_MIPS_SIM_NET) += mipsnet.o diff --git a/drivers/net/b44.c b/drivers/net/b44.c index 96fb0ec..37f1b6f 100644 --- a/drivers/net/b44.c +++ b/drivers/net/b44.c @@ -1519,14 +1519,13 @@ static void b44_setup_pseudo_magicp(struct b44 *bp) u8 *pwol_pattern; u8 pwol_mask[B44_PMASK_SIZE]; - pwol_pattern = kmalloc(B44_PATTERN_SIZE, GFP_KERNEL); + pwol_pattern = kzalloc(B44_PATTERN_SIZE, GFP_KERNEL); if (!pwol_pattern) { printk(KERN_ERR PFX "Memory not available for WOL\n"); return; } /* Ipv4 magic packet pattern - pattern 0.*/ - memset(pwol_pattern, 0, B44_PATTERN_SIZE); memset(pwol_mask, 0, B44_PMASK_SIZE); plen0 = b44_magic_pattern(bp->dev->dev_addr, pwol_pattern, pwol_mask, B44_ETHIPV4UDP_HLEN); diff --git a/drivers/net/bsd_comp.c b/drivers/net/bsd_comp.c index 7845eaf..202d4a4 100644 --- a/drivers/net/bsd_comp.c +++ b/drivers/net/bsd_comp.c @@ -395,14 +395,13 @@ static void *bsd_alloc (unsigned char *options, int opt_len, int decomp) * Allocate the main control structure for this instance. */ maxmaxcode = MAXCODE(bits); - db = kmalloc(sizeof (struct bsd_db), + db = kzalloc(sizeof (struct bsd_db), GFP_KERNEL); if (!db) { return NULL; } - memset (db, 0, sizeof(struct bsd_db)); /* * Allocate space for the dictionary. This may be more than one page in * length. diff --git a/drivers/net/forcedeth.c b/drivers/net/forcedeth.c index 136827f..6d1d50a 100644 --- a/drivers/net/forcedeth.c +++ b/drivers/net/forcedeth.c @@ -5137,12 +5137,10 @@ static int __devinit nv_probe(struct pci_dev *pci_dev, const struct pci_device_i goto out_unmap; np->tx_ring.ex = &np->rx_ring.ex[np->rx_ring_size]; } - np->rx_skb = kmalloc(sizeof(struct nv_skb_map) * np->rx_ring_size, GFP_KERNEL); - np->tx_skb = kmalloc(sizeof(struct nv_skb_map) * np->tx_ring_size, GFP_KERNEL); + np->rx_skb = kcalloc(np->rx_ring_size, sizeof(struct nv_skb_map), GFP_KERNEL); + np->tx_skb = kcalloc(np->tx_ring_size, sizeof(struct nv_skb_map), GFP_KERNEL); if (!np->rx_skb || !np->tx_skb) goto out_freering; - memset(np->rx_skb, 0, sizeof(struct nv_skb_map) * np->rx_ring_size); - memset(np->tx_skb, 0, sizeof(struct nv_skb_map) * np->tx_ring_size); dev->open = nv_open; dev->stop = nv_close; diff --git a/drivers/net/hamradio/dmascc.c b/drivers/net/hamradio/dmascc.c index 3be8c50..205f096 100644 --- a/drivers/net/hamradio/dmascc.c +++ b/drivers/net/hamradio/dmascc.c @@ -453,8 +453,8 @@ static int __init setup_adapter(int card_base, int type, int n) int scc_base = card_base + hw[type].scc_offset; char *chipnames[] = CHIPNAMES; - /* Allocate memory */ - info = kmalloc(sizeof(struct scc_info), GFP_KERNEL | GFP_DMA); + /* Initialize what is necessary for write_scc and write_scc_data */ + info = kzalloc(sizeof(struct scc_info), GFP_KERNEL | GFP_DMA); if (!info) { printk(KERN_ERR "dmascc: " "could not allocate memory for %s at %#3x\n", @@ -462,8 +462,6 @@ static int __init setup_adapter(int card_base, int type, int n) goto out; } - /* Initialize what is necessary for write_scc and write_scc_data */ - memset(info, 0, sizeof(struct scc_info)); info->dev[0] = alloc_netdev(0, "", dev_setup); if (!info->dev[0]) { diff --git a/drivers/net/irda/irport.c b/drivers/net/irda/irport.c index 3078c41..2073245 100644 --- a/drivers/net/irda/irport.c +++ b/drivers/net/irda/irport.c @@ -164,14 +164,13 @@ irport_open(int i, unsigned int iobase, unsigned int irq) /* Allocate memory if needed */ if (self->tx_buff.truesize > 0) { - self->tx_buff.head = kmalloc(self->tx_buff.truesize, + self->tx_buff.head = kzalloc(self->tx_buff.truesize, GFP_KERNEL); if (self->tx_buff.head == NULL) { IRDA_ERROR("%s(), can't allocate memory for " "transmit buffer!\n", __FUNCTION__); goto err_out4; } - memset(self->tx_buff.head, 0, self->tx_buff.truesize); } self->tx_buff.data = self->tx_buff.head; diff --git a/drivers/net/irda/irtty-sir.c b/drivers/net/irda/irtty-sir.c index ad18573..6f5f697 100644 --- a/drivers/net/irda/irtty-sir.c +++ b/drivers/net/irda/irtty-sir.c @@ -505,10 +505,9 @@ static int irtty_open(struct tty_struct *tty) } /* allocate private device info block */ - priv = kmalloc(sizeof(*priv), GFP_KERNEL); + priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) goto out_put; - memset(priv, 0, sizeof(*priv)); priv->magic = IRTTY_MAGIC; priv->tty = tty; diff --git a/drivers/net/iseries_veth.c b/drivers/net/iseries_veth.c index 347d50c..0433c41 100644 --- a/drivers/net/iseries_veth.c +++ b/drivers/net/iseries_veth.c @@ -822,10 +822,9 @@ static int veth_init_connection(u8 rlp) || ! HvLpConfig_doLpsCommunicateOnVirtualLan(this_lp, rlp) ) return 0; - cnx = kmalloc(sizeof(*cnx), GFP_KERNEL); + cnx = kzalloc(sizeof(*cnx), GFP_KERNEL); if (! cnx) return -ENOMEM; - memset(cnx, 0, sizeof(*cnx)); cnx->remote_lp = rlp; spin_lock_init(&cnx->lock); @@ -852,14 +851,13 @@ static int veth_init_connection(u8 rlp) if (rc != 0) return rc; - msgs = kmalloc(VETH_NUMBUFFERS * sizeof(struct veth_msg), GFP_KERNEL); + msgs = kcalloc(VETH_NUMBUFFERS, sizeof(struct veth_msg), GFP_KERNEL); if (! msgs) { veth_error("Can't allocate buffers for LPAR %d.\n", rlp); return -ENOMEM; } cnx->msgs = msgs; - memset(msgs, 0, VETH_NUMBUFFERS * sizeof(struct veth_msg)); for (i = 0; i < VETH_NUMBUFFERS; i++) { msgs[i].token = i; diff --git a/drivers/net/lance.c b/drivers/net/lance.c index a2f37e5..a4e5fab 100644 --- a/drivers/net/lance.c +++ b/drivers/net/lance.c @@ -533,11 +533,10 @@ static int __init lance_probe1(struct net_device *dev, int ioaddr, int irq, int dev->base_addr = ioaddr; /* Make certain the data structures used by the LANCE are aligned and DMAble. */ - lp = kmalloc(sizeof(*lp), GFP_DMA | GFP_KERNEL); + lp = kzalloc(sizeof(*lp), GFP_DMA | GFP_KERNEL); if(lp==NULL) return -ENODEV; if (lance_debug > 6) printk(" (#0x%05lx)", (unsigned long)lp); - memset(lp, 0, sizeof(*lp)); dev->priv = lp; lp->name = chipname; lp->rx_buffs = (unsigned long)kmalloc(PKT_BUF_SZ*RX_RING_SIZE, diff --git a/drivers/net/lguest_net.c b/drivers/net/lguest_net.c new file mode 100644 index 0000000..1127786 --- /dev/null +++ b/drivers/net/lguest_net.c @@ -0,0 +1,354 @@ +/* A simple network driver for lguest. + * + * Copyright 2006 Rusty Russell <rusty@rustcorp.com.au> IBM Corporation + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +//#define DEBUG +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/module.h> +#include <linux/mm_types.h> +#include <linux/io.h> +#include <linux/lguest_bus.h> + +#define SHARED_SIZE PAGE_SIZE +#define MAX_LANS 4 +#define NUM_SKBS 8 + +struct lguestnet_info +{ + /* The shared page(s). */ + struct lguest_net *peer; + unsigned long peer_phys; + unsigned long mapsize; + + /* The lguest_device I come from */ + struct lguest_device *lgdev; + + /* My peerid. */ + unsigned int me; + + /* Receive queue. */ + struct sk_buff *skb[NUM_SKBS]; + struct lguest_dma dma[NUM_SKBS]; +}; + +/* How many bytes left in this page. */ +static unsigned int rest_of_page(void *data) +{ + return PAGE_SIZE - ((unsigned long)data % PAGE_SIZE); +} + +/* Simple convention: offset 4 * peernum. */ +static unsigned long peer_key(struct lguestnet_info *info, unsigned peernum) +{ + return info->peer_phys + 4 * peernum; +} + +static void skb_to_dma(const struct sk_buff *skb, unsigned int headlen, + struct lguest_dma *dma) +{ + unsigned int i, seg; + + for (i = seg = 0; i < headlen; seg++, i += rest_of_page(skb->data+i)) { + dma->addr[seg] = virt_to_phys(skb->data + i); + dma->len[seg] = min((unsigned)(headlen - i), + rest_of_page(skb->data + i)); + } + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++, seg++) { + const skb_frag_t *f = &skb_shinfo(skb)->frags[i]; + /* Should not happen with MTU less than 64k - 2 * PAGE_SIZE. */ + if (seg == LGUEST_MAX_DMA_SECTIONS) { + printk("Woah dude! Megapacket!\n"); + break; + } + dma->addr[seg] = page_to_phys(f->page) + f->page_offset; + dma->len[seg] = f->size; + } + if (seg < LGUEST_MAX_DMA_SECTIONS) + dma->len[seg] = 0; +} + +/* We overload multicast bit to show promiscuous mode. */ +#define PROMISC_BIT 0x01 + +static void lguestnet_set_multicast(struct net_device *dev) +{ + struct lguestnet_info *info = netdev_priv(dev); + + if ((dev->flags & (IFF_PROMISC|IFF_ALLMULTI)) || dev->mc_count) + info->peer[info->me].mac[0] |= PROMISC_BIT; + else + info->peer[info->me].mac[0] &= ~PROMISC_BIT; +} + +static int promisc(struct lguestnet_info *info, unsigned int peer) +{ + return info->peer[peer].mac[0] & PROMISC_BIT; +} + +static int mac_eq(const unsigned char mac[ETH_ALEN], + struct lguestnet_info *info, unsigned int peer) +{ + /* Ignore multicast bit, which peer turns on to mean promisc. */ + if ((info->peer[peer].mac[0] & (~PROMISC_BIT)) != mac[0]) + return 0; + return memcmp(mac+1, info->peer[peer].mac+1, ETH_ALEN-1) == 0; +} + +static void transfer_packet(struct net_device *dev, + struct sk_buff *skb, + unsigned int peernum) +{ + struct lguestnet_info *info = netdev_priv(dev); + struct lguest_dma dma; + + skb_to_dma(skb, skb_headlen(skb), &dma); + pr_debug("xfer length %04x (%u)\n", htons(skb->len), skb->len); + + lguest_send_dma(peer_key(info, peernum), &dma); + if (dma.used_len != skb->len) { + dev->stats.tx_carrier_errors++; + pr_debug("Bad xfer to peer %i: %i of %i (dma %p/%i)\n", + peernum, dma.used_len, skb->len, + (void *)dma.addr[0], dma.len[0]); + } else { + dev->stats.tx_bytes += skb->len; + dev->stats.tx_packets++; + } +} + +static int unused_peer(const struct lguest_net peer[], unsigned int num) +{ + return peer[num].mac[0] == 0; +} + +static int lguestnet_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + unsigned int i; + int broadcast; + struct lguestnet_info *info = netdev_priv(dev); + const unsigned char *dest = ((struct ethhdr *)skb->data)->h_dest; + + pr_debug("%s: xmit %02x:%02x:%02x:%02x:%02x:%02x\n", + dev->name, dest[0],dest[1],dest[2],dest[3],dest[4],dest[5]); + + broadcast = is_multicast_ether_addr(dest); + for (i = 0; i < info->mapsize/sizeof(struct lguest_net); i++) { + if (i == info->me || unused_peer(info->peer, i)) + continue; + + if (!broadcast && !promisc(info, i) && !mac_eq(dest, info, i)) + continue; + + pr_debug("lguestnet %s: sending from %i to %i\n", + dev->name, info->me, i); + transfer_packet(dev, skb, i); + } + dev_kfree_skb(skb); + return 0; +} + +/* Find a new skb to put in this slot in shared mem. */ +static int fill_slot(struct net_device *dev, unsigned int slot) +{ + struct lguestnet_info *info = netdev_priv(dev); + /* Try to create and register a new one. */ + info->skb[slot] = netdev_alloc_skb(dev, ETH_HLEN + ETH_DATA_LEN); + if (!info->skb[slot]) { + printk("%s: could not fill slot %i\n", dev->name, slot); + return -ENOMEM; + } + + skb_to_dma(info->skb[slot], ETH_HLEN + ETH_DATA_LEN, &info->dma[slot]); + wmb(); + /* Now we tell hypervisor it can use the slot. */ + info->dma[slot].used_len = 0; + return 0; +} + +static irqreturn_t lguestnet_rcv(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct lguestnet_info *info = netdev_priv(dev); + unsigned int i, done = 0; + + for (i = 0; i < ARRAY_SIZE(info->dma); i++) { + unsigned int length; + struct sk_buff *skb; + + length = info->dma[i].used_len; + if (length == 0) + continue; + + done++; + skb = info->skb[i]; + fill_slot(dev, i); + + if (length < ETH_HLEN || length > ETH_HLEN + ETH_DATA_LEN) { + pr_debug(KERN_WARNING "%s: unbelievable skb len: %i\n", + dev->name, length); + dev_kfree_skb(skb); + continue; + } + + skb_put(skb, length); + skb->protocol = eth_type_trans(skb, dev); + /* This is a reliable transport. */ + if (dev->features & NETIF_F_NO_CSUM) + skb->ip_summed = CHECKSUM_UNNECESSARY; + pr_debug("Receiving skb proto 0x%04x len %i type %i\n", + ntohs(skb->protocol), skb->len, skb->pkt_type); + + dev->stats.rx_bytes += skb->len; + dev->stats.rx_packets++; + netif_rx(skb); + } + return done ? IRQ_HANDLED : IRQ_NONE; +} + +static int lguestnet_open(struct net_device *dev) +{ + int i; + struct lguestnet_info *info = netdev_priv(dev); + + /* Set up our MAC address */ + memcpy(info->peer[info->me].mac, dev->dev_addr, ETH_ALEN); + + /* Turn on promisc mode if needed */ + lguestnet_set_multicast(dev); + + for (i = 0; i < ARRAY_SIZE(info->dma); i++) { + if (fill_slot(dev, i) != 0) + goto cleanup; + } + if (lguest_bind_dma(peer_key(info,info->me), info->dma, + NUM_SKBS, lgdev_irq(info->lgdev)) != 0) + goto cleanup; + return 0; + +cleanup: + while (--i >= 0) + dev_kfree_skb(info->skb[i]); + return -ENOMEM; +} + +static int lguestnet_close(struct net_device *dev) +{ + unsigned int i; + struct lguestnet_info *info = netdev_priv(dev); + + /* Clear all trace: others might deliver packets, we'll ignore it. */ + memset(&info->peer[info->me], 0, sizeof(info->peer[info->me])); + + /* Deregister sg lists. */ + lguest_unbind_dma(peer_key(info, info->me), info->dma); + for (i = 0; i < ARRAY_SIZE(info->dma); i++) + dev_kfree_skb(info->skb[i]); + return 0; +} + +static int lguestnet_probe(struct lguest_device *lgdev) +{ + int err, irqf = IRQF_SHARED; + struct net_device *dev; + struct lguestnet_info *info; + struct lguest_device_desc *desc = &lguest_devices[lgdev->index]; + + pr_debug("lguest_net: probing for device %i\n", lgdev->index); + + dev = alloc_etherdev(sizeof(struct lguestnet_info)); + if (!dev) + return -ENOMEM; + + SET_MODULE_OWNER(dev); + + /* Ethernet defaults with some changes */ + ether_setup(dev); + dev->set_mac_address = NULL; + + dev->dev_addr[0] = 0x02; /* set local assignment bit (IEEE802) */ + dev->dev_addr[1] = 0x00; + memcpy(&dev->dev_addr[2], &lguest_data.guestid, 2); + dev->dev_addr[4] = 0x00; + dev->dev_addr[5] = 0x00; + + dev->open = lguestnet_open; + dev->stop = lguestnet_close; + dev->hard_start_xmit = lguestnet_start_xmit; + + /* Turning on/off promisc will call dev->set_multicast_list. + * We don't actually support multicast yet */ + dev->set_multicast_list = lguestnet_set_multicast; + SET_NETDEV_DEV(dev, &lgdev->dev); + if (desc->features & LGUEST_NET_F_NOCSUM) + dev->features = NETIF_F_SG|NETIF_F_NO_CSUM; + + info = netdev_priv(dev); + info->mapsize = PAGE_SIZE * desc->num_pages; + info->peer_phys = ((unsigned long)desc->pfn << PAGE_SHIFT); + info->lgdev = lgdev; + info->peer = lguest_map(info->peer_phys, desc->num_pages); + if (!info->peer) { + err = -ENOMEM; + goto free; + } + + /* This stores our peerid (upper bits reserved for future). */ + info->me = (desc->features & (info->mapsize-1)); + + err = register_netdev(dev); + if (err) { + pr_debug("lguestnet: registering device failed\n"); + goto unmap; + } + + if (lguest_devices[lgdev->index].features & LGUEST_DEVICE_F_RANDOMNESS) + irqf |= IRQF_SAMPLE_RANDOM; + if (request_irq(lgdev_irq(lgdev), lguestnet_rcv, irqf, "lguestnet", + dev) != 0) { + pr_debug("lguestnet: cannot get irq %i\n", lgdev_irq(lgdev)); + goto unregister; + } + + pr_debug("lguestnet: registered device %s\n", dev->name); + lgdev->private = dev; + return 0; + +unregister: + unregister_netdev(dev); +unmap: + lguest_unmap(info->peer); +free: + free_netdev(dev); + return err; +} + +static struct lguest_driver lguestnet_drv = { + .name = "lguestnet", + .owner = THIS_MODULE, + .device_type = LGUEST_DEVICE_T_NET, + .probe = lguestnet_probe, +}; + +static __init int lguestnet_init(void) +{ + return register_lguest_driver(&lguestnet_drv); +} +module_init(lguestnet_init); + +MODULE_DESCRIPTION("Lguest network driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/pcmcia/com20020_cs.c b/drivers/net/pcmcia/com20020_cs.c index 0d1c7a4..ea9414c4 100644 --- a/drivers/net/pcmcia/com20020_cs.c +++ b/drivers/net/pcmcia/com20020_cs.c @@ -147,7 +147,7 @@ static int com20020_probe(struct pcmcia_device *p_dev) DEBUG(0, "com20020_attach()\n"); /* Create new network device */ - info = kmalloc(sizeof(struct com20020_dev_t), GFP_KERNEL); + info = kzalloc(sizeof(struct com20020_dev_t), GFP_KERNEL); if (!info) goto fail_alloc_info; @@ -155,7 +155,6 @@ static int com20020_probe(struct pcmcia_device *p_dev) if (!dev) goto fail_alloc_dev; - memset(info, 0, sizeof(struct com20020_dev_t)); lp = dev->priv; lp->timeout = timeout; lp->backplane = backplane; diff --git a/drivers/net/pcmcia/ibmtr_cs.c b/drivers/net/pcmcia/ibmtr_cs.c index 4ecb8ca..4eafa4f 100644 --- a/drivers/net/pcmcia/ibmtr_cs.c +++ b/drivers/net/pcmcia/ibmtr_cs.c @@ -146,9 +146,8 @@ static int __devinit ibmtr_attach(struct pcmcia_device *link) DEBUG(0, "ibmtr_attach()\n"); /* Create new token-ring device */ - info = kmalloc(sizeof(*info), GFP_KERNEL); + info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; - memset(info,0,sizeof(*info)); dev = alloc_trdev(sizeof(struct tok_info)); if (!dev) { kfree(info); diff --git a/drivers/net/ppp_async.c b/drivers/net/ppp_async.c index caabbc4..27f5b90 100644 --- a/drivers/net/ppp_async.c +++ b/drivers/net/ppp_async.c @@ -159,12 +159,11 @@ ppp_asynctty_open(struct tty_struct *tty) int err; err = -ENOMEM; - ap = kmalloc(sizeof(*ap), GFP_KERNEL); + ap = kzalloc(sizeof(*ap), GFP_KERNEL); if (ap == 0) goto out; /* initialize the asyncppp structure */ - memset(ap, 0, sizeof(*ap)); ap->tty = tty; ap->mru = PPP_MRU; spin_lock_init(&ap->xmit_lock); diff --git a/drivers/net/ppp_deflate.c b/drivers/net/ppp_deflate.c index 72c8d66..eb98b66 100644 --- a/drivers/net/ppp_deflate.c +++ b/drivers/net/ppp_deflate.c @@ -121,12 +121,11 @@ static void *z_comp_alloc(unsigned char *options, int opt_len) if (w_size < DEFLATE_MIN_SIZE || w_size > DEFLATE_MAX_SIZE) return NULL; - state = kmalloc(sizeof(*state), + state = kzalloc(sizeof(*state), GFP_KERNEL); if (state == NULL) return NULL; - memset (state, 0, sizeof (struct ppp_deflate_state)); state->strm.next_in = NULL; state->w_size = w_size; state->strm.workspace = vmalloc(zlib_deflate_workspacesize()); @@ -341,11 +340,10 @@ static void *z_decomp_alloc(unsigned char *options, int opt_len) if (w_size < DEFLATE_MIN_SIZE || w_size > DEFLATE_MAX_SIZE) return NULL; - state = kmalloc(sizeof(*state), GFP_KERNEL); + state = kzalloc(sizeof(*state), GFP_KERNEL); if (state == NULL) return NULL; - memset (state, 0, sizeof (struct ppp_deflate_state)); state->w_size = w_size; state->strm.next_out = NULL; state->strm.workspace = kmalloc(zlib_inflate_workspacesize(), diff --git a/drivers/net/ppp_generic.c b/drivers/net/ppp_generic.c index 3ef0092..ef3325b 100644 --- a/drivers/net/ppp_generic.c +++ b/drivers/net/ppp_generic.c @@ -2684,8 +2684,7 @@ static void __exit ppp_cleanup(void) if (atomic_read(&ppp_unit_count) || atomic_read(&channel_count)) printk(KERN_ERR "PPP: removing module but units remain!\n"); cardmap_destroy(&all_ppp_units); - if (unregister_chrdev(PPP_MAJOR, "ppp") != 0) - printk(KERN_ERR "PPP: failed to unregister PPP device\n"); + unregister_chrdev(PPP_MAJOR, "ppp"); device_destroy(ppp_class, MKDEV(PPP_MAJOR, 0)); class_destroy(ppp_class); } diff --git a/drivers/net/ppp_mppe.c b/drivers/net/ppp_mppe.c index d5bdd25..f79cf87 100644 --- a/drivers/net/ppp_mppe.c +++ b/drivers/net/ppp_mppe.c @@ -200,11 +200,10 @@ static void *mppe_alloc(unsigned char *options, int optlen) || options[0] != CI_MPPE || options[1] != CILEN_MPPE) goto out; - state = kmalloc(sizeof(*state), GFP_KERNEL); + state = kzalloc(sizeof(*state), GFP_KERNEL); if (state == NULL) goto out; - memset(state, 0, sizeof(*state)); state->arc4 = crypto_alloc_blkcipher("ecb(arc4)", 0, CRYPTO_ALG_ASYNC); if (IS_ERR(state->arc4)) { diff --git a/drivers/net/ppp_synctty.c b/drivers/net/ppp_synctty.c index 5918fab..ce64032 100644 --- a/drivers/net/ppp_synctty.c +++ b/drivers/net/ppp_synctty.c @@ -207,13 +207,12 @@ ppp_sync_open(struct tty_struct *tty) struct syncppp *ap; int err; - ap = kmalloc(sizeof(*ap), GFP_KERNEL); + ap = kzalloc(sizeof(*ap), GFP_KERNEL); err = -ENOMEM; if (ap == 0) goto out; /* initialize the syncppp structure */ - memset(ap, 0, sizeof(*ap)); ap->tty = tty; ap->mru = PPP_MRU; spin_lock_init(&ap->xmit_lock); diff --git a/drivers/net/shaper.c b/drivers/net/shaper.c index e886e8d..4c3d98f 100644 --- a/drivers/net/shaper.c +++ b/drivers/net/shaper.c @@ -600,10 +600,9 @@ static int __init shaper_init(void) return -ENODEV; alloc_size = sizeof(*dev) * shapers; - devs = kmalloc(alloc_size, GFP_KERNEL); + devs = kzalloc(alloc_size, GFP_KERNEL); if (!devs) return -ENOMEM; - memset(devs, 0, alloc_size); for (i = 0; i < shapers; i++) { diff --git a/drivers/net/tg3.c b/drivers/net/tg3.c index 5ee1476..887b9a5 100644 --- a/drivers/net/tg3.c +++ b/drivers/net/tg3.c @@ -64,8 +64,8 @@ #define DRV_MODULE_NAME "tg3" #define PFX DRV_MODULE_NAME ": " -#define DRV_MODULE_VERSION "3.78" -#define DRV_MODULE_RELDATE "July 11, 2007" +#define DRV_MODULE_VERSION "3.79" +#define DRV_MODULE_RELDATE "July 18, 2007" #define TG3_DEF_MAC_MODE 0 #define TG3_DEF_RX_MODE 0 @@ -4847,6 +4847,59 @@ static int tg3_poll_fw(struct tg3 *tp) return 0; } +/* Save PCI command register before chip reset */ +static void tg3_save_pci_state(struct tg3 *tp) +{ + u32 val; + + pci_read_config_dword(tp->pdev, TG3PCI_COMMAND, &val); + tp->pci_cmd = val; +} + +/* Restore PCI state after chip reset */ +static void tg3_restore_pci_state(struct tg3 *tp) +{ + u32 val; + + /* Re-enable indirect register accesses. */ + pci_write_config_dword(tp->pdev, TG3PCI_MISC_HOST_CTRL, + tp->misc_host_ctrl); + + /* Set MAX PCI retry to zero. */ + val = (PCISTATE_ROM_ENABLE | PCISTATE_ROM_RETRY_ENABLE); + if (tp->pci_chip_rev_id == CHIPREV_ID_5704_A0 && + (tp->tg3_flags & TG3_FLAG_PCIX_MODE)) + val |= PCISTATE_RETRY_SAME_DMA; + pci_write_config_dword(tp->pdev, TG3PCI_PCISTATE, val); + + pci_write_config_dword(tp->pdev, TG3PCI_COMMAND, tp->pci_cmd); + + /* Make sure PCI-X relaxed ordering bit is clear. */ + pci_read_config_dword(tp->pdev, TG3PCI_X_CAPS, &val); + val &= ~PCIX_CAPS_RELAXED_ORDERING; + pci_write_config_dword(tp->pdev, TG3PCI_X_CAPS, val); + + if (tp->tg3_flags2 & TG3_FLG2_5780_CLASS) { + u32 val; + + /* Chip reset on 5780 will reset MSI enable bit, + * so need to restore it. + */ + if (tp->tg3_flags2 & TG3_FLG2_USING_MSI) { + u16 ctrl; + + pci_read_config_word(tp->pdev, + tp->msi_cap + PCI_MSI_FLAGS, + &ctrl); + pci_write_config_word(tp->pdev, + tp->msi_cap + PCI_MSI_FLAGS, + ctrl | PCI_MSI_FLAGS_ENABLE); + val = tr32(MSGINT_MODE); + tw32(MSGINT_MODE, val | MSGINT_MODE_ENABLE); + } + } +} + static void tg3_stop_fw(struct tg3 *); /* tp->lock is held. */ @@ -4863,6 +4916,12 @@ static int tg3_chip_reset(struct tg3 *tp) */ tp->nvram_lock_cnt = 0; + /* GRC_MISC_CFG core clock reset will clear the memory + * enable bit in PCI register 4 and the MSI enable bit + * on some chips, so we save relevant registers here. + */ + tg3_save_pci_state(tp); + if (GET_ASIC_REV(tp->pci_chip_rev_id) == ASIC_REV_5752 || GET_ASIC_REV(tp->pci_chip_rev_id) == ASIC_REV_5755 || GET_ASIC_REV(tp->pci_chip_rev_id) == ASIC_REV_5787) @@ -4961,50 +5020,14 @@ static int tg3_chip_reset(struct tg3 *tp) pci_write_config_dword(tp->pdev, 0xd8, 0xf5000); } - /* Re-enable indirect register accesses. */ - pci_write_config_dword(tp->pdev, TG3PCI_MISC_HOST_CTRL, - tp->misc_host_ctrl); - - /* Set MAX PCI retry to zero. */ - val = (PCISTATE_ROM_ENABLE | PCISTATE_ROM_RETRY_ENABLE); - if (tp->pci_chip_rev_id == CHIPREV_ID_5704_A0 && - (tp->tg3_flags & TG3_FLAG_PCIX_MODE)) - val |= PCISTATE_RETRY_SAME_DMA; - pci_write_config_dword(tp->pdev, TG3PCI_PCISTATE, val); - - pci_restore_state(tp->pdev); + tg3_restore_pci_state(tp); tp->tg3_flags &= ~TG3_FLAG_CHIP_RESETTING; - /* Make sure PCI-X relaxed ordering bit is clear. */ - pci_read_config_dword(tp->pdev, TG3PCI_X_CAPS, &val); - val &= ~PCIX_CAPS_RELAXED_ORDERING; - pci_write_config_dword(tp->pdev, TG3PCI_X_CAPS, val); - - if (tp->tg3_flags2 & TG3_FLG2_5780_CLASS) { - u32 val; - - /* Chip reset on 5780 will reset MSI enable bit, - * so need to restore it. - */ - if (tp->tg3_flags2 & TG3_FLG2_USING_MSI) { - u16 ctrl; - - pci_read_config_word(tp->pdev, - tp->msi_cap + PCI_MSI_FLAGS, - &ctrl); - pci_write_config_word(tp->pdev, - tp->msi_cap + PCI_MSI_FLAGS, - ctrl | PCI_MSI_FLAGS_ENABLE); - val = tr32(MSGINT_MODE); - tw32(MSGINT_MODE, val | MSGINT_MODE_ENABLE); - } - + val = 0; + if (tp->tg3_flags2 & TG3_FLG2_5780_CLASS) val = tr32(MEMARB_MODE); - tw32(MEMARB_MODE, val | MEMARB_MODE_ENABLE); - - } else - tw32(MEMARB_MODE, MEMARB_MODE_ENABLE); + tw32(MEMARB_MODE, val | MEMARB_MODE_ENABLE); if (tp->pci_chip_rev_id == CHIPREV_ID_5750_A3) { tg3_stop_fw(tp); @@ -11978,7 +12001,6 @@ static int __devinit tg3_init_one(struct pci_dev *pdev, */ if ((tr32(HOSTCC_MODE) & HOSTCC_MODE_ENABLE) || (tr32(WDMAC_MODE) & WDMAC_MODE_ENABLE)) { - pci_save_state(tp->pdev); tw32(MEMARB_MODE, MEMARB_MODE_ENABLE); tg3_halt(tp, RESET_KIND_SHUTDOWN, 1); } @@ -12007,12 +12029,6 @@ static int __devinit tg3_init_one(struct pci_dev *pdev, tg3_init_coal(tp); - /* Now that we have fully setup the chip, save away a snapshot - * of the PCI config space. We need to restore this after - * GRC_MISC_CFG core clock resets and some resume events. - */ - pci_save_state(tp->pdev); - pci_set_drvdata(pdev, dev); err = register_netdev(dev); diff --git a/drivers/net/tg3.h b/drivers/net/tg3.h index d84e75e..5c21f49 100644 --- a/drivers/net/tg3.h +++ b/drivers/net/tg3.h @@ -2345,6 +2345,7 @@ struct tg3 { #define PHY_REV_BCM5411_X0 0x1 /* Found on Netgear GA302T */ u32 led_ctrl; + u32 pci_cmd; char board_part_number[24]; char fw_ver[16]; diff --git a/drivers/net/wan/c101.c b/drivers/net/wan/c101.c index 6b63b35..8ead774 100644 --- a/drivers/net/wan/c101.c +++ b/drivers/net/wan/c101.c @@ -315,12 +315,11 @@ static int __init c101_run(unsigned long irq, unsigned long winbase) return -ENODEV; } - card = kmalloc(sizeof(card_t), GFP_KERNEL); + card = kzalloc(sizeof(card_t), GFP_KERNEL); if (card == NULL) { printk(KERN_ERR "c101: unable to allocate memory\n"); return -ENOBUFS; } - memset(card, 0, sizeof(card_t)); card->dev = alloc_hdlcdev(card); if (!card->dev) { diff --git a/drivers/net/wan/cosa.c b/drivers/net/wan/cosa.c index 9ef49ce..26058b4 100644 --- a/drivers/net/wan/cosa.c +++ b/drivers/net/wan/cosa.c @@ -572,13 +572,11 @@ static int cosa_probe(int base, int irq, int dma) sprintf(cosa->name, "cosa%d", cosa->num); /* Initialize the per-channel data */ - cosa->chan = kmalloc(sizeof(struct channel_data)*cosa->nchannels, - GFP_KERNEL); + cosa->chan = kcalloc(cosa->nchannels, sizeof(struct channel_data), GFP_KERNEL); if (!cosa->chan) { err = -ENOMEM; goto err_out3; } - memset(cosa->chan, 0, sizeof(struct channel_data)*cosa->nchannels); for (i=0; i<cosa->nchannels; i++) { cosa->chan[i].cosa = cosa; cosa->chan[i].num = i; diff --git a/drivers/net/wan/cycx_main.c b/drivers/net/wan/cycx_main.c index 6e5f1c8..a0e8611 100644 --- a/drivers/net/wan/cycx_main.c +++ b/drivers/net/wan/cycx_main.c @@ -113,12 +113,10 @@ static int __init cycx_init(void) /* Verify number of cards and allocate adapter data space */ cycx_ncards = min_t(int, cycx_ncards, CYCX_MAX_CARDS); cycx_ncards = max_t(int, cycx_ncards, 1); - cycx_card_array = kmalloc(sizeof(struct cycx_device) * cycx_ncards, - GFP_KERNEL); + cycx_card_array = kcalloc(cycx_ncards, sizeof(struct cycx_device), GFP_KERNEL); if (!cycx_card_array) goto out; - memset(cycx_card_array, 0, sizeof(struct cycx_device) * cycx_ncards); /* Register adapters with WAN router */ for (cnt = 0; cnt < cycx_ncards; ++cnt) { diff --git a/drivers/net/wan/cycx_x25.c b/drivers/net/wan/cycx_x25.c index 016b3ff..a8af28b 100644 --- a/drivers/net/wan/cycx_x25.c +++ b/drivers/net/wan/cycx_x25.c @@ -376,11 +376,10 @@ static int cycx_wan_new_if(struct wan_device *wandev, struct net_device *dev, } /* allocate and initialize private data */ - chan = kmalloc(sizeof(struct cycx_x25_channel), GFP_KERNEL); + chan = kzalloc(sizeof(struct cycx_x25_channel), GFP_KERNEL); if (!chan) return -ENOMEM; - memset(chan, 0, sizeof(*chan)); strcpy(chan->name, conf->name); chan->card = card; chan->link = conf->port; diff --git a/drivers/net/wan/dscc4.c b/drivers/net/wan/dscc4.c index dca0244..50d2f91 100644 --- a/drivers/net/wan/dscc4.c +++ b/drivers/net/wan/dscc4.c @@ -890,12 +890,11 @@ static int dscc4_found1(struct pci_dev *pdev, void __iomem *ioaddr) struct dscc4_dev_priv *root; int i, ret = -ENOMEM; - root = kmalloc(dev_per_card*sizeof(*root), GFP_KERNEL); + root = kcalloc(dev_per_card, sizeof(*root), GFP_KERNEL); if (!root) { printk(KERN_ERR "%s: can't allocate data\n", DRV_NAME); goto err_out; } - memset(root, 0, dev_per_card*sizeof(*root)); for (i = 0; i < dev_per_card; i++) { root[i].dev = alloc_hdlcdev(root + i); @@ -903,12 +902,11 @@ static int dscc4_found1(struct pci_dev *pdev, void __iomem *ioaddr) goto err_free_dev; } - ppriv = kmalloc(sizeof(*ppriv), GFP_KERNEL); + ppriv = kzalloc(sizeof(*ppriv), GFP_KERNEL); if (!ppriv) { printk(KERN_ERR "%s: can't allocate private data\n", DRV_NAME); goto err_free_dev; } - memset(ppriv, 0, sizeof(struct dscc4_pci_priv)); ppriv->root = root; spin_lock_init(&ppriv->lock); diff --git a/drivers/net/wan/farsync.c b/drivers/net/wan/farsync.c index 58a53b6..12dae8e 100644 --- a/drivers/net/wan/farsync.c +++ b/drivers/net/wan/farsync.c @@ -2476,13 +2476,12 @@ fst_add_one(struct pci_dev *pdev, const struct pci_device_id *ent) } /* Allocate driver private data */ - card = kmalloc(sizeof (struct fst_card_info), GFP_KERNEL); + card = kzalloc(sizeof (struct fst_card_info), GFP_KERNEL); if (card == NULL) { printk_err("FarSync card found but insufficient memory for" " driver storage\n"); return -ENOMEM; } - memset(card, 0, sizeof (struct fst_card_info)); /* Try to enable the device */ if ((err = pci_enable_device(pdev)) != 0) { diff --git a/drivers/net/wan/hostess_sv11.c b/drivers/net/wan/hostess_sv11.c index 9ba3e4e..bf5f8d9 100644 --- a/drivers/net/wan/hostess_sv11.c +++ b/drivers/net/wan/hostess_sv11.c @@ -231,11 +231,10 @@ static struct sv11_device *sv11_init(int iobase, int irq) return NULL; } - sv = kmalloc(sizeof(struct sv11_device), GFP_KERNEL); + sv = kzalloc(sizeof(struct sv11_device), GFP_KERNEL); if(!sv) goto fail3; - memset(sv, 0, sizeof(*sv)); sv->if_ptr=&sv->netdev; sv->netdev.dev = alloc_netdev(0, "hdlc%d", sv11_setup); diff --git a/drivers/net/wan/n2.c b/drivers/net/wan/n2.c index 5c322df..cbdf0b7 100644 --- a/drivers/net/wan/n2.c +++ b/drivers/net/wan/n2.c @@ -351,12 +351,11 @@ static int __init n2_run(unsigned long io, unsigned long irq, return -ENODEV; } - card = kmalloc(sizeof(card_t), GFP_KERNEL); + card = kzalloc(sizeof(card_t), GFP_KERNEL); if (card == NULL) { printk(KERN_ERR "n2: unable to allocate memory\n"); return -ENOBUFS; } - memset(card, 0, sizeof(card_t)); card->ports[0].dev = alloc_hdlcdev(&card->ports[0]); card->ports[1].dev = alloc_hdlcdev(&card->ports[1]); diff --git a/drivers/net/wan/pc300_drv.c b/drivers/net/wan/pc300_drv.c index 5d8c78e..99fee2f 100644 --- a/drivers/net/wan/pc300_drv.c +++ b/drivers/net/wan/pc300_drv.c @@ -3456,7 +3456,7 @@ cpc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) if ((err = pci_enable_device(pdev)) < 0) return err; - card = kmalloc(sizeof(pc300_t), GFP_KERNEL); + card = kzalloc(sizeof(pc300_t), GFP_KERNEL); if (card == NULL) { printk("PC300 found at RAM 0x%016llx, " "but could not allocate card structure.\n", @@ -3464,7 +3464,6 @@ cpc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) err = -ENOMEM; goto err_disable_dev; } - memset(card, 0, sizeof(pc300_t)); err = -ENODEV; diff --git a/drivers/net/wan/pc300too.c b/drivers/net/wan/pc300too.c index dfbd3b0..6353cb5 100644 --- a/drivers/net/wan/pc300too.c +++ b/drivers/net/wan/pc300too.c @@ -334,14 +334,13 @@ static int __devinit pc300_pci_init_one(struct pci_dev *pdev, return i; } - card = kmalloc(sizeof(card_t), GFP_KERNEL); + card = kzalloc(sizeof(card_t), GFP_KERNEL); if (card == NULL) { printk(KERN_ERR "pc300: unable to allocate memory\n"); pci_release_regions(pdev); pci_disable_device(pdev); return -ENOBUFS; } - memset(card, 0, sizeof(card_t)); pci_set_drvdata(pdev, card); if (pdev->device == PCI_DEVICE_ID_PC300_TE_1 || diff --git a/drivers/net/wan/pci200syn.c b/drivers/net/wan/pci200syn.c index 7f720de..092e51d 100644 --- a/drivers/net/wan/pci200syn.c +++ b/drivers/net/wan/pci200syn.c @@ -312,14 +312,13 @@ static int __devinit pci200_pci_init_one(struct pci_dev *pdev, return i; } - card = kmalloc(sizeof(card_t), GFP_KERNEL); + card = kzalloc(sizeof(card_t), GFP_KERNEL); if (card == NULL) { printk(KERN_ERR "pci200syn: unable to allocate memory\n"); pci_release_regions(pdev); pci_disable_device(pdev); return -ENOBUFS; } - memset(card, 0, sizeof(card_t)); pci_set_drvdata(pdev, card); card->ports[0].dev = alloc_hdlcdev(&card->ports[0]); card->ports[1].dev = alloc_hdlcdev(&card->ports[1]); diff --git a/drivers/net/wan/sdla.c b/drivers/net/wan/sdla.c index 6a485f0..792e588 100644 --- a/drivers/net/wan/sdla.c +++ b/drivers/net/wan/sdla.c @@ -1196,10 +1196,9 @@ static int sdla_xfer(struct net_device *dev, struct sdla_mem __user *info, int r if (read) { - temp = kmalloc(mem.len, GFP_KERNEL); + temp = kzalloc(mem.len, GFP_KERNEL); if (!temp) return(-ENOMEM); - memset(temp, 0, mem.len); sdla_read(dev, mem.addr, temp, mem.len); if(copy_to_user(mem.data, temp, mem.len)) { diff --git a/drivers/net/wan/sealevel.c b/drivers/net/wan/sealevel.c index 1313581..11276bf 100644 --- a/drivers/net/wan/sealevel.c +++ b/drivers/net/wan/sealevel.c @@ -270,11 +270,10 @@ static __init struct slvl_board *slvl_init(int iobase, int irq, return NULL; } - b = kmalloc(sizeof(struct slvl_board), GFP_KERNEL); + b = kzalloc(sizeof(struct slvl_board), GFP_KERNEL); if(!b) goto fail3; - memset(b, 0, sizeof(*b)); if (!(b->dev[0]= slvl_alloc(iobase, irq))) goto fail2; diff --git a/drivers/net/wan/wanxl.c b/drivers/net/wan/wanxl.c index c7360157..3c78f98 100644 --- a/drivers/net/wan/wanxl.c +++ b/drivers/net/wan/wanxl.c @@ -599,7 +599,7 @@ static int __devinit wanxl_pci_init_one(struct pci_dev *pdev, } alloc_size = sizeof(card_t) + ports * sizeof(port_t); - card = kmalloc(alloc_size, GFP_KERNEL); + card = kzalloc(alloc_size, GFP_KERNEL); if (card == NULL) { printk(KERN_ERR "wanXL %s: unable to allocate memory\n", pci_name(pdev)); @@ -607,7 +607,6 @@ static int __devinit wanxl_pci_init_one(struct pci_dev *pdev, pci_disable_device(pdev); return -ENOBUFS; } - memset(card, 0, alloc_size); pci_set_drvdata(pdev, card); card->pdev = pdev; diff --git a/drivers/net/wan/x25_asy.c b/drivers/net/wan/x25_asy.c index 1c9edd9..c48b1cc 100644 --- a/drivers/net/wan/x25_asy.c +++ b/drivers/net/wan/x25_asy.c @@ -786,14 +786,12 @@ static int __init init_x25_asy(void) printk(KERN_INFO "X.25 async: version 0.00 ALPHA " "(dynamic channels, max=%d).\n", x25_asy_maxdev ); - x25_asy_devs = kmalloc(sizeof(struct net_device *)*x25_asy_maxdev, - GFP_KERNEL); + x25_asy_devs = kcalloc(x25_asy_maxdev, sizeof(struct net_device*), GFP_KERNEL); if (!x25_asy_devs) { printk(KERN_WARNING "X25 async: Can't allocate x25_asy_ctrls[] " "array! Uaargh! (-> No X.25 available)\n"); return -ENOMEM; } - memset(x25_asy_devs, 0, sizeof(struct net_device *)*x25_asy_maxdev); return tty_register_ldisc(N_X25, &x25_ldisc); } diff --git a/drivers/nubus/nubus.c b/drivers/nubus/nubus.c index 3a0a3a7..e503c9c 100644 --- a/drivers/nubus/nubus.c +++ b/drivers/nubus/nubus.c @@ -466,9 +466,8 @@ static struct nubus_dev* __init parent->base, dir.base); /* Actually we should probably panic if this fails */ - if ((dev = kmalloc(sizeof(*dev), GFP_ATOMIC)) == NULL) + if ((dev = kzalloc(sizeof(*dev), GFP_ATOMIC)) == NULL) return NULL; - memset(dev, 0, sizeof(*dev)); dev->resid = parent->type; dev->directory = dir.base; dev->board = board; @@ -800,9 +799,8 @@ static struct nubus_board* __init nubus_add_board(int slot, int bytelanes) nubus_rewind(&rp, FORMAT_BLOCK_SIZE, bytelanes); /* Actually we should probably panic if this fails */ - if ((board = kmalloc(sizeof(*board), GFP_ATOMIC)) == NULL) + if ((board = kzalloc(sizeof(*board), GFP_ATOMIC)) == NULL) return NULL; - memset(board, 0, sizeof(*board)); board->fblock = rp; /* Dump the format block for debugging purposes */ diff --git a/drivers/parport/parport_cs.c b/drivers/parport/parport_cs.c index 8b7d84e..802a81d 100644 --- a/drivers/parport/parport_cs.c +++ b/drivers/parport/parport_cs.c @@ -105,9 +105,8 @@ static int parport_probe(struct pcmcia_device *link) DEBUG(0, "parport_attach()\n"); /* Create new parport device */ - info = kmalloc(sizeof(*info), GFP_KERNEL); + info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; - memset(info, 0, sizeof(*info)); link->priv = info; info->p_dev = link; diff --git a/drivers/parport/parport_serial.c b/drivers/parport/parport_serial.c index 90ea3b8..bd6ad8b 100644 --- a/drivers/parport/parport_serial.c +++ b/drivers/parport/parport_serial.c @@ -324,10 +324,9 @@ static int __devinit parport_serial_pci_probe (struct pci_dev *dev, struct parport_serial_private *priv; int err; - priv = kmalloc (sizeof *priv, GFP_KERNEL); + priv = kzalloc (sizeof *priv, GFP_KERNEL); if (!priv) return -ENOMEM; - memset(priv, 0, sizeof(struct parport_serial_private)); pci_set_drvdata (dev, priv); err = pci_enable_device (dev); diff --git a/drivers/pci/pcie/aer/aerdrv.c b/drivers/pci/pcie/aer/aerdrv.c index 6846fb4..ad90a01 100644 --- a/drivers/pci/pcie/aer/aerdrv.c +++ b/drivers/pci/pcie/aer/aerdrv.c @@ -148,11 +148,10 @@ static struct aer_rpc* aer_alloc_rpc(struct pcie_device *dev) { struct aer_rpc *rpc; - if (!(rpc = kmalloc(sizeof(struct aer_rpc), + if (!(rpc = kzalloc(sizeof(struct aer_rpc), GFP_KERNEL))) return NULL; - memset(rpc, 0, sizeof(struct aer_rpc)); /* * Initialize Root lock access, e_lock, to Root Error Status Reg, * Root Error ID Reg, and Root error producer/consumer index. diff --git a/drivers/pnp/core.c b/drivers/pnp/core.c index 3e20b1cc..8e7b2dd 100644 --- a/drivers/pnp/core.c +++ b/drivers/pnp/core.c @@ -35,12 +35,11 @@ void *pnp_alloc(long size) { void *result; - result = kmalloc(size, GFP_KERNEL); + result = kzalloc(size, GFP_KERNEL); if (!result){ printk(KERN_ERR "pnp: Out of Memory\n"); return NULL; } - memset(result, 0, size); return result; } diff --git a/drivers/rapidio/rio-scan.c b/drivers/rapidio/rio-scan.c index f935c1f..4442072 100644 --- a/drivers/rapidio/rio-scan.c +++ b/drivers/rapidio/rio-scan.c @@ -297,11 +297,10 @@ static struct rio_dev *rio_setup_device(struct rio_net *net, struct rio_switch *rswitch; int result, rdid; - rdev = kmalloc(sizeof(struct rio_dev), GFP_KERNEL); + rdev = kzalloc(sizeof(struct rio_dev), GFP_KERNEL); if (!rdev) goto out; - memset(rdev, 0, sizeof(struct rio_dev)); rdev->net = net; rio_mport_read_config_32(port, destid, hopcount, RIO_DEV_ID_CAR, &result); @@ -801,9 +800,8 @@ static struct rio_net __devinit *rio_alloc_net(struct rio_mport *port) { struct rio_net *net; - net = kmalloc(sizeof(struct rio_net), GFP_KERNEL); + net = kzalloc(sizeof(struct rio_net), GFP_KERNEL); if (net) { - memset(net, 0, sizeof(struct rio_net)); INIT_LIST_HEAD(&net->node); INIT_LIST_HEAD(&net->devices); INIT_LIST_HEAD(&net->mports); diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index e24ea82..5d760bb 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -235,7 +235,7 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t) return 0; } -static int cmos_set_freq(struct device *dev, int freq) +static int cmos_irq_set_freq(struct device *dev, int freq) { struct cmos_rtc *cmos = dev_get_drvdata(dev); int f; @@ -259,6 +259,34 @@ static int cmos_set_freq(struct device *dev, int freq) return 0; } +static int cmos_irq_set_state(struct device *dev, int enabled) +{ + struct cmos_rtc *cmos = dev_get_drvdata(dev); + unsigned char rtc_control, rtc_intr; + unsigned long flags; + + if (!is_valid_irq(cmos->irq)) + return -ENXIO; + + spin_lock_irqsave(&rtc_lock, flags); + rtc_control = CMOS_READ(RTC_CONTROL); + + if (enabled) + rtc_control |= RTC_PIE; + else + rtc_control &= ~RTC_PIE; + + CMOS_WRITE(rtc_control, RTC_CONTROL); + + rtc_intr = CMOS_READ(RTC_INTR_FLAGS); + rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF; + if (is_intr(rtc_intr)) + rtc_update_irq(cmos->rtc, 1, rtc_intr); + + spin_unlock_irqrestore(&rtc_lock, flags); + return 0; +} + #if defined(CONFIG_RTC_INTF_DEV) || defined(CONFIG_RTC_INTF_DEV_MODULE) static int @@ -360,7 +388,8 @@ static const struct rtc_class_ops cmos_rtc_ops = { .read_alarm = cmos_read_alarm, .set_alarm = cmos_set_alarm, .proc = cmos_procfs, - .irq_set_freq = cmos_set_freq, + .irq_set_freq = cmos_irq_set_freq, + .irq_set_state = cmos_irq_set_state, }; /*----------------------------------------------------------------*/ diff --git a/drivers/s390/char/tape_34xx.c b/drivers/s390/char/tape_34xx.c index e765875..80e7a53 100644 --- a/drivers/s390/char/tape_34xx.c +++ b/drivers/s390/char/tape_34xx.c @@ -131,10 +131,9 @@ tape_34xx_schedule_work(struct tape_device *device, enum tape_op op) { struct tape_34xx_work *p; - if ((p = kmalloc(sizeof(*p), GFP_ATOMIC)) == NULL) + if ((p = kzalloc(sizeof(*p), GFP_ATOMIC)) == NULL) return -ENOMEM; - memset(p, 0, sizeof(*p)); INIT_WORK(&p->work, tape_34xx_work_handler); p->device = tape_get_device_reference(device); diff --git a/drivers/s390/net/claw.c b/drivers/s390/net/claw.c index 348bb7b..023455a0b 100644 --- a/drivers/s390/net/claw.c +++ b/drivers/s390/net/claw.c @@ -317,8 +317,8 @@ claw_probe(struct ccwgroup_device *cgdev) CLAW_DBF_TEXT_(2,setup,"probex%d",-ENOMEM); return -ENOMEM; } - privptr->p_mtc_envelope= kmalloc( MAX_ENVELOPE_SIZE, GFP_KERNEL); - privptr->p_env = kmalloc(sizeof(struct claw_env), GFP_KERNEL); + privptr->p_mtc_envelope= kzalloc( MAX_ENVELOPE_SIZE, GFP_KERNEL); + privptr->p_env = kzalloc(sizeof(struct claw_env), GFP_KERNEL); if ((privptr->p_mtc_envelope==NULL) || (privptr->p_env==NULL)) { probe_error(cgdev); put_device(&cgdev->dev); @@ -327,8 +327,6 @@ claw_probe(struct ccwgroup_device *cgdev) CLAW_DBF_TEXT_(2,setup,"probex%d",-ENOMEM); return -ENOMEM; } - memset(privptr->p_mtc_envelope, 0x00, MAX_ENVELOPE_SIZE); - memset(privptr->p_env, 0x00, sizeof(struct claw_env)); memcpy(privptr->p_env->adapter_name,WS_NAME_NOT_DEF,8); memcpy(privptr->p_env->host_name,WS_NAME_NOT_DEF,8); memcpy(privptr->p_env->api_type,WS_NAME_NOT_DEF,8); @@ -3924,7 +3922,7 @@ add_channel(struct ccw_device *cdev,int i,struct claw_privbk *privptr) snprintf(p_ch->id, CLAW_ID_SIZE, "cl-%s", cdev->dev.bus_id); ccw_device_get_id(cdev, &dev_id); p_ch->devno = dev_id.devno; - if ((p_ch->irb = kmalloc(sizeof (struct irb),GFP_KERNEL)) == NULL) { + if ((p_ch->irb = kzalloc(sizeof (struct irb),GFP_KERNEL)) == NULL) { printk(KERN_WARNING "%s Out of memory in %s for irb\n", p_ch->id,__FUNCTION__); #ifdef FUNCTRACE @@ -3933,7 +3931,6 @@ add_channel(struct ccw_device *cdev,int i,struct claw_privbk *privptr) #endif return -ENOMEM; } - memset(p_ch->irb, 0, sizeof (struct irb)); #ifdef FUNCTRACE printk(KERN_INFO "%s:%s Exit on line %d\n", cdev->dev.bus_id,__FUNCTION__,__LINE__); diff --git a/drivers/sbus/char/bbc_i2c.c b/drivers/sbus/char/bbc_i2c.c index 178155b..fbadd4d 100644 --- a/drivers/sbus/char/bbc_i2c.c +++ b/drivers/sbus/char/bbc_i2c.c @@ -156,10 +156,9 @@ struct bbc_i2c_client *bbc_i2c_attach(struct linux_ebus_child *echild) if (!bp) return NULL; - client = kmalloc(sizeof(*client), GFP_KERNEL); + client = kzalloc(sizeof(*client), GFP_KERNEL); if (!client) return NULL; - memset(client, 0, sizeof(*client)); client->bp = bp; client->echild = echild; client->bus = echild->resource[0].start; diff --git a/drivers/sbus/char/vfc_dev.c b/drivers/sbus/char/vfc_dev.c index 6afc7e5..26b1d2a 100644 --- a/drivers/sbus/char/vfc_dev.c +++ b/drivers/sbus/char/vfc_dev.c @@ -656,12 +656,9 @@ static int vfc_probe(void) if (!cards) return -ENODEV; - vfc_dev_lst = kmalloc(sizeof(struct vfc_dev *) * - (cards+1), - GFP_KERNEL); + vfc_dev_lst = kcalloc(cards + 1, sizeof(struct vfc_dev*), GFP_KERNEL); if (vfc_dev_lst == NULL) return -ENOMEM; - memset(vfc_dev_lst, 0, sizeof(struct vfc_dev *) * (cards + 1)); vfc_dev_lst[cards] = NULL; ret = register_chrdev(VFC_MAJOR, vfcstr, &vfc_fops); diff --git a/drivers/scsi/3w-9xxx.c b/drivers/scsi/3w-9xxx.c index 76c0909..6b49f6a 100644 --- a/drivers/scsi/3w-9xxx.c +++ b/drivers/scsi/3w-9xxx.c @@ -1160,13 +1160,12 @@ static int twa_initialize_device_extension(TW_Device_Extension *tw_dev) } /* Allocate event info space */ - tw_dev->event_queue[0] = kmalloc(sizeof(TW_Event) * TW_Q_LENGTH, GFP_KERNEL); + tw_dev->event_queue[0] = kcalloc(TW_Q_LENGTH, sizeof(TW_Event), GFP_KERNEL); if (!tw_dev->event_queue[0]) { TW_PRINTK(tw_dev->host, TW_DRIVER, 0x18, "Event info memory allocation failed"); goto out; } - memset(tw_dev->event_queue[0], 0, sizeof(TW_Event) * TW_Q_LENGTH); for (i = 0; i < TW_Q_LENGTH; i++) { tw_dev->event_queue[i] = (TW_Event *)((unsigned char *)tw_dev->event_queue[0] + (i * sizeof(TW_Event))); diff --git a/drivers/scsi/NCR53C9x.c b/drivers/scsi/NCR53C9x.c index 8b5334c..773d11d 100644 --- a/drivers/scsi/NCR53C9x.c +++ b/drivers/scsi/NCR53C9x.c @@ -3606,11 +3606,10 @@ out: int esp_slave_alloc(struct scsi_device *SDptr) { struct esp_device *esp_dev = - kmalloc(sizeof(struct esp_device), GFP_ATOMIC); + kzalloc(sizeof(struct esp_device), GFP_ATOMIC); if (!esp_dev) return -ENOMEM; - memset(esp_dev, 0, sizeof(struct esp_device)); SDptr->hostdata = esp_dev; return 0; } diff --git a/drivers/scsi/NCR_D700.c b/drivers/scsi/NCR_D700.c index f12864a..3a80897 100644 --- a/drivers/scsi/NCR_D700.c +++ b/drivers/scsi/NCR_D700.c @@ -181,13 +181,12 @@ NCR_D700_probe_one(struct NCR_D700_private *p, int siop, int irq, struct Scsi_Host *host; int ret; - hostdata = kmalloc(sizeof(*hostdata), GFP_KERNEL); + hostdata = kzalloc(sizeof(*hostdata), GFP_KERNEL); if (!hostdata) { printk(KERN_ERR "NCR D700: SIOP%d: Failed to allocate host" "data, detatching\n", siop); return -ENOMEM; } - memset(hostdata, 0, sizeof(*hostdata)); if (!request_region(region, 64, "NCR_D700")) { printk(KERN_ERR "NCR D700: Failed to reserve IO region 0x%x\n", diff --git a/drivers/scsi/NCR_Q720.c b/drivers/scsi/NCR_Q720.c index 778844c..a8bbdc2 100644 --- a/drivers/scsi/NCR_Q720.c +++ b/drivers/scsi/NCR_Q720.c @@ -148,11 +148,10 @@ NCR_Q720_probe(struct device *dev) __u32 base_addr, mem_size; void __iomem *mem_base; - p = kmalloc(sizeof(*p), GFP_KERNEL); + p = kzalloc(sizeof(*p), GFP_KERNEL); if (!p) return -ENOMEM; - memset(p, 0, sizeof(*p)); pos2 = mca_device_read_pos(mca_dev, 2); /* enable device */ pos2 |= NCR_Q720_POS2_BOARD_ENABLE | NCR_Q720_POS2_INTERRUPT_ENABLE; diff --git a/drivers/scsi/imm.c b/drivers/scsi/imm.c index 0464c18..005d2b0 100644 --- a/drivers/scsi/imm.c +++ b/drivers/scsi/imm.c @@ -1159,11 +1159,10 @@ static int __imm_attach(struct parport *pb) init_waitqueue_head(&waiting); - dev = kmalloc(sizeof(imm_struct), GFP_KERNEL); + dev = kzalloc(sizeof(imm_struct), GFP_KERNEL); if (!dev) return -ENOMEM; - memset(dev, 0, sizeof(imm_struct)); dev->base = -1; dev->mode = IMM_AUTODETECT; diff --git a/drivers/scsi/ips.c b/drivers/scsi/ips.c index 9f8ed6b..492a51b 100644 --- a/drivers/scsi/ips.c +++ b/drivers/scsi/ips.c @@ -7068,14 +7068,13 @@ ips_init_phase1(struct pci_dev *pci_dev, int *indexPtr) subdevice_id = pci_dev->subsystem_device; /* found a controller */ - ha = kmalloc(sizeof (ips_ha_t), GFP_KERNEL); + ha = kzalloc(sizeof (ips_ha_t), GFP_KERNEL); if (ha == NULL) { IPS_PRINTK(KERN_WARNING, pci_dev, "Unable to allocate temporary ha struct\n"); return -1; } - memset(ha, 0, sizeof (ips_ha_t)); ips_sh[index] = NULL; ips_ha[index] = ha; diff --git a/drivers/scsi/lasi700.c b/drivers/scsi/lasi700.c index 5c32a69..3126824 100644 --- a/drivers/scsi/lasi700.c +++ b/drivers/scsi/lasi700.c @@ -101,13 +101,12 @@ lasi700_probe(struct parisc_device *dev) struct NCR_700_Host_Parameters *hostdata; struct Scsi_Host *host; - hostdata = kmalloc(sizeof(*hostdata), GFP_KERNEL); + hostdata = kzalloc(sizeof(*hostdata), GFP_KERNEL); if (!hostdata) { printk(KERN_ERR "%s: Failed to allocate host data\n", dev->dev.bus_id); return -ENOMEM; } - memset(hostdata, 0, sizeof(struct NCR_700_Host_Parameters)); hostdata->dev = &dev->dev; dma_set_mask(&dev->dev, DMA_32BIT_MASK); diff --git a/drivers/scsi/lpfc/lpfc_init.c b/drivers/scsi/lpfc/lpfc_init.c index f81f85e..07bd0dc 100644 --- a/drivers/scsi/lpfc/lpfc_init.c +++ b/drivers/scsi/lpfc/lpfc_init.c @@ -1830,7 +1830,7 @@ lpfc_pci_probe_one(struct pci_dev *pdev, const struct pci_device_id *pid) /* Initialize and populate the iocb list per host. */ INIT_LIST_HEAD(&phba->lpfc_iocb_list); for (i = 0; i < LPFC_IOCB_LIST_CNT; i++) { - iocbq_entry = kmalloc(sizeof(struct lpfc_iocbq), GFP_KERNEL); + iocbq_entry = kzalloc(sizeof(struct lpfc_iocbq), GFP_KERNEL); if (iocbq_entry == NULL) { printk(KERN_ERR "%s: only allocated %d iocbs of " "expected %d count. Unloading driver.\n", @@ -1839,7 +1839,6 @@ lpfc_pci_probe_one(struct pci_dev *pdev, const struct pci_device_id *pid) goto out_free_iocbq; } - memset(iocbq_entry, 0, sizeof(struct lpfc_iocbq)); iotag = lpfc_sli_next_iotag(phba, iocbq_entry); if (iotag == 0) { kfree (iocbq_entry); diff --git a/drivers/scsi/megaraid/megaraid_mbox.c b/drivers/scsi/megaraid/megaraid_mbox.c index c46685a..c6a53dc 100644 --- a/drivers/scsi/megaraid/megaraid_mbox.c +++ b/drivers/scsi/megaraid/megaraid_mbox.c @@ -454,7 +454,7 @@ megaraid_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) pci_set_master(pdev); // Allocate the per driver initialization structure - adapter = kmalloc(sizeof(adapter_t), GFP_KERNEL); + adapter = kzalloc(sizeof(adapter_t), GFP_KERNEL); if (adapter == NULL) { con_log(CL_ANN, (KERN_WARNING @@ -462,7 +462,6 @@ megaraid_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) goto out_probe_one; } - memset(adapter, 0, sizeof(adapter_t)); // set up PCI related soft state and other pre-known parameters @@ -746,10 +745,9 @@ megaraid_init_mbox(adapter_t *adapter) * Allocate and initialize the init data structure for mailbox * controllers */ - raid_dev = kmalloc(sizeof(mraid_device_t), GFP_KERNEL); + raid_dev = kzalloc(sizeof(mraid_device_t), GFP_KERNEL); if (raid_dev == NULL) return -1; - memset(raid_dev, 0, sizeof(mraid_device_t)); /* * Attach the adapter soft state to raid device soft state @@ -1050,8 +1048,7 @@ megaraid_alloc_cmd_packets(adapter_t *adapter) * since the calling routine does not yet know the number of available * commands. */ - adapter->kscb_list = kmalloc(sizeof(scb_t) * MBOX_MAX_SCSI_CMDS, - GFP_KERNEL); + adapter->kscb_list = kcalloc(MBOX_MAX_SCSI_CMDS, sizeof(scb_t), GFP_KERNEL); if (adapter->kscb_list == NULL) { con_log(CL_ANN, (KERN_WARNING @@ -1059,7 +1056,6 @@ megaraid_alloc_cmd_packets(adapter_t *adapter) __LINE__)); goto out_free_ibuf; } - memset(adapter->kscb_list, 0, sizeof(scb_t) * MBOX_MAX_SCSI_CMDS); // memory allocation for our command packets if (megaraid_mbox_setup_dma_pools(adapter) != 0) { @@ -3495,8 +3491,7 @@ megaraid_cmm_register(adapter_t *adapter) int i; // Allocate memory for the base list of scb for management module. - adapter->uscb_list = kmalloc(sizeof(scb_t) * MBOX_MAX_USER_CMDS, - GFP_KERNEL); + adapter->uscb_list = kcalloc(MBOX_MAX_USER_CMDS, sizeof(scb_t), GFP_KERNEL); if (adapter->uscb_list == NULL) { con_log(CL_ANN, (KERN_WARNING @@ -3504,7 +3499,6 @@ megaraid_cmm_register(adapter_t *adapter) __LINE__)); return -1; } - memset(adapter->uscb_list, 0, sizeof(scb_t) * MBOX_MAX_USER_CMDS); // Initialize the synchronization parameters for resources for diff --git a/drivers/scsi/megaraid/megaraid_mm.c b/drivers/scsi/megaraid/megaraid_mm.c index 84d9c27..b6587a6 100644 --- a/drivers/scsi/megaraid/megaraid_mm.c +++ b/drivers/scsi/megaraid/megaraid_mm.c @@ -890,12 +890,11 @@ mraid_mm_register_adp(mraid_mmadp_t *lld_adp) if (lld_adp->drvr_type != DRVRTYPE_MBOX) return (-EINVAL); - adapter = kmalloc(sizeof(mraid_mmadp_t), GFP_KERNEL); + adapter = kzalloc(sizeof(mraid_mmadp_t), GFP_KERNEL); if (!adapter) return -ENOMEM; - memset(adapter, 0, sizeof(mraid_mmadp_t)); adapter->unique_id = lld_adp->unique_id; adapter->drvr_type = lld_adp->drvr_type; diff --git a/drivers/scsi/megaraid/megaraid_sas.c b/drivers/scsi/megaraid/megaraid_sas.c index b7f2e61..ebb948c 100644 --- a/drivers/scsi/megaraid/megaraid_sas.c +++ b/drivers/scsi/megaraid/megaraid_sas.c @@ -1636,15 +1636,13 @@ static int megasas_alloc_cmds(struct megasas_instance *instance) * Allocate the dynamic array first and then allocate individual * commands. */ - instance->cmd_list = kmalloc(sizeof(struct megasas_cmd *) * max_cmd, - GFP_KERNEL); + instance->cmd_list = kcalloc(max_cmd, sizeof(struct megasas_cmd*), GFP_KERNEL); if (!instance->cmd_list) { printk(KERN_DEBUG "megasas: out of memory\n"); return -ENOMEM; } - memset(instance->cmd_list, 0, sizeof(struct megasas_cmd *) * max_cmd); for (i = 0; i < max_cmd; i++) { instance->cmd_list[i] = kmalloc(sizeof(struct megasas_cmd), diff --git a/drivers/scsi/pcmcia/aha152x_stub.c b/drivers/scsi/pcmcia/aha152x_stub.c index 370802d..2dd0dc9 100644 --- a/drivers/scsi/pcmcia/aha152x_stub.c +++ b/drivers/scsi/pcmcia/aha152x_stub.c @@ -106,9 +106,8 @@ static int aha152x_probe(struct pcmcia_device *link) DEBUG(0, "aha152x_attach()\n"); /* Create new SCSI device */ - info = kmalloc(sizeof(*info), GFP_KERNEL); + info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; - memset(info, 0, sizeof(*info)); info->p_dev = link; link->priv = info; diff --git a/drivers/scsi/pcmcia/nsp_cs.c b/drivers/scsi/pcmcia/nsp_cs.c index c6f8c6e..445cfbb 100644 --- a/drivers/scsi/pcmcia/nsp_cs.c +++ b/drivers/scsi/pcmcia/nsp_cs.c @@ -1602,9 +1602,8 @@ static int nsp_cs_probe(struct pcmcia_device *link) nsp_dbg(NSP_DEBUG_INIT, "in"); /* Create new SCSI device */ - info = kmalloc(sizeof(*info), GFP_KERNEL); + info = kzalloc(sizeof(*info), GFP_KERNEL); if (info == NULL) { return -ENOMEM; } - memset(info, 0, sizeof(*info)); info->p_dev = link; link->priv = info; data->ScsiInfo = info; diff --git a/drivers/scsi/pcmcia/qlogic_stub.c b/drivers/scsi/pcmcia/qlogic_stub.c index 697cfb7..67c5a58 100644 --- a/drivers/scsi/pcmcia/qlogic_stub.c +++ b/drivers/scsi/pcmcia/qlogic_stub.c @@ -162,10 +162,9 @@ static int qlogic_probe(struct pcmcia_device *link) DEBUG(0, "qlogic_attach()\n"); /* Create new SCSI device */ - info = kmalloc(sizeof(*info), GFP_KERNEL); + info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; - memset(info, 0, sizeof(*info)); info->p_dev = link; link->priv = info; link->io.NumPorts1 = 16; diff --git a/drivers/scsi/pcmcia/sym53c500_cs.c b/drivers/scsi/pcmcia/sym53c500_cs.c index 2695b71..961839e 100644 --- a/drivers/scsi/pcmcia/sym53c500_cs.c +++ b/drivers/scsi/pcmcia/sym53c500_cs.c @@ -875,10 +875,9 @@ SYM53C500_probe(struct pcmcia_device *link) DEBUG(0, "SYM53C500_attach()\n"); /* Create new SCSI device */ - info = kmalloc(sizeof(*info), GFP_KERNEL); + info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; - memset(info, 0, sizeof(*info)); info->p_dev = link; link->priv = info; link->io.NumPorts1 = 16; diff --git a/drivers/scsi/ppa.c b/drivers/scsi/ppa.c index 2f1fa1e..67b6d76 100644 --- a/drivers/scsi/ppa.c +++ b/drivers/scsi/ppa.c @@ -1014,10 +1014,9 @@ static int __ppa_attach(struct parport *pb) int modes, ppb, ppb_hi; int err = -ENOMEM; - dev = kmalloc(sizeof(ppa_struct), GFP_KERNEL); + dev = kzalloc(sizeof(ppa_struct), GFP_KERNEL); if (!dev) return -ENOMEM; - memset(dev, 0, sizeof(ppa_struct)); dev->base = -1; dev->mode = PPA_AUTODETECT; dev->recon_tmo = PPA_RECON_TMO; diff --git a/drivers/scsi/sim710.c b/drivers/scsi/sim710.c index 018c65f..710f19d 100644 --- a/drivers/scsi/sim710.c +++ b/drivers/scsi/sim710.c @@ -100,7 +100,7 @@ sim710_probe_common(struct device *dev, unsigned long base_addr, { struct Scsi_Host * host = NULL; struct NCR_700_Host_Parameters *hostdata = - kmalloc(sizeof(struct NCR_700_Host_Parameters), GFP_KERNEL); + kzalloc(sizeof(struct NCR_700_Host_Parameters), GFP_KERNEL); printk(KERN_NOTICE "sim710: %s\n", dev->bus_id); printk(KERN_NOTICE "sim710: irq = %d, clock = %d, base = 0x%lx, scsi_id = %d\n", @@ -110,7 +110,6 @@ sim710_probe_common(struct device *dev, unsigned long base_addr, printk(KERN_ERR "sim710: Failed to allocate host data\n"); goto out; } - memset(hostdata, 0, sizeof(struct NCR_700_Host_Parameters)); if(request_region(base_addr, 64, "sim710") == NULL) { printk(KERN_ERR "sim710: Failed to reserve IO region 0x%lx\n", diff --git a/drivers/scsi/tmscsim.c b/drivers/scsi/tmscsim.c index 14cba1c..5db1520 100644 --- a/drivers/scsi/tmscsim.c +++ b/drivers/scsi/tmscsim.c @@ -2082,10 +2082,9 @@ static int dc390_slave_alloc(struct scsi_device *scsi_device) uint id = scsi_device->id; uint lun = scsi_device->lun; - pDCB = kmalloc(sizeof(struct dc390_dcb), GFP_KERNEL); + pDCB = kzalloc(sizeof(struct dc390_dcb), GFP_KERNEL); if (!pDCB) return -ENOMEM; - memset(pDCB, 0, sizeof(struct dc390_dcb)); if (!pACB->DCBCnt++) { pACB->pLinkDCB = pDCB; diff --git a/drivers/serial/amba-pl011.c b/drivers/serial/amba-pl011.c index 954073c..72229df 100644 --- a/drivers/serial/amba-pl011.c +++ b/drivers/serial/amba-pl011.c @@ -716,7 +716,7 @@ static int pl011_probe(struct amba_device *dev, void *id) goto out; } - uap = kmalloc(sizeof(struct uart_amba_port), GFP_KERNEL); + uap = kzalloc(sizeof(struct uart_amba_port), GFP_KERNEL); if (uap == NULL) { ret = -ENOMEM; goto out; @@ -728,7 +728,6 @@ static int pl011_probe(struct amba_device *dev, void *id) goto free; } - memset(uap, 0, sizeof(struct uart_amba_port)); uap->clk = clk_get(&dev->dev, "UARTCLK"); if (IS_ERR(uap->clk)) { ret = PTR_ERR(uap->clk); diff --git a/drivers/sh/superhyway/superhyway.c b/drivers/sh/superhyway/superhyway.c index 94b2290..7d873b3 100644 --- a/drivers/sh/superhyway/superhyway.c +++ b/drivers/sh/superhyway/superhyway.c @@ -56,11 +56,10 @@ int superhyway_add_device(unsigned long base, struct superhyway_device *sdev, struct superhyway_device *dev = sdev; if (!dev) { - dev = kmalloc(sizeof(struct superhyway_device), GFP_KERNEL); + dev = kzalloc(sizeof(struct superhyway_device), GFP_KERNEL); if (!dev) return -ENOMEM; - memset(dev, 0, sizeof(struct superhyway_device)); } dev->bus = bus; diff --git a/drivers/sn/ioc3.c b/drivers/sn/ioc3.c index 2dd6eed..29fcd6d 100644 --- a/drivers/sn/ioc3.c +++ b/drivers/sn/ioc3.c @@ -629,7 +629,7 @@ static int ioc3_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) #endif /* Set up per-IOC3 data */ - idd = kmalloc(sizeof(struct ioc3_driver_data), GFP_KERNEL); + idd = kzalloc(sizeof(struct ioc3_driver_data), GFP_KERNEL); if (!idd) { printk(KERN_WARNING "%s: Failed to allocate IOC3 data for pci_dev %s.\n", @@ -637,7 +637,6 @@ static int ioc3_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) ret = -ENODEV; goto out_idd; } - memset(idd, 0, sizeof(struct ioc3_driver_data)); spin_lock_init(&idd->ir_lock); spin_lock_init(&idd->gpio_lock); idd->pdev = pdev; diff --git a/drivers/telephony/ixj_pcmcia.c b/drivers/telephony/ixj_pcmcia.c index 3e658dc..ff9a29b 100644 --- a/drivers/telephony/ixj_pcmcia.c +++ b/drivers/telephony/ixj_pcmcia.c @@ -45,11 +45,10 @@ static int ixj_probe(struct pcmcia_device *p_dev) p_dev->io.Attributes2 = IO_DATA_PATH_WIDTH_8; p_dev->io.IOAddrLines = 3; p_dev->conf.IntType = INT_MEMORY_AND_IO; - p_dev->priv = kmalloc(sizeof(struct ixj_info_t), GFP_KERNEL); + p_dev->priv = kzalloc(sizeof(struct ixj_info_t), GFP_KERNEL); if (!p_dev->priv) { return -ENOMEM; } - memset(p_dev->priv, 0, sizeof(struct ixj_info_t)); return ixj_config(p_dev); } diff --git a/drivers/usb/gadget/goku_udc.c b/drivers/usb/gadget/goku_udc.c index d6c5f11..349b816 100644 --- a/drivers/usb/gadget/goku_udc.c +++ b/drivers/usb/gadget/goku_udc.c @@ -1777,14 +1777,13 @@ static int goku_probe(struct pci_dev *pdev, const struct pci_device_id *id) } /* alloc, and start init */ - dev = kmalloc (sizeof *dev, GFP_KERNEL); + dev = kzalloc (sizeof *dev, GFP_KERNEL); if (dev == NULL){ pr_debug("enomem %s\n", pci_name(pdev)); retval = -ENOMEM; goto done; } - memset(dev, 0, sizeof *dev); spin_lock_init(&dev->lock); dev->pdev = pdev; dev->gadget.ops = &goku_ops; diff --git a/drivers/usb/gadget/serial.c b/drivers/usb/gadget/serial.c index dd33ff0..38138bb 100644 --- a/drivers/usb/gadget/serial.c +++ b/drivers/usb/gadget/serial.c @@ -1427,7 +1427,7 @@ static int __init gs_bind(struct usb_gadget *gadget) gs_acm_config_desc.bmAttributes |= USB_CONFIG_ATT_WAKEUP; } - gs_device = dev = kmalloc(sizeof(struct gs_dev), GFP_KERNEL); + gs_device = dev = kzalloc(sizeof(struct gs_dev), GFP_KERNEL); if (dev == NULL) return -ENOMEM; @@ -1435,7 +1435,6 @@ static int __init gs_bind(struct usb_gadget *gadget) init_utsname()->sysname, init_utsname()->release, gadget->name); - memset(dev, 0, sizeof(struct gs_dev)); dev->dev_gadget = gadget; spin_lock_init(&dev->dev_lock); INIT_LIST_HEAD(&dev->dev_req_list); diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 2038125..6edf409 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -171,11 +171,10 @@ static int ohci_urb_enqueue ( } /* allocate the private part of the URB */ - urb_priv = kmalloc (sizeof (urb_priv_t) + size * sizeof (struct td *), + urb_priv = kzalloc (sizeof (urb_priv_t) + size * sizeof (struct td *), mem_flags); if (!urb_priv) return -ENOMEM; - memset (urb_priv, 0, sizeof (urb_priv_t) + size * sizeof (struct td *)); INIT_LIST_HEAD (&urb_priv->pending); urb_priv->length = size; urb_priv->ed = ed; diff --git a/drivers/usb/host/sl811_cs.c b/drivers/usb/host/sl811_cs.c index 2d0e73b..5da63f5 100644 --- a/drivers/usb/host/sl811_cs.c +++ b/drivers/usb/host/sl811_cs.c @@ -278,10 +278,9 @@ static int sl811_cs_probe(struct pcmcia_device *link) { local_info_t *local; - local = kmalloc(sizeof(local_info_t), GFP_KERNEL); + local = kzalloc(sizeof(local_info_t), GFP_KERNEL); if (!local) return -ENOMEM; - memset(local, 0, sizeof(local_info_t)); local->p_dev = link; link->priv = local; diff --git a/drivers/video/amba-clcd.c b/drivers/video/amba-clcd.c index 6c9dc2e..a7a1c89 100644 --- a/drivers/video/amba-clcd.c +++ b/drivers/video/amba-clcd.c @@ -447,13 +447,12 @@ static int clcdfb_probe(struct amba_device *dev, void *id) goto out; } - fb = kmalloc(sizeof(struct clcd_fb), GFP_KERNEL); + fb = kzalloc(sizeof(struct clcd_fb), GFP_KERNEL); if (!fb) { printk(KERN_INFO "CLCD: could not allocate new clcd_fb struct\n"); ret = -ENOMEM; goto free_region; } - memset(fb, 0, sizeof(struct clcd_fb)); fb->dev = dev; fb->board = board; diff --git a/drivers/video/aty/atyfb_base.c b/drivers/video/aty/atyfb_base.c index ef330e3..0c7bf75 100644 --- a/drivers/video/aty/atyfb_base.c +++ b/drivers/video/aty/atyfb_base.c @@ -2937,12 +2937,11 @@ static int __devinit atyfb_setup_sparc(struct pci_dev *pdev, /* nothing */ ; j = i + 4; - par->mmap_map = kmalloc(j * sizeof(*par->mmap_map), GFP_ATOMIC); + par->mmap_map = kcalloc(j, sizeof(*par->mmap_map), GFP_ATOMIC); if (!par->mmap_map) { PRINTKE("atyfb_setup_sparc() can't alloc mmap_map\n"); return -ENOMEM; } - memset(par->mmap_map, 0, j * sizeof(*par->mmap_map)); for (i = 0, j = 2; i < 6 && pdev->resource[i].start; i++) { struct resource *rp = &pdev->resource[i]; diff --git a/drivers/video/au1200fb.c b/drivers/video/au1200fb.c index dbf4ec3..03e57ef 100644 --- a/drivers/video/au1200fb.c +++ b/drivers/video/au1200fb.c @@ -1589,11 +1589,10 @@ static int au1200fb_init_fbinfo(struct au1200fb_device *fbdev) return -EFAULT; } - fbi->pseudo_palette = kmalloc(sizeof(u32) * 16, GFP_KERNEL); + fbi->pseudo_palette = kcalloc(16, sizeof(u32), GFP_KERNEL); if (!fbi->pseudo_palette) { return -ENOMEM; } - memset(fbi->pseudo_palette, 0, sizeof(u32) * 16); if (fb_alloc_cmap(&fbi->cmap, AU1200_LCD_NBR_PALETTE_ENTRIES, 0) < 0) { print_err("Fail to allocate colormap (%d entries)", diff --git a/drivers/video/clps711xfb.c b/drivers/video/clps711xfb.c index 50b78af..dea6579 100644 --- a/drivers/video/clps711xfb.c +++ b/drivers/video/clps711xfb.c @@ -366,11 +366,10 @@ int __init clps711xfb_init(void) if (fb_get_options("clps711xfb", NULL)) return -ENODEV; - cfb = kmalloc(sizeof(*cfb), GFP_KERNEL); + cfb = kzalloc(sizeof(*cfb), GFP_KERNEL); if (!cfb) goto out; - memset(cfb, 0, sizeof(*cfb)); strcpy(cfb->fix.id, "clps711x"); cfb->fbops = &clps7111fb_ops; diff --git a/drivers/video/cyber2000fb.c b/drivers/video/cyber2000fb.c index 7a6eeda..30ede6e 100644 --- a/drivers/video/cyber2000fb.c +++ b/drivers/video/cyber2000fb.c @@ -1221,11 +1221,10 @@ cyberpro_alloc_fb_info(unsigned int id, char *name) { struct cfb_info *cfb; - cfb = kmalloc(sizeof(struct cfb_info), GFP_KERNEL); + cfb = kzalloc(sizeof(struct cfb_info), GFP_KERNEL); if (!cfb) return NULL; - memset(cfb, 0, sizeof(struct cfb_info)); cfb->id = id; diff --git a/drivers/video/logo/logo.c b/drivers/video/logo/logo.c index 80c0361..2b0f799 100644 --- a/drivers/video/logo/logo.c +++ b/drivers/video/logo/logo.c @@ -34,8 +34,11 @@ extern const struct linux_logo logo_superh_vga16; extern const struct linux_logo logo_superh_clut224; extern const struct linux_logo logo_m32r_clut224; - -const struct linux_logo *fb_find_logo(int depth) +/* logo's are marked __initdata. Use __init_refok to tell + * modpost that it is intended that this function uses data + * marked __initdata. + */ +const struct linux_logo * __init_refok fb_find_logo(int depth) { const struct linux_logo *logo = NULL; diff --git a/drivers/video/pvr2fb.c b/drivers/video/pvr2fb.c index 0f88c30..f930026 100644 --- a/drivers/video/pvr2fb.c +++ b/drivers/video/pvr2fb.c @@ -1082,13 +1082,12 @@ static int __init pvr2fb_init(void) #endif size = sizeof(struct fb_info) + sizeof(struct pvr2fb_par) + 16 * sizeof(u32); - fb_info = kmalloc(size, GFP_KERNEL); + fb_info = kzalloc(size, GFP_KERNEL); if (!fb_info) { printk(KERN_ERR "Failed to allocate memory for fb_info\n"); return -ENOMEM; } - memset(fb_info, 0, size); currentpar = (struct pvr2fb_par *)(fb_info + 1); diff --git a/drivers/video/savage/savagefb_driver.c b/drivers/video/savage/savagefb_driver.c index 3d7507a..b855f4a 100644 --- a/drivers/video/savage/savagefb_driver.c +++ b/drivers/video/savage/savagefb_driver.c @@ -2174,11 +2174,10 @@ static int __devinit savage_init_fb_info(struct fb_info *info, #if defined(CONFIG_FB_SAVAGE_ACCEL) /* FIFO size + padding for commands */ - info->pixmap.addr = kmalloc(8*1024, GFP_KERNEL); + info->pixmap.addr = kcalloc(8, 1024, GFP_KERNEL); err = -ENOMEM; if (info->pixmap.addr) { - memset(info->pixmap.addr, 0, 8*1024); info->pixmap.size = 8*1024; info->pixmap.scan_align = 4; info->pixmap.buf_align = 4; diff --git a/drivers/video/valkyriefb.c b/drivers/video/valkyriefb.c index ad66f07..7b0cef9 100644 --- a/drivers/video/valkyriefb.c +++ b/drivers/video/valkyriefb.c @@ -356,10 +356,9 @@ int __init valkyriefb_init(void) } #endif /* ppc (!CONFIG_MAC) */ - p = kmalloc(sizeof(*p), GFP_ATOMIC); + p = kzalloc(sizeof(*p), GFP_ATOMIC); if (p == 0) return -ENOMEM; - memset(p, 0, sizeof(*p)); /* Map in frame buffer and registers */ if (!request_mem_region(frame_buffer_phys, 0x100000, "valkyriefb")) { diff --git a/drivers/w1/masters/matrox_w1.c b/drivers/w1/masters/matrox_w1.c index 6f9d880..d356da5 100644 --- a/drivers/w1/masters/matrox_w1.c +++ b/drivers/w1/masters/matrox_w1.c @@ -164,7 +164,7 @@ static int __devinit matrox_w1_probe(struct pci_dev *pdev, const struct pci_devi if (pdev->vendor != PCI_VENDOR_ID_MATROX || pdev->device != PCI_DEVICE_ID_MATROX_G400) return -ENODEV; - dev = kmalloc(sizeof(struct matrox_device) + + dev = kzalloc(sizeof(struct matrox_device) + sizeof(struct w1_bus_master), GFP_KERNEL); if (!dev) { dev_err(&pdev->dev, @@ -173,7 +173,6 @@ static int __devinit matrox_w1_probe(struct pci_dev *pdev, const struct pci_devi return -ENOMEM; } - memset(dev, 0, sizeof(struct matrox_device) + sizeof(struct w1_bus_master)); dev->bus_master = (struct w1_bus_master *)(dev + 1); diff --git a/drivers/w1/slaves/w1_ds2433.c b/drivers/w1/slaves/w1_ds2433.c index cab5600..858c16a 100644 --- a/drivers/w1/slaves/w1_ds2433.c +++ b/drivers/w1/slaves/w1_ds2433.c @@ -266,10 +266,9 @@ static int w1_f23_add_slave(struct w1_slave *sl) #ifdef CONFIG_W1_SLAVE_DS2433_CRC struct w1_f23_data *data; - data = kmalloc(sizeof(struct w1_f23_data), GFP_KERNEL); + data = kzalloc(sizeof(struct w1_f23_data), GFP_KERNEL); if (!data) return -ENOMEM; - memset(data, 0, sizeof(struct w1_f23_data)); sl->family_data = data; #endif /* CONFIG_W1_SLAVE_DS2433_CRC */ diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index c633210..8d7ab74 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -520,7 +520,7 @@ static int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn) int err; struct w1_netlink_msg msg; - sl = kmalloc(sizeof(struct w1_slave), GFP_KERNEL); + sl = kzalloc(sizeof(struct w1_slave), GFP_KERNEL); if (!sl) { dev_err(&dev->dev, "%s: failed to allocate new slave device.\n", @@ -528,7 +528,6 @@ static int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn) return -ENOMEM; } - memset(sl, 0, sizeof(*sl)); sl->owner = THIS_MODULE; sl->master = dev; diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c index 258defd..2fbd8dd 100644 --- a/drivers/w1/w1_int.c +++ b/drivers/w1/w1_int.c @@ -41,7 +41,7 @@ static struct w1_master * w1_alloc_dev(u32 id, int slave_count, int slave_ttl, /* * We are in process context(kernel thread), so can sleep. */ - dev = kmalloc(sizeof(struct w1_master) + sizeof(struct w1_bus_master), GFP_KERNEL); + dev = kzalloc(sizeof(struct w1_master) + sizeof(struct w1_bus_master), GFP_KERNEL); if (!dev) { printk(KERN_ERR "Failed to allocate %zd bytes for new w1 device.\n", @@ -49,7 +49,6 @@ static struct w1_master * w1_alloc_dev(u32 id, int slave_count, int slave_ttl, return NULL; } - memset(dev, 0, sizeof(struct w1_master) + sizeof(struct w1_bus_master)); dev->bus_master = (struct w1_bus_master *)(dev + 1); |