diff options
Diffstat (limited to 'usr.sbin/iovctl')
| -rw-r--r-- | usr.sbin/iovctl/Makefile | 13 | ||||
| -rw-r--r-- | usr.sbin/iovctl/Makefile.depend | 18 | ||||
| -rw-r--r-- | usr.sbin/iovctl/iovctl.8 | 121 | ||||
| -rw-r--r-- | usr.sbin/iovctl/iovctl.c | 410 | ||||
| -rw-r--r-- | usr.sbin/iovctl/iovctl.conf.5 | 172 | ||||
| -rw-r--r-- | usr.sbin/iovctl/iovctl.h | 35 | ||||
| -rw-r--r-- | usr.sbin/iovctl/parse.c | 438 | ||||
| -rw-r--r-- | usr.sbin/iovctl/validate.c | 271 | 
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); +		} +	} +} + | 
