Author Topic: USB port power control (turn off/on the connected devices)  (Read 6538 times)

Offline vlsoft

  • Newbie
  • *
  • Posts: 1
  • Karma: +1/-0
    • View Profile
USB port power control (turn off/on the connected devices)
« on: June 07, 2015, 10:56:09 pm »
This is my first post here... I would like to contribute something, a piece of code, but to point out how it can be useful, I have a story first, so bear with me! :-)

I use my Cubieboard 2 as a small home server that's on 24/7, boots a custom kernel (patched with various little bits like temperature sensing) from NAND, and continues the boot from an attached SATA notebook HDD. It runs Debian GNU/Linux 9.0 "Stretch", debootstrapped by hand, as I like to build my own things. I have a ZTE 4G USB stick for internet connectivity, and the Cubieboard acts as my router. While I'm away, it's my secure VPN endpoint back into my home network, it updates a dynamic DNS entry for this, can wake machines on the internal LAN, runs Asterisk for telephony, and does a zillion other things.

But why a 4G stick? I live at a strange place where you can't get any wired connection from ISPs, but 4G reception is exceptionally good. The downside is the monthly data limit though, so life is not so peachy after all.

Anyway, this 4G stick of mine tends to lock up hard from time to time. It's not the Cubieboard or the PSU (I have a nice beefy 3A one), as the stick tends to hiccup when plugged into a laptop or various desktop PCs too. It did this way before I bought my Cubieboard. It's just a badly designed stick, probably they never meant it to run 24/7. No sysfs trickery can reset it once it dies, only physically removing it for 10 seconds or so from the system helps it.

I needed a way to power this stick down, remotely. When I'm not in my "home lab" but still in the house, it's easy to do through a shell connection from whatever device I'm using over the LAN. When I'm really away, a script helps me out that looks for the specific error message in dmesg that usually means the stick died, and then it kicks it for me and reconnects the PPP daemon. Anyone can write such a script, so I won't bother with that part. The really interesting one I'll provide below is the code for powering down/up the USB ports:

cbusb.c
Code: [Select]
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>

#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1)

int verbose = 0;
int main(int argc, char **argv)
{
  int fd;
  void *map_base, *virt_addr;
  unsigned long readval, writeval;
  off_t porth;

  if ( argc == 2 && ( argv[1][1] == 'h' || argv[1][1] == '-' ) )
  {
    printf("Usage: cbusb [+|-][1|2]...\n");
    printf("Sets the Cubieboard (A10 and A20) USB power directly for port 1 and 2.\n");
    printf("USB1 is the upper, USB2 is the lower - closer to the PCB - port.\n\n");
    printf("Be warned: it directly writes the data register of Port H, bypassing the kernel.\n\n");
    printf("Without parameters just prints the port power status.\n");
    printf("+ turns the power on, - turns it off for the specified port.\n");
    printf("Port H only gets written if the settings differ from the current ones.\n");
    printf("Note, changing power for one port might momentarily disrupt the other too.\n\n");
    printf("Examples:\n        cbusb +1\n        cbusb +2 -1\n\n");
    fflush(stdout);
  }

  setuid(0); // try

  if ( (fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1 )
  {
    printf("ERR: Can't open /dev/mem\n");
    exit(1);
  }
  if ( verbose )
  {
    printf("/dev/mem opened.\n");
    fflush(stdout);
  }

  porth = 0x1c2090c; // Port H data reg.

  map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, porth & ~MAP_MASK);
  if ( map_base == (void *) -1 )
  {
    printf("ERR: Can't map the requested memory page\n");
    exit(2);
  }
  if ( verbose )
  {
    printf("Memory mapped at address %p.\n", map_base);
    fflush(stdout);
  }

  virt_addr = map_base + (porth & MAP_MASK);
  readval = *((unsigned long *) virt_addr);
  printf("USB1: %i\n", (readval & 8) >> 3);
  printf("USB2: %i\n", (readval & 64) >> 6);
  if ( verbose ) printf("OLD: 0x%08x\n", readval);
  fflush(stdout);

  writeval = readval;
  int i;
  for (i=1; i<=2; ++i)
  {
    if ( argc > i )
    {
      unsigned long usbmask = 0;
      if ( argv[i][1]=='1' ) usbmask = 8; else if ( argv[i][1]=='2' ) usbmask = 64;
      if ( argv[i][0]=='+' ) writeval |= usbmask; else writeval &= ~usbmask;
    }
  }
  if ( verbose ) { printf("NEW: 0x%08x\n", writeval); fflush(stdout); }

  if ( writeval != readval )
  {
    *((unsigned long *) virt_addr) = writeval;
    printf("NEWUSB1: %i\n", (writeval & 8) >> 3);
    printf("NEWUSB2: %i\n", (writeval & 64) >> 6);
    fflush(stdout);
  }

  if ( munmap(map_base, MAP_SIZE) == -1 )
  {
    printf("ERR: munmap failed\n");
    exit(3);
  }

  close(fd);

  return 0;
}

To compile it, first save it as cbusb.c and compile it like this:
gcc -o cbusb cbusb.c

Then copy/move the resulting binary like so:
mv cbusb /usr/local/sbin/

Using it is pretty much self-explanatory, but you can execute it like this to get some help:
cbusb -h

As I have my USB stick on the second port, my script does this when it needs to be power cycled:
...
cbusb -2
sleep 15
cbusb +2
dmesg -c >/dev/null 2>&1
...
Cron obviously sends me a nasty output in e-mail, as cbusb is a bit chatty; but then I'll know the stick died again; and dmesg gets cleared, so that it won't cycle the stick endlessly once it did a hiccup.

About licensing: I feel I don't need to put this under any kind of license, just take it and use it! Whoever decides to copyright it under his/her own name, should be happy with it; I won't sue anyone to death. I think this is a simple bit of code that anyone who takes the time to read the A20 manual and look at the board schematics could write when needed, so it is of little financial value to me, and I considered it to be a quick hack. Also, I'm not responsible for any kind of damage that this code might cause, don't try to find me! :-P
For my 4G stick, powering it down while the kernel modules still use it is fine, the only bad thing that happens is that I get quite a few lines of errors in dmesg. But your mileage may vary. Powering down a USB HDD like this is probably unhealthy, you should umount it, flush the filesystem buffers with sync, and then park the head with hdparm or something... It's up to you to find out how to use this code semi-safely. :-)

Offline actkk2000

  • Hero Member
  • *****
  • Posts: 572
  • Karma: +2/-11
    • View Profile
Re: USB port power control (turn off/on the connected devices)
« Reply #1 on: June 08, 2015, 01:28:27 pm »
Thank you  :)

Offline AVI

  • Newbie
  • *
  • Posts: 29
  • Karma: +0/-0
    • View Profile
Re: USB port power control (turn off/on the connected devices)
« Reply #2 on: June 09, 2015, 05:05:15 am »
4g modem (for example zte m823) usually can be switched to the router mode. He works much more stable. After you can control the CGI commands of the form: http://192.168.0.1/goform/goform_set_cmd_process?goformId=CONNECT_NETWORK
http://192.168.0.1/goform/goform_set_cmd_process?goformId=DISCONNECT_NETWORK
...

Offline null

  • Full Member
  • ***
  • Posts: 154
  • Karma: +5/-11
    • View Profile
Re: USB port power control (turn off/on the connected devices)
« Reply #3 on: November 01, 2015, 11:59:03 am »
Also, usb power can be controlled by corresponding gpio.
To find gpio port used to control usb power, see usb_drv_vbus_gpio in your fex.
« Last Edit: November 04, 2015, 01:13:16 pm by null »