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_device.hpp b/test/common/exampledevice/example_device.hpp
new file mode 100644
index 0000000..928af3b
--- /dev/null
+++ b/test/common/exampledevice/example_device.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "common/include/device.hpp"
+#include "common/include/software_manager.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/server.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
+#include <xyz/openbmc_project/Software/Update/server.hpp>
+
+namespace phosphor::software::example_device
+{
+
+class ExampleCodeUpdater : public phosphor::software::manager::SoftwareManager
+{
+  public:
+    ExampleCodeUpdater(sdbusplus::async::context& ctx,
+                       long uniqueSuffix = getRandomId());
+
+    sdbusplus::async::task<bool> initDevice(const std::string& service,
+                                            const std::string& path,
+                                            SoftwareConfig& config) final;
+
+  private:
+    static long getRandomId();
+};
+
+const std::string exampleName = "ExampleSoftware";
+
+const uint32_t exampleVendorIANA = 0x0000a015;
+const std::string exampleCompatibleHardware = "com.example.CompatibleDevice";
+
+const std::string exampleInvObjPath =
+    "/xyz/openbmc_project/inventory/system/board/ExampleBoard/ExampleDevice";
+
+class ExampleDevice : public Device
+{
+  public:
+    using Device::softwarePending;
+    using phosphor::software::device::Device::softwareCurrent;
+
+    static SoftwareConfig defaultConfig;
+
+    ExampleDevice(sdbusplus::async::context& ctx,
+                  phosphor::software::manager::SoftwareManager* parent,
+                  const SoftwareConfig& config = defaultConfig);
+
+    // NOLINTBEGIN(readability-static-accessed-through-instance)
+    sdbusplus::async::task<bool> updateDevice(const uint8_t* image,
+                                              size_t image_size) override;
+    // NOLINTEND(readability-static-accessed-through-instance)
+
+    bool deviceSpecificUpdateFunctionCalled = false;
+};
+
+} // namespace phosphor::software::example_device