diff options
Diffstat (limited to 'usr.sbin/devctl')
| -rw-r--r-- | usr.sbin/devctl/Makefile | 6 | ||||
| -rw-r--r-- | usr.sbin/devctl/Makefile.depend | 16 | ||||
| -rw-r--r-- | usr.sbin/devctl/devctl.8 | 250 | ||||
| -rw-r--r-- | usr.sbin/devctl/devctl.c | 457 |
4 files changed, 729 insertions, 0 deletions
diff --git a/usr.sbin/devctl/Makefile b/usr.sbin/devctl/Makefile new file mode 100644 index 000000000000..376e05f5c0f3 --- /dev/null +++ b/usr.sbin/devctl/Makefile @@ -0,0 +1,6 @@ +PROG= devctl +MAN= devctl.8 + +LIBADD= devctl + +.include <bsd.prog.mk> diff --git a/usr.sbin/devctl/Makefile.depend b/usr.sbin/devctl/Makefile.depend new file mode 100644 index 000000000000..fed1efaf7831 --- /dev/null +++ b/usr.sbin/devctl/Makefile.depend @@ -0,0 +1,16 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libdevctl \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/devctl/devctl.8 b/usr.sbin/devctl/devctl.8 new file mode 100644 index 000000000000..41f2dd915708 --- /dev/null +++ b/usr.sbin/devctl/devctl.8 @@ -0,0 +1,250 @@ +.\" +.\" Copyright (c) 2015 John Baldwin <jhb@FreeBSD.org> +.\" +.\" 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 October 31, 2021 +.Dt DEVCTL 8 +.Os +.Sh NAME +.Nm devctl +.Nd device control utility +.Sh SYNOPSIS +.Nm +.Cm attach +.Ar device +.Nm +.Cm clear driver +.Op Fl f +.Ar device +.Nm +.Cm detach +.Op Fl f +.Ar device +.Nm +.Cm disable +.Op Fl f +.Ar device +.Nm +.Cm enable +.Ar device +.Nm +.Cm suspend +.Ar device +.Nm +.Cm resume +.Ar device +.Nm +.Cm set driver +.Op Fl f +.Ar device driver +.Nm +.Cm rescan +.Ar device +.Nm +.Cm delete +.Op Fl f +.Ar device +.Nm +.Cm freeze +.Nm +.Cm thaw +.Nm +.Cm reset +.Op Fl d +.Ar device +.Nm +.Cm getpath +.Ar locator +.Ar device +.Sh DESCRIPTION +The +.Nm +utility adjusts the state of individual devices in the kernel's +internal device hierarchy. +Each invocation of +.Nm +consists of a single command followed by command-specific arguments. +Each command operates on a single device specified via the +.Ar device +argument. +The +.Ar device +may be specified either as the name of an existing device or as a +bus-specific address. +More details on supported address formats can be found in +.Xr devctl 3 . +.Pp +The following commands are supported: +.Bl -tag -width indent +.It Cm attach Ar device +Force the kernel to re-probe the device. +If a suitable driver is found, +it is attached to the device. +.It Xo Cm detach +.Op Fl f +.Ar device +.Xc +Detach the device from its current device driver. +If the +.Fl f +flag is specified, +the device driver will be detached even if the device is busy. +.It Xo Cm disable +.Op Fl f +.Ar device +.Xc +Disable a device. +If the device is currently attached to a device driver, +the device driver will be detached from the device, +but the device will retain its current name. +If the +.Fl f +flag is specified, +the device driver will be detached even if the device is busy. +.It Cm enable Ar device +Enable a device. +The device will probe and attach if a suitable device driver is found. +Note that this can re-enable a device disabled at boot time via a +loader tunable. +.It Cm suspend Ar device +Suspend a device. +This may include placing the device in a reduced power state. +.It Cm resume Ar device +Resume a suspended device to a fully working state. +.It Xo Cm set driver +.Op Fl f +.Ar device driver +.Xc +Force the device to use a device driver named +.Ar driver . +If the device is already attached to a device driver and the +.Fl f +flag is specified, +the device will be detached from its current device driver before it is +attached to the new device driver. +If the device is already attached to a device driver and the +.Fl f +flag is not specified, +the device will not be changed. +.It Xo Cm clear driver +.Op Fl f +.Ar device +.Xc +Clear a previously-forced driver name so that the device is able to use any +valid device driver. +After the previous name has been cleared, +the device is reprobed so that other device drivers may attach to it. +This can be used to undo an earlier +.Cm set driver +command. +If the device is currently attached to a device driver and the +.Fl f +flag is not specified, +the device will not be changed. +.It Cm rescan Ar device +Rescan a bus device checking for devices that have been added or +removed. +.It Xo Cm delete +.Op Fl f +.Ar device +.Xc +Delete the device from the device tree. +If the +.Fl f +flag is specified, +the device will be deleted even if it is physically present. +This command should be used with care as a device that is deleted but present +can no longer be used unless the parent bus device rediscovers the device via +a rescan request. +.It Cm freeze +Freeze probe and attach processing initiated in response to drivers being +loaded. +Drivers are placed on a +.Do +frozen list +.Dc +and processed when a later +.Do +thaw +.Dc +occurs. +.It Cm thaw +Resume (thaw the freeze) probe and attach initiated in response to drivers +being loaded. +In addition to resuming, all pending actions that were frozen during the freeze +are performed. +.It Xo Cm reset +.Op Fl d +.Ar device +.Xc +Reset the device, using bus-specific reset method. +Drivers for the devices being reset are suspended around the reset. +If the +.Fl d +option is specified, drivers are detached instead. +.Pp +Currently, resets are implemented for PCIe buses and PCI devices. +For PCIe bus, the link is disabled and then re-trained, causing all +children of the bus to reset. +Use +.Fl p +option of +.Xr devinfo 8 +tool to report parent bus for the device. +For PCI device, if Function-Level Reset is implemented by it, FLR is +tried first; if failed or not implemented, power reset is tried. +.Pp +If you have detached or suspended a child device explicitly and then +do a reset, the child device will end up attached. +.It Xo Cm getpath +.Ar locator +.Ar device +.Xc +Prints the full path to the +.Ar device +using the +.Ar locator +method to get the path name. +Currently, only the +.Dq UEFI +and +.Dq FreeBSD +locators are implemented. +The UEFI locator constructs a path to the device using the rules +outlines for DEVICE_PATH in the UEFI standard. +The FreeBSD locator constructs a path back to the root of the tree +with the nodes separated by slashes. +.El +.Sh SEE ALSO +.Xr devctl 3 , +.Xr devinfo 8 +.Sh HISTORY +The +.Nm +utility first appeared in +.Fx 10.3 . +.Sh BUGS +Currently there is no administrative flag to prevent re-attach or resume +of the manually detached or suspended devices after reset. +Similarly, there is no flag to prevent un-suspending of the manually +suspended devices after system resume. diff --git a/usr.sbin/devctl/devctl.c b/usr.sbin/devctl/devctl.c new file mode 100644 index 000000000000..66bfb7dc6ed1 --- /dev/null +++ b/usr.sbin/devctl/devctl.c @@ -0,0 +1,457 @@ +/*- + * Copyright (c) 2014 John Baldwin <jhb@FreeBSD.org> + * + * 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/cdefs.h> +#include <sys/linker_set.h> +#include <devctl.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> + +struct devctl_command { + const char *name; + int (*handler)(int ac, char **av); +}; + +#define DEVCTL_DATASET(name) devctl_ ## name ## _table + +#define DEVCTL_COMMAND(set, name, function) \ + static struct devctl_command function ## _devctl_command = \ + { #name, function }; \ + DATA_SET(DEVCTL_DATASET(set), function ## _devctl_command) + +#define DEVCTL_TABLE(set, name) \ + SET_DECLARE(DEVCTL_DATASET(name), struct devctl_command); \ + \ + static int \ + devctl_ ## name ## _table_handler(int ac, char **av) \ + { \ + return (devctl_table_handler(SET_BEGIN(DEVCTL_DATASET(name)), \ + SET_LIMIT(DEVCTL_DATASET(name)), ac, av)); \ + } \ + DEVCTL_COMMAND(set, name, devctl_ ## name ## _table_handler) + +static int devctl_table_handler(struct devctl_command **start, + struct devctl_command **end, int ac, char **av); + +SET_DECLARE(DEVCTL_DATASET(top), struct devctl_command); + +DEVCTL_TABLE(top, clear); +DEVCTL_TABLE(top, set); + +static void +usage(void) +{ + fprintf(stderr, + "usage: devctl attach device\n" + " devctl detach [-f] device\n" + " devctl disable [-f] device\n" + " devctl enable device\n" + " devctl suspend device\n" + " devctl resume device\n" + " devctl set driver [-f] device driver\n" + " devctl clear driver [-f] device\n" + " devctl rescan device\n" + " devctl delete [-f] device\n" + " devctl freeze\n" + " devctl thaw\n" + " devctl reset [-d] device\n" + " devctl getpath locator device\n" + ); + exit(1); +} + +static int +devctl_table_handler(struct devctl_command **start, + struct devctl_command **end, int ac, char **av) +{ + struct devctl_command **cmd; + + if (ac < 2) { + warnx("The %s command requires a sub-command.", av[0]); + return (EINVAL); + } + for (cmd = start; cmd < end; cmd++) { + if (strcmp((*cmd)->name, av[1]) == 0) + return ((*cmd)->handler(ac - 1, av + 1)); + } + + warnx("%s is not a valid sub-command of %s.", av[1], av[0]); + return (ENOENT); +} + +static int +help(int ac __unused, char **av __unused) +{ + + usage(); + return (0); +} +DEVCTL_COMMAND(top, help, help); + +static int +attach(int ac, char **av) +{ + + if (ac != 2) + usage(); + if (devctl_attach(av[1]) < 0) + err(1, "Failed to attach %s", av[1]); + return (0); +} +DEVCTL_COMMAND(top, attach, attach); + +static void +detach_usage(void) +{ + + fprintf(stderr, "usage: devctl detach [-f] device\n"); + exit(1); +} + +static int +detach(int ac, char **av) +{ + bool force; + int ch; + + force = false; + while ((ch = getopt(ac, av, "f")) != -1) + switch (ch) { + case 'f': + force = true; + break; + default: + detach_usage(); + } + ac -= optind; + av += optind; + + if (ac != 1) + detach_usage(); + if (devctl_detach(av[0], force) < 0) + err(1, "Failed to detach %s", av[0]); + return (0); +} +DEVCTL_COMMAND(top, detach, detach); + +static void +disable_usage(void) +{ + + fprintf(stderr, "usage: devctl disable [-f] device\n"); + exit(1); +} + +static int +disable(int ac, char **av) +{ + bool force; + int ch; + + force = false; + while ((ch = getopt(ac, av, "f")) != -1) + switch (ch) { + case 'f': + force = true; + break; + default: + disable_usage(); + } + ac -= optind; + av += optind; + + if (ac != 1) + disable_usage(); + if (devctl_disable(av[0], force) < 0) + err(1, "Failed to disable %s", av[0]); + return (0); +} +DEVCTL_COMMAND(top, disable, disable); + +static int +enable(int ac, char **av) +{ + + if (ac != 2) + usage(); + if (devctl_enable(av[1]) < 0) + err(1, "Failed to enable %s", av[1]); + return (0); +} +DEVCTL_COMMAND(top, enable, enable); + +static int +suspend(int ac, char **av) +{ + + if (ac != 2) + usage(); + if (devctl_suspend(av[1]) < 0) + err(1, "Failed to suspend %s", av[1]); + return (0); +} +DEVCTL_COMMAND(top, suspend, suspend); + +static int +resume(int ac, char **av) +{ + + if (ac != 2) + usage(); + if (devctl_resume(av[1]) < 0) + err(1, "Failed to resume %s", av[1]); + return (0); +} +DEVCTL_COMMAND(top, resume, resume); + +static void +set_driver_usage(void) +{ + + fprintf(stderr, "usage: devctl set driver [-f] device driver\n"); + exit(1); +} + +static int +set_driver(int ac, char **av) +{ + bool force; + int ch; + + force = false; + while ((ch = getopt(ac, av, "f")) != -1) + switch (ch) { + case 'f': + force = true; + break; + default: + set_driver_usage(); + } + ac -= optind; + av += optind; + + if (ac != 2) + set_driver_usage(); + if (devctl_set_driver(av[0], av[1], force) < 0) + err(1, "Failed to set %s driver to %s", av[0], av[1]); + return (0); +} +DEVCTL_COMMAND(set, driver, set_driver); + +static void +clear_driver_usage(void) +{ + + fprintf(stderr, "usage: devctl clear driver [-f] device\n"); + exit(1); +} + +static int +clear_driver(int ac, char **av) +{ + bool force; + int ch; + + force = false; + while ((ch = getopt(ac, av, "f")) != -1) + switch (ch) { + case 'f': + force = true; + break; + default: + clear_driver_usage(); + } + ac -= optind; + av += optind; + + if (ac != 1) + clear_driver_usage(); + if (devctl_clear_driver(av[0], force) < 0) + err(1, "Failed to clear %s driver", av[0]); + return (0); +} +DEVCTL_COMMAND(clear, driver, clear_driver); + +static int +rescan(int ac, char **av) +{ + + if (ac != 2) + usage(); + if (devctl_rescan(av[1]) < 0) + err(1, "Failed to rescan %s", av[1]); + return (0); +} +DEVCTL_COMMAND(top, rescan, rescan); + +static void +delete_usage(void) +{ + + fprintf(stderr, "usage: devctl delete [-f] device\n"); + exit(1); +} + +static int +delete(int ac, char **av) +{ + bool force; + int ch; + + force = false; + while ((ch = getopt(ac, av, "f")) != -1) + switch (ch) { + case 'f': + force = true; + break; + default: + delete_usage(); + } + ac -= optind; + av += optind; + + if (ac != 1) + delete_usage(); + if (devctl_delete(av[0], force) < 0) + err(1, "Failed to delete %s", av[0]); + return (0); +} +DEVCTL_COMMAND(top, delete, delete); + +static void +freeze_usage(void) +{ + + fprintf(stderr, "usage: devctl freeze\n"); + exit(1); +} + +static int +freeze(int ac, char **av __unused) +{ + + if (ac != 1) + freeze_usage(); + if (devctl_freeze() < 0) + err(1, "Failed to freeze probe/attach"); + return (0); +} +DEVCTL_COMMAND(top, freeze, freeze); + +static void +thaw_usage(void) +{ + + fprintf(stderr, "usage: devctl thaw\n"); + exit(1); +} + +static int +thaw(int ac, char **av __unused) +{ + + if (ac != 1) + thaw_usage(); + if (devctl_thaw() < 0) + err(1, "Failed to thaw probe/attach"); + return (0); +} +DEVCTL_COMMAND(top, thaw, thaw); + +static void +reset_usage(void) +{ + + fprintf(stderr, "usage: devctl reset [-d] device\n"); + exit(1); +} + +static int +reset(int ac, char **av) +{ + bool detach_drv; + int ch; + + detach_drv = false; + while ((ch = getopt(ac, av, "d")) != -1) + switch (ch) { + case 'd': + detach_drv = true; + break; + default: + reset_usage(); + } + ac -= optind; + av += optind; + + if (ac != 1) + reset_usage(); + if (devctl_reset(av[0], detach_drv) < 0) + err(1, "Failed to reset %s", av[0]); + return (0); +} +DEVCTL_COMMAND(top, reset, reset); + +static int +getpath(int ac, char **av) +{ + char *buffer = NULL; + + if (ac != 3) + usage(); + if (devctl_getpath(av[2], av[1], &buffer) < 0) + err(1, "Failed to get path via %s to %s", av[1], av[2]); + printf("%s\n", buffer); + free(buffer); + return (0); +} +DEVCTL_COMMAND(top, getpath, getpath); + +int +main(int ac, char *av[]) +{ + struct devctl_command **cmd; + + if (ac == 1) + usage(); + ac--; + av++; + + SET_FOREACH(cmd, DEVCTL_DATASET(top)) { + if (strcmp((*cmd)->name, av[0]) == 0) { + if ((*cmd)->handler(ac, av) != 0) + return (1); + else + return (0); + } + } + warnx("Unknown command %s.", av[0]); + return (1); +} |
