bios: support for tool: flashcp

flashcp as an alternative to flashrom which is already present in some
images.

Tested: On Tyan S8030

Set
```
  "Tool": "flashcp"
```
in the EM json config.

Using the images from [1], update to v4.01 and v4.03, dump the
flash contents and compare md5sum output for both the image and flash
dumps after each update.

```
[alex@odroid host-fw]$ md5sum v4.01/8030V401.ROM
fbbe6ff9b05b6a21f43d58fb0e5108cd  v4.01/8030V401.ROM
[alex@odroid host-fw]$ md5sum v4.03/8030V403.ROM
8a0f85d559913ea63b40cd467cc65cd3  v4.03/8030V403.ROM
```
on the bmc:
```
root@s8030-bmc-30303035c0c1:~# md5sum /tmp/mtd6_dump_v4.01.bin
fbbe6ff9b05b6a21f43d58fb0e5108cd  /tmp/mtd6_dump_v4.01.bin
root@s8030-bmc-30303035c0c1:~# md5sum /tmp/mtd6_dump_v4.03.bin
8a0f85d559913ea63b40cd467cc65cd3  /tmp/mtd6_dump_v4.03.bin
```

References:
[1] https://www.tyan.com/Motherboards=S8030=S8030GM2NE=downloads=EN/_Motherboards_S8030_S8030GM2NE

Change-Id: I95b473a5e90da38da866f982946855bb9452aafd
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/bios/spi_device.cpp b/bios/spi_device.cpp
index fbf6fe9..af9ef89 100644
--- a/bios/spi_device.cpp
+++ b/bios/spi_device.cpp
@@ -297,6 +297,11 @@
                 }
                 success = (status == 0);
             }
+            else if (tool == flashToolFlashcp)
+            {
+                success = co_await SPIDevice::writeSPIFlashWithFlashcp(
+                    image, image_size);
+            }
             else
             {
                 success =
@@ -443,6 +448,54 @@
 }
 
 // NOLINTBEGIN(readability-static-accessed-through-instance)
+sdbusplus::async::task<bool> SPIDevice::writeSPIFlashWithFlashcp(
+    const uint8_t* image, size_t image_size) const
+// NOLINTEND(readability-static-accessed-through-instance)
+{
+    // randomize the name to enable parallel updates
+    const std::string path = "/tmp/spi-device-image-" +
+                             std::to_string(Software::getRandomId()) + ".bin";
+
+    int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
+    if (fd < 0)
+    {
+        error("Failed to open file: {PATH}", "PATH", path);
+        co_return 1;
+    }
+
+    const ssize_t bytesWritten = write(fd, image, image_size);
+
+    close(fd);
+
+    setUpdateProgress(30);
+
+    if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size)
+    {
+        error("Failed to write image to file");
+        co_return 1;
+    }
+
+    debug("wrote {SIZE} bytes to {PATH}", "SIZE", bytesWritten, "PATH", path);
+
+    auto devPath = getMTDDevicePath();
+
+    if (!devPath.has_value())
+    {
+        co_return 1;
+    }
+
+    std::string cmd = std::format("flashcp -v {} {}", path, devPath.value());
+
+    debug("running {CMD}", "CMD", cmd);
+
+    const int exitCode = co_await asyncSystem(ctx, cmd);
+
+    std::filesystem::remove(path);
+
+    co_return exitCode == 0;
+}
+
+// NOLINTBEGIN(readability-static-accessed-through-instance)
 sdbusplus::async::task<bool> SPIDevice::writeSPIFlashDefault(
     const uint8_t* image, size_t image_size)
 // NOLINTEND(readability-static-accessed-through-instance)