new file mode 100644
@@ -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]
new file mode 100644
@@ -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]
@@ -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; \
@@ -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
@@ -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_ */
@@ -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)
@@ -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");
@@ -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_ */
@@ -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);
@@ -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;
@@ -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);
@@ -1,6 +1,7 @@
cxl_src = [
'cxl.c',
'list.c',
+ 'port.c',
'memdev.c',
'../util/json.c',
'../util/log.c',
new file mode 100644
@@ -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", ¶m.debug, "turn on debug"), \
+OPT_BOOLEAN('e', "endpoint", ¶m.endpoint, \
+ "target endpoints instead of switch ports")
+
+#define ENABLE_OPTIONS() \
+OPT_BOOLEAN('m', "enable-memdevs", ¶m.memdevs, \
+ "enable downstream memdev(s)")
+
+#define DISABLE_OPTIONS() \
+OPT_BOOLEAN('f', "force", ¶m.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;
+ }
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