#include <ddk.h>
#include <linux/errno.h>
#include <mutex.h>
#include <pci.h>
#include <syscall.h>

#include "acpi.h"
#include "acpi_bus.h"


#define PREFIX "ACPI: "


struct acpi_handle_node {
    struct list_head node;
    ACPI_HANDLE handle;
};

static const struct acpi_device_ids root_device_ids[] = {
    {"PNP0A03", 0},
    {"", 0},
};

LIST_HEAD(acpi_pci_roots);


/**
 * acpi_is_root_bridge - determine whether an ACPI CA node is a PCI root bridge
 * @handle - the ACPI CA node in question.
 *
 * Note: we could make this API take a struct acpi_device * instead, but
 * for now, it's more convenient to operate on an acpi_handle.
 */
int acpi_is_root_bridge(ACPI_HANDLE handle)
{
    int ret;
    struct acpi_device *device;

    ret = acpi_bus_get_device(handle, &device);
    if (ret)
        return 0;

    ret = acpi_match_device_ids(device, root_device_ids);
    if (ret)
        return 0;
    else
        return 1;
}


struct acpi_pci_root *acpi_pci_find_root(ACPI_HANDLE handle)
{
    struct acpi_pci_root *root;

    list_for_each_entry(root, &acpi_pci_roots, node) {
        if (root->device->handle == handle)
            return root;
    }
    return NULL;
}


/**
 * acpi_get_pci_dev - convert ACPI CA handle to struct pci_dev
 * @handle: the handle in question
 *
 * Given an ACPI CA handle, the desired PCI device is located in the
 * list of PCI devices.
 *
 * If the device is found, its reference count is increased and this
 * function returns a pointer to its data structure.  The caller must
 * decrement the reference count by calling pci_dev_put().
 * If no device is found, %NULL is returned.
 */
struct pci_dev *acpi_get_pci_dev(ACPI_HANDLE handle)
{
    int dev, fn;
    unsigned long long adr;
    ACPI_STATUS status;
    ACPI_HANDLE phandle;
    struct pci_bus *pbus;
    struct pci_dev *pdev = NULL;
    struct acpi_handle_node *node, *tmp;
    struct acpi_pci_root *root;
    LIST_HEAD(device_list);

    /*
     * Walk up the ACPI CA namespace until we reach a PCI root bridge.
     */
    phandle = handle;
    while (!acpi_is_root_bridge(phandle)) {
        node = kzalloc(sizeof(struct acpi_handle_node), GFP_KERNEL);
        if (!node)
            goto out;

        INIT_LIST_HEAD(&node->node);
        node->handle = phandle;
        list_add(&node->node, &device_list);

        status = AcpiGetParent(phandle, &phandle);
        if (ACPI_FAILURE(status))
            goto out;
    }

    root = acpi_pci_find_root(phandle);
    if (!root)
        goto out;

    pbus = root->bus;

    /*
     * Now, walk back down the PCI device tree until we return to our
     * original handle. Assumes that everything between the PCI root
     * bridge and the device we're looking for must be a P2P bridge.
     */
    list_for_each_entry(node, &device_list, node) {
        ACPI_HANDLE hnd = node->handle;
        status = acpi_evaluate_integer(hnd, "_ADR", NULL, &adr);
        if (ACPI_FAILURE(status))
            goto out;
        dev = (adr >> 16) & 0xffff;
        fn  = adr & 0xffff;

        pdev = pci_get_slot(pbus, PCI_DEVFN(dev, fn));
        if (!pdev || hnd == handle)
            break;

        pbus = pdev->subordinate;
//        pci_dev_put(pdev);

        /*
         * This function may be called for a non-PCI device that has a
         * PCI parent (eg. a disk under a PCI SATA controller).  In that
         * case pdev->subordinate will be NULL for the parent.
         */
        if (!pbus) {
            dbgprintf("Not a PCI-to-PCI bridge\n");
            pdev = NULL;
            break;
        }
    }
out:
    list_for_each_entry_safe(node, tmp, &device_list, node)
        kfree(node);

    return pdev;
}


static void print_bus_irqs(struct pci_bus *bus)
{
    struct pci_dev *dev;

    list_for_each_entry(dev, &bus->devices, bus_list)
    {
        if(dev->pin)
        {
            dbgprintf("PCI_%x_%x bus:%d devfn: %x pin %d bios irq: %d acpi irq: %d\n",
                       dev->vendor, dev->device, dev->busnr, dev->devfn,
                       dev->pin, dev->irq, acpi_get_irq(dev));
        };
    };
}

void print_pci_irqs()
{
    struct acpi_pci_root *root;

    list_for_each_entry(root, &acpi_pci_roots, node)
    {
        struct pci_bus *pbus, *tbus;
        struct pci_dev *dev;

        pbus = root->bus;

        list_for_each_entry(dev, &pbus->devices, bus_list)
        {
            if(dev->pin)
                dbgprintf("PCI_%x_%x bus:%d devfn: %x pin %d bios irq: %d acpi irq: %d\n",
                          dev->vendor, dev->device, dev->busnr, dev->devfn,
                          dev->pin, dev->irq, acpi_get_irq(dev));
        };

        list_for_each_entry(tbus, &pbus->children, node)
        {
            print_bus_irqs(tbus);
        };
    }
};