Blame SOURCES/0103-cxl-list-Add-port-enumeration.patch

e0018b
From fef3f05ca8cdfd8d783162042d5cf20325c8b64b Mon Sep 17 00:00:00 2001
e0018b
From: Dan Williams <dan.j.williams@intel.com>
e0018b
Date: Sun, 23 Jan 2022 16:53:18 -0800
e0018b
Subject: [PATCH 103/217] cxl/list: Add port enumeration
e0018b
e0018b
Between a cxl_bus (root port) and an endpoint there can be an arbitrary
e0018b
level of switches. Add enumeration for these ports at each level of the
e0018b
hierarchy.
e0018b
e0018b
However, given the CXL root ports are also "ports" infer that if the port
e0018b
filter argument is the word "root" or "root%d" then include root ports in
e0018b
the listing. The keyword "switch" is also provided to filter only the ports
e0018b
beneath the root that are not endpoint ports.
e0018b
e0018b
Link: https://lore.kernel.org/r/164298559854.3021641.17724828997703051001.stgit@dwillia2-desk3.amr.corp.intel.com
e0018b
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
e0018b
Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
e0018b
---
e0018b
 .clang-format                    |   1 +
e0018b
 Documentation/cxl/cxl-list.txt   |  24 ++++
e0018b
 Documentation/cxl/lib/libcxl.txt |  42 ++++++
e0018b
 cxl/filter.c                     | 224 ++++++++++++++++++++++++++++++-
e0018b
 cxl/filter.h                     |   4 +
e0018b
 cxl/json.c                       |  23 ++++
e0018b
 cxl/json.h                       |   3 +
e0018b
 cxl/lib/libcxl.c                 | 160 +++++++++++++++++++++-
e0018b
 cxl/lib/libcxl.sym               |  12 ++
e0018b
 cxl/lib/private.h                |  11 ++
e0018b
 cxl/libcxl.h                     |  19 +++
e0018b
 cxl/list.c                       |  17 ++-
e0018b
 12 files changed, 534 insertions(+), 6 deletions(-)
e0018b
e0018b
diff --git a/.clang-format b/.clang-format
e0018b
index 1154c76..391cd34 100644
e0018b
--- a/.clang-format
e0018b
+++ b/.clang-format
e0018b
@@ -79,6 +79,7 @@ ExperimentalAutoDetectBinPacking: false
e0018b
 ForEachMacros:
e0018b
   - 'cxl_memdev_foreach'
e0018b
   - 'cxl_bus_foreach'
e0018b
+  - 'cxl_port_foreach'
e0018b
   - 'daxctl_dev_foreach'
e0018b
   - 'daxctl_mapping_foreach'
e0018b
   - 'daxctl_region_foreach'
e0018b
diff --git a/Documentation/cxl/cxl-list.txt b/Documentation/cxl/cxl-list.txt
e0018b
index be131ae..3076deb 100644
e0018b
--- a/Documentation/cxl/cxl-list.txt
e0018b
+++ b/Documentation/cxl/cxl-list.txt
e0018b
@@ -176,6 +176,30 @@ OPTIONS
e0018b
 	names to filter the listing. The supported provider names are "ACPI.CXL"
e0018b
 	and "cxl_test".
e0018b
 
e0018b
+-P::
e0018b
+--ports::
e0018b
+	Include port objects (CXL / PCIe root ports + Upstream Switch Ports) in
e0018b
+	the listing.
e0018b
+
e0018b
+-p::
e0018b
+--port=::
e0018b
+	Specify CXL Port device name(s), device id(s), and or port type
e0018b
+	names to filter the listing. The supported port type names are "root"
e0018b
+	and "switch". Note that since a bus object is also a port, the following
e0018b
+	two syntaxes are equivalent:
e0018b
+----
e0018b
+# cxl list -B
e0018b
+# cxl list -P -p root
e0018b
+----
e0018b
+	By default, only 'switch' ports are listed.
e0018b
+
e0018b
+-S::
e0018b
+--single::
e0018b
+	Specify whether the listing should emit all the objects that are
e0018b
+	descendants of a port that matches the port filter, or only direct
e0018b
+	descendants of the individual ports that match the filter. By default
e0018b
+	all descendant objects are listed.
e0018b
+
e0018b
 include::human-option.txt[]
e0018b
 
e0018b
 include::verbose-option.txt[]
e0018b
diff --git a/Documentation/cxl/lib/libcxl.txt b/Documentation/cxl/lib/libcxl.txt
e0018b
index 84af66a..804e9ca 100644
e0018b
--- a/Documentation/cxl/lib/libcxl.txt
e0018b
+++ b/Documentation/cxl/lib/libcxl.txt
e0018b
@@ -164,6 +164,48 @@ discovery order. The possible provider names are 'ACPI.CXL' and
e0018b
 the kernel device names that are subject to change based on discovery
e0018b
 order.
e0018b
 
e0018b
+PORTS
e0018b
+-----
e0018b
+CXL ports track the PCIe hierarchy between a platform firmware CXL root
e0018b
+object, through CXL / PCIe Host Bridges, CXL / PCIe Root Ports, and CXL
e0018b
+/ PCIe Switch Ports.
e0018b
+
e0018b
+=== PORT: Enumeration
e0018b
+----
e0018b
+struct cxl_port *cxl_bus_get_port(struct cxl_bus *bus);
e0018b
+struct cxl_port *cxl_port_get_first(struct cxl_port *parent);
e0018b
+struct cxl_port *cxl_port_get_next(struct cxl_port *port);
e0018b
+struct cxl_port *cxl_port_get_parent(struct cxl_port *port);
e0018b
+struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
e0018b
+struct cxl_ctx *cxl_port_get_ctx(struct cxl_port *port);
e0018b
+
e0018b
+#define cxl_port_foreach(parent, port)                                      \
e0018b
+       for (port = cxl_port_get_first(parent); port != NULL;                \
e0018b
+            port = cxl_port_get_next(port))
e0018b
+----
e0018b
+A bus object encapsulates a CXL port object. Use cxl_bus_get_port() to
e0018b
+use generic port APIs on root objects.
e0018b
+
e0018b
+Ports are hierarchical. All but the a root object have another CXL port
e0018b
+as a parent object retrievable via cxl_port_get_parent().
e0018b
+
e0018b
+The root port of a hiearchy can be retrieved via any port instance in
e0018b
+that hierarchy via cxl_port_get_bus().
e0018b
+
e0018b
+=== PORT: Attributes
e0018b
+----
e0018b
+const char *cxl_port_get_devname(struct cxl_port *port);
e0018b
+int cxl_port_get_id(struct cxl_port *port);
e0018b
+int cxl_port_is_enabled(struct cxl_port *port);
e0018b
+bool cxl_port_is_root(struct cxl_port *port);
e0018b
+bool cxl_port_is_switch(struct cxl_port *port);
e0018b
+----
e0018b
+The port type is communicated via cxl_port_is_<type>(). An 'enabled' port
e0018b
+is one that has succeeded in discovering the CXL component registers in
e0018b
+the host device and has enumerated its downstream ports. In order for a
e0018b
+memdev to be enabled for CXL memory operation all CXL ports in its
e0018b
+ancestry must also be enabled.
e0018b
+
e0018b
 include::../../copyright.txt[]
e0018b
 
e0018b
 SEE ALSO
e0018b
diff --git a/cxl/filter.c b/cxl/filter.c
e0018b
index 5f4844b..8b79db3 100644
e0018b
--- a/cxl/filter.c
e0018b
+++ b/cxl/filter.c
e0018b
@@ -21,6 +21,101 @@ static const char *which_sep(const char *filter)
e0018b
 	return " ";
e0018b
 }
e0018b
 
e0018b
+bool cxl_filter_has(const char *__filter, const char *needle)
e0018b
+{
e0018b
+	char *filter, *save;
e0018b
+	const char *arg;
e0018b
+
e0018b
+	if (!needle)
e0018b
+		return true;
e0018b
+
e0018b
+	if (!__filter)
e0018b
+		return false;
e0018b
+
e0018b
+	filter = strdup(__filter);
e0018b
+	if (!filter)
e0018b
+		return false;
e0018b
+
e0018b
+	for (arg = strtok_r(filter, which_sep(__filter), &save); arg;
e0018b
+	     arg = strtok_r(NULL, which_sep(__filter), &save))
e0018b
+		if (strstr(arg, needle))
e0018b
+			break;
e0018b
+
e0018b
+	free(filter);
e0018b
+	if (arg)
e0018b
+		return true;
e0018b
+	return false;
e0018b
+}
e0018b
+
e0018b
+static struct cxl_port *__util_cxl_port_filter(struct cxl_port *port,
e0018b
+					     const char *__ident)
e0018b
+{
e0018b
+	char *ident, *save;
e0018b
+	const char *arg;
e0018b
+	int port_id;
e0018b
+
e0018b
+	if (!__ident)
e0018b
+		return port;
e0018b
+
e0018b
+	ident = strdup(__ident);
e0018b
+	if (!ident)
e0018b
+		return NULL;
e0018b
+
e0018b
+	for (arg = strtok_r(ident, which_sep(__ident), &save); arg;
e0018b
+	     arg = strtok_r(NULL, which_sep(__ident), &save)) {
e0018b
+		if (strcmp(arg, "all") == 0)
e0018b
+			break;
e0018b
+
e0018b
+		if (strcmp(arg, "root") == 0 && cxl_port_is_root(port))
e0018b
+			break;
e0018b
+
e0018b
+		if (strcmp(arg, "switch") == 0 && cxl_port_is_switch(port))
e0018b
+			break;
e0018b
+
e0018b
+		if ((sscanf(arg, "%d", &port_id) == 1 ||
e0018b
+		     sscanf(arg, "port%d", &port_id) == 1) &&
e0018b
+		    cxl_port_get_id(port) == port_id)
e0018b
+			break;
e0018b
+
e0018b
+		if (strcmp(arg, cxl_port_get_devname(port)) == 0)
e0018b
+			break;
e0018b
+	}
e0018b
+
e0018b
+	free(ident);
e0018b
+	if (arg)
e0018b
+		return port;
e0018b
+	return NULL;
e0018b
+}
e0018b
+
e0018b
+enum cxl_port_filter_mode {
e0018b
+	CXL_PF_SINGLE,
e0018b
+	CXL_PF_ANCESTRY,
e0018b
+};
e0018b
+
e0018b
+static enum cxl_port_filter_mode pf_mode(struct cxl_filter_params *p)
e0018b
+{
e0018b
+	if (p->single)
e0018b
+		return CXL_PF_SINGLE;
e0018b
+	return CXL_PF_ANCESTRY;
e0018b
+}
e0018b
+
e0018b
+static struct cxl_port *util_cxl_port_filter(struct cxl_port *port,
e0018b
+					     const char *ident,
e0018b
+					     enum cxl_port_filter_mode mode)
e0018b
+{
e0018b
+	struct cxl_port *iter = port;
e0018b
+
e0018b
+	while (iter) {
e0018b
+		if (__util_cxl_port_filter(iter, ident))
e0018b
+			return port;
e0018b
+		if (mode == CXL_PF_SINGLE)
e0018b
+			return NULL;
e0018b
+		iter = cxl_port_get_parent(iter);
e0018b
+	}
e0018b
+
e0018b
+	return NULL;
e0018b
+}
e0018b
+
e0018b
 static struct cxl_bus *util_cxl_bus_filter(struct cxl_bus *bus,
e0018b
 					   const char *__ident)
e0018b
 {
e0018b
@@ -58,6 +153,31 @@ static struct cxl_bus *util_cxl_bus_filter(struct cxl_bus *bus,
e0018b
 	return NULL;
e0018b
 }
e0018b
 
e0018b
+static struct cxl_port *util_cxl_port_filter_by_bus(struct cxl_port *port,
e0018b
+						    const char *__ident)
e0018b
+{
e0018b
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
e0018b
+	struct cxl_bus *bus;
e0018b
+
e0018b
+	if (!__ident)
e0018b
+		return port;
e0018b
+
e0018b
+	if (cxl_port_is_root(port)) {
e0018b
+		bus = cxl_port_to_bus(port);
e0018b
+		bus = util_cxl_bus_filter(bus, __ident);
e0018b
+		return bus ? port : NULL;
e0018b
+	}
e0018b
+
e0018b
+	cxl_bus_foreach(ctx, bus) {
e0018b
+		if (!util_cxl_bus_filter(bus, __ident))
e0018b
+			continue;
e0018b
+		if (bus == cxl_port_get_bus(port))
e0018b
+			return port;
e0018b
+	}
e0018b
+
e0018b
+	return NULL;
e0018b
+}
e0018b
+
e0018b
 static struct cxl_memdev *
e0018b
 util_cxl_memdev_serial_filter(struct cxl_memdev *memdev, const char *__serials)
e0018b
 {
e0018b
@@ -169,10 +289,82 @@ static void splice_array(struct cxl_filter_params *p, struct json_object *jobjs,
e0018b
 	json_object_put(jobjs);
e0018b
 }
e0018b
 
e0018b
+static bool cond_add_put_array(struct json_object *jobj, const char *key,
e0018b
+			       struct json_object *array)
e0018b
+{
e0018b
+	if (jobj && array && json_object_array_length(array) > 0) {
e0018b
+		json_object_object_add(jobj, key, array);
e0018b
+		return true;
e0018b
+	} else {
e0018b
+		json_object_put(array);
e0018b
+		return false;
e0018b
+	}
e0018b
+}
e0018b
+
e0018b
+static bool cond_add_put_array_suffix(struct json_object *jobj, const char *key,
e0018b
+				      const char *suffix,
e0018b
+				      struct json_object *array)
e0018b
+{
e0018b
+	char *name;
e0018b
+	bool rc;
e0018b
+
e0018b
+	if (asprintf(&name, "%s:%s", key, suffix) < 0)
e0018b
+		return false;
e0018b
+	rc = cond_add_put_array(jobj, name, array);
e0018b
+	free(name);
e0018b
+	return rc;
e0018b
+}
e0018b
+
e0018b
+static struct json_object *pick_array(struct json_object *child,
e0018b
+				      struct json_object *container)
e0018b
+{
e0018b
+	if (child)
e0018b
+		return child;
e0018b
+	if (container)
e0018b
+		return container;
e0018b
+	return NULL;
e0018b
+}
e0018b
+
e0018b
+static void walk_child_ports(struct cxl_port *parent_port,
e0018b
+			     struct cxl_filter_params *p,
e0018b
+			     struct json_object *jports,
e0018b
+			     unsigned long flags)
e0018b
+{
e0018b
+	struct cxl_port *port;
e0018b
+
e0018b
+	cxl_port_foreach(parent_port, port) {
e0018b
+		const char *devname = cxl_port_get_devname(port);
e0018b
+		struct json_object *jport = NULL;
e0018b
+		struct json_object *jchildports = NULL;
e0018b
+
e0018b
+		if (!util_cxl_port_filter(port, p->port_filter, pf_mode(p)))
e0018b
+			goto walk_children;
e0018b
+		if (!util_cxl_port_filter_by_bus(port, p->bus_filter))
e0018b
+			goto walk_children;
e0018b
+		if (!p->idle && !cxl_port_is_enabled(port))
e0018b
+			continue;
e0018b
+		if (p->ports)
e0018b
+			jport = util_cxl_port_to_json(port, flags);
e0018b
+		if (!jport)
e0018b
+			continue;
e0018b
+		json_object_array_add(jports, jport);
e0018b
+		jchildports = json_object_new_array();
e0018b
+		if (!jchildports) {
e0018b
+			err(p, "%s: failed to enumerate child ports\n",
e0018b
+			    devname);
e0018b
+			continue;
e0018b
+		}
e0018b
+walk_children:
e0018b
+		walk_child_ports(port, p, pick_array(jchildports, jports),
e0018b
+				 flags);
e0018b
+		cond_add_put_array_suffix(jport, "ports", devname, jchildports);
e0018b
+	}
e0018b
+}
e0018b
+
e0018b
 int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
e0018b
 {
e0018b
+	struct json_object *jdevs = NULL, *jbuses = NULL, *jports = NULL;
e0018b
 	struct json_object *jplatform = json_object_new_array();
e0018b
-	struct json_object *jdevs = NULL, *jbuses = NULL;
e0018b
 	unsigned long flags = params_to_flags(p);
e0018b
 	struct cxl_memdev *memdev;
e0018b
 	int top_level_objs = 0;
e0018b
@@ -191,6 +383,10 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
e0018b
 	if (!jbuses)
e0018b
 		goto err;
e0018b
 
e0018b
+	jports = json_object_new_array();
e0018b
+	if (!jports)
e0018b
+		goto err;
e0018b
+
e0018b
 	cxl_memdev_foreach(ctx, memdev) {
e0018b
 		struct json_object *jdev;
e0018b
 
e0018b
@@ -208,10 +404,15 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
e0018b
 	}
e0018b
 
e0018b
 	cxl_bus_foreach(ctx, bus) {
e0018b
-		struct json_object *jbus;
e0018b
+		struct json_object *jbus = NULL;
e0018b
+		struct json_object *jchildports = NULL;
e0018b
+		struct cxl_port *port = cxl_bus_get_port(bus);
e0018b
+		const char *devname = cxl_bus_get_devname(bus);
e0018b
 
e0018b
 		if (!util_cxl_bus_filter(bus, p->bus_filter))
e0018b
-			continue;
e0018b
+			goto walk_children;
e0018b
+		if (!util_cxl_port_filter(port, p->port_filter, pf_mode(p)))
e0018b
+			goto walk_children;
e0018b
 		if (p->buses) {
e0018b
 			jbus = util_cxl_bus_to_json(bus, flags);
e0018b
 			if (!jbus) {
e0018b
@@ -219,16 +420,32 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
e0018b
 				continue;
e0018b
 			}
e0018b
 			json_object_array_add(jbuses, jbus);
e0018b
+			if (p->ports) {
e0018b
+				jchildports = json_object_new_array();
e0018b
+				if (!jchildports) {
e0018b
+					err(p,
e0018b
+					    "%s: failed to enumerate child ports\n",
e0018b
+					    devname);
e0018b
+					continue;
e0018b
+				}
e0018b
+			}
e0018b
 		}
e0018b
+walk_children:
e0018b
+		walk_child_ports(port, p, pick_array(jchildports, jports),
e0018b
+				 flags);
e0018b
+		cond_add_put_array_suffix(jbus, "ports", devname, jchildports);
e0018b
 	}
e0018b
 
e0018b
 	if (json_object_array_length(jdevs))
e0018b
 		top_level_objs++;
e0018b
 	if (json_object_array_length(jbuses))
e0018b
 		top_level_objs++;
e0018b
+	if (json_object_array_length(jports))
e0018b
+		top_level_objs++;
e0018b
 
e0018b
 	splice_array(p, jdevs, jplatform, "anon memdevs", top_level_objs > 1);
e0018b
 	splice_array(p, jbuses, jplatform, "buses", top_level_objs > 1);
e0018b
+	splice_array(p, jports, jplatform, "ports", top_level_objs > 1);
e0018b
 
e0018b
 	util_display_json_array(stdout, jplatform, flags);
e0018b
 
e0018b
@@ -236,6 +453,7 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
e0018b
 err:
e0018b
 	json_object_put(jdevs);
e0018b
 	json_object_put(jbuses);
e0018b
+	json_object_put(jports);
e0018b
 	json_object_put(jplatform);
e0018b
 	return -ENOMEM;
e0018b
 }
e0018b
diff --git a/cxl/filter.h b/cxl/filter.h
e0018b
index d41e757..0d83304 100644
e0018b
--- a/cxl/filter.h
e0018b
+++ b/cxl/filter.h
e0018b
@@ -10,7 +10,10 @@ struct cxl_filter_params {
e0018b
 	const char *memdev_filter;
e0018b
 	const char *serial_filter;
e0018b
 	const char *bus_filter;
e0018b
+	const char *port_filter;
e0018b
+	bool single;
e0018b
 	bool memdevs;
e0018b
+	bool ports;
e0018b
 	bool buses;
e0018b
 	bool idle;
e0018b
 	bool human;
e0018b
@@ -22,4 +25,5 @@ struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev,
e0018b
 					  const char *__ident,
e0018b
 					  const char *serials);
e0018b
 int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *param);
e0018b
+bool cxl_filter_has(const char *needle, const char *__filter);
e0018b
 #endif /* _CXL_UTIL_FILTER_H_ */
e0018b
diff --git a/cxl/json.c b/cxl/json.c
e0018b
index a584594..d9f864e 100644
e0018b
--- a/cxl/json.c
e0018b
+++ b/cxl/json.c
e0018b
@@ -242,3 +242,26 @@ struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
e0018b
 
e0018b
 	return jbus;
e0018b
 }
e0018b
+
e0018b
+struct json_object *util_cxl_port_to_json(struct cxl_port *port,
e0018b
+					  unsigned long flags)
e0018b
+{
e0018b
+	const char *devname = cxl_port_get_devname(port);
e0018b
+	struct json_object *jport, *jobj;
e0018b
+
e0018b
+	jport = json_object_new_object();
e0018b
+	if (!jport)
e0018b
+		return NULL;
e0018b
+
e0018b
+	jobj = json_object_new_string(devname);
e0018b
+	if (jobj)
e0018b
+		json_object_object_add(jport, "port", jobj);
e0018b
+
e0018b
+	if (!cxl_port_is_enabled(port)) {
e0018b
+		jobj = json_object_new_string("disabled");
e0018b
+		if (jobj)
e0018b
+			json_object_object_add(jport, "state", jobj);
e0018b
+	}
e0018b
+
e0018b
+	return jport;
e0018b
+}
e0018b
diff --git a/cxl/json.h b/cxl/json.h
e0018b
index 4abf6e5..36653db 100644
e0018b
--- a/cxl/json.h
e0018b
+++ b/cxl/json.h
e0018b
@@ -8,4 +8,7 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
e0018b
 struct cxl_bus;
e0018b
 struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
e0018b
 					 unsigned long flags);
e0018b
+struct cxl_port;
e0018b
+struct json_object *util_cxl_port_to_json(struct cxl_port *port,
e0018b
+					  unsigned long flags);
e0018b
 #endif /* __CXL_UTIL_JSON_H__ */
e0018b
diff --git a/cxl/lib/libcxl.c b/cxl/lib/libcxl.c
e0018b
index 8548a45..03eff3c 100644
e0018b
--- a/cxl/lib/libcxl.c
e0018b
+++ b/cxl/lib/libcxl.c
e0018b
@@ -66,15 +66,27 @@ static void free_memdev(struct cxl_memdev *memdev, struct list_head *head)
e0018b
 	free(memdev);
e0018b
 }
e0018b
 
e0018b
+static void free_port(struct cxl_port *port, struct list_head *head);
e0018b
 static void __free_port(struct cxl_port *port, struct list_head *head)
e0018b
 {
e0018b
+	struct cxl_port *child, *_c;
e0018b
+
e0018b
 	if (head)
e0018b
 		list_del_from(head, &port->list);
e0018b
+	list_for_each_safe(&port->child_ports, child, _c, list)
e0018b
+		free_port(child, &port->child_ports);
e0018b
+	kmod_module_unref(port->module);
e0018b
 	free(port->dev_buf);
e0018b
 	free(port->dev_path);
e0018b
 	free(port->uport);
e0018b
 }
e0018b
 
e0018b
+static void free_port(struct cxl_port *port, struct list_head *head)
e0018b
+{
e0018b
+	__free_port(port, head);
e0018b
+	free(port);
e0018b
+}
e0018b
+
e0018b
 static void free_bus(struct cxl_bus *bus, struct list_head *head)
e0018b
 {
e0018b
 	__free_port(&bus->port, head);
e0018b
@@ -471,10 +483,12 @@ CXL_EXPORT int cxl_memdev_nvdimm_bridge_active(struct cxl_memdev *memdev)
e0018b
 	return is_enabled(path);
e0018b
 }
e0018b
 
e0018b
-static int cxl_port_init(struct cxl_port *port, struct cxl_ctx *ctx, int id,
e0018b
+static int cxl_port_init(struct cxl_port *port, struct cxl_port *parent_port,
e0018b
+			 enum cxl_port_type type, struct cxl_ctx *ctx, int id,
e0018b
 			 const char *cxlport_base)
e0018b
 {
e0018b
 	char *path = calloc(1, strlen(cxlport_base) + 100);
e0018b
+	char buf[SYSFS_ATTR_SIZE];
e0018b
 	size_t rc;
e0018b
 
e0018b
 	if (!path)
e0018b
@@ -482,6 +496,10 @@ static int cxl_port_init(struct cxl_port *port, struct cxl_ctx *ctx, int id,
e0018b
 
e0018b
 	port->id = id;
e0018b
 	port->ctx = ctx;
e0018b
+	port->type = type;
e0018b
+	port->parent = parent_port;
e0018b
+
e0018b
+	list_head_init(&port->child_ports);
e0018b
 
e0018b
 	port->dev_path = strdup(cxlport_base);
e0018b
 	if (!port->dev_path)
e0018b
@@ -499,6 +517,10 @@ static int cxl_port_init(struct cxl_port *port, struct cxl_ctx *ctx, int id,
e0018b
 	if (!port->uport)
e0018b
 		goto err;
e0018b
 
e0018b
+	sprintf(path, "%s/modalias", cxlport_base);
e0018b
+	if (sysfs_read_attr(ctx, path, buf) == 0)
e0018b
+		port->module = util_modalias_to_module(ctx, buf);
e0018b
+
e0018b
 	return 0;
e0018b
 err:
e0018b
 	free(port->dev_path);
e0018b
@@ -507,6 +529,135 @@ err:
e0018b
 	return -ENOMEM;
e0018b
 }
e0018b
 
e0018b
+static void *add_cxl_port(void *parent, int id, const char *cxlport_base)
e0018b
+{
e0018b
+	const char *devname = devpath_to_devname(cxlport_base);
e0018b
+	struct cxl_port *port, *port_dup;
e0018b
+	struct cxl_port *parent_port = parent;
e0018b
+	struct cxl_ctx *ctx = cxl_port_get_ctx(parent_port);
e0018b
+	int rc;
e0018b
+
e0018b
+	dbg(ctx, "%s: base: \'%s\'\n", devname, cxlport_base);
e0018b
+
e0018b
+	port = calloc(1, sizeof(*port));
e0018b
+	if (!port)
e0018b
+		return NULL;
e0018b
+
e0018b
+	rc = cxl_port_init(port, parent_port, CXL_PORT_SWITCH, ctx, id,
e0018b
+			   cxlport_base);
e0018b
+	if (rc)
e0018b
+		goto err;
e0018b
+
e0018b
+	cxl_port_foreach(parent_port, port_dup)
e0018b
+		if (port_dup->id == port->id) {
e0018b
+			free_port(port, NULL);
e0018b
+			return port_dup;
e0018b
+		}
e0018b
+
e0018b
+	list_add(&parent_port->child_ports, &port->list);
e0018b
+	return port;
e0018b
+
e0018b
+err:
e0018b
+	free(port);
e0018b
+	return NULL;
e0018b
+
e0018b
+}
e0018b
+
e0018b
+static void cxl_ports_init(struct cxl_port *port)
e0018b
+{
e0018b
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
e0018b
+
e0018b
+	if (port->ports_init)
e0018b
+		return;
e0018b
+
e0018b
+	port->ports_init = 1;
e0018b
+
e0018b
+	sysfs_device_parse(ctx, port->dev_path, "port", port, add_cxl_port);
e0018b
+}
e0018b
+
e0018b
+CXL_EXPORT struct cxl_ctx *cxl_port_get_ctx(struct cxl_port *port)
e0018b
+{
e0018b
+	return port->ctx;
e0018b
+}
e0018b
+
e0018b
+CXL_EXPORT struct cxl_port *cxl_port_get_first(struct cxl_port *port)
e0018b
+{
e0018b
+	cxl_ports_init(port);
e0018b
+
e0018b
+	return list_top(&port->child_ports, struct cxl_port, list);
e0018b
+}
e0018b
+
e0018b
+CXL_EXPORT struct cxl_port *cxl_port_get_next(struct cxl_port *port)
e0018b
+{
e0018b
+	struct cxl_port *parent_port = port->parent;
e0018b
+
e0018b
+	return list_next(&parent_port->child_ports, port, list);
e0018b
+}
e0018b
+
e0018b
+CXL_EXPORT const char *cxl_port_get_devname(struct cxl_port *port)
e0018b
+{
e0018b
+	return devpath_to_devname(port->dev_path);
e0018b
+}
e0018b
+
e0018b
+CXL_EXPORT int cxl_port_get_id(struct cxl_port *port)
e0018b
+{
e0018b
+	return port->id;
e0018b
+}
e0018b
+
e0018b
+CXL_EXPORT struct cxl_port *cxl_port_get_parent(struct cxl_port *port)
e0018b
+{
e0018b
+	return port->parent;
e0018b
+}
e0018b
+
e0018b
+CXL_EXPORT bool cxl_port_is_root(struct cxl_port *port)
e0018b
+{
e0018b
+	return port->type == CXL_PORT_ROOT;
e0018b
+}
e0018b
+
e0018b
+CXL_EXPORT bool cxl_port_is_switch(struct cxl_port *port)
e0018b
+{
e0018b
+	return port->type == CXL_PORT_SWITCH;
e0018b
+}
e0018b
+
e0018b
+CXL_EXPORT struct cxl_bus *cxl_port_get_bus(struct cxl_port *port)
e0018b
+{
e0018b
+	struct cxl_bus *bus;
e0018b
+
e0018b
+	if (!cxl_port_is_enabled(port))
e0018b
+		return NULL;
e0018b
+
e0018b
+	if (port->bus)
e0018b
+		return port->bus;
e0018b
+
e0018b
+	while (port->parent)
e0018b
+		port = port->parent;
e0018b
+
e0018b
+	bus = container_of(port, typeof(*bus), port);
e0018b
+	port->bus = bus;
e0018b
+	return bus;
e0018b
+}
e0018b
+
e0018b
+CXL_EXPORT int cxl_port_is_enabled(struct cxl_port *port)
e0018b
+{
e0018b
+	struct cxl_ctx *ctx = cxl_port_get_ctx(port);
e0018b
+	char *path = port->dev_buf;
e0018b
+	int len = port->buf_len;
e0018b
+
e0018b
+	if (snprintf(path, len, "%s/driver", port->dev_path) >= len) {
e0018b
+		err(ctx, "%s: buffer too small!\n", cxl_port_get_devname(port));
e0018b
+		return 0;
e0018b
+	}
e0018b
+
e0018b
+	return is_enabled(path);
e0018b
+}
e0018b
+
e0018b
+CXL_EXPORT struct cxl_bus *cxl_port_to_bus(struct cxl_port *port)
e0018b
+{
e0018b
+	if (!cxl_port_is_root(port))
e0018b
+		return NULL;
e0018b
+	return container_of(port, struct cxl_bus, port);
e0018b
+}
e0018b
+
e0018b
 static void *add_cxl_bus(void *parent, int id, const char *cxlbus_base)
e0018b
 {
e0018b
 	const char *devname = devpath_to_devname(cxlbus_base);
e0018b
@@ -522,7 +673,7 @@ static void *add_cxl_bus(void *parent, int id, const char *cxlbus_base)
e0018b
 		return NULL;
e0018b
 
e0018b
 	port = &bus->port;
e0018b
-	rc = cxl_port_init(port, ctx, id, cxlbus_base);
e0018b
+	rc = cxl_port_init(port, NULL, CXL_PORT_ROOT, ctx, id, cxlbus_base);
e0018b
 	if (rc)
e0018b
 		goto err;
e0018b
 
e0018b
@@ -579,6 +730,11 @@ CXL_EXPORT int cxl_bus_get_id(struct cxl_bus *bus)
e0018b
 	return port->id;
e0018b
 }
e0018b
 
e0018b
+CXL_EXPORT struct cxl_port *cxl_bus_get_port(struct cxl_bus *bus)
e0018b
+{
e0018b
+	return &bus->port;
e0018b
+}
e0018b
+
e0018b
 CXL_EXPORT const char *cxl_bus_get_provider(struct cxl_bus *bus)
e0018b
 {
e0018b
 	struct cxl_port *port = &bus->port;
e0018b
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
e0018b
index 781ff99..a7e923f 100644
e0018b
--- a/cxl/lib/libcxl.sym
e0018b
+++ b/cxl/lib/libcxl.sym
e0018b
@@ -82,4 +82,16 @@ global:
e0018b
 	cxl_bus_get_provider;
e0018b
 	cxl_bus_get_devname;
e0018b
 	cxl_bus_get_id;
e0018b
+	cxl_bus_get_port;
e0018b
+	cxl_port_get_first;
e0018b
+	cxl_port_get_next;
e0018b
+	cxl_port_get_devname;
e0018b
+	cxl_port_get_id;
e0018b
+	cxl_port_get_ctx;
e0018b
+	cxl_port_is_enabled;
e0018b
+	cxl_port_get_parent;
e0018b
+	cxl_port_is_root;
e0018b
+	cxl_port_is_switch;
e0018b
+	cxl_port_to_bus;
e0018b
+	cxl_port_get_bus;
e0018b
 } LIBCXL_1;
e0018b
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
e0018b
index 0758d05..637f90d 100644
e0018b
--- a/cxl/lib/private.h
e0018b
+++ b/cxl/lib/private.h
e0018b
@@ -34,14 +34,25 @@ struct cxl_memdev {
e0018b
 	unsigned long long serial;
e0018b
 };
e0018b
 
e0018b
+enum cxl_port_type {
e0018b
+	CXL_PORT_ROOT,
e0018b
+	CXL_PORT_SWITCH,
e0018b
+};
e0018b
+
e0018b
 struct cxl_port {
e0018b
 	int id;
e0018b
 	void *dev_buf;
e0018b
 	size_t buf_len;
e0018b
 	char *dev_path;
e0018b
 	char *uport;
e0018b
+	int ports_init;
e0018b
 	struct cxl_ctx *ctx;
e0018b
+	struct cxl_bus *bus;
e0018b
+	enum cxl_port_type type;
e0018b
+	struct cxl_port *parent;
e0018b
+	struct kmod_module *module;
e0018b
 	struct list_node list;
e0018b
+	struct list_head child_ports;
e0018b
 };
e0018b
 
e0018b
 struct cxl_bus {
e0018b
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
e0018b
index da66eb2..efbb397 100644
e0018b
--- a/cxl/libcxl.h
e0018b
+++ b/cxl/libcxl.h
e0018b
@@ -5,6 +5,7 @@
e0018b
 
e0018b
 #include <stdarg.h>
e0018b
 #include <unistd.h>
e0018b
+#include <stdbool.h>
e0018b
 
e0018b
 #ifdef HAVE_UUID
e0018b
 #include <uuid/uuid.h>
e0018b
@@ -63,11 +64,29 @@ struct cxl_bus *cxl_bus_get_next(struct cxl_bus *bus);
e0018b
 const char *cxl_bus_get_provider(struct cxl_bus *bus);
e0018b
 const char *cxl_bus_get_devname(struct cxl_bus *bus);
e0018b
 int cxl_bus_get_id(struct cxl_bus *bus);
e0018b
+struct cxl_port *cxl_bus_get_port(struct cxl_bus *bus);
e0018b
 
e0018b
 #define cxl_bus_foreach(ctx, bus)                                              \
e0018b
 	for (bus = cxl_bus_get_first(ctx); bus != NULL;                        \
e0018b
 	     bus = cxl_bus_get_next(bus))
e0018b
 
e0018b
+struct cxl_port;
e0018b
+struct cxl_port *cxl_port_get_first(struct cxl_port *parent);
e0018b
+struct cxl_port *cxl_port_get_next(struct cxl_port *port);
e0018b
+const char *cxl_port_get_devname(struct cxl_port *port);
e0018b
+int cxl_port_get_id(struct cxl_port *port);
e0018b
+struct cxl_ctx *cxl_port_get_ctx(struct cxl_port *port);
e0018b
+int cxl_port_is_enabled(struct cxl_port *port);
e0018b
+struct cxl_port *cxl_port_get_parent(struct cxl_port *port);
e0018b
+bool cxl_port_is_root(struct cxl_port *port);
e0018b
+bool cxl_port_is_switch(struct cxl_port *port);
e0018b
+struct cxl_bus *cxl_port_to_bus(struct cxl_port *port);
e0018b
+struct cxl_bus *cxl_port_get_bus(struct cxl_port *port);
e0018b
+
e0018b
+#define cxl_port_foreach(parent, port)                                         \
e0018b
+	for (port = cxl_port_get_first(parent); port != NULL;                  \
e0018b
+	     port = cxl_port_get_next(port))
e0018b
+
e0018b
 struct cxl_cmd;
e0018b
 const char *cxl_cmd_get_devname(struct cxl_cmd *cmd);
e0018b
 struct cxl_cmd *cxl_cmd_new_raw(struct cxl_memdev *memdev, int opcode);
e0018b
diff --git a/cxl/list.c b/cxl/list.c
e0018b
index 9500e61..1ef91b4 100644
e0018b
--- a/cxl/list.c
e0018b
+++ b/cxl/list.c
e0018b
@@ -25,6 +25,11 @@ static const struct option options[] = {
e0018b
 	OPT_STRING('b', "bus", &param.bus_filter, "bus device name",
e0018b
 		   "filter by CXL bus device name(s)"),
e0018b
 	OPT_BOOLEAN('B', "buses", &param.buses, "include CXL bus info"),
e0018b
+	OPT_STRING('p', "port", &param.port_filter, "port device name",
e0018b
+		   "filter by CXL port device name(s)"),
e0018b
+	OPT_BOOLEAN('P', "ports", &param.ports, "include CXL port info"),
e0018b
+	OPT_BOOLEAN('S', "single", &param.single,
e0018b
+		    "skip listing descendant objects"),
e0018b
 	OPT_BOOLEAN('i', "idle", &param.idle, "include disabled devices"),
e0018b
 	OPT_BOOLEAN('u', "human", &param.human,
e0018b
 		    "use human friendly number formats "),
e0018b
@@ -35,7 +40,7 @@ static const struct option options[] = {
e0018b
 
e0018b
 static int num_list_flags(void)
e0018b
 {
e0018b
-       return !!param.memdevs + !!param.buses;
e0018b
+       return !!param.memdevs + !!param.buses + !!param.ports;
e0018b
 }
e0018b
 
e0018b
 int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
e0018b
@@ -53,11 +58,18 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
e0018b
 	if (argc)
e0018b
 		usage_with_options(u, options);
e0018b
 
e0018b
+	if (param.single && !param.port_filter) {
e0018b
+		error("-S/--single expects a port filter: -p/--port=\n");
e0018b
+		usage_with_options(u, options);
e0018b
+	}
e0018b
+
e0018b
 	if (num_list_flags() == 0) {
e0018b
 		if (param.memdev_filter || param.serial_filter)
e0018b
 			param.memdevs = true;
e0018b
 		if (param.bus_filter)
e0018b
 			param.buses = true;
e0018b
+		if (param.port_filter)
e0018b
+			param.ports = true;
e0018b
 		if (num_list_flags() == 0) {
e0018b
 			/*
e0018b
 			 * TODO: We likely want to list regions by default if
e0018b
@@ -73,5 +85,8 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
e0018b
 
e0018b
 	log_init(&param.ctx, "cxl list", "CXL_LIST_LOG");
e0018b
 
e0018b
+	if (cxl_filter_has(param.port_filter, "root") && param.ports)
e0018b
+		param.buses = true;
e0018b
+
e0018b
 	return cxl_filter_walk(ctx, ¶m;;
e0018b
 }
e0018b
-- 
e0018b
2.27.0
e0018b