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