mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
chzcrypt: Support for SE bind, unbind and associate
This patch adds support for Secure Execution with AP pass-through support for chzcrypt. chzcrypt details: * new command: --se-associate <secret-id> <queue device> * new command: --se-bind <queue device> * new command: --se-unbind <queue device> Signed-off-by: Harald Freudenberger <freude@linux.ibm.com> Reviewed-by: Holger Dengler <dengler@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
committed by
Jan Höppner
parent
f821f31a51
commit
e35e73d2a3
@@ -1,10 +1,16 @@
|
||||
.\" chzcrypt.8
|
||||
.\"
|
||||
.\" Copyright 2020 IBM Corp.
|
||||
.\" Copyright 2020, 2023 IBM Corp.
|
||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||
.\"
|
||||
.TH CHZCRYPT 8 "OCT 2020" "s390-tools"
|
||||
.\" use
|
||||
.\" groff -man -Tutf8 chzcrypt.8
|
||||
.\" or
|
||||
.\" nroff -man chzcrypt.8
|
||||
.\" to process this source
|
||||
.\"
|
||||
.TH CHZCRYPT 8 "MAY 2023" "s390-tools"
|
||||
.SH NAME
|
||||
chzcrypt \- modify zcrypt configuration
|
||||
.SH SYNOPSIS
|
||||
@@ -46,8 +52,8 @@ chzcrypt \- modify zcrypt configuration
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B chzcrypt
|
||||
command is used to configure cryptographic devices managed by zcrypt and
|
||||
modify zcrypt's AP bus attributes.
|
||||
command is used to configure cryptographic devices managed by zcrypt
|
||||
and modify zcrypt's AP bus attributes.
|
||||
|
||||
Attributes may vary depending on the kernel
|
||||
version.
|
||||
@@ -70,19 +76,6 @@ Set the given cryptographic card device(s) config on ('configured').
|
||||
.B --config-off
|
||||
Set the given cryptographic card device(s) config off ('deconfigured').
|
||||
.TP 8
|
||||
.B <device id>
|
||||
Specifies a cryptographic device which will be set either online or
|
||||
offline or configured on or off. For online and offline the device can
|
||||
either be a card device or a queue device. A queue device can only get
|
||||
switched online when the providing card is online.
|
||||
.br
|
||||
For config on/off the device needs to be a card device. A card or
|
||||
queue device cannot get switched online if the card is in deconfigured
|
||||
state.
|
||||
.br
|
||||
Please note that the card device and queue device representation are both
|
||||
in hexadecimal notation.
|
||||
.TP 8
|
||||
.B -p, --poll-thread-enable
|
||||
Enable zcrypt's poll thread.
|
||||
.TP 8
|
||||
@@ -94,15 +87,28 @@ Set configuration timer for re-scanning the AP bus to
|
||||
.I <timeout>
|
||||
seconds.
|
||||
.TP 8
|
||||
.B --se-associate <association-index> <queue-device>
|
||||
Associate the given queue device with the given association
|
||||
index. This command is only valid within an Secure Execution guest
|
||||
with AP pass-through support enabled.
|
||||
.TP 8
|
||||
.B --se-bind <queue-device>
|
||||
Bind the given queue device. This command is only valid within an
|
||||
Secure Execution guest with AP pass-through support enabled.
|
||||
.TP 8
|
||||
.B --se-unbind <queue-device>
|
||||
Unbind the given queue device. This command is only valid within an
|
||||
Secure Execution guest with AP pass-through support enabled.
|
||||
.TP 8
|
||||
.BI "-t, --poll-timeout" " <poll_timeout>"
|
||||
Set poll timer to run poll tasklet all
|
||||
.I <poll_timeout>
|
||||
nanoseconds.
|
||||
.TP 8
|
||||
.BI "-q, --default-domain" " <domain>"
|
||||
Set the new default domain of the AP bus to <domain>.
|
||||
The number of available domains can be retrieved with the lszcrypt
|
||||
command ('-d' option).
|
||||
Set the new default domain of the AP bus to <domain>. The number of
|
||||
available domains can be retrieved with the lszcrypt command ('-d'
|
||||
option).
|
||||
.TP 8
|
||||
.B -V, --verbose
|
||||
Print verbose messages.
|
||||
@@ -112,6 +118,22 @@ Print help text and exit.
|
||||
.TP 8
|
||||
.B -v, --version
|
||||
Print version information and exit.
|
||||
.TP 8
|
||||
.B <device id>
|
||||
Specifies a cryptographic device which will be set either online or
|
||||
offline or configured on or off. For online and offline the device can
|
||||
either be a card device or a queue device. A queue device can only get
|
||||
switched online when the providing card is online.
|
||||
.br
|
||||
For config on/off the device needs to be a card device. A card or
|
||||
queue device cannot get switched online if the card is in deconfigured
|
||||
state.
|
||||
.br
|
||||
Please note that the card device and queue device representation are
|
||||
both in hexadecimal notation.
|
||||
.TP 8
|
||||
.B <queue-device>
|
||||
An APQN queue device given as xy.abcd as it is listed by lszcrypt -V.
|
||||
.SH EXAMPLES
|
||||
.TP
|
||||
.B chzcrypt -e 0 1 12
|
||||
@@ -131,8 +153,8 @@ Set all available crypto cards to config on, be verbose.
|
||||
Switch the two crypto cards 1 and 3 to deconfigured, be verbose.
|
||||
.TP
|
||||
.B chzcrypt -c 60 -n
|
||||
Will set configuration timer for re-scanning the AP bus to 60 seconds and
|
||||
disable zcrypt's poll thread.
|
||||
Will set configuration timer for re-scanning the AP bus to 60 seconds
|
||||
and disable zcrypt's poll thread.
|
||||
.TP
|
||||
.B chzcrypt -q 67
|
||||
Will set the default domain to 67.
|
||||
@@ -144,5 +166,11 @@ chzcrypt exits with an appropriate message. Even more config on/off
|
||||
may require support from a hypervisor like KVM or zVM and may fail if
|
||||
the Linux kernel is unable to perform the SCLP command. Check syslog
|
||||
on failure.
|
||||
.TP
|
||||
Bind, associate and unbind command on an queue device are only
|
||||
available and valid within an Secure Execution environment with AP
|
||||
pass-through enabled and a Linux kernel providing the low level sysfs
|
||||
API. If these conditions are not fulfilled, the command will fail with
|
||||
an appropriate error messages.
|
||||
.SH SEE ALSO
|
||||
\fBlszcrypt\fR(8)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* chzcrypt - Tool to modify zcrypt configuration
|
||||
*
|
||||
* Copyright IBM Corp. 2008, 2020
|
||||
* Copyright IBM Corp. 2008, 2023
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
@@ -28,6 +28,12 @@
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
/* max seconds the se-association command will wait for completion */
|
||||
#define MAX_ASSOC_POLL_TIME_IN_S 30
|
||||
|
||||
/* max seconds the se-unbind command will wait for unbind complete */
|
||||
#define MAX_UNBIND_POLL_TIME_IN_S 30
|
||||
|
||||
/*
|
||||
* Private data
|
||||
*/
|
||||
@@ -45,7 +51,7 @@ static const struct util_prg prg = {
|
||||
{
|
||||
.owner = "IBM Corp.",
|
||||
.pub_first = 2008,
|
||||
.pub_last = 2020,
|
||||
.pub_last = 2023,
|
||||
},
|
||||
UTIL_PRG_COPYRIGHT_END
|
||||
}
|
||||
@@ -57,6 +63,9 @@ static const struct util_prg prg = {
|
||||
|
||||
#define OPT_CONFIG_ON 0x80
|
||||
#define OPT_CONFIG_OFF 0x81
|
||||
#define OPT_SE_ASSOC 0x82
|
||||
#define OPT_SE_BIND 0x83
|
||||
#define OPT_SE_UNBIND 0x84
|
||||
|
||||
static struct util_opt opt_vec[] = {
|
||||
{
|
||||
@@ -116,6 +125,22 @@ static struct util_opt opt_vec[] = {
|
||||
.option = { "verbose", no_argument, NULL, 'V'},
|
||||
.desc = "Print verbose messages",
|
||||
},
|
||||
{
|
||||
.option = { "se-associate", required_argument, NULL, OPT_SE_ASSOC},
|
||||
.argument = "assoc_idx",
|
||||
.flags = UTIL_OPT_FLAG_NOSHORT,
|
||||
.desc = "SE guest with AP support only: Associate the given queue device",
|
||||
},
|
||||
{
|
||||
.option = { "se-bind", no_argument, NULL, OPT_SE_BIND},
|
||||
.flags = UTIL_OPT_FLAG_NOSHORT,
|
||||
.desc = "SE guest with AP support only: Bind the given queue device",
|
||||
},
|
||||
{
|
||||
.option = { "se-unbind", no_argument, NULL, OPT_SE_UNBIND},
|
||||
.flags = UTIL_OPT_FLAG_NOSHORT,
|
||||
.desc = "SE guest with AP support only: Unbind the given queue device",
|
||||
},
|
||||
UTIL_OPT_HELP,
|
||||
UTIL_OPT_VERSION,
|
||||
UTIL_OPT_END
|
||||
@@ -336,6 +361,186 @@ next:
|
||||
}
|
||||
}
|
||||
|
||||
static void se_assoc(const char *assoc_idx, const char *dev)
|
||||
{
|
||||
int i, idx, rc, ap, dom, loop;
|
||||
char *dev_path, *attr;
|
||||
char buf[256];
|
||||
|
||||
if (!ap_bus_has_SB_support())
|
||||
errx(EXIT_FAILURE, "Error - AP bus: SE bind support is not available.");
|
||||
|
||||
if (sscanf(dev, "%02x.%04x", &ap, &dom) != 2)
|
||||
errx(EXIT_FAILURE, "Error - Can't parse queue device '%s' as xy.abcd.",
|
||||
dev);
|
||||
dev_path = util_path_sysfs("bus/ap/devices/card%02x/%02x.%04x",
|
||||
ap, ap, dom);
|
||||
if (!util_path_is_dir(dev_path))
|
||||
errx(EXIT_FAILURE, "Error - Queue device %s does not exist.",
|
||||
dev);
|
||||
|
||||
if (sscanf(assoc_idx, "%i", &idx) != 1)
|
||||
errx(EXIT_FAILURE, "Error - Can't parse association index '%s' as number.",
|
||||
assoc_idx);
|
||||
if (idx < 0 || idx > 0xFFFF)
|
||||
errx(EXIT_FAILURE, "Error - Association index needs to be in range [0...%d].",
|
||||
0xffff);
|
||||
|
||||
attr = util_path_sysfs("bus/ap/devices/card%02x/%02x.%04x/se_associate",
|
||||
ap, ap, dom);
|
||||
if (!util_path_is_writable(attr))
|
||||
errx(EXIT_FAILURE, "Error - Can't write to %s (errno '%s').",
|
||||
attr, strerror(errno));
|
||||
|
||||
/* read se_associate attribute and check for 'unassociated' */
|
||||
rc = util_file_read_line(buf, sizeof(buf), attr);
|
||||
if (rc)
|
||||
errx(EXIT_FAILURE, "Error - Failure reading from %s (errno '%s').",
|
||||
attr, strerror(errno));
|
||||
if (strcmp(buf, "unassociated"))
|
||||
errx(EXIT_FAILURE,
|
||||
"Error - Queue device %s is NOT in 'unassociated' state (state '%s' found).",
|
||||
dev, buf);
|
||||
|
||||
/* write assocition index to the se_associate attribute */
|
||||
rc = util_file_write_l(idx, 10, attr);
|
||||
if (rc)
|
||||
errx(EXIT_FAILURE, "Error - Failure writing to %s (errno '%s').",
|
||||
attr, strerror(errno));
|
||||
|
||||
/* loop up to MAX_ASSOC_POLL_TIME_IN_S seconds for completion */
|
||||
for (loop = 0; loop < 2 * MAX_ASSOC_POLL_TIME_IN_S; usleep(500000), loop++) {
|
||||
rc = util_file_read_line(buf, sizeof(buf), attr);
|
||||
if (rc)
|
||||
errx(EXIT_FAILURE, "Error - Failure reading from %s (errno '%s').",
|
||||
attr, strerror(errno));
|
||||
if (!strncmp(buf, "associated", strlen("associated")))
|
||||
break;
|
||||
if (!strcmp(buf, "unassociated"))
|
||||
errx(EXIT_FAILURE,
|
||||
"Error - Failure associating queue device %s (state '%s' found).",
|
||||
dev, buf);
|
||||
}
|
||||
if (loop >= 2 * MAX_ASSOC_POLL_TIME_IN_S)
|
||||
errx(EXIT_FAILURE,
|
||||
"Error - Failure associating queue device %s (timeout after %d s).",
|
||||
dev, MAX_ASSOC_POLL_TIME_IN_S);
|
||||
|
||||
if (sscanf(buf, "associated %d", &i) != 1 || idx != i)
|
||||
errx(EXIT_FAILURE,
|
||||
"Error - Failure associating queue device %s (state '%s' found).",
|
||||
dev, buf);
|
||||
|
||||
verbose("Queue device %s successful associated with index %d.\n",
|
||||
dev, idx);
|
||||
|
||||
free(dev_path);
|
||||
free(attr);
|
||||
}
|
||||
|
||||
static void se_bind(const char *dev)
|
||||
{
|
||||
char *dev_path, *attr;
|
||||
int rc, ap, dom;
|
||||
char buf[256];
|
||||
|
||||
if (!ap_bus_has_SB_support())
|
||||
errx(EXIT_FAILURE, "Error - AP bus: SE bind support is not available.");
|
||||
|
||||
if (sscanf(dev, "%02x.%04x", &ap, &dom) != 2)
|
||||
errx(EXIT_FAILURE, "Error - Can't parse queue device '%s' as xy.abcd.",
|
||||
dev);
|
||||
dev_path = util_path_sysfs("bus/ap/devices/card%02x/%02x.%04x",
|
||||
ap, ap, dom);
|
||||
if (!util_path_is_dir(dev_path))
|
||||
errx(EXIT_FAILURE, "Error - Queue device %s does not exist.",
|
||||
dev);
|
||||
|
||||
attr = util_path_sysfs("bus/ap/devices/card%02x/%02x.%04x/se_bind",
|
||||
ap, ap, dom);
|
||||
if (!util_path_is_writable(attr))
|
||||
errx(EXIT_FAILURE, "Error - Can't write to %s (errno '%s').",
|
||||
attr, strerror(errno));
|
||||
|
||||
/* read se_bind attribute and check for 'unboud' */
|
||||
rc = util_file_read_line(buf, sizeof(buf), attr);
|
||||
if (rc)
|
||||
errx(EXIT_FAILURE, "Error - Failure reading from %s (errno '%s').",
|
||||
attr, strerror(errno));
|
||||
if (strcmp(buf, "unbound"))
|
||||
errx(EXIT_FAILURE,
|
||||
"Error - Queue device %s is NOT in 'unbound' state (state '%s' found).",
|
||||
dev, buf);
|
||||
|
||||
/* write se_bind attribute, check for 'bound' afterwards */
|
||||
rc = util_file_write_l(1, 10, attr);
|
||||
if (rc)
|
||||
errx(EXIT_FAILURE, "Error - Failure writing to %s (errno '%s').",
|
||||
attr, strerror(errno));
|
||||
rc = util_file_read_line(buf, sizeof(buf), attr);
|
||||
if (rc)
|
||||
errx(EXIT_FAILURE, "Error - Failure reading from %s (errno '%s').",
|
||||
attr, strerror(errno));
|
||||
if (strcmp(buf, "bound"))
|
||||
errx(EXIT_FAILURE, "Error - Failure binding queue device %s (state '%s' found).",
|
||||
dev, buf);
|
||||
|
||||
verbose("Queue device %s successful bound.\n", dev);
|
||||
|
||||
free(dev_path);
|
||||
free(attr);
|
||||
}
|
||||
|
||||
static void se_unbind(const char *dev)
|
||||
{
|
||||
int rc, ap, dom, loop;
|
||||
char *dev_path, *attr;
|
||||
char buf[256];
|
||||
|
||||
if (!ap_bus_has_SB_support())
|
||||
errx(EXIT_FAILURE, "Error - AP bus: SE bind support is not available.");
|
||||
|
||||
if (sscanf(dev, "%02x.%04x", &ap, &dom) != 2)
|
||||
errx(EXIT_FAILURE, "Error - Can't parse queue device '%s' as xy.abcd.",
|
||||
dev);
|
||||
dev_path = util_path_sysfs("bus/ap/devices/card%02x/%02x.%04x",
|
||||
ap, ap, dom);
|
||||
if (!util_path_is_dir(dev_path))
|
||||
errx(EXIT_FAILURE, "Error - Queue device %s does not exist.",
|
||||
dev);
|
||||
|
||||
attr = util_path_sysfs("bus/ap/devices/card%02x/%02x.%04x/se_bind",
|
||||
ap, ap, dom);
|
||||
if (!util_path_is_writable(attr))
|
||||
errx(EXIT_FAILURE, "Error - Can't write to %s (errno '%s').",
|
||||
attr, strerror(errno));
|
||||
|
||||
/* write se_bind attribute */
|
||||
rc = util_file_write_l(0, 10, attr);
|
||||
if (rc)
|
||||
errx(EXIT_FAILURE, "Error - Failure writing to %s (errno '%s').",
|
||||
attr, strerror(errno));
|
||||
|
||||
/* loop up to MAX_UNBIND_POLL_TIME_IN_S seconds for completion */
|
||||
for (loop = 0; loop < 2 * MAX_UNBIND_POLL_TIME_IN_S; usleep(500000), loop++) {
|
||||
rc = util_file_read_line(buf, sizeof(buf), attr);
|
||||
if (rc)
|
||||
errx(EXIT_FAILURE, "Error - Failure reading from %s (errno '%s').",
|
||||
attr, strerror(errno));
|
||||
if (!strcmp(buf, "unbound"))
|
||||
break;
|
||||
}
|
||||
if (loop >= 2 * MAX_UNBIND_POLL_TIME_IN_S)
|
||||
errx(EXIT_FAILURE,
|
||||
"Error - Failure unbinding queue device %s (timeout after %d s).",
|
||||
dev, MAX_UNBIND_POLL_TIME_IN_S);
|
||||
|
||||
verbose("Queue device %s successful unbound.\n", dev);
|
||||
|
||||
free(dev_path);
|
||||
free(attr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print invalid commandline error message and then exit with error code
|
||||
*/
|
||||
@@ -389,10 +594,10 @@ static void print_adapter_id_help(void)
|
||||
printf("DEVICE_IDS\n");
|
||||
printf(" List of cryptographic device ids separated by blanks which will be set\n");
|
||||
printf(" online/offline. Must be used in conjunction with the enable or disable option.\n");
|
||||
|
||||
printf(" DEVICE_ID could either be card device id ('<card-id>') or queue device id\n");
|
||||
printf(" '<card-id>.<domain-id>').\n");
|
||||
printf(" \n");
|
||||
printf(" '<card-id>.<domain-id>').\n\n");
|
||||
printf("QUEUE_DEVICE:\n");
|
||||
printf(" An APQN queue device given as xy.abcd as it is listed by lszcrypt -V.\n\n");
|
||||
printf("EXAMPLE:\n");
|
||||
printf(" Disable the cryptographic device with card id '02' (inclusive all queues).\n");
|
||||
printf(" #>chzcrypt -d 02\n");
|
||||
@@ -407,13 +612,14 @@ static void print_adapter_id_help(void)
|
||||
*/
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const char *default_domain = NULL, *config = NULL, *config_text = NULL;
|
||||
const char *online = NULL, *online_text = NULL, *poll_thread = NULL;
|
||||
const char *config_time = NULL, *poll_timeout = NULL;
|
||||
const char *default_domain = NULL, *config = NULL, *config_text = NULL;
|
||||
const char *queue_device = NULL, *assoc_idx = NULL;
|
||||
int c, i, j, action = 0;
|
||||
char *path, *dev_list;
|
||||
bool all = false, actionset = false;
|
||||
bool all = false;
|
||||
size_t len;
|
||||
int c, i, j;
|
||||
|
||||
for (i=0; i < argc; i++)
|
||||
for (j=2; j < (int) strlen(argv[i]); j++)
|
||||
@@ -428,12 +634,12 @@ int main(int argc, char *argv[])
|
||||
break;
|
||||
switch (c) {
|
||||
case 'e':
|
||||
actionset = true;
|
||||
action = c;
|
||||
online = "1";
|
||||
online_text = "online";
|
||||
break;
|
||||
case 'd':
|
||||
actionset = true;
|
||||
action = c;
|
||||
online = "0";
|
||||
online_text = "offline";
|
||||
break;
|
||||
@@ -441,23 +647,23 @@ int main(int argc, char *argv[])
|
||||
all = true;
|
||||
break;
|
||||
case 'p':
|
||||
actionset = true;
|
||||
action = c;
|
||||
poll_thread = "1";
|
||||
break;
|
||||
case 'n':
|
||||
actionset = true;
|
||||
action = c;
|
||||
poll_thread = "0";
|
||||
break;
|
||||
case 'c':
|
||||
actionset = true;
|
||||
action = c;
|
||||
config_time = optarg;
|
||||
break;
|
||||
case 't':
|
||||
actionset = true;
|
||||
action = c;
|
||||
poll_timeout = optarg;
|
||||
break;
|
||||
case 'q':
|
||||
actionset = true;
|
||||
action = c;
|
||||
default_domain = optarg;
|
||||
break;
|
||||
case 'V':
|
||||
@@ -472,21 +678,31 @@ int main(int argc, char *argv[])
|
||||
util_prg_print_version();
|
||||
return EXIT_SUCCESS;
|
||||
case OPT_CONFIG_ON:
|
||||
actionset = true;
|
||||
action = c;
|
||||
config = "1";
|
||||
config_text = "config on";
|
||||
break;
|
||||
case OPT_CONFIG_OFF:
|
||||
actionset = true;
|
||||
action = c;
|
||||
config = "0";
|
||||
config_text = "config off";
|
||||
break;
|
||||
case OPT_SE_ASSOC:
|
||||
action = c;
|
||||
assoc_idx = optarg;
|
||||
break;
|
||||
case OPT_SE_BIND:
|
||||
action = c;
|
||||
break;
|
||||
case OPT_SE_UNBIND:
|
||||
action = c;
|
||||
break;
|
||||
default:
|
||||
util_opt_print_parse_error(c, argv);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
if (!actionset)
|
||||
if (!action)
|
||||
invalid_cmdline_exit("Error - missing argument.\n");
|
||||
path = util_path_sysfs("bus/ap");
|
||||
if (!util_path_is_dir(path))
|
||||
@@ -508,6 +724,32 @@ int main(int argc, char *argv[])
|
||||
default_domain_set(default_domain);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
if (action == OPT_SE_ASSOC) {
|
||||
if (optind >= argc)
|
||||
errx(EXIT_FAILURE,
|
||||
"Error - The --se-associate needs a queue device given.");
|
||||
queue_device = argv[optind];
|
||||
se_assoc(assoc_idx, queue_device);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (action == OPT_SE_BIND) {
|
||||
if (optind >= argc)
|
||||
errx(EXIT_FAILURE,
|
||||
"Error - The --se-bind needs a queue device given.");
|
||||
queue_device = argv[optind];
|
||||
se_bind(queue_device);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (action == OPT_SE_UNBIND) {
|
||||
if (optind >= argc)
|
||||
errx(EXIT_FAILURE,
|
||||
"Error - The --se-unbind needs a queue device given.");
|
||||
queue_device = argv[optind];
|
||||
se_unbind(queue_device);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
if (all)
|
||||
dev_list_all(&dev_list, &len);
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user