The Pedigree Project 0.1
GicIts.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "GicIts.h"
3#include "pedigree/kernel/LockGuard.h"
4#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
5#include "pedigree/kernel/processor/VirtualAddressSpace.h"
6
7#include <string.h>
8
9namespace {
10#if ARM64
11constexpr uintptr_t DirectMapBase = 0xffff000000000000ULL;
12constexpr uintptr_t ItsControl = 0x0000;
13constexpr uintptr_t ItsType = 0x0008;
14constexpr uintptr_t ItsCommandBase = 0x0080;
15constexpr uintptr_t ItsCommandWrite = 0x0088;
16constexpr uintptr_t ItsCommandRead = 0x0090;
17constexpr uintptr_t ItsBaser = 0x0100;
18constexpr uintptr_t RedistControl = 0x0000;
19constexpr uintptr_t RedistType = 0x0008;
20constexpr uintptr_t RedistPropertyBase = 0x0070;
21constexpr uintptr_t RedistPendingBase = 0x0078;
22constexpr uint64_t TableValid = 1ULL << 63;
23constexpr uint64_t InnerShareable = 1ULL << 10;
24constexpr uint64_t CacheableBaser = 7ULL << 59;
25constexpr uint64_t CacheableRedist = 7ULL << 7;
26constexpr size_t PageSize = 4096;
27constexpr size_t LpiTableAlignment = 65536;
28
29uint32_t read32(uintptr_t base, uintptr_t offset) {
30 uint32_t value;
31 asm volatile("ldr %w0, [%1]" : "=r"(value) : "r"(base + offset) : "memory");
32 return value;
33}
34
35uint64_t read64(uintptr_t base, uintptr_t offset) {
36 uint64_t value;
37 asm volatile("ldr %0, [%1]" : "=r"(value) : "r"(base + offset) : "memory");
38 return value;
39}
40
41void write32(uintptr_t base, uintptr_t offset, uint32_t value) {
42 asm volatile("str %w1, [%0]" : : "r"(base + offset), "r"(value) : "memory");
43}
44
45void write64(uintptr_t base, uintptr_t offset, uint64_t value) {
46 asm volatile("str %1, [%0]" : : "r"(base + offset), "r"(value) : "memory");
47}
48
49void barrier() {
50 asm volatile("dsb sy\n\tisb" : : : "memory");
51}
52#endif
53} // namespace
54
55GicIts::GicIts()
56 : m_DeviceTable("ITS device table"),
57 m_CollectionTable("ITS collection table"),
58 m_CommandQueue("ITS command queue"),
59 m_Properties("GIC LPI properties"),
60 m_Pending("GIC LPI pending"),
61 m_MappingLock(false),
62 m_CommandLock(false),
63 m_Mappings(),
64 m_Base(0),
65 m_Redistributor(0),
66 m_PhysicalBase(0),
67 m_Target(0),
68 m_CommandVirtual(nullptr),
69 m_PropertyVirtual(nullptr),
70 m_CommandWrite(0),
71 m_CommandSize(0),
72 m_DeviceLimit(0),
73 m_EventBits(0),
74 m_Ready(false) {}
75
76bool GicIts::ready() const {
77 return m_Ready;
78}
79
80uint64_t GicIts::messageAddress() const {
81 return m_Ready ? m_PhysicalBase + 0x10040 : 0;
82}
83
84bool GicIts::allocateAligned(MemoryRegion& region, size_t size, size_t alignment,
85 uint64_t& physical, uint8_t*& virtualAddress) {
86#if ARM64
87 if (!size || !alignment || (alignment & (alignment - 1)) ||
88 size > SIZE_MAX - alignment + PageSize) {
89 return false;
90 }
91 const size_t pages = (size + alignment - PageSize + PageSize - 1) / PageSize;
95 return false;
96 }
97 const uint64_t base = region.physicalAddress();
98 const size_t offset = (alignment - (base & (alignment - 1))) & (alignment - 1);
99 if (offset > region.size() || size > region.size() - offset) {
100 region.free();
101 return false;
102 }
103 physical = base + offset;
104 virtualAddress = static_cast<uint8_t*>(region.virtualAddress()) + offset;
105 memset(virtualAddress, 0, size);
106 barrier();
107 return true;
108#else
109 (void)region;
110 (void)size;
111 (void)alignment;
112 (void)physical;
113 (void)virtualAddress;
114 return false;
115#endif
116}
117
118bool GicIts::setupBaser(uint32_t type, MemoryRegion& region, size_t requiredEntries) {
119#if ARM64
120 for (size_t index = 0; index < 8; ++index) {
121 const uintptr_t offset = ItsBaser + index * 8;
122 const uint64_t original = read64(m_Base, offset);
123 if (((original >> 56) & 7) != type) {
124 continue;
125 }
126 const size_t entrySize = ((original >> 48) & 31) + 1;
127 if (requiredEntries > SIZE_MAX / entrySize) {
128 return false;
129 }
130 const size_t bytes = requiredEntries * entrySize;
131 const size_t pages = (bytes + PageSize - 1) / PageSize;
132 if (!pages || pages > 256) {
133 return false;
134 }
135 uint64_t physical = 0;
136 uint8_t* virtualAddress = nullptr;
137 if (!allocateAligned(region, pages * PageSize, PageSize, physical, virtualAddress)) {
138 return false;
139 }
140 const uint64_t value = (original & ((7ULL << 56) | (31ULL << 48))) | physical | CacheableBaser |
141 InnerShareable | TableValid | (pages - 1);
142 write64(m_Base, offset, value);
143 barrier();
144 const uint64_t actual = read64(m_Base, offset);
145 const uint64_t mask =
146 TableValid | (0x000ffffffffff000ULL) | (3ULL << 10) | (3ULL << 8) | (7ULL << 59) | 0xffULL;
147 return (actual & mask) == (value & mask);
148 }
149#else
150 (void)type;
151 (void)region;
152 (void)requiredEntries;
153#endif
154 return false;
155}
156
157bool GicIts::send(uint64_t word0, uint64_t word1, uint64_t word2) {
158#if ARM64
159 LockGuard<Spinlock> guard(m_CommandLock);
160 if (!m_CommandVirtual || !m_CommandSize) {
161 return false;
162 }
163 auto* command = reinterpret_cast<volatile uint64_t*>(m_CommandVirtual + m_CommandWrite);
164 command[0] = word0;
165 command[1] = word1;
166 command[2] = word2;
167 command[3] = 0;
168 barrier();
169 const size_t next = (m_CommandWrite + 32) % m_CommandSize;
170 write64(m_Base, ItsCommandWrite, next);
171 for (size_t wait = 0; wait < 100000; ++wait) {
172 const uint64_t reader = read64(m_Base, ItsCommandRead);
173 if (reader & 1U) {
174 return false;
175 }
176 if ((reader & (m_CommandSize - 1)) == next) {
177 m_CommandWrite = next;
178 return true;
179 }
180 asm volatile("yield");
181 }
182#else
183 (void)word0;
184 (void)word1;
185 (void)word2;
186#endif
187 return false;
188}
189
190bool GicIts::sync() {
191 return send(0x05, 0, m_Target);
192}
193
194void GicIts::releaseTables() {
195#if ARM64
196 bool quiescent = true;
197 if (m_Base) {
198 write32(m_Base, ItsControl, read32(m_Base, ItsControl) & ~1U);
199 size_t wait = 0;
200 while (wait < 100000 && !(read32(m_Base, ItsControl) & (1U << 31))) {
201 ++wait;
202 asm volatile("yield");
203 }
204 quiescent = wait < 100000;
205 }
206 if (m_Redistributor) {
207 write32(m_Redistributor, RedistControl, read32(m_Redistributor, RedistControl) & ~1U);
208 size_t wait = 0;
209 while (wait < 100000 && (read32(m_Redistributor, RedistControl) & (1U << 3))) {
210 ++wait;
211 asm volatile("yield");
212 }
213 quiescent = quiescent && wait < 100000;
214 }
215 if (!quiescent) {
216 m_Ready = false;
217 return;
218 }
219#endif
220 if (m_DeviceTable) {
221 m_DeviceTable.free();
222 }
223 if (m_CollectionTable) {
224 m_CollectionTable.free();
225 }
226 if (m_CommandQueue) {
227 m_CommandQueue.free();
228 }
229 if (m_Properties) {
230 m_Properties.free();
231 }
232 if (m_Pending) {
233 m_Pending.free();
234 }
235 m_Ready = false;
236}
237
238bool GicIts::initialise(const VirtMsiController& controller, uintptr_t redistributor,
239 uint64_t redistributorPhysical) {
240#if ARM64
241 if (m_Ready) {
242 return true;
243 }
244 if (controller.type != VirtMsiController::Type::GicV3Its || controller.size < 0x20000 ||
245 !redistributor || !redistributorPhysical || controller.base >= (64ULL << 30) ||
246 controller.base > UINT64_MAX - 0x10040) {
247 return false;
248 }
249 m_Base = DirectMapBase + controller.base;
250 m_PhysicalBase = controller.base;
251 m_Redistributor = redistributor;
252 if ((read32(m_Base, ItsControl) & 1U) || (read32(redistributor, RedistControl) & 1U) ||
253 !(read64(m_Base, ItsType) & 1U) || !(read64(redistributor, RedistType) & 1U)) {
254 return false;
255 }
256 const uint64_t typer = read64(m_Base, ItsType);
257 const uint32_t deviceBits = ((typer >> 13) & 31) + 1;
258 const uint32_t eventBits = ((typer >> 8) & 31) + 1;
259 const uintptr_t distributor = VirtDeviceTree::gicDistributorBase();
260 if (!distributor) {
261 return false;
262 }
263 const uint32_t lpiBits = ((read32(distributor, 0x4) >> 19) & 31) + 1;
264 if (lpiBits < 14) {
265 return false;
266 }
267 m_DeviceLimit = deviceBits < 16 ? 1U << deviceBits : 65536;
268 m_EventBits = eventBits;
269 const uint32_t usableLpiBits = lpiBits < 16 ? lpiBits : 16;
270
271 uint64_t propertyPhysical = 0, pendingPhysical = 0, commandPhysical = 0;
272 uint8_t* pendingVirtual = nullptr;
273 if (!allocateAligned(m_Properties, LpiTableAlignment, LpiTableAlignment, propertyPhysical,
274 m_PropertyVirtual) ||
275 !allocateAligned(m_Pending, LpiTableAlignment, LpiTableAlignment, pendingPhysical,
276 pendingVirtual)) {
277 releaseTables();
278 return false;
279 }
280 memset(m_PropertyVirtual, 0xa2, LpiTableAlignment);
281 barrier();
282
283 const uint64_t prop = propertyPhysical | CacheableRedist | InnerShareable | (usableLpiBits - 1);
284 const uint64_t pend = pendingPhysical | CacheableRedist | InnerShareable;
285 write64(redistributor, RedistPropertyBase, prop);
286 write64(redistributor, RedistPendingBase, pend);
287 barrier();
288 const uint64_t propMask = 0x000ffffffffff000ULL | (7ULL << 7) | (3ULL << 10) | 31ULL;
289 const uint64_t pendMask = 0x000fffffffff0000ULL | (7ULL << 7) | (3ULL << 10);
290 if ((read64(redistributor, RedistPropertyBase) & propMask) != (prop & propMask) ||
291 (read64(redistributor, RedistPendingBase) & pendMask) != (pend & pendMask) ||
292 !setupBaser(1, m_DeviceTable, m_DeviceLimit) || !setupBaser(4, m_CollectionTable, 1) ||
293 !allocateAligned(m_CommandQueue, PageSize, PageSize, commandPhysical, m_CommandVirtual)) {
294 releaseTables();
295 return false;
296 }
297
298 m_CommandSize = PageSize;
299 const uint64_t cbaser = commandPhysical | CacheableBaser | InnerShareable | TableValid;
300 write64(m_Base, ItsCommandBase, cbaser);
301 barrier();
302 const uint64_t cbaserMask =
303 0x000ffffffffff000ULL | (7ULL << 59) | (3ULL << 10) | TableValid | 0xffULL;
304 if ((read64(m_Base, ItsCommandBase) & cbaserMask) != (cbaser & cbaserMask)) {
305 releaseTables();
306 return false;
307 }
308 write64(m_Base, ItsCommandWrite, 0);
309 m_CommandWrite = 0;
310 m_Target = (typer & (1ULL << 19)) ? redistributorPhysical
311 : ((read64(redistributor, RedistType) >> 8) & 0xffff) << 16;
312 if (m_Target & 0xffff) {
313 releaseTables();
314 return false;
315 }
316 write32(redistributor, RedistControl, read32(redistributor, RedistControl) | 1U);
317 write32(m_Base, ItsControl, read32(m_Base, ItsControl) | 1U);
318 barrier();
319 if (!(read32(redistributor, RedistControl) & 1U) || !(read32(m_Base, ItsControl) & 1U) ||
320 !send(0x09, 0, m_Target | TableValid) || !send(0x0d, 0, 0) || !sync()) {
321 releaseTables();
322 return false;
323 }
324 m_Ready = true;
325 return true;
326#else
327 (void)controller;
328 (void)redistributor;
329 (void)redistributorPhysical;
330 return false;
331#endif
332}
333
334bool GicIts::mapDevice(size_t slot, uint32_t deviceId, uint32_t lpi) {
335 return lpi == FirstLpi + slot && mapDeviceGroup(&slot, 1, deviceId);
336}
337
338bool GicIts::mapDeviceGroup(const size_t* slots, size_t count, uint32_t deviceId) {
339#if ARM64
340 LockGuard<Spinlock> guard(m_MappingLock);
341 if (!m_Ready || !slots || !count || count > MaxMappings || deviceId >= m_DeviceLimit) {
342 return false;
343 }
344 uint32_t size = 0;
345 while ((size_t(2) << size) < count) {
346 ++size;
347 }
348 if (size >= m_EventBits) {
349 return false;
350 }
351 for (size_t i = 0; i < count; ++i) {
352 if (slots[i] >= MaxMappings || m_Mappings[slots[i]].valid) {
353 return false;
354 }
355 for (size_t j = 0; j < i; ++j) {
356 if (slots[j] == slots[i]) {
357 return false;
358 }
359 }
360 }
361 const uint64_t itt = PhysicalMemoryManager::instance().tryAllocatePage();
362 if (!itt) {
363 return false;
364 }
365 memset(reinterpret_cast<void*>(DirectMapBase + itt), 0, PageSize);
366 barrier();
367 bool mapped = send(0x08 | (uint64_t(deviceId) << 32), size, itt | TableValid);
368 for (size_t i = 0; mapped && i < count; ++i) {
369 mapped = send(0x0a | (uint64_t(deviceId) << 32), (uint64_t(FirstLpi + slots[i]) << 32) | i, 0);
370 }
371 mapped = mapped && sync();
372 if (!mapped) {
373 if (send(0x08 | (uint64_t(deviceId) << 32), 0, 0) && sync()) {
375 } else {
376 for (size_t i = 0; i < count; ++i) {
377 m_Mappings[slots[i]] = {deviceId, static_cast<uint32_t>(i), itt, true};
378 }
379 }
380 return false;
381 }
382 for (size_t i = 0; i < count; ++i) {
383 m_Mappings[slots[i]] = {deviceId, static_cast<uint32_t>(i), itt, true};
384 }
385 return true;
386#else
387 (void)slots;
388 (void)count;
389 (void)deviceId;
390 return false;
391#endif
392}
393
394bool GicIts::mappingActive(size_t slot) const {
395 LockGuard<Spinlock> guard(m_MappingLock);
396 return slot < MaxMappings && m_Mappings[slot].valid;
397}
398
399bool GicIts::unmapDevice(size_t slot) {
400#if ARM64
401 LockGuard<Spinlock> guard(m_MappingLock);
402 if (!m_Ready || slot >= MaxMappings || !m_Mappings[slot].valid) {
403 return false;
404 }
405 const Mapping mapping = m_Mappings[slot];
406 bool last = true;
407 for (size_t i = 0; i < MaxMappings; ++i) {
408 if (i != slot && m_Mappings[i].valid && m_Mappings[i].deviceId == mapping.deviceId) {
409 last = false;
410 break;
411 }
412 }
413 const bool removed = last ? send(0x08 | (uint64_t(mapping.deviceId) << 32), 0, 0)
414 : send(0x0f | (uint64_t(mapping.deviceId) << 32), mapping.eventId, 0);
415 if (!removed || !sync()) {
416 return false;
417 }
418 if (last) {
420 }
421 m_Mappings[slot] = {};
422 return true;
423#else
424 (void)slot;
425 return false;
426#endif
427}
428
429bool GicIts::setEnabled(uint32_t lpi, bool enabled) {
430#if ARM64
431 LockGuard<Spinlock> guard(m_MappingLock);
432 if (!m_Ready || lpi < FirstLpi || lpi >= FirstLpi + MaxMappings ||
433 !m_Mappings[lpi - FirstLpi].valid) {
434 return false;
435 }
436 const Mapping& mapping = m_Mappings[lpi - FirstLpi];
437 m_PropertyVirtual[lpi - FirstLpi] = 0xa2 | (enabled ? 1U : 0U);
438 barrier();
439 return send(0x0c | (uint64_t(mapping.deviceId) << 32), mapping.eventId, 0) && sync();
440#else
441 (void)lpi;
442 (void)enabled;
443#endif
444 return false;
445}
Special memory entity in the kernel's virtual address space.
void * virtualAddress() const
physical_uintptr_t physicalAddress() const
size_t size() const
static PhysicalMemoryManager & instance()
virtual void freePage(physical_uintptr_t page)=0
virtual bool allocateRegion(MemoryRegion &Region, size_t cPages, size_t pageConstraints, size_t Flags, physical_uintptr_t start=-1)=0
Definition waits.c:9