fw update: implement example device

Introduce example-device and example-code-updater, which are used as
examples for how to implement devices and code updaters according to the
design [1]

Tested:

The example code updater allows us to perform manual testing without
a dependency on any specific device.

Running the example code updater results in follow dbus output:

```
busctl --full --no-pager tree xyz.openbmc_project.ExampleCodeUpdater

└─ /xyz
  └─ /xyz/openbmc_project
    └─ /xyz/openbmc_project/software
      └─ /xyz/openbmc_project/software/ExampleSoftware_4081

busctl --full --no-pager introspect xyz.openbmc_project.ExampleCodeUpdater /xyz/openbmc_project/software/ExampleSoftware_4081

CodeUpdater /xyz/openbmc_project/software/ExampleSoftware_4081
NAME                                    TYPE      SIGNATURE RESULT/VALUE                                                           FLAGS
...
xyz.openbmc_project.Software.Activation interface -         -                                                                      -
.Activation                             property  s         "xyz.openbmc_project.Software.Activation.Activations.Active"           emits-change writable
.RequestedActivation                    property  s         "xyz.openbmc_project.Software.Activation.RequestedActivations.None"    emits-change writable
xyz.openbmc_project.Software.Update     interface -         -                                                                      -
.StartUpdate                            method    hs        o                                                                      -
.AllowedApplyTimes                      property  as        1 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset" emits-change
xyz.openbmc_project.Software.Version    interface -         -                                                                      -
.Purpose                                property  s         "xyz.openbmc_project.Software.Version.VersionPurpose.Unknown"          emits-change writable
.Version                                property  s         "v1.0"
```

References:
[1] https://github.com/openbmc/docs/blob/master/designs/code-update.md

Change-Id: I2bad241b3102e58eda5139174791adda82f6ca95
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/test/common/exampledevice/example_updater_main.cpp b/test/common/exampledevice/example_updater_main.cpp
new file mode 100644
index 0000000..425ab35
--- /dev/null
+++ b/test/common/exampledevice/example_updater_main.cpp
@@ -0,0 +1,40 @@
+#include "example_device.hpp"
+
+#include <sdbusplus/async/context.hpp>
+
+using namespace phosphor::software::example_device;
+
+// NOLINTBEGIN(readability-static-accessed-through-instance)
+sdbusplus::async::task<void> init(ExampleCodeUpdater& updater)
+// NOLINTEND(readability-static-accessed-through-instance)
+{
+    co_await updater.initDevice("", "", ExampleDevice::defaultConfig);
+
+    co_return;
+}
+
+int main()
+{
+    sdbusplus::async::context ctx;
+
+    ExampleCodeUpdater updater(ctx);
+
+    /*
+     * In Concrete updaters, the initDevices() function needs to be called,
+     * which in turn invokes the virtual initDevice() function implemented here.
+     * However, in ExampleUpdater, the initDevice() function is called directly
+     * because there is no example configuration from EM to consume, which would
+     * otherwise cause the initDevices() API to throw an error. Therefore,
+     * calling initDevice() directly in this case.
+     */
+
+    // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Branch)
+    ctx.spawn(init(updater));
+
+    std::string busName = "xyz.openbmc_project.Software.ExampleDevice";
+    ctx.get_bus().request_name(busName.c_str());
+
+    ctx.run();
+
+    return 0;
+}