* [PATCHv6 0/4] watchdog: Extend kernel API and add early_timeout_sec feature
@ 2015-04-14 7:26 Timo Kokkonen
2015-04-14 7:26 ` [PATCHv6 1/4] watchdog: Extend kernel API to know about HW limitations Timo Kokkonen
` (4 more replies)
0 siblings, 5 replies; 7+ messages in thread
From: Timo Kokkonen @ 2015-04-14 7:26 UTC (permalink / raw)
To: linux-arm-kernel
The watchdog kernel API is quite limited. It has support for providing
generic device handling, but it doesn't really know anything about the
watchdog hardware or its constraints. The watchdog drivers come with a
lot of diversity and their own set of quirks and constraints. Some of
their limitations are not nice for the user space, so the drivers work
around them with all sorts of ad hoc implementations.
One common pattern is to use kernel timers or work queues to allow
longer timeout parameters than the actual hardware supports. To solve
this problem, this patch set extends the kernel watchdog API with a few
parameters that let the core know more about the watchdog HW and take
care about the timeout extending.
The patch set also implements "early_timeout_sec" feature that is very
common on many production systems where early kernel or user space
crashes must lead to a device reset. Traditional watchdog handling
does not allow this as the watchdog is stopped (fully or emulating
stopped state with kernel timers) before user space opens it for the
first time.
The changes are designed to be taken in use one driver at time. If the
driver does not set the new parameters and call
watchdog_init_params(), the watchdog behavior is exactly the same as
before.
In principle this new API makes it possible for the user space to see
every watchdog hardware to behave the same, at least in terms of
watchdog timeouts. Once the API is in, it should be easier to move
even more common behavior out of the driver code to the watchdog core
and make the drivers simpler. This patch set converts at91sam9_wdt to
use the new API.
Please review and give feedback.
Patch revision history:
-v6: Fixed some issued based on feedback from Wenyou Yang. The logic
in watchdog_worker() function is now significantly easier to
read. Several errors with stopping and starting the worker are also
now fixed.
-v5: Re-think the approach to be fully generic. The early_timeout_sec
handling is no longer in the driver but in the watchdog core. As a
result the core needed to gain knowledge about the watchdog
hardware. Appropriate handling is added in the core. The side effect
for this is that drivers using the new extensions can be simplified
a lot and different kinds of watchdog hardware can be made to
behave the same for the user space.
-v4: Binding documentation is now separated completely from the driver
patch. The documentation no longer makes any assumptions about how
the actual implementation is made, it just describes the actual
behavior the driver should implement in order to satisfy the
requirement.
- v3: Rename the property to "early-timeout-sec" and use it as a
timeout value that stops the timer in the atmel driver after the
timeout expires. A watchdog.txt is also introduced for documenting
the common watchdog properties, including now this one and
"timeout-sec" property.
- v2: Rename the property to "enable-early-reset" as the behavior
itself is not atmel specific. This way other drivers are free to
implement same behavior with the same property name.
- v1: Propose property name "atmle,no-early-timer" for disabling the
timer that keeps the atmel watchdog running until user space opens
the device.
Timo Kokkonen (4):
watchdog: Extend kernel API to know about HW limitations
watchdog: Allow watchdog to reset device at early boot
devicetree: Document generic watchdog properties
watchdog: at91sam9_wdt: Convert to use new watchdog core extensions
.../devicetree/bindings/watchdog/watchdog.txt | 20 ++++
drivers/watchdog/at91sam9_wdt.c | 58 ++--------
drivers/watchdog/watchdog_core.c | 124 ++++++++++++++++++++-
drivers/watchdog/watchdog_dev.c | 26 ++++-
include/linux/watchdog.h | 24 ++++
5 files changed, 201 insertions(+), 51 deletions(-)
create mode 100644 Documentation/devicetree/bindings/watchdog/watchdog.txt
--
2.1.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCHv6 1/4] watchdog: Extend kernel API to know about HW limitations
2015-04-14 7:26 [PATCHv6 0/4] watchdog: Extend kernel API and add early_timeout_sec feature Timo Kokkonen
@ 2015-04-14 7:26 ` Timo Kokkonen
2015-04-14 11:43 ` Timo Kokkonen
2015-04-14 7:26 ` [PATCHv6 2/4] watchdog: Allow watchdog to reset device at early boot Timo Kokkonen
` (3 subsequent siblings)
4 siblings, 1 reply; 7+ messages in thread
From: Timo Kokkonen @ 2015-04-14 7:26 UTC (permalink / raw)
To: linux-arm-kernel
There is a great deal of diversity in the watchdog hardware found on
different devices. Differen hardware have different contstraints on
them, many of the constraints that are excessively difficult for the
user space to satisfy.
One such constraint is the length of the timeout value, which in many
cases can be just a few seconds. Drivers are creating ad hoc solutions
with timers and workqueues to extend the timeout in order to give user
space more time between updates. Looking at the drivers it is clear
that this has resulted to a lot of duplicate code.
Add an extension to the watchdog kernel API that allows the driver to
describe tis HW constraints to the watchdog code. A kernel worker in
the core is then used to extend the watchdog timeout on behalf of the
user space. This allows the drivers to be simplified as core takes
over the timer extending.
Tested-by: Wenyou Yang <wenyou.yang@atmel.com>
Signed-off-by: Timo Kokkonen <timo.kokkonen@offcode.fi>
---
drivers/watchdog/watchdog_core.c | 98 ++++++++++++++++++++++++++++++++++++++--
drivers/watchdog/watchdog_dev.c | 22 ++++++++-
include/linux/watchdog.h | 23 ++++++++++
3 files changed, 138 insertions(+), 5 deletions(-)
diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c
index cec9b55..7854ecb 100644
--- a/drivers/watchdog/watchdog_core.c
+++ b/drivers/watchdog/watchdog_core.c
@@ -99,6 +99,86 @@ int watchdog_init_timeout(struct watchdog_device *wdd,
EXPORT_SYMBOL_GPL(watchdog_init_timeout);
/**
+ * watchdog_init_parms() - initialize generic watchdog parameters
+ * @wdd: Watchdog device to operate
+ * @dev: Device that stores the device tree properties
+ *
+ * Initialize the generic timeout parameters. The driver needs to set
+ * hw_features bitmask from @wdd prior calling this function in order
+ * to ensure the core knows how to handle the HW.
+ *
+ * A zero is returned on success and -EINVAL for failure.
+ */
+int watchdog_init_params(struct watchdog_device *wdd, struct device *dev)
+{
+ int ret = 0;
+
+ ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Max HW timeout needs to be set so that core knows when to
+ * use a kernel worker to support longer watchdog timeouts
+ */
+ if (!wdd->hw_max_timeout)
+ return -EINVAL;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(watchdog_init_params);
+
+static void watchdog_worker(struct work_struct *work)
+{
+ struct watchdog_device *wdd = container_of(to_delayed_work(work),
+ struct watchdog_device, work);
+ bool boot_keepalive;
+ bool active_keepalive;
+
+ mutex_lock(&wdd->lock);
+
+ boot_keepalive = !watchdog_active(wdd) &&
+ !watchdog_is_stoppable(wdd);
+
+ active_keepalive = watchdog_active(wdd) &&
+ wdd->hw_max_timeout < wdd->timeout * HZ;
+
+ if (time_after(jiffies, wdd->expires) && watchdog_active(wdd)) {
+ pr_crit("I will reset your machine !\n");
+ mutex_unlock(&wdd->lock);
+ return;
+ }
+
+ if (boot_keepalive || active_keepalive) {
+ if (wdd->ops->ping)
+ wdd->ops->ping(wdd);
+ else
+ wdd->ops->start(wdd);
+
+ schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+ }
+
+ mutex_unlock(&wdd->lock);
+}
+
+static int prepare_watchdog(struct watchdog_device *wdd)
+{
+ /* Stop the watchdog now before user space opens the device */
+ if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&
+ wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
+ wdd->ops->stop(wdd);
+
+ } else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+ /*
+ * Can't stop it, use a kernel timer to tick
+ * it until it's open by user space
+ */
+ schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+ }
+ return 0;
+}
+
+/**
* watchdog_register_device() - register a watchdog device
* @wdd: watchdog device
*
@@ -156,13 +236,24 @@ int watchdog_register_device(struct watchdog_device *wdd)
wdd->dev = device_create(watchdog_class, wdd->parent, devno,
NULL, "watchdog%d", wdd->id);
if (IS_ERR(wdd->dev)) {
- watchdog_dev_unregister(wdd);
- ida_simple_remove(&watchdog_ida, id);
ret = PTR_ERR(wdd->dev);
- return ret;
+ goto dev_create_fail;
+ }
+
+ INIT_DELAYED_WORK(&wdd->work, watchdog_worker);
+
+ if (wdd->hw_max_timeout) {
+ ret = prepare_watchdog(wdd);
+ if (ret)
+ goto dev_create_fail;
}
return 0;
+
+dev_create_fail:
+ watchdog_dev_unregister(wdd);
+ ida_simple_remove(&watchdog_ida, id);
+ return ret;
}
EXPORT_SYMBOL_GPL(watchdog_register_device);
@@ -181,6 +272,7 @@ void watchdog_unregister_device(struct watchdog_device *wdd)
if (wdd == NULL)
return;
+ cancel_delayed_work_sync(&wdd->work);
devno = wdd->cdev.dev;
ret = watchdog_dev_unregister(wdd);
if (ret)
diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c
index 6aaefba..b4c2c6cd2 100644
--- a/drivers/watchdog/watchdog_dev.c
+++ b/drivers/watchdog/watchdog_dev.c
@@ -78,6 +78,12 @@ static int watchdog_ping(struct watchdog_device *wddev)
else
err = wddev->ops->start(wddev); /* restart watchdog */
+ if (wddev->hw_max_timeout &&
+ wddev->timeout * HZ > wddev->hw_max_timeout) {
+ wddev->expires = jiffies + wddev->timeout * HZ;
+ schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+ }
+
out_ping:
mutex_unlock(&wddev->lock);
return err;
@@ -110,6 +116,12 @@ static int watchdog_start(struct watchdog_device *wddev)
if (err == 0)
set_bit(WDOG_ACTIVE, &wddev->status);
+ if (wddev->hw_max_timeout &&
+ wddev->timeout * HZ > wddev->hw_max_timeout) {
+ wddev->expires = jiffies + wddev->timeout * HZ;
+ schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+ }
+
out_start:
mutex_unlock(&wddev->lock);
return err;
@@ -145,9 +157,15 @@ static int watchdog_stop(struct watchdog_device *wddev)
goto out_stop;
}
- err = wddev->ops->stop(wddev);
- if (err == 0)
+ if (!wddev->hw_max_timeout || watchdog_is_stoppable(wddev)) {
+ err = wddev->ops->stop(wddev);
+ if (err == 0)
+ clear_bit(WDOG_ACTIVE, &wddev->status);
+ } else {
+ /* Unstoppable watchdogs need the worker to keep them alive */
clear_bit(WDOG_ACTIVE, &wddev->status);
+ schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+ }
out_stop:
mutex_unlock(&wddev->lock);
diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h
index 395b70e..027c99d 100644
--- a/include/linux/watchdog.h
+++ b/include/linux/watchdog.h
@@ -12,6 +12,7 @@
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/cdev.h>
+#include <linux/workqueue.h>
#include <uapi/linux/watchdog.h>
struct watchdog_ops;
@@ -62,9 +63,13 @@ struct watchdog_ops {
* @timeout: The watchdog devices timeout value.
* @min_timeout:The watchdog devices minimum timeout value.
* @max_timeout:The watchdog devices maximum timeout value.
+ * @hw_max_timeout:The watchdog hardware maximum timeout value.
+ * @hw_heartbeat:Time interval in HW between timer pings.
* @driver-data:Pointer to the drivers private data.
* @lock: Lock for watchdog core internal use only.
+ * @work: Worker used to provide longer timeouts than hardware supports.
* @status: Field that contains the devices internal status bits.
+ * @hw_features:Feature bits describing how the watchdog HW works.
*
* The watchdog_device structure contains all information about a
* watchdog timer device.
@@ -86,8 +91,12 @@ struct watchdog_device {
unsigned int timeout;
unsigned int min_timeout;
unsigned int max_timeout;
+ unsigned int hw_max_timeout; /* in jiffies */
+ unsigned int hw_heartbeat; /* in jiffies */
+ unsigned long int expires; /* for keepalive worker */
void *driver_data;
struct mutex lock;
+ struct delayed_work work;
unsigned long status;
/* Bit numbers for status flags */
#define WDOG_ACTIVE 0 /* Is the watchdog running/active */
@@ -95,6 +104,14 @@ struct watchdog_device {
#define WDOG_ALLOW_RELEASE 2 /* Did we receive the magic char ? */
#define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */
#define WDOG_UNREGISTERED 4 /* Has the device been unregistered */
+
+/* Bits describing features supported by the HW */
+ unsigned long hw_features;
+
+/* Can the watchdog be stopped and started */
+#define WDOG_HW_IS_STOPPABLE BIT(0)
+/* Is the watchdog already running when the driver starts up */
+#define WDOG_HW_RUNNING_AT_BOOT BIT(1)
};
#define WATCHDOG_NOWAYOUT IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT)
@@ -120,6 +137,11 @@ static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigne
(t < wdd->min_timeout || t > wdd->max_timeout));
}
+static inline bool watchdog_is_stoppable(struct watchdog_device *wdd)
+{
+ return wdd->hw_features & WDOG_HW_IS_STOPPABLE;
+}
+
/* Use the following functions to manipulate watchdog driver specific data */
static inline void watchdog_set_drvdata(struct watchdog_device *wdd, void *data)
{
@@ -134,6 +156,7 @@ static inline void *watchdog_get_drvdata(struct watchdog_device *wdd)
/* drivers/watchdog/watchdog_core.c */
extern int watchdog_init_timeout(struct watchdog_device *wdd,
unsigned int timeout_parm, struct device *dev);
+int watchdog_init_params(struct watchdog_device *wdd, struct device *dev);
extern int watchdog_register_device(struct watchdog_device *);
extern void watchdog_unregister_device(struct watchdog_device *);
--
2.1.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCHv6 2/4] watchdog: Allow watchdog to reset device at early boot
2015-04-14 7:26 [PATCHv6 0/4] watchdog: Extend kernel API and add early_timeout_sec feature Timo Kokkonen
2015-04-14 7:26 ` [PATCHv6 1/4] watchdog: Extend kernel API to know about HW limitations Timo Kokkonen
@ 2015-04-14 7:26 ` Timo Kokkonen
2015-04-14 7:26 ` [PATCHv6 3/4] devicetree: Document generic watchdog properties Timo Kokkonen
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Timo Kokkonen @ 2015-04-14 7:26 UTC (permalink / raw)
To: linux-arm-kernel
Historically the watchdogs have always been stopped before user space
opens and takes over the device. This is not good on many production
systems where any crash, in kernel or user space, must always result
in a device reset.
Add a new early_timeout_sec parameter to the watchdog that gives user
space certain amount of time to set up itself and take over the
watchdog. Until this timeout has been reached the watchdog core takes
care of petting the watchdog HW. If there is any crash in kernel or
user space, reboot is guaranteed as watchdog hardware is never
stopped.
There is also mode of supplying zero seconds for the early_timeout_sec
parameter. In this mode the worker is not scheduled, so the watchdog
timer is not touched nor is the HW petted until user space takes over
it.
Tested-by: Wenyou Yang <wenyou.yang@atmel.com>
Signed-off-by: Timo Kokkonen <timo.kokkonen@offcode.fi>
---
drivers/watchdog/watchdog_core.c | 46 +++++++++++++++++++++++++++++++---------
drivers/watchdog/watchdog_dev.c | 4 ++++
include/linux/watchdog.h | 1 +
3 files changed, 41 insertions(+), 10 deletions(-)
diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c
index 7854ecb..6a3a21f 100644
--- a/drivers/watchdog/watchdog_core.c
+++ b/drivers/watchdog/watchdog_core.c
@@ -111,12 +111,18 @@ EXPORT_SYMBOL_GPL(watchdog_init_timeout);
*/
int watchdog_init_params(struct watchdog_device *wdd, struct device *dev)
{
+ unsigned int t = 0;
int ret = 0;
ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
if (ret < 0)
return ret;
+ if (!of_property_read_u32(dev->of_node, "early-timeout-sec", &t))
+ wdd->early_timeout_sec = t;
+ else
+ wdd->early_timeout_sec = -1;
+
/*
* Max HW timeout needs to be set so that core knows when to
* use a kernel worker to support longer watchdog timeouts
@@ -134,11 +140,16 @@ static void watchdog_worker(struct work_struct *work)
struct watchdog_device, work);
bool boot_keepalive;
bool active_keepalive;
+ bool early_timeout_expired;
mutex_lock(&wdd->lock);
+ early_timeout_expired = !watchdog_active(wdd) &&
+ wdd->early_timeout_sec >= 0 &&
+ time_after(jiffies, wdd->expires);
+
boot_keepalive = !watchdog_active(wdd) &&
- !watchdog_is_stoppable(wdd);
+ !watchdog_is_stoppable(wdd) && !early_timeout_expired;
active_keepalive = watchdog_active(wdd) &&
wdd->hw_max_timeout < wdd->timeout * HZ;
@@ -163,17 +174,32 @@ static void watchdog_worker(struct work_struct *work)
static int prepare_watchdog(struct watchdog_device *wdd)
{
- /* Stop the watchdog now before user space opens the device */
- if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&
- wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
- wdd->ops->stop(wdd);
-
- } else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+ if (wdd->early_timeout_sec >= 0) {
/*
- * Can't stop it, use a kernel timer to tick
- * it until it's open by user space
+ * early timeout, if set, ensures that watchdog will
+ * reset the device unless user space opens the
+ * watchdog device within the given interval.
*/
- schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+ if (!(wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT))
+ wdd->ops->start(wdd);
+
+ if (wdd->early_timeout_sec > 0) {
+ wdd->expires = jiffies + wdd->early_timeout_sec * HZ;
+ schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+ }
+ } else {
+ /* Stop the watchdog now before user space opens the device */
+ if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&
+ wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
+ wdd->ops->stop(wdd);
+
+ } else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+ /*
+ * Can't stop it, use a kernel timer to tick
+ * it until it's open by user space
+ */
+ schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+ }
}
return 0;
}
diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c
index b4c2c6cd2..86989c1 100644
--- a/drivers/watchdog/watchdog_dev.c
+++ b/drivers/watchdog/watchdog_dev.c
@@ -122,6 +122,10 @@ static int watchdog_start(struct watchdog_device *wddev)
schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
}
+ /* Once we open the device, early timeout can be disabled */
+ if (wddev->early_timeout_sec >= 0)
+ wddev->early_timeout_sec = -1;
+
out_start:
mutex_unlock(&wddev->lock);
return err;
diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h
index 027c99d..a9d2598 100644
--- a/include/linux/watchdog.h
+++ b/include/linux/watchdog.h
@@ -94,6 +94,7 @@ struct watchdog_device {
unsigned int hw_max_timeout; /* in jiffies */
unsigned int hw_heartbeat; /* in jiffies */
unsigned long int expires; /* for keepalive worker */
+ int early_timeout_sec;
void *driver_data;
struct mutex lock;
struct delayed_work work;
--
2.1.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCHv6 3/4] devicetree: Document generic watchdog properties
2015-04-14 7:26 [PATCHv6 0/4] watchdog: Extend kernel API and add early_timeout_sec feature Timo Kokkonen
2015-04-14 7:26 ` [PATCHv6 1/4] watchdog: Extend kernel API to know about HW limitations Timo Kokkonen
2015-04-14 7:26 ` [PATCHv6 2/4] watchdog: Allow watchdog to reset device at early boot Timo Kokkonen
@ 2015-04-14 7:26 ` Timo Kokkonen
2015-04-14 7:26 ` [PATCHv6 4/4] watchdog: at91sam9_wdt: Convert to use new watchdog core extensions Timo Kokkonen
2015-04-15 1:51 ` [PATCHv6 0/4] watchdog: Extend kernel API and add early_timeout_sec feature Yang, Wenyou
4 siblings, 0 replies; 7+ messages in thread
From: Timo Kokkonen @ 2015-04-14 7:26 UTC (permalink / raw)
To: linux-arm-kernel
There is no documentation for the watchdog properties that are common
among most of the watchdog drivers. Add document where these generic
properties can be described and told how they should be used in
drivers.
Signed-off-by: Timo Kokkonen <timo.kokkonen@offcode.fi>
---
.../devicetree/bindings/watchdog/watchdog.txt | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
create mode 100644 Documentation/devicetree/bindings/watchdog/watchdog.txt
diff --git a/Documentation/devicetree/bindings/watchdog/watchdog.txt b/Documentation/devicetree/bindings/watchdog/watchdog.txt
new file mode 100644
index 0000000..3781406
--- /dev/null
+++ b/Documentation/devicetree/bindings/watchdog/watchdog.txt
@@ -0,0 +1,20 @@
+These properties are common among most watchdog drivers. Any driver
+that requires the functionality listed below should implement them
+using these definitions.
+
+Optional properties:
+- timeout-sec: Contains the watchdog timeout in seconds.
+- early-timeout-sec: If present, specify the timeout in seconds for
+ how long it can take for the watchdog daemon to take over the
+ watchdog device. If driver supports this property it must ensure the
+ watchdog hardware is running during this period and a watchdog reset
+ must occur if user space fails to open the device in time. If left
+ zero, the driver only needs to guarantee the watchdog is not
+ stopped or is started during driver init.
+
+Example:
+
+watchdog {
+ timeout-sec = <60>;
+ early-timeout-sec = <120>;
+};
--
2.1.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCHv6 4/4] watchdog: at91sam9_wdt: Convert to use new watchdog core extensions
2015-04-14 7:26 [PATCHv6 0/4] watchdog: Extend kernel API and add early_timeout_sec feature Timo Kokkonen
` (2 preceding siblings ...)
2015-04-14 7:26 ` [PATCHv6 3/4] devicetree: Document generic watchdog properties Timo Kokkonen
@ 2015-04-14 7:26 ` Timo Kokkonen
2015-04-15 1:51 ` [PATCHv6 0/4] watchdog: Extend kernel API and add early_timeout_sec feature Yang, Wenyou
4 siblings, 0 replies; 7+ messages in thread
From: Timo Kokkonen @ 2015-04-14 7:26 UTC (permalink / raw)
To: linux-arm-kernel
Fill in the new watchdog core parameters and call
watchdog_init_params() to let core know we are ready to support the
new core API extensions. This allows the ad hoc timer code to be
removed from the driver as the watchdog core takes care of petting the
driver as needed.
Tested-by: Wenyou Yang <wenyou.yang@atmel.com>
Signed-off-by: Timo Kokkonen <timo.kokkonen@offcode.fi>
---
drivers/watchdog/at91sam9_wdt.c | 58 +++++++++--------------------------------
1 file changed, 12 insertions(+), 46 deletions(-)
diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c
index 1443b3c..a30dadb 100644
--- a/drivers/watchdog/at91sam9_wdt.c
+++ b/drivers/watchdog/at91sam9_wdt.c
@@ -29,7 +29,6 @@
#include <linux/types.h>
#include <linux/watchdog.h>
#include <linux/jiffies.h>
-#include <linux/timer.h>
#include <linux/bitops.h>
#include <linux/uaccess.h>
#include <linux/of.h>
@@ -83,11 +82,8 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
struct at91wdt {
struct watchdog_device wdd;
void __iomem *base;
- unsigned long next_heartbeat; /* the next_heartbeat for the timer */
- struct timer_list timer; /* The timer that pings the watchdog */
u32 mr;
u32 mr_mask;
- unsigned long heartbeat; /* WDT heartbeat in jiffies */
bool nowayout;
unsigned int irq;
};
@@ -115,26 +111,11 @@ static inline void at91_wdt_reset(struct at91wdt *wdt)
wdt_write(wdt, AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT);
}
-/*
- * Timer tick
- */
-static void at91_ping(unsigned long data)
-{
- struct at91wdt *wdt = (struct at91wdt *)data;
- if (time_before(jiffies, wdt->next_heartbeat) ||
- !watchdog_active(&wdt->wdd)) {
- at91_wdt_reset(wdt);
- mod_timer(&wdt->timer, jiffies + wdt->heartbeat);
- } else {
- pr_crit("I will reset your machine !\n");
- }
-}
-
static int at91_wdt_start(struct watchdog_device *wdd)
{
struct at91wdt *wdt = to_wdt(wdd);
- /* calculate when the next userspace timeout will be */
- wdt->next_heartbeat = jiffies + wdd->timeout * HZ;
+
+ at91_wdt_reset(wdt);
return 0;
}
@@ -146,7 +127,7 @@ static int at91_wdt_stop(struct watchdog_device *wdd)
static int at91_wdt_set_timeout(struct watchdog_device *wdd, unsigned int new_timeout)
{
- wdd->timeout = new_timeout;
+ /* The watchdog hardware can't be reconfigured */
return at91_wdt_start(wdd);
}
@@ -196,11 +177,11 @@ static int at91_wdt_init(struct platform_device *pdev, struct at91wdt *wdt)
* it at the min_heartbeat period.
*/
if ((max_heartbeat / 4) >= min_heartbeat)
- wdt->heartbeat = max_heartbeat / 4;
+ wdt->wdd.hw_heartbeat = max_heartbeat / 4;
else if ((max_heartbeat / 2) >= min_heartbeat)
- wdt->heartbeat = max_heartbeat / 2;
+ wdt->wdd.hw_heartbeat = max_heartbeat / 2;
else
- wdt->heartbeat = min_heartbeat;
+ wdt->wdd.hw_heartbeat = min_heartbeat;
if (max_heartbeat < min_heartbeat + 4)
dev_warn(dev,
@@ -220,32 +201,15 @@ static int at91_wdt_init(struct platform_device *pdev, struct at91wdt *wdt)
"watchdog already configured differently (mr = %x expecting %x)\n",
tmp & wdt->mr_mask, wdt->mr & wdt->mr_mask);
- setup_timer(&wdt->timer, at91_ping, (unsigned long)wdt);
+ wdt->wdd.hw_features = WDOG_HW_RUNNING_AT_BOOT;
+ watchdog_init_params(&wdt->wdd, dev);
- /*
- * Use min_heartbeat the first time to avoid spurious watchdog reset:
- * we don't know for how long the watchdog counter is running, and
- * - resetting it right now might trigger a watchdog fault reset
- * - waiting for heartbeat time might lead to a watchdog timeout
- * reset
- */
- mod_timer(&wdt->timer, jiffies + min_heartbeat);
-
- /* Try to set timeout from device tree first */
- if (watchdog_init_timeout(&wdt->wdd, 0, dev))
- watchdog_init_timeout(&wdt->wdd, heartbeat, dev);
watchdog_set_nowayout(&wdt->wdd, wdt->nowayout);
err = watchdog_register_device(&wdt->wdd);
if (err)
- goto out_stop_timer;
-
- wdt->next_heartbeat = jiffies + wdt->wdd.timeout * HZ;
+ return err;
return 0;
-
-out_stop_timer:
- del_timer(&wdt->timer);
- return err;
}
/* ......................................................................... */
@@ -287,6 +251,8 @@ static int of_at91wdt_init(struct device_node *np, struct at91wdt *wdt)
}
}
+ wdt->wdd.hw_max_timeout = max * HZ;
+
min = secs_to_ticks(min);
max = secs_to_ticks(max);
@@ -346,6 +312,7 @@ static int __init at91wdt_probe(struct platform_device *pdev)
wdt->wdd.timeout = WDT_HEARTBEAT;
wdt->wdd.min_timeout = 1;
wdt->wdd.max_timeout = 0xFFFF;
+ wdt->wdd.hw_max_timeout = 15 * HZ;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
wdt->base = devm_ioremap_resource(&pdev->dev, r);
@@ -376,7 +343,6 @@ static int __exit at91wdt_remove(struct platform_device *pdev)
watchdog_unregister_device(&wdt->wdd);
pr_warn("I quit now, hardware will probably reboot!\n");
- del_timer(&wdt->timer);
return 0;
}
--
2.1.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCHv6 1/4] watchdog: Extend kernel API to know about HW limitations
2015-04-14 7:26 ` [PATCHv6 1/4] watchdog: Extend kernel API to know about HW limitations Timo Kokkonen
@ 2015-04-14 11:43 ` Timo Kokkonen
0 siblings, 0 replies; 7+ messages in thread
From: Timo Kokkonen @ 2015-04-14 11:43 UTC (permalink / raw)
To: linux-arm-kernel
On 14.04.2015 10:26, Timo Kokkonen wrote:
> There is a great deal of diversity in the watchdog hardware found on
> different devices. Differen hardware have different contstraints on
> them, many of the constraints that are excessively difficult for the
> user space to satisfy.
>
> One such constraint is the length of the timeout value, which in many
> cases can be just a few seconds. Drivers are creating ad hoc solutions
> with timers and workqueues to extend the timeout in order to give user
> space more time between updates. Looking at the drivers it is clear
> that this has resulted to a lot of duplicate code.
>
> Add an extension to the watchdog kernel API that allows the driver to
> describe tis HW constraints to the watchdog code. A kernel worker in
> the core is then used to extend the watchdog timeout on behalf of the
> user space. This allows the drivers to be simplified as core takes
> over the timer extending.
>
> Tested-by: Wenyou Yang <wenyou.yang@atmel.com>
> Signed-off-by: Timo Kokkonen <timo.kokkonen@offcode.fi>
> ---
> drivers/watchdog/watchdog_core.c | 98 ++++++++++++++++++++++++++++++++++++++--
> drivers/watchdog/watchdog_dev.c | 22 ++++++++-
> include/linux/watchdog.h | 23 ++++++++++
> 3 files changed, 138 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c
> index cec9b55..7854ecb 100644
> --- a/drivers/watchdog/watchdog_core.c
> +++ b/drivers/watchdog/watchdog_core.c
> @@ -99,6 +99,86 @@ int watchdog_init_timeout(struct watchdog_device *wdd,
> EXPORT_SYMBOL_GPL(watchdog_init_timeout);
>
> /**
> + * watchdog_init_parms() - initialize generic watchdog parameters
> + * @wdd: Watchdog device to operate
> + * @dev: Device that stores the device tree properties
> + *
> + * Initialize the generic timeout parameters. The driver needs to set
> + * hw_features bitmask from @wdd prior calling this function in order
> + * to ensure the core knows how to handle the HW.
> + *
> + * A zero is returned on success and -EINVAL for failure.
> + */
> +int watchdog_init_params(struct watchdog_device *wdd, struct device *dev)
> +{
> + int ret = 0;
> +
> + ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
> + if (ret < 0)
> + return ret;
> +
> + /*
> + * Max HW timeout needs to be set so that core knows when to
> + * use a kernel worker to support longer watchdog timeouts
> + */
> + if (!wdd->hw_max_timeout)
> + return -EINVAL;
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(watchdog_init_params);
> +
> +static void watchdog_worker(struct work_struct *work)
> +{
> + struct watchdog_device *wdd = container_of(to_delayed_work(work),
> + struct watchdog_device, work);
> + bool boot_keepalive;
> + bool active_keepalive;
> +
> + mutex_lock(&wdd->lock);
> +
> + boot_keepalive = !watchdog_active(wdd) &&
> + !watchdog_is_stoppable(wdd);
> +
> + active_keepalive = watchdog_active(wdd) &&
> + wdd->hw_max_timeout < wdd->timeout * HZ;
> +
> + if (time_after(jiffies, wdd->expires) && watchdog_active(wdd)) {
> + pr_crit("I will reset your machine !\n");
> + mutex_unlock(&wdd->lock);
> + return;
> + }
> +
> + if (boot_keepalive || active_keepalive) {
> + if (wdd->ops->ping)
> + wdd->ops->ping(wdd);
> + else
> + wdd->ops->start(wdd);
> +
> + schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
> + }
> +
> + mutex_unlock(&wdd->lock);
> +}
> +
> +static int prepare_watchdog(struct watchdog_device *wdd)
> +{
> + /* Stop the watchdog now before user space opens the device */
> + if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&
> + wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
> + wdd->ops->stop(wdd);
> +
> + } else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
> + /*
> + * Can't stop it, use a kernel timer to tick
> + * it until it's open by user space
> + */
> + schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
> + }
> + return 0;
> +}
> +
> +/**
> * watchdog_register_device() - register a watchdog device
> * @wdd: watchdog device
> *
> @@ -156,13 +236,24 @@ int watchdog_register_device(struct watchdog_device *wdd)
> wdd->dev = device_create(watchdog_class, wdd->parent, devno,
> NULL, "watchdog%d", wdd->id);
> if (IS_ERR(wdd->dev)) {
> - watchdog_dev_unregister(wdd);
> - ida_simple_remove(&watchdog_ida, id);
> ret = PTR_ERR(wdd->dev);
> - return ret;
> + goto dev_create_fail;
> + }
> +
> + INIT_DELAYED_WORK(&wdd->work, watchdog_worker);
> +
> + if (wdd->hw_max_timeout) {
> + ret = prepare_watchdog(wdd);
> + if (ret)
> + goto dev_create_fail;
> }
>
> return 0;
> +
> +dev_create_fail:
> + watchdog_dev_unregister(wdd);
> + ida_simple_remove(&watchdog_ida, id);
> + return ret;
> }
> EXPORT_SYMBOL_GPL(watchdog_register_device);
>
> @@ -181,6 +272,7 @@ void watchdog_unregister_device(struct watchdog_device *wdd)
> if (wdd == NULL)
> return;
>
> + cancel_delayed_work_sync(&wdd->work);
> devno = wdd->cdev.dev;
> ret = watchdog_dev_unregister(wdd);
> if (ret)
> diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c
> index 6aaefba..b4c2c6cd2 100644
> --- a/drivers/watchdog/watchdog_dev.c
> +++ b/drivers/watchdog/watchdog_dev.c
> @@ -78,6 +78,12 @@ static int watchdog_ping(struct watchdog_device *wddev)
> else
> err = wddev->ops->start(wddev); /* restart watchdog */
>
> + if (wddev->hw_max_timeout &&
> + wddev->timeout * HZ > wddev->hw_max_timeout) {
> + wddev->expires = jiffies + wddev->timeout * HZ;
> + schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
> + }
> +
> out_ping:
> mutex_unlock(&wddev->lock);
> return err;
> @@ -110,6 +116,12 @@ static int watchdog_start(struct watchdog_device *wddev)
> if (err == 0)
> set_bit(WDOG_ACTIVE, &wddev->status);
>
> + if (wddev->hw_max_timeout &&
> + wddev->timeout * HZ > wddev->hw_max_timeout) {
> + wddev->expires = jiffies + wddev->timeout * HZ;
> + schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
> + }
> +
We need to delete the possibly active keepalive worker here unless we
need to keep running it:
- }
+ } else
+ cancel_delayed_work(&wddev->work);
Otherwise we might get a spurious "I will reset your machine" print in
dmesg as the worker is scheduled to run one more time even though it is
not needed any more. This of course happens only with unstoppable
watchdogs that don't need a timer after user space starts pinging them.
I'll add this to the next revision.
-Timo
> out_start:
> mutex_unlock(&wddev->lock);
> return err;
> @@ -145,9 +157,15 @@ static int watchdog_stop(struct watchdog_device *wddev)
> goto out_stop;
> }
>
> - err = wddev->ops->stop(wddev);
> - if (err == 0)
> + if (!wddev->hw_max_timeout || watchdog_is_stoppable(wddev)) {
> + err = wddev->ops->stop(wddev);
> + if (err == 0)
> + clear_bit(WDOG_ACTIVE, &wddev->status);
> + } else {
> + /* Unstoppable watchdogs need the worker to keep them alive */
> clear_bit(WDOG_ACTIVE, &wddev->status);
> + schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
> + }
>
> out_stop:
> mutex_unlock(&wddev->lock);
> diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h
> index 395b70e..027c99d 100644
> --- a/include/linux/watchdog.h
> +++ b/include/linux/watchdog.h
> @@ -12,6 +12,7 @@
> #include <linux/bitops.h>
> #include <linux/device.h>
> #include <linux/cdev.h>
> +#include <linux/workqueue.h>
> #include <uapi/linux/watchdog.h>
>
> struct watchdog_ops;
> @@ -62,9 +63,13 @@ struct watchdog_ops {
> * @timeout: The watchdog devices timeout value.
> * @min_timeout:The watchdog devices minimum timeout value.
> * @max_timeout:The watchdog devices maximum timeout value.
> + * @hw_max_timeout:The watchdog hardware maximum timeout value.
> + * @hw_heartbeat:Time interval in HW between timer pings.
> * @driver-data:Pointer to the drivers private data.
> * @lock: Lock for watchdog core internal use only.
> + * @work: Worker used to provide longer timeouts than hardware supports.
> * @status: Field that contains the devices internal status bits.
> + * @hw_features:Feature bits describing how the watchdog HW works.
> *
> * The watchdog_device structure contains all information about a
> * watchdog timer device.
> @@ -86,8 +91,12 @@ struct watchdog_device {
> unsigned int timeout;
> unsigned int min_timeout;
> unsigned int max_timeout;
> + unsigned int hw_max_timeout; /* in jiffies */
> + unsigned int hw_heartbeat; /* in jiffies */
> + unsigned long int expires; /* for keepalive worker */
> void *driver_data;
> struct mutex lock;
> + struct delayed_work work;
> unsigned long status;
> /* Bit numbers for status flags */
> #define WDOG_ACTIVE 0 /* Is the watchdog running/active */
> @@ -95,6 +104,14 @@ struct watchdog_device {
> #define WDOG_ALLOW_RELEASE 2 /* Did we receive the magic char ? */
> #define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */
> #define WDOG_UNREGISTERED 4 /* Has the device been unregistered */
> +
> +/* Bits describing features supported by the HW */
> + unsigned long hw_features;
> +
> +/* Can the watchdog be stopped and started */
> +#define WDOG_HW_IS_STOPPABLE BIT(0)
> +/* Is the watchdog already running when the driver starts up */
> +#define WDOG_HW_RUNNING_AT_BOOT BIT(1)
> };
>
> #define WATCHDOG_NOWAYOUT IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT)
> @@ -120,6 +137,11 @@ static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigne
> (t < wdd->min_timeout || t > wdd->max_timeout));
> }
>
> +static inline bool watchdog_is_stoppable(struct watchdog_device *wdd)
> +{
> + return wdd->hw_features & WDOG_HW_IS_STOPPABLE;
> +}
> +
> /* Use the following functions to manipulate watchdog driver specific data */
> static inline void watchdog_set_drvdata(struct watchdog_device *wdd, void *data)
> {
> @@ -134,6 +156,7 @@ static inline void *watchdog_get_drvdata(struct watchdog_device *wdd)
> /* drivers/watchdog/watchdog_core.c */
> extern int watchdog_init_timeout(struct watchdog_device *wdd,
> unsigned int timeout_parm, struct device *dev);
> +int watchdog_init_params(struct watchdog_device *wdd, struct device *dev);
> extern int watchdog_register_device(struct watchdog_device *);
> extern void watchdog_unregister_device(struct watchdog_device *);
>
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCHv6 0/4] watchdog: Extend kernel API and add early_timeout_sec feature
2015-04-14 7:26 [PATCHv6 0/4] watchdog: Extend kernel API and add early_timeout_sec feature Timo Kokkonen
` (3 preceding siblings ...)
2015-04-14 7:26 ` [PATCHv6 4/4] watchdog: at91sam9_wdt: Convert to use new watchdog core extensions Timo Kokkonen
@ 2015-04-15 1:51 ` Yang, Wenyou
4 siblings, 0 replies; 7+ messages in thread
From: Yang, Wenyou @ 2015-04-15 1:51 UTC (permalink / raw)
To: linux-arm-kernel
> -----Original Message-----
> From: Timo Kokkonen [mailto:timo.kokkonen at offcode.fi]
> Sent: 2015?4?14? 15:27
> To: linux-arm-kernel at lists.infradead.org; linux-watchdog at vger.kernel.org;
> boris.brezillon at free-electrons.com; Ferre, Nicolas; alexandre.belloni at free-
> electrons.com
> Cc: Yang, Wenyou; Timo Kokkonen
> Subject: [PATCHv6 0/4] watchdog: Extend kernel API and add early_timeout_sec
> feature
>
> The watchdog kernel API is quite limited. It has support for providing generic
> device handling, but it doesn't really know anything about the watchdog hardware
> or its constraints. The watchdog drivers come with a lot of diversity and their own
> set of quirks and constraints. Some of their limitations are not nice for the user
> space, so the drivers work around them with all sorts of ad hoc implementations.
>
> One common pattern is to use kernel timers or work queues to allow longer
> timeout parameters than the actual hardware supports. To solve this problem, this
> patch set extends the kernel watchdog API with a few parameters that let the core
> know more about the watchdog HW and take care about the timeout extending.
>
> The patch set also implements "early_timeout_sec" feature that is very common
> on many production systems where early kernel or user space crashes must lead
> to a device reset. Traditional watchdog handling does not allow this as the
> watchdog is stopped (fully or emulating stopped state with kernel timers) before
> user space opens it for the first time.
>
> The changes are designed to be taken in use one driver at time. If the driver does
> not set the new parameters and call watchdog_init_params(), the watchdog
> behavior is exactly the same as before.
>
> In principle this new API makes it possible for the user space to see every
> watchdog hardware to behave the same, at least in terms of watchdog timeouts.
> Once the API is in, it should be easier to move even more common behavior out of
> the driver code to the watchdog core and make the drivers simpler. This patch set
> converts at91sam9_wdt to use the new API.
>
> Please review and give feedback.
Tested on sama5d4ek, it works fine.
>
> Patch revision history:
>
> -v6: Fixed some issued based on feedback from Wenyou Yang. The logic
> in watchdog_worker() function is now significantly easier to
> read. Several errors with stopping and starting the worker are also
> now fixed.
>
> -v5: Re-think the approach to be fully generic. The early_timeout_sec
> handling is no longer in the driver but in the watchdog core. As a
> result the core needed to gain knowledge about the watchdog
> hardware. Appropriate handling is added in the core. The side effect
> for this is that drivers using the new extensions can be simplified
> a lot and different kinds of watchdog hardware can be made to
> behave the same for the user space.
>
> -v4: Binding documentation is now separated completely from the driver
> patch. The documentation no longer makes any assumptions about how
> the actual implementation is made, it just describes the actual
> behavior the driver should implement in order to satisfy the
> requirement.
>
> - v3: Rename the property to "early-timeout-sec" and use it as a
> timeout value that stops the timer in the atmel driver after the
> timeout expires. A watchdog.txt is also introduced for documenting
> the common watchdog properties, including now this one and
> "timeout-sec" property.
>
> - v2: Rename the property to "enable-early-reset" as the behavior
> itself is not atmel specific. This way other drivers are free to
> implement same behavior with the same property name.
>
> - v1: Propose property name "atmle,no-early-timer" for disabling the
> timer that keeps the atmel watchdog running until user space opens
> the device.
>
> Timo Kokkonen (4):
> watchdog: Extend kernel API to know about HW limitations
> watchdog: Allow watchdog to reset device at early boot
> devicetree: Document generic watchdog properties
> watchdog: at91sam9_wdt: Convert to use new watchdog core extensions
>
> .../devicetree/bindings/watchdog/watchdog.txt | 20 ++++
> drivers/watchdog/at91sam9_wdt.c | 58 ++--------
> drivers/watchdog/watchdog_core.c | 124 ++++++++++++++++++++-
> drivers/watchdog/watchdog_dev.c | 26 ++++-
> include/linux/watchdog.h | 24 ++++
> 5 files changed, 201 insertions(+), 51 deletions(-) create mode 100644
> Documentation/devicetree/bindings/watchdog/watchdog.txt
>
> --
> 2.1.0
Best Regards,
Wenyou Yang
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2015-04-15 1:51 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-04-14 7:26 [PATCHv6 0/4] watchdog: Extend kernel API and add early_timeout_sec feature Timo Kokkonen
2015-04-14 7:26 ` [PATCHv6 1/4] watchdog: Extend kernel API to know about HW limitations Timo Kokkonen
2015-04-14 11:43 ` Timo Kokkonen
2015-04-14 7:26 ` [PATCHv6 2/4] watchdog: Allow watchdog to reset device at early boot Timo Kokkonen
2015-04-14 7:26 ` [PATCHv6 3/4] devicetree: Document generic watchdog properties Timo Kokkonen
2015-04-14 7:26 ` [PATCHv6 4/4] watchdog: at91sam9_wdt: Convert to use new watchdog core extensions Timo Kokkonen
2015-04-15 1:51 ` [PATCHv6 0/4] watchdog: Extend kernel API and add early_timeout_sec feature Yang, Wenyou
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).