xref: /linux/drivers/watchdog/hpwdt.c (revision 838534e50e2e5c1e644e30ab6cb28da88eb31368)
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