Mailing List Archive

[HP ProLiant WatchDog driver] hpwdt HP WatchDog Patch <resend>
Hp is providing a Hardware WatchDog Timer driver that will only work with the
specific HW Timer located in the HP ProLiant iLO 2 ASIC. The iLO 2 HW Timer
will generate a Non-maskable Interrupt (NMI) 9 seconds before physically
resetting the server, by removing power, so that the event can be logged to
the HP Integrated Management Log (IML), a Non-Volatile Random Access Memory
(NVRAM). The logging of the event is performed using the HP ProLiant ROM via
an Industry Standard access known as a BIOS Service Directory Entry.

Signed-off-by: Thomas Mingarelli <thomas.mingarelli@hp.com>

--- linux-2.6.23.1/drivers/char/watchdog/Makefile.orig 2007-10-12 11:43:44.000000000 -0500
+++ linux-2.6.23.1/drivers/char/watchdog/Makefile 2007-10-15 07:56:31.000000000 -0500
@@ -118,3 +118,10 @@

# Architecture Independant
obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
+
+#
+# Makefile for the hp WatchDog driver.
+#
+CFLAGS_hpwdt.o += -O
+obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
+
--- linux-2.6.23.1/drivers/char/watchdog/Kconfig.orig 2007-10-12 11:43:44.000000000 -0500
+++ linux-2.6.23.1/drivers/char/watchdog/Kconfig 2007-10-15 07:57:27.000000000 -0500
@@ -55,6 +55,19 @@
To compile this driver as a module, choose M here: the
module will be called softdog.

+config HP_WATCHDOG
+ tristate "Hewlett-Packard watchdog"
+ depends on WATCHDOG && X86
+ help
+ A software monitoring watchdog and NMI sourcing driver. This driver
+ will detect lockups and provide stack trace. Also, when an NMI
+ occurs this driver will make the necessary BIOS calls to log
+ the cause of the NMI. This is a driver that will only load on a
+ HP ProLiant system with a minimum of iLO2 support.
+ To compile this driver as a module, choose M here: the
+ module will be called hpwdt.
+
+
# ALPHA Architecture

# ARM Architecture
--- linux-2.6.23.1/drivers/char/watchdog/hpwdt.c.orig 2007-10-15 07:53:12.000000000 -0500
+++ linux-2.6.23.1/drivers/char/watchdog/hpwdt.c 2007-10-22 11:53:06.000000000 -0500
@@ -1 +1,878 @@
+/*
+ * HP WatchDog Driver
+ * based on
+ *
+ * SoftDog 0.05: A Software Watchdog Device
+ *
+ * (c) Copyright 2007 Hewlett-Packard Development Company, L.P.
+ * Thomas Mingarelli <thomas.mingarelli@hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation
+ *
+ */

+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kdebug.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/notifier.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/reboot.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#include <asm/desc.h>
+
+#define PCI_BIOS32_SD_VALUE 0x5F32335F
+#define CRU_BIOS_SIGNATURE_VALUE 0x55524324
+#define PCI_BIOS32_PARAGRAPH_LEN 16
+#define PCI_ROM_BASE1 0x000F0000
+#define ROM_SIZE 0x0FFFF
+
+struct bios32_service_dir {
+ u32 signature;
+ u32 entry_point;
+ u8 revision;
+ u8 length;
+ u8 checksum;
+ u8 reserved[5];
+};
+
+
+/*
+ * smbios_entry_point - defines SMBIOS entry point structure
+ *
+ * anchor_string[4] - anchor string (_SM_)
+ * check_sum - checksum of the entry point structure
+ * length - length of the entry point structure
+ * major_ver - major version (02h for revision 2.1)
+ * minor_ver - minor version (01h for revision 2.1)
+ * max_struct_size - size of the largest SMBIOS structure
+ * revision - entry point structure revision implemented
+ * reserved[5] - reserved
+ * intermediate_anchor[5] - intermediate anchor string (_DM_)
+ * intermediate_check_sum - intermediate checksum
+ * table_length - structure table length
+ * table_address - structure table address
+ * number_structs - number of structures present
+ * bcd_revision - BCD revision
+ */
+struct smbios_entry_point {
+ u8 anchor_string[4];
+ u8 check_sum;
+ u8 length;
+ u8 major_ver;
+ u8 minor_ver;
+ u16 max_struct_size;
+ u8 revision;
+ u8 reserved[5];
+ u8 intermediate_anchor[5];
+ u8 intermediate_check_sum;
+ u16 table_length;
+ u64 table_address;
+ u16 number_structs;
+ u8 bcd_revision;
+};
+
+
+/* type 212 */
+struct smbios_cru64_info {
+ u8 type;
+ u8 byte_length;
+ u16 handle;
+ u32 signature;
+ u64 physical_address;
+ u32 double_length;
+ u32 double_offset;
+};
+
+struct smbios_header {
+ u8 byte_type;
+ u8 byte_length;
+ u16 handle;
+};
+
+struct cmn_registers {
+ union {
+ struct {
+ u8 ral;
+ u8 rah;
+ u16 rea2;
+ };
+ u32 reax;
+ } u1;
+ union {
+ struct {
+ u8 rbl;
+ u8 rbh;
+ u8 reb2l;
+ u8 reb2h;
+ };
+ u32 rebx;
+ } u2;
+ union {
+ struct {
+ u8 rcl;
+ u8 rch;
+ u16 rec2;
+ };
+ u32 recx;
+ } u3;
+ union {
+ struct {
+ u8 rdl;
+ u8 rdh;
+ u16 red2;
+ };
+ u32 redx;
+ } u4;
+
+ u32 resi;
+ u32 redi;
+ u16 rds;
+ u16 res;
+ u32 reflags;
+} __attribute__((packed));
+
+#define SMBIOS_CRU64_INFORMATION 212
+
+static unsigned int soft_margin = 2; /* in minutes */
+static unsigned long __iomem *hpwdt_timer_reg;
+static unsigned long __iomem *hpwdt_timer_con;
+static unsigned int reload;
+static unsigned int new_margin;
+static u16 tbl_len;
+static u64 tbl_addr;
+static int die_reg;
+static int pci_enable;
+
+static DEFINE_SPINLOCK(rom_lock);
+
+static void *gpVirtRomCRUAddr;
+static void *gpBios32Map;
+static unsigned char *gpBios32EntryPoint;
+static void *p_mem_addr;
+
+static struct cmn_registers cmn_regs;
+
+static struct pci_device_id hpwdt_devices[] = {
+ {
+ .vendor = PCI_VENDOR_ID_COMPAQ,
+ .device = 0xB203,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ {0}, /* terminate list */
+};
+
+MODULE_DEVICE_TABLE(pci, hpwdt_devices);
+
+asmlinkage void asminline_call(struct cmn_registers *pi86Regs,
+ unsigned long *pRomEntry)
+{
+#ifdef CONFIG_X86_64
+ asm("pushq %rbp \n\t"
+ "movq %rsp, %rbp \n\t"
+ "pushq %rax \n\t"
+ "pushq %rbx \n\t"
+ "pushq %rdx \n\t"
+ "pushq %r12 \n\t"
+ "pushq %r9 \n\t"
+ "movq %rsi, %r12 \n\t"
+ "movq %rdi, %r9 \n\t"
+ "movl 4(%r9),%ebx \n\t"
+ "movl 8(%r9),%ecx \n\t"
+ "movl 12(%r9),%edx \n\t"
+ "movl 16(%r9),%esi \n\t"
+ "movl 20(%r9),%edi \n\t"
+ "movl (%r9),%eax \n\t"
+ "call *%r12 \n\t"
+ "pushfq \n\t"
+ "popq %r12 \n\t"
+ "popfq \n\t"
+ "movl %eax, (%r9) \n\t"
+ "movl %ebx, 4(%r9) \n\t"
+ "movl %ecx, 8(%r9) \n\t"
+ "movl %edx, 12(%r9) \n\t"
+ "movl %esi, 16(%r9) \n\t"
+ "movl %edi, 20(%r9) \n\t"
+ "movq %r12, %rax \n\t"
+ "movl %eax, 28(%r9) \n\t"
+ "popq %r9 \n\t"
+ "popq %r12 \n\t"
+ "popq %rdx \n\t"
+ "popq %rbx \n\t"
+ "popq %rax \n\t"
+ "leave \n\t" "ret");
+#endif
+
+#ifdef CONFIG_X86_32
+ asm("pushl %ebp \n\t"
+ "movl %esp, %ebp \n\t"
+ "pusha \n\t"
+ "pushf \n\t"
+ "push %es \n\t"
+ "push %ds \n\t"
+ "pop %es \n\t"
+ "movl 8(%ebp),%eax \n\t"
+ "movl 4(%eax),%ebx \n\t"
+ "movl 8(%eax),%ecx \n\t"
+ "movl 12(%eax),%edx \n\t"
+ "movl 16(%eax),%esi \n\t"
+ "movl 20(%eax),%edi \n\t"
+ "movl (%eax),%eax \n\t"
+ "push %cs \n\t"
+ "call *12(%ebp) \n\t"
+ "pushf \n\t"
+ "pushl %eax \n\t"
+ "movl 8(%ebp),%eax \n\t"
+ "movl %ebx,4(%eax) \n\t"
+ "movl %ecx,8(%eax) \n\t"
+ "movl %edx,12(%eax) \n\t"
+ "movl %esi,16(%eax) \n\t"
+ "movl %edi,20(%eax) \n\t"
+ "movw %ds,24(%eax) \n\t"
+ "movw %es,26(%eax) \n\t"
+ "popl %ebx \n\t"
+ "movl %ebx,(%eax) \n\t"
+ "popl %ebx \n\t"
+ "movl %ebx,28(%eax) \n\t"
+ "pop %es \n\t"
+ "popf \n\t"
+ "popa \n\t"
+ "leave \n\t" "ret");
+#endif
+}
+
+/*
+ * smbios_get_record
+ *
+ * Routine Description:
+ * This function will get the next record in the SMB Tables
+ *
+ * Return Value:
+ * 1 : SUCCESS - if record found
+ * 0 : FAILURE - if record not found
+ */
+int smbios_get_record(unsigned char **pp_record, void *smbios_table_ptr)
+{
+ unsigned char *buffer;
+ struct smbios_header *header_ptr;
+
+ /*
+ * if pp_record is NULL, find the first record
+ */
+ if (*pp_record == 0) {
+ *pp_record = smbios_table_ptr;
+ buffer = smbios_table_ptr;
+ } else {
+ header_ptr = (struct smbios_header *) * pp_record;
+ buffer = *pp_record;
+
+ /*
+ * skip over the structure itself
+ */
+ *pp_record += header_ptr->byte_length;
+
+ buffer += header_ptr->byte_length;
+
+ /*
+ * skip over any strings following the structure
+ */
+ while (*buffer || *(buffer + 1))
+ buffer++;
+
+ /*
+ * skip over the double NULL characters
+ */
+ buffer += 2;
+ }
+ if (buffer >= ((unsigned char *)smbios_table_ptr + tbl_len))
+ return 0;
+
+ *pp_record = buffer;
+ return 1;
+}
+
+/*
+ * smbios_get_record_by_type
+ *
+ * Routine Description:
+ * This function will get a record in the SMB Table by the type specified
+ *
+ * Return Value:
+ * 1 : SUCCESS - if record found
+ * 0 : FAILURE - if record not found
+ */
+int smbios_get_record_by_type(unsigned char type, unsigned short copy,
+ void **pp_record, void *smbios_table_ptr)
+{
+ unsigned char *save_state_ptr = NULL;
+ struct smbios_header *header_ptr;
+
+ while (smbios_get_record(&save_state_ptr, smbios_table_ptr)) {
+ header_ptr = (struct smbios_header *) save_state_ptr;
+ if (header_ptr->byte_type == type) {
+ if (copy == 0) {
+ *pp_record = (void *)save_state_ptr;
+ return 1;
+ } else
+ copy--;
+ }
+ }
+ return 0;
+}
+
+/*
+ * smbios_get_64bit_cru_info
+ *
+ * Routine Description:
+ * This routine will collect the 64bit CRU entry point from SMBIOS
+ * table.
+ *
+ * Return Value:
+ * 0 : SUCCESS
+ * <0 : FAILURE
+ */
+int __devinit smbios_get_64bit_cru_info(void)
+{
+ unsigned short copy;
+ unsigned char *record_ptr = NULL;
+ struct smbios_cru64_info *smbios_cru64_ptr;
+ unsigned long cru_physical_address;
+ void *smbios_table_ptr;
+ int retval = -ENOMEM;
+
+ smbios_table_ptr = ioremap((unsigned int)tbl_addr, tbl_len);
+ if (!smbios_table_ptr)
+ return retval;
+
+ copy = 0;
+ while (smbios_get_record_by_type
+ (SMBIOS_CRU64_INFORMATION, copy, (void *)&record_ptr,
+ smbios_table_ptr)) {
+ /*
+ * In the event the ROM has a bug, let's print this
+ * out and quit before we get into trouble.
+ */
+ if (!record_ptr) {
+ printk(KERN_WARNING
+ "hpwdt: smbios: Missing SMBIOS_CRU64_INFO!\n");
+ retval = -ENODEV;
+ break;
+ }
+ smbios_cru64_ptr = (struct smbios_cru64_info *) record_ptr;
+ if (smbios_cru64_ptr->signature == CRU_BIOS_SIGNATURE_VALUE) {
+
+ cru_physical_address =
+ smbios_cru64_ptr->physical_address +
+ smbios_cru64_ptr->double_offset;
+ gpVirtRomCRUAddr =
+ ioremap((unsigned long)cru_physical_address,
+ smbios_cru64_ptr->double_length);
+ if (gpVirtRomCRUAddr)
+ retval = 0;
+ break;
+ }
+ }
+
+ iounmap(smbios_table_ptr);
+ return retval;
+}
+
+/*
+ * smbios_detect
+ *
+ * Routine Description:
+ * This function parses SMBIOS to retrieve the 64 bit CRU Service.
+ *
+ * Return Value:
+ * 0 : SUCCESS
+ * <0 : FAILURE
+ */
+int smbios_detect(void)
+{
+ unsigned char *p_virtual_rom;
+ unsigned char *p;
+ struct smbios_entry_point *pEPS;
+ int retval = -ENOMEM;
+
+ /*
+ * Search from 0x0f0000 through 0x0fffff, inclusive.
+ */
+ p_virtual_rom = ioremap(PCI_ROM_BASE1, ROM_SIZE);
+ if (!p_virtual_rom)
+ return retval;
+
+ for (p = p_virtual_rom; p < p_virtual_rom + ROM_SIZE; p += 16) {
+ pEPS = (struct smbios_entry_point *) p;
+ if ((strncmp((char *)pEPS->anchor_string, "_SM_",
+ sizeof(pEPS->anchor_string))) == 0) {
+ tbl_addr = pEPS->table_address;
+ tbl_len = pEPS->table_length;
+ retval = 0;
+ break;
+ }
+ }
+ iounmap(p_virtual_rom);
+ return retval;
+}
+
+/*
+ * cru_detect
+ *
+ * Routine Description:
+ * This function uses the 32-bit BIOS Service Directory record to
+ * search for a $CRU record.
+ *
+ * Return Value:
+ * 0 : SUCCESS
+ * <0 : FAILURE
+ */
+int __devinit cru_detect(void)
+{
+ unsigned long cru_physical_address;
+ unsigned long cru_length;
+ unsigned long physical_bios_base = 0;
+ unsigned long physical_bios_offset = 0;
+ int retval = -ENODEV;
+
+ cmn_regs.u1.reax = CRU_BIOS_SIGNATURE_VALUE;
+
+ asminline_call(&cmn_regs, (unsigned long *)gpBios32EntryPoint);
+
+ if (cmn_regs.u1.ral != 0) {
+ printk(KERN_WARNING
+ "hpwdt: Call succeeded but with an error: 0x%x\n",
+ cmn_regs.u1.ral);
+ } else {
+ physical_bios_base = cmn_regs.u2.rebx;
+ physical_bios_offset = cmn_regs.u4.redx;
+ cru_length = cmn_regs.u3.recx;
+ cru_physical_address =
+ physical_bios_base + physical_bios_offset;
+
+ /* If the values look OK, then map it in. */
+ if ((physical_bios_base + physical_bios_offset)) {
+ gpVirtRomCRUAddr =
+ ioremap(cru_physical_address, cru_length);
+ if (gpVirtRomCRUAddr)
+ retval = 0;
+ }
+
+ printk(KERN_DEBUG "hpwdt: CRU Base Address: 0x%lx\n",
+ physical_bios_base);
+ printk(KERN_DEBUG "hpwdt: CRU Offset Address: 0x%lx\n",
+ physical_bios_offset);
+ printk(KERN_DEBUG "hpwdt: CRU Length: 0x%lx\n",
+ cru_length);
+ printk(KERN_DEBUG "hpwdt: CRU Mapped Address: 0x%x\n",
+ (unsigned int)&gpVirtRomCRUAddr);
+ }
+ iounmap(gpBios32Map);
+ return retval;
+}
+
+/*
+ * valid_checksum
+ */
+static int valid_checksum(void *header_ptr, unsigned char size)
+{
+ unsigned char i;
+ unsigned char check_sum = 0;
+ unsigned char *ptr = header_ptr;
+
+ /*
+ * calculate checksum of size bytes. This should add up
+ * to zero if we have a valid header.
+ */
+ for (i = 0; i < size; i++, ptr++)
+ check_sum = (check_sum + (*ptr));
+
+ return ((check_sum == 0) && (size > 0));
+}
+
+/*
+ * check_for_bios32_support
+ *
+ * Routine Description:
+ * This function determine if a pointer is pointing to the
+ * 32 bit BIOS Directory Services entry in ROM space.
+ *
+ * Return Value:
+ * 1 - if found the correct signature
+ * 0 - unable to find _32_
+ */
+static int check_for_bios32_support(struct bios32_service_dir *bios_32_ptr)
+{
+ /*
+ * Search for signature by checking equal to the swizzled value
+ * instead of calling another routine to perform a strcmp.
+ */
+ if (bios_32_ptr->signature == PCI_BIOS32_SD_VALUE) {
+ if (valid_checksum((void *)bios_32_ptr,
+ ((unsigned char)(bios_32_ptr->length *
+ PCI_BIOS32_PARAGRAPH_LEN))))
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * find_32bit_bios_sd
+ *
+ * Routine Description:
+ * This function find the 32-bit BIOS Service Directory
+ *
+ * Return Value:
+ * 0 : SUCCESS
+ * <0 : FAILURE
+ */
+int __devinit find_32bit_bios_sd(void)
+{
+ struct bios32_service_dir *bios_32_data_ptr;
+ unsigned long map_entry, map_offset;
+ void *pRomVirtAddr;
+ unsigned char *p;
+ int retval = -ENOMEM;
+
+ pRomVirtAddr = ioremap(PCI_ROM_BASE1, ROM_SIZE);
+ if (!pRomVirtAddr) {
+ printk(KERN_WARNING "hpwdt: Unable to map in BIOS ROM.\n");
+ return retval;
+ }
+
+ /*
+ * BIOS32 spec indicates that the signature will be
+ * paragraph-aligned, so we'll skip by 16 bytes each pass
+ * through the loop.
+ */
+ for (p = pRomVirtAddr;
+ p < ((unsigned char *)pRomVirtAddr + ROM_SIZE); p += 16) {
+
+ if (!check_for_bios32_support((struct bios32_service_dir *) p))
+ continue;
+
+ /* Found a Service Directory. */
+ bios_32_data_ptr = (struct bios32_service_dir *) p;
+
+ /*
+ * According to the spec, we're looking for the
+ * first 4KB-aligned address below the entrypoint
+ * listed in the header. The Service Directory code
+ * is guaranteed to occupy no more than 2 4KB pages.
+ */
+ map_entry = bios_32_data_ptr->entry_point & ~(PAGE_SIZE - 1);
+ map_offset = bios_32_data_ptr->entry_point - map_entry;
+
+ gpBios32Map = ioremap(map_entry, (2 * PAGE_SIZE));
+
+ if (gpBios32Map) {
+ /* This will be unmapped in cru_detect. */
+ gpBios32EntryPoint = gpBios32Map + map_offset;
+ retval = 0;
+ }
+ break;
+ }
+ iounmap(pRomVirtAddr);
+ return retval;
+}
+
+static int hpwdt_pretimeout(struct notifier_block *nb, unsigned long ulReason,
+ void *dummy)
+{
+ static unsigned long rom_pl;
+ static int die_nmi_called;
+
+ if (ulReason == DIE_NMI || ulReason == DIE_NMI_IPI) {
+ spin_lock_irqsave(&rom_lock, rom_pl);
+ if (!die_nmi_called)
+ asminline_call(&cmn_regs, gpVirtRomCRUAddr);
+ die_nmi_called = 1;
+ spin_unlock_irqrestore(&rom_lock, rom_pl);
+ if (cmn_regs.u1.ral == 0) {
+ printk(KERN_WARNING "hpwdt: An NMI occurred, "
+ "but unable to determine source.\n");
+ } else {
+ panic("An NMI occurred, please see the Integrated "
+ "Management Log for details.\n");
+ }
+ }
+ return NOTIFY_STOP;
+}
+
+/*
+ * Refresh the timer.
+ */
+static void hpwdt_ping(void)
+{
+ writew(reload, hpwdt_timer_reg);
+}
+
+static int hpwdt_open(struct inode *inode, struct file *file)
+{
+ reload = ((soft_margin * 60) * 1000) / 128;
+ /*
+ * Setting this bit is irreversible; once enabled, there is
+ * no way to disable the watchdog.
+ */
+ writew(0x85, hpwdt_timer_con);
+ return 0;
+}
+
+/*
+ * Shut off the timer.
+ * Note: if we really have enabled the watchdog, there
+ * is no way to turn off.
+ */
+static int hpwdt_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static ssize_t
+hpwdt_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+ /*
+ * Refresh the timer.
+ */
+ if (len)
+ hpwdt_ping();
+
+ return len;
+}
+
+static struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT,
+ .identity = "HP Integrated Lights-Out HW Watchdog Timer",
+};
+
+static long
+hpwdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = -ENOIOCTLCMD;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = 0;
+ if (copy_to_user((void *)arg, &ident, sizeof(ident)))
+ ret = -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ ret = 0;
+ break;
+
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(0, (int *)arg);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ hpwdt_ping();
+ ret = 0;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(new_margin, (int *)arg);
+ if (ret)
+ break;
+
+ /* Arbitrary, can't find the card's limits */
+ if (new_margin < 2 || new_margin > 30) {
+ ret = -EINVAL;
+ break;
+ }
+
+ soft_margin = new_margin;
+ printk(KERN_DEBUG
+ "hpwdt: New timer passed in is %d minutes.\n",
+ new_margin);
+ reload = ((soft_margin * 60) * 1000) / 128;
+ hpwdt_ping();
+ /* Fall */
+ case WDIOC_GETTIMEOUT:
+ ret = put_user(new_margin, (int *)arg);
+ break;
+ }
+ return ret;
+}
+
+static struct file_operations hpwdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = hpwdt_write,
+ .unlocked_ioctl = hpwdt_ioctl,
+ .open = hpwdt_open,
+ .release = hpwdt_release,
+};
+
+static struct miscdevice hpwdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &hpwdt_fops,
+};
+
+static struct notifier_block die_notifier = {
+ .notifier_call = hpwdt_pretimeout,
+ .priority = 0x7FFFFFFF,
+};
+
+static int __devinit hpwdt_init_one(struct pci_dev *dev,
+ const struct pci_device_id *ent)
+{
+ int retval;
+
+ if (pci_enable_device(dev)) {
+ printk(KERN_WARNING
+ "hpwdt: Not possible to enable PCI Device\n");
+ return -ENODEV;
+ }
+ pci_enable = 1;
+
+ /*
+ * First let's find out if we are on an iLO2 server. We will
+ * not run on a legacy ASM box.
+ */
+ if (dev->subsystem_vendor != PCI_VENDOR_ID_HP) {
+ printk(KERN_WARNING
+ "hpwdt: This server does not have an iLO2 ASIC.\n");
+ pci_disable_device(dev);
+ return -ENODEV;
+ }
+
+ retval = misc_register(&hpwdt_miscdev);
+ if (retval < 0) {
+ pci_disable_device(dev);
+ return retval;
+ }
+
+ p_mem_addr = ioremap((unsigned long)pci_resource_start(dev, 1), 0x80);
+ if (!p_mem_addr) {
+ retval = -ENOMEM;
+ goto error_out;
+ }
+ hpwdt_timer_reg = (p_mem_addr + 0x70);
+ hpwdt_timer_con = (p_mem_addr + 0x72);
+
+#ifndef CONFIG_X86_64
+ /*
+ * Let's try to map in the ROM for 32 bit Operating Systems because
+ * we need get the CRU Service through the 32 Bit BIOS SD.
+ * This is not the case for 64 bit Operating Systems where we get
+ * that service through SMBIOS.
+ */
+ retval = find_32bit_bios_sd();
+ if (retval < 0) {
+ printk(KERN_WARNING
+ "hpwdt: No 32 Bit BIOS Service Directory found.\n");
+ goto error_out;
+ }
+
+ retval = cru_detect();
+ if (retval < 0) {
+ printk(KERN_WARNING
+ "hpwdt: Unable to detect 32 Bit CRU Service.\n");
+ goto error_out;
+ }
+#else
+ retval = smbios_detect();
+ if (retval < 0) {
+ printk(KERN_WARNING "hpwdt: No 64 Bit SMBIOS found.\n");
+ goto error_out;
+ }
+
+ retval = smbios_get_64bit_cru_info();
+ if (retval < 0) {
+ printk(KERN_WARNING
+ "hpwdt: Unable to detect the 64 Bit CRU Service.\n");
+ goto error_out;
+ }
+#endif
+ /*
+ * We know this is the only CRU call we need to make so lets keep as
+ * few instructions as possible once the NMI comes in.
+ */
+ memset(&cmn_regs, 0, sizeof(struct cmn_registers));
+ cmn_regs.u1.rah = 0x0D;
+ cmn_regs.u1.ral = 0x02;
+
+ retval = register_die_notifier(&die_notifier);
+ if (retval != 0) {
+ printk(KERN_WARNING
+ "hpwdt: Unable to register a die notifier.\n");
+ goto error_out;
+ }
+ die_reg = 1;
+
+ new_margin = soft_margin;
+ printk("hp Watchdog Timer Driver: 1.00, timer margin: %d minutes\n",
+ new_margin);
+
+ return 0;
+error_out:
+ printk(KERN_WARNING "hpwdt: NMI support is not possible.\n");
+ misc_deregister(&hpwdt_miscdev);
+ if (pci_enable)
+ pci_disable_device(dev);
+ if (p_mem_addr)
+ iounmap(p_mem_addr);
+ if (gpVirtRomCRUAddr)
+ iounmap(gpVirtRomCRUAddr);
+ return retval;
+}
+
+static void __devexit hpwdt_exit(struct pci_dev *dev)
+{
+ if (die_reg)
+ unregister_die_notifier(&die_notifier);
+
+ if (pci_enable)
+ pci_disable_device(dev);
+
+ misc_deregister(&hpwdt_miscdev);
+ if (p_mem_addr)
+ iounmap(p_mem_addr);
+ if (gpVirtRomCRUAddr)
+ iounmap(gpVirtRomCRUAddr);
+}
+
+static struct pci_driver hpwdt_driver = {
+ .name = "hpwdt",
+ .id_table = hpwdt_devices,
+ .probe = hpwdt_init_one,
+ .remove = __devexit_p(hpwdt_exit),
+};
+
+static void __exit hpwdt_cleanup(void)
+{
+ pci_unregister_driver(&hpwdt_driver);
+}
+
+static int __init hpwdt_init(void)
+{
+ return pci_register_driver(&hpwdt_driver);
+}
+
+MODULE_AUTHOR("Tom Mingarelli");
+MODULE_DESCRIPTION("hp watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+module_param(soft_margin, int, 0);
+MODULE_PARM_DESC(soft_margin, "Watchdog timeout in minutes");
+
+module_init(hpwdt_init);
+module_exit(hpwdt_cleanup);
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Re: [HP ProLiant WatchDog driver] hpwdt HP WatchDog Patch <resend> [ In reply to ]
On Monday 22 October 2007 05:09:51 pm thomas.mingarelli@hp.com wrote:
> +config HP_WATCHDOG
> + tristate "Hewlett-Packard watchdog"
> + depends on WATCHDOG && X86

I wouldn't be surprised if this device someday turned up on non-x86
systems. I know there's some x86 firmware stuff in there that clearly
requires x86. But it'd be nice if the rest of the driver compiled and
worked (minus the x86 firmware functionality) on other architectures.
For example, you could wrap all the event logging code in "#ifdef
CONFIG_X86" and provide a null implementation for !X86.

> +asmlinkage void asminline_call(struct cmn_registers *pi86Regs,
> + unsigned long *pRomEntry)
> +{
> +#ifdef CONFIG_X86_64
> + asm("pushq %rbp \n\t"
> + "movq %rsp, %rbp \n\t"
> + "pushq %rax \n\t"
> + "pushq %rbx \n\t"
> + "pushq %rdx \n\t"
> + "pushq %r12 \n\t"
> + "pushq %r9 \n\t"
> + "movq %rsi, %r12 \n\t"
> + "movq %rdi, %r9 \n\t"
> + "movl 4(%r9),%ebx \n\t"
> + "movl 8(%r9),%ecx \n\t"
> + "movl 12(%r9),%edx \n\t"
> + "movl 16(%r9),%esi \n\t"
> + "movl 20(%r9),%edi \n\t"
> + "movl (%r9),%eax \n\t"
> + "call *%r12 \n\t"
> + "pushfq \n\t"
> + "popq %r12 \n\t"
> + "popfq \n\t"
> + "movl %eax, (%r9) \n\t"
> + "movl %ebx, 4(%r9) \n\t"
> + "movl %ecx, 8(%r9) \n\t"
> + "movl %edx, 12(%r9) \n\t"
> + "movl %esi, 16(%r9) \n\t"
> + "movl %edi, 20(%r9) \n\t"
> + "movq %r12, %rax \n\t"
> + "movl %eax, 28(%r9) \n\t"
> + "popq %r9 \n\t"
> + "popq %r12 \n\t"
> + "popq %rdx \n\t"
> + "popq %rbx \n\t"
> + "popq %rax \n\t"
> + "leave \n\t" "ret");
> +#endif

This is more dangerous than using the gcc assembler operand
syntax, because it assumes things about how the parameters are
put on the stack.

> +static int __devinit hpwdt_init_one(struct pci_dev *dev,
> + const struct pci_device_id *ent)
> +{
> + int retval;
> +
> + if (pci_enable_device(dev)) {
> + printk(KERN_WARNING
> + "hpwdt: Not possible to enable PCI Device\n");

You might consider using dev_printk(), dev_warn(), etc, instead of most
of your printk calls. Then you get the device ID and driver name
automatically.

> + return -ENODEV;
> + }
> + pci_enable = 1;

The driver assumes only a single instance of the device. But PCI being
what it is, it's often possible to have multiple cards, so you might
want some protection in case you trip over more than one.

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Re: [HP ProLiant WatchDog driver] hpwdt HP WatchDog Patch <resend> [ In reply to ]
On Tue, Oct 23, 2007 at 02:31:15PM -0600, Bjorn Helgaas wrote:
> The driver assumes only a single instance of the device. But PCI being
> what it is, it's often possible to have multiple cards, so you might
> want some protection in case you trip over more than one.

Yes. And even with that protection please move all the global variables
into some per-device structure. That makes the code a lot more understandable
and future-proof in case multiple devices of this type may appear somewhere.

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
RE: [HP ProLiant WatchDog driver] hpwdt HP WatchDog Patch <resend> [ In reply to ]
We will never have more than one iLO2 device in a server so no multiple
instances are possible.

Tom

-----Original Message-----
From: Christoph Hellwig [mailto:hch@infradead.org]
Sent: Tuesday, October 23, 2007 4:21 PM
To: Helgaas, Bjorn
Cc: Mingarelli, Thomas; linux-kernel@vger.kernel.org; Wim Van Sebroeck
Subject: Re: [HP ProLiant WatchDog driver] hpwdt HP WatchDog Patch
<resend>

On Tue, Oct 23, 2007 at 02:31:15PM -0600, Bjorn Helgaas wrote:
> The driver assumes only a single instance of the device. But PCI
> being what it is, it's often possible to have multiple cards, so you
> might want some protection in case you trip over more than one.

Yes. And even with that protection please move all the global variables
into some per-device structure. That makes the code a lot more
understandable and future-proof in case multiple devices of this type
may appear somewhere.

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
RE: [HP ProLiant WatchDog driver] hpwdt HP WatchDog Patch <resend> [ In reply to ]
As I stated before, we will never have more than one ilO2 device on a
system. Also, this device will only be used on x86 and x86_64
architectures. I will check in the dev_warn changes asap.

Tom

-----Original Message-----
From: Helgaas, Bjorn
Sent: Tuesday, October 23, 2007 3:31 PM
To: Mingarelli, Thomas
Cc: linux-kernel@vger.kernel.org; Wim Van Sebroeck
Subject: Re: [HP ProLiant WatchDog driver] hpwdt HP WatchDog Patch
<resend>

On Monday 22 October 2007 05:09:51 pm thomas.mingarelli@hp.com wrote:
> +config HP_WATCHDOG
> + tristate "Hewlett-Packard watchdog"
> + depends on WATCHDOG && X86

I wouldn't be surprised if this device someday turned up on non-x86
systems. I know there's some x86 firmware stuff in there that clearly
requires x86. But it'd be nice if the rest of the driver compiled and
worked (minus the x86 firmware functionality) on other architectures.
For example, you could wrap all the event logging code in "#ifdef
CONFIG_X86" and provide a null implementation for !X86.

> +asmlinkage void asminline_call(struct cmn_registers *pi86Regs,
> + unsigned long *pRomEntry)
> +{
> +#ifdef CONFIG_X86_64
> + asm("pushq %rbp \n\t"
> + "movq %rsp, %rbp \n\t"
> + "pushq %rax \n\t"
> + "pushq %rbx \n\t"
> + "pushq %rdx \n\t"
> + "pushq %r12 \n\t"
> + "pushq %r9 \n\t"
> + "movq %rsi, %r12 \n\t"
> + "movq %rdi, %r9 \n\t"
> + "movl 4(%r9),%ebx \n\t"
> + "movl 8(%r9),%ecx \n\t"
> + "movl 12(%r9),%edx \n\t"
> + "movl 16(%r9),%esi \n\t"
> + "movl 20(%r9),%edi \n\t"
> + "movl (%r9),%eax \n\t"
> + "call *%r12 \n\t"
> + "pushfq \n\t"
> + "popq %r12 \n\t"
> + "popfq \n\t"
> + "movl %eax, (%r9) \n\t"
> + "movl %ebx, 4(%r9) \n\t"
> + "movl %ecx, 8(%r9) \n\t"
> + "movl %edx, 12(%r9) \n\t"
> + "movl %esi, 16(%r9) \n\t"
> + "movl %edi, 20(%r9) \n\t"
> + "movq %r12, %rax \n\t"
> + "movl %eax, 28(%r9) \n\t"
> + "popq %r9 \n\t"
> + "popq %r12 \n\t"
> + "popq %rdx \n\t"
> + "popq %rbx \n\t"
> + "popq %rax \n\t"
> + "leave \n\t" "ret");
> +#endif

This is more dangerous than using the gcc assembler operand syntax,
because it assumes things about how the parameters are put on the stack.

> +static int __devinit hpwdt_init_one(struct pci_dev *dev,
> + const struct pci_device_id *ent) {
> + int retval;
> +
> + if (pci_enable_device(dev)) {
> + printk(KERN_WARNING
> + "hpwdt: Not possible to enable PCI Device\n");

You might consider using dev_printk(), dev_warn(), etc, instead of most
of your printk calls. Then you get the device ID and driver name
automatically.

> + return -ENODEV;
> + }
> + pci_enable = 1;

The driver assumes only a single instance of the device. But PCI being
what it is, it's often possible to have multiple cards, so you might
want some protection in case you trip over more than one.

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Re: [HP ProLiant WatchDog driver] hpwdt HP WatchDog Patch <resend> [ In reply to ]
Hi Thomas,

> --- linux-2.6.23.1/drivers/char/watchdog/Makefile.orig 2007-10-12 11:43:44.000000000 -0500
> +++ linux-2.6.23.1/drivers/char/watchdog/Makefile 2007-10-15 07:56:31.000000000 -0500
> @@ -118,3 +118,10 @@
>
> # Architecture Independant
> obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
> +
> +#
> +# Makefile for the hp WatchDog driver.
> +#
> +CFLAGS_hpwdt.o += -O
> +obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
> +
> --- linux-2.6.23.1/drivers/char/watchdog/Kconfig.orig 2007-10-12 11:43:44.000000000 -0500
> +++ linux-2.6.23.1/drivers/char/watchdog/Kconfig 2007-10-15 07:57:27.000000000 -0500
> @@ -55,6 +55,19 @@
> To compile this driver as a module, choose M here: the
> module will be called softdog.
>
> +config HP_WATCHDOG
> + tristate "Hewlett-Packard watchdog"
> + depends on WATCHDOG && X86
> + help
> + A software monitoring watchdog and NMI sourcing driver. This driver
> + will detect lockups and provide stack trace. Also, when an NMI
> + occurs this driver will make the necessary BIOS calls to log
> + the cause of the NMI. This is a driver that will only load on a
> + HP ProLiant system with a minimum of iLO2 support.
> + To compile this driver as a module, choose M here: the
> + module will be called hpwdt.
> +
> +
> # ALPHA Architecture
>
> # ARM Architecture

Before reviewing the rest of your driver: can you please put your "X86 Architecture related"
driver in the X86 related parts of Kconfig and Makefile ? Also the depend on WATCHDOG is not
needed since we allready have a general dependency for all the drivers in Kconfig.

Please also note that we just shifted the watchdog drivers from drivers/char/watchdog to
drivers/watchdog -> so your driver will appear in the drivers/watchdog directory when it
will be included.

Greetings,
Wim.

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[HP ProLiant WatchDog driver] hpwdt HP WatchDog Patch <resend> [ In reply to ]
Hp is providing a Hardware WatchDog Timer driver that will only work with the
specific HW Timer located in the HP ProLiant iLO 2 ASIC. The iLO 2 HW Timer
will generate a Non-maskable Interrupt (NMI) 9 seconds before physically
resetting the server, by removing power, so that the event can be logged to
the HP Integrated Management Log (IML), a Non-Volatile Random Access Memory
(NVRAM). The logging of the event is performed using the HP ProLiant ROM via
an Industry Standard access known as a BIOS Service Directory Entry.

Signed-off-by: Thomas Mingarelli <thomas.mingarelli@hp.com>

--- linux-2.6.23.1/drivers/char/watchdog/Makefile.orig 2007-10-12 11:43:44.000000000 -0500
+++ linux-2.6.23.1/drivers/char/watchdog/Makefile 2007-10-24 08:34:13.000000000 -0500
@@ -79,6 +79,8 @@
obj-$(CONFIG_W83977F_WDT) += w83977f_wdt.o
obj-$(CONFIG_MACHZ_WDT) += machzwd.o
obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o
+CFLAGS_hpwdt.o += -O
+obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o

# M32R Architecture

--- linux-2.6.23.1/drivers/char/watchdog/Kconfig.orig 2007-10-12 11:43:44.000000000 -0500
+++ linux-2.6.23.1/drivers/char/watchdog/Kconfig 2007-10-24 08:40:50.000000000 -0500
@@ -575,6 +575,18 @@
To compile this driver as a module, choose M here: the
module will be called sbc_epx_c3.

+config HP_WATCHDOG
+ tristate "Hewlett-Packard watchdog"
+ depends on X86
+ help
+ A software monitoring watchdog and NMI sourcing driver. This driver
+ will detect lockups and provide stack trace. Also, when an NMI
+ occurs this driver will make the necessary BIOS calls to log
+ the cause of the NMI. This is a driver that will only load on a
+ HP ProLiant system with a minimum of iLO2 support.
+ To compile this driver as a module, choose M here: the
+ module will be called hpwdt.
+
# M32R Architecture

# M68K Architecture
--- linux-2.6.23.1/drivers/char/watchdog/hpwdt.c.orig 2007-10-15 07:53:12.000000000 -0500
+++ linux-2.6.23.1/drivers/char/watchdog/hpwdt.c 2007-10-24 04:45:36.000000000 -0500
@@ -1 +1,879 @@
+/*
+ * HP WatchDog Driver
+ * based on
+ *
+ * SoftDog 0.05: A Software Watchdog Device
+ *
+ * (c) Copyright 2007 Hewlett-Packard Development Company, L.P.
+ * Thomas Mingarelli <thomas.mingarelli@hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation
+ *
+ */

+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kdebug.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/notifier.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/reboot.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#include <asm/desc.h>
+
+#define PCI_BIOS32_SD_VALUE 0x5F32335F
+#define CRU_BIOS_SIGNATURE_VALUE 0x55524324
+#define PCI_BIOS32_PARAGRAPH_LEN 16
+#define PCI_ROM_BASE1 0x000F0000
+#define ROM_SIZE 0x0FFFF
+
+struct bios32_service_dir {
+ u32 signature;
+ u32 entry_point;
+ u8 revision;
+ u8 length;
+ u8 checksum;
+ u8 reserved[5];
+};
+
+/*
+ * smbios_entry_point - defines SMBIOS entry point structure
+ *
+ * anchor_string[4] - anchor string (_SM_)
+ * check_sum - checksum of the entry point structure
+ * length - length of the entry point structure
+ * major_ver - major version (02h for revision 2.1)
+ * minor_ver - minor version (01h for revision 2.1)
+ * max_struct_size - size of the largest SMBIOS structure
+ * revision - entry point structure revision implemented
+ * reserved[5] - reserved
+ * intermediate_anchor[5] - intermediate anchor string (_DM_)
+ * intermediate_check_sum - intermediate checksum
+ * table_length - structure table length
+ * table_address - structure table address
+ * number_structs - number of structures present
+ * bcd_revision - BCD revision
+ */
+struct smbios_entry_point {
+ u8 anchor_string[4];
+ u8 check_sum;
+ u8 length;
+ u8 major_ver;
+ u8 minor_ver;
+ u16 max_struct_size;
+ u8 revision;
+ u8 reserved[5];
+ u8 intermediate_anchor[5];
+ u8 intermediate_check_sum;
+ u16 table_length;
+ u64 table_address;
+ u16 number_structs;
+ u8 bcd_revision;
+};
+
+
+/* type 212 */
+struct smbios_cru64_info {
+ u8 type;
+ u8 byte_length;
+ u16 handle;
+ u32 signature;
+ u64 physical_address;
+ u32 double_length;
+ u32 double_offset;
+};
+
+struct smbios_header {
+ u8 byte_type;
+ u8 byte_length;
+ u16 handle;
+};
+
+struct cmn_registers {
+ union {
+ struct {
+ u8 ral;
+ u8 rah;
+ u16 rea2;
+ };
+ u32 reax;
+ } u1;
+ union {
+ struct {
+ u8 rbl;
+ u8 rbh;
+ u8 reb2l;
+ u8 reb2h;
+ };
+ u32 rebx;
+ } u2;
+ union {
+ struct {
+ u8 rcl;
+ u8 rch;
+ u16 rec2;
+ };
+ u32 recx;
+ } u3;
+ union {
+ struct {
+ u8 rdl;
+ u8 rdh;
+ u16 red2;
+ };
+ u32 redx;
+ } u4;
+
+ u32 resi;
+ u32 redi;
+ u16 rds;
+ u16 res;
+ u32 reflags;
+} __attribute__((packed));
+
+#define SMBIOS_CRU64_INFORMATION 212
+
+static unsigned int soft_margin = 2; /* in minutes */
+static unsigned long __iomem *hpwdt_timer_reg;
+static unsigned long __iomem *hpwdt_timer_con;
+static unsigned int reload;
+static unsigned int new_margin;
+static u16 tbl_len;
+static u64 tbl_addr;
+static int die_reg;
+static int pci_enable;
+
+static DEFINE_SPINLOCK(rom_lock);
+
+static void *gpVirtRomCRUAddr;
+static void *gpBios32Map;
+static unsigned char *gpBios32EntryPoint;
+static void *p_mem_addr;
+
+static struct cmn_registers cmn_regs;
+
+static struct pci_device_id hpwdt_devices[] = {
+ {
+ .vendor = PCI_VENDOR_ID_COMPAQ,
+ .device = 0xB203,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ {0}, /* terminate list */
+};
+
+MODULE_DEVICE_TABLE(pci, hpwdt_devices);
+
+asmlinkage void asminline_call(struct cmn_registers *pi86Regs,
+ unsigned long *pRomEntry)
+{
+#ifdef CONFIG_X86_64
+ asm("pushq %rbp \n\t"
+ "movq %rsp, %rbp \n\t"
+ "pushq %rax \n\t"
+ "pushq %rbx \n\t"
+ "pushq %rdx \n\t"
+ "pushq %r12 \n\t"
+ "pushq %r9 \n\t"
+ "movq %rsi, %r12 \n\t"
+ "movq %rdi, %r9 \n\t"
+ "movl 4(%r9),%ebx \n\t"
+ "movl 8(%r9),%ecx \n\t"
+ "movl 12(%r9),%edx \n\t"
+ "movl 16(%r9),%esi \n\t"
+ "movl 20(%r9),%edi \n\t"
+ "movl (%r9),%eax \n\t"
+ "call *%r12 \n\t"
+ "pushfq \n\t"
+ "popq %r12 \n\t"
+ "popfq \n\t"
+ "movl %eax, (%r9) \n\t"
+ "movl %ebx, 4(%r9) \n\t"
+ "movl %ecx, 8(%r9) \n\t"
+ "movl %edx, 12(%r9) \n\t"
+ "movl %esi, 16(%r9) \n\t"
+ "movl %edi, 20(%r9) \n\t"
+ "movq %r12, %rax \n\t"
+ "movl %eax, 28(%r9) \n\t"
+ "popq %r9 \n\t"
+ "popq %r12 \n\t"
+ "popq %rdx \n\t"
+ "popq %rbx \n\t"
+ "popq %rax \n\t"
+ "leave \n\t" "ret");
+#endif
+
+#ifdef CONFIG_X86_32
+ asm("pushl %ebp \n\t"
+ "movl %esp, %ebp \n\t"
+ "pusha \n\t"
+ "pushf \n\t"
+ "push %es \n\t"
+ "push %ds \n\t"
+ "pop %es \n\t"
+ "movl 8(%ebp),%eax \n\t"
+ "movl 4(%eax),%ebx \n\t"
+ "movl 8(%eax),%ecx \n\t"
+ "movl 12(%eax),%edx \n\t"
+ "movl 16(%eax),%esi \n\t"
+ "movl 20(%eax),%edi \n\t"
+ "movl (%eax),%eax \n\t"
+ "push %cs \n\t"
+ "call *12(%ebp) \n\t"
+ "pushf \n\t"
+ "pushl %eax \n\t"
+ "movl 8(%ebp),%eax \n\t"
+ "movl %ebx,4(%eax) \n\t"
+ "movl %ecx,8(%eax) \n\t"
+ "movl %edx,12(%eax) \n\t"
+ "movl %esi,16(%eax) \n\t"
+ "movl %edi,20(%eax) \n\t"
+ "movw %ds,24(%eax) \n\t"
+ "movw %es,26(%eax) \n\t"
+ "popl %ebx \n\t"
+ "movl %ebx,(%eax) \n\t"
+ "popl %ebx \n\t"
+ "movl %ebx,28(%eax) \n\t"
+ "pop %es \n\t"
+ "popf \n\t"
+ "popa \n\t"
+ "leave \n\t" "ret");
+#endif
+}
+
+/*
+ * smbios_get_record
+ *
+ * Routine Description:
+ * This function will get the next record in the SMB Tables
+ *
+ * Return Value:
+ * 1 : SUCCESS - if record found
+ * 0 : FAILURE - if record not found
+ */
+int smbios_get_record(unsigned char **pp_record, void *smbios_table_ptr)
+{
+ unsigned char *buffer;
+ struct smbios_header *header_ptr;
+
+ /*
+ * if pp_record is NULL, find the first record
+ */
+ if (*pp_record == 0) {
+ *pp_record = smbios_table_ptr;
+ buffer = smbios_table_ptr;
+ } else {
+ header_ptr = (struct smbios_header *) * pp_record;
+ buffer = *pp_record;
+
+ /*
+ * skip over the structure itself
+ */
+ *pp_record += header_ptr->byte_length;
+
+ buffer += header_ptr->byte_length;
+
+ /*
+ * skip over any strings following the structure
+ */
+ while (*buffer || *(buffer + 1))
+ buffer++;
+
+ /*
+ * skip over the double NULL characters
+ */
+ buffer += 2;
+ }
+ if (buffer >= ((unsigned char *)smbios_table_ptr + tbl_len))
+ return 0;
+
+ *pp_record = buffer;
+ return 1;
+}
+
+/*
+ * smbios_get_record_by_type
+ *
+ * Routine Description:
+ * This function will get a record in the SMB Table by the type specified
+ *
+ * Return Value:
+ * 1 : SUCCESS - if record found
+ * 0 : FAILURE - if record not found
+ */
+int smbios_get_record_by_type(unsigned char type, unsigned short copy,
+ void **pp_record, void *smbios_table_ptr)
+{
+ unsigned char *save_state_ptr = NULL;
+ struct smbios_header *header_ptr;
+
+ while (smbios_get_record(&save_state_ptr, smbios_table_ptr)) {
+ header_ptr = (struct smbios_header *) save_state_ptr;
+ if (header_ptr->byte_type == type) {
+ if (copy == 0) {
+ *pp_record = (void *)save_state_ptr;
+ return 1;
+ } else
+ copy--;
+ }
+ }
+ return 0;
+}
+
+/*
+ * smbios_get_64bit_cru_info
+ *
+ * Routine Description:
+ * This routine will collect the 64bit CRU entry point from SMBIOS
+ * table.
+ *
+ * Return Value:
+ * 0 : SUCCESS
+ * <0 : FAILURE
+ */
+int __devinit smbios_get_64bit_cru_info(void)
+{
+ unsigned short copy;
+ unsigned char *record_ptr = NULL;
+ struct smbios_cru64_info *smbios_cru64_ptr;
+ unsigned long cru_physical_address;
+ void *smbios_table_ptr;
+ int retval = -ENOMEM;
+
+ smbios_table_ptr = ioremap((unsigned int)tbl_addr, tbl_len);
+ if (!smbios_table_ptr)
+ return retval;
+
+ copy = 0;
+ while (smbios_get_record_by_type
+ (SMBIOS_CRU64_INFORMATION, copy, (void *)&record_ptr,
+ smbios_table_ptr)) {
+ /*
+ * In the event the ROM has a bug, let's print this
+ * out and quit before we get into trouble.
+ */
+ if (!record_ptr) {
+ printk(KERN_WARNING
+ "hpwdt: smbios: Missing SMBIOS_CRU64_INFO!\n");
+ retval = -ENODEV;
+ break;
+ }
+ smbios_cru64_ptr = (struct smbios_cru64_info *) record_ptr;
+ if (smbios_cru64_ptr->signature == CRU_BIOS_SIGNATURE_VALUE) {
+
+ cru_physical_address =
+ smbios_cru64_ptr->physical_address +
+ smbios_cru64_ptr->double_offset;
+ gpVirtRomCRUAddr =
+ ioremap((unsigned long)cru_physical_address,
+ smbios_cru64_ptr->double_length);
+ if (gpVirtRomCRUAddr)
+ retval = 0;
+ break;
+ }
+ }
+
+ iounmap(smbios_table_ptr);
+ return retval;
+}
+
+/*
+ * smbios_detect
+ *
+ * Routine Description:
+ * This function parses SMBIOS to retrieve the 64 bit CRU Service.
+ *
+ * Return Value:
+ * 0 : SUCCESS
+ * <0 : FAILURE
+ */
+int smbios_detect(void)
+{
+ unsigned char *p_virtual_rom;
+ unsigned char *p;
+ struct smbios_entry_point *pEPS;
+ int retval = -ENOMEM;
+
+ /*
+ * Search from 0x0f0000 through 0x0fffff, inclusive.
+ */
+ p_virtual_rom = ioremap(PCI_ROM_BASE1, ROM_SIZE);
+ if (!p_virtual_rom)
+ return retval;
+
+ for (p = p_virtual_rom; p < p_virtual_rom + ROM_SIZE; p += 16) {
+ pEPS = (struct smbios_entry_point *) p;
+ if ((strncmp((char *)pEPS->anchor_string, "_SM_",
+ sizeof(pEPS->anchor_string))) == 0) {
+ tbl_addr = pEPS->table_address;
+ tbl_len = pEPS->table_length;
+ retval = 0;
+ break;
+ }
+ }
+ iounmap(p_virtual_rom);
+ return retval;
+}
+
+/*
+ * cru_detect
+ *
+ * Routine Description:
+ * This function uses the 32-bit BIOS Service Directory record to
+ * search for a $CRU record.
+ *
+ * Return Value:
+ * 0 : SUCCESS
+ * <0 : FAILURE
+ */
+int __devinit cru_detect(void)
+{
+ unsigned long cru_physical_address;
+ unsigned long cru_length;
+ unsigned long physical_bios_base = 0;
+ unsigned long physical_bios_offset = 0;
+ int retval = -ENODEV;
+
+ cmn_regs.u1.reax = CRU_BIOS_SIGNATURE_VALUE;
+
+ asminline_call(&cmn_regs, (unsigned long *)gpBios32EntryPoint);
+
+ if (cmn_regs.u1.ral != 0) {
+ printk(KERN_WARNING
+ "hpwdt: Call succeeded but with an error: 0x%x\n",
+ cmn_regs.u1.ral);
+ } else {
+ physical_bios_base = cmn_regs.u2.rebx;
+ physical_bios_offset = cmn_regs.u4.redx;
+ cru_length = cmn_regs.u3.recx;
+ cru_physical_address =
+ physical_bios_base + physical_bios_offset;
+
+ /* If the values look OK, then map it in. */
+ if ((physical_bios_base + physical_bios_offset)) {
+ gpVirtRomCRUAddr =
+ ioremap(cru_physical_address, cru_length);
+ if (gpVirtRomCRUAddr)
+ retval = 0;
+ }
+
+ printk(KERN_DEBUG "hpwdt: CRU Base Address: 0x%lx\n",
+ physical_bios_base);
+ printk(KERN_DEBUG "hpwdt: CRU Offset Address: 0x%lx\n",
+ physical_bios_offset);
+ printk(KERN_DEBUG "hpwdt: CRU Length: 0x%lx\n",
+ cru_length);
+ printk(KERN_DEBUG "hpwdt: CRU Mapped Address: 0x%x\n",
+ (unsigned int)&gpVirtRomCRUAddr);
+ }
+ iounmap(gpBios32Map);
+ return retval;
+}
+
+/*
+ * valid_checksum
+ */
+static int valid_checksum(void *header_ptr, unsigned char size)
+{
+ unsigned char i;
+ unsigned char check_sum = 0;
+ unsigned char *ptr = header_ptr;
+
+ /*
+ * calculate checksum of size bytes. This should add up
+ * to zero if we have a valid header.
+ */
+ for (i = 0; i < size; i++, ptr++)
+ check_sum = (check_sum + (*ptr));
+
+ return ((check_sum == 0) && (size > 0));
+}
+
+/*
+ * check_for_bios32_support
+ *
+ * Routine Description:
+ * This function determine if a pointer is pointing to the
+ * 32 bit BIOS Directory Services entry in ROM space.
+ *
+ * Return Value:
+ * 1 - if found the correct signature
+ * 0 - unable to find _32_
+ */
+static int check_for_bios32_support(struct bios32_service_dir *bios_32_ptr)
+{
+ /*
+ * Search for signature by checking equal to the swizzled value
+ * instead of calling another routine to perform a strcmp.
+ */
+ if (bios_32_ptr->signature == PCI_BIOS32_SD_VALUE) {
+ if (valid_checksum((void *)bios_32_ptr,
+ ((unsigned char)(bios_32_ptr->length *
+ PCI_BIOS32_PARAGRAPH_LEN))))
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * find_32bit_bios_sd
+ *
+ * Routine Description:
+ * This function find the 32-bit BIOS Service Directory
+ *
+ * Return Value:
+ * 0 : SUCCESS
+ * <0 : FAILURE
+ */
+int __devinit find_32bit_bios_sd(void)
+{
+ struct bios32_service_dir *bios_32_data_ptr;
+ unsigned long map_entry, map_offset;
+ void *pRomVirtAddr;
+ unsigned char *p;
+ int retval = -ENOMEM;
+
+ pRomVirtAddr = ioremap(PCI_ROM_BASE1, ROM_SIZE);
+ if (!pRomVirtAddr) {
+ printk(KERN_WARNING "hpwdt: Unable to map in BIOS ROM.\n");
+ return retval;
+ }
+
+ /*
+ * BIOS32 spec indicates that the signature will be
+ * paragraph-aligned, so we'll skip by 16 bytes each pass
+ * through the loop.
+ */
+ for (p = pRomVirtAddr;
+ p < ((unsigned char *)pRomVirtAddr + ROM_SIZE); p += 16) {
+
+ if (!check_for_bios32_support((struct bios32_service_dir *) p))
+ continue;
+
+ /* Found a Service Directory. */
+ bios_32_data_ptr = (struct bios32_service_dir *) p;
+
+ /*
+ * According to the spec, we're looking for the
+ * first 4KB-aligned address below the entrypoint
+ * listed in the header. The Service Directory code
+ * is guaranteed to occupy no more than 2 4KB pages.
+ */
+ map_entry = bios_32_data_ptr->entry_point & ~(PAGE_SIZE - 1);
+ map_offset = bios_32_data_ptr->entry_point - map_entry;
+
+ gpBios32Map = ioremap(map_entry, (2 * PAGE_SIZE));
+
+ if (gpBios32Map) {
+ /* This will be unmapped in cru_detect. */
+ gpBios32EntryPoint = gpBios32Map + map_offset;
+ retval = 0;
+ }
+ break;
+ }
+ iounmap(pRomVirtAddr);
+ return retval;
+}
+
+static int hpwdt_pretimeout(struct notifier_block *nb, unsigned long ulReason,
+ void *dummy)
+{
+ static unsigned long rom_pl;
+ static int die_nmi_called;
+
+ if (ulReason == DIE_NMI || ulReason == DIE_NMI_IPI) {
+ spin_lock_irqsave(&rom_lock, rom_pl);
+ if (!die_nmi_called)
+ asminline_call(&cmn_regs, gpVirtRomCRUAddr);
+ die_nmi_called = 1;
+ spin_unlock_irqrestore(&rom_lock, rom_pl);
+ if (cmn_regs.u1.ral == 0) {
+ printk(KERN_WARNING "hpwdt: An NMI occurred, "
+ "but unable to determine source.\n");
+ } else {
+ panic("An NMI occurred, please see the Integrated "
+ "Management Log for details.\n");
+ }
+ }
+ return NOTIFY_STOP;
+}
+
+/*
+ * Refresh the timer.
+ */
+static void hpwdt_ping(void)
+{
+ writew(reload, hpwdt_timer_reg);
+}
+
+static int hpwdt_open(struct inode *inode, struct file *file)
+{
+ reload = ((soft_margin * 60) * 1000) / 128;
+ /*
+ * Setting this bit is irreversible; once enabled, there is
+ * no way to disable the watchdog.
+ */
+ writew(0x85, hpwdt_timer_con);
+ return 0;
+}
+
+/*
+ * Shut off the timer.
+ * Note: if we really have enabled the watchdog, there
+ * is no way to turn off.
+ */
+static int hpwdt_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static ssize_t
+hpwdt_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+ /*
+ * Refresh the timer.
+ */
+ if (len)
+ hpwdt_ping();
+
+ return len;
+}
+
+static struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT,
+ .identity = "hp iLO2 HW Watchdog Timer",
+};
+
+static long
+hpwdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = -ENOIOCTLCMD;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = 0;
+ if (copy_to_user((void *)arg, &ident, sizeof(ident)))
+ ret = -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ ret = 0;
+ break;
+
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(0, (int *)arg);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ hpwdt_ping();
+ ret = 0;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(new_margin, (int *)arg);
+ if (ret)
+ break;
+
+ /* Arbitrary, can't find the card's limits */
+ if (new_margin < 2 || new_margin > 30) {
+ ret = -EINVAL;
+ break;
+ }
+
+ soft_margin = new_margin;
+ printk(KERN_DEBUG
+ "hpwdt: New timer passed in is %d minutes.\n",
+ new_margin);
+ reload = ((soft_margin * 60) * 1000) / 128;
+ hpwdt_ping();
+ /* Fall */
+ case WDIOC_GETTIMEOUT:
+ ret = put_user(new_margin, (int *)arg);
+ break;
+ }
+ return ret;
+}
+
+static struct file_operations hpwdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = hpwdt_write,
+ .unlocked_ioctl = hpwdt_ioctl,
+ .open = hpwdt_open,
+ .release = hpwdt_release,
+};
+
+static struct miscdevice hpwdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &hpwdt_fops,
+};
+
+static struct notifier_block die_notifier = {
+ .notifier_call = hpwdt_pretimeout,
+ .priority = 0x7FFFFFFF,
+};
+
+static int __devinit hpwdt_init_one(struct pci_dev *dev,
+ const struct pci_device_id *ent)
+{
+ int retval;
+
+ if (pci_enable_device(dev)) {
+ dev_warn(&dev->dev,
+ "Not possible to enable PCI Device: 0x%x:0x%x.\n",
+ ent->vendor, ent->device);
+ return -ENODEV;
+ }
+ pci_enable = 1;
+
+ /*
+ * First let's find out if we are on an iLO2 server. We will
+ * not run on a legacy ASM box.
+ */
+ if (dev->subsystem_vendor != PCI_VENDOR_ID_HP) {
+ dev_warn(&dev->dev,
+ "This server does not have an iLO2 ASIC.\n");
+ pci_disable_device(dev);
+ return -ENODEV;
+ }
+
+ retval = misc_register(&hpwdt_miscdev);
+ if (retval < 0) {
+ pci_disable_device(dev);
+ return retval;
+ }
+
+ p_mem_addr = ioremap((unsigned long)pci_resource_start(dev, 1), 0x80);
+ if (!p_mem_addr) {
+ retval = -ENOMEM;
+ goto error_out;
+ }
+ hpwdt_timer_reg = (p_mem_addr + 0x70);
+ hpwdt_timer_con = (p_mem_addr + 0x72);
+
+#ifndef CONFIG_X86_64
+ /*
+ * Let's try to map in the ROM for 32 bit Operating Systems because
+ * we need get the CRU Service through the 32 Bit BIOS SD.
+ * This is not the case for 64 bit Operating Systems where we get
+ * that service through SMBIOS.
+ */
+ retval = find_32bit_bios_sd();
+ if (retval < 0) {
+ dev_warn(&dev->dev,
+ "No 32 Bit BIOS Service Directory found.\n");
+ goto error_out;
+ }
+
+ retval = cru_detect();
+ if (retval < 0) {
+ dev_warn(&dev->dev,
+ "Unable to detect 32 Bit CRU Service.\n");
+ goto error_out;
+ }
+#else
+ retval = smbios_detect();
+ if (retval < 0) {
+ dev_warn(&dev->dev, "No 64 Bit SMBIOS found.\n");
+ goto error_out;
+ }
+
+ retval = smbios_get_64bit_cru_info();
+ if (retval < 0) {
+ dev_warn(&dev->dev,
+ "Unable to detect the 64 Bit CRU Service.\n");
+ goto error_out;
+ }
+#endif
+ /*
+ * We know this is the only CRU call we need to make so lets keep as
+ * few instructions as possible once the NMI comes in.
+ */
+ memset(&cmn_regs, 0, sizeof(struct cmn_registers));
+ cmn_regs.u1.rah = 0x0D;
+ cmn_regs.u1.ral = 0x02;
+
+ retval = register_die_notifier(&die_notifier);
+ if (retval != 0) {
+ dev_warn(&dev->dev,
+ "Unable to register a die notifier.\n");
+ goto error_out;
+ }
+ die_reg = 1;
+
+ new_margin = soft_margin;
+ printk("hp Watchdog Timer Driver: 1.00, timer margin: %d minutes\n",
+ new_margin);
+
+ return 0;
+error_out:
+ dev_warn(&dev->dev, "NMI support is not possiible.\n");
+ misc_deregister(&hpwdt_miscdev);
+ if (pci_enable)
+ pci_disable_device(dev);
+ if (p_mem_addr)
+ iounmap(p_mem_addr);
+ if (gpVirtRomCRUAddr)
+ iounmap(gpVirtRomCRUAddr);
+ return retval;
+}
+
+static void __devexit hpwdt_exit(struct pci_dev *dev)
+{
+ if (die_reg)
+ unregister_die_notifier(&die_notifier);
+
+ if (pci_enable)
+ pci_disable_device(dev);
+
+ misc_deregister(&hpwdt_miscdev);
+ if (p_mem_addr)
+ iounmap(p_mem_addr);
+ if (gpVirtRomCRUAddr)
+ iounmap(gpVirtRomCRUAddr);
+}
+
+static struct pci_driver hpwdt_driver = {
+ .name = "hpwdt",
+ .id_table = hpwdt_devices,
+ .probe = hpwdt_init_one,
+ .remove = __devexit_p(hpwdt_exit),
+};
+
+static void __exit hpwdt_cleanup(void)
+{
+ pci_unregister_driver(&hpwdt_driver);
+}
+
+static int __init hpwdt_init(void)
+{
+ return pci_register_driver(&hpwdt_driver);
+}
+
+MODULE_AUTHOR("Tom Mingarelli");
+MODULE_DESCRIPTION("hp watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+module_param(soft_margin, int, 0);
+MODULE_PARM_DESC(soft_margin, "Watchdog timeout in minutes");
+
+module_init(hpwdt_init);
+module_exit(hpwdt_cleanup);
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Re: [HP ProLiant WatchDog driver] hpwdt HP WatchDog Patch <resend> [ In reply to ]
Hi Thomas,

I reviewed yor code and have ome remarks:
* All watchdog device drivers should work with seconds. Your driver works
with minutes. Please change to seconds.
* The misc_register function (which opens your driver towards user-space)
should be the last iregister-action you take into the pci probe function.
(So just before you do the printk where you say that the driver has been
initialized).
* The open and release functions should make sure that /dev/watchdog can
only be opened once.
* /dev/watchdog is a VFS (Virtual File System) so your open function should
return with "return nonseekable_open(inode, file);".
* please put the code to start the watchdog in a seperate function (this
will make it easier to migrate your driver to the generic watchdog model
in the future).
* If you watchdog can't be stopped then you should use a timer to make sure
that when /dev/watchdog is closed, that you continue to keepalive/ping the
watchdog device. (please see mixcomwd.c (this device has the same problem)).
* please put the code to change the timeout in a seperate function.
* the default return value for the ioctl calls should be -ENOTTY (instead of
-ENOIOCTLCMD).
* the GETSTATUS call should also have a "put_user(0, (int *)arg);".
* if possible please add sparse testing code into the ioctl handling.
* Personnaly: I would rather see the generic smbios code somewhere in the
arch/x86/ directory. We will probably need that also in other (non-watchdog)
drivers in the future.

Greetings,
Wim.

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/