1 /*
2  * Copyright (c) 2009 Corey Tabaka
3  * Copyright (c) 2020 Travis Geiselbrecht
4  *
5  * Use of this source code is governed by a MIT-style
6  * license that can be found in the LICENSE file or at
7  * https://opensource.org/licenses/MIT
8  */
9 #include "ecam.h"
10 
11 #include <lk/debug.h>
12 #include <lk/err.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <kernel/thread.h>
16 #include <kernel/spinlock.h>
17 #include <dev/bus/pci.h>
18 #include <lk/trace.h>
19 
20 #if WITH_KERNEL_VM
21 #include <kernel/vm.h>
22 #endif
23 
24 #include "pci_priv.h"
25 
26 #define LOCAL_TRACE 0
27 
pci_ecam(paddr_t base,uint16_t segment,uint8_t start_bus,uint8_t end_bus)28 pci_ecam::pci_ecam(paddr_t base, uint16_t segment, uint8_t start_bus, uint8_t end_bus) :
29     base_(base), segment_(segment), start_bus_(start_bus), end_bus_(end_bus) {}
30 
~pci_ecam()31 pci_ecam::~pci_ecam() {
32     LTRACE_ENTRY;
33 #if WITH_KERNEL_VM
34     if (ecam_ptr_) {
35         vmm_free_region(vmm_get_kernel_aspace(), (vaddr_t)ecam_ptr_);
36     }
37 #endif
38 }
39 
detect(paddr_t base,uint16_t segment,uint8_t start_bus,uint8_t end_bus)40 pci_ecam *pci_ecam::detect(paddr_t base, uint16_t segment, uint8_t start_bus, uint8_t end_bus) {
41     LTRACEF("base %#lx, segment %hu, bus [%hhu...%hhu]\n", base, segment, start_bus, end_bus);
42 
43     // we only support a limited configuration at the moment
44     if (segment != 0 || start_bus != 0) {
45         return nullptr;
46     }
47 
48     auto ecam = new pci_ecam(base, segment, start_bus, end_bus);
49 
50     // initialize the object, which may fail
51     status_t err = ecam->initialize();
52     if (err != NO_ERROR) {
53         delete ecam;
54         return nullptr;
55     }
56 
57     return ecam;
58 }
59 
initialize()60 status_t pci_ecam::initialize() {
61     // compute the aperture size of this
62     size_t size = ((size_t)end_bus_ - (size_t)start_bus_ + 1) << 20; // each bus occupies 20 bits of address space
63     LTRACEF("aperture size %#zx\n", size);
64 
65 
66 #if WITH_KERNEL_VM
67     // try to map the aperture
68     //status_t vmm_alloc_physical(vmm_aspace_t *aspace, const char *name, size_t size, void **ptr, uint8_t align_log2, paddr_t paddr, uint vmm_flags, uint arch_mmu_flags)
69     status_t err = vmm_alloc_physical(vmm_get_kernel_aspace(), "pci_ecam", size, (void **)&ecam_ptr_, 0, base_, 0, ARCH_MMU_FLAG_UNCACHED_DEVICE);
70     LTRACEF("vmm_alloc_physical returns %d, ptr %p\n", err, ecam_ptr_);
71 
72     if (err != NO_ERROR) {
73         ecam_ptr_ = nullptr;
74         return err;
75     }
76 #else
77     // no vm, so can directly access the aperture
78     ecam_ptr_ = (uint8_t *)base_;
79 #endif
80 
81     set_last_bus(end_bus_);
82 
83     return NO_ERROR;
84 }
85 
86 // compute the offset into the ecam given the location and register offset
location_to_offset(const pci_location_t * state,uint32_t reg)87 inline size_t location_to_offset(const pci_location_t *state, uint32_t reg) {
88     // TODO: clamp or assert on invalid offset
89     size_t offset = (size_t)state->bus << 20;
90     offset += (size_t)state->dev_fn << 12;
91     offset += reg;
92     return offset;
93 }
94 
95 // templatized routines to access the pci config space using a specific type
96 template <typename T>
read_config(const pci_location_t * state,uint32_t reg,T * value,const uint8_t * ecam_ptr)97 inline int read_config(const pci_location_t *state, uint32_t reg, T *value, const uint8_t *ecam_ptr) {
98     auto off = location_to_offset(state, reg);
99 
100     *value = *reinterpret_cast<const volatile T *>(&ecam_ptr[off]);
101 
102     return NO_ERROR;
103 }
104 
105 template <typename T>
write_config(const pci_location_t * state,uint32_t reg,T value,uint8_t * ecam_ptr)106 inline int write_config(const pci_location_t *state, uint32_t reg, T value, uint8_t *ecam_ptr) {
107     auto off = location_to_offset(state, reg);
108 
109     *reinterpret_cast<volatile T *>(&ecam_ptr[off]) = value;
110 
111     return NO_ERROR;
112 }
113 
read_config_byte(const pci_location_t * state,uint32_t reg,uint8_t * value)114 int pci_ecam::read_config_byte(const pci_location_t *state, uint32_t reg, uint8_t *value) {
115     LTRACEF_LEVEL(2, "state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
116     return read_config(state, reg, value, ecam_ptr_);
117 }
118 
read_config_half(const pci_location_t * state,uint32_t reg,uint16_t * value)119 int pci_ecam::read_config_half(const pci_location_t *state, uint32_t reg, uint16_t *value) {
120     LTRACEF_LEVEL(2, "state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
121     return read_config(state, reg, value, ecam_ptr_);
122 }
123 
read_config_word(const pci_location_t * state,uint32_t reg,uint32_t * value)124 int pci_ecam::read_config_word(const pci_location_t *state, uint32_t reg, uint32_t *value) {
125     LTRACEF_LEVEL(2, "state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
126     return read_config(state, reg, value, ecam_ptr_);
127 }
128 
write_config_byte(const pci_location_t * state,uint32_t reg,uint8_t value)129 int pci_ecam::write_config_byte(const pci_location_t *state, uint32_t reg, uint8_t value) {
130     LTRACEF_LEVEL(2, "state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
131     return write_config(state, reg, value, ecam_ptr_);
132 }
133 
write_config_half(const pci_location_t * state,uint32_t reg,uint16_t value)134 int pci_ecam::write_config_half(const pci_location_t *state, uint32_t reg, uint16_t value) {
135     LTRACEF_LEVEL(2, "state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
136     return write_config(state, reg, value, ecam_ptr_);
137 }
138 
write_config_word(const pci_location_t * state,uint32_t reg,uint32_t value)139 int pci_ecam::write_config_word(const pci_location_t *state, uint32_t reg, uint32_t value) {
140     LTRACEF_LEVEL(2, "state bus %#hhx dev_fn %#hhx reg %#x\n", state->bus, state->dev_fn, reg);
141     return write_config(state, reg, value, ecam_ptr_);
142 }
143