/*---------------------------------------------------------------------------*\

    FILE....: VPBSCAN.C
    TYPE....: C Console program
    AUTHOR..: Jean-Michel Dault
    DATE....: 19/08/03

    This program scans the PCI bus for OpenSwitch boards and prints info.


         Voicetronix Voice Processing Board (VPB) Software
         Copyright (C) 1999-2006 Voicetronix www.voicetronix.com.au

         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.1 of the License, or (at your option) any later version.

         This library 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
         Lesser General Public License for more details.

         You should have received a copy of the GNU General Public
         License along with this library; if not, write to the Free Software
         Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
         MA  02110-1301  USA

\*---------------------------------------------------------------------------*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>

#include <pci/pci.h>

#define V6PCI 0x20365654
#define V12PCI 0x31325654
#define V4PCI 0x56345654

#define MAX_BOARDS	12

static struct pci_access *pacc;

struct device {
	struct device *next;
	struct pci_dev *dev;
	unsigned int config_cnt;
	unsigned char config[256];
};

static struct device *first_dev;
//static int verbose;                     /* Show detailed information */
static int show_hex;			/* Show contents of config space as hexadecimal numbers */
//static struct pci_filter filter;	/* Device filter */

static void __attribute__((noreturn)) die(const char *msg, ...)
{
	va_list args;

	va_start(args, msg);
	fputs("lspci: ", stderr);
	vfprintf(stderr, msg, args);
	fputc('\n', stderr);
	exit(1);
}

// alias for die() since pciutils callbacks are not const correct
static void __attribute__((noreturn)) libpci_error(char *msg, ...)
{
	va_list args;

	va_start(args, msg);
	fputs("lspci: ", stderr);
	vfprintf(stderr, msg, args);
	fputc('\n', stderr);
	exit(1);
}

static void *xmalloc(unsigned int howmuch)
{
	void *p = malloc(howmuch);
	if (!p)
		die("Unable to allocate %d bytes of memory", howmuch);
	return p;
}


static struct device *scan_device(struct pci_dev *p)
{
	int how_much = (show_hex > 2) ? 256 : 64;
	struct device *d;

	/*
	if (!pci_filter_match(&filter, p))
	return NULL;
	*/
	d = xmalloc(sizeof(struct device));
	memset(d, 0, sizeof(struct device));
	d->dev = p;
	if (!pci_read_block(p, 0, d->config, how_much))
		die("Unable to read %d bytes of configuration space.", how_much);
	if (how_much < 128 && (d->config[PCI_HEADER_TYPE] & 0x7f) == PCI_HEADER_TYPE_CARDBUS){
		/* For cardbus bridges, we need to fetch 64 bytes more to get the full standard header... */
		if (!pci_read_block(p, 64, d->config+64, 64))
			die("Unable to read cardbus bridge extension data.");
		how_much = 128;
	}
	d->config_cnt = how_much;
	pci_setup_cache(p, d->config, d->config_cnt);
	pci_fill_info(p, PCI_FILL_IDENT | PCI_FILL_IRQ | PCI_FILL_BASES | PCI_FILL_ROM_BASE | PCI_FILL_SIZES);
	return d;
}

static void scan_devices(void)
{
	struct device *d;
	struct pci_dev *p;

	pci_scan_bus(pacc);
	for(p=pacc->devices; p; p=p->next){
		if((d = scan_device(p)) != NULL){
			d->next = first_dev;
			first_dev = d;
		}
	}
}

/* Config space accesses */

static inline uint8_t get_conf_byte(struct device *d, unsigned int pos)
{
	return d->config[pos];
}

#if 0
static uint16_t get_conf_word(struct device *d, unsigned int pos)
{
	return d->config[pos] | (d->config[pos+1] << 8);
}

static u32 get_conf_long(struct device *d, unsigned int pos)
{
	return d->config[pos] | (d->config[pos+1] << 8) | (d->config[pos+2] << 16) | (d->config[pos+3] << 24);
}
#endif

/* Sorting */

static int compare_them(const void *A, const void *B)
{
	const struct pci_dev *a = (*(struct device* const*)A)->dev;
	const struct pci_dev *b = (*(struct device* const*)B)->dev;

	if (a->bus < b->bus)
		return -1;
	if (a->bus > b->bus)
		return 1;
	if (a->dev < b->dev)
		return -1;
	if (a->dev > b->dev)
		return 1;
	if (a->func < b->func)
		return -1;
	if (a->func > b->func)
		return 1;
	return 0;
}

static void sort_them(void)
{
	struct device **index, **h, **last_dev;
	int cnt;
	struct device *d;

	cnt = 0;
	for(d=first_dev; d; d=d->next)
		cnt++;
	h = index = alloca(sizeof(struct device *) * cnt);
	for(d=first_dev; d; d=d->next)
		*h++ = d;
	qsort(index, cnt, sizeof(struct device *), compare_them);
	last_dev = &first_dev;
	h = index;
	while (cnt--){
		*last_dev = *h;
		last_dev = &(*h)->next;
		h++;
	}
	*last_dev = NULL;
}

#if 0
static void show_terse(struct device *d)
{
	int c;
	struct pci_dev *p = d->dev;
	char classbuf[128], devbuf[128];

	printf("%02x:%02x.%x %s: %s",
			p->bus,
			p->dev,
			p->func,
			pci_lookup_name(pacc, classbuf, sizeof(classbuf),
					PCI_LOOKUP_CLASS,
					get_conf_word(d, PCI_CLASS_DEVICE), 0, 0, 0),
					pci_lookup_name(pacc, devbuf, sizeof(devbuf),
							PCI_LOOKUP_VENDOR | PCI_LOOKUP_DEVICE,
							p->vendor_id, p->device_id, 0, 0));
	c = get_conf_byte(d, PCI_REVISION_ID);
	if(c) printf(" (rev %02x)", c);
	if (verbose){
		char *x;
		c = get_conf_byte(d, PCI_CLASS_PROG);
		x = pci_lookup_name(pacc, devbuf, sizeof(devbuf),
					PCI_LOOKUP_PROGIF,
					get_conf_word(d, PCI_CLASS_DEVICE), c, 0, 0);
		if (c || x){
			printf(" (prog-if %02x", c);
			if (x)
				printf(" [%s]", x);
			putchar(')');
		}
	}
	putchar('\n');
}
#endif

int main(void)
{
	struct device *d;
	struct pci_dev *dev;
	u32 s[MAX_BOARDS];
	int numboards=0;

	pacc = pci_alloc();		/* Get the pci_access structure */
	pacc->error = libpci_error;
	/* Set all options you want -- here we stick with the defaults */
	pci_init(pacc);		/* Initialize the PCI library */

	scan_devices();
	sort_them();

	for(d=first_dev; d; d=d->next) {
		dev=d->dev;
		pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_BASES);	/* Fill in header info we need */
		if(dev->vendor_id==0x10b5 && dev->device_id==0x9050) {
			s[numboards] = pci_read_long(dev, 0x2c);	/* Read config register directly */
			if(s[numboards]==V6PCI) {
				printf("CARD%d:V6PCI:",numboards+1);
			} else if(s[numboards]==V12PCI) {
				printf("CARD%d:V12PCI:",numboards+1);
			} else if(s[numboards]==V4PCI) {
				printf("CARD%d:V4PCI:",numboards+1);
			} else {
				printf("CARD%d:UNKNOWN:",numboards+1);
			}
			printf("irq=%d sub=%04x bus-dev:%02X:%02X\n", dev->irq,s[numboards],dev->bus,dev->dev);
			numboards++;
		}
		else if(dev->vendor_id==0x1923 && dev->device_id==0x0300) {
			printf("CARD%d:OPENPRI:",numboards+1);
			printf("irq=%d sub=%04x bus-dev:%02X:%02X\n", dev->irq,0,dev->bus,dev->dev);
			numboards++;
		}
		else if(dev->vendor_id==0x1923 && dev->device_id==0x0040) {
			printf("CARD%d:OPENPRIv2:",numboards+1);
			printf("irq=%d sub=%04x bus-dev:%02X:%02X\n", dev->irq,0,dev->bus,dev->dev);
			numboards++;
		}
		else if(dev->vendor_id==0x1923 && dev->device_id==0x0100) {
			printf("CARD%d:QUADOPENPRIv2:",numboards+1);
			printf("irq=%d sub=%04x bus-dev:%02X:%02X\n", dev->irq,0,dev->bus,dev->dev);
			numboards++;
		}
		else if(dev->vendor_id==0xe159 && dev->device_id==0x0001) {
			// e159:0001
			printf("CARD%d:OPENPCI:",numboards+1);
			printf("irq=%d sub=%04x bus-dev:%02X:%02X\n", dev->irq,0,dev->bus,dev->dev);
			numboards++;
		}
	}
	if(numboards==0) {
		printf("Cannot find any Voicetronix boards.\n");
		return 255;
        } else {
		printf("BOARDS:%d\n",numboards);
	}

	pci_cleanup(pacc);		/* Close everything */
	return 0;
}

