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

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