/*
 * Copyright (c) 2008 Anish Mistry. 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 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 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.
 */

/*
  This software allows the selective suspending and enabling of usb ports on a hub.
  Provide it the uhub device eg. /dev/usb2 the port on the hub to which the device
  is attached and the action.
  Use usbdevs -v to find the hub and port number
  eg. ./usb_power /dev/usb4 4 suspend

  NOTE: Using the disable action is not recommended.  suspend should achieve
  the desired result.
*/

#include <dev/usb/usb.h>
#include <sys/ioctl.h>

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define PORT_DISABLE 0
#define PORT_ENABLE 1
#define PORT_SUSPEND 2

int
main(int argc, char *argv[])
{
	usb_device_descriptor_t usbdd;
	struct usb_ctl_request req;
	int fd, ret, port_num, dev_address;
	int enable = 0;

	if(argc != 4)
	{
		printf("usage: %s uhub_device port_num [enable|suspend|disable]\n", argv[0]);
		return 1;
	}

	if((fd = open(argv[1], O_RDWR, 0)) == -1)
	{
		printf("Can't open device %s!\n", argv[1]);
		return 1;
	}

	port_num = atoi(argv[2]);
	dev_address = 1;	// why? magic?

	if(strncmp("enable", argv[3], 6) == 0)
	{
		enable = PORT_ENABLE;
	}
	else if(strncmp("disable", argv[3], 7) == 0)
	{
		enable = PORT_DISABLE;
	}
	else if(strncmp("suspend", argv[3], 7) == 0)
	{
		enable = PORT_SUSPEND;
	}

	switch(enable)
	{
		case PORT_ENABLE:
			// power up
			bzero(&req, sizeof(req));
			req.ucr_addr = dev_address;
			req.ucr_request.bmRequestType = UT_WRITE_CLASS_OTHER;
			req.ucr_request.bRequest = UR_SET_FEATURE;	// clear a feature
			USETW(req.ucr_request.wIndex, port_num); // usb port number
			USETW(req.ucr_request.wLength, 0);
			USETW(req.ucr_request.wValue, UHF_PORT_POWER);
			if (ioctl(fd, USB_REQUEST, &req) != 0) {
				printf("ioctl USB_REQUEST UR_CLEAR_FEATURE UHF_PORT_POWER failed\n");
				close(fd);
				exit(EXIT_FAILURE);
			}
	
			// resume
			bzero(&req, sizeof(req));
			req.ucr_addr = dev_address;
			req.ucr_request.bmRequestType = UT_WRITE_CLASS_OTHER;
			req.ucr_request.bRequest = UR_CLEAR_FEATURE;	// set a feature
			USETW(req.ucr_request.wIndex, port_num); // usb port number
			USETW(req.ucr_request.wLength, 0);
			USETW(req.ucr_request.wValue, UHF_PORT_SUSPEND);
			if (ioctl(fd, USB_REQUEST, &req) != 0) {
				printf("ioctl USB_REQUEST UR_SET_FEATURE UHF_PORT_SUSPEND failed\n");
				close(fd);
				exit(EXIT_FAILURE);
			}
	
			// enable
			bzero(&req, sizeof(req));
			req.ucr_addr = dev_address;
			req.ucr_request.bmRequestType = UT_WRITE_CLASS_OTHER;
			req.ucr_request.bRequest = UR_SET_FEATURE;	// set a feature
			USETW(req.ucr_request.wIndex, port_num); // usb port number
			USETW(req.ucr_request.wLength, 0);
			USETW(req.ucr_request.wValue, UHF_PORT_ENABLE);
			if (ioctl(fd, USB_REQUEST, &req) != 0) {
				printf("ioctl USB_REQUEST UR_SET_FEATURE UHF_PORT_ENABLE failed\n");
				close(fd);
				exit(EXIT_FAILURE);
			}
	
			// reset
			bzero(&req, sizeof(req));
			req.ucr_addr = dev_address;
			req.ucr_request.bmRequestType = UT_WRITE_CLASS_OTHER;
			req.ucr_request.bRequest = UR_SET_FEATURE;	// set a feature
			USETW(req.ucr_request.wIndex, port_num); // usb port number
			USETW(req.ucr_request.wLength, 0);
			USETW(req.ucr_request.wValue, UHF_PORT_RESET);
			if (ioctl(fd, USB_REQUEST, &req) != 0) {
				printf("ioctl USB_REQUEST UR_SET_FEATURE UHF_PORT_RESET failed\n");
				close(fd);
				exit(EXIT_FAILURE);
			}
			break;
		case PORT_SUSPEND:
			// suspend the port
			bzero(&req, sizeof(req));
			req.ucr_addr = dev_address;
			req.ucr_request.bmRequestType = UT_WRITE_CLASS_OTHER;
			req.ucr_request.bRequest = UR_SET_FEATURE;	// set a feature
			USETW(req.ucr_request.wIndex, port_num); // usb port number
			USETW(req.ucr_request.wLength, 0);
			USETW(req.ucr_request.wValue, UHF_PORT_SUSPEND);
			if (ioctl(fd, USB_REQUEST, &req) != 0) {
				printf("ioctl USB_REQUEST UR_SET_FEATURE UHF_PORT_SUSPEND failed\n");
				close(fd);
				exit(EXIT_FAILURE);
			}
			break;
		case PORT_DISABLE:
			printf("WARNING! Only use disable if suspend doesn't work!\n");
			// disable the port
			bzero(&req, sizeof(req));
			req.ucr_addr = dev_address; // device address
			req.ucr_request.bmRequestType = UT_WRITE_CLASS_OTHER;
			req.ucr_request.bRequest = UR_CLEAR_FEATURE;	// clear a feature
			USETW(req.ucr_request.wIndex, port_num); // usb port number
			USETW(req.ucr_request.wLength, 0);
			USETW(req.ucr_request.wValue, UHF_PORT_ENABLE);
			if (ioctl(fd, USB_REQUEST, &req) != 0) {
				printf("ioctl USB_REQUEST UHF_PORT_ENABLE failed\n");
				close(fd);
				exit(EXIT_FAILURE);
			}
			break;
		default:
			printf("Unknown action: %d", enable);
			break;
	}

	close(fd);

	return 0;
}
