Merge branch 'for-79/disable-region-check' into pending

Add a check for memdev disable to see if there are active regions present
before disabling the device.
diff --git a/Documentation/cxl/cxl-create-region.txt b/Documentation/cxl/cxl-create-region.txt
index f11a412..b244af6 100644
--- a/Documentation/cxl/cxl-create-region.txt
+++ b/Documentation/cxl/cxl-create-region.txt
@@ -105,6 +105,12 @@
 	supplied, the first cross-host bridge (if available), decoder that
 	supports the largest interleave will be chosen.
 
+-Q::
+--enforce-qos::
+	Parameter to enforce qos_class mismatch failure. Region create operation
+	will fail of the qos_class of the root decoder and one of the memdev that
+	backs the region mismatches.
+
 include::human-option.txt[]
 
 include::debug-option.txt[]
diff --git a/Documentation/cxl/cxl-wait-sanitize.txt b/Documentation/cxl/cxl-wait-sanitize.txt
index 9047b74..e8f2044 100644
--- a/Documentation/cxl/cxl-wait-sanitize.txt
+++ b/Documentation/cxl/cxl-wait-sanitize.txt
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
 cxl-wait-sanitize(1)
-======================
+====================
 
 NAME
 ----
diff --git a/cxl/event_trace.c b/cxl/event_trace.c
index db8cc85..1b5aa09 100644
--- a/cxl/event_trace.c
+++ b/cxl/event_trace.c
@@ -109,7 +109,13 @@
 		struct tep_format_field *f = fields[i];
 		int len;
 
-		if (f->flags & TEP_FIELD_IS_STRING) {
+		/*
+		 * libtraceevent differentiates arrays and strings like this:
+		 * array:  TEP_FIELD_IS_[ARRAY | STRING]
+		 * string: TEP_FIELD_IS_[ARRAY | STRING | DYNAMIC]
+		 */
+		if ((f->flags & TEP_FIELD_IS_STRING) &&
+		    ((f->flags & TEP_FIELD_IS_DYNAMIC))) {
 			char *str;
 
 			str = tep_get_field_raw(NULL, event, f->name, record, &len, 0);
diff --git a/cxl/json.c b/cxl/json.c
index 33800c6..0c27aba 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -578,6 +578,7 @@
 	struct json_object *jdev, *jobj;
 	unsigned long long serial, size;
 	int numa_node;
+	int qos_class;
 
 	jdev = json_object_new_object();
 	if (!jdev)
@@ -592,6 +593,13 @@
 		jobj = util_json_object_size(size, flags);
 		if (jobj)
 			json_object_object_add(jdev, "pmem_size", jobj);
+
+		qos_class = cxl_memdev_get_pmem_qos_class(memdev);
+		if (qos_class != CXL_QOS_CLASS_NONE) {
+			jobj = json_object_new_int(qos_class);
+			if (jobj)
+				json_object_object_add(jdev, "pmem_qos_class", jobj);
+		}
 	}
 
 	size = cxl_memdev_get_ram_size(memdev);
@@ -599,6 +607,13 @@
 		jobj = util_json_object_size(size, flags);
 		if (jobj)
 			json_object_object_add(jdev, "ram_size", jobj);
+
+		qos_class = cxl_memdev_get_ram_qos_class(memdev);
+		if (qos_class != CXL_QOS_CLASS_NONE) {
+			jobj = json_object_new_int(qos_class);
+			if (jobj)
+				json_object_object_add(jdev, "ram_qos_class", jobj);
+		}
 	}
 
 	if (flags & UTIL_JSON_HEALTH) {
@@ -857,6 +872,16 @@
 					       jobj);
 	}
 
+	if (cxl_port_is_root(port)) {
+		int qos_class = cxl_root_decoder_get_qos_class(decoder);
+
+		if (qos_class != CXL_QOS_CLASS_NONE) {
+			jobj = json_object_new_int(qos_class);
+			if (jobj)
+				json_object_object_add(jdecoder, "qos_class", jobj);
+		}
+	}
+
 	json_object_set_userdata(jdecoder, decoder, NULL);
 	return jdecoder;
 }
@@ -1002,6 +1027,12 @@
 		}
 	}
 
+	if (cxl_region_qos_class_mismatch(region)) {
+		jobj = json_object_new_boolean(true);
+		if (jobj)
+			json_object_object_add(jregion, "qos_class_mismatch", jobj);
+	}
+
 	json_object_set_userdata(jregion, region, NULL);
 
 
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 0240e21..91725ac 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -414,6 +414,35 @@
 	return is_enabled(path);
 }
 
+CXL_EXPORT bool cxl_region_qos_class_mismatch(struct cxl_region *region)
+{
+	struct cxl_decoder *root_decoder = cxl_region_get_decoder(region);
+	struct cxl_memdev_mapping *mapping;
+
+	cxl_mapping_foreach(region, mapping) {
+		struct cxl_decoder *decoder;
+		struct cxl_memdev *memdev;
+
+		decoder = cxl_mapping_get_decoder(mapping);
+		if (!decoder)
+			continue;
+
+		memdev = cxl_decoder_get_memdev(decoder);
+		if (!memdev)
+			continue;
+
+		if (region->mode == CXL_DECODER_MODE_RAM) {
+			if (root_decoder->qos_class != memdev->ram_qos_class)
+				return true;
+		} else if (region->mode == CXL_DECODER_MODE_PMEM) {
+			if (root_decoder->qos_class != memdev->pmem_qos_class)
+				return true;
+		}
+	}
+
+	return false;
+}
+
 CXL_EXPORT int cxl_region_disable(struct cxl_region *region)
 {
 	const char *devname = cxl_region_get_devname(region);
@@ -1260,6 +1289,18 @@
 		goto err_read;
 	memdev->ram_size = strtoull(buf, NULL, 0);
 
+	sprintf(path, "%s/pmem/qos_class", cxlmem_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		memdev->pmem_qos_class = CXL_QOS_CLASS_NONE;
+	else
+		memdev->pmem_qos_class = atoi(buf);
+
+	sprintf(path, "%s/ram/qos_class", cxlmem_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		memdev->ram_qos_class = CXL_QOS_CLASS_NONE;
+	else
+		memdev->ram_qos_class = atoi(buf);
+
 	sprintf(path, "%s/payload_max", cxlmem_base);
 	if (sysfs_read_attr(ctx, path, buf) < 0)
 		goto err_read;
@@ -1483,6 +1524,16 @@
 	return memdev->ram_size;
 }
 
+CXL_EXPORT int cxl_memdev_get_pmem_qos_class(struct cxl_memdev *memdev)
+{
+	return memdev->pmem_qos_class;
+}
+
+CXL_EXPORT int cxl_memdev_get_ram_qos_class(struct cxl_memdev *memdev)
+{
+	return memdev->ram_qos_class;
+}
+
 CXL_EXPORT const char *cxl_memdev_get_firmware_verison(struct cxl_memdev *memdev)
 {
 	return memdev->firmware_version;
@@ -2186,6 +2237,12 @@
 	else
 		decoder->interleave_ways = strtoul(buf, NULL, 0);
 
+	sprintf(path, "%s/qos_class", cxldecoder_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		decoder->qos_class = CXL_QOS_CLASS_NONE;
+	else
+		decoder->qos_class = atoi(buf);
+
 	switch (port->type) {
 	case CXL_PORT_ENDPOINT:
 		sprintf(path, "%s/dpa_resource", cxldecoder_base);
@@ -2380,6 +2437,14 @@
 	return decoder->size;
 }
 
+CXL_EXPORT int cxl_root_decoder_get_qos_class(struct cxl_decoder *decoder)
+{
+	if (!cxl_port_is_root(decoder->port))
+		return -EINVAL;
+
+	return decoder->qos_class;
+}
+
 CXL_EXPORT unsigned long long
 cxl_decoder_get_dpa_resource(struct cxl_decoder *decoder)
 {
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index 936484a..304d7fa 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -276,5 +276,9 @@
 	cxl_cmd_alert_config_set_enable_alert_actions;
 	cxl_cmd_new_set_alert_config;
 	cxl_memdev_wait_sanitize;
+	cxl_root_decoder_get_qos_class;
+	cxl_memdev_get_pmem_qos_class;
+	cxl_memdev_get_ram_qos_class;
+	cxl_region_qos_class_mismatch;
 	cxl_port_decoders_committed;
 } LIBCXL_6;
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
index 30c8989..b6cd910 100644
--- a/cxl/lib/private.h
+++ b/cxl/lib/private.h
@@ -47,6 +47,8 @@
 	struct list_node list;
 	unsigned long long pmem_size;
 	unsigned long long ram_size;
+	int ram_qos_class;
+	int pmem_qos_class;
 	int payload_max;
 	size_t lsa_size;
 	struct kmod_module *module;
@@ -145,6 +147,7 @@
 	struct list_head targets;
 	struct list_head regions;
 	struct list_head stale_regions;
+	int qos_class;
 };
 
 enum cxl_decode_state {
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index ec0b1ce..fc6dd00 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -72,6 +72,8 @@
 struct cxl_ctx *cxl_memdev_get_ctx(struct cxl_memdev *memdev);
 unsigned long long cxl_memdev_get_pmem_size(struct cxl_memdev *memdev);
 unsigned long long cxl_memdev_get_ram_size(struct cxl_memdev *memdev);
+int cxl_memdev_get_pmem_qos_class(struct cxl_memdev *memdev);
+int cxl_memdev_get_ram_qos_class(struct cxl_memdev *memdev);
 const char *cxl_memdev_get_firmware_verison(struct cxl_memdev *memdev);
 bool cxl_memdev_fw_update_in_progress(struct cxl_memdev *memdev);
 size_t cxl_memdev_fw_update_get_remaining(struct cxl_memdev *memdev);
@@ -174,6 +176,8 @@
 	for (dport = cxl_dport_get_first(port); dport != NULL;                 \
 	     dport = cxl_dport_get_next(dport))
 
+#define CXL_QOS_CLASS_NONE		-1
+
 struct cxl_decoder;
 struct cxl_decoder *cxl_decoder_get_first(struct cxl_port *port);
 struct cxl_decoder *cxl_decoder_get_next(struct cxl_decoder *decoder);
@@ -185,6 +189,7 @@
 unsigned long long cxl_decoder_get_dpa_size(struct cxl_decoder *decoder);
 unsigned long long
 cxl_decoder_get_max_available_extent(struct cxl_decoder *decoder);
+int cxl_root_decoder_get_qos_class(struct cxl_decoder *decoder);
 
 enum cxl_decoder_mode {
 	CXL_DECODER_MODE_NONE,
@@ -331,6 +336,7 @@
 int cxl_region_clear_all_targets(struct cxl_region *region);
 int cxl_region_decode_commit(struct cxl_region *region);
 int cxl_region_decode_reset(struct cxl_region *region);
+bool cxl_region_qos_class_mismatch(struct cxl_region *region);
 
 #define cxl_region_foreach(decoder, region)                                    \
 	for (region = cxl_region_get_first(decoder); region != NULL;           \
diff --git a/cxl/region.c b/cxl/region.c
index 3a762db..96aa593 100644
--- a/cxl/region.c
+++ b/cxl/region.c
@@ -32,6 +32,7 @@
 	bool force;
 	bool human;
 	bool debug;
+	bool enforce_qos;
 } param = {
 	.ways = INT_MAX,
 	.granularity = INT_MAX,
@@ -49,6 +50,7 @@
 	const char **argv;
 	struct cxl_decoder *root_decoder;
 	enum cxl_decoder_mode mode;
+	bool enforce_qos;
 };
 
 enum region_actions {
@@ -81,7 +83,8 @@
 	   "region uuid", "uuid for the new region (default: autogenerate)"), \
 OPT_BOOLEAN('m', "memdevs", &param.memdevs, \
 	    "non-option arguments are memdevs"), \
-OPT_BOOLEAN('u', "human", &param.human, "use human friendly number formats")
+OPT_BOOLEAN('u', "human", &param.human, "use human friendly number formats"), \
+OPT_BOOLEAN('Q', "enforce-qos", &param.enforce_qos, "enforce qos_class match")
 
 static const struct option create_options[] = {
 	BASE_OPTIONS(),
@@ -360,6 +363,8 @@
 		}
 	}
 
+	p->enforce_qos = param.enforce_qos;
+
 	return 0;
 
 err:
@@ -423,10 +428,52 @@
 	}
 }
 
+static int create_region_validate_qos_class(struct parsed_params *p)
+{
+	int root_qos_class;
+	int qos_class;
+	int i;
+
+	if (!p->enforce_qos)
+		return 0;
+
+	root_qos_class = cxl_root_decoder_get_qos_class(p->root_decoder);
+	if (root_qos_class == CXL_QOS_CLASS_NONE)
+		return 0;
+
+	for (i = 0; i < p->ways; i++) {
+		struct json_object *jobj =
+			json_object_array_get_idx(p->memdevs, i);
+		struct cxl_memdev *memdev = json_object_get_userdata(jobj);
+
+		if (p->mode == CXL_DECODER_MODE_RAM)
+			qos_class = cxl_memdev_get_ram_qos_class(memdev);
+		else
+			qos_class = cxl_memdev_get_pmem_qos_class(memdev);
+
+		/* No qos_class entries. Possibly no kernel support */
+		if (qos_class == CXL_QOS_CLASS_NONE)
+			break;
+
+		if (qos_class != root_qos_class) {
+			if (p->enforce_qos) {
+				log_err(&rl, "%s qos_class mismatch %s\n",
+					cxl_decoder_get_devname(p->root_decoder),
+					cxl_memdev_get_devname(memdev));
+
+				return -ENXIO;
+			}
+		}
+	}
+
+	return 0;
+}
+
 static int validate_decoder(struct cxl_decoder *decoder,
 			    struct parsed_params *p)
 {
 	const char *devname = cxl_decoder_get_devname(decoder);
+	int rc;
 
 	switch(p->mode) {
 	case CXL_DECODER_MODE_RAM:
@@ -446,6 +493,10 @@
 		return -EINVAL;
 	}
 
+	rc = create_region_validate_qos_class(p);
+	if (rc)
+		return rc;
+
 	/* TODO check if the interleave config is possible under this decoder */
 
 	return 0;
diff --git a/test/common b/test/common
index f1023ef..75ff1a6 100644
--- a/test/common
+++ b/test/common
@@ -150,3 +150,6 @@
 	grep -q "Call Trace" <<< $log && err $1
 	true
 }
+
+# CXL COMMON
+CXL_TEST_QOS_CLASS=42
diff --git a/test/cxl-qos-class.sh b/test/cxl-qos-class.sh
new file mode 100755
index 0000000..a31df13
--- /dev/null
+++ b/test/cxl-qos-class.sh
@@ -0,0 +1,100 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2024 Intel Corporation. All rights reserved.
+
+. $(dirname $0)/common
+
+rc=77
+
+set -ex
+
+trap 'err $LINENO' ERR
+
+check_prereq "jq"
+
+modprobe -r cxl_test
+modprobe cxl_test
+rc=1
+
+check_qos_decoders () {
+	# check root decoders have expected fake qos_class
+	# also make sure the number of root decoders equal to the number
+	# with qos_class found
+	json=$($CXL list -b cxl_test -D -d root)
+	num_decoders=$(echo "$json" | jq length)
+	count=0
+	while read -r qos_class; do
+		if [[ "$qos_class" != "$CXL_TEST_QOS_CLASS" ]]; then
+			err "$LINENO"
+		fi
+		count=$((count+1))
+	done <<< "$(echo "$json" | jq -r '.[] | .qos_class')"
+
+	if [[ "$count" != "$num_decoders" ]]; then
+		err "$LINENO"
+	fi
+}
+
+check_qos_memdevs () {
+	# Check that memdevs that expose ram_qos_class or pmem_qos_class have
+	# expected fake value programmed.
+	json=$($CXL list -b cxl_test -M)
+	num_memdevs=$(echo "$json" | jq length)
+
+	for (( i = 0; i < num_memdevs; i++ )); do
+		ram_size="$(jq ".[$i] | .ram_size" <<< "$json")"
+		ram_qos_class="$(jq ".[$i] | .ram_qos_class" <<< "$json")"
+		pmem_size="$(jq ".[$i] | .pmem_size" <<< "$json")"
+		pmem_qos_class="$(jq ".[$i] | .pmem_qos_class" <<< "$json")"
+
+		if [[ "$ram_size" != null ]] && ((ram_qos_class != CXL_TEST_QOS_CLASS)); then
+			err "$LINENO"
+		fi
+
+		if [[ "$pmem_size" != null ]] && ((pmem_qos_class != CXL_TEST_QOS_CLASS)); then
+			err "$LINENO"
+		fi
+	done
+}
+
+# Based on cxl-create-region.sh create_single()
+destroy_regions()
+{
+	if [[ "$*" ]]; then
+		$CXL destroy-region -f -b cxl_test "$@"
+	else
+		$CXL destroy-region -f -b cxl_test all
+	fi
+}
+
+create_region_check_qos()
+{
+	# Find an x1 decoder
+	decoder=$($CXL list -b cxl_test -D -d root | jq -r "[ .[] |
+		  select(.max_available_extent > 0) |
+		  select(.pmem_capable == true) |
+		  select(.nr_targets == 1) ] |
+		  .[0].decoder")
+
+	# Find a memdev for this host-bridge
+	port_dev0="$("$CXL" list -T -d "$decoder" | jq -r ".[] |
+		    .targets | .[] | select(.position == 0) | .target")"
+	mem0="$("$CXL" list -M -p "$port_dev0" | jq -r ".[0].memdev")"
+	memdevs="$mem0"
+
+	# Send create-region with -Q to enforce qos_class matching
+	region="$("$CXL" create-region -Q -d "$decoder" -m "$memdevs" | jq -r ".region")"
+	if [[ ! $region ]]; then
+		echo "failed to create region"
+		err "$LINENO"
+	fi
+
+	destroy_regions "$region"
+}
+
+check_qos_decoders
+check_qos_memdevs
+create_region_check_qos
+check_dmesg "$LINEO"
+
+modprobe -r cxl_test
diff --git a/test/cxl-xor-region.sh b/test/cxl-xor-region.sh
index 117e7a4..b9e1d79 100644
--- a/test/cxl-xor-region.sh
+++ b/test/cxl-xor-region.sh
@@ -23,7 +23,8 @@
 
 create_and_destroy_region()
 {
-	region=$($CXL create-region -d $decoder -m $memdevs | jq -r ".region")
+	region=$($CXL create-region -d "$decoder" -m "$memdevs" |
+		jq -r ".region")
 
 	if [[ ! $region ]]; then
 		echo "create-region failed for $decoder"
@@ -35,55 +36,81 @@
 
 setup_x1()
 {
-        # Find an x1 decoder
-        decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] |
-          select(.pmem_capable == true) |
-          select(.nr_targets == 1) |
-          .decoder")
+	# Find an x1 decoder
+	decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] |
+		select(.pmem_capable == true) |
+		select(.nr_targets == 1) |
+		.decoder")
 
-        # Find a memdev for this host-bridge
-        port_dev0=$($CXL list -T -d $decoder | jq -r ".[] |
-            .targets | .[] | select(.position == 0) | .target")
-        mem0=$($CXL list -M -p $port_dev0 | jq -r ".[0].memdev")
-        memdevs="$mem0"
+	# Find a memdev for this host-bridge
+	port_dev0=$($CXL list -T -d "$decoder" | jq -r ".[] |
+		.targets | .[] | select(.position == 0) | .target")
+	mem0=$($CXL list -M -p "$port_dev0" | jq -r ".[0].memdev")
+	memdevs="$mem0"
 }
 
 setup_x2()
 {
-        # Find an x2 decoder
-        decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] |
-          select(.pmem_capable == true) |
-          select(.nr_targets == 2) |
-          .decoder")
+	# Find an x2 decoder
+	decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] |
+		select(.pmem_capable == true) |
+		select(.nr_targets == 2) |
+		.decoder")
 
-        # Find a memdev for each host-bridge interleave position
-        port_dev0=$($CXL list -T -d $decoder | jq -r ".[] |
-            .targets | .[] | select(.position == 0) | .target")
-        port_dev1=$($CXL list -T -d $decoder | jq -r ".[] |
-            .targets | .[] | select(.position == 1) | .target")
-        mem0=$($CXL list -M -p $port_dev0 | jq -r ".[0].memdev")
-        mem1=$($CXL list -M -p $port_dev1 | jq -r ".[0].memdev")
-        memdevs="$mem0 $mem1"
+	# Find a memdev for each host-bridge interleave position
+	port_dev0=$($CXL list -T -d "$decoder" | jq -r ".[] |
+		.targets | .[] | select(.position == 0) | .target")
+	port_dev1=$($CXL list -T -d "$decoder" | jq -r ".[] |
+		.targets | .[] | select(.position == 1) | .target")
+	mem0=$($CXL list -M -p "$port_dev0" | jq -r ".[0].memdev")
+	mem1=$($CXL list -M -p "$port_dev1" | jq -r ".[0].memdev")
+	memdevs="$mem0 $mem1"
 }
 
 setup_x4()
 {
-        # find an x2 decoder
-        decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] |
-          select(.pmem_capable == true) |
-          select(.nr_targets == 2) |
-          .decoder")
+	# find an x2 decoder
+	decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] |
+		select(.pmem_capable == true) |
+		select(.nr_targets == 2) |
+		.decoder")
 
-        # Find a memdev for each host-bridge interleave position
-        port_dev0=$($CXL list -T -d $decoder | jq -r ".[] |
-            .targets | .[] | select(.position == 0) | .target")
-        port_dev1=$($CXL list -T -d $decoder | jq -r ".[] |
-            .targets | .[] | select(.position == 1) | .target")
-        mem0=$($CXL list -M -p $port_dev0 | jq -r ".[0].memdev")
-        mem1=$($CXL list -M -p $port_dev1 | jq -r ".[0].memdev")
-        mem2=$($CXL list -M -p $port_dev0 | jq -r ".[1].memdev")
-        mem3=$($CXL list -M -p $port_dev1 | jq -r ".[1].memdev")
-        memdevs="$mem0 $mem1 $mem2 $mem3"
+	# Find a memdev for each host-bridge interleave position
+	port_dev0=$($CXL list -T -d "$decoder" | jq -r ".[] |
+		.targets | .[] | select(.position == 0) | .target")
+	port_dev1=$($CXL list -T -d "$decoder" | jq -r ".[] |
+		.targets | .[] | select(.position == 1) | .target")
+	mem0=$($CXL list -M -p "$port_dev0" | jq -r ".[0].memdev")
+	mem1=$($CXL list -M -p "$port_dev1" | jq -r ".[0].memdev")
+	mem2=$($CXL list -M -p "$port_dev0" | jq -r ".[1].memdev")
+	mem3=$($CXL list -M -p "$port_dev1" | jq -r ".[1].memdev")
+	memdevs="$mem0 $mem1 $mem2 $mem3"
+}
+
+setup_x3()
+{
+	# find an x3 decoder
+	decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] |
+		select(.pmem_capable == true) |
+		select(.nr_targets == 3) |
+		.decoder")
+
+	if [[ ! $decoder ]]; then
+		echo "no x3 decoder found, skipping xor-x3 test"
+		return
+	fi
+
+	# Find a memdev for each host-bridge interleave position
+	port_dev0=$($CXL list -T -d "$decoder" | jq -r ".[] |
+		.targets | .[] | select(.position == 0) | .target")
+	port_dev1=$($CXL list -T -d "$decoder" | jq -r ".[] |
+		.targets | .[] | select(.position == 1) | .target")
+	port_dev2=$($CXL list -T -d "$decoder" | jq -r ".[] |
+		.targets | .[] | select(.position == 2) | .target")
+	mem0=$($CXL list -M -p "$port_dev0" | jq -r ".[0].memdev")
+	mem1=$($CXL list -M -p "$port_dev1" | jq -r ".[0].memdev")
+	mem2=$($CXL list -M -p "$port_dev2" | jq -r ".[0].memdev")
+	memdevs="$mem0 $mem1 $mem2"
 }
 
 setup_x1
@@ -92,5 +119,12 @@
 create_and_destroy_region
 setup_x4
 create_and_destroy_region
+# x3 decoder may not be available in cxl/test topo yet
+setup_x3
+if [[ $decoder ]]; then
+	create_and_destroy_region
+fi
+
+check_dmesg "$LINENO"
 
 modprobe -r cxl_test
diff --git a/test/meson.build b/test/meson.build
index 65db049..a965a79 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -159,6 +159,7 @@
 cxl_events = find_program('cxl-events.sh')
 cxl_sanitize = find_program('cxl-sanitize.sh')
 cxl_destroy_region = find_program('cxl-destroy-region.sh')
+cxl_qos_class = find_program('cxl-qos-class.sh')
 
 tests = [
   [ 'libndctl',               libndctl,		  'ndctl' ],
@@ -190,6 +191,7 @@
   [ 'cxl-events.sh',          cxl_events,         'cxl'   ],
   [ 'cxl-sanitize.sh',        cxl_sanitize,       'cxl'   ],
   [ 'cxl-destroy-region.sh',  cxl_destroy_region, 'cxl'   ],
+  [ 'cxl-qos-class.sh',       cxl_qos_class,      'cxl'   ],
 ]
 
 if get_option('destructive').enabled()
diff --git a/util/json.c b/util/json.c
index 1d5c6bc..ba9daa3 100644
--- a/util/json.c
+++ b/util/json.c
@@ -75,7 +75,7 @@
 static int display_hex(struct json_object *jobj, struct printbuf *pbuf,
 		int level, int flags)
 {
-	unsigned long long val = json_object_get_int64(jobj);
+	unsigned long long val = util_json_get_u64(jobj);
 	static char buf[32];
 
 	snprintf(buf, sizeof(buf), "\"%#llx\"", val);
diff --git a/util/json.h b/util/json.h
index ea370df..560f845 100644
--- a/util/json.h
+++ b/util/json.h
@@ -34,10 +34,22 @@
 {
 	return json_object_new_uint64(val);
 }
+
+static inline unsigned long long util_json_get_u64(struct json_object *jobj)
+{
+	return json_object_get_uint64(jobj);
+}
+
 #else /* fallback to signed */
 static inline struct json_object *util_json_new_u64(unsigned long long val)
 {
 	return json_object_new_int64(val);
 }
+
+static inline unsigned long long util_json_get_u64(struct json_object *jobj)
+{
+	return json_object_get_int64(jobj);
+}
+
 #endif /* HAVE_JSON_U64 */
 #endif /* __UTIL_JSON_H__ */