x86_pci.c 10.9 KB
/*
 * Copyright (C) 2014  René Kijewski  <rene.kijewski@fu-berlin.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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 Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/**
 * @ingroup     x86-irq
 * @{
 *
 * @file
 * @brief       PCI configuration and accessing.
 *
 * @author      René Kijewski <rene.kijewski@fu-berlin.de>
 *
 * @}
 */

#include "x86_memory.h"
#include "x86_pci.h"
#include "x86_pci_init.h"
#include "x86_pci_strings.h"
#include "x86_ports.h"

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

static struct x86_known_pci_device **known_pci_devices = NULL;
static unsigned num_known_pci_devices;

static void set_addr(unsigned bus, unsigned dev, unsigned fun, unsigned reg)
{
    unsigned addr = PCI_IO_ENABLE
                  | (bus << PCI_IO_SHIFT_BUS)
                  | (dev << PCI_IO_SHIFT_DEV)
                  | (fun << PCI_IO_SHIFT_FUN)
                  | (reg << PCI_IO_SHIFT_REG);
    outl(PCI_CONFIG_ADDRESS, addr);
}

uint32_t x86_pci_read(unsigned bus, unsigned dev, unsigned fun, unsigned reg)
{
    set_addr(bus, dev, fun, reg);
    return U32_PCItoH(inl(PCI_CONFIG_DATA));
}

uint8_t x86_pci_read8(unsigned bus, unsigned dev, unsigned fun, unsigned reg)
{
    set_addr(bus, dev, fun, reg);
    return U8_PCItoH(inb(PCI_CONFIG_DATA));
}

uint16_t x86_pci_read16(unsigned bus, unsigned dev, unsigned fun, unsigned reg)
{
    set_addr(bus, dev, fun, reg);
    return U16_PCItoH(inw(PCI_CONFIG_DATA));
}

static bool pci_vendor_id_valid(uint16_t vendor_id)
{
    return vendor_id != PCI_HEADER_VENDOR_ID_INVALID &&
           vendor_id != PCI_HEADER_VENDOR_ID_UNRESPONSIVE &&
           vendor_id != PCI_HEADER_VENDOR_ID_ABSENT;
}

void x86_pci_write(unsigned bus, unsigned dev, unsigned fun, unsigned reg, uint32_t datum)
{
    set_addr(bus, dev, fun, reg);
    outl(PCI_CONFIG_DATA, U32_HtoPCI(datum));
}

void x86_pci_write8(unsigned bus, unsigned dev, unsigned fun, unsigned reg, uint8_t datum)
{
    set_addr(bus, dev, fun, reg);
    outb(PCI_CONFIG_DATA, U8_HtoPCI(datum));
}

void x86_pci_write16(unsigned bus, unsigned dev, unsigned fun, unsigned reg, uint16_t datum)
{
    set_addr(bus, dev, fun, reg);
    outw(PCI_CONFIG_DATA, U16_HtoPCI(datum));
}

static unsigned pci_init_secondary_bus(unsigned bus, unsigned dev, unsigned fun)
{
    known_pci_devices[num_known_pci_devices - 1]->managed = true;

    /* TODO */
    printf("    TODO: pci_init_secondary_bus(0x%x, 0x%x, 0x%x)\n", bus, dev, fun);
    (void) bus;
    (void) dev;
    (void) fun;
    return 0;
}

static void pci_setup_ios(struct x86_known_pci_device *dev)
{
    /* § 6.2.5. (pp. 224) */

    unsigned bar_count = 0;
    unsigned bar_base;

    unsigned header_type = x86_pci_read_reg(0x0c, dev->bus, dev->dev, dev->fun).header_type & PCI_HEADER_TYPE_MASK;
    switch (header_type) {
        case PCI_HEADER_TYPE_GENERAL_DEVICE:
            bar_count = 6;
            bar_base = 0x10;
            break;
        case PCI_HEADER_TYPE_BRIDGE:
            bar_count = 2;
            bar_base = 0x10;
            break;
        default:
            printf("    Cannot configure header_type == 0x%02x, yet.\n", header_type);
            return;
    }

    for (unsigned bar_num = 0; bar_num < bar_count; ++bar_num) {
        uint32_t old_bar = x86_pci_read(dev->bus, dev->dev, dev->fun, bar_base + 4 * bar_num);
        if (old_bar == 0) {
            continue;
        }

        x86_pci_write(dev->bus, dev->dev, dev->fun, bar_base + 4 * bar_num, -1ul);
        uint32_t tmp_bar = x86_pci_read(dev->bus, dev->dev, dev->fun, bar_base + 4 * bar_num);
        x86_pci_write(dev->bus, dev->dev, dev->fun, bar_base + 4 * bar_num, old_bar);
        if ((old_bar & PCI_BAR_IO_SPACE) != (tmp_bar & PCI_BAR_IO_SPACE)) {
            /* cannot happen (?) */
            continue;
        }

        dev->io = realloc(dev->io, sizeof (*dev->io) * (dev->io_count + 1));
        struct x86_pci_io *io = calloc(1, sizeof *io);
        dev->io[dev->io_count] = io;
        io->bar_num = bar_num;
        ++dev->io_count;

        unsigned addr_offs = (tmp_bar & PCI_BAR_IO_SPACE) ? PCI_BAR_ADDR_OFFS_IO : PCI_BAR_ADDR_OFFS_MEM;
        uint32_t length_tmp = tmp_bar >> addr_offs;
        uint32_t length = 1 << addr_offs;
        while ((length_tmp & 1) == 0) {
            length <<= 1;
            length_tmp >>= 1;
        }
        io->length = length;

        if (tmp_bar & PCI_BAR_IO_SPACE) {
            io->type = PCI_IO_PORT;
            io->addr.port = old_bar & ~((1 << PCI_BAR_ADDR_OFFS_IO) - 1);
            printf("    BAR %u: I/O space, ports 0x%04x-0x%04x\n",
                   bar_num, io->addr.port, io->addr.port + length - 1);
        }
        else if ((old_bar & PCI_BAR_IO_SPACE) != PCI_BAR_SPACE_32 && (old_bar & PCI_BAR_IO_SPACE) != PCI_BAR_SPACE_64) {
            printf("    BAR %u: memory with unknown location 0x%x, ERROR!\n", bar_num, (old_bar >> 1) & 3);
        }
        else {
            uint32_t physical_start = old_bar & ~0xfff;
            void *ptr = x86_map_physical_pages(physical_start, (length + 0xfff) / 0x1000, PT_P | PT_G | PT_RW | PT_PWT | PT_PCD | PT_XD);
            if (!ptr) {
                io->type = PCI_IO_INVALID;
                printf("    BAR %u: memory, physical = 0x%08x-0x%08x, ERROR!\n",
                       bar_num, (unsigned) physical_start, physical_start + length - 1);
            }
            else {
                io->type = PCI_IO_MEM;
                io->addr.ptr = (char *) ptr + (old_bar & ~0xfff & ~((1 << PCI_BAR_ADDR_OFFS_MEM) - 1));
                printf("    BAR %u: memory, physical = 0x%08x-0x%08x, virtual = 0x%08x-0x%08x\n",
                       bar_num,
                       physical_start, physical_start + length - 1,
                       (unsigned) ptr, (unsigned) ((uintptr_t) ptr + length - 1));
            }
        }
    }
}

static void pci_find_on_bus(unsigned bus);

void x86_pci_set_irq(struct x86_known_pci_device *d, uint8_t irq_num)
{
    if (d->irq == irq_num) {
        return;
    }

    d->irq = irq_num;
    uint32_t old_3c = x86_pci_read(d->bus, d->dev, d->fun, 0x3c);
    x86_pci_write(d->bus, d->dev, d->fun, 0x3c, (old_3c & ~0xff) | d->irq);

    printf("    IRQ: new = %u, old = %u\n", d->irq, old_3c & 0xff);
}

static void pci_find_function(unsigned bus, unsigned dev, unsigned fun)
{
    union pci_reg_0x00 vendor = x86_pci_read_reg(0x00, bus, dev, fun);
    if (!pci_vendor_id_valid(vendor.vendor_id)) {
        return;
    }

    union pci_reg_0x08 class = x86_pci_read_reg(0x08, bus, dev, fun);
    const char *baseclass_name, *subclass_name = x86_pci_subclass_to_string(class.baseclass,
                                                                            class.subclass,
                                                                            class.programming_interface,
                                                                            &baseclass_name);
    const char *vendor_name, *device_name = x86_pci_device_id_to_string(vendor.vendor_id, vendor.device_id, &vendor_name);
    printf("  %02x:%02x.%x \"%s\": \"%s\" (%s: %s, rev: %02hhx)\n",
           bus, dev, fun, vendor_name, device_name, baseclass_name, subclass_name, class.revision_id);

    /* cppcheck-suppress memleakOnRealloc
     * (reason: TODO this is a bug) */
    known_pci_devices = realloc(known_pci_devices, sizeof (*known_pci_devices) * (num_known_pci_devices + 1));
    struct x86_known_pci_device *d = calloc(1, sizeof *d);
    known_pci_devices[num_known_pci_devices] = d;
    ++num_known_pci_devices;

    d->bus = bus;
    d->dev = dev;
    d->fun = fun;
    d->vendor = vendor;
    d->class = class;
    d->managed = false;

    uint32_t old_3c = x86_pci_read(bus, dev, fun, 0x3c);
    if (old_3c & 0xff) {
        d->irq = PCI_IRQ_DEFAULT;
        x86_pci_write(bus, dev, fun, 0x3c, (old_3c & ~0xff) | d->irq);
        printf("    IRQ: new = %u, old = %u\n", d->irq, old_3c & 0xff);
    }

    pci_setup_ios(d);

    if (class.baseclass == 0x06 && class.subclass == 0x04) {
        unsigned secondary_bus = pci_init_secondary_bus(bus, dev, fun);
        if (secondary_bus != 0) {
            pci_find_on_bus(secondary_bus);
        }
    }
}

static void pci_find_on_bus(unsigned bus)
{
    for (unsigned dev = 0; dev < PCI_DEV_COUNT; ++dev) {
        if (!pci_vendor_id_valid(x86_pci_read_reg(0x00, bus, dev, 0).vendor_id)) {
            continue;
        }

        if (x86_pci_read_reg(0x0c, bus, dev, 0).header_type & PCI_HEADER_TYPE_MULTI_FUNCTION) {
            for (unsigned fun = 0; fun < PCI_FUN_COUNT; ++fun) {
                pci_find_function(bus, dev, fun);
            }
        }
        else {
            pci_find_function(bus, dev, 0);
        }
    }
}

static void pci_find(void)
{
    if (x86_pci_read_reg(0x0c, 0, 0, 0).header_type & PCI_HEADER_TYPE_MULTI_FUNCTION) {
        for (unsigned fun = 0; fun < PCI_FUN_COUNT; ++fun) {
            if (pci_vendor_id_valid(x86_pci_read_reg(0x00, 0, 0, fun).vendor_id)) {
                pci_find_on_bus(fun);
            }
        }
    }
    else {
        pci_find_on_bus(0);
    }
}

static void irq_handler(uint8_t irq_num)
{
    for (unsigned i = 0; i < num_known_pci_devices; ++i) {
        struct x86_known_pci_device *d = known_pci_devices[i];
        if (d->managed && d->irq_handler && d->irq == irq_num) {
            d->irq_handler(d);
        }
    }
}

void x86_init_pci(void)
{
    x86_pic_set_handler(PCI_IRQ_ACPI, irq_handler);
    x86_pic_enable_irq(PCI_IRQ_ACPI);
    x86_pic_set_handler(PCI_IRQ_NETWORKING, irq_handler);
    x86_pic_enable_irq(PCI_IRQ_NETWORKING);
    x86_pic_set_handler(PCI_IRQ_DEFAULT, irq_handler);
    x86_pic_enable_irq(PCI_IRQ_DEFAULT);
    x86_pic_set_handler(PCI_IRQ_USB, irq_handler);
    x86_pic_enable_irq(PCI_IRQ_USB);

    puts("Looking up PCI devices");
    pci_find();

    x86_init_pci_devices();
}

struct x86_known_pci_device **x86_enumerate_unmanaged_pci_devices(unsigned *index)
{
    while (*index < num_known_pci_devices) {
        struct x86_known_pci_device **result = &known_pci_devices[*index];
        ++*index;
        if (*result && !(**result).managed) {
            return result;
        }
    }
    return NULL;
}

const struct x86_known_pci_device *x86_enumerate_pci_devices(unsigned *index)
{
    while (*index < num_known_pci_devices) {
        struct x86_known_pci_device *result = known_pci_devices[*index];
        ++*index;
        if (result) {
            return result;
        }
    }
    return NULL;
}