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     // ask for 4MB aligned regions (log2 22) to help with the mmu on most architectures
69     //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)
70     status_t err = vmm_alloc_physical(vmm_get_kernel_aspace(), "pci_ecam", size, (void **)&ecam_ptr_, 22, base_, 0, ARCH_MMU_FLAG_UNCACHED_DEVICE);
71     LTRACEF("vmm_alloc_physical returns %d, ptr %p\n", err, ecam_ptr_);
72 
73     if (err != NO_ERROR) {
74         ecam_ptr_ = nullptr;
75         return err;
76     }
77 #else
78     // no vm, so can directly access the aperture
79     ecam_ptr_ = (uint8_t *)base_;
80 #endif
81 
82     set_last_bus(end_bus_);
83 
84     return NO_ERROR;
85 }
86 
87 // compute the offset into the ecam given the location and register offset
location_to_offset(const pci_location_t state,uint32_t reg)88 inline size_t location_to_offset(const pci_location_t state, uint32_t reg) {
89     //
90     // | 27 - 20 | 19 - 15 | 14 - 12     |  11 - 8          | 7 - 2       | 1 - 0       |
91     // | Bus Nr  | Dev Nr  | Function Nr | Ext. Register Nr | Register Nr | Byte Enable |
92 
93     // TODO: clamp or assert on invalid offset
94     size_t offset = (size_t)state.bus << 20;
95     offset += (size_t)state.dev << 15;
96     offset += (size_t)state.fn << 12;
97     offset += reg;
98     return offset;
99 }
100 
101 // templatized routines to access the pci config space using a specific type
102 template <typename T>
read_config(const pci_location_t state,uint32_t reg,T * value,const uint8_t * ecam_ptr)103 inline int read_config(const pci_location_t state, uint32_t reg, T *value, const uint8_t *ecam_ptr) {
104     auto off = location_to_offset(state, reg);
105 
106     *value = *reinterpret_cast<const volatile T *>(&ecam_ptr[off]);
107 
108     return NO_ERROR;
109 }
110 
111 template <typename T>
write_config(const pci_location_t state,uint32_t reg,T value,uint8_t * ecam_ptr)112 inline int write_config(const pci_location_t state, uint32_t reg, T value, uint8_t *ecam_ptr) {
113     auto off = location_to_offset(state, reg);
114 
115     *reinterpret_cast<volatile T *>(&ecam_ptr[off]) = value;
116 
117     return NO_ERROR;
118 }
119 
read_config_byte(const pci_location_t state,uint32_t reg,uint8_t * value)120 int pci_ecam::read_config_byte(const pci_location_t state, uint32_t reg, uint8_t *value) {
121     LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
122     return read_config(state, reg, value, ecam_ptr_);
123 }
124 
read_config_half(const pci_location_t state,uint32_t reg,uint16_t * value)125 int pci_ecam::read_config_half(const pci_location_t state, uint32_t reg, uint16_t *value) {
126     LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
127     return read_config(state, reg, value, ecam_ptr_);
128 }
129 
read_config_word(const pci_location_t state,uint32_t reg,uint32_t * value)130 int pci_ecam::read_config_word(const pci_location_t state, uint32_t reg, uint32_t *value) {
131     LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
132     return read_config(state, reg, value, ecam_ptr_);
133 }
134 
write_config_byte(const pci_location_t state,uint32_t reg,uint8_t value)135 int pci_ecam::write_config_byte(const pci_location_t state, uint32_t reg, uint8_t value) {
136     LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
137     return write_config(state, reg, value, ecam_ptr_);
138 }
139 
write_config_half(const pci_location_t state,uint32_t reg,uint16_t value)140 int pci_ecam::write_config_half(const pci_location_t state, uint32_t reg, uint16_t value) {
141     LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
142     return write_config(state, reg, value, ecam_ptr_);
143 }
144 
write_config_word(const pci_location_t state,uint32_t reg,uint32_t value)145 int pci_ecam::write_config_word(const pci_location_t state, uint32_t reg, uint32_t value) {
146     LTRACEF_LEVEL(2, "state bus %#hhx dev %#hhx %#hhx reg %#x\n", state.bus, state.dev, state.fn, reg);
147     return write_config(state, reg, value, ecam_ptr_);
148 }
149