1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/x86/x86_pci.c
2017-04-14 14:36:16 +02:00

333 lines
11 KiB
C

/*
* 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 */
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;
}