aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/iovctl
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/iovctl')
-rw-r--r--usr.sbin/iovctl/Makefile13
-rw-r--r--usr.sbin/iovctl/Makefile.depend18
-rw-r--r--usr.sbin/iovctl/iovctl.8121
-rw-r--r--usr.sbin/iovctl/iovctl.c410
-rw-r--r--usr.sbin/iovctl/iovctl.conf.5172
-rw-r--r--usr.sbin/iovctl/iovctl.h35
-rw-r--r--usr.sbin/iovctl/parse.c438
-rw-r--r--usr.sbin/iovctl/validate.c271
8 files changed, 1478 insertions, 0 deletions
diff --git a/usr.sbin/iovctl/Makefile b/usr.sbin/iovctl/Makefile
new file mode 100644
index 000000000000..3298a6a21217
--- /dev/null
+++ b/usr.sbin/iovctl/Makefile
@@ -0,0 +1,13 @@
+PROG= iovctl
+SRCS= iovctl.c parse.c validate.c
+LIBADD= nv ucl m
+
+CFLAGS+=-I${SRCTOP}/contrib/libucl/include
+
+MAN= \
+ iovctl.8 \
+ iovctl.conf.5 \
+
+.include <bsd.own.mk>
+.include <bsd.prog.mk>
+
diff --git a/usr.sbin/iovctl/Makefile.depend b/usr.sbin/iovctl/Makefile.depend
new file mode 100644
index 000000000000..552b40d7838b
--- /dev/null
+++ b/usr.sbin/iovctl/Makefile.depend
@@ -0,0 +1,18 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libnv \
+ lib/libucl \
+ lib/msun \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/iovctl/iovctl.8 b/usr.sbin/iovctl/iovctl.8
new file mode 100644
index 000000000000..2574503e5ae7
--- /dev/null
+++ b/usr.sbin/iovctl/iovctl.8
@@ -0,0 +1,121 @@
+.\"
+.\" Copyright (c) 2014 Sandvine Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd May 31, 2020
+.Dt IOVCTL 8
+.Os
+.Sh NAME
+.Nm iovctl
+.Nd "PCI SR-IOV configuration utility"
+.Sh SYNOPSIS
+.Nm
+.Fl C
+.Fl f Ar config-file
+.Op Fl n
+.Nm
+.Fl D
+.Op Fl f Ar config-file | Fl d Ar device
+.Op Fl n
+.Nm
+.Fl S
+.Op Fl f Ar config-file | Fl d Ar device
+.Sh DESCRIPTION
+The
+.Nm
+utility creates or destroys PCI Single-Root I/O Virtualization
+.Pq SR-IOV
+Virtual Functions
+.Pq VFs .
+When invoked with the
+.Fl C
+flag,
+.Nm
+creates VFs as children of the Physical Function
+.Pq PF
+configured in the specified configuration file.
+When invoked with the
+.Fl D
+flag,
+.Nm
+destroys all VFs that are children of the specified device.
+Available PF devices can be seen in
+.Pa /dev/iov/ .
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl C
+Enable SR-IOV on the specified PF device and create VF children.
+This operation will fail if the PF already has VF children.
+This option must be used in conjunction with the
+.Fl f
+option.
+.It Fl d Ar device
+Specify the PF device to use for the given operation.
+.Ar device
+may either be the name of a PF device, or a full path name to a node in
+.Pa /dev/iov/ .
+This option may not be used with the
+.Fl C
+option.
+.It Fl D
+Delete all VF children of the specified PF device.
+This operation will fail if SR-IOV is not currently enabled on the specified
+device.
+.It Fl f Ar config-file
+Specify the pathname of the configuration file.
+For the
+.Fl C
+option, this file will be used to specify all configuration values.
+For the
+.Fl D
+and
+.Fl S
+options, this file will only be used to specify the name of the PF device.
+.Pp
+See
+.Xr iovctl.conf 5
+for a description of the config file format and documentation of the
+configuration parameters that apply to all PF drivers.
+See the PF driver manual page for configuration parameters specific to
+particular hardware.
+.It Fl n
+Perform a dry-run.
+Perform all validation of the specified action and print what would be done,
+but do not perform the actual creation or destruction of VFs.
+This option may not be used with the
+.Fl S
+flag.
+.It Fl S
+Read the configuration schema from the specified device and print its contents
+to stdout.
+This action may be used to discover the configuration parameters supported on
+a given PF device.
+.El
+.Sh SEE ALSO
+.Xr iovctl.conf 5 ,
+.Xr rc.conf 5
+.Sh AUTHORS
+This manual page was written by
+.An Ryan Stone Aq Mt rstone@FreeBSD.org .
diff --git a/usr.sbin/iovctl/iovctl.c b/usr.sbin/iovctl/iovctl.c
new file mode 100644
index 000000000000..28d2e0a93504
--- /dev/null
+++ b/usr.sbin/iovctl/iovctl.c
@@ -0,0 +1,410 @@
+/*-
+ * Copyright (c) 2013-2015 Sandvine Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/iov.h>
+#include <sys/dnv.h>
+#include <sys/nv.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "iovctl.h"
+
+static void config_action(const char *filename, int dryrun);
+static void delete_action(const char *device, int dryrun);
+static void print_schema(const char *device);
+
+/*
+ * Fetch the config schema from the kernel via ioctl. This function has to
+ * call the ioctl twice: the first returns the amount of memory that we need
+ * to allocate for the schema, and the second actually fetches the schema.
+ */
+static nvlist_t *
+get_schema(int fd)
+{
+ struct pci_iov_schema arg;
+ nvlist_t *schema;
+ int error;
+
+ /* Do the ioctl() once to fetch the size of the schema. */
+ arg.schema = NULL;
+ arg.len = 0;
+ arg.error = 0;
+ error = ioctl(fd, IOV_GET_SCHEMA, &arg);
+ if (error != 0)
+ err(1, "Could not fetch size of config schema");
+
+ arg.schema = malloc(arg.len);
+ if (arg.schema == NULL)
+ err(1, "Could not allocate %zu bytes for schema",
+ arg.len);
+
+ /* Now do the ioctl() for real to get the schema. */
+ error = ioctl(fd, IOV_GET_SCHEMA, &arg);
+ if (error != 0 || arg.error != 0) {
+ if (arg.error != 0)
+ errno = arg.error;
+ err(1, "Could not fetch config schema");
+ }
+
+ schema = nvlist_unpack(arg.schema, arg.len, NV_FLAG_IGNORE_CASE);
+ if (schema == NULL)
+ err(1, "Could not unpack schema");
+
+ free(arg.schema);
+ return (schema);
+}
+
+/*
+ * Call the ioctl that activates SR-IOV and creates the VFs.
+ */
+static void
+config_iov(int fd, const char *dev_name, const nvlist_t *config, int dryrun)
+{
+ struct pci_iov_arg arg;
+ int error;
+
+ arg.config = nvlist_pack(config, &arg.len);
+ if (arg.config == NULL)
+ err(1, "Could not pack configuration");
+
+ if (dryrun) {
+ printf("Would enable SR-IOV on device '%s'.\n", dev_name);
+ printf(
+ "The following configuration parameters would be used:\n");
+ nvlist_fdump(config, stdout);
+ printf(
+ "The configuration parameters consume %zu bytes when packed.\n",
+ arg.len);
+ } else {
+ error = ioctl(fd, IOV_CONFIG, &arg);
+ if (error != 0)
+ err(1, "Failed to configure SR-IOV");
+ }
+
+ free(arg.config);
+}
+
+static int
+open_device(const char *dev_name)
+{
+ char *dev;
+ int fd;
+ size_t copied, size;
+ long path_max;
+
+ path_max = pathconf("/dev", _PC_PATH_MAX);
+ if (path_max < 0)
+ err(1, "Could not get maximum path length");
+
+ size = path_max;
+ dev = malloc(size);
+ if (dev == NULL)
+ err(1, "Could not allocate memory for device path");
+
+ if (dev_name[0] == '/')
+ copied = strlcpy(dev, dev_name, size);
+ else
+ copied = snprintf(dev, size, "/dev/iov/%s", dev_name);
+
+ /* >= to account for null terminator. */
+ if (copied >= size)
+ errx(1, "Provided file name too long");
+
+ fd = open(dev, O_RDWR);
+ if (fd < 0)
+ err(1, "Could not open device '%s'", dev);
+
+ free(dev);
+ return (fd);
+}
+
+static void
+usage(void)
+{
+
+ warnx("Usage: iovctl -C -f <config file> [-n]");
+ warnx(" iovctl -D [-d <PF device> | -f <config file>] [-n]");
+ warnx(" iovctl -S [-d <PF device> | -f <config file>]");
+ exit(1);
+
+}
+
+enum main_action {
+ NONE,
+ CONFIG,
+ DELETE,
+ PRINT_SCHEMA,
+};
+
+int
+main(int argc, char **argv)
+{
+ char *device;
+ const char *filename;
+ int ch, dryrun;
+ enum main_action action;
+
+ device = NULL;
+ filename = NULL;
+ dryrun = 0;
+ action = NONE;
+
+ while ((ch = getopt(argc, argv, "Cd:Df:nS")) != -1) {
+ switch (ch) {
+ case 'C':
+ if (action != NONE) {
+ warnx(
+ "Only one of -C, -D or -S may be specified");
+ usage();
+ }
+ action = CONFIG;
+ break;
+ case 'd':
+ device = strdup(optarg);
+ break;
+ case 'D':
+ if (action != NONE) {
+ warnx(
+ "Only one of -C, -D or -S may be specified");
+ usage();
+ }
+ action = DELETE;
+ break;
+ case 'f':
+ filename = optarg;
+ break;
+ case 'n':
+ dryrun = 1;
+ break;
+ case 'S':
+ if (action != NONE) {
+ warnx(
+ "Only one of -C, -D or -S may be specified");
+ usage();
+ }
+ action = PRINT_SCHEMA;
+ break;
+ case '?':
+ warnx("Unrecognized argument '-%c'\n", optopt);
+ usage();
+ break;
+ }
+ }
+
+ if (device != NULL && filename != NULL) {
+ warnx("Only one of the -d and -f flags may be specified");
+ usage();
+ }
+
+ if (device == NULL && filename == NULL && action != CONFIG) {
+ warnx("Either the -d or -f flag must be specified");
+ usage();
+ }
+
+ switch (action) {
+ case CONFIG:
+ if (device != NULL) {
+ warnx("-d flag cannot be used with the -C flag");
+ usage();
+ }
+ if (filename == NULL) {
+ warnx("The -f flag must be specified");
+ usage();
+ }
+ config_action(filename, dryrun);
+ break;
+ case DELETE:
+ if (device == NULL)
+ device = find_device(filename);
+ delete_action(device, dryrun);
+ free(device);
+ break;
+ case PRINT_SCHEMA:
+ if (dryrun) {
+ warnx("-n flag cannot be used with the -S flag");
+ usage();
+ }
+ if (device == NULL)
+ device = find_device(filename);
+ print_schema(device);
+ free(device);
+ break;
+ default:
+ usage();
+ break;
+ }
+
+ exit(0);
+}
+
+static void
+config_action(const char *filename, int dryrun)
+{
+ char *dev;
+ nvlist_t *schema, *config;
+ int fd;
+
+ dev = find_device(filename);
+ fd = open(dev, O_RDWR);
+ if (fd < 0)
+ err(1, "Could not open device '%s'", dev);
+
+ schema = get_schema(fd);
+ config = parse_config_file(filename, schema);
+ if (config == NULL)
+ errx(1, "Could not parse config");
+
+ config_iov(fd, dev, config, dryrun);
+
+ nvlist_destroy(config);
+ nvlist_destroy(schema);
+ free(dev);
+ close(fd);
+}
+
+static void
+delete_action(const char *dev_name, int dryrun)
+{
+ int fd, error;
+
+ fd = open_device(dev_name);
+
+ if (dryrun)
+ printf("Would attempt to delete all VF children of '%s'\n",
+ dev_name);
+ else {
+ error = ioctl(fd, IOV_DELETE);
+ if (error != 0)
+ err(1, "Failed to delete VFs");
+ }
+
+ close(fd);
+}
+
+static void
+print_default_value(const nvlist_t *parameter, const char *type)
+{
+ const uint8_t *mac;
+ size_t size;
+
+ if (strcasecmp(type, "bool") == 0)
+ printf(" (default = %s)",
+ nvlist_get_bool(parameter, DEFAULT_SCHEMA_NAME) ? "true" :
+ "false");
+ else if (strcasecmp(type, "string") == 0)
+ printf(" (default = %s)",
+ nvlist_get_string(parameter, DEFAULT_SCHEMA_NAME));
+ else if (strcasecmp(type, "uint8_t") == 0)
+ printf(" (default = %ju)",
+ (uintmax_t)nvlist_get_number(parameter,
+ DEFAULT_SCHEMA_NAME));
+ else if (strcasecmp(type, "uint16_t") == 0)
+ printf(" (default = %ju)",
+ (uintmax_t)nvlist_get_number(parameter,
+ DEFAULT_SCHEMA_NAME));
+ else if (strcasecmp(type, "uint32_t") == 0)
+ printf(" (default = %ju)",
+ (uintmax_t)nvlist_get_number(parameter,
+ DEFAULT_SCHEMA_NAME));
+ else if (strcasecmp(type, "uint64_t") == 0)
+ printf(" (default = %ju)",
+ (uintmax_t)nvlist_get_number(parameter,
+ DEFAULT_SCHEMA_NAME));
+ else if (strcasecmp(type, "unicast-mac") == 0) {
+ mac = nvlist_get_binary(parameter, DEFAULT_SCHEMA_NAME, &size);
+ printf(" (default = %02x:%02x:%02x:%02x:%02x:%02x)", mac[0],
+ mac[1], mac[2], mac[3], mac[4], mac[5]);
+ } else if (strcasecmp(type, "vlan") == 0) {
+ uint16_t vlan = nvlist_get_number(parameter, DEFAULT_SCHEMA_NAME);
+ if (vlan == VF_VLAN_TRUNK)
+ printf(" (default = trunk)");
+ else
+ printf(" (default = %d)", vlan);
+ } else
+ errx(1, "Unexpected type in schema: '%s'", type);
+}
+
+static void
+print_subsystem_schema(const nvlist_t * subsystem_schema)
+{
+ const char *name, *type;
+ const nvlist_t *parameter;
+ void *it;
+ int nvtype;
+
+ it = NULL;
+ while ((name = nvlist_next(subsystem_schema, &nvtype, &it)) != NULL) {
+ parameter = nvlist_get_nvlist(subsystem_schema, name);
+ type = nvlist_get_string(parameter, TYPE_SCHEMA_NAME);
+
+ printf("\t%s : %s", name, type);
+ if (dnvlist_get_bool(parameter, REQUIRED_SCHEMA_NAME, false))
+ printf(" (required)");
+ else if (nvlist_exists(parameter, DEFAULT_SCHEMA_NAME))
+ print_default_value(parameter, type);
+ else
+ printf(" (optional)");
+ printf("\n");
+ }
+}
+
+static void
+print_schema(const char *dev_name)
+{
+ nvlist_t *schema;
+ const nvlist_t *iov_schema, *driver_schema, *pf_schema, *vf_schema;
+ int fd;
+
+ fd = open_device(dev_name);
+ schema = get_schema(fd);
+
+ pf_schema = nvlist_get_nvlist(schema, PF_CONFIG_NAME);
+ iov_schema = nvlist_get_nvlist(pf_schema, IOV_CONFIG_NAME);
+ driver_schema = nvlist_get_nvlist(pf_schema, DRIVER_CONFIG_NAME);
+ printf(
+"The following configuration parameters may be configured on the PF:\n");
+ print_subsystem_schema(iov_schema);
+ print_subsystem_schema(driver_schema);
+
+ vf_schema = nvlist_get_nvlist(schema, VF_SCHEMA_NAME);
+ iov_schema = nvlist_get_nvlist(vf_schema, IOV_CONFIG_NAME);
+ driver_schema = nvlist_get_nvlist(vf_schema, DRIVER_CONFIG_NAME);
+ printf(
+"\nThe following configuration parameters may be configured on a VF:\n");
+ print_subsystem_schema(iov_schema);
+ print_subsystem_schema(driver_schema);
+
+ nvlist_destroy(schema);
+ close(fd);
+}
diff --git a/usr.sbin/iovctl/iovctl.conf.5 b/usr.sbin/iovctl/iovctl.conf.5
new file mode 100644
index 000000000000..1bae96e150ba
--- /dev/null
+++ b/usr.sbin/iovctl/iovctl.conf.5
@@ -0,0 +1,172 @@
+.\"
+.\" Copyright (c) 2014 Sandvine Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd May 30, 2024
+.Dt IOVCTL.CONF 5
+.Os
+.Sh NAME
+.Nm iovctl.conf
+.Nd IOVCTL configuration file
+.Sh DESCRIPTION
+The
+.Nm
+file is the configuration file for the
+.Xr iovctl 8
+program.
+This file specifies configuration parameters for a single Physical Function
+.Pq PF
+device.
+To configure SR-IOV on multiple PF devices, use one configuration file for each
+PF.
+The locations of all
+.Xr iovctl 8
+configuration files are specified in
+.Xr rc.conf 5 .
+.Pp
+The
+.Nm
+file uses UCL format.
+UCL syntax is documented at the official UCL website:
+.Lk http://github.com/vstakhov/libucl .
+.Pp
+There are three types of sections in the
+.Nm
+file.
+A section is a key at the top level of the file with a list as its value.
+The list may contain the keys specified in the
+.Sx OPTIONS
+section of this manual page.
+Individual PF driver implementations may specify additional device-specific
+configuration keys that they will accept.
+The order in which sections appear in
+.Nm
+is ignored.
+No two sections may have the same key.
+For example, two sections for VF-1 must not be defined.
+.Pp
+The first section type is the PF section.
+This section always has the key "PF"; therefore, only one such section may be
+defined.
+This section defines configuration parameters that apply to the PF as a whole.
+.Pp
+The second section type is the VF section.
+This section has the key "VF-" followed by a VF index.
+VF indices start at 0 and always increment by 1.
+Valid VF indices are in the range of 0 to
+.Pq num_vfs - 1 .
+The VF index must be given as a decimal integer with no leading zeros.
+This section defines configuration parameters that apply to a single VF.
+.Pp
+The third section type is the default section.
+This section always has the key "DEFAULT"; therefore, only one such section may
+be specified.
+This section defines default configuration parameters that apply to all VFs.
+All configuration keys that are valid to be applied to a VF are valid in this
+section.
+An individual VF section may override a default specified in this section by
+providing a different value for the configuration parameter.
+Note that the default section applies to ALL VFs.
+The default section must appear before any VF sections.
+The default section may appear before or after the PF section.
+.Pp
+The following option types are supported:
+.Bl -tag -width indent
+.It boolean
+Accepts a boolean value of true or false.
+.It vlan
+Accepts a VLAN ID, or "trunk" to allow any VLAN ID.
+.It mac-addr
+Accepts a unicast MAC address specified as a string of the form
+xx:xx:xx:xx:xx:xx, where xx is one or two hexadecimal digits.
+.It string
+Accepts any string value.
+.It uint8_t
+Accepts any integer in the range 0 to 255, inclusive.
+.It uint16_t
+Accepts any integer in the range 0 to 65535, inclusive.
+.It uint32_t
+Accepts any integer in the range 0 to
+.Pq 2**32 - 1 ,
+inclusive.
+.It uint64_t
+Accepts any integer in the range 0 to
+.Pq 2**64 - 1 ,
+inclusive.
+.El
+.Sh OPTIONS
+The following parameters are accepted by all PF drivers:
+.Bl -tag -width indent
+.It device Pq string
+This parameter specifies the name of the PF device.
+This parameter is required to be specified.
+.It num_vfs Pq uint16_t
+This parameter specifies the number of VF children to create.
+This parameter may not be zero.
+The maximum value of this parameter is device-specific.
+.El
+.Pp
+The following parameters are accepted by all VFs:
+.Bl -tag -width indent
+.It passthrough Pq boolean
+This parameter controls whether the VF is reserved for the use of the
+.Xr bhyve 8
+hypervisor as a PCI passthrough device.
+If this parameter is set to true, then the VF will be reserved as a PCI
+passthrough device and it will not be accessible from the host OS.
+The default value of this parameter is false.
+.El
+.Pp
+See the PF driver manual page for configuration parameters specific to
+particular hardware.
+.Sh EXAMPLES
+This sample file will create 3 VFs as children of the ix0 device.
+VF-1 and VF-2 are set as
+.Xr bhyve 8
+passthrough devices through the use of the default section.
+VF-0 is not configured as a passthrough device as it explicitly overrides the
+default.
+VF-0 also sets a device-specific parameter named mac-addr.
+.Bd -literal -offset ident
+PF {
+ device : "ix0";
+ num_vfs : 3;
+}
+
+DEFAULT {
+ passthrough : true;
+}
+
+VF-0 {
+ mac-addr : "02:56:48:7e:d9:f7";
+ passthrough : false;
+ vlan: 1;
+}
+.Ed
+.Sh SEE ALSO
+.Xr rc.conf 5 ,
+.Xr iovctl 8
+.Sh AUTHORS
+This manual page was written by
+.An Ryan Stone Aq Mt rstone@FreeBSD.org .
diff --git a/usr.sbin/iovctl/iovctl.h b/usr.sbin/iovctl/iovctl.h
new file mode 100644
index 000000000000..5a39092395ca
--- /dev/null
+++ b/usr.sbin/iovctl/iovctl.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 2013-2015 Sandvine Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef IOVCTL_H
+#define IOVCTL_H
+
+char * find_device(const char *);
+nvlist_t * parse_config_file(const char *, const nvlist_t *);
+void validate_config(nvlist_t *, const nvlist_t *, const regex_t *);
+
+#endif
+
diff --git a/usr.sbin/iovctl/parse.c b/usr.sbin/iovctl/parse.c
new file mode 100644
index 000000000000..6b7645f341cc
--- /dev/null
+++ b/usr.sbin/iovctl/parse.c
@@ -0,0 +1,438 @@
+/*-
+ * Copyright (c) 2014-2015 Sandvine Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/iov.h>
+#include <sys/nv.h>
+#include <net/ethernet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ucl.h>
+#include <unistd.h>
+
+#include "iovctl.h"
+
+static void
+report_config_error(const char *key, const ucl_object_t *obj, const char *type)
+{
+
+ errx(1, "Value '%s' of key '%s' is not of type %s",
+ ucl_object_tostring(obj), key, type);
+}
+
+/*
+ * Verifies that the value specified in the config file is a boolean value, and
+ * then adds the value to the configuration.
+ */
+static void
+add_bool_config(const char *key, const ucl_object_t *obj, nvlist_t *config)
+{
+ bool val;
+
+ if (!ucl_object_toboolean_safe(obj, &val))
+ report_config_error(key, obj, "bool");
+
+ nvlist_add_bool(config, key, val);
+}
+
+/*
+ * Verifies that the value specified in the config file is a string, and then
+ * adds the value to the configuration.
+ */
+static void
+add_string_config(const char *key, const ucl_object_t *obj, nvlist_t *config)
+{
+ const char *val;
+
+ if (!ucl_object_tostring_safe(obj, &val))
+ report_config_error(key, obj, "string");
+
+ nvlist_add_string(config, key, val);
+}
+
+/*
+ * Verifies that the value specified in the config file is a integer value
+ * within the specified range, and then adds the value to the configuration.
+ */
+static void
+add_uint_config(const char *key, const ucl_object_t *obj, nvlist_t *config,
+ const char *type, uint64_t max)
+{
+ int64_t val;
+ uint64_t uval;
+
+ /* I must use a signed type here as libucl doesn't provide unsigned. */
+ if (!ucl_object_toint_safe(obj, &val))
+ report_config_error(key, obj, type);
+
+ if (val < 0)
+ report_config_error(key, obj, type);
+
+ uval = val;
+ if (uval > max)
+ report_config_error(key, obj, type);
+
+ nvlist_add_number(config, key, uval);
+}
+
+/*
+ * Verifies that the value specified in the config file is a unicast MAC
+ * address, and then adds the value to the configuration.
+ */
+static void
+add_unicast_mac_config(const char *key, const ucl_object_t *obj, nvlist_t *config)
+{
+ uint8_t mac[ETHER_ADDR_LEN];
+ const char *val, *token;
+ char *parse, *orig_parse, *tokpos, *endpos;
+ size_t len;
+ u_long value;
+ int i;
+
+ if (!ucl_object_tostring_safe(obj, &val))
+ report_config_error(key, obj, "unicast-mac");
+
+ parse = strdup(val);
+ orig_parse = parse;
+
+ i = 0;
+ while ((token = strtok_r(parse, ":", &tokpos)) != NULL) {
+ parse = NULL;
+
+ len = strlen(token);
+ if (len < 1 || len > 2)
+ report_config_error(key, obj, "unicast-mac");
+
+ value = strtoul(token, &endpos, 16);
+
+ if (*endpos != '\0')
+ report_config_error(key, obj, "unicast-mac");
+
+ if (value > UINT8_MAX)
+ report_config_error(key, obj, "unicast-mac");
+
+ if (i >= ETHER_ADDR_LEN)
+ report_config_error(key, obj, "unicast-mac");
+
+ mac[i] = value;
+ i++;
+ }
+
+ free(orig_parse);
+
+ if (i != ETHER_ADDR_LEN)
+ report_config_error(key, obj, "unicast-mac");
+
+ if (ETHER_IS_MULTICAST(mac))
+ errx(1, "Value '%s' of key '%s' is a multicast address",
+ ucl_object_tostring(obj), key);
+
+ nvlist_add_binary(config, key, mac, ETHER_ADDR_LEN);
+}
+
+static void
+add_vlan_config(const char *key, const ucl_object_t *obj, nvlist_t *config)
+{
+ int64_t val;
+ const char *strVal = "";
+
+ if(ucl_object_tostring_safe(obj, &strVal)) {
+ if (strcasecmp(strVal, "trunk") == 0) {
+ nvlist_add_number(config, key, VF_VLAN_TRUNK);
+ return;
+ }
+ report_config_error(key, obj, "vlan");
+ }
+
+ if (!ucl_object_toint_safe(obj, &val))
+ report_config_error(key, obj, "vlan");
+
+ if (val < 0 || val > 4095)
+ report_config_error(key, obj, "vlan");
+
+ nvlist_add_number(config, key, val);
+}
+
+/*
+ * Validates that the given configuration value has the right type as specified
+ * in the schema, and then adds the value to the configuration node.
+ */
+static void
+add_config(const char *key, const ucl_object_t *obj, nvlist_t *config,
+ const nvlist_t *schema)
+{
+ const char *type;
+
+ type = nvlist_get_string(schema, TYPE_SCHEMA_NAME);
+
+ if (strcasecmp(type, "bool") == 0)
+ add_bool_config(key, obj, config);
+ else if (strcasecmp(type, "string") == 0)
+ add_string_config(key, obj, config);
+ else if (strcasecmp(type, "uint8_t") == 0)
+ add_uint_config(key, obj, config, type, UINT8_MAX);
+ else if (strcasecmp(type, "uint16_t") == 0)
+ add_uint_config(key, obj, config, type, UINT16_MAX);
+ else if (strcasecmp(type, "uint32_t") == 0)
+ add_uint_config(key, obj, config, type, UINT32_MAX);
+ else if (strcasecmp(type, "uint64_t") == 0)
+ add_uint_config(key, obj, config, type, UINT64_MAX);
+ else if (strcasecmp(type, "unicast-mac") == 0)
+ add_unicast_mac_config(key, obj, config);
+ else if (strcasecmp(type, "vlan") == 0)
+ add_vlan_config(key, obj, config);
+ else
+ errx(1, "Unexpected type '%s' in schema", type);
+}
+
+/*
+ * Parses all values specified in a device section in the configuration file,
+ * validates that the key/value pair is valid in the schema, and then adds
+ * the key/value pair to the correct subsystem in the config.
+ */
+static void
+parse_device_config(const ucl_object_t *top, nvlist_t *config,
+ const char *subsystem, const nvlist_t *schema)
+{
+ ucl_object_iter_t it;
+ const ucl_object_t *obj;
+ nvlist_t *subsystem_config, *driver_config, *iov_config;
+ const nvlist_t *driver_schema, *iov_schema;
+ const char *key;
+
+ if (nvlist_exists(config, subsystem))
+ errx(1, "Multiple definitions of '%s' in config file",
+ subsystem);
+
+ driver_schema = nvlist_get_nvlist(schema, DRIVER_CONFIG_NAME);
+ iov_schema = nvlist_get_nvlist(schema, IOV_CONFIG_NAME);
+
+ driver_config = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (driver_config == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ iov_config = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (iov_config == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ subsystem_config = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (subsystem_config == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ it = NULL;
+ while ((obj = ucl_iterate_object(top, &it, true)) != NULL) {
+ key = ucl_object_key(obj);
+
+ if (nvlist_exists_nvlist(iov_schema, key))
+ add_config(key, obj, iov_config,
+ nvlist_get_nvlist(iov_schema, key));
+ else if (nvlist_exists_nvlist(driver_schema, key))
+ add_config(key, obj, driver_config,
+ nvlist_get_nvlist(driver_schema, key));
+ else
+ errx(1, "%s: Invalid config key '%s'", subsystem, key);
+ }
+
+ nvlist_move_nvlist(subsystem_config, DRIVER_CONFIG_NAME, driver_config);
+ nvlist_move_nvlist(subsystem_config, IOV_CONFIG_NAME, iov_config);
+ nvlist_move_nvlist(config, subsystem, subsystem_config);
+}
+
+/*
+ * Parses the specified config file using the given schema, and returns an
+ * nvlist containing the configuration specified by the file.
+ *
+ * Exits with a message to stderr and an error if any config validation fails.
+ */
+nvlist_t *
+parse_config_file(const char *filename, const nvlist_t *schema)
+{
+ ucl_object_iter_t it;
+ struct ucl_parser *parser;
+ ucl_object_t *top;
+ const ucl_object_t *obj;
+ nvlist_t *config;
+ const nvlist_t *pf_schema, *vf_schema;
+ const char *errmsg, *key;
+ regex_t vf_pat;
+ int regex_err, processed_vf;
+
+ regex_err = regcomp(&vf_pat, "^"VF_PREFIX"([1-9][0-9]*|0)$",
+ REG_EXTENDED | REG_ICASE);
+ if (regex_err != 0)
+ errx(1, "Could not compile VF regex");
+
+ parser = ucl_parser_new(0);
+ if (parser == NULL)
+ err(1, "Could not allocate parser");
+
+ if (!ucl_parser_add_file(parser, filename))
+ err(1, "Could not open '%s' for reading", filename);
+
+ errmsg = ucl_parser_get_error(parser);
+ if (errmsg != NULL)
+ errx(1, "Could not parse '%s': %s", filename, errmsg);
+
+ config = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (config == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ pf_schema = nvlist_get_nvlist(schema, PF_CONFIG_NAME);
+ vf_schema = nvlist_get_nvlist(schema, VF_SCHEMA_NAME);
+
+ processed_vf = 0;
+ top = ucl_parser_get_object(parser);
+ it = NULL;
+ while ((obj = ucl_iterate_object(top, &it, true)) != NULL) {
+ key = ucl_object_key(obj);
+
+ if (strcasecmp(key, PF_CONFIG_NAME) == 0)
+ parse_device_config(obj, config, key, pf_schema);
+ else if (strcasecmp(key, DEFAULT_SCHEMA_NAME) == 0) {
+ /*
+ * Enforce that the default section must come before all
+ * VF sections. This will hopefully prevent confusing
+ * the user by having a default value apply to a VF
+ * that was declared earlier in the file.
+ *
+ * This also gives us the flexibility to extend the file
+ * format in the future to allow for multiple default
+ * sections that do only apply to subsequent VF
+ * sections.
+ */
+ if (processed_vf)
+ errx(1,
+ "'default' section must precede all VF sections");
+
+ parse_device_config(obj, config, key, vf_schema);
+ } else if (regexec(&vf_pat, key, 0, NULL, 0) == 0) {
+ processed_vf = 1;
+ parse_device_config(obj, config, key, vf_schema);
+ } else
+ errx(1, "Unexpected top-level node: %s", key);
+ }
+
+ validate_config(config, schema, &vf_pat);
+
+ ucl_object_unref(top);
+ ucl_parser_free(parser);
+ regfree(&vf_pat);
+
+ return (config);
+}
+
+/*
+ * Parse the PF configuration section for and return the value specified for
+ * the device parameter, or NULL if the device is not specified.
+ */
+static const char *
+find_pf_device(const ucl_object_t *pf)
+{
+ ucl_object_iter_t it;
+ const ucl_object_t *obj;
+ const char *key, *device;
+
+ it = NULL;
+ while ((obj = ucl_iterate_object(pf, &it, true)) != NULL) {
+ key = ucl_object_key(obj);
+
+ if (strcasecmp(key, "device") == 0) {
+ if (!ucl_object_tostring_safe(obj, &device))
+ err(1,
+ "Config PF.device must be a string");
+
+ return (device);
+ }
+ }
+
+ return (NULL);
+}
+
+/*
+ * Manually parse the config file looking for the name of the PF device. We
+ * have to do this separately because we need the config schema to call the
+ * normal config file parsing code, and we need to know the name of the PF
+ * device so that we can fetch the schema from it.
+ *
+ * This will always exit on failure, so if it returns then it is guaranteed to
+ * have returned a valid device name.
+ */
+char *
+find_device(const char *filename)
+{
+ char *device;
+ const char *deviceName;
+ ucl_object_iter_t it;
+ struct ucl_parser *parser;
+ ucl_object_t *top;
+ const ucl_object_t *obj;
+ const char *errmsg, *key;
+ int error;
+
+ device = NULL;
+ deviceName = NULL;
+
+ parser = ucl_parser_new(0);
+ if (parser == NULL)
+ err(1, "Could not allocate parser");
+
+ if (!ucl_parser_add_file(parser, filename))
+ err(1, "Could not open '%s' for reading", filename);
+
+ errmsg = ucl_parser_get_error(parser);
+ if (errmsg != NULL)
+ errx(1, "Could not parse '%s': %s", filename, errmsg);
+
+ top = ucl_parser_get_object (parser);
+ it = NULL;
+ while ((obj = ucl_iterate_object(top, &it, true)) != NULL) {
+ key = ucl_object_key(obj);
+
+ if (strcasecmp(key, PF_CONFIG_NAME) == 0) {
+ deviceName = find_pf_device(obj);
+ break;
+ }
+ }
+
+ if (deviceName == NULL)
+ errx(1, "Config file does not specify device");
+
+ error = asprintf(&device, "/dev/iov/%s", deviceName);
+ if (error < 0)
+ err(1, "Could not allocate memory for device");
+
+ ucl_object_unref(top);
+ ucl_parser_free(parser);
+
+ return (device);
+}
diff --git a/usr.sbin/iovctl/validate.c b/usr.sbin/iovctl/validate.c
new file mode 100644
index 000000000000..cd187291c9c7
--- /dev/null
+++ b/usr.sbin/iovctl/validate.c
@@ -0,0 +1,271 @@
+/*-
+ * Copyright (c) 2014-2015 Sandvine Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/iov.h>
+#include <sys/dnv.h>
+#include <sys/nv.h>
+
+#include <err.h>
+#include <regex.h>
+#include <stdlib.h>
+
+#include "iovctl.h"
+
+/*
+ * Returns a writeable pointer to the configuration for the given device.
+ * If no configuration exists, a new nvlist with empty driver and iov
+ * sections is allocated and returned.
+ *
+ * Returning a writeable pointer requires removing the configuration from config
+ * using nvlist_take. It is the responsibility of the caller to re-insert the
+ * nvlist in config with nvlist_move_nvlist.
+ */
+static nvlist_t *
+find_config(nvlist_t *config, const char * device)
+{
+ nvlist_t *subsystem, *empty_driver, *empty_iov;
+
+ subsystem = dnvlist_take_nvlist(config, device, NULL);
+
+ if (subsystem != NULL)
+ return (subsystem);
+
+ empty_driver = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (empty_driver == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ empty_iov = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (empty_iov == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ subsystem = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (subsystem == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ nvlist_move_nvlist(subsystem, DRIVER_CONFIG_NAME, empty_driver);
+ nvlist_move_nvlist(subsystem, IOV_CONFIG_NAME, empty_iov);
+
+ return (subsystem);
+}
+
+static uint16_t
+parse_vf_num(const char *key, regmatch_t *matches)
+{
+ u_long vf_num;
+
+ vf_num = strtoul(key + matches[1].rm_so, NULL, 10);
+
+ if (vf_num > UINT16_MAX)
+ errx(1, "VF number %lu is too large to be valid",
+ vf_num);
+
+ return (vf_num);
+}
+
+/*
+ * Apply the default values specified in device_defaults to the specified
+ * subsystem in the given device_config.
+ *
+ * This function assumes that the values specified in device_defaults have
+ * already been validated.
+ */
+static void
+apply_subsystem_defaults(nvlist_t *device_config, const char *subsystem,
+ const nvlist_t *device_defaults)
+{
+ nvlist_t *config;
+ const nvlist_t *defaults;
+ const char *name;
+ void *cookie;
+ size_t len;
+ const void *bin;
+ int type;
+
+ config = nvlist_take_nvlist(device_config, subsystem);
+ defaults = nvlist_get_nvlist(device_defaults, subsystem);
+
+ cookie = NULL;
+ while ((name = nvlist_next(defaults, &type, &cookie)) != NULL) {
+ if (nvlist_exists(config, name))
+ continue;
+
+ switch (type) {
+ case NV_TYPE_BOOL:
+ nvlist_add_bool(config, name,
+ nvlist_get_bool(defaults, name));
+ break;
+ case NV_TYPE_NUMBER:
+ nvlist_add_number(config, name,
+ nvlist_get_number(defaults, name));
+ break;
+ case NV_TYPE_STRING:
+ nvlist_add_string(config, name,
+ nvlist_get_string(defaults, name));
+ break;
+ case NV_TYPE_NVLIST:
+ nvlist_add_nvlist(config, name,
+ nvlist_get_nvlist(defaults, name));
+ break;
+ case NV_TYPE_BINARY:
+ bin = nvlist_get_binary(defaults, name, &len);
+ nvlist_add_binary(config, name, bin, len);
+ break;
+ default:
+ errx(1, "Unexpected type '%d'", type);
+ }
+ }
+ nvlist_move_nvlist(device_config, subsystem, config);
+}
+
+/*
+ * Iterate over every subsystem in the given VF device and apply default values
+ * for parameters that were not configured with a value.
+ *
+ * This function assumes that the values specified in defaults have already been
+ * validated.
+ */
+static void
+apply_defaults(nvlist_t *vf, const nvlist_t *defaults)
+{
+
+ apply_subsystem_defaults(vf, DRIVER_CONFIG_NAME, defaults);
+ apply_subsystem_defaults(vf, IOV_CONFIG_NAME, defaults);
+}
+
+/*
+ * Validate that all required parameters have been configured in the specified
+ * subsystem.
+ */
+static void
+validate_subsystem(const nvlist_t *device, const nvlist_t *device_schema,
+ const char *subsystem_name, const char *config_name)
+{
+ const nvlist_t *subsystem, *schema, *config;
+ const char *name;
+ void *cookie;
+ int type;
+
+ subsystem = nvlist_get_nvlist(device, subsystem_name);
+ schema = nvlist_get_nvlist(device_schema, subsystem_name);
+
+ cookie = NULL;
+ while ((name = nvlist_next(schema, &type, &cookie)) != NULL) {
+ config = nvlist_get_nvlist(schema, name);
+
+ if (dnvlist_get_bool(config, REQUIRED_SCHEMA_NAME, false)) {
+ if (!nvlist_exists(subsystem, name))
+ errx(1,
+ "Required parameter '%s' not found in '%s'",
+ name, config_name);
+ }
+ }
+}
+
+/*
+ * Validate that all required parameters have been configured in all subsystems
+ * in the device.
+ */
+static void
+validate_device(const nvlist_t *device, const nvlist_t *schema,
+ const char *config_name)
+{
+
+ validate_subsystem(device, schema, DRIVER_CONFIG_NAME, config_name);
+ validate_subsystem(device, schema, IOV_CONFIG_NAME, config_name);
+}
+
+static uint16_t
+get_num_vfs(const nvlist_t *pf)
+{
+ const nvlist_t *iov;
+
+ iov = nvlist_get_nvlist(pf, IOV_CONFIG_NAME);
+ return (nvlist_get_number(iov, "num_vfs"));
+}
+
+/*
+ * Validates the configuration that has been parsed into config using the given
+ * config schema. Note that the parser is required to not insert configuration
+ * keys that are not valid in the schema, and to not insert configuration values
+ * that are of the incorrect type. Therefore this function will not validate
+ * either condition. This function is only responsible for inserting config
+ * file defaults in individual VF sections and removing the DEFAULT_SCHEMA_NAME
+ * subsystem from config, validating that all required parameters in the schema
+ * are present in each PF and VF subsystem, and that there is no VF subsystem
+ * section whose number exceeds num_vfs.
+ */
+void
+validate_config(nvlist_t *config, const nvlist_t *schema, const regex_t *vf_pat)
+{
+ char device_name[VF_MAX_NAME];
+ regmatch_t matches[2];
+ nvlist_t *defaults, *pf, *vf;
+ const nvlist_t *vf_schema;
+ const char *key;
+ void *cookie;
+ int i, type;
+ uint16_t vf_num, num_vfs;
+
+ pf = find_config(config, PF_CONFIG_NAME);
+ validate_device(pf, nvlist_get_nvlist(schema, PF_CONFIG_NAME),
+ PF_CONFIG_NAME);
+ nvlist_move_nvlist(config, PF_CONFIG_NAME, pf);
+
+ num_vfs = get_num_vfs(pf);
+ vf_schema = nvlist_get_nvlist(schema, VF_SCHEMA_NAME);
+
+ if (num_vfs == 0)
+ errx(1, "PF.num_vfs must be at least 1");
+
+ defaults = dnvlist_take_nvlist(config, DEFAULT_SCHEMA_NAME, NULL);
+
+ for (i = 0; i < num_vfs; i++) {
+ snprintf(device_name, sizeof(device_name), VF_PREFIX"%d",
+ i);
+
+ vf = find_config(config, device_name);
+
+ if (defaults != NULL)
+ apply_defaults(vf, defaults);
+
+ validate_device(vf, vf_schema, device_name);
+ nvlist_move_nvlist(config, device_name, vf);
+ }
+ nvlist_destroy(defaults);
+
+ cookie = NULL;
+ while ((key = nvlist_next(config, &type, &cookie)) != NULL) {
+ if (regexec(vf_pat, key, nitems(matches), matches, 0) == 0) {
+ vf_num = parse_vf_num(key, matches);
+ if (vf_num >= num_vfs)
+ errx(1,
+ "VF number %d is out of bounds (num_vfs=%d)",
+ vf_num, num_vfs);
+ }
+ }
+}
+