static ssize_t iTCO_wdt_trigger_write(struct file *file, const char __user *buff, size_t count, loff_t *ppos) { iTCO_wdt_last_kick(1); return 0; }
static long iTCO_wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int new_options, retval = -EINVAL; int new_heartbeat; void __user *argp = (void __user *)arg; int __user *p = argp; static const struct watchdog_info ident = { .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, .firmware_version = 0, .identity = DRV_NAME, }; switch (cmd) { case WDIOC_GETSUPPORT: return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: return put_user(0, p); case WDIOC_SETOPTIONS: { if (get_user(new_options, p)) return -EFAULT; if (new_options & WDIOS_DISABLECARD) { iTCO_wdt_stop(); retval = 0; } if (new_options & WDIOS_ENABLECARD) { iTCO_wdt_keepalive(); iTCO_wdt_start(); retval = 0; } return retval; } case WDIOC_KEEPALIVE: iTCO_wdt_keepalive(); return 0; case WDIOC_SETTIMEOUT: { if (get_user(new_heartbeat, p)) return -EFAULT; if (iTCO_wdt_set_heartbeat(new_heartbeat)) return -EINVAL; iTCO_wdt_keepalive(); /* Fall */ } case WDIOC_GETTIMEOUT: return put_user(heartbeat, p); case WDIOC_GETTIMELEFT: { int time_left; if (iTCO_wdt_get_timeleft(&time_left)) return -EINVAL; return put_user(time_left, p); } default: return -ENOTTY; } } /* * Kernel Interfaces */ static const struct file_operations iTCO_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = iTCO_wdt_write, .unlocked_ioctl = iTCO_wdt_ioctl, .open = iTCO_wdt_open, .release = iTCO_wdt_release, }; static struct miscdevice iTCO_wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &iTCO_wdt_fops, }; /* * Reboot notifier */ static int TCO_reboot_notifier(struct notifier_block *this, unsigned long code, void *another_unused) { if (code == SYS_HALT || code == SYS_POWER_OFF) { iTCO_wdt_set_reset_type(TCO_POLICY_HALT); } iTCO_wdt_last_kick(5); #ifdef CONFIG_DEBUG_FS if (iTCO_wdt_private.panic_reboot_notifier) { BUG(); } #endif return NOTIFY_DONE; } static irqreturn_t tco_irq_handler(int irq, void *arg) { pr_warn("[SHTDWN] %s, WATCHDOG TIMEOUT HANDLER!\n", __func__); /* reduce the timeout to the minimum, but sufficient for tracing */ bypass_keepalive = false; iTCO_wdt_last_kick(15); trigger_all_cpu_backtrace(); /* Let the watchdog reset the board */ panic_timeout = 0; panic("Kernel Watchdog"); /* This code should not be reached */ return IRQ_HANDLED; } static ssize_t shutdown_ongoing_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { iTCO_wdt_set_reset_type(TCO_POLICY_HALT); return size; }
static long iTCO_wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int new_options, retval = -EINVAL; int new_heartbeat; void __user *argp = (void __user *)arg; int __user *p = argp; static const struct watchdog_info ident = { .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, .firmware_version = 0, .identity = DRV_NAME, }; switch (cmd) { case WDIOC_GETSUPPORT: return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: return put_user(0, p); case WDIOC_SETOPTIONS: { if (get_user(new_options, p)) return -EFAULT; if (new_options & WDIOS_DISABLECARD) { iTCO_wdt_stop(); retval = 0; } if (new_options & WDIOS_ENABLECARD) { iTCO_wdt_keepalive(); iTCO_wdt_start(); retval = 0; } return retval; } case WDIOC_KEEPALIVE: iTCO_wdt_keepalive(); return 0; case WDIOC_SETTIMEOUT: { if (get_user(new_heartbeat, p)) return -EFAULT; if (iTCO_wdt_set_heartbeat(new_heartbeat)) return -EINVAL; iTCO_wdt_keepalive(); /* Fall */ } case WDIOC_GETTIMEOUT: return put_user(heartbeat, p); case WDIOC_GETTIMELEFT: { int time_left; if (iTCO_wdt_get_timeleft(&time_left)) return -EINVAL; return put_user(time_left, p); } default: return -ENOTTY; } } /* * Kernel Interfaces */ static const struct file_operations iTCO_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = iTCO_wdt_write, .unlocked_ioctl = iTCO_wdt_ioctl, .open = iTCO_wdt_open, .release = iTCO_wdt_release, }; static struct miscdevice iTCO_wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &iTCO_wdt_fops, }; /* * Reboot notifier */ static int TCO_reboot_notifier(struct notifier_block *this, unsigned long code, void *another_unused) { if (code == SYS_HALT || code == SYS_POWER_OFF) { iTCO_wdt_set_reset_type(TCO_POLICY_HALT); } iTCO_wdt_last_kick(5); #ifdef CONFIG_DEBUG_FS if (iTCO_wdt_private.panic_reboot_notifier) { BUG(); } #endif return NOTIFY_DONE; } static irqreturn_t tco_irq_handler(int irq, void *arg) { unsigned long val32; pr_warn("[SHTDWN] %s, WATCHDOG TIMEOUT HANDLER!\n", __func__); /* reduce the timeout to the minimum, but sufficient for tracing */ iTCO_wdt_set_heartbeat(15); iTCO_wdt_keepalive(); trigger_all_cpu_backtrace(); panic("Kernel Watchdog"); /* This code should not be reached */ return IRQ_HANDLED; } /* * Init & exit routines */ static int iTCO_wdt_init(struct pci_dev *pdev, const struct pci_device_id *ent, struct platform_device *dev) { int ret; u32 base_address; unsigned long val32; /* * Find the ACPI/PM base I/O address which is the base * for the TCO registers (TCOBASE=ACPIBASE + 0x60) * ACPIBASE is bits [15:7] from 0x40-0x43 */ pci_read_config_dword(pdev, 0x40, &base_address); base_address &= 0x0000ff80; if (base_address == 0x00000000) { /* Something's wrong here, ACPIBASE has to be set */ pr_err("failed to get TCOBASE address, device disabled by hardware/BIOS\n"); return -ENODEV; } iTCO_wdt_private.iTCO_version = iTCO_chipset_info[ent->driver_data].iTCO_version; iTCO_wdt_private.ACPIBASE = base_address; iTCO_wdt_private.pdev = pdev; pci_read_config_dword(pdev, 0x44, &pmc_base_address); pmc_base_address &= 0xFFFFFE00; /* * Disable watchdog on command-line demand */ if (strstr(saved_command_line, "disable_kernel_watchdog=1")) { pr_warn("disable_kernel_watchdog=1 watchdog will not be started\n"); iTCO_wdt_private.enable = false; /* Set the NO_REBOOT bit to prevent later reboots */ iTCO_wdt_set_NO_REBOOT_bit(); /* Ensure Wdt is well stopped in case started by IAFW */ iTCO_wdt_stop(); } else { iTCO_wdt_private.enable = true; /* Check chipset's NO_REBOOT bit */ if (iTCO_wdt_unset_NO_REBOOT_bit()) { pr_err("unable to reset NO_REBOOT flag, device disabled by hardware/BIOS\n"); ret = -ENODEV; /* Cannot reset NO_REBOOT bit */ goto out; } } /* The TCO logic uses the TCO_EN bit in the SMI_EN register */ if (!request_region(SMI_EN, 4, "iTCO_wdt")) { pr_err("I/O address 0x%04lx already in use, device disabled\n", SMI_EN); ret = -EIO; goto out; } if (!request_region(SMI_STS, 4, "iTCO_wdt")) { pr_err("I/O address 0x%04lx already in use, device disabled\n", SMI_STS); ret = -EIO; goto unreg_smi_sts; } if (turn_SMI_watchdog_clear_off >= iTCO_wdt_private.iTCO_version) { /* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI# */ val32 = inl(SMI_EN); val32 &= ~TCO_EN_BIT; /* Turn off TCO watchdog timer */ outl(val32, SMI_EN); } /* The TCO I/O registers reside in a 32-byte range pointed to by the TCOBASE value */ if (!request_region(TCOBASE, 0x20, "iTCO_wdt")) { pr_err("I/O address 0x%04lx already in use, device disabled\n", TCOBASE); ret = -EIO; goto unreg_smi_en; } pr_info("Found a %s TCO device (Version=%d, TCOBASE=0x%04lx)\n", iTCO_chipset_info[ent->driver_data].name, iTCO_chipset_info[ent->driver_data].iTCO_version, TCOBASE); /* Check that the heartbeat value is within it's range; if not reset to the default */ if (iTCO_wdt_set_heartbeat(heartbeat)) { iTCO_wdt_set_heartbeat(WATCHDOG_HEARTBEAT); pr_info("timeout value out of range, using %d\n", heartbeat); } ret = misc_register(&iTCO_wdt_miscdev); if (ret != 0) { pr_err("cannot register miscdev on minor=%d (err=%d)\n", WATCHDOG_MINOR, ret); goto unreg_region; } pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", heartbeat, nowayout); /* Reset OS policy */ iTCO_wdt_set_reset_type(TCO_POLICY_NORM); ret = acpi_register_gsi(NULL, TCO_WARNING_IRQ, ACPI_EDGE_SENSITIVE, ACPI_ACTIVE_HIGH); if (ret < 0) { pr_err("failed to configure TCO warning IRQ %d\n", TCO_WARNING_IRQ); goto misc_unreg; } ret = request_irq(TCO_WARNING_IRQ, tco_irq_handler, 0, "tco_watchdog", NULL); if (ret < 0) { pr_err("failed to request TCO warning IRQ %d\n", TCO_WARNING_IRQ); goto gsi_unreg; } /* Clear old TCO timeout status */ val32 = TCO_TIMEOUT_BIT | SECOND_TO_STS_BIT; outl(val32, TCO1_STS); /* Clear the SMI status */ outl(TCO_STS_BIT, SMI_STS); /* Enable SMI for TCO */ val32 = inl(SMI_EN); val32 |= TCO_EN_BIT; outl(val32, SMI_EN); /* then ensure that PMC is ready to handle next SMI */ val32 |= EOS_BIT; outl(val32, SMI_EN); reboot_notifier.notifier_call = TCO_reboot_notifier; reboot_notifier.priority = 1; ret = register_reboot_notifier(&reboot_notifier); if (ret) /* We continue as reboot notifier is not critical for * watchdog */ pr_err("cannot register reboot notifier %d\n", ret); return 0; gsi_unreg: acpi_unregister_gsi(TCO_WARNING_IRQ); misc_unreg: misc_deregister(&iTCO_wdt_miscdev); unreg_region: release_region(TCOBASE, 0x20); unreg_smi_sts: release_region(SMI_STS, 4); unreg_smi_en: release_region(SMI_EN, 4); out: iTCO_wdt_private.ACPIBASE = 0; return ret; }