--- zzzz-none-000/linux-4.9.279/drivers/watchdog/iTCO_wdt.c 2021-08-08 06:38:54.000000000 +0000 +++ puma7-atom-6591-750/linux-4.9.279/drivers/watchdog/iTCO_wdt.c 2023-02-08 11:43:42.000000000 +0000 @@ -1,7 +1,7 @@ /* * intel TCO Watchdog Driver * - * (c) Copyright 2006-2011 Wim Van Sebroeck . + * (c) Copyright 2006-2016 Wim Van Sebroeck . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -67,6 +67,7 @@ #include /* For copy_to_user/put_user/... */ #include /* For inb/outb/... */ #include +#include #include "iTCO_vendor.h" @@ -86,6 +87,10 @@ #define TCO2_CNT (TCOBASE + 0x0a) /* TCO2 Control Register */ #define TCOv2_TMR (TCOBASE + 0x12) /* TCOv2 Timer Initial Value */ +#define TCO1_CNT_OS_POLICY_MASK ((1<<20) | (1<<21)) +#define TCO_TVAL_MASK 0x3ff /* Current count of the timer mask */ +#define TCO_TVAL_RELOAD 0x1 /* Writing any value reloads the timer */ + /* internal variables */ static struct { /* this is private data for the iTCO_wdt device */ /* TCO version/generation */ @@ -105,10 +110,12 @@ struct pci_dev *pdev; /* whether or not the watchdog has been suspended */ bool suspended; + /* total alive time in seconds */ + unsigned long alive_time; } iTCO_wdt_private; /* module parameters */ -#define WATCHDOG_TIMEOUT 30 /* 30 sec default heartbeat */ +#define WATCHDOG_TIMEOUT 180 /* AVM: 180 sec default heartbeat */ static int heartbeat = WATCHDOG_TIMEOUT; /* in seconds */ module_param(heartbeat, int, 0); MODULE_PARM_DESC(heartbeat, "Watchdog timeout in seconds. " @@ -126,6 +133,15 @@ MODULE_PARM_DESC(turn_SMI_watchdog_clear_off, "Turn off SMI clearing watchdog (depends on TCO-version)(default=1)"); +static bool stop_timer = !IS_ENABLED(CONFIG_ITCO_WDT_KEEP_RUNNING_ON_PROBE); +module_param(stop_timer, bool, 0444); +MODULE_PARM_DESC(stop_timer, + "Stop the watchdog timer once probed, until a client connects"); + +static int first_ping_by_wd_daemon = 1; + +static atomic_t itco_ready = ATOMIC_INIT(0); + /* * Some TCO specific functions */ @@ -267,21 +283,80 @@ return 0; } +/* The AVM lockless ping variant */ +static int iTCO_wdt_hw_ping(struct watchdog_device *wd_dev) +{ + static atomic_t lock = ATOMIC_INIT(0); + static bool first_ping = true; + + if (!atomic_read(&itco_ready)) + return -ENODEV; + + rmb(); + if (atomic_xchg(&lock, 1)) + return -EBUSY; + + /* We are targeting the puma7-atom, which has an iTCO version 3 */ + + rmb(); + if (first_ping) { + u32 val32; + + /* Clear the OS_POLICY bits to indicate that the + * kernel booted fine. */ + val32 = inl(TCO1_CNT); + val32 &= ~TCO1_CNT_OS_POLICY_MASK; + outl(val32, TCO1_CNT); + + first_ping = false; + } + + /* Not sure if all the barriers are needed... */ + wmb(); + outw(TCO_TVAL_RELOAD, TCO_RLD); + wmb(); + + atomic_set(&lock, 0); + wmb(); + + return 0; +} + static int iTCO_wdt_ping(struct watchdog_device *wd_dev) { + unsigned int time_left = 0; + spin_lock(&iTCO_wdt_private.io_lock); iTCO_vendor_pre_keepalive(iTCO_wdt_private.smi_res, wd_dev->timeout); - /* Reload the timer by writing to the TCO Timer Counter register */ - if (iTCO_wdt_private.iTCO_version >= 2) { - outw(0x01, TCO_RLD); + if (iTCO_wdt_private.iTCO_version >= 3) { + /* read the time left */ + time_left = inw(TCO_RLD); + /* reload the timer */ + outw(TCO_TVAL_RELOAD, TCO_RLD); + time_left &= TCO_TVAL_MASK; + iTCO_wdt_private.alive_time += + (wd_dev->timeout - ticks_to_seconds(time_left)); + if(first_ping_by_wd_daemon) { + u32 val32; + /* Clear the OS_POLICY bits to indicate that the + * kernel booted fine. */ + val32 = inl(TCO1_CNT); + val32 &= ~TCO1_CNT_OS_POLICY_MASK; + outl(val32, TCO1_CNT); + first_ping_by_wd_daemon = 0; + } + } else if (iTCO_wdt_private.iTCO_version == 2) { + /* Reload the timer by writing to the TCO + Timer Counter register */ + outw(TCO_TVAL_RELOAD, TCO_RLD); } else if (iTCO_wdt_private.iTCO_version == 1) { /* Reset the timeout status bit so that the timer * needs to count down twice again before rebooting */ outw(0x0008, TCO1_STS); /* write 1 to clear bit */ - outb(0x01, TCO_RLD); + outb(TCO_TVAL_RELOAD, TCO_RLD); } spin_unlock(&iTCO_wdt_private.io_lock); @@ -366,6 +441,49 @@ return time_left; } +static long iTCO_wdt_ioctl(struct watchdog_device *wd_dev, unsigned int cmd, + unsigned long arg) +{ + int ret = -ENOIOCTLCMD; + unsigned int time_left = 0; + unsigned long total_time_alive = 0; + + switch (cmd) { + case WDIOC_GETALIVETIME: /* Get the total alive duration */ + if (iTCO_wdt_private.iTCO_version >= 3) { + spin_lock(&iTCO_wdt_private.io_lock); + /* read the time left */ + time_left = inw(TCO_RLD); + time_left &= TCO_TVAL_MASK; + total_time_alive = iTCO_wdt_private.alive_time + + (wd_dev->timeout - ticks_to_seconds(time_left)); + spin_unlock(&iTCO_wdt_private.io_lock); + ret = put_user(total_time_alive, + (unsigned long __user *)arg); + } + break; + case WDIOC_PINGGETALIVETIME: + /* Ping the WD and get the total alive duration */ + if (iTCO_wdt_private.iTCO_version >= 3) { + spin_lock(&iTCO_wdt_private.io_lock); + /* read the time left */ + time_left = inw(TCO_RLD); + /* reload the timer */ + outw(TCO_TVAL_RELOAD, TCO_RLD); + time_left &= TCO_TVAL_MASK; + total_time_alive = iTCO_wdt_private.alive_time + + (wd_dev->timeout - ticks_to_seconds(time_left)); + iTCO_wdt_private.alive_time = total_time_alive; + spin_unlock(&iTCO_wdt_private.io_lock); + ret = put_user(total_time_alive, + (unsigned long __user *)arg); + } + break; + } + + return ret; +} + /* * Kernel Interfaces */ @@ -383,8 +501,10 @@ .start = iTCO_wdt_start, .stop = iTCO_wdt_stop, .ping = iTCO_wdt_ping, - .set_timeout = iTCO_wdt_set_timeout, - .get_timeleft = iTCO_wdt_get_timeleft, + .avm_hw_ping = iTCO_wdt_hw_ping, + .set_timeout = iTCO_wdt_set_timeout, + .get_timeleft = iTCO_wdt_get_timeleft, + .ioctl = iTCO_wdt_ioctl, }; static struct watchdog_device iTCO_wdt_watchdog_dev = { @@ -425,12 +545,15 @@ static int iTCO_wdt_probe(struct platform_device *dev) { int ret = -ENODEV; - unsigned long val32; + u32 val32; struct itco_wdt_platform_data *pdata = dev_get_platdata(&dev->dev); if (!pdata) goto out; + /* AVM: In .avm_hw_ping, we're currently supporting version 3 only. */ + BUG_ON(pdata->version != 3); + spin_lock_init(&iTCO_wdt_private.io_lock); iTCO_wdt_private.tco_res = @@ -443,6 +566,7 @@ if (!iTCO_wdt_private.smi_res) goto out; + iTCO_wdt_private.alive_time = 0; iTCO_wdt_private.iTCO_version = pdata->version; iTCO_wdt_private.dev = dev; iTCO_wdt_private.pdev = to_pci_dev(dev->dev.parent); @@ -479,8 +603,13 @@ goto unmap_gcs_pmc; } - /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ - iTCO_wdt_set_NO_REBOOT_bit(); + if (stop_timer) { + /* + * Set the NO_REBOOT bit to prevent later reboots, just for + * sure + */ + iTCO_wdt_set_NO_REBOOT_bit(); + } /* The TCO logic uses the TCO_EN bit in the SMI_EN register */ if (!request_region(iTCO_wdt_private.smi_res->start, @@ -517,6 +646,7 @@ case 4: outw(0x0008, TCO1_STS); /* Clear the Time Out Status bit */ outw(0x0002, TCO2_STS); /* Clear SECOND_TO_STS bit */ + outw(0x0004, TCO2_STS); /* Clear BOOT_STS bit */ break; case 3: outl(0x20008, TCO1_STS); @@ -535,8 +665,17 @@ watchdog_set_nowayout(&iTCO_wdt_watchdog_dev, nowayout); iTCO_wdt_watchdog_dev.parent = &dev->dev; - /* Make sure the watchdog is not running */ - iTCO_wdt_stop(&iTCO_wdt_watchdog_dev); + /* Clear the OS_POLICY bits to indicate that the kernel booted fine. */ + if ((iTCO_wdt_private.iTCO_version == 3) && (stop_timer)) { + val32 = inl(TCO1_CNT); + val32 &= ~TCO1_CNT_OS_POLICY_MASK; + outl(val32, TCO1_CNT); + } + + if (stop_timer) { + /* Make sure the watchdog is not running */ + iTCO_wdt_stop(&iTCO_wdt_watchdog_dev); + } /* Check that the heartbeat value is within it's range; if not reset to the default */ @@ -546,18 +685,31 @@ WATCHDOG_TIMEOUT); } + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); + + if (!stop_timer) { + iTCO_wdt_start(&iTCO_wdt_watchdog_dev); + set_bit(WDOG_ACTIVE, &iTCO_wdt_watchdog_dev.status); + pr_info("timer still running"); + } + ret = watchdog_register_device(&iTCO_wdt_watchdog_dev); if (ret != 0) { pr_err("cannot register watchdog device (err=%d)\n", ret); - goto unreg_tco; + goto stop_wdt; } - pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", - heartbeat, nowayout); + smp_mb__before_atomic(); + atomic_inc(&itco_ready); return 0; -unreg_tco: +stop_wdt: + if (!stop_timer) { + iTCO_wdt_stop(&iTCO_wdt_watchdog_dev); + clear_bit(WDOG_ACTIVE, &iTCO_wdt_watchdog_dev.status); + } release_region(iTCO_wdt_private.tco_res->start, resource_size(iTCO_wdt_private.tco_res)); unreg_smi: