diff options
Diffstat (limited to 'src/common/host_memory.cpp')
| -rw-r--r-- | src/common/host_memory.cpp | 191 |
1 files changed, 150 insertions, 41 deletions
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index ba22595e0..3a9ea6eb4 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp | |||
| @@ -21,15 +21,18 @@ | |||
| 21 | #include <boost/icl/interval_set.hpp> | 21 | #include <boost/icl/interval_set.hpp> |
| 22 | #include <fcntl.h> | 22 | #include <fcntl.h> |
| 23 | #include <sys/mman.h> | 23 | #include <sys/mman.h> |
| 24 | #include <sys/random.h> | ||
| 24 | #include <unistd.h> | 25 | #include <unistd.h> |
| 25 | #include "common/scope_exit.h" | 26 | #include "common/scope_exit.h" |
| 26 | 27 | ||
| 27 | #endif // ^^^ Linux ^^^ | 28 | #endif // ^^^ Linux ^^^ |
| 28 | 29 | ||
| 29 | #include <mutex> | 30 | #include <mutex> |
| 31 | #include <random> | ||
| 30 | 32 | ||
| 31 | #include "common/alignment.h" | 33 | #include "common/alignment.h" |
| 32 | #include "common/assert.h" | 34 | #include "common/assert.h" |
| 35 | #include "common/free_region_manager.h" | ||
| 33 | #include "common/host_memory.h" | 36 | #include "common/host_memory.h" |
| 34 | #include "common/logging/log.h" | 37 | #include "common/logging/log.h" |
| 35 | 38 | ||
| @@ -141,7 +144,7 @@ public: | |||
| 141 | Release(); | 144 | Release(); |
| 142 | } | 145 | } |
| 143 | 146 | ||
| 144 | void Map(size_t virtual_offset, size_t host_offset, size_t length) { | 147 | void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) { |
| 145 | std::unique_lock lock{placeholder_mutex}; | 148 | std::unique_lock lock{placeholder_mutex}; |
| 146 | if (!IsNiechePlaceholder(virtual_offset, length)) { | 149 | if (!IsNiechePlaceholder(virtual_offset, length)) { |
| 147 | Split(virtual_offset, length); | 150 | Split(virtual_offset, length); |
| @@ -160,7 +163,7 @@ public: | |||
| 160 | } | 163 | } |
| 161 | } | 164 | } |
| 162 | 165 | ||
| 163 | void Protect(size_t virtual_offset, size_t length, bool read, bool write) { | 166 | void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) { |
| 164 | DWORD new_flags{}; | 167 | DWORD new_flags{}; |
| 165 | if (read && write) { | 168 | if (read && write) { |
| 166 | new_flags = PAGE_READWRITE; | 169 | new_flags = PAGE_READWRITE; |
| @@ -186,6 +189,11 @@ public: | |||
| 186 | } | 189 | } |
| 187 | } | 190 | } |
| 188 | 191 | ||
| 192 | void EnableDirectMappedAddress() { | ||
| 193 | // TODO | ||
| 194 | UNREACHABLE(); | ||
| 195 | } | ||
| 196 | |||
| 189 | const size_t backing_size; ///< Size of the backing memory in bytes | 197 | const size_t backing_size; ///< Size of the backing memory in bytes |
| 190 | const size_t virtual_size; ///< Size of the virtual address placeholder in bytes | 198 | const size_t virtual_size; ///< Size of the virtual address placeholder in bytes |
| 191 | 199 | ||
| @@ -353,6 +361,55 @@ private: | |||
| 353 | 361 | ||
| 354 | #elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv | 362 | #elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv |
| 355 | 363 | ||
| 364 | #ifdef ARCHITECTURE_arm64 | ||
| 365 | |||
| 366 | static void* ChooseVirtualBase(size_t virtual_size) { | ||
| 367 | constexpr uintptr_t Map39BitSize = (1ULL << 39); | ||
| 368 | constexpr uintptr_t Map36BitSize = (1ULL << 36); | ||
| 369 | |||
| 370 | // This is not a cryptographic application, we just want something random. | ||
| 371 | std::mt19937_64 rng; | ||
| 372 | |||
| 373 | // We want to ensure we are allocating at an address aligned to the L2 block size. | ||
| 374 | // For Qualcomm devices, we must also allocate memory above 36 bits. | ||
| 375 | const size_t lower = Map36BitSize / HugePageSize; | ||
| 376 | const size_t upper = (Map39BitSize - virtual_size) / HugePageSize; | ||
| 377 | const size_t range = upper - lower; | ||
| 378 | |||
| 379 | // Try up to 64 times to allocate memory at random addresses in the range. | ||
| 380 | for (int i = 0; i < 64; i++) { | ||
| 381 | // Calculate a possible location. | ||
| 382 | uintptr_t hint_address = ((rng() % range) + lower) * HugePageSize; | ||
| 383 | |||
| 384 | // Try to map. | ||
| 385 | // Note: we may be able to take advantage of MAP_FIXED_NOREPLACE here. | ||
| 386 | void* map_pointer = | ||
| 387 | mmap(reinterpret_cast<void*>(hint_address), virtual_size, PROT_READ | PROT_WRITE, | ||
| 388 | MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); | ||
| 389 | |||
| 390 | // If we successfully mapped, we're done. | ||
| 391 | if (reinterpret_cast<uintptr_t>(map_pointer) == hint_address) { | ||
| 392 | return map_pointer; | ||
| 393 | } | ||
| 394 | |||
| 395 | // Unmap if necessary, and try again. | ||
| 396 | if (map_pointer != MAP_FAILED) { | ||
| 397 | munmap(map_pointer, virtual_size); | ||
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | return MAP_FAILED; | ||
| 402 | } | ||
| 403 | |||
| 404 | #else | ||
| 405 | |||
| 406 | static void* ChooseVirtualBase(size_t virtual_size) { | ||
| 407 | return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, | ||
| 408 | MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); | ||
| 409 | } | ||
| 410 | |||
| 411 | #endif | ||
| 412 | |||
| 356 | class HostMemory::Impl { | 413 | class HostMemory::Impl { |
| 357 | public: | 414 | public: |
| 358 | explicit Impl(size_t backing_size_, size_t virtual_size_) | 415 | explicit Impl(size_t backing_size_, size_t virtual_size_) |
| @@ -415,8 +472,7 @@ public: | |||
| 415 | } | 472 | } |
| 416 | } | 473 | } |
| 417 | #else | 474 | #else |
| 418 | virtual_base = static_cast<u8*>(mmap(nullptr, virtual_size, PROT_NONE, | 475 | virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size)); |
| 419 | MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0)); | ||
| 420 | if (virtual_base == MAP_FAILED) { | 476 | if (virtual_base == MAP_FAILED) { |
| 421 | LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno)); | 477 | LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno)); |
| 422 | throw std::bad_alloc{}; | 478 | throw std::bad_alloc{}; |
| @@ -424,7 +480,7 @@ public: | |||
| 424 | madvise(virtual_base, virtual_size, MADV_HUGEPAGE); | 480 | madvise(virtual_base, virtual_size, MADV_HUGEPAGE); |
| 425 | #endif | 481 | #endif |
| 426 | 482 | ||
| 427 | placeholders.add({0, virtual_size}); | 483 | free_manager.SetAddressSpace(virtual_base, virtual_size); |
| 428 | good = true; | 484 | good = true; |
| 429 | } | 485 | } |
| 430 | 486 | ||
| @@ -432,14 +488,29 @@ public: | |||
| 432 | Release(); | 488 | Release(); |
| 433 | } | 489 | } |
| 434 | 490 | ||
| 435 | void Map(size_t virtual_offset, size_t host_offset, size_t length) { | 491 | void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) { |
| 436 | { | 492 | // Intersect the range with our address space. |
| 437 | std::scoped_lock lock{placeholder_mutex}; | 493 | AdjustMap(&virtual_offset, &length); |
| 438 | placeholders.subtract({virtual_offset, virtual_offset + length}); | 494 | |
| 495 | // We are removing a placeholder. | ||
| 496 | free_manager.AllocateBlock(virtual_base + virtual_offset, length); | ||
| 497 | |||
| 498 | // Deduce mapping protection flags. | ||
| 499 | int flags = PROT_NONE; | ||
| 500 | if (True(perms & MemoryPermission::Read)) { | ||
| 501 | flags |= PROT_READ; | ||
| 439 | } | 502 | } |
| 503 | if (True(perms & MemoryPermission::Write)) { | ||
| 504 | flags |= PROT_WRITE; | ||
| 505 | } | ||
| 506 | #ifdef ARCHITECTURE_arm64 | ||
| 507 | if (True(perms & MemoryPermission::Execute)) { | ||
| 508 | flags |= PROT_EXEC; | ||
| 509 | } | ||
| 510 | #endif | ||
| 440 | 511 | ||
| 441 | void* ret = mmap(virtual_base + virtual_offset, length, PROT_READ | PROT_WRITE, | 512 | void* ret = mmap(virtual_base + virtual_offset, length, flags, MAP_SHARED | MAP_FIXED, fd, |
| 442 | MAP_SHARED | MAP_FIXED, fd, host_offset); | 513 | host_offset); |
| 443 | ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno)); | 514 | ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno)); |
| 444 | } | 515 | } |
| 445 | 516 | ||
| @@ -447,47 +518,54 @@ public: | |||
| 447 | // The method name is wrong. We're still talking about the virtual range. | 518 | // The method name is wrong. We're still talking about the virtual range. |
| 448 | // We don't want to unmap, we want to reserve this memory. | 519 | // We don't want to unmap, we want to reserve this memory. |
| 449 | 520 | ||
| 450 | { | 521 | // Intersect the range with our address space. |
| 451 | std::scoped_lock lock{placeholder_mutex}; | 522 | AdjustMap(&virtual_offset, &length); |
| 452 | auto it = placeholders.find({virtual_offset - 1, virtual_offset + length + 1}); | ||
| 453 | 523 | ||
| 454 | if (it != placeholders.end()) { | 524 | // Merge with any adjacent placeholder mappings. |
| 455 | size_t prev_upper = virtual_offset + length; | 525 | auto [merged_pointer, merged_size] = |
| 456 | virtual_offset = std::min(virtual_offset, it->lower()); | 526 | free_manager.FreeBlock(virtual_base + virtual_offset, length); |
| 457 | length = std::max(it->upper(), prev_upper) - virtual_offset; | ||
| 458 | } | ||
| 459 | |||
| 460 | placeholders.add({virtual_offset, virtual_offset + length}); | ||
| 461 | } | ||
| 462 | 527 | ||
| 463 | void* ret = mmap(virtual_base + virtual_offset, length, PROT_NONE, | 528 | void* ret = mmap(merged_pointer, merged_size, PROT_NONE, |
| 464 | MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); | 529 | MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); |
| 465 | ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno)); | 530 | ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno)); |
| 466 | } | 531 | } |
| 467 | 532 | ||
| 468 | void Protect(size_t virtual_offset, size_t length, bool read, bool write) { | 533 | void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) { |
| 469 | int flags = 0; | 534 | // Intersect the range with our address space. |
| 535 | AdjustMap(&virtual_offset, &length); | ||
| 536 | |||
| 537 | int flags = PROT_NONE; | ||
| 470 | if (read) { | 538 | if (read) { |
| 471 | flags |= PROT_READ; | 539 | flags |= PROT_READ; |
| 472 | } | 540 | } |
| 473 | if (write) { | 541 | if (write) { |
| 474 | flags |= PROT_WRITE; | 542 | flags |= PROT_WRITE; |
| 475 | } | 543 | } |
| 544 | #ifdef HAS_NCE | ||
| 545 | if (execute) { | ||
| 546 | flags |= PROT_EXEC; | ||
| 547 | } | ||
| 548 | #endif | ||
| 476 | int ret = mprotect(virtual_base + virtual_offset, length, flags); | 549 | int ret = mprotect(virtual_base + virtual_offset, length, flags); |
| 477 | ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno)); | 550 | ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno)); |
| 478 | } | 551 | } |
| 479 | 552 | ||
| 553 | void EnableDirectMappedAddress() { | ||
| 554 | virtual_base = nullptr; | ||
| 555 | } | ||
| 556 | |||
| 480 | const size_t backing_size; ///< Size of the backing memory in bytes | 557 | const size_t backing_size; ///< Size of the backing memory in bytes |
| 481 | const size_t virtual_size; ///< Size of the virtual address placeholder in bytes | 558 | const size_t virtual_size; ///< Size of the virtual address placeholder in bytes |
| 482 | 559 | ||
| 483 | u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)}; | 560 | u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)}; |
| 484 | u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)}; | 561 | u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)}; |
| 562 | u8* virtual_map_base{reinterpret_cast<u8*>(MAP_FAILED)}; | ||
| 485 | 563 | ||
| 486 | private: | 564 | private: |
| 487 | /// Release all resources in the object | 565 | /// Release all resources in the object |
| 488 | void Release() { | 566 | void Release() { |
| 489 | if (virtual_base != MAP_FAILED) { | 567 | if (virtual_map_base != MAP_FAILED) { |
| 490 | int ret = munmap(virtual_base, virtual_size); | 568 | int ret = munmap(virtual_map_base, virtual_size); |
| 491 | ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno)); | 569 | ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno)); |
| 492 | } | 570 | } |
| 493 | 571 | ||
| @@ -502,10 +580,29 @@ private: | |||
| 502 | } | 580 | } |
| 503 | } | 581 | } |
| 504 | 582 | ||
| 505 | int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create | 583 | void AdjustMap(size_t* virtual_offset, size_t* length) { |
| 584 | if (virtual_base != nullptr) { | ||
| 585 | return; | ||
| 586 | } | ||
| 587 | |||
| 588 | // If we are direct mapped, we want to make sure we are operating on a region | ||
| 589 | // that is in range of our virtual mapping. | ||
| 590 | size_t intended_start = *virtual_offset; | ||
| 591 | size_t intended_end = intended_start + *length; | ||
| 592 | size_t address_space_start = reinterpret_cast<size_t>(virtual_map_base); | ||
| 593 | size_t address_space_end = address_space_start + virtual_size; | ||
| 594 | |||
| 595 | if (address_space_start > intended_end || intended_start > address_space_end) { | ||
| 596 | *virtual_offset = 0; | ||
| 597 | *length = 0; | ||
| 598 | } else { | ||
| 599 | *virtual_offset = std::max(intended_start, address_space_start); | ||
| 600 | *length = std::min(intended_end, address_space_end) - *virtual_offset; | ||
| 601 | } | ||
| 602 | } | ||
| 506 | 603 | ||
| 507 | boost::icl::interval_set<size_t> placeholders; ///< Mapped placeholders | 604 | int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create |
| 508 | std::mutex placeholder_mutex; ///< Mutex for placeholders | 605 | FreeRegionManager free_manager{}; |
| 509 | }; | 606 | }; |
| 510 | 607 | ||
| 511 | #else // ^^^ Linux ^^^ vvv Generic vvv | 608 | #else // ^^^ Linux ^^^ vvv Generic vvv |
| @@ -518,11 +615,13 @@ public: | |||
| 518 | throw std::bad_alloc{}; | 615 | throw std::bad_alloc{}; |
| 519 | } | 616 | } |
| 520 | 617 | ||
| 521 | void Map(size_t virtual_offset, size_t host_offset, size_t length) {} | 618 | void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perm) {} |
| 522 | 619 | ||
| 523 | void Unmap(size_t virtual_offset, size_t length) {} | 620 | void Unmap(size_t virtual_offset, size_t length) {} |
| 524 | 621 | ||
| 525 | void Protect(size_t virtual_offset, size_t length, bool read, bool write) {} | 622 | void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {} |
| 623 | |||
| 624 | void EnableDirectMappedAddress() {} | ||
| 526 | 625 | ||
| 527 | u8* backing_base{nullptr}; | 626 | u8* backing_base{nullptr}; |
| 528 | u8* virtual_base{nullptr}; | 627 | u8* virtual_base{nullptr}; |
| @@ -535,15 +634,16 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) | |||
| 535 | try { | 634 | try { |
| 536 | // Try to allocate a fastmem arena. | 635 | // Try to allocate a fastmem arena. |
| 537 | // The implementation will fail with std::bad_alloc on errors. | 636 | // The implementation will fail with std::bad_alloc on errors. |
| 538 | impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), | 637 | impl = |
| 539 | AlignUp(virtual_size, PageAlignment) + | 638 | std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), |
| 540 | 3 * HugePageSize); | 639 | AlignUp(virtual_size, PageAlignment) + HugePageSize); |
| 541 | backing_base = impl->backing_base; | 640 | backing_base = impl->backing_base; |
| 542 | virtual_base = impl->virtual_base; | 641 | virtual_base = impl->virtual_base; |
| 543 | 642 | ||
| 544 | if (virtual_base) { | 643 | if (virtual_base) { |
| 545 | virtual_base += 2 * HugePageSize - 1; | 644 | // Ensure the virtual base is aligned to the L2 block size. |
| 546 | virtual_base -= reinterpret_cast<size_t>(virtual_base) & (HugePageSize - 1); | 645 | virtual_base = reinterpret_cast<u8*>( |
| 646 | Common::AlignUp(reinterpret_cast<uintptr_t>(virtual_base), HugePageSize)); | ||
| 547 | virtual_base_offset = virtual_base - impl->virtual_base; | 647 | virtual_base_offset = virtual_base - impl->virtual_base; |
| 548 | } | 648 | } |
| 549 | 649 | ||
| @@ -562,7 +662,8 @@ HostMemory::HostMemory(HostMemory&&) noexcept = default; | |||
| 562 | 662 | ||
| 563 | HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default; | 663 | HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default; |
| 564 | 664 | ||
| 565 | void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length) { | 665 | void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, |
| 666 | MemoryPermission perms) { | ||
| 566 | ASSERT(virtual_offset % PageAlignment == 0); | 667 | ASSERT(virtual_offset % PageAlignment == 0); |
| 567 | ASSERT(host_offset % PageAlignment == 0); | 668 | ASSERT(host_offset % PageAlignment == 0); |
| 568 | ASSERT(length % PageAlignment == 0); | 669 | ASSERT(length % PageAlignment == 0); |
| @@ -571,7 +672,7 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length) { | |||
| 571 | if (length == 0 || !virtual_base || !impl) { | 672 | if (length == 0 || !virtual_base || !impl) { |
| 572 | return; | 673 | return; |
| 573 | } | 674 | } |
| 574 | impl->Map(virtual_offset + virtual_base_offset, host_offset, length); | 675 | impl->Map(virtual_offset + virtual_base_offset, host_offset, length, perms); |
| 575 | } | 676 | } |
| 576 | 677 | ||
| 577 | void HostMemory::Unmap(size_t virtual_offset, size_t length) { | 678 | void HostMemory::Unmap(size_t virtual_offset, size_t length) { |
| @@ -584,14 +685,22 @@ void HostMemory::Unmap(size_t virtual_offset, size_t length) { | |||
| 584 | impl->Unmap(virtual_offset + virtual_base_offset, length); | 685 | impl->Unmap(virtual_offset + virtual_base_offset, length); |
| 585 | } | 686 | } |
| 586 | 687 | ||
| 587 | void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write) { | 688 | void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write, |
| 689 | bool execute) { | ||
| 588 | ASSERT(virtual_offset % PageAlignment == 0); | 690 | ASSERT(virtual_offset % PageAlignment == 0); |
| 589 | ASSERT(length % PageAlignment == 0); | 691 | ASSERT(length % PageAlignment == 0); |
| 590 | ASSERT(virtual_offset + length <= virtual_size); | 692 | ASSERT(virtual_offset + length <= virtual_size); |
| 591 | if (length == 0 || !virtual_base || !impl) { | 693 | if (length == 0 || !virtual_base || !impl) { |
| 592 | return; | 694 | return; |
| 593 | } | 695 | } |
| 594 | impl->Protect(virtual_offset + virtual_base_offset, length, read, write); | 696 | impl->Protect(virtual_offset + virtual_base_offset, length, read, write, execute); |
| 697 | } | ||
| 698 | |||
| 699 | void HostMemory::EnableDirectMappedAddress() { | ||
| 700 | if (impl) { | ||
| 701 | impl->EnableDirectMappedAddress(); | ||
| 702 | virtual_size += reinterpret_cast<uintptr_t>(virtual_base); | ||
| 703 | } | ||
| 595 | } | 704 | } |
| 596 | 705 | ||
| 597 | } // namespace Common | 706 | } // namespace Common |