text: // Copyright (C) 2018-2022 Intel Corporation
  // SPDX-License-Identifier: Apache-2.0
  //
  
  #include "XLinkPlatform.h"
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <stdbool.h>
  #include <stdint.h>
  #include <string.h>
  #include <errno.h>
  #include <signal.h>
  #include <fcntl.h>
  #include <ctype.h>
  
  #if (defined(_WIN32) || defined(_WIN64))
  #include "win_time.h"
  #include <windows.h>
  #include <setupapi.h>
  #include <strsafe.h>
  #include <cfgmgr32.h>
  #include <tchar.h>
  #else
  #include <sys/types.h>
  #include <sys/ioctl.h>
  #include <sys/time.h>
  #include <sys/select.h>
  #include <unistd.h>
  #include <dirent.h>
  #endif
  
  #define MVLOG_UNIT_NAME PCIe
  #include "XLinkLog.h"
  #include "XLinkStringUtils.h"
  #include "pcie_host.h"
  #include "XLinkPlatformErrorUtils.h"
  
  #ifndef XLINK_PCIE_DATA_TIMEOUT
  #define XLINK_PCIE_DATA_TIMEOUT 0
  #endif
  
  #define PCIE_DEVICE_ID 0x6200
  #define PCIE_VENDOR_ID 0x8086
  
  #if (defined(_WIN32) || defined(_WIN64))
  /* IOCTL commands IDs. for Windows*/
  #define MXLK_DEVICE_TYPE 40001
  
  #define MXLK_STATUS_DEV         CTL_CODE(MXLK_DEVICE_TYPE, 0xA08, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
  #define MXLK_RESET_DEV          CTL_CODE(MXLK_DEVICE_TYPE, 0xA09, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
  #define MXLK_BOOT_DEV           CTL_CODE(MXLK_DEVICE_TYPE, 0xA0A, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
  #define MXLK_BOOTLOADER_DEV     CTL_CODE(MXLK_DEVICE_TYPE, 0xA0B, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
  
  #endif
  
  #if (!defined(_WIN32) && !defined(_WIN64))
  /**         MXLK data           */
  /* IOCTL commands IDs. */
  #define IOC_MAGIC 'Z'
  #define MXLK_RESET_DEV    _IO(IOC_MAGIC,  0x80)
  #define MXLK_BOOT_DEV     _IOW(IOC_MAGIC, 0x81, struct mxlk_boot_param)
  #define MXLK_STATUS_DEV   _IOR(IOC_MAGIC, 0x82,  enum mx_fw_status)
  #endif
  
  struct mxlk_boot_param {
      /* Buffer containing the MX application image (MVCMD format) */
      const char *buffer;
      /* Size of the image in bytes. */
      size_t length;
  };
  
  /* State of Myriad X device. */
  enum mx_fw_status {
      /* MX waiting for FW to be loaded from host */
      MX_FW_STATE_BOOTLOADER,
      /* MX running FW loaded from host. */
      MX_FW_STATUS_USER_APP,
      /* MX context is not restored or device is lost*/
      MX_FW_STATUS_UNKNOWN_STATE,
  };
  /**         MXLK data end       */
  
  static inline void sleepForSeconds(const unsigned int seconds) {
  #if (!defined(_WIN32) && !defined(_WIN64))
      sleep(seconds);
  #else
      Sleep(seconds * 1000); // Sleep using miliseconds as input
  #endif
  }
  
  #if !defined(_WIN32)
  static pcieHostError_t getDeviceFwStatusIOCTL(const int fd, enum mx_fw_status *fwStatus) {
      ASSERT_XLINK_PLATFORM_R(fd, PCIE_INVALID_PARAMETERS);
      ASSERT_XLINK_PLATFORM_R(fwStatus, PCIE_INVALID_PARAMETERS);
  
      int ret = ioctl(fd, MXLK_STATUS_DEV, fwStatus);
  
      if (ret) {
          mvLog(MVLOG_INFO, "Get device status ioctl failed with error: %d", ret);
          *fwStatus = MX_FW_STATUS_UNKNOWN_STATE;
          return PCIE_HOST_ERROR;
      }
      return PCIE_HOST_SUCCESS;
  }
  #else
  static pcieHostError_t getDeviceFwStatusIOCTL(const HANDLE deviceHandle, enum mx_fw_status *fwStatus) {
      ASSERT_XLINK_PLATFORM_R(deviceHandle, PCIE_INVALID_PARAMETERS);
      ASSERT_XLINK_PLATFORM_R(fwStatus, PCIE_INVALID_PARAMETERS);
  
      DWORD junk = 0;
  
      BOOL successRc = DeviceIoControl(deviceHandle,          // device to be queried
                                     MXLK_STATUS_DEV,             // operation to perform
                                     NULL, 0,                     // no input buffer
                                     fwStatus, sizeof(*fwStatus), // output buffer
                                     &junk,                       // # bytes returned
                                     (LPOVERLAPPED) NULL);        // synchronous I/O
  
       if (!successRc) {
           mvLog(MVLOG_ERROR, "Get PCIe device status ioctl failed with error: 0x%x", GetLastError());
           *fwStatus = MX_FW_STATUS_UNKNOWN_STATE;
           return PCIE_HOST_ERROR;
       }
       return PCIE_HOST_SUCCESS;
  }
  #endif
  
  #if (defined(_WIN32) || defined(_WIN64))
  int pcie_write(HANDLE fd, void * buf, size_t bufSize)
  {
      ASSERT_XLINK_PLATFORM_R(fd, PCIE_INVALID_PARAMETERS);
      ASSERT_XLINK_PLATFORM_R(buf, PCIE_INVALID_PARAMETERS);
  
      HANDLE dev =  fd;
      OVERLAPPED Overlapped;
      HANDLE Event = NULL;
      bool OutputCode = FALSE;
      ULONG  bytesWrite;
  
      ZeroMemory(&Overlapped, sizeof(Overlapped));
      Event = CreateEvent(NULL, TRUE, FALSE, NULL);
  
      if (Event == NULL) {
          mvLog(MVLOG_INFO, "Error creating I/O event for pcie_write - 0x%x\n", GetLastError());
          return PCIE_HOST_ERROR;
      }
  
      Overlapped.hEvent = Event;
      ResetEvent(Overlapped.hEvent);
      OutputCode = WriteFile(dev, buf, (DWORD)bufSize, NULL, &Overlapped);
  
      if (OutputCode == FALSE) {
          if (GetLastError() == ERROR_IO_PENDING) {
              if (GetOverlappedResult(dev, &Overlapped, &bytesWrite, TRUE)) {
                  CloseHandle(Overlapped.hEvent);
                  return bytesWrite;
              } else {
                  mvLog(MVLOG_DEBUG, "WriteFile GetOverlappedResult failed");
              }
          } else {
              mvLog(MVLOG_DEBUG, "WriteFile failed with error code = 0x%x \n", GetLastError());
          }
      } else {
          mvLog(MVLOG_DEBUG, "fOverlapped - operation complete immediately");
      }
  
      CloseHandle(Overlapped.hEvent);
  
      return PCIE_HOST_ERROR;
  }
  #else
  int pcie_write(void *fd, void * buf, size_t bufSize)
  {
      ASSERT_XLINK_PLATFORM_R(fd, PCIE_INVALID_PARAMETERS);
      ASSERT_XLINK_PLATFORM_R(buf, PCIE_INVALID_PARAMETERS);
      
  
      fd_set wrfds;
      int ret;
  
      FD_ZERO(&wrfds);
      FD_SET(*((int*)fd), &wrfds);
  
      ret = select(*((int*)fd) + 1, NULL, &wrfds, NULL, NULL);
      if (ret < 0)
      {
          return PCIE_HOST_ERROR;
      }
      if (!FD_ISSET(*((int*)fd), &wrfds))
      {
          return PCIE_HOST_TIMEOUT;
      }
  
      ret = write(*((int*)fd), buf, bufSize);
  
      if (ret < 0)
      {
          return PCIE_HOST_ERROR;
      }
      return ret;
  }
  #endif  // (defined(_WIN32) || defined(_WIN64))
  
  #if (defined(_WIN32) || defined(_WIN64))
  int pcie_read(HANDLE fd, void * buf, size_t bufSize)
  {
      ASSERT_XLINK_PLATFORM_R(fd, PCIE_INVALID_PARAMETERS);
      ASSERT_XLINK_PLATFORM_R(buf, PCIE_INVALID_PARAMETERS);
  
      HANDLE dev =  fd;
      OVERLAPPED Overlapped;
      HANDLE Event = NULL;
      bool OutputCode = FALSE;
      ULONG  bytesRead;
  
      ZeroMemory(&Overlapped, sizeof(Overlapped));
      Event = CreateEvent(NULL, TRUE, FALSE, NULL);
  
      if (Event == NULL) {
          mvLog(MVLOG_ERROR, "Error creating I/O event for pcie_read - 0x%x\n", GetLastError());
          return PCIE_HOST_ERROR;
      }
  
      Overlapped.hEvent = Event;
      ResetEvent(Overlapped.hEvent);
      OutputCode = ReadFile(dev, buf, (DWORD)bufSize, NULL, &Overlapped);
  
      if (OutputCode == FALSE) {
         if (GetLastError() == ERROR_IO_PENDING) {
             if (GetOverlappedResult(dev, &Overlapped, &bytesRead, TRUE))
             {
                 CloseHandle(Overlapped.hEvent);
                 return bytesRead;
              } else{
                 mvLog(MVLOG_DEBUG, "ReadFile GetOverlappedResult failed" );
              }
          } else {
              mvLog(MVLOG_DEBUG, "ReadFile failed with error code = 0x%x \n", GetLastError());
          }
      } else {
           mvLog(MVLOG_DEBUG, "fOverlapped - operation complete immediately");
      }
      CloseHandle(Overlapped.hEvent);
  
      return PCIE_HOST_ERROR;
  }
  #else
  int pcie_read(void *fd, void *buf, size_t bufSize)
  {
      ASSERT_XLINK_PLATFORM_R(fd, PCIE_INVALID_PARAMETERS);
      ASSERT_XLINK_PLATFORM_R(buf, PCIE_INVALID_PARAMETERS);
  
      fd_set rdfds;
      int ret;
  
      FD_ZERO(&rdfds);
      FD_SET(*((int*)fd), &rdfds);
  
      ret = select(*((int*)fd) + 1, &rdfds, NULL, NULL, NULL);
      if (ret < 0) {
          return PCIE_HOST_ERROR;
      }
      if (!FD_ISSET(*((int*)fd), &rdfds)) {
          return PCIE_HOST_TIMEOUT;
      }
  
      ret = read(*((int*)fd), buf, bufSize);
  
      if (ret < 0) {
          return PCIE_HOST_ERROR;
      }
      return ret;
  }
  #endif
  
  #if (defined(_WIN32) || defined(_WIN64))
  pcieHostError_t pcie_init(const char *slot, HANDLE *fd)
  {
      ASSERT_XLINK_PLATFORM_R(slot, PCIE_INVALID_PARAMETERS);
      ASSERT_XLINK_PLATFORM_R(fd, PCIE_INVALID_PARAMETERS);
  
      HANDLE hDevice = CreateFile(slot,
          GENERIC_READ | GENERIC_WRITE,
          FILE_SHARE_READ | FILE_SHARE_WRITE,
          NULL,
          OPEN_EXISTING,
          FILE_FLAG_OVERLAPPED,
          NULL);
  
      if (hDevice == INVALID_HANDLE_VALUE) {
          mvLog(MVLOG_ERROR, "Failed to open device. Error %d", GetLastError());
          return PCIE_HOST_ERROR;
      }
  
      *fd = hDevice;
  
      mvLog(MVLOG_DEBUG, "PCIe init completed, new fd %d", *fd);
      return PCIE_HOST_SUCCESS;
  }
  #else
  pcieHostError_t pcie_init(const char *slot, void **fd)
  {
      ASSERT_XLINK_PLATFORM_R(slot, PCIE_INVALID_PARAMETERS);
      ASSERT_XLINK_PLATFORM_R(fd, PCIE_INVALID_PARAMETERS);
  
      int mx_fd = open(slot, O_RDWR);
  
      if (mx_fd == -1) {
          return PCIE_HOST_DEVICE_NOT_FOUND;
      } else {
          if (!(*fd)) {
              *fd = (int *) malloc(sizeof(int));
          }
  
          if (!(*fd)) {
              mvLog(MVLOG_ERROR, "Memory allocation failed");
              close(mx_fd);
              return PCIE_HOST_ERROR;
          }
          *((int*)*fd) = mx_fd;
      }
  
      return PCIE_HOST_SUCCESS;
  }
  #endif
  
  pcieHostError_t pcie_close(void *fd)
  {
      ASSERT_XLINK_PLATFORM_R(fd, PCIE_INVALID_PARAMETERS);
  #if (defined(_WIN32) || defined(_WIN64))
      HANDLE hDevice = (HANDLE)fd;
      if (hDevice == INVALID_HANDLE_VALUE) {
          mvLog(MVLOG_ERROR, "Invalid device handle");
          return PCIE_HOST_ERROR;
      }
      CloseHandle(hDevice);
  
      return PCIE_HOST_SUCCESS;
  #else
  
      int mx_fd = *((int*) fd);
      close(mx_fd);
      free(fd);
  
      return PCIE_HOST_SUCCESS;
  #endif
  }
  
  #if (defined(_WIN32) || defined(_WIN64))
  int pci_count_devices(uint16_t vid, uint16_t pid)
  {
      int i;
      int deviceCnt = 0;
  
      HDEVINFO hDevInfo;
      SP_DEVINFO_DATA DeviceInfoData;
      char hwid_buff[256];
      DeviceInfoData.cbSize = sizeof(DeviceInfoData);
  
      // List all connected PCI devices
      hDevInfo = SetupDiGetClassDevs(NULL, TEXT("PCI"), NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES);
      if (hDevInfo == INVALID_HANDLE_VALUE)
          return PCIE_HOST_ERROR;
  
      for (i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInfoData); i++)
      {
          DeviceInfoData.cbSize = sizeof(DeviceInfoData);
          if (!SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInfoData))
              break;
  
          if (!SetupDiGetDeviceRegistryPropertyA(hDevInfo, &DeviceInfoData, SPDRP_HARDWAREID, NULL, (PBYTE)hwid_buff, sizeof(hwid_buff), NULL)) {
              continue;
          }
  
          uint16_t venid, devid;
          if (sscanf_s(hwid_buff, "PCI\\VEN_%hx&DEV_%hx", (int16_t *)&venid, (int16_t *)&devid) != 2) {
              continue;
          }
          if (venid == vid && devid == pid)
          {
              deviceCnt++;
          }
      }
      return deviceCnt;
  }
  #endif  // (defined(_WIN32) || defined(_WIN64))
  
  pcieHostError_t pcie_find_device_port(
      int index, char* port_name, int name_length, const pciePlatformState_t requiredState) {
      ASSERT_XLINK_PLATFORM_R(port_name, PCIE_INVALID_PARAMETERS);
      ASSERT_XLINK_PLATFORM_R(index >= 0, PCIE_INVALID_PARAMETERS);
      ASSERT_XLINK_PLATFORM_R(name_length > 0, PCIE_INVALID_PARAMETERS);
  
      pcieHostError_t rc = PCIE_HOST_DEVICE_NOT_FOUND;
  
      char found_device[XLINK_MAX_NAME_SIZE] = { 0 };
      pciePlatformState_t platformState;
  
  #if (defined(_WIN32) || defined(_WIN64))
      int amoutOfMyriadPCIeDevices = pci_count_devices(PCIE_VENDOR_ID, PCIE_DEVICE_ID);
      if (amoutOfMyriadPCIeDevices <= 0)
          return PCIE_HOST_DEVICE_NOT_FOUND;
  
      int amountOfSuitableDevices = 0;
      int deviceCount = 0;
  
      while (deviceCount < amoutOfMyriadPCIeDevices) {
          snprintf(found_device, XLINK_MAX_NAME_SIZE, "%s%d", "\\\\.\\mxlink", deviceCount);
  
          // Get state of device
          if (pcie_get_device_state(found_device, &platformState) != 0) {
              return PCIE_HOST_ERROR;   // Get device state step failed
          }
  
          // Found device suits requested state
          if (platformState == requiredState || requiredState == PCIE_PLATFORM_ANY_STATE) {
              // If port_name is specified, we search for specific device
              if (strnlen(port_name, name_length) > 1 &&
                  strncmp(port_name, found_device, name_length) == 0) {
                  rc = PCIE_HOST_SUCCESS;
                  break;
                  // Trying to find device which suits requirements and index
              }
              else if (amountOfSuitableDevices == index) {
                  mv_strncpy(port_name, name_length,
                      found_device, XLINK_MAX_NAME_SIZE - 1);
                  rc = PCIE_HOST_SUCCESS;
                  break;
              }
              ++amountOfSuitableDevices;
          }
          ++deviceCount;
      }
  
      return rc;
  #else
      struct dirent *entry;
      DIR *dp;
  
      dp = opendir("/sys/class/mxlk/");
      if (dp == NULL) {
          return PCIE_HOST_DRIVER_NOT_LOADED;
      }
  
      // All entries in this (virtual) directory are generated when the driver
      // is loaded, and correspond 1:1 to entries in /dev/
      int device_cnt = 0;
      while((entry = readdir(dp))) {
          // Compare the beginning of the name to make sure it is a device name
          if (strncmp(entry->d_name, "mxlk", 4) == 0)
          {
              // Save name
              snprintf(found_device, name_length, "/dev/%s", entry->d_name);
              // Get state of device
              if (pcie_get_device_state(found_device, &platformState) != 0) {
                  closedir(dp);
                  return PCIE_HOST_ERROR;   // Get device state step failed
              }
  
              // Found device suits requested state
              if (platformState == requiredState || requiredState == PCIE_PLATFORM_ANY_STATE) {
                  // If port_name is specified, we search for specific device
                  if (strnlen(port_name, name_length) > 1 &&
                      strncmp(port_name, found_device, name_length) == 0) {
                      rc = PCIE_HOST_SUCCESS;
                      break;
                      // Trying to find device which suits requirements and index
                  } else if (device_cnt == index){
                      mv_strncpy(port_name, name_length,
                                 found_device, XLINK_MAX_NAME_SIZE - 1);
                      rc = PCIE_HOST_SUCCESS;
                      break;
                  }
                  ++device_cnt;
              }
          }
      }
      closedir(dp);
  
      return rc;
  #endif  // (!defined(_WIN32) && !defined(_WIN64))
  }
  
  #if (!defined(_WIN32) && !defined(_WIN64))
  pcieHostError_t pcie_reset_device(int fd)
  {
      ASSERT_XLINK_PLATFORM_R(fd, PCIE_INVALID_PARAMETERS);
      int ret = ioctl(fd, MXLK_RESET_DEV);
  
      if (ret) {
          mvLog(MVLOG_INFO, "Reset ioctl failed with error: %d", ret);
          return PCIE_HOST_ERROR;
      }
      return PCIE_HOST_SUCCESS;
  }
  #else
  pcieHostError_t pcie_reset_device(HANDLE fd)
  {
      ASSERT_XLINK_PLATFORM_R(fd, PCIE_INVALID_PARAMETERS);
  
      BOOL bResult   = FALSE;
      DWORD junk     = 0;                     // discard results
      int output_buffer;
  
      mvLog(MVLOG_DEBUG, "calling Windows RESET DeviceIoControl fd %d", fd);
  
      bResult = DeviceIoControl(fd,                    // device to be queried
                                MXLK_RESET_DEV,                // operation to perform
                                NULL, 0,                       // no input buffer
                                &output_buffer, sizeof(output_buffer), // output buffer
                                &junk,                         // # bytes returned
                                (LPOVERLAPPED) NULL);          // synchronous I/O
  
      if (!bResult) {
          mvLog(MVLOG_ERROR, "RESET failed(status = %d).", GetLastError());
          return PCIE_HOST_ERROR;
      }
      return PCIE_HOST_SUCCESS;
  }
  #endif
  
  #if !defined(_WIN32)
  pcieHostError_t pcie_boot_device(int fd, const char *buffer, size_t length)
  #else
  pcieHostError_t pcie_boot_device(HANDLE fd, const char  *buffer, size_t length)
  #endif
  {
      ASSERT_XLINK_PLATFORM_R(fd, PCIE_INVALID_PARAMETERS);
      ASSERT_XLINK_PLATFORM_R(buffer, PCIE_INVALID_PARAMETERS);
  
      // Get device context
      enum mx_fw_status fw_status = MX_FW_STATUS_UNKNOWN_STATE;
      int deviceStatusRC = getDeviceFwStatusIOCTL(fd, &fw_status);
  
      if (deviceStatusRC) {
          return PCIE_HOST_ERROR;
      }
  
      // After reset PCIe device will lose context, so we need to restore in by calling reset
      if (fw_status == MX_FW_STATUS_UNKNOWN_STATE) {
          // Device may be not ready if reset context just after reset
          sleepForSeconds(3);
  
          int resetDeviceRC = pcie_reset_device(fd);
          if (resetDeviceRC) {
              mvLog(MVLOG_ERROR, "Device resetting failed with error: %d\n", resetDeviceRC);
              return resetDeviceRC;
          }
      }
  #if !defined(_WIN32)
      struct mxlk_boot_param boot_param;
  
      boot_param.buffer = buffer;
      boot_param.length = length;
  
      int ret = ioctl(fd, MXLK_BOOT_DEV, &boot_param);
  
      if (ret) {
          mvLog(MVLOG_INFO, "Boot ioctl failed with error: %d", ret);
          return PCIE_HOST_ERROR;
      }
      return PCIE_HOST_SUCCESS;
  #else
      BOOL bResult   = FALSE;
      DWORD junk     = 0;                     // discard results
      int output_buffer;
  
      mvLog(MVLOG_DEBUG, "calling Windows BOOT DeviceIoControl %d, buffer = %p, size = %d",fd, buffer, length);
  
      bResult = DeviceIoControl(fd,                    // device to be queried
                                MXLK_BOOT_DEV,                 // operation to perform
                                (void*)buffer, (DWORD)length,
                                &output_buffer, sizeof(output_buffer), // output buffer
                                &junk,                         // # bytes returned
                                (LPOVERLAPPED) NULL);          // synchronous I/O
      if (!bResult) {
          mvLog(MVLOG_ERROR, "BOOT failed(status = %d)", GetLastError());
          return PCIE_HOST_ERROR;
      }
      return PCIE_HOST_SUCCESS;
  #endif
  }
  
  pcieHostError_t pcie_get_device_state(const char *port_name, pciePlatformState_t *platformState) {
      ASSERT_XLINK_PLATFORM_R(port_name, PCIE_INVALID_PARAMETERS);
      ASSERT_XLINK_PLATFORM_R(platformState, PCIE_INVALID_PARAMETERS);
      pcieHostError_t retCode = PCIE_HOST_SUCCESS;
  
  #if (!defined(_WIN32) && !defined(_WIN64))       // Linux implementation
      int mx_fd = open(port_name, O_RDONLY);
  
      if (mx_fd == -1) {
          // driver returns EACCESS in case it instance already used.
          *platformState = PCIE_PLATFORM_BOOTED;
      } else {
          enum mx_fw_status fw_status = MX_FW_STATUS_UNKNOWN_STATE;
          int deviceStatusRC = getDeviceFwStatusIOCTL(mx_fd, &fw_status);
  
          if(deviceStatusRC){
              *platformState = PCIE_PLATFORM_ANY_STATE;
              retCode = PCIE_HOST_DEVICE_NOT_FOUND;
          } else if(fw_status == MX_FW_STATUS_USER_APP) {
              *platformState = PCIE_PLATFORM_BOOTED;
          } else {
              *platformState = PCIE_PLATFORM_UNBOOTED;
          }
  
          close(mx_fd);
      }
  #else                                           // Windows implementation
      HANDLE hDevice = INVALID_HANDLE_VALUE;  // handle to the drive to be examined
      DWORD junk     = 0;                     // discard results
  
      hDevice = CreateFile(port_name,         // drive to open
                           0,                 // no access to the drive
                           FILE_SHARE_READ |  // share mode
                           FILE_SHARE_WRITE,
                           NULL,              // default security attributes
                           OPEN_EXISTING,     // disposition
                           0,                 // file attributes
                           NULL);             // do not copy file attributes
  
      if (hDevice == INVALID_HANDLE_VALUE){   // cannot open the drive
          mvLog(MVLOG_DEBUG, "No PCIE device found: %s. Error %d", port_name, GetLastError());
          *platformState = PCIE_PLATFORM_ANY_STATE;
          return PCIE_HOST_DEVICE_NOT_FOUND;
      }
      enum mx_fw_status fw_status = MX_FW_STATUS_USER_APP;
      int deviceStatusRC = getDeviceFwStatusIOCTL(hDevice, &fw_status);
  
      if (deviceStatusRC) {
          *platformState = PCIE_PLATFORM_ANY_STATE;
          retCode = PCIE_HOST_DEVICE_NOT_FOUND;
          mvLog(MVLOG_DEBUG, "PCIE_PLATFORM_ANY_STATE");
      } else if (fw_status == MX_FW_STATUS_USER_APP) {
          *platformState = PCIE_PLATFORM_BOOTED;
          mvLog(MVLOG_DEBUG, "PCIE_PLATFORM_BOOTED");
      } else {
          *platformState = PCIE_PLATFORM_UNBOOTED;
          mvLog(MVLOG_DEBUG, "PCIE_PLATFORM_UNBOOTED");
      }
  
      CloseHandle(hDevice);
  #endif
      return retCode;
  }
  