linux-cxl.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [ndctl PATCH 00/37] cxl: Full topology enumeration
@ 2022-01-24  0:51 Dan Williams
  2022-01-24  0:51 ` [ndctl PATCH 01/37] test: Add 'suite' identifiers to tests Dan Williams
                   ` (36 more replies)
  0 siblings, 37 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:51 UTC (permalink / raw)
  To: linux-cxl; +Cc: Vishal Verma, Ben Widawsky

In coordination with kernel enabling to enumerate switch ports and
validate the CXL.mem capability through the topology and in endpoints
[1], enhance the 'cxl list' command accordingly. The 'cxl list' command
gains the ability to enumerate buses (CXL root ports), ports (Switch
ports), endpoints (kernel port objects associated with the HDM Decoder
Capability in endpoints), decoders, and downstream ports. Similar to
'ndctl list', 'cxl list' supports the ability to cross filter the
listing by different object types. This allows 'cxl list' to answer
questions like, "which memdevs can decode a given root level CXL decoder
range?" ("cxl list -M -d $decoder_id"), "what are the set of decoders
that a memdev can contribute?" ("cxl list -D -m $memdev_id").

In addition to that functionality new commands for disabling/enabling
ports and memdevs are added.

See the 'cxl list' man page for more details. See the libcxl man page
for a description of APIs added to support the new commands.

[1]: https://lore.kernel.org/r/164298411792.3018233.7493009997525360044.stgit@dwillia2-desk3.amr.corp.intel.com

---

Dan Williams (37):
      test: Add 'suite' identifiers to tests
      ndctl: Rename util_filter to ndctl_filter
      build: Add tags
      json: Add support for json_object_new_uint64()
      cxl/json: Cleanup object leak false positive
      cxl/list: Support multiple memdev device name filter arguments
      cxl/list: Support comma separated lists
      cxl/list: Introduce cxl_filter_walk()
      cxl/list: Emit device serial numbers
      cxl/list: Add filter by serial support
      cxl/lib: Rename nvdimm bridge to pmem
      cxl/list: Cleanup options definitions
      Documentation: Enhance libcxl memdev API documentation
      cxl/list: Add bus objects
      util/json: Warn on stderr about empty list results
      util/sysfs: Uplevel modalias lookup helper to util/
      cxl/list: Add port enumeration
      cxl/list: Add --debug option
      cxl/list: Add endpoints
      cxl/list: Add 'host' entries for port-like objects
      cxl/list: Add 'host' entries for memdevs
      cxl/list: Move enabled memdevs underneath their endpoint
      cxl/list: Filter memdev by ancestry
      cxl/memdev: Use a local logger for debug
      cxl/memdev: Cleanup memdev filter
      cxl/memdev: Add serial support for memdev-related commands
      cxl/list: Add 'numa_node' to memdev listings
      util: Implement common bind/unbind helpers
      cxl/memdev: Enable / disable support
      cxl/list: Add decoder support
      cxl/list: Extend decoder objects with target information
      cxl/list: Use 'physical_node' for root port attachment detection
      cxl/list: Reuse the --target option for ports
      cxl/list: Support filtering memdevs by decoders
      cxl/list: Support filtering memdevs by ports
      cxl/port: Add {disable,enable}-port command
      cxl/list: Filter dports and targets by memdevs


 .clang-format                            |    7 
 Documentation/copyright.txt              |    2 
 Documentation/cxl/cxl-disable-memdev.txt |   37 +
 Documentation/cxl/cxl-disable-port.txt   |   46 +
 Documentation/cxl/cxl-enable-memdev.txt  |   34 +
 Documentation/cxl/cxl-enable-port.txt    |   43 +
 Documentation/cxl/cxl-list.txt           |  248 ++++++
 Documentation/cxl/lib/libcxl.txt         |  448 ++++++++++-
 Documentation/cxl/memdev-option.txt      |    5 
 Documentation/cxl/meson.build            |    8 
 config.h.meson                           |    3 
 cxl/builtin.h                            |    4 
 cxl/cxl.c                                |    4 
 cxl/filter.c                             | 1068 +++++++++++++++++++++++++
 cxl/filter.h                             |   50 +
 cxl/json.c                               |  283 +++++++
 cxl/json.h                               |   19 
 cxl/lib/libcxl.c                         | 1267 +++++++++++++++++++++++++++++-
 cxl/lib/libcxl.sym                       |   83 ++
 cxl/lib/private.h                        |   85 ++
 cxl/libcxl.h                             |  133 +++
 cxl/list.c                               |  167 ++--
 cxl/memdev.c                             |  153 +++-
 cxl/meson.build                          |    2 
 cxl/port.c                               |  253 ++++++
 daxctl/json.c                            |    2 
 meson.build                              |   25 +
 ndctl/dimm.c                             |    2 
 ndctl/filter.c                           |    4 
 ndctl/filter.h                           |   21 
 ndctl/json.c                             |   10 
 ndctl/lib/libndctl.c                     |  136 ---
 ndctl/list.c                             |   14 
 ndctl/monitor.c                          |   12 
 test/list-smart-dimm.c                   |   12 
 test/meson.build                         |   73 +-
 util/json.c                              |    9 
 util/json.h                              |   14 
 util/sysfs.c                             |  103 ++
 util/sysfs.h                             |   16 
 40 files changed, 4503 insertions(+), 402 deletions(-)
 create mode 100644 Documentation/cxl/cxl-disable-memdev.txt
 create mode 100644 Documentation/cxl/cxl-disable-port.txt
 create mode 100644 Documentation/cxl/cxl-enable-memdev.txt
 create mode 100644 Documentation/cxl/cxl-enable-port.txt
 create mode 100644 cxl/port.c

base-commit: e1b4076b742f2e7aab8e9c9612a9b5c247d0d8e9

^ permalink raw reply	[flat|nested] 40+ messages in thread

* [ndctl PATCH 01/37] test: Add 'suite' identifiers to tests
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
@ 2022-01-24  0:51 ` Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 02/37] ndctl: Rename util_filter to ndctl_filter Dan Williams
                   ` (35 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:51 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

In preparation for adding CXL tests, and in anticipation of wanting to only
run the CXL tests, label each test with a suite id.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 test/meson.build |   73 +++++++++++++++++++++++++++---------------------------
 1 file changed, 37 insertions(+), 36 deletions(-)

diff --git a/test/meson.build b/test/meson.build
index 94287aaef85b..07a5bb6e7f62 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -152,28 +152,28 @@ pfn_meta_errors = find_program('pfn-meta-errors.sh')
 track_uuid = find_program('track-uuid.sh')
 
 tests = [
-  [ 'libndctl',               libndctl ],
-  [ 'dsm-fail',               dsm_fail ],
-  [ 'create.sh',              create ],
-  [ 'clear.sh',               clear ],
-  [ 'pmem-errors.sh',         pmem_errors ],
-  [ 'daxdev-errors.sh',       daxdev_errors_sh ],
-  [ 'multi-dax.sh',           multi_dax ],
-  [ 'btt-check.sh',           btt_check ],
-  [ 'label-compat.sh',        label_compat ],
-  [ 'sector-mode.sh',         sector_mode ],
-  [ 'inject-error.sh',        inject_error ],
-  [ 'btt-errors.sh',          btt_errors ],
-  [ 'hugetlb',                hugetlb ],
-  [ 'btt-pad-compat.sh',      btt_pad_compat ],
-  [ 'firmware-update.sh',     firmware_update ],
-  [ 'ack-shutdown-count-set', ack_shutdown_count ],
-  [ 'rescan-partitions.sh',   rescan_partitions ],
-  [ 'inject-smart.sh',        inject_smart ],
-  [ 'monitor.sh',             monitor ],
-  [ 'max_extent_ns',          max_extent ],
-  [ 'pfn-meta-errors.sh',     pfn_meta_errors ],
-  [ 'track-uuid.sh',          track_uuid ],
+  [ 'libndctl',               libndctl,		  'ndctl' ],
+  [ 'dsm-fail',               dsm_fail,	      	  'ndctl' ],
+  [ 'create.sh',              create,	      	  'ndctl' ],
+  [ 'clear.sh',               clear,	      	  'ndctl' ],
+  [ 'pmem-errors.sh',         pmem_errors,    	  'ndctl' ],
+  [ 'daxdev-errors.sh',       daxdev_errors_sh,	  'dax'	  ],
+  [ 'multi-dax.sh',           multi_dax,	  'dax'   ],
+  [ 'btt-check.sh',           btt_check,	  'ndctl' ],
+  [ 'label-compat.sh',        label_compat,       'ndctl' ],
+  [ 'sector-mode.sh',         sector_mode,        'ndctl' ],
+  [ 'inject-error.sh',        inject_error,	  'ndctl' ],
+  [ 'btt-errors.sh',          btt_errors,	  'ndctl' ],
+  [ 'hugetlb',                hugetlb,		  'ndctl' ],
+  [ 'btt-pad-compat.sh',      btt_pad_compat,	  'ndctl' ],
+  [ 'firmware-update.sh',     firmware_update,	  'ndctl' ],
+  [ 'ack-shutdown-count-set', ack_shutdown_count, 'ndctl' ],
+  [ 'rescan-partitions.sh',   rescan_partitions,  'ndctl' ],
+  [ 'inject-smart.sh',        inject_smart,	  'ndctl' ],
+  [ 'monitor.sh',             monitor,		  'ndctl' ],
+  [ 'max_extent_ns',          max_extent,	  'ndctl' ],
+  [ 'pfn-meta-errors.sh',     pfn_meta_errors,	  'ndctl' ],
+  [ 'track-uuid.sh',          track_uuid,	  'ndctl' ],
 ]
 
 if get_option('destructive').enabled()
@@ -188,26 +188,26 @@ if get_option('destructive').enabled()
   mmap_test = find_program('mmap.sh')
 
   tests += [
-    [ 'pmem-ns',           pmem_ns ],
-    [ 'sub-section.sh',    sub_section ],
-    [ 'dax-dev',           dax_dev ],
-    [ 'dax-ext4.sh',       dax_ext4 ],
-    [ 'dax-xfs.sh',        dax_xfs ],
-    [ 'align.sh',          align ],
-    [ 'device-dax',        device_dax ],
-    [ 'revoke-devmem',     revoke_devmem ],
-    [ 'device-dax-fio.sh', device_dax_fio ],
-    [ 'daxctl-devices.sh', daxctl_devices ],
-    [ 'daxctl-create.sh',  daxctl_create ],
-    [ 'dm.sh',             dm ],
-    [ 'mmap.sh',           mmap_test ],
+    [ 'pmem-ns',           pmem_ns,	   'ndctl' ],
+    [ 'sub-section.sh',    sub_section,	   'dax'   ],
+    [ 'dax-dev',           dax_dev,	   'dax'   ],
+    [ 'dax-ext4.sh',       dax_ext4,	   'dax'   ],
+    [ 'dax-xfs.sh',        dax_xfs,	   'dax'   ],
+    [ 'align.sh',          align,	   'ndctl' ],
+    [ 'device-dax',        device_dax,	   'dax'   ],
+    [ 'revoke-devmem',     revoke_devmem,  'dax'   ],
+    [ 'device-dax-fio.sh', device_dax_fio, 'dax'   ],
+    [ 'daxctl-devices.sh', daxctl_devices, 'dax'   ],
+    [ 'daxctl-create.sh',  daxctl_create,  'dax'   ],
+    [ 'dm.sh',             dm,		   'dax'   ],
+    [ 'mmap.sh',           mmap_test,	   'dax'   ],
   ]
 endif
 
 if get_option('keyutils').enabled()
   security = find_program('security.sh')
   tests += [
-    [ 'security.sh', security ]
+    [ 'security.sh', security, 'ndctl' ]
   ]
 endif
 
@@ -226,6 +226,7 @@ foreach t : tests
       dax_dev,
       mmap,
     ],
+    suite: t[2],
     timeout : 0,
     env : [
       'NDCTL=@0@'.format(ndctl_tool.full_path()),


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 02/37] ndctl: Rename util_filter to ndctl_filter
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
  2022-01-24  0:51 ` [ndctl PATCH 01/37] test: Add 'suite' identifiers to tests Dan Williams
@ 2022-01-24  0:52 ` Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 03/37] build: Add tags Dan Williams
                   ` (34 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:52 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

In preparation for introducing a cxl_filter_walk() implementation rename
the current filter_walk infrastructure with an ndctl_ prefix.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 ndctl/filter.c         |    4 ++--
 ndctl/filter.h         |   21 +++++++++++----------
 ndctl/list.c           |   14 +++++++-------
 ndctl/monitor.c        |   12 ++++++------
 test/list-smart-dimm.c |   12 ++++++------
 5 files changed, 32 insertions(+), 31 deletions(-)

diff --git a/ndctl/filter.c b/ndctl/filter.c
index 64d00ce87dd5..5b810dc5926d 100644
--- a/ndctl/filter.c
+++ b/ndctl/filter.c
@@ -338,8 +338,8 @@ const char *util_nsmode_name(enum ndctl_namespace_mode mode)
 	return modes[mode];
 }
 
-int util_filter_walk(struct ndctl_ctx *ctx, struct util_filter_ctx *fctx,
-		struct util_filter_params *param)
+int ndctl_filter_walk(struct ndctl_ctx *ctx, struct ndctl_filter_ctx *fctx,
+		      struct ndctl_filter_params *param)
 {
 	struct ndctl_bus *bus;
 	unsigned int type = 0;
diff --git a/ndctl/filter.h b/ndctl/filter.h
index 9800cc230865..f6fe325acdc1 100644
--- a/ndctl/filter.h
+++ b/ndctl/filter.h
@@ -31,7 +31,7 @@ const char *util_nsmode_name(enum ndctl_namespace_mode mode);
 
 struct json_object;
 
-/* json object hierarchy for the util_filter_walk() performed by cmd_list() */
+/* json object hierarchy for the ndctl_filter_walk() performed by cmd_list() */
 struct list_filter_arg {
 	struct json_object *jnamespaces;
 	struct json_object *jregions;
@@ -50,19 +50,20 @@ struct monitor_filter_arg {
 };
 
 /*
- * struct util_filter_ctx - control and callbacks for util_filter_walk()
+ * struct ndctl_filter_ctx - control and callbacks for ndctl_filter_walk()
  * ->filter_bus() and ->filter_region() return bool because the
  * child-object filter routines can not be called if the parent context
  * is not established. ->filter_dimm() and ->filter_namespace() are leaf
  * objects, so no child dependencies to check.
  */
-struct util_filter_ctx {
-	bool (*filter_bus)(struct ndctl_bus *bus, struct util_filter_ctx *ctx);
-	void (*filter_dimm)(struct ndctl_dimm *dimm, struct util_filter_ctx *ctx);
+struct ndctl_filter_ctx {
+	bool (*filter_bus)(struct ndctl_bus *bus, struct ndctl_filter_ctx *ctx);
+	void (*filter_dimm)(struct ndctl_dimm *dimm,
+			    struct ndctl_filter_ctx *ctx);
 	bool (*filter_region)(struct ndctl_region *region,
-			struct util_filter_ctx *ctx);
+			      struct ndctl_filter_ctx *ctx);
 	void (*filter_namespace)(struct ndctl_namespace *ndns,
-			struct util_filter_ctx *ctx);
+				 struct ndctl_filter_ctx *ctx);
 	union {
 		void *arg;
 		struct list_filter_arg *list;
@@ -70,7 +71,7 @@ struct util_filter_ctx {
 	};
 };
 
-struct util_filter_params {
+struct ndctl_filter_params {
 	const char *bus;
 	const char *region;
 	const char *type;
@@ -81,6 +82,6 @@ struct util_filter_params {
 };
 
 struct ndctl_ctx;
-int util_filter_walk(struct ndctl_ctx *ctx, struct util_filter_ctx *fctx,
-		struct util_filter_params *param);
+int ndctl_filter_walk(struct ndctl_ctx *ctx, struct ndctl_filter_ctx *fctx,
+		      struct ndctl_filter_params *param);
 #endif /* _NDCTL_UTIL_FILTER_H_ */
diff --git a/ndctl/list.c b/ndctl/list.c
index 869edde4fc65..d89801946a0d 100644
--- a/ndctl/list.c
+++ b/ndctl/list.c
@@ -55,7 +55,7 @@ static unsigned long listopts_to_flags(void)
 	return flags;
 }
 
-static struct util_filter_params param;
+static struct ndctl_filter_params param;
 
 static int did_fail;
 
@@ -234,7 +234,7 @@ static struct json_object *region_to_json(struct ndctl_region *region,
 }
 
 static void filter_namespace(struct ndctl_namespace *ndns,
-		struct util_filter_ctx *ctx)
+			     struct ndctl_filter_ctx *ctx)
 {
 	struct json_object *jndns;
 	struct list_filter_arg *lfa = ctx->list;
@@ -272,7 +272,7 @@ static void filter_namespace(struct ndctl_namespace *ndns,
 }
 
 static bool filter_region(struct ndctl_region *region,
-		struct util_filter_ctx *ctx)
+			  struct ndctl_filter_ctx *ctx)
 {
 	struct list_filter_arg *lfa = ctx->list;
 	struct json_object *jbus = lfa->jbus;
@@ -318,7 +318,7 @@ static bool filter_region(struct ndctl_region *region,
 	return true;
 }
 
-static void filter_dimm(struct ndctl_dimm *dimm, struct util_filter_ctx *ctx)
+static void filter_dimm(struct ndctl_dimm *dimm, struct ndctl_filter_ctx *ctx)
 {
 	struct list_filter_arg *lfa = ctx->list;
 	struct json_object *jdimm;
@@ -367,7 +367,7 @@ static void filter_dimm(struct ndctl_dimm *dimm, struct util_filter_ctx *ctx)
 	json_object_array_add(lfa->jdimms, jdimm);
 }
 
-static bool filter_bus(struct ndctl_bus *bus, struct util_filter_ctx *ctx)
+static bool filter_bus(struct ndctl_bus *bus, struct ndctl_filter_ctx *ctx)
 {
 	struct list_filter_arg *lfa = ctx->list;
 
@@ -489,7 +489,7 @@ int cmd_list(int argc, const char **argv, struct ndctl_ctx *ctx)
 		NULL
 	};
 	bool lint = !!secure_getenv("NDCTL_LIST_LINT");
-	struct util_filter_ctx fctx = { 0 };
+	struct ndctl_filter_ctx fctx = { 0 };
 	struct list_filter_arg lfa = { 0 };
 	int i, rc;
 
@@ -544,7 +544,7 @@ int cmd_list(int argc, const char **argv, struct ndctl_ctx *ctx)
 	fctx.list = &lfa;
 	lfa.flags = listopts_to_flags();
 
-	rc = util_filter_walk(ctx, &fctx, &param);
+	rc = ndctl_filter_walk(ctx, &fctx, &param);
 	if (rc)
 		return rc;
 
diff --git a/ndctl/monitor.c b/ndctl/monitor.c
index ae694c614593..3e6a42527088 100644
--- a/ndctl/monitor.c
+++ b/ndctl/monitor.c
@@ -49,7 +49,7 @@ struct monitor_dimm {
 	struct list_node list;
 };
 
-static struct util_filter_params param;
+static struct ndctl_filter_params param;
 
 static int did_fail;
 
@@ -264,12 +264,12 @@ out:
 }
 
 static bool filter_region(struct ndctl_region *region,
-		struct util_filter_ctx *fctx)
+			  struct ndctl_filter_ctx *fctx)
 {
 	return true;
 }
 
-static void filter_dimm(struct ndctl_dimm *dimm, struct util_filter_ctx *fctx)
+static void filter_dimm(struct ndctl_dimm *dimm, struct ndctl_filter_ctx *fctx)
 {
 	struct monitor_dimm *mdimm;
 	struct monitor_filter_arg *mfa = fctx->monitor;
@@ -317,7 +317,7 @@ static void filter_dimm(struct ndctl_dimm *dimm, struct util_filter_ctx *fctx)
 	return;
 }
 
-static bool filter_bus(struct ndctl_bus *bus, struct util_filter_ctx *fctx)
+static bool filter_bus(struct ndctl_bus *bus, struct ndctl_filter_ctx *fctx)
 {
 	return true;
 }
@@ -602,7 +602,7 @@ int cmd_monitor(int argc, const char **argv, struct ndctl_ctx *ctx)
 		CONF_END(),
 	};
 	const char *prefix = "./", *ndctl_configs;
-	struct util_filter_ctx fctx = { 0 };
+	struct ndctl_filter_ctx fctx = { 0 };
 	struct monitor_filter_arg mfa = { 0 };
 	int i, rc;
 
@@ -672,7 +672,7 @@ int cmd_monitor(int argc, const char **argv, struct ndctl_ctx *ctx)
 	mfa.maxfd_dimm = -1;
 	mfa.flags = 0;
 
-	rc = util_filter_walk(ctx, &fctx, &param);
+	rc = ndctl_filter_walk(ctx, &fctx, &param);
 	if (rc)
 		goto out;
 
diff --git a/test/list-smart-dimm.c b/test/list-smart-dimm.c
index 47b711e63670..f94277e8febd 100644
--- a/test/list-smart-dimm.c
+++ b/test/list-smart-dimm.c
@@ -11,7 +11,7 @@
 #include <ndctl/ndctl.h>
 #include <ndctl/json.h>
 
-struct util_filter_params param;
+struct ndctl_filter_params param;
 static int did_fail;
 static int jflag = JSON_C_TO_STRING_PRETTY;
 
@@ -23,12 +23,12 @@ do { \
 } while (0)
 
 static bool filter_region(struct ndctl_region *region,
-		struct util_filter_ctx *ctx)
+		struct ndctl_filter_ctx *ctx)
 {
 	return true;
 }
 
-static void filter_dimm(struct ndctl_dimm *dimm, struct util_filter_ctx *ctx)
+static void filter_dimm(struct ndctl_dimm *dimm, struct ndctl_filter_ctx *ctx)
 {
 	struct list_filter_arg *lfa = ctx->list;
 	struct json_object *jdimm;
@@ -57,7 +57,7 @@ static void filter_dimm(struct ndctl_dimm *dimm, struct util_filter_ctx *ctx)
 	json_object_array_add(lfa->jdimms, jdimm);
 }
 
-static bool filter_bus(struct ndctl_bus *bus, struct util_filter_ctx *ctx)
+static bool filter_bus(struct ndctl_bus *bus, struct ndctl_filter_ctx *ctx)
 {
 	return true;
 }
@@ -89,7 +89,7 @@ int main(int argc, const char *argv[])
 		"list-smart-dimm [<options>]",
 		NULL
 	};
-	struct util_filter_ctx fctx = { 0 };
+	struct ndctl_filter_ctx fctx = { 0 };
 	struct list_filter_arg lfa = { 0 };
 
 	rc = ndctl_new(&ctx);
@@ -108,7 +108,7 @@ int main(int argc, const char *argv[])
 	fctx.list = &lfa;
 	lfa.flags = 0;
 
-	rc = util_filter_walk(ctx, &fctx, &param);
+	rc = ndctl_filter_walk(ctx, &fctx, &param);
 	if (rc)
 		return rc;
 


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 03/37] build: Add tags
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
  2022-01-24  0:51 ` [ndctl PATCH 01/37] test: Add 'suite' identifiers to tests Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 02/37] ndctl: Rename util_filter to ndctl_filter Dan Williams
@ 2022-01-24  0:52 ` Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 04/37] json: Add support for json_object_new_uint64() Dan Williams
                   ` (33 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:52 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Copy the systemd approach to generating tags with a file listing from git.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 meson.build |   19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/meson.build b/meson.build
index b22fb2e57f6b..68f3d0ceff1c 100644
--- a/meson.build
+++ b/meson.build
@@ -82,6 +82,7 @@ project_source_root = meson.current_source_dir()
 # Cleanup the leftover config.h files to avoid conflicts with the meson
 # generated config.h
 git = find_program('git', required : false)
+env = find_program('env')
 if git.found()
   run_command('clean_config.sh',
     env : 'GIT_DIR=@0@/.git'.format(project_source_root),
@@ -111,6 +112,24 @@ else
   )
 endif
 
+if git.found()
+  all_files = run_command(
+    env, '-u', 'GIT_WORK_TREE',
+    git, '--git-dir=@0@/.git'.format(project_source_root),
+         'ls-files', ':/*.[ch]',
+    check : false)
+  if all_files.returncode() == 0
+    all_files = files(all_files.stdout().split())
+    custom_target(
+            'tags',
+            output : 'tags',
+            command : [env, 'etags', '-o', '@0@/TAGS'.format(project_source_root)] + all_files)
+    run_target(
+            'ctags',
+            command : [env, 'ctags', '-o', '@0@/tags'.format(project_source_root)] + all_files)
+  endif
+endif
+
 versiondep = declare_dependency(
   compile_args: ['-include', 'version.h'],
   sources: version_h


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 04/37] json: Add support for json_object_new_uint64()
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (2 preceding siblings ...)
  2022-01-24  0:52 ` [ndctl PATCH 03/37] build: Add tags Dan Williams
@ 2022-01-24  0:52 ` Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 05/37] cxl/json: Cleanup object leak false positive Dan Williams
                   ` (32 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:52 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Recent versions of json-c add a proper u64 type. However since ndctl still
needs to build against older json-c add build infrastructure to fallback to
s64.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 config.h.meson |    3 +++
 cxl/json.c     |    6 +++---
 daxctl/json.c  |    2 +-
 meson.build    |    6 ++++++
 ndctl/dimm.c   |    2 +-
 ndctl/json.c   |   10 +++++-----
 util/json.c    |    2 +-
 util/json.h    |   13 ++++++++++++-
 8 files changed, 32 insertions(+), 12 deletions(-)

diff --git a/config.h.meson b/config.h.meson
index 98102251b494..2852f1e9cd8b 100644
--- a/config.h.meson
+++ b/config.h.meson
@@ -88,6 +88,9 @@
 /* Define to 1 if you have the `__secure_getenv' function. */
 #mesondefine HAVE___SECURE_GETENV
 
+/* Define to 1 if you have json_object_new_uint64 in json-c */
+#mesondefine HAVE_JSON_U64
+
 /* Define to the sub-directory where libtool stores uninstalled libraries. */
 #mesondefine LT_OBJDIR
 
diff --git a/cxl/json.c b/cxl/json.c
index e562502d9116..97ed76b65a6b 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -159,17 +159,17 @@ static struct json_object *util_cxl_memdev_health_to_json(
 	}
 
 	field = cxl_cmd_health_info_get_dirty_shutdowns(cmd);
-	jobj = json_object_new_int64(field);
+	jobj = util_json_new_u64(field);
 	if (jobj)
 		json_object_object_add(jhealth, "dirty_shutdowns", jobj);
 
 	field = cxl_cmd_health_info_get_volatile_errors(cmd);
-	jobj = json_object_new_int64(field);
+	jobj = util_json_new_u64(field);
 	if (jobj)
 		json_object_object_add(jhealth, "volatile_errors", jobj);
 
 	field = cxl_cmd_health_info_get_pmem_errors(cmd);
-	jobj = json_object_new_int64(field);
+	jobj = util_json_new_u64(field);
 	if (jobj)
 		json_object_object_add(jhealth, "pmem_errors", jobj);
 
diff --git a/daxctl/json.c b/daxctl/json.c
index 66a795e2e544..3cbce9dcd651 100644
--- a/daxctl/json.c
+++ b/daxctl/json.c
@@ -190,7 +190,7 @@ struct json_object *util_daxctl_region_to_json(struct daxctl_region *region,
 
 	align = daxctl_region_get_align(region);
 	if (align < ULONG_MAX) {
-		jobj = json_object_new_int64(align);
+		jobj = util_json_new_u64(align);
 		if (!jobj)
 			goto err;
 		json_object_object_add(jregion, "align", jobj);
diff --git a/meson.build b/meson.build
index 68f3d0ceff1c..f25ec6c47e7b 100644
--- a/meson.build
+++ b/meson.build
@@ -241,6 +241,12 @@ foreach ident : ['secure_getenv', '__secure_getenv']
   conf.set10('HAVE_' + ident.to_upper(), cc.has_function(ident))
 endforeach
 
+conf.set10('HAVE_JSON_U64',
+  cc.has_function('json_object_new_uint64',
+    prefix : '''#include <json-c/json.h>''',
+    dependencies : json,
+  )
+)
 
 ndctlconf_dir = sysconfdir / 'ndctl.conf.d'
 ndctlconf = ndctlconf_dir / 'monitor.conf'
diff --git a/ndctl/dimm.c b/ndctl/dimm.c
index 0f052644a46e..d9718a33b22f 100644
--- a/ndctl/dimm.c
+++ b/ndctl/dimm.c
@@ -168,7 +168,7 @@ static struct json_object *dump_label_json(struct ndctl_dimm *dimm,
 			break;
 		json_object_object_add(jlabel, "isetcookie", jobj);
 
-		jobj = json_object_new_int64(le64_to_cpu(nslabel.lbasize));
+		jobj = util_json_new_u64(le64_to_cpu(nslabel.lbasize));
 		if (!jobj)
 			break;
 		json_object_object_add(jlabel, "lbasize", jobj);
diff --git a/ndctl/json.c b/ndctl/json.c
index c62e6cae01a9..23bad7fd7445 100644
--- a/ndctl/json.c
+++ b/ndctl/json.c
@@ -357,7 +357,7 @@ static struct json_object *util_##type##_build_size_array(struct ndctl_##type *a
 		int64_t align;					\
 								\
 		align = get_elem(arg, i);			\
-		jobj = json_object_new_int64(align);		\
+		jobj = util_json_new_u64(align);		\
 		if (!jobj)					\
 			goto err;				\
 		json_object_array_add(arr, jobj);		\
@@ -550,7 +550,7 @@ struct json_object *util_region_badblocks_to_json(struct ndctl_region *region,
 		if (!jbb)
 			goto err_array;
 
-		jobj = json_object_new_int64(bb->offset);
+		jobj = util_json_new_u64(bb->offset);
 		if (!jobj)
 			goto err;
 		json_object_object_add(jbb, "offset", jobj);
@@ -604,7 +604,7 @@ static struct json_object *util_namespace_badblocks_to_json(
 		if (!jbb)
 			goto err_array;
 
-		jobj = json_object_new_int64(bb->offset);
+		jobj = util_json_new_u64(bb->offset);
 		if (!jobj)
 			goto err;
 		json_object_object_add(jbb, "offset", jobj);
@@ -682,7 +682,7 @@ static struct json_object *dev_badblocks_to_json(struct ndctl_region *region,
 		if (!jbb)
 			goto err_array;
 
-		jobj = json_object_new_int64(offset);
+		jobj = util_json_new_u64(offset);
 		if (!jobj)
 			goto err;
 		json_object_object_add(jbb, "offset", jobj);
@@ -972,7 +972,7 @@ struct json_object *util_namespace_to_json(struct ndctl_namespace *ndns,
 	}
 
 	if (align) {
-		jobj = json_object_new_int64(align);
+		jobj = util_json_new_u64(align);
 		if (!jobj)
 			goto err;
 		json_object_object_add(jndns, "align", jobj);
diff --git a/util/json.c b/util/json.c
index 9f0a8e137caa..bd5f8fc47a2b 100644
--- a/util/json.c
+++ b/util/json.c
@@ -82,7 +82,7 @@ struct json_object *util_json_object_size(unsigned long long size,
 struct json_object *util_json_object_hex(unsigned long long val,
 		unsigned long flags)
 {
-	struct json_object *jobj = json_object_new_int64(val);
+	struct json_object *jobj = util_json_new_u64(val);
 
 	if (jobj && (flags & UTIL_JSON_HUMAN))
 		json_object_set_serializer(jobj, display_hex, NULL, NULL);
diff --git a/util/json.h b/util/json.h
index 4ca2c890fa5c..061f0d423158 100644
--- a/util/json.h
+++ b/util/json.h
@@ -4,6 +4,7 @@
 #define __UTIL_JSON_H__
 #include <stdio.h>
 #include <stdbool.h>
+#include <json-c/json.h>
 
 enum util_json_flags {
 	UTIL_JSON_IDLE		= (1 << 0),
@@ -19,11 +20,21 @@ enum util_json_flags {
 	UTIL_JSON_HEALTH	= (1 << 10),
 };
 
-struct json_object;
 void util_display_json_array(FILE *f_out, struct json_object *jarray,
 		unsigned long flags);
 struct json_object *util_json_object_size(unsigned long long size,
 		unsigned long flags);
 struct json_object *util_json_object_hex(unsigned long long val,
 		unsigned long flags);
+#if HAVE_JSON_U64
+static inline struct json_object *util_json_new_u64(unsigned long long val)
+{
+	return json_object_new_uint64(val);
+}
+#else /* fallback to signed */
+static inline struct json_object *util_json_new_u64(unsigned long long val)
+{
+	return json_object_new_int64(val);
+}
+#endif /* HAVE_JSON_U64 */
 #endif /* __UTIL_JSON_H__ */


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 05/37] cxl/json: Cleanup object leak false positive
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (3 preceding siblings ...)
  2022-01-24  0:52 ` [ndctl PATCH 04/37] json: Add support for json_object_new_uint64() Dan Williams
@ 2022-01-24  0:52 ` Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 06/37] cxl/list: Support multiple memdev device name filter arguments Dan Williams
                   ` (31 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:52 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

As written it is a leak of the json object to return if devname is NULL.
However, the devname can not be NULL because the memdev would not have been
enumerated. Drop the error checking.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 cxl/json.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cxl/json.c b/cxl/json.c
index 97ed76b65a6b..3ef9f7670510 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -190,7 +190,7 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
 	struct json_object *jdev, *jobj;
 
 	jdev = json_object_new_object();
-	if (!devname || !jdev)
+	if (!jdev)
 		return NULL;
 
 	jobj = json_object_new_string(devname);


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 06/37] cxl/list: Support multiple memdev device name filter arguments
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (4 preceding siblings ...)
  2022-01-24  0:52 ` [ndctl PATCH 05/37] cxl/json: Cleanup object leak false positive Dan Williams
@ 2022-01-24  0:52 ` Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 07/37] cxl/list: Support comma separated lists Dan Williams
                   ` (30 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:52 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Similar to 'ndctl list', allow for a syntax like:

    cxl list -m "$(seq -s ' ' 2 5)"

...to filter the output to just those 4 memdevs.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/cxl-list.txt |   21 ++++++++++++++++++++-
 cxl/filter.c                   |   38 +++++++++++++++++++++++++++-----------
 2 files changed, 47 insertions(+), 12 deletions(-)

diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index c8d10fb5ef86..686e0ea768f0 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -30,7 +30,7 @@ OPTIONS
 -------
 -m::
 --memdev=::
-	Specify a cxl memory device name to filter the listing. For example:
+	Specify CXL memory device name(s), or device id(s), to filter the listing. For example:
 ----
 # cxl list --memdev=mem0
 {
@@ -38,6 +38,25 @@ OPTIONS
   "pmem_size":268435456,
   "ram_size":0,
 }
+
+# cxl list -m "0 mem1 2"
+[
+  {
+    "memdev":"mem0",
+    "pmem_size":268435456,
+    "ram_size":0
+  },
+  {
+    "memdev":"mem2",
+    "pmem_size":268435456,
+    "ram_size":268435456
+  },
+  {
+    "memdev":"mem1",
+    "pmem_size":268435456,
+    "ram_size":268435456
+  }
+]
 ----
 
 -M::
diff --git a/cxl/filter.c b/cxl/filter.c
index 21322ed4b4d0..efafaf5a3197 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -2,24 +2,40 @@
 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved.
 #include <stdio.h>
 #include <string.h>
+#include <stdlib.h>
 #include <cxl/libcxl.h>
 #include "filter.h"
 
 struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev,
-                                         const char *ident)
+					  const char *__ident)
 {
-       int memdev_id;
+	char *ident, *save;
+	const char *name;
+	int memdev_id;
 
-       if (!ident || strcmp(ident, "all") == 0)
-               return memdev;
+	if (!__ident)
+		return memdev;
 
-       if (strcmp(ident, cxl_memdev_get_devname(memdev)) == 0)
-               return memdev;
+	ident = strdup(__ident);
+	if (!ident)
+		return NULL;
 
-       if ((sscanf(ident, "%d", &memdev_id) == 1
-                       || sscanf(ident, "mem%d", &memdev_id) == 1)
-                       && cxl_memdev_get_id(memdev) == memdev_id)
-               return memdev;
+	for (name = strtok_r(ident, " ", &save); name;
+	     name = strtok_r(NULL, " ", &save)) {
+		if (strcmp(name, "all") == 0)
+			break;
 
-       return NULL;
+		if ((sscanf(name, "%d", &memdev_id) == 1 ||
+		     sscanf(name, "mem%d", &memdev_id) == 1) &&
+		    cxl_memdev_get_id(memdev) == memdev_id)
+			break;
+
+		if (strcmp(name, cxl_memdev_get_devname(memdev)) == 0)
+			break;
+	}
+
+	free(ident);
+	if (name)
+		return memdev;
+	return NULL;
 }


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 07/37] cxl/list: Support comma separated lists
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (5 preceding siblings ...)
  2022-01-24  0:52 ` [ndctl PATCH 06/37] cxl/list: Support multiple memdev device name filter arguments Dan Williams
@ 2022-01-24  0:52 ` Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 08/37] cxl/list: Introduce cxl_filter_walk() Dan Williams
                   ` (29 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:52 UTC (permalink / raw)
  To: linux-cxl; +Cc: Vishal Verma

In addition to supporting a syntax like:

   cxl list -m "0 1 2"

...support:

   cxl list -m 0,1,2

Reported-by: Vishal Verma <vishal.l.verma@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 cxl/filter.c |   13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/cxl/filter.c b/cxl/filter.c
index efafaf5a3197..405b653d80b2 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -6,6 +6,15 @@
 #include <cxl/libcxl.h>
 #include "filter.h"
 
+static const char *which_sep(const char *filter)
+{
+	if (strchr(filter, ' '))
+		return " ";
+	if (strchr(filter, ','))
+		return ",";
+	return " ";
+}
+
 struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev,
 					  const char *__ident)
 {
@@ -20,8 +29,8 @@ struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev,
 	if (!ident)
 		return NULL;
 
-	for (name = strtok_r(ident, " ", &save); name;
-	     name = strtok_r(NULL, " ", &save)) {
+	for (name = strtok_r(ident, which_sep(__ident), &save); name;
+	     name = strtok_r(NULL, which_sep(__ident), &save)) {
 		if (strcmp(name, "all") == 0)
 			break;
 


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 08/37] cxl/list: Introduce cxl_filter_walk()
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (6 preceding siblings ...)
  2022-01-24  0:52 ` [ndctl PATCH 07/37] cxl/list: Support comma separated lists Dan Williams
@ 2022-01-24  0:52 ` Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 09/37] cxl/list: Emit device serial numbers Dan Williams
                   ` (28 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:52 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

In preparation for introducing more objects and filtering options for 'cxl
list' introduce cxl_filter_walk() to centralize CXL topology walks. It
fills the same role as ndctl_filter_walk() as a way to distribute topology
interrogation beyond 'cxl list' to other commands, and serve as the
template for CXL object hierarchy in JSON output payloads.

Use the common dbg() logger for log messages.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/cxl-list.txt |    2 +
 cxl/filter.c                   |   50 ++++++++++++++++++++
 cxl/filter.h                   |   18 ++++++-
 cxl/list.c                     |  102 ++++++++--------------------------------
 cxl/meson.build                |    1 
 5 files changed, 90 insertions(+), 83 deletions(-)

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',
 ]


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 09/37] cxl/list: Emit device serial numbers
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (7 preceding siblings ...)
  2022-01-24  0:52 ` [ndctl PATCH 08/37] cxl/list: Introduce cxl_filter_walk() Dan Williams
@ 2022-01-24  0:52 ` Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 10/37] cxl/list: Add filter by serial support Dan Williams
                   ` (27 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:52 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Starting with the v5.17 kernel the CXL driver emits the mandatory device
serial number for each memory device. Include it in the memory device
listing.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/cxl-list.txt |   15 +++++++++------
 cxl/json.c                     |   11 ++++++++++-
 cxl/lib/libcxl.c               |   11 +++++++++++
 cxl/lib/libcxl.sym             |    5 +++++
 cxl/lib/private.h              |    1 +
 cxl/libcxl.h                   |    1 +
 6 files changed, 37 insertions(+), 7 deletions(-)

diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index 4d409babb99a..bd0207e942cb 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -41,22 +41,25 @@ OPTIONS
   "ram_size":0,
 }
 
-# cxl list -m "0 mem1 2"
+# cxl list -M --memdev="0 mem3 5"
 [
   {
     "memdev":"mem0",
     "pmem_size":268435456,
-    "ram_size":0
+    "ram_size":0,
+    "serial":0
   },
   {
-    "memdev":"mem2",
+    "memdev":"mem3",
     "pmem_size":268435456,
-    "ram_size":268435456
+    "ram_size":268435456,
+    "serial":2
   },
   {
-    "memdev":"mem1",
+    "memdev":"mem5",
     "pmem_size":268435456,
-    "ram_size":268435456
+    "ram_size":268435456,
+    "serial":4
   }
 ]
 ----
diff --git a/cxl/json.c b/cxl/json.c
index 3ef9f7670510..d8e65df241a1 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
-// Copyright (C) 2015-2020 Intel Corporation. All rights reserved.
+// Copyright (C) 2015-2021 Intel Corporation. All rights reserved.
+#include <limits.h>
 #include <util/json.h>
 #include <uuid/uuid.h>
 #include <cxl/libcxl.h>
@@ -188,6 +189,7 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
 {
 	const char *devname = cxl_memdev_get_devname(memdev);
 	struct json_object *jdev, *jobj;
+	unsigned long long serial;
 
 	jdev = json_object_new_object();
 	if (!jdev)
@@ -210,5 +212,12 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
 		if (jobj)
 			json_object_object_add(jdev, "health", jobj);
 	}
+
+	serial = cxl_memdev_get_serial(memdev);
+	if (serial < ULLONG_MAX) {
+		jobj = util_json_object_hex(serial, flags);
+		if (jobj)
+			json_object_object_add(jdev, "serial", jobj);
+	}
 	return jdev;
 }
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 3390eb91ecb5..8d3cf8092c8b 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -296,6 +296,12 @@ static void *add_cxl_memdev(void *parent, int id, const char *cxlmem_base)
 	if (memdev->lsa_size == ULLONG_MAX)
 		goto err_read;
 
+	sprintf(path, "%s/serial", cxlmem_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		memdev->serial = ULLONG_MAX;
+	else
+		memdev->serial = strtoull(buf, NULL, 0);
+
 	memdev->dev_path = strdup(cxlmem_base);
 	if (!memdev->dev_path)
 		goto err_read;
@@ -371,6 +377,11 @@ CXL_EXPORT int cxl_memdev_get_id(struct cxl_memdev *memdev)
 	return memdev->id;
 }
 
+CXL_EXPORT unsigned long long cxl_memdev_get_serial(struct cxl_memdev *memdev)
+{
+	return memdev->serial;
+}
+
 CXL_EXPORT const char *cxl_memdev_get_devname(struct cxl_memdev *memdev)
 {
 	return devpath_to_devname(memdev->dev_path);
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index 077d10434cde..4411035f962a 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -73,3 +73,8 @@ global:
 local:
         *;
 };
+
+LIBCXL_2 {
+global:
+	cxl_memdev_get_serial;
+} LIBCXL_1;
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
index a1b8b507225e..28f7e16dbd04 100644
--- a/cxl/lib/private.h
+++ b/cxl/lib/private.h
@@ -31,6 +31,7 @@ struct cxl_memdev {
 	size_t lsa_size;
 	struct kmod_module *module;
 	struct cxl_nvdimm_bridge *bridge;
+	unsigned long long serial;
 };
 
 enum cxl_cmd_query_status {
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index 89d35ba957e6..bcdede8f12e8 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -35,6 +35,7 @@ struct cxl_memdev;
 struct cxl_memdev *cxl_memdev_get_first(struct cxl_ctx *ctx);
 struct cxl_memdev *cxl_memdev_get_next(struct cxl_memdev *memdev);
 int cxl_memdev_get_id(struct cxl_memdev *memdev);
+unsigned long long cxl_memdev_get_serial(struct cxl_memdev *memdev);
 const char *cxl_memdev_get_devname(struct cxl_memdev *memdev);
 int cxl_memdev_get_major(struct cxl_memdev *memdev);
 int cxl_memdev_get_minor(struct cxl_memdev *memdev);


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 10/37] cxl/list: Add filter by serial support
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (8 preceding siblings ...)
  2022-01-24  0:52 ` [ndctl PATCH 09/37] cxl/list: Emit device serial numbers Dan Williams
@ 2022-01-24  0:52 ` Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 11/37] cxl/lib: Rename nvdimm bridge to pmem Dan Williams
                   ` (26 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:52 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Given that serial numbers are intended to be unique device identifiers,
enable them as a memdev filter option.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/cxl-list.txt |    4 ++++
 cxl/filter.c                   |   38 ++++++++++++++++++++++++++++++++++----
 cxl/filter.h                   |    4 +++-
 cxl/list.c                     |    4 +++-
 cxl/memdev.c                   |    2 +-
 5 files changed, 45 insertions(+), 7 deletions(-)

diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index bd0207e942cb..224c972498ea 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -64,6 +64,10 @@ OPTIONS
 ]
 ----
 
+-s::
+--serial=::
+	Specify CXL memory device serial number(s) to filter the listing
+
 -M::
 --memdevs::
 	Include CXL memory devices in the listing
diff --git a/cxl/filter.c b/cxl/filter.c
index d1ff4b62c806..26efc65f4dd7 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -21,15 +21,45 @@ static const char *which_sep(const char *filter)
 	return " ";
 }
 
+static struct cxl_memdev *
+util_cxl_memdev_serial_filter(struct cxl_memdev *memdev, const char *__serials)
+{
+	unsigned long long serial = 0;
+	char *serials, *save, *end;
+	const char *arg;
+
+	if (!__serials)
+		return memdev;
+
+	serials = strdup(__serials);
+	if (!serials)
+		return NULL;
+
+	for (arg = strtok_r(serials, which_sep(__serials), &save); arg;
+	     arg = strtok_r(NULL, which_sep(__serials), &save)) {
+		serial = strtoull(arg, &end, 0);
+		if (!arg[0] || end[0] != 0)
+			continue;
+		if (cxl_memdev_get_serial(memdev) == serial)
+			break;
+	}
+
+	free(serials);
+	if (arg)
+		return memdev;
+	return NULL;
+}
+
 struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev,
-					  const char *__ident)
+					  const char *__ident,
+					  const char *serials)
 {
 	char *ident, *save;
 	const char *name;
 	int memdev_id;
 
 	if (!__ident)
-		return memdev;
+		return util_cxl_memdev_serial_filter(memdev, serials);
 
 	ident = strdup(__ident);
 	if (!ident)
@@ -51,7 +81,7 @@ struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev,
 
 	free(ident);
 	if (name)
-		return memdev;
+		return util_cxl_memdev_serial_filter(memdev, serials);
 	return NULL;
 }
 
@@ -82,7 +112,7 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	cxl_memdev_foreach(ctx, memdev) {
 		struct json_object *jdev;
 
-		if (!util_cxl_memdev_filter(memdev, p->memdev_filter))
+		if (!util_cxl_memdev_filter(memdev, p->memdev_filter, p->serial_filter))
 			continue;
 		if (p->memdevs) {
 			jdev = util_cxl_memdev_to_json(memdev, flags);
diff --git a/cxl/filter.h b/cxl/filter.h
index 664b74b23b0a..12d934474554 100644
--- a/cxl/filter.h
+++ b/cxl/filter.h
@@ -8,6 +8,7 @@
 
 struct cxl_filter_params {
 	const char *memdev_filter;
+	const char *serial_filter;
 	bool memdevs;
 	bool idle;
 	bool human;
@@ -16,6 +17,7 @@ struct cxl_filter_params {
 };
 
 struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev,
-					  const char *ident);
+					  const char *__ident,
+					  const char *serials);
 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 17303073b49a..6bc48df0ea84 100644
--- a/cxl/list.c
+++ b/cxl/list.c
@@ -24,6 +24,8 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 	const struct option options[] = {
 		OPT_STRING('m', "memdev", &param.memdev_filter, "memory device name",
 			   "filter by CXL memory device name"),
+		OPT_STRING('s', "serial", &param.serial_filter, "memory device serial",
+			   "filter by CXL memory device serial number"),
 		OPT_BOOLEAN('M', "memdevs", &param.memdevs,
 			    "include CXL memory device info"),
 		OPT_BOOLEAN('i', "idle", &param.idle, "include disabled devices"),
@@ -47,7 +49,7 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 		usage_with_options(u, options);
 
 	if (num_list_flags() == 0) {
-		if (param.memdev_filter)
+		if (param.memdev_filter || param.serial_filter)
 			param.memdevs = true;
 		else {
 			/*
diff --git a/cxl/memdev.c b/cxl/memdev.c
index d063d51cc571..b9141be62c87 100644
--- a/cxl/memdev.c
+++ b/cxl/memdev.c
@@ -248,7 +248,7 @@ static int memdev_action(int argc, const char **argv, struct cxl_ctx *ctx,
 			continue;
 
 		cxl_memdev_foreach (ctx, memdev) {
-			if (!util_cxl_memdev_filter(memdev, argv[i]))
+			if (!util_cxl_memdev_filter(memdev, argv[i], NULL))
 				continue;
 
 			if (action == action_write) {


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 11/37] cxl/lib: Rename nvdimm bridge to pmem
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (9 preceding siblings ...)
  2022-01-24  0:52 ` [ndctl PATCH 10/37] cxl/list: Add filter by serial support Dan Williams
@ 2022-01-24  0:52 ` Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 12/37] cxl/list: Cleanup options definitions Dan Williams
                   ` (25 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:52 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

The kernel has 2 object classes for connecting CXL to NVDIMM. There is an
'nvdimm-bridge' object (one per root CXL port) that represents a CXL NVDIMM
Bus, and there are 'pmem' object that represent CXL NVDIMM DIMM devices.
The object that the library is currently calling an nvdimm-bridge is
actually the 'pmem' object. Rename accordingly.

The exported function cxl_memdev_nvdimm_bridge_active() is not renamed, but
since it is a cxl_memdev operation and 'struct cxl_pmem' is an
implementation detail it is fine as is.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 cxl/lib/libcxl.c  |   56 +++++++++++++++++++++++++++--------------------------
 cxl/lib/private.h |    4 ++--
 2 files changed, 30 insertions(+), 30 deletions(-)

diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 8d3cf8092c8b..9839f26f9507 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -45,11 +45,11 @@ struct cxl_ctx {
 	void *private_data;
 };
 
-static void free_bridge(struct cxl_nvdimm_bridge *bridge)
+static void free_pmem(struct cxl_pmem *pmem)
 {
-	free(bridge->dev_buf);
-	free(bridge->dev_path);
-	free(bridge);
+	free(pmem->dev_buf);
+	free(pmem->dev_path);
+	free(pmem);
 }
 
 static void free_memdev(struct cxl_memdev *memdev, struct list_head *head)
@@ -57,7 +57,7 @@ static void free_memdev(struct cxl_memdev *memdev, struct list_head *head)
 	if (head)
 		list_del_from(head, &memdev->list);
 	kmod_module_unref(memdev->module);
-	free_bridge(memdev->bridge);
+	free_pmem(memdev->pmem);
 	free(memdev->firmware_version);
 	free(memdev->dev_buf);
 	free(memdev->dev_path);
@@ -213,36 +213,36 @@ CXL_EXPORT void cxl_set_log_priority(struct cxl_ctx *ctx, int priority)
 	ctx->ctx.log_priority = priority;
 }
 
-static void *add_cxl_bridge(void *parent, int id, const char *br_base)
+static void *add_cxl_pmem(void *parent, int id, const char *br_base)
 {
 	const char *devname = devpath_to_devname(br_base);
 	struct cxl_memdev *memdev = parent;
 	struct cxl_ctx *ctx = memdev->ctx;
-	struct cxl_nvdimm_bridge *bridge;
+	struct cxl_pmem *pmem;
 
-	dbg(ctx, "%s: bridge_base: \'%s\'\n", devname, br_base);
+	dbg(ctx, "%s: pmem_base: \'%s\'\n", devname, br_base);
 
-	bridge = calloc(1, sizeof(*bridge));
-	if (!bridge)
+	pmem = calloc(1, sizeof(*pmem));
+	if (!pmem)
 		goto err_dev;
-	bridge->id = id;
+	pmem->id = id;
 
-	bridge->dev_path = strdup(br_base);
-	if (!bridge->dev_path)
+	pmem->dev_path = strdup(br_base);
+	if (!pmem->dev_path)
 		goto err_read;
 
-	bridge->dev_buf = calloc(1, strlen(br_base) + 50);
-	if (!bridge->dev_buf)
+	pmem->dev_buf = calloc(1, strlen(br_base) + 50);
+	if (!pmem->dev_buf)
 		goto err_read;
-	bridge->buf_len = strlen(br_base) + 50;
+	pmem->buf_len = strlen(br_base) + 50;
 
-	memdev->bridge = bridge;
-	return bridge;
+	memdev->pmem = pmem;
+	return pmem;
 
  err_read:
-	free(bridge->dev_buf);
-	free(bridge->dev_path);
-	free(bridge);
+	free(pmem->dev_buf);
+	free(pmem->dev_path);
+	free(pmem);
  err_dev:
 	return NULL;
 }
@@ -319,7 +319,7 @@ static void *add_cxl_memdev(void *parent, int id, const char *cxlmem_base)
 		goto err_read;
 	memdev->buf_len = strlen(cxlmem_base) + 50;
 
-	sysfs_device_parse(ctx, cxlmem_base, "pmem", memdev, add_cxl_bridge);
+	sysfs_device_parse(ctx, cxlmem_base, "pmem", memdev, add_cxl_pmem);
 
 	cxl_memdev_foreach(ctx, memdev_dup)
 		if (memdev_dup->id == memdev->id) {
@@ -430,18 +430,18 @@ static int is_enabled(const char *drvpath)
 CXL_EXPORT int cxl_memdev_nvdimm_bridge_active(struct cxl_memdev *memdev)
 {
 	struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev);
-	struct cxl_nvdimm_bridge *bridge = memdev->bridge;
+	struct cxl_pmem *pmem = memdev->pmem;
 	char *path;
 	int len;
 
-	if (!bridge)
+	if (!pmem)
 		return 0;
 
-	path = bridge->dev_buf;
-	len = bridge->buf_len;
+	path = pmem->dev_buf;
+	len = pmem->buf_len;
 
-	if (snprintf(path, len, "%s/driver", bridge->dev_path) >= len) {
-		err(ctx, "%s: nvdimm bridge buffer too small!\n",
+	if (snprintf(path, len, "%s/driver", pmem->dev_path) >= len) {
+		err(ctx, "%s: nvdimm pmem buffer too small!\n",
 				cxl_memdev_get_devname(memdev));
 		return 0;
 	}
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
index 28f7e16dbd04..7c81e24a6f79 100644
--- a/cxl/lib/private.h
+++ b/cxl/lib/private.h
@@ -10,7 +10,7 @@
 
 #define CXL_EXPORT __attribute__ ((visibility("default")))
 
-struct cxl_nvdimm_bridge {
+struct cxl_pmem {
 	int id;
 	void *dev_buf;
 	size_t buf_len;
@@ -30,7 +30,7 @@ struct cxl_memdev {
 	int payload_max;
 	size_t lsa_size;
 	struct kmod_module *module;
-	struct cxl_nvdimm_bridge *bridge;
+	struct cxl_pmem *pmem;
 	unsigned long long serial;
 };
 


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 12/37] cxl/list: Cleanup options definitions
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (10 preceding siblings ...)
  2022-01-24  0:52 ` [ndctl PATCH 11/37] cxl/lib: Rename nvdimm bridge to pmem Dan Williams
@ 2022-01-24  0:52 ` Dan Williams
  2022-01-24  0:52 ` [ndctl PATCH 13/37] Documentation: Enhance libcxl memdev API documentation Dan Williams
                   ` (24 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:52 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Clarify which options take lists by adding a "(s)" to the object name, and
move the option block out of cmd_list() to reduce the column-80 collisions.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 cxl/list.c |   30 ++++++++++++++++--------------
 1 file changed, 16 insertions(+), 14 deletions(-)

diff --git a/cxl/list.c b/cxl/list.c
index 6bc48df0ea84..7e2744dd8b19 100644
--- a/cxl/list.c
+++ b/cxl/list.c
@@ -19,22 +19,24 @@ static int num_list_flags(void)
 	return param.memdevs;
 }
 
+static const struct option options[] = {
+	OPT_STRING('m', "memdev", &param.memdev_filter, "memory device name(s)",
+		   "filter by CXL memory device name(s)"),
+	OPT_STRING('s', "serial", &param.serial_filter,
+		   "memory device serial(s)",
+		   "filter by CXL memory device serial number(s)"),
+	OPT_BOOLEAN('M', "memdevs", &param.memdevs,
+		    "include CXL memory device info"),
+	OPT_BOOLEAN('i', "idle", &param.idle, "include disabled devices"),
+	OPT_BOOLEAN('u', "human", &param.human,
+		    "use human friendly number formats "),
+	OPT_BOOLEAN('H', "health", &param.health,
+		    "include memory device health information "),
+	OPT_END(),
+};
+
 int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 {
-	const struct option options[] = {
-		OPT_STRING('m', "memdev", &param.memdev_filter, "memory device name",
-			   "filter by CXL memory device name"),
-		OPT_STRING('s', "serial", &param.serial_filter, "memory device serial",
-			   "filter by CXL memory device serial number"),
-		OPT_BOOLEAN('M', "memdevs", &param.memdevs,
-			    "include CXL memory device info"),
-		OPT_BOOLEAN('i', "idle", &param.idle, "include disabled devices"),
-		OPT_BOOLEAN('u', "human", &param.human,
-				"use human friendly number formats "),
-		OPT_BOOLEAN('H', "health", &param.health,
-				"include memory device health information "),
-		OPT_END(),
-	};
 	const char * const u[] = {
 		"cxl list [<options>]",
 		NULL


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 13/37] Documentation: Enhance libcxl memdev API documentation
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (11 preceding siblings ...)
  2022-01-24  0:52 ` [ndctl PATCH 12/37] cxl/list: Cleanup options definitions Dan Williams
@ 2022-01-24  0:52 ` Dan Williams
  2022-01-24  0:53 ` [ndctl PATCH 14/37] cxl/list: Add bus objects Dan Williams
                   ` (23 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:52 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

In preparation for adding documentation for more objects, organize the
current into subsections and flesh out descriptions for the current APIs.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/copyright.txt      |    2 -
 Documentation/cxl/lib/libcxl.txt |  111 ++++++++++++++++++++++++++++++++++----
 2 files changed, 99 insertions(+), 14 deletions(-)

diff --git a/Documentation/copyright.txt b/Documentation/copyright.txt
index a9380e199750..af9caf7ba22a 100644
--- a/Documentation/copyright.txt
+++ b/Documentation/copyright.txt
@@ -2,7 +2,7 @@
 
 COPYRIGHT
 ---------
-Copyright (C) 2016 - 2020, Intel Corporation. License GPLv2: GNU GPL
+Copyright (C) 2016 - 2022, Intel Corporation. License GPLv2: GNU GPL
 version 2 <http://gnu.org/licenses/gpl.html>.  This is free software:
 you are free to change and redistribute it.  There is NO WARRANTY, to
 the extent permitted by law.
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index 2539369e8111..c127326a2840 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -20,27 +20,100 @@ libcxl provides interfaces to interact with CXL devices in Linux, using sysfs
 interfaces for most kernel interactions, and the ioctl() interface for command
 submission.
 
-The starting point for all library interfaces is a 'cxl_ctx' object, returned
-by linklibcxl:cxl_new[3]. CXL 'Type 3' memory devices are children of the
-cxl_ctx object, and can be iterated through using an iterator API.
+The starting point for all library interfaces is a 'cxl_ctx' object,
+returned by linklibcxl:cxl_new[3]. CXL 'Type 3' memory devices and other
+CXL device objects are descendants of the cxl_ctx object, and can be
+iterated via an object an iterator API of the form
+cxl_<object>_foreach(<parent object>, <object iterator>).
 
-Library level interfaces that are agnostic to any device, or a specific
-subclass of operations have the prefix 'cxl_'
+MEMDEVS
+-------
+The object representing a CXL memory expander (Type 3 device) is 'struct
+cxl_memdev'. Library interfaces related to these devices have the prefix
+'cxl_memdev_'. These interfaces are mostly associated with sysfs
+interactions (unless otherwise noted in their respective documentation
+sections). They are typically used to retrieve data published by the
+kernel, or to send data or trigger kernel operations for a given device.
 
-The object representing a CXL Type 3 device is 'cxl_memdev'. Library interfaces
-related to these devices have the prefix 'cxl_memdev_'. These interfaces are
-mostly associated with sysfs interactions (unless otherwise noted in their
-respective documentation pages). They are typically used to retrieve data
-published by the kernel, or to send data or trigger kernel operations for a
-given device.
+=== MEMDEV: Enumeration
+----
+struct cxl_memdev *cxl_memdev_get_first(struct cxl_ctx *ctx);
+struct cxl_memdev *cxl_memdev_get_next(struct cxl_memdev *memdev);
+struct cxl_ctx *cxl_memdev_get_ctx(struct cxl_memdev *memdev);
+
+#define cxl_memdev_foreach(ctx, memdev) \
+        for (memdev = cxl_memdev_get_first(ctx); \
+             memdev != NULL; \
+             memdev = cxl_memdev_get_next(memdev))
+
+----
+
+CXL memdev instances are enumerated from the global library context
+'struct cxl_ctx'. By default a memdev only offers a portal to submit
+memory device commands, see the port, decoder, and endpoint APIs to
+determine what if any CXL Memory Resources are reachable given a
+specific memdev.
+
+=== MEMDEV: Attributes
+----
+int cxl_memdev_get_id(struct cxl_memdev *memdev);
+unsigned long long cxl_memdev_get_serial(struct cxl_memdev *memdev);
+const char *cxl_memdev_get_devname(struct cxl_memdev *memdev);
+int cxl_memdev_get_major(struct cxl_memdev *memdev);
+int cxl_memdev_get_minor(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);
+const char *cxl_memdev_get_firmware_verison(struct cxl_memdev *memdev);
+size_t cxl_memdev_get_label_size(struct cxl_memdev *memdev);
+int cxl_memdev_nvdimm_bridge_active(struct cxl_memdev *memdev);
+----
+
+A memdev is given a kernel device name of the form "mem%d" where an id
+(cxl_memdev_get_id()) is dynamically allocated as devices are
+discovered. Note that there are no guarantees that ids / kernel device
+names for memdevs are stable from one boot to the next, devices are
+enumerated asynchronously. If a stable identifier is use
+cxl_memdev_get_serial() which returns a value according to the 'Device
+Serial Number Extended Capability' in the PCIe 5.0 Base Specification.
+
+The character device node for command submission can be found by default
+at /dev/cxl/mem%d, or created with a major / minor returned from
+cxl_memdev_get_{major,minor}().
+
+The 'pmem_size' and 'ram_size' attributes return the current
+provisioning of DPA (Device Physical Address / local capacity) in the
+device.
+
+=== MEMDEV: Commands
+----
+struct cxl_cmd *cxl_cmd_new_raw(struct cxl_memdev *memdev, int opcode);
+struct cxl_cmd *cxl_cmd_new_identify(struct cxl_memdev *memdev);
+struct cxl_cmd *cxl_cmd_new_get_health_info(struct cxl_memdev *memdev);
+struct cxl_cmd *cxl_cmd_new_read_label(struct cxl_memdev *memdev,
+					unsigned int offset, unsigned int length);
+struct cxl_cmd *cxl_cmd_new_write_label(struct cxl_memdev *memdev, void *buf,
+					unsigned int offset, unsigned int length);
+int cxl_memdev_zero_label(struct cxl_memdev *memdev, size_t length,
+			  size_t offset);
+int cxl_memdev_read_label(struct cxl_memdev *memdev, void *buf, size_t length,
+			  size_t offset);
+int cxl_memdev_write_label(struct cxl_memdev *memdev, void *buf, size_t length,
+			   size_t offset);
+
+----
 
 A 'cxl_cmd' is a reference counted object which is used to perform 'Mailbox'
 commands as described in the CXL Specification. A 'cxl_cmd' object is tied to a
 'cxl_memdev'. Associated library interfaces have the prefix 'cxl_cmd_'. Within
 this sub-class of interfaces, there are:
 
- * 'cxl_cmd_new_*' interfaces that allocate a new cxl_cmd object for a given
-   command type.
+ * 'cxl_cmd_new_*()' interfaces that allocate a new cxl_cmd object for a given
+   command type targeted at a given memdev. As part of the command
+   instantiation process the library validates that the command is
+   supported by the memory device, otherwise it returns NULL to indicate
+   'no support'. The libcxl command id is translated by the kernel into
+   a CXL standard opcode. See the potential command ids in
+   /usr/include/linux/cxl_mem.h.
 
  * 'cxl_cmd_submit' which submits the command via ioctl()
 
@@ -49,6 +122,18 @@ this sub-class of interfaces, there are:
 
  * 'cxl_cmd_get_*' interfaces to get general command related information.
 
+cxl_cmd_new_raw() supports so called 'RAW' commands where the command id
+is 'RAW' and it carries an unmodified CXL memory device command payload
+associated with the 'opcode' argument. Given the kernel does minimal
+input validation on these commands typically raw commands are not
+supported by the kernel outside debug build scenarios. libcxl is limited
+to supporting commands that appear in the CXL standard / public
+specifications.
+
+cxl_memdev{read,write,zero}_label() are helpers for marshaling multiple
+label access commands over an arbitrary extent of the device's label
+area.
+
 include::../../copyright.txt[]
 
 SEE ALSO


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 14/37] cxl/list: Add bus objects
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (12 preceding siblings ...)
  2022-01-24  0:52 ` [ndctl PATCH 13/37] Documentation: Enhance libcxl memdev API documentation Dan Williams
@ 2022-01-24  0:53 ` Dan Williams
  2022-01-24  0:53 ` [ndctl PATCH 15/37] util/json: Warn on stderr about empty list results Dan Williams
                   ` (22 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:53 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

A 'struct cxl_bus' represents a CXL.mem domain. It is the root of a
Host-managed Device Memory (HDM) hierarchy. When memory devices are enabled
for CXL operation they appear underneath a bus in a 'cxl list -BM' listing,
otherwise they display as disconnected.

A 'bus' is identical to the kernel's CXL root port object, but given the
confusion between CXL root ports, and PCIe root ports, the 'bus' name is
less ambiguous. It also serves a similar role in the object hierarchy as a
'struct ndctl_bus' object. It is also the case that the "root" name will
appear as the kernel device-name, so the association will be clear.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 .clang-format                    |    1 
 Documentation/cxl/cxl-list.txt   |   88 ++++++++++++++++++++----
 Documentation/cxl/lib/libcxl.txt |   30 ++++++++
 cxl/filter.c                     |  117 +++++++++++++++++++++++++++++++
 cxl/filter.h                     |    2 +
 cxl/json.c                       |   21 ++++++
 cxl/json.h                       |    5 +
 cxl/lib/libcxl.c                 |  142 ++++++++++++++++++++++++++++++++++++++
 cxl/lib/libcxl.sym               |    5 +
 cxl/lib/private.h                |   14 ++++
 cxl/libcxl.h                     |   11 +++
 cxl/list.c                       |   19 +++--
 12 files changed, 431 insertions(+), 24 deletions(-)

diff --git a/.clang-format b/.clang-format
index d2e77d0e0b4b..1154c76b1399 100644
--- a/.clang-format
+++ b/.clang-format
@@ -78,6 +78,7 @@ ExperimentalAutoDetectBinPacking: false
 # 	| sort -u)
 ForEachMacros:
   - 'cxl_memdev_foreach'
+  - 'cxl_bus_foreach'
   - 'daxctl_dev_foreach'
   - 'daxctl_mapping_foreach'
   - 'daxctl_region_foreach'
diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index 224c972498ea..be131ae7b02a 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -15,17 +15,60 @@ 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.
+Options can be specified to limit the output to specific objects. When a
+single object type is specified the return json object is an array of
+just those objects, when multiple objects types are specified the
+returned the returned object may be an array of arrays with the inner
+array named for the given object type.
+
+Filters can by specifed as either a single identidier, a space separated
+quoted string, or a comma separated list. When multiple filter
+identifiers are specified within a filter string, like "-m
+mem0,mem1,mem2", they are combined as an 'OR' filter.  When multiple
+filter string types are specified, like "-m mem0,mem1,mem2 -p port10",
+they are combined as an 'AND' filter. So, "-m mem0,mem1,mem2 -p port10"
+would only list objects that are beneath port10 AND map mem0, mem1, OR
+mem2.
+
+The --human option in addition to reformatting some fields to more human
+friendly strings also unwraps the array to reduce the number of lines of
+output.
 
 EXAMPLE
 -------
 ----
 # cxl list --memdevs
-{
-  "memdev":"mem0",
-  "pmem_size":268435456,
-  "ram_size":0,
-}
+[
+  {
+    "memdev":"mem0",
+    "pmem_size":268435456,
+    "ram_size":0,
+    "serial":0
+  }
+]
+
+# cxl list -BMu
+[
+  {
+    "anon memdevs":[
+      {
+        "memdev":"mem0",
+        "pmem_size":"256.00 MiB (268.44 MB)",
+        "ram_size":0,
+        "serial":"0"
+      }
+    ]
+  },
+  {
+    "buses":[
+      {
+        "bus":"root0",
+        "provider":"ACPI.CXL"
+      }
+    ]
+  }
+]
+
 ----
 
 OPTIONS
@@ -34,13 +77,6 @@ OPTIONS
 --memdev=::
 	Specify CXL memory device name(s), or device id(s), to filter the listing. For example:
 ----
-# cxl list --memdev=mem0
-{
-  "memdev":"mem0",
-  "pmem_size":268435456,
-  "ram_size":0,
-}
-
 # cxl list -M --memdev="0 mem3 5"
 [
   {
@@ -114,6 +150,32 @@ OPTIONS
 ]
 ----
 
+-B::
+--buses::
+	Include 'bus' / CXL root object(s) in the listing. Typically, on ACPI
+	systems the bus object is a singleton associated with the ACPI0017
+	device, but there are test scenerios where there may be multiple CXL
+	memory hierarchies.
+----
+# cxl list -B
+[
+  {
+    "bus":"root3",
+    "provider":"cxl_test"
+  },
+  {
+    "bus":"root0",
+    "provider":"ACPI.CXL"
+  }
+]
+----
+
+-b::
+--bus=::
+	Specify CXL root device name(s), device id(s), and / or CXL bus provider
+	names to filter the listing. The supported provider names are "ACPI.CXL"
+	and "cxl_test".
+
 include::human-option.txt[]
 
 include::verbose-option.txt[]
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index c127326a2840..84af66a3a7bd 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -134,6 +134,36 @@ cxl_memdev{read,write,zero}_label() are helpers for marshaling multiple
 label access commands over an arbitrary extent of the device's label
 area.
 
+BUSES
+-----
+The CXL Memory space is CPU and Device coherent. The address ranges that
+support coherent access are described by platform firmware and
+communicated to the operating system via a CXL root object 'struct
+cxl_bus'.
+
+=== BUS: Enumeration
+----
+struct cxl_bus *cxl_bus_get_first(struct cxl_ctx *ctx);
+struct cxl_bus *cxl_bus_get_next(struct cxl_bus *bus);
+
+#define cxl_bus_foreach(ctx, bus)                                           \
+       for (bus = cxl_bus_get_first(ctx); bus != NULL;                      \
+            bus = cxl_bus_get_next(bus))
+----
+
+=== BUS: Attributes
+----
+const char *cxl_bus_get_provider(struct cxl_bus *bus);
+const char *cxl_bus_get_devname(struct cxl_bus *bus);
+int cxl_bus_get_id(struct cxl_bus *bus);
+----
+
+The provider name of a bus is a persistent name that is independent of
+discovery order. The possible provider names are 'ACPI.CXL' and
+'cxl_test'. The devname and id attributes, like other objects, are just
+the kernel device names that are subject to change based on discovery
+order.
+
 include::../../copyright.txt[]
 
 SEE ALSO
diff --git a/cxl/filter.c b/cxl/filter.c
index 26efc65f4dd7..5f4844b4cab9 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -1,5 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0
-// Copyright (C) 2015-2020 Intel Corporation. All rights reserved.
+// Copyright (C) 2015-2022 Intel Corporation. All rights reserved.
 #include <errno.h>
 #include <stdio.h>
 #include <string.h>
@@ -21,6 +21,43 @@ static const char *which_sep(const char *filter)
 	return " ";
 }
 
+static struct cxl_bus *util_cxl_bus_filter(struct cxl_bus *bus,
+					   const char *__ident)
+{
+	char *ident, *save;
+	const char *arg;
+	int bus_id;
+
+	if (!__ident)
+		return bus;
+
+	ident = strdup(__ident);
+	if (!ident)
+		return NULL;
+
+	for (arg = strtok_r(ident, which_sep(__ident), &save); arg;
+	     arg = strtok_r(NULL, which_sep(__ident), &save)) {
+		if (strcmp(arg, "all") == 0)
+			break;
+
+		if ((sscanf(arg, "%d", &bus_id) == 1 ||
+		     sscanf(arg, "root%d", &bus_id) == 1) &&
+		    cxl_bus_get_id(bus) == bus_id)
+			break;
+
+		if (strcmp(arg, cxl_bus_get_devname(bus)) == 0)
+			break;
+
+		if (strcmp(arg, cxl_bus_get_provider(bus)) == 0)
+			break;
+	}
+
+	free(ident);
+	if (arg)
+		return bus;
+	return NULL;
+}
+
 static struct cxl_memdev *
 util_cxl_memdev_serial_filter(struct cxl_memdev *memdev, const char *__serials)
 {
@@ -98,21 +135,67 @@ static unsigned long params_to_flags(struct cxl_filter_params *param)
 	return flags;
 }
 
+static void splice_array(struct cxl_filter_params *p, struct json_object *jobjs,
+			 struct json_object *platform,
+			 const char *container_name, bool do_container)
+{
+	size_t count;
+
+	if (!json_object_array_length(jobjs)) {
+		json_object_put(jobjs);
+		return;
+	}
+
+	if (do_container) {
+		struct json_object *container = json_object_new_object();
+
+		if (!container) {
+			err(p, "failed to list: %s\n", container_name);
+			return;
+		}
+
+		json_object_object_add(container, container_name, jobjs);
+		json_object_array_add(platform, container);
+		return;
+	}
+
+	for (count = json_object_array_length(jobjs); count; count--) {
+		struct json_object *jobj = json_object_array_get_idx(jobjs, 0);
+
+		json_object_get(jobj);
+		json_object_array_del_idx(jobjs, 0, 1);
+		json_object_array_add(platform, jobj);
+	}
+	json_object_put(jobjs);
+}
+
 int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 {
 	struct json_object *jplatform = json_object_new_array();
+	struct json_object *jdevs = NULL, *jbuses = NULL;
 	unsigned long flags = params_to_flags(p);
 	struct cxl_memdev *memdev;
+	int top_level_objs = 0;
+	struct cxl_bus *bus;
 
 	if (!jplatform) {
 		dbg(p, "platform object allocation failure\n");
 		return -ENOMEM;
 	}
 
+	jdevs = json_object_new_array();
+	if (!jdevs)
+		goto err;
+
+	jbuses = json_object_new_array();
+	if (!jbuses)
+		goto err;
+
 	cxl_memdev_foreach(ctx, memdev) {
 		struct json_object *jdev;
 
-		if (!util_cxl_memdev_filter(memdev, p->memdev_filter, p->serial_filter))
+		if (!util_cxl_memdev_filter(memdev, p->memdev_filter,
+					    p->serial_filter))
 			continue;
 		if (p->memdevs) {
 			jdev = util_cxl_memdev_to_json(memdev, flags);
@@ -120,11 +203,39 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 				dbg(p, "memdev object allocation failure\n");
 				continue;
 			}
-			json_object_array_add(jplatform, jdev);
+			json_object_array_add(jdevs, jdev);
+		}
+	}
+
+	cxl_bus_foreach(ctx, bus) {
+		struct json_object *jbus;
+
+		if (!util_cxl_bus_filter(bus, p->bus_filter))
+			continue;
+		if (p->buses) {
+			jbus = util_cxl_bus_to_json(bus, flags);
+			if (!jbus) {
+				dbg(p, "bus object allocation failure\n");
+				continue;
+			}
+			json_object_array_add(jbuses, jbus);
 		}
 	}
 
+	if (json_object_array_length(jdevs))
+		top_level_objs++;
+	if (json_object_array_length(jbuses))
+		top_level_objs++;
+
+	splice_array(p, jdevs, jplatform, "anon memdevs", top_level_objs > 1);
+	splice_array(p, jbuses, jplatform, "buses", top_level_objs > 1);
+
 	util_display_json_array(stdout, jplatform, flags);
 
 	return 0;
+err:
+	json_object_put(jdevs);
+	json_object_put(jbuses);
+	json_object_put(jplatform);
+	return -ENOMEM;
 }
diff --git a/cxl/filter.h b/cxl/filter.h
index 12d934474554..d41e757cf51d 100644
--- a/cxl/filter.h
+++ b/cxl/filter.h
@@ -9,7 +9,9 @@
 struct cxl_filter_params {
 	const char *memdev_filter;
 	const char *serial_filter;
+	const char *bus_filter;
 	bool memdevs;
+	bool buses;
 	bool idle;
 	bool human;
 	bool health;
diff --git a/cxl/json.c b/cxl/json.c
index d8e65df241a1..a58459482360 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -221,3 +221,24 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
 	}
 	return jdev;
 }
+
+struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
+					 unsigned long flags)
+{
+	const char *devname = cxl_bus_get_devname(bus);
+	struct json_object *jbus, *jobj;
+
+	jbus = json_object_new_object();
+	if (!jbus)
+		return NULL;
+
+	jobj = json_object_new_string(devname);
+	if (jobj)
+		json_object_object_add(jbus, "bus", jobj);
+
+	jobj = json_object_new_string(cxl_bus_get_provider(bus));
+	if (jobj)
+		json_object_object_add(jbus, "provider", jobj);
+
+	return jbus;
+}
diff --git a/cxl/json.h b/cxl/json.h
index 3abcfe6661bf..4abf6e500f0b 100644
--- a/cxl/json.h
+++ b/cxl/json.h
@@ -1,8 +1,11 @@
 /* SPDX-License-Identifier: GPL-2.0 */
-/* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */
+/* Copyright (C) 2015-2022 Intel Corporation. All rights reserved. */
 #ifndef __CXL_UTIL_JSON_H__
 #define __CXL_UTIL_JSON_H__
 struct cxl_memdev;
 struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
 		unsigned long flags);
+struct cxl_bus;
+struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
+					 unsigned long flags);
 #endif /* __CXL_UTIL_JSON_H__ */
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 9839f26f9507..8548a458b131 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -40,7 +40,9 @@ struct cxl_ctx {
 	int refcount;
 	void *userdata;
 	int memdevs_init;
+	int buses_init;
 	struct list_head memdevs;
+	struct list_head buses;
 	struct kmod_ctx *kmod_ctx;
 	void *private_data;
 };
@@ -64,6 +66,21 @@ static void free_memdev(struct cxl_memdev *memdev, struct list_head *head)
 	free(memdev);
 }
 
+static void __free_port(struct cxl_port *port, struct list_head *head)
+{
+	if (head)
+		list_del_from(head, &port->list);
+	free(port->dev_buf);
+	free(port->dev_path);
+	free(port->uport);
+}
+
+static void free_bus(struct cxl_bus *bus, struct list_head *head)
+{
+	__free_port(&bus->port, head);
+	free(bus);
+}
+
 /**
  * cxl_get_userdata - retrieve stored data pointer from library context
  * @ctx: cxl library context
@@ -130,6 +147,7 @@ CXL_EXPORT int cxl_new(struct cxl_ctx **ctx)
 	dbg(c, "log_priority=%d\n", c->ctx.log_priority);
 	*ctx = c;
 	list_head_init(&c->memdevs);
+	list_head_init(&c->buses);
 	c->kmod_ctx = kmod_ctx;
 
 	return 0;
@@ -160,6 +178,7 @@ CXL_EXPORT struct cxl_ctx *cxl_ref(struct cxl_ctx *ctx)
 CXL_EXPORT void cxl_unref(struct cxl_ctx *ctx)
 {
 	struct cxl_memdev *memdev, *_d;
+	struct cxl_bus *bus, *_b;
 
 	if (ctx == NULL)
 		return;
@@ -170,6 +189,9 @@ CXL_EXPORT void cxl_unref(struct cxl_ctx *ctx)
 	list_for_each_safe(&ctx->memdevs, memdev, _d, list)
 		free_memdev(memdev, &ctx->memdevs);
 
+	list_for_each_safe(&ctx->buses, bus, _b, port.list)
+		free_bus(bus, &ctx->buses);
+
 	kmod_unref(ctx->kmod_ctx);
 	info(ctx, "context %p released\n", ctx);
 	free(ctx);
@@ -449,6 +471,126 @@ CXL_EXPORT int cxl_memdev_nvdimm_bridge_active(struct cxl_memdev *memdev)
 	return is_enabled(path);
 }
 
+static int cxl_port_init(struct cxl_port *port, struct cxl_ctx *ctx, int id,
+			 const char *cxlport_base)
+{
+	char *path = calloc(1, strlen(cxlport_base) + 100);
+	size_t rc;
+
+	if (!path)
+		return -ENOMEM;
+
+	port->id = id;
+	port->ctx = ctx;
+
+	port->dev_path = strdup(cxlport_base);
+	if (!port->dev_path)
+		goto err;
+
+	port->dev_buf = calloc(1, strlen(cxlport_base) + 50);
+	if (!port->dev_buf)
+		goto err;
+	port->buf_len = strlen(cxlport_base) + 50;
+
+	rc = snprintf(port->dev_buf, port->buf_len, "%s/uport", cxlport_base);
+	if (rc >= port->buf_len)
+		goto err;
+	port->uport = realpath(port->dev_buf, NULL);
+	if (!port->uport)
+		goto err;
+
+	return 0;
+err:
+	free(port->dev_path);
+	free(port->dev_buf);
+	free(path);
+	return -ENOMEM;
+}
+
+static void *add_cxl_bus(void *parent, int id, const char *cxlbus_base)
+{
+	const char *devname = devpath_to_devname(cxlbus_base);
+	struct cxl_bus *bus, *bus_dup;
+	struct cxl_ctx *ctx = parent;
+	struct cxl_port *port;
+	int rc;
+
+	dbg(ctx, "%s: base: \'%s\'\n", devname, cxlbus_base);
+
+	bus = calloc(1, sizeof(*bus));
+	if (!bus)
+		return NULL;
+
+	port = &bus->port;
+	rc = cxl_port_init(port, ctx, id, cxlbus_base);
+	if (rc)
+		goto err;
+
+	cxl_bus_foreach(ctx, bus_dup)
+		if (bus_dup->port.id == bus->port.id) {
+			free_bus(bus, NULL);
+			return bus_dup;
+		}
+
+	list_add(&ctx->buses, &port->list);
+	return bus;
+
+err:
+	free(bus);
+	return NULL;
+}
+
+static void cxl_buses_init(struct cxl_ctx *ctx)
+{
+	if (ctx->buses_init)
+		return;
+
+	ctx->buses_init = 1;
+
+	sysfs_device_parse(ctx, "/sys/bus/cxl/devices", "root", ctx,
+			   add_cxl_bus);
+}
+
+CXL_EXPORT struct cxl_bus *cxl_bus_get_first(struct cxl_ctx *ctx)
+{
+	cxl_buses_init(ctx);
+
+	return list_top(&ctx->buses, struct cxl_bus, port.list);
+}
+
+CXL_EXPORT struct cxl_bus *cxl_bus_get_next(struct cxl_bus *bus)
+{
+	struct cxl_ctx *ctx = bus->port.ctx;
+
+	return list_next(&ctx->buses, bus, port.list);
+}
+
+CXL_EXPORT const char *cxl_bus_get_devname(struct cxl_bus *bus)
+{
+	struct cxl_port *port = &bus->port;
+
+	return devpath_to_devname(port->dev_path);
+}
+
+CXL_EXPORT int cxl_bus_get_id(struct cxl_bus *bus)
+{
+	struct cxl_port *port = &bus->port;
+
+	return port->id;
+}
+
+CXL_EXPORT const char *cxl_bus_get_provider(struct cxl_bus *bus)
+{
+	struct cxl_port *port = &bus->port;
+	const char *devname = devpath_to_devname(port->uport);
+
+	if (strcmp(devname, "ACPI0017:00") == 0)
+		return "ACPI.CXL";
+	if (strcmp(devname, "cxl_acpi.0") == 0)
+		return "cxl_test";
+	return devname;
+}
+
 CXL_EXPORT void cxl_cmd_unref(struct cxl_cmd *cmd)
 {
 	if (!cmd)
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index 4411035f962a..781ff996af73 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -77,4 +77,9 @@ local:
 LIBCXL_2 {
 global:
 	cxl_memdev_get_serial;
+	cxl_bus_get_first;
+	cxl_bus_get_next;
+	cxl_bus_get_provider;
+	cxl_bus_get_devname;
+	cxl_bus_get_id;
 } LIBCXL_1;
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
index 7c81e24a6f79..0758d0578acf 100644
--- a/cxl/lib/private.h
+++ b/cxl/lib/private.h
@@ -34,6 +34,20 @@ struct cxl_memdev {
 	unsigned long long serial;
 };
 
+struct cxl_port {
+	int id;
+	void *dev_buf;
+	size_t buf_len;
+	char *dev_path;
+	char *uport;
+	struct cxl_ctx *ctx;
+	struct list_node list;
+};
+
+struct cxl_bus {
+	struct cxl_port port;
+};
+
 enum cxl_cmd_query_status {
 	CXL_CMD_QUERY_NOT_RUN = 0,
 	CXL_CMD_QUERY_OK,
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index bcdede8f12e8..da66eb298ab8 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -57,6 +57,17 @@ int cxl_memdev_write_label(struct cxl_memdev *memdev, void *buf, size_t length,
              memdev != NULL; \
              memdev = cxl_memdev_get_next(memdev))
 
+struct cxl_bus;
+struct cxl_bus *cxl_bus_get_first(struct cxl_ctx *ctx);
+struct cxl_bus *cxl_bus_get_next(struct cxl_bus *bus);
+const char *cxl_bus_get_provider(struct cxl_bus *bus);
+const char *cxl_bus_get_devname(struct cxl_bus *bus);
+int cxl_bus_get_id(struct cxl_bus *bus);
+
+#define cxl_bus_foreach(ctx, bus)                                              \
+	for (bus = cxl_bus_get_first(ctx); bus != NULL;                        \
+	     bus = cxl_bus_get_next(bus))
+
 struct cxl_cmd;
 const char *cxl_cmd_get_devname(struct cxl_cmd *cmd);
 struct cxl_cmd *cxl_cmd_new_raw(struct cxl_memdev *memdev, int opcode);
diff --git a/cxl/list.c b/cxl/list.c
index 7e2744dd8b19..9500e610e0ca 100644
--- a/cxl/list.c
+++ b/cxl/list.c
@@ -1,5 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0
-/* Copyright (C) 2020-2021 Intel Corporation. All rights reserved. */
+/* Copyright (C) 2020-2022 Intel Corporation. All rights reserved. */
 #include <stdio.h>
 #include <errno.h>
 #include <stdlib.h>
@@ -14,11 +14,6 @@
 
 static struct cxl_filter_params param;
 
-static int num_list_flags(void)
-{
-	return param.memdevs;
-}
-
 static const struct option options[] = {
 	OPT_STRING('m', "memdev", &param.memdev_filter, "memory device name(s)",
 		   "filter by CXL memory device name(s)"),
@@ -27,6 +22,9 @@ static const struct option options[] = {
 		   "filter by CXL memory device serial number(s)"),
 	OPT_BOOLEAN('M', "memdevs", &param.memdevs,
 		    "include CXL memory device info"),
+	OPT_STRING('b', "bus", &param.bus_filter, "bus device name",
+		   "filter by CXL bus device name(s)"),
+	OPT_BOOLEAN('B', "buses", &param.buses, "include CXL bus info"),
 	OPT_BOOLEAN('i', "idle", &param.idle, "include disabled devices"),
 	OPT_BOOLEAN('u', "human", &param.human,
 		    "use human friendly number formats "),
@@ -35,6 +33,11 @@ static const struct option options[] = {
 	OPT_END(),
 };
 
+static int num_list_flags(void)
+{
+       return !!param.memdevs + !!param.buses;
+}
+
 int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 {
 	const char * const u[] = {
@@ -53,7 +56,9 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 	if (num_list_flags() == 0) {
 		if (param.memdev_filter || param.serial_filter)
 			param.memdevs = true;
-		else {
+		if (param.bus_filter)
+			param.buses = true;
+		if (num_list_flags() == 0) {
 			/*
 			 * TODO: We likely want to list regions by default if
 			 * nothing was explicitly asked for. But until we have


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 15/37] util/json: Warn on stderr about empty list results
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (13 preceding siblings ...)
  2022-01-24  0:53 ` [ndctl PATCH 14/37] cxl/list: Add bus objects Dan Williams
@ 2022-01-24  0:53 ` Dan Williams
  2022-01-24  0:53 ` [ndctl PATCH 16/37] util/sysfs: Uplevel modalias lookup helper to util/ Dan Williams
                   ` (21 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:53 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Help interactive users notice something is wrong with the list parameters
by warning that no devices matched the specified filter settings.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 util/json.c |    7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/util/json.c b/util/json.c
index bd5f8fc47a2b..f8cc81f6e706 100644
--- a/util/json.c
+++ b/util/json.c
@@ -3,6 +3,7 @@
 #include <limits.h>
 #include <string.h>
 #include <stdio.h>
+#include <util/util.h>
 #include <util/json.h>
 #include <json-c/json.h>
 #include <json-c/printbuf.h>
@@ -95,9 +96,11 @@ void util_display_json_array(FILE *f_out, struct json_object *jarray,
 	int len = json_object_array_length(jarray);
 	int jflag = JSON_C_TO_STRING_PRETTY;
 
-	if (json_object_array_length(jarray) > 1 || !(flags & UTIL_JSON_HUMAN))
+	if (len > 1 || !(flags & UTIL_JSON_HUMAN)) {
+		if (len == 0)
+			warning("no matching devices found\n");
 		fprintf(f_out, "%s\n", json_object_to_json_string_ext(jarray, jflag));
-	else if (len) {
+	} else if (len) {
 		struct json_object *jobj;
 
 		jobj = json_object_array_get_idx(jarray, 0);


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 16/37] util/sysfs: Uplevel modalias lookup helper to util/
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (14 preceding siblings ...)
  2022-01-24  0:53 ` [ndctl PATCH 15/37] util/json: Warn on stderr about empty list results Dan Williams
@ 2022-01-24  0:53 ` Dan Williams
  2022-01-24  0:53 ` [ndctl PATCH 17/37] cxl/list: Add port enumeration Dan Williams
                   ` (20 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:53 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

The to_module() helper looks up modules relative to a modalias. Uplevel
this to share with libcxl.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 ndctl/lib/libndctl.c |   33 +++++----------------------------
 util/sysfs.c         |   27 +++++++++++++++++++++++++++
 util/sysfs.h         |    8 ++++++++
 3 files changed, 40 insertions(+), 28 deletions(-)

diff --git a/ndctl/lib/libndctl.c b/ndctl/lib/libndctl.c
index 47a234ccc8ce..1374ad9e504f 100644
--- a/ndctl/lib/libndctl.c
+++ b/ndctl/lib/libndctl.c
@@ -1668,7 +1668,6 @@ static enum ndctl_fwa_result fwa_result_to_result(const char *result)
 static int ndctl_bind(struct ndctl_ctx *ctx, struct kmod_module *module,
 		const char *devname);
 static int ndctl_unbind(struct ndctl_ctx *ctx, const char *devpath);
-static struct kmod_module *to_module(struct ndctl_ctx *ctx, const char *alias);
 
 static int populate_dimm_attributes(struct ndctl_dimm *dimm,
 				    const char *dimm_base,
@@ -1878,7 +1877,7 @@ static void *add_dimm(void *parent, int id, const char *dimm_base)
 	sprintf(path, "%s/modalias", dimm_base);
 	if (sysfs_read_attr(ctx, path, buf) < 0)
 		goto err_read;
-	dimm->module = to_module(ctx, buf);
+	dimm->module = util_modalias_to_module(ctx, buf);
 
 	dimm->handle = -1;
 	dimm->phys_id = -1;
@@ -2597,7 +2596,7 @@ static void *add_region(void *parent, int id, const char *region_base)
 	sprintf(path, "%s/modalias", region_base);
 	if (sysfs_read_attr(ctx, path, buf) < 0)
 		goto err_read;
-	region->module = to_module(ctx, buf);
+	region->module = util_modalias_to_module(ctx, buf);
 
 	sprintf(path, "%s/numa_node", region_base);
 	if ((rc = sysfs_read_attr(ctx, path, buf)) == 0)
@@ -3885,28 +3884,6 @@ NDCTL_EXPORT struct ndctl_ctx *ndctl_mapping_get_ctx(
 	return ndctl_mapping_get_bus(mapping)->ctx;
 }
 
-static struct kmod_module *to_module(struct ndctl_ctx *ctx, const char *alias)
-{
-	struct kmod_list *list = NULL;
-	struct kmod_module *mod;
-	int rc;
-
-	if (!ctx->kmod_ctx)
-		return NULL;
-
-	rc = kmod_module_new_from_lookup(ctx->kmod_ctx, alias, &list);
-	if (rc < 0 || !list) {
-		dbg(ctx, "failed to find module for alias: %s %d list: %s\n",
-				alias, rc, list ? "populated" : "empty");
-		return NULL;
-	}
-	mod = kmod_module_get_module(list);
-	dbg(ctx, "alias: %s module: %s\n", alias, kmod_module_get_name(mod));
-	kmod_module_unref_list(list);
-
-	return mod;
-}
-
 static char *get_block_device(struct ndctl_ctx *ctx, const char *block_path)
 {
 	char *bdev_name = NULL;
@@ -4069,7 +4046,7 @@ static void *add_namespace(void *parent, int id, const char *ndns_base)
 	sprintf(path, "%s/modalias", ndns_base);
 	if (sysfs_read_attr(ctx, path, buf) < 0)
 		goto err_read;
-	ndns->module = to_module(ctx, buf);
+	ndns->module = util_modalias_to_module(ctx, buf);
 
 	ndctl_namespace_foreach(region, ndns_dup)
 		if (ndns_dup->id == ndns->id) {
@@ -5182,7 +5159,7 @@ static void *add_btt(void *parent, int id, const char *btt_base)
 	sprintf(path, "%s/modalias", btt_base);
 	if (sysfs_read_attr(ctx, path, buf) < 0)
 		goto err_read;
-	btt->module = to_module(ctx, buf);
+	btt->module = util_modalias_to_module(ctx, buf);
 
 	sprintf(path, "%s/uuid", btt_base);
 	if (sysfs_read_attr(ctx, path, buf) < 0)
@@ -5533,7 +5510,7 @@ static void *__add_pfn(struct ndctl_pfn *pfn, const char *pfn_base)
 	sprintf(path, "%s/modalias", pfn_base);
 	if (sysfs_read_attr(ctx, path, buf) < 0)
 		goto err_read;
-	pfn->module = to_module(ctx, buf);
+	pfn->module = util_modalias_to_module(ctx, buf);
 
 	sprintf(path, "%s/uuid", pfn_base);
 	if (sysfs_read_attr(ctx, path, buf) < 0)
diff --git a/util/sysfs.c b/util/sysfs.c
index cfbab7da74e9..23330cb29002 100644
--- a/util/sysfs.c
+++ b/util/sysfs.c
@@ -10,6 +10,7 @@
 #include <ctype.h>
 #include <fcntl.h>
 #include <dirent.h>
+#include <libkmod.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/ioctl.h>
@@ -118,3 +119,29 @@ int __sysfs_device_parse(struct log_ctx *ctx, const char *base_path,
 
 	return add_errors;
 }
+
+struct kmod_module *__util_modalias_to_module(struct kmod_ctx *kmod_ctx,
+					      const char *alias,
+					      struct log_ctx *log)
+{
+	struct kmod_list *list = NULL;
+	struct kmod_module *mod;
+	int rc;
+
+	if (!kmod_ctx)
+		return NULL;
+
+	rc = kmod_module_new_from_lookup(kmod_ctx, alias, &list);
+	if (rc < 0 || !list) {
+		log_dbg(log,
+			"failed to find module for alias: %s %d list: %s\n",
+			alias, rc, list ? "populated" : "empty");
+		return NULL;
+	}
+	mod = kmod_module_get_module(list);
+	log_dbg(log, "alias: %s module: %s\n", alias,
+		kmod_module_get_name(mod));
+	kmod_module_unref_list(list);
+
+	return mod;
+}
diff --git a/util/sysfs.h b/util/sysfs.h
index 6485a73d8ed7..bdee4f5c291d 100644
--- a/util/sysfs.h
+++ b/util/sysfs.h
@@ -27,4 +27,12 @@ static inline const char *devpath_to_devname(const char *devpath)
 {
 	return strrchr(devpath, '/') + 1;
 }
+
+struct kmod_ctx;
+struct kmod_module;
+struct kmod_module *__util_modalias_to_module(struct kmod_ctx *kmod_ctx,
+					      const char *alias,
+					      struct log_ctx *log);
+#define util_modalias_to_module(ctx, buf)                                      \
+	__util_modalias_to_module((ctx)->kmod_ctx, buf, &(ctx)->ctx)
 #endif /* __UTIL_SYSFS_H__ */


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 17/37] cxl/list: Add port enumeration
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (15 preceding siblings ...)
  2022-01-24  0:53 ` [ndctl PATCH 16/37] util/sysfs: Uplevel modalias lookup helper to util/ Dan Williams
@ 2022-01-24  0:53 ` Dan Williams
  2022-01-24  0:53 ` [ndctl PATCH 18/37] cxl/list: Add --debug option Dan Williams
                   ` (19 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:53 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Between a cxl_bus (root port) and an endpoint there can be an arbitrary
level of switches. Add enumeration for these ports at each level of the
hierarchy.

However, given the CXL root ports are also "ports" infer that if the port
filter argument is the word "root" or "root%d" then include root ports in
the listing. The keyword "switch" is also provided to filter only the ports
beneath the root that are not endpoint ports.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 .clang-format                    |    1 
 Documentation/cxl/cxl-list.txt   |   24 ++++
 Documentation/cxl/lib/libcxl.txt |   42 +++++++
 cxl/filter.c                     |  224 +++++++++++++++++++++++++++++++++++++-
 cxl/filter.h                     |    4 +
 cxl/json.c                       |   23 ++++
 cxl/json.h                       |    3 +
 cxl/lib/libcxl.c                 |  160 +++++++++++++++++++++++++++
 cxl/lib/libcxl.sym               |   12 ++
 cxl/lib/private.h                |   11 ++
 cxl/libcxl.h                     |   19 +++
 cxl/list.c                       |   17 +++
 12 files changed, 534 insertions(+), 6 deletions(-)

diff --git a/.clang-format b/.clang-format
index 1154c76b1399..391cd3420947 100644
--- a/.clang-format
+++ b/.clang-format
@@ -79,6 +79,7 @@ ExperimentalAutoDetectBinPacking: false
 ForEachMacros:
   - 'cxl_memdev_foreach'
   - 'cxl_bus_foreach'
+  - 'cxl_port_foreach'
   - 'daxctl_dev_foreach'
   - 'daxctl_mapping_foreach'
   - 'daxctl_region_foreach'
diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index be131ae7b02a..3076deb43b9e 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -176,6 +176,30 @@ OPTIONS
 	names to filter the listing. The supported provider names are "ACPI.CXL"
 	and "cxl_test".
 
+-P::
+--ports::
+	Include port objects (CXL / PCIe root ports + Upstream Switch Ports) in
+	the listing.
+
+-p::
+--port=::
+	Specify CXL Port device name(s), device id(s), and or port type
+	names to filter the listing. The supported port type names are "root"
+	and "switch". Note that since a bus object is also a port, the following
+	two syntaxes are equivalent:
+----
+# cxl list -B
+# cxl list -P -p root
+----
+	By default, only 'switch' ports are listed.
+
+-S::
+--single::
+	Specify whether the listing should emit all the objects that are
+	descendants of a port that matches the port filter, or only direct
+	descendants of the individual ports that match the filter. By default
+	all descendant objects are listed.
+
 include::human-option.txt[]
 
 include::verbose-option.txt[]
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index 84af66a3a7bd..804e9ca1500e 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -164,6 +164,48 @@ discovery order. The possible provider names are 'ACPI.CXL' and
 the kernel device names that are subject to change based on discovery
 order.
 
+PORTS
+-----
+CXL ports track the PCIe hierarchy between a platform firmware CXL root
+object, through CXL / PCIe Host Bridges, CXL / PCIe Root Ports, and CXL
+/ PCIe Switch Ports.
+
+=== PORT: Enumeration
+----
+struct cxl_port *cxl_bus_get_port(struct cxl_bus *bus);
+struct cxl_port *cxl_port_get_first(struct cxl_port *parent);
+struct cxl_port *cxl_port_get_next(struct cxl_port *port);
+struct cxl_port *cxl_port_get_parent(struct cxl_port *port);
+struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
+struct cxl_ctx *cxl_port_get_ctx(struct cxl_port *port);
+
+#define cxl_port_foreach(parent, port)                                      \
+       for (port = cxl_port_get_first(parent); port != NULL;                \
+            port = cxl_port_get_next(port))
+----
+A bus object encapsulates a CXL port object. Use cxl_bus_get_port() to
+use generic port APIs on root objects.
+
+Ports are hierarchical. All but the a root object have another CXL port
+as a parent object retrievable via cxl_port_get_parent().
+
+The root port of a hiearchy can be retrieved via any port instance in
+that hierarchy via cxl_port_get_bus().
+
+=== PORT: Attributes
+----
+const char *cxl_port_get_devname(struct cxl_port *port);
+int cxl_port_get_id(struct cxl_port *port);
+int cxl_port_is_enabled(struct cxl_port *port);
+bool cxl_port_is_root(struct cxl_port *port);
+bool cxl_port_is_switch(struct cxl_port *port);
+----
+The port type is communicated via cxl_port_is_<type>(). An 'enabled' port
+is one that has succeeded in discovering the CXL component registers in
+the host device and has enumerated its downstream ports. In order for a
+memdev to be enabled for CXL memory operation all CXL ports in its
+ancestry must also be enabled.
+
 include::../../copyright.txt[]
 
 SEE ALSO
diff --git a/cxl/filter.c b/cxl/filter.c
index 5f4844b4cab9..8b79db3dae8d 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -21,6 +21,101 @@ static const char *which_sep(const char *filter)
 	return " ";
 }
 
+bool cxl_filter_has(const char *__filter, const char *needle)
+{
+	char *filter, *save;
+	const char *arg;
+
+	if (!needle)
+		return true;
+
+	if (!__filter)
+		return false;
+
+	filter = strdup(__filter);
+	if (!filter)
+		return false;
+
+	for (arg = strtok_r(filter, which_sep(__filter), &save); arg;
+	     arg = strtok_r(NULL, which_sep(__filter), &save))
+		if (strstr(arg, needle))
+			break;
+
+	free(filter);
+	if (arg)
+		return true;
+	return false;
+}
+
+static struct cxl_port *__util_cxl_port_filter(struct cxl_port *port,
+					     const char *__ident)
+{
+	char *ident, *save;
+	const char *arg;
+	int port_id;
+
+	if (!__ident)
+		return port;
+
+	ident = strdup(__ident);
+	if (!ident)
+		return NULL;
+
+	for (arg = strtok_r(ident, which_sep(__ident), &save); arg;
+	     arg = strtok_r(NULL, which_sep(__ident), &save)) {
+		if (strcmp(arg, "all") == 0)
+			break;
+
+		if (strcmp(arg, "root") == 0 && cxl_port_is_root(port))
+			break;
+
+		if (strcmp(arg, "switch") == 0 && cxl_port_is_switch(port))
+			break;
+
+		if ((sscanf(arg, "%d", &port_id) == 1 ||
+		     sscanf(arg, "port%d", &port_id) == 1) &&
+		    cxl_port_get_id(port) == port_id)
+			break;
+
+		if (strcmp(arg, cxl_port_get_devname(port)) == 0)
+			break;
+	}
+
+	free(ident);
+	if (arg)
+		return port;
+	return NULL;
+}
+
+enum cxl_port_filter_mode {
+	CXL_PF_SINGLE,
+	CXL_PF_ANCESTRY,
+};
+
+static enum cxl_port_filter_mode pf_mode(struct cxl_filter_params *p)
+{
+	if (p->single)
+		return CXL_PF_SINGLE;
+	return CXL_PF_ANCESTRY;
+}
+
+static struct cxl_port *util_cxl_port_filter(struct cxl_port *port,
+					     const char *ident,
+					     enum cxl_port_filter_mode mode)
+{
+	struct cxl_port *iter = port;
+
+	while (iter) {
+		if (__util_cxl_port_filter(iter, ident))
+			return port;
+		if (mode == CXL_PF_SINGLE)
+			return NULL;
+		iter = cxl_port_get_parent(iter);
+	}
+
+	return NULL;
+}
+
 static struct cxl_bus *util_cxl_bus_filter(struct cxl_bus *bus,
 					   const char *__ident)
 {
@@ -58,6 +153,31 @@ static struct cxl_bus *util_cxl_bus_filter(struct cxl_bus *bus,
 	return NULL;
 }
 
+static struct cxl_port *util_cxl_port_filter_by_bus(struct cxl_port *port,
+						    const char *__ident)
+{
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+	struct cxl_bus *bus;
+
+	if (!__ident)
+		return port;
+
+	if (cxl_port_is_root(port)) {
+		bus = cxl_port_to_bus(port);
+		bus = util_cxl_bus_filter(bus, __ident);
+		return bus ? port : NULL;
+	}
+
+	cxl_bus_foreach(ctx, bus) {
+		if (!util_cxl_bus_filter(bus, __ident))
+			continue;
+		if (bus == cxl_port_get_bus(port))
+			return port;
+	}
+
+	return NULL;
+}
+
 static struct cxl_memdev *
 util_cxl_memdev_serial_filter(struct cxl_memdev *memdev, const char *__serials)
 {
@@ -169,10 +289,82 @@ static void splice_array(struct cxl_filter_params *p, struct json_object *jobjs,
 	json_object_put(jobjs);
 }
 
+static bool cond_add_put_array(struct json_object *jobj, const char *key,
+			       struct json_object *array)
+{
+	if (jobj && array && json_object_array_length(array) > 0) {
+		json_object_object_add(jobj, key, array);
+		return true;
+	} else {
+		json_object_put(array);
+		return false;
+	}
+}
+
+static bool cond_add_put_array_suffix(struct json_object *jobj, const char *key,
+				      const char *suffix,
+				      struct json_object *array)
+{
+	char *name;
+	bool rc;
+
+	if (asprintf(&name, "%s:%s", key, suffix) < 0)
+		return false;
+	rc = cond_add_put_array(jobj, name, array);
+	free(name);
+	return rc;
+}
+
+static struct json_object *pick_array(struct json_object *child,
+				      struct json_object *container)
+{
+	if (child)
+		return child;
+	if (container)
+		return container;
+	return NULL;
+}
+
+static void walk_child_ports(struct cxl_port *parent_port,
+			     struct cxl_filter_params *p,
+			     struct json_object *jports,
+			     unsigned long flags)
+{
+	struct cxl_port *port;
+
+	cxl_port_foreach(parent_port, port) {
+		const char *devname = cxl_port_get_devname(port);
+		struct json_object *jport = NULL;
+		struct json_object *jchildports = NULL;
+
+		if (!util_cxl_port_filter(port, p->port_filter, pf_mode(p)))
+			goto walk_children;
+		if (!util_cxl_port_filter_by_bus(port, p->bus_filter))
+			goto walk_children;
+		if (!p->idle && !cxl_port_is_enabled(port))
+			continue;
+		if (p->ports)
+			jport = util_cxl_port_to_json(port, flags);
+		if (!jport)
+			continue;
+		json_object_array_add(jports, jport);
+		jchildports = json_object_new_array();
+		if (!jchildports) {
+			err(p, "%s: failed to enumerate child ports\n",
+			    devname);
+			continue;
+		}
+walk_children:
+		walk_child_ports(port, p, pick_array(jchildports, jports),
+				 flags);
+		cond_add_put_array_suffix(jport, "ports", devname, jchildports);
+	}
+}
+
 int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 {
+	struct json_object *jdevs = NULL, *jbuses = NULL, *jports = NULL;
 	struct json_object *jplatform = json_object_new_array();
-	struct json_object *jdevs = NULL, *jbuses = NULL;
 	unsigned long flags = params_to_flags(p);
 	struct cxl_memdev *memdev;
 	int top_level_objs = 0;
@@ -191,6 +383,10 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	if (!jbuses)
 		goto err;
 
+	jports = json_object_new_array();
+	if (!jports)
+		goto err;
+
 	cxl_memdev_foreach(ctx, memdev) {
 		struct json_object *jdev;
 
@@ -208,10 +404,15 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	}
 
 	cxl_bus_foreach(ctx, bus) {
-		struct json_object *jbus;
+		struct json_object *jbus = NULL;
+		struct json_object *jchildports = NULL;
+		struct cxl_port *port = cxl_bus_get_port(bus);
+		const char *devname = cxl_bus_get_devname(bus);
 
 		if (!util_cxl_bus_filter(bus, p->bus_filter))
-			continue;
+			goto walk_children;
+		if (!util_cxl_port_filter(port, p->port_filter, pf_mode(p)))
+			goto walk_children;
 		if (p->buses) {
 			jbus = util_cxl_bus_to_json(bus, flags);
 			if (!jbus) {
@@ -219,16 +420,32 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 				continue;
 			}
 			json_object_array_add(jbuses, jbus);
+			if (p->ports) {
+				jchildports = json_object_new_array();
+				if (!jchildports) {
+					err(p,
+					    "%s: failed to enumerate child ports\n",
+					    devname);
+					continue;
+				}
+			}
 		}
+walk_children:
+		walk_child_ports(port, p, pick_array(jchildports, jports),
+				 flags);
+		cond_add_put_array_suffix(jbus, "ports", devname, jchildports);
 	}
 
 	if (json_object_array_length(jdevs))
 		top_level_objs++;
 	if (json_object_array_length(jbuses))
 		top_level_objs++;
+	if (json_object_array_length(jports))
+		top_level_objs++;
 
 	splice_array(p, jdevs, jplatform, "anon memdevs", top_level_objs > 1);
 	splice_array(p, jbuses, jplatform, "buses", top_level_objs > 1);
+	splice_array(p, jports, jplatform, "ports", top_level_objs > 1);
 
 	util_display_json_array(stdout, jplatform, flags);
 
@@ -236,6 +453,7 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 err:
 	json_object_put(jdevs);
 	json_object_put(jbuses);
+	json_object_put(jports);
 	json_object_put(jplatform);
 	return -ENOMEM;
 }
diff --git a/cxl/filter.h b/cxl/filter.h
index d41e757cf51d..0d83304bd612 100644
--- a/cxl/filter.h
+++ b/cxl/filter.h
@@ -10,7 +10,10 @@ struct cxl_filter_params {
 	const char *memdev_filter;
 	const char *serial_filter;
 	const char *bus_filter;
+	const char *port_filter;
+	bool single;
 	bool memdevs;
+	bool ports;
 	bool buses;
 	bool idle;
 	bool human;
@@ -22,4 +25,5 @@ struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev,
 					  const char *__ident,
 					  const char *serials);
 int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *param);
+bool cxl_filter_has(const char *needle, const char *__filter);
 #endif /* _CXL_UTIL_FILTER_H_ */
diff --git a/cxl/json.c b/cxl/json.c
index a58459482360..d9f864edb86b 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -242,3 +242,26 @@ struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
 
 	return jbus;
 }
+
+struct json_object *util_cxl_port_to_json(struct cxl_port *port,
+					  unsigned long flags)
+{
+	const char *devname = cxl_port_get_devname(port);
+	struct json_object *jport, *jobj;
+
+	jport = json_object_new_object();
+	if (!jport)
+		return NULL;
+
+	jobj = json_object_new_string(devname);
+	if (jobj)
+		json_object_object_add(jport, "port", jobj);
+
+	if (!cxl_port_is_enabled(port)) {
+		jobj = json_object_new_string("disabled");
+		if (jobj)
+			json_object_object_add(jport, "state", jobj);
+	}
+
+	return jport;
+}
diff --git a/cxl/json.h b/cxl/json.h
index 4abf6e500f0b..36653db6ef6a 100644
--- a/cxl/json.h
+++ b/cxl/json.h
@@ -8,4 +8,7 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
 struct cxl_bus;
 struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
 					 unsigned long flags);
+struct cxl_port;
+struct json_object *util_cxl_port_to_json(struct cxl_port *port,
+					  unsigned long flags);
 #endif /* __CXL_UTIL_JSON_H__ */
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 8548a458b131..03eff3cd6073 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -66,15 +66,27 @@ static void free_memdev(struct cxl_memdev *memdev, struct list_head *head)
 	free(memdev);
 }
 
+static void free_port(struct cxl_port *port, struct list_head *head);
 static void __free_port(struct cxl_port *port, struct list_head *head)
 {
+	struct cxl_port *child, *_c;
+
 	if (head)
 		list_del_from(head, &port->list);
+	list_for_each_safe(&port->child_ports, child, _c, list)
+		free_port(child, &port->child_ports);
+	kmod_module_unref(port->module);
 	free(port->dev_buf);
 	free(port->dev_path);
 	free(port->uport);
 }
 
+static void free_port(struct cxl_port *port, struct list_head *head)
+{
+	__free_port(port, head);
+	free(port);
+}
+
 static void free_bus(struct cxl_bus *bus, struct list_head *head)
 {
 	__free_port(&bus->port, head);
@@ -471,10 +483,12 @@ CXL_EXPORT int cxl_memdev_nvdimm_bridge_active(struct cxl_memdev *memdev)
 	return is_enabled(path);
 }
 
-static int cxl_port_init(struct cxl_port *port, struct cxl_ctx *ctx, int id,
+static int cxl_port_init(struct cxl_port *port, struct cxl_port *parent_port,
+			 enum cxl_port_type type, struct cxl_ctx *ctx, int id,
 			 const char *cxlport_base)
 {
 	char *path = calloc(1, strlen(cxlport_base) + 100);
+	char buf[SYSFS_ATTR_SIZE];
 	size_t rc;
 
 	if (!path)
@@ -482,6 +496,10 @@ static int cxl_port_init(struct cxl_port *port, struct cxl_ctx *ctx, int id,
 
 	port->id = id;
 	port->ctx = ctx;
+	port->type = type;
+	port->parent = parent_port;
+
+	list_head_init(&port->child_ports);
 
 	port->dev_path = strdup(cxlport_base);
 	if (!port->dev_path)
@@ -499,6 +517,10 @@ static int cxl_port_init(struct cxl_port *port, struct cxl_ctx *ctx, int id,
 	if (!port->uport)
 		goto err;
 
+	sprintf(path, "%s/modalias", cxlport_base);
+	if (sysfs_read_attr(ctx, path, buf) == 0)
+		port->module = util_modalias_to_module(ctx, buf);
+
 	return 0;
 err:
 	free(port->dev_path);
@@ -507,6 +529,135 @@ err:
 	return -ENOMEM;
 }
 
+static void *add_cxl_port(void *parent, int id, const char *cxlport_base)
+{
+	const char *devname = devpath_to_devname(cxlport_base);
+	struct cxl_port *port, *port_dup;
+	struct cxl_port *parent_port = parent;
+	struct cxl_ctx *ctx = cxl_port_get_ctx(parent_port);
+	int rc;
+
+	dbg(ctx, "%s: base: \'%s\'\n", devname, cxlport_base);
+
+	port = calloc(1, sizeof(*port));
+	if (!port)
+		return NULL;
+
+	rc = cxl_port_init(port, parent_port, CXL_PORT_SWITCH, ctx, id,
+			   cxlport_base);
+	if (rc)
+		goto err;
+
+	cxl_port_foreach(parent_port, port_dup)
+		if (port_dup->id == port->id) {
+			free_port(port, NULL);
+			return port_dup;
+		}
+
+	list_add(&parent_port->child_ports, &port->list);
+	return port;
+
+err:
+	free(port);
+	return NULL;
+
+}
+
+static void cxl_ports_init(struct cxl_port *port)
+{
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+
+	if (port->ports_init)
+		return;
+
+	port->ports_init = 1;
+
+	sysfs_device_parse(ctx, port->dev_path, "port", port, add_cxl_port);
+}
+
+CXL_EXPORT struct cxl_ctx *cxl_port_get_ctx(struct cxl_port *port)
+{
+	return port->ctx;
+}
+
+CXL_EXPORT struct cxl_port *cxl_port_get_first(struct cxl_port *port)
+{
+	cxl_ports_init(port);
+
+	return list_top(&port->child_ports, struct cxl_port, list);
+}
+
+CXL_EXPORT struct cxl_port *cxl_port_get_next(struct cxl_port *port)
+{
+	struct cxl_port *parent_port = port->parent;
+
+	return list_next(&parent_port->child_ports, port, list);
+}
+
+CXL_EXPORT const char *cxl_port_get_devname(struct cxl_port *port)
+{
+	return devpath_to_devname(port->dev_path);
+}
+
+CXL_EXPORT int cxl_port_get_id(struct cxl_port *port)
+{
+	return port->id;
+}
+
+CXL_EXPORT struct cxl_port *cxl_port_get_parent(struct cxl_port *port)
+{
+	return port->parent;
+}
+
+CXL_EXPORT bool cxl_port_is_root(struct cxl_port *port)
+{
+	return port->type == CXL_PORT_ROOT;
+}
+
+CXL_EXPORT bool cxl_port_is_switch(struct cxl_port *port)
+{
+	return port->type == CXL_PORT_SWITCH;
+}
+
+CXL_EXPORT struct cxl_bus *cxl_port_get_bus(struct cxl_port *port)
+{
+	struct cxl_bus *bus;
+
+	if (!cxl_port_is_enabled(port))
+		return NULL;
+
+	if (port->bus)
+		return port->bus;
+
+	while (port->parent)
+		port = port->parent;
+
+	bus = container_of(port, typeof(*bus), port);
+	port->bus = bus;
+	return bus;
+}
+
+CXL_EXPORT int cxl_port_is_enabled(struct cxl_port *port)
+{
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+	char *path = port->dev_buf;
+	int len = port->buf_len;
+
+	if (snprintf(path, len, "%s/driver", port->dev_path) >= len) {
+		err(ctx, "%s: buffer too small!\n", cxl_port_get_devname(port));
+		return 0;
+	}
+
+	return is_enabled(path);
+}
+
+CXL_EXPORT struct cxl_bus *cxl_port_to_bus(struct cxl_port *port)
+{
+	if (!cxl_port_is_root(port))
+		return NULL;
+	return container_of(port, struct cxl_bus, port);
+}
+
 static void *add_cxl_bus(void *parent, int id, const char *cxlbus_base)
 {
 	const char *devname = devpath_to_devname(cxlbus_base);
@@ -522,7 +673,7 @@ static void *add_cxl_bus(void *parent, int id, const char *cxlbus_base)
 		return NULL;
 
 	port = &bus->port;
-	rc = cxl_port_init(port, ctx, id, cxlbus_base);
+	rc = cxl_port_init(port, NULL, CXL_PORT_ROOT, ctx, id, cxlbus_base);
 	if (rc)
 		goto err;
 
@@ -579,6 +730,11 @@ CXL_EXPORT int cxl_bus_get_id(struct cxl_bus *bus)
 	return port->id;
 }
 
+CXL_EXPORT struct cxl_port *cxl_bus_get_port(struct cxl_bus *bus)
+{
+	return &bus->port;
+}
+
 CXL_EXPORT const char *cxl_bus_get_provider(struct cxl_bus *bus)
 {
 	struct cxl_port *port = &bus->port;
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index 781ff996af73..a7e923f7d721 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -82,4 +82,16 @@ global:
 	cxl_bus_get_provider;
 	cxl_bus_get_devname;
 	cxl_bus_get_id;
+	cxl_bus_get_port;
+	cxl_port_get_first;
+	cxl_port_get_next;
+	cxl_port_get_devname;
+	cxl_port_get_id;
+	cxl_port_get_ctx;
+	cxl_port_is_enabled;
+	cxl_port_get_parent;
+	cxl_port_is_root;
+	cxl_port_is_switch;
+	cxl_port_to_bus;
+	cxl_port_get_bus;
 } LIBCXL_1;
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
index 0758d0578acf..637f90d8dfa2 100644
--- a/cxl/lib/private.h
+++ b/cxl/lib/private.h
@@ -34,14 +34,25 @@ struct cxl_memdev {
 	unsigned long long serial;
 };
 
+enum cxl_port_type {
+	CXL_PORT_ROOT,
+	CXL_PORT_SWITCH,
+};
+
 struct cxl_port {
 	int id;
 	void *dev_buf;
 	size_t buf_len;
 	char *dev_path;
 	char *uport;
+	int ports_init;
 	struct cxl_ctx *ctx;
+	struct cxl_bus *bus;
+	enum cxl_port_type type;
+	struct cxl_port *parent;
+	struct kmod_module *module;
 	struct list_node list;
+	struct list_head child_ports;
 };
 
 struct cxl_bus {
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index da66eb298ab8..efbb397eb2be 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -5,6 +5,7 @@
 
 #include <stdarg.h>
 #include <unistd.h>
+#include <stdbool.h>
 
 #ifdef HAVE_UUID
 #include <uuid/uuid.h>
@@ -63,11 +64,29 @@ struct cxl_bus *cxl_bus_get_next(struct cxl_bus *bus);
 const char *cxl_bus_get_provider(struct cxl_bus *bus);
 const char *cxl_bus_get_devname(struct cxl_bus *bus);
 int cxl_bus_get_id(struct cxl_bus *bus);
+struct cxl_port *cxl_bus_get_port(struct cxl_bus *bus);
 
 #define cxl_bus_foreach(ctx, bus)                                              \
 	for (bus = cxl_bus_get_first(ctx); bus != NULL;                        \
 	     bus = cxl_bus_get_next(bus))
 
+struct cxl_port;
+struct cxl_port *cxl_port_get_first(struct cxl_port *parent);
+struct cxl_port *cxl_port_get_next(struct cxl_port *port);
+const char *cxl_port_get_devname(struct cxl_port *port);
+int cxl_port_get_id(struct cxl_port *port);
+struct cxl_ctx *cxl_port_get_ctx(struct cxl_port *port);
+int cxl_port_is_enabled(struct cxl_port *port);
+struct cxl_port *cxl_port_get_parent(struct cxl_port *port);
+bool cxl_port_is_root(struct cxl_port *port);
+bool cxl_port_is_switch(struct cxl_port *port);
+struct cxl_bus *cxl_port_to_bus(struct cxl_port *port);
+struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
+
+#define cxl_port_foreach(parent, port)                                         \
+	for (port = cxl_port_get_first(parent); port != NULL;                  \
+	     port = cxl_port_get_next(port))
+
 struct cxl_cmd;
 const char *cxl_cmd_get_devname(struct cxl_cmd *cmd);
 struct cxl_cmd *cxl_cmd_new_raw(struct cxl_memdev *memdev, int opcode);
diff --git a/cxl/list.c b/cxl/list.c
index 9500e610e0ca..1ef91b44dca1 100644
--- a/cxl/list.c
+++ b/cxl/list.c
@@ -25,6 +25,11 @@ static const struct option options[] = {
 	OPT_STRING('b', "bus", &param.bus_filter, "bus device name",
 		   "filter by CXL bus device name(s)"),
 	OPT_BOOLEAN('B', "buses", &param.buses, "include CXL bus info"),
+	OPT_STRING('p', "port", &param.port_filter, "port device name",
+		   "filter by CXL port device name(s)"),
+	OPT_BOOLEAN('P', "ports", &param.ports, "include CXL port info"),
+	OPT_BOOLEAN('S', "single", &param.single,
+		    "skip listing descendant objects"),
 	OPT_BOOLEAN('i', "idle", &param.idle, "include disabled devices"),
 	OPT_BOOLEAN('u', "human", &param.human,
 		    "use human friendly number formats "),
@@ -35,7 +40,7 @@ static const struct option options[] = {
 
 static int num_list_flags(void)
 {
-       return !!param.memdevs + !!param.buses;
+       return !!param.memdevs + !!param.buses + !!param.ports;
 }
 
 int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
@@ -53,11 +58,18 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 	if (argc)
 		usage_with_options(u, options);
 
+	if (param.single && !param.port_filter) {
+		error("-S/--single expects a port filter: -p/--port=\n");
+		usage_with_options(u, options);
+	}
+
 	if (num_list_flags() == 0) {
 		if (param.memdev_filter || param.serial_filter)
 			param.memdevs = true;
 		if (param.bus_filter)
 			param.buses = true;
+		if (param.port_filter)
+			param.ports = true;
 		if (num_list_flags() == 0) {
 			/*
 			 * TODO: We likely want to list regions by default if
@@ -73,5 +85,8 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 
 	log_init(&param.ctx, "cxl list", "CXL_LIST_LOG");
 
+	if (cxl_filter_has(param.port_filter, "root") && param.ports)
+		param.buses = true;
+
 	return cxl_filter_walk(ctx, &param);
 }


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 18/37] cxl/list: Add --debug option
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (16 preceding siblings ...)
  2022-01-24  0:53 ` [ndctl PATCH 17/37] cxl/list: Add port enumeration Dan Williams
@ 2022-01-24  0:53 ` Dan Williams
  2022-01-24  0:53 ` [ndctl PATCH 19/37] cxl/list: Add endpoints Dan Williams
                   ` (18 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:53 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Add an option to turn on libray and cxl_filter_walk() messages. Gate it
based on the global ENABLE_DEBUG configuration setting.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/cxl-list.txt |    6 ++++--
 cxl/filter.c                   |    3 +++
 cxl/list.c                     |    9 +++++++++
 3 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index 3076deb43b9e..42b6de6e5c61 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -200,9 +200,11 @@ OPTIONS
 	descendants of the individual ports that match the filter. By default
 	all descendant objects are listed.
 
-include::human-option.txt[]
+--debug::
+	If the cxl tool was built with debug enabled, turn on debug
+	messages.
 
-include::verbose-option.txt[]
+include::human-option.txt[]
 
 include::../copyright.txt[]
 
diff --git a/cxl/filter.c b/cxl/filter.c
index 8b79db3dae8d..32171a47e8d5 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -387,6 +387,7 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	if (!jports)
 		goto err;
 
+	dbg(p, "walk memdevs\n");
 	cxl_memdev_foreach(ctx, memdev) {
 		struct json_object *jdev;
 
@@ -403,6 +404,7 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 		}
 	}
 
+	dbg(p, "walk buses\n");
 	cxl_bus_foreach(ctx, bus) {
 		struct json_object *jbus = NULL;
 		struct json_object *jchildports = NULL;
@@ -431,6 +433,7 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 			}
 		}
 walk_children:
+		dbg(p, "walk ports\n");
 		walk_child_ports(port, p, pick_array(jchildports, jports),
 				 flags);
 		cond_add_put_array_suffix(jbus, "ports", devname, jchildports);
diff --git a/cxl/list.c b/cxl/list.c
index 1ef91b44dca1..01ab19bff706 100644
--- a/cxl/list.c
+++ b/cxl/list.c
@@ -13,6 +13,7 @@
 #include "filter.h"
 
 static struct cxl_filter_params param;
+static bool debug;
 
 static const struct option options[] = {
 	OPT_STRING('m', "memdev", &param.memdev_filter, "memory device name(s)",
@@ -35,6 +36,9 @@ static const struct option options[] = {
 		    "use human friendly number formats "),
 	OPT_BOOLEAN('H', "health", &param.health,
 		    "include memory device health information "),
+#ifdef ENABLE_DEBUG
+	OPT_BOOLEAN(0, "debug", &debug, "debug list walk"),
+#endif
 	OPT_END(),
 };
 
@@ -84,9 +88,14 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 	}
 
 	log_init(&param.ctx, "cxl list", "CXL_LIST_LOG");
+	if (debug) {
+		cxl_set_log_priority(ctx, LOG_DEBUG);
+		param.ctx.log_priority = LOG_DEBUG;
+	}
 
 	if (cxl_filter_has(param.port_filter, "root") && param.ports)
 		param.buses = true;
 
+	dbg(&param, "walk topology\n");
 	return cxl_filter_walk(ctx, &param);
 }


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 19/37] cxl/list: Add endpoints
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (17 preceding siblings ...)
  2022-01-24  0:53 ` [ndctl PATCH 18/37] cxl/list: Add --debug option Dan Williams
@ 2022-01-24  0:53 ` Dan Williams
  2022-01-24  0:53 ` [ndctl PATCH 20/37] cxl/list: Add 'host' entries for port-like objects Dan Williams
                   ` (17 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:53 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Endpoints are port-like objects that represent the HDM decoders at terminal
end of a decode chain. Unlike port decoders that route to downstream ports,
endpoint decoders route to endpoint DPA (Device Physical Address) ranges.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 .clang-format                    |    1 
 Documentation/cxl/cxl-list.txt   |   16 ++++
 Documentation/cxl/lib/libcxl.txt |   31 ++++++++
 cxl/filter.c                     |  147 +++++++++++++++++++++++++++++++++++---
 cxl/filter.h                     |    2 +
 cxl/json.c                       |   20 ++++-
 cxl/json.h                       |    2 +
 cxl/lib/libcxl.c                 |  107 ++++++++++++++++++++++++++++
 cxl/lib/libcxl.sym               |    9 ++
 cxl/lib/private.h                |   10 +++
 cxl/libcxl.h                     |   15 ++++
 cxl/list.c                       |   13 +++
 12 files changed, 355 insertions(+), 18 deletions(-)

diff --git a/.clang-format b/.clang-format
index 391cd3420947..106bc5e25434 100644
--- a/.clang-format
+++ b/.clang-format
@@ -80,6 +80,7 @@ ForEachMacros:
   - 'cxl_memdev_foreach'
   - 'cxl_bus_foreach'
   - 'cxl_port_foreach'
+  - 'cxl_endpoint_foreach'
   - 'daxctl_dev_foreach'
   - 'daxctl_mapping_foreach'
   - 'daxctl_region_foreach'
diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index 42b6de6e5c61..d342da27d3da 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -190,6 +190,12 @@ OPTIONS
 ----
 # cxl list -B
 # cxl list -P -p root
+----
+	Additionally, endpoint objects are also ports so the following commands
+	are also equivalent.
+----
+# cxl list -E
+# cxl list -P -p endpoint
 ----
 	By default, only 'switch' ports are listed.
 
@@ -200,6 +206,16 @@ OPTIONS
 	descendants of the individual ports that match the filter. By default
 	all descendant objects are listed.
 
+-E::
+--endpoints::
+	Include endpoint objects (CXL Memory Device decoders) in the
+	listing.
+
+-e::
+--endpoint::
+	Specify CXL endpoint device name(s), or device id(s) to filter
+	the emitted endpoint(s).
+
 --debug::
 	If the cxl tool was built with debug enabled, turn on debug
 	messages.
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index 804e9ca1500e..eebab37acb3d 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -199,12 +199,41 @@ int cxl_port_get_id(struct cxl_port *port);
 int cxl_port_is_enabled(struct cxl_port *port);
 bool cxl_port_is_root(struct cxl_port *port);
 bool cxl_port_is_switch(struct cxl_port *port);
+bool cxl_port_is_endpoint(struct cxl_port *port);
 ----
 The port type is communicated via cxl_port_is_<type>(). An 'enabled' port
 is one that has succeeded in discovering the CXL component registers in
 the host device and has enumerated its downstream ports. In order for a
 memdev to be enabled for CXL memory operation all CXL ports in its
-ancestry must also be enabled.
+ancestry must also be enabled including a root port, an arbitrary number
+of intervening switch ports, and a terminal endpoint port.
+
+ENDPOINTS
+---------
+CXL endpoint objects encapsulate the set of host-managed device-memory
+(HDM) decoders in a physical memory device. The endpoint is the last hop
+in a decoder chain that translate SPA to DPA (system-physical-address to
+device-local-physical-address).
+
+=== ENDPOINT: Enumeration
+----
+struct cxl_endpoint *cxl_endpoint_get_first(struct cxl_port *parent);
+struct cxl_endpoint *cxl_endpoint_get_next(struct cxl_endpoint *endpoint);
+struct cxl_ctx *cxl_endpoint_get_ctx(struct cxl_endpoint *endpoint);
+struct cxl_port *cxl_endpoint_get_parent(struct cxl_endpoint *endpoint);
+struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint);
+
+#define cxl_endpoint_foreach(port, endpoint)                                 \
+       for (endpoint = cxl_endpoint_get_first(port); endpoint != NULL;       \
+            endpoint = cxl_endpoint_get_next(endpoint))
+----
+
+=== ENDPOINT: Attributes
+----
+const char *cxl_endpoint_get_devname(struct cxl_endpoint *endpoint);
+int cxl_endpoint_get_id(struct cxl_endpoint *endpoint);
+int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint);
+----
 
 include::../../copyright.txt[]
 
diff --git a/cxl/filter.c b/cxl/filter.c
index 32171a47e8d5..5d80d1b3922a 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -47,8 +47,42 @@ bool cxl_filter_has(const char *__filter, const char *needle)
 	return false;
 }
 
+static struct cxl_endpoint *
+util_cxl_endpoint_filter(struct cxl_endpoint *endpoint, const char *__ident)
+{
+	char *ident, *save;
+	const char *arg;
+	int endpoint_id;
+
+	if (!__ident)
+		return endpoint;
+
+	ident = strdup(__ident);
+	if (!ident)
+		return NULL;
+
+	for (arg = strtok_r(ident, which_sep(__ident), &save); arg;
+	     arg = strtok_r(NULL, which_sep(__ident), &save)) {
+		if (strcmp(arg, "all") == 0)
+			break;
+
+		if ((sscanf(arg, "%d", &endpoint_id) == 1 ||
+		     sscanf(arg, "endpoint%d", &endpoint_id) == 1) &&
+		    cxl_endpoint_get_id(endpoint) == endpoint_id)
+			break;
+
+		if (strcmp(arg, cxl_endpoint_get_devname(endpoint)) == 0)
+			break;
+	}
+
+	free(ident);
+	if (arg)
+		return endpoint;
+	return NULL;
+}
+
 static struct cxl_port *__util_cxl_port_filter(struct cxl_port *port,
-					     const char *__ident)
+					       const char *__ident)
 {
 	char *ident, *save;
 	const char *arg;
@@ -72,6 +106,9 @@ static struct cxl_port *__util_cxl_port_filter(struct cxl_port *port,
 		if (strcmp(arg, "switch") == 0 && cxl_port_is_switch(port))
 			break;
 
+		if (strcmp(arg, "endpoint") == 0 && cxl_port_is_endpoint(port))
+			break;
+
 		if ((sscanf(arg, "%d", &port_id) == 1 ||
 		     sscanf(arg, "port%d", &port_id) == 1) &&
 		    cxl_port_get_id(port) == port_id)
@@ -116,6 +153,24 @@ static struct cxl_port *util_cxl_port_filter(struct cxl_port *port,
 	return NULL;
 }
 
+static struct cxl_endpoint *
+util_cxl_endpoint_filter_by_port(struct cxl_endpoint *endpoint,
+				 const char *ident,
+				 enum cxl_port_filter_mode mode)
+{
+	struct cxl_port *iter = cxl_endpoint_get_port(endpoint);
+
+	if (util_cxl_port_filter(iter, ident, CXL_PF_SINGLE))
+		return endpoint;
+	iter = cxl_port_get_parent(iter);
+	if (!iter)
+		return NULL;
+	if (util_cxl_port_filter(iter, ident, mode))
+		return endpoint;
+
+	return NULL;
+}
+
 static struct cxl_bus *util_cxl_bus_filter(struct cxl_bus *bus,
 					   const char *__ident)
 {
@@ -325,10 +380,34 @@ static struct json_object *pick_array(struct json_object *child,
 	return NULL;
 }
 
+static void walk_endpoints(struct cxl_port *port, struct cxl_filter_params *p,
+			   struct json_object *jeps, unsigned long flags)
+{
+	struct cxl_endpoint *endpoint;
+
+	cxl_endpoint_foreach(port, endpoint) {
+		struct cxl_port *ep_port = cxl_endpoint_get_port(endpoint);
+		struct json_object *jendpoint;
+
+		if (!util_cxl_endpoint_filter(endpoint, p->endpoint_filter))
+			continue;
+		if (!util_cxl_port_filter_by_bus(ep_port, p->bus_filter))
+			continue;
+		if (!util_cxl_endpoint_filter_by_port(endpoint, p->port_filter,
+						      pf_mode(p)))
+			continue;
+		if (!p->idle && !cxl_endpoint_is_enabled(endpoint))
+			continue;
+		jendpoint = util_cxl_endpoint_to_json(endpoint, flags);
+		if (jendpoint)
+			json_object_array_add(jeps, jendpoint);
+	}
+}
+
 static void walk_child_ports(struct cxl_port *parent_port,
 			     struct cxl_filter_params *p,
 			     struct json_object *jports,
-			     unsigned long flags)
+			     struct json_object *jeps, unsigned long flags)
 {
 	struct cxl_port *port;
 
@@ -336,6 +415,7 @@ static void walk_child_ports(struct cxl_port *parent_port,
 		const char *devname = cxl_port_get_devname(port);
 		struct json_object *jport = NULL;
 		struct json_object *jchildports = NULL;
+		struct json_object *jchildendpoints = NULL;
 
 		if (!util_cxl_port_filter(port, p->port_filter, pf_mode(p)))
 			goto walk_children;
@@ -343,21 +423,41 @@ static void walk_child_ports(struct cxl_port *parent_port,
 			goto walk_children;
 		if (!p->idle && !cxl_port_is_enabled(port))
 			continue;
-		if (p->ports)
+		if (p->ports) {
 			jport = util_cxl_port_to_json(port, flags);
-		if (!jport)
-			continue;
-		json_object_array_add(jports, jport);
-		jchildports = json_object_new_array();
-		if (!jchildports) {
-			err(p, "%s: failed to enumerate child ports\n",
-			    devname);
-			continue;
+			if (!jport) {
+				err(p, "%s: failed to list\n", devname);
+				continue;
+			}
+			json_object_array_add(jports, jport);
+			jchildports = json_object_new_array();
+			if (!jchildports) {
+				err(p, "%s: failed to enumerate child ports\n",
+				    devname);
+				continue;
+			}
+		}
+
+		if (p->ports && p->endpoints) {
+			jchildendpoints = json_object_new_array();
+			if (!jchildendpoints) {
+				err(p,
+				    "%s: failed to enumerate child endpoints\n",
+				    devname);
+				continue;
+			}
 		}
+
 walk_children:
+		if (p->endpoints)
+			walk_endpoints(port, p, pick_array(jchildendpoints, jeps),
+				       flags);
+
 		walk_child_ports(port, p, pick_array(jchildports, jports),
-				 flags);
+				 pick_array(jchildendpoints, jeps), flags);
 		cond_add_put_array_suffix(jport, "ports", devname, jchildports);
+		cond_add_put_array_suffix(jport, "endpoints", devname,
+					  jchildendpoints);
 	}
 }
 
@@ -366,6 +466,7 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	struct json_object *jdevs = NULL, *jbuses = NULL, *jports = NULL;
 	struct json_object *jplatform = json_object_new_array();
 	unsigned long flags = params_to_flags(p);
+	struct json_object *jeps = NULL;
 	struct cxl_memdev *memdev;
 	int top_level_objs = 0;
 	struct cxl_bus *bus;
@@ -387,6 +488,10 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	if (!jports)
 		goto err;
 
+	jeps = json_object_new_array();
+	if (!jeps)
+		goto err;
+
 	dbg(p, "walk memdevs\n");
 	cxl_memdev_foreach(ctx, memdev) {
 		struct json_object *jdev;
@@ -408,6 +513,7 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	cxl_bus_foreach(ctx, bus) {
 		struct json_object *jbus = NULL;
 		struct json_object *jchildports = NULL;
+		struct json_object *jchildeps = NULL;
 		struct cxl_port *port = cxl_bus_get_port(bus);
 		const char *devname = cxl_bus_get_devname(bus);
 
@@ -431,12 +537,23 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 					continue;
 				}
 			}
+			if (p->endpoints) {
+				jchildeps = json_object_new_array();
+				if (!jchildeps) {
+					err(p,
+					    "%s: failed to enumerate child endpoints\n",
+					    devname);
+					continue;
+				}
+			}
 		}
 walk_children:
 		dbg(p, "walk ports\n");
 		walk_child_ports(port, p, pick_array(jchildports, jports),
-				 flags);
+				 pick_array(jchildeps, jeps), flags);
 		cond_add_put_array_suffix(jbus, "ports", devname, jchildports);
+		cond_add_put_array_suffix(jbus, "endpoints", devname,
+					  jchildeps);
 	}
 
 	if (json_object_array_length(jdevs))
@@ -445,10 +562,13 @@ walk_children:
 		top_level_objs++;
 	if (json_object_array_length(jports))
 		top_level_objs++;
+	if (json_object_array_length(jeps))
+		top_level_objs++;
 
 	splice_array(p, jdevs, jplatform, "anon memdevs", top_level_objs > 1);
 	splice_array(p, jbuses, jplatform, "buses", top_level_objs > 1);
 	splice_array(p, jports, jplatform, "ports", top_level_objs > 1);
+	splice_array(p, jeps, jplatform, "endpoints", top_level_objs > 1);
 
 	util_display_json_array(stdout, jplatform, flags);
 
@@ -457,6 +577,7 @@ err:
 	json_object_put(jdevs);
 	json_object_put(jbuses);
 	json_object_put(jports);
+	json_object_put(jeps);
 	json_object_put(jplatform);
 	return -ENOMEM;
 }
diff --git a/cxl/filter.h b/cxl/filter.h
index 0d83304bd612..bbd341c71721 100644
--- a/cxl/filter.h
+++ b/cxl/filter.h
@@ -11,7 +11,9 @@ struct cxl_filter_params {
 	const char *serial_filter;
 	const char *bus_filter;
 	const char *port_filter;
+	const char *endpoint_filter;
 	bool single;
+	bool endpoints;
 	bool memdevs;
 	bool ports;
 	bool buses;
diff --git a/cxl/json.c b/cxl/json.c
index d9f864edb86b..08f6192fe121 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -243,8 +243,9 @@ struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
 	return jbus;
 }
 
-struct json_object *util_cxl_port_to_json(struct cxl_port *port,
-					  unsigned long flags)
+static struct json_object *__util_cxl_port_to_json(struct cxl_port *port,
+						   const char *name_key,
+						   unsigned long flags)
 {
 	const char *devname = cxl_port_get_devname(port);
 	struct json_object *jport, *jobj;
@@ -255,7 +256,7 @@ struct json_object *util_cxl_port_to_json(struct cxl_port *port,
 
 	jobj = json_object_new_string(devname);
 	if (jobj)
-		json_object_object_add(jport, "port", jobj);
+		json_object_object_add(jport, name_key, jobj);
 
 	if (!cxl_port_is_enabled(port)) {
 		jobj = json_object_new_string("disabled");
@@ -265,3 +266,16 @@ struct json_object *util_cxl_port_to_json(struct cxl_port *port,
 
 	return jport;
 }
+
+struct json_object *util_cxl_port_to_json(struct cxl_port *port,
+					  unsigned long flags)
+{
+	return __util_cxl_port_to_json(port, "port", flags);
+}
+
+struct json_object *util_cxl_endpoint_to_json(struct cxl_endpoint *endpoint,
+					      unsigned long flags)
+{
+	return __util_cxl_port_to_json(cxl_endpoint_get_port(endpoint),
+				       "endpoint", flags);
+}
diff --git a/cxl/json.h b/cxl/json.h
index 36653db6ef6a..8f45190f67e5 100644
--- a/cxl/json.h
+++ b/cxl/json.h
@@ -11,4 +11,6 @@ struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
 struct cxl_port;
 struct json_object *util_cxl_port_to_json(struct cxl_port *port,
 					  unsigned long flags);
+struct json_object *util_cxl_endpoint_to_json(struct cxl_endpoint *endpoint,
+					      unsigned long flags);
 #endif /* __CXL_UTIL_JSON_H__ */
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 03eff3cd6073..a25e7152af2a 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -67,14 +67,18 @@ static void free_memdev(struct cxl_memdev *memdev, struct list_head *head)
 }
 
 static void free_port(struct cxl_port *port, struct list_head *head);
+static void free_endpoint(struct cxl_endpoint *endpoint, struct list_head *head);
 static void __free_port(struct cxl_port *port, struct list_head *head)
 {
 	struct cxl_port *child, *_c;
+	struct cxl_endpoint *endpoint, *_e;
 
 	if (head)
 		list_del_from(head, &port->list);
 	list_for_each_safe(&port->child_ports, child, _c, list)
 		free_port(child, &port->child_ports);
+	list_for_each_safe(&port->endpoints, endpoint, _e, port.list)
+		free_endpoint(endpoint, &port->endpoints);
 	kmod_module_unref(port->module);
 	free(port->dev_buf);
 	free(port->dev_path);
@@ -87,6 +91,12 @@ static void free_port(struct cxl_port *port, struct list_head *head)
 	free(port);
 }
 
+static void free_endpoint(struct cxl_endpoint *endpoint, struct list_head *head)
+{
+	__free_port(&endpoint->port, head);
+	free(endpoint);
+}
+
 static void free_bus(struct cxl_bus *bus, struct list_head *head)
 {
 	__free_port(&bus->port, head);
@@ -500,6 +510,7 @@ static int cxl_port_init(struct cxl_port *port, struct cxl_port *parent_port,
 	port->parent = parent_port;
 
 	list_head_init(&port->child_ports);
+	list_head_init(&port->endpoints);
 
 	port->dev_path = strdup(cxlport_base);
 	if (!port->dev_path)
@@ -529,6 +540,97 @@ err:
 	return -ENOMEM;
 }
 
+static void *add_cxl_endpoint(void *parent, int id, const char *cxlep_base)
+{
+	const char *devname = devpath_to_devname(cxlep_base);
+	struct cxl_endpoint *endpoint, *endpoint_dup;
+	struct cxl_port *port = parent;
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+	int rc;
+
+	dbg(ctx, "%s: base: \'%s\'\n", devname, cxlep_base);
+
+	endpoint = calloc(1, sizeof(*endpoint));
+	if (!endpoint)
+		return NULL;
+
+	rc = cxl_port_init(&endpoint->port, port, CXL_PORT_ENDPOINT, ctx, id,
+			   cxlep_base);
+	if (rc)
+		goto err;
+
+	cxl_endpoint_foreach(port, endpoint_dup)
+		if (endpoint_dup->port.id == endpoint->port.id) {
+			free_endpoint(endpoint, NULL);
+			return endpoint_dup;
+		}
+
+	list_add(&port->endpoints, &endpoint->port.list);
+	return endpoint;
+
+err:
+	free(endpoint);
+	return NULL;
+
+}
+
+static void cxl_endpoints_init(struct cxl_port *port)
+{
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+
+	if (port->endpoints_init)
+		return;
+
+	port->endpoints_init = 1;
+
+	sysfs_device_parse(ctx, port->dev_path, "endpoint", port,
+			   add_cxl_endpoint);
+}
+
+CXL_EXPORT struct cxl_ctx *cxl_endpoint_get_ctx(struct cxl_endpoint *endpoint)
+{
+	return endpoint->port.ctx;
+}
+
+CXL_EXPORT struct cxl_endpoint *cxl_endpoint_get_first(struct cxl_port *port)
+{
+	cxl_endpoints_init(port);
+
+	return list_top(&port->endpoints, struct cxl_endpoint, port.list);
+}
+
+CXL_EXPORT struct cxl_endpoint *cxl_endpoint_get_next(struct cxl_endpoint *endpoint)
+{
+	struct cxl_port *port = endpoint->port.parent;
+
+	return list_next(&port->endpoints, endpoint, port.list);
+}
+
+CXL_EXPORT const char *cxl_endpoint_get_devname(struct cxl_endpoint *endpoint)
+{
+	return devpath_to_devname(endpoint->port.dev_path);
+}
+
+CXL_EXPORT int cxl_endpoint_get_id(struct cxl_endpoint *endpoint)
+{
+	return endpoint->port.id;
+}
+
+CXL_EXPORT struct cxl_port *cxl_endpoint_get_parent(struct cxl_endpoint *endpoint)
+{
+	return endpoint->port.parent;
+}
+
+CXL_EXPORT struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint)
+{
+	return &endpoint->port;
+}
+
+CXL_EXPORT int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint)
+{
+	return cxl_port_is_enabled(&endpoint->port);
+}
+
 static void *add_cxl_port(void *parent, int id, const char *cxlport_base)
 {
 	const char *devname = devpath_to_devname(cxlport_base);
@@ -619,6 +721,11 @@ CXL_EXPORT bool cxl_port_is_switch(struct cxl_port *port)
 	return port->type == CXL_PORT_SWITCH;
 }
 
+CXL_EXPORT bool cxl_port_is_endpoint(struct cxl_port *port)
+{
+	return port->type == CXL_PORT_ENDPOINT;
+}
+
 CXL_EXPORT struct cxl_bus *cxl_port_get_bus(struct cxl_port *port)
 {
 	struct cxl_bus *bus;
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index a7e923f7d721..7a51a0c42069 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -93,5 +93,14 @@ global:
 	cxl_port_is_root;
 	cxl_port_is_switch;
 	cxl_port_to_bus;
+	cxl_port_is_endpoint;
 	cxl_port_get_bus;
+	cxl_endpoint_get_first;
+	cxl_endpoint_get_next;
+	cxl_endpoint_get_devname;
+	cxl_endpoint_get_id;
+	cxl_endpoint_get_ctx;
+	cxl_endpoint_is_enabled;
+	cxl_endpoint_get_parent;
+	cxl_endpoint_get_port;
 } LIBCXL_1;
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
index 637f90d8dfa2..cedd2f2361a1 100644
--- a/cxl/lib/private.h
+++ b/cxl/lib/private.h
@@ -17,6 +17,7 @@ struct cxl_pmem {
 	char *dev_path;
 };
 
+struct cxl_endpoint;
 struct cxl_memdev {
 	int id, major, minor;
 	void *dev_buf;
@@ -32,11 +33,13 @@ struct cxl_memdev {
 	struct kmod_module *module;
 	struct cxl_pmem *pmem;
 	unsigned long long serial;
+	struct cxl_endpoint *endpoint;
 };
 
 enum cxl_port_type {
 	CXL_PORT_ROOT,
 	CXL_PORT_SWITCH,
+	CXL_PORT_ENDPOINT,
 };
 
 struct cxl_port {
@@ -46,6 +49,7 @@ struct cxl_port {
 	char *dev_path;
 	char *uport;
 	int ports_init;
+	int endpoints_init;
 	struct cxl_ctx *ctx;
 	struct cxl_bus *bus;
 	enum cxl_port_type type;
@@ -53,12 +57,18 @@ struct cxl_port {
 	struct kmod_module *module;
 	struct list_node list;
 	struct list_head child_ports;
+	struct list_head endpoints;
 };
 
 struct cxl_bus {
 	struct cxl_port port;
 };
 
+struct cxl_endpoint {
+	struct cxl_port port;
+	struct cxl_memdev *memdev;
+};
+
 enum cxl_cmd_query_status {
 	CXL_CMD_QUERY_NOT_RUN = 0,
 	CXL_CMD_QUERY_OK,
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index efbb397eb2be..f6ba9a105168 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -81,12 +81,27 @@ struct cxl_port *cxl_port_get_parent(struct cxl_port *port);
 bool cxl_port_is_root(struct cxl_port *port);
 bool cxl_port_is_switch(struct cxl_port *port);
 struct cxl_bus *cxl_port_to_bus(struct cxl_port *port);
+bool cxl_port_is_endpoint(struct cxl_port *port);
 struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
 
 #define cxl_port_foreach(parent, port)                                         \
 	for (port = cxl_port_get_first(parent); port != NULL;                  \
 	     port = cxl_port_get_next(port))
 
+struct cxl_endpoint;
+struct cxl_endpoint *cxl_endpoint_get_first(struct cxl_port *parent);
+struct cxl_endpoint *cxl_endpoint_get_next(struct cxl_endpoint *endpoint);
+const char *cxl_endpoint_get_devname(struct cxl_endpoint *endpoint);
+int cxl_endpoint_get_id(struct cxl_endpoint *endpoint);
+struct cxl_ctx *cxl_endpoint_get_ctx(struct cxl_endpoint *endpoint);
+int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint);
+struct cxl_port *cxl_endpoint_get_parent(struct cxl_endpoint *endpoint);
+struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint);
+
+#define cxl_endpoint_foreach(port, endpoint)                                   \
+	for (endpoint = cxl_endpoint_get_first(port); endpoint != NULL;        \
+	     endpoint = cxl_endpoint_get_next(endpoint))
+
 struct cxl_cmd;
 const char *cxl_cmd_get_devname(struct cxl_cmd *cmd);
 struct cxl_cmd *cxl_cmd_new_raw(struct cxl_memdev *memdev, int opcode);
diff --git a/cxl/list.c b/cxl/list.c
index 01ab19bff706..b15e01ce40f6 100644
--- a/cxl/list.c
+++ b/cxl/list.c
@@ -31,6 +31,11 @@ static const struct option options[] = {
 	OPT_BOOLEAN('P', "ports", &param.ports, "include CXL port info"),
 	OPT_BOOLEAN('S', "single", &param.single,
 		    "skip listing descendant objects"),
+	OPT_STRING('e', "endpoint", &param.endpoint_filter,
+		   "endpoint device name",
+		   "filter by CXL endpoint device name(s)"),
+	OPT_BOOLEAN('E', "endpoints", &param.endpoints,
+		    "include CXL endpoint info"),
 	OPT_BOOLEAN('i', "idle", &param.idle, "include disabled devices"),
 	OPT_BOOLEAN('u', "human", &param.human,
 		    "use human friendly number formats "),
@@ -44,7 +49,8 @@ static const struct option options[] = {
 
 static int num_list_flags(void)
 {
-       return !!param.memdevs + !!param.buses + !!param.ports;
+	return !!param.memdevs + !!param.buses + !!param.ports +
+	       !!param.endpoints;
 }
 
 int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
@@ -74,6 +80,8 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 			param.buses = true;
 		if (param.port_filter)
 			param.ports = true;
+		if (param.endpoint_filter)
+			param.endpoints = true;
 		if (num_list_flags() == 0) {
 			/*
 			 * TODO: We likely want to list regions by default if
@@ -96,6 +104,9 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 	if (cxl_filter_has(param.port_filter, "root") && param.ports)
 		param.buses = true;
 
+	if (cxl_filter_has(param.port_filter, "endpoint") && param.ports)
+		param.endpoints = true;
+
 	dbg(&param, "walk topology\n");
 	return cxl_filter_walk(ctx, &param);
 }


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 20/37] cxl/list: Add 'host' entries for port-like objects
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (18 preceding siblings ...)
  2022-01-24  0:53 ` [ndctl PATCH 19/37] cxl/list: Add endpoints Dan Williams
@ 2022-01-24  0:53 ` Dan Williams
  2022-01-24  0:53 ` [ndctl PATCH 21/37] cxl/list: Add 'host' entries for memdevs Dan Williams
                   ` (16 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:53 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Add the device name of the "host" device for a given CXL port object. The
kernel calls this the 'uport' attribute.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/cxl-list.txt   |    9 +++++++++
 Documentation/cxl/lib/libcxl.txt |    5 +++++
 cxl/json.c                       |    4 ++++
 cxl/lib/libcxl.c                 |   10 ++++++++++
 cxl/lib/libcxl.sym               |    2 ++
 cxl/libcxl.h                     |    2 ++
 6 files changed, 32 insertions(+)

diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index d342da27d3da..30b61615644b 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -210,6 +210,15 @@ OPTIONS
 --endpoints::
 	Include endpoint objects (CXL Memory Device decoders) in the
 	listing.
+----
+# cxl list -E
+[
+  {
+    "endpoint":"endpoint2",
+    "host":"mem0"
+  }
+]
+----
 
 -e::
 --endpoint::
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index eebab37acb3d..e4b372d781ec 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -178,6 +178,7 @@ struct cxl_port *cxl_port_get_next(struct cxl_port *port);
 struct cxl_port *cxl_port_get_parent(struct cxl_port *port);
 struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
 struct cxl_ctx *cxl_port_get_ctx(struct cxl_port *port);
+const char *cxl_port_get_host(struct cxl_port *port);
 
 #define cxl_port_foreach(parent, port)                                      \
        for (port = cxl_port_get_first(parent); port != NULL;                \
@@ -192,6 +193,9 @@ as a parent object retrievable via cxl_port_get_parent().
 The root port of a hiearchy can be retrieved via any port instance in
 that hierarchy via cxl_port_get_bus().
 
+The host of a port is the corresponding device name of the PCIe Root
+Port, or Switch Upstream Port with CXL capabilities.
+
 === PORT: Attributes
 ----
 const char *cxl_port_get_devname(struct cxl_port *port);
@@ -222,6 +226,7 @@ struct cxl_endpoint *cxl_endpoint_get_next(struct cxl_endpoint *endpoint);
 struct cxl_ctx *cxl_endpoint_get_ctx(struct cxl_endpoint *endpoint);
 struct cxl_port *cxl_endpoint_get_parent(struct cxl_endpoint *endpoint);
 struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint);
+const char *cxl_endpoint_get_host(struct cxl_endpoint *endpoint);
 
 #define cxl_endpoint_foreach(port, endpoint)                                 \
        for (endpoint = cxl_endpoint_get_first(port); endpoint != NULL;       \
diff --git a/cxl/json.c b/cxl/json.c
index 08f6192fe121..af3b4fe6a0eb 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -258,6 +258,10 @@ static struct json_object *__util_cxl_port_to_json(struct cxl_port *port,
 	if (jobj)
 		json_object_object_add(jport, name_key, jobj);
 
+	jobj = json_object_new_string(cxl_port_get_host(port));
+	if (jobj)
+		json_object_object_add(jport, "host", jobj);
+
 	if (!cxl_port_is_enabled(port)) {
 		jobj = json_object_new_string("disabled");
 		if (jobj)
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index a25e7152af2a..5f48202d7b93 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -626,6 +626,11 @@ CXL_EXPORT struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint)
 	return &endpoint->port;
 }
 
+CXL_EXPORT const char *cxl_endpoint_get_host(struct cxl_endpoint *endpoint)
+{
+	return cxl_port_get_host(&endpoint->port);
+}
+
 CXL_EXPORT int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint)
 {
 	return cxl_port_is_enabled(&endpoint->port);
@@ -744,6 +749,11 @@ CXL_EXPORT struct cxl_bus *cxl_port_get_bus(struct cxl_port *port)
 	return bus;
 }
 
+CXL_EXPORT const char *cxl_port_get_host(struct cxl_port *port)
+{
+	return devpath_to_devname(port->uport);
+}
+
 CXL_EXPORT int cxl_port_is_enabled(struct cxl_port *port)
 {
 	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index 7a51a0c42069..dc2863e150cf 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -95,6 +95,7 @@ global:
 	cxl_port_to_bus;
 	cxl_port_is_endpoint;
 	cxl_port_get_bus;
+	cxl_port_get_host;
 	cxl_endpoint_get_first;
 	cxl_endpoint_get_next;
 	cxl_endpoint_get_devname;
@@ -103,4 +104,5 @@ global:
 	cxl_endpoint_is_enabled;
 	cxl_endpoint_get_parent;
 	cxl_endpoint_get_port;
+	cxl_endpoint_get_host;
 } LIBCXL_1;
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index f6ba9a105168..a60777ed0c7a 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -83,6 +83,7 @@ bool cxl_port_is_switch(struct cxl_port *port);
 struct cxl_bus *cxl_port_to_bus(struct cxl_port *port);
 bool cxl_port_is_endpoint(struct cxl_port *port);
 struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
+const char *cxl_port_get_host(struct cxl_port *port);
 
 #define cxl_port_foreach(parent, port)                                         \
 	for (port = cxl_port_get_first(parent); port != NULL;                  \
@@ -97,6 +98,7 @@ struct cxl_ctx *cxl_endpoint_get_ctx(struct cxl_endpoint *endpoint);
 int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint);
 struct cxl_port *cxl_endpoint_get_parent(struct cxl_endpoint *endpoint);
 struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint);
+const char *cxl_endpoint_get_host(struct cxl_endpoint *endpoint);
 
 #define cxl_endpoint_foreach(port, endpoint)                                   \
 	for (endpoint = cxl_endpoint_get_first(port); endpoint != NULL;        \


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 21/37] cxl/list: Add 'host' entries for memdevs
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (19 preceding siblings ...)
  2022-01-24  0:53 ` [ndctl PATCH 20/37] cxl/list: Add 'host' entries for port-like objects Dan Williams
@ 2022-01-24  0:53 ` Dan Williams
  2022-01-24  0:53 ` [ndctl PATCH 22/37] cxl/list: Move enabled memdevs underneath their endpoint Dan Williams
                   ` (15 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:53 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

For debugging CXL port connectivity issues it will be useful to have the
PCI device name for the memory expander in the 'memdev' listing.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/cxl-list.txt   |    3 ++-
 Documentation/cxl/lib/libcxl.txt |    4 ++++
 cxl/json.c                       |    5 +++++
 cxl/lib/libcxl.c                 |   24 ++++++++++++++++++++++++
 cxl/lib/libcxl.sym               |    1 +
 cxl/lib/private.h                |    1 +
 cxl/libcxl.h                     |    1 +
 7 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index 30b61615644b..9c21ab7afd74 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -43,7 +43,8 @@ EXAMPLE
     "memdev":"mem0",
     "pmem_size":268435456,
     "ram_size":0,
-    "serial":0
+    "serial":0,
+    "host":"0000:35:00.0"
   }
 ]
 
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index e4b372d781ec..91fd33e8b1ae 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -40,6 +40,7 @@ kernel, or to send data or trigger kernel operations for a given device.
 struct cxl_memdev *cxl_memdev_get_first(struct cxl_ctx *ctx);
 struct cxl_memdev *cxl_memdev_get_next(struct cxl_memdev *memdev);
 struct cxl_ctx *cxl_memdev_get_ctx(struct cxl_memdev *memdev);
+const char *cxl_memdev_get_host(struct cxl_memdev *memdev)
 
 #define cxl_memdev_foreach(ctx, memdev) \
         for (memdev = cxl_memdev_get_first(ctx); \
@@ -54,6 +55,9 @@ memory device commands, see the port, decoder, and endpoint APIs to
 determine what if any CXL Memory Resources are reachable given a
 specific memdev.
 
+The host of a memdev is the PCIe Endpoint device that registered its CXL
+capabilities with the Linux CXL core.
+
 === MEMDEV: Attributes
 ----
 int cxl_memdev_get_id(struct cxl_memdev *memdev);
diff --git a/cxl/json.c b/cxl/json.c
index af3b4fe6a0eb..18686867e0c5 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -219,6 +219,11 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
 		if (jobj)
 			json_object_object_add(jdev, "serial", jobj);
 	}
+
+	jobj = json_object_new_string(cxl_memdev_get_host(memdev));
+	if (jobj)
+		json_object_object_add(jdev, "host", jobj);
+
 	return jdev;
 }
 
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 5f48202d7b93..c4ddc7d80cff 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -63,6 +63,7 @@ static void free_memdev(struct cxl_memdev *memdev, struct list_head *head)
 	free(memdev->firmware_version);
 	free(memdev->dev_buf);
 	free(memdev->dev_path);
+	free(memdev->host);
 	free(memdev);
 }
 
@@ -297,6 +298,7 @@ static void *add_cxl_memdev(void *parent, int id, const char *cxlmem_base)
 	char *path = calloc(1, strlen(cxlmem_base) + 100);
 	struct cxl_ctx *ctx = parent;
 	struct cxl_memdev *memdev, *memdev_dup;
+	char *host, *rpath = NULL;
 	char buf[SYSFS_ATTR_SIZE];
 	struct stat st;
 
@@ -350,6 +352,22 @@ static void *add_cxl_memdev(void *parent, int id, const char *cxlmem_base)
 	if (!memdev->dev_path)
 		goto err_read;
 
+	rpath = realpath(cxlmem_base, NULL);
+	if (!rpath)
+		goto err_read;
+	host = strrchr(rpath, '/');
+	if (host) {
+		host[0] = '\0';
+		host = strrchr(rpath, '/');
+	}
+	if (!host)
+		goto err_read;
+	memdev->host = strdup(host + 1);
+	if (!memdev->host)
+		goto err_read;
+	free(rpath);
+	rpath = NULL;
+
 	sprintf(path, "%s/firmware_version", cxlmem_base);
 	if (sysfs_read_attr(ctx, path, buf) < 0)
 		goto err_read;
@@ -381,6 +399,7 @@ static void *add_cxl_memdev(void *parent, int id, const char *cxlmem_base)
 	free(memdev->dev_buf);
 	free(memdev->dev_path);
 	free(memdev);
+	free(rpath);
  err_dev:
 	free(path);
 	return NULL;
@@ -431,6 +450,11 @@ CXL_EXPORT const char *cxl_memdev_get_devname(struct cxl_memdev *memdev)
 	return devpath_to_devname(memdev->dev_path);
 }
 
+CXL_EXPORT const char *cxl_memdev_get_host(struct cxl_memdev *memdev)
+{
+	return memdev->host;
+}
+
 CXL_EXPORT int cxl_memdev_get_major(struct cxl_memdev *memdev)
 {
 	return memdev->major;
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index dc2863e150cf..8f0688a95f5d 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -77,6 +77,7 @@ local:
 LIBCXL_2 {
 global:
 	cxl_memdev_get_serial;
+	cxl_memdev_get_host;
 	cxl_bus_get_first;
 	cxl_bus_get_next;
 	cxl_bus_get_provider;
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
index cedd2f2361a1..b097bdfbfd6b 100644
--- a/cxl/lib/private.h
+++ b/cxl/lib/private.h
@@ -22,6 +22,7 @@ struct cxl_memdev {
 	int id, major, minor;
 	void *dev_buf;
 	size_t buf_len;
+	char *host;
 	char *dev_path;
 	char *firmware_version;
 	struct cxl_ctx *ctx;
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index a60777ed0c7a..5487b55d2fc0 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -38,6 +38,7 @@ struct cxl_memdev *cxl_memdev_get_next(struct cxl_memdev *memdev);
 int cxl_memdev_get_id(struct cxl_memdev *memdev);
 unsigned long long cxl_memdev_get_serial(struct cxl_memdev *memdev);
 const char *cxl_memdev_get_devname(struct cxl_memdev *memdev);
+const char *cxl_memdev_get_host(struct cxl_memdev *memdev);
 int cxl_memdev_get_major(struct cxl_memdev *memdev);
 int cxl_memdev_get_minor(struct cxl_memdev *memdev);
 struct cxl_ctx *cxl_memdev_get_ctx(struct cxl_memdev *memdev);


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 22/37] cxl/list: Move enabled memdevs underneath their endpoint
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (20 preceding siblings ...)
  2022-01-24  0:53 ` [ndctl PATCH 21/37] cxl/list: Add 'host' entries for memdevs Dan Williams
@ 2022-01-24  0:53 ` Dan Williams
  2022-01-24  0:53 ` [ndctl PATCH 23/37] cxl/list: Filter memdev by ancestry Dan Williams
                   ` (14 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:53 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

When a memdev is enabled it means that the kernel was able to validate a
CXL connection from the CXL root, through intervening switches, and to the
endpoint. Reflect that state by listing memdevs as child objects of
endpoints, or aggregated into an array if individual endpoints are not
listed.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/cxl-list.txt   |   11 +++
 Documentation/cxl/lib/libcxl.txt |    2 +
 cxl/filter.c                     |  130 ++++++++++++++++++++++++++++++--------
 cxl/json.c                       |    6 ++
 cxl/lib/libcxl.c                 |   97 ++++++++++++++++++++++++++++
 cxl/lib/libcxl.sym               |    3 +
 cxl/libcxl.h                     |    4 +
 7 files changed, 223 insertions(+), 30 deletions(-)

diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index 9c21ab7afd74..175186819312 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -19,7 +19,16 @@ Options can be specified to limit the output to specific objects. When a
 single object type is specified the return json object is an array of
 just those objects, when multiple objects types are specified the
 returned the returned object may be an array of arrays with the inner
-array named for the given object type.
+array named for the given object type. The top-level arrays are ellided
+when the objects can nest under a higher object-type in the hierararchy.
+The potential top-level array names and their nesting properties are:
+
+"anon memdevs":: (disabled memory devices) do not nest
+"buses":: do not nest
+"ports":: nest under buses
+"endpoints":: nest under ports or buses (if ports are not emitted)
+"memdevs":: nest under endpoints or ports (if endpoints are not
+   emitted) or buses (if endpoints and ports are not emitted)
 
 Filters can by specifed as either a single identidier, a space separated
 quoted string, or a comma separated list. When multiple filter
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index 91fd33e8b1ae..73b0fb9c2062 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -41,6 +41,7 @@ struct cxl_memdev *cxl_memdev_get_first(struct cxl_ctx *ctx);
 struct cxl_memdev *cxl_memdev_get_next(struct cxl_memdev *memdev);
 struct cxl_ctx *cxl_memdev_get_ctx(struct cxl_memdev *memdev);
 const char *cxl_memdev_get_host(struct cxl_memdev *memdev)
+struct cxl_memdev *cxl_endpoint_get_memdev(struct cxl_endpoint *endpoint);
 
 #define cxl_memdev_foreach(ctx, memdev) \
         for (memdev = cxl_memdev_get_first(ctx); \
@@ -231,6 +232,7 @@ struct cxl_ctx *cxl_endpoint_get_ctx(struct cxl_endpoint *endpoint);
 struct cxl_port *cxl_endpoint_get_parent(struct cxl_endpoint *endpoint);
 struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint);
 const char *cxl_endpoint_get_host(struct cxl_endpoint *endpoint);
+struct cxl_endpoint *cxl_memdev_get_endpoint(struct cxl_memdev *memdev);
 
 #define cxl_endpoint_foreach(port, endpoint)                                 \
        for (endpoint = cxl_endpoint_get_first(port); endpoint != NULL;       \
diff --git a/cxl/filter.c b/cxl/filter.c
index 5d80d1b3922a..2130816931e5 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -381,13 +381,16 @@ static struct json_object *pick_array(struct json_object *child,
 }
 
 static void walk_endpoints(struct cxl_port *port, struct cxl_filter_params *p,
-			   struct json_object *jeps, unsigned long flags)
+			   struct json_object *jeps, struct json_object *jdevs,
+			   unsigned long flags)
 {
 	struct cxl_endpoint *endpoint;
 
 	cxl_endpoint_foreach(port, endpoint) {
 		struct cxl_port *ep_port = cxl_endpoint_get_port(endpoint);
-		struct json_object *jendpoint;
+		const char *devname = cxl_endpoint_get_devname(endpoint);
+		struct json_object *jendpoint = NULL;
+		struct cxl_memdev *memdev;
 
 		if (!util_cxl_endpoint_filter(endpoint, p->endpoint_filter))
 			continue;
@@ -398,24 +401,54 @@ static void walk_endpoints(struct cxl_port *port, struct cxl_filter_params *p,
 			continue;
 		if (!p->idle && !cxl_endpoint_is_enabled(endpoint))
 			continue;
-		jendpoint = util_cxl_endpoint_to_json(endpoint, flags);
-		if (jendpoint)
+		if (p->endpoints) {
+			jendpoint = util_cxl_endpoint_to_json(endpoint, flags);
+			if (!jendpoint) {
+				err(p, "%s: failed to list\n", devname);
+				continue;
+			}
 			json_object_array_add(jeps, jendpoint);
+		}
+		if (p->memdevs) {
+			struct json_object *jobj;
+
+			memdev = cxl_endpoint_get_memdev(endpoint);
+			if (!memdev)
+				continue;
+			if (!util_cxl_memdev_filter(memdev, p->memdev_filter,
+						    p->serial_filter))
+				continue;
+			if (!p->idle && !cxl_memdev_is_enabled(memdev))
+				continue;
+			jobj = util_cxl_memdev_to_json(memdev, flags);
+			if (!jobj) {
+				err(p, "failed to json serialize %s\n",
+				    cxl_memdev_get_devname(memdev));
+				continue;
+			}
+			if (p->endpoints)
+				json_object_object_add(jendpoint, "memdev",
+						       jobj);
+			else
+				json_object_array_add(jdevs, jobj);
+		}
 	}
 }
 
 static void walk_child_ports(struct cxl_port *parent_port,
 			     struct cxl_filter_params *p,
 			     struct json_object *jports,
-			     struct json_object *jeps, unsigned long flags)
+			     struct json_object *jeps,
+			     struct json_object *jdevs, unsigned long flags)
 {
 	struct cxl_port *port;
 
 	cxl_port_foreach(parent_port, port) {
 		const char *devname = cxl_port_get_devname(port);
 		struct json_object *jport = NULL;
+		struct json_object *jchilddevs = NULL;
 		struct json_object *jchildports = NULL;
-		struct json_object *jchildendpoints = NULL;
+		struct json_object *jchildeps = NULL;
 
 		if (!util_cxl_port_filter(port, p->port_filter, pf_mode(p)))
 			goto walk_children;
@@ -436,28 +469,41 @@ static void walk_child_ports(struct cxl_port *parent_port,
 				    devname);
 				continue;
 			}
-		}
 
-		if (p->ports && p->endpoints) {
-			jchildendpoints = json_object_new_array();
-			if (!jchildendpoints) {
-				err(p,
-				    "%s: failed to enumerate child endpoints\n",
-				    devname);
-				continue;
+			if (p->memdevs && !p->endpoints) {
+				jchilddevs = json_object_new_array();
+				if (!jchilddevs) {
+					err(p,
+					    "%s: failed to enumerate child memdevs\n",
+					    devname);
+					continue;
+				}
+			}
+
+			if (p->endpoints) {
+				jchildeps = json_object_new_array();
+				if (!jchildeps) {
+					err(p,
+					    "%s: failed to enumerate child endpoints\n",
+					    devname);
+					continue;
+				}
 			}
 		}
 
 walk_children:
-		if (p->endpoints)
-			walk_endpoints(port, p, pick_array(jchildendpoints, jeps),
-				       flags);
+		if (p->endpoints || p->memdevs)
+			walk_endpoints(port, p, pick_array(jchildeps, jeps),
+				       pick_array(jchilddevs, jdevs), flags);
 
 		walk_child_ports(port, p, pick_array(jchildports, jports),
-				 pick_array(jchildendpoints, jeps), flags);
+				 pick_array(jchildeps, jeps),
+				 pick_array(jchilddevs, jdevs), flags);
 		cond_add_put_array_suffix(jport, "ports", devname, jchildports);
 		cond_add_put_array_suffix(jport, "endpoints", devname,
-					  jchildendpoints);
+					  jchildeps);
+		cond_add_put_array_suffix(jport, "memdevs", devname,
+					  jchilddevs);
 	}
 }
 
@@ -466,6 +512,7 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	struct json_object *jdevs = NULL, *jbuses = NULL, *jports = NULL;
 	struct json_object *jplatform = json_object_new_array();
 	unsigned long flags = params_to_flags(p);
+	struct json_object *janondevs = NULL;
 	struct json_object *jeps = NULL;
 	struct cxl_memdev *memdev;
 	int top_level_objs = 0;
@@ -476,8 +523,8 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 		return -ENOMEM;
 	}
 
-	jdevs = json_object_new_array();
-	if (!jdevs)
+	janondevs = json_object_new_array();
+	if (!janondevs)
 		goto err;
 
 	jbuses = json_object_new_array();
@@ -492,20 +539,28 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	if (!jeps)
 		goto err;
 
+	jdevs = json_object_new_array();
+	if (!jdevs)
+		goto err;
+
 	dbg(p, "walk memdevs\n");
 	cxl_memdev_foreach(ctx, memdev) {
-		struct json_object *jdev;
+		struct json_object *janondev;
 
 		if (!util_cxl_memdev_filter(memdev, p->memdev_filter,
 					    p->serial_filter))
 			continue;
+		if (cxl_memdev_is_enabled(memdev))
+			continue;
+		if (!p->idle)
+			continue;
 		if (p->memdevs) {
-			jdev = util_cxl_memdev_to_json(memdev, flags);
-			if (!jdev) {
+			janondev = util_cxl_memdev_to_json(memdev, flags);
+			if (!janondev) {
 				dbg(p, "memdev object allocation failure\n");
 				continue;
 			}
-			json_object_array_add(jdevs, jdev);
+			json_object_array_add(janondevs, janondev);
 		}
 	}
 
@@ -513,6 +568,7 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	cxl_bus_foreach(ctx, bus) {
 		struct json_object *jbus = NULL;
 		struct json_object *jchildports = NULL;
+		struct json_object *jchilddevs = NULL;
 		struct json_object *jchildeps = NULL;
 		struct cxl_port *port = cxl_bus_get_port(bus);
 		const char *devname = cxl_bus_get_devname(bus);
@@ -546,17 +602,29 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 					continue;
 				}
 			}
+
+			if (p->memdevs && !p->ports && !p->endpoints) {
+				jchilddevs = json_object_new_array();
+				if (!jchilddevs) {
+					err(p,
+					    "%s: failed to enumerate child memdevs\n",
+					    devname);
+					continue;
+				}
+			}
 		}
 walk_children:
 		dbg(p, "walk ports\n");
 		walk_child_ports(port, p, pick_array(jchildports, jports),
-				 pick_array(jchildeps, jeps), flags);
+				 pick_array(jchildeps, jeps),
+				 pick_array(jchilddevs, jdevs), flags);
 		cond_add_put_array_suffix(jbus, "ports", devname, jchildports);
 		cond_add_put_array_suffix(jbus, "endpoints", devname,
 					  jchildeps);
+		cond_add_put_array_suffix(jbus, "memdevs", devname, jchilddevs);
 	}
 
-	if (json_object_array_length(jdevs))
+	if (json_object_array_length(janondevs))
 		top_level_objs++;
 	if (json_object_array_length(jbuses))
 		top_level_objs++;
@@ -564,20 +632,24 @@ walk_children:
 		top_level_objs++;
 	if (json_object_array_length(jeps))
 		top_level_objs++;
+	if (json_object_array_length(jdevs))
+		top_level_objs++;
 
-	splice_array(p, jdevs, jplatform, "anon memdevs", top_level_objs > 1);
+	splice_array(p, janondevs, jplatform, "anon memdevs", top_level_objs > 1);
 	splice_array(p, jbuses, jplatform, "buses", top_level_objs > 1);
 	splice_array(p, jports, jplatform, "ports", top_level_objs > 1);
 	splice_array(p, jeps, jplatform, "endpoints", top_level_objs > 1);
+	splice_array(p, jdevs, jplatform, "memdevs", top_level_objs > 1);
 
 	util_display_json_array(stdout, jplatform, flags);
 
 	return 0;
 err:
-	json_object_put(jdevs);
+	json_object_put(janondevs);
 	json_object_put(jbuses);
 	json_object_put(jports);
 	json_object_put(jeps);
+	json_object_put(jdevs);
 	json_object_put(jplatform);
 	return -ENOMEM;
 }
diff --git a/cxl/json.c b/cxl/json.c
index 18686867e0c5..b809332b8d51 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -224,6 +224,12 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
 	if (jobj)
 		json_object_object_add(jdev, "host", jobj);
 
+	if (!cxl_memdev_is_enabled(memdev)) {
+		jobj = json_object_new_string("disabled");
+		if (jobj)
+			json_object_object_add(jdev, "state", jobj);
+	}
+
 	return jdev;
 }
 
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index c4ddc7d80cff..4523ca630d11 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -480,6 +480,60 @@ CXL_EXPORT const char *cxl_memdev_get_firmware_verison(struct cxl_memdev *memdev
 	return memdev->firmware_version;
 }
 
+static struct cxl_endpoint *cxl_port_find_endpoint(struct cxl_port *parent_port,
+						   struct cxl_memdev *memdev)
+{
+	struct cxl_endpoint *endpoint;
+	struct cxl_port *port;
+
+	cxl_port_foreach(parent_port, port) {
+		cxl_endpoint_foreach(port, endpoint)
+			if (strcmp(cxl_endpoint_get_host(endpoint),
+				   cxl_memdev_get_devname(memdev)) == 0)
+				return endpoint;
+		endpoint = cxl_port_find_endpoint(port, memdev);
+		if (endpoint)
+			return endpoint;
+	}
+
+	return NULL;
+}
+
+CXL_EXPORT struct cxl_endpoint *
+cxl_memdev_get_endpoint(struct cxl_memdev *memdev)
+{
+	struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev);
+	struct cxl_endpoint *endpoint = NULL;
+	struct cxl_bus *bus;
+
+	if (memdev->endpoint)
+		return memdev->endpoint;
+
+	if (!cxl_memdev_is_enabled(memdev))
+		return NULL;
+
+	cxl_bus_foreach (ctx, bus) {
+		struct cxl_port *port = cxl_bus_get_port(bus);
+
+		endpoint = cxl_port_find_endpoint(port, memdev);
+		if (endpoint)
+			break;
+	}
+
+	if (!endpoint)
+		return NULL;
+
+	if (endpoint->memdev && endpoint->memdev != memdev)
+		err(ctx, "%s assigned to %s not %s\n",
+		    cxl_endpoint_get_devname(endpoint),
+		    cxl_memdev_get_devname(endpoint->memdev),
+		    cxl_memdev_get_devname(memdev));
+	memdev->endpoint = endpoint;
+	endpoint->memdev = memdev;
+
+	return endpoint;
+}
+
 CXL_EXPORT size_t cxl_memdev_get_label_size(struct cxl_memdev *memdev)
 {
 	return memdev->lsa_size;
@@ -495,6 +549,21 @@ static int is_enabled(const char *drvpath)
 		return 1;
 }
 
+CXL_EXPORT int cxl_memdev_is_enabled(struct cxl_memdev *memdev)
+{
+	struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev);
+	char *path = memdev->dev_buf;
+	int len = memdev->buf_len;
+
+	if (snprintf(path, len, "%s/driver", memdev->dev_path) >= len) {
+		err(ctx, "%s: buffer too small!\n",
+		    cxl_memdev_get_devname(memdev));
+		return 0;
+	}
+
+	return is_enabled(path);
+}
+
 CXL_EXPORT int cxl_memdev_nvdimm_bridge_active(struct cxl_memdev *memdev)
 {
 	struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev);
@@ -660,6 +729,34 @@ CXL_EXPORT int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint)
 	return cxl_port_is_enabled(&endpoint->port);
 }
 
+CXL_EXPORT struct cxl_memdev *
+cxl_endpoint_get_memdev(struct cxl_endpoint *endpoint)
+{
+	struct cxl_ctx *ctx = cxl_endpoint_get_ctx(endpoint);
+	struct cxl_memdev *memdev;
+
+	if (endpoint->memdev)
+		return endpoint->memdev;
+
+	if (!cxl_endpoint_is_enabled(endpoint))
+		return NULL;
+
+	cxl_memdev_foreach(ctx, memdev)
+		if (strcmp(cxl_memdev_get_devname(memdev),
+			   cxl_endpoint_get_host(endpoint)) == 0) {
+			if (memdev->endpoint && memdev->endpoint != endpoint)
+				err(ctx, "%s assigned to %s not %s\n",
+				    cxl_memdev_get_devname(memdev),
+				    cxl_endpoint_get_devname(memdev->endpoint),
+				    cxl_endpoint_get_devname(endpoint));
+			endpoint->memdev = memdev;
+			memdev->endpoint = endpoint;
+			return memdev;
+		}
+
+	return NULL;
+}
+
 static void *add_cxl_port(void *parent, int id, const char *cxlport_base)
 {
 	const char *devname = devpath_to_devname(cxlport_base);
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index 8f0688a95f5d..321acac57e3e 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -106,4 +106,7 @@ global:
 	cxl_endpoint_get_parent;
 	cxl_endpoint_get_port;
 	cxl_endpoint_get_host;
+	cxl_endpoint_get_memdev;
+	cxl_memdev_get_endpoint;
+	cxl_memdev_is_enabled;
 } LIBCXL_1;
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index 5487b55d2fc0..790ece86529d 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -46,6 +46,8 @@ unsigned long long cxl_memdev_get_pmem_size(struct cxl_memdev *memdev);
 unsigned long long cxl_memdev_get_ram_size(struct cxl_memdev *memdev);
 const char *cxl_memdev_get_firmware_verison(struct cxl_memdev *memdev);
 size_t cxl_memdev_get_label_size(struct cxl_memdev *memdev);
+struct cxl_endpoint;
+struct cxl_endpoint *cxl_memdev_get_endpoint(struct cxl_memdev *memdev);
 int cxl_memdev_nvdimm_bridge_active(struct cxl_memdev *memdev);
 int cxl_memdev_zero_label(struct cxl_memdev *memdev, size_t length,
 		size_t offset);
@@ -100,6 +102,8 @@ int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint);
 struct cxl_port *cxl_endpoint_get_parent(struct cxl_endpoint *endpoint);
 struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint);
 const char *cxl_endpoint_get_host(struct cxl_endpoint *endpoint);
+struct cxl_memdev *cxl_endpoint_get_memdev(struct cxl_endpoint *endpoint);
+int cxl_memdev_is_enabled(struct cxl_memdev *memdev);
 
 #define cxl_endpoint_foreach(port, endpoint)                                   \
 	for (endpoint = cxl_endpoint_get_first(port); endpoint != NULL;        \


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 23/37] cxl/list: Filter memdev by ancestry
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (21 preceding siblings ...)
  2022-01-24  0:53 ` [ndctl PATCH 22/37] cxl/list: Move enabled memdevs underneath their endpoint Dan Williams
@ 2022-01-24  0:53 ` Dan Williams
  2022-01-24  0:53 ` [ndctl PATCH 24/37] cxl/memdev: Use a local logger for debug Dan Williams
                   ` (13 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:53 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Whenever a memdev filter is specified limit output of buses, ports and
endpoints to those that are in the memdev's ancestry.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/cxl-list.txt   |   19 ++++++++++
 Documentation/cxl/lib/libcxl.txt |   11 ++++++
 cxl/filter.c                     |   69 ++++++++++++++++++++++++++++++++++++++
 cxl/lib/libcxl.c                 |   36 ++++++++++++++++++++
 cxl/lib/libcxl.sym               |    5 +++
 cxl/libcxl.h                     |    4 ++
 6 files changed, 144 insertions(+)

diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index 175186819312..bac27c7b70a1 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -39,6 +39,25 @@ they are combined as an 'AND' filter. So, "-m mem0,mem1,mem2 -p port10"
 would only list objects that are beneath port10 AND map mem0, mem1, OR
 mem2.
 
+Given that many topology queries seek to answer questions relative to a
+given memdev, buses, ports, and endpoints can be filtered by one or more
+memdevs. For example:
+----
+# cxl list -P -p switch,endpoint -m mem0
+[
+  {
+    "port":"port1",
+    "host":"ACPI0016:00",
+    "endpoints:port1":[
+      {
+        "endpoint":"endpoint2",
+        "host":"mem0"
+      }
+    ]
+  }
+]
+----
+
 The --human option in addition to reformatting some fields to more human
 friendly strings also unwraps the array to reduce the number of lines of
 output.
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index 73b0fb9c2062..b0253d7be07c 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -150,11 +150,18 @@ cxl_bus'.
 ----
 struct cxl_bus *cxl_bus_get_first(struct cxl_ctx *ctx);
 struct cxl_bus *cxl_bus_get_next(struct cxl_bus *bus);
+struct cxl_ctx *cxl_bus_get_ctx(struct cxl_bus *bus);
+struct cxl_bus *cxl_memdev_get_bus(struct cxl_memdev *memdev);
+struct cxl_bus *cxl_endpoint_get_bus(struct cxl_endpoint *endpoint);
 
 #define cxl_bus_foreach(ctx, bus)                                           \
        for (bus = cxl_bus_get_first(ctx); bus != NULL;                      \
             bus = cxl_bus_get_next(bus))
 ----
+When a memdev is active it has established a CXL port hierarchy between
+itself and the root of its associated CXL topology. The
+cxl_{memdev,endpoint}_get_bus() helpers walk that topology to retrieve
+the associated bus object.
 
 === BUS: Attributes
 ----
@@ -209,6 +216,7 @@ int cxl_port_is_enabled(struct cxl_port *port);
 bool cxl_port_is_root(struct cxl_port *port);
 bool cxl_port_is_switch(struct cxl_port *port);
 bool cxl_port_is_endpoint(struct cxl_port *port);
+bool cxl_port_hosts_memdev(struct cxl_port *port, struct cxl_memdev *memdev);
 ----
 The port type is communicated via cxl_port_is_<type>(). An 'enabled' port
 is one that has succeeded in discovering the CXL component registers in
@@ -217,6 +225,9 @@ memdev to be enabled for CXL memory operation all CXL ports in its
 ancestry must also be enabled including a root port, an arbitrary number
 of intervening switch ports, and a terminal endpoint port.
 
+cxl_port_hosts_memdev() returns true if the port's host appears in the
+memdev host's device topology ancestry.
+
 ENDPOINTS
 ---------
 CXL endpoint objects encapsulate the set of host-managed device-memory
diff --git a/cxl/filter.c b/cxl/filter.c
index 2130816931e5..6dc61a135351 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -297,6 +297,66 @@ struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev,
 	return NULL;
 }
 
+static struct cxl_bus *util_cxl_bus_filter_by_memdev(struct cxl_bus *bus,
+						     const char *ident,
+						     const char *serial)
+{
+	struct cxl_ctx *ctx = cxl_bus_get_ctx(bus);
+	struct cxl_memdev *memdev;
+
+	if (!ident && !serial)
+		return bus;
+
+	cxl_memdev_foreach(ctx, memdev) {
+		if (!util_cxl_memdev_filter(memdev, ident, serial))
+			continue;
+		if (cxl_memdev_get_bus(memdev) == bus)
+			return bus;
+	}
+
+	return NULL;
+}
+
+static struct cxl_endpoint *
+util_cxl_endpoint_filter_by_memdev(struct cxl_endpoint *endpoint,
+				   const char *ident, const char *serial)
+{
+	struct cxl_ctx *ctx = cxl_endpoint_get_ctx(endpoint);
+	struct cxl_memdev *memdev;
+
+	if (!ident && !serial)
+		return endpoint;
+
+	cxl_memdev_foreach(ctx, memdev) {
+		if (!util_cxl_memdev_filter(memdev, ident, serial))
+			continue;
+		if (cxl_memdev_get_endpoint(memdev) == endpoint)
+			return endpoint;
+	}
+
+	return NULL;
+}
+
+static struct cxl_port *util_cxl_port_filter_by_memdev(struct cxl_port *port,
+						       const char *ident,
+						       const char *serial)
+{
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+	struct cxl_memdev *memdev;
+
+	if (!ident && !serial)
+		return port;
+
+	cxl_memdev_foreach(ctx, memdev) {
+		if (!util_cxl_memdev_filter(memdev, ident, serial))
+			continue;
+		if (cxl_port_hosts_memdev(port, memdev))
+			return port;
+	}
+
+	return NULL;
+}
+
 static unsigned long params_to_flags(struct cxl_filter_params *param)
 {
 	unsigned long flags = 0;
@@ -399,6 +459,9 @@ static void walk_endpoints(struct cxl_port *port, struct cxl_filter_params *p,
 		if (!util_cxl_endpoint_filter_by_port(endpoint, p->port_filter,
 						      pf_mode(p)))
 			continue;
+		if (!util_cxl_endpoint_filter_by_memdev(
+			    endpoint, p->memdev_filter, p->serial_filter))
+			continue;
 		if (!p->idle && !cxl_endpoint_is_enabled(endpoint))
 			continue;
 		if (p->endpoints) {
@@ -450,6 +513,9 @@ static void walk_child_ports(struct cxl_port *parent_port,
 		struct json_object *jchildports = NULL;
 		struct json_object *jchildeps = NULL;
 
+		if (!util_cxl_port_filter_by_memdev(port, p->memdev_filter,
+						    p->serial_filter))
+			continue;
 		if (!util_cxl_port_filter(port, p->port_filter, pf_mode(p)))
 			goto walk_children;
 		if (!util_cxl_port_filter_by_bus(port, p->bus_filter))
@@ -573,6 +639,9 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 		struct cxl_port *port = cxl_bus_get_port(bus);
 		const char *devname = cxl_bus_get_devname(bus);
 
+		if (!util_cxl_bus_filter_by_memdev(bus, p->memdev_filter,
+						   p->serial_filter))
+			continue;
 		if (!util_cxl_bus_filter(bus, p->bus_filter))
 			goto walk_children;
 		if (!util_cxl_port_filter(port, p->port_filter, pf_mode(p)))
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 4523ca630d11..0065f6baeead 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -455,6 +455,15 @@ CXL_EXPORT const char *cxl_memdev_get_host(struct cxl_memdev *memdev)
 	return memdev->host;
 }
 
+CXL_EXPORT struct cxl_bus *cxl_memdev_get_bus(struct cxl_memdev *memdev)
+{
+	struct cxl_endpoint *endpoint = cxl_memdev_get_endpoint(memdev);
+
+	if (!endpoint)
+		return NULL;
+	return cxl_endpoint_get_bus(endpoint);
+}
+
 CXL_EXPORT int cxl_memdev_get_major(struct cxl_memdev *memdev)
 {
 	return memdev->major;
@@ -724,6 +733,13 @@ CXL_EXPORT const char *cxl_endpoint_get_host(struct cxl_endpoint *endpoint)
 	return cxl_port_get_host(&endpoint->port);
 }
 
+CXL_EXPORT struct cxl_bus *cxl_endpoint_get_bus(struct cxl_endpoint *endpoint)
+{
+	struct cxl_port *port = &endpoint->port;
+
+	return cxl_port_get_bus(port);
+}
+
 CXL_EXPORT int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint)
 {
 	return cxl_port_is_enabled(&endpoint->port);
@@ -875,6 +891,21 @@ CXL_EXPORT const char *cxl_port_get_host(struct cxl_port *port)
 	return devpath_to_devname(port->uport);
 }
 
+CXL_EXPORT bool cxl_port_hosts_memdev(struct cxl_port *port,
+				      struct cxl_memdev *memdev)
+{
+	struct cxl_endpoint *endpoint = cxl_memdev_get_endpoint(memdev);
+	struct cxl_port *iter;
+
+	if (!endpoint)
+		return false;
+
+	iter = cxl_endpoint_get_port(endpoint);
+	while (iter && iter != port)
+		iter = iter->parent;
+	return iter != NULL;
+}
+
 CXL_EXPORT int cxl_port_is_enabled(struct cxl_port *port)
 {
 	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
@@ -985,6 +1016,11 @@ CXL_EXPORT const char *cxl_bus_get_provider(struct cxl_bus *bus)
 	return devname;
 }
 
+CXL_EXPORT struct cxl_ctx *cxl_bus_get_ctx(struct cxl_bus *bus)
+{
+	return cxl_port_get_ctx(&bus->port);
+}
+
 CXL_EXPORT void cxl_cmd_unref(struct cxl_cmd *cmd)
 {
 	if (!cmd)
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index 321acac57e3e..29f34988e42f 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -84,6 +84,7 @@ global:
 	cxl_bus_get_devname;
 	cxl_bus_get_id;
 	cxl_bus_get_port;
+	cxl_bus_get_ctx;
 	cxl_port_get_first;
 	cxl_port_get_next;
 	cxl_port_get_devname;
@@ -97,6 +98,8 @@ global:
 	cxl_port_is_endpoint;
 	cxl_port_get_bus;
 	cxl_port_get_host;
+	cxl_port_get_bus;
+	cxl_port_hosts_memdev;
 	cxl_endpoint_get_first;
 	cxl_endpoint_get_next;
 	cxl_endpoint_get_devname;
@@ -107,6 +110,8 @@ global:
 	cxl_endpoint_get_port;
 	cxl_endpoint_get_host;
 	cxl_endpoint_get_memdev;
+	cxl_endpoint_get_bus;
 	cxl_memdev_get_endpoint;
 	cxl_memdev_is_enabled;
+	cxl_memdev_get_bus;
 } LIBCXL_1;
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index 790ece86529d..e7b675e5328f 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -39,6 +39,7 @@ int cxl_memdev_get_id(struct cxl_memdev *memdev);
 unsigned long long cxl_memdev_get_serial(struct cxl_memdev *memdev);
 const char *cxl_memdev_get_devname(struct cxl_memdev *memdev);
 const char *cxl_memdev_get_host(struct cxl_memdev *memdev);
+struct cxl_bus *cxl_memdev_get_bus(struct cxl_memdev *memdev);
 int cxl_memdev_get_major(struct cxl_memdev *memdev);
 int cxl_memdev_get_minor(struct cxl_memdev *memdev);
 struct cxl_ctx *cxl_memdev_get_ctx(struct cxl_memdev *memdev);
@@ -68,6 +69,7 @@ const char *cxl_bus_get_provider(struct cxl_bus *bus);
 const char *cxl_bus_get_devname(struct cxl_bus *bus);
 int cxl_bus_get_id(struct cxl_bus *bus);
 struct cxl_port *cxl_bus_get_port(struct cxl_bus *bus);
+struct cxl_ctx *cxl_bus_get_ctx(struct cxl_bus *bus);
 
 #define cxl_bus_foreach(ctx, bus)                                              \
 	for (bus = cxl_bus_get_first(ctx); bus != NULL;                        \
@@ -87,6 +89,7 @@ struct cxl_bus *cxl_port_to_bus(struct cxl_port *port);
 bool cxl_port_is_endpoint(struct cxl_port *port);
 struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
 const char *cxl_port_get_host(struct cxl_port *port);
+bool cxl_port_hosts_memdev(struct cxl_port *port, struct cxl_memdev *memdev);
 
 #define cxl_port_foreach(parent, port)                                         \
 	for (port = cxl_port_get_first(parent); port != NULL;                  \
@@ -102,6 +105,7 @@ int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint);
 struct cxl_port *cxl_endpoint_get_parent(struct cxl_endpoint *endpoint);
 struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint);
 const char *cxl_endpoint_get_host(struct cxl_endpoint *endpoint);
+struct cxl_bus *cxl_endpoint_get_bus(struct cxl_endpoint *endpoint);
 struct cxl_memdev *cxl_endpoint_get_memdev(struct cxl_endpoint *endpoint);
 int cxl_memdev_is_enabled(struct cxl_memdev *memdev);
 


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 24/37] cxl/memdev: Use a local logger for debug
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (22 preceding siblings ...)
  2022-01-24  0:53 ` [ndctl PATCH 23/37] cxl/list: Filter memdev by ancestry Dan Williams
@ 2022-01-24  0:53 ` Dan Williams
  2022-01-24  0:54 ` [ndctl PATCH 25/37] cxl/memdev: Cleanup memdev filter Dan Williams
                   ` (12 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:53 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

The "fail()" macro skips some of the nicer features of the centralized
logger. Add one to supplement the library logger.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 cxl/memdev.c |   48 ++++++++++++++++++++++++------------------------
 1 file changed, 24 insertions(+), 24 deletions(-)

diff --git a/cxl/memdev.c b/cxl/memdev.c
index b9141be62c87..327c2608f179 100644
--- a/cxl/memdev.c
+++ b/cxl/memdev.c
@@ -26,11 +26,7 @@ static struct parameters {
 	bool verbose;
 } param;
 
-#define fail(fmt, ...) \
-do { \
-	fprintf(stderr, "cxl-%s:%s:%d: " fmt, \
-			VERSION, __func__, __LINE__, ##__VA_ARGS__); \
-} while (0)
+static struct log_ctx ml;
 
 #define BASE_OPTIONS() \
 OPT_BOOLEAN('v',"verbose", &param.verbose, "turn on debug")
@@ -79,7 +75,7 @@ static int action_zero(struct cxl_memdev *memdev, struct action_context *actx)
 		size = cxl_memdev_get_label_size(memdev);
 
 	if (cxl_memdev_nvdimm_bridge_active(memdev)) {
-		fprintf(stderr,
+		log_err(&ml,
 			"%s: has active nvdimm bridge, abort label write\n",
 			cxl_memdev_get_devname(memdev));
 		return -EBUSY;
@@ -87,7 +83,7 @@ static int action_zero(struct cxl_memdev *memdev, struct action_context *actx)
 
 	rc = cxl_memdev_zero_label(memdev, size, param.offset);
 	if (rc < 0)
-		fprintf(stderr, "%s: label zeroing failed: %s\n",
+		log_err(&ml, "%s: label zeroing failed: %s\n",
 			cxl_memdev_get_devname(memdev), strerror(-rc));
 
 	return rc;
@@ -100,7 +96,7 @@ static int action_write(struct cxl_memdev *memdev, struct action_context *actx)
 	int rc;
 
 	if (cxl_memdev_nvdimm_bridge_active(memdev)) {
-		fprintf(stderr,
+		log_err(&ml,
 			"%s: has active nvdimm bridge, abort label write\n",
 			cxl_memdev_get_devname(memdev));
 		return -EBUSY;
@@ -114,7 +110,7 @@ static int action_write(struct cxl_memdev *memdev, struct action_context *actx)
 		fseek(actx->f_in, 0L, SEEK_SET);
 
 		if (size > label_size) {
-			fprintf(stderr,
+			log_err(&ml,
 				"File size (%zu) greater than label area size (%zu), aborting\n",
 				size, label_size);
 			return -EINVAL;
@@ -133,7 +129,7 @@ static int action_write(struct cxl_memdev *memdev, struct action_context *actx)
 
 	rc = cxl_memdev_write_label(memdev, buf, size, param.offset);
 	if (rc < 0)
-		fprintf(stderr, "%s: label write failed: %s\n",
+		log_err(&ml, "%s: label write failed: %s\n",
 			cxl_memdev_get_devname(memdev), strerror(-rc));
 
 out:
@@ -158,7 +154,7 @@ static int action_read(struct cxl_memdev *memdev, struct action_context *actx)
 
 	rc = cxl_memdev_read_label(memdev, buf, size, param.offset);
 	if (rc < 0) {
-		fprintf(stderr, "%s: label read failed: %s\n",
+		log_err(&ml, "%s: label read failed: %s\n",
 			cxl_memdev_get_devname(memdev), strerror(-rc));
 		goto out;
 	}
@@ -188,6 +184,7 @@ static int memdev_action(int argc, const char **argv, struct cxl_ctx *ctx,
 	};
 	unsigned long id;
 
+	log_init(&ml, "cxl memdev", "CXL_MEMDEV_LOG");
 	argc = parse_options(argc, argv, options, u, 0);
 
 	if (argc == 0)
@@ -200,8 +197,8 @@ static int memdev_action(int argc, const char **argv, struct cxl_ctx *ctx,
 		}
 
 		if (sscanf(argv[i], "mem%lu", &id) != 1) {
-			fprintf(stderr, "'%s' is not a valid memdev name\n",
-					argv[i]);
+			log_err(&ml, "'%s' is not a valid memdev name\n",
+				argv[i]);
 			err++;
 		}
 	}
@@ -216,8 +213,8 @@ static int memdev_action(int argc, const char **argv, struct cxl_ctx *ctx,
 	else {
 		actx.f_out = fopen(param.outfile, "w+");
 		if (!actx.f_out) {
-			fprintf(stderr, "failed to open: %s: (%s)\n",
-					param.outfile, strerror(errno));
+			log_err(&ml, "failed to open: %s: (%s)\n",
+				param.outfile, strerror(errno));
 			rc = -errno;
 			goto out;
 		}
@@ -228,15 +225,18 @@ static int memdev_action(int argc, const char **argv, struct cxl_ctx *ctx,
 	} else {
 		actx.f_in = fopen(param.infile, "r");
 		if (!actx.f_in) {
-			fprintf(stderr, "failed to open: %s: (%s)\n",
-					param.infile, strerror(errno));
+			log_err(&ml, "failed to open: %s: (%s)\n", param.infile,
+				strerror(errno));
 			rc = -errno;
 			goto out_close_fout;
 		}
 	}
 
-	if (param.verbose)
+	if (param.verbose) {
 		cxl_set_log_priority(ctx, LOG_DEBUG);
+		ml.log_priority = LOG_DEBUG;
+	} else
+		ml.log_priority = LOG_INFO;
 
 	rc = 0;
 	err = 0;
@@ -299,8 +299,8 @@ int cmd_write_labels(int argc, const char **argv, struct cxl_ctx *ctx)
 	int count = memdev_action(argc, argv, ctx, action_write, write_options,
 			"cxl write-labels <memdev> [-i <filename>]");
 
-	fprintf(stderr, "wrote %d mem%s\n", count >= 0 ? count : 0,
-			count > 1 ? "s" : "");
+	log_info(&ml, "wrote %d mem%s\n", count >= 0 ? count : 0,
+		 count > 1 ? "s" : "");
 	return count >= 0 ? 0 : EXIT_FAILURE;
 }
 
@@ -309,8 +309,8 @@ int cmd_read_labels(int argc, const char **argv, struct cxl_ctx *ctx)
 	int count = memdev_action(argc, argv, ctx, action_read, read_options,
 			"cxl read-labels <mem0> [<mem1>..<memN>] [-o <filename>]");
 
-	fprintf(stderr, "read %d mem%s\n", count >= 0 ? count : 0,
-			count > 1 ? "s" : "");
+	log_info(&ml, "read %d mem%s\n", count >= 0 ? count : 0,
+		 count > 1 ? "s" : "");
 	return count >= 0 ? 0 : EXIT_FAILURE;
 }
 
@@ -319,7 +319,7 @@ int cmd_zero_labels(int argc, const char **argv, struct cxl_ctx *ctx)
 	int count = memdev_action(argc, argv, ctx, action_zero, zero_options,
 			"cxl zero-labels <mem0> [<mem1>..<memN>] [<options>]");
 
-	fprintf(stderr, "zeroed %d mem%s\n", count >= 0 ? count : 0,
-			count > 1 ? "s" : "");
+	log_info(&ml, "zeroed %d mem%s\n", count >= 0 ? count : 0,
+		 count > 1 ? "s" : "");
 	return count >= 0 ? 0 : EXIT_FAILURE;
 }


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 25/37] cxl/memdev: Cleanup memdev filter
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (23 preceding siblings ...)
  2022-01-24  0:53 ` [ndctl PATCH 24/37] cxl/memdev: Use a local logger for debug Dan Williams
@ 2022-01-24  0:54 ` Dan Williams
  2022-01-24  0:54 ` [ndctl PATCH 26/37] cxl/memdev: Add serial support for memdev-related commands Dan Williams
                   ` (11 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:54 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

util_cxl_memdev_filter() already handles the difference between 'mem%d',
'%d', and 'all' for the identifier format. Drop the duplicate / incomplete
format checking.

If the checking for bad formats was dropped too then this command could
support "0,1,2" syntax in addition to "0 1 2" like 'cxl list'. However, it is
not clear that's worthwhile since 'list' is ok to be imprecise, but memdev
commands need to be stricter.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 cxl/memdev.c |   18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)

diff --git a/cxl/memdev.c b/cxl/memdev.c
index 327c2608f179..4cca8b8c10be 100644
--- a/cxl/memdev.c
+++ b/cxl/memdev.c
@@ -191,16 +191,16 @@ static int memdev_action(int argc, const char **argv, struct cxl_ctx *ctx,
 		usage_with_options(u, options);
 	for (i = 0; i < argc; i++) {
 		if (strcmp(argv[i], "all") == 0) {
-			argv[0] = "all";
 			argc = 1;
 			break;
 		}
+		if (sscanf(argv[i], "mem%lu", &id) == 1)
+			continue;
+		if (sscanf(argv[i], "%lu", &id) == 1)
+			continue;
 
-		if (sscanf(argv[i], "mem%lu", &id) != 1) {
-			log_err(&ml, "'%s' is not a valid memdev name\n",
-				argv[i]);
-			err++;
-		}
+		log_err(&ml, "'%s' is not a valid memdev name\n", argv[i]);
+		err++;
 	}
 
 	if (err == argc) {
@@ -243,11 +243,7 @@ static int memdev_action(int argc, const char **argv, struct cxl_ctx *ctx,
 	count = 0;
 
 	for (i = 0; i < argc; i++) {
-		if (sscanf(argv[i], "mem%lu", &id) != 1
-				&& strcmp(argv[i], "all") != 0)
-			continue;
-
-		cxl_memdev_foreach (ctx, memdev) {
+		cxl_memdev_foreach(ctx, memdev) {
 			if (!util_cxl_memdev_filter(memdev, argv[i], NULL))
 				continue;
 


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 26/37] cxl/memdev: Add serial support for memdev-related commands
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (24 preceding siblings ...)
  2022-01-24  0:54 ` [ndctl PATCH 25/37] cxl/memdev: Cleanup memdev filter Dan Williams
@ 2022-01-24  0:54 ` Dan Williams
  2022-02-10  1:10   ` Alison Schofield
  2022-01-24  0:54 ` [ndctl PATCH 27/37] cxl/list: Add 'numa_node' to memdev listings Dan Williams
                   ` (10 subsequent siblings)
  36 siblings, 1 reply; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:54 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Allow for a "-s, --serial" option to turn the argument list into serial
identifiers.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/memdev-option.txt |    5 ++++
 Documentation/cxl/meson.build       |    4 ++-
 cxl/memdev.c                        |   45 ++++++++++++++++++++++++++---------
 3 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/Documentation/cxl/memdev-option.txt b/Documentation/cxl/memdev-option.txt
index e7785827ab4c..64348bed1791 100644
--- a/Documentation/cxl/memdev-option.txt
+++ b/Documentation/cxl/memdev-option.txt
@@ -2,3 +2,8 @@
 A 'memX' device name, or a memdev id number. Restrict the operation to
 the specified memdev(s). The keyword 'all' can be specified to indicate
 the lack of any restriction.
+
+-S::
+--serial::
+	Rather an a memdev id number, interpret the <memdev> argument(s)
+	as a list of serial numbers.
diff --git a/Documentation/cxl/meson.build b/Documentation/cxl/meson.build
index 64ce13f59012..0a6346bc47ca 100644
--- a/Documentation/cxl/meson.build
+++ b/Documentation/cxl/meson.build
@@ -19,7 +19,9 @@ else
 endif
 
 filedeps = [
-        '../copyright.txt',
+  '../copyright.txt',
+  'memdev-option.txt',
+  'labels-options.txt',
 ]
 
 cxl_manpages = [
diff --git a/cxl/memdev.c b/cxl/memdev.c
index 4cca8b8c10be..ef5343ad892b 100644
--- a/cxl/memdev.c
+++ b/cxl/memdev.c
@@ -24,12 +24,14 @@ static struct parameters {
 	unsigned len;
 	unsigned offset;
 	bool verbose;
+	bool serial;
 } param;
 
 static struct log_ctx ml;
 
 #define BASE_OPTIONS() \
-OPT_BOOLEAN('v',"verbose", &param.verbose, "turn on debug")
+OPT_BOOLEAN('v',"verbose", &param.verbose, "turn on debug"), \
+OPT_BOOLEAN('S', "serial", &param.serial, "user serials numbers to id memdevs")
 
 #define READ_OPTIONS() \
 OPT_STRING('o', "output", &param.outfile, "output-file", \
@@ -172,8 +174,9 @@ out:
 }
 
 static int memdev_action(int argc, const char **argv, struct cxl_ctx *ctx,
-		int (*action)(struct cxl_memdev *memdev, struct action_context *actx),
-		const struct option *options, const char *usage)
+			 int (*action)(struct cxl_memdev *memdev,
+				       struct action_context *actx),
+			 const struct option *options, const char *usage)
 {
 	struct cxl_memdev *memdev, *single = NULL;
 	struct action_context actx = { 0 };
@@ -190,16 +193,25 @@ static int memdev_action(int argc, const char **argv, struct cxl_ctx *ctx,
 	if (argc == 0)
 		usage_with_options(u, options);
 	for (i = 0; i < argc; i++) {
-		if (strcmp(argv[i], "all") == 0) {
-			argc = 1;
-			break;
+		if (param.serial) {
+			char *end;
+
+			strtoull(argv[i], &end, 0);
+			if (end[0] == 0)
+				continue;
+		} else {
+			if (strcmp(argv[i], "all") == 0) {
+				argc = 1;
+				break;
+			}
+			if (sscanf(argv[i], "mem%lu", &id) == 1)
+				continue;
+			if (sscanf(argv[i], "%lu", &id) == 1)
+				continue;
 		}
-		if (sscanf(argv[i], "mem%lu", &id) == 1)
-			continue;
-		if (sscanf(argv[i], "%lu", &id) == 1)
-			continue;
 
-		log_err(&ml, "'%s' is not a valid memdev name\n", argv[i]);
+		log_err(&ml, "'%s' is not a valid memdev %s\n", argv[i],
+			param.serial ? "serial number" : "name");
 		err++;
 	}
 
@@ -244,7 +256,16 @@ static int memdev_action(int argc, const char **argv, struct cxl_ctx *ctx,
 
 	for (i = 0; i < argc; i++) {
 		cxl_memdev_foreach(ctx, memdev) {
-			if (!util_cxl_memdev_filter(memdev, argv[i], NULL))
+			const char *memdev_filter = NULL;
+			const char *serial_filter = NULL;
+
+			if (param.serial)
+				serial_filter = argv[i];
+			else
+				memdev_filter = argv[i];
+
+			if (!util_cxl_memdev_filter(memdev, memdev_filter,
+						    serial_filter))
 				continue;
 
 			if (action == action_write) {


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 27/37] cxl/list: Add 'numa_node' to memdev listings
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (25 preceding siblings ...)
  2022-01-24  0:54 ` [ndctl PATCH 26/37] cxl/memdev: Add serial support for memdev-related commands Dan Williams
@ 2022-01-24  0:54 ` Dan Williams
  2022-01-24  0:54 ` [ndctl PATCH 28/37] util: Implement common bind/unbind helpers Dan Williams
                   ` (9 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:54 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

If the kernel exports a valid numa_node, >= 0, include it in memdev objects
listings.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/lib/libcxl.txt |    4 ++++
 cxl/json.c                       |    8 ++++++++
 cxl/lib/libcxl.c                 |   11 +++++++++++
 cxl/lib/libcxl.sym               |    1 +
 cxl/lib/private.h                |    1 +
 cxl/libcxl.h                     |    1 +
 6 files changed, 26 insertions(+)

diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index b0253d7be07c..de88d192523a 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -71,6 +71,7 @@ unsigned long long cxl_memdev_get_ram_size(struct cxl_memdev *memdev);
 const char *cxl_memdev_get_firmware_verison(struct cxl_memdev *memdev);
 size_t cxl_memdev_get_label_size(struct cxl_memdev *memdev);
 int cxl_memdev_nvdimm_bridge_active(struct cxl_memdev *memdev);
+int cxl_memdev_get_numa_node(struct cxl_memdev *memdev);
 ----
 
 A memdev is given a kernel device name of the form "mem%d" where an id
@@ -89,6 +90,9 @@ The 'pmem_size' and 'ram_size' attributes return the current
 provisioning of DPA (Device Physical Address / local capacity) in the
 device.
 
+cxl_memdev_get_numa_node() returns the affinitized CPU node number if
+available or -1 otherwise.
+
 === MEMDEV: Commands
 ----
 struct cxl_cmd *cxl_cmd_new_raw(struct cxl_memdev *memdev, int opcode);
diff --git a/cxl/json.c b/cxl/json.c
index b809332b8d51..51918d65f671 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -190,6 +190,7 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
 	const char *devname = cxl_memdev_get_devname(memdev);
 	struct json_object *jdev, *jobj;
 	unsigned long long serial;
+	int numa_node;
 
 	jdev = json_object_new_object();
 	if (!jdev)
@@ -220,6 +221,13 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
 			json_object_object_add(jdev, "serial", jobj);
 	}
 
+	numa_node = cxl_memdev_get_numa_node(memdev);
+	if (numa_node >= 0) {
+		jobj = json_object_new_int(numa_node);
+		if (jobj)
+			json_object_object_add(jdev, "numa_node", jobj);
+	}
+
 	jobj = json_object_new_string(cxl_memdev_get_host(memdev));
 	if (jobj)
 		json_object_object_add(jdev, "host", jobj);
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 0065f6baeead..14c7db88ff5e 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -348,6 +348,12 @@ static void *add_cxl_memdev(void *parent, int id, const char *cxlmem_base)
 	else
 		memdev->serial = strtoull(buf, NULL, 0);
 
+	sprintf(path, "%s/numa_node", cxlmem_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		memdev->numa_node = -1;
+	else
+		memdev->numa_node = strtol(buf, NULL, 0);
+
 	memdev->dev_path = strdup(cxlmem_base);
 	if (!memdev->dev_path)
 		goto err_read;
@@ -445,6 +451,11 @@ CXL_EXPORT unsigned long long cxl_memdev_get_serial(struct cxl_memdev *memdev)
 	return memdev->serial;
 }
 
+CXL_EXPORT int cxl_memdev_get_numa_node(struct cxl_memdev *memdev)
+{
+	return memdev->numa_node;
+}
+
 CXL_EXPORT const char *cxl_memdev_get_devname(struct cxl_memdev *memdev)
 {
 	return devpath_to_devname(memdev->dev_path);
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index 29f34988e42f..b13a2d6d25d1 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -77,6 +77,7 @@ local:
 LIBCXL_2 {
 global:
 	cxl_memdev_get_serial;
+	cxl_memdev_get_numa_node;
 	cxl_memdev_get_host;
 	cxl_bus_get_first;
 	cxl_bus_get_next;
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
index b097bdfbfd6b..c00bb3600138 100644
--- a/cxl/lib/private.h
+++ b/cxl/lib/private.h
@@ -20,6 +20,7 @@ struct cxl_pmem {
 struct cxl_endpoint;
 struct cxl_memdev {
 	int id, major, minor;
+	int numa_node;
 	void *dev_buf;
 	size_t buf_len;
 	char *host;
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index e7b675e5328f..be656ed0d90d 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -37,6 +37,7 @@ struct cxl_memdev *cxl_memdev_get_first(struct cxl_ctx *ctx);
 struct cxl_memdev *cxl_memdev_get_next(struct cxl_memdev *memdev);
 int cxl_memdev_get_id(struct cxl_memdev *memdev);
 unsigned long long cxl_memdev_get_serial(struct cxl_memdev *memdev);
+int cxl_memdev_get_numa_node(struct cxl_memdev *memdev);
 const char *cxl_memdev_get_devname(struct cxl_memdev *memdev);
 const char *cxl_memdev_get_host(struct cxl_memdev *memdev);
 struct cxl_bus *cxl_memdev_get_bus(struct cxl_memdev *memdev);


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 28/37] util: Implement common bind/unbind helpers
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (26 preceding siblings ...)
  2022-01-24  0:54 ` [ndctl PATCH 27/37] cxl/list: Add 'numa_node' to memdev listings Dan Williams
@ 2022-01-24  0:54 ` Dan Williams
  2022-01-24  0:54 ` [ndctl PATCH 29/37] cxl/memdev: Enable / disable support Dan Williams
                   ` (8 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:54 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Refactor ndctl_{bind,unbind}() into util_{bind,unbind}() for libcxl to
reuse.

daxctl can not join the party for now as it needs to play games with
'new_id'.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 ndctl/lib/libndctl.c |  103 ++++++--------------------------------------------
 util/sysfs.c         |   76 +++++++++++++++++++++++++++++++++++++
 util/sysfs.h         |    8 ++++
 3 files changed, 96 insertions(+), 91 deletions(-)

diff --git a/ndctl/lib/libndctl.c b/ndctl/lib/libndctl.c
index 1374ad9e504f..98d184ba1bf8 100644
--- a/ndctl/lib/libndctl.c
+++ b/ndctl/lib/libndctl.c
@@ -1665,10 +1665,6 @@ static enum ndctl_fwa_result fwa_result_to_result(const char *result)
 	return NDCTL_FWA_RESULT_INVALID;
 }
 
-static int ndctl_bind(struct ndctl_ctx *ctx, struct kmod_module *module,
-		const char *devname);
-static int ndctl_unbind(struct ndctl_ctx *ctx, const char *devpath);
-
 static int populate_dimm_attributes(struct ndctl_dimm *dimm,
 				    const char *dimm_base,
 				    const char *bus_prefix)
@@ -2305,7 +2301,7 @@ NDCTL_EXPORT int ndctl_dimm_disable(struct ndctl_dimm *dimm)
 	if (!ndctl_dimm_is_enabled(dimm))
 		return 0;
 
-	ndctl_unbind(ctx, dimm->dimm_path);
+	util_unbind(dimm->dimm_path, ctx);
 
 	if (ndctl_dimm_is_enabled(dimm)) {
 		err(ctx, "%s: failed to disable\n", devname);
@@ -2324,7 +2320,7 @@ NDCTL_EXPORT int ndctl_dimm_enable(struct ndctl_dimm *dimm)
 	if (ndctl_dimm_is_enabled(dimm))
 		return 0;
 
-	ndctl_bind(ctx, dimm->module, devname);
+	util_bind(devname, dimm->module, "nd", ctx);
 
 	if (!ndctl_dimm_is_enabled(dimm)) {
 		err(ctx, "%s: failed to enable\n", devname);
@@ -3573,7 +3569,7 @@ NDCTL_EXPORT int ndctl_region_enable(struct ndctl_region *region)
 	if (ndctl_region_is_enabled(region))
 		return 0;
 
-	ndctl_bind(ctx, region->module, devname);
+	util_bind(devname, region->module, "nd", ctx);
 
 	if (!ndctl_region_is_enabled(region)) {
 		err(ctx, "%s: failed to enable\n", devname);
@@ -3610,7 +3606,7 @@ static int ndctl_region_disable(struct ndctl_region *region, int cleanup)
 	if (!ndctl_region_is_enabled(region))
 		return 0;
 
-	ndctl_unbind(ctx, region->region_path);
+	util_unbind(region->region_path, ctx);
 
 	if (ndctl_region_is_enabled(region)) {
 		err(ctx, "%s: failed to disable\n", devname);
@@ -4373,81 +4369,6 @@ NDCTL_EXPORT struct badblock *ndctl_namespace_get_first_badblock(
 	return badblocks_iter_first(&ndns->bb_iter, ctx, path);
 }
 
-static int ndctl_bind(struct ndctl_ctx *ctx, struct kmod_module *module,
-		const char *devname)
-{
-	DIR *dir;
-	int rc = 0;
-	char path[200];
-	struct dirent *de;
-	const int len = sizeof(path);
-
-	if (!devname) {
-		err(ctx, "missing devname\n");
-		return -EINVAL;
-	}
-
-	if (module) {
-		rc = kmod_module_probe_insert_module(module,
-				KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL,
-				NULL);
-		if (rc < 0) {
-			err(ctx, "%s: insert failure: %d\n", __func__, rc);
-			return rc;
-		}
-	}
-
-	if (snprintf(path, len, "/sys/bus/nd/drivers") >= len) {
-		err(ctx, "%s: buffer too small!\n", devname);
-		return -ENXIO;
-	}
-
-	dir = opendir(path);
-	if (!dir) {
-		err(ctx, "%s: opendir(\"%s\") failed\n", devname, path);
-		return -ENXIO;
-	}
-
-	while ((de = readdir(dir)) != NULL) {
-		char *drv_path;
-
-		if (de->d_ino == 0)
-			continue;
-		if (de->d_name[0] == '.')
-			continue;
-		if (asprintf(&drv_path, "%s/%s/bind", path, de->d_name) < 0) {
-			err(ctx, "%s: path allocation failure\n", devname);
-			continue;
-		}
-
-		rc = sysfs_write_attr_quiet(ctx, drv_path, devname);
-		free(drv_path);
-		if (rc == 0)
-			break;
-	}
-	closedir(dir);
-
-	if (rc) {
-		dbg(ctx, "%s: bind failed\n", devname);
-		return -ENXIO;
-	}
-	return 0;
-}
-
-static int ndctl_unbind(struct ndctl_ctx *ctx, const char *devpath)
-{
-	const char *devname = devpath_to_devname(devpath);
-	char path[200];
-	const int len = sizeof(path);
-
-	if (snprintf(path, len, "%s/driver/unbind", devpath) >= len) {
-		err(ctx, "%s: buffer too small!\n", devname);
-		return -ENXIO;
-	}
-
-	return sysfs_write_attr(ctx, path, devname);
-}
-
 static void *add_btt(void *parent, int id, const char *btt_base);
 static void *add_pfn(void *parent, int id, const char *pfn_base);
 static void *add_dax(void *parent, int id, const char *dax_base);
@@ -4533,7 +4454,7 @@ NDCTL_EXPORT int ndctl_namespace_enable(struct ndctl_namespace *ndns)
 	if (ndctl_namespace_is_enabled(ndns))
 		return 0;
 
-	rc = ndctl_bind(ctx, ndns->module, devname);
+	rc = util_bind(devname, ndns->module, "nd", ctx);
 
 	/*
 	 * Rescan now as successfully enabling a namespace device leads
@@ -4581,7 +4502,7 @@ NDCTL_EXPORT int ndctl_namespace_disable(struct ndctl_namespace *ndns)
 	if (!ndctl_namespace_is_enabled(ndns))
 		return 0;
 
-	ndctl_unbind(ctx, ndns->ndns_path);
+	util_unbind(ndns->ndns_path, ctx);
 
 	if (ndctl_namespace_is_enabled(ndns)) {
 		err(ctx, "%s: failed to disable\n", devname);
@@ -5420,7 +5341,7 @@ NDCTL_EXPORT int ndctl_btt_enable(struct ndctl_btt *btt)
 	if (ndctl_btt_is_enabled(btt))
 		return 0;
 
-	ndctl_bind(ctx, btt->module, devname);
+	util_bind(devname, btt->module, "nd", ctx);
 
 	if (!ndctl_btt_is_enabled(btt)) {
 		err(ctx, "%s: failed to enable\n", devname);
@@ -5457,7 +5378,7 @@ NDCTL_EXPORT int ndctl_btt_delete(struct ndctl_btt *btt)
 		return 0;
 	}
 
-	ndctl_unbind(ctx, btt->btt_path);
+	util_unbind(btt->btt_path, ctx);
 
 	rc = ndctl_btt_set_namespace(btt, NULL);
 	if (rc) {
@@ -5908,7 +5829,7 @@ NDCTL_EXPORT int ndctl_pfn_enable(struct ndctl_pfn *pfn)
 	if (ndctl_pfn_is_enabled(pfn))
 		return 0;
 
-	ndctl_bind(ctx, pfn->module, devname);
+	util_bind(devname, pfn->module, "nd", ctx);
 
 	if (!ndctl_pfn_is_enabled(pfn)) {
 		err(ctx, "%s: failed to enable\n", devname);
@@ -5945,7 +5866,7 @@ NDCTL_EXPORT int ndctl_pfn_delete(struct ndctl_pfn *pfn)
 		return 0;
 	}
 
-	ndctl_unbind(ctx, pfn->pfn_path);
+	util_unbind(pfn->pfn_path, ctx);
 
 	rc = ndctl_pfn_set_namespace(pfn, NULL);
 	if (rc) {
@@ -6101,7 +6022,7 @@ NDCTL_EXPORT int ndctl_dax_enable(struct ndctl_dax *dax)
 	if (ndctl_dax_is_enabled(dax))
 		return 0;
 
-	ndctl_bind(ctx, pfn->module, devname);
+	util_bind(devname, pfn->module, "nd", ctx);
 
 	if (!ndctl_dax_is_enabled(dax)) {
 		err(ctx, "%s: failed to enable\n", devname);
@@ -6132,7 +6053,7 @@ NDCTL_EXPORT int ndctl_dax_delete(struct ndctl_dax *dax)
 		return 0;
 	}
 
-	ndctl_unbind(ctx, pfn->pfn_path);
+	util_unbind(pfn->pfn_path, ctx);
 
 	rc = ndctl_dax_set_namespace(dax, NULL);
 	if (rc) {
diff --git a/util/sysfs.c b/util/sysfs.c
index 23330cb29002..968683b19f4e 100644
--- a/util/sysfs.c
+++ b/util/sysfs.c
@@ -145,3 +145,79 @@ struct kmod_module *__util_modalias_to_module(struct kmod_ctx *kmod_ctx,
 
 	return mod;
 }
+
+int __util_bind(const char *devname, struct kmod_module *module,
+		const char *bus, struct log_ctx *ctx)
+{
+	DIR *dir;
+	int rc = 0;
+	char path[200];
+	struct dirent *de;
+	const int len = sizeof(path);
+
+	if (!devname) {
+		log_err(ctx, "missing devname\n");
+		return -EINVAL;
+	}
+
+	if (module) {
+		rc = kmod_module_probe_insert_module(module,
+						     KMOD_PROBE_APPLY_BLACKLIST,
+						     NULL, NULL, NULL, NULL);
+		if (rc < 0) {
+			log_err(ctx, "%s: insert failure: %d\n", __func__, rc);
+			return rc;
+		}
+	}
+
+	if (snprintf(path, len, "/sys/bus/%s/drivers", bus) >= len) {
+		log_err(ctx, "%s: buffer too small!\n", devname);
+		return -ENXIO;
+	}
+
+	dir = opendir(path);
+	if (!dir) {
+		log_err(ctx, "%s: opendir(\"%s\") failed\n", devname, path);
+		return -ENXIO;
+	}
+
+	while ((de = readdir(dir)) != NULL) {
+		char *drv_path;
+
+		if (de->d_ino == 0)
+			continue;
+		if (de->d_name[0] == '.')
+			continue;
+
+		if (asprintf(&drv_path, "%s/%s/bind", path, de->d_name) < 0) {
+			log_err(ctx, "%s: path allocation failure\n", devname);
+			continue;
+		}
+
+		rc = __sysfs_write_attr_quiet(ctx, drv_path, devname);
+		free(drv_path);
+		if (rc == 0)
+			break;
+	}
+	closedir(dir);
+
+	if (rc) {
+		log_dbg(ctx, "%s: bind failed\n", devname);
+		return -ENXIO;
+	}
+	return 0;
+}
+
+int __util_unbind(const char *devpath, struct log_ctx *ctx)
+{
+	const char *devname = devpath_to_devname(devpath);
+	char path[200];
+	const int len = sizeof(path);
+
+	if (snprintf(path, len, "%s/driver/unbind", devpath) >= len) {
+		log_err(ctx, "%s: buffer too small!\n", devname);
+		return -ENXIO;
+	}
+
+	return __sysfs_write_attr(ctx, path, devname);
+}
diff --git a/util/sysfs.h b/util/sysfs.h
index bdee4f5c291d..4c95c70558ba 100644
--- a/util/sysfs.h
+++ b/util/sysfs.h
@@ -35,4 +35,12 @@ struct kmod_module *__util_modalias_to_module(struct kmod_ctx *kmod_ctx,
 					      struct log_ctx *log);
 #define util_modalias_to_module(ctx, buf)                                      \
 	__util_modalias_to_module((ctx)->kmod_ctx, buf, &(ctx)->ctx)
+
+int __util_bind(const char *devname, struct kmod_module *module, const char *bus,
+	      struct log_ctx *ctx);
+#define util_bind(n, m, b, c) __util_bind(n, m, b, &(c)->ctx)
+
+int __util_unbind(const char *devpath, struct log_ctx *ctx);
+#define util_unbind(p, c) __util_unbind(p, &(c)->ctx)
+
 #endif /* __UTIL_SYSFS_H__ */


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 29/37] cxl/memdev: Enable / disable support
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (27 preceding siblings ...)
  2022-01-24  0:54 ` [ndctl PATCH 28/37] util: Implement common bind/unbind helpers Dan Williams
@ 2022-01-24  0:54 ` Dan Williams
  2022-01-24  0:54 ` [ndctl PATCH 30/37] cxl/list: Add decoder support Dan Williams
                   ` (7 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:54 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Introduce the 'cxl {enable,disable}-memdev' commands. When a memdev is
disabled the ports in the topology may be unregistered. CXL memory regions
require each endpoint in the interleave to attach to the cxl_mem driver
before regions are activated.

Note that this starts out with the deliberate bug that it has false
positive detection of active memdevs. The fix for that bug requires kernel
support to detect the device's active participation in a region, until then
require all disable attempts to specify the --force override. This way
there are never any releases of cxl-cli that lack disable-memdev safety.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/cxl-disable-memdev.txt |   37 +++++++++++++++++++
 Documentation/cxl/cxl-enable-memdev.txt  |   34 +++++++++++++++++
 Documentation/cxl/lib/libcxl.txt         |   23 ++++++++++++
 Documentation/cxl/meson.build            |    2 +
 cxl/builtin.h                            |    2 +
 cxl/cxl.c                                |    2 +
 cxl/lib/libcxl.c                         |   58 +++++++++++++++++++++++++++++
 cxl/lib/libcxl.sym                       |    2 +
 cxl/libcxl.h                             |    2 +
 cxl/memdev.c                             |   60 ++++++++++++++++++++++++++++++
 10 files changed, 221 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/cxl/cxl-disable-memdev.txt
 create mode 100644 Documentation/cxl/cxl-enable-memdev.txt

diff --git a/Documentation/cxl/cxl-disable-memdev.txt b/Documentation/cxl/cxl-disable-memdev.txt
new file mode 100644
index 000000000000..edd538578b14
--- /dev/null
+++ b/Documentation/cxl/cxl-disable-memdev.txt
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+
+cxl-disable-memdev(1)
+=====================
+
+NAME
+----
+cxl-disable-memdev - deactivate / hot-remove a given CXL memdev
+
+SYNOPSIS
+--------
+[verse]
+'cxl disable-memdev' <mem0> [<mem1>..<memN>] [<options>]
+
+
+OPTIONS
+-------
+<memory device(s)>::
+include::memdev-option.txt[]
+
+-f::
+--force::
+	DANGEROUS: Override the safety measure that blocks attempts to disable
+	a device if the tool determines the memdev is in active usage. Recall
+	that CXL memory ranges might have been established by platform
+	firmware and disabling an active device is akin to force removing
+	memory from a running system.
+
+-v::
+	Turn on verbose debug messages in the library (if libcxl was built with
+	logging and debug enabled).
+
+include::../copyright.txt[]
+
+SEE ALSO
+--------
+linkcxl:cxl-enable-memdev[1]
diff --git a/Documentation/cxl/cxl-enable-memdev.txt b/Documentation/cxl/cxl-enable-memdev.txt
new file mode 100644
index 000000000000..088d5e0a4a3c
--- /dev/null
+++ b/Documentation/cxl/cxl-enable-memdev.txt
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+
+cxl-enable-memdev(1)
+====================
+
+NAME
+----
+cxl-enable-memdev - activate / hot-add a given CXL memdev
+
+SYNOPSIS
+--------
+[verse]
+'cxl enable-memdev' <mem0> [<mem1>..<memN>] [<options>]
+
+A memdev typically autoenables at initial device discovery. However, if
+it was manually disabled this command can trigger the kernel to activate
+it again. This involves detecting the state of the HDM (Host Managed
+Device Memory) Decoders and validating that CXL.mem is enabled for each
+port in the device's hierarchy.
+
+OPTIONS
+-------
+<memory device(s)>::
+include::memdev-option.txt[]
+
+-v::
+	Turn on verbose debug messages in the library (if libcxl was built with
+	logging and debug enabled).
+
+include::../copyright.txt[]
+
+SEE ALSO
+--------
+linkcxl:cxl-disable-memdev[1]
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index de88d192523a..49edb71b1f97 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -93,6 +93,29 @@ device.
 cxl_memdev_get_numa_node() returns the affinitized CPU node number if
 available or -1 otherwise.
 
+=== MEMDEV: Control
+----
+int cxl_memdev_disable_invalidate(struct cxl_memdev *memdev);
+int cxl_memdev_enable(struct cxl_memdev *memdev);
+----
+When a memory device is disabled it unregisters its associated endpoints
+and potentially intervening switch ports if there are no other memdevs
+pinning that port active. That means that any existing port objects that
+the library has previously returned are in valid and need to be re-read.
+Callers must be careful to re-retrieve port objects after
+cxl_memdev_disable_invalidate(). Any usage of a previously obtained port
+object after a cxl_memdev_disable_invalidate() call is a use-after-free
+programming error. It follows that after cxl_memdev_enable() new ports
+may appear in the topology that were not previously enumerable.
+
+NOTE: cxl_memdev_disable_invalidate() will force disable the memdev
+regardless of whether the memory provided by the device is in active use
+by the operating system. Callers take responisbility for assuring that
+it is safe to disable the memory device. Otherwise, this call can be as
+destructive as ripping a DIMM out of a running system. Like all other
+libcxl calls that mutate the system state or divulge security sensitive
+information this call requires root / CAP_SYS_ADMIN.
+
 === MEMDEV: Commands
 ----
 struct cxl_cmd *cxl_cmd_new_raw(struct cxl_memdev *memdev, int opcode);
diff --git a/Documentation/cxl/meson.build b/Documentation/cxl/meson.build
index 0a6346bc47ca..7618c97278b4 100644
--- a/Documentation/cxl/meson.build
+++ b/Documentation/cxl/meson.build
@@ -30,6 +30,8 @@ cxl_manpages = [
   'cxl-read-labels.txt',
   'cxl-write-labels.txt',
   'cxl-zero-labels.txt',
+  'cxl-enable-memdev.txt',
+  'cxl-disable-memdev.txt',
 ]
 
 foreach man : cxl_manpages
diff --git a/cxl/builtin.h b/cxl/builtin.h
index 78eca6e2faf9..621c85c2ffcc 100644
--- a/cxl/builtin.h
+++ b/cxl/builtin.h
@@ -10,4 +10,6 @@ int cmd_read_labels(int argc, const char **argv, struct cxl_ctx *ctx);
 int cmd_zero_labels(int argc, const char **argv, struct cxl_ctx *ctx);
 int cmd_init_labels(int argc, const char **argv, struct cxl_ctx *ctx);
 int cmd_check_labels(int argc, const char **argv, struct cxl_ctx *ctx);
+int cmd_disable_memdev(int argc, const char **argv, struct cxl_ctx *ctx);
+int cmd_enable_memdev(int argc, const char **argv, struct cxl_ctx *ctx);
 #endif /* _CXL_BUILTIN_H_ */
diff --git a/cxl/cxl.c b/cxl/cxl.c
index 4b1661d8d4c1..78d2e9aec0bd 100644
--- a/cxl/cxl.c
+++ b/cxl/cxl.c
@@ -64,6 +64,8 @@ static struct cmd_struct commands[] = {
 	{ "zero-labels", .c_fn = cmd_zero_labels },
 	{ "read-labels", .c_fn = cmd_read_labels },
 	{ "write-labels", .c_fn = cmd_write_labels },
+	{ "disable-memdev", .c_fn = cmd_disable_memdev },
+	{ "enable-memdev", .c_fn = cmd_enable_memdev },
 };
 
 int main(int argc, const char **argv)
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 14c7db88ff5e..2fdaf71bb837 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -500,6 +500,64 @@ CXL_EXPORT const char *cxl_memdev_get_firmware_verison(struct cxl_memdev *memdev
 	return memdev->firmware_version;
 }
 
+CXL_EXPORT int cxl_memdev_disable_invalidate(struct cxl_memdev *memdev)
+{
+	struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev);
+	const char *devname = cxl_memdev_get_devname(memdev);
+	struct cxl_port *port, *_p, *bus_port;
+	struct cxl_bus *bus;
+
+	if (!cxl_memdev_is_enabled(memdev))
+		return 0;
+
+	bus = cxl_memdev_get_bus(memdev);
+	if (!bus) {
+		err(ctx, "%s: failed to invalidate\n", devname);
+		return -ENXIO;
+	}
+
+	util_unbind(memdev->dev_path, ctx);
+
+	if (cxl_memdev_is_enabled(memdev)) {
+		err(ctx, "%s: failed to disable\n", devname);
+		return -EBUSY;
+	}
+
+	/*
+	 * The state of all ports is now indeterminate, delete them all
+	 * and start over.
+	 */
+	bus_port = cxl_bus_get_port(bus);
+	list_for_each_safe(&bus_port->child_ports, port, _p, list)
+		free_port(port, &bus_port->child_ports);
+	bus_port->ports_init = 0;
+	memdev->endpoint = NULL;
+
+	dbg(ctx, "%s: disabled\n", devname);
+
+	return 0;
+}
+
+CXL_EXPORT int cxl_memdev_enable(struct cxl_memdev *memdev)
+{
+	struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev);
+	const char *devname = cxl_memdev_get_devname(memdev);
+
+	if (cxl_memdev_is_enabled(memdev))
+		return 0;
+
+	util_bind(devname, memdev->module, "cxl", ctx);
+
+	if (!cxl_memdev_is_enabled(memdev)) {
+		err(ctx, "%s: failed to enable\n", devname);
+		return -ENXIO;
+	}
+
+	dbg(ctx, "%s: enabled\n", devname);
+
+	return 0;
+}
+
 static struct cxl_endpoint *cxl_port_find_endpoint(struct cxl_port *parent_port,
 						   struct cxl_memdev *memdev)
 {
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index b13a2d6d25d1..f235e99e3bf0 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -115,4 +115,6 @@ global:
 	cxl_memdev_get_endpoint;
 	cxl_memdev_is_enabled;
 	cxl_memdev_get_bus;
+	cxl_memdev_disable_invalidate;
+	cxl_memdev_enable;
 } LIBCXL_1;
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index be656ed0d90d..53f68dde2770 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -48,6 +48,8 @@ unsigned long long cxl_memdev_get_pmem_size(struct cxl_memdev *memdev);
 unsigned long long cxl_memdev_get_ram_size(struct cxl_memdev *memdev);
 const char *cxl_memdev_get_firmware_verison(struct cxl_memdev *memdev);
 size_t cxl_memdev_get_label_size(struct cxl_memdev *memdev);
+int cxl_memdev_disable_invalidate(struct cxl_memdev *memdev);
+int cxl_memdev_enable(struct cxl_memdev *memdev);
 struct cxl_endpoint;
 struct cxl_endpoint *cxl_memdev_get_endpoint(struct cxl_memdev *memdev);
 int cxl_memdev_nvdimm_bridge_active(struct cxl_memdev *memdev);
diff --git a/cxl/memdev.c b/cxl/memdev.c
index ef5343ad892b..90b33e1b4195 100644
--- a/cxl/memdev.c
+++ b/cxl/memdev.c
@@ -25,13 +25,14 @@ static struct parameters {
 	unsigned offset;
 	bool verbose;
 	bool serial;
+	bool force;
 } param;
 
 static struct log_ctx ml;
 
 #define BASE_OPTIONS() \
 OPT_BOOLEAN('v',"verbose", &param.verbose, "turn on debug"), \
-OPT_BOOLEAN('S', "serial", &param.serial, "user serials numbers to id memdevs")
+OPT_BOOLEAN('S', "serial", &param.serial, "use serial numbers to id memdevs")
 
 #define READ_OPTIONS() \
 OPT_STRING('o', "output", &param.outfile, "output-file", \
@@ -46,6 +47,10 @@ OPT_UINTEGER('s', "size", &param.len, "number of label bytes to operate"), \
 OPT_UINTEGER('O', "offset", &param.offset, \
 	"offset into the label area to start operation")
 
+#define DISABLE_OPTIONS()                                              \
+OPT_BOOLEAN('f', "force", &param.force,                                \
+	    "DANGEROUS: override active memdev safety checks")
+
 static const struct option read_options[] = {
 	BASE_OPTIONS(),
 	LABEL_OPTIONS(),
@@ -66,6 +71,37 @@ static const struct option zero_options[] = {
 	OPT_END(),
 };
 
+static const struct option disable_options[] = {
+	BASE_OPTIONS(),
+	DISABLE_OPTIONS(),
+	OPT_END(),
+};
+
+static const struct option enable_options[] = {
+	BASE_OPTIONS(),
+	OPT_END(),
+};
+
+static int action_disable(struct cxl_memdev *memdev, struct action_context *actx)
+{
+	if (!cxl_memdev_is_enabled(memdev))
+		return 0;
+
+	if (!param.force) {
+		/* TODO: actually detect rather than assume active */
+		log_err(&ml, "%s is part of an active region\n",
+			cxl_memdev_get_devname(memdev));
+		return -EBUSY;
+	}
+
+	return cxl_memdev_disable_invalidate(memdev);
+}
+
+static int action_enable(struct cxl_memdev *memdev, struct action_context *actx)
+{
+	return cxl_memdev_enable(memdev);
+}
+
 static int action_zero(struct cxl_memdev *memdev, struct action_context *actx)
 {
 	size_t size;
@@ -340,3 +376,25 @@ int cmd_zero_labels(int argc, const char **argv, struct cxl_ctx *ctx)
 		 count > 1 ? "s" : "");
 	return count >= 0 ? 0 : EXIT_FAILURE;
 }
+
+int cmd_disable_memdev(int argc, const char **argv, struct cxl_ctx *ctx)
+{
+	int count = memdev_action(
+		argc, argv, ctx, action_disable, disable_options,
+		"cxl disable-memdev <mem0> [<mem1>..<memN>] [<options>]");
+
+	log_info(&ml, "disabled %d mem%s\n", count >= 0 ? count : 0,
+		 count > 1 ? "s" : "");
+	return count >= 0 ? 0 : EXIT_FAILURE;
+}
+
+int cmd_enable_memdev(int argc, const char **argv, struct cxl_ctx *ctx)
+{
+	int count = memdev_action(
+		argc, argv, ctx, action_enable, enable_options,
+		"cxl enable-memdev <mem0> [<mem1>..<memN>] [<options>]");
+
+	log_info(&ml, "enabled %d mem%s\n", count >= 0 ? count : 0,
+		 count > 1 ? "s" : "");
+	return count >= 0 ? 0 : EXIT_FAILURE;
+}


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 30/37] cxl/list: Add decoder support
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (28 preceding siblings ...)
  2022-01-24  0:54 ` [ndctl PATCH 29/37] cxl/memdev: Enable / disable support Dan Williams
@ 2022-01-24  0:54 ` Dan Williams
  2022-01-24  0:54 ` [ndctl PATCH 31/37] cxl/list: Extend decoder objects with target information Dan Williams
                   ` (6 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:54 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Decoder objects exist at each level of a CXL port topology and map a
physical address range a set of interleaved ports at each level of the
hierarchy. Typically end users mostly care about the root-level decoders
which enumerate the potential CXL address space in the system, and the
endpoint decoders that indicate which address ranges a given device
contributes resources. Intermediate switch-level decoders are typically
only useful for debugging decode problems.

$ cxl list -D -d 3.1 -u
{
  "decoder":"decoder3.1",
  "resource":"0x8030000000",
  "size":"512.00 MiB (536.87 MB)",
  "volatile_capable":true
}

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 .clang-format                    |    1 
 Documentation/cxl/cxl-list.txt   |   18 +++
 Documentation/cxl/lib/libcxl.txt |   70 +++++++++++
 cxl/filter.c                     |  194 ++++++++++++++++++++++++++++++-
 cxl/filter.h                     |    2 
 cxl/json.c                       |   62 ++++++++++
 cxl/json.h                       |    2 
 cxl/lib/libcxl.c                 |  239 +++++++++++++++++++++++++++++++++++---
 cxl/lib/libcxl.sym               |   14 ++
 cxl/lib/private.h                |   31 +++++
 cxl/libcxl.h                     |   28 ++++
 cxl/list.c                       |    9 +
 util/json.h                      |    1 
 13 files changed, 643 insertions(+), 28 deletions(-)

diff --git a/.clang-format b/.clang-format
index 106bc5e25434..16e28ac86959 100644
--- a/.clang-format
+++ b/.clang-format
@@ -80,6 +80,7 @@ ForEachMacros:
   - 'cxl_memdev_foreach'
   - 'cxl_bus_foreach'
   - 'cxl_port_foreach'
+  - 'cxl_decoder_foreach'
   - 'cxl_endpoint_foreach'
   - 'daxctl_dev_foreach'
   - 'daxctl_mapping_foreach'
diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index bac27c7b70a1..84872b912975 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -5,7 +5,7 @@ cxl-list(1)
 
 NAME
 ----
-cxl-list - List CXL capable memory devices, and their attributes in json.
+cxl-list - List platform CXL objects, and their attributes, in json.
 
 SYNOPSIS
 --------
@@ -29,6 +29,10 @@ The potential top-level array names and their nesting properties are:
 "endpoints":: nest under ports or buses (if ports are not emitted)
 "memdevs":: nest under endpoints or ports (if endpoints are not
    emitted) or buses (if endpoints and ports are not emitted)
+"root decoders":: nest under buses
+"port decoders":: nest under ports, or buses (if ports are not emitted)
+"endpoint decoders":: nest under endpoints, or ports (if endpoints are
+   not emitted) or buses (if endpoints and ports are not emitted)
 
 Filters can by specifed as either a single identidier, a space separated
 quoted string, or a comma separated list. When multiple filter
@@ -254,6 +258,18 @@ OPTIONS
 	Specify CXL endpoint device name(s), or device id(s) to filter
 	the emitted endpoint(s).
 
+-D::
+--decoders::
+	Include decoder objects (CXL Memory decode capability instances
+	in buses, ports, and endpoints) in the listing.
+
+-d::
+--decoder::
+	Specify CXL decoder device name(s), device id(s), or decoder type names
+	to filter the emitted decoder(s). The format for a decoder name is
+	"decoder<port_id>.<instance_id>". The possible decoder type names are
+	"root", "switch", or "endpoint", similar to the port filter syntax.
+
 --debug::
 	If the cxl tool was built with debug enabled, turn on debug
 	messages.
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index 49edb71b1f97..73af3d03b108 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -179,6 +179,7 @@ struct cxl_bus *cxl_bus_get_first(struct cxl_ctx *ctx);
 struct cxl_bus *cxl_bus_get_next(struct cxl_bus *bus);
 struct cxl_ctx *cxl_bus_get_ctx(struct cxl_bus *bus);
 struct cxl_bus *cxl_memdev_get_bus(struct cxl_memdev *memdev);
+struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
 struct cxl_bus *cxl_endpoint_get_bus(struct cxl_endpoint *endpoint);
 
 #define cxl_bus_foreach(ctx, bus)                                           \
@@ -215,9 +216,9 @@ struct cxl_port *cxl_bus_get_port(struct cxl_bus *bus);
 struct cxl_port *cxl_port_get_first(struct cxl_port *parent);
 struct cxl_port *cxl_port_get_next(struct cxl_port *port);
 struct cxl_port *cxl_port_get_parent(struct cxl_port *port);
-struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
 struct cxl_ctx *cxl_port_get_ctx(struct cxl_port *port);
 const char *cxl_port_get_host(struct cxl_port *port);
+struct cxl_port *cxl_decoder_get_port(struct cxl_decoder *decoder);
 
 #define cxl_port_foreach(parent, port)                                      \
        for (port = cxl_port_get_first(parent); port != NULL;                \
@@ -284,6 +285,73 @@ int cxl_endpoint_get_id(struct cxl_endpoint *endpoint);
 int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint);
 ----
 
+DECODERS
+--------
+Decoder objects are associated with the "HDM Decoder Capability"
+published in Port devices and CXL capable PCIe endpoints. The kernel
+additionally models platform firmware described CXL memory ranges (like
+the ACPI CEDT.CFMWS) as static decoder objects. They route System
+Physical Addresses through a port topology to an endpoint decoder that
+does the final translation from SPA to DPA (system-physical-address to
+device-local-physical-address).
+
+=== DECODER: Enumeration
+----
+struct cxl_decoder *cxl_decoder_get_first(struct cxl_port *port);
+struct cxl_decoder *cxl_decoder_get_next(struct cxl_decoder *decoder);
+struct cxl_ctx *cxl_decoder_get_ctx(struct cxl_decoder *decoder);
+
+#define cxl_decoder_foreach(port, decoder)                                  \
+       for (decoder = cxl_decoder_get_first(port); decoder != NULL;         \
+            decoder = cxl_decoder_get_next(decoder))
+----
+The definition of a CXL port in libcxl is an object that hosts one or
+more CXL decoder objects.
+
+=== DECODER: Attributes
+----
+unsigned long long cxl_decoder_get_resource(struct cxl_decoder *decoder);
+unsigned long long cxl_decoder_get_size(struct cxl_decoder *decoder);
+const char *cxl_decoder_get_devname(struct cxl_decoder *decoder);
+int cxl_decoder_get_id(struct cxl_decoder *decoder);
+
+enum cxl_decoder_target_type {
+       CXL_DECODER_TTYPE_UNKNOWN,
+       CXL_DECODER_TTYPE_EXPANDER,
+       CXL_DECODER_TTYPE_ACCELERATOR,
+};
+
+cxl_decoder_get_target_type(struct cxl_decoder *decoder);
+bool cxl_decoder_is_pmem_capable(struct cxl_decoder *decoder);
+bool cxl_decoder_is_volatile_capable(struct cxl_decoder *decoder);
+bool cxl_decoder_is_mem_capable(struct cxl_decoder *decoder);
+bool cxl_decoder_is_accelmem_capable(struct cxl_decoder *decoder);
+bool cxl_decoder_is_locked(struct cxl_decoder *decoder);
+----
+The kernel protects the enumeration of the physical address layout of
+the system. Without CAP_SYS_ADMIN cxl_decoder_get_resource() returns
+ULLONG_MAX to indicate that the address information was not retrievable.
+Otherwise, cxl_decoder_get_resource() returns the currently programmed
+value of the base of the decoder's decode range. A zero-sized decoder
+indicates a disabled decoder.
+
+Root level decoders only support limited set of memory types in their
+address range. The cxl_decoder_is_<memtype>_capable() helpers identify
+what is supported. Switch level decoders, in contrast are capable of
+routing any memory type, i.e. they just forward along the memory type
+support from their parent port. Endpoint decoders follow the
+capabilities of their host memory device.
+
+The capabilities of a decoder are not to be confused with their type /
+mode.  The type ultimately depends on the endpoint. For example an
+accelerator requires all decoders in its ancestry to be set to
+CXL_DECODER_TTYPE_ACCELERATOR, and conversely plain memory expander
+devices require CXL_DECODER_TTYPE_EXPANDER.
+
+Platform firmware may setup the CXL decode hierarchy before the OS
+boots, and may additionally require that the OS not change the decode
+settings. This property is indicated by the cxl_decoder_is_locked() API.
+
 include::../../copyright.txt[]
 
 SEE ALSO
diff --git a/cxl/filter.c b/cxl/filter.c
index 6dc61a135351..dc052f63d0c5 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -171,6 +171,17 @@ util_cxl_endpoint_filter_by_port(struct cxl_endpoint *endpoint,
 	return NULL;
 }
 
+static struct cxl_decoder *
+util_cxl_decoder_filter_by_port(struct cxl_decoder *decoder, const char *ident,
+				enum cxl_port_filter_mode mode)
+{
+	struct cxl_port *port = cxl_decoder_get_port(decoder);
+
+	if (util_cxl_port_filter(port, ident, mode))
+		return decoder;
+	return NULL;
+}
+
 static struct cxl_bus *util_cxl_bus_filter(struct cxl_bus *bus,
 					   const char *__ident)
 {
@@ -233,6 +244,16 @@ static struct cxl_port *util_cxl_port_filter_by_bus(struct cxl_port *port,
 	return NULL;
 }
 
+static struct cxl_decoder *
+util_cxl_decoder_filter_by_bus(struct cxl_decoder *decoder, const char *__ident)
+{
+	struct cxl_port *port = cxl_decoder_get_port(decoder);
+
+	if (!util_cxl_port_filter_by_bus(port, __ident))
+		return NULL;
+	return decoder;
+}
+
 static struct cxl_memdev *
 util_cxl_memdev_serial_filter(struct cxl_memdev *memdev, const char *__serials)
 {
@@ -357,6 +378,49 @@ static struct cxl_port *util_cxl_port_filter_by_memdev(struct cxl_port *port,
 	return NULL;
 }
 
+static struct cxl_decoder *util_cxl_decoder_filter(struct cxl_decoder *decoder,
+						   const char *__ident)
+{
+	struct cxl_port *port = cxl_decoder_get_port(decoder);
+	int pid, did;
+	char *ident, *save;
+	const char *name;
+
+	if (!__ident)
+		return decoder;
+
+	ident = strdup(__ident);
+	if (!ident)
+		return NULL;
+
+	for (name = strtok_r(ident, which_sep(__ident), &save); name;
+	     name = strtok_r(NULL, which_sep(__ident), &save)) {
+		if (strcmp(name, "all") == 0)
+			break;
+
+		if (strcmp(name, "root") == 0 && cxl_port_is_root(port))
+			break;
+		if (strcmp(name, "switch") == 0 && cxl_port_is_switch(port))
+			break;
+		if (strcmp(name, "endpoint") == 0 && cxl_port_is_endpoint(port))
+			break;
+
+		if ((sscanf(name, "%d.%d", &pid, &did) == 2 ||
+		     sscanf(name, "decoder%d.%d", &pid, &did) == 2) &&
+		    cxl_port_get_id(port) == pid &&
+		    cxl_decoder_get_id(decoder) == did)
+			break;
+
+		if (strcmp(name, cxl_decoder_get_devname(decoder)) == 0)
+			break;
+	}
+
+	free(ident);
+	if (name)
+		return decoder;
+	return NULL;
+}
+
 static unsigned long params_to_flags(struct cxl_filter_params *param)
 {
 	unsigned long flags = 0;
@@ -440,15 +504,44 @@ static struct json_object *pick_array(struct json_object *child,
 	return NULL;
 }
 
+static void walk_decoders(struct cxl_port *port, struct cxl_filter_params *p,
+			  struct json_object *jdecoders, unsigned long flags)
+{
+	struct cxl_decoder *decoder;
+
+	cxl_decoder_foreach(port, decoder) {
+		struct json_object *jdecoder;
+
+		if (!p->decoders)
+			continue;
+		if (!util_cxl_decoder_filter(decoder, p->decoder_filter))
+			continue;
+		if (!util_cxl_decoder_filter_by_bus(decoder, p->bus_filter))
+			continue;
+		if (!util_cxl_decoder_filter_by_port(decoder, p->port_filter,
+						     pf_mode(p)))
+			continue;
+		if (!p->idle && cxl_decoder_get_size(decoder) == 0)
+			continue;
+		jdecoder = util_cxl_decoder_to_json(decoder, flags);
+		if (!decoder) {
+			dbg(p, "decoder object allocation failure\n");
+			continue;
+		}
+		json_object_array_add(jdecoders, jdecoder);
+	}
+}
+
 static void walk_endpoints(struct cxl_port *port, struct cxl_filter_params *p,
 			   struct json_object *jeps, struct json_object *jdevs,
-			   unsigned long flags)
+			   struct json_object *jdecoders, unsigned long flags)
 {
 	struct cxl_endpoint *endpoint;
 
 	cxl_endpoint_foreach(port, endpoint) {
 		struct cxl_port *ep_port = cxl_endpoint_get_port(endpoint);
 		const char *devname = cxl_endpoint_get_devname(endpoint);
+		struct json_object *jchilddecoders = NULL;
 		struct json_object *jendpoint = NULL;
 		struct cxl_memdev *memdev;
 
@@ -495,14 +588,31 @@ static void walk_endpoints(struct cxl_port *port, struct cxl_filter_params *p,
 			else
 				json_object_array_add(jdevs, jobj);
 		}
+
+		if (p->decoders && p->endpoints) {
+			jchilddecoders = json_object_new_array();
+			if (!jchilddecoders) {
+				err(p,
+				    "%s: failed to enumerate child decoders\n",
+				    devname);
+				continue;
+			}
+		}
+
+		if (!p->decoders)
+			continue;
+		walk_decoders(cxl_endpoint_get_port(endpoint), p,
+			      pick_array(jchilddecoders, jdecoders), flags);
+		cond_add_put_array_suffix(jendpoint, "decoders", devname,
+					  jchilddecoders);
 	}
 }
 
-static void walk_child_ports(struct cxl_port *parent_port,
-			     struct cxl_filter_params *p,
-			     struct json_object *jports,
-			     struct json_object *jeps,
-			     struct json_object *jdevs, unsigned long flags)
+static void
+walk_child_ports(struct cxl_port *parent_port, struct cxl_filter_params *p,
+		 struct json_object *jports, struct json_object *jportdecoders,
+		 struct json_object *jeps, struct json_object *jepdecoders,
+		 struct json_object *jdevs, unsigned long flags)
 {
 	struct cxl_port *port;
 
@@ -512,6 +622,7 @@ static void walk_child_ports(struct cxl_port *parent_port,
 		struct json_object *jchilddevs = NULL;
 		struct json_object *jchildports = NULL;
 		struct json_object *jchildeps = NULL;
+		struct json_object *jchilddecoders = NULL;
 
 		if (!util_cxl_port_filter_by_memdev(port, p->memdev_filter,
 						    p->serial_filter))
@@ -555,19 +666,37 @@ static void walk_child_ports(struct cxl_port *parent_port,
 					continue;
 				}
 			}
+
+			if (p->decoders) {
+				jchilddecoders = json_object_new_array();
+				if (!jchilddecoders) {
+					err(p,
+					    "%s: failed to enumerate child decoders\n",
+					    devname);
+					continue;
+				}
+			}
 		}
 
 walk_children:
-		if (p->endpoints || p->memdevs)
+		if (p->endpoints || p->memdevs || p->decoders)
 			walk_endpoints(port, p, pick_array(jchildeps, jeps),
-				       pick_array(jchilddevs, jdevs), flags);
+				       pick_array(jchilddevs, jdevs),
+				       pick_array(jchilddecoders, jepdecoders),
+				       flags);
 
+		walk_decoders(port, p,
+			      pick_array(jchilddecoders, jportdecoders), flags);
 		walk_child_ports(port, p, pick_array(jchildports, jports),
+				 pick_array(jchilddecoders, jportdecoders),
 				 pick_array(jchildeps, jeps),
+				 pick_array(jchilddecoders, jepdecoders),
 				 pick_array(jchilddevs, jdevs), flags);
 		cond_add_put_array_suffix(jport, "ports", devname, jchildports);
 		cond_add_put_array_suffix(jport, "endpoints", devname,
 					  jchildeps);
+		cond_add_put_array_suffix(jport, "decoders", devname,
+					  jchilddecoders);
 		cond_add_put_array_suffix(jport, "memdevs", devname,
 					  jchilddevs);
 	}
@@ -578,6 +707,9 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	struct json_object *jdevs = NULL, *jbuses = NULL, *jports = NULL;
 	struct json_object *jplatform = json_object_new_array();
 	unsigned long flags = params_to_flags(p);
+	struct json_object *jportdecoders = NULL;
+	struct json_object *jbusdecoders = NULL;
+	struct json_object *jepdecoders = NULL;
 	struct json_object *janondevs = NULL;
 	struct json_object *jeps = NULL;
 	struct cxl_memdev *memdev;
@@ -609,6 +741,18 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	if (!jdevs)
 		goto err;
 
+	jbusdecoders = json_object_new_array();
+	if (!jbusdecoders)
+		goto err;
+
+	jportdecoders = json_object_new_array();
+	if (!jportdecoders)
+		goto err;
+
+	jepdecoders = json_object_new_array();
+	if (!jepdecoders)
+		goto err;
+
 	dbg(p, "walk memdevs\n");
 	cxl_memdev_foreach(ctx, memdev) {
 		struct json_object *janondev;
@@ -633,6 +777,7 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 	dbg(p, "walk buses\n");
 	cxl_bus_foreach(ctx, bus) {
 		struct json_object *jbus = NULL;
+		struct json_object *jchilddecoders = NULL;
 		struct json_object *jchildports = NULL;
 		struct json_object *jchilddevs = NULL;
 		struct json_object *jchildeps = NULL;
@@ -681,15 +826,33 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 					continue;
 				}
 			}
+			if (p->decoders) {
+				jchilddecoders = json_object_new_array();
+				if (!jchilddecoders) {
+					err(p,
+					    "%s: failed to enumerate child decoders\n",
+					    devname);
+					continue;
+				}
+			}
+
 		}
 walk_children:
+		dbg(p, "walk decoders\n");
+		walk_decoders(port, p, pick_array(jchilddecoders, jbusdecoders),
+			      flags);
+
 		dbg(p, "walk ports\n");
 		walk_child_ports(port, p, pick_array(jchildports, jports),
+				 pick_array(jchilddecoders, jportdecoders),
 				 pick_array(jchildeps, jeps),
+				 pick_array(jchilddecoders, jepdecoders),
 				 pick_array(jchilddevs, jdevs), flags);
 		cond_add_put_array_suffix(jbus, "ports", devname, jchildports);
 		cond_add_put_array_suffix(jbus, "endpoints", devname,
 					  jchildeps);
+		cond_add_put_array_suffix(jbus, "decoders", devname,
+					  jchilddecoders);
 		cond_add_put_array_suffix(jbus, "memdevs", devname, jchilddevs);
 	}
 
@@ -703,12 +866,24 @@ walk_children:
 		top_level_objs++;
 	if (json_object_array_length(jdevs))
 		top_level_objs++;
+	if (json_object_array_length(jbusdecoders))
+		top_level_objs++;
+	if (json_object_array_length(jportdecoders))
+		top_level_objs++;
+	if (json_object_array_length(jepdecoders))
+		top_level_objs++;
 
 	splice_array(p, janondevs, jplatform, "anon memdevs", top_level_objs > 1);
 	splice_array(p, jbuses, jplatform, "buses", top_level_objs > 1);
 	splice_array(p, jports, jplatform, "ports", top_level_objs > 1);
 	splice_array(p, jeps, jplatform, "endpoints", top_level_objs > 1);
 	splice_array(p, jdevs, jplatform, "memdevs", top_level_objs > 1);
+	splice_array(p, jbusdecoders, jplatform, "root decoders",
+		     top_level_objs > 1);
+	splice_array(p, jportdecoders, jplatform, "port decoders",
+		     top_level_objs > 1);
+	splice_array(p, jepdecoders, jplatform, "endpoint decoders",
+		     top_level_objs > 1);
 
 	util_display_json_array(stdout, jplatform, flags);
 
@@ -719,6 +894,9 @@ err:
 	json_object_put(jports);
 	json_object_put(jeps);
 	json_object_put(jdevs);
+	json_object_put(jbusdecoders);
+	json_object_put(jportdecoders);
+	json_object_put(jepdecoders);
 	json_object_put(jplatform);
 	return -ENOMEM;
 }
diff --git a/cxl/filter.h b/cxl/filter.h
index bbd341c71721..5d7bf45f3301 100644
--- a/cxl/filter.h
+++ b/cxl/filter.h
@@ -12,8 +12,10 @@ struct cxl_filter_params {
 	const char *bus_filter;
 	const char *port_filter;
 	const char *endpoint_filter;
+	const char *decoder_filter;
 	bool single;
 	bool endpoints;
+	bool decoders;
 	bool memdevs;
 	bool ports;
 	bool buses;
diff --git a/cxl/json.c b/cxl/json.c
index 51918d65f671..548bc52f2d16 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -262,6 +262,68 @@ struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
 	return jbus;
 }
 
+struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
+					     unsigned long flags)
+{
+	const char *devname = cxl_decoder_get_devname(decoder);
+	struct cxl_port *port = cxl_decoder_get_port(decoder);
+	struct json_object *jdecoder, *jobj;
+	u64 val;
+
+	jdecoder = json_object_new_object();
+	if (!jdecoder)
+		return NULL;
+
+	jobj = json_object_new_string(devname);
+	if (jobj)
+		json_object_object_add(jdecoder, "decoder", jobj);
+
+	val = cxl_decoder_get_resource(decoder);
+	if (val < ULLONG_MAX) {
+		jobj = util_json_object_hex(val, flags);
+		if (jobj)
+			json_object_object_add(jdecoder, "resource", jobj);
+	}
+
+	val = cxl_decoder_get_size(decoder);
+	if (val < ULLONG_MAX) {
+		jobj = util_json_object_size(val, flags);
+		if (jobj)
+			json_object_object_add(jdecoder, "size", jobj);
+	}
+
+	if (val == 0) {
+		jobj = json_object_new_string("disabled");
+		if (jobj)
+			json_object_object_add(jdecoder, "state", jobj);
+	}
+
+	if (cxl_port_is_root(port) && cxl_decoder_is_mem_capable(decoder)) {
+		if (cxl_decoder_is_pmem_capable(decoder)) {
+			jobj = json_object_new_boolean(true);
+			if (jobj)
+				json_object_object_add(jdecoder, "pmem_capable",
+						       jobj);
+		}
+		if (cxl_decoder_is_volatile_capable(decoder)) {
+			jobj = json_object_new_boolean(true);
+			if (jobj)
+				json_object_object_add(
+					jdecoder, "volatile_capable", jobj);
+		}
+	}
+
+	if (cxl_port_is_root(port) &&
+	    cxl_decoder_is_accelmem_capable(decoder)) {
+		jobj = json_object_new_boolean(true);
+		if (jobj)
+			json_object_object_add(jdecoder, "accelmem_capable",
+					       jobj);
+	}
+
+	return jdecoder;
+}
+
 static struct json_object *__util_cxl_port_to_json(struct cxl_port *port,
 						   const char *name_key,
 						   unsigned long flags)
diff --git a/cxl/json.h b/cxl/json.h
index 8f45190f67e5..fcca2e6e9cb6 100644
--- a/cxl/json.h
+++ b/cxl/json.h
@@ -13,4 +13,6 @@ struct json_object *util_cxl_port_to_json(struct cxl_port *port,
 					  unsigned long flags);
 struct json_object *util_cxl_endpoint_to_json(struct cxl_endpoint *endpoint,
 					      unsigned long flags);
+struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
+					     unsigned long flags);
 #endif /* __CXL_UTIL_JSON_H__ */
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 2fdaf71bb837..5e3092364e4a 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -63,16 +63,26 @@ static void free_memdev(struct cxl_memdev *memdev, struct list_head *head)
 	free(memdev->firmware_version);
 	free(memdev->dev_buf);
 	free(memdev->dev_path);
-	free(memdev->host);
+	free(memdev->host_path);
 	free(memdev);
 }
 
+static void free_decoder(struct cxl_decoder *decoder, struct list_head *head)
+{
+	if (head)
+		list_del_from(head, &decoder->list);
+	free(decoder->dev_buf);
+	free(decoder->dev_path);
+	free(decoder);
+}
+
 static void free_port(struct cxl_port *port, struct list_head *head);
 static void free_endpoint(struct cxl_endpoint *endpoint, struct list_head *head);
 static void __free_port(struct cxl_port *port, struct list_head *head)
 {
 	struct cxl_port *child, *_c;
 	struct cxl_endpoint *endpoint, *_e;
+	struct cxl_decoder *decoder, *_d;
 
 	if (head)
 		list_del_from(head, &port->list);
@@ -80,6 +90,8 @@ static void __free_port(struct cxl_port *port, struct list_head *head)
 		free_port(child, &port->child_ports);
 	list_for_each_safe(&port->endpoints, endpoint, _e, port.list)
 		free_endpoint(endpoint, &port->endpoints);
+	list_for_each_safe(&port->decoders, decoder, _d, list)
+		free_decoder(decoder, &port->decoders);
 	kmod_module_unref(port->module);
 	free(port->dev_buf);
 	free(port->dev_path);
@@ -298,9 +310,9 @@ static void *add_cxl_memdev(void *parent, int id, const char *cxlmem_base)
 	char *path = calloc(1, strlen(cxlmem_base) + 100);
 	struct cxl_ctx *ctx = parent;
 	struct cxl_memdev *memdev, *memdev_dup;
-	char *host, *rpath = NULL;
 	char buf[SYSFS_ATTR_SIZE];
 	struct stat st;
+	char *host;
 
 	if (!path)
 		return NULL;
@@ -358,21 +370,13 @@ static void *add_cxl_memdev(void *parent, int id, const char *cxlmem_base)
 	if (!memdev->dev_path)
 		goto err_read;
 
-	rpath = realpath(cxlmem_base, NULL);
-	if (!rpath)
+	memdev->host_path = realpath(cxlmem_base, NULL);
+	if (!memdev->host_path)
 		goto err_read;
-	host = strrchr(rpath, '/');
-	if (host) {
-		host[0] = '\0';
-		host = strrchr(rpath, '/');
-	}
+	host = strrchr(memdev->host_path, '/');
 	if (!host)
 		goto err_read;
-	memdev->host = strdup(host + 1);
-	if (!memdev->host)
-		goto err_read;
-	free(rpath);
-	rpath = NULL;
+	host[0] = '\0';
 
 	sprintf(path, "%s/firmware_version", cxlmem_base);
 	if (sysfs_read_attr(ctx, path, buf) < 0)
@@ -404,8 +408,8 @@ static void *add_cxl_memdev(void *parent, int id, const char *cxlmem_base)
 	free(memdev->firmware_version);
 	free(memdev->dev_buf);
 	free(memdev->dev_path);
+	free(memdev->host_path);
 	free(memdev);
-	free(rpath);
  err_dev:
 	free(path);
 	return NULL;
@@ -463,7 +467,7 @@ CXL_EXPORT const char *cxl_memdev_get_devname(struct cxl_memdev *memdev)
 
 CXL_EXPORT const char *cxl_memdev_get_host(struct cxl_memdev *memdev)
 {
-	return memdev->host;
+	return devpath_to_devname(memdev->host_path);
 }
 
 CXL_EXPORT struct cxl_bus *cxl_memdev_get_bus(struct cxl_memdev *memdev)
@@ -679,9 +683,11 @@ static int cxl_port_init(struct cxl_port *port, struct cxl_port *parent_port,
 	port->ctx = ctx;
 	port->type = type;
 	port->parent = parent_port;
+	port->type = type;
 
 	list_head_init(&port->child_ports);
 	list_head_init(&port->endpoints);
+	list_head_init(&port->decoders);
 
 	port->dev_path = strdup(cxlport_base);
 	if (!port->dev_path)
@@ -842,6 +848,207 @@ cxl_endpoint_get_memdev(struct cxl_endpoint *endpoint)
 	return NULL;
 }
 
+static void *add_cxl_decoder(void *parent, int id, const char *cxldecoder_base)
+{
+	const char *devname = devpath_to_devname(cxldecoder_base);
+	char *path = calloc(1, strlen(cxldecoder_base) + 100);
+	struct cxl_decoder *decoder, *decoder_dup;
+	struct cxl_port *port = parent;
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+	char buf[SYSFS_ATTR_SIZE];
+	size_t i;
+
+	dbg(ctx, "%s: base: \'%s\'\n", devname, cxldecoder_base);
+
+	if (!path)
+		return NULL;
+
+	decoder = calloc(1, sizeof(*decoder));
+	if (!decoder)
+		goto err;
+
+	decoder->id = id;
+	decoder->ctx = ctx;
+	decoder->port = port;
+
+	decoder->dev_path = strdup(cxldecoder_base);
+	if (!decoder->dev_path)
+		goto err;
+
+	decoder->dev_buf = calloc(1, strlen(cxldecoder_base) + 50);
+	if (!decoder->dev_buf)
+		goto err;
+	decoder->buf_len = strlen(cxldecoder_base) + 50;
+
+	sprintf(path, "%s/start", cxldecoder_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		decoder->start = ULLONG_MAX;
+	else
+		decoder->start = strtoull(buf, NULL, 0);
+
+	sprintf(path, "%s/size", cxldecoder_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		decoder->size = ULLONG_MAX;
+	else
+		decoder->size = strtoull(buf, NULL, 0);
+
+	switch (port->type) {
+	case CXL_PORT_SWITCH:
+	case CXL_PORT_ENDPOINT:
+		decoder->pmem_capable = true;
+		decoder->volatile_capable = true;
+		decoder->mem_capable = true;
+		decoder->accelmem_capable = true;
+		sprintf(path, "%s/locked", cxldecoder_base);
+		if (sysfs_read_attr(ctx, path, buf) == 0)
+			decoder->locked = !!strtoul(buf, NULL, 0);
+		sprintf(path, "%s/target_type", cxldecoder_base);
+		if (sysfs_read_attr(ctx, path, buf) == 0) {
+			if (strcmp(buf, "accelerator") == 0)
+				decoder->target_type =
+					CXL_DECODER_TTYPE_ACCELERATOR;
+			if (strcmp(buf, "expander") == 0)
+				decoder->target_type =
+					CXL_DECODER_TTYPE_EXPANDER;
+		}
+		break;
+	case CXL_PORT_ROOT: {
+		struct cxl_decoder_flag {
+			char *name;
+			bool *flag;
+		} flags[] = {
+			{ "cap_type2", &decoder->accelmem_capable },
+			{ "cap_type3", &decoder->mem_capable },
+			{ "cap_ram", &decoder->volatile_capable },
+			{ "cap_pmem", &decoder->pmem_capable },
+			{ "locked", &decoder->locked },
+		};
+
+		for (i = 0; i < ARRAY_SIZE(flags); i++) {
+			struct cxl_decoder_flag *flag = &flags[i];
+
+			sprintf(path, "%s/%s", cxldecoder_base, flag->name);
+			if (sysfs_read_attr(ctx, path, buf) == 0)
+				*(flag->flag) = !!strtoul(buf, NULL, 0);
+		}
+		break;
+	}
+	}
+
+	cxl_decoder_foreach(port, decoder_dup)
+		if (decoder_dup->id == decoder->id) {
+			free_decoder(decoder, NULL);
+			return decoder_dup;
+		}
+
+	list_add(&port->decoders, &decoder->list);
+
+	return decoder;
+err:
+	free(decoder->dev_path);
+	free(decoder->dev_buf);
+	free(decoder);
+	free(path);
+	return NULL;
+}
+
+static void cxl_decoders_init(struct cxl_port *port)
+{
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+	char *decoder_fmt;
+
+	if (port->decoders_init)
+		return;
+
+	if (asprintf(&decoder_fmt, "decoder%d.", cxl_port_get_id(port)) < 0) {
+		err(ctx, "%s: failed to add decoder(s)\n",
+		    cxl_port_get_devname(port));
+		return;
+	}
+
+	port->decoders_init = 1;
+
+	sysfs_device_parse(ctx, port->dev_path, decoder_fmt, port,
+			   add_cxl_decoder);
+
+	free(decoder_fmt);
+}
+
+CXL_EXPORT struct cxl_decoder *cxl_decoder_get_first(struct cxl_port *port)
+{
+	cxl_decoders_init(port);
+
+	return list_top(&port->decoders, struct cxl_decoder, list);
+}
+
+CXL_EXPORT struct cxl_decoder *cxl_decoder_get_next(struct cxl_decoder *decoder)
+{
+	struct cxl_port *port = decoder->port;
+
+	return list_next(&port->decoders, decoder, list);
+}
+
+CXL_EXPORT struct cxl_ctx *cxl_decoder_get_ctx(struct cxl_decoder *decoder)
+{
+	return decoder->ctx;
+}
+
+CXL_EXPORT int cxl_decoder_get_id(struct cxl_decoder *decoder)
+{
+	return decoder->id;
+}
+
+CXL_EXPORT struct cxl_port *cxl_decoder_get_port(struct cxl_decoder *decoder)
+{
+	return decoder->port;
+}
+
+CXL_EXPORT unsigned long long cxl_decoder_get_resource(struct cxl_decoder *decoder)
+{
+	return decoder->start;
+}
+
+CXL_EXPORT unsigned long long cxl_decoder_get_size(struct cxl_decoder *decoder)
+{
+	return decoder->size;
+}
+
+CXL_EXPORT enum cxl_decoder_target_type
+cxl_decoder_get_target_type(struct cxl_decoder *decoder)
+{
+	return decoder->target_type;
+}
+
+CXL_EXPORT bool cxl_decoder_is_pmem_capable(struct cxl_decoder *decoder)
+{
+	return decoder->pmem_capable;
+}
+
+CXL_EXPORT bool cxl_decoder_is_volatile_capable(struct cxl_decoder *decoder)
+{
+	return decoder->volatile_capable;
+}
+
+CXL_EXPORT bool cxl_decoder_is_mem_capable(struct cxl_decoder *decoder)
+{
+	return decoder->mem_capable;
+}
+
+CXL_EXPORT bool cxl_decoder_is_accelmem_capable(struct cxl_decoder *decoder)
+{
+	return decoder->accelmem_capable;
+}
+
+CXL_EXPORT bool cxl_decoder_is_locked(struct cxl_decoder *decoder)
+{
+	return decoder->locked;
+}
+
+CXL_EXPORT const char *cxl_decoder_get_devname(struct cxl_decoder *decoder)
+{
+	return devpath_to_devname(decoder->dev_path);
+}
+
 static void *add_cxl_port(void *parent, int id, const char *cxlport_base)
 {
 	const char *devname = devpath_to_devname(cxlport_base);
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index f235e99e3bf0..22babb760f23 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -117,4 +117,18 @@ global:
 	cxl_memdev_get_bus;
 	cxl_memdev_disable_invalidate;
 	cxl_memdev_enable;
+	cxl_decoder_get_first;
+	cxl_decoder_get_next;
+	cxl_decoder_get_ctx;
+	cxl_decoder_get_id;
+	cxl_decoder_get_port;
+	cxl_decoder_get_resource;
+	cxl_decoder_get_size;
+	cxl_decoder_get_devname;
+	cxl_decoder_get_target_type;
+	cxl_decoder_is_pmem_capable;
+	cxl_decoder_is_volatile_capable;
+	cxl_decoder_is_mem_capable;
+	cxl_decoder_is_accelmem_capable;
+	cxl_decoder_is_locked;
 } LIBCXL_1;
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
index c00bb3600138..1743a244877a 100644
--- a/cxl/lib/private.h
+++ b/cxl/lib/private.h
@@ -23,7 +23,7 @@ struct cxl_memdev {
 	int numa_node;
 	void *dev_buf;
 	size_t buf_len;
-	char *host;
+	char *host_path;
 	char *dev_path;
 	char *firmware_version;
 	struct cxl_ctx *ctx;
@@ -52,6 +52,7 @@ struct cxl_port {
 	char *uport;
 	int ports_init;
 	int endpoints_init;
+	int decoders_init;
 	struct cxl_ctx *ctx;
 	struct cxl_bus *bus;
 	enum cxl_port_type type;
@@ -60,6 +61,7 @@ struct cxl_port {
 	struct list_node list;
 	struct list_head child_ports;
 	struct list_head endpoints;
+	struct list_head decoders;
 };
 
 struct cxl_bus {
@@ -71,6 +73,33 @@ struct cxl_endpoint {
 	struct cxl_memdev *memdev;
 };
 
+struct cxl_target {
+	struct list_node list;
+	struct cxl_decoder *decoder;
+	char *dev_path;
+	int id, position;
+};
+
+struct cxl_decoder {
+	struct cxl_port *port;
+	struct list_node list;
+	struct cxl_ctx *ctx;
+	u64 start;
+	u64 size;
+	void *dev_buf;
+	size_t buf_len;
+	char *dev_path;
+	int nr_targets;
+	int id;
+	bool pmem_capable;
+	bool volatile_capable;
+	bool mem_capable;
+	bool accelmem_capable;
+	bool locked;
+	enum cxl_decoder_target_type target_type;
+	struct list_head targets;
+};
+
 enum cxl_cmd_query_status {
 	CXL_CMD_QUERY_NOT_RUN = 0,
 	CXL_CMD_QUERY_OK,
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index 53f68dde2770..439ed9305397 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -98,6 +98,34 @@ bool cxl_port_hosts_memdev(struct cxl_port *port, struct cxl_memdev *memdev);
 	for (port = cxl_port_get_first(parent); port != NULL;                  \
 	     port = cxl_port_get_next(port))
 
+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);
+unsigned long long cxl_decoder_get_resource(struct cxl_decoder *decoder);
+unsigned long long cxl_decoder_get_size(struct cxl_decoder *decoder);
+const char *cxl_decoder_get_devname(struct cxl_decoder *decoder);
+struct cxl_ctx *cxl_decoder_get_ctx(struct cxl_decoder *decoder);
+int cxl_decoder_get_id(struct cxl_decoder *decoder);
+struct cxl_port *cxl_decoder_get_port(struct cxl_decoder *decoder);
+
+enum cxl_decoder_target_type {
+	CXL_DECODER_TTYPE_UNKNOWN,
+	CXL_DECODER_TTYPE_EXPANDER,
+	CXL_DECODER_TTYPE_ACCELERATOR,
+};
+
+enum cxl_decoder_target_type
+cxl_decoder_get_target_type(struct cxl_decoder *decoder);
+bool cxl_decoder_is_pmem_capable(struct cxl_decoder *decoder);
+bool cxl_decoder_is_volatile_capable(struct cxl_decoder *decoder);
+bool cxl_decoder_is_mem_capable(struct cxl_decoder *decoder);
+bool cxl_decoder_is_accelmem_capable(struct cxl_decoder *decoder);
+bool cxl_decoder_is_locked(struct cxl_decoder *decoder);
+
+#define cxl_decoder_foreach(port, decoder)                                     \
+	for (decoder = cxl_decoder_get_first(port); decoder != NULL;           \
+	     decoder = cxl_decoder_get_next(decoder))
+
 struct cxl_endpoint;
 struct cxl_endpoint *cxl_endpoint_get_first(struct cxl_port *parent);
 struct cxl_endpoint *cxl_endpoint_get_next(struct cxl_endpoint *endpoint);
diff --git a/cxl/list.c b/cxl/list.c
index b15e01ce40f6..d70192a8cecf 100644
--- a/cxl/list.c
+++ b/cxl/list.c
@@ -36,6 +36,11 @@ static const struct option options[] = {
 		   "filter by CXL endpoint device name(s)"),
 	OPT_BOOLEAN('E', "endpoints", &param.endpoints,
 		    "include CXL endpoint info"),
+	OPT_STRING('d', "decoder", &param.decoder_filter,
+		   "decoder device name",
+		   "filter by CXL decoder device name(s) / class"),
+	OPT_BOOLEAN('D', "decoders", &param.decoders,
+		    "include CXL decoder info"),
 	OPT_BOOLEAN('i', "idle", &param.idle, "include disabled devices"),
 	OPT_BOOLEAN('u', "human", &param.human,
 		    "use human friendly number formats "),
@@ -50,7 +55,7 @@ static const struct option options[] = {
 static int num_list_flags(void)
 {
 	return !!param.memdevs + !!param.buses + !!param.ports +
-	       !!param.endpoints;
+	       !!param.endpoints + !!param.decoders;
 }
 
 int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
@@ -82,6 +87,8 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
 			param.ports = true;
 		if (param.endpoint_filter)
 			param.endpoints = true;
+		if (param.decoder_filter)
+			param.decoders = true;
 		if (num_list_flags() == 0) {
 			/*
 			 * TODO: We likely want to list regions by default if
diff --git a/util/json.h b/util/json.h
index 061f0d423158..e026df1e1519 100644
--- a/util/json.h
+++ b/util/json.h
@@ -18,6 +18,7 @@ enum util_json_flags {
 	UTIL_JSON_FIRMWARE	= (1 << 8),
 	UTIL_JSON_DAX_MAPPINGS	= (1 << 9),
 	UTIL_JSON_HEALTH	= (1 << 10),
+	UTIL_JSON_TARGETS	= (1 << 11),
 };
 
 void util_display_json_array(FILE *f_out, struct json_object *jarray,


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 31/37] cxl/list: Extend decoder objects with target information
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (29 preceding siblings ...)
  2022-01-24  0:54 ` [ndctl PATCH 30/37] cxl/list: Add decoder support Dan Williams
@ 2022-01-24  0:54 ` Dan Williams
  2022-01-24  0:54 ` [ndctl PATCH 32/37] cxl/list: Use 'physical_node' for root port attachment detection Dan Williams
                   ` (5 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:54 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

A target combines information about a dport along with its position in the
intereleave order. With targets enumerated decoders can also be filtered be
memory devices by seeing which decoders have a dport in the memory-device's
ancestry.

$ cxl list -D -d 3.1 -T -u
{
  "decoder":"decoder3.1",
  "resource":"0x8030000000",
  "size":"512.00 MiB (536.87 MB)",
  "volatile_capable":true,
  "nr_targets":2,
  "targets":[
    {
      "target":"cxl_host_bridge.1",
      "position":1,
      "id":"0x1"
    },
    {
      "target":"cxl_host_bridge.0",
      "position":0,
      "id":"0"
    }
  ]
}

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 .clang-format                    |    1 
 Documentation/cxl/cxl-list.txt   |    8 ++-
 Documentation/cxl/lib/libcxl.txt |   58 +++++++++++++++++++
 cxl/filter.c                     |   25 ++++++++
 cxl/filter.h                     |    1 
 cxl/json.c                       |   46 +++++++++++++++
 cxl/lib/libcxl.c                 |  115 ++++++++++++++++++++++++++++++++++++++
 cxl/lib/libcxl.sym               |   10 +++
 cxl/libcxl.h                     |   19 ++++++
 cxl/list.c                       |    2 +
 10 files changed, 283 insertions(+), 2 deletions(-)

diff --git a/.clang-format b/.clang-format
index 16e28ac86959..47fb657ff09a 100644
--- a/.clang-format
+++ b/.clang-format
@@ -81,6 +81,7 @@ ForEachMacros:
   - 'cxl_bus_foreach'
   - 'cxl_port_foreach'
   - 'cxl_decoder_foreach'
+  - 'cxl_target_foreach'
   - 'cxl_endpoint_foreach'
   - 'daxctl_dev_foreach'
   - 'daxctl_mapping_foreach'
diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index 84872b912975..20ff2cb54fcb 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -44,8 +44,8 @@ would only list objects that are beneath port10 AND map mem0, mem1, OR
 mem2.
 
 Given that many topology queries seek to answer questions relative to a
-given memdev, buses, ports, and endpoints can be filtered by one or more
-memdevs. For example:
+given memdev, buses, ports, endpoints, and decoders can be filtered by
+one or more memdevs. For example:
 ----
 # cxl list -P -p switch,endpoint -m mem0
 [
@@ -270,6 +270,10 @@ OPTIONS
 	"decoder<port_id>.<instance_id>". The possible decoder type names are
 	"root", "switch", or "endpoint", similar to the port filter syntax.
 
+-T::
+--targets::
+	Extend decoder listings with downstream port target information.
+
 --debug::
 	If the cxl tool was built with debug enabled, turn on debug
 	messages.
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index 73af3d03b108..bd92fef962fa 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -300,6 +300,7 @@ device-local-physical-address).
 struct cxl_decoder *cxl_decoder_get_first(struct cxl_port *port);
 struct cxl_decoder *cxl_decoder_get_next(struct cxl_decoder *decoder);
 struct cxl_ctx *cxl_decoder_get_ctx(struct cxl_decoder *decoder);
+struct cxl_decoder *cxl_target_get_decoder(struct cxl_target *target);
 
 #define cxl_decoder_foreach(port, decoder)                                  \
        for (decoder = cxl_decoder_get_first(port); decoder != NULL;         \
@@ -314,6 +315,7 @@ unsigned long long cxl_decoder_get_resource(struct cxl_decoder *decoder);
 unsigned long long cxl_decoder_get_size(struct cxl_decoder *decoder);
 const char *cxl_decoder_get_devname(struct cxl_decoder *decoder);
 int cxl_decoder_get_id(struct cxl_decoder *decoder);
+int cxl_decoder_get_nr_targets(struct cxl_decoder *decoder);
 
 enum cxl_decoder_target_type {
        CXL_DECODER_TTYPE_UNKNOWN,
@@ -352,6 +354,62 @@ Platform firmware may setup the CXL decode hierarchy before the OS
 boots, and may additionally require that the OS not change the decode
 settings. This property is indicated by the cxl_decoder_is_locked() API.
 
+==== TARGETS
+A root or switch level decoder takes an SPA (system-physical-address) as
+input and routes it to a downstream port. Which downstream port depends
+on the downstream port's position in the interleave. A 'struct
+cxl_target' object represents the properties of a given downstream port
+relative to its interleave configuration.
+
+===== TARGET: Enumeration
+----
+struct cxl_target *cxl_decoder_get_target_by_memdev(struct cxl_decoder *decoder,
+                                                   struct cxl_memdev *memdev);
+struct cxl_target *
+cxl_decoder_get_target_by_position(struct cxl_decoder *decoder, int position);
+struct cxl_target *cxl_target_get_first(struct cxl_decoder *decoder);
+struct cxl_target *cxl_target_get_next(struct cxl_target *target);
+
+#define cxl_target_foreach(decoder, target)                                   \
+       for (target = cxl_target_get_first(decoder); target != NULL;           \
+            target = cxl_target_get_next(target))
+----
+Target objects can only be enumerated if the decoder has been
+configured, for switch decoders. For root decoders they are always
+available since the root decoder target mapping is static. The
+cxl_decoder_get_target_by_memdev() helper walks the topology to validate
+if the given memory device is capable of receiving cycles from this
+upstream decoder. It does not validate if the memory device is currently
+configured to participate in that decode.
+
+===== TARGET: Attributes
+----
+int cxl_target_get_position(struct cxl_target *target);
+unsigned long cxl_target_get_id(struct cxl_target *target);
+const char *cxl_target_get_devname(struct cxl_target *target);
+bool cxl_target_maps_memdev(struct cxl_target *target,
+                           struct cxl_memdev *memdev);
+----
+The position of a decoder along with the interleave granularity dictate
+which address in the decoder's resource range map to which port.
+
+The target id is an identifier that the CXL port uses to reference this
+downstream port. For CXL / PCIe downstream switch ports the id is
+defined by the PCIe Link Capability Port Number field. For root decoders
+the id is specified by platform firmware specific mechanism. For
+ACPI.CXL defined root ports the id comes from the CEDT.CHBS / ACPI0016
+_UID.
+
+The device name of a target is the name of the host device for the
+downstream port. For CXL / PCIe downstream ports the devname is
+downstream switch port PCI device. For CXL root ports the devname is a
+platform firmware object for the host bridge like a ACPI0016 device
+instance.
+
+The cxl_target_maps_memdev() helper is the companion of
+cxl_decoder_get_target_by_memdev() to determine which downstream ports /
+targets are capable of mapping which memdevs.
+
 include::../../copyright.txt[]
 
 SEE ALSO
diff --git a/cxl/filter.c b/cxl/filter.c
index dc052f63d0c5..05ede9116559 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -421,6 +421,26 @@ static struct cxl_decoder *util_cxl_decoder_filter(struct cxl_decoder *decoder,
 	return NULL;
 }
 
+static struct cxl_decoder *
+util_cxl_decoder_filter_by_memdev(struct cxl_decoder *decoder,
+				  const char *ident, const char *serial)
+{
+	struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder);
+	struct cxl_memdev *memdev;
+
+	if (!ident && !serial)
+		return decoder;
+
+	cxl_memdev_foreach(ctx, memdev) {
+		if (!util_cxl_memdev_filter(memdev, ident, serial))
+			continue;
+		if (cxl_decoder_get_target_by_memdev(decoder, memdev))
+			return decoder;
+	}
+
+	return NULL;
+}
+
 static unsigned long params_to_flags(struct cxl_filter_params *param)
 {
 	unsigned long flags = 0;
@@ -431,6 +451,8 @@ static unsigned long params_to_flags(struct cxl_filter_params *param)
 		flags |= UTIL_JSON_HUMAN;
 	if (param->health)
 		flags |= UTIL_JSON_HEALTH;
+	if (param->targets)
+		flags |= UTIL_JSON_TARGETS;
 	return flags;
 }
 
@@ -521,6 +543,9 @@ static void walk_decoders(struct cxl_port *port, struct cxl_filter_params *p,
 		if (!util_cxl_decoder_filter_by_port(decoder, p->port_filter,
 						     pf_mode(p)))
 			continue;
+		if (!util_cxl_decoder_filter_by_memdev(
+			    decoder, p->memdev_filter, p->serial_filter))
+			continue;
 		if (!p->idle && cxl_decoder_get_size(decoder) == 0)
 			continue;
 		jdecoder = util_cxl_decoder_to_json(decoder, flags);
diff --git a/cxl/filter.h b/cxl/filter.h
index 5d7bf45f3301..6fd469f02ea2 100644
--- a/cxl/filter.h
+++ b/cxl/filter.h
@@ -16,6 +16,7 @@ struct cxl_filter_params {
 	bool single;
 	bool endpoints;
 	bool decoders;
+	bool targets;
 	bool memdevs;
 	bool ports;
 	bool buses;
diff --git a/cxl/json.c b/cxl/json.c
index 548bc52f2d16..3a3790955370 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -268,6 +268,8 @@ struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
 	const char *devname = cxl_decoder_get_devname(decoder);
 	struct cxl_port *port = cxl_decoder_get_port(decoder);
 	struct json_object *jdecoder, *jobj;
+	struct json_object *jtargets;
+	struct cxl_target *target;
 	u64 val;
 
 	jdecoder = json_object_new_object();
@@ -321,7 +323,51 @@ struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
 					       jobj);
 	}
 
+	/* Endpoints don't have targets, they *are* targets */
+	if (cxl_port_is_endpoint(port))
+		return jdecoder;
+
+	val = cxl_decoder_get_nr_targets(decoder);
+	jobj = json_object_new_int(val);
+	if (jobj)
+		json_object_object_add(jdecoder, "nr_targets", jobj);
+
+	if (!(flags & UTIL_JSON_TARGETS) ||
+	    !cxl_decoder_get_nr_targets(decoder))
+		return jdecoder;
+
+	jtargets = json_object_new_array();
+	if (!jtargets)
+		return jdecoder;
+
+	cxl_target_foreach(decoder, target) {
+		struct json_object *jtarget = json_object_new_object();
+
+		if (!jtarget)
+			continue;
+
+		devname = cxl_target_get_devname(target);
+		jobj = json_object_new_string(devname);
+		if (jobj)
+			json_object_object_add(jtarget, "target", jobj);
+
+		val = cxl_target_get_position(target);
+		jobj = json_object_new_int(val);
+		if (jobj)
+			json_object_object_add(jtarget, "position", jobj);
+
+		val = cxl_target_get_id(target);
+		jobj = util_json_object_hex(val, flags);
+		if (jobj)
+			json_object_object_add(jtarget, "id", jobj);
+
+		json_object_array_add(jtargets, jtarget);
+	}
+
+	json_object_object_add(jdecoder, "targets", jtargets);
+
 	return jdecoder;
+
 }
 
 static struct json_object *__util_cxl_port_to_json(struct cxl_port *port,
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 5e3092364e4a..877f42c9145e 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -67,10 +67,22 @@ static void free_memdev(struct cxl_memdev *memdev, struct list_head *head)
 	free(memdev);
 }
 
+static void free_target(struct cxl_target *target, struct list_head *head)
+{
+	if (head)
+		list_del_from(head, &target->list);
+	free(target->dev_path);
+	free(target);
+}
+
 static void free_decoder(struct cxl_decoder *decoder, struct list_head *head)
 {
+	struct cxl_target *target, *_t;
+
 	if (head)
 		list_del_from(head, &decoder->list);
+	list_for_each_safe(&decoder->targets, target, _t, list)
+		free_target(target, &decoder->targets);
 	free(decoder->dev_buf);
 	free(decoder->dev_path);
 	free(decoder);
@@ -856,6 +868,7 @@ static void *add_cxl_decoder(void *parent, int id, const char *cxldecoder_base)
 	struct cxl_port *port = parent;
 	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
 	char buf[SYSFS_ATTR_SIZE];
+	char *target_id, *save;
 	size_t i;
 
 	dbg(ctx, "%s: base: \'%s\'\n", devname, cxldecoder_base);
@@ -870,6 +883,7 @@ static void *add_cxl_decoder(void *parent, int id, const char *cxldecoder_base)
 	decoder->id = id;
 	decoder->ctx = ctx;
 	decoder->port = port;
+	list_head_init(&decoder->targets);
 
 	decoder->dev_path = strdup(cxldecoder_base);
 	if (!decoder->dev_path)
@@ -935,6 +949,36 @@ static void *add_cxl_decoder(void *parent, int id, const char *cxldecoder_base)
 	}
 	}
 
+	sprintf(path, "%s/target_list", cxldecoder_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		buf[0] = '\0';
+
+	for (i = 0, target_id = strtok_r(buf, ",", &save); target_id;
+	     target_id = strtok_r(NULL, ",", &save), i++) {
+		int did = strtoul(target_id, NULL, 0);
+		struct cxl_target *target = calloc(1, sizeof(*target));
+
+		if (!target)
+			break;
+
+		target->id = did;
+		target->position = i;
+		target->decoder = decoder;
+		sprintf(port->dev_buf, "%s/dport%d", port->dev_path, did);
+		target->dev_path = realpath(port->dev_buf, NULL);
+		if (!target->dev_path) {
+			free(target);
+			break;
+		}
+		dbg(ctx, "%s: target%ld %s\n", devname, i, target->dev_path);
+		list_add(&decoder->targets, &target->list);
+	}
+
+	if (target_id)
+		err(ctx, "%s: failed to parse target%ld\n",
+		    devpath_to_devname(cxldecoder_base), i);
+	decoder->nr_targets = i;
+
 	cxl_decoder_foreach(port, decoder_dup)
 		if (decoder_dup->id == decoder->id) {
 			free_decoder(decoder, NULL);
@@ -1044,11 +1088,82 @@ CXL_EXPORT bool cxl_decoder_is_locked(struct cxl_decoder *decoder)
 	return decoder->locked;
 }
 
+CXL_EXPORT int cxl_decoder_get_nr_targets(struct cxl_decoder *decoder)
+{
+	return decoder->nr_targets;
+}
+
 CXL_EXPORT const char *cxl_decoder_get_devname(struct cxl_decoder *decoder)
 {
 	return devpath_to_devname(decoder->dev_path);
 }
 
+CXL_EXPORT struct cxl_target *cxl_target_get_first(struct cxl_decoder *decoder)
+{
+	return list_top(&decoder->targets, struct cxl_target, list);
+}
+
+CXL_EXPORT struct cxl_decoder *cxl_target_get_decoder(struct cxl_target *target)
+{
+	return target->decoder;
+}
+
+CXL_EXPORT struct cxl_target *cxl_target_get_next(struct cxl_target *target)
+{
+	struct cxl_decoder *decoder = cxl_target_get_decoder(target);
+
+	return list_next(&decoder->targets, target, list);
+}
+
+CXL_EXPORT const char *cxl_target_get_devname(struct cxl_target *target)
+{
+	return devpath_to_devname(target->dev_path);
+}
+
+CXL_EXPORT unsigned long cxl_target_get_id(struct cxl_target *target)
+{
+	return target->id;
+}
+
+CXL_EXPORT int cxl_target_get_position(struct cxl_target *target)
+{
+	return target->position;
+}
+
+CXL_EXPORT bool cxl_target_maps_memdev(struct cxl_target *target,
+					struct cxl_memdev *memdev)
+{
+	struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev);
+
+	dbg(ctx, "memdev: %s target: %s\n", memdev->host_path,
+	    target->dev_path);
+
+	return !!strstr(memdev->host_path, target->dev_path);
+}
+
+CXL_EXPORT struct cxl_target *
+cxl_decoder_get_target_by_memdev(struct cxl_decoder *decoder,
+				 struct cxl_memdev *memdev)
+{
+	struct cxl_target *target;
+
+	cxl_target_foreach(decoder, target)
+		if (cxl_target_maps_memdev(target, memdev))
+			return target;
+	return NULL;
+}
+
+CXL_EXPORT struct cxl_target *
+cxl_decoder_get_target_by_position(struct cxl_decoder *decoder, int position)
+{
+	struct cxl_target *target;
+
+	cxl_target_foreach(decoder, target)
+		if (target->position == position)
+			return target;
+	return NULL;
+}
+
 static void *add_cxl_port(void *parent, int id, const char *cxlport_base)
 {
 	const char *devname = devpath_to_devname(cxlport_base);
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index 22babb760f23..cb33180c2aa8 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -125,10 +125,20 @@ global:
 	cxl_decoder_get_resource;
 	cxl_decoder_get_size;
 	cxl_decoder_get_devname;
+	cxl_decoder_get_target_by_memdev;
+	cxl_decoder_get_target_by_position;
+	cxl_decoder_get_nr_targets;
 	cxl_decoder_get_target_type;
 	cxl_decoder_is_pmem_capable;
 	cxl_decoder_is_volatile_capable;
 	cxl_decoder_is_mem_capable;
 	cxl_decoder_is_accelmem_capable;
 	cxl_decoder_is_locked;
+	cxl_target_get_first;
+	cxl_target_get_next;
+	cxl_target_get_decoder;
+	cxl_target_get_position;
+	cxl_target_get_id;
+	cxl_target_get_devname;
+	cxl_target_maps_memdev;
 } LIBCXL_1;
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index 439ed9305397..abda0e51644c 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -104,6 +104,11 @@ struct cxl_decoder *cxl_decoder_get_next(struct cxl_decoder *decoder);
 unsigned long long cxl_decoder_get_resource(struct cxl_decoder *decoder);
 unsigned long long cxl_decoder_get_size(struct cxl_decoder *decoder);
 const char *cxl_decoder_get_devname(struct cxl_decoder *decoder);
+struct cxl_target *cxl_decoder_get_target_by_memdev(struct cxl_decoder *decoder,
+						    struct cxl_memdev *memdev);
+struct cxl_target *
+cxl_decoder_get_target_by_position(struct cxl_decoder *decoder, int position);
+int cxl_decoder_get_nr_targets(struct cxl_decoder *decoder);
 struct cxl_ctx *cxl_decoder_get_ctx(struct cxl_decoder *decoder);
 int cxl_decoder_get_id(struct cxl_decoder *decoder);
 struct cxl_port *cxl_decoder_get_port(struct cxl_decoder *decoder);
@@ -126,6 +131,20 @@ bool cxl_decoder_is_locked(struct cxl_decoder *decoder);
 	for (decoder = cxl_decoder_get_first(port); decoder != NULL;           \
 	     decoder = cxl_decoder_get_next(decoder))
 
+struct cxl_target;
+struct cxl_target *cxl_target_get_first(struct cxl_decoder *decoder);
+struct cxl_target *cxl_target_get_next(struct cxl_target *target);
+struct cxl_decoder *cxl_target_get_decoder(struct cxl_target *target);
+int cxl_target_get_position(struct cxl_target *target);
+unsigned long cxl_target_get_id(struct cxl_target *target);
+const char *cxl_target_get_devname(struct cxl_target *target);
+bool cxl_target_maps_memdev(struct cxl_target *target,
+			    struct cxl_memdev *memdev);
+
+#define cxl_target_foreach(decoder, target)                                    \
+	for (target = cxl_target_get_first(decoder); target != NULL;           \
+	     target = cxl_target_get_next(target))
+
 struct cxl_endpoint;
 struct cxl_endpoint *cxl_endpoint_get_first(struct cxl_port *parent);
 struct cxl_endpoint *cxl_endpoint_get_next(struct cxl_endpoint *endpoint);
diff --git a/cxl/list.c b/cxl/list.c
index d70192a8cecf..27c963a93aaa 100644
--- a/cxl/list.c
+++ b/cxl/list.c
@@ -41,6 +41,8 @@ static const struct option options[] = {
 		   "filter by CXL decoder device name(s) / class"),
 	OPT_BOOLEAN('D', "decoders", &param.decoders,
 		    "include CXL decoder info"),
+	OPT_BOOLEAN('T', "targets", &param.targets,
+		    "include CXL target data with decoders"),
 	OPT_BOOLEAN('i', "idle", &param.idle, "include disabled devices"),
 	OPT_BOOLEAN('u', "human", &param.human,
 		    "use human friendly number formats "),


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 32/37] cxl/list: Use 'physical_node' for root port attachment detection
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (30 preceding siblings ...)
  2022-01-24  0:54 ` [ndctl PATCH 31/37] cxl/list: Extend decoder objects with target information Dan Williams
@ 2022-01-24  0:54 ` Dan Williams
  2022-01-24  0:54 ` [ndctl PATCH 33/37] cxl/list: Reuse the --target option for ports Dan Williams
                   ` (4 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:54 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

Platform firmware objects like ACPI0016 link from /sys/bus/acpi to
/sys/bus/pci via a 'physical_node' attribute. Consider that link when
attempting to link memdevs to root ports.

Emit it in the the target listing as the 'alias' for the listed target
device.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/lib/libcxl.txt |    6 ++++++
 cxl/json.c                       |    8 ++++++++
 cxl/lib/libcxl.c                 |   16 +++++++++++++++-
 cxl/lib/libcxl.sym               |    1 +
 cxl/lib/private.h                |    1 +
 cxl/libcxl.h                     |    1 +
 6 files changed, 32 insertions(+), 1 deletion(-)

diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index bd92fef962fa..a68a58b06c5d 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -389,6 +389,7 @@ unsigned long cxl_target_get_id(struct cxl_target *target);
 const char *cxl_target_get_devname(struct cxl_target *target);
 bool cxl_target_maps_memdev(struct cxl_target *target,
                            struct cxl_memdev *memdev);
+const char *cxl_target_get_physical_node(struct cxl_target *target);
 ----
 The position of a decoder along with the interleave granularity dictate
 which address in the decoder's resource range map to which port.
@@ -410,6 +411,11 @@ The cxl_target_maps_memdev() helper is the companion of
 cxl_decoder_get_target_by_memdev() to determine which downstream ports /
 targets are capable of mapping which memdevs.
 
+Some platform firmware implementations define an alias / companion
+device to represent the root of a PCI device hierarchy. The
+cxl_target_get_physical_node() helper returns the device name of that
+companion object in the PCI hierarchy.
+
 include::../../copyright.txt[]
 
 SEE ALSO
diff --git a/cxl/json.c b/cxl/json.c
index 3a3790955370..d81aed8f7729 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -342,6 +342,7 @@ struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
 
 	cxl_target_foreach(decoder, target) {
 		struct json_object *jtarget = json_object_new_object();
+		const char *phys_node;
 
 		if (!jtarget)
 			continue;
@@ -351,6 +352,13 @@ struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
 		if (jobj)
 			json_object_object_add(jtarget, "target", jobj);
 
+		phys_node = cxl_target_get_physical_node(target);
+		if (phys_node) {
+			jobj = json_object_new_string(phys_node);
+			if (jobj)
+				json_object_object_add(jtarget, "alias", jobj);
+		}
+
 		val = cxl_target_get_position(target);
 		jobj = json_object_new_int(val);
 		if (jobj)
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 877f42c9145e..7bf79492ca88 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -72,6 +72,7 @@ static void free_target(struct cxl_target *target, struct list_head *head)
 	if (head)
 		list_del_from(head, &target->list);
 	free(target->dev_path);
+	free(target->phys_path);
 	free(target);
 }
 
@@ -970,7 +971,11 @@ static void *add_cxl_decoder(void *parent, int id, const char *cxldecoder_base)
 			free(target);
 			break;
 		}
-		dbg(ctx, "%s: target%ld %s\n", devname, i, target->dev_path);
+		sprintf(port->dev_buf, "%s/dport%d/physical_node", port->dev_path, did);
+		target->phys_path = realpath(port->dev_buf, NULL);
+		dbg(ctx, "%s: target%ld %s phys_path: %s\n", devname, i,
+		    target->dev_path,
+		    target->phys_path ? target->phys_path : "none");
 		list_add(&decoder->targets, &target->list);
 	}
 
@@ -1138,9 +1143,18 @@ CXL_EXPORT bool cxl_target_maps_memdev(struct cxl_target *target,
 	dbg(ctx, "memdev: %s target: %s\n", memdev->host_path,
 	    target->dev_path);
 
+	if (target->phys_path)
+		return !!strstr(memdev->host_path, target->phys_path);
 	return !!strstr(memdev->host_path, target->dev_path);
 }
 
+CXL_EXPORT const char *cxl_target_get_physical_node(struct cxl_target *target)
+{
+	if (!target->phys_path)
+		return NULL;
+	return devpath_to_devname(target->phys_path);
+}
+
 CXL_EXPORT struct cxl_target *
 cxl_decoder_get_target_by_memdev(struct cxl_decoder *decoder,
 				 struct cxl_memdev *memdev)
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index cb33180c2aa8..ce01298d8b43 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -141,4 +141,5 @@ global:
 	cxl_target_get_id;
 	cxl_target_get_devname;
 	cxl_target_maps_memdev;
+	cxl_target_get_physical_node;
 } LIBCXL_1;
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
index 1743a244877a..7e7742d523f3 100644
--- a/cxl/lib/private.h
+++ b/cxl/lib/private.h
@@ -77,6 +77,7 @@ struct cxl_target {
 	struct list_node list;
 	struct cxl_decoder *decoder;
 	char *dev_path;
+	char *phys_path;
 	int id, position;
 };
 
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index abda0e51644c..0e484cc79f28 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -140,6 +140,7 @@ unsigned long cxl_target_get_id(struct cxl_target *target);
 const char *cxl_target_get_devname(struct cxl_target *target);
 bool cxl_target_maps_memdev(struct cxl_target *target,
 			    struct cxl_memdev *memdev);
+const char *cxl_target_get_physical_node(struct cxl_target *target);
 
 #define cxl_target_foreach(decoder, target)                                    \
 	for (target = cxl_target_get_first(decoder); target != NULL;           \


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 33/37] cxl/list: Reuse the --target option for ports
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (31 preceding siblings ...)
  2022-01-24  0:54 ` [ndctl PATCH 32/37] cxl/list: Use 'physical_node' for root port attachment detection Dan Williams
@ 2022-01-24  0:54 ` Dan Williams
  2022-01-24  0:54 ` [ndctl PATCH 34/37] cxl/list: Support filtering memdevs by decoders Dan Williams
                   ` (3 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:54 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

It is useful to be able to dump the dport-id to host-device-name. Rather
than introduce a new option, just interpret --target as "list dports" for
port objects.

$ cxl list -BTu -b ACPI.CXL
{
  "bus":"root0",
  "provider":"ACPI.CXL",
  "nr_dports":1,
  "dports":[
    {
      "dport":"ACPI0016:00",
      "alias":"pci0000:34",
      "id":"0"
    }
  ]
}

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 .clang-format                    |    1 
 Documentation/cxl/cxl-list.txt   |   18 ++++++
 Documentation/cxl/lib/libcxl.txt |   27 +++++++++
 cxl/json.c                       |   56 +++++++++++++++++++-
 cxl/lib/libcxl.c                 |  109 ++++++++++++++++++++++++++++++++++++++
 cxl/lib/libcxl.sym               |    7 ++
 cxl/lib/private.h                |   13 +++++
 cxl/libcxl.h                     |   12 ++++
 cxl/list.c                       |    2 -
 9 files changed, 240 insertions(+), 5 deletions(-)

diff --git a/.clang-format b/.clang-format
index 47fb657ff09a..c75348764105 100644
--- a/.clang-format
+++ b/.clang-format
@@ -82,6 +82,7 @@ ForEachMacros:
   - 'cxl_port_foreach'
   - 'cxl_decoder_foreach'
   - 'cxl_target_foreach'
+  - 'cxl_dport_foreach'
   - 'cxl_endpoint_foreach'
   - 'daxctl_dev_foreach'
   - 'daxctl_mapping_foreach'
diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index 20ff2cb54fcb..e1299d9fbd84 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -272,7 +272,23 @@ OPTIONS
 
 -T::
 --targets::
-	Extend decoder listings with downstream port target information.
+	Extend decoder listings with downstream port target information, and /
+	or port and bus listings with the downstream port information.
+----
+# cxl list -BTu -b ACPI.CXL
+{
+  "bus":"root0",
+  "provider":"ACPI.CXL",
+  "nr_dports":1,
+  "dports":[
+    {
+      "dport":"ACPI0016:00",
+      "alias":"pci0000:34",
+      "id":"0"
+    }
+  ]
+}
+----
 
 --debug::
 	If the cxl tool was built with debug enabled, turn on debug
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index a68a58b06c5d..2e8570d18a86 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -245,6 +245,7 @@ bool cxl_port_is_root(struct cxl_port *port);
 bool cxl_port_is_switch(struct cxl_port *port);
 bool cxl_port_is_endpoint(struct cxl_port *port);
 bool cxl_port_hosts_memdev(struct cxl_port *port, struct cxl_memdev *memdev);
+int cxl_port_get_nr_dports(struct cxl_port *port);
 ----
 The port type is communicated via cxl_port_is_<type>(). An 'enabled' port
 is one that has succeeded in discovering the CXL component registers in
@@ -256,6 +257,32 @@ of intervening switch ports, and a terminal endpoint port.
 cxl_port_hosts_memdev() returns true if the port's host appears in the
 memdev host's device topology ancestry.
 
+==== DPORTS
+A CXL dport object represents a CXL / PCIe Switch Downstream Port, or a
+CXL / PCIe host bridge.
+
+===== DPORT: Enumeration
+----
+struct cxl_dport *cxl_dport_get_first(struct cxl_port *port);
+struct cxl_dport *cxl_dport_get_next(struct cxl_dport *dport);
+
+#define cxl_dport_foreach(port, dport)                                     \
+       for (dport = cxl_dport_get_first(port); dport != NULL;              \
+            dport = cxl_dport_get_next(dport))
+
+----
+
+===== DPORT: Attributes
+----
+const char *cxl_dport_get_devname(struct cxl_dport *dport);
+const char *cxl_dport_get_physical_node(struct cxl_dport *dport);
+int cxl_dport_get_id(struct cxl_dport *dport);
+----
+The id of a dport is the hardware idenfifier used by an upstream port to
+reference a downstream port. The physical node of a dport is only
+available for platform firmware defined downstream ports and alias the
+companion object, like a PCI host bridge, in the PCI device hierarchy.
+
 ENDPOINTS
 ---------
 CXL endpoint objects encapsulate the set of host-managed device-memory
diff --git a/cxl/json.c b/cxl/json.c
index d81aed8f7729..4fb5eec11077 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -241,6 +241,58 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
 	return jdev;
 }
 
+static struct json_object *util_cxl_dports_to_json(struct json_object *jport,
+						   struct cxl_port *port,
+						   unsigned long flags)
+{
+	struct json_object *jobj, *jdports;
+	struct cxl_dport *dport;
+	int val;
+
+	val = cxl_port_get_nr_dports(port);
+	if (!val || !(flags & UTIL_JSON_TARGETS))
+		return jport;
+
+	jobj = json_object_new_int(val);
+	if (jobj)
+		json_object_object_add(jport, "nr_dports", jobj);
+
+	jdports = json_object_new_array();
+	if (!jdports)
+		return jport;
+
+	cxl_dport_foreach(port, dport) {
+		struct json_object *jdport;
+		const char *phys_node;
+
+		jdport = json_object_new_object();
+		if (!jdport)
+			continue;
+
+		jobj = json_object_new_string(cxl_dport_get_devname(dport));
+		if (jobj)
+			json_object_object_add(jdport, "dport", jobj);
+
+		phys_node = cxl_dport_get_physical_node(dport);
+		if (phys_node) {
+			jobj = json_object_new_string(phys_node);
+			if (jobj)
+				json_object_object_add(jdport, "alias", jobj);
+		}
+
+		val = cxl_dport_get_id(dport);
+		jobj = util_json_object_hex(val, flags);
+		if (jobj)
+			json_object_object_add(jdport, "id", jobj);
+
+		json_object_array_add(jdports, jdport);
+	}
+
+	json_object_object_add(jport, "dports", jdports);
+
+	return jport;
+}
+
 struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
 					 unsigned long flags)
 {
@@ -259,7 +311,7 @@ struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
 	if (jobj)
 		json_object_object_add(jbus, "provider", jobj);
 
-	return jbus;
+	return util_cxl_dports_to_json(jbus, cxl_bus_get_port(bus), flags);
 }
 
 struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
@@ -403,7 +455,7 @@ static struct json_object *__util_cxl_port_to_json(struct cxl_port *port,
 			json_object_object_add(jport, "state", jobj);
 	}
 
-	return jport;
+	return util_cxl_dports_to_json(jport, port, flags);
 }
 
 struct json_object *util_cxl_port_to_json(struct cxl_port *port,
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 7bf79492ca88..d7a3f1084465 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -89,13 +89,24 @@ static void free_decoder(struct cxl_decoder *decoder, struct list_head *head)
 	free(decoder);
 }
 
+static void free_dport(struct cxl_dport *dport, struct list_head *head)
+{
+	if (head)
+		list_del_from(head, &dport->list);
+	free(dport->dev_buf);
+	free(dport->dev_path);
+	free(dport->phys_path);
+	free(dport);
+}
+
 static void free_port(struct cxl_port *port, struct list_head *head);
 static void free_endpoint(struct cxl_endpoint *endpoint, struct list_head *head);
 static void __free_port(struct cxl_port *port, struct list_head *head)
 {
-	struct cxl_port *child, *_c;
 	struct cxl_endpoint *endpoint, *_e;
 	struct cxl_decoder *decoder, *_d;
+	struct cxl_dport *dport, *_dp;
+	struct cxl_port *child, *_c;
 
 	if (head)
 		list_del_from(head, &port->list);
@@ -105,6 +116,8 @@ static void __free_port(struct cxl_port *port, struct list_head *head)
 		free_endpoint(endpoint, &port->endpoints);
 	list_for_each_safe(&port->decoders, decoder, _d, list)
 		free_decoder(decoder, &port->decoders);
+	list_for_each_safe(&port->dports, dport, _dp, list)
+		free_dport(dport , &port->dports);
 	kmod_module_unref(port->module);
 	free(port->dev_buf);
 	free(port->dev_path);
@@ -701,6 +714,7 @@ static int cxl_port_init(struct cxl_port *port, struct cxl_port *parent_port,
 	list_head_init(&port->child_ports);
 	list_head_init(&port->endpoints);
 	list_head_init(&port->decoders);
+	list_head_init(&port->dports);
 
 	port->dev_path = strdup(cxlport_base);
 	if (!port->dev_path)
@@ -1332,6 +1346,99 @@ CXL_EXPORT struct cxl_bus *cxl_port_to_bus(struct cxl_port *port)
 	return container_of(port, struct cxl_bus, port);
 }
 
+static void *add_cxl_dport(void *parent, int id, const char *cxldport_base)
+{
+	const char *devname = devpath_to_devname(cxldport_base);
+	struct cxl_dport *dport, *dport_dup;
+	struct cxl_port *port = parent;
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+
+	dbg(ctx, "%s: base: \'%s\'\n", devname, cxldport_base);
+
+	dport = calloc(1, sizeof(*dport));
+	if (!dport)
+		return NULL;
+
+	dport->id = id;
+	dport->port = port;
+
+	dport->dev_path = realpath(cxldport_base, NULL);
+	if (!dport->dev_path)
+		goto err;
+
+	dport->dev_buf = calloc(1, strlen(cxldport_base) + 50);
+	if (!dport->dev_buf)
+		goto err;
+	dport->buf_len = strlen(cxldport_base) + 50;
+
+	sprintf(dport->dev_buf, "%s/physical_node", cxldport_base);
+	dport->phys_path = realpath(dport->dev_buf, NULL);
+
+	cxl_dport_foreach(port, dport_dup)
+		if (dport_dup->id == dport->id) {
+			free_dport(dport, NULL);
+			return dport_dup;
+		}
+
+	port->nr_dports++;
+	list_add(&port->dports, &dport->list);
+	return dport;
+
+err:
+	free_dport(dport, NULL);
+	return NULL;
+}
+
+static void cxl_dports_init(struct cxl_port *port)
+{
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+
+	if (port->dports_init)
+		return;
+
+	port->dports_init = 1;
+
+	sysfs_device_parse(ctx, port->dev_path, "dport", port, add_cxl_dport);
+}
+
+CXL_EXPORT int cxl_port_get_nr_dports(struct cxl_port *port)
+{
+	if (!port->dports_init)
+		cxl_dports_init(port);
+	return port->nr_dports;
+}
+
+CXL_EXPORT struct cxl_dport *cxl_dport_get_first(struct cxl_port *port)
+{
+	cxl_dports_init(port);
+
+	return list_top(&port->dports, struct cxl_dport, list);
+}
+
+CXL_EXPORT struct cxl_dport *cxl_dport_get_next(struct cxl_dport *dport)
+{
+	struct cxl_port *port = dport->port;
+
+	return list_next(&port->dports, dport, list);
+}
+
+CXL_EXPORT const char *cxl_dport_get_devname(struct cxl_dport *dport)
+{
+	return devpath_to_devname(dport->dev_path);
+}
+
+CXL_EXPORT const char *cxl_dport_get_physical_node(struct cxl_dport *dport)
+{
+	if (!dport->phys_path)
+		return NULL;
+	return devpath_to_devname(dport->phys_path);
+}
+
+CXL_EXPORT int cxl_dport_get_id(struct cxl_dport *dport)
+{
+	return dport->id;
+}
+
 static void *add_cxl_bus(void *parent, int id, const char *cxlbus_base)
 {
 	const char *devname = devpath_to_devname(cxlbus_base);
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index ce01298d8b43..0190b132d3d6 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -101,6 +101,8 @@ global:
 	cxl_port_get_host;
 	cxl_port_get_bus;
 	cxl_port_hosts_memdev;
+	cxl_port_get_nr_dports;
+	cxl_port_get_next_all;
 	cxl_endpoint_get_first;
 	cxl_endpoint_get_next;
 	cxl_endpoint_get_devname;
@@ -142,4 +144,9 @@ global:
 	cxl_target_get_devname;
 	cxl_target_maps_memdev;
 	cxl_target_get_physical_node;
+	cxl_dport_get_first;
+	cxl_dport_get_next;
+	cxl_dport_get_devname;
+	cxl_dport_get_physical_node;
+	cxl_dport_get_id;
 } LIBCXL_1;
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
index 7e7742d523f3..f483c308a8b5 100644
--- a/cxl/lib/private.h
+++ b/cxl/lib/private.h
@@ -38,6 +38,16 @@ struct cxl_memdev {
 	struct cxl_endpoint *endpoint;
 };
 
+struct cxl_dport {
+	int id;
+	void *dev_buf;
+	size_t buf_len;
+	char *dev_path;
+	char *phys_path;
+	struct cxl_port *port;
+	struct list_node list;
+};
+
 enum cxl_port_type {
 	CXL_PORT_ROOT,
 	CXL_PORT_SWITCH,
@@ -53,6 +63,8 @@ struct cxl_port {
 	int ports_init;
 	int endpoints_init;
 	int decoders_init;
+	int dports_init;
+	int nr_dports;
 	struct cxl_ctx *ctx;
 	struct cxl_bus *bus;
 	enum cxl_port_type type;
@@ -62,6 +74,7 @@ struct cxl_port {
 	struct list_head child_ports;
 	struct list_head endpoints;
 	struct list_head decoders;
+	struct list_head dports;
 };
 
 struct cxl_bus {
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index 0e484cc79f28..07f4a311d29d 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -93,11 +93,23 @@ bool cxl_port_is_endpoint(struct cxl_port *port);
 struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
 const char *cxl_port_get_host(struct cxl_port *port);
 bool cxl_port_hosts_memdev(struct cxl_port *port, struct cxl_memdev *memdev);
+int cxl_port_get_nr_dports(struct cxl_port *port);
 
 #define cxl_port_foreach(parent, port)                                         \
 	for (port = cxl_port_get_first(parent); port != NULL;                  \
 	     port = cxl_port_get_next(port))
 
+struct cxl_dport;
+struct cxl_dport *cxl_dport_get_first(struct cxl_port *port);
+struct cxl_dport *cxl_dport_get_next(struct cxl_dport *dport);
+const char *cxl_dport_get_devname(struct cxl_dport *dport);
+const char *cxl_dport_get_physical_node(struct cxl_dport *dport);
+int cxl_dport_get_id(struct cxl_dport *dport);
+
+#define cxl_dport_foreach(port, dport)                                         \
+	for (dport = cxl_dport_get_first(port); dport != NULL;                 \
+	     dport = cxl_dport_get_next(dport))
+
 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);
diff --git a/cxl/list.c b/cxl/list.c
index 27c963a93aaa..de96ff91fb5d 100644
--- a/cxl/list.c
+++ b/cxl/list.c
@@ -42,7 +42,7 @@ static const struct option options[] = {
 	OPT_BOOLEAN('D', "decoders", &param.decoders,
 		    "include CXL decoder info"),
 	OPT_BOOLEAN('T', "targets", &param.targets,
-		    "include CXL target data with decoders"),
+		    "include CXL target data with decoders or ports"),
 	OPT_BOOLEAN('i', "idle", &param.idle, "include disabled devices"),
 	OPT_BOOLEAN('u', "human", &param.human,
 		    "use human friendly number formats "),


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 34/37] cxl/list: Support filtering memdevs by decoders
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (32 preceding siblings ...)
  2022-01-24  0:54 ` [ndctl PATCH 33/37] cxl/list: Reuse the --target option for ports Dan Williams
@ 2022-01-24  0:54 ` Dan Williams
  2022-01-24  0:54 ` [ndctl PATCH 35/37] cxl/list: Support filtering memdevs by ports Dan Williams
                   ` (2 subsequent siblings)
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:54 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

In order to filter memdevs by decoders all the ports in the hierarchy need
to be iterated, so introduce cxl_port_foreach_all() that starts at the bus
and does a depth first iteration of all the descendant ports.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 .clang-format                    |    1 +
 Documentation/cxl/cxl-list.txt   |   13 ++++++++++
 Documentation/cxl/lib/libcxl.txt |   11 +++++++++
 cxl/filter.c                     |   48 ++++++++++++++++++++++++++++++++++++++
 cxl/lib/libcxl.c                 |   13 ++++++++++
 cxl/libcxl.h                     |    6 +++++
 6 files changed, 92 insertions(+)

diff --git a/.clang-format b/.clang-format
index c75348764105..6aabcb68e6a9 100644
--- a/.clang-format
+++ b/.clang-format
@@ -84,6 +84,7 @@ ForEachMacros:
   - 'cxl_target_foreach'
   - 'cxl_dport_foreach'
   - 'cxl_endpoint_foreach'
+  - 'cxl_port_foreach_all'
   - 'daxctl_dev_foreach'
   - 'daxctl_mapping_foreach'
   - 'daxctl_region_foreach'
diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index e1299d9fbd84..04e831ed3f34 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -61,6 +61,19 @@ one or more memdevs. For example:
   }
 ]
 ----
+Additionally, when provisioning new interleave configurations it is
+useful to know which memdevs can be referenced by a given decoder like a
+root decoder:
+----
+# cxl list -Mu -d decoder0.0
+{
+  "memdev":"mem0",
+  "pmem_size":"256.00 MiB (268.44 MB)",
+  "ram_size":0,
+  "serial":"0",
+  "host":"0000:35:00.0"
+}
+----
 
 The --human option in addition to reformatting some fields to more human
 friendly strings also unwraps the array to reduce the number of lines of
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index 2e8570d18a86..5ad3027acd24 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -219,10 +219,18 @@ struct cxl_port *cxl_port_get_parent(struct cxl_port *port);
 struct cxl_ctx *cxl_port_get_ctx(struct cxl_port *port);
 const char *cxl_port_get_host(struct cxl_port *port);
 struct cxl_port *cxl_decoder_get_port(struct cxl_decoder *decoder);
+struct cxl_port *cxl_port_get_next_all(struct cxl_port *port,
+                                       const struct cxl_port *top);
 
 #define cxl_port_foreach(parent, port)                                      \
        for (port = cxl_port_get_first(parent); port != NULL;                \
             port = cxl_port_get_next(port))
+
+#define cxl_port_foreach_all(top, port)                                        \
+       for (port = cxl_port_get_first(top); port != NULL;                     \
+            port = cxl_port_get_next_all(port, top))
+
+
 ----
 A bus object encapsulates a CXL port object. Use cxl_bus_get_port() to
 use generic port APIs on root objects.
@@ -236,6 +244,9 @@ that hierarchy via cxl_port_get_bus().
 The host of a port is the corresponding device name of the PCIe Root
 Port, or Switch Upstream Port with CXL capabilities.
 
+The cxl_port_foreach_all() helper does a depth first iteration of all
+ports beneath the 'top' port argument.
+
 === PORT: Attributes
 ----
 const char *cxl_port_get_devname(struct cxl_port *port);
diff --git a/cxl/filter.c b/cxl/filter.c
index 05ede9116559..c972545eb0de 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -441,6 +441,51 @@ util_cxl_decoder_filter_by_memdev(struct cxl_decoder *decoder,
 	return NULL;
 }
 
+static bool __memdev_filter_by_decoder(struct cxl_memdev *memdev,
+				       struct cxl_port *port, const char *ident)
+{
+	struct cxl_decoder *decoder;
+	struct cxl_endpoint *endpoint;
+
+	cxl_decoder_foreach(port, decoder) {
+		if (!util_cxl_decoder_filter(decoder, ident))
+			continue;
+		if (cxl_decoder_get_target_by_memdev(decoder, memdev))
+			return true;
+	}
+
+	cxl_endpoint_foreach(port, endpoint)
+		if (__memdev_filter_by_decoder(
+			    memdev, cxl_endpoint_get_port(endpoint), ident))
+			return true;
+	return false;
+}
+
+static struct cxl_memdev *
+util_cxl_memdev_filter_by_decoder(struct cxl_memdev *memdev, const char *ident)
+{
+	struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev);
+	struct cxl_bus *bus;
+
+	if (!ident)
+		return memdev;
+
+	cxl_bus_foreach(ctx, bus) {
+		struct cxl_port *port, *top;
+
+		port = cxl_bus_get_port(bus);
+		if (__memdev_filter_by_decoder(memdev, port, ident))
+			return memdev;
+
+		top = port;
+		cxl_port_foreach_all(top, port)
+			if (__memdev_filter_by_decoder(memdev, port, ident))
+				return memdev;
+	}
+
+	return NULL;
+}
+
 static unsigned long params_to_flags(struct cxl_filter_params *param)
 {
 	unsigned long flags = 0;
@@ -599,6 +644,9 @@ static void walk_endpoints(struct cxl_port *port, struct cxl_filter_params *p,
 			if (!util_cxl_memdev_filter(memdev, p->memdev_filter,
 						    p->serial_filter))
 				continue;
+			if (!util_cxl_memdev_filter_by_decoder(
+				    memdev, p->decoder_filter))
+				continue;
 			if (!p->idle && !cxl_memdev_is_enabled(memdev))
 				continue;
 			jobj = util_cxl_memdev_to_json(memdev, flags);
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index d7a3f1084465..4ebb8b9a5e7f 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -1257,6 +1257,19 @@ CXL_EXPORT struct cxl_port *cxl_port_get_next(struct cxl_port *port)
 	return list_next(&parent_port->child_ports, port, list);
 }
 
+CXL_EXPORT struct cxl_port *cxl_port_get_next_all(struct cxl_port *port,
+						  const struct cxl_port *top)
+{
+	struct cxl_port *child, *iter = port;
+
+	child = cxl_port_get_first(iter);
+	if (child)
+		return child;
+	while (!cxl_port_get_next(iter) && iter->parent && iter->parent != top)
+		iter = iter->parent;
+	return cxl_port_get_next(iter);
+}
+
 CXL_EXPORT const char *cxl_port_get_devname(struct cxl_port *port)
 {
 	return devpath_to_devname(port->dev_path);
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index 07f4a311d29d..874c38188a2c 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -94,11 +94,17 @@ struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
 const char *cxl_port_get_host(struct cxl_port *port);
 bool cxl_port_hosts_memdev(struct cxl_port *port, struct cxl_memdev *memdev);
 int cxl_port_get_nr_dports(struct cxl_port *port);
+struct cxl_port *cxl_port_get_next_all(struct cxl_port *port,
+				       const struct cxl_port *top);
 
 #define cxl_port_foreach(parent, port)                                         \
 	for (port = cxl_port_get_first(parent); port != NULL;                  \
 	     port = cxl_port_get_next(port))
 
+#define cxl_port_foreach_all(top, port)                                        \
+	for (port = cxl_port_get_first(top); port != NULL;                     \
+	     port = cxl_port_get_next_all(port, top))
+
 struct cxl_dport;
 struct cxl_dport *cxl_dport_get_first(struct cxl_port *port);
 struct cxl_dport *cxl_dport_get_next(struct cxl_dport *dport);


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 35/37] cxl/list: Support filtering memdevs by ports
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (33 preceding siblings ...)
  2022-01-24  0:54 ` [ndctl PATCH 34/37] cxl/list: Support filtering memdevs by decoders Dan Williams
@ 2022-01-24  0:54 ` Dan Williams
  2022-01-24  0:55 ` [ndctl PATCH 36/37] cxl/port: Add {disable,enable}-port command Dan Williams
  2022-01-24  0:55 ` [ndctl PATCH 37/37] cxl/list: Filter dports and targets by memdevs Dan Williams
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:54 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

The ability to filter memdevs by decoders falls short when the decoder does
not have its target list programmed. So, introduce a by port filter to show
the potential memdevs that can be targeted by the decoder.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/cxl-list.txt   |    3 ++
 Documentation/cxl/lib/libcxl.txt |    7 +++++
 cxl/filter.c                     |   50 ++++++++++++++++++++++++++++++++++++++
 cxl/lib/libcxl.c                 |   23 +++++++++++++++++
 cxl/lib/libcxl.sym               |    2 ++
 cxl/libcxl.h                     |    3 ++
 6 files changed, 86 insertions(+), 2 deletions(-)

diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
index 04e831ed3f34..90e6d9f9658b 100644
--- a/Documentation/cxl/cxl-list.txt
+++ b/Documentation/cxl/cxl-list.txt
@@ -63,7 +63,8 @@ one or more memdevs. For example:
 ----
 Additionally, when provisioning new interleave configurations it is
 useful to know which memdevs can be referenced by a given decoder like a
-root decoder:
+root decoder, or mapped by a given port if the decoders are not
+configured.
 ----
 # cxl list -Mu -d decoder0.0
 {
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index 5ad3027acd24..a0fcee987b03 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -276,11 +276,12 @@ CXL / PCIe host bridge.
 ----
 struct cxl_dport *cxl_dport_get_first(struct cxl_port *port);
 struct cxl_dport *cxl_dport_get_next(struct cxl_dport *dport);
+struct cxl_dport *cxl_port_get_dport_by_memdev(struct cxl_port *port,
+                                               struct cxl_memdev *memdev);
 
 #define cxl_dport_foreach(port, dport)                                     \
        for (dport = cxl_dport_get_first(port); dport != NULL;              \
             dport = cxl_dport_get_next(dport))
-
 ----
 
 ===== DPORT: Attributes
@@ -288,12 +289,16 @@ struct cxl_dport *cxl_dport_get_next(struct cxl_dport *dport);
 const char *cxl_dport_get_devname(struct cxl_dport *dport);
 const char *cxl_dport_get_physical_node(struct cxl_dport *dport);
 int cxl_dport_get_id(struct cxl_dport *dport);
+bool cxl_dport_maps_memdev(struct cxl_dport *dport, struct cxl_memdev *memdev);
 ----
 The id of a dport is the hardware idenfifier used by an upstream port to
 reference a downstream port. The physical node of a dport is only
 available for platform firmware defined downstream ports and alias the
 companion object, like a PCI host bridge, in the PCI device hierarchy.
 
+The cxl_dport_maps_memdev() helper checks if a dport is an ancestor of a
+given memdev.
+
 ENDPOINTS
 ---------
 CXL endpoint objects encapsulate the set of host-managed device-memory
diff --git a/cxl/filter.c b/cxl/filter.c
index c972545eb0de..c691edf9501e 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -486,6 +486,53 @@ util_cxl_memdev_filter_by_decoder(struct cxl_memdev *memdev, const char *ident)
 	return NULL;
 }
 
+static bool __memdev_filter_by_port(struct cxl_memdev *memdev,
+				    struct cxl_port *port,
+				    const char *port_ident)
+{
+	struct cxl_endpoint *endpoint;
+
+	if (util_cxl_port_filter(port, port_ident, CXL_PF_SINGLE) &&
+	    cxl_port_get_dport_by_memdev(port, memdev))
+		return true;
+
+	cxl_endpoint_foreach(port, endpoint)
+		if (__memdev_filter_by_port(memdev,
+					    cxl_endpoint_get_port(endpoint),
+					    port_ident))
+			return true;
+	return false;
+}
+
+static struct cxl_memdev *
+util_cxl_memdev_filter_by_port(struct cxl_memdev *memdev, const char *bus_ident,
+			       const char *port_ident)
+{
+	struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev);
+	struct cxl_bus *bus;
+
+	if (!bus_ident && !port_ident)
+		return memdev;
+
+	cxl_bus_foreach(ctx, bus) {
+		struct cxl_port *port, *top;
+
+		port = cxl_bus_get_port(bus);
+		if (util_cxl_bus_filter(bus, bus_ident))
+			if (__memdev_filter_by_port(memdev, port,
+						    cxl_bus_get_devname(bus)))
+				return memdev;
+		if (__memdev_filter_by_port(memdev, port, port_ident))
+				return memdev;
+		top = port;
+		cxl_port_foreach_all(top, port)
+			if (__memdev_filter_by_port(memdev, port, port_ident))
+				return memdev;
+	}
+
+	return NULL;
+}
+
 static unsigned long params_to_flags(struct cxl_filter_params *param)
 {
 	unsigned long flags = 0;
@@ -647,6 +694,9 @@ static void walk_endpoints(struct cxl_port *port, struct cxl_filter_params *p,
 			if (!util_cxl_memdev_filter_by_decoder(
 				    memdev, p->decoder_filter))
 				continue;
+			if (!util_cxl_memdev_filter_by_port(
+				    memdev, p->bus_filter, p->port_filter))
+				continue;
 			if (!p->idle && !cxl_memdev_is_enabled(memdev))
 				continue;
 			jobj = util_cxl_memdev_to_json(memdev, flags);
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 4ebb8b9a5e7f..dcfc82689ca9 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -1452,6 +1452,29 @@ CXL_EXPORT int cxl_dport_get_id(struct cxl_dport *dport)
 	return dport->id;
 }
 
+CXL_EXPORT bool cxl_dport_maps_memdev(struct cxl_dport *dport,
+				      struct cxl_memdev *memdev)
+{
+	struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev);
+
+	dbg(ctx, "memdev: %s dport: %s\n", memdev->host_path, dport->dev_path);
+
+	if (dport->phys_path)
+		return !!strstr(memdev->host_path, dport->phys_path);
+	return !!strstr(memdev->host_path, dport->dev_path);
+}
+
+CXL_EXPORT struct cxl_dport *
+cxl_port_get_dport_by_memdev(struct cxl_port *port, struct cxl_memdev *memdev)
+{
+	struct cxl_dport *dport;
+
+	cxl_dport_foreach(port, dport)
+		if (cxl_dport_maps_memdev(dport, memdev))
+			return dport;
+	return NULL;
+}
+
 static void *add_cxl_bus(void *parent, int id, const char *cxlbus_base)
 {
 	const char *devname = devpath_to_devname(cxlbus_base);
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index 0190b132d3d6..2c8358e1683e 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -149,4 +149,6 @@ global:
 	cxl_dport_get_devname;
 	cxl_dport_get_physical_node;
 	cxl_dport_get_id;
+	cxl_port_get_dport_by_memdev;
+	cxl_dport_maps_memdev;
 } LIBCXL_1;
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index 874c38188a2c..c8d07bb4efc4 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -111,6 +111,9 @@ struct cxl_dport *cxl_dport_get_next(struct cxl_dport *dport);
 const char *cxl_dport_get_devname(struct cxl_dport *dport);
 const char *cxl_dport_get_physical_node(struct cxl_dport *dport);
 int cxl_dport_get_id(struct cxl_dport *dport);
+bool cxl_dport_maps_memdev(struct cxl_dport *dport, struct cxl_memdev *memdev);
+struct cxl_dport *cxl_port_get_dport_by_memdev(struct cxl_port *port,
+					       struct cxl_memdev *memdev);
 
 #define cxl_dport_foreach(port, dport)                                         \
 	for (dport = cxl_dport_get_first(port); dport != NULL;                 \


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 36/37] cxl/port: Add {disable,enable}-port command
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (34 preceding siblings ...)
  2022-01-24  0:54 ` [ndctl PATCH 35/37] cxl/list: Support filtering memdevs by ports Dan Williams
@ 2022-01-24  0:55 ` Dan Williams
  2022-01-24  0:55 ` [ndctl PATCH 37/37] cxl/list: Filter dports and targets by memdevs Dan Williams
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:55 UTC (permalink / raw)
  To: linux-cxl; +Cc: vishal.l.verma

The {disable,enable}-port commands are used for debugging port enumeration
corner cases and testing the kernel CXL device hotplug implementation.

In addition to unbinding the port from its driver, which also kicks of
unregistration of descendent ports, the disable operation also flushes the
kernels delayed workqueue for memory device removal.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/cxl-disable-port.txt |   46 ++++++
 Documentation/cxl/cxl-enable-port.txt  |   43 +++++
 Documentation/cxl/lib/libcxl.txt       |   11 +
 Documentation/cxl/meson.build          |    2 
 cxl/builtin.h                          |    2 
 cxl/cxl.c                              |    2 
 cxl/filter.c                           |   21 +--
 cxl/filter.h                           |   13 ++
 cxl/lib/libcxl.c                       |   95 +++++++++++-
 cxl/lib/libcxl.sym                     |    3 
 cxl/libcxl.h                           |    3 
 cxl/meson.build                        |    1 
 cxl/port.c                             |  253 ++++++++++++++++++++++++++++++++
 13 files changed, 471 insertions(+), 24 deletions(-)
 create mode 100644 Documentation/cxl/cxl-disable-port.txt
 create mode 100644 Documentation/cxl/cxl-enable-port.txt
 create mode 100644 cxl/port.c

diff --git a/Documentation/cxl/cxl-disable-port.txt b/Documentation/cxl/cxl-disable-port.txt
new file mode 100644
index 000000000000..de13c07d149b
--- /dev/null
+++ b/Documentation/cxl/cxl-disable-port.txt
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0
+
+cxl-disable-port(1)
+===================
+
+NAME
+----
+cxl-disable-port - activate / hot-add a given CXL port
+
+SYNOPSIS
+--------
+[verse]
+'cxl disable-port' <port0> [<port1>..<portN>] [<options>]
+
+For test and debug scenarios, disable a CXL port and any memory devices
+dependent on this port being active for CXL.mem operation.
+
+OPTIONS
+-------
+-e::
+--endpoint::
+	Toggle from treating the port arguments as Switch Port identifiers to
+	Endpoint Port identifiers.
+
+
+-f::
+--force::
+	DANGEROUS: Override the safety measure that blocks attempts to disable a
+	port if the tool determines a descendent memdev is in active usage.
+	Recall that CXL memory ranges might have been established by platform
+	firmware and disabling an active device is akin to force removing memory
+	from a running system.
+
+	Toggle from treating the port arguments as Switch Port identifiers to
+	Endpoint Port identifiers.
+
+--debug::
+	If the cxl tool was built with debug disabled, turn on debug
+	messages.
+
+
+include::../copyright.txt[]
+
+SEE ALSO
+--------
+linkcxl:cxl-disable-port[1]
diff --git a/Documentation/cxl/cxl-enable-port.txt b/Documentation/cxl/cxl-enable-port.txt
new file mode 100644
index 000000000000..9a37cef0c4d4
--- /dev/null
+++ b/Documentation/cxl/cxl-enable-port.txt
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0
+
+cxl-enable-port(1)
+==================
+
+NAME
+----
+cxl-enable-port - activate / hot-add a given CXL port
+
+SYNOPSIS
+--------
+[verse]
+'cxl enable-port' <port0> [<port1>..<portN>] [<options>]
+
+A port typically autoenables at initial device discovery. However, if it
+was manually disabled this command can trigger the kernel to activate it
+again. This involves detecting the state of the HDM (Host Managed Device
+Memory) Decoders and validating that CXL.mem is enabled for each port in
+the device's hierarchy.
+
+OPTIONS
+-------
+-e::
+--endpoint::
+	Toggle from treating the port arguments as Switch Port identifiers to
+	Endpoint Port identifiers.
+
+-m::
+--enable-memdevs::
+	Try to enable descendant memdevs after enabling the port. Recall that a
+	memdev is only enabled after all CXL ports in its device topology
+	ancestry are enabled.
+
+--debug::
+	If the cxl tool was built with debug enabled, turn on debug
+	messages.
+
+
+include::../copyright.txt[]
+
+SEE ALSO
+--------
+linkcxl:cxl-disable-port[1]
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index a0fcee987b03..27eb29ed2bae 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -247,6 +247,16 @@ Port, or Switch Upstream Port with CXL capabilities.
 The cxl_port_foreach_all() helper does a depth first iteration of all
 ports beneath the 'top' port argument.
 
+=== PORT: Control
+---
+int cxl_port_disable_invalidate(struct cxl_port *port);
+int cxl_port_enable(struct cxl_port *port);
+---
+cxl_port_disable_invalidate() is a violent operation that disables
+entire sub-tree of CXL Memory Device and Ports, only use it for test /
+debug scenarios, or ensuring that all impacted devices are deactivated
+first.
+
 === PORT: Attributes
 ----
 const char *cxl_port_get_devname(struct cxl_port *port);
@@ -315,6 +325,7 @@ struct cxl_port *cxl_endpoint_get_parent(struct cxl_endpoint *endpoint);
 struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint);
 const char *cxl_endpoint_get_host(struct cxl_endpoint *endpoint);
 struct cxl_endpoint *cxl_memdev_get_endpoint(struct cxl_memdev *memdev);
+struct cxl_endpoint *cxl_port_to_endpoint(struct cxl_port *port);
 
 #define cxl_endpoint_foreach(port, endpoint)                                 \
        for (endpoint = cxl_endpoint_get_first(port); endpoint != NULL;       \
diff --git a/Documentation/cxl/meson.build b/Documentation/cxl/meson.build
index 7618c97278b4..96f4666a828d 100644
--- a/Documentation/cxl/meson.build
+++ b/Documentation/cxl/meson.build
@@ -32,6 +32,8 @@ cxl_manpages = [
   'cxl-zero-labels.txt',
   'cxl-enable-memdev.txt',
   'cxl-disable-memdev.txt',
+  'cxl-enable-port.txt',
+  'cxl-disable-port.txt',
 ]
 
 foreach man : cxl_manpages
diff --git a/cxl/builtin.h b/cxl/builtin.h
index 621c85c2ffcc..3123d5e0d4ed 100644
--- a/cxl/builtin.h
+++ b/cxl/builtin.h
@@ -12,4 +12,6 @@ int cmd_init_labels(int argc, const char **argv, struct cxl_ctx *ctx);
 int cmd_check_labels(int argc, const char **argv, struct cxl_ctx *ctx);
 int cmd_disable_memdev(int argc, const char **argv, struct cxl_ctx *ctx);
 int cmd_enable_memdev(int argc, const char **argv, struct cxl_ctx *ctx);
+int cmd_disable_port(int argc, const char **argv, struct cxl_ctx *ctx);
+int cmd_enable_port(int argc, const char **argv, struct cxl_ctx *ctx);
 #endif /* _CXL_BUILTIN_H_ */
diff --git a/cxl/cxl.c b/cxl/cxl.c
index 78d2e9aec0bd..c20c5693fd2b 100644
--- a/cxl/cxl.c
+++ b/cxl/cxl.c
@@ -66,6 +66,8 @@ static struct cmd_struct commands[] = {
 	{ "write-labels", .c_fn = cmd_write_labels },
 	{ "disable-memdev", .c_fn = cmd_disable_memdev },
 	{ "enable-memdev", .c_fn = cmd_enable_memdev },
+	{ "disable-port", .c_fn = cmd_disable_port },
+	{ "enable-port", .c_fn = cmd_enable_port },
 };
 
 int main(int argc, const char **argv)
diff --git a/cxl/filter.c b/cxl/filter.c
index c691edf9501e..f6a23b7ff864 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -47,8 +47,8 @@ bool cxl_filter_has(const char *__filter, const char *needle)
 	return false;
 }
 
-static struct cxl_endpoint *
-util_cxl_endpoint_filter(struct cxl_endpoint *endpoint, const char *__ident)
+struct cxl_endpoint *util_cxl_endpoint_filter(struct cxl_endpoint *endpoint,
+					      const char *__ident)
 {
 	char *ident, *save;
 	const char *arg;
@@ -124,11 +124,6 @@ static struct cxl_port *__util_cxl_port_filter(struct cxl_port *port,
 	return NULL;
 }
 
-enum cxl_port_filter_mode {
-	CXL_PF_SINGLE,
-	CXL_PF_ANCESTRY,
-};
-
 static enum cxl_port_filter_mode pf_mode(struct cxl_filter_params *p)
 {
 	if (p->single)
@@ -136,9 +131,8 @@ static enum cxl_port_filter_mode pf_mode(struct cxl_filter_params *p)
 	return CXL_PF_ANCESTRY;
 }
 
-static struct cxl_port *util_cxl_port_filter(struct cxl_port *port,
-					     const char *ident,
-					     enum cxl_port_filter_mode mode)
+struct cxl_port *util_cxl_port_filter(struct cxl_port *port, const char *ident,
+				      enum cxl_port_filter_mode mode)
 {
 	struct cxl_port *iter = port;
 
@@ -358,9 +352,9 @@ util_cxl_endpoint_filter_by_memdev(struct cxl_endpoint *endpoint,
 	return NULL;
 }
 
-static struct cxl_port *util_cxl_port_filter_by_memdev(struct cxl_port *port,
-						       const char *ident,
-						       const char *serial)
+struct cxl_port *util_cxl_port_filter_by_memdev(struct cxl_port *port,
+						const char *ident,
+						const char *serial)
 {
 	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
 	struct cxl_memdev *memdev;
@@ -958,7 +952,6 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 					continue;
 				}
 			}
-
 		}
 walk_children:
 		dbg(p, "walk decoders\n");
diff --git a/cxl/filter.h b/cxl/filter.h
index 6fd469f02ea2..850df70da964 100644
--- a/cxl/filter.h
+++ b/cxl/filter.h
@@ -29,6 +29,19 @@ struct cxl_filter_params {
 struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev,
 					  const char *__ident,
 					  const char *serials);
+struct cxl_port *util_cxl_port_filter_by_memdev(struct cxl_port *port,
+						const char *ident,
+						const char *serial);
+
+enum cxl_port_filter_mode {
+	CXL_PF_SINGLE,
+	CXL_PF_ANCESTRY,
+};
+
+struct cxl_port *util_cxl_port_filter(struct cxl_port *port, const char *ident,
+				      enum cxl_port_filter_mode mode);
+struct cxl_endpoint *util_cxl_endpoint_filter(struct cxl_endpoint *endpoint,
+					      const char *__ident);
 int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *param);
 bool cxl_filter_has(const char *needle, const char *__filter);
 #endif /* _CXL_UTIL_FILTER_H_ */
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index dcfc82689ca9..1a7dccb245d8 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -258,6 +258,11 @@ CXL_EXPORT void cxl_unref(struct cxl_ctx *ctx)
 	free(ctx);
 }
 
+static int cxl_flush(struct cxl_ctx *ctx)
+{
+	return sysfs_write_attr(ctx, "/sys/bus/cxl/flush", "1\n");
+}
+
 /**
  * cxl_set_log_fn - override default log routine
  * @ctx: cxl library context
@@ -530,11 +535,31 @@ CXL_EXPORT const char *cxl_memdev_get_firmware_verison(struct cxl_memdev *memdev
 	return memdev->firmware_version;
 }
 
+static void bus_invalidate(struct cxl_bus *bus)
+{
+	struct cxl_ctx *ctx = cxl_bus_get_ctx(bus);
+	struct cxl_port *bus_port, *port, *_p;
+	struct cxl_memdev *memdev;
+
+	/*
+	 * Something happend to cause the state of all ports to be
+	 * indeterminate, delete them all and start over.
+	 */
+	cxl_memdev_foreach(ctx, memdev)
+		if (cxl_memdev_get_bus(memdev) == bus)
+			memdev->endpoint = NULL;
+
+	bus_port = cxl_bus_get_port(bus);
+	list_for_each_safe(&bus_port->child_ports, port, _p, list)
+		free_port(port, &bus_port->child_ports);
+	bus_port->ports_init = 0;
+	cxl_flush(ctx);
+}
+
 CXL_EXPORT int cxl_memdev_disable_invalidate(struct cxl_memdev *memdev)
 {
 	struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev);
 	const char *devname = cxl_memdev_get_devname(memdev);
-	struct cxl_port *port, *_p, *bus_port;
 	struct cxl_bus *bus;
 
 	if (!cxl_memdev_is_enabled(memdev))
@@ -553,15 +578,7 @@ CXL_EXPORT int cxl_memdev_disable_invalidate(struct cxl_memdev *memdev)
 		return -EBUSY;
 	}
 
-	/*
-	 * The state of all ports is now indeterminate, delete them all
-	 * and start over.
-	 */
-	bus_port = cxl_bus_get_port(bus);
-	list_for_each_safe(&bus_port->child_ports, port, _p, list)
-		free_port(port, &bus_port->child_ports);
-	bus_port->ports_init = 0;
-	memdev->endpoint = NULL;
+	bus_invalidate(bus);
 
 	dbg(ctx, "%s: disabled\n", devname);
 
@@ -1352,6 +1369,57 @@ CXL_EXPORT int cxl_port_is_enabled(struct cxl_port *port)
 	return is_enabled(path);
 }
 
+CXL_EXPORT int cxl_port_disable_invalidate(struct cxl_port *port)
+{
+	const char *devname = cxl_port_get_devname(port);
+	struct cxl_bus *bus = cxl_port_get_bus(port);
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+
+	if (cxl_port_is_root(port)) {
+		err(ctx, "%s: can not be disabled through this interface\n",
+		    devname);
+		return -EINVAL;
+	}
+
+	if (!bus) {
+		err(ctx, "%s: failed to invalidate\n", devname);
+		return -ENXIO;
+	}
+
+	util_unbind(port->dev_path, ctx);
+
+	if (cxl_port_is_enabled(port)) {
+		err(ctx, "%s: failed to disable\n", devname);
+		return -EBUSY;
+	}
+
+	dbg(ctx, "%s: disabled\n", devname);
+
+	bus_invalidate(bus);
+
+	return 0;
+}
+
+CXL_EXPORT int cxl_port_enable(struct cxl_port *port)
+{
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+	const char *devname = cxl_port_get_devname(port);
+
+	if (cxl_port_is_enabled(port))
+		return 0;
+
+	util_bind(devname, port->module, "cxl", ctx);
+
+	if (!cxl_port_is_enabled(port)) {
+		err(ctx, "%s: failed to enable\n", devname);
+		return -ENXIO;
+	}
+
+	dbg(ctx, "%s: enabled\n", devname);
+
+	return 0;
+}
+
 CXL_EXPORT struct cxl_bus *cxl_port_to_bus(struct cxl_port *port)
 {
 	if (!cxl_port_is_root(port))
@@ -1359,6 +1427,13 @@ CXL_EXPORT struct cxl_bus *cxl_port_to_bus(struct cxl_port *port)
 	return container_of(port, struct cxl_bus, port);
 }
 
+CXL_EXPORT struct cxl_endpoint *cxl_port_to_endpoint(struct cxl_port *port)
+{
+	if (!cxl_port_is_endpoint(port))
+		return NULL;
+	return container_of(port, struct cxl_endpoint, port);
+}
+
 static void *add_cxl_dport(void *parent, int id, const char *cxldport_base)
 {
 	const char *devname = devpath_to_devname(cxldport_base);
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index 2c8358e1683e..67c7fd57c35c 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -97,11 +97,14 @@ global:
 	cxl_port_is_switch;
 	cxl_port_to_bus;
 	cxl_port_is_endpoint;
+	cxl_port_to_endpoint;
 	cxl_port_get_bus;
 	cxl_port_get_host;
 	cxl_port_get_bus;
 	cxl_port_hosts_memdev;
 	cxl_port_get_nr_dports;
+	cxl_port_disable_invalidate;
+	cxl_port_enable;
 	cxl_port_get_next_all;
 	cxl_endpoint_get_first;
 	cxl_endpoint_get_next;
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index c8d07bb4efc4..1aac39618371 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -90,10 +90,13 @@ bool cxl_port_is_root(struct cxl_port *port);
 bool cxl_port_is_switch(struct cxl_port *port);
 struct cxl_bus *cxl_port_to_bus(struct cxl_port *port);
 bool cxl_port_is_endpoint(struct cxl_port *port);
+struct cxl_endpoint *cxl_port_to_endpoint(struct cxl_port *port);
 struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
 const char *cxl_port_get_host(struct cxl_port *port);
 bool cxl_port_hosts_memdev(struct cxl_port *port, struct cxl_memdev *memdev);
 int cxl_port_get_nr_dports(struct cxl_port *port);
+int cxl_port_disable_invalidate(struct cxl_port *port);
+int cxl_port_enable(struct cxl_port *port);
 struct cxl_port *cxl_port_get_next_all(struct cxl_port *port,
 				       const struct cxl_port *top);
 
diff --git a/cxl/meson.build b/cxl/meson.build
index fc7ee71b54f0..87cfea73e40b 100644
--- a/cxl/meson.build
+++ b/cxl/meson.build
@@ -1,6 +1,7 @@
 cxl_src = [
   'cxl.c',
   'list.c',
+  'port.c',
   'memdev.c',
   '../util/json.c',
   '../util/log.c',
diff --git a/cxl/port.c b/cxl/port.c
new file mode 100644
index 000000000000..46a8f32df8e1
--- /dev/null
+++ b/cxl/port.c
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2020-2022 Intel Corporation. All rights reserved. */
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <limits.h>
+#include <util/log.h>
+#include <cxl/libcxl.h>
+#include <util/parse-options.h>
+#include <ccan/minmax/minmax.h>
+#include <ccan/array_size/array_size.h>
+
+#include "filter.h"
+
+static struct parameters {
+	bool debug;
+	bool force;
+	bool memdevs;
+	bool endpoint;
+} param;
+
+static struct log_ctx pl;
+
+#define BASE_OPTIONS()                                                 \
+OPT_BOOLEAN(0, "debug", &param.debug, "turn on debug"),                \
+OPT_BOOLEAN('e', "endpoint", &param.endpoint,                          \
+	    "target endpoints instead of switch ports")
+
+#define ENABLE_OPTIONS()                                               \
+OPT_BOOLEAN('m', "enable-memdevs", &param.memdevs,                   \
+	    "enable downstream memdev(s)")
+
+#define DISABLE_OPTIONS()                                              \
+OPT_BOOLEAN('f', "force", &param.force,                                \
+	    "DANGEROUS: override active memdev safety checks")
+
+static const struct option disable_options[] = {
+	BASE_OPTIONS(),
+	DISABLE_OPTIONS(),
+	OPT_END(),
+};
+
+static const struct option enable_options[] = {
+	BASE_OPTIONS(),
+	ENABLE_OPTIONS(),
+	OPT_END(),
+};
+
+static int action_disable(struct cxl_port *port)
+{
+	const char *devname = cxl_port_get_devname(port);
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+	struct cxl_memdev *memdev;
+	int active_memdevs = 0;
+
+	if (!cxl_port_is_enabled(port)) {
+		log_dbg(&pl, "%s already disabled\n", devname);
+		return 0;
+	}
+
+	if (param.endpoint) {
+		struct cxl_endpoint *endpoint = cxl_port_to_endpoint(port);
+
+		if (cxl_endpoint_get_memdev(endpoint))
+			active_memdevs++;
+	}
+
+	cxl_memdev_foreach(ctx, memdev) {
+		if (!cxl_port_get_dport_by_memdev(port, memdev))
+			continue;
+		if (cxl_memdev_is_enabled(memdev))
+			active_memdevs++;
+	}
+
+	if (active_memdevs && !param.force) {
+		/*
+		 * TODO: actually detect rather than assume active just
+		 * because the memdev is enabled
+		 */
+		log_err(&pl,
+			"%s hosts %d memdev%s which %s part of an active region\n",
+			devname, active_memdevs, active_memdevs > 1 ? "s" : "",
+			active_memdevs > 1 ? "are" : "is");
+		log_err(&pl,
+			"See 'cxl list -M -p %s' to see impacted device%s\n",
+			devname, active_memdevs > 1 ? "s" : "");
+		return -EBUSY;
+	}
+
+	return cxl_port_disable_invalidate(port);
+}
+
+static int action_enable(struct cxl_port *port)
+{
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+	struct cxl_memdev *memdev;
+	int rc;
+
+	rc = cxl_port_enable(port);
+	if (rc || !param.memdevs)
+		return rc;
+
+	cxl_memdev_foreach(ctx, memdev)
+		if (cxl_port_get_dport_by_memdev(port, memdev))
+			cxl_memdev_enable(memdev);
+	return 0;
+}
+
+static struct cxl_port *find_cxl_port(struct cxl_ctx *ctx, const char *ident)
+{
+	struct cxl_bus *bus;
+	struct cxl_port *port;
+
+	cxl_bus_foreach(ctx, bus)
+		cxl_port_foreach_all(cxl_bus_get_port(bus), port)
+			if (util_cxl_port_filter(port, ident, CXL_PF_SINGLE))
+				return port;
+	return NULL;
+}
+
+static struct cxl_endpoint *find_cxl_endpoint(struct cxl_ctx *ctx,
+					      const char *ident)
+{
+	struct cxl_bus *bus;
+	struct cxl_port *port;
+	struct cxl_endpoint *endpoint;
+
+	cxl_bus_foreach(ctx, bus)
+		cxl_port_foreach_all(cxl_bus_get_port(bus), port)
+			cxl_endpoint_foreach(port, endpoint)
+				if (util_cxl_endpoint_filter(endpoint, ident))
+					return endpoint;
+	return NULL;
+}
+
+
+
+static int port_action(int argc, const char **argv, struct cxl_ctx *ctx,
+		       int (*action)(struct cxl_port *port),
+		       const struct option *options, const char *usage)
+{
+	int i, rc = 0, count = 0, err = 0;
+	const char * const u[] = {
+		usage,
+		NULL
+	};
+	unsigned long id;
+
+	log_init(&pl, "cxl port", "CXL_PORT_LOG");
+	argc = parse_options(argc, argv, options, u, 0);
+
+	if (argc == 0)
+		usage_with_options(u, options);
+	for (i = 0; i < argc; i++) {
+		const char *fmt;
+
+		if (strcmp(argv[i], "all") == 0) {
+			argc = 1;
+			break;
+		}
+
+		if (param.endpoint)
+			fmt = "endpoint%lu";
+		else
+			fmt = "port%lu";
+
+		if (sscanf(argv[i], fmt, &id) == 1)
+			continue;
+		if (sscanf(argv[i], "%lu", &id) == 1)
+			continue;
+
+		log_err(&pl, "'%s' is not a valid %s identifer\n", argv[i],
+			param.endpoint ? "endpoint" : "port");
+		err++;
+	}
+
+	if (err == argc) {
+		usage_with_options(u, options);
+		return -EINVAL;
+	}
+
+	if (param.debug) {
+		cxl_set_log_priority(ctx, LOG_DEBUG);
+		pl.log_priority = LOG_DEBUG;
+	} else
+		pl.log_priority = LOG_INFO;
+
+	rc = 0;
+	err = 0;
+	count = 0;
+
+	for (i = 0; i < argc; i++) {
+		struct cxl_port *port;
+
+		if (param.endpoint) {
+			struct cxl_endpoint *endpoint;
+
+			endpoint = find_cxl_endpoint(ctx, argv[i]);
+			if (!endpoint) {
+				log_dbg(&pl, "endpoint: %s not found\n",
+					argv[i]);
+				continue;
+			}
+			port = cxl_endpoint_get_port(endpoint);
+		} else {
+			port = find_cxl_port(ctx, argv[i]);
+			if (!port) {
+				log_dbg(&pl, "port: %s not found\n", argv[i]);
+				continue;
+			}
+		}
+
+		log_dbg(&pl, "run action on port: %s\n",
+			cxl_port_get_devname(port));
+		rc = action(port);
+		if (rc == 0)
+			count++;
+		else if (rc && !err)
+			err = rc;
+	}
+	rc = err;
+
+	/*
+	 * count if some actions succeeded, 0 if none were attempted,
+	 * negative error code otherwise.
+	 */
+	if (count > 0)
+		return count;
+	return rc;
+ }
+
+ int cmd_disable_port(int argc, const char **argv, struct cxl_ctx *ctx)
+ {
+	 int count = port_action(
+		 argc, argv, ctx, action_disable, disable_options,
+		 "cxl disable-port <port0> [<port1>..<portN>] [<options>]");
+
+	 log_info(&pl, "disabled %d port%s\n", count >= 0 ? count : 0,
+		  count > 1 ? "s" : "");
+	 return count >= 0 ? 0 : EXIT_FAILURE;
+ }
+
+ int cmd_enable_port(int argc, const char **argv, struct cxl_ctx *ctx)
+ {
+	 int count = port_action(
+		 argc, argv, ctx, action_enable, enable_options,
+		 "cxl enable-port <port0> [<port1>..<portN>] [<options>]");
+
+	 log_info(&pl, "enabled %d port%s\n", count >= 0 ? count : 0,
+		  count > 1 ? "s" : "");
+	 return count >= 0 ? 0 : EXIT_FAILURE;
+ }


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [ndctl PATCH 37/37] cxl/list: Filter dports and targets by memdevs
  2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
                   ` (35 preceding siblings ...)
  2022-01-24  0:55 ` [ndctl PATCH 36/37] cxl/port: Add {disable,enable}-port command Dan Williams
@ 2022-01-24  0:55 ` Dan Williams
  36 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-01-24  0:55 UTC (permalink / raw)
  To: linux-cxl; +Cc: Ben Widawsky, vishal.l.verma

Trim dport / target information by the memdev filter. This is useful when
validating connectivity and decoder programming.

Reported-by: Ben Widawsky <ben.widawsky@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 Documentation/cxl/lib/libcxl.txt |    1 +
 cxl/filter.c                     |   51 ++++++++++++++++++++++++++++++++++++++
 cxl/filter.h                     |    6 ++++
 cxl/json.c                       |   51 +++++++++++++++++++++++++-------------
 cxl/json.h                       |    7 +++++
 cxl/lib/libcxl.c                 |    5 ++++
 cxl/lib/libcxl.sym               |    1 +
 cxl/libcxl.h                     |    1 +
 8 files changed, 105 insertions(+), 18 deletions(-)

diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
index 27eb29ed2bae..4392b47e6ece 100644
--- a/Documentation/cxl/lib/libcxl.txt
+++ b/Documentation/cxl/lib/libcxl.txt
@@ -221,6 +221,7 @@ const char *cxl_port_get_host(struct cxl_port *port);
 struct cxl_port *cxl_decoder_get_port(struct cxl_decoder *decoder);
 struct cxl_port *cxl_port_get_next_all(struct cxl_port *port,
                                        const struct cxl_port *top);
+struct cxl_port *cxl_dport_get_port(struct cxl_dport *dport);
 
 #define cxl_port_foreach(parent, port)                                      \
        for (port = cxl_port_get_first(parent); port != NULL;                \
diff --git a/cxl/filter.c b/cxl/filter.c
index f6a23b7ff864..925bf3a1c62d 100644
--- a/cxl/filter.c
+++ b/cxl/filter.c
@@ -435,6 +435,48 @@ util_cxl_decoder_filter_by_memdev(struct cxl_decoder *decoder,
 	return NULL;
 }
 
+struct cxl_target *util_cxl_target_filter_by_memdev(struct cxl_target *target,
+						    const char *ident,
+						    const char *serial)
+{
+	struct cxl_decoder *decoder = cxl_target_get_decoder(target);
+	struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder);
+	struct cxl_memdev *memdev;
+
+	if (!ident && !serial)
+		return target;
+
+	cxl_memdev_foreach(ctx, memdev) {
+		if (!util_cxl_memdev_filter(memdev, ident, serial))
+			continue;
+		if (cxl_target_maps_memdev(target, memdev))
+			return target;
+	}
+
+	return NULL;
+}
+
+struct cxl_dport *util_cxl_dport_filter_by_memdev(struct cxl_dport *dport,
+						  const char *ident,
+						  const char *serial)
+{
+	struct cxl_port *port = cxl_dport_get_port(dport);
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
+	struct cxl_memdev *memdev;
+
+	if (!ident && !serial)
+		return dport;
+
+	cxl_memdev_foreach (ctx, memdev) {
+		if (!util_cxl_memdev_filter(memdev, ident, serial))
+			continue;
+		if (cxl_dport_maps_memdev(dport, memdev))
+			return dport;
+	}
+
+	return NULL;
+}
+
 static bool __memdev_filter_by_decoder(struct cxl_memdev *memdev,
 				       struct cxl_port *port, const char *ident)
 {
@@ -639,6 +681,9 @@ static void walk_decoders(struct cxl_port *port, struct cxl_filter_params *p,
 			dbg(p, "decoder object allocation failure\n");
 			continue;
 		}
+		util_cxl_targets_append_json(jdecoder, decoder,
+					     p->memdev_filter, p->serial_filter,
+					     flags);
 		json_object_array_add(jdecoders, jdecoder);
 	}
 }
@@ -756,6 +801,9 @@ walk_child_ports(struct cxl_port *parent_port, struct cxl_filter_params *p,
 				err(p, "%s: failed to list\n", devname);
 				continue;
 			}
+			util_cxl_dports_append_json(jport, port,
+						    p->memdev_filter,
+						    p->serial_filter, flags);
 			json_object_array_add(jports, jport);
 			jchildports = json_object_new_array();
 			if (!jchildports) {
@@ -914,6 +962,9 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
 				dbg(p, "bus object allocation failure\n");
 				continue;
 			}
+			util_cxl_dports_append_json(jbus, port,
+						    p->memdev_filter,
+						    p->serial_filter, flags);
 			json_object_array_add(jbuses, jbus);
 			if (p->ports) {
 				jchildports = json_object_new_array();
diff --git a/cxl/filter.h b/cxl/filter.h
index 850df70da964..5deabb3d38fe 100644
--- a/cxl/filter.h
+++ b/cxl/filter.h
@@ -42,6 +42,12 @@ struct cxl_port *util_cxl_port_filter(struct cxl_port *port, const char *ident,
 				      enum cxl_port_filter_mode mode);
 struct cxl_endpoint *util_cxl_endpoint_filter(struct cxl_endpoint *endpoint,
 					      const char *__ident);
+struct cxl_target *util_cxl_target_filter_by_memdev(struct cxl_target *target,
+						    const char *ident,
+						    const char *serial);
+struct cxl_dport *util_cxl_dport_filter_by_memdev(struct cxl_dport *dport,
+						  const char *ident,
+						  const char *serial);
 int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *param);
 bool cxl_filter_has(const char *needle, const char *__filter);
 #endif /* _CXL_UTIL_FILTER_H_ */
diff --git a/cxl/json.c b/cxl/json.c
index 4fb5eec11077..f3b536e1d4fa 100644
--- a/cxl/json.c
+++ b/cxl/json.c
@@ -8,6 +8,7 @@
 #include <json-c/printbuf.h>
 #include <ccan/short_types/short_types.h>
 
+#include "filter.h"
 #include "json.h"
 
 static struct json_object *util_cxl_memdev_health_to_json(
@@ -241,9 +242,9 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
 	return jdev;
 }
 
-static struct json_object *util_cxl_dports_to_json(struct json_object *jport,
-						   struct cxl_port *port,
-						   unsigned long flags)
+void util_cxl_dports_append_json(struct json_object *jport,
+				 struct cxl_port *port, const char *ident,
+				 const char *serial, unsigned long flags)
 {
 	struct json_object *jobj, *jdports;
 	struct cxl_dport *dport;
@@ -251,7 +252,7 @@ static struct json_object *util_cxl_dports_to_json(struct json_object *jport,
 
 	val = cxl_port_get_nr_dports(port);
 	if (!val || !(flags & UTIL_JSON_TARGETS))
-		return jport;
+		return;
 
 	jobj = json_object_new_int(val);
 	if (jobj)
@@ -259,12 +260,15 @@ static struct json_object *util_cxl_dports_to_json(struct json_object *jport,
 
 	jdports = json_object_new_array();
 	if (!jdports)
-		return jport;
+		return;
 
 	cxl_dport_foreach(port, dport) {
 		struct json_object *jdport;
 		const char *phys_node;
 
+		if (!util_cxl_dport_filter_by_memdev(dport, ident, serial))
+			continue;
+
 		jdport = json_object_new_object();
 		if (!jdport)
 			continue;
@@ -289,8 +293,6 @@ static struct json_object *util_cxl_dports_to_json(struct json_object *jport,
 	}
 
 	json_object_object_add(jport, "dports", jdports);
-
-	return jport;
 }
 
 struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
@@ -311,7 +313,7 @@ struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
 	if (jobj)
 		json_object_object_add(jbus, "provider", jobj);
 
-	return util_cxl_dports_to_json(jbus, cxl_bus_get_port(bus), flags);
+	return jbus;
 }
 
 struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
@@ -320,8 +322,6 @@ struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
 	const char *devname = cxl_decoder_get_devname(decoder);
 	struct cxl_port *port = cxl_decoder_get_port(decoder);
 	struct json_object *jdecoder, *jobj;
-	struct json_object *jtargets;
-	struct cxl_target *target;
 	u64 val;
 
 	jdecoder = json_object_new_object();
@@ -375,9 +375,22 @@ struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
 					       jobj);
 	}
 
+	return jdecoder;
+}
+
+void util_cxl_targets_append_json(struct json_object *jdecoder,
+				  struct cxl_decoder *decoder,
+				  const char *ident, const char *serial,
+				  unsigned long flags)
+{
+	struct cxl_port *port = cxl_decoder_get_port(decoder);
+	struct json_object *jobj, *jtargets;
+	struct cxl_target *target;
+	int val;
+
 	/* Endpoints don't have targets, they *are* targets */
 	if (cxl_port_is_endpoint(port))
-		return jdecoder;
+		return;
 
 	val = cxl_decoder_get_nr_targets(decoder);
 	jobj = json_object_new_int(val);
@@ -386,16 +399,21 @@ struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
 
 	if (!(flags & UTIL_JSON_TARGETS) ||
 	    !cxl_decoder_get_nr_targets(decoder))
-		return jdecoder;
+		return;
 
 	jtargets = json_object_new_array();
 	if (!jtargets)
-		return jdecoder;
+		return;
 
 	cxl_target_foreach(decoder, target) {
-		struct json_object *jtarget = json_object_new_object();
+		struct json_object *jtarget;
 		const char *phys_node;
+		const char *devname;
+
+		if (!util_cxl_target_filter_by_memdev(target, ident, serial))
+			continue;
 
+		jtarget = json_object_new_object();
 		if (!jtarget)
 			continue;
 
@@ -425,9 +443,6 @@ struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
 	}
 
 	json_object_object_add(jdecoder, "targets", jtargets);
-
-	return jdecoder;
-
 }
 
 static struct json_object *__util_cxl_port_to_json(struct cxl_port *port,
@@ -455,7 +470,7 @@ static struct json_object *__util_cxl_port_to_json(struct cxl_port *port,
 			json_object_object_add(jport, "state", jobj);
 	}
 
-	return util_cxl_dports_to_json(jport, port, flags);
+	return jport;
 }
 
 struct json_object *util_cxl_port_to_json(struct cxl_port *port,
diff --git a/cxl/json.h b/cxl/json.h
index fcca2e6e9cb6..9a5a845eebbb 100644
--- a/cxl/json.h
+++ b/cxl/json.h
@@ -15,4 +15,11 @@ struct json_object *util_cxl_endpoint_to_json(struct cxl_endpoint *endpoint,
 					      unsigned long flags);
 struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder,
 					     unsigned long flags);
+void util_cxl_targets_append_json(struct json_object *jdecoder,
+				  struct cxl_decoder *decoder,
+				  const char *ident, const char *serial,
+				  unsigned long flags);
+void util_cxl_dports_append_json(struct json_object *jport,
+				 struct cxl_port *port, const char *ident,
+				 const char *serial, unsigned long flags);
 #endif /* __CXL_UTIL_JSON_H__ */
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
index 1a7dccb245d8..e0b443fb0aa4 100644
--- a/cxl/lib/libcxl.c
+++ b/cxl/lib/libcxl.c
@@ -1527,6 +1527,11 @@ CXL_EXPORT int cxl_dport_get_id(struct cxl_dport *dport)
 	return dport->id;
 }
 
+CXL_EXPORT struct cxl_port *cxl_dport_get_port(struct cxl_dport *dport)
+{
+	return dport->port;
+}
+
 CXL_EXPORT bool cxl_dport_maps_memdev(struct cxl_dport *dport,
 				      struct cxl_memdev *memdev)
 {
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
index 67c7fd57c35c..e56a2bf96e22 100644
--- a/cxl/lib/libcxl.sym
+++ b/cxl/lib/libcxl.sym
@@ -152,6 +152,7 @@ global:
 	cxl_dport_get_devname;
 	cxl_dport_get_physical_node;
 	cxl_dport_get_id;
+	cxl_dport_get_port;
 	cxl_port_get_dport_by_memdev;
 	cxl_dport_maps_memdev;
 } LIBCXL_1;
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
index 1aac39618371..3b2293bbee84 100644
--- a/cxl/libcxl.h
+++ b/cxl/libcxl.h
@@ -113,6 +113,7 @@ struct cxl_dport *cxl_dport_get_first(struct cxl_port *port);
 struct cxl_dport *cxl_dport_get_next(struct cxl_dport *dport);
 const char *cxl_dport_get_devname(struct cxl_dport *dport);
 const char *cxl_dport_get_physical_node(struct cxl_dport *dport);
+struct cxl_port *cxl_dport_get_port(struct cxl_dport *dport);
 int cxl_dport_get_id(struct cxl_dport *dport);
 bool cxl_dport_maps_memdev(struct cxl_dport *dport, struct cxl_memdev *memdev);
 struct cxl_dport *cxl_port_get_dport_by_memdev(struct cxl_port *port,


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Re: [ndctl PATCH 26/37] cxl/memdev: Add serial support for memdev-related commands
  2022-01-24  0:54 ` [ndctl PATCH 26/37] cxl/memdev: Add serial support for memdev-related commands Dan Williams
@ 2022-02-10  1:10   ` Alison Schofield
  2022-02-10  1:55     ` Dan Williams
  0 siblings, 1 reply; 40+ messages in thread
From: Alison Schofield @ 2022-02-10  1:10 UTC (permalink / raw)
  To: Dan Williams; +Cc: linux-cxl, vishal.l.verma

On Sun, Jan 23, 2022 at 04:54:06PM -0800, Dan Williams wrote:
> Allow for a "-s, --serial" option to turn the argument list into serial
> identifiers.
> 
> Signed-off-by: Dan Williams <dan.j.williams@intel.com>
> ---
>  Documentation/cxl/memdev-option.txt |    5 ++++
>  Documentation/cxl/meson.build       |    4 ++-
>  cxl/memdev.c                        |   45 ++++++++++++++++++++++++++---------
>  3 files changed, 41 insertions(+), 13 deletions(-)
> 
> diff --git a/Documentation/cxl/memdev-option.txt b/Documentation/cxl/memdev-option.txt
> index e7785827ab4c..64348bed1791 100644
> --- a/Documentation/cxl/memdev-option.txt
> +++ b/Documentation/cxl/memdev-option.txt
> @@ -2,3 +2,8 @@
>  A 'memX' device name, or a memdev id number. Restrict the operation to
>  the specified memdev(s). The keyword 'all' can be specified to indicate
>  the lack of any restriction.
> +
> +-S::
> +--serial::
> +	Rather an a memdev id number, interpret the <memdev> argument(s)

Rather 'than'

That's all :)
> +	as a list of serial numbers.

snip


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [ndctl PATCH 26/37] cxl/memdev: Add serial support for memdev-related commands
  2022-02-10  1:10   ` Alison Schofield
@ 2022-02-10  1:55     ` Dan Williams
  0 siblings, 0 replies; 40+ messages in thread
From: Dan Williams @ 2022-02-10  1:55 UTC (permalink / raw)
  To: Alison Schofield; +Cc: linux-cxl, Vishal L Verma

On Wed, Feb 9, 2022 at 5:06 PM Alison Schofield
<alison.schofield@intel.com> wrote:
>
> On Sun, Jan 23, 2022 at 04:54:06PM -0800, Dan Williams wrote:
> > Allow for a "-s, --serial" option to turn the argument list into serial
> > identifiers.
> >
> > Signed-off-by: Dan Williams <dan.j.williams@intel.com>
> > ---
> >  Documentation/cxl/memdev-option.txt |    5 ++++
> >  Documentation/cxl/meson.build       |    4 ++-
> >  cxl/memdev.c                        |   45 ++++++++++++++++++++++++++---------
> >  3 files changed, 41 insertions(+), 13 deletions(-)
> >
> > diff --git a/Documentation/cxl/memdev-option.txt b/Documentation/cxl/memdev-option.txt
> > index e7785827ab4c..64348bed1791 100644
> > --- a/Documentation/cxl/memdev-option.txt
> > +++ b/Documentation/cxl/memdev-option.txt
> > @@ -2,3 +2,8 @@
> >  A 'memX' device name, or a memdev id number. Restrict the operation to
> >  the specified memdev(s). The keyword 'all' can be specified to indicate
> >  the lack of any restriction.
> > +
> > +-S::
> > +--serial::
> > +     Rather an a memdev id number, interpret the <memdev> argument(s)
>
> Rather 'than'
>
> That's all :)

Appreciate it, thanks!

^ permalink raw reply	[flat|nested] 40+ messages in thread

end of thread, other threads:[~2022-02-10  2:00 UTC | newest]

Thread overview: 40+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-01-24  0:51 [ndctl PATCH 00/37] cxl: Full topology enumeration Dan Williams
2022-01-24  0:51 ` [ndctl PATCH 01/37] test: Add 'suite' identifiers to tests Dan Williams
2022-01-24  0:52 ` [ndctl PATCH 02/37] ndctl: Rename util_filter to ndctl_filter Dan Williams
2022-01-24  0:52 ` [ndctl PATCH 03/37] build: Add tags Dan Williams
2022-01-24  0:52 ` [ndctl PATCH 04/37] json: Add support for json_object_new_uint64() Dan Williams
2022-01-24  0:52 ` [ndctl PATCH 05/37] cxl/json: Cleanup object leak false positive Dan Williams
2022-01-24  0:52 ` [ndctl PATCH 06/37] cxl/list: Support multiple memdev device name filter arguments Dan Williams
2022-01-24  0:52 ` [ndctl PATCH 07/37] cxl/list: Support comma separated lists Dan Williams
2022-01-24  0:52 ` [ndctl PATCH 08/37] cxl/list: Introduce cxl_filter_walk() Dan Williams
2022-01-24  0:52 ` [ndctl PATCH 09/37] cxl/list: Emit device serial numbers Dan Williams
2022-01-24  0:52 ` [ndctl PATCH 10/37] cxl/list: Add filter by serial support Dan Williams
2022-01-24  0:52 ` [ndctl PATCH 11/37] cxl/lib: Rename nvdimm bridge to pmem Dan Williams
2022-01-24  0:52 ` [ndctl PATCH 12/37] cxl/list: Cleanup options definitions Dan Williams
2022-01-24  0:52 ` [ndctl PATCH 13/37] Documentation: Enhance libcxl memdev API documentation Dan Williams
2022-01-24  0:53 ` [ndctl PATCH 14/37] cxl/list: Add bus objects Dan Williams
2022-01-24  0:53 ` [ndctl PATCH 15/37] util/json: Warn on stderr about empty list results Dan Williams
2022-01-24  0:53 ` [ndctl PATCH 16/37] util/sysfs: Uplevel modalias lookup helper to util/ Dan Williams
2022-01-24  0:53 ` [ndctl PATCH 17/37] cxl/list: Add port enumeration Dan Williams
2022-01-24  0:53 ` [ndctl PATCH 18/37] cxl/list: Add --debug option Dan Williams
2022-01-24  0:53 ` [ndctl PATCH 19/37] cxl/list: Add endpoints Dan Williams
2022-01-24  0:53 ` [ndctl PATCH 20/37] cxl/list: Add 'host' entries for port-like objects Dan Williams
2022-01-24  0:53 ` [ndctl PATCH 21/37] cxl/list: Add 'host' entries for memdevs Dan Williams
2022-01-24  0:53 ` [ndctl PATCH 22/37] cxl/list: Move enabled memdevs underneath their endpoint Dan Williams
2022-01-24  0:53 ` [ndctl PATCH 23/37] cxl/list: Filter memdev by ancestry Dan Williams
2022-01-24  0:53 ` [ndctl PATCH 24/37] cxl/memdev: Use a local logger for debug Dan Williams
2022-01-24  0:54 ` [ndctl PATCH 25/37] cxl/memdev: Cleanup memdev filter Dan Williams
2022-01-24  0:54 ` [ndctl PATCH 26/37] cxl/memdev: Add serial support for memdev-related commands Dan Williams
2022-02-10  1:10   ` Alison Schofield
2022-02-10  1:55     ` Dan Williams
2022-01-24  0:54 ` [ndctl PATCH 27/37] cxl/list: Add 'numa_node' to memdev listings Dan Williams
2022-01-24  0:54 ` [ndctl PATCH 28/37] util: Implement common bind/unbind helpers Dan Williams
2022-01-24  0:54 ` [ndctl PATCH 29/37] cxl/memdev: Enable / disable support Dan Williams
2022-01-24  0:54 ` [ndctl PATCH 30/37] cxl/list: Add decoder support Dan Williams
2022-01-24  0:54 ` [ndctl PATCH 31/37] cxl/list: Extend decoder objects with target information Dan Williams
2022-01-24  0:54 ` [ndctl PATCH 32/37] cxl/list: Use 'physical_node' for root port attachment detection Dan Williams
2022-01-24  0:54 ` [ndctl PATCH 33/37] cxl/list: Reuse the --target option for ports Dan Williams
2022-01-24  0:54 ` [ndctl PATCH 34/37] cxl/list: Support filtering memdevs by decoders Dan Williams
2022-01-24  0:54 ` [ndctl PATCH 35/37] cxl/list: Support filtering memdevs by ports Dan Williams
2022-01-24  0:55 ` [ndctl PATCH 36/37] cxl/port: Add {disable,enable}-port command Dan Williams
2022-01-24  0:55 ` [ndctl PATCH 37/37] cxl/list: Filter dports and targets by memdevs Dan Williams

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).