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

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