diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index 686e0ea768f0..4d409babb99a 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -15,6 +15,8 @@ SYNOPSIS
 Walk the CXL capable device hierarchy in the system and list all device
 instances along with some of their major attributes.
 
+Options can be specified to limit the output to specific objects.
+
 EXAMPLE
 -------
 ----
diff --git a/cxl/filter.c b/cxl/filter.c
index 405b653d80b2..d1ff4b62c806 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -1,10 +1,16 @@
 // SPDX-License-Identifier: GPL-2.0
 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved.
+#include <errno.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
+#include <util/log.h>
+#include <util/json.h>
 #include <cxl/libcxl.h>
+#include <json-c/json.h>
+
 #include "filter.h"
+#include "json.h"
 
 static const char *which_sep(const char *filter)
 {
@@ -48,3 +54,47 @@ struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev,
 		return memdev;
 	return NULL;
 }
+
+static unsigned long params_to_flags(struct cxl_filter_params *param)
+{
+	unsigned long flags = 0;
+
+	if (param->idle)
+		flags |= UTIL_JSON_IDLE;
+	if (param->human)
+		flags |= UTIL_JSON_HUMAN;
+	if (param->health)
+		flags |= UTIL_JSON_HEALTH;
+	return flags;
+}
+
+int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
+{
+	struct json_object *jplatform = json_object_new_array();
+	unsigned long flags = params_to_flags(p);
+	struct cxl_memdev *memdev;
+
+	if (!jplatform) {
+		dbg(p, "platform object allocation failure\n");
+		return -ENOMEM;
+	}
+
+	cxl_memdev_foreach(ctx, memdev) {
+		struct json_object *jdev;
+
+		if (!util_cxl_memdev_filter(memdev, p->memdev_filter))
+			continue;
+		if (p->memdevs) {
+			jdev = util_cxl_memdev_to_json(memdev, flags);
+			if (!jdev) {
+				dbg(p, "memdev object allocation failure\n");
+				continue;
+			}
+			json_object_array_add(jplatform, jdev);
+		}
+	}
+
+	util_display_json_array(stdout, jplatform, flags);
+
+	return 0;
+}
diff --git a/cxl/filter.h b/cxl/filter.h
index da800336b528..664b74b23b0a 100644
--- a/cxl/filter.h
+++ b/cxl/filter.h
@@ -1,7 +1,21 @@
 /* SPDX-License-Identifier: GPL-2.0 */
-/* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */
+/* Copyright (C) 2021 Intel Corporation. All rights reserved. */
 #ifndef _CXL_UTIL_FILTER_H_
 #define _CXL_UTIL_FILTER_H_
+
+#include <stdbool.h>
+#include <util/log.h>
+
+struct cxl_filter_params {
+	const char *memdev_filter;
+	bool memdevs;
+	bool idle;
+	bool human;
+	bool health;
+	struct log_ctx ctx;
+};
+
 struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev,
-		const char *ident);
+					  const char *ident);
+int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *param);
 #endif /* _CXL_UTIL_FILTER_H_ */
diff --git a/cxl/list.c b/cxl/list.c
index 7f7a04d9a6e5..17303073b49a 100644
--- a/cxl/list.c
+++ b/cxl/list.c
@@ -9,60 +9,27 @@
 #include <json-c/json.h>
 #include <cxl/libcxl.h>
 #include <util/parse-options.h>
-#include <ccan/array_size/array_size.h>
 
-#include "json.h"
 #include "filter.h"
 
-static struct {
-	bool memdevs;
-	bool idle;
-	bool human;
-	bool health;
-} list;
-
-static unsigned long listopts_to_flags(void)
-{
-	unsigned long flags = 0;
-
-	if (list.idle)
-		flags |= UTIL_JSON_IDLE;
-	if (list.human)
-		flags |= UTIL_JSON_HUMAN;
-	if (list.health)
-		flags |= UTIL_JSON_HEALTH;
-	return flags;
-}
-
-static struct {
-	const char *memdev;
-} param;
-
-static int did_fail;
-
-#define fail(fmt, ...) \
-do { \
-	did_fail = 1; \
-	fprintf(stderr, "cxl-%s:%s:%d: " fmt, \
-			VERSION, __func__, __LINE__, ##__VA_ARGS__); \
-} while (0)
+static struct cxl_filter_params param;
 
 static int num_list_flags(void)
 {
-	return list.memdevs;
+	return param.memdevs;
 }
 
 int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 {
 	const struct option options[] = {
-		OPT_STRING('m', "memdev", &param.memdev, "memory device name",
+		OPT_STRING('m', "memdev", &param.memdev_filter, "memory device name",
 			   "filter by CXL memory device name"),
-		OPT_BOOLEAN('M', "memdevs", &list.memdevs,
+		OPT_BOOLEAN('M', "memdevs", &param.memdevs,
 			    "include CXL memory device info"),
-		OPT_BOOLEAN('i', "idle", &list.idle, "include idle devices"),
-		OPT_BOOLEAN('u', "human", &list.human,
+		OPT_BOOLEAN('i', "idle", &param.idle, "include disabled devices"),
+		OPT_BOOLEAN('u', "human", &param.human,
 				"use human friendly number formats "),
-		OPT_BOOLEAN('H', "health", &list.health,
+		OPT_BOOLEAN('H', "health", &param.health,
 				"include memory device health information "),
 		OPT_END(),
 	};
@@ -70,9 +37,6 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 		"cxl list [<options>]",
 		NULL
 	};
-	struct json_object *jdevs = NULL;
-	unsigned long list_flags;
-	struct cxl_memdev *memdev;
 	int i;
 
 	argc = parse_options(argc, argv, options, u, 0);
@@ -83,46 +47,22 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 		usage_with_options(u, options);
 
 	if (num_list_flags() == 0) {
-		/*
-		 * TODO: We likely want to list regions by default if nothing
-		 * was explicitly asked for. But until we have region support,
-		 * print this error asking for devices explicitly.
-		 * Once region support is added, this TODO can be removed.
-		 */
-		error("please specify entities to list, e.g. using -m/-M\n");
-		usage_with_options(u, options);
-	}
-
-	list_flags = listopts_to_flags();
-
-	cxl_memdev_foreach(ctx, memdev) {
-		struct json_object *jdev = NULL;
-
-		if (!util_cxl_memdev_filter(memdev, param.memdev))
-			continue;
-
-		if (list.memdevs) {
-			if (!jdevs) {
-				jdevs = json_object_new_array();
-				if (!jdevs) {
-					fail("\n");
-					continue;
-				}
-			}
-
-			jdev = util_cxl_memdev_to_json(memdev, list_flags);
-			if (!jdev) {
-				fail("\n");
-				continue;
-			}
-			json_object_array_add(jdevs, jdev);
+		if (param.memdev_filter)
+			param.memdevs = true;
+		else {
+			/*
+			 * TODO: We likely want to list regions by default if
+			 * nothing was explicitly asked for. But until we have
+			 * region support, print this error asking for devices
+			 * explicitly.  Once region support is added, this TODO
+			 * can be removed.
+			 */
+			error("please specify entities to list, e.g. using -m/-M\n");
+			usage_with_options(u, options);
 		}
 	}
 
-	if (jdevs)
-		util_display_json_array(stdout, jdevs, list_flags);
+	log_init(&param.ctx, "cxl list", "CXL_LIST_LOG");
 
-	if (did_fail)
-		return -ENOMEM;
-	return 0;
+	return cxl_filter_walk(ctx, &param);
 }
diff --git a/cxl/meson.build b/cxl/meson.build
index 805924b9df9b..fc7ee71b54f0 100644
--- a/cxl/meson.build
+++ b/cxl/meson.build
@@ -3,6 +3,7 @@ cxl_src = [
   'list.c',
   'memdev.c',
   '../util/json.c',
+  '../util/log.c',
   'json.c',
   'filter.c',
 ]
