#ifndef _SYS_PCI_H
#define _SYS_PCI_H

#include <inttypes.h>
#include <sys/io.h>

#define MAX_PCI_FUNC		  8
#define MAX_PCI_DEVICES		 32
#define MAX_PCI_BUSES		256
#define LINUX_KERNEL_MODULE_SIZE 64
#define PCI_VENDOR_NAME_SIZE	256
#define PCI_PRODUCT_NAME_SIZE	256
#define PCI_CLASS_NAME_SIZE	256
#define MAX_KERNEL_MODULES_PER_PCI_DEVICE 10
#define MAX_PCI_CLASSES		256

typedef uint32_t pciaddr_t;

enum {
    ENOPCIIDS = 100,
    ENOMODULESPCIMAP
};

/* a structure for extended pci information */
/* XXX: use pointers for these? */
struct pci_dev_info {
    char vendor_name[PCI_VENDOR_NAME_SIZE];
    char product_name[PCI_PRODUCT_NAME_SIZE];
    char linux_kernel_module[LINUX_KERNEL_MODULE_SIZE]
	[MAX_KERNEL_MODULES_PER_PCI_DEVICE];
    int linux_kernel_module_count;
    char class_name[PCI_CLASS_NAME_SIZE];	/* The most precise class name */
    char category_name[PCI_CLASS_NAME_SIZE];	/*The general category */
    uint8_t irq;
    uint8_t latency;
};

/* PCI device (really, function) */
struct pci_device {
    union {
	struct {
	    uint16_t vendor;
	    uint16_t product;
	    uint16_t sub_vendor;
	    uint16_t sub_product;
	    uint8_t revision;
	    uint8_t class[3];
	};
	struct {
	    uint32_t vid_did;
	    uint32_t svid_sdid;
	    uint32_t rid_class;
	};
    };
    struct pci_dev_info *dev_info;
    struct pci_device *next;
};

/* PCI device ("slot") structure */
struct pci_slot {
    struct pci_device *func[MAX_PCI_FUNC];
};

/* PCI bus structure */
struct pci_bus {
    struct pci_slot *slot[MAX_PCI_DEVICES];
};

/* PCI domain structure */
struct pci_domain {
    struct pci_bus *bus[MAX_PCI_BUSES];
};

/* Iterate over a PCI domain */
#define for_each_pci_func(funcp, domain) \
  for (int __pci_bus = 0; __pci_bus < MAX_PCI_BUSES; __pci_bus++) \
    if ((domain)->bus[__pci_bus]) \
      for (int __pci_slot = 0; __pci_slot < MAX_PCI_DEVICES; __pci_slot++) \
	if ((domain)->bus[__pci_bus]->slot[__pci_slot]) \
	  for (int __pci_func = 0; __pci_func < MAX_PCI_FUNC; __pci_func++) \
	    if (((funcp) = (domain)->bus[__pci_bus]->slot[__pci_slot]-> \
		 func[__pci_func]))

#define for_each_pci_func3(funcp, domain, addr) \
  for (int __pci_bus = 0; __pci_bus < MAX_PCI_BUSES; __pci_bus++) \
    if ((domain)->bus[__pci_bus]) \
      for (int __pci_slot = 0; __pci_slot < MAX_PCI_DEVICES; __pci_slot++) \
	if ((domain)->bus[__pci_bus]->slot[__pci_slot]) \
	  for (int __pci_func = 0; __pci_func < MAX_PCI_FUNC; __pci_func++) \
	    if (((addr) = pci_mkaddr(__pci_bus, __pci_slot, __pci_func, 0)), \
		((funcp) = (domain)->bus[__pci_bus]->slot[__pci_slot]-> \
		 func[__pci_func]))

struct match {
    struct match *next;
    uint32_t did;
    uint32_t did_mask;
    uint32_t sid;
    uint32_t sid_mask;
    uint8_t rid_min, rid_max;
    char *filename;
};

static inline pciaddr_t pci_mkaddr(uint32_t bus, uint32_t dev,
				   uint32_t func, uint32_t reg)
{
    return 0x80000000 | ((bus & 0xff) << 16) | ((dev & 0x1f) << 11) |
	((func & 0x07) << 8) | (reg & 0xff);
}

static inline int pci_bus(pciaddr_t addr)
{
    return (addr >> 16) & 0xff;
}

static inline int pci_dev(pciaddr_t addr)
{
    return (addr >> 11) & 0x1f;
}

static inline int pci_func(pciaddr_t addr)
{
    return (addr >> 8) & 0x07;
}

enum pci_config_type {
    PCI_CFG_NONE = -1,		/* badness */
    PCI_CFG_AUTO = 0,		/* autodetect */
    PCI_CFG_TYPE1 = 1,
    PCI_CFG_TYPE2 = 2,
    PCI_CFG_BIOS = 3,
};

enum pci_config_type pci_set_config_type(enum pci_config_type);

uint8_t pci_readb(pciaddr_t);
uint16_t pci_readw(pciaddr_t);
uint32_t pci_readl(pciaddr_t);
void pci_writeb(uint8_t, pciaddr_t);
void pci_writew(uint16_t, pciaddr_t);
void pci_writel(uint32_t, pciaddr_t);

struct pci_domain *pci_scan(void);
void free_pci_domain(struct pci_domain *domain);
struct match *find_pci_device(const struct pci_domain *pci_domain,
			      struct match *list);
int get_name_from_pci_ids(struct pci_domain *pci_domain, char *pciids_path);
int get_module_name_from_pcimap(struct pci_domain *pci_domain,
				char *modules_pcimap_path);
int get_class_name_from_pci_ids(struct pci_domain *pci_domain,
				char *pciids_path);
void gather_additional_pci_config(struct pci_domain *domain);
#endif /* _SYS_PCI_H */
