|
|
26ccd9 |
From 9dce91c303720a336c55ecdc2e01e423589b85b2 Mon Sep 17 00:00:00 2001
|
|
|
26ccd9 |
From: Dan Williams <dan.j.williams@intel.com>
|
|
|
26ccd9 |
Date: Sun, 23 Jan 2022 16:53:02 -0800
|
|
|
26ccd9 |
Subject: [PATCH 100/217] cxl/list: Add bus objects
|
|
|
26ccd9 |
|
|
|
26ccd9 |
A 'struct cxl_bus' represents a CXL.mem domain. It is the root of a
|
|
|
26ccd9 |
Host-managed Device Memory (HDM) hierarchy. When memory devices are enabled
|
|
|
26ccd9 |
for CXL operation they appear underneath a bus in a 'cxl list -BM' listing,
|
|
|
26ccd9 |
otherwise they display as disconnected.
|
|
|
26ccd9 |
|
|
|
26ccd9 |
A 'bus' is identical to the kernel's CXL root port object, but given the
|
|
|
26ccd9 |
confusion between CXL root ports, and PCIe root ports, the 'bus' name is
|
|
|
26ccd9 |
less ambiguous. It also serves a similar role in the object hierarchy as a
|
|
|
26ccd9 |
'struct ndctl_bus' object. It is also the case that the "root" name will
|
|
|
26ccd9 |
appear as the kernel device-name, so the association will be clear.
|
|
|
26ccd9 |
|
|
|
26ccd9 |
Link: https://lore.kernel.org/r/164298558278.3021641.16323855851736615358.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 | 88 ++++++++++++++++---
|
|
|
26ccd9 |
Documentation/cxl/lib/libcxl.txt | 30 +++++++
|
|
|
26ccd9 |
cxl/filter.c | 117 ++++++++++++++++++++++++-
|
|
|
26ccd9 |
cxl/filter.h | 2 +
|
|
|
26ccd9 |
cxl/json.c | 21 +++++
|
|
|
26ccd9 |
cxl/json.h | 5 +-
|
|
|
26ccd9 |
cxl/lib/libcxl.c | 142 +++++++++++++++++++++++++++++++
|
|
|
26ccd9 |
cxl/lib/libcxl.sym | 5 ++
|
|
|
26ccd9 |
cxl/lib/private.h | 14 +++
|
|
|
26ccd9 |
cxl/libcxl.h | 11 +++
|
|
|
26ccd9 |
cxl/list.c | 19 +++--
|
|
|
26ccd9 |
12 files changed, 431 insertions(+), 24 deletions(-)
|
|
|
26ccd9 |
|
|
|
26ccd9 |
diff --git a/.clang-format b/.clang-format
|
|
|
26ccd9 |
index d2e77d0..1154c76 100644
|
|
|
26ccd9 |
--- a/.clang-format
|
|
|
26ccd9 |
+++ b/.clang-format
|
|
|
26ccd9 |
@@ -78,6 +78,7 @@ ExperimentalAutoDetectBinPacking: false
|
|
|
26ccd9 |
# | sort -u)
|
|
|
26ccd9 |
ForEachMacros:
|
|
|
26ccd9 |
- 'cxl_memdev_foreach'
|
|
|
26ccd9 |
+ - 'cxl_bus_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 224c972..be131ae 100644
|
|
|
26ccd9 |
--- a/Documentation/cxl/cxl-list.txt
|
|
|
26ccd9 |
+++ b/Documentation/cxl/cxl-list.txt
|
|
|
26ccd9 |
@@ -15,17 +15,60 @@ SYNOPSIS
|
|
|
26ccd9 |
Walk the CXL capable device hierarchy in the system and list all device
|
|
|
26ccd9 |
instances along with some of their major attributes.
|
|
|
26ccd9 |
|
|
|
26ccd9 |
-Options can be specified to limit the output to specific objects.
|
|
|
26ccd9 |
+Options can be specified to limit the output to specific objects. When a
|
|
|
26ccd9 |
+single object type is specified the return json object is an array of
|
|
|
26ccd9 |
+just those objects, when multiple objects types are specified the
|
|
|
26ccd9 |
+returned the returned object may be an array of arrays with the inner
|
|
|
26ccd9 |
+array named for the given object type.
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+Filters can by specifed as either a single identidier, a space separated
|
|
|
26ccd9 |
+quoted string, or a comma separated list. When multiple filter
|
|
|
26ccd9 |
+identifiers are specified within a filter string, like "-m
|
|
|
26ccd9 |
+mem0,mem1,mem2", they are combined as an 'OR' filter. When multiple
|
|
|
26ccd9 |
+filter string types are specified, like "-m mem0,mem1,mem2 -p port10",
|
|
|
26ccd9 |
+they are combined as an 'AND' filter. So, "-m mem0,mem1,mem2 -p port10"
|
|
|
26ccd9 |
+would only list objects that are beneath port10 AND map mem0, mem1, OR
|
|
|
26ccd9 |
+mem2.
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+The --human option in addition to reformatting some fields to more human
|
|
|
26ccd9 |
+friendly strings also unwraps the array to reduce the number of lines of
|
|
|
26ccd9 |
+output.
|
|
|
26ccd9 |
|
|
|
26ccd9 |
EXAMPLE
|
|
|
26ccd9 |
-------
|
|
|
26ccd9 |
----
|
|
|
26ccd9 |
# cxl list --memdevs
|
|
|
26ccd9 |
-{
|
|
|
26ccd9 |
- "memdev":"mem0",
|
|
|
26ccd9 |
- "pmem_size":268435456,
|
|
|
26ccd9 |
- "ram_size":0,
|
|
|
26ccd9 |
-}
|
|
|
26ccd9 |
+[
|
|
|
26ccd9 |
+ {
|
|
|
26ccd9 |
+ "memdev":"mem0",
|
|
|
26ccd9 |
+ "pmem_size":268435456,
|
|
|
26ccd9 |
+ "ram_size":0,
|
|
|
26ccd9 |
+ "serial":0
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+]
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+# cxl list -BMu
|
|
|
26ccd9 |
+[
|
|
|
26ccd9 |
+ {
|
|
|
26ccd9 |
+ "anon memdevs":[
|
|
|
26ccd9 |
+ {
|
|
|
26ccd9 |
+ "memdev":"mem0",
|
|
|
26ccd9 |
+ "pmem_size":"256.00 MiB (268.44 MB)",
|
|
|
26ccd9 |
+ "ram_size":0,
|
|
|
26ccd9 |
+ "serial":"0"
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+ ]
|
|
|
26ccd9 |
+ },
|
|
|
26ccd9 |
+ {
|
|
|
26ccd9 |
+ "buses":[
|
|
|
26ccd9 |
+ {
|
|
|
26ccd9 |
+ "bus":"root0",
|
|
|
26ccd9 |
+ "provider":"ACPI.CXL"
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+ ]
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+]
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
----
|
|
|
26ccd9 |
|
|
|
26ccd9 |
OPTIONS
|
|
|
26ccd9 |
@@ -34,13 +77,6 @@ OPTIONS
|
|
|
26ccd9 |
--memdev=::
|
|
|
26ccd9 |
Specify CXL memory device name(s), or device id(s), to filter the listing. For example:
|
|
|
26ccd9 |
----
|
|
|
26ccd9 |
-# cxl list --memdev=mem0
|
|
|
26ccd9 |
-{
|
|
|
26ccd9 |
- "memdev":"mem0",
|
|
|
26ccd9 |
- "pmem_size":268435456,
|
|
|
26ccd9 |
- "ram_size":0,
|
|
|
26ccd9 |
-}
|
|
|
26ccd9 |
-
|
|
|
26ccd9 |
# cxl list -M --memdev="0 mem3 5"
|
|
|
26ccd9 |
[
|
|
|
26ccd9 |
{
|
|
|
26ccd9 |
@@ -114,6 +150,32 @@ OPTIONS
|
|
|
26ccd9 |
]
|
|
|
26ccd9 |
----
|
|
|
26ccd9 |
|
|
|
26ccd9 |
+-B::
|
|
|
26ccd9 |
+--buses::
|
|
|
26ccd9 |
+ Include 'bus' / CXL root object(s) in the listing. Typically, on ACPI
|
|
|
26ccd9 |
+ systems the bus object is a singleton associated with the ACPI0017
|
|
|
26ccd9 |
+ device, but there are test scenerios where there may be multiple CXL
|
|
|
26ccd9 |
+ memory hierarchies.
|
|
|
26ccd9 |
+----
|
|
|
26ccd9 |
+# cxl list -B
|
|
|
26ccd9 |
+[
|
|
|
26ccd9 |
+ {
|
|
|
26ccd9 |
+ "bus":"root3",
|
|
|
26ccd9 |
+ "provider":"cxl_test"
|
|
|
26ccd9 |
+ },
|
|
|
26ccd9 |
+ {
|
|
|
26ccd9 |
+ "bus":"root0",
|
|
|
26ccd9 |
+ "provider":"ACPI.CXL"
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+]
|
|
|
26ccd9 |
+----
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+-b::
|
|
|
26ccd9 |
+--bus=::
|
|
|
26ccd9 |
+ Specify CXL root device name(s), device id(s), and / or CXL bus provider
|
|
|
26ccd9 |
+ names to filter the listing. The supported provider names are "ACPI.CXL"
|
|
|
26ccd9 |
+ and "cxl_test".
|
|
|
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 c127326..84af66a 100644
|
|
|
26ccd9 |
--- a/Documentation/cxl/lib/libcxl.txt
|
|
|
26ccd9 |
+++ b/Documentation/cxl/lib/libcxl.txt
|
|
|
26ccd9 |
@@ -134,6 +134,36 @@ cxl_memdev{read,write,zero}_label() are helpers for marshaling multiple
|
|
|
26ccd9 |
label access commands over an arbitrary extent of the device's label
|
|
|
26ccd9 |
area.
|
|
|
26ccd9 |
|
|
|
26ccd9 |
+BUSES
|
|
|
26ccd9 |
+-----
|
|
|
26ccd9 |
+The CXL Memory space is CPU and Device coherent. The address ranges that
|
|
|
26ccd9 |
+support coherent access are described by platform firmware and
|
|
|
26ccd9 |
+communicated to the operating system via a CXL root object 'struct
|
|
|
26ccd9 |
+cxl_bus'.
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+=== BUS: Enumeration
|
|
|
26ccd9 |
+----
|
|
|
26ccd9 |
+struct cxl_bus *cxl_bus_get_first(struct cxl_ctx *ctx);
|
|
|
26ccd9 |
+struct cxl_bus *cxl_bus_get_next(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 |
+
|
|
|
26ccd9 |
+=== BUS: Attributes
|
|
|
26ccd9 |
+----
|
|
|
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 |
+----
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+The provider name of a bus is a persistent name that is independent of
|
|
|
26ccd9 |
+discovery order. The possible provider names are 'ACPI.CXL' and
|
|
|
26ccd9 |
+'cxl_test'. The devname and id attributes, like other objects, are just
|
|
|
26ccd9 |
+the kernel device names that are subject to change based on discovery
|
|
|
26ccd9 |
+order.
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
include::../../copyright.txt[]
|
|
|
26ccd9 |
|
|
|
26ccd9 |
SEE ALSO
|
|
|
26ccd9 |
diff --git a/cxl/filter.c b/cxl/filter.c
|
|
|
26ccd9 |
index 26efc65..5f4844b 100644
|
|
|
26ccd9 |
--- a/cxl/filter.c
|
|
|
26ccd9 |
+++ b/cxl/filter.c
|
|
|
26ccd9 |
@@ -1,5 +1,5 @@
|
|
|
26ccd9 |
// SPDX-License-Identifier: GPL-2.0
|
|
|
26ccd9 |
-// Copyright (C) 2015-2020 Intel Corporation. All rights reserved.
|
|
|
26ccd9 |
+// Copyright (C) 2015-2022 Intel Corporation. All rights reserved.
|
|
|
26ccd9 |
#include <errno.h>
|
|
|
26ccd9 |
#include <stdio.h>
|
|
|
26ccd9 |
#include <string.h>
|
|
|
26ccd9 |
@@ -21,6 +21,43 @@ static const char *which_sep(const char *filter)
|
|
|
26ccd9 |
return " ";
|
|
|
26ccd9 |
}
|
|
|
26ccd9 |
|
|
|
26ccd9 |
+static struct cxl_bus *util_cxl_bus_filter(struct cxl_bus *bus,
|
|
|
26ccd9 |
+ const char *__ident)
|
|
|
26ccd9 |
+{
|
|
|
26ccd9 |
+ char *ident, *save;
|
|
|
26ccd9 |
+ const char *arg;
|
|
|
26ccd9 |
+ int bus_id;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ if (!__ident)
|
|
|
26ccd9 |
+ return bus;
|
|
|
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 ((sscanf(arg, "%d", &bus_id) == 1 ||
|
|
|
26ccd9 |
+ sscanf(arg, "root%d", &bus_id) == 1) &&
|
|
|
26ccd9 |
+ cxl_bus_get_id(bus) == bus_id)
|
|
|
26ccd9 |
+ break;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ if (strcmp(arg, cxl_bus_get_devname(bus)) == 0)
|
|
|
26ccd9 |
+ break;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ if (strcmp(arg, cxl_bus_get_provider(bus)) == 0)
|
|
|
26ccd9 |
+ break;
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ free(ident);
|
|
|
26ccd9 |
+ if (arg)
|
|
|
26ccd9 |
+ return bus;
|
|
|
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 |
@@ -98,21 +135,67 @@ static unsigned long params_to_flags(struct cxl_filter_params *param)
|
|
|
26ccd9 |
return flags;
|
|
|
26ccd9 |
}
|
|
|
26ccd9 |
|
|
|
26ccd9 |
+static void splice_array(struct cxl_filter_params *p, struct json_object *jobjs,
|
|
|
26ccd9 |
+ struct json_object *platform,
|
|
|
26ccd9 |
+ const char *container_name, bool do_container)
|
|
|
26ccd9 |
+{
|
|
|
26ccd9 |
+ size_t count;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ if (!json_object_array_length(jobjs)) {
|
|
|
26ccd9 |
+ json_object_put(jobjs);
|
|
|
26ccd9 |
+ return;
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ if (do_container) {
|
|
|
26ccd9 |
+ struct json_object *container = json_object_new_object();
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ if (!container) {
|
|
|
26ccd9 |
+ err(p, "failed to list: %s\n", container_name);
|
|
|
26ccd9 |
+ return;
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ json_object_object_add(container, container_name, jobjs);
|
|
|
26ccd9 |
+ json_object_array_add(platform, container);
|
|
|
26ccd9 |
+ return;
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ for (count = json_object_array_length(jobjs); count; count--) {
|
|
|
26ccd9 |
+ struct json_object *jobj = json_object_array_get_idx(jobjs, 0);
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ json_object_get(jobj);
|
|
|
26ccd9 |
+ json_object_array_del_idx(jobjs, 0, 1);
|
|
|
26ccd9 |
+ json_object_array_add(platform, jobj);
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+ json_object_put(jobjs);
|
|
|
26ccd9 |
+}
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
|
|
|
26ccd9 |
{
|
|
|
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 |
+ struct cxl_bus *bus;
|
|
|
26ccd9 |
|
|
|
26ccd9 |
if (!jplatform) {
|
|
|
26ccd9 |
dbg(p, "platform object allocation failure\n");
|
|
|
26ccd9 |
return -ENOMEM;
|
|
|
26ccd9 |
}
|
|
|
26ccd9 |
|
|
|
26ccd9 |
+ jdevs = json_object_new_array();
|
|
|
26ccd9 |
+ if (!jdevs)
|
|
|
26ccd9 |
+ goto err;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ jbuses = json_object_new_array();
|
|
|
26ccd9 |
+ if (!jbuses)
|
|
|
26ccd9 |
+ goto err;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
cxl_memdev_foreach(ctx, memdev) {
|
|
|
26ccd9 |
struct json_object *jdev;
|
|
|
26ccd9 |
|
|
|
26ccd9 |
- if (!util_cxl_memdev_filter(memdev, p->memdev_filter, p->serial_filter))
|
|
|
26ccd9 |
+ if (!util_cxl_memdev_filter(memdev, p->memdev_filter,
|
|
|
26ccd9 |
+ p->serial_filter))
|
|
|
26ccd9 |
continue;
|
|
|
26ccd9 |
if (p->memdevs) {
|
|
|
26ccd9 |
jdev = util_cxl_memdev_to_json(memdev, flags);
|
|
|
26ccd9 |
@@ -120,11 +203,39 @@ int cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p)
|
|
|
26ccd9 |
dbg(p, "memdev object allocation failure\n");
|
|
|
26ccd9 |
continue;
|
|
|
26ccd9 |
}
|
|
|
26ccd9 |
- json_object_array_add(jplatform, jdev);
|
|
|
26ccd9 |
+ json_object_array_add(jdevs, jdev);
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ cxl_bus_foreach(ctx, bus) {
|
|
|
26ccd9 |
+ struct json_object *jbus;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ if (!util_cxl_bus_filter(bus, p->bus_filter))
|
|
|
26ccd9 |
+ continue;
|
|
|
26ccd9 |
+ if (p->buses) {
|
|
|
26ccd9 |
+ jbus = util_cxl_bus_to_json(bus, flags);
|
|
|
26ccd9 |
+ if (!jbus) {
|
|
|
26ccd9 |
+ dbg(p, "bus object allocation failure\n");
|
|
|
26ccd9 |
+ continue;
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+ json_object_array_add(jbuses, jbus);
|
|
|
26ccd9 |
}
|
|
|
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 |
+
|
|
|
26ccd9 |
+ splice_array(p, jdevs, jplatform, "anon memdevs", top_level_objs > 1);
|
|
|
26ccd9 |
+ splice_array(p, jbuses, jplatform, "buses", top_level_objs > 1);
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
util_display_json_array(stdout, jplatform, flags);
|
|
|
26ccd9 |
|
|
|
26ccd9 |
return 0;
|
|
|
26ccd9 |
+err:
|
|
|
26ccd9 |
+ json_object_put(jdevs);
|
|
|
26ccd9 |
+ json_object_put(jbuses);
|
|
|
26ccd9 |
+ json_object_put(jplatform);
|
|
|
26ccd9 |
+ return -ENOMEM;
|
|
|
26ccd9 |
}
|
|
|
26ccd9 |
diff --git a/cxl/filter.h b/cxl/filter.h
|
|
|
26ccd9 |
index 12d9344..d41e757 100644
|
|
|
26ccd9 |
--- a/cxl/filter.h
|
|
|
26ccd9 |
+++ b/cxl/filter.h
|
|
|
26ccd9 |
@@ -9,7 +9,9 @@
|
|
|
26ccd9 |
struct cxl_filter_params {
|
|
|
26ccd9 |
const char *memdev_filter;
|
|
|
26ccd9 |
const char *serial_filter;
|
|
|
26ccd9 |
+ const char *bus_filter;
|
|
|
26ccd9 |
bool memdevs;
|
|
|
26ccd9 |
+ bool buses;
|
|
|
26ccd9 |
bool idle;
|
|
|
26ccd9 |
bool human;
|
|
|
26ccd9 |
bool health;
|
|
|
26ccd9 |
diff --git a/cxl/json.c b/cxl/json.c
|
|
|
26ccd9 |
index d8e65df..a584594 100644
|
|
|
26ccd9 |
--- a/cxl/json.c
|
|
|
26ccd9 |
+++ b/cxl/json.c
|
|
|
26ccd9 |
@@ -221,3 +221,24 @@ struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
|
|
|
26ccd9 |
}
|
|
|
26ccd9 |
return jdev;
|
|
|
26ccd9 |
}
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
|
|
|
26ccd9 |
+ unsigned long flags)
|
|
|
26ccd9 |
+{
|
|
|
26ccd9 |
+ const char *devname = cxl_bus_get_devname(bus);
|
|
|
26ccd9 |
+ struct json_object *jbus, *jobj;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ jbus = json_object_new_object();
|
|
|
26ccd9 |
+ if (!jbus)
|
|
|
26ccd9 |
+ return NULL;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ jobj = json_object_new_string(devname);
|
|
|
26ccd9 |
+ if (jobj)
|
|
|
26ccd9 |
+ json_object_object_add(jbus, "bus", jobj);
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ jobj = json_object_new_string(cxl_bus_get_provider(bus));
|
|
|
26ccd9 |
+ if (jobj)
|
|
|
26ccd9 |
+ json_object_object_add(jbus, "provider", jobj);
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ return jbus;
|
|
|
26ccd9 |
+}
|
|
|
26ccd9 |
diff --git a/cxl/json.h b/cxl/json.h
|
|
|
26ccd9 |
index 3abcfe6..4abf6e5 100644
|
|
|
26ccd9 |
--- a/cxl/json.h
|
|
|
26ccd9 |
+++ b/cxl/json.h
|
|
|
26ccd9 |
@@ -1,8 +1,11 @@
|
|
|
26ccd9 |
/* SPDX-License-Identifier: GPL-2.0 */
|
|
|
26ccd9 |
-/* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */
|
|
|
26ccd9 |
+/* Copyright (C) 2015-2022 Intel Corporation. All rights reserved. */
|
|
|
26ccd9 |
#ifndef __CXL_UTIL_JSON_H__
|
|
|
26ccd9 |
#define __CXL_UTIL_JSON_H__
|
|
|
26ccd9 |
struct cxl_memdev;
|
|
|
26ccd9 |
struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev,
|
|
|
26ccd9 |
unsigned long flags);
|
|
|
26ccd9 |
+struct cxl_bus;
|
|
|
26ccd9 |
+struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus,
|
|
|
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 9839f26..8548a45 100644
|
|
|
26ccd9 |
--- a/cxl/lib/libcxl.c
|
|
|
26ccd9 |
+++ b/cxl/lib/libcxl.c
|
|
|
26ccd9 |
@@ -40,7 +40,9 @@ struct cxl_ctx {
|
|
|
26ccd9 |
int refcount;
|
|
|
26ccd9 |
void *userdata;
|
|
|
26ccd9 |
int memdevs_init;
|
|
|
26ccd9 |
+ int buses_init;
|
|
|
26ccd9 |
struct list_head memdevs;
|
|
|
26ccd9 |
+ struct list_head buses;
|
|
|
26ccd9 |
struct kmod_ctx *kmod_ctx;
|
|
|
26ccd9 |
void *private_data;
|
|
|
26ccd9 |
};
|
|
|
26ccd9 |
@@ -64,6 +66,21 @@ 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 |
+{
|
|
|
26ccd9 |
+ if (head)
|
|
|
26ccd9 |
+ list_del_from(head, &port->list);
|
|
|
26ccd9 |
+ free(port->dev_buf);
|
|
|
26ccd9 |
+ free(port->dev_path);
|
|
|
26ccd9 |
+ free(port->uport);
|
|
|
26ccd9 |
+}
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+static void free_bus(struct cxl_bus *bus, struct list_head *head)
|
|
|
26ccd9 |
+{
|
|
|
26ccd9 |
+ __free_port(&bus->port, head);
|
|
|
26ccd9 |
+ free(bus);
|
|
|
26ccd9 |
+}
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
/**
|
|
|
26ccd9 |
* cxl_get_userdata - retrieve stored data pointer from library context
|
|
|
26ccd9 |
* @ctx: cxl library context
|
|
|
26ccd9 |
@@ -130,6 +147,7 @@ CXL_EXPORT int cxl_new(struct cxl_ctx **ctx)
|
|
|
26ccd9 |
dbg(c, "log_priority=%d\n", c->ctx.log_priority);
|
|
|
26ccd9 |
*ctx = c;
|
|
|
26ccd9 |
list_head_init(&c->memdevs);
|
|
|
26ccd9 |
+ list_head_init(&c->buses);
|
|
|
26ccd9 |
c->kmod_ctx = kmod_ctx;
|
|
|
26ccd9 |
|
|
|
26ccd9 |
return 0;
|
|
|
26ccd9 |
@@ -160,6 +178,7 @@ CXL_EXPORT struct cxl_ctx *cxl_ref(struct cxl_ctx *ctx)
|
|
|
26ccd9 |
CXL_EXPORT void cxl_unref(struct cxl_ctx *ctx)
|
|
|
26ccd9 |
{
|
|
|
26ccd9 |
struct cxl_memdev *memdev, *_d;
|
|
|
26ccd9 |
+ struct cxl_bus *bus, *_b;
|
|
|
26ccd9 |
|
|
|
26ccd9 |
if (ctx == NULL)
|
|
|
26ccd9 |
return;
|
|
|
26ccd9 |
@@ -170,6 +189,9 @@ CXL_EXPORT void cxl_unref(struct cxl_ctx *ctx)
|
|
|
26ccd9 |
list_for_each_safe(&ctx->memdevs, memdev, _d, list)
|
|
|
26ccd9 |
free_memdev(memdev, &ctx->memdevs);
|
|
|
26ccd9 |
|
|
|
26ccd9 |
+ list_for_each_safe(&ctx->buses, bus, _b, port.list)
|
|
|
26ccd9 |
+ free_bus(bus, &ctx->buses);
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
kmod_unref(ctx->kmod_ctx);
|
|
|
26ccd9 |
info(ctx, "context %p released\n", ctx);
|
|
|
26ccd9 |
free(ctx);
|
|
|
26ccd9 |
@@ -449,6 +471,126 @@ 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 |
+ const char *cxlport_base)
|
|
|
26ccd9 |
+{
|
|
|
26ccd9 |
+ char *path = calloc(1, strlen(cxlport_base) + 100);
|
|
|
26ccd9 |
+ size_t rc;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ if (!path)
|
|
|
26ccd9 |
+ return -ENOMEM;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ port->id = id;
|
|
|
26ccd9 |
+ port->ctx = ctx;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ port->dev_path = strdup(cxlport_base);
|
|
|
26ccd9 |
+ if (!port->dev_path)
|
|
|
26ccd9 |
+ goto err;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ port->dev_buf = calloc(1, strlen(cxlport_base) + 50);
|
|
|
26ccd9 |
+ if (!port->dev_buf)
|
|
|
26ccd9 |
+ goto err;
|
|
|
26ccd9 |
+ port->buf_len = strlen(cxlport_base) + 50;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ rc = snprintf(port->dev_buf, port->buf_len, "%s/uport", cxlport_base);
|
|
|
26ccd9 |
+ if (rc >= port->buf_len)
|
|
|
26ccd9 |
+ goto err;
|
|
|
26ccd9 |
+ port->uport = realpath(port->dev_buf, NULL);
|
|
|
26ccd9 |
+ if (!port->uport)
|
|
|
26ccd9 |
+ goto err;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ return 0;
|
|
|
26ccd9 |
+err:
|
|
|
26ccd9 |
+ free(port->dev_path);
|
|
|
26ccd9 |
+ free(port->dev_buf);
|
|
|
26ccd9 |
+ free(path);
|
|
|
26ccd9 |
+ return -ENOMEM;
|
|
|
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 |
+ struct cxl_bus *bus, *bus_dup;
|
|
|
26ccd9 |
+ struct cxl_ctx *ctx = parent;
|
|
|
26ccd9 |
+ struct cxl_port *port;
|
|
|
26ccd9 |
+ int rc;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ dbg(ctx, "%s: base: \'%s\'\n", devname, cxlbus_base);
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ bus = calloc(1, sizeof(*bus));
|
|
|
26ccd9 |
+ if (!bus)
|
|
|
26ccd9 |
+ return NULL;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ port = &bus->port;
|
|
|
26ccd9 |
+ rc = cxl_port_init(port, ctx, id, cxlbus_base);
|
|
|
26ccd9 |
+ if (rc)
|
|
|
26ccd9 |
+ goto err;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ cxl_bus_foreach(ctx, bus_dup)
|
|
|
26ccd9 |
+ if (bus_dup->port.id == bus->port.id) {
|
|
|
26ccd9 |
+ free_bus(bus, NULL);
|
|
|
26ccd9 |
+ return bus_dup;
|
|
|
26ccd9 |
+ }
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ list_add(&ctx->buses, &port->list);
|
|
|
26ccd9 |
+ return bus;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+err:
|
|
|
26ccd9 |
+ free(bus);
|
|
|
26ccd9 |
+ return NULL;
|
|
|
26ccd9 |
+}
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+static void cxl_buses_init(struct cxl_ctx *ctx)
|
|
|
26ccd9 |
+{
|
|
|
26ccd9 |
+ if (ctx->buses_init)
|
|
|
26ccd9 |
+ return;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ ctx->buses_init = 1;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ sysfs_device_parse(ctx, "/sys/bus/cxl/devices", "root", ctx,
|
|
|
26ccd9 |
+ add_cxl_bus);
|
|
|
26ccd9 |
+}
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+CXL_EXPORT struct cxl_bus *cxl_bus_get_first(struct cxl_ctx *ctx)
|
|
|
26ccd9 |
+{
|
|
|
26ccd9 |
+ cxl_buses_init(ctx);
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ return list_top(&ctx->buses, struct cxl_bus, port.list);
|
|
|
26ccd9 |
+}
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+CXL_EXPORT struct cxl_bus *cxl_bus_get_next(struct cxl_bus *bus)
|
|
|
26ccd9 |
+{
|
|
|
26ccd9 |
+ struct cxl_ctx *ctx = bus->port.ctx;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ return list_next(&ctx->buses, bus, port.list);
|
|
|
26ccd9 |
+}
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+CXL_EXPORT const char *cxl_bus_get_devname(struct cxl_bus *bus)
|
|
|
26ccd9 |
+{
|
|
|
26ccd9 |
+ struct cxl_port *port = &bus->port;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ return devpath_to_devname(port->dev_path);
|
|
|
26ccd9 |
+}
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+CXL_EXPORT int cxl_bus_get_id(struct cxl_bus *bus)
|
|
|
26ccd9 |
+{
|
|
|
26ccd9 |
+ struct cxl_port *port = &bus->port;
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ return port->id;
|
|
|
26ccd9 |
+}
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+CXL_EXPORT const char *cxl_bus_get_provider(struct cxl_bus *bus)
|
|
|
26ccd9 |
+{
|
|
|
26ccd9 |
+ struct cxl_port *port = &bus->port;
|
|
|
26ccd9 |
+ const char *devname = devpath_to_devname(port->uport);
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+ if (strcmp(devname, "ACPI0017:00") == 0)
|
|
|
26ccd9 |
+ return "ACPI.CXL";
|
|
|
26ccd9 |
+ if (strcmp(devname, "cxl_acpi.0") == 0)
|
|
|
26ccd9 |
+ return "cxl_test";
|
|
|
26ccd9 |
+ return devname;
|
|
|
26ccd9 |
+}
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
CXL_EXPORT void cxl_cmd_unref(struct cxl_cmd *cmd)
|
|
|
26ccd9 |
{
|
|
|
26ccd9 |
if (!cmd)
|
|
|
26ccd9 |
diff --git a/cxl/lib/libcxl.sym b/cxl/lib/libcxl.sym
|
|
|
26ccd9 |
index 4411035..781ff99 100644
|
|
|
26ccd9 |
--- a/cxl/lib/libcxl.sym
|
|
|
26ccd9 |
+++ b/cxl/lib/libcxl.sym
|
|
|
26ccd9 |
@@ -77,4 +77,9 @@ local:
|
|
|
26ccd9 |
LIBCXL_2 {
|
|
|
26ccd9 |
global:
|
|
|
26ccd9 |
cxl_memdev_get_serial;
|
|
|
26ccd9 |
+ cxl_bus_get_first;
|
|
|
26ccd9 |
+ cxl_bus_get_next;
|
|
|
26ccd9 |
+ cxl_bus_get_provider;
|
|
|
26ccd9 |
+ cxl_bus_get_devname;
|
|
|
26ccd9 |
+ cxl_bus_get_id;
|
|
|
26ccd9 |
} LIBCXL_1;
|
|
|
26ccd9 |
diff --git a/cxl/lib/private.h b/cxl/lib/private.h
|
|
|
26ccd9 |
index 7c81e24..0758d05 100644
|
|
|
26ccd9 |
--- a/cxl/lib/private.h
|
|
|
26ccd9 |
+++ b/cxl/lib/private.h
|
|
|
26ccd9 |
@@ -34,6 +34,20 @@ struct cxl_memdev {
|
|
|
26ccd9 |
unsigned long long serial;
|
|
|
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 |
+ struct cxl_ctx *ctx;
|
|
|
26ccd9 |
+ struct list_node list;
|
|
|
26ccd9 |
+};
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
+struct cxl_bus {
|
|
|
26ccd9 |
+ struct cxl_port port;
|
|
|
26ccd9 |
+};
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
enum cxl_cmd_query_status {
|
|
|
26ccd9 |
CXL_CMD_QUERY_NOT_RUN = 0,
|
|
|
26ccd9 |
CXL_CMD_QUERY_OK,
|
|
|
26ccd9 |
diff --git a/cxl/libcxl.h b/cxl/libcxl.h
|
|
|
26ccd9 |
index bcdede8..da66eb2 100644
|
|
|
26ccd9 |
--- a/cxl/libcxl.h
|
|
|
26ccd9 |
+++ b/cxl/libcxl.h
|
|
|
26ccd9 |
@@ -57,6 +57,17 @@ int cxl_memdev_write_label(struct cxl_memdev *memdev, void *buf, size_t length,
|
|
|
26ccd9 |
memdev != NULL; \
|
|
|
26ccd9 |
memdev = cxl_memdev_get_next(memdev))
|
|
|
26ccd9 |
|
|
|
26ccd9 |
+struct cxl_bus;
|
|
|
26ccd9 |
+struct cxl_bus *cxl_bus_get_first(struct cxl_ctx *ctx);
|
|
|
26ccd9 |
+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 |
+
|
|
|
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_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 7e2744d..9500e61 100644
|
|
|
26ccd9 |
--- a/cxl/list.c
|
|
|
26ccd9 |
+++ b/cxl/list.c
|
|
|
26ccd9 |
@@ -1,5 +1,5 @@
|
|
|
26ccd9 |
// SPDX-License-Identifier: GPL-2.0
|
|
|
26ccd9 |
-/* Copyright (C) 2020-2021 Intel Corporation. All rights reserved. */
|
|
|
26ccd9 |
+/* Copyright (C) 2020-2022 Intel Corporation. All rights reserved. */
|
|
|
26ccd9 |
#include <stdio.h>
|
|
|
26ccd9 |
#include <errno.h>
|
|
|
26ccd9 |
#include <stdlib.h>
|
|
|
26ccd9 |
@@ -14,11 +14,6 @@
|
|
|
26ccd9 |
|
|
|
26ccd9 |
static struct cxl_filter_params param;
|
|
|
26ccd9 |
|
|
|
26ccd9 |
-static int num_list_flags(void)
|
|
|
26ccd9 |
-{
|
|
|
26ccd9 |
- return param.memdevs;
|
|
|
26ccd9 |
-}
|
|
|
26ccd9 |
-
|
|
|
26ccd9 |
static const struct option options[] = {
|
|
|
26ccd9 |
OPT_STRING('m', "memdev", ¶m.memdev_filter, "memory device name(s)",
|
|
|
26ccd9 |
"filter by CXL memory device name(s)"),
|
|
|
26ccd9 |
@@ -27,6 +22,9 @@ static const struct option options[] = {
|
|
|
26ccd9 |
"filter by CXL memory device serial number(s)"),
|
|
|
26ccd9 |
OPT_BOOLEAN('M', "memdevs", ¶m.memdevs,
|
|
|
26ccd9 |
"include CXL memory device info"),
|
|
|
26ccd9 |
+ OPT_STRING('b', "bus", ¶m.bus_filter, "bus device name",
|
|
|
26ccd9 |
+ "filter by CXL bus device name(s)"),
|
|
|
26ccd9 |
+ OPT_BOOLEAN('B', "buses", ¶m.buses, "include CXL bus info"),
|
|
|
26ccd9 |
OPT_BOOLEAN('i', "idle", ¶m.idle, "include disabled devices"),
|
|
|
26ccd9 |
OPT_BOOLEAN('u', "human", ¶m.human,
|
|
|
26ccd9 |
"use human friendly number formats "),
|
|
|
26ccd9 |
@@ -35,6 +33,11 @@ static const struct option options[] = {
|
|
|
26ccd9 |
OPT_END(),
|
|
|
26ccd9 |
};
|
|
|
26ccd9 |
|
|
|
26ccd9 |
+static int num_list_flags(void)
|
|
|
26ccd9 |
+{
|
|
|
26ccd9 |
+ return !!param.memdevs + !!param.buses;
|
|
|
26ccd9 |
+}
|
|
|
26ccd9 |
+
|
|
|
26ccd9 |
int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
|
|
|
26ccd9 |
{
|
|
|
26ccd9 |
const char * const u[] = {
|
|
|
26ccd9 |
@@ -53,7 +56,9 @@ int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx)
|
|
|
26ccd9 |
if (num_list_flags() == 0) {
|
|
|
26ccd9 |
if (param.memdev_filter || param.serial_filter)
|
|
|
26ccd9 |
param.memdevs = true;
|
|
|
26ccd9 |
- else {
|
|
|
26ccd9 |
+ if (param.bus_filter)
|
|
|
26ccd9 |
+ param.buses = true;
|
|
|
26ccd9 |
+ if (num_list_flags() == 0) {
|
|
|
26ccd9 |
/*
|
|
|
26ccd9 |
* TODO: We likely want to list regions by default if
|
|
|
26ccd9 |
* nothing was explicitly asked for. But until we have
|
|
|
26ccd9 |
--
|
|
|
26ccd9 |
2.27.0
|
|
|
26ccd9 |
|