Serenity Operating System
1/*
2 * Copyright (c) 2020, Liav A. <liavalb@hotmail.co.il>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/Optional.h>
28#include <AK/StringView.h>
29#include <Kernel/PCI/MMIOAccess.h>
30#include <Kernel/VM/MemoryManager.h>
31
32namespace Kernel {
33namespace PCI {
34
35class MMIOSegment {
36public:
37 MMIOSegment(PhysicalAddress, u8, u8);
38 u8 get_start_bus();
39 u8 get_end_bus();
40 size_t get_size();
41 PhysicalAddress get_paddr();
42
43private:
44 PhysicalAddress m_base_addr;
45 u8 m_start_bus;
46 u8 m_end_bus;
47};
48
49#define PCI_MMIO_CONFIG_SPACE_SIZE 4096
50
51uint32_t MMIOAccess::segment_count() const
52{
53 return m_segments.size();
54}
55
56uint8_t MMIOAccess::segment_start_bus(u32 seg) const
57{
58 auto segment = m_segments.get(seg);
59 ASSERT(segment.has_value());
60 return segment.value().get_start_bus();
61}
62
63uint8_t MMIOAccess::segment_end_bus(u32 seg) const
64{
65 auto segment = m_segments.get(seg);
66 ASSERT(segment.has_value());
67 return segment.value().get_end_bus();
68}
69
70void MMIOAccess::initialize(PhysicalAddress mcfg)
71{
72 if (!Access::is_initialized())
73 new MMIOAccess(mcfg);
74}
75
76MMIOAccess::MMIOAccess(PhysicalAddress p_mcfg)
77 : m_mcfg(p_mcfg)
78 , m_mapped_address(ChangeableAddress(0xFFFF, 0xFF, 0xFF, 0xFF))
79{
80 klog() << "PCI: Using MMIO for PCI configuration space access";
81 m_mmio_window_region = MM.allocate_kernel_region(PAGE_ROUND_UP(PCI_MMIO_CONFIG_SPACE_SIZE), "PCI MMIO", Region::Access::Read | Region::Access::Write);
82
83 auto checkup_region = MM.allocate_kernel_region(p_mcfg.page_base(), (PAGE_SIZE * 2), "PCI MCFG Checkup", Region::Access::Read | Region::Access::Write);
84#ifdef PCI_DEBUG
85 dbg() << "PCI: Checking MCFG Table length to choose the correct mapping size";
86#endif
87
88 auto* sdt = (ACPI::Structures::SDTHeader*)checkup_region->vaddr().offset(p_mcfg.offset_in_page()).as_ptr();
89 u32 length = sdt->length;
90 u8 revision = sdt->revision;
91
92 klog() << "PCI: MCFG, length - " << length << ", revision " << revision;
93 checkup_region->unmap();
94
95 auto mcfg_region = MM.allocate_kernel_region(p_mcfg.page_base(), PAGE_ROUND_UP(length) + PAGE_SIZE, "PCI Parsing MCFG", Region::Access::Read | Region::Access::Write);
96
97 auto& mcfg = *(ACPI::Structures::MCFG*)mcfg_region->vaddr().offset(p_mcfg.offset_in_page()).as_ptr();
98#ifdef PCI_DEBUG
99 dbg() << "PCI: Checking MCFG @ V " << &mcfg << ", P 0x" << String::format("%x", p_mcfg.get());
100#endif
101
102 for (u32 index = 0; index < ((mcfg.header.length - sizeof(ACPI::Structures::MCFG)) / sizeof(ACPI::Structures::PCI_MMIO_Descriptor)); index++) {
103 u8 start_bus = mcfg.descriptors[index].start_pci_bus;
104 u8 end_bus = mcfg.descriptors[index].end_pci_bus;
105 u32 lower_addr = mcfg.descriptors[index].base_addr;
106
107 m_segments.set(index, { PhysicalAddress(lower_addr), start_bus, end_bus });
108 klog() << "PCI: New PCI segment @ " << PhysicalAddress(lower_addr) << ", PCI buses (" << start_bus << "-" << end_bus << ")";
109 }
110 mcfg_region->unmap();
111 klog() << "PCI: MMIO segments - " << m_segments.size();
112 InterruptDisabler disabler;
113#ifdef PCI_DEBUG
114 dbg() << "PCI: mapped address (" << String::format("%w", m_mapped_address.seg()) << ":" << String::format("%b", m_mapped_address.bus()) << ":" << String::format("%b", m_mapped_address.slot()) << "." << String::format("%b", m_mapped_address.function()) << ")";
115#endif
116 map_device(Address(0, 0, 0, 0));
117#ifdef PCI_DEBUG
118 dbg() << "PCI: Default mapped address (" << String::format("%w", m_mapped_address.seg()) << ":" << String::format("%b", m_mapped_address.bus()) << ":" << String::format("%b", m_mapped_address.slot()) << "." << String::format("%b", m_mapped_address.function()) << ")";
119#endif
120}
121
122void MMIOAccess::map_device(Address address)
123{
124 if (m_mapped_address == address)
125 return;
126 // FIXME: Map and put some lock!
127 ASSERT_INTERRUPTS_DISABLED();
128 auto segment = m_segments.get(address.seg());
129 ASSERT(segment.has_value());
130 PhysicalAddress segment_lower_addr = segment.value().get_paddr();
131 PhysicalAddress device_physical_mmio_space = segment_lower_addr.offset(
132 PCI_MMIO_CONFIG_SPACE_SIZE * address.function() + (PCI_MMIO_CONFIG_SPACE_SIZE * PCI_MAX_FUNCTIONS_PER_DEVICE) * address.slot() + (PCI_MMIO_CONFIG_SPACE_SIZE * PCI_MAX_FUNCTIONS_PER_DEVICE * PCI_MAX_DEVICES_PER_BUS) * (address.bus() - segment.value().get_start_bus()));
133
134#ifdef PCI_DEBUG
135 dbg() << "PCI: Mapping device @ pci (" << String::format("%w", address.seg()) << ":" << String::format("%b", address.bus()) << ":" << String::format("%b", address.slot()) << "." << String::format("%b", address.function()) << ")"
136 << " V 0x" << String::format("%x", m_mmio_window_region->vaddr().get()) << " P 0x" << String::format("%x", device_physical_mmio_space.get());
137#endif
138 m_mmio_window_region->vmobject().physical_pages()[0] = PhysicalPage::create(device_physical_mmio_space, false, false);
139 m_mmio_window_region->remap();
140 m_mapped_address = address;
141}
142
143u8 MMIOAccess::read8_field(Address address, u32 field)
144{
145 InterruptDisabler disabler;
146 ASSERT(field <= 0xfff);
147#ifdef PCI_DEBUG
148 dbg() << "PCI: Reading field " << field << ", Address(" << String::format("%w", address.seg()) << ":" << String::format("%b", address.bus()) << ":" << String::format("%b", address.slot()) << "." << String::format("%b", address.function()) << ")";
149#endif
150 map_device(address);
151 return *((u8*)(m_mmio_window_region->vaddr().get() + (field & 0xfff)));
152}
153
154u16 MMIOAccess::read16_field(Address address, u32 field)
155{
156 InterruptDisabler disabler;
157 ASSERT(field < 0xfff);
158#ifdef PCI_DEBUG
159 dbg() << "PCI: Reading field " << field << ", Address(" << String::format("%w", address.seg()) << ":" << String::format("%b", address.bus()) << ":" << String::format("%b", address.slot()) << "." << String::format("%b", address.function()) << ")";
160#endif
161 map_device(address);
162 return *((u16*)(m_mmio_window_region->vaddr().get() + (field & 0xfff)));
163}
164
165u32 MMIOAccess::read32_field(Address address, u32 field)
166{
167 InterruptDisabler disabler;
168 ASSERT(field <= 0xffc);
169#ifdef PCI_DEBUG
170 dbg() << "PCI: Reading field " << field << ", Address(" << String::format("%w", address.seg()) << ":" << String::format("%b", address.bus()) << ":" << String::format("%b", address.slot()) << "." << String::format("%b", address.function()) << ")";
171#endif
172 map_device(address);
173 return *((u32*)(m_mmio_window_region->vaddr().get() + (field & 0xfff)));
174}
175
176void MMIOAccess::write8_field(Address address, u32 field, u8 value)
177{
178 InterruptDisabler disabler;
179 ASSERT(field <= 0xfff);
180#ifdef PCI_DEBUG
181 dbg() << "PCI: Writing to field " << field << ", Address(" << String::format("%w", address.seg()) << ":" << String::format("%b", address.bus()) << ":" << String::format("%b", address.slot()) << "." << String::format("%b", address.function()) << ") value 0x" << String::format("%x", value);
182#endif
183 map_device(address);
184 *((u8*)(m_mmio_window_region->vaddr().get() + (field & 0xfff))) = value;
185}
186void MMIOAccess::write16_field(Address address, u32 field, u16 value)
187{
188 InterruptDisabler disabler;
189 ASSERT(field < 0xfff);
190#ifdef PCI_DEBUG
191 dbg() << "PCI: Writing to field " << field << ", Address(" << String::format("%w", address.seg()) << ":" << String::format("%b", address.bus()) << ":" << String::format("%b", address.slot()) << "." << String::format("%b", address.function()) << ") value 0x" << String::format("%x", value);
192#endif
193 map_device(address);
194 *((u16*)(m_mmio_window_region->vaddr().get() + (field & 0xfff))) = value;
195}
196void MMIOAccess::write32_field(Address address, u32 field, u32 value)
197{
198 InterruptDisabler disabler;
199 ASSERT(field <= 0xffc);
200#ifdef PCI_DEBUG
201 dbg() << "PCI: Writing to field " << field << ", Address(" << String::format("%w", address.seg()) << ":" << String::format("%b", address.bus()) << ":" << String::format("%b", address.slot()) << "." << String::format("%b", address.function()) << ") value 0x" << String::format("%x", value);
202#endif
203 map_device(address);
204 *((u32*)(m_mmio_window_region->vaddr().get() + (field & 0xfff))) = value;
205}
206
207void MMIOAccess::enumerate_all(Function<void(Address, ID)>& callback)
208{
209 for (u16 seg = 0; seg < m_segments.size(); seg++) {
210#ifdef PCI_DEBUG
211 dbg() << "PCI: Enumerating Memory mapped IO segment " << seg;
212#endif
213 // Single PCI host controller.
214 if ((read8_field(Address(seg), PCI_HEADER_TYPE) & 0x80) == 0) {
215 enumerate_bus(-1, 0, callback);
216 return;
217 }
218
219 // Multiple PCI host controllers.
220 for (u8 function = 0; function < 8; ++function) {
221 if (read16_field(Address(seg, 0, 0, function), PCI_VENDOR_ID) == PCI_NONE)
222 break;
223 enumerate_bus(-1, function, callback);
224 }
225 }
226}
227
228MMIOSegment::MMIOSegment(PhysicalAddress segment_base_addr, u8 start_bus, u8 end_bus)
229 : m_base_addr(segment_base_addr)
230 , m_start_bus(start_bus)
231 , m_end_bus(end_bus)
232{
233}
234
235u8 MMIOSegment::get_start_bus()
236{
237 return m_start_bus;
238}
239
240u8 MMIOSegment::get_end_bus()
241{
242 return m_end_bus;
243}
244
245size_t MMIOSegment::get_size()
246{
247 return (PCI_MMIO_CONFIG_SPACE_SIZE * PCI_MAX_FUNCTIONS_PER_DEVICE * PCI_MAX_DEVICES_PER_BUS * (get_end_bus() - get_start_bus()));
248}
249
250PhysicalAddress MMIOSegment::get_paddr()
251{
252 return m_base_addr;
253}
254
255}
256}