Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ UpdateManager.ConfirmDeploymentImage();
- [Update sessions](docs/update-sessions.md) - staging an image, resuming an interrupted download,
session rules and result codes.
- [Confirm and revert](docs/confirm-and-revert.md) - `ConfirmDeploymentImage`,
`RequestDeploymentRevert`, `RequestClrRevert`, `RequestReboot` and the test cycle.
`RequestDeploymentRevert`, `RequestClrRevert`, `RequestReboot`, the test cycle and the
`[DeploymentConfirmationTimeout]` failsafe.
- [Writing an update provider](docs/writing-an-update-provider.md) - how to build an update library
that fetches images from a server or other provider, end to end, on the `IUpdateProvider`
contract from the `nanoFramework.Runtime.InFieldUpdate.Provider` package.
Expand Down
2 changes: 2 additions & 0 deletions Samples/IFU_blink-app/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class Program
{
private static GpioController s_GpioController;

// revert this deployment if it isn't confirmed within 60 seconds
[DeploymentConfirmationTimeout(60)]
public static void Main()
{
Debug.WriteLine("Hello from nanoFramework!");
Expand Down
5 changes: 5 additions & 0 deletions Samples/IFU_blink-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ This app can be used to visually test the update workflow.
Adjust the `blinkCount` var around line 26 to the number of blinks matching the version build number. For example, when setting `AssemblyVersion` to 1.0.3, change it to 3 so it blinks 3 times. For version 1.0.2 make 2 and so on.

Note: if needed, change/add a new GPIO configuration to open the LED pin. Around line 19.

`Main` carries `[DeploymentConfirmationTimeout(60)]`. To see the failsafe at work, comment out the
`ConfirmDeploymentImage()` call: about 60 seconds after booting the new version the device resets, and the
previous version (with its blink count) comes back. See
[Confirmation timeout](../../docs/confirm-and-revert.md#confirmation-timeout).
69 changes: 68 additions & 1 deletion docs/confirm-and-revert.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ A completed update session marks the new deployment image for a **test** swap:
- on the next reboot the previous image is restored.

This makes a bad update self-healing: an image that cannot get far enough to confirm itself is
rolled back automatically.
rolled back automatically. To make sure that reboot actually happens when the new application
hangs, add a [confirmation timeout](#confirmation-timeout).

### `ConfirmDeploymentImage`

Expand Down Expand Up @@ -93,6 +94,72 @@ Things worth checking in `SelfTest`:
Keep it bounded in time: until the image is confirmed, any reset brings the previous image back,
so a self-test that never completes means the update does not stick.

## Confirmation timeout

"On the next reboot" needs something to cause that reboot. A new application that hangs,
deadlocks or waits forever never confirms itself, and it never reboots either. The device stays
stuck on the broken image until someone power-cycles it. On a device in the field that can be a
long wait.

Put `DeploymentConfirmationTimeout` on `Main` to close that gap:

```csharp
using nanoFramework.Runtime.InFieldUpdate;

public class Program
{
// this deployment has 2 minutes to confirm itself, otherwise it is reverted
[DeploymentConfirmationTimeout(120)]
public static void Main()
{
if (UpdateManager.GetStatus(ImageType.Deployment) == UpdateStatus.Testing && SelfTest())
{
UpdateManager.ConfirmDeploymentImage();
}

// normal application start
}
}
```

When the deployment is running as an unconfirmed test image, the nanoCLR starts a countdown just
before `Main` runs (static constructors are included). If `ConfirmDeploymentImage()` isn't called
in time, the device is reset and MCUboot restores the previous deployment.

- **Units**: seconds. `0`, or no attribute at all, leaves the countdown off.
- **Only when it matters**: the countdown is only armed when the deployment is unconfirmed. Once
the image is confirmed, the attribute has no effect, so it is safe to leave in place.
- **Per deployment**: the value travels with the application. Each deployment can choose its
own, without a firmware change.
- **Once per power cycle**: the countdown starts once. A CLR-only reboot neither restarts nor
extends it.
- **What stops it**:
- a successful `ConfirmDeploymentImage()`;
- a confirm sent by a tool over the Wire Protocol;
- the start of a debugger session (source-level debugging), so a developer stepping through
code isn't reset in the middle of it.

A failed confirm keeps the countdown running. When it is cancelled by a debugger, the image is
still unconfirmed, so it is reverted on the next reboot unless it gets confirmed.
- **A deployment that can't start**: this case doesn't need the attribute. If an unconfirmed
deployment fails to load (corrupted assembly, mismatch with the firmware's native code) or has
no entry point, the nanoCLR reboots straight away so it is reverted.
- **Platform support**: implemented for ChibiOS targets with MCUboot. On other platforms the
attribute is ignored.

### Choosing a value

The timeout has to cover the worst-case time from power-on to the `ConfirmDeploymentImage()` call:

- static constructors;
- the self-test, including waiting for the network to come up and the update server to answer;
- any retries the self-test makes.

Leave a generous margin. A timeout that is too short reverts a perfectly good update on a slow day.
That is recoverable, because the update can be retried, but it is still wasted effort. Values in
minutes are typical for applications that check connectivity. A few tens of seconds is enough for
one that only checks local peripherals.

## nanoCLR image

The nanoCLR image behaves differently: **the running nanoCLR is already confirmed at startup**, so
Expand Down
5 changes: 3 additions & 2 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ flowchart LR
[confirms itself](confirm-and-revert.md).
4. **Confirm or revert.** A confirmed image becomes permanent. An image that is not confirmed -
because it crashed, hung, or decided it is broken - is replaced by the previous one on the next
reboot.
reboot. A [confirmation timeout](confirm-and-revert.md#confirmation-timeout) on `Main` forces
that reboot when the new application hangs.

## Update states

Expand Down Expand Up @@ -88,7 +89,7 @@ interleave. See [Update sessions](update-sessions.md).
| Query | `GetStatus`, `GetPrimaryImageInfo`, `GetSecondaryImageInfo`, `GetImageList`, `ImageInfo`, `ImageInfoExtensions.ToTable` | [Querying images](querying-images.md) |
| Stage | `StartUpdateSession`, `ResumeUpdateSession`, `StoreImageChunk`, `UpdateSession.Write`, `CompleteUpdateSession`, `AbortUpdateSession`, `EraseSecondaryImage` | [Update sessions](update-sessions.md) |
| Diagnostics | `GetUpdateSessionOwner`, `UpdateSessionResult` (returned by every session operation) | [Update sessions](update-sessions.md) |
| Confirm / revert | `ConfirmDeploymentImage`, `RequestDeploymentRevert`, `RequestClrRevert` | [Confirm and revert](confirm-and-revert.md) |
| Confirm / revert | `ConfirmDeploymentImage`, `RequestDeploymentRevert`, `RequestClrRevert`, `DeploymentConfirmationTimeoutAttribute` | [Confirm and revert](confirm-and-revert.md) |
| Reboot | `RequestReboot` | [Confirm and revert](confirm-and-revert.md) |
| Provider contract (`nanoFramework.Runtime.InFieldUpdate.Provider` package) | `IUpdateProvider`, `UpdateAgentOptions`, `HealthCheck` | [Writing an update provider](writing-an-update-provider.md) |

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//

using System;

namespace nanoFramework.Runtime.InFieldUpdate
{
/// <summary>
/// Sets how long a new deployment has to confirm itself before the device reboots and reverts it.
/// </summary>
/// <remarks>
/// <para>
/// Apply it to the application entry point (<c>Main</c>). When the deployment is running as an unconfirmed
/// test image, the CLR starts a countdown before <c>Main</c> runs. If <see cref="UpdateManager.ConfirmDeploymentImage"/>
/// isn't called before it expires, the device is reset and the bootloader reverts to the previous deployment.
/// </para>
/// <para>
/// The countdown is armed at most once per power cycle and only for an unconfirmed deployment. It is cancelled by a
/// successful confirmation or when a debugger session starts. A value of 0 disables it.
/// </para>
/// <para>
/// The value is read by the CLR from the attribute metadata. Platforms without support for it ignore the attribute.
/// </para>
/// </remarks>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class DeploymentConfirmationTimeoutAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="DeploymentConfirmationTimeoutAttribute"/> class.
/// </summary>
/// <param name="seconds">Time, in seconds, the deployment has to confirm itself. 0 disables the countdown.</param>
public DeploymentConfirmationTimeoutAttribute(uint seconds)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.props" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.props')" />
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="DeploymentConfirmationTimeoutAttribute.cs" />
<Compile Include="ImageInfo.cs" />
<Compile Include="ImageInfoExtensions.cs" />
<Compile Include="ImageType.cs" />
Expand Down
Loading