Add the group membership information to a CCW device. Allow to filter
CCW devices based on a group membership.
Signed-off-by: Boris Fiuczynski <fiuczy(a)linux.ibm.com>
---
docs/manpages/virsh.rst | 19 ++--
include/libvirt/libvirt-nodedev.h | 1 +
src/conf/node_device_conf.c | 105 +++++++++++++++++-
src/conf/node_device_conf.h | 10 +-
src/conf/schemas/nodedev.rng | 12 ++
src/conf/virnodedeviceobj.c | 11 +-
src/libvirt_private.syms | 1 +
src/node_device/node_device_driver.c | 2 +
src/node_device/node_device_udev.c | 4 +
src/util/virccw.c | 23 ++++
src/util/virccw.h | 3 +
.../ccw_0_0_ff02_ccwgroup.xml | 13 +++
.../ccw_0_0_ff02_ccwgroup.xml | 1 +
tests/nodedevxml2xmltest.c | 1 +
tools/virsh-nodedev.c | 3 +
15 files changed, 196 insertions(+), 13 deletions(-)
create mode 100644 tests/nodedevschemadata/ccw_0_0_ff02_ccwgroup.xml
create mode 120000 tests/nodedevxml2xmlout/ccw_0_0_ff02_ccwgroup.xml
diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst
index 5e5734dff1..9e549f25a6 100644
--- a/docs/manpages/virsh.rst
+++ b/docs/manpages/virsh.rst
@@ -5532,15 +5532,16 @@ List all of the devices available on the node that are known by
libvirt.
separated by comma, e.g. --cap pci,scsi. Valid capability types include
'system', 'pci', 'usb_device', 'usb', 'net',
'scsi_host', 'scsi_target',
'scsi', 'storage', 'fc_host', 'vports',
'scsi_generic', 'drm', 'mdev',
-'mdev_types', 'ccw', 'ccwgroup', 'css',
'ap_card', 'ap_queue', 'ap_matrix'.
-By default, only active devices are listed. *--inactive* is used to list only
-inactive devices, and *--all* is used to list both active and inactive devices.
-*--persistent* is used to list only persistent devices, and *--transient* is
-used to list only transient devices. Not providing *--persistent* or
-*--transient* will list all devices unless filtered otherwise. *--transient*
-is mutually exclusive with *--persistent* and *--inactive*.
-If *--tree* is used, the output is formatted in a tree representing parents of
-each node. *--tree* is mutually exclusive with all other options but *--all*.
+'mdev_types', 'ccw', 'ccwgroup', 'ccwgroup_member',
'css', 'ap_card',
+'ap_queue', 'ap_matrix'. By default, only active devices are listed.
+*--inactive* is used to list only inactive devices, and *--all* is used to
+list both active and inactive devices. *--persistent* is used to list only
+persistent devices, and *--transient* is used to list only transient devices.
+Not providing *--persistent* or *--transient* will list all devices unless
+filtered otherwise. *--transient* is mutually exclusive with *--persistent*
+and *--inactive*. If *--tree* is used, the output is formatted in a tree
+representing parents of each node. *--tree* is mutually exclusive with all
+other options but *--all*.
nodedev-reattach
diff --git a/include/libvirt/libvirt-nodedev.h b/include/libvirt/libvirt-nodedev.h
index 79bee4fb04..9fccbeefeb 100644
--- a/include/libvirt/libvirt-nodedev.h
+++ b/include/libvirt/libvirt-nodedev.h
@@ -91,6 +91,7 @@ typedef enum {
VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_MATRIX = 1 << 20, /* s390 AP Matrix
(Since: 7.0.0) */
VIR_CONNECT_LIST_NODE_DEVICES_CAP_VPD = 1 << 21, /* Device with VPD
(Since: 7.9.0) */
VIR_CONNECT_LIST_NODE_DEVICES_CAP_CCWGROUP_DEV = 1 << 22, /* s390 CCWGROUP
device (Since: 11.1.0) */
+ VIR_CONNECT_LIST_NODE_DEVICES_CAP_CCWGROUP_MEMBER = 1 << 23, /* s390 CCW device
member of CCWGROUP device (Since: 11.1.0) */
VIR_CONNECT_LIST_NODE_DEVICES_PERSISTENT = 1 << 28, /* Persisted devices
(Since: 10.1.0) */
VIR_CONNECT_LIST_NODE_DEVICES_TRANSIENT = 1 << 29, /* Transient devices
(Since: 10.1.0) */
diff --git a/src/conf/node_device_conf.c b/src/conf/node_device_conf.c
index 1649df09a1..3e88f5da87 100644
--- a/src/conf/node_device_conf.c
+++ b/src/conf/node_device_conf.c
@@ -72,6 +72,7 @@ VIR_ENUM_IMPL(virNodeDevCap,
"ap_matrix",
"vpd",
"ccwgroup",
+ "ccwgroup_member",
);
VIR_ENUM_IMPL(virNodeDevCCWGroupCap,
@@ -642,6 +643,23 @@ virCCWDeviceAddressFormat(virBuffer *buf,
}
+static void
+virNodeDeviceCapCCWGroupMemberDefFormat(virBuffer *buf,
+ const virNodeDevCapData *data)
+{
+ virNodeDevCapCCW ccw_dev = data->ccw_dev;
+
+ if (ccw_dev.group_dev) {
+ virBufferAddLit(buf, "<capability
type='ccwgroup_member'>\n");
+ virBufferAdjustIndent(buf, 2);
+ virBufferEscapeString(buf,
"<group_device>%s</group_device>\n",
+ ccw_dev.group_dev);
+ virBufferAdjustIndent(buf, -2);
+ virBufferAddLit(buf, "</capability>\n");
+ }
+}
+
+
static void
virNodeDeviceCapCSSDefFormat(virBuffer *buf,
const virNodeDevCapData *data)
@@ -812,6 +830,8 @@ virNodeDeviceDefFormat(const virNodeDeviceDef *def, unsigned int
flags)
case VIR_NODE_DEV_CAP_CCW_DEV:
virNodeDeviceCapCCWStateTypeFormat(&buf, data->ccw_dev.state);
virCCWDeviceAddressFormat(&buf, data->ccw_dev.dev_addr);
+ if (data->ccw_dev.flags & VIR_NODE_DEV_CAP_FLAG_CCW_CCWGROUP_MEMBER)
+ virNodeDeviceCapCCWGroupMemberDefFormat(&buf, data);
break;
case VIR_NODE_DEV_CAP_CSS_DEV:
virNodeDeviceCapCSSDefFormat(&buf, data);
@@ -843,6 +863,7 @@ virNodeDeviceDefFormat(const virNodeDeviceDef *def, unsigned int
flags)
case VIR_NODE_DEV_CAP_CCWGROUP_DEV:
virNodeDeviceCapCCWGroupDefFormat(&buf, data);
break;
+ case VIR_NODE_DEV_CAP_CCWGROUP_MEMBER:
case VIR_NODE_DEV_CAP_FC_HOST:
case VIR_NODE_DEV_CAP_VPORTS:
case VIR_NODE_DEV_CAP_VPD:
@@ -1252,6 +1273,33 @@ virNodeDevCCWDeviceAddressParseXML(xmlXPathContextPtr ctxt,
}
+static int
+virNodeDevCCWCapabilityParseXML(xmlXPathContextPtr ctxt,
+ xmlNodePtr node,
+ const char *dev_name,
+ virNodeDevCapCCW *ccw_dev)
+{
+ g_autofree char *type = virXMLPropString(node, "type");
+ VIR_XPATH_NODE_AUTORESTORE(ctxt)
+
+ ctxt->node = node;
+
+ if (!type)
+ return 0; /* optional */
+
+ if (STREQ(type, "ccwgroup_member")) {
+ if (!(ccw_dev->group_dev =
virXPathString("string(./group_device[1])", ctxt))) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ _("missing group_device value for '%1$s'"),
dev_name);
+ return -1;
+ }
+ ccw_dev->flags |= VIR_NODE_DEV_CAP_FLAG_CCW_CCWGROUP_MEMBER;
+ }
+
+ return 0;
+}
+
+
static int
virNodeDevCapCCWParseXML(xmlXPathContextPtr ctxt,
virNodeDeviceDef *def,
@@ -1260,7 +1308,10 @@ virNodeDevCapCCWParseXML(xmlXPathContextPtr ctxt,
{
VIR_XPATH_NODE_AUTORESTORE(ctxt)
g_autofree virCCWDeviceAddress *ccw_addr = NULL;
+ g_autofree xmlNodePtr *nodes = NULL;
g_autofree char *state = NULL;
+ int n = 0;
+ size_t i = 0;
int val;
ctxt->node = node;
@@ -1284,6 +1335,15 @@ virNodeDevCapCCWParseXML(xmlXPathContextPtr ctxt,
ccw_dev->dev_addr = g_steal_pointer(&ccw_addr);
+ /* capabilities are optional */
+ if ((n = virXPathNodeSet("./capability", ctxt, &nodes)) < 0)
+ return -1;
+
+ for (i = 0; i < n; i++) {
+ if (virNodeDevCCWCapabilityParseXML(ctxt, nodes[i], def->name, ccw_dev) <
0)
+ return -1;
+ }
+
return 0;
}
@@ -2504,6 +2564,7 @@ virNodeDevCapsDefParseXML(xmlXPathContextPtr ctxt,
ret = virNodeDevCapCCWGroupParseXML(ctxt, def, node,
&caps->data.ccwgroup_dev);
break;
+ case VIR_NODE_DEV_CAP_CCWGROUP_MEMBER:
case VIR_NODE_DEV_CAP_MDEV_TYPES:
case VIR_NODE_DEV_CAP_FC_HOST:
case VIR_NODE_DEV_CAP_VPORTS:
@@ -2795,6 +2856,7 @@ virNodeDevCapsDefFree(virNodeDevCapsDef *caps)
break;
case VIR_NODE_DEV_CAP_CCW_DEV:
g_free(data->ccw_dev.dev_addr);
+ g_free(data->ccw_dev.group_dev);
break;
case VIR_NODE_DEV_CAP_CCWGROUP_DEV:
g_free(data->ccwgroup_dev.address);
@@ -2809,6 +2871,7 @@ virNodeDevCapsDefFree(virNodeDevCapsDef *caps)
break;
}
break;
+ case VIR_NODE_DEV_CAP_CCWGROUP_MEMBER:
case VIR_NODE_DEV_CAP_DRM:
case VIR_NODE_DEV_CAP_FC_HOST:
case VIR_NODE_DEV_CAP_VPORTS:
@@ -2868,6 +2931,12 @@ virNodeDeviceUpdateCaps(virNodeDeviceDef *def)
&cap->data.mdev_parent) <
0)
return -1;
break;
+ case VIR_NODE_DEV_CAP_CCW_DEV:
+ case VIR_NODE_DEV_CAP_CCWGROUP_MEMBER:
+ if (virNodeDeviceGetCCWDynamicCaps(def->sysfs_path,
+ &cap->data.ccw_dev) < 0)
+ return -1;
+ break;
case VIR_NODE_DEV_CAP_CCWGROUP_DEV:
if (virNodeDeviceGetCCWGroupDynamicCaps(def->sysfs_path,
&cap->data.ccwgroup_dev) <
0)
@@ -2887,7 +2956,6 @@ virNodeDeviceUpdateCaps(virNodeDeviceDef *def)
case VIR_NODE_DEV_CAP_VPORTS:
case VIR_NODE_DEV_CAP_SCSI_GENERIC:
case VIR_NODE_DEV_CAP_MDEV:
- case VIR_NODE_DEV_CAP_CCW_DEV:
case VIR_NODE_DEV_CAP_VDPA:
case VIR_NODE_DEV_CAP_AP_CARD:
case VIR_NODE_DEV_CAP_AP_QUEUE:
@@ -2986,6 +3054,15 @@ virNodeDeviceCapsListExport(virNodeDeviceDef *def,
ncaps++;
}
}
+
+ if (caps->data.type == VIR_NODE_DEV_CAP_CCW_DEV) {
+ flags = caps->data.ccw_dev.flags;
+
+ if (flags & VIR_NODE_DEV_CAP_FLAG_CCW_CCWGROUP_MEMBER) {
+ MAYBE_ADD_CAP(VIR_NODE_DEV_CAP_CCWGROUP_MEMBER);
+ ncaps++;
+ }
+ }
}
#undef MAYBE_ADD_CAP
@@ -3335,6 +3412,25 @@ virNodeDeviceGetCSSDynamicCaps(const char *sysfsPath,
return 0;
}
+/* virNodeDeviceGetCCWDynamicCaps() get info that is stored in sysfs
+ * about devices related to this device, i.e. things that can change
+ * without this device itself changing. These must be refreshed
+ * anytime full XML of the device is requested, because they can
+ * change with no corresponding notification from the kernel/udev.
+ */
+int
+virNodeDeviceGetCCWDynamicCaps(const char *sysfsPath,
+ virNodeDevCapCCW *ccw_dev)
+{
+ g_free(ccw_dev->group_dev);
+ ccw_dev->flags &= ~VIR_NODE_DEV_CAP_FLAG_CCW_CCWGROUP_MEMBER;
+
+ if ((ccw_dev->group_dev = virCCWDeviceGetGroupDev(sysfsPath)))
+ ccw_dev->flags |= VIR_NODE_DEV_CAP_FLAG_CCW_CCWGROUP_MEMBER;
+
+ return 0;
+}
+
/* virNodeDeviceGetAPMatrixDynamicCaps() get info that is stored in sysfs
* about devices related to this device, i.e. things that can change
* without this device itself changing. These must be refreshed
@@ -3428,6 +3524,13 @@ virNodeDeviceGetCSSDynamicCaps(const char *sysfsPath
G_GNUC_UNUSED,
return -1;
}
+int
+virNodeDeviceGetCCWDynamicCaps(const char *sysfsPath G_GNUC_UNUSED,
+ virNodeDevCapCCW *ccw_dev G_GNUC_UNUSED)
+{
+ return -1;
+}
+
int
virNodeDeviceGetAPMatrixDynamicCaps(const char *sysfsPath G_GNUC_UNUSED,
virNodeDevCapAPMatrix *ap_matrix G_GNUC_UNUSED)
diff --git a/src/conf/node_device_conf.h b/src/conf/node_device_conf.h
index d94670e074..a6cef57b95 100644
--- a/src/conf/node_device_conf.h
+++ b/src/conf/node_device_conf.h
@@ -72,6 +72,7 @@ typedef enum {
VIR_NODE_DEV_CAP_AP_MATRIX, /* s390 AP Matrix device */
VIR_NODE_DEV_CAP_VPD, /* Device provides VPD */
VIR_NODE_DEV_CAP_CCWGROUP_DEV, /* s390 CCWGROUP device */
+ VIR_NODE_DEV_CAP_CCWGROUP_MEMBER, /* s390 CCW device is member of CCWGROUP */
VIR_NODE_DEV_CAP_LAST
} virNodeDevCapType;
@@ -118,6 +119,7 @@ typedef enum {
typedef enum {
VIR_NODE_DEV_CAP_FLAG_CSS_MDEV = (1 << 0),
+ VIR_NODE_DEV_CAP_FLAG_CCW_CCWGROUP_MEMBER = (2 << 0),
} virNodeDevCCWCapFlags;
typedef enum {
@@ -295,6 +297,7 @@ struct _virNodeDevCapCCW {
size_t nmdev_types;
virCCWDeviceAddress *channel_dev_addr;
virNodeDevCCWStateType state;
+ char *group_dev;
};
typedef struct _virNodeDevCapVDPA virNodeDevCapVDPA;
@@ -457,7 +460,8 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(virNodeDevCapsDef,
virNodeDevCapsDefFree);
VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_QUEUE | \
VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_MATRIX | \
VIR_CONNECT_LIST_NODE_DEVICES_CAP_VPD | \
- VIR_CONNECT_LIST_NODE_DEVICES_CAP_CCWGROUP_DEV)
+ VIR_CONNECT_LIST_NODE_DEVICES_CAP_CCWGROUP_DEV | \
+ VIR_CONNECT_LIST_NODE_DEVICES_CAP_CCWGROUP_MEMBER)
#define VIR_CONNECT_LIST_NODE_DEVICES_FILTERS_ACTIVE \
VIR_CONNECT_LIST_NODE_DEVICES_ACTIVE | \
@@ -495,6 +499,10 @@ int
virNodeDeviceGetMdevParentDynamicCaps(const char *sysfsPath,
virNodeDevCapMdevParent *mdev_parent);
+int
+virNodeDeviceGetCCWDynamicCaps(const char *sysfsPath,
+ virNodeDevCapCCW *ccw_dev);
+
int
virNodeDeviceGetCCWGroupDynamicCaps(const char *sysfsPath,
virNodeDevCapCCWGroup *ccwgroup);
diff --git a/src/conf/schemas/nodedev.rng b/src/conf/schemas/nodedev.rng
index ebcda30f1f..f52c6ab752 100644
--- a/src/conf/schemas/nodedev.rng
+++ b/src/conf/schemas/nodedev.rng
@@ -712,6 +712,17 @@
</element>
</define>
+ <define name="capccwgroupmember">
+ <optional>
+ <element name="capability">
+ <attribute name="type">
+ <value>ccwgroup_member</value>
+ </attribute>
+ <element name="group_device"><text/></element>
+ </element>
+ </optional>
+ </define>
+
<define name="capccwdev">
<attribute name="type">
<value>ccw</value>
@@ -725,6 +736,7 @@
</element>
</optional>
<ref name="capccwaddress"/>
+ <ref name="capccwgroupmember"/>
</define>
<define name="capcssdev">
diff --git a/src/conf/virnodedeviceobj.c b/src/conf/virnodedeviceobj.c
index 23984995c8..c5ddf0b4fb 100644
--- a/src/conf/virnodedeviceobj.c
+++ b/src/conf/virnodedeviceobj.c
@@ -723,6 +723,12 @@ virNodeDeviceObjHasCap(const virNodeDeviceObj *obj,
return true;
break;
+ case VIR_NODE_DEV_CAP_CCW_DEV:
+ if (type == VIR_NODE_DEV_CAP_CCWGROUP_MEMBER &&
+ (cap->data.ccw_dev.flags &
VIR_NODE_DEV_CAP_FLAG_CCW_CCWGROUP_MEMBER))
+ return true;
+ break;
+
case VIR_NODE_DEV_CAP_SYSTEM:
case VIR_NODE_DEV_CAP_USB_DEV:
case VIR_NODE_DEV_CAP_USB_INTERFACE:
@@ -736,12 +742,12 @@ virNodeDeviceObjHasCap(const virNodeDeviceObj *obj,
case VIR_NODE_DEV_CAP_DRM:
case VIR_NODE_DEV_CAP_MDEV_TYPES:
case VIR_NODE_DEV_CAP_MDEV:
- case VIR_NODE_DEV_CAP_CCW_DEV:
case VIR_NODE_DEV_CAP_VDPA:
case VIR_NODE_DEV_CAP_AP_CARD:
case VIR_NODE_DEV_CAP_AP_QUEUE:
case VIR_NODE_DEV_CAP_VPD:
case VIR_NODE_DEV_CAP_CCWGROUP_DEV:
+ case VIR_NODE_DEV_CAP_CCWGROUP_MEMBER:
case VIR_NODE_DEV_CAP_LAST:
break;
}
@@ -901,7 +907,8 @@ virNodeDeviceObjMatch(virNodeDeviceObj *obj,
MATCH_CAP(AP_QUEUE) ||
MATCH_CAP(AP_MATRIX) ||
MATCH_CAP(VPD) ||
- MATCH_CAP(CCWGROUP_DEV)))
+ MATCH_CAP(CCWGROUP_DEV) ||
+ MATCH_CAP(CCWGROUP_MEMBER)))
return false;
}
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 07e1e673f0..8abda5fa7e 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -899,6 +899,7 @@ virNodeDeviceDefFree;
virNodeDeviceDefParse;
virNodeDeviceDefParseXML;
virNodeDeviceGetAPMatrixDynamicCaps;
+virNodeDeviceGetCCWDynamicCaps;
virNodeDeviceGetCCWGroupDynamicCaps;
virNodeDeviceGetCSSDynamicCaps;
virNodeDeviceGetMdevParentDynamicCaps;
diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c
index d716561361..123b16a292 100644
--- a/src/node_device/node_device_driver.c
+++ b/src/node_device/node_device_driver.c
@@ -717,6 +717,7 @@ nodeDeviceObjFormatAddress(virNodeDeviceObj *obj)
case VIR_NODE_DEV_CAP_AP_CARD:
case VIR_NODE_DEV_CAP_AP_QUEUE:
case VIR_NODE_DEV_CAP_VPD:
+ case VIR_NODE_DEV_CAP_CCWGROUP_MEMBER:
case VIR_NODE_DEV_CAP_LAST:
break;
}
@@ -2194,6 +2195,7 @@ int nodeDeviceDefValidate(virNodeDeviceDef *def,
case VIR_NODE_DEV_CAP_AP_MATRIX:
case VIR_NODE_DEV_CAP_VPD:
case VIR_NODE_DEV_CAP_CCWGROUP_DEV:
+ case VIR_NODE_DEV_CAP_CCWGROUP_MEMBER:
case VIR_NODE_DEV_CAP_LAST:
break;
}
diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c
index a78f47b65a..ba5727ed8f 100644
--- a/src/node_device/node_device_udev.c
+++ b/src/node_device/node_device_udev.c
@@ -1239,6 +1239,9 @@ udevProcessCCW(struct udev_device *device,
udevGenerateDeviceName(device, def, NULL);
+ if (virNodeDeviceGetCCWDynamicCaps(def->sysfs_path,
&def->caps->data.ccw_dev) < 0)
+ return -1;
+
return 0;
}
@@ -1575,6 +1578,7 @@ udevGetDeviceDetails(virNodeDeviceDriverState *driver_state,
return udevProcessMdevParent(device, def);
case VIR_NODE_DEV_CAP_CCWGROUP_DEV:
return udevProcessCCWGroup(device, def);
+ case VIR_NODE_DEV_CAP_CCWGROUP_MEMBER:
case VIR_NODE_DEV_CAP_VPD:
case VIR_NODE_DEV_CAP_SYSTEM:
case VIR_NODE_DEV_CAP_FC_HOST:
diff --git a/src/util/virccw.c b/src/util/virccw.c
index 0873c61889..762ef671a2 100644
--- a/src/util/virccw.c
+++ b/src/util/virccw.c
@@ -203,3 +203,26 @@ virCCWGroupTypeQethFree(virCCWGroupTypeQeth *qeth)
VIR_FREE(qeth->card_type);
VIR_FREE(qeth->chpid);
}
+
+char *
+virCCWDeviceGetGroupDev(const char *sysfs_path)
+{
+ g_autofree char *ccwgroup_path = NULL;
+ g_autofree char *group_dev_path = NULL;
+
+ group_dev_path = g_build_filename(sysfs_path, "group_device", NULL);
+
+ if (!virFileExists(group_dev_path))
+ return NULL;
+
+ if (virFileIsLink(group_dev_path) != 1)
+ return NULL;
+
+ if (virFileResolveLink(group_dev_path, &ccwgroup_path) < 0)
+ return NULL;
+
+ if (!virFileExists(ccwgroup_path))
+ return NULL;
+
+ return virCCWGroupDeviceDevNodeName("ccwgroup", ccwgroup_path);
+}
diff --git a/src/util/virccw.h b/src/util/virccw.h
index a8c9fa83ef..67faf878a8 100644
--- a/src/util/virccw.h
+++ b/src/util/virccw.h
@@ -71,4 +71,7 @@ int virCCWGroupDeviceGetMembers(const char *sysfs_path,
void virCCWGroupTypeQethFree(virCCWGroupTypeQeth *qeth);
+char* virCCWDeviceGetGroupDev(const char *sysfs_path)
+ ATTRIBUTE_NONNULL(1);
+
G_DEFINE_AUTOPTR_CLEANUP_FUNC(virCCWGroupMemberType, virCCWGroupMemberTypeFree);
diff --git a/tests/nodedevschemadata/ccw_0_0_ff02_ccwgroup.xml
b/tests/nodedevschemadata/ccw_0_0_ff02_ccwgroup.xml
new file mode 100644
index 0000000000..11767facd3
--- /dev/null
+++ b/tests/nodedevschemadata/ccw_0_0_ff02_ccwgroup.xml
@@ -0,0 +1,13 @@
+<device>
+ <name>ccw_0_0_ff02</name>
+ <path>/sys/devices/css0/0.0.0070/0.0.ff02</path>
+ <parent>css_0_0_0070</parent>
+ <capability type='ccw'>
+ <cssid>0x0</cssid>
+ <ssid>0x0</ssid>
+ <devno>0xff02</devno>
+ <capability type='ccwgroup_member'>
+ <group_device>ccwgroup_0_0_ff00</group_device>
+ </capability>
+ </capability>
+</device>
diff --git a/tests/nodedevxml2xmlout/ccw_0_0_ff02_ccwgroup.xml
b/tests/nodedevxml2xmlout/ccw_0_0_ff02_ccwgroup.xml
new file mode 120000
index 0000000000..4d2b000b11
--- /dev/null
+++ b/tests/nodedevxml2xmlout/ccw_0_0_ff02_ccwgroup.xml
@@ -0,0 +1 @@
+../nodedevschemadata/ccw_0_0_ff02_ccwgroup.xml
\ No newline at end of file
diff --git a/tests/nodedevxml2xmltest.c b/tests/nodedevxml2xmltest.c
index d4d87b3bdc..265b37b218 100644
--- a/tests/nodedevxml2xmltest.c
+++ b/tests/nodedevxml2xmltest.c
@@ -146,6 +146,7 @@ mymain(void)
DO_TEST("mdev_3627463d_b7f0_4fea_b468_f1da537d301b");
DO_TEST_INACTIVE("mdev_3627463d_b7f0_4fea_b468_f1da537d301b");
DO_TEST("ccw_0_0_ffff");
+ DO_TEST("ccw_0_0_ff02_ccwgroup");
DO_TEST("ccwgroup_0_0_bd00");
DO_TEST("css_0_0_ffff");
DO_TEST("css_0_0_ffff_channel_dev_addr");
diff --git a/tools/virsh-nodedev.c b/tools/virsh-nodedev.c
index 3aae7285a9..e759b9f629 100644
--- a/tools/virsh-nodedev.c
+++ b/tools/virsh-nodedev.c
@@ -504,6 +504,9 @@ cmdNodeListDevices(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED)
case VIR_NODE_DEV_CAP_CCWGROUP_DEV:
flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_CCWGROUP_DEV;
break;
+ case VIR_NODE_DEV_CAP_CCWGROUP_MEMBER:
+ flags |= VIR_CONNECT_LIST_NODE_DEVICES_CAP_CCWGROUP_MEMBER;
+ break;
case VIR_NODE_DEV_CAP_LAST:
break;
}
--
2.47.0