Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S934280Ab3IDFng (ORCPT ); Wed, 4 Sep 2013 01:43:36 -0400 Received: from mail.active-venture.com ([67.228.131.205]:56346 "EHLO mail.active-venture.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932292Ab3IDFne (ORCPT ); Wed, 4 Sep 2013 01:43:34 -0400 X-Originating-IP: 108.223.40.66 Message-ID: <5226C880.8010704@roeck-us.net> Date: Tue, 03 Sep 2013 22:43:28 -0700 From: Guenter Roeck User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20130803 Thunderbird/17.0.8 MIME-Version: 1.0 To: Matthew Garrett CC: linux-kernel@vger.kernel.org, Thomas.Mingarelli@hp.com, toshi.kani@hp.com, wim@iguana.be, linux-watchdog@vger.kernel.org, platform-driver-x86@vger.kernel.org Subject: Re: [PATCH 1/2] Move and rename HP watchdog timer driver References: <1378272591-9755-1-git-send-email-matthew.garrett@nebula.com> In-Reply-To: <1378272591-9755-1-git-send-email-matthew.garrett@nebula.com> Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 54116 Lines: 1912 On 09/03/2013 10:29 PM, Matthew Garrett wrote: > The HP watchdog timer driver binds to the HP iLO system support controller > PCI device. This PCI device actually supports a wider range of > functionality, so give the driver a more generic name and move it to the x86 > platform driver directory in preparation for the following changes. > If that is the case, wouldn't it make more sense to have separate drivers, one per functionality, plus an mfd driver to bind them all together ? Guenter > Signed-off-by: Matthew Garrett > --- > drivers/platform/x86/Kconfig | 20 + > drivers/platform/x86/Makefile | 1 + > drivers/platform/x86/hpilo_support.c | 890 +++++++++++++++++++++++++++++++++++ > drivers/watchdog/Kconfig | 18 - > drivers/watchdog/Makefile | 1 - > drivers/watchdog/hpwdt.c | 890 ----------------------------------- > 6 files changed, 911 insertions(+), 909 deletions(-) > create mode 100644 drivers/platform/x86/hpilo_support.c > delete mode 100644 drivers/watchdog/hpwdt.c > > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig > index 36a9e60..34e6a47 100644 > --- a/drivers/platform/x86/Kconfig > +++ b/drivers/platform/x86/Kconfig > @@ -817,4 +817,24 @@ config PVPANIC > a paravirtualized device provided by QEMU; it lets a virtual machine > (guest) communicate panic events to the host. > > +config HP_ILO_SUPPORT > + tristate "HP ProLiant iLO2+ Hardware Watchdog Timer" > + depends on X86 && PCI > + help > + A software monitoring watchdog and NMI sourcing driver. This driver > + will detect lockups and provide a stack trace. This is a driver that > + will only load on an HP ProLiant system with a minimum of iLO2 support. > + > + To compile this driver as a module, choose M here: the module will be > + called hpilo_support. > + > +config HPWDT_NMI_DECODING > + bool "NMI decoding support for the HP ProLiant iLO2+ Hardware Watchdog Timer" > + depends on HP_ILO_SUPPORT > + default y > + help > + When an NMI occurs this feature will make the necessary BIOS calls to > + log the cause of the NMI. > + > + > endif # X86_PLATFORM_DEVICES > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile > index 5dbe193..9198df2 100644 > --- a/drivers/platform/x86/Makefile > +++ b/drivers/platform/x86/Makefile > @@ -16,6 +16,7 @@ obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o > obj-$(CONFIG_ACER_WMI) += acer-wmi.o > obj-$(CONFIG_ACERHDF) += acerhdf.o > obj-$(CONFIG_HP_ACCEL) += hp_accel.o > +obj-$(CONFIG_HPILO_SUPPORT) += hpilo_support.o > obj-$(CONFIG_HP_WMI) += hp-wmi.o > obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o > obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o > diff --git a/drivers/platform/x86/hpilo_support.c b/drivers/platform/x86/hpilo_support.c > new file mode 100644 > index 0000000..de7e4f4 > --- /dev/null > +++ b/drivers/platform/x86/hpilo_support.c > @@ -0,0 +1,890 @@ > +/* > + * HP WatchDog Driver > + * based on > + * > + * SoftDog 0.05: A Software Watchdog Device > + * > + * (c) Copyright 2007 Hewlett-Packard Development Company, L.P. > + * Thomas Mingarelli > + * > + * 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 > + * > + */ > + > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#ifdef CONFIG_HPWDT_NMI_DECODING > +#include > +#include > +#include > +#include > +#include > +#include > +#endif /* CONFIG_HPWDT_NMI_DECODING */ > +#include > + > +#define HPWDT_VERSION "1.3.2" > +#define SECS_TO_TICKS(secs) ((secs) * 1000 / 128) > +#define TICKS_TO_SECS(ticks) ((ticks) * 128 / 1000) > +#define HPWDT_MAX_TIMER TICKS_TO_SECS(65535) > +#define DEFAULT_MARGIN 30 > + > +static unsigned int soft_margin = DEFAULT_MARGIN; /* in seconds */ > +static unsigned int reload; /* the computed soft_margin */ > +static bool nowayout = WATCHDOG_NOWAYOUT; > +static char expect_release; > +static unsigned long hpwdt_is_open; > + > +static void __iomem *pci_mem_addr; /* the PCI-memory address */ > +static unsigned long __iomem *hpwdt_timer_reg; > +static unsigned long __iomem *hpwdt_timer_con; > + > +static DEFINE_PCI_DEVICE_TABLE(hpwdt_devices) = { > + { PCI_DEVICE(PCI_VENDOR_ID_COMPAQ, 0xB203) }, /* iLO2 */ > + { PCI_DEVICE(PCI_VENDOR_ID_HP, 0x3306) }, /* iLO3 */ > + {0}, /* terminate list */ > +}; > +MODULE_DEVICE_TABLE(pci, hpwdt_devices); > + > +#ifdef CONFIG_HPWDT_NMI_DECODING > +#define PCI_BIOS32_SD_VALUE 0x5F32335F /* "_32_" */ > +#define CRU_BIOS_SIGNATURE_VALUE 0x55524324 > +#define PCI_BIOS32_PARAGRAPH_LEN 16 > +#define PCI_ROM_BASE1 0x000F0000 > +#define ROM_SIZE 0x10000 > + > +struct bios32_service_dir { > + u32 signature; > + u32 entry_point; > + u8 revision; > + u8 length; > + u8 checksum; > + u8 reserved[5]; > +}; > + > +/* type 212 */ > +struct smbios_cru64_info { > + u8 type; > + u8 byte_length; > + u16 handle; > + u32 signature; > + u64 physical_address; > + u32 double_length; > + u32 double_offset; > +}; > +#define SMBIOS_CRU64_INFORMATION 212 > + > +/* type 219 */ > +struct smbios_proliant_info { > + u8 type; > + u8 byte_length; > + u16 handle; > + u32 power_features; > + u32 omega_features; > + u32 reserved; > + u32 misc_features; > +}; > +#define SMBIOS_ICRU_INFORMATION 219 > + > + > +struct cmn_registers { > + union { > + struct { > + u8 ral; > + u8 rah; > + u16 rea2; > + }; > + u32 reax; > + } u1; > + union { > + struct { > + u8 rbl; > + u8 rbh; > + u8 reb2l; > + u8 reb2h; > + }; > + u32 rebx; > + } u2; > + union { > + struct { > + u8 rcl; > + u8 rch; > + u16 rec2; > + }; > + u32 recx; > + } u3; > + union { > + struct { > + u8 rdl; > + u8 rdh; > + u16 red2; > + }; > + u32 redx; > + } u4; > + > + u32 resi; > + u32 redi; > + u16 rds; > + u16 res; > + u32 reflags; > +} __attribute__((packed)); > + > +static unsigned int hpwdt_nmi_decoding; > +static unsigned int allow_kdump = 1; > +static unsigned int is_icru; > +static unsigned int is_uefi; > +static DEFINE_SPINLOCK(rom_lock); > +static void *cru_rom_addr; > +static struct cmn_registers cmn_regs; > + > +extern asmlinkage void asminline_call(struct cmn_registers *pi86Regs, > + unsigned long *pRomEntry); > + > +#ifdef CONFIG_X86_32 > +/* --32 Bit Bios------------------------------------------------------------ */ > + > +#define HPWDT_ARCH 32 > + > +asm(".text \n\t" > + ".align 4 \n" > + "asminline_call: \n\t" > + "pushl %ebp \n\t" > + "movl %esp, %ebp \n\t" > + "pusha \n\t" > + "pushf \n\t" > + "push %es \n\t" > + "push %ds \n\t" > + "pop %es \n\t" > + "movl 8(%ebp),%eax \n\t" > + "movl 4(%eax),%ebx \n\t" > + "movl 8(%eax),%ecx \n\t" > + "movl 12(%eax),%edx \n\t" > + "movl 16(%eax),%esi \n\t" > + "movl 20(%eax),%edi \n\t" > + "movl (%eax),%eax \n\t" > + "push %cs \n\t" > + "call *12(%ebp) \n\t" > + "pushf \n\t" > + "pushl %eax \n\t" > + "movl 8(%ebp),%eax \n\t" > + "movl %ebx,4(%eax) \n\t" > + "movl %ecx,8(%eax) \n\t" > + "movl %edx,12(%eax) \n\t" > + "movl %esi,16(%eax) \n\t" > + "movl %edi,20(%eax) \n\t" > + "movw %ds,24(%eax) \n\t" > + "movw %es,26(%eax) \n\t" > + "popl %ebx \n\t" > + "movl %ebx,(%eax) \n\t" > + "popl %ebx \n\t" > + "movl %ebx,28(%eax) \n\t" > + "pop %es \n\t" > + "popf \n\t" > + "popa \n\t" > + "leave \n\t" > + "ret \n\t" > + ".previous"); > + > + > +/* > + * cru_detect > + * > + * Routine Description: > + * This function uses the 32-bit BIOS Service Directory record to > + * search for a $CRU record. > + * > + * Return Value: > + * 0 : SUCCESS > + * <0 : FAILURE > + */ > +static int cru_detect(unsigned long map_entry, > + unsigned long map_offset) > +{ > + void *bios32_map; > + unsigned long *bios32_entrypoint; > + unsigned long cru_physical_address; > + unsigned long cru_length; > + unsigned long physical_bios_base = 0; > + unsigned long physical_bios_offset = 0; > + int retval = -ENODEV; > + > + bios32_map = ioremap(map_entry, (2 * PAGE_SIZE)); > + > + if (bios32_map == NULL) > + return -ENODEV; > + > + bios32_entrypoint = bios32_map + map_offset; > + > + cmn_regs.u1.reax = CRU_BIOS_SIGNATURE_VALUE; > + > + set_memory_x((unsigned long)bios32_map, 2); > + asminline_call(&cmn_regs, bios32_entrypoint); > + > + if (cmn_regs.u1.ral != 0) { > + pr_warn("Call succeeded but with an error: 0x%x\n", > + cmn_regs.u1.ral); > + } else { > + physical_bios_base = cmn_regs.u2.rebx; > + physical_bios_offset = cmn_regs.u4.redx; > + cru_length = cmn_regs.u3.recx; > + cru_physical_address = > + physical_bios_base + physical_bios_offset; > + > + /* If the values look OK, then map it in. */ > + if ((physical_bios_base + physical_bios_offset)) { > + cru_rom_addr = > + ioremap(cru_physical_address, cru_length); > + if (cru_rom_addr) { > + set_memory_x((unsigned long)cru_rom_addr & PAGE_MASK, > + (cru_length + PAGE_SIZE - 1) >> PAGE_SHIFT); > + retval = 0; > + } > + } > + > + pr_debug("CRU Base Address: 0x%lx\n", physical_bios_base); > + pr_debug("CRU Offset Address: 0x%lx\n", physical_bios_offset); > + pr_debug("CRU Length: 0x%lx\n", cru_length); > + pr_debug("CRU Mapped Address: %p\n", &cru_rom_addr); > + } > + iounmap(bios32_map); > + return retval; > +} > + > +/* > + * bios_checksum > + */ > +static int bios_checksum(const char __iomem *ptr, int len) > +{ > + char sum = 0; > + int i; > + > + /* > + * calculate checksum of size bytes. This should add up > + * to zero if we have a valid header. > + */ > + for (i = 0; i < len; i++) > + sum += ptr[i]; > + > + return ((sum == 0) && (len > 0)); > +} > + > +/* > + * bios32_present > + * > + * Routine Description: > + * This function finds the 32-bit BIOS Service Directory > + * > + * Return Value: > + * 0 : SUCCESS > + * <0 : FAILURE > + */ > +static int bios32_present(const char __iomem *p) > +{ > + struct bios32_service_dir *bios_32_ptr; > + int length; > + unsigned long map_entry, map_offset; > + > + bios_32_ptr = (struct bios32_service_dir *) p; > + > + /* > + * Search for signature by checking equal to the swizzled value > + * instead of calling another routine to perform a strcmp. > + */ > + if (bios_32_ptr->signature == PCI_BIOS32_SD_VALUE) { > + length = bios_32_ptr->length * PCI_BIOS32_PARAGRAPH_LEN; > + if (bios_checksum(p, length)) { > + /* > + * According to the spec, we're looking for the > + * first 4KB-aligned address below the entrypoint > + * listed in the header. The Service Directory code > + * is guaranteed to occupy no more than 2 4KB pages. > + */ > + map_entry = bios_32_ptr->entry_point & ~(PAGE_SIZE - 1); > + map_offset = bios_32_ptr->entry_point - map_entry; > + > + return cru_detect(map_entry, map_offset); > + } > + } > + return -ENODEV; > +} > + > +static int detect_cru_service(void) > +{ > + char __iomem *p, *q; > + int rc = -1; > + > + /* > + * Search from 0x0f0000 through 0x0fffff, inclusive. > + */ > + p = ioremap(PCI_ROM_BASE1, ROM_SIZE); > + if (p == NULL) > + return -ENOMEM; > + > + for (q = p; q < p + ROM_SIZE; q += 16) { > + rc = bios32_present(q); > + if (!rc) > + break; > + } > + iounmap(p); > + return rc; > +} > +/* ------------------------------------------------------------------------- */ > +#endif /* CONFIG_X86_32 */ > +#ifdef CONFIG_X86_64 > +/* --64 Bit Bios------------------------------------------------------------ */ > + > +#define HPWDT_ARCH 64 > + > +asm(".text \n\t" > + ".align 4 \n" > + "asminline_call: \n\t" > + "pushq %rbp \n\t" > + "movq %rsp, %rbp \n\t" > + "pushq %rax \n\t" > + "pushq %rbx \n\t" > + "pushq %rdx \n\t" > + "pushq %r12 \n\t" > + "pushq %r9 \n\t" > + "movq %rsi, %r12 \n\t" > + "movq %rdi, %r9 \n\t" > + "movl 4(%r9),%ebx \n\t" > + "movl 8(%r9),%ecx \n\t" > + "movl 12(%r9),%edx \n\t" > + "movl 16(%r9),%esi \n\t" > + "movl 20(%r9),%edi \n\t" > + "movl (%r9),%eax \n\t" > + "call *%r12 \n\t" > + "pushfq \n\t" > + "popq %r12 \n\t" > + "movl %eax, (%r9) \n\t" > + "movl %ebx, 4(%r9) \n\t" > + "movl %ecx, 8(%r9) \n\t" > + "movl %edx, 12(%r9) \n\t" > + "movl %esi, 16(%r9) \n\t" > + "movl %edi, 20(%r9) \n\t" > + "movq %r12, %rax \n\t" > + "movl %eax, 28(%r9) \n\t" > + "popq %r9 \n\t" > + "popq %r12 \n\t" > + "popq %rdx \n\t" > + "popq %rbx \n\t" > + "popq %rax \n\t" > + "leave \n\t" > + "ret \n\t" > + ".previous"); > + > +/* > + * dmi_find_cru > + * > + * Routine Description: > + * This function checks whether or not a SMBIOS/DMI record is > + * the 64bit CRU info or not > + */ > +static void dmi_find_cru(const struct dmi_header *dm, void *dummy) > +{ > + struct smbios_cru64_info *smbios_cru64_ptr; > + unsigned long cru_physical_address; > + > + if (dm->type == SMBIOS_CRU64_INFORMATION) { > + smbios_cru64_ptr = (struct smbios_cru64_info *) dm; > + if (smbios_cru64_ptr->signature == CRU_BIOS_SIGNATURE_VALUE) { > + cru_physical_address = > + smbios_cru64_ptr->physical_address + > + smbios_cru64_ptr->double_offset; > + cru_rom_addr = ioremap(cru_physical_address, > + smbios_cru64_ptr->double_length); > + set_memory_x((unsigned long)cru_rom_addr & PAGE_MASK, > + smbios_cru64_ptr->double_length >> PAGE_SHIFT); > + } > + } > +} > + > +static int detect_cru_service(void) > +{ > + cru_rom_addr = NULL; > + > + dmi_walk(dmi_find_cru, NULL); > + > + /* if cru_rom_addr has been set then we found a CRU service */ > + return ((cru_rom_addr != NULL) ? 0 : -ENODEV); > +} > +/* ------------------------------------------------------------------------- */ > +#endif /* CONFIG_X86_64 */ > +#endif /* CONFIG_HPWDT_NMI_DECODING */ > + > +/* > + * Watchdog operations > + */ > +static void hpwdt_start(void) > +{ > + reload = SECS_TO_TICKS(soft_margin); > + iowrite16(reload, hpwdt_timer_reg); > + iowrite8(0x85, hpwdt_timer_con); > +} > + > +static void hpwdt_stop(void) > +{ > + unsigned long data; > + > + data = ioread8(hpwdt_timer_con); > + data &= 0xFE; > + iowrite8(data, hpwdt_timer_con); > +} > + > +static void hpwdt_ping(void) > +{ > + iowrite16(reload, hpwdt_timer_reg); > +} > + > +static int hpwdt_change_timer(int new_margin) > +{ > + if (new_margin < 1 || new_margin > HPWDT_MAX_TIMER) { > + pr_warn("New value passed in is invalid: %d seconds\n", > + new_margin); > + return -EINVAL; > + } > + > + soft_margin = new_margin; > + pr_debug("New timer passed in is %d seconds\n", new_margin); > + reload = SECS_TO_TICKS(soft_margin); > + > + return 0; > +} > + > +static int hpwdt_time_left(void) > +{ > + return TICKS_TO_SECS(ioread16(hpwdt_timer_reg)); > +} > + > +#ifdef CONFIG_HPWDT_NMI_DECODING > +/* > + * NMI Handler > + */ > +static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs) > +{ > + unsigned long rom_pl; > + static int die_nmi_called; > + > + if (!hpwdt_nmi_decoding) > + goto out; > + > + spin_lock_irqsave(&rom_lock, rom_pl); > + if (!die_nmi_called && !is_icru && !is_uefi) > + asminline_call(&cmn_regs, cru_rom_addr); > + die_nmi_called = 1; > + spin_unlock_irqrestore(&rom_lock, rom_pl); > + > + if (allow_kdump) > + hpwdt_stop(); > + > + if (!is_icru && !is_uefi) { > + if (cmn_regs.u1.ral == 0) { > + panic("An NMI occurred, " > + "but unable to determine source.\n"); > + } > + } > + panic("An NMI occurred, please see the Integrated " > + "Management Log for details.\n"); > + > +out: > + return NMI_DONE; > +} > +#endif /* CONFIG_HPWDT_NMI_DECODING */ > + > +/* > + * /dev/watchdog handling > + */ > +static int hpwdt_open(struct inode *inode, struct file *file) > +{ > + /* /dev/watchdog can only be opened once */ > + if (test_and_set_bit(0, &hpwdt_is_open)) > + return -EBUSY; > + > + /* Start the watchdog */ > + hpwdt_start(); > + hpwdt_ping(); > + > + return nonseekable_open(inode, file); > +} > + > +static int hpwdt_release(struct inode *inode, struct file *file) > +{ > + /* Stop the watchdog */ > + if (expect_release == 42) { > + hpwdt_stop(); > + } else { > + pr_crit("Unexpected close, not stopping watchdog!\n"); > + hpwdt_ping(); > + } > + > + expect_release = 0; > + > + /* /dev/watchdog is being closed, make sure it can be re-opened */ > + clear_bit(0, &hpwdt_is_open); > + > + return 0; > +} > + > +static ssize_t hpwdt_write(struct file *file, const char __user *data, > + size_t len, loff_t *ppos) > +{ > + /* See if we got the magic character 'V' and reload the timer */ > + if (len) { > + if (!nowayout) { > + size_t i; > + > + /* note: just in case someone wrote the magic character > + * five months ago... */ > + expect_release = 0; > + > + /* scan to see whether or not we got the magic char. */ > + for (i = 0; i != len; i++) { > + char c; > + if (get_user(c, data + i)) > + return -EFAULT; > + if (c == 'V') > + expect_release = 42; > + } > + } > + > + /* someone wrote to us, we should reload the timer */ > + hpwdt_ping(); > + } > + > + return len; > +} > + > +static const struct watchdog_info ident = { > + .options = WDIOF_SETTIMEOUT | > + WDIOF_KEEPALIVEPING | > + WDIOF_MAGICCLOSE, > + .identity = "HP iLO2+ HW Watchdog Timer", > +}; > + > +static long hpwdt_ioctl(struct file *file, unsigned int cmd, > + unsigned long arg) > +{ > + void __user *argp = (void __user *)arg; > + int __user *p = argp; > + int new_margin; > + int ret = -ENOTTY; > + > + switch (cmd) { > + case WDIOC_GETSUPPORT: > + ret = 0; > + if (copy_to_user(argp, &ident, sizeof(ident))) > + ret = -EFAULT; > + break; > + > + case WDIOC_GETSTATUS: > + case WDIOC_GETBOOTSTATUS: > + ret = put_user(0, p); > + break; > + > + case WDIOC_KEEPALIVE: > + hpwdt_ping(); > + ret = 0; > + break; > + > + case WDIOC_SETTIMEOUT: > + ret = get_user(new_margin, p); > + if (ret) > + break; > + > + ret = hpwdt_change_timer(new_margin); > + if (ret) > + break; > + > + hpwdt_ping(); > + /* Fall */ > + case WDIOC_GETTIMEOUT: > + ret = put_user(soft_margin, p); > + break; > + > + case WDIOC_GETTIMELEFT: > + ret = put_user(hpwdt_time_left(), p); > + break; > + } > + return ret; > +} > + > +/* > + * Kernel interfaces > + */ > +static const struct file_operations hpwdt_fops = { > + .owner = THIS_MODULE, > + .llseek = no_llseek, > + .write = hpwdt_write, > + .unlocked_ioctl = hpwdt_ioctl, > + .open = hpwdt_open, > + .release = hpwdt_release, > +}; > + > +static struct miscdevice hpwdt_miscdev = { > + .minor = WATCHDOG_MINOR, > + .name = "watchdog", > + .fops = &hpwdt_fops, > +}; > + > +/* > + * Init & Exit > + */ > + > +#ifdef CONFIG_HPWDT_NMI_DECODING > +#ifdef CONFIG_X86_LOCAL_APIC > +static void hpwdt_check_nmi_decoding(struct pci_dev *dev) > +{ > + /* > + * If nmi_watchdog is turned off then we can turn on > + * our nmi decoding capability. > + */ > + hpwdt_nmi_decoding = 1; > +} > +#else > +static void hpwdt_check_nmi_decoding(struct pci_dev *dev) > +{ > + dev_warn(&dev->dev, "NMI decoding is disabled. " > + "Your kernel does not support a NMI Watchdog.\n"); > +} > +#endif /* CONFIG_X86_LOCAL_APIC */ > + > +/* > + * dmi_find_icru > + * > + * Routine Description: > + * This function checks whether or not we are on an iCRU-based server. > + * This check is independent of architecture and needs to be made for > + * any ProLiant system. > + */ > +static void dmi_find_icru(const struct dmi_header *dm, void *dummy) > +{ > + struct smbios_proliant_info *smbios_proliant_ptr; > + > + if (dm->type == SMBIOS_ICRU_INFORMATION) { > + smbios_proliant_ptr = (struct smbios_proliant_info *) dm; > + if (smbios_proliant_ptr->misc_features & 0x01) > + is_icru = 1; > + if (smbios_proliant_ptr->misc_features & 0x408) > + is_uefi = 1; > + } > +} > + > +static int hpwdt_init_nmi_decoding(struct pci_dev *dev) > +{ > + int retval; > + > + /* > + * On typical CRU-based systems we need to map that service in > + * the BIOS. For 32 bit Operating Systems we need to go through > + * the 32 Bit BIOS Service Directory. For 64 bit Operating > + * Systems we get that service through SMBIOS. > + * > + * On systems that support the new iCRU service all we need to > + * do is call dmi_walk to get the supported flag value and skip > + * the old cru detect code. > + */ > + dmi_walk(dmi_find_icru, NULL); > + if (!is_icru && !is_uefi) { > + > + /* > + * We need to map the ROM to get the CRU service. > + * For 32 bit Operating Systems we need to go through the 32 Bit > + * BIOS Service Directory > + * For 64 bit Operating Systems we get that service through SMBIOS. > + */ > + retval = detect_cru_service(); > + if (retval < 0) { > + dev_warn(&dev->dev, > + "Unable to detect the %d Bit CRU Service.\n", > + HPWDT_ARCH); > + return retval; > + } > + > + /* > + * We know this is the only CRU call we need to make so lets keep as > + * few instructions as possible once the NMI comes in. > + */ > + cmn_regs.u1.rah = 0x0D; > + cmn_regs.u1.ral = 0x02; > + } > + > + /* > + * Only one function can register for NMI_UNKNOWN > + */ > + retval = register_nmi_handler(NMI_UNKNOWN, hpwdt_pretimeout, 0, "hpwdt"); > + if (retval) > + goto error; > + retval = register_nmi_handler(NMI_SERR, hpwdt_pretimeout, 0, "hpwdt"); > + if (retval) > + goto error1; > + retval = register_nmi_handler(NMI_IO_CHECK, hpwdt_pretimeout, 0, "hpwdt"); > + if (retval) > + goto error2; > + > + dev_info(&dev->dev, > + "HP Watchdog Timer Driver: NMI decoding initialized" > + ", allow kernel dump: %s (default = 0/OFF)\n", > + (allow_kdump == 0) ? "OFF" : "ON"); > + return 0; > + > +error2: > + unregister_nmi_handler(NMI_SERR, "hpwdt"); > +error1: > + unregister_nmi_handler(NMI_UNKNOWN, "hpwdt"); > +error: > + dev_warn(&dev->dev, > + "Unable to register a die notifier (err=%d).\n", > + retval); > + if (cru_rom_addr) > + iounmap(cru_rom_addr); > + return retval; > +} > + > +static void hpwdt_exit_nmi_decoding(void) > +{ > + unregister_nmi_handler(NMI_UNKNOWN, "hpwdt"); > + unregister_nmi_handler(NMI_SERR, "hpwdt"); > + unregister_nmi_handler(NMI_IO_CHECK, "hpwdt"); > + if (cru_rom_addr) > + iounmap(cru_rom_addr); > +} > +#else /* !CONFIG_HPWDT_NMI_DECODING */ > +static void hpwdt_check_nmi_decoding(struct pci_dev *dev) > +{ > +} > + > +static int hpwdt_init_nmi_decoding(struct pci_dev *dev) > +{ > + return 0; > +} > + > +static void hpwdt_exit_nmi_decoding(void) > +{ > +} > +#endif /* CONFIG_HPWDT_NMI_DECODING */ > + > +static int hpwdt_init_one(struct pci_dev *dev, > + const struct pci_device_id *ent) > +{ > + int retval; > + > + /* > + * Check if we can do NMI decoding or not > + */ > + hpwdt_check_nmi_decoding(dev); > + > + /* > + * First let's find out if we are on an iLO2+ server. We will > + * not run on a legacy ASM box. > + * So we only support the G5 ProLiant servers and higher. > + */ > + if (dev->subsystem_vendor != PCI_VENDOR_ID_HP) { > + dev_warn(&dev->dev, > + "This server does not have an iLO2+ ASIC.\n"); > + return -ENODEV; > + } > + > + if (pci_enable_device(dev)) { > + dev_warn(&dev->dev, > + "Not possible to enable PCI Device: 0x%x:0x%x.\n", > + ent->vendor, ent->device); > + return -ENODEV; > + } > + > + pci_mem_addr = pci_iomap(dev, 1, 0x80); > + if (!pci_mem_addr) { > + dev_warn(&dev->dev, > + "Unable to detect the iLO2+ server memory.\n"); > + retval = -ENOMEM; > + goto error_pci_iomap; > + } > + hpwdt_timer_reg = pci_mem_addr + 0x70; > + hpwdt_timer_con = pci_mem_addr + 0x72; > + > + /* Make sure that timer is disabled until /dev/watchdog is opened */ > + hpwdt_stop(); > + > + /* Make sure that we have a valid soft_margin */ > + if (hpwdt_change_timer(soft_margin)) > + hpwdt_change_timer(DEFAULT_MARGIN); > + > + /* Initialize NMI Decoding functionality */ > + retval = hpwdt_init_nmi_decoding(dev); > + if (retval != 0) > + goto error_init_nmi_decoding; > + > + retval = misc_register(&hpwdt_miscdev); > + if (retval < 0) { > + dev_warn(&dev->dev, > + "Unable to register miscdev on minor=%d (err=%d).\n", > + WATCHDOG_MINOR, retval); > + goto error_misc_register; > + } > + > + dev_info(&dev->dev, "HP Watchdog Timer Driver: %s" > + ", timer margin: %d seconds (nowayout=%d).\n", > + HPWDT_VERSION, soft_margin, nowayout); > + return 0; > + > +error_misc_register: > + hpwdt_exit_nmi_decoding(); > +error_init_nmi_decoding: > + pci_iounmap(dev, pci_mem_addr); > +error_pci_iomap: > + pci_disable_device(dev); > + return retval; > +} > + > +static void hpwdt_exit(struct pci_dev *dev) > +{ > + if (!nowayout) > + hpwdt_stop(); > + > + misc_deregister(&hpwdt_miscdev); > + hpwdt_exit_nmi_decoding(); > + pci_iounmap(dev, pci_mem_addr); > + pci_disable_device(dev); > +} > + > +static struct pci_driver hpwdt_driver = { > + .name = "hpwdt", > + .id_table = hpwdt_devices, > + .probe = hpwdt_init_one, > + .remove = hpwdt_exit, > +}; > + > +MODULE_AUTHOR("Tom Mingarelli"); > +MODULE_DESCRIPTION("hp watchdog driver"); > +MODULE_LICENSE("GPL"); > +MODULE_VERSION(HPWDT_VERSION); > +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); > + > +module_param(soft_margin, int, 0); > +MODULE_PARM_DESC(soft_margin, "Watchdog timeout in seconds"); > + > +module_param(nowayout, bool, 0); > +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" > + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); > + > +#ifdef CONFIG_HPWDT_NMI_DECODING > +module_param(allow_kdump, int, 0); > +MODULE_PARM_DESC(allow_kdump, "Start a kernel dump after NMI occurs"); > +#endif /* !CONFIG_HPWDT_NMI_DECODING */ > + > +module_pci_driver(hpwdt_driver); > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig > index 362085d..a4e2823 100644 > --- a/drivers/watchdog/Kconfig > +++ b/drivers/watchdog/Kconfig > @@ -668,16 +668,6 @@ config IT87_WDT > To compile this driver as a module, choose M here: the module will > be called it87_wdt. > > -config HP_WATCHDOG > - tristate "HP ProLiant iLO2+ Hardware Watchdog Timer" > - depends on X86 && PCI > - help > - A software monitoring watchdog and NMI sourcing driver. This driver > - will detect lockups and provide a stack trace. This is a driver that > - will only load on an HP ProLiant system with a minimum of iLO2 support. > - To compile this driver as a module, choose M here: the module will be > - called hpwdt. > - > config KEMPLD_WDT > tristate "Kontron COM Watchdog Timer" > depends on MFD_KEMPLD > @@ -689,14 +679,6 @@ config KEMPLD_WDT > This driver can also be built as a module. If so, the module will be > called kempld_wdt. > > -config HPWDT_NMI_DECODING > - bool "NMI decoding support for the HP ProLiant iLO2+ Hardware Watchdog Timer" > - depends on HP_WATCHDOG > - default y > - help > - When an NMI occurs this feature will make the necessary BIOS calls to > - log the cause of the NMI. > - > config SC1200_WDT > tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog" > depends on X86 > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile > index 2f26a0b..bb20ee0 100644 > --- a/drivers/watchdog/Makefile > +++ b/drivers/watchdog/Makefile > @@ -89,7 +89,6 @@ obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o > endif > obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o > obj-$(CONFIG_IT87_WDT) += it87_wdt.o > -obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o > obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o > obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o > obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o > diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c > deleted file mode 100644 > index de7e4f4..0000000 > --- a/drivers/watchdog/hpwdt.c > +++ /dev/null > @@ -1,890 +0,0 @@ > -/* > - * HP WatchDog Driver > - * based on > - * > - * SoftDog 0.05: A Software Watchdog Device > - * > - * (c) Copyright 2007 Hewlett-Packard Development Company, L.P. > - * Thomas Mingarelli > - * > - * 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 > - * > - */ > - > -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt > - > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#ifdef CONFIG_HPWDT_NMI_DECODING > -#include > -#include > -#include > -#include > -#include > -#include > -#endif /* CONFIG_HPWDT_NMI_DECODING */ > -#include > - > -#define HPWDT_VERSION "1.3.2" > -#define SECS_TO_TICKS(secs) ((secs) * 1000 / 128) > -#define TICKS_TO_SECS(ticks) ((ticks) * 128 / 1000) > -#define HPWDT_MAX_TIMER TICKS_TO_SECS(65535) > -#define DEFAULT_MARGIN 30 > - > -static unsigned int soft_margin = DEFAULT_MARGIN; /* in seconds */ > -static unsigned int reload; /* the computed soft_margin */ > -static bool nowayout = WATCHDOG_NOWAYOUT; > -static char expect_release; > -static unsigned long hpwdt_is_open; > - > -static void __iomem *pci_mem_addr; /* the PCI-memory address */ > -static unsigned long __iomem *hpwdt_timer_reg; > -static unsigned long __iomem *hpwdt_timer_con; > - > -static DEFINE_PCI_DEVICE_TABLE(hpwdt_devices) = { > - { PCI_DEVICE(PCI_VENDOR_ID_COMPAQ, 0xB203) }, /* iLO2 */ > - { PCI_DEVICE(PCI_VENDOR_ID_HP, 0x3306) }, /* iLO3 */ > - {0}, /* terminate list */ > -}; > -MODULE_DEVICE_TABLE(pci, hpwdt_devices); > - > -#ifdef CONFIG_HPWDT_NMI_DECODING > -#define PCI_BIOS32_SD_VALUE 0x5F32335F /* "_32_" */ > -#define CRU_BIOS_SIGNATURE_VALUE 0x55524324 > -#define PCI_BIOS32_PARAGRAPH_LEN 16 > -#define PCI_ROM_BASE1 0x000F0000 > -#define ROM_SIZE 0x10000 > - > -struct bios32_service_dir { > - u32 signature; > - u32 entry_point; > - u8 revision; > - u8 length; > - u8 checksum; > - u8 reserved[5]; > -}; > - > -/* type 212 */ > -struct smbios_cru64_info { > - u8 type; > - u8 byte_length; > - u16 handle; > - u32 signature; > - u64 physical_address; > - u32 double_length; > - u32 double_offset; > -}; > -#define SMBIOS_CRU64_INFORMATION 212 > - > -/* type 219 */ > -struct smbios_proliant_info { > - u8 type; > - u8 byte_length; > - u16 handle; > - u32 power_features; > - u32 omega_features; > - u32 reserved; > - u32 misc_features; > -}; > -#define SMBIOS_ICRU_INFORMATION 219 > - > - > -struct cmn_registers { > - union { > - struct { > - u8 ral; > - u8 rah; > - u16 rea2; > - }; > - u32 reax; > - } u1; > - union { > - struct { > - u8 rbl; > - u8 rbh; > - u8 reb2l; > - u8 reb2h; > - }; > - u32 rebx; > - } u2; > - union { > - struct { > - u8 rcl; > - u8 rch; > - u16 rec2; > - }; > - u32 recx; > - } u3; > - union { > - struct { > - u8 rdl; > - u8 rdh; > - u16 red2; > - }; > - u32 redx; > - } u4; > - > - u32 resi; > - u32 redi; > - u16 rds; > - u16 res; > - u32 reflags; > -} __attribute__((packed)); > - > -static unsigned int hpwdt_nmi_decoding; > -static unsigned int allow_kdump = 1; > -static unsigned int is_icru; > -static unsigned int is_uefi; > -static DEFINE_SPINLOCK(rom_lock); > -static void *cru_rom_addr; > -static struct cmn_registers cmn_regs; > - > -extern asmlinkage void asminline_call(struct cmn_registers *pi86Regs, > - unsigned long *pRomEntry); > - > -#ifdef CONFIG_X86_32 > -/* --32 Bit Bios------------------------------------------------------------ */ > - > -#define HPWDT_ARCH 32 > - > -asm(".text \n\t" > - ".align 4 \n" > - "asminline_call: \n\t" > - "pushl %ebp \n\t" > - "movl %esp, %ebp \n\t" > - "pusha \n\t" > - "pushf \n\t" > - "push %es \n\t" > - "push %ds \n\t" > - "pop %es \n\t" > - "movl 8(%ebp),%eax \n\t" > - "movl 4(%eax),%ebx \n\t" > - "movl 8(%eax),%ecx \n\t" > - "movl 12(%eax),%edx \n\t" > - "movl 16(%eax),%esi \n\t" > - "movl 20(%eax),%edi \n\t" > - "movl (%eax),%eax \n\t" > - "push %cs \n\t" > - "call *12(%ebp) \n\t" > - "pushf \n\t" > - "pushl %eax \n\t" > - "movl 8(%ebp),%eax \n\t" > - "movl %ebx,4(%eax) \n\t" > - "movl %ecx,8(%eax) \n\t" > - "movl %edx,12(%eax) \n\t" > - "movl %esi,16(%eax) \n\t" > - "movl %edi,20(%eax) \n\t" > - "movw %ds,24(%eax) \n\t" > - "movw %es,26(%eax) \n\t" > - "popl %ebx \n\t" > - "movl %ebx,(%eax) \n\t" > - "popl %ebx \n\t" > - "movl %ebx,28(%eax) \n\t" > - "pop %es \n\t" > - "popf \n\t" > - "popa \n\t" > - "leave \n\t" > - "ret \n\t" > - ".previous"); > - > - > -/* > - * cru_detect > - * > - * Routine Description: > - * This function uses the 32-bit BIOS Service Directory record to > - * search for a $CRU record. > - * > - * Return Value: > - * 0 : SUCCESS > - * <0 : FAILURE > - */ > -static int cru_detect(unsigned long map_entry, > - unsigned long map_offset) > -{ > - void *bios32_map; > - unsigned long *bios32_entrypoint; > - unsigned long cru_physical_address; > - unsigned long cru_length; > - unsigned long physical_bios_base = 0; > - unsigned long physical_bios_offset = 0; > - int retval = -ENODEV; > - > - bios32_map = ioremap(map_entry, (2 * PAGE_SIZE)); > - > - if (bios32_map == NULL) > - return -ENODEV; > - > - bios32_entrypoint = bios32_map + map_offset; > - > - cmn_regs.u1.reax = CRU_BIOS_SIGNATURE_VALUE; > - > - set_memory_x((unsigned long)bios32_map, 2); > - asminline_call(&cmn_regs, bios32_entrypoint); > - > - if (cmn_regs.u1.ral != 0) { > - pr_warn("Call succeeded but with an error: 0x%x\n", > - cmn_regs.u1.ral); > - } else { > - physical_bios_base = cmn_regs.u2.rebx; > - physical_bios_offset = cmn_regs.u4.redx; > - cru_length = cmn_regs.u3.recx; > - cru_physical_address = > - physical_bios_base + physical_bios_offset; > - > - /* If the values look OK, then map it in. */ > - if ((physical_bios_base + physical_bios_offset)) { > - cru_rom_addr = > - ioremap(cru_physical_address, cru_length); > - if (cru_rom_addr) { > - set_memory_x((unsigned long)cru_rom_addr & PAGE_MASK, > - (cru_length + PAGE_SIZE - 1) >> PAGE_SHIFT); > - retval = 0; > - } > - } > - > - pr_debug("CRU Base Address: 0x%lx\n", physical_bios_base); > - pr_debug("CRU Offset Address: 0x%lx\n", physical_bios_offset); > - pr_debug("CRU Length: 0x%lx\n", cru_length); > - pr_debug("CRU Mapped Address: %p\n", &cru_rom_addr); > - } > - iounmap(bios32_map); > - return retval; > -} > - > -/* > - * bios_checksum > - */ > -static int bios_checksum(const char __iomem *ptr, int len) > -{ > - char sum = 0; > - int i; > - > - /* > - * calculate checksum of size bytes. This should add up > - * to zero if we have a valid header. > - */ > - for (i = 0; i < len; i++) > - sum += ptr[i]; > - > - return ((sum == 0) && (len > 0)); > -} > - > -/* > - * bios32_present > - * > - * Routine Description: > - * This function finds the 32-bit BIOS Service Directory > - * > - * Return Value: > - * 0 : SUCCESS > - * <0 : FAILURE > - */ > -static int bios32_present(const char __iomem *p) > -{ > - struct bios32_service_dir *bios_32_ptr; > - int length; > - unsigned long map_entry, map_offset; > - > - bios_32_ptr = (struct bios32_service_dir *) p; > - > - /* > - * Search for signature by checking equal to the swizzled value > - * instead of calling another routine to perform a strcmp. > - */ > - if (bios_32_ptr->signature == PCI_BIOS32_SD_VALUE) { > - length = bios_32_ptr->length * PCI_BIOS32_PARAGRAPH_LEN; > - if (bios_checksum(p, length)) { > - /* > - * According to the spec, we're looking for the > - * first 4KB-aligned address below the entrypoint > - * listed in the header. The Service Directory code > - * is guaranteed to occupy no more than 2 4KB pages. > - */ > - map_entry = bios_32_ptr->entry_point & ~(PAGE_SIZE - 1); > - map_offset = bios_32_ptr->entry_point - map_entry; > - > - return cru_detect(map_entry, map_offset); > - } > - } > - return -ENODEV; > -} > - > -static int detect_cru_service(void) > -{ > - char __iomem *p, *q; > - int rc = -1; > - > - /* > - * Search from 0x0f0000 through 0x0fffff, inclusive. > - */ > - p = ioremap(PCI_ROM_BASE1, ROM_SIZE); > - if (p == NULL) > - return -ENOMEM; > - > - for (q = p; q < p + ROM_SIZE; q += 16) { > - rc = bios32_present(q); > - if (!rc) > - break; > - } > - iounmap(p); > - return rc; > -} > -/* ------------------------------------------------------------------------- */ > -#endif /* CONFIG_X86_32 */ > -#ifdef CONFIG_X86_64 > -/* --64 Bit Bios------------------------------------------------------------ */ > - > -#define HPWDT_ARCH 64 > - > -asm(".text \n\t" > - ".align 4 \n" > - "asminline_call: \n\t" > - "pushq %rbp \n\t" > - "movq %rsp, %rbp \n\t" > - "pushq %rax \n\t" > - "pushq %rbx \n\t" > - "pushq %rdx \n\t" > - "pushq %r12 \n\t" > - "pushq %r9 \n\t" > - "movq %rsi, %r12 \n\t" > - "movq %rdi, %r9 \n\t" > - "movl 4(%r9),%ebx \n\t" > - "movl 8(%r9),%ecx \n\t" > - "movl 12(%r9),%edx \n\t" > - "movl 16(%r9),%esi \n\t" > - "movl 20(%r9),%edi \n\t" > - "movl (%r9),%eax \n\t" > - "call *%r12 \n\t" > - "pushfq \n\t" > - "popq %r12 \n\t" > - "movl %eax, (%r9) \n\t" > - "movl %ebx, 4(%r9) \n\t" > - "movl %ecx, 8(%r9) \n\t" > - "movl %edx, 12(%r9) \n\t" > - "movl %esi, 16(%r9) \n\t" > - "movl %edi, 20(%r9) \n\t" > - "movq %r12, %rax \n\t" > - "movl %eax, 28(%r9) \n\t" > - "popq %r9 \n\t" > - "popq %r12 \n\t" > - "popq %rdx \n\t" > - "popq %rbx \n\t" > - "popq %rax \n\t" > - "leave \n\t" > - "ret \n\t" > - ".previous"); > - > -/* > - * dmi_find_cru > - * > - * Routine Description: > - * This function checks whether or not a SMBIOS/DMI record is > - * the 64bit CRU info or not > - */ > -static void dmi_find_cru(const struct dmi_header *dm, void *dummy) > -{ > - struct smbios_cru64_info *smbios_cru64_ptr; > - unsigned long cru_physical_address; > - > - if (dm->type == SMBIOS_CRU64_INFORMATION) { > - smbios_cru64_ptr = (struct smbios_cru64_info *) dm; > - if (smbios_cru64_ptr->signature == CRU_BIOS_SIGNATURE_VALUE) { > - cru_physical_address = > - smbios_cru64_ptr->physical_address + > - smbios_cru64_ptr->double_offset; > - cru_rom_addr = ioremap(cru_physical_address, > - smbios_cru64_ptr->double_length); > - set_memory_x((unsigned long)cru_rom_addr & PAGE_MASK, > - smbios_cru64_ptr->double_length >> PAGE_SHIFT); > - } > - } > -} > - > -static int detect_cru_service(void) > -{ > - cru_rom_addr = NULL; > - > - dmi_walk(dmi_find_cru, NULL); > - > - /* if cru_rom_addr has been set then we found a CRU service */ > - return ((cru_rom_addr != NULL) ? 0 : -ENODEV); > -} > -/* ------------------------------------------------------------------------- */ > -#endif /* CONFIG_X86_64 */ > -#endif /* CONFIG_HPWDT_NMI_DECODING */ > - > -/* > - * Watchdog operations > - */ > -static void hpwdt_start(void) > -{ > - reload = SECS_TO_TICKS(soft_margin); > - iowrite16(reload, hpwdt_timer_reg); > - iowrite8(0x85, hpwdt_timer_con); > -} > - > -static void hpwdt_stop(void) > -{ > - unsigned long data; > - > - data = ioread8(hpwdt_timer_con); > - data &= 0xFE; > - iowrite8(data, hpwdt_timer_con); > -} > - > -static void hpwdt_ping(void) > -{ > - iowrite16(reload, hpwdt_timer_reg); > -} > - > -static int hpwdt_change_timer(int new_margin) > -{ > - if (new_margin < 1 || new_margin > HPWDT_MAX_TIMER) { > - pr_warn("New value passed in is invalid: %d seconds\n", > - new_margin); > - return -EINVAL; > - } > - > - soft_margin = new_margin; > - pr_debug("New timer passed in is %d seconds\n", new_margin); > - reload = SECS_TO_TICKS(soft_margin); > - > - return 0; > -} > - > -static int hpwdt_time_left(void) > -{ > - return TICKS_TO_SECS(ioread16(hpwdt_timer_reg)); > -} > - > -#ifdef CONFIG_HPWDT_NMI_DECODING > -/* > - * NMI Handler > - */ > -static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs) > -{ > - unsigned long rom_pl; > - static int die_nmi_called; > - > - if (!hpwdt_nmi_decoding) > - goto out; > - > - spin_lock_irqsave(&rom_lock, rom_pl); > - if (!die_nmi_called && !is_icru && !is_uefi) > - asminline_call(&cmn_regs, cru_rom_addr); > - die_nmi_called = 1; > - spin_unlock_irqrestore(&rom_lock, rom_pl); > - > - if (allow_kdump) > - hpwdt_stop(); > - > - if (!is_icru && !is_uefi) { > - if (cmn_regs.u1.ral == 0) { > - panic("An NMI occurred, " > - "but unable to determine source.\n"); > - } > - } > - panic("An NMI occurred, please see the Integrated " > - "Management Log for details.\n"); > - > -out: > - return NMI_DONE; > -} > -#endif /* CONFIG_HPWDT_NMI_DECODING */ > - > -/* > - * /dev/watchdog handling > - */ > -static int hpwdt_open(struct inode *inode, struct file *file) > -{ > - /* /dev/watchdog can only be opened once */ > - if (test_and_set_bit(0, &hpwdt_is_open)) > - return -EBUSY; > - > - /* Start the watchdog */ > - hpwdt_start(); > - hpwdt_ping(); > - > - return nonseekable_open(inode, file); > -} > - > -static int hpwdt_release(struct inode *inode, struct file *file) > -{ > - /* Stop the watchdog */ > - if (expect_release == 42) { > - hpwdt_stop(); > - } else { > - pr_crit("Unexpected close, not stopping watchdog!\n"); > - hpwdt_ping(); > - } > - > - expect_release = 0; > - > - /* /dev/watchdog is being closed, make sure it can be re-opened */ > - clear_bit(0, &hpwdt_is_open); > - > - return 0; > -} > - > -static ssize_t hpwdt_write(struct file *file, const char __user *data, > - size_t len, loff_t *ppos) > -{ > - /* See if we got the magic character 'V' and reload the timer */ > - if (len) { > - if (!nowayout) { > - size_t i; > - > - /* note: just in case someone wrote the magic character > - * five months ago... */ > - expect_release = 0; > - > - /* scan to see whether or not we got the magic char. */ > - for (i = 0; i != len; i++) { > - char c; > - if (get_user(c, data + i)) > - return -EFAULT; > - if (c == 'V') > - expect_release = 42; > - } > - } > - > - /* someone wrote to us, we should reload the timer */ > - hpwdt_ping(); > - } > - > - return len; > -} > - > -static const struct watchdog_info ident = { > - .options = WDIOF_SETTIMEOUT | > - WDIOF_KEEPALIVEPING | > - WDIOF_MAGICCLOSE, > - .identity = "HP iLO2+ HW Watchdog Timer", > -}; > - > -static long hpwdt_ioctl(struct file *file, unsigned int cmd, > - unsigned long arg) > -{ > - void __user *argp = (void __user *)arg; > - int __user *p = argp; > - int new_margin; > - int ret = -ENOTTY; > - > - switch (cmd) { > - case WDIOC_GETSUPPORT: > - ret = 0; > - if (copy_to_user(argp, &ident, sizeof(ident))) > - ret = -EFAULT; > - break; > - > - case WDIOC_GETSTATUS: > - case WDIOC_GETBOOTSTATUS: > - ret = put_user(0, p); > - break; > - > - case WDIOC_KEEPALIVE: > - hpwdt_ping(); > - ret = 0; > - break; > - > - case WDIOC_SETTIMEOUT: > - ret = get_user(new_margin, p); > - if (ret) > - break; > - > - ret = hpwdt_change_timer(new_margin); > - if (ret) > - break; > - > - hpwdt_ping(); > - /* Fall */ > - case WDIOC_GETTIMEOUT: > - ret = put_user(soft_margin, p); > - break; > - > - case WDIOC_GETTIMELEFT: > - ret = put_user(hpwdt_time_left(), p); > - break; > - } > - return ret; > -} > - > -/* > - * Kernel interfaces > - */ > -static const struct file_operations hpwdt_fops = { > - .owner = THIS_MODULE, > - .llseek = no_llseek, > - .write = hpwdt_write, > - .unlocked_ioctl = hpwdt_ioctl, > - .open = hpwdt_open, > - .release = hpwdt_release, > -}; > - > -static struct miscdevice hpwdt_miscdev = { > - .minor = WATCHDOG_MINOR, > - .name = "watchdog", > - .fops = &hpwdt_fops, > -}; > - > -/* > - * Init & Exit > - */ > - > -#ifdef CONFIG_HPWDT_NMI_DECODING > -#ifdef CONFIG_X86_LOCAL_APIC > -static void hpwdt_check_nmi_decoding(struct pci_dev *dev) > -{ > - /* > - * If nmi_watchdog is turned off then we can turn on > - * our nmi decoding capability. > - */ > - hpwdt_nmi_decoding = 1; > -} > -#else > -static void hpwdt_check_nmi_decoding(struct pci_dev *dev) > -{ > - dev_warn(&dev->dev, "NMI decoding is disabled. " > - "Your kernel does not support a NMI Watchdog.\n"); > -} > -#endif /* CONFIG_X86_LOCAL_APIC */ > - > -/* > - * dmi_find_icru > - * > - * Routine Description: > - * This function checks whether or not we are on an iCRU-based server. > - * This check is independent of architecture and needs to be made for > - * any ProLiant system. > - */ > -static void dmi_find_icru(const struct dmi_header *dm, void *dummy) > -{ > - struct smbios_proliant_info *smbios_proliant_ptr; > - > - if (dm->type == SMBIOS_ICRU_INFORMATION) { > - smbios_proliant_ptr = (struct smbios_proliant_info *) dm; > - if (smbios_proliant_ptr->misc_features & 0x01) > - is_icru = 1; > - if (smbios_proliant_ptr->misc_features & 0x408) > - is_uefi = 1; > - } > -} > - > -static int hpwdt_init_nmi_decoding(struct pci_dev *dev) > -{ > - int retval; > - > - /* > - * On typical CRU-based systems we need to map that service in > - * the BIOS. For 32 bit Operating Systems we need to go through > - * the 32 Bit BIOS Service Directory. For 64 bit Operating > - * Systems we get that service through SMBIOS. > - * > - * On systems that support the new iCRU service all we need to > - * do is call dmi_walk to get the supported flag value and skip > - * the old cru detect code. > - */ > - dmi_walk(dmi_find_icru, NULL); > - if (!is_icru && !is_uefi) { > - > - /* > - * We need to map the ROM to get the CRU service. > - * For 32 bit Operating Systems we need to go through the 32 Bit > - * BIOS Service Directory > - * For 64 bit Operating Systems we get that service through SMBIOS. > - */ > - retval = detect_cru_service(); > - if (retval < 0) { > - dev_warn(&dev->dev, > - "Unable to detect the %d Bit CRU Service.\n", > - HPWDT_ARCH); > - return retval; > - } > - > - /* > - * We know this is the only CRU call we need to make so lets keep as > - * few instructions as possible once the NMI comes in. > - */ > - cmn_regs.u1.rah = 0x0D; > - cmn_regs.u1.ral = 0x02; > - } > - > - /* > - * Only one function can register for NMI_UNKNOWN > - */ > - retval = register_nmi_handler(NMI_UNKNOWN, hpwdt_pretimeout, 0, "hpwdt"); > - if (retval) > - goto error; > - retval = register_nmi_handler(NMI_SERR, hpwdt_pretimeout, 0, "hpwdt"); > - if (retval) > - goto error1; > - retval = register_nmi_handler(NMI_IO_CHECK, hpwdt_pretimeout, 0, "hpwdt"); > - if (retval) > - goto error2; > - > - dev_info(&dev->dev, > - "HP Watchdog Timer Driver: NMI decoding initialized" > - ", allow kernel dump: %s (default = 0/OFF)\n", > - (allow_kdump == 0) ? "OFF" : "ON"); > - return 0; > - > -error2: > - unregister_nmi_handler(NMI_SERR, "hpwdt"); > -error1: > - unregister_nmi_handler(NMI_UNKNOWN, "hpwdt"); > -error: > - dev_warn(&dev->dev, > - "Unable to register a die notifier (err=%d).\n", > - retval); > - if (cru_rom_addr) > - iounmap(cru_rom_addr); > - return retval; > -} > - > -static void hpwdt_exit_nmi_decoding(void) > -{ > - unregister_nmi_handler(NMI_UNKNOWN, "hpwdt"); > - unregister_nmi_handler(NMI_SERR, "hpwdt"); > - unregister_nmi_handler(NMI_IO_CHECK, "hpwdt"); > - if (cru_rom_addr) > - iounmap(cru_rom_addr); > -} > -#else /* !CONFIG_HPWDT_NMI_DECODING */ > -static void hpwdt_check_nmi_decoding(struct pci_dev *dev) > -{ > -} > - > -static int hpwdt_init_nmi_decoding(struct pci_dev *dev) > -{ > - return 0; > -} > - > -static void hpwdt_exit_nmi_decoding(void) > -{ > -} > -#endif /* CONFIG_HPWDT_NMI_DECODING */ > - > -static int hpwdt_init_one(struct pci_dev *dev, > - const struct pci_device_id *ent) > -{ > - int retval; > - > - /* > - * Check if we can do NMI decoding or not > - */ > - hpwdt_check_nmi_decoding(dev); > - > - /* > - * First let's find out if we are on an iLO2+ server. We will > - * not run on a legacy ASM box. > - * So we only support the G5 ProLiant servers and higher. > - */ > - if (dev->subsystem_vendor != PCI_VENDOR_ID_HP) { > - dev_warn(&dev->dev, > - "This server does not have an iLO2+ ASIC.\n"); > - return -ENODEV; > - } > - > - if (pci_enable_device(dev)) { > - dev_warn(&dev->dev, > - "Not possible to enable PCI Device: 0x%x:0x%x.\n", > - ent->vendor, ent->device); > - return -ENODEV; > - } > - > - pci_mem_addr = pci_iomap(dev, 1, 0x80); > - if (!pci_mem_addr) { > - dev_warn(&dev->dev, > - "Unable to detect the iLO2+ server memory.\n"); > - retval = -ENOMEM; > - goto error_pci_iomap; > - } > - hpwdt_timer_reg = pci_mem_addr + 0x70; > - hpwdt_timer_con = pci_mem_addr + 0x72; > - > - /* Make sure that timer is disabled until /dev/watchdog is opened */ > - hpwdt_stop(); > - > - /* Make sure that we have a valid soft_margin */ > - if (hpwdt_change_timer(soft_margin)) > - hpwdt_change_timer(DEFAULT_MARGIN); > - > - /* Initialize NMI Decoding functionality */ > - retval = hpwdt_init_nmi_decoding(dev); > - if (retval != 0) > - goto error_init_nmi_decoding; > - > - retval = misc_register(&hpwdt_miscdev); > - if (retval < 0) { > - dev_warn(&dev->dev, > - "Unable to register miscdev on minor=%d (err=%d).\n", > - WATCHDOG_MINOR, retval); > - goto error_misc_register; > - } > - > - dev_info(&dev->dev, "HP Watchdog Timer Driver: %s" > - ", timer margin: %d seconds (nowayout=%d).\n", > - HPWDT_VERSION, soft_margin, nowayout); > - return 0; > - > -error_misc_register: > - hpwdt_exit_nmi_decoding(); > -error_init_nmi_decoding: > - pci_iounmap(dev, pci_mem_addr); > -error_pci_iomap: > - pci_disable_device(dev); > - return retval; > -} > - > -static void hpwdt_exit(struct pci_dev *dev) > -{ > - if (!nowayout) > - hpwdt_stop(); > - > - misc_deregister(&hpwdt_miscdev); > - hpwdt_exit_nmi_decoding(); > - pci_iounmap(dev, pci_mem_addr); > - pci_disable_device(dev); > -} > - > -static struct pci_driver hpwdt_driver = { > - .name = "hpwdt", > - .id_table = hpwdt_devices, > - .probe = hpwdt_init_one, > - .remove = hpwdt_exit, > -}; > - > -MODULE_AUTHOR("Tom Mingarelli"); > -MODULE_DESCRIPTION("hp watchdog driver"); > -MODULE_LICENSE("GPL"); > -MODULE_VERSION(HPWDT_VERSION); > -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); > - > -module_param(soft_margin, int, 0); > -MODULE_PARM_DESC(soft_margin, "Watchdog timeout in seconds"); > - > -module_param(nowayout, bool, 0); > -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" > - __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); > - > -#ifdef CONFIG_HPWDT_NMI_DECODING > -module_param(allow_kdump, int, 0); > -MODULE_PARM_DESC(allow_kdump, "Start a kernel dump after NMI occurs"); > -#endif /* !CONFIG_HPWDT_NMI_DECODING */ > - > -module_pci_driver(hpwdt_driver); > -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/