17f4da474SThomas Mingarelli /* 2ca22e79fSMingarelli, Thomas * HPE WatchDog Driver 37f4da474SThomas Mingarelli * based on 47f4da474SThomas Mingarelli * 57f4da474SThomas Mingarelli * SoftDog 0.05: A Software Watchdog Device 67f4da474SThomas Mingarelli * 7ca22e79fSMingarelli, Thomas * (c) Copyright 2015 Hewlett Packard Enterprise Development LP 8ca22e79fSMingarelli, Thomas * Thomas Mingarelli <thomas.mingarelli@hpe.com> 97f4da474SThomas Mingarelli * 107f4da474SThomas Mingarelli * This program is free software; you can redistribute it and/or 117f4da474SThomas Mingarelli * modify it under the terms of the GNU General Public License 127f4da474SThomas Mingarelli * version 2 as published by the Free Software Foundation 137f4da474SThomas Mingarelli * 147f4da474SThomas Mingarelli */ 157f4da474SThomas Mingarelli 1627c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1727c766aaSJoe Perches 187f4da474SThomas Mingarelli #include <linux/device.h> 197f4da474SThomas Mingarelli #include <linux/fs.h> 207f4da474SThomas Mingarelli #include <linux/io.h> 21a52e6d18Sdann frazier #include <linux/bitops.h> 227f4da474SThomas Mingarelli #include <linux/kernel.h> 237f4da474SThomas Mingarelli #include <linux/miscdevice.h> 247f4da474SThomas Mingarelli #include <linux/module.h> 257f4da474SThomas Mingarelli #include <linux/moduleparam.h> 267f4da474SThomas Mingarelli #include <linux/pci.h> 277f4da474SThomas Mingarelli #include <linux/pci_ids.h> 287f4da474SThomas Mingarelli #include <linux/types.h> 297f4da474SThomas Mingarelli #include <linux/uaccess.h> 307f4da474SThomas Mingarelli #include <linux/watchdog.h> 3186ded1f3Sdann frazier #ifdef CONFIG_HPWDT_NMI_DECODING 327f4da474SThomas Mingarelli #include <linux/dmi.h> 33a52e6d18Sdann frazier #include <linux/spinlock.h> 34923410d0Sdann frazier #include <linux/nmi.h> 35923410d0Sdann frazier #include <linux/kdebug.h> 36923410d0Sdann frazier #include <linux/notifier.h> 3723f19a56SLaura Abbott #include <asm/set_memory.h> 3886ded1f3Sdann frazier #endif /* CONFIG_HPWDT_NMI_DECODING */ 39d48b0e17SIngo Molnar #include <asm/nmi.h> 401923f3d0SIngo Molnar #include <asm/frame.h> 417f4da474SThomas Mingarelli 42fc113d54SBrian Boylston #define HPWDT_VERSION "1.4.0" 43e802e32dSdann frazier #define SECS_TO_TICKS(secs) ((secs) * 1000 / 128) 446f681c2eSdann frazier #define TICKS_TO_SECS(ticks) ((ticks) * 128 / 1000) 456f681c2eSdann frazier #define HPWDT_MAX_TIMER TICKS_TO_SECS(65535) 46923410d0Sdann frazier #define DEFAULT_MARGIN 30 47923410d0Sdann frazier 48923410d0Sdann frazier static unsigned int soft_margin = DEFAULT_MARGIN; /* in seconds */ 49923410d0Sdann frazier static unsigned int reload; /* the computed soft_margin */ 5086a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT; 51923410d0Sdann frazier static char expect_release; 52923410d0Sdann frazier static unsigned long hpwdt_is_open; 53923410d0Sdann frazier 54923410d0Sdann frazier static void __iomem *pci_mem_addr; /* the PCI-memory address */ 55*838534e5SJerry Hoemann static unsigned long __iomem *hpwdt_nmistat; 56923410d0Sdann frazier static unsigned long __iomem *hpwdt_timer_reg; 57923410d0Sdann frazier static unsigned long __iomem *hpwdt_timer_con; 58923410d0Sdann frazier 59bc17f9dcSJingoo Han static const struct pci_device_id hpwdt_devices[] = { 6036e3ff44Sdann frazier { PCI_DEVICE(PCI_VENDOR_ID_COMPAQ, 0xB203) }, /* iLO2 */ 6136e3ff44Sdann frazier { PCI_DEVICE(PCI_VENDOR_ID_HP, 0x3306) }, /* iLO3 */ 62923410d0Sdann frazier {0}, /* terminate list */ 63923410d0Sdann frazier }; 64923410d0Sdann frazier MODULE_DEVICE_TABLE(pci, hpwdt_devices); 65923410d0Sdann frazier 6686ded1f3Sdann frazier #ifdef CONFIG_HPWDT_NMI_DECODING 677f4da474SThomas Mingarelli #define PCI_BIOS32_SD_VALUE 0x5F32335F /* "_32_" */ 687f4da474SThomas Mingarelli #define CRU_BIOS_SIGNATURE_VALUE 0x55524324 697f4da474SThomas Mingarelli #define PCI_BIOS32_PARAGRAPH_LEN 16 707f4da474SThomas Mingarelli #define PCI_ROM_BASE1 0x000F0000 717f4da474SThomas Mingarelli #define ROM_SIZE 0x10000 727f4da474SThomas Mingarelli 737f4da474SThomas Mingarelli struct bios32_service_dir { 747f4da474SThomas Mingarelli u32 signature; 757f4da474SThomas Mingarelli u32 entry_point; 767f4da474SThomas Mingarelli u8 revision; 777f4da474SThomas Mingarelli u8 length; 787f4da474SThomas Mingarelli u8 checksum; 797f4da474SThomas Mingarelli u8 reserved[5]; 807f4da474SThomas Mingarelli }; 817f4da474SThomas Mingarelli 827f4da474SThomas Mingarelli /* type 212 */ 837f4da474SThomas Mingarelli struct smbios_cru64_info { 847f4da474SThomas Mingarelli u8 type; 857f4da474SThomas Mingarelli u8 byte_length; 867f4da474SThomas Mingarelli u16 handle; 877f4da474SThomas Mingarelli u32 signature; 887f4da474SThomas Mingarelli u64 physical_address; 897f4da474SThomas Mingarelli u32 double_length; 907f4da474SThomas Mingarelli u32 double_offset; 917f4da474SThomas Mingarelli }; 927f4da474SThomas Mingarelli #define SMBIOS_CRU64_INFORMATION 212 937f4da474SThomas Mingarelli 945efc7a62SThomas Mingarelli /* type 219 */ 955efc7a62SThomas Mingarelli struct smbios_proliant_info { 965efc7a62SThomas Mingarelli u8 type; 975efc7a62SThomas Mingarelli u8 byte_length; 985efc7a62SThomas Mingarelli u16 handle; 995efc7a62SThomas Mingarelli u32 power_features; 1005efc7a62SThomas Mingarelli u32 omega_features; 1015efc7a62SThomas Mingarelli u32 reserved; 1025efc7a62SThomas Mingarelli u32 misc_features; 1035efc7a62SThomas Mingarelli }; 1045efc7a62SThomas Mingarelli #define SMBIOS_ICRU_INFORMATION 219 1055efc7a62SThomas Mingarelli 1065efc7a62SThomas Mingarelli 1077f4da474SThomas Mingarelli struct cmn_registers { 1087f4da474SThomas Mingarelli union { 1097f4da474SThomas Mingarelli struct { 1107f4da474SThomas Mingarelli u8 ral; 1117f4da474SThomas Mingarelli u8 rah; 1127f4da474SThomas Mingarelli u16 rea2; 1137f4da474SThomas Mingarelli }; 1147f4da474SThomas Mingarelli u32 reax; 1157f4da474SThomas Mingarelli } u1; 1167f4da474SThomas Mingarelli union { 1177f4da474SThomas Mingarelli struct { 1187f4da474SThomas Mingarelli u8 rbl; 1197f4da474SThomas Mingarelli u8 rbh; 1207f4da474SThomas Mingarelli u8 reb2l; 1217f4da474SThomas Mingarelli u8 reb2h; 1227f4da474SThomas Mingarelli }; 1237f4da474SThomas Mingarelli u32 rebx; 1247f4da474SThomas Mingarelli } u2; 1257f4da474SThomas Mingarelli union { 1267f4da474SThomas Mingarelli struct { 1277f4da474SThomas Mingarelli u8 rcl; 1287f4da474SThomas Mingarelli u8 rch; 1297f4da474SThomas Mingarelli u16 rec2; 1307f4da474SThomas Mingarelli }; 1317f4da474SThomas Mingarelli u32 recx; 1327f4da474SThomas Mingarelli } u3; 1337f4da474SThomas Mingarelli union { 1347f4da474SThomas Mingarelli struct { 1357f4da474SThomas Mingarelli u8 rdl; 1367f4da474SThomas Mingarelli u8 rdh; 1377f4da474SThomas Mingarelli u16 red2; 1387f4da474SThomas Mingarelli }; 1397f4da474SThomas Mingarelli u32 redx; 1407f4da474SThomas Mingarelli } u4; 1417f4da474SThomas Mingarelli 1427f4da474SThomas Mingarelli u32 resi; 1437f4da474SThomas Mingarelli u32 redi; 1447f4da474SThomas Mingarelli u16 rds; 1457f4da474SThomas Mingarelli u16 res; 1467f4da474SThomas Mingarelli u32 reflags; 1477f4da474SThomas Mingarelli } __attribute__((packed)); 1487f4da474SThomas Mingarelli 14934572b29Sdann frazier static unsigned int hpwdt_nmi_decoding; 150a089361cSMingarelli, Thomas static unsigned int allow_kdump = 1; 1515efc7a62SThomas Mingarelli static unsigned int is_icru; 152cce78da7SMingarelli, Thomas static unsigned int is_uefi; 1537f4da474SThomas Mingarelli static DEFINE_SPINLOCK(rom_lock); 1547f4da474SThomas Mingarelli static void *cru_rom_addr; 1557f4da474SThomas Mingarelli static struct cmn_registers cmn_regs; 1567f4da474SThomas Mingarelli 157143a2e54SWim Van Sebroeck extern asmlinkage void asminline_call(struct cmn_registers *pi86Regs, 158143a2e54SWim Van Sebroeck unsigned long *pRomEntry); 1591f6ef234SLinus Torvalds 1606b7f3d53Sdann frazier #ifdef CONFIG_X86_32 1617f4da474SThomas Mingarelli /* --32 Bit Bios------------------------------------------------------------ */ 1627f4da474SThomas Mingarelli 1637f4da474SThomas Mingarelli #define HPWDT_ARCH 32 1647f4da474SThomas Mingarelli 1651f6ef234SLinus Torvalds asm(".text \n\t" 166a6b08887SAndi Kleen ".align 4 \n\t" 167a6b08887SAndi Kleen ".globl asminline_call \n" 1681f6ef234SLinus Torvalds "asminline_call: \n\t" 1691f6ef234SLinus Torvalds "pushl %ebp \n\t" 1707f4da474SThomas Mingarelli "movl %esp, %ebp \n\t" 1717f4da474SThomas Mingarelli "pusha \n\t" 1727f4da474SThomas Mingarelli "pushf \n\t" 1737f4da474SThomas Mingarelli "push %es \n\t" 1747f4da474SThomas Mingarelli "push %ds \n\t" 1757f4da474SThomas Mingarelli "pop %es \n\t" 1767f4da474SThomas Mingarelli "movl 8(%ebp),%eax \n\t" 1777f4da474SThomas Mingarelli "movl 4(%eax),%ebx \n\t" 1787f4da474SThomas Mingarelli "movl 8(%eax),%ecx \n\t" 1797f4da474SThomas Mingarelli "movl 12(%eax),%edx \n\t" 1807f4da474SThomas Mingarelli "movl 16(%eax),%esi \n\t" 1817f4da474SThomas Mingarelli "movl 20(%eax),%edi \n\t" 1827f4da474SThomas Mingarelli "movl (%eax),%eax \n\t" 1837f4da474SThomas Mingarelli "push %cs \n\t" 1847f4da474SThomas Mingarelli "call *12(%ebp) \n\t" 1857f4da474SThomas Mingarelli "pushf \n\t" 1867f4da474SThomas Mingarelli "pushl %eax \n\t" 1877f4da474SThomas Mingarelli "movl 8(%ebp),%eax \n\t" 1887f4da474SThomas Mingarelli "movl %ebx,4(%eax) \n\t" 1897f4da474SThomas Mingarelli "movl %ecx,8(%eax) \n\t" 1907f4da474SThomas Mingarelli "movl %edx,12(%eax) \n\t" 1917f4da474SThomas Mingarelli "movl %esi,16(%eax) \n\t" 1927f4da474SThomas Mingarelli "movl %edi,20(%eax) \n\t" 1937f4da474SThomas Mingarelli "movw %ds,24(%eax) \n\t" 1947f4da474SThomas Mingarelli "movw %es,26(%eax) \n\t" 1957f4da474SThomas Mingarelli "popl %ebx \n\t" 1967f4da474SThomas Mingarelli "movl %ebx,(%eax) \n\t" 1977f4da474SThomas Mingarelli "popl %ebx \n\t" 1987f4da474SThomas Mingarelli "movl %ebx,28(%eax) \n\t" 1997f4da474SThomas Mingarelli "pop %es \n\t" 2007f4da474SThomas Mingarelli "popf \n\t" 2017f4da474SThomas Mingarelli "popa \n\t" 2021f6ef234SLinus Torvalds "leave \n\t" 2031f6ef234SLinus Torvalds "ret \n\t" 2041f6ef234SLinus Torvalds ".previous"); 2051f6ef234SLinus Torvalds 2067f4da474SThomas Mingarelli 2077f4da474SThomas Mingarelli /* 2087f4da474SThomas Mingarelli * cru_detect 2097f4da474SThomas Mingarelli * 2107f4da474SThomas Mingarelli * Routine Description: 2117f4da474SThomas Mingarelli * This function uses the 32-bit BIOS Service Directory record to 2127f4da474SThomas Mingarelli * search for a $CRU record. 2137f4da474SThomas Mingarelli * 2147f4da474SThomas Mingarelli * Return Value: 2157f4da474SThomas Mingarelli * 0 : SUCCESS 2167f4da474SThomas Mingarelli * <0 : FAILURE 2177f4da474SThomas Mingarelli */ 2182d991a16SBill Pemberton static int cru_detect(unsigned long map_entry, 2197f4da474SThomas Mingarelli unsigned long map_offset) 2207f4da474SThomas Mingarelli { 2217f4da474SThomas Mingarelli void *bios32_map; 2227f4da474SThomas Mingarelli unsigned long *bios32_entrypoint; 2237f4da474SThomas Mingarelli unsigned long cru_physical_address; 2247f4da474SThomas Mingarelli unsigned long cru_length; 2257f4da474SThomas Mingarelli unsigned long physical_bios_base = 0; 2267f4da474SThomas Mingarelli unsigned long physical_bios_offset = 0; 2277f4da474SThomas Mingarelli int retval = -ENODEV; 2287f4da474SThomas Mingarelli 2297f4da474SThomas Mingarelli bios32_map = ioremap(map_entry, (2 * PAGE_SIZE)); 2307f4da474SThomas Mingarelli 2317f4da474SThomas Mingarelli if (bios32_map == NULL) 2327f4da474SThomas Mingarelli return -ENODEV; 2337f4da474SThomas Mingarelli 2347f4da474SThomas Mingarelli bios32_entrypoint = bios32_map + map_offset; 2357f4da474SThomas Mingarelli 2367f4da474SThomas Mingarelli cmn_regs.u1.reax = CRU_BIOS_SIGNATURE_VALUE; 2377f4da474SThomas Mingarelli 23897d2a10dSMaxim Uvarov set_memory_x((unsigned long)bios32_map, 2); 2397f4da474SThomas Mingarelli asminline_call(&cmn_regs, bios32_entrypoint); 2407f4da474SThomas Mingarelli 2417f4da474SThomas Mingarelli if (cmn_regs.u1.ral != 0) { 24227c766aaSJoe Perches pr_warn("Call succeeded but with an error: 0x%x\n", 2437f4da474SThomas Mingarelli cmn_regs.u1.ral); 2447f4da474SThomas Mingarelli } else { 2457f4da474SThomas Mingarelli physical_bios_base = cmn_regs.u2.rebx; 2467f4da474SThomas Mingarelli physical_bios_offset = cmn_regs.u4.redx; 2477f4da474SThomas Mingarelli cru_length = cmn_regs.u3.recx; 2487f4da474SThomas Mingarelli cru_physical_address = 2497f4da474SThomas Mingarelli physical_bios_base + physical_bios_offset; 2507f4da474SThomas Mingarelli 2517f4da474SThomas Mingarelli /* If the values look OK, then map it in. */ 2527f4da474SThomas Mingarelli if ((physical_bios_base + physical_bios_offset)) { 2537f4da474SThomas Mingarelli cru_rom_addr = 2547f4da474SThomas Mingarelli ioremap(cru_physical_address, cru_length); 255e67d668eSMingarelli, Thomas if (cru_rom_addr) { 25697d2a10dSMaxim Uvarov set_memory_x((unsigned long)cru_rom_addr & PAGE_MASK, 25797d2a10dSMaxim Uvarov (cru_length + PAGE_SIZE - 1) >> PAGE_SHIFT); 2587f4da474SThomas Mingarelli retval = 0; 2597f4da474SThomas Mingarelli } 260e67d668eSMingarelli, Thomas } 2617f4da474SThomas Mingarelli 26227c766aaSJoe Perches pr_debug("CRU Base Address: 0x%lx\n", physical_bios_base); 26327c766aaSJoe Perches pr_debug("CRU Offset Address: 0x%lx\n", physical_bios_offset); 26427c766aaSJoe Perches pr_debug("CRU Length: 0x%lx\n", cru_length); 26527c766aaSJoe Perches pr_debug("CRU Mapped Address: %p\n", &cru_rom_addr); 2667f4da474SThomas Mingarelli } 2677f4da474SThomas Mingarelli iounmap(bios32_map); 2687f4da474SThomas Mingarelli return retval; 2697f4da474SThomas Mingarelli } 2707f4da474SThomas Mingarelli 2717f4da474SThomas Mingarelli /* 27230ec910eSRoland Dreier * bios_checksum 27330ec910eSRoland Dreier */ 2742d991a16SBill Pemberton static int bios_checksum(const char __iomem *ptr, int len) 27530ec910eSRoland Dreier { 27630ec910eSRoland Dreier char sum = 0; 27730ec910eSRoland Dreier int i; 27830ec910eSRoland Dreier 27930ec910eSRoland Dreier /* 28030ec910eSRoland Dreier * calculate checksum of size bytes. This should add up 28130ec910eSRoland Dreier * to zero if we have a valid header. 28230ec910eSRoland Dreier */ 28330ec910eSRoland Dreier for (i = 0; i < len; i++) 28430ec910eSRoland Dreier sum += ptr[i]; 28530ec910eSRoland Dreier 28630ec910eSRoland Dreier return ((sum == 0) && (len > 0)); 28730ec910eSRoland Dreier } 28830ec910eSRoland Dreier 28930ec910eSRoland Dreier /* 2907f4da474SThomas Mingarelli * bios32_present 2917f4da474SThomas Mingarelli * 2927f4da474SThomas Mingarelli * Routine Description: 2937f4da474SThomas Mingarelli * This function finds the 32-bit BIOS Service Directory 2947f4da474SThomas Mingarelli * 2957f4da474SThomas Mingarelli * Return Value: 2967f4da474SThomas Mingarelli * 0 : SUCCESS 2977f4da474SThomas Mingarelli * <0 : FAILURE 2987f4da474SThomas Mingarelli */ 2992d991a16SBill Pemberton static int bios32_present(const char __iomem *p) 3007f4da474SThomas Mingarelli { 3017f4da474SThomas Mingarelli struct bios32_service_dir *bios_32_ptr; 3027f4da474SThomas Mingarelli int length; 3037f4da474SThomas Mingarelli unsigned long map_entry, map_offset; 3047f4da474SThomas Mingarelli 3057f4da474SThomas Mingarelli bios_32_ptr = (struct bios32_service_dir *) p; 3067f4da474SThomas Mingarelli 3077f4da474SThomas Mingarelli /* 3087f4da474SThomas Mingarelli * Search for signature by checking equal to the swizzled value 3097f4da474SThomas Mingarelli * instead of calling another routine to perform a strcmp. 3107f4da474SThomas Mingarelli */ 3117f4da474SThomas Mingarelli if (bios_32_ptr->signature == PCI_BIOS32_SD_VALUE) { 3127f4da474SThomas Mingarelli length = bios_32_ptr->length * PCI_BIOS32_PARAGRAPH_LEN; 3137f4da474SThomas Mingarelli if (bios_checksum(p, length)) { 3147f4da474SThomas Mingarelli /* 3157f4da474SThomas Mingarelli * According to the spec, we're looking for the 3167f4da474SThomas Mingarelli * first 4KB-aligned address below the entrypoint 3177f4da474SThomas Mingarelli * listed in the header. The Service Directory code 3187f4da474SThomas Mingarelli * is guaranteed to occupy no more than 2 4KB pages. 3197f4da474SThomas Mingarelli */ 3207f4da474SThomas Mingarelli map_entry = bios_32_ptr->entry_point & ~(PAGE_SIZE - 1); 3217f4da474SThomas Mingarelli map_offset = bios_32_ptr->entry_point - map_entry; 3227f4da474SThomas Mingarelli 3237f4da474SThomas Mingarelli return cru_detect(map_entry, map_offset); 3247f4da474SThomas Mingarelli } 3257f4da474SThomas Mingarelli } 3267f4da474SThomas Mingarelli return -ENODEV; 3277f4da474SThomas Mingarelli } 3287f4da474SThomas Mingarelli 3292d991a16SBill Pemberton static int detect_cru_service(void) 3307f4da474SThomas Mingarelli { 3317f4da474SThomas Mingarelli char __iomem *p, *q; 3327f4da474SThomas Mingarelli int rc = -1; 3337f4da474SThomas Mingarelli 3347f4da474SThomas Mingarelli /* 3357f4da474SThomas Mingarelli * Search from 0x0f0000 through 0x0fffff, inclusive. 3367f4da474SThomas Mingarelli */ 3377f4da474SThomas Mingarelli p = ioremap(PCI_ROM_BASE1, ROM_SIZE); 3387f4da474SThomas Mingarelli if (p == NULL) 3397f4da474SThomas Mingarelli return -ENOMEM; 3407f4da474SThomas Mingarelli 3417f4da474SThomas Mingarelli for (q = p; q < p + ROM_SIZE; q += 16) { 3427f4da474SThomas Mingarelli rc = bios32_present(q); 3437f4da474SThomas Mingarelli if (!rc) 3447f4da474SThomas Mingarelli break; 3457f4da474SThomas Mingarelli } 3467f4da474SThomas Mingarelli iounmap(p); 3477f4da474SThomas Mingarelli return rc; 3487f4da474SThomas Mingarelli } 3496b7f3d53Sdann frazier /* ------------------------------------------------------------------------- */ 3506b7f3d53Sdann frazier #endif /* CONFIG_X86_32 */ 3516b7f3d53Sdann frazier #ifdef CONFIG_X86_64 3527f4da474SThomas Mingarelli /* --64 Bit Bios------------------------------------------------------------ */ 3537f4da474SThomas Mingarelli 3547f4da474SThomas Mingarelli #define HPWDT_ARCH 64 3557f4da474SThomas Mingarelli 3561f6ef234SLinus Torvalds asm(".text \n\t" 357a6b08887SAndi Kleen ".align 4 \n\t" 3585c1d5f28SJosh Poimboeuf ".globl asminline_call \n\t" 3595c1d5f28SJosh Poimboeuf ".type asminline_call, @function \n\t" 3601f6ef234SLinus Torvalds "asminline_call: \n\t" 3615c1d5f28SJosh Poimboeuf FRAME_BEGIN 3627f4da474SThomas Mingarelli "pushq %rax \n\t" 3637f4da474SThomas Mingarelli "pushq %rbx \n\t" 3647f4da474SThomas Mingarelli "pushq %rdx \n\t" 3657f4da474SThomas Mingarelli "pushq %r12 \n\t" 3667f4da474SThomas Mingarelli "pushq %r9 \n\t" 3677f4da474SThomas Mingarelli "movq %rsi, %r12 \n\t" 3687f4da474SThomas Mingarelli "movq %rdi, %r9 \n\t" 3697f4da474SThomas Mingarelli "movl 4(%r9),%ebx \n\t" 3707f4da474SThomas Mingarelli "movl 8(%r9),%ecx \n\t" 3717f4da474SThomas Mingarelli "movl 12(%r9),%edx \n\t" 3727f4da474SThomas Mingarelli "movl 16(%r9),%esi \n\t" 3737f4da474SThomas Mingarelli "movl 20(%r9),%edi \n\t" 3747f4da474SThomas Mingarelli "movl (%r9),%eax \n\t" 3757f4da474SThomas Mingarelli "call *%r12 \n\t" 3767f4da474SThomas Mingarelli "pushfq \n\t" 3777f4da474SThomas Mingarelli "popq %r12 \n\t" 3787f4da474SThomas Mingarelli "movl %eax, (%r9) \n\t" 3797f4da474SThomas Mingarelli "movl %ebx, 4(%r9) \n\t" 3807f4da474SThomas Mingarelli "movl %ecx, 8(%r9) \n\t" 3817f4da474SThomas Mingarelli "movl %edx, 12(%r9) \n\t" 3827f4da474SThomas Mingarelli "movl %esi, 16(%r9) \n\t" 3837f4da474SThomas Mingarelli "movl %edi, 20(%r9) \n\t" 3847f4da474SThomas Mingarelli "movq %r12, %rax \n\t" 3857f4da474SThomas Mingarelli "movl %eax, 28(%r9) \n\t" 3867f4da474SThomas Mingarelli "popq %r9 \n\t" 3877f4da474SThomas Mingarelli "popq %r12 \n\t" 3887f4da474SThomas Mingarelli "popq %rdx \n\t" 3897f4da474SThomas Mingarelli "popq %rbx \n\t" 3907f4da474SThomas Mingarelli "popq %rax \n\t" 3915c1d5f28SJosh Poimboeuf FRAME_END 3921f6ef234SLinus Torvalds "ret \n\t" 3931f6ef234SLinus Torvalds ".previous"); 3947f4da474SThomas Mingarelli 3957f4da474SThomas Mingarelli /* 3967f4da474SThomas Mingarelli * dmi_find_cru 3977f4da474SThomas Mingarelli * 3987f4da474SThomas Mingarelli * Routine Description: 39930ec910eSRoland Dreier * This function checks whether or not a SMBIOS/DMI record is 4007f4da474SThomas Mingarelli * the 64bit CRU info or not 4017f4da474SThomas Mingarelli */ 4022d991a16SBill Pemberton static void dmi_find_cru(const struct dmi_header *dm, void *dummy) 4037f4da474SThomas Mingarelli { 4047f4da474SThomas Mingarelli struct smbios_cru64_info *smbios_cru64_ptr; 4057f4da474SThomas Mingarelli unsigned long cru_physical_address; 4067f4da474SThomas Mingarelli 4077f4da474SThomas Mingarelli if (dm->type == SMBIOS_CRU64_INFORMATION) { 4087f4da474SThomas Mingarelli smbios_cru64_ptr = (struct smbios_cru64_info *) dm; 4097f4da474SThomas Mingarelli if (smbios_cru64_ptr->signature == CRU_BIOS_SIGNATURE_VALUE) { 4107f4da474SThomas Mingarelli cru_physical_address = 4117f4da474SThomas Mingarelli smbios_cru64_ptr->physical_address + 4127f4da474SThomas Mingarelli smbios_cru64_ptr->double_offset; 4137f4da474SThomas Mingarelli cru_rom_addr = ioremap(cru_physical_address, 4147f4da474SThomas Mingarelli smbios_cru64_ptr->double_length); 41506026413SBernhard Walle set_memory_x((unsigned long)cru_rom_addr & PAGE_MASK, 41606026413SBernhard Walle smbios_cru64_ptr->double_length >> PAGE_SHIFT); 4177f4da474SThomas Mingarelli } 4187f4da474SThomas Mingarelli } 4197f4da474SThomas Mingarelli } 4207f4da474SThomas Mingarelli 4212d991a16SBill Pemberton static int detect_cru_service(void) 4227f4da474SThomas Mingarelli { 4237f4da474SThomas Mingarelli cru_rom_addr = NULL; 4247f4da474SThomas Mingarelli 425e7a19c56SJean Delvare dmi_walk(dmi_find_cru, NULL); 4267f4da474SThomas Mingarelli 4277f4da474SThomas Mingarelli /* if cru_rom_addr has been set then we found a CRU service */ 4287f4da474SThomas Mingarelli return ((cru_rom_addr != NULL) ? 0 : -ENODEV); 4297f4da474SThomas Mingarelli } 4307f4da474SThomas Mingarelli /* ------------------------------------------------------------------------- */ 4316b7f3d53Sdann frazier #endif /* CONFIG_X86_64 */ 43286ded1f3Sdann frazier #endif /* CONFIG_HPWDT_NMI_DECODING */ 4337f4da474SThomas Mingarelli 4347f4da474SThomas Mingarelli /* 4357f4da474SThomas Mingarelli * Watchdog operations 4367f4da474SThomas Mingarelli */ 4377f4da474SThomas Mingarelli static void hpwdt_start(void) 4387f4da474SThomas Mingarelli { 439e802e32dSdann frazier reload = SECS_TO_TICKS(soft_margin); 4407f4da474SThomas Mingarelli iowrite16(reload, hpwdt_timer_reg); 441d08c9a33SMingarelli, Thomas iowrite8(0x85, hpwdt_timer_con); 4427f4da474SThomas Mingarelli } 4437f4da474SThomas Mingarelli 4447f4da474SThomas Mingarelli static void hpwdt_stop(void) 4457f4da474SThomas Mingarelli { 4467f4da474SThomas Mingarelli unsigned long data; 4477f4da474SThomas Mingarelli 448d08c9a33SMingarelli, Thomas data = ioread8(hpwdt_timer_con); 4497f4da474SThomas Mingarelli data &= 0xFE; 450d08c9a33SMingarelli, Thomas iowrite8(data, hpwdt_timer_con); 4517f4da474SThomas Mingarelli } 4527f4da474SThomas Mingarelli 4537f4da474SThomas Mingarelli static void hpwdt_ping(void) 4547f4da474SThomas Mingarelli { 4557f4da474SThomas Mingarelli iowrite16(reload, hpwdt_timer_reg); 4567f4da474SThomas Mingarelli } 4577f4da474SThomas Mingarelli 4587f4da474SThomas Mingarelli static int hpwdt_change_timer(int new_margin) 4597f4da474SThomas Mingarelli { 4606f681c2eSdann frazier if (new_margin < 1 || new_margin > HPWDT_MAX_TIMER) { 46127c766aaSJoe Perches pr_warn("New value passed in is invalid: %d seconds\n", 4627f4da474SThomas Mingarelli new_margin); 4637f4da474SThomas Mingarelli return -EINVAL; 4647f4da474SThomas Mingarelli } 4657f4da474SThomas Mingarelli 4667f4da474SThomas Mingarelli soft_margin = new_margin; 46727c766aaSJoe Perches pr_debug("New timer passed in is %d seconds\n", new_margin); 468e802e32dSdann frazier reload = SECS_TO_TICKS(soft_margin); 4697f4da474SThomas Mingarelli 4707f4da474SThomas Mingarelli return 0; 4717f4da474SThomas Mingarelli } 4727f4da474SThomas Mingarelli 473aae67f36Sdann frazier static int hpwdt_time_left(void) 474aae67f36Sdann frazier { 475aae67f36Sdann frazier return TICKS_TO_SECS(ioread16(hpwdt_timer_reg)); 476aae67f36Sdann frazier } 477aae67f36Sdann frazier 478*838534e5SJerry Hoemann static int hpwdt_my_nmi(void) 479*838534e5SJerry Hoemann { 480*838534e5SJerry Hoemann return ioread8(hpwdt_nmistat) & 0x6; 481*838534e5SJerry Hoemann } 482*838534e5SJerry Hoemann 48386ded1f3Sdann frazier #ifdef CONFIG_HPWDT_NMI_DECODING 4847f4da474SThomas Mingarelli /* 485ab4ba3cdSThomas Mingarelli * NMI Handler 486ab4ba3cdSThomas Mingarelli */ 4879c48f1c6SDon Zickus static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs) 488ab4ba3cdSThomas Mingarelli { 489ab4ba3cdSThomas Mingarelli unsigned long rom_pl; 490ab4ba3cdSThomas Mingarelli static int die_nmi_called; 491ab4ba3cdSThomas Mingarelli 49234572b29Sdann frazier if (!hpwdt_nmi_decoding) 493abc514c5SHidehiro Kawai return NMI_DONE; 494243066baSdann frazier 495*838534e5SJerry Hoemann if ((ulReason == NMI_UNKNOWN) && !hpwdt_my_nmi()) 496*838534e5SJerry Hoemann return NMI_DONE; 497*838534e5SJerry Hoemann 498ab4ba3cdSThomas Mingarelli spin_lock_irqsave(&rom_lock, rom_pl); 499cce78da7SMingarelli, Thomas if (!die_nmi_called && !is_icru && !is_uefi) 500ab4ba3cdSThomas Mingarelli asminline_call(&cmn_regs, cru_rom_addr); 501ab4ba3cdSThomas Mingarelli die_nmi_called = 1; 502ab4ba3cdSThomas Mingarelli spin_unlock_irqrestore(&rom_lock, rom_pl); 5035efc7a62SThomas Mingarelli 504ab4ba3cdSThomas Mingarelli if (allow_kdump) 505ab4ba3cdSThomas Mingarelli hpwdt_stop(); 506dbc018ecSNaga Chumbalkar 507cce78da7SMingarelli, Thomas if (!is_icru && !is_uefi) { 508dbc018ecSNaga Chumbalkar if (cmn_regs.u1.ral == 0) { 509abc514c5SHidehiro Kawai nmi_panic(regs, "An NMI occurred, but unable to determine source.\n"); 510abc514c5SHidehiro Kawai return NMI_HANDLED; 511dbc018ecSNaga Chumbalkar } 512dbc018ecSNaga Chumbalkar } 513abc514c5SHidehiro Kawai nmi_panic(regs, "An NMI occurred. Depending on your system the reason " 5147bb5be94SThomas Mingarelli "for the NMI is logged in any one of the following " 5157bb5be94SThomas Mingarelli "resources:\n" 5167bb5be94SThomas Mingarelli "1. Integrated Management Log (IML)\n" 5177bb5be94SThomas Mingarelli "2. OA Syslog\n" 5187bb5be94SThomas Mingarelli "3. OA Forward Progress Log\n" 5197bb5be94SThomas Mingarelli "4. iLO Event Log"); 5205efc7a62SThomas Mingarelli 521abc514c5SHidehiro Kawai return NMI_HANDLED; 522ab4ba3cdSThomas Mingarelli } 52386ded1f3Sdann frazier #endif /* CONFIG_HPWDT_NMI_DECODING */ 524ab4ba3cdSThomas Mingarelli 525ab4ba3cdSThomas Mingarelli /* 5267f4da474SThomas Mingarelli * /dev/watchdog handling 5277f4da474SThomas Mingarelli */ 5287f4da474SThomas Mingarelli static int hpwdt_open(struct inode *inode, struct file *file) 5297f4da474SThomas Mingarelli { 5307f4da474SThomas Mingarelli /* /dev/watchdog can only be opened once */ 5317f4da474SThomas Mingarelli if (test_and_set_bit(0, &hpwdt_is_open)) 5327f4da474SThomas Mingarelli return -EBUSY; 5337f4da474SThomas Mingarelli 5347f4da474SThomas Mingarelli /* Start the watchdog */ 5357f4da474SThomas Mingarelli hpwdt_start(); 5367f4da474SThomas Mingarelli hpwdt_ping(); 5377f4da474SThomas Mingarelli 5387f4da474SThomas Mingarelli return nonseekable_open(inode, file); 5397f4da474SThomas Mingarelli } 5407f4da474SThomas Mingarelli 5417f4da474SThomas Mingarelli static int hpwdt_release(struct inode *inode, struct file *file) 5427f4da474SThomas Mingarelli { 5437f4da474SThomas Mingarelli /* Stop the watchdog */ 5447f4da474SThomas Mingarelli if (expect_release == 42) { 5457f4da474SThomas Mingarelli hpwdt_stop(); 5467f4da474SThomas Mingarelli } else { 54727c766aaSJoe Perches pr_crit("Unexpected close, not stopping watchdog!\n"); 5487f4da474SThomas Mingarelli hpwdt_ping(); 5497f4da474SThomas Mingarelli } 5507f4da474SThomas Mingarelli 5517f4da474SThomas Mingarelli expect_release = 0; 5527f4da474SThomas Mingarelli 5537f4da474SThomas Mingarelli /* /dev/watchdog is being closed, make sure it can be re-opened */ 5547f4da474SThomas Mingarelli clear_bit(0, &hpwdt_is_open); 5557f4da474SThomas Mingarelli 5567f4da474SThomas Mingarelli return 0; 5577f4da474SThomas Mingarelli } 5587f4da474SThomas Mingarelli 5597f4da474SThomas Mingarelli static ssize_t hpwdt_write(struct file *file, const char __user *data, 5607f4da474SThomas Mingarelli size_t len, loff_t *ppos) 5617f4da474SThomas Mingarelli { 5627f4da474SThomas Mingarelli /* See if we got the magic character 'V' and reload the timer */ 5637f4da474SThomas Mingarelli if (len) { 5647f4da474SThomas Mingarelli if (!nowayout) { 5657f4da474SThomas Mingarelli size_t i; 5667f4da474SThomas Mingarelli 5677f4da474SThomas Mingarelli /* note: just in case someone wrote the magic character 5687f4da474SThomas Mingarelli * five months ago... */ 5697f4da474SThomas Mingarelli expect_release = 0; 5707f4da474SThomas Mingarelli 5717f4da474SThomas Mingarelli /* scan to see whether or not we got the magic char. */ 5727f4da474SThomas Mingarelli for (i = 0; i != len; i++) { 5737f4da474SThomas Mingarelli char c; 5747f4da474SThomas Mingarelli if (get_user(c, data + i)) 5757f4da474SThomas Mingarelli return -EFAULT; 5767f4da474SThomas Mingarelli if (c == 'V') 5777f4da474SThomas Mingarelli expect_release = 42; 5787f4da474SThomas Mingarelli } 5797f4da474SThomas Mingarelli } 5807f4da474SThomas Mingarelli 5817f4da474SThomas Mingarelli /* someone wrote to us, we should reload the timer */ 5827f4da474SThomas Mingarelli hpwdt_ping(); 5837f4da474SThomas Mingarelli } 5847f4da474SThomas Mingarelli 5857f4da474SThomas Mingarelli return len; 5867f4da474SThomas Mingarelli } 5877f4da474SThomas Mingarelli 58842747d71SWim Van Sebroeck static const struct watchdog_info ident = { 5897f4da474SThomas Mingarelli .options = WDIOF_SETTIMEOUT | 5907f4da474SThomas Mingarelli WDIOF_KEEPALIVEPING | 5917f4da474SThomas Mingarelli WDIOF_MAGICCLOSE, 592ca22e79fSMingarelli, Thomas .identity = "HPE iLO2+ HW Watchdog Timer", 5937f4da474SThomas Mingarelli }; 5947f4da474SThomas Mingarelli 5957f4da474SThomas Mingarelli static long hpwdt_ioctl(struct file *file, unsigned int cmd, 5967f4da474SThomas Mingarelli unsigned long arg) 5977f4da474SThomas Mingarelli { 5987f4da474SThomas Mingarelli void __user *argp = (void __user *)arg; 5997f4da474SThomas Mingarelli int __user *p = argp; 60046c80b20SJean Delvare int new_margin, options; 6017f4da474SThomas Mingarelli int ret = -ENOTTY; 6027f4da474SThomas Mingarelli 6037f4da474SThomas Mingarelli switch (cmd) { 6047f4da474SThomas Mingarelli case WDIOC_GETSUPPORT: 6057f4da474SThomas Mingarelli ret = 0; 6067f4da474SThomas Mingarelli if (copy_to_user(argp, &ident, sizeof(ident))) 6077f4da474SThomas Mingarelli ret = -EFAULT; 6087f4da474SThomas Mingarelli break; 6097f4da474SThomas Mingarelli 6107f4da474SThomas Mingarelli case WDIOC_GETSTATUS: 6117f4da474SThomas Mingarelli case WDIOC_GETBOOTSTATUS: 6127f4da474SThomas Mingarelli ret = put_user(0, p); 6137f4da474SThomas Mingarelli break; 6147f4da474SThomas Mingarelli 6157f4da474SThomas Mingarelli case WDIOC_KEEPALIVE: 6167f4da474SThomas Mingarelli hpwdt_ping(); 6177f4da474SThomas Mingarelli ret = 0; 6187f4da474SThomas Mingarelli break; 6197f4da474SThomas Mingarelli 62046c80b20SJean Delvare case WDIOC_SETOPTIONS: 62146c80b20SJean Delvare ret = get_user(options, p); 62246c80b20SJean Delvare if (ret) 62346c80b20SJean Delvare break; 62446c80b20SJean Delvare 62546c80b20SJean Delvare if (options & WDIOS_DISABLECARD) 62646c80b20SJean Delvare hpwdt_stop(); 62746c80b20SJean Delvare 62846c80b20SJean Delvare if (options & WDIOS_ENABLECARD) { 62946c80b20SJean Delvare hpwdt_start(); 63046c80b20SJean Delvare hpwdt_ping(); 63146c80b20SJean Delvare } 63246c80b20SJean Delvare break; 63346c80b20SJean Delvare 6347f4da474SThomas Mingarelli case WDIOC_SETTIMEOUT: 6357f4da474SThomas Mingarelli ret = get_user(new_margin, p); 6367f4da474SThomas Mingarelli if (ret) 6377f4da474SThomas Mingarelli break; 6387f4da474SThomas Mingarelli 6397f4da474SThomas Mingarelli ret = hpwdt_change_timer(new_margin); 6407f4da474SThomas Mingarelli if (ret) 6417f4da474SThomas Mingarelli break; 6427f4da474SThomas Mingarelli 6437f4da474SThomas Mingarelli hpwdt_ping(); 6447f4da474SThomas Mingarelli /* Fall */ 6457f4da474SThomas Mingarelli case WDIOC_GETTIMEOUT: 6467f4da474SThomas Mingarelli ret = put_user(soft_margin, p); 6477f4da474SThomas Mingarelli break; 648aae67f36Sdann frazier 649aae67f36Sdann frazier case WDIOC_GETTIMELEFT: 650aae67f36Sdann frazier ret = put_user(hpwdt_time_left(), p); 651aae67f36Sdann frazier break; 6527f4da474SThomas Mingarelli } 6537f4da474SThomas Mingarelli return ret; 6547f4da474SThomas Mingarelli } 6557f4da474SThomas Mingarelli 6567f4da474SThomas Mingarelli /* 6577f4da474SThomas Mingarelli * Kernel interfaces 6587f4da474SThomas Mingarelli */ 659d5c26a59SWim Van Sebroeck static const struct file_operations hpwdt_fops = { 6607f4da474SThomas Mingarelli .owner = THIS_MODULE, 6617f4da474SThomas Mingarelli .llseek = no_llseek, 6627f4da474SThomas Mingarelli .write = hpwdt_write, 6637f4da474SThomas Mingarelli .unlocked_ioctl = hpwdt_ioctl, 6647f4da474SThomas Mingarelli .open = hpwdt_open, 6657f4da474SThomas Mingarelli .release = hpwdt_release, 6667f4da474SThomas Mingarelli }; 6677f4da474SThomas Mingarelli 6687f4da474SThomas Mingarelli static struct miscdevice hpwdt_miscdev = { 6697f4da474SThomas Mingarelli .minor = WATCHDOG_MINOR, 6707f4da474SThomas Mingarelli .name = "watchdog", 6717f4da474SThomas Mingarelli .fops = &hpwdt_fops, 6727f4da474SThomas Mingarelli }; 6737f4da474SThomas Mingarelli 6747f4da474SThomas Mingarelli /* 6757f4da474SThomas Mingarelli * Init & Exit 6767f4da474SThomas Mingarelli */ 6777f4da474SThomas Mingarelli 67886ded1f3Sdann frazier #ifdef CONFIG_HPWDT_NMI_DECODING 6794a7863ccSDon Zickus #ifdef CONFIG_X86_LOCAL_APIC 6802d991a16SBill Pemberton static void hpwdt_check_nmi_decoding(struct pci_dev *dev) 68147bece87SThomas Mingarelli { 68247bece87SThomas Mingarelli /* 68347bece87SThomas Mingarelli * If nmi_watchdog is turned off then we can turn on 68434572b29Sdann frazier * our nmi decoding capability. 68547bece87SThomas Mingarelli */ 68634572b29Sdann frazier hpwdt_nmi_decoding = 1; 68747bece87SThomas Mingarelli } 68847bece87SThomas Mingarelli #else 6892d991a16SBill Pemberton static void hpwdt_check_nmi_decoding(struct pci_dev *dev) 69047bece87SThomas Mingarelli { 69134572b29Sdann frazier dev_warn(&dev->dev, "NMI decoding is disabled. " 69247bece87SThomas Mingarelli "Your kernel does not support a NMI Watchdog.\n"); 69347bece87SThomas Mingarelli } 6944a7863ccSDon Zickus #endif /* CONFIG_X86_LOCAL_APIC */ 6952ec7ed67Sdann frazier 6965efc7a62SThomas Mingarelli /* 6975efc7a62SThomas Mingarelli * dmi_find_icru 6985efc7a62SThomas Mingarelli * 6995efc7a62SThomas Mingarelli * Routine Description: 7005efc7a62SThomas Mingarelli * This function checks whether or not we are on an iCRU-based server. 7015efc7a62SThomas Mingarelli * This check is independent of architecture and needs to be made for 7025efc7a62SThomas Mingarelli * any ProLiant system. 7035efc7a62SThomas Mingarelli */ 7042d991a16SBill Pemberton static void dmi_find_icru(const struct dmi_header *dm, void *dummy) 7055efc7a62SThomas Mingarelli { 7065efc7a62SThomas Mingarelli struct smbios_proliant_info *smbios_proliant_ptr; 7075efc7a62SThomas Mingarelli 7085efc7a62SThomas Mingarelli if (dm->type == SMBIOS_ICRU_INFORMATION) { 7095efc7a62SThomas Mingarelli smbios_proliant_ptr = (struct smbios_proliant_info *) dm; 7105efc7a62SThomas Mingarelli if (smbios_proliant_ptr->misc_features & 0x01) 7115efc7a62SThomas Mingarelli is_icru = 1; 712c42cbe41SJerry Hoemann if (smbios_proliant_ptr->misc_features & 0x1400) 713cce78da7SMingarelli, Thomas is_uefi = 1; 7145efc7a62SThomas Mingarelli } 7155efc7a62SThomas Mingarelli } 7165efc7a62SThomas Mingarelli 7172d991a16SBill Pemberton static int hpwdt_init_nmi_decoding(struct pci_dev *dev) 7182ec7ed67Sdann frazier { 7192ec7ed67Sdann frazier int retval; 7202ec7ed67Sdann frazier 7212ec7ed67Sdann frazier /* 7225efc7a62SThomas Mingarelli * On typical CRU-based systems we need to map that service in 7235efc7a62SThomas Mingarelli * the BIOS. For 32 bit Operating Systems we need to go through 7245efc7a62SThomas Mingarelli * the 32 Bit BIOS Service Directory. For 64 bit Operating 7255efc7a62SThomas Mingarelli * Systems we get that service through SMBIOS. 7265efc7a62SThomas Mingarelli * 7275efc7a62SThomas Mingarelli * On systems that support the new iCRU service all we need to 7285efc7a62SThomas Mingarelli * do is call dmi_walk to get the supported flag value and skip 7295efc7a62SThomas Mingarelli * the old cru detect code. 7305efc7a62SThomas Mingarelli */ 7315efc7a62SThomas Mingarelli dmi_walk(dmi_find_icru, NULL); 732cce78da7SMingarelli, Thomas if (!is_icru && !is_uefi) { 7335efc7a62SThomas Mingarelli 7345efc7a62SThomas Mingarelli /* 7352ec7ed67Sdann frazier * We need to map the ROM to get the CRU service. 7362ec7ed67Sdann frazier * For 32 bit Operating Systems we need to go through the 32 Bit 7372ec7ed67Sdann frazier * BIOS Service Directory 7382ec7ed67Sdann frazier * For 64 bit Operating Systems we get that service through SMBIOS. 7392ec7ed67Sdann frazier */ 7402ec7ed67Sdann frazier retval = detect_cru_service(); 7412ec7ed67Sdann frazier if (retval < 0) { 7422ec7ed67Sdann frazier dev_warn(&dev->dev, 7432ec7ed67Sdann frazier "Unable to detect the %d Bit CRU Service.\n", 7442ec7ed67Sdann frazier HPWDT_ARCH); 7452ec7ed67Sdann frazier return retval; 7462ec7ed67Sdann frazier } 7472ec7ed67Sdann frazier 7482ec7ed67Sdann frazier /* 7492ec7ed67Sdann frazier * We know this is the only CRU call we need to make so lets keep as 7502ec7ed67Sdann frazier * few instructions as possible once the NMI comes in. 7512ec7ed67Sdann frazier */ 7522ec7ed67Sdann frazier cmn_regs.u1.rah = 0x0D; 7532ec7ed67Sdann frazier cmn_regs.u1.ral = 0x02; 7545efc7a62SThomas Mingarelli } 7552ec7ed67Sdann frazier 7562ec7ed67Sdann frazier /* 75709ee1014SDon Zickus * Only one function can register for NMI_UNKNOWN 7582ec7ed67Sdann frazier */ 75909ee1014SDon Zickus retval = register_nmi_handler(NMI_UNKNOWN, hpwdt_pretimeout, 0, "hpwdt"); 760553222f3SDon Zickus if (retval) 761553222f3SDon Zickus goto error; 762553222f3SDon Zickus retval = register_nmi_handler(NMI_SERR, hpwdt_pretimeout, 0, "hpwdt"); 763553222f3SDon Zickus if (retval) 764553222f3SDon Zickus goto error1; 765553222f3SDon Zickus retval = register_nmi_handler(NMI_IO_CHECK, hpwdt_pretimeout, 0, "hpwdt"); 766553222f3SDon Zickus if (retval) 767553222f3SDon Zickus goto error2; 7682ec7ed67Sdann frazier 7692ec7ed67Sdann frazier dev_info(&dev->dev, 770ca22e79fSMingarelli, Thomas "HPE Watchdog Timer Driver: NMI decoding initialized" 771b91b5be5SMasanari Iida ", allow kernel dump: %s (default = 1/ON)\n", 77209ee1014SDon Zickus (allow_kdump == 0) ? "OFF" : "ON"); 7732ec7ed67Sdann frazier return 0; 774553222f3SDon Zickus 775553222f3SDon Zickus error2: 776553222f3SDon Zickus unregister_nmi_handler(NMI_SERR, "hpwdt"); 777553222f3SDon Zickus error1: 778553222f3SDon Zickus unregister_nmi_handler(NMI_UNKNOWN, "hpwdt"); 779553222f3SDon Zickus error: 7802ec7ed67Sdann frazier dev_warn(&dev->dev, 7812ec7ed67Sdann frazier "Unable to register a die notifier (err=%d).\n", 7822ec7ed67Sdann frazier retval); 7832ec7ed67Sdann frazier if (cru_rom_addr) 7842ec7ed67Sdann frazier iounmap(cru_rom_addr); 785553222f3SDon Zickus return retval; 7862ec7ed67Sdann frazier } 7872ec7ed67Sdann frazier 788b77b7088SAxel Lin static void hpwdt_exit_nmi_decoding(void) 7892ec7ed67Sdann frazier { 7909c48f1c6SDon Zickus unregister_nmi_handler(NMI_UNKNOWN, "hpwdt"); 791a089361cSMingarelli, Thomas unregister_nmi_handler(NMI_SERR, "hpwdt"); 792a089361cSMingarelli, Thomas unregister_nmi_handler(NMI_IO_CHECK, "hpwdt"); 7932ec7ed67Sdann frazier if (cru_rom_addr) 7942ec7ed67Sdann frazier iounmap(cru_rom_addr); 7952ec7ed67Sdann frazier } 79686ded1f3Sdann frazier #else /* !CONFIG_HPWDT_NMI_DECODING */ 7972d991a16SBill Pemberton static void hpwdt_check_nmi_decoding(struct pci_dev *dev) 79886ded1f3Sdann frazier { 79986ded1f3Sdann frazier } 80086ded1f3Sdann frazier 8012d991a16SBill Pemberton static int hpwdt_init_nmi_decoding(struct pci_dev *dev) 80286ded1f3Sdann frazier { 80386ded1f3Sdann frazier return 0; 80486ded1f3Sdann frazier } 80586ded1f3Sdann frazier 806b77b7088SAxel Lin static void hpwdt_exit_nmi_decoding(void) 80786ded1f3Sdann frazier { 80886ded1f3Sdann frazier } 80986ded1f3Sdann frazier #endif /* CONFIG_HPWDT_NMI_DECODING */ 81047bece87SThomas Mingarelli 8112d991a16SBill Pemberton static int hpwdt_init_one(struct pci_dev *dev, 8127f4da474SThomas Mingarelli const struct pci_device_id *ent) 8137f4da474SThomas Mingarelli { 8147f4da474SThomas Mingarelli int retval; 8157f4da474SThomas Mingarelli 8167f4da474SThomas Mingarelli /* 81734572b29Sdann frazier * Check if we can do NMI decoding or not 81847bece87SThomas Mingarelli */ 81934572b29Sdann frazier hpwdt_check_nmi_decoding(dev); 82047bece87SThomas Mingarelli 82147bece87SThomas Mingarelli /* 82236e3ff44Sdann frazier * First let's find out if we are on an iLO2+ server. We will 8237f4da474SThomas Mingarelli * not run on a legacy ASM box. 824ab4ba3cdSThomas Mingarelli * So we only support the G5 ProLiant servers and higher. 8257f4da474SThomas Mingarelli */ 826fc113d54SBrian Boylston if (dev->subsystem_vendor != PCI_VENDOR_ID_HP && 827fc113d54SBrian Boylston dev->subsystem_vendor != PCI_VENDOR_ID_HP_3PAR) { 8287f4da474SThomas Mingarelli dev_warn(&dev->dev, 82936e3ff44Sdann frazier "This server does not have an iLO2+ ASIC.\n"); 8307f4da474SThomas Mingarelli return -ENODEV; 8317f4da474SThomas Mingarelli } 8327f4da474SThomas Mingarelli 8330821f20dSMingarelli, Thomas /* 8340821f20dSMingarelli, Thomas * Ignore all auxilary iLO devices with the following PCI ID 8350821f20dSMingarelli, Thomas */ 836fc113d54SBrian Boylston if (dev->subsystem_vendor == PCI_VENDOR_ID_HP && 837fc113d54SBrian Boylston dev->subsystem_device == 0x1979) 8380821f20dSMingarelli, Thomas return -ENODEV; 8390821f20dSMingarelli, Thomas 8407f4da474SThomas Mingarelli if (pci_enable_device(dev)) { 8417f4da474SThomas Mingarelli dev_warn(&dev->dev, 8427f4da474SThomas Mingarelli "Not possible to enable PCI Device: 0x%x:0x%x.\n", 8437f4da474SThomas Mingarelli ent->vendor, ent->device); 8447f4da474SThomas Mingarelli return -ENODEV; 8457f4da474SThomas Mingarelli } 8467f4da474SThomas Mingarelli 8477f4da474SThomas Mingarelli pci_mem_addr = pci_iomap(dev, 1, 0x80); 8487f4da474SThomas Mingarelli if (!pci_mem_addr) { 8497f4da474SThomas Mingarelli dev_warn(&dev->dev, 85036e3ff44Sdann frazier "Unable to detect the iLO2+ server memory.\n"); 8517f4da474SThomas Mingarelli retval = -ENOMEM; 8527f4da474SThomas Mingarelli goto error_pci_iomap; 8537f4da474SThomas Mingarelli } 854*838534e5SJerry Hoemann hpwdt_nmistat = pci_mem_addr + 0x6e; 8557f4da474SThomas Mingarelli hpwdt_timer_reg = pci_mem_addr + 0x70; 8567f4da474SThomas Mingarelli hpwdt_timer_con = pci_mem_addr + 0x72; 8577f4da474SThomas Mingarelli 858308b135eSToshi Kani /* Make sure that timer is disabled until /dev/watchdog is opened */ 859308b135eSToshi Kani hpwdt_stop(); 860308b135eSToshi Kani 8617f4da474SThomas Mingarelli /* Make sure that we have a valid soft_margin */ 8627f4da474SThomas Mingarelli if (hpwdt_change_timer(soft_margin)) 8637f4da474SThomas Mingarelli hpwdt_change_timer(DEFAULT_MARGIN); 8647f4da474SThomas Mingarelli 8652ec7ed67Sdann frazier /* Initialize NMI Decoding functionality */ 8662ec7ed67Sdann frazier retval = hpwdt_init_nmi_decoding(dev); 8672ec7ed67Sdann frazier if (retval != 0) 8682ec7ed67Sdann frazier goto error_init_nmi_decoding; 8697f4da474SThomas Mingarelli 8707f4da474SThomas Mingarelli retval = misc_register(&hpwdt_miscdev); 8717f4da474SThomas Mingarelli if (retval < 0) { 8727f4da474SThomas Mingarelli dev_warn(&dev->dev, 8737f4da474SThomas Mingarelli "Unable to register miscdev on minor=%d (err=%d).\n", 8747f4da474SThomas Mingarelli WATCHDOG_MINOR, retval); 8757f4da474SThomas Mingarelli goto error_misc_register; 8767f4da474SThomas Mingarelli } 8777f4da474SThomas Mingarelli 878ca22e79fSMingarelli, Thomas dev_info(&dev->dev, "HPE Watchdog Timer Driver: %s" 8792ec7ed67Sdann frazier ", timer margin: %d seconds (nowayout=%d).\n", 8802ec7ed67Sdann frazier HPWDT_VERSION, soft_margin, nowayout); 8817f4da474SThomas Mingarelli return 0; 8827f4da474SThomas Mingarelli 8837f4da474SThomas Mingarelli error_misc_register: 8842ec7ed67Sdann frazier hpwdt_exit_nmi_decoding(); 8852ec7ed67Sdann frazier error_init_nmi_decoding: 8867f4da474SThomas Mingarelli pci_iounmap(dev, pci_mem_addr); 8877f4da474SThomas Mingarelli error_pci_iomap: 8887f4da474SThomas Mingarelli pci_disable_device(dev); 8897f4da474SThomas Mingarelli return retval; 8907f4da474SThomas Mingarelli } 8917f4da474SThomas Mingarelli 8924b12b896SBill Pemberton static void hpwdt_exit(struct pci_dev *dev) 8937f4da474SThomas Mingarelli { 8947f4da474SThomas Mingarelli if (!nowayout) 8957f4da474SThomas Mingarelli hpwdt_stop(); 8967f4da474SThomas Mingarelli 8977f4da474SThomas Mingarelli misc_deregister(&hpwdt_miscdev); 8982ec7ed67Sdann frazier hpwdt_exit_nmi_decoding(); 8997f4da474SThomas Mingarelli pci_iounmap(dev, pci_mem_addr); 9007f4da474SThomas Mingarelli pci_disable_device(dev); 9017f4da474SThomas Mingarelli } 9027f4da474SThomas Mingarelli 9037f4da474SThomas Mingarelli static struct pci_driver hpwdt_driver = { 9047f4da474SThomas Mingarelli .name = "hpwdt", 9057f4da474SThomas Mingarelli .id_table = hpwdt_devices, 9067f4da474SThomas Mingarelli .probe = hpwdt_init_one, 90782268714SBill Pemberton .remove = hpwdt_exit, 9087f4da474SThomas Mingarelli }; 9097f4da474SThomas Mingarelli 9107f4da474SThomas Mingarelli MODULE_AUTHOR("Tom Mingarelli"); 9117f4da474SThomas Mingarelli MODULE_DESCRIPTION("hp watchdog driver"); 9127f4da474SThomas Mingarelli MODULE_LICENSE("GPL"); 913d8100c3aSThomas Mingarelli MODULE_VERSION(HPWDT_VERSION); 9147f4da474SThomas Mingarelli 9157f4da474SThomas Mingarelli module_param(soft_margin, int, 0); 9167f4da474SThomas Mingarelli MODULE_PARM_DESC(soft_margin, "Watchdog timeout in seconds"); 9177f4da474SThomas Mingarelli 91886a1e189SWim Van Sebroeck module_param(nowayout, bool, 0); 9197f4da474SThomas Mingarelli MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 9207f4da474SThomas Mingarelli __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 9217f4da474SThomas Mingarelli 92286ded1f3Sdann frazier #ifdef CONFIG_HPWDT_NMI_DECODING 923550d299eSdann frazier module_param(allow_kdump, int, 0); 924550d299eSdann frazier MODULE_PARM_DESC(allow_kdump, "Start a kernel dump after NMI occurs"); 92586ded1f3Sdann frazier #endif /* !CONFIG_HPWDT_NMI_DECODING */ 92644df7535STom Mingarelli 9275ce9c371SWim Van Sebroeck module_pci_driver(hpwdt_driver); 928