Repository: oneapi-src/unified-memory-framework (language: python)

Issue:
Backward compatibility for configs of pools/providers

## Rationale

UMF should provide backward compatible interfaces.

## Description

### Background

MPI team experienced the issue after the PR #692 extends `level_zero_memory_provider_params_t` config structure. The root cause of the issue relates to how MPI instantiate/initialize the L0 provider config. They did the following:
```c
level_zero_memory_provider_params_t l0_config = {
    .level_zero_context_handle = hContext;
    .level_zero_device_handle = hDevice;
    .memory_type = UMF_MEMORY_TYPE_DEVICE;
};

umf_memory_provider_handle_t hProvider = NULL;
umf_memory_provider_ops_t *l0_ops = umfLevelZeroMemoryProviderOps();
umfMemoryProviderCreate(l0_ops, &l0_config, &hProvider);
```

After PR #692 the code above start crashing because two new fields were added to the `level_zero_memory_provider_params_t` data structure and the example above does not initialize these new fields:
```c
typedef struct level_zero_memory_provider_params_t {
    ze_context_handle_t level_zero_context_handle;
    ze_device_handle_t level_zero_device_handle;
    umf_usm_memory_type_t memory_type;

    // New fields
    ze_device_handle_t *resident_device_handles;
    uint32_t resident_device_count;
} level_zero_memory_provider_params_t;
```

A quick fix for the issue was to init the config data structure with `0` and than assign the required fields:
```c
level_zero_memory_provider_params_t l0_config = { 0 }; // zero-initialize
l0_config.level_zero_context_handle = hContext;
l0_config.level_zero_device_handle = hDevice;
l0_config.memory_type = UMF_MEMORY_TYPE_DEVICE;
```

### Open Questions

The quick fix above works only because it is OK to init fields of the `level_zero_memory_provider_params_t` structure with zeroes. But there are 2 related major question we should address:

#### 1. How to initialize the configs with default values?

In general case, not every field of the config data structure could/should be initialized with zeroes.

#### 2. How to support backward compatibility?

Config data structures are defined in the headers. If application was built with old version of UMF but on the system there is a newer version than even the application initialize all config's fields properly it initializes only fields that exists in the old version of UMF.
Consider an example. There is a `provider_foo` and corresponding `foo_config_t` structure that contains an int field in the 1st version:

```c
// provider_foo.h:
typedef struct foo_params_t {
    int field 1;
} foo_config_t;

// provider_foo.c:
umf_result_t foo_provider_initialize(void *params, void **provider) {
    foo_params_t *foo_params = (foo_params_t *)params;
    // expected sizeof(foo_params_t) is equal sizeof(int) that is 4 bytes

    assert(foo_params->field1 == 0);
}

// application code:
int main() {
    foo_params_t foo_params = {0};

    umf_memory_provider_handle_t hProvider = NULL;
    umf_memory_provider_ops_t *foo_ops = umfFooMemoryProviderOps();
    umfMemoryProviderCreate(foo_ops, &foo_params, &hProvider);
}
```

Now in the 2nd version of UMF we extend the `foo_params_t` structure with additional field:

```c
// provider_foo.h:
typedef struct foo_params_t {
    int field 1;
    int field2;
} foo_config_t;

// provider_foo.c:
umf_result_t foo_provider_initialize(void *params, void **provider) {
    foo_params_t *foo_params = (foo_params_t *)params;
    // expected sizeof(foo_params_t) is equal 2*sizeof(int) that is 8 bytes

    assert(foo_params->field1 == 0);
    assert(foo_params->field2 == 0); // ERROR: if application was compiled with 1st version of the UMF lib then the size of memory pointed by `params` is 4 bytes
}
```

## Possible API Changes

### Option 1: handle-based approach
Do not expose the config structure in interfaces. The config object is allocated inside libumf.so and handle to the config is returned to the client code. Setter/getter APIs are used to setup config parameters. For example:

```c
foo_params_handle_t foo_params = umfCreateFooParams();
umfFooParamsSetField1(foo_params, 7);

umf_memory_provider_handle_t hProvider = NULL;
umf_memory_provider_ops_t *foo_ops = umfFooMemoryProviderOps();
umfMemoryProviderCreate(foo_ops, foo_params, &hProvider);
```

Client code does not depend on the layout of the `foo_params_t` structure only UMF knows it and can change in different versions.

### Option 2: explicitly pass the size of params 

Data structures that define configs for the pools/providers remains in headers (the same as today). The new fields can be added only to the end. Application explicitly passes the size of the config data structure. Provider/pool implementation determine the version of the config based on the size. To init the config data structure we need to introduce a special macros or header-based inline functions. For example:

```c
// Version 1 provider_foo.h:
typedef struct foo_params_t {
    int field 1;
} foo_config_t;

inline void umfFooParamsInit(foo_params_t *params) {
     params->field1 = 0;
}

// Version 2 provider_foo.h:
typedef struct foo_params_t {
    int field 1;
    int field2;
} foo_config_t;

inline void umfFooParamsInit(foo_params_t *params) {
     params->field1 = 0;
     params->field2 = 0;
}

// Version 2 provider_foo.c:
umf_result_t foo_provider_initialize(void *params, size_t params_size, void **provider) {
    foo_params_t foo_params;
    umfFooParamsInit(& foo_params);

    if(sizeof(foo_params_t) == params_size) {
         // current version, just copy input params as is.
        foo_params = *(foo_params_t*)params;
    } else {
        // old version, copy only first `params_size` bytes and keep the rest default initialized
        memcpy(& foo_params, params, params_size);
    }

    assert(foo_params->field1 == 0);
    assert(foo_params->field2 == 0);
}

// application code:
int main() {
    foo_params_t foo_params;
    umfFooParamsInit(&params);

    umf_memory_provider_handle_t hProvider = NULL;
    umf_memory_provider_ops_t *foo_ops = umfFooMemoryProviderOps();
    umfMemoryProviderCreate(foo_ops, &foo_params, sizeof(foo_params_t), &hProvider);
}
```

### Option 3: store version in the params data structure

Similar to Option 2, but instead of using the size of params data structure to determine the version store it explicitly as a first field in the params data structure.

```c
// Version 1 provider_foo.h:
typedef struct foo_params_t {
    int version;
    int field 1;
} foo_config_t;

inline void umfFooParamsInit(foo_params_t *params) {
    version = 1;
    params->field1 = 0;
}

// Version 2 provider_foo.h:
typedef struct foo_params_t {
    int field 1;
    int field2;
} foo_config_t;

inline void umfFooParamsInit(foo_params_t *params) {
    version = 2;
    params->field1 = 0;
    params->field2 = 0;
}

// Version 2 provider_foo.c:
umf_result_t foo_provider_initialize(void *params, size_t params_size, void **provider) {
    foo_params_t foo_params;
    umfFooParamsInit(& foo_params);

    int params_version = *(int *)params;

    switch(params_version) {
        case 1:
            ...
            break;
        case 2:
            ...
            break;
        default:
            LOG_ERR("Wrong version");
    }

    assert(foo_params->field1 == 0);
    assert(foo_params->field2 == 0);
}

// application code:
int main() {
    foo_params_t foo_params;
    umfFooParamsInit(&params);

    umf_memory_provider_handle_t hProvider = NULL;
    umf_memory_provider_ops_t *foo_ops = umfFooMemoryProviderOps();
    umfMemoryProviderCreate(foo_ops, &foo_params, &hProvider);
}
```

### Design Considerations

I prefer Option 1 over Option 2 and Option 3. I think it provides best ABI compatibility. Another advantage is that the config data structure always initialized when user calls `umfCreateFooParams()`. There is no way to get non-initialized fields in the config, while in case of Option 2 or 3 user might forget to call `umfFooParamsInit(&params)` function.

Between Options 2 and 3, I prefer Option 3 because of two things:

- it stores version explicitly.
- the `umfMemoryProviderCreate` API remains unchanged.

## Implementation details

TBD




Discussion / hints:
We must address this issue before the first stable release because all three options require non-backward compatible changes.
:+1: to option 1.

Another option is to do what UR does and create a "chain" (linked list) of parameters.
```
struct foo_param {
  param_type type;
  int foo;
  void *next;
}

struct bar_param {
  param_type type;
  int bar;
  void *next;
}

struct foo_param foo = { PARAM_FOO, 5, nullptr };
struct bar_param bar = { PARAM_BAR, 10, &foo };

someApi(&bar); // this would apply all options in the chain.

```

This is super verbose, but really flexible. One downside is that it's on the api function to validate the parameters, and users need to remember to correctly set param type.

I also vote for option 1. What UR does is also OK but from my experience it's quite easy to forget setting the type (and we even had a bug in UMF where we set the type incorrectly).

One question: how's responsible for destroying the params - user or UMF?
> One question: how's responsible for destroying the params - user or UMF?

In the case of option 1, User calls `umfCreateFooParams()` function which internally allocates required data structure. After pool/provider is created user should call the appropriate `umfDestroyFooParams` function to tell UMF library that params data structure should be destroyed. 
Option 1 > Option 3 >>>> Option 2

~The same issue we have with other structures in public header. Also we have the same issue with other structures, like memprovider ops - in case of those structures probably option 3 (version field) is the way.~

Edit. I checked code and i found that we already have version in ops structure so it is fine. 

Fix patch:
diff --git a/include/umf/providers/provider_devdax_memory.h b/include/umf/providers/provider_devdax_memory.h
index 113d38372..0fb5218bc 100644
--- a/include/umf/providers/provider_devdax_memory.h
+++ b/include/umf/providers/provider_devdax_memory.h
@@ -18,15 +18,43 @@ extern "C" {
 #define UMF_DEVDAX_RESULTS_START_FROM 2000
 /// @endcond
 
-/// @brief Memory provider settings struct
-typedef struct umf_devdax_memory_provider_params_t {
-    /// path of the device DAX
-    char *path;
-    /// size of the device DAX in bytes
-    size_t size;
-    /// combination of 'umf_mem_protection_flags_t' flags
-    unsigned protection;
-} umf_devdax_memory_provider_params_t;
+struct umf_devdax_memory_provider_params_t;
+
+typedef struct umf_devdax_memory_provider_params_t
+    *umf_devdax_memory_provider_params_handle_t;
+
+/// @brief  Create a struct to store parameters of the Devdax Memory Provider.
+/// @param  hParams [out] handle to the newly created parameters struct.
+/// @param  path [in] path of the device DAX.
+/// @param  size [in] size of the device DAX in bytes.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfDevDaxMemoryProviderParamsCreate(
+    umf_devdax_memory_provider_params_handle_t *hParams, const char *path,
+    size_t size);
+
+/// @brief  Destroy parameters struct.
+/// @param  hParams [in] handle to the parameters of the Devdax Memory Provider.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfDevDaxMemoryProviderParamsDestroy(
+    umf_devdax_memory_provider_params_handle_t hParams);
+
+/// @brief  Set a device DAX in the parameters struct. Overwrites the previous value.
+///         It provides an ability to use the same instance of params to create multiple
+///         instances of the provider for different DAX devices.
+/// @param  hParams [in] handle to the parameters of the Devdax Memory Provider.
+/// @param  path [in] path of the device DAX.
+/// @param  size [in] size of the device DAX in bytes.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfDevDaxMemoryProviderParamsSetDeviceDax(
+    umf_devdax_memory_provider_params_handle_t hParams, const char *path,
+    size_t size);
+
+/// @brief  Set the protection flags in the parameters struct.
+/// @param  hParams [in] handle to the parameters of the Devdax Memory Provider.
+/// @param  protection [in] combination of 'umf_mem_protection_flags_t' flags.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfDevDaxMemoryProviderParamsSetProtection(
+    umf_devdax_memory_provider_params_handle_t hParams, unsigned protection);
 
 /// @brief Devdax Memory Provider operation results
 typedef enum umf_devdax_memory_provider_native_error {
@@ -39,18 +67,6 @@ typedef enum umf_devdax_memory_provider_native_error {
 
 umf_memory_provider_ops_t *umfDevDaxMemoryProviderOps(void);
 
-/// @brief Create default params for the devdax memory provider
-static inline umf_devdax_memory_provider_params_t
-umfDevDaxMemoryProviderParamsDefault(char *path, size_t size) {
-    umf_devdax_memory_provider_params_t params = {
-        path, /* path of the device DAX */
-        size, /* size of the device DAX in bytes */
-        UMF_PROTECTION_READ | UMF_PROTECTION_WRITE, /* protection */
-    };
-
-    return params;
-}
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/libumf.def b/src/libumf.def
index ae1ba6809..0b4588bb8 100644
--- a/src/libumf.def
+++ b/src/libumf.def
@@ -23,6 +23,10 @@ EXPORTS
     umfCUDAMemoryProviderParamsSetDevice
     umfCUDAMemoryProviderParamsSetMemoryType
     umfDevDaxMemoryProviderOps
+    umfDevDaxMemoryProviderParamsCreate
+    umfDevDaxMemoryProviderParamsDestroy
+    umfDevDaxMemoryProviderParamsSetDeviceDax
+    umfDevDaxMemoryProviderParamsSetProtection
     umfFree
     umfFileMemoryProviderOps
     umfFileMemoryProviderParamsCreate
diff --git a/src/libumf.map b/src/libumf.map
index 923112384..41467bad5 100644
--- a/src/libumf.map
+++ b/src/libumf.map
@@ -17,6 +17,10 @@ UMF_1.0 {
         umfCUDAMemoryProviderParamsSetDevice;
         umfCUDAMemoryProviderParamsSetMemoryType;
         umfDevDaxMemoryProviderOps;
+        umfDevDaxMemoryProviderParamsCreate;
+        umfDevDaxMemoryProviderParamsDestroy;
+        umfDevDaxMemoryProviderParamsSetDeviceDax;
+        umfDevDaxMemoryProviderParamsSetProtection;
         umfFree;
         umfFileMemoryProviderOps;
         umfFileMemoryProviderParamsCreate;
diff --git a/src/provider/provider_devdax_memory.c b/src/provider/provider_devdax_memory.c
index 1179ed115..32407acbb 100644
--- a/src/provider/provider_devdax_memory.c
+++ b/src/provider/provider_devdax_memory.c
@@ -24,9 +24,41 @@ umf_memory_provider_ops_t *umfDevDaxMemoryProviderOps(void) {
     return NULL;
 }
 
+umf_result_t umfDevDaxMemoryProviderParamsCreate(
+    umf_devdax_memory_provider_params_handle_t *hParams, const char *path,
+    size_t size) {
+    (void)hParams;
+    (void)path;
+    (void)size;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
+umf_result_t umfDevDaxMemoryProviderParamsDestroy(
+    umf_devdax_memory_provider_params_handle_t hParams) {
+    (void)hParams;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
+umf_result_t umfDevDaxMemoryProviderParamsSetDeviceDax(
+    umf_devdax_memory_provider_params_handle_t hParams, const char *path,
+    size_t size) {
+    (void)hParams;
+    (void)path;
+    (void)size;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
+umf_result_t umfDevDaxMemoryProviderParamsSetProtection(
+    umf_devdax_memory_provider_params_handle_t hParams, unsigned protection) {
+    (void)hParams;
+    (void)protection;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
 #else // !defined(_WIN32) && !defined(UMF_NO_HWLOC)
 
 #include "base_alloc_global.h"
+#include "libumf.h"
 #include "utils_common.h"
 #include "utils_concurrency.h"
 #include "utils_log.h"
@@ -44,6 +76,13 @@ typedef struct devdax_memory_provider_t {
     unsigned protection; // combination of OS-specific protection flags
 } devdax_memory_provider_t;
 
+// DevDax Memory provider settings struct
+typedef struct umf_devdax_memory_provider_params_t {
+    char *path;
+    size_t size;
+    unsigned protection;
+} umf_devdax_memory_provider_params_t;
+
 typedef struct devdax_last_native_error_t {
     int32_t native_error;
     int errno_value;
@@ -511,4 +550,110 @@ umf_memory_provider_ops_t *umfDevDaxMemoryProviderOps(void) {
     return &UMF_DEVDAX_MEMORY_PROVIDER_OPS;
 }
 
+umf_result_t umfDevDaxMemoryProviderParamsCreate(
+    umf_devdax_memory_provider_params_handle_t *hParams, const char *path,
+    size_t size) {
+    libumfInit();
+    if (hParams == NULL) {
+        LOG_ERR("DevDax Memory Provider params handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    if (path == NULL) {
+        LOG_ERR("DevDax path is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    umf_devdax_memory_provider_params_handle_t params =
+        umf_ba_global_alloc(sizeof(*params));
+    if (params == NULL) {
+        LOG_ERR(
+            "Allocating memory for the DevDax Memory Provider params failed");
+        return UMF_RESULT_ERROR_OUT_OF_HOST_MEMORY;
+    }
+
+    params->path = NULL;
+    params->size = 0;
+    params->protection = UMF_PROTECTION_READ | UMF_PROTECTION_WRITE;
+
+    umf_result_t res =
+        umfDevDaxMemoryProviderParamsSetDeviceDax(params, path, size);
+    if (res != UMF_RESULT_SUCCESS) {
+        umf_ba_global_free(params);
+        return res;
+    }
+
+    *hParams = params;
+
+    return UMF_RESULT_SUCCESS;
+}
+
+umf_result_t umfDevDaxMemoryProviderParamsDestroy(
+    umf_devdax_memory_provider_params_handle_t hParams) {
+    if (hParams != NULL) {
+        umf_ba_global_free(hParams->path);
+        umf_ba_global_free(hParams);
+    }
+
+    return UMF_RESULT_SUCCESS;
+}
+
+umf_result_t umfDevDaxMemoryProviderParamsSetDeviceDax(
+    umf_devdax_memory_provider_params_handle_t hParams, const char *path,
+    size_t size) {
+    if (hParams == NULL) {
+        LOG_ERR("DevDax Memory Provider params handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    if (path == NULL) {
+        LOG_ERR("DevDax path is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    size_t path_len = strlen(path);
+    if (path_len == 0) {
+        LOG_ERR("DevDax path is empty");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    path_len += 1; // for the null terminator
+    char *new_path = umf_ba_global_alloc(path_len);
+    if (new_path == NULL) {
+        LOG_ERR("Allocating memory for the DevDax path failed");
+        return UMF_RESULT_ERROR_OUT_OF_HOST_MEMORY;
+    }
+
+    strncpy(new_path, path, path_len);
+
+    umf_ba_global_free(hParams->path);
+
+    hParams->path = new_path;
+    hParams->size = size;
+
+    return UMF_RESULT_SUCCESS;
+}
+
+umf_result_t umfDevDaxMemoryProviderParamsSetProtection(
+    umf_devdax_memory_provider_params_handle_t hParams, unsigned protection) {
+    if (hParams == NULL) {
+        LOG_ERR("DevDax Memory Provider params handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    // verify that protection contains only valid bits set
+    // (UMF_PROTECTION_MAX-1) - highest possible bit
+    // (UMF_PROTECTION_MAX-1) << 1 - next after highest possible bit
+    // ((UMF_PROTECTION_MAX-1) << 1) - 1 - all valid bits set
+    const unsigned VALID_FLAGS_ALL = ((UMF_PROTECTION_MAX - 1) << 1) - 1;
+    if (protection & ~VALID_FLAGS_ALL || protection == 0) {
+        LOG_ERR("Incorrect memory protection flags: %u", protection);
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    hParams->protection = protection;
+
+    return UMF_RESULT_SUCCESS;
+}
+
 #endif // !defined(_WIN32) && !defined(UMF_NO_HWLOC)


Test patch:
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 6ce94654a..bf9884dc9 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -343,6 +343,10 @@ else()
         NAME provider_file_memory_not_impl
         SRCS provider_file_memory_not_impl.cpp
         LIBS ${UMF_UTILS_FOR_TEST})
+    add_umf_test(
+        NAME provider_devdax_memory_not_impl
+        SRCS provider_devdax_memory_not_impl.cpp
+        LIBS ${UMF_UTILS_FOR_TEST})
 endif()
 
 if(UMF_DISABLE_HWLOC)
diff --git a/test/ipc_devdax_prov_consumer.c b/test/ipc_devdax_prov_consumer.c
index a8fd8211d..286b6de78 100644
--- a/test/ipc_devdax_prov_consumer.c
+++ b/test/ipc_devdax_prov_consumer.c
@@ -19,6 +19,7 @@ int main(int argc, char *argv[]) {
         return -1;
     }
 
+    int ret = 0;
     int port = atoi(argv[1]);
 
     char *path = getenv("UMF_TESTS_DEVDAX_PATH");
@@ -33,12 +34,22 @@ int main(int argc, char *argv[]) {
         return 0;
     }
 
-    umf_devdax_memory_provider_params_t devdax_params =
-        umfDevDaxMemoryProviderParamsDefault(path, atol(size));
+    umf_devdax_memory_provider_params_handle_t devdax_params = NULL;
+    umf_result_t umf_result =
+        umfDevDaxMemoryProviderParamsCreate(&devdax_params, path, atol(size));
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "[consumer] ERROR: creating DevDax Memory Provider "
+                        "params failed\n");
+        return -1;
+    }
 
     void *pool_params = NULL;
 
-    return run_consumer(port, umfScalablePoolOps(), pool_params,
-                        umfDevDaxMemoryProviderOps(), &devdax_params, memcopy,
-                        NULL);
+    ret = run_consumer(port, umfScalablePoolOps(), pool_params,
+                       umfDevDaxMemoryProviderOps(), devdax_params, memcopy,
+                       NULL);
+
+    umfDevDaxMemoryProviderParamsDestroy(devdax_params);
+
+    return ret;
 }
diff --git a/test/ipc_devdax_prov_producer.c b/test/ipc_devdax_prov_producer.c
index 90afe64dd..479c1f945 100644
--- a/test/ipc_devdax_prov_producer.c
+++ b/test/ipc_devdax_prov_producer.c
@@ -19,6 +19,7 @@ int main(int argc, char *argv[]) {
         return -1;
     }
 
+    int ret = 0;
     int port = atoi(argv[1]);
 
     char *path = getenv("UMF_TESTS_DEVDAX_PATH");
@@ -33,12 +34,22 @@ int main(int argc, char *argv[]) {
         return 0;
     }
 
-    umf_devdax_memory_provider_params_t devdax_params =
-        umfDevDaxMemoryProviderParamsDefault(path, atol(size));
+    umf_devdax_memory_provider_params_handle_t devdax_params = NULL;
+    umf_result_t umf_result =
+        umfDevDaxMemoryProviderParamsCreate(&devdax_params, path, atol(size));
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "[producer] ERROR: creating DevDax Memory Provider "
+                        "params failed\n");
+        return -1;
+    }
 
     void *pool_params = NULL;
 
-    return run_producer(port, umfScalablePoolOps(), pool_params,
-                        umfDevDaxMemoryProviderOps(), &devdax_params, memcopy,
-                        NULL);
+    ret = run_producer(port, umfScalablePoolOps(), pool_params,
+                       umfDevDaxMemoryProviderOps(), devdax_params, memcopy,
+                       NULL);
+
+    umfDevDaxMemoryProviderParamsDestroy(devdax_params);
+
+    return ret;
 }
diff --git a/test/poolFixtures.hpp b/test/poolFixtures.hpp
index 995db981b..e5ec85012 100644
--- a/test/poolFixtures.hpp
+++ b/test/poolFixtures.hpp
@@ -69,27 +69,6 @@ struct umfPoolTest : umf_test::test,
     void SetUp() override {
         test::SetUp();
 
-        auto [pool_ops, pool_params, provider_ops, provider_params,
-              coarse_params] = this->GetParam();
-        (void)pool_ops;
-        (void)pool_params;
-        (void)provider_params;
-        (void)coarse_params;
-
-        if (provider_ops == umfDevDaxMemoryProviderOps()) {
-            char *path = getenv("UMF_TESTS_DEVDAX_PATH");
-            if (path == nullptr || path[0] == 0) {
-                GTEST_SKIP()
-                    << "Test skipped, UMF_TESTS_DEVDAX_PATH is not set";
-            }
-
-            char *size = getenv("UMF_TESTS_DEVDAX_SIZE");
-            if (size == nullptr || size[0] == 0) {
-                GTEST_SKIP()
-                    << "Test skipped, UMF_TESTS_DEVDAX_SIZE is not set";
-            }
-        }
-
         pool = poolCreateExtUnique(this->GetParam());
     }
 
diff --git a/test/pools/jemalloc_coarse_devdax.cpp b/test/pools/jemalloc_coarse_devdax.cpp
index ae98ecf4b..350e053ab 100644
--- a/test/pools/jemalloc_coarse_devdax.cpp
+++ b/test/pools/jemalloc_coarse_devdax.cpp
@@ -7,14 +7,39 @@
 
 #include "pool_coarse.hpp"
 
+using devdax_params_unique_handle_t =
+    std::unique_ptr<umf_devdax_memory_provider_params_t,
+                    decltype(&umfDevDaxMemoryProviderParamsDestroy)>;
+
+devdax_params_unique_handle_t create_devdax_params() {
+    char *path = getenv("UMF_TESTS_DEVDAX_PATH");
+    char *size = getenv("UMF_TESTS_DEVDAX_SIZE");
+    if (path == nullptr || path[0] == 0 || size == nullptr || size[0] == 0) {
+        return devdax_params_unique_handle_t(
+            nullptr, &umfDevDaxMemoryProviderParamsDestroy);
+    }
+
+    umf_devdax_memory_provider_params_handle_t params = NULL;
+    umf_result_t res =
+        umfDevDaxMemoryProviderParamsCreate(&params, path, atol(size));
+    if (res != UMF_RESULT_SUCCESS) {
+        throw std::runtime_error(
+            "Failed to create DevDax Memory Provider params");
+    }
+
+    return devdax_params_unique_handle_t(params,
+                                         &umfDevDaxMemoryProviderParamsDestroy);
+}
+
 auto coarseParams = umfCoarseMemoryProviderParamsDefault();
-auto devdaxParams = umfDevDaxMemoryProviderParamsDefault(
-    getenv("UMF_TESTS_DEVDAX_PATH"), getenv("UMF_TESTS_DEVDAX_SIZE")
-                                         ? atol(getenv("UMF_TESTS_DEVDAX_SIZE"))
-                                         : 0);
+auto devdaxParams = create_devdax_params();
+
+static std::vector<poolCreateExtParams> poolParamsList =
+    devdaxParams.get()
+        ? std::vector<poolCreateExtParams>{poolCreateExtParams{
+              umfJemallocPoolOps(), nullptr, umfDevDaxMemoryProviderOps(),
+              devdaxParams.get(), &coarseParams}}
+        : std::vector<poolCreateExtParams>{};
 
 INSTANTIATE_TEST_SUITE_P(jemallocCoarseDevDaxTest, umfPoolTest,
-                         ::testing::Values(poolCreateExtParams{
-                             umfJemallocPoolOps(), nullptr,
-                             umfDevDaxMemoryProviderOps(), &devdaxParams,
-                             &coarseParams}));
+                         ::testing::ValuesIn(poolParamsList));
diff --git a/test/pools/scalable_coarse_devdax.cpp b/test/pools/scalable_coarse_devdax.cpp
index b5da7d242..1bf77c61c 100644
--- a/test/pools/scalable_coarse_devdax.cpp
+++ b/test/pools/scalable_coarse_devdax.cpp
@@ -7,14 +7,39 @@
 
 #include "pool_coarse.hpp"
 
+using devdax_params_unique_handle_t =
+    std::unique_ptr<umf_devdax_memory_provider_params_t,
+                    decltype(&umfDevDaxMemoryProviderParamsDestroy)>;
+
+devdax_params_unique_handle_t create_devdax_params() {
+    char *path = getenv("UMF_TESTS_DEVDAX_PATH");
+    char *size = getenv("UMF_TESTS_DEVDAX_SIZE");
+    if (path == nullptr || path[0] == 0 || size == nullptr || size[0] == 0) {
+        return devdax_params_unique_handle_t(
+            nullptr, &umfDevDaxMemoryProviderParamsDestroy);
+    }
+
+    umf_devdax_memory_provider_params_handle_t params = NULL;
+    umf_result_t res =
+        umfDevDaxMemoryProviderParamsCreate(&params, path, atol(size));
+    if (res != UMF_RESULT_SUCCESS) {
+        throw std::runtime_error(
+            "Failed to create DevDax Memory Provider params");
+    }
+
+    return devdax_params_unique_handle_t(params,
+                                         &umfDevDaxMemoryProviderParamsDestroy);
+}
+
 auto coarseParams = umfCoarseMemoryProviderParamsDefault();
-auto devdaxParams = umfDevDaxMemoryProviderParamsDefault(
-    getenv("UMF_TESTS_DEVDAX_PATH"), getenv("UMF_TESTS_DEVDAX_SIZE")
-                                         ? atol(getenv("UMF_TESTS_DEVDAX_SIZE"))
-                                         : 0);
+auto devdaxParams = create_devdax_params();
+
+static std::vector<poolCreateExtParams> poolParamsList =
+    devdaxParams.get()
+        ? std::vector<poolCreateExtParams>{poolCreateExtParams{
+              umfScalablePoolOps(), nullptr, umfDevDaxMemoryProviderOps(),
+              devdaxParams.get(), &coarseParams}}
+        : std::vector<poolCreateExtParams>{};
 
 INSTANTIATE_TEST_SUITE_P(scalableCoarseDevDaxTest, umfPoolTest,
-                         ::testing::Values(poolCreateExtParams{
-                             umfScalablePoolOps(), nullptr,
-                             umfDevDaxMemoryProviderOps(), &devdaxParams,
-                             &coarseParams}));
+                         ::testing::ValuesIn(poolParamsList));
diff --git a/test/provider_devdax_memory.cpp b/test/provider_devdax_memory.cpp
index c41fb8769..0fd0705da 100644
--- a/test/provider_devdax_memory.cpp
+++ b/test/provider_devdax_memory.cpp
@@ -63,16 +63,6 @@ struct umfProviderTest
     : umf_test::test,
       ::testing::WithParamInterface<providerCreateExtParams> {
     void SetUp() override {
-        char *path = getenv("UMF_TESTS_DEVDAX_PATH");
-        if (path == nullptr || path[0] == 0) {
-            GTEST_SKIP() << "Test skipped, UMF_TESTS_DEVDAX_PATH is not set";
-        }
-
-        char *size = getenv("UMF_TESTS_DEVDAX_SIZE");
-        if (size == nullptr || size[0] == 0) {
-            GTEST_SKIP() << "Test skipped, UMF_TESTS_DEVDAX_SIZE is not set";
-        }
-
         test::SetUp();
         providerCreateExt(this->GetParam(), &provider);
         umf_result_t umf_result = umfMemoryProviderGetMinPageSize(
@@ -154,9 +144,12 @@ TEST_F(test, test_if_mapped_with_MAP_SYNC) {
     }
 
     size_t size = atol(size_str);
-    auto params = umfDevDaxMemoryProviderParamsDefault(path, size);
+    umf_devdax_memory_provider_params_handle_t params = NULL;
+    umf_result = umfDevDaxMemoryProviderParamsCreate(&params, path, size);
+    ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
+    ASSERT_NE(params, nullptr);
 
-    umf_result = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(), &params,
+    umf_result = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(), params,
                                          &hProvider);
     ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
     ASSERT_NE(hProvider, nullptr);
@@ -179,15 +172,42 @@ TEST_F(test, test_if_mapped_with_MAP_SYNC) {
 
 // positive tests using test_alloc_free_success
 
-auto defaultDevDaxParams = umfDevDaxMemoryProviderParamsDefault(
-    getenv("UMF_TESTS_DEVDAX_PATH"),
-    atol(getenv("UMF_TESTS_DEVDAX_SIZE") ? getenv("UMF_TESTS_DEVDAX_SIZE")
-                                         : "0"));
+using devdax_params_unique_handle_t =
+    std::unique_ptr<umf_devdax_memory_provider_params_t,
+                    decltype(&umfDevDaxMemoryProviderParamsDestroy)>;
+
+devdax_params_unique_handle_t create_devdax_params() {
+    char *path = getenv("UMF_TESTS_DEVDAX_PATH");
+    char *size = getenv("UMF_TESTS_DEVDAX_SIZE");
+    if (path == nullptr || path[0] == 0 || size == nullptr || size[0] == 0) {
+        return devdax_params_unique_handle_t(
+            nullptr, &umfDevDaxMemoryProviderParamsDestroy);
+    }
+
+    umf_devdax_memory_provider_params_handle_t params = NULL;
+    umf_result_t res =
+        umfDevDaxMemoryProviderParamsCreate(&params, path, atol(size));
+    if (res != UMF_RESULT_SUCCESS) {
+        throw std::runtime_error(
+            "Failed to create DevDax Memory Provider params");
+    }
+
+    return devdax_params_unique_handle_t(params,
+                                         &umfDevDaxMemoryProviderParamsDestroy);
+}
+
+auto defaultDevDaxParams = create_devdax_params();
+
+static std::vector<providerCreateExtParams> devdaxProviderTestParamsList =
+    defaultDevDaxParams.get()
+        ? std::vector<providerCreateExtParams>{providerCreateExtParams{
+              umfDevDaxMemoryProviderOps(), defaultDevDaxParams.get()}}
+        : std::vector<providerCreateExtParams>{};
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(umfProviderTest);
 
 INSTANTIATE_TEST_SUITE_P(devdaxProviderTest, umfProviderTest,
-                         ::testing::Values(providerCreateExtParams{
-                             umfDevDaxMemoryProviderOps(),
-                             &defaultDevDaxParams}));
+                         ::testing::ValuesIn(devdaxProviderTestParamsList));
 
 TEST_P(umfProviderTest, create_destroy) {}
 
@@ -306,47 +326,160 @@ TEST_P(umfProviderTest, purge_force_INVALID_POINTER) {
                              UMF_DEVDAX_RESULT_ERROR_PURGE_FORCE_FAILED);
 }
 
-// negative tests
+// params tests
+
+TEST_F(test, params_protection_flag) {
+    umf_devdax_memory_provider_params_handle_t params = nullptr;
+    umf_result_t ret =
+        umfDevDaxMemoryProviderParamsCreate(&params, "/dev/dax0.0", 4096);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
+    ASSERT_NE(params, nullptr);
+
+    //test all valid combinations
+    for (unsigned protection = UMF_PROTECTION_NONE;
+         protection < (UMF_PROTECTION_MAX - 1) << 1; ++protection) {
+        ret = umfDevDaxMemoryProviderParamsSetProtection(params, protection);
+        ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
+    }
+
+    umfDevDaxMemoryProviderParamsDestroy(params);
+}
+
+// negative params tests
+
+TEST_F(test, params_invalid_protection_flag) {
+    umf_devdax_memory_provider_params_handle_t params = nullptr;
+    umf_result_t ret =
+        umfDevDaxMemoryProviderParamsCreate(&params, "/dev/dax0.0", 4096);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
+    ASSERT_NE(params, nullptr);
+
+    ret = umfDevDaxMemoryProviderParamsSetProtection(params, 0);
+    ASSERT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    for (unsigned protection = UMF_PROTECTION_NONE;
+         protection < (UMF_PROTECTION_MAX - 1) << 1; ++protection) {
+        unsigned invalid_protection = protection | (UMF_PROTECTION_MAX << 1);
+        ret = umfDevDaxMemoryProviderParamsSetProtection(params,
+                                                         invalid_protection);
+        ASSERT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+    }
+
+    umfDevDaxMemoryProviderParamsDestroy(params);
+}
+
+TEST_F(test, params_null_handle) {
+    auto ret =
+        umfDevDaxMemoryProviderParamsCreate(nullptr, "/dev/dax0.0", 4096);
+    ASSERT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    ret = umfDevDaxMemoryProviderParamsDestroy(nullptr);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
+
+    ret =
+        umfDevDaxMemoryProviderParamsSetDeviceDax(nullptr, "/dev/dax0.0", 4096);
+    ASSERT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    ret = umfDevDaxMemoryProviderParamsSetProtection(nullptr, 1);
+    ASSERT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+}
 
 TEST_F(test, create_empty_path) {
-    umf_memory_provider_handle_t hProvider = nullptr;
     const char *path = "";
-    auto wrong_params =
-        umfDevDaxMemoryProviderParamsDefault((char *)path, 4096);
-    auto ret = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(),
-                                       &wrong_params, &hProvider);
-    EXPECT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
-    EXPECT_EQ(hProvider, nullptr);
+    umf_devdax_memory_provider_params_handle_t wrong_params = NULL;
+    auto ret = umfDevDaxMemoryProviderParamsCreate(&wrong_params, path, 4096);
+    ASSERT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+    ASSERT_EQ(wrong_params, nullptr);
+}
+
+TEST_F(test, create_null_path) {
+    const char *path = nullptr;
+    umf_devdax_memory_provider_params_handle_t wrong_params = NULL;
+    auto ret = umfDevDaxMemoryProviderParamsCreate(&wrong_params, path, 4096);
+    ASSERT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+    ASSERT_EQ(wrong_params, nullptr);
+}
+
+TEST_F(test, set_empty_path) {
+    const char *path = "tmp";
+    const char *empty_path = "";
+    umf_devdax_memory_provider_params_handle_t params = NULL;
+    auto ret = umfDevDaxMemoryProviderParamsCreate(&params, path, 4096);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
+    ASSERT_NE(params, nullptr);
+
+    ret = umfDevDaxMemoryProviderParamsSetDeviceDax(params, empty_path, 4096);
+    ASSERT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    ret = umfDevDaxMemoryProviderParamsDestroy(params);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
+}
+
+TEST_F(test, set_null_path) {
+    const char *path = "tmp";
+    const char *null_path = nullptr;
+    umf_devdax_memory_provider_params_handle_t params = NULL;
+    auto ret = umfDevDaxMemoryProviderParamsCreate(&params, path, 4096);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
+    ASSERT_NE(params, nullptr);
+
+    ret = umfDevDaxMemoryProviderParamsSetDeviceDax(params, null_path, 4096);
+    ASSERT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    ret = umfDevDaxMemoryProviderParamsDestroy(params);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
 }
 
 TEST_F(test, create_wrong_path) {
     umf_memory_provider_handle_t hProvider = nullptr;
     const char *path = "/tmp/dev/dax0.0";
-    auto wrong_params =
-        umfDevDaxMemoryProviderParamsDefault((char *)path, 4096);
-    auto ret = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(),
-                                       &wrong_params, &hProvider);
+    umf_devdax_memory_provider_params_handle_t wrong_params = nullptr;
+
+    auto ret = umfDevDaxMemoryProviderParamsCreate(&wrong_params, path, 4096);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
+    ASSERT_NE(wrong_params, nullptr);
+
+    ret = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(), wrong_params,
+                                  &hProvider);
     EXPECT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
     EXPECT_EQ(hProvider, nullptr);
+
+    ret = umfDevDaxMemoryProviderParamsDestroy(wrong_params);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
 }
 
 TEST_F(test, create_wrong_path_not_exist) {
     umf_memory_provider_handle_t hProvider = nullptr;
     const char *path = "/dev/dax1.1";
-    auto wrong_params =
-        umfDevDaxMemoryProviderParamsDefault((char *)path, 4096);
-    auto ret = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(),
-                                       &wrong_params, &hProvider);
+    umf_devdax_memory_provider_params_handle_t wrong_params = nullptr;
+
+    auto ret = umfDevDaxMemoryProviderParamsCreate(&wrong_params, path, 4096);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
+    ASSERT_NE(wrong_params, nullptr);
+
+    ret = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(), wrong_params,
+                                  &hProvider);
     EXPECT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
     EXPECT_EQ(hProvider, nullptr);
+
+    ret = umfDevDaxMemoryProviderParamsDestroy(wrong_params);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
 }
 
 TEST_F(test, create_wrong_size_0) {
     umf_memory_provider_handle_t hProvider = nullptr;
     const char *path = "/dev/dax0.0";
-    auto wrong_params = umfDevDaxMemoryProviderParamsDefault((char *)path, 0);
-    auto ret = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(),
-                                       &wrong_params, &hProvider);
+    umf_devdax_memory_provider_params_handle_t wrong_params = nullptr;
+
+    auto ret = umfDevDaxMemoryProviderParamsCreate(&wrong_params, path, 0);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
+    ASSERT_NE(wrong_params, nullptr);
+
+    ret = umfMemoryProviderCreate(umfDevDaxMemoryProviderOps(), wrong_params,
+                                  &hProvider);
     EXPECT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
     EXPECT_EQ(hProvider, nullptr);
+
+    ret = umfDevDaxMemoryProviderParamsDestroy(wrong_params);
+    ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
 }
diff --git a/test/provider_devdax_memory_ipc.cpp b/test/provider_devdax_memory_ipc.cpp
index 071196c94..3941f66e9 100644
--- a/test/provider_devdax_memory_ipc.cpp
+++ b/test/provider_devdax_memory_ipc.cpp
@@ -15,38 +15,52 @@
 
 using umf_test::test;
 
-auto defaultDevDaxParams = umfDevDaxMemoryProviderParamsDefault(
-    getenv("UMF_TESTS_DEVDAX_PATH"),
-    atol(getenv("UMF_TESTS_DEVDAX_SIZE") ? getenv("UMF_TESTS_DEVDAX_SIZE")
-                                         : "0"));
+using devdax_params_unique_handle_t =
+    std::unique_ptr<umf_devdax_memory_provider_params_t,
+                    decltype(&umfDevDaxMemoryProviderParamsDestroy)>;
+
+devdax_params_unique_handle_t create_devdax_params() {
+    char *path = getenv("UMF_TESTS_DEVDAX_PATH");
+    char *size = getenv("UMF_TESTS_DEVDAX_SIZE");
+    if (path == nullptr || path[0] == 0 || size == nullptr || size[0] == 0) {
+        return devdax_params_unique_handle_t(
+            nullptr, &umfDevDaxMemoryProviderParamsDestroy);
+    }
+
+    umf_devdax_memory_provider_params_handle_t params = NULL;
+    umf_result_t res =
+        umfDevDaxMemoryProviderParamsCreate(&params, path, atol(size));
+    if (res != UMF_RESULT_SUCCESS) {
+        throw std::runtime_error(
+            "Failed to create DevDax Memory Provider params");
+    }
+
+    return devdax_params_unique_handle_t(params,
+                                         &umfDevDaxMemoryProviderParamsDestroy);
+}
+
+auto defaultDevDaxParams = create_devdax_params();
 
 HostMemoryAccessor hostAccessor;
 
 static std::vector<ipcTestParams> getIpcProxyPoolTestParamsList(void) {
     std::vector<ipcTestParams> ipcProxyPoolTestParamsList = {};
 
-    char *path = getenv("UMF_TESTS_DEVDAX_PATH");
-    if (path == nullptr || path[0] == 0) {
-        // skipping the test, UMF_TESTS_DEVDAX_PATH is not set
-        return ipcProxyPoolTestParamsList;
-    }
-
-    char *size = getenv("UMF_TESTS_DEVDAX_SIZE");
-    if (size == nullptr || size[0] == 0) {
-        // skipping the test, UMF_TESTS_DEVDAX_SIZE is not set
+    if (!defaultDevDaxParams.get()) {
+        // return empty list to skip the test
         return ipcProxyPoolTestParamsList;
     }
 
     ipcProxyPoolTestParamsList = {
         {umfProxyPoolOps(), nullptr, umfDevDaxMemoryProviderOps(),
-         &defaultDevDaxParams, &hostAccessor, true},
+         defaultDevDaxParams.get(), &hostAccessor, true},
 #ifdef UMF_POOL_JEMALLOC_ENABLED
         {umfJemallocPoolOps(), nullptr, umfDevDaxMemoryProviderOps(),
-         &defaultDevDaxParams, &hostAccessor, false},
+         defaultDevDaxParams.get(), &hostAccessor, false},
 #endif
 #ifdef UMF_POOL_SCALABLE_ENABLED
         {umfScalablePoolOps(), nullptr, umfDevDaxMemoryProviderOps(),
-         &defaultDevDaxParams, &hostAccessor, false},
+         defaultDevDaxParams.get(), &hostAccessor, false},
 #endif
     };
 
diff --git a/test/provider_devdax_memory_not_impl.cpp b/test/provider_devdax_memory_not_impl.cpp
new file mode 100644
index 000000000..3b97443a0
--- /dev/null
+++ b/test/provider_devdax_memory_not_impl.cpp
@@ -0,0 +1,30 @@
+// Copyright (C) 2024 Intel Corporation
+// Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "base.hpp"
+
+#include <umf/providers/provider_devdax_memory.h>
+
+using umf_test::test;
+
+TEST_F(test, devdax_provider_not_implemented) {
+    umf_devdax_memory_provider_params_handle_t params = nullptr;
+    umf_result_t umf_result =
+        umfDevDaxMemoryProviderParamsCreate(&params, "path", 4096);
+    EXPECT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED);
+    EXPECT_EQ(params, nullptr);
+
+    umf_result = umfDevDaxMemoryProviderParamsDestroy(nullptr);
+    EXPECT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED);
+
+    umf_result =
+        umfDevDaxMemoryProviderParamsSetDeviceDax(nullptr, "path", 4096);
+    EXPECT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED);
+
+    umf_result = umfDevDaxMemoryProviderParamsSetProtection(nullptr, 0);
+    EXPECT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED);
+
+    umf_memory_provider_ops_t *ops = umfDevDaxMemoryProviderOps();
+    EXPECT_EQ(ops, nullptr);
+}