On Mon, May 19, 2025 at 01:00:54PM -0700, Dave Jiang wrote:
> Add a unit test to verify the features ioctl commands. Test support added
> for locating a features device, retrieve and verify the supported features
> commands, retrieve specific feature command data, retrieve test feature
> data, and write and verify test feature data.
> 
> Acked-by: Dan Williams <dan.j.willi...@intel.com>
> Signed-off-by: Dave Jiang <dave.ji...@intel.com>
> ---
>  cxl/fwctl/cxl.h      |   2 +-
>  test/cxl-features.sh |  31 +++
>  test/fwctl.c         | 439 +++++++++++++++++++++++++++++++++++++++++++
>  test/meson.build     |  19 ++
>  4 files changed, 490 insertions(+), 1 deletion(-)
>  create mode 100755 test/cxl-features.sh
>  create mode 100644 test/fwctl.c
> 
> diff --git a/cxl/fwctl/cxl.h b/cxl/fwctl/cxl.h
> index 43f522f0cdcd..c560b2a1181d 100644
> --- a/cxl/fwctl/cxl.h
> +++ b/cxl/fwctl/cxl.h
> @@ -9,7 +9,7 @@
>  
>  #include <linux/types.h>
>  #include <linux/stddef.h>
> -#include <cxl/features.h>
> +#include "features.h"
>  
>  /**
>   * struct fwctl_rpc_cxl - ioctl(FWCTL_RPC) input for CXL
> diff --git a/test/cxl-features.sh b/test/cxl-features.sh
> new file mode 100755
> index 000000000000..3498fa08be53
> --- /dev/null
> +++ b/test/cxl-features.sh
> @@ -0,0 +1,31 @@
> +#!/bin/bash -Ex
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (C) 2025 Intel Corporation. All rights reserved.
> +
> +rc=77
> +# 237 is -ENODEV
> +ERR_NODEV=237
> +
> +. $(dirname $0)/common
> +FEATURES="$TEST_PATH"/fwctl
> +
> +trap 'err $LINENO' ERR
> +
> +modprobe cxl_test
> +
> +test -x "$FEATURES" || do_skip "no CXL Features Contrl"

Seems like a comment to help understand skip - 

# fwctl test is omitted when ndctl is built with -Dfwctl=disabled
# or the kernel is built without CONFIG_CXL_FEATURES enabled.

Now the above is assuming the .sh got in the test list and is 
not left out completely with -Dfwctl=disabled. Going to look.


> +# disable trap
> +trap - $(compgen -A signal)
> +"$FEATURES"
> +rc=$?
> +
> +echo "error: $rc"
> +if [ "$rc" -eq "$ERR_NODEV" ]; then
> +     do_skip "no CXL FWCTL char dev"
> +elif [ "$rc" -ne 0 ]; then
> +     echo "fail: $LINENO" && exit 1
> +fi
> +
> +trap 'err $LINENO' ERR
> +
> +_cxl_cleanup
> diff --git a/test/fwctl.c b/test/fwctl.c
> new file mode 100644
> index 000000000000..7a780e718872
> --- /dev/null
> +++ b/test/fwctl.c
> @@ -0,0 +1,439 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// Copyright (C) 2024-2025 Intel Corporation. All rights reserved.
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <stdio.h>
> +#include <endian.h>
> +#include <stdint.h>
> +#include <stdlib.h>
> +#include <syslog.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <cxl/libcxl.h>
> +#include <linux/uuid.h>
> +#include <uuid/uuid.h>
> +#include <util/bitmap.h>
> +#include <cxl/fwctl/features.h>
> +#include <cxl/fwctl/fwctl.h>
> +#include <cxl/fwctl/cxl.h>
> +
> +static const char provider[] = "cxl_test";
> +
> +UUID_DEFINE(test_uuid,
> +         0xff, 0xff, 0xff, 0xff,
> +         0xff, 0xff,
> +         0xff, 0xff,
> +         0xff, 0xff,
> +         0xff, 0xff, 0xff, 0xff, 0xff, 0xff
> +);
> +
> +#define CXL_MBOX_OPCODE_GET_SUPPORTED_FEATURES       0x0500
> +#define CXL_MBOX_OPCODE_GET_FEATURE          0x0501
> +#define CXL_MBOX_OPCODE_SET_FEATURE          0x0502
> +
> +#define GET_FEAT_SIZE        4
> +#define SET_FEAT_SIZE        4
> +#define EFFECTS_MASK (BIT(0) | BIT(9))
> +
> +#define MAX_TEST_FEATURES    1
> +#define DEFAULT_TEST_DATA    0xdeadbeef
> +#define DEFAULT_TEST_DATA2   0xabcdabcd
> +
> +struct test_feature {
> +     uuid_t uuid;
> +     size_t get_size;
> +     size_t set_size;
> +};
> +
> +static int send_command(int fd, struct fwctl_rpc *rpc, struct 
> fwctl_rpc_cxl_out *out)
> +{
> +     if (ioctl(fd, FWCTL_RPC, rpc) == -1) {
> +             fprintf(stderr, "RPC ioctl error: %s\n", strerror(errno));
> +             return -errno;
> +     }
> +
> +     if (out->retval) {
> +             fprintf(stderr, "operation returned failure: %d\n", 
> out->retval);
> +             return -ENXIO;
> +     }
> +
> +     return 0;
> +}
> +
> +static int get_scope(u16 opcode)
> +{
> +     switch (opcode) {
> +     case CXL_MBOX_OPCODE_GET_SUPPORTED_FEATURES:
> +     case CXL_MBOX_OPCODE_GET_FEATURE:
> +             return FWCTL_RPC_CONFIGURATION;
> +     case CXL_MBOX_OPCODE_SET_FEATURE:
> +             return FWCTL_RPC_DEBUG_WRITE_FULL;
> +     default:
> +             return -EINVAL;
> +     }
> +}
> +
> +static size_t hw_op_size(u16 opcode)
> +{
> +     switch (opcode) {
> +     case CXL_MBOX_OPCODE_GET_SUPPORTED_FEATURES:
> +             return sizeof(struct cxl_mbox_get_sup_feats_in);
> +     case CXL_MBOX_OPCODE_GET_FEATURE:
> +             return sizeof(struct cxl_mbox_get_feat_in);
> +     case CXL_MBOX_OPCODE_SET_FEATURE:
> +             return sizeof(struct cxl_mbox_set_feat_in) + sizeof(u32);
> +     default:
> +             return SIZE_MAX;
> +     }
> +}
> +
> +static void free_rpc(struct fwctl_rpc *rpc)
> +{
> +     void *in, *out;
> +
> +     in = (void *)rpc->in;
> +     out = (void *)rpc->out;
> +     free(in);
> +     free(out);
> +     free(rpc);
> +}
> +
> +static void *zmalloc_aligned(size_t align, size_t size)
> +{
> +     void *ptr;
> +     int rc;
> +
> +     rc = posix_memalign((void **)&ptr, align, size);
> +     if (rc)
> +             return NULL;
> +     memset(ptr, 0, size);
> +
> +     return ptr;
> +}
> +
> +static struct fwctl_rpc *get_prepped_command(size_t in_size, size_t out_size,
> +                                          u16 opcode)
> +{
> +     struct fwctl_rpc_cxl_out *out;
> +     struct fwctl_rpc_cxl *in;
> +     struct fwctl_rpc *rpc;
> +     size_t op_size;
> +     int scope;
> +
> +     rpc = zmalloc_aligned(16, sizeof(*rpc));
> +     if (!rpc)
> +             return NULL;
> +
> +     in = zmalloc_aligned(16, in_size);
> +     if (!in)
> +             goto free_rpc;
> +
> +     out = zmalloc_aligned(16, out_size);
> +     if (!out)
> +             goto free_in;
> +
> +     in->opcode = opcode;
> +
> +     op_size = hw_op_size(opcode);
> +     if (op_size == SIZE_MAX)
> +             goto free_in;
> +
> +     in->op_size = op_size;
> +
> +     rpc->size = sizeof(*rpc);
> +     scope = get_scope(opcode);
> +     if (scope < 0)
> +             goto free_all;
> +
> +     rpc->scope = scope;
> +
> +     rpc->in_len = in_size;
> +     rpc->out_len = out_size;
> +     rpc->in = (uint64_t)(uint64_t *)in;
> +     rpc->out = (uint64_t)(uint64_t *)out;
> +
> +     return rpc;
> +
> +free_all:
> +     free(out);
> +free_in:
> +     free(in);
> +free_rpc:
> +     free(rpc);
> +     return NULL;
> +}
> +
> +static int cxl_fwctl_rpc_get_test_feature(int fd, struct test_feature 
> *feat_ctx,
> +                                       const uint32_t expected_data)
> +{
> +     struct cxl_mbox_get_feat_in *feat_in;
> +     struct fwctl_rpc_cxl_out *out;
> +     size_t out_size, in_size;
> +     struct fwctl_rpc_cxl *in;
> +     struct fwctl_rpc *rpc;
> +     uint32_t val;
> +     void *data;
> +     int rc;
> +
> +     in_size = sizeof(*in) + sizeof(*feat_in);
> +     out_size = sizeof(*out) + feat_ctx->get_size;
> +
> +     rpc = get_prepped_command(in_size, out_size,
> +                               CXL_MBOX_OPCODE_GET_FEATURE);
> +     if (!rpc)
> +             return -ENXIO;
> +
> +     in = (struct fwctl_rpc_cxl *)rpc->in;
> +     out = (struct fwctl_rpc_cxl_out *)rpc->out;
> +
> +     feat_in = &in->get_feat_in;
> +     uuid_copy(feat_in->uuid, feat_ctx->uuid);
> +     feat_in->count = feat_ctx->get_size;
> +
> +     rc = send_command(fd, rpc, out);
> +     if (rc)
> +             goto out;
> +
> +     data = out->payload;
> +     val = le32toh(*(__le32 *)data);
> +     if (memcmp(&val, &expected_data, sizeof(val)) != 0) {
> +             rc = -ENXIO;
> +             goto out;
> +     }
> +
> +out:
> +     free_rpc(rpc);
> +     return rc;
> +}
> +
> +static int cxl_fwctl_rpc_set_test_feature(int fd, struct test_feature 
> *feat_ctx)
> +{
> +     struct cxl_mbox_set_feat_in *feat_in;
> +     struct fwctl_rpc_cxl_out *out;
> +     size_t in_size, out_size;
> +     struct fwctl_rpc_cxl *in;
> +     struct fwctl_rpc *rpc;
> +     uint32_t val;
> +     void *data;
> +     int rc;
> +
> +     in_size = sizeof(*in) + sizeof(*feat_in) + sizeof(val);
> +     out_size = sizeof(*out) + sizeof(val);
> +     rpc = get_prepped_command(in_size, out_size,
> +                               CXL_MBOX_OPCODE_SET_FEATURE);
> +     if (!rpc)
> +             return -ENXIO;
> +
> +     in = (struct fwctl_rpc_cxl *)rpc->in;
> +     out = (struct fwctl_rpc_cxl_out *)rpc->out;
> +     feat_in = &in->set_feat_in;
> +     uuid_copy(feat_in->uuid, feat_ctx->uuid);
> +     data = feat_in->feat_data;
> +     val = DEFAULT_TEST_DATA2;
> +     *(uint32_t *)data = htole32(val);
> +     feat_in->flags = CXL_SET_FEAT_FLAG_FULL_DATA_TRANSFER;
> +
> +     rc = send_command(fd, rpc, out);
> +     if (rc)
> +             goto out;
> +
> +     rc = cxl_fwctl_rpc_get_test_feature(fd, feat_ctx, DEFAULT_TEST_DATA2);
> +     if (rc) {
> +             fprintf(stderr, "Failed ioctl to get feature verify: %d\n", rc);
> +             goto out;
> +     }
> +
> +out:
> +     free_rpc(rpc);
> +     return rc;
> +}
> +
> +static int cxl_fwctl_rpc_get_supported_features(int fd, struct test_feature 
> *feat_ctx)
> +{
> +     struct cxl_mbox_get_sup_feats_out *feat_out;
> +     struct cxl_mbox_get_sup_feats_in *feat_in;
> +     struct fwctl_rpc_cxl_out *out;
> +     struct cxl_feat_entry *entry;
> +     size_t out_size, in_size;
> +     struct fwctl_rpc_cxl *in;
> +     struct fwctl_rpc *rpc;
> +     int feats, rc;
> +
> +     in_size = sizeof(*in) + sizeof(*feat_in);
> +     out_size = sizeof(*out) + sizeof(*feat_out);
> +     /* First query, to get number of features w/o per feature data */
> +     rpc = get_prepped_command(in_size, out_size,
> +                               CXL_MBOX_OPCODE_GET_SUPPORTED_FEATURES);
> +     if (!rpc)
> +             return -ENXIO;
> +
> +     /* No need to fill in feat_in first go as we are passing in all 0's */
> +
> +     out = (struct fwctl_rpc_cxl_out *)rpc->out;
> +     rc = send_command(fd, rpc, out);
> +     if (rc)
> +             goto out;
> +
> +     feat_out = &out->get_sup_feats_out;
> +     feats = le16toh(feat_out->supported_feats);
> +     if (feats != MAX_TEST_FEATURES) {
> +             fprintf(stderr, "Test device has greater than %d test 
> features.\n",
> +                     MAX_TEST_FEATURES);
> +             rc = -ENXIO;
> +             goto out;
> +     }
> +
> +     free_rpc(rpc);
> +
> +     /* Going second round to retrieve each feature details */
> +     in_size = sizeof(*in) + sizeof(*feat_in);
> +     out_size = sizeof(*out) + sizeof(*feat_out);
> +     out_size += feats * sizeof(*entry);
> +     rpc = get_prepped_command(in_size, out_size,
> +                               CXL_MBOX_OPCODE_GET_SUPPORTED_FEATURES);
> +     if (!rpc)
> +             return -ENXIO;
> +
> +     in = (struct fwctl_rpc_cxl *)rpc->in;
> +     out = (struct fwctl_rpc_cxl_out *)rpc->out;
> +     feat_in = &in->get_sup_feats_in;
> +     feat_in->count = htole32(feats * sizeof(*entry));
> +
> +     rc = send_command(fd, rpc, out);
> +     if (rc)
> +             goto out;
> +
> +     feat_out = &out->get_sup_feats_out;
> +     feats = le16toh(feat_out->supported_feats);
> +     if (feats != MAX_TEST_FEATURES) {
> +             fprintf(stderr, "Test device has greater than %u test 
> features.\n",
> +                     MAX_TEST_FEATURES);
> +             rc = -ENXIO;
> +             goto out;
> +     }
> +
> +     if (le16toh(feat_out->num_entries) != MAX_TEST_FEATURES) {
> +             fprintf(stderr, "Test device did not return expected entries. 
> %u\n",
> +                     le16toh(feat_out->num_entries));
> +             rc = -ENXIO;
> +             goto out;
> +     }
> +
> +     entry = &feat_out->ents[0];
> +     if (uuid_compare(test_uuid, entry->uuid) != 0) {
> +             fprintf(stderr, "Test device did not export expected test 
> feature.\n");
> +             rc = -ENXIO;
> +             goto out;
> +     }
> +
> +     if (le16toh(entry->get_feat_size) != GET_FEAT_SIZE ||
> +         le16toh(entry->set_feat_size) != SET_FEAT_SIZE) {
> +             fprintf(stderr, "Test device feature in/out size incorrect.\n");
> +             rc = -ENXIO;
> +             goto out;
> +     }
> +
> +     if (le16toh(entry->effects) != EFFECTS_MASK) {
> +             fprintf(stderr, "Test device set effects incorrect\n");
> +             rc = -ENXIO;
> +             goto out;
> +     }
> +
> +     uuid_copy(feat_ctx->uuid, entry->uuid);
> +     feat_ctx->get_size = le16toh(entry->get_feat_size);
> +     feat_ctx->set_size = le16toh(entry->set_feat_size);
> +
> +out:
> +     free_rpc(rpc);
> +     return rc;
> +}
> +
> +static int test_fwctl_features(struct cxl_memdev *memdev)
> +{
> +     struct test_feature feat_ctx;
> +     unsigned int major, minor;
> +     struct cxl_fwctl *fwctl;
> +     int fd, rc;
> +     char path[256];
> +
> +     fwctl = cxl_memdev_get_fwctl(memdev);
> +     if (!fwctl)
> +             return -ENODEV;
> +
> +     major = cxl_fwctl_get_major(fwctl);
> +     minor = cxl_fwctl_get_minor(fwctl);
> +
> +     if (!major && !minor)
> +             return -ENODEV;
> +
> +     sprintf(path, "/dev/char/%d:%d", major, minor);
> +
> +     fd = open(path, O_RDONLY, 0644);
> +     if (fd < 0) {
> +             fprintf(stderr, "Failed to open: %d\n", -errno);
> +             return -errno;
> +     }
> +
> +     rc = cxl_fwctl_rpc_get_supported_features(fd, &feat_ctx);
> +     if (rc) {
> +             fprintf(stderr, "Failed ioctl to get supported features: %d\n", 
> rc);
> +             goto out;
> +     }
> +
> +     rc = cxl_fwctl_rpc_get_test_feature(fd, &feat_ctx, DEFAULT_TEST_DATA);
> +     if (rc) {
> +             fprintf(stderr, "Failed ioctl to get feature: %d\n", rc);
> +             goto out;
> +     }
> +
> +     rc = cxl_fwctl_rpc_set_test_feature(fd, &feat_ctx);
> +     if (rc) {
> +             fprintf(stderr, "Failed ioctl to set feature: %d\n", rc);
> +             goto out;
> +     }
> +
> +out:
> +     close(fd);
> +     return rc;
> +}
> +
> +static int test_fwctl(struct cxl_ctx *ctx, struct cxl_bus *bus)
> +{
> +     struct cxl_memdev *memdev;
> +
> +     cxl_memdev_foreach(ctx, memdev) {
> +             if (cxl_memdev_get_bus(memdev) != bus)
> +                     continue;
> +             return test_fwctl_features(memdev);
> +     }
> +
> +     return 0;
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +     struct cxl_ctx *ctx;
> +     struct cxl_bus *bus;
> +     int rc;
> +
> +     rc = cxl_new(&ctx);
> +     if (rc < 0)
> +             return rc;
> +
> +     cxl_set_log_priority(ctx, LOG_DEBUG);
> +
> +     bus = cxl_bus_get_by_provider(ctx, provider);
> +     if (!bus) {
> +             fprintf(stderr, "%s: unable to find bus (%s)\n",
> +                     argv[0], provider);
> +             rc = -EINVAL;
> +             goto out;
> +     }
> +
> +     rc = test_fwctl(ctx, bus);
> +
> +out:
> +     cxl_unref(ctx);
> +     return rc;
> +}
> diff --git a/test/meson.build b/test/meson.build
> index 2fd7df5211dd..93b1d78671ef 100644
> --- a/test/meson.build
> +++ b/test/meson.build
> @@ -17,6 +17,13 @@ ndctl_deps = libndctl_deps + [
>    versiondep,
>  ]
>  
> +libcxl_deps = [
> +  cxl_dep,
> +  ndctl_dep,
> +  uuid,
> +  kmod,
> +]
> +
>  libndctl = executable('libndctl', testcore + [ 'libndctl.c'],
>    dependencies : libndctl_deps,
>    include_directories : root_inc,
> @@ -235,6 +242,18 @@ if get_option('keyutils').enabled()
>    ]
>  endif
>  
> +uuid_dep = dependency('uuid', required: false)
> +if get_option('fwctl').enabled() and uuid_dep.found()
> +  fwctl = executable('fwctl', 'fwctl.c',
> +    dependencies : libcxl_deps,
> +    include_directories : root_inc,
> +  )
> +  cxl_features = find_program('cxl-features.sh')
> +  tests += [
> +    [ 'cxl-features.sh',        cxl_features,       'cxl'   ],
> +  ]
> +endif
> +
>  foreach t : tests
>    test(t[0], t[1],
>      is_parallel : false,
> -- 
> 2.49.0
> 
> 

Reply via email to