/*****************************************************************************/

/*
 *      devtree.c  --  USB device tree.
 *
 *      Copyright (C) 1999 Thomas Sailer, sailer@ife.ee.ethz.ch
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Please note that the GPL allows you to use the driver, NOT the radio.
 *  In order to use the radio, you need a license from the communications
 *  authority of your country.
 *
 */

/*****************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

#include "devtree.h"

/* ---------------------------------------------------------------------- */

LIST_HEAD(usbbuslist);

/* ---------------------------------------------------------------------- */

static void freedev(struct usbdevnode *dev)
{
	free(dev);
}

static void freebus(struct usbbusnode *bus)
{
	free(bus);
}

/* ---------------------------------------------------------------------- */

static void markdel(struct list_head *list)
{
	struct usbdevnode *dev;
	struct list_head *list2;

	for (list2 = list->next; list2 != list; list2 = list2->next) {
		dev = list_entry(list2, struct usbdevnode, list);
		dev->flags |= USBFLG_DELETED;
		markdel(&dev->childlist);
	}
}

void devtree_markdeleted(void)
{
	struct usbbusnode *bus;
	struct list_head *list;
	
	for(list = usbbuslist.next; list != &usbbuslist; list = list->next) {
		bus = list_entry(list, struct usbbusnode, list);
		markdel(&bus->childlist);
	}
}

struct usbbusnode *devtree_findbus(unsigned int busn)
{
	struct usbbusnode *bus;
	struct list_head *list;
	
	for(list = usbbuslist.next; list != &usbbuslist; list = list->next) {
		bus = list_entry(list, struct usbbusnode, list);
		if (bus->busnum == busn)
			return bus;
	}
	return NULL;
}

static struct usbdevnode *findsubdevice(struct list_head *list, unsigned int devn)
{
	struct usbdevnode *dev, *dev2;
	struct list_head *list2;

	for (list2 = list->next; list2 != list; list2 = list2->next) {
		dev = list_entry(list2, struct usbdevnode, list);
		if (dev->devnum == devn)
			return dev;
		dev2 = findsubdevice(&dev->childlist, devn);
		if (dev2)
			return dev2;
	}
	return NULL;
}

struct usbdevnode *devtree_finddevice(struct usbbusnode *bus, unsigned int devn)
{
	return findsubdevice(&bus->childlist, devn);
}

/* ---------------------------------------------------------------------- */

void devtree_parsedevfile(int fd)
{
	char buf[16384];
	char *start, *end, *lineend, *cp;
	int ret;
	unsigned int devnum = 0, busnum = 0, parentdevnum = 0, level = 0;
	unsigned int class = 0xff, vendor = 0xffff, prodid = 0xffff, speed = 0;
	struct usbbusnode *bus;
	struct usbdevnode *dev, *dev2;

	devtree_markdeleted();
	if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
		lprintf(0, "lseek: %s (%d)\n", strerror(errno), errno);
	ret = read(fd, buf, sizeof(buf)-1);
	if (ret == -1)
		lprintf(0, "read: %s (%d)\n", strerror(errno), errno);
	end = buf + ret;
	*end = 0;
	start = buf;
	while (start < end) {
		lineend = strchr(start, '\n');
		if (!lineend)
			break;
		*lineend = 0;
		switch (start[0]) {
		case 'T':  /* topology line */
			if ((cp = strstr(start, "Dev#="))) {
				devnum = strtoul(cp + 5, NULL, 0);
			} else
				devnum = 0;
			if ((cp = strstr(start, "Bus="))) {
				busnum = strtoul(cp + 4, NULL, 0);
			} else
				busnum = 0;
			if ((cp = strstr(start, "Prnt="))) {
				parentdevnum = strtoul(cp + 5, NULL, 0);
			} else
				parentdevnum = 0;
			if ((cp = strstr(start, "Lev="))) {
				level = strtoul(cp + 4, NULL, 0);
			} else
				level = 0;
			if (strstr(start, "Spd=1.5"))
				speed = 1;
			else if (strstr(start, "Spd=12"))
				speed = 2;
			else 
				speed = 0;
			break;

		case 'D':
			if ((cp = strstr(start, "Cls="))) {
				class = strtoul(cp + 4, NULL, 16);
			} else
				class = 0xff;
			break;

		case 'P':
			if ((cp = strstr(start, "Vendor="))) {
				vendor = strtoul(cp + 7, NULL, 16);
			} else
				vendor = 0xffff;
			if ((cp = strstr(start, "ProdID="))) {
				prodid = strtoul(cp + 7, NULL, 16);
			} else
				prodid = 0xffff;
			/* print device */
#if 0
			printf("Device %3d Vendor %04x Product ID %04x Class %02x Speed %s\n",
			       devnum, vendor, prodid, class, speed == 2 ? "12 MBPS" : speed == 1 ? "1.5 MBPS" : "unknown");
#endif
			if (!(bus = devtree_findbus(busnum))) {
				if (!(bus = malloc(sizeof(struct usbbusnode))))
					lprintf(0, "Out of memory\n");
				bus->busnum = busnum;
				bus->flags = USBFLG_NEW;
				INIT_LIST_HEAD(&bus->childlist);
				list_add_tail(&bus->list, &usbbuslist);
			} else {
				bus->flags &= ~USBFLG_DELETED;
			}
			if (!(dev = devtree_finddevice(bus, devnum)) || dev->vendorid != vendor || dev->productid != prodid) {
				if (!(dev = malloc(sizeof(struct usbdevnode))))
					lprintf(0, "Out of memory\n");
				dev->devnum = devnum;
				dev->flags = USBFLG_NEW;
				dev->bus = bus;
				dev->vendorid = vendor;
				dev->productid = prodid;
				INIT_LIST_HEAD(&dev->childlist);
				if (level == 0 && parentdevnum == 0) {
					list_add_tail(&dev->list, &bus->childlist);
					dev->parent = NULL;
				} else {
					if (!(dev2 = devtree_finddevice(bus, parentdevnum)))
						lprintf(0, "Bus %d Device %d Parent Device %d not found\n", busnum, devnum, parentdevnum);
					dev->parent = dev2;
					list_add_tail(&dev->list, &dev2->childlist);
				}
			} else {
				dev->flags &= ~USBFLG_DELETED;
			}
			break;

		default:
			break;
		}
#if 0
		printf("line: %s\n", start);
#endif
		start = lineend + 1;
	}
}

/* ---------------------------------------------------------------------- */

static void deletetree(struct list_head *list, unsigned int force)
{
	struct usbdevnode *dev;
	struct list_head *list2;

	for (list2 = list->next; list2 != list;) {
		dev = list_entry(list2, struct usbdevnode, list);
		list2 = list2->next;
		deletetree(&dev->childlist, force || dev->flags & USBFLG_DELETED);
		if (!force && !(dev->flags & USBFLG_DELETED))
			continue;
		list_del(&dev->list);
		INIT_LIST_HEAD(&dev->list);
		devtree_devdisconnect(dev);
		freedev(dev);
	}
}

static void newtree(struct list_head *list)
{
	struct usbdevnode *dev;
	struct list_head *list2;

	for (list2 = list->next; list2 != list; list2 = list2->next) {
		dev = list_entry(list2, struct usbdevnode, list);
		if (dev->flags & USBFLG_NEW)
			devtree_devconnect(dev);
		dev->flags &= ~USBFLG_NEW;
		newtree(&dev->childlist);
	}
}

void devtree_processchanges(void)
{
	struct list_head *list;
	struct usbbusnode *bus;

	for (list = usbbuslist.next; list != &usbbuslist;) {
		bus = list_entry(list, struct usbbusnode, list);
		list = list->next;
		deletetree(&bus->childlist, bus->flags & USBFLG_DELETED);
		if (!(bus->flags & USBFLG_DELETED))
			continue;
		list_del(&bus->list);
		INIT_LIST_HEAD(&bus->list);
		devtree_busdisconnect(bus);
		freebus(bus);
	}
	for (list = usbbuslist.next; list != &usbbuslist; list = list->next) {
		bus = list_entry(list, struct usbbusnode, list);
		if (bus->flags & USBFLG_NEW)
			devtree_busconnect(bus);
		bus->flags &= ~USBFLG_NEW;
		newtree(&bus->childlist);
	}
}

/* ---------------------------------------------------------------------- */

static void dumpdevlist(struct list_head *list, unsigned int level, unsigned int mask)
{
	struct usbdevnode *dev;
	struct list_head *list2;
	char buf[512];
	char *cp;
	unsigned int i;

	for (list2 = list->next; list2 != list; ) {
		dev = list_entry(list2, struct usbdevnode, list);
		list2 = list2->next;
		for (cp = buf, i = 0; i < level; i++) {
			*cp++ = (mask & (1 << i)) ? '|' : ' ';
			*cp++ = ' ';
		}
		if (list2 != list) {
			mask |= (1 << level);
			*cp++ = '|';
		} else {
			mask &= ~(1 << level);
			*cp++ = '`';
		}
		*cp++ = '-';
		snprintf(cp, buf + sizeof(buf) - cp, "Dev# %3d Vendor 0x%04x Product 0x%04x",
			 dev->devnum, dev->vendorid, dev->productid);
		lprintf(1, "%s\n", buf);
		dumpdevlist(&dev->childlist, level+1, mask);
	}
}

void devtree_dump(void)
{
	struct list_head *list;
	struct usbbusnode *bus;

	for (list = usbbuslist.next; list != &usbbuslist; list = list->next) {
		bus = list_entry(list, struct usbbusnode, list);
		lprintf(1, "Bus# %2d\n", bus->busnum);
		dumpdevlist(&bus->childlist, 0, 0);
	}
}
