[dev][bus][pci] major refactor of the PCI bus driver

-Add a bus manager level, which is an object oriented walk of the pci
busses to build a per device object for later manipulation.
-Add features to enable MSI interrupts.
-Extend generic interrupt api to allow the platform to allocate vectors
for MSI interrupts.
-Rearrange a bit of the pc platform for the platform api changes.
-Add PC platform support for using the local apic to EOI MSI vectors.
-Fix up a few existing PCI drivers for small API changes.
-Add a few stubbed out routines for non PC platforms that use PCI.
This commit is contained in:
Travis Geiselbrecht
2021-12-27 20:19:38 -08:00
parent 522e62f648
commit fb1e414a09
29 changed files with 2072 additions and 718 deletions

View File

@@ -17,7 +17,7 @@
#include <dev/bus/pci.h>
#include <lk/trace.h>
#include "pci_priv.h"
#include "../pci_priv.h"
#if ARCH_X86_32
// Only actually supported on x86-32
@@ -26,6 +26,19 @@
#define LOCAL_TRACE 0
/*
* PCI BIOS access return codes
*/
// TODO: write routine to convert these to LK errors
#define _PCI_SUCCESSFUL 0x00
#define _PCI_FUNC_NOT_SUPPORTED 0x81
#define _PCI_BAD_VENDOR_ID 0x83
#define _PCI_DEVICE_NOT_FOUND 0x86
#define _PCI_BAD_REGISTER_NUMBER 0x87
#define _PCI_SET_FAILED 0x88
#define _PCI_BUFFER_TOO_SMALL 0x89
#define PCIBIOS_PRESENT 0xB101
#define PCIBIOS_FIND_PCI_DEVICE 0xB102
#define PCIBIOS_FIND_PCI_CLASS_CODE 0xB103
@@ -179,59 +192,12 @@ pci_bios32 *pci_bios32::detect() {
return b32;
}
int pci_bios32::find_pci_device(pci_location_t *state, uint16_t device_id, uint16_t vendor_id, uint16_t index) {
int pci_bios32::read_config_byte(const pci_location_t state, uint32_t reg, uint8_t *value) {
uint32_t bx, ret;
__asm__(
"lcall *(%%edi) \n\t"
"jc 1f \n\t"
"xor %%ah,%%ah \n"
"1:"
: "=b"(bx),
"=a"(ret)
: "1"(PCIBIOS_FIND_PCI_DEVICE),
"c"(device_id),
"d"(vendor_id),
"S"(index),
"D"(&bios32_entry_)
: "cc", "memory");
state->bus = bx >> 8;
state->dev_fn = bx & 0xFF;
ret >>= 8;
return ret & 0xFF;
}
int pci_bios32::find_pci_class_code(pci_location_t *state, uint32_t class_code, uint16_t index) {
uint32_t bx, ret;
__asm__(
"lcall *(%%edi) \n\t"
"jc 1f \n\t"
"xor %%ah,%%ah \n"
"1:"
: "=b"(bx),
"=a"(ret)
: "1"(PCIBIOS_FIND_PCI_CLASS_CODE),
"c"(class_code),
"S"(index),
"D"(&bios32_entry_)
: "cc", "memory");
state->bus = bx >> 8;
state->dev_fn = bx & 0xFF;
ret >>= 8;
return ret & 0xFF;
}
int pci_bios32::read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t *value) {
uint32_t bx, ret;
bx = state->bus;
bx = state.bus;
bx <<= 8;
bx |= state->dev_fn;
bx |= (state.dev << 3) | state.fn;
__asm__(
"lcall *(%%esi) \n\t"
"jc 1f \n\t"
@@ -248,12 +214,12 @@ int pci_bios32::read_config_byte(const pci_location_t *state, uint32_t reg, uint
return ret & 0xFF;
}
int pci_bios32::read_config_half(const pci_location_t *state, uint32_t reg, uint16_t *value) {
int pci_bios32::read_config_half(const pci_location_t state, uint32_t reg, uint16_t *value) {
uint32_t bx, ret;
bx = state->bus;
bx = state.bus;
bx <<= 8;
bx |= state->dev_fn;
bx |= (state.dev << 3) | state.fn;
__asm__(
"lcall *(%%esi) \n\t"
"jc 1f \n\t"
@@ -270,12 +236,12 @@ int pci_bios32::read_config_half(const pci_location_t *state, uint32_t reg, uint
return ret & 0xFF;
}
int pci_bios32::read_config_word(const pci_location_t *state, uint32_t reg, uint32_t *value) {
int pci_bios32::read_config_word(const pci_location_t state, uint32_t reg, uint32_t *value) {
uint32_t bx, ret;
bx = state->bus;
bx = state.bus;
bx <<= 8;
bx |= state->dev_fn;
bx |= (state.dev << 3) | state.fn;
__asm__(
"lcall *(%%esi) \n\t"
"jc 1f \n\t"
@@ -292,12 +258,12 @@ int pci_bios32::read_config_word(const pci_location_t *state, uint32_t reg, uint
return ret & 0xFF;
}
int pci_bios32::write_config_byte(const pci_location_t *state, uint32_t reg, uint8_t value) {
int pci_bios32::write_config_byte(const pci_location_t state, uint32_t reg, uint8_t value) {
uint32_t bx, ret;
bx = state->bus;
bx = state.bus;
bx <<= 8;
bx |= state->dev_fn;
bx |= (state.dev << 3) | state.fn;
__asm__(
"lcall *(%%esi) \n\t"
"jc 1f \n\t"
@@ -314,12 +280,12 @@ int pci_bios32::write_config_byte(const pci_location_t *state, uint32_t reg, uin
return ret & 0xFF;
}
int pci_bios32::write_config_half(const pci_location_t *state, uint32_t reg, uint16_t value) {
int pci_bios32::write_config_half(const pci_location_t state, uint32_t reg, uint16_t value) {
uint32_t bx, ret;
bx = state->bus;
bx = state.bus;
bx <<= 8;
bx |= state->dev_fn;
bx |= (state.dev << 3) | state.fn;
__asm__(
"lcall *(%%esi) \n\t"
"jc 1f \n\t"
@@ -336,12 +302,12 @@ int pci_bios32::write_config_half(const pci_location_t *state, uint32_t reg, uin
return ret & 0xFF;
}
int pci_bios32::write_config_word(const pci_location_t *state, uint32_t reg, uint32_t value) {
int pci_bios32::write_config_word(const pci_location_t state, uint32_t reg, uint32_t value) {
uint32_t bx, ret;
bx = state->bus;
bx = state.bus;
bx <<= 8;
bx |= state->dev_fn;
bx |= (state.dev << 3) | state.fn;
__asm__(
"lcall *(%%esi) \n\t"
"jc 1f \n\t"
@@ -358,47 +324,4 @@ int pci_bios32::write_config_word(const pci_location_t *state, uint32_t reg, uin
return ret & 0xFF;
}
int pci_bios32::get_irq_routing_options(irq_routing_options_t *options, uint16_t *pci_irqs) {
uint32_t ret;
__asm__(
"lcall *(%%esi) \n\t"
"jc 1f \n\t"
"xor %%ah,%%ah \n"
"1:"
: "=b"(*pci_irqs),
"=a"(ret)
: "1"(PCIBIOS_GET_IRQ_ROUTING_OPTIONS),
"b"(0),
"D"(options),
"S"(&bios32_entry_)
: "cc", "memory");
ret >>= 8;
return ret & 0xff;
}
int pci_bios32::set_irq_hw_int(const pci_location_t *state, uint8_t int_pin, uint8_t irq) {
uint32_t bx, cx, ret;
bx = state->bus;
bx <<= 8;
bx |= state->dev_fn;
cx = irq;
cx <<= 8;
cx |= int_pin;
__asm__(
"lcall *(%%esi) \n\t"
"jc 1f \n\t"
"xor %%ah,%%ah \n"
"1:"
: "=a"(ret)
: "0"(PCIBIOS_PCI_SET_IRQ_HW_INT),
"b"(bx),
"c"(cx),
"S"(&bios32_entry_)
: "cc", "memory");
ret >>= 8;
return ret & 0xFF;
}
#endif

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2021 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "pci_backend.h"
#include <stdint.h>
class pci_bios32 final : public pci_backend {
#if ARCH_X86_32
public:
virtual ~pci_bios32() = default;
// factory to detect and create an instance
static pci_bios32 *detect();
// a few overridden methods
virtual int read_config_byte(pci_location_t state, uint32_t reg, uint8_t *value) override;
virtual int read_config_half(pci_location_t state, uint32_t reg, uint16_t *value) override;
virtual int read_config_word(pci_location_t state, uint32_t reg, uint32_t *value) override;
virtual int write_config_byte(pci_location_t state, uint32_t reg, uint8_t value) override;
virtual int write_config_half(pci_location_t state, uint32_t reg, uint16_t value) override;
virtual int write_config_word(pci_location_t state, uint32_t reg, uint32_t value) override;
private:
// far call structure used by BIOS32 routines
struct bios32_entry {
uint32_t offset;
uint16_t selector;
} __PACKED;
// only created via the detect() factory
explicit pci_bios32(bios32_entry b32_entry) : bios32_entry_(b32_entry) {}
bios32_entry bios32_entry_ {};
#else // !ARCH_X86_32
// not present on anything but x86-32
public:
static pci_bios32 *detect() { return nullptr; }
#endif
};

View File

@@ -21,7 +21,7 @@
#include <kernel/vm.h>
#endif
#include "pci_priv.h"
#include "../pci_priv.h"
#define LOCAL_TRACE 0
@@ -84,17 +84,22 @@ status_t pci_ecam::initialize() {
}
// compute the offset into the ecam given the location and register offset
inline size_t location_to_offset(const pci_location_t *state, uint32_t reg) {
inline size_t location_to_offset(const pci_location_t state, uint32_t reg) {
//
// | 27 - 20 | 19 - 15 | 14 - 12 | 11 - 8 | 7 - 2 | 1 - 0 |
// | Bus Nr | Dev Nr | Function Nr | Ext. Register Nr | Register Nr | Byte Enable |
// TODO: clamp or assert on invalid offset
size_t offset = (size_t)state->bus << 20;
offset += (size_t)state->dev_fn << 12;
size_t offset = (size_t)state.bus << 20;
offset += (size_t)state.dev << 15;
offset += (size_t)state.fn << 12;
offset += reg;
return offset;
}
// templatized routines to access the pci config space using a specific type
template <typename T>
inline int read_config(const pci_location_t *state, uint32_t reg, T *value, const uint8_t *ecam_ptr) {
inline int read_config(const pci_location_t state, uint32_t reg, T *value, const uint8_t *ecam_ptr) {
auto off = location_to_offset(state, reg);
*value = *reinterpret_cast<const volatile T *>(&ecam_ptr[off]);
@@ -103,7 +108,7 @@ inline int read_config(const pci_location_t *state, uint32_t reg, T *value, cons
}
template <typename T>
inline int write_config(const pci_location_t *state, uint32_t reg, T value, uint8_t *ecam_ptr) {
inline int write_config(const pci_location_t state, uint32_t reg, T value, uint8_t *ecam_ptr) {
auto off = location_to_offset(state, reg);
*reinterpret_cast<volatile T *>(&ecam_ptr[off]) = value;
@@ -111,32 +116,32 @@ inline int write_config(const pci_location_t *state, uint32_t reg, T value, uint
return NO_ERROR;
}
int pci_ecam::read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t *value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
int pci_ecam::read_config_byte(const pci_location_t state, uint32_t reg, uint8_t *value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
return read_config(state, reg, value, ecam_ptr_);
}
int pci_ecam::read_config_half(const pci_location_t *state, uint32_t reg, uint16_t *value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
int pci_ecam::read_config_half(const pci_location_t state, uint32_t reg, uint16_t *value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
return read_config(state, reg, value, ecam_ptr_);
}
int pci_ecam::read_config_word(const pci_location_t *state, uint32_t reg, uint32_t *value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
int pci_ecam::read_config_word(const pci_location_t state, uint32_t reg, uint32_t *value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
return read_config(state, reg, value, ecam_ptr_);
}
int pci_ecam::write_config_byte(const pci_location_t *state, uint32_t reg, uint8_t value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
int pci_ecam::write_config_byte(const pci_location_t state, uint32_t reg, uint8_t value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
return write_config(state, reg, value, ecam_ptr_);
}
int pci_ecam::write_config_half(const pci_location_t *state, uint32_t reg, uint16_t value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
int pci_ecam::write_config_half(const pci_location_t state, uint32_t reg, uint16_t value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
return write_config(state, reg, value, ecam_ptr_);
}
int pci_ecam::write_config_word(const pci_location_t *state, uint32_t reg, uint32_t value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
int pci_ecam::write_config_word(const pci_location_t state, uint32_t reg, uint32_t value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
return write_config(state, reg, value, ecam_ptr_);
}

View File

@@ -17,12 +17,12 @@ public:
static pci_ecam *detect(paddr_t ecam_base, uint16_t segment, uint8_t start_bus, uint8_t end_bus);
// a few overridden methods
int read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t *value) override;
int read_config_half(const pci_location_t *state, uint32_t reg, uint16_t *value) override;
int read_config_word(const pci_location_t *state, uint32_t reg, uint32_t *value) override;
int write_config_byte(const pci_location_t *state, uint32_t reg, uint8_t value) override;
int write_config_half(const pci_location_t *state, uint32_t reg, uint16_t value) override;
int write_config_word(const pci_location_t *state, uint32_t reg, uint32_t value) override;
int read_config_byte(pci_location_t state, uint32_t reg, uint8_t *value) override;
int read_config_half(pci_location_t state, uint32_t reg, uint16_t *value) override;
int read_config_word(pci_location_t state, uint32_t reg, uint32_t *value) override;
int write_config_byte(pci_location_t state, uint32_t reg, uint8_t value) override;
int write_config_half(pci_location_t state, uint32_t reg, uint16_t value) override;
int write_config_word(pci_location_t state, uint32_t reg, uint32_t value) override;
private:
// only created via the detect() factory

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2021 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <stdint.h>
#include <lk/compiler.h>
#include <lk/cpp.h>
#include <lk/err.h>
#include <dev/bus/pci.h>
// Default implementation of a particular PCI bus/config accessor method.
// Intended to be subclassed and specialized based on specific method.
class pci_backend {
public:
constexpr pci_backend() = default;
virtual ~pci_backend() = default;
DISALLOW_COPY_ASSIGN_AND_MOVE(pci_backend);
public:
int get_last_bus() const { return last_bus_; }
// virtuals that a concrete implementation should override
virtual int read_config_byte(pci_location_t state, uint32_t reg, uint8_t *value) {
return ERR_NOT_CONFIGURED;
}
virtual int read_config_half(pci_location_t state, uint32_t reg, uint16_t *value) {
return ERR_NOT_CONFIGURED;
}
virtual int read_config_word(pci_location_t state, uint32_t reg, uint32_t *value) {
return ERR_NOT_CONFIGURED;
}
virtual int write_config_byte(pci_location_t state, uint32_t reg, uint8_t value) {
return ERR_NOT_CONFIGURED;
}
virtual int write_config_half(pci_location_t state, uint32_t reg, uint16_t value) {
return ERR_NOT_CONFIGURED;
}
virtual int write_config_word(pci_location_t state, uint32_t reg, uint32_t value) {
return ERR_NOT_CONFIGURED;
}
protected:
// detection should find the last bus
void set_last_bus(int last_bus) { last_bus_ = last_bus; }
int last_bus_ = -1;
};

View File

@@ -17,7 +17,7 @@
#include <dev/bus/pci.h>
#include <lk/trace.h>
#include "pci_priv.h"
#include "../pci_priv.h"
#if ARCH_X86
// Only supported on x86
@@ -80,21 +80,21 @@ pci_type1 *pci_type1::detect() {
return t1;
}
int pci_type1::read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t *value) {
LTRACEF("state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
*value = type1_read_half(state->bus, state->dev_fn >> 3, state->dev_fn & 0x7, reg);
int pci_type1::read_config_byte(const pci_location_t state, uint32_t reg, uint8_t *value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
*value = type1_read_half(state.bus, state.dev, state.fn, reg);
return NO_ERROR;
}
int pci_type1::read_config_half(const pci_location_t *state, uint32_t reg, uint16_t *value) {
LTRACEF("state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
*value = type1_read_half(state->bus, state->dev_fn >> 3, state->dev_fn & 0x7, reg);
int pci_type1::read_config_half(const pci_location_t state, uint32_t reg, uint16_t *value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
*value = type1_read_half(state.bus, state.dev, state.fn, reg);
return NO_ERROR;
}
int pci_type1::read_config_word(const pci_location_t *state, uint32_t reg, uint32_t *value) {
LTRACEF("state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
*value = type1_read_word(state->bus, state->dev_fn >> 3, state->dev_fn & 0x7, reg);
int pci_type1::read_config_word(const pci_location_t state, uint32_t reg, uint32_t *value) {
LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
*value = type1_read_word(state.bus, state.dev, state.fn, reg);
return NO_ERROR;
}

View File

@@ -18,9 +18,9 @@ public:
static pci_type1 *detect();
// a few overridden methods
int read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t *value) override;
int read_config_half(const pci_location_t *state, uint32_t reg, uint16_t *value) override;
int read_config_word(const pci_location_t *state, uint32_t reg, uint32_t *value) override;
int read_config_byte(pci_location_t state, uint32_t reg, uint8_t *value) override;
int read_config_half(pci_location_t state, uint32_t reg, uint16_t *value) override;
int read_config_word(pci_location_t state, uint32_t reg, uint32_t *value) override;
private:
// only created via the detect() factory

View File

@@ -1,57 +0,0 @@
/*
* Copyright (c) 2021 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "pci_backend.h"
#include <stdint.h>
class pci_bios32 final : public pci_backend {
#if ARCH_X86_32
public:
virtual ~pci_bios32() = default;
// factory to detect and create an instance
static pci_bios32 *detect();
// a few overridden methods
virtual int find_pci_device(pci_location_t *state, uint16_t device_id, uint16_t vendor_id, uint16_t index) override;
virtual int find_pci_class_code(pci_location_t *state, uint32_t class_code, uint16_t index) override;
virtual int read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t *value) override;
virtual int read_config_half(const pci_location_t *state, uint32_t reg, uint16_t *value) override;
virtual int read_config_word(const pci_location_t *state, uint32_t reg, uint32_t *value) override;
virtual int write_config_byte(const pci_location_t *state, uint32_t reg, uint8_t value) override;
virtual int write_config_half(const pci_location_t *state, uint32_t reg, uint16_t value) override;
virtual int write_config_word(const pci_location_t *state, uint32_t reg, uint32_t value) override;
virtual int get_irq_routing_options(irq_routing_options_t *options, uint16_t *pci_irqs) override;
virtual int set_irq_hw_int(const pci_location_t *state, uint8_t int_pin, uint8_t irq) override;
private:
// far call structure used by BIOS32 routines
struct bios32_entry {
uint32_t offset;
uint16_t selector;
} __PACKED;
// only created via the detect() factory
explicit pci_bios32(bios32_entry b32_entry) : bios32_entry_(b32_entry) {}
bios32_entry bios32_entry_ {};
#else // !ARCH_X86_32
// not present on anything but x86-32
public:
static pci_bios32 *detect() { return nullptr; }
#endif
};

834
dev/bus/pci/bus_mgr.cpp Normal file
View File

@@ -0,0 +1,834 @@
/*
* Copyright (c) 2021 Travis Geiseblrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#include "bus_mgr.h"
#include <sys/types.h>
#include <lk/cpp.h>
#include <lk/debug.h>
#include <lk/err.h>
#include <lk/list.h>
#include <lk/trace.h>
#include <dev/bus/pci.h>
#include <stdio.h>
#include <assert.h>
#include <platform/interrupts.h>
#define LOCAL_TRACE 0
namespace pci {
// root of the pci bus
class bus;
bus *root = nullptr;
list_node bus_list = LIST_INITIAL_VALUE(bus_list);
struct capability;
// generic pci device
class device {
public:
device(pci_location_t loc, bus *bus) : loc_(loc), bus_(bus) {}
virtual ~device();
DISALLOW_COPY_ASSIGN_AND_MOVE(device);
static status_t probe(pci_location_t loc, bus *bus, device **out_device, bool *out_multifunction);
status_t probe_capabilities();
status_t init_msi_capability(capability *cap);
status_t init_msix_capability(capability *cap);
status_t allocate_irq(uint *irq);
status_t allocate_msi(size_t num_requested, uint *msi_base);
pci_location_t loc() const { return loc_; }
const bus *get_bus() const { return bus_; }
uint16_t device_id() const { return config_.device_id; }
uint16_t vendor_id() const { return config_.vendor_id; }
uint8_t base_class() const { return config_.base_class; }
uint8_t sub_class() const { return config_.sub_class; }
uint8_t interface() const { return config_.program_interface; }
uint8_t header_type() const { return config_.header_type & PCI_HEADER_TYPE_MASK; }
size_t read_bars(pci_bar_t bar[6]);
bool has_msi() const { return msi_cap_; }
bool has_msix() const { return msix_cap_; }
virtual void dump(size_t indent = 0);
protected:
// let the bus device directly manipulate our list node
friend class bus;
list_node node = LIST_INITIAL_CLEARED_VALUE;
pci_location_t loc_ = {};
bus *bus_ = nullptr;
pci_config_t config_ = {};
// capability list
list_node capability_list_ = LIST_INITIAL_VALUE(capability_list_);
capability *msi_cap_ = nullptr;
capability *msix_cap_ = nullptr;
};
// bridge device, holds a list of busses that it is responsible for
class bridge : public device {
public:
bridge(pci_location_t loc, bus *bus) : device(loc, bus) {}
virtual ~bridge() = default;
DISALLOW_COPY_ASSIGN_AND_MOVE(bridge);
static status_t probe(pci_location_t loc, bus *bus, device **out_device);
void add_bus(bus *b) { secondary_bus_ = b; }
virtual void dump(size_t indent = 0);
private:
bus *secondary_bus_ = nullptr;
};
// bus device holds a list of devices and a reference to its bridge device
class bus {
public:
explicit bus(pci_location_t loc, bridge *b) : loc_(loc), b_(b) {}
virtual ~bus() = default;
DISALLOW_COPY_ASSIGN_AND_MOVE(bus);
static status_t probe(pci_location_t loc, bridge *bridge, bus **out_bus);
pci_location_t loc() const { return loc_; }
uint bus_num() const { return loc().bus; }
void add_device(device *d);
void dump(size_t indent = 0);
list_node *list_node_ptr() { return &node; }
template <typename F>
status_t for_every_device(F func);
// master list of busses for easy iteration
list_node node = LIST_INITIAL_CLEARED_VALUE;
private:
pci_location_t loc_ = {};
bridge *b_ = nullptr;
list_node child_devices_ = LIST_INITIAL_VALUE(child_devices_);
};
struct capability {
list_node node = LIST_INITIAL_CLEARED_VALUE;
uint16_t config_offset = 0;
uint16_t id = 0;
// simple accessors
bool is_msi() const { return id == 0x5; }
bool is_msix() const { return id == 0x11; }
};
void bus::add_device(device *d) {
// TODO: assert that no two devices have the same address
list_add_tail(&child_devices_, &d->node);
}
namespace {
// helper routines
// iterate all devices on all busses with the functor
template <typename F>
status_t for_every_device_on_every_bus(F func) {
status_t err = NO_ERROR;
bus *b;
list_for_every_entry(&bus_list, b, bus, node) {
err = b->for_every_device(func);
if (err != NO_ERROR) {
return err;
}
}
return err;
}
// return a pointer to the device that matches a particular location
device *lookup_device_by_loc(pci_location_t loc) {
device *ret = nullptr;
auto v = [&](device *d) -> status_t {
if (d->loc() == loc) {
ret = d;
return 1;
}
return 0;
};
for_every_device_on_every_bus(v);
return ret;
}
} // namespace
device::~device() {
LTRACE;
capability *cap;
while ((cap = list_remove_head_type(&capability_list_, capability, node))) {
delete cap;
}
}
// probe the device, return a new node and a bool if it's a multifunction device or not
status_t device::probe(pci_location_t loc, bus *parent_bus, device **out_device, bool *out_multifunction) {
status_t err;
*out_device = nullptr;
*out_multifunction = false;
// read vendor id and see if this is a real device
uint16_t vendor_id;
err = pci_read_config_half(loc, PCI_CONFIG_VENDOR_ID, &vendor_id);
if (err != NO_ERROR) {
return ERR_NOT_FOUND;
}
if (vendor_id == 0xffff) {
return ERR_NOT_FOUND;
}
char str[14];
LTRACEF("something at %s\n", pci_loc_string(loc, str));
// read base and sub class
uint8_t base_class;
err = pci_read_config_byte(loc, PCI_CONFIG_CLASS_CODE_BASE, &base_class);
if (err != NO_ERROR) {
return ERR_NOT_FOUND;
}
uint8_t sub_class;
err = pci_read_config_byte(loc, PCI_CONFIG_CLASS_CODE_SUB, &sub_class);
if (err != NO_ERROR) {
return ERR_NOT_FOUND;
}
// read header type (0 or 1)
uint8_t header_type;
err = pci_read_config_byte(loc, PCI_CONFIG_HEADER_TYPE, &header_type);
if (err != NO_ERROR) {
return ERR_NOT_FOUND;
}
// is it multifunction?
bool possibly_multifunction = false;
if (loc.fn == 0 && header_type & PCI_HEADER_TYPE_MULTI_FN) {
possibly_multifunction = true;
LTRACEF_LEVEL(2, "possibly multifunction\n");
}
header_type &= PCI_HEADER_TYPE_MASK;
LTRACEF_LEVEL(2, "base:sub class %#hhx:%hhx\n", base_class, sub_class);
// if it's a bridge, probe that
if (base_class == 0x6) { // XXX replace with #define
// bridge
if (sub_class == 0x4) { // PCI-PCI bridge, normal decode
LTRACEF("found bridge, recursing\n");
return bridge::probe(loc, parent_bus, out_device);
}
}
LTRACEF_LEVEL(2, "type %#hhx\n", header_type);
if (header_type != 0) {
LTRACEF("type %d header on bridge we don't understand, skipping\n", header_type);
return ERR_NOT_FOUND;
}
// create a new device and pass it up
device *d = new device(loc, parent_bus);
// try to read in the basic config space for this device
err = pci_read_config(loc, &d->config_);
if (err < 0) {
delete d;
return err;
}
// probe the device's capabilities
d->probe_capabilities();
// we know we're a device at this point, set multifunction or not
*out_multifunction = possibly_multifunction;
// return the newly constructed device
*out_device = d;
return NO_ERROR;
}
void device::dump(size_t indent) {
for (size_t i = 0; i < indent; i++) {
printf(" ");
}
char str[14];
printf("dev %s %04hx:%04hx\n", pci_loc_string(loc_, str), config_.vendor_id, config_.device_id);
}
// walk the device's capability list, reading them in and creating sub objects per
status_t device::probe_capabilities() {
char str[14];
LTRACEF("%s\n", pci_loc_string(loc(), str));
// does this device have any capabilities?
if ((config_.status & PCI_STATUS_NEW_CAPS) == 0) {
// no capabilities, just move on
return NO_ERROR;
}
status_t err;
size_t cap_ptr = config_.type0.capabilities_ptr; // type 0 and 1 are at same offset
for (;;) {
if (cap_ptr == 0) {
break;
}
// read the capability id
uint8_t cap_id;
err = pci_read_config_byte(loc(), cap_ptr, &cap_id);
if (err != NO_ERROR) {
return err;
}
LTRACEF("cap id %#x at offset %#zx\n", cap_id, cap_ptr);
// we only handle a few kinds of capabilities at the moment
capability *cap = new capability;
cap->id = cap_id;
cap->config_offset = cap_ptr;
// add the cap to our list
if (cap) {
list_add_tail(&capability_list_, &cap->node);
}
switch (cap_id) {
case 0x5: { // MSI
LTRACEF("MSI\n");
if (init_msi_capability(cap) == NO_ERROR) {
msi_cap_ = cap;
}
break;
}
case 0x11: { // MSI-X
LTRACEF("MSI-X\n");
if (init_msix_capability(cap) == NO_ERROR) {
msix_cap_ = cap;
}
break;
}
}
// read the next pointer
uint8_t next_cap_ptr;
err = pci_read_config_byte(loc(), cap_ptr + 1, &next_cap_ptr);
if (err != NO_ERROR) {
return err;
}
cap_ptr = next_cap_ptr;
}
return NO_ERROR;
}
status_t device::init_msi_capability(capability *cap) {
LTRACE_ENTRY;
DEBUG_ASSERT(cap->id == 0x5);
// plain MSI
uint32_t cap_buf[6];
pci_read_config_word(loc(), cap->config_offset, &cap_buf[0]);
pci_read_config_word(loc(), cap->config_offset + 4, &cap_buf[1]);
pci_read_config_word(loc(), cap->config_offset + 8, &cap_buf[2]);
pci_read_config_word(loc(), cap->config_offset + 12, &cap_buf[3]);
pci_read_config_word(loc(), cap->config_offset + 16, &cap_buf[4]);
pci_read_config_word(loc(), cap->config_offset + 20, &cap_buf[5]);
//hexdump(cap_buf, sizeof(cap_buf));
return NO_ERROR;
}
status_t device::init_msix_capability(capability *cap) {
LTRACE_ENTRY;
DEBUG_ASSERT(cap->id == 0x11);
// MSI-X
uint32_t cap_buf[3];
pci_read_config_word(loc(), cap->config_offset, &cap_buf[0]);
pci_read_config_word(loc(), cap->config_offset + 4, &cap_buf[1]);
pci_read_config_word(loc(), cap->config_offset + 8, &cap_buf[2]);
//hexdump(cap_buf, sizeof(cap_buf));
// TODO: we dont really support msi-x right now
return ERR_NOT_IMPLEMENTED;
}
status_t device::allocate_irq(uint *irq) {
LTRACE_ENTRY;
uint8_t interrupt_line;
status_t err = pci_read_config_byte(loc(), PCI_CONFIG_INTERRUPT_LINE, &interrupt_line);
if (err != NO_ERROR) return err;
if (interrupt_line == 0) {
return ERR_NO_RESOURCES;
}
// map the irq number in config space to platform vector space
err = platform_pci_int_to_vector(interrupt_line, irq);
return err;
}
status_t device::allocate_msi(size_t num_requested, uint *msi_base) {
LTRACE_ENTRY;
DEBUG_ASSERT(num_requested == 1);
if (!has_msi()) {
return ERR_NOT_SUPPORTED;
}
DEBUG_ASSERT(msi_cap_ && msi_cap_->is_msi());
// ask the platform for interrupts
uint vector_base;
status_t err = platform_allocate_interrupts(num_requested, 1, &vector_base);
if (err != NO_ERROR) {
return err;
}
// compute the MSI message to construct
uint64_t msi_address = 0;
uint16_t msi_data = 0;
#if ARCH_X86
msi_data = (vector_base & 0xff) | (0<<15); // edge triggered
msi_address = 0xfee0'0000 | (0 << 12); // cpu 0
#else
return ERR_NOT_SUPPORTED;
#endif
// program it into the capability
const uint16_t cap_offset = msi_cap_->config_offset;
uint16_t control;
pci_read_config_half(loc(), cap_offset + 2, &control);
pci_write_config_half(loc(), cap_offset + 2, control & ~(0x1)); // disable MSI
pci_write_config_word(loc(), cap_offset + 4, msi_address & 0xffff'ffff); // lower 32bits
if (control & (1<<7)) {
// 64bit
pci_write_config_word(loc(), cap_offset + 8, msi_address >> 32); // upper 32bits
pci_write_config_half(loc(), cap_offset + 0xc, msi_data);
} else {
pci_write_config_half(loc(), cap_offset + 8, msi_data);
}
// set up the control register and enable it
control = 1; // NME/NMI = 1, no per vector masking, keep 64bit flag, enable
pci_write_config_half(loc(), cap_offset + 2, control);
// write it back to the pci config in the interrupt line offset
pci_write_config_byte(loc(), PCI_CONFIG_INTERRUPT_LINE, vector_base);
// pass back the allocated irq to the caller
*msi_base = vector_base;
return NO_ERROR;
}
size_t device::read_bars(pci_bar_t bar[6]) {
size_t count = 0;
if (header_type() == 0) {
for (int i=0; i < 6; i++) {
bar[i].valid = false;
uint64_t bar_addr = config_.type0.base_addresses[i];
if (bar_addr & 0x1) {
// io address
bar[i].io = true;
bar[i].addr = bar_addr & ~0x3;
bar[i].size = 0; // XXX
bar[i].valid = (bar[i].addr != 0);
} else if ((bar_addr & 0b110) == 0) {
// 32bit memory address
bar[i].io = false;
bar[i].addr = bar_addr & ~0xf;
bar[i].size = 0; // XXX
bar[i].valid = (bar[i].addr != 0);
} else if ((bar_addr & 0b110) == 2) {
// 64bit memory address
if (i % 2) {
// root of 64bit memory range can only be on 0, 2, 4 slot
continue;
}
bar[i].io = false;
bar[i].addr = bar_addr & ~0xf;
bar[i].addr |= (uint64_t)config_.type0.base_addresses[i + 1] << 32;
bar[i].size = 0; // XXX
bar[i].valid = true;
// mark the next entry as invalid
i++;
bar[i].valid = false;
}
count++;
}
} else if (header_type() == 1) {
PANIC_UNIMPLEMENTED;
}
return count;
}
// examine the bridge device, figuring out the bus range it controls and recurse
status_t bridge::probe(pci_location_t loc, bus *parent_bus, device **out_device) {
char str[14];
LTRACEF("%s\n", pci_loc_string(loc, str));
// read vendor id and see if this is a real device
uint16_t vendor_id;
status_t err = pci_read_config_half(loc, PCI_CONFIG_VENDOR_ID, &vendor_id);
if (err != NO_ERROR) {
return ERR_NOT_FOUND;
}
if (vendor_id == 0xffff) {
return ERR_NOT_FOUND;
}
// read header type (0 or 1)
uint8_t header_type;
err = pci_read_config_byte(loc, PCI_CONFIG_HEADER_TYPE, &header_type);
if (err != NO_ERROR) {
return ERR_NOT_FOUND;
}
// we are a bridge to a new set of busses
bridge *br = new bridge(loc, parent_bus);
// we only grok type 1 headers here
err = pci_read_config(loc, &br->config_);
if (err < 0) {
delete br;
return err;
}
LTRACEF("primary bus %hhd secondary %hhd subordinate %hhd\n",
br->config_.type1.primary_bus, br->config_.type1.secondary_bus,
br->config_.type1.subordinate_bus);
// probe the bridge's capabilities
br->probe_capabilities();
*out_device = br;
// start a scan of the secondary bus downstream of this.
// via bridge devices on this bus, should find all of the subordinate busses.
bus *new_bus;
pci_location_t bus_location = {};
bus_location.segment = loc.segment;
bus_location.bus = br->config_.type1.secondary_bus;
err = bus::probe(bus_location, br, &new_bus);
if (err < 0) {
return err;
}
// add the bus to our list of children
DEBUG_ASSERT(new_bus);
br->add_bus(new_bus);
// add the bus to the global bus list
list_add_tail(&bus_list, new_bus->list_node_ptr());
return NO_ERROR;
}
void bridge::dump(size_t indent) {
for (size_t i = 0; i < indent; i++) {
printf(" ");
}
char str[14];
printf("bridge %s %04hx:%04hx child busses [%d..%d]\n", pci_loc_string(loc_, str),
config_.vendor_id, config_.device_id,
config_.type1.secondary_bus, config_.type1.subordinate_bus);
if (secondary_bus_) {
secondary_bus_->dump(indent + 2);
}
}
// walk all devices on a bus recursively walking into any bridge and scanning those busses
status_t bus::probe(pci_location_t loc, bridge *bridge, bus **out_bus) {
char str[14];
LTRACEF("%s\n", pci_loc_string(loc, str));
status_t err;
// create a bus to hold any devices we find
bus *b = new bus(loc, bridge);
// probe all functions on all 32 devices on this bus.
// add any devices found to this bus
for (uint dev = 0; dev < 32; dev++) {
loc.dev = dev;
for (uint fn = 0; fn < 8; fn++) {
loc.fn = fn;
device *d;
bool multifunction;
err = device::probe(loc, b, &d, &multifunction);
if (err < 0) {
break;
}
b->add_device(d);
// move on to the next device
if (fn == 0 && !multifunction) {
break;
}
}
}
*out_bus = b;
return NO_ERROR;
}
void bus::dump(size_t indent) {
for (size_t i = 0; i < indent; i++) {
printf(" ");
}
printf("bus %d\n", loc().bus);
device *d;
list_for_every_entry(&child_devices_, d, device, node) {
d->dump(indent + 2);
}
}
// call the provided functor on every device in this bus
template <typename F>
status_t bus::for_every_device(F func) {
status_t err = NO_ERROR;
device *d;
list_for_every_entry(&child_devices_, d, device, node) {
err = func(d);
if (err != NO_ERROR) {
return err;
}
}
return err;
}
} // namespace pci
// C api, so outside of the namespace
using namespace pci;
status_t pci_bus_mgr_init() {
LTRACE_ENTRY;
// start drilling into the pci bus tree
pci_location_t loc;
loc = {}; // start at 0:0:0.0
bus *b;
// TODO: deal with root bus not having reference to bridge device
status_t err = bus::probe(loc, nullptr, &b);
if (err < 0) {
return err;
}
// if we found anything there should be at least an empty bus device
DEBUG_ASSERT(b);
root = b;
list_add_tail(&bus_list, b->list_node_ptr());
// iterate over all the devices found
printf("PCI dump:\n");
root->dump(2);
#if 0
printf("visit all devices\n");
pci_bus_mgr_visit_devices([](pci_location_t _loc) {
char str[14];
printf("%s\n", pci_loc_string(_loc, str));
});
#endif
return NO_ERROR;
}
// for every bus in the system, pass the visit routine to the device
status_t pci_bus_mgr_visit_devices(pci_visit_routine routine, void *cookie) {
auto v = [&](device *d) -> status_t {
routine(d->loc(), cookie);
return NO_ERROR;
};
return for_every_device_on_every_bus(v);
}
status_t pci_bus_mgr_find_device(pci_location_t *state, uint16_t device_id, uint16_t vendor_id, size_t index) {
LTRACEF("device_id dev %#hx vendor %#hx index %zu\n", device_id, vendor_id, index);
if (device_id == 0xffff && vendor_id == 0xffff) {
return ERR_INVALID_ARGS;
}
auto v = [&](device *d) -> status_t {
if (device_id != 0xffff && device_id != d->device_id())
return NO_ERROR;
if (vendor_id != 0xffff && vendor_id != d->vendor_id())
return NO_ERROR;
if (index-- == 0) {
char str[14];
LTRACEF_LEVEL(2, "match at loc %s: device id %#hx vendor id %#hx\n", pci_loc_string(d->loc(), str), d->device_id(), d->vendor_id());
*state = d->loc();
return 1; // signals stop
}
return NO_ERROR;
};
status_t err = for_every_device_on_every_bus(v);
return (err > 0) ? NO_ERROR : ERR_NOT_FOUND;
}
status_t pci_bus_mgr_find_device_by_class(pci_location_t *state, uint8_t base_class, uint8_t sub_class, uint8_t interface, size_t index) {
LTRACEF("class %#x sub %#x interface %#x index %zu\n", base_class, sub_class, interface, index);
if (sub_class == 0xff && interface == 0xff) {
return ERR_INVALID_ARGS;
}
auto v = [&](device *d) -> status_t {
//LTRACEF_LEVEL(2, "class %#x\n", d->base_class());
if (base_class != d->base_class())
return NO_ERROR;
if (sub_class != 0xff && sub_class != d->sub_class())
return NO_ERROR;
if (interface != 0xff && interface != d->interface())
return NO_ERROR;
if (index-- == 0) {
char str[14];
LTRACEF_LEVEL(2, "match at loc %s: class %#hhx sub %#hhx interface %hhx\n",
pci_loc_string(d->loc(), str), d->base_class(), d->sub_class(), d->interface());
*state = d->loc();
return 1; // signals stop
}
return NO_ERROR;
};
status_t err = for_every_device_on_every_bus(v);
return (err > 0) ? NO_ERROR : ERR_NOT_FOUND;
}
status_t pci_bus_mgr_enable_device(const pci_location_t loc) {
char str[14];
LTRACEF("%s\n", pci_loc_string(loc, str));
device *d = lookup_device_by_loc(loc);
if (!d) {
return ERR_NOT_FOUND;
}
uint16_t command;
status_t err = pci_read_config_half(loc, PCI_CONFIG_COMMAND, &command);
if (err != NO_ERROR) return err;
LTRACEF("command reg %#x\n", command);
command |= PCI_COMMAND_IO_EN | PCI_COMMAND_MEM_EN | PCI_COMMAND_BUS_MASTER_EN;
err = pci_write_config_half(loc, PCI_CONFIG_COMMAND, command);
if (err != NO_ERROR) return err;
return NO_ERROR;
}
status_t pci_bus_mgr_read_bars(const pci_location_t loc, pci_bar_t bar[6], size_t *count) {
char str[14];
LTRACEF("%s\n", pci_loc_string(loc, str));
*count = 0;
device *d = lookup_device_by_loc(loc);
if (!d) {
return ERR_NOT_FOUND;
}
*count = d->read_bars(bar);
return NO_ERROR;
}
status_t pci_bus_mgr_allocate_msi(const pci_location_t loc, size_t num_requested, uint *irqbase) {
char str[14];
LTRACEF("%s num_request %zu\n", pci_loc_string(loc, str), num_requested);
*irqbase = 0;
device *d = lookup_device_by_loc(loc);
if (!d) {
return ERR_NOT_FOUND;
}
if (!d->has_msi()) {
return ERR_NO_RESOURCES;
}
return d->allocate_msi(num_requested, irqbase);
}
status_t pci_bus_mgr_allocate_irq(const pci_location_t loc, uint *irqbase) {
char str[14];
LTRACEF("%s\n", pci_loc_string(loc, str));
*irqbase = 0;
device *d = lookup_device_by_loc(loc);
if (!d) {
return ERR_NOT_FOUND;
}
return d->allocate_irq(irqbase);
}
void pci_dump_bars(pci_bar_t bar[6], size_t count) {
for (size_t i = 0; i < count; i++) {
if (bar[i].valid) {
printf("BAR %zu: addr %#16llx size %#16zx io %d\n",
i, bar[i].addr, bar[i].size, bar[i].io);
}
}
}
const char *pci_loc_string(pci_location_t loc, char out_str[14]) {
snprintf(out_str, 14, "%04x:%02x:%02x.%1x", loc.segment, loc.bus, loc.dev, loc.fn);
return out_str;
}

11
dev/bus/pci/bus_mgr.h Normal file
View File

@@ -0,0 +1,11 @@
/*
* Copyright (c) 2021 Travis Geiseblrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <sys/types.h>
#include <dev/bus/pci.h>

View File

@@ -12,6 +12,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <lk/compiler.h>
#include <lk/err.h>
#include <platform.h>
#include <dev/bus/pci.h>
#include <lk/console_cmd.h>
@@ -21,67 +22,57 @@
*/
static void pci_list(void) {
pci_location_t state;
uint16_t device_id, vendor_id;
uint8_t header_type;
uint8_t base_class, sub_class, interface;
int busses = 0, devices = 0, lines = 0, devfn, ret;
int c;
int busses = 0, devices = 0, lines = 0, ret;
printf("Scanning...\n");
printf("Scanning (brute force)...\n");
for (int bus = 0; bus <= (int)pci_get_last_bus(); bus++) {
busses++;
for (int segment = 0; segment <= (int)pci_get_last_segment(); segment++) {
state.segment = segment;
state.bus = bus;
for (int bus = 0; bus <= (int)pci_get_last_bus(); bus++) {
state.bus = bus;
busses++;
for (devfn = 0; devfn < 256; devfn++) {
state.dev_fn = devfn;
for (int dev = 0; dev < 32; dev++) {
state.dev = dev;
ret = pci_read_config_half(&state, PCI_CONFIG_VENDOR_ID, &vendor_id);
if (ret != _PCI_SUCCESSFUL) goto error;
for (int fn = 0; fn < 8; fn++) {
state.fn = fn;
ret = pci_read_config_half(&state, PCI_CONFIG_DEVICE_ID, &device_id);
if (ret != _PCI_SUCCESSFUL) goto error;
uint16_t vendor_id;
ret = pci_read_config_half(state, PCI_CONFIG_VENDOR_ID, &vendor_id);
if (ret != NO_ERROR) goto error;
if (vendor_id == 0xffff && fn == 0) {
// skip this device now before bothering to read the rest of the
// configuration in.
break;
}
ret = pci_read_config_byte(&state, PCI_CONFIG_HEADER_TYPE, &header_type);
if (ret != _PCI_SUCCESSFUL) goto error;
pci_config_t config;
ret = pci_read_config(state, &config);
if (ret != NO_ERROR) continue;
ret = pci_read_config_byte(&state, PCI_CONFIG_CLASS_CODE_BASE, &base_class);
if (ret != _PCI_SUCCESSFUL) goto error;
if (config.vendor_id != 0xffff) {
printf("%04x:%02x:%02x.%0x vendor_id=%04x device_id=%04x, header_type=%02x "
"base_class=%02x, sub_class=%02x, interface=%02x, irq=%u\n",
state.segment, state.bus, state.dev, state.fn,
config.vendor_id, config.device_id, config.header_type, config.base_class,
config.sub_class, config.program_interface, config.type0.interrupt_line);
devices++;
lines++;
}
ret = pci_read_config_byte(&state, PCI_CONFIG_CLASS_CODE_SUB, &sub_class);
if (ret != _PCI_SUCCESSFUL) goto error;
ret = pci_read_config_byte(&state, PCI_CONFIG_CLASS_CODE_INTR, &interface);
if (ret != _PCI_SUCCESSFUL) goto error;
if (vendor_id != 0xffff) {
printf("%02x:%02x.%0x vendor_id=%04x device_id=%04x, header_type=%02x "
"base_class=%02x, sub_class=%02x, interface=%02x\n",
state.bus, state.dev_fn >> 3, state.dev_fn & 7,
vendor_id, device_id, header_type, base_class, sub_class, interface);
devices++;
lines++;
}
if (((devfn & 7) == 0) && ~header_type & PCI_HEADER_TYPE_MULTI_FN) {
// this is not a multi-function device, so advance to the next device
// only check when looking at function 0 of a device
devfn |= 7;
}
if (lines == 23) {
printf("... press any key to continue, q to quit ...");
while ((c = getchar()) < 0);
printf("\n");
lines = 0;
if (c == 'q' || c == 'Q') goto quit;
if ((fn == 0) && ~config.header_type & PCI_HEADER_TYPE_MULTI_FN) {
// this is not a multi-function device, so advance to the next device
// only check when looking at function 0 of a device
break;
}
}
}
}
}
printf("... done. Scanned %d busses, %d device/functions\n", busses, devices);
printf("... done. Scanned %d busses, found %d device/functions\n", busses, devices);
quit:
return;
@@ -100,100 +91,159 @@ static int pci_config(int argc, const console_cmd_args *argv) {
unsigned int i;
int ret;
if (argc < 5) {
if (argc < 6) {
return -1;
}
if (!strcmp(argv[2].str, "dump")) {
loc.segment = 0;
loc.bus = atoui(argv[3].str);
loc.dev_fn = atoui(argv[4].str);
loc.dev = atoui(argv[4].str);
loc.fn = atoui(argv[5].str);
for (i=0; i < sizeof(pci_config_t); i++) {
ret = pci_read_config_byte(&loc, i, (uint8_t *) &config + i);
if (ret != _PCI_SUCCESSFUL) goto error;
}
printf("Device at %02x:%02x.%1x vendor id=%04x device id=%04x\n", loc.bus,
loc.dev_fn >> 3, loc.dev_fn & 7, config.vendor_id, config.device_id);
printf("command=%04x status=%04x pi=%02x sub cls=%02x base cls=%02x\n",
config.command, config.status, config.program_interface,
config.sub_class, config.base_class);
for (i=0; i < 6; i+=2) {
printf("bar%d=%08x bar%d=%08x\n", i, config.base_addresses[i],
i+1, config.base_addresses[i+1]);
}
} else if (!strcmp(argv[2].str, "rb") || !strcmp(argv[2].str, "rh") || !strcmp(argv[2].str, "rw")) {
if (argc != 6) {
if (pci_read_config(loc, &config) != NO_ERROR) {
printf("error reading configuration\n");
return -1;
}
printf("Device at %04x:%02x:%02x.%1x vendor id=%04x device id=%04x\n", loc.segment, loc.bus,
loc.dev, loc.fn, config.vendor_id, config.device_id);
printf("command=%04x status=%04x pi=%02x sub cls=%02x base cls=%02x htype=%02x\n",
config.command, config.status, config.program_interface,
config.sub_class, config.base_class, config.header_type & PCI_HEADER_TYPE_MASK);
printf("int pin %u int line %u\n", config.type0.interrupt_pin, config.type0.interrupt_line);
uint8_t header_type = config.header_type & PCI_HEADER_TYPE_MASK;
if (header_type == PCI_HEADER_TYPE_STANDARD) { // type 0
for (i=0; i < 6; i+=2) {
printf("bar%d=%08x bar%d=%08x\n", i, config.type0.base_addresses[i],
i+1, config.type0.base_addresses[i+1]);
}
} else if (header_type == PCI_HEADER_TYPE_PCI_BRIDGE) { // type 1
printf("primary bus=%02x secondary=%02x subordinate=%02x\n",
config.type1.primary_bus, config.type1.secondary_bus, config.type1.subordinate_bus);
for (i=0; i < 2; i+=2) {
printf("bar%d=%08x bar%d=%08x\n", i, config.type1.base_addresses[i],
i+1, config.type1.base_addresses[i+1]);
}
printf("mem base=%08x mem limit=%08x\n", config.type1.memory_base << 16,
(config.type1.memory_limit << 16) | 0xf'ffff);
switch ((config.type1.io_base & 0xf)) {
case 0: // 16 bit io addressing
printf("io base=%04x io limit=%04x\n", (config.type1.io_base & 0xf0) << 8,
((config.type1.io_limit & 0xf0) << 8) | 0xfff );
break;
case 1: // 32 bit io addressing
printf("io base=%08x io limit=%08x\n",
(config.type1.io_base % 0xf0) << 8 | config.type1.io_base_upper << 16,
((config.type1.io_limit & 0xf0) << 8) | 0xfff | config.type1.io_limit_upper << 16);
break;
}
switch ((config.type1.prefetchable_memory_base & 0xf)) {
case 0: // 32 bit prefetchable addressing
printf("prefetchable base=%08x prefetchable limit=%08x\n",
(config.type1.prefetchable_memory_base & 0xfff0) << 16,
(config.type1.prefetchable_memory_limit & 0xfff0) << 16 | 0xf'ffff);
break;
case 1: // 64 bit prefetchable addressing
printf("prefetchable base=%llx prefetchable limit=%llx\n",
((uint64_t)config.type1.prefetchable_memory_base & 0xfff0) << 16 |
((uint64_t)config.type1.prefetchable_base_upper << 32),
((uint64_t)config.type1.prefetchable_memory_limit & 0xfff0) << 16 | 0xf'ffff |
((uint64_t)config.type1.prefetchable_limit_upper << 32));
break;
}
}
hexdump8_ex(&config, sizeof(config), 0);
} else if (!strcmp(argv[2].str, "hexdump")) {
loc.segment = 0;
loc.bus = atoui(argv[3].str);
loc.dev_fn = atoui(argv[4].str);
offset = atoui(argv[5].str);
loc.dev = atoui(argv[4].str);
loc.fn = atoui(argv[5].str);
if (pci_read_config(loc, &config) != NO_ERROR) {
printf("error reading configuration\n");
return -1;
}
hexdump8_ex(&config, sizeof(config), 0);
} else if (!strcmp(argv[2].str, "rb") || !strcmp(argv[2].str, "rh") || !strcmp(argv[2].str, "rw")) {
if (argc != 7) {
return -1;
}
loc.segment = 0;
loc.bus = atoui(argv[3].str);
loc.dev = atoui(argv[4].str);
loc.fn = atoui(argv[5].str);
offset = atoui(argv[6].str);
switch (argv[2].str[1]) {
case 'b': {
uint8_t value;
ret = pci_read_config_byte(&loc, offset, &value);
if (ret != _PCI_SUCCESSFUL) goto error;
ret = pci_read_config_byte(loc, offset, &value);
if (ret != NO_ERROR) goto error;
printf("byte at device %02x:%02x config offset %04x: %02x\n", loc.bus, loc.dev_fn, offset, value);
printf("byte at device %04x:%02x:%02x.%1x config offset %04x: %02x\n", loc.segment, loc.bus, loc.dev, loc.fn, offset, value);
}
break;
case 'h': {
uint16_t value;
ret = pci_read_config_half(&loc, offset, &value);
if (ret != _PCI_SUCCESSFUL) goto error;
ret = pci_read_config_half(loc, offset, &value);
if (ret != NO_ERROR) goto error;
printf("half at device %02x:%02x config offset %04x: %04x\n", loc.bus, loc.dev_fn, offset, value);
printf("half at device %04x:%02x:%02x.%1x config offset %04x: %04x\n", loc.segment, loc.bus, loc.dev, loc.fn, offset, value);
}
break;
case 'w': {
uint32_t value;
ret = pci_read_config_word(&loc, offset, &value);
if (ret != _PCI_SUCCESSFUL) goto error;
ret = pci_read_config_word(loc, offset, &value);
if (ret != NO_ERROR) goto error;
printf("word at device %02x:%02x config offset %04x: %08x\n", loc.bus, loc.dev_fn, offset, value);
printf("word at device %04x:%02x:%02x.%1x config offset %04x: %08x\n", loc.segment, loc.bus, loc.dev, loc.fn, offset, value);
}
break;
}
} else if (!strcmp(argv[2].str, "mb") || !strcmp(argv[2].str, "mh") || !strcmp(argv[2].str, "mw")) {
if (argc != 7) {
if (argc != 8) {
return -1;
}
loc.segment = 0;
loc.bus = atoui(argv[3].str);
loc.dev_fn = atoui(argv[4].str);
offset = atoui(argv[5].str);
loc.dev = atoui(argv[4].str);
loc.fn = atoui(argv[5].str);
offset = atoui(argv[6].str);
switch (argv[2].str[1]) {
case 'b': {
uint8_t value = atoui(argv[6].str);
ret = pci_write_config_byte(&loc, offset, value);
if (ret != _PCI_SUCCESSFUL) goto error;
uint8_t value = atoui(argv[7].str);
ret = pci_write_config_byte(loc, offset, value);
if (ret != NO_ERROR) goto error;
printf("byte to device %02x:%02x config offset %04x: %02x\n", loc.bus, loc.dev_fn, offset, value);
printf("byte to device %04x:%02x:%02x.%1x config offset %04x: %02x\n", loc.segment, loc.bus, loc.dev, loc.fn, offset, value);
}
break;
case 'h': {
uint16_t value = atoui(argv[6].str);
ret = pci_write_config_half(&loc, offset, value);
if (ret != _PCI_SUCCESSFUL) goto error;
uint16_t value = atoui(argv[7].str);
ret = pci_write_config_half(loc, offset, value);
if (ret != NO_ERROR) goto error;
printf("half to device %04x:%02x:%02x.%1x config offset %04x: %04x\n", loc.segment, loc.bus, loc.dev, loc.fn, offset, value);
printf("half to device %02x:%02x config offset %04x: %04x\n", loc.bus, loc.dev_fn, offset, value);
}
break;
case 'w': {
uint32_t value = atoui(argv[6].str);
ret = pci_write_config_word(&loc, offset, value);
if (ret != _PCI_SUCCESSFUL) goto error;
uint32_t value = atoui(argv[7].str);
ret = pci_write_config_word(loc, offset, value);
if (ret != NO_ERROR) goto error;
printf("word to device %02x:%02x config offset %04x: %08x\n", loc.bus, loc.dev_fn, offset, value);
printf("word to device %04x:%02x:%02x.%1x config offset %04x: %08x\n", loc.segment, loc.bus, loc.dev, loc.fn, offset, value);
}
break;
}
@@ -213,9 +263,10 @@ static int pci_cmd(int argc, const console_cmd_args *argv) {
printf("pci commands:\n");
usage:
printf("%s list\n", argv[0].str);
printf("%s config dump <bus> <devfn>\n", argv[0].str);
printf("%s config <rb|rh|rw> <bus> <devfn> <offset>\n", argv[0].str);
printf("%s config <mb|mh|mw> <bus> <devfn> <offset> <value>\n", argv[0].str);
printf("%s config dump <bus> <dev> <fn>\n", argv[0].str);
printf("%s config hexdump <bus> <dev> <fn>\n", argv[0].str);
printf("%s config <rb|rh|rw> <bus> <dev> <fn> <offset>\n", argv[0].str);
printf("%s config <mb|mh|mw> <bus> <dev> <fn> <offset> <value>\n", argv[0].str);
goto out;
}

View File

@@ -8,125 +8,32 @@
*/
#pragma once
#include <assert.h>
#include <sys/types.h>
#include <lk/compiler.h>
// pci level structures and defines
#include <hw/pci.h>
__BEGIN_CDECLS
/*
* PCI access return codes
*/
#define _PCI_SUCCESSFUL 0x00
#define _PCI_FUNC_NOT_SUPPORTED 0x81
#define _PCI_BAD_VENDOR_ID 0x83
#define _PCI_DEVICE_NOT_FOUND 0x86
#define _PCI_BAD_REGISTER_NUMBER 0x87
#define _PCI_SET_FAILED 0x88
#define _PCI_BUFFER_TOO_SMALL 0x89
/*
* PCI configuration space offsets
*/
#define PCI_CONFIG_VENDOR_ID 0x00
#define PCI_CONFIG_DEVICE_ID 0x02
#define PCI_CONFIG_COMMAND 0x04
#define PCI_CONFIG_STATUS 0x06
#define PCI_CONFIG_REVISION_ID 0x08
#define PCI_CONFIG_CLASS_CODE 0x09
#define PCI_CONFIG_CLASS_CODE_INTR 0x09
#define PCI_CONFIG_CLASS_CODE_SUB 0x0a
#define PCI_CONFIG_CLASS_CODE_BASE 0x0b
#define PCI_CONFIG_CACHE_LINE_SIZE 0x0c
#define PCI_CONFIG_LATENCY_TIMER 0x0d
#define PCI_CONFIG_HEADER_TYPE 0x0e
#define PCI_CONFIG_BIST 0x0f
#define PCI_CONFIG_BASE_ADDRESSES 0x10
#define PCI_CONFIG_CARDBUS_CIS_PTR 0x28
#define PCI_CONFIG_SUBSYS_VENDOR_ID 0x2c
#define PCI_CONFIG_SUBSYS_ID 0x2e
#define PCI_CONFIG_EXP_ROM_ADDRESS 0x30
#define PCI_CONFIG_CAPABILITIES 0x34
#define PCI_CONFIG_INTERRUPT_LINE 0x3c
#define PCI_CONFIG_INTERRUPT_PIN 0x3d
#define PCI_CONFIG_MIN_GRANT 0x3e
#define PCI_CONFIG_MAX_LATENCY 0x3f
/*
* PCI header type register bits
*/
#define PCI_HEADER_TYPE_MASK 0x7f
#define PCI_HEADER_TYPE_MULTI_FN 0x80
/*
* PCI header types
*/
#define PCI_HEADER_TYPE_STANDARD 0x00
#define PCI_HEADER_TYPE_PCI_BRIDGE 0x01
#define PCI_HEADER_TYPE_CARD_BUS 0x02
/*
* PCI command register bits
*/
#define PCI_COMMAND_IO_EN 0x0001
#define PCI_COMMAND_MEM_EN 0x0002
#define PCI_COMMAND_BUS_MASTER_EN 0x0004
#define PCI_COMMAND_SPECIAL_EN 0x0008
#define PCI_COMMAND_MEM_WR_INV_EN 0x0010
#define PCI_COMMAND_PAL_SNOOP_EN 0x0020
#define PCI_COMMAND_PERR_RESP_EN 0x0040
#define PCI_COMMAND_AD_STEP_EN 0x0080
#define PCI_COMMAND_SERR_EN 0x0100
#define PCI_COMMAND_FAST_B2B_EN 0x0200
/*
* PCI status register bits
*/
#define PCI_STATUS_NEW_CAPS 0x0010
#define PCI_STATUS_66_MHZ 0x0020
#define PCI_STATUS_FAST_B2B 0x0080
#define PCI_STATUS_MSTR_PERR 0x0100
#define PCI_STATUS_DEVSEL_MASK 0x0600
#define PCI_STATUS_TARG_ABORT_SIG 0x0800
#define PCI_STATUS_TARG_ABORT_RCV 0x1000
#define PCI_STATUS_MSTR_ABORT_RCV 0x2000
#define PCI_STATUS_SERR_SIG 0x4000
#define PCI_STATUS_PERR 0x8000
typedef struct {
uint16_t vendor_id;
uint16_t device_id;
uint16_t command;
uint16_t status;
uint8_t revision_id_0;
uint8_t program_interface;
uint8_t sub_class;
uint8_t base_class;
uint8_t cache_line_size;
uint8_t latency_timer;
uint8_t header_type;
uint8_t bist;
uint32_t base_addresses[6];
uint32_t cardbus_cis_ptr;
uint16_t subsystem_vendor_id;
uint16_t subsystem_id;
uint32_t expansion_rom_address;
uint8_t capabilities_ptr;
uint8_t reserved_0[3];
uint32_t reserved_1;
uint8_t interrupt_line;
uint8_t interrupt_pin;
uint8_t min_grant;
uint8_t max_latency;
} __PACKED pci_config_t;
/*
* PCI address structure
*/
typedef struct {
uint16_t segment;
uint8_t bus;
uint8_t dev_fn;
uint8_t dev;
uint8_t fn;
} pci_location_t;
typedef struct {
uint64_t addr;
size_t size;
bool io;
bool valid;
} pci_bar_t;
typedef struct {
uint8_t id;
uint8_t next;
@@ -147,6 +54,7 @@ typedef struct {
uint8_t reserved;
} __PACKED irq_routing_entry;
// only use one of these two:
// try to detect PCI based on legacy PC PCI accessor methods
status_t pci_init_legacy(void);
@@ -154,20 +62,82 @@ status_t pci_init_legacy(void);
// try to detect PCI based on a known ecam base.
status_t pci_init_ecam(paddr_t ecam_base, uint16_t segment, uint8_t start_bus, uint8_t end_bus);
// user facing C api
int pci_get_last_bus(void);
int pci_get_last_segment(void);
status_t pci_find_pci_device(pci_location_t *state, uint16_t device_id, uint16_t vendor_id, uint16_t index);
status_t pci_find_pci_class_code(pci_location_t *state, uint32_t class_code, uint16_t index);
status_t pci_read_config(pci_location_t loc, pci_config_t *config);
status_t pci_read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t *value);
status_t pci_read_config_half(const pci_location_t *state, uint32_t reg, uint16_t *value);
status_t pci_read_config_word(const pci_location_t *state, uint32_t reg, uint32_t *value);
status_t pci_read_config_byte(pci_location_t state, uint32_t reg, uint8_t *value);
status_t pci_read_config_half(pci_location_t state, uint32_t reg, uint16_t *value);
status_t pci_read_config_word(pci_location_t state, uint32_t reg, uint32_t *value);
status_t pci_write_config_byte(const pci_location_t *state, uint32_t reg, uint8_t value);
status_t pci_write_config_half(const pci_location_t *state, uint32_t reg, uint16_t value);
status_t pci_write_config_word(const pci_location_t *state, uint32_t reg, uint32_t value);
status_t pci_write_config_byte(pci_location_t state, uint32_t reg, uint8_t value);
status_t pci_write_config_half(pci_location_t state, uint32_t reg, uint16_t value);
status_t pci_write_config_word(pci_location_t state, uint32_t reg, uint32_t value);
status_t pci_get_irq_routing_options(irq_routing_entry *entries, uint16_t *count, uint16_t *pci_irqs);
status_t pci_set_irq_hw_int(const pci_location_t *state, uint8_t int_pin, uint8_t irq);
// pci bus manager
// builds a list of devices and allows for various operations on the list
// C level visitor routine
typedef void(*pci_visit_routine)(pci_location_t loc, void *cookie);
status_t pci_bus_mgr_visit_devices(pci_visit_routine routine, void *cookie);
// must be called after pci_init_*();
status_t pci_bus_mgr_init(void);
// Look for the Nth match of device id and vendor id.
// Either device or vendor is skipped if set to 0xffff.
// Error if both is set to 0xffff.
status_t pci_bus_mgr_find_device(pci_location_t *state, uint16_t device_id, uint16_t vendor_id, size_t index);
// Look for the Nth match of combination of base, subclass, and interface.
// interface and subclass may be set to 0xff in which case it will skip.
status_t pci_bus_mgr_find_device_by_class(pci_location_t *state, uint8_t base_class, uint8_t subclass, uint8_t interface, size_t index);
// set io and mem enable on the device
status_t pci_bus_mgr_enable_device(const pci_location_t loc);
// read a list of up to 6 bars out of the device. each is marked with a valid bit
status_t pci_bus_mgr_read_bars(const pci_location_t loc, pci_bar_t bar[6], size_t *count);
// try to allocate one or more msi vectors for this device
status_t pci_bus_mgr_allocate_msi(const pci_location_t loc, size_t num_requested, uint *irqbase);
// allocate a regular irq for this device and return it in irqbase
status_t pci_bus_mgr_allocate_irq(const pci_location_t loc, uint *irqbase);
// return a pointer to a formatted string
const char *pci_loc_string(pci_location_t loc, char out_str[14]);
void pci_dump_bars(pci_bar_t bar[6], size_t count);
__END_CDECLS
#if __cplusplus
// C++ helper routine for pci_bus_mgr_visit_devices
// Wrapper to convert lambdas and other function like things to the C api
template <typename T>
void pci_bus_mgr_visit_devices(T routine) {
struct vdata {
T &routine;
};
auto v = [](pci_location_t loc, void *cookie) {
vdata *data = static_cast<vdata *>(cookie);
data->routine(loc);
};
vdata data = { routine };
pci_bus_mgr_visit_devices(v, &data);
}
inline bool operator==(pci_location_t a, pci_location_t b) {
return a.segment == b.segment &&
a.bus == b.bus &&
a.dev == b.dev &&
a.fn == b.fn;
}
#endif // __cplusplus

View File

@@ -27,6 +27,7 @@ SpinLock lock;
pci_backend *pcib = nullptr;
} // namespace
/* user facing routines */
int pci_get_last_bus() {
if (!pcib) {
return ERR_NOT_CONFIGURED;
@@ -35,32 +36,12 @@ int pci_get_last_bus() {
return pcib->get_last_bus();
}
/* user facing routines */
status_t pci_find_pci_device(pci_location_t *state, uint16_t device_id, uint16_t vendor_id, uint16_t index) {
LTRACEF("device_id dev %#hx vendor %#hx index %#hx\n", device_id, vendor_id, index);
if (!pcib) return ERR_NOT_CONFIGURED;
AutoSpinLock guard(&lock);
int res = pcib->find_pci_device(state, device_id, vendor_id, index);
return res;
int pci_get_last_segment() {
// currently hard coded to 1 segment
return 0;
}
status_t pci_find_pci_class_code(pci_location_t *state, uint32_t class_code, uint16_t index) {
LTRACEF("device_id class %#x index %#hx\n", class_code, index);
if (!pcib) return ERR_NOT_CONFIGURED;
AutoSpinLock guard(&lock);
int res = pcib->find_pci_class_code(state, class_code, index);
return res;
}
status_t pci_read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t *value) {
status_t pci_read_config_byte(const pci_location_t state, uint32_t reg, uint8_t *value) {
if (!pcib) return ERR_NOT_CONFIGURED;
AutoSpinLock guard(&lock);
@@ -69,7 +50,7 @@ status_t pci_read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t
return res;
}
status_t pci_read_config_half(const pci_location_t *state, uint32_t reg, uint16_t *value) {
status_t pci_read_config_half(const pci_location_t state, uint32_t reg, uint16_t *value) {
if (!pcib) return ERR_NOT_CONFIGURED;
AutoSpinLock guard(&lock);
@@ -79,7 +60,7 @@ status_t pci_read_config_half(const pci_location_t *state, uint32_t reg, uint16_
return res;
}
status_t pci_read_config_word(const pci_location_t *state, uint32_t reg, uint32_t *value) {
status_t pci_read_config_word(const pci_location_t state, uint32_t reg, uint32_t *value) {
if (!pcib) return ERR_NOT_CONFIGURED;
AutoSpinLock guard(&lock);
@@ -89,7 +70,7 @@ status_t pci_read_config_word(const pci_location_t *state, uint32_t reg, uint32_
return res;
}
status_t pci_write_config_byte(const pci_location_t *state, uint32_t reg, uint8_t value) {
status_t pci_write_config_byte(const pci_location_t state, uint32_t reg, uint8_t value) {
if (!pcib) return ERR_NOT_CONFIGURED;
AutoSpinLock guard(&lock);
@@ -99,7 +80,7 @@ status_t pci_write_config_byte(const pci_location_t *state, uint32_t reg, uint8_
return res;
}
status_t pci_write_config_half(const pci_location_t *state, uint32_t reg, uint16_t value) {
status_t pci_write_config_half(const pci_location_t state, uint32_t reg, uint16_t value) {
if (!pcib) return ERR_NOT_CONFIGURED;
AutoSpinLock guard(&lock);
@@ -109,7 +90,7 @@ status_t pci_write_config_half(const pci_location_t *state, uint32_t reg, uint16
return res;
}
status_t pci_write_config_word(const pci_location_t *state, uint32_t reg, uint32_t value) {
status_t pci_write_config_word(const pci_location_t state, uint32_t reg, uint32_t value) {
if (!pcib) return ERR_NOT_CONFIGURED;
AutoSpinLock guard(&lock);
@@ -119,35 +100,159 @@ status_t pci_write_config_word(const pci_location_t *state, uint32_t reg, uint32
return res;
}
status_t pci_get_irq_routing_options(irq_routing_entry *entries, uint16_t *count, uint16_t *pci_irqs) {
if (!pcib) return ERR_NOT_CONFIGURED;
status_t pci_read_config(const pci_location_t loc, pci_config_t *config) {
status_t err;
// TODO: highly bios32 specific, abstract this differently
pci_backend::irq_routing_options_t options;
options.size = sizeof(irq_routing_entry) * *count;
options.selector = 0x10; // XXX actually DATA_SELECTOR
options.offset = entries;
*config = {};
int res;
{
AutoSpinLock guard(&lock);
// TODO: handle endian swapping (if necessary)
res = pcib->get_irq_routing_options(&options, pci_irqs);
// define some helper routines to read config offsets in the proper unit
size_t next_index;
auto read_byte = [&]() -> uint8_t {
uint8_t val;
err = pci_read_config_byte(loc, next_index, &val);
next_index++;
if (err < 0) return 0;
return val;
};
auto read_half = [&]() -> uint16_t {
uint16_t val;
err = pci_read_config_half(loc, next_index, &val);
next_index += 2;
if (err < 0) return 0;
return val;
};
auto read_word = [&]() -> uint32_t {
uint32_t val;
err = pci_read_config_word(loc, next_index, &val);
next_index += 4;
if (err < 0) return 0;
return val;
};
// shared, standard part of the pci config space
/*
uint16_t vendor_id;
uint16_t device_id;
uint16_t command;
uint16_t status;
uint8_t revision_id_0;
uint8_t program_interface;
uint8_t sub_class;
uint8_t base_class;
uint8_t cache_line_size;
uint8_t latency_timer;
uint8_t header_type;
uint8_t bist;
*/
next_index = 0;
config->vendor_id = read_half(); if (err < 0) return err;
config->device_id = read_half(); if (err < 0) return err;
config->command = read_half(); if (err < 0) return err;
config->status = read_half(); if (err < 0) return err;
config->revision_id_0 = read_byte(); if (err < 0) return err;
config->program_interface = read_byte(); if (err < 0) return err;
config->sub_class = read_byte(); if (err < 0) return err;
config->base_class = read_byte(); if (err < 0) return err;
config->cache_line_size = read_byte(); if (err < 0) return err;
config->latency_timer = read_byte(); if (err < 0) return err;
config->header_type = read_byte(); if (err < 0) return err;
config->bist = read_byte(); if (err < 0) return err;
DEBUG_ASSERT(next_index == 0x10); // should have read this many bytes at this point
// based on the type field, read two different types
const uint8_t type = config->header_type & 0x7f;
if (type == 0) {
/*
uint32_t base_addresses[6];
uint32_t cardbus_cis_ptr;
uint16_t subsystem_vendor_id;
uint16_t subsystem_id;
uint32_t expansion_rom_address;
uint8_t capabilities_ptr;
uint8_t reserved_0[3];
uint32_t reserved_1;
uint8_t interrupt_line;
uint8_t interrupt_pin;
uint8_t min_grant;
uint8_t max_latency;
*/
config->type0.base_addresses[0] = read_word(); if (err < 0) return err;
config->type0.base_addresses[1] = read_word(); if (err < 0) return err;
config->type0.base_addresses[2] = read_word(); if (err < 0) return err;
config->type0.base_addresses[3] = read_word(); if (err < 0) return err;
config->type0.base_addresses[4] = read_word(); if (err < 0) return err;
config->type0.base_addresses[5] = read_word(); if (err < 0) return err;
config->type0.cardbus_cis_ptr = read_word(); if (err < 0) return err;
config->type0.subsystem_vendor_id = read_half(); if (err < 0) return err;
config->type0.subsystem_id = read_half(); if (err < 0) return err;
config->type0.expansion_rom_address = read_word(); if (err < 0) return err;
config->type0.capabilities_ptr = read_byte(); if (err < 0) return err;
next_index += 3 + 4; // 7 bytes of reserved space
config->type0.interrupt_line = read_byte(); if (err < 0) return err;
config->type0.interrupt_pin = read_byte(); if (err < 0) return err;
config->type0.min_grant = read_byte(); if (err < 0) return err;
config->type0.max_latency = read_byte(); if (err < 0) return err;
DEBUG_ASSERT(next_index == 0x40); // should have read this many bytes at this point
} else if (type == 1) {
/*
uint32_t base_addresses[2];
uint8_t primary_bus;
uint8_t secondary_bus;
uint8_t subordinate_bus;
uint8_t secondary_latency_timer;
uint8_t io_base;
uint8_t io_limit;
uint16_t secondary_status;
uint16_t memory_base;
uint16_t memory_limit;
uint16_t prefetchable_memory_base;
uint16_t prefetchable_memory_limit;
uint32_t prefetchable_base_upper;
uint32_t prefetchable_limit_upper;
uint16_t io_base_upper;
uint16_t io_limit_upper;
uint8_t capabilities_ptr;
uint8_t reserved_0[3];
uint32_t expansion_rom_address;
uint8_t interrupt_line;
uint8_t interrupt_pin;
uint16_t bridge_control;
*/
config->type1.base_addresses[0] = read_word(); if (err < 0) return err;
config->type1.base_addresses[1] = read_word(); if (err < 0) return err;
config->type1.primary_bus = read_byte(); if (err < 0) return err;
config->type1.secondary_bus = read_byte(); if (err < 0) return err;
config->type1.subordinate_bus = read_byte(); if (err < 0) return err;
config->type1.secondary_latency_timer = read_byte(); if (err < 0) return err;
config->type1.io_base = read_byte(); if (err < 0) return err;
config->type1.io_limit = read_byte(); if (err < 0) return err;
config->type1.secondary_status = read_half(); if (err < 0) return err;
config->type1.memory_base = read_half(); if (err < 0) return err;
config->type1.memory_limit = read_half(); if (err < 0) return err;
config->type1.prefetchable_memory_base = read_half(); if (err < 0) return err;
config->type1.prefetchable_memory_limit = read_half(); if (err < 0) return err;
config->type1.prefetchable_base_upper = read_word(); if (err < 0) return err;
config->type1.prefetchable_limit_upper = read_word(); if (err < 0) return err;
config->type1.io_base_upper = read_half(); if (err < 0) return err;
config->type1.io_limit_upper = read_half(); if (err < 0) return err;
config->type1.capabilities_ptr = read_byte(); if (err < 0) return err;
next_index += 3; // 3 reserved bytes
config->type1.expansion_rom_address = read_word(); if (err < 0) return err;
config->type1.interrupt_line = read_byte(); if (err < 0) return err;
config->type1.interrupt_pin = read_byte(); if (err < 0) return err;
config->type1.bridge_control = read_half(); if (err < 0) return err;
DEBUG_ASSERT(next_index == 0x40); // should have read this many bytes at this point
} else {
// cant handle other types
return ERR_NOT_VALID;
}
*count = options.size / sizeof(irq_routing_entry);
return res;
}
status_t pci_set_irq_hw_int(const pci_location_t *state, uint8_t int_pin, uint8_t irq) {
if (!pcib) return ERR_NOT_CONFIGURED;
AutoSpinLock guard(&lock);
int res = pcib->set_irq_hw_int(state, int_pin, irq);
return res;
return NO_ERROR;
}
status_t pci_init_legacy() {
@@ -161,6 +266,7 @@ status_t pci_init_legacy() {
if ((pcib = pci_bios32::detect())) {
dprintf(INFO, "PCI: pci bios functions installed\n");
dprintf(INFO, "PCI: last pci bus is %d\n", pcib->get_last_bus());
pci_bus_mgr_init();
return NO_ERROR;
}
@@ -168,6 +274,7 @@ status_t pci_init_legacy() {
if ((pcib = pci_type1::detect())) {
dprintf(INFO, "PCI: pci type1 functions installed\n");
dprintf(INFO, "PCI: last pci bus is %d\n", pcib->get_last_bus());
pci_bus_mgr_init();
return NO_ERROR;
}
@@ -183,6 +290,7 @@ status_t pci_init_ecam(paddr_t ecam_base, uint16_t segment, uint8_t start_bus, u
if ((pcib = pci_ecam::detect(ecam_base, segment, start_bus, end_bus))) {
dprintf(INFO, "PCI: pci ecam functions installed\n");
dprintf(INFO, "PCI: last pci bus is %d\n", pcib->get_last_bus());
pci_bus_mgr_init();
return NO_ERROR;
}

View File

@@ -1,76 +0,0 @@
/*
* Copyright (c) 2021 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <stdint.h>
#include <lk/compiler.h>
#include <lk/cpp.h>
#include <lk/err.h>
#include <dev/bus/pci.h>
// Default implementation of a particular PCI bus/config accessor method.
// Intended to be subclassed and specialized based on specific method.
class pci_backend {
public:
constexpr pci_backend() = default;
virtual ~pci_backend() = default;
DISALLOW_COPY_ASSIGN_AND_MOVE(pci_backend);
public:
int get_last_bus() const { return last_bus_; }
// virtuals that a concrete implementation should override
virtual int find_pci_device(pci_location_t *state, uint16_t device_id, uint16_t vendor_id, uint16_t index) {
return ERR_NOT_CONFIGURED;
}
virtual int find_pci_class_code(pci_location_t *state, uint32_t class_code, uint16_t index) {
return ERR_NOT_CONFIGURED;
}
virtual int read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t *value) {
return ERR_NOT_CONFIGURED;
}
virtual int read_config_half(const pci_location_t *state, uint32_t reg, uint16_t *value) {
return ERR_NOT_CONFIGURED;
}
virtual int read_config_word(const pci_location_t *state, uint32_t reg, uint32_t *value) {
return ERR_NOT_CONFIGURED;
}
virtual int write_config_byte(const pci_location_t *state, uint32_t reg, uint8_t value) {
return ERR_NOT_CONFIGURED;
}
virtual int write_config_half(const pci_location_t *state, uint32_t reg, uint16_t value) {
return ERR_NOT_CONFIGURED;
}
virtual int write_config_word(const pci_location_t *state, uint32_t reg, uint32_t value) {
return ERR_NOT_CONFIGURED;
}
// TODO: highly bios32 specific
struct irq_routing_options_t {
uint16_t size;
void *offset;
uint16_t selector;
} __PACKED;
virtual int get_irq_routing_options(irq_routing_options_t *options, uint16_t *pci_irqs) {
return ERR_NOT_CONFIGURED;
}
virtual int set_irq_hw_int(const pci_location_t *state, uint8_t int_pin, uint8_t irq) {
return ERR_NOT_CONFIGURED;
}
protected:
// detection should find the last bus
void set_last_bus(int last_bus) { last_bus_ = last_bus; }
int last_bus_ = -1;
};

View File

@@ -12,8 +12,9 @@
#include <lk/compiler.h>
#include <dev/bus/pci.h>
#include "bios32.h"
#include "ecam.h"
#include "type1.h"
#include "backend/bios32.h"
#include "backend/ecam.h"
#include "backend/type1.h"
#include "bus_mgr.h"

View File

@@ -3,12 +3,17 @@ LOCAL_DIR := $(GET_LOCAL_DIR)
MODULE := $(LOCAL_DIR)
MODULE_SRCS += \
$(LOCAL_DIR)/bios32.cpp \
$(LOCAL_DIR)/bus_mgr.cpp \
$(LOCAL_DIR)/debug.cpp \
$(LOCAL_DIR)/ecam.cpp \
$(LOCAL_DIR)/pci.cpp \
$(LOCAL_DIR)/type1.cpp \
\
$(LOCAL_DIR)/backend/ecam.cpp \
$(LOCAL_DIR)/backend/bios32.cpp \
$(LOCAL_DIR)/backend/type1.cpp \
MODULE_DEPS += lib/libcpp
MODULE_CPPFLAGS += -Wno-invalid-offsetof
MODULE_COMPILEFLAGS += -Wmissing-declarations
include make/module.mk

212
dev/include/hw/pci.h Normal file
View File

@@ -0,0 +1,212 @@
/*
* Copyright (c) 2009 Corey Tabaka
* Copyright (c) 2021 Travis Geiseblrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <assert.h>
#include <sys/types.h>
#include <lk/compiler.h>
// This file contains defines and structures for the PCI bus independent
// of any devices or drivers that may use them.
__BEGIN_CDECLS
/*
* PCI configuration space offsets
*/
#define PCI_CONFIG_VENDOR_ID 0x00
#define PCI_CONFIG_DEVICE_ID 0x02
#define PCI_CONFIG_COMMAND 0x04
#define PCI_CONFIG_STATUS 0x06
#define PCI_CONFIG_REVISION_ID 0x08
#define PCI_CONFIG_CLASS_CODE 0x09
#define PCI_CONFIG_CLASS_CODE_INTR 0x09
#define PCI_CONFIG_CLASS_CODE_SUB 0x0a
#define PCI_CONFIG_CLASS_CODE_BASE 0x0b
#define PCI_CONFIG_CACHE_LINE_SIZE 0x0c
#define PCI_CONFIG_LATENCY_TIMER 0x0d
#define PCI_CONFIG_HEADER_TYPE 0x0e
#define PCI_CONFIG_BIST 0x0f
/* Type 0 */
#define PCI_CONFIG_BASE_ADDRESSES 0x10
#define PCI_CONFIG_CARDBUS_CIS_PTR 0x28
#define PCI_CONFIG_SUBSYS_VENDOR_ID 0x2c
#define PCI_CONFIG_SUBSYS_ID 0x2e
#define PCI_CONFIG_EXP_ROM_ADDRESS 0x30
#define PCI_CONFIG_CAPABILITIES 0x34
#define PCI_CONFIG_INTERRUPT_LINE 0x3c
#define PCI_CONFIG_INTERRUPT_PIN 0x3d
#define PCI_CONFIG_MIN_GRANT 0x3e
#define PCI_CONFIG_MAX_LATENCY 0x3f
/*
* PCI header type register bits
*/
#define PCI_HEADER_TYPE_MASK 0x7f
#define PCI_HEADER_TYPE_MULTI_FN 0x80
/*
* PCI header types
*/
#define PCI_HEADER_TYPE_STANDARD 0x00
#define PCI_HEADER_TYPE_PCI_BRIDGE 0x01
#define PCI_HEADER_TYPE_CARD_BUS 0x02
/*
* PCI command register bits
*/
#define PCI_COMMAND_IO_EN 0x0001
#define PCI_COMMAND_MEM_EN 0x0002
#define PCI_COMMAND_BUS_MASTER_EN 0x0004
#define PCI_COMMAND_SPECIAL_EN 0x0008
#define PCI_COMMAND_MEM_WR_INV_EN 0x0010
#define PCI_COMMAND_PAL_SNOOP_EN 0x0020
#define PCI_COMMAND_PERR_RESP_EN 0x0040
#define PCI_COMMAND_AD_STEP_EN 0x0080
#define PCI_COMMAND_SERR_EN 0x0100
#define PCI_COMMAND_FAST_B2B_EN 0x0200
/*
* PCI status register bits
*/
#define PCI_STATUS_NEW_CAPS 0x0010
#define PCI_STATUS_66_MHZ 0x0020
#define PCI_STATUS_FAST_B2B 0x0080
#define PCI_STATUS_MSTR_PERR 0x0100
#define PCI_STATUS_DEVSEL_MASK 0x0600
#define PCI_STATUS_TARG_ABORT_SIG 0x0800
#define PCI_STATUS_TARG_ABORT_RCV 0x1000
#define PCI_STATUS_MSTR_ABORT_RCV 0x2000
#define PCI_STATUS_SERR_SIG 0x4000
#define PCI_STATUS_PERR 0x8000
/* structure version of the standard pci config space */
typedef struct {
uint16_t vendor_id;
uint16_t device_id;
uint16_t command;
uint16_t status;
uint8_t revision_id_0;
uint8_t program_interface;
uint8_t sub_class;
uint8_t base_class;
uint8_t cache_line_size;
uint8_t latency_timer;
uint8_t header_type;
uint8_t bist;
/* offset 0x10 */
union {
struct {
uint32_t base_addresses[6];
uint32_t cardbus_cis_ptr;
uint16_t subsystem_vendor_id;
uint16_t subsystem_id;
uint32_t expansion_rom_address;
uint8_t capabilities_ptr;
uint8_t reserved_0[3];
uint32_t reserved_1;
uint8_t interrupt_line;
uint8_t interrupt_pin;
uint8_t min_grant;
uint8_t max_latency;
} type0; // configuration for normal devices
struct {
uint32_t base_addresses[2];
uint8_t primary_bus;
uint8_t secondary_bus;
uint8_t subordinate_bus;
uint8_t secondary_latency_timer;
uint8_t io_base;
uint8_t io_limit;
uint16_t secondary_status;
uint16_t memory_base;
uint16_t memory_limit;
uint16_t prefetchable_memory_base;
uint16_t prefetchable_memory_limit;
uint32_t prefetchable_base_upper;
uint32_t prefetchable_limit_upper;
uint16_t io_base_upper;
uint16_t io_limit_upper;
uint8_t capabilities_ptr;
uint8_t reserved_0[3];
uint32_t expansion_rom_address;
uint8_t interrupt_line;
uint8_t interrupt_pin;
uint16_t bridge_control;
} type1; // configuration for bridge devices
};
} __PACKED pci_config_t;
static_assert(sizeof(pci_config_t) == 0x40, "");
/* Class/subclass codes (incomplete) */
#define PCI_SUBCLASS_OTHER 0x80 // common 'other' in many of the subclasses
#define PCI_CLASS_UNCLASSIFIED 0x0
#define PCI_SUBCLASS_NON_VGA_UNCLASSIFIED 0x0
#define PCI_SUBCLASS_VGA_UNCLASSIFIED 0x1
#define PCI_CLASS_MASS_STORAGE 0x1
#define PCI_SUBCLASS_SCSI 0x0
#define PCI_SUBCLASS_IDE 0x1
#define PCI_SUBCLASS_FLOPPY 0x2
#define PCI_SUBCLASS_IPI 0x3
#define PCI_SUBCLASS_RAID 0x4
#define PCI_SUBCLASS_ATA 0x5
#define PCI_SUBCLASS_SERIAL_ATA 0x6
#define PCI_SUBCLASS_SAS 0x7
#define PCI_SUBCLASS_NON_VOLATILE 0x8
#define PCI_CLASS_NETWORK 0x2
#define PCI_SUBCLASS_ETHERNET 0x0
#define PCI_SUBCLASS_TOKEN_RING 0x1
#define PCI_SUBCLASS_FDDI 0x2
#define PCI_SUBCLASS_ATM 0x3
#define PCI_SUBCLASS_ISDN 0x4
#define PCI_SUBCLASS_WORLDFIP 0x5
#define PCI_SUBCLASS_PICMG 0x6
#define PCI_SUBCLASS_INFINIBAND 0x7
#define PCI_CLASS_DISPLAY 0x3
#define PCI_SUBCLASS_VGA 0x0
#define PCI_SUBCLASS_XGA 0x1
#define PCI_SUBCLASS_3D 0x2
#define PCI_CLASS_MULTIMEDIA 0x4
#define PCI_CLASS_MEMORY 0x5
#define PCI_CLASS_BRIDGE 0x6
#define PCI_SUBCLASS_HOST_BRIDGE 0x0
#define PCI_SUBCLASS_ISA_BRIDGE 0x1
#define PCI_SUBCLASS_EISA_BRIDGE 0x2
#define PCI_SUBCLASS_MCA_BRIDGE 0x3
#define PCI_SUBCLASS_PCI_PCI_BRIDGE 0x4
#define PCI_SUBCLASS_PCMCIA_BRIDGE 0x5
#define PCI_SUBCLASS_NUBUS_BRIDGE 0x6
#define PCI_SUBCLASS_CARDBUS_BRIDGE 0x7
#define PCI_SUBCLASS_RACEWAY_BRIDGE 0x8
#define PCI_SUBCLASS_PCI_PCI_BRIDGE2 0x9
#define PCI_SUBCLASS_INFINIBAND_TO_PCI_BRIDGE 0xa
#define PCI_CLASS_SIMPLE_COMMS 0x7
#define PCI_CLASS_BASE_PERIPH 0x8
#define PCI_CLASS_INPUT 0x9
#define PCI_CLASS_DOCKING_STATION 0xa
#define PCI_CLASS_PROCESSOR 0xb
#define PCI_CLASS_SERIAL_BUS 0xc
#define PCI_CLASS_WIRELESS 0xd
#define PCI_CLASS_INTELLIGENT_CONTROLLER 0xe
#define PCI_CLASS_SATELLITE 0xf
#define PCI_CLASS_ENCRYPTION 0x10
#define PCI_CLASS_SIGNAL_PROCESSING 0x11
#define PCI_CLASS_PROCESSING_ACCEL 0x12
#define PCI_CLASS_COPROCESSOR 0x40
#define PCI_CLASS_UNASSIGNED 0xff
__END_CDECLS

View File

@@ -70,7 +70,7 @@ struct pcnet_state {
};
static status_t pcnet_init(struct device *dev);
static status_t pcnet_read_pci_config(struct device *dev, pci_location_t *loc);
static status_t pcnet_read_pci_config(struct device *dev, pci_location_t loc);
static enum handler_return pcnet_irq_handler(void *arg);
@@ -136,7 +136,7 @@ static status_t pcnet_init(struct device *dev) {
if (!config)
return ERR_NOT_CONFIGURED;
if (pci_find_pci_device(&loc, config->device_id, config->vendor_id, config->index) != _PCI_SUCCESSFUL) {
if (pci_bus_mgr_find_device(&loc, config->device_id, config->vendor_id, config->index) != _PCI_SUCCESSFUL) {
TRACEF("device not found\n");
return ERR_NOT_FOUND;
}
@@ -147,7 +147,7 @@ static status_t pcnet_init(struct device *dev) {
dev->state = state;
res = pcnet_read_pci_config(dev, &loc);
res = pcnet_read_pci_config(dev, loc);
if (res)
goto error;
@@ -270,26 +270,24 @@ error:
return res;
}
static status_t pcnet_read_pci_config(struct device *dev, pci_location_t *loc) {
static status_t pcnet_read_pci_config(struct device *dev, pci_location_t loc) {
status_t res = NO_ERROR;
pci_config_t config;
uint8_t *buf = (uint8_t *) &config;
unsigned i;
DEBUG_ASSERT(dev->state);
struct pcnet_state *state = dev->state;
for (i=0; i < sizeof(config); i++)
pci_read_config_byte(loc, i, buf + i);
pci_read_config(loc, &config);
LTRACEF("Resources:\n");
for (i=0; i < countof(config.base_addresses); i++) {
if (config.base_addresses[i] & 0x1) {
LTRACEF(" BAR %d I/O REG: %04x\n", i, config.base_addresses[i] & ~0x3);
for (i=0; i < countof(config.type0.base_addresses); i++) {
if (config.type0.base_addresses[i] & 0x1) {
LTRACEF(" BAR %d I/O REG: %04x\n", i, config.type0.base_addresses[i] & ~0x3);
state->base = config.base_addresses[i] & ~0x3;
state->base = config.type0.base_addresses[i] & ~0x3;
break;
}
}
@@ -299,10 +297,10 @@ static status_t pcnet_read_pci_config(struct device *dev, pci_location_t *loc) {
goto error;
}
if (config.interrupt_line != 0xff) {
LTRACEF(" IRQ %u\n", config.interrupt_line);
if (config.type0.interrupt_line != 0xff) {
LTRACEF(" IRQ %u\n", config.type0.interrupt_line);
state->irq = config.interrupt_line + INT_BASE;
state->irq = config.type0.interrupt_line + INT_BASE;
} else {
res = ERR_NOT_CONFIGURED;
goto error;

View File

@@ -21,7 +21,21 @@ status_t mask_interrupt(unsigned int vector);
status_t unmask_interrupt(unsigned int vector);
typedef enum handler_return (*int_handler)(void *arg);
void register_int_handler(unsigned int vector, int_handler handler, void *arg);
/* Register a MSI interrupt handler. Basically the same as register_int_handler, except
* interrupt controller may have additional setup.
*/
void register_int_handler_msi(unsigned int vector, int_handler handler, void *arg, bool edge);
/* Allocate a run of interrupts with alignment log2 in a platform specific way.
* Used for PCI MSI and possibly other use cases.
*/
status_t platform_allocate_interrupts(size_t count, uint align_log2, unsigned int *vector);
/* Map the incoming interrupt line number from the pci bus config to raw
* vector number, usable in the above apis.
*/
status_t platform_pci_int_to_vector(unsigned int pci_int, unsigned int *vector);
__END_CDECLS

View File

@@ -18,11 +18,14 @@
#include <platform/ide.h>
#include <platform/pc.h>
#include <platform.h>
#include <dev/bus/pci.h>
#include <dev/driver.h>
#include <dev/class/block.h>
#include <kernel/event.h>
#if WITH_DEV_BUS_PCI
#include <dev/bus/pci.h>
#endif
#define LOCAL_TRACE 0
// status register bits
@@ -200,8 +203,6 @@ static void ide_lba_setup(struct device *dev, uint32_t addr, int index);
static status_t ide_init(struct device *dev) {
status_t res = NO_ERROR;
uint32_t i;
int err;
if (!dev)
return ERR_INVALID_ARGS;
@@ -221,37 +222,42 @@ static status_t ide_init(struct device *dev) {
// attempt pci detection
if (config->legacy_index == 0x80 || config->legacy_index == 0x81) {
#if WITH_DEV_BUS_PCI
pci_location_t loc;
pci_config_t pci_config;
err = pci_find_pci_class_code(&loc, 0x010180, 0);
if (err != _PCI_SUCCESSFUL) {
status_t err = pci_bus_mgr_find_device_by_class(&loc, PCI_CLASS_MASS_STORAGE, PCI_SUBCLASS_IDE, 0x80, 0);
if (err != NO_ERROR) {
LTRACEF("Failed to find PCI IDE device\n");
res = ERR_NOT_FOUND;
goto err;
}
LTRACEF("Found PCI IDE device at %02x:%02x\n", loc.bus, loc.dev_fn);
LTRACEF("Found PCI IDE device at %04x:%02x:%02x.%02x\n", loc.segment, loc.bus, loc.dev, loc.fn);
for (i=0; i < sizeof(pci_config) / sizeof(uint32_t); i++) {
for (size_t i=0; i < sizeof(pci_config) / sizeof(uint32_t); i++) {
uint32_t reg = sizeof(uint32_t) * i;
err = pci_read_config_word(&loc, reg, ((uint32_t *) &pci_config) + i);
if (err != _PCI_SUCCESSFUL) {
err = pci_read_config_word(loc, reg, ((uint32_t *) &pci_config) + i);
if (err != NO_ERROR) {
LTRACEF("Failed to read config reg %d: 0x%02x\n", reg, err);
res = ERR_NOT_CONFIGURED;
goto err;
}
}
for (i=0; i < 6; i++) {
LTRACEF("BAR[%d]: 0x%08x\n", i, pci_config.base_addresses[i]);
for (int i=0; i < 6; i++) {
LTRACEF("BAR[%d]: 0x%08x\n", i, pci_config.type0.base_addresses[i]);
}
// TODO: fill this in from the bars
state->irq = ide_device_irqs[config->legacy_index & 0x7f];
state->regs = ide_device_regs[config->legacy_index & 0x7f];
state->type[0] = state->type[1] = TYPE_NONE;
#else
res = ERR_NOT_CONFIGURED;
goto err;
#endif// PCI
} else {
// legacy isa
DEBUG_ASSERT(config->legacy_index < 2);

View File

@@ -14,7 +14,7 @@
/* NOTE: keep arch/x86/crt0.S in sync with these definitions */
/* interrupts */
#define INT_VECTORS 0x31
#define INT_VECTORS 256
/* defined interrupts */
#define INT_BASE 0x20
@@ -30,10 +30,14 @@
#define INT_IDE0 0x2e
#define INT_IDE1 0x2f
/* dynamic interrupts are allocated in this range */
#define INT_DYNAMIC_START 0x30
#define INT_DYNAMIC_END 0xef
/* APIC vectors */
#define INT_APIC_TIMER 0x22
#define INT_APIC_TIMER 0xf0
/* PIC remap bases */
#define PIC1_BASE 0x20
#define PIC2_BASE 0x28
#define INT_PIC1_BASE 0x20
#define INT_PIC2_BASE 0x28

View File

@@ -10,6 +10,7 @@
#include <lk/debug.h>
#include <lk/err.h>
#include <lk/reg.h>
#include <lk/trace.h>
#include <assert.h>
#include <kernel/thread.h>
#include <platform/interrupts.h>
@@ -19,130 +20,55 @@
#include "platform_p.h"
#include <platform/pc.h>
#define LOCAL_TRACE 0
static spin_lock_t lock;
#define PIC1 0x20
#define PIC2 0xA0
#define INTC_TYPE_INTERNAL 0
#define INTC_TYPE_PIC 1
#define INTC_TYPE_MSI 2
#define ICW1 0x11
#define ICW4 0x01
struct int_handler_struct {
struct int_vector {
int_handler handler;
void *arg;
struct {
uint allocated : 1;
uint type : 2; // INTC_TYPE
uint edge : 1; // edge vs level
} flags;
};
static struct int_handler_struct int_handler_table[INT_VECTORS];
/*
* Cached IRQ mask (enabled/disabled)
*/
static uint8_t irqMask[2];
/*
* init the PICs and remap them
*/
static void map(uint32_t pic1, uint32_t pic2) {
/* send ICW1 */
outp(PIC1, ICW1);
outp(PIC2, ICW1);
/* send ICW2 */
outp(PIC1 + 1, pic1); /* remap */
outp(PIC2 + 1, pic2); /* pics */
/* send ICW3 */
outp(PIC1 + 1, 4); /* IRQ2 -> connection to slave */
outp(PIC2 + 1, 2);
/* send ICW4 */
outp(PIC1 + 1, 5);
outp(PIC2 + 1, 1);
/* disable all IRQs */
outp(PIC1 + 1, 0xff);
outp(PIC2 + 1, 0xff);
irqMask[0] = 0xff;
irqMask[1] = 0xff;
}
static void enable(unsigned int vector, bool enable) {
if (vector >= PIC1_BASE && vector < PIC1_BASE + 8) {
vector -= PIC1_BASE;
uint8_t bit = 1 << vector;
if (enable && (irqMask[0] & bit)) {
irqMask[0] = inp(PIC1 + 1);
irqMask[0] &= ~bit;
outp(PIC1 + 1, irqMask[0]);
irqMask[0] = inp(PIC1 + 1);
} else if (!enable && !(irqMask[0] & bit)) {
irqMask[0] = inp(PIC1 + 1);
irqMask[0] |= bit;
outp(PIC1 + 1, irqMask[0]);
irqMask[0] = inp(PIC1 + 1);
}
} else if (vector >= PIC2_BASE && vector < PIC2_BASE + 8) {
vector -= PIC2_BASE;
uint8_t bit = 1 << vector;
if (enable && (irqMask[1] & bit)) {
irqMask[1] = inp(PIC2 + 1);
irqMask[1] &= ~bit;
outp(PIC2 + 1, irqMask[1]);
irqMask[1] = inp(PIC2 + 1);
} else if (!enable && !(irqMask[1] & bit)) {
irqMask[1] = inp(PIC2 + 1);
irqMask[1] |= bit;
outp(PIC2 + 1, irqMask[1]);
irqMask[1] = inp(PIC2 + 1);
}
bit = 1 << (INT_PIC2 - PIC1_BASE);
if (irqMask[1] != 0xff && (irqMask[0] & bit)) {
irqMask[0] = inp(PIC1 + 1);
irqMask[0] &= ~bit;
outp(PIC1 + 1, irqMask[0]);
irqMask[0] = inp(PIC1 + 1);
} else if (irqMask[1] == 0 && !(irqMask[0] & bit)) {
irqMask[0] = inp(PIC1 + 1);
irqMask[0] |= bit;
outp(PIC1 + 1, irqMask[0]);
irqMask[0] = inp(PIC1 + 1);
}
} else {
//dprintf(DEBUG, "Invalid PIC interrupt: %02x\n", vector);
}
}
static void issueEOI(unsigned int vector) {
if (vector >= PIC1_BASE && vector <= PIC1_BASE + 7) {
outp(PIC1, 0x20);
} else if (vector >= PIC2_BASE && vector <= PIC2_BASE + 7) {
outp(PIC2, 0x20);
outp(PIC1, 0x20); // must issue both for the second PIC
}
}
static struct int_vector int_table[INT_VECTORS];
void platform_init_interrupts(void) {
// rebase the PIC out of the way of processor exceptions
map(PIC1_BASE, PIC2_BASE);
pic_init();
lapic_init();
// initialize all of the vectors
for (int i = 0; i < INT_VECTORS; i++) {
if (i >= INT_PIC1_BASE && i <= INT_PIC2_BASE + 8) {
int_table[i].flags.type = INTC_TYPE_PIC;
}
if (i >= INT_DYNAMIC_START && i <= INT_DYNAMIC_END) {
int_table[i].flags.allocated = false;
} else {
int_table[i].flags.allocated = true;
}
}
}
status_t mask_interrupt(unsigned int vector) {
if (vector >= INT_VECTORS)
return ERR_INVALID_ARGS;
// dprintf(DEBUG, "%s: vector %d\n", __PRETTY_FUNCTION__, vector);
LTRACEF("vector %#x\n", vector);
spin_lock_saved_state_t state;
spin_lock_irqsave(&lock, state);
enable(vector, false);
if (int_table[vector].flags.type == INTC_TYPE_PIC) {
pic_enable(vector, false);
}
spin_unlock_irqrestore(&lock, state);
@@ -150,27 +76,18 @@ status_t mask_interrupt(unsigned int vector) {
}
static void platform_mask_irqs(void) {
irqMask[0] = inp(PIC1 + 1);
irqMask[1] = inp(PIC2 + 1);
outp(PIC1 + 1, 0xff);
outp(PIC2 + 1, 0xff);
irqMask[0] = inp(PIC1 + 1);
irqMask[1] = inp(PIC2 + 1);
}
status_t unmask_interrupt(unsigned int vector) {
if (vector >= INT_VECTORS)
return ERR_INVALID_ARGS;
// dprintf("%s: vector %d\n", __PRETTY_FUNCTION__, vector);
LTRACEF("vector %#x\n", vector);
spin_lock_saved_state_t state;
spin_lock_irqsave(&lock, state);
enable(vector, true);
if (int_table[vector].flags.type == INTC_TYPE_PIC) {
pic_enable(vector, true);
}
spin_unlock_irqrestore(&lock, state);
@@ -184,27 +101,98 @@ enum handler_return platform_irq(x86_iframe_t *frame) {
DEBUG_ASSERT(vector >= 0x20);
// deliver the interrupt
struct int_vector *handler = &int_table[vector];
// edge triggered interrupts are acked beforehand
if (handler->flags.edge) {
if (handler->flags.type == INTC_TYPE_MSI) {
lapic_eoi(vector);
} else {
pic_eoi(vector);
}
}
// call the registered interrupt handler
enum handler_return ret = INT_NO_RESCHEDULE;
if (handler->handler) {
ret = handler->handler(handler->arg);
}
if (int_handler_table[vector].handler)
ret = int_handler_table[vector].handler(int_handler_table[vector].arg);
// ack the interrupt
issueEOI(vector);
// level triggered ack
if (!handler->flags.edge) {
if (handler->flags.type == INTC_TYPE_MSI) {
lapic_eoi(vector);
} else {
pic_eoi(vector);
}
}
return ret;
}
void register_int_handler(unsigned int vector, int_handler handler, void *arg) {
if (vector >= INT_VECTORS)
panic("register_int_handler: vector out of range %d\n", vector);
static void register_int_handler_etc(unsigned int vector, int_handler handler, void *arg, bool edge, uint type) {
ASSERT(vector < INT_VECTORS);
spin_lock_saved_state_t state;
spin_lock_irqsave(&lock, state);
int_handler_table[vector].arg = arg;
int_handler_table[vector].handler = handler;
int_table[vector].arg = arg;
int_table[vector].handler = handler;
int_table[vector].flags.allocated = true;
int_table[vector].flags.edge = edge;
int_table[vector].flags.type = type;
spin_unlock_irqrestore(&lock, state);
}
void register_int_handler(unsigned int vector, int_handler handler, void *arg) {
register_int_handler_etc(vector, handler, arg, false, INTC_TYPE_PIC);
}
void register_int_handler_msi(unsigned int vector, int_handler handler, void *arg, bool edge) {
register_int_handler_etc(vector, handler, arg, edge, INTC_TYPE_MSI);
}
void platform_mask_irqs(void) {
pic_mask_interrupts();
}
status_t platform_pci_int_to_vector(unsigned int pci_int, unsigned int *vector) {
LTRACEF("pci_int %u\n", pci_int);
// pci interrupts are relative to PIC style irq #s so simply add INT_BASE to it
uint out_vector = pci_int + INT_BASE;
if (out_vector > INT_VECTORS) {
return ERR_INVALID_ARGS;
}
*vector = out_vector;
return NO_ERROR;
}
status_t platform_allocate_interrupts(size_t count, uint align_log2, unsigned int *vector) {
LTRACEF("count %zu, align %u\n", count, align_log2);
if (align_log2 > 1) {
PANIC_UNIMPLEMENTED;
}
spin_lock_saved_state_t state;
spin_lock_irqsave(&lock, state);
// find a free interrupt
status_t err = ERR_NOT_FOUND;
for (unsigned int i = 0; i < INT_VECTORS; i++) {
if (!int_table[i].flags.allocated) {
int_table[i].flags.allocated = true;
*vector = i;
LTRACEF("found irq %#x\n", i);
err = NO_ERROR;
break;
}
}
spin_unlock_irqrestore(&lock, state);
return err;
}

72
platform/pc/lapic.c Normal file
View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2021 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#include <sys/types.h>
#include <lk/debug.h>
#include <lk/err.h>
#include <lk/reg.h>
#include <lk/trace.h>
#include <lk/init.h>
#include <assert.h>
#include <kernel/thread.h>
#include <platform/interrupts.h>
#include <arch/ops.h>
#include <arch/x86.h>
#include <kernel/spinlock.h>
#include "platform_p.h"
#include <platform/pc.h>
#include <kernel/vm.h>
#define LOCAL_TRACE 0
static bool lapic_present = false;
static uint8_t *lapic_mmio;
void lapic_init(void) {
// discover the presence of the local apic and map it
LTRACE_ENTRY;
// check feature bit 9 in edx of leaf 1 for presence of lapic
uint32_t a, b, c, d;
cpuid(0x1, &a, &b, &c, &d);
LTRACEF("%#x %#x %#x %#x\n", a, b, c, d);
if ((d & (1 << 9)) == 0) {
// no lapic detected
return;
}
lapic_present = true;
}
void lapic_init_postvm(uint level) {
if (!lapic_present)
return;
// IA32_APIC_BASE_MSR
uint64_t apic_base = read_msr(0x1b);
LTRACEF("apic base %#llx\n", apic_base);
// TODO: assert that it's enabled
apic_base &= ~0xfff;
dprintf(INFO, "LAPIC: physical address %#llx\n", apic_base);
// map the lapic into the kernel since it's not guaranteed that the physmap covers it
status_t err = vmm_alloc_physical(vmm_get_kernel_aspace(), "lapic", PAGE_SIZE, (void **)&lapic_mmio, 0,
apic_base & ~0xfff, /* vmm_flags */ 0, ARCH_MMU_FLAG_UNCACHED_DEVICE);
ASSERT(err == NO_ERROR);
}
LK_INIT_HOOK(lapic, lapic_init_postvm, LK_INIT_LEVEL_VM);
void lapic_eoi(unsigned int vector) {
LTRACEF("vector %#x\n", vector);
if (lapic_present) {
*REG32(lapic_mmio + 0xb0) = 1;
}
}

138
platform/pc/pic.c Normal file
View File

@@ -0,0 +1,138 @@
/*
* Copyright (c) 2009 Corey Tabaka
* Copyright (c) 2015 Intel Corporation
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#include <sys/types.h>
#include <lk/debug.h>
#include <lk/err.h>
#include <lk/reg.h>
#include <lk/trace.h>
#include <assert.h>
#include <kernel/thread.h>
#include <platform/interrupts.h>
#include <arch/ops.h>
#include <arch/x86.h>
#include <kernel/spinlock.h>
#include "platform_p.h"
#include <platform/pc.h>
#define LOCAL_TRACE 1
/* PIC information */
/*
* Cached IRQ mask (enabled/disabled)
*/
static uint8_t irqMask[2];
#define PIC1 0x20
#define PIC2 0xA0
#define ICW1 0x11
#define ICW4 0x01
/*
* init the PICs and remap them
*/
static void map(uint32_t pic1, uint32_t pic2) {
/* send ICW1 */
outp(PIC1, ICW1);
outp(PIC2, ICW1);
/* send ICW2 */
outp(PIC1 + 1, pic1); /* remap */
outp(PIC2 + 1, pic2); /* pics */
/* send ICW3 */
outp(PIC1 + 1, 4); /* IRQ2 -> connection to slave */
outp(PIC2 + 1, 2);
/* send ICW4 */
outp(PIC1 + 1, 5);
outp(PIC2 + 1, 1);
/* disable all IRQs */
outp(PIC1 + 1, 0xff);
outp(PIC2 + 1, 0xff);
irqMask[0] = 0xff;
irqMask[1] = 0xff;
}
void pic_init(void) {
// rebase the PIC out of the way of processor exceptions
map(INT_PIC1_BASE, INT_PIC2_BASE);
}
void pic_enable(unsigned int vector, bool enable) {
if (vector >= INT_PIC1_BASE && vector < INT_PIC1_BASE + 8) {
vector -= INT_PIC1_BASE;
uint8_t bit = 1 << vector;
if (enable && (irqMask[0] & bit)) {
irqMask[0] = inp(PIC1 + 1);
irqMask[0] &= ~bit;
outp(PIC1 + 1, irqMask[0]);
irqMask[0] = inp(PIC1 + 1);
} else if (!enable && !(irqMask[0] & bit)) {
irqMask[0] = inp(PIC1 + 1);
irqMask[0] |= bit;
outp(PIC1 + 1, irqMask[0]);
irqMask[0] = inp(PIC1 + 1);
}
} else if (vector >= INT_PIC2_BASE && vector < INT_PIC2_BASE + 8) {
vector -= INT_PIC2_BASE;
uint8_t bit = 1 << vector;
if (enable && (irqMask[1] & bit)) {
irqMask[1] = inp(PIC2 + 1);
irqMask[1] &= ~bit;
outp(PIC2 + 1, irqMask[1]);
irqMask[1] = inp(PIC2 + 1);
} else if (!enable && !(irqMask[1] & bit)) {
irqMask[1] = inp(PIC2 + 1);
irqMask[1] |= bit;
outp(PIC2 + 1, irqMask[1]);
irqMask[1] = inp(PIC2 + 1);
}
bit = 1 << (INT_PIC2 - INT_PIC1_BASE);
if (irqMask[1] != 0xff && (irqMask[0] & bit)) {
irqMask[0] = inp(PIC1 + 1);
irqMask[0] &= ~bit;
outp(PIC1 + 1, irqMask[0]);
irqMask[0] = inp(PIC1 + 1);
} else if (irqMask[1] == 0 && !(irqMask[0] & bit)) {
irqMask[0] = inp(PIC1 + 1);
irqMask[0] |= bit;
outp(PIC1 + 1, irqMask[0]);
irqMask[0] = inp(PIC1 + 1);
}
}
}
void pic_eoi(unsigned int vector) {
if (vector >= INT_PIC1_BASE && vector <= INT_PIC1_BASE + 7) {
outp(PIC1, 0x20);
} else if (vector >= INT_PIC2_BASE && vector <= INT_PIC2_BASE + 7) {
outp(PIC2, 0x20);
outp(PIC1, 0x20); // must issue both for the second PIC
}
}
void pic_mask_interrupts(void) {
irqMask[0] = inp(PIC1 + 1);
irqMask[1] = inp(PIC2 + 1);
outp(PIC1 + 1, 0xff);
outp(PIC2 + 1, 0xff);
irqMask[0] = inp(PIC1 + 1);
irqMask[1] = inp(PIC2 + 1);
}

View File

@@ -17,7 +17,6 @@
#include <platform/multiboot.h>
#include <platform/console.h>
#include <platform/keyboard.h>
#include <dev/bus/pci.h>
#include <dev/uart.h>
#include <arch/x86.h>
#include <arch/mmu.h>
@@ -27,6 +26,13 @@
#include <kernel/vm.h>
#include <lib/acpi_lite.h>
#if WITH_DEV_BUS_PCI
#include <dev/bus/pci.h>
#endif
#if WITH_LIB_MINIP
#include <lib/minip.h>
#endif
#define LOCAL_TRACE 0
/* multiboot information passed in, if present */
@@ -253,6 +259,7 @@ void platform_init(void) {
platform_init_keyboard(&console_input_buf);
#if WITH_DEV_BUS_PCI
bool pci_initted = false;
if (acpi_lite_init(0) == NO_ERROR) {
if (LOCAL_TRACE) {
@@ -286,6 +293,7 @@ void platform_init(void) {
if (!pci_initted) {
pci_init_legacy();
}
#endif
platform_init_mmu_mappings();
}

View File

@@ -16,3 +16,13 @@ void platform_init_debug(void);
void platform_init_interrupts(void);
void platform_init_timer(void);
// legacy programmable interrupt controller
void pic_init(void);
void pic_enable(unsigned int vector, bool enable);
void pic_eoi(unsigned int vector);
void pic_mask_interrupts(void);
// local apic
void lapic_init(void);
void lapic_eoi(unsigned int vector);

View File

@@ -2,24 +2,29 @@ LOCAL_DIR := $(GET_LOCAL_DIR)
MODULE := $(LOCAL_DIR)
# two implementations, generic and legacy
# two implementations, modern and legacy
# legacy implies older hardware, pre pentium, pre pci
CPU ?= generic
CPU ?= modern
MODULE_DEPS += \
dev/bus/pci \
lib/acpi_lite \
lib/bio \
lib/cbuf
ifneq ($(CPU),legacy)
MODULE_DEPS += dev/bus/pci
endif
MODULE_SRCS += \
$(LOCAL_DIR)/console.c \
$(LOCAL_DIR)/debug.c \
$(LOCAL_DIR)/ide.c \
$(LOCAL_DIR)/interrupts.c \
$(LOCAL_DIR)/keyboard.c \
$(LOCAL_DIR)/lapic.c \
$(LOCAL_DIR)/pic.c \
$(LOCAL_DIR)/platform.c \
$(LOCAL_DIR)/timer.c \
$(LOCAL_DIR)/debug.c \
$(LOCAL_DIR)/console.c \
$(LOCAL_DIR)/keyboard.c \
$(LOCAL_DIR)/ide.c \
$(LOCAL_DIR)/uart.c \
LK_HEAP_IMPLEMENTATION ?= dlmalloc

View File

@@ -222,3 +222,13 @@ void platform_init(void) {
}
#endif
}
status_t platform_pci_int_to_vector(unsigned int pci_int, unsigned int *vector) {
// at the moment there's no translation between PCI IRQs and native irqs
*vector = pci_int;
return NO_ERROR;
}
status_t platform_allocate_interrupts(size_t count, uint align_log2, unsigned int *vector) {
return ERR_NOT_SUPPORTED;
}

View File

@@ -92,3 +92,12 @@ enum handler_return riscv_platform_irq(void) {
return ret;
}
status_t platform_pci_int_to_vector(unsigned int pci_int, unsigned int *vector) {
// at the moment there's no translation between PCI IRQs and native irqs
*vector = pci_int;
return NO_ERROR;
}
status_t platform_allocate_interrupts(size_t count, uint align_log2, unsigned int *vector) {
return ERR_NOT_SUPPORTED;
}