[patch] proof of concept: enable/disable usb devices from user mode

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[patch] proof of concept: enable/disable usb devices from user mode

Il Ka
Hello,

People on misc@ asked if it is possible to disable usb devices without reboot.
Some of them want to disable cameras on laptops to save energy,
other people want to disable mass storages on servers where
it is not easy to disconnect cable.

Any usb device is connected to hub's port, and any hub knows how to
disable port and power off it, so I decided to create such API and tool.

Disclamer: this is the first time I do unix kernel development,
and this tool is not ready to be commited: I just want to make sure that
my idea is generally accepted by tech@.

This is how it works:
* usb(4) has ioctl to enable/disable port on uhub(4)
* usbports accepts device number, hub address, port number and status
* it then forwards it to usb(4), which delegates command to uhub(4)
* usbd_detach() called to inform usb(4) that device is disabled
* to enable, port is enabled, powered and reseted, so usb(4) finds device

To find hub number use usbdevs(8)

Consider following output

$ usbdevs -v
Controller /dev/usb1:
addr 1: ...
 port 1 addr 2: high speed, self powered, config 1, Rate Matching Hub(0x0024)
  port 1 powered
  port 2 addr 3: low speed, power 100 mA, config 1, USB Optical Mouse(0xc077)

That means we have bus=1 (usb1), hub_addr=2 (addr 2), port=2 (port 2)

$ doas usbports 1 2 2 0

This will disable mouse.

$ doas usbports 1 2 2 1

This will enable mouse back.

If people accept this approach, here are my plans:

Fix kernel API (check port status before disable/enable, write man).
Merge usbdevs and usbports (there should be one tool for usb).
Write man about tool.

Create TUI/GUI tool that displays usb ports tree providing user with ability
to click on port to disable/enable it.
This tool may also have command line interface and use device ids instead of
ports, so user may write starting script to persist changes.

Add ability to disable port on root hub: whole controller could be disabled
in *hci specific-manner, so I will add method to each *hci to do that.
For example: in ehci you use PORTC to disable port.


By the way:
In Windows they have device manager (UI for PnPmanager) that gives user
ability to disable any node in tree.
When node is disabled, all its ancestors are disabled first in device-specific
manner:
USB controller could be disabled using PCI power API because
*hci is PCI device.
PCI controller could be disabled using ACPI.
I like idea of "tool to show device tree and disable any device" which
is bus-agnostic, but not sure if it does not compromise security and stability,
and it seems to be huge task.

Since header files in dev/usb/ changed, make sure you have them in
/usr/include/dev/usb to compile usbports.

Index: sys/dev/usb/uhub.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/uhub.c,v
retrieving revision 1.90
diff -u -p -u -p -r1.90 uhub.c
--- sys/dev/usb/uhub.c 8 Apr 2017 02:57:25 -0000 1.90
+++ sys/dev/usb/uhub.c 24 Jun 2018 22:51:12 -0000
@@ -70,6 +70,7 @@ struct uhub_softc {
 #define UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB)
 #define UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT)
 
+int uhub_port_ctl(struct usbd_device *hub_dev, u_int8_t cmd);
 int uhub_explore(struct usbd_device *hub);
 void uhub_intr(struct usbd_xfer *, void *, usbd_status);
 int uhub_port_connect(struct uhub_softc *, int, int, int);
@@ -221,6 +222,7 @@ uhub_attach(struct device *parent, struc
  dev->hub = hub;
  dev->hub->hubsoftc = sc;
  hub->explore = uhub_explore;
+ hub->port_ctl = uhub_port_ctl;
  hub->nports = nports;
  hub->powerdelay = powerdelay;
  hub->ttthink = ttthink >> 5;
@@ -347,6 +349,80 @@ uhub_attach(struct device *parent, struc
  free(hub, M_USBDEV, sizeof *hub);
  }
  dev->hub = NULL;
+}
+
+int
+uhub_port_ctl(struct usbd_device *hub_dev, u_int8_t cmd)
+{
+ struct uhub_softc *sc = hub_dev->hub->hubsoftc;
+ u_int8_t port = cmd >> 1;
+ u_int8_t enable = cmd & 1;
+
+ DPRINTF("uhub_port_ctl port %d %s \n", port,
+ (enable ? "enable" : "disable"));
+
+ if (port > hub_dev->hub->nports || port == 0)
+ return (ENXIO);
+
+ int err;
+ // TODO: Check current port status using usbd_get_port_feature
+ if (enable)
+ {
+ err = usbd_set_port_feature(hub_dev, port, UHF_PORT_ENABLE);
+ if (err != 0)
+ {
+ printf("Can't enable port: %d\n", err);
+ return err;
+ }
+
+ err = usbd_set_port_feature(hub_dev, port, UHF_PORT_POWER);
+ if (err != 0)
+ {
+ printf("Can't enable power on port: %d\n", err);
+ return err;
+ }
+
+ usbd_delay_ms(hub_dev, USB_POWER_DOWN_TIME);
+
+ err = usbd_reset_port(hub_dev, port);
+ if (err != 0)
+ {
+ printf("Can't reset port: %d\n", err);
+ return err;
+ }
+
+ } else {
+ struct usbd_port u_port = hub_dev->hub->ports[port - 1];
+ struct usbd_device *child = u_port.device;
+ if (child)
+ {
+ err = usbd_detach(child, (struct device*)sc);
+ if (err != 0)
+ {
+ printf("Can't detach device: %d\n", err);
+ return err;
+ }
+ hub_dev->hub->ports[port - 1].device = NULL;
+
+ }
+ err = usbd_clear_port_feature(hub_dev, port, UHF_PORT_POWER);
+ if (err != 0)
+ {
+ printf("Can't turn power on: %d\n", err);
+ return err;
+ }
+
+ usbd_delay_ms(hub_dev, USB_POWER_DOWN_TIME);
+
+ err = usbd_clear_port_feature(hub_dev, port, UHF_PORT_ENABLE);
+ if (err != 0)
+ {
+ printf("Can't enable device: %d\n", err);
+ return err;
+ }
+
+ }
+ return 0;
 }
 
 int
Index: sys/dev/usb/usb.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/usb.c,v
retrieving revision 1.119
diff -u -p -u -p -r1.119 usb.c
--- sys/dev/usb/usb.c 1 May 2018 18:14:46 -0000 1.119
+++ sys/dev/usb/usb.c 24 Jun 2018 22:51:12 -0000
@@ -102,6 +102,11 @@ struct usb_softc {
 
 struct rwlock usbpalock;
 
+struct usb_port_cmd {
+ struct usbd_device *hub_dev;
+ u_int8_t port_cmd; /* See hub's port_ctl */
+};
+
 TAILQ_HEAD(, usb_task) usb_abort_tasks;
 TAILQ_HEAD(, usb_task) usb_explore_tasks;
 TAILQ_HEAD(, usb_task) usb_generic_tasks;
@@ -111,6 +116,7 @@ static int usb_run_tasks, usb_run_abort_
 int explore_pending;
 const char *usbrev_str[] = USBREV_STR;
 
+void usb_port_cmd(void *); /* struct usb_port_cmd*/
 void usb_explore(void *);
 void usb_create_task_threads(void *);
 void usb_task_thread(void *);
@@ -595,6 +601,30 @@ usbioctl(dev_t devt, u_long cmd, caddr_t
 
  error = 0;
  switch (cmd) {
+ case USB_PORT_CTL:
+ {
+ if ((error = suser(curproc)) != 0)
+ return (error);
+
+ struct usb_task pc_task;
+ struct usb_port_ctl *upc = (struct usb_port_ctl*)data;
+ struct usbd_device *hub_dev = sc->sc_bus->devices[upc->upc_hub_addr];
+ DPRINTF(("USB_PORT_CTL hub:%u \n", upc->upc_hub_addr));
+
+ if (hub_dev == NULL || hub_dev->hub == NULL)
+ return (ENODEV);
+
+ struct usb_port_cmd cmd = {
+ .hub_dev = hub_dev,
+   .port_cmd = upc->upc_cfg.upc_cfg_c
+ };
+
+ usb_init_task(&pc_task, usb_port_cmd, &cmd, USB_TASK_TYPE_EXPLORE);
+ usb_add_task(sc->sc_bus->root_hub, &pc_task);
+ usb_wait_task(sc->sc_bus->root_hub, &pc_task);
+
+ return 0;
+ }
 #ifdef USB_DEBUG
  case USB_SETDEBUG:
  /* only root can access to these debug flags */
@@ -817,6 +847,19 @@ usbioctl(dev_t devt, u_long cmd, caddr_t
  return (EINVAL);
  }
  return (0);
+}
+/*
+ * To be run in usb explore thread: enables/disables port on hub
+ */
+void
+usb_port_cmd(void *data)
+{
+ struct usb_port_cmd* cmd = (struct usb_port_cmd*)data;
+ int result = cmd->hub_dev->hub->port_ctl(cmd->hub_dev, cmd->port_cmd);
+ if (result != 0)
+ {
+ printf("port_crl error: %d\n", result);
+ }
 }
 
 /*
Index: sys/dev/usb/usb.h
===================================================================
RCS file: /cvs/src/sys/dev/usb/usb.h,v
retrieving revision 1.59
diff -u -p -u -p -r1.59 usb.h
--- sys/dev/usb/usb.h 1 Sep 2017 16:38:14 -0000 1.59
+++ sys/dev/usb/usb.h 24 Jun 2018 22:51:12 -0000
@@ -756,6 +756,17 @@ struct usb_device_stats {
  u_long uds_requests[4]; /* indexed by transfer type UE_* */
 };
 
+struct usb_port_ctl {
+ u_int8_t upc_hub_addr; /* hub device address */
+ union {
+ struct {
+ u_int8_t upc_enabled: 1; /* enabled */
+ u_int8_t upc_port: 7; /* port number */
+ } upc_cfg_s;
+ u_int8_t upc_cfg_c;
+ } upc_cfg;
+};
+
 /* USB controller */
 #define USB_REQUEST _IOWR('U', 1, struct usb_ctl_request)
 #define USB_SETDEBUG _IOW ('U', 2, unsigned int)
@@ -764,6 +775,7 @@ struct usb_device_stats {
 #define USB_DEVICE_GET_CDESC _IOWR('U', 6, struct usb_device_cdesc)
 #define USB_DEVICE_GET_FDESC _IOWR('U', 7, struct usb_device_fdesc)
 #define USB_DEVICE_GET_DDESC _IOWR('U', 8, struct usb_device_ddesc)
+#define USB_PORT_CTL _IOWR('U', 9, struct usb_port_ctl)
 
 /* Generic HID device */
 #define USB_GET_REPORT_DESC _IOR ('U', 21, struct usb_ctl_report_desc)
Index: sys/dev/usb/usbdivar.h
===================================================================
RCS file: /cvs/src/sys/dev/usb/usbdivar.h,v
retrieving revision 1.75
diff -u -p -u -p -r1.75 usbdivar.h
--- sys/dev/usb/usbdivar.h 1 May 2018 18:14:46 -0000 1.75
+++ sys/dev/usb/usbdivar.h 24 Jun 2018 22:51:13 -0000
@@ -94,6 +94,8 @@ struct usbd_port {
 
 struct usbd_hub {
  int      (*explore)(struct usbd_device *);
+ /*(port_n << 1) + enabled*/
+ int (*port_ctl)(struct usbd_device*, u_int8_t);
  void       *hubsoftc;
  struct usbd_port       *ports;
  int nports;
Index: usr.sbin/usbports/Makefile
===================================================================
RCS file: usr.sbin/usbports/Makefile
diff -N usr.sbin/usbports/Makefile
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/usbports/Makefile 24 Jun 2018 22:51:14 -0000
@@ -0,0 +1,4 @@
+PROG= usbports
+MAN=
+
+.include <bsd.prog.mk>
Index: usr.sbin/usbports/usbports.c
===================================================================
RCS file: usr.sbin/usbports/usbports.c
diff -N usr.sbin/usbports/usbports.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/usbports/usbports.c 24 Jun 2018 22:51:14 -0000
@@ -0,0 +1,69 @@
+#include <stdio.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <dev/usb/usb.h>
+#include <sys/ioctl.h>
+
+#define USBDEV "/dev/usb%u"
+#define USBDEV_LEN sizeof(USBDEV) + 1
+
+#define ARG_BUS 1
+#define ARG_HUB 2
+#define ARG_PORT 3
+#define ARG_ENABLE 4
+
+#define ARG(s, d) sscanf(s, "%u", &d) == 1
+#define MAX_BUS 99
+
+int
+main(int argc, char** argv)
+{
+ unsigned int bus, hub, port, enable;
+ struct usb_port_ctl upc;
+ char dev_name[USBDEV_LEN];
+ int f;
+
+ if (argc != ARG_ENABLE + 1 ||
+ !((
+ ARG(argv[ARG_BUS], bus) &&
+ ARG(argv[ARG_HUB], hub) &&
+ ARG(argv[ARG_PORT], port) &&
+ ARG(argv[ARG_ENABLE], enable))))
+ {
+ fprintf(stderr, "Usage: bus_n hub_dev_addr port_n enable({1,0})\n");
+ return 1;
+ }
+
+ if (bus > MAX_BUS)
+ {
+ fprintf(stderr, "Bus can't be  > %u \n", MAX_BUS);
+ return 1;
+ }
+
+ if (snprintf(dev_name, sizeof(dev_name), USBDEV, bus) < 0)
+ {
+ perror("Can't generate usbdev name");
+ return 1;
+ }
+
+ f = open(dev_name, O_RDWR);
+ if (f <= 0)
+ {
+ perror("Can't open usb device");
+ return 1;
+ }
+
+ upc.upc_hub_addr = hub;
+ upc.upc_cfg.upc_cfg_s.upc_port = port;
+ upc.upc_cfg.upc_cfg_s.upc_enabled = enable;
+
+ if (ioctl(f, USB_PORT_CTL, &upc) != 0)
+ {
+ perror("ioctl error");
+ return 1;
+ }
+
+ close(f);
+ return 0;
+}